138 std::function<bool(
const T&,
const std::vector<T>&)>
step_stop;
145 std::vector<std::vector<T>>
y;
152 if (
y.empty())
throw NumericError(
"OdeSolution: no state was recorded");
156 if (
t.empty())
throw NumericError(
"OdeSolution: no state was recorded");
161namespace ode_detail {
165inline T num_sqrt(
const T& v) {
173 return std::numeric_limits<T>::epsilon();
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",
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]);
231 for (
int i = 0; i < 4; ++i) {
232 alpha[i] = num_traits<T>::from_int(0);
234 for (
int j = 0; j < i; ++j) {
236 gamma_i[i] += gam[i][j];
241 static T parse(
const char* s) {
return T(s); }
245inline double Ros4<double>::parse(
const char* s) {
262template <
class T,
class F>
264 const std::vector<T>& fy) {
266 const std::size_t n = y.size();
267 const T eps = ode_detail::num_eps<T>();
271 std::vector<T> yp = y;
272 std::vector<T> ym = y;
273 for (std::size_t j = 0; j < n; ++j) {
276 const T d = delta_scale * mag;
279 const T den = yp[j] - ym[j];
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;
303template <
class T,
class F,
class J>
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");
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");
315 throw InputError(
"ode_rosenbrock4: rtol and atol must both be positive");
317 const ode_detail::Ros4<T> C;
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>() *
327 std::vector<T> y = y0;
334 if (h > hmax) h = hmax;
335 if (h < hmin) h = hmin;
343 std::vector<T> ystage(n), rhs(n), ynew(n), acc(n);
344 bool previous_rejected =
false;
348 throw NumericError(
"ode_rosenbrock4: step budget exhausted before reaching t1");
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>() *
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 "
362 const std::vector<T> fy = f(t, y);
365 throw InputError(
"ode_rosenbrock4: the right-hand side returned the wrong dimension");
368 if (Jm.
rows() != n || Jm.
cols() != n)
369 throw InputError(
"ode_rosenbrock4: the Jacobian has the wrong shape");
373 std::vector<T> ft(n, zero);
377 if (tmag < one) tmag = one;
379 const std::vector<T> fp = f(T(t + dt), y);
380 const std::vector<T> fm = f(T(t - dt), y);
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;
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);
393 for (
int s = 0; s < 4; ++s) {
394 for (std::size_t i = 0; i < n; ++i) {
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];
403 const std::vector<T> fs = s == 0 ? fy : f(T(t + C.alpha[s] * h), ystage);
405 for (std::size_t i = 0; i < n; ++i) {
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];
414 for (std::size_t i = 0; i < n; ++i) {
416 for (
int s = 0; s < 4; ++s) ynew[i] += C.b[s] * k[s][i];
421 for (std::size_t i = 0; i < n; ++i) {
423 for (
int s = 0; s < 4; ++s) d += (C.b[s] - C.bhat[s]) * k[s][i];
426 const T scale =
opt.atol +
opt.rtol * (ay > an ? ay : an);
427 const T r = d / scale;
441 if (factor < fac_min) factor = fac_min;
442 if (factor > fac_max) factor = fac_max;
446 t = last_step ? t1 : T(t + h);
449 if (
opt.store_trajectory || t >= t1) {
458 if (
opt.step_stop &&
opt.step_stop(t, y)) {
460 if (
opt.store_trajectory) {
469 if (previous_rejected && factor > reject_max) factor = reject_max;
470 previous_rejected =
false;
472 if (h > hmax) h = hmax;
475 previous_rejected =
true;
483template <
class T,
class F>
488 [&f](
const T& t,
const std::vector<T>& y) {
495template <
class T,
class F>
497 const std::vector<T>& y0) {
499 opt.store_trajectory =
false;
NumericError(const std::string &what)
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.
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.
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.
std::vector< std::size_t > lu_factor(Matrix< T > &A)
In-place LU of A (n x n).
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.
Number-type abstraction for the templated API port.
bool store_trajectory
keep every accepted point, not just the last
T atol
absolute tolerance per component
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...
std::size_t max_steps
abort after this many accepted steps
T h_max
largest admissible step; 0 means |t1 - t0|
T h_init
initial step; 0 selects one automatically
T h_min
smallest admissible step; 0 derives one
T rtol
relative tolerance per component
Result of an integration.
std::vector< std::vector< T > > y
y[i] is the state at t[i]
std::vector< T > t
accepted time points, t[0] = t0
const std::vector< T > & final_state() const
std::size_t jacobians
Jacobian evaluations.
const T & final_time() const
std::size_t steps
accepted steps
std::size_t rejected
rejected steps
std::size_t f_evals
right-hand side evaluations