Cantera 2.6.0
ReactorNet.cpp
Go to the documentation of this file.
1//! @file ReactorNet.cpp
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
10#include "cantera/base/Array.h"
12
13using namespace std;
14
15namespace Cantera
16{
17
18ReactorNet::ReactorNet() :
19 m_integ(newIntegrator("CVODE")),
20 m_time(0.0), m_init(false), m_integrator_init(false),
21 m_nv(0), m_rtol(1.0e-9), m_rtolsens(1.0e-4),
22 m_atols(1.0e-15), m_atolsens(1.0e-6),
23 m_maxstep(0.0), m_maxErrTestFails(0),
24 m_verbose(false), m_checked_eval_deprecation(false)
25{
26 suppressErrors(true);
27
28 // use backward differencing, with a full Jacobian computed
29 // numerically, and use a Newton linear iterator
30 m_integ->setMethod(BDF_Method);
31 m_integ->setProblemType(DENSE + NOJAC);
32}
33
34ReactorNet::~ReactorNet()
35{
36}
37
38void ReactorNet::setInitialTime(double time)
39{
40 m_time = time;
41 m_integrator_init = false;
42}
43
44void ReactorNet::setMaxTimeStep(double maxstep)
45{
46 m_maxstep = maxstep;
47 m_init = false;
48}
49
50void ReactorNet::setMaxErrTestFails(int nmax)
51{
52 m_maxErrTestFails = nmax;
53 m_init = false;
54}
55
56void ReactorNet::setTolerances(double rtol, double atol)
57{
58 if (rtol >= 0.0) {
59 m_rtol = rtol;
60 }
61 if (atol >= 0.0) {
62 m_atols = atol;
63 }
64 m_init = false;
65}
66
67void ReactorNet::setSensitivityTolerances(double rtol, double atol)
68{
69 if (rtol >= 0.0) {
70 m_rtolsens = rtol;
71 }
72 if (atol >= 0.0) {
73 m_atolsens = atol;
74 }
75 m_init = false;
76}
77
78void ReactorNet::initialize()
79{
80 m_nv = 0;
81 debuglog("Initializing reactor network.\n", m_verbose);
82 if (m_reactors.empty()) {
83 throw CanteraError("ReactorNet::initialize",
84 "no reactors in network!");
85 }
86 m_start.assign(1, 0);
87 for (size_t n = 0; n < m_reactors.size(); n++) {
88 Reactor& r = *m_reactors[n];
89 r.initialize(m_time);
90 size_t nv = r.neq();
91 m_nv += nv;
92 m_start.push_back(m_nv);
93
94 if (m_verbose) {
95 writelog("Reactor {:d}: {:d} variables.\n", n, nv);
96 writelog(" {:d} sensitivity params.\n", r.nSensParams());
97 }
98 if (r.type() == "FlowReactor" && m_reactors.size() > 1) {
99 throw CanteraError("ReactorNet::initialize",
100 "FlowReactors must be used alone.");
101 }
102 }
103
104 m_ydot.resize(m_nv,0.0);
105 m_yest.resize(m_nv,0.0);
106 m_advancelimits.resize(m_nv,-1.0);
107 m_atol.resize(neq());
108 fill(m_atol.begin(), m_atol.end(), m_atols);
109 m_integ->setTolerances(m_rtol, neq(), m_atol.data());
110 m_integ->setSensitivityTolerances(m_rtolsens, m_atolsens);
111 m_integ->setMaxStepSize(m_maxstep);
112 m_integ->setMaxErrTestFails(m_maxErrTestFails);
113 if (m_verbose) {
114 writelog("Number of equations: {:d}\n", neq());
115 writelog("Maximum time step: {:14.6g}\n", m_maxstep);
116 }
117 m_integ->initialize(m_time, *this);
118 m_integrator_init = true;
119 m_init = true;
120}
121
122void ReactorNet::reinitialize()
123{
124 if (m_init) {
125 debuglog("Re-initializing reactor network.\n", m_verbose);
126 m_integ->reinitialize(m_time, *this);
127 m_integrator_init = true;
128 } else {
129 initialize();
130 }
131}
132
133void ReactorNet::setMaxSteps(int nmax)
134{
135 m_integ->setMaxSteps(nmax);
136}
137
138int ReactorNet::maxSteps()
139{
140 return m_integ->maxSteps();
141}
142
143void ReactorNet::advance(doublereal time)
144{
145 if (!m_init) {
146 initialize();
147 } else if (!m_integrator_init) {
148 reinitialize();
149 }
150 m_integ->integrate(time);
151 m_time = time;
152 updateState(m_integ->solution());
153}
154
155double ReactorNet::advance(double time, bool applylimit)
156{
157 if (!m_init) {
158 initialize();
159 } else if (!m_integrator_init) {
160 reinitialize();
161 }
162
163 if (!applylimit) {
164 // take full step
165 advance(time);
166 return time;
167 }
168
169 if (!hasAdvanceLimits()) {
170 // take full step
171 advance(time);
172 return time;
173 }
174
175 getAdvanceLimits(m_advancelimits.data());
176
177 // ensure that gradient is available
178 while (lastOrder() < 1) {
179 step();
180 }
181
182 int k = lastOrder();
183 double t = time, delta;
184 double* y = m_integ->solution();
185
186 // reduce time step if limits are exceeded
187 while (true) {
188 bool exceeded = false;
189 getEstimate(t, k, &m_yest[0]);
190 for (size_t j = 0; j < m_nv; j++) {
191 delta = abs(m_yest[j] - y[j]);
192 if ( (m_advancelimits[j] > 0.) && ( delta > m_advancelimits[j]) ) {
193 exceeded = true;
194 if (m_verbose) {
195 writelog(" Limiting global state vector component {:d} (dt = {:9.4g}):"
196 "{:11.6g} > {:9.4g}\n",
197 j, t - m_time, delta, m_advancelimits[j]);
198 }
199 }
200 }
201 if (!exceeded) {
202 break;
203 }
204 t = .5 * (m_time + t);
205 }
206 advance(t);
207 return t;
208}
209
210double ReactorNet::step()
211{
212 if (!m_init) {
213 initialize();
214 } else if (!m_integrator_init) {
215 reinitialize();
216 }
217 m_time = m_integ->step(m_time + 1.0);
218 updateState(m_integ->solution());
219 return m_time;
220}
221
222void ReactorNet::getEstimate(double time, int k, double* yest)
223{
224 // initialize
225 double* cvode_dky = m_integ->solution();
226 for (size_t j = 0; j < m_nv; j++) {
227 yest[j] = cvode_dky[j];
228 }
229
230 // Taylor expansion
231 double factor = 1.;
232 double deltat = time - m_time;
233 for (int n = 1; n <= k; n++) {
234 factor *= deltat / n;
235 cvode_dky = m_integ->derivative(m_time, n);
236 for (size_t j = 0; j < m_nv; j++) {
237 yest[j] += factor * cvode_dky[j];
238 }
239 }
240}
241
242int ReactorNet::lastOrder()
243{
244 return m_integ->lastOrder();
245}
246
247void ReactorNet::addReactor(Reactor& r)
248{
249 r.setNetwork(this);
250 m_reactors.push_back(&r);
251}
252
253void ReactorNet::eval(doublereal t, doublereal* y,
254 doublereal* ydot, doublereal* p)
255{
256 m_time = t;
257 updateState(y);
258 m_LHS.assign(m_nv, 1);
259 m_RHS.assign(m_nv, 0);
260 if (!m_checked_eval_deprecation) {
261 m_have_deprecated_eval.assign(m_reactors.size(), false);
262 for (size_t n = 0; n < m_reactors.size(); n++) {
263 m_reactors[n]->applySensitivity(p);
264 try {
265 m_reactors[n]->evalEqs(t, y + m_start[n], ydot + m_start[n], p);
266 warn_deprecated(m_reactors[n]->type() +
267 "::evalEqs(double t, double* y , double* ydot, double* params)",
268 "Reactor time derivative evaluation now uses signature "
269 "eval(double t, double* ydot)");
270 m_have_deprecated_eval[n] = true;
271 } catch (NotImplementedError&) {
272 m_reactors[n]->eval(t, m_LHS.data() + m_start[n], m_RHS.data() + m_start[n]);
273 size_t yEnd = 0;
274 if (n == m_reactors.size() - 1) {
275 yEnd = m_RHS.size();
276 } else {
277 yEnd = m_start[n + 1];
278 }
279 for (size_t i = m_start[n]; i < yEnd; i++) {
280 ydot[i] = m_RHS[i] / m_LHS[i];
281 }
282 }
283 m_reactors[n]->resetSensitivity(p);
284 }
285 m_checked_eval_deprecation = true;
286 } else {
287 for (size_t n = 0; n < m_reactors.size(); n++) {
288 m_reactors[n]->applySensitivity(p);
289 if (m_have_deprecated_eval[n]) {
290 m_reactors[n]->evalEqs(t, y + m_start[n], ydot + m_start[n], p);
291 } else {
292 m_reactors[n]->eval(t, m_LHS.data() + m_start[n], m_RHS.data() + m_start[n]);
293 size_t yEnd = 0;
294 if (n == m_reactors.size() - 1) {
295 yEnd = m_RHS.size();
296 } else {
297 yEnd = m_start[n + 1];
298 }
299 for (size_t i = m_start[n]; i < yEnd; i++) {
300 ydot[i] = m_RHS[i] / m_LHS[i];
301 }
302 }
303 m_reactors[n]->resetSensitivity(p);
304 }
305 }
306 checkFinite("ydot", ydot, m_nv);
307}
308
309double ReactorNet::sensitivity(size_t k, size_t p)
310{
311 if (!m_init) {
312 initialize();
313 }
314 if (p >= m_sens_params.size()) {
315 throw IndexError("ReactorNet::sensitivity",
316 "m_sens_params", p, m_sens_params.size()-1);
317 }
318 double denom = m_integ->solution(k);
319 if (denom == 0.0) {
320 denom = SmallNumber;
321 }
322 return m_integ->sensitivity(k, p) / denom;
323}
324
325void ReactorNet::evalJacobian(doublereal t, doublereal* y,
326 doublereal* ydot, doublereal* p, Array2D* j)
327{
328 //evaluate the unperturbed ydot
329 eval(t, y, ydot, p);
330 for (size_t n = 0; n < m_nv; n++) {
331 // perturb x(n)
332 double ysave = y[n];
333 double dy = m_atol[n] + fabs(ysave)*m_rtol;
334 y[n] = ysave + dy;
335 dy = y[n] - ysave;
336
337 // calculate perturbed residual
338 eval(t, y, m_ydot.data(), p);
339
340 // compute nth column of Jacobian
341 for (size_t m = 0; m < m_nv; m++) {
342 j->value(m,n) = (m_ydot[m] - ydot[m])/dy;
343 }
344 y[n] = ysave;
345 }
346}
347
348void ReactorNet::updateState(doublereal* y)
349{
350 checkFinite("y", y, m_nv);
351 for (size_t n = 0; n < m_reactors.size(); n++) {
352 m_reactors[n]->updateState(y + m_start[n]);
353 }
354}
355
356void ReactorNet::getState(double* y)
357{
358 for (size_t n = 0; n < m_reactors.size(); n++) {
359 m_reactors[n]->getState(y + m_start[n]);
360 }
361}
362
363void ReactorNet::getDerivative(int k, double* dky)
364{
365 double* cvode_dky = m_integ->derivative(m_time, k);
366 for (size_t j = 0; j < m_nv; j++) {
367 dky[j] = cvode_dky[j];
368 }
369}
370
371void ReactorNet::setAdvanceLimits(const double *limits)
372{
373 if (!m_init) {
374 initialize();
375 }
376 for (size_t n = 0; n < m_reactors.size(); n++) {
377 m_reactors[n]->setAdvanceLimits(limits + m_start[n]);
378 }
379}
380
381bool ReactorNet::hasAdvanceLimits()
382{
383 bool has_limit = false;
384 for (size_t n = 0; n < m_reactors.size(); n++) {
385 has_limit |= m_reactors[n]->hasAdvanceLimits();
386 }
387 return has_limit;
388}
389
390bool ReactorNet::getAdvanceLimits(double *limits)
391{
392 bool has_limit = false;
393 for (size_t n = 0; n < m_reactors.size(); n++) {
394 has_limit |= m_reactors[n]->getAdvanceLimits(limits + m_start[n]);
395 }
396 return has_limit;
397}
398
399size_t ReactorNet::globalComponentIndex(const string& component, size_t reactor)
400{
401 if (!m_init) {
402 initialize();
403 }
404 return m_start[reactor] + m_reactors[reactor]->componentIndex(component);
405}
406
407std::string ReactorNet::componentName(size_t i) const
408{
409 for (auto r : m_reactors) {
410 if (i < r->neq()) {
411 return r->name() + ": " + r->componentName(i);
412 } else {
413 i -= r->neq();
414 }
415 }
416 throw CanteraError("ReactorNet::componentName", "Index out of bounds");
417}
418
419size_t ReactorNet::registerSensitivityParameter(
420 const std::string& name, double value, double scale)
421{
422 if (m_integrator_init) {
423 throw CanteraError("ReactorNet::registerSensitivityParameter",
424 "Sensitivity parameters cannot be added after the"
425 "integrator has been initialized.");
426 }
427 m_paramNames.push_back(name);
428 m_sens_params.push_back(value);
429 m_paramScales.push_back(scale);
430 return m_sens_params.size() - 1;
431}
432
433}
Header file for class Cantera::Array2D.
Header file for base class WallBase.
A class for 2D arrays stored in column-major (Fortran-compatible) form.
Definition: Array.h:30
doublereal & value(size_t i, size_t j)
Returns a changeable reference to position in the matrix.
Definition: Array.h:172
Base class for exceptions thrown by Cantera classes.
Definition: ctexceptions.h:61
An array index is out of range.
Definition: ctexceptions.h:158
An error indicating that an unimplemented function has been called.
Definition: ctexceptions.h:187
void setNetwork(ReactorNet *net)
Set the ReactorNet that this reactor belongs to.
Class Reactor is a general-purpose class for stirred reactors.
Definition: Reactor.h:41
size_t neq()
Number of equations (state variables) for this reactor.
Definition: Reactor.h:92
virtual std::string type() const
String indicating the reactor model implemented.
Definition: Reactor.h:51
virtual void initialize(doublereal t0=0.0)
Initialize the reactor.
Definition: Reactor.cpp:84
virtual size_t nSensParams()
Number of sensitivity parameters associated with this reactor (including walls)
Definition: Reactor.cpp:112
void writelog(const std::string &fmt, const Args &... args)
Write a formatted message to the screen.
Definition: global.h:164
Namespace for the Cantera kernel.
Definition: AnyMap.h:29
void checkFinite(const double tmp)
Check to see that a number is finite (not NaN, +Inf or -Inf)
Definition: checkFinite.cpp:15
@ BDF_Method
Backward Differentiation.
Definition: Integrator.h:33
void warn_deprecated(const std::string &source, const AnyBase &node, const std::string &message)
A deprecation warning for syntax in an input file.
Definition: AnyMap.cpp:1901
void debuglog(const std::string &msg, int loglevel)
Write a message to the log only if loglevel > 0.
Definition: global.h:146
const double SmallNumber
smallest number to compare to zero.
Definition: ct_defs.h:153
void scale(InputIter begin, InputIter end, OutputIter out, S scale_factor)
Multiply elements of an array by a scale factor.
Definition: utilities.h:100
Various templated functions that carry out common vector operations (see Templated Utility Functions)...