1import itertools 2from pathlib import Path 3import logging 4import io 5 6from . import utilities 7import cantera as ct 8from cantera import ck2cti, ck2yaml, cti2yaml, ctml2yaml 9 10 11class converterTestCommon: 12 def convert(self, inputFile, thermo=None, transport=None, 13 surface=None, output=None, extra=None, 14 quiet=True, permissive=None): 15 if output is None: 16 output = Path(inputFile).stem # strip '.inp' 17 if inputFile is not None: 18 inputFile = self.test_data_path / inputFile 19 if thermo is not None: 20 thermo = self.test_data_path / thermo 21 if transport is not None: 22 transport = self.test_data_path / transport 23 if surface is not None: 24 surface = self.test_data_path / surface 25 if extra is not None: 26 extra = self.test_data_path / extra 27 output = self.test_work_path / (output + self.ext) 28 # In Python >= 3.8, this can be replaced by the missing_ok argument 29 if output.is_file(): 30 output.unlink() 31 self._convert(inputFile, thermo=thermo, transport=transport, 32 surface=surface, output=output, extra=extra, 33 quiet=quiet, permissive=permissive) 34 return output 35 36 def checkConversion(self, refFile, testFile): 37 ref = ct.Solution(refFile) 38 gas = ct.Solution(testFile) 39 40 self.assertEqual(ref.element_names, gas.element_names) 41 self.assertEqual(ref.species_names, gas.species_names) 42 coeffs_ref = ref.reactant_stoich_coeffs3 43 coeffs_gas = gas.reactant_stoich_coeffs3 44 self.assertEqual(coeffs_gas.shape, coeffs_ref.shape) 45 self.assertTrue((coeffs_gas == coeffs_ref).all()) 46 47 compositionA = [[ref.n_atoms(i,j) for j in range(ref.n_elements)] 48 for i in range(ref.n_species)] 49 compositionB = [[gas.n_atoms(i,j) for j in range(gas.n_elements)] 50 for i in range(gas.n_species)] 51 self.assertEqual(compositionA, compositionB) 52 53 return ref, gas 54 55 def checkThermo(self, ref, gas, temperatures): 56 for T in temperatures: 57 ref.TP = T, ct.one_atm 58 gas.TP = T, ct.one_atm 59 ref_cp = ref.standard_cp_R 60 gas_cp = gas.standard_cp_R 61 ref_h = ref.standard_enthalpies_RT 62 gas_h = gas.standard_enthalpies_RT 63 ref_s = ref.standard_entropies_R 64 gas_s = gas.standard_entropies_R 65 for i in range(gas.n_species): 66 message = ' for species {0} at T = {1}'.format(i, T) 67 self.assertNear(ref_cp[i], gas_cp[i], 1e-7, msg='cp'+message) 68 self.assertNear(ref_h[i], gas_h[i], 1e-7, msg='h'+message) 69 self.assertNear(ref_s[i], gas_s[i], 1e-7, msg='s'+message) 70 71 def checkKinetics(self, ref, gas, temperatures, pressures, tol=1e-8): 72 for T,P in itertools.product(temperatures, pressures): 73 ref.TP = T, P 74 gas.TP = T, P 75 ref_kf = ref.forward_rate_constants 76 ref_kr = ref.reverse_rate_constants 77 gas_kf = gas.forward_rate_constants 78 gas_kr = gas.reverse_rate_constants 79 for i in range(gas.n_reactions): 80 message = ' for reaction {0} at T = {1}, P = {2}'.format(i, T, P) 81 self.assertNear(ref_kf[i], gas_kf[i], rtol=tol, msg='kf' + message) 82 self.assertNear(ref_kr[i], gas_kr[i], rtol=tol, msg='kr' + message) 83 84 @utilities.slow_test 85 def test_gri30(self): 86 output = self.convert('gri30.inp', thermo='gri30_thermo.dat', 87 transport='gri30_tran.dat', output='gri30_test') 88 89 ref, gas = self.checkConversion('gri30.xml', output) 90 self.checkKinetics(ref, gas, [300, 1500], [5e3, 1e5, 2e6]) 91 92 def test_soot(self): 93 output = self.convert('soot.inp', thermo='soot-therm.dat', output='soot_test') 94 ref, gas = self.checkConversion('soot.xml', output) 95 self.checkThermo(ref, gas, [300, 1100]) 96 self.checkKinetics(ref, gas, [300, 1100], [5e3, 1e5, 2e6]) 97 98 def test_pdep(self): 99 output = self.convert('pdep-test.inp') 100 ref, gas = self.checkConversion('pdep-test.yaml', output) 101 # Chebyshev coefficients in XML are truncated to 6 digits, limiting accuracy 102 self.checkKinetics(ref, gas, [300, 800, 1450, 2800], [5e3, 1e5, 2e6], 103 tol=2e-4) 104 105 def test_species_only(self): 106 self.convert(None, thermo='dummy-thermo.dat', output='dummy-thermo') 107 if self.ext == ".cti": 108 cti = "ideal_gas(elements='C H', species='dummy-thermo:R1A R1B P1')" 109 gas = ct.Solution(source=cti) 110 elif self.ext == ".yaml": 111 yaml = ("{phases: [{name: gas, species: " 112 "[{dummy-thermo.yaml/species: [R1A, R1B, P1]}], " 113 "thermo: ideal-gas}]}") 114 gas = ct.Solution(yaml=yaml) 115 self.assertEqual(gas.n_species, 3) 116 self.assertEqual(gas.n_reactions, 0) 117 118 def test_missingThermo(self): 119 with self.assertRaisesRegex(self.InputError, 'No thermo data'): 120 self.convert('h2o2_missingThermo.inp') 121 122 def test_duplicate_thermo(self): 123 with self.assertRaisesRegex(self.InputError, 'additional thermo'): 124 self.convert('duplicate-thermo.inp') 125 126 output = self.convert('duplicate-thermo.inp', permissive=True) 127 128 gas = ct.Solution(output) 129 self.assertEqual(gas.n_species, 3) 130 self.assertEqual(gas.n_reactions, 2) 131 132 def test_duplicate_species(self): 133 with self.assertRaisesRegex(self.InputError, 'additional declaration'): 134 self.convert('duplicate-species.inp') 135 136 output = self.convert('duplicate-species.inp', permissive=True) 137 138 gas = ct.Solution(output) 139 self.assertEqual(gas.species_names, ['foo','bar','baz']) 140 141 def test_pathologicalSpeciesNames(self): 142 output = self.convert('species-names.inp') 143 gas = ct.Solution(output) 144 145 self.assertEqual(gas.n_species, 9) 146 self.assertEqual(gas.species_name(0), '(Parens)') 147 self.assertEqual(gas.species_name(1), '@#$%^-2') 148 self.assertEqual(gas.species_index('co:lons:'), 2) 149 self.assertEqual(gas.species_name(3), '[xy2]*{.}') 150 self.assertEqual(gas.species_name(4), 'plus+') 151 self.assertEqual(gas.species_name(5), 'eq=uals') 152 self.assertEqual(gas.species_name(6), 'plus') 153 self.assertEqual(gas.species_name(7), 'trans_butene') 154 self.assertEqual(gas.species_name(8), 'co') 155 156 self.assertEqual(gas.n_reactions, 12) 157 nu = gas.product_stoich_coeffs3 - gas.reactant_stoich_coeffs3 158 self.assertEqual(list(nu[:,0]), [-1, -1, 0, 2, 0, 0, 0, 0, 0]) 159 self.assertEqual(list(nu[:,1]), [-2, 3, 0, -1, 0, 0, 0, 0, 0]) 160 self.assertEqual(list(nu[:,2]), [-1, 0, 0, 0, 1, 0, 0, 0, 0]) 161 self.assertEqual(list(nu[:,3]), [3, 0, 0, 0, -2, -1, 0, 0, 0]) 162 self.assertEqual(list(nu[:,4]), [2, 0, 0, 0, -1, 0, -1, 0, 0]) 163 self.assertEqual(list(nu[:,5]), [1, 0, 0, 0, 1, -1, -1, 0, 0]) 164 self.assertEqual(list(nu[:,6]), [2, 0, -1, 0, 0, -1, 0, 0, 0]) 165 self.assertEqual(list(nu[:,7]), [0, 0, 0, 0, -1, 1, 0, 0, 0]) 166 self.assertEqual(list(nu[:,8]), [0, 0, 0, 0, -1, 1, 0, 0, 0]) 167 self.assertEqual(list(nu[:,9]), [0, 0, 0, 0, -1, 1, 0, 0, 0]) 168 self.assertEqual(list(nu[:,10]), [0, 0, -1, 0, 2, 0, 0, -1, 0]) 169 self.assertEqual(list(nu[:,11]), [0, 0, -1, 0, 2, 0, 0, 0, -1]) 170 171 def test_unterminatedSections(self): 172 with self.assertRaisesRegex(self.InputError, 'implicitly ended'): 173 self.convert('unterminated-sections.inp') 174 175 output = self.convert('unterminated-sections.inp', permissive=True) 176 gas = ct.Solution(output) 177 self.assertEqual(gas.n_species, 3) 178 self.assertEqual(gas.n_reactions, 2) 179 180 def test_unterminatedSections2(self): 181 with self.assertRaisesRegex(self.InputError, 'implicitly ended'): 182 self.convert('unterminated-sections2.inp') 183 184 output = self.convert('unterminated-sections2.inp', permissive=True) 185 gas = ct.Solution(output) 186 self.assertEqual(gas.n_species, 3) 187 self.assertEqual(gas.n_reactions, 2) 188 189 def test_unrecognized_section(self): 190 with self.assertRaisesRegex(self.InputError, 'SPAM'): 191 self.convert('unrecognized-section.inp', thermo='dummy-thermo.dat', 192 permissive=True) 193 194 def test_nasa9(self): 195 output = self.convert('nasa9-test.inp', thermo='nasa9-test-therm.dat') 196 ref, gas = self.checkConversion('nasa9-test.xml', output) 197 self.checkThermo(ref, gas, [300, 500, 1200, 5000]) 198 199 def test_nasa9_subset(self): 200 output = self.convert('nasa9-test-subset.inp', thermo='nasa9-test-therm.dat') 201 ref, gas = self.checkConversion('nasa9-test-subset.xml', output) 202 self.checkThermo(ref, gas, [300, 500, 1200, 5000]) 203 204 def test_sri_falloff(self): 205 output = self.convert('sri-falloff.inp', thermo='dummy-thermo.dat') 206 ref, gas = self.checkConversion('sri-falloff.xml', output) 207 self.checkKinetics(ref, gas, [300, 800, 1450, 2800], [5e3, 1e5, 2e6]) 208 209 def test_chemically_activated(self): 210 output = self.convert('chemically-activated-reaction.inp') 211 ref, gas = self.checkConversion('chemically-activated-reaction.xml', 212 output) 213 # pre-exponential factor in XML is truncated to 7 sig figs, limiting accuracy 214 self.checkKinetics(ref, gas, [300, 800, 1450, 2800], [5e3, 1e5, 2e6, 1e7], 215 tol=1e-7) 216 217 def test_explicit_third_bodies(self): 218 output = self.convert('explicit-third-bodies.inp', thermo='dummy-thermo.dat') 219 ref, gas = self.checkConversion('explicit-third-bodies.xml', output) 220 self.checkKinetics(ref, gas, [300, 800, 1450, 2800], [5e3, 1e5, 2e6]) 221 222 def test_explicit_reverse_rate(self): 223 output = self.convert('explicit-reverse-rate.inp', thermo='dummy-thermo.dat') 224 ref, gas = self.checkConversion('explicit-reverse-rate.xml', output) 225 self.checkKinetics(ref, gas, [300, 800, 1450, 2800], [5e3, 1e5, 2e6]) 226 227 # Reactions with explicit reverse rate constants are transformed into 228 # two irreversible reactions with reactants and products swapped, unless 229 # the explicit reverse rate is zero so only the forward reaction is used. 230 Rr = gas.reverse_rate_constants 231 self.assertEqual(Rr[0], 0.0) 232 self.assertEqual(Rr[1], 0.0) 233 self.assertEqual(Rr[2], 0.0) 234 self.assertEqual(Rr[3], 0.0) 235 self.assertEqual(Rr[4], 0.0) 236 Rstoich = gas.reactant_stoich_coeffs3 237 Pstoich = gas.product_stoich_coeffs3 238 self.assertEqual(list(Rstoich[:, 0]), list(Pstoich[:, 1])) 239 self.assertEqual(list(Rstoich[:, 1]), list(Pstoich[:, 0])) 240 self.assertEqual(list(Rstoich[:, 2]), list(Pstoich[:, 3])) 241 self.assertEqual(list(Rstoich[:, 3]), list(Pstoich[:, 2])) 242 243 self.assertEqual(gas.n_reactions, 5) 244 245 def test_explicit_forward_order(self): 246 output = self.convert('explicit-forward-order.inp', thermo='dummy-thermo.dat') 247 ref, gas = self.checkConversion('explicit-forward-order.xml', output) 248 # pre-exponential factor in XML is truncated to 7 sig figs, limiting accuracy 249 self.checkKinetics(ref, gas, [300, 800, 1450, 2800], [5e3, 1e5, 2e6], 250 tol=2e-7) 251 252 def test_negative_order(self): 253 with self.assertRaisesRegex(self.InputError, 'Negative reaction order'): 254 self.convert('negative-order.inp', thermo='dummy-thermo.dat') 255 256 def test_negative_order_permissive(self): 257 output = self.convert('negative-order.inp', thermo='dummy-thermo.dat', 258 permissive=True) 259 ref, gas = self.checkConversion('negative-order.cti', output) 260 # pre-exponential factor in XML is truncated to 7 sig figs, limiting accuracy 261 self.checkKinetics(ref, gas, [300, 800, 1450, 2800], [5e3, 1e5, 2e6], 262 tol=2e-7) 263 264 def test_negative_A_factor(self): 265 output = self.convert('negative-rate.inp', thermo='dummy-thermo.dat') 266 gas = ct.Solution(output) # Validate the mechanism 267 self.assertLess(gas.reaction(4).rate.pre_exponential_factor, 0) 268 self.assertLess(gas.reaction(1).rate.pre_exponential_factor, 0) 269 self.assertLess(gas.reaction(2).rate.pre_exponential_factor, 0) 270 self.assertLess(gas.forward_rate_constants[5], 0) 271 272 def test_bad_troe_value(self): 273 with self.assertRaises(ValueError): 274 self.convert('bad-troe.inp', thermo='dummy-thermo.dat') 275 276 def test_invalid_reaction_equation(self): 277 with self.assertRaisesRegex(self.InputError, 'Unparsable'): 278 self.convert('invalid-equation.inp', thermo='dummy-thermo.dat') 279 280 @utilities.slow_test 281 def test_reaction_units(self): 282 out_def = self.convert('units-default.inp', thermo='dummy-thermo.dat') 283 out_cus = self.convert('units-custom.inp', thermo='dummy-thermo.dat') 284 default, custom = self.checkConversion(out_def, out_cus) 285 self.checkKinetics(default, custom, 286 [300, 800, 1450, 2800], [5e0, 5e3, 1e5, 2e6, 1e8], 1e-7) 287 288 def test_float_stoich_coeffs(self): 289 output = self.convert('float-stoich.inp', thermo='dummy-thermo.dat') 290 gas = ct.Solution(output) 291 292 R = gas.reactant_stoich_coeffs3 293 P = gas.product_stoich_coeffs3 294 self.assertArrayNear(R[:,0], [0, 1.5, 0.5, 0]) 295 self.assertArrayNear(P[:,0], [1, 0, 0, 1]) 296 self.assertArrayNear(R[:,1], [1, 0, 0, 1]) 297 self.assertArrayNear(P[:,1], [0, 0.33, 1.67, 0]) 298 299 def test_photon(self): 300 output = self.convert('photo-reaction.inp', thermo='dummy-thermo.dat', 301 permissive=True) 302 303 ref, gas = self.checkConversion('photo-reaction.xml', output) 304 self.checkKinetics(ref, gas, [300, 800, 1450, 2800], [5e3, 1e5, 2e6]) 305 306 def test_transport_normal(self): 307 output = self.convert('h2o2.inp', transport='gri30_tran.dat', 308 output='h2o2_transport_normal') 309 310 gas = ct.Solution(output) 311 gas.TPX = 300, 101325, 'H2:1.0, O2:1.0' 312 self.assertAlmostEqual(gas.thermal_conductivity, 0.07663, 4) 313 314 def test_transport_embedded(self): 315 output = self.convert('with-transport.inp') 316 gas = ct.Solution(output) 317 gas.X = [0.2, 0.3, 0.5] 318 D = gas.mix_diff_coeffs 319 for d in D: 320 self.assertTrue(d > 0.0) 321 322 def test_transport_missing_species(self): 323 with self.assertRaisesRegex(self.InputError, 'No transport data'): 324 self.convert('h2o2.inp', transport='h2o2-missing-species-tran.dat', 325 output='h2o2_transport_missing_species') 326 327 def test_transport_extra_column_entries(self): 328 with self.assertRaisesRegex(self.InputError, '572.400'): 329 self.convert('h2o2.inp', 330 transport='h2o2-extra-column-entries-tran.dat', 331 output='h2o2_extra-column-entries-tran') 332 333 def test_transport_duplicate_species(self): 334 with self.assertRaisesRegex(self.InputError, 'duplicate transport'): 335 self.convert('h2o2.inp', 336 transport='h2o2-duplicate-species-tran.dat', 337 output='h2o2_transport_duplicate_species.cti') 338 339 self.convert('h2o2.inp', 340 transport='h2o2-duplicate-species-tran.dat', 341 output='h2o2_transport_duplicate_species', permissive=True) 342 343 def test_transport_bad_geometry(self): 344 with self.assertRaisesRegex(self.InputError, 'geometry flag'): 345 self.convert('h2o2.inp', 346 transport='h2o2-bad-geometry-tran.dat', 347 output='h2o2_transport_bad_geometry') 348 349 def test_transport_float_geometry(self): 350 with self.assertRaisesRegex(self.InputError, 'geometry flag'): 351 self.convert('h2o2.inp', 352 transport='h2o2-float-geometry-tran.dat', 353 output='h2o2_transport_float_geometry') 354 355 def test_empty_reaction_section(self): 356 output = self.convert('h2o2_emptyReactions.inp') 357 gas = ct.Solution(output) 358 self.assertEqual(gas.n_species, 9) 359 self.assertEqual(gas.n_reactions, 0) 360 361 def test_reaction_comments1(self): 362 output = self.convert('pdep-test.inp') 363 text = output.read_text() 364 self.assertIn('Generic mechanism header', text) 365 self.assertIn('Single PLOG reaction', text) 366 self.assertIn('Multiple PLOG expressions at the same pressure', text) 367 368 def test_reaction_comments2(self): 369 output = self.convert('explicit-third-bodies.inp', thermo='dummy-thermo.dat') 370 text = output.read_text() 371 self.assertIn('An end of line comment', text) 372 self.assertIn('A comment after the last reaction', text) 373 374 def test_custom_element(self): 375 output = self.convert('custom-elements.inp') 376 gas = ct.Solution(output) 377 self.assertEqual(gas.n_elements, 4) 378 self.assertNear(gas.atomic_weight(2), 13.003) 379 self.assertEqual(gas.n_atoms('ethane', 'C'), 2) 380 self.assertEqual(gas.n_atoms('CC', 'C'), 1) 381 self.assertEqual(gas.n_atoms('CC', 'Ci'), 1) 382 383 def test_surface_mech(self): 384 output = self.convert('surface1-gas.inp', surface='surface1.inp', 385 output='surface1') 386 387 gas = ct.Solution(output, 'gas') 388 surf = ct.Interface(output, 'PT_SURFACE', [gas]) 389 390 self.assertEqual(gas.n_reactions, 11) 391 self.assertEqual(surf.n_reactions, 15) 392 self.assertEqual(surf.species('O2_Pt').size, 3) 393 394 # Different units for rate constants in each input file 395 # 62.1 kJ/gmol = 6.21e7 J/kmol 396 self.assertNear(gas.reaction(0).rate.activation_energy, 6.21e7) 397 # 67400 J/mol = 6.74e7 J/kmol 398 self.assertNear(surf.reaction(1).rate.activation_energy, 6.74e7) 399 400 # Sticking coefficients 401 self.assertFalse(surf.reaction(1).is_sticking_coefficient) 402 self.assertTrue(surf.reaction(2).is_sticking_coefficient) 403 self.assertTrue(surf.reaction(2).use_motz_wise_correction) 404 self.assertTrue(surf.reaction(4).is_sticking_coefficient) 405 self.assertFalse(surf.reaction(4).use_motz_wise_correction) 406 self.assertTrue(surf.reaction(4).duplicate) 407 self.assertTrue(surf.reaction(6).use_motz_wise_correction) 408 409 # Coverage dependencies 410 covdeps = surf.reaction(1).coverage_deps 411 self.assertEqual(len(covdeps), 2) 412 self.assertIn('H_Pt', covdeps) 413 self.assertEqual(covdeps['OH_Pt'][1], 1.0) 414 self.assertNear(covdeps['H_Pt'][2], -6e6) # 6000 J/gmol = 6e6 J/kmol 415 416 def test_surface_mech2(self): 417 output = self.convert('surface1-gas-noreac.inp', surface='surface1.inp', 418 output='surface1-nogasreac') 419 420 gas = ct.Solution(output, 'gas') 421 surf = ct.Interface(output, 'PT_SURFACE', [gas]) 422 423 self.assertEqual(gas.n_reactions, 0) 424 self.assertEqual(surf.n_reactions, 15) 425 426 # Coverage dependencies 427 covdeps = surf.reaction(1).coverage_deps 428 self.assertIn('H_Pt', covdeps) 429 self.assertEqual(covdeps['OH_Pt'][1], 1.0) 430 self.assertNear(covdeps['H_Pt'][2], -6e6) 431 432 def test_third_body_plus_falloff_reactions(self): 433 self.convert('third_body_plus_falloff_reaction.inp') 434 gas = ct.Solution('third_body_plus_falloff_reaction' + self.ext) 435 self.assertEqual(gas.n_reactions, 2) 436 437 def test_blank_line_in_header(self): 438 self.convert('blank_line_in_header.inp') 439 gas = ct.Solution('blank_line_in_header' + self.ext) 440 self.assertEqual(gas.n_reactions, 1) 441 442 443class ck2ctiTest(converterTestCommon, utilities.CanteraTest): 444 ext = '.cti' 445 InputError = ck2cti.InputParseError 446 447 def _convert(self, inputFile, *, thermo, transport, surface, output, extra, 448 quiet, permissive): 449 ck2cti.convertMech(inputFile, thermoFile=thermo, 450 transportFile=transport, surfaceFile=surface, outName=output, 451 quiet=quiet, permissive=permissive) 452 453 def test_missingElement(self): 454 with self.assertRaisesRegex(self.InputError, 'Undefined elements'): 455 self.convert('h2o2_missingElement.inp', output='h2o2_missingElement') 456 457 458class ck2yamlTest(converterTestCommon, utilities.CanteraTest): 459 ext = '.yaml' 460 InputError = ck2yaml.InputError 461 462 def _convert(self, inputFile, *, thermo, transport, surface, output, extra, 463 quiet, permissive): 464 ck2yaml.convert_mech(inputFile, thermo_file=thermo, 465 transport_file=transport, surface_file=surface, out_name=output, 466 extra_file=extra, quiet=quiet, permissive=permissive) 467 468 @utilities.slow_test 469 def test_extra(self): 470 self.convert('gri30.inp', thermo='gri30_thermo.dat', 471 transport='gri30_tran.dat', output='gri30_extra', 472 extra='extra.yaml') 473 474 output = self.test_work_path / ("gri30_extra" + self.ext) 475 yml = utilities.load_yaml(output) 476 477 desc = yml['description'].split('\n')[-1] 478 self.assertEqual(desc, 'This is an alternative description.') 479 for key in ['foo', 'bar']: 480 self.assertIn(key, yml.keys()) 481 482 def test_sri_zero(self): 483 # This test tests it can convert the SRI parameters when D or E equal to 0 484 self.convert('sri_convert_test.txt') 485 output = self.test_work_path / ("sri_convert_test" + self.ext) 486 mech = utilities.load_yaml(output) 487 D = mech['reactions'][0]['SRI']['D'] 488 E = mech['reactions'][0]['SRI']['E'] 489 self.assertEqual(D, 0) 490 self.assertEqual(E, 0) 491 492 def test_duplicate_reactions(self): 493 # Running a test this way instead of using the convertMech function 494 # tests the behavior of the ck2yaml.main function and the mechanism 495 # validation step. 496 497 # clear global handler created by logging.basicConfig() in ck2cti 498 logging.getLogger().handlers.clear() 499 500 # Replace the ck2yaml logger with our own in order to capture the output 501 log_stream = io.StringIO() 502 logger = logging.getLogger('cantera.ck2yaml') 503 original_handler = logger.handlers.pop() 504 logformatter = logging.Formatter('%(message)s') 505 handler = logging.StreamHandler(log_stream) 506 handler.setFormatter(logformatter) 507 logger.addHandler(handler) 508 509 with self.assertRaises(SystemExit): 510 ck2yaml.main([ 511 f"--input={self.test_data_path}/undeclared-duplicate-reactions.inp", 512 f"--thermo={self.test_data_path}/dummy-thermo.dat"]) 513 514 # Put the original logger back in place 515 logger.handlers.clear() 516 logger.addHandler(original_handler) 517 518 message = log_stream.getvalue() 519 for token in ('FAILED', 'lines 12 and 14', 'R1A', 'R1B'): 520 self.assertIn(token, message) 521 522 def test_error_for_big_element_number(self): 523 with self.assertRaisesRegex(self.InputError, 'Element amounts can have no more than 3 digits.'): 524 self.convert('big_element_num_err.inp') 525 526class CtmlConverterTest(utilities.CanteraTest): 527 def test_sofc(self): 528 gas_a, anode_bulk, oxide_a = ct.import_phases( 529 'sofc.cti', 530 ['gas', 'metal', 'oxide_bulk']) 531 532 self.assertNear(gas_a.P, ct.one_atm) 533 self.assertNear(anode_bulk['electron'].X, 1.0) 534 self.assertNear(oxide_a.density_mole, 17.6) 535 536 @utilities.slow_test 537 def test_diamond(self): 538 gas, solid = ct.import_phases('diamond.cti', ['gas','diamond']) 539 face = ct.Interface('diamond.cti', 'diamond_100', [gas, solid]) 540 541 self.assertNear(face.site_density, 3e-8) 542 543 def test_invalid(self): 544 try: 545 gas = ct.Solution('invalid.cti') 546 except ct.CanteraError as e: 547 err = e 548 549 self.assertIn('already contains', err.args[0]) 550 551 def test_noninteger_atomicity(self): 552 gas = ct.Solution('noninteger-atomicity.cti') 553 self.assertNear(gas.molecular_weights[gas.species_index('CnHm')], 554 10.65*gas.atomic_weight('C') + 21.8*gas.atomic_weight('H')) 555 556 def test_reaction_orders(self): 557 gas = ct.Solution('reaction-orders.cti') 558 self.assertEqual(gas.n_reactions, 1) 559 R = gas.reaction(0) 560 self.assertTrue(R.allow_nonreactant_orders) 561 self.assertNear(R.orders.get('OH'), 0.15) 562 self.assertTrue(R.allow_negative_orders) 563 self.assertNear(R.orders.get('H2'), -0.25) 564 565 def test_long_source_input(self): 566 """ 567 Here we are testing if passing a very long string will result in a 568 Solution object. This should result in a temp file creation in most OS's 569 """ 570 571 gas = ct.Solution('pdep-test.yaml') 572 573 data = (self.test_data_path / "pdep-test.cti").read_text() 574 data_size_2048kB = data + ' '*2048*1024 575 gas2 = ct.Solution(source=data_size_2048kB) 576 577 self.assertEqual(gas.n_reactions, gas2.n_reactions) 578 579 def test_short_source_input(self): 580 """ 581 Here we are testing if passing a short string will result in a Solution 582 object. This should not result in a temp file creation in most OS's 583 """ 584 585 gas = ct.Solution('pdep-test.yaml') 586 587 data = (self.test_data_path / "pdep-test.cti").read_text() 588 data_size_32kB = data + ' '*18000 589 gas2 = ct.Solution(source=data_size_32kB) 590 591 self.assertEqual(gas.n_reactions, gas2.n_reactions) 592 593 594class cti2yamlTest(utilities.CanteraTest): 595 def checkConversion(self, basename, cls=ct.Solution, ctiphases=(), 596 yamlphases=(), **kwargs): 597 ctiPhase = cls(basename + '.cti', adjacent=ctiphases, **kwargs) 598 yamlPhase = cls(basename + '.yaml', adjacent=yamlphases, **kwargs) 599 600 self.assertEqual(ctiPhase.element_names, yamlPhase.element_names) 601 self.assertEqual(ctiPhase.species_names, yamlPhase.species_names) 602 self.assertEqual(ctiPhase.n_reactions, yamlPhase.n_reactions) 603 for C, Y in zip(ctiPhase.species(), yamlPhase.species()): 604 self.assertEqual(C.composition, Y.composition) 605 606 for C, Y in zip(ctiPhase.reactions(), yamlPhase.reactions()): 607 self.assertEqual(C.__class__, Y.__class__) 608 self.assertEqual(C.reactants, Y.reactants) 609 self.assertEqual(C.products, Y.products) 610 self.assertEqual(C.duplicate, Y.duplicate) 611 612 for i, sp in zip(range(ctiPhase.n_reactions), ctiPhase.kinetics_species_names): 613 self.assertEqual(ctiPhase.reactant_stoich_coeff(sp, i), 614 yamlPhase.reactant_stoich_coeff(sp, i)) 615 616 return ctiPhase, yamlPhase 617 618 def checkThermo(self, ctiPhase, yamlPhase, temperatures, tol=1e-7): 619 for T in temperatures: 620 ctiPhase.TP = T, ct.one_atm 621 yamlPhase.TP = T, ct.one_atm 622 cp_cti = ctiPhase.partial_molar_cp 623 cp_yaml = yamlPhase.partial_molar_cp 624 h_cti = ctiPhase.partial_molar_enthalpies 625 h_yaml = yamlPhase.partial_molar_enthalpies 626 s_cti = ctiPhase.partial_molar_entropies 627 s_yaml = yamlPhase.partial_molar_entropies 628 self.assertNear(ctiPhase.density, yamlPhase.density) 629 for i in range(ctiPhase.n_species): 630 message = ' for species {0} at T = {1}'.format(i, T) 631 self.assertNear(cp_cti[i], cp_yaml[i], tol, msg='cp'+message) 632 self.assertNear(h_cti[i], h_yaml[i], tol, msg='h'+message) 633 self.assertNear(s_cti[i], s_yaml[i], tol, msg='s'+message) 634 635 def checkKinetics(self, ctiPhase, yamlPhase, temperatures, pressures, tol=1e-7): 636 for T,P in itertools.product(temperatures, pressures): 637 ctiPhase.TP = T, P 638 yamlPhase.TP = T, P 639 kf_cti = ctiPhase.forward_rate_constants 640 kr_cti = ctiPhase.reverse_rate_constants 641 kf_yaml = yamlPhase.forward_rate_constants 642 kr_yaml = yamlPhase.reverse_rate_constants 643 for i in range(yamlPhase.n_reactions): 644 message = ' for reaction {0} at T = {1}, P = {2}'.format(i, T, P) 645 self.assertNear(kf_cti[i], kf_yaml[i], rtol=tol, msg='kf '+message) 646 self.assertNear(kr_cti[i], kr_yaml[i], rtol=tol, msg='kr '+message) 647 648 def checkTransport(self, ctiPhase, yamlPhase, temperatures, 649 model='mixture-averaged'): 650 ctiPhase.transport_model = model 651 yamlPhase.transport_model = model 652 for T in temperatures: 653 ctiPhase.TP = T, ct.one_atm 654 yamlPhase.TP = T, ct.one_atm 655 self.assertNear(ctiPhase.viscosity, yamlPhase.viscosity) 656 self.assertNear(ctiPhase.thermal_conductivity, 657 yamlPhase.thermal_conductivity) 658 Dkm_cti = ctiPhase.mix_diff_coeffs 659 Dkm_yaml = yamlPhase.mix_diff_coeffs 660 for i in range(ctiPhase.n_species): 661 message = 'dkm for species {0} at T = {1}'.format(i, T) 662 self.assertNear(Dkm_cti[i], Dkm_yaml[i], msg=message) 663 664 @utilities.slow_test 665 def test_gri30(self): 666 cti2yaml.convert( 667 self.cantera_data_path / "gri30.cti", 668 self.test_work_path / "gri30.yaml", 669 ) 670 ctiPhase, yamlPhase = self.checkConversion('gri30') 671 X = {'O2': 0.3, 'H2': 0.1, 'CH4': 0.2, 'CO2': 0.4} 672 ctiPhase.X = X 673 yamlPhase.X = X 674 self.checkThermo(ctiPhase, yamlPhase, [300, 500, 1300, 2000]) 675 self.checkKinetics(ctiPhase, yamlPhase, [900, 1800], [2e5, 20e5]) 676 self.checkTransport(ctiPhase, yamlPhase, [298, 1001, 2400]) 677 678 def test_pdep(self): 679 cti2yaml.convert( 680 self.test_data_path / "pdep-test.cti", 681 self.test_work_path / "pdep-test.yaml", 682 ) 683 ctiPhase, yamlPhase = self.checkConversion('pdep-test') 684 self.checkKinetics(ctiPhase, yamlPhase, [300, 1000, 2200], 685 [100, ct.one_atm, 2e5, 2e6, 9.9e6]) 686 687 def test_ptcombust(self): 688 cti2yaml.convert( 689 self.cantera_data_path / "ptcombust.cti", 690 self.test_work_path / "ptcombust.yaml", 691 ) 692 ctiGas, yamlGas = self.checkConversion('ptcombust') 693 ctiSurf, yamlSurf = self.checkConversion('ptcombust', ct.Interface, 694 name='Pt_surf', ctiphases=[ctiGas], yamlphases=[yamlGas]) 695 696 self.checkKinetics(ctiGas, yamlGas, [500, 1200], [1e4, 3e5]) 697 self.checkThermo(ctiSurf, yamlSurf, [400, 800, 1600]) 698 self.checkKinetics(ctiSurf, yamlSurf, [500, 1200], [1e4, 3e5]) 699 700 @utilities.slow_test 701 def test_ptcombust_motzwise(self): 702 cti2yaml.convert( 703 self.test_data_path / "ptcombust-motzwise.cti", 704 self.test_work_path / "ptcombust-motzwise.yaml", 705 ) 706 ctiGas, yamlGas = self.checkConversion('ptcombust-motzwise') 707 ctiSurf, yamlSurf = self.checkConversion('ptcombust-motzwise', ct.Interface, 708 name='Pt_surf', ctiphases=[ctiGas], yamlphases=[yamlGas]) 709 710 711 self.checkKinetics(ctiGas, yamlGas, [500, 1200], [1e4, 3e5]) 712 self.checkThermo(ctiSurf, yamlSurf, [400, 800, 1600]) 713 self.checkKinetics(ctiSurf, yamlSurf, [900], [101325]) 714 715 def test_sofc(self): 716 cti2yaml.convert( 717 self.cantera_data_path / "sofc.cti", 718 self.test_work_path / "sofc.yaml", 719 ) 720 ctiGas, yamlGas = self.checkConversion('sofc') 721 ctiMetal, yamlMetal = self.checkConversion('sofc', name='metal') 722 ctiOxide, yamlOxide = self.checkConversion('sofc', name='oxide_bulk') 723 ctiMSurf, yamlMSurf = self.checkConversion('sofc', ct.Interface, 724 name='metal_surface', ctiphases=[ctiGas, ctiMetal], 725 yamlphases=[yamlGas, yamlMetal]) 726 ctiOSurf, yamlOSurf = self.checkConversion('sofc', ct.Interface, 727 name='oxide_surface', ctiphases=[ctiGas, ctiOxide], 728 yamlphases=[yamlGas, yamlOxide]) 729 cti_tpb, yaml_tpb = self.checkConversion('sofc', ct.Interface, 730 name='tpb', ctiphases=[ctiMetal, ctiMSurf, ctiOSurf], 731 yamlphases=[yamlMetal, yamlMSurf, yamlOSurf]) 732 733 self.checkThermo(ctiMSurf, yamlMSurf, [900, 1000, 1100]) 734 self.checkThermo(ctiOSurf, yamlOSurf, [900, 1000, 1100]) 735 ctiMetal.electric_potential = yamlMetal.electric_potential = 2 736 self.checkKinetics(cti_tpb, yaml_tpb, [900, 1000, 1100], [1e5]) 737 ctiMetal.electric_potential = yamlMetal.electric_potential = 4 738 self.checkKinetics(cti_tpb, yaml_tpb, [900, 1000, 1100], [1e5]) 739 740 @utilities.slow_test 741 def test_liquidvapor(self): 742 output_file = self.test_work_path / "liquidvapor.yaml" 743 cti2yaml.convert( 744 self.cantera_data_path / "liquidvapor.cti", 745 output_file, 746 ) 747 for name in ['water', 'nitrogen', 'methane', 'hydrogen', 'oxygen', 748 'hfc134a', 'carbondioxide', 'heptane']: 749 ctiPhase, yamlPhase = self.checkConversion('liquidvapor', name=name) 750 self.checkThermo(ctiPhase, yamlPhase, 751 [1.3 * ctiPhase.min_temp, 0.7 * ctiPhase.max_temp]) 752 # The output file must be removed after this test because it shadows 753 # a file distributed with Cantera that is used in other tests. 754 output_file.unlink() 755 756 def test_Redlich_Kwong_CO2(self): 757 cti2yaml.convert( 758 self.test_data_path / "co2_RK_example.cti", 759 self.test_work_path / "co2_RK_example.yaml", 760 ) 761 ctiGas, yamlGas = self.checkConversion('co2_RK_example') 762 for P in [1e5, 2e6, 1.3e7]: 763 yamlGas.TP = ctiGas.TP = 300, P 764 self.checkThermo(ctiGas, yamlGas, [300, 400, 500]) 765 766 @utilities.slow_test 767 def test_Redlich_Kwong_ndodecane(self): 768 cti2yaml.convert( 769 self.cantera_data_path / "nDodecane_Reitz.cti", 770 self.test_work_path / "nDodecane_Reitz.yaml", 771 ) 772 ctiGas, yamlGas = self.checkConversion('nDodecane_Reitz') 773 self.checkThermo(ctiGas, yamlGas, [300, 400, 500]) 774 self.checkKinetics(ctiGas, yamlGas, [300, 500, 1300], [1e5, 2e6, 1.4e7], 775 1e-6) 776 777 def test_diamond(self): 778 cti2yaml.convert( 779 self.cantera_data_path / "diamond.cti", 780 self.test_work_path / "diamond.yaml", 781 ) 782 ctiGas, yamlGas = self.checkConversion('diamond', name='gas') 783 ctiSolid, yamlSolid = self.checkConversion('diamond', name='diamond') 784 ctiSurf, yamlSurf = self.checkConversion('diamond', 785 ct.Interface, name='diamond_100', ctiphases=[ctiGas, ctiSolid], 786 yamlphases=[yamlGas, yamlSolid]) 787 self.checkThermo(ctiSolid, yamlSolid, [300, 500]) 788 self.checkThermo(ctiSurf, yamlSurf, [330, 490]) 789 self.checkKinetics(ctiSurf, yamlSurf, [400, 800], [2e5]) 790 791 def test_lithium_ion_battery(self): 792 cti2yaml.convert( 793 self.cantera_data_path / "lithium_ion_battery.cti", 794 self.test_work_path / "lithium_ion_battery.yaml", 795 ) 796 name = 'lithium_ion_battery' 797 ctiAnode, yamlAnode = self.checkConversion(name, name='anode') 798 ctiCathode, yamlCathode = self.checkConversion(name, name='cathode') 799 ctiMetal, yamlMetal = self.checkConversion(name, name='electron') 800 ctiElyt, yamlElyt = self.checkConversion(name, name='electrolyte') 801 ctiAnodeInt, yamlAnodeInt = self.checkConversion(name, 802 name='edge_anode_electrolyte', 803 ctiphases=[ctiAnode, ctiMetal, ctiElyt], 804 yamlphases=[yamlAnode, yamlMetal, yamlElyt]) 805 ctiCathodeInt, yamlCathodeInt = self.checkConversion(name, 806 name='edge_cathode_electrolyte', 807 ctiphases=[ctiCathode, ctiMetal, ctiElyt], 808 yamlphases=[yamlCathode, yamlMetal, yamlElyt]) 809 810 self.checkThermo(ctiAnode, yamlAnode, [300, 330]) 811 self.checkThermo(ctiCathode, yamlCathode, [300, 330]) 812 813 ctiAnode.X = yamlAnode.X = [0.7, 0.3] 814 self.checkThermo(ctiAnode, yamlAnode, [300, 330]) 815 ctiCathode.X = yamlCathode.X = [0.2, 0.8] 816 self.checkThermo(ctiCathode, yamlCathode, [300, 330]) 817 818 for phase in [ctiAnode, yamlAnode, ctiCathode, yamlCathode, ctiMetal, 819 yamlMetal, ctiElyt, yamlElyt, ctiAnodeInt, yamlAnodeInt, 820 ctiCathodeInt, yamlCathodeInt]: 821 phase.TP = 300, 1e5 822 ctiMetal.electric_potential = yamlMetal.electric_potential = 0 823 ctiElyt.electric_potential = yamlElyt.electric_potential = 1.9 824 self.checkKinetics(ctiAnodeInt, yamlAnodeInt, [300], [1e5]) 825 826 ctiMetal.electric_potential = yamlMetal.electric_potential = 2.2 827 ctiElyt.electric_potential = yamlElyt.electric_potential = 0 828 self.checkKinetics(ctiCathodeInt, yamlCathodeInt, [300], [1e5]) 829 830 def test_ch4_ion(self): 831 cti2yaml.convert( 832 self.test_data_path / "ch4_ion.cti", 833 self.test_work_path / "ch4_ion.yaml", 834 ) 835 ctiGas, yamlGas = self.checkConversion("ch4_ion") 836 self.checkThermo(ctiGas, yamlGas, [300, 500, 1300, 2000]) 837 self.checkKinetics(ctiGas, yamlGas, [900, 1800], [2e5, 20e5]) 838 self.checkTransport(ctiGas, yamlGas, [298, 1001, 2400]) 839 840class ctml2yamlTest(utilities.CanteraTest): 841 842 def checkConversion(self, basename, cls=ct.Solution, ctmlphases=(), 843 yamlphases=(), **kwargs): 844 ctmlPhase = cls(basename + '.xml', adjacent=ctmlphases, **kwargs) 845 yamlPhase = cls(basename + '.yaml', adjacent=yamlphases, **kwargs) 846 847 self.assertEqual(ctmlPhase.element_names, yamlPhase.element_names) 848 self.assertEqual(ctmlPhase.species_names, yamlPhase.species_names) 849 self.assertEqual(ctmlPhase.n_reactions, yamlPhase.n_reactions) 850 for C, Y in zip(ctmlPhase.species(), yamlPhase.species()): 851 self.assertEqual(C.composition, Y.composition) 852 853 for C, Y in zip(ctmlPhase.reactions(), yamlPhase.reactions()): 854 self.assertEqual(C.__class__, Y.__class__) 855 self.assertEqual(C.reactants, Y.reactants) 856 self.assertEqual(C.products, Y.products) 857 self.assertEqual(C.duplicate, Y.duplicate) 858 859 for i, sp in zip(range(ctmlPhase.n_reactions), ctmlPhase.kinetics_species_names): 860 self.assertEqual(ctmlPhase.reactant_stoich_coeff(sp, i), 861 yamlPhase.reactant_stoich_coeff(sp, i)) 862 863 return ctmlPhase, yamlPhase 864 865 def checkThermo(self, ctmlPhase, yamlPhase, temperatures, pressure=ct.one_atm, tol=1e-7): 866 for T in temperatures: 867 ctmlPhase.TP = T, pressure 868 yamlPhase.TP = T, pressure 869 cp_ctml = ctmlPhase.partial_molar_cp 870 cp_yaml = yamlPhase.partial_molar_cp 871 h_ctml = ctmlPhase.partial_molar_enthalpies 872 h_yaml = yamlPhase.partial_molar_enthalpies 873 s_ctml = ctmlPhase.partial_molar_entropies 874 s_yaml = yamlPhase.partial_molar_entropies 875 self.assertNear(ctmlPhase.density, yamlPhase.density) 876 for i in range(ctmlPhase.n_species): 877 message = ' for species {0} at T = {1}'.format(ctmlPhase.species_names[i], T) 878 self.assertNear(cp_ctml[i], cp_yaml[i], tol, msg='cp'+message) 879 self.assertNear(h_ctml[i], h_yaml[i], tol, msg='h'+message) 880 self.assertNear(s_ctml[i], s_yaml[i], tol, msg='s'+message) 881 882 def checkKinetics(self, ctmlPhase, yamlPhase, temperatures, pressures, tol=1e-7): 883 for T,P in itertools.product(temperatures, pressures): 884 ctmlPhase.TP = T, P 885 yamlPhase.TP = T, P 886 kf_ctml = ctmlPhase.forward_rate_constants 887 kr_ctml = ctmlPhase.reverse_rate_constants 888 kf_yaml = yamlPhase.forward_rate_constants 889 kr_yaml = yamlPhase.reverse_rate_constants 890 for i in range(yamlPhase.n_reactions): 891 message = ' for reaction {0} at T = {1}, P = {2}'.format(i, T, P) 892 self.assertNear(kf_ctml[i], kf_yaml[i], rtol=tol, msg='kf '+message) 893 self.assertNear(kr_ctml[i], kr_yaml[i], rtol=tol, msg='kr '+message) 894 895 def checkTransport(self, ctmlPhase, yamlPhase, temperatures, 896 model='mixture-averaged'): 897 ctmlPhase.transport_model = model 898 yamlPhase.transport_model = model 899 for T in temperatures: 900 ctmlPhase.TP = T, ct.one_atm 901 yamlPhase.TP = T, ct.one_atm 902 self.assertNear(ctmlPhase.viscosity, yamlPhase.viscosity) 903 self.assertNear(ctmlPhase.thermal_conductivity, 904 yamlPhase.thermal_conductivity) 905 Dkm_ctml = ctmlPhase.mix_diff_coeffs 906 Dkm_yaml = yamlPhase.mix_diff_coeffs 907 for i in range(ctmlPhase.n_species): 908 message = 'dkm for species {0} at T = {1}'.format(i, T) 909 self.assertNear(Dkm_ctml[i], Dkm_yaml[i], msg=message) 910 911 @utilities.slow_test 912 def test_gri30(self): 913 ctml2yaml.convert( 914 self.cantera_data_path / "gri30.xml", 915 self.test_work_path / "gri30.yaml", 916 ) 917 ctmlPhase, yamlPhase = self.checkConversion('gri30') 918 X = {'O2': 0.3, 'H2': 0.1, 'CH4': 0.2, 'CO2': 0.4} 919 ctmlPhase.X = X 920 yamlPhase.X = X 921 self.checkThermo(ctmlPhase, yamlPhase, [300, 500, 1300, 2000]) 922 self.checkKinetics(ctmlPhase, yamlPhase, [900, 1800], [2e5, 20e5]) 923 self.checkTransport(ctmlPhase, yamlPhase, [298, 1001, 2400]) 924 925 def test_pdep(self): 926 ctml2yaml.convert( 927 self.test_data_path / "pdep-test.xml", 928 self.test_work_path / "pdep-test.yaml", 929 ) 930 ctmlPhase, yamlPhase = self.checkConversion('pdep-test') 931 self.checkKinetics(ctmlPhase, yamlPhase, [300, 1000, 2200], 932 [100, ct.one_atm, 2e5, 2e6, 9.9e6]) 933 934 def test_ptcombust(self): 935 ctml2yaml.convert( 936 self.cantera_data_path / "ptcombust.xml", 937 self.test_work_path / "ptcombust.yaml", 938 ) 939 ctmlGas, yamlGas = self.checkConversion('ptcombust') 940 ctmlSurf, yamlSurf = self.checkConversion('ptcombust', ct.Interface, 941 name='Pt_surf', ctmlphases=[ctmlGas], yamlphases=[yamlGas]) 942 943 self.checkKinetics(ctmlGas, yamlGas, [500, 1200], [1e4, 3e5]) 944 self.checkThermo(ctmlSurf, yamlSurf, [400, 800, 1600]) 945 self.checkKinetics(ctmlSurf, yamlSurf, [500, 1200], [1e4, 3e5]) 946 947 def test_ptcombust_motzwise(self): 948 ctml2yaml.convert( 949 self.test_data_path / "ptcombust-motzwise.xml", 950 self.test_work_path / "ptcombust-motzwise.yaml", 951 ) 952 ctmlGas, yamlGas = self.checkConversion('ptcombust-motzwise') 953 ctmlSurf, yamlSurf = self.checkConversion('ptcombust-motzwise', ct.Interface, 954 name='Pt_surf', ctmlphases=[ctmlGas], yamlphases=[yamlGas]) 955 956 self.checkKinetics(ctmlGas, yamlGas, [500, 1200], [1e4, 3e5]) 957 self.checkThermo(ctmlSurf, yamlSurf, [400, 800, 1600]) 958 self.checkKinetics(ctmlSurf, yamlSurf, [500, 1200], [1e4, 3e5]) 959 960 def test_sofc(self): 961 ctml2yaml.convert( 962 self.cantera_data_path / "sofc.xml", 963 self.test_work_path / "sofc.yaml", 964 ) 965 ctmlGas, yamlGas = self.checkConversion('sofc') 966 ctmlMetal, yamlMetal = self.checkConversion('sofc', name='metal') 967 ctmlOxide, yamlOxide = self.checkConversion('sofc', name='oxide_bulk') 968 ctmlMSurf, yamlMSurf = self.checkConversion('sofc', ct.Interface, 969 name='metal_surface', ctmlphases=[ctmlGas, ctmlMetal], 970 yamlphases=[yamlGas, yamlMetal]) 971 ctmlOSurf, yamlOSurf = self.checkConversion('sofc', ct.Interface, 972 name='oxide_surface', ctmlphases=[ctmlGas, ctmlOxide], 973 yamlphases=[yamlGas, yamlOxide]) 974 ctml_tpb, yaml_tpb = self.checkConversion('sofc', ct.Interface, 975 name='tpb', ctmlphases=[ctmlMetal, ctmlMSurf, ctmlOSurf], 976 yamlphases=[yamlMetal, yamlMSurf, yamlOSurf]) 977 978 self.checkThermo(ctmlMSurf, yamlMSurf, [900, 1000, 1100]) 979 self.checkThermo(ctmlOSurf, yamlOSurf, [900, 1000, 1100]) 980 ctmlMetal.electric_potential = yamlMetal.electric_potential = 2 981 self.checkKinetics(ctml_tpb, yaml_tpb, [900, 1000, 1100], [1e5]) 982 ctmlMetal.electric_potential = yamlMetal.electric_potential = 4 983 self.checkKinetics(ctml_tpb, yaml_tpb, [900, 1000, 1100], [1e5]) 984 985 def test_liquidvapor(self): 986 output_file = self.test_work_path / "liquidvapor.yaml" 987 ctml2yaml.convert( 988 self.cantera_data_path / "liquidvapor.xml", output_file, 989 ) 990 for name in ['water', 'nitrogen', 'methane', 'hydrogen', 'oxygen', 991 'hfc134a', 'carbondioxide', 'heptane']: 992 ctmlPhase, yamlPhase = self.checkConversion('liquidvapor', name=name) 993 self.checkThermo(ctmlPhase, yamlPhase, 994 [1.3 * ctmlPhase.min_temp, 0.7 * ctmlPhase.max_temp]) 995 # The output file must be removed after this test because it shadows 996 # a file distributed with Cantera that is used in other tests. 997 output_file.unlink() 998 999 def test_Redlich_Kwong_CO2(self): 1000 ctml2yaml.convert( 1001 self.test_data_path / "co2_RK_example.xml", 1002 self.test_work_path / "co2_RK_example.yaml", 1003 ) 1004 ctmlGas, yamlGas = self.checkConversion('co2_RK_example') 1005 for P in [1e5, 2e6, 1.3e7]: 1006 yamlGas.TP = ctmlGas.TP = 300, P 1007 self.checkThermo(ctmlGas, yamlGas, [300, 400, 500]) 1008 1009 @utilities.slow_test 1010 def test_Redlich_Kwong_ndodecane(self): 1011 ctml2yaml.convert( 1012 self.cantera_data_path / "nDodecane_Reitz.xml", 1013 self.test_work_path / "nDodecane_Reitz.yaml", 1014 ) 1015 ctmlGas, yamlGas = self.checkConversion('nDodecane_Reitz') 1016 self.checkThermo(ctmlGas, yamlGas, [300, 400, 500]) 1017 self.checkKinetics(ctmlGas, yamlGas, [300, 500, 1300], [1e5, 2e6, 1.4e7], 1018 1e-6) 1019 1020 def test_diamond(self): 1021 ctml2yaml.convert( 1022 self.cantera_data_path / "diamond.xml", 1023 self.test_work_path / "diamond.yaml", 1024 ) 1025 ctmlGas, yamlGas = self.checkConversion('diamond', name='gas') 1026 ctmlSolid, yamlSolid = self.checkConversion('diamond', name='diamond') 1027 ctmlSurf, yamlSurf = self.checkConversion('diamond', 1028 ct.Interface, name='diamond_100', ctmlphases=[ctmlGas, ctmlSolid], 1029 yamlphases=[yamlGas, yamlSolid]) 1030 self.checkThermo(ctmlSolid, yamlSolid, [300, 500]) 1031 self.checkThermo(ctmlSurf, yamlSurf, [330, 490]) 1032 self.checkKinetics(ctmlSurf, yamlSurf, [400, 800], [2e5]) 1033 1034 def test_lithium_ion_battery(self): 1035 name = 'lithium_ion_battery' 1036 ctml2yaml.convert( 1037 self.cantera_data_path / (name + ".xml"), 1038 self.test_work_path / (name + ".yaml"), 1039 ) 1040 ctmlAnode, yamlAnode = self.checkConversion(name, name='anode') 1041 ctmlCathode, yamlCathode = self.checkConversion(name, name='cathode') 1042 ctmlMetal, yamlMetal = self.checkConversion(name, name='electron') 1043 ctmlElyt, yamlElyt = self.checkConversion(name, name='electrolyte') 1044 ctmlAnodeInt, yamlAnodeInt = self.checkConversion(name, 1045 name='edge_anode_electrolyte', 1046 ctmlphases=[ctmlAnode, ctmlMetal, ctmlElyt], 1047 yamlphases=[yamlAnode, yamlMetal, yamlElyt]) 1048 ctmlCathodeInt, yamlCathodeInt = self.checkConversion(name, 1049 name='edge_cathode_electrolyte', 1050 ctmlphases=[ctmlCathode, ctmlMetal, ctmlElyt], 1051 yamlphases=[yamlCathode, yamlMetal, yamlElyt]) 1052 1053 self.checkThermo(ctmlAnode, yamlAnode, [300, 330]) 1054 self.checkThermo(ctmlCathode, yamlCathode, [300, 330]) 1055 1056 ctmlAnode.X = yamlAnode.X = [0.7, 0.3] 1057 self.checkThermo(ctmlAnode, yamlAnode, [300, 330]) 1058 ctmlCathode.X = yamlCathode.X = [0.2, 0.8] 1059 self.checkThermo(ctmlCathode, yamlCathode, [300, 330]) 1060 1061 for phase in [ctmlAnode, yamlAnode, ctmlCathode, yamlCathode, ctmlMetal, 1062 yamlMetal, ctmlElyt, yamlElyt, ctmlAnodeInt, yamlAnodeInt, 1063 ctmlCathodeInt, yamlCathodeInt]: 1064 phase.TP = 300, 1e5 1065 ctmlMetal.electric_potential = yamlMetal.electric_potential = 0 1066 ctmlElyt.electric_potential = yamlElyt.electric_potential = 1.9 1067 self.checkKinetics(ctmlAnodeInt, yamlAnodeInt, [300], [1e5]) 1068 1069 ctmlMetal.electric_potential = yamlMetal.electric_potential = 2.2 1070 ctmlElyt.electric_potential = yamlElyt.electric_potential = 0 1071 self.checkKinetics(ctmlCathodeInt, yamlCathodeInt, [300], [1e5]) 1072 1073 def test_noxNeg(self): 1074 ctml2yaml.convert( 1075 self.test_data_path / "noxNeg.xml", 1076 self.test_work_path / "noxNeg.yaml", 1077 ) 1078 ctmlGas, yamlGas = self.checkConversion('noxNeg') 1079 self.checkThermo(ctmlGas, yamlGas, [300, 1000]) 1080 self.checkKinetics(ctmlGas, yamlGas, [300, 1000], [1e5]) 1081 1082 def test_ch4_ion(self): 1083 ctml2yaml.convert( 1084 self.test_data_path / "ch4_ion.xml", 1085 self.test_work_path / "ch4_ion.yaml", 1086 ) 1087 ctmlGas, yamlGas = self.checkConversion("ch4_ion") 1088 self.checkThermo(ctmlGas, yamlGas, [300, 500, 1300, 2000]) 1089 self.checkKinetics(ctmlGas, yamlGas, [900, 1800], [2e5, 20e5]) 1090 self.checkTransport(ctmlGas, yamlGas, [298, 1001, 2400]) 1091 1092 def test_nasa9(self): 1093 ctml2yaml.convert( 1094 self.test_data_path / "nasa9-test.xml", 1095 self.test_work_path / "nasa9-test.yaml", 1096 ) 1097 ctmlGas, yamlGas = self.checkConversion("nasa9-test") 1098 self.checkThermo(ctmlGas, yamlGas, [300, 500, 1300, 2000]) 1099 1100 def test_chemically_activated(self): 1101 ctml2yaml.convert( 1102 self.test_data_path / "chemically-activated-reaction.xml", 1103 self.test_work_path / "chemically-activated-reaction.yaml", 1104 ) 1105 ctmlGas, yamlGas = self.checkConversion("chemically-activated-reaction") 1106 self.checkThermo(ctmlGas, yamlGas, [300, 500, 1300, 2000]) 1107 self.checkKinetics(ctmlGas, yamlGas, [900, 1800], [2e5, 20e5]) 1108 1109 def test_explicit_forward_order(self): 1110 ctml2yaml.convert( 1111 self.test_data_path / "explicit-forward-order.xml", 1112 self.test_work_path / "explicit-forward-order.yaml", 1113 ) 1114 ctmlGas, yamlGas = self.checkConversion("explicit-forward-order") 1115 self.checkThermo(ctmlGas, yamlGas, [300, 500, 1300, 2000]) 1116 self.checkKinetics(ctmlGas, yamlGas, [900, 1800], [2e5, 20e5]) 1117 1118 def test_explicit_reverse_rate(self): 1119 ctml2yaml.convert( 1120 self.test_data_path / "explicit-reverse-rate.xml", 1121 self.test_work_path / "explicit-reverse-rate.yaml", 1122 ) 1123 ctmlGas, yamlGas = self.checkConversion("explicit-reverse-rate") 1124 self.checkThermo(ctmlGas, yamlGas, [300, 500, 1300, 2000]) 1125 self.checkKinetics(ctmlGas, yamlGas, [900, 1800], [2e5, 20e5]) 1126 1127 def test_explicit_third_bodies(self): 1128 ctml2yaml.convert( 1129 self.test_data_path / "explicit-third-bodies.xml", 1130 self.test_work_path / "explicit-third-bodies.yaml", 1131 ) 1132 ctmlGas, yamlGas = self.checkConversion("explicit-third-bodies") 1133 self.checkThermo(ctmlGas, yamlGas, [300, 500, 1300, 2000]) 1134 self.checkKinetics(ctmlGas, yamlGas, [900, 1800], [2e5, 20e5]) 1135 1136 def test_fractional_stoich_coeffs(self): 1137 ctml2yaml.convert( 1138 self.test_data_path / "frac.xml", 1139 self.test_work_path / "frac.yaml", 1140 ) 1141 ctmlGas, yamlGas = self.checkConversion("frac") 1142 self.checkThermo(ctmlGas, yamlGas, [300, 500, 1300, 2000]) 1143 self.checkKinetics(ctmlGas, yamlGas, [900, 1800], [2e5, 20e5]) 1144 1145 def test_water_IAPWS95_thermo(self): 1146 ctml2yaml.convert( 1147 self.test_data_path / "liquid-water.xml", 1148 self.test_work_path / "liquid-water.yaml", 1149 ) 1150 ctmlWater, yamlWater = self.checkConversion("liquid-water") 1151 self.checkThermo(ctmlWater, yamlWater, [300, 500, 1300, 2000], pressure=22064000.0) 1152 self.assertEqual(ctmlWater.transport_model, yamlWater.transport_model) 1153 dens = ctmlWater.density 1154 for T in [298, 1001, 2400]: 1155 ctmlWater.TD = T, dens 1156 yamlWater.TD = T, dens 1157 self.assertNear(ctmlWater.viscosity, yamlWater.viscosity) 1158 self.assertNear(ctmlWater.thermal_conductivity, 1159 yamlWater.thermal_conductivity) 1160 1161 def test_hmw_nacl_phase(self): 1162 basename = "HMW_NaCl_sp1977_alt" 1163 xml_file = self.test_data_path / (basename + ".xml") 1164 yaml_file = self.test_data_path / (basename + ".yaml") 1165 ctml2yaml.convert(xml_file, yaml_file) 1166 1167 # Can only be loaded by ThermoPhase due to a bug in TransportFactory 1168 # ThermoPhase does not have reactions (neither does the input file) 1169 # so we can't use checkConversion 1170 ctmlPhase = ct.ThermoPhase(str(xml_file)) 1171 yamlPhase = ct.ThermoPhase(str(yaml_file)) 1172 self.assertEqual(ctmlPhase.element_names, yamlPhase.element_names) 1173 self.assertEqual(ctmlPhase.species_names, yamlPhase.species_names) 1174 self.checkThermo(ctmlPhase, yamlPhase, [300, 500]) 1175 1176 def test_NaCl_solid_phase(self): 1177 ctml2yaml.convert( 1178 self.test_data_path / "NaCl_Solid.xml", 1179 self.test_work_path / "NaCl_Solid.yaml", 1180 ) 1181 ctmlPhase, yamlPhase = self.checkConversion("NaCl_Solid") 1182 self.checkThermo(ctmlPhase, yamlPhase, [300, 500, 1300, 2000]) 1183 1184 def test_DH_NaCl_phase(self): 1185 ctml2yaml.convert( 1186 self.test_data_path / "debye-huckel-all.xml", 1187 self.test_work_path / "debye-huckel-all.yaml", 1188 ) 1189 for name in [ 1190 "debye-huckel-dilute", 1191 "debye-huckel-B-dot-ak", 1192 "debye-huckel-B-dot-a", 1193 "debye-huckel-pitzer-beta_ij", 1194 "debye-huckel-beta_ij", 1195 ]: 1196 # Can only be loaded by ThermoPhase due to a bug in TransportFactory 1197 # ThermoPhase does not have reactions (neither does the input file) 1198 # so we can't use checkConversion 1199 ctmlPhase = ct.ThermoPhase("debye-huckel-all.xml", name=name) 1200 yamlPhase = ct.ThermoPhase("debye-huckel-all.yaml", name=name) 1201 self.assertEqual(ctmlPhase.element_names, yamlPhase.element_names) 1202 self.assertEqual(ctmlPhase.species_names, yamlPhase.species_names) 1203 self.checkThermo(ctmlPhase, yamlPhase, [300, 500]) 1204 1205 def test_Maskell_solid_soln(self): 1206 ctml2yaml.convert( 1207 self.test_data_path / "MaskellSolidSolnPhase_valid.xml", 1208 self.test_work_path / "MaskellSolidSolnPhase_valid.yaml", 1209 ) 1210 1211 ctmlPhase, yamlPhase = self.checkConversion("MaskellSolidSolnPhase_valid") 1212 # Maskell phase doesn't support partial molar properties, so just check density 1213 for T in [300, 500, 1300, 2000]: 1214 ctmlPhase.TP = T, ct.one_atm 1215 yamlPhase.TP = T, ct.one_atm 1216 self.assertNear(ctmlPhase.density, yamlPhase.density) 1217 1218 def test_mock_ion(self): 1219 ctml2yaml.convert( 1220 self.test_data_path / "mock_ion.xml", 1221 self.test_work_path / "mock_ion.yaml", 1222 ) 1223 ctmlPhase = ct.ThermoPhase("mock_ion.xml") 1224 yamlPhase = ct.ThermoPhase("mock_ion.yaml") 1225 # Due to changes in how the species elements are specified, the composition 1226 # of the species differs from XML to YAML (electrons are used to specify charge 1227 # in YAML while the charge node is used in XML). Therefore, checkConversion 1228 # won't work and we have to check a few things manually. There are also no 1229 # reactions specified for these phases so don't need to do any checks for that. 1230 self.assertEqual(ctmlPhase.element_names, yamlPhase.element_names) 1231 self.assertEqual(ctmlPhase.species_names, yamlPhase.species_names) 1232 # ions-from-neutral-molecule phase doesn't support partial molar properties, 1233 # so just check density 1234 for T in [300, 500, 1300, 2000]: 1235 ctmlPhase.TP = T, ct.one_atm 1236 yamlPhase.TP = T, ct.one_atm 1237 self.assertNear(ctmlPhase.density, yamlPhase.density) 1238 1239 def test_Redlich_Kister(self): 1240 ctml2yaml.convert( 1241 self.test_data_path / "RedlichKisterVPSSTP_valid.xml", 1242 self.test_work_path / "RedlichKisterVPSSTP_valid.yaml", 1243 ) 1244 1245 ctmlPhase, yamlPhase = self.checkConversion("RedlichKisterVPSSTP_valid") 1246 self.checkThermo(ctmlPhase, yamlPhase, [300, 500]) 1247 1248 def test_species_names(self): 1249 ctml2yaml.convert( 1250 self.test_data_path / "species-names.xml", 1251 self.test_work_path / "species-names.yaml", 1252 ) 1253 ctmlGas, yamlGas = self.checkConversion('species-names') 1254 self.checkThermo(ctmlGas, yamlGas, [300, 500, 1300, 2000]) 1255 1256 def test_sri_falloff_reaction(self): 1257 ctml2yaml.convert( 1258 self.test_data_path / "sri-falloff.xml", 1259 self.test_work_path / "sri-falloff.yaml", 1260 ) 1261 ctmlGas, yamlGas = self.checkConversion("sri-falloff") 1262 self.checkThermo(ctmlGas, yamlGas, [300, 500, 1300, 2000]) 1263 self.checkKinetics(ctmlGas, yamlGas, [900, 1800], [2e5, 20e5]) 1264 1265 def test_vpss_and_hkft(self): 1266 ctml2yaml.convert( 1267 self.test_data_path / "pdss_hkft.xml", 1268 self.test_work_path / "pdss_hkft.yaml", 1269 ) 1270 ctmlPhase = ct.ThermoPhase("pdss_hkft.xml") 1271 yamlPhase = ct.ThermoPhase("pdss_hkft.yaml") 1272 # Due to changes in how the species elements are specified, the 1273 # composition of the species differs from XML to YAML (electrons are used 1274 # to specify charge in YAML while the charge node is used in XML). 1275 # Therefore, checkConversion won't work and we have to check a few things 1276 # manually. There are also no reactions specified for these phases so don't 1277 # need to do any checks for that. 1278 self.assertEqual(ctmlPhase.element_names, yamlPhase.element_names) 1279 self.assertEqual(ctmlPhase.species_names, yamlPhase.species_names) 1280 self.checkThermo(ctmlPhase, yamlPhase, [300, 500]) 1281 1282 def test_lattice_solid(self): 1283 ctml2yaml.convert( 1284 self.test_data_path / "Li7Si3_ls.xml", 1285 self.test_work_path / "Li7Si3_ls.yaml", 1286 ) 1287 # Use ThermoPhase to avoid constructing a default Transport object which 1288 # throws an error for the LatticeSolidPhase 1289 basename = "Li7Si3_ls" 1290 name = "Li7Si3_and_Interstitials(S)" 1291 ctmlPhase = ct.ThermoPhase(basename + ".xml", name=name) 1292 yamlPhase = ct.ThermoPhase(basename + ".yaml", name=name) 1293 self.assertEqual(ctmlPhase.element_names, yamlPhase.element_names) 1294 self.assertEqual(ctmlPhase.species_names, yamlPhase.species_names) 1295 self.checkThermo(ctmlPhase, yamlPhase, [300, 500]) 1296 1297 def test_margules(self): 1298 ctml2yaml.convert( 1299 self.test_data_path / "LiKCl_liquid.xml", 1300 self.test_work_path / "LiKCl_liquid.yaml", 1301 ) 1302 ctmlPhase, yamlPhase = self.checkConversion("LiKCl_liquid") 1303 self.checkThermo(ctmlPhase, yamlPhase, [300, 500]) 1304 1305 def test_idealsolidsoln(self): 1306 with self.assertWarnsRegex(UserWarning, "SolidKinetics type is not implemented"): 1307 ctml2yaml.convert( 1308 self.test_data_path / "IdealSolidSolnPhaseExample.xml", 1309 self.test_work_path / "IdealSolidSolnPhaseExample.yaml", 1310 ) 1311 1312 # SolidKinetics is not implemented, so can't create a Kinetics class instance. 1313 basename = "IdealSolidSolnPhaseExample" 1314 ctmlPhase = ct.ThermoPhase(basename + ".xml") 1315 yamlPhase = ct.ThermoPhase(basename + ".yaml") 1316 1317 self.assertEqual(ctmlPhase.element_names, yamlPhase.element_names) 1318 self.assertEqual(ctmlPhase.species_names, yamlPhase.species_names) 1319 self.checkThermo(ctmlPhase, yamlPhase, [300, 500]) 1320 1321 def test_idealmolalsoln(self): 1322 ctml2yaml.convert( 1323 self.test_data_path / "IdealMolalSolnPhaseExample.xml", 1324 self.test_work_path / "IdealMolalSolnPhaseExample.yaml", 1325 ) 1326 1327 ctmlPhase, yamlPhase = self.checkConversion("IdealMolalSolnPhaseExample") 1328 self.checkThermo(ctmlPhase, yamlPhase, [300, 500]) 1329 1330 def test_transport_models(self): 1331 ctml2yaml.convert( 1332 self.test_data_path / "transport_models_test.xml", 1333 self.test_work_path / "transport_models_test.yaml", 1334 ) 1335 for name in ["UnityLewis", "CK_Mix", "CK_Multi", "HighP"]: 1336 ctmlPhase, yamlPhase = self.checkConversion("transport_models_test", name=name) 1337 self.checkTransport(ctmlPhase, yamlPhase, [298, 1001, 2500]) 1338 1339 def test_nonreactant_orders(self): 1340 ctml2yaml.convert( 1341 self.test_data_path / "reaction-orders.xml", 1342 self.test_work_path / "reaction-orders.yaml", 1343 ) 1344 1345 ctmlPhase, yamlPhase = self.checkConversion("reaction-orders") 1346 self.checkThermo(ctmlPhase, yamlPhase, [300, 500]) 1347 self.checkKinetics(ctmlPhase, yamlPhase, [300, 1001, 2500], [1e5, 10e5]) 1348 1349 def test_species_ss_temperature_polynomials(self): 1350 ctml2yaml.convert( 1351 self.test_data_path / "Li_Liquid.xml", 1352 self.test_work_path / "Li_Liquid.yaml", 1353 ) 1354 1355 ctmlPhase, yamlPhase = self.checkConversion("Li_Liquid") 1356 self.checkThermo(ctmlPhase, yamlPhase, [300, 500]) 1357 1358 def test_duplicate_section_ids(self): 1359 with self.assertWarnsRegex(UserWarning, "Duplicate 'speciesData' id"): 1360 ctml2yaml.convert( 1361 self.test_data_path / "duplicate-speciesData-ids.xml", 1362 self.test_work_path / "duplicate-speciesData-ids.yaml", 1363 ) 1364 with self.assertWarnsRegex(UserWarning, "Duplicate 'reactionData' id"): 1365 ctml2yaml.convert( 1366 self.test_data_path / "duplicate-reactionData-ids.xml", 1367 self.test_work_path / "duplicate-reactionData-ids.yaml", 1368 ) 1369