16#include "cantera/numerics/eigen_dense.h"
18#include <boost/math/tools/roots.hpp>
21namespace bmt = boost::math::tools;
26Reactor::Reactor(shared_ptr<Solution> sol,
const string& name)
27 : Reactor(sol, true, name)
31Reactor::Reactor(shared_ptr<Solution> sol,
bool clone,
const string& name)
32 : ReactorBase(sol, clone, name)
34 m_kin = m_solution->kinetics().get();
35 setChemistryEnabled(m_kin->nReactions() > 0);
37 m_sdot.resize(m_nsp, 0.0);
38 m_wdot.resize(m_nsp, 0.0);
42void Reactor::setDerivativeSettings(
AnyMap& settings)
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);
49 if (force || settings.
hasKey(
"skip-walls")) {
50 m_jac_skip_walls = settings.
getBool(
"skip-walls",
false);
52 if (force || settings.
hasKey(
"skip-connector-composition-dependence")) {
53 m_jac_skip_connector_composition_dependence =
54 settings.
getBool(
"skip-connector-composition-dependence",
false);
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);
62void Reactor::getState(span<double> y)
65 m_mass = m_thermo->density() * m_vol;
72 y[2] = m_thermo->intEnergy_mass() * m_mass;
75 m_thermo->getMassFractions(y.subspan(3, m_nsp));
78void Reactor::initialize(
double t0)
80 if (!m_thermo || (m_chem && !m_kin)) {
81 throw CanteraError(
"Reactor::initialize",
"Reactor contents not set"
82 " for reactor '" + m_name +
"'.");
84 updateConnected(
true);
86 for (
size_t n = 0; n < m_wall.size(); n++) {
92void Reactor::updateState(span<const double> y)
99 m_thermo->setMassFractions_NoNorm(y.subspan(3, m_nsp));
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;
109 double T = m_thermo->temperature();
110 boost::uintmax_t maxiter = 100;
111 pair<double, double> TT;
113 TT = bmt::bracket_and_solve_root(
114 u_err, T, 1.2,
true, bmt::eps_tolerance<double>(48), maxiter);
115 }
catch (std::exception&) {
119 TT = bmt::bisect(u_err, m_thermo->minTemp(), m_thermo->maxTemp(),
120 bmt::eps_tolerance<double>(48), maxiter);
121 }
catch (std::exception& err2) {
123 m_thermo->setState_TD(T, m_mass / m_vol);
125 "{}\nat U = {}, rho = {}", err2.what(), U, m_mass / m_vol);
128 if (fabs(TT.first - TT.second) > 1e-7*TT.first) {
129 throw CanteraError(
"Reactor::updateState",
"root finding failed");
131 m_thermo->setState_TD(TT.second, m_mass / m_vol);
133 m_thermo->setDensity(m_mass/m_vol);
136 updateConnected(
true);
139void Reactor::eval(
double time, span<double> LHS, span<double> RHS)
141 double& dmdt = RHS[0];
142 auto mdYdt = RHS.subspan(3);
145 updateSurfaceProductionRates();
146 auto mw = m_thermo->molecularWeights();
147 auto Y = m_thermo->massFractions();
150 double mdot_surf =
dot(m_sdot.begin(), m_sdot.end(), mw.begin());
157 m_kin->getNetProductionRates(m_wdot);
160 for (
size_t k = 0; k < m_nsp; k++) {
162 mdYdt[k] = (m_wdot[k] * m_vol + m_sdot[k]) * mw[k];
164 mdYdt[k] -= Y[k] * mdot_surf;
173 RHS[2] = - m_thermo->pressure() * m_vdot + m_Qdot;
174 RHS[2] += m_thermo->intrinsicHeating() * m_vol;
180 for (
auto outlet : m_outlet) {
181 double mdot = outlet->massFlowRate();
184 RHS[2] -= mdot * m_enthalpy;
189 for (
auto inlet : m_inlet) {
190 double mdot = inlet->massFlowRate();
192 for (
size_t n = 0; n < m_nsp; n++) {
193 double mdot_spec = inlet->outletSpeciesMassFlowRate(n);
195 mdYdt[n] += (mdot_spec - mdot * Y[n]);
198 RHS[2] += mdot * inlet->enthalpy_mass();
203void Reactor::evalSteady(
double time, span<double> LHS, span<double> RHS)
205 eval(time, LHS, RHS);
206 RHS[1] = m_vol - m_initialVolume;
209void Reactor::evalWalls(
double t)
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();
221vector<size_t> Reactor::initializeSteady()
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());
229 m_initialVolume = m_vol;
233Eigen::SparseMatrix<double> Reactor::finiteDifferenceJacobian()
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;
240 Eigen::ArrayXd yPerturbed = yCurrent;
241 Eigen::ArrayXd lhsPerturbed(m_nv), lhsCurrent(m_nv);
242 Eigen::ArrayXd rhsPerturbed(m_nv), rhsCurrent(m_nv);
245 updateState(
asSpan(yCurrent));
248 double rel_perturb = std::sqrt(std::numeric_limits<double>::epsilon());
249 double atol = (m_net !=
nullptr) ? m_net->atol() : 1e-15;
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;
256 updateState(
asSpan(yPerturbed));
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);
271 updateState(
asSpan(yCurrent));
273 Eigen::SparseMatrix<double> jac(m_nv, m_nv);
274 jac.setFromTriplets(trips.begin(), trips.end());
278void Reactor::addSensitivityReaction(
size_t rxn)
280 if (!m_chem || rxn >= m_kin->nReactions()) {
282 "Reaction number out of range ({})", rxn);
285 size_t p = network().registerSensitivityParameter(
286 name()+
": "+m_kin->reaction(rxn)->equation(), 1.0, 1.0);
287 m_sensParams.emplace_back(
291void Reactor::addSensitivitySpeciesEnthalpy(
size_t k)
293 if (k >= m_thermo->nSpecies()) {
294 throw CanteraError(
"Reactor::addSensitivitySpeciesEnthalpy",
295 "Species index out of range ({})", k);
298 size_t p = network().registerSensitivityParameter(
299 name() +
": " + m_thermo->speciesName(k) +
" enthalpy",
301 m_sensParams.emplace_back(
303 SensParameterType::enthalpy});
306void Reactor::updateSurfaceProductionRates()
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();
318size_t Reactor::componentIndex(
const string& nm)
const
323 if (nm ==
"volume") {
326 if (nm ==
"int_energy") {
330 return m_thermo->speciesIndex(nm) + 3;
333 "Component '{}' not found", nm);
337string Reactor::componentName(
size_t k) {
344 }
else if (k >= 3 && k < neq()) {
345 return m_thermo->speciesName(k - 3);
347 throw IndexError(
"Reactor::componentName",
"component", k, m_nv);
350double Reactor::upperBound(
size_t k)
const {
357 }
else if (k >= 3 && k < m_nv) {
360 throw CanteraError(
"Reactor::upperBound",
"Index {} is out of bounds.", k);
364double Reactor::lowerBound(
size_t k)
const {
371 }
else if (k >= 3 && k < m_nv) {
374 throw CanteraError(
"Reactor::lowerBound",
"Index {} is out of bounds.", k);
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);
384void Reactor::applySensitivity(span<const double> params)
386 if (params.empty()) {
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]);
397 m_thermo->invalidateCache();
399 m_kin->invalidateCache();
403void Reactor::resetSensitivity(span<const double> params)
405 if (params.empty()) {
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);
415 m_thermo->invalidateCache();
417 m_kin->invalidateCache();
421void Reactor::setAdvanceLimits(span<const double> limits)
423 m_advancelimits.assign(limits.begin(), limits.end());
426 if (std::none_of(m_advancelimits.begin(), m_advancelimits.end(),
427 [](
double val){return val>0;})) {
428 m_advancelimits.resize(0);
432bool Reactor::getAdvanceLimits(span<double> limits)
const
434 bool has_limit = hasAdvanceLimits();
436 std::copy(m_advancelimits.begin(), m_advancelimits.end(), limits.begin());
438 std::fill(limits.begin(), limits.end(), -1.0);
443void Reactor::setAdvanceLimit(
const string& nm,
const double limit)
445 size_t k = componentIndex(nm);
446 m_advancelimits.resize(m_nv, -1.0);
447 m_advancelimits[k] = limit;
450 if (std::none_of(m_advancelimits.begin(), m_advancelimits.end(),
451 [](
double val){return val>0;})) {
452 m_advancelimits.resize(0);
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.
bool hasKey(const string &key) const
Returns true if the map contains an item named key.
bool empty() const
Return boolean indicating whether AnyMap is empty.
bool getBool(const string &key, bool default_) const
If key exists, return it as a bool, otherwise return default_.
Base class for exceptions thrown by Cantera classes.
An array index is out of range.
Base class for 'walls' (walls, pistons, etc.) connecting reactors.
virtual void initialize()
Called just before the start of integration.
double dot(InputIter x_begin, InputIter x_end, InputIter2 y_begin)
Function that calculates a templated inner product.
const double GasConstant
Universal Gas Constant [J/kmol/K].
Namespace for the Cantera kernel.
const double Tiny
Small number to compare differences of mole fractions against.
span< double > asSpan(Eigen::DenseBase< Derived > &v)
Convenience wrapper for accessing Eigen vector/array/map data as a span.
offset
Offsets of solution components in the 1D solution array.
const double BigNumber
largest number to compare to inf.
Various templated functions that carry out common vector and polynomial operations (see Templated Arr...