Cantera  4.0.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
12#include "cantera/base/Array.h"
20
21#include <cstdio>
22#include <deque>
23
24namespace Cantera
25{
26
27ReactorNet::~ReactorNet()
28{
29}
30
31ReactorNet::ReactorNet(shared_ptr<ReactorBase> reactor)
32 : ReactorNet(span<shared_ptr<ReactorBase>>{&reactor, 1})
33{
34}
35
36ReactorNet::ReactorNet(span<shared_ptr<ReactorBase>> reactors)
37{
38 if (reactors.empty()) {
39 throw CanteraError("ReactorNet::ReactorNet", "No reactors in network!");
40 }
41
42 suppressErrors(true);
43 bool isOde = true;
44
45 // Names of Reactors and ReactorSurfaces using each Solution; should be only one
46 // reactor per Solution.
47 map<Solution*, set<string>> solutions;
48
49 // All ReactorBase objects connected to this network, starting with the ones
50 // explicitly included.
51 std::deque<shared_ptr<ReactorBase>> reactorQueue(reactors.begin(), reactors.end());
52 std::set<shared_ptr<ReactorBase>> visited;
53
54 while (!reactorQueue.empty()) {
55 auto R = reactorQueue.front();
56 reactorQueue.pop_front();
57
58 if (visited.find(R) != visited.end()) {
59 continue;
60 }
61 visited.insert(R);
62
63 if (auto bulk = std::dynamic_pointer_cast<Reactor>(R)) {
64 m_bulkReactors.push_back(bulk);
65 m_reactors.push_back(R);
66 } else if (auto surf = std::dynamic_pointer_cast<ReactorSurface>(R)) {
67 m_surfaces.push_back(surf);
68 m_reactors.push_back(R);
69 for (size_t i = 0; i < surf->nAdjacent(); i++) {
70 reactorQueue.push_back(surf->adjacent(i));
71 }
72 } else if (auto res = std::dynamic_pointer_cast<Reservoir>(R)) {
73 m_reservoirs.push_back(res);
74 }
75
76 // Discover reservoirs, flow devices, and walls connected to this reactor
77 for (size_t i = 0; i < R->nInlets(); i++) {
78 auto& inlet = R->inlet(i);
79 m_flowDevices.insert(&inlet);
80 reactorQueue.push_back(inlet.in().shared_from_this());
81 }
82 for (size_t i = 0; i < R->nOutlets(); i++) {
83 auto& outlet = R->outlet(i);
84 m_flowDevices.insert(&outlet);
85 reactorQueue.push_back(outlet.out().shared_from_this());
86 }
87 for (size_t i = 0; i < R->nWalls(); i++) {
88 auto& wall = R->wall(i);
89 m_walls.insert(&wall);
90 reactorQueue.push_back(wall.left().shared_from_this());
91 reactorQueue.push_back(wall.right().shared_from_this());
92 }
93
94 auto phase = R->phase();
95 for (size_t i = 0; i < R->nSurfs(); i++) {
96 if (R->surface(i)->phase()->adjacent(phase->name()) != phase) {
97 throw CanteraError("ReactorNet::initialize",
98 "Bulk phase '{}' used by interface '{}' must be the same object\n"
99 "as the contents of the adjacent reactor '{}'.",
100 phase->name(), R->surface(i)->name(), R->name());
101 }
102 reactorQueue.push_back(R->surface(i)->shared_from_this());
103 }
104 R->setNetwork(this);
105 updateNames(*R);
106 solutions[phase.get()].insert(R->name());
107 }
108
109 for (auto& R : m_bulkReactors) {
110 if (R->type() == "FlowReactor" && m_bulkReactors.size() > 1) {
111 throw CanteraError("ReactorNet::initialize",
112 "FlowReactors must be used alone.");
113 }
114 m_timeIsIndependent = R->timeIsIndependent();
115 R->initialize(m_time);
116 isOde = R->isOde();
117 R->setOffset(m_nv);
118 size_t nv = R->neq();
119 m_nv += nv;
120
121 for (auto current : m_bulkReactors) {
122 if (current->isOde() != R->isOde()) {
123 throw CanteraError("ReactorNet::ReactorNet",
124 "Cannot mix Reactor types using both ODEs and DAEs ({} and {})",
125 current->type(), R->type());
126 }
127 if (current->timeIsIndependent() != R->timeIsIndependent()) {
128 throw CanteraError("ReactorNet::ReactorNet",
129 "Cannot mix Reactor types using time and space as independent variables"
130 "\n({} and {})", current->type(), R->type());
131 }
132 }
133 }
134
135 for (auto surf : m_surfaces) {
136 surf->setOffset(m_nv);
137 surf->initialize(m_time);
138 m_nv += surf->neq();
139 solutions[surf->phase().get()].insert(surf->name());
140 }
141
142 for (auto& [soln, reactors] : solutions) {
143 if (reactors.size() > 1) {
144 string shared;
145 for (auto r : reactors) {
146 if (r != *reactors.begin()) {
147 shared += ", ";
148 }
149 shared += fmt::format("'{}'", r);
150 }
151 throw CanteraError("ReactorNet::initialize", "The following reactors /"
152 " reactor surfaces are using the same Solution object: {}. Use"
153 " independent Solution objects or set the 'clone' argument to 'true'"
154 " when creating the Reactor or ReactorSurface objects.", shared);
155 }
156 }
157
158 m_ydot.resize(m_nv, 0.0);
159 m_yest.resize(m_nv, 0.0);
160 m_advancelimits.resize(m_nv, -1.0);
161 m_atol.resize(m_nv);
162
163 m_integ.reset(newIntegrator(isOde ? "CVODE" : "IDA"));
164 // use backward differencing, with a full Jacobian computed
165 // numerically, and use a Newton linear iterator
166 m_integ->setMethod(BDF_Method);
167 m_integ->setLinearSolverType("DENSE");
168 setTolerances(-1.0, -1.0);
169}
170
172{
173 m_time = time;
176}
177
178void ReactorNet::setMaxTimeStep(double maxstep)
179{
180 m_maxstep = maxstep;
182}
183
185{
187}
188
189void ReactorNet::setTolerances(double rtol, double atol)
190{
191 if (rtol >= 0.0) {
192 m_rtol = rtol;
193 }
194 if (atol >= 0.0) {
195 m_atols = atol;
196 }
197 fill(m_atol.begin(), m_atol.end(), m_atols);
198 m_integ->setTolerances(m_rtol, neq(), m_atol.data());
199}
200
201void ReactorNet::setSensitivityTolerances(double rtol, double atol)
202{
203 if (rtol >= 0.0) {
204 m_rtolsens = rtol;
205 }
206 if (atol >= 0.0) {
207 m_atolsens = atol;
208 }
209 m_integ->setSensitivityTolerances(m_rtolsens, m_atolsens);
210}
211
214 return m_time;
215 } else {
216 throw CanteraError("ReactorNet::time", "Time is not the independent variable"
217 " for this reactor network.");
218 }
219}
220
222 if (!m_timeIsIndependent) {
223 return m_time;
224 } else {
225 throw CanteraError("ReactorNet::distance", "Distance is not the independent"
226 " variable for this reactor network.");
227 }
228}
229
231{
234 reinitialize();
235 }
236 return;
237 }
238 if (!m_linearSolverType.empty()) {
239 m_integ->setLinearSolverType(m_linearSolverType);
240 }
241 if (m_precon) {
242 m_integ->setPreconditioner(m_precon);
243 }
244 for (auto& reactor : m_reactors) {
246 }
247 m_integ->initialize(m_time, *this);
248 if (m_verbose) {
249 writelog("Number of equations: {:d}\n", neq());
250 writelog("Maximum time step: {:14.6g}\n", m_maxstep);
251 }
252 if (m_integ->preconditionerSide() != PreconditionerSide::NO_PRECONDITION) {
254 }
255 m_needIntegratorInit = false;
257}
258
260{
262 debuglog("Re-initializing reactor network.\n", m_verbose);
263 for (auto& reactor : m_reactors) {
265 }
266 m_integ->reinitialize(m_time, *this);
267 if (m_integ->preconditionerSide() != PreconditionerSide::NO_PRECONDITION) {
269 }
270 m_needIntegratorInit = false;
271 } else {
272 initialize();
273 }
274}
275
276void ReactorNet::setLinearSolverType(const string& linSolverType)
277{
278 m_linearSolverType = linSolverType;
280}
281
282void ReactorNet::setPreconditioner(shared_ptr<SystemJacobian> preconditioner)
283{
284 m_precon = preconditioner;
286}
287
289{
290 integrator().setMaxSteps(nmax);
291}
292
294{
295 return integrator().maxSteps();
296}
297
298void ReactorNet::advance(double time)
299{
300 initialize();
301 m_integ->integrate(time);
302 m_time = m_integ->currentTime();
303 updateState(m_integ->solution());
304}
305
306double ReactorNet::advance(double time, bool applylimit)
307{
308 initialize();
309 if (!applylimit || !hasAdvanceLimits()) {
310 // No limit enforcement requested; integrate to requested time
311 advance(time);
312 return time;
313 }
314
315 // Enable root-based limit detection and set the base state to the current state
316 m_ybase.assign(m_nv, 0.0);
317 getState(m_ybase.data());
320 m_integ->setRootFunctionCount(nRootFunctions());
321
322 // Integrate toward the requested time; integrator will return early if a limit is
323 // reached (CV_ROOT_RETURN). The try/catch ensures the temporary root-finding state
324 // is cleared even when CVODE throws so subsequent calls start clean.
325 try {
326 m_integ->integrate(time);
327 } catch (...) {
328 m_limit_check_active = false;
329 m_integ->setRootFunctionCount(nRootFunctions());
330 throw;
331 }
332 m_time = m_integ->currentTime();
333
334 // Update reactor states to match the integrator solution at the time reached
335 // (which may be earlier than 'time' if a limit was triggered)
336 updateState(m_integ->solution());
337
338 // Disable limit checking after this call
339 m_limit_check_active = false;
340 m_integ->setRootFunctionCount(nRootFunctions());
341
342 // When a root event stopped integration before reaching the requested time, report
343 // the most limiting component and details about the step.
344 if (m_verbose && m_time < time) {
345 // Ensure limits are available
346 if (m_advancelimits.size() != m_nv) {
347 m_advancelimits.assign(m_nv, -1.0);
348 }
349 getAdvanceLimits(m_advancelimits.data());
350 double* ycurr = m_integ->solution();
351 size_t jmax = npos;
352 double max_ratio = -1.0;
353 double best_limit = 0.0;
354 for (size_t j = 0; j < m_nv; j++) {
355 double lim = m_advancelimits[j];
356 if (lim > 0.0) {
357 double delta = std::abs(ycurr[j] - m_ybase[j]);
358 double ratio = delta / lim;
359 if (ratio > max_ratio) {
360 max_ratio = ratio;
361 jmax = j;
362 best_limit = lim;
363 }
364 }
365 }
366 if (jmax != npos) {
367 double dt = m_time - m_ybase_time;
368 double y_start = m_ybase[jmax];
369 double y_end = ycurr[jmax];
370 double delta = y_end - y_start;
371 writelog(" Advance limit triggered for component {:d} (dt = {:9.4g}):"
372 " y_start = {:11.6g}, y_end = {:11.6g},"
373 " delta = {:11.6g}, limit = {:9.4g}\n",
374 jmax, dt, y_start, y_end, delta, best_limit);
375 }
376 }
377
378 // m_time is tracked via callbacks during integration
379 return m_time;
380}
381
383{
384 initialize();
385 m_time = m_integ->step(m_time + 1.0);
386 updateState(m_integ->solution());
387 return m_time;
388}
389
390void ReactorNet::solveSteady(int loglevel)
391{
392 initialize();
393 vector<double> y(neq());
394 getState(y.data());
395 SteadyReactorSolver solver(this, y.data());
397 solver.solve(loglevel);
398 solver.getState(y.data());
399 updateState(y.data());
400}
401
402Eigen::SparseMatrix<double> ReactorNet::steadyJacobian(double rdt)
403{
404 initialize();
405 vector<double> y0(neq());
406 vector<double> y1(neq());
407 getState(y0.data());
408 SteadyReactorSolver solver(this, y0.data());
409 solver.evalJacobian(y0.data());
410 if (rdt) {
411 solver.linearSolver()->updateTransient(rdt, solver.transientMask().data());
412 }
413 return std::dynamic_pointer_cast<EigenSparseJacobian>(solver.linearSolver())->jacobian();
414}
415
416void ReactorNet::getEstimate(double time, int k, double* yest)
417{
418 initialize();
419 double* cvode_dky = m_integ->solution();
420 for (size_t j = 0; j < m_nv; j++) {
421 yest[j] = cvode_dky[j];
422 }
423
424 // Taylor expansion
425 double factor = 1.;
426 double deltat = time - m_time;
427 for (int n = 1; n <= k; n++) {
428 factor *= deltat / n;
429 cvode_dky = m_integ->derivative(m_time, n);
430 for (size_t j = 0; j < m_nv; j++) {
431 yest[j] += factor * cvode_dky[j];
432 }
433 }
434}
435
437{
438 if (m_integ) {
439 return m_integ->lastOrder();
440 } else {
441 return 0;
442 }
443}
444
446{
447 return (m_limit_check_active && hasAdvanceLimits()) ? 1 : 0;
448}
449
450void ReactorNet::evalRootFunctions(double t, const double* y, double* gout)
451{
452 // Default: no root detected
453 double g = 1.0;
454
456 // Ensure limits vector is current
457 if (m_advancelimits.size() != m_nv) {
458 m_advancelimits.assign(m_nv, -1.0);
459 }
460 getAdvanceLimits(m_advancelimits.data());
461
462 double max_ratio = 0.0;
463 for (size_t i = 0; i < m_nv; i++) {
464 double lim = m_advancelimits[i];
465 if (lim > 0.0) {
466 double delta = std::abs(y[i] - m_ybase[i]);
467 double ratio = delta / lim;
468 if (ratio > max_ratio) {
469 max_ratio = ratio;
470 }
471 }
472 }
473 g = 1.0 - max_ratio; // root at g = 0 when any component reaches its limit
474 }
475
476 gout[0] = g;
477}
478
480{
481 // ensure that reactors and components have reproducible names
483
484 for (size_t i=0; i<r.nWalls(); i++) {
485 auto& w = r.wall(i);
487 if (w.left().type() == "Reservoir") {
488 w.left().setDefaultName(m_counts);
489 }
490 if (w.right().type() == "Reservoir") {
491 w.right().setDefaultName(m_counts);
492 }
493 }
494
495 for (size_t i=0; i<r.nInlets(); i++) {
496 auto& in = r.inlet(i);
498 if (in.in().type() == "Reservoir") {
499 in.in().setDefaultName(m_counts);
500 }
501 }
502
503 for (size_t i=0; i<r.nOutlets(); i++) {
504 auto& out = r.outlet(i);
506 if (out.out().type() == "Reservoir") {
507 out.out().setDefaultName(m_counts);
508 }
509 }
510
511 for (size_t i=0; i<r.nSurfs(); i++) {
513 }
514}
515
517 if (m_integ == nullptr) {
518 throw CanteraError("ReactorNet::integrator",
519 "Integrator has not been instantiated. Add one or more reactors first.");
520 }
521 return *m_integ;
522}
523
524void ReactorNet::eval(double t, double* y, double* ydot, double* p)
525{
526 m_time = t;
527 updateState(y);
528 m_LHS.assign(m_nv, 1);
529 m_RHS.assign(m_nv, 0);
530 for (auto& R : m_reactors) {
531 size_t offset = R->offset();
532 R->applySensitivity(p);
533 R->eval(t, m_LHS.data() + offset, m_RHS.data() + offset);
534 for (size_t i = offset; i < offset + R->neq(); i++) {
535 ydot[i] = m_RHS[i] / m_LHS[i];
536 }
537 R->resetSensitivity(p);
538 }
539 checkFinite("ydot", ydot, m_nv);
540}
541
542void ReactorNet::evalSteady(double* y, double* residual)
543{
544 updateState(y);
545 m_LHS.assign(m_nv, 1);
546 m_RHS.assign(m_nv, 0);
547 for (auto& R : m_reactors) {
548 size_t offset = R->offset();
549 R->evalSteady(m_time, m_LHS.data() + offset, m_RHS.data() + offset);
550 for (size_t i = offset; i < offset + R->neq(); i++) {
551 residual[i] = m_RHS[i] / m_LHS[i];
552 }
553 }
554 checkFinite("residual", residual, m_nv);
555}
556
557void ReactorNet::evalDae(double t, double* y, double* ydot, double* p, double* residual)
558{
559 m_time = t;
560 updateState(y);
561 for (auto& R : m_reactors) {
562 size_t offset = R->offset();
563 R->applySensitivity(p);
564 R->evalDae(t, y + offset, ydot + offset, residual + offset);
565 R->resetSensitivity(p);
566 }
567 checkFinite("ydot", ydot, m_nv);
568}
569
570void ReactorNet::getConstraints(double* constraints)
571{
572 for (auto& R : m_reactors) {
573 R->getConstraints(constraints + R->offset());
574 }
575}
576
577double ReactorNet::sensitivity(size_t k, size_t p)
578{
579 initialize();
580 if (p >= m_sens_params.size()) {
581 throw IndexError("ReactorNet::sensitivity",
582 "m_sens_params", p, m_sens_params.size());
583 }
584 double denom = m_integ->solution(k);
585 if (denom == 0.0) {
586 denom = SmallNumber;
587 }
588 return m_integ->sensitivity(k, p) / denom;
589}
590
591void ReactorNet::evalJacobian(double t, double* y, double* ydot, double* p, Array2D* j)
592{
593 //evaluate the unperturbed ydot
594 eval(t, y, ydot, p);
595 for (size_t n = 0; n < m_nv; n++) {
596 // perturb x(n)
597 double ysave = y[n];
598 double dy = m_atol[n] + fabs(ysave)*m_rtol;
599 y[n] = ysave + dy;
600 dy = y[n] - ysave;
601
602 // calculate perturbed residual
603 eval(t, y, m_ydot.data(), p);
604
605 // compute nth column of Jacobian
606 for (size_t m = 0; m < m_nv; m++) {
607 j->value(m,n) = (m_ydot[m] - ydot[m])/dy;
608 }
609 y[n] = ysave;
610 }
611}
612
614{
615 checkFinite("y", y, m_nv);
616 for (auto& R : m_reactors) {
617 R->updateState(y + R->offset());
618 }
619}
620
621void ReactorNet::getDerivative(int k, double* dky)
622{
623 initialize();
624 double* cvode_dky = m_integ->derivative(m_time, k);
625 for (size_t j = 0; j < m_nv; j++) {
626 dky[j] = cvode_dky[j];
627 }
628}
629
630void ReactorNet::setAdvanceLimits(const double *limits)
631{
632 initialize();
633 for (auto& R : m_bulkReactors) {
634 R->setAdvanceLimits(limits + R->offset());
635 }
636}
637
639{
640 bool has_limit = false;
641 for (size_t n = 0; n < m_bulkReactors.size(); n++) {
642 has_limit |= m_bulkReactors[n]->hasAdvanceLimits();
643 }
644 return has_limit;
645}
646
647bool ReactorNet::getAdvanceLimits(double *limits) const
648{
649 bool has_limit = false;
650 for (auto& R : m_bulkReactors) {
651 has_limit |= R->getAdvanceLimits(limits + R->offset());
652 }
653 return has_limit;
654}
655
656void ReactorNet::getState(double* y)
657{
658 for (auto& R : m_reactors) {
659 R->getState(y + R->offset());
660 }
661}
662
663void ReactorNet::getStateDae(double* y, double* ydot)
664{
665 // Iterate in reverse order so that surfaces will be handled first and up-to-date
666 // values of the surface production rates of bulk species will be available when
667 // bulk reactors are processed.
668 // TODO: Replace with view::reverse once Cantera requires C++20
669 for (size_t n = m_reactors.size(); n != 0 ; n--) {
670 auto& R = m_reactors[n-1];
671 R->getStateDae(y + R->offset(), ydot + R->offset());
672 }
673}
674
675size_t ReactorNet::globalComponentIndex(const string& component, size_t reactor)
676{
677 initialize();
678 return m_reactors[reactor]->offset()
679 + m_reactors[reactor]->componentIndex(component);
680}
681
682string ReactorNet::componentName(size_t i) const
683{
684 size_t iTot = 0;
685 size_t i0 = i;
686 for (auto r : m_reactors) {
687 if (i < r->neq()) {
688 return r->name() + ": " + r->componentName(i);
689 } else {
690 i -= r->neq();
691 }
692 iTot += r->neq();
693 }
694 throw IndexError("ReactorNet::componentName", "component", i0, iTot);
695}
696
697double ReactorNet::upperBound(size_t i) const
698{
699 size_t iTot = 0;
700 size_t i0 = i;
701 for (auto r : m_reactors) {
702 if (i < r->neq()) {
703 return r->upperBound(i);
704 } else {
705 i -= r->neq();
706 }
707 iTot += r->neq();
708 }
709 throw IndexError("ReactorNet::upperBound", "upperBound", i0, iTot);
710}
711
712double ReactorNet::lowerBound(size_t i) const
713{
714 size_t iTot = 0;
715 size_t i0 = i;
716 for (auto r : m_reactors) {
717 if (i < r->neq()) {
718 return r->lowerBound(i);
719 } else {
720 i -= r->neq();
721 }
722 iTot += r->neq();
723 }
724 throw IndexError("ReactorNet::lowerBound", "lowerBound", i0, iTot);
725}
726
728 for (auto& R : m_reactors) {
729 R->resetBadValues(y + R->offset());
730 }
731}
732
734 const string& name, double value, double scale)
735{
737 throw CanteraError("ReactorNet::registerSensitivityParameter",
738 "Sensitivity parameters cannot be added after the"
739 "integrator has been initialized.");
740 }
741 m_paramNames.push_back(name);
742 m_sens_params.push_back(value);
743 m_paramScales.push_back(scale);
744 return m_sens_params.size() - 1;
745}
746
748{
749 // Apply given settings to all reactors
750 for (auto& R : m_bulkReactors) {
751 R->setDerivativeSettings(settings);
752 }
753}
754
756{
757 if (m_integ) {
758 return m_integ->solverStats();
759 } else {
760 return AnyMap();
761 }
762}
763
765{
766 if (m_integ) {
767 return m_integ->linearSolverType();
768 } else {
769 return "";
770 }
771}
772
773void ReactorNet::preconditionerSolve(double* rhs, double* output)
774{
775 if (!m_integ) {
776 throw CanteraError("ReactorNet::preconditionerSolve",
777 "Must only be called after ReactorNet is initialized.");
778 }
779 m_integ->preconditionerSolve(m_nv, rhs, output);
780}
781
782void ReactorNet::preconditionerSetup(double t, double* y, double gamma)
783{
784 // ensure state is up to date.
785 updateState(y);
786 // get the preconditioner
787 auto precon = std::dynamic_pointer_cast<EigenSparseJacobian>(
788 m_integ->preconditioner());
789 // Reset preconditioner
790 precon->reset();
791 // Set gamma value for M =I - gamma*J
792 precon->setGamma(gamma);
793 // Make a copy of state to adjust it for preconditioner
794 vector<double> yCopy(m_nv);
795 // Get state of reactor
796 getState(yCopy.data());
797 // transform state based on preconditioner rules
798 precon->stateAdjustment(yCopy);
799 // update network with adjusted state
800 updateState(yCopy.data());
801 // Get jacobians and give elements to preconditioners
802 vector<Eigen::Triplet<double>> trips;
803 for (auto& R : m_reactors) {
804 R->getJacobianElements(trips);
805 }
806 precon->setFromTriplets(trips);
807 // post reactor setup operations
808 precon->updatePreconditioner();
809}
810
812{
813 if (!m_integ) {
814 throw CanteraError("ReactorNet::updatePreconditioner",
815 "Must only be called after ReactorNet is initialized.");
816 }
817 auto precon = m_integ->preconditioner();
818 precon->setGamma(gamma);
819 precon->updatePreconditioner();
820}
821
823 // check for non-mole-based reactors and throw an error otherwise
824 for (auto reactor : m_bulkReactors) {
826 throw CanteraError("ReactorNet::checkPreconditionerSupported",
827 "Preconditioning is only supported for type *MoleReactor,\n"
828 "Reactor type given: '{}'.",
829 reactor->type());
830 }
831 }
832}
833
834SteadyReactorSolver::SteadyReactorSolver(ReactorNet* net, double* x0)
835 : m_net(net)
836{
837 m_size = m_net->neq();
838 m_jac = newSystemJacobian("eigen-sparse-direct");
840 m_initialState.assign(x0, x0 + m_size);
841 setInitialGuess(x0);
842 setJacobianPerturbation(m_jacobianRelPerturb, 1000 * m_net->atol(),
843 m_jacobianThreshold);
844 m_mask.assign(m_size, 1);
845 size_t start = 0;
846 for (size_t i = 0; i < net->nReactors(); i++) {
847 auto& R = net->reactor(i);
848 R.updateState(x0 + start);
849 auto algebraic = R.initializeSteady();
850 for (auto& m : algebraic) {
851 m_mask[start + m] = 0;
852 }
853 start += R.neq();
854 }
855}
856
857void SteadyReactorSolver::eval(double* x, double* r, double rdt, int count)
858{
859 if (rdt < 0.0) {
860 rdt = m_rdt;
861 }
862 vector<double> xv(x, x + size());
863 m_net->evalSteady(x, r);
864 for (size_t i = 0; i < size(); i++) {
865 r[i] -= (x[i] - m_initialState[i]) * rdt * m_mask[i];
866 }
867}
868
869void SteadyReactorSolver::initTimeInteg(double dt, double* x)
870{
872 m_initialState.assign(x, x + size());
873}
874
876{
877 m_jac->reset();
878 clock_t t0 = clock();
879 m_work1.resize(size());
880 m_work2.resize(size());
881 eval(x0, m_work1.data(), 0.0, 0);
882 for (size_t j = 0; j < size(); j++) {
883 // perturb x(n); preserve sign(x(n))
884 double xsave = x0[j];
885 double dx = fabs(xsave) * m_jacobianRelPerturb + m_jacobianAbsPerturb;
886 if (xsave < 0) {
887 dx = -dx;
888 }
889 x0[j] = xsave + dx;
890 double rdx = 1.0 / (x0[j] - xsave);
891
892 // calculate perturbed residual
893 eval(x0, m_work2.data(), 0.0, 0);
894
895 // compute nth column of Jacobian
896 for (size_t i = 0; i < size(); i++) {
897 double delta = m_work2[i] - m_work1[i];
898 if (std::abs(delta) > m_jacobianThreshold || i == j) {
899 m_jac->setValue(i, j, delta * rdx);
900 }
901 }
902 x0[j] = xsave;
903 }
904 // Restore system to unperturbed state
905 m_net->updateState(x0);
906
907 m_jac->updateElapsed(double(clock() - t0) / CLOCKS_PER_SEC);
908 m_jac->incrementEvals();
909 m_jac->setAge(0);
910}
911
912double SteadyReactorSolver::weightedNorm(const double* step) const
913{
914 double sum = 0.0;
915 const double* x = m_state->data();
916 for (size_t i = 0; i < size(); i++) {
917 double ewt = m_net->rtol()*fabs(x[i]) + m_net->atol();
918 double f = step[i] / ewt;
919 sum += f*f;
920 }
921 return sqrt(sum / size());
922}
923
925{
926 return m_net->componentName(i);
927}
928
930{
931 return m_net->upperBound(i);
932}
933
935{
936 return m_net->lowerBound(i);
937}
938
940{
941 m_net->resetBadValues(x);
942}
943
944void SteadyReactorSolver::writeDebugInfo(const string& header_suffix,
945 const string& message, int loglevel, int attempt_counter)
946{
947 if (loglevel >= 6 && !m_state->empty()) {
948 const auto& state = *m_state;
949 writelog("Current state ({}):\n[", header_suffix);
950 for (size_t i = 0; i < state.size() - 1; i++) {
951 writelog("{}, ", state[i]);
952 }
953 writelog("{}]\n", state.back());
954 }
955 if (loglevel >= 7 && !m_xnew.empty()) {
956 writelog("Current residual ({}):\n[", header_suffix);
957 for (size_t i = 0; i < m_xnew.size() - 1; i++) {
958 writelog("{}, ", m_xnew[i]);
959 }
960 writelog("{}]\n", m_xnew.back());
961 }
962}
963
964shared_ptr<ReactorNet> newReactorNet(vector<shared_ptr<ReactorBase>>& reactors)
965{
966 return make_shared<ReactorNet>(reactors);
967}
968
969}
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
Base class for reactor objects.
Definition ReactorBase.h:51
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.
virtual string type() const
String indicating the reactor model implemented.
Definition ReactorBase.h:76
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.
virtual bool preconditionerSupported() const
Return a false if preconditioning is not supported or true otherwise.
virtual void updateState(double *y)
Set the state of the reactor to correspond to the state vector y.
virtual void updateConnected(bool updatePressure)
Update state information needed by connected reactors, flow devices, and walls.
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:442
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:249
ReactorBase & reactor(int n)
Return a reference to the n-th reactor in this network.
Definition ReactorNet.h:185
vector< double > m_LHS
m_LHS is a vector representing the coefficients on the "left hand side" of each governing equation
Definition ReactorNet.h:448
double m_initial_time
The initial value of the independent variable in the system.
Definition ReactorNet.h:410
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 updateNames(ReactorBase &r)
Create reproducible names for reactors and walls/connectors.
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:407
AnyMap solverStats() const
Get solver stats from integrator.
bool m_limit_check_active
Indicates whether the advance-limit root check is active for the current call to advance(t,...
Definition ReactorNet.h:445
map< string, int > m_counts
Map used for default name generation.
Definition ReactorNet.h:402
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...
bool m_integratorInitialized
True if the integrator has been initialized at least once.
Definition ReactorNet.h:412
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...
void evalSteady(double *y, double *residual)
Evaluate the governing equations adapted for the steady-state solver.
double m_maxstep
Maximum integrator internal timestep. Default of 0.0 means infinity.
Definition ReactorNet.h:425
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.
ReactorNet(shared_ptr< ReactorBase > reactor)
Create reactor network containing single reactor.
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 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.
bool m_needIntegratorInit
True if integrator needs to be (re)initialized.
Definition ReactorNet.h:413
void setAdvanceLimits(const double *limits)
Set absolute step size limits during advance.
double rtol()
Relative tolerance.
Definition ReactorNet.h:95
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:430
double atol()
Absolute integration tolerance.
Definition ReactorNet.h:100
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:440
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.
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:433
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.
Adapter class to enable using the SteadyStateSystem solver with ReactorNet.
Definition ReactorNet.h:458
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:476
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.
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.
vector< int > m_mask
Transient mask.
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:182
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:160
offset
Offsets of solution components in the 1D solution array.
Definition Flow1D.h:25
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...