def testJobackOnBenzeneBonds(self): "Test Joback doesn't crash on Cb desription of beneze" adjlist = """ 1 C u0 p0 {2,D} {6,S} {7,S} 2 C u0 p0 {1,D} {3,S} {8,S} 3 C u0 p0 {2,S} {4,D} {9,S} 4 C u0 p0 {3,D} {5,S} {10,S} 5 C u0 p0 {4,S} {6,D} {11,S} 6 C u0 p0 {1,S} {5,D} {12,S} 7 H u0 p0 {1,S} 8 H u0 p0 {2,S} 9 H u0 p0 {3,S} 10 H u0 p0 {4,S} 11 H u0 p0 {5,S} 12 H u0 p0 {6,S} """ m = Molecule().fromAdjacencyList(adjlist) species = Species(molecule=[m]) transportData, blank, blank2 = self.transportdb.getTransportPropertiesViaGroupEstimates(species) self.assertIsNotNone(transportData)
def save_kinetics_lib(rxn_list, path, name, lib_long_desc): """ Save an RMG kinetics library of all reactions in `rxn_list` in the supplied `path` `rxn_list` is a list of ARCReaction objects `name` is the library's name (or project's name) `long_desc` is a multiline string with level of theory description """ entries = dict() if rxn_list: for i, rxn in enumerate(rxn_list): if rxn.kinetics is not None: if len(rxn.rmg_reaction.reactants): reactants = rxn.rmg_reaction.reactants products = rxn.rmg_reaction.products elif rxn.r_species.mol_list is not None: reactants = [Species(molecule=arc_spc.mol_list) for arc_spc in rxn.r_species] products = [Species(molecule=arc_spc.mol_list) for arc_spc in rxn.p_species] elif rxn.r_species.mol is not None: reactants = [Species(molecule=[arc_spc.mol]) for arc_spc in rxn.r_species] products = [Species(molecule=[arc_spc.mol]) for arc_spc in rxn.p_species] else: reactants = [Species(molecule=[arc_spc.xyz_mol]) for arc_spc in rxn.r_species] products = [Species(molecule=[arc_spc.xyz_mol]) for arc_spc in rxn.p_species] rxn.rmg_reaction.reactants = reactants rxn.rmg_reaction.products = products entry = Entry( index=i, item=rxn.rmg_reaction, data=rxn.kinetics, label=rxn.label) rxn.ts_species.make_ts_report() entry.longDesc = rxn.ts_species.ts_report + '\n\nOptimized TS geometry:\n' + rxn.ts_species.final_xyz rxn.rmg_reaction.kinetics = rxn.kinetics rxn.rmg_reaction.kinetics.comment = str('') entries[i+1] = entry else: logging.warning('Reaction {0} did not contain any kinetic data and was omitted from the kinetics' ' library.'.format(rxn.label)) kinetics_library = KineticsLibrary(name=name, longDesc=lib_long_desc, autoGenerated=True) kinetics_library.entries = entries lib_path = os.path.join(path, 'kinetics', '') if os.path.exists(lib_path): shutil.rmtree(lib_path) try: os.makedirs(lib_path) except OSError: pass kinetics_library.save(os.path.join(lib_path, 'reactions.py')) kinetics_library.saveDictionary(os.path.join(lib_path, 'dictionary.txt'))
def test_joback(self): """Test transport property estimation via Joback groups.""" self.testCases = [ ['acetone', 'CC(=O)C', Length(5.36421, 'angstroms'), Energy(3.20446, 'kJ/mol'), "Epsilon & sigma estimated with Tc=500.53 K, Pc=47.11 bar (from Joback method)"], ['cyclopenta-1,2-diene', 'C1=C=CCC1', None, None, None], # not sure what to expect, we just want to make sure it doesn't crash ['benzene', 'c1ccccc1', None, None, None], ] # values calculate from joback's estimations for name, smiles, sigma, epsilon, comment in self.testCases: species = Species().from_smiles(smiles) transport_data, blank, blank2 = self.database.get_transport_properties_via_group_estimates(species) # check Joback worked. # If we don't know what to expect, don't check (just make sure we didn't crash) if comment: self.assertTrue(transport_data.comment == comment) if sigma: self.assertAlmostEqual(transport_data.sigma.value_si * 1e10, sigma.value_si * 1e10, 4) if epsilon: self.assertAlmostEqual(transport_data.epsilon.value_si, epsilon.value_si, 1)
def ensure_species(input_list, resonance=False, keep_isomorphic=False): """ The input list of :class:`Species` or :class:`Molecule` objects is modified in place to only have :class:`Species` objects. Returns None. """ for index, item in enumerate(input_list): if isinstance(item, Molecule): new_item = Species(molecule=[item]) elif isinstance(item, Species): new_item = item else: raise TypeError('Only Molecule or Species objects can be handled.') if resonance: if not any([mol.reactive for mol in new_item.molecule]): # if generating a reaction containing a Molecule with a reactive=False flag (e.g., for degeneracy # calculations), that was now converted into a Species, first mark as reactive=True new_item.molecule[0].reactive = True new_item.generate_resonance_structures( keep_isomorphic=keep_isomorphic) input_list[index] = new_item
def testMcGowan(self): "Test we can calculate and set the McGowan volume for species containing H,C,O,N or S" self.testCases = [ ['CCCCCCCC', 1.2358], #n-octane, in library ['C(CO)O', 0.5078], #ethylene glycol ['CC#N', 0.4042], #acetonitrile ['CCS', 0.5539] #ethanethiol ] for smiles, volume in self.testCases: species = Species(molecule=[Molecule(SMILES=smiles)]) soluteData = self.database.getSoluteData(species) soluteData.setMcGowanVolume( species) # even if it was found in library, recalculate self.assertTrue( soluteData.V is not None ) # so if it wasn't found in library, we should have calculated it self.assertAlmostEqual( soluteData.V, volume ) # the volume is what we expect given the atoms and bonds
def testDeflateReaction(self): """ Test if the deflateReaction function works. """ molA = Molecule().fromSMILES('[OH]') molB = Molecule().fromSMILES('CC') molC = Molecule().fromSMILES('[CH3]') reactants = [molA, molB] # both reactants were already part of the core: reactantIndices = [1, 2] molDict = {molA: 1, molB: 2} rxn = Reaction(reactants=[molA, molB], products=[molC], pairs=[(molA, molC), (molB, molC)]) deflateReaction(rxn, molDict) for spc, t in zip(rxn.reactants, [int, int]): self.assertTrue(isinstance(spc, t)) self.assertEquals(rxn.reactants, reactantIndices) for spc in rxn.products: self.assertTrue(isinstance(spc, Species)) # one of the reactants was not yet part of the core: reactantIndices = [-1, 2] molDict = {molA: Species(molecule=[molA]), molB: 2} rxn = Reaction(reactants=[molA, molB], products=[molC], pairs=[(molA, molC), (molB, molC)]) deflateReaction(rxn, molDict) for spc, t in zip(rxn.reactants, [Species, int]): self.assertTrue(isinstance(spc, t)) for spc in rxn.products: self.assertTrue(isinstance(spc, Species))
def test_deterministic_reaction_template_matching(self): """ Test RMG work flow can match reaction template for kinetics estimation deterministically. In this test, a change of molecules order in a reacting species should not change the reaction template matched. However, this is inherently impossible with the existing reaction generation algorithm. Currently, the first reaction will be the one that is kept if the reactions are identical. If different templates are a result of different transition states, all are kept. {O=C-[C]=C, [O]-C=C=C} -> H + C=C=C=O """ # react spc = Species().from_smiles("O=C[C]=C") spc.generate_resonance_structures() new_reactions = react_species((spc,)) # try to pick out the target reaction mol_H = Molecule().from_smiles("[H]") mol_C3H2O = Molecule().from_smiles("C=C=C=O") target_rxns = find_target_rxns_containing(mol_H, mol_C3H2O, new_reactions) self.assertEqual(len(target_rxns), 2) # reverse the order of molecules in spc spc.molecule = list(reversed(spc.molecule)) # react again new_reactions_reverse = [] new_reactions_reverse.extend(react_species((spc,))) # try to pick out the target reaction target_rxns_reverse = find_target_rxns_containing(mol_H, mol_C3H2O, new_reactions_reverse) self.assertEqual(len(target_rxns_reverse), 2) # whatever order of molecules in spc, the reaction template matched should be same self.assertEqual(target_rxns[0].template, target_rxns_reverse[0].template)
def setUpClass(self): """A function that is run ONCE before all unit tests in this class.""" self.database = TransportDatabase() self.database.load( os.path.join(settings['database.directory'], 'transport'), ['GRI-Mech', 'PrimaryTransportLibrary']) self.speciesList = [ Species().fromSMILES('C'), Species().fromSMILES('CCCC'), Species().fromSMILES('O'), Species().fromSMILES('[CH3]'), Species().fromSMILES('[OH]'), Species().fromSMILES('c1ccccc1'), ]
def compare(self, inchi, u_indices=None, p_indices=None): u_layer = U_LAYER_PREFIX + U_LAYER_SEPARATOR.join(map(str, u_indices)) if u_indices else None p_layer = P_LAYER_PREFIX + P_LAYER_SEPARATOR.join(map(str, p_indices)) if p_indices else None aug_inchi = compose_aug_inchi(inchi, u_layer, p_layer) mol = from_augmented_inchi(Molecule(), aug_inchi) ConsistencyChecker.check_multiplicity(mol.get_radical_count(), mol.multiplicity) for at in mol.atoms: ConsistencyChecker.check_partial_charge(at) spc = Species(molecule=[mol]) spc.generate_resonance_structures() ignore_prefix = r"(InChI=1+)(S*)/" aug_inchi_expected = re.split(ignore_prefix, aug_inchi)[-1] aug_inchi_computed = re.split(ignore_prefix, spc.get_augmented_inchi())[-1] self.assertEquals(aug_inchi_expected, aug_inchi_computed) return mol
def test_cantera(self): """ Test that a Cantera Species object is created correctly. """ from rmgpy.thermo import NASA, NASAPolynomial import cantera as ct rmg_species = Species(label="Ar", thermo=NASA( polynomials=[NASAPolynomial(coeffs=[2.5, 0, 0, 0, 0, -745.375, 4.37967], Tmin=(200, 'K'), Tmax=(1000, 'K')), NASAPolynomial(coeffs=[2.5, 0, 0, 0, 0, -745.375, 4.37967], Tmin=(1000, 'K'), Tmax=(6000, 'K'))], Tmin=(200, 'K'), Tmax=(6000, 'K'), comment=""" Thermo library: primaryThermoLibrary """), molecule=[Molecule(smiles="[Ar]")], transport_data=TransportData(shapeIndex=0, epsilon=(1134.93, 'J/mol'), sigma=(3.33, 'angstrom'), dipoleMoment=(2, 'De'), polarizability=(1, 'angstrom^3'), rotrelaxcollnum=15.0, comment="""GRI-Mech""")) rmg_ct_species = rmg_species.to_cantera(use_chemkin_identifier=True) ct_species = ct.Species.fromCti("""species(name=u'Ar', atoms='Ar:1', thermo=(NASA([200.00, 1000.00], [ 2.50000000E+00, 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, -7.45375000E+02, 4.37967000E+00]), NASA([1000.00, 6000.00], [ 2.50000000E+00, 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, 0.00000000E+00, -7.45375000E+02, 4.37967000E+00])), transport=gas_transport(geom='atom', diam=3.33, well_depth=136.501, dipole=2.0, polar=1.0, rot_relax=15.0))""") self.assertEqual(type(rmg_ct_species), type(ct_species)) self.assertEqual(rmg_ct_species.name, ct_species.name) self.assertEqual(rmg_ct_species.composition, ct_species.composition) self.assertEqual(rmg_ct_species.size, ct_species.size) self.assertEqual(type(rmg_ct_species.thermo), type(ct_species.thermo)) self.assertEqual(type(rmg_ct_species.transport), type(ct_species.transport))
def test_get_kinetic_isotope_effect_simple(self): reactant_pair = [Species().from_smiles("C"), Species().from_smiles("[H]")] product_pair = [Species().from_smiles("[H][H]"), Species().from_smiles("[CH3]")] rxn_unlabeled = TemplateReaction(reactants=reactant_pair, products=product_pair, family='H_Abstraction', kinetics=Arrhenius(A=(1e5, 'cm^3/(mol*s)'), Ea=(0, 'J/mol'))) rxn_labeled = TemplateReaction(reactants=[ Species().from_adjacency_list(""" 1 C u0 p0 c0 i13 {2,S} {3,S} {4,S} {5,S} 2 H u0 p0 c0 {1,S} 3 H u0 p0 c0 {1,S} 4 H u0 p0 c0 {1,S} 5 H u0 p0 c0 {1,S} """), Species().from_adjacency_list(""" multiplicity 2 1 H u1 p0 c0 """)], products=[ Species().from_adjacency_list(""" 1 H u0 p0 c0 {2,S} 2 H u0 p0 c0 {1,S} """), Species().from_adjacency_list(""" multiplicity 2 1 C u1 p0 c0 i13 {2,S} {3,S} {4,S} 2 H u0 p0 c0 {1,S} 3 H u0 p0 c0 {1,S} 4 H u0 p0 c0 {1,S} """)], family='H_Abstraction', kinetics=Arrhenius(A=(1e5, 'cm^3/(mol*s)'), Ea=(0, 'J/mol'))) rxn_cluster = [[rxn_labeled, rxn_unlabeled]] apply_kinetic_isotope_effect_simple(rxn_cluster, self.database.kinetics) expected_kie = ((1 / 1.008 + 1 / (13.01 + 1.008)) / (1 / 1.008 + 1 / (12.01 + 1.008))) ** 0.5 self.assertAlmostEqual(rxn_cluster[0][0].kinetics.A.value, 1e5 * expected_kie, places=-1)
def test_specifying_absolute_file_paths(self): """Test specifying absolute file paths of statmech files""" h2o2_input = """#!/usr/bin/env python # -*- coding: utf-8 -*- bonds = {{'H-O': 2, 'O-O': 1}} externalSymmetry = 2 spinMultiplicity = 1 opticalIsomers = 1 energy = {{'b3lyp/6-311+g(3df,2p)': Log('{energy}')}} geometry = Log('{freq}') frequencies = Log('{freq}') rotors = [HinderedRotor(scanLog=Log('{scan}'), pivots=[1, 2], top=[1, 3], symmetry=1, fit='fourier')] """ abs_arkane_path = os.path.abspath(os.path.dirname(__file__)) # this is the absolute path to `.../RMG-Py/arkane` energy_path = os.path.join('arkane', 'data', 'H2O2', 'sp_a19032.out') freq_path = os.path.join('arkane', 'data', 'H2O2', 'freq_a19031.out') scan_path = os.path.join('arkane', 'data', 'H2O2', 'scan_a19034.out') h2o2_input = h2o2_input.format(energy=energy_path, freq=freq_path, scan=scan_path) h2o2_path = os.path.join(abs_arkane_path, 'data', 'H2O2', 'H2O2.py') if not os.path.exists(os.path.dirname(h2o2_path)): os.makedirs(os.path.dirname(h2o2_path)) with open(h2o2_path, 'w') as f: f.write(h2o2_input) h2o2 = Species(label='H2O2', smiles='OO') self.assertIsNone(h2o2.conformer) statmech_job = StatMechJob(species=h2o2, path=h2o2_path) statmech_job.modelChemistry = 'b3lyp/6-311+g(3df,2p)' statmech_job.load(pdep=False, plot=False) self.assertAlmostEqual(h2o2.conformer.E0.value_si, -146031.49933673252) os.remove(h2o2_path)
def deflateReaction(rxn, molDict): """ This function deflates a single reaction, and uses the provided dictionary to populate reactants/products/pairs with integer indices, if possible. If the Molecule object could not be found in the dictionary, a new dictionary entry is created, creating a new Species object as the value for the entry. The reactants/products/pairs of both the forward and reverse reaction object are populated with the value of the dictionary, either an integer index, or either a Species object. """ for mol in itertools.chain(rxn.reactants, rxn.products): if not mol in molDict: molDict[mol] = Species(molecule=[mol]) rxn.reactants = [molDict[mol] for mol in rxn.reactants] rxn.products = [molDict[mol] for mol in rxn.products] rxn.pairs = [(molDict[reactant], molDict[product]) for reactant, product in rxn.pairs]
def main(inputPath, outputPath): species = [] with open(inputPath, 'r+b') as f: for line0 in f: line1 = line0.strip() if line1 and line1[0] != '!': # Parse line using regex match = re.match(r"([^\s!]+)\s+([^\s!]+)", line1.strip()) # Get label and SMILES string label = match.group(1) smiles = match.group(2) # Save in species dictionary spec = Species().fromSMILES(smiles) spec.label = label species.append(spec) # Write to dictionary file saveSpeciesDictionary(outputPath, species)
def generate_isotopomers(spc, N=1): """ Generate all isotopomers of the parameter species by adding max. N carbon isotopes to the atoms of the species. """ mol = spc.molecule[0] isotope = get_element(6, 13) mols = [] add_isotope(0, N, mol, mols, isotope) spcs = [] for isomol in mols: isotopomer = Species(molecule=[isomol], thermo=deepcopy(spc.thermo), transport_data=spc.transport_data, reactive=spc.reactive) isotopomer.generate_resonance_structures(keep_isomorphic=True) spcs.append(isotopomer) # do not retain identical species: filtered = [] while spcs: candidate = spcs.pop() unique = True for isotopomer in filtered: if isotopomer.is_isomorphic(candidate): unique = False break if unique: filtered.append(candidate) if spc.thermo: for isotopomer in filtered: correct_entropy(isotopomer, spc) return filtered
def setUp(self): """ A method that is run before each unit test in this class. """ self.species = Species( index=1, label='C2H4', thermo=ThermoData( Tdata=([300.0, 400.0, 500.0, 600.0, 800.0, 1000.0, 1500.0], 'K'), Cpdata=([3.0, 4.0, 5.0, 6.0, 8.0, 10.0, 15.0], 'cal/(mol*K)'), H298=(-20.0, 'kcal/mol'), S298=(50.0, 'cal/(mol*K)'), Tmin=(300.0, 'K'), Tmax=(2000.0, 'K'), ), conformer=Conformer( E0=(0.0, 'kJ/mol'), modes=[ IdealGasTranslation(mass=(28.03, 'amu')), NonlinearRotor( inertia=([5.6952e-47, 2.7758e-46, 3.3454e-46], 'kg*m^2'), symmetry=1), HarmonicOscillator(frequencies=([ 834.50, 973.31, 975.37, 1067.1, 1238.5, 1379.5, 1472.3, 1691.3, 3121.6, 3136.7, 3192.5, 3221.0 ], 'cm^-1')), ], spinMultiplicity=1, opticalIsomers=1, ), molecule=[Molecule().fromSMILES('C=C')], transportData=TransportData(sigma=(1, 'angstrom'), epsilon=(100, 'K')), molecularWeight=(28.03, 'amu'), reactive=True, )
def create_species_from_smiles(smiles_dictionary): """ Creates a dictionary with user names as keys and specie objects as values =========================== ======================================================================= Input Description =========================== ======================================================================= smiles_dictionary A dictionary with user names as keys and SMILES strings as values =================================================================================================== =========================== ======================================================================= Output Description =========================== ======================================================================= user_species_dictionary A dictionary with user names as keys and species objects as values =================================================================================================== """ user_species_dictionary = {} for (user_name, smiles_string) in smiles_dictionary.iteritems(): user_species_dictionary[user_name] = Species( label=user_name).fromSMILES(smiles_string) return user_species_dictionary
def test_mark_duplicate_reactions(self): """Test that we can properly mark duplicate reactions for Chemkin.""" s1 = Species().from_smiles('CC') s2 = Species().from_smiles('[CH3]') s3 = Species().from_smiles('[OH]') s4 = Species().from_smiles('C[CH2]') s5 = Species().from_smiles('O') s6 = Species().from_smiles('[H]') # Try initializing with duplicate=False reaction_list = [ Reaction(reactants=[s1], products=[s2, s2], duplicate=False, kinetics=Arrhenius()), Reaction(reactants=[s1], products=[s2, s2], duplicate=False, kinetics=Arrhenius()), Reaction(reactants=[s1, s3], products=[s4, s5], duplicate=False, kinetics=Arrhenius()), Reaction(reactants=[s1, s3], products=[s4, s5], duplicate=False, kinetics=Chebyshev()), Reaction(reactants=[s1], products=[s4, s6], duplicate=False, kinetics=Arrhenius(), reversible=False), Reaction(reactants=[s1], products=[s4, s6], duplicate=False, kinetics=Arrhenius(), reversible=False), Reaction(reactants=[s5], products=[s3, s6], duplicate=False, kinetics=Arrhenius(), reversible=False), Reaction(reactants=[s3, s6], products=[s5], duplicate=False, kinetics=Arrhenius(), reversible=False), ] expected_flags = [True, True, False, False, True, True, False, False] mark_duplicate_reactions(reaction_list) duplicate_flags = [rxn.duplicate for rxn in reaction_list] self.assertEqual(duplicate_flags, expected_flags) # Try initializing with duplicate=True reaction_list = [ Reaction(reactants=[s1], products=[s2, s2], duplicate=True, kinetics=Arrhenius()), Reaction(reactants=[s1], products=[s2, s2], duplicate=True, kinetics=Arrhenius()), Reaction(reactants=[s1, s3], products=[s4, s5], duplicate=True, kinetics=Arrhenius()), Reaction(reactants=[s1, s3], products=[s4, s5], duplicate=True, kinetics=Chebyshev()), Reaction(reactants=[s1], products=[s4, s6], duplicate=True, kinetics=Arrhenius(), reversible=False), Reaction(reactants=[s1], products=[s4, s6], duplicate=True, kinetics=Arrhenius(), reversible=False), Reaction(reactants=[s5], products=[s3, s6], duplicate=True, kinetics=Arrhenius(), reversible=False), Reaction(reactants=[s3, s6], products=[s5], duplicate=True, kinetics=Arrhenius(), reversible=False), ] mark_duplicate_reactions(reaction_list) duplicate_flags = [rxn.duplicate for rxn in reaction_list] self.assertEqual(duplicate_flags, expected_flags)
def load_entry( self, index, label, solvent, molecule=None, reference=None, referenceType='', shortDesc='', longDesc='', ): """ Method for parsing entries in database files. Note that these argument names are retained for backward compatibility. """ if molecule is not None: if not isinstance(molecule, list): molecule = [molecule] spc_list = [] for mol in molecule: spc0 = Species(label=label) spc0.set_structure(mol) spc_list.append(spc0) else: spc_list = None self.entries[label] = Entry( index=index, label=label, item=spc_list, data=solvent, reference=reference, reference_type=referenceType, short_desc=shortDesc, long_desc=longDesc.strip(), )
def testCorrectionGeneration(self): "Test we can estimate solvation thermochemistry." self.testCases = [ # solventName, soluteName, soluteSMILES, Hsolv, Gsolv ['water', 'acetic acid', 'C(C)(=O)O', -56500, -6700 * 4.184], [ 'water', 'naphthalene', 'C1=CC=CC2=CC=CC=C12', -42800, -2390 * 4.184 ], ['1-octanol', 'octane', 'CCCCCCCC', -40080, -4180 * 4.184], ['1-octanol', 'tetrahydrofuran', 'C1CCOC1', -28320, -3930 * 4.184], ['benzene', 'toluene', 'C1(=CC=CC=C1)C', -37660, -5320 * 4.184], ['benzene', '1,4-dioxane', 'C1COCCO1', -39030, -5210 * 4.184] ] for solventName, soluteName, smiles, H, G in self.testCases: species = Species(molecule=[Molecule(SMILES=smiles)]) soluteData = self.database.getSoluteData(species) solventData = self.database.getSolventData(solventName) solvationCorrection = self.database.getSolvationCorrection( soluteData, solventData) self.assertAlmostEqual( solvationCorrection.enthalpy / 10000., H / 10000., 0, msg= "Solvation enthalpy discrepancy ({2:.0f}!={3:.0f}) for {0} in {1}" .format(soluteName, solventName, solvationCorrection.enthalpy, H)) #0 decimal place, in 10kJ. self.assertAlmostEqual( solvationCorrection.gibbs / 10000., G / 10000., 0, msg= "Solvation Gibbs free energy discrepancy ({2:.0f}!={3:.0f}) for {0} in {1}" .format(soluteName, solventName, solvationCorrection.gibbs, G))
def test_smiles_instantiation(self): """Test that we can create a species using the SMILES argument""" test = Species(smiles='C1=CC=CC=C1') self.assertTrue(test.is_isomorphic(self.species2))
def test_inchi_instantiation(self): """Test that we can create a species using the InChI argument""" test = Species(inchi='InChI=1S/C6H6/c1-2-4-6-5-3-1/h1-6H') self.assertTrue(test.is_isomorphic(self.species2))
def test_is_isomorphic_to_filtered_resonance_structure(self): """ Test that a Species containing a non-representative resonance structure is isomorphic with the "correct" Species containing only representative structures (which were not filtered out) When generating resonance isomers for N/O/S atoms, a large number of resonance structures per species could potentially be generated, yet most are filtered out and only the "correct" / "representative" structures are kept. This test makes sure that if a non-representative structure (i.e., a structure that was filtered out) is generated, RMG finds the Species it belongs to, if the last exists. """ spc1_correct = Species().from_smiles( '[O]N=O') # check charge separation with higher octet deviation spc1_nonrepresentative = Species().from_adjacency_list( """multiplicity 2 1 N u1 p1 c0 {2,S} {3,S} 2 O u0 p3 c-1 {1,S} 3 O u0 p2 c+1 {1,S}""" ) spc2_correct = Species().from_smiles( '[N]=NON=O') # check atoms with val 6 spc2_nonrepresentative = Species().from_adjacency_list( """multiplicity 2 1 O u0 p2 c0 {2,S} {3,S} 2 N u1 p1 c0 {1,S} {4,S} 3 N u0 p2 c-1 {1,S} {5,S} 4 N u0 p2 c0 {2,S} 5 O u0 p2 c+1 {3,S}""" ) spc3_correct = Species().from_smiles('[O]S(O)=O') # check O4tc penalty spc3_nonrepresentative = Species().from_adjacency_list( """multiplicity 2 1 S u0 p1 c-1 {2,S} {3,S} {4,T} 2 O u0 p2 c0 {1,S} {5,S} 3 O u1 p2 c0 {1,S} 4 O u0 p1 c+1 {1,T} 5 H u0 p0 c0 {2,S}""" ) spc4_correct = Species().from_smiles( 'OS(=[N+]=[N-])O') # check O4dc penalty spc4_nonrepresentative = Species().from_adjacency_list( """1 S u0 p0 c+1 {2,D} {3,D} {4,S} 2 N u0 p1 c0 {1,D} {5,S} 3 O u0 p1 c+1 {1,D} {6,S} 4 O u0 p2 c0 {1,S} {7,S} 5 N u0 p3 c-2 {2,S} 6 H u0 p0 c0 {3,S} 7 H u0 p0 c0 {4,S}""" ) spc5_correct = Species().from_smiles('[O][S]') # checks birad penalty spc5_nonrepresentative = Species().from_adjacency_list( """multiplicity 3 1 O u0 p2 c0 {2,D} 2 S u2 p1 c0 {1,D}""" ) spc6_correct = Species().from_smiles( '[N-]=[N+]=S=S=O') # checks the S#S case spc6_nonrepresentative = Species().from_adjacency_list( """1 S u0 p1 c0 {2,S} {3,T} 2 N u0 p0 c+1 {1,S} {4,T} 3 S u0 p1 c0 {1,T} {5,S} 4 N u0 p1 c0 {2,T} 5 O u0 p3 c-1 {3,S}""" ) # check that the structures are not isomorphic if resonance structures are not generated: self.assertFalse( spc1_correct.is_isomorphic(spc1_nonrepresentative, strict=True)) # check that the nonrepresentative structure is isomorphic by generating resonance structures: self.assertTrue( spc1_correct.is_isomorphic(spc1_nonrepresentative, strict=False)) self.assertTrue( spc2_correct.is_isomorphic(spc2_nonrepresentative, strict=False)) self.assertTrue( spc3_correct.is_isomorphic(spc3_nonrepresentative, strict=False)) self.assertTrue( spc4_correct.is_isomorphic(spc4_nonrepresentative, strict=False)) self.assertTrue( spc5_correct.is_isomorphic(spc5_nonrepresentative, strict=False)) self.assertTrue( spc6_correct.is_isomorphic(spc6_nonrepresentative, strict=False))
def test_resonance_isomers_generated(self): """Test that 1-penten-3-yl makes 2-penten-1-yl resonance isomer""" spec = Species().from_smiles('C=C[CH]CC') spec.generate_resonance_structures() self.assertEquals(len(spec.molecule), 2) self.assertEquals(spec.molecule[1].to_smiles(), "[CH2]C=CCC")
def species(label, structure): spc = Species(label=label, molecule=[structure]) initialSpecies[label] = spc return spc
def loadFAMEInput(path, moleculeDict=None): """ Load the contents of a FAME input file into the MEASURE object. FAME is an early version of MEASURE written in Fortran and used by RMG-Java. This script enables importing FAME input files into MEASURE so we can use the additional functionality that MEASURE provides. Note that it is mostly designed to load the FAME input files generated automatically by RMG-Java, and may not load hand-crafted FAME input files. If you specify a `moleculeDict`, then this script will use it to associate the species with their structures. """ def readMeaningfulLine(f): line = f.readline() while line != '': line = line.strip() if len(line) > 0 and line[0] != '#': return line else: line = f.readline() return '' moleculeDict = moleculeDict or {} logging.info('Loading file "{0}"...'.format(path)) f = open(path) job = PressureDependenceJob(network=None) # Read method method = readMeaningfulLine(f).lower() if method == 'modifiedstrongcollision': job.method = 'modified strong collision' elif method == 'reservoirstate': job.method = 'reservoir state' # Read temperatures Tcount, Tunits, Tmin, Tmax = readMeaningfulLine(f).split() job.Tmin = Quantity(float(Tmin), Tunits) job.Tmax = Quantity(float(Tmax), Tunits) job.Tcount = int(Tcount) Tlist = [] for i in range(int(Tcount)): Tlist.append(float(readMeaningfulLine(f))) job.Tlist = Quantity(Tlist, Tunits) # Read pressures Pcount, Punits, Pmin, Pmax = readMeaningfulLine(f).split() job.Pmin = Quantity(float(Pmin), Punits) job.Pmax = Quantity(float(Pmax), Punits) job.Pcount = int(Pcount) Plist = [] for i in range(int(Pcount)): Plist.append(float(readMeaningfulLine(f))) job.Plist = Quantity(Plist, Punits) # Read interpolation model model = readMeaningfulLine(f).split() if model[0].lower() == 'chebyshev': job.interpolationModel = ('chebyshev', int(model[1]), int(model[2])) elif model[0].lower() == 'pdeparrhenius': job.interpolationModel = ('pdeparrhenius',) # Read grain size or number of grains job.minimumGrainCount = 0 job.maximumGrainSize = None for i in range(2): data = readMeaningfulLine(f).split() if data[0].lower() == 'numgrains': job.minimumGrainCount = int(data[1]) elif data[0].lower() == 'grainsize': job.maximumGrainSize = (float(data[2]), data[1]) # A FAME file is almost certainly created during an RMG job, so use RMG mode job.rmgmode = True # Create the Network job.network = Network() # Read collision model data = readMeaningfulLine(f) assert data.lower() == 'singleexpdown' alpha0units, alpha0 = readMeaningfulLine(f).split() T0units, T0 = readMeaningfulLine(f).split() n = readMeaningfulLine(f) energyTransferModel = SingleExponentialDown( alpha0 = Quantity(float(alpha0), alpha0units), T0 = Quantity(float(T0), T0units), n = float(n), ) speciesDict = {} # Read bath gas parameters bathGas = Species(label='bath_gas', energyTransferModel=energyTransferModel) molWtunits, molWt = readMeaningfulLine(f).split() if molWtunits == 'u': molWtunits = 'amu' bathGas.molecularWeight = Quantity(float(molWt), molWtunits) sigmaLJunits, sigmaLJ = readMeaningfulLine(f).split() epsilonLJunits, epsilonLJ = readMeaningfulLine(f).split() assert epsilonLJunits == 'J' bathGas.transportData = TransportData( sigma = Quantity(float(sigmaLJ), sigmaLJunits), epsilon = Quantity(float(epsilonLJ) / constants.kB, 'K'), ) job.network.bathGas = {bathGas: 1.0} # Read species data Nspec = int(readMeaningfulLine(f)) for i in range(Nspec): species = Species() species.conformer = Conformer() species.energyTransferModel = energyTransferModel # Read species label species.label = readMeaningfulLine(f) speciesDict[species.label] = species if species.label in moleculeDict: species.molecule = [moleculeDict[species.label]] # Read species E0 E0units, E0 = readMeaningfulLine(f).split() species.conformer.E0 = Quantity(float(E0), E0units) species.conformer.E0.units = 'kJ/mol' # Read species thermo data H298units, H298 = readMeaningfulLine(f).split() S298units, S298 = readMeaningfulLine(f).split() Cpcount, Cpunits = readMeaningfulLine(f).split() Cpdata = [] for i in range(int(Cpcount)): Cpdata.append(float(readMeaningfulLine(f))) if S298units == 'J/mol*K': S298units = 'J/(mol*K)' if Cpunits == 'J/mol*K': Cpunits = 'J/(mol*K)' species.thermo = ThermoData( H298 = Quantity(float(H298), H298units), S298 = Quantity(float(S298), S298units), Tdata = Quantity([300,400,500,600,800,1000,1500], "K"), Cpdata = Quantity(Cpdata, Cpunits), Cp0 = (Cpdata[0], Cpunits), CpInf = (Cpdata[-1], Cpunits), ) # Read species collision parameters molWtunits, molWt = readMeaningfulLine(f).split() if molWtunits == 'u': molWtunits = 'amu' species.molecularWeight = Quantity(float(molWt), molWtunits) sigmaLJunits, sigmaLJ = readMeaningfulLine(f).split() epsilonLJunits, epsilonLJ = readMeaningfulLine(f).split() assert epsilonLJunits == 'J' species.transportData = TransportData( sigma = Quantity(float(sigmaLJ), sigmaLJunits), epsilon = Quantity(float(epsilonLJ) / constants.kB, 'K'), ) # Read species vibrational frequencies freqCount, freqUnits = readMeaningfulLine(f).split() frequencies = [] for j in range(int(freqCount)): frequencies.append(float(readMeaningfulLine(f))) species.conformer.modes.append(HarmonicOscillator( frequencies = Quantity(frequencies, freqUnits), )) # Read species external rotors rotCount, rotUnits = readMeaningfulLine(f).split() if int(rotCount) > 0: raise NotImplementedError('Cannot handle external rotational modes in FAME input.') # Read species internal rotors freqCount, freqUnits = readMeaningfulLine(f).split() frequencies = [] for j in range(int(freqCount)): frequencies.append(float(readMeaningfulLine(f))) barrCount, barrUnits = readMeaningfulLine(f).split() barriers = [] for j in range(int(barrCount)): barriers.append(float(readMeaningfulLine(f))) if barrUnits == 'cm^-1': barrUnits = 'J/mol' barriers = [barr * constants.h * constants.c * constants.Na * 100. for barr in barriers] elif barrUnits in ['Hz', 's^-1']: barrUnits = 'J/mol' barriers = [barr * constants.h * constants.Na for barr in barriers] elif barrUnits != 'J/mol': raise Exception('Unexpected units "{0}" for hindered rotor barrier height.'.format(barrUnits)) inertia = [V0 / 2.0 / (nu * constants.c * 100.)**2 / constants.Na for nu, V0 in zip(frequencies, barriers)] for I, V0 in zip(inertia, barriers): species.conformer.modes.append(HinderedRotor( inertia = Quantity(I,"kg*m^2"), barrier = Quantity(V0,barrUnits), symmetry = 1, semiclassical = False, )) # Read overall symmetry number species.conformer.spinMultiplicity = int(readMeaningfulLine(f)) # Read isomer, reactant channel, and product channel data Nisom = int(readMeaningfulLine(f)) Nreac = int(readMeaningfulLine(f)) Nprod = int(readMeaningfulLine(f)) for i in range(Nisom): data = readMeaningfulLine(f).split() assert data[0] == '1' job.network.isomers.append(speciesDict[data[1]]) for i in range(Nreac): data = readMeaningfulLine(f).split() assert data[0] == '2' job.network.reactants.append([speciesDict[data[1]], speciesDict[data[2]]]) for i in range(Nprod): data = readMeaningfulLine(f).split() if data[0] == '1': job.network.products.append([speciesDict[data[1]]]) elif data[0] == '2': job.network.products.append([speciesDict[data[1]], speciesDict[data[2]]]) # Read path reactions Nrxn = int(readMeaningfulLine(f)) for i in range(Nrxn): # Read and ignore reaction equation equation = readMeaningfulLine(f) reaction = Reaction(transitionState=TransitionState(), reversible=True) job.network.pathReactions.append(reaction) reaction.transitionState.conformer = Conformer() # Read reactant and product indices data = readMeaningfulLine(f).split() reac = int(data[0]) - 1 prod = int(data[1]) - 1 if reac < Nisom: reaction.reactants = [job.network.isomers[reac]] elif reac < Nisom+Nreac: reaction.reactants = job.network.reactants[reac-Nisom] else: reaction.reactants = job.network.products[reac-Nisom-Nreac] if prod < Nisom: reaction.products = [job.network.isomers[prod]] elif prod < Nisom+Nreac: reaction.products = job.network.reactants[prod-Nisom] else: reaction.products = job.network.products[prod-Nisom-Nreac] # Read reaction E0 E0units, E0 = readMeaningfulLine(f).split() reaction.transitionState.conformer.E0 = Quantity(float(E0), E0units) reaction.transitionState.conformer.E0.units = 'kJ/mol' # Read high-pressure limit kinetics data = readMeaningfulLine(f) assert data.lower() == 'arrhenius' Aunits, A = readMeaningfulLine(f).split() if '/' in Aunits: index = Aunits.find('/') Aunits = '{0}/({1})'.format(Aunits[0:index], Aunits[index+1:]) Eaunits, Ea = readMeaningfulLine(f).split() n = readMeaningfulLine(f) reaction.kinetics = Arrhenius( A = Quantity(float(A), Aunits), Ea = Quantity(float(Ea), Eaunits), n = Quantity(float(n)), ) reaction.kinetics.Ea.units = 'kJ/mol' f.close() job.network.isomers = [Configuration(isomer) for isomer in job.network.isomers] job.network.reactants = [Configuration(*reactants) for reactants in job.network.reactants] job.network.products = [Configuration(*products) for products in job.network.products] return job
def getForwardReactionForFamilyEntry(self, entry, family, thermoDatabase): """ For a given `entry` for a reaction of the given reaction `family` (the string label of the family), return the reaction with kinetics and degeneracy for the "forward" direction as defined by the reaction family. For families that are their own reverse, the direction the kinetics is given in will be preserved. If the entry contains functional groups for the reactants, assume that it is given in the forward direction and do nothing. Returns the reaction in the direction consistent with the reaction family template, and the matching template. Note that the returned reaction will have its kinetics and degeneracy set appropriately. In order to reverse the reactions that are given in the reverse of the direction the family is defined, we need to compute the thermodynamics of the reactants and products. For this reason you must also pass the `thermoDatabase` to use to generate the thermo data. """ def generateThermoData(species, thermoDatabase): thermoData = [thermoDatabase.getThermoData(species)] thermoData.sort(key=lambda x: x.getEnthalpy(298)) return thermoData[0] def matchSpeciesToMolecules(species, molecules): if len(species) == len(molecules) == 1: return species[0].isIsomorphic(molecules[0]) elif len(species) == len(molecules) == 2: if species[0].isIsomorphic( molecules[0]) and species[1].isIsomorphic( molecules[1]): return True elif species[0].isIsomorphic( molecules[1]) and species[1].isIsomorphic( molecules[0]): return True return False reaction = None template = None # Get the indicated reaction family try: groups = self.families[family].groups except KeyError: raise ValueError( 'Invalid value "{0}" for family parameter.'.format(family)) if all([(isinstance(reactant, Group) or isinstance(reactant, LogicNode)) for reactant in entry.item.reactants]): # The entry is a rate rule, containing functional groups only # By convention, these are always given in the forward direction and # have kinetics defined on a per-site basis reaction = Reaction( reactants=entry.item.reactants[:], products=[], kinetics=entry.data, degeneracy=1, ) template = [ groups.entries[label] for label in entry.label.split(';') ] elif (all([ isinstance(reactant, Molecule) for reactant in entry.item.reactants ]) and all( [isinstance(product, Molecule) for product in entry.item.products])): # The entry is a real reaction, containing molecules # These could be defined for either the forward or reverse direction # and could have a reaction-path degeneracy reaction = Reaction(reactants=[], products=[]) for molecule in entry.item.reactants: reactant = Species(molecule=[molecule]) reactant.generateResonanceIsomers() reactant.thermo = generateThermoData(reactant, thermoDatabase) reaction.reactants.append(reactant) for molecule in entry.item.products: product = Species(molecule=[molecule]) product.generateResonanceIsomers() product.thermo = generateThermoData(product, thermoDatabase) reaction.products.append(product) # Generate all possible reactions involving the reactant species generatedReactions = self.generateReactionsFromFamilies( [reactant.molecule for reactant in reaction.reactants], [], only_families=[family]) # Remove from that set any reactions that don't produce the desired reactants and products forward = [] reverse = [] for rxn in generatedReactions: if matchSpeciesToMolecules( reaction.reactants, rxn.reactants) and matchSpeciesToMolecules( reaction.products, rxn.products): forward.append(rxn) if matchSpeciesToMolecules( reaction.reactants, rxn.products) and matchSpeciesToMolecules( reaction.products, rxn.reactants): reverse.append(rxn) # We should now know whether the reaction is given in the forward or # reverse direction if len(forward) == 1 and len(reverse) == 0: # The reaction is in the forward direction, so use as-is reaction = forward[0] template = reaction.template # Don't forget to overwrite the estimated kinetics from the database with the kinetics for this entry reaction.kinetics = entry.data elif len(reverse) == 1 and len(forward) == 0: # The reaction is in the reverse direction # First fit Arrhenius kinetics in that direction Tdata = 1000.0 / numpy.arange(0.5, 3.301, 0.1, numpy.float64) kdata = numpy.zeros_like(Tdata) for i in range(Tdata.shape[0]): kdata[i] = entry.data.getRateCoefficient( Tdata[i]) / reaction.getEquilibriumConstant(Tdata[i]) kunits = 'm^3/(mol*s)' if len( reverse[0].reactants) == 2 else 's^-1' kinetics = Arrhenius().fitToData(Tdata, kdata, kunits, T0=1.0) kinetics.Tmin = entry.data.Tmin kinetics.Tmax = entry.data.Tmax kinetics.Pmin = entry.data.Pmin kinetics.Pmax = entry.data.Pmax # Now flip the direction reaction = reverse[0] reaction.kinetics = kinetics template = reaction.template elif len(reverse) > 0 and len(forward) > 0: print 'FAIL: Multiple reactions found for {0!r}.'.format( entry.label) elif len(reverse) == 0 and len(forward) == 0: print 'FAIL: No reactions found for "%s".' % (entry.label) else: print 'FAIL: Unable to estimate kinetics for {0!r}.'.format( entry.label) assert reaction is not None assert template is not None return reaction, template
def test_restart(self): """ Test restarting ARC through the ARC class in main.py via the input_dict argument of the API Rather than through ARC.py. Check that all files are in place and tst file content. """ restart_path = os.path.join(arc_path, 'arc', 'testing', 'restart(H,H2O2,N2H3,CH3CO2).yml') project = 'arc_project_for_testing_delete_after_usage2' project_directory = os.path.join(arc_path, 'Projects', project) arc1 = ARC(project=project, ess_settings=dict(), input_dict=restart_path, project_directory=project_directory) arc1.execute() with open(os.path.join(project_directory, 'output', 'thermo.info'), 'r') as f: thermo_sft_ccsdtf12_bac = False for line in f.readlines(): if 'thermo_DFT_CCSDTF12_BAC' in line: thermo_sft_ccsdtf12_bac = True break self.assertTrue(thermo_sft_ccsdtf12_bac) with open(os.path.join(project_directory, 'arc_project_for_testing_delete_after_usage2.info'), 'r') as f: sts, n2h3, oet, lot, ap = False, False, False, False, False for line in f.readlines(): if 'Considered the following species and TSs:' in line: sts = True elif 'Species N2H3' in line: n2h3 = True elif 'Overall time since project initiation:' in line: oet = True elif 'Levels of theory used:' in line: lot = True elif 'ARC project arc_project_for_testing_delete_after_usage2' in line: ap = True self.assertTrue(sts) self.assertTrue(n2h3) self.assertTrue(oet) self.assertTrue(lot) self.assertTrue(ap) with open(os.path.join(project_directory, 'arc.log'), 'r') as f: aei, ver, git, spc, rtm, ldb, therm, src, ter = False, False, False, False, False, False, False, False, False for line in f.readlines(): if 'ARC execution initiated on' in line: aei = True elif '# Version:' in line: ver = True elif 'The current git HEAD for ARC is:' in line: git = True elif 'Considering species: CH3CO2_rad' in line: spc = True elif 'All jobs for species N2H3 successfully converged. Run time: 1:16:03' in line: rtm = True elif 'Loading the RMG database...' in line: ldb = True elif 'Thermodynamics for H2O2:' in line: therm = True elif 'Sources of thermoproperties determined by RMG for the parity plots:' in line: src = True elif 'ARC execution terminated on' in line: ter = True self.assertTrue(aei) self.assertTrue(ver) self.assertTrue(git) self.assertTrue(spc) self.assertTrue(rtm) self.assertTrue(ldb) self.assertTrue(therm) self.assertTrue(src) self.assertTrue(ter) self.assertTrue(os.path.isfile(os.path.join(project_directory, 'output', 'thermo_parity_plots.pdf'))) with open(os.path.join(project_directory, 'output', 'Species', 'H2O2', 'species_dictionary.txt'), 'r') as f: lines = f.readlines() adj_list = str(''.join([line for line in lines if (line and 'H2O2' not in line)])) mol1 = Molecule().fromAdjacencyList(adj_list) self.assertEqual(mol1.toSMILES(), str('OO')) thermo_library_path = os.path.join(project_directory, 'output', 'RMG libraries', 'thermo', 'arc_project_for_testing_delete_after_usage2.py') new_thermo_library_path = os.path.join(settings['database.directory'], 'thermo', 'libraries', 'arc_project_for_testing_delete_after_usage2.py') # copy the generated library to RMG-database shutil.copyfile(thermo_library_path, new_thermo_library_path) db = RMGDatabase() db.load( path=settings['database.directory'], thermoLibraries=[str('arc_project_for_testing_delete_after_usage2')], transportLibraries=[], reactionLibraries=[], seedMechanisms=[], kineticsFamilies='none', kineticsDepositories=[], statmechLibraries=None, depository=False, solvation=False, testing=True, ) spc2 = Species().fromSMILES(str('CC([O])=O')) spc2.generate_resonance_structures() spc2.thermo = db.thermo.getThermoData(spc2) self.assertAlmostEqual(spc2.getEnthalpy(298), -178003.44650359568, 1) self.assertAlmostEqual(spc2.getEntropy(298), 283.5983103176096, 1) self.assertAlmostEqual(spc2.getHeatCapacity(1000), 118.99753808225603, 1) self.assertTrue('arc_project_for_testing_delete_after_usage2' in spc2.thermo.comment) # delete the generated library from RMG-database os.remove(new_thermo_library_path)
def saveOld(self, path): """ Save an old-style reaction library to `path`. This creates files named ``species.txt``, ``reactions.txt``, and ``pdepreactions.txt`` in the given directory; these contain the species dictionary, high-pressure limit reactions and kinetics, and pressure-dependent reactions and kinetics, respectively. """ try: os.makedirs(path) except OSError: pass def writeArrhenius(f, arrhenius): f.write( ' {0:<12.3E} {1:>7.3f} {2:>11.2f} {3}{4:g} {5:g} {6:g}\n'. format( arrhenius.A.value_si, arrhenius.n.value_si, arrhenius.Ea.value_si / 4.184, '*' if arrhenius.A.isUncertaintyMultiplicative() else '', arrhenius.A.uncertainty, arrhenius.n.uncertainty, arrhenius.Ea.uncertainty / 4.184, )) # Gather all of the species used in this kinetics library speciesDict = self.getSpecies() # Also include colliders in the above for entry in self.entries.values(): if isinstance(entry.data, ThirdBody): for molecule in entry.data.efficiencies: formula = molecule.getFormula() if formula in ['He', 'Ar', 'N2', 'Ne']: pass else: found = False for species in speciesDict.values(): for mol in species.molecule: if mol.isIsomorphic(molecule): found = True break if not found: speciesDict[formula] = Species(label=formula, molecule=[molecule]) entries = self.entries.values() entries.sort(key=lambda x: x.index) # Save the species dictionary speciesList = speciesDict.values() speciesList.sort(key=lambda x: x.label) f = open(os.path.join(path, 'species.txt'), 'w') for species in speciesList: f.write(species.molecule[0].toAdjacencyList(label=species.label, removeH=False) + "\n") f.close() # Save the high-pressure limit reactions # Currently only Arrhenius kinetics are allowed f = open(os.path.join(path, 'reactions.txt'), 'w') f.write('Unit:\n') f.write('A: mol/m3/s\n') f.write('E: cal/mol\n\n') f.write('Reactions:\n') for entry in entries: kinetics = entry.data rateList = [] if isinstance(kinetics, MultiArrhenius): entry.item.duplicate = True rateList = kinetics.arrhenius[:] else: if not kinetics.isPressureDependent(): rateList.append(kinetics) for rate in rateList: # Write reaction equation f.write('{0:<59}'.format(entry.item)) # Write kinetics if isinstance(rate, Arrhenius): writeArrhenius(f, rate) else: raise DatabaseError( 'Unexpected kinetics type "{0}" encountered while saving old kinetics library (reactions.txt).' .format(rate.__class__)) # Mark as duplicate if needed if entry.item.duplicate: f.write(' DUPLICATE\n') f.close() # Save the pressure-dependent reactions # Currently only ThirdBody, Lindemann, Troe, and PDepArrhenius kinetics are allowed f = open(os.path.join(path, 'pdepreactions.txt'), 'w') f.write('Unit:\n') f.write('A: mol/m3/s\n') f.write('E: cal/mol\n\n') f.write('Reactions:\n') for entry in entries: kinetics = entry.data if not kinetics.isPressureDependent(): continue rateList = [] if isinstance(kinetics, MultiPDepArrhenius): entry.item.duplicate = True rateList = kinetics.arrhenius[:] else: rateList.append(kinetics) for rate in rateList: # Write reaction equation equation = str(entry.item) if entry.item.reversible: index = equation.find('<=>') else: index = equation.find('=>') if isinstance(rate, ThirdBody) and not isinstance(rate, Lindemann): equation = '{0}+ M {1} + M'.format(equation[0:index], equation[index:]) elif isinstance(rate, PDepArrhenius): pass else: equation = '{0}(+M) {1} (+M)'.format( equation[0:index], equation[index:]) f.write('{0:<59}'.format(equation)) # Write kinetics if isinstance(rate, (ThirdBody, Lindemann, Troe)): if isinstance(rate, Lindemann): # Lindemann (and Troe) fall-off have the High-P as default, and Low-P labeled LOW writeArrhenius(f, rate.arrheniusHigh) else: # Non-falloff ThirdBody reactions are always in the Low-P limit writeArrhenius(f, rate.arrheniusLow) if len(rate.efficiencies) > 0: eff_line = '' for molecule, efficiency in rate.efficiencies.iteritems( ): for spec in speciesDict.values(): if molecule in spec.molecule: mol_label = spec.label break else: mol_label = molecule.getFormula().upper() eff_line += '{0}/{1:g}/ '.format( mol_label, efficiency) f.write(eff_line.strip() + '\n') if isinstance(rate, Lindemann): f.write(' LOW / {0:10.3e} {1:9.3f} {2:10.2f}/\n'. format( rate.arrheniusLow.A.value_si, rate.arrheniusLow.n.value_si, rate.arrheniusLow.Ea.value_si / 4.184, )) if isinstance(rate, Troe): if rate.T2 is not None: f.write( ' TROE / {0:10.4f} {1:10.2g} {2:10.2g} {3:10.2g}/\n' .format( rate.alpha, rate.T3.value_si, rate.T1.value_si, rate.T2.value_si, )) else: f.write( ' TROE / {0:10.4f} {1:10.2g} {2:10.2g}/\n' .format( rate.alpha, rate.T3.value_si, rate.T1.value_si, )) elif isinstance(rate, PDepArrhenius): writeArrhenius(f, rate.arrhenius[-1]) for pressure, arrhenius in zip(rate.pressures.value_si, rate.arrhenius): f.write( ' PLOG / {0:10g} {1:10.3e} {2:9.3f} {3:10.2f} /\n' .format( pressure / 1e5, arrhenius.A.value_si, arrhenius.n.value_si, arrhenius.Ea.value_si / 4.184, )) else: raise DatabaseError( 'Unexpected kinetics type "{0}" encountered while saving old kinetics library (reactions.txt).' .format(rate.__class__)) # Mark as duplicate if needed if entry.item.duplicate: f.write(' DUPLICATE\n') f.write('\n') f.close()
def loadEntry( self, index, reactant1, product1, kinetics, reactant2=None, reactant3=None, product2=None, product3=None, degeneracy=1, label='', duplicate=False, reversible=True, reference=None, referenceType='', shortDesc='', longDesc='', ): reactants = [ Species(label=reactant1.strip().splitlines()[0].strip(), molecule=[Molecule().fromAdjacencyList(reactant1)]) ] if reactant2 is not None: reactants.append( Species(label=reactant2.strip().splitlines()[0].strip(), molecule=[Molecule().fromAdjacencyList(reactant2)])) if reactant3 is not None: reactants.append( Species(label=reactant3.strip().splitlines()[0].strip(), molecule=[Molecule().fromAdjacencyList(reactant3)])) products = [ Species(label=product1.strip().splitlines()[0].strip(), molecule=[Molecule().fromAdjacencyList(product1)]) ] if product2 is not None: products.append( Species(label=product2.strip().splitlines()[0].strip(), molecule=[Molecule().fromAdjacencyList(product2)])) if product3 is not None: products.append( Species(label=product3.strip().splitlines()[0].strip(), molecule=[Molecule().fromAdjacencyList(product3)])) comment = "Reaction and kinetics from {0}.".format(self.label) if shortDesc.strip(): comment += "{0!s}\n".format(shortDesc.strip()) if longDesc.strip(): comment += str(re.sub('\s*\n\s*', '\n', longDesc)) kinetics.comment = comment.strip() # Perform mass balance check on the reaction rxn = Reaction(reactants=reactants, products=products, degeneracy=degeneracy, duplicate=duplicate, reversible=reversible) if not rxn.isBalanced(): raise DatabaseError( 'Reaction {0} in kinetics library {1} was not balanced! Please reformulate.' .format(rxn, self.label)) assert index not in self.entries, "Reaction with index {0} already present!".format( index) self.entries[index] = Entry( index=index, label=label, item=rxn, data=kinetics, reference=reference, referenceType=referenceType, shortDesc=shortDesc, longDesc=longDesc.strip(), ) # Convert SMILES to Molecule objects in collision efficiencies if isinstance(kinetics, PDepKineticsModel): efficiencies = {} for smiles, eff in kinetics.efficiencies.items(): if isinstance(smiles, str): efficiencies[Molecule().fromSMILES(smiles)] = eff kinetics.efficiencies = efficiencies