17 #include <boost/math/tools/roots.hpp>
20 namespace bmt = boost::math::tools;
25 void Reactor::insert(shared_ptr<Solution> sol) {
26 setThermoMgr(*sol->thermo());
27 setKineticsMgr(*sol->kinetics());
30 void Reactor::setDerivativeSettings(
AnyMap& settings)
32 m_kin->setDerivativeSettings(settings);
34 for (
auto S : m_surfaces) {
35 S->kinetics()->setDerivativeSettings(settings);
42 if (m_kin->nReactions() == 0) {
49 void Reactor::getState(
double* y)
53 "Error: reactor is empty.");
55 m_thermo->restoreState(m_state);
58 m_mass = m_thermo->density() * m_vol;
65 y[2] = m_thermo->intEnergy_mass() * m_mass;
68 m_thermo->getMassFractions(y+3);
72 getSurfaceInitialConditions(y + m_nsp + 3);
75 void Reactor::getSurfaceInitialConditions(
double* y)
78 for (
auto& S : m_surfaces) {
79 S->getCoverages(y + loc);
80 loc += S->thermo()->nSpecies();
84 void Reactor::initialize(
double t0)
86 if (!m_thermo || (m_chem && !m_kin)) {
87 throw CanteraError(
"Reactor::initialize",
"Reactor contents not set"
88 " for reactor '" + m_name +
"'.");
90 m_thermo->restoreState(m_state);
91 m_sdot.resize(m_nsp, 0.0);
92 m_wdot.resize(m_nsp, 0.0);
93 updateConnected(
true);
95 for (
size_t n = 0; n < m_wall.size(); n++) {
103 for (
auto& S : m_surfaces) {
104 m_nv_surf += S->thermo()->nSpecies();
105 size_t nt = S->kinetics()->nTotalSpecies();
106 maxnt = std::max(maxnt, nt);
109 m_work.resize(maxnt);
112 size_t Reactor::nSensParams()
const
114 size_t ns = m_sensParams.size();
115 for (
auto& S : m_surfaces) {
116 ns += S->nSensParams();
121 void Reactor::syncState()
123 ReactorBase::syncState();
124 m_mass = m_thermo->density() * m_vol;
127 void Reactor::updateState(
double* y)
134 m_thermo->setMassFractions_NoNorm(y+3);
139 auto u_err = [
this, U](
double T) {
140 m_thermo->setState_TD(T, m_mass / m_vol);
141 return m_thermo->intEnergy_mass() * m_mass - U;
144 double T = m_thermo->temperature();
145 boost::uintmax_t maxiter = 100;
146 pair<double, double> TT;
148 TT = bmt::bracket_and_solve_root(
149 u_err, T, 1.2,
true, bmt::eps_tolerance<double>(48), maxiter);
150 }
catch (std::exception&) {
154 TT = bmt::bisect(u_err, m_thermo->minTemp(), m_thermo->maxTemp(),
155 bmt::eps_tolerance<double>(48), maxiter);
156 }
catch (std::exception& err2) {
158 m_thermo->setState_TD(T, m_mass / m_vol);
160 "{}\nat U = {}, rho = {}", err2.what(), U, m_mass / m_vol);
163 if (fabs(TT.first - TT.second) > 1e-7*TT.first) {
164 throw CanteraError(
"Reactor::updateState",
"root finding failed");
166 m_thermo->setState_TD(TT.second, m_mass / m_vol);
168 m_thermo->setDensity(m_mass/m_vol);
171 updateConnected(
true);
172 updateSurfaceState(y + m_nsp + 3);
175 void Reactor::updateSurfaceState(
double* y)
178 for (
auto& S : m_surfaces) {
179 S->setCoverages(y+loc);
180 loc += S->thermo()->nSpecies();
184 void Reactor::updateConnected(
bool updatePressure) {
186 m_enthalpy = m_thermo->enthalpy_mass();
187 if (updatePressure) {
188 m_pressure = m_thermo->pressure();
190 m_intEnergy = m_thermo->intEnergy_mass();
191 m_thermo->saveState(m_state);
195 if (m_net !=
nullptr) {
196 time = (timeIsIndependent()) ? m_net->time() : m_net->distance();
198 for (
size_t i = 0; i < m_outlet.size(); i++) {
199 m_outlet[i]->setSimTime(time);
200 m_outlet[i]->updateMassFlowRate(time);
202 for (
size_t i = 0; i < m_inlet.size(); i++) {
203 m_inlet[i]->setSimTime(time);
204 m_inlet[i]->updateMassFlowRate(time);
206 for (
size_t i = 0; i < m_wall.size(); i++) {
207 m_wall[i]->setSimTime(time);
211 void Reactor::eval(
double time,
double* LHS,
double* RHS)
213 double& dmdt = RHS[0];
214 double* mdYdt = RHS + 3;
217 m_thermo->restoreState(m_state);
218 const vector<double>& mw = m_thermo->molecularWeights();
219 const double* Y = m_thermo->massFractions();
221 evalSurfaces(LHS + m_nsp + 3, RHS + m_nsp + 3, m_sdot.data());
223 double mdot_surf =
dot(m_sdot.begin(), m_sdot.end(), mw.begin());
230 m_kin->getNetProductionRates(&m_wdot[0]);
233 for (
size_t k = 0; k < m_nsp; k++) {
235 mdYdt[k] = (m_wdot[k] * m_vol + m_sdot[k]) * mw[k];
237 mdYdt[k] -= Y[k] * mdot_surf;
246 RHS[2] = - m_thermo->pressure() * m_vdot + m_Qdot;
252 for (
auto outlet : m_outlet) {
253 double mdot = outlet->massFlowRate();
256 RHS[2] -= mdot * m_enthalpy;
261 for (
auto inlet : m_inlet) {
262 double mdot = inlet->massFlowRate();
264 for (
size_t n = 0; n < m_nsp; n++) {
265 double mdot_spec = inlet->outletSpeciesMassFlowRate(n);
267 mdYdt[n] += (mdot_spec - mdot * Y[n]);
270 RHS[2] += mdot * inlet->enthalpy_mass();
275 void Reactor::evalWalls(
double t)
280 for (
size_t i = 0; i < m_wall.size(); i++) {
281 int f = 2 * m_lr[i] - 1;
282 m_vdot -= f * m_wall[i]->expansionRate();
283 m_Qdot += f * m_wall[i]->heatRate();
287 void Reactor::evalSurfaces(
double* LHS,
double* RHS,
double* sdot)
289 fill(sdot, sdot + m_nsp, 0.0);
292 for (
auto S : m_surfaces) {
301 for (
size_t k = 1; k < nk; k++) {
302 RHS[loc + k] = m_work[k] * rs0 * surf->
size(k);
309 double wallarea = S->area();
310 for (
size_t k = 0; k < m_nsp; k++) {
311 sdot[k] += m_work[bulkloc + k] * wallarea;
316 Eigen::SparseMatrix<double> Reactor::finiteDifferenceJacobian()
320 "Reactor must be initialized first.");
325 Eigen::ArrayXd yCurrent(m_nv);
326 getState(yCurrent.data());
327 double time = (m_net !=
nullptr) ? m_net->time() : 0.0;
329 Eigen::ArrayXd yPerturbed = yCurrent;
330 Eigen::ArrayXd lhsPerturbed(m_nv), lhsCurrent(m_nv);
331 Eigen::ArrayXd rhsPerturbed(m_nv), rhsCurrent(m_nv);
334 updateState(yCurrent.data());
335 eval(time, lhsCurrent.data(), rhsCurrent.data());
337 double rel_perturb = std::sqrt(std::numeric_limits<double>::epsilon());
338 double atol = (m_net !=
nullptr) ? m_net->atol() : 1e-15;
340 for (
size_t j = 0; j < m_nv; j++) {
341 yPerturbed = yCurrent;
342 double delta_y = std::max(std::abs(yCurrent[j]), 1000 * atol) * rel_perturb;
343 yPerturbed[j] += delta_y;
345 updateState(yPerturbed.data());
348 eval(time, lhsPerturbed.data(), rhsPerturbed.data());
351 for (
size_t i = 0; i < m_nv; i++) {
352 double ydotPerturbed = rhsPerturbed[i] / lhsPerturbed[i];
353 double ydotCurrent = rhsCurrent[i] / lhsCurrent[i];
354 if (ydotCurrent != ydotPerturbed) {
355 m_jac_trips.emplace_back(
356 static_cast<int>(i),
static_cast<int>(j),
357 (ydotPerturbed - ydotCurrent) / delta_y);
361 updateState(yCurrent.data());
363 Eigen::SparseMatrix<double> jac(m_nv, m_nv);
364 jac.setFromTriplets(m_jac_trips.begin(), m_jac_trips.end());
369 void Reactor::evalSurfaces(
double* RHS,
double* sdot)
371 fill(sdot, sdot + m_nsp, 0.0);
374 for (
auto S : m_surfaces) {
383 for (
size_t k = 1; k < nk; k++) {
384 RHS[loc + k] = m_work[k] * rs0 * surf->
size(k);
391 double wallarea = S->area();
392 for (
size_t k = 0; k < m_nsp; k++) {
393 sdot[k] += m_work[bulkloc + k] * wallarea;
398 void Reactor::addSensitivityReaction(
size_t rxn)
400 if (!m_chem || rxn >= m_kin->nReactions()) {
402 "Reaction number out of range ({})", rxn);
405 size_t p = network().registerSensitivityParameter(
406 name()+
": "+m_kin->reaction(rxn)->equation(), 1.0, 1.0);
407 m_sensParams.emplace_back(
408 SensitivityParameter{rxn, p, 1.0, SensParameterType::reaction});
411 void Reactor::addSensitivitySpeciesEnthalpy(
size_t k)
413 if (k >= m_thermo->nSpecies()) {
414 throw CanteraError(
"Reactor::addSensitivitySpeciesEnthalpy",
415 "Species index out of range ({})", k);
418 size_t p = network().registerSensitivityParameter(
419 name() +
": " + m_thermo->speciesName(k) +
" enthalpy",
421 m_sensParams.emplace_back(
422 SensitivityParameter{k, p, m_thermo->Hf298SS(k),
423 SensParameterType::enthalpy});
426 size_t Reactor::speciesIndex(
const string& nm)
const
429 size_t k = m_thermo->speciesIndex(nm);
436 for (
auto& S : m_surfaces) {
448 size_t Reactor::componentIndex(
const string& nm)
const
450 size_t k = speciesIndex(nm);
453 }
else if (nm ==
"mass") {
455 }
else if (nm ==
"volume") {
457 }
else if (nm ==
"int_energy") {
464 string Reactor::componentName(
size_t k) {
471 }
else if (k >= 3 && k < neq()) {
473 if (k < m_thermo->nSpecies()) {
474 return m_thermo->speciesName(k);
476 k -= m_thermo->nSpecies();
478 for (
auto& S : m_surfaces) {
480 if (k < th->nSpecies()) {
487 throw CanteraError(
"Reactor::componentName",
"Index is out of bounds.");
490 void Reactor::applySensitivity(
double* params)
495 for (
auto& p : m_sensParams) {
496 if (p.type == SensParameterType::reaction) {
497 p.value = m_kin->multiplier(p.local);
498 m_kin->setMultiplier(p.local, p.value*params[p.global]);
499 }
else if (p.type == SensParameterType::enthalpy) {
500 m_thermo->modifyOneHf298SS(p.local, p.value + params[p.global]);
503 for (
auto& S : m_surfaces) {
504 S->setSensitivityParameters(params);
506 m_thermo->invalidateCache();
508 m_kin->invalidateCache();
512 void Reactor::resetSensitivity(
double* params)
517 for (
auto& p : m_sensParams) {
518 if (p.type == SensParameterType::reaction) {
519 m_kin->setMultiplier(p.local, p.value);
520 }
else if (p.type == SensParameterType::enthalpy) {
521 m_thermo->resetHf298(p.local);
524 for (
auto& S : m_surfaces) {
525 S->resetSensitivityParameters();
527 m_thermo->invalidateCache();
529 m_kin->invalidateCache();
533 void Reactor::setAdvanceLimits(
const double *limits)
537 "Error: reactor is empty.");
539 m_advancelimits.assign(limits, limits + m_nv);
542 if (std::none_of(m_advancelimits.begin(), m_advancelimits.end(),
543 [](
double val){return val>0;})) {
544 m_advancelimits.resize(0);
548 bool Reactor::getAdvanceLimits(
double *limits)
const
550 bool has_limit = hasAdvanceLimits();
552 std::copy(m_advancelimits.begin(), m_advancelimits.end(), limits);
554 std::fill(limits, limits + m_nv, -1.0);
559 void Reactor::setAdvanceLimit(
const string& nm,
const double limit)
561 size_t k = componentIndex(nm);
563 throw CanteraError(
"Reactor::setAdvanceLimit",
"No component named '{}'", nm);
568 "Error: reactor is empty.");
573 "Cannot set limit on a reactor that is not "
574 "assigned to a ReactorNet object.");
578 }
else if (k > m_nv) {
580 "Index out of bounds.");
582 m_advancelimits.resize(m_nv, -1.0);
583 m_advancelimits[k] = limit;
586 if (std::none_of(m_advancelimits.begin(), m_advancelimits.end(),
587 [](
double val){return val>0;})) {
588 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...