Tuesday, November 10, 2015

The Submission Script (SubScript.sh)

Purpose:
This shell script is meant to submit the Gaussian jobs to the head node of the server and then have these jobs distributed. This is done for the directory in which the script is called and the results from the calculations are then sent back to the this directory. A script was used because this was the easiest way to submit multiple files for calculation without having to worry about scheduling.

The Script: (Written in Bash)
#! /bin/bash
#
#$ -cwd
#$ -j y
#$ -S /bin/bash
#$ -m abes
#$ -pe mpich 2
#$ -notify
#

# Necessary variables

. /share/apps/bin/bashrc
. /share/apps/bin/an_functions.sh

# Gaussian 09
export g09root="/share/apps/gaussian"
. $g09root/g09/bsd/g09.profile

#. $g09root/g09/bsd/g09.login

# Folder where the files are located
export INIT_DIR="$PWD"

# Name of the Gaussian 09 input file
export INAME="Gaussian03"
export ARRAY_JOB=""

# Prepare to run Gaussian 09
## changed by hhe

#export GAUSS_SCRDIR="/misc/hhe1"
export GAUSS_LFLAGS=' -vv -opt "Tsnet.Node.lindarsharg: ssh"'
LINDAWORKERS=$(cat $PE_HOSTFILE | grep -v "catch_rsh" | awk -F '.' '{ print $1}' | tr '\n' ',' | sed 's/,$//')

# Calculation specific information
export LOCATION=`hostname | awk -F '.' '{print $1}'`
cat << EndOfFile > $INIT_DIR/job_info.${JOB_ID}${ARRAY_JOB}

  Job ID               : $JOB_ID
  Username             : $USER
  Primary group        : hhe-users
  Login node           : $SGE_O_HOST
  Working directory    : $PWD
  Program              : Gaussian 09 (parallel)
  Input file           : t
  Exclusive access     : No
  Array job            : No
  Task ID range        : Not applicable
  Dependent job ID     : None specified
  SMS notification     : No
  # of hosts           : $NHOSTS
  # of processors      : $NSLOTS
  Parent node          : $LOCATION
  Worker nodes         : `cat $TMP/machines | sed q`
`cat $TMP/machines | sed '1d' | sed 's/^/                         /'`
  Job submission time  : `sge_jst $JOB_ID `
  Job start time       : `date -R`
EndOfFile

# Start the timer
TIME_START=$(date +%s)
cat << EndOfFile > $INIT_DIR/Status
Running
EndOfFile
chmod 755 Status
# Prepend input deck with necessary information and run
# Gaussian 09 (parallel)

#changed hhe
 $g09root/g09/g09 < ${INAME}.com > ${INAME}_NP${NSLOTS}${ARRAY_JOB}.log

# End the timer

TIME_END=$(date +%s)

# Delete the core* files
rm -f ${INIT_DIR}/core*
rm -f ${INIT_DIR}/g09.sh.o${JOB_ID}${ARRAY_JOB}


# Calculate time difference
TIME_TOTAL=`time2dhms $(( $TIME_END - $TIME_START ))`

cat << EndOfFile >> $INIT_DIR/job_info.${JOB_ID}${ARRAY_JOB}
  Job end time         : `date -R`
  Total run time       : $TIME_TOTAL

EndOfFile
rm -f ${INIT_DIR}/g09.sh.po${JOB_ID}${ARRAY_JOB}

rm -f ${INIT_DIR}/Status

#cat << EndOfFile > $INIT_DIR/Status
#Completed
#EndOfFile

Monday, June 15, 2015

Gaussian09 Submission Successes and Failures

Improvements From Previous Code:
The previous version of the code used to create the directories for the individual images for the NEB algorithm suffered in that it was somewhat overly restrictive for any potential user. That problem was partially addressed by making it so that the program only starts to copy the REACTANT and PRODUCT files from after the Route Card. The Route Card starts with the '#' in any Gaussian input file. Other suggestions for how to make the program easier in terms of input are of course welcome.

Current Issues
The bulk of this post is concerning some of the current issues of the program which are proving particularly difficult to resolve. The main problem thus far centers around the submission and execution of the Gaussian09 input files. All information presented here is from the same program which was run once producing the errors in question. This run is to be taken as representative of the issues currently facing the program.

