From 19cc760177c5413c9e1b751d26d5e50ab670fa10 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 17 Dec 2012 16:44:32 -0500 Subject: [PATCH 01/55] New file for working with solute descriptors for solvated molecules; modeled after file for fetching thermo data --- rmgpy/data/solvation.py | 597 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 597 insertions(+) create mode 100644 rmgpy/data/solvation.py diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py new file mode 100644 index 00000000000..748d8a383e6 --- /dev/null +++ b/rmgpy/data/solvation.py @@ -0,0 +1,597 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +################################################################################ +# +# RMG - Reaction Mechanism Generator +# +# Copyright (c) 2002-2010 Prof. William H. Green (whgreen@mit.edu) and the +# RMG Team (rmg_dev@mit.edu) +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the 'Software'), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# +################################################################################ + +""" + +""" + +import os +import os.path +import math +import logging +import numpy +from copy import copy, deepcopy + +from base import Database, Entry, makeLogicNode + +import rmgpy.constants as constants +from rmgpy.thermo import * +from rmgpy.molecule import Molecule, Atom, Bond, Group + +################################################################################ + +def saveEntry(f, entry): + """ + Write a Pythonic string representation of the given `entry` in the thermo + database to the file object `f`. + """ + raise NotImplementedError() + +def generateOldLibraryEntry(data): + """ + Return a list of values used to save entries to the old-style RMG + thermo database based on the thermodynamics object `data`. + """ + raise NotImplementedError() + +def processOldLibraryEntry(data): + """ + Process a list of parameters `data` as read from an old-style RMG + thermo database, returning the corresponding thermodynamics object. + """ + raise NotImplementedError() + + +class SoluteData(): + """ + Stores Abraham parameters to characterize a solute + """ + def __init__(self, S=None, B=None, E=None, L=None, A=None): + #: :math:`\pi_2^H` + self.S = S + self.B = B + self.E = E + self.L = L + self.A = A + + + +################################################################################ + +class SoluteLibrary(Database): + """ + A class for working with a RMG solute library. + """ + pass + +################################################################################ + +class SoluteGroups(Database): + """ + A class for working with an RMG solute group additivity database. + """ + + def __init__(self, label='', name='', shortDesc='', longDesc=''): + Database.__init__(self, label=label, name=name, shortDesc=shortDesc, longDesc=longDesc) + + def loadEntry(self, + index, + label, + group, + solute, + reference=None, + referenceType='', + shortDesc='', + longDesc='', + history=None + ): + if group[0:3].upper() == 'OR{' or group[0:4].upper() == 'AND{' or group[0:7].upper() == 'NOT OR{' or group[0:8].upper() == 'NOT AND{': + item = makeLogicNode(group) + else: + item = Group().fromAdjacencyList(group) + self.entries[label] = Entry( + index = index, + label = label, + item = item, + data = solute, + reference = reference, + referenceType = referenceType, + shortDesc = shortDesc, + longDesc = longDesc.strip(), + history = history or [], + ) + + def saveEntry(self, f, entry): + """ + Write the given `entry` in the thermo database to the file object `f`. + """ + return saveEntry(f, entry) + + def generateOldLibraryEntry(self, data): + """ + Return a list of values used to save entries to the old-style RMG + thermo database based on the thermodynamics object `data`. + """ + + return generateOldLibraryEntry(data) + + def processOldLibraryEntry(self, data): + """ + Process a list of parameters `data` as read from an old-style RMG + thermo database, returning the corresponding thermodynamics object. + """ + return processOldLibraryEntry(data) + +################################################################################ + +class SoluteDatabase(object): + """ + A class for working with the RMG solute database. + """ + + def __init__(self): + self.depository = {} + self.libraries = {} + self.groups = {} + self.libraryOrder = [] + self.local_context = { + 'ThermoData': ThermoData, + 'Wilhoit': Wilhoit, + 'NASAPolynomial': NASAPolynomial, + 'NASA': NASA, + } + self.global_context = {} + + def __reduce__(self): + """ + A helper function used when pickling a ThermoDatabase object. + """ + raise NotImplementedError() + + def __setstate__(self, d): + """ + A helper function used when unpickling a ThermoDatabase object. + """ + raise NotImplementedError() + + def load(self, path, libraries=None, depository=True): + """ + Load the thermo database from the given `path` on disk, where `path` + points to the top-level folder of the thermo database. + """ + if depository: + self.loadDepository(os.path.join(path, 'depository')) + else: + self.depository = {} + self.loadLibraries(os.path.join(path, 'libraries'), libraries) + self.loadGroups(os.path.join(path, 'groups')) + + def loadDepository(self, path): + """ + Load the thermo database from the given `path` on disk, where `path` + points to the top-level folder of the thermo database. + """ + self.depository = {} + self.depository['stable'] = ThermoDepository().load(os.path.join(path, 'stable.py'), self.local_context, self.global_context) + self.depository['radical'] = ThermoDepository().load(os.path.join(path, 'radical.py'), self.local_context, self.global_context) + + def loadLibraries(self, path, libraries=None): + """ + Load the thermo database from the given `path` on disk, where `path` + points to the top-level folder of the thermo database. + """ + self.libraries = {}; self.libraryOrder = [] + for (root, dirs, files) in os.walk(os.path.join(path)): + for f in files: + name, ext = os.path.splitext(f) + if ext.lower() == '.py' and (libraries is None or name in libraries): + logging.info('Loading thermodynamics library from {0} in {1}...'.format(f, root)) + library = ThermoLibrary() + library.load(os.path.join(root, f), self.local_context, self.global_context) + library.label = os.path.splitext(f)[0] + self.libraries[library.label] = library + self.libraryOrder.append(library.label) + if libraries is not None: + self.libraryOrder = libraries + + def loadGroups(self, path): + """ + Load the thermo database from the given `path` on disk, where `path` + points to the top-level folder of the thermo database. + """ + logging.info('Loading thermodynamics group database from {0}...'.format(path)) + self.groups = {} + self.groups['group'] = ThermoGroups(label='group').load(os.path.join(path, 'group.py' ), self.local_context, self.global_context) + self.groups['gauche'] = ThermoGroups(label='gauche').load(os.path.join(path, 'gauche.py' ), self.local_context, self.global_context) + self.groups['int15'] = ThermoGroups(label='int15').load(os.path.join(path, 'int15.py' ), self.local_context, self.global_context) + self.groups['ring'] = ThermoGroups(label='ring').load(os.path.join(path, 'ring.py' ), self.local_context, self.global_context) + self.groups['radical'] = ThermoGroups(label='radical').load(os.path.join(path, 'radical.py'), self.local_context, self.global_context) + self.groups['other'] = ThermoGroups(label='other').load(os.path.join(path, 'other.py' ), self.local_context, self.global_context) + + def save(self, path): + """ + Save the thermo database to the given `path` on disk, where `path` + points to the top-level folder of the thermo database. + """ + path = os.path.abspath(path) + if not os.path.exists(path): os.mkdir(path) + self.saveDepository(os.path.join(path, 'depository')) + self.saveLibraries(os.path.join(path, 'libraries')) + self.saveGroups(os.path.join(path, 'groups')) + + def saveDepository(self, path): + """ + Save the thermo depository to the given `path` on disk, where `path` + points to the top-level folder of the thermo depository. + """ + if not os.path.exists(path): os.mkdir(path) + self.depository['stable'].save(os.path.join(path, 'stable.py')) + self.depository['radical'].save(os.path.join(path, 'radical.py')) + + def saveLibraries(self, path): + """ + Save the thermo libraries to the given `path` on disk, where `path` + points to the top-level folder of the thermo libraries. + """ + if not os.path.exists(path): os.mkdir(path) + for library in self.libraries.values(): + library.save(os.path.join(path, '{0}.py'.format(library.label))) + + def saveGroups(self, path): + """ + Save the thermo groups to the given `path` on disk, where `path` + points to the top-level folder of the thermo groups. + """ + if not os.path.exists(path): os.mkdir(path) + self.groups['group'].save(os.path.join(path, 'group.py')) + self.groups['gauche'].save(os.path.join(path, 'gauche.py')) + self.groups['int15'].save(os.path.join(path, 'int15.py')) + self.groups['ring'].save(os.path.join(path, 'ring.py')) + self.groups['radical'].save(os.path.join(path, 'radical.py')) + self.groups['other'].save(os.path.join(path, 'other.py')) + + def loadOld(self, path): + """ + Load the old RMG thermo database from the given `path` on disk, where + `path` points to the top-level folder of the old RMG database. + """ + # The old database does not have a depository, so create an empty one + self.depository = {} + self.depository['stable'] = ThermoDepository(label='stable', name='Stable Molecules') + self.depository['radical'] = ThermoDepository(label='radical', name='Radical Molecules') + + for (root, dirs, files) in os.walk(os.path.join(path, 'thermo_libraries')): + if os.path.exists(os.path.join(root, 'Dictionary.txt')) and os.path.exists(os.path.join(root, 'Library.txt')): + library = ThermoLibrary(label=os.path.basename(root), name=os.path.basename(root)) + library.loadOld( + dictstr = os.path.join(root, 'Dictionary.txt'), + treestr = '', + libstr = os.path.join(root, 'Library.txt'), + numParameters = 12, + numLabels = 1, + pattern = False, + ) + library.label = os.path.basename(root) + self.libraries[library.label] = library + + self.groups = {} + self.groups['group'] = ThermoGroups(label='group', name='Functional Group Additivity Values').loadOld( + dictstr = os.path.join(path, 'thermo_groups', 'Group_Dictionary.txt'), + treestr = os.path.join(path, 'thermo_groups', 'Group_Tree.txt'), + libstr = os.path.join(path, 'thermo_groups', 'Group_Library.txt'), + numParameters = 12, + numLabels = 1, + pattern = True, + ) + self.groups['gauche'] = ThermoGroups(label='gauche', name='Gauche Interaction Corrections').loadOld( + dictstr = os.path.join(path, 'thermo_groups', 'Gauche_Dictionary.txt'), + treestr = os.path.join(path, 'thermo_groups', 'Gauche_Tree.txt'), + libstr = os.path.join(path, 'thermo_groups', 'Gauche_Library.txt'), + numParameters = 12, + numLabels = 1, + pattern = True, + ) + self.groups['int15'] = ThermoGroups(label='int15', name='1,5-Interaction Corrections').loadOld( + dictstr = os.path.join(path, 'thermo_groups', '15_Dictionary.txt'), + treestr = os.path.join(path, 'thermo_groups', '15_Tree.txt'), + libstr = os.path.join(path, 'thermo_groups', '15_Library.txt'), + numParameters = 12, + numLabels = 1, + pattern = True, + ) + self.groups['radical'] = ThermoGroups(label='radical', name='Radical Corrections').loadOld( + dictstr = os.path.join(path, 'thermo_groups', 'Radical_Dictionary.txt'), + treestr = os.path.join(path, 'thermo_groups', 'Radical_Tree.txt'), + libstr = os.path.join(path, 'thermo_groups', 'Radical_Library.txt'), + numParameters = 12, + numLabels = 1, + pattern = True, + ) + self.groups['ring'] = ThermoGroups(label='ring', name='Ring Corrections').loadOld( + dictstr = os.path.join(path, 'thermo_groups', 'Ring_Dictionary.txt'), + treestr = os.path.join(path, 'thermo_groups', 'Ring_Tree.txt'), + libstr = os.path.join(path, 'thermo_groups', 'Ring_Library.txt'), + numParameters = 12, + numLabels = 1, + pattern = True, + ) + self.groups['other'] = ThermoGroups(label='other', name='Other Corrections').loadOld( + dictstr = os.path.join(path, 'thermo_groups', 'Other_Dictionary.txt'), + treestr = os.path.join(path, 'thermo_groups', 'Other_Tree.txt'), + libstr = os.path.join(path, 'thermo_groups', 'Other_Library.txt'), + numParameters = 12, + numLabels = 1, + pattern = True, + ) + + def saveOld(self, path): + """ + Save the old RMG thermo database to the given `path` on disk, where + `path` points to the top-level folder of the old RMG database. + """ + raise NotImplementedError() + + def getSoluteData(self, species): + """ + Return the thermodynamic parameters for a given :class:`Species` + object `species`. This function first searches the loaded libraries + in order, returning the first match found, before falling back to + estimation via group additivity. + """ + soluteData = None + # Check the libraries in order first; return the first successful match + for label in self.libraryOrder: + soluteData = self.getSoluteDataFromLibrary(species, self.libraries[label]) + if soluteData is not None: + soluteData[0].comment = label + break + else: + # Solute not found in any loaded libraries, so estimate + soluteData = self.getSoluteDataFromGroups(species) + # Add Cp0 and CpInf values + Cp0 = species.calculateCp0() + CpInf = species.calculateCpInf() + data, library, entry = soluteData + if isinstance(data,SoluteData): + data.Cp0 = (Cp0,"J/(mol*K)") + data.CpInf = (CpInf,"J/(mol*K)") + # Return the resulting solute parameters + return data + + def getAllThermoData(self, species): + """ + Return all possible sets of thermodynamic parameters for a given + :class:`Species` object `species`. The hits from the depository come + first, then the libraries (in order), and then the group additivity + estimate. This method is useful for a generic search job. + """ + raise NotImplementedError() + + def getThermoDataFromDepository(self, species): + """ + Return all possible sets of thermodynamic parameters for a given + :class:`Species` object `species` from the depository. If no + depository is loaded, a :class:`DatabaseError` is raised. + """ + raise NotImplementedError() + + def getSoluteDataFromLibrary(self, species, library): + """ + Return the set of Abraham solute descriptors corresponding to a given + :class:`Species` object `species` from the specified solute + `library`. If `library` is a string, the list of libraries is searched + for a library with that name. If no match is found in that library, + ``None`` is returned. If no corresponding library is found, a + :class:`DatabaseError` is raised. + """ + raise NotImplementedError() + + def getThermoDataFromGroups(self, species): + """ + Return the set of Abraham solute parameters corresponding to a given + :class:`Species` object `species` by estimation using the Platts group + additivity method. If no group additivity values are loaded, a + :class:`DatabaseError` is raised. + """ + solute = [] + for molecule in species.molecule: + molecule.clearLabeledAtoms() + molecule.updateAtomTypes() + tdata = self.estimateSoluteViaGroupAdditivity(molecule) + solute.append(sdata) + + #H298 = numpy.array([t.getEnthalpy(298.) for t in thermo]) + #indices = H298.argsort() + + species.molecule = [species.molecule[ind] for ind in indices] + + return (solute[indices[0]], None, None) + + def estimateSoluteViaGroupAdditivity(self, molecule): + """ + Return the set of Abraham solute parameters corresponding to a given + :class:`Molecule` object `molecule` by estimation using the Platts' group + additivity method. If no group additivity values are loaded, a + :class:`DatabaseError` is raised. + """ + # For thermo estimation we need the atoms to already be sorted because we + # iterate over them; if the order changes during the iteration then we + # will probably not visit the right atoms, and so will get the thermo wrong + molecule.sortVertices() + + # Create the SoluteData object + thermoData = ThermoData( + S = 0.0, + B = 0.0, + E = 0.0, + L = 0.0, + A = 0.0 + ) + + if sum([atom.radicalElectrons for atom in molecule.atoms]) > 0: # radical species + + # Make a copy of the structure so we don't change the original + saturatedStruct = molecule.copy(deep=True) + + # Saturate structure by replacing all radicals with bonds to + # hydrogen atoms + added = {} + for atom in saturatedStruct.atoms: + for i in range(atom.radicalElectrons): + H = Atom('H') + bond = Bond(atom, H, 'S') + saturatedStruct.addAtom(H) + saturatedStruct.addBond(bond) + if atom not in added: + added[atom] = [] + added[atom].append([H, bond]) + atom.decrementRadical() + + # Update the atom types of the saturated structure (not sure why + # this is necessary, because saturating with H shouldn't be + # changing atom types, but it doesn't hurt anything and is not + # very expensive, so will do it anyway) + saturatedStruct.updateConnectivityValues() + saturatedStruct.sortVertices() + saturatedStruct.updateAtomTypes() + + # Get solute descriptor estimates for saturated form of structure + soluteData = self.estimateSoluteViaGroupAdditivity(saturatedStruct) + assert soluteData is not None, "Solute data of saturated {0} of molecule {1} is None!".format(saturatedStruct, molecule) + # Undo symmetry number correction for saturated structure + # thermoData.S298.value_si += constants.R * math.log(saturatedStruct.symmetryNumber) + + # For each radical site, get radical correction + # Only one radical site should be considered at a time; all others + # should be saturated with hydrogen atoms + for atom in added: + + # Remove the added hydrogen atoms and bond and restore the radical + for H, bond in added[atom]: + saturatedStruct.removeBond(bond) + saturatedStruct.removeAtom(H) + atom.incrementRadical() + + saturatedStruct.updateConnectivityValues() + + + + # Re-saturate + + + # Subtract the enthalpy of the added hydrogens + + + # Correct the entropy for the symmetry number + + else: # non-radical species + # Generate estimate of solute data + for atom in molecule.atoms: + # Iterate over heavy (non-hydrogen) atoms + if atom.isNonHydrogen(): + # Get initial solute data from main group database + try: + self.__addGroupSoluteoData(soluteData, self.groups['abraham'], molecule, {'*':atom}) + except KeyError: + logging.error("Couldn't find in main abraham database:") + logging.error(molecule) + logging.error(molecule.toAdjacencyList()) + raise + + # Correct for gauche and 1,5- interactions + + + # Do ring corrections separately because we only want to match + # each ring one time; this doesn't work yet + + + # Get thermo correction for this ring + + + # Correct entropy for symmetry number + + + return soluteData + + def __addGroupSoluteData(self, soluteData, database, molecule, atom): + """ + Determine the Platts group additivity solute data for the atom `atom` + in the structure `structure`, and add it to the existing solute data + `soluteData`. + """ + + node0 = database.descendTree(molecule, atom, None) + + if node0 is None: + raise KeyError('Node not found in database.') + + # It's possible (and allowed) that items in the tree may not be in the + # library, in which case we need to fall up the tree until we find an + # ancestor that has an entry in the library + node = node0 + while node.data is None and node is not None: + node = node.parent + if node is None: + raise InvalidDatabaseError('Unable to determine solute parameters for {0}: no library entries for {1} or any of its ancestors.'.format(molecule, node0) ) + + data = node.data; comment = node.label + while isinstance(data, basestring) and data is not None: + for entry in database.entries.values(): + if entry.label == data: + data = entry.data + comment = entry.label + break + comment = '{0}({1})'.format(database.label, comment) + + # This code prints the hierarchy of the found node; useful for debugging + #result = '' + #while node is not None: + # result = ' -> ' + node + result + # node = database.tree.parent[node] + #print result[4:] + + #if len(thermoData.Tdata.value_si) != len(data.Tdata.value_si) or any([T1 != T2 for T1, T2 in zip(thermoData.Tdata.value_si, data.Tdata.value_si)]): + #raise ThermoError('Cannot add these ThermoData objects due to their having different temperature points.') + + for i in range(7): + #thermoData.Cpdata.value_si[i] += data.Cpdata.value_si[i] + soluteData.S.value_si += data.S.value_si + soluteData.B.value_si += data.B.value_si + soluteData.E.value_si += data.E.value_si + soluteData.L.value_si += data.L.value_si + soluteData.A.value_si += data.A.value_si + + + if soluteData.comment: + solute.comment += ' + {0}'.format(comment) + else: + solute.comment = comment + + return soluteData From 01ef413aaaa69098f2a4b56162ada2a25bd0ff90 Mon Sep 17 00:00:00 2001 From: bslakman Date: Thu, 20 Dec 2012 17:13:37 -0500 Subject: [PATCH 02/55] further changes to solvation.py --- rmgpy/data/solvation.py | 1307 +++++++++++++++++++++------------------ 1 file changed, 710 insertions(+), 597 deletions(-) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index 748d8a383e6..696c43d3084 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -1,597 +1,710 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -################################################################################ -# -# RMG - Reaction Mechanism Generator -# -# Copyright (c) 2002-2010 Prof. William H. Green (whgreen@mit.edu) and the -# RMG Team (rmg_dev@mit.edu) -# -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the 'Software'), -# to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. -# -################################################################################ - -""" - -""" - -import os -import os.path -import math -import logging -import numpy -from copy import copy, deepcopy - -from base import Database, Entry, makeLogicNode - -import rmgpy.constants as constants -from rmgpy.thermo import * -from rmgpy.molecule import Molecule, Atom, Bond, Group - -################################################################################ - -def saveEntry(f, entry): - """ - Write a Pythonic string representation of the given `entry` in the thermo - database to the file object `f`. - """ - raise NotImplementedError() - -def generateOldLibraryEntry(data): - """ - Return a list of values used to save entries to the old-style RMG - thermo database based on the thermodynamics object `data`. - """ - raise NotImplementedError() - -def processOldLibraryEntry(data): - """ - Process a list of parameters `data` as read from an old-style RMG - thermo database, returning the corresponding thermodynamics object. - """ - raise NotImplementedError() - - -class SoluteData(): - """ - Stores Abraham parameters to characterize a solute - """ - def __init__(self, S=None, B=None, E=None, L=None, A=None): - #: :math:`\pi_2^H` - self.S = S - self.B = B - self.E = E - self.L = L - self.A = A - - - -################################################################################ - -class SoluteLibrary(Database): - """ - A class for working with a RMG solute library. - """ - pass - -################################################################################ - -class SoluteGroups(Database): - """ - A class for working with an RMG solute group additivity database. - """ - - def __init__(self, label='', name='', shortDesc='', longDesc=''): - Database.__init__(self, label=label, name=name, shortDesc=shortDesc, longDesc=longDesc) - - def loadEntry(self, - index, - label, - group, - solute, - reference=None, - referenceType='', - shortDesc='', - longDesc='', - history=None - ): - if group[0:3].upper() == 'OR{' or group[0:4].upper() == 'AND{' or group[0:7].upper() == 'NOT OR{' or group[0:8].upper() == 'NOT AND{': - item = makeLogicNode(group) - else: - item = Group().fromAdjacencyList(group) - self.entries[label] = Entry( - index = index, - label = label, - item = item, - data = solute, - reference = reference, - referenceType = referenceType, - shortDesc = shortDesc, - longDesc = longDesc.strip(), - history = history or [], - ) - - def saveEntry(self, f, entry): - """ - Write the given `entry` in the thermo database to the file object `f`. - """ - return saveEntry(f, entry) - - def generateOldLibraryEntry(self, data): - """ - Return a list of values used to save entries to the old-style RMG - thermo database based on the thermodynamics object `data`. - """ - - return generateOldLibraryEntry(data) - - def processOldLibraryEntry(self, data): - """ - Process a list of parameters `data` as read from an old-style RMG - thermo database, returning the corresponding thermodynamics object. - """ - return processOldLibraryEntry(data) - -################################################################################ - -class SoluteDatabase(object): - """ - A class for working with the RMG solute database. - """ - - def __init__(self): - self.depository = {} - self.libraries = {} - self.groups = {} - self.libraryOrder = [] - self.local_context = { - 'ThermoData': ThermoData, - 'Wilhoit': Wilhoit, - 'NASAPolynomial': NASAPolynomial, - 'NASA': NASA, - } - self.global_context = {} - - def __reduce__(self): - """ - A helper function used when pickling a ThermoDatabase object. - """ - raise NotImplementedError() - - def __setstate__(self, d): - """ - A helper function used when unpickling a ThermoDatabase object. - """ - raise NotImplementedError() - - def load(self, path, libraries=None, depository=True): - """ - Load the thermo database from the given `path` on disk, where `path` - points to the top-level folder of the thermo database. - """ - if depository: - self.loadDepository(os.path.join(path, 'depository')) - else: - self.depository = {} - self.loadLibraries(os.path.join(path, 'libraries'), libraries) - self.loadGroups(os.path.join(path, 'groups')) - - def loadDepository(self, path): - """ - Load the thermo database from the given `path` on disk, where `path` - points to the top-level folder of the thermo database. - """ - self.depository = {} - self.depository['stable'] = ThermoDepository().load(os.path.join(path, 'stable.py'), self.local_context, self.global_context) - self.depository['radical'] = ThermoDepository().load(os.path.join(path, 'radical.py'), self.local_context, self.global_context) - - def loadLibraries(self, path, libraries=None): - """ - Load the thermo database from the given `path` on disk, where `path` - points to the top-level folder of the thermo database. - """ - self.libraries = {}; self.libraryOrder = [] - for (root, dirs, files) in os.walk(os.path.join(path)): - for f in files: - name, ext = os.path.splitext(f) - if ext.lower() == '.py' and (libraries is None or name in libraries): - logging.info('Loading thermodynamics library from {0} in {1}...'.format(f, root)) - library = ThermoLibrary() - library.load(os.path.join(root, f), self.local_context, self.global_context) - library.label = os.path.splitext(f)[0] - self.libraries[library.label] = library - self.libraryOrder.append(library.label) - if libraries is not None: - self.libraryOrder = libraries - - def loadGroups(self, path): - """ - Load the thermo database from the given `path` on disk, where `path` - points to the top-level folder of the thermo database. - """ - logging.info('Loading thermodynamics group database from {0}...'.format(path)) - self.groups = {} - self.groups['group'] = ThermoGroups(label='group').load(os.path.join(path, 'group.py' ), self.local_context, self.global_context) - self.groups['gauche'] = ThermoGroups(label='gauche').load(os.path.join(path, 'gauche.py' ), self.local_context, self.global_context) - self.groups['int15'] = ThermoGroups(label='int15').load(os.path.join(path, 'int15.py' ), self.local_context, self.global_context) - self.groups['ring'] = ThermoGroups(label='ring').load(os.path.join(path, 'ring.py' ), self.local_context, self.global_context) - self.groups['radical'] = ThermoGroups(label='radical').load(os.path.join(path, 'radical.py'), self.local_context, self.global_context) - self.groups['other'] = ThermoGroups(label='other').load(os.path.join(path, 'other.py' ), self.local_context, self.global_context) - - def save(self, path): - """ - Save the thermo database to the given `path` on disk, where `path` - points to the top-level folder of the thermo database. - """ - path = os.path.abspath(path) - if not os.path.exists(path): os.mkdir(path) - self.saveDepository(os.path.join(path, 'depository')) - self.saveLibraries(os.path.join(path, 'libraries')) - self.saveGroups(os.path.join(path, 'groups')) - - def saveDepository(self, path): - """ - Save the thermo depository to the given `path` on disk, where `path` - points to the top-level folder of the thermo depository. - """ - if not os.path.exists(path): os.mkdir(path) - self.depository['stable'].save(os.path.join(path, 'stable.py')) - self.depository['radical'].save(os.path.join(path, 'radical.py')) - - def saveLibraries(self, path): - """ - Save the thermo libraries to the given `path` on disk, where `path` - points to the top-level folder of the thermo libraries. - """ - if not os.path.exists(path): os.mkdir(path) - for library in self.libraries.values(): - library.save(os.path.join(path, '{0}.py'.format(library.label))) - - def saveGroups(self, path): - """ - Save the thermo groups to the given `path` on disk, where `path` - points to the top-level folder of the thermo groups. - """ - if not os.path.exists(path): os.mkdir(path) - self.groups['group'].save(os.path.join(path, 'group.py')) - self.groups['gauche'].save(os.path.join(path, 'gauche.py')) - self.groups['int15'].save(os.path.join(path, 'int15.py')) - self.groups['ring'].save(os.path.join(path, 'ring.py')) - self.groups['radical'].save(os.path.join(path, 'radical.py')) - self.groups['other'].save(os.path.join(path, 'other.py')) - - def loadOld(self, path): - """ - Load the old RMG thermo database from the given `path` on disk, where - `path` points to the top-level folder of the old RMG database. - """ - # The old database does not have a depository, so create an empty one - self.depository = {} - self.depository['stable'] = ThermoDepository(label='stable', name='Stable Molecules') - self.depository['radical'] = ThermoDepository(label='radical', name='Radical Molecules') - - for (root, dirs, files) in os.walk(os.path.join(path, 'thermo_libraries')): - if os.path.exists(os.path.join(root, 'Dictionary.txt')) and os.path.exists(os.path.join(root, 'Library.txt')): - library = ThermoLibrary(label=os.path.basename(root), name=os.path.basename(root)) - library.loadOld( - dictstr = os.path.join(root, 'Dictionary.txt'), - treestr = '', - libstr = os.path.join(root, 'Library.txt'), - numParameters = 12, - numLabels = 1, - pattern = False, - ) - library.label = os.path.basename(root) - self.libraries[library.label] = library - - self.groups = {} - self.groups['group'] = ThermoGroups(label='group', name='Functional Group Additivity Values').loadOld( - dictstr = os.path.join(path, 'thermo_groups', 'Group_Dictionary.txt'), - treestr = os.path.join(path, 'thermo_groups', 'Group_Tree.txt'), - libstr = os.path.join(path, 'thermo_groups', 'Group_Library.txt'), - numParameters = 12, - numLabels = 1, - pattern = True, - ) - self.groups['gauche'] = ThermoGroups(label='gauche', name='Gauche Interaction Corrections').loadOld( - dictstr = os.path.join(path, 'thermo_groups', 'Gauche_Dictionary.txt'), - treestr = os.path.join(path, 'thermo_groups', 'Gauche_Tree.txt'), - libstr = os.path.join(path, 'thermo_groups', 'Gauche_Library.txt'), - numParameters = 12, - numLabels = 1, - pattern = True, - ) - self.groups['int15'] = ThermoGroups(label='int15', name='1,5-Interaction Corrections').loadOld( - dictstr = os.path.join(path, 'thermo_groups', '15_Dictionary.txt'), - treestr = os.path.join(path, 'thermo_groups', '15_Tree.txt'), - libstr = os.path.join(path, 'thermo_groups', '15_Library.txt'), - numParameters = 12, - numLabels = 1, - pattern = True, - ) - self.groups['radical'] = ThermoGroups(label='radical', name='Radical Corrections').loadOld( - dictstr = os.path.join(path, 'thermo_groups', 'Radical_Dictionary.txt'), - treestr = os.path.join(path, 'thermo_groups', 'Radical_Tree.txt'), - libstr = os.path.join(path, 'thermo_groups', 'Radical_Library.txt'), - numParameters = 12, - numLabels = 1, - pattern = True, - ) - self.groups['ring'] = ThermoGroups(label='ring', name='Ring Corrections').loadOld( - dictstr = os.path.join(path, 'thermo_groups', 'Ring_Dictionary.txt'), - treestr = os.path.join(path, 'thermo_groups', 'Ring_Tree.txt'), - libstr = os.path.join(path, 'thermo_groups', 'Ring_Library.txt'), - numParameters = 12, - numLabels = 1, - pattern = True, - ) - self.groups['other'] = ThermoGroups(label='other', name='Other Corrections').loadOld( - dictstr = os.path.join(path, 'thermo_groups', 'Other_Dictionary.txt'), - treestr = os.path.join(path, 'thermo_groups', 'Other_Tree.txt'), - libstr = os.path.join(path, 'thermo_groups', 'Other_Library.txt'), - numParameters = 12, - numLabels = 1, - pattern = True, - ) - - def saveOld(self, path): - """ - Save the old RMG thermo database to the given `path` on disk, where - `path` points to the top-level folder of the old RMG database. - """ - raise NotImplementedError() - - def getSoluteData(self, species): - """ - Return the thermodynamic parameters for a given :class:`Species` - object `species`. This function first searches the loaded libraries - in order, returning the first match found, before falling back to - estimation via group additivity. - """ - soluteData = None - # Check the libraries in order first; return the first successful match - for label in self.libraryOrder: - soluteData = self.getSoluteDataFromLibrary(species, self.libraries[label]) - if soluteData is not None: - soluteData[0].comment = label - break - else: - # Solute not found in any loaded libraries, so estimate - soluteData = self.getSoluteDataFromGroups(species) - # Add Cp0 and CpInf values - Cp0 = species.calculateCp0() - CpInf = species.calculateCpInf() - data, library, entry = soluteData - if isinstance(data,SoluteData): - data.Cp0 = (Cp0,"J/(mol*K)") - data.CpInf = (CpInf,"J/(mol*K)") - # Return the resulting solute parameters - return data - - def getAllThermoData(self, species): - """ - Return all possible sets of thermodynamic parameters for a given - :class:`Species` object `species`. The hits from the depository come - first, then the libraries (in order), and then the group additivity - estimate. This method is useful for a generic search job. - """ - raise NotImplementedError() - - def getThermoDataFromDepository(self, species): - """ - Return all possible sets of thermodynamic parameters for a given - :class:`Species` object `species` from the depository. If no - depository is loaded, a :class:`DatabaseError` is raised. - """ - raise NotImplementedError() - - def getSoluteDataFromLibrary(self, species, library): - """ - Return the set of Abraham solute descriptors corresponding to a given - :class:`Species` object `species` from the specified solute - `library`. If `library` is a string, the list of libraries is searched - for a library with that name. If no match is found in that library, - ``None`` is returned. If no corresponding library is found, a - :class:`DatabaseError` is raised. - """ - raise NotImplementedError() - - def getThermoDataFromGroups(self, species): - """ - Return the set of Abraham solute parameters corresponding to a given - :class:`Species` object `species` by estimation using the Platts group - additivity method. If no group additivity values are loaded, a - :class:`DatabaseError` is raised. - """ - solute = [] - for molecule in species.molecule: - molecule.clearLabeledAtoms() - molecule.updateAtomTypes() - tdata = self.estimateSoluteViaGroupAdditivity(molecule) - solute.append(sdata) - - #H298 = numpy.array([t.getEnthalpy(298.) for t in thermo]) - #indices = H298.argsort() - - species.molecule = [species.molecule[ind] for ind in indices] - - return (solute[indices[0]], None, None) - - def estimateSoluteViaGroupAdditivity(self, molecule): - """ - Return the set of Abraham solute parameters corresponding to a given - :class:`Molecule` object `molecule` by estimation using the Platts' group - additivity method. If no group additivity values are loaded, a - :class:`DatabaseError` is raised. - """ - # For thermo estimation we need the atoms to already be sorted because we - # iterate over them; if the order changes during the iteration then we - # will probably not visit the right atoms, and so will get the thermo wrong - molecule.sortVertices() - - # Create the SoluteData object - thermoData = ThermoData( - S = 0.0, - B = 0.0, - E = 0.0, - L = 0.0, - A = 0.0 - ) - - if sum([atom.radicalElectrons for atom in molecule.atoms]) > 0: # radical species - - # Make a copy of the structure so we don't change the original - saturatedStruct = molecule.copy(deep=True) - - # Saturate structure by replacing all radicals with bonds to - # hydrogen atoms - added = {} - for atom in saturatedStruct.atoms: - for i in range(atom.radicalElectrons): - H = Atom('H') - bond = Bond(atom, H, 'S') - saturatedStruct.addAtom(H) - saturatedStruct.addBond(bond) - if atom not in added: - added[atom] = [] - added[atom].append([H, bond]) - atom.decrementRadical() - - # Update the atom types of the saturated structure (not sure why - # this is necessary, because saturating with H shouldn't be - # changing atom types, but it doesn't hurt anything and is not - # very expensive, so will do it anyway) - saturatedStruct.updateConnectivityValues() - saturatedStruct.sortVertices() - saturatedStruct.updateAtomTypes() - - # Get solute descriptor estimates for saturated form of structure - soluteData = self.estimateSoluteViaGroupAdditivity(saturatedStruct) - assert soluteData is not None, "Solute data of saturated {0} of molecule {1} is None!".format(saturatedStruct, molecule) - # Undo symmetry number correction for saturated structure - # thermoData.S298.value_si += constants.R * math.log(saturatedStruct.symmetryNumber) - - # For each radical site, get radical correction - # Only one radical site should be considered at a time; all others - # should be saturated with hydrogen atoms - for atom in added: - - # Remove the added hydrogen atoms and bond and restore the radical - for H, bond in added[atom]: - saturatedStruct.removeBond(bond) - saturatedStruct.removeAtom(H) - atom.incrementRadical() - - saturatedStruct.updateConnectivityValues() - - - - # Re-saturate - - - # Subtract the enthalpy of the added hydrogens - - - # Correct the entropy for the symmetry number - - else: # non-radical species - # Generate estimate of solute data - for atom in molecule.atoms: - # Iterate over heavy (non-hydrogen) atoms - if atom.isNonHydrogen(): - # Get initial solute data from main group database - try: - self.__addGroupSoluteoData(soluteData, self.groups['abraham'], molecule, {'*':atom}) - except KeyError: - logging.error("Couldn't find in main abraham database:") - logging.error(molecule) - logging.error(molecule.toAdjacencyList()) - raise - - # Correct for gauche and 1,5- interactions - - - # Do ring corrections separately because we only want to match - # each ring one time; this doesn't work yet - - - # Get thermo correction for this ring - - - # Correct entropy for symmetry number - - - return soluteData - - def __addGroupSoluteData(self, soluteData, database, molecule, atom): - """ - Determine the Platts group additivity solute data for the atom `atom` - in the structure `structure`, and add it to the existing solute data - `soluteData`. - """ - - node0 = database.descendTree(molecule, atom, None) - - if node0 is None: - raise KeyError('Node not found in database.') - - # It's possible (and allowed) that items in the tree may not be in the - # library, in which case we need to fall up the tree until we find an - # ancestor that has an entry in the library - node = node0 - while node.data is None and node is not None: - node = node.parent - if node is None: - raise InvalidDatabaseError('Unable to determine solute parameters for {0}: no library entries for {1} or any of its ancestors.'.format(molecule, node0) ) - - data = node.data; comment = node.label - while isinstance(data, basestring) and data is not None: - for entry in database.entries.values(): - if entry.label == data: - data = entry.data - comment = entry.label - break - comment = '{0}({1})'.format(database.label, comment) - - # This code prints the hierarchy of the found node; useful for debugging - #result = '' - #while node is not None: - # result = ' -> ' + node + result - # node = database.tree.parent[node] - #print result[4:] - - #if len(thermoData.Tdata.value_si) != len(data.Tdata.value_si) or any([T1 != T2 for T1, T2 in zip(thermoData.Tdata.value_si, data.Tdata.value_si)]): - #raise ThermoError('Cannot add these ThermoData objects due to their having different temperature points.') - - for i in range(7): - #thermoData.Cpdata.value_si[i] += data.Cpdata.value_si[i] - soluteData.S.value_si += data.S.value_si - soluteData.B.value_si += data.B.value_si - soluteData.E.value_si += data.E.value_si - soluteData.L.value_si += data.L.value_si - soluteData.A.value_si += data.A.value_si - - - if soluteData.comment: - solute.comment += ' + {0}'.format(comment) - else: - solute.comment = comment - - return soluteData +#!/usr/bin/python +# -*- coding: utf-8 -*- + +################################################################################ +# +# RMG - Reaction Mechanism Generator +# +# Copyright (c) 2002-2010 Prof. William H. Green (whgreen@mit.edu) and the +# RMG Team (rmg_dev@mit.edu) +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the 'Software'), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# +################################################################################ + +""" + +""" + +import os +import os.path +import math +import logging +import numpy +from copy import copy, deepcopy + +from base import Database, Entry, makeLogicNode + +import rmgpy.constants as constants +from rmgpy.thermo import * +from rmgpy.molecule import Molecule, Atom, Bond, Group + +################################################################################ + +def saveEntry(f, entry): + """ + Write a Pythonic string representation of the given `entry` in the thermo + database to the file object `f`. + """ + raise NotImplementedError() + +def generateOldLibraryEntry(data): + """ + Return a list of values used to save entries to the old-style RMG + thermo database based on the thermodynamics object `data`. + """ + raise NotImplementedError() + +def processOldLibraryEntry(data): + """ + Process a list of parameters `data` as read from an old-style RMG + thermo database, returning the corresponding thermodynamics object. + """ + raise NotImplementedError() + + +class SoluteData(): + """ + Stores Abraham parameters to characterize a solute + """ + def __init__(self, S=None, B=None, E=None, L=None, A=None): + #: :math:`\pi_2^H` + self.S = S + self.B = B + self.E = E + self.L = L + self.A = A + + + +################################################################################ + +class SoluteLibrary(Database): + """ + A class for working with a RMG solute library. + """ + def __init__(self, label='', name='', shortDesc='', longDesc=''): + Database.__init__(self, label=label, name=name, shortDesc=shortDesc, longDesc=longDesc) + + def loadEntry(self, + index, + label, + molecule, + solute, + reference=None, + referenceType='', + shortDesc='', + longDesc='', + history=None + ): + self.entries[label] = Entry( + index = index, + label = label, + item = Molecule().fromAdjacencyList(molecule), + data = solute, + reference = reference, + referenceType = referenceType, + shortDesc = shortDesc, + longDesc = longDesc.strip(), + history = history or [], + ) + + def saveEntry(self, f, entry): + """ + Write the given `entry` in the solute database to the file object `f`. + """ + return saveEntry(f, entry) + + def generateOldLibraryEntry(self, data): + """ + Return a list of values used to save entries to the old-style RMG + thermo database based on the thermodynamics object `data`. + """ + return generateOldLibraryEntry(data) + + def processOldLibraryEntry(self, data): + """ + Process a list of parameters `data` as read from an old-style RMG + thermo database, returning the corresponding thermodynamics object. + """ + return processOldLibraryEntry(data) + +################################################################################ + +class SoluteGroups(Database): + """ + A class for working with an RMG solute group additivity database. + """ + + def __init__(self, label='', name='', shortDesc='', longDesc=''): + Database.__init__(self, label=label, name=name, shortDesc=shortDesc, longDesc=longDesc) + + def loadEntry(self, + index, + label, + group, + solute, + reference=None, + referenceType='', + shortDesc='', + longDesc='', + history=None + ): + if group[0:3].upper() == 'OR{' or group[0:4].upper() == 'AND{' or group[0:7].upper() == 'NOT OR{' or group[0:8].upper() == 'NOT AND{': + item = makeLogicNode(group) + else: + item = Group().fromAdjacencyList(group) + self.entries[label] = Entry( + index = index, + label = label, + item = item, + data = solute, + reference = reference, + referenceType = referenceType, + shortDesc = shortDesc, + longDesc = longDesc.strip(), + history = history or [], + ) + + def saveEntry(self, f, entry): + """ + Write the given `entry` in the thermo database to the file object `f`. + """ + return saveEntry(f, entry) + + def generateOldLibraryEntry(self, data): + """ + Return a list of values used to save entries to the old-style RMG + thermo database based on the thermodynamics object `data`. + """ + + return generateOldLibraryEntry(data) + + def processOldLibraryEntry(self, data): + """ + Process a list of parameters `data` as read from an old-style RMG + thermo database, returning the corresponding thermodynamics object. + """ + return processOldLibraryEntry(data) + +################################################################################ + +class SoluteDatabase(object): + """ + A class for working with the RMG solute database. + """ + + def __init__(self): + #self.depository = {} + self.libraries = {} + self.groups = {} + self.libraryOrder = [] + self.local_context = {} + self.global_context = {} + + def __reduce__(self): + """ + A helper function used when pickling a SoluteDatabase object. + """ + d = { + #'depository': self.depository, + 'libraries': self.libraries, + 'groups': self.groups, + 'libraryOrder': self.libraryOrder, + } + return (SoluteDatabase, (), d) + + def __setstate__(self, d): + """ + A helper function used when unpickling a SoluteDatabase object. + """ + #self.depository = d['depository'] + self.libraries = d['libraries'] + self.groups = d['groups'] + self.libraryOrder = d['libraryOrder'] + + def load(self, path, libraries=None, depository=True): + """ + Load the solute database from the given `path` on disk, where `path` + points to the top-level folder of the solute database. + """ + # if depository: + # self.loadDepository(os.path.join(path, 'depository')) + # else: + # self.depository = {} + self.loadLibraries(os.path.join(path, 'libraries'), libraries) + self.loadGroups(os.path.join(path, 'groups')) + + def loadDepository(self, path): + """ + Load the thermo database from the given `path` on disk, where `path` + points to the top-level folder of the thermo database. + """ + raise NotImplementedError() + + def loadLibraries(self, path, libraries=None): + """ + Load the solute database from the given `path` on disk, where `path` + points to the top-level folder of the aolute database. + """ + self.libraries = {}; self.libraryOrder = [] + for (root, dirs, files) in os.walk(os.path.join(path)): + for f in files: + name, ext = os.path.splitext(f) + if ext.lower() == '.py' and (libraries is None or name in libraries): + logging.info('Loading solute library from {0} in {1}...'.format(f, root)) + library = SoluteLibrary() + library.load(os.path.join(root, f), self.local_context, self.global_context) + library.label = os.path.splitext(f)[0] + self.libraries[library.label] = library + self.libraryOrder.append(library.label) + if libraries is not None: + self.libraryOrder = libraries + + def loadGroups(self, path): + """ + Load the solute database from the given `path` on disk, where `path` + points to the top-level folder of the solute database. + """ + logging.info('Loading Platts additivity group database from {0}...'.format(path)) + self.groups = {} + self.groups['abraham'] = SoluteGroups(label='abraham').load(os.path.join(path, 'abraham.py' ), self.local_context, self.global_context) + # self.groups['gauche'] = ThermoGroups(label='gauche').load(os.path.join(path, 'gauche.py' ), self.local_context, self.global_context) + # self.groups['int15'] = ThermoGroups(label='int15').load(os.path.join(path, 'int15.py' ), self.local_context, self.global_context) + # self.groups['ring'] = ThermoGroups(label='ring').load(os.path.join(path, 'ring.py' ), self.local_context, self.global_context) + # self.groups['radical'] = ThermoGroups(label='radical').load(os.path.join(path, 'radical.py'), self.local_context, self.global_context) + # self.groups['other'] = ThermoGroups(label='other').load(os.path.join(path, 'other.py' ), self.local_context, self.global_context) + + def save(self, path): + """ + Save the solvation database to the given `path` on disk, where `path` + points to the top-level folder of the solvation database. + """ + path = os.path.abspath(path) + if not os.path.exists(path): os.mkdir(path) + #self.saveDepository(os.path.join(path, 'depository')) + self.saveLibraries(os.path.join(path, 'libraries')) + self.saveGroups(os.path.join(path, 'groups')) + + def saveDepository(self, path): + """ + Save the thermo depository to the given `path` on disk, where `path` + points to the top-level folder of the thermo depository. + """ + raise NotImplementedError() + + def saveLibraries(self, path): + """ + Save the solute libraries to the given `path` on disk, where `path` + points to the top-level folder of the solute libraries. + """ + if not os.path.exists(path): os.mkdir(path) + for library in self.libraries.values(): + library.save(os.path.join(path, '{0}.py'.format(library.label))) + + def saveGroups(self, path): + """ + Save the solute groups to the given `path` on disk, where `path` + points to the top-level folder of the solute groups. + """ + if not os.path.exists(path): os.mkdir(path) + self.groups['abraham'].save(os.path.join(path, 'abraham.py')) + # self.groups['gauche'].save(os.path.join(path, 'gauche.py')) + # self.groups['int15'].save(os.path.join(path, 'int15.py')) + # self.groups['ring'].save(os.path.join(path, 'ring.py')) + # self.groups['radical'].save(os.path.join(path, 'radical.py')) + # self.groups['other'].save(os.path.join(path, 'other.py')) + + def loadOld(self, path): + """ + Load the old RMG solute database from the given `path` on disk, where + `path` points to the top-level folder of the old RMG database. + """ + # The old database does not have a depository, so create an empty one + # self.depository = {} + # self.depository['stable'] = ThermoDepository(label='stable', name='Stable Molecules') + # self.depository['radical'] = ThermoDepository(label='radical', name='Radical Molecules') + + for (root, dirs, files) in os.walk(os.path.join(path, 'thermo_libraries')): + if os.path.exists(os.path.join(root, 'Dictionary.txt')) and os.path.exists(os.path.join(root, 'Library.txt')): + library = SoluteLibrary(label=os.path.basename(root), name=os.path.basename(root)) + library.loadOld( + dictstr = os.path.join(root, 'Dictionary.txt'), + treestr = '', + libstr = os.path.join(root, 'Library.txt'), + numParameters = 5, + numLabels = 1, + pattern = False, + ) + library.label = os.path.basename(root) + self.libraries[library.label] = library + + self.groups = {} + self.groups['abraham'] = SoluteGroups(label='abraham', name='Platts Group Additivity Values for Abraham Solute Descriptors').loadOld( + dictstr = os.path.join(path, 'thermo_groups', 'Abraham_Dictionary.txt'), + treestr = os.path.join(path, 'thermo_groups', 'Abraham_Tree.txt'), + libstr = os.path.join(path, 'thermo_groups', 'Abraham_Library.txt'), + numParameters = 5, + numLabels = 1, + pattern = True, + ) + # self.groups['gauche'] = ThermoGroups(label='gauche', name='Gauche Interaction Corrections').loadOld( + # dictstr = os.path.join(path, 'thermo_groups', 'Gauche_Dictionary.txt'), + # treestr = os.path.join(path, 'thermo_groups', 'Gauche_Tree.txt'), + # libstr = os.path.join(path, 'thermo_groups', 'Gauche_Library.txt'), + # numParameters = 12, + # numLabels = 1, + # pattern = True, + # ) + # self.groups['int15'] = ThermoGroups(label='int15', name='1,5-Interaction Corrections').loadOld( + # dictstr = os.path.join(path, 'thermo_groups', '15_Dictionary.txt'), + # treestr = os.path.join(path, 'thermo_groups', '15_Tree.txt'), + # libstr = os.path.join(path, 'thermo_groups', '15_Library.txt'), + # numParameters = 12, + # numLabels = 1, + # pattern = True, + # ) + # self.groups['radical'] = ThermoGroups(label='radical', name='Radical Corrections').loadOld( + # dictstr = os.path.join(path, 'thermo_groups', 'Radical_Dictionary.txt'), + # treestr = os.path.join(path, 'thermo_groups', 'Radical_Tree.txt'), + # libstr = os.path.join(path, 'thermo_groups', 'Radical_Library.txt'), + # numParameters = 12, + # numLabels = 1, + # pattern = True, + # ) + # self.groups['ring'] = ThermoGroups(label='ring', name='Ring Corrections').loadOld( + # dictstr = os.path.join(path, 'thermo_groups', 'Ring_Dictionary.txt'), + # treestr = os.path.join(path, 'thermo_groups', 'Ring_Tree.txt'), + # libstr = os.path.join(path, 'thermo_groups', 'Ring_Library.txt'), + # numParameters = 12, + # numLabels = 1, + # pattern = True, + # ) + # self.groups['other'] = ThermoGroups(label='other', name='Other Corrections').loadOld( + # dictstr = os.path.join(path, 'thermo_groups', 'Other_Dictionary.txt'), + # treestr = os.path.join(path, 'thermo_groups', 'Other_Tree.txt'), + # libstr = os.path.join(path, 'thermo_groups', 'Other_Library.txt'), + # numParameters = 12, + # numLabels = 1, + # pattern = True, + # ) + + def saveOld(self, path): + """ + Save the old RMG Abraham database to the given `path` on disk, where + `path` points to the top-level folder of the old RMG database. + """ + # Depository not used in old database, so it is not saved + + librariesPath = os.path.join(path, 'thermo_libraries') + if not os.path.exists(librariesPath): os.mkdir(librariesPath) + for library in self.libraries.values(): + libraryPath = os.path.join(librariesPath, library.label) + if not os.path.exists(libraryPath): os.mkdir(libraryPath) + library.saveOld( + dictstr = os.path.join(libraryPath, 'Dictionary.txt'), + treestr = '', + libstr = os.path.join(libraryPath, 'Library.txt'), + ) + + groupsPath = os.path.join(path, 'thermo_groups') + if not os.path.exists(groupsPath): os.mkdir(groupsPath) + self.groups['abraham'].saveOld( + dictstr = os.path.join(groupsPath, 'Abraham_Dictionary.txt'), + treestr = os.path.join(groupsPath, 'Abraham_Tree.txt'), + libstr = os.path.join(groupsPath, 'Abraham_Library.txt'), + ) + # self.groups['gauche'].saveOld( + # dictstr = os.path.join(groupsPath, 'Gauche_Dictionary.txt'), + # treestr = os.path.join(groupsPath, 'Gauche_Tree.txt'), + # libstr = os.path.join(groupsPath, 'Gauche_Library.txt'), + # ) + # self.groups['int15'].saveOld( + # dictstr = os.path.join(groupsPath, '15_Dictionary.txt'), + # treestr = os.path.join(groupsPath, '15_Tree.txt'), + # libstr = os.path.join(groupsPath, '15_Library.txt'), + # ) + # self.groups['radical'].saveOld( + # dictstr = os.path.join(groupsPath, 'Radical_Dictionary.txt'), + # treestr = os.path.join(groupsPath, 'Radical_Tree.txt'), + # libstr = os.path.join(groupsPath, 'Radical_Library.txt'), + # ) + # self.groups['ring'].saveOld( + # dictstr = os.path.join(groupsPath, 'Ring_Dictionary.txt'), + # treestr = os.path.join(groupsPath, 'Ring_Tree.txt'), + # libstr = os.path.join(groupsPath, 'Ring_Library.txt'), + # ) + # self.groups['other'].saveOld( + # dictstr = os.path.join(groupsPath, 'Other_Dictionary.txt'), + # treestr = os.path.join(groupsPath, 'Other_Tree.txt'), + # libstr = os.path.join(groupsPath, 'Other_Library.txt'), + # ) + + def getSoluteData(self, species): + """ + Return the solute descriptors for a given :class:`Species` + object `species`. This function first searches the loaded libraries + in order, returning the first match found, before falling back to + estimation via Platts group additivity. + """ + soluteData = None + # Check the libraries in order first; return the first successful match + for label in self.libraryOrder: + soluteData = self.getSoluteDataFromLibrary(species, self.libraries[label]) + if soluteData is not None: + soluteData[0].comment = label + break + else: + # Solute not found in any loaded libraries, so estimate + soluteData = self.getSoluteDataFromGroups(species) + # Add Cp0 and CpInf values + # Cp0 = species.calculateCp0() + # CpInf = species.calculateCpInf() + data, library, entry = soluteData + # if isinstance(data,SoluteData): + # data.Cp0 = (Cp0,"J/(mol*K)") + # data.CpInf = (CpInf,"J/(mol*K)") + # Return the resulting solute parameters + return data + + def getAllSoluteData(self, species): + """ + Return all possible sets of Abraham solute descriptors for a given + :class:`Species` object `species`. The hits from the library come + first, then the group additivity estimate. This method is useful + for a generic search job. + """ + thermoData = [] + # Data from depository comes first + # thermoData.extend(self.getThermoDataFromDepository(species)) + # Data from libraries comes second + for label in self.libraryOrder: + data = self.getSoluteDataFromLibrary(species, self.libraries[label]) + if data: + data[0].comment = label + soluteData.append(data) + # Last entry is always the estimate from group additivity + soluteData.append(self.getSoluteDataFromGroups(species)) + + # Add Cp0 and CpInf values + # Cp0 = species.calculateCp0() + # CpInf = species.calculateCpInf() + # for data, library, entry in thermoData: + # if isinstance(data,ThermoData): + # data.Cp0 = (Cp0,"J/(mol*K)") + # data.CpInf = (CpInf,"J/(mol*K)") + + # Return all of the resulting thermo parameters + return thermoData + + def getThermoDataFromDepository(self, species): + """ + Return all possible sets of thermodynamic parameters for a given + :class:`Species` object `species` from the depository. If no + depository is loaded, a :class:`DatabaseError` is raised. + """ + raise NotImplementedError() + + def getSoluteDataFromLibrary(self, species, library): + """ + Return the set of Abraham solute descriptors corresponding to a given + :class:`Species` object `species` from the specified solute + `library`. If `library` is a string, the list of libraries is searched + for a library with that name. If no match is found in that library, + ``None`` is returned. If no corresponding library is found, a + :class:`DatabaseError` is raised. + """ + for label, entry in library.entries.iteritems(): + for molecule in species.molecule: + if molecule.isIsomorphic(entry.item) and entry.data is not None: + return (deepcopy(entry.data), library, entry) + return None + + def getSoluteDataFromGroups(self, species): + """ + Return the set of Abraham solute parameters corresponding to a given + :class:`Species` object `species` by estimation using the Platts group + additivity method. If no group additivity values are loaded, a + :class:`DatabaseError` is raised. + """ + solute = [] + for molecule in species.molecule: + molecule.clearLabeledAtoms() + molecule.updateAtomTypes() + tdata = self.estimateSoluteViaGroupAdditivity(molecule) + solute.append(sdata) + + #H298 = numpy.array([t.getEnthalpy(298.) for t in thermo]) + #indices = H298.argsort() + + species.molecule = [species.molecule[ind] for ind in indices] + + return (solute[indices[0]], None, None) + + def estimateSoluteViaGroupAdditivity(self, molecule): + """ + Return the set of Abraham solute parameters corresponding to a given + :class:`Molecule` object `molecule` by estimation using the Platts' group + additivity method. If no group additivity values are loaded, a + :class:`DatabaseError` is raised. + """ + # For thermo estimation we need the atoms to already be sorted because we + # iterate over them; if the order changes during the iteration then we + # will probably not visit the right atoms, and so will get the thermo wrong + molecule.sortVertices() + + # Create the SoluteData object + soluteData = SoluteData( + S = 0.0, + B = 0.0, + E = 0.0, + L = 0.0, + A = 0.0 + ) + + if sum([atom.radicalElectrons for atom in molecule.atoms]) > 0: # radical species + + # Make a copy of the structure so we don't change the original + saturatedStruct = molecule.copy(deep=True) + + # Saturate structure by replacing all radicals with bonds to + # hydrogen atoms + added = {} + for atom in saturatedStruct.atoms: + for i in range(atom.radicalElectrons): + H = Atom('H') + bond = Bond(atom, H, 'S') + saturatedStruct.addAtom(H) + saturatedStruct.addBond(bond) + if atom not in added: + added[atom] = [] + added[atom].append([H, bond]) + atom.decrementRadical() + + # Update the atom types of the saturated structure (not sure why + # this is necessary, because saturating with H shouldn't be + # changing atom types, but it doesn't hurt anything and is not + # very expensive, so will do it anyway) + saturatedStruct.updateConnectivityValues() + saturatedStruct.sortVertices() + saturatedStruct.updateAtomTypes() + + # Get solute descriptor estimates for saturated form of structure + soluteData = self.estimateSoluteViaGroupAdditivity(saturatedStruct) + assert soluteData is not None, "Solute data of saturated {0} of molecule {1} is None!".format(saturatedStruct, molecule) + # Undo symmetry number correction for saturated structure + # thermoData.S298.value_si += constants.R * math.log(saturatedStruct.symmetryNumber) + + # For each radical site, get radical correction + # Only one radical site should be considered at a time; all others + # should be saturated with hydrogen atoms + for atom in added: + + # Remove the added hydrogen atoms and bond and restore the radical + for H, bond in added[atom]: + saturatedStruct.removeBond(bond) + saturatedStruct.removeAtom(H) + atom.incrementRadical() + + saturatedStruct.updateConnectivityValues() + + + + # Re-saturate + + + # Subtract the enthalpy of the added hydrogens + + + # Correct the entropy for the symmetry number + + else: # non-radical species + # Generate estimate of solute data + for atom in molecule.atoms: + # Iterate over heavy (non-hydrogen) atoms + if atom.isNonHydrogen(): + # Get initial solute data from main group database + try: + self.__addGroupSoluteData(soluteData, self.groups['abraham'], molecule, {'*':atom}) + except KeyError: + logging.error("Couldn't find in main abraham database:") + logging.error(molecule) + logging.error(molecule.toAdjacencyList()) + raise + + # Correct for gauche and 1,5- interactions + + + # Do ring corrections separately because we only want to match + # each ring one time; this doesn't work yet + + + # Get thermo correction for this ring + + + # Correct entropy for symmetry number + + + return soluteData + + def __addGroupSoluteData(self, soluteData, database, molecule, atom): + """ + Determine the Platts group additivity solute data for the atom `atom` + in the structure `structure`, and add it to the existing solute data + `soluteData`. + """ + + node0 = database.descendTree(molecule, atom, None) + + if node0 is None: + raise KeyError('Node not found in database.') + + # It's possible (and allowed) that items in the tree may not be in the + # library, in which case we need to fall up the tree until we find an + # ancestor that has an entry in the library + node = node0 + while node.data is None and node is not None: + node = node.parent + if node is None: + raise InvalidDatabaseError('Unable to determine solute parameters for {0}: no library entries for {1} or any of its ancestors.'.format(molecule, node0) ) + + data = node.data; comment = node.label + while isinstance(data, basestring) and data is not None: + for entry in database.entries.values(): + if entry.label == data: + data = entry.data + comment = entry.label + break + comment = '{0}({1})'.format(database.label, comment) + + # This code prints the hierarchy of the found node; useful for debugging + #result = '' + #while node is not None: + # result = ' -> ' + node + result + # node = database.tree.parent[node] + #print result[4:] + + #if len(thermoData.Tdata.value_si) != len(data.Tdata.value_si) or any([T1 != T2 for T1, T2 in zip(thermoData.Tdata.value_si, data.Tdata.value_si)]): + #raise ThermoError('Cannot add these ThermoData objects due to their having different temperature points.') + + #for i in range(7): + #thermoData.Cpdata.value_si[i] += data.Cpdata.value_si[i] + soluteData.S += data.S + soluteData.B += data.B + soluteData.E += data.E + soluteData.L += data.L + soluteData.A += data.A + + + if soluteData.comment: + solute.comment += ' + {0}'.format(comment) + else: + solute.comment = comment + + return soluteData From fb73f4b1124b707e40eac70cd70312b9fe8261b9 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 28 Jan 2013 16:39:59 -0500 Subject: [PATCH 03/55] Created file for testing solvation.py and abraham.py for getting solute descriptors via Platts group additivity. --- unittest/data/solvationtest.py | 45 ++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 unittest/data/solvationtest.py diff --git a/unittest/data/solvationtest.py b/unittest/data/solvationtest.py new file mode 100644 index 00000000000..e7d3a05ecf5 --- /dev/null +++ b/unittest/data/solvationtest.py @@ -0,0 +1,45 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import os +from unittest import TestCase + +from rmgpy import settings +from species import Species +from rmgpy.data.solvation import * +from rmgpy.molecule.molecule import Molecule + +################################################### + +class TestSoluteDatabase(TestCase): + + def runTest(self): + pass + + def testSoluteGeneration(self): + + self.database = SoluteDatabase() + self.database.load(os.path.join(settings['database.directory'], 'thermo')) + + self.testCases = [ + + ['cyclopentane', 'C1CCCC1', 0.10, 0, 0.263, 2.477, 0], + ['methylcyclopentane', 'C1(CCCC1)C', 0.10, 0, 0.225, 2.816, 0], + ['cyclohexane', 'C1CCCCC1', 0.10, 0, 0.305, 2.964, 0] + + ] + + for name, smiles, S, B, E, L, A in self.testCases: + species = Species(molecule=[Molecule(SMILES=smiles)]) + soluteData = self.database.getSoluteData(Species(molecule=[species.molecule[0]])) + print self.assertEqual(soluteData.S, S) + print self.assertEqual(soluteData.B, B) + print self.assertEqual(soluteData.E, E) + print self.assertEqual(soluteData.L, L) + print self.assertEqual(soluteData.A, A) + +##################################################### + +if __name__ == '__main__': + myTest = TestSoluteDatabase() + myTest.testSoluteGeneration() \ No newline at end of file From a3d13a51b3ba08ea3d72ef1758bdf33bdc694398 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 28 Jan 2013 16:44:22 -0500 Subject: [PATCH 04/55] Fixes to solvation module --- rmgpy/data/solvation.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index 696c43d3084..a3f35c280c5 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -42,7 +42,7 @@ from base import Database, Entry, makeLogicNode import rmgpy.constants as constants -from rmgpy.thermo import * +#from rmgpy.data.thermo import * from rmgpy.molecule import Molecule, Atom, Bond, Group ################################################################################ @@ -205,19 +205,20 @@ def __init__(self): self.libraries = {} self.groups = {} self.libraryOrder = [] - self.local_context = {} + self.local_context = { + 'SoluteData': SoluteData, + } self.global_context = {} def __reduce__(self): """ A helper function used when pickling a SoluteDatabase object. """ - d = { - #'depository': self.depository, + d = { 'libraries': self.libraries, 'groups': self.groups, 'libraryOrder': self.libraryOrder, - } + } return (SoluteDatabase, (), d) def __setstate__(self, d): @@ -237,8 +238,9 @@ def load(self, path, libraries=None, depository=True): # if depository: # self.loadDepository(os.path.join(path, 'depository')) # else: - # self.depository = {} - self.loadLibraries(os.path.join(path, 'libraries'), libraries) + # self.depository = {} + #no solute library right now... + #self.loadLibraries(os.path.join(path, 'libraries'), libraries) self.loadGroups(os.path.join(path, 'groups')) def loadDepository(self, path): @@ -466,7 +468,7 @@ def getSoluteData(self, species): # Add Cp0 and CpInf values # Cp0 = species.calculateCp0() # CpInf = species.calculateCpInf() - data, library, entry = soluteData + data, library, entry = soluteData # if isinstance(data,SoluteData): # data.Cp0 = (Cp0,"J/(mol*K)") # data.CpInf = (CpInf,"J/(mol*K)") @@ -694,12 +696,12 @@ def __addGroupSoluteData(self, soluteData, database, molecule, atom): #raise ThermoError('Cannot add these ThermoData objects due to their having different temperature points.') #for i in range(7): - #thermoData.Cpdata.value_si[i] += data.Cpdata.value_si[i] - soluteData.S += data.S - soluteData.B += data.B - soluteData.E += data.E - soluteData.L += data.L - soluteData.A += data.A + #thermoData.Cpdata.value_si[i] += data.Cpdata.value_si[i] + soluteData.S += data.S + soluteData.B += data.B + soluteData.E += data.E + soluteData.L += data.L + soluteData.A += data.A if soluteData.comment: From da7e7460688dc7f7ded69b6087bba1eb9e141930 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 28 Jan 2013 17:14:41 -0500 Subject: [PATCH 05/55] Moved the solvationTest into rmgpy/data to live alongside the file it's testing. --- unittest/data/solvationtest.py => rmgpy/data/solvationTest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename unittest/data/solvationtest.py => rmgpy/data/solvationTest.py (97%) diff --git a/unittest/data/solvationtest.py b/rmgpy/data/solvationTest.py similarity index 97% rename from unittest/data/solvationtest.py rename to rmgpy/data/solvationTest.py index e7d3a05ecf5..d796a29fe1d 100644 --- a/unittest/data/solvationtest.py +++ b/rmgpy/data/solvationTest.py @@ -5,7 +5,7 @@ from unittest import TestCase from rmgpy import settings -from species import Species +from rmgpy.species import Species from rmgpy.data.solvation import * from rmgpy.molecule.molecule import Molecule From 64328fbf8c0fd497c27c982c8927e9f84ca37083 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 28 Jan 2013 17:16:32 -0500 Subject: [PATCH 06/55] getSoluteDataFromGroups(species) averages over resonance isomers. Not sure this is the best thing to do, but it's one option. The LSER is a linear equation, so linear averaging seems reasonable. What did @ajalan do in Java? --- rmgpy/data/solvation.py | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index a3f35c280c5..2dd92dd37ed 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -81,7 +81,6 @@ def __init__(self, S=None, B=None, E=None, L=None, A=None): self.L = L self.A = A - ################################################################################ @@ -533,21 +532,33 @@ def getSoluteDataFromGroups(self, species): Return the set of Abraham solute parameters corresponding to a given :class:`Species` object `species` by estimation using the Platts group additivity method. If no group additivity values are loaded, a - :class:`DatabaseError` is raised. + :class:`DatabaseError` is raised. + + It averages (linearly) over the desciptors for each Molecule (resonance isomer) + in the Species. """ - solute = [] + + soluteData = SoluteData(0.0,0.0,0.0,0.0,0.0) + count = 0 for molecule in species.molecule: molecule.clearLabeledAtoms() molecule.updateAtomTypes() - tdata = self.estimateSoluteViaGroupAdditivity(molecule) - solute.append(sdata) - - #H298 = numpy.array([t.getEnthalpy(298.) for t in thermo]) - #indices = H298.argsort() - - species.molecule = [species.molecule[ind] for ind in indices] - - return (solute[indices[0]], None, None) + sdata = self.estimateSoluteViaGroupAdditivity(molecule) + + soluteData.S += sdata.S + soluteData.B += sdata.B + soluteData.E += sdata.E + soluteData.L += sdata.L + soluteData.A += sdata.A + count += 1 + + soluteData.S /= count + soluteData.B /= count + soluteData.E /= count + soluteData.L /= count + soluteData.A /= count + + return soluteData, None, None def estimateSoluteViaGroupAdditivity(self, molecule): """ @@ -702,11 +713,5 @@ def __addGroupSoluteData(self, soluteData, database, molecule, atom): soluteData.E += data.E soluteData.L += data.L soluteData.A += data.A - - - if soluteData.comment: - solute.comment += ' + {0}'.format(comment) - else: - solute.comment = comment return soluteData From bcbe4efd7430c4242cb6fad1521ddb5cd82dc22b Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 28 Jan 2013 18:21:00 -0500 Subject: [PATCH 07/55] Corrected some errors to solvation module and its test class. --- rmgpy/data/solvation.py | 30 +++++++++++++++++++----------- rmgpy/data/solvationTest.py | 13 +++++++------ 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index 2dd92dd37ed..d93db1ffc85 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -73,13 +73,16 @@ class SoluteData(): """ Stores Abraham parameters to characterize a solute """ - def __init__(self, S=None, B=None, E=None, L=None, A=None): + def __init__(self, S=None, B=None, E=None, L=None, A=None, comment=""): #: :math:`\pi_2^H` self.S = S self.B = B self.E = E self.L = L - self.A = A + self.A = A + self.comment = comment + def __repr__(self): + return "SoluteData(S={0},B={1},E={2},L={3},A={4},comment={5!r})".format(self.S, self.B, self.E, self.L, self.A, self.comment) ################################################################################ @@ -539,7 +542,8 @@ def getSoluteDataFromGroups(self, species): """ soluteData = SoluteData(0.0,0.0,0.0,0.0,0.0) - count = 0 + count = 0 + comments = [] for molecule in species.molecule: molecule.clearLabeledAtoms() molecule.updateAtomTypes() @@ -551,12 +555,14 @@ def getSoluteDataFromGroups(self, species): soluteData.L += sdata.L soluteData.A += sdata.A count += 1 + comments.append(sdata.comment) soluteData.S /= count soluteData.B /= count soluteData.E /= count soluteData.L /= count - soluteData.A /= count + soluteData.A /= count + soluteData.comment = "Average of {0}".format(" and ".join(comments)) return soluteData, None, None @@ -574,11 +580,11 @@ def estimateSoluteViaGroupAdditivity(self, molecule): # Create the SoluteData object soluteData = SoluteData( - S = 0.0, - B = 0.0, - E = 0.0, - L = 0.0, - A = 0.0 + S = 0.277, + B = 0.071, + E = 0.248, + L = 0.13, + A = 0.003 ) if sum([atom.radicalElectrons for atom in molecule.atoms]) > 0: # radical species @@ -687,7 +693,8 @@ def __addGroupSoluteData(self, soluteData, database, molecule, atom): if node is None: raise InvalidDatabaseError('Unable to determine solute parameters for {0}: no library entries for {1} or any of its ancestors.'.format(molecule, node0) ) - data = node.data; comment = node.label + data = node.data + comment = node.label while isinstance(data, basestring) and data is not None: for entry in database.entries.values(): if entry.label == data: @@ -712,6 +719,7 @@ def __addGroupSoluteData(self, soluteData, database, molecule, atom): soluteData.B += data.B soluteData.E += data.E soluteData.L += data.L - soluteData.A += data.A + soluteData.A += data.A + soluteData.comment += comment + "+" return soluteData diff --git a/rmgpy/data/solvationTest.py b/rmgpy/data/solvationTest.py index d796a29fe1d..936fb21d221 100644 --- a/rmgpy/data/solvationTest.py +++ b/rmgpy/data/solvationTest.py @@ -22,16 +22,17 @@ def testSoluteGeneration(self): self.database.load(os.path.join(settings['database.directory'], 'thermo')) self.testCases = [ - - ['cyclopentane', 'C1CCCC1', 0.10, 0, 0.263, 2.477, 0], - ['methylcyclopentane', 'C1(CCCC1)C', 0.10, 0, 0.225, 2.816, 0], - ['cyclohexane', 'C1CCCCC1', 0.10, 0, 0.305, 2.964, 0] - + ['octane', 'CCCCCCCC', 0.127, 0.085, 0.04, 3.766, 0.0030 , 1.2358], # from RMG-Java + #['cyclopentane', 'C1CCCC1', 0.10, 0, 0.263, 2.477, 0], + #['methylcyclopentane', 'C1(CCCC1)C', 0.10, 0, 0.225, 2.816, 0], + #['cyclohexane', 'C1CCCCC1', 0.10, 0, 0.305, 2.964, 0] + # ] - for name, smiles, S, B, E, L, A in self.testCases: + for name, smiles, S, B, E, L, A, V in self.testCases: species = Species(molecule=[Molecule(SMILES=smiles)]) soluteData = self.database.getSoluteData(Species(molecule=[species.molecule[0]])) + print name, soluteData print self.assertEqual(soluteData.S, S) print self.assertEqual(soluteData.B, B) print self.assertEqual(soluteData.E, E) From f9860f4db7d0606a35ff5b362c4c47fee0aa4890 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Tue, 5 Feb 2013 18:33:33 -0500 Subject: [PATCH 08/55] Changed test to "assertAlmostEqual()" and added some test cases. --- rmgpy/data/solvationTest.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/rmgpy/data/solvationTest.py b/rmgpy/data/solvationTest.py index 936fb21d221..0464b62548f 100644 --- a/rmgpy/data/solvationTest.py +++ b/rmgpy/data/solvationTest.py @@ -22,22 +22,34 @@ def testSoluteGeneration(self): self.database.load(os.path.join(settings['database.directory'], 'thermo')) self.testCases = [ - ['octane', 'CCCCCCCC', 0.127, 0.085, 0.04, 3.766, 0.0030 , 1.2358], # from RMG-Java - #['cyclopentane', 'C1CCCC1', 0.10, 0, 0.263, 2.477, 0], - #['methylcyclopentane', 'C1(CCCC1)C', 0.10, 0, 0.225, 2.816, 0], + + # from RMG-Java test run + ['octane', 'CCCCCCCC', 0.127, 0.085, 0.04, 3.766, 0.0030 , 1.2358], + ['water', 'O', 0.524, 0.378, 0.309, 0.802, 0.348, 0.1673], + + + # from RMG-Java (Jalan et al supplementary data): + #['1,2-ethanediol', 'C(CO)O', 0.823, 0.685, 0.327, 2.572, 0.693, None] + ['1-decanol', 'C(CCCCCCCCC)O', 0.449, 0.385, 0.205, 5.614, 0.348, None], + #['acetylaldehyde', 'CC=O', 0.622, 0.423, 0.171, 1.415, 0.0030, 0.4061], + ['acetic acid', 'C(C)(=O)O', 0.508, 0.411, 0.152, 1.873, 0.591, None], + #['anthracene', 'C2=CC=CC3=CC1=CC=CC=C1C=C23', 1.181, 0.181, 1.648, 7.316, 0.003, None] + #['dimethyl ether'] + + # from Abraham: + #['cyclopentane', 'C1CCCC1', 0.10, 0, 0.263, 2.477, 0, None], #['methylcyclopentane', 'C1(CCCC1)C', 0.10, 0, 0.225, 2.816, 0], #['cyclohexane', 'C1CCCCC1', 0.10, 0, 0.305, 2.964, 0] - # ] for name, smiles, S, B, E, L, A, V in self.testCases: species = Species(molecule=[Molecule(SMILES=smiles)]) soluteData = self.database.getSoluteData(Species(molecule=[species.molecule[0]])) print name, soluteData - print self.assertEqual(soluteData.S, S) - print self.assertEqual(soluteData.B, B) - print self.assertEqual(soluteData.E, E) - print self.assertEqual(soluteData.L, L) - print self.assertEqual(soluteData.A, A) + print self.assertAlmostEqual(soluteData.S, S) + print self.assertAlmostEqual(soluteData.B, B) + print self.assertAlmostEqual(soluteData.E, E) + print self.assertAlmostEqual(soluteData.L, L) + print self.assertAlmostEqual(soluteData.A, A) ##################################################### From bae2615d691bfd2a844957c530d9e0fe7986f1d8 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Tue, 19 Feb 2013 14:23:41 -0500 Subject: [PATCH 09/55] Modified solvation module to read in Platts non atom centered groups. --- rmgpy/data/solvation.py | 63 ++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index d93db1ffc85..5dd0191a5e9 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -42,7 +42,6 @@ from base import Database, Entry, makeLogicNode import rmgpy.constants as constants -#from rmgpy.data.thermo import * from rmgpy.molecule import Molecule, Atom, Bond, Group ################################################################################ @@ -279,7 +278,7 @@ def loadGroups(self, path): logging.info('Loading Platts additivity group database from {0}...'.format(path)) self.groups = {} self.groups['abraham'] = SoluteGroups(label='abraham').load(os.path.join(path, 'abraham.py' ), self.local_context, self.global_context) - # self.groups['gauche'] = ThermoGroups(label='gauche').load(os.path.join(path, 'gauche.py' ), self.local_context, self.global_context) + self.groups['nonacentered'] = SoluteGroups(label='nonacentered').load(os.path.join(path, 'nonacentered.py' ), self.local_context, self.global_context) # self.groups['int15'] = ThermoGroups(label='int15').load(os.path.join(path, 'int15.py' ), self.local_context, self.global_context) # self.groups['ring'] = ThermoGroups(label='ring').load(os.path.join(path, 'ring.py' ), self.local_context, self.global_context) # self.groups['radical'] = ThermoGroups(label='radical').load(os.path.join(path, 'radical.py'), self.local_context, self.global_context) @@ -319,7 +318,7 @@ def saveGroups(self, path): """ if not os.path.exists(path): os.mkdir(path) self.groups['abraham'].save(os.path.join(path, 'abraham.py')) - # self.groups['gauche'].save(os.path.join(path, 'gauche.py')) + self.groups['nonacentered'].save(os.path.join(path, 'nonacentered.py')) # self.groups['int15'].save(os.path.join(path, 'int15.py')) # self.groups['ring'].save(os.path.join(path, 'ring.py')) # self.groups['radical'].save(os.path.join(path, 'radical.py')) @@ -556,6 +555,7 @@ def getSoluteDataFromGroups(self, species): soluteData.A += sdata.A count += 1 comments.append(sdata.comment) + # print count #debugging purposes soluteData.S /= count soluteData.B /= count @@ -656,18 +656,10 @@ def estimateSoluteViaGroupAdditivity(self, molecule): logging.error(molecule) logging.error(molecule.toAdjacencyList()) raise - - # Correct for gauche and 1,5- interactions - - - # Do ring corrections separately because we only want to match - # each ring one time; this doesn't work yet - - - # Get thermo correction for this ring - - - # Correct entropy for symmetry number + # Get solute data for non-atom centered groups + try: + self.__addGroupSoluteData(soluteData, self.groups['nonacentered'], molecule, {'*':atom}) + except KeyError: pass return soluteData @@ -687,21 +679,22 @@ def __addGroupSoluteData(self, soluteData, database, molecule, atom): # It's possible (and allowed) that items in the tree may not be in the # library, in which case we need to fall up the tree until we find an # ancestor that has an entry in the library - node = node0 - while node.data is None and node is not None: + node = node0 + data = node.data + while data is None and node is not None: node = node.parent - if node is None: - raise InvalidDatabaseError('Unable to determine solute parameters for {0}: no library entries for {1} or any of its ancestors.'.format(molecule, node0) ) - - data = node.data - comment = node.label - while isinstance(data, basestring) and data is not None: - for entry in database.entries.values(): - if entry.label == data: - data = entry.data - comment = entry.label - break - comment = '{0}({1})'.format(database.label, comment) + if node is None: + raise KeyError('Node has no parent with data in database.') + + if node is not None: + comment = node.label + while isinstance(data, basestring) and data is not None: + for entry in database.entries.values(): + if entry.label == data: + data = entry.data + comment = entry.label + break + comment = '{0}({1})'.format(database.label, comment) # This code prints the hierarchy of the found node; useful for debugging #result = '' @@ -715,11 +708,11 @@ def __addGroupSoluteData(self, soluteData, database, molecule, atom): #for i in range(7): #thermoData.Cpdata.value_si[i] += data.Cpdata.value_si[i] - soluteData.S += data.S - soluteData.B += data.B - soluteData.E += data.E - soluteData.L += data.L - soluteData.A += data.A - soluteData.comment += comment + "+" + soluteData.S += data.S + soluteData.B += data.B + soluteData.E += data.E + soluteData.L += data.L + soluteData.A += data.A + soluteData.comment += comment + "+" return soluteData From b81ae799a3dcdcf5fcdbaa01a939552c223bdc59 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 4 Mar 2013 14:59:54 -0500 Subject: [PATCH 10/55] Added methods for working with solvent descriptors. --- rmgpy/data/solvation.py | 55 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index 5dd0191a5e9..c03f7da1872 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -68,6 +68,25 @@ def processOldLibraryEntry(data): raise NotImplementedError() +class SolventData(): + """ + Stores Abraham/Mintz parameters for characterizing a solvent. + """ + def __init__(self, s_h=None, b_h=None, e_h=None, l_h=None, a_h=None, + c_h=None, s_g=None, b_g=None, e_g=None, l_g=None, a_g=None, c_g=None): + self.s_h = s_h + self.b_h = b_h + self.e_h = e_h + self.l_h = l_h + self.a_h = a_h + self.c_h = c_h + self.s_g = s_g + self.b_g = b_g + self.e_g = e_g + self.l_g = l_g + self.a_g = a_g + self.c_g = c_g + class SoluteData(): """ Stores Abraham parameters to characterize a solute @@ -716,3 +735,39 @@ def __addGroupSoluteData(self, soluteData, database, molecule, atom): soluteData.comment += comment + "+" return soluteData + +# def addSolventData(self, solventData, database, solvent): +# node = +# +# if node is None: +# raise KeyError('Node not found in database.') +# else +# data = node.data +# solventData.s_h = data.s_h +# solventData.b_h = data.b_h +# solventData.e_h = data.e_h +# solventData.l_h = data.l_h +# solventData.a_h = data.a_h +# solventData.c_h = data.c_h +# solventData.s_g = data.s_g +# solventData.b_g = data.b_g +# solventData.e_g = data.e_g +# solventData.l_g = data.l_g +# solventData.a_g = data.a_g +# solventData.c_g = data.c_g +# +# return solventData + + def calcH(self, soluteData, solventData): + delH = (soluteData.S*solventData.s_h)+(soluteData.B*solventData.b_h)+(soluteData.E*solventData.e_h)+(soluteData.L*solventData.l_h)+(soluteData.A*solventData.a_h)+solventData.c_h + return delH + + def calcG(self, soluteData, solventData): + logk = (soluteData.S*solventData.s_g)+(soluteData.B*solventData.b_g)+(soluteData.E*solventData.e_g)+(soluteData.L*solventData.l_g)+(soluteData.A*solventData.a_g)+solventData.c_g + delG = -8.314*298*0.4343*k + return delG + + + def calcS(self, delG, delH): + delS = (delH-delG)/298 + return delS \ No newline at end of file From 8bcbae15d05e405e68338666cc7542643e54b9a6 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 4 Mar 2013 18:54:58 -0500 Subject: [PATCH 11/55] Changes for incorporating solvation. Modified input file, loading and building models for including salvation. Changed the thermo of a species based on its solute parameters and the solvent it's in. --- examples/rmg/minimal/input.py | 4 ++++ rmgpy/data/rmg.py | 16 +++++++++++++++- rmgpy/rmg/input.py | 9 ++++++++- rmgpy/rmg/main.py | 6 ++++++ rmgpy/rmg/model.py | 13 +++++++++++++ 5 files changed, 46 insertions(+), 2 deletions(-) diff --git a/examples/rmg/minimal/input.py b/examples/rmg/minimal/input.py index adce7e561a2..10839f4cbad 100644 --- a/examples/rmg/minimal/input.py +++ b/examples/rmg/minimal/input.py @@ -28,6 +28,10 @@ terminationTime=(1e6,'s'), ) +solvation( + solvent='water' +) + simulator( atol=1e-16, rtol=1e-8, diff --git a/rmgpy/data/rmg.py b/rmgpy/data/rmg.py index 48c986dd658..9fcb51203e8 100644 --- a/rmgpy/data/rmg.py +++ b/rmgpy/data/rmg.py @@ -40,6 +40,7 @@ from thermo import ThermoDatabase from kinetics import KineticsDatabase from statmech import StatmechDatabase +from solvation import SolvationDatabase # Module-level variable to store the (only) instance of RMGDatabase in use. database = None @@ -56,6 +57,7 @@ def __init__(self): self.forbiddenStructures = None self.kinetics = None self.statmech = None + self.solvation = None # Store the newly created database in the module. global database @@ -70,7 +72,8 @@ def load(self, kineticsFamilies=None, kineticsDepositories=None, statmechLibraries=None, - depository=True + depository=True, + solvation=True, ): """ Load the RMG database from the given `path` on disk, where `path` @@ -88,6 +91,9 @@ def load(self, kineticsDepositories ) self.loadStatmech(os.path.join(path, 'statmech'), statmechLibraries, depository) + + if solvation: + self.loadSolvation(os.path.join(path, 'solvation')) def loadThermo(self, path, thermoLibraries=None, depository=True): """ @@ -134,6 +140,14 @@ def loadKinetics(self, depositories=kineticsDepositories ) + def loadSolvation(self, path): + """ + Load the RMG solvation database from the given `path` on disk, where + `path` points to the top-level folder of the RMG solvation database. + """ + self.solvation = SolvationDatabase() + self.solvation.load(path) + def loadStatmech(self, path, statmechLibraries=None, depository=True): """ Load the RMG statmech database from the given `path` on disk, where diff --git a/rmgpy/rmg/input.py b/rmgpy/rmg/input.py index 45b5e0c3800..7fe87315a31 100644 --- a/rmgpy/rmg/input.py +++ b/rmgpy/rmg/input.py @@ -126,6 +126,10 @@ def simpleReactor(temperature, pressure, initialMoleFractions, terminationConver def simulator(atol, rtol): rmg.absoluteTolerance = atol rmg.relativeTolerance = rtol + +def solvation(solvent): + assert isinstance(solvent,str), "solvent should be a string like 'water'" + rmg.solvent = solvent def model(toleranceMoveToCore, toleranceKeepInEdge=0.0, toleranceInterruptSimulation=1.0, maximumEdgeSpecies=None): rmg.fluxToleranceKeepInEdge = toleranceKeepInEdge @@ -234,6 +238,7 @@ def readInputFile(path, rmg0): 'adjacencyList': adjacencyList, 'simpleReactor': simpleReactor, 'simulator': simulator, + 'solvation': solvation, 'model': model, 'pressureDependence': pressureDependence, 'options': options, @@ -315,7 +320,9 @@ def saveInputFile(path, rmg): f.write(' },\n') f.write(')\n\n') - + + if rmg.solvent: + f.write("solvation(\n solvent = '{0!s}'\n)\n\n".format(solvent)) # Simulator tolerances f.write('simulator(\n') diff --git a/rmgpy/rmg/main.py b/rmgpy/rmg/main.py index 636ad27f203..7edfa9bce0b 100644 --- a/rmgpy/rmg/main.py +++ b/rmgpy/rmg/main.py @@ -75,6 +75,7 @@ class RMG: `kineticsFamilies` The kinetics families to use for reaction generation `kineticsDepositories` The kinetics depositories to use for looking up kinetics in each family `kineticsEstimator` The method to use to estimate kinetics: 'group additivity' or 'rate rules' + `solvent` If solvation estimates are required, the name of the solvent. --------------------------- ------------------------------------------------ `reactionModel` The core-edge reaction model generated by this job `reactionSystems` A list of the reaction systems used in this job @@ -124,6 +125,7 @@ def clear(self): self.kineticsFamilies = None self.kineticsDepositories = None self.kineticsEstimator = 'group additivity' + self.solvent = None self.reactionModel = None self.reactionSystems = None @@ -260,6 +262,10 @@ def initialize(self, args): # Load databases self.loadDatabase() + + if self.solvent: + Species.solventData = self.database.solvation.getSolventData(self.solvent) + logging.info("Setting solvent data for {0}".format(self.solvent)) # Set wall time if args.walltime == '0': diff --git a/rmgpy/rmg/model.py b/rmgpy/rmg/model.py index 929a83dbaf2..bccc8db2406 100644 --- a/rmgpy/rmg/model.py +++ b/rmgpy/rmg/model.py @@ -47,6 +47,7 @@ from rmgpy.statmech import Conformer from rmgpy.data.thermo import * +from rmgpy.data.solvation import * from rmgpy.data.kinetics import * from rmgpy.data.statmech import * import rmgpy.data.rmg @@ -57,6 +58,8 @@ ################################################################################ class Species(rmgpy.species.Species): + solventName = None + solventData = None def __init__(self, index=-1, label='', thermo=None, conformer=None, molecule=None, lennardJones=None, molecularWeight=None, energyTransferModel=None, reactive=True, coreSizeAtCreation=0): rmgpy.species.Species.__init__(self, index, label, thermo, conformer, molecule, lennardJones, molecularWeight, energyTransferModel, reactive) @@ -94,6 +97,16 @@ def generateThermoData(self, database, thermoClass=NASA): CpInf = self.calculateCpInf() wilhoit = thermo0.toWilhoit(Cp0=Cp0, CpInf=CpInf) + + # Add on solvation correction + if Species.solventData: + logging.info("Making solvent correction for {0} with {1!r}".format(Species.solventName,Species.solventData)) +# soluteData = database.solvation.getSoluteData(Species) +# solventData = database.solvation.getSolventData(label) +# solvation_correction = database.solvation.getSolvationCorrection(self) +# wilhoit.H0 = wilhoit.H0 + solvation_correction.enthalpy +# wilhoit.S0 = wilhoit.S0 + solvation_correction.entropy + # Compute E0 by extrapolation to 0 K if self.conformer is None: self.conformer = Conformer() From 19c3fa6829b0776cf9fc8289b41f35af7aa68f97 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 4 Mar 2013 18:55:16 -0500 Subject: [PATCH 12/55] Reorganization of solvation module. --- rmgpy/data/solvation.py | 1460 +++++++++++++++++++-------------------- 1 file changed, 722 insertions(+), 738 deletions(-) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index c03f7da1872..b63565c52aa 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -1,572 +1,571 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -################################################################################ -# -# RMG - Reaction Mechanism Generator -# -# Copyright (c) 2002-2010 Prof. William H. Green (whgreen@mit.edu) and the -# RMG Team (rmg_dev@mit.edu) -# -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the 'Software'), -# to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. -# -################################################################################ - -""" - -""" - -import os -import os.path -import math -import logging -import numpy -from copy import copy, deepcopy - -from base import Database, Entry, makeLogicNode - -import rmgpy.constants as constants -from rmgpy.molecule import Molecule, Atom, Bond, Group - -################################################################################ - -def saveEntry(f, entry): - """ - Write a Pythonic string representation of the given `entry` in the thermo - database to the file object `f`. - """ - raise NotImplementedError() - -def generateOldLibraryEntry(data): - """ - Return a list of values used to save entries to the old-style RMG - thermo database based on the thermodynamics object `data`. - """ - raise NotImplementedError() - -def processOldLibraryEntry(data): - """ - Process a list of parameters `data` as read from an old-style RMG - thermo database, returning the corresponding thermodynamics object. - """ - raise NotImplementedError() - - -class SolventData(): - """ - Stores Abraham/Mintz parameters for characterizing a solvent. - """ - def __init__(self, s_h=None, b_h=None, e_h=None, l_h=None, a_h=None, - c_h=None, s_g=None, b_g=None, e_g=None, l_g=None, a_g=None, c_g=None): - self.s_h = s_h - self.b_h = b_h - self.e_h = e_h - self.l_h = l_h - self.a_h = a_h - self.c_h = c_h - self.s_g = s_g - self.b_g = b_g - self.e_g = e_g - self.l_g = l_g - self.a_g = a_g - self.c_g = c_g - -class SoluteData(): - """ - Stores Abraham parameters to characterize a solute - """ - def __init__(self, S=None, B=None, E=None, L=None, A=None, comment=""): - #: :math:`\pi_2^H` - self.S = S - self.B = B - self.E = E - self.L = L +#!/usr/bin/python +# -*- coding: utf-8 -*- + +################################################################################ +# +# RMG - Reaction Mechanism Generator +# +# Copyright (c) 2002-2010 Prof. William H. Green (whgreen@mit.edu) and the +# RMG Team (rmg_dev@mit.edu) +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the 'Software'), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# +################################################################################ + +""" + +""" + +import os +import os.path +import math +import logging +import numpy +from copy import copy, deepcopy + +from base import Database, Entry, makeLogicNode + +import rmgpy.constants as constants +from rmgpy.molecule import Molecule, Atom, Bond, Group + +################################################################################ + +def saveEntry(f, entry): + """ + Write a Pythonic string representation of the given `entry` in the thermo + database to the file object `f`. + """ + raise NotImplementedError() + +def generateOldLibraryEntry(data): + """ + Return a list of values used to save entries to the old-style RMG + thermo database based on the thermodynamics object `data`. + """ + raise NotImplementedError() + +def processOldLibraryEntry(data): + """ + Process a list of parameters `data` as read from an old-style RMG + thermo database, returning the corresponding thermodynamics object. + """ + raise NotImplementedError() + + +class SolventData(): + """ + Stores Abraham/Mintz parameters for characterizing a solvent. + """ + def __init__(self, s_h=None, b_h=None, e_h=None, l_h=None, a_h=None, + c_h=None, s_g=None, b_g=None, e_g=None, l_g=None, a_g=None, c_g=None): + self.s_h = s_h + self.b_h = b_h + self.e_h = e_h + self.l_h = l_h + self.a_h = a_h + self.c_h = c_h + self.s_g = s_g + self.b_g = b_g + self.e_g = e_g + self.l_g = l_g + self.a_g = a_g + self.c_g = c_g + +class SolvationCorrection(): + """ + Stores corrections for enthalpy, entropy, and Gibbs free energy when a species is solvated. + """ + def __init__(self, enthalpy=None, entropy=None, gibbs=None): + self.enthalpy = enthalpy + self.entropy = entropy + self.gibbs = gibbs + +class SoluteData(): + """ + Stores Abraham parameters to characterize a solute + """ + def __init__(self, S=None, B=None, E=None, L=None, A=None, comment=""): + self.S = S + self.B = B + self.E = E + self.L = L self.A = A self.comment = comment def __repr__(self): - return "SoluteData(S={0},B={1},E={2},L={3},A={4},comment={5!r})".format(self.S, self.B, self.E, self.L, self.A, self.comment) - - -################################################################################ - -class SoluteLibrary(Database): - """ - A class for working with a RMG solute library. - """ - def __init__(self, label='', name='', shortDesc='', longDesc=''): - Database.__init__(self, label=label, name=name, shortDesc=shortDesc, longDesc=longDesc) - - def loadEntry(self, - index, - label, - molecule, - solute, - reference=None, - referenceType='', - shortDesc='', - longDesc='', - history=None - ): - self.entries[label] = Entry( - index = index, - label = label, - item = Molecule().fromAdjacencyList(molecule), - data = solute, - reference = reference, - referenceType = referenceType, - shortDesc = shortDesc, - longDesc = longDesc.strip(), - history = history or [], - ) - - def saveEntry(self, f, entry): - """ - Write the given `entry` in the solute database to the file object `f`. - """ - return saveEntry(f, entry) - - def generateOldLibraryEntry(self, data): - """ - Return a list of values used to save entries to the old-style RMG - thermo database based on the thermodynamics object `data`. - """ - return generateOldLibraryEntry(data) - - def processOldLibraryEntry(self, data): - """ - Process a list of parameters `data` as read from an old-style RMG - thermo database, returning the corresponding thermodynamics object. - """ - return processOldLibraryEntry(data) - -################################################################################ - -class SoluteGroups(Database): - """ - A class for working with an RMG solute group additivity database. - """ - - def __init__(self, label='', name='', shortDesc='', longDesc=''): - Database.__init__(self, label=label, name=name, shortDesc=shortDesc, longDesc=longDesc) - - def loadEntry(self, - index, - label, - group, - solute, - reference=None, - referenceType='', - shortDesc='', - longDesc='', - history=None - ): - if group[0:3].upper() == 'OR{' or group[0:4].upper() == 'AND{' or group[0:7].upper() == 'NOT OR{' or group[0:8].upper() == 'NOT AND{': - item = makeLogicNode(group) - else: - item = Group().fromAdjacencyList(group) - self.entries[label] = Entry( - index = index, - label = label, - item = item, - data = solute, - reference = reference, - referenceType = referenceType, - shortDesc = shortDesc, - longDesc = longDesc.strip(), - history = history or [], - ) - - def saveEntry(self, f, entry): - """ - Write the given `entry` in the thermo database to the file object `f`. - """ - return saveEntry(f, entry) - - def generateOldLibraryEntry(self, data): - """ - Return a list of values used to save entries to the old-style RMG - thermo database based on the thermodynamics object `data`. - """ - - return generateOldLibraryEntry(data) - - def processOldLibraryEntry(self, data): - """ - Process a list of parameters `data` as read from an old-style RMG - thermo database, returning the corresponding thermodynamics object. - """ - return processOldLibraryEntry(data) - -################################################################################ - -class SoluteDatabase(object): - """ - A class for working with the RMG solute database. - """ - - def __init__(self): - #self.depository = {} - self.libraries = {} - self.groups = {} - self.libraryOrder = [] + return "SoluteData(S={0},B={1},E={2},L={3},A={4},comment={5!r})".format(self.S, self.B, self.E, self.L, self.A, self.comment) + + +################################################################################ + + +################################################################################ + +class SolventLibrary(Database): + """ + A class for working with a RMG solvent library. + """ + def __init__(self, label='', name='', shortDesc='', longDesc=''): + Database.__init__(self, label=label, name=name, shortDesc=shortDesc, longDesc=longDesc) + + def loadEntry(self, + index, + label, + solvent, + reference=None, + referenceType='', + shortDesc='', + longDesc='', + history=None + ): + self.entries[label] = Entry( + index = index, + label = label, + data = solvent, + reference = reference, + referenceType = referenceType, + shortDesc = shortDesc, + longDesc = longDesc.strip(), + history = history or [], + ) + + def load(self, path): + """ + Load the solvent library from the given path + """ + Database.load(self, path, local_context={'SolventData': SolventData}, global_context={}) + + def saveEntry(self, f, entry): + """ + Write the given `entry` in the solute database to the file object `f`. + """ + return saveEntry(f, entry) + + def getSolventData(self, label): + """ + Get a solvent's data from its name + """ + return self.entries[label].data + + +class SoluteLibrary(Database): + """ + A class for working with a RMG solute library. + """ + def __init__(self, label='', name='', shortDesc='', longDesc=''): + Database.__init__(self, label=label, name=name, shortDesc=shortDesc, longDesc=longDesc) + + def loadEntry(self, + index, + label, + molecule, + solute, + reference=None, + referenceType='', + shortDesc='', + longDesc='', + history=None + ): + self.entries[label] = Entry( + index = index, + label = label, + item = Molecule().fromAdjacencyList(molecule), + data = solute, + reference = reference, + referenceType = referenceType, + shortDesc = shortDesc, + longDesc = longDesc.strip(), + history = history or [], + ) + + def load(self, path): + pass + + def saveEntry(self, f, entry): + """ + Write the given `entry` in the solute database to the file object `f`. + """ + return saveEntry(f, entry) + + def generateOldLibraryEntry(self, data): + """ + Return a list of values used to save entries to the old-style RMG + thermo database based on the thermodynamics object `data`. + """ + return generateOldLibraryEntry(data) + + def processOldLibraryEntry(self, data): + """ + Process a list of parameters `data` as read from an old-style RMG + thermo database, returning the corresponding thermodynamics object. + """ + return processOldLibraryEntry(data) + +################################################################################ + +class SoluteGroups(Database): + """ + A class for working with an RMG solute group additivity database. + """ + + def __init__(self, label='', name='', shortDesc='', longDesc=''): + Database.__init__(self, label=label, name=name, shortDesc=shortDesc, longDesc=longDesc) + + def loadEntry(self, + index, + label, + group, + solute, + reference=None, + referenceType='', + shortDesc='', + longDesc='', + history=None + ): + if group[0:3].upper() == 'OR{' or group[0:4].upper() == 'AND{' or group[0:7].upper() == 'NOT OR{' or group[0:8].upper() == 'NOT AND{': + item = makeLogicNode(group) + else: + item = Group().fromAdjacencyList(group) + self.entries[label] = Entry( + index = index, + label = label, + item = item, + data = solute, + reference = reference, + referenceType = referenceType, + shortDesc = shortDesc, + longDesc = longDesc.strip(), + history = history or [], + ) + + def saveEntry(self, f, entry): + """ + Write the given `entry` in the thermo database to the file object `f`. + """ + return saveEntry(f, entry) + + def generateOldLibraryEntry(self, data): + """ + Return a list of values used to save entries to the old-style RMG + thermo database based on the thermodynamics object `data`. + """ + + return generateOldLibraryEntry(data) + + def processOldLibraryEntry(self, data): + """ + Process a list of parameters `data` as read from an old-style RMG + thermo database, returning the corresponding thermodynamics object. + """ + return processOldLibraryEntry(data) + +################################################################################ + +class SolvationDatabase(object): + """ + A class for working with the RMG solute database. + """ + + def __init__(self): + #self.depository = {} + self.solventLibrary = SolventLibrary() + self.soluteLibrary = SoluteLibrary() + self.groups = {} self.local_context = { 'SoluteData': SoluteData, - } - self.global_context = {} - - def __reduce__(self): - """ - A helper function used when pickling a SoluteDatabase object. - """ - d = { - 'libraries': self.libraries, - 'groups': self.groups, - 'libraryOrder': self.libraryOrder, - } - return (SoluteDatabase, (), d) - - def __setstate__(self, d): - """ - A helper function used when unpickling a SoluteDatabase object. - """ - #self.depository = d['depository'] - self.libraries = d['libraries'] - self.groups = d['groups'] - self.libraryOrder = d['libraryOrder'] - - def load(self, path, libraries=None, depository=True): - """ - Load the solute database from the given `path` on disk, where `path` - points to the top-level folder of the solute database. - """ - # if depository: - # self.loadDepository(os.path.join(path, 'depository')) - # else: - # self.depository = {} - #no solute library right now... - #self.loadLibraries(os.path.join(path, 'libraries'), libraries) - self.loadGroups(os.path.join(path, 'groups')) - - def loadDepository(self, path): - """ - Load the thermo database from the given `path` on disk, where `path` - points to the top-level folder of the thermo database. - """ - raise NotImplementedError() - - def loadLibraries(self, path, libraries=None): - """ - Load the solute database from the given `path` on disk, where `path` - points to the top-level folder of the aolute database. - """ - self.libraries = {}; self.libraryOrder = [] - for (root, dirs, files) in os.walk(os.path.join(path)): - for f in files: - name, ext = os.path.splitext(f) - if ext.lower() == '.py' and (libraries is None or name in libraries): - logging.info('Loading solute library from {0} in {1}...'.format(f, root)) - library = SoluteLibrary() - library.load(os.path.join(root, f), self.local_context, self.global_context) - library.label = os.path.splitext(f)[0] - self.libraries[library.label] = library - self.libraryOrder.append(library.label) - if libraries is not None: - self.libraryOrder = libraries - - def loadGroups(self, path): - """ - Load the solute database from the given `path` on disk, where `path` - points to the top-level folder of the solute database. - """ - logging.info('Loading Platts additivity group database from {0}...'.format(path)) - self.groups = {} - self.groups['abraham'] = SoluteGroups(label='abraham').load(os.path.join(path, 'abraham.py' ), self.local_context, self.global_context) - self.groups['nonacentered'] = SoluteGroups(label='nonacentered').load(os.path.join(path, 'nonacentered.py' ), self.local_context, self.global_context) - # self.groups['int15'] = ThermoGroups(label='int15').load(os.path.join(path, 'int15.py' ), self.local_context, self.global_context) - # self.groups['ring'] = ThermoGroups(label='ring').load(os.path.join(path, 'ring.py' ), self.local_context, self.global_context) - # self.groups['radical'] = ThermoGroups(label='radical').load(os.path.join(path, 'radical.py'), self.local_context, self.global_context) - # self.groups['other'] = ThermoGroups(label='other').load(os.path.join(path, 'other.py' ), self.local_context, self.global_context) - - def save(self, path): - """ - Save the solvation database to the given `path` on disk, where `path` - points to the top-level folder of the solvation database. - """ - path = os.path.abspath(path) - if not os.path.exists(path): os.mkdir(path) - #self.saveDepository(os.path.join(path, 'depository')) - self.saveLibraries(os.path.join(path, 'libraries')) - self.saveGroups(os.path.join(path, 'groups')) - - def saveDepository(self, path): - """ - Save the thermo depository to the given `path` on disk, where `path` - points to the top-level folder of the thermo depository. - """ - raise NotImplementedError() - - def saveLibraries(self, path): - """ - Save the solute libraries to the given `path` on disk, where `path` - points to the top-level folder of the solute libraries. - """ - if not os.path.exists(path): os.mkdir(path) - for library in self.libraries.values(): - library.save(os.path.join(path, '{0}.py'.format(library.label))) - - def saveGroups(self, path): - """ - Save the solute groups to the given `path` on disk, where `path` - points to the top-level folder of the solute groups. - """ - if not os.path.exists(path): os.mkdir(path) - self.groups['abraham'].save(os.path.join(path, 'abraham.py')) - self.groups['nonacentered'].save(os.path.join(path, 'nonacentered.py')) - # self.groups['int15'].save(os.path.join(path, 'int15.py')) - # self.groups['ring'].save(os.path.join(path, 'ring.py')) - # self.groups['radical'].save(os.path.join(path, 'radical.py')) - # self.groups['other'].save(os.path.join(path, 'other.py')) - - def loadOld(self, path): - """ - Load the old RMG solute database from the given `path` on disk, where - `path` points to the top-level folder of the old RMG database. - """ - # The old database does not have a depository, so create an empty one - # self.depository = {} - # self.depository['stable'] = ThermoDepository(label='stable', name='Stable Molecules') - # self.depository['radical'] = ThermoDepository(label='radical', name='Radical Molecules') - - for (root, dirs, files) in os.walk(os.path.join(path, 'thermo_libraries')): - if os.path.exists(os.path.join(root, 'Dictionary.txt')) and os.path.exists(os.path.join(root, 'Library.txt')): - library = SoluteLibrary(label=os.path.basename(root), name=os.path.basename(root)) - library.loadOld( - dictstr = os.path.join(root, 'Dictionary.txt'), - treestr = '', - libstr = os.path.join(root, 'Library.txt'), - numParameters = 5, - numLabels = 1, - pattern = False, - ) - library.label = os.path.basename(root) - self.libraries[library.label] = library - - self.groups = {} - self.groups['abraham'] = SoluteGroups(label='abraham', name='Platts Group Additivity Values for Abraham Solute Descriptors').loadOld( - dictstr = os.path.join(path, 'thermo_groups', 'Abraham_Dictionary.txt'), - treestr = os.path.join(path, 'thermo_groups', 'Abraham_Tree.txt'), - libstr = os.path.join(path, 'thermo_groups', 'Abraham_Library.txt'), - numParameters = 5, - numLabels = 1, - pattern = True, - ) - # self.groups['gauche'] = ThermoGroups(label='gauche', name='Gauche Interaction Corrections').loadOld( - # dictstr = os.path.join(path, 'thermo_groups', 'Gauche_Dictionary.txt'), - # treestr = os.path.join(path, 'thermo_groups', 'Gauche_Tree.txt'), - # libstr = os.path.join(path, 'thermo_groups', 'Gauche_Library.txt'), - # numParameters = 12, - # numLabels = 1, - # pattern = True, - # ) - # self.groups['int15'] = ThermoGroups(label='int15', name='1,5-Interaction Corrections').loadOld( - # dictstr = os.path.join(path, 'thermo_groups', '15_Dictionary.txt'), - # treestr = os.path.join(path, 'thermo_groups', '15_Tree.txt'), - # libstr = os.path.join(path, 'thermo_groups', '15_Library.txt'), - # numParameters = 12, - # numLabels = 1, - # pattern = True, - # ) - # self.groups['radical'] = ThermoGroups(label='radical', name='Radical Corrections').loadOld( - # dictstr = os.path.join(path, 'thermo_groups', 'Radical_Dictionary.txt'), - # treestr = os.path.join(path, 'thermo_groups', 'Radical_Tree.txt'), - # libstr = os.path.join(path, 'thermo_groups', 'Radical_Library.txt'), - # numParameters = 12, - # numLabels = 1, - # pattern = True, - # ) - # self.groups['ring'] = ThermoGroups(label='ring', name='Ring Corrections').loadOld( - # dictstr = os.path.join(path, 'thermo_groups', 'Ring_Dictionary.txt'), - # treestr = os.path.join(path, 'thermo_groups', 'Ring_Tree.txt'), - # libstr = os.path.join(path, 'thermo_groups', 'Ring_Library.txt'), - # numParameters = 12, - # numLabels = 1, - # pattern = True, - # ) - # self.groups['other'] = ThermoGroups(label='other', name='Other Corrections').loadOld( - # dictstr = os.path.join(path, 'thermo_groups', 'Other_Dictionary.txt'), - # treestr = os.path.join(path, 'thermo_groups', 'Other_Tree.txt'), - # libstr = os.path.join(path, 'thermo_groups', 'Other_Library.txt'), - # numParameters = 12, - # numLabels = 1, - # pattern = True, - # ) - - def saveOld(self, path): - """ - Save the old RMG Abraham database to the given `path` on disk, where - `path` points to the top-level folder of the old RMG database. - """ - # Depository not used in old database, so it is not saved - - librariesPath = os.path.join(path, 'thermo_libraries') - if not os.path.exists(librariesPath): os.mkdir(librariesPath) - for library in self.libraries.values(): - libraryPath = os.path.join(librariesPath, library.label) - if not os.path.exists(libraryPath): os.mkdir(libraryPath) - library.saveOld( - dictstr = os.path.join(libraryPath, 'Dictionary.txt'), - treestr = '', - libstr = os.path.join(libraryPath, 'Library.txt'), - ) - - groupsPath = os.path.join(path, 'thermo_groups') - if not os.path.exists(groupsPath): os.mkdir(groupsPath) - self.groups['abraham'].saveOld( - dictstr = os.path.join(groupsPath, 'Abraham_Dictionary.txt'), - treestr = os.path.join(groupsPath, 'Abraham_Tree.txt'), - libstr = os.path.join(groupsPath, 'Abraham_Library.txt'), - ) - # self.groups['gauche'].saveOld( - # dictstr = os.path.join(groupsPath, 'Gauche_Dictionary.txt'), - # treestr = os.path.join(groupsPath, 'Gauche_Tree.txt'), - # libstr = os.path.join(groupsPath, 'Gauche_Library.txt'), - # ) - # self.groups['int15'].saveOld( - # dictstr = os.path.join(groupsPath, '15_Dictionary.txt'), - # treestr = os.path.join(groupsPath, '15_Tree.txt'), - # libstr = os.path.join(groupsPath, '15_Library.txt'), - # ) - # self.groups['radical'].saveOld( - # dictstr = os.path.join(groupsPath, 'Radical_Dictionary.txt'), - # treestr = os.path.join(groupsPath, 'Radical_Tree.txt'), - # libstr = os.path.join(groupsPath, 'Radical_Library.txt'), - # ) - # self.groups['ring'].saveOld( - # dictstr = os.path.join(groupsPath, 'Ring_Dictionary.txt'), - # treestr = os.path.join(groupsPath, 'Ring_Tree.txt'), - # libstr = os.path.join(groupsPath, 'Ring_Library.txt'), - # ) - # self.groups['other'].saveOld( - # dictstr = os.path.join(groupsPath, 'Other_Dictionary.txt'), - # treestr = os.path.join(groupsPath, 'Other_Tree.txt'), - # libstr = os.path.join(groupsPath, 'Other_Library.txt'), - # ) - - def getSoluteData(self, species): - """ - Return the solute descriptors for a given :class:`Species` - object `species`. This function first searches the loaded libraries - in order, returning the first match found, before falling back to - estimation via Platts group additivity. - """ - soluteData = None - # Check the libraries in order first; return the first successful match - for label in self.libraryOrder: - soluteData = self.getSoluteDataFromLibrary(species, self.libraries[label]) - if soluteData is not None: - soluteData[0].comment = label - break - else: - # Solute not found in any loaded libraries, so estimate - soluteData = self.getSoluteDataFromGroups(species) - # Add Cp0 and CpInf values - # Cp0 = species.calculateCp0() - # CpInf = species.calculateCpInf() - data, library, entry = soluteData - # if isinstance(data,SoluteData): - # data.Cp0 = (Cp0,"J/(mol*K)") - # data.CpInf = (CpInf,"J/(mol*K)") - # Return the resulting solute parameters - return data - - def getAllSoluteData(self, species): - """ - Return all possible sets of Abraham solute descriptors for a given - :class:`Species` object `species`. The hits from the library come - first, then the group additivity estimate. This method is useful - for a generic search job. - """ - thermoData = [] - # Data from depository comes first - # thermoData.extend(self.getThermoDataFromDepository(species)) - # Data from libraries comes second - for label in self.libraryOrder: - data = self.getSoluteDataFromLibrary(species, self.libraries[label]) - if data: - data[0].comment = label - soluteData.append(data) - # Last entry is always the estimate from group additivity - soluteData.append(self.getSoluteDataFromGroups(species)) - - # Add Cp0 and CpInf values - # Cp0 = species.calculateCp0() - # CpInf = species.calculateCpInf() - # for data, library, entry in thermoData: - # if isinstance(data,ThermoData): - # data.Cp0 = (Cp0,"J/(mol*K)") - # data.CpInf = (CpInf,"J/(mol*K)") - - # Return all of the resulting thermo parameters - return thermoData - - def getThermoDataFromDepository(self, species): - """ - Return all possible sets of thermodynamic parameters for a given - :class:`Species` object `species` from the depository. If no - depository is loaded, a :class:`DatabaseError` is raised. - """ - raise NotImplementedError() - - def getSoluteDataFromLibrary(self, species, library): - """ - Return the set of Abraham solute descriptors corresponding to a given - :class:`Species` object `species` from the specified solute - `library`. If `library` is a string, the list of libraries is searched - for a library with that name. If no match is found in that library, - ``None`` is returned. If no corresponding library is found, a - :class:`DatabaseError` is raised. - """ - for label, entry in library.entries.iteritems(): - for molecule in species.molecule: - if molecule.isIsomorphic(entry.item) and entry.data is not None: - return (deepcopy(entry.data), library, entry) - return None - - def getSoluteDataFromGroups(self, species): - """ - Return the set of Abraham solute parameters corresponding to a given - :class:`Species` object `species` by estimation using the Platts group - additivity method. If no group additivity values are loaded, a + 'SolventData': SolventData + } + self.global_context = {} + + def __reduce__(self): + """ + A helper function used when pickling a SoluteDatabase object. + """ + d = { + 'libraries': self.libraries, + 'groups': self.groups, + 'libraryOrder': self.libraryOrder, + } + return (SoluteDatabase, (), d) + + def __setstate__(self, d): + """ + A helper function used when unpickling a SoluteDatabase object. + """ + #self.depository = d['depository'] + self.libraries = d['libraries'] + self.groups = d['groups'] + self.libraryOrder = d['libraryOrder'] + + def load(self, path, libraries=None, depository=True): + """ + Load the solvation database from the given `path` on disk, where `path` + points to the top-level folder of the solvation database. + """ + + self.solventLibrary.load(os.path.join(path,'libraries','solvent.py')) + self.soluteLibrary.load(os.path.join(path,'libraries','solute.py')) + + self.loadGroups(os.path.join(path, 'groups')) + + def getSolventData(self, solvent_name): + return self.solventLibrary.getSolventData(solvent_name) + + def loadDepository(self, path): + """ + Load the thermo database from the given `path` on disk, where `path` + points to the top-level folder of the thermo database. + """ + raise NotImplementedError() + + + def loadGroups(self, path): + """ + Load the solute database from the given `path` on disk, where `path` + points to the top-level folder of the solute database. + """ + logging.info('Loading Platts additivity group database from {0}...'.format(path)) + self.groups = {} + self.groups['abraham'] = SoluteGroups(label='abraham').load(os.path.join(path, 'abraham.py' ), self.local_context, self.global_context) + self.groups['nonacentered'] = SoluteGroups(label='nonacentered').load(os.path.join(path, 'nonacentered.py' ), self.local_context, self.global_context) + + def save(self, path): + """ + Save the solvation database to the given `path` on disk, where `path` + points to the top-level folder of the solvation database. + """ + path = os.path.abspath(path) + if not os.path.exists(path): os.mkdir(path) + #self.saveDepository(os.path.join(path, 'depository')) + self.saveLibraries(os.path.join(path, 'libraries')) + self.saveGroups(os.path.join(path, 'groups')) + + def saveDepository(self, path): + """ + Save the thermo depository to the given `path` on disk, where `path` + points to the top-level folder of the thermo depository. + """ + raise NotImplementedError() + + def saveLibraries(self, path): + """ + Save the solute libraries to the given `path` on disk, where `path` + points to the top-level folder of the solute libraries. + """ + if not os.path.exists(path): os.mkdir(path) + for library in self.libraries.values(): + library.save(os.path.join(path, '{0}.py'.format(library.label))) + + def saveGroups(self, path): + """ + Save the solute groups to the given `path` on disk, where `path` + points to the top-level folder of the solute groups. + """ + if not os.path.exists(path): os.mkdir(path) + self.groups['abraham'].save(os.path.join(path, 'abraham.py')) + self.groups['nonacentered'].save(os.path.join(path, 'nonacentered.py')) + # self.groups['int15'].save(os.path.join(path, 'int15.py')) + # self.groups['ring'].save(os.path.join(path, 'ring.py')) + # self.groups['radical'].save(os.path.join(path, 'radical.py')) + # self.groups['other'].save(os.path.join(path, 'other.py')) + + def loadOld(self, path): + """ + Load the old RMG solute database from the given `path` on disk, where + `path` points to the top-level folder of the old RMG database. + """ + # The old database does not have a depository, so create an empty one + # self.depository = {} + # self.depository['stable'] = ThermoDepository(label='stable', name='Stable Molecules') + # self.depository['radical'] = ThermoDepository(label='radical', name='Radical Molecules') + + for (root, dirs, files) in os.walk(os.path.join(path, 'thermo_libraries')): + if os.path.exists(os.path.join(root, 'Dictionary.txt')) and os.path.exists(os.path.join(root, 'Library.txt')): + library = SoluteLibrary(label=os.path.basename(root), name=os.path.basename(root)) + library.loadOld( + dictstr = os.path.join(root, 'Dictionary.txt'), + treestr = '', + libstr = os.path.join(root, 'Library.txt'), + numParameters = 5, + numLabels = 1, + pattern = False, + ) + library.label = os.path.basename(root) + self.libraries[library.label] = library + + self.groups = {} + self.groups['abraham'] = SoluteGroups(label='abraham', name='Platts Group Additivity Values for Abraham Solute Descriptors').loadOld( + dictstr = os.path.join(path, 'thermo_groups', 'Abraham_Dictionary.txt'), + treestr = os.path.join(path, 'thermo_groups', 'Abraham_Tree.txt'), + libstr = os.path.join(path, 'thermo_groups', 'Abraham_Library.txt'), + numParameters = 5, + numLabels = 1, + pattern = True, + ) + + def saveOld(self, path): + """ + Save the old RMG Abraham database to the given `path` on disk, where + `path` points to the top-level folder of the old RMG database. + """ + # Depository not used in old database, so it is not saved + + librariesPath = os.path.join(path, 'thermo_libraries') + if not os.path.exists(librariesPath): os.mkdir(librariesPath) + for library in self.libraries.values(): + libraryPath = os.path.join(librariesPath, library.label) + if not os.path.exists(libraryPath): os.mkdir(libraryPath) + library.saveOld( + dictstr = os.path.join(libraryPath, 'Dictionary.txt'), + treestr = '', + libstr = os.path.join(libraryPath, 'Library.txt'), + ) + + groupsPath = os.path.join(path, 'thermo_groups') + if not os.path.exists(groupsPath): os.mkdir(groupsPath) + self.groups['abraham'].saveOld( + dictstr = os.path.join(groupsPath, 'Abraham_Dictionary.txt'), + treestr = os.path.join(groupsPath, 'Abraham_Tree.txt'), + libstr = os.path.join(groupsPath, 'Abraham_Library.txt'), + ) + # self.groups['gauche'].saveOld( + # dictstr = os.path.join(groupsPath, 'Gauche_Dictionary.txt'), + # treestr = os.path.join(groupsPath, 'Gauche_Tree.txt'), + # libstr = os.path.join(groupsPath, 'Gauche_Library.txt'), + # ) + # self.groups['int15'].saveOld( + # dictstr = os.path.join(groupsPath, '15_Dictionary.txt'), + # treestr = os.path.join(groupsPath, '15_Tree.txt'), + # libstr = os.path.join(groupsPath, '15_Library.txt'), + # ) + # self.groups['radical'].saveOld( + # dictstr = os.path.join(groupsPath, 'Radical_Dictionary.txt'), + # treestr = os.path.join(groupsPath, 'Radical_Tree.txt'), + # libstr = os.path.join(groupsPath, 'Radical_Library.txt'), + # ) + # self.groups['ring'].saveOld( + # dictstr = os.path.join(groupsPath, 'Ring_Dictionary.txt'), + # treestr = os.path.join(groupsPath, 'Ring_Tree.txt'), + # libstr = os.path.join(groupsPath, 'Ring_Library.txt'), + # ) + # self.groups['other'].saveOld( + # dictstr = os.path.join(groupsPath, 'Other_Dictionary.txt'), + # treestr = os.path.join(groupsPath, 'Other_Tree.txt'), + # libstr = os.path.join(groupsPath, 'Other_Library.txt'), + # ) + + def getSoluteData(self, species): + """ + Return the solute descriptors for a given :class:`Species` + object `species`. This function first searches the loaded libraries + in order, returning the first match found, before falling back to + estimation via Platts group additivity. + """ + soluteData = None + # Check the libraries in order first; return the first successful match + for label in self.libraryOrder: + soluteData = self.getSoluteDataFromLibrary(species, self.libraries[label]) + if soluteData is not None: + soluteData[0].comment = label + break + else: + # Solute not found in any loaded libraries, so estimate + soluteData = self.getSoluteDataFromGroups(species) + # Add Cp0 and CpInf values + # Cp0 = species.calculateCp0() + # CpInf = species.calculateCpInf() + data, library, entry = soluteData + # if isinstance(data,SoluteData): + # data.Cp0 = (Cp0,"J/(mol*K)") + # data.CpInf = (CpInf,"J/(mol*K)") + # Return the resulting solute parameters + return data + + def getAllSoluteData(self, species): + """ + Return all possible sets of Abraham solute descriptors for a given + :class:`Species` object `species`. The hits from the library come + first, then the group additivity estimate. This method is useful + for a generic search job. + """ + thermoData = [] + # Data from depository comes first + # thermoData.extend(self.getThermoDataFromDepository(species)) + # Data from libraries comes second + for label in self.libraryOrder: + data = self.getSoluteDataFromLibrary(species, self.libraries[label]) + if data: + data[0].comment = label + soluteData.append(data) + # Last entry is always the estimate from group additivity + soluteData.append(self.getSoluteDataFromGroups(species)) + + # Add Cp0 and CpInf values + # Cp0 = species.calculateCp0() + # CpInf = species.calculateCpInf() + # for data, library, entry in thermoData: + # if isinstance(data,ThermoData): + # data.Cp0 = (Cp0,"J/(mol*K)") + # data.CpInf = (CpInf,"J/(mol*K)") + + # Return all of the resulting thermo parameters + return thermoData + + def getThermoDataFromDepository(self, species): + """ + Return all possible sets of thermodynamic parameters for a given + :class:`Species` object `species` from the depository. If no + depository is loaded, a :class:`DatabaseError` is raised. + """ + raise NotImplementedError() + + def getSoluteDataFromLibrary(self, species, library): + """ + Return the set of Abraham solute descriptors corresponding to a given + :class:`Species` object `species` from the specified solute + `library`. If `library` is a string, the list of libraries is searched + for a library with that name. If no match is found in that library, + ``None`` is returned. If no corresponding library is found, a + :class:`DatabaseError` is raised. + """ + for label, entry in library.entries.iteritems(): + for molecule in species.molecule: + if molecule.isIsomorphic(entry.item) and entry.data is not None: + return (deepcopy(entry.data), library, entry) + return None + + def getSoluteDataFromGroups(self, species): + """ + Return the set of Abraham solute parameters corresponding to a given + :class:`Species` object `species` by estimation using the Platts group + additivity method. If no group additivity values are loaded, a :class:`DatabaseError` is raised. It averages (linearly) over the desciptors for each Molecule (resonance isomer) - in the Species. - """ - + in the Species. + """ soluteData = SoluteData(0.0,0.0,0.0,0.0,0.0) count = 0 - comments = [] - for molecule in species.molecule: - molecule.clearLabeledAtoms() - molecule.updateAtomTypes() - sdata = self.estimateSoluteViaGroupAdditivity(molecule) - + comments = [] + for molecule in species.molecule: + molecule.clearLabeledAtoms() + molecule.updateAtomTypes() + sdata = self.estimateSoluteViaGroupAdditivity(molecule) soluteData.S += sdata.S soluteData.B += sdata.B soluteData.E += sdata.E @@ -581,193 +580,178 @@ def getSoluteDataFromGroups(self, species): soluteData.E /= count soluteData.L /= count soluteData.A /= count - soluteData.comment = "Average of {0}".format(" and ".join(comments)) - - return soluteData, None, None - - def estimateSoluteViaGroupAdditivity(self, molecule): - """ - Return the set of Abraham solute parameters corresponding to a given - :class:`Molecule` object `molecule` by estimation using the Platts' group - additivity method. If no group additivity values are loaded, a - :class:`DatabaseError` is raised. - """ - # For thermo estimation we need the atoms to already be sorted because we - # iterate over them; if the order changes during the iteration then we - # will probably not visit the right atoms, and so will get the thermo wrong - molecule.sortVertices() - - # Create the SoluteData object - soluteData = SoluteData( - S = 0.277, - B = 0.071, - E = 0.248, - L = 0.13, - A = 0.003 - ) - - if sum([atom.radicalElectrons for atom in molecule.atoms]) > 0: # radical species - - # Make a copy of the structure so we don't change the original - saturatedStruct = molecule.copy(deep=True) - - # Saturate structure by replacing all radicals with bonds to - # hydrogen atoms - added = {} - for atom in saturatedStruct.atoms: - for i in range(atom.radicalElectrons): - H = Atom('H') - bond = Bond(atom, H, 'S') - saturatedStruct.addAtom(H) - saturatedStruct.addBond(bond) - if atom not in added: - added[atom] = [] - added[atom].append([H, bond]) - atom.decrementRadical() - - # Update the atom types of the saturated structure (not sure why - # this is necessary, because saturating with H shouldn't be - # changing atom types, but it doesn't hurt anything and is not - # very expensive, so will do it anyway) - saturatedStruct.updateConnectivityValues() - saturatedStruct.sortVertices() - saturatedStruct.updateAtomTypes() - - # Get solute descriptor estimates for saturated form of structure - soluteData = self.estimateSoluteViaGroupAdditivity(saturatedStruct) - assert soluteData is not None, "Solute data of saturated {0} of molecule {1} is None!".format(saturatedStruct, molecule) - # Undo symmetry number correction for saturated structure - # thermoData.S298.value_si += constants.R * math.log(saturatedStruct.symmetryNumber) - - # For each radical site, get radical correction - # Only one radical site should be considered at a time; all others - # should be saturated with hydrogen atoms - for atom in added: - - # Remove the added hydrogen atoms and bond and restore the radical - for H, bond in added[atom]: - saturatedStruct.removeBond(bond) - saturatedStruct.removeAtom(H) - atom.incrementRadical() - - saturatedStruct.updateConnectivityValues() - - - - # Re-saturate - - - # Subtract the enthalpy of the added hydrogens - - - # Correct the entropy for the symmetry number - - else: # non-radical species - # Generate estimate of solute data - for atom in molecule.atoms: - # Iterate over heavy (non-hydrogen) atoms - if atom.isNonHydrogen(): - # Get initial solute data from main group database - try: - self.__addGroupSoluteData(soluteData, self.groups['abraham'], molecule, {'*':atom}) - except KeyError: - logging.error("Couldn't find in main abraham database:") - logging.error(molecule) - logging.error(molecule.toAdjacencyList()) - raise - # Get solute data for non-atom centered groups + soluteData.comment = "Average of {0}".format(" and ".join(comments)) + + return soluteData, None, None + + def estimateSoluteViaGroupAdditivity(self, molecule): + """ + Return the set of Abraham solute parameters corresponding to a given + :class:`Molecule` object `molecule` by estimation using the Platts' group + additivity method. If no group additivity values are loaded, a + :class:`DatabaseError` is raised. + """ + # For thermo estimation we need the atoms to already be sorted because we + # iterate over them; if the order changes during the iteration then we + # will probably not visit the right atoms, and so will get the thermo wrong + molecule.sortVertices() + + # Create the SoluteData object + soluteData = SoluteData( + S = 0.277, + B = 0.071, + E = 0.248, + L = 0.13, + A = 0.003 + ) + + if sum([atom.radicalElectrons for atom in molecule.atoms]) > 0: # radical species + + # Make a copy of the structure so we don't change the original + saturatedStruct = molecule.copy(deep=True) + + # Saturate structure by replacing all radicals with bonds to + # hydrogen atoms + added = {} + for atom in saturatedStruct.atoms: + for i in range(atom.radicalElectrons): + H = Atom('H') + bond = Bond(atom, H, 'S') + saturatedStruct.addAtom(H) + saturatedStruct.addBond(bond) + if atom not in added: + added[atom] = [] + added[atom].append([H, bond]) + atom.decrementRadical() + + # Update the atom types of the saturated structure (not sure why + # this is necessary, because saturating with H shouldn't be + # changing atom types, but it doesn't hurt anything and is not + # very expensive, so will do it anyway) + saturatedStruct.updateConnectivityValues() + saturatedStruct.sortVertices() + saturatedStruct.updateAtomTypes() + + # Get solute descriptor estimates for saturated form of structure + soluteData = self.estimateSoluteViaGroupAdditivity(saturatedStruct) + assert soluteData is not None, "Solute data of saturated {0} of molecule {1} is None!".format(saturatedStruct, molecule) + # Undo symmetry number correction for saturated structure + # thermoData.S298.value_si += constants.R * math.log(saturatedStruct.symmetryNumber) + + # For each radical site, get radical correction + # Only one radical site should be considered at a time; all others + # should be saturated with hydrogen atoms + for atom in added: + + # Remove the added hydrogen atoms and bond and restore the radical + for H, bond in added[atom]: + saturatedStruct.removeBond(bond) + saturatedStruct.removeAtom(H) + atom.incrementRadical() + + saturatedStruct.updateConnectivityValues() + + + + # Re-saturate + + + # Subtract the enthalpy of the added hydrogens + + + # Correct the entropy for the symmetry number + + else: # non-radical species + # Generate estimate of solute data + for atom in molecule.atoms: + # Iterate over heavy (non-hydrogen) atoms + if atom.isNonHydrogen(): + # Get initial solute data from main group database + try: + self.__addGroupSoluteData(soluteData, self.groups['abraham'], molecule, {'*':atom}) + except KeyError: + logging.error("Couldn't find in main abraham database:") + logging.error(molecule) + logging.error(molecule.toAdjacencyList()) + raise + # Get solute data for non-atom centered groups try: self.__addGroupSoluteData(soluteData, self.groups['nonacentered'], molecule, {'*':atom}) - except KeyError: pass - - - return soluteData - - def __addGroupSoluteData(self, soluteData, database, molecule, atom): - """ - Determine the Platts group additivity solute data for the atom `atom` - in the structure `structure`, and add it to the existing solute data - `soluteData`. - """ - - node0 = database.descendTree(molecule, atom, None) - - if node0 is None: - raise KeyError('Node not found in database.') - - # It's possible (and allowed) that items in the tree may not be in the - # library, in which case we need to fall up the tree until we find an - # ancestor that has an entry in the library + except KeyError: pass + + + return soluteData + + def __addGroupSoluteData(self, soluteData, database, molecule, atom): + """ + Determine the Platts group additivity solute data for the atom `atom` + in the structure `structure`, and add it to the existing solute data + `soluteData`. + """ + + node0 = database.descendTree(molecule, atom, None) + + if node0 is None: + raise KeyError('Node not found in database.') + + # It's possible (and allowed) that items in the tree may not be in the + # library, in which case we need to fall up the tree until we find an + # ancestor that has an entry in the library node = node0 - data = node.data - while data is None and node is not None: - node = node.parent + data = node.data + while data is None and node is not None: + node = node.parent if node is None: - raise KeyError('Node has no parent with data in database.') - + raise KeyError('Node has no parent with data in database.') if node is not None: - comment = node.label - while isinstance(data, basestring) and data is not None: - for entry in database.entries.values(): - if entry.label == data: - data = entry.data - comment = entry.label - break - comment = '{0}({1})'.format(database.label, comment) - - # This code prints the hierarchy of the found node; useful for debugging - #result = '' - #while node is not None: - # result = ' -> ' + node + result - # node = database.tree.parent[node] - #print result[4:] - - #if len(thermoData.Tdata.value_si) != len(data.Tdata.value_si) or any([T1 != T2 for T1, T2 in zip(thermoData.Tdata.value_si, data.Tdata.value_si)]): - #raise ThermoError('Cannot add these ThermoData objects due to their having different temperature points.') - - #for i in range(7): + comment = node.label + while isinstance(data, basestring) and data is not None: + for entry in database.entries.values(): + if entry.label == data: + data = entry.data + comment = entry.label + break + comment = '{0}({1})'.format(database.label, comment) + + # This code prints the hierarchy of the found node; useful for debugging + #result = '' + #while node is not None: + # result = ' -> ' + node + result + # node = database.tree.parent[node] + #print result[4:] + + #if len(thermoData.Tdata.value_si) != len(data.Tdata.value_si) or any([T1 != T2 for T1, T2 in zip(thermoData.Tdata.value_si, data.Tdata.value_si)]): + #raise ThermoError('Cannot add these ThermoData objects due to their having different temperature points.') + + #for i in range(7): #thermoData.Cpdata.value_si[i] += data.Cpdata.value_si[i] soluteData.S += data.S soluteData.B += data.B soluteData.E += data.E soluteData.L += data.L soluteData.A += data.A - soluteData.comment += comment + "+" - - return soluteData - -# def addSolventData(self, solventData, database, solvent): -# node = -# -# if node is None: -# raise KeyError('Node not found in database.') -# else -# data = node.data -# solventData.s_h = data.s_h -# solventData.b_h = data.b_h -# solventData.e_h = data.e_h -# solventData.l_h = data.l_h -# solventData.a_h = data.a_h -# solventData.c_h = data.c_h -# solventData.s_g = data.s_g -# solventData.b_g = data.b_g -# solventData.e_g = data.e_g -# solventData.l_g = data.l_g -# solventData.a_g = data.a_g -# solventData.c_g = data.c_g -# -# return solventData - - def calcH(self, soluteData, solventData): - delH = (soluteData.S*solventData.s_h)+(soluteData.B*solventData.b_h)+(soluteData.E*solventData.e_h)+(soluteData.L*solventData.l_h)+(soluteData.A*solventData.a_h)+solventData.c_h - return delH - - def calcG(self, soluteData, solventData): - logk = (soluteData.S*solventData.s_g)+(soluteData.B*solventData.b_g)+(soluteData.E*solventData.e_g)+(soluteData.L*solventData.l_g)+(soluteData.A*solventData.a_g)+solventData.c_g - delG = -8.314*298*0.4343*k - return delG - - - def calcS(self, delG, delH): - delS = (delH-delG)/298 - return delS \ No newline at end of file + soluteData.comment += comment + "+" + + return soluteData + + + def calcH(self, soluteData, solventData): + delH = (soluteData.S*solventData.s_h)+(soluteData.B*solventData.b_h)+(soluteData.E*solventData.e_h)+(soluteData.L*solventData.l_h)+(soluteData.A*solventData.a_h)+solventData.c_h + return delH + + def calcG(self, soluteData, solventData): + logk = (soluteData.S*solventData.s_g)+(soluteData.B*solventData.b_g)+(soluteData.E*solventData.e_g)+(soluteData.L*solventData.l_g)+(soluteData.A*solventData.a_g)+solventData.c_g + delG = -8.314*298*0.4343*k + return delG + + + def calcS(self, delG, delH): + delS = (delH-delG)/298 + return delS + + def getSolvationCorrection(self, soluteData, solventData): + correction = SolvationCorrection(0.0, 0.0, 0.0) + correction.enthalpy = calcH(self, soluteData, solventData) + correction.gibbs = calcG(self, soluteData, solventData) + correction.entropy = calcS(self, correction.gibbs, correction.enthalpy) + return correction \ No newline at end of file From 95774f2672d52a6e574d96cdeef3c2d904e71d39 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Wed, 6 Mar 2013 17:06:55 -0500 Subject: [PATCH 13/55] Fixes to solvation module and calculating solvation correction. --- rmgpy/data/solvation.py | 124 +++++++++++----------------------------- rmgpy/rmg/model.py | 10 ++-- 2 files changed, 38 insertions(+), 96 deletions(-) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index b63565c52aa..6744d33fbb2 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -351,7 +351,6 @@ def save(self, path): """ path = os.path.abspath(path) if not os.path.exists(path): os.mkdir(path) - #self.saveDepository(os.path.join(path, 'depository')) self.saveLibraries(os.path.join(path, 'libraries')) self.saveGroups(os.path.join(path, 'groups')) @@ -379,10 +378,6 @@ def saveGroups(self, path): if not os.path.exists(path): os.mkdir(path) self.groups['abraham'].save(os.path.join(path, 'abraham.py')) self.groups['nonacentered'].save(os.path.join(path, 'nonacentered.py')) - # self.groups['int15'].save(os.path.join(path, 'int15.py')) - # self.groups['ring'].save(os.path.join(path, 'ring.py')) - # self.groups['radical'].save(os.path.join(path, 'radical.py')) - # self.groups['other'].save(os.path.join(path, 'other.py')) def loadOld(self, path): """ @@ -443,31 +438,6 @@ def saveOld(self, path): treestr = os.path.join(groupsPath, 'Abraham_Tree.txt'), libstr = os.path.join(groupsPath, 'Abraham_Library.txt'), ) - # self.groups['gauche'].saveOld( - # dictstr = os.path.join(groupsPath, 'Gauche_Dictionary.txt'), - # treestr = os.path.join(groupsPath, 'Gauche_Tree.txt'), - # libstr = os.path.join(groupsPath, 'Gauche_Library.txt'), - # ) - # self.groups['int15'].saveOld( - # dictstr = os.path.join(groupsPath, '15_Dictionary.txt'), - # treestr = os.path.join(groupsPath, '15_Tree.txt'), - # libstr = os.path.join(groupsPath, '15_Library.txt'), - # ) - # self.groups['radical'].saveOld( - # dictstr = os.path.join(groupsPath, 'Radical_Dictionary.txt'), - # treestr = os.path.join(groupsPath, 'Radical_Tree.txt'), - # libstr = os.path.join(groupsPath, 'Radical_Library.txt'), - # ) - # self.groups['ring'].saveOld( - # dictstr = os.path.join(groupsPath, 'Ring_Dictionary.txt'), - # treestr = os.path.join(groupsPath, 'Ring_Tree.txt'), - # libstr = os.path.join(groupsPath, 'Ring_Library.txt'), - # ) - # self.groups['other'].saveOld( - # dictstr = os.path.join(groupsPath, 'Other_Dictionary.txt'), - # treestr = os.path.join(groupsPath, 'Other_Tree.txt'), - # libstr = os.path.join(groupsPath, 'Other_Library.txt'), - # ) def getSoluteData(self, species): """ @@ -477,24 +447,17 @@ def getSoluteData(self, species): estimation via Platts group additivity. """ soluteData = None - # Check the libraries in order first; return the first successful match - for label in self.libraryOrder: - soluteData = self.getSoluteDataFromLibrary(species, self.libraries[label]) - if soluteData is not None: - soluteData[0].comment = label - break + + # Check the library first + soluteData = self.getSoluteDataFromLibrary(species, self.soluteLibrary) + if soluteData is not None: + soluteData[0].comment = 'solute' else: # Solute not found in any loaded libraries, so estimate soluteData = self.getSoluteDataFromGroups(species) - # Add Cp0 and CpInf values - # Cp0 = species.calculateCp0() - # CpInf = species.calculateCpInf() - data, library, entry = soluteData - # if isinstance(data,SoluteData): - # data.Cp0 = (Cp0,"J/(mol*K)") - # data.CpInf = (CpInf,"J/(mol*K)") # Return the resulting solute parameters - return data + return soluteData + def getAllSoluteData(self, species): """ @@ -503,29 +466,8 @@ def getAllSoluteData(self, species): first, then the group additivity estimate. This method is useful for a generic search job. """ - thermoData = [] - # Data from depository comes first - # thermoData.extend(self.getThermoDataFromDepository(species)) - # Data from libraries comes second - for label in self.libraryOrder: - data = self.getSoluteDataFromLibrary(species, self.libraries[label]) - if data: - data[0].comment = label - soluteData.append(data) - # Last entry is always the estimate from group additivity - soluteData.append(self.getSoluteDataFromGroups(species)) + raise NotImplementedError() - # Add Cp0 and CpInf values - # Cp0 = species.calculateCp0() - # CpInf = species.calculateCpInf() - # for data, library, entry in thermoData: - # if isinstance(data,ThermoData): - # data.Cp0 = (Cp0,"J/(mol*K)") - # data.CpInf = (CpInf,"J/(mol*K)") - - # Return all of the resulting thermo parameters - return thermoData - def getThermoDataFromDepository(self, species): """ Return all possible sets of thermodynamic parameters for a given @@ -582,7 +524,7 @@ def getSoluteDataFromGroups(self, species): soluteData.A /= count soluteData.comment = "Average of {0}".format(" and ".join(comments)) - return soluteData, None, None + return soluteData def estimateSoluteViaGroupAdditivity(self, molecule): """ @@ -698,20 +640,20 @@ def __addGroupSoluteData(self, soluteData, database, molecule, atom): # library, in which case we need to fall up the tree until we find an # ancestor that has an entry in the library node = node0 - data = node.data - while data is None and node is not None: + + while node is not None and node.data is None: node = node.parent if node is None: raise KeyError('Node has no parent with data in database.') - if node is not None: - comment = node.label - while isinstance(data, basestring) and data is not None: - for entry in database.entries.values(): - if entry.label == data: - data = entry.data - comment = entry.label - break - comment = '{0}({1})'.format(database.label, comment) + data = node.data + comment = node.label + while isinstance(data, basestring) and data is not None: + for entry in database.entries.values(): + if entry.label == data: + data = entry.data + comment = entry.label + break + comment = '{0}({1})'.format(database.label, comment) # This code prints the hierarchy of the found node; useful for debugging #result = '' @@ -724,24 +666,24 @@ def __addGroupSoluteData(self, soluteData, database, molecule, atom): #raise ThermoError('Cannot add these ThermoData objects due to their having different temperature points.') #for i in range(7): - #thermoData.Cpdata.value_si[i] += data.Cpdata.value_si[i] - soluteData.S += data.S - soluteData.B += data.B - soluteData.E += data.E - soluteData.L += data.L - soluteData.A += data.A - soluteData.comment += comment + "+" + #thermoData.Cpdata.value_si[i] += data.Cpdata.value_si[i] + soluteData.S += data.S + soluteData.B += data.B + soluteData.E += data.E + soluteData.L += data.L + soluteData.A += data.A + soluteData.comment += comment + "+" return soluteData def calcH(self, soluteData, solventData): - delH = (soluteData.S*solventData.s_h)+(soluteData.B*solventData.b_h)+(soluteData.E*solventData.e_h)+(soluteData.L*solventData.l_h)+(soluteData.A*solventData.a_h)+solventData.c_h + delH = 1000*((soluteData.S*solventData.s_h)+(soluteData.B*solventData.b_h)+(soluteData.E*solventData.e_h)+(soluteData.L*solventData.l_h)+(soluteData.A*solventData.a_h)+solventData.c_h) return delH def calcG(self, soluteData, solventData): - logk = (soluteData.S*solventData.s_g)+(soluteData.B*solventData.b_g)+(soluteData.E*solventData.e_g)+(soluteData.L*solventData.l_g)+(soluteData.A*solventData.a_g)+solventData.c_g - delG = -8.314*298*0.4343*k + logK = (soluteData.S*solventData.s_g)+(soluteData.B*solventData.b_g)+(soluteData.E*solventData.e_g)+(soluteData.L*solventData.l_g)+(soluteData.A*solventData.a_g)+solventData.c_g + delG = -8.314*298*0.4343*logK return delG @@ -751,7 +693,7 @@ def calcS(self, delG, delH): def getSolvationCorrection(self, soluteData, solventData): correction = SolvationCorrection(0.0, 0.0, 0.0) - correction.enthalpy = calcH(self, soluteData, solventData) - correction.gibbs = calcG(self, soluteData, solventData) - correction.entropy = calcS(self, correction.gibbs, correction.enthalpy) + correction.enthalpy = self.calcH(soluteData, solventData) + correction.gibbs = self.calcG(soluteData, solventData) + correction.entropy = self.calcS(correction.gibbs, correction.enthalpy) return correction \ No newline at end of file diff --git a/rmgpy/rmg/model.py b/rmgpy/rmg/model.py index bccc8db2406..2be645f1cf2 100644 --- a/rmgpy/rmg/model.py +++ b/rmgpy/rmg/model.py @@ -101,11 +101,11 @@ def generateThermoData(self, database, thermoClass=NASA): # Add on solvation correction if Species.solventData: logging.info("Making solvent correction for {0} with {1!r}".format(Species.solventName,Species.solventData)) -# soluteData = database.solvation.getSoluteData(Species) -# solventData = database.solvation.getSolventData(label) -# solvation_correction = database.solvation.getSolvationCorrection(self) -# wilhoit.H0 = wilhoit.H0 + solvation_correction.enthalpy -# wilhoit.S0 = wilhoit.S0 + solvation_correction.entropy + soluteData = database.solvation.getSoluteData(self) + solvation_correction = database.solvation.getSolvationCorrection(soluteData, Species.solventData) + #import ipdb; ipdb.set_trace() + wilhoit.S0.setValue(wilhoit.S0.value_si + solvation_correction.entropy) + wilhoit.H0.setValue(wilhoit.H0.value_si + solvation_correction.enthalpy) # Compute E0 by extrapolation to 0 K if self.conformer is None: From fdaae953e60270b29b1a66c83415454a19ae64ae Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 11 Mar 2013 17:46:20 -0400 Subject: [PATCH 14/55] More changes for working with solvents. If equilibrium constant is 0, raise an error. Update solvent name in the main method. Correct the way we add enthalpy and entropy of solvation to Wilhoit.S0 and Wilhoit.H0. --- rmgpy/reaction.py | 2 ++ rmgpy/rmg/main.py | 1 + rmgpy/rmg/model.py | 4 ++-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/rmgpy/reaction.py b/rmgpy/reaction.py index b65782aabee..35454206d71 100644 --- a/rmgpy/reaction.py +++ b/rmgpy/reaction.py @@ -448,6 +448,8 @@ def getEquilibriumConstant(self, T, type='Kc'): K *= P0 ** (len(self.products) - len(self.reactants)) elif type != 'Ka' and type != '': raise ReactionError('Invalid type "%s" passed to Reaction.getEquilibriumConstant(); should be "Ka", "Kc", or "Kp".') + if K == 0: + raise ReactionError('Got equilibrium constant of 0') return K def getEnthalpiesOfReaction(self, Tlist): diff --git a/rmgpy/rmg/main.py b/rmgpy/rmg/main.py index 7edfa9bce0b..67fef9bf0a8 100644 --- a/rmgpy/rmg/main.py +++ b/rmgpy/rmg/main.py @@ -265,6 +265,7 @@ def initialize(self, args): if self.solvent: Species.solventData = self.database.solvation.getSolventData(self.solvent) + Species.solventName = self.solvent logging.info("Setting solvent data for {0}".format(self.solvent)) # Set wall time diff --git a/rmgpy/rmg/model.py b/rmgpy/rmg/model.py index 2be645f1cf2..fca71e88b7d 100644 --- a/rmgpy/rmg/model.py +++ b/rmgpy/rmg/model.py @@ -104,8 +104,8 @@ def generateThermoData(self, database, thermoClass=NASA): soluteData = database.solvation.getSoluteData(self) solvation_correction = database.solvation.getSolvationCorrection(soluteData, Species.solventData) #import ipdb; ipdb.set_trace() - wilhoit.S0.setValue(wilhoit.S0.value_si + solvation_correction.entropy) - wilhoit.H0.setValue(wilhoit.H0.value_si + solvation_correction.enthalpy) + wilhoit.S0.value_si = (wilhoit.S0.value_si + solvation_correction.entropy) + wilhoit.H0.value_si = (wilhoit.H0.value_si + solvation_correction.enthalpy) # Compute E0 by extrapolation to 0 K if self.conformer is None: From 20bf1a5abc3266ca492fd5d5bb56630cd15b50e3 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 18 Mar 2013 17:25:26 -0400 Subject: [PATCH 15/55] Copied simple.pyx to liquid.pyx to start making a liquid reactor. Next commit will change it. --- rmgpy/solver/liquid.pyx | 293 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 rmgpy/solver/liquid.pyx diff --git a/rmgpy/solver/liquid.pyx b/rmgpy/solver/liquid.pyx new file mode 100644 index 00000000000..e700e8f0307 --- /dev/null +++ b/rmgpy/solver/liquid.pyx @@ -0,0 +1,293 @@ +################################################################################ +# +# RMG - Reaction Mechanism Generator +# +# Copyright (c) 2002-2010 Prof. William H. Green (whgreen@mit.edu) and the +# RMG Team (rmg_dev@mit.edu) +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the 'Software'), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# +################################################################################ + +""" +Contains the :class:`SimpleReactor` class, providing a reaction system +consisting of a homogeneous, isothermal, isobaric batch reactor. +""" + +import numpy +cimport numpy +from pydas cimport DASSL +from base cimport ReactionSystem +cimport cython + +import rmgpy.constants as constants +cimport rmgpy.constants as constants +from rmgpy.quantity import Quantity +from rmgpy.quantity cimport ScalarQuantity, ArrayQuantity + +cdef class SimpleReactor(ReactionSystem): + """ + A reaction system consisting of a homogeneous, isothermal, isobaric batch + reactor. These assumptions allow for a number of optimizations that enable + this solver to complete very rapidly, even for large kinetic models. + """ + + cdef public ScalarQuantity T + cdef public ScalarQuantity P + cdef public dict initialMoleFractions + + cdef numpy.ndarray reactantIndices + cdef numpy.ndarray productIndices + cdef numpy.ndarray networkIndices + cdef numpy.ndarray forwardRateCoefficients + cdef numpy.ndarray reverseRateCoefficients + cdef numpy.ndarray networkLeakCoefficients + + def __init__(self, T, P, initialMoleFractions, termination): + ReactionSystem.__init__(self, termination) + self.T = Quantity(T) + self.P = Quantity(P) + self.initialMoleFractions = initialMoleFractions + + # These are helper variables used within the solver + self.reactantIndices = None + self.productIndices = None + self.networkIndices = None + self.forwardRateCoefficients = None + self.reverseRateCoefficients = None + + cpdef initializeModel(self, list coreSpecies, list coreReactions, list edgeSpecies, list edgeReactions, list pdepNetworks=None, atol=1e-16, rtol=1e-8): + """ + Initialize a simulation of the simple reactor using the provided kinetic + model. + """ + + # First call the base class version of the method + # This initializes the attributes declared in the base class + ReactionSystem.initializeModel(self, coreSpecies, coreReactions, edgeSpecies, edgeReactions, pdepNetworks, atol, rtol) + + cdef int numCoreSpecies, numCoreReactions, numEdgeSpecies, numEdgeReactions, numPdepNetworks + cdef int i, j, l, index + cdef dict speciesIndex, reactionIndex + cdef numpy.ndarray[numpy.int_t, ndim=2] reactantIndices, productIndices, networkIndices + cdef numpy.ndarray[numpy.float64_t, ndim=1] forwardRateCoefficients, reverseRateCoefficients, networkLeakCoefficients + + pdepNetworks = pdepNetworks or [] + + numCoreSpecies = len(coreSpecies) + numCoreReactions = len(coreReactions) + numEdgeSpecies = len(edgeSpecies) + numEdgeReactions = len(edgeReactions) + numPdepNetworks = len(pdepNetworks) + + # Assign an index to each species (core first, then edge) + speciesIndex = {} + for index, spec in enumerate(coreSpecies): + speciesIndex[spec] = index + for index, spec in enumerate(edgeSpecies): + speciesIndex[spec] = index + numCoreSpecies + # Assign an index to each reaction (core first, then edge) + reactionIndex = {} + for index, rxn in enumerate(coreReactions): + reactionIndex[rxn] = index + for index, rxn in enumerate(edgeReactions): + reactionIndex[rxn] = index + numCoreReactions + + # Generate reactant and product indices + # Generate forward and reverse rate coefficients k(T,P) + reactantIndices = -numpy.ones((numCoreReactions + numEdgeReactions, 3), numpy.int ) + productIndices = -numpy.ones_like(reactantIndices) + forwardRateCoefficients = numpy.zeros((numCoreReactions + numEdgeReactions), numpy.float64) + reverseRateCoefficients = numpy.zeros_like(forwardRateCoefficients) + for rxnList in [coreReactions, edgeReactions]: + for rxn in rxnList: + j = reactionIndex[rxn] + forwardRateCoefficients[j] = rxn.getRateCoefficient(self.T.value_si, self.P.value_si) + reverseRateCoefficients[j] = forwardRateCoefficients[j] / rxn.getEquilibriumConstant(self.T.value_si) + for l, spec in enumerate(rxn.reactants): + i = speciesIndex[spec] + reactantIndices[j,l] = i + for l, spec in enumerate(rxn.products): + i = speciesIndex[spec] + productIndices[j,l] = i + + networkIndices = -numpy.ones((numPdepNetworks, 3), numpy.int ) + networkLeakCoefficients = numpy.zeros((numPdepNetworks), numpy.float64) + for j, network in enumerate(pdepNetworks): + networkLeakCoefficients[j] = network.getLeakCoefficient(self.T.value_si, self.P.value_si) + for l, spec in enumerate(network.source): + i = speciesIndex[spec] + networkIndices[j,l] = i + + self.reactantIndices = reactantIndices + self.productIndices = productIndices + self.forwardRateCoefficients = forwardRateCoefficients + self.reverseRateCoefficients = reverseRateCoefficients + self.networkIndices = networkIndices + self.networkLeakCoefficients = networkLeakCoefficients + + # Set initial conditions + t0 = 0.0 + y0 = numpy.zeros((numCoreSpecies), numpy.float64) + for spec, moleFrac in self.initialMoleFractions.iteritems(): + y0[speciesIndex[spec]] = moleFrac * (self.P.value_si / constants.R / self.T.value_si) + self.coreSpeciesConcentrations[speciesIndex[spec]] = y0[speciesIndex[spec]] + + # Initialize the model + dydt0 = - self.residual(t0, y0, numpy.zeros((numCoreSpecies), numpy.float64))[0] + DASSL.initialize(self, t0, y0, dydt0, atol, rtol) + + cpdef writeWorksheetHeader(self, worksheet): + """ + Write some descriptive information about the reaction system to the + first two rows of the given `worksheet`. + """ + import xlwt + style0 = xlwt.easyxf('font: bold on') + worksheet.write(0, 0, 'Simple Reactor', style0) + worksheet.write(1, 0, 'T = {0:g} K, P = {1:g} bar'.format(self.T.value_si, self.P.value_si/1e5)) + + @cython.boundscheck(False) + def residual(self, double t, numpy.ndarray[numpy.float64_t, ndim=1] y, numpy.ndarray[numpy.float64_t, ndim=1] dydt): + + """ + Return the residual function for the governing DAE system for the + simple reaction system. + """ + cdef numpy.ndarray[numpy.int_t, ndim=2] ir, ip, inet + cdef numpy.ndarray[numpy.float64_t, ndim=1] res, kf, kr, knet + cdef int numCoreSpecies, numCoreReactions, numEdgeSpecies, numEdgeReactions, numPdepNetworks + cdef int j, first, second, third + cdef double k, reactionRate + cdef numpy.ndarray[numpy.float64_t, ndim=1] coreSpeciesConcentrations, coreSpeciesRates, coreReactionRates, edgeSpeciesRates, edgeReactionRates, networkLeakRates + + res = numpy.zeros(y.shape[0], numpy.float64) + + ir = self.reactantIndices + ip = self.productIndices + kf = self.forwardRateCoefficients + kr = self.reverseRateCoefficients + inet = self.networkIndices + knet = self.networkLeakCoefficients + + numCoreSpecies = len(self.coreSpeciesRates) + numCoreReactions = len(self.coreReactionRates) + numEdgeSpecies = len(self.edgeSpeciesRates) + numEdgeReactions = len(self.edgeReactionRates) + numPdepNetworks = len(self.networkLeakRates) + + coreSpeciesConcentrations = numpy.zeros_like(self.coreSpeciesConcentrations) + coreSpeciesRates = numpy.zeros_like(self.coreSpeciesRates) + coreReactionRates = numpy.zeros_like(self.coreReactionRates) + edgeSpeciesRates = numpy.zeros_like(self.edgeSpeciesRates) + edgeReactionRates = numpy.zeros_like(self.edgeReactionRates) + networkLeakRates = numpy.zeros_like(self.networkLeakRates) + + for j in range(y.shape[0]): + coreSpeciesConcentrations[j] = y[j] + + for j in range(ir.shape[0]): + k = kf[j] + if ir[j,0] >= numCoreSpecies or ir[j,1] >= numCoreSpecies or ir[j,2] >= numCoreSpecies: + reactionRate = 0.0 + elif ir[j,1] == -1: # only one reactant + reactionRate = k * y[ir[j,0]] + elif ir[j,2] == -1: # only two reactants + reactionRate = k * y[ir[j,0]] * y[ir[j,1]] + else: # three reactants!! (really?) + reactionRate = k * y[ir[j,0]] * y[ir[j,1]] * y[ir[j,2]] + k = kr[j] + if ip[j,0] >= numCoreSpecies or ip[j,1] >= numCoreSpecies or ip[j,2] >= numCoreSpecies: + pass + elif ip[j,1] == -1: # only one reactant + reactionRate -= k * y[ip[j,0]] + elif ip[j,2] == -1: # only two reactants + reactionRate -= k * y[ip[j,0]] * y[ip[j,1]] + else: # three reactants!! (really?) + reactionRate -= k * y[ip[j,0]] * y[ip[j,1]] * y[ip[j,2]] + + # Set the reaction and species rates + if j < numCoreReactions: + # The reaction is a core reaction + coreReactionRates[j] = reactionRate + + # Add/substract the total reaction rate from each species rate + # Since it's a core reaction we know that all of its reactants + # and products are core species + first = ir[j,0] + coreSpeciesRates[first] -= reactionRate + second = ir[j,1] + if second != -1: + coreSpeciesRates[second] -= reactionRate + third = ir[j,2] + if third != -1: + coreSpeciesRates[third] -= reactionRate + first = ip[j,0] + coreSpeciesRates[first] += reactionRate + second = ip[j,1] + if second != -1: + coreSpeciesRates[second] += reactionRate + third = ip[j,2] + if third != -1: + coreSpeciesRates[third] += reactionRate + + else: + # The reaction is an edge reaction + edgeReactionRates[j-numCoreReactions] = reactionRate + + # Add/substract the total reaction rate from each species rate + # Since it's an edge reaction its reactants and products could + # be either core or edge species + # We're only interested in the edge species + first = ir[j,0] + if first >= numCoreSpecies: edgeSpeciesRates[first-numCoreSpecies] -= reactionRate + second = ir[j,1] + if second != -1: + if second >= numCoreSpecies: edgeSpeciesRates[second-numCoreSpecies] -= reactionRate + third = ir[j,2] + if third != -1: + if third >= numCoreSpecies: edgeSpeciesRates[third-numCoreSpecies] -= reactionRate + first = ip[j,0] + if first >= numCoreSpecies: edgeSpeciesRates[first-numCoreSpecies] += reactionRate + second = ip[j,1] + if second != -1: + if second >= numCoreSpecies: edgeSpeciesRates[second-numCoreSpecies] += reactionRate + third = ip[j,2] + if third != -1: + if third >= numCoreSpecies: edgeSpeciesRates[third-numCoreSpecies] += reactionRate + + for j in range(inet.shape[0]): + k = knet[j] + if inet[j,1] == -1: # only one reactant + reactionRate = k * y[inet[j,0]] + elif inet[j,2] == -1: # only two reactants + reactionRate = k * y[inet[j,0]] * y[inet[j,1]] + else: # three reactants!! (really?) + reactionRate = k * y[inet[j,0]] * y[inet[j,1]] * y[inet[j,2]] + networkLeakRates[j] = reactionRate + + self.coreSpeciesConcentrations = coreSpeciesConcentrations + self.coreSpeciesRates = coreSpeciesRates + self.coreReactionRates = coreReactionRates + self.edgeSpeciesRates = edgeSpeciesRates + self.edgeReactionRates = edgeReactionRates + self.networkLeakRates = networkLeakRates + + res = coreSpeciesRates - dydt + return res, 0 From 7ddef7fb3a9ecab5965afb5774ea1527f7e3376d Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 18 Mar 2013 17:28:00 -0400 Subject: [PATCH 16/55] Turned solver/liquid.pyx into a Liquid Phase Batch Reactor. There is no pressure, and you initialize it with species concentrations (IN SI UNITS!) not mole fractions. It looks like the ODEs were already neglecting volume changes due to stoichiometry, so we don't need to change that here. --- rmgpy/solver/liquid.pyx | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/rmgpy/solver/liquid.pyx b/rmgpy/solver/liquid.pyx index e700e8f0307..5464bb7cfc3 100644 --- a/rmgpy/solver/liquid.pyx +++ b/rmgpy/solver/liquid.pyx @@ -41,16 +41,15 @@ cimport rmgpy.constants as constants from rmgpy.quantity import Quantity from rmgpy.quantity cimport ScalarQuantity, ArrayQuantity -cdef class SimpleReactor(ReactionSystem): +cdef class LiquidReactor(ReactionSystem): """ - A reaction system consisting of a homogeneous, isothermal, isobaric batch + A reaction system consisting of a homogeneous, isothermal, constant volume batch reactor. These assumptions allow for a number of optimizations that enable this solver to complete very rapidly, even for large kinetic models. """ cdef public ScalarQuantity T - cdef public ScalarQuantity P - cdef public dict initialMoleFractions + cdef public dict initialConcentrations cdef numpy.ndarray reactantIndices cdef numpy.ndarray productIndices @@ -59,11 +58,10 @@ cdef class SimpleReactor(ReactionSystem): cdef numpy.ndarray reverseRateCoefficients cdef numpy.ndarray networkLeakCoefficients - def __init__(self, T, P, initialMoleFractions, termination): + def __init__(self, T, initialConcentrations, termination): ReactionSystem.__init__(self, termination) self.T = Quantity(T) - self.P = Quantity(P) - self.initialMoleFractions = initialMoleFractions + self.initialConcentrations = initialConcentrations # should be passed in SI # These are helper variables used within the solver self.reactantIndices = None @@ -145,8 +143,8 @@ cdef class SimpleReactor(ReactionSystem): # Set initial conditions t0 = 0.0 y0 = numpy.zeros((numCoreSpecies), numpy.float64) - for spec, moleFrac in self.initialMoleFractions.iteritems(): - y0[speciesIndex[spec]] = moleFrac * (self.P.value_si / constants.R / self.T.value_si) + for spec, conc in self.initialConcentrations.iteritems(): + y0[speciesIndex[spec]] = conc self.coreSpeciesConcentrations[speciesIndex[spec]] = y0[speciesIndex[spec]] # Initialize the model @@ -160,8 +158,8 @@ cdef class SimpleReactor(ReactionSystem): """ import xlwt style0 = xlwt.easyxf('font: bold on') - worksheet.write(0, 0, 'Simple Reactor', style0) - worksheet.write(1, 0, 'T = {0:g} K, P = {1:g} bar'.format(self.T.value_si, self.P.value_si/1e5)) + worksheet.write(0, 0, 'Liquid Reactor', style0) + worksheet.write(1, 0, 'T = {0:g} K'.format(self.T.value_si)) @cython.boundscheck(False) def residual(self, double t, numpy.ndarray[numpy.float64_t, ndim=1] y, numpy.ndarray[numpy.float64_t, ndim=1] dydt): From e300265f9c30c207f68adc2e21f28194460d9989 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 25 Mar 2013 17:42:02 -0400 Subject: [PATCH 17/55] setup.py cythonizes the new liquid reactor --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index fcab3bb0934..7f4c303d6da 100644 --- a/setup.py +++ b/setup.py @@ -117,6 +117,7 @@ def getSolverExtensionModules(): return [ Extension('rmgpy.solver.base', ['rmgpy/solver/base.pyx'], include_dirs=['.']), Extension('rmgpy.solver.simple', ['rmgpy/solver/simple.pyx'], include_dirs=['.']), + Extension('rmgpy.solver.liquid', ['rmgpy/solver/liquid.pyx'], include_dirs=['.']), ] ################################################################################ From 59ec1333b08572a8539b1cede2d56f75b8c957ce Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 18 Mar 2013 17:29:40 -0400 Subject: [PATCH 18/55] Make "liquidReactor" field in input file. Should be able to use it something like: # Reaction systems liquidReactor( temperature=(750,'K'), initialConcentrations={ "ethane": (1.0,'mol/L') }, terminationConversion={ 'ethane': 0.9, }, terminationTime=(1e6,'s'), ) ...but don't forget the solvation( solvent='water' ) --- rmgpy/rmg/input.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/rmgpy/rmg/input.py b/rmgpy/rmg/input.py index 7fe87315a31..50cbab9eac1 100644 --- a/rmgpy/rmg/input.py +++ b/rmgpy/rmg/input.py @@ -39,6 +39,7 @@ from rmgpy.solver.base import TerminationTime, TerminationConversion from rmgpy.solver.simple import SimpleReactor +from rmgpy.solver.liquid import LiquidReactor from model import * @@ -123,6 +124,27 @@ def simpleReactor(temperature, pressure, initialMoleFractions, terminationConver system = SimpleReactor(T, P, initialMoleFractions, termination) rmg.reactionSystems.append(system) + +# Reaction systems +def liquidReactor(temperature, initialConcentrations, terminationConversion=None, terminationTime=None): + logging.debug('Found LiquidReactor reaction system') + T = Quantity(temperature) + for spec,conc in initialConcentrations.iteritems(): + concentration = Quantity(conc) + # check the dimensions are ok + # convert to mol/m^3 (or something numerically nice? or must it be SI) + initialConcentrations[spec] = concentration.value_si + termination = [] + if terminationConversion is not None: + for spec, conv in terminationConversion.iteritems(): + termination.append(TerminationConversion(speciesDict[spec], conv)) + if terminationTime is not None: + termination.append(TerminationTime(Quantity(terminationTime))) + if len(termination) == 0: + raise InputError('No termination conditions specified for reaction system #{0}.'.format(len(rmg.reactionSystems)+2)) + system = LiquidReactor(T, initialConcentrations, termination) + rmg.reactionSystems.append(system) + def simulator(atol, rtol): rmg.absoluteTolerance = atol rmg.relativeTolerance = rtol @@ -237,6 +259,7 @@ def readInputFile(path, rmg0): 'InChI': InChI, 'adjacencyList': adjacencyList, 'simpleReactor': simpleReactor, + 'liquidReactor': liquidReactor, 'simulator': simulator, 'solvation': solvation, 'model': model, From 31cb1c4564f87477a4f093ee7fd959f7a2537cac Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 25 Mar 2013 17:45:32 -0400 Subject: [PATCH 19/55] Made a liquid_phase example input file. Settings might not make much sense yet, but it's a start. --- examples/rmg/liquid_phase/input.py | 51 ++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 examples/rmg/liquid_phase/input.py diff --git a/examples/rmg/liquid_phase/input.py b/examples/rmg/liquid_phase/input.py new file mode 100644 index 00000000000..afc697ce702 --- /dev/null +++ b/examples/rmg/liquid_phase/input.py @@ -0,0 +1,51 @@ +# Data sources +database( + thermoLibraries = ['primaryThermoLibrary'], + reactionLibraries = [], + seedMechanisms = [], + kineticsDepositories = ['training'], + kineticsFamilies = ['!Intra_Disproportionation'], + kineticsEstimator = 'rate rules', +) + +# List of species +species( + label='ethane', + reactive=True, + structure=SMILES("CC"), +) + +# Reaction systems +liquidReactor( + temperature=(750,'K'), + initialConcentrations={ + "ethane": (1.0,'mol/L') + }, + terminationConversion={ + 'ethane': 0.9, + }, + terminationTime=(1e6,'s'), +) + +solvation( + solvent='water' +) + +simulator( + atol=1e-16, + rtol=1e-8, +) + +model( + toleranceKeepInEdge=0.0, + toleranceMoveToCore=0.1, + toleranceInterruptSimulation=0.1, + maximumEdgeSpecies=100000 +) + +options( + units='si', + saveRestartPeriod=None, + drawMolecules=False, + generatePlots=False, +) From 882667612d78dfe28305c622dcdbdd3097b54394 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 25 Mar 2013 17:40:20 -0400 Subject: [PATCH 20/55] Converting initialMoleFractions keys from names into species objects moved. After reading an input file, the dictionary storing initial mole fractions has its keys changed from species names into species objects. This is now delegated to the reactonSystem object, so that liquidReactor and simpleReactor can do it differently (liquidReactors don't have initial mole fractions). --- rmgpy/rmg/input.py | 6 ++---- rmgpy/solver/liquid.pyx | 10 ++++++++++ rmgpy/solver/simple.pyx | 10 ++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/rmgpy/rmg/input.py b/rmgpy/rmg/input.py index 50cbab9eac1..9e9ffa300a1 100644 --- a/rmgpy/rmg/input.py +++ b/rmgpy/rmg/input.py @@ -277,11 +277,9 @@ def readInputFile(path, rmg0): finally: f.close() + # convert keys from species names into species objects. for reactionSystem in rmg.reactionSystems: - initialMoleFractions = {} - for label, moleFrac in reactionSystem.initialMoleFractions.iteritems(): - initialMoleFractions[speciesDict[label]] = moleFrac - reactionSystem.initialMoleFractions = initialMoleFractions + reactionSystem.convertInitalKeysToSpeciesObjects(speciesDict) logging.info('') diff --git a/rmgpy/solver/liquid.pyx b/rmgpy/solver/liquid.pyx index 5464bb7cfc3..1de0c31300f 100644 --- a/rmgpy/solver/liquid.pyx +++ b/rmgpy/solver/liquid.pyx @@ -69,6 +69,16 @@ cdef class LiquidReactor(ReactionSystem): self.networkIndices = None self.forwardRateCoefficients = None self.reverseRateCoefficients = None + + def convertInitalKeysToSpeciesObjects(self, speciesDict): + """ + Convert the initialConcentrations dictionary from species names into species objects, + using the given dictionary of species. + """ + initialConcentrations = {} + for label, moleFrac in self.initialConcentrations.iteritems(): + initialConcentrations[speciesDict[label]] = moleFrac + self.initialConcentrations = initialConcentrations cpdef initializeModel(self, list coreSpecies, list coreReactions, list edgeSpecies, list edgeReactions, list pdepNetworks=None, atol=1e-16, rtol=1e-8): """ diff --git a/rmgpy/solver/simple.pyx b/rmgpy/solver/simple.pyx index e700e8f0307..6175474f240 100644 --- a/rmgpy/solver/simple.pyx +++ b/rmgpy/solver/simple.pyx @@ -71,6 +71,16 @@ cdef class SimpleReactor(ReactionSystem): self.networkIndices = None self.forwardRateCoefficients = None self.reverseRateCoefficients = None + + def convertInitalKeysToSpeciesObjects(self, speciesDict): + """ + Convert the initialMoleFractions dictionary from species names into species objects, + using the given dictionary of species. + """ + initialMoleFractions = {} + for label, moleFrac in self.initialMoleFractions.iteritems(): + initialMoleFractions[speciesDict[label]] = moleFrac + self.initialMoleFractions = initialMoleFractions cpdef initializeModel(self, list coreSpecies, list coreReactions, list edgeSpecies, list edgeReactions, list pdepNetworks=None, atol=1e-16, rtol=1e-8): """ From 70e4b417b04ca55b6ad8a343bca0ea4a6b116799 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 25 Mar 2013 17:41:42 -0400 Subject: [PATCH 21/55] Liquid reactors have arbitrary high 1000 Bar pressure for kinetics calculations. This should ensure the high pressure limit for any pressure-dependent reactions. Perhaps there's a better way to do this..? --- rmgpy/solver/liquid.pyx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rmgpy/solver/liquid.pyx b/rmgpy/solver/liquid.pyx index 1de0c31300f..c5af3633a86 100644 --- a/rmgpy/solver/liquid.pyx +++ b/rmgpy/solver/liquid.pyx @@ -49,6 +49,8 @@ cdef class LiquidReactor(ReactionSystem): """ cdef public ScalarQuantity T + cdef public ScalarQuantity P + cdef public dict initialConcentrations cdef numpy.ndarray reactantIndices @@ -61,6 +63,7 @@ cdef class LiquidReactor(ReactionSystem): def __init__(self, T, initialConcentrations, termination): ReactionSystem.__init__(self, termination) self.T = Quantity(T) + self.P = Quantity(100000.,'kPa') # Arbitrary high pressure (1000 Bar) to get reactions in the high-pressure limit! self.initialConcentrations = initialConcentrations # should be passed in SI # These are helper variables used within the solver From dcf76ee635b4602c892872657fe516716e77c9d7 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 25 Mar 2013 17:47:44 -0400 Subject: [PATCH 22/55] Tests solvation module for calculating Abraham solute descriptors and enthalpy of solvation. --- rmgpy/data/solvationTest.py | 121 ++++++++++++++++++++++++------------ 1 file changed, 80 insertions(+), 41 deletions(-) diff --git a/rmgpy/data/solvationTest.py b/rmgpy/data/solvationTest.py index 0464b62548f..dfbe887afa7 100644 --- a/rmgpy/data/solvationTest.py +++ b/rmgpy/data/solvationTest.py @@ -12,47 +12,86 @@ ################################################### class TestSoluteDatabase(TestCase): - - def runTest(self): - pass - - def testSoluteGeneration(self): - - self.database = SoluteDatabase() - self.database.load(os.path.join(settings['database.directory'], 'thermo')) - - self.testCases = [ - - # from RMG-Java test run - ['octane', 'CCCCCCCC', 0.127, 0.085, 0.04, 3.766, 0.0030 , 1.2358], - ['water', 'O', 0.524, 0.378, 0.309, 0.802, 0.348, 0.1673], - - - # from RMG-Java (Jalan et al supplementary data): - #['1,2-ethanediol', 'C(CO)O', 0.823, 0.685, 0.327, 2.572, 0.693, None] - ['1-decanol', 'C(CCCCCCCCC)O', 0.449, 0.385, 0.205, 5.614, 0.348, None], - #['acetylaldehyde', 'CC=O', 0.622, 0.423, 0.171, 1.415, 0.0030, 0.4061], - ['acetic acid', 'C(C)(=O)O', 0.508, 0.411, 0.152, 1.873, 0.591, None], - #['anthracene', 'C2=CC=CC3=CC1=CC=CC=C1C=C23', 1.181, 0.181, 1.648, 7.316, 0.003, None] - #['dimethyl ether'] - - # from Abraham: - #['cyclopentane', 'C1CCCC1', 0.10, 0, 0.263, 2.477, 0, None], #['methylcyclopentane', 'C1(CCCC1)C', 0.10, 0, 0.225, 2.816, 0], - #['cyclohexane', 'C1CCCCC1', 0.10, 0, 0.305, 2.964, 0] - ] - - for name, smiles, S, B, E, L, A, V in self.testCases: - species = Species(molecule=[Molecule(SMILES=smiles)]) - soluteData = self.database.getSoluteData(Species(molecule=[species.molecule[0]])) - print name, soluteData - print self.assertAlmostEqual(soluteData.S, S) - print self.assertAlmostEqual(soluteData.B, B) - print self.assertAlmostEqual(soluteData.E, E) - print self.assertAlmostEqual(soluteData.L, L) - print self.assertAlmostEqual(soluteData.A, A) - + + def runTest(self): + pass + + def testSoluteGeneration(self): + + self.database = SolvationDatabase() + self.database.load(os.path.join(settings['database.directory'], 'solvation')) + + self.testCases = [ + # from RMG-Java test runs by RWest (mostly in agreement with Jalan et. al. supplementary data) + # ['octane', 'CCCCCCCC', 0.127, 0.085, 0.04, 3.766, 0.0030 , 1.2358], +# ['water', 'O', 0.524, 0.378, 0.309, 0.802, 0.348, 0.1673], +# ['chrysene', 'C1=CC=CC3=C1C=CC4=C2C=CC=CC2=CC=C34', 1.603, 0.317, 2.864, 10.222, 0.003, None], +# ['anthracene', 'C2=CC=CC3=CC1=CC=CC=C1C=C23', 1.261, 0.257, 2.128, 7.796, 0.003, None], + ['1,2-ethanediol', 'C(CO)O', 0.823, 0.685, 0.327, 2.572, 0.693, None], + # ['1-decanol', 'C(CCCCCCCCC)O', 0.449, 0.385, 0.205, 5.614, 0.348, None], +# ['acetylaldehyde', 'CC=O', 0.622, 0.423, 0.171, 1.415, 0.0030, 0.4061], +# ['acetic acid', 'C(C)(=O)O', 0.508, 0.411, 0.152, 1.873, 0.591, None], +# ['ethyloctadecanoate', 'C(C)OC(CCCCCCCCCCCCCCCCC)=O', 0.558, 0.424, 0.08, 10.344, 0.003, None], +# ['naphthalene', 'C1=CC=CC2=CC=CC=C12', 0.919, 0.197, 1.392, 5.37, 0.003, None], +# ['phenol', 'C1(=CC=CC=C1)O', 0.875, 0.433, 0.829, 3.771, 0.546, None], +# ['m-cresol', 'C1=C(C=CC=C1O)C', 0.851, 0.429, 0.837, 4.247, 0.546, None], +# ['m-hydroxybenzaldehyde', 'OC1=CC=CC(=C1)C=O', 1.346, 0.767, 0.968, 4.89, 0.546, None], +# ['ethylbenzene', 'C(C)C1=CC=CC=C1', 0.553, 0.133, 0.664, 3.919, 0.003, None], +# ['toluene', 'C1(=CC=CC=C1)C', 0.553, 0.133, 0.664, 3.42, 0.003, None], +# ['1-butene', 'C=CCC', 0.167, 0.108, 0.167, 1.663, 0.003, None], +# ['g-decalactone', 'C(C1OC(=O)CC1)CCCCC', 0.669, 0.428, 0.273, 5.482, 0.003, None], + + #['dimethyl ether'] + ] + + for name, smiles, S, B, E, L, A, V in self.testCases: + species = Species(molecule=[Molecule(SMILES=smiles)]) + soluteData = self.database.getSoluteDataFromGroups(Species(molecule=[species.molecule[0]])) + print name, soluteData + print self.assertAlmostEqual(soluteData.S, S) + print self.assertAlmostEqual(soluteData.B, B) + print self.assertAlmostEqual(soluteData.E, E) + print self.assertAlmostEqual(soluteData.L, L) + print self.assertAlmostEqual(soluteData.A, A) + + def testCorrectionGeneration(self): + self.database = SolvationDatabase() + self.database.load(os.path.join(settings['database.directory'], 'solvation')) + self.testCases = [ + # solventName, soluteName, soluteSMILES, Hsolv, Gsolv, Ssolv + ['water', 'methane', 'C', -12000, None, None], + ['water', 'octane', 'CCCCCCCC', -36000, None, None], + ['water', '1,2-ethanediol', 'C(CO)O', -77300, None, None], + ['water', 'acetic acid', 'C(C)(=O)O', -56500, None, None], + ['water', 'naphthalene', 'C1=CC=CC2=CC=CC=C12', -42800, None, None], + ['water', 'm-hydroxybenzaldehyde', 'OC1=CC=CC(=C1)C=O', -70700, None, None], + ['water', 'ethylbenzene', 'C(C)C1=CC=CC=C1', -39400, None, None], + ['water', 'toluene', 'C1(=CC=CC=C1)C', -32400, None, None], + ['water', 'ethane', 'CC', -17900, None, None], + ['water', 'propane', 'CCC', -20400, None, None], + ['water', 'ethene', 'C=C', -13700, None, None], + ['water', 'propene', 'CC=C', -21600, None, None], + ['water', 'dimethyl ether', 'COC', -34000, None, None], + ['water', 'diethyl ether', 'C(C)OCC', -45300, None, None], + ['water', 'tetrahydrofuran', 'C1CCOC1', -47300, None, None], + ['water', '1,4-dioxane', 'C1COCCO1', -48400, None, None], + ['water', 'methanol', 'CO', -52000, None, None], + ['water', 'ethanol', 'C(C)O', -50600, None, None], + ['water', '1,2 propanediol', 'C(CO)CO', -81100, None, None], + ] + + for solventName, soluteName, smiles, H, G, S in self.testCases: + species = Species(molecule=[Molecule(SMILES=smiles)]) + soluteData = self.database.getSoluteDataFromGroups(Species(molecule=[species.molecule[0]])) + solventData = self.database.getSolventData(solventName) + solvationCorrection = self.database.getSolvationCorrection(soluteData, solventData) + #print("Enthalpy of solvation for {0} in {1} is {2} J/mol".format(soluteName, solventName, solvationCorrection.enthalpy)) + print("{0} {1} {2}".format(soluteName, H, solvationCorrection.enthalpy)) + #self.assertAlmostEqual(solvationCorrection.enthalpy/10000., H/10000.,0) #0 decimal place, in 10kJ. + ##################################################### if __name__ == '__main__': - myTest = TestSoluteDatabase() - myTest.testSoluteGeneration() \ No newline at end of file + myTest = TestSoluteDatabase() + #myTest.testSoluteGeneration() + myTest.testCorrectionGeneration() \ No newline at end of file From 37b15d991b7d41b73b0aa6e2ee36861261753449 Mon Sep 17 00:00:00 2001 From: Richard West Date: Tue, 26 Mar 2013 19:07:32 -0400 Subject: [PATCH 23/55] Replaced liquid.pyx with modified simple.pyx This new one treats y as the mole number (not concentration) and has analytical Jacobian. I will then apply the changes again to turn it back into a LiquidReactor. --- rmgpy/solver/liquid.pyx | 461 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 426 insertions(+), 35 deletions(-) diff --git a/rmgpy/solver/liquid.pyx b/rmgpy/solver/liquid.pyx index c5af3633a86..eb0e97c777e 100644 --- a/rmgpy/solver/liquid.pyx +++ b/rmgpy/solver/liquid.pyx @@ -41,30 +41,30 @@ cimport rmgpy.constants as constants from rmgpy.quantity import Quantity from rmgpy.quantity cimport ScalarQuantity, ArrayQuantity -cdef class LiquidReactor(ReactionSystem): +cdef class SimpleReactor(ReactionSystem): """ - A reaction system consisting of a homogeneous, isothermal, constant volume batch + A reaction system consisting of a homogeneous, isothermal, isobaric batch reactor. These assumptions allow for a number of optimizations that enable this solver to complete very rapidly, even for large kinetic models. """ cdef public ScalarQuantity T cdef public ScalarQuantity P - - cdef public dict initialConcentrations + cdef public dict initialMoleFractions - cdef numpy.ndarray reactantIndices - cdef numpy.ndarray productIndices - cdef numpy.ndarray networkIndices - cdef numpy.ndarray forwardRateCoefficients - cdef numpy.ndarray reverseRateCoefficients - cdef numpy.ndarray networkLeakCoefficients + cdef public numpy.ndarray reactantIndices + cdef public numpy.ndarray productIndices + cdef public numpy.ndarray networkIndices + cdef public numpy.ndarray forwardRateCoefficients + cdef public numpy.ndarray reverseRateCoefficients + cdef public numpy.ndarray networkLeakCoefficients + cdef public numpy.ndarray jacobianMatrix - def __init__(self, T, initialConcentrations, termination): + def __init__(self, T, P, initialMoleFractions, termination): ReactionSystem.__init__(self, termination) self.T = Quantity(T) - self.P = Quantity(100000.,'kPa') # Arbitrary high pressure (1000 Bar) to get reactions in the high-pressure limit! - self.initialConcentrations = initialConcentrations # should be passed in SI + self.P = Quantity(P) + self.initialMoleFractions = initialMoleFractions # These are helper variables used within the solver self.reactantIndices = None @@ -72,16 +72,17 @@ cdef class LiquidReactor(ReactionSystem): self.networkIndices = None self.forwardRateCoefficients = None self.reverseRateCoefficients = None + self.jacobianMatrix = None def convertInitalKeysToSpeciesObjects(self, speciesDict): """ - Convert the initialConcentrations dictionary from species names into species objects, + Convert the initialMoleFractions dictionary from species names into species objects, using the given dictionary of species. """ - initialConcentrations = {} - for label, moleFrac in self.initialConcentrations.iteritems(): - initialConcentrations[speciesDict[label]] = moleFrac - self.initialConcentrations = initialConcentrations + initialMoleFractions = {} + for label, moleFrac in self.initialMoleFractions.iteritems(): + initialMoleFractions[speciesDict[label]] = moleFrac + self.initialMoleFractions = initialMoleFractions cpdef initializeModel(self, list coreSpecies, list coreReactions, list edgeSpecies, list edgeReactions, list pdepNetworks=None, atol=1e-16, rtol=1e-8): """ @@ -95,6 +96,7 @@ cdef class LiquidReactor(ReactionSystem): cdef int numCoreSpecies, numCoreReactions, numEdgeSpecies, numEdgeReactions, numPdepNetworks cdef int i, j, l, index + cdef double V cdef dict speciesIndex, reactionIndex cdef numpy.ndarray[numpy.int_t, ndim=2] reactantIndices, productIndices, networkIndices cdef numpy.ndarray[numpy.float64_t, ndim=1] forwardRateCoefficients, reverseRateCoefficients, networkLeakCoefficients @@ -156,9 +158,12 @@ cdef class LiquidReactor(ReactionSystem): # Set initial conditions t0 = 0.0 y0 = numpy.zeros((numCoreSpecies), numpy.float64) - for spec, conc in self.initialConcentrations.iteritems(): - y0[speciesIndex[spec]] = conc - self.coreSpeciesConcentrations[speciesIndex[spec]] = y0[speciesIndex[spec]] + for spec, moleFrac in self.initialMoleFractions.iteritems(): + y0[speciesIndex[spec]] = moleFrac + # Use ideal gas law to compute volume + V = constants.R * self.T.value_si * numpy.sum(y0) / self.P.value_si + for j in range(y0.shape[0]): + self.coreSpeciesConcentrations[j] = y0[j] / V # Initialize the model dydt0 = - self.residual(t0, y0, numpy.zeros((numCoreSpecies), numpy.float64))[0] @@ -171,8 +176,8 @@ cdef class LiquidReactor(ReactionSystem): """ import xlwt style0 = xlwt.easyxf('font: bold on') - worksheet.write(0, 0, 'Liquid Reactor', style0) - worksheet.write(1, 0, 'T = {0:g} K'.format(self.T.value_si)) + worksheet.write(0, 0, 'Simple Reactor', style0) + worksheet.write(1, 0, 'T = {0:g} K, P = {1:g} bar'.format(self.T.value_si, self.P.value_si/1e5)) @cython.boundscheck(False) def residual(self, double t, numpy.ndarray[numpy.float64_t, ndim=1] y, numpy.ndarray[numpy.float64_t, ndim=1] dydt): @@ -185,8 +190,9 @@ cdef class LiquidReactor(ReactionSystem): cdef numpy.ndarray[numpy.float64_t, ndim=1] res, kf, kr, knet cdef int numCoreSpecies, numCoreReactions, numEdgeSpecies, numEdgeReactions, numPdepNetworks cdef int j, first, second, third - cdef double k, reactionRate + cdef double k, V, reactionRate cdef numpy.ndarray[numpy.float64_t, ndim=1] coreSpeciesConcentrations, coreSpeciesRates, coreReactionRates, edgeSpeciesRates, edgeReactionRates, networkLeakRates + cdef numpy.ndarray[numpy.float64_t, ndim=1] C res = numpy.zeros(y.shape[0], numpy.float64) @@ -210,28 +216,34 @@ cdef class LiquidReactor(ReactionSystem): edgeReactionRates = numpy.zeros_like(self.edgeReactionRates) networkLeakRates = numpy.zeros_like(self.networkLeakRates) + C = numpy.zeros_like(self.coreSpeciesConcentrations) + + # Use ideal gas law to compute volume + V = constants.R * self.T.value_si * numpy.sum(y) / self.P.value_si + for j in range(y.shape[0]): - coreSpeciesConcentrations[j] = y[j] + C[j] = y[j] / V + coreSpeciesConcentrations[j] = C[j] for j in range(ir.shape[0]): k = kf[j] if ir[j,0] >= numCoreSpecies or ir[j,1] >= numCoreSpecies or ir[j,2] >= numCoreSpecies: reactionRate = 0.0 elif ir[j,1] == -1: # only one reactant - reactionRate = k * y[ir[j,0]] + reactionRate = k * C[ir[j,0]] elif ir[j,2] == -1: # only two reactants - reactionRate = k * y[ir[j,0]] * y[ir[j,1]] + reactionRate = k * C[ir[j,0]] * C[ir[j,1]] else: # three reactants!! (really?) - reactionRate = k * y[ir[j,0]] * y[ir[j,1]] * y[ir[j,2]] + reactionRate = k * C[ir[j,0]] * C[ir[j,1]] * C[ir[j,2]] k = kr[j] if ip[j,0] >= numCoreSpecies or ip[j,1] >= numCoreSpecies or ip[j,2] >= numCoreSpecies: pass elif ip[j,1] == -1: # only one reactant - reactionRate -= k * y[ip[j,0]] + reactionRate -= k * C[ip[j,0]] elif ip[j,2] == -1: # only two reactants - reactionRate -= k * y[ip[j,0]] * y[ip[j,1]] + reactionRate -= k * C[ip[j,0]] * C[ip[j,1]] else: # three reactants!! (really?) - reactionRate -= k * y[ip[j,0]] * y[ip[j,1]] * y[ip[j,2]] + reactionRate -= k * C[ip[j,0]] * C[ip[j,1]] * C[ip[j,2]] # Set the reaction and species rates if j < numCoreReactions: @@ -286,11 +298,11 @@ cdef class LiquidReactor(ReactionSystem): for j in range(inet.shape[0]): k = knet[j] if inet[j,1] == -1: # only one reactant - reactionRate = k * y[inet[j,0]] + reactionRate = k * C[inet[j,0]] elif inet[j,2] == -1: # only two reactants - reactionRate = k * y[inet[j,0]] * y[inet[j,1]] + reactionRate = k * C[inet[j,0]] * C[inet[j,1]] else: # three reactants!! (really?) - reactionRate = k * y[inet[j,0]] * y[inet[j,1]] * y[inet[j,2]] + reactionRate = k * C[inet[j,0]] * C[inet[j,1]] * C[inet[j,2]] networkLeakRates[j] = reactionRate self.coreSpeciesConcentrations = coreSpeciesConcentrations @@ -300,5 +312,384 @@ cdef class LiquidReactor(ReactionSystem): self.edgeReactionRates = edgeReactionRates self.networkLeakRates = networkLeakRates - res = coreSpeciesRates - dydt + res = coreSpeciesRates * V - dydt return res, 0 + + @cython.boundscheck(False) + def jacobian(self, double t, numpy.ndarray[numpy.float64_t, ndim=1] y, numpy.ndarray[numpy.float64_t, ndim=1] dydt, double cj): + """ + Return the analytical Jacobian for the reaction system. + """ + cdef numpy.ndarray[numpy.int_t, ndim=2] ir, ip + cdef numpy.ndarray[numpy.float64_t, ndim=1] kf, kr, C + cdef numpy.ndarray[numpy.float64_t, ndim=2] pd + cdef int numCoreReactions, j + cdef double k, deriv + + pd = -cj * numpy.identity(y.shape[0], numpy.float64) + ir = self.reactantIndices + ip = self.productIndices + kf = self.forwardRateCoefficients + kr = self.reverseRateCoefficients + numCoreReactions = len(self.coreReactionRates) + + # Use ideal gas law to compute volume + V = constants.R * self.T.value_si * numpy.sum(y) / self.P.value_si + + C = numpy.zeros_like(self.coreSpeciesConcentrations) + for j in range(y.shape[0]): + C[j] = y[j] / V + + for j in range(numCoreReactions): + + k = kf[j] + if ir[j,1] == -1: # only one reactant + deriv = k + pd[ir[j,0], ir[j,0]] -= deriv + + pd[ip[j,0], ir[j,0]] += deriv + if ip[j,1] != -1: + pd[ip[j,1], ir[j,0]] += deriv + if ip[j,2] != -1: + pd[ip[j,2], ir[j,0]] += deriv + + + elif ir[j,2] == -1: # only two reactants + if ir[j,0] == ir[j,1]: + deriv = 2 * k * C[ir[j,0]] + pd[ir[j,0], ir[j,0]] -= 2 * deriv + + pd[ip[j,0], ir[j,0]] += deriv + if ip[j,1] != -1: + pd[ip[j,1], ir[j,0]] += deriv + if ip[j,2] != -1: + pd[ip[j,2], ir[j,0]] += deriv + + else: + # Derivative with respect to reactant 1 + deriv = k * C[ir[j, 1]] + pd[ir[j,0], ir[j,0]] -= deriv + pd[ir[j,1], ir[j,0]] -= deriv + + pd[ip[j,0], ir[j,0]] += deriv + if ip[j,1] != -1: + pd[ip[j,1], ir[j,0]] += deriv + if ip[j,2] != -1: + pd[ip[j,2], ir[j,0]] += deriv + + # Derivative with respect to reactant 2 + deriv = k * C[ir[j, 0]] + pd[ir[j,0], ir[j,1]] -= deriv + pd[ir[j,1], ir[j,1]] -= deriv + + pd[ip[j,0], ir[j,1]] += deriv + if ip[j,1] != -1: + pd[ip[j,1], ir[j,1]] += deriv + if ip[j,2] != -1: + pd[ip[j,2], ir[j,1]] += deriv + + + else: # three reactants!! (really?) + if (ir[j,0] == ir[j,1] & ir[j,0] == ir[j,2]): + deriv = 3 * k * C[ir[j,0]] * C[ir[j,0]] + pd[ir[j,0], ir[j,0]] -= 3 * deriv + + pd[ip[j,0], ir[j,0]] += deriv + if ip[j,1] != -1: + pd[ip[j,1], ir[j,0]] += deriv + if ip[j,2] != -1: + pd[ip[j,2], ir[j,0]] += deriv + + elif ir[j,0] == ir[j,1]: + # derivative with respect to reactant 1 + deriv = 2 * k * C[ir[j,0]] * C[ir[j,2]] + pd[ir[j,0], ir[j,0]] -= 2 * deriv + pd[ir[j,2], ir[j,0]] -= deriv + + pd[ip[j,0], ir[j,0]] += deriv + if ip[j,1] != -1: + pd[ip[j,1], ir[j,0]] += deriv + if ip[j,2] != -1: + pd[ip[j,2], ir[j,0]] += deriv + + # derivative with respect to reactant 3 + deriv = k * C[ir[j,0]] * C[ir[j,0]] + pd[ir[j,0], ir[j,2]] -= 2 * deriv + pd[ir[j,2], ir[j,2]] -= deriv + + pd[ip[j,0], ir[j,2]] += deriv + if ip[j,1] != -1: + pd[ip[j,1], ir[j,2]] += deriv + if ip[j,2] != -1: + pd[ip[j,2], ir[j,2]] += deriv + + + elif ir[j,1] == ir[j,2]: + # derivative with respect to reactant 1 + deriv = k * C[ir[j,1]] * C[ir[j,1]] + pd[ir[j,0], ir[j,0]] -= deriv + pd[ir[j,1], ir[j,0]] -= 2 * deriv + + pd[ip[j,0], ir[j,0]] += deriv + if ip[j,1] != -1: + pd[ip[j,1], ir[j,0]] += deriv + if ip[j,2] != -1: + pd[ip[j,2], ir[j,0]] += deriv + + # derivative with respect to reactant 2 + deriv = 2 * k * C[ir[j,0]] * C[ir[j,1]] + pd[ir[j,0], ir[j,1]] -= deriv + pd[ir[j,1], ir[j,1]] -= 2 * deriv + + pd[ip[j,0], ir[j,1]] += deriv + if ip[j,1] != -1: + pd[ip[j,1], ir[j,1]] += deriv + if ip[j,2] != -1: + pd[ip[j,2], ir[j,1]] += deriv + + else: + # derivative with respect to reactant 1 + deriv = k * C[ir[j,1]] * C[ir[j,2]] + pd[ir[j,0], ir[j,0]] -= deriv + pd[ir[j,1], ir[j,0]] -= deriv + pd[ir[j,2], ir[j,0]] -= deriv + + pd[ip[j,0], ir[j,0]] += deriv + if ip[j,1] != -1: + pd[ip[j,1], ir[j,0]] += deriv + if ip[j,2] != -1: + pd[ip[j,2], ir[j,0]] += deriv + + # derivative with respect to reactant 2 + deriv = k * C[ir[j,0]] * C[ir[j,2]] + pd[ir[j,0], ir[j,1]] -= deriv + pd[ir[j,1], ir[j,1]] -= deriv + pd[ir[j,2], ir[j,1]] -= deriv + + pd[ip[j,0], ir[j,1]] += deriv + if ip[j,1] != -1: + pd[ip[j,1], ir[j,1]] += deriv + if ip[j,2] != -1: + pd[ip[j,2], ir[j,1]] += deriv + + # derivative with respect to reactant 3 + deriv = k * C[ir[j,0]] * C[ir[j,1]] + pd[ir[j,0], ir[j,2]] -= deriv + pd[ir[j,1], ir[j,2]] -= deriv + pd[ir[j,2], ir[j,2]] -= deriv + + pd[ip[j,0], ir[j,2]] += deriv + if ip[j,1] != -1: + pd[ip[j,1], ir[j,2]] += deriv + if ip[j,2] != -1: + pd[ip[j,2], ir[j,2]] += deriv + + + + k = kr[j] + if ip[j,1] == -1: # only one reactant + deriv = k + pd[ip[j,0], ip[j,0]] -= deriv + + pd[ir[j,0], ip[j,0]] += deriv + if ir[j,1] != -1: + pd[ir[j,1], ip[j,0]] += deriv + if ir[j,2] != -1: + pd[ir[j,2], ip[j,0]] += deriv + + + elif ip[j,2] == -1: # only two reactants + if ip[j,0] == ip[j,1]: + deriv = 2 * k * C[ip[j,0]] + pd[ip[j,0], ip[j,0]] -= 2 * deriv + + pd[ir[j,0], ip[j,0]] += deriv + if ir[j,1] != -1: + pd[ir[j,1], ip[j,0]] += deriv + if ir[j,2] != -1: + pd[ir[j,2], ip[j,0]] += deriv + + else: + # Derivative with respect to reactant 1 + deriv = k * C[ip[j, 1]] + pd[ip[j,0], ip[j,0]] -= deriv + pd[ip[j,1], ip[j,0]] -= deriv + + pd[ir[j,0], ip[j,0]] += deriv + if ir[j,1] != -1: + pd[ir[j,1], ip[j,0]] += deriv + if ir[j,2] != -1: + pd[ir[j,2], ip[j,0]] += deriv + + # Derivative with respect to reactant 2 + deriv = k * C[ip[j, 0]] + pd[ip[j,0], ip[j,1]] -= deriv + pd[ip[j,1], ip[j,1]] -= deriv + + pd[ir[j,0], ip[j,1]] += deriv + if ir[j,1] != -1: + pd[ir[j,1], ip[j,1]] += deriv + if ir[j,2] != -1: + pd[ir[j,2], ip[j,1]] += deriv + + + else: # three reactants!! (really?) + if (ip[j,0] == ip[j,1] & ip[j,0] == ip[j,2]): + deriv = 3 * k * C[ip[j,0]] * C[ip[j,0]] + pd[ip[j,0], ip[j,0]] -= 3 * deriv + + pd[ir[j,0], ip[j,0]] += deriv + if ir[j,1] != -1: + pd[ir[j,1], ip[j,0]] += deriv + if ir[j,2] != -1: + pd[ir[j,2], ip[j,0]] += deriv + + elif ip[j,0] == ip[j,1]: + # derivative with respect to reactant 1 + deriv = 2 * k * C[ip[j,0]] * C[ip[j,2]] + pd[ip[j,0], ip[j,0]] -= 2 * deriv + pd[ip[j,2], ip[j,0]] -= deriv + + pd[ir[j,0], ip[j,0]] += deriv + if ir[j,1] != -1: + pd[ir[j,1], ip[j,0]] += deriv + if ir[j,2] != -1: + pd[ir[j,2], ip[j,0]] += deriv + + # derivative with respect to reactant 3 + deriv = k * C[ip[j,0]] * C[ip[j,0]] + pd[ip[j,0], ip[j,2]] -= 2 * deriv + pd[ip[j,2], ip[j,2]] -= deriv + + pd[ir[j,0], ip[j,2]] += deriv + if ir[j,1] != -1: + pd[ir[j,1], ip[j,2]] += deriv + if ir[j,2] != -1: + pd[ir[j,2], ip[j,2]] += deriv + + + elif ip[j,1] == ip[j,2]: + # derivative with respect to reactant 1 + deriv = k * C[ip[j,1]] * C[ip[j,1]] + pd[ip[j,0], ip[j,0]] -= deriv + pd[ip[j,1], ip[j,0]] -= 2 * deriv + + pd[ir[j,0], ip[j,0]] += deriv + if ir[j,1] != -1: + pd[ir[j,1], ip[j,0]] += deriv + if ir[j,2] != -1: + pd[ir[j,2], ip[j,0]] += deriv + + # derivative with respect to reactant 2 + deriv = 2 * k * C[ip[j,0]] * C[ip[j,1]] + pd[ip[j,0], ip[j,1]] -= deriv + pd[ip[j,1], ip[j,1]] -= 2 * deriv + + pd[ir[j,0], ip[j,1]] += deriv + if ir[j,1] != -1: + pd[ir[j,1], ip[j,1]] += deriv + if ir[j,2] != -1: + pd[ir[j,2], ip[j,1]] += deriv + + else: + # derivative with respect to reactant 1 + deriv = k * C[ip[j,1]] * C[ip[j,2]] + pd[ip[j,0], ip[j,0]] -= deriv + pd[ip[j,1], ip[j,0]] -= deriv + pd[ip[j,2], ip[j,0]] -= deriv + + pd[ir[j,0], ip[j,0]] += deriv + if ir[j,1] != -1: + pd[ir[j,1], ip[j,0]] += deriv + if ir[j,2] != -1: + pd[ir[j,2], ip[j,0]] += deriv + + # derivative with respect to reactant 2 + deriv = k * C[ip[j,0]] * C[ip[j,2]] + pd[ip[j,0], ip[j,1]] -= deriv + pd[ip[j,1], ip[j,1]] -= deriv + pd[ip[j,2], ip[j,1]] -= deriv + + pd[ir[j,0], ip[j,1]] += deriv + if ir[j,1] != -1: + pd[ir[j,1], ip[j,1]] += deriv + if ir[j,2] != -1: + pd[ir[j,2], ip[j,1]] += deriv + + # derivative with respect to reactant 3 + deriv = k * C[ip[j,0]] * C[ip[j,1]] + pd[ip[j,0], ip[j,2]] -= deriv + pd[ip[j,1], ip[j,2]] -= deriv + pd[ip[j,2], ip[j,2]] -= deriv + + pd[ir[j,0], ip[j,2]] += deriv + if ir[j,1] != -1: + pd[ir[j,1], ip[j,2]] += deriv + if ir[j,2] != -1: + pd[ir[j,2], ip[j,2]] += deriv + + self.jacobianMatrix = pd + cj * numpy.identity(y.shape[0], numpy.float64) + return pd + + @cython.boundscheck(False) + def computeRateDerivative(self): + """ + Returns derivative vector df/dk_j where dc/dt = f(c, t, k) and + k_j is the rate parameter for the jth core reaction. + """ + cdef numpy.ndarray[numpy.int_t, ndim=2] ir, ip + cdef numpy.ndarray[numpy.float64_t, ndim=1] y, kf, kr + cdef numpy.ndarray[numpy.float64_t, ndim=2] rateDeriv + cdef double fderiv, rderiv, flux + cdef int j, numCoreReactions + + ir = self.reactantIndices + ip = self.productIndices + + kf = self.forwardRateCoefficients + kr = self.reverseRateCoefficients + y = self.coreSpeciesConcentrations + + + numCoreReactions = len(self.coreReactionRates) + + rateDeriv = numpy.zeros((y.shape[0],numCoreReactions), numpy.float64) + + for j in range(numCoreReactions): + if ir[j,1] == -1: # only one reactant + fderiv = y[ir[j,0]] + elif ir[j,2] == -1: # only two reactants + fderiv = y[ir[j,0]] * y[ir[j,1]] + else: # three reactants!! (really?) + fderiv = y[ir[j,0]] * y[ir[j,1]] * y[ir[j,2]] + + if ip[j,1] == -1: # only one reactant + rderiv = kr[j] / kf [j] * y[ip[j,0]] + elif ip[j,2] == -1: # only two reactants + rderiv = kr[j] / kf [j] * y[ip[j,0]] * y[ip[j,1]] + else: # three reactants!! (really?) + rderiv = kr[j] / kf [j] * y[ip[j,0]] * y[ip[j,1]] * y[ip[j,2]] + + + flux = fderiv - rderiv + if ir[j,1] == -1: + rateDeriv[ir[j,0], j] -= flux + elif ir[j,2] == -1: + rateDeriv[ir[j,0], j] -= flux + rateDeriv[ir[j,1], j] -= flux + else: + rateDeriv[ir[j,0], j] -= flux + rateDeriv[ir[j,1], j] -= flux + rateDeriv[ir[j,2], j] -= flux + + if ip[j,1] == -1: + rateDeriv[ip[j,0], j] += flux + elif ip[j,2] == -1: + rateDeriv[ip[j,0], j] += flux + rateDeriv[ip[j,1], j] += flux + else: + rateDeriv[ip[j,0], j] += flux + rateDeriv[ip[j,1], j] += flux + rateDeriv[ip[j,2], j] += flux + + return rateDeriv \ No newline at end of file From 288a41a8803c763735d97e1580d1d2ac6039bac4 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 18 Mar 2013 17:28:00 -0400 Subject: [PATCH 24/55] Turned solver/liquid.pyx into a Liquid Phase Batch Reactor. There is no pressure, and you initialize it with species concentrations (IN SI UNITS!) not mole fractions. the volume is calculated at the beginning so that you contain one mole total of initial core species (like for the ideal gas reactor where we have used mole fractions as initial mole number) but then the volume is stored and held constant. This is then used to calculate concentrations as the simulation proceeds. This was based on a couple of cherry-picked commits that has been modified by @rwest Richard West to reflect recent changes --- rmgpy/solver/liquid.pyx | 48 ++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/rmgpy/solver/liquid.pyx b/rmgpy/solver/liquid.pyx index eb0e97c777e..eb8dde56478 100644 --- a/rmgpy/solver/liquid.pyx +++ b/rmgpy/solver/liquid.pyx @@ -41,16 +41,16 @@ cimport rmgpy.constants as constants from rmgpy.quantity import Quantity from rmgpy.quantity cimport ScalarQuantity, ArrayQuantity -cdef class SimpleReactor(ReactionSystem): +cdef class LiquidReactor(ReactionSystem): """ - A reaction system consisting of a homogeneous, isothermal, isobaric batch + A reaction system consisting of a homogeneous, isothermal, constant volume batch reactor. These assumptions allow for a number of optimizations that enable this solver to complete very rapidly, even for large kinetic models. """ cdef public ScalarQuantity T - cdef public ScalarQuantity P - cdef public dict initialMoleFractions + cdef public ScalarQuantity V + cdef public dict initialConcentrations cdef public numpy.ndarray reactantIndices cdef public numpy.ndarray productIndices @@ -60,11 +60,11 @@ cdef class SimpleReactor(ReactionSystem): cdef public numpy.ndarray networkLeakCoefficients cdef public numpy.ndarray jacobianMatrix - def __init__(self, T, P, initialMoleFractions, termination): + def __init__(self, T, initialConcentrations, termination): ReactionSystem.__init__(self, termination) self.T = Quantity(T) - self.P = Quantity(P) - self.initialMoleFractions = initialMoleFractions + self.initialConcentrations = initialConcentrations # should be passed in SI + self.V = None # will be set from initialConcentrations in initializeModel # These are helper variables used within the solver self.reactantIndices = None @@ -76,13 +76,13 @@ cdef class SimpleReactor(ReactionSystem): def convertInitalKeysToSpeciesObjects(self, speciesDict): """ - Convert the initialMoleFractions dictionary from species names into species objects, + Convert the initialConcentrations dictionary from species names into species objects, using the given dictionary of species. """ - initialMoleFractions = {} - for label, moleFrac in self.initialMoleFractions.iteritems(): - initialMoleFractions[speciesDict[label]] = moleFrac - self.initialMoleFractions = initialMoleFractions + initialConcentrations = {} + for label, moleFrac in self.initialConcentrations.iteritems(): + initialConcentrations[speciesDict[label]] = moleFrac + self.initialConcentrations = initialConcentrations cpdef initializeModel(self, list coreSpecies, list coreReactions, list edgeSpecies, list edgeReactions, list pdepNetworks=None, atol=1e-16, rtol=1e-8): """ @@ -158,12 +158,13 @@ cdef class SimpleReactor(ReactionSystem): # Set initial conditions t0 = 0.0 y0 = numpy.zeros((numCoreSpecies), numpy.float64) - for spec, moleFrac in self.initialMoleFractions.iteritems(): - y0[speciesIndex[spec]] = moleFrac - # Use ideal gas law to compute volume - V = constants.R * self.T.value_si * numpy.sum(y0) / self.P.value_si + + for spec, conc in self.initialConcentrations.iteritems(): + self.coreSpeciesConcentrations[speciesIndex[spec]] = conc + V = 1.0 / numpy.sum(self.coreSpeciesConcentrations) + self.V = Quantity(V,'m^3') #: volume (m3) required to contain one mole total of core species at start for j in range(y0.shape[0]): - self.coreSpeciesConcentrations[j] = y0[j] / V + y0[j] = self.coreSpeciesConcentrations[j] * V # Initialize the model dydt0 = - self.residual(t0, y0, numpy.zeros((numCoreSpecies), numpy.float64))[0] @@ -176,8 +177,8 @@ cdef class SimpleReactor(ReactionSystem): """ import xlwt style0 = xlwt.easyxf('font: bold on') - worksheet.write(0, 0, 'Simple Reactor', style0) - worksheet.write(1, 0, 'T = {0:g} K, P = {1:g} bar'.format(self.T.value_si, self.P.value_si/1e5)) + worksheet.write(0, 0, 'Liquid Reactor', style0) + worksheet.write(1, 0, 'T = {0:g} K'.format(self.T.value_si)) @cython.boundscheck(False) def residual(self, double t, numpy.ndarray[numpy.float64_t, ndim=1] y, numpy.ndarray[numpy.float64_t, ndim=1] dydt): @@ -217,9 +218,7 @@ cdef class SimpleReactor(ReactionSystem): networkLeakRates = numpy.zeros_like(self.networkLeakRates) C = numpy.zeros_like(self.coreSpeciesConcentrations) - - # Use ideal gas law to compute volume - V = constants.R * self.T.value_si * numpy.sum(y) / self.P.value_si + V = self.V.value_si # constant volume reactor for j in range(y.shape[0]): C[j] = y[j] / V @@ -333,8 +332,7 @@ cdef class SimpleReactor(ReactionSystem): kr = self.reverseRateCoefficients numCoreReactions = len(self.coreReactionRates) - # Use ideal gas law to compute volume - V = constants.R * self.T.value_si * numpy.sum(y) / self.P.value_si + V = self.V.value_si # constant volume reactor C = numpy.zeros_like(self.coreSpeciesConcentrations) for j in range(y.shape[0]): @@ -692,4 +690,4 @@ cdef class SimpleReactor(ReactionSystem): rateDeriv[ip[j,1], j] += flux rateDeriv[ip[j,2], j] += flux - return rateDeriv \ No newline at end of file + return rateDeriv From 558a7e9b678b5cda6b2b4ec7caca69b2cff929aa Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 25 Mar 2013 17:41:42 -0400 Subject: [PATCH 25/55] Liquid reactors have arbitrary high 1000 Bar pressure for kinetics calculations. This should ensure the high pressure limit for any pressure-dependent reactions. Perhaps there's a better way to do this..? Conflicts: rmgpy/solver/liquid.pyx Resolved on cherry-pick by @rwest Richard West --- rmgpy/solver/liquid.pyx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rmgpy/solver/liquid.pyx b/rmgpy/solver/liquid.pyx index eb8dde56478..715ac2e17e1 100644 --- a/rmgpy/solver/liquid.pyx +++ b/rmgpy/solver/liquid.pyx @@ -50,6 +50,8 @@ cdef class LiquidReactor(ReactionSystem): cdef public ScalarQuantity T cdef public ScalarQuantity V + cdef public ScalarQuantity P + cdef public dict initialConcentrations cdef public numpy.ndarray reactantIndices @@ -63,6 +65,7 @@ cdef class LiquidReactor(ReactionSystem): def __init__(self, T, initialConcentrations, termination): ReactionSystem.__init__(self, termination) self.T = Quantity(T) + self.P = Quantity(100000.,'kPa') # Arbitrary high pressure (1000 Bar) to get reactions in the high-pressure limit! self.initialConcentrations = initialConcentrations # should be passed in SI self.V = None # will be set from initialConcentrations in initializeModel From a8ed54a1703aad7da5d9688e5c8970e309309ab2 Mon Sep 17 00:00:00 2001 From: Richard West Date: Tue, 26 Mar 2013 19:57:49 -0400 Subject: [PATCH 26/55] Made liquid phase example input file save the concentration profiles. This helped me debug something; doesn't hurt to leave it on. --- examples/rmg/liquid_phase/input.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/rmg/liquid_phase/input.py b/examples/rmg/liquid_phase/input.py index afc697ce702..def467b74bc 100644 --- a/examples/rmg/liquid_phase/input.py +++ b/examples/rmg/liquid_phase/input.py @@ -48,4 +48,5 @@ saveRestartPeriod=None, drawMolecules=False, generatePlots=False, + saveConcentrationProfiles=True, ) From 4a5b4c65f64db93d45950b6df54fc0795ae37b9f Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Wed, 27 Mar 2013 16:48:07 -0400 Subject: [PATCH 27/55] Fixed calculation of delG solvation. Factor for converting from logK to lnK was incorrect --- rmgpy/data/solvation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index 6744d33fbb2..6e1e7070ccf 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -91,7 +91,7 @@ class SolvationCorrection(): """ Stores corrections for enthalpy, entropy, and Gibbs free energy when a species is solvated. """ - def __init__(self, enthalpy=None, entropy=None, gibbs=None): + def __init__(self, enthalpy=None, gibbs=None, entropy=None): self.enthalpy = enthalpy self.entropy = entropy self.gibbs = gibbs @@ -683,7 +683,7 @@ def calcH(self, soluteData, solventData): def calcG(self, soluteData, solventData): logK = (soluteData.S*solventData.s_g)+(soluteData.B*solventData.b_g)+(soluteData.E*solventData.e_g)+(soluteData.L*solventData.l_g)+(soluteData.A*solventData.a_g)+solventData.c_g - delG = -8.314*298*0.4343*logK + delG = -8.314*298*2.303*logK return delG From a50cb0867d328e2628a9f038991460a034d4fd2c Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Wed, 27 Mar 2013 16:48:50 -0400 Subject: [PATCH 28/55] Added test cases for delG solvation. Values are from UMinnesota solvation database and have been converted from kcal/mol to J/mol. --- rmgpy/data/solvationTest.py | 39 +++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/rmgpy/data/solvationTest.py b/rmgpy/data/solvationTest.py index dfbe887afa7..044665b00fd 100644 --- a/rmgpy/data/solvationTest.py +++ b/rmgpy/data/solvationTest.py @@ -59,24 +59,24 @@ def testCorrectionGeneration(self): self.database.load(os.path.join(settings['database.directory'], 'solvation')) self.testCases = [ # solventName, soluteName, soluteSMILES, Hsolv, Gsolv, Ssolv - ['water', 'methane', 'C', -12000, None, None], - ['water', 'octane', 'CCCCCCCC', -36000, None, None], - ['water', '1,2-ethanediol', 'C(CO)O', -77300, None, None], - ['water', 'acetic acid', 'C(C)(=O)O', -56500, None, None], - ['water', 'naphthalene', 'C1=CC=CC2=CC=CC=C12', -42800, None, None], - ['water', 'm-hydroxybenzaldehyde', 'OC1=CC=CC(=C1)C=O', -70700, None, None], - ['water', 'ethylbenzene', 'C(C)C1=CC=CC=C1', -39400, None, None], - ['water', 'toluene', 'C1(=CC=CC=C1)C', -32400, None, None], - ['water', 'ethane', 'CC', -17900, None, None], - ['water', 'propane', 'CCC', -20400, None, None], - ['water', 'ethene', 'C=C', -13700, None, None], - ['water', 'propene', 'CC=C', -21600, None, None], - ['water', 'dimethyl ether', 'COC', -34000, None, None], - ['water', 'diethyl ether', 'C(C)OCC', -45300, None, None], - ['water', 'tetrahydrofuran', 'C1CCOC1', -47300, None, None], - ['water', '1,4-dioxane', 'C1COCCO1', -48400, None, None], - ['water', 'methanol', 'CO', -52000, None, None], - ['water', 'ethanol', 'C(C)O', -50600, None, None], + ['water', 'methane', 'C', -12000, 2000*4.184, None], + ['water', 'octane', 'CCCCCCCC', -36000, 2890*4.184, None], + ['water', '1,2-ethanediol', 'C(CO)O', -77300, -9300*4.184, None], + ['water', 'acetic acid', 'C(C)(=O)O', -56500, -6700*4.184, None], + ['water', 'naphthalene', 'C1=CC=CC2=CC=CC=C12', -42800, -2390*4.184, None], + ['water', 'm-hydroxybenzaldehyde', 'OC1=CC=CC(=C1)C=O', -70700, -9510*4.184, None], + ['water', 'ethylbenzene', 'C(C)C1=CC=CC=C1', -39400, -800*4.184, None], + ['water', 'toluene', 'C1(=CC=CC=C1)C', -32400, -890*4.184, None], + ['water', 'ethane', 'CC', -17900, 1830*4.184, None], + ['water', 'propane', 'CCC', -20400, 1960*4.184, None], + ['water', 'ethene', 'C=C', -13700, 1270*4.184, None], + ['water', 'propene', 'CC=C', -21600, 1270*4.184, None], + ['water', 'dimethyl ether', 'COC', -34000, -1920*4.184, None], + ['water', 'diethyl ether', 'C(C)OCC', -45300, -1760*4.184, None], + ['water', 'tetrahydrofuran', 'C1CCOC1', -47300, -3470*4.184, None], + ['water', '1,4-dioxane', 'C1COCCO1', -48400, -5050*4.184, None], + ['water', 'methanol', 'CO', -52000, -5110*4.184, None], + ['water', 'ethanol', 'C(C)O', -50600, -5010*4.184, None], ['water', '1,2 propanediol', 'C(CO)CO', -81100, None, None], ] @@ -86,7 +86,8 @@ def testCorrectionGeneration(self): solventData = self.database.getSolventData(solventName) solvationCorrection = self.database.getSolvationCorrection(soluteData, solventData) #print("Enthalpy of solvation for {0} in {1} is {2} J/mol".format(soluteName, solventName, solvationCorrection.enthalpy)) - print("{0} {1} {2}".format(soluteName, H, solvationCorrection.enthalpy)) + #print("Enthalpy: {0} {1} {2}".format(soluteName, H, solvationCorrection.enthalpy)) + print("Gibbs: {0} {1} {2}".format(soluteName, G, solvationCorrection.gibbs)) #self.assertAlmostEqual(solvationCorrection.enthalpy/10000., H/10000.,0) #0 decimal place, in 10kJ. ##################################################### From 810210c4734e4bbb58dfa485b4bc09cefb7494dd Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 8 Apr 2013 13:09:09 -0400 Subject: [PATCH 29/55] Made a sample input file for liquid-phase. This mirrors the liquid phase example from RMG-Java for octane autoxidation. --- examples/rmg/liquid_phase/input.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/examples/rmg/liquid_phase/input.py b/examples/rmg/liquid_phase/input.py index def467b74bc..82661c26cb8 100644 --- a/examples/rmg/liquid_phase/input.py +++ b/examples/rmg/liquid_phase/input.py @@ -10,25 +10,34 @@ # List of species species( - label='ethane', + label='octane', reactive=True, - structure=SMILES("CC"), + structure=SMILES("C(CCCCC)CC"), +) + +species( + label='oxygen', + reactive=True, + structure=SMILES("[O][O]"), ) # Reaction systems liquidReactor( - temperature=(750,'K'), + temperature=(500,'K'), initialConcentrations={ - "ethane": (1.0,'mol/L') + "octane": (6.154e-3,'mol/cm^3'), + "oxygen": (4.953e-6,'mol/cm^3') }, terminationConversion={ - 'ethane': 0.9, + 'octane': 0.9, }, terminationTime=(1e6,'s'), ) solvation( - solvent='water' + solvent='octane', + viscosity = (1e-3, "cP"), + temperature = (298, "K"), # T at which diffusion limits on reaction rates evaluated for chemkin output ) simulator( From f12e644f4730b5a8404790f1eebe7d38591ff791 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 8 Apr 2013 13:10:42 -0400 Subject: [PATCH 30/55] Added solvation test cases for some other solvents. --- rmgpy/data/solvationTest.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rmgpy/data/solvationTest.py b/rmgpy/data/solvationTest.py index 044665b00fd..af8e3157f18 100644 --- a/rmgpy/data/solvationTest.py +++ b/rmgpy/data/solvationTest.py @@ -78,6 +78,17 @@ def testCorrectionGeneration(self): ['water', 'methanol', 'CO', -52000, -5110*4.184, None], ['water', 'ethanol', 'C(C)O', -50600, -5010*4.184, None], ['water', '1,2 propanediol', 'C(CO)CO', -81100, None, None], + ['1-octanol', 'methane', 'C', -3900, 510*4.184, None], + ['1-octanol', 'octane', 'CCCCCCCC', -40080, -4180*4.184, None], + ['1-octanol', 'ethylbenzene', 'C(C)C1=CC=CC=C1', -39910, -5080*4.184, None], + ['1-octanol', 'toluene', 'C1(=CC=CC=C1)C', -35990, -4550*4.184, None], + ['1-octanol', 'tetrahydrofuran', 'C1CCOC1', -28320, -3930*4.184, None], + ['benzene', 'octane', 'CCCCCCCC', -35480, -5350*4.184, None], + ['benzene', 'toluene', 'C1(=CC=CC=C1)C', -37660, -5320*4.184, None], + ['benzene', '1,4-dioxane', 'C1COCCO1', -39030, -5210*4.184, None], + ['benzene', 'methanol', 'CO', None, -2580*4.184, None], + ['benzene', 'ethanol', 'C(CO)CO', -29620, -3420*4.184, None], + ] for solventName, soluteName, smiles, H, G, S in self.testCases: From 65e2be376386957f1a846ff09f0e9292ea899cc0 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 8 Apr 2013 13:43:17 -0400 Subject: [PATCH 31/55] Add method for finding McGowan volume of a solute. The McGowan volume, V, is now a 6th solute descriptor and is set by the method setMcGowanVolume(species) --- rmgpy/data/solvation.py | 62 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index 6e1e7070ccf..e5b866f5f7d 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -100,16 +100,75 @@ class SoluteData(): """ Stores Abraham parameters to characterize a solute """ - def __init__(self, S=None, B=None, E=None, L=None, A=None, comment=""): + def __init__(self, S=None, B=None, E=None, L=None, A=None, V=None, comment=""): self.S = S self.B = B self.E = E self.L = L self.A = A + self.V = V self.comment = comment def __repr__(self): return "SoluteData(S={0},B={1},E={2},L={3},A={4},comment={5!r})".format(self.S, self.B, self.E, self.L, self.A, self.comment) + def setMcGowanVolume(self, species): + """ + Find and store the McGowan's Volume + Returned volumes are in cm^3/mol/100 (see note below) + See Table 2 in Abraham & McGowan, Chromatographia Vol. 23, No. 4, p. 243. April 1987 + doi: 10.1007/BF02311772 + + "V is scaled to have similar values to the other + descriptors by division by 100 and has units of (cm3mol−1/100)." + the contibutions in this function are in cm3/mol, and the division by 100 is done at the very end. + """ + molecule = species.molecules[0] # any will do, use the first. + Vtot = 0 + + for atom in molecule.atoms: + if isCarbon(atom): + thisV = 16.35 + elif (atom.element.number == 7): # nitrogen, do this way if we don't have an isElement method + thisV = 14.39 + elif isOxygen(atom): + thisV = thisV + 12.43 + #else if (element.equals("F")) + #thisV = thisV + 10.48; + elif isHydrogen(atom): + thisV = thisV + 8.71 + # else if (element.equals("Si")) +# thisV = thisV + 26.83; +# else if (element.equals("P")) +# thisV = thisV + 24.87; +# else if (element.equals("S")) +# thisV = thisV + 22.91; +# else if (element.equals("Cl")) +# thisV = thisV + 20.95; +# else if (element.equals("B")) +# thisV = thisV + 18.32; +# else if (element.equals("Ge")) +# thisV = thisV + 31.02; +# else if (element.equals("As")) +# thisV = thisV + 29.42; +# else if (element.equals("Se")) +# thisV = thisV + 27.81; +# else if (element.equals("Br")) +# thisV = thisV + 26.21; +# else if (element.equals("Sn")) +# thisV = thisV + 39.35; +# else if (element.equals("Te")) +# thisV = thisV + 36.14; +# else if (element.equals("I")) +# thisV = thisV + 34.53; + else: + raise Exception() + Vtot = Vtot + thisV + + for bond in molecule.bonds: + Vtot = Vtot - 6.56; + + return Vtot / 100; # division by 100 to get units correct. + ################################################################################ @@ -456,6 +515,7 @@ def getSoluteData(self, species): # Solute not found in any loaded libraries, so estimate soluteData = self.getSoluteDataFromGroups(species) # Return the resulting solute parameters + soluteData.setMcGowanVolume(species) return soluteData From 18fc7e306f3b3157a77488dd61788b588566ca9b Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 8 Apr 2013 15:27:56 -0400 Subject: [PATCH 32/55] Added solvent viscosity and temperature to RMG model. Right now, the temperature is arbitrarily set for that which you want the diffusivity limit to be evaluated in the chemkin output. --- rmgpy/rmg/input.py | 4 +++- rmgpy/rmg/main.py | 4 ++++ rmgpy/rmg/model.py | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/rmgpy/rmg/input.py b/rmgpy/rmg/input.py index e0d8c5418dd..b08029535c5 100644 --- a/rmgpy/rmg/input.py +++ b/rmgpy/rmg/input.py @@ -149,9 +149,11 @@ def simulator(atol, rtol): rmg.absoluteTolerance = atol rmg.relativeTolerance = rtol -def solvation(solvent): +def solvation(solvent, viscosity, temperature): assert isinstance(solvent,str), "solvent should be a string like 'water'" rmg.solvent = solvent + rmg.viscosity = viscosity + rmg.diffusionTemp = temperature def model(toleranceMoveToCore, toleranceKeepInEdge=0.0, toleranceInterruptSimulation=1.0, maximumEdgeSpecies=None): rmg.fluxToleranceKeepInEdge = toleranceKeepInEdge diff --git a/rmgpy/rmg/main.py b/rmgpy/rmg/main.py index 5fb16bb4f4d..85557cd9fd0 100644 --- a/rmgpy/rmg/main.py +++ b/rmgpy/rmg/main.py @@ -126,6 +126,8 @@ def clear(self): self.kineticsDepositories = None self.kineticsEstimator = 'group additivity' self.solvent = None + self.viscosity = None + self.diffusionTemp = None self.reactionModel = None self.reactionSystems = None @@ -268,6 +270,8 @@ def initialize(self, args): if self.solvent: Species.solventData = self.database.solvation.getSolventData(self.solvent) Species.solventName = self.solvent + Species.solventViscosity = self.viscosity + Species.diffusionTemp = self.diffusionTemp logging.info("Setting solvent data for {0}".format(self.solvent)) # Set wall time diff --git a/rmgpy/rmg/model.py b/rmgpy/rmg/model.py index 9f5ea82afb3..2720f83667e 100644 --- a/rmgpy/rmg/model.py +++ b/rmgpy/rmg/model.py @@ -60,6 +60,8 @@ class Species(rmgpy.species.Species): solventName = None solventData = None + solventViscosity = None + diffusionTemp = None def __init__(self, index=-1, label='', thermo=None, conformer=None, molecule=None, lennardJones=None, molecularWeight=None, From c72baff574b0cf13c59afd5e0745ae962d785cc5 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 8 Apr 2013 15:28:37 -0400 Subject: [PATCH 33/55] Method for calculating diffusivity in a solvent. Uses the Stokes-Einstein diffusivity model for spheres --- rmgpy/rmg/model.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/rmgpy/rmg/model.py b/rmgpy/rmg/model.py index 2720f83667e..887f1e704fe 100644 --- a/rmgpy/rmg/model.py +++ b/rmgpy/rmg/model.py @@ -188,6 +188,19 @@ def generateEnergyTransferModel(self): T0 = (300,"K"), n = 0.85, ) + + def getStokesDiffusivity(self): + """ + Get diffusivity of solute using the Stokes-Einstein sphere relation. Radius is + found from the McGowan volume. + """ + soluteData = database.solvation.getSoluteData(self) + k_b = 1.3806488e-23 # m2*kg/s2/K + T = Species.diffusionTemp.value_si + viscosity = Species.solventViscosity.values_si # should have units of kg*m/s + radius = ((75*soluteData.V/3.14159)^(1/3))/100 # in meters + D = k_b*T/6/3.14159/viscosity/radius # m2/s + return D ################################################################################ From c2c147db3415cce57e278e6672a2a096e65e3cfe Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Tue, 9 Apr 2013 15:31:23 -0400 Subject: [PATCH 34/55] Some fixes for working with diffusivity. --- rmgpy/data/solvation.py | 14 +++++++------- rmgpy/rmg/main.py | 2 +- rmgpy/rmg/model.py | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index e5b866f5f7d..f391fbdd384 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -122,19 +122,20 @@ def setMcGowanVolume(self, species): descriptors by division by 100 and has units of (cm3mol−1/100)." the contibutions in this function are in cm3/mol, and the division by 100 is done at the very end. """ - molecule = species.molecules[0] # any will do, use the first. + molecule = species.molecule[0] # any will do, use the first. Vtot = 0 for atom in molecule.atoms: - if isCarbon(atom): + thisV = 0.0 + if atom.isCarbon(): thisV = 16.35 elif (atom.element.number == 7): # nitrogen, do this way if we don't have an isElement method thisV = 14.39 - elif isOxygen(atom): + elif atom.isOxygen(): thisV = thisV + 12.43 #else if (element.equals("F")) #thisV = thisV + 10.48; - elif isHydrogen(atom): + elif atom.isHydrogen(): thisV = thisV + 8.71 # else if (element.equals("Si")) # thisV = thisV + 26.83; @@ -164,12 +165,11 @@ def setMcGowanVolume(self, species): raise Exception() Vtot = Vtot + thisV - for bond in molecule.bonds: - Vtot = Vtot - 6.56; + for bond in molecule.getBonds(atom): + Vtot = Vtot - 6.56 return Vtot / 100; # division by 100 to get units correct. - ################################################################################ diff --git a/rmgpy/rmg/main.py b/rmgpy/rmg/main.py index 85557cd9fd0..a27bfcc7af5 100644 --- a/rmgpy/rmg/main.py +++ b/rmgpy/rmg/main.py @@ -127,7 +127,7 @@ def clear(self): self.kineticsEstimator = 'group additivity' self.solvent = None self.viscosity = None - self.diffusionTemp = None + self.diffusionTemp = None self.reactionModel = None self.reactionSystems = None diff --git a/rmgpy/rmg/model.py b/rmgpy/rmg/model.py index 887f1e704fe..abc5b8cbe1c 100644 --- a/rmgpy/rmg/model.py +++ b/rmgpy/rmg/model.py @@ -197,7 +197,7 @@ def getStokesDiffusivity(self): soluteData = database.solvation.getSoluteData(self) k_b = 1.3806488e-23 # m2*kg/s2/K T = Species.diffusionTemp.value_si - viscosity = Species.solventViscosity.values_si # should have units of kg*m/s + viscosity = Species.solventViscosity.value_si # should have units of kg*m/s radius = ((75*soluteData.V/3.14159)^(1/3))/100 # in meters D = k_b*T/6/3.14159/viscosity/radius # m2/s return D From 79edf96f4ade6a4ed1e5e61e0ee62a5ede7a1ac9 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Sun, 14 Apr 2013 15:51:22 -0400 Subject: [PATCH 35/55] Count bonds the correct number of times when calculating McGowan volume. --- rmgpy/data/solvation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index f391fbdd384..dd9bd597847 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -166,7 +166,7 @@ def setMcGowanVolume(self, species): Vtot = Vtot + thisV for bond in molecule.getBonds(atom): - Vtot = Vtot - 6.56 + Vtot = Vtot - 6.56/2 return Vtot / 100; # division by 100 to get units correct. From 129f9a64b83406349c9b93eeb1a782595b3a5ad3 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Sun, 14 Apr 2013 15:53:12 -0400 Subject: [PATCH 36/55] Methods for calculating k_diff, the diffusion factor, and the pre-exponential factor. The diffusion factor is the effective rate constant divided by the intrinsic rate constant. The pre-exponential factor, A, is decreased by the diffusion factor. --- rmgpy/reaction.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/rmgpy/reaction.py b/rmgpy/reaction.py index 35454206d71..a1f1bbe1449 100644 --- a/rmgpy/reaction.py +++ b/rmgpy/reaction.py @@ -50,6 +50,7 @@ from rmgpy.molecule.molecule import Molecule, Atom from rmgpy.molecule.element import Element from rmgpy.species import Species +from rmgpy.data.solvation import SoluteData, SoluteGroups, SolvationDatabase from rmgpy.kinetics.arrhenius import Arrhenius #PyDev: @UnresolvedImport from rmgpy.kinetics import KineticsData, ArrheniusEP, ThirdBody, Lindemann, Troe, Chebyshev, PDepArrhenius, MultiArrhenius, MultiPDepArrhenius #PyDev: @UnresolvedImport from rmgpy.pdep.reaction import calculateMicrocanonicalRateCoefficient @@ -497,6 +498,73 @@ def getStoichiometricCoefficient(self, spec): for product in self.products: if product is spec: stoich += 1 return stoich + + def getDiffusionLimit(self, forward=True): + """ + Return the diffusive limit on the rate coefficient, k_diff. + + This is the upper limit on the rate, in the specified direction. + (ie. forward direction if forward=True [default] or reverse if forward=False) + """ + self.database = SolvationDatabase() + if forward: + reacting = self.reactants + else: + reacting = self.products + assert len(reacting)==2, "Can only calculate diffusion limit in a bimolecular direction" + radii = 0.0 + diffusivities = 0.0 + for spec in reacting: + soluteData = self.database.getSoluteData(spec) + radius = ((75*soluteData.V/3.14159)^(1/3))/100 + diff = spec.getStokesDiffusivity*() + radii += radius + diffusivities += diff + N_a = 6.022e23 # Avogadro's Number + k_diff = 4*3.14159*radii*diffusivities*N_a + return k_diff + + def getDiffusionFactor(self, T): + """ + Return the ratio of k_eff to k_intrinsic, which is between 0 and 1. + + It is 1.0 if diffusion has no effect. + + For 1<=>2 reactions, the reverse rate is limited. + For 2<=>2 reactions, the faster direction is limited. + For 2<=>1 or 2<=>3 reactions, the forward rate is limited. + """ + reactants = len(self.reactants) + products = len(self.products) + factor = 1.0 + k_forward = self.getRateCoefficient(T) + k_reverse = self.generateReverseRateCoefficient() + Keq = k_forward / k_reverse + if reactants == 1: + if products == 1: + factor = 1.0 + else: # two products; reverse rate is limited + k_diff = self.getDiffusionLimit(forward=False) + k_eff_reverse = k_reverse*k_diff/(k_reverse+k_diff) + k_eff = k_eff_reverse * Keq + factor = k_eff / k_forward + else: # 2 reactants + if products == 1 or products == 3: + k_diff = self.getDiffusionLimit(forward=True) + k_eff = k_forward*k_diff/(k_forward+k_diff) + factor = k_eff / k_forward + else: # 2 products + if Keq > 1.0: # forward rate is faster and thus limited + k_diff = self.getDiffusionLimit(forward=True) + k_eff = k_forward*k_diff/(k_forward+k_diff) + factor = k_eff / k_forward + else: # reverse rate is faster and thus limited + k_diff = self.getDiffusionLimit(forward=False) + k_eff_reverse = k_reverse*k_diff/(k_reverse+k_diff) + k_eff = k_eff_reverse * Keq + factor = k_eff / k_forward + return factor + def getRateCoefficient(self, T, P=0): """ @@ -504,7 +572,7 @@ def getRateCoefficient(self, T, P=0): temperature `T` in K and pressure `P` in Pa, including any reaction path degeneracies. """ - return self.kinetics.getRateCoefficient(T, P) + return self.kinetics.getRateCoefficient(T, P) def getRate(self, T, P, conc, totalConc=-1.0): """ @@ -554,6 +622,16 @@ def getRate(self, T, P, conc, totalConc=-1.0): # Return rate return rateConstant * (forward - reverse / equilibriumConstant) + def fixDiffusionLimitedA(self, T): + """ + Decrease the pre-exponential factor (A) by a factor of getDiffusionFactor + to account for the diffusion limit. + """ + # Decrease self.kinetics.A (if Arrhenius or ArrheniusEP) + self.kinetics.A = self.kinetics.A * self.getDiffusionFactor(T) + # Add a comment to self.kinetics.comment + self.kinetics.comment.append("Pre-exponential factor A has been decreased by the diffusion factor.") + def fixBarrierHeight(self): """ Turns the kinetics into Arrhenius (if they were ArrheniusEP) From 67a9142cb0eae9b3e385bae55ad8219b97b45673 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Tue, 16 Apr 2013 14:52:38 -0400 Subject: [PATCH 37/55] Moved method for calculating Stokes-Einstein diffusivity. This method is now in the solvation module instead of the Species class in model.py. --- rmgpy/data/solvation.py | 14 +++++++++++++- rmgpy/rmg/model.py | 13 ------------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index dd9bd597847..3eadab0ffe2 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -110,7 +110,19 @@ def __init__(self, S=None, B=None, E=None, L=None, A=None, V=None, comment=""): self.comment = comment def __repr__(self): return "SoluteData(S={0},B={1},E={2},L={3},A={4},comment={5!r})".format(self.S, self.B, self.E, self.L, self.A, self.comment) - + + def getStokesDiffusivity(self, T, solventViscosity): + """ + Get diffusivity of solute using the Stokes-Einstein sphere relation. Radius is + found from the McGowan volume. + """ + k_b = 1.3806488e-23 # m2*kg/s2/K + temp = T.value_si + viscosity = solventViscosity.value_si # should have units of kg*m/s + radius = ((75*self.V/3.14159)**(1/3))/100 # in meters + D = k_b*temp/6/3.14159/viscosity/radius # m2/s + return D + def setMcGowanVolume(self, species): """ Find and store the McGowan's Volume diff --git a/rmgpy/rmg/model.py b/rmgpy/rmg/model.py index abc5b8cbe1c..2720f83667e 100644 --- a/rmgpy/rmg/model.py +++ b/rmgpy/rmg/model.py @@ -188,19 +188,6 @@ def generateEnergyTransferModel(self): T0 = (300,"K"), n = 0.85, ) - - def getStokesDiffusivity(self): - """ - Get diffusivity of solute using the Stokes-Einstein sphere relation. Radius is - found from the McGowan volume. - """ - soluteData = database.solvation.getSoluteData(self) - k_b = 1.3806488e-23 # m2*kg/s2/K - T = Species.diffusionTemp.value_si - viscosity = Species.solventViscosity.value_si # should have units of kg*m/s - radius = ((75*soluteData.V/3.14159)^(1/3))/100 # in meters - D = k_b*T/6/3.14159/viscosity/radius # m2/s - return D ################################################################################ From c7e3a3d35684509de00b1a71a1b040f80ced2d64 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Tue, 23 Apr 2013 16:15:27 -0400 Subject: [PATCH 38/55] Fix to setMcGowanVolume method Sets the V parameter to the McGowan volume rather than returning it --- rmgpy/data/solvation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index 3eadab0ffe2..62b03bafa87 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -180,7 +180,7 @@ def setMcGowanVolume(self, species): for bond in molecule.getBonds(atom): Vtot = Vtot - 6.56/2 - return Vtot / 100; # division by 100 to get units correct. + self.V= Vtot / 100; # division by 100 to get units correct. ################################################################################ From 12e0a493e9feaec06e4d078499cdc890b4a32812 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Tue, 23 Apr 2013 16:17:25 -0400 Subject: [PATCH 39/55] Moved methods for diffusion kinetics into a new file. New class DiffusionLimited contains methods for calculating diffusion limited kinetics given a reaction's intrinsic kinetics --- rmgpy/kinetics/diffusionLimited.py | 78 ++++++++++++++++++++++++++++++ rmgpy/reaction.py | 77 +++-------------------------- 2 files changed, 86 insertions(+), 69 deletions(-) create mode 100644 rmgpy/kinetics/diffusionLimited.py diff --git a/rmgpy/kinetics/diffusionLimited.py b/rmgpy/kinetics/diffusionLimited.py new file mode 100644 index 00000000000..8dddfe65799 --- /dev/null +++ b/rmgpy/kinetics/diffusionLimited.py @@ -0,0 +1,78 @@ +import rmgpy.quantity as quantity + +from rmgpy.species import Species +from rmgpy.data.solvation import SoluteData, SoluteGroups, SolvationDatabase +from rmgpy.reaction import Reaction + + +class DiffusionLimited(): + + def __init__(self, database = None, comment=''): + self.database = SolvationDatabase() + + def getSolventViscosity(T): + + return solventViscosity or Quantity(1,"cp") + + def getEffectiveRate(self, reaction, T): + """ + Return the ratio of k_eff to k_intrinsic, which is between 0 and 1. + + It is 1.0 if diffusion has no effect. + + For 1<=>2 reactions, the reverse rate is limited. + For 2<=>2 reactions, the faster direction is limited. + For 2<=>1 or 2<=>3 reactions, the forward rate is limited. + """ + intrinsicKinetics = reaction.kinetics + reactants = len(reaction.reactants) + products = len(reaction.products) + k_forward = intrinsicKinetics.getRateCoefficient(T,P=100e5) + Keq = reaction.getEquilibriumConstant(T) # Kc + k_reverse = k_forward / Keq + k_eff = k_forward + + if reactants == 1: + if products == 1: + k_eff = k_forward + else: # two products; reverse rate is limited + k_diff = self.getDiffusionLimit(T, forward=False) + k_eff_reverse = k_reverse*k_diff/(k_reverse+k_diff) + k_eff = k_eff_reverse * Keq + else: # 2 reactants + if products == 1 or products == 3: + k_diff = self.getDiffusionLimit(T, reaction, forward=True) + k_eff = k_forward*k_diff/(k_forward+k_diff) + else: # 2 products + if Keq > 1.0: # forward rate is faster and thus limited + k_diff = self.getDiffusionLimit(T, reaction, forward=True) + k_eff = k_forward*k_diff/(k_forward+k_diff) + else: # reverse rate is faster and thus limited + k_diff = self.getDiffusionLimit(T, reaction, forward=False) + k_eff_reverse = k_reverse*k_diff/(k_reverse+k_diff) + k_eff = k_eff_reverse * Keq + return k_eff + + def getDiffusionLimit(self, T, reaction, forward=True): + """ + Return the diffusive limit on the rate coefficient, k_diff. + + This is the upper limit on the rate, in the specified direction. + (ie. forward direction if forward=True [default] or reverse if forward=False) + """ + if forward: + reacting = reaction.reactants + else: + reacting = reaction.products + assert len(reacting)==2, "Can only calculate diffusion limit in a bimolecular direction" + radii = 0.0 + diffusivities = 0.0 + for spec in reacting: + soluteData = self.database.getSoluteData(spec) + radius = ((75*soluteData.V/3.14159)**(1/3))/100 + diff = soluteData.getStokesDiffusivity(T, self.getSolventViscosity(T)) + radii += radius + diffusivities += diff + N_a = 6.022e23 # Avogadro's Number + k_diff = 4*3.14159*radii*diffusivities*N_a + return k_diff \ No newline at end of file diff --git a/rmgpy/reaction.py b/rmgpy/reaction.py index a1f1bbe1449..ef4c16b4b4c 100644 --- a/rmgpy/reaction.py +++ b/rmgpy/reaction.py @@ -50,11 +50,12 @@ from rmgpy.molecule.molecule import Molecule, Atom from rmgpy.molecule.element import Element from rmgpy.species import Species -from rmgpy.data.solvation import SoluteData, SoluteGroups, SolvationDatabase from rmgpy.kinetics.arrhenius import Arrhenius #PyDev: @UnresolvedImport from rmgpy.kinetics import KineticsData, ArrheniusEP, ThirdBody, Lindemann, Troe, Chebyshev, PDepArrhenius, MultiArrhenius, MultiPDepArrhenius #PyDev: @UnresolvedImport from rmgpy.pdep.reaction import calculateMicrocanonicalRateCoefficient +from rmgpy.kinetics.diffusionLimited import DiffusionLimited + ################################################################################ class ReactionError(Exception): @@ -498,73 +499,6 @@ def getStoichiometricCoefficient(self, spec): for product in self.products: if product is spec: stoich += 1 return stoich - - def getDiffusionLimit(self, forward=True): - """ - Return the diffusive limit on the rate coefficient, k_diff. - - This is the upper limit on the rate, in the specified direction. - (ie. forward direction if forward=True [default] or reverse if forward=False) - """ - self.database = SolvationDatabase() - if forward: - reacting = self.reactants - else: - reacting = self.products - assert len(reacting)==2, "Can only calculate diffusion limit in a bimolecular direction" - radii = 0.0 - diffusivities = 0.0 - for spec in reacting: - soluteData = self.database.getSoluteData(spec) - radius = ((75*soluteData.V/3.14159)^(1/3))/100 - diff = spec.getStokesDiffusivity*() - radii += radius - diffusivities += diff - N_a = 6.022e23 # Avogadro's Number - k_diff = 4*3.14159*radii*diffusivities*N_a - return k_diff - - def getDiffusionFactor(self, T): - """ - Return the ratio of k_eff to k_intrinsic, which is between 0 and 1. - - It is 1.0 if diffusion has no effect. - - For 1<=>2 reactions, the reverse rate is limited. - For 2<=>2 reactions, the faster direction is limited. - For 2<=>1 or 2<=>3 reactions, the forward rate is limited. - """ - reactants = len(self.reactants) - products = len(self.products) - factor = 1.0 - k_forward = self.getRateCoefficient(T) - k_reverse = self.generateReverseRateCoefficient() - Keq = k_forward / k_reverse - if reactants == 1: - if products == 1: - factor = 1.0 - else: # two products; reverse rate is limited - k_diff = self.getDiffusionLimit(forward=False) - k_eff_reverse = k_reverse*k_diff/(k_reverse+k_diff) - k_eff = k_eff_reverse * Keq - factor = k_eff / k_forward - else: # 2 reactants - if products == 1 or products == 3: - k_diff = self.getDiffusionLimit(forward=True) - k_eff = k_forward*k_diff/(k_forward+k_diff) - factor = k_eff / k_forward - else: # 2 products - if Keq > 1.0: # forward rate is faster and thus limited - k_diff = self.getDiffusionLimit(forward=True) - k_eff = k_forward*k_diff/(k_forward+k_diff) - factor = k_eff / k_forward - else: # reverse rate is faster and thus limited - k_diff = self.getDiffusionLimit(forward=False) - k_eff_reverse = k_reverse*k_diff/(k_reverse+k_diff) - k_eff = k_eff_reverse * Keq - factor = k_eff / k_forward - return factor - def getRateCoefficient(self, T, P=0): """ @@ -572,7 +506,12 @@ def getRateCoefficient(self, T, P=0): temperature `T` in K and pressure `P` in Pa, including any reaction path degeneracies. """ - return self.kinetics.getRateCoefficient(T, P) + if DiffusionLimited.solventViscosity: + logging.info("Correcting rate for diffusion limited reaction") + diffusionLimited = DiffusionLimited() + return diffusionLimited.getEffectiveRate(self, T) + else: + return self.kinetics.getRateCoefficient(T, P) def getRate(self, T, P, conc, totalConc=-1.0): """ From 1310785aabc66a7d7f6d8f75ac7e279e10b1906e Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Tue, 23 Apr 2013 16:19:13 -0400 Subject: [PATCH 40/55] Removed the arbitrary temperature that had been used to calculate solvation properties --- rmgpy/rmg/input.py | 3 +-- rmgpy/rmg/main.py | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/rmgpy/rmg/input.py b/rmgpy/rmg/input.py index b08029535c5..4b25dc8b15a 100644 --- a/rmgpy/rmg/input.py +++ b/rmgpy/rmg/input.py @@ -149,11 +149,10 @@ def simulator(atol, rtol): rmg.absoluteTolerance = atol rmg.relativeTolerance = rtol -def solvation(solvent, viscosity, temperature): +def solvation(solvent, viscosity): assert isinstance(solvent,str), "solvent should be a string like 'water'" rmg.solvent = solvent rmg.viscosity = viscosity - rmg.diffusionTemp = temperature def model(toleranceMoveToCore, toleranceKeepInEdge=0.0, toleranceInterruptSimulation=1.0, maximumEdgeSpecies=None): rmg.fluxToleranceKeepInEdge = toleranceKeepInEdge diff --git a/rmgpy/rmg/main.py b/rmgpy/rmg/main.py index a27bfcc7af5..1af1210bdc5 100644 --- a/rmgpy/rmg/main.py +++ b/rmgpy/rmg/main.py @@ -50,6 +50,9 @@ from rmgpy.data.rmg import RMGDatabase from rmgpy.data.kinetics import KineticsLibrary, KineticsFamily, LibraryReaction, TemplateReaction +from rmgpy.reaction import Reaction +from rmgpy.kinetics.diffusionLimited import DiffusionLimited + from model import Species, CoreEdgeReactionModel from pdep import PDepNetwork @@ -270,8 +273,7 @@ def initialize(self, args): if self.solvent: Species.solventData = self.database.solvation.getSolventData(self.solvent) Species.solventName = self.solvent - Species.solventViscosity = self.viscosity - Species.diffusionTemp = self.diffusionTemp + DiffusionLimited.solventViscosity = self.viscosity logging.info("Setting solvent data for {0}".format(self.solvent)) # Set wall time From ad1d80fd0fed22ec78e5a95f35ea4e3780ecd26c Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Tue, 30 Apr 2013 11:19:21 -0400 Subject: [PATCH 41/55] Fixes to input file, and model.py after merge --- examples/rmg/liquid_phase/input.py | 7 +++---- rmgpy/rmg/model.py | 6 +----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/examples/rmg/liquid_phase/input.py b/examples/rmg/liquid_phase/input.py index 82661c26cb8..b227e958c46 100644 --- a/examples/rmg/liquid_phase/input.py +++ b/examples/rmg/liquid_phase/input.py @@ -36,8 +36,7 @@ solvation( solvent='octane', - viscosity = (1e-3, "cP"), - temperature = (298, "K"), # T at which diffusion limits on reaction rates evaluated for chemkin output + viscosity = (1e-3, "cP") ) simulator( @@ -46,9 +45,9 @@ ) model( - toleranceKeepInEdge=0.0, + toleranceKeepInEdge=1E-9, toleranceMoveToCore=0.1, - toleranceInterruptSimulation=0.1, + toleranceInterruptSimulation=1E9, maximumEdgeSpecies=100000 ) diff --git a/rmgpy/rmg/model.py b/rmgpy/rmg/model.py index 8d55056b9b0..2111ed6524e 100644 --- a/rmgpy/rmg/model.py +++ b/rmgpy/rmg/model.py @@ -104,9 +104,7 @@ def generateThermoData(self, database, thermoClass=NASA): wilhoit = thermo0.toWilhoit(Cp0=Cp0, CpInf=CpInf) wilhoit.comment = thermo0.comment -<<<<<<< HEAD - - + # Add on solvation correction if Species.solventData: logging.info("Making solvent correction for {0} with {1!r}".format(Species.solventName,Species.solventData)) @@ -120,8 +118,6 @@ def generateThermoData(self, database, thermoClass=NASA): if self.conformer is None: self.conformer = Conformer() self.conformer.E0 = (wilhoit.getEnthalpy(1.0)*1e-3,"kJ/mol") -======= ->>>>>>> refs/remotes/richard/master # Convert to desired thermo class if isinstance(thermo0, thermoClass): From 014b56f35f117e057e002d345b7b84cda7eccb58 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Thu, 2 May 2013 15:01:42 -0400 Subject: [PATCH 42/55] Temperature is a float, not a Quantity --- rmgpy/data/solvation.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index 62b03bafa87..996fb16de56 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -117,10 +117,9 @@ def getStokesDiffusivity(self, T, solventViscosity): found from the McGowan volume. """ k_b = 1.3806488e-23 # m2*kg/s2/K - temp = T.value_si viscosity = solventViscosity.value_si # should have units of kg*m/s radius = ((75*self.V/3.14159)**(1/3))/100 # in meters - D = k_b*temp/6/3.14159/viscosity/radius # m2/s + D = k_b*T/6/3.14159/viscosity/radius # m2/s return D def setMcGowanVolume(self, species): From 96c39202d277bb5f8be463d5e28ae602751aa80d Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Thu, 2 May 2013 15:03:42 -0400 Subject: [PATCH 43/55] diffusionLimiter is module-level variable There is one DiffusionLimited object, diffusionLimiter. It is enabled upon startup if there is solvation --- rmgpy/kinetics/diffusionLimited.py | 27 +++++++++++++++++++-------- rmgpy/reaction.py | 7 +++---- rmgpy/rmg/main.py | 8 +++++--- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/rmgpy/kinetics/diffusionLimited.py b/rmgpy/kinetics/diffusionLimited.py index 8dddfe65799..077f5bf44c9 100644 --- a/rmgpy/kinetics/diffusionLimited.py +++ b/rmgpy/kinetics/diffusionLimited.py @@ -1,5 +1,5 @@ import rmgpy.quantity as quantity - +import logging from rmgpy.species import Species from rmgpy.data.solvation import SoluteData, SoluteGroups, SolvationDatabase from rmgpy.reaction import Reaction @@ -7,12 +7,18 @@ class DiffusionLimited(): - def __init__(self, database = None, comment=''): - self.database = SolvationDatabase() - - def getSolventViscosity(T): + def __init__(self): + self.enabled = False + + def enable(self, viscosity, solvationDatabase, comment=''): + logging.info("Enabling diffusion-limited kinetics with solvent viscosity {0!r}".format(viscosity)) + diffusionLimiter.enabled = True + diffusionLimiter.database = solvationDatabase + diffusionLimiter.solventViscosity = viscosity + + def getSolventViscosity(self, T): - return solventViscosity or Quantity(1,"cp") + return self.solventViscosity or Quantity(1,"cp") def getEffectiveRate(self, reaction, T): """ @@ -36,7 +42,7 @@ def getEffectiveRate(self, reaction, T): if products == 1: k_eff = k_forward else: # two products; reverse rate is limited - k_diff = self.getDiffusionLimit(T, forward=False) + k_diff = self.getDiffusionLimit(T, reaction, forward=False) k_eff_reverse = k_reverse*k_diff/(k_reverse+k_diff) k_eff = k_eff_reverse * Keq else: # 2 reactants @@ -68,6 +74,7 @@ def getDiffusionLimit(self, T, reaction, forward=True): radii = 0.0 diffusivities = 0.0 for spec in reacting: + #import ipdb; ipdb.set_trace() soluteData = self.database.getSoluteData(spec) radius = ((75*soluteData.V/3.14159)**(1/3))/100 diff = soluteData.getStokesDiffusivity(T, self.getSolventViscosity(T)) @@ -75,4 +82,8 @@ def getDiffusionLimit(self, T, reaction, forward=True): diffusivities += diff N_a = 6.022e23 # Avogadro's Number k_diff = 4*3.14159*radii*diffusivities*N_a - return k_diff \ No newline at end of file + return k_diff + + +# module level variable. There should only ever be one. It starts off disabled +diffusionLimiter = DiffusionLimited() \ No newline at end of file diff --git a/rmgpy/reaction.py b/rmgpy/reaction.py index 88bae926cf7..125f9b9065c 100644 --- a/rmgpy/reaction.py +++ b/rmgpy/reaction.py @@ -54,7 +54,7 @@ from rmgpy.kinetics import KineticsData, ArrheniusEP, ThirdBody, Lindemann, Troe, Chebyshev, PDepArrhenius, MultiArrhenius, MultiPDepArrhenius #PyDev: @UnresolvedImport from rmgpy.pdep.reaction import calculateMicrocanonicalRateCoefficient -from rmgpy.kinetics.diffusionLimited import DiffusionLimited +from rmgpy.kinetics.diffusionLimited import diffusionLimiter ################################################################################ @@ -514,10 +514,9 @@ def getRateCoefficient(self, T, P=0): temperature `T` in K and pressure `P` in Pa, including any reaction path degeneracies. """ - if DiffusionLimited.solventViscosity: + if diffusionLimiter.enabled: logging.info("Correcting rate for diffusion limited reaction") - diffusionLimited = DiffusionLimited() - return diffusionLimited.getEffectiveRate(self, T) + return diffusionLimiter.getEffectiveRate(self, T) else: return self.kinetics.getRateCoefficient(T, P) diff --git a/rmgpy/rmg/main.py b/rmgpy/rmg/main.py index e1a141c40ea..0bc417d3e7d 100644 --- a/rmgpy/rmg/main.py +++ b/rmgpy/rmg/main.py @@ -52,7 +52,8 @@ from rmgpy.data.kinetics import KineticsLibrary, KineticsFamily, LibraryReaction, TemplateReaction from rmgpy.reaction import Reaction -from rmgpy.kinetics.diffusionLimited import DiffusionLimited +from rmgpy.kinetics.diffusionLimited import diffusionLimiter +from rmgpy.quantity import Quantity from model import Species, CoreEdgeReactionModel from pdep import PDepNetwork @@ -131,7 +132,7 @@ def clear(self): self.kineticsEstimator = 'group additivity' self.solvent = None self.viscosity = None - self.diffusionTemp = None + self.diffusionLimiter = None self.reactionModel = None self.reactionSystems = None @@ -275,10 +276,11 @@ def initialize(self, args): # Load databases self.loadDatabase() + # Do all liquid-phase startup things: if self.solvent: Species.solventData = self.database.solvation.getSolventData(self.solvent) Species.solventName = self.solvent - DiffusionLimited.solventViscosity = self.viscosity + diffusionLimiter.enable(self.viscosity, self.database.solvation) logging.info("Setting solvent data for {0}".format(self.solvent)) # Set wall time From b79a6373c29ff04a237b01bf3bd21b5b0699ad55 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Thu, 2 May 2013 15:04:05 -0400 Subject: [PATCH 44/55] Viscosity should be a Quantity with units --- rmgpy/rmg/input.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rmgpy/rmg/input.py b/rmgpy/rmg/input.py index 4b25dc8b15a..96835e1dad1 100644 --- a/rmgpy/rmg/input.py +++ b/rmgpy/rmg/input.py @@ -152,7 +152,7 @@ def simulator(atol, rtol): def solvation(solvent, viscosity): assert isinstance(solvent,str), "solvent should be a string like 'water'" rmg.solvent = solvent - rmg.viscosity = viscosity + rmg.viscosity = Quantity(viscosity) def model(toleranceMoveToCore, toleranceKeepInEdge=0.0, toleranceInterruptSimulation=1.0, maximumEdgeSpecies=None): rmg.fluxToleranceKeepInEdge = toleranceKeepInEdge From 6b464eca112a14bd379f002fdee0960a50764d00 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Fri, 10 May 2013 11:34:12 -0400 Subject: [PATCH 45/55] Cleaned up an ugly printout in logging --- rmgpy/rmg/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rmgpy/rmg/model.py b/rmgpy/rmg/model.py index 2111ed6524e..7539ef318b5 100644 --- a/rmgpy/rmg/model.py +++ b/rmgpy/rmg/model.py @@ -107,7 +107,7 @@ def generateThermoData(self, database, thermoClass=NASA): # Add on solvation correction if Species.solventData: - logging.info("Making solvent correction for {0} with {1!r}".format(Species.solventName,Species.solventData)) + logging.info("Making solvent correction for {0}".format(Species.solventName)) soluteData = database.solvation.getSoluteData(self) solvation_correction = database.solvation.getSolvationCorrection(soluteData, Species.solventData) #import ipdb; ipdb.set_trace() From 0d85d36bb9162541a489a20d8d25600052393489 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 13 May 2013 15:49:10 -0400 Subject: [PATCH 46/55] Cleanup and comments for some modules. Solvation, DiffusionLimited, Reaction & Model --- rmgpy/data/solvation.py | 82 ++++++++++-------------------- rmgpy/kinetics/diffusionLimited.py | 2 +- rmgpy/reaction.py | 4 ++ rmgpy/rmg/model.py | 2 +- 4 files changed, 32 insertions(+), 58 deletions(-) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index 996fb16de56..d2a5b0b9eb6 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -117,7 +117,7 @@ def getStokesDiffusivity(self, T, solventViscosity): found from the McGowan volume. """ k_b = 1.3806488e-23 # m2*kg/s2/K - viscosity = solventViscosity.value_si # should have units of kg*m/s + viscosity = solventViscosity.value_si # should have units of kg/m*s radius = ((75*self.V/3.14159)**(1/3))/100 # in meters D = k_b*T/6/3.14159/viscosity/radius # m2/s return D @@ -177,6 +177,7 @@ def setMcGowanVolume(self, species): Vtot = Vtot + thisV for bond in molecule.getBonds(atom): + # divide contribution in half since all bonds would be counted twice this way Vtot = Vtot - 6.56/2 self.V= Vtot / 100; # division by 100 to get units correct. @@ -235,7 +236,7 @@ def getSolventData(self, label): class SoluteLibrary(Database): """ - A class for working with a RMG solute library. + A class for working with a RMG solute library. Not currently used. """ def __init__(self, label='', name='', shortDesc='', longDesc=''): Database.__init__(self, label=label, name=name, shortDesc=shortDesc, longDesc=longDesc) @@ -352,7 +353,6 @@ class SolvationDatabase(object): """ def __init__(self): - #self.depository = {} self.solventLibrary = SolventLibrary() self.soluteLibrary = SoluteLibrary() self.groups = {} @@ -377,7 +377,6 @@ def __setstate__(self, d): """ A helper function used when unpickling a SoluteDatabase object. """ - #self.depository = d['depository'] self.libraries = d['libraries'] self.groups = d['groups'] self.libraryOrder = d['libraryOrder'] @@ -386,6 +385,8 @@ def load(self, path, libraries=None, depository=True): """ Load the solvation database from the given `path` on disk, where `path` points to the top-level folder of the solvation database. + + Load the solvent and solute (not used) libraries, then the solute groups. """ self.solventLibrary.load(os.path.join(path,'libraries','solvent.py')) @@ -396,18 +397,14 @@ def load(self, path, libraries=None, depository=True): def getSolventData(self, solvent_name): return self.solventLibrary.getSolventData(solvent_name) - def loadDepository(self, path): - """ - Load the thermo database from the given `path` on disk, where `path` - points to the top-level folder of the thermo database. - """ - raise NotImplementedError() - - + def loadGroups(self, path): """ Load the solute database from the given `path` on disk, where `path` points to the top-level folder of the solute database. + + Two sets of groups for additivity, atom-centered ('abraham') and non atom-centered + ('nonacentered'). """ logging.info('Loading Platts additivity group database from {0}...'.format(path)) self.groups = {} @@ -424,13 +421,6 @@ def save(self, path): self.saveLibraries(os.path.join(path, 'libraries')) self.saveGroups(os.path.join(path, 'groups')) - def saveDepository(self, path): - """ - Save the thermo depository to the given `path` on disk, where `path` - points to the top-level folder of the thermo depository. - """ - raise NotImplementedError() - def saveLibraries(self, path): """ Save the solute libraries to the given `path` on disk, where `path` @@ -454,10 +444,6 @@ def loadOld(self, path): Load the old RMG solute database from the given `path` on disk, where `path` points to the top-level folder of the old RMG database. """ - # The old database does not have a depository, so create an empty one - # self.depository = {} - # self.depository['stable'] = ThermoDepository(label='stable', name='Stable Molecules') - # self.depository['radical'] = ThermoDepository(label='radical', name='Radical Molecules') for (root, dirs, files) in os.walk(os.path.join(path, 'thermo_libraries')): if os.path.exists(os.path.join(root, 'Dictionary.txt')) and os.path.exists(os.path.join(root, 'Library.txt')): @@ -518,14 +504,15 @@ def getSoluteData(self, species): """ soluteData = None - # Check the library first + # Check the library first (not currently used) soluteData = self.getSoluteDataFromLibrary(species, self.soluteLibrary) if soluteData is not None: soluteData[0].comment = 'solute' else: # Solute not found in any loaded libraries, so estimate soluteData = self.getSoluteDataFromGroups(species) - # Return the resulting solute parameters + # Return the resulting solute parameters S, B, E, L, A + # Set McGowan Volume (V) soluteData.setMcGowanVolume(species) return soluteData @@ -538,14 +525,6 @@ def getAllSoluteData(self, species): for a generic search job. """ raise NotImplementedError() - - def getThermoDataFromDepository(self, species): - """ - Return all possible sets of thermodynamic parameters for a given - :class:`Species` object `species` from the depository. If no - depository is loaded, a :class:`DatabaseError` is raised. - """ - raise NotImplementedError() def getSoluteDataFromLibrary(self, species, library): """ @@ -586,13 +565,14 @@ def getSoluteDataFromGroups(self, species): soluteData.A += sdata.A count += 1 comments.append(sdata.comment) - # print count #debugging purposes soluteData.S /= count soluteData.B /= count soluteData.E /= count soluteData.L /= count soluteData.A /= count + + # Print groups that are used for debugging purposes soluteData.comment = "Average of {0}".format(" and ".join(comments)) return soluteData @@ -609,7 +589,7 @@ def estimateSoluteViaGroupAdditivity(self, molecule): # will probably not visit the right atoms, and so will get the thermo wrong molecule.sortVertices() - # Create the SoluteData object + # Create the SoluteData object with the intercepts from the Platts groups soluteData = SoluteData( S = 0.277, B = 0.071, @@ -648,8 +628,6 @@ def estimateSoluteViaGroupAdditivity(self, molecule): # Get solute descriptor estimates for saturated form of structure soluteData = self.estimateSoluteViaGroupAdditivity(saturatedStruct) assert soluteData is not None, "Solute data of saturated {0} of molecule {1} is None!".format(saturatedStruct, molecule) - # Undo symmetry number correction for saturated structure - # thermoData.S298.value_si += constants.R * math.log(saturatedStruct.symmetryNumber) # For each radical site, get radical correction # Only one radical site should be considered at a time; all others @@ -663,23 +641,14 @@ def estimateSoluteViaGroupAdditivity(self, molecule): atom.incrementRadical() saturatedStruct.updateConnectivityValues() - - - - # Re-saturate - - - # Subtract the enthalpy of the added hydrogens - - - # Correct the entropy for the symmetry number else: # non-radical species # Generate estimate of solute data for atom in molecule.atoms: # Iterate over heavy (non-hydrogen) atoms if atom.isNonHydrogen(): - # Get initial solute data from main group database + # Get initial solute data from main group database. Every atom must + # be found in the main abraham database try: self.__addGroupSoluteData(soluteData, self.groups['abraham'], molecule, {'*':atom}) except KeyError: @@ -687,12 +656,12 @@ def estimateSoluteViaGroupAdditivity(self, molecule): logging.error(molecule) logging.error(molecule.toAdjacencyList()) raise - # Get solute data for non-atom centered groups + # Get solute data for non-atom centered groups (being found in this group + # database is optional) try: self.__addGroupSoluteData(soluteData, self.groups['nonacentered'], molecule, {'*':atom}) except KeyError: pass - return soluteData def __addGroupSoluteData(self, soluteData, database, molecule, atom): @@ -732,12 +701,8 @@ def __addGroupSoluteData(self, soluteData, database, molecule, atom): # result = ' -> ' + node + result # node = database.tree.parent[node] #print result[4:] - - #if len(thermoData.Tdata.value_si) != len(data.Tdata.value_si) or any([T1 != T2 for T1, T2 in zip(thermoData.Tdata.value_si, data.Tdata.value_si)]): - #raise ThermoError('Cannot add these ThermoData objects due to their having different temperature points.') - #for i in range(7): - #thermoData.Cpdata.value_si[i] += data.Cpdata.value_si[i] + # Add solute data for each atom to the overall solute data for the molecule. soluteData.S += data.S soluteData.B += data.B soluteData.E += data.E @@ -749,20 +714,25 @@ def __addGroupSoluteData(self, soluteData, database, molecule, atom): def calcH(self, soluteData, solventData): + # Use Mintz parameters for solvents delH = 1000*((soluteData.S*solventData.s_h)+(soluteData.B*solventData.b_h)+(soluteData.E*solventData.e_h)+(soluteData.L*solventData.l_h)+(soluteData.A*solventData.a_h)+solventData.c_h) return delH def calcG(self, soluteData, solventData): + # Use Abraham parameters for solvents logK = (soluteData.S*solventData.s_g)+(soluteData.B*solventData.b_g)+(soluteData.E*solventData.e_g)+(soluteData.L*solventData.l_g)+(soluteData.A*solventData.a_g)+solventData.c_g delG = -8.314*298*2.303*logK return delG - def calcS(self, delG, delH): delS = (delH-delG)/298 return delS def getSolvationCorrection(self, soluteData, solventData): + """ + Given a soluteData and solventData object, calculates the enthalpy, entropy, + and Gibbs free energy of solvation at 298 K + """ correction = SolvationCorrection(0.0, 0.0, 0.0) correction.enthalpy = self.calcH(soluteData, solventData) correction.gibbs = self.calcG(soluteData, solventData) diff --git a/rmgpy/kinetics/diffusionLimited.py b/rmgpy/kinetics/diffusionLimited.py index 077f5bf44c9..9fa6ebca0b2 100644 --- a/rmgpy/kinetics/diffusionLimited.py +++ b/rmgpy/kinetics/diffusionLimited.py @@ -74,8 +74,8 @@ def getDiffusionLimit(self, T, reaction, forward=True): radii = 0.0 diffusivities = 0.0 for spec in reacting: - #import ipdb; ipdb.set_trace() soluteData = self.database.getSoluteData(spec) + # calculate radius with the McGowan volume and assuming sphere radius = ((75*soluteData.V/3.14159)**(1/3))/100 diff = soluteData.getStokesDiffusivity(T, self.getSolventViscosity(T)) radii += radius diff --git a/rmgpy/reaction.py b/rmgpy/reaction.py index 125f9b9065c..aa5c0d0d723 100644 --- a/rmgpy/reaction.py +++ b/rmgpy/reaction.py @@ -513,6 +513,10 @@ def getRateCoefficient(self, T, P=0): Return the overall rate coefficient for the forward reaction at temperature `T` in K and pressure `P` in Pa, including any reaction path degeneracies. + + If diffusionLimiter is enabled, the reaction is in the liquid phase and we use + a diffusion limitation to correct the rate. If not, then use the intrinsic rate + coefficient. """ if diffusionLimiter.enabled: logging.info("Correcting rate for diffusion limited reaction") diff --git a/rmgpy/rmg/model.py b/rmgpy/rmg/model.py index 7539ef318b5..a9751d721dc 100644 --- a/rmgpy/rmg/model.py +++ b/rmgpy/rmg/model.py @@ -110,7 +110,7 @@ def generateThermoData(self, database, thermoClass=NASA): logging.info("Making solvent correction for {0}".format(Species.solventName)) soluteData = database.solvation.getSoluteData(self) solvation_correction = database.solvation.getSolvationCorrection(soluteData, Species.solventData) - #import ipdb; ipdb.set_trace() + # correction is added to the entropy and enthalpy wilhoit.S0.value_si = (wilhoit.S0.value_si + solvation_correction.entropy) wilhoit.H0.value_si = (wilhoit.H0.value_si + solvation_correction.enthalpy) From 4f114f2a2c5c73639a4173218c123162aa5588e6 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Wed, 15 May 2013 15:38:07 -0400 Subject: [PATCH 47/55] Solvent viscosity can now be calculated at any temperature. We use 5 solvent parameters from the DIPPR for a viscosity correlation found in Perry's Handbook. --- examples/rmg/liquid_phase/input.py | 7 +++---- rmgpy/data/solvation.py | 20 ++++++++++++++++++-- rmgpy/kinetics/diffusionLimited.py | 16 +++++++++------- rmgpy/rmg/input.py | 4 ++-- rmgpy/rmg/main.py | 3 +-- 5 files changed, 33 insertions(+), 17 deletions(-) diff --git a/examples/rmg/liquid_phase/input.py b/examples/rmg/liquid_phase/input.py index b227e958c46..236771e8a49 100644 --- a/examples/rmg/liquid_phase/input.py +++ b/examples/rmg/liquid_phase/input.py @@ -35,8 +35,7 @@ ) solvation( - solvent='octane', - viscosity = (1e-3, "cP") + solvent='octane' ) simulator( @@ -46,8 +45,8 @@ model( toleranceKeepInEdge=1E-9, - toleranceMoveToCore=0.1, - toleranceInterruptSimulation=1E9, + toleranceMoveToCore=0.001, + toleranceInterruptSimulation=0.1, maximumEdgeSpecies=100000 ) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index d2a5b0b9eb6..ca60a8a3686 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -31,6 +31,7 @@ """ """ +import rmgpy.quantity as quantity import os import os.path @@ -73,7 +74,8 @@ class SolventData(): Stores Abraham/Mintz parameters for characterizing a solvent. """ def __init__(self, s_h=None, b_h=None, e_h=None, l_h=None, a_h=None, - c_h=None, s_g=None, b_g=None, e_g=None, l_g=None, a_g=None, c_g=None): + c_h=None, s_g=None, b_g=None, e_g=None, l_g=None, a_g=None, c_g=None, A=None, B=None, + C=None, D=None, E=None): self.s_h = s_h self.b_h = b_h self.e_h = e_h @@ -86,7 +88,21 @@ def __init__(self, s_h=None, b_h=None, e_h=None, l_h=None, a_h=None, self.l_g = l_g self.a_g = a_g self.c_g = c_g - + # These are parameters for calculating viscosity + self.A = A + self.B = B + self.C = C + self.D = D + self.E = E + + def getSolventViscosity(self, T): + """ + Returns the viscosity in cp, according to correlation in Perry's Handbook + and coefficients in DIPPR + """ + viscosity = (math.exp(self.A + (self.B / T) + (self.C*math.log(T)) + (self.D * (T**self.E))))/1000 + return Quantity(viscosity, "cp") + class SolvationCorrection(): """ Stores corrections for enthalpy, entropy, and Gibbs free energy when a species is solvated. diff --git a/rmgpy/kinetics/diffusionLimited.py b/rmgpy/kinetics/diffusionLimited.py index 9fa6ebca0b2..b418f9271a3 100644 --- a/rmgpy/kinetics/diffusionLimited.py +++ b/rmgpy/kinetics/diffusionLimited.py @@ -1,25 +1,27 @@ import rmgpy.quantity as quantity import logging from rmgpy.species import Species -from rmgpy.data.solvation import SoluteData, SoluteGroups, SolvationDatabase +from rmgpy.data.solvation import SolventData, SoluteData, SoluteGroups, SolvationDatabase from rmgpy.reaction import Reaction class DiffusionLimited(): def __init__(self): + # default is false, enabled if there is a solvent self.enabled = False - def enable(self, viscosity, solvationDatabase, comment=''): - logging.info("Enabling diffusion-limited kinetics with solvent viscosity {0!r}".format(viscosity)) + def enable(self, solventData, solvationDatabase, comment=''): + # diffusionLimiter is enabled if a solvent has been added to the RMG object. + logging.info("Enabling diffusion-limited kinetics...") diffusionLimiter.enabled = True diffusionLimiter.database = solvationDatabase - diffusionLimiter.solventViscosity = viscosity + diffusionLimiter.solventData = solventData def getSolventViscosity(self, T): - - return self.solventViscosity or Quantity(1,"cp") - + solventViscosity = self.solventData.getSolventViscosity(T) + return solventViscosity or Quantity(1, "cp") + def getEffectiveRate(self, reaction, T): """ Return the ratio of k_eff to k_intrinsic, which is between 0 and 1. diff --git a/rmgpy/rmg/input.py b/rmgpy/rmg/input.py index 96835e1dad1..46b1ba5e50b 100644 --- a/rmgpy/rmg/input.py +++ b/rmgpy/rmg/input.py @@ -149,10 +149,10 @@ def simulator(atol, rtol): rmg.absoluteTolerance = atol rmg.relativeTolerance = rtol -def solvation(solvent, viscosity): +def solvation(solvent): + # If solvation module in input file, set the RMG solvent variable assert isinstance(solvent,str), "solvent should be a string like 'water'" rmg.solvent = solvent - rmg.viscosity = Quantity(viscosity) def model(toleranceMoveToCore, toleranceKeepInEdge=0.0, toleranceInterruptSimulation=1.0, maximumEdgeSpecies=None): rmg.fluxToleranceKeepInEdge = toleranceKeepInEdge diff --git a/rmgpy/rmg/main.py b/rmgpy/rmg/main.py index 0bc417d3e7d..686176d9227 100644 --- a/rmgpy/rmg/main.py +++ b/rmgpy/rmg/main.py @@ -131,7 +131,6 @@ def clear(self): self.kineticsDepositories = None self.kineticsEstimator = 'group additivity' self.solvent = None - self.viscosity = None self.diffusionLimiter = None self.reactionModel = None @@ -280,7 +279,7 @@ def initialize(self, args): if self.solvent: Species.solventData = self.database.solvation.getSolventData(self.solvent) Species.solventName = self.solvent - diffusionLimiter.enable(self.viscosity, self.database.solvation) + diffusionLimiter.enable(Species.solventData, self.database.solvation) logging.info("Setting solvent data for {0}".format(self.solvent)) # Set wall time From 1f70cdc7d788d56e60a6927658bdb983bdd97bc7 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Wed, 15 May 2013 15:38:39 -0400 Subject: [PATCH 48/55] Error message now more clear. --- rmgpy/rmg/output.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rmgpy/rmg/output.py b/rmgpy/rmg/output.py index a05d574fa9d..e5d796612ef 100644 --- a/rmgpy/rmg/output.py +++ b/rmgpy/rmg/output.py @@ -66,7 +66,7 @@ def saveOutputHTML(path, reactionModel): try: import jinja2 except ImportError: - logging.warning("jinja package not found; HTML output will not be saved.") + logging.warning("jinja2 package not found; HTML output will not be saved.") return path = os.path.abspath(path) From d2e5aafeb86d79babb55d69a5b1af7b50c94e20c Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Wed, 22 May 2013 16:37:17 -0400 Subject: [PATCH 49/55] Keep viscosity a value, rather than a Quantity. This will speed things up a little by not doing quantity conversions for every reaction and simulation step. --- rmgpy/data/solvation.py | 8 +++----- rmgpy/kinetics/diffusionLimited.py | 3 +-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index ca60a8a3686..e3c8d0bf146 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -97,11 +97,10 @@ def __init__(self, s_h=None, b_h=None, e_h=None, l_h=None, a_h=None, def getSolventViscosity(self, T): """ - Returns the viscosity in cp, according to correlation in Perry's Handbook + Returns the viscosity in Pa s, according to correlation in Perry's Handbook and coefficients in DIPPR """ - viscosity = (math.exp(self.A + (self.B / T) + (self.C*math.log(T)) + (self.D * (T**self.E))))/1000 - return Quantity(viscosity, "cp") + return math.exp(self.A + (self.B / T) + (self.C*math.log(T)) + (self.D * (T**self.E))) class SolvationCorrection(): """ @@ -133,9 +132,8 @@ def getStokesDiffusivity(self, T, solventViscosity): found from the McGowan volume. """ k_b = 1.3806488e-23 # m2*kg/s2/K - viscosity = solventViscosity.value_si # should have units of kg/m*s radius = ((75*self.V/3.14159)**(1/3))/100 # in meters - D = k_b*T/6/3.14159/viscosity/radius # m2/s + D = k_b*T/6/3.14159/solventViscosity/radius # m2/s return D def setMcGowanVolume(self, species): diff --git a/rmgpy/kinetics/diffusionLimited.py b/rmgpy/kinetics/diffusionLimited.py index b418f9271a3..04eb9d12f23 100644 --- a/rmgpy/kinetics/diffusionLimited.py +++ b/rmgpy/kinetics/diffusionLimited.py @@ -19,8 +19,7 @@ def enable(self, solventData, solvationDatabase, comment=''): diffusionLimiter.solventData = solventData def getSolventViscosity(self, T): - solventViscosity = self.solventData.getSolventViscosity(T) - return solventViscosity or Quantity(1, "cp") + return self.solventData.getSolventViscosity(T) def getEffectiveRate(self, reaction, T): """ From c73756839bfd6d7da9b740c1d3dced3bd6376b89 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Wed, 22 May 2013 16:38:36 -0400 Subject: [PATCH 50/55] Calculation of intrinsic rate correction for a solvent. Using Abraham's A & B values for solutes, calculate a correction to the intrinsic rate constant for a reaction in solution. --- rmgpy/data/solvation.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index e3c8d0bf146..70b426d1ecb 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -75,7 +75,7 @@ class SolventData(): """ def __init__(self, s_h=None, b_h=None, e_h=None, l_h=None, a_h=None, c_h=None, s_g=None, b_g=None, e_g=None, l_g=None, a_g=None, c_g=None, A=None, B=None, - C=None, D=None, E=None): + C=None, D=None, E=None, alpha=None, beta=None): self.s_h = s_h self.b_h = b_h self.e_h = e_h @@ -94,6 +94,16 @@ def __init__(self, s_h=None, b_h=None, e_h=None, l_h=None, a_h=None, self.C = C self.D = D self.E = E + # These are SOLUTE parameters used for intrinsic rate correction in H-abstraction rxns + self.alpha = alpha + self.beta = beta + + def getIntrinsicCorrection + """ + If solvation is on, this will give the log10 of the ratio of the intrinsic rate + constants log10(k_sol/k_gas) for H-abstraction rxns + """ + return -8.3*self.alpha*self.beta def getSolventViscosity(self, T): """ From a8b0c43de1898e77098a7b8a0e020c4eb2141339 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 10 Jun 2013 17:05:41 -0400 Subject: [PATCH 51/55] Removed unused import. --- rmgpy/data/kinetics/family.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rmgpy/data/kinetics/family.py b/rmgpy/data/kinetics/family.py index e40175d4bec..452d7a9d2d3 100644 --- a/rmgpy/data/kinetics/family.py +++ b/rmgpy/data/kinetics/family.py @@ -39,7 +39,7 @@ from rmgpy.data.base import Database, Entry, LogicNode, LogicOr, ForbiddenStructures,\ ForbiddenStructureException, getAllCombinations -from rmgpy.reaction import Reaction, ReactionError +from rmgpy.reaction import Reaction from rmgpy.kinetics import Arrhenius, ArrheniusEP, ThirdBody, Lindemann, Troe, \ PDepArrhenius, MultiArrhenius, MultiPDepArrhenius, \ Chebyshev, KineticsData, PDepKineticsModel From eca7ca5f03fab936a258337af2dda11619e4f262 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 10 Jun 2013 17:06:16 -0400 Subject: [PATCH 52/55] Fix to solvation module. --- rmgpy/data/solvation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index 70b426d1ecb..e315b8121d1 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -98,7 +98,7 @@ def __init__(self, s_h=None, b_h=None, e_h=None, l_h=None, a_h=None, self.alpha = alpha self.beta = beta - def getIntrinsicCorrection + def getIntrinsicCorrection(self): """ If solvation is on, this will give the log10 of the ratio of the intrinsic rate constants log10(k_sol/k_gas) for H-abstraction rxns From a46b97942acc1fe1c26840b5a83cd61a251838ef Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 10 Jun 2013 17:09:20 -0400 Subject: [PATCH 53/55] Speed up calculations for k_eff. If we already know the k_eff for a reaction at a given temperature, we don't need to calculate it again, just look it up. --- rmgpy/reaction.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/rmgpy/reaction.py b/rmgpy/reaction.py index aa5c0d0d723..f222722ab04 100644 --- a/rmgpy/reaction.py +++ b/rmgpy/reaction.py @@ -100,6 +100,9 @@ def __init__(self, index=-1, label='', reactants=None, products=None, kinetics=N self.duplicate = duplicate self.degeneracy = degeneracy self.pairs = pairs + + if diffusionLimiter.enabled: + self.__k_effective_cache = {} def __repr__(self): """ @@ -519,8 +522,12 @@ def getRateCoefficient(self, T, P=0): coefficient. """ if diffusionLimiter.enabled: - logging.info("Correcting rate for diffusion limited reaction") - return diffusionLimiter.getEffectiveRate(self, T) + try: + k = self.__k_effective_cache[T] + except KeyError: + k = diffusionLimiter.getEffectiveRate(self, T) + self.__k_effective_cache[T] = k + return k else: return self.kinetics.getRateCoefficient(T, P) From 54d1399adaa73b4a9991130f4bb15b841f2b6e6c Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Tue, 11 Jun 2013 17:51:11 -0400 Subject: [PATCH 54/55] Changes for using a solute library. --- rmgpy/data/solvation.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index e315b8121d1..58caee7aaac 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -279,7 +279,7 @@ def loadEntry(self, self.entries[label] = Entry( index = index, label = label, - item = Molecule().fromAdjacencyList(molecule), + item = Molecule(SMILES=molecule), data = solute, reference = reference, referenceType = referenceType, @@ -528,18 +528,17 @@ def getSoluteData(self, species): """ soluteData = None - # Check the library first (not currently used) + # Check the library first soluteData = self.getSoluteDataFromLibrary(species, self.soluteLibrary) if soluteData is not None: - soluteData[0].comment = 'solute' + soluteData[0].comment = 'solute' else: # Solute not found in any loaded libraries, so estimate soluteData = self.getSoluteDataFromGroups(species) + # No Platts group additivity for V, so set using atom sizes + soluteData.setMcGowanVolume(species) # Return the resulting solute parameters S, B, E, L, A - # Set McGowan Volume (V) - soluteData.setMcGowanVolume(species) return soluteData - def getAllSoluteData(self, species): """ From f082a5c05274b1f33c701b411fd97d01fb7ab5b4 Mon Sep 17 00:00:00 2001 From: Belinda Slakman Date: Mon, 22 Jul 2013 12:16:43 -0400 Subject: [PATCH 55/55] Fixed some bugs involving solvation --- rmgpy/data/solvation.py | 4 +++- rmgpy/rmg/model.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py index 58caee7aaac..2e40bd06ae4 100644 --- a/rmgpy/data/solvation.py +++ b/rmgpy/data/solvation.py @@ -75,7 +75,7 @@ class SolventData(): """ def __init__(self, s_h=None, b_h=None, e_h=None, l_h=None, a_h=None, c_h=None, s_g=None, b_g=None, e_g=None, l_g=None, a_g=None, c_g=None, A=None, B=None, - C=None, D=None, E=None, alpha=None, beta=None): + C=None, D=None, E=None, alpha=None, beta=None, eps=None): self.s_h = s_h self.b_h = b_h self.e_h = e_h @@ -97,6 +97,8 @@ def __init__(self, s_h=None, b_h=None, e_h=None, l_h=None, a_h=None, # These are SOLUTE parameters used for intrinsic rate correction in H-abstraction rxns self.alpha = alpha self.beta = beta + # This is the dielectric constant + self.eps = eps def getIntrinsicCorrection(self): """ diff --git a/rmgpy/rmg/model.py b/rmgpy/rmg/model.py index d8d294f7b5e..980aa272832 100644 --- a/rmgpy/rmg/model.py +++ b/rmgpy/rmg/model.py @@ -142,9 +142,9 @@ def generateThermoData(self, database, thermoClass=NASA, quantumMechanics=None): if thermo0 is None: thermo0 = database.thermo.getThermoData(self) - return self.processThermoData(thermo0, thermoClass) + return self.processThermoData(database, thermo0, thermoClass) - def processThermoData(self, thermo0, thermoClass=NASA): + def processThermoData(self, database, thermo0, thermoClass=NASA): """ Converts via Wilhoit into required `thermoClass` and sets `E0`.