17#include <boost/math/tools/roots.hpp>
20namespace bmt = boost::math::tools;
25Reactor::Reactor(shared_ptr<Solution> sol,
const string& name)
28 if (!sol || !(sol->thermo())) {
30 "Creation of empty reactor objects is deprecated in Cantera 3.1 and will "
31 "raise\nexceptions thereafter; reactor contents should be provided in the "
36 setThermo(*sol->thermo());
37 setKinetics(*sol->kinetics());
40void Reactor::setDerivativeSettings(
AnyMap& settings)
42 m_kin->setDerivativeSettings(settings);
44 for (
auto S : m_surfaces) {
45 S->kinetics()->setDerivativeSettings(settings);
52 if (m_kin->nReactions() == 0) {
59void Reactor::getState(
double* y)
63 "Error: reactor is empty.");
65 m_thermo->restoreState(m_state);
68 m_mass = m_thermo->density() * m_vol;
75 y[2] = m_thermo->intEnergy_mass() * m_mass;
78 m_thermo->getMassFractions(y+3);
82 getSurfaceInitialConditions(y + m_nsp + 3);
85void Reactor::getSurfaceInitialConditions(
double* y)
88 for (
auto& S : m_surfaces) {
89 S->getCoverages(y + loc);
90 loc += S->thermo()->nSpecies();
94void Reactor::initialize(
double t0)
96 if (!m_thermo || (m_chem && !m_kin)) {
97 throw CanteraError(
"Reactor::initialize",
"Reactor contents not set"
98 " for reactor '" + m_name +
"'.");
100 m_thermo->restoreState(m_state);
101 m_sdot.resize(m_nsp, 0.0);
102 m_wdot.resize(m_nsp, 0.0);
103 updateConnected(
true);
105 for (
size_t n = 0; n < m_wall.size(); n++) {
113 for (
auto& S : m_surfaces) {
114 m_nv_surf += S->thermo()->nSpecies();
115 size_t nt = S->kinetics()->nTotalSpecies();
116 maxnt = std::max(maxnt, nt);
119 m_work.resize(maxnt);
122size_t Reactor::nSensParams()
const
124 size_t ns = m_sensParams.size();
125 for (
auto& S : m_surfaces) {
126 ns += S->nSensParams();
131void Reactor::syncState()
133 ReactorBase::syncState();
134 m_mass = m_thermo->density() * m_vol;
137void Reactor::updateState(
double* y)
144 m_thermo->setMassFractions_NoNorm(y+3);
149 auto u_err = [
this, U](
double T) {
150 m_thermo->setState_TD(T, m_mass / m_vol);
151 return m_thermo->intEnergy_mass() * m_mass - U;
154 double T = m_thermo->temperature();
155 boost::uintmax_t maxiter = 100;
156 pair<double, double> TT;
158 TT = bmt::bracket_and_solve_root(
159 u_err, T, 1.2,
true, bmt::eps_tolerance<double>(48), maxiter);
160 }
catch (std::exception&) {
164 TT = bmt::bisect(u_err, m_thermo->minTemp(), m_thermo->maxTemp(),
165 bmt::eps_tolerance<double>(48), maxiter);
166 }
catch (std::exception& err2) {
168 m_thermo->setState_TD(T, m_mass / m_vol);
170 "{}\nat U = {}, rho = {}", err2.what(), U, m_mass / m_vol);
173 if (fabs(TT.first - TT.second) > 1e-7*TT.first) {
174 throw CanteraError(
"Reactor::updateState",
"root finding failed");
176 m_thermo->setState_TD(TT.second, m_mass / m_vol);
178 m_thermo->setDensity(m_mass/m_vol);
181 updateConnected(
true);
182 updateSurfaceState(y + m_nsp + 3);
185void Reactor::updateSurfaceState(
double* y)
188 for (
auto& S : m_surfaces) {
189 S->setCoverages(y+loc);
190 loc += S->thermo()->nSpecies();
194void Reactor::updateConnected(
bool updatePressure) {
196 m_enthalpy = m_thermo->enthalpy_mass();
197 if (updatePressure) {
198 m_pressure = m_thermo->pressure();
200 m_intEnergy = m_thermo->intEnergy_mass();
201 m_thermo->saveState(m_state);
205 if (m_net !=
nullptr) {
206 time = (timeIsIndependent()) ? m_net->time() : m_net->distance();
208 for (
size_t i = 0; i < m_outlet.size(); i++) {
209 m_outlet[i]->setSimTime(time);
210 m_outlet[i]->updateMassFlowRate(time);
212 for (
size_t i = 0; i < m_inlet.size(); i++) {
213 m_inlet[i]->setSimTime(time);
214 m_inlet[i]->updateMassFlowRate(time);
216 for (
size_t i = 0; i < m_wall.size(); i++) {
217 m_wall[i]->setSimTime(time);
221void Reactor::eval(
double time,
double* LHS,
double* RHS)
223 double& dmdt = RHS[0];
224 double* mdYdt = RHS + 3;
227 m_thermo->restoreState(m_state);
228 const vector<double>& mw = m_thermo->molecularWeights();
229 const double* Y = m_thermo->massFractions();
231 evalSurfaces(LHS + m_nsp + 3, RHS + m_nsp + 3, m_sdot.data());
233 double mdot_surf =
dot(m_sdot.begin(), m_sdot.end(), mw.begin());
240 m_kin->getNetProductionRates(&m_wdot[0]);
243 for (
size_t k = 0; k < m_nsp; k++) {
245 mdYdt[k] = (m_wdot[k] * m_vol + m_sdot[k]) * mw[k];
247 mdYdt[k] -= Y[k] * mdot_surf;
256 RHS[2] = - m_thermo->pressure() * m_vdot + m_Qdot;
262 for (
auto outlet : m_outlet) {
263 double mdot = outlet->massFlowRate();
266 RHS[2] -= mdot * m_enthalpy;
271 for (
auto inlet : m_inlet) {
272 double mdot = inlet->massFlowRate();
274 for (
size_t n = 0; n < m_nsp; n++) {
275 double mdot_spec = inlet->outletSpeciesMassFlowRate(n);
277 mdYdt[n] += (mdot_spec - mdot * Y[n]);
280 RHS[2] += mdot * inlet->enthalpy_mass();
285void Reactor::evalWalls(
double t)
290 for (
size_t i = 0; i < m_wall.size(); i++) {
291 int f = 2 * m_lr[i] - 1;
292 m_vdot -= f * m_wall[i]->expansionRate();
293 m_Qdot += f * m_wall[i]->heatRate();
297void Reactor::evalSurfaces(
double* LHS,
double* RHS,
double* sdot)
299 fill(sdot, sdot + m_nsp, 0.0);
302 for (
auto S : m_surfaces) {
311 for (
size_t k = 1; k < nk; k++) {
312 RHS[loc + k] = m_work[k] * rs0 * surf->
size(k);
319 double wallarea = S->area();
320 for (
size_t k = 0; k < m_nsp; k++) {
321 sdot[k] += m_work[bulkloc + k] * wallarea;
326Eigen::SparseMatrix<double> Reactor::finiteDifferenceJacobian()
330 "Reactor must be initialized first.");
335 Eigen::ArrayXd yCurrent(m_nv);
336 getState(yCurrent.data());
337 double time = (m_net !=
nullptr) ? m_net->time() : 0.0;
339 Eigen::ArrayXd yPerturbed = yCurrent;
340 Eigen::ArrayXd lhsPerturbed(m_nv), lhsCurrent(m_nv);
341 Eigen::ArrayXd rhsPerturbed(m_nv), rhsCurrent(m_nv);
344 updateState(yCurrent.data());
345 eval(time, lhsCurrent.data(), rhsCurrent.data());
347 double rel_perturb = std::sqrt(std::numeric_limits<double>::epsilon());
348 double atol = (m_net !=
nullptr) ? m_net->atol() : 1e-15;
350 for (
size_t j = 0; j < m_nv; j++) {
351 yPerturbed = yCurrent;
352 double delta_y = std::max(std::abs(yCurrent[j]), 1000 * atol) * rel_perturb;
353 yPerturbed[j] += delta_y;
355 updateState(yPerturbed.data());
358 eval(time, lhsPerturbed.data(), rhsPerturbed.data());
361 for (
size_t i = 0; i < m_nv; i++) {
362 double ydotPerturbed = rhsPerturbed[i] / lhsPerturbed[i];
363 double ydotCurrent = rhsCurrent[i] / lhsCurrent[i];
364 if (ydotCurrent != ydotPerturbed) {
365 m_jac_trips.emplace_back(
366 static_cast<int>(i),
static_cast<int>(j),
367 (ydotPerturbed - ydotCurrent) / delta_y);
371 updateState(yCurrent.data());
373 Eigen::SparseMatrix<double> jac(m_nv, m_nv);
374 jac.setFromTriplets(m_jac_trips.begin(), m_jac_trips.end());
379void Reactor::evalSurfaces(
double* RHS,
double* sdot)
381 fill(sdot, sdot + m_nsp, 0.0);
384 for (
auto S : m_surfaces) {
393 for (
size_t k = 1; k < nk; k++) {
394 RHS[loc + k] = m_work[k] * rs0 * surf->
size(k);
401 double wallarea = S->area();
402 for (
size_t k = 0; k < m_nsp; k++) {
403 sdot[k] += m_work[bulkloc + k] * wallarea;
408void Reactor::addSensitivityReaction(
size_t rxn)
410 if (!m_chem || rxn >= m_kin->nReactions()) {
412 "Reaction number out of range ({})", rxn);
415 size_t p = network().registerSensitivityParameter(
416 name()+
": "+m_kin->reaction(rxn)->equation(), 1.0, 1.0);
417 m_sensParams.emplace_back(
421void Reactor::addSensitivitySpeciesEnthalpy(
size_t k)
423 if (k >= m_thermo->nSpecies()) {
424 throw CanteraError(
"Reactor::addSensitivitySpeciesEnthalpy",
425 "Species index out of range ({})", k);
428 size_t p = network().registerSensitivityParameter(
429 name() +
": " + m_thermo->speciesName(k) +
" enthalpy",
431 m_sensParams.emplace_back(
433 SensParameterType::enthalpy});
436size_t Reactor::speciesIndex(
const string& nm)
const
439 size_t k = m_thermo->speciesIndex(nm);
446 for (
auto& S : m_surfaces) {
458size_t Reactor::componentIndex(
const string& nm)
const
460 size_t k = speciesIndex(nm);
463 }
else if (nm ==
"mass") {
465 }
else if (nm ==
"volume") {
467 }
else if (nm ==
"int_energy") {
474string Reactor::componentName(
size_t k) {
481 }
else if (k >= 3 && k < neq()) {
483 if (k < m_thermo->nSpecies()) {
484 return m_thermo->speciesName(k);
486 k -= m_thermo->nSpecies();
488 for (
auto& S : m_surfaces) {
490 if (k < th->nSpecies()) {
497 throw CanteraError(
"Reactor::componentName",
"Index is out of bounds.");
500void Reactor::applySensitivity(
double* params)
505 for (
auto& p : m_sensParams) {
506 if (p.type == SensParameterType::reaction) {
507 p.value = m_kin->multiplier(p.local);
508 m_kin->setMultiplier(p.local, p.value*params[p.global]);
509 }
else if (p.type == SensParameterType::enthalpy) {
510 m_thermo->modifyOneHf298SS(p.local, p.value + params[p.global]);
513 for (
auto& S : m_surfaces) {
514 S->setSensitivityParameters(params);
516 m_thermo->invalidateCache();
518 m_kin->invalidateCache();
522void Reactor::resetSensitivity(
double* params)
527 for (
auto& p : m_sensParams) {
528 if (p.type == SensParameterType::reaction) {
529 m_kin->setMultiplier(p.local, p.value);
530 }
else if (p.type == SensParameterType::enthalpy) {
531 m_thermo->resetHf298(p.local);
534 for (
auto& S : m_surfaces) {
535 S->resetSensitivityParameters();
537 m_thermo->invalidateCache();
539 m_kin->invalidateCache();
543void Reactor::setAdvanceLimits(
const double *limits)
547 "Error: reactor is empty.");
549 m_advancelimits.assign(limits, limits + m_nv);
552 if (std::none_of(m_advancelimits.begin(), m_advancelimits.end(),
553 [](
double val){return val>0;})) {
554 m_advancelimits.resize(0);
558bool Reactor::getAdvanceLimits(
double *limits)
const
560 bool has_limit = hasAdvanceLimits();
562 std::copy(m_advancelimits.begin(), m_advancelimits.end(), limits);
564 std::fill(limits, limits + m_nv, -1.0);
569void Reactor::setAdvanceLimit(
const string& nm,
const double limit)
571 size_t k = componentIndex(nm);
573 throw CanteraError(
"Reactor::setAdvanceLimit",
"No component named '{}'", nm);
578 "Error: reactor is empty.");
583 "Cannot set limit on a reactor that is not "
584 "assigned to a ReactorNet object.");
588 }
else if (k > m_nv) {
590 "Index out of bounds.");
592 m_advancelimits.resize(m_nv, -1.0);
593 m_advancelimits[k] = limit;
596 if (std::none_of(m_advancelimits.begin(), m_advancelimits.end(),
597 [](
double val){return val>0;})) {
598 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.
void warn_deprecated(const string &source, const AnyBase &node, const string &message)
A deprecation warning for syntax in an input file.
Various templated functions that carry out common vector and polynomial operations (see Templated Arr...