From a4184d46767d222f920a9ef0025f8135c29c6886 Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Thu, 20 Jan 2022 14:49:55 -0600 Subject: [PATCH] [UnitTests] Add unit tests for InterfaceKinetics Switch from self.gas to self.kin to accommodate non-gaseous phases --- .../cython/cantera/test/test_reaction.py | 150 ++++++++++++------ 1 file changed, 101 insertions(+), 49 deletions(-) diff --git a/interfaces/cython/cantera/test/test_reaction.py b/interfaces/cython/cantera/test/test_reaction.py index 8275be79080..4cc31949cc9 100644 --- a/interfaces/cython/cantera/test/test_reaction.py +++ b/interfaces/cython/cantera/test/test_reaction.py @@ -71,7 +71,7 @@ def test_duplicate(self): gas1.write_yaml(fname) with self.assertRaisesRegex(Exception, "Undeclared duplicate reactions"): - gas2 = ct.Solution(fname) + ct.Solution(fname) Path(fname).unlink() @@ -676,53 +676,53 @@ class ReactionTests: @classmethod def setUpClass(cls): utilities.CanteraTest.setUpClass() - cls.gas = ct.Solution("kineticsfromscratch.yaml", transport_model=None) - cls.species = cls.gas.species() + cls.kin = ct.Solution("kineticsfromscratch.yaml", transport_model=None) + cls.species = cls.kin.species() def setUp(self): - self.gas.X = "H2:0.1, H2O:0.2, O2:0.7, O:1e-4, OH:1e-5, H:2e-5" - self.gas.TP = 900, 2*ct.one_atm + self.kin.X = "H2:0.1, H2O:0.2, O2:0.7, O:1e-4, OH:1e-5, H:2e-5" + self.kin.TP = 900, 2*ct.one_atm def eval_rate(self, rate): # evaluate rate expression - return rate(self.gas.T) + return rate(self.kin.T) def from_yaml(self, deprecated=False): # create reaction object from yaml if deprecated: with self.assertWarnsRegex(DeprecationWarning, "is renamed to 'from_yaml'"): - return ct.Reaction.fromYaml(self._yaml, kinetics=self.gas) - return ct.Reaction.from_yaml(self._yaml, kinetics=self.gas) + return ct.Reaction.fromYaml(self._yaml, kinetics=self.kin) + return ct.Reaction.from_yaml(self._yaml, kinetics=self.kin) def from_dict(self): # create reaction rate object from input data input_data = self.from_yaml().input_data - return ct.Reaction.from_dict(input_data, kinetics=self.gas) + return ct.Reaction.from_dict(input_data, kinetics=self.kin) def from_rate(self, rate): # create reaction object from keywords / rate - return self._cls(equation=self._equation, rate=rate, kinetics=self.gas, + return self._cls(equation=self._equation, rate=rate, kinetics=self.kin, legacy=self._legacy, **self._kwargs) def from_parts(self): # create reaction rate object from parts - orig = self.gas.reaction(self._index) + orig = self.kin.reaction(self._index) rxn = self._cls(orig.reactants, orig.products, legacy=self._legacy) rxn.rate = self._rate_obj return rxn def check_rate(self, rate_obj): if self._legacy: - rate = rate_obj(self.gas.T) + rate = rate_obj(self.kin.T) else: rate = self.eval_rate(rate_obj) - self.assertNear(rate, self.gas.forward_rate_constants[self._index]) + self.assertNear(rate, self.kin.forward_rate_constants[self._index]) def check_rxn(self, rxn, check_legacy=True): # helper function that checks reaction configuration ix = self._index - self.assertEqual(rxn.reactants, self.gas.reaction(ix).reactants) - self.assertEqual(rxn.products, self.gas.reaction(ix).products) + self.assertEqual(rxn.reactants, self.kin.reaction(ix).reactants) + self.assertEqual(rxn.products, self.kin.reaction(ix).products) if check_legacy: self.assertEqual(rxn.reaction_type, self._type) self.assertEqual(rxn.uses_legacy, self._type.endswith("-legacy")) @@ -731,20 +731,20 @@ def check_rxn(self, rxn, check_legacy=True): if not self._legacy: # legacy rate evaluation is not consistent self.check_rate(rxn.rate) - gas2 = ct.Solution(thermo="IdealGas", kinetics="GasKinetics", + kin2 = ct.Solution(thermo="IdealGas", kinetics="GasKinetics", species=self.species, reactions=[rxn]) - gas2.TPX = self.gas.TPX - self.check_solution(gas2, check_legacy) + kin2.TPX = self.kin.TPX + self.check_solution(kin2, check_legacy) - def check_solution(self, gas2, check_legacy=True): + def check_solution(self, kin2, check_legacy=True): # helper function that checks evaluation of reaction rates ix = self._index if check_legacy: - self.assertEqual(gas2.reaction_type_str(0), self._type) - self.assertNear(gas2.forward_rate_constants[0], - self.gas.forward_rate_constants[ix]) - self.assertNear(gas2.net_rates_of_progress[0], - self.gas.net_rates_of_progress[ix]) + self.assertEqual(kin2.reaction_type_str(0), self._type) + self.assertNear(kin2.forward_rate_constants[0], + self.kin.forward_rate_constants[ix]) + self.assertNear(kin2.net_rates_of_progress[0], + self.kin.net_rates_of_progress[ix]) def test_rate(self): # check consistency of reaction rate and forward rate constant @@ -782,12 +782,12 @@ def test_add_rxn(self): # check adding new reaction to solution if self._rate_obj is None: return - gas2 = ct.Solution(thermo="IdealGas", kinetics="GasKinetics", + kin2 = ct.Solution(thermo="IdealGas", kinetics="GasKinetics", species=self.species, reactions=[]) - gas2.TPX = self.gas.TPX + kin2.TPX = self.kin.TPX rxn = self.from_rate(self._rate_obj) - gas2.add_reaction(rxn) - self.check_solution(gas2) + kin2.add_reaction(rxn) + self.check_solution(kin2) def test_raises_invalid_rate(self): # check exception for instantiation from keywords / invalid rate @@ -806,22 +806,22 @@ def test_no_rate(self): return rxn = self.from_rate(None) if self._legacy: - self.assertNear(rxn.rate(self.gas.T), 0.) + self.assertNear(rxn.rate(self.kin.T), 0.) else: self.assertIsNaN(self.eval_rate(rxn.rate)) - gas2 = ct.Solution(thermo="IdealGas", kinetics="GasKinetics", + kin2 = ct.Solution(thermo="IdealGas", kinetics="GasKinetics", species=self.species, reactions=[rxn]) - gas2.TPX = self.gas.TPX + kin2.TPX = self.kin.TPX if self._legacy: - self.assertNear(gas2.forward_rate_constants[0], 0.) - self.assertNear(gas2.net_rates_of_progress[0], 0.) + self.assertNear(kin2.forward_rate_constants[0], 0.) + self.assertNear(kin2.net_rates_of_progress[0], 0.) elif not ct.debug_mode_enabled(): - self.assertIsNaN(gas2.forward_rate_constants[0]) - self.assertIsNaN(gas2.net_rates_of_progress[0]) + self.assertIsNaN(kin2.forward_rate_constants[0]) + self.assertIsNaN(kin2.net_rates_of_progress[0]) else: with self.assertRaisesRegex(ct.CanteraError, "not finite"): - gas2.net_rates_of_progress + kin2.net_rates_of_progress def test_replace_rate(self): # check replacing reaction rate expression @@ -974,7 +974,7 @@ def test_rate(self): def test_efficiencies(self): # check efficiencies - rxn = self._cls(equation=self._equation, rate=self._rate_obj, kinetics=self.gas, + rxn = self._cls(equation=self._equation, rate=self._rate_obj, kinetics=self.kin, legacy=self._legacy, **self._kwargs) self.assertEqual(rxn.efficiencies, self._kwargs["efficiencies"]) @@ -1037,7 +1037,7 @@ def setUpClass(cls): cls._rate_obj = ct.TwoTempPlasmaRate(**cls._rate) def eval_rate(self, rate): - return rate(self.gas.T, self.gas.Te) + return rate(self.kin.T, self.kin.Te) class TestBlowersMasel(ReactionTests, utilities.CanteraTest): @@ -1060,8 +1060,8 @@ def setUpClass(cls): cls._rate_obj = ct.BlowersMaselRate(**cls._rate) def eval_rate(self, rate): - delta_enthalpy = self.gas.delta_enthalpy[self._index] - return rate(self.gas.T, delta_enthalpy) + delta_enthalpy = self.kin.delta_enthalpy[self._index] + return rate(self.kin.T, delta_enthalpy) class TestTroe2(ReactionTests, utilities.CanteraTest): @@ -1130,8 +1130,8 @@ def setUpClass(cls): cls._rate_obj = ct.TroeRate(low=low, high=high, falloff_coeffs=data) def eval_rate(self, rate): - concm = self.gas.third_body_concentrations[self._index] - return rate(self.gas.T, concm) + concm = self.kin.third_body_concentrations[self._index] + return rate(self.kin.T, concm) def from_parts(self): rxn = ReactionTests.from_parts(self) @@ -1172,8 +1172,8 @@ def setUpClass(cls): cls._rate_obj = ct.LindemannRate(low=low, high=high, falloff_coeffs=[]) def eval_rate(self, rate): - concm = self.gas.third_body_concentrations[self._index] - return rate(self.gas.T, concm) + concm = self.kin.third_body_concentrations[self._index] + return rate(self.kin.T, concm) def from_parts(self): rxn = ReactionTests.from_parts(self) @@ -1237,8 +1237,8 @@ def setUpClass(cls): cls._rate_obj.chemically_activated = True def eval_rate(self, rate): - concm = self.gas.third_body_concentrations[self._index] - return rate(self.gas.T, concm) + concm = self.kin.third_body_concentrations[self._index] + return rate(self.kin.T, concm) class TestPlog2(ReactionTests, utilities.CanteraTest): @@ -1321,7 +1321,7 @@ class TestPlog(TestPlog2): """ def eval_rate(self, rate): - return rate(self.gas.T, self.gas.P) + return rate(self.kin.T, self.kin.P) class TestChebyshev2(ReactionTests, utilities.CanteraTest): @@ -1377,7 +1377,7 @@ class TestChebyshev(TestChebyshev2): """ def eval_rate(self, rate): - return rate(self.gas.T, self.gas.P) + return rate(self.kin.T, self.kin.P) class TestCustom(ReactionTests, utilities.CanteraTest): @@ -1418,9 +1418,61 @@ def test_rate_func(self): # check result of rate expression f = ct.Func1(self._rate) rate = ct.CustomRate(f) - self.assertNear(rate(self.gas.T), self.gas.forward_rate_constants[self._index]) + self.assertNear(rate(self.kin.T), self.kin.forward_rate_constants[self._index]) def test_custom_lambda(self): # check instantiation from keywords / rate provided as lambda function rxn = self.from_rate(lambda T: 38.7 * T**2.7 * exp(-3150.15428/T)) self.check_rxn(rxn) + + +class SurfaceReactionTests(ReactionTests): + # test suite for reaction expressions + + @classmethod + def setUpClass(cls): + utilities.CanteraTest.setUpClass() + cls.kin = ct.Interface("ptcombust.yaml", "Pt_surf", transport_model=None) + cls.gas = cls.kin.adjacent["gas"] + cls.species = cls.kin.species() + + def setUp(self): + self.kin.TP = 900, ct.one_atm + self.gas.X = "H2:0.05, O2:0.21, N2:0.78, AR:0.01" + self.gas.TP = 900, ct.one_atm + self.kin.advance_coverages(1.0) + + +class TestInterfaceReaction(SurfaceReactionTests, utilities.CanteraTest): + + _cls = ct.InterfaceReaction + _equation = "H(S) + O(S) <=> OH(S) + PT(S)" + _rate = {"A": 3.7e+20, "b": 0, "Ea": 1.15e7} + _index = 11 + _type = "interface" + _legacy = True + _yaml = """ + equation: H(S) + O(S) <=> OH(S) + PT(S) + rate-constant: {A: 3.7e+20, b: 0, Ea: 11500 J/mol} + """ + + def check_rxn(self, rxn, check_legacy=True): + # helper function that checks reaction configuration + ix = self._index + self.assertEqual(rxn.reactants, self.kin.reaction(ix).reactants) + self.assertEqual(rxn.products, self.kin.reaction(ix).products) + if check_legacy: + self.assertEqual(rxn.reaction_type, self._type) + #self.assertEqual(rxn.uses_legacy, self._type.endswith("-legacy")) + self.assertEqual(rxn.uses_legacy, self._legacy) + + if not self._legacy: + # legacy rate evaluation is not consistent + self.check_rate(rxn.rate) + kin2 = ct.Interface(thermo="Surface", kinetics="interface", + species=self.species, reactions=[rxn], adjacent=[self.gas]) + + kin2.site_density = self.kin.site_density + kin2.coverages = self.kin.coverages + kin2.TP = self.kin.TP + self.check_solution(kin2, check_legacy)