diff --git a/rmgpy/cantherm/input.py b/rmgpy/cantherm/input.py index 9b94d995f0..0e5e0b9bd1 100644 --- a/rmgpy/cantherm/input.py +++ b/rmgpy/cantherm/input.py @@ -51,6 +51,7 @@ from rmgpy.kinetics.chebyshev import Chebyshev from rmgpy.kinetics.falloff import ThirdBody, Lindemann, Troe from rmgpy.kinetics.kineticsdata import KineticsData, PDepKineticsData +from rmgpy.kinetics.model import TunnelingModel from rmgpy.kinetics.tunneling import Wigner, Eckart from rmgpy.pdep.configuration import Configuration @@ -65,6 +66,7 @@ from rmgpy.cantherm.statmech import StatMechJob, assign_frequency_scale_factor from rmgpy.cantherm.thermo import ThermoJob from rmgpy.cantherm.pdep import PressureDependenceJob +from rmgpy.cantherm.common import is_pdep ################################################################################ @@ -76,6 +78,7 @@ ################################################################################ + def species(label, *args, **kwargs): global speciesDict, jobList if label in speciesDict: @@ -124,15 +127,26 @@ def species(label, *args, **kwargs): else: raise TypeError('species() got an unexpected keyword argument {0!r}.'.format(key)) - if structure: spec.molecule = [structure] + if structure: + spec.molecule = [structure] spec.conformer = Conformer(E0=E0, modes=modes, spinMultiplicity=spinMultiplicity, opticalIsomers=opticalIsomers) - spec.molecularWeight = molecularWeight + if molecularWeight is not None: + spec.molecularWeight = molecularWeight + else: + if structure: + # If a structure was given, simply calling spec.molecularWeight will calculate the molecular weight + spec.molecularWeight = spec.molecularWeight + elif is_pdep(jobList): + # If one of the jobs is pdep and no molecular weight is given or calculated, raise an error + raise ValueError("No molecularWeight was entered for species {0}. Since a structure wasn't given" + " as well, the molecularWeight cannot be reconstructed.".format(spec.label)) spec.transportData = collisionModel spec.energyTransferModel = energyTransferModel spec.thermo = thermo return spec + def transitionState(label, *args, **kwargs): global transitionStateDict if label in transitionStateDict: @@ -173,9 +187,9 @@ def transitionState(label, *args, **kwargs): return ts + def reaction(label, reactants, products, transitionState, kinetics=None, tunneling=''): global reactionDict, speciesDict, transitionStateDict - #label = 'reaction'+transitionState if label in reactionDict: label = label+transitionState if label in reactionDict: @@ -194,9 +208,9 @@ def reaction(label, reactants, products, transitionState, kinetics=None, tunneli raise ValueError('Unknown tunneling model {0!r}.'.format(tunneling)) rxn = Reaction(label=label, reactants=reactants, products=products, transitionState=transitionState, kinetics=kinetics) reactionDict[label] = rxn - return rxn + def network(label, isomers=None, reactants=None, products=None, pathReactions=None, bathGas=None): global networkDict, speciesDict, reactionDict logging.info('Loading network {0}...'.format(label)) @@ -266,6 +280,7 @@ def network(label, isomers=None, reactants=None, products=None, pathReactions=No ) networkDict[label] = network + def kinetics(label, Tmin=None, Tmax=None, Tlist=None, Tcount=0, sensitivity_conditions=None): global jobList, reactionDict try: @@ -276,6 +291,7 @@ def kinetics(label, Tmin=None, Tmax=None, Tlist=None, Tcount=0, sensitivity_cond sensitivity_conditions=sensitivity_conditions) jobList.append(job) + def statmech(label): global jobList, speciesDict, transitionStateDict if label in speciesDict or label in transitionStateDict: @@ -287,6 +303,7 @@ def statmech(label): else: raise ValueError('Unknown species or transition state label {0!r} for statmech() job.'.format(label)) + def thermo(label, thermoClass): global jobList, speciesDict try: @@ -296,6 +313,7 @@ def thermo(label, thermoClass): job = ThermoJob(species=spec, thermoClass=thermoClass) jobList.append(job) + def pressureDependence(label, Tmin=None, Tmax=None, Tcount=0, Tlist=None, Pmin=None, Pmax=None, Pcount=0, Plist=None, @@ -315,17 +333,22 @@ def pressureDependence(label, rmgmode=rmgmode, sensitivity_conditions=sensitivity_conditions) jobList.append(job) + def SMILES(smiles): return Molecule().fromSMILES(smiles) + def adjacencyList(adj): return Molecule().fromAdjacencyList(adj) + def InChI(inchi): return Molecule().fromInChI(inchi) + ################################################################################ + def loadInputFile(path): """ Load the CanTherm input file located at `path` on disk, and return a list of @@ -388,27 +411,41 @@ def loadInputFile(path): logging.error('The input file {0!r} was invalid:'.format(path)) raise - modelChemistry = local_context.get('modelChemistry', '') + model_chemistry = local_context.get('modelChemistry', '') + level_of_theory = local_context.get('levelOfTheory', '') + author = local_context.get('author', '') if 'frequencyScaleFactor' not in local_context: logging.debug('Assigning a frequencyScaleFactor according to the modelChemistry...') - frequencyScaleFactor = assign_frequency_scale_factor(modelChemistry) + frequency_scale_factor = assign_frequency_scale_factor(model_chemistry) else: - frequencyScaleFactor = local_context.get('frequencyScaleFactor') - useHinderedRotors = local_context.get('useHinderedRotors', True) - useAtomCorrections = local_context.get('useAtomCorrections', True) - useBondCorrections = local_context.get('useBondCorrections', False) - atomEnergies = local_context.get('atomEnergies', None) + frequency_scale_factor = local_context.get('frequencyScaleFactor') + use_hindered_rotors = local_context.get('useHinderedRotors', True) + use_atom_corrections = local_context.get('useAtomCorrections', True) + use_bond_corrections = local_context.get('useBondCorrections', False) + atom_energies = local_context.get('atomEnergies', None) directory = os.path.dirname(path) for job in jobList: if isinstance(job, StatMechJob): job.path = os.path.join(directory, job.path) - job.modelChemistry = modelChemistry.lower() - job.frequencyScaleFactor = frequencyScaleFactor - job.includeHinderedRotors = useHinderedRotors - job.applyAtomEnergyCorrections = useAtomCorrections - job.applyBondEnergyCorrections = useBondCorrections - job.atomEnergies = atomEnergies + job.modelChemistry = model_chemistry.lower() + job.frequencyScaleFactor = frequency_scale_factor + job.includeHinderedRotors = use_hindered_rotors + job.applyAtomEnergyCorrections = use_atom_corrections + job.applyBondEnergyCorrections = use_bond_corrections + job.atomEnergies = atom_energies + if isinstance(job, ThermoJob): + job.cantherm_species.author = author + job.cantherm_species.level_of_theory = level_of_theory + level_of_theory_energy = level_of_theory.split('//')[0] + if level_of_theory_energy != model_chemistry: + # Only log the model chemistry if it isn't identical to the first part of level_of_theory + job.cantherm_species.model_chemistry = model_chemistry + job.cantherm_species.frequency_scale_factor = frequency_scale_factor + job.cantherm_species.use_hindered_rotors = use_hindered_rotors + job.cantherm_species.use_bond_corrections = use_bond_corrections + if atom_energies is not None: + job.cantherm_species.atom_energies = atom_energies return jobList diff --git a/rmgpy/cantherm/main.py b/rmgpy/cantherm/main.py index cdb62cfdbd..a28ef14471 100644 --- a/rmgpy/cantherm/main.py +++ b/rmgpy/cantherm/main.py @@ -51,9 +51,11 @@ from rmgpy.cantherm.statmech import StatMechJob from rmgpy.cantherm.thermo import ThermoJob from rmgpy.cantherm.pdep import PressureDependenceJob +from rmgpy.cantherm.common import is_pdep ################################################################################ + class CanTherm: """ The :class:`CanTherm` class represents an instance of CanTherm, a tool for @@ -253,8 +255,10 @@ def execute(self): # run thermo jobs (printing out thermo stuff) for job in self.jobList: - if isinstance(job,ThermoJob) or isinstance(job, StatMechJob): + if isinstance(job, ThermoJob): job.execute(outputFile=outputFile, plot=self.plot) + if isinstance(job, StatMechJob): + job.execute(outputFile=outputFile, plot=self.plot, pdep=is_pdep(self.jobList)) with open(chemkinFile, 'a') as f: f.write('\n') diff --git a/rmgpy/cantherm/statmech.py b/rmgpy/cantherm/statmech.py index bf3ebb78c1..6de23cee1b 100644 --- a/rmgpy/cantherm/statmech.py +++ b/rmgpy/cantherm/statmech.py @@ -53,6 +53,7 @@ from rmgpy.statmech.torsion import Torsion, HinderedRotor, FreeRotor from rmgpy.statmech.conformer import Conformer from rmgpy.exceptions import InputError +from rmgpy.cantherm.common import CanthermSpecies ################################################################################ @@ -66,6 +67,7 @@ ################################################################################ + class ScanLog(object): """ Represent a text file containing a table of angles and corresponding @@ -180,29 +182,43 @@ def __init__(self, species, path): self.applyAtomEnergyCorrections = True self.applyBondEnergyCorrections = True self.atomEnergies = None + if isinstance(species, Species): + # Currently we do not dump and load transition states in YAML form + self.cantherm_species = CanthermSpecies(species=species) - def execute(self, outputFile=None, plot=False): + def execute(self, outputFile=None, plot=False, pdep=False): """ Execute the statistical mechanics job, saving the results to the given `outputFile` on disk. + `pdep` passed to load() and is used to distinguish between necessary and unnecessary + species attributes when loading a YAML file. """ - self.load() + self.load(pdep) if outputFile is not None: self.save(outputFile=outputFile) logging.debug('Finished statmech job for species {0}.'.format(self.species)) logging.debug(repr(self.species)) - def load(self): + def load(self, pdep): """ Load the statistical mechanics parameters for each conformer from the associated files on disk. Creates :class:`Conformer` objects for each conformer and appends them to the list of conformers on the species object. """ - logging.info('Loading statistical mechanics parameters for {0}...'.format(self.species.label)) - path = self.path TS = isinstance(self.species, TransitionState) + filename, file_extension = os.path.splitext(path) + if file_extension in ['.yml', '.yaml']: + if TS: + raise ValueError('Loading transition states from a YAML file is still unsupported.') + self.cantherm_species.load_yaml(path=path, species=self.species, pdep=pdep) + self.species.conformer = self.cantherm_species.conformer + self.species.transportData = self.cantherm_species.transport_data + self.species.energyTransferModel = self.cantherm_species.energy_transfer_model + return + + logging.info('Loading statistical mechanics parameters for {0}...'.format(self.species.label)) global_context = { '__builtins__': None, @@ -489,7 +505,6 @@ def load(self): self.species.conformer = conformer - def save(self, outputFile): """ Save the results of the statistical mechanics job to the file located @@ -567,6 +582,7 @@ def plotHinderedRotor(self, angle, Vlist, cosineRotor, fourierRotor, rotor, roto ################################################################################ + def applyEnergyCorrections(E0, modelChemistry, atoms, bonds, atomEnergies=None, applyAtomEnergyCorrections=True, applyBondEnergyCorrections=False): """ @@ -790,6 +806,7 @@ def applyEnergyCorrections(E0, modelChemistry, atoms, bonds, return E0 + class Log(object): """ Represent a general log file. @@ -1046,6 +1063,7 @@ def projectRotors(conformer, F, rotors, linear, TS): return numpy.sqrt(eig[-Nvib:]) / (2 * math.pi * constants.c * 100) + def assign_frequency_scale_factor(model_chemistry): """ Assign the frequency scaling factor according to the model chemistry. diff --git a/rmgpy/cantherm/thermo.py b/rmgpy/cantherm/thermo.py index baa88f34a8..71e434403e 100644 --- a/rmgpy/cantherm/thermo.py +++ b/rmgpy/cantherm/thermo.py @@ -53,9 +53,11 @@ from rmgpy.species import Species from rmgpy.molecule import Molecule from rmgpy.molecule.util import retrieveElementCount +from rmgpy.cantherm.common import CanthermSpecies ################################################################################ + class ThermoJob(object): """ A representation of a CanTherm thermodynamics job. This job is used to @@ -65,6 +67,7 @@ class ThermoJob(object): def __init__(self, species, thermoClass): self.species = species self.thermoClass = thermoClass + self.cantherm_species = CanthermSpecies(species=species) def execute(self, outputFile=None, plot=False): """ @@ -73,7 +76,13 @@ def execute(self, outputFile=None, plot=False): """ self.generateThermo() if outputFile is not None: - self.save(outputFile) + self.cantherm_species.chemkin_thermo_string = self.save(outputFile) + if self.species.molecule is None or len(self.species.molecule) == 0: + logging.debug("Not generating database file for species {0}, since its structure wasn't" + " specified".format(self.species.label)) + else: + self.cantherm_species.update_species_attributes(self.species) + self.cantherm_species.save_yaml(path=os.path.dirname(outputFile)) if plot: self.plot(os.path.dirname(outputFile)) @@ -88,8 +97,8 @@ def generateThermo(self): species = self.species - logging.info('Generating {0} thermo model for {1}...'.format(self.thermoClass, species)) - + logging.debug('Generating {0} thermo model for {1}...'.format(self.thermoClass, species)) + Tlist = np.arange(10.0, 3001.0, 10.0, np.float64) Cplist = np.zeros_like(Tlist) H298 = 0.0 @@ -186,6 +195,7 @@ def save(self, outputFile): f.write(species.molecule[0].toAdjacencyList(removeH=False,label=species.label)) f.write('\n') f.close() + return chemkin_thermo_string def plot(self, outputDirectory): """