1from math import exp 2from pathlib import Path 3import textwrap 4 5import cantera as ct 6import numpy as np 7from . import utilities 8 9 10class TestImplicitThirdBody(utilities.CanteraTest): 11 # tests for three-body reactions with specified collision partner 12 13 @classmethod 14 def setUpClass(cls): 15 utilities.CanteraTest.setUpClass() 16 cls.gas = ct.Solution("gri30.yaml") 17 18 def test_implicit_three_body(self): 19 # check equivalency of auto-detected and explicit specification 20 yaml1 = """ 21 equation: H + 2 O2 <=> HO2 + O2 22 rate-constant: {A: 2.08e+19, b: -1.24, Ea: 0.0} 23 """ 24 rxn1 = ct.Reaction.from_yaml(yaml1, self.gas) 25 self.assertEqual(rxn1.reaction_type, "three-body") 26 self.assertEqual(rxn1.default_efficiency, 0.) 27 self.assertEqual(rxn1.efficiencies, {"O2": 1}) 28 29 yaml2 = """ 30 equation: H + O2 + M <=> HO2 + M 31 rate-constant: {A: 2.08e+19, b: -1.24, Ea: 0.0} 32 type: three-body 33 default-efficiency: 0 34 efficiencies: {O2: 1.0} 35 """ 36 rxn2 = ct.Reaction.from_yaml(yaml2, self.gas) 37 self.assertEqual(rxn1.efficiencies, rxn2.efficiencies) 38 self.assertEqual(rxn1.default_efficiency, rxn2.default_efficiency) 39 40 def test_duplicate(self): 41 # @todo simplify this test 42 # duplicates are currently only checked for import from file 43 gas1 = ct.Solution(thermo="IdealGas", kinetics="GasKinetics", 44 species=self.gas.species(), reactions=[]) 45 46 yaml1 = """ 47 equation: H + O2 + H2O <=> HO2 + H2O 48 rate-constant: {A: 1.126e+19, b: -0.76, Ea: 0.0} 49 """ 50 rxn1 = ct.Reaction.from_yaml(yaml1, gas1) 51 52 yaml2 = """ 53 equation: H + O2 + M <=> HO2 + M 54 rate-constant: {A: 1.126e+19, b: -0.76, Ea: 0.0} 55 type: three-body 56 default-efficiency: 0 57 efficiencies: {H2O: 1} 58 """ 59 rxn2 = ct.Reaction.from_yaml(yaml2, gas1) 60 61 self.assertEqual(rxn1.reaction_type, rxn2.reaction_type) 62 self.assertEqual(rxn1.reactants, rxn2.reactants) 63 self.assertEqual(rxn1.products, rxn2.products) 64 self.assertEqual(rxn1.efficiencies, rxn2.efficiencies) 65 self.assertEqual(rxn1.default_efficiency, rxn2.default_efficiency) 66 67 gas1.add_reaction(rxn1) 68 gas1.add_reaction(rxn2) 69 70 fname = "duplicate.yaml" 71 gas1.write_yaml(fname) 72 73 with self.assertRaisesRegex(Exception, "Undeclared duplicate reactions"): 74 gas2 = ct.Solution(fname) 75 76 Path(fname).unlink() 77 78 def test_short_serialization(self): 79 # check that serialized output is compact 80 yaml = """ 81 equation: H + O2 + H2O <=> HO2 + H2O 82 rate-constant: {A: 1.126e+19, b: -0.76, Ea: 0.0} 83 """ 84 rxn = ct.Reaction.from_yaml(yaml, self.gas) 85 input_data = rxn.input_data 86 87 self.assertNotIn("type", input_data) 88 self.assertNotIn("default-efficiency", input_data) 89 self.assertNotIn("efficiencies", input_data) 90 91 def test_non_integer_stoich(self): 92 # check that non-integer coefficients prevent automatic conversion 93 yaml = """ 94 equation: 2 H + 1.5 O2 <=> H2O + O2 95 rate-constant: {A: 2.08e+19, b: -1.24, Ea: 0.0} 96 """ 97 rxn = ct.Reaction.from_yaml(yaml, self.gas) 98 self.assertEqual(rxn.reaction_type, "elementary") 99 100 def test_not_three_body(self): 101 # check that insufficient reactants prevent automatic conversion 102 yaml = """ 103 equation: HCNO + H <=> H + HNCO # Reaction 270 104 rate-constant: {A: 2.1e+15, b: -0.69, Ea: 2850.0} 105 """ 106 rxn = ct.Reaction.from_yaml(yaml, self.gas) 107 self.assertEqual(rxn.reaction_type, "elementary") 108 109 def test_user_override(self): 110 # check that type specification prevents automatic conversion 111 yaml = """ 112 equation: H + 2 O2 <=> HO2 + O2 113 rate-constant: {A: 2.08e+19, b: -1.24, Ea: 0.0} 114 type: elementary 115 """ 116 rxn = ct.Reaction.from_yaml(yaml, self.gas) 117 self.assertEqual(rxn.reaction_type, "elementary") 118 119 120class ReactionRateTests: 121 # test suite for reaction rate expressions 122 123 _cls = None # reaction rate object to be tested 124 _type = None # name of reaction rate 125 _uses_pressure = False # rate evaluation requires pressure 126 _index = None # index of reaction in "kineticsfromscratch.yaml" 127 _input = None # input parameters (dict corresponding to YAML) 128 _yaml = None # yaml string specifying parameters 129 130 @classmethod 131 def setUpClass(cls): 132 utilities.CanteraTest.setUpClass() 133 cls.gas = ct.Solution("kineticsfromscratch.yaml") 134 cls.gas.X = "H2:0.1, H2O:0.2, O2:0.7, O:1e-4, OH:1e-5, H:2e-5" 135 cls.gas.TP = 900, 2*ct.one_atm 136 137 def test_type(self): 138 # check reaction type 139 self.assertIn(self._cls.__name__, "{}".format(self.rate)) 140 141 def test_rate_T(self): 142 # check evaluation as a function of temperature only 143 if self._uses_pressure: 144 with self.assertRaisesRegex(ct.CanteraError, "reaction type requires pressure"): 145 self.assertNear(self.rate(self.gas.T), 146 self.gas.forward_rate_constants[self._index]) 147 else: 148 self.assertNear(self.rate(self.gas.T), 149 self.gas.forward_rate_constants[self._index]) 150 151 def test_rate_TP(self): 152 # check evaluation as a function of temperature and pressure 153 self.assertNear(self.rate(self.gas.T, self.gas.P), 154 self.gas.forward_rate_constants[self._index]) 155 156 def test_input(self): 157 # check instantiation based on input_data 158 rate = self._cls(input_data=self._input) 159 self.assertIn(self._cls.__name__, "{}".format(rate)) 160 self.assertNear(rate(self.gas.T, self.gas.P), 161 self.rate(self.gas.T, self.gas.P)) 162 163 def test_unconfigured(self): 164 # check behavior of unconfigured rate object 165 rate = self._cls(input_data={}) 166 self.assertTrue(np.isnan(rate(self.gas.T, self.gas.P))) 167 input_data = rate.input_data 168 self.assertIsInstance(input_data, dict) 169 if input_data: 170 self.assertEqual(input_data, {"type": self._type}) 171 172 def test_roundtrip(self): 173 # check round-trip instantiation via input_data 174 input_data = self.rate.input_data 175 rate = self._cls(input_data=input_data) 176 self.assertNear(rate(self.gas.T, self.gas.P), 177 self.rate(self.gas.T, self.gas.P)) 178 179 def test_from_dict(self): 180 # check instantiation from dictionary 181 input_data = self.rate.input_data 182 rate = ct.ReactionRate.from_dict(input_data) 183 self.assertNear(rate(self.gas.T, self.gas.P), 184 self.rate(self.gas.T, self.gas.P)) 185 186 def test_from_yaml(self): 187 # check instantiation from yaml string 188 rate = ct.ReactionRate.from_yaml(self._yaml) 189 self.assertNear(rate(self.gas.T, self.gas.P), 190 self.rate(self.gas.T, self.gas.P)) 191 192 def test_with_units(self): 193 units = "units: {length: cm, quantity: mol}" 194 yaml = f"{textwrap.dedent(self._yaml)}\n{units}" 195 with self.assertRaisesRegex(Exception, "not supported"): 196 rate = ct.ReactionRate.from_yaml(yaml) 197 198 199class TestArrheniusRate(ReactionRateTests, utilities.CanteraTest): 200 # test Arrhenius rate expressions 201 202 _cls = ct.ArrheniusRate 203 _type = "Arrhenius" 204 _uses_pressure = False 205 _index = 0 206 _input = {"rate-constant": {"A": 38.7, "b": 2.7, "Ea": 26191840.0}} 207 _yaml = "rate-constant: {A: 38.7, b: 2.7, Ea: 6260.0 cal/mol}" 208 209 def setUp(self): 210 self.A = self.gas.reaction(self._index).rate.pre_exponential_factor 211 self.b = self.gas.reaction(self._index).rate.temperature_exponent 212 self.Ea = self.gas.reaction(self._index).rate.activation_energy 213 self.rate = ct.ArrheniusRate(self.A, self.b, self.Ea) 214 215 def test_parameters(self): 216 # test reaction rate parameters 217 self.assertEqual(self.A, self.rate.pre_exponential_factor) 218 self.assertEqual(self.b, self.rate.temperature_exponent) 219 self.assertEqual(self.Ea, self.rate.activation_energy) 220 221 def test_allow_negative_pre_exponential_factor(self): 222 # test reaction rate property 223 self.assertFalse(self.rate.allow_negative_pre_exponential_factor) 224 self.rate.allow_negative_pre_exponential_factor = True 225 self.assertTrue(self.rate.allow_negative_pre_exponential_factor) 226 227 def test_standalone(self): 228 yaml = "rate-constant: {A: 4.0e+21 cm^6/mol^2/s, b: 0.0, Ea: 1207.72688}" 229 with self.assertRaisesRegex(Exception, "not supported"): 230 rate = ct.ReactionRate.from_yaml(yaml) 231 232 233class TestPlogRate(ReactionRateTests, utilities.CanteraTest): 234 # test Plog rate expressions 235 236 _cls = ct.PlogRate 237 _type = "pressure-dependent-Arrhenius" 238 _uses_pressure = True 239 _index = 3 240 _input = {"rate-constants": [ 241 {"P": 1013.25, "A": 1.2124e+16, "b": -0.5779, "Ea": 45491376.8}, 242 {"P": 101325., "A": 4.9108e+31, "b": -4.8507, "Ea": 103649395.2}, 243 {"P": 1013250., "A": 1.2866e+47, "b": -9.0246, "Ea": 166508556.0}, 244 {"P": 10132500., "A": 5.9632e+56, "b": -11.529, "Ea": 220076726.4}]} 245 _yaml = """ 246 type: pressure-dependent-Arrhenius 247 rate-constants: 248 - {P: 0.01 atm, A: 1.2124e+16, b: -0.5779, Ea: 1.08727e+04 cal/mol} 249 - {P: 1.0 atm, A: 4.9108e+31, b: -4.8507, Ea: 2.47728e+04 cal/mol} 250 - {P: 10.0 atm, A: 1.2866e+47, b: -9.0246, Ea: 3.97965e+04 cal/mol} 251 - {P: 100.0 atm, A: 5.9632e+56, b: -11.529, Ea: 5.25996e+04 cal/mol} 252 """ 253 254 def setUp(self): 255 self.rate = ct.PlogRate([(1013.25, ct.Arrhenius(1.2124e+16, -0.5779, 45491376.8)), 256 (101325., ct.Arrhenius(4.9108e+31, -4.8507, 103649395.2)), 257 (1013250., ct.Arrhenius(1.2866e+47, -9.0246, 166508556.0)), 258 (10132500., ct.Arrhenius(5.9632e+56, -11.529, 220076726.4))]) 259 260 def test_get_rates(self): 261 # test getter for property rates 262 rates = self.rate.rates 263 self.assertIsInstance(rates, list) 264 265 other = self._input["rate-constants"] 266 self.assertEqual(len(rates), len(other)) 267 for index, item in enumerate(rates): 268 P, rate = item 269 self.assertNear(P, other[index]["P"]) 270 self.assertNear(rate.pre_exponential_factor, other[index]["A"]) 271 self.assertNear(rate.temperature_exponent, other[index]["b"]) 272 self.assertNear(rate.activation_energy, other[index]["Ea"]) 273 274 def test_set_rates(self): 275 # test setter for property rates 276 other = [ 277 {"P": 100., "A": 1.2124e+16, "b": -1., "Ea": 45491376.8}, 278 {"P": 10000., "A": 4.9108e+31, "b": -2., "Ea": 103649395.2}, 279 {"P": 1000000., "A": 1.2866e+47, "b": -3., "Ea": 166508556.0}] 280 rate = ct.PlogRate([(o["P"], ct.Arrhenius(o["A"], o["b"], o["Ea"])) 281 for o in other]) 282 rates = rate.rates 283 self.assertEqual(len(rates), len(other)) 284 285 for index, item in enumerate(rates): 286 P, rate = item 287 self.assertNear(P, other[index]["P"]) 288 self.assertNear(rate.pre_exponential_factor, other[index]["A"]) 289 self.assertNear(rate.temperature_exponent, other[index]["b"]) 290 self.assertNear(rate.activation_energy, other[index]["Ea"]) 291 292 def test_no_rates(self): 293 # test instantiation of empty rate 294 rate = ct.PlogRate() 295 self.assertIsInstance(rate.rates, list) 296 297 def test_standalone(self): 298 yaml = """ 299 type: pressure-dependent-Arrhenius 300 rate-constants: 301 - {P: 0.01 atm, A: 1.2124e+16, b: -0.5779, Ea: 1.08727e+04 cal/mol} 302 - {P: 1.0 atm, A: 4.9108e+31 cm^6/mol^2/s, b: -4.8507, Ea: 2.47728e+04 cal/mol} 303 - {P: 10.0 atm, A: 1.2866e+47, b: -9.0246, Ea: 3.97965e+04 cal/mol} 304 - {P: 100.0 atm, A: 5.9632e+56, b: -11.529, Ea: 5.25996e+04 cal/mol} 305 """ 306 with self.assertRaisesRegex(Exception, "not supported"): 307 rate = ct.ReactionRate.from_yaml(yaml) 308 309 310class TestChebyshevRate(ReactionRateTests, utilities.CanteraTest): 311 # test Chebyshev rate expressions 312 313 _cls = ct.ChebyshevRate 314 _type = "Chebyshev" 315 _uses_pressure = True 316 _index = 4 317 _input = {"data": [[8.2883, -1.1397, -0.12059, 0.016034], 318 [1.9764, 1.0037, 0.0072865, -0.030432], 319 [0.3177, 0.26889, 0.094806, -0.0076385]], 320 "pressure-range": [1000.0, 10000000.0], 321 "temperature-range": [290.0, 3000.0]} 322 _yaml = """ 323 type: Chebyshev 324 temperature-range: [290.0, 3000.0] 325 pressure-range: [9.869232667160128e-03 atm, 98.69232667160128 atm] 326 data: 327 - [8.2883, -1.1397, -0.12059, 0.016034] 328 - [1.9764, 1.0037, 7.2865e-03, -0.030432] 329 - [0.3177, 0.26889, 0.094806, -7.6385e-03] 330 """ 331 332 def setUp(self): 333 self.Trange = self.gas.reaction(self._index).rate.temperature_range 334 self.Prange = self.gas.reaction(self._index).rate.pressure_range 335 self.data = self.gas.reaction(self._index).rate.data 336 self.rate = ct.ChebyshevRate(self.Trange, self.Prange, self.data) 337 338 def test_parameters(self): 339 # test getters for rate properties 340 self.assertEqual(self.Trange[0], self.rate.temperature_range[0]) 341 self.assertEqual(self.Trange[1], self.rate.temperature_range[1]) 342 self.assertEqual(self.Prange[0], self.rate.pressure_range[0]) 343 self.assertEqual(self.Prange[1], self.rate.pressure_range[1]) 344 self.assertTrue(np.all(self.data == self.rate.data)) 345 346 347class ReactionTests: 348 # test suite for reaction expressions 349 350 _cls = None # reaction object to be tested 351 _type = None # name of reaction rate 352 _legacy = False # object uses legacy framework 353 _equation = None # reaction equation string 354 _rate = None # parameters for reaction rate object constructor 355 _rate_obj = None # reaction rate object 356 _kwargs = {} # additional parameters required by contructor 357 _index = None # index of reaction in "kineticsfromscratch.yaml" 358 _yaml = None # YAML parameterization 359 _input = None # input parameters (dict corresponding to YAML) 360 _deprecated_getters = {} # test legacy getters (old framework) 361 _deprecated_setters = {} # test legacy setters (old framework) 362 _deprecated_callers = {} # test legacy callers (old framework) 363 364 @classmethod 365 def setUpClass(cls): 366 utilities.CanteraTest.setUpClass() 367 cls.gas = ct.Solution("kineticsfromscratch.yaml", transport_model=None) 368 cls.gas.X = "H2:0.1, H2O:0.2, O2:0.7, O:1e-4, OH:1e-5, H:2e-5" 369 cls.gas.TP = 900, 2*ct.one_atm 370 cls.species = cls.gas.species() 371 372 def check_rxn(self, rxn, check_legacy=True): 373 # helper function that checks reaction configuration 374 ix = self._index 375 self.assertEqual(rxn.reactants, self.gas.reaction(ix).reactants) 376 self.assertEqual(rxn.products, self.gas.reaction(ix).products) 377 if check_legacy: 378 self.assertEqual(rxn.reaction_type, self._type) 379 self.assertEqual(rxn.uses_legacy, self._type.endswith("-legacy")) 380 self.assertEqual(rxn.uses_legacy, self._legacy) 381 382 gas2 = ct.Solution(thermo="IdealGas", kinetics="GasKinetics", 383 species=self.species, reactions=[rxn]) 384 gas2.TPX = self.gas.TPX 385 self.check_solution(gas2, check_legacy) 386 387 def check_solution(self, gas2, check_legacy=True): 388 # helper function that checks evaluation of reaction rates 389 ix = self._index 390 if check_legacy: 391 self.assertEqual(gas2.reaction_type_str(0), self._type) 392 self.assertNear(gas2.forward_rate_constants[0], 393 self.gas.forward_rate_constants[ix]) 394 self.assertNear(gas2.net_rates_of_progress[0], 395 self.gas.net_rates_of_progress[ix]) 396 397 def test_rate(self): 398 # check consistency of reaction rate and forward rate constant 399 if self._rate_obj is None: 400 return 401 if self._legacy: 402 self.assertNear(self._rate_obj(self.gas.T), self.gas.forward_rate_constants[self._index]) 403 else: 404 self.assertNear(self._rate_obj(self.gas.T, self.gas.P), 405 self.gas.forward_rate_constants[self._index]) 406 407 def test_from_parts(self): 408 # check instantiation from parts (reactants, products, rate expression) 409 if not self._rate_obj: 410 return 411 orig = self.gas.reaction(self._index) 412 rxn = self._cls(orig.reactants, orig.products, legacy=self._legacy) 413 rxn.rate = self._rate_obj 414 self.check_rxn(rxn) 415 416 def test_from_dict1(self): 417 # check instantiation from keywords / rate defined by dictionary 418 rxn = self._cls(equation=self._equation, rate=self._rate, kinetics=self.gas, 419 legacy=self._legacy, **self._kwargs) 420 self.check_rxn(rxn) 421 422 def test_from_yaml(self): 423 # check instantiation from yaml string 424 if self._yaml is None: 425 return 426 rxn = ct.Reaction.from_yaml(self._yaml, kinetics=self.gas) 427 self.check_rxn(rxn) 428 429 def test_deprecated_yaml(self): 430 # check instantiation from yaml string 431 if self._yaml is None: 432 return 433 with self.assertWarnsRegex(DeprecationWarning, "is renamed to 'from_yaml'"): 434 rxn = ct.Reaction.fromYaml(self._yaml, kinetics=self.gas) 435 self.check_rxn(rxn) 436 437 def test_from_dict2(self): 438 # check instantiation from a yaml dictionary 439 if self._yaml is None: 440 return 441 rxn1 = ct.Reaction.from_yaml(self._yaml, kinetics=self.gas) 442 input_data = rxn1.input_data 443 rxn2 = ct.Reaction.from_dict(input_data, kinetics=self.gas) 444 # cannot compare types as input_data does not recreate legacy objects 445 self.check_rxn(rxn2, check_legacy=False) 446 447 def test_from_rate(self): 448 # check instantiation from keywords / rate provided as object 449 if self._rate_obj is None: 450 return 451 rxn = self._cls(equation=self._equation, rate=self._rate_obj, kinetics=self.gas, 452 legacy=self._legacy, **self._kwargs) 453 self.check_rxn(rxn) 454 455 def test_add_rxn(self): 456 # check adding new reaction to solution 457 if self._rate_obj is None: 458 return 459 gas2 = ct.Solution(thermo="IdealGas", kinetics="GasKinetics", 460 species=self.species, reactions=[]) 461 gas2.TPX = self.gas.TPX 462 463 rxn = self._cls(equation=self._equation, rate=self._rate_obj, kinetics=self.gas, 464 legacy=self._legacy, **self._kwargs) 465 gas2.add_reaction(rxn) 466 self.check_solution(gas2) 467 468 def test_raises_invalid_rate(self): 469 # check exception for instantiation from keywords / invalid rate 470 with self.assertRaises(TypeError): 471 rxn = self._cls(equation=self._equation, rate=(), kinetics=self.gas, 472 legacy=self._legacy, **self._kwargs) 473 474 def test_no_rate(self): 475 # check behavior for instantiation from keywords / no rate 476 if self._rate_obj is None: 477 return 478 rxn = self._cls(equation=self._equation, kinetics=self.gas, 479 legacy=self._legacy, **self._kwargs) 480 if self._legacy: 481 self.assertNear(rxn.rate(self.gas.T), 0.) 482 else: 483 self.assertTrue(np.isnan(rxn.rate(self.gas.T, self.gas.P))) 484 485 gas2 = ct.Solution(thermo="IdealGas", kinetics="GasKinetics", 486 species=self.species, reactions=[rxn]) 487 gas2.TPX = self.gas.TPX 488 489 self.assertNear(gas2.forward_rate_constants[0], 0.) 490 self.assertNear(gas2.net_rates_of_progress[0], 0.) 491 492 def test_replace_rate(self): 493 # check replacing reaction rate expression 494 if self._rate_obj is None: 495 return 496 rxn = self._cls(equation=self._equation, kinetics=self.gas, 497 legacy=self._legacy, **self._kwargs) 498 rxn.rate = self._rate_obj 499 self.check_rxn(rxn) 500 501 def test_roundtrip(self): 502 # check round-trip instantiation via input_data 503 if self._legacy: 504 return 505 rxn = self._cls(equation=self._equation, rate=self._rate_obj, kinetics=self.gas, 506 legacy=self._legacy, **self._kwargs) 507 input_data = rxn.rate.input_data 508 rate_obj = rxn.rate.__class__(input_data=input_data) 509 rxn2 = self._cls(equation=self._equation, rate=rate_obj, kinetics=self.gas, 510 legacy=self._legacy, **self._kwargs) 511 self.check_rxn(rxn2) 512 513 def check_equal(self, one, two): 514 # helper function for deprecation tests 515 self.assertEqual(type(one), type(two)) 516 if isinstance(one, (list, tuple, np.ndarray)): 517 self.assertArrayNear(one, two) 518 else: 519 self.assertNear(one, two) 520 521 def test_deprecated_getters(self): 522 # check property getters deprecated in new framework 523 if self._yaml is None: 524 return 525 526 ct.suppress_deprecation_warnings() # disable fatal deprecation warnings 527 528 rxn = ct.Reaction.from_yaml(self._yaml, kinetics=self.gas) 529 for attr, default in self._deprecated_getters.items(): 530 if self._legacy: 531 self.check_equal(getattr(rxn, attr), default) 532 else: 533 with self.assertWarnsRegex(DeprecationWarning, "property is moved"): 534 try: 535 self.check_equal(getattr(rxn, attr), default) 536 except Exception as err: 537 print(f"Exception raised when testing getter for '{attr}'") 538 raise err 539 540 ct.make_deprecation_warnings_fatal() # re-enable fatal deprecation warnings 541 542 def test_deprecated_setters(self): 543 # check property setters deprecated in new framework 544 if self._yaml is None: 545 return 546 547 rxn = ct.Reaction.from_yaml(self._yaml, kinetics=self.gas) 548 for attr, new in self._deprecated_setters.items(): 549 if self._legacy: 550 setattr(rxn, attr, new) 551 self.check_equal(getattr(rxn, attr), new) 552 else: 553 with self.assertWarnsRegex(DeprecationWarning, "property is moved"): 554 setattr(rxn, attr, new) 555 with self.assertWarnsRegex(DeprecationWarning, "property is moved"): 556 self.check_equal(getattr(rxn, attr), new) 557 558 def test_deprecated_callers(self): 559 # check methods deprecated in new framework 560 if self._yaml is None: 561 return 562 563 rxn = ct.Reaction.from_yaml(self._yaml, kinetics=self.gas) 564 for state, value in self._deprecated_callers.items(): 565 T, P = state 566 if self._legacy: 567 self.check_equal(rxn(T, P), value) 568 else: 569 with self.assertWarnsRegex(DeprecationWarning, "method is moved"): 570 self.check_equal(rxn(T, P), value) 571 572 573class TestElementary(ReactionTests, utilities.CanteraTest): 574 # test updated version of elementary reaction 575 576 _cls = ct.ElementaryReaction 577 _type = "elementary" 578 _equation = "H2 + O <=> H + OH" 579 _rate = {"A": 38.7, "b": 2.7, "Ea": 2.619184e+07} 580 _index = 0 581 _yaml = """ 582 equation: O + H2 <=> H + OH 583 rate-constant: {A: 38.7, b: 2.7, Ea: 6260.0 cal/mol} 584 """ 585 _deprecated_getters = {"allow_negative_pre_exponential_factor": False} 586 _deprecated_setters = {"allow_negative_pre_exponential_factor": True} 587 588 @classmethod 589 def setUpClass(cls): 590 ReactionTests.setUpClass() 591 if cls._legacy: 592 args = list(cls._rate.values()) 593 cls._rate_obj = ct.Arrhenius(*args) 594 else: 595 cls._rate_obj = ct.ArrheniusRate(**cls._rate) 596 597 def test_arrhenius(self): 598 # test assigning Arrhenius rate 599 rate = ct.Arrhenius(self._rate["A"], self._rate["b"], self._rate["Ea"]) 600 rxn = self._cls(equation=self._equation, kinetics=self.gas, 601 legacy=self._legacy, **self._kwargs) 602 if self._legacy: 603 rxn.rate = rate 604 else: 605 with self.assertWarnsRegex(DeprecationWarning, "'Arrhenius' object is deprecated"): 606 rxn.rate = rate 607 self.check_rxn(rxn) 608 609 610class TestElementary2(TestElementary): 611 # test legacy version of elementary reaction 612 613 _cls = ct.ElementaryReaction 614 _type = "elementary-legacy" 615 _legacy = True 616 _yaml = """ 617 equation: O + H2 <=> H + OH 618 type: elementary-legacy 619 rate-constant: {A: 38.7, b: 2.7, Ea: 6260.0 cal/mol} 620 """ 621 622 623class TestThreeBody(TestElementary): 624 # test updated version of three-body reaction 625 626 _cls = ct.ThreeBodyReaction 627 _type = "three-body" 628 _equation = "2 O + M <=> O2 + M" 629 _rate = {"A": 1.2e11, "b": -1.0, "Ea": 0.0} 630 _kwargs = {"efficiencies": {"H2": 2.4, "H2O": 15.4, "AR": 0.83}} 631 _index = 1 632 _yaml = """ 633 equation: 2 O + M <=> O2 + M 634 type: three-body 635 rate-constant: {A: 1.2e+11, b: -1.0, Ea: 0.0 cal/mol} 636 efficiencies: {H2: 2.4, H2O: 15.4, AR: 0.83} 637 """ 638 639 def test_from_parts(self): 640 # overload default reaction creation from parts 641 orig = self.gas.reaction(self._index) 642 rxn = self._cls(orig.reactants, orig.products, legacy=self._legacy) 643 rxn.rate = self._rate_obj 644 rxn.efficiencies = self._kwargs["efficiencies"] 645 self.check_rxn(rxn) 646 647 def test_rate(self): 648 # rate constant contains third-body concentration 649 pass 650 651 def test_efficiencies(self): 652 # check efficiencies 653 rxn = self._cls(equation=self._equation, rate=self._rate_obj, kinetics=self.gas, 654 legacy=self._legacy, **self._kwargs) 655 656 self.assertEqual(rxn.efficiencies, self._kwargs["efficiencies"]) 657 658 659class TestThreeBody2(TestThreeBody): 660 # test legacy version of three-body reaction 661 662 _cls = ct.ThreeBodyReaction 663 _type = "three-body-legacy" 664 _legacy = True 665 _yaml = """ 666 equation: 2 O + M <=> O2 + M 667 type: three-body-legacy 668 rate-constant: {A: 1.2e+11, b: -1.0, Ea: 0.0 cal/mol} 669 efficiencies: {H2: 2.4, H2O: 15.4, AR: 0.83} 670 """ 671 672 673class TestImplicitThreeBody(TestThreeBody): 674 # test three-body reactions with explicit collision parther 675 676 _cls = ct.ThreeBodyReaction 677 _type = "three-body" 678 _equation = "H + 2 O2 <=> HO2 + O2" 679 _rate = {"A": 2.08e+19, "b": -1.24, "Ea": 0.0} 680 _index = 5 681 _yaml = """ 682 equation: H + 2 O2 <=> HO2 + O2 683 rate-constant: {A: 2.08e+19, b: -1.24, Ea: 0.0} 684 """ 685 686 def test_efficiencies(self): 687 # overload of default tester 688 rxn = self._cls(equation=self._equation, rate=self._rate_obj, kinetics=self.gas, 689 legacy=self._legacy) 690 self.assertEqual(rxn.efficiencies, {"O2": 1.}) 691 self.assertEqual(rxn.default_efficiency, 0.) 692 693 def test_from_parts(self): 694 # overload of default tester 695 orig = self.gas.reaction(self._index) 696 rxn = self._cls(orig.reactants, orig.products) 697 rxn.rate = self._rate_obj 698 rxn.efficiencies = {"O2": 1.} 699 rxn.default_efficiency = 0 700 self.check_rxn(rxn) 701 702 703class TestPlog(ReactionTests, utilities.CanteraTest): 704 # test updated version of Plog reaction 705 706 _cls = ct.PlogReaction 707 _type = "pressure-dependent-Arrhenius" 708 _legacy = False 709 _equation = "H2 + O2 <=> 2 OH" 710 _rate = [(1013.25, ct.Arrhenius(1.2124e+16, -0.5779, 45491376.8)), 711 (101325., ct.Arrhenius(4.9108e+31, -4.8507, 103649395.2)), 712 (1013250., ct.Arrhenius(1.2866e+47, -9.0246, 166508556.0)), 713 (10132500., ct.Arrhenius(5.9632e+56, -11.529, 220076726.4))] 714 _index = 3 715 _yaml = """ 716 equation: H2 + O2 <=> 2 OH 717 type: pressure-dependent-Arrhenius 718 rate-constants: 719 - {P: 0.01 atm, A: 1.2124e+16, b: -0.5779, Ea: 1.08727e+04 cal/mol} 720 - {P: 1.0 atm, A: 4.9108e+31, b: -4.8507, Ea: 2.47728e+04 cal/mol} 721 - {P: 10.0 atm, A: 1.2866e+47, b: -9.0246, Ea: 3.97965e+04 cal/mol} 722 - {P: 100.0 atm, A: 5.9632e+56, b: -11.529, Ea: 5.25996e+04 cal/mol} 723 """ 724 _deprecated_callers = {(1000., ct.one_atm): 530968934612.9017} 725 726 @classmethod 727 def setUpClass(cls): 728 ReactionTests.setUpClass() 729 if not cls._legacy: 730 cls._rate_obj = ct.PlogRate(cls._rate) 731 732 def check_rates(self, rates, other): 733 # helper function used by deprecation tests 734 self.assertEqual(len(rates), len(other)) 735 for index, item in enumerate(rates): 736 P, rate = item 737 self.assertNear(P, other[index][0]) 738 self.assertNear(rate.pre_exponential_factor, other[index][1].pre_exponential_factor) 739 self.assertNear(rate.temperature_exponent, other[index][1].temperature_exponent) 740 self.assertNear(rate.activation_energy, other[index][1].activation_energy) 741 742 def test_deprecated_getters(self): 743 # overload default tester for deprecated property getters 744 rxn = ct.Reaction.from_yaml(self._yaml, kinetics=self.gas) 745 if self._legacy: 746 self.check_rates(rxn.rates, self._rate) 747 else: 748 with self.assertWarnsRegex(DeprecationWarning, "property is moved"): 749 self.check_rates(rxn.rates, self._rate) 750 751 def test_deprecated_setters(self): 752 # overload default tester for deprecated property setters 753 rate = ct.PlogRate(self._rate) 754 rates = rate.rates 755 756 rxn = ct.Reaction.from_yaml(self._yaml, kinetics=self.gas) 757 if self._legacy: 758 rxn.rates = rates 759 self.check_rates(rxn.rates, self._rate) 760 else: 761 with self.assertWarnsRegex(DeprecationWarning, "Setter is replaceable"): 762 rxn.rates = rates 763 with self.assertWarnsRegex(DeprecationWarning, "property is moved"): 764 self.check_rates(rxn.rates, self._rate) 765 766 767class TestPlog2(TestPlog): 768 # test legacy version of Plog reaction 769 770 _cls = ct.PlogReaction 771 _type = "pressure-dependent-Arrhenius-legacy" 772 _legacy = True 773 _rate_obj = None 774 _yaml = """ 775 equation: H2 + O2 <=> 2 OH 776 type: pressure-dependent-Arrhenius-legacy 777 rate-constants: 778 - {P: 0.01 atm, A: 1.2124e+16, b: -0.5779, Ea: 1.08727e+04 cal/mol} 779 - {P: 1.0 atm, A: 4.9108e+31, b: -4.8507, Ea: 2.47728e+04 cal/mol} 780 - {P: 10.0 atm, A: 1.2866e+47, b: -9.0246, Ea: 3.97965e+04 cal/mol} 781 - {P: 100.0 atm, A: 5.9632e+56, b: -11.529, Ea: 5.25996e+04 cal/mol} 782 """ 783 784 785class TestChebyshev(ReactionTests, utilities.CanteraTest): 786 # test updated version of Chebyshev reaction 787 788 _cls = ct.ChebyshevReaction 789 _type = "Chebyshev" 790 _equation = "HO2 <=> OH + O" 791 _rate = {"temperature_range": (290., 3000.), "pressure_range": (1000., 10000000.0), 792 "data": [[ 8.2883e+00, -1.1397e+00, -1.2059e-01, 1.6034e-02], 793 [ 1.9764e+00, 1.0037e+00, 7.2865e-03, -3.0432e-02], 794 [ 3.1770e-01, 2.6889e-01, 9.4806e-02, -7.6385e-03]]} 795 _index = 4 796 _yaml = """ 797 equation: HO2 <=> OH + O 798 type: Chebyshev 799 temperature-range: [290.0, 3000.0] 800 pressure-range: [9.869232667160128e-03 atm, 98.69232667160128 atm] 801 data: 802 - [8.2883, -1.1397, -0.12059, 0.016034] 803 - [1.9764, 1.0037, 7.2865e-03, -0.030432] 804 - [0.3177, 0.26889, 0.094806, -7.6385e-03] 805 """ 806 _deprecated_getters = {"nPressure": 4, "nTemperature": 3} 807 _deprecated_callers = {(1000., ct.one_atm): 2858762454.1119065} 808 809 @classmethod 810 def setUpClass(cls): 811 ReactionTests.setUpClass() 812 if not cls._legacy: 813 cls._rate_obj = ct.ChebyshevRate(**cls._rate) 814 cls._deprecated_getters.update({"coeffs": np.array(cls._rate["data"])}) 815 cls._deprecated_getters.update( 816 {k: v for k, v in cls._rate.items() 817 if k not in ["data", "temperature_range", "pressure_range"]}) 818 819 820class TestChebyshev2(TestChebyshev): 821 # test legacy version of Chebyshev reaction 822 823 _cls = ct.ChebyshevReaction 824 _type = "Chebyshev-legacy" 825 _legacy = True 826 _rate_obj = None 827 _yaml = """ 828 equation: HO2 <=> OH + O 829 type: Chebyshev-legacy 830 temperature-range: [290.0, 3000.0] 831 pressure-range: [9.869232667160128e-03 atm, 98.69232667160128 atm] 832 data: 833 - [8.2883, -1.1397, -0.12059, 0.016034] 834 - [1.9764, 1.0037, 7.2865e-03, -0.030432] 835 - [0.3177, 0.26889, 0.094806, -7.6385e-03] 836 """ 837 838 839class TestCustom(ReactionTests, utilities.CanteraTest): 840 # test Custom reaction 841 842 # probe O + H2 <=> H + OH 843 _cls = ct.CustomReaction 844 _type = "custom-rate-function" 845 _legacy = False 846 _equation = "H2 + O <=> H + OH" 847 _rate_obj = ct.CustomRate(lambda T: 38.7 * T**2.7 * exp(-3150.15428/T)) 848 _index = 0 849 _yaml = None 850 851 def setUp(self): 852 # need to overwrite rate to ensure correct type ("method" is not compatible with Func1) 853 self._rate = lambda T: 38.7 * T**2.7 * exp(-3150.15428/T) 854 855 def test_roundtrip(self): 856 # overload default tester for round trip 857 pass 858 859 def test_from_func1(self): 860 # check instantiation from keywords / rate provided as func1 861 f = ct.Func1(self._rate) 862 rxn = ct.CustomReaction(equation=self._equation, rate=f, kinetics=self.gas) 863 self.check_rxn(rxn) 864 865 def test_rate_func(self): 866 # check result of rate expression 867 f = ct.Func1(self._rate) 868 rate = ct.CustomRate(f) 869 self.assertNear(rate(self.gas.T), self.gas.forward_rate_constants[self._index]) 870 871 def test_custom_lambda(self): 872 # check instantiation from keywords / rate provided as lambda function 873 rxn = ct.CustomReaction(equation=self._equation, 874 rate=lambda T: 38.7 * T**2.7 * exp(-3150.15428/T), 875 kinetics=self.gas) 876 self.check_rxn(rxn) 877