numerics
Loading...
Searching...
No Matches
ode.cpp
Go to the documentation of this file.
1#include "ode/ode.hpp"
2#include <algorithm>
3#include <cmath>
4
5namespace num {
6
7// ── helpers ───────────────────────────────────────────────────────────────────
8
9static void axpy_vec(real alpha, const Vector& x, Vector& y) {
10 for (idx i = 0; i < x.size(); ++i) y[i] += alpha * x[i];
11}
12
13// ── Euler ─────────────────────────────────────────────────────────────────────
14
17 const idx n = y0.size();
18 Vector dydt(n);
19 idx steps = 0;
20
21 for (real t = t0; t < t1 - 1e-14 * std::abs(t1); t += h) {
22 real dt = std::min(h, t1 - t);
23 f(t, y0, dydt);
24 axpy_vec(dt, dydt, y0);
25 ++steps;
26 if (on_step) on_step(t + dt, y0);
27 }
28 return {std::move(y0), t1, steps, true};
29}
30
31// ── RK4 ───────────────────────────────────────────────────────────────────────
32
35 const idx n = y0.size();
36 Vector k1(n), k2(n), k3(n), k4(n), ytmp(n);
37 idx steps = 0;
38
39 for (real t = t0; t < t1 - 1e-14 * std::abs(t1); t += h) {
40 real dt = std::min(h, t1 - t);
41
42 f(t, y0, k1);
43
44 for (idx i = 0; i < n; ++i) ytmp[i] = y0[i] + 0.5 * dt * k1[i];
45 f(t + 0.5 * dt, ytmp, k2);
46
47 for (idx i = 0; i < n; ++i) ytmp[i] = y0[i] + 0.5 * dt * k2[i];
48 f(t + 0.5 * dt, ytmp, k3);
49
50 for (idx i = 0; i < n; ++i) ytmp[i] = y0[i] + dt * k3[i];
51 f(t + dt, ytmp, k4);
52
53 for (idx i = 0; i < n; ++i)
54 y0[i] += (dt / 6.0) * (k1[i] + 2*k2[i] + 2*k3[i] + k4[i]);
55 ++steps;
56 if (on_step) on_step(t + dt, y0);
57 }
58 return {std::move(y0), t1, steps, true};
59}
60
61// ── RK45 (Dormand-Prince) ─────────────────────────────────────────────────────
62//
63// Butcher tableau from Dormand & Prince, "A family of embedded Runge-Kutta
64// formulae", J. Comput. Appl. Math. 6(1), 19-26 (1980).
65// 5th-order propagation, 4th-order embedded error estimate, FSAL property.
66
70 static constexpr real a21 = 1.0/5.0;
71 static constexpr real a31 = 3.0/40.0, a32 = 9.0/40.0;
72 static constexpr real a41 = 44.0/45.0, a42 = -56.0/15.0, a43 = 32.0/9.0;
73 static constexpr real a51 = 19372.0/6561.0, a52 = -25360.0/2187.0,
74 a53 = 64448.0/6561.0, a54 = -212.0/729.0;
75 static constexpr real a61 = 9017.0/3168.0, a62 = -355.0/33.0,
76 a63 = 46732.0/5247.0, a64 = 49.0/176.0,
77 a65 = -5103.0/18656.0;
78
79 static constexpr real b1 = 35.0/384.0, b3 = 500.0/1113.0,
80 b4 = 125.0/192.0, b5 = -2187.0/6784.0, b6 = 11.0/84.0;
81
82 static constexpr real e1 = 71.0/57600.0, e3 = -71.0/16695.0,
83 e4 = 71.0/1920.0, e5 = -17253.0/339200.0,
84 e6 = 22.0/525.0, e7 = -1.0/40.0;
85
86 const idx n = y0.size();
87 Vector k1(n), k2(n), k3(n), k4(n), k5(n), k6(n), k7(n), ytmp(n), err(n);
88
89 real h = std::min(h0, t1 - t0);
90 real t = t0;
91 idx steps = 0;
92 bool converged = true;
93
94 f(t, y0, k1);
95
96 while (t < t1 - 1e-14 * std::abs(t1)) {
97 if (steps >= max_steps) { converged = false; break; }
98 h = std::min(h, t1 - t);
99
100 for (idx i = 0; i < n; ++i) ytmp[i] = y0[i] + h * a21 * k1[i];
101 f(t + h/5.0, ytmp, k2);
102
103 for (idx i = 0; i < n; ++i) ytmp[i] = y0[i] + h*(a31*k1[i] + a32*k2[i]);
104 f(t + 3*h/10.0, ytmp, k3);
105
106 for (idx i = 0; i < n; ++i) ytmp[i] = y0[i] + h*(a41*k1[i] + a42*k2[i] + a43*k3[i]);
107 f(t + 4*h/5.0, ytmp, k4);
108
109 for (idx i = 0; i < n; ++i)
110 ytmp[i] = y0[i] + h*(a51*k1[i] + a52*k2[i] + a53*k3[i] + a54*k4[i]);
111 f(t + 8*h/9.0, ytmp, k5);
112
113 for (idx i = 0; i < n; ++i)
114 ytmp[i] = y0[i] + h*(a61*k1[i] + a62*k2[i] + a63*k3[i] + a64*k4[i] + a65*k5[i]);
115 f(t + h, ytmp, k6);
116
117 for (idx i = 0; i < n; ++i)
118 ytmp[i] = y0[i] + h*(b1*k1[i] + b3*k3[i] + b4*k4[i] + b5*k5[i] + b6*k6[i]);
119
120 f(t + h, ytmp, k7);
121
122 for (idx i = 0; i < n; ++i)
123 err[i] = h * (e1*k1[i] + e3*k3[i] + e4*k4[i] + e5*k5[i] + e6*k6[i] + e7*k7[i]);
124
125 real err_norm = 0;
126 for (idx i = 0; i < n; ++i) {
127 real sc = atol + rtol * std::max(std::abs(y0[i]), std::abs(ytmp[i]));
128 real ratio = err[i] / sc;
129 err_norm = std::max(err_norm, std::abs(ratio));
130 }
131
132 if (err_norm <= 1.0) {
133 t += h;
134 y0 = ytmp;
135 k1 = k7;
136 ++steps;
137 if (on_step) on_step(t, y0);
138 }
139
140 real factor = 0.9 * std::pow(err_norm + 1e-10, -0.2);
141 factor = std::max(real(0.1), std::min(real(10.0), factor));
142 h *= factor;
143 }
144
145 return {std::move(y0), t, steps, converged};
146}
147
148// ── Velocity Verlet (2nd-order symplectic) ────────────────────────────────────
149//
150// Kick-drift-kick form with FSAL: acceleration is computed once per step
151// (except the first). Exactly preserves a discrete symplectic structure and
152// is time-reversible, giving O(h²) energy oscillation rather than drift.
153
155 real t0, real t1, real h,
157 const idx n = q0.size();
158 Vector a_cur(n), a_next(n);
159 accel(q0, a_cur);
160
161 idx steps = 0;
162 real t = t0;
163
164 while (t < t1 - 1e-14 * std::abs(t1)) {
165 real dt = std::min(h, t1 - t);
166
167 // q_{n+1} = q_n + h*v_n + h²/2 * a_n
168 for (idx i = 0; i < n; ++i)
169 q0[i] += dt * v0[i] + 0.5 * dt * dt * a_cur[i];
170
171 accel(q0, a_next);
172
173 // v_{n+1} = v_n + h/2 * (a_n + a_{n+1})
174 for (idx i = 0; i < n; ++i)
175 v0[i] += 0.5 * dt * (a_cur[i] + a_next[i]);
176
177 std::swap(a_cur, a_next);
178 t += dt;
179 ++steps;
180 if (on_step) on_step(t, q0, v0);
181 }
182
183 return {std::move(q0), std::move(v0), t, steps};
184}
185
186// ── Yoshida 4th-order symplectic ──────────────────────────────────────────────
187//
188// Yoshida, H. "Construction of higher order symplectic integrators."
189// Phys. Lett. A 150, 262-268 (1990).
190//
191// Coefficients for the 4th-order method:
192// w1 = 1 / (2 - cbrt(2)), w0 = 1 - 2*w1
193// c1=c4=w1/2, c2=c3=(w0+w1)/2, d1=d3=w1, d2=w0
194
196 real t0, real t1, real h,
198 static const real w1 = 1.0 / (2.0 - std::cbrt(2.0));
199 static const real w0 = 1.0 - 2.0 * w1;
200 static const real c1 = w1 * 0.5;
201 static const real c2 = (w0 + w1) * 0.5;
202 // c3 == c2, c4 == c1
203 static const real d1 = w1;
204 static const real d2 = w0;
205 // d3 == d1
206
207 const idx n = q0.size();
208 Vector acc(n);
209 idx steps = 0;
210 real t = t0;
211
212 while (t < t1 - 1e-14 * std::abs(t1)) {
213 real dt = std::min(h, t1 - t);
214
215 // drift c1
216 for (idx i = 0; i < n; ++i) q0[i] += c1 * dt * v0[i];
217 // kick d1
218 accel(q0, acc);
219 for (idx i = 0; i < n; ++i) v0[i] += d1 * dt * acc[i];
220 // drift c2
221 for (idx i = 0; i < n; ++i) q0[i] += c2 * dt * v0[i];
222 // kick d2
223 accel(q0, acc);
224 for (idx i = 0; i < n; ++i) v0[i] += d2 * dt * acc[i];
225 // drift c3 (== c2)
226 for (idx i = 0; i < n; ++i) q0[i] += c2 * dt * v0[i];
227 // kick d3 (== d1)
228 accel(q0, acc);
229 for (idx i = 0; i < n; ++i) v0[i] += d1 * dt * acc[i];
230 // drift c4 (== c1)
231 for (idx i = 0; i < n; ++i) q0[i] += c1 * dt * v0[i];
232
233 t += dt;
234 ++steps;
235 if (on_step) on_step(t, q0, v0);
236 }
237
238 return {std::move(q0), std::move(v0), t, steps};
239}
240
241} // namespace num
constexpr idx size() const noexcept
Definition vector.hpp:77
std::function< void(real t, const Vector &q, const Vector &v)> SymplecticCallback
Definition ode.hpp:43
double real
Definition types.hpp:10
std::function< void(real t, const Vector &y, Vector &dydt)> ODERhsFn
Right-hand side callable: fills dydt = f(t, y) in-place.
Definition ode.hpp:28
ODEResult ode_rk45(ODERhsFn f, Vector y0, real t0, real t1, real rtol=1e-6, real atol=1e-9, real h0=1e-3, idx max_steps=1000000, StepCallback on_step=nullptr)
Definition ode.cpp:67
constexpr T ipow(T x) noexcept
Compute x^N at compile time via repeated squaring.
std::size_t idx
Definition types.hpp:11
SymplecticResult ode_yoshida4(AccelFn accel, Vector q0, Vector v0, real t0, real t1, real h, SymplecticCallback on_step=nullptr)
Definition ode.cpp:195
constexpr real e
Definition math.hpp:41
BasicVector< real > Vector
Real-valued dense vector with full backend dispatch (CPU + GPU)
Definition vector.hpp:122
SymplecticResult ode_verlet(AccelFn accel, Vector q0, Vector v0, real t0, real t1, real h, SymplecticCallback on_step=nullptr)
Definition ode.cpp:154
ODEResult ode_rk4(ODERhsFn f, Vector y0, real t0, real t1, real h, StepCallback on_step=nullptr)
Classic 4th-order Runge-Kutta (fixed step).
Definition ode.cpp:33
std::function< void(real t, const Vector &y)> StepCallback
Definition ode.hpp:33
std::function< void(const Vector &q, Vector &acc)> AccelFn
Definition ode.hpp:37
ODEResult ode_euler(ODERhsFn f, Vector y0, real t0, real t1, real h, StepCallback on_step=nullptr)
Definition ode.cpp:15
ODE time integrators: Euler, RK4, adaptive RK45 (Dormand-Prince), and symplectic Velocity Verlet / Yo...
Result returned by general ODE integrators.
Definition ode.hpp:48
Result returned by symplectic integrators.
Definition ode.hpp:56