Cantera  3.1.0a1
MoleReactor.cpp
Go to the documentation of this file.
1 //! @file MoleReactor.cpp A zero-dimensional reactor with a moles as the
2 //! state
3 
4 // This file is part of Cantera. See License.txt in the top-level directory or
5 // at https://cantera.org/license.txt for license and copyright information.
6 
12 #include "cantera/base/utilities.h"
13 #include <boost/math/tools/roots.hpp>
14 
15 using namespace std;
16 namespace bmt = boost::math::tools;
17 
18 namespace Cantera
19 {
20 
21 void MoleReactor::getSurfaceInitialConditions(double* y)
22 {
23  size_t loc = 0;
24  for (auto& S : m_surfaces) {
25  double area = S->area();
26  auto currPhase = S->thermo();
27  size_t tempLoc = currPhase->nSpecies();
28  double surfDensity = currPhase->siteDensity();
29  S->getCoverages(y + loc);
30  // convert coverages to moles
31  for (size_t i = 0; i < tempLoc; i++) {
32  y[i + loc] = y[i + loc] * area * surfDensity / currPhase->size(i);
33  }
34  loc += tempLoc;
35  }
36 }
37 
38 void MoleReactor::initialize(double t0)
39 {
40  Reactor::initialize(t0);
41  m_nv -= 1; // moles gives the state one fewer variables
42 }
43 
44 void MoleReactor::updateSurfaceState(double* y)
45 {
46  size_t loc = 0;
47  vector<double> coverages(m_nv_surf, 0.0);
48  for (auto& S : m_surfaces) {
49  auto surf = S->thermo();
50  double invArea = 1/S->area();
51  double invSurfDensity = 1/surf->siteDensity();
52  size_t tempLoc = surf->nSpecies();
53  for (size_t i = 0; i < tempLoc; i++) {
54  coverages[i + loc] = y[i + loc] * invArea * surf->size(i) * invSurfDensity;
55  }
56  S->setCoverages(coverages.data()+loc);
57  loc += tempLoc;
58  }
59 }
60 
61 void MoleReactor::evalSurfaces(double* LHS, double* RHS, double* sdot)
62 {
63  fill(sdot, sdot + m_nsp, 0.0);
64  size_t loc = 0; // offset into ydot
65  for (auto S : m_surfaces) {
66  Kinetics* kin = S->kinetics();
67  SurfPhase* surf = S->thermo();
68  double wallarea = S->area();
69  size_t nk = surf->nSpecies();
70  S->syncState();
71  kin->getNetProductionRates(&m_work[0]);
72  for (size_t k = 0; k < nk; k++) {
73  RHS[loc + k] = m_work[k] * wallarea / surf->size(k);
74  }
75  loc += nk;
76 
77  size_t bulkloc = kin->kineticsSpeciesIndex(m_thermo->speciesName(0));
78 
79  for (size_t k = 0; k < m_nsp; k++) {
80  sdot[k] += m_work[bulkloc + k] * wallarea;
81  }
82  }
83 }
84 
85 void MoleReactor::addSurfaceJacobian(vector<Eigen::Triplet<double>> &triplets)
86 {
87  size_t offset = m_nsp;
88  for (auto& S : m_surfaces) {
89  S->syncState();
90  double A = S->area();
91  auto kin = S->kinetics();
92  size_t nk = S->thermo()->nSpecies();
93  // index of gas and surface phases to check if the species is in gas or surface
94  size_t spi = 0;
95  size_t gpi = kin->speciesPhaseIndex(kin->kineticsSpeciesIndex(
96  m_thermo->speciesName(0)));
97  // get surface jacobian in concentration units
98  Eigen::SparseMatrix<double> surfJac = kin->netProductionRates_ddCi();
99  // loop through surface specific jacobian and add elements to triplets vector
100  // accordingly
101  for (int k=0; k<surfJac.outerSize(); ++k) {
102  for (Eigen::SparseMatrix<double>::InnerIterator it(surfJac, k); it; ++it) {
103  size_t row = it.row();
104  size_t col = it.col();
105  auto& rowPhase = kin->speciesPhase(row);
106  auto& colPhase = kin->speciesPhase(col);
107  size_t rpi = kin->phaseIndex(rowPhase.name());
108  size_t cpi = kin->phaseIndex(colPhase.name());
109  // check if the reactor kinetics object contains both phases to avoid
110  // any solid phases which may be included then use phases to map surf
111  // kinetics indicies to reactor kinetic indices
112  if ((rpi == spi || rpi == gpi) && (cpi == spi || cpi == gpi) ) {
113  // subtract start of phase
114  row -= kin->kineticsSpeciesIndex(0, rpi);
115  col -= kin->kineticsSpeciesIndex(0, cpi);
116  // since the gas phase is the first phase in the reactor state
117  // vector add the offset only if it is a surf species index to
118  // both row and col
119  row = (rpi != spi) ? row : row + offset;
120  // determine appropriate scalar to account for dimensionality
121  // gas phase species indices will be less than m_nsp
122  // so use volume if that is the case or area otherwise
123  double scalar = A;
124  if (cpi == spi) {
125  col += offset;
126  scalar /= A;
127  } else {
128  scalar /= m_vol;
129  }
130  // push back scaled value triplet
131  triplets.emplace_back(static_cast<int>(row), static_cast<int>(col),
132  scalar * it.value());
133  }
134  }
135  }
136  // add species in this surface to the offset
137  offset += nk;
138  }
139 }
140 
141 void MoleReactor::getMoles(double* y)
142 {
143  // Use inverse molecular weights to convert to moles
144  const double* Y = m_thermo->massFractions();
145  const vector<double>& imw = m_thermo->inverseMolecularWeights();
146  for (size_t i = 0; i < m_nsp; i++) {
147  y[i] = m_mass * imw[i] * Y[i];
148  }
149 }
150 
151 void MoleReactor::setMassFromMoles(double* y)
152 {
153  const vector<double>& mw = m_thermo->molecularWeights();
154  // calculate mass from moles
155  m_mass = 0;
156  for (size_t i = 0; i < m_nsp; i++) {
157  m_mass += y[i] * mw[i];
158  }
159 }
160 
161 void MoleReactor::getState(double* y)
162 {
163  if (m_thermo == 0) {
164  throw CanteraError("MoleReactor::getState",
165  "Error: reactor is empty.");
166  }
167  m_thermo->restoreState(m_state);
168  // set the first component to the internal energy
169  m_mass = m_thermo->density() * m_vol;
170  y[0] = m_thermo->intEnergy_mass() * m_mass;
171  // set the second component to the total volume
172  y[1] = m_vol;
173  // set components y+2 ... y+K+2 to the moles of each species
174  getMoles(y + m_sidx);
175  // set the remaining components to the surface species
176  // moles on walls
177  getSurfaceInitialConditions(y+m_nsp+m_sidx);
178 }
179 
180 void MoleReactor::updateState(double* y)
181 {
182  // The components of y are [0] total internal energy, [1] the total volume, and
183  // [2...K+3] are the moles of each species, and [K+3...] are the moles
184  // of surface species on each wall.
185  setMassFromMoles(y + m_sidx);
186  m_vol = y[1];
187  m_thermo->setMolesNoTruncate(y + m_sidx);
188  if (m_energy) {
189  double U = y[0];
190  // Residual function: error in internal energy as a function of T
191  auto u_err = [this, U](double T) {
192  m_thermo->setState_TD(T, m_mass / m_vol);
193  return m_thermo->intEnergy_mass() * m_mass - U;
194  };
195  double T = m_thermo->temperature();
196  boost::uintmax_t maxiter = 100;
197  pair<double, double> TT;
198  try {
199  TT = bmt::bracket_and_solve_root(
200  u_err, T, 1.2, true, bmt::eps_tolerance<double>(48), maxiter);
201  } catch (std::exception&) {
202  // Try full-range bisection if bracketing fails (for example, near
203  // temperature limits for the phase's equation of state)
204  try {
205  TT = bmt::bisect(u_err, m_thermo->minTemp(), m_thermo->maxTemp(),
206  bmt::eps_tolerance<double>(48), maxiter);
207  } catch (std::exception& err2) {
208  // Set m_thermo back to a reasonable state if root finding fails
209  m_thermo->setState_TD(T, m_mass / m_vol);
210  throw CanteraError("MoleReactor::updateState",
211  "{}\nat U = {}, rho = {}", err2.what(), U, m_mass / m_vol);
212  }
213  }
214  if (fabs(TT.first - TT.second) > 1e-7*TT.first) {
215  throw CanteraError("MoleReactor::updateState", "root finding failed");
216  }
217  m_thermo->setState_TD(TT.second, m_mass / m_vol);
218  } else {
219  m_thermo->setDensity(m_mass / m_vol);
220  }
221  updateConnected(true);
222  updateSurfaceState(y + m_nsp + m_sidx);
223 }
224 
225 void MoleReactor::eval(double time, double* LHS, double* RHS)
226 {
227  double* dndt = RHS + m_sidx; // moles per time
228 
229  evalWalls(time);
230  m_thermo->restoreState(m_state);
231 
232  evalSurfaces(LHS + m_nsp + m_sidx, RHS + m_nsp + m_sidx, m_sdot.data());
233  // inverse molecular weights for conversion
234  const vector<double>& imw = m_thermo->inverseMolecularWeights();
235  // volume equation
236  RHS[1] = m_vdot;
237 
238  if (m_chem) {
239  m_kin->getNetProductionRates(&m_wdot[0]); // "omega dot"
240  }
241 
242  // Energy equation.
243  // @f[
244  // \dot U = -P\dot V + A \dot q + \dot m_{in} h_{in} - \dot m_{out} h.
245  // @f]
246  if (m_energy) {
247  RHS[0] = - m_thermo->pressure() * m_vdot + m_Qdot;
248  } else {
249  RHS[0] = 0.0;
250  }
251 
252  for (size_t k = 0; k < m_nsp; k++) {
253  // production in gas phase and from surfaces
254  dndt[k] = m_wdot[k] * m_vol + m_sdot[k];
255  }
256 
257  // add terms for outlets
258  for (auto outlet : m_outlet) {
259  // flow of species into system and dilution by other species
260  for (size_t n = 0; n < m_nsp; n++) {
261  dndt[n] -= outlet->outletSpeciesMassFlowRate(n) * imw[n];
262  }
263  // energy update based on mass flow
264  double mdot = outlet->massFlowRate();
265  if (m_energy) {
266  RHS[0] -= mdot * m_enthalpy;
267  }
268  }
269 
270  // add terms for inlets
271  for (auto inlet : m_inlet) {
272  double mdot = inlet->massFlowRate();
273  for (size_t n = 0; n < m_nsp; n++) {
274  // flow of species into system and dilution by other species
275  dndt[n] += inlet->outletSpeciesMassFlowRate(n) * imw[n];
276  }
277  if (m_energy) {
278  RHS[0] += mdot * inlet->enthalpy_mass();
279  }
280  }
281 }
282 
283 
284 size_t MoleReactor::componentIndex(const string& nm) const
285 {
286  size_t k = speciesIndex(nm);
287  if (k != npos) {
288  return k + m_sidx;
289  } else if (nm == "int_energy") {
290  return 0;
291  } else if (nm == "volume") {
292  return 1;
293  } else {
294  return npos;
295  }
296 }
297 
298 string MoleReactor::componentName(size_t k) {
299  if (k == 0) {
300  return "int_energy";
301  } else if (k == 1) {
302  return "volume";
303  } else if (k >= m_sidx && k < neq()) {
304  k -= m_sidx;
305  if (k < m_thermo->nSpecies()) {
306  return m_thermo->speciesName(k);
307  } else {
308  k -= m_thermo->nSpecies();
309  }
310  for (auto& S : m_surfaces) {
311  ThermoPhase* th = S->thermo();
312  if (k < th->nSpecies()) {
313  return th->speciesName(k);
314  } else {
315  k -= th->nSpecies();
316  }
317  }
318  }
319  throw CanteraError("MoleReactor::componentName", "Index is out of bounds.");
320 }
321 
322 }
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,...
Base class for exceptions thrown by Cantera classes.
Definition: ctexceptions.h:66
Public interface for kinetics managers.
Definition: Kinetics.h:125
size_t kineticsSpeciesIndex(size_t k, size_t n) const
The location of species k of phase n in species arrays.
Definition: Kinetics.h:276
virtual void getNetProductionRates(double *wdot)
Species net production rates [kmol/m^3/s or kmol/m^2/s].
Definition: Kinetics.cpp:363
size_t nSpecies() const
Returns the number of species in the phase.
Definition: Phase.h:231
string speciesName(size_t k) const
Name of the species with index k.
Definition: Phase.cpp:142
A simple thermodynamic model for a surface phase, assuming an ideal solution model.
Definition: SurfPhase.h:98
double size(size_t k) const
Returns the number of sites occupied by one molecule of species k.
Definition: SurfPhase.h:221
Base class for a phase with thermodynamic properties.
Definition: ThermoPhase.h:390
Namespace for the Cantera kernel.
Definition: AnyMap.cpp:564
const size_t npos
index returned by functions to indicate "no position"
Definition: ct_defs.h:180
offset
Offsets of solution components in the 1D solution array.
Definition: StFlow.h:24
Various templated functions that carry out common vector and polynomial operations (see Templated Arr...