17#include <boost/math/tools/roots.hpp>
20namespace bmt = boost::math::tools;
25Reactor::Reactor(shared_ptr<Solution> sol,
const string& name)
26 : ReactorBase(sol, name)
28 setKinetics(*sol->kinetics());
31void Reactor::setDerivativeSettings(
AnyMap& settings)
33 m_kin->setDerivativeSettings(settings);
35 for (
auto S : m_surfaces) {
36 S->kinetics()->setDerivativeSettings(settings);
43 if (m_kin->nReactions() == 0) {
50void Reactor::getState(
double* y)
54 "Error: reactor is empty.");
56 m_thermo->restoreState(m_state);
59 m_mass = m_thermo->density() * m_vol;
66 y[2] = m_thermo->intEnergy_mass() * m_mass;
69 m_thermo->getMassFractions(y+3);
73 getSurfaceInitialConditions(y + m_nsp + 3);
76void Reactor::getSurfaceInitialConditions(
double* y)
79 for (
auto& S : m_surfaces) {
80 S->getCoverages(y + loc);
81 loc += S->thermo()->nSpecies();
85void Reactor::initialize(
double t0)
87 if (!m_thermo || (m_chem && !m_kin)) {
88 throw CanteraError(
"Reactor::initialize",
"Reactor contents not set"
89 " for reactor '" + m_name +
"'.");
91 m_thermo->restoreState(m_state);
92 m_sdot.resize(m_nsp, 0.0);
93 m_wdot.resize(m_nsp, 0.0);
94 updateConnected(
true);
96 for (
size_t n = 0; n < m_wall.size(); n++) {
104 for (
auto& S : m_surfaces) {
105 m_nv_surf += S->thermo()->nSpecies();
106 size_t nt = S->kinetics()->nTotalSpecies();
107 maxnt = std::max(maxnt, nt);
110 m_work.resize(maxnt);
113size_t Reactor::nSensParams()
const
115 size_t ns = m_sensParams.size();
116 for (
auto& S : m_surfaces) {
117 ns += S->nSensParams();
122void Reactor::syncState()
124 ReactorBase::syncState();
125 m_mass = m_thermo->density() * m_vol;
128void Reactor::updateState(
double* y)
135 m_thermo->setMassFractions_NoNorm(y+3);
140 auto u_err = [
this, U](
double T) {
141 m_thermo->setState_TD(T, m_mass / m_vol);
142 return m_thermo->intEnergy_mass() * m_mass - U;
145 double T = m_thermo->temperature();
146 boost::uintmax_t maxiter = 100;
147 pair<double, double> TT;
149 TT = bmt::bracket_and_solve_root(
150 u_err, T, 1.2,
true, bmt::eps_tolerance<double>(48), maxiter);
151 }
catch (std::exception&) {
155 TT = bmt::bisect(u_err, m_thermo->minTemp(), m_thermo->maxTemp(),
156 bmt::eps_tolerance<double>(48), maxiter);
157 }
catch (std::exception& err2) {
159 m_thermo->setState_TD(T, m_mass / m_vol);
161 "{}\nat U = {}, rho = {}", err2.what(), U, m_mass / m_vol);
164 if (fabs(TT.first - TT.second) > 1e-7*TT.first) {
165 throw CanteraError(
"Reactor::updateState",
"root finding failed");
167 m_thermo->setState_TD(TT.second, m_mass / m_vol);
169 m_thermo->setDensity(m_mass/m_vol);
172 updateConnected(
true);
173 updateSurfaceState(y + m_nsp + 3);
176void Reactor::updateSurfaceState(
double* y)
179 for (
auto& S : m_surfaces) {
180 S->setCoverages(y+loc);
181 loc += S->thermo()->nSpecies();
185void Reactor::updateConnected(
bool updatePressure) {
187 m_enthalpy = m_thermo->enthalpy_mass();
188 if (updatePressure) {
189 m_pressure = m_thermo->pressure();
191 m_intEnergy = m_thermo->intEnergy_mass();
192 m_thermo->saveState(m_state);
196 if (m_net !=
nullptr) {
197 time = (timeIsIndependent()) ? m_net->time() : m_net->distance();
199 for (
size_t i = 0; i < m_outlet.size(); i++) {
200 m_outlet[i]->setSimTime(time);
201 m_outlet[i]->updateMassFlowRate(time);
203 for (
size_t i = 0; i < m_inlet.size(); i++) {
204 m_inlet[i]->setSimTime(time);
205 m_inlet[i]->updateMassFlowRate(time);
207 for (
size_t i = 0; i < m_wall.size(); i++) {
208 m_wall[i]->setSimTime(time);
212void Reactor::eval(
double time,
double* LHS,
double* RHS)
214 double& dmdt = RHS[0];
215 double* mdYdt = RHS + 3;
218 m_thermo->restoreState(m_state);
219 const vector<double>& mw = m_thermo->molecularWeights();
220 const double* Y = m_thermo->massFractions();
222 evalSurfaces(LHS + m_nsp + 3, RHS + m_nsp + 3, m_sdot.data());
224 double mdot_surf =
dot(m_sdot.begin(), m_sdot.end(), mw.begin());
231 m_kin->getNetProductionRates(&m_wdot[0]);
234 for (
size_t k = 0; k < m_nsp; k++) {
236 mdYdt[k] = (m_wdot[k] * m_vol + m_sdot[k]) * mw[k];
238 mdYdt[k] -= Y[k] * mdot_surf;
247 RHS[2] = - m_thermo->pressure() * m_vdot + m_Qdot;
253 for (
auto outlet : m_outlet) {
254 double mdot = outlet->massFlowRate();
257 RHS[2] -= mdot * m_enthalpy;
262 for (
auto inlet : m_inlet) {
263 double mdot = inlet->massFlowRate();
265 for (
size_t n = 0; n < m_nsp; n++) {
266 double mdot_spec = inlet->outletSpeciesMassFlowRate(n);
268 mdYdt[n] += (mdot_spec - mdot * Y[n]);
271 RHS[2] += mdot * inlet->enthalpy_mass();
276void Reactor::evalWalls(
double t)
281 for (
size_t i = 0; i < m_wall.size(); i++) {
282 int f = 2 * m_lr[i] - 1;
283 m_vdot -= f * m_wall[i]->expansionRate();
284 m_Qdot += f * m_wall[i]->heatRate();
288void Reactor::evalSurfaces(
double* LHS,
double* RHS,
double* sdot)
290 fill(sdot, sdot + m_nsp, 0.0);
293 for (
auto S : m_surfaces) {
302 for (
size_t k = 1; k < nk; k++) {
303 RHS[loc + k] = m_work[k] * rs0 * surf->
size(k);
310 double wallarea = S->area();
311 for (
size_t k = 0; k < m_nsp; k++) {
312 sdot[k] += m_work[bulkloc + k] * wallarea;
317Eigen::SparseMatrix<double> Reactor::finiteDifferenceJacobian()
321 "Reactor must be initialized first.");
326 Eigen::ArrayXd yCurrent(m_nv);
327 getState(yCurrent.data());
328 double time = (m_net !=
nullptr) ? m_net->time() : 0.0;
330 Eigen::ArrayXd yPerturbed = yCurrent;
331 Eigen::ArrayXd lhsPerturbed(m_nv), lhsCurrent(m_nv);
332 Eigen::ArrayXd rhsPerturbed(m_nv), rhsCurrent(m_nv);
335 updateState(yCurrent.data());
336 eval(time, lhsCurrent.data(), rhsCurrent.data());
338 double rel_perturb = std::sqrt(std::numeric_limits<double>::epsilon());
339 double atol = (m_net !=
nullptr) ? m_net->atol() : 1e-15;
341 for (
size_t j = 0; j < m_nv; j++) {
342 yPerturbed = yCurrent;
343 double delta_y = std::max(std::abs(yCurrent[j]), 1000 * atol) * rel_perturb;
344 yPerturbed[j] += delta_y;
346 updateState(yPerturbed.data());
349 eval(time, lhsPerturbed.data(), rhsPerturbed.data());
352 for (
size_t i = 0; i < m_nv; i++) {
353 double ydotPerturbed = rhsPerturbed[i] / lhsPerturbed[i];
354 double ydotCurrent = rhsCurrent[i] / lhsCurrent[i];
355 if (ydotCurrent != ydotPerturbed) {
356 m_jac_trips.emplace_back(
357 static_cast<int>(i),
static_cast<int>(j),
358 (ydotPerturbed - ydotCurrent) / delta_y);
362 updateState(yCurrent.data());
364 Eigen::SparseMatrix<double> jac(m_nv, m_nv);
365 jac.setFromTriplets(m_jac_trips.begin(), m_jac_trips.end());
370void Reactor::evalSurfaces(
double* RHS,
double* sdot)
372 fill(sdot, sdot + m_nsp, 0.0);
375 for (
auto S : m_surfaces) {
384 for (
size_t k = 1; k < nk; k++) {
385 RHS[loc + k] = m_work[k] * rs0 * surf->
size(k);
392 double wallarea = S->area();
393 for (
size_t k = 0; k < m_nsp; k++) {
394 sdot[k] += m_work[bulkloc + k] * wallarea;
399void Reactor::addSensitivityReaction(
size_t rxn)
401 if (!m_chem || rxn >= m_kin->nReactions()) {
403 "Reaction number out of range ({})", rxn);
406 size_t p = network().registerSensitivityParameter(
407 name()+
": "+m_kin->reaction(rxn)->equation(), 1.0, 1.0);
408 m_sensParams.emplace_back(
412void Reactor::addSensitivitySpeciesEnthalpy(
size_t k)
414 if (k >= m_thermo->nSpecies()) {
415 throw CanteraError(
"Reactor::addSensitivitySpeciesEnthalpy",
416 "Species index out of range ({})", k);
419 size_t p = network().registerSensitivityParameter(
420 name() +
": " + m_thermo->speciesName(k) +
" enthalpy",
422 m_sensParams.emplace_back(
424 SensParameterType::enthalpy});
427size_t Reactor::speciesIndex(
const string& nm)
const
430 size_t k = m_thermo->speciesIndex(nm);
437 for (
auto& S : m_surfaces) {
449size_t Reactor::componentIndex(
const string& nm)
const
451 size_t k = speciesIndex(nm);
454 }
else if (nm ==
"mass") {
456 }
else if (nm ==
"volume") {
458 }
else if (nm ==
"int_energy") {
465string Reactor::componentName(
size_t k) {
472 }
else if (k >= 3 && k < neq()) {
474 if (k < m_thermo->nSpecies()) {
475 return m_thermo->speciesName(k);
477 k -= m_thermo->nSpecies();
479 for (
auto& S : m_surfaces) {
481 if (k < th->nSpecies()) {
488 throw CanteraError(
"Reactor::componentName",
"Index is out of bounds.");
491void Reactor::applySensitivity(
double* params)
496 for (
auto& p : m_sensParams) {
497 if (p.type == SensParameterType::reaction) {
498 p.value = m_kin->multiplier(p.local);
499 m_kin->setMultiplier(p.local, p.value*params[p.global]);
500 }
else if (p.type == SensParameterType::enthalpy) {
501 m_thermo->modifyOneHf298SS(p.local, p.value + params[p.global]);
504 for (
auto& S : m_surfaces) {
505 S->setSensitivityParameters(params);
507 m_thermo->invalidateCache();
509 m_kin->invalidateCache();
513void Reactor::resetSensitivity(
double* params)
518 for (
auto& p : m_sensParams) {
519 if (p.type == SensParameterType::reaction) {
520 m_kin->setMultiplier(p.local, p.value);
521 }
else if (p.type == SensParameterType::enthalpy) {
522 m_thermo->resetHf298(p.local);
525 for (
auto& S : m_surfaces) {
526 S->resetSensitivityParameters();
528 m_thermo->invalidateCache();
530 m_kin->invalidateCache();
534void Reactor::setAdvanceLimits(
const double *limits)
538 "Error: reactor is empty.");
540 m_advancelimits.assign(limits, limits + m_nv);
543 if (std::none_of(m_advancelimits.begin(), m_advancelimits.end(),
544 [](
double val){return val>0;})) {
545 m_advancelimits.resize(0);
549bool Reactor::getAdvanceLimits(
double *limits)
const
551 bool has_limit = hasAdvanceLimits();
553 std::copy(m_advancelimits.begin(), m_advancelimits.end(), limits);
555 std::fill(limits, limits + m_nv, -1.0);
560void Reactor::setAdvanceLimit(
const string& nm,
const double limit)
562 size_t k = componentIndex(nm);
564 throw CanteraError(
"Reactor::setAdvanceLimit",
"No component named '{}'", nm);
569 "Error: reactor is empty.");
574 "Cannot set limit on a reactor that is not "
575 "assigned to a ReactorNet object.");
579 }
else if (k > m_nv) {
581 "Index out of bounds.");
583 m_advancelimits.resize(m_nv, -1.0);
584 m_advancelimits[k] = limit;
587 if (std::none_of(m_advancelimits.begin(), m_advancelimits.end(),
588 [](
double val){return val>0;})) {
589 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.
Base class for exceptions thrown by Cantera classes.
Public interface for kinetics managers.
size_t kineticsSpeciesIndex(size_t k, size_t n) const
The location of species k of phase n in species arrays.
virtual void getNetProductionRates(double *wdot)
Species net production rates [kmol/m^3/s or kmol/m^2/s].
size_t nSpecies() const
Returns the number of species in the phase.
string speciesName(size_t k) const
Name of the species with index k.
size_t speciesIndex(const string &name) const
Returns the index of a species named 'name' within the Phase object.
A simple thermodynamic model for a surface phase, assuming an ideal solution model.
double size(size_t k) const
Returns the number of sites occupied by one molecule of species k.
double siteDensity() const
Returns the site density.
Base class for a phase with thermodynamic properties.
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 size_t npos
index returned by functions to indicate "no position"
offset
Offsets of solution components in the 1D solution array.
Various templated functions that carry out common vector and polynomial operations (see Templated Arr...