In simplest terms the issue is that the submission of Gaussian files is producing inconsistent results. Sometimes when the files in the Image directories are submitted automatically they execute normally and produce the desired results. The other, and more common situation, is that Gaussian experiences an "Error termination" claiming that it could now find the route card.

The Code:
...
...
(This is a thread, I created it to automatically submit all the jobs in each directory after all the initial files have been created)
void *SubmitJob(void *threadID)
{
    long tID;
    int k;
    int pass;
    k = 0;
    tID = (long)threadID;
    pass = tID;
    int c;
   
    char ImageArray[pass][50];
    char WrittingArray[7][100];
    c = 0;
    (creates relevant names for the various directories)
    while(c < pass)
    {
        sprintf(ImageArray[c], "Image_%d", c + 1);
        c++;
    }
    c = 0;
   
    while(c < pass)
    {
        k = chdir(ImageArray[c]);
        if(k != 0)
        {
            printf("Directory Change Failed!");
        }
        else
        {
           (This is the critical, and troublesome step, where the Gaussian job is submitted in its directory)
            system("qsub g09.sh");
            printf("Job Submitted \n");
            sleep(1);
        }
        c++;
        k = chdir("../");
    }

    pthread_exit(NULL);
}
...
...
(Code is almost entirely the same as earlier here, up until after the creation of the files. This is where counters are all reset and the bash script g09.sh is copied to each directory for the submission process.)
//Reset all counters
    j = 0;
    g = 0;
    c = 0;
    c = 0;
    char CommandArray[3+i][100];
    sprintf(CommandArray[0], "g03 <Gaussian03.com> output.log");
    while(c < i)
    {
        sprintf(CommandArray[c], "cp g09.sh Image_%d", c+1);
        system(CommandArray[c]);
        c++;
    }
    c = 0;
    pthread_t threads[i];
    int rc;
    long t;
    t = i;
    (Here is where the call to the thread is made to start going through directories to submit the jobs)
    rc = pthread_create(&threads[t], NULL, SubmitJob, (void *)t);
    if (rc)
    {
        printf("ERROR; return code from pthread_create() is %d\n", rc);
    }
   
    c = 0;
    pthread_exit(NULL);
    return 0;

The Bash Script:
I have altered the original g09.sh script slightly to add a file to the output called "Status." It is meant to be easy to read by program by having the first letter be either 'I' or 'C'. This is a nice switch statement for the program when reading through submitted directories to check and see if the job has been completed yet. Although, I might have to change its current implementation so that the program doesn't accidentally try and read the file while it is being written to.

#! /bin/bash
#
#$ -cwd
#$ -j y
#$ -S /bin/bash
#$ -m abes
#$ -pe mpich 2
#$ -notify
#


# Necessary variables
. /share/apps/bin/bashrc
. /share/apps/bin/an_functions.sh

# Gaussian 09
export g09root="/share/apps/gaussian"
. $g09root/g09/bsd/g09.profile
## added by hhe
#. $g09root/g09/bsd/g09.login


# Folder where the files are located
export INIT_DIR="$PWD"

# Name of the Gaussian 09 input file
export INAME="Gaussian03"
export ARRAY_JOB=""

# Prepare to run Gaussian 09
## changed by hhe
#export GAUSS_SCRDIR="/misc/hhe1"

export GAUSS_LFLAGS=' -vv -opt "Tsnet.Node.lindarsharg: ssh"'
LINDAWORKERS=$(cat $PE_HOSTFILE | grep -v "catch_rsh" | awk -F '.' '{ print $1}' | tr '\n' ',' | sed 's/,$//')

# Calculation specific information
export LOCATION=`hostname | awk -F '.' '{print $1}'`
cat << EndOfFile > $INIT_DIR/job_info.${JOB_ID}${ARRAY_JOB}

  Job ID               : $JOB_ID
  Username             : $USER
  Primary group        : hhe-users
  Login node           : $SGE_O_HOST
  Working directory    : $PWD
  Program              : Gaussian 09 (parallel)
  Input file           : t
  Exclusive access     : No
  Array job            : No
  Task ID range        : Not applicable
  Dependent job ID     : None specified
  SMS notification     : No
  # of hosts           : $NHOSTS
  # of processors      : $NSLOTS
  Parent node          : $LOCATION
  Worker nodes         : `cat $TMP/machines | sed q`
