Cantera  2.5.1
Reactor.cpp
Go to the documentation of this file.
1 //! @file Reactor.cpp A zero-dimensional reactor
2 
3 // This file is part of Cantera. See License.txt in the top-level directory or
4 // at https://cantera.org/license.txt for license and copyright information.
5 
8 #include "cantera/zeroD/Wall.h"
12 
13 #include <boost/math/tools/roots.hpp>
14 
15 using namespace std;
16 namespace bmt = boost::math::tools;
17 
18 namespace Cantera
19 {
20 Reactor::Reactor() :
21  m_kin(0),
22  m_vdot(0.0),
23  m_Q(0.0),
24  m_mass(0.0),
25  m_chem(false),
26  m_energy(true),
27  m_nv(0)
28 {}
29 
30 void Reactor::setKineticsMgr(Kinetics& kin)
31 {
32  m_kin = &kin;
33  if (m_kin->nReactions() == 0) {
34  setChemistry(false);
35  } else {
36  setChemistry(true);
37  }
38 }
39 
40 void Reactor::getState(double* y)
41 {
42  if (m_thermo == 0) {
43  throw CanteraError("Reactor::getState",
44  "Error: reactor is empty.");
45  }
46  m_thermo->restoreState(m_state);
47 
48  // set the first component to the total mass
49  m_mass = m_thermo->density() * m_vol;
50  y[0] = m_mass;
51 
52  // set the second component to the total volume
53  y[1] = m_vol;
54 
55  // set the third component to the total internal energy
56  y[2] = m_thermo->intEnergy_mass() * m_mass;
57 
58  // set components y+3 ... y+K+2 to the mass fractions of each species
59  m_thermo->getMassFractions(y+3);
60 
61  // set the remaining components to the surface species
62  // coverages on the walls
63  getSurfaceInitialConditions(y + m_nsp + 3);
64 }
65 
66 void Reactor::getSurfaceInitialConditions(double* y)
67 {
68  size_t loc = 0;
69  for (auto& S : m_surfaces) {
70  S->getCoverages(y + loc);
71  loc += S->thermo()->nSpecies();
72  }
73 }
74 
75 void Reactor::initialize(doublereal t0)
76 {
77  if (!m_thermo || (m_chem && !m_kin)) {
78  throw CanteraError("Reactor::initialize", "Reactor contents not set"
79  " for reactor '" + m_name + "'.");
80  }
81  m_thermo->restoreState(m_state);
82  m_sdot.resize(m_nsp, 0.0);
83  m_wdot.resize(m_nsp, 0.0);
84  updateConnected(true);
85 
86  for (size_t n = 0; n < m_wall.size(); n++) {
87  WallBase* W = m_wall[n];
88  W->initialize();
89  }
90 
91  m_nv = m_nsp + 3;
92  size_t maxnt = 0;
93  for (auto& S : m_surfaces) {
94  m_nv += S->thermo()->nSpecies();
95  size_t nt = S->kinetics()->nTotalSpecies();
96  maxnt = std::max(maxnt, nt);
97  if (m_chem && &m_kin->thermo(0) != &S->kinetics()->thermo(0)) {
98  throw CanteraError("Reactor::initialize",
99  "First phase of all kinetics managers must be the gas.");
100  }
101  }
102  m_work.resize(maxnt);
103 }
104 
105 size_t Reactor::nSensParams()
106 {
107  size_t ns = m_sensParams.size();
108  for (auto& S : m_surfaces) {
109  ns += S->nSensParams();
110  }
111  return ns;
112 }
113 
114 void Reactor::syncState()
115 {
116  ReactorBase::syncState();
117  m_mass = m_thermo->density() * m_vol;
118 }
119 
120 void Reactor::updateState(doublereal* y)
121 {
122  // The components of y are [0] the total mass, [1] the total volume,
123  // [2] the total internal energy, [3...K+3] are the mass fractions of each
124  // species, and [K+3...] are the coverages of surface species on each wall.
125  m_mass = y[0];
126  m_vol = y[1];
127  m_thermo->setMassFractions_NoNorm(y+3);
128 
129  if (m_energy) {
130  double U = y[2];
131  // Residual function: error in internal energy as a function of T
132  auto u_err = [this, U](double T) {
133  m_thermo->setState_TR(T, m_mass / m_vol);
134  return m_thermo->intEnergy_mass() * m_mass - U;
135  };
136 
137  double T = m_thermo->temperature();
138  boost::uintmax_t maxiter = 100;
139  std::pair<double, double> TT;
140  try {
141  TT = bmt::bracket_and_solve_root(
142  u_err, T, 1.2, true, bmt::eps_tolerance<double>(48), maxiter);
143  } catch (std::exception&) {
144  // Try full-range bisection if bracketing fails (e.g. near
145  // temperature limits for the phase's equation of state)
146  try {
147  TT = bmt::bisect(u_err, m_thermo->minTemp(), m_thermo->maxTemp(),
148  bmt::eps_tolerance<double>(48), maxiter);
149  } catch (std::exception& err2) {
150  // Set m_thermo back to a reasonable state if root finding fails
151  m_thermo->setState_TR(T, m_mass / m_vol);
152  throw CanteraError("Reactor::updateState",
153  "{}\nat U = {}, rho = {}", err2.what(), U, m_mass / m_vol);
154  }
155  }
156  if (fabs(TT.first - TT.second) > 1e-7*TT.first) {
157  throw CanteraError("Reactor::updateState", "root finding failed");
158  }
159  m_thermo->setState_TR(TT.second, m_mass / m_vol);
160  } else {
161  m_thermo->setDensity(m_mass/m_vol);
162  }
163 
164  updateSurfaceState(y + m_nsp + 3);
165  updateConnected(true);
166 }
167 
168 void Reactor::updateSurfaceState(double* y)
169 {
170  size_t loc = 0;
171  for (auto& S : m_surfaces) {
172  S->setCoverages(y+loc);
173  loc += S->thermo()->nSpecies();
174  }
175 }
176 
177 void Reactor::updateConnected(bool updatePressure) {
178  // save parameters needed by other connected reactors
179  m_enthalpy = m_thermo->enthalpy_mass();
180  if (updatePressure) {
181  m_pressure = m_thermo->pressure();
182  }
183  m_intEnergy = m_thermo->intEnergy_mass();
184  m_thermo->saveState(m_state);
185 
186  // Update the mass flow rate of connected flow devices
187  double time = m_net->time();
188  for (size_t i = 0; i < m_outlet.size(); i++) {
189  m_outlet[i]->updateMassFlowRate(time);
190  }
191  for (size_t i = 0; i < m_inlet.size(); i++) {
192  m_inlet[i]->updateMassFlowRate(time);
193  }
194 }
195 
196 void Reactor::evalEqs(doublereal time, doublereal* y,
197  doublereal* ydot, doublereal* params)
198 {
199  double dmdt = 0.0; // dm/dt (gas phase)
200  double* dYdt = ydot + 3;
201 
202  evalWalls(time);
203  applySensitivity(params);
204  m_thermo->restoreState(m_state);
205  double mdot_surf = evalSurfaces(time, ydot + m_nsp + 3);
206  dmdt += mdot_surf; // mass added to gas phase from surface reactions
207 
208  // volume equation
209  ydot[1] = m_vdot;
210 
211  const vector_fp& mw = m_thermo->molecularWeights();
212  const doublereal* Y = m_thermo->massFractions();
213 
214  if (m_chem) {
215  m_kin->getNetProductionRates(&m_wdot[0]); // "omega dot"
216  }
217 
218  for (size_t k = 0; k < m_nsp; k++) {
219  // production in gas phase and from surfaces
220  dYdt[k] = (m_wdot[k] * m_vol + m_sdot[k]) * mw[k] / m_mass;
221  // dilution by net surface mass flux
222  dYdt[k] -= Y[k] * mdot_surf / m_mass;
223  }
224 
225  // Energy equation.
226  // \f[
227  // \dot U = -P\dot V + A \dot q + \dot m_{in} h_{in} - \dot m_{out} h.
228  // \f]
229  if (m_energy) {
230  ydot[2] = - m_thermo->pressure() * m_vdot - m_Q;
231  } else {
232  ydot[2] = 0.0;
233  }
234 
235  // add terms for outlets
236  for (auto outlet : m_outlet) {
237  double mdot = outlet->massFlowRate();
238  dmdt -= mdot; // mass flow out of system
239  if (m_energy) {
240  ydot[2] -= mdot * m_enthalpy;
241  }
242  }
243 
244  // add terms for inlets
245  for (auto inlet : m_inlet) {
246  double mdot = inlet->massFlowRate();
247  dmdt += mdot; // mass flow into system
248  for (size_t n = 0; n < m_nsp; n++) {
249  double mdot_spec = inlet->outletSpeciesMassFlowRate(n);
250  // flow of species into system and dilution by other species
251  dYdt[n] += (mdot_spec - mdot * Y[n]) / m_mass;
252  }
253  if (m_energy) {
254  ydot[2] += mdot * inlet->enthalpy_mass();
255  }
256  }
257 
258  ydot[0] = dmdt;
259  resetSensitivity(params);
260 }
261 
262 void Reactor::evalWalls(double t)
263 {
264  m_vdot = 0.0;
265  m_Q = 0.0;
266  for (size_t i = 0; i < m_wall.size(); i++) {
267  int lr = 1 - 2*m_lr[i];
268  m_vdot += lr*m_wall[i]->vdot(t);
269  m_Q += lr*m_wall[i]->Q(t);
270  }
271 }
272 
273 double Reactor::evalSurfaces(double t, double* ydot)
274 {
275  const vector_fp& mw = m_thermo->molecularWeights();
276  fill(m_sdot.begin(), m_sdot.end(), 0.0);
277  size_t loc = 0; // offset into ydot
278  double mdot_surf = 0.0; // net mass flux from surface
279 
280  for (auto S : m_surfaces) {
281  Kinetics* kin = S->kinetics();
282  SurfPhase* surf = S->thermo();
283 
284  double rs0 = 1.0/surf->siteDensity();
285  size_t nk = surf->nSpecies();
286  double sum = 0.0;
287  surf->setTemperature(m_state[0]);
288  S->syncCoverages();
289  kin->getNetProductionRates(&m_work[0]);
290  size_t ns = kin->surfacePhaseIndex();
291  size_t surfloc = kin->kineticsSpeciesIndex(0,ns);
292  for (size_t k = 1; k < nk; k++) {
293  ydot[loc + k] = m_work[surfloc+k]*rs0*surf->size(k);
294  sum -= ydot[loc + k];
295  }
296  ydot[loc] = sum;
297  loc += nk;
298 
299  size_t bulkloc = kin->kineticsSpeciesIndex(m_thermo->speciesName(0));
300  double wallarea = S->area();
301  for (size_t k = 0; k < m_nsp; k++) {
302  m_sdot[k] += m_work[bulkloc + k] * wallarea;
303  mdot_surf += m_sdot[k] * mw[k];
304  }
305  }
306  return mdot_surf;
307 }
308 
309 void Reactor::addSensitivityReaction(size_t rxn)
310 {
311  if (!m_chem || rxn >= m_kin->nReactions()) {
312  throw CanteraError("Reactor::addSensitivityReaction",
313  "Reaction number out of range ({})", rxn);
314  }
315 
316  size_t p = network().registerSensitivityParameter(
317  name()+": "+m_kin->reactionString(rxn), 1.0, 1.0);
318  m_sensParams.emplace_back(
319  SensitivityParameter{rxn, p, 1.0, SensParameterType::reaction});
320 }
321 
322 void Reactor::addSensitivitySpeciesEnthalpy(size_t k)
323 {
324  if (k >= m_thermo->nSpecies()) {
325  throw CanteraError("Reactor::addSensitivitySpeciesEnthalpy",
326  "Species index out of range ({})", k);
327  }
328 
329  size_t p = network().registerSensitivityParameter(
330  name() + ": " + m_thermo->speciesName(k) + " enthalpy",
331  0.0, GasConstant * 298.15);
332  m_sensParams.emplace_back(
333  SensitivityParameter{k, p, m_thermo->Hf298SS(k),
334  SensParameterType::enthalpy});
335 }
336 
337 size_t Reactor::speciesIndex(const string& nm) const
338 {
339  // check for a gas species name
340  size_t k = m_thermo->speciesIndex(nm);
341  if (k != npos) {
342  return k;
343  }
344 
345  // check for a wall species
346  size_t offset = m_nsp;
347  for (auto& S : m_surfaces) {
348  ThermoPhase* th = S->thermo();
349  k = th->speciesIndex(nm);
350  if (k != npos) {
351  return k + offset;
352  } else {
353  offset += th->nSpecies();
354  }
355  }
356  return npos;
357 }
358 
359 size_t Reactor::componentIndex(const string& nm) const
360 {
361  size_t k = speciesIndex(nm);
362  if (k != npos) {
363  return k + 3;
364  } else if (nm == "mass") {
365  return 0;
366  } else if (nm == "volume") {
367  return 1;
368  } else if (nm == "int_energy") {
369  return 2;
370  } else {
371  return npos;
372  }
373 }
374 
375 std::string Reactor::componentName(size_t k) {
376  if (k == 0) {
377  return "mass";
378  } else if (k == 1) {
379  return "volume";
380  } else if (k == 2) {
381  return "int_energy";
382  } else if (k >= 3 && k < neq()) {
383  k -= 3;
384  if (k < m_thermo->nSpecies()) {
385  return m_thermo->speciesName(k);
386  } else {
387  k -= m_thermo->nSpecies();
388  }
389  for (auto& S : m_surfaces) {
390  ThermoPhase* th = S->thermo();
391  if (k < th->nSpecies()) {
392  return th->speciesName(k);
393  } else {
394  k -= th->nSpecies();
395  }
396  }
397  }
398  throw CanteraError("Reactor::componentName", "Index is out of bounds.");
399 }
400 
401 void Reactor::applySensitivity(double* params)
402 {
403  if (!params) {
404  return;
405  }
406  for (auto& p : m_sensParams) {
407  if (p.type == SensParameterType::reaction) {
408  p.value = m_kin->multiplier(p.local);
409  m_kin->setMultiplier(p.local, p.value*params[p.global]);
410  } else if (p.type == SensParameterType::enthalpy) {
411  m_thermo->modifyOneHf298SS(p.local, p.value + params[p.global]);
412  }
413  }
414  for (auto& S : m_surfaces) {
415  S->setSensitivityParameters(params);
416  }
417  m_thermo->invalidateCache();
418  if (m_kin) {
419  m_kin->invalidateCache();
420  }
421 }
422 
423 void Reactor::resetSensitivity(double* params)
424 {
425  if (!params) {
426  return;
427  }
428  for (auto& p : m_sensParams) {
429  if (p.type == SensParameterType::reaction) {
430  m_kin->setMultiplier(p.local, p.value);
431  } else if (p.type == SensParameterType::enthalpy) {
432  m_thermo->resetHf298(p.local);
433  }
434  }
435  for (auto& S : m_surfaces) {
436  S->resetSensitivityParameters();
437  }
438  m_thermo->invalidateCache();
439  if (m_kin) {
440  m_kin->invalidateCache();
441  }
442 }
443 
444 void Reactor::setAdvanceLimits(const double *limits)
445 {
446  if (m_thermo == 0) {
447  throw CanteraError("Reactor::setAdvanceLimits",
448  "Error: reactor is empty.");
449  }
450  m_advancelimits.assign(limits, limits + m_nv);
451 
452  // resize to zero length if no limits are set
453  if (std::none_of(m_advancelimits.begin(), m_advancelimits.end(),
454  [](double val){return val>0;})) {
455  m_advancelimits.resize(0);
456  }
457 }
458 
459 bool Reactor::getAdvanceLimits(double *limits)
460 {
461  bool has_limit = hasAdvanceLimits();
462  if (has_limit) {
463  std::copy(m_advancelimits.begin(), m_advancelimits.end(), limits);
464  } else {
465  std::fill(limits, limits + m_nv, -1.0);
466  }
467  return has_limit;
468 }
469 
470 void Reactor::setAdvanceLimit(const string& nm, const double limit)
471 {
472  size_t k = componentIndex(nm);
473 
474  if (m_thermo == 0) {
475  throw CanteraError("Reactor::setAdvanceLimit",
476  "Error: reactor is empty.");
477  }
478  if (m_nv == 0) {
479  if (m_net == 0) {
480  throw CanteraError("Reactor::setAdvanceLimit",
481  "Cannot set limit on a reactor that is not "
482  "assigned to a ReactorNet object.");
483  } else {
484  m_net->initialize();
485  }
486  } else if (k > m_nv) {
487  throw CanteraError("Reactor::setAdvanceLimit",
488  "Index out of bounds.");
489  }
490  m_advancelimits.resize(m_nv, -1.0);
491  m_advancelimits[k] = limit;
492 
493  // resize to zero length if no limits are set
494  if (std::none_of(m_advancelimits.begin(), m_advancelimits.end(),
495  [](double val){return val>0;})) {
496  m_advancelimits.resize(0);
497  }
498 }
499 
500 }
Header file for class ReactorSurface.
Header for a simple thermodynamics model of a surface phase derived from ThermoPhase,...
Header file for base class WallBase.
Base class for exceptions thrown by Cantera classes.
Definition: ctexceptions.h:61
Public interface for kinetics managers.
Definition: Kinetics.h:111
virtual void getNetProductionRates(doublereal *wdot)
Species net production rates [kmol/m^3/s or kmol/m^2/s].
Definition: Kinetics.cpp:433
size_t kineticsSpeciesIndex(size_t k, size_t n) const
The location of species k of phase n in species arrays.
Definition: Kinetics.h:261
size_t surfacePhaseIndex() const
This returns the integer index of the phase which has ThermoPhase type cSurf.
Definition: Kinetics.h:202
size_t nSpecies() const
Returns the number of species in the phase.
Definition: Phase.h:285
std::string speciesName(size_t k) const
Name of the species with index k.
Definition: Phase.cpp:229
size_t speciesIndex(const std::string &name) const
Returns the index of a species named 'name' within the Phase object.
Definition: Phase.cpp:201
virtual void setTemperature(const doublereal temp)
Set the internally stored temperature of the phase (K).
Definition: Phase.h:724
A simple thermodynamic model for a surface phase, assuming an ideal solution model.
Definition: SurfPhase.h:143
virtual double size(size_t k) const
Returns the number of sites occupied by one molecule of species k.
Definition: SurfPhase.h:327
doublereal siteDensity()
Returns the site density.
Definition: SurfPhase.h:322
Base class for a phase with thermodynamic properties.
Definition: ThermoPhase.h:102
Base class for 'walls' (walls, pistons, etc.) connecting reactors.
Definition: Wall.h:29
virtual void initialize()
Called just before the start of integration.
Definition: Wall.h:85
const size_t npos
index returned by functions to indicate "no position"
Definition: ct_defs.h:188
std::vector< double > vector_fp
Turn on the use of stl vectors for the basic array type within cantera Vector of doubles.
Definition: ct_defs.h:180
const double GasConstant
Universal Gas Constant [J/kmol/K].
Definition: ct_defs.h:109
Namespace for the Cantera kernel.
Definition: AnyMap.cpp:264