LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
ode.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_UTIL_ODE_H
6#define LINE_UTIL_ODE_H
7
8/**
9 * @file
10 * @ingroup line_util
11 * Adaptive stiff ODE integrator: a four-stage Rosenbrock method of order four
12 * with an embedded order-three estimate for step-size control.
13 *
14 * WHY IT IS HERE. Several LINE algorithms are defined by an initial value
15 * problem that MATLAB hands to ode15s or ode23s: the refined mean-field cache
16 * approximation (cache_miss_rmf.m), the RANDOM(m) multi-list mean field
17 * (cache_rrm_meanfield.m), the fluid solvers. Those right-hand sides are stiff
18 * -- the mean-field drift of a cache mixes per-item request rates that differ
19 * by orders of magnitude, and the relaxation to the fixed point is integrated
20 * over a horizon of 1e4 -- so an explicit method is not merely slower, it is
21 * unusable: its step is capped by the fastest time constant for the whole
22 * integration even after every fast mode has died. This header is the port's
23 * own integrator; nothing is taken from an external solver library.
24 *
25 * THE METHOD. A Rosenbrock (linearly implicit Runge-Kutta) method replaces the
26 * nonlinear stage equations of an implicit method by linear ones built on the
27 * Jacobian, so each step costs one Jacobian, one LU factorization and s
28 * back-substitutions and no Newton iteration ever fails to converge. With
29 * J = df/dy and f_t = df/dt evaluated once per step at (t,y), the stages are
30 *
31 * (I - h gamma J) k_i = h f(t + alpha_i h, y + sum_{j<i} a_ij k_j)
32 * + h J sum_{j<i} gamma_ij k_j
33 * + h^2 gamma_i f_t,
34 * y_{n+1} = y_n + sum_i b_i k_i, yhat_{n+1} = y_n + sum_i bhat_i k_i,
35 *
36 * with alpha_i = sum_{j<i} a_ij and gamma_i = gamma + sum_{j<i} gamma_ij. The
37 * f_t term is exactly what the augmented system (y,t)' = (f,1) produces for the
38 * y-block, so the method is invariant under autonomization and the order
39 * conditions are the autonomous ones. The same matrix I - h gamma J serves
40 * every stage, which is the whole point of the constant diagonal gamma.
41 *
42 * THE COEFFICIENTS ARE DERIVED HERE, NOT COPIED. They were obtained by
43 * imposing the order conditions directly rather than by quoting a published
44 * table: for random polynomial vector fields the one-step numerical solution
45 * was expanded as a truncated power series in h and matched, coefficient by
46 * coefficient, against the exact Taylor series of the solution, and the
47 * parameters solved so that the h^1 through h^4 coefficients agree. That is
48 * the definition of order four, with no intermediate rooted-tree bookkeeping
49 * to get wrong. Two further requirements were imposed at the same time:
50 * b^T B^-1 1 = 1 with B = A + Gamma the lower triangular matrix with constant
51 * diagonal gamma, which makes R(-inf) = 0 and the method L-stable -- an
52 * A-stable but not L-stable method leaves the fastest modes ringing at
53 * |R| = 1 instead of damping them, precisely the failure a stiff integrator
54 * exists to avoid -- and gamma pinned to a value for which |R(z)| <= 1 holds
55 * along the whole negative real axis.
56 *
57 * The embedded estimate is SECOND order and uses the first three stages. With
58 * four stages and four weights the order-three conditions have a unique
59 * solution, which is the order-four weight vector itself, so no order-three
60 * estimate with these stages exists; the weights below instead reproduce the
61 * solution through h^2 exactly and use their remaining degree of freedom to
62 * make the h^3 mismatch as small as possible, which makes y - yhat a sharp
63 * estimate of the O(h^3) term. The step controller therefore uses the
64 * exponent 1/(2+1) = 1/3.
65 *
66 * What the test suite checks about the coefficients is what can be checked
67 * exactly: alpha is the row sum of a, gamma_i is gamma plus the row sum of
68 * Gamma, the linear order conditions b^T B^(k-1) 1 = 1/k! for k = 1..4 hold
69 * (on a linear problem the method IS the implicit Runge-Kutta with matrix B,
70 * so these are necessary and sufficient there), the embedded weights are
71 * consistent and differ from b, |R| <= 1 on the negative axis with R(-inf) = 0,
72 * and the observed convergence rate on a nonlinear non-autonomous problem is
73 * four. A mistyped digit fails at least one of those.
74 *
75 * ARITHMETIC. Gated on num_traits<T>::has_transcendental. The coefficients are
76 * irrational, the step size is chosen by a tolerance comparison and the result
77 * is an approximation controlled by rtol/atol no matter how the arithmetic is
78 * carried out, so an exact-rational instantiation would be a fiction. double
79 * and Real<D> both instantiate; at Real<D> the coefficients are parsed from
80 * their decimal strings, so the method keeps its order at any precision, and
81 * the numeric Jacobian's difference increment is scaled by the precision of T.
82 *
83 * DETERMINISM. No global state, no static mutable data, no clock, no random
84 * numbers. The same inputs return the same trajectory bit for bit, including
85 * the sequence of accepted and rejected steps. Every tolerance and bound is
86 * supplied by the caller through OdeOptions.
87 */
88
89#include <cmath>
90#include <cstddef>
91#include <functional>
92#include <limits>
93#include <string>
94#include <vector>
95
96#include "line/num/number.h"
97#include "line/util/error.h"
98#include "line/util/lu.h"
99#include "line/util/matrix.h"
100
101namespace line {
102
103/**
104 * Integration controls. All fields are caller-supplied; the defaults match the
105 * odeset('RelTol',1e-8,'AbsTol',1e-10) that the MATLAB callers of ode15s in
106 * this tree use.
107 */
108/**
109 * A note on how tight rtol can usefully be. The step is chosen so that the
110 * ORDER-TWO embedded estimate meets the tolerance, so h scales as rtol^(1/3)
111 * while the order-four solution is far more accurate than asked. Asking for
112 * 1e-8 costs a few hundred steps on the problems in this tree; asking for
113 * 1e-14 costs a few million and will hit max_steps. When the goal is accuracy
114 * rather than error control, pin h_init = h_min = h_max and run a fixed step:
115 * the solution error is O(h^4) and nothing is spent on the estimate.
116 */
117template <class T>
119 T rtol = num_traits<T>::from_double(1e-8); ///< relative tolerance per component
120 T atol = num_traits<T>::from_double(1e-10); ///< absolute tolerance per component
121 T h_init = num_traits<T>::from_int(0); ///< initial step; 0 selects one automatically
122 T h_min = num_traits<T>::from_int(0); ///< smallest admissible step; 0 derives one
123 T h_max = num_traits<T>::from_int(0); ///< largest admissible step; 0 means |t1 - t0|
124 std::size_t max_steps = 100000; ///< abort after this many accepted steps
125 bool store_trajectory = true; ///< keep every accepted point, not just the last
126 /**
127 * A test consulted after every ACCEPTED STEP; true ends the integration
128 * there, holding that state as the solution for the rest of the span.
129 *
130 * WHY IT EXISTS. A drift that has reached a fixed point cannot move again:
131 * f(y*) = 0 means y is y* for every later t, so the remaining span is known
132 * and stepping it is waste -- and worse than waste, because a stiff step
133 * controller handed a state it is already at cannot pick a step and the
134 * integration stops advancing. Empty by default, and the loop then runs
135 * exactly as it always has. See `LsodaOptions::step_stop` for the twin on
136 * the other arm, and `solver_fluid.h` for the caller.
137 */
138 std::function<bool(const T&, const std::vector<T>&)> step_stop;
139};
140
141/** Result of an integration. */
142template <class T>
144 std::vector<T> t; ///< accepted time points, t[0] = t0
145 std::vector<std::vector<T>> y; ///< y[i] is the state at t[i]
146 std::size_t steps = 0; ///< accepted steps
147 std::size_t rejected = 0; ///< rejected steps
148 std::size_t jacobians = 0; ///< Jacobian evaluations
149 std::size_t f_evals = 0; ///< right-hand side evaluations
150
151 const std::vector<T>& final_state() const {
152 if (y.empty()) throw NumericError("OdeSolution: no state was recorded");
153 return y.back();
154 }
155 const T& final_time() const {
156 if (t.empty()) throw NumericError("OdeSolution: no state was recorded");
157 return t.back();
158 }
159};
160
161namespace ode_detail {
162
163/** sqrt by ADL, so double, cpp_bin_float and mpfr all resolve. */
164template <class T>
165inline T num_sqrt(const T& v) {
166 using std::sqrt;
167 return sqrt(v);
168}
169
170/** Machine epsilon of T as a value of T. */
171template <class T>
172inline T num_eps() {
173 return std::numeric_limits<T>::epsilon();
174}
175
176/**
177 * The coefficient set, parsed once per instantiation from decimal strings so
178 * that a Real<200> instantiation is not silently limited to double precision.
179 *
180 * The strings carry 32 significant digits. The values were obtained by solving
181 * the Taylor-match system in double and then refining it by Gauss-Newton in
182 * 60-digit arithmetic on problems with exactly representable rational data,
183 * until every matched Taylor coefficient and the L-stability condition were
184 * satisfied to better than 1e-50. That refinement is what makes a Real<50>
185 * instantiation worth having: coefficients good only to 1e-16 would cap the
186 * attainable local error at 1e-16 h no matter how much precision the caller
187 * asked for.
188 *
189 * alpha and gamma_i are NOT stored, they are the row sums of a and of gamma,
190 * so the three cannot drift apart.
191 */
192template <class T>
193struct Ros4 {
194 T gamma;
195 T alpha[4]; ///< row sums of a: the stage abscissae
196 T a[4][4]; ///< strictly lower
197 T gam[4][4]; ///< strictly lower
198 T gamma_i[4]; ///< gamma + row sum of gam
199 T b[4]; ///< order-four weights
200 T bhat[4]; ///< embedded order-two weights (first three stages)
201
202 Ros4() {
203 gamma = parse("0.57281606248213485540800138497677");
204 const char* a_s[4][4] = {
205 {"0", "0", "0", "0"},
206 {"0.75000000000001384828217514743208", "0", "0", "0"},
207 {"0.70000000000001188142192436149683", "-0.19284762489916794335116325012491", "0", "0"},
208 {"-0.21564100871060819772037702114782", "0.058458776820062370659853044595891",
209 "0.92804737156696904284870455820564", "0"}};
210 const char* g_s[4][4] = {
211 {"0", "0", "0", "0"},
212 {"-0.84635396502372715969128660212009", "0", "0", "0"},
213 {"0.050072468390209764931220387127165", "-0.74834720180244088425591310078693", "0",
214 "0"},
215 {"-0.42466874748751782239409914842761", "-0.9086245862275405564736317955516",
216 "0.45665088789848053231587535636693", "0"}};
217 const char* b_s[4] = {"0.38309583630415199434118358963363",
218 "0.049177316168702520768096197158938",
219 "0.09403024921808945954457206670416",
220 "0.47369659830905602534614814650327"};
221 const char* bhat_s[4] = {"0.44804121887667558433", "0.34479429610719691812",
222 "0.20716448501612805266", "0"};
223 for (int i = 0; i < 4; ++i) {
224 b[i] = parse(b_s[i]);
225 bhat[i] = parse(bhat_s[i]);
226 for (int j = 0; j < 4; ++j) {
227 a[i][j] = parse(a_s[i][j]);
228 gam[i][j] = parse(g_s[i][j]);
229 }
230 }
231 for (int i = 0; i < 4; ++i) {
232 alpha[i] = num_traits<T>::from_int(0);
233 gamma_i[i] = gamma;
234 for (int j = 0; j < i; ++j) {
235 alpha[i] += a[i][j];
236 gamma_i[i] += gam[i][j];
237 }
238 }
239 }
240
241 static T parse(const char* s) { return T(s); }
242};
243
244template <>
245inline double Ros4<double>::parse(const char* s) {
246 return std::stod(s);
247}
248
249} // namespace ode_detail
250
251/**
252 * Numeric Jacobian by central differences.
253 *
254 * The increment is eps^(1/3) scaled by the magnitude of the component, which
255 * is the standard balance for a central difference: the truncation error is
256 * O(delta^2) and the cancellation error O(eps/delta), and the two meet at
257 * delta ~ eps^(1/3), giving about two thirds of the digits of T. A one-sided
258 * difference would cost one fewer evaluation per column and half the digits;
259 * the extra accuracy matters here because the Jacobian of a stiff problem is
260 * what the whole stability of the step rests on.
261 */
262template <class T, class F>
263Matrix<T> ode_numeric_jacobian(const F& f, const T& t, const std::vector<T>& y,
264 const std::vector<T>& fy) {
265 (void)fy;
266 const std::size_t n = y.size();
267 const T eps = ode_detail::num_eps<T>();
268 using std::pow;
269 const T delta_scale = pow(eps, num_traits<T>::from_rational(1, 3));
271 std::vector<T> yp = y;
272 std::vector<T> ym = y;
273 for (std::size_t j = 0; j < n; ++j) {
274 T mag = num_abs(y[j]);
276 const T d = delta_scale * mag;
277 yp[j] = y[j] + d;
278 ym[j] = y[j] - d;
279 const T den = yp[j] - ym[j]; // the actually representable increment
280 const std::vector<T> fp = f(t, yp);
281 const std::vector<T> fm = f(t, ym);
282 if (fp.size() != n || fm.size() != n)
283 throw InputError("ode_numeric_jacobian: the right-hand side changed dimension");
284 for (std::size_t i = 0; i < n; ++i) J(i, j) = (fp[i] - fm[i]) / den;
285 yp[j] = y[j];
286 ym[j] = y[j];
287 }
288 return J;
289}
290
291/**
292 * Integrate y' = f(t,y) from t0 to t1 with an analytic Jacobian.
293 *
294 * @param f right-hand side, std::vector<T> f(const T& t, const std::vector<T>& y)
295 * @param jac Jacobian, Matrix<T> jac(const T& t, const std::vector<T>& y)
296 * @param t0 initial time
297 * @param t1 final time; t1 > t0 is required (this is an initial value problem
298 * marched forwards, and a backwards request is an input error
299 * rather than a silently reversed integration)
300 * @param y0 initial state
301 * @param opt tolerances and step bounds
302 */
303template <class T, class F, class J>
304OdeSolution<T> ode_rosenbrock4(const F& f, const J& jac, const T& t0, const T& t1,
305 const std::vector<T>& y0, const OdeOptions<T>& opt) {
307 "ode_rosenbrock4 requires transcendental arithmetic: its coefficients are "
308 "irrational and its step size is chosen by a tolerance comparison, so the "
309 "result is an approximation that exact rational arithmetic cannot deliver");
310
311 const std::size_t n = y0.size();
312 if (n == 0) throw InputError("ode_rosenbrock4: empty initial state");
313 if (!(t1 > t0)) throw InputError("ode_rosenbrock4: the final time must exceed the initial time");
314 if (!(opt.rtol > num_traits<T>::from_int(0)) || !(opt.atol > num_traits<T>::from_int(0)))
315 throw InputError("ode_rosenbrock4: rtol and atol must both be positive");
316
317 const ode_detail::Ros4<T> C;
318 const T zero = num_traits<T>::from_int(0);
319 const T one = num_traits<T>::from_int(1);
320 const T span = t1 - t0;
321 const T hmax = opt.h_max > zero ? opt.h_max : span;
322 const T hmin = opt.h_min > zero ? opt.h_min
323 : T(span * ode_detail::num_eps<T>() *
325
326 OdeSolution<T> sol;
327 std::vector<T> y = y0;
328 T t = t0;
329 sol.t.push_back(t);
330 sol.y.push_back(y);
331
332 // initial step heuristic: see _kb/14-cpp-multiprecision.md
333 T h = opt.h_init > zero ? opt.h_init : T(span / num_traits<T>::from_int(1000));
334 if (h > hmax) h = hmax;
335 if (h < hmin) h = hmin;
336
337 const T safety = num_traits<T>::from_rational(9, 10);
338 const T fac_min = num_traits<T>::from_rational(1, 5);
339 const T fac_max = num_traits<T>::from_int(6);
340 const T reject_max = num_traits<T>::from_int(1); // no growth right after a rejection
341
342 std::vector<T> k[4];
343 std::vector<T> ystage(n), rhs(n), ynew(n), acc(n);
344 bool previous_rejected = false;
345
346 while (t < t1) {
347 if (sol.steps >= opt.max_steps)
348 throw NumericError("ode_rosenbrock4: step budget exhausted before reaching t1");
349 // exact landing on t1: see _kb/14-cpp-multiprecision.md
350 bool last_step = false;
351 const T remaining = t1 - t;
352 if (h >= remaining || remaining - h <= num_abs(t1) * ode_detail::num_eps<T>() *
354 h = remaining;
355 last_step = true;
356 }
357 if (h < hmin && !last_step)
358 throw NumericError("ode_rosenbrock4: the step size fell below h_min; the problem is "
359 "either singular or the tolerances are unreachable in this "
360 "arithmetic");
361
362 const std::vector<T> fy = f(t, y);
363 ++sol.f_evals;
364 if (fy.size() != n)
365 throw InputError("ode_rosenbrock4: the right-hand side returned the wrong dimension");
366 const Matrix<T> Jm = jac(t, y);
367 ++sol.jacobians;
368 if (Jm.rows() != n || Jm.cols() != n)
369 throw InputError("ode_rosenbrock4: the Jacobian has the wrong shape");
370
371 // df/dt by a central difference. Autonomous problems return zero here
372 // and the term drops out exactly.
373 std::vector<T> ft(n, zero);
374 {
375 using std::pow;
376 T tmag = num_abs(t);
377 if (tmag < one) tmag = one;
378 const T dt = pow(ode_detail::num_eps<T>(), num_traits<T>::from_rational(1, 3)) * tmag;
379 const std::vector<T> fp = f(T(t + dt), y);
380 const std::vector<T> fm = f(T(t - dt), y);
381 sol.f_evals += 2;
382 const T den = (t + dt) - (t - dt);
383 for (std::size_t i = 0; i < n; ++i) ft[i] = (fp[i] - fm[i]) / den;
384 }
385
386 // One factorization of I - h gamma J serves all four stages.
387 Matrix<T> LHS(n, n, zero);
388 for (std::size_t i = 0; i < n; ++i)
389 for (std::size_t j = 0; j < n; ++j)
390 LHS(i, j) = (i == j ? one : zero) - h * C.gamma * Jm(i, j);
391 std::vector<std::size_t> piv = lu_factor(LHS);
392
393 for (int s = 0; s < 4; ++s) {
394 for (std::size_t i = 0; i < n; ++i) {
395 ystage[i] = y[i];
396 acc[i] = zero;
397 }
398 for (int j = 0; j < s; ++j)
399 for (std::size_t i = 0; i < n; ++i) {
400 ystage[i] += C.a[s][j] * k[j][i];
401 acc[i] += C.gam[s][j] * k[j][i];
402 }
403 const std::vector<T> fs = s == 0 ? fy : f(T(t + C.alpha[s] * h), ystage);
404 if (s != 0) ++sol.f_evals;
405 for (std::size_t i = 0; i < n; ++i) {
406 T Jacc = zero;
407 for (std::size_t j = 0; j < n; ++j) Jacc += Jm(i, j) * acc[j];
408 rhs[i] = h * fs[i] + h * Jacc + h * h * C.gamma_i[s] * ft[i];
409 }
410 k[s] = rhs;
411 lu_solve(LHS, piv, k[s]);
412 }
413
414 for (std::size_t i = 0; i < n; ++i) {
415 ynew[i] = y[i];
416 for (int s = 0; s < 4; ++s) ynew[i] += C.b[s] * k[s][i];
417 }
418
419 // embedded order-3 error estimate: see _kb/14-cpp-multiprecision.md
420 T err_sq = zero;
421 for (std::size_t i = 0; i < n; ++i) {
422 T d = zero;
423 for (int s = 0; s < 4; ++s) d += (C.b[s] - C.bhat[s]) * k[s][i];
424 const T ay = num_abs(y[i]);
425 const T an = num_abs(ynew[i]);
426 const T scale = opt.atol + opt.rtol * (ay > an ? ay : an);
427 const T r = d / scale;
428 err_sq += r * r;
429 }
430 const T err = ode_detail::num_sqrt(T(err_sq / num_traits<T>::from_int(static_cast<long>(n))));
431
432 // Step-size factor safety * err^(-1/(p+1)) with p = 2 the order of the
433 // embedded estimate, so the cube root of the error ratio.
434 T factor;
435 if (err <= zero) {
436 factor = fac_max;
437 } else {
438 using std::pow;
439 const T q = pow(err, num_traits<T>::from_rational(1, 3));
440 factor = safety / q;
441 if (factor < fac_min) factor = fac_min;
442 if (factor > fac_max) factor = fac_max;
443 }
444
445 if (err <= one) {
446 t = last_step ? t1 : T(t + h);
447 y = ynew;
448 ++sol.steps;
449 if (opt.store_trajectory || t >= t1) {
450 sol.t.push_back(t);
451 sol.y.push_back(y);
452 } else {
453 sol.t.back() = t;
454 sol.y.back() = y;
455 }
456 // A settled state ends the span in closed form: the accepted point
457 // is already recorded above, and it is the answer for every later t.
458 if (opt.step_stop && opt.step_stop(t, y)) {
459 if (t < t1) {
460 if (opt.store_trajectory) {
461 sol.t.push_back(t1);
462 sol.y.push_back(y);
463 } else {
464 sol.t.back() = t1;
465 }
466 }
467 break;
468 }
469 if (previous_rejected && factor > reject_max) factor = reject_max;
470 previous_rejected = false;
471 h *= factor;
472 if (h > hmax) h = hmax;
473 } else {
474 ++sol.rejected;
475 previous_rejected = true;
476 h *= factor;
477 }
478 }
479 return sol;
480}
481
482/** Integrate y' = f(t,y) with a numeric Jacobian by central differences. */
483template <class T, class F>
484OdeSolution<T> ode_rosenbrock4(const F& f, const T& t0, const T& t1, const std::vector<T>& y0,
485 const OdeOptions<T>& opt) {
486 return ode_rosenbrock4(
487 f,
488 [&f](const T& t, const std::vector<T>& y) {
489 return ode_numeric_jacobian<T>(f, t, y, std::vector<T>());
490 },
491 t0, t1, y0, opt);
492}
493
494/** Integrate with the default options and return only the state at t1. */
495template <class T, class F>
496std::vector<T> ode_rosenbrock4_endpoint(const F& f, const T& t0, const T& t1,
497 const std::vector<T>& y0) {
499 opt.store_trajectory = false;
500 return ode_rosenbrock4(f, t0, t1, y0, opt).final_state();
501}
502
503} // namespace line
504
505#endif // LINE_UTIL_ODE_H
InputError(const std::string &what)
Definition error.h:39
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
NumericError(const std::string &what)
Definition error.h:45
The exception types the port throws.
LU factorization with partial pivoting, templated on the number type.
Dense matrix and non-owning view.
void lu_solve(const Matrix< T > &LU, const std::vector< std::size_t > &piv, std::vector< T > &b)
Solve LUx = Pb in place on b, using the factors from lu_factor.
Definition lu.h:94
T num_abs(const T &v)
Definition number.h:172
OdeSolution< T > ode_rosenbrock4(const F &f, const J &jac, const T &t0, const T &t1, const std::vector< T > &y0, const OdeOptions< T > &opt)
Integrate y' = f(t,y) from t0 to t1 with an analytic Jacobian.
Definition ode.h:304
std::vector< T > ode_rosenbrock4_endpoint(const F &f, const T &t0, const T &t1, const std::vector< T > &y0)
Integrate with the default options and return only the state at t1.
Definition ode.h:496
std::vector< std::size_t > lu_factor(Matrix< T > &A)
In-place LU of A (n x n).
Definition lu.h:48
Matrix< T > ode_numeric_jacobian(const F &f, const T &t, const std::vector< T > &y, const std::vector< T > &fy)
Numeric Jacobian by central differences.
Definition ode.h:263
Number-type abstraction for the templated API port.
Integration controls.
Definition ode.h:118
bool store_trajectory
keep every accepted point, not just the last
Definition ode.h:125
T atol
absolute tolerance per component
Definition ode.h:120
std::function< bool(const T &, const std::vector< T > &)> step_stop
A test consulted after every ACCEPTED STEP; true ends the integration there, holding that state as th...
Definition ode.h:138
std::size_t max_steps
abort after this many accepted steps
Definition ode.h:124
T h_max
largest admissible step; 0 means |t1 - t0|
Definition ode.h:123
T h_init
initial step; 0 selects one automatically
Definition ode.h:121
T h_min
smallest admissible step; 0 derives one
Definition ode.h:122
T rtol
relative tolerance per component
Definition ode.h:119
Result of an integration.
Definition ode.h:143
std::vector< std::vector< T > > y
y[i] is the state at t[i]
Definition ode.h:145
std::vector< T > t
accepted time points, t[0] = t0
Definition ode.h:144
const std::vector< T > & final_state() const
Definition ode.h:151
std::size_t jacobians
Jacobian evaluations.
Definition ode.h:148
const T & final_time() const
Definition ode.h:155
std::size_t steps
accepted steps
Definition ode.h:146
std::size_t rejected
rejected steps
Definition ode.h:147
std::size_t f_evals
right-hand side evaluations
Definition ode.h:149