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 * A note on how tight rtol can usefully be. The step is chosen so that the
109 * ORDER-TWO embedded estimate meets the tolerance, so h scales as rtol^(1/3)
110 * while the order-four solution is far more accurate than asked. Asking for
111 * 1e-8 costs a few hundred steps on the problems in this tree; asking for
112 * 1e-14 costs a few million and will hit max_steps. When the goal is accuracy
113 * rather than error control, pin h_init = h_min = h_max and run a fixed step:
114 * the solution error is O(h^4) and nothing is spent on the estimate.
115 */
116template <class T>
118 T rtol = num_traits<T>::from_double(1e-8); ///< relative tolerance per component
119 T atol = num_traits<T>::from_double(1e-10); ///< absolute tolerance per component
120 T h_init = num_traits<T>::from_int(0); ///< initial step; 0 selects one automatically
121 T h_min = num_traits<T>::from_int(0); ///< smallest admissible step; 0 derives one
122 T h_max = num_traits<T>::from_int(0); ///< largest admissible step; 0 means |t1 - t0|
123 std::size_t max_steps = 100000; ///< abort after this many accepted steps
124 bool store_trajectory = true; ///< keep every accepted point, not just the last
125 /**
126 * A test consulted after every ACCEPTED STEP; true ends the integration
127 * there, holding that state as the solution for the rest of the span.
128 *
129 * WHY IT EXISTS. A drift that has reached a fixed point cannot move again:
130 * f(y*) = 0 means y is y* for every later t, so the remaining span is known
131 * and stepping it is waste -- and worse than waste, because a stiff step
132 * controller handed a state it is already at cannot pick a step and the
133 * integration stops advancing. Empty by default, and the loop then runs
134 * exactly as it always has. See `LsodaOptions::step_stop` for the twin on
135 * the other arm, and `solver_fluid.h` for the caller.
136 */
137 std::function<bool(const T&, const std::vector<T>&)> step_stop;
138};
139
140/** Result of an integration. */
141template <class T>
143 std::vector<T> t; ///< accepted time points, t[0] = t0
144 std::vector<std::vector<T>> y; ///< y[i] is the state at t[i]
145 std::size_t steps = 0; ///< accepted steps
146 std::size_t rejected = 0; ///< rejected steps
147 std::size_t jacobians = 0; ///< Jacobian evaluations
148 std::size_t f_evals = 0; ///< right-hand side evaluations
149
150 const std::vector<T>& final_state() const {
151 if (y.empty()) throw NumericError(ErrorKind{"Degenerate"}, "OdeSolution: no state was recorded");
152 return y.back();
153 }
154 const T& final_time() const {
155 if (t.empty()) throw NumericError(ErrorKind{"Degenerate"}, "OdeSolution: no state was recorded");
156 return t.back();
157 }
158};
159
160namespace ode_detail {
161
162/** sqrt by ADL, so double, cpp_bin_float and mpfr all resolve. */
163template <class T>
164inline T num_sqrt(const T& v) {
165 using std::sqrt;
166 return sqrt(v);
167}
168
169/** Machine epsilon of T as a value of T. */
170template <class T>
171inline T num_eps() {
172 return std::numeric_limits<T>::epsilon();
173}
174
175/**
176 * The coefficient set, parsed once per instantiation from decimal strings so
177 * that a Real<200> instantiation is not silently limited to double precision.
178 *
179 * The strings carry 32 significant digits. The values were obtained by solving
180 * the Taylor-match system in double and then refining it by Gauss-Newton in
181 * 60-digit arithmetic on problems with exactly representable rational data,
182 * until every matched Taylor coefficient and the L-stability condition were
183 * satisfied to better than 1e-50. That refinement is what makes a Real<50>
184 * instantiation worth having: coefficients good only to 1e-16 would cap the
185 * attainable local error at 1e-16 h no matter how much precision the caller
186 * asked for.
187 *
188 * alpha and gamma_i are NOT stored, they are the row sums of a and of gamma,
189 * so the three cannot drift apart.
190 */
191template <class T>
192struct Ros4 {
193 T gamma;
194 T alpha[4]; ///< row sums of a: the stage abscissae
195 T a[4][4]; ///< strictly lower
196 T gam[4][4]; ///< strictly lower
197 T gamma_i[4]; ///< gamma + row sum of gam
198 T b[4]; ///< order-four weights
199 T bhat[4]; ///< embedded order-two weights (first three stages)
200
201 Ros4() {
202 gamma = parse("0.57281606248213485540800138497677");
203 const char* a_s[4][4] = {
204 {"0", "0", "0", "0"},
205 {"0.75000000000001384828217514743208", "0", "0", "0"},
206 {"0.70000000000001188142192436149683", "-0.19284762489916794335116325012491", "0", "0"},
207 {"-0.21564100871060819772037702114782", "0.058458776820062370659853044595891",
208 "0.92804737156696904284870455820564", "0"}};
209 const char* g_s[4][4] = {
210 {"0", "0", "0", "0"},
211 {"-0.84635396502372715969128660212009", "0", "0", "0"},
212 {"0.050072468390209764931220387127165", "-0.74834720180244088425591310078693", "0",
213 "0"},
214 {"-0.42466874748751782239409914842761", "-0.9086245862275405564736317955516",
215 "0.45665088789848053231587535636693", "0"}};
216 const char* b_s[4] = {"0.38309583630415199434118358963363",
217 "0.049177316168702520768096197158938",
218 "0.09403024921808945954457206670416",
219 "0.47369659830905602534614814650327"};
220 const char* bhat_s[4] = {"0.44804121887667558433", "0.34479429610719691812",
221 "0.20716448501612805266", "0"};
222 for (int i = 0; i < 4; ++i) {
223 b[i] = parse(b_s[i]);
224 bhat[i] = parse(bhat_s[i]);
225 for (int j = 0; j < 4; ++j) {
226 a[i][j] = parse(a_s[i][j]);
227 gam[i][j] = parse(g_s[i][j]);
228 }
229 }
230 for (int i = 0; i < 4; ++i) {
231 alpha[i] = num_traits<T>::from_int(0);
232 gamma_i[i] = gamma;
233 for (int j = 0; j < i; ++j) {
234 alpha[i] += a[i][j];
235 gamma_i[i] += gam[i][j];
236 }
237 }
238 }
239
240 static T parse(const char* s) { return T(s); }
241};
242
243template <>
244inline double Ros4<double>::parse(const char* s) {
245 return std::stod(s);
246}
247
248} // namespace ode_detail
249
250/**
251 * Numeric Jacobian by central differences.
252 *
253 * The increment is eps^(1/3) scaled by the magnitude of the component, which
254 * is the standard balance for a central difference: the truncation error is
255 * O(delta^2) and the cancellation error O(eps/delta), and the two meet at
256 * delta ~ eps^(1/3), giving about two thirds of the digits of T. A one-sided
257 * difference would cost one fewer evaluation per column and half the digits;
258 * the extra accuracy matters here because the Jacobian of a stiff problem is
259 * what the whole stability of the step rests on.
260 */
261template <class T, class F>
262Matrix<T> ode_numeric_jacobian(const F& f, const T& t, const std::vector<T>& y,
263 const std::vector<T>& fy) {
264 (void)fy;
265 const std::size_t n = y.size();
266 const T eps = ode_detail::num_eps<T>();
267 using std::pow;
268 const T delta_scale = pow(eps, num_traits<T>::from_rational(1, 3));
270 std::vector<T> yp = y;
271 std::vector<T> ym = y;
272 for (std::size_t j = 0; j < n; ++j) {
273 T mag = num_abs(y[j]);
275 const T d = delta_scale * mag;
276 yp[j] = y[j] + d;
277 ym[j] = y[j] - d;
278 const T den = yp[j] - ym[j]; // the actually representable increment
279 const std::vector<T> fp = f(t, yp);
280 const std::vector<T> fm = f(t, ym);
281 if (fp.size() != n || fm.size() != n)
282 throw InputError("ode_numeric_jacobian: the right-hand side changed dimension");
283 for (std::size_t i = 0; i < n; ++i) J(i, j) = (fp[i] - fm[i]) / den;
284 yp[j] = y[j];
285 ym[j] = y[j];
286 }
287 return J;
288}
289
290/**
291 * Integrate y' = f(t,y) from t0 to t1 with an analytic Jacobian.
292 *
293 * @param f right-hand side, std::vector<T> f(const T& t, const std::vector<T>& y)
294 * @param jac Jacobian, Matrix<T> jac(const T& t, const std::vector<T>& y)
295 * @param t0 initial time
296 * @param t1 final time; t1 > t0 is required (this is an initial value problem
297 * marched forwards, and a backwards request is an input error
298 * rather than a silently reversed integration)
299 * @param y0 initial state
300 * @param opt tolerances and step bounds
301 */
302template <class T, class F, class J>
303OdeSolution<T> ode_rosenbrock4(const F& f, const J& jac, const T& t0, const T& t1,
304 const std::vector<T>& y0, const OdeOptions<T>& opt) {
306 "ode_rosenbrock4 requires transcendental arithmetic: its coefficients are "
307 "irrational and its step size is chosen by a tolerance comparison, so the "
308 "result is an approximation that exact rational arithmetic cannot deliver");
309
310 const std::size_t n = y0.size();
311 if (n == 0) throw InputError("ode_rosenbrock4: empty initial state");
312 if (!(t1 > t0)) throw InputError("ode_rosenbrock4: the final time must exceed the initial time");
313 if (!(opt.rtol > num_traits<T>::from_int(0)) || !(opt.atol > num_traits<T>::from_int(0)))
314 throw InputError("ode_rosenbrock4: rtol and atol must both be positive");
315
316 const ode_detail::Ros4<T> C;
317 const T zero = num_traits<T>::from_int(0);
318 const T one = num_traits<T>::from_int(1);
319 const T span = t1 - t0;
320 const T hmax = opt.h_max > zero ? opt.h_max : span;
321 const T hmin = opt.h_min > zero ? opt.h_min
322 : T(span * ode_detail::num_eps<T>() *
324
325 OdeSolution<T> sol;
326 std::vector<T> y = y0;
327 T t = t0;
328 sol.t.push_back(t);
329 sol.y.push_back(y);
330
331 // initial step heuristic: see _kb/14-cpp-multiprecision.md
332 T h = opt.h_init > zero ? opt.h_init : T(span / num_traits<T>::from_int(1000));
333 if (h > hmax) h = hmax;
334 if (h < hmin) h = hmin;
335
336 const T safety = num_traits<T>::from_rational(9, 10);
337 const T fac_min = num_traits<T>::from_rational(1, 5);
338 const T fac_max = num_traits<T>::from_int(6);
339 const T reject_max = num_traits<T>::from_int(1); // no growth right after a rejection
340
341 std::vector<T> k[4];
342 std::vector<T> ystage(n), rhs(n), ynew(n), acc(n);
343 bool previous_rejected = false;
344
345 while (t < t1) {
346 if (sol.steps >= opt.max_steps)
347 throw NumericError(ErrorKind{"NonConvergence"}, "ode_rosenbrock4: step budget exhausted before reaching t1");
348 // exact landing on t1: see _kb/14-cpp-multiprecision.md
349 bool last_step = false;
350 const T remaining = t1 - t;
351 if (h >= remaining || remaining - h <= num_abs(t1) * ode_detail::num_eps<T>() *
353 h = remaining;
354 last_step = true;
355 }
356 if (h < hmin && !last_step)
357 throw NumericError(ErrorKind{"Singular"}, "ode_rosenbrock4: the step size fell below h_min; the problem is "
358 "either singular or the tolerances are unreachable in this "
359 "arithmetic");
360
361 const std::vector<T> fy = f(t, y);
362 ++sol.f_evals;
363 if (fy.size() != n)
364 throw InputError("ode_rosenbrock4: the right-hand side returned the wrong dimension");
365 const Matrix<T> Jm = jac(t, y);
366 ++sol.jacobians;
367 if (Jm.rows() != n || Jm.cols() != n)
368 throw InputError("ode_rosenbrock4: the Jacobian has the wrong shape");
369
370 // df/dt by a central difference. Autonomous problems return zero here
371 // and the term drops out exactly.
372 std::vector<T> ft(n, zero);
373 {
374 using std::pow;
375 T tmag = num_abs(t);
376 if (tmag < one) tmag = one;
377 const T dt = pow(ode_detail::num_eps<T>(), num_traits<T>::from_rational(1, 3)) * tmag;
378 const std::vector<T> fp = f(T(t + dt), y);
379 const std::vector<T> fm = f(T(t - dt), y);
380 sol.f_evals += 2;
381 const T den = (t + dt) - (t - dt);
382 for (std::size_t i = 0; i < n; ++i) ft[i] = (fp[i] - fm[i]) / den;
383 }
384
385 // One factorization of I - h gamma J serves all four stages.
386 Matrix<T> LHS(n, n, zero);
387 for (std::size_t i = 0; i < n; ++i)
388 for (std::size_t j = 0; j < n; ++j)
389 LHS(i, j) = (i == j ? one : zero) - h * C.gamma * Jm(i, j);
390 std::vector<std::size_t> piv = lu_factor(LHS);
391
392 for (int s = 0; s < 4; ++s) {
393 for (std::size_t i = 0; i < n; ++i) {
394 ystage[i] = y[i];
395 acc[i] = zero;
396 }
397 for (int j = 0; j < s; ++j)
398 for (std::size_t i = 0; i < n; ++i) {
399 ystage[i] += C.a[s][j] * k[j][i];
400 acc[i] += C.gam[s][j] * k[j][i];
401 }
402 const std::vector<T> fs = s == 0 ? fy : f(T(t + C.alpha[s] * h), ystage);
403 if (s != 0) ++sol.f_evals;
404 for (std::size_t i = 0; i < n; ++i) {
405 T Jacc = zero;
406 for (std::size_t j = 0; j < n; ++j) Jacc += Jm(i, j) * acc[j];
407 rhs[i] = h * fs[i] + h * Jacc + h * h * C.gamma_i[s] * ft[i];
408 }
409 k[s] = rhs;
410 lu_solve(LHS, piv, k[s]);
411 }
412
413 for (std::size_t i = 0; i < n; ++i) {
414 ynew[i] = y[i];
415 for (int s = 0; s < 4; ++s) ynew[i] += C.b[s] * k[s][i];
416 }
417
418 // embedded order-3 error estimate: see _kb/14-cpp-multiprecision.md
419 T err_sq = zero;
420 for (std::size_t i = 0; i < n; ++i) {
421 T d = zero;
422 for (int s = 0; s < 4; ++s) d += (C.b[s] - C.bhat[s]) * k[s][i];
423 const T ay = num_abs(y[i]);
424 const T an = num_abs(ynew[i]);
425 const T scale = opt.atol + opt.rtol * (ay > an ? ay : an);
426 const T r = d / scale;
427 err_sq += r * r;
428 }
429 const T err = ode_detail::num_sqrt(T(err_sq / num_traits<T>::from_int(static_cast<long>(n))));
430
431 // Step-size factor safety * err^(-1/(p+1)) with p = 2 the order of the
432 // embedded estimate, so the cube root of the error ratio.
433 T factor;
434 if (err <= zero) {
435 factor = fac_max;
436 } else {
437 using std::pow;
438 const T q = pow(err, num_traits<T>::from_rational(1, 3));
439 factor = safety / q;
440 if (factor < fac_min) factor = fac_min;
441 if (factor > fac_max) factor = fac_max;
442 }
443
444 if (err <= one) {
445 t = last_step ? t1 : T(t + h);
446 y = ynew;
447 ++sol.steps;
448 if (opt.store_trajectory || t >= t1) {
449 sol.t.push_back(t);
450 sol.y.push_back(y);
451 } else {
452 sol.t.back() = t;
453 sol.y.back() = y;
454 }
455 // A settled state ends the span in closed form: the accepted point
456 // is already recorded above, and it is the answer for every later t.
457 if (opt.step_stop && opt.step_stop(t, y)) {
458 if (t < t1) {
459 if (opt.store_trajectory) {
460 sol.t.push_back(t1);
461 sol.y.push_back(y);
462 } else {
463 sol.t.back() = t1;
464 }
465 }
466 break;
467 }
468 if (previous_rejected && factor > reject_max) factor = reject_max;
469 previous_rejected = false;
470 h *= factor;
471 if (h > hmax) h = hmax;
472 } else {
473 ++sol.rejected;
474 previous_rejected = true;
475 h *= factor;
476 }
477 }
478 return sol;
479}
480
481/** Integrate y' = f(t,y) with a numeric Jacobian by central differences. */
482template <class T, class F>
483OdeSolution<T> ode_rosenbrock4(const F& f, const T& t0, const T& t1, const std::vector<T>& y0,
484 const OdeOptions<T>& opt) {
485 return ode_rosenbrock4(
486 f,
487 [&f](const T& t, const std::vector<T>& y) {
488 return ode_numeric_jacobian<T>(f, t, y, std::vector<T>());
489 },
490 t0, t1, y0, opt);
491}
492
493/** Integrate with the default options and return only the state at t1. */
494template <class T, class F>
495std::vector<T> ode_rosenbrock4_endpoint(const F& f, const T& t0, const T& t1,
496 const std::vector<T>& y0) {
498 opt.store_trajectory = false;
499 return ode_rosenbrock4(f, t0, t1, y0, opt).final_state();
500}
501
502} // namespace line
503
504#endif // LINE_UTIL_ODE_H
InputError(const std::string &what)
Definition error.h:105
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:112
The exception types the port throws.
LU factorization with partial pivoting, templated on the number type.
Dense matrix and non-owning view.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
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:184
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:303
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:495
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:262
Number-type abstraction for the templated API port.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
Integration controls.
Definition ode.h:117
bool store_trajectory
keep every accepted point, not just the last
Definition ode.h:124
T atol
absolute tolerance per component
Definition ode.h:119
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:137
std::size_t max_steps
abort after this many accepted steps
Definition ode.h:123
T h_max
largest admissible step; 0 means |t1 - t0|
Definition ode.h:122
T h_init
initial step; 0 selects one automatically
Definition ode.h:120
T h_min
smallest admissible step; 0 derives one
Definition ode.h:121
T rtol
relative tolerance per component
Definition ode.h:118
Result of an integration.
Definition ode.h:142
std::vector< std::vector< T > > y
y[i] is the state at t[i]
Definition ode.h:144
std::vector< T > t
accepted time points, t[0] = t0
Definition ode.h:143
const std::vector< T > & final_state() const
Definition ode.h:150
std::size_t jacobians
Jacobian evaluations.
Definition ode.h:147
const T & final_time() const
Definition ode.h:154
std::size_t steps
accepted steps
Definition ode.h:145
std::size_t rejected
rejected steps
Definition ode.h:146
std::size_t f_evals
right-hand side evaluations
Definition ode.h:148