def __init__(self, reaction, Tmin=None, Tmax=None, Tlist=None, Tcount=0, sensitivity_conditions=None): self.usedTST = False self.Tmin = Tmin if Tmin is not None else (298, 'K') self.Tmax = Tmax if Tmax is not None else (2500, 'K') self.Tcount = Tcount if Tcount > 3 else 50 if Tlist is not None: self.Tlist = Tlist self.Tmin = (min(self.Tlist.value_si), 'K') self.Tmax = (max(self.Tlist.value_si), 'K') self.Tcount = len(self.Tlist.value_si) else: self.Tlist = (1 / np.linspace(1 / self.Tmax.value_si, 1 / self.Tmin.value_si, self.Tcount), 'K') self.reaction = reaction self.k_units = None if sensitivity_conditions is not None: self.sensitivity_conditions = [ quantity.Quantity(condition) for condition in sensitivity_conditions ] else: self.sensitivity_conditions = None self.arkane_species = ArkaneSpecies( species=self.reaction.transition_state)
def test_loading_different_versions_of_yaml(self): """Test loading a YAML file generated by RMG v 2.4.1 and by a more recent version""" arkane_spc_v_241 = ArkaneSpecies.__new__(ArkaneSpecies) arkane_spc_v_241.load_yaml( path=os.path.join(self.data_path, 'vinoxy_v_2.4.1.yml')) self.assertIsInstance(arkane_spc_v_241, ArkaneSpecies) # checks make_object self.assertEqual(arkane_spc_v_241.conformer.spin_multiplicity, 2) arkane_current = ArkaneSpecies.__new__(ArkaneSpecies) arkane_current.load_yaml( path=os.path.join(self.data_path, 'vinoxy_current.yml')) self.assertIsInstance(arkane_current, ArkaneSpecies) # checks make_object self.assertEqual(arkane_current.conformer.spin_multiplicity, 2)
def test_load_existing_yaml(self): """ Test that existing Arkane YAML files can still be loaded """ # Load in YAML file arkane_spc = ArkaneSpecies.__new__(ArkaneSpecies) arkane_spc.load_yaml(path=os.path.join(self.load_path, 'C2H6.yml')) self.assertIsInstance(arkane_spc, ArkaneSpecies) # checks make_object self.assertIsInstance(arkane_spc.molecular_weight, ScalarQuantity) self.assertIsInstance(arkane_spc.thermo, NASA) self.assertNotEqual(arkane_spc.author, '') self.assertEqual(arkane_spc.inchi, 'InChI=1S/C2H6/c1-2/h1-2H3') self.assertEqual(arkane_spc.inchi_key, 'OTMSDBZUPAUEDD-UHFFFAOYSA-N') self.assertEqual(arkane_spc.smiles, 'CC') self.assertTrue('8 H u0 p0 c0 {2,S}' in arkane_spc.adjacency_list) self.assertEqual(arkane_spc.label, 'C2H6') self.assertEqual(arkane_spc.frequency_scale_factor, 0.99) # checks float conversion self.assertFalse(arkane_spc.use_bond_corrections) self.assertAlmostEqual( arkane_spc.conformer.modes[2].frequencies.value_si[0], 818.91718, 4) # HarmonicOsc. self.assertIsInstance(arkane_spc.energy_transfer_model, SingleExponentialDown) self.assertFalse(arkane_spc.is_ts) self.assertTrue(arkane_spc.use_hindered_rotors) self.assertTrue('C 7.54e-14 1.193e-13 5.52e-14' in arkane_spc.xyz) self.assertIsInstance(arkane_spc.chemkin_thermo_string, str)
def __init__(self, reaction, Tmin=None, Tmax=None, Tlist=None, Tcount=0, sensitivity_conditions=None): self.usedTST = False if Tmin is not None: self.Tmin = quantity.Quantity(Tmin) else: self.Tmin = None if Tmax is not None: self.Tmax = quantity.Quantity(Tmax) else: self.Tmax = None self.Tcount = Tcount if Tlist is not None: self.Tlist = quantity.Quantity(Tlist) self.Tmin = quantity.Quantity(numpy.min(self.Tlist.value_si), "K") self.Tmax = quantity.Quantity(numpy.max(self.Tlist.value_si), "K") self.Tcount = len(self.Tlist.value_si) else: if Tmin and Tmax is not None: if self.Tcount <= 3.: self.Tcount = 50 stepsize = (self.Tmax.value_si - self.Tmin.value_si) / self.Tcount self.Tlist = quantity.Quantity(numpy.arange(self.Tmin.value_si, self.Tmax.value_si + stepsize, stepsize), 'K') else: self.Tlist = None self.reaction = reaction self.kunits = None if sensitivity_conditions is not None: self.sensitivity_conditions = [quantity.Quantity(condition) for condition in sensitivity_conditions] else: self.sensitivity_conditions = None self.arkane_species = ArkaneSpecies(species=self.reaction.transitionState)
def test_create_and_load_yaml(self): """ Test properly loading the ArkaneSpecies object and respective sub-objects """ # Create YAML file by running Arkane job_list = self.arkane.load_input_file(self.dump_input_path) for job in job_list: job.execute(output_directory=self.dump_path) # Load in newly created YAML file arkane_spc_old = job_list[0].arkane_species arkane_spc = ArkaneSpecies.__new__(ArkaneSpecies) arkane_spc.load_yaml( path=os.path.join(self.dump_path, 'species', arkane_spc_old.label + '.yml')) self.assertIsInstance(arkane_spc, ArkaneSpecies) # checks make_object self.assertIsInstance(arkane_spc.molecular_weight, ScalarQuantity) self.assertIsInstance(arkane_spc.thermo, NASA) self.assertNotEqual(arkane_spc.author, '') self.assertEqual(arkane_spc.inchi, 'InChI=1S/C2H6/c1-2/h1-2H3') self.assertEqual(arkane_spc.inchi_key, 'OTMSDBZUPAUEDD-UHFFFAOYSA-N') self.assertEqual(arkane_spc.smiles, 'CC') self.assertTrue('8 H u0 p0 c0 {2,S}' in arkane_spc.adjacency_list) self.assertEqual(arkane_spc.label, 'C2H6') self.assertEqual(arkane_spc.frequency_scale_factor, 0.99 * 1.014) # checks float conversion self.assertFalse(arkane_spc.use_bond_corrections) self.assertAlmostEqual( arkane_spc.conformer.modes[2].frequencies.value_si[0], 830.38202, 4) # HarmonicOsc. self.assertIsInstance(arkane_spc.energy_transfer_model, SingleExponentialDown) self.assertFalse(arkane_spc.is_ts) self.assertEqual(arkane_spc.level_of_theory, 'cbs-qb3') self.assertIsInstance(arkane_spc.thermo_data, ThermoData) self.assertTrue(arkane_spc.use_hindered_rotors) self.assertIsInstance(arkane_spc.chemkin_thermo_string, str) expected_xyz = """8 C2H6 C 0.00075400 0.00119300 0.00055200 H 0.00074000 0.00117100 1.09413800 H 1.04376600 0.00117100 -0.32820200 H -0.44760300 0.94289500 -0.32825300 C -0.76014200 -1.20389600 -0.55748300 H -0.76012800 -1.20387400 -1.65106900 H -0.31178500 -2.14559800 -0.22867800 H -1.80315400 -1.20387400 -0.22872900""" self.assertEqual(arkane_spc.xyz, expected_xyz)
def __init__(self, reaction, Tmin=None, Tmax=None, Tlist=None, Tcount=0, sensitivity_conditions=None): self.usedTST = False if Tmin is not None: self.Tmin = quantity.Quantity(Tmin) else: self.Tmin = None if Tmax is not None: self.Tmax = quantity.Quantity(Tmax) else: self.Tmax = None self.Tcount = Tcount if Tlist is not None: self.Tlist = quantity.Quantity(Tlist) self.Tmin = quantity.Quantity(numpy.min(self.Tlist.value_si), "K") self.Tmax = quantity.Quantity(numpy.max(self.Tlist.value_si), "K") self.Tcount = len(self.Tlist.value_si) else: if Tmin and Tmax is not None: if self.Tcount <= 3.: self.Tcount = 50 stepsize = (self.Tmax.value_si-self.Tmin.value_si) / self.Tcount self.Tlist = quantity.Quantity(numpy.arange(self.Tmin.value_si, self.Tmax.value_si+stepsize, stepsize),"K") else: self.Tlist = None self.reaction = reaction self.kunits = None if sensitivity_conditions is not None: self.sensitivity_conditions = [quantity.Quantity(condition) for condition in sensitivity_conditions] else: self.sensitivity_conditions = None self.arkane_species = ArkaneSpecies(species=self.reaction.transitionState)
def test_create_and_load_yaml(self): """ Test properly loading the ArkaneSpecies object and respective sub-objects """ # Create YAML file by running Arkane jobList = self.arkane.loadInputFile(self.dump_input_path) for job in jobList: job.execute(output_directory=self.dump_path) # Load in newly created YAML file arkane_spc_old = jobList[0].arkane_species arkane_spc = ArkaneSpecies.__new__(ArkaneSpecies) arkane_spc.load_yaml( path=os.path.join(self.dump_path, 'species', arkane_spc_old.label + '.yml')) self.assertIsInstance(arkane_spc, ArkaneSpecies) # checks make_object self.assertIsInstance(arkane_spc.molecular_weight, ScalarQuantity) self.assertIsInstance(arkane_spc.thermo, NASA) self.assertNotEqual(arkane_spc.author, '') self.assertEqual(arkane_spc.inchi, 'InChI=1S/C2H6/c1-2/h1-2H3') self.assertEqual(arkane_spc.inchi_key, 'OTMSDBZUPAUEDD-UHFFFAOYSA-N') self.assertEqual(arkane_spc.smiles, 'CC') self.assertTrue('8 H u0 p0 c0 {2,S}' in arkane_spc.adjacency_list) self.assertEqual(arkane_spc.label, 'C2H6') self.assertEqual(arkane_spc.frequency_scale_factor, 0.99 * 1.014) # checks float conversion self.assertFalse(arkane_spc.use_bond_corrections) self.assertAlmostEqual( arkane_spc.conformer.modes[2].frequencies.value_si[0], 830.38202, 4) # HarmonicOsc. self.assertIsInstance(arkane_spc.energy_transfer_model, SingleExponentialDown) self.assertFalse(arkane_spc.is_ts) self.assertEqual(arkane_spc.level_of_theory, 'cbs-qb3') self.assertIsInstance(arkane_spc.thermo_data, ThermoData) self.assertTrue(arkane_spc.use_hindered_rotors) self.assertTrue('C 7.54e-14 1.193e-13 5.52e-14' in arkane_spc.xyz) self.assertIsInstance(arkane_spc.chemkin_thermo_string, str)
class KineticsJob(object): """ A representation of an Arkane kinetics job. This job is used to compute and save the high-pressure-limit kinetics information for a single reaction. `usedTST` - a boolean representing if TST was used to calculate the kinetics if kinetics is already given in the input, then it is False. """ def __init__(self, reaction, Tmin=None, Tmax=None, Tlist=None, Tcount=0, sensitivity_conditions=None): self.usedTST = False if Tmin is not None: self.Tmin = quantity.Quantity(Tmin) else: self.Tmin = None if Tmax is not None: self.Tmax = quantity.Quantity(Tmax) else: self.Tmax = None self.Tcount = Tcount if Tlist is not None: self.Tlist = quantity.Quantity(Tlist) self.Tmin = quantity.Quantity(numpy.min(self.Tlist.value_si), "K") self.Tmax = quantity.Quantity(numpy.max(self.Tlist.value_si), "K") self.Tcount = len(self.Tlist.value_si) else: if Tmin and Tmax is not None: if self.Tcount <= 3.: self.Tcount = 50 stepsize = (self.Tmax.value_si-self.Tmin.value_si) / self.Tcount self.Tlist = quantity.Quantity(numpy.arange(self.Tmin.value_si, self.Tmax.value_si+stepsize, stepsize),"K") else: self.Tlist = None self.reaction = reaction self.kunits = None if sensitivity_conditions is not None: self.sensitivity_conditions = [quantity.Quantity(condition) for condition in sensitivity_conditions] else: self.sensitivity_conditions = None self.arkane_species = ArkaneSpecies(species=self.reaction.transitionState) @property def Tmin(self): """The minimum temperature at which the computed k(T) values are valid, or ``None`` if not defined.""" return self._Tmin @Tmin.setter def Tmin(self, value): self._Tmin = quantity.Temperature(value) @property def Tmax(self): """The maximum temperature at which the computed k(T) values are valid, or ``None`` if not defined.""" return self._Tmax @Tmax.setter def Tmax(self, value): self._Tmax = quantity.Temperature(value) @property def Tlist(self): """The temperatures at which the k(T) values are computed.""" return self._Tlist @Tlist.setter def Tlist(self, value): self._Tlist = quantity.Temperature(value) def execute(self, outputFile=None, plot=True): """ Execute the kinetics job, saving the results to the given `outputFile` on disk. """ if self.Tlist is not None: self.generateKinetics(self.Tlist.value_si) else: self.generateKinetics() if outputFile is not None: self.save(outputFile) if plot: self.plot(os.path.dirname(outputFile)) self.draw(os.path.dirname(outputFile)) if self.sensitivity_conditions is not None: logging.info('\n\nRunning sensitivity analysis...') sa(self, os.path.dirname(outputFile)) logging.debug('Finished kinetics job for reaction {0}.'.format(self.reaction)) logging.debug(repr(self.reaction)) def generateKinetics(self,Tlist=None): """ Generate the kinetics data for the reaction and fit it to a modified Arrhenius model. """ if isinstance(self.reaction.kinetics, Arrhenius): return None self.usedTST=True kineticsClass = 'Arrhenius' tunneling = self.reaction.transitionState.tunneling if isinstance(tunneling, Wigner) and tunneling.frequency is None: tunneling.frequency = (self.reaction.transitionState.frequency.value_si,"cm^-1") elif isinstance(tunneling, Eckart) and tunneling.frequency is None: tunneling.frequency = (self.reaction.transitionState.frequency.value_si,"cm^-1") tunneling.E0_reac = (sum([reactant.conformer.E0.value_si for reactant in self.reaction.reactants])*0.001,"kJ/mol") tunneling.E0_TS = (self.reaction.transitionState.conformer.E0.value_si*0.001,"kJ/mol") tunneling.E0_prod = (sum([product.conformer.E0.value_si for product in self.reaction.products])*0.001,"kJ/mol") elif tunneling is not None: if tunneling.frequency is not None: # Frequency was given by the user pass else: raise ValueError('Unknown tunneling model {0!r} for reaction {1}.'.format(tunneling, self.reaction)) logging.debug('Generating {0} kinetics model for {1}...'.format(kineticsClass, self.reaction)) if Tlist is None: Tlist = 1000.0/numpy.arange(0.4, 3.35, 0.05) klist = numpy.zeros_like(Tlist) for i in range(Tlist.shape[0]): klist[i] = self.reaction.calculateTSTRateCoefficient(Tlist[i]) order = len(self.reaction.reactants) klist *= 1e6 ** (order-1) self.kunits = {1: 's^-1', 2: 'cm^3/(mol*s)', 3: 'cm^6/(mol^2*s)'}[order] self.Kequnits = {2:'mol^2/cm^6', 1:'mol/cm^3', 0:' ', -1:'cm^3/mol', -2:'cm^6/mol^2'}[len(self.reaction.products)-len(self.reaction.reactants)] self.krunits = {1: 's^-1', 2: 'cm^3/(mol*s)', 3: 'cm^6/(mol^2*s)'}[len(self.reaction.products)] self.reaction.kinetics = Arrhenius().fitToData(Tlist, klist, kunits=self.kunits) self.reaction.elementary_high_p = True def save(self, outputFile): """ Save the results of the kinetics job to the file located at `path` on disk. """ reaction = self.reaction ks = [] k0s = [] k0revs = [] krevs = [] logging.info('Saving kinetics for {0}...'.format(reaction)) order = len(self.reaction.reactants) factor = 1e6 ** (order-1) f = open(outputFile, 'a') if self.usedTST: # If TST is not used, eg. it was given in 'reaction', then this will throw an error. f.write('# ======= =========== =========== =========== ===============\n') f.write('# Temp. k (TST) Tunneling k (TST+T) Units\n') f.write('# ======= =========== =========== =========== ===============\n') if self.Tlist is None: Tlist = numpy.array([300,400,500,600,800,1000,1500,2000]) else: Tlist =self.Tlist.value_si for T in Tlist: tunneling = reaction.transitionState.tunneling reaction.transitionState.tunneling = None try: k0 = reaction.calculateTSTRateCoefficient(T) * factor except SpeciesError: k0 = 0 reaction.transitionState.tunneling = tunneling try: k = reaction.calculateTSTRateCoefficient(T) * factor kappa = k / k0 except (SpeciesError,ZeroDivisionError): k = reaction.getRateCoefficient(T) kappa = 0 logging.info("The species in reaction {} do not have adequate information for TST, using default kinetics values.".format(reaction)) tunneling = reaction.transitionState.tunneling ks.append(k) k0s.append(k0) f.write('# {0:4g} K {1:11.3e} {2:11g} {3:11.3e} {4}\n'.format(T, k0, kappa, k, self.kunits)) f.write('# ======= =========== =========== =========== ===============\n') f.write('\n\n') f.write('# ======= ============ =========== ============ ============= =========\n') f.write('# Temp. Kc (eq) Units krev (TST) krev (TST+T) Units\n') f.write('# ======= ============ =========== ============ ============= =========\n') # Initialize Object for Converting Units if self.Kequnits != ' ': keq_unit_converter = quantity.Units(self.Kequnits).getConversionFactorFromSI() else: keq_unit_converter = 1 for n,T in enumerate(Tlist): k = ks[n] k0 = k0s[n] Keq = keq_unit_converter * reaction.getEquilibriumConstant(T) # getEquilibriumConstant returns SI units k0rev = k0/Keq krev = k/Keq k0revs.append(k0rev) krevs.append(krev) f.write('# {0:4g} K {1:11.3e} {2} {3:11.3e} {4:11.3e} {5}\n'.format(T, Keq, self.Kequnits, k0rev, krev, self.krunits)) f.write('# ======= ============ =========== ============ ============= =========\n') f.write('\n\n') kinetics0rev = Arrhenius().fitToData(Tlist, numpy.array(k0revs), kunits=self.krunits) kineticsrev = Arrhenius().fitToData(Tlist, numpy.array(krevs), kunits=self.krunits) f.write('# krev (TST) = {0} \n'.format(kinetics0rev)) f.write('# krev (TST+T) = {0} \n\n'.format(kineticsrev)) # Reaction path degeneracy is INCLUDED in the kinetics itself! string = 'kinetics(label={0!r}, kinetics={1!r})'.format(reaction.label, reaction.kinetics) f.write('{0}\n\n'.format(prettify(string))) f.close() # Also save the result to chem.inp f = open(os.path.join(os.path.dirname(outputFile), 'chem.inp'), 'a') reaction = self.reaction kinetics = reaction.kinetics string = '' if reaction.kinetics.comment: for line in reaction.kinetics.comment.split("\n"): string += "! {0}\n".format(line) string += '{0!s:51} {1:9.3e} {2:9.3f} {3:9.3f}\n'.format( reaction, kinetics.A.value_si * factor, kinetics.n.value_si, kinetics.Ea.value_si / 4184., ) f.write('{0}\n'.format(string)) f.close() # We're saving a YAML file for TSs iff structures of the respective reactant/s and product/s are known if all ([spc.molecule is not None and len(spc.molecule) for spc in self.reaction.reactants + self.reaction.products]): self.arkane_species.update_species_attributes(self.reaction.transitionState) self.arkane_species.reaction_label = reaction.label self.arkane_species.reactants = [{'label': spc.label, 'adjacency_list': spc.molecule[0].toAdjacencyList()} for spc in self.reaction.reactants] self.arkane_species.products = [{'label': spc.label, 'adjacency_list': spc.molecule[0].toAdjacencyList()} for spc in self.reaction.products] self.arkane_species.save_yaml(path=os.path.dirname(outputFile)) def plot(self, outputDirectory): """ Plot both the raw kinetics data and the Arrhenius fit versus temperature. The plot is saved to the file ``kinetics.pdf`` in the output directory. The plot is not generated if ``matplotlib`` is not installed. """ # Skip this step if matplotlib is not installed try: import matplotlib.pyplot as plt except ImportError: return if self.Tlist is not None: t_list = [t for t in self.Tlist.value_si] else: t_list = 1000.0/numpy.arange(0.4, 3.35, 0.05) klist = numpy.zeros_like(t_list) klist2 = numpy.zeros_like(t_list) for i in xrange(len(t_list)): klist[i] = self.reaction.calculateTSTRateCoefficient(t_list[i]) klist2[i] = self.reaction.kinetics.getRateCoefficient(t_list[i]) order = len(self.reaction.reactants) klist *= 1e6 ** (order-1) klist2 *= 1e6 ** (order-1) t_list = [1000.0 / t for t in t_list] plt.semilogy(t_list, klist, 'ob', label='TST calculation') plt.semilogy(t_list, klist2, '-k', label='Fitted rate') plt.legend() reaction_str = '{0} {1} {2}'.format( ' + '.join([reactant.label for reactant in self.reaction.reactants]), '<=>', ' + '.join([product.label for product in self.reaction.products])) plt.title(reaction_str) plt.xlabel('1000 / Temperature (1000/K)') plt.ylabel('Rate coefficient ({0})'.format(self.kunits)) plot_path = os.path.join(outputDirectory, 'plots') if not os.path.exists(plot_path): os.mkdir(plot_path) valid_chars = "-_.()<=> %s%s" % (string.ascii_letters, string.digits) filename = ''.join(c for c in reaction_str if c in valid_chars) + '.pdf' plt.savefig(os.path.join(plot_path, filename)) plt.close() def draw(self, outputDirectory, format='pdf'): """ Generate a PDF drawing of the reaction. This requires that Cairo and its Python wrapper be available; if not, the drawing is not generated. You may also generate different formats of drawings, by changing format to one of the following: `pdf`, `svg`, `png`. """ drawing_path = os.path.join(outputDirectory, 'paths') if not os.path.exists(drawing_path): os.mkdir(drawing_path) valid_chars = "-_.()<=> %s%s" % (string.ascii_letters, string.digits) reaction_str = '{0} {1} {2}'.format( ' + '.join([reactant.label for reactant in self.reaction.reactants]), '<=>', ' + '.join([product.label for product in self.reaction.products])) filename = ''.join(c for c in reaction_str if c in valid_chars) + '.pdf' path = os.path.join(drawing_path, filename) KineticsDrawer().draw(self.reaction, format=format, path=path)
def __init__(self, species, thermo_class): self.species = species self.thermo_class = thermo_class self.arkane_species = ArkaneSpecies(species=species)
class ThermoJob(object): """ A representation of an Arkane thermodynamics job. This job is used to compute and save the thermodynamics information for a single species. """ def __init__(self, species, thermo_class): self.species = species self.thermo_class = thermo_class self.arkane_species = ArkaneSpecies(species=species) def execute(self, output_directory=None, plot=False): """ Execute the thermodynamics job, saving the results within the `output_directory`. If `plot` is true, then plots of the raw and fitted values for heat capacity, entropy, enthalpy, gibbs free energy, and hindered rotors will be saved. """ self.generate_thermo() if output_directory is not None: try: self.write_output(output_directory) except Exception as e: logging.warning("Could not write output file due to error: " "{0} for species {1}".format( e, self.species.label)) try: self.arkane_species.chemkin_thermo_string = self.write_chemkin( output_directory) except Exception as e: logging.warning("Could not write chemkin output due to error: " "{0} for species {1}".format( e, self.species.label)) if self.species.molecule is None or len( self.species.molecule) == 0: logging.debug( "Not generating a YAML file for species {0}, since its structure wasn't" " specified".format(self.species.label)) else: # We're saving a YAML file for species iff Thermo is called and they're structure is known self.arkane_species.update_species_attributes(self.species) self.arkane_species.save_yaml(path=output_directory) if plot: try: self.plot(output_directory) except Exception as e: logging.warning("Could not create plots due to error: " "{0} for species {1}".format( e, self.species.label)) def generate_thermo(self): """ Generate the thermodynamic data for the species and fit it to the desired heat capacity model (as specified in the `thermo_class` attribute). """ if self.thermo_class.lower() not in ['wilhoit', 'nasa']: raise InputError('Unknown thermodynamic model "{0}".'.format( self.thermo_class)) species = self.species logging.debug('Generating {0} thermo model for {1}...'.format( self.thermo_class, species)) if species.thermo is not None: logging.info( "Thermo already generated for species {}. Skipping thermo generation." .format(species)) return None Tlist = np.arange(10.0, 3001.0, 10.0, np.float64) Cplist = np.zeros_like(Tlist) H298 = 0.0 S298 = 0.0 conformer = self.species.conformer for i in range(Tlist.shape[0]): Cplist[i] += conformer.get_heat_capacity(Tlist[i]) H298 += conformer.get_enthalpy(298.) + conformer.E0.value_si S298 += conformer.get_entropy(298.) if not any([ isinstance(mode, (LinearRotor, NonlinearRotor)) for mode in conformer.modes ]): # Monatomic species n_freq = 0 n_rotors = 0 Cp0 = 2.5 * constants.R CpInf = 2.5 * constants.R else: # Polyatomic species linear = True if isinstance(conformer.modes[1], LinearRotor) else False n_freq = len(conformer.modes[2].frequencies.value) n_rotors = len(conformer.modes[3:]) Cp0 = (3.5 if linear else 4.0) * constants.R CpInf = Cp0 + (n_freq + 0.5 * n_rotors) * constants.R wilhoit = Wilhoit() if n_freq == 0 and n_rotors == 0: wilhoit.Cp0 = (Cplist[0], "J/(mol*K)") wilhoit.CpInf = (Cplist[0], "J/(mol*K)") wilhoit.B = (500., "K") wilhoit.H0 = (0.0, "J/mol") wilhoit.S0 = (0.0, "J/(mol*K)") wilhoit.H0 = (H298 - wilhoit.get_enthalpy(298.15), "J/mol") wilhoit.S0 = (S298 - wilhoit.get_entropy(298.15), "J/(mol*K)") else: wilhoit.fit_to_data(Tlist, Cplist, Cp0, CpInf, H298, S298, B0=500.0) if self.thermo_class.lower() == 'nasa': species.thermo = wilhoit.to_nasa(Tmin=10.0, Tmax=3000.0, Tint=500.0) else: species.thermo = wilhoit def write_output(self, output_directory): """ Save the results of the thermodynamics job to the `output.py` file located in `output_directory`. """ species = self.species output_file = os.path.join(output_directory, 'output.py') logging.info('Saving thermo for {0}...'.format(species.label)) with open(output_file, 'a') as f: f.write('# Thermodynamics for {0}:\n'.format(species.label)) H298 = species.get_thermo_data().get_enthalpy(298) / 4184. S298 = species.get_thermo_data().get_entropy(298) / 4.184 f.write( '# Enthalpy of formation (298 K) = {0:9.3f} kcal/mol\n'. format(H298)) f.write( '# Entropy of formation (298 K) = {0:9.3f} cal/(mol*K)\n'. format(S298)) f.write( '# =========== =========== =========== =========== ===========\n' ) f.write( '# Temperature Heat cap. Enthalpy Entropy Free energy\n' ) f.write( '# (K) (cal/mol*K) (kcal/mol) (cal/mol*K) (kcal/mol)\n' ) f.write( '# =========== =========== =========== =========== ===========\n' ) for T in [300, 400, 500, 600, 800, 1000, 1500, 2000, 2400]: try: Cp = species.get_thermo_data().get_heat_capacity(T) / 4.184 H = species.get_thermo_data().get_enthalpy(T) / 4184. S = species.get_thermo_data().get_entropy(T) / 4.184 G = species.get_thermo_data().get_free_energy(T) / 4184. f.write( '# {0:11g} {1:11.3f} {2:11.3f} {3:11.3f} {4:11.3f}\n' .format(T, Cp, H, S, G)) except ValueError: logging.debug( "Valid thermo for {0} is outside range for temperature {1}" .format(species, T)) f.write( '# =========== =========== =========== =========== ===========\n' ) thermo_string = 'thermo(label={0!r}, thermo={1!r})'.format( species.label, species.get_thermo_data()) f.write('{0}\n\n'.format(prettify(thermo_string))) def write_chemkin(self, output_directory): """ Appends the thermo block to `chem.inp` and species name to `species_dictionary.txt` within the `outut_directory` specified """ species = self.species with open(os.path.join(output_directory, 'chem.inp'), 'a') as f: if isinstance(species, Species): if species.molecule and isinstance(species.molecule[0], Molecule): element_counts = get_element_count(species.molecule[0]) else: try: element_counts = species.props['element_counts'] except KeyError: element_counts = self.element_count_from_conformer() else: element_counts = {'C': 0, 'H': 0} chemkin_thermo_string = write_thermo_entry( species, element_counts=element_counts, verbose=True) f.write('{0}\n'.format(chemkin_thermo_string)) # write species dictionary if isinstance(species, Species): if species.molecule and isinstance(species.molecule[0], Molecule): spec_dict_path = os.path.join(output_directory, 'species_dictionary.txt') is_species_in_dict = False if os.path.isfile(spec_dict_path): with open(spec_dict_path, 'r') as f: # check whether the species dictionary contains this species, in which case do not re-append for line in f.readlines(): if species.label == line.strip(): is_species_in_dict = True break if not is_species_in_dict: with open(spec_dict_path, 'a') as f: f.write(species.molecule[0].to_adjacency_list( remove_h=False, label=species.label)) f.write('\n') return chemkin_thermo_string def element_count_from_conformer(self): """ Get an element count in a dictionary form (e.g., {'C': 3, 'H': 8}) from the species.conformer attribute. Returns: dict: Element count, keys are element symbols, values are number of occurrences of the element in the molecule. """ element_counts = dict() for number in self.species.conformer.number.value_si: symbol = symbol_by_number[number] if symbol in element_counts: element_counts[symbol] += 1 else: element_counts[symbol] = 1 return element_counts def plot(self, output_directory): """ Plot the heat capacity, enthapy, entropy, and Gibbs free energy of the fitted thermodynamics model, along with the same values from the statistical mechanics model that the thermodynamics model was fitted to. The plot is saved to the file ``thermo.pdf`` in the output directory. The plot is not generated if ``matplotlib`` is not installed. """ # Skip this step if matplotlib is not installed try: import matplotlib.pyplot as plt except ImportError: return Tlist = np.arange(10.0, 2501.0, 10.0) Cplist = np.zeros_like(Tlist) Cplist1 = np.zeros_like(Tlist) Hlist = np.zeros_like(Tlist) Hlist1 = np.zeros_like(Tlist) Slist = np.zeros_like(Tlist) Slist1 = np.zeros_like(Tlist) Glist = np.zeros_like(Tlist) Glist1 = np.zeros_like(Tlist) conformer = self.species.conformer thermo = self.species.get_thermo_data() for i in range(Tlist.shape[0]): try: Cplist[i] = conformer.get_heat_capacity(Tlist[i]) Slist[i] = conformer.get_entropy(Tlist[i]) Hlist[i] = (conformer.get_enthalpy(Tlist[i]) + conformer.E0.value_si) * 0.001 Glist[i] = Hlist[i] - Tlist[i] * Slist[i] * 0.001 Cplist1[i] = thermo.get_heat_capacity(Tlist[i]) Slist1[i] = thermo.get_entropy(Tlist[i]) Hlist1[i] = thermo.get_enthalpy(Tlist[i]) * 0.001 Glist1[i] = thermo.get_free_energy(Tlist[i]) * 0.001 except (ValueError, AttributeError): continue fig = plt.figure(figsize=(10, 8)) fig.suptitle('{0}'.format(self.species.label)) plt.subplot(2, 2, 1) plt.plot(Tlist, Cplist / 4.184, '-r', Tlist, Cplist1 / 4.184, '-b') plt.xlabel('Temperature (K)') plt.ylabel('Heat capacity (cal/mol*K)') plt.legend(['statmech', 'fitted'], loc=4) plt.subplot(2, 2, 2) plt.plot(Tlist, Slist / 4.184, '-r', Tlist, Slist1 / 4.184, '-b') plt.xlabel('Temperature (K)') plt.ylabel('Entropy (cal/mol*K)') plt.subplot(2, 2, 3) plt.plot(Tlist, Hlist / 4.184, '-r', Tlist, Hlist1 / 4.184, '-b') plt.xlabel('Temperature (K)') plt.ylabel('Enthalpy (kcal/mol)') plt.subplot(2, 2, 4) plt.plot(Tlist, Glist / 4.184, '-r', Tlist, Glist1 / 4.184, '-b') plt.xlabel('Temperature (K)') plt.ylabel('Gibbs free energy (kcal/mol)') fig.subplots_adjust(left=0.10, bottom=0.08, right=0.95, top=0.95, wspace=0.35, hspace=0.20) plot_path = os.path.join(output_directory, 'plots') if not os.path.exists(plot_path): os.mkdir(plot_path) valid_chars = "-_.()<=> %s%s" % (string.ascii_letters, string.digits) filename = ''.join( c for c in self.species.label if c in valid_chars) + '.pdf' plt.savefig(os.path.join(plot_path, filename)) plt.close()
class KineticsJob(object): """ A representation of an Arkane kinetics job. This job is used to compute and save the high-pressure-limit kinetics information for a single reaction. `usedTST` - a boolean representing if TST was used to calculate the kinetics if kinetics is already given in the input, then it is False. `three_params` - a boolean representing if the modified three-parameter Arrhenius equation is used to calculate high pressure kinetic rate coefficients. If it is False, the classical two-parameter Arrhenius equation is used. """ def __init__(self, reaction, Tmin=None, Tmax=None, Tlist=None, Tcount=0, sensitivity_conditions=None, three_params=True): self.usedTST = False self.Tmin = Tmin if Tmin is not None else (298, 'K') self.Tmax = Tmax if Tmax is not None else (2500, 'K') self.Tcount = Tcount if Tcount > 3 else 50 self.three_params = three_params if Tlist is not None: self.Tlist = Tlist self.Tmin = (min(self.Tlist.value_si), 'K') self.Tmax = (max(self.Tlist.value_si), 'K') self.Tcount = len(self.Tlist.value_si) else: self.Tlist = (1 / np.linspace(1 / self.Tmax.value_si, 1 / self.Tmin.value_si, self.Tcount), 'K') self.reaction = reaction self.k_units = None if sensitivity_conditions is not None: self.sensitivity_conditions = [ quantity.Quantity(condition) for condition in sensitivity_conditions ] else: self.sensitivity_conditions = None self.arkane_species = ArkaneSpecies( species=self.reaction.transition_state) @property def Tmin(self): """The minimum temperature at which the computed k(T) values are valid, or ``None`` if not defined.""" return self._Tmin @Tmin.setter def Tmin(self, value): self._Tmin = quantity.Temperature(value) @property def Tmax(self): """The maximum temperature at which the computed k(T) values are valid, or ``None`` if not defined.""" return self._Tmax @Tmax.setter def Tmax(self, value): self._Tmax = quantity.Temperature(value) @property def Tlist(self): """The temperatures at which the k(T) values are computed.""" return self._Tlist @Tlist.setter def Tlist(self, value): self._Tlist = quantity.Temperature(value) def execute(self, output_directory=None, plot=True): """ Execute the kinetics job, saving the results within the `output_directory`. If `plot` is True, then plots of the raw and fitted values for the kinetics will be saved. """ self.generate_kinetics() if output_directory is not None: try: self.write_output(output_directory) except Exception as e: logging.warning( "Could not write kinetics output file due to error: " "{0} in reaction {1}".format(e, self.reaction.label)) try: self.write_chemkin(output_directory) except Exception as e: logging.warning( "Could not write kinetics chemkin output due to error: " "{0} in reaction {1}".format(e, self.reaction.label)) if plot: try: self.plot(output_directory) except Exception as e: logging.warning("Could not plot kinetics due to error: " "{0} in reaction {1}".format( e, self.reaction.label)) try: self.draw(output_directory) except Exception as e: logging.warning( "Could not draw reaction {1} due to error: {0}".format( e, self.reaction.label)) if self.sensitivity_conditions is not None: logging.info('\n\nRunning sensitivity analysis...') SensAnalysis(self, output_directory) logging.debug('Finished kinetics job for reaction {0}.'.format( self.reaction)) logging.debug(repr(self.reaction)) def generate_kinetics(self): """ Generate the kinetics data for the reaction and fit it to a modified Arrhenius model. """ if isinstance(self.reaction.kinetics, Arrhenius): return None self.usedTST = True kinetics_class = 'Arrhenius' tunneling = self.reaction.transition_state.tunneling if isinstance(tunneling, Wigner) and tunneling.frequency is None: tunneling.frequency = ( self.reaction.transition_state.frequency.value_si, "cm^-1") elif isinstance(tunneling, Eckart) and tunneling.frequency is None: tunneling.frequency = ( self.reaction.transition_state.frequency.value_si, "cm^-1") tunneling.E0_reac = (sum([ reactant.conformer.E0.value_si for reactant in self.reaction.reactants ]) * 0.001, "kJ/mol") tunneling.E0_TS = ( self.reaction.transition_state.conformer.E0.value_si * 0.001, "kJ/mol") tunneling.E0_prod = (sum([ product.conformer.E0.value_si for product in self.reaction.products ]) * 0.001, "kJ/mol") elif tunneling is not None: if tunneling.frequency is not None: # Frequency was given by the user pass else: raise ValueError( 'Unknown tunneling model {0!r} for reaction {1}.'.format( tunneling, self.reaction)) logging.debug('Generating {0} kinetics model for {1}...'.format( kinetics_class, self.reaction)) klist = np.zeros_like(self.Tlist.value_si) for i, t in enumerate(self.Tlist.value_si): klist[i] = self.reaction.calculate_tst_rate_coefficient(t) order = len(self.reaction.reactants) klist *= 1e6**(order - 1) self.k_units = { 1: 's^-1', 2: 'cm^3/(mol*s)', 3: 'cm^6/(mol^2*s)' }[order] self.K_eq_units = { 2: 'mol^2/cm^6', 1: 'mol/cm^3', 0: ' ', -1: 'cm^3/mol', -2: 'cm^6/mol^2' }[len(self.reaction.products) - len(self.reaction.reactants)] self.k_r_units = { 1: 's^-1', 2: 'cm^3/(mol*s)', 3: 'cm^6/(mol^2*s)' }[len(self.reaction.products)] self.reaction.kinetics = Arrhenius().fit_to_data( self.Tlist.value_si, klist, kunits=self.k_units, three_params=self.three_params) self.reaction.elementary_high_p = True def write_output(self, output_directory): """ Save the results of the kinetics job to the `output.py` file located in `output_directory`. """ reaction = self.reaction ks, k0s, k0_revs, k_revs = [], [], [], [] logging.info('Saving kinetics for {0}...'.format(reaction)) order = len(self.reaction.reactants) factor = 1e6**(order - 1) f = open(os.path.join(output_directory, 'output.py'), 'a') if self.usedTST: # If TST is not used, eg. it was given in 'reaction', then this will throw an error. f.write( '# ======= =========== =========== =========== ===============\n' ) f.write('# Temp. k (TST) Tunneling k (TST+T) Units\n') f.write( '# ======= =========== =========== =========== ===============\n' ) if self.Tlist is None: t_list = np.array([300, 400, 500, 600, 800, 1000, 1500, 2000]) else: t_list = self.Tlist.value_si for T in t_list: tunneling = reaction.transition_state.tunneling reaction.transition_state.tunneling = None try: k0 = reaction.calculate_tst_rate_coefficient(T) * factor except SpeciesError: k0 = 0 reaction.transition_state.tunneling = tunneling try: k = reaction.calculate_tst_rate_coefficient(T) * factor kappa = k / k0 except (SpeciesError, ZeroDivisionError): k = reaction.get_rate_coefficient(T) kappa = 0 logging.info( "The species in reaction {0} do not have adequate information for TST, " "using default kinetics values.".format(reaction)) tunneling = reaction.transition_state.tunneling ks.append(k) k0s.append(k0) f.write( '# {0:4g} K {1:11.3e} {2:11g} {3:11.3e} {4}\n'.format( T, k0, kappa, k, self.k_units)) f.write( '# ======= =========== =========== =========== ===============\n' ) f.write('\n\n') f.write( '# ======= ============ =========== ============ ============= =========\n' ) f.write( '# Temp. Kc (eq) Units k_rev (TST) k_rev (TST+T) Units\n' ) f.write( '# ======= ============ =========== ============ ============= =========\n' ) # Initialize Object for Converting Units if self.K_eq_units != ' ': keq_unit_converter = quantity.Units( self.K_eq_units).get_conversion_factor_from_si() else: keq_unit_converter = 1 for n, T in enumerate(t_list): k = ks[n] k0 = k0s[n] K_eq = keq_unit_converter * reaction.get_equilibrium_constant( T) # returns SI units k0_rev = k0 / K_eq k_rev = k / K_eq k0_revs.append(k0_rev) k_revs.append(k_rev) f.write( '# {0:4g} K {1:11.3e} {2} {3:11.3e} {4:11.3e} {5}\n' .format(T, K_eq, self.K_eq_units, k0_rev, k_rev, self.k_r_units)) f.write( '# ======= ============ =========== ============ ============= =========\n' ) f.write('\n\n') kinetics_0_rev = Arrhenius().fit_to_data( t_list, np.array(k0_revs), kunits=self.k_r_units, three_params=self.three_params) kinetics_rev = Arrhenius().fit_to_data( t_list, np.array(k_revs), kunits=self.k_r_units, three_params=self.three_params) f.write('# k_rev (TST) = {0} \n'.format(kinetics_0_rev)) f.write('# k_rev (TST+T) = {0} \n\n'.format(kinetics_rev)) if self.three_params: f.write( '# kinetics fitted using the modified three-parameter Arrhenius equation ' 'k = A * (T/T0)^n * exp(-Ea/RT) \n') else: f.write( '# kinetics fitted using the two-parameter Arrhenius equation k = A * exp(-Ea/RT) \n' ) # Reaction path degeneracy is INCLUDED in the kinetics itself! rxn_str = 'kinetics(label={0!r}, kinetics={1!r})'.format( reaction.label, reaction.kinetics) f.write('{0}\n\n'.format(prettify(rxn_str))) f.close() def write_chemkin(self, output_directory): """ Appends the kinetics rates to `chem.inp` in `outut_directory` """ # obtain a unit conversion factor order = len(self.reaction.reactants) factor = 1e6**(order - 1) reaction = self.reaction kinetics = reaction.kinetics rxn_str = '' if reaction.kinetics.comment: for line in reaction.kinetics.comment.split("\n"): rxn_str += "! {0}\n".format(line) rxn_str += '{0!s:51} {1:9.3e} {2:9.3f} {3:9.3f}\n'.format( reaction, kinetics.A.value_si * factor, kinetics.n.value_si, kinetics.Ea.value_si / 4184., ) with open(os.path.join(output_directory, 'chem.inp'), 'a') as f: f.write('{0}\n'.format(rxn_str)) def save_yaml(self, output_directory): """ Save a YAML file for TSs if structures of the respective reactant/s and product/s are known """ if all([ spc.molecule is not None and len(spc.molecule) for spc in self.reaction.reactants + self.reaction.products ]): self.arkane_species.update_species_attributes( self.reaction.transition_state) self.arkane_species.reaction_label = self.reaction.label self.arkane_species.reactants = [{ 'label': spc.label, 'adjacency_list': spc.molecule[0].to_adjacency_list() } for spc in self.reaction.reactants] self.arkane_species.products = [{ 'label': spc.label, 'adjacency_list': spc.molecule[0].to_adjacency_list() } for spc in self.reaction.products] self.arkane_species.save_yaml(path=output_directory) def plot(self, output_directory): """ Plot both the raw kinetics data and the Arrhenius fit versus temperature. The plot is saved to the file ``kinetics.pdf`` in the output directory. The plot is not generated if ``matplotlib`` is not installed. """ import matplotlib.pyplot as plt f, ax = plt.subplots() if self.Tlist is not None: t_list = [t for t in self.Tlist.value_si] else: t_list = 1000.0 / np.arange(0.4, 3.35, 0.05) klist = np.zeros_like(t_list) klist2 = np.zeros_like(t_list) for i in range(len(t_list)): klist[i] = self.reaction.calculate_tst_rate_coefficient(t_list[i]) klist2[i] = self.reaction.kinetics.get_rate_coefficient(t_list[i]) order = len(self.reaction.reactants) klist *= 1e6**(order - 1) klist2 *= 1e6**(order - 1) t_list = [1000.0 / t for t in t_list] plt.semilogy(t_list, klist, 'ob', label='TST calculation') plt.semilogy(t_list, klist2, '-k', label='Fitted rate') plt.legend() reaction_str = '{0} {1} {2}'.format( ' + '.join([ reactant.label for reactant in self.reaction.reactants ]), '<=>', ' + '.join([product.label for product in self.reaction.products])) plt.title(reaction_str) plt.xlabel('1000 / Temperature (K^-1)') plt.ylabel('Rate coefficient ({0})'.format(self.k_units)) plot_path = os.path.join(output_directory, 'plots') if not os.path.exists(plot_path): os.mkdir(plot_path) valid_chars = "-_.()<=> %s%s" % (string.ascii_letters, string.digits) filename = ''.join(c for c in reaction_str if c in valid_chars) + '.pdf' plt.savefig(os.path.join(plot_path, filename)) plt.close() def draw(self, output_directory, file_format='pdf'): """ Generate a PDF drawing of the reaction. This requires that Cairo and its Python wrapper be available; if not, the drawing is not generated. You may also generate different formats of drawings, by changing format to one of the following: `pdf`, `svg`, `png`. """ drawing_path = os.path.join(output_directory, 'paths') if not os.path.exists(drawing_path): os.mkdir(drawing_path) valid_chars = "-_.()<=> %s%s" % (string.ascii_letters, string.digits) reaction_str = '{0} {1} {2}'.format( ' + '.join([ reactant.label for reactant in self.reaction.reactants ]), '<=>', ' + '.join([product.label for product in self.reaction.products])) filename = ''.join(c for c in reaction_str if c in valid_chars) + '.pdf' path = os.path.join(drawing_path, filename) KineticsDrawer().draw(self.reaction, file_format=file_format, path=path)
class ThermoJob(object): """ A representation of an Arkane thermodynamics job. This job is used to compute and save the thermodynamics information for a single species. """ def __init__(self, species, thermoClass): self.species = species self.thermoClass = thermoClass self.arkane_species = ArkaneSpecies(species=species) def execute(self, outputFile=None, plot=False): """ Execute the thermodynamics job, saving the results to the given `outputFile` on disk. """ self.generateThermo() if outputFile is not None: self.arkane_species.chemkin_thermo_string = self.save(outputFile) if self.species.molecule is None or len( self.species.molecule) == 0: logging.debug( "Not generating database YAML file for species {0}, since its structure wasn't" " specified".format(self.species.label)) else: self.arkane_species.update_species_attributes(self.species) self.arkane_species.save_yaml(path=os.path.dirname(outputFile)) if plot: self.plot(os.path.dirname(outputFile)) def generateThermo(self): """ Generate the thermodynamic data for the species and fit it to the desired heat capacity model (as specified in the `thermoClass` attribute). """ if self.thermoClass.lower() not in ['wilhoit', 'nasa']: raise Exception('Unknown thermodynamic model "{0}".'.format( self.thermoClass)) species = self.species logging.debug('Generating {0} thermo model for {1}...'.format( self.thermoClass, species)) if species.thermo is not None: logging.info( "Thermo already generated for species {}. Skipping thermo generation." .format(species)) return None Tlist = np.arange(10.0, 3001.0, 10.0, np.float64) Cplist = np.zeros_like(Tlist) H298 = 0.0 S298 = 0.0 conformer = self.species.conformer for i in range(Tlist.shape[0]): Cplist[i] += conformer.getHeatCapacity(Tlist[i]) H298 += conformer.getEnthalpy(298.) + conformer.E0.value_si S298 += conformer.getEntropy(298.) if not any([ isinstance(mode, (LinearRotor, NonlinearRotor)) for mode in conformer.modes ]): # Monatomic species linear = False Nfreq = 0 Nrotors = 0 Cp0 = 2.5 * constants.R CpInf = 2.5 * constants.R else: # Polyatomic species linear = True if isinstance(conformer.modes[1], LinearRotor) else False Nfreq = len(conformer.modes[2].frequencies.value) Nrotors = len(conformer.modes[3:]) Cp0 = (3.5 if linear else 4.0) * constants.R CpInf = Cp0 + (Nfreq + 0.5 * Nrotors) * constants.R wilhoit = Wilhoit() if Nfreq == 0 and Nrotors == 0: wilhoit.Cp0 = (Cplist[0], "J/(mol*K)") wilhoit.CpInf = (Cplist[0], "J/(mol*K)") wilhoit.B = (500., "K") wilhoit.H0 = (0.0, "J/mol") wilhoit.S0 = (0.0, "J/(mol*K)") wilhoit.H0 = (H298 - wilhoit.getEnthalpy(298.15), "J/mol") wilhoit.S0 = (S298 - wilhoit.getEntropy(298.15), "J/(mol*K)") else: wilhoit.fitToData(Tlist, Cplist, Cp0, CpInf, H298, S298, B0=500.0) if self.thermoClass.lower() == 'nasa': species.thermo = wilhoit.toNASA(Tmin=10.0, Tmax=3000.0, Tint=500.0) else: species.thermo = wilhoit def save(self, outputFile): """ Save the results of the thermodynamics job to the file located at `path` on disk. """ species = self.species logging.info('Saving thermo for {0}...'.format(species.label)) f = open(outputFile, 'a') f.write('# Thermodynamics for {0}:\n'.format(species.label)) H298 = species.getThermoData().getEnthalpy(298) / 4184. S298 = species.getThermoData().getEntropy(298) / 4.184 f.write( '# Enthalpy of formation (298 K) = {0:9.3f} kcal/mol\n'.format( H298)) f.write('# Entropy of formation (298 K) = {0:9.3f} cal/(mol*K)\n'. format(S298)) f.write( '# =========== =========== =========== =========== ===========\n' ) f.write( '# Temperature Heat cap. Enthalpy Entropy Free energy\n' ) f.write( '# (K) (cal/mol*K) (kcal/mol) (cal/mol*K) (kcal/mol)\n' ) f.write( '# =========== =========== =========== =========== ===========\n' ) for T in [300, 400, 500, 600, 800, 1000, 1500, 2000, 2400]: try: Cp = species.getThermoData().getHeatCapacity(T) / 4.184 H = species.getThermoData().getEnthalpy(T) / 4184. S = species.getThermoData().getEntropy(T) / 4.184 G = species.getThermoData().getFreeEnergy(T) / 4184. f.write( '# {0:11g} {1:11.3f} {2:11.3f} {3:11.3f} {4:11.3f}\n'. format(T, Cp, H, S, G)) except ValueError: logging.debug( "Valid thermo for {0} is outside range for temperature {1}" .format(species, T)) f.write( '# =========== =========== =========== =========== ===========\n' ) thermo_string = 'thermo(label={0!r}, thermo={1!r})'.format( species.label, species.getThermoData()) f.write('{0}\n\n'.format(prettify(thermo_string))) f.close() # write chemkin file f = open(os.path.join(os.path.dirname(outputFile), 'chem.inp'), 'a') if isinstance(species, Species): if species.molecule and isinstance(species.molecule[0], Molecule): elementCounts = retrieveElementCount(species.molecule[0]) else: try: elementCounts = species.props['elementCounts'] except KeyError: elementCounts = {'C': 0, 'H': 0} else: elementCounts = {'C': 0, 'H': 0} chemkin_thermo_string = writeThermoEntry(species, elementCounts=elementCounts, verbose=True) f.write('{0}\n'.format(chemkin_thermo_string)) f.close() # write species dictionary if isinstance(species, Species): if species.molecule and isinstance(species.molecule[0], Molecule): with open( os.path.join(os.path.dirname(outputFile), 'species_dictionary.txt'), 'a') as f: f.write(species.molecule[0].toAdjacencyList( removeH=False, label=species.label)) f.write('\n') return chemkin_thermo_string def plot(self, outputDirectory): """ Plot the heat capacity, enthapy, entropy, and Gibbs free energy of the fitted thermodynamics model, along with the same values from the statistical mechanics model that the thermodynamics model was fitted to. The plot is saved to the file ``thermo.pdf`` in the output directory. The plot is not generated if ``matplotlib`` is not installed. """ # Skip this step if matplotlib is not installed try: import matplotlib.pyplot as plt except ImportError: return Tlist = np.arange(10.0, 2501.0, 10.0) Cplist = np.zeros_like(Tlist) Cplist1 = np.zeros_like(Tlist) Hlist = np.zeros_like(Tlist) Hlist1 = np.zeros_like(Tlist) Slist = np.zeros_like(Tlist) Slist1 = np.zeros_like(Tlist) Glist = np.zeros_like(Tlist) Glist1 = np.zeros_like(Tlist) conformer = self.species.conformer thermo = self.species.getThermoData() for i in range(Tlist.shape[0]): try: Cplist[i] = conformer.getHeatCapacity(Tlist[i]) Slist[i] = conformer.getEntropy(Tlist[i]) Hlist[i] = (conformer.getEnthalpy(Tlist[i]) + conformer.E0.value_si) * 0.001 Glist[i] = Hlist[i] - Tlist[i] * Slist[i] * 0.001 Cplist1[i] = thermo.getHeatCapacity(Tlist[i]) Slist1[i] = thermo.getEntropy(Tlist[i]) Hlist1[i] = thermo.getEnthalpy(Tlist[i]) * 0.001 Glist1[i] = thermo.getFreeEnergy(Tlist[i]) * 0.001 except (ValueError, AttributeError): continue fig = plt.figure(figsize=(10, 8)) fig.suptitle('{0}'.format(self.species.label)) plt.subplot(2, 2, 1) plt.plot(Tlist, Cplist / 4.184, '-r', Tlist, Cplist1 / 4.184, '-b') plt.xlabel('Temperature (K)') plt.ylabel('Heat capacity (cal/mol*K)') plt.legend(['statmech', 'fitted'], loc=4) plt.subplot(2, 2, 2) plt.plot(Tlist, Slist / 4.184, '-r', Tlist, Slist1 / 4.184, '-b') plt.xlabel('Temperature (K)') plt.ylabel('Entropy (cal/mol*K)') plt.subplot(2, 2, 3) plt.plot(Tlist, Hlist / 4.184, '-r', Tlist, Hlist1 / 4.184, '-b') plt.xlabel('Temperature (K)') plt.ylabel('Enthalpy (kcal/mol)') plt.subplot(2, 2, 4) plt.plot(Tlist, Glist / 4.184, '-r', Tlist, Glist1 / 4.184, '-b') plt.xlabel('Temperature (K)') plt.ylabel('Gibbs free energy (kcal/mol)') fig.subplots_adjust(left=0.10, bottom=0.08, right=0.95, top=0.95, wspace=0.35, hspace=0.20) plot_path = os.path.join(outputDirectory, 'plots') if not os.path.exists(plot_path): os.mkdir(plot_path) valid_chars = "-_.()<=> %s%s" % (string.ascii_letters, string.digits) filename = ''.join( c for c in self.species.label if c in valid_chars) + '.pdf' plt.savefig(os.path.join(plot_path, filename)) plt.close()
def __init__(self, species, thermoClass): self.species = species self.thermoClass = thermoClass self.arkane_species = ArkaneSpecies(species=species)
class ThermoJob(object): """ A representation of an Arkane thermodynamics job. This job is used to compute and save the thermodynamics information for a single species. """ def __init__(self, species, thermoClass): self.species = species self.thermoClass = thermoClass self.arkane_species = ArkaneSpecies(species=species) def execute(self, outputFile=None, plot=False): """ Execute the thermodynamics job, saving the results to the given `outputFile` on disk. """ self.generateThermo() if outputFile is not None: self.arkane_species.chemkin_thermo_string = self.save(outputFile) if self.species.molecule is None or len(self.species.molecule) == 0: logging.debug("Not generating a YAML file for species {0}, since its structure wasn't" " specified".format(self.species.label)) else: # We're saving a YAML file for species iff Thermo is called and they're structure is known self.arkane_species.update_species_attributes(self.species) self.arkane_species.save_yaml(path=os.path.dirname(outputFile)) if plot: self.plot(os.path.dirname(outputFile)) def generateThermo(self): """ Generate the thermodynamic data for the species and fit it to the desired heat capacity model (as specified in the `thermoClass` attribute). """ if self.thermoClass.lower() not in ['wilhoit', 'nasa']: raise Exception('Unknown thermodynamic model "{0}".'.format(self.thermoClass)) species = self.species logging.debug('Generating {0} thermo model for {1}...'.format(self.thermoClass, species)) if species.thermo is not None: logging.info("Thermo already generated for species {}. Skipping thermo generation.".format(species)) return None Tlist = np.arange(10.0, 3001.0, 10.0, np.float64) Cplist = np.zeros_like(Tlist) H298 = 0.0 S298 = 0.0 conformer = self.species.conformer for i in range(Tlist.shape[0]): Cplist[i] += conformer.getHeatCapacity(Tlist[i]) H298 += conformer.getEnthalpy(298.) + conformer.E0.value_si S298 += conformer.getEntropy(298.) if not any([isinstance(mode, (LinearRotor, NonlinearRotor)) for mode in conformer.modes]): # Monatomic species linear = False Nfreq = 0 Nrotors = 0 Cp0 = 2.5 * constants.R CpInf = 2.5 * constants.R else: # Polyatomic species linear = True if isinstance(conformer.modes[1], LinearRotor) else False Nfreq = len(conformer.modes[2].frequencies.value) Nrotors = len(conformer.modes[3:]) Cp0 = (3.5 if linear else 4.0) * constants.R CpInf = Cp0 + (Nfreq + 0.5 * Nrotors) * constants.R wilhoit = Wilhoit() if Nfreq == 0 and Nrotors == 0: wilhoit.Cp0 = (Cplist[0],"J/(mol*K)") wilhoit.CpInf = (Cplist[0],"J/(mol*K)") wilhoit.B = (500.,"K") wilhoit.H0 = (0.0,"J/mol") wilhoit.S0 = (0.0,"J/(mol*K)") wilhoit.H0 = (H298 -wilhoit.getEnthalpy(298.15), "J/mol") wilhoit.S0 = (S298 - wilhoit.getEntropy(298.15),"J/(mol*K)") else: wilhoit.fitToData(Tlist, Cplist, Cp0, CpInf, H298, S298, B0=500.0) if self.thermoClass.lower() == 'nasa': species.thermo = wilhoit.toNASA(Tmin=10.0, Tmax=3000.0, Tint=500.0) else: species.thermo = wilhoit def save(self, outputFile): """ Save the results of the thermodynamics job to the file located at `path` on disk. """ species = self.species logging.info('Saving thermo for {0}...'.format(species.label)) f = open(outputFile, 'a') f.write('# Thermodynamics for {0}:\n'.format(species.label)) H298 = species.getThermoData().getEnthalpy(298) / 4184. S298 = species.getThermoData().getEntropy(298) / 4.184 f.write('# Enthalpy of formation (298 K) = {0:9.3f} kcal/mol\n'.format(H298)) f.write('# Entropy of formation (298 K) = {0:9.3f} cal/(mol*K)\n'.format(S298)) f.write('# =========== =========== =========== =========== ===========\n') f.write('# Temperature Heat cap. Enthalpy Entropy Free energy\n') f.write('# (K) (cal/mol*K) (kcal/mol) (cal/mol*K) (kcal/mol)\n') f.write('# =========== =========== =========== =========== ===========\n') for T in [300,400,500,600,800,1000,1500,2000,2400]: try: Cp = species.getThermoData().getHeatCapacity(T) / 4.184 H = species.getThermoData().getEnthalpy(T) / 4184. S = species.getThermoData().getEntropy(T) / 4.184 G = species.getThermoData().getFreeEnergy(T) / 4184. f.write('# {0:11g} {1:11.3f} {2:11.3f} {3:11.3f} {4:11.3f}\n'.format(T, Cp, H, S, G)) except ValueError: logging.debug("Valid thermo for {0} is outside range for temperature {1}".format(species,T)) f.write('# =========== =========== =========== =========== ===========\n') thermo_string = 'thermo(label={0!r}, thermo={1!r})'.format(species.label, species.getThermoData()) f.write('{0}\n\n'.format(prettify(thermo_string))) f.close() # write chemkin file f = open(os.path.join(os.path.dirname(outputFile), 'chem.inp'), 'a') if isinstance(species, Species): if species.molecule and isinstance(species.molecule[0], Molecule): elementCounts = retrieveElementCount(species.molecule[0]) else: try: elementCounts = species.props['elementCounts'] except KeyError: elementCounts = {'C': 0, 'H': 0} else: elementCounts = {'C': 0, 'H': 0} chemkin_thermo_string = writeThermoEntry(species, elementCounts=elementCounts, verbose=True) f.write('{0}\n'.format(chemkin_thermo_string)) f.close() # write species dictionary if isinstance(species, Species): if species.molecule and isinstance(species.molecule[0], Molecule): with open(os.path.join(os.path.dirname(outputFile), 'species_dictionary.txt'), 'a') as f: f.write(species.molecule[0].toAdjacencyList(removeH=False, label=species.label)) f.write('\n') return chemkin_thermo_string def plot(self, outputDirectory): """ Plot the heat capacity, enthapy, entropy, and Gibbs free energy of the fitted thermodynamics model, along with the same values from the statistical mechanics model that the thermodynamics model was fitted to. The plot is saved to the file ``thermo.pdf`` in the output directory. The plot is not generated if ``matplotlib`` is not installed. """ # Skip this step if matplotlib is not installed try: import matplotlib.pyplot as plt except ImportError: return Tlist = np.arange(10.0, 2501.0, 10.0) Cplist = np.zeros_like(Tlist) Cplist1 = np.zeros_like(Tlist) Hlist = np.zeros_like(Tlist) Hlist1 = np.zeros_like(Tlist) Slist = np.zeros_like(Tlist) Slist1 = np.zeros_like(Tlist) Glist = np.zeros_like(Tlist) Glist1 = np.zeros_like(Tlist) conformer = self.species.conformer thermo = self.species.getThermoData() for i in range(Tlist.shape[0]): try: Cplist[i] = conformer.getHeatCapacity(Tlist[i]) Slist[i] = conformer.getEntropy(Tlist[i]) Hlist[i] = (conformer.getEnthalpy(Tlist[i]) + conformer.E0.value_si) * 0.001 Glist[i] = Hlist[i] - Tlist[i] * Slist[i] * 0.001 Cplist1[i] = thermo.getHeatCapacity(Tlist[i]) Slist1[i] = thermo.getEntropy(Tlist[i]) Hlist1[i] = thermo.getEnthalpy(Tlist[i]) * 0.001 Glist1[i] = thermo.getFreeEnergy(Tlist[i]) * 0.001 except (ValueError,AttributeError): continue fig = plt.figure(figsize=(10,8)) fig.suptitle('{0}'.format(self.species.label)) plt.subplot(2,2,1) plt.plot(Tlist, Cplist / 4.184, '-r', Tlist, Cplist1 / 4.184, '-b') plt.xlabel('Temperature (K)') plt.ylabel('Heat capacity (cal/mol*K)') plt.legend(['statmech', 'fitted'], loc=4) plt.subplot(2,2,2) plt.plot(Tlist, Slist / 4.184, '-r', Tlist, Slist1 / 4.184, '-b') plt.xlabel('Temperature (K)') plt.ylabel('Entropy (cal/mol*K)') plt.subplot(2,2,3) plt.plot(Tlist, Hlist / 4.184, '-r', Tlist, Hlist1 / 4.184, '-b') plt.xlabel('Temperature (K)') plt.ylabel('Enthalpy (kcal/mol)') plt.subplot(2,2,4) plt.plot(Tlist, Glist / 4.184, '-r', Tlist, Glist1 / 4.184, '-b') plt.xlabel('Temperature (K)') plt.ylabel('Gibbs free energy (kcal/mol)') fig.subplots_adjust(left=0.10, bottom=0.08, right=0.95, top=0.95, wspace=0.35, hspace=0.20) plot_path = os.path.join(outputDirectory, 'plots') if not os.path.exists(plot_path): os.mkdir(plot_path) valid_chars = "-_.()<=> %s%s" % (string.ascii_letters, string.digits) filename = ''.join(c for c in self.species.label if c in valid_chars) + '.pdf' plt.savefig(os.path.join(plot_path, filename)) plt.close()