numerics 0.1.0
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 {
6using std::max;
7
8static void axpy_vec(real alpha, const Vector &x, Vector &y) {
9 for (idx i = 0; i < x.size(); ++i)
10 y[i] += alpha * x[i];
11}
12
13static real eps_guard(real t1) { return 1e-14 * std::abs(t1); }
14
16 : f_(std::move(f)), y_(std::move(y0)), dydt_(y_.size()), t_(p.t0),
17 t1_(p.tf), h_(p.h) {}
18
19void EulerSteps::advance() {
20 if (t_ >= t1_ - eps_guard(t1_)) {
21 done_ = true;
22 return;
23 }
24 real dt = std::min(h_, t1_ - t_);
25 f_(t_, y_, dydt_);
26 axpy_vec(dt, dydt_, y_);
27 t_ += dt;
28 ++steps_;
29}
30
32 while (!done_)
33 advance();
34 return {std::move(y_), t1_, steps_, true};
35}
36
38 : f_(std::move(f)), y_(std::move(y0)), k1_(y_.size()), k2_(y_.size()),
39 k3_(y_.size()), k4_(y_.size()), ytmp_(y_.size()), t_(p.t0), t1_(p.tf),
40 h_(p.h) {}
41
42void RK4Steps::advance() {
43 if (t_ >= t1_ - eps_guard(t1_)) {
44 done_ = true;
45 return;
46 }
47 const idx n = y_.size();
48 const real dt = std::min(h_, t1_ - t_);
49
50 f_(t_, y_, k1_);
51 for (idx i = 0; i < n; ++i)
52 ytmp_[i] = y_[i] + 0.5 * dt * k1_[i];
53 f_(t_ + 0.5 * dt, ytmp_, k2_);
54 for (idx i = 0; i < n; ++i)
55 ytmp_[i] = y_[i] + 0.5 * dt * k2_[i];
56 f_(t_ + 0.5 * dt, ytmp_, k3_);
57 for (idx i = 0; i < n; ++i)
58 ytmp_[i] = y_[i] + dt * k3_[i];
59 f_(t_ + dt, ytmp_, k4_);
60 for (idx i = 0; i < n; ++i)
61 y_[i] += (dt / 6.0) * (k1_[i] + 2 * k2_[i] + 2 * k3_[i] + k4_[i]);
62 t_ += dt;
63 ++steps_;
64}
65
67 while (!done_)
68 advance();
69 return {std::move(y_), t1_, steps_, true};
70}
71
72//
73// Butcher tableau from Dormand & Prince, "A family of embedded Runge-Kutta
74// formulae", J. Comput. Appl. Math. 6(1), 19-26 (1980).
75// 5th-order propagation, 4th-order embedded error estimate, FSAL property.
76
77static constexpr real rk45_a21 = 1.0 / 5.0;
78static constexpr real rk45_a31 = 3.0 / 40.0, rk45_a32 = 9.0 / 40.0;
79static constexpr real rk45_a41 = 44.0 / 45.0, rk45_a42 = -56.0 / 15.0,
80 rk45_a43 = 32.0 / 9.0;
81static constexpr real rk45_a51 = 19372.0 / 6561.0, rk45_a52 = -25360.0 / 2187.0,
82 rk45_a53 = 64448.0 / 6561.0, rk45_a54 = -212.0 / 729.0;
83static constexpr real rk45_a61 = 9017.0 / 3168.0, rk45_a62 = -355.0 / 33.0,
84 rk45_a63 = 46732.0 / 5247.0, rk45_a64 = 49.0 / 176.0,
85 rk45_a65 = -5103.0 / 18656.0;
86
87static constexpr real rk45_b1 = 35.0 / 384.0, rk45_b3 = 500.0 / 1113.0,
88 rk45_b4 = 125.0 / 192.0, rk45_b5 = -2187.0 / 6784.0,
89 rk45_b6 = 11.0 / 84.0;
90
91static constexpr real rk45_e1 = 71.0 / 57600.0, rk45_e3 = -71.0 / 16695.0,
92 rk45_e4 = 71.0 / 1920.0, rk45_e5 = -17253.0 / 339200.0,
93 rk45_e6 = 22.0 / 525.0, rk45_e7 = -1.0 / 40.0;
94
96 : f_(std::move(f)), y_(std::move(y0)), k1_(y_.size()), k2_(y_.size()),
97 k3_(y_.size()), k4_(y_.size()), k5_(y_.size()), k6_(y_.size()),
98 k7_(y_.size()), ytmp_(y_.size()), err_(y_.size()), t_(p.t0), t1_(p.tf),
99 h_(std::min(p.h, p.tf - p.t0)), rtol_(p.rtol), atol_(p.atol),
100 max_steps_(p.max_steps) {
101 f_(t_, y_, k1_); // prime k1 for FSAL
102}
103
104void RK45Steps::advance() {
105 if (t_ >= t1_ - eps_guard(t1_)) {
106 done_ = true;
107 return;
108 }
109 if (steps_ >= max_steps_) {
110 done_ = true;
111 converged_ = false;
112 return;
113 }
114
115 const idx n = y_.size();
116
117 for (;;) {
118 h_ = std::min(h_, t1_ - t_);
119
120 for (idx i = 0; i < n; ++i) {
121 ytmp_[i] = y_[i] + (h_ * rk45_a21 * k1_[i]);
122 }
123 f_(t_ + (h_ / 5.0), ytmp_, k2_);
124
125 for (idx i = 0; i < n; ++i) {
126 ytmp_[i] = y_[i] + (h_ * ((rk45_a31 * k1_[i]) + (rk45_a32 * k2_[i])));
127 }
128 f_(t_ + (3 * h_ / 10.0), ytmp_, k3_);
129
130 for (idx i = 0; i < n; ++i) {
131 ytmp_[i] = y_[i] + (h_ * ((rk45_a41 * k1_[i]) + (rk45_a42 * k2_[i]) +
132 (rk45_a43 * k3_[i])));
133 }
134 f_(t_ + (4 * h_ / 5.0), ytmp_, k4_);
135
136 for (idx i = 0; i < n; ++i) {
137 ytmp_[i] = y_[i] + (h_ * ((rk45_a51 * k1_[i]) + (rk45_a52 * k2_[i]) +
138 (rk45_a53 * k3_[i]) + (rk45_a54 * k4_[i])));
139 }
140 f_(t_ + (8 * h_ / 9.0), ytmp_, k5_);
141
142 for (idx i = 0; i < n; ++i) {
143 ytmp_[i] = y_[i] + (h_ * ((rk45_a61 * k1_[i]) + (rk45_a62 * k2_[i]) +
144 rk45_a63 * k3_[i] + rk45_a64 * k4_[i] +
145 rk45_a65 * k5_[i]));
146 }
147 f_(t_ + h_, ytmp_, k6_);
148
149 for (idx i = 0; i < n; ++i) {
150 ytmp_[i] = y_[i] + (h_ * ((rk45_b1 * k1_[i]) + rk45_b3 * k3_[i] +
151 rk45_b4 * k4_[i] + (rk45_b5 * k5_[i]) +
152 (rk45_b6 * k6_[i])));
153 }
154 f_(t_ + h_, ytmp_, k7_);
155
156 for (idx i = 0; i < n; ++i) {
157 err_[i] =
158 h_ * ((k1_[i] * rk45_e1) + (rk45_e3 * k3_[i]) + (rk45_e4 * k4_[i]) +
159 (k5_[i] * rk45_e5) + (rk45_e6 * k6_[i]) + (rk45_e7 * k7_[i]));
160 }
161
162 real err_norm = 0;
163 for (idx i = 0; i < n; ++i) {
164 real sc = (rtol_ * max(std::abs(y_[i]), std::abs(ytmp_[i]))) + atol_;
165 err_norm = std::max(err_norm, std::abs(err_[i] / sc));
166 }
167
168 real factor = 0.9 * std::pow(err_norm + 1e-10, -0.2);
169 factor = std::max(real(0.1), std::min(real(10.0), factor));
170
171 if (err_norm <= 1.0) {
172 t_ += h_;
173 y_ = ytmp_;
174 k1_ = k7_; // FSAL
175 ++steps_;
176 h_ *= factor;
177 return;
178 }
179 h_ *= factor;
180 }
181}
182
184 while (!done_)
185 advance();
186 return {std::move(y_), t_, steps_, converged_};
187}
188
190 : accel_(std::move(accel)), q_(std::move(q0)), v_(std::move(v0)),
191 a_cur_(q_.size()), a_next_(q_.size()), t_(p.t0), t1_(p.tf), h_(p.h) {
192 accel_(q_, a_cur_); // prime initial acceleration
193}
194
195void VerletSteps::advance() {
196 if (t_ >= t1_ - eps_guard(t1_)) {
197 done_ = true;
198 return;
199 }
200 const idx n = q_.size();
201 const real dt = std::min(h_, t1_ - t_);
202
203 for (idx i = 0; i < n; ++i)
204 q_[i] += dt * v_[i] + 0.5 * dt * dt * a_cur_[i];
205
206 accel_(q_, a_next_);
207
208 for (idx i = 0; i < n; ++i)
209 v_[i] += 0.5 * dt * (a_cur_[i] + a_next_[i]);
210
211 std::swap(a_cur_, a_next_);
212 t_ += dt;
213 ++steps_;
214}
215
217 while (!done_)
218 advance();
219 return {std::move(q_), std::move(v_), t_, steps_};
220}
221
223 : accel_(std::move(accel)), q_(std::move(q0)), v_(std::move(v0)),
224 acc_(q_.size()), t_(p.t0), t1_(p.tf), h_(p.h) {}
225
226void Yoshida4Steps::advance() {
227 if (t_ >= t1_ - eps_guard(t1_)) {
228 done_ = true;
229 return;
230 }
231 static const real w1 = 1.0 / (2.0 - std::cbrt(2.0));
232 static const real w0 = 1.0 - 2.0 * w1;
233 static const real c1 = w1 * 0.5;
234 static const real c2 = (w0 + w1) * 0.5;
235 static const real d1 = w1;
236 static const real d2 = w0;
237
238 const idx n = q_.size();
239 const real dt = std::min(h_, t1_ - t_);
240
241 for (idx i = 0; i < n; ++i)
242 q_[i] += c1 * dt * v_[i];
243 accel_(q_, acc_);
244 for (idx i = 0; i < n; ++i)
245 v_[i] += d1 * dt * acc_[i];
246 for (idx i = 0; i < n; ++i)
247 q_[i] += c2 * dt * v_[i];
248 accel_(q_, acc_);
249 for (idx i = 0; i < n; ++i)
250 v_[i] += d2 * dt * acc_[i];
251 for (idx i = 0; i < n; ++i)
252 q_[i] += c2 * dt * v_[i];
253 accel_(q_, acc_);
254 for (idx i = 0; i < n; ++i)
255 v_[i] += d1 * dt * acc_[i];
256 for (idx i = 0; i < n; ++i)
257 q_[i] += c1 * dt * v_[i];
258
259 t_ += dt;
260 ++steps_;
261}
262
264 while (!done_)
265 advance();
266 return {std::move(q_), std::move(v_), t_, steps_};
267}
268
270 : accel_(std::move(accel)), q_(std::move(q0)), v_(std::move(v0)),
271 a1_(q_.size()), a2_(q_.size()), a3_(q_.size()), a4_(q_.size()),
272 qtmp_(q_.size()), t_(p.t0), t1_(p.tf), h_(p.h) {}
273
274void RK4_2ndSteps::advance() {
275 if (t_ >= t1_ - eps_guard(t1_)) {
276 done_ = true;
277 return;
278 }
279 const idx n = q_.size();
280 const real dt = std::min(h_, t1_ - t_);
281
282 accel_(q_, a1_);
283 for (idx i = 0; i < n; ++i)
284 qtmp_[i] = q_[i] + 0.5 * dt * v_[i];
285 accel_(qtmp_, a2_);
286 for (idx i = 0; i < n; ++i)
287 qtmp_[i] = q_[i] + 0.5 * dt * (v_[i] + 0.5 * dt * a1_[i]);
288 accel_(qtmp_, a3_);
289 for (idx i = 0; i < n; ++i)
290 qtmp_[i] = q_[i] + dt * (v_[i] + 0.5 * dt * a2_[i]);
291 accel_(qtmp_, a4_);
292
293 for (idx i = 0; i < n; ++i) {
294 q_[i] += dt * v_[i] + (dt * dt / 6.0) * (a1_[i] + a2_[i] + a3_[i]);
295 v_[i] += (dt / 6.0) * (a1_[i] + 2.0 * a2_[i] + 2.0 * a3_[i] + a4_[i]);
296 }
297 t_ += dt;
298 ++steps_;
299}
300
302 while (!done_)
303 advance();
304 return {std::move(q_), std::move(v_), t_, steps_};
305}
306
307// Factory functions
308
310 return EulerSteps(std::move(f), std::move(y0), p);
311}
313 return RK4Steps(std::move(f), std::move(y0), p);
314}
316 return RK45Steps(std::move(f), std::move(y0), p);
317}
318
320 return VerletSteps(std::move(a), std::move(q0), std::move(v0), p);
321}
323 return Yoshida4Steps(std::move(a), std::move(q0), std::move(v0), p);
324}
326 return RK4_2ndSteps(std::move(a), std::move(q0), std::move(v0), p);
327}
328
329// High-level wrappers — run to completion, call obs at each accepted step if
330// provided.
331
333 auto s = euler(std::move(f), std::move(y0), p);
334 if (obs)
335 for (auto step : s)
336 obs(step.t, step.y);
337 return s.run();
338}
340 auto s = rk4(std::move(f), std::move(y0), p);
341 if (obs)
342 for (auto step : s)
343 obs(step.t, step.y);
344 return s.run();
345}
347 auto s = rk45(std::move(f), std::move(y0), p);
348 if (obs)
349 for (auto step : s)
350 obs(step.t, step.y);
351 return s.run();
352}
354 SympObserverFn obs) {
355 auto s = verlet(std::move(a), std::move(q0), std::move(v0), p);
356 if (obs)
357 for (auto step : s)
358 obs(step.t, step.q, step.v);
359 return s.run();
360}
362 SympObserverFn obs) {
363 auto s = yoshida4(std::move(a), std::move(q0), std::move(v0), p);
364 if (obs)
365 for (auto step : s)
366 obs(step.t, step.q, step.v);
367 return s.run();
368}
370 SympObserverFn obs) {
371 auto s = rk4_2nd(std::move(a), std::move(q0), std::move(v0), p);
372 if (obs)
373 for (auto step : s)
374 obs(step.t, step.q, step.v);
375 return s.run();
376}
377
378} // namespace num
constexpr idx size() const noexcept
Definition vector.hpp:80
EulerSteps(ODERhsFn f, Vector y0, ODEParams p)
Definition ode.cpp:15
ODEResult run()
Definition ode.cpp:31
ODEResult run()
Definition ode.cpp:183
RK45Steps(ODERhsFn f, Vector y0, ODEParams p)
Definition ode.cpp:95
RK4Steps(ODERhsFn f, Vector y0, ODEParams p)
Definition ode.cpp:37
ODEResult run()
Definition ode.cpp:66
SymplecticResult run()
Definition ode.cpp:301
RK4_2ndSteps(AccelFn accel, Vector q0, Vector v0, ODEParams p)
Definition ode.cpp:269
VerletSteps(AccelFn accel, Vector q0, Vector v0, ODEParams p)
Definition ode.cpp:189
SymplecticResult run()
Definition ode.cpp:216
Yoshida4Steps(AccelFn accel, Vector q0, Vector v0, ODEParams p)
Definition ode.cpp:222
SymplecticResult run()
Definition ode.cpp:263
EulerSteps euler(ODERhsFn f, Vector y0, ODEParams p={})
Definition ode.cpp:309
ODEResult ode_rk4(ODERhsFn f, Vector y0, ODEParams p={}, ObserverFn obs=nullptr)
Classic 4th-order Runge-Kutta, fixed step.
Definition ode.cpp:339
double real
Definition types.hpp:10
Yoshida4Steps yoshida4(AccelFn accel, Vector q0, Vector v0, ODEParams p={})
Definition ode.cpp:322
std::function< void(real t, const Vector &q, const Vector &v)> SympObserverFn
Definition ode.hpp:36
std::function< void(real t, const Vector &y, Vector &dydt)> ODERhsFn
Definition ode.hpp:32
SymplecticResult ode_verlet(AccelFn accel, Vector q0, Vector v0, ODEParams p={}, SympObserverFn obs=nullptr)
Velocity Verlet, 2nd-order symplectic, 1 force evaluation per step.
Definition ode.cpp:353
SymplecticResult ode_rk4_2nd(AccelFn accel, Vector q0, Vector v0, ODEParams p={}, SympObserverFn obs=nullptr)
RK4 for second-order systems q'' = accel(q), Nystrom form.
Definition ode.cpp:369
std::size_t idx
Definition types.hpp:11
std::function< void(real t, const Vector &y)> ObserverFn
Definition ode.hpp:34
constexpr real e
Definition math.hpp:43
RK4Steps rk4(ODERhsFn f, Vector y0, ODEParams p={})
Definition ode.cpp:312
BasicVector< real > Vector
Real-valued dense vector with full backend dispatch (CPU + GPU)
Definition vector.hpp:130
std::function< void(const Vector &q, Vector &acc)> AccelFn
Definition ode.hpp:33
RK4_2ndSteps rk4_2nd(AccelFn accel, Vector q0, Vector v0, ODEParams p={})
Definition ode.cpp:325
ODEResult ode_euler(ODERhsFn f, Vector y0, ODEParams p={}, ObserverFn obs=nullptr)
Forward Euler, 1st-order, fixed step.
Definition ode.cpp:332
SymplecticResult ode_yoshida4(AccelFn accel, Vector q0, Vector v0, ODEParams p={}, SympObserverFn obs=nullptr)
Yoshida 4th-order symplectic, 3 force evaluations per step.
Definition ode.cpp:361
RK45Steps rk45(ODERhsFn f, Vector y0, ODEParams p={})
Definition ode.cpp:315
VerletSteps verlet(AccelFn accel, Vector q0, Vector v0, ODEParams p={})
Definition ode.cpp:319
ODEResult ode_rk45(ODERhsFn f, Vector y0, ODEParams p={}, ObserverFn obs=nullptr)
Adaptive Dormand-Prince RK45 with FSAL and PI step-size control.
Definition ode.cpp:346
ODE integrators: Euler, RK4, adaptive RK45 (Dormand-Prince), and symplectic Velocity Verlet / Yoshida...
Parameters for all ODE integrators. Set only the fields you need.
Definition ode.hpp:64