Cantera  4.0.0a2
Loading...
Searching...
No Matches
SteadyStateSystem.cpp
Go to the documentation of this file.
1//! @file SteadyStateSystem.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
9
10#include <chrono>
11
12using namespace std;
13
14namespace Cantera
15{
16
17SteadyStateSystem::SteadyStateSystem()
18{
19 m_state = make_shared<vector<double>>();
20 m_newt = make_unique<MultiNewton>(1);
21 clearStats();
22}
23
24SteadyStateSystem::~SteadyStateSystem()
25{
26}
27
29{
30 auto t0 = std::chrono::steady_clock::now();
31 m_jac->updateTransient(m_rdt, m_mask);
32 recordFactorization(std::chrono::duration<double>(
33 std::chrono::steady_clock::now() - t0).count());
34}
35
37{
38 if (!m_jac) {
39 return;
40 }
41 long int nev = m_jac->nEvals();
42 if (nev > 0 && m_nevals > 0) {
43 m_stats["grid_points"].asVector<long int>().push_back(
44 static_cast<long int>(gridSize()));
45 m_stats["steps"].asVector<long int>().push_back(m_nsteps);
46 m_stats["residual_evals"].asVector<long int>().push_back(m_nevals);
47 m_stats["residual_time"].asVector<double>().push_back(m_evaltime);
48 m_stats["jacobian_evals"].asVector<long int>().push_back(nev);
49 m_stats["jacobian_time"].asVector<double>().push_back(m_jac->elapsedTime());
50 m_stats["factorizations"].asVector<long int>().push_back(m_factorizations);
51 m_stats["factor_time"].asVector<double>().push_back(m_factorTime);
52 m_stats["linear_solves"].asVector<long int>().push_back(m_linearSolves);
53 m_stats["solve_time"].asVector<double>().push_back(m_solveTime);
54 m_stats["total_time"].asVector<double>().push_back(m_gridTime);
55 m_nevals = 0;
56 m_evaltime = 0.0;
58 m_factorTime = 0.0;
60 m_solveTime = 0.0;
61 m_gridTime = 0.0;
62 m_nsteps = 0;
63 }
64}
65
67{
68 m_stats["grid_points"] = vector<long int>();
69 m_stats["steps"] = vector<long int>();
70 m_stats["residual_evals"] = vector<long int>();
71 m_stats["residual_time"] = vector<double>();
72 m_stats["jacobian_evals"] = vector<long int>();
73 m_stats["jacobian_time"] = vector<double>();
74 m_stats["factorizations"] = vector<long int>();
75 m_stats["factor_time"] = vector<double>();
76 m_stats["linear_solves"] = vector<long int>();
77 m_stats["solve_time"] = vector<double>();
78 m_stats["total_time"] = vector<double>();
79 m_nevals = 0;
80 m_evaltime = 0.0;
82 m_factorTime = 0.0;
84 m_solveTime = 0.0;
85 m_gridTime = 0.0;
86 m_nsteps = 0;
87}
88
89void SteadyStateSystem::setInitialGuess(span<const double> x)
90{
93 m_state->assign(x.begin(), x.end());
94}
95
96void SteadyStateSystem::getState(span<double> x) const
97{
98 copy(m_xnew.begin(), m_xnew.end(), x.begin());
99}
100
101void SteadyStateSystem::solve(int loglevel)
102{
103 auto tStart = std::chrono::steady_clock::now();
104 size_t istep = 0;
105 int nsteps = m_steps[istep];
106 m_nsteps = 0;
107 double dt = m_tstep;
108
109 while (true) {
110 // Keep the attempt_counter in the range of [1, max_history]
112
113 // Attempt to solve the steady problem
116 debuglog("\nAttempt Newton solution of steady-state problem.", loglevel);
117 if (!m_jac_ok) {
119 try {
121 m_jac_ok = true;
122 } catch (CanteraError& err) {
123 // Allow solver to continue after failure to factorize the steady-state
124 // Jacobian by returning to time stepping mode
125 if (m_rdt == 0.0) {
126 if (loglevel > 1) {
127 writelog("\nSteady Jacobian factorization failed:"
128 "\n {}:\n {}",
129 err.getMethod(), err.getMessage());
130 }
131 } else {
132 throw;
133 }
134 }
135 }
136 int m = -1;
137 if (m_jac_ok) {
138 m = newton().solve(*m_state, m_xnew, *this, loglevel);
139 }
140 if (m == 1) {
141 *m_state = m_xnew;
142 writeDebugInfo("NewtonSuccess", "After successful Newton solve",
143 loglevel, m_attempt_counter);
144 m_gridTime += std::chrono::duration<double>(
145 std::chrono::steady_clock::now() - tStart).count();
146 return;
147 } else {
148 debuglog("\nNewton steady-state solve failed.\n", loglevel);
149 writeDebugInfo("NewtonFail", "After unsuccessful Newton solve",
150 loglevel, m_attempt_counter);
151
152 if (loglevel > 0) {
153 writelog("\nAttempt {} timesteps.", nsteps);
154 }
155
156 dt = timeStep(nsteps, dt, *m_state, m_xnew, loglevel-1);
158 writeDebugInfo("Timestepping", "After timestepping", loglevel,
160
161 // Repeat the last timestep's data for logging purposes
162 if (loglevel == 1) {
163 writelog("\nFinal timestep info: dt= {:<10.4g} log(ss)= {:<10.4g}\n", dt,
164 log10(ssnorm(*m_state, m_xnew)));
165 }
166 istep++;
167 if (istep >= m_steps.size()) {
168 nsteps = m_steps.back();
169 } else {
170 nsteps = m_steps[istep];
171 }
172 dt = std::min(dt, m_tmax);
173 }
174 }
175}
176
177SteadyStateSystem::TimeStepGrowthStrategy
178SteadyStateSystem::parseTimeStepGrowthStrategy(const string& strategy)
179{
180 if (strategy == "fixed-growth") {
181 return TimeStepGrowthStrategy::fixed;
182 } else if (strategy == "steady-norm") {
183 return TimeStepGrowthStrategy::steadyNorm;
184 } else if (strategy == "transient-residual") {
185 return TimeStepGrowthStrategy::transientResidual;
186 } else if (strategy == "residual-ratio") {
187 return TimeStepGrowthStrategy::residualRatio;
188 } else if (strategy == "newton-iterations") {
189 return TimeStepGrowthStrategy::newtonIterations;
190 }
191 throw CanteraError("SteadyStateSystem::setTimeStepGrowthStrategy",
192 "Unknown time step growth strategy '{}'; must be one of "
193 "'fixed-growth', 'steady-norm', 'transient-residual', "
194 "'residual-ratio', or 'newton-iterations'.", strategy);
195}
196
197string SteadyStateSystem::timeStepGrowthStrategyName(TimeStepGrowthStrategy strategy)
198{
199 switch (strategy) {
200 case TimeStepGrowthStrategy::fixed:
201 return "fixed-growth";
202 case TimeStepGrowthStrategy::steadyNorm:
203 return "steady-norm";
204 case TimeStepGrowthStrategy::transientResidual:
205 return "transient-residual";
206 case TimeStepGrowthStrategy::residualRatio:
207 return "residual-ratio";
208 case TimeStepGrowthStrategy::newtonIterations:
209 return "newton-iterations";
210 }
211 throw CanteraError("SteadyStateSystem::timeStepGrowthStrategyName",
212 "Unknown time step growth strategy.");
213}
214
216{
217 m_tstep_growth_strategy = parseTimeStepGrowthStrategy(strategy);
218}
219
221{
222 return timeStepGrowthStrategyName(m_tstep_growth_strategy);
223}
224
225double SteadyStateSystem::calculateTimeStepGrowthFactor(span<const double> x_before,
226 span<const double> x_after)
227{
228 if (m_tstep_growth_strategy == TimeStepGrowthStrategy::fixed) {
229 return m_tstep_growth;
230 }
231
232 m_work1.resize(m_size);
233 const double growth = m_tstep_growth;
234
235 switch (m_tstep_growth_strategy) {
236 case TimeStepGrowthStrategy::fixed:
237 return growth;
238 case TimeStepGrowthStrategy::steadyNorm: {
239 double ss_before = ssnorm(x_before, m_work1);
240 double ss_after = ssnorm(x_after, m_work1);
241 return (ss_after < ss_before) ? growth : 1.0;
242 }
243 case TimeStepGrowthStrategy::transientResidual: {
244 double ts_before = tsnorm(x_before, m_work1);
245 double ts_after = tsnorm(x_after, m_work1);
246 return (ts_after < ts_before) ? growth : 1.0;
247 }
248 case TimeStepGrowthStrategy::residualRatio: {
249 double ts_before = tsnorm(x_before, m_work1);
250 double ts_after = tsnorm(x_after, m_work1);
251 if (!(ts_after > 0.0) || !(ts_before > ts_after)) {
252 return 1.0;
253 }
254 const double exponent = 0.2;
255 double ratio = ts_before / ts_after;
256 double factor = std::pow(ratio, exponent);
257 return std::min(growth, std::max(1.0, factor));
258 }
259 case TimeStepGrowthStrategy::newtonIterations: {
260 const int max_iters_for_growth = 3;
261 return (newton().lastIterations() <= max_iters_for_growth) ? growth : 1.0;
262 }
263 }
264 throw CanteraError("SteadyStateSystem::calculateTimeStepGrowthFactor",
265 "Unknown time step growth strategy '{}'.", timeStepGrowthStrategy());
266}
267
268double SteadyStateSystem::timeStep(int nsteps, double dt, span<double> x,
269 span<double> r, int loglevel)
270{
271 // set the Jacobian age parameter to the transient value
273
274 int n = 0;
275 int successiveFailures = 0;
276
277 // Only output this if nothing else under this function call will be output
278 if (loglevel == 1) {
279 writelog("\n============================");
280 writelog("\n{:<5s} {:<11s} {:<7s}\n", "step", "dt (s)", "log(ss)");
281 writelog("============================");
282 }
283 while (n < nsteps) {
284 if (loglevel == 1) { // At level 1, output concise information
285 double ss = ssnorm(x, r);
286 writelog("\n{:<5d} {:<6.4e} {:>7.4f}", n, dt, log10(ss));
287 } else if (loglevel > 1) {
288 double ss = ssnorm(x, r);
289 writelog("\nTimestep ({}) dt= {:<11.4e} log(ss)= {:<7.4f}", n, dt, log10(ss));
290 }
291
292 // set up for time stepping with stepsize dt
293 initTimeInteg(dt, x);
294
295 int j0 = m_jac->nEvals(); // Store the current number of Jacobian evaluations
296
297 // solve the transient problem
298 int status = newton().solve(x, r, *this, loglevel);
299
300 // successful time step. Copy the new solution in r to
301 // the current solution in x.
302 if (status >= 0) {
303 if (loglevel > 1) {
304 writelog("\nTimestep ({}) succeeded", n);
305 }
306 successiveFailures = 0;
307 m_nsteps++;
308 n += 1;
309 if (m_jac->nEvals() == j0) {
311 }
312 copy(r.begin(), r.end(), x.begin());
315 }
316 dt = std::min(dt, m_tmax);
317 if (m_nsteps >= m_nsteps_max) {
318 throw TimeStepError("SteadyStateSystem::timeStep",
319 "Took maximum number of timesteps allowed ({}) without "
320 "reaching steady-state solution.", m_nsteps_max);
321 }
322 } else {
323 // No solution could be found with this time step.
324 // Decrease the stepsize and try again.
325 successiveFailures++;
326 if (loglevel == 1) {
327 writelog("\nTimestep failed");
328 } else if (loglevel > 1) {
329 writelog("\nTimestep ({}) failed", n);
330 }
331 if (successiveFailures > 2) {
332 debuglog("--> Resetting negative species concentrations", loglevel);
334 successiveFailures = 0;
335 } else {
336 debuglog("--> Reducing timestep", loglevel);
337 dt *= m_tfactor;
338 if (dt < m_tmin) {
339 throw TimeStepError("SteadyStateSystem::timeStep",
340 "Time integration failed. Minimum timestep ({}) reached.", m_tmin);
341 }
342 }
343 }
344 }
345
346 // Write the final step to the log
347 if (loglevel == 1) {
348 double ss = ssnorm(x, r);
349 writelog("\n{:<5d} {:<6.4e} {:>7.4f}", n, dt, log10(ss));
350 writelog("\n============================");
351 } else if (loglevel > 1) {
352 double ss = ssnorm(x, r);
353 writelog("\nTimestep ({}) dt= {:<11.4e} log10(ss)= {:<7.4f}\n", n, dt, log10(ss));
354 }
355
356 // return the value of the last stepsize, which may be smaller
357 // than the initial stepsize
358 return dt;
359}
360
361double SteadyStateSystem::ssnorm(span<const double> x, span<double> r)
362{
363 eval(x, r, 0.0, 0);
364 double ss = 0.0;
365 for (size_t i = 0; i < m_size; i++) {
366 ss = std::max(fabs(r[i]),ss);
367 }
368 return ss;
369}
370
371double SteadyStateSystem::tsnorm(span<const double> x, span<double> r)
372{
373 eval(x, r, m_rdt, 0);
374 double ts = 0.0;
375 for (size_t i = 0; i < m_size; i++) {
376 ts = std::max(fabs(r[i]), ts);
377 }
378 return ts;
379}
380
381void SteadyStateSystem::setTimeStep(double stepsize, span<const int> tsteps)
382{
383 m_tstep = stepsize;
384 m_steps.assign(tsteps.begin(), tsteps.end());
385}
386
388{
389 m_state->resize(size());
390 m_xnew.resize(size());
391 m_newt->resize(size());
392 m_mask.resize(size());
393 if (!m_jac) {
394 throw CanteraError("SteadyStateSystem::resize",
395 "Jacobian evaluator must be instantiated before calling resize()");
396 }
397 m_jac->initialize(size());
398 m_jac->setBandwidth(bandwidth());
399 m_jac->clearStats();
400 m_jac_ok = false;
401}
402
403void SteadyStateSystem::setLinearSolver(shared_ptr<SystemJacobian> solver)
404{
405 m_jac = solver;
406 m_jac->initialize(size());
407 m_jac->setBandwidth(bandwidth());
408 m_jac->clearStats();
409 m_jac_ok = false;
410}
411
412void SteadyStateSystem::evalSSJacobian(span<const double> x)
413{
414 double rdt_save = m_rdt;
415 m_jac_ok = false;
417 evalJacobian(x);
418 m_rdt = rdt_save;
419}
420
421void SteadyStateSystem::initTimeInteg(double dt, span<const double> x)
422{
423 double rdt_old = m_rdt;
424 m_rdt = 1.0/dt;
425
426 // if the stepsize has changed, then update the transient part of the Jacobian
427 if (fabs(rdt_old - m_rdt) > Tiny) {
429 }
430}
431
433{
434 if (m_rdt == 0) {
435 return;
436 }
437
438 m_rdt = 0.0;
439 if (m_jac_ok) {
441 }
442}
443
444void SteadyStateSystem::setJacAge(int ss_age, int ts_age)
445{
446 m_ss_jac_age = ss_age;
447 if (ts_age > 0) {
448 m_ts_jac_age = ts_age;
449 } else {
451 }
452}
453
455{
456 return *m_newt;
457}
458
459}
Base class for exceptions thrown by Cantera classes.
virtual string getMessage() const
Method overridden by derived classes to format the error message.
virtual string getMethod() const
Get the name of the method that threw the exception.
virtual double eval(double t) const
Evaluate the function.
Definition Func1.cpp:28
Newton iterator for multi-domain, one-dimensional problems.
Definition MultiNewton.h:24
void setOptions(int maxJacAge=5)
Set options.
int solve(span< const double > x0, span< double > x1, SteadyStateSystem &r, int loglevel)
Find the solution to F(x) = 0 by damped Newton iteration.
int m_nsteps
Number of time steps taken in the current call to solve()
double calculateTimeStepGrowthFactor(span< const double > x_before, span< const double > x_after)
Determine the timestep growth factor after a successful step.
size_t m_size
Solution vector size
int m_nsteps_max
Maximum number of timesteps allowed per call to solve()
virtual void resize()
Call to set the size of internal data structures after first defining the system or if the problem si...
int m_linearSolves
Linear solves on the current grid.
double timeStep(int nsteps, double dt, span< double > x, span< double > r, int loglevel)
Take time steps using Backward Euler.
virtual void resetBadValues(span< double > x)
Reset values such as negative species concentrations.
vector< double > m_xnew
Work array used to hold the residual or the new solution.
virtual void saveStats()
Commit statistics for the current grid into m_stats and reset the staging counters.
unique_ptr< MultiNewton > m_newt
Newton iterator.
void getState(span< double > x) const
Get the converged steady-state solution after calling solve().
size_t size() const
Total solution vector length;.
void factorizeJacobian()
Update the transient term of the Jacobian (forming and factorizing ) and record the elapsed wall-cloc...
virtual void initTimeInteg(double dt, span< const double > x)
Prepare for time stepping beginning with solution x and timestep dt.
AnyMap m_stats
Committed per-grid statistics.
double tsnorm(span< const double > x, span< double > r)
Transient max norm (infinity norm) of the residual evaluated using solution x and the current timeste...
void evalSSJacobian(span< const double > x)
Evaluate the steady-state Jacobian, accessible via linearSolver()
double m_solveTime
Wall time [s] in linear solves.
size_t bandwidth() const
Jacobian bandwidth.
double m_rdt
Reciprocal of time step.
virtual size_t gridSize() const
Value reported as grid_points in solver statistics.
void recordFactorization(double wallTime)
Record wall-clock time [s] spent factorizing the Jacobian.
shared_ptr< SystemJacobian > m_jac
Jacobian evaluator.
void setInitialGuess(span< const double > x)
Set the initial guess. Should be called before solve().
shared_ptr< vector< double > > m_state
Solution vector.
double m_factorTime
Wall time [s] factorizing the Jacobian.
void setTimeStepGrowthStrategy(const string &strategy)
Set the strategy used to grow the timestep after a successful step that reuses the current Jacobian.
virtual void eval(span< const double > x, span< double > r, double rdt=-1.0, int count=1)=0
Evaluate the residual function.
vector< int > m_mask
Transient mask.
void solve(int loglevel=0)
Solve the steady-state problem, taking internal timesteps as necessary until the Newton solver can co...
double ssnorm(span< const double > x, span< double > r)
Steady-state max norm (infinity norm) of the residual evaluated using solution x.
string timeStepGrowthStrategy() const
Get the configured timestep growth strategy.
void setTimeStep(double stepsize, span< const int > tsteps)
Set the number of time steps to try when the steady Newton solver is unsuccessful.
vector< int > m_steps
Array of number of steps to take after each unsuccessful steady-state solve before re-attempting the ...
double m_evaltime
Wall time [s] in standalone residual evals.
TimeStepGrowthStrategy m_tstep_growth_strategy
Selected strategy for successful-step growth.
double m_tfactor
Factor time step is multiplied by if time stepping fails ( < 1 )
bool m_jac_ok
If true, Jacobian is current.
double m_tstep
Initial timestep.
int m_factorizations
Jacobian factorizations on the current grid.
int m_ts_jac_age
Maximum age of the Jacobian in time-stepping mode.
void setJacAge(int ss_age, int ts_age=-1)
Set the maximum number of steps that can be taken using the same Jacobian before it must be re-evalua...
virtual void writeDebugInfo(const string &header_suffix, const string &message, int loglevel, int attempt_counter)
Write solver debugging based on the specified log level.
double m_tmin
Minimum timestep size.
double m_tmax
Maximum timestep size.
int m_ss_jac_age
Maximum age of the Jacobian in steady-state mode.
double m_gridTime
Total wall time [s] solving on the current grid.
int m_nevals
Standalone residual evaluations (not Jacobian fill)
virtual void evalJacobian(span< const double > x0)=0
Evaluates the Jacobian at x0 using finite differences.
virtual void clearStats()
Clear all saved solver statistics and reset the staging counters.
virtual void setSteadyMode()
Prepare to solve the steady-state problem.
virtual void clearDebugFile()
Delete debug output file that may be created by writeDebugInfo() when solving with high loglevel.
int m_attempt_counter
Counter used to manage the number of states stored in the debug log file generated by writeDebugInfo(...
void setLinearSolver(shared_ptr< SystemJacobian > solver)
Set the linear solver used to hold the Jacobian matrix and solve linear systems as part of each Newto...
int m_max_history
Constant that determines the maximum number of states stored in the debug log file generated by write...
Func1 * m_time_step_callback
User-supplied function called after each successful timestep.
vector< double > m_xlast_ts
State vector after the last successful set of time steps.
vector< double > m_work1
Work arrays used during Jacobian evaluation.
MultiNewton & newton()
Return a reference to the Newton iterator.
double m_tstep_growth
Growth factor for successful steps that reuse the Jacobian.
Error thrown when time stepping cannot proceed and the steady-state solver should be given a chance t...
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
Namespace for the Cantera kernel.
Definition AnyMap.cpp:595
const double Tiny
Small number to compare differences of mole fractions against.
Definition ct_defs.h:176