`cat $TMP/machines | sed '1d' | sed 's/^/                         /'`
  Job submission time  : `sge_jst $JOB_ID `
  Job start time       : `date -R`
EndOfFile

# Start the timer
TIME_START=$(date +%s)
(My addition to the code)
cat << EndOfFile > $INIT_DIR/Status
Incomplete
EndOfFile
# Prepend input deck with necessary information and run
# Gaussian 09 (parallel)
## commented by hhe
#( sed -i '/%chk=/d' ${INAME}${ARRAY_JOB}.com; echo %NProcShared=${NSLOTS}; echo %LindaWorkers=${LINDAWORKERS}; echo %chk=${INAME}${ARRAY_JOB}.chk; cat ${INAME}${ARRAY_JOB}.com ) | $g09root/g09/g09 >&  ${INAME}_NP${NSLOTS}${ARRAY_JOB}.log
#changed hhe

 $g09root/g09/g09 < ${INAME}.com > ${INAME}_NP${NSLOTS}${ARRAY_JOB}.log

# End the timer
TIME_END=$(date +%s)

# Delete the core* files
rm -f ${INIT_DIR}/core*
rm -f ${INIT_DIR}/g09.sh.o${JOB_ID}${ARRAY_JOB}


# Calculate time difference
TIME_TOTAL=`time2dhms $(( $TIME_END - $TIME_START ))`

cat << EndOfFile >> $INIT_DIR/job_info.${JOB_ID}${ARRAY_JOB}
  Job end time         : `date -R`
  Total run time       : $TIME_TOTAL

EndOfFile
rm -f ${INIT_DIR}/g09.sh.po${JOB_ID}${ARRAY_JOB}

rm -f ${INIT_DIR}/Status
(Status file is removed and then replaced with a new one with the word "Completed" inside)
cat << EndOfFile > $INIT_DIR/Status
Completed
EndOfFile

Reuslts
After compiling the code using gcc -pthread -o (since the program uses threads) I obtained the following results:
  • Image_1:
    • Termination Status: Error termination (Route card not found)
  • Image_2:
    •  Termination Status: Error termination (Route card not found)
  • Image_3:
    • Termination Status: Error termination (Route card not found) 
  • Image_4:
    • Termination Status: Error termination (Route card not found) 
  • Image_5:
    • Termination Status: Normal termination
  • Image_6:
    •  Termination Status: Normal termination
  • Image_7:
    •  Termination Status: Normal termination
  • Image_8:
    • Termination Status: Normal termination
  • Image_9:
    • Termination Status: Normal termination
  • Image_10:
    • Termination Status: Normal termination
Herein lies the problem then. If the error termination were consistent I could narrow down the problem of the submission process to my own written code. However, it seems that my program is not the cause since in behaving the same way each time it has managed to receive half the files with an error termination and the other half with normal termination. In addition, if I go into any one of these directories and manually and enter "qsub g09.sh" Gaussian will return a normal termination.

This leads me to the conclusion that there must be an issue in communicating the files to Gaussian in either the 'qsub' or 'g09.sh' process. However, I am not terribly familiar with either one of these commands. Perhaps, there is a work around which I am not aware of. I hope to try and find some sort of fix to this problem in the next week. If you have any comments or suggestions please let me know. Thank you!

Sunday, May 31, 2015

Code Sample 1

Directory and Intermediate Image Maker


Presented here are my first fruits. This set of code was written using Notepad++ and compiled/run in MinGW. This code has the following abilities:
  • Generate a user specified number of intermediate images (directories)
  • Generate a relevant set of atomic coordinates for each image in the form of a Gaussian submission file. This is done using a linear interpolation at the moment so it's nothing all that great in my opinion.
The Code: (to look at detail I recommend copying this into Notepad++ and setting the language option to C.)


#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


//This program is meant to demonstrate the creation of a user specified number of directories
//with a file being written in each directory. Ultimately, this program will form the base of
//the directory and file creation in the NEB program, allowing each image to be contained in
//its own directory.


