Cantera  4.0.0a1
Loading...
Searching...
No Matches
Reactor.cpp
Go to the documentation of this file.
1//! @file Reactor.cpp A zero-dimensional reactor
2
3// This file is part of Cantera. See License.txt in the top-level directory or
4// at https://cantera.org/license.txt for license and copyright information.
5
16#include "cantera/numerics/eigen_dense.h"
17
18#include <boost/math/tools/roots.hpp>
19
20using namespace std;
21namespace bmt = boost::math::tools;
22
23namespace Cantera
24{
25
26Reactor::Reactor(shared_ptr<Solution> sol, const string& name)
27 : Reactor(sol, true, name)
28{
29}
30
31Reactor::Reactor(shared_ptr<Solution> sol, bool clone, const string& name)
32 : ReactorBase(sol, clone, name)
33{
34 m_kin = m_solution->kinetics().get();
35 setChemistryEnabled(m_kin->nReactions() > 0);
36 m_vol = 1.0; // By default, the volume is set to 1.0 m^3.
37 m_sdot.resize(m_nsp, 0.0);
38 m_wdot.resize(m_nsp, 0.0);
39 m_nv = 3 + m_nsp; // mass, volume, internal energy, and species mass fractions
40}
41
42void Reactor::setDerivativeSettings(AnyMap& settings)
43{
44 m_kin->setDerivativeSettings(settings);
45 bool force = settings.empty();
46 if (force || settings.hasKey("skip-flow-devices")) {
47 m_jac_skip_flow_devices = settings.getBool("skip-flow-devices", false);
48 }
49 if (force || settings.hasKey("skip-walls")) {
50 m_jac_skip_walls = settings.getBool("skip-walls", false);
51 }
52 if (force || settings.hasKey("skip-connector-composition-dependence")) {
53 m_jac_skip_connector_composition_dependence =
54 settings.getBool("skip-connector-composition-dependence", false);
55 }
56 if (force || settings.hasKey("skip-connector-pressure-composition-dependence")) {
57 m_jac_skip_connector_pressure_composition_dependence =
58 settings.getBool("skip-connector-pressure-composition-dependence", false);
59 }
60}
61
62void Reactor::getState(span<double> y)
63{
64 // set the first component to the total mass
65 m_mass = m_thermo->density() * m_vol;
66 y[0] = m_mass;
67
68 // set the second component to the total volume
69 y[1] = m_vol;
70
71 // set the third component to the total internal energy
72 y[2] = m_thermo->intEnergy_mass() * m_mass;
73
74 // set components y+3 ... y+K+2 to the mass fractions of each species
75 m_thermo->getMassFractions(y.subspan(3, m_nsp));
76}
77
78void Reactor::initialize(double t0)
79{
80 if (!m_thermo || (m_chem && !m_kin)) {
81 throw CanteraError("Reactor::initialize", "Reactor contents not set"
82 " for reactor '" + m_name + "'.");
83 }
84 updateConnected(true);
85
86 for (size_t n = 0; n < m_wall.size(); n++) {
87 WallBase* W = m_wall[n];
88 W->initialize();
89 }
90}
91
92void Reactor::updateState(span<const double> y)
93{
94 // The components of y are [0] the total mass, [1] the total volume,
95 // [2] the total internal energy, [3...K+3] are the mass fractions of each
96 // species, and [K+3...] are the coverages of surface species on each wall.
97 m_mass = y[0];
98 m_vol = y[1];
99 m_thermo->setMassFractions_NoNorm(y.subspan(3, m_nsp));
100
101 if (m_energy) {
102 double U = y[2];
103 // Residual function: error in internal energy as a function of T
104 auto u_err = [this, U](double T) {
105 m_thermo->setState_TD(T, m_mass / m_vol);
106 return m_thermo->intEnergy_mass() * m_mass - U;
107 };
108
109 double T = m_thermo->temperature();
110 boost::uintmax_t maxiter = 100;
111 pair<double, double> TT;
112 try {
113 TT = bmt::bracket_and_solve_root(
114 u_err, T, 1.2, true, bmt::eps_tolerance<double>(48), maxiter);
115 } catch (std::exception&) {
116 // Try full-range bisection if bracketing fails (for example, near
117 // temperature limits for the phase's equation of state)
118 try {
119 TT = bmt::bisect(u_err, m_thermo->minTemp(), m_thermo->maxTemp(),
120 bmt::eps_tolerance<double>(48), maxiter);
121 } catch (std::exception& err2) {
122 // Set m_thermo back to a reasonable state if root finding fails
123 m_thermo->setState_TD(T, m_mass / m_vol);
124 throw CanteraError("Reactor::updateState",
125 "{}\nat U = {}, rho = {}", err2.what(), U, m_mass / m_vol);
126 }
127 }
128 if (fabs(TT.first - TT.second) > 1e-7*TT.first) {
129 throw CanteraError("Reactor::updateState", "root finding failed");
130 }
131 m_thermo->setState_TD(TT.second, m_mass / m_vol);
132 } else {
133 m_thermo->setDensity(m_mass/m_vol);
134 }
135
136 updateConnected(true);
137}
138
139void Reactor::eval(double time, span<double> LHS, span<double> RHS)
140{
141 double& dmdt = RHS[0];
142 auto mdYdt = RHS.subspan(3); // mass * dY/dt
143
144 evalWalls(time);
145 updateSurfaceProductionRates();
146 auto mw = m_thermo->molecularWeights();
147 auto Y = m_thermo->massFractions();
148
149 // mass added to gas phase from surface reactions
150 double mdot_surf = dot(m_sdot.begin(), m_sdot.end(), mw.begin());
151 dmdt = mdot_surf;
152
153 // volume equation
154 RHS[1] = m_vdot;
155
156 if (m_chem) {
157 m_kin->getNetProductionRates(m_wdot); // "omega dot"
158 }
159
160 for (size_t k = 0; k < m_nsp; k++) {
161 // production in gas phase and from surfaces
162 mdYdt[k] = (m_wdot[k] * m_vol + m_sdot[k]) * mw[k];
163 // dilution by net surface mass flux
164 mdYdt[k] -= Y[k] * mdot_surf;
165 LHS[k+3] = m_mass;
166 }
167
168 // Energy equation.
169 // @f[
170 // \dot U = -P\dot V + A \dot q + \dot m_{in} h_{in} - \dot m_{out} h.
171 // @f]
172 if (m_energy) {
173 RHS[2] = - m_thermo->pressure() * m_vdot + m_Qdot;
174 RHS[2] += m_thermo->intrinsicHeating() * m_vol;
175 } else {
176 RHS[2] = 0.0;
177 }
178
179 // add terms for outlets
180 for (auto outlet : m_outlet) {
181 double mdot = outlet->massFlowRate();
182 dmdt -= mdot; // mass flow out of system
183 if (m_energy) {
184 RHS[2] -= mdot * m_enthalpy;
185 }
186 }
187
188 // add terms for inlets
189 for (auto inlet : m_inlet) {
190 double mdot = inlet->massFlowRate();
191 dmdt += mdot; // mass flow into system
192 for (size_t n = 0; n < m_nsp; n++) {
193 double mdot_spec = inlet->outletSpeciesMassFlowRate(n);
194 // flow of species into system and dilution by other species
195 mdYdt[n] += (mdot_spec - mdot * Y[n]);
196 }
197 if (m_energy) {
198 RHS[2] += mdot * inlet->enthalpy_mass();
199 }
200 }
201}
202
203void Reactor::evalSteady(double time, span<double> LHS, span<double> RHS)
204{
205 eval(time, LHS, RHS);
206 RHS[1] = m_vol - m_initialVolume;
207}
208
209void Reactor::evalWalls(double t)
210{
211 // time is currently unused
212 m_vdot = 0.0;
213 m_Qdot = 0.0;
214 for (size_t i = 0; i < m_wall.size(); i++) {
215 int f = 2 * m_lr[i] - 1;
216 m_vdot -= f * m_wall[i]->expansionRate();
217 m_Qdot += f * m_wall[i]->heatRate();
218 }
219}
220
221vector<size_t> Reactor::initializeSteady()
222{
223 if (!energyEnabled()) {
224 throw CanteraError("Reactor::initializeSteady", "Steady state solver cannot"
225 " be used with {0} when energy equation is disabled."
226 "\nConsider using IdealGas{0} instead.\n"
227 "See https://github.com/Cantera/enhancements/issues/234", type());
228 }
229 m_initialVolume = m_vol;
230 return {1}; // volume
231}
232
233Eigen::SparseMatrix<double> Reactor::finiteDifferenceJacobian()
234{
235 vector<Eigen::Triplet<double>> trips;
236 Eigen::ArrayXd yCurrent(m_nv);
237 getState(asSpan(yCurrent));
238 double time = (m_net != nullptr) ? m_net->time() : 0.0;
239
240 Eigen::ArrayXd yPerturbed = yCurrent;
241 Eigen::ArrayXd lhsPerturbed(m_nv), lhsCurrent(m_nv);
242 Eigen::ArrayXd rhsPerturbed(m_nv), rhsCurrent(m_nv);
243 lhsCurrent = 1.0;
244 rhsCurrent = 0.0;
245 updateState(asSpan(yCurrent));
246 eval(time, asSpan(lhsCurrent), asSpan(rhsCurrent));
247
248 double rel_perturb = std::sqrt(std::numeric_limits<double>::epsilon());
249 double atol = (m_net != nullptr) ? m_net->atol() : 1e-15;
250
251 for (size_t j = 0; j < m_nv; j++) {
252 yPerturbed = yCurrent;
253 double delta_y = std::max(std::abs(yCurrent[j]), 1000 * atol) * rel_perturb;
254 yPerturbed[j] += delta_y;
255
256 updateState(asSpan(yPerturbed));
257 lhsPerturbed = 1.0;
258 rhsPerturbed = 0.0;
259 eval(time, asSpan(lhsPerturbed), asSpan(rhsPerturbed));
260
261 // d ydot_i/dy_j
262 for (size_t i = 0; i < m_nv; i++) {
263 double ydotPerturbed = rhsPerturbed[i] / lhsPerturbed[i];
264 double ydotCurrent = rhsCurrent[i] / lhsCurrent[i];
265 if (ydotCurrent != ydotPerturbed) {
266 trips.emplace_back(static_cast<int>(i), static_cast<int>(j),
267 (ydotPerturbed - ydotCurrent) / delta_y);
268 }
269 }
270 }
271 updateState(asSpan(yCurrent));
272
273 Eigen::SparseMatrix<double> jac(m_nv, m_nv);
274 jac.setFromTriplets(trips.begin(), trips.end());
275 return jac;
276}
277
278void Reactor::addSensitivityReaction(size_t rxn)
279{
280 if (!m_chem || rxn >= m_kin->nReactions()) {
281 throw CanteraError("Reactor::addSensitivityReaction",
282 "Reaction number out of range ({})", rxn);
283 }
284
285 size_t p = network().registerSensitivityParameter(
286 name()+": "+m_kin->reaction(rxn)->equation(), 1.0, 1.0);
287 m_sensParams.emplace_back(
288 SensitivityParameter{rxn, p, 1.0, SensParameterType::reaction});
289}
290
291void Reactor::addSensitivitySpeciesEnthalpy(size_t k)
292{
293 if (k >= m_thermo->nSpecies()) {
294 throw CanteraError("Reactor::addSensitivitySpeciesEnthalpy",
295 "Species index out of range ({})", k);
296 }
297
298 size_t p = network().registerSensitivityParameter(
299 name() + ": " + m_thermo->speciesName(k) + " enthalpy",
300 0.0, GasConstant * 298.15);
301 m_sensParams.emplace_back(
302 SensitivityParameter{k, p, m_thermo->Hf298SS(k),
303 SensParameterType::enthalpy});
304}
305
306void Reactor::updateSurfaceProductionRates()
307{
308 m_sdot.assign(m_nsp, 0.0);
309 for (auto& S : m_surfaces) {
310 const auto& sdot = S->surfaceProductionRates();
311 size_t offset = S->kinetics()->kineticsSpeciesIndex(m_thermo->speciesName(0));
312 for (size_t k = 0; k < m_nsp; k++) {
313 m_sdot[k] += sdot[offset + k] * S->area();
314 }
315 }
316}
317
318size_t Reactor::componentIndex(const string& nm) const
319{
320 if (nm == "mass") {
321 return 0;
322 }
323 if (nm == "volume") {
324 return 1;
325 }
326 if (nm == "int_energy") {
327 return 2;
328 }
329 try {
330 return m_thermo->speciesIndex(nm) + 3;
331 } catch (const CanteraError&) {
332 throw CanteraError("Reactor::componentIndex",
333 "Component '{}' not found", nm);
334 }
335}
336
337string Reactor::componentName(size_t k) {
338 if (k == 0) {
339 return "mass";
340 } else if (k == 1) {
341 return "volume";
342 } else if (k == 2) {
343 return "int_energy";
344 } else if (k >= 3 && k < neq()) {
345 return m_thermo->speciesName(k - 3);
346 }
347 throw IndexError("Reactor::componentName", "component", k, m_nv);
348}
349
350double Reactor::upperBound(size_t k) const {
351 if (k == 0) {
352 return BigNumber; // mass
353 } else if (k == 1) {
354 return BigNumber; // volume
355 } else if (k == 2) {
356 return BigNumber; // internal energy
357 } else if (k >= 3 && k < m_nv) {
358 return 1.0; // species mass fraction or surface coverage
359 } else {
360 throw CanteraError("Reactor::upperBound", "Index {} is out of bounds.", k);
361 }
362}
363
364double Reactor::lowerBound(size_t k) const {
365 if (k == 0) {
366 return 0; // mass
367 } else if (k == 1) {
368 return 0; // volume
369 } else if (k == 2) {
370 return -BigNumber; // internal energy
371 } else if (k >= 3 && k < m_nv) {
372 return -Tiny; // species mass fraction or surface coverage
373 } else {
374 throw CanteraError("Reactor::lowerBound", "Index {} is out of bounds.", k);
375 }
376}
377
378void Reactor::resetBadValues(span<double> y) {
379 for (size_t k = 3; k < m_nv; k++) {
380 y[k] = std::max(y[k], 0.0);
381 }
382}
383
384void Reactor::applySensitivity(span<const double> params)
385{
386 if (params.empty()) {
387 return;
388 }
389 for (auto& p : m_sensParams) {
390 if (p.type == SensParameterType::reaction) {
391 p.value = m_kin->multiplier(p.local);
392 m_kin->setMultiplier(p.local, p.value*params[p.global]);
393 } else if (p.type == SensParameterType::enthalpy) {
394 m_thermo->modifyOneHf298SS(p.local, p.value + params[p.global]);
395 }
396 }
397 m_thermo->invalidateCache();
398 if (m_kin) {
399 m_kin->invalidateCache();
400 }
401}
402
403void Reactor::resetSensitivity(span<const double> params)
404{
405 if (params.empty()) {
406 return;
407 }
408 for (auto& p : m_sensParams) {
409 if (p.type == SensParameterType::reaction) {
410 m_kin->setMultiplier(p.local, p.value);
411 } else if (p.type == SensParameterType::enthalpy) {
412 m_thermo->resetHf298(p.local);
413 }
414 }
415 m_thermo->invalidateCache();
416 if (m_kin) {
417 m_kin->invalidateCache();
418 }
419}
420
421void Reactor::setAdvanceLimits(span<const double> limits)
422{
423 m_advancelimits.assign(limits.begin(), limits.end());
424
425 // resize to zero length if no limits are set
426 if (std::none_of(m_advancelimits.begin(), m_advancelimits.end(),
427 [](double val){return val>0;})) {
428 m_advancelimits.resize(0);
429 }
430}
431
432bool Reactor::getAdvanceLimits(span<double> limits) const
433{
434 bool has_limit = hasAdvanceLimits();
435 if (has_limit) {
436 std::copy(m_advancelimits.begin(), m_advancelimits.end(), limits.begin());
437 } else {
438 std::fill(limits.begin(), limits.end(), -1.0);
439 }
440 return has_limit;
441}
442
443void Reactor::setAdvanceLimit(const string& nm, const double limit)
444{
445 size_t k = componentIndex(nm);
446 m_advancelimits.resize(m_nv, -1.0);
447 m_advancelimits[k] = limit;
448
449 // resize to zero length if no limits are set
450 if (std::none_of(m_advancelimits.begin(), m_advancelimits.end(),
451 [](double val){return val>0;})) {
452 m_advancelimits.resize(0);
453 }
454}
455
456}
Base class for kinetics managers and also contains the kineticsmgr module documentation (see Kinetics...
Header file for class ReactorSurface.
Header for a simple thermodynamics model of a surface phase derived from ThermoPhase,...
Header file for base class WallBase.
A map of string keys to values whose type can vary at runtime.
Definition AnyMap.h:431
bool hasKey(const string &key) const
Returns true if the map contains an item named key.
Definition AnyMap.cpp:1477
bool empty() const
Return boolean indicating whether AnyMap is empty.
Definition AnyMap.cpp:1468
bool getBool(const string &key, bool default_) const
If key exists, return it as a bool, otherwise return default_.
Definition AnyMap.cpp:1575
Base class for exceptions thrown by Cantera classes.
An array index is out of range.
Base class for 'walls' (walls, pistons, etc.) connecting reactors.
Definition Wall.h:24
virtual void initialize()
Called just before the start of integration.
Definition Wall.h:64
double dot(InputIter x_begin, InputIter x_end, InputIter2 y_begin)
Function that calculates a templated inner product.
Definition utilities.h:96
const double GasConstant
Universal Gas Constant [J/kmol/K].
Definition ct_defs.h:123
Namespace for the Cantera kernel.
Definition AnyMap.cpp:595
const double Tiny
Small number to compare differences of mole fractions against.
Definition ct_defs.h:176
span< double > asSpan(Eigen::DenseBase< Derived > &v)
Convenience wrapper for accessing Eigen vector/array/map data as a span.
Definition eigen_dense.h:46
offset
Offsets of solution components in the 1D solution array.
Definition Flow1D.h:25
const double BigNumber
largest number to compare to inf.
Definition ct_defs.h:163
Various templated functions that carry out common vector and polynomial operations (see Templated Arr...