17#include <boost/math/tools/roots.hpp>
20namespace bmt = boost::math::tools;
25Reactor::Reactor(shared_ptr<Solution> sol,
const string& name)
26 : Reactor(sol, true, name)
30Reactor::Reactor(shared_ptr<Solution> sol,
bool clone,
const string& name)
31 : ReactorBase(sol, clone, name)
33 m_kin = m_solution->kinetics().get();
34 setChemistryEnabled(m_kin->nReactions() > 0);
38void Reactor::setDerivativeSettings(
AnyMap& settings)
40 m_kin->setDerivativeSettings(settings);
42 for (
auto S : m_surfaces) {
43 S->kinetics()->setDerivativeSettings(settings);
47void Reactor::getState(
double* y)
51 "Error: reactor is empty.");
53 m_thermo->restoreState(m_state);
56 m_mass = m_thermo->density() * m_vol;
63 y[2] = m_thermo->intEnergy_mass() * m_mass;
66 m_thermo->getMassFractions(y+3);
70 getSurfaceInitialConditions(y + m_nsp + 3);
73void Reactor::getSurfaceInitialConditions(
double* y)
76 for (
auto& S : m_surfaces) {
77 S->getCoverages(y + loc);
78 loc += S->thermo()->nSpecies();
82void Reactor::initialize(
double t0)
84 if (!m_thermo || (m_chem && !m_kin)) {
85 throw CanteraError(
"Reactor::initialize",
"Reactor contents not set"
86 " for reactor '" + m_name +
"'.");
88 m_thermo->restoreState(m_state);
89 m_sdot.resize(m_nsp, 0.0);
90 m_wdot.resize(m_nsp, 0.0);
91 updateConnected(
true);
93 for (
size_t n = 0; n < m_wall.size(); n++) {
101 for (
auto& S : m_surfaces) {
102 m_nv_surf += S->thermo()->nSpecies();
103 size_t nt = S->kinetics()->nTotalSpecies();
104 maxnt = std::max(maxnt, nt);
107 m_work.resize(maxnt);
110size_t Reactor::nSensParams()
const
112 size_t ns = m_sensParams.size();
113 for (
auto& S : m_surfaces) {
114 ns += S->nSensParams();
119void Reactor::updateState(
double* y)
126 m_thermo->setMassFractions_NoNorm(y+3);
131 auto u_err = [
this, U](
double T) {
132 m_thermo->setState_TD(T, m_mass / m_vol);
133 return m_thermo->intEnergy_mass() * m_mass - U;
136 double T = m_thermo->temperature();
137 boost::uintmax_t maxiter = 100;
138 pair<double, double> TT;
140 TT = bmt::bracket_and_solve_root(
141 u_err, T, 1.2,
true, bmt::eps_tolerance<double>(48), maxiter);
142 }
catch (std::exception&) {
146 TT = bmt::bisect(u_err, m_thermo->minTemp(), m_thermo->maxTemp(),
147 bmt::eps_tolerance<double>(48), maxiter);
148 }
catch (std::exception& err2) {
150 m_thermo->setState_TD(T, m_mass / m_vol);
152 "{}\nat U = {}, rho = {}", err2.what(), U, m_mass / m_vol);
155 if (fabs(TT.first - TT.second) > 1e-7*TT.first) {
156 throw CanteraError(
"Reactor::updateState",
"root finding failed");
158 m_thermo->setState_TD(TT.second, m_mass / m_vol);
160 m_thermo->setDensity(m_mass/m_vol);
163 updateConnected(
true);
164 updateSurfaceState(y + m_nsp + 3);
167void Reactor::updateSurfaceState(
double* y)
170 for (
auto& S : m_surfaces) {
171 S->setCoverages(y+loc);
172 loc += S->thermo()->nSpecies();
176void Reactor::updateConnected(
bool updatePressure) {
178 m_enthalpy = m_thermo->enthalpy_mass();
179 if (updatePressure) {
180 m_pressure = m_thermo->pressure();
182 m_thermo->saveState(m_state);
186 if (m_net !=
nullptr) {
187 time = (timeIsIndependent()) ? m_net->time() : m_net->distance();
189 for (
size_t i = 0; i < m_outlet.size(); i++) {
190 m_outlet[i]->setSimTime(time);
191 m_outlet[i]->updateMassFlowRate(time);
193 for (
size_t i = 0; i < m_inlet.size(); i++) {
194 m_inlet[i]->setSimTime(time);
195 m_inlet[i]->updateMassFlowRate(time);
197 for (
size_t i = 0; i < m_wall.size(); i++) {
198 m_wall[i]->setSimTime(time);
202void Reactor::eval(
double time,
double* LHS,
double* RHS)
204 double& dmdt = RHS[0];
205 double* mdYdt = RHS + 3;
208 m_thermo->restoreState(m_state);
209 const vector<double>& mw = m_thermo->molecularWeights();
210 const double* Y = m_thermo->massFractions();
212 evalSurfaces(LHS + m_nsp + 3, RHS + m_nsp + 3, m_sdot.data());
214 double mdot_surf =
dot(m_sdot.begin(), m_sdot.end(), mw.begin());
221 m_kin->getNetProductionRates(&m_wdot[0]);
224 for (
size_t k = 0; k < m_nsp; k++) {
226 mdYdt[k] = (m_wdot[k] * m_vol + m_sdot[k]) * mw[k];
228 mdYdt[k] -= Y[k] * mdot_surf;
237 RHS[2] = - m_thermo->pressure() * m_vdot + m_Qdot;
243 for (
auto outlet : m_outlet) {
244 double mdot = outlet->massFlowRate();
247 RHS[2] -= mdot * m_enthalpy;
252 for (
auto inlet : m_inlet) {
253 double mdot = inlet->massFlowRate();
255 for (
size_t n = 0; n < m_nsp; n++) {
256 double mdot_spec = inlet->outletSpeciesMassFlowRate(n);
258 mdYdt[n] += (mdot_spec - mdot * Y[n]);
261 RHS[2] += mdot * inlet->enthalpy_mass();
266void Reactor::evalWalls(
double t)
271 for (
size_t i = 0; i < m_wall.size(); i++) {
272 int f = 2 * m_lr[i] - 1;
273 m_vdot -= f * m_wall[i]->expansionRate();
274 m_Qdot += f * m_wall[i]->heatRate();
278void Reactor::evalSurfaces(
double* LHS,
double* RHS,
double* sdot)
280 fill(sdot, sdot + m_nsp, 0.0);
283 for (
auto S : m_surfaces) {
292 for (
size_t k = 1; k < nk; k++) {
293 RHS[loc + k] = m_work[k] * rs0 * surf->
size(k);
300 double wallarea = S->area();
301 for (
size_t k = 0; k < m_nsp; k++) {
302 sdot[k] += m_work[bulkloc + k] * wallarea;
307vector<size_t> Reactor::steadyConstraints()
const
309 if (!energyEnabled()) {
310 throw CanteraError(
"Reactor::steadyConstraints",
"Steady state solver cannot"
311 " be used with {0} when energy equation is disabled."
312 "\nConsider using IdealGas{0} instead.\n"
313 "See https://github.com/Cantera/enhancements/issues/234", type());
316 throw CanteraError(
"Reactor::steadyConstraints",
"Steady state solver cannot"
317 " currently be used when reactor surfaces are present.\n"
318 "See https://github.com/Cantera/enhancements/issues/234.");
323Eigen::SparseMatrix<double> Reactor::finiteDifferenceJacobian()
327 "Reactor must be initialized first.");
332 Eigen::ArrayXd yCurrent(m_nv);
333 getState(yCurrent.data());
334 double time = (m_net !=
nullptr) ? m_net->time() : 0.0;
336 Eigen::ArrayXd yPerturbed = yCurrent;
337 Eigen::ArrayXd lhsPerturbed(m_nv), lhsCurrent(m_nv);
338 Eigen::ArrayXd rhsPerturbed(m_nv), rhsCurrent(m_nv);
341 updateState(yCurrent.data());
342 eval(time, lhsCurrent.data(), rhsCurrent.data());
344 double rel_perturb = std::sqrt(std::numeric_limits<double>::epsilon());
345 double atol = (m_net !=
nullptr) ? m_net->atol() : 1e-15;
347 for (
size_t j = 0; j < m_nv; j++) {
348 yPerturbed = yCurrent;
349 double delta_y = std::max(std::abs(yCurrent[j]), 1000 * atol) * rel_perturb;
350 yPerturbed[j] += delta_y;
352 updateState(yPerturbed.data());
355 eval(time, lhsPerturbed.data(), rhsPerturbed.data());
358 for (
size_t i = 0; i < m_nv; i++) {
359 double ydotPerturbed = rhsPerturbed[i] / lhsPerturbed[i];
360 double ydotCurrent = rhsCurrent[i] / lhsCurrent[i];
361 if (ydotCurrent != ydotPerturbed) {
362 m_jac_trips.emplace_back(
363 static_cast<int>(i),
static_cast<int>(j),
364 (ydotPerturbed - ydotCurrent) / delta_y);
368 updateState(yCurrent.data());
370 Eigen::SparseMatrix<double> jac(m_nv, m_nv);
371 jac.setFromTriplets(m_jac_trips.begin(), m_jac_trips.end());
376void Reactor::evalSurfaces(
double* RHS,
double* sdot)
378 fill(sdot, sdot + m_nsp, 0.0);
381 for (
auto S : m_surfaces) {
390 for (
size_t k = 1; k < nk; k++) {
391 RHS[loc + k] = m_work[k] * rs0 * surf->
size(k);
398 double wallarea = S->area();
399 for (
size_t k = 0; k < m_nsp; k++) {
400 sdot[k] += m_work[bulkloc + k] * wallarea;
405void Reactor::addSensitivityReaction(
size_t rxn)
407 if (!m_chem || rxn >= m_kin->nReactions()) {
409 "Reaction number out of range ({})", rxn);
412 size_t p = network().registerSensitivityParameter(
413 name()+
": "+m_kin->reaction(rxn)->equation(), 1.0, 1.0);
414 m_sensParams.emplace_back(
418void Reactor::addSensitivitySpeciesEnthalpy(
size_t k)
420 if (k >= m_thermo->nSpecies()) {
421 throw CanteraError(
"Reactor::addSensitivitySpeciesEnthalpy",
422 "Species index out of range ({})", k);
425 size_t p = network().registerSensitivityParameter(
426 name() +
": " + m_thermo->speciesName(k) +
" enthalpy",
428 m_sensParams.emplace_back(
430 SensParameterType::enthalpy});
433size_t Reactor::speciesIndex(
const string& nm)
const
436 size_t k = m_thermo->speciesIndex(nm,
false);
443 for (
auto& S : m_surfaces) {
453 "Species '{}' not found", nm);
456size_t Reactor::componentIndex(
const string& nm)
const
461 if (nm ==
"volume") {
464 if (nm ==
"int_energy") {
468 return speciesIndex(nm) + 3;
471 "Component '{}' not found", nm);
475string Reactor::componentName(
size_t k) {
482 }
else if (k >= 3 && k < neq()) {
484 if (k < m_thermo->nSpecies()) {
485 return m_thermo->speciesName(k);
487 k -= m_thermo->nSpecies();
489 for (
auto& S : m_surfaces) {
491 if (k < th->nSpecies()) {
498 throw IndexError(
"Reactor::componentName",
"component", k, m_nv);
501double Reactor::upperBound(
size_t k)
const {
508 }
else if (k >= 3 && k < m_nv) {
511 throw CanteraError(
"Reactor::upperBound",
"Index {} is out of bounds.", k);
515double Reactor::lowerBound(
size_t k)
const {
522 }
else if (k >= 3 && k < m_nv) {
525 throw CanteraError(
"Reactor::lowerBound",
"Index {} is out of bounds.", k);
529void Reactor::resetBadValues(
double* y) {
530 for (
size_t k = 3; k < m_nv; k++) {
531 y[k] = std::max(y[k], 0.0);
535void Reactor::applySensitivity(
double* params)
540 for (
auto& p : m_sensParams) {
541 if (p.type == SensParameterType::reaction) {
542 p.value = m_kin->multiplier(p.local);
543 m_kin->setMultiplier(p.local, p.value*params[p.global]);
544 }
else if (p.type == SensParameterType::enthalpy) {
545 m_thermo->modifyOneHf298SS(p.local, p.value + params[p.global]);
548 for (
auto& S : m_surfaces) {
549 S->setSensitivityParameters(params);
551 m_thermo->invalidateCache();
553 m_kin->invalidateCache();
557void Reactor::resetSensitivity(
double* params)
562 for (
auto& p : m_sensParams) {
563 if (p.type == SensParameterType::reaction) {
564 m_kin->setMultiplier(p.local, p.value);
565 }
else if (p.type == SensParameterType::enthalpy) {
566 m_thermo->resetHf298(p.local);
569 for (
auto& S : m_surfaces) {
570 S->resetSensitivityParameters();
572 m_thermo->invalidateCache();
574 m_kin->invalidateCache();
578void Reactor::setAdvanceLimits(
const double *limits)
582 "Error: reactor is empty.");
584 m_advancelimits.assign(limits, limits + m_nv);
587 if (std::none_of(m_advancelimits.begin(), m_advancelimits.end(),
588 [](
double val){return val>0;})) {
589 m_advancelimits.resize(0);
593bool Reactor::getAdvanceLimits(
double *limits)
const
595 bool has_limit = hasAdvanceLimits();
597 std::copy(m_advancelimits.begin(), m_advancelimits.end(), limits);
599 std::fill(limits, limits + m_nv, -1.0);
604void Reactor::setAdvanceLimit(
const string& nm,
const double limit)
606 size_t k = componentIndex(nm);
610 "Error: reactor is empty.");
615 "Cannot set limit on a reactor that is not "
616 "assigned to a ReactorNet object.");
620 }
else if (k > m_nv) {
622 "Index out of bounds.");
624 m_advancelimits.resize(m_nv, -1.0);
625 m_advancelimits[k] = limit;
628 if (std::none_of(m_advancelimits.begin(), m_advancelimits.end(),
629 [](
double val){return val>0;})) {
630 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.
An array index is out of range.
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.
size_t speciesIndex(const string &name, bool raise=true) const
Returns the index of a species named 'name' within the Phase object.
string speciesName(size_t k) const
Name of the species with index k.
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"
const double Tiny
Small number to compare differences of mole fractions against.
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...