int main(void)
{
    //Here the variables for the program are declared. They all function as either counters
    //or switches. Several file pointers are declared as well, these are used later one for
    //keeping track of which files are in use.

   
    int i; //'i' is for image, this is the image counter
    int k;
    FILE* fp;
    FILE *fp1 = NULL;
    FILE *fp2 = NULL;
    int j;
    int number_of_atoms;
    float temp; //this is used to transfer the integer value of 'i' to a floating point number
    int c;
    c = 1;
    int q = 0;
   
    //Prompt the user for how many images they would like the NEB algorithm to use. This then
    //creates the desired number of directories.

    printf("Please enter the number of images you would like use: ");
    scanf("%d", &i);

    //set temp equal to i. This will become important later on when determining the slope
    //for the linear interpolation between points.
    temp = i;

   
    //Open the REACTANT file.
    FILE* myfile = fopen("REACTANT.txt", "r");
    //Another counter, this one used for helping to determine the number of lines in the REACTANT.
    int ch;
    int number_of_lines = 0;
   
    do
    {
        ch = fgetc(myfile);
        if(ch == '\n')
            number_of_lines++;
    } while (ch != EOF);
   
     //last line doesn't end with a new line!
     //but there has to be a line at least before the last line

    if(ch != '\n' && number_of_lines != 0)
        number_of_lines++;
   
    fclose(myfile);
   
    printf("number of lines in REACTANT.txt = %d \n", number_of_lines);
   
    //The number of atoms in the REACTANT file are calculated here.
    number_of_atoms = number_of_lines - 6;
   
    //The position arrays for the atoms are declared here since declaring them earlier wouldn't
    //have taken into account the unknown size of the file.

    float xyz1[number_of_atoms][3];
    float xyz2[number_of_atoms][3];
    float slope[number_of_atoms][3];
    //The Interpolation Array has to be big enough to store 3 coordinate for each atom in each image.
    //Hence the calculation and its inevitable large size.

    float InterpArray[number_of_atoms*i][3];
   
    //These are the input strings used to hold the information in both REACTANT and  PRODUCT
    //files as a series of strings.
    char inputString1[(4*number_of_atoms)+10][100];
    char inputString2[(4*number_of_atoms)+10][100];
   
    //This for loop reads the REACTANT file into the first input string.
    fp1 = fopen("REACTANT.txt", "r");
    for(i = 1; i < ((4*number_of_atoms)+10); i++)
    {
        fscanf(fp1, "%s", &inputString1[i]);
    }
   
    //This for loop does so for the PRODUCT file and the second input string.
    fp2 = fopen("PRODUCT.txt", "r");
    for(i = 1; i < ((4*number_of_atoms)+10); i++)
    {
        fscanf(fp2, "%s", &inputString2[i]);
    }
   
    //Reset counter values
    k = 0;
    i=0;
    //This fills in the xzy arrays with the atomic coordinates from the input strings.
    while(k < number_of_atoms)

    {
        //IMPORTANT!!! The 9 here has its value because that is where the coordinates start on
        //the input string. If the number of arguments change in the input file, this number
        //and the numbers below it must change as well. This will be changed in the final
        //code since it is assumed that the NEB algorithm will only require 'sp' calculations.

        xyz1[k][0] = atof(inputString1[i+9]);
        xyz1[k][1] = atof(inputString1[i+10]);
        xyz1[k][2] = atof(inputString1[i+11]);
       
        xyz2[k][0] = atof(inputString2[i+9]);
        xyz2[k][1] = atof(inputString2[i+10]);
        xyz2[k][2] = atof(inputString2[i+11]);
       
        i+=4;
        k++;
    }
    k = 0;
    //Calculate the slope for each atom, this is the linear interpolation bit.
    while(k < number_of_atoms)
    {
        slope[k][0] = (xyz2[k][0] - xyz1[k][0])/(temp+1);
        slope[k][1] = (xyz2[k][1] - xyz1[k][1])/(temp+1);
        slope[k][2] = (xyz2[k][2] - xyz1[k][2])/(temp+1);
        k++;
    }   
   
    i = temp;
    int g;
    //Calculate the intermediate positions for all of the images and their corresponding atoms.
    while(q < i*number_of_atoms)
    {
        g = 0;
        while(g < number_of_atoms)
        {
            InterpArray[q+g][0] = ((c*slope[g][0]) + xyz1[g][0]);
            InterpArray[q+g][1] = ((c*slope[g][1]) + xyz1[g][1]);
            InterpArray[q+g][2] = ((c*slope[g][2]) + xyz1[g][2]);
            g++;
        }   
        c++;
        q+=number_of_atoms;
    }   
   
    //Create the character array which will be used to store the names of the needed directories.
    //In the future it might be a good idea to take the floor/log of the number of images to calculate
    //the necessary length of each row. This would mean that the program could use an unlimited
    //number of images.

    char ImageArray[i][50];
    char WrittingArray[7][100];
    c = 0;
   
    //Here, the directories are named and stored in the array 'ImageArray.'
    while(c < i)
    {
        sprintf(ImageArray[c], "Image_%d", c + 1);
        c++;
    }

    //Reset the value of c. This is so it can be used in the next while loop.
    c = 0;
   
    //The directories are created here. 'i' directories are created as specified by the user.
    //They are produced using the 'mkdir' command which then passes its result value to 'j.'
    //This value is used to flag if the command executed successfully or not.

    while(c < i)
    {
        //Invocation of mkdir, with appropriate directory name being passed to it.
        j = mkdir(ImageArray[c]);
   
        //Check if directory creation was successful.
        if(j != 0)
        {
            printf("Directory Creation Failed For Image: %d", c);
        }
       
        //Add to c, moving to the next directory to be created.
        c++;
    }

    //Reset value of c to zero.
    c = 0;
   
    //This section of code switches between the newly created directories and adds a file to each
    //one.

    j = 0;
    while(c < i)
    {
        //Here, the program switches its working directory. This is done in the while loop, switching
        //to a new directory until the end of the images is reached.

        k = chdir(ImageArray[c]);
        //Check that the directory switch occurred.
        if(k != 0)
        {
            printf("Directory Change Failed!");
        }
        //Create the test file in the new directory.
        else
        {
            fp = fopen("TestFile.txt", "w");
            if(!fp)
            {
                printf("File Creation Failed");
            }
           
            //Here we write into the Writing Array and subsequently write the information to the
            //new file within the image directory.

            sprintf(WrittingArray[0], "%s %s %s %s \n", inputString1[1], inputString1[2],
            inputString1[3], inputString1[4]);
            sprintf(WrittingArray[1], "\n");
            sprintf(WrittingArray[2], "%s \n", inputString1[5]);
            sprintf(WrittingArray[3], "\n");
            sprintf(WrittingArray[4], "%s %s \n", inputString1[6], inputString1[7]);
            sprintf(WrittingArray[5], "\n");
            int h;
            for(h = 0; h < 5; h++)
            {
                fprintf(fp, WrittingArray[h]);
            }   
           
            k = 0;
            int m;
            m = 0;
            //Write all the atomic coordinates to the file.
            while(k < number_of_atoms)
            {
                sprintf(WrittingArray[0], "%s    %f    %f    %f \n", inputString1[8+m],
                InterpArray[j+k][0], InterpArray[j+k][1], InterpArray[j+k][2]);
                fprintf(fp, WrittingArray[0]);
                k++;
                m+=4;
            }
        }
        //Change directories to the directory above current directory.
        k = chdir("../");
        if(k != 0)
        {
            printf("Directory Change Failed");
        }
        //Increment c to switch to the next directory.
        c++;
        j+=number_of_atoms;
    }
    //Reset all counters
    j = 0;
    g = 0;
    c = 0;
    //Print off the Images and the atomic coordinates (minus elements) to the screen.
    while(j < i*number_of_atoms)
    {
        printf("Image %d: \n", (c+1));
        g = 0;
        while(g < number_of_atoms)
        {
            printf("%f %f %f \n", InterpArray[j+g][0], InterpArray[j+g][1], InterpArray[j+g][2]);
            g++;
        }   
        j += number_of_atoms;
        c++;
    }   

    return 0;   
}

