From 5647d386fc250fa9c1b53689357303398efae82a Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Fri, 3 Dec 2021 20:35:36 -0500 Subject: [PATCH 1/2] [Kinetics] Implement conditional updates for reactions using MultiRate Updating the "shared data" for a rate type returns flags indicating whether the "update" or "eval" methods for each reaction of that type need to be called to get new values of the rate constant. --- include/cantera/kinetics/MultiRate.h | 12 +++- include/cantera/kinetics/MultiRateBase.h | 3 +- include/cantera/kinetics/ReactionData.h | 65 ++++++++++++++++---- src/kinetics/GasKinetics.cpp | 6 +- src/kinetics/ReactionData.cpp | 76 +++++++++++++++++++----- src/thermo/Phase.cpp | 1 + 6 files changed, 131 insertions(+), 32 deletions(-) diff --git a/include/cantera/kinetics/MultiRate.h b/include/cantera/kinetics/MultiRate.h index 0b61ff65f5..d615cfbb3a 100644 --- a/include/cantera/kinetics/MultiRate.h +++ b/include/cantera/kinetics/MultiRate.h @@ -35,6 +35,7 @@ class MultiBulkRate final : public MultiRateBase virtual void add(const size_t rxn_index, ReactionRate& rate) override { m_indices[rxn_index] = m_rxn_rates.size(); m_rxn_rates.emplace_back(rxn_index, dynamic_cast(rate)); + m_shared.invalidateCache(); } virtual bool replace(const size_t rxn_index, ReactionRate& rate) override { @@ -48,6 +49,7 @@ class MultiBulkRate final : public MultiRateBase "Invalid operation: cannot replace rate object of type '{}' " "with a new rate of type '{}'.", type(), rate.type()); } + m_shared.invalidateCache(); if (m_indices.find(rxn_index) != m_indices.end()) { size_t j = m_indices[rxn_index]; m_rxn_rates.at(j).second = dynamic_cast(rate); @@ -58,6 +60,7 @@ class MultiBulkRate final : public MultiRateBase virtual void resize(size_t n_species, size_t n_reactions) override { m_shared.resize(n_species, n_reactions); + m_shared.invalidateCache(); } virtual void getRateConstants(double* kf) override { @@ -78,10 +81,13 @@ class MultiBulkRate final : public MultiRateBase _updateRates(); } - virtual void update(const ThermoPhase& bulk, const Kinetics& kin) override { + virtual bool update(const ThermoPhase& bulk, const Kinetics& kin) override { // update common data once for each reaction type - m_shared.update(bulk, kin); - _updateRates(); + std::pair changed = m_shared.update(bulk, kin); + if (changed.first) { + _updateRates(); + } + return changed.second; } virtual double evalSingle(ReactionRate& rate) override diff --git a/include/cantera/kinetics/MultiRateBase.h b/include/cantera/kinetics/MultiRateBase.h index ed94b5856a..1c534e987a 100644 --- a/include/cantera/kinetics/MultiRateBase.h +++ b/include/cantera/kinetics/MultiRateBase.h @@ -67,7 +67,8 @@ class MultiRateBase //! Update data common to reaction rates of a specific type //! @param bulk object representing bulk phase //! @param kin object representing kinetics - virtual void update(const ThermoPhase& bulk, const Kinetics& kin) = 0; + //! @returns flag indicating reaction rates need to be re-evaluated + virtual bool update(const ThermoPhase& bulk, const Kinetics& kin) = 0; //! Get the rate for a single reaction. Used to implement ReactionRate::eval. virtual double evalSingle(ReactionRate& rate) = 0; diff --git a/include/cantera/kinetics/ReactionData.h b/include/cantera/kinetics/ReactionData.h index 3fb640823f..63f411f5fa 100644 --- a/include/cantera/kinetics/ReactionData.h +++ b/include/cantera/kinetics/ReactionData.h @@ -37,11 +37,23 @@ struct ArrheniusData void update(double T, double P) { update(T); } //! Update data container based on *bulk* phase state - void update(const ThermoPhase& bulk, const Kinetics& kin); + //! @returns A pair where the first element indicates whether the `updateFromStruct` + //! function for individual reactions needs to be called, and the second + //! element indicates whether the `evalFromStruct` method needs to be called + //! (assuming previously-calculated values were cached) + std::pair update(const ThermoPhase& bulk, const Kinetics& kin); //! Update number of species and reactions; unused void resize(size_t n_species, size_t n_reactions) {} + //! Force shared data and reaction rates to be updated next time. This is called by + //! functions that change quantities affecting rate calculations that are normally + //! assumed to be constant, like the reaction rate parameters or the number of + //! reactions. + void invalidateCache() { + temperature = NAN; + } + double temperature; //!< temperature double logT; //!< logarithm of temperature double recipT; //!< inverse of temperature @@ -55,8 +67,7 @@ struct ArrheniusData */ struct BlowersMaselData { - BlowersMaselData() - : temperature(1.), logT(0.), recipT(1.), finalized(false) {} + BlowersMaselData(); //! Update data container based on temperature *T* void update(double T); @@ -67,7 +78,7 @@ struct BlowersMaselData } //! Update data container based on *bulk* phase state and *kin* kinetics - void update(const ThermoPhase& bulk, const Kinetics& kin); + std::pair update(const ThermoPhase& bulk, const Kinetics& kin); //! Finalize setup void resize(size_t n_species, size_t n_reactions) { @@ -76,9 +87,16 @@ struct BlowersMaselData finalized = true; } + void invalidateCache() { + temperature = NAN; + } + double temperature; //!< temperature double logT; //!< logarithm of temperature double recipT; //!< inverse of temperature + double density; //!< used to determine if updates are needed + int state_mf_number; //!< integer that is incremented when composition changes + bool finalized; //!< boolean indicating whether vectors are accessible vector_fp dH; //!< enthalpy change for each reaction @@ -95,10 +113,10 @@ struct BlowersMaselData */ struct FalloffData : public ArrheniusData { - FalloffData() : finalized(false) {} + FalloffData() : finalized(false), molar_density(NAN), state_mf_number(-1) {} //! Update data container based on *bulk* phase state and *kin* kinetics - void update(const ThermoPhase& bulk, const Kinetics& kin); + std::pair update(const ThermoPhase& bulk, const Kinetics& kin); using ArrheniusData::update; //! Finalize setup @@ -107,8 +125,15 @@ struct FalloffData : public ArrheniusData finalized = true; } + void invalidateCache() { + ArrheniusData::invalidateCache(); + molar_density = NAN; + } + bool finalized; //!< boolean indicating whether vectors are accessible vector_fp conc_3b; //!< vector of effective third-body concentrations + double molar_density; //!< used to determine if updates are needed + int state_mf_number; //!< integer that is incremented when composition changes }; @@ -119,7 +144,7 @@ struct FalloffData : public ArrheniusData */ struct PlogData { - PlogData() : temperature(1.), logT(0.), recipT(1.), logP(0.) {} + PlogData() : temperature(1.), logT(0.), recipT(1.), pressure(NAN), logP(0.) {} //! Update data container based on temperature *T* (raises exception) void update(double T); @@ -129,18 +154,25 @@ struct PlogData temperature = T; logT = std::log(T); recipT = 1./T; + pressure = P; logP = std::log(P); } //! Update data container based on *bulk* phase state - void update(const ThermoPhase& bulk, const Kinetics& kin); + std::pair update(const ThermoPhase& bulk, const Kinetics& kin); //! Update number of species and reactions; unused void resize(size_t n_species, size_t n_reactions) {} + void invalidateCache() { + temperature = NAN; + pressure = NAN; + } + double temperature; //!< temperature double logT; //!< logarithm of temperature double recipT; //!< inverse of temperature + double pressure; //!< Pressure [Pa] double logP; //!< logarithm of pressure }; @@ -152,7 +184,7 @@ struct PlogData */ struct ChebyshevData { - ChebyshevData() : temperature(1.), recipT(1.), log10P(0.) {} + ChebyshevData() : temperature(1.), recipT(1.), pressure(NAN), log10P(0.) {} //! Update data container based on temperature *T* (raises exception) void update(double T); @@ -162,17 +194,24 @@ struct ChebyshevData { temperature = T; recipT = 1./T; + pressure = P; log10P = std::log10(P); } //! Update data container based on *bulk* phase state - void update(const ThermoPhase& bulk, const Kinetics& kin); + std::pair update(const ThermoPhase& bulk, const Kinetics& kin); //! Update number of species and reactions; unused void resize(size_t n_species, size_t n_reactions) {} + void invalidateCache() { + temperature = NAN; + pressure = NAN; + } + double temperature; //!< temperature double recipT; //!< inverse of temperature + double pressure; //!< Pressure [Pa] double log10P; //!< base 10 logarithm of pressure }; @@ -189,11 +228,15 @@ struct CustomFunc1Data void update(double T, double P) { update(T); } //! Update data container based on *bulk* phase state - void update(const ThermoPhase& bulk, const Kinetics& kin); + std::pair update(const ThermoPhase& bulk, const Kinetics& kin); //! Update number of species and reactions; unused void resize(size_t n_species, size_t n_reactions) {} + void invalidateCache() { + temperature = NAN; + } + double temperature; //!< temperature }; diff --git a/src/kinetics/GasKinetics.cpp b/src/kinetics/GasKinetics.cpp index 6d80eff5fb..f81faf9ee3 100644 --- a/src/kinetics/GasKinetics.cpp +++ b/src/kinetics/GasKinetics.cpp @@ -60,8 +60,10 @@ void GasKinetics::update_rates_T() // KineticsAddSpecies - add_species_sequential) // a work-around is to call GasKinetics::invalidateCache() for (auto& rates : m_bulk_rates) { - rates->update(thermo(), *this); - rates->getRateConstants(m_rfn.data()); + bool changed = rates->update(thermo(), *this); + if (changed) { + rates->getRateConstants(m_rfn.data()); + } } // P-log reactions (legacy) diff --git a/src/kinetics/ReactionData.cpp b/src/kinetics/ReactionData.cpp index 1698a2d314..960e1194d0 100644 --- a/src/kinetics/ReactionData.cpp +++ b/src/kinetics/ReactionData.cpp @@ -11,9 +11,13 @@ namespace Cantera { -void ArrheniusData::update(const ThermoPhase& bulk, const Kinetics& kin) +std::pair ArrheniusData::update(const ThermoPhase& bulk, + const Kinetics& kin) { - update(bulk.temperature()); + double T = bulk.temperature(); + std::pair changed{ false, T != temperature }; + update(T); + return changed; } void BlowersMaselData::update(double T) @@ -23,17 +27,48 @@ void BlowersMaselData::update(double T) recipT = 1./T; } -void BlowersMaselData::update(const ThermoPhase& bulk, const Kinetics& kin) +BlowersMaselData::BlowersMaselData() + : temperature(1.) + , logT(0.) + , recipT(1.) + , density(NAN) + , state_mf_number(-1) + , finalized(false) { - update(bulk.temperature()); - bulk.getPartialMolarEnthalpies(m_grt.data()); - kin.getReactionDelta(m_grt.data(), dH.data()); } -void FalloffData::update(const ThermoPhase& bulk, const Kinetics& kin) +std::pair BlowersMaselData::update(const ThermoPhase& bulk, + const Kinetics& kin) { - update(bulk.temperature()); - kin.getThirdBodyConcentrations(conc_3b.data()); + double rho = bulk.density(); + int mf = bulk.stateMFNumber(); + double T = bulk.temperature(); + std::pair changed { false, T != temperature }; + if (T != temperature || rho != density || mf != state_mf_number) { + density = rho; + state_mf_number = mf; + bulk.getPartialMolarEnthalpies(m_grt.data()); + kin.getReactionDelta(m_grt.data(), dH.data()); + changed.first = changed.second = true; + } + update(T); + return changed; +} + +std::pair FalloffData::update(const ThermoPhase& bulk, const Kinetics& kin) +{ + double rho_m = bulk.molarDensity(); + int mf = bulk.stateMFNumber(); + double T = bulk.temperature(); + std::pair changed { false, T != temperature }; + if (rho_m != molar_density || mf != state_mf_number) { + molar_density = rho_m; + state_mf_number = mf; + kin.getThirdBodyConcentrations(conc_3b.data()); + changed.first = changed.second = true; + } + update(T); + return changed; } void PlogData::update(double T) @@ -42,9 +77,13 @@ void PlogData::update(double T) "Missing state information: reaction type requires pressure."); } -void PlogData::update(const ThermoPhase& bulk, const Kinetics& kin) +std::pair PlogData::update(const ThermoPhase& bulk, const Kinetics& kin) { - update(bulk.temperature(), bulk.pressure()); + double T = bulk.temperature(); + double P = bulk.pressure(); + std::pair changed{ P != pressure, P != pressure || T != temperature }; + update(T, P); + return changed; } void ChebyshevData::update(double T) @@ -53,14 +92,21 @@ void ChebyshevData::update(double T) "Missing state information: reaction type requires pressure."); } -void ChebyshevData::update(const ThermoPhase& bulk, const Kinetics& kin) +std::pair ChebyshevData::update(const ThermoPhase& bulk, const Kinetics& kin) { - update(bulk.temperature(), bulk.pressure()); + double T = bulk.temperature(); + double P = bulk.pressure(); + std::pair changed{ P != pressure, P != pressure || T != temperature }; + update(T, P); + return changed; } -void CustomFunc1Data::update(const ThermoPhase& bulk, const Kinetics& kin) +std::pair CustomFunc1Data::update(const ThermoPhase& bulk, const Kinetics& kin) { - temperature = bulk.temperature(); + double T = bulk.temperature(); + std::pair changed { false, T != temperature }; + temperature = T; + return changed; } } diff --git a/src/thermo/Phase.cpp b/src/thermo/Phase.cpp index 9a21d8d58b..c729b3c0a1 100644 --- a/src/thermo/Phase.cpp +++ b/src/thermo/Phase.cpp @@ -983,6 +983,7 @@ bool Phase::ready() const } void Phase::invalidateCache() { + m_stateNum++; m_cache.clear(); } From 73db64aa306f8c83b2efe6a29fe3a7b0e2aaaef5 Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Fri, 3 Dec 2021 21:18:25 -0500 Subject: [PATCH 2/2] [Kinetics] Fix cache invalidation corner cases --- src/kinetics/GasKinetics.cpp | 21 ++++++++------------- test/kinetics/kineticsFromScratch.cpp | 3 --- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/kinetics/GasKinetics.cpp b/src/kinetics/GasKinetics.cpp index f81faf9ee3..787c693cf8 100644 --- a/src/kinetics/GasKinetics.cpp +++ b/src/kinetics/GasKinetics.cpp @@ -52,20 +52,15 @@ void GasKinetics::update_rates_T() m_ROP_ok = false; } - if (T != m_temp || P != m_pres) { - - // loop over MultiBulkRates evaluators - // @todo ... address/reassess logic as this update can fail - // (see tests/kinetics/KineticsFromScratch.cpp: - // KineticsAddSpecies - add_species_sequential) - // a work-around is to call GasKinetics::invalidateCache() - for (auto& rates : m_bulk_rates) { - bool changed = rates->update(thermo(), *this); - if (changed) { - rates->getRateConstants(m_rfn.data()); - } + // loop over MultiBulkRate evaluators for each reaction type + for (auto& rates : m_bulk_rates) { + bool changed = rates->update(thermo(), *this); + if (changed) { + rates->getRateConstants(m_rfn.data()); + m_ROP_ok = false; } - + } + if (T != m_temp || P != m_pres) { // P-log reactions (legacy) if (m_plog_rates.nReactions()) { m_plog_rates.update(T, logT, m_rfn.data()); diff --git a/test/kinetics/kineticsFromScratch.cpp b/test/kinetics/kineticsFromScratch.cpp index e2c9d2a016..7870809b52 100644 --- a/test/kinetics/kineticsFromScratch.cpp +++ b/test/kinetics/kineticsFromScratch.cpp @@ -427,9 +427,6 @@ class KineticsAddSpecies : public testing::Test p.setState_TPX(1200, 5*OneAtm, X); p_ref.setState_TPX(1200, 5*OneAtm, X); - // need to invalidate cache to force update - kin_ref->invalidateCache(); - vector_fp k(kin.nReactions()), k_ref(kin_ref->nReactions()); vector_fp w(kin.nTotalSpecies()), w_ref(kin_ref->nTotalSpecies());