Cantera  3.3.0a1
Loading...
Searching...
No Matches
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
11#include "cantera/base/Array.h"
19
20#include <cstdio>
21
22namespace Cantera
23{
24
25ReactorNet::ReactorNet()
26{
27 suppressErrors(true);
28}
29
30ReactorNet::ReactorNet(shared_ptr<ReactorBase> reactor)
31{
32 suppressErrors(true);
34}
35
36ReactorNet::ReactorNet(vector<shared_ptr<ReactorBase>>& reactors)
37{
38 suppressErrors(true);
39 for (auto& reactor : reactors) {
41 }
42}
43
44ReactorNet::~ReactorNet()
45{
46}
47
49{
50 m_time = time;
52 m_integrator_init = false;
53}
54
55void ReactorNet::setMaxTimeStep(double maxstep)
56{
57 m_maxstep = maxstep;
59}
60
62{
64}
65
66void ReactorNet::setTolerances(double rtol, double atol)
67{
68 if (rtol >= 0.0) {
69 m_rtol = rtol;
70 }
71 if (atol >= 0.0) {
72 m_atols = atol;
73 }
74 m_init = false;
75}
76
77void ReactorNet::setSensitivityTolerances(double rtol, double atol)
78{
79 if (rtol >= 0.0) {
80 m_rtolsens = rtol;
81 }
82 if (atol >= 0.0) {
83 m_atolsens = atol;
84 }
85 m_init = false;
86}
87
90 return m_time;
91 } else {
92 throw CanteraError("ReactorNet::time", "Time is not the independent variable"
93 " for this reactor network.");
94 }
95}
96
99 return m_time;
100 } else {
101 throw CanteraError("ReactorNet::distance", "Distance is not the independent"
102 " variable for this reactor network.");
103 }
104}
105
107{
108 m_nv = 0;
109 debuglog("Initializing reactor network.\n", m_verbose);
110 if (m_reactors.empty()) {
111 throw CanteraError("ReactorNet::initialize",
112 "no reactors in network!");
113 }
114 // Names of Reactors and ReactorSurfaces using each Solution; should be only one
115 map<Solution*, vector<string>> solutions;
116 // Unique ReactorSurface objects. Can be attached to multiple Reactor objects
117 set<ReactorBase*> surfaces;
118 m_start.assign(1, 0);
119 for (size_t n = 0; n < m_reactors.size(); n++) {
120 Reactor& r = *m_reactors[n];
121 shared_ptr<Solution> bulk = r.phase();
123 size_t nv = r.neq();
124 m_nv += nv;
125 m_start.push_back(m_nv);
126
127 if (m_verbose) {
128 writelog("Reactor {:d}: {:d} variables.\n", n, nv);
129 writelog(" {:d} sensitivity params.\n", r.nSensParams());
130 }
131 if (r.type() == "FlowReactor" && m_reactors.size() > 1) {
132 throw CanteraError("ReactorNet::initialize",
133 "FlowReactors must be used alone.");
134 }
135 solutions[bulk.get()].push_back(r.name());
136 for (size_t i = 0; i < r.nSurfs(); i++) {
137 if (r.surface(i)->phase()->adjacent(bulk->name()) != bulk) {
138 throw CanteraError("ReactorNet::initialize",
139 "Bulk phase '{}' used by interface '{}' must be the same object\n"
140 "as the contents of the adjacent reactor '{}'.",
141 bulk->name(), r.surface(i)->name(), r.name());
142 }
143 surfaces.insert(r.surface(i));
144 }
145 }
146 for (auto surf : surfaces) {
147 solutions[surf->phase().get()].push_back(surf->name());
148 }
149 for (auto& [soln, reactors] : solutions) {
150 if (reactors.size() > 1) {
151 string shared;
152 for (size_t i = 0; i < reactors.size() - 1; i++) {
153 shared += fmt::format("'{}', ", reactors[i]);
154 }
155 shared += fmt::format("'{}'", reactors.back());
156 throw CanteraError("ReactorNet::initialize", "The following reactors /"
157 " reactor surfaces are using the same Solution object: {}. Use"
158 " independent Solution objects or set the 'clone' argument to 'true'"
159 " when creating the Reactor or ReactorSurface objects.", shared);
160 }
161 }
162
163 m_ydot.resize(m_nv,0.0);
164 m_yest.resize(m_nv,0.0);
165 m_advancelimits.resize(m_nv,-1.0);
166 m_atol.resize(neq());
167 fill(m_atol.begin(), m_atol.end(), m_atols);
168 m_integ->setTolerances(m_rtol, neq(), m_atol.data());
169 m_integ->setSensitivityTolerances(m_rtolsens, m_atolsens);
170 if (!m_linearSolverType.empty()) {
171 m_integ->setLinearSolverType(m_linearSolverType);
172 }
173 if (m_precon) {
174 m_integ->setPreconditioner(m_precon);
175 }
176 m_integ->initialize(m_time, *this);
177 if (m_verbose) {
178 writelog("Number of equations: {:d}\n", neq());
179 writelog("Maximum time step: {:14.6g}\n", m_maxstep);
180 }
181 if (m_integ->preconditionerSide() != PreconditionerSide::NO_PRECONDITION) {
183 }
184 m_integrator_init = true;
185 m_init = true;
186}
187
189{
190 if (m_init) {
191 debuglog("Re-initializing reactor network.\n", m_verbose);
192 m_integ->reinitialize(m_time, *this);
193 if (m_integ->preconditionerSide() != PreconditionerSide::NO_PRECONDITION) {
195 }
196 m_integrator_init = true;
197 } else {
198 initialize();
199 }
200}
201
202void ReactorNet::setLinearSolverType(const string& linSolverType)
203{
204 m_linearSolverType = linSolverType;
205 m_integrator_init = false;
206}
207
208void ReactorNet::setPreconditioner(shared_ptr<SystemJacobian> preconditioner)
209{
210 m_precon = preconditioner;
211 m_integrator_init = false;
212}
213
215{
216 integrator().setMaxSteps(nmax);
217}
218
220{
221 return integrator().maxSteps();
222}
223
224void ReactorNet::advance(double time)
225{
226 if (!m_init) {
227 initialize();
228 } else if (!m_integrator_init) {
229 reinitialize();
230 }
231 m_integ->integrate(time);
232 m_time = m_integ->currentTime();
233 updateState(m_integ->solution());
234}
235
236double ReactorNet::advance(double time, bool applylimit)
237{
238 if (!m_init) {
239 initialize();
240 } else if (!m_integrator_init) {
241 reinitialize();
242 }
243
244 if (!applylimit || !hasAdvanceLimits()) {
245 // No limit enforcement requested; integrate to requested time
246 advance(time);
247 return time;
248 }
249
250 // Enable root-based limit detection and set the base state to the current state
251 m_ybase.assign(m_nv, 0.0);
252 getState(m_ybase.data());
255 m_integ->setRootFunctionCount(nRootFunctions());
256
257 // Integrate toward the requested time; integrator will return early if a limit is
258 // reached (CV_ROOT_RETURN). The try/catch ensures the temporary root-finding state
259 // is cleared even when CVODE throws so subsequent calls start clean.
260 try {
261 m_integ->integrate(time);
262 } catch (...) {
263 m_limit_check_active = false;
264 m_integ->setRootFunctionCount(nRootFunctions());
265 throw;
266 }
267 m_time = m_integ->currentTime();
268
269 // Update reactor states to match the integrator solution at the time reached
270 // (which may be earlier than 'time' if a limit was triggered)
271 updateState(m_integ->solution());
272
273 // Disable limit checking after this call
274 m_limit_check_active = false;
275 m_integ->setRootFunctionCount(nRootFunctions());
276
277 // When a root event stopped integration before reaching the requested time, report
278 // the most limiting component and details about the step.
279 if (m_verbose && m_time < time) {
280 // Ensure limits are available
281 if (m_advancelimits.size() != m_nv) {
282 m_advancelimits.assign(m_nv, -1.0);
283 }
284 getAdvanceLimits(m_advancelimits.data());
285 double* ycurr = m_integ->solution();
286 size_t jmax = npos;
287 double max_ratio = -1.0;
288 double best_limit = 0.0;
289 for (size_t j = 0; j < m_nv; j++) {
290 double lim = m_advancelimits[j];
291 if (lim > 0.0) {
292 double delta = std::abs(ycurr[j] - m_ybase[j]);
293 double ratio = delta / lim;
294 if (ratio > max_ratio) {
295 max_ratio = ratio;
296 jmax = j;
297 best_limit = lim;
298 }
299 }
300 }
301 if (jmax != npos) {
302 double dt = m_time - m_ybase_time;
303 double y_start = m_ybase[jmax];
304 double y_end = ycurr[jmax];
305 double delta = y_end - y_start;
306 writelog(" Advance limit triggered for component {:d} (dt = {:9.4g}):"
307 " y_start = {:11.6g}, y_end = {:11.6g},"
308 " delta = {:11.6g}, limit = {:9.4g}\n",
309 jmax, dt, y_start, y_end, delta, best_limit);
310 }
311 }
312
313 // m_time is tracked via callbacks during integration
314 return m_time;
315}
316
318{
319 if (!m_init) {
320 initialize();
321 } else if (!m_integrator_init) {
322 reinitialize();
323 }
324 m_time = m_integ->step(m_time + 1.0);
325 updateState(m_integ->solution());
326 return m_time;
327}
328
329void ReactorNet::solveSteady(int loglevel)
330{
331 if (!m_init) {
332 initialize();
333 } else if (!m_integrator_init) {
334 reinitialize();
335 }
336 vector<double> y(neq());
337 getState(y.data());
338 SteadyReactorSolver solver(this, y.data());
340 solver.solve(loglevel);
341 solver.getState(y.data());
342 updateState(y.data());
343}
344
345Eigen::SparseMatrix<double> ReactorNet::steadyJacobian(double rdt)
346{
347 if (!m_init) {
348 initialize();
349 } else if (!m_integrator_init) {
350 reinitialize();
351 }
352 vector<double> y0(neq());
353 vector<double> y1(neq());
354 getState(y0.data());
355 SteadyReactorSolver solver(this, y0.data());
356 solver.evalJacobian(y0.data());
357 if (rdt) {
358 solver.linearSolver()->updateTransient(rdt, solver.transientMask().data());
359 }
360 return std::dynamic_pointer_cast<EigenSparseJacobian>(solver.linearSolver())->jacobian();
361}
362
363void ReactorNet::getEstimate(double time, int k, double* yest)
364{
365 if (!m_init) {
366 initialize();
367 }
368 // initialize
369 double* cvode_dky = m_integ->solution();
370 for (size_t j = 0; j < m_nv; j++) {
371 yest[j] = cvode_dky[j];
372 }
373
374 // Taylor expansion
375 double factor = 1.;
376 double deltat = time - m_time;
377 for (int n = 1; n <= k; n++) {
378 factor *= deltat / n;
379 cvode_dky = m_integ->derivative(m_time, n);
380 for (size_t j = 0; j < m_nv; j++) {
381 yest[j] += factor * cvode_dky[j];
382 }
383 }
384}
385
387{
388 if (m_integ) {
389 return m_integ->lastOrder();
390 } else {
391 return 0;
392 }
393}
394
396{
397 return (m_limit_check_active && hasAdvanceLimits()) ? 1 : 0;
398}
399
400void ReactorNet::evalRootFunctions(double t, const double* y, double* gout)
401{
402 // Default: no root detected
403 double g = 1.0;
404
406 // Ensure limits vector is current
407 if (m_advancelimits.size() != m_nv) {
408 m_advancelimits.assign(m_nv, -1.0);
409 }
410 getAdvanceLimits(m_advancelimits.data());
411
412 double max_ratio = 0.0;
413 for (size_t i = 0; i < m_nv; i++) {
414 double lim = m_advancelimits[i];
415 if (lim > 0.0) {
416 double delta = std::abs(y[i] - m_ybase[i]);
417 double ratio = delta / lim;
418 if (ratio > max_ratio) {
419 max_ratio = ratio;
420 }
421 }
422 }
423 g = 1.0 - max_ratio; // root at g = 0 when any component reaches its limit
424 }
425
426 gout[0] = g;
427}
428
429void ReactorNet::addReactor(shared_ptr<ReactorBase> reactor)
430{
431 auto r = std::dynamic_pointer_cast<Reactor>(reactor);
432 if (!r) {
433 throw CanteraError("ReactorNet::addReactor",
434 "Reactor with type '{}' cannot be added to network.",
435 reactor->type());
436 }
437
438 for (auto current : m_reactors) {
439 if (current->isOde() != r->isOde()) {
440 throw CanteraError("ReactorNet::addReactor",
441 "Cannot mix Reactor types using both ODEs and DAEs ({} and {})",
442 current->type(), r->type());
443 }
444 if (current->timeIsIndependent() != r->timeIsIndependent()) {
445 throw CanteraError("ReactorNet::addReactor",
446 "Cannot mix Reactor types using time and space as independent variables"
447 "\n({} and {})", current->type(), r->type());
448 }
449 }
450 m_timeIsIndependent = r->timeIsIndependent();
451 r->setNetwork(this);
452 m_reactors.push_back(r.get());
453 if (!m_integ) {
454 m_integ.reset(newIntegrator(r->isOde() ? "CVODE" : "IDA"));
455 // use backward differencing, with a full Jacobian computed
456 // numerically, and use a Newton linear iterator
457 m_integ->setMethod(BDF_Method);
458 m_integ->setLinearSolverType("DENSE");
459 }
460 updateNames(*r);
461}
462
464{
465 // ensure that reactors and components have reproducible names
467
468 for (size_t i=0; i<r.nWalls(); i++) {
469 auto& w = r.wall(i);
471 if (w.left().type() == "Reservoir") {
472 w.left().setDefaultName(m_counts);
473 }
474 if (w.right().type() == "Reservoir") {
475 w.right().setDefaultName(m_counts);
476 }
477 }
478
479 for (size_t i=0; i<r.nInlets(); i++) {
480 auto& in = r.inlet(i);
482 if (in.in().type() == "Reservoir") {
483 in.in().setDefaultName(m_counts);
484 }
485 }
486
487 for (size_t i=0; i<r.nOutlets(); i++) {
488 auto& out = r.outlet(i);
490 if (out.out().type() == "Reservoir") {
491 out.out().setDefaultName(m_counts);
492 }
493 }
494
495 for (size_t i=0; i<r.nSurfs(); i++) {
497 }
498}
499
501 if (m_integ == nullptr) {
502 throw CanteraError("ReactorNet::integrator",
503 "Integrator has not been instantiated. Add one or more reactors first.");
504 }
505 return *m_integ;
506}
507
508void ReactorNet::eval(double t, double* y, double* ydot, double* p)
509{
510 m_time = t;
511 updateState(y);
512 m_LHS.assign(m_nv, 1);
513 m_RHS.assign(m_nv, 0);
514 for (size_t n = 0; n < m_reactors.size(); n++) {
515 m_reactors[n]->applySensitivity(p);
516 m_reactors[n]->eval(t, m_LHS.data() + m_start[n], m_RHS.data() + m_start[n]);
517 size_t yEnd = 0;
518 if (n == m_reactors.size() - 1) {
519 yEnd = m_RHS.size();
520 } else {
521 yEnd = m_start[n + 1];
522 }
523 for (size_t i = m_start[n]; i < yEnd; i++) {
524 ydot[i] = m_RHS[i] / m_LHS[i];
525 }
526 m_reactors[n]->resetSensitivity(p);
527 }
528 checkFinite("ydot", ydot, m_nv);
529}
530
531void ReactorNet::evalDae(double t, double* y, double* ydot, double* p, double* residual)
532{
533 m_time = t;
534 updateState(y);
535 for (size_t n = 0; n < m_reactors.size(); n++) {
536 m_reactors[n]->applySensitivity(p);
537 m_reactors[n]->evalDae(t, y, ydot, residual);
538 m_reactors[n]->resetSensitivity(p);
539 }
540 checkFinite("ydot", ydot, m_nv);
541}
542
543void ReactorNet::getConstraints(double* constraints)
544{
545 for (size_t n = 0; n < m_reactors.size(); n++) {
546 m_reactors[n]->getConstraints(constraints + m_start[n]);
547 }
548}
549
550double ReactorNet::sensitivity(size_t k, size_t p)
551{
552 if (!m_init) {
553 initialize();
554 }
555 if (p >= m_sens_params.size()) {
556 throw IndexError("ReactorNet::sensitivity",
557 "m_sens_params", p, m_sens_params.size());
558 }
559 double denom = m_integ->solution(k);
560 if (denom == 0.0) {
561 denom = SmallNumber;
562 }
563 return m_integ->sensitivity(k, p) / denom;
564}
565
566void ReactorNet::evalJacobian(double t, double* y, double* ydot, double* p, Array2D* j)
567{
568 //evaluate the unperturbed ydot
569 eval(t, y, ydot, p);
570 for (size_t n = 0; n < m_nv; n++) {
571 // perturb x(n)
572 double ysave = y[n];
573 double dy = m_atol[n] + fabs(ysave)*m_rtol;
574 y[n] = ysave + dy;
575 dy = y[n] - ysave;
576
577 // calculate perturbed residual
578 eval(t, y, m_ydot.data(), p);
579
580 // compute nth column of Jacobian
581 for (size_t m = 0; m < m_nv; m++) {
582 j->value(m,n) = (m_ydot[m] - ydot[m])/dy;
583 }
584 y[n] = ysave;
585 }
586}
587
589{
590 checkFinite("y", y, m_nv);
591 for (size_t n = 0; n < m_reactors.size(); n++) {
592 m_reactors[n]->updateState(y + m_start[n]);
593 }
594}
595
596void ReactorNet::getDerivative(int k, double* dky)
597{
598 if (!m_init) {
599 initialize();
600 }
601 double* cvode_dky = m_integ->derivative(m_time, k);
602 for (size_t j = 0; j < m_nv; j++) {
603 dky[j] = cvode_dky[j];
604 }
605}
606
607void ReactorNet::setAdvanceLimits(const double *limits)
608{
609 if (!m_init) {
610 initialize();
611 }
612 for (size_t n = 0; n < m_reactors.size(); n++) {
613 m_reactors[n]->setAdvanceLimits(limits + m_start[n]);
614 }
615}
616
618{
619 bool has_limit = false;
620 for (size_t n = 0; n < m_reactors.size(); n++) {
621 has_limit |= m_reactors[n]->hasAdvanceLimits();
622 }
623 return has_limit;
624}
625
626bool ReactorNet::getAdvanceLimits(double *limits) const
627{
628 bool has_limit = false;
629 for (size_t n = 0; n < m_reactors.size(); n++) {
630 has_limit |= m_reactors[n]->getAdvanceLimits(limits + m_start[n]);
631 }
632 return has_limit;
633}
634
635void ReactorNet::getState(double* y)
636{
637 for (size_t n = 0; n < m_reactors.size(); n++) {
638 m_reactors[n]->getState(y + m_start[n]);
639 }
640}
641
642void ReactorNet::getStateDae(double* y, double* ydot)
643{
644 for (size_t n = 0; n < m_reactors.size(); n++) {
645 m_reactors[n]->getStateDae(y + m_start[n], ydot + m_start[n]);
646 }
647}
648
649size_t ReactorNet::globalComponentIndex(const string& component, size_t reactor)
650{
651 if (!m_init) {
652 initialize();
653 }
654 return m_start[reactor] + m_reactors[reactor]->componentIndex(component);
655}
656
657string ReactorNet::componentName(size_t i) const
658{
659 size_t iTot = 0;
660 size_t i0 = i;
661 for (auto r : m_reactors) {
662 if (i < r->neq()) {
663 return r->name() + ": " + r->componentName(i);
664 } else {
665 i -= r->neq();
666 }
667 iTot += r->neq();
668 }
669 throw IndexError("ReactorNet::componentName", "component", i0, iTot);
670}
671
672double ReactorNet::upperBound(size_t i) const
673{
674 size_t iTot = 0;
675 size_t i0 = i;
676 for (auto r : m_reactors) {
677 if (i < r->neq()) {
678 return r->upperBound(i);
679 } else {
680 i -= r->neq();
681 }
682 iTot += r->neq();
683 }
684 throw IndexError("ReactorNet::upperBound", "upperBound", i0, iTot);
685}
686
687double ReactorNet::lowerBound(size_t i) const
688{
689 size_t iTot = 0;
690 size_t i0 = i;
691 for (auto r : m_reactors) {
692 if (i < r->neq()) {
693 return r->lowerBound(i);
694 } else {
695 i -= r->neq();
696 }
697 iTot += r->neq();
698 }
699 throw IndexError("ReactorNet::lowerBound", "lowerBound", i0, iTot);
700}
701
703 size_t i = 0;
704 for (auto r : m_reactors) {
705 r->resetBadValues(y + m_start[i++]);
706 }
707}
708
710 const string& name, double value, double scale)
711{
712 if (m_integrator_init) {
713 throw CanteraError("ReactorNet::registerSensitivityParameter",
714 "Sensitivity parameters cannot be added after the"
715 "integrator has been initialized.");
716 }
717 m_paramNames.push_back(name);
718 m_sens_params.push_back(value);
719 m_paramScales.push_back(scale);
720 return m_sens_params.size() - 1;
721}
722
724{
725 // Apply given settings to all reactors
726 for (size_t i = 0; i < m_reactors.size(); i++) {
727 m_reactors[i]->setDerivativeSettings(settings);
728 }
729}
730
732{
733 if (m_integ) {
734 return m_integ->solverStats();
735 } else {
736 return AnyMap();
737 }
738}
739
741{
742 if (m_integ) {
743 return m_integ->linearSolverType();
744 } else {
745 return "";
746 }
747}
748
749void ReactorNet::preconditionerSolve(double* rhs, double* output)
750{
751 if (!m_integ) {
752 throw CanteraError("ReactorNet::preconditionerSolve",
753 "Must only be called after ReactorNet is initialized.");
754 }
755 m_integ->preconditionerSolve(m_nv, rhs, output);
756}
757
758void ReactorNet::preconditionerSetup(double t, double* y, double gamma)
759{
760 // ensure state is up to date.
761 updateState(y);
762 // get the preconditioner
763 auto precon = m_integ->preconditioner();
764 // Reset preconditioner
765 precon->reset();
766 // Set gamma value for M =I - gamma*J
767 precon->setGamma(gamma);
768 // Make a copy of state to adjust it for preconditioner
769 vector<double> yCopy(m_nv);
770 // Get state of reactor
771 getState(yCopy.data());
772 // transform state based on preconditioner rules
773 precon->stateAdjustment(yCopy);
774 // update network with adjusted state
775 updateState(yCopy.data());
776 // Get jacobians and give elements to preconditioners
777 for (size_t i = 0; i < m_reactors.size(); i++) {
778 Eigen::SparseMatrix<double> rJac = m_reactors[i]->jacobian();
779 for (int k=0; k<rJac.outerSize(); ++k) {
780 for (Eigen::SparseMatrix<double>::InnerIterator it(rJac, k); it; ++it) {
781 precon->setValue(it.row() + m_start[i], it.col() + m_start[i],
782 it.value());
783 }
784 }
785 }
786 // post reactor setup operations
787 precon->updatePreconditioner();
788}
789
791{
792 if (!m_integ) {
793 throw CanteraError("ReactorNet::updatePreconditioner",
794 "Must only be called after ReactorNet is initialized.");
795 }
796 auto precon = m_integ->preconditioner();
797 precon->setGamma(gamma);
798 precon->updatePreconditioner();
799}
800
802 // check for non-mole-based reactors and throw an error otherwise
803 for (auto reactor : m_reactors) {
805 throw CanteraError("ReactorNet::checkPreconditionerSupported",
806 "Preconditioning is only supported for type *MoleReactor,\n"
807 "Reactor type given: '{}'.",
808 reactor->type());
809 }
810 }
811}
812
813SteadyReactorSolver::SteadyReactorSolver(ReactorNet* net, double* x0)
814 : m_net(net)
815{
816 m_size = m_net->neq();
817 m_jac = newSystemJacobian("eigen-sparse-direct");
819 m_initialState.assign(x0, x0 + m_size);
820 setInitialGuess(x0);
821 m_mask.assign(m_size, 1);
822 size_t start = 0;
823 for (size_t i = 0; i < net->nReactors(); i++) {
824 auto& R = net->reactor(i);
825 for (auto& m : R.steadyConstraints()) {
826 m_algebraic.push_back(start + m);
827 }
828 start += R.neq();
829 }
830 for (auto& n : m_algebraic) {
831 m_mask[n] = 0;
832 }
833}
834
835void SteadyReactorSolver::eval(double* x, double* r, double rdt, int count)
836{
837 if (rdt < 0.0) {
838 rdt = m_rdt;
839 }
840 vector<double> xv(x, x + size());
841 m_net->eval(0.0, x, r, nullptr);
842 for (size_t i = 0; i < size(); i++) {
843 r[i] -= (x[i] - m_initialState[i]) * rdt;
844 }
845 // Hold algebraic constraints fixed
846 for (auto& n : m_algebraic) {
847 r[n] = x[n] - m_initialState[n];
848 }
849}
850
851void SteadyReactorSolver::initTimeInteg(double dt, double* x)
852{
854 m_initialState.assign(x, x + size());
855}
856
858{
859 m_jac->reset();
860 clock_t t0 = clock();
861 m_work1.resize(size());
862 m_work2.resize(size());
863 eval(x0, m_work1.data(), 0.0, 0);
864 for (size_t j = 0; j < size(); j++) {
865 // perturb x(n); preserve sign(x(n))
866 double xsave = x0[j];
867 double dx = fabs(xsave) * m_jacobianRelPerturb + m_jacobianAbsPerturb;
868 if (xsave < 0) {
869 dx = -dx;
870 }
871 x0[j] = xsave + dx;
872 double rdx = 1.0 / (x0[j] - xsave);
873
874 // calculate perturbed residual
875 eval(x0, m_work2.data(), 0.0, 0);
876
877 // compute nth column of Jacobian
878 for (size_t i = 0; i < size(); i++) {
879 double delta = m_work2[i] - m_work1[i];
880 if (std::abs(delta) > m_jacobianThreshold || i == j) {
881 m_jac->setValue(i, j, delta * rdx);
882 }
883 }
884 x0[j] = xsave;
885 }
886
887 m_jac->updateElapsed(double(clock() - t0) / CLOCKS_PER_SEC);
888 m_jac->incrementEvals();
889 m_jac->setAge(0);
890}
891
892double SteadyReactorSolver::weightedNorm(const double* step) const
893{
894 double sum = 0.0;
895 const double* x = m_state->data();
896 for (size_t i = 0; i < size(); i++) {
897 double ewt = m_net->rtol()*x[i] + m_net->atol();
898 double f = step[i] / ewt;
899 sum += f*f;
900 }
901 return sqrt(sum / size());
902}
903
905{
906 return m_net->componentName(i);
907}
908
910{
911 return m_net->upperBound(i);
912}
913
915{
916 return m_net->lowerBound(i);
917}
918
920{
921 m_net->resetBadValues(x);
922}
923
924void SteadyReactorSolver::writeDebugInfo(const string& header_suffix,
925 const string& message, int loglevel, int attempt_counter)
926{
927 if (loglevel >= 6 && !m_state->empty()) {
928 const auto& state = *m_state;
929 writelog("Current state ({}):\n[", header_suffix);
930 for (size_t i = 0; i < state.size() - 1; i++) {
931 writelog("{}, ", state[i]);
932 }
933 writelog("{}]\n", state.back());
934 }
935 if (loglevel >= 7 && !m_xnew.empty()) {
936 writelog("Current residual ({}):\n[", header_suffix);
937 for (size_t i = 0; i < m_xnew.size() - 1; i++) {
938 writelog("{}, ", m_xnew[i]);
939 }
940 writelog("{}]\n", m_xnew.back());
941 }
942}
943
944shared_ptr<ReactorNet> newReactorNet(vector<shared_ptr<ReactorBase>>& reactors)
945{
946 return make_shared<ReactorNet>(reactors);
947}
948
949}
Header file for class Cantera::Array2D.
Header file for class ReactorSurface.
Header file for base class WallBase.
A map of string keys to values whose type can vary at runtime.
Definition AnyMap.h:431
A class for 2D arrays stored in column-major (Fortran-compatible) form.
Definition Array.h:32
double & value(size_t i, size_t j)
Returns a changeable reference to position in the matrix.
Definition Array.h:160
Base class for exceptions thrown by Cantera classes.
void setDefaultName(map< string, int > &counts)
Set the default name of a connector. Returns false if it was previously set.
vector< double > m_paramScales
Scaling factors for each sensitivity parameter.
Definition FuncEval.h:206
bool suppressErrors() const
Get current state of error suppression.
Definition FuncEval.h:188
vector< double > m_sens_params
Values for the problem parameters for which sensitivities are computed This is the array which is per...
Definition FuncEval.h:203
An array index is out of range.
Abstract base class for ODE system integrators.
Definition Integrator.h:44
virtual void setMaxStepSize(double hmax)
Set the maximum step size.
Definition Integrator.h:232
virtual void setMaxSteps(int nmax)
Set the maximum number of time-steps the integrator can take before reaching the next output time.
Definition Integrator.h:250
virtual int maxSteps()
Returns the maximum number of time-steps the integrator can take before reaching the next output time...
Definition Integrator.h:256
virtual void setMaxErrTestFails(int n)
Set the maximum permissible number of error test failures.
Definition Integrator.h:242
FlowDevice & outlet(size_t n=0)
Return a reference to the n-th outlet FlowDevice connected to this reactor.
size_t nWalls()
Return the number of Wall objects connected to this reactor.
WallBase & wall(size_t n)
Return a reference to the n-th Wall connected to this reactor.
bool setDefaultName(map< string, int > &counts)
Set the default name of a reactor. Returns false if it was previously set.
virtual size_t nSurfs() const
Return the number of surfaces in a reactor.
FlowDevice & inlet(size_t n=0)
Return a reference to the n-th inlet FlowDevice connected to this reactor.
size_t nOutlets()
Return the number of outlet FlowDevice objects connected to this reactor.
size_t nInlets()
Return the number of inlet FlowDevice objects connected to this reactor.
ReactorSurface * surface(size_t n)
Return a reference to the n-th ReactorSurface connected to this reactor.
shared_ptr< Solution > phase()
Access the Solution object used to represent the contents of this reactor.
Definition ReactorBase.h:93
string name() const
Return the name of this reactor.
Definition ReactorBase.h:79
A class representing a network of connected reactors.
Definition ReactorNet.h:30
void setLinearSolverType(const string &linSolverType="DENSE")
Set the type of linear solver used in the integration.
void preconditionerSetup(double t, double *y, double gamma) override
Evaluate the setup processes for the Jacobian preconditioner.
double step()
Advance the state of all reactors with respect to the independent variable (time or space).
virtual int lastOrder() const
Returns the order used for last solution step of the ODE integrator The function is intended for inte...
void eval(double t, double *y, double *ydot, double *p) override
Evaluate the right-hand-side ODE function.
double m_ybase_time
Base time corresponding to m_ybase.
Definition ReactorNet.h:439
void initialize()
Initialize the reactor network.
void advance(double t)
Advance the state of all reactors in the independent variable (time or space).
size_t neq() const override
Number of equations.
Definition ReactorNet.h:253
vector< size_t > m_start
m_start[n] is the starting point in the state vector for reactor n
Definition ReactorNet.h:411
vector< double > m_LHS
m_LHS is a vector representing the coefficients on the "left hand side" of each governing equation
Definition ReactorNet.h:445
double m_initial_time
The initial value of the independent variable in the system.
Definition ReactorNet.h:404
void evalJacobian(double t, double *y, double *ydot, double *p, Array2D *j)
Evaluate the Jacobian matrix for the reactor network.
double time()
Current value of the simulation time [s], for reactor networks that are solved in the time domain.
void getConstraints(double *constraints) override
Given a vector of length neq(), mark which variables should be considered algebraic constraints.
double m_time
The independent variable in the system.
Definition ReactorNet.h:401
AnyMap solverStats() const
Get solver stats from integrator.
Reactor & reactor(int n)
Return a reference to the n-th reactor in this network.
Definition ReactorNet.h:189
void addReactor(shared_ptr< ReactorBase > reactor)
Add the reactor r to this reactor network.
bool m_limit_check_active
Indicates whether the advance-limit root check is active for the current call to advance(t,...
Definition ReactorNet.h:442
map< string, int > m_counts
Map used for default name generation.
Definition ReactorNet.h:396
double upperBound(size_t i) const
Get the upper bound on the i-th component of the global state vector.
virtual void setMaxSteps(int nmax)
Set the maximum number of internal integration steps the integrator will take before reaching the nex...
string componentName(size_t i) const
Return the name of the i-th component of the global state vector.
void evalRootFunctions(double t, const double *y, double *gout) override
Evaluate the advance-limit root function used to stop integration once a limit is met.
size_t nRootFunctions() const override
Root finding is enabled only while enforcing advance limits.
void getStateDae(double *y, double *ydot) override
Fill in the vectors y and ydot with the current state of the system.
void setInitialTime(double time)
Set the initial value of the independent variable (typically time).
virtual void getDerivative(int k, double *dky)
Return k-th derivative at the current state of the system.
void setMaxErrTestFails(int nmax)
Set the maximum number of error test failures permitted by the CVODES integrator in a single step.
size_t registerSensitivityParameter(const string &name, double value, double scale)
Used by Reactor and Wall objects to register the addition of sensitivity parameters so that the React...
double m_maxstep
Maximum integrator internal timestep. Default of 0.0 means infinity.
Definition ReactorNet.h:422
double distance()
Current position [m] along the length of the reactor network, for reactors that are solved as a funct...
void setSensitivityTolerances(double rtol, double atol)
Set the relative and absolute tolerances for integrating the sensitivity equations.
int maxSteps()
Returns the maximum number of internal integration steps the integrator will take before reaching the...
virtual void setDerivativeSettings(AnyMap &settings)
Set derivative settings of all reactors.
double sensitivity(size_t k, size_t p)
Return the sensitivity of the k-th solution component with respect to the p-th sensitivity parameter.
void updateNames(Reactor &r)
Create reproducible names for reactors and walls/connectors.
void updateState(double *y)
Update the state of all the reactors in the network to correspond to the values in the solution vecto...
void getState(double *y) override
Fill in the vector y with the current state of the system.
void setAdvanceLimits(const double *limits)
Set absolute step size limits during advance.
double rtol()
Relative tolerance.
Definition ReactorNet.h:96
size_t globalComponentIndex(const string &component, size_t reactor=0)
Return the index corresponding to the component named component in the reactor with index reactor in ...
void solveSteady(int loglevel=0)
Solve directly for the steady-state solution.
bool m_timeIsIndependent
Indicates whether time or space is the independent variable.
Definition ReactorNet.h:427
double atol()
Absolute integration tolerance.
Definition ReactorNet.h:101
bool hasAdvanceLimits() const
Check whether ReactorNet object uses advance limits.
vector< double > m_ybase
Base state used for evaluating advance limits during a single advance() call when root-finding is ena...
Definition ReactorNet.h:437
void setMaxTimeStep(double maxstep)
Set the maximum integrator step.
double lowerBound(size_t i) const
Get the lower bound on the i-th component of the global state vector.
void evalDae(double t, double *y, double *ydot, double *p, double *residual) override
eval coupling for IDA / DAEs
virtual void checkPreconditionerSupported() const
Check that preconditioning is supported by all reactors in the network.
bool m_integrator_init
True if integrator initialization is current.
Definition ReactorNet.h:407
void reinitialize()
Reinitialize the integrator.
Integrator & integrator()
Return a reference to the integrator.
bool getAdvanceLimits(double *limits) const
Retrieve absolute step size limits during advance.
Eigen::SparseMatrix< double > steadyJacobian(double rdt=0.0)
Get the Jacobian used by the steady-state solver.
string linearSolverType() const
Problem type of integrator.
void updatePreconditioner(double gamma) override
Update the preconditioner based on already computed jacobian values.
void setPreconditioner(shared_ptr< SystemJacobian > preconditioner)
Set preconditioner used by the linear solver.
void preconditionerSolve(double *rhs, double *output) override
Evaluate the linear system Ax=b where A is the preconditioner.
vector< string > m_paramNames
Names corresponding to each sensitivity parameter.
Definition ReactorNet.h:430
void resetBadValues(double *y)
Reset physically or mathematically problematic values, such as negative species concentrations.
void setTolerances(double rtol, double atol)
Set the relative and absolute tolerances for the integrator.
virtual void getEstimate(double time, int k, double *yest)
Estimate a future state based on current derivatives.
Class Reactor is a general-purpose class for stirred reactors.
Definition Reactor.h:47
size_t neq()
Number of equations (state variables) for this reactor.
Definition Reactor.h:90
size_t nSensParams() const override
Number of sensitivity parameters associated with this reactor.
Definition Reactor.cpp:110
string type() const override
String indicating the reactor model implemented.
Definition Reactor.h:52
void initialize(double t0=0.0) override
Initialize the reactor.
Definition Reactor.cpp:82
virtual bool preconditionerSupported() const
Return a false if preconditioning is not supported or true otherwise.
Definition Reactor.h:240
Adapter class to enable using the SteadyStateSystem solver with ReactorNet.
Definition ReactorNet.h:455
double weightedNorm(const double *step) const override
Compute the weighted norm of step.
vector< double > m_initialState
Initial value of each state variable.
Definition ReactorNet.h:473
string componentName(size_t i) const override
Get the name of the i-th component of the state vector.
double upperBound(size_t i) const override
Get the upper bound for global component i in the state vector.
vector< size_t > m_algebraic
Indices of variables that are held constant in the time-stepping mode of the steady-state solver.
Definition ReactorNet.h:477
void evalJacobian(double *x0) override
Evaluates the Jacobian at x0 using finite differences.
void resetBadValues(double *x) override
Reset values such as negative species concentrations.
void writeDebugInfo(const string &header_suffix, const string &message, int loglevel, int attempt_counter) override
Write solver debugging based on the specified log level.
void eval(double *x, double *r, double rdt=-1.0, int count=1) override
Evaluate the residual function.
void initTimeInteg(double dt, double *x) override
Prepare for time stepping beginning with solution x and timestep dt.
double lowerBound(size_t i) const override
Get the lower bound for global component i in the state vector.
virtual void resize()
Call to set the size of internal data structures after first defining the system or if the problem si...
shared_ptr< SystemJacobian > linearSolver() const
Get the the linear solver being used to hold the Jacobian matrix and solve linear systems as part of ...
vector< double > m_xnew
Work array used to hold the residual or the new solution.
double m_jacobianAbsPerturb
Absolute perturbation of each component in finite difference Jacobian.
size_t size() const
Total solution vector length;.
vector< int > & transientMask()
Access the vector indicating which equations contain a transient term.
double rdt() const
Reciprocal of the time step.
virtual void initTimeInteg(double dt, double *x)
Prepare for time stepping beginning with solution x and timestep dt.
double m_rdt
Reciprocal of time step.
double m_jacobianThreshold
Threshold for ignoring small elements in Jacobian.
shared_ptr< SystemJacobian > m_jac
Jacobian evaluator.
shared_ptr< vector< double > > m_state
Solution vector.
void setMaxTimeStepCount(int nmax)
Set the maximum number of timeteps allowed before successful steady-state solve.
void solve(int loglevel=0)
Solve the steady-state problem, taking internal timesteps as necessary until the Newton solver can co...
void getState(double *x) const
Get the converged steady-state solution after calling solve().
double m_jacobianRelPerturb
Relative perturbation of each component in finite difference Jacobian.
vector< double > m_work1
Work arrays used during Jacobian evaluation.
void debuglog(const string &msg, int loglevel)
Write a message to the log only if loglevel > 0.
Definition global.h:154
void writelog(const string &fmt, const Args &... args)
Write a formatted message to the screen.
Definition global.h:171
void scale(InputIter begin, InputIter end, OutputIter out, S scale_factor)
Multiply elements of an array by a scale factor.
Definition utilities.h:104
Integrator * newIntegrator(const string &itype)
Create new Integrator object.
Namespace for the Cantera kernel.
Definition AnyMap.cpp:595
const size_t npos
index returned by functions to indicate "no position"
Definition ct_defs.h:180
void checkFinite(const double tmp)
Check to see that a number is finite (not NaN, +Inf or -Inf)
@ BDF_Method
Backward Differentiation.
Definition Integrator.h:24
shared_ptr< SystemJacobian > newSystemJacobian(const string &type)
Create a SystemJacobian object of the specified type.
const double SmallNumber
smallest number to compare to zero.
Definition ct_defs.h:158
shared_ptr< ReactorNet > newReactorNet(vector< shared_ptr< ReactorBase > > &reactors)
Create a reactor network containing one or more coupled reactors.
Various templated functions that carry out common vector and polynomial operations (see Templated Arr...