17#include <boost/math/tools/roots.hpp>
20namespace bmt = boost::math::tools;
27Reactor::Reactor(shared_ptr<Solution> sol,
const string& name)
28 : ReactorBase(sol, name)
30 m_kin = m_solution->kinetics().get();
31 if (m_kin->nReactions() == 0) {
39Reactor::Reactor(shared_ptr<Solution> sol,
bool clone,
const string& name)
40 : ReactorBase(sol, clone, name)
42 m_kin = m_solution->kinetics().get();
43 if (m_kin->nReactions() == 0) {
51void Reactor::setDerivativeSettings(
AnyMap& settings)
53 m_kin->setDerivativeSettings(settings);
55 for (
auto S : m_surfaces) {
56 S->kinetics()->setDerivativeSettings(settings);
63 "After Cantera 3.2, a change of reactor contents after instantiation "
66 if (m_kin->nReactions() == 0) {
73void Reactor::getState(
double* y)
77 "Error: reactor is empty.");
79 m_thermo->restoreState(m_state);
82 m_mass = m_thermo->density() * m_vol;
89 y[2] = m_thermo->intEnergy_mass() * m_mass;
92 m_thermo->getMassFractions(y+3);
96 getSurfaceInitialConditions(y + m_nsp + 3);
99void Reactor::getSurfaceInitialConditions(
double* y)
102 for (
auto& S : m_surfaces) {
103 S->getCoverages(y + loc);
104 loc += S->thermo()->nSpecies();
108void Reactor::initialize(
double t0)
110 if (!m_thermo || (m_chem && !m_kin)) {
111 throw CanteraError(
"Reactor::initialize",
"Reactor contents not set"
112 " for reactor '" + m_name +
"'.");
114 m_thermo->restoreState(m_state);
115 m_sdot.resize(m_nsp, 0.0);
116 m_wdot.resize(m_nsp, 0.0);
117 updateConnected(
true);
119 for (
size_t n = 0; n < m_wall.size(); n++) {
127 for (
auto& S : m_surfaces) {
128 m_nv_surf += S->thermo()->nSpecies();
129 size_t nt = S->kinetics()->nTotalSpecies();
130 maxnt = std::max(maxnt, nt);
133 m_work.resize(maxnt);
136size_t Reactor::nSensParams()
const
138 size_t ns = m_sensParams.size();
139 for (
auto& S : m_surfaces) {
140 ns += S->nSensParams();
145void Reactor::updateState(
double* y)
152 m_thermo->setMassFractions_NoNorm(y+3);
157 auto u_err = [
this, U](
double T) {
158 m_thermo->setState_TD(T, m_mass / m_vol);
159 return m_thermo->intEnergy_mass() * m_mass - U;
162 double T = m_thermo->temperature();
163 boost::uintmax_t maxiter = 100;
164 pair<double, double> TT;
166 TT = bmt::bracket_and_solve_root(
167 u_err, T, 1.2,
true, bmt::eps_tolerance<double>(48), maxiter);
168 }
catch (std::exception&) {
172 TT = bmt::bisect(u_err, m_thermo->minTemp(), m_thermo->maxTemp(),
173 bmt::eps_tolerance<double>(48), maxiter);
174 }
catch (std::exception& err2) {
176 m_thermo->setState_TD(T, m_mass / m_vol);
178 "{}\nat U = {}, rho = {}", err2.what(), U, m_mass / m_vol);
181 if (fabs(TT.first - TT.second) > 1e-7*TT.first) {
182 throw CanteraError(
"Reactor::updateState",
"root finding failed");
184 m_thermo->setState_TD(TT.second, m_mass / m_vol);
186 m_thermo->setDensity(m_mass/m_vol);
189 updateConnected(
true);
190 updateSurfaceState(y + m_nsp + 3);
193void Reactor::updateSurfaceState(
double* y)
196 for (
auto& S : m_surfaces) {
197 S->setCoverages(y+loc);
198 loc += S->thermo()->nSpecies();
202void Reactor::updateConnected(
bool updatePressure) {
204 m_enthalpy = m_thermo->enthalpy_mass();
205 if (updatePressure) {
206 m_pressure = m_thermo->pressure();
209 m_intEnergy = m_thermo->intEnergy_mass();
213 m_thermo->saveState(m_state);
217 if (m_net !=
nullptr) {
218 time = (timeIsIndependent()) ? m_net->time() : m_net->distance();
220 for (
size_t i = 0; i < m_outlet.size(); i++) {
221 m_outlet[i]->setSimTime(time);
222 m_outlet[i]->updateMassFlowRate(time);
224 for (
size_t i = 0; i < m_inlet.size(); i++) {
225 m_inlet[i]->setSimTime(time);
226 m_inlet[i]->updateMassFlowRate(time);
228 for (
size_t i = 0; i < m_wall.size(); i++) {
229 m_wall[i]->setSimTime(time);
233void Reactor::eval(
double time,
double* LHS,
double* RHS)
235 double& dmdt = RHS[0];
236 double* mdYdt = RHS + 3;
239 m_thermo->restoreState(m_state);
240 const vector<double>& mw = m_thermo->molecularWeights();
241 const double* Y = m_thermo->massFractions();
243 evalSurfaces(LHS + m_nsp + 3, RHS + m_nsp + 3, m_sdot.data());
245 double mdot_surf =
dot(m_sdot.begin(), m_sdot.end(), mw.begin());
252 m_kin->getNetProductionRates(&m_wdot[0]);
255 for (
size_t k = 0; k < m_nsp; k++) {
257 mdYdt[k] = (m_wdot[k] * m_vol + m_sdot[k]) * mw[k];
259 mdYdt[k] -= Y[k] * mdot_surf;
268 RHS[2] = - m_thermo->pressure() * m_vdot + m_Qdot;
274 for (
auto outlet : m_outlet) {
275 double mdot = outlet->massFlowRate();
278 RHS[2] -= mdot * m_enthalpy;
283 for (
auto inlet : m_inlet) {
284 double mdot = inlet->massFlowRate();
286 for (
size_t n = 0; n < m_nsp; n++) {
287 double mdot_spec = inlet->outletSpeciesMassFlowRate(n);
289 mdYdt[n] += (mdot_spec - mdot * Y[n]);
292 RHS[2] += mdot * inlet->enthalpy_mass();
297void Reactor::evalWalls(
double t)
302 for (
size_t i = 0; i < m_wall.size(); i++) {
303 int f = 2 * m_lr[i] - 1;
304 m_vdot -= f * m_wall[i]->expansionRate();
305 m_Qdot += f * m_wall[i]->heatRate();
309void Reactor::evalSurfaces(
double* LHS,
double* RHS,
double* sdot)
311 fill(sdot, sdot + m_nsp, 0.0);
314 for (
auto S : m_surfaces) {
323 for (
size_t k = 1; k < nk; k++) {
324 RHS[loc + k] = m_work[k] * rs0 * surf->
size(k);
331 double wallarea = S->area();
332 for (
size_t k = 0; k < m_nsp; k++) {
333 sdot[k] += m_work[bulkloc + k] * wallarea;
338vector<size_t> Reactor::steadyConstraints()
const
340 if (!energyEnabled()) {
341 throw CanteraError(
"Reactor::steadyConstraints",
"Steady state solver cannot"
342 " be used with {0} when energy equation is disabled."
343 "\nConsider using IdealGas{0} instead.\n"
344 "See https://github.com/Cantera/enhancements/issues/234", type());
347 throw CanteraError(
"Reactor::steadyConstraints",
"Steady state solver cannot"
348 " currently be used when reactor surfaces are present.\n"
349 "See https://github.com/Cantera/enhancements/issues/234.");
354Eigen::SparseMatrix<double> Reactor::finiteDifferenceJacobian()
358 "Reactor must be initialized first.");
363 Eigen::ArrayXd yCurrent(m_nv);
364 getState(yCurrent.data());
365 double time = (m_net !=
nullptr) ? m_net->time() : 0.0;
367 Eigen::ArrayXd yPerturbed = yCurrent;
368 Eigen::ArrayXd lhsPerturbed(m_nv), lhsCurrent(m_nv);
369 Eigen::ArrayXd rhsPerturbed(m_nv), rhsCurrent(m_nv);
372 updateState(yCurrent.data());
373 eval(time, lhsCurrent.data(), rhsCurrent.data());
375 double rel_perturb = std::sqrt(std::numeric_limits<double>::epsilon());
376 double atol = (m_net !=
nullptr) ? m_net->atol() : 1e-15;
378 for (
size_t j = 0; j < m_nv; j++) {
379 yPerturbed = yCurrent;
380 double delta_y = std::max(std::abs(yCurrent[j]), 1000 * atol) * rel_perturb;
381 yPerturbed[j] += delta_y;
383 updateState(yPerturbed.data());
386 eval(time, lhsPerturbed.data(), rhsPerturbed.data());
389 for (
size_t i = 0; i < m_nv; i++) {
390 double ydotPerturbed = rhsPerturbed[i] / lhsPerturbed[i];
391 double ydotCurrent = rhsCurrent[i] / lhsCurrent[i];
392 if (ydotCurrent != ydotPerturbed) {
393 m_jac_trips.emplace_back(
394 static_cast<int>(i),
static_cast<int>(j),
395 (ydotPerturbed - ydotCurrent) / delta_y);
399 updateState(yCurrent.data());
401 Eigen::SparseMatrix<double> jac(m_nv, m_nv);
402 jac.setFromTriplets(m_jac_trips.begin(), m_jac_trips.end());
407void Reactor::evalSurfaces(
double* RHS,
double* sdot)
409 fill(sdot, sdot + m_nsp, 0.0);
412 for (
auto S : m_surfaces) {
421 for (
size_t k = 1; k < nk; k++) {
422 RHS[loc + k] = m_work[k] * rs0 * surf->
size(k);
429 double wallarea = S->area();
430 for (
size_t k = 0; k < m_nsp; k++) {
431 sdot[k] += m_work[bulkloc + k] * wallarea;
436void Reactor::addSensitivityReaction(
size_t rxn)
438 if (!m_chem || rxn >= m_kin->nReactions()) {
440 "Reaction number out of range ({})", rxn);
443 size_t p = network().registerSensitivityParameter(
444 name()+
": "+m_kin->reaction(rxn)->equation(), 1.0, 1.0);
445 m_sensParams.emplace_back(
449void Reactor::addSensitivitySpeciesEnthalpy(
size_t k)
451 if (k >= m_thermo->nSpecies()) {
452 throw CanteraError(
"Reactor::addSensitivitySpeciesEnthalpy",
453 "Species index out of range ({})", k);
456 size_t p = network().registerSensitivityParameter(
457 name() +
": " + m_thermo->speciesName(k) +
" enthalpy",
459 m_sensParams.emplace_back(
461 SensParameterType::enthalpy});
464size_t Reactor::speciesIndex(
const string& nm)
const
467 size_t k = m_thermo->speciesIndex(nm);
474 for (
auto& S : m_surfaces) {
486size_t Reactor::componentIndex(
const string& nm)
const
488 size_t k = speciesIndex(nm);
491 }
else if (nm ==
"mass") {
493 }
else if (nm ==
"volume") {
495 }
else if (nm ==
"int_energy") {
502string Reactor::componentName(
size_t k) {
509 }
else if (k >= 3 && k < neq()) {
511 if (k < m_thermo->nSpecies()) {
512 return m_thermo->speciesName(k);
514 k -= m_thermo->nSpecies();
516 for (
auto& S : m_surfaces) {
518 if (k < th->nSpecies()) {
525 throw CanteraError(
"Reactor::componentName",
"Index is out of bounds.");
528double Reactor::upperBound(
size_t k)
const {
535 }
else if (k >= 3 && k < m_nv) {
538 throw CanteraError(
"Reactor::upperBound",
"Index {} is out of bounds.", k);
542double Reactor::lowerBound(
size_t k)
const {
549 }
else if (k >= 3 && k < m_nv) {
552 throw CanteraError(
"Reactor::lowerBound",
"Index {} is out of bounds.", k);
556void Reactor::resetBadValues(
double* y) {
557 for (
size_t k = 3; k < m_nv; k++) {
558 y[k] = std::max(y[k], 0.0);
562void Reactor::applySensitivity(
double* params)
567 for (
auto& p : m_sensParams) {
568 if (p.type == SensParameterType::reaction) {
569 p.value = m_kin->multiplier(p.local);
570 m_kin->setMultiplier(p.local, p.value*params[p.global]);
571 }
else if (p.type == SensParameterType::enthalpy) {
572 m_thermo->modifyOneHf298SS(p.local, p.value + params[p.global]);
575 for (
auto& S : m_surfaces) {
576 S->setSensitivityParameters(params);
578 m_thermo->invalidateCache();
580 m_kin->invalidateCache();
584void Reactor::resetSensitivity(
double* params)
589 for (
auto& p : m_sensParams) {
590 if (p.type == SensParameterType::reaction) {
591 m_kin->setMultiplier(p.local, p.value);
592 }
else if (p.type == SensParameterType::enthalpy) {
593 m_thermo->resetHf298(p.local);
596 for (
auto& S : m_surfaces) {
597 S->resetSensitivityParameters();
599 m_thermo->invalidateCache();
601 m_kin->invalidateCache();
605void Reactor::setAdvanceLimits(
const double *limits)
609 "Error: reactor is empty.");
611 m_advancelimits.assign(limits, limits + m_nv);
614 if (std::none_of(m_advancelimits.begin(), m_advancelimits.end(),
615 [](
double val){return val>0;})) {
616 m_advancelimits.resize(0);
620bool Reactor::getAdvanceLimits(
double *limits)
const
622 bool has_limit = hasAdvanceLimits();
624 std::copy(m_advancelimits.begin(), m_advancelimits.end(), limits);
626 std::fill(limits, limits + m_nv, -1.0);
631void Reactor::setAdvanceLimit(
const string& nm,
const double limit)
633 size_t k = componentIndex(nm);
635 throw CanteraError(
"Reactor::setAdvanceLimit",
"No component named '{}'", nm);
640 "Error: reactor is empty.");
645 "Cannot set limit on a reactor that is not "
646 "assigned to a ReactorNet object.");
650 }
else if (k > m_nv) {
652 "Index out of bounds.");
654 m_advancelimits.resize(m_nv, -1.0);
655 m_advancelimits[k] = limit;
658 if (std::none_of(m_advancelimits.begin(), m_advancelimits.end(),
659 [](
double val){return val>0;})) {
660 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].
An error indicating that an unimplemented function has been called.
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"
const double Tiny
Small number to compare differences of mole fractions against.
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.
const double BigNumber
largest number to compare to inf.
Various templated functions that carry out common vector and polynomial operations (see Templated Arr...