diff --git a/README2SCOOP.rst b/README2SCOOP.rst new file mode 100644 index 0000000000..5e07d8de83 --- /dev/null +++ b/README2SCOOP.rst @@ -0,0 +1,21 @@ +****************************************************** +SCOOP enabled RMG-Py +****************************************************** + +RMG-Py can be run in parallel (only for the thermochemical parameter +estimation part) using SCOOP module. +More info on SCOOP: http://code.google.com/p/scoop/ + +Running RMG-Py in parallel: + +python -m scoop.__main__ -n 8 $RMGpy/rmddg.py input.py > RMG.sdout.log & + +-n 8 specifies that you will have 8 workers. +Set it based on the available number of processors. +For job submission scripts check examples/rmg/scoop. + +Installing SCOOP: + +You need the development version of SCOOP (tagged with 0.7RC2). +Download link: http://scoop.googlecode.com/archive/0.7RC2.zip + diff --git a/examples/rmg/scoop/input.py b/examples/rmg/scoop/input.py new file mode 100644 index 0000000000..40efdda024 --- /dev/null +++ b/examples/rmg/scoop/input.py @@ -0,0 +1,159 @@ +# Data sources +database( + thermoLibraries = ['primaryThermoLibrary','DFT_QCI_thermo','GRI-Mech3.0'], + reactionLibraries = [('Methylformate',False),('Glarborg/highP',False)], + seedMechanisms = ['Glarborg/C2'], + kineticsDepositories = ['training'], + kineticsFamilies = ['!Intra_Disproportionation'], + kineticsEstimator = 'rate rules', +) + +# List of species +species( + label='Mfmt', + reactive=True, + structure=SMILES("COC=O"), +) +species( + label='O2', + reactive=True, + structure=SMILES("[O][O]"), +) +species( + label='C2H', + reactive=True, + structure=SMILES("C#[C]"), +) +species( + label='CH', + reactive=True, + structure=adjacencyList( + """ + 1 C 3 {2,S} + 2 H 0 {1,S} + """), +) +species( + label='H2O', + reactive=True, + structure=SMILES("O"), +) +species( + label='H2', + reactive=True, + structure=SMILES("[H][H]"), +) +species( + label='CO', + reactive=True, + structure=SMILES("[C]=O"), +) +species( + label='CO2', + reactive=True, + structure=SMILES("C(=O)=O"), +) +species( + label='CH4', + reactive=True, + structure=SMILES("C"), +) +species( + label='CH3', + reactive=True, + structure=SMILES("[CH3]"), +) +species( + label='CH3OH', + reactive=True, + structure=SMILES("CO"), +) +species( + label='C2H4', + reactive=True, + structure=SMILES("C=C"), +) +species( + label='C2H2', + reactive=True, + structure=SMILES("C#C"), +) +species( + label='CH2O', + reactive=True, + structure=SMILES("C=O"), +) +species( + label='CH3CHO', + reactive=True, + structure=SMILES("CC=O"), +) + + +# Bath gas +species( + label='Ar', + reactive=False, + structure=InChI("InChI=1S/Ar"), +) + +# Reaction systems +simpleReactor( + temperature=(650,'K'), + pressure=(1.0,'bar'), + initialMoleFractions={ + "Mfmt": 0.01, + "O2": 0.02, + "Ar": 0.08, + }, + terminationTime=(0.5,'s'), +) +simpleReactor( + temperature=(1350,'K'), + pressure=(3.0,'bar'), + initialMoleFractions={ + "Mfmt": 0.01, + "O2": 0.02, + "Ar": 0.97, + }, + terminationTime=(0.5,'s'), +) +simpleReactor( + temperature=(1950,'K'), + pressure=(10.0,'bar'), + initialMoleFractions={ + "Mfmt": 0.01, + "O2": 0.02, + "Ar": 0.97, + }, + terminationTime=(0.5,'s'), +) + +simulator( + atol=1e-22, + rtol=1e-8, +) + +model( + toleranceKeepInEdge=0.0, + toleranceMoveToCore=0.0005, + toleranceInterruptSimulation=1.0, + maximumEdgeSpecies=100000 +) + +pressureDependence( + method='modified strong collision', # 'reservoir state' + maximumGrainSize=(1.0,'kcal/mol'), + minimumNumberOfGrains=200, + temperatures=(290,3500,'K',8), + pressures=(0.02,100,'bar',5), + interpolation=('Chebyshev', 6, 4), +) + +options( + units='si', + saveRestartPeriod=None, + drawMolecules=False, + generatePlots=False, + saveConcentrationProfiles=True, +) diff --git a/examples/rmg/scoop/lsf.sh b/examples/rmg/scoop/lsf.sh new file mode 100755 index 0000000000..b559fb0224 --- /dev/null +++ b/examples/rmg/scoop/lsf.sh @@ -0,0 +1,59 @@ +#!/bin/sh +#BSUB -o RMG.out +#BSUB -J RMGPyScoop +#BSUB -n 8 +#BSUB -e error_log +#BSUB -q medium_priority + +# This is a job submission file for a LSF queuing system to run +# the SCOOP-enabled parallel version of RMG-Py across 8 CPUs on +# a number of different compute nodes on a (potentially heterogeneous) cluster. + +source ~/.bash_profile + +LAMHOST_FILE=hosts + +# start a new host file from scratch +rm -f $LAMHOST_FILE +touch $LAMHOST_FILE +# echo "# LAMMPI host file created by LSF on `date`" >> $LAMHOST_FILE +# check if we were able to start writing the conf file +if [ -f $LAMHOST_FILE ]; then + : +else + echo "$0: can't create $LAMHOST_FILE" + exit 1 +fi +HOST="" +NUM_PROC="" +FLAG="" +TOTAL_CPUS=0 +for TOKEN in $LSB_MCPU_HOSTS +do + if [ -z "$FLAG" ]; then + HOST="$TOKEN" + FLAG="0" + else + NUM_PROC="$TOKEN" + TOTAL_CPUS=`expr $TOTAL_CPUS + $NUM_PROC` + FLAG="1" + fi + if [ "$FLAG" = "1" ]; then + _x=0 + while [ $_x -lt $NUM_PROC ] + do + echo "$HOST" >>$LAMHOST_FILE + _x=`expr $_x + 1` + done + # get ready for the next host + FLAG="" + HOST="" + NUM_PROC="" + fi +done +# last thing added to LAMHOST_FILE +#echo "# end of LAMHOST file" >> $LAMHOST_FILE +echo "Your lamboot hostfile looks like:" +cat $LAMHOST_FILE + +python -m scoop -vv --hostfile $LAMHOST_FILE $RMGpy/rmg.py input.py > RMG.stdout.log diff --git a/examples/rmg/scoop/prolog.sh b/examples/rmg/scoop/prolog.sh new file mode 100755 index 0000000000..8fb7afab28 --- /dev/null +++ b/examples/rmg/scoop/prolog.sh @@ -0,0 +1,2 @@ +#!/bin/bash +source ~/.bash_profile diff --git a/examples/rmg/scoop/sge.sh b/examples/rmg/scoop/sge.sh new file mode 100755 index 0000000000..6fb3df6625 --- /dev/null +++ b/examples/rmg/scoop/sge.sh @@ -0,0 +1,25 @@ +#!/bin/bash +#####################i################################################ +# This is a job submission file for a SGE queuing system to run +# the SCOOP-enabled parallel version of RMG-Py across 48 CPUs on +# a single node. +# +# Define RMGPy as the path to rmg.py in your ~/.bash_profile +# NSLOTS is an SGE env. variable for total number of CPUs. +# prolog.sh is a script used by SCOOP to pass env. variables +# +# You can run the jobs on different nodes as well, but it is not +# recommended since you might have problems with SGE job termination. +# Type `qconf -spl` to see available parallel environments and modify +# the last #$ line if you really want to run it on many nodes. +#####################i################################################ +#$ -S /bin/bash +#$ -cwd +#$ -notify +#$ -o job.log -j y +#$ -N RMGscoop +#$ -l normal +#$ -l h_rt=09:05:00 +#$ -pe singlenode 48 +source ~/.bash_profile +python -m scoop.__main__ --tunnel --prolog $RMGpy/examples/rmg/scoop/prolog.sh -n $NSLOTS $RMGpy/rmg.py input.py > std.out diff --git a/examples/thermoEstimator/scoop/input.py b/examples/thermoEstimator/scoop/input.py new file mode 100644 index 0000000000..2e71d42a79 --- /dev/null +++ b/examples/thermoEstimator/scoop/input.py @@ -0,0 +1,1547 @@ +database( + thermoLibraries = ['KlippensteinH2O2'] +) + +quantumMechanics( + software='mopac', + fileStore='QMfiles', + scratchDirectory = None, # not currently used + onlyCyclics = True, + maxRadicalNumber = 0, +) +species( +label="InChI=1/C10H18/c1-8-4-2-5-9-6-3-7-10(8)9/h8-10H,2-7H2,1H3", +structure=adjacencyList(""" +1 C 0 {2,S} {9,S} {11,S} {27,S} +2 C 0 {1,S} {3,S} {6,S} {12,S} +3 C 0 {2,S} {4,S} {13,S} {14,S} +4 C 0 {3,S} {5,S} {15,S} {16,S} +5 C 0 {4,S} {6,S} {17,S} {18,S} +6 C 0 {5,S} {2,S} {7,S} {19,S} +7 C 0 {6,S} {8,S} {10,S} {20,S} +8 C 0 {7,S} {9,S} {21,S} {22,S} +9 C 0 {8,S} {1,S} {23,S} {24,S} +10 C 0 {7,S} {25,S} {26,S} {28,S} +11 H 0 {1,S} +12 H 0 {2,S} +13 H 0 {3,S} +14 H 0 {3,S} +15 H 0 {4,S} +16 H 0 {4,S} +17 H 0 {5,S} +18 H 0 {5,S} +19 H 0 {6,S} +20 H 0 {7,S} +21 H 0 {8,S} +22 H 0 {8,S} +23 H 0 {9,S} +24 H 0 {9,S} +25 H 0 {10,S} +26 H 0 {10,S} +27 H 0 {1,S} +28 H 0 {10,S} +""")) +species( +label="InChI=1/C10H18/c1-2-6-9(5-1)10-7-3-4-8-10/h9-10H,1-8H2", +structure=adjacencyList(""" +1 C 0 {9,S} {10,S} {11,S} {27,S} +2 C 0 {3,S} {6,S} {12,S} {28,S} +3 C 0 {2,S} {4,S} {13,S} {14,S} +4 C 0 {3,S} {5,S} {15,S} {16,S} +5 C 0 {4,S} {6,S} {17,S} {18,S} +6 C 0 {5,S} {2,S} {7,S} {19,S} +7 C 0 {8,S} {10,S} {6,S} {20,S} +8 C 0 {7,S} {9,S} {21,S} {22,S} +9 C 0 {8,S} {1,S} {23,S} {24,S} +10 C 0 {1,S} {7,S} {25,S} {26,S} +11 H 0 {1,S} +12 H 0 {2,S} +13 H 0 {3,S} +14 H 0 {3,S} +15 H 0 {4,S} +16 H 0 {4,S} +17 H 0 {5,S} +18 H 0 {5,S} +19 H 0 {6,S} +20 H 0 {7,S} +21 H 0 {8,S} +22 H 0 {8,S} +23 H 0 {9,S} +24 H 0 {9,S} +25 H 0 {10,S} +26 H 0 {10,S} +27 H 0 {1,S} +28 H 0 {2,S} +""")) +species( +label="InChI=1/C10H18/c1-2-8-6-7-9-4-3-5-10(8)9/h8-10H,2-7H2,1H3", +structure=adjacencyList(""" +1 C 0 {2,S} {10,S} {11,S} {27,S} +2 C 0 {1,S} {3,S} {6,S} {12,S} +3 C 0 {2,S} {4,S} {13,S} {14,S} +4 C 0 {3,S} {5,S} {15,S} {16,S} +5 C 0 {4,S} {6,S} {17,S} {18,S} +6 C 0 {5,S} {2,S} {7,S} {19,S} +7 C 0 {6,S} {10,S} {8,S} {20,S} +8 C 0 {7,S} {9,S} {21,S} {22,S} +9 C 0 {8,S} {23,S} {24,S} {28,S} +10 C 0 {1,S} {7,S} {25,S} {26,S} +11 H 0 {1,S} +12 H 0 {2,S} +13 H 0 {3,S} +14 H 0 {3,S} +15 H 0 {4,S} +16 H 0 {4,S} +17 H 0 {5,S} +18 H 0 {5,S} +19 H 0 {6,S} +20 H 0 {7,S} +21 H 0 {8,S} +22 H 0 {8,S} +23 H 0 {9,S} +24 H 0 {9,S} +25 H 0 {10,S} +26 H 0 {10,S} +27 H 0 {1,S} +28 H 0 {9,S} +""")) +species( +label="InChI=1/C10H18/c1-2-4-9-6-7-10(8-9)5-3-1/h9-10H,1-8H2", +structure=adjacencyList(""" +1 C 0 {2,S} {9,S} {10,S} {11,S} +2 C 0 {1,S} {3,S} {12,S} {27,S} +3 C 0 {2,S} {4,S} {13,S} {14,S} +4 C 0 {3,S} {5,S} {15,S} {16,S} +5 C 0 {4,S} {6,S} {17,S} {18,S} +6 C 0 {5,S} {7,S} {19,S} {28,S} +7 C 0 {6,S} {8,S} {10,S} {20,S} +8 C 0 {7,S} {9,S} {21,S} {22,S} +9 C 0 {8,S} {1,S} {23,S} {24,S} +10 C 0 {1,S} {7,S} {25,S} {26,S} +11 H 0 {1,S} +12 H 0 {2,S} +13 H 0 {3,S} +14 H 0 {3,S} +15 H 0 {4,S} +16 H 0 {4,S} +17 H 0 {5,S} +18 H 0 {5,S} +19 H 0 {6,S} +20 H 0 {7,S} +21 H 0 {8,S} +22 H 0 {8,S} +23 H 0 {9,S} +24 H 0 {9,S} +25 H 0 {10,S} +26 H 0 {10,S} +27 H 0 {2,S} +28 H 0 {6,S} +""")) +species( +label="InChI=1/C10H18/c1-2-3-9-6-8-4-5-10(9)7-8/h8-10H,2-7H2,1H3", +structure=adjacencyList(""" +1 C 0 {2,S} {9,S} {10,S} {11,S} +2 C 0 {1,S} {6,S} {12,S} {27,S} +3 C 0 {4,S} {13,S} {14,S} {28,S} +4 C 0 {3,S} {5,S} {15,S} {16,S} +5 C 0 {4,S} {6,S} {17,S} {18,S} +6 C 0 {2,S} {7,S} {5,S} {19,S} +7 C 0 {6,S} {8,S} {10,S} {20,S} +8 C 0 {7,S} {9,S} {21,S} {22,S} +9 C 0 {8,S} {1,S} {23,S} {24,S} +10 C 0 {1,S} {7,S} {25,S} {26,S} +11 H 0 {1,S} +12 H 0 {2,S} +13 H 0 {3,S} +14 H 0 {3,S} +15 H 0 {4,S} +16 H 0 {4,S} +17 H 0 {5,S} +18 H 0 {5,S} +19 H 0 {6,S} +20 H 0 {7,S} +21 H 0 {8,S} +22 H 0 {8,S} +23 H 0 {9,S} +24 H 0 {9,S} +25 H 0 {10,S} +26 H 0 {10,S} +27 H 0 {2,S} +28 H 0 {3,S} +""")) +species( +label="InChI=1/C10H18/c1-3-10-7(2)8-4-5-9(10)6-8/h7-10H,3-6H2,1-2H3", +structure=adjacencyList(""" +1 C 0 {2,S} {9,S} {10,S} {11,S} +2 C 0 {1,S} {6,S} {3,S} {12,S} +3 C 0 {2,S} {13,S} {14,S} {27,S} +4 C 0 {5,S} {15,S} {16,S} {28,S} +5 C 0 {4,S} {6,S} {17,S} {18,S} +6 C 0 {2,S} {7,S} {5,S} {19,S} +7 C 0 {6,S} {8,S} {10,S} {20,S} +8 C 0 {7,S} {9,S} {21,S} {22,S} +9 C 0 {8,S} {1,S} {23,S} {24,S} +10 C 0 {1,S} {7,S} {25,S} {26,S} +11 H 0 {1,S} +12 H 0 {2,S} +13 H 0 {3,S} +14 H 0 {3,S} +15 H 0 {4,S} +16 H 0 {4,S} +17 H 0 {5,S} +18 H 0 {5,S} +19 H 0 {6,S} +20 H 0 {7,S} +21 H 0 {8,S} +22 H 0 {8,S} +23 H 0 {9,S} +24 H 0 {9,S} +25 H 0 {10,S} +26 H 0 {10,S} +27 H 0 {3,S} +28 H 0 {4,S} +""")) +species( +label="InChI=1/C10H18/c1-7-6-8(2)10-5-3-4-9(7)10/h7-10H,3-6H2,1-2H3", +structure=adjacencyList(""" +1 C 0 {2,S} {10,S} {9,S} {11,S} +2 C 0 {1,S} {3,S} {6,S} {12,S} +3 C 0 {2,S} {4,S} {13,S} {14,S} +4 C 0 {3,S} {5,S} {15,S} {16,S} +5 C 0 {4,S} {6,S} {17,S} {18,S} +6 C 0 {5,S} {2,S} {7,S} {19,S} +7 C 0 {6,S} {10,S} {8,S} {20,S} +8 C 0 {7,S} {21,S} {22,S} {27,S} +9 C 0 {1,S} {23,S} {24,S} {28,S} +10 C 0 {1,S} {7,S} {25,S} {26,S} +11 H 0 {1,S} +12 H 0 {2,S} +13 H 0 {3,S} +14 H 0 {3,S} +15 H 0 {4,S} +16 H 0 {4,S} +17 H 0 {5,S} +18 H 0 {5,S} +19 H 0 {6,S} +20 H 0 {7,S} +21 H 0 {8,S} +22 H 0 {8,S} +23 H 0 {9,S} +24 H 0 {9,S} +25 H 0 {10,S} +26 H 0 {10,S} +27 H 0 {8,S} +28 H 0 {9,S} +""")) +species( +label="InChI=1/C10H18/c1-3-9(4-2)10-7-5-6-8-10/h3,9-10H,1,4-8H2,2H3", +structure=adjacencyList(""" +1 C 0 {9,S} {10,S} {11,S} {27,S} +2 C 0 {3,D} {6,S} {12,S} +3 C 0 {2,D} {13,S} {14,S} +4 C 0 {5,S} {15,S} {16,S} {28,S} +5 C 0 {4,S} {6,S} {17,S} {18,S} +6 C 0 {5,S} {2,S} {7,S} {19,S} +7 C 0 {8,S} {10,S} {6,S} {20,S} +8 C 0 {7,S} {9,S} {21,S} {22,S} +9 C 0 {8,S} {1,S} {23,S} {24,S} +10 C 0 {1,S} {7,S} {25,S} {26,S} +11 H 0 {1,S} +12 H 0 {2,S} +13 H 0 {3,S} +14 H 0 {3,S} +15 H 0 {4,S} +16 H 0 {4,S} +17 H 0 {5,S} +18 H 0 {5,S} +19 H 0 {6,S} +20 H 0 {7,S} +21 H 0 {8,S} +22 H 0 {8,S} +23 H 0 {9,S} +24 H 0 {9,S} +25 H 0 {10,S} +26 H 0 {10,S} +27 H 0 {1,S} +28 H 0 {4,S} +""")) +species( +label="InChI=1/C10H18/c1-2-3-4-7-10-8-5-6-9-10/h4,7,10H,2-3,5-6,8-9H2,1H3", +structure=adjacencyList(""" +1 C 0 {9,S} {10,S} {11,S} {27,S} +2 C 0 {3,S} {6,D} {12,S} +3 C 0 {2,S} {4,S} {13,S} {14,S} +4 C 0 {3,S} {5,S} {15,S} {16,S} +5 C 0 {4,S} {17,S} {18,S} {28,S} +6 C 0 {2,D} {7,S} {19,S} +7 C 0 {8,S} {10,S} {6,S} {20,S} +8 C 0 {7,S} {9,S} {21,S} {22,S} +9 C 0 {8,S} {1,S} {23,S} {24,S} +10 C 0 {1,S} {7,S} {25,S} {26,S} +11 H 0 {1,S} +12 H 0 {2,S} +13 H 0 {3,S} +14 H 0 {3,S} +15 H 0 {4,S} +16 H 0 {4,S} +17 H 0 {5,S} +18 H 0 {5,S} +19 H 0 {6,S} +20 H 0 {7,S} +21 H 0 {8,S} +22 H 0 {8,S} +23 H 0 {9,S} +24 H 0 {9,S} +25 H 0 {10,S} +26 H 0 {10,S} +27 H 0 {1,S} +28 H 0 {5,S} +""")) +species( +label="InChI=1/C10H18/c1-2-3-4-7-10-8-5-6-9-10/h2,10H,1,3-9H2", +structure=adjacencyList(""" +1 C 0 {10,D} {9,S} {11,S} +2 C 0 {3,S} {6,S} {12,S} {27,S} +3 C 0 {2,S} {4,S} {13,S} {14,S} +4 C 0 {3,S} {5,S} {15,S} {16,S} +5 C 0 {4,S} {6,S} {17,S} {18,S} +6 C 0 {5,S} {2,S} {7,S} {19,S} +7 C 0 {6,S} {8,S} {20,S} {28,S} +8 C 0 {7,S} {9,S} {21,S} {22,S} +9 C 0 {8,S} {1,S} {23,S} {24,S} +10 C 0 {1,D} {25,S} {26,S} +11 H 0 {1,S} +12 H 0 {2,S} +13 H 0 {3,S} +14 H 0 {3,S} +15 H 0 {4,S} +16 H 0 {4,S} +17 H 0 {5,S} +18 H 0 {5,S} +19 H 0 {6,S} +20 H 0 {7,S} +21 H 0 {8,S} +22 H 0 {8,S} +23 H 0 {9,S} +24 H 0 {9,S} +25 H 0 {10,S} +26 H 0 {10,S} +27 H 0 {2,S} +28 H 0 {7,S} +""")) +species( +label="InChI=1/C10H18/c1-3-6-9(2)10-7-4-5-8-10/h3,9-10H,1,4-8H2,2H3", +structure=adjacencyList(""" +1 C 0 {9,D} {10,S} {11,S} +2 C 0 {3,S} {6,S} {12,S} {27,S} +3 C 0 {2,S} {4,S} {13,S} {14,S} +4 C 0 {3,S} {5,S} {15,S} {16,S} +5 C 0 {4,S} {6,S} {17,S} {18,S} +6 C 0 {5,S} {2,S} {7,S} {19,S} +7 C 0 {6,S} {8,S} {10,S} {20,S} +8 C 0 {7,S} {21,S} {22,S} {28,S} +9 C 0 {1,D} {23,S} {24,S} +10 C 0 {1,S} {7,S} {25,S} {26,S} +11 H 0 {1,S} +12 H 0 {2,S} +13 H 0 {3,S} +14 H 0 {3,S} +15 H 0 {4,S} +16 H 0 {4,S} +17 H 0 {5,S} +18 H 0 {5,S} +19 H 0 {6,S} +20 H 0 {7,S} +21 H 0 {8,S} +22 H 0 {8,S} +23 H 0 {9,S} +24 H 0 {9,S} +25 H 0 {10,S} +26 H 0 {10,S} +27 H 0 {2,S} +28 H 0 {8,S} +""")) +species( +label="InChI=1/C10H18/c1-3-6-10-8-5-7-9(10)4-2/h5,8-10H,3-4,6-7H2,1-2H3", +structure=adjacencyList(""" +1 C 0 {2,D} {10,S} {11,S} +2 C 0 {1,D} {6,S} {12,S} +3 C 0 {4,S} {13,S} {14,S} {27,S} +4 C 0 {3,S} {5,S} {15,S} {16,S} +5 C 0 {4,S} {6,S} {17,S} {18,S} +6 C 0 {2,S} {7,S} {5,S} {19,S} +7 C 0 {6,S} {10,S} {8,S} {20,S} +8 C 0 {7,S} {9,S} {21,S} {22,S} +9 C 0 {8,S} {23,S} {24,S} {28,S} +10 C 0 {1,S} {7,S} {25,S} {26,S} +11 H 0 {1,S} +12 H 0 {2,S} +13 H 0 {3,S} +14 H 0 {3,S} +15 H 0 {4,S} +16 H 0 {4,S} +17 H 0 {5,S} +18 H 0 {5,S} +19 H 0 {6,S} +20 H 0 {7,S} +21 H 0 {8,S} +22 H 0 {8,S} +23 H 0 {9,S} +24 H 0 {9,S} +25 H 0 {10,S} +26 H 0 {10,S} +27 H 0 {3,S} +28 H 0 {9,S} +""")) +species( +label="InChI=1/C10H18/c1-2-10-8-6-4-3-5-7-9-10/h4,6,10H,2-3,5,7-9H2,1H3", +structure=adjacencyList(""" +1 C 0 {2,D} {10,S} {11,S} +2 C 0 {1,D} {3,S} {12,S} +3 C 0 {2,S} {4,S} {13,S} {14,S} +4 C 0 {3,S} {5,S} {15,S} {16,S} +5 C 0 {4,S} {6,S} {17,S} {18,S} +6 C 0 {5,S} {7,S} {19,S} {27,S} +7 C 0 {6,S} {10,S} {8,S} {20,S} +8 C 0 {7,S} {9,S} {21,S} {22,S} +9 C 0 {8,S} {23,S} {24,S} {28,S} +10 C 0 {1,S} {7,S} {25,S} {26,S} +11 H 0 {1,S} +12 H 0 {2,S} +13 H 0 {3,S} +14 H 0 {3,S} +15 H 0 {4,S} +16 H 0 {4,S} +17 H 0 {5,S} +18 H 0 {5,S} +19 H 0 {6,S} +20 H 0 {7,S} +21 H 0 {8,S} +22 H 0 {8,S} +23 H 0 {9,S} +24 H 0 {9,S} +25 H 0 {10,S} +26 H 0 {10,S} +27 H 0 {6,S} +28 H 0 {9,S} +""")) +species( +label="InChI=1/C10H18/c1-3-6-10-8-5-7-9(10)4-2/h4,9-10H,2-3,5-8H2,1H3", +structure=adjacencyList(""" +1 C 0 {10,D} {2,S} {11,S} +2 C 0 {3,S} {6,S} {1,S} {12,S} +3 C 0 {2,S} {4,S} {13,S} {14,S} +4 C 0 {3,S} {5,S} {15,S} {16,S} +5 C 0 {4,S} {6,S} {17,S} {18,S} +6 C 0 {5,S} {2,S} {7,S} {19,S} +7 C 0 {6,S} {8,S} {20,S} {27,S} +8 C 0 {7,S} {9,S} {21,S} {22,S} +9 C 0 {8,S} {23,S} {24,S} {28,S} +10 C 0 {1,D} {25,S} {26,S} +11 H 0 {1,S} +12 H 0 {2,S} +13 H 0 {3,S} +14 H 0 {3,S} +15 H 0 {4,S} +16 H 0 {4,S} +17 H 0 {5,S} +18 H 0 {5,S} +19 H 0 {6,S} +20 H 0 {7,S} +21 H 0 {8,S} +22 H 0 {8,S} +23 H 0 {9,S} +24 H 0 {9,S} +25 H 0 {10,S} +26 H 0 {10,S} +27 H 0 {7,S} +28 H 0 {9,S} +""")) +species( +label="InChI=1/C10H18/c1-8-6-7-10(8)9-4-2-3-5-9/h8-10H,2-7H2,1H3", +structure=adjacencyList(""" +1 C 0 {2,S} {5,S} {6,S} {14,S} +2 C 0 {1,S} {3,S} {7,S} {8,S} +3 C 0 {2,S} {4,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {1,S} {13,S} {27,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {17,S} {15,S} {19,S} {1,S} +15 C 0 {16,S} {14,S} {18,S} {20,S} +16 C 0 {15,S} {17,S} {21,S} {22,S} +17 C 0 {16,S} {14,S} {23,S} {24,S} +18 C 0 {15,S} {25,S} {26,S} {28,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {16,S} +22 H 0 {16,S} +23 H 0 {17,S} +24 H 0 {17,S} +25 H 0 {18,S} +26 H 0 {18,S} +27 H 0 {5,S} +28 H 0 {18,S} +""")) +species( +label="InChI=1/C10H18/c1-2-5-9(4-1)8-10-6-3-7-10/h9-10H,1-8H2", +structure=adjacencyList(""" +1 C 0 {2,S} {5,S} {6,S} {18,S} +2 C 0 {1,S} {3,S} {7,S} {8,S} +3 C 0 {2,S} {4,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {1,S} {13,S} {27,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {17,S} {15,S} {19,S} {28,S} +15 C 0 {16,S} {14,S} {18,S} {20,S} +16 C 0 {15,S} {17,S} {21,S} {22,S} +17 C 0 {16,S} {14,S} {23,S} {24,S} +18 C 0 {15,S} {25,S} {26,S} {1,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {16,S} +22 H 0 {16,S} +23 H 0 {17,S} +24 H 0 {17,S} +25 H 0 {18,S} +26 H 0 {18,S} +27 H 0 {5,S} +28 H 0 {14,S} +""")) +species( +label="InChI=1/C10H18/c1-7-3-5-9(7)10-6-4-8(10)2/h7-10H,3-6H2,1-2H3", +structure=adjacencyList(""" +1 C 0 {4,S} {2,S} {6,S} {14,S} +2 C 0 {3,S} {1,S} {5,S} {7,S} +3 C 0 {2,S} {4,S} {8,S} {9,S} +4 C 0 {3,S} {1,S} {10,S} {11,S} +5 C 0 {2,S} {12,S} {13,S} {27,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {3,S} +9 H 0 {3,S} +10 H 0 {4,S} +11 H 0 {4,S} +12 H 0 {5,S} +13 H 0 {5,S} +14 C 0 {17,S} {15,S} {19,S} {1,S} +15 C 0 {16,S} {14,S} {18,S} {20,S} +16 C 0 {15,S} {17,S} {21,S} {22,S} +17 C 0 {16,S} {14,S} {23,S} {24,S} +18 C 0 {15,S} {25,S} {26,S} {28,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {16,S} +22 H 0 {16,S} +23 H 0 {17,S} +24 H 0 {17,S} +25 H 0 {18,S} +26 H 0 {18,S} +27 H 0 {5,S} +28 H 0 {18,S} +""")) +species( +label="InChI=1/C10H18/c1-8-5-6-10(8)7-9-3-2-4-9/h8-10H,2-7H2,1H3", +structure=adjacencyList(""" +1 C 0 {4,S} {2,S} {6,S} {18,S} +2 C 0 {3,S} {1,S} {5,S} {7,S} +3 C 0 {2,S} {4,S} {8,S} {9,S} +4 C 0 {3,S} {1,S} {10,S} {11,S} +5 C 0 {2,S} {12,S} {13,S} {27,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {3,S} +9 H 0 {3,S} +10 H 0 {4,S} +11 H 0 {4,S} +12 H 0 {5,S} +13 H 0 {5,S} +14 C 0 {17,S} {15,S} {19,S} {28,S} +15 C 0 {16,S} {14,S} {18,S} {20,S} +16 C 0 {15,S} {17,S} {21,S} {22,S} +17 C 0 {16,S} {14,S} {23,S} {24,S} +18 C 0 {15,S} {25,S} {26,S} {1,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {16,S} +22 H 0 {16,S} +23 H 0 {17,S} +24 H 0 {17,S} +25 H 0 {18,S} +26 H 0 {18,S} +27 H 0 {5,S} +28 H 0 {14,S} +""")) +species( +label="InChI=1/C10H18/c1-3-9(4-1)7-8-10-5-2-6-10/h9-10H,1-8H2", +structure=adjacencyList(""" +1 C 0 {4,S} {2,S} {6,S} {27,S} +2 C 0 {3,S} {1,S} {5,S} {7,S} +3 C 0 {2,S} {4,S} {8,S} {9,S} +4 C 0 {3,S} {1,S} {10,S} {11,S} +5 C 0 {2,S} {12,S} {13,S} {18,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {3,S} +9 H 0 {3,S} +10 H 0 {4,S} +11 H 0 {4,S} +12 H 0 {5,S} +13 H 0 {5,S} +14 C 0 {17,S} {15,S} {19,S} {28,S} +15 C 0 {16,S} {14,S} {18,S} {20,S} +16 C 0 {15,S} {17,S} {21,S} {22,S} +17 C 0 {16,S} {14,S} {23,S} {24,S} +18 C 0 {15,S} {25,S} {26,S} {5,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {16,S} +22 H 0 {16,S} +23 H 0 {17,S} +24 H 0 {17,S} +25 H 0 {18,S} +26 H 0 {18,S} +27 H 0 {1,S} +28 H 0 {14,S} +""")) +species( +label="InChI=1/C10H18/c1-2-3-4-7-10-8-5-6-9-10/h3-4,10H,2,5-9H2,1H3", +structure=adjacencyList(""" +1 C 0 {2,S} {5,S} {6,S} {15,S} +2 C 0 {1,S} {3,S} {7,S} {8,S} +3 C 0 {2,S} {4,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {1,S} {13,S} {27,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {15,S} {18,D} {19,S} +15 C 0 {14,S} {20,S} {21,S} {1,S} +16 C 0 {17,S} {22,S} {23,S} {28,S} +17 C 0 {16,S} {18,S} {24,S} {25,S} +18 C 0 {14,D} {17,S} {26,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {15,S} +22 H 0 {16,S} +23 H 0 {16,S} +24 H 0 {17,S} +25 H 0 {17,S} +26 H 0 {18,S} +27 H 0 {5,S} +28 H 0 {16,S} +""")) +species( +label="InChI=1/C10H18/c1-2-3-4-7-10-8-5-6-9-10/h2-3,10H,4-9H2,1H3", +structure=adjacencyList(""" +1 C 0 {2,S} {5,S} {6,S} {16,S} +2 C 0 {1,S} {3,S} {7,S} {8,S} +3 C 0 {2,S} {4,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {1,S} {13,S} {27,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {15,S} {18,D} {19,S} +15 C 0 {14,S} {20,S} {21,S} {28,S} +16 C 0 {17,S} {22,S} {23,S} {1,S} +17 C 0 {16,S} {18,S} {24,S} {25,S} +18 C 0 {14,D} {17,S} {26,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {15,S} +22 H 0 {16,S} +23 H 0 {16,S} +24 H 0 {17,S} +25 H 0 {17,S} +26 H 0 {18,S} +27 H 0 {5,S} +28 H 0 {15,S} +""")) +species( +label="InChI=1/C10H18/c1-3-4-5-6-10-8-7-9(10)2/h4-5,9-10H,3,6-8H2,1-2H3", +structure=adjacencyList(""" +1 C 0 {2,S} {5,D} {6,S} +2 C 0 {1,S} {7,S} {8,S} {14,S} +3 C 0 {4,S} {9,S} {10,S} {27,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {1,D} {4,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {17,S} {15,S} {19,S} {2,S} +15 C 0 {16,S} {14,S} {18,S} {20,S} +16 C 0 {15,S} {17,S} {21,S} {22,S} +17 C 0 {16,S} {14,S} {23,S} {24,S} +18 C 0 {15,S} {25,S} {26,S} {28,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {16,S} +22 H 0 {16,S} +23 H 0 {17,S} +24 H 0 {17,S} +25 H 0 {18,S} +26 H 0 {18,S} +27 H 0 {3,S} +28 H 0 {18,S} +""")) +species( +label="InChI=1/C10H18/c1-2-3-4-5-7-10-8-6-9-10/h3-4,10H,2,5-9H2,1H3", +structure=adjacencyList(""" +1 C 0 {2,S} {5,D} {6,S} +2 C 0 {1,S} {7,S} {8,S} {18,S} +3 C 0 {4,S} {9,S} {10,S} {27,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {1,D} {4,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {17,S} {15,S} {19,S} {28,S} +15 C 0 {16,S} {14,S} {18,S} {20,S} +16 C 0 {15,S} {17,S} {21,S} {22,S} +17 C 0 {16,S} {14,S} {23,S} {24,S} +18 C 0 {15,S} {25,S} {26,S} {2,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {16,S} +22 H 0 {16,S} +23 H 0 {17,S} +24 H 0 {17,S} +25 H 0 {18,S} +26 H 0 {18,S} +27 H 0 {3,S} +28 H 0 {14,S} +""")) +species( +label="InChI=1/C10H18/c1-3-4-5-6-10-8-7-9(10)2/h3-4,9-10H,5-8H2,1-2H3", +structure=adjacencyList(""" +1 C 0 {2,S} {5,D} {6,S} +2 C 0 {1,S} {7,S} {8,S} {27,S} +3 C 0 {4,S} {9,S} {10,S} {14,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {1,D} {4,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {17,S} {15,S} {19,S} {3,S} +15 C 0 {16,S} {14,S} {18,S} {20,S} +16 C 0 {15,S} {17,S} {21,S} {22,S} +17 C 0 {16,S} {14,S} {23,S} {24,S} +18 C 0 {15,S} {25,S} {26,S} {28,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {16,S} +22 H 0 {16,S} +23 H 0 {17,S} +24 H 0 {17,S} +25 H 0 {18,S} +26 H 0 {18,S} +27 H 0 {2,S} +28 H 0 {18,S} +""")) +species( +label="InChI=1/C10H18/c1-3-4-5-6-10-8-7-9(10)2/h3,9-10H,1,4-8H2,2H3", +structure=adjacencyList(""" +1 C 0 {2,D} {5,S} {6,S} +2 C 0 {1,D} {7,S} {8,S} +3 C 0 {4,S} {9,S} {10,S} {14,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {1,S} {13,S} {27,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {17,S} {15,S} {19,S} {3,S} +15 C 0 {16,S} {14,S} {18,S} {20,S} +16 C 0 {15,S} {17,S} {21,S} {22,S} +17 C 0 {16,S} {14,S} {23,S} {24,S} +18 C 0 {15,S} {25,S} {26,S} {28,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {16,S} +22 H 0 {16,S} +23 H 0 {17,S} +24 H 0 {17,S} +25 H 0 {18,S} +26 H 0 {18,S} +27 H 0 {5,S} +28 H 0 {18,S} +""")) +species( +label="InChI=1/C10H18/c1-2-3-4-5-7-10-8-6-9-10/h2-3,10H,4-9H2,1H3", +structure=adjacencyList(""" +1 C 0 {2,S} {5,D} {6,S} +2 C 0 {1,S} {7,S} {8,S} {27,S} +3 C 0 {4,S} {9,S} {10,S} {18,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {1,D} {4,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {17,S} {15,S} {19,S} {28,S} +15 C 0 {16,S} {14,S} {18,S} {20,S} +16 C 0 {15,S} {17,S} {21,S} {22,S} +17 C 0 {16,S} {14,S} {23,S} {24,S} +18 C 0 {15,S} {25,S} {26,S} {3,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {16,S} +22 H 0 {16,S} +23 H 0 {17,S} +24 H 0 {17,S} +25 H 0 {18,S} +26 H 0 {18,S} +27 H 0 {2,S} +28 H 0 {14,S} +""")) +species( +label="InChI=1/C10H18/c1-2-3-4-5-7-10-8-6-9-10/h2,10H,1,3-9H2", +structure=adjacencyList(""" +1 C 0 {2,D} {5,S} {6,S} +2 C 0 {1,D} {7,S} {8,S} +3 C 0 {4,S} {9,S} {10,S} {18,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {1,S} {13,S} {27,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {17,S} {15,S} {19,S} {28,S} +15 C 0 {16,S} {14,S} {18,S} {20,S} +16 C 0 {15,S} {17,S} {21,S} {22,S} +17 C 0 {16,S} {14,S} {23,S} {24,S} +18 C 0 {15,S} {25,S} {26,S} {3,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {16,S} +22 H 0 {16,S} +23 H 0 {17,S} +24 H 0 {17,S} +25 H 0 {18,S} +26 H 0 {18,S} +27 H 0 {5,S} +28 H 0 {14,S} +""")) +species( +label="InChI=1/C10H18/c1-4-9(5-2)10-7-6-8(10)3/h4,8-10H,1,5-7H2,2-3H3", +structure=adjacencyList(""" +1 C 0 {2,D} {5,S} {6,S} +2 C 0 {1,D} {7,S} {8,S} +3 C 0 {4,S} {9,S} {10,S} {27,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {1,S} {13,S} {14,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {17,S} {15,S} {19,S} {5,S} +15 C 0 {16,S} {14,S} {18,S} {20,S} +16 C 0 {15,S} {17,S} {21,S} {22,S} +17 C 0 {16,S} {14,S} {23,S} {24,S} +18 C 0 {15,S} {25,S} {26,S} {28,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {16,S} +22 H 0 {16,S} +23 H 0 {17,S} +24 H 0 {17,S} +25 H 0 {18,S} +26 H 0 {18,S} +27 H 0 {3,S} +28 H 0 {18,S} +""")) +species( +label="InChI=1/C10H18/c1-3-9(4-2)8-10-6-5-7-10/h3,9-10H,1,4-8H2,2H3", +structure=adjacencyList(""" +1 C 0 {2,D} {5,S} {6,S} +2 C 0 {1,D} {7,S} {8,S} +3 C 0 {4,S} {9,S} {10,S} {27,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {1,S} {13,S} {18,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {17,S} {15,S} {19,S} {28,S} +15 C 0 {16,S} {14,S} {18,S} {20,S} +16 C 0 {15,S} {17,S} {21,S} {22,S} +17 C 0 {16,S} {14,S} {23,S} {24,S} +18 C 0 {15,S} {25,S} {26,S} {5,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {16,S} +22 H 0 {16,S} +23 H 0 {17,S} +24 H 0 {17,S} +25 H 0 {18,S} +26 H 0 {18,S} +27 H 0 {3,S} +28 H 0 {14,S} +""")) +species( +label="InChI=1/C10H18/c1-3-8-7(2)9-5-4-6-10(8)9/h7-10H,3-6H2,1-2H3", +structure=adjacencyList(""" +1 C 0 {2,S} {5,S} {14,S} {6,S} +2 C 0 {1,S} {3,S} {7,S} {8,S} +3 C 0 {2,S} {4,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {1,S} {18,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {18,S} {1,S} {15,S} {19,S} +15 C 0 {14,S} {20,S} {21,S} {27,S} +16 C 0 {17,S} {22,S} {23,S} {28,S} +17 C 0 {16,S} {18,S} {24,S} {25,S} +18 C 0 {14,S} {5,S} {17,S} {26,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {15,S} +22 H 0 {16,S} +23 H 0 {16,S} +24 H 0 {17,S} +25 H 0 {17,S} +26 H 0 {18,S} +27 H 0 {15,S} +28 H 0 {16,S} +""")) +species( +label="InChI=1/C10H18/c1-2-4-8-7-9-5-3-6-10(8)9/h8-10H,2-7H2,1H3", +structure=adjacencyList(""" +1 C 0 {2,S} {18,S} {5,S} {6,S} +2 C 0 {1,S} {14,S} {7,S} {8,S} +3 C 0 {4,S} {9,S} {10,S} {27,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {1,S} {13,S} {28,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {15,S} {18,S} {2,S} {19,S} +15 C 0 {14,S} {16,S} {20,S} {21,S} +16 C 0 {15,S} {17,S} {22,S} {23,S} +17 C 0 {16,S} {18,S} {24,S} {25,S} +18 C 0 {17,S} {14,S} {1,S} {26,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {15,S} +22 H 0 {16,S} +23 H 0 {16,S} +24 H 0 {17,S} +25 H 0 {17,S} +26 H 0 {18,S} +27 H 0 {3,S} +28 H 0 {5,S} +""")) +species( +label="InChI=1/C10H18/c1-8(10-6-7-10)9-4-2-3-5-9/h8-10H,2-7H2,1H3", +structure=adjacencyList(""" +1 C 0 {2,S} {5,S} {6,S} {14,S} +2 C 0 {1,S} {7,S} {8,S} {27,S} +3 C 0 {4,S} {5,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {3,S} {1,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {17,S} {18,S} {19,S} {1,S} +15 C 0 {16,S} {18,S} {20,S} {28,S} +16 C 0 {15,S} {17,S} {21,S} {22,S} +17 C 0 {16,S} {14,S} {23,S} {24,S} +18 C 0 {14,S} {15,S} {25,S} {26,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {16,S} +22 H 0 {16,S} +23 H 0 {17,S} +24 H 0 {17,S} +25 H 0 {18,S} +26 H 0 {18,S} +27 H 0 {2,S} +28 H 0 {15,S} +""")) +species( +label="InChI=1/C10H18/c1-2-4-9(3-1)5-6-10-7-8-10/h9-10H,1-8H2", +structure=adjacencyList(""" +1 C 0 {2,S} {5,S} {6,S} {27,S} +2 C 0 {1,S} {7,S} {8,S} {14,S} +3 C 0 {4,S} {5,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {3,S} {1,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {17,S} {18,S} {19,S} {2,S} +15 C 0 {16,S} {18,S} {20,S} {28,S} +16 C 0 {15,S} {17,S} {21,S} {22,S} +17 C 0 {16,S} {14,S} {23,S} {24,S} +18 C 0 {14,S} {15,S} {25,S} {26,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {16,S} +22 H 0 {16,S} +23 H 0 {17,S} +24 H 0 {17,S} +25 H 0 {18,S} +26 H 0 {18,S} +27 H 0 {1,S} +28 H 0 {15,S} +""")) +species( +label="InChI=1/C10H18/c1-7-3-6-10(7)8(2)9-4-5-9/h7-10H,3-6H2,1-2H3", +structure=adjacencyList(""" +1 C 0 {2,S} {5,S} {6,S} {14,S} +2 C 0 {1,S} {7,S} {8,S} {27,S} +3 C 0 {4,S} {5,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {3,S} {1,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {17,S} {15,S} {19,S} {1,S} +15 C 0 {16,S} {14,S} {18,S} {20,S} +16 C 0 {15,S} {17,S} {21,S} {22,S} +17 C 0 {16,S} {14,S} {23,S} {24,S} +18 C 0 {15,S} {25,S} {26,S} {28,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {16,S} +22 H 0 {16,S} +23 H 0 {17,S} +24 H 0 {17,S} +25 H 0 {18,S} +26 H 0 {18,S} +27 H 0 {2,S} +28 H 0 {18,S} +""")) +species( +label="InChI=1/C10H18/c1-8(10-5-6-10)7-9-3-2-4-9/h8-10H,2-7H2,1H3", +structure=adjacencyList(""" +1 C 0 {2,S} {5,S} {6,S} {18,S} +2 C 0 {1,S} {7,S} {8,S} {27,S} +3 C 0 {4,S} {5,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {3,S} {1,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {17,S} {15,S} {19,S} {28,S} +15 C 0 {16,S} {14,S} {18,S} {20,S} +16 C 0 {15,S} {17,S} {21,S} {22,S} +17 C 0 {16,S} {14,S} {23,S} {24,S} +18 C 0 {15,S} {25,S} {26,S} {1,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {16,S} +22 H 0 {16,S} +23 H 0 {17,S} +24 H 0 {17,S} +25 H 0 {18,S} +26 H 0 {18,S} +27 H 0 {2,S} +28 H 0 {14,S} +""")) +species( +label="InChI=1/C10H18/c1-8-2-6-10(8)7-5-9-3-4-9/h8-10H,2-7H2,1H3", +structure=adjacencyList(""" +1 C 0 {2,S} {5,S} {6,S} {27,S} +2 C 0 {1,S} {7,S} {8,S} {14,S} +3 C 0 {4,S} {5,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {3,S} {1,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {17,S} {15,S} {19,S} {2,S} +15 C 0 {16,S} {14,S} {18,S} {20,S} +16 C 0 {15,S} {17,S} {21,S} {22,S} +17 C 0 {16,S} {14,S} {23,S} {24,S} +18 C 0 {15,S} {25,S} {26,S} {28,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {16,S} +22 H 0 {16,S} +23 H 0 {17,S} +24 H 0 {17,S} +25 H 0 {18,S} +26 H 0 {18,S} +27 H 0 {1,S} +28 H 0 {18,S} +""")) +species( +label="InChI=1/C10H18/c1-3-9(4-1)5-2-6-10-7-8-10/h9-10H,1-8H2", +structure=adjacencyList(""" +1 C 0 {2,S} {5,S} {6,S} {27,S} +2 C 0 {1,S} {7,S} {8,S} {18,S} +3 C 0 {4,S} {5,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {3,S} {1,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {17,S} {15,S} {19,S} {28,S} +15 C 0 {16,S} {14,S} {18,S} {20,S} +16 C 0 {15,S} {17,S} {21,S} {22,S} +17 C 0 {16,S} {14,S} {23,S} {24,S} +18 C 0 {15,S} {25,S} {26,S} {2,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {16,S} +22 H 0 {16,S} +23 H 0 {17,S} +24 H 0 {17,S} +25 H 0 {18,S} +26 H 0 {18,S} +27 H 0 {1,S} +28 H 0 {14,S} +""")) +species( +label="InChI=1/C10H18/c1-3-4-5-6-9(2)10-7-8-10/h4-5,9-10H,3,6-8H2,1-2H3", +structure=adjacencyList(""" +1 C 0 {2,S} {5,S} {6,S} {15,S} +2 C 0 {1,S} {7,S} {8,S} {27,S} +3 C 0 {4,S} {5,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {3,S} {1,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {15,S} {18,D} {19,S} +15 C 0 {14,S} {20,S} {21,S} {1,S} +16 C 0 {17,S} {22,S} {23,S} {28,S} +17 C 0 {16,S} {18,S} {24,S} {25,S} +18 C 0 {14,D} {17,S} {26,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {15,S} +22 H 0 {16,S} +23 H 0 {16,S} +24 H 0 {17,S} +25 H 0 {17,S} +26 H 0 {18,S} +27 H 0 {2,S} +28 H 0 {16,S} +""")) +species( +label="InChI=1/C10H18/c1-3-4-5-6-9(2)10-7-8-10/h3-4,9-10H,5-8H2,1-2H3", +structure=adjacencyList(""" +1 C 0 {2,S} {5,S} {6,S} {16,S} +2 C 0 {1,S} {7,S} {8,S} {27,S} +3 C 0 {4,S} {5,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {3,S} {1,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {15,S} {18,D} {19,S} +15 C 0 {14,S} {20,S} {21,S} {28,S} +16 C 0 {17,S} {22,S} {23,S} {1,S} +17 C 0 {16,S} {18,S} {24,S} {25,S} +18 C 0 {14,D} {17,S} {26,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {15,S} +22 H 0 {16,S} +23 H 0 {16,S} +24 H 0 {17,S} +25 H 0 {17,S} +26 H 0 {18,S} +27 H 0 {2,S} +28 H 0 {15,S} +""")) +species( +label="InChI=1/C10H18/c1-3-4-5-6-9(2)10-7-8-10/h3,9-10H,1,4-8H2,2H3", +structure=adjacencyList(""" +1 C 0 {2,S} {5,S} {6,S} {16,S} +2 C 0 {1,S} {7,S} {8,S} {27,S} +3 C 0 {4,S} {5,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {3,S} {1,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {15,D} {18,S} {19,S} +15 C 0 {14,D} {20,S} {21,S} +16 C 0 {17,S} {22,S} {23,S} {1,S} +17 C 0 {16,S} {18,S} {24,S} {25,S} +18 C 0 {17,S} {14,S} {26,S} {28,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {15,S} +22 H 0 {16,S} +23 H 0 {16,S} +24 H 0 {17,S} +25 H 0 {17,S} +26 H 0 {18,S} +27 H 0 {2,S} +28 H 0 {18,S} +""")) +species( +label="InChI=1/C10H18/c1-2-3-4-5-6-7-10-8-9-10/h3-4,10H,2,5-9H2,1H3", +structure=adjacencyList(""" +1 C 0 {2,S} {5,S} {6,S} {27,S} +2 C 0 {1,S} {7,S} {8,S} {15,S} +3 C 0 {4,S} {5,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {3,S} {1,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {15,S} {18,D} {19,S} +15 C 0 {14,S} {20,S} {21,S} {2,S} +16 C 0 {17,S} {22,S} {23,S} {28,S} +17 C 0 {16,S} {18,S} {24,S} {25,S} +18 C 0 {14,D} {17,S} {26,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {15,S} +22 H 0 {16,S} +23 H 0 {16,S} +24 H 0 {17,S} +25 H 0 {17,S} +26 H 0 {18,S} +27 H 0 {1,S} +28 H 0 {16,S} +""")) +species( +label="InChI=1/C10H18/c1-2-3-4-5-6-7-10-8-9-10/h2-3,10H,4-9H2,1H3", +structure=adjacencyList(""" +1 C 0 {2,S} {5,S} {6,S} {27,S} +2 C 0 {1,S} {7,S} {8,S} {16,S} +3 C 0 {4,S} {5,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {3,S} {1,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {15,S} {18,D} {19,S} +15 C 0 {14,S} {20,S} {21,S} {28,S} +16 C 0 {17,S} {22,S} {23,S} {2,S} +17 C 0 {16,S} {18,S} {24,S} {25,S} +18 C 0 {14,D} {17,S} {26,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {15,S} +22 H 0 {16,S} +23 H 0 {16,S} +24 H 0 {17,S} +25 H 0 {17,S} +26 H 0 {18,S} +27 H 0 {1,S} +28 H 0 {15,S} +""")) +species( +label="InChI=1/C10H18/c1-2-3-4-5-6-7-10-8-9-10/h2,10H,1,3-9H2", +structure=adjacencyList(""" +1 C 0 {2,S} {5,S} {6,S} {27,S} +2 C 0 {1,S} {7,S} {8,S} {16,S} +3 C 0 {4,S} {5,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {3,S} {1,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {15,D} {18,S} {19,S} +15 C 0 {14,D} {20,S} {21,S} +16 C 0 {17,S} {22,S} {23,S} {2,S} +17 C 0 {16,S} {18,S} {24,S} {25,S} +18 C 0 {17,S} {14,S} {26,S} {28,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {15,S} +22 H 0 {16,S} +23 H 0 {16,S} +24 H 0 {17,S} +25 H 0 {17,S} +26 H 0 {18,S} +27 H 0 {1,S} +28 H 0 {18,S} +""")) +species( +label="InChI=1/C10H18/c1-4-9(5-2)8(3)10-6-7-10/h4,8-10H,1,5-7H2,2-3H3", +structure=adjacencyList(""" +1 C 0 {2,S} {5,S} {6,S} {18,S} +2 C 0 {1,S} {7,S} {8,S} {27,S} +3 C 0 {4,S} {5,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {3,S} {1,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {15,D} {18,S} {19,S} +15 C 0 {14,D} {20,S} {21,S} +16 C 0 {17,S} {22,S} {23,S} {28,S} +17 C 0 {16,S} {18,S} {24,S} {25,S} +18 C 0 {17,S} {14,S} {26,S} {1,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {15,S} +22 H 0 {16,S} +23 H 0 {16,S} +24 H 0 {17,S} +25 H 0 {17,S} +26 H 0 {18,S} +27 H 0 {2,S} +28 H 0 {16,S} +""")) +species( +label="InChI=1/C10H18/c1-3-9(4-2)5-6-10-7-8-10/h3,9-10H,1,4-8H2,2H3", +structure=adjacencyList(""" +1 C 0 {2,S} {5,S} {6,S} {27,S} +2 C 0 {1,S} {7,S} {8,S} {18,S} +3 C 0 {4,S} {5,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {3,S} {1,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {15,D} {18,S} {19,S} +15 C 0 {14,D} {20,S} {21,S} +16 C 0 {17,S} {22,S} {23,S} {28,S} +17 C 0 {16,S} {18,S} {24,S} {25,S} +18 C 0 {17,S} {14,S} {26,S} {2,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {15,S} +22 H 0 {16,S} +23 H 0 {16,S} +24 H 0 {17,S} +25 H 0 {17,S} +26 H 0 {18,S} +27 H 0 {1,S} +28 H 0 {16,S} +""")) +species( +label="InChI=1/C10H18/c1-3-9-7(2)6-10(9)8-4-5-8/h7-10H,3-6H2,1-2H3", +structure=adjacencyList(""" +1 C 0 {2,S} {18,S} {5,S} {6,S} +2 C 0 {1,S} {14,S} {7,S} {8,S} +3 C 0 {4,S} {5,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {3,S} {1,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {18,S} {2,S} {15,S} {19,S} +15 C 0 {14,S} {20,S} {21,S} {27,S} +16 C 0 {17,S} {22,S} {23,S} {28,S} +17 C 0 {16,S} {18,S} {24,S} {25,S} +18 C 0 {14,S} {1,S} {17,S} {26,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {15,S} +22 H 0 {16,S} +23 H 0 {16,S} +24 H 0 {17,S} +25 H 0 {17,S} +26 H 0 {18,S} +27 H 0 {15,S} +28 H 0 {16,S} +""")) +species( +label="InChI=1/C10H18/c1-3-8-6-10(7(8)2)9-4-5-9/h7-10H,3-6H2,1-2H3", +structure=adjacencyList(""" +1 C 0 {2,S} {14,S} {5,S} {6,S} +2 C 0 {1,S} {18,S} {7,S} {8,S} +3 C 0 {4,S} {5,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {3,S} {1,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {18,S} {1,S} {15,S} {19,S} +15 C 0 {14,S} {20,S} {21,S} {27,S} +16 C 0 {17,S} {22,S} {23,S} {28,S} +17 C 0 {16,S} {18,S} {24,S} {25,S} +18 C 0 {14,S} {2,S} {17,S} {26,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {15,S} +22 H 0 {16,S} +23 H 0 {16,S} +24 H 0 {17,S} +25 H 0 {17,S} +26 H 0 {18,S} +27 H 0 {15,S} +28 H 0 {16,S} +""")) +species( +label="InChI=1/C10H18/c1-2-3-8-6-10(7-8)9-4-5-9/h8-10H,2-7H2,1H3", +structure=adjacencyList(""" +1 C 0 {2,S} {15,S} {5,S} {6,S} +2 C 0 {1,S} {14,S} {7,S} {8,S} +3 C 0 {4,S} {5,S} {9,S} {10,S} +4 C 0 {3,S} {5,S} {11,S} {12,S} +5 C 0 {4,S} {3,S} {1,S} {13,S} +6 H 0 {1,S} +7 H 0 {2,S} +8 H 0 {2,S} +9 H 0 {3,S} +10 H 0 {3,S} +11 H 0 {4,S} +12 H 0 {4,S} +13 H 0 {5,S} +14 C 0 {15,S} {2,S} {18,S} {19,S} +15 C 0 {14,S} {1,S} {20,S} {21,S} +16 C 0 {17,S} {22,S} {23,S} {27,S} +17 C 0 {16,S} {18,S} {24,S} {25,S} +18 C 0 {17,S} {14,S} {26,S} {28,S} +19 H 0 {14,S} +20 H 0 {15,S} +21 H 0 {15,S} +22 H 0 {16,S} +23 H 0 {16,S} +24 H 0 {17,S} +25 H 0 {17,S} +26 H 0 {18,S} +27 H 0 {16,S} +28 H 0 {18,S} +""")) diff --git a/examples/thermoEstimator/scoop/lsf.sh b/examples/thermoEstimator/scoop/lsf.sh new file mode 100755 index 0000000000..b2061860dd --- /dev/null +++ b/examples/thermoEstimator/scoop/lsf.sh @@ -0,0 +1,59 @@ +#!/bin/sh +#BSUB -o RMG.out +#BSUB -J RMGPyScoop +#BSUB -n 8 +#BSUB -e error_log +#BSUB -q medium_priority + +# This is a job submission file for a LSF queuing system to run +# the SCOOP-enabled parallel version of RMG-Py across 8 CPUs on +# a number of different compute nodes on a (potentially heterogeneous) cluster. + +source ~/.bash_profile + +LAMHOST_FILE=hosts + +# start a new host file from scratch +rm -f $LAMHOST_FILE +touch $LAMHOST_FILE +# echo "# LAMMPI host file created by LSF on `date`" >> $LAMHOST_FILE +# check if we were able to start writing the conf file +if [ -f $LAMHOST_FILE ]; then + : +else + echo "$0: can't create $LAMHOST_FILE" + exit 1 +fi +HOST="" +NUM_PROC="" +FLAG="" +TOTAL_CPUS=0 +for TOKEN in $LSB_MCPU_HOSTS +do + if [ -z "$FLAG" ]; then + HOST="$TOKEN" + FLAG="0" + else + NUM_PROC="$TOKEN" + TOTAL_CPUS=`expr $TOTAL_CPUS + $NUM_PROC` + FLAG="1" + fi + if [ "$FLAG" = "1" ]; then + _x=0 + while [ $_x -lt $NUM_PROC ] + do + echo "$HOST" >>$LAMHOST_FILE + _x=`expr $_x + 1` + done + # get ready for the next host + FLAG="" + HOST="" + NUM_PROC="" + fi +done +# last thing added to LAMHOST_FILE +#echo "# end of LAMHOST file" >> $LAMHOST_FILE +echo "Your lamboot hostfile looks like:" +cat $LAMHOST_FILE + +python -m scoop -vv --hostfile $LAMHOST_FILE $RMGpy/thermoEstimator.py input.py > RMG.stdout.log diff --git a/examples/thermoEstimator/scoop/prolog.sh b/examples/thermoEstimator/scoop/prolog.sh new file mode 100755 index 0000000000..8fb7afab28 --- /dev/null +++ b/examples/thermoEstimator/scoop/prolog.sh @@ -0,0 +1,2 @@ +#!/bin/bash +source ~/.bash_profile diff --git a/examples/thermoEstimator/scoop/sge.sh b/examples/thermoEstimator/scoop/sge.sh new file mode 100755 index 0000000000..652e66eee9 --- /dev/null +++ b/examples/thermoEstimator/scoop/sge.sh @@ -0,0 +1,25 @@ +#!/bin/bash +#####################i################################################ +# This is a job submission file for a SGE queuing system to run +# the SCOOP-enabled parallel version of RMG-Py across 48 CPUs on +# a single node. +# +# Define RMGPy as the path to rmg.py in your ~/.bash_profile +# NSLOTS is an SGE env. variable for total number of CPUs. +# prolog.sh is a script used by SCOOP to pass env. variables +# +# You can run the jobs on different nodes as well, but it is not +# recommended since you might have problems with SGE job termination. +# Type `qconf -spl` to see available parallel environments and modify +# the last #$ line if you really want to run it on many nodes. +#####################i################################################ +#$ -S /bin/bash +#$ -cwd +#$ -notify +#$ -o job.log -j y +#$ -N RMGscoop +#$ -l normal +#$ -l h_rt=09:05:00 +#$ -pe singlenode 48 +source ~/.bash_profile +python -m scoop.__main__ --tunnel --prolog $RMGpy/examples/rmg/scoop/prolog.sh -n $NSLOTS $RMGpy/thermoEstimator.py input.py > std.out diff --git a/external/gprof2dot.py b/external/gprof2dot.py new file mode 100755 index 0000000000..55eb53ad84 --- /dev/null +++ b/external/gprof2dot.py @@ -0,0 +1,2227 @@ +#!/usr/bin/env python +# +# Copyright 2008-2009 Jose Fonseca +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . +# + +"""Generate a dot graph from the output of several profilers.""" + +__author__ = "Jose Fonseca" + +__version__ = "1.0" + + +import sys +import math +import os.path +import re +import textwrap +import optparse +import xml.parsers.expat + + +try: + # Debugging helper module + import debug +except ImportError: + pass + + +def percentage(p): + return "%.02f%%" % (p*100.0,) + +def add(a, b): + return a + b + +def equal(a, b): + if a == b: + return a + else: + return None + +def fail(a, b): + assert False + + +tol = 2 ** -23 + +def ratio(numerator, denominator): + try: + ratio = float(numerator)/float(denominator) + except ZeroDivisionError: + # 0/0 is undefined, but 1.0 yields more useful results + return 1.0 + if ratio < 0.0: + if ratio < -tol: + sys.stderr.write('warning: negative ratio (%s/%s)\n' % (numerator, denominator)) + return 0.0 + if ratio > 1.0: + if ratio > 1.0 + tol: + sys.stderr.write('warning: ratio greater than one (%s/%s)\n' % (numerator, denominator)) + return 1.0 + return ratio + + +class UndefinedEvent(Exception): + """Raised when attempting to get an event which is undefined.""" + + def __init__(self, event): + Exception.__init__(self) + self.event = event + + def __str__(self): + return 'unspecified event %s' % self.event.name + + +class Event(object): + """Describe a kind of event, and its basic operations.""" + + def __init__(self, name, null, aggregator, formatter = str): + self.name = name + self._null = null + self._aggregator = aggregator + self._formatter = formatter + + def __eq__(self, other): + return self is other + + def __hash__(self): + return id(self) + + def null(self): + return self._null + + def aggregate(self, val1, val2): + """Aggregate two event values.""" + assert val1 is not None + assert val2 is not None + return self._aggregator(val1, val2) + + def format(self, val): + """Format an event value.""" + assert val is not None + return self._formatter(val) + + +MODULE = Event("Module", None, equal) +PROCESS = Event("Process", None, equal) + +CALLS = Event("Calls", 0, add) +SAMPLES = Event("Samples", 0, add) +SAMPLES2 = Event("Samples", 0, add) + +TIME = Event("Time", 0.0, add, lambda x: '(' + str(x) + ')') +TIME_RATIO = Event("Time ratio", 0.0, add, lambda x: '(' + percentage(x) + ')') +TOTAL_TIME = Event("Total time", 0.0, fail) +TOTAL_TIME_RATIO = Event("Total time ratio", 0.0, fail, percentage) + +CALL_RATIO = Event("Call ratio", 0.0, add, percentage) + +PRUNE_RATIO = Event("Prune ratio", 0.0, add, percentage) + + +class Object(object): + """Base class for all objects in profile which can store events.""" + + def __init__(self, events=None): + if events is None: + self.events = {} + else: + self.events = events + + def __hash__(self): + return id(self) + + def __eq__(self, other): + return self is other + + def __contains__(self, event): + return event in self.events + + def __getitem__(self, event): + try: + return self.events[event] + except KeyError: + raise UndefinedEvent(event) + + def __setitem__(self, event, value): + if value is None: + if event in self.events: + del self.events[event] + else: + self.events[event] = value + + +class Call(Object): + """A call between functions. + + There should be at most one call object for every pair of functions. + """ + + def __init__(self, callee_id): + Object.__init__(self) + self.callee_id = callee_id + + +class Function(Object): + """A function.""" + + def __init__(self, id, name): + Object.__init__(self) + self.id = id + self.name = name + self.calls = {} + self.cycle = None + + def add_call(self, call): + if call.callee_id in self.calls: + sys.stderr.write('warning: overwriting call from function %s to %s\n' % (str(self.id), str(call.callee_id))) + self.calls[call.callee_id] = call + + # TODO: write utility functions + + def __repr__(self): + return self.name + + +class Cycle(Object): + """A cycle made from recursive function calls.""" + + def __init__(self): + Object.__init__(self) + # XXX: Do cycles need an id? + self.functions = set() + + def add_function(self, function): + assert function not in self.functions + self.functions.add(function) + # XXX: Aggregate events? + if function.cycle is not None: + for other in function.cycle.functions: + if function not in self.functions: + self.add_function(other) + function.cycle = self + + +class Profile(Object): + """The whole profile.""" + + def __init__(self): + Object.__init__(self) + self.functions = {} + self.cycles = [] + + def add_function(self, function): + if function.id in self.functions: + sys.stderr.write('warning: overwriting function %s (id %s)\n' % (function.name, str(function.id))) + self.functions[function.id] = function + + def add_cycle(self, cycle): + self.cycles.append(cycle) + + def validate(self): + """Validate the edges.""" + + for function in self.functions.itervalues(): + for callee_id in function.calls.keys(): + assert function.calls[callee_id].callee_id == callee_id + if callee_id not in self.functions: + sys.stderr.write('warning: call to undefined function %s from function %s\n' % (str(callee_id), function.name)) + del function.calls[callee_id] + + def find_cycles(self): + """Find cycles using Tarjan's strongly connected components algorithm.""" + + # Apply the Tarjan's algorithm successively until all functions are visited + visited = set() + for function in self.functions.itervalues(): + if function not in visited: + self._tarjan(function, 0, [], {}, {}, visited) + cycles = [] + for function in self.functions.itervalues(): + if function.cycle is not None and function.cycle not in cycles: + cycles.append(function.cycle) + self.cycles = cycles + if 0: + for cycle in cycles: + sys.stderr.write("Cycle:\n") + for member in cycle.functions: + sys.stderr.write("\tFunction %s\n" % member.name) + + def _tarjan(self, function, order, stack, orders, lowlinks, visited): + """Tarjan's strongly connected components algorithm. + + See also: + - http://en.wikipedia.org/wiki/Tarjan's_strongly_connected_components_algorithm + """ + + visited.add(function) + orders[function] = order + lowlinks[function] = order + order += 1 + pos = len(stack) + stack.append(function) + for call in function.calls.itervalues(): + callee = self.functions[call.callee_id] + # TODO: use a set to optimize lookup + if callee not in orders: + order = self._tarjan(callee, order, stack, orders, lowlinks, visited) + lowlinks[function] = min(lowlinks[function], lowlinks[callee]) + elif callee in stack: + lowlinks[function] = min(lowlinks[function], orders[callee]) + if lowlinks[function] == orders[function]: + # Strongly connected component found + members = stack[pos:] + del stack[pos:] + if len(members) > 1: + cycle = Cycle() + for member in members: + cycle.add_function(member) + return order + + def call_ratios(self, event): + # Aggregate for incoming calls + cycle_totals = {} + for cycle in self.cycles: + cycle_totals[cycle] = 0.0 + function_totals = {} + for function in self.functions.itervalues(): + function_totals[function] = 0.0 + for function in self.functions.itervalues(): + for call in function.calls.itervalues(): + if call.callee_id != function.id: + callee = self.functions[call.callee_id] + function_totals[callee] += call[event] + if callee.cycle is not None and callee.cycle is not function.cycle: + cycle_totals[callee.cycle] += call[event] + + # Compute the ratios + for function in self.functions.itervalues(): + for call in function.calls.itervalues(): + assert CALL_RATIO not in call + if call.callee_id != function.id: + callee = self.functions[call.callee_id] + if callee.cycle is not None and callee.cycle is not function.cycle: + total = cycle_totals[callee.cycle] + else: + total = function_totals[callee] + call[CALL_RATIO] = ratio(call[event], total) + + def integrate(self, outevent, inevent): + """Propagate function time ratio allong the function calls. + + Must be called after finding the cycles. + + See also: + - http://citeseer.ist.psu.edu/graham82gprof.html + """ + + # Sanity checking + assert outevent not in self + for function in self.functions.itervalues(): + assert outevent not in function + assert inevent in function + for call in function.calls.itervalues(): + assert outevent not in call + if call.callee_id != function.id: + assert CALL_RATIO in call + + # Aggregate the input for each cycle + for cycle in self.cycles: + total = inevent.null() + for function in self.functions.itervalues(): + total = inevent.aggregate(total, function[inevent]) + self[inevent] = total + + # Integrate along the edges + total = inevent.null() + for function in self.functions.itervalues(): + total = inevent.aggregate(total, function[inevent]) + self._integrate_function(function, outevent, inevent) + self[outevent] = total + + def _integrate_function(self, function, outevent, inevent): + if function.cycle is not None: + return self._integrate_cycle(function.cycle, outevent, inevent) + else: + if outevent not in function: + total = function[inevent] + for call in function.calls.itervalues(): + if call.callee_id != function.id: + total += self._integrate_call(call, outevent, inevent) + function[outevent] = total + return function[outevent] + + def _integrate_call(self, call, outevent, inevent): + assert outevent not in call + assert CALL_RATIO in call + callee = self.functions[call.callee_id] + subtotal = call[CALL_RATIO]*self._integrate_function(callee, outevent, inevent) + call[outevent] = subtotal + return subtotal + + def _integrate_cycle(self, cycle, outevent, inevent): + if outevent not in cycle: + + # Compute the outevent for the whole cycle + total = inevent.null() + for member in cycle.functions: + subtotal = member[inevent] + for call in member.calls.itervalues(): + callee = self.functions[call.callee_id] + if callee.cycle is not cycle: + subtotal += self._integrate_call(call, outevent, inevent) + total += subtotal + cycle[outevent] = total + + # Compute the time propagated to callers of this cycle + callees = {} + for function in self.functions.itervalues(): + if function.cycle is not cycle: + for call in function.calls.itervalues(): + callee = self.functions[call.callee_id] + if callee.cycle is cycle: + try: + callees[callee] += call[CALL_RATIO] + except KeyError: + callees[callee] = call[CALL_RATIO] + + for member in cycle.functions: + member[outevent] = outevent.null() + + for callee, call_ratio in callees.iteritems(): + ranks = {} + call_ratios = {} + partials = {} + self._rank_cycle_function(cycle, callee, 0, ranks) + self._call_ratios_cycle(cycle, callee, ranks, call_ratios, set()) + partial = self._integrate_cycle_function(cycle, callee, call_ratio, partials, ranks, call_ratios, outevent, inevent) + assert partial == max(partials.values()) + assert not total or abs(1.0 - partial/(call_ratio*total)) <= 0.001 + + return cycle[outevent] + + def _rank_cycle_function(self, cycle, function, rank, ranks): + if function not in ranks or ranks[function] > rank: + ranks[function] = rank + for call in function.calls.itervalues(): + if call.callee_id != function.id: + callee = self.functions[call.callee_id] + if callee.cycle is cycle: + self._rank_cycle_function(cycle, callee, rank + 1, ranks) + + def _call_ratios_cycle(self, cycle, function, ranks, call_ratios, visited): + if function not in visited: + visited.add(function) + for call in function.calls.itervalues(): + if call.callee_id != function.id: + callee = self.functions[call.callee_id] + if callee.cycle is cycle: + if ranks[callee] > ranks[function]: + call_ratios[callee] = call_ratios.get(callee, 0.0) + call[CALL_RATIO] + self._call_ratios_cycle(cycle, callee, ranks, call_ratios, visited) + + def _integrate_cycle_function(self, cycle, function, partial_ratio, partials, ranks, call_ratios, outevent, inevent): + if function not in partials: + partial = partial_ratio*function[inevent] + for call in function.calls.itervalues(): + if call.callee_id != function.id: + callee = self.functions[call.callee_id] + if callee.cycle is not cycle: + assert outevent in call + partial += partial_ratio*call[outevent] + else: + if ranks[callee] > ranks[function]: + callee_partial = self._integrate_cycle_function(cycle, callee, partial_ratio, partials, ranks, call_ratios, outevent, inevent) + call_ratio = ratio(call[CALL_RATIO], call_ratios[callee]) + call_partial = call_ratio*callee_partial + try: + call[outevent] += call_partial + except UndefinedEvent: + call[outevent] = call_partial + partial += call_partial + partials[function] = partial + try: + function[outevent] += partial + except UndefinedEvent: + function[outevent] = partial + return partials[function] + + def aggregate(self, event): + """Aggregate an event for the whole profile.""" + + total = event.null() + for function in self.functions.itervalues(): + try: + total = event.aggregate(total, function[event]) + except UndefinedEvent: + return + self[event] = total + + def ratio(self, outevent, inevent): + assert outevent not in self + assert inevent in self + for function in self.functions.itervalues(): + assert outevent not in function + assert inevent in function + function[outevent] = ratio(function[inevent], self[inevent]) + for call in function.calls.itervalues(): + assert outevent not in call + if inevent in call: + call[outevent] = ratio(call[inevent], self[inevent]) + self[outevent] = 1.0 + + def prune(self, node_thres, edge_thres): + """Prune the profile""" + + # compute the prune ratios + for function in self.functions.itervalues(): + try: + function[PRUNE_RATIO] = function[TOTAL_TIME_RATIO] + except UndefinedEvent: + pass + + for call in function.calls.itervalues(): + callee = self.functions[call.callee_id] + + if TOTAL_TIME_RATIO in call: + # handle exact cases first + call[PRUNE_RATIO] = call[TOTAL_TIME_RATIO] + else: + try: + # make a safe estimate + call[PRUNE_RATIO] = min(function[TOTAL_TIME_RATIO], callee[TOTAL_TIME_RATIO]) + except UndefinedEvent: + pass + + # prune the nodes + for function_id in self.functions.keys(): + function = self.functions[function_id] + try: + if function[PRUNE_RATIO] < node_thres: + del self.functions[function_id] + except UndefinedEvent: + pass + + # prune the egdes + for function in self.functions.itervalues(): + for callee_id in function.calls.keys(): + call = function.calls[callee_id] + try: + if callee_id not in self.functions or call[PRUNE_RATIO] < edge_thres: + del function.calls[callee_id] + except UndefinedEvent: + pass + + def dump(self): + for function in self.functions.itervalues(): + sys.stderr.write('Function %s:\n' % (function.name,)) + self._dump_events(function.events) + for call in function.calls.itervalues(): + callee = self.functions[call.callee_id] + sys.stderr.write(' Call %s:\n' % (callee.name,)) + self._dump_events(call.events) + for cycle in self.cycles: + sys.stderr.write('Cycle:\n') + self._dump_events(cycle.events) + for function in cycle.functions: + sys.stderr.write(' Function %s\n' % (function.name,)) + + def _dump_events(self, events): + for event, value in events.iteritems(): + sys.stderr.write(' %s: %s\n' % (event.name, event.format(value))) + + +class Struct: + """Masquerade a dictionary with a structure-like behavior.""" + + def __init__(self, attrs = None): + if attrs is None: + attrs = {} + self.__dict__['_attrs'] = attrs + + def __getattr__(self, name): + try: + return self._attrs[name] + except KeyError: + raise AttributeError(name) + + def __setattr__(self, name, value): + self._attrs[name] = value + + def __str__(self): + return str(self._attrs) + + def __repr__(self): + return repr(self._attrs) + + +class ParseError(Exception): + """Raised when parsing to signal mismatches.""" + + def __init__(self, msg, line): + self.msg = msg + # TODO: store more source line information + self.line = line + + def __str__(self): + return '%s: %r' % (self.msg, self.line) + + +class Parser: + """Parser interface.""" + + def __init__(self): + pass + + def parse(self): + raise NotImplementedError + + +class LineParser(Parser): + """Base class for parsers that read line-based formats.""" + + def __init__(self, file): + Parser.__init__(self) + self._file = file + self.__line = None + self.__eof = False + + def readline(self): + line = self._file.readline() + if not line: + self.__line = '' + self.__eof = True + self.__line = line.rstrip('\r\n') + + def lookahead(self): + assert self.__line is not None + return self.__line + + def consume(self): + assert self.__line is not None + line = self.__line + self.readline() + return line + + def eof(self): + assert self.__line is not None + return self.__eof + + +XML_ELEMENT_START, XML_ELEMENT_END, XML_CHARACTER_DATA, XML_EOF = range(4) + + +class XmlToken: + + def __init__(self, type, name_or_data, attrs = None, line = None, column = None): + assert type in (XML_ELEMENT_START, XML_ELEMENT_END, XML_CHARACTER_DATA, XML_EOF) + self.type = type + self.name_or_data = name_or_data + self.attrs = attrs + self.line = line + self.column = column + + def __str__(self): + if self.type == XML_ELEMENT_START: + return '<' + self.name_or_data + ' ...>' + if self.type == XML_ELEMENT_END: + return '' + if self.type == XML_CHARACTER_DATA: + return self.name_or_data + if self.type == XML_EOF: + return 'end of file' + assert 0 + + +class XmlTokenizer: + """Expat based XML tokenizer.""" + + def __init__(self, fp, skip_ws = True): + self.fp = fp + self.tokens = [] + self.index = 0 + self.final = False + self.skip_ws = skip_ws + + self.character_pos = 0, 0 + self.character_data = '' + + self.parser = xml.parsers.expat.ParserCreate() + self.parser.StartElementHandler = self.handle_element_start + self.parser.EndElementHandler = self.handle_element_end + self.parser.CharacterDataHandler = self.handle_character_data + + def handle_element_start(self, name, attributes): + self.finish_character_data() + line, column = self.pos() + token = XmlToken(XML_ELEMENT_START, name, attributes, line, column) + self.tokens.append(token) + + def handle_element_end(self, name): + self.finish_character_data() + line, column = self.pos() + token = XmlToken(XML_ELEMENT_END, name, None, line, column) + self.tokens.append(token) + + def handle_character_data(self, data): + if not self.character_data: + self.character_pos = self.pos() + self.character_data += data + + def finish_character_data(self): + if self.character_data: + if not self.skip_ws or not self.character_data.isspace(): + line, column = self.character_pos + token = XmlToken(XML_CHARACTER_DATA, self.character_data, None, line, column) + self.tokens.append(token) + self.character_data = '' + + def next(self): + size = 16*1024 + while self.index >= len(self.tokens) and not self.final: + self.tokens = [] + self.index = 0 + data = self.fp.read(size) + self.final = len(data) < size + try: + self.parser.Parse(data, self.final) + except xml.parsers.expat.ExpatError, e: + #if e.code == xml.parsers.expat.errors.XML_ERROR_NO_ELEMENTS: + if e.code == 3: + pass + else: + raise e + if self.index >= len(self.tokens): + line, column = self.pos() + token = XmlToken(XML_EOF, None, None, line, column) + else: + token = self.tokens[self.index] + self.index += 1 + return token + + def pos(self): + return self.parser.CurrentLineNumber, self.parser.CurrentColumnNumber + + +class XmlTokenMismatch(Exception): + + def __init__(self, expected, found): + self.expected = expected + self.found = found + + def __str__(self): + return '%u:%u: %s expected, %s found' % (self.found.line, self.found.column, str(self.expected), str(self.found)) + + +class XmlParser(Parser): + """Base XML document parser.""" + + def __init__(self, fp): + Parser.__init__(self) + self.tokenizer = XmlTokenizer(fp) + self.consume() + + def consume(self): + self.token = self.tokenizer.next() + + def match_element_start(self, name): + return self.token.type == XML_ELEMENT_START and self.token.name_or_data == name + + def match_element_end(self, name): + return self.token.type == XML_ELEMENT_END and self.token.name_or_data == name + + def element_start(self, name): + while self.token.type == XML_CHARACTER_DATA: + self.consume() + if self.token.type != XML_ELEMENT_START: + raise XmlTokenMismatch(XmlToken(XML_ELEMENT_START, name), self.token) + if self.token.name_or_data != name: + raise XmlTokenMismatch(XmlToken(XML_ELEMENT_START, name), self.token) + attrs = self.token.attrs + self.consume() + return attrs + + def element_end(self, name): + while self.token.type == XML_CHARACTER_DATA: + self.consume() + if self.token.type != XML_ELEMENT_END: + raise XmlTokenMismatch(XmlToken(XML_ELEMENT_END, name), self.token) + if self.token.name_or_data != name: + raise XmlTokenMismatch(XmlToken(XML_ELEMENT_END, name), self.token) + self.consume() + + def character_data(self, strip = True): + data = '' + while self.token.type == XML_CHARACTER_DATA: + data += self.token.name_or_data + self.consume() + if strip: + data = data.strip() + return data + + +class GprofParser(Parser): + """Parser for GNU gprof output. + + See also: + - Chapter "Interpreting gprof's Output" from the GNU gprof manual + http://sourceware.org/binutils/docs-2.18/gprof/Call-Graph.html#Call-Graph + - File "cg_print.c" from the GNU gprof source code + http://sourceware.org/cgi-bin/cvsweb.cgi/~checkout~/src/gprof/cg_print.c?rev=1.12&cvsroot=src + """ + + def __init__(self, fp): + Parser.__init__(self) + self.fp = fp + self.functions = {} + self.cycles = {} + + def readline(self): + line = self.fp.readline() + if not line: + sys.stderr.write('error: unexpected end of file\n') + sys.exit(1) + line = line.rstrip('\r\n') + return line + + _int_re = re.compile(r'^\d+$') + _float_re = re.compile(r'^\d+\.\d+$') + + def translate(self, mo): + """Extract a structure from a match object, while translating the types in the process.""" + attrs = {} + groupdict = mo.groupdict() + for name, value in groupdict.iteritems(): + if value is None: + value = None + elif self._int_re.match(value): + value = int(value) + elif self._float_re.match(value): + value = float(value) + attrs[name] = (value) + return Struct(attrs) + + _cg_header_re = re.compile( + # original gprof header + r'^\s+called/total\s+parents\s*$|' + + r'^index\s+%time\s+self\s+descendents\s+called\+self\s+name\s+index\s*$|' + + r'^\s+called/total\s+children\s*$|' + + # GNU gprof header + r'^index\s+%\s+time\s+self\s+children\s+called\s+name\s*$' + ) + + _cg_ignore_re = re.compile( + # spontaneous + r'^\s+\s*$|' + # internal calls (such as "mcount") + r'^.*\((\d+)\)$' + ) + + _cg_primary_re = re.compile( + r'^\[(?P\d+)\]?' + + r'\s+(?P\d+\.\d+)' + + r'\s+(?P\d+\.\d+)' + + r'\s+(?P\d+\.\d+)' + + r'\s+(?:(?P\d+)(?:\+(?P\d+))?)?' + + r'\s+(?P\S.*?)' + + r'(?:\s+\d+)>)?' + + r'\s\[(\d+)\]$' + ) + + _cg_parent_re = re.compile( + r'^\s+(?P\d+\.\d+)?' + + r'\s+(?P\d+\.\d+)?' + + r'\s+(?P\d+)(?:/(?P\d+))?' + + r'\s+(?P\S.*?)' + + r'(?:\s+\d+)>)?' + + r'\s\[(?P\d+)\]$' + ) + + _cg_child_re = _cg_parent_re + + _cg_cycle_header_re = re.compile( + r'^\[(?P\d+)\]?' + + r'\s+(?P\d+\.\d+)' + + r'\s+(?P\d+\.\d+)' + + r'\s+(?P\d+\.\d+)' + + r'\s+(?:(?P\d+)(?:\+(?P\d+))?)?' + + r'\s+\d+)\sas\sa\swhole>' + + r'\s\[(\d+)\]$' + ) + + _cg_cycle_member_re = re.compile( + r'^\s+(?P\d+\.\d+)?' + + r'\s+(?P\d+\.\d+)?' + + r'\s+(?P\d+)(?:\+(?P\d+))?' + + r'\s+(?P\S.*?)' + + r'(?:\s+\d+)>)?' + + r'\s\[(?P\d+)\]$' + ) + + _cg_sep_re = re.compile(r'^--+$') + + def parse_function_entry(self, lines): + parents = [] + children = [] + + while True: + if not lines: + sys.stderr.write('warning: unexpected end of entry\n') + line = lines.pop(0) + if line.startswith('['): + break + + # read function parent line + mo = self._cg_parent_re.match(line) + if not mo: + if self._cg_ignore_re.match(line): + continue + sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line) + else: + parent = self.translate(mo) + parents.append(parent) + + # read primary line + mo = self._cg_primary_re.match(line) + if not mo: + sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line) + return + else: + function = self.translate(mo) + + while lines: + line = lines.pop(0) + + # read function subroutine line + mo = self._cg_child_re.match(line) + if not mo: + if self._cg_ignore_re.match(line): + continue + sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line) + else: + child = self.translate(mo) + children.append(child) + + function.parents = parents + function.children = children + + self.functions[function.index] = function + + def parse_cycle_entry(self, lines): + + # read cycle header line + line = lines[0] + mo = self._cg_cycle_header_re.match(line) + if not mo: + sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line) + return + cycle = self.translate(mo) + + # read cycle member lines + cycle.functions = [] + for line in lines[1:]: + mo = self._cg_cycle_member_re.match(line) + if not mo: + sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line) + continue + call = self.translate(mo) + cycle.functions.append(call) + + self.cycles[cycle.cycle] = cycle + + def parse_cg_entry(self, lines): + if lines[0].startswith("["): + self.parse_cycle_entry(lines) + else: + self.parse_function_entry(lines) + + def parse_cg(self): + """Parse the call graph.""" + + # skip call graph header + while not self._cg_header_re.match(self.readline()): + pass + line = self.readline() + while self._cg_header_re.match(line): + line = self.readline() + + # process call graph entries + entry_lines = [] + while line != '\014': # form feed + if line and not line.isspace(): + if self._cg_sep_re.match(line): + self.parse_cg_entry(entry_lines) + entry_lines = [] + else: + entry_lines.append(line) + line = self.readline() + + def parse(self): + self.parse_cg() + self.fp.close() + + profile = Profile() + profile[TIME] = 0.0 + + cycles = {} + for index in self.cycles.iterkeys(): + cycles[index] = Cycle() + + for entry in self.functions.itervalues(): + # populate the function + function = Function(entry.index, entry.name) + function[TIME] = entry.self + if entry.called is not None: + function[CALLS] = entry.called + if entry.called_self is not None: + call = Call(entry.index) + call[CALLS] = entry.called_self + function[CALLS] += entry.called_self + + # populate the function calls + for child in entry.children: + call = Call(child.index) + + assert child.called is not None + call[CALLS] = child.called + + if child.index not in self.functions: + # NOTE: functions that were never called but were discovered by gprof's + # static call graph analysis dont have a call graph entry so we need + # to add them here + missing = Function(child.index, child.name) + function[TIME] = 0.0 + function[CALLS] = 0 + profile.add_function(missing) + + function.add_call(call) + + profile.add_function(function) + + if entry.cycle is not None: + try: + cycle = cycles[entry.cycle] + except KeyError: + sys.stderr.write('warning: entry missing\n' % entry.cycle) + cycle = Cycle() + cycles[entry.cycle] = cycle + cycle.add_function(function) + + profile[TIME] = profile[TIME] + function[TIME] + + for cycle in cycles.itervalues(): + profile.add_cycle(cycle) + + # Compute derived events + profile.validate() + profile.ratio(TIME_RATIO, TIME) + profile.call_ratios(CALLS) + profile.integrate(TOTAL_TIME, TIME) + profile.ratio(TOTAL_TIME_RATIO, TOTAL_TIME) + + return profile + + +class OprofileParser(LineParser): + """Parser for oprofile callgraph output. + + See also: + - http://oprofile.sourceforge.net/doc/opreport.html#opreport-callgraph + """ + + _fields_re = { + 'samples': r'(?P\d+)', + '%': r'(?P\S+)', + 'linenr info': r'(?P\(no location information\)|\S+:\d+)', + 'image name': r'(?P\S+(?:\s\(tgid:[^)]*\))?)', + 'app name': r'(?P\S+)', + 'symbol name': r'(?P\(no symbols\)|.+?)', + } + + def __init__(self, infile): + LineParser.__init__(self, infile) + self.entries = {} + self.entry_re = None + + def add_entry(self, callers, function, callees): + try: + entry = self.entries[function.id] + except KeyError: + self.entries[function.id] = (callers, function, callees) + else: + callers_total, function_total, callees_total = entry + self.update_subentries_dict(callers_total, callers) + function_total.samples += function.samples + self.update_subentries_dict(callees_total, callees) + + def update_subentries_dict(self, totals, partials): + for partial in partials.itervalues(): + try: + total = totals[partial.id] + except KeyError: + totals[partial.id] = partial + else: + total.samples += partial.samples + + def parse(self): + # read lookahead + self.readline() + + self.parse_header() + while self.lookahead(): + self.parse_entry() + + profile = Profile() + + reverse_call_samples = {} + + # populate the profile + profile[SAMPLES] = 0 + for _callers, _function, _callees in self.entries.itervalues(): + function = Function(_function.id, _function.name) + function[SAMPLES] = _function.samples + profile.add_function(function) + profile[SAMPLES] += _function.samples + + if _function.application: + function[PROCESS] = os.path.basename(_function.application) + if _function.image: + function[MODULE] = os.path.basename(_function.image) + + total_callee_samples = 0 + for _callee in _callees.itervalues(): + total_callee_samples += _callee.samples + + for _callee in _callees.itervalues(): + if not _callee.self: + call = Call(_callee.id) + call[SAMPLES2] = _callee.samples + function.add_call(call) + + # compute derived data + profile.validate() + profile.find_cycles() + profile.ratio(TIME_RATIO, SAMPLES) + profile.call_ratios(SAMPLES2) + profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO) + + return profile + + def parse_header(self): + while not self.match_header(): + self.consume() + line = self.lookahead() + fields = re.split(r'\s\s+', line) + entry_re = r'^\s*' + r'\s+'.join([self._fields_re[field] for field in fields]) + r'(?P\s+\[self\])?$' + self.entry_re = re.compile(entry_re) + self.skip_separator() + + def parse_entry(self): + callers = self.parse_subentries() + if self.match_primary(): + function = self.parse_subentry() + if function is not None: + callees = self.parse_subentries() + self.add_entry(callers, function, callees) + self.skip_separator() + + def parse_subentries(self): + subentries = {} + while self.match_secondary(): + subentry = self.parse_subentry() + subentries[subentry.id] = subentry + return subentries + + def parse_subentry(self): + entry = Struct() + line = self.consume() + mo = self.entry_re.match(line) + if not mo: + raise ParseError('failed to parse', line) + fields = mo.groupdict() + entry.samples = int(fields.get('samples', 0)) + entry.percentage = float(fields.get('percentage', 0.0)) + if 'source' in fields and fields['source'] != '(no location information)': + source = fields['source'] + filename, lineno = source.split(':') + entry.filename = filename + entry.lineno = int(lineno) + else: + source = '' + entry.filename = None + entry.lineno = None + entry.image = fields.get('image', '') + entry.application = fields.get('application', '') + if 'symbol' in fields and fields['symbol'] != '(no symbols)': + entry.symbol = fields['symbol'] + else: + entry.symbol = '' + if entry.symbol.startswith('"') and entry.symbol.endswith('"'): + entry.symbol = entry.symbol[1:-1] + entry.id = ':'.join((entry.application, entry.image, source, entry.symbol)) + entry.self = fields.get('self', None) != None + if entry.self: + entry.id += ':self' + if entry.symbol: + entry.name = entry.symbol + else: + entry.name = entry.image + return entry + + def skip_separator(self): + while not self.match_separator(): + self.consume() + self.consume() + + def match_header(self): + line = self.lookahead() + return line.startswith('samples') + + def match_separator(self): + line = self.lookahead() + return line == '-'*len(line) + + def match_primary(self): + line = self.lookahead() + return not line[:1].isspace() + + def match_secondary(self): + line = self.lookahead() + return line[:1].isspace() + + +class SysprofParser(XmlParser): + + def __init__(self, stream): + XmlParser.__init__(self, stream) + + def parse(self): + objects = {} + nodes = {} + + self.element_start('profile') + while self.token.type == XML_ELEMENT_START: + if self.token.name_or_data == 'objects': + assert not objects + objects = self.parse_items('objects') + elif self.token.name_or_data == 'nodes': + assert not nodes + nodes = self.parse_items('nodes') + else: + self.parse_value(self.token.name_or_data) + self.element_end('profile') + + return self.build_profile(objects, nodes) + + def parse_items(self, name): + assert name[-1] == 's' + items = {} + self.element_start(name) + while self.token.type == XML_ELEMENT_START: + id, values = self.parse_item(name[:-1]) + assert id not in items + items[id] = values + self.element_end(name) + return items + + def parse_item(self, name): + attrs = self.element_start(name) + id = int(attrs['id']) + values = self.parse_values() + self.element_end(name) + return id, values + + def parse_values(self): + values = {} + while self.token.type == XML_ELEMENT_START: + name = self.token.name_or_data + value = self.parse_value(name) + assert name not in values + values[name] = value + return values + + def parse_value(self, tag): + self.element_start(tag) + value = self.character_data() + self.element_end(tag) + if value.isdigit(): + return int(value) + if value.startswith('"') and value.endswith('"'): + return value[1:-1] + return value + + def build_profile(self, objects, nodes): + profile = Profile() + + profile[SAMPLES] = 0 + for id, object in objects.iteritems(): + # Ignore fake objects (process names, modules, "Everything", "kernel", etc.) + if object['self'] == 0: + continue + + function = Function(id, object['name']) + function[SAMPLES] = object['self'] + profile.add_function(function) + profile[SAMPLES] += function[SAMPLES] + + for id, node in nodes.iteritems(): + # Ignore fake calls + if node['self'] == 0: + continue + + # Find a non-ignored parent + parent_id = node['parent'] + while parent_id != 0: + parent = nodes[parent_id] + caller_id = parent['object'] + if objects[caller_id]['self'] != 0: + break + parent_id = parent['parent'] + if parent_id == 0: + continue + + callee_id = node['object'] + + assert objects[caller_id]['self'] + assert objects[callee_id]['self'] + + function = profile.functions[caller_id] + + samples = node['self'] + try: + call = function.calls[callee_id] + except KeyError: + call = Call(callee_id) + call[SAMPLES2] = samples + function.add_call(call) + else: + call[SAMPLES2] += samples + + # Compute derived events + profile.validate() + profile.find_cycles() + profile.ratio(TIME_RATIO, SAMPLES) + profile.call_ratios(SAMPLES2) + profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO) + + return profile + + +class SharkParser(LineParser): + """Parser for MacOSX Shark output. + + Author: tom@dbservice.com + """ + + def __init__(self, infile): + LineParser.__init__(self, infile) + self.stack = [] + self.entries = {} + + def add_entry(self, function): + try: + entry = self.entries[function.id] + except KeyError: + self.entries[function.id] = (function, { }) + else: + function_total, callees_total = entry + function_total.samples += function.samples + + def add_callee(self, function, callee): + func, callees = self.entries[function.id] + try: + entry = callees[callee.id] + except KeyError: + callees[callee.id] = callee + else: + entry.samples += callee.samples + + def parse(self): + self.readline() + self.readline() + self.readline() + self.readline() + + match = re.compile(r'(?P[|+ ]*)(?P\d+), (?P[^,]+), (?P.*)') + + while self.lookahead(): + line = self.consume() + mo = match.match(line) + if not mo: + raise ParseError('failed to parse', line) + + fields = mo.groupdict() + prefix = len(fields.get('prefix', 0)) / 2 - 1 + + symbol = str(fields.get('symbol', 0)) + image = str(fields.get('image', 0)) + + entry = Struct() + entry.id = ':'.join([symbol, image]) + entry.samples = int(fields.get('samples', 0)) + + entry.name = symbol + entry.image = image + + # adjust the callstack + if prefix < len(self.stack): + del self.stack[prefix:] + + if prefix == len(self.stack): + self.stack.append(entry) + + # if the callstack has had an entry, it's this functions caller + if prefix > 0: + self.add_callee(self.stack[prefix - 1], entry) + + self.add_entry(entry) + + profile = Profile() + profile[SAMPLES] = 0 + for _function, _callees in self.entries.itervalues(): + function = Function(_function.id, _function.name) + function[SAMPLES] = _function.samples + profile.add_function(function) + profile[SAMPLES] += _function.samples + + if _function.image: + function[MODULE] = os.path.basename(_function.image) + + for _callee in _callees.itervalues(): + call = Call(_callee.id) + call[SAMPLES] = _callee.samples + function.add_call(call) + + # compute derived data + profile.validate() + profile.find_cycles() + profile.ratio(TIME_RATIO, SAMPLES) + profile.call_ratios(SAMPLES) + profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO) + + return profile + + +class SleepyParser(Parser): + """Parser for GNU gprof output. + + See also: + - http://www.codersnotes.com/sleepy/ + - http://sleepygraph.sourceforge.net/ + """ + + def __init__(self, filename): + Parser.__init__(self) + + from zipfile import ZipFile + + self.database = ZipFile(filename) + + self.symbols = {} + self.calls = {} + + self.profile = Profile() + + _symbol_re = re.compile( + r'^(?P\w+)' + + r'\s+"(?P[^"]*)"' + + r'\s+"(?P[^"]*)"' + + r'\s+"(?P[^"]*)"' + + r'\s+(?P\d+)$' + ) + + def parse_symbols(self): + lines = self.database.read('symbols.txt').splitlines() + for line in lines: + mo = self._symbol_re.match(line) + if mo: + symbol_id, module, procname, sourcefile, sourceline = mo.groups() + + function_id = ':'.join([module, procname]) + + try: + function = self.profile.functions[function_id] + except KeyError: + function = Function(function_id, procname) + function[SAMPLES] = 0 + self.profile.add_function(function) + + self.symbols[symbol_id] = function + + def parse_callstacks(self): + lines = self.database.read("callstacks.txt").splitlines() + for line in lines: + fields = line.split() + samples = int(fields[0]) + callstack = fields[1:] + + callstack = [self.symbols[symbol_id] for symbol_id in callstack] + + callee = callstack[0] + + callee[SAMPLES] += samples + self.profile[SAMPLES] += samples + + for caller in callstack[1:]: + try: + call = caller.calls[callee.id] + except KeyError: + call = Call(callee.id) + call[SAMPLES2] = samples + caller.add_call(call) + else: + call[SAMPLES2] += samples + + callee = caller + + def parse(self): + profile = self.profile + profile[SAMPLES] = 0 + + self.parse_symbols() + self.parse_callstacks() + + # Compute derived events + profile.validate() + profile.find_cycles() + profile.ratio(TIME_RATIO, SAMPLES) + profile.call_ratios(SAMPLES2) + profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO) + + return profile + + +class AQtimeTable: + + def __init__(self, name, fields): + self.name = name + + self.fields = fields + self.field_column = {} + for column in range(len(fields)): + self.field_column[fields[column]] = column + self.rows = [] + + def __len__(self): + return len(self.rows) + + def __iter__(self): + for values, children in self.rows: + fields = {} + for name, value in zip(self.fields, values): + fields[name] = value + children = dict([(child.name, child) for child in children]) + yield fields, children + raise StopIteration + + def add_row(self, values, children=()): + self.rows.append((values, children)) + + +class AQtimeParser(XmlParser): + + def __init__(self, stream): + XmlParser.__init__(self, stream) + self.tables = {} + + def parse(self): + self.element_start('AQtime_Results') + self.parse_headers() + results = self.parse_results() + self.element_end('AQtime_Results') + return self.build_profile(results) + + def parse_headers(self): + self.element_start('HEADERS') + while self.token.type == XML_ELEMENT_START: + self.parse_table_header() + self.element_end('HEADERS') + + def parse_table_header(self): + attrs = self.element_start('TABLE_HEADER') + name = attrs['NAME'] + id = int(attrs['ID']) + field_types = [] + field_names = [] + while self.token.type == XML_ELEMENT_START: + field_type, field_name = self.parse_table_field() + field_types.append(field_type) + field_names.append(field_name) + self.element_end('TABLE_HEADER') + self.tables[id] = name, field_types, field_names + + def parse_table_field(self): + attrs = self.element_start('TABLE_FIELD') + type = attrs['TYPE'] + name = self.character_data() + self.element_end('TABLE_FIELD') + return type, name + + def parse_results(self): + self.element_start('RESULTS') + table = self.parse_data() + self.element_end('RESULTS') + return table + + def parse_data(self): + rows = [] + attrs = self.element_start('DATA') + table_id = int(attrs['TABLE_ID']) + table_name, field_types, field_names = self.tables[table_id] + table = AQtimeTable(table_name, field_names) + while self.token.type == XML_ELEMENT_START: + row, children = self.parse_row(field_types) + table.add_row(row, children) + self.element_end('DATA') + return table + + def parse_row(self, field_types): + row = [None]*len(field_types) + children = [] + self.element_start('ROW') + while self.token.type == XML_ELEMENT_START: + if self.token.name_or_data == 'FIELD': + field_id, field_value = self.parse_field(field_types) + row[field_id] = field_value + elif self.token.name_or_data == 'CHILDREN': + children = self.parse_children() + else: + raise XmlTokenMismatch(" or ", self.token) + self.element_end('ROW') + return row, children + + def parse_field(self, field_types): + attrs = self.element_start('FIELD') + id = int(attrs['ID']) + type = field_types[id] + value = self.character_data() + if type == 'Integer': + value = int(value) + elif type == 'Float': + value = float(value) + elif type == 'Address': + value = int(value) + elif type == 'String': + pass + else: + assert False + self.element_end('FIELD') + return id, value + + def parse_children(self): + children = [] + self.element_start('CHILDREN') + while self.token.type == XML_ELEMENT_START: + table = self.parse_data() + assert table.name not in children + children.append(table) + self.element_end('CHILDREN') + return children + + def build_profile(self, results): + assert results.name == 'Routines' + profile = Profile() + profile[TIME] = 0.0 + for fields, tables in results: + function = self.build_function(fields) + children = tables['Children'] + for fields, _ in children: + call = self.build_call(fields) + function.add_call(call) + profile.add_function(function) + profile[TIME] = profile[TIME] + function[TIME] + profile[TOTAL_TIME] = profile[TIME] + profile.ratio(TOTAL_TIME_RATIO, TOTAL_TIME) + return profile + + def build_function(self, fields): + function = Function(self.build_id(fields), self.build_name(fields)) + function[TIME] = fields['Time'] + function[TOTAL_TIME] = fields['Time with Children'] + #function[TIME_RATIO] = fields['% Time']/100.0 + #function[TOTAL_TIME_RATIO] = fields['% with Children']/100.0 + return function + + def build_call(self, fields): + call = Call(self.build_id(fields)) + call[TIME] = fields['Time'] + call[TOTAL_TIME] = fields['Time with Children'] + #call[TIME_RATIO] = fields['% Time']/100.0 + #call[TOTAL_TIME_RATIO] = fields['% with Children']/100.0 + return call + + def build_id(self, fields): + return ':'.join([fields['Module Name'], fields['Unit Name'], fields['Routine Name']]) + + def build_name(self, fields): + # TODO: use more fields + return fields['Routine Name'] + + +class PstatsParser: + """Parser python profiling statistics saved with te pstats module.""" + + def __init__(self, *filename): + import pstats + try: + self.stats = pstats.Stats(*filename) + except ValueError: + import hotshot.stats + self.stats = hotshot.stats.load(filename[0]) + self.profile = Profile() + self.function_ids = {} + + def get_function_name(self, (filename, line, name)): + module = os.path.splitext(filename)[0] + module = os.path.basename(module) + return "%s:%d:%s" % (module, line, name) + + def get_function(self, key): + try: + id = self.function_ids[key] + except KeyError: + id = len(self.function_ids) + name = self.get_function_name(key) + function = Function(id, name) + self.profile.functions[id] = function + self.function_ids[key] = id + else: + function = self.profile.functions[id] + return function + + def parse(self): + self.profile[TIME] = 0.0 + self.profile[TOTAL_TIME] = self.stats.total_tt + for fn, (cc, nc, tt, ct, callers) in self.stats.stats.iteritems(): + callee = self.get_function(fn) + callee[CALLS] = nc + callee[TOTAL_TIME] = ct + callee[TIME] = tt + self.profile[TIME] += tt + self.profile[TOTAL_TIME] = max(self.profile[TOTAL_TIME], ct) + for fn, value in callers.iteritems(): + caller = self.get_function(fn) + call = Call(callee.id) + if isinstance(value, tuple): + for i in xrange(0, len(value), 4): + nc, cc, tt, ct = value[i:i+4] + if CALLS in call: + call[CALLS] += cc + else: + call[CALLS] = cc + + if TOTAL_TIME in call: + call[TOTAL_TIME] += ct + else: + call[TOTAL_TIME] = ct + + else: + call[CALLS] = value + call[TOTAL_TIME] = ratio(value, nc)*ct + + caller.add_call(call) + #self.stats.print_stats() + #self.stats.print_callees() + + # Compute derived events + self.profile.validate() + self.profile.ratio(TIME_RATIO, TIME) + self.profile.ratio(TOTAL_TIME_RATIO, TOTAL_TIME) + + return self.profile + + +class Theme: + + def __init__(self, + bgcolor = (0.0, 0.0, 1.0), + mincolor = (0.0, 0.0, 0.0), + maxcolor = (0.0, 0.0, 1.0), + fontname = "Arial", + minfontsize = 10.0, + maxfontsize = 10.0, + minpenwidth = 0.5, + maxpenwidth = 4.0, + gamma = 2.2, + skew = 1.0): + self.bgcolor = bgcolor + self.mincolor = mincolor + self.maxcolor = maxcolor + self.fontname = fontname + self.minfontsize = minfontsize + self.maxfontsize = maxfontsize + self.minpenwidth = minpenwidth + self.maxpenwidth = maxpenwidth + self.gamma = gamma + self.skew = skew + + def graph_bgcolor(self): + return self.hsl_to_rgb(*self.bgcolor) + + def graph_fontname(self): + return self.fontname + + def graph_fontsize(self): + return self.minfontsize + + def node_bgcolor(self, weight): + return self.color(weight) + + def node_fgcolor(self, weight): + return self.graph_bgcolor() + + def node_fontsize(self, weight): + return self.fontsize(weight) + + def edge_color(self, weight): + return self.color(weight) + + def edge_fontsize(self, weight): + return self.fontsize(weight) + + def edge_penwidth(self, weight): + return max(weight*self.maxpenwidth, self.minpenwidth) + + def edge_arrowsize(self, weight): + return 0.5 * math.sqrt(self.edge_penwidth(weight)) + + def fontsize(self, weight): + return max(weight**2 * self.maxfontsize, self.minfontsize) + + def color(self, weight): + weight = min(max(weight, 0.0), 1.0) + + hmin, smin, lmin = self.mincolor + hmax, smax, lmax = self.maxcolor + + if self.skew < 0: + raise ValueError("Skew must be greater than 0") + elif self.skew == 1.0: + h = hmin + weight*(hmax - hmin) + s = smin + weight*(smax - smin) + l = lmin + weight*(lmax - lmin) + else: + base = self.skew + h = hmin + ((hmax-hmin)*(-1.0 + (base ** weight)) / (base - 1.0)) + s = smin + ((smax-smin)*(-1.0 + (base ** weight)) / (base - 1.0)) + l = lmin + ((lmax-lmin)*(-1.0 + (base ** weight)) / (base - 1.0)) + + return self.hsl_to_rgb(h, s, l) + + def hsl_to_rgb(self, h, s, l): + """Convert a color from HSL color-model to RGB. + + See also: + - http://www.w3.org/TR/css3-color/#hsl-color + """ + + h = h % 1.0 + s = min(max(s, 0.0), 1.0) + l = min(max(l, 0.0), 1.0) + + if l <= 0.5: + m2 = l*(s + 1.0) + else: + m2 = l + s - l*s + m1 = l*2.0 - m2 + r = self._hue_to_rgb(m1, m2, h + 1.0/3.0) + g = self._hue_to_rgb(m1, m2, h) + b = self._hue_to_rgb(m1, m2, h - 1.0/3.0) + + # Apply gamma correction + r **= self.gamma + g **= self.gamma + b **= self.gamma + + return (r, g, b) + + def _hue_to_rgb(self, m1, m2, h): + if h < 0.0: + h += 1.0 + elif h > 1.0: + h -= 1.0 + if h*6 < 1.0: + return m1 + (m2 - m1)*h*6.0 + elif h*2 < 1.0: + return m2 + elif h*3 < 2.0: + return m1 + (m2 - m1)*(2.0/3.0 - h)*6.0 + else: + return m1 + + +TEMPERATURE_COLORMAP = Theme( + mincolor = (2.0/3.0, 0.80, 0.25), # dark blue + maxcolor = (0.0, 1.0, 0.5), # satured red + gamma = 1.0 +) + +PINK_COLORMAP = Theme( + mincolor = (0.0, 1.0, 0.90), # pink + maxcolor = (0.0, 1.0, 0.5), # satured red +) + +GRAY_COLORMAP = Theme( + mincolor = (0.0, 0.0, 0.85), # light gray + maxcolor = (0.0, 0.0, 0.0), # black +) + +BW_COLORMAP = Theme( + minfontsize = 8.0, + maxfontsize = 24.0, + mincolor = (0.0, 0.0, 0.0), # black + maxcolor = (0.0, 0.0, 0.0), # black + minpenwidth = 0.1, + maxpenwidth = 8.0, +) + + +class DotWriter: + """Writer for the DOT language. + + See also: + - "The DOT Language" specification + http://www.graphviz.org/doc/info/lang.html + """ + + def __init__(self, fp): + self.fp = fp + + def graph(self, profile, theme): + self.begin_graph() + + fontname = theme.graph_fontname() + + self.attr('graph', fontname=fontname, ranksep=0.25, nodesep=0.125) + self.attr('node', fontname=fontname, shape="box", style="filled", fontcolor="white", width=0, height=0) + self.attr('edge', fontname=fontname) + + for function in profile.functions.itervalues(): + labels = [] + for event in PROCESS, MODULE: + if event in function.events: + label = event.format(function[event]) + labels.append(label) + labels.append(function.name) + for event in TOTAL_TIME_RATIO, TIME_RATIO, CALLS: + if event in function.events: + label = event.format(function[event]) + labels.append(label) + + try: + weight = function[PRUNE_RATIO] + except UndefinedEvent: + weight = 0.0 + + label = '\n'.join(labels) + self.node(function.id, + label = label, + color = self.color(theme.node_bgcolor(weight)), + fontcolor = self.color(theme.node_fgcolor(weight)), + fontsize = "%.2f" % theme.node_fontsize(weight), + ) + + for call in function.calls.itervalues(): + callee = profile.functions[call.callee_id] + + labels = [] + for event in TOTAL_TIME_RATIO, CALLS: + if event in call.events: + label = event.format(call[event]) + labels.append(label) + + try: + weight = call[PRUNE_RATIO] + except UndefinedEvent: + try: + weight = callee[PRUNE_RATIO] + except UndefinedEvent: + weight = 0.0 + + label = '\n'.join(labels) + + self.edge(function.id, call.callee_id, + label = label, + color = self.color(theme.edge_color(weight)), + fontcolor = self.color(theme.edge_color(weight)), + fontsize = "%.2f" % theme.edge_fontsize(weight), + penwidth = "%.2f" % theme.edge_penwidth(weight), + labeldistance = "%.2f" % theme.edge_penwidth(weight), + arrowsize = "%.2f" % theme.edge_arrowsize(weight), + ) + + self.end_graph() + + def begin_graph(self): + self.write('digraph {\n') + + def end_graph(self): + self.write('}\n') + + def attr(self, what, **attrs): + self.write("\t") + self.write(what) + self.attr_list(attrs) + self.write(";\n") + + def node(self, node, **attrs): + self.write("\t") + self.id(node) + self.attr_list(attrs) + self.write(";\n") + + def edge(self, src, dst, **attrs): + self.write("\t") + self.id(src) + self.write(" -> ") + self.id(dst) + self.attr_list(attrs) + self.write(";\n") + + def attr_list(self, attrs): + if not attrs: + return + self.write(' [') + first = True + for name, value in attrs.iteritems(): + if first: + first = False + else: + self.write(", ") + self.id(name) + self.write('=') + self.id(value) + self.write(']') + + def id(self, id): + if isinstance(id, (int, float)): + s = str(id) + elif isinstance(id, basestring): + if id.isalnum(): + s = id + else: + s = self.escape(id) + else: + raise TypeError + self.write(s) + + def color(self, (r, g, b)): + + def float2int(f): + if f <= 0.0: + return 0 + if f >= 1.0: + return 255 + return int(255.0*f + 0.5) + + return "#" + "".join(["%02x" % float2int(c) for c in (r, g, b)]) + + def escape(self, s): + s = s.encode('utf-8') + s = s.replace('\\', r'\\') + s = s.replace('\n', r'\n') + s = s.replace('\t', r'\t') + s = s.replace('"', r'\"') + return '"' + s + '"' + + def write(self, s): + self.fp.write(s) + + +class Main: + """Main program.""" + + themes = { + "color": TEMPERATURE_COLORMAP, + "pink": PINK_COLORMAP, + "gray": GRAY_COLORMAP, + "bw": BW_COLORMAP, + } + + def main(self): + """Main program.""" + + parser = optparse.OptionParser( + usage="\n\t%prog [options] [file] ...", + version="%%prog %s" % __version__) + parser.add_option( + '-o', '--output', metavar='FILE', + type="string", dest="output", + help="output filename [stdout]") + parser.add_option( + '-n', '--node-thres', metavar='PERCENTAGE', + type="float", dest="node_thres", default=0.5, + help="eliminate nodes below this threshold [default: %default]") + parser.add_option( + '-e', '--edge-thres', metavar='PERCENTAGE', + type="float", dest="edge_thres", default=0.1, + help="eliminate edges below this threshold [default: %default]") + parser.add_option( + '-f', '--format', + type="choice", choices=('prof', 'oprofile', 'sysprof', 'pstats', 'shark', 'sleepy', 'aqtime'), + dest="format", default="prof", + help="profile format: prof, oprofile, sysprof, shark, sleepy, aqtime, or pstats [default: %default]") + parser.add_option( + '-c', '--colormap', + type="choice", choices=('color', 'pink', 'gray', 'bw'), + dest="theme", default="color", + help="color map: color, pink, gray, or bw [default: %default]") + parser.add_option( + '-s', '--strip', + action="store_true", + dest="strip", default=False, + help="strip function parameters, template parameters, and const modifiers from demangled C++ function names") + parser.add_option( + '-w', '--wrap', + action="store_true", + dest="wrap", default=False, + help="wrap function names") + # add a new option to control skew of the colorization curve + parser.add_option( + '--skew', + type="float", dest="theme_skew", default=1.0, + help="skew the colorization curve. Values < 1.0 give more variety to lower percentages. Value > 1.0 give less variety to lower percentages") + (self.options, self.args) = parser.parse_args(sys.argv[1:]) + + if len(self.args) > 1 and self.options.format != 'pstats': + parser.error('incorrect number of arguments') + + try: + self.theme = self.themes[self.options.theme] + except KeyError: + parser.error('invalid colormap \'%s\'' % self.options.theme) + + # set skew on the theme now that it has been picked. + if self.options.theme_skew: + self.theme.skew = self.options.theme_skew + + if self.options.format == 'prof': + if not self.args: + fp = sys.stdin + else: + fp = open(self.args[0], 'rt') + parser = GprofParser(fp) + elif self.options.format == 'oprofile': + if not self.args: + fp = sys.stdin + else: + fp = open(self.args[0], 'rt') + parser = OprofileParser(fp) + elif self.options.format == 'sysprof': + if not self.args: + fp = sys.stdin + else: + fp = open(self.args[0], 'rt') + parser = SysprofParser(fp) + elif self.options.format == 'pstats': + if not self.args: + parser.error('at least a file must be specified for pstats input') + parser = PstatsParser(*self.args) + elif self.options.format == 'shark': + if not self.args: + fp = sys.stdin + else: + fp = open(self.args[0], 'rt') + parser = SharkParser(fp) + elif self.options.format == 'sleepy': + if len(self.args) != 1: + parser.error('exactly one file must be specified for sleepy input') + parser = SleepyParser(self.args[0]) + elif self.options.format == 'aqtime': + if not self.args: + fp = sys.stdin + else: + fp = open(self.args[0], 'rt') + parser = AQtimeParser(fp) + else: + parser.error('invalid format \'%s\'' % self.options.format) + + self.profile = parser.parse() + + if self.options.output is None: + self.output = sys.stdout + else: + self.output = open(self.options.output, 'wt') + + self.write_graph() + + _parenthesis_re = re.compile(r'\([^()]*\)') + _angles_re = re.compile(r'<[^<>]*>') + _const_re = re.compile(r'\s+const$') + + def strip_function_name(self, name): + """Remove extraneous information from C++ demangled function names.""" + + # Strip function parameters from name by recursively removing paired parenthesis + while True: + name, n = self._parenthesis_re.subn('', name) + if not n: + break + + # Strip const qualifier + name = self._const_re.sub('', name) + + # Strip template parameters from name by recursively removing paired angles + while True: + name, n = self._angles_re.subn('', name) + if not n: + break + + return name + + def wrap_function_name(self, name): + """Split the function name on multiple lines.""" + + if len(name) > 32: + ratio = 2.0/3.0 + height = max(int(len(name)/(1.0 - ratio) + 0.5), 1) + width = max(len(name)/height, 32) + # TODO: break lines in symbols + name = textwrap.fill(name, width, break_long_words=False) + + # Take away spaces + name = name.replace(", ", ",") + name = name.replace("> >", ">>") + name = name.replace("> >", ">>") # catch consecutive + + return name + + def compress_function_name(self, name): + """Compress function name according to the user preferences.""" + + if self.options.strip: + name = self.strip_function_name(name) + + if self.options.wrap: + name = self.wrap_function_name(name) + + # TODO: merge functions with same resulting name + + return name + + def write_graph(self): + dot = DotWriter(self.output) + profile = self.profile + profile.prune(self.options.node_thres/100.0, self.options.edge_thres/100.0) + + for function in profile.functions.itervalues(): + function.name = self.compress_function_name(function.name) + + dot.graph(profile, self.theme) + + +if __name__ == '__main__': + Main().main() diff --git a/external/gprof2dot/__init__.py b/external/gprof2dot/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/makeProfileGraph.py b/makeProfileGraph.py new file mode 100755 index 0000000000..e63f517b33 --- /dev/null +++ b/makeProfileGraph.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +def makeProfileGraph(stats_file,thresh_node,thresh_edge): + """ + Uses gprof2dot to create a graphviz dot file of the profiling information. + + This requires the gprof2dot package available via `pip install gprof2dot`. + Renders the result using the program 'dot' via a command like + `dot -Tpdf input.dot -o output.pdf`. + """ + try: + from external.gprof2dot import gprof2dot + except ImportError: + try: + from external import gprof2dot + except ImportError: + print('Package gprof2dot not found. Unable to create a graph of the profile statistics.') + print("`pip install gprof2dot` if you don't have it.") + return + import subprocess + m = gprof2dot.Main() + class Options: + pass + m.options = Options() + m.options.node_thres = thresh_node# default 0.8 + m.options.edge_thres = thresh_edge # default 0.1 + m.options.strip = False + m.options.wrap = True + m.theme = m.themes['color'] # bw color gray pink + parser = gprof2dot.PstatsParser(stats_file) + m.profile = parser.parse() + dot_file = stats_file + '.dot' + m.output = open(dot_file,'wt') + m.write_graph() + m.output.close() + try: + subprocess.check_call(['dot', '-Tpdf', dot_file, '-o', '{0}.pdf'.format(dot_file)]) + except subprocess.CalledProcessError: + print("Error returned by 'dot' when generating graph of the profile statistics.") + print("To try it yourself:\n dot -Tpdf {0} -o {0}.pdf".format(dot_file)) + except OSError: + print("Couldn't run 'dot' to create graph of profile statistics. Check graphviz is installed properly and on your path.") + print("Once you've got it, try:\n dot -Tpdf {0} -o {0}.pdf".format(dot_file)) + else: + print("Graph of profile statistics saved to: \n {0}.pdf".format(dot_file)) + +if __name__ == '__main__': + + import argparse + + parser = argparse.ArgumentParser(description="Creates a call graph with profiling information.") + parser.add_argument('FILE', type=str, default='RMG.profile',nargs='?', help='.profile file (default file is RMG.profile)') + parser.add_argument('THRESH_NODE', type=float, default=0.8,nargs='?', help='threshold percentage value for nodes (default value is 0.8)') + parser.add_argument('THRESH_EDGE', type=float, default=0.1, nargs='?', help='threshold percentage value for nodes (default value is 0.1)') + args = parser.parse_args() + stats_file=args.FILE + thresh_node=args.THRESH_NODE + thresh_edge=args.THRESH_EDGE + + makeProfileGraph(stats_file,thresh_node,thresh_edge) + + diff --git a/rmgpy/chemkin.py b/rmgpy/chemkin.py index ec4bb05aec..b7cf253e0e 100644 --- a/rmgpy/chemkin.py +++ b/rmgpy/chemkin.py @@ -1576,8 +1576,8 @@ def saveTransportFile(path, species): (from the chemkin TRANSPORT manual) """ with open(path, 'w') as f: - f.write("! {:15} {:8} {:9} {:9} {:9} {:9} {:9} {:9}\n".format('Species','Shape', 'LJ-depth', 'LJ-diam', 'DiplMom', 'Polzblty', 'RotRelaxNum','Data')) - f.write("! {:15} {:8} {:9} {:9} {:9} {:9} {:9} {:9}\n".format('Name','Index', 'epsilon/k_B', 'sigma', 'mu', 'alpha', 'Zrot','Source')) + f.write("! {0:15} {1:8} {2:9} {3:9} {4:9} {5:9} {6:9} {7:9}\n".format('Species','Shape', 'LJ-depth', 'LJ-diam', 'DiplMom', 'Polzblty', 'RotRelaxNum','Data')) + f.write("! {0:15} {1:8} {2:9} {3:9} {4:9} {5:9} {6:9} {7:9}\n".format('Name','Index', 'epsilon/k_B', 'sigma', 'mu', 'alpha', 'Zrot','Source')) for spec in species: if (not spec.transportData or len(spec.molecule) == 0): @@ -1596,7 +1596,7 @@ def saveTransportFile(path, species): shapeIndex = 2 if missingData: - f.write('! {:19s} {!r}\n'.format(label, spec.transportData)) + f.write('! {0:19s} {1!r}\n'.format(label, spec.transportData)) else: f.write('{0:19} {1:d} {2:9.3f} {3:9.3f} {4:9.3f} {5:9.3f} {6:9.3f} ! {7:s}\n'.format( label, diff --git a/rmgpy/data/__init__.py b/rmgpy/data/__init__.py index e4558f7c1a..afa4dea966 100644 --- a/rmgpy/data/__init__.py +++ b/rmgpy/data/__init__.py @@ -31,4 +31,9 @@ import os.path def getDatabaseDirectory(): + raise NotImplementedError("This is wrong.") return os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'database')) + +def getSourceHash(): + import rmgpy.utilities + return rmgpy.utilities.path_checksum([os.path.dirname(__file__)]) \ No newline at end of file diff --git a/rmgpy/data/rmg.py b/rmgpy/data/rmg.py index 9b4f87b46a..c8ed04fd2c 100644 --- a/rmgpy/data/rmg.py +++ b/rmgpy/data/rmg.py @@ -34,6 +34,7 @@ """ import os.path +import logging from base import ForbiddenStructures from thermo import ThermoDatabase @@ -75,7 +76,7 @@ def load(self, kineticsDepositories=None, statmechLibraries=None, depository=True, - solvation=True, + solvation=False, ): """ Load the RMG database from the given `path` on disk, where `path` @@ -204,3 +205,18 @@ def saveOld(self, path): self.forbiddenStructures.saveOld(os.path.join(path, 'ForbiddenStructures.txt')) self.kinetics.saveOld(path) self.statmech.saveOld(path) + + def saveToPickle(self, path): + """ + Save the database to a pickle file. + + This is so that other workers (in a parallel computing environment) + can load it easily from disk. + """ + import cPickle + global databaseFilePath + databaseFilePath = path + logging.info('Saving database pickle file {0!s}'.format(databaseFilePath)) + f = open(path, 'wb') + cPickle.dump(self, f, cPickle.HIGHEST_PROTOCOL) + f.close() diff --git a/rmgpy/molecule/draw.py b/rmgpy/molecule/draw.py index eb961c2812..b82381845a 100644 --- a/rmgpy/molecule/draw.py +++ b/rmgpy/molecule/draw.py @@ -873,6 +873,12 @@ def render(self, cr, offset=None): coordinates = self.coordinates atoms = self.molecule.atoms symbols = self.symbols + + drawLonePairs = False + + for atom in atoms: + if atom.isNitrogen(): + drawLonePairs = True left = 0.0 top = 0.0 @@ -931,7 +937,7 @@ def render(self, cr, offset=None): heavyFirst = False cr.set_font_size(self.options['fontSizeNormal']) x0 += cr.text_extents(symbols[0])[2] / 2.0 - atomBoundingRect = self.__renderAtom(symbol, atom, x0, y0, cr, heavyFirst) + atomBoundingRect = self.__renderAtom(symbol, atom, x0, y0, cr, heavyFirst, drawLonePairs) # Add a small amount of whitespace on all sides padding = self.options['padding'] @@ -997,7 +1003,7 @@ def __renderBond(self, atom1, atom2, bond, cr): self.__drawLine(cr, x1 - du + dx, y1 - dv + dy, x2 - du - dx, y2 - dv - dy) self.__drawLine(cr, x1 + du + dx, y1 + dv + dy, x2 + du - dx, y2 + dv - dy) - def __renderAtom(self, symbol, atom, x0, y0, cr, heavyFirst=True): + def __renderAtom(self, symbol, atom, x0, y0, cr, heavyFirst=True, drawLonePairs=False): """ Render the `label` for an atom centered around the coordinates (`x0`, `y0`) onto the Cairo context `cr`. If `heavyFirst` is ``False``, then the order @@ -1249,25 +1255,27 @@ def __renderAtom(self, symbol, atom, x0, y0, cr, heavyFirst=True): cr.set_source_rgba(0.0, 0.0, 0.0, 1.0) cr.show_text(text) - # Draw lone electron pairs - for i in range (atom.lonePairs): - cr.new_sub_path() - if i == 0: - x1lp = x-2 - y1lp = y-8 - x2lp = x+2 - y2lp = y-12 - elif i == 1: - x1lp = x+12 - y1lp = y-8 - x2lp = x+8 - y2lp = y-12 - elif i == 2: - x1lp = x-2 - y1lp = y-1 - x2lp = x+2 - y2lp = y+3 - self.__drawLine(cr, x1lp, y1lp, x2lp, y2lp) + # Draw lone electron pairs + # Draw them for nitrogen containing molecules only + if drawLonePairs: + for i in range (atom.lonePairs): + cr.new_sub_path() + if i == 0: + x1lp = x-2 + y1lp = y-8 + x2lp = x+2 + y2lp = y-12 + elif i == 1: + x1lp = x+12 + y1lp = y-8 + x2lp = x+8 + y2lp = y-12 + elif i == 2: + x1lp = x-2 + y1lp = y-1 + x2lp = x+2 + y2lp = y+3 + self.__drawLine(cr, x1lp, y1lp, x2lp, y2lp) elif orientation[0] == 'l' or orientation[0] == 'r': # Draw charges first @@ -1289,24 +1297,26 @@ def __renderAtom(self, symbol, atom, x0, y0, cr, heavyFirst=True): cr.set_source_rgba(0.0, 0.0, 0.0, 1.0) cr.fill() # Draw lone electron pairs - for i in range (atom.lonePairs): - cr.new_sub_path() - if i == 0: - x1lp = x-2 - y1lp = y-8 - x2lp = x+2 - y2lp = y-12 - elif i == 1: - x1lp = x+12 - y1lp = y-8 - x2lp = x+8 - y2lp = y-12 - elif i == 2: - x1lp = x-2 - y1lp = y-1 - x2lp = x+2 - y2lp = y+3 - self.__drawLine(cr, x1lp, y1lp, x2lp, y2lp) + # Draw them for nitrogen atoms only + if drawLonePairs: + for i in range (atom.lonePairs): + cr.new_sub_path() + if i == 0: + x1lp = x-2 + y1lp = y-8 + x2lp = x+2 + y2lp = y-12 + elif i == 1: + x1lp = x+12 + y1lp = y-8 + x2lp = x+8 + y2lp = y-12 + elif i == 2: + x1lp = x-2 + y1lp = y-1 + x2lp = x+2 + y2lp = y+3 + self.__drawLine(cr, x1lp, y1lp, x2lp, y2lp) # Update bounding rect to ensure atoms are included if boundingRect[0] < self.left: diff --git a/rmgpy/qm/gaussian.py b/rmgpy/qm/gaussian.py index f6a7e87d94..0090e4ffe7 100644 --- a/rmgpy/qm/gaussian.py +++ b/rmgpy/qm/gaussian.py @@ -184,28 +184,6 @@ def inputFileKeywords(self, attempt): """ raise NotImplementedError("Should be defined by subclass, eg. GaussianMolPM3") - def generateQMData(self): - """ - Calculate the QM data and return a QMData object. - """ - self.createGeometry() - if self.verifyOutputFile(): - logging.info("Found a successful output file already; using that.") - else: - success = False - for attempt in range(1, self.maxAttempts+1): - self.writeInputFile(attempt) - success = self.run() - if success: - logging.info('Attempt {0} of {1} on species {2} succeeded.'.format(attempt, self.maxAttempts, self.molecule.toAugmentedInChI())) - break - else: - logging.error('QM thermo calculation failed for {0}.'.format(self.molecule.toAugmentedInChI())) - return None - result = self.parse() # parsed in cclib - return result - - class GaussianMolPM3(GaussianMol): diff --git a/rmgpy/qm/main.py b/rmgpy/qm/main.py index d5243f4ca3..ef975f2fb5 100644 --- a/rmgpy/qm/main.py +++ b/rmgpy/qm/main.py @@ -69,6 +69,7 @@ def checkAllSet(self): assert type(self.onlyCyclics) is BooleanType assert self.maxRadicalNumber is not None # but it can be 0 assert type(self.maxRadicalNumber) is IntType + logging.debug("QM settings are ok.") class QMCalculator(): """ @@ -96,7 +97,7 @@ def __init__(self, def setDefaultOutputDirectory(self, outputDirectory): """ - IF the fileStore or scratchDirectory are not already set, put them in here. + If the fileStore or scratchDirectory are not already set, put them in here. """ if not self.settings.fileStore: self.settings.fileStore = os.path.join(outputDirectory, 'QMfiles') @@ -124,7 +125,8 @@ def checkPaths(self): """ self.settings.fileStore = os.path.expandvars(self.settings.fileStore) # to allow things like $HOME or $RMGpy self.settings.scratchDirectory = os.path.expandvars(self.settings.scratchDirectory) - for path in [self.settings.fileStore, self.settings.scratchDirectory]: +# for path in [self.settings.fileStore, self.settings.scratchDirectory]: + for path in [self.settings.fileStore]: if not os.path.exists(path): logging.info("Creating directory %s for QM files."%os.path.abspath(path)) os.makedirs(path) @@ -133,6 +135,7 @@ def checkPaths(self): raise Exception("RMG-Py 'bin' directory {0} does not exist.".format(self.settings.RMG_bin_path)) if not os.path.isdir(self.settings.RMG_bin_path): raise Exception("RMG-Py 'bin' directory {0} is not a directory.".format(self.settings.RMG_bin_path)) + logging.debug("QM paths are ok.") def getThermoData(self, molecule): @@ -141,15 +144,32 @@ def getThermoData(self, molecule): Ignores the settings onlyCyclics and maxRadicalNumber and does the calculation anyway if asked. (I.e. the code that chooses whether to call this method should consider those settings). + Options for QM calculations are: + mopac: Default calculation with Mopac is PM3 semiempirical method, should be changed to PM6 or PM7 + mopacPM3: PM3, Same as mopac option. + mopacPM6: PM6, better than PM3 (Journal of Molecular Modeling 13, 1173–1213, 2007.) + mopacPM7: PM7, excludes computational results from training set, might be better or slightly worse compared to PM6 + gaussian: Only PM3 is available. """ - if self.settings.software == 'mopac': + if self.settings.software == 'mopac' or self.settings.software == 'mopacPM3': + logging.debug("Attempting for a {0} calculation.".format(self.settings.software)) qm_molecule_calculator = rmgpy.qm.mopac.MopacMolPM3(molecule, self.settings) thermo0 = qm_molecule_calculator.generateThermoData() + logging.debug("{0} calculation attempted.".format(self.settings.software)) + elif self.settings.software == 'mopacPM6': + logging.debug("Attempting for a {0} calculation.".format(self.settings.software)) + qm_molecule_calculator = rmgpy.qm.mopac.MopacMolPM6(molecule, self.settings) + thermo0 = qm_molecule_calculator.generateThermoData() + elif self.settings.software == 'mopacPM7': + logging.debug("Attempting for a {0} calculation.".format(self.settings.software)) + qm_molecule_calculator = rmgpy.qm.mopac.MopacMolPM7(molecule, self.settings) + thermo0 = qm_molecule_calculator.generateThermoData() elif self.settings.software == 'gaussian': + logging.debug("Attempting for a {0} calculation.".format(self.settings.software)) qm_molecule_calculator = rmgpy.qm.gaussian.GaussianMolPM3(molecule, self.settings) thermo0 = qm_molecule_calculator.generateThermoData() else: raise Exception("Unknown QM software '{0}'".format(self.settings.software)) return thermo0 - \ No newline at end of file + diff --git a/rmgpy/qm/molecule.py b/rmgpy/qm/molecule.py index 9d45bd7597..cd7cba8776 100644 --- a/rmgpy/qm/molecule.py +++ b/rmgpy/qm/molecule.py @@ -204,8 +204,26 @@ def generateQMData(self): """ Calculate the QM data somehow and return a CCLibData object, or None if it fails. """ - raise NotImplementedError("This should be defined in a subclass that inherits from QMMolecule") - return qmdata.QMData() or None + logging.debug("{0} calculation".format(self.__class__.__name__)) + if self.verifyOutputFile(): + logging.info("Found a successful output file already; using that.") + source = "QM {0} result file found from previous run.".format(self.__class__.__name__) + else: + self.createGeometry() + success = False + for attempt in range(1, self.maxAttempts+1): + self.writeInputFile(attempt) + logging.info('Trying {3} attempt {0} of {1} on molecule {2}.'.format(attempt, self.maxAttempts, self.molecule.toSMILES(), self.__class__.__name__)) + success = self.run() + if success: + source = "QM {0} calculation attempt {1}".format(self.__class__.__name__, attempt ) + break + else: + logging.error('QM thermo calculation failed for {0}.'.format(self.molecule.toAugmentedInChI())) + return None + result = self.parse() # parsed in cclib + result.source = source + return result # a CCLibData object def generateThermoData(self): """ @@ -215,6 +233,7 @@ def generateThermoData(self): """ # First, see if we already have it. if self.loadThermoData(): + logging.debug("Already have thermo data") return self.thermo # If not, generate the QM data @@ -222,20 +241,24 @@ def generateThermoData(self): # If that fails, give up and return None. if self.qmData is None: + logging.debug("QM data is not found") return None self.determinePointGroup() # If that fails, give up and return None. if self.pointGroup is None: + logging.debug("No point group found") return None self.calculateThermoData() + logging.debug("Thermo data calculated") Cp0 = self.molecule.calculateCp0() CpInf = self.molecule.calculateCpInf() self.thermo.Cp0 = (Cp0,"J/(mol*K)") self.thermo.CpInf = (CpInf,"J/(mol*K)") self.saveThermoData() + logging.debug("Thermo data saved") return self.thermo def saveThermoData(self): @@ -326,6 +349,7 @@ def calculateThermoData(self): trans = rmgpy.statmech.IdealGasTranslation( mass=self.qmData.molecularMass ) if self.pointGroup.linear: + logging.debug("Linear molecule") rot = rmgpy.statmech.LinearRotor( rotationalConstant = self.qmData.rotationalConstants, symmetry = self.pointGroup.symmetryNumber, diff --git a/rmgpy/qm/mopac.py b/rmgpy/qm/mopac.py index 176720ec24..90cabce9f2 100644 --- a/rmgpy/qm/mopac.py +++ b/rmgpy/qm/mopac.py @@ -20,10 +20,13 @@ class Mopac: mopacEnv = os.getenv('MOPAC_DIR', default="/opt/mopac") if os.path.exists(os.path.join(mopacEnv , 'MOPAC2012.exe')): executablePath = os.path.join(mopacEnv , 'MOPAC2012.exe') + logging.debug("{0} is found.".format(executablePath)) elif os.path.exists(os.path.join(mopacEnv , 'MOPAC2009.exe')): executablePath = os.path.join(mopacEnv , 'MOPAC2009.exe') + logging.debug("{0} is found.".format(executablePath)) else: executablePath = os.path.join(mopacEnv , '(MOPAC 2009 or 2012)') + logging.debug("{0} is found.".format(executablePath)) usePolar = False #use polar keyword in MOPAC @@ -56,6 +59,7 @@ class Mopac: def testReady(self): if not os.path.exists(self.executablePath): + logging.debug("{0} is not found.").format(self.executablePath) raise Exception("Couldn't find MOPAC executable at {0}. Try setting your MOPAC_DIR environment variable.".format(self.executablePath)) def run(self): @@ -193,36 +197,107 @@ def inputFileKeywords(self, attempt): """ raise NotImplementedError("Should be defined by subclass, eg. MopacMolPM3") - def generateQMData(self): +class MopacMolPM3(MopacMol): + + #: Keywords that will be added at the top and bottom of the qm input file + keywords = [ + {'top':"precise nosym", 'bottom':"oldgeo thermo nosym precise "}, + {'top':"precise nosym gnorm=0.0 nonr", 'bottom':"oldgeo thermo nosym precise "}, + {'top':"precise nosym gnorm=0.0", 'bottom':"oldgeo thermo nosym precise "}, + {'top':"precise nosym gnorm=0.0 bfgs", 'bottom':"oldgeo thermo nosym precise "}, + {'top':"precise nosym recalc=10 dmax=0.10 nonr cycles=2000 t=2000", 'bottom':"oldgeo thermo nosym precise "}, + ] + + @property + def scriptAttempts(self): + "The number of attempts with different script keywords" + return len(self.keywords) + + @property + def maxAttempts(self): + "The total number of attempts to try" + return 2 * len(self.keywords) + + + def inputFileKeywords(self, attempt): """ - Calculate the QM data and return a QMData object, or None if it fails. + Return the top, bottom, and polar keywords for attempt number `attempt`. + + NB. `attempt`s begin at 1, not 0. """ - for atom in self.molecule.vertices: - if atom.atomType.label == 'N5s' or atom.atomType.label == 'N5d' or atom.atomType.label =='N5dd' or atom.atomType.label == 'N5t' or atom.atomType.label == 'N5b': - return None + assert attempt <= self.maxAttempts + + if attempt > self.scriptAttempts: + attempt -= self.scriptAttempts + + multiplicity_keys = self.multiplicityKeywords[self.geometry.multiplicity] - if self.verifyOutputFile(): - logging.info("Found a successful output file already; using that.") - source = "QM MOPAC result file found from previous run." - else: - self.createGeometry() - success = False - for attempt in range(1, self.maxAttempts+1): - self.writeInputFile(attempt) - logging.info('Trying {3} attempt {0} of {1} on molecule {2}.'.format(attempt, self.maxAttempts, self.molecule.toSMILES(), self.__class__.__name__)) - success = self.run() - if success: - source = "QM {0} calculation attempt {1}".format(self.__class__.__name__, attempt ) - break - else: - logging.error('QM thermo calculation failed for {0}.'.format(self.molecule.toAugmentedInChI())) - return None - result = self.parse() # parsed in cclib - result.source = source - return result # a CCLibData object + top_keys = "pm3 {0} {1}".format( + multiplicity_keys, + self.keywords[attempt-1]['top'], + ) + bottom_keys = "{0} pm3 {1}".format( + self.keywords[attempt-1]['bottom'], + multiplicity_keys, + ) + polar_keys = "oldgeo {0} nosym precise pm3 {1}".format( + 'polar' if self.geometry.multiplicity == 1 else 'static', + multiplicity_keys, + ) + return top_keys, bottom_keys, polar_keys + +class MopacMolPM6(MopacMol): -class MopacMolPM3(MopacMol): + #: Keywords that will be added at the top and bottom of the qm input file + keywords = [ + {'top':"precise nosym", 'bottom':"oldgeo thermo nosym precise "}, + {'top':"precise nosym gnorm=0.0 nonr", 'bottom':"oldgeo thermo nosym precise "}, + {'top':"precise nosym gnorm=0.0", 'bottom':"oldgeo thermo nosym precise "}, + {'top':"precise nosym gnorm=0.0 bfgs", 'bottom':"oldgeo thermo nosym precise "}, + {'top':"precise nosym recalc=10 dmax=0.10 nonr cycles=2000 t=2000", 'bottom':"oldgeo thermo nosym precise "}, + ] + + @property + def scriptAttempts(self): + "The number of attempts with different script keywords" + return len(self.keywords) + + @property + def maxAttempts(self): + "The total number of attempts to try" + return 2 * len(self.keywords) + + + def inputFileKeywords(self, attempt): + """ + Return the top, bottom, and polar keywords for attempt number `attempt`. + + NB. `attempt`s begin at 1, not 0. + """ + assert attempt <= self.maxAttempts + + if attempt > self.scriptAttempts: + attempt -= self.scriptAttempts + + multiplicity_keys = self.multiplicityKeywords[self.geometry.multiplicity] + + top_keys = "pm6 {0} {1}".format( + multiplicity_keys, + self.keywords[attempt-1]['top'], + ) + bottom_keys = "{0} pm6 {1}".format( + self.keywords[attempt-1]['bottom'], + multiplicity_keys, + ) + polar_keys = "oldgeo {0} nosym precise pm6 {1}".format( + 'polar' if self.geometry.multiplicity == 1 else 'static', + multiplicity_keys, + ) + + return top_keys, bottom_keys, polar_keys + +class MopacMolPM7(MopacMol): #: Keywords that will be added at the top and bottom of the qm input file keywords = [ @@ -257,17 +332,17 @@ def inputFileKeywords(self, attempt): multiplicity_keys = self.multiplicityKeywords[self.geometry.multiplicity] - top_keys = "pm3 {0} {1}".format( + top_keys = "pm7 {0} {1}".format( multiplicity_keys, self.keywords[attempt-1]['top'], ) - bottom_keys = "{0} pm3 {1}".format( + bottom_keys = "{0} pm7 {1}".format( self.keywords[attempt-1]['bottom'], multiplicity_keys, ) - polar_keys = "oldgeo {0} nosym precise pm3 {1}".format( + polar_keys = "oldgeo {0} nosym precise pm7 {1}".format( 'polar' if self.geometry.multiplicity == 1 else 'static', multiplicity_keys, ) - return top_keys, bottom_keys, polar_keys \ No newline at end of file + return top_keys, bottom_keys, polar_keys diff --git a/rmgpy/rmg/input.py b/rmgpy/rmg/input.py index 37f4a25b5e..34235ddc64 100644 --- a/rmgpy/rmg/input.py +++ b/rmgpy/rmg/input.py @@ -93,9 +93,12 @@ def database( def species(label, structure, reactive=True): logging.debug('Found {0} species "{1}" ({2})'.format('reactive' if reactive else 'nonreactive', label, structure.toSMILES())) spec, isNew = rmg.reactionModel.makeNewSpecies(structure, label=label, reactive=reactive) - assert isNew, "Species {0} is a duplicate of {1}. Species in input file must be unique".format(label,spec.label) - rmg.initialSpecies.append(spec) - speciesDict[label] = spec + #assert isNew, "Species {0} is a duplicate of {1}. Species in input file must be unique".format(label,spec.label) + if isNew: + rmg.initialSpecies.append(spec) + speciesDict[label] = spec + else: + logging.info("Species {0} is a duplicate of {1}. Avoid it and continue calculation ...".format(label,spec.label)) def SMARTS(string): return Molecule().fromSMARTS(string) diff --git a/rmgpy/rmg/main.py b/rmgpy/rmg/main.py index 00e6b15ef6..8eb147a99c 100644 --- a/rmgpy/rmg/main.py +++ b/rmgpy/rmg/main.py @@ -224,7 +224,68 @@ def saveInput(self, path=None): saveInputFile(path, self) def loadDatabase(self): + """ + Load the RMG Database. + + The data is loaded from self.databaseDirectory, according to settings in: + + * self.thermoLibraries + * self.reactionLibraries + * self.seedMechanisms + * self.kineticsFamilies + * self.kineticsDepositories + + If `self.kineticsEstimator == 'rate rules'` then the training set values are + added and the blanks are filled in by averaging. + + If self.outputDirectory contains :file:`database.pkl` and :file:`database.hash` files then + these are checked for validity and used as a cache. Once loaded (and averages filled + in if necessary) then a cache (pickle and hash) is saved. + """ + import inspect, hashlib, cPickle, rmgpy.utilities, scoop.shared + # Make a hash of everything that could alter the contents of the database once it is fully loaded. + # Then we can compare this hash to the cached file to see if the cache is valid. + database_metadata = { + 'path': self.databaseDirectory, + 'database hash': rmgpy.utilities.path_checksum([self.databaseDirectory]), + 'thermoLibraries': self.thermoLibraries, + 'reactionLibraries': [library for library, option in self.reactionLibraries], + 'seedMechanisms': self.seedMechanisms, + 'kineticsFamilies': self.kineticsFamilies, + 'kineticsDepositories': self.kineticsDepositories, + #'frequenciesLibraries': self.statmechLibraries, + 'kineticsEstimator': self.kineticsEstimator, + 'rmgpy.data source hash': rmgpy.data.getSourceHash(), + 'this source hash': hashlib.sha1(inspect.getsource(self.__class__)).hexdigest(), + } + database_hash = hashlib.sha1(cPickle.dumps(database_metadata)).hexdigest() + cache_hash_file = os.path.join(self.outputDirectory,'database.hash') + cache_pickle_file = os.path.join(self.outputDirectory,'database.pkl') + scoop.shared.setConst(databaseFile=cache_pickle_file, databaseHash=database_hash) + if not os.path.exists(cache_pickle_file): + logging.info("Couldn't find a database cache file {0!r} so will reload from source.".format(cache_pickle_file)) + elif not os.path.exists(cache_hash_file): + logging.info("Couldn't find database cache hash file {0!r} to validate cache so will reload from source.".format(cache_hash_file)) + else: + if database_hash != open(cache_hash_file,'r').read(): + logging.info("According to hash file, it looks like database cache is not valid. Will clear it and reload.") + os.unlink(cache_hash_file) + os.unlink(cache_pickle_file) + else: + logging.info("According to hash file, it looks like database cache is valid.") + database = cPickle.load(open(cache_pickle_file, 'rb')) + # Check the database from the pickle really does have the hash in the database.hash file. + if database.hash == database_hash: + logging.info("Database loaded from {0} has correct hash. Will use this cache.".format(cache_pickle_file)) + self.database = database + rmgpy.data.rmg.database = database # we need to store it in this module level variable too! + return + else: + logging.info("Database loaded from {0} has INCORRECT hash. Will clear the cache and reload.".format(cache_pickle_file)) + os.unlink(cache_hash_file) + os.unlink(cache_pickle_file) + self.database = RMGDatabase() self.database.load( path = self.databaseDirectory, @@ -247,6 +308,84 @@ def loadDatabase(self): logging.info('Filling in rate rules in kinetics families by averaging...') for family in self.database.kinetics.families.values(): family.fillKineticsRulesByAveragingUp() + + self.database.hash = database_hash # store the hash in the database so we can check it when it is next pickled. + logging.info("Saving database cache in {0!r}".format(cache_pickle_file)) + self.database.saveToPickle(cache_pickle_file) + with open(cache_hash_file,'w') as f: + f.write(database_hash) + + def loadThermoDatabase(self): + """ + Load the RMG Database. + + The data is loaded from self.databaseDirectory, according to settings in: + + * self.thermoLibraries + * self.reactionLibraries + * self.seedMechanisms + * self.kineticsFamilies + * self.kineticsDepositories + + If `self.kineticsEstimator == 'rate rules'` then the training set values are + added and the blanks are filled in by averaging. + + If self.outputDirectory contains :file:`database.pkl` and :file:`database.hash` files then + these are checked for validity and used as a cache. Once loaded (and averages filled + in if necessary) then a cache (pickle and hash) is saved. + """ + import inspect, hashlib, cPickle, rmgpy.utilities, scoop.shared + + # Make a hash of everything that could alter the contents of the database once it is fully loaded. + # Then we can compare this hash to the cached file to see if the cache is valid. + database_metadata = { + 'path': self.databaseDirectory, + 'database hash': rmgpy.utilities.path_checksum([self.databaseDirectory]), + 'thermoLibraries': self.thermoLibraries, + 'rmgpy.data source hash': rmgpy.data.getSourceHash(), + 'this source hash': hashlib.sha1(inspect.getsource(self.__class__)).hexdigest(), + } + database_hash = hashlib.sha1(cPickle.dumps(database_metadata)).hexdigest() + cache_hash_file = os.path.join(self.outputDirectory,'database.hash') + cache_pickle_file = os.path.join(self.outputDirectory,'database.pkl') + scoop.shared.setConst(databaseFile=cache_pickle_file, databaseHash=database_hash) + if not os.path.exists(cache_pickle_file): + logging.info("Couldn't find a database cache file {0!r} so will reload from source.".format(cache_pickle_file)) + elif not os.path.exists(cache_hash_file): + logging.info("Couldn't find database cache hash file {0!r} to validate cache so will reload from source.".format(cache_hash_file)) + else: + if database_hash != open(cache_hash_file,'r').read(): + logging.info("According to hash file, it looks like database cache is not valid. Will clear it and reload.") + os.unlink(cache_hash_file) + os.unlink(cache_pickle_file) + else: + logging.info("According to hash file, it looks like database cache is valid.") + database = cPickle.load(open(cache_pickle_file, 'rb')) + # Check the database from the pickle really does have the hash in the database.hash file. + if database.hash == database_hash: + logging.info("Database loaded from {0} has correct hash. Will use this cache.".format(cache_pickle_file)) + self.database = database + rmgpy.data.rmg.database = database # we need to store it in this module level variable too! + return + else: + logging.info("Database loaded from {0} has INCORRECT hash. Will clear the cache and reload.".format(cache_pickle_file)) + os.unlink(cache_hash_file) + os.unlink(cache_pickle_file) + + self.database = RMGDatabase() + self.database.loadThermo( + path = os.path.join(self.databaseDirectory, 'thermo'), + thermoLibraries = self.thermoLibraries, + depository = False, # Don't bother loading the depository information, as we don't use it + ) + + self.database.hash = database_hash # store the hash in the database so we can check it when it is next pickled. + logging.info("Saving database cache in {0!r}".format(cache_pickle_file)) + self.database.saveToPickle(cache_pickle_file) + with open(cache_hash_file,'w') as f: + f.write(database_hash) + + def initialize(self, args): """ @@ -1044,7 +1183,7 @@ def initializeLog(verbose, log_file_name): logging.addLevelName(logging.ERROR, 'Error: ') logging.addLevelName(logging.WARNING, 'Warning: ') logging.addLevelName(logging.INFO, '') - logging.addLevelName(logging.DEBUG, '') + logging.addLevelName(logging.DEBUG, 'Debug:') logging.addLevelName(0, '') # Create formatter and add to console handler diff --git a/rmgpy/rmg/model.py b/rmgpy/rmg/model.py index bd4e2753fb..389dc57fbb 100644 --- a/rmgpy/rmg/model.py +++ b/rmgpy/rmg/model.py @@ -38,6 +38,9 @@ import os.path import itertools +import scoop +from scoop import futures,shared + from rmgpy.display import display #import rmgpy.chemkin import rmgpy.constants as constants @@ -61,6 +64,30 @@ from pdep import PDepReaction, PDepNetwork, PressureDependenceError # generateThermoDataFromQM under the Species class imports the qm package +__database = None +qmValue = None + +def makeThermoForSpecies(spec): + """ + Make thermo for a species. + """ + global __database, qmValue + if qmValue == None: qmValue = scoop.shared.getConst('qmValue') + if __database == None: + """Load the database from some pickle file""" + import cPickle + filename = scoop.shared.getConst('databaseFile') + database_hash = scoop.shared.getConst('databaseHash') + logging.debug('Loading database pickle2 file'.format(filename)) + #logging.info('Loading database pickle2 file from {0!r} on worker {1}'.format(filename, scoop.WORKER_NAME.decode() )) + f = open(filename, 'rb') + __database = cPickle.load(f) + f.close() + assert __database.hash == database_hash, "Database loaded from {0!r} doesn't match expected hash!".format(filename) + logging.debug("Generate thermo data in makeThermoForSpecies") + spec.generateThermoData(__database,quantumMechanics=qmValue) + logging.debug("Thermo generated for {0}".format(spec.label)) + return spec.thermo ################################################################################ @@ -101,17 +128,18 @@ def generateThermoData(self, database, thermoClass=NASA, quantumMechanics=None): from rmgpy.data.thermo import saveEntry thermo0 = None - thermo0 = database.thermo.getThermoDataFromLibraries(self) - + if quantumMechanics is None : logging.debug("qmValue is None at generateThermoData in model.py") if thermo0 is not None: - logging.info("Found thermo for {0} in thermo library".format(self.label)) + logging.debug("Found thermo for {0} in thermo library".format(self.label)) assert len(thermo0) == 3, "thermo0 should be a tuple at this point: (thermoData, library, entry)" thermo0 = thermo0[0] elif quantumMechanics: + logging.debug("Generate thermo data with QM") molecule = self.molecule[0] if quantumMechanics.settings.onlyCyclics and not molecule.isCyclic(): + logging.debug("Bypassing QM for ".format(self.label)) pass else: # try a QM calculation if molecule.getRadicalCount() > quantumMechanics.settings.maxRadicalNumber: @@ -146,10 +174,12 @@ def generateThermoData(self, database, thermoClass=NASA, quantumMechanics=None): f.write('{0}\n'.format(molecule.toSMILES())) f.write('{0}\n\n'.format(molecule.toAdjacencyList(removeH=False))) else: # Not too many radicals: do a direct calculation. + logging.debug("Generate thermo for {0} with QM".format(self.label)) thermo0 = quantumMechanics.getThermoData(molecule) # returns None if it fails - + if thermo0 is None: logging.debug("QM for {0} failed.".format(self.label)) if thermo0 is not None: # Write the QM molecule thermo to a library so that can be used in future RMG jobs. + logging.debug("QM for {0} is successful.".format(self.label)) quantumMechanics.database.loadEntry(index = len(quantumMechanics.database.entries) + 1, label = molecule.toSMILES(), molecule = molecule.toAdjacencyList(), @@ -359,7 +389,7 @@ def checkForExistingSpecies(self, molecule): # Return an existing species if a match is found formula = molecule.getFormula() try: - speciesList = self.speciesDict[formula] + speciesList = self.speciesDict[formula] except KeyError: return False, None for spec in speciesList: @@ -716,8 +746,8 @@ def enlarge(self, newObject): # Generate thermodynamics of new species logging.info('Generating thermodynamics for new species...') + self.generateThermoDataForListOfSpecies(newSpeciesList) for spec in newSpeciesList: - spec.generateThermoData(database, quantumMechanics=self.quantumMechanics) spec.generateTransportData(database) # Generate kinetics of new reactions @@ -777,6 +807,19 @@ def enlarge(self, newObject): ) logging.info('') + + def generateThermoDataForListOfSpecies(self, listOfSpecies): + """ + Generates the thermo data for a list of species. + + Results are stored in the species objects themselves. + """ + # this works without scoop: + #outputs = map(makeThermoForSpecies, listOfSpecies) + # this tried so do it via scoop's map: + outputs = futures.map(makeThermoForSpecies, listOfSpecies,qmValue=self.quantumMechanics) + for spec, thermo in zip(listOfSpecies, outputs): + spec.thermo = thermo def processNewReactions(self, newReactions, newSpecies, pdepNetwork=None): """ diff --git a/rmgpy/statmech/rotation.pyx b/rmgpy/statmech/rotation.pyx index c39b4feac3..826a235531 100644 --- a/rmgpy/statmech/rotation.pyx +++ b/rmgpy/statmech/rotation.pyx @@ -159,7 +159,7 @@ cdef class LinearRotor(Rotation): def __set__(self, B): cdef double I B = quantity.Frequency(B) - I = constants.h / (8 * constants.pi * constants.pi * (B.value_si * constants.c * 100.)) + I = constants.h / (8 * constants.pi * constants.pi * (max(B.value_si) * constants.c * 100.)) self._inertia = quantity.ScalarQuantity(I / (constants.amu * 1e-20), "amu*angstrom^2") cpdef double getLevelEnergy(self, int J) except -1: diff --git a/rmgpy/utilities.py b/rmgpy/utilities.py new file mode 100644 index 0000000000..3a7ec28ba8 --- /dev/null +++ b/rmgpy/utilities.py @@ -0,0 +1,52 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +''' +Created on Apr 5, 2013 + +@author: rwest + + +''' + +import hashlib +from os.path import normpath, walk, isdir, isfile, dirname, basename, \ + exists as path_exists, join as path_join + +def path_checksum(paths): + """ + Recursively calculates a checksum representing the contents of all files + found with a sequence of file and/or directory paths. + + eg. path_checksum(['/tmp']) + + Based on post by David Moss at + http://code.activestate.com/recipes/576973-getting-the-sha-1-or-md5-hash-of-a-directory/ + """ + if not hasattr(paths, '__iter__'): + raise TypeError('sequence or iterable expected not %r!' % type(paths)) + + def _update_checksum(checksum, dirname, filenames): + for filename in sorted(filenames): + path = path_join(dirname, filename) + if isfile(path): + #print path + fh = open(path, 'rb') + while 1: + buf = fh.read(4096) + if not buf : break + checksum.update(buf) + fh.close() + + chksum = hashlib.sha1() + + for path in sorted([normpath(f) for f in paths]): + if path_exists(path): + if isdir(path): + walk(path, _update_checksum, chksum) + elif isfile(path): + _update_checksum(chksum, dirname(path), basename(path)) + + return chksum.hexdigest() + +if __name__ == '__main__': + print path_checksum([r'/tmp', '/etc/hosts']) diff --git a/thermoEstimator.py b/thermoEstimator.py index 086dca17b6..5f079e82a1 100755 --- a/thermoEstimator.py +++ b/thermoEstimator.py @@ -9,57 +9,137 @@ """ import os.path -from rmgpy.rmg.main import RMG +import logging +from rmgpy.rmg.main import RMG, initializeLog, processProfileStats, makeProfileGraph from rmgpy.data.thermo import ThermoLibrary from rmgpy.chemkin import writeThermoEntry - +from rmgpy.rmg.model import makeThermoForSpecies +from scoop import futures,shared +import resource # to see memory usage ################################################################################ - -def runThermoEstimator(inputFile): +def chunks(l, n): + """ + Yield successive n-sized chunks from l. + """ + for i in range(0, len(l), n): + yield l[i:i+n] + +def runThermoEstimator(inputFile,chunkSize): """ Estimate thermo for a list of species using RMG and the settings chosen inside a thermo input file. """ - + logging.debug("Maximum memory usage:{0} MBs.".format(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1000)) rmg = RMG() + + logging.debug("RMG object created...") + logging.debug("Maximum memory usage:{0} MBs.".format(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1000)) rmg.loadThermoInput(inputFile) + logging.debug("Input file loaded...") + logging.debug("Maximum memory usage:{0} MBs.".format(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1000)) # initialize and load the database as well as any QM settings - rmg.loadDatabase() + rmg.loadThermoDatabase() + logging.debug("Thermo database loaded...") + logging.debug("Maximum memory usage:{0} MBs.".format(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1000)) if rmg.quantumMechanics: rmg.quantumMechanics.initialize() + logging.debug("QM module initialized...") + logging.debug("Maximum memory usage:{0} MBs.".format(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1000)) # Generate the thermo for all the species and write them to chemkin format as well as # ThermoLibrary format with values for H, S, and Cp's. output = open(os.path.join(rmg.outputDirectory, 'output.txt'),'wb') - library = ThermoLibrary(name='Thermo Estimation Library') - for species in rmg.initialSpecies: - species.generateThermoData(rmg.database, quantumMechanics=rmg.reactionModel.quantumMechanics) + listOfSpecies=rmg.initialSpecies + logging.debug("Initial species loaded...") + logging.debug("Maximum memory usage:{0} MBs.".format(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1000)) - library.loadEntry( - index = len(library.entries) + 1, - label = species.label, - molecule = species.molecule[0].toAdjacencyList(), - thermo = species.thermo.toThermoData(), - shortDesc = species.thermo.comment, - ) - output.write(writeThermoEntry(species)) - output.write('\n') - + chunkIndex=0 + shared.setConst(qmValue=rmg.reactionModel.quantumMechanics) + for chunk in list(chunks(listOfSpecies,chunkSize)): + # There will be no stdout from workers except the main one. + outputList = futures.map(makeThermoForSpecies, chunk) + if chunkIndex == 0: libraryName = 'ThermoLibrary' + else: libraryName = 'ThermoLibrary'+ str(chunkIndex) + library = ThermoLibrary(name=libraryName) + for species, thermo in zip(chunk, outputList): + logging.debug("Species {0}".format(species.label)) + species.thermo = thermo + library.loadEntry( + index = len(library.entries) + 1, + label = species.label, + molecule = species.molecule[0].toAdjacencyList(), + thermo = species.thermo.toThermoData(), + shortDesc = species.thermo.comment, + ) + output.write(writeThermoEntry(species)) + output.write('\n') + logging.debug("Maximum memory usage:{0} MBs.".format(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1000)) + libraryFile = libraryName + '.py' + library.save(os.path.join(rmg.outputDirectory, libraryFile)) + logging.debug("{0} created.".format(libraryFile)) + del library + chunkIndex += 1 output.close() - library.save(os.path.join(rmg.outputDirectory,'ThermoLibrary.py')) + logging.debug("runThermoEstimator is done.") + ################################################################################ if __name__ == '__main__': - import argparse - parser = argparse.ArgumentParser() - parser.add_argument('input', metavar='INPUT', type=str, nargs=1, - help='Thermo input file') + parser = argparse.ArgumentParser(description= + """ + thermoEstimator.py generates thermochemical parameters based on Benson group additivity + or quantum mechanical calculations. \n + Generates three output files. + RMG.log: Contains information about the process. + output.txt: Contains string representations of the NASA model for each species, readable by Chemkin. + ThermoLibrary.py: Thermo library that can be used in RMG simulations. Can be uploaded to RMG-database. + """) + parser.add_argument('input', metavar='FILE', type=str, default='input.py', nargs='?', + help='Thermo input file. (Default file is input.py)') + parser.add_argument('CHUNKSIZE', type=int, default=10000,nargs='?', help='''chunk size that determines number of species passed to + workers at once, should be larger than the number of processors. (default value is 10000)''') + group1 = parser.add_mutually_exclusive_group() + group1.add_argument('-p', '--profile', action='store_true', help='run under cProfile to gather profiling statistics, and postprocess them if job completes') + group1.add_argument('-P', '--postprocess', action='store_true', help='postprocess profiling statistics from previous [failed] run; does not run the simulation') + group2 = parser.add_mutually_exclusive_group() + group2.add_argument('-d', '--debug', action='store_true', help='print debug information') + group2.add_argument('-q', '--quiet', action='store_true', help='only print warnings and errors') + + args = parser.parse_args() - inputFile = os.path.abspath(args.input[0]) + inputFile = os.path.abspath(args.input) + inputDirectory = os.path.abspath(os.path.dirname(args.input)) + chunkSize = args.CHUNKSIZE + if args.postprocess: + print "Postprocessing the profiler statistics (will be appended to thermo.log)" + print "Use `dot -Tpdf RMG.profile.dot -o RMG.profile.pdf`" + args.profile = True - runThermoEstimator(inputFile) \ No newline at end of file + if args.profile: + import cProfile, sys, pstats, os + global_vars = {} + local_vars = {'inputFile': inputFile,'chunkSize':chunkSize,'runThermoEstimator':runThermoEstimator} + command = """runThermoEstimator(inputFile,chunkSize)""" + stats_file = 'RMG.profile' + print("Running under cProfile") + if not args.postprocess: + # actually run the program! + cProfile.runctx(command, global_vars, local_vars, stats_file) + # postprocess the stats + log_file = os.path.join(inputDirectory,'RMG.log') + processProfileStats(stats_file, log_file) + makeProfileGraph(stats_file) + + else: + + if args.debug: level = logging.DEBUG + elif args.quiet: level = logging.WARNING + else: level = logging.INFO + initializeLog(level, 'RMG.log') + logging.debug("runThermoEstimator starts...") + runThermoEstimator(inputFile,chunkSize)