def test_identical_reactants_have_similar_kinetics(self): """ tests identical reactants have the same kinetics than different reactants. this test assures that r addition multiple bond reactions from the same rate rule have the same reaction rate if the reactants are identicaal since little changes in the reactant or transition state symmetry. This method should be more robust than just checking the degeneracy of reactions. """ rxn_family_str = 'R_Addition_MultipleBond' butenyl_adj_list = """ multiplicity 2 1 C u0 p0 c0 {2,S} {3,S} {5,S} {6,S} 2 C u0 p0 c0 {1,S} {4,D} {7,S} 3 C u1 p0 c0 {1,S} {8,S} {9,S} 4 C u0 p0 c0 {2,D} {10,S} {11,S} 5 H u0 p0 c0 {1,S} 6 H u0 p0 c0 {1,S} 7 H u0 p0 c0 {2,S} 8 H u0 p0 c0 {3,S} 9 H u0 p0 c0 {3,S} 10 H u0 p0 c0 {4,S} 11 H u0 p0 c0 {4,S} """ pentenyl_adj_list = """ multiplicity 2 1 C u0 p0 c0 {2,S} {3,S} {8,S} {9,S} 2 C u0 p0 c0 {1,S} {4,S} {6,S} {7,S} 3 C u0 p0 c0 {1,S} {5,D} {10,S} 4 C u1 p0 c0 {2,S} {11,S} {12,S} 5 C u0 p0 c0 {3,D} {13,S} {14,S} 6 H u0 p0 c0 {2,S} 7 H u0 p0 c0 {2,S} 8 H u0 p0 c0 {1,S} 9 H u0 p0 c0 {1,S} 10 H u0 p0 c0 {3,S} 11 H u0 p0 c0 {4,S} 12 H u0 p0 c0 {4,S} 13 H u0 p0 c0 {5,S} 14 H u0 p0 c0 {5,S} """ family = self.database.kinetics.families[rxn_family_str] # get reaction objects and their degeneracy pp_degeneracy, pp_reactions = self.find_reaction_degeneracy( [butenyl_adj_list, butenyl_adj_list], rxn_family_str, num_independent_reactions=2) pb_degeneracy, pb_reactions = self.find_reaction_degeneracy( [butenyl_adj_list, pentenyl_adj_list], rxn_family_str, num_independent_reactions=4) # find the correct reaction from the list symmetric_product = Molecule().fromAdjacencyList(''' multiplicity 3 1 C u0 p0 c0 {2,S} {3,S} {6,S} {9,S} 2 C u0 p0 c0 {1,S} {4,S} {10,S} {11,S} 3 C u0 p0 c0 {1,S} {7,S} {12,S} {13,S} 4 C u0 p0 c0 {2,S} {5,S} {14,S} {15,S} 5 C u0 p0 c0 {4,S} {8,D} {16,S} 6 C u1 p0 c0 {1,S} {19,S} {20,S} 7 C u1 p0 c0 {3,S} {17,S} {18,S} 8 C u0 p0 c0 {5,D} {21,S} {22,S} 9 H u0 p0 c0 {1,S} 10 H u0 p0 c0 {2,S} 11 H u0 p0 c0 {2,S} 12 H u0 p0 c0 {3,S} 13 H u0 p0 c0 {3,S} 14 H u0 p0 c0 {4,S} 15 H u0 p0 c0 {4,S} 16 H u0 p0 c0 {5,S} 17 H u0 p0 c0 {7,S} 18 H u0 p0 c0 {7,S} 19 H u0 p0 c0 {6,S} 20 H u0 p0 c0 {6,S} 21 H u0 p0 c0 {8,S} 22 H u0 p0 c0 {8,S} ''') asymmetric_product = Molecule().fromAdjacencyList(''' multiplicity 3 1 C u0 p0 c0 {2,S} {3,S} {7,S} {10,S} 2 C u0 p0 c0 {1,S} {5,S} {11,S} {12,S} 3 C u0 p0 c0 {1,S} {4,S} {13,S} {14,S} 4 C u0 p0 c0 {3,S} {6,S} {17,S} {18,S} 5 C u0 p0 c0 {2,S} {8,S} {15,S} {16,S} 6 C u0 p0 c0 {4,S} {9,D} {19,S} 7 C u1 p0 c0 {1,S} {22,S} {23,S} 8 C u1 p0 c0 {5,S} {20,S} {21,S} 9 C u0 p0 c0 {6,D} {24,S} {25,S} 10 H u0 p0 c0 {1,S} 11 H u0 p0 c0 {2,S} 12 H u0 p0 c0 {2,S} 13 H u0 p0 c0 {3,S} 14 H u0 p0 c0 {3,S} 15 H u0 p0 c0 {5,S} 16 H u0 p0 c0 {5,S} 17 H u0 p0 c0 {4,S} 18 H u0 p0 c0 {4,S} 19 H u0 p0 c0 {6,S} 20 H u0 p0 c0 {8,S} 21 H u0 p0 c0 {8,S} 22 H u0 p0 c0 {7,S} 23 H u0 p0 c0 {7,S} 24 H u0 p0 c0 {9,S} 25 H u0 p0 c0 {9,S} ''') pp_reaction = next( (reaction for reaction in pp_reactions if reaction.products[0].isIsomorphic(symmetric_product)), None) pb_reaction = next( (reaction for reaction in pb_reactions if reaction.products[0].isIsomorphic(asymmetric_product)), None) pp_kinetics_list = family.getKinetics( pp_reaction, pp_reaction.template, degeneracy=pp_reaction.degeneracy, estimator='rate rules') self.assertEqual( len(pp_kinetics_list), 1, 'The propyl and propyl recombination should only return one reaction. It returned {0}. Here is the full kinetics: {1}' .format(len(pp_kinetics_list), pp_kinetics_list)) pb_kinetics_list = family.getKinetics( pb_reaction, pb_reaction.template, degeneracy=pb_reaction.degeneracy, estimator='rate rules') self.assertEqual( len(pb_kinetics_list), 1, 'The propyl and butyl recombination should only return one reaction. It returned {0}. Here is the full kinetics: {1}' .format(len(pb_kinetics_list), pb_kinetics_list)) # the same reaction group must be found or this test will not work self.assertIn( pb_kinetics_list[0][0].comment, pp_kinetics_list[0][0].comment, 'this test found different kinetics for the two groups, so it will not function as expected\n' + str(pp_kinetics_list) + str(pb_kinetics_list)) # test that the kinetics are correct self.assertAlmostEqual(pp_kinetics_list[0][0].getRateCoefficient(300), pb_kinetics_list[0][0].getRateCoefficient(300))
def test_from_smarts(self): smarts = '[CH4]' mol = from_smarts(Molecule(), smarts) self.assertTrue(mol.is_isomorphic(self.methane))
def test_read_inchikey_error(self): """Test that the correct error is raised when reading an InChIKey""" with self.assertRaises(ValueError) as cm: Molecule().from_inchi('InChIKey=UHOVQNZJYSORNB-UHFFFAOYSA-N') self.assertTrue('InChIKey is a write-only format' in str(cm.exception))
def test_axis_symmetry_number7(self): """ Test the Molecule.calculate_axis_symmetry_number() on C=C=C=[N] """ molecule = Molecule().from_smiles('C=C=C=[N]') self.assertEqual(calculate_axis_symmetry_number(molecule), 2)
def testParseThermoComments(self): """ Test that the ThermoDatabase.extractSourceFromComments function works properly on various thermo comments. """ from rmgpy.thermo import NASA, NASAPolynomial # Pure group additivity thermo propane = Species( index=3, label="Propane", thermo=NASA( polynomials=[ NASAPolynomial(coeffs=[ 3.05257, 0.0125099, 3.79386e-05, -5.12022e-08, 1.87065e-11, -14454.2, 10.0672 ], Tmin=(100, 'K'), Tmax=(986.57, 'K')), NASAPolynomial(coeffs=[ 5.91316, 0.0218763, -8.17661e-06, 1.49855e-09, -1.05991e-13, -16038.9, -8.86555 ], Tmin=(986.57, 'K'), Tmax=(5000, 'K')) ], Tmin=(100, 'K'), Tmax=(5000, 'K'), comment= """Thermo group additivity estimation: group(Cs-CsCsHH) + gauche(Cs(CsCsRR)) + other(R) + group(Cs-CsHHH) + gauche(Cs(Cs(CsRR)RRR)) + other(R) + group(Cs-CsHHH) + gauche(Cs(Cs(CsRR)RRR)) + other(R)""" ), molecule=[Molecule(SMILES="CCC")]) source = self.database.extractSourceFromComments(propane) self.assertTrue( 'GAV' in source, 'Should have found that propane thermo source is GAV.') self.assertEqual(len(source['GAV']['group']), 2) self.assertEqual(len(source['GAV']['other']), 1) self.assertEqual(len(source['GAV']['gauche']), 2) # Pure library thermo dipk = Species(index=1, label="DIPK", thermo=NASA(polynomials=[ NASAPolynomial(coeffs=[ 3.35002, 0.017618, -2.46235e-05, 1.7684e-08, -4.87962e-12, 35555.7, 5.75335 ], Tmin=(100, 'K'), Tmax=(888.28, 'K')), NASAPolynomial(coeffs=[ 6.36001, 0.00406378, -1.73509e-06, 5.05949e-10, -4.49975e-14, 35021, -8.41155 ], Tmin=(888.28, 'K'), Tmax=(5000, 'K')) ], Tmin=(100, 'K'), Tmax=(5000, 'K'), comment="""Thermo library: DIPK"""), molecule=[Molecule(SMILES="CC(C)C(=O)C(C)C")]) source = self.database.extractSourceFromComments(dipk) self.assertTrue('Library' in source) # Mixed library and HBI thermo dipk_rad = Species( index=4, label="R_tert", thermo=NASA(polynomials=[ NASAPolynomial(coeffs=[ 2.90061, 0.0298018, -7.06268e-05, 6.9636e-08, -2.42414e-11, 54431, 5.44492 ], Tmin=(100, 'K'), Tmax=(882.19, 'K')), NASAPolynomial(coeffs=[ 6.70999, 0.000201027, 6.65617e-07, -7.99543e-11, 4.08721e-15, 54238.6, -9.73662 ], Tmin=(882.19, 'K'), Tmax=(5000, 'K')) ], Tmin=(100, 'K'), Tmax=(5000, 'K'), comment="""Thermo library: DIPK + radical(C2CJCHO)"""), molecule=[ Molecule(SMILES="C[C](C)C(=O)C(C)C"), Molecule(SMILES="CC(C)=C([O])C(C)C") ]) source = self.database.extractSourceFromComments(dipk_rad) self.assertTrue('Library' in source) self.assertTrue('GAV' in source) self.assertEqual(len(source['GAV']['radical']), 1) # Pure QM thermo cineole = Species( index=6, label="Cineole", thermo=NASA(polynomials=[ NASAPolynomial(coeffs=[ -0.324129, 0.0619667, 9.71008e-05, -1.60598e-07, 6.28285e-11, -38699.9, 29.3686 ], Tmin=(100, 'K'), Tmax=(985.52, 'K')), NASAPolynomial(coeffs=[ 20.6043, 0.0586913, -2.22152e-05, 4.19949e-09, -3.06046e-13, -46791, -91.4152 ], Tmin=(985.52, 'K'), Tmax=(5000, 'K')) ], Tmin=(100, 'K'), Tmax=(5000, 'K'), comment="""QM MopacMolPM3 calculation attempt 1"""), molecule=[Molecule(SMILES="CC12CCC(CC1)C(C)(C)O2")]) source = self.database.extractSourceFromComments(cineole) self.assertTrue('QM' in source) # Mixed QM and HBI thermo cineole_rad = Species( index=7, label="CineoleRad", thermo=NASA( polynomials=[ NASAPolynomial(coeffs=[ -0.2897, 0.0627717, 8.63299e-05, -1.47868e-07, 5.81665e-11, -14017.6, 31.0266 ], Tmin=(100, 'K'), Tmax=(988.76, 'K')), NASAPolynomial(coeffs=[ 20.4836, 0.0562555, -2.13903e-05, 4.05725e-09, -2.96023e-13, -21915, -88.1205 ], Tmin=(988.76, 'K'), Tmax=(5000, 'K')) ], Tmin=(100, 'K'), Tmax=(5000, 'K'), comment= """QM MopacMolPM3 calculation attempt 1 + radical(Cs_P)"""), molecule=[Molecule(SMILES="[CH2]C12CCC(CC1)C(C)(C)O2")]) source = self.database.extractSourceFromComments(cineole_rad) self.assertTrue('QM' in source) self.assertTrue('GAV' in source) self.assertEqual(len(source['GAV']['radical']), 1) # No thermo comments other = Species( index=7, label="CineoleRad", thermo=NASA( polynomials=[ NASAPolynomial(coeffs=[ -0.2897, 0.0627717, 8.63299e-05, -1.47868e-07, 5.81665e-11, -14017.6, 31.0266 ], Tmin=(100, 'K'), Tmax=(988.76, 'K')), NASAPolynomial(coeffs=[ 20.4836, 0.0562555, -2.13903e-05, 4.05725e-09, -2.96023e-13, -21915, -88.1205 ], Tmin=(988.76, 'K'), Tmax=(5000, 'K')) ], Tmin=(100, 'K'), Tmax=(5000, 'K'), ), molecule=[Molecule(SMILES="[CH2]C12CCC(CC1)C(C)(C)O2")]) # Function should complain if there's no thermo comments self.assertRaises(self.database.extractSourceFromComments(cineole_rad)) # Check a dummy species that has plus and minus thermo group contributions polycyclic = Species( index=7, label="dummy", thermo=NASA( polynomials=[ NASAPolynomial(coeffs=[ -0.2897, 0.0627717, 8.63299e-05, -1.47868e-07, 5.81665e-11, -14017.6, 31.0266 ], Tmin=(100, 'K'), Tmax=(988.76, 'K')), NASAPolynomial(coeffs=[ 20.4836, 0.0562555, -2.13903e-05, 4.05725e-09, -2.96023e-13, -21915, -88.1205 ], Tmin=(988.76, 'K'), Tmax=(5000, 'K')) ], Tmin=(100, 'K'), Tmax=(5000, 'K'), comment= """Thermo group additivity estimation: group(Cs-CsCsHH) + group(Cs-CsCsHH) - ring(Benzene)""" ), molecule=[Molecule(SMILES="[CH2]C12CCC(CC1)C(C)(C)O2")]) source = self.database.extractSourceFromComments(polycyclic) self.assertTrue('GAV' in source) self.assertEqual(source['GAV']['ring'][0][1], -1) # the weight of benzene contribution should be -1 self.assertEqual( source['GAV']['group'][0][1], 2) # weight of the group(Cs-CsCsHH) conbtribution should be 2
def test_axis_symmetry_number_butatrienyl(self): """ Test the Molecule.calculate_axis_symmetry_number() on C=C=C=[CH] """ molecule = Molecule().from_smiles('C=C=C=[CH]') self.assertEqual(calculate_axis_symmetry_number(molecule), 1)
def test_axis_symmetry_number1(self): """ Test the Molecule.calculate_axis_symmetry_number() on CC(C)=C=C(CC)CC """ molecule = Molecule().from_smiles('CC(C)=C=C(CC)CC') self.assertEqual(calculate_axis_symmetry_number(molecule), 2)
def test_from_smiles(self): smiles = 'C' mol = from_smiles(Molecule(), smiles) self.assertTrue(mol.is_isomorphic(self.methane)) # Test that atomtypes that rely on lone pairs for identity are typed correctly smiles = 'CN' mol = from_smiles(Molecule(), smiles) self.assertEquals(mol.atoms[1].atomtype, ATOMTYPES['N3s']) # Test N2 adjlist = ''' 1 N u0 p1 c0 {2,T} 2 N u0 p1 c0 {1,T} ''' smiles = 'N#N' self.compare(adjlist, smiles) # Test CH4 adjlist = ''' 1 C u0 p0 c0 {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} ''' smiles = 'C' self.compare(adjlist, smiles) # Test H2O adjlist = ''' 1 O u0 p2 c0 {2,S} {3,S} 2 H u0 p0 c0 {1,S} 3 H u0 p0 c0 {1,S} ''' smiles = 'O' self.compare(adjlist, smiles) # Test C2H6 adjlist = ''' 1 C u0 p0 c0 {2,S} {3,S} {4,S} {5,S} 2 C u0 p0 c0 {1,S} {6,S} {7,S} {8,S} 3 H u0 p0 c0 {1,S} 4 H u0 p0 c0 {1,S} 5 H u0 p0 c0 {1,S} 6 H u0 p0 c0 {2,S} 7 H u0 p0 c0 {2,S} 8 H u0 p0 c0 {2,S} ''' smiles = 'CC' self.compare(adjlist, smiles) # Test H2 adjlist = ''' 1 H u0 p0 c0 {2,S} 2 H u0 p0 c0 {1,S} ''' smiles = '[H][H]' self.compare(adjlist, smiles) # Test H2O2 adjlist = ''' 1 O u0 p2 c0 {2,S} {3,S} 2 O u0 p2 c0 {1,S} {4,S} 3 H u0 p0 c0 {1,S} 4 H u0 p0 c0 {2,S} ''' smiles = 'OO' self.compare(adjlist, smiles) # Test C3H8 adjlist = ''' 1 C u0 p0 c0 {2,S} {4,S} {5,S} {6,S} 2 C u0 p0 c0 {1,S} {3,S} {7,S} {8,S} 3 C u0 p0 c0 {2,S} {9,S} {10,S} {11,S} 4 H u0 p0 c0 {1,S} 5 H u0 p0 c0 {1,S} 6 H u0 p0 c0 {1,S} 7 H u0 p0 c0 {2,S} 8 H u0 p0 c0 {2,S} 9 H u0 p0 c0 {3,S} 10 H u0 p0 c0 {3,S} 11 H u0 p0 c0 {3,S} ''' smiles = 'CCC' self.compare(adjlist, smiles) # Test Ar adjlist = ''' 1 Ar u0 p4 c0 ''' smiles = '[Ar]' self.compare(adjlist, smiles) # Test He adjlist = ''' 1 He u0 p1 c0 ''' smiles = '[He]' self.compare(adjlist, smiles) # Test CH4O adjlist = ''' 1 C u0 p0 c0 {2,S} {3,S} {4,S} {5,S} 2 O u0 p2 c0 {1,S} {6,S} 3 H u0 p0 c0 {1,S} 4 H u0 p0 c0 {1,S} 5 H u0 p0 c0 {1,S} 6 H u0 p0 c0 {2,S} ''' smiles = 'CO' self.compare(adjlist, smiles) # Test CO2 adjlist = ''' 1 O u0 p2 c0 {2,D} 2 C u0 p0 c0 {1,D} {3,D} 3 O u0 p2 c0 {2,D} ''' smiles = 'O=C=O' self.compare(adjlist, smiles) # Test CO adjlist = ''' 1 C u0 p1 c-1 {2,T} 2 O u0 p1 c+1 {1,T} ''' smiles = '[C-]#[O+]' self.compare(adjlist, smiles) # Test C2H4 adjlist = ''' 1 C u0 p0 c0 {2,D} {3,S} {4,S} 2 C u0 p0 c0 {1,D} {5,S} {6,S} 3 H u0 p0 c0 {1,S} 4 H u0 p0 c0 {1,S} 5 H u0 p0 c0 {2,S} 6 H u0 p0 c0 {2,S} ''' smiles = 'C=C' self.compare(adjlist, smiles) # Test O2 adjlist = ''' 1 O u0 p2 c0 {2,D} 2 O u0 p2 c0 {1,D} ''' smiles = 'O=O' self.compare(adjlist, smiles) # Test CH3 adjlist = ''' multiplicity 2 1 C u1 p0 c0 {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} ''' smiles = '[CH3]' self.compare(adjlist, smiles) # Test HO adjlist = ''' multiplicity 2 1 O u1 p2 c0 {2,S} 2 H u0 p0 c0 {1,S} ''' smiles = '[OH]' self.compare(adjlist, smiles) # Test C2H5 adjlist = ''' multiplicity 2 1 C u0 p0 c0 {2,S} {5,S} {6,S} {7,S} 2 C u1 p0 c0 {1,S} {3,S} {4,S} 3 H u0 p0 c0 {2,S} 4 H u0 p0 c0 {2,S} 5 H u0 p0 c0 {1,S} 6 H u0 p0 c0 {1,S} 7 H u0 p0 c0 {1,S} ''' smiles = 'C[CH2]' self.compare(adjlist, smiles) # Test O adjlist = ''' multiplicity 3 1 O u2 p2 c0 ''' smiles = '[O]' self.compare(adjlist, smiles) # Test HO2 adjlist = ''' multiplicity 2 1 O u1 p2 c0 {2,S} 2 O u0 p2 c0 {1,S} {3,S} 3 H u0 p0 c0 {2,S} ''' smiles = '[O]O' self.compare(adjlist, smiles) # Test CH, methylidyne. # Wikipedia reports: # The ground state is a doublet radical with one unpaired electron, # and the first two excited states are a quartet radical with three # unpaired electrons and a doublet radical with one unpaired electron. # With the quartet radical only 71 kJ above the ground state, a sample # of methylidyne exists as a mixture of electronic states even at # room temperature, giving rise to complex reactions. # adjlist = ''' multiplicity 2 1 C u1 p1 c0 {2,S} 2 H u0 p0 c0 {1,S} ''' smiles = '[CH]' self.compare(adjlist, smiles) # Test H adjlist = ''' multiplicity 2 1 H u1 p0 c0 ''' smiles = '[H]' self.compare(adjlist, smiles) # Test atomic C, which is triplet in ground state adjlist = ''' multiplicity 3 1 C u2 p1 c0 ''' smiles = '[C]' self.compare(adjlist, smiles) # Test O2 adjlist = ''' multiplicity 3 1 O u1 p2 c0 {2,S} 2 O u1 p2 c0 {1,S} ''' smiles = '[O][O]' self.compare(adjlist, smiles)
def testAxisSymmetryNumberButatrienyl(self): """ Test the Molecule.calculateAxisSymmetryNumber() on C=C=C=[CH] """ molecule = Molecule().fromSMILES('C=C=C=[CH]') self.assertEqual(calculateAxisSymmetryNumber(molecule), 1)
def test_empty_molecule(self): """Test that we can safely return a blank identifier for an empty molecule.""" mol = Molecule() self.assertEqual(mol.to_smiles(), '') self.assertEqual(mol.to_inchi(), '')
def test_aromatics(self): """Test that different aromatics representations returns different SMILES.""" mol1 = Molecule().from_adjacency_list(""" 1 O u0 p2 c0 {6,S} {9,S} 2 C u0 p0 c0 {3,D} {5,S} {11,S} 3 C u0 p0 c0 {2,D} {4,S} {12,S} 4 C u0 p0 c0 {3,S} {6,D} {13,S} 5 C u0 p0 c0 {2,S} {7,D} {10,S} 6 C u0 p0 c0 {1,S} {4,D} {7,S} 7 C u0 p0 c0 {5,D} {6,S} {8,S} 8 C u0 p0 c0 {7,S} {14,S} {15,S} {16,S} 9 H u0 p0 c0 {1,S} 10 H u0 p0 c0 {5,S} 11 H u0 p0 c0 {2,S} 12 H u0 p0 c0 {3,S} 13 H u0 p0 c0 {4,S} 14 H u0 p0 c0 {8,S} 15 H u0 p0 c0 {8,S} 16 H u0 p0 c0 {8,S} """) mol2 = Molecule().from_adjacency_list(""" 1 O u0 p2 c0 {6,S} {9,S} 2 C u0 p0 c0 {3,S} {5,D} {11,S} 3 C u0 p0 c0 {2,S} {4,D} {12,S} 4 C u0 p0 c0 {3,D} {6,S} {13,S} 5 C u0 p0 c0 {2,D} {7,S} {10,S} 6 C u0 p0 c0 {1,S} {4,S} {7,D} 7 C u0 p0 c0 {5,S} {6,D} {8,S} 8 C u0 p0 c0 {7,S} {14,S} {15,S} {16,S} 9 H u0 p0 c0 {1,S} 10 H u0 p0 c0 {5,S} 11 H u0 p0 c0 {2,S} 12 H u0 p0 c0 {3,S} 13 H u0 p0 c0 {4,S} 14 H u0 p0 c0 {8,S} 15 H u0 p0 c0 {8,S} 16 H u0 p0 c0 {8,S} """) mol3 = Molecule().from_adjacency_list(""" 1 O u0 p2 c0 {6,S} {9,S} 2 C u0 p0 c0 {3,B} {5,B} {11,S} 3 C u0 p0 c0 {2,B} {4,B} {12,S} 4 C u0 p0 c0 {3,B} {6,B} {13,S} 5 C u0 p0 c0 {2,B} {7,B} {10,S} 6 C u0 p0 c0 {1,S} {4,B} {7,B} 7 C u0 p0 c0 {5,B} {6,B} {8,S} 8 C u0 p0 c0 {7,S} {14,S} {15,S} {16,S} 9 H u0 p0 c0 {1,S} 10 H u0 p0 c0 {5,S} 11 H u0 p0 c0 {2,S} 12 H u0 p0 c0 {3,S} 13 H u0 p0 c0 {4,S} 14 H u0 p0 c0 {8,S} 15 H u0 p0 c0 {8,S} 16 H u0 p0 c0 {8,S} """) smiles1 = mol1.to_smiles() smiles2 = mol2.to_smiles() smiles3 = mol3.to_smiles() self.assertNotEqual(smiles1, smiles2) self.assertNotEqual(smiles2, smiles3) self.assertNotEqual(smiles1, smiles3)
def compare(self, adjlist, smiles): mol = Molecule().from_adjacency_list(adjlist) self.assertEquals(smiles, mol.to_smiles())
def verify_output_file(self): """ Check's that an output file exists and was successful. Returns a boolean flag that states whether a successful GAUSSIAN simulation already exists for the molecule with the given (augmented) InChI Key. The definition of finding a successful simulation is based on these criteria: 1) finding an output file with the file name equal to the InChI Key 2) NOT finding any of the keywords that are denote a calculation failure 3) finding all the keywords that denote a calculation success. 4) finding a match between the InChI of the given molecule and the InchI found in the calculation files 5) checking that the optimized geometry, when connected by single bonds, is isomorphic with self.molecule (converted to single bonds) If any of the above criteria is not matched, False will be returned. If all are satisfied, it will return True. """ if not os.path.exists(self.output_file_path): logging.info("Output file {0} does not exist.".format(self.output_file_path)) return False inchi_match = False # flag (1 or 0) indicating whether the InChI in the file matches InChIaug this can only be 1 if inchi_found is also 1 inchi_found = False # flag (1 or 0) indicating whether an InChI was found in the log file # Initialize dictionary with "False"s success_keys_found = dict([(key, False) for key in self.successKeys]) with open(self.output_file_path) as outputFile: for line in outputFile: line = line.strip() for element in self.failureKeys: # search for failure keywords if element in line: logging.error("Gaussian output file contains the following error: {0}".format(element)) return False for element in self.successKeys: # search for success keywords if element in line: success_keys_found[element] = True if line.startswith("InChI="): log_file_inchi = line # output files should take up to 240 characters of the name in the input file inchi_found = True if self.unique_id_long in log_file_inchi: inchi_match = True elif self.unique_id_long.startswith(log_file_inchi): logging.info("InChI too long to check, but beginning matches so assuming OK.") inchi_match = True else: logging.warning("InChI in log file ({0}) didn't match that in geometry " "({1}).".format(log_file_inchi, self.geometry.unique_id_long)) if self.geometry.unique_id_long.startswith(log_file_inchi): logging.warning("but the beginning matches so it's probably just a truncation problem.") inchi_match = True # Check that ALL 'success' keywords were found in the file. if not all(success_keys_found.values()): logging.error('Not all of the required keywords for success were found in the output file!') return False if not inchi_found: logging.error("No InChI was found in the Gaussian output file {0}".format(self.output_file_path)) return False if not inchi_match: # InChIs do not match (most likely due to limited name length mirrored in log file (240 characters), but possibly due to a collision) return self.checkForInChiKeyCollision(log_file_inchi) # Not yet implemented! # Compare the optimized geometry to the original molecule qm_data = self.parse() cclib_mol = Molecule() cclib_mol.from_xyz(qm_data.atomicNumbers, qm_data.atomCoords.value) test_mol = self.molecule.to_single_bonds() if not cclib_mol.is_isomorphic(test_mol): logging.info("Incorrect connectivity for optimized geometry in file {0}".format(self.output_file_path)) return False logging.info("Successful {1} quantum result in {0}".format(self.output_file_path, self.__class__.__name__)) return True
#image = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data', 'hough_test', 'Test_Set_1', 'PNGs', # 'C(C)C(CCCC)(C)C.png') #image = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data', 'hough_test', 'Test_Set_1', 'PNGs', # 'C(C)(CC)(CC)CCCCC.png') #image = os.path.join(os.path.dirname(os.path.dirname(__file__)),'data','hough_test','short test set','C(C)(CC)(CC)CCCCC.png') #image = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data', 'hough_test','square.jpg' ) #image = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data', 'hough_test', 'Test_Set_1','double_bonds', 'C((CCC)CC)C.png') image = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data', 'hand_drawn', 'CC=CC(CC)C.png') bname = os.path.basename(os.path.normpath(image)) smiles = bname.split('.')[0] print smiles m = Molecule().fromSMILES(str(smiles)) image = imread(image, as_grey=True) thresh = threshold_otsu(image) image = image > thresh image = np.invert(image) plt.imshow(image, cmap=plt.gray()) lines = get_hough_lines(image) plotLines(lines) # min_dist_merge = params[0] # min_angle_merge = params[1] # min_width_merge = params[2] # split_tol = params[3] # min_dist_bond = params[4] # max_dist_bond = params[5]
def setUp(self): self.vf2 = VF2() self.mol = Molecule().from_smiles("CC(=O)C[CH2]") self.mol2 = self.mol.copy(deep=True)
def testAxisSymmetryNumber12Hexadienyl(self): """ Test the Molecule.calculateAxisSymmetryNumber() on C=C=CCCC """ molecule = Molecule().fromSMILES('C=C=CCCC') self.assertEqual(calculateAxisSymmetryNumber(molecule), 1)
def test_molecules_from_xyz(self): """Tests that atom orders are preserved when converting xyz's into RMG Molecules""" s_mol, b_mol = converter.molecules_from_xyz(self.xyz4) # check that the atom order is the same self.assertTrue(s_mol.atoms[0].isSulfur()) self.assertTrue(b_mol.atoms[0].isSulfur()) self.assertTrue(s_mol.atoms[1].isOxygen()) self.assertTrue(b_mol.atoms[1].isOxygen()) self.assertTrue(s_mol.atoms[2].isOxygen()) self.assertTrue(b_mol.atoms[2].isOxygen()) self.assertTrue(s_mol.atoms[3].isNitrogen()) self.assertTrue(b_mol.atoms[3].isNitrogen()) self.assertTrue(s_mol.atoms[4].isCarbon()) self.assertTrue(b_mol.atoms[4].isCarbon()) self.assertTrue(s_mol.atoms[5].isHydrogen()) self.assertTrue(b_mol.atoms[5].isHydrogen()) self.assertTrue(s_mol.atoms[6].isHydrogen()) self.assertTrue(b_mol.atoms[6].isHydrogen()) self.assertTrue(s_mol.atoms[7].isHydrogen()) self.assertTrue(b_mol.atoms[7].isHydrogen()) self.assertTrue(s_mol.atoms[8].isHydrogen()) self.assertTrue(b_mol.atoms[8].isHydrogen()) self.assertTrue(s_mol.atoms[9].isHydrogen()) self.assertTrue(b_mol.atoms[9].isHydrogen()) s_mol, b_mol = converter.molecules_from_xyz(self.xyz5) self.assertTrue(s_mol.atoms[0].isOxygen()) self.assertTrue(b_mol.atoms[0].isOxygen()) self.assertTrue(s_mol.atoms[2].isCarbon()) self.assertTrue(b_mol.atoms[2].isCarbon()) expected_bonded_adjlist = """multiplicity 2 1 O u0 p2 c0 {6,S} {10,S} 2 O u0 p2 c0 {3,S} {28,S} 3 C u0 p0 c0 {2,S} {8,S} {14,S} {15,S} 4 C u0 p0 c0 {7,S} {16,S} {17,S} {18,S} 5 C u0 p0 c0 {7,S} {19,S} {20,S} {21,S} 6 C u0 p0 c0 {1,S} {22,S} {23,S} {24,S} 7 C u1 p0 c0 {4,S} {5,S} {9,S} 8 C u0 p0 c0 {3,S} {10,D} {11,S} 9 C u0 p0 c0 {7,S} {11,D} {12,S} 10 C u0 p0 c0 {1,S} {8,D} {13,S} 11 C u0 p0 c0 {8,S} {9,D} {25,S} 12 C u0 p0 c0 {9,S} {13,D} {26,S} 13 C u0 p0 c0 {10,S} {12,D} {27,S} 14 H u0 p0 c0 {3,S} 15 H u0 p0 c0 {3,S} 16 H u0 p0 c0 {4,S} 17 H u0 p0 c0 {4,S} 18 H u0 p0 c0 {4,S} 19 H u0 p0 c0 {5,S} 20 H u0 p0 c0 {5,S} 21 H u0 p0 c0 {5,S} 22 H u0 p0 c0 {6,S} 23 H u0 p0 c0 {6,S} 24 H u0 p0 c0 {6,S} 25 H u0 p0 c0 {11,S} 26 H u0 p0 c0 {12,S} 27 H u0 p0 c0 {13,S} 28 H u0 p0 c0 {2,S} """ expected_mol = Molecule().fromAdjacencyList( str(expected_bonded_adjlist)) self.assertEqual(b_mol.toAdjacencyList(), expected_bonded_adjlist) # the isIsomorphic test must come after the adjlist test since it changes the atom order self.assertTrue(b_mol.isIsomorphic(expected_mol))
def testAxisSymmetryNumber1(self): """ Test the Molecule.calculateAxisSymmetryNumber() on CC(C)=C=C(CC)CC """ molecule = Molecule().fromSMILES('CC(C)=C=C(CC)CC') self.assertEqual(calculateAxisSymmetryNumber(molecule), 2)
def test_axis_symmetry_number_12_hexadienyl(self): """ Test the Molecule.calculate_axis_symmetry_number() on C=C=CCCC """ molecule = Molecule().from_smiles('C=C=CCCC') self.assertEqual(calculate_axis_symmetry_number(molecule), 1)
def testAxisSymmetryNumber3(self): """ Test the Molecule.calculateAxisSymmetryNumber() on C=C=[C]C(C)(C)[C]=C=C """ molecule = Molecule().fromSMILES('C=C=[C]C(C)(C)[C]=C=C') self.assertEqual(calculateAxisSymmetryNumber(molecule), 4)
def test_axis_symmetry_number3(self): """ Test the Molecule.calculate_axis_symmetry_number() on C=C=[C]C(C)(C)[C]=C=C """ molecule = Molecule().from_smiles('C=C=[C]C(C)(C)[C]=C=C') self.assertEqual(calculate_axis_symmetry_number(molecule), 4)
def testAxisSymmetryNumber5(self): """ Test the Molecule.calculateAxisSymmetryNumber() on CC=C=C=O """ molecule = Molecule().fromSMILES('CC=C=C=O') self.assertEqual(calculateAxisSymmetryNumber(molecule), 1)
def test_axis_symmetry_oxygen_singlet(self): """ Test the Molecule.calculate_axis_symmetry_number() on O=O """ molecule = Molecule().from_smiles('O=O') self.assertEqual(calculate_axis_symmetry_number(molecule), 1)
def testAxisSymmetryNumber7(self): """ Test the Molecule.calculateAxisSymmetryNumber() on C=C=C=[N] """ molecule = Molecule().fromSMILES('C=C=C=[N]') self.assertEqual(calculateAxisSymmetryNumber(molecule), 2)
def saveForm(self, posted, form): """ Save form data into input.py file specified by the path. """ # Clean past history self.rmg = RMG() # Databases #self.rmg.databaseDirectory = settings['database.directory'] self.rmg.thermoLibraries = [] if posted.thermo_libraries.all(): self.rmg.thermoLibraries = [ item.thermolib.encode() for item in posted.thermo_libraries.all() ] self.rmg.reactionLibraries = [] self.rmg.seedMechanisms = [] if posted.reaction_libraries.all(): for item in posted.reaction_libraries.all(): if not item.seedmech and not item.edge: self.rmg.reactionLibraries.append( (item.reactionlib.encode(), False)) elif not item.seedmech: self.rmg.reactionLibraries.append( (item.reactionlib.encode(), True)) else: self.rmg.seedMechanisms.append(item.reactionlib.encode()) self.rmg.statmechLibraries = [] self.rmg.kineticsDepositories = 'default' self.rmg.kineticsFamilies = 'default' self.rmg.kineticsEstimator = 'rate rules' # Species self.rmg.initialSpecies = [] speciesDict = {} initialMoleFractions = {} self.rmg.reactionModel = CoreEdgeReactionModel() for item in posted.reactor_species.all(): structure = Molecule().fromAdjacencyList(item.adjlist.encode()) spec, isNew = self.rmg.reactionModel.makeNewSpecies( structure, label=item.name.encode(), reactive=False if item.inert else True) self.rmg.initialSpecies.append(spec) speciesDict[item.name.encode()] = spec initialMoleFractions[spec] = item.molefrac # Reactor systems self.rmg.reactionSystems = [] for item in posted.reactor_systems.all(): T = Quantity(item.temperature, item.temperature_units.encode()) P = Quantity(item.pressure, item.pressure_units.encode()) termination = [] if item.conversion: termination.append( TerminationConversion(speciesDict[item.species.encode()], item.conversion)) termination.append( TerminationTime( Quantity(item.terminationtime, item.time_units.encode()))) # Sensitivity Analysis sensitiveSpecies = [] if item.sensitivity: if isinstance(item.sensitivity.encode(), str): sensitivity = item.sensitivity.encode().split(',').strip() for spec in sensitivity: sensitiveSpecies.append(speciesDict[spec]) system = SimpleReactor(T, P, initialMoleFractions, termination, sensitiveSpecies, item.sensitivityThreshold) self.rmg.reactionSystems.append(system) # Simulator tolerances self.rmg.absoluteTolerance = form.cleaned_data['simulator_atol'] self.rmg.relativeTolerance = form.cleaned_data['simulator_rtol'] self.rmg.sensitivityAbsoluteTolerance = form.cleaned_data[ 'simulator_sens_atol'] self.rmg.sensitivityRelativeTolerance = form.cleaned_data[ 'simulator_sens_rtol'] self.rmg.fluxToleranceKeepInEdge = form.cleaned_data[ 'toleranceKeepInEdge'] self.rmg.fluxToleranceMoveToCore = form.cleaned_data[ 'toleranceMoveToCore'] self.rmg.fluxToleranceInterrupt = form.cleaned_data[ 'toleranceInterruptSimulation'] self.rmg.maximumEdgeSpecies = form.cleaned_data['maximumEdgeSpecies'] self.rmg.minCoreSizeForPrune = form.cleaned_data['minCoreSizeForPrune'] self.rmg.minSpeciesExistIterationsForPrune = form.cleaned_data[ 'minSpeciesExistIterationsForPrune'] self.rmg.filterReactions = form.cleaned_data['filterReactions'] # Pressure Dependence pdep = form.cleaned_data['pdep'].encode() if pdep != 'off': self.rmg.pressureDependence = PressureDependenceJob(network=None) self.rmg.pressureDependence.method = pdep # Process interpolation model if form.cleaned_data['interpolation'].encode() == 'chebyshev': self.rmg.pressureDependence.interpolationModel = ( form.cleaned_data['interpolation'].encode(), form.cleaned_data['temp_basis'], form.cleaned_data['p_basis']) else: self.rmg.pressureDependence.interpolationModel = ( form.cleaned_data['interpolation'].encode(), ) # Temperature and pressure range self.rmg.pressureDependence.Tmin = Quantity( form.cleaned_data['temp_low'], form.cleaned_data['temprange_units'].encode()) self.rmg.pressureDependence.Tmax = Quantity( form.cleaned_data['temp_high'], form.cleaned_data['temprange_units'].encode()) self.rmg.pressureDependence.Tcount = form.cleaned_data[ 'temp_interp'] self.rmg.pressureDependence.generateTemperatureList() self.rmg.pressureDependence.Pmin = Quantity( form.cleaned_data['p_low'], form.cleaned_data['prange_units'].encode()) self.rmg.pressureDependence.Pmax = Quantity( form.cleaned_data['p_high'], form.cleaned_data['prange_units'].encode()) self.rmg.pressureDependence.Pcount = form.cleaned_data['p_interp'] self.rmg.pressureDependence.generatePressureList() # Process grain size and count self.rmg.pressureDependence.grainSize = Quantity( form.cleaned_data['maximumGrainSize'], form.cleaned_data['grainsize_units'].encode()) self.rmg.pressureDependence.grainCount = form.cleaned_data[ 'minimumNumberOfGrains'] self.rmg.pressureDependence.maximumAtoms = form.cleaned_data[ 'maximumAtoms'] # Additional Options self.rmg.units = 'si' self.rmg.saveRestartPeriod = Quantity( form.cleaned_data['saveRestartPeriod'], form.cleaned_data['saveRestartPeriodUnits'].encode( )) if form.cleaned_data['saveRestartPeriod'] else None self.rmg.generateOutputHTML = form.cleaned_data['generateOutputHTML'] self.rmg.generatePlots = form.cleaned_data['generatePlots'] self.rmg.saveSimulationProfiles = form.cleaned_data[ 'saveSimulationProfiles'] self.rmg.saveEdgeSpecies = form.cleaned_data['saveEdgeSpecies'] self.rmg.verboseComments = form.cleaned_data['verboseComments'] # Species Constraints speciesConstraints = form.cleaned_data['speciesConstraints'] if speciesConstraints == 'on': allowed = [] if form.cleaned_data['allowed_inputSpecies']: allowed.append('input species') if form.cleaned_data['allowed_seedMechanisms']: allowed.append('seed mechanisms') if form.cleaned_data['allowed_reactionLibraries']: allowed.append('reaction libraries') self.rmg.speciesConstraints['allowed'] = allowed self.rmg.speciesConstraints[ 'maximumCarbonAtoms'] = form.cleaned_data['maximumCarbonAtoms'] self.rmg.speciesConstraints[ 'maximumOxygenAtoms'] = form.cleaned_data['maximumOxygenAtoms'] self.rmg.speciesConstraints[ 'maximumNitrogenAtoms'] = form.cleaned_data[ 'maximumNitrogenAtoms'] self.rmg.speciesConstraints[ 'maximumSiliconAtoms'] = form.cleaned_data[ 'maximumSiliconAtoms'] self.rmg.speciesConstraints[ 'maximumSulfurAtoms'] = form.cleaned_data['maximumSulfurAtoms'] self.rmg.speciesConstraints[ 'maximumHeavyAtoms'] = form.cleaned_data['maximumHeavyAtoms'] self.rmg.speciesConstraints[ 'maximumRadicalElectrons'] = form.cleaned_data[ 'maximumRadicalElectrons'] self.rmg.speciesConstraints['allowSingletO2'] = form.cleaned_data[ 'allowSingletO2'] # Quantum Calculations quantumCalc = form.cleaned_data['quantumCalc'] if quantumCalc == 'on': from rmgpy.qm.main import QMCalculator self.rmg.quantumMechanics = QMCalculator( software=form.cleaned_data['software'].encode(), method=form.cleaned_data['method'].encode(), fileStore=form.cleaned_data['fileStore'].encode(), scratchDirectory=form.cleaned_data['scratchDirectory'].encode( ), onlyCyclics=form.cleaned_data['onlyCyclics'], maxRadicalNumber=form.cleaned_data['maxRadicalNumber'], ) # Save the input.py file self.rmg.saveInput(self.savepath)
def testAxisSymmetryOxygenSinglet(self): """ Test the Molecule.calculateAxisSymmetryNumber() on O=O """ molecule = Molecule().fromSMILES('O=O') self.assertEqual(calculateAxisSymmetryNumber(molecule), 1)
def test_incorrect_identifier_type(self): """Test that the appropriate error is raised for identifier/type mismatch.""" with self.assertRaises(ValueError) as cm: Molecule().from_smiles('InChI=1S/C6H6/c1-2-4-6-5-3-1/h1-6H') self.assertTrue('Improper identifier type' in str(cm.exception))
def get_resonance_hybrid(self): """ Returns a molecule object with bond orders that are the average of all the resonance structures. """ # get labeled resonance isomers self.generate_resonance_structures(keep_isomorphic=True) # only consider reactive molecules as representative structures molecules = [mol for mol in self.molecule if mol.reactive] # return if no resonance if len(molecules) == 1: return molecules[0] # create a sorted list of atom objects for each resonance structure cython.declare( atomsFromStructures=list, oldAtoms=list, newAtoms=list, numResonanceStructures=cython.short, structureNum=cython.short, oldBondOrder=cython.float, index1=cython.short, index2=cython.short, newMol=Molecule, oldMol=Molecule, atom1=Atom, atom2=Atom, bond=Bond, atoms=list, ) atoms_from_structures = [] for new_mol in molecules: new_mol.atoms.sort(key=lambda atom: atom.id) atoms_from_structures.append(new_mol.atoms) num_resonance_structures = len(molecules) # make original structure with no bonds new_mol = Molecule() original_atoms = atoms_from_structures[0] for atom1 in original_atoms: atom = new_mol.add_atom(Atom(atom1.element)) atom.id = atom1.id new_atoms = new_mol.atoms # initialize bonds to zero order for index1, atom1 in enumerate(original_atoms): for atom2 in atom1.bonds: index2 = original_atoms.index(atom2) bond = Bond(new_atoms[index1], new_atoms[index2], 0) new_mol.add_bond(bond) # set bonds to the proper value for structureNum, oldMol in enumerate(molecules): old_atoms = atoms_from_structures[structureNum] for index1, atom1 in enumerate(old_atoms): # make bond orders average of resonance structures for atom2 in atom1.bonds: index2 = old_atoms.index(atom2) new_bond = new_mol.get_bond(new_atoms[index1], new_atoms[index2]) old_bond_order = oldMol.get_bond( old_atoms[index1], old_atoms[index2]).get_order_num() new_bond.apply_action( ('CHANGE_BOND', None, old_bond_order / num_resonance_structures / 2)) # set radicals in resonance hybrid to maximum of all structures if atom1.radical_electrons > 0: new_atoms[index1].radical_electrons = max( atom1.radical_electrons, new_atoms[index1].radical_electrons) new_mol.update_atomtypes(log_species=False, raise_exception=False) return new_mol
def test_isotopic_molecule_1(self): """Test that we can parse an InChI for an isotopic molecule.""" mol = Molecule().from_inchi('InChI=1S/CH4/h1H4/i1+1') self.assertTrue(len(mol.atoms), 4) self.assertEqual([atom.element.isotope for atom in mol.atoms].count(13), 1)
def testaddReverseAttribute(self): """ tests that the addReverseAttribute method gets the reverse degeneracy correct """ from rmgpy.data.rmg import getDB from rmgpy.data.kinetics.family import TemplateReaction adjlist = [ ''' multiplicity 2 1 H u0 p0 c0 {7,S} 2 H u0 p0 c0 {4,S} 3 C u1 p0 c0 {5,S} {7,S} {8,S} 4 C u0 p0 c0 {2,S} {6,S} {7,D} 5 H u0 p0 c0 {3,S} 6 H u0 p0 c0 {4,S} 7 C u0 p0 c0 {1,S} {3,S} {4,D} 8 H u0 p0 c0 {3,S} ''', ''' 1 C u0 p0 c0 {2,S} {4,S} {5,S} {6,S} 2 C u0 p0 c0 i13 {1,S} {3,D} {7,S} 3 C u0 p0 c0 {2,D} {8,S} {9,S} 4 H u0 p0 c0 {1,S} 5 H u0 p0 c0 {1,S} 6 H u0 p0 c0 {1,S} 7 H u0 p0 c0 {2,S} 8 H u0 p0 c0 {3,S} 9 H u0 p0 c0 {3,S} ''', ''' multiplicity 2 1 H u0 p0 c0 {7,S} 2 H u0 p0 c0 {4,S} 3 C u1 p0 c0 {5,S} {7,S} {8,S} 4 C u0 p0 c0 {2,S} {6,S} {7,D} 5 H u0 p0 c0 {3,S} 6 H u0 p0 c0 {4,S} 7 C u0 p0 c0 i13 {1,S} {3,S} {4,D} 8 H u0 p0 c0 {3,S} ''', ''' 1 C u0 p0 c0 {2,S} {4,S} {5,S} {6,S} 2 C u0 p0 c0 {1,S} {3,D} {7,S} 3 C u0 p0 c0 {2,D} {8,S} {9,S} 4 H u0 p0 c0 {1,S} 5 H u0 p0 c0 {1,S} 6 H u0 p0 c0 {1,S} 7 H u0 p0 c0 {2,S} 8 H u0 p0 c0 {3,S} 9 H u0 p0 c0 {3,S} ''' ] family = getDB('kinetics').families['H_Abstraction'] r1 = Species(molecule=[Molecule().fromAdjacencyList(adjlist[0])]) r2 = Species(molecule=[Molecule().fromAdjacencyList(adjlist[1])]) p1 = Species(molecule=[Molecule().fromAdjacencyList(adjlist[2])]) p2 = Species(molecule=[Molecule().fromAdjacencyList(adjlist[3])]) r1.generateResonanceIsomers(keepIsomorphic=True) p1.generateResonanceIsomers(keepIsomorphic=True) rxn = TemplateReaction(reactants=[r1, r2], products=[p1, p2]) rxn.degeneracy = family.calculateDegeneracy(rxn) self.assertEqual(rxn.degeneracy, 6) family.addReverseAttribute(rxn) self.assertEqual(rxn.reverse.degeneracy, 6)