Cantera  3.2.0
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 warn_user("ReactorNet::initialize", "The following reactors / reactor"
157 " surfaces are using the same Solution object: {}. Use independent"
158 " Solution objects or set the 'clone' argument to 'true' when creating"
159 " the Reactor or ReactorSurface objects. Shared Solution objects within"
160 " a single ReactorNet will be an error after Cantera 3.2.", shared);
161 }
162 }
163
164 m_ydot.resize(m_nv,0.0);
165 m_yest.resize(m_nv,0.0);
166 m_advancelimits.resize(m_nv,-1.0);
167 m_atol.resize(neq());
168 fill(m_atol.begin(), m_atol.end(), m_atols);
169 m_integ->setTolerances(m_rtol, neq(), m_atol.data());
170 m_integ->setSensitivityTolerances(m_rtolsens, m_atolsens);
171 if (!m_linearSolverType.empty()) {
172 m_integ->setLinearSolverType(m_linearSolverType);
173 }
174 if (m_precon) {
175 m_integ->setPreconditioner(m_precon);
176 }
177 m_integ->initialize(m_time, *this);
178 if (m_verbose) {
179 writelog("Number of equations: {:d}\n", neq());
180 writelog("Maximum time step: {:14.6g}\n", m_maxstep);
181 }
182 if (m_integ->preconditionerSide() != PreconditionerSide::NO_PRECONDITION) {
184 }
185 m_integrator_init = true;
186 m_init = true;
187}
188
190{
191 if (m_init) {
192 debuglog("Re-initializing reactor network.\n", m_verbose);
193 m_integ->reinitialize(m_time, *this);
194 if (m_integ->preconditionerSide() != PreconditionerSide::NO_PRECONDITION) {
196 }
197 m_integrator_init = true;
198 } else {
199 initialize();
200 }
201}
202
203void ReactorNet::setLinearSolverType(const string& linSolverType)
204{
205 m_linearSolverType = linSolverType;
206 m_integrator_init = false;
207}
208
209void ReactorNet::setPreconditioner(shared_ptr<SystemJacobian> preconditioner)
210{
211 m_precon = preconditioner;
212 m_integrator_init = false;
213}
214
216{
217 integrator().setMaxSteps(nmax);
218}
219
221{
222 return integrator().maxSteps();
223}
224
225void ReactorNet::advance(double time)
226{
227 if (!m_init) {
228 initialize();
229 } else if (!m_integrator_init) {
230 reinitialize();
231 }
232 m_integ->integrate(time);
233 m_time = m_integ->currentTime();
234 updateState(m_integ->solution());
235}
236
237double ReactorNet::advance(double time, bool applylimit)
238{
239 if (!m_init) {
240 initialize();
241 } else if (!m_integrator_init) {
242 reinitialize();
243 }
244
245 if (!applylimit || !hasAdvanceLimits()) {
246 // No limit enforcement requested; integrate to requested time
247 advance(time);
248 return time;
249 }
250
251 // Enable root-based limit detection and set the base state to the current state
252 m_ybase.assign(m_nv, 0.0);
253 getState(m_ybase.data());
256 m_integ->setRootFunctionCount(nRootFunctions());
257
258 // Integrate toward the requested time; integrator will return early if a limit is
259 // reached (CV_ROOT_RETURN). The try/catch ensures the temporary root-finding state
260 // is cleared even when CVODE throws so subsequent calls start clean.
261 try {
262 m_integ->integrate(time);
263 } catch (...) {
264 m_limit_check_active = false;
265 m_integ->setRootFunctionCount(nRootFunctions());
266 throw;
267 }
268 m_time = m_integ->currentTime();
269
270 // Update reactor states to match the integrator solution at the time reached
271 // (which may be earlier than 'time' if a limit was triggered)
272 updateState(m_integ->solution());
273
274 // Disable limit checking after this call
275 m_limit_check_active = false;
276 m_integ->setRootFunctionCount(nRootFunctions());
277
278 // When a root event stopped integration before reaching the requested time, report
279 // the most limiting component and details about the step.
280 if (m_verbose && m_time < time) {
281 // Ensure limits are available
282 if (m_advancelimits.size() != m_nv) {
283 m_advancelimits.assign(m_nv, -1.0);
284 }
285 getAdvanceLimits(m_advancelimits.data());
286 double* ycurr = m_integ->solution();
287 size_t jmax = npos;
288 double max_ratio = -1.0;
289 double best_limit = 0.0;
290 for (size_t j = 0; j < m_nv; j++) {
291 double lim = m_advancelimits[j];
292 if (lim > 0.0) {
293 double delta = std::abs(ycurr[j] - m_ybase[j]);
294 double ratio = delta / lim;
295 if (ratio > max_ratio) {
296 max_ratio = ratio;
297 jmax = j;
298 best_limit = lim;
299 }
300 }
301 }
302 if (jmax != npos) {
303 double dt = m_time - m_ybase_time;
304 double y_start = m_ybase[jmax];
305 double y_end = ycurr[jmax];
306 double delta = y_end - y_start;
307 writelog(" Advance limit triggered for component {:d} (dt = {:9.4g}):"
308 " y_start = {:11.6g}, y_end = {:11.6g},"
309 " delta = {:11.6g}, limit = {:9.4g}\n",
310 jmax, dt, y_start, y_end, delta, best_limit);
311 }
312 }
313
314 // m_time is tracked via callbacks during integration
315 return m_time;
316}
317
319{
320 if (!m_init) {
321 initialize();
322 } else if (!m_integrator_init) {
323 reinitialize();
324 }
325 m_time = m_integ->step(m_time + 1.0);
326 updateState(m_integ->solution());
327 return m_time;
328}
329
330void ReactorNet::solveSteady(int loglevel)
331{
332 if (!m_init) {
333 initialize();
334 } else if (!m_integrator_init) {
335 reinitialize();
336 }
337 vector<double> y(neq());
338 getState(y.data());
339 SteadyReactorSolver solver(this, y.data());
341 solver.solve(loglevel);
342 solver.getState(y.data());
343 updateState(y.data());
344}
345
346Eigen::SparseMatrix<double> ReactorNet::steadyJacobian(double rdt)
347{
348 if (!m_init) {
349 initialize();
350 } else if (!m_integrator_init) {
351 reinitialize();
352 }
353 vector<double> y0(neq());
354 vector<double> y1(neq());
355 getState(y0.data());
356 SteadyReactorSolver solver(this, y0.data());
357 solver.evalJacobian(y0.data());
358 if (rdt) {
359 solver.linearSolver()->updateTransient(rdt, solver.transientMask().data());
360 }
361 return std::dynamic_pointer_cast<EigenSparseJacobian>(solver.linearSolver())->jacobian();
362}
363
364void ReactorNet::getEstimate(double time, int k, double* yest)
365{
366 if (!m_init) {
367 initialize();
368 }
369 // initialize
370 double* cvode_dky = m_integ->solution();
371 for (size_t j = 0; j < m_nv; j++) {
372 yest[j] = cvode_dky[j];
373 }
374
375 // Taylor expansion
376 double factor = 1.;
377 double deltat = time - m_time;
378 for (int n = 1; n <= k; n++) {
379 factor *= deltat / n;
380 cvode_dky = m_integ->derivative(m_time, n);
381 for (size_t j = 0; j < m_nv; j++) {
382 yest[j] += factor * cvode_dky[j];
383 }
384 }
385}
386
388{
389 if (m_integ) {
390 return m_integ->lastOrder();
391 } else {
392 return 0;
393 }
394}
395
397{
398 return (m_limit_check_active && hasAdvanceLimits()) ? 1 : 0;
399}
400
401void ReactorNet::evalRootFunctions(double t, const double* y, double* gout)
402{
403 // Default: no root detected
404 double g = 1.0;
405
407 // Ensure limits vector is current
408 if (m_advancelimits.size() != m_nv) {
409 m_advancelimits.assign(m_nv, -1.0);
410 }
411 getAdvanceLimits(m_advancelimits.data());
412
413 double max_ratio = 0.0;
414 for (size_t i = 0; i < m_nv; i++) {
415 double lim = m_advancelimits[i];
416 if (lim > 0.0) {
417 double delta = std::abs(y[i] - m_ybase[i]);
418 double ratio = delta / lim;
419 if (ratio > max_ratio) {
420 max_ratio = ratio;
421 }
422 }
423 }
424 g = 1.0 - max_ratio; // root at g = 0 when any component reaches its limit
425 }
426
427 gout[0] = g;
428}
429
431{
432 warn_deprecated("ReactorNet::addReactor",
433 "To be removed after Cantera 3.2. Replaceable by reactor net "
434 "instantiation with contents.");
435 for (auto current : m_reactors) {
436 if (current->isOde() != r.isOde()) {
437 throw CanteraError("ReactorNet::addReactor",
438 "Cannot mix Reactor types using both ODEs and DAEs ({} and {})",
439 current->type(), r.type());
440 }
441 if (current->timeIsIndependent() != r.timeIsIndependent()) {
442 throw CanteraError("ReactorNet::addReactor",
443 "Cannot mix Reactor types using time and space as independent variables"
444 "\n({} and {})", current->type(), r.type());
445 }
446 }
448 r.setNetwork(this);
449 m_reactors.push_back(&r);
450 if (!m_integ) {
451 m_integ.reset(newIntegrator(r.isOde() ? "CVODE" : "IDA"));
452 // use backward differencing, with a full Jacobian computed
453 // numerically, and use a Newton linear iterator
454 m_integ->setMethod(BDF_Method);
455 m_integ->setLinearSolverType("DENSE");
456 }
457 updateNames(r);
458}
459
460void ReactorNet::addReactor(shared_ptr<ReactorBase> reactor)
461{
462 auto r = std::dynamic_pointer_cast<Reactor>(reactor);
463 if (!r) {
464 throw CanteraError("ReactorNet::addReactor",
465 "Reactor with type '{}' cannot be added to network.",
466 reactor->type());
467 }
468
469 for (auto current : m_reactors) {
470 if (current->isOde() != r->isOde()) {
471 throw CanteraError("ReactorNet::addReactor",
472 "Cannot mix Reactor types using both ODEs and DAEs ({} and {})",
473 current->type(), r->type());
474 }
475 if (current->timeIsIndependent() != r->timeIsIndependent()) {
476 throw CanteraError("ReactorNet::addReactor",
477 "Cannot mix Reactor types using time and space as independent variables"
478 "\n({} and {})", current->type(), r->type());
479 }
480 }
481 m_timeIsIndependent = r->timeIsIndependent();
482 r->setNetwork(this);
483 m_reactors.push_back(r.get());
484 if (!m_integ) {
485 m_integ.reset(newIntegrator(r->isOde() ? "CVODE" : "IDA"));
486 // use backward differencing, with a full Jacobian computed
487 // numerically, and use a Newton linear iterator
488 m_integ->setMethod(BDF_Method);
489 m_integ->setLinearSolverType("DENSE");
490 }
491 updateNames(*r);
492}
493
495{
496 // ensure that reactors and components have reproducible names
498
499 for (size_t i=0; i<r.nWalls(); i++) {
500 auto& w = r.wall(i);
502 if (w.left().type() == "Reservoir") {
503 w.left().setDefaultName(m_counts);
504 }
505 if (w.right().type() == "Reservoir") {
506 w.right().setDefaultName(m_counts);
507 }
508 }
509
510 for (size_t i=0; i<r.nInlets(); i++) {
511 auto& in = r.inlet(i);
513 if (in.in().type() == "Reservoir") {
514 in.in().setDefaultName(m_counts);
515 }
516 }
517
518 for (size_t i=0; i<r.nOutlets(); i++) {
519 auto& out = r.outlet(i);
521 if (out.out().type() == "Reservoir") {
522 out.out().setDefaultName(m_counts);
523 }
524 }
525
526 for (size_t i=0; i<r.nSurfs(); i++) {
528 }
529}
530
532 if (m_integ == nullptr) {
533 throw CanteraError("ReactorNet::integrator",
534 "Integrator has not been instantiated. Add one or more reactors first.");
535 }
536 return *m_integ;
537}
538
539void ReactorNet::eval(double t, double* y, double* ydot, double* p)
540{
541 m_time = t;
542 updateState(y);
543 m_LHS.assign(m_nv, 1);
544 m_RHS.assign(m_nv, 0);
545 for (size_t n = 0; n < m_reactors.size(); n++) {
546 m_reactors[n]->applySensitivity(p);
547 m_reactors[n]->eval(t, m_LHS.data() + m_start[n], m_RHS.data() + m_start[n]);
548 size_t yEnd = 0;
549 if (n == m_reactors.size() - 1) {
550 yEnd = m_RHS.size();
551 } else {
552 yEnd = m_start[n + 1];
553 }
554 for (size_t i = m_start[n]; i < yEnd; i++) {
555 ydot[i] = m_RHS[i] / m_LHS[i];
556 }
557 m_reactors[n]->resetSensitivity(p);
558 }
559 checkFinite("ydot", ydot, m_nv);
560}
561
562void ReactorNet::evalDae(double t, double* y, double* ydot, double* p, double* residual)
563{
564 m_time = t;
565 updateState(y);
566 for (size_t n = 0; n < m_reactors.size(); n++) {
567 m_reactors[n]->applySensitivity(p);
568 m_reactors[n]->evalDae(t, y, ydot, residual);
569 m_reactors[n]->resetSensitivity(p);
570 }
571 checkFinite("ydot", ydot, m_nv);
572}
573
574void ReactorNet::getConstraints(double* constraints)
575{
576 for (size_t n = 0; n < m_reactors.size(); n++) {
577 m_reactors[n]->getConstraints(constraints + m_start[n]);
578 }
579}
580
581double ReactorNet::sensitivity(size_t k, size_t p)
582{
583 if (!m_init) {
584 initialize();
585 }
586 if (p >= m_sens_params.size()) {
587 throw IndexError("ReactorNet::sensitivity",
588 "m_sens_params", p, m_sens_params.size());
589 }
590 double denom = m_integ->solution(k);
591 if (denom == 0.0) {
592 denom = SmallNumber;
593 }
594 return m_integ->sensitivity(k, p) / denom;
595}
596
597void ReactorNet::evalJacobian(double t, double* y, double* ydot, double* p, Array2D* j)
598{
599 //evaluate the unperturbed ydot
600 eval(t, y, ydot, p);
601 for (size_t n = 0; n < m_nv; n++) {
602 // perturb x(n)
603 double ysave = y[n];
604 double dy = m_atol[n] + fabs(ysave)*m_rtol;
605 y[n] = ysave + dy;
606 dy = y[n] - ysave;
607
608 // calculate perturbed residual
609 eval(t, y, m_ydot.data(), p);
610
611 // compute nth column of Jacobian
612 for (size_t m = 0; m < m_nv; m++) {
613 j->value(m,n) = (m_ydot[m] - ydot[m])/dy;
614 }
615 y[n] = ysave;
616 }
617}
618
620{
621 checkFinite("y", y, m_nv);
622 for (size_t n = 0; n < m_reactors.size(); n++) {
623 m_reactors[n]->updateState(y + m_start[n]);
624 }
625}
626
627void ReactorNet::getDerivative(int k, double* dky)
628{
629 if (!m_init) {
630 initialize();
631 }
632 double* cvode_dky = m_integ->derivative(m_time, k);
633 for (size_t j = 0; j < m_nv; j++) {
634 dky[j] = cvode_dky[j];
635 }
636}
637
638void ReactorNet::setAdvanceLimits(const double *limits)
639{
640 if (!m_init) {
641 initialize();
642 }
643 for (size_t n = 0; n < m_reactors.size(); n++) {
644 m_reactors[n]->setAdvanceLimits(limits + m_start[n]);
645 }
646}
647
649{
650 bool has_limit = false;
651 for (size_t n = 0; n < m_reactors.size(); n++) {
652 has_limit |= m_reactors[n]->hasAdvanceLimits();
653 }
654 return has_limit;
655}
656
657bool ReactorNet::getAdvanceLimits(double *limits) const
658{
659 bool has_limit = false;
660 for (size_t n = 0; n < m_reactors.size(); n++) {
661 has_limit |= m_reactors[n]->getAdvanceLimits(limits + m_start[n]);
662 }
663 return has_limit;
664}
665
666void ReactorNet::getState(double* y)
667{
668 for (size_t n = 0; n < m_reactors.size(); n++) {
669 m_reactors[n]->getState(y + m_start[n]);
670 }
671}
672
673void ReactorNet::getStateDae(double* y, double* ydot)
674{
675 for (size_t n = 0; n < m_reactors.size(); n++) {
676 m_reactors[n]->getStateDae(y + m_start[n], ydot + m_start[n]);
677 }
678}
679
680size_t ReactorNet::globalComponentIndex(const string& component, size_t reactor)
681{
682 if (!m_init) {
683 initialize();
684 }
685 return m_start[reactor] + m_reactors[reactor]->componentIndex(component);
686}
687
688string ReactorNet::componentName(size_t i) const
689{
690 size_t iTot = 0;
691 size_t i0 = i;
692 for (auto r : m_reactors) {
693 if (i < r->neq()) {
694 return r->name() + ": " + r->componentName(i);
695 } else {
696 i -= r->neq();
697 }
698 iTot += r->neq();
699 }
700 throw IndexError("ReactorNet::componentName", "component", i0, iTot);
701}
702
703double ReactorNet::upperBound(size_t i) const
704{
705 size_t iTot = 0;
706 size_t i0 = i;
707 for (auto r : m_reactors) {
708 if (i < r->neq()) {
709 return r->upperBound(i);
710 } else {
711 i -= r->neq();
712 }
713 iTot += r->neq();
714 }
715 throw IndexError("ReactorNet::upperBound", "upperBound", i0, iTot);
716}
717
718double ReactorNet::lowerBound(size_t i) const
719{
720 size_t iTot = 0;
721 size_t i0 = i;
722 for (auto r : m_reactors) {
723 if (i < r->neq()) {
724 return r->lowerBound(i);
725 } else {
726 i -= r->neq();
727 }
728 iTot += r->neq();
729 }
730 throw IndexError("ReactorNet::lowerBound", "lowerBound", i0, iTot);
731}
732
734 size_t i = 0;
735 for (auto r : m_reactors) {
736 r->resetBadValues(y + m_start[i++]);
737 }
738}
739
741 const string& name, double value, double scale)
742{
743 if (m_integrator_init) {
744 throw CanteraError("ReactorNet::registerSensitivityParameter",
745 "Sensitivity parameters cannot be added after the"
746 "integrator has been initialized.");
747 }
748 m_paramNames.push_back(name);
749 m_sens_params.push_back(value);
750 m_paramScales.push_back(scale);
751 return m_sens_params.size() - 1;
752}
753
755{
756 // Apply given settings to all reactors
757 for (size_t i = 0; i < m_reactors.size(); i++) {
758 m_reactors[i]->setDerivativeSettings(settings);
759 }
760}
761
763{
764 if (m_integ) {
765 return m_integ->solverStats();
766 } else {
767 return AnyMap();
768 }
769}
770
772{
773 if (m_integ) {
774 return m_integ->linearSolverType();
775 } else {
776 return "";
777 }
778}
779
780void ReactorNet::preconditionerSolve(double* rhs, double* output)
781{
782 if (!m_integ) {
783 throw CanteraError("ReactorNet::preconditionerSolve",
784 "Must only be called after ReactorNet is initialized.");
785 }
786 m_integ->preconditionerSolve(m_nv, rhs, output);
787}
788
789void ReactorNet::preconditionerSetup(double t, double* y, double gamma)
790{
791 // ensure state is up to date.
792 updateState(y);
793 // get the preconditioner
794 auto precon = m_integ->preconditioner();
795 // Reset preconditioner
796 precon->reset();
797 // Set gamma value for M =I - gamma*J
798 precon->setGamma(gamma);
799 // Make a copy of state to adjust it for preconditioner
800 vector<double> yCopy(m_nv);
801 // Get state of reactor
802 getState(yCopy.data());
803 // transform state based on preconditioner rules
804 precon->stateAdjustment(yCopy);
805 // update network with adjusted state
806 updateState(yCopy.data());
807 // Get jacobians and give elements to preconditioners
808 for (size_t i = 0; i < m_reactors.size(); i++) {
809 Eigen::SparseMatrix<double> rJac = m_reactors[i]->jacobian();
810 for (int k=0; k<rJac.outerSize(); ++k) {
811 for (Eigen::SparseMatrix<double>::InnerIterator it(rJac, k); it; ++it) {
812 precon->setValue(it.row() + m_start[i], it.col() + m_start[i],
813 it.value());
814 }
815 }
816 }
817 // post reactor setup operations
818 precon->updatePreconditioner();
819}
820
822{
823 if (!m_integ) {
824 throw CanteraError("ReactorNet::updatePreconditioner",
825 "Must only be called after ReactorNet is initialized.");
826 }
827 auto precon = m_integ->preconditioner();
828 precon->setGamma(gamma);
829 precon->updatePreconditioner();
830}
831
833 // check for non-mole-based reactors and throw an error otherwise
834 for (auto reactor : m_reactors) {
836 throw CanteraError("ReactorNet::checkPreconditionerSupported",
837 "Preconditioning is only supported for type *MoleReactor,\n"
838 "Reactor type given: '{}'.",
839 reactor->type());
840 }
841 }
842}
843
844SteadyReactorSolver::SteadyReactorSolver(ReactorNet* net, double* x0)
845 : m_net(net)
846{
847 m_size = m_net->neq();
848 m_jac = newSystemJacobian("eigen-sparse-direct");
850 m_initialState.assign(x0, x0 + m_size);
851 setInitialGuess(x0);
852 m_mask.assign(m_size, 1);
853 size_t start = 0;
854 for (size_t i = 0; i < net->nReactors(); i++) {
855 auto& R = net->reactor(i);
856 for (auto& m : R.steadyConstraints()) {
857 m_algebraic.push_back(start + m);
858 }
859 start += R.neq();
860 }
861 for (auto& n : m_algebraic) {
862 m_mask[n] = 0;
863 }
864}
865
866void SteadyReactorSolver::eval(double* x, double* r, double rdt, int count)
867{
868 if (rdt < 0.0) {
869 rdt = m_rdt;
870 }
871 vector<double> xv(x, x + size());
872 m_net->eval(0.0, x, r, nullptr);
873 for (size_t i = 0; i < size(); i++) {
874 r[i] -= (x[i] - m_initialState[i]) * rdt;
875 }
876 // Hold algebraic constraints fixed
877 for (auto& n : m_algebraic) {
878 r[n] = x[n] - m_initialState[n];
879 }
880}
881
882void SteadyReactorSolver::initTimeInteg(double dt, double* x)
883{
885 m_initialState.assign(x, x + size());
886}
887
889{
890 m_jac->reset();
891 clock_t t0 = clock();
892 m_work1.resize(size());
893 m_work2.resize(size());
894 eval(x0, m_work1.data(), 0.0, 0);
895 for (size_t j = 0; j < size(); j++) {
896 // perturb x(n); preserve sign(x(n))
897 double xsave = x0[j];
898 double dx = fabs(xsave) * m_jacobianRelPerturb + m_jacobianAbsPerturb;
899 if (xsave < 0) {
900 dx = -dx;
901 }
902 x0[j] = xsave + dx;
903 double rdx = 1.0 / (x0[j] - xsave);
904
905 // calculate perturbed residual
906 eval(x0, m_work2.data(), 0.0, 0);
907
908 // compute nth column of Jacobian
909 for (size_t i = 0; i < size(); i++) {
910 double delta = m_work2[i] - m_work1[i];
911 if (std::abs(delta) > m_jacobianThreshold || i == j) {
912 m_jac->setValue(i, j, delta * rdx);
913 }
914 }
915 x0[j] = xsave;
916 }
917
918 m_jac->updateElapsed(double(clock() - t0) / CLOCKS_PER_SEC);
919 m_jac->incrementEvals();
920 m_jac->setAge(0);
921}
922
923double SteadyReactorSolver::weightedNorm(const double* step) const
924{
925 double sum = 0.0;
926 const double* x = m_state->data();
927 for (size_t i = 0; i < size(); i++) {
928 double ewt = m_net->rtol()*x[i] + m_net->atol();
929 double f = step[i] / ewt;
930 sum += f*f;
931 }
932 return sqrt(sum / size());
933}
934
936{
937 return m_net->componentName(i);
938}
939
941{
942 return m_net->upperBound(i);
943}
944
946{
947 return m_net->lowerBound(i);
948}
949
951{
952 m_net->resetBadValues(x);
953}
954
955void SteadyReactorSolver::writeDebugInfo(const string& header_suffix,
956 const string& message, int loglevel, int attempt_counter)
957{
958 if (loglevel >= 6 && !m_state->empty()) {
959 const auto& state = *m_state;
960 writelog("Current state ({}):\n[", header_suffix);
961 for (size_t i = 0; i < state.size() - 1; i++) {
962 writelog("{}, ", state[i]);
963 }
964 writelog("{}]\n", state.back());
965 }
966 if (loglevel >= 7 && !m_xnew.empty()) {
967 writelog("Current residual ({}):\n[", header_suffix);
968 for (size_t i = 0; i < m_xnew.size() - 1; i++) {
969 writelog("{}, ", m_xnew[i]);
970 }
971 writelog("{}]\n", m_xnew.back());
972 }
973}
974
975shared_ptr<ReactorNet> newReactorNet(vector<shared_ptr<ReactorBase>>& reactors)
976{
977 return make_shared<ReactorNet>(reactors);
978}
979
980}
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.
void setNetwork(ReactorNet *net)
Set the ReactorNet that this reactor belongs to.
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.
string name() const
Return the name of this reactor.
Definition ReactorBase.h:82
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:444
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:258
vector< size_t > m_start
m_start[n] is the starting point in the state vector for reactor n
Definition ReactorNet.h:416
vector< double > m_LHS
m_LHS is a vector representing the coefficients on the "left hand side" of each governing equation
Definition ReactorNet.h:450
double m_initial_time
The initial value of the independent variable in the system.
Definition ReactorNet.h:409
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:406
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:194
bool m_limit_check_active
Indicates whether the advance-limit root check is active for the current call to advance(t,...
Definition ReactorNet.h:447
map< string, int > m_counts
Map used for default name generation.
Definition ReactorNet.h:401
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.
void addReactor(Reactor &r)
Add the reactor r to this reactor network.
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:427
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:432
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:442
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:412
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:435
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:93
size_t nSensParams() const override
Number of sensitivity parameters associated with this reactor.
Definition Reactor.cpp:124
string type() const override
String indicating the reactor model implemented.
Definition Reactor.h:55
void initialize(double t0=0.0) override
Initialize the reactor.
Definition Reactor.cpp:96
virtual bool preconditionerSupported() const
Return a false if preconditioning is not supported or true otherwise.
Definition Reactor.h:243
virtual bool isOde() const
Indicate whether the governing equations for this reactor type are a system of ODEs or DAEs.
Definition Reactor.h:62
virtual bool timeIsIndependent() const
Indicates whether the governing equations for this reactor are functions of time or a spatial variabl...
Definition Reactor.h:68
Adapter class to enable using the SteadyStateSystem solver with ReactorNet.
Definition ReactorNet.h:460
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:478
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:482
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.
int solve(double *x0, double *x1, int loglevel)
Solve , where is the residual function.
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 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:159
void writelog(const string &fmt, const Args &... args)
Write a formatted message to the screen.
Definition global.h:176
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.
void warn_user(const string &method, const string &msg, const Args &... args)
Print a user warning raised from method as CanteraWarning.
Definition global.h:268
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.
void warn_deprecated(const string &source, const AnyBase &node, const string &message)
A deprecation warning for syntax in an input file.
Definition AnyMap.cpp:1997
Various templated functions that carry out common vector and polynomial operations (see Templated Arr...