Planned additions to this code:
  • Produce an XYZ file which can be used to generate an "inspection" GIF for Jmol or some other program
  • The NEB algorithm (perhaps as a separate function?)
  • Other features (they will be added as they are needed or recommended)

Saturday, May 30, 2015

Code Progress Update 1

Accomplishments:
This week I made significant progress towards completing the initial parts of the program. Most of this work has been focused on generating the necessary files in the correct format for Gaussian 09 to use later on. To date the program has the following features:
  • Generate 'i' images between reactant and product
  • Use a formatted Gaussian 09 input file containing any number of atoms (limitation being it must be formatted in a specific way)
  • Generate relevant coordinates between reactant and product
  • Storage of all coordinates in one super-array which will be used later on for spring force calculations
 Examples:
Here is a Gaussian 09 input file, you will note that the same coordinates are repeated throughout. This is because I wanted to make a simple test file, of course in an actual calculation the atoms will not be located on the same spatial coordinate.

The Reactant Coordinates:
 #T RHF/6-31G(d) Opt Freq

Title

0 1
O    0.00000    0.00000    0.00000
H    0.00000    0.00000    0.00000
H    0.00000    0.00000    0.00000
H    1.00000    1.00000    1.00000
O    2.36581    2.36581    2.36581

The Product Coordinates:
 #T RHF/6-31G(d) Opt Freq

Title

0 1
O    5.00000    5.00000    5.00000
H    5.00000    5.00000    5.00000
H    5.00000    5.00000    5.00000
H    10.0000    10.0000    10.0000
O    12.5679    12.5679    12.5679

With these two files entered, the reactant being 'REACTANT.txt' and the product being 'PRODUCT.txt' in the same directory, the user can now enter how many intermediate images they would like the program to generate.

For this example I have selected 10 intermediate images to be generated. Now, obviously I won't show each and every text file since that would be tedious and a waste of time. Rather, the program at the moment spits out the image and the relevant coordinates (minus the element) to the terminal and writes the whole output to the file under the image's relevant directory. So, the output of the above files after selecting 10 images is like this for the terminal:

Image 1:
0.454545 0.454545 0.454545
0.454545 0.454545 0.454545
0.454545 0.454545 0.454545
1.818182 1.818182 1.818182
3.293272 3.293272 3.293272
Image 2:
0.909091 0.909091 0.909091
0.909091 0.909091 0.909091
0.909091 0.909091 0.909091
2.636364 2.636364 2.636364
4.220736 4.220736 4.220736
Image 3:
1.363636 1.363636 1.363636
1.363636 1.363636 1.363636
1.363636 1.363636 1.363636
3.454545 3.454545 3.454545
5.148198 5.148198 5.148198
... (more the same) ...
Image 10:
4.545455 4.545455 4.545455
4.545455 4.545455 4.545455
4.545455 4.545455 4.545455
9.181818 9.181818 9.181818
11.640437 11.640437 11.640437

And for image 3 looks like this in the text file:

#T RHF/6-31G(d) Opt Freq

Title

0 1
O    1.363636    1.363636    1.363636
H    1.363636    1.363636    1.363636
H    1.363636    1.363636    1.363636
H    3.454545    3.454545    3.454545
O    5.148198    5.148198    5.148198

In short order then, the program works! Well, that is to say that it can successfully generate 'i' directories with a Gaussian 09 submission file written to each directory.

Next Steps:
The goals for this coming week are to complete the following:
  • Throughly comment the code and make it more legiable
  • Begin working on the NEB function
  • Write a page explaining how NEB works in theory, both in general and how it will be specifically implemented in this program
In any case, that pretty much covers all that was accomplished since Wednesday of this last week. It is my plan to write another update on either Tuesday or Wednesday next week. Till then!

Tuesday, May 26, 2015

Introductions

Hello, I am John Eric Tiessen (Jet) and welcome to the first blog post for this development blog. To get started, I want to go over the purpose of this blog, how I will be using it and why you should be interested in it.

Purpose:
The reason for creating this blog is to help document the creation of several molecular dynamic simulation tools which I will developing over the course of this summer. These tools will be an important part of my senior research project at Valparaiso University and will form the basis of any research that I intend to carry out next year.

Blog Use:
My intention with this blog is to create an open and easily accessible platform for people to both view, criticize, inquire and, perhaps, help improve the code which I will be working on. Overall, the point is to make this work as public as possible so as nothing is left undocumented and unexplained in the development process.

The Interest:
If you happen to use theoretical chemistry programs in your line of work then this blog will probably be of some interest to you and your colleges. During this development project I intend to create at least two scripts/programs which will accomplish the following tasks: Perform NEB (Nudged Elastic Band) simulations using Gaussian, and link this script to Jmol so as to create a seamless GUI that does not require a user to understand command line. Also, I intend for my code to be completely free and well supported. Lofty ambitions maybe, but we'll see what I can get done over the course of the summer.

If you do happen to have a vested interest in this project, please let me know! I am very open to feedback and I am excited to have input from any source!