Skip to content

Commit 17ff242

Browse files
authored
Merge pull request #952 from ReactionMechanismGenerator/chemkinTest
Add unit tests to chemkinTest
2 parents c8d2365 + 99634cd commit 17ff242

7 files changed

Lines changed: 228 additions & 115 deletions

File tree

environment_linux.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,4 @@ dependencies:
3737
- ffmpeg
3838
- jupyter
3939
- networkx
40+
- mock

environment_mac.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@ dependencies:
3636
- ffmpeg
3737
- jupyter
3838
- networkx
39+
- mock

environment_windows.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,4 @@ dependencies:
3535
- ffmpeg
3636
- jupyter
3737
- networkx
38+
- mock

meta.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ requirements:
6161
- lpsolve55
6262
- markupsafe
6363
- matplotlib >=1.5
64+
- mock
6465
- mopac
6566
- nose
6667
- numpy

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,5 @@ pyzmq
3636
# SCOOP for parallel computing
3737
git+https://github.com/soravux/scoop.git@0.7.0
3838

39+
# For unit tests
40+
mock

rmgpy/chemkin.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -610,14 +610,12 @@ def readReactionComments(reaction, comments, read = True):
610610
if reactant.label == reacStr:
611611
break
612612
else:
613-
import pdb; pdb.set_trace()
614613
raise ChemkinError('Unexpected species identifier {0} encountered in flux pairs for reaction {1}.'.format(reacStr, reaction))
615614
if prodStr[-1] == ';': prodStr = prodStr[:-1]
616615
for product in reaction.products:
617616
if product.label == prodStr:
618617
break
619618
else:
620-
import pdb; pdb.set_trace()
621619
raise ChemkinError('Unexpected species identifier {0} encountered in flux pairs for reaction {1}.'.format(prodStr, reaction))
622620
reaction.pairs.append((reactant, product))
623621
assert len(reaction.pairs) == max(len(reaction.reactants), len(reaction.products))

rmgpy/chemkinTest.py

Lines changed: 222 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,122 +1,231 @@
11
import unittest
2+
import mock
23
import os
34
from chemkin import *
45
import rmgpy
6+
7+
58
###################################################
69

710
class ChemkinTest(unittest.TestCase):
11+
@mock.patch('rmgpy.chemkin.logging')
12+
def test_readThermoEntry_BadElementCount(self, mock_logging):
13+
"""
14+
Test that invalid element count logs the appropriate warning.
15+
16+
This test uses the `mock` module in order to test calls to logging.
17+
The `mock.patch` decorator replaces the logging module instance in
18+
rmgpy.chemkin with a mock instance that can be accessed by this
19+
unit test. By using the mock instance, it is possible to assert that
20+
the expected logging statements are being created.
21+
"""
22+
entry = """C2H6 H XC X L 100.000 5000.000 827.28 1
23+
2.44813916E+00 1.83377834E-02-7.25714119E-06 1.35300042E-09-9.60327447E-14 2
24+
-1.19655244E+04 8.07917520E+00 3.50507145E+00-3.65219841E-03 6.32200490E-05 3
25+
-8.01049582E-08 3.19734088E-11-1.15627878E+04 6.67152939E+00 4
26+
"""
27+
with self.assertRaises(ValueError):
28+
readThermoEntry(entry)
29+
30+
mock_logging.info.assert_called_with("Trouble reading line 'C2H6 H XC X L 100.000 5000.000 827.28 1' element segment 'H X'")
31+
32+
@mock.patch('rmgpy.chemkin.logging')
33+
def test_readThermoEntry_NotGasPhase(self, mock_logging):
34+
"""
35+
Test that non gas phase data logs the appropriate warning.
36+
37+
This test uses the `mock` module in order to test calls to logging.
38+
The `mock.patch` decorator replaces the logging module instance in
39+
rmgpy.chemkin with a mock instance that can be accessed by this
40+
unit test. By using the mock instance, it is possible to assert that
41+
the expected logging statements are being created.
42+
"""
43+
entry = """C2H6 H 6C 2 L 100.000 5000.000 827.28 1
44+
2.44813916E+00 1.83377834E-02-7.25714119E-06 1.35300042E-09-9.60327447E-14 2
45+
-1.19655244E+04 8.07917520E+00 3.50507145E+00-3.65219841E-03 6.32200490E-05 3
46+
-8.01049582E-08 3.19734088E-11-1.15627878E+04 6.67152939E+00 4
47+
"""
48+
species, thermo, formula = readThermoEntry(entry)
49+
50+
mock_logging.warning.assert_called_with("Was expecting gas phase thermo data for C2H6. Skipping thermo data.")
51+
self.assertEqual(species, 'C2H6')
52+
self.assertIsNone(formula)
53+
self.assertIsNone(thermo)
54+
55+
@mock.patch('rmgpy.chemkin.logging')
56+
def test_readThermoEntry_NotFloat(self, mock_logging):
57+
"""
58+
Test that non-float parameters log the appropriate warning.
59+
60+
This test uses the `mock` module in order to test calls to logging.
61+
The `mock.patch` decorator replaces the logging module instance in
62+
rmgpy.chemkin with a mock instance that can be accessed by this
63+
unit test. By using the mock instance, it is possible to assert that
64+
the expected logging statements are being created.
65+
"""
66+
entry = """C2H6 H 6C 2 G 100.000 5000.000 827.28 1
67+
X.44813916E+00 1.83377834E-02-7.25714119E-06 1.35300042E-09-9.60327447E-14 2
68+
-1.19655244E+04 8.07917520E+00 3.50507145E+00-3.65219841E-03 6.32200490E-05 3
69+
-8.01049582E-08 3.19734088E-11-1.15627878E+04 6.67152939E+00 4
70+
"""
71+
species, thermo, formula = readThermoEntry(entry)
72+
73+
mock_logging.warning.assert_called_with("could not convert string to float: X.44813916E+00")
74+
self.assertEqual(species, 'C2H6')
75+
self.assertIsNone(formula)
76+
self.assertIsNone(thermo)
77+
78+
def test_readThermoEntry_NoTRange(self):
79+
"""Test that missing temperature range can be handled for thermo entry."""
80+
entry = """C2H6 H 6C 2 G 1
81+
2.44813916E+00 1.83377834E-02-7.25714119E-06 1.35300042E-09-9.60327447E-14 2
82+
-1.19655244E+04 8.07917520E+00 3.50507145E+00-3.65219841E-03 6.32200490E-05 3
83+
-8.01049582E-08 3.19734088E-11-1.15627878E+04 6.67152939E+00 4
84+
"""
85+
species, thermo, formula = readThermoEntry(entry, Tmin=100.0, Tint=827.28, Tmax=5000.0)
86+
87+
self.assertEqual(species, 'C2H6')
88+
self.assertEqual(formula, {'H': 6, 'C': 2})
89+
self.assertTrue(isinstance(thermo, NASA))
90+
91+
def testReadAndWriteTemplateReactionFamilyForMinimalExample(self):
92+
"""
93+
This example is mainly to test if family info can be correctly
94+
parsed from comments like '!Template reaction: R_Recombination'.
95+
"""
96+
folder = os.path.join(os.path.dirname(rmgpy.__file__), 'test_data/chemkin/chemkin_py')
97+
98+
chemkinPath = os.path.join(folder, 'minimal', 'chem.inp')
99+
dictionaryPath = os.path.join(folder, 'minimal', 'species_dictionary.txt')
100+
101+
# loadChemkinFile
102+
species, reactions = loadChemkinFile(chemkinPath, dictionaryPath)
103+
104+
reaction1 = reactions[0]
105+
self.assertEqual(reaction1.family, "R_Recombination")
106+
107+
reaction2 = reactions[1]
108+
self.assertEqual(reaction2.family, "H_Abstraction")
109+
110+
# saveChemkinFile
111+
chemkinSavePath = os.path.join(folder, 'minimal', 'chem_new.inp')
112+
dictionarySavePath = os.path.join(folder, 'minimal', 'species_dictionary_new.txt')
113+
114+
saveChemkinFile(chemkinSavePath, species, reactions, verbose=True, checkForDuplicates=True)
115+
saveSpeciesDictionary(dictionarySavePath, species, oldStyle=False)
116+
117+
self.assertTrue(os.path.isfile(chemkinSavePath))
118+
self.assertTrue(os.path.isfile(dictionarySavePath))
119+
120+
# clean up
121+
os.remove(chemkinSavePath)
122+
os.remove(dictionarySavePath)
123+
124+
def testReadAndWriteTemplateReactionFamilyForPDDExample(self):
125+
"""
126+
This example is mainly to ensure comments like
127+
'! Kinetics were estimated in this direction instead
128+
of the reverse because:' or '! This direction matched
129+
an entry in H_Abstraction, the other was just an estimate.'
130+
won't interfere reaction family info retrival.
131+
"""
132+
folder = os.path.join(os.path.dirname(rmgpy.__file__), 'test_data/chemkin/chemkin_py')
133+
134+
chemkinPath = os.path.join(folder, 'pdd', 'chem.inp')
135+
dictionaryPath = os.path.join(folder, 'pdd', 'species_dictionary.txt')
136+
137+
# loadChemkinFile
138+
species, reactions = loadChemkinFile(chemkinPath, dictionaryPath)
139+
140+
reaction1 = reactions[0]
141+
self.assertEqual(reaction1.family, "H_Abstraction")
142+
143+
reaction2 = reactions[1]
144+
self.assertEqual(reaction2.family, "H_Abstraction")
145+
146+
# saveChemkinFile
147+
chemkinSavePath = os.path.join(folder, 'minimal', 'chem_new.inp')
148+
dictionarySavePath = os.path.join(folder, 'minimal', 'species_dictionary_new.txt')
149+
150+
saveChemkinFile(chemkinSavePath, species, reactions, verbose=False, checkForDuplicates=False)
151+
saveSpeciesDictionary(dictionarySavePath, species, oldStyle=False)
152+
153+
self.assertTrue(os.path.isfile(chemkinSavePath))
154+
self.assertTrue(os.path.isfile(dictionarySavePath))
155+
156+
# clean up
157+
os.remove(chemkinSavePath)
158+
os.remove(dictionarySavePath)
159+
160+
def testTransportDataReadAndWrite(self):
161+
"""
162+
Test that we can write to chemkin and recreate the same transport object
163+
"""
164+
from rmgpy.species import Species
165+
from rmgpy.molecule import Molecule
166+
from rmgpy.transport import TransportData
167+
168+
Ar = Species(label="Ar",
169+
transportData=TransportData(shapeIndex=0, epsilon=(1134.93, 'J/mol'), sigma=(3.33, 'angstrom'),
170+
dipoleMoment=(0, 'De'), polarizability=(0, 'angstrom^3'),
171+
rotrelaxcollnum=0.0, comment="""GRI-Mech"""))
172+
173+
Ar_write = Species(label="Ar")
174+
folder = os.path.join(os.path.dirname(rmgpy.__file__), 'test_data')
175+
176+
tempTransportPath = os.path.join(folder, 'tran_temp.dat')
177+
178+
saveTransportFile(tempTransportPath, [Ar])
179+
speciesDict = {'Ar': Ar_write}
180+
loadTransportFile(tempTransportPath, speciesDict)
181+
self.assertEqual(repr(Ar), repr(Ar_write))
182+
183+
os.remove(tempTransportPath)
184+
185+
def testUseChemkinNames(self):
186+
"""
187+
Test that the official chemkin names are used as labels for the created Species objects.
188+
"""
189+
190+
folder = os.path.join(os.path.dirname(rmgpy.__file__), 'test_data/chemkin/chemkin_py')
191+
192+
chemkinPath = os.path.join(folder, 'minimal', 'chem.inp')
193+
dictionaryPath = os.path.join(folder, 'minimal', 'species_dictionary.txt')
194+
195+
# loadChemkinFile
196+
species, reactions = loadChemkinFile(chemkinPath, dictionaryPath, useChemkinNames=True)
8197

9-
def testReadTemplateReactionFamilyForMinimalExample(self):
10-
"""
11-
This example is mainly to test if family info can be correctly
12-
parsed from comments like '!Template reaction: R_Recombination'.
13-
"""
14-
folder = os.path.join(os.path.dirname(rmgpy.__file__),'test_data/chemkin/chemkin_py')
15-
16-
chemkinPath = os.path.join(folder, 'minimal', 'chem.inp')
17-
dictionaryPath = os.path.join(folder,'minimal', 'species_dictionary.txt')
18-
19-
# loadChemkinFile
20-
species, reactions = loadChemkinFile(chemkinPath, dictionaryPath)
21-
22-
reaction1 = reactions[0]
23-
self.assertEqual(reaction1.family, "R_Recombination")
24-
25-
reaction2 = reactions[1]
26-
self.assertEqual(reaction2.family, "H_Abstraction")
27-
28-
def testReadTemplateReactionFamilyForPDDExample(self):
29-
"""
30-
This example is mainly to ensure comments like
31-
'! Kinetics were estimated in this direction instead
32-
of the reverse because:' or '! This direction matched
33-
an entry in H_Abstraction, the other was just an estimate.'
34-
won't interfere reaction family info retrival.
35-
"""
36-
folder = os.path.join(os.path.dirname(rmgpy.__file__),'test_data/chemkin/chemkin_py')
37-
38-
chemkinPath = os.path.join(folder, 'pdd', 'chem.inp')
39-
dictionaryPath = os.path.join(folder,'pdd', 'species_dictionary.txt')
40-
41-
# loadChemkinFile
42-
species, reactions = loadChemkinFile(chemkinPath, dictionaryPath)
43-
44-
reaction1 = reactions[0]
45-
self.assertEqual(reaction1.family, "H_Abstraction")
46-
47-
reaction2 = reactions[1]
48-
self.assertEqual(reaction2.family, "H_Abstraction")
49-
50-
def testTransportDataReadAndWrite(self):
51-
"""
52-
Test that we can write to chemkin and recreate the same transport object
53-
"""
54-
from rmgpy.species import Species
55-
from rmgpy.molecule import Molecule
56-
from rmgpy.transport import TransportData
57-
58-
Ar = Species(label="Ar", transportData=TransportData(shapeIndex=0, epsilon=(1134.93,'J/mol'), sigma=(3.33,'angstrom'), dipoleMoment=(0,'De'), polarizability=(0,'angstrom^3'), rotrelaxcollnum=0.0, comment="""GRI-Mech"""))
59-
60-
Ar_write = Species(label="Ar")
61-
folder = os.path.join(os.path.dirname(rmgpy.__file__),'test_data')
62-
63-
tempTransportPath = os.path.join(folder, 'tran_temp.dat')
64-
65-
saveTransportFile(tempTransportPath, [Ar])
66-
speciesDict = {'Ar': Ar_write}
67-
loadTransportFile(tempTransportPath, speciesDict)
68-
self.assertEqual(repr(Ar),repr(Ar_write))
69-
70-
os.remove(tempTransportPath)
71-
72-
73-
def testUseChemkinNames(self):
74-
"""
75-
Test that the official chemkin names are used as labels for the created Species objects.
76-
"""
77-
78-
folder = os.path.join(os.path.dirname(rmgpy.__file__),'test_data/chemkin/chemkin_py')
79-
80-
chemkinPath = os.path.join(folder, 'minimal', 'chem.inp')
81-
dictionaryPath = os.path.join(folder,'minimal', 'species_dictionary.txt')
82-
83-
# loadChemkinFile
84-
species, reactions = loadChemkinFile(chemkinPath, dictionaryPath, useChemkinNames=True)
85-
86-
expected = [
87-
'Ar',
88-
'He',
89-
'Ne',
90-
'N2',
91-
'ethane',
92-
'CH3',
93-
'C2H5',
94-
'C'
95-
]
96-
97-
for spc, label in zip(species, expected):
98-
self.assertEqual(spc.label, label)
99-
100-
def testReactantN2IsReactiveAndGetsRightSpeciesIdentifier(self):
101-
"""
102-
Test that after loading chemkin files, species such as N2, which is in the default
103-
inert list of RMG, should be treated as reactive species and given right species
104-
Identifier when it's reacting in reactions.
105-
"""
106-
folder = os.path.join(os.path.dirname(rmgpy.__file__),'test_data/chemkin/chemkin_py')
107-
108-
chemkinPath = os.path.join(folder, 'NC', 'chem.inp')
109-
dictionaryPath = os.path.join(folder,'NC', 'species_dictionary.txt')
110-
111-
# loadChemkinFile
112-
species, reactions = loadChemkinFile(chemkinPath, dictionaryPath, useChemkinNames=True)
113-
114-
for n2 in species:
115-
if n2.label == 'N2':
116-
break
117-
self.assertTrue(n2.reactive)
118-
119-
self.assertEqual(getSpeciesIdentifier(n2), 'N2(35)')
120-
121-
198+
expected = [
199+
'Ar',
200+
'He',
201+
'Ne',
202+
'N2',
203+
'ethane',
204+
'CH3',
205+
'C2H5',
206+
'C'
207+
]
208+
209+
for spc, label in zip(species, expected):
210+
self.assertEqual(spc.label, label)
211+
212+
def testReactantN2IsReactiveAndGetsRightSpeciesIdentifier(self):
213+
"""
214+
Test that after loading chemkin files, species such as N2, which is in the default
215+
inert list of RMG, should be treated as reactive species and given right species
216+
Identifier when it's reacting in reactions.
217+
"""
218+
folder = os.path.join(os.path.dirname(rmgpy.__file__), 'test_data/chemkin/chemkin_py')
219+
220+
chemkinPath = os.path.join(folder, 'NC', 'chem.inp')
221+
dictionaryPath = os.path.join(folder, 'NC', 'species_dictionary.txt')
222+
223+
# loadChemkinFile
224+
species, reactions = loadChemkinFile(chemkinPath, dictionaryPath, useChemkinNames=True)
225+
226+
for n2 in species:
227+
if n2.label == 'N2':
228+
break
229+
self.assertTrue(n2.reactive)
122230

231+
self.assertEqual(getSpeciesIdentifier(n2), 'N2(35)')

0 commit comments

Comments
 (0)