1 /**
2  *  @file GasKinetics.cpp Homogeneous kinetics in ideal gases
3  */
4 
5 // This file is part of Cantera. See License.txt in the top-level directory or
6 // at https://cantera.org/license.txt for license and copyright information.
7 
8 #include "cantera/kinetics/GasKinetics.h"
9 #include "cantera/thermo/ThermoPhase.h"
10 
11 using namespace std;
12 
13 namespace Cantera
14 {
GasKinetics(ThermoPhase * thermo)15 GasKinetics::GasKinetics(ThermoPhase* thermo) :
16     BulkKinetics(thermo),
17     m_logp_ref(0.0),
18     m_logc_ref(0.0),
19     m_logStandConc(0.0),
20     m_pres(0.0)
21 {
22 }
23 
update_rates_T()24 void GasKinetics::update_rates_T()
25 {
26     double T = thermo().temperature();
27     double P = thermo().pressure();
28     m_logStandConc = log(thermo().standardConcentration());
29     double logT = log(T);
30 
31     if (T != m_temp) {
32         if (!m_rfn.empty()) {
33             m_rates.update(T, logT, m_rfn.data());
34         }
35 
36         if (!m_rfn_low.empty()) {
37             m_falloff_low_rates.update(T, logT, m_rfn_low.data());
38             m_falloff_high_rates.update(T, logT, m_rfn_high.data());
39         }
40         if (!falloff_work.empty()) {
41             m_falloffn.updateTemp(T, falloff_work.data());
42         }
43         updateKc();
44         m_ROP_ok = false;
45         if (m_blowersmasel_rates.nReactions()) {
46             thermo().getPartialMolarEnthalpies(m_grt.data());
47             getReactionDelta(m_grt.data(), m_dH.data());
48             m_blowersmasel_rates.updateBlowersMasel(T, logT, m_rfn.data(), m_dH.data());
49         }
50     }
51 
52     if (T != m_temp || P != m_pres) {
53 
54         // loop over MultiBulkRates evaluators
55         for (auto& rates : m_bulk_rates) {
56             rates->update(thermo(), m_concm.data());
57             rates->getRateConstants(thermo(), m_rfn.data(), m_concm.data());
58         }
59 
60         if (m_plog_rates.nReactions()) {
61             m_plog_rates.update(T, logT, m_rfn.data());
62             m_ROP_ok = false;
63         }
64 
65         if (m_cheb_rates.nReactions()) {
66             m_cheb_rates.update(T, logT, m_rfn.data());
67             m_ROP_ok = false;
68         }
69     }
70     m_pres = P;
71     m_temp = T;
72 }
73 
update_rates_C()74 void GasKinetics::update_rates_C()
75 {
76     thermo().getActivityConcentrations(m_act_conc.data());
77     thermo().getConcentrations(m_phys_conc.data());
78     doublereal ctot = thermo().molarDensity();
79 
80     // 3-body reactions
81     if (!concm_3b_values.empty()) {
82         m_3b_concm.update(m_phys_conc, ctot, concm_3b_values.data());
83     }
84 
85     // Falloff reactions
86     if (!concm_falloff_values.empty()) {
87         m_falloff_concm.update(m_phys_conc, ctot, concm_falloff_values.data());
88     }
89 
90     // Third-body objects interacting with MultiRate evaluator
91     if (!concm_multi_values.empty()) {
92         // using pre-existing third-body handlers requires copying;
93         m_multi_concm.update(m_phys_conc, ctot, concm_multi_values.data());
94         for (size_t i = 0; i < m_multi_indices.size(); i++) {
95             m_concm[m_multi_indices[i]] = concm_multi_values[i];
96         }
97     }
98 
99     // P-log reactions
100     if (m_plog_rates.nReactions()) {
101         double logP = log(thermo().pressure());
102         m_plog_rates.update_C(&logP);
103     }
104 
105     // Chebyshev reactions
106     if (m_cheb_rates.nReactions()) {
107         double log10P = log10(thermo().pressure());
108         m_cheb_rates.update_C(&log10P);
109     }
110 
111     m_ROP_ok = false;
112 }
113 
updateKc()114 void GasKinetics::updateKc()
115 {
116     thermo().getStandardChemPotentials(m_grt.data());
117     fill(m_rkcn.begin(), m_rkcn.end(), 0.0);
118 
119     // compute Delta G^0 for all reversible reactions
120     getRevReactionDelta(m_grt.data(), m_rkcn.data());
121 
122     doublereal rrt = 1.0 / thermo().RT();
123     for (size_t i = 0; i < m_revindex.size(); i++) {
124         size_t irxn = m_revindex[i];
125         m_rkcn[irxn] = std::min(exp(m_rkcn[irxn]*rrt - m_dn[irxn]*m_logStandConc),
126                                 BigNumber);
127     }
128 
129     for (size_t i = 0; i != m_irrev.size(); ++i) {
130         m_rkcn[ m_irrev[i] ] = 0.0;
131     }
132 }
133 
getEquilibriumConstants(doublereal * kc)134 void GasKinetics::getEquilibriumConstants(doublereal* kc)
135 {
136     update_rates_T();
137     thermo().getStandardChemPotentials(m_grt.data());
138     fill(m_rkcn.begin(), m_rkcn.end(), 0.0);
139 
140     // compute Delta G^0 for all reactions
141     getReactionDelta(m_grt.data(), m_rkcn.data());
142 
143     doublereal rrt = 1.0 / thermo().RT();
144     for (size_t i = 0; i < nReactions(); i++) {
145         kc[i] = exp(-m_rkcn[i]*rrt + m_dn[i]*m_logStandConc);
146     }
147 
148     // force an update of T-dependent properties, so that m_rkcn will
149     // be updated before it is used next.
150     m_temp = 0.0;
151 }
152 
processFalloffReactions()153 void GasKinetics::processFalloffReactions()
154 {
155     // use m_ropr for temporary storage of reduced pressure
156     vector_fp& pr = m_ropr;
157 
158     for (size_t i = 0; i < m_falloff_low_rates.nReactions(); i++) {
159         pr[i] = concm_falloff_values[i] * m_rfn_low[i] / (m_rfn_high[i] + SmallNumber);
160         AssertFinite(pr[i], "GasKinetics::processFalloffReactions",
161                      "pr[{}] is not finite.", i);
162     }
163 
164     m_falloffn.pr_to_falloff(pr.data(), falloff_work.data());
165 
166     for (size_t i = 0; i < m_falloff_low_rates.nReactions(); i++) {
167         if (reactionTypeStr(m_fallindx[i]) == "falloff") {
168             pr[i] *= m_rfn_high[i];
169         } else { // CHEMACT_RXN
170             pr[i] *= m_rfn_low[i];
171         }
172         m_ropf[m_fallindx[i]] = pr[i];
173     }
174 }
175 
updateROP()176 void GasKinetics::updateROP()
177 {
178     update_rates_C();
179     update_rates_T();
180     if (m_ROP_ok) {
181         return;
182     }
183 
184     // copy rate coefficients into ropf
185     m_ropf = m_rfn;
186 
187     // multiply ropf by enhanced 3b conc for all 3b rxns
188     if (!concm_3b_values.empty()) {
189         m_3b_concm.multiply(m_ropf.data(), concm_3b_values.data());
190     }
191 
192     if (m_falloff_high_rates.nReactions()) {
193         processFalloffReactions();
194     }
195 
196     // reactions involving third body
197     for (auto& index : m_multi_indices) {
198         m_ropf[index] *= m_concm[index];
199     }
200 
201     for (size_t i = 0; i < nReactions(); i++) {
202         // Scale the forward rate coefficient by the perturbation factor
203         m_ropf[i] *= m_perturb[i];
204         // For reverse rates computed from thermochemistry, multiply the forward
205         // rate coefficients by the reciprocals of the equilibrium constants
206         m_ropr[i] = m_ropf[i] * m_rkcn[i];
207     }
208 
209     // multiply ropf by concentration products
210     m_reactantStoich.multiply(m_act_conc.data(), m_ropf.data());
211 
212     // for reversible reactions, multiply ropr by concentration products
213     m_revProductStoich.multiply(m_act_conc.data(), m_ropr.data());
214 
215     for (size_t j = 0; j != nReactions(); ++j) {
216         m_ropnet[j] = m_ropf[j] - m_ropr[j];
217     }
218 
219     for (size_t i = 0; i < m_rfn.size(); i++) {
220         AssertFinite(m_rfn[i], "GasKinetics::updateROP",
221                      "m_rfn[{}] is not finite.", i);
222         AssertFinite(m_ropf[i], "GasKinetics::updateROP",
223                      "m_ropf[{}] is not finite.", i);
224         AssertFinite(m_ropr[i], "GasKinetics::updateROP",
225                      "m_ropr[{}] is not finite.", i);
226     }
227     m_ROP_ok = true;
228 }
229 
getFwdRateConstants(double * kfwd)230 void GasKinetics::getFwdRateConstants(double* kfwd)
231 {
232     update_rates_C();
233     update_rates_T();
234 
235     // copy rate coefficients into ropf
236     m_ropf = m_rfn;
237 
238     if (legacy_rate_constants_used()) {
239         warn_deprecated("GasKinetics::getFwdRateConstants",
240             "Behavior to change after Cantera 2.6;\nresults will no longer include "
241             "third-body concentrations for three-body reactions.\nTo switch to new "
242             "behavior, use 'cantera.use_legacy_rate_constants(False)' (Python),\n"
243             "'useLegacyRateConstants(0)' (MATLAB), 'Cantera::use_legacy_rate_constants"
244             "(false)' (C++),\nor 'ct_use_legacy_rate_constants(0)' (clib).");
245 
246         // multiply ropf by enhanced 3b conc for all 3b rxns
247         if (!concm_3b_values.empty()) {
248             m_3b_concm.multiply(m_ropf.data(), concm_3b_values.data());
249         }
250 
251         // reactions involving third body
252         for (auto& index : m_multi_indices) {
253             m_ropf[index] *= m_concm[index];
254         }
255     }
256 
257     if (m_falloff_high_rates.nReactions()) {
258         processFalloffReactions();
259     }
260 
261     for (size_t i = 0; i < nReactions(); i++) {
262         // multiply by perturbation factor
263         kfwd[i] = m_ropf[i] * m_perturb[i];
264     }
265 }
266 
addReaction(shared_ptr<Reaction> r,bool resize)267 bool GasKinetics::addReaction(shared_ptr<Reaction> r, bool resize)
268 {
269     // operations common to all reaction types
270     bool added = BulkKinetics::addReaction(r, resize);
271     if (!added) {
272         return false;
273     } else if (!(r->usesLegacy())) {
274         // Rate object already added in BulkKinetics::addReaction
275         return true;
276     }
277 
278     if (r->type() == "elementary-legacy") {
279         addElementaryReaction(dynamic_cast<ElementaryReaction2&>(*r));
280     } else if (r->type() == "three-body-legacy") {
281         addThreeBodyReaction(dynamic_cast<ThreeBodyReaction2&>(*r));
282     } else if (r->type() == "falloff") {
283         addFalloffReaction(dynamic_cast<FalloffReaction&>(*r));
284     } else if (r->type() == "chemically-activated") {
285         addFalloffReaction(dynamic_cast<FalloffReaction&>(*r));
286     } else if (r->type() == "pressure-dependent-Arrhenius-legacy") {
287         addPlogReaction(dynamic_cast<PlogReaction2&>(*r));
288     } else if (r->type() == "Chebyshev-legacy") {
289         addChebyshevReaction(dynamic_cast<ChebyshevReaction2&>(*r));
290     } else if (r->type() == "Blowers-Masel") {
291         addBlowersMaselReaction(dynamic_cast<BlowersMaselReaction&>(*r));
292     } else {
293         throw CanteraError("GasKinetics::addReaction",
294             "Unknown reaction type specified: '{}'", r->type());
295     }
296     return true;
297 }
298 
addFalloffReaction(FalloffReaction & r)299 void GasKinetics::addFalloffReaction(FalloffReaction& r)
300 {
301     // install high and low rate coeff calculators and extend the high and low
302     // rate coeff value vectors
303     size_t nfall = m_falloff_high_rates.nReactions();
304     m_falloff_high_rates.install(nfall, r.high_rate);
305     m_rfn_high.push_back(0.0);
306     m_falloff_low_rates.install(nfall, r.low_rate);
307     m_rfn_low.push_back(0.0);
308 
309     // add this reaction number to the list of falloff reactions
310     m_fallindx.push_back(nReactions()-1);
311     m_rfallindx[nReactions()-1] = nfall;
312 
313     // install the enhanced third-body concentration calculator
314     map<size_t, double> efficiencies;
315     for (const auto& eff : r.third_body.efficiencies) {
316         size_t k = kineticsSpeciesIndex(eff.first);
317         if (k != npos) {
318             efficiencies[k] = eff.second;
319         }
320     }
321     m_falloff_concm.install(nfall, efficiencies,
322                             r.third_body.default_efficiency);
323     concm_falloff_values.resize(m_falloff_concm.workSize());
324 
325     // install the falloff function calculator for this reaction
326     m_falloffn.install(nfall, r.type(), r.falloff);
327     falloff_work.resize(m_falloffn.workSize());
328 }
329 
addThreeBodyReaction(ThreeBodyReaction2 & r)330 void GasKinetics::addThreeBodyReaction(ThreeBodyReaction2& r)
331 {
332     m_rates.install(nReactions()-1, r.rate);
333     map<size_t, double> efficiencies;
334     for (const auto& eff : r.third_body.efficiencies) {
335         size_t k = kineticsSpeciesIndex(eff.first);
336         if (k != npos) {
337             efficiencies[k] = eff.second;
338         }
339     }
340     m_3b_concm.install(nReactions()-1, efficiencies,
341                        r.third_body.default_efficiency);
342     concm_3b_values.resize(m_3b_concm.workSize());
343 }
344 
addPlogReaction(PlogReaction2 & r)345 void GasKinetics::addPlogReaction(PlogReaction2& r)
346 {
347     m_plog_rates.install(nReactions()-1, r.rate);
348 }
349 
addChebyshevReaction(ChebyshevReaction2 & r)350 void GasKinetics::addChebyshevReaction(ChebyshevReaction2& r)
351 {
352     m_cheb_rates.install(nReactions()-1, r.rate);
353 }
354 
addBlowersMaselReaction(BlowersMaselReaction & r)355 void GasKinetics::addBlowersMaselReaction(BlowersMaselReaction& r)
356 {
357     m_blowersmasel_rates.install(nReactions()-1, r.rate);
358 }
359 
modifyReaction(size_t i,shared_ptr<Reaction> rNew)360 void GasKinetics::modifyReaction(size_t i, shared_ptr<Reaction> rNew)
361 {
362     // operations common to all bulk reaction types
363     BulkKinetics::modifyReaction(i, rNew);
364 
365     if (!(rNew->usesLegacy())) {
366         // Rate object already modified in BulkKinetics::modifyReaction
367         return;
368     }
369 
370     if (rNew->type() == "elementary-legacy") {
371         modifyElementaryReaction(i, dynamic_cast<ElementaryReaction2&>(*rNew));
372     } else if (rNew->type() == "three-body-legacy") {
373         modifyThreeBodyReaction(i, dynamic_cast<ThreeBodyReaction2&>(*rNew));
374     } else if (rNew->type() == "falloff") {
375         modifyFalloffReaction(i, dynamic_cast<FalloffReaction&>(*rNew));
376     } else if (rNew->type() == "chemically-activated") {
377         modifyFalloffReaction(i, dynamic_cast<FalloffReaction&>(*rNew));
378     } else if (rNew->type() == "pressure-dependent-Arrhenius-legacy") {
379         modifyPlogReaction(i, dynamic_cast<PlogReaction2&>(*rNew));
380     } else if (rNew->type() == "Chebyshev-legacy") {
381         modifyChebyshevReaction(i, dynamic_cast<ChebyshevReaction2&>(*rNew));
382     } else if (rNew->type() == "Blowers-Masel") {
383         modifyBlowersMaselReaction(i, dynamic_cast<BlowersMaselReaction&>(*rNew));
384     } else {
385         throw CanteraError("GasKinetics::modifyReaction",
386             "Unknown reaction type specified: '{}'", rNew->type());
387     }
388 
389     // invalidate all cached data
390     m_ROP_ok = false;
391     m_temp += 0.1234;
392     m_pres += 0.1234;
393 }
394 
modifyThreeBodyReaction(size_t i,ThreeBodyReaction2 & r)395 void GasKinetics::modifyThreeBodyReaction(size_t i, ThreeBodyReaction2& r)
396 {
397     m_rates.replace(i, r.rate);
398 }
399 
modifyFalloffReaction(size_t i,FalloffReaction & r)400 void GasKinetics::modifyFalloffReaction(size_t i, FalloffReaction& r)
401 {
402     size_t iFall = m_rfallindx[i];
403     m_falloff_high_rates.replace(iFall, r.high_rate);
404     m_falloff_low_rates.replace(iFall, r.low_rate);
405     m_falloffn.replace(iFall, r.falloff);
406 }
407 
modifyPlogReaction(size_t i,PlogReaction2 & r)408 void GasKinetics::modifyPlogReaction(size_t i, PlogReaction2& r)
409 {
410     m_plog_rates.replace(i, r.rate);
411 }
412 
modifyChebyshevReaction(size_t i,ChebyshevReaction2 & r)413 void GasKinetics::modifyChebyshevReaction(size_t i, ChebyshevReaction2& r)
414 {
415     m_cheb_rates.replace(i, r.rate);
416 }
417 
modifyBlowersMaselReaction(size_t i,BlowersMaselReaction & r)418 void GasKinetics::modifyBlowersMaselReaction(size_t i, BlowersMaselReaction& r)
419 {
420     m_blowersmasel_rates.replace(i, r.rate);
421 }
422 
init()423 void GasKinetics::init()
424 {
425     BulkKinetics::init();
426     m_logp_ref = log(thermo().refPressure()) - log(GasConstant);
427 }
428 
invalidateCache()429 void GasKinetics::invalidateCache()
430 {
431     BulkKinetics::invalidateCache();
432     m_pres += 0.13579;
433 }
434 
435 }
436