From 155f4672b2d83f44297d6cb5a5fbf7fa1b14b80d Mon Sep 17 00:00:00 2001 From: Ingmar Schoegl Date: Sun, 28 Mar 2021 19:01:35 -0500 Subject: [PATCH] [Kinetics] make MultiRate and ReactionRate aware of each other --- include/cantera/kinetics/MultiRate.h | 62 +++++++++++----- include/cantera/kinetics/Reaction.h | 16 +--- include/cantera/kinetics/ReactionRate.h | 28 ++++++- interfaces/cython/cantera/_cantera.pxd | 2 + interfaces/cython/cantera/reaction.pyx | 7 ++ .../cython/cantera/test/test_reaction.py | 19 ++++- src/kinetics/BulkKinetics.cpp | 9 ++- src/kinetics/Reaction.cpp | 28 +++---- src/kinetics/ReactionRate.cpp | 74 +++++++++++++------ 9 files changed, 169 insertions(+), 76 deletions(-) diff --git a/include/cantera/kinetics/MultiRate.h b/include/cantera/kinetics/MultiRate.h index 606034d5201..7add7e0640e 100644 --- a/include/cantera/kinetics/MultiRate.h +++ b/include/cantera/kinetics/MultiRate.h @@ -33,13 +33,13 @@ class MultiRateBase //! @param rxn_index index of reaction //! @param rate reaction rate object virtual void add(const size_t rxn_index, - ReactionRateBase& rate) = 0; + shared_ptr rate) = 0; //! Replace reaction rate object handled by the evaluator //! @param rxn_index index of reaction //! @param rate reaction rate object virtual bool replace(const size_t rxn_index, - ReactionRateBase& rate) = 0; + shared_ptr rate) = 0; //! Evaluate all rate constants handled by the evaluator //! @param bulk object representing bulk phase @@ -58,38 +58,61 @@ template class MultiBulkRates final : public MultiRateBase { public: + virtual ~MultiBulkRates() { + for (size_t i = 0; i < m_bases.size(); i++) { + m_bases[i]->releaseEvaluator(); + } + } + + //! Called by destructor of ReactionRate object + void releaseBase(size_t rxn_index) { + size_t j = m_indices[rxn_index]; + m_bases[j] = nullptr; + } + virtual void add(const size_t rxn_index, - ReactionRateBase& rate) override { - if (typeid(rate) != typeid(RateType)) { + shared_ptr rate) override { + if (typeid(*rate) != typeid(RateType)) { + throw CanteraError("MultiBulkRate::add", + "Wrong type: cannot add rate object of type '{}'", + rate->type()); + } else if (rate->linked()) { + // ensure there are no dangling objects (unlikely) throw CanteraError("MultiBulkRate::add", - "Wrong type: cannot add rate object of type '{}' ", - rate.type()); + "Reaction rate is already linked to a reaction rate " + "evaluator. Re-linked reaction rates are not allowed"); } size_t j = m_rates.size(); m_indices[rxn_index] = j; - m_rates.push_back(static_cast(rate)); + m_rates.push_back(static_cast(*rate)); m_rxn.push_back(rxn_index); - // reassign pointer to newly created copy - //rate = std::make_shared(m_rates[j]); + // keep link to original object + m_bases.push_back(rate); } virtual bool replace(const size_t rxn_index, - ReactionRateBase& rate) override { + shared_ptr rate) override { if (!m_rates.size()) { throw CanteraError("MultiBulkRate::replace", "Invalid operation: cannot replace rate object " "in empty rate handler."); - } else if (typeid(rate) != typeid(RateType)) { + } else if (typeid(*rate) != typeid(RateType)) { throw CanteraError("MultiBulkRate::replace", "Invalid operation: cannot replace rate object of type '{}' " "with a new rate of type '{}'.", - m_rates[0].type(), rate.type()); + m_rates[0].type(), rate->type()); + } else if (rate->linked()) { + // ensure there are no dangling objects (unlikely) + throw CanteraError("MultiBulkRate::replace", + "Reaction rate is already linked to a reaction rate " + "evaluator. Re-linked reaction rates are not allowed"); } if (m_indices.find(rxn_index) != m_indices.end()) { size_t j = m_indices[rxn_index]; - m_rates[j] = static_cast(rate); - // reassign pointer to rate object - //rate = std::make_shared(m_rates[j]); + m_rates[j] = static_cast(*rate); + // release evaluator from previously used rate object and update + m_bases[j]->releaseEvaluator(); + m_bases[j] = rate; return true; } return false; @@ -116,9 +139,12 @@ class MultiBulkRates final : public MultiRateBase } protected: - std::vector m_rates; //! Reaction rate objects - std::vector m_rxn; //! Index within overall rate vector - std::map m_indices; //! Mapping of indices + //! Raw pointers to reaction rate objects managed by Reaction object + std::vector> m_bases; + + std::vector m_rates; //!< Reaction rate objects + std::vector m_rxn; //!< Index within overall rate vector + std::map m_indices; //!< Mapping of indices DataType m_shared; }; diff --git a/include/cantera/kinetics/Reaction.h b/include/cantera/kinetics/Reaction.h index c71b5845ae5..13e30a21bdd 100644 --- a/include/cantera/kinetics/Reaction.h +++ b/include/cantera/kinetics/Reaction.h @@ -18,7 +18,6 @@ class Kinetics; class Falloff; class XML_Node; class ThirdBody; -class MultiRateBase; //! Abstract base class which stores data about a reaction and its rate //! parameterization so that it can be added to a Kinetics object. @@ -331,13 +330,8 @@ class Reaction3 : public Reaction //! Set reaction rate pointer void setRate(shared_ptr rate); - //! Link reaction to kinetics object - void linkEvaluator(size_t index, const shared_ptr evaluator); - - //! Indicate whether reaction is linked to a rate evaluator - bool linked() { - return bool(m_evaluator); - } + //! Indicate whether associated reaction rate is linked to a rate evaluator + bool linked(); //! Return index of reaction within the Kinetics object owning the rate //! evaluator. Raises an exception if the reaction is not linked. @@ -360,12 +354,6 @@ class Reaction3 : public Reaction //! Relative efficiencies of third-body species in enhancing the reaction //! rate (if applicable) shared_ptr m_third_body; - - //! Evaluator handling the reaction rate - shared_ptr m_evaluator; - - //! Index of reaction within kinetics object (if applicable) - size_t m_index; }; diff --git a/include/cantera/kinetics/ReactionRate.h b/include/cantera/kinetics/ReactionRate.h index 7daff618047..ace09f7aba1 100644 --- a/include/cantera/kinetics/ReactionRate.h +++ b/include/cantera/kinetics/ReactionRate.h @@ -19,6 +19,7 @@ namespace Cantera { class Func1; +class MultiRateBase; //! Abstract base class for reaction rate definitions @@ -34,6 +35,9 @@ class Func1; */ class ReactionRateBase { +protected: + ReactionRateBase() : m_index(npos) {} + public: virtual ~ReactionRateBase() {} @@ -91,6 +95,26 @@ class ReactionRateBase //! Validate the reaction rate expression virtual void validate(const std::string& equation) = 0; + + //! Indicate whether reaction is linked to a rate evaluator + bool linked() { return bool(m_evaluator); } + + //! Link reaction to MultiRateBase evaluator + void linkEvaluator(size_t index, const shared_ptr evaluator); + + //! Link reaction to MultiRateBase evaluator + void releaseEvaluator(); + + //! Return index of reaction within the Kinetics object owning the rate + //! evaluator. Raises an exception if the reaction is not linked. + size_t index(); + +protected: + //! Evaluator handling the reaction rate + std::shared_ptr m_evaluator; + + //! Index of reaction within kinetics object (if applicable) + size_t m_index; }; @@ -103,7 +127,7 @@ template class ReactionRate : public ReactionRateBase { public: - ReactionRate() = default; + ReactionRate() : ReactionRateBase() {} //! Update information specific to reaction //! @param shared_data data shared by all reactions of a given type @@ -382,7 +406,7 @@ class CustomFunc1Rate final : public ReactionRate //! Constructor does nothing, as there is no formalized parameterization //! @param node AnyMap object containing reaction rate specification //! @param rate_units Description of units used for rate parameters - CustomFunc1Rate(const AnyMap& rate, const Units& rate_units) {} + CustomFunc1Rate(const AnyMap& rate, const Units& rate_units); virtual std::string type() const override { return "custom-function"; } diff --git a/interfaces/cython/cantera/_cantera.pxd b/interfaces/cython/cantera/_cantera.pxd index 00bdf51f298..015549c609f 100644 --- a/interfaces/cython/cantera/_cantera.pxd +++ b/interfaces/cython/cantera/_cantera.pxd @@ -315,6 +315,8 @@ cdef extern from "cantera/kinetics/Reaction.h" namespace "Cantera": cdef cppclass CxxReactionRateBase "Cantera::ReactionRateBase": CxxReactionRateBase() string type() + cbool linked() + void releaseEvaluator() void update(double) except +translate_exception void update(double, double) except +translate_exception double eval(double) except +translate_exception diff --git a/interfaces/cython/cantera/reaction.pyx b/interfaces/cython/cantera/reaction.pyx index 0ad814ae40e..6b16314b243 100644 --- a/interfaces/cython/cantera/reaction.pyx +++ b/interfaces/cython/cantera/reaction.pyx @@ -16,6 +16,13 @@ cdef class _ReactionRate: def __repr__(self): return "<{} at {:0x}>".format(pystr(self.base.type()), id(self)) + property _linked: + def __get__(self): + return self.base.linked() + + def _release(self): + self.base.releaseEvaluator() + def __call__(self, double temperature, pressure=None): if pressure: self.base.update(temperature, pressure) diff --git a/interfaces/cython/cantera/test/test_reaction.py b/interfaces/cython/cantera/test/test_reaction.py index d6cf138658e..583718d25fd 100644 --- a/interfaces/cython/cantera/test/test_reaction.py +++ b/interfaces/cython/cantera/test/test_reaction.py @@ -11,6 +11,7 @@ class TestReactionRate(utilities.CanteraTest): _type = None _uses_pressure = False _index = None + rate = None @classmethod def setUpClass(cls): @@ -41,6 +42,11 @@ def test_rate_TP(self): self.assertNear(self.rate(self.gas.T, self.gas.P), self.gas.forward_rate_constants[self._index]) + def test_linked(self): + if self.rate is None: + return + self.assertFalse(self.rate._linked) + class TestArrheniusRate(TestReactionRate): @@ -145,6 +151,11 @@ def setUpClass(cls): cls.gas.TP = 900, 2*ct.one_atm cls.species = cls.gas.species() + def setUp(self): + if self._rate_obj is None or not self._new: + return + self._rate_obj._release() + def check_rxn(self, rxn): ix = self._index self.assertEqual(rxn.reaction_type, self._type) @@ -154,6 +165,7 @@ def check_rxn(self, rxn): self.assertFalse(rxn._linked) with self.assertRaises(ct.CanteraError): rxn.index + self.assertFalse(rxn.rate._linked) gas2 = ct.Solution(thermo='IdealGas', kinetics='GasKinetics', species=self.species, reactions=[rxn]) @@ -168,6 +180,7 @@ def check_sol(self, gas2, index=None): rxn = self.gas.reaction(index) self.assertTrue(rxn._linked) self.assertEqual(rxn.index, index) + self.assertTrue(rxn.rate._linked) self.assertEqual(gas2.reaction_type_str(index), self._type) self.assertNear(gas2.forward_rate_constants[index], @@ -180,9 +193,11 @@ def test_rate(self): return if 'Rate' in type(self._rate_obj).__name__: self.assertNear(self._rate_obj(self.gas.T, self.gas.P), - self.gas.forward_rate_constants[self._index]) + self.gas.forward_rate_constants[self._index]) else: self.assertNear(self._rate_obj(self.gas.T), self.gas.forward_rate_constants[self._index]) + if self._new: + self.assertFalse(self._rate_obj._linked) def test_from_parts(self): if self._cls is None or not hasattr(self._cls, 'rate'): @@ -256,6 +271,7 @@ def test_replace_reaction(self): rxn = self._cls(equation=self._equation, rate=self._rate, kinetics=self.gas, **self._kwargs) if self._new: self.assertFalse(rxn._linked) + self.assertFalse(rxn.rate._linked) gas2 = ct.Solution('kineticsfromscratch.yaml') gas2.TPX = self.gas.TPX @@ -307,6 +323,7 @@ class TestCustom(TestReaction): def setUp(self): # need to overwrite rate to ensure correct type ('method' is not compatible with Func1) + super().setUp() self._rate = lambda T: 38.7 * T**2.7 * exp(-3150.15428/T) def test_no_rate(self): diff --git a/src/kinetics/BulkKinetics.cpp b/src/kinetics/BulkKinetics.cpp index 6da0fa580da..1350b462e13 100644 --- a/src/kinetics/BulkKinetics.cpp +++ b/src/kinetics/BulkKinetics.cpp @@ -149,8 +149,9 @@ bool BulkKinetics::addReaction(shared_ptr r) // Add reaction rate to evaluator size_t index = m_bulk_types[rate->type()]; - m_bulk_rates[index]->add(nReactions() - 1, *rate); - r3->linkEvaluator(nReactions() - 1, m_bulk_rates[index]); + size_t i = nReactions() - 1; + m_bulk_rates[index]->add(i, rate); + rate->linkEvaluator(i, m_bulk_rates[index]); // Add reaction to third-body evaluator if (r3->thirdBody() != nullptr) { @@ -203,8 +204,8 @@ void BulkKinetics::modifyReaction(size_t i, shared_ptr rNew) // Replace reaction rate to evaluator size_t index = m_bulk_types[rate->type()]; - m_bulk_rates[index]->replace(i, *rate); - r3->linkEvaluator(i, m_bulk_rates[index]); + m_bulk_rates[index]->replace(i, rate); + rate->linkEvaluator(i, m_bulk_rates[index]); } } diff --git a/src/kinetics/Reaction.cpp b/src/kinetics/Reaction.cpp index ee65138cc70..92046a1be16 100644 --- a/src/kinetics/Reaction.cpp +++ b/src/kinetics/Reaction.cpp @@ -9,7 +9,6 @@ #include "cantera/kinetics/ReactionFactory.h" #include "cantera/kinetics/FalloffFactory.h" #include "cantera/kinetics/Kinetics.h" -#include "cantera/kinetics/MultiRate.h" #include "cantera/thermo/ThermoPhase.h" #include "cantera/base/ctml.h" #include "cantera/base/Array.h" @@ -346,38 +345,39 @@ ElectrochemicalReaction::ElectrochemicalReaction(const Composition& reactants_, } Reaction3::Reaction3() - : Reaction() - , m_index(npos) + : Reaction() { } Reaction3::Reaction3(const Composition& reactants, const Composition& products) : Reaction(reactants, products) - , m_index(npos) { } void Reaction3::setRate(shared_ptr rate) { - m_rate = rate; - if (m_evaluator) { - m_evaluator->replace(m_index, *rate); + if (m_rate) { + m_rate->releaseEvaluator(); } + m_rate = rate; } -void Reaction3::linkEvaluator(size_t index, shared_ptr evaluator) +bool Reaction3::linked() { - m_index = index; - m_evaluator = evaluator; + if (m_rate) { + return m_rate->linked(); + } + throw CanteraError("Reaction3::linked", "Not applicable: no " + "associated reaction rate object."); } size_t Reaction3::index() { - if (m_evaluator) { - return m_index; + if (m_rate) { + return m_rate->index(); } - throw CanteraError("Reaction3::index", "Not applicable, as reaction is not " - "linked to Kinetics object with associated rate evaluator"); + throw CanteraError("Reaction3::index", "Not applicable: no " + "associated reaction rate object."); } bool Reaction3::setParameters(const AnyMap& node, const Kinetics& kin) diff --git a/src/kinetics/ReactionRate.cpp b/src/kinetics/ReactionRate.cpp index 8c8105ab1fa..75ee0bd0e36 100644 --- a/src/kinetics/ReactionRate.cpp +++ b/src/kinetics/ReactionRate.cpp @@ -10,27 +10,46 @@ namespace Cantera { -ArrheniusRate::ArrheniusRate() - : Arrhenius() - , allow_negative_pre_exponential_factor(false) -{ +void ReactionRateBase::linkEvaluator(size_t index, + const shared_ptr evaluator) { + m_index = index; + m_evaluator = evaluator; } -ArrheniusRate::ArrheniusRate(double A, double b, double E) - : Arrhenius(A, b, E / GasConstant) - , allow_negative_pre_exponential_factor(false) +void ReactionRateBase::releaseEvaluator() { + m_index = npos; + m_evaluator.reset(); +} + +size_t ReactionRateBase::index() { + if (m_evaluator) { + return m_index; + } + throw CanteraError("ReactionRateBase::index", "Not applicable, as reaction " + "rate is not linked to Kinetics object with assoicated rate evaluator"); } +ArrheniusRate::ArrheniusRate() + : ReactionRate() + , Arrhenius() + , allow_negative_pre_exponential_factor(false) {} + +ArrheniusRate::ArrheniusRate(double A, double b, double E) + : ReactionRate() + , Arrhenius(A, b, E / GasConstant) + , allow_negative_pre_exponential_factor(false) {} + ArrheniusRate::ArrheniusRate(const Arrhenius& arr, bool allow_negative_A) - : Arrhenius(arr.preExponentialFactor(), + : ReactionRate() + , Arrhenius(arr.preExponentialFactor(), arr.temperatureExponent(), arr.activationEnergy_R()) - , allow_negative_pre_exponential_factor(allow_negative_A) -{ -} + , allow_negative_pre_exponential_factor(allow_negative_A) {} -ArrheniusRate::ArrheniusRate(const AnyMap& node, const Units& rate_units) { +ArrheniusRate::ArrheniusRate(const AnyMap& node, const Units& rate_units) + : ReactionRate() +{ setParameters(node, rate_units); } @@ -52,15 +71,17 @@ void ArrheniusRate::validate(const std::string& equation) { } PlogRate::PlogRate() - : Plog() { -} + : ReactionRate() + , Plog() {} PlogRate::PlogRate(const std::multimap& rates) - : Plog(rates) { -} + : ReactionRate() + , Plog(rates) {} PlogRate::PlogRate(const AnyMap& node, const Units& rate_units) - : Plog() { + : ReactionRate() + , Plog() +{ setParameters(node, rate_units); } @@ -74,16 +95,18 @@ bool PlogRate::setParameters(const AnyMap& node, const Units& rate_units) { } ChebyshevRate3::ChebyshevRate3() - : Chebyshev() { -} + : ReactionRate() + , Chebyshev() {} ChebyshevRate3::ChebyshevRate3(double Tmin, double Tmax, double Pmin, double Pmax, const Array2D& coeffs) - : Chebyshev(Tmin, Tmax, Pmin, Pmax, coeffs) { -} + : ReactionRate() + , Chebyshev(Tmin, Tmax, Pmin, Pmax, coeffs) {} ChebyshevRate3::ChebyshevRate3(const AnyMap& node, const Units& rate_units) - : Chebyshev() { + : ReactionRate() + , Chebyshev() +{ setParameters(node, rate_units); } @@ -98,7 +121,12 @@ bool ChebyshevRate3::setParameters(const AnyMap& node, const Units& rate_units) void ChebyshevRate3::validate(const std::string& equation) { } -CustomFunc1Rate::CustomFunc1Rate() : m_ratefunc(0) {} +CustomFunc1Rate::CustomFunc1Rate() + : ReactionRate() + , m_ratefunc(0) {} + +CustomFunc1Rate::CustomFunc1Rate(const AnyMap& rate, const Units& rate_units) + : ReactionRate() {} void CustomFunc1Rate::setRateFunction(shared_ptr f) { m_ratefunc = f;