DNDSR 0.1.0.dev1+gcd065ad
Distributed Numeric Data Structure for CFV
Loading...
Searching...
No Matches
test_ODE.cpp
Go to the documentation of this file.
1/**
2 * @file test_ODE.cpp
3 * @brief Unit tests for ODE time integrators in Solver/ODE.hpp.
4 *
5 * Tests the harmonic oscillator du/dt = A*u, A = [[0,-w],[w,0]],
6 * u(0) = [1,0], exact: u(t) = [cos(wt), sin(wt)].
7 * This oscillatory (non-dissipative) problem reveals phase/amplitude
8 * errors that decay tests hide.
9 *
10 * Verified convergence orders:
11 * - ExplicitSSPRK3: 3rd
12 * - ImplicitEuler: 1st
13 * - SDIRK4 sc=0: 4th
14 * - ESDIRK4 sc=1 (DITR U2R2 form): 4th
15 * - ESDIRK3 sc=2: 3rd
16 * - Trapezoid sc=3: 2nd
17 * - ESDIRK2 sc=4: 2nd
18 * - VBDF k=1: 1st
19 * - VBDF k=2: 2nd
20 * - DITR U2R2 (Hermite3 mask=0): 4th (single-step, FSAL)
21 * - DITR U2R1 (Hermite3 mask=1): 3rd (L-stable)
22 *
23 * All tests are serial (no MPI).
24 */
25
26#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
27#include "doctest.h"
28
29#include "Solver/ODE.hpp"
30
31#include <cmath>
32#include <iostream>
33#include <iomanip>
34
35using namespace DNDS;
36
37// ===================================================================
38// 2-component vector satisfying TDATA/TDTAU interface
39// ===================================================================
40struct Vec2
41{
42 double x = 0, y = 0;
43 Vec2() = default;
44 Vec2(double a, double b) : x(a), y(b) {}
45 Vec2 &operator=(const Vec2 &) = default;
46 void operator+=(const Vec2 &o) { x += o.x; y += o.y; }
47 void operator*=(double s) { x *= s; y *= s; }
48 void operator*=(const Vec2 &o) { x *= o.x; y *= o.y; } // element-wise (SSPRK3 dTau)
49 void setConstant(double s) { x = s; y = s; }
50 void addTo(const Vec2 &o, double s) { x += o.x * s; y += o.y * s; }
51 double norm() const { return std::sqrt(x * x + y * y); }
52};
53
54static auto v2init = [](Vec2 &v) { v = Vec2(0, 0); };
55
56// ===================================================================
57// Harmonic oscillator: du/dt = [[0,-w],[w,0]] * u, w=2*pi
58// Exact: u(t) = [cos(wt), sin(wt)]
59// Jacobian J = [[0,-w],[w,0]]
60// ===================================================================
61static const double g_omega = 2.0 * pi;
62
64
65// frhs: rhs = A*u
66static tODE::Frhs g_frhs = [](Vec2 &rhs, Vec2 &u, Vec2 &, int, real, int)
67{
68 rhs.x = -g_omega * u.y;
69 rhs.y = g_omega * u.x;
70};
71
72// fdt: dTau = very large
73static tODE::Fdt g_fdt = [](Vec2 &, Vec2 &dTau, real, int)
74{
75 dTau = Vec2(1e100, 1e100);
76};
77
78// fsolve: solve (1/dTau + 1/dt - alpha*J) * xinc = rhs
79// J = [[0,-w],[w,0]], so -alpha*J = [[0,alpha*w],[-alpha*w,0]]
80// Diagonal of (1/dTau + 1/dt) * I - alpha * J:
81// [[1/dTau+1/dt, alpha*w],[-alpha*w, 1/dTau+1/dt]]
82// With dTau~inf: [[1/dt, alpha*w],[-alpha*w, 1/dt]]
83static tODE::Fsolve g_fsolve = [](Vec2 &, Vec2 &rhs, Vec2 &, Vec2 &dTau,
84 real dt, real alpha, Vec2 &xinc, int, real, int)
85{
86 double d = 1.0 / dTau.x + 1.0 / dt;
87 double aw = alpha * g_omega;
88 double det = d * d + aw * aw;
89 xinc.x = (d * rhs.x - aw * rhs.y) / det;
90 xinc.y = (aw * rhs.x + d * rhs.y) / det;
91};
92
93static tODE::Fincrement g_fincrement = [](Vec2 &u, Vec2 &xinc, real s, int)
94{
95 u.x += s * xinc.x;
96 u.y += s * xinc.y;
97};
98
99// ===================================================================
100// Integration helper
101// ===================================================================
102static const int g_maxNewton = 50;
103static const double g_newtonTol = 1e-13;
104
105static double integrateOscillator(ODE::ImplicitDualTimeStep<Vec2, Vec2> &ode,
106 double T, int N)
107{
108 double dt = T / N;
109 Vec2 u(1.0, 0.0), xinc;
110
111 // Stop when residual norm < threshold OR max iterations reached
112 auto fstop = [&](int iter, Vec2 &res, int) -> bool
113 {
114 double resNorm = std::sqrt(res.x * res.x + res.y * res.y);
115 return resNorm < g_newtonTol || iter >= g_maxNewton;
116 };
117
118 for (int step = 0; step < N; step++)
119 ode.Step(u, xinc, g_frhs, g_fdt, g_fsolve, g_maxNewton, fstop, g_fincrement, dt);
120
121 // Error: distance from exact [cos(wT), sin(wT)]
122 double ex = std::cos(g_omega * T);
123 double ey = std::sin(g_omega * T);
124 return std::sqrt((u.x - ex) * (u.x - ex) + (u.y - ey) * (u.y - ey));
125}
126
128{
130};
131
132static ConvergenceResult measureOrder(
134 double T, int N_base)
135{
136 double e1 = integrateOscillator(ode, T, N_base);
137 double e2 = integrateOscillator(ode, T, 2 * N_base);
138 return {e1, e2, std::log2(e1 / e2)};
139}
140
141// ESDIRK: fresh instances per run (internal FSAL state)
142template <int sc>
143static ConvergenceResult measureOrderESDIRK(double T, int N_base)
144{
145 auto run = [&](int N) {
147 return integrateOscillator(ode, T, N);
148 };
149 double e1 = run(N_base);
150 double e2 = run(2 * N_base);
151 return {e1, e2, std::log2(e1 / e2)};
152}
153
154// DITR (Hermite3): fresh instances per run
155static ConvergenceResult measureOrderDITR(int mask, double alpha,
156 double T, int N_base)
157{
158 auto run = [&](int N) {
160 1, v2init, v2init, alpha, /*curSolveMethod=*/0, /*nStartIter=*/0,
161 /*thetaM1n=*/0.9146, /*thetaM2n=*/0.0, mask, /*nMGn=*/0);
162 return integrateOscillator(ode, T, N);
163 };
164 double e1 = run(N_base);
165 double e2 = run(2 * N_base);
166 return {e1, e2, std::log2(e1 / e2)};
167}
168
169// ===================================================================
170// Tests
171// ===================================================================
172
173TEST_CASE("SSPRK3: 3rd-order on oscillator")
174{
176 auto res = measureOrder(ode, 1.0, 100);
177 std::cout << "[SSPRK3] err1=" << std::scientific << res.err_coarse
178 << " err2=" << res.err_fine << " order=" << std::fixed << res.order << std::endl;
179 CHECK(res.order > 2.8);
180 CHECK(res.order < 3.5);
181}
182
183TEST_CASE("ImplicitEuler: 1st-order on oscillator")
184{
186 auto res = measureOrder(ode, 1.0, 200);
187 std::cout << "[ImplicitEuler] err1=" << std::scientific << res.err_coarse
188 << " err2=" << res.err_fine << " order=" << std::fixed << res.order << std::endl;
189 CHECK(res.order > 0.9);
190 CHECK(res.order < 1.3);
191}
192
193TEST_CASE("SDIRK4 (sc=0): 4th-order on oscillator")
194{
196 auto res = measureOrder(ode, 1.0, 20);
197 std::cout << "[SDIRK4-sc0] err1=" << std::scientific << res.err_coarse
198 << " err2=" << res.err_fine << " order=" << std::fixed << res.order << std::endl;
199 CHECK(res.order > 3.5);
200 CHECK(res.order < 4.5);
201}
202
203TEST_CASE("ESDIRK4 (sc=1): 4th-order on oscillator")
204{
205 auto res = measureOrderESDIRK<1>(1.0, 20);
206 std::cout << "[ESDIRK4-sc1] err1=" << std::scientific << res.err_coarse
207 << " err2=" << res.err_fine << " order=" << std::fixed << res.order << std::endl;
208 CHECK(res.order > 3.5);
209 CHECK(res.order < 5.0);
210}
211
212TEST_CASE("ESDIRK3 (sc=2): 3rd-order on oscillator")
213{
214 auto res = measureOrderESDIRK<2>(1.0, 40);
215 std::cout << "[ESDIRK3-sc2] err1=" << std::scientific << res.err_coarse
216 << " err2=" << res.err_fine << " order=" << std::fixed << res.order << std::endl;
217 CHECK(res.order > 2.7);
218 CHECK(res.order < 3.5);
219}
220
221TEST_CASE("Trapezoid (sc=3): 2nd-order on oscillator")
222{
223 auto res = measureOrderESDIRK<3>(1.0, 40);
224 std::cout << "[Trapezoid-sc3] err1=" << std::scientific << res.err_coarse
225 << " err2=" << res.err_fine << " order=" << std::fixed << res.order << std::endl;
226 CHECK(res.order > 1.8);
227 CHECK(res.order < 2.5);
228}
229
230TEST_CASE("ESDIRK2 (sc=4): 2nd-order on oscillator")
231{
232 auto res = measureOrderESDIRK<4>(1.0, 40);
233 std::cout << "[ESDIRK2-sc4] err1=" << std::scientific << res.err_coarse
234 << " err2=" << res.err_fine << " order=" << std::fixed << res.order << std::endl;
235 CHECK(res.order > 1.8);
236 CHECK(res.order < 2.5);
237}
238
239TEST_CASE("VBDF k=1: 1st-order on oscillator")
240{
242 auto res = measureOrder(ode, 1.0, 200);
243 std::cout << "[VBDF1] err1=" << std::scientific << res.err_coarse
244 << " err2=" << res.err_fine << " order=" << std::fixed << res.order << std::endl;
245 CHECK(res.order > 0.9);
246 CHECK(res.order < 1.3);
247}
248
249TEST_CASE("VBDF k=2: 2nd-order on oscillator")
250{
251 auto run = [](int N) {
253 return integrateOscillator(ode, 1.0, N);
254 };
255 double e1 = run(100);
256 double e2 = run(200);
257 double order = std::log2(e1 / e2);
258 std::cout << "[VBDF2] err1=" << std::scientific << e1
259 << " err2=" << e2 << " order=" << std::fixed << order << std::endl;
260 CHECK(order > 1.8);
261 CHECK(order < 2.5);
262}
263
264// ===================================================================
265// DITR (Hermite3) methods
266// ===================================================================
267
268// DITR methods have coupled mid-point + endpoint Newton iterations.
269// Need enough iterations for both sub-solves to converge on nonlinear
270// (oscillatory) problems.
271TEST_CASE("DITR U2R2 (mask=0, alpha=0.5): 4th-order on oscillator")
272{
273 // U2R2 with alpha=0.5 is Lobatto IIIA, 4th order
274 auto res = measureOrderDITR(0, 0.5, 1.0, 20);
275 std::cout << "[DITR-U2R2] err1=" << std::scientific << res.err_coarse
276 << " err2=" << res.err_fine << " order=" << std::fixed << res.order << std::endl;
277 CHECK(res.order > 3.5);
278 CHECK(res.order < 5.0);
279}
280
281TEST_CASE("DITR U2R2 (mask=0, alpha=0.55): 3rd-order on oscillator")
282{
283 // U2R2 with alpha != 0.5 is 3rd order
284 auto res = measureOrderDITR(0, 0.55, 1.0, 40);
285 std::cout << "[DITR-U2R2-0.55] err1=" << std::scientific << res.err_coarse
286 << " err2=" << res.err_fine << " order=" << std::fixed << res.order << std::endl;
287 CHECK(res.order > 2.7);
288 CHECK(res.order < 4.0);
289}
290
291TEST_CASE("DITR U2R1 (mask=1): 3rd-order L-stable on oscillator")
292{
293 auto res = measureOrderDITR(1, 0.55, 1.0, 40);
294 std::cout << "[DITR-U2R1] err1=" << std::scientific << res.err_coarse
295 << " err2=" << res.err_fine << " order=" << std::fixed << res.order << std::endl;
296 CHECK(res.order > 2.7);
297 CHECK(res.order < 3.5);
298}
299
300// ===================================================================
301// Golden value: SSPRK3 on oscillator at T=0.5, N=200
302// ===================================================================
303
304TEST_CASE("SSPRK3: golden value on oscillator")
305{
307 double err = integrateOscillator(ode, 0.5, 200);
308 std::cout << "[SSPRK3/golden] err=" << std::scientific << std::setprecision(10) << err << std::endl;
309 CHECK(err < 1e-6); // very small at 200 steps for T=0.5
310}
std::function< void(TDATA &, TDATA &, TDTAU &, int, real, int)> Frhs
Definition ODE.hpp:13
std::function< void(TDATA &, TDATA &, TDATA &, TDTAU &, real, real, TDATA &, int, real, int)> Fsolve
Definition ODE.hpp:16
std::function< void(TDATA &, TDTAU &, real, int)> Fdt
Definition ODE.hpp:14
std::function< void(TDATA &, TDATA &, real, int)> Fincrement
Definition ODE.hpp:18
the host side operators are provided as implemented
DNDS_CONSTANT const real pi
π in double precision (matches DNDS_E_PI macro).
Definition Defines.hpp:199
double real
Canonical floating-point scalar used throughout DNDSR (double precision).
Definition Defines.hpp:105
double x
Definition test_ODE.cpp:42
double norm() const
Definition test_ODE.cpp:51
void operator*=(const Vec2 &o)
Definition test_ODE.cpp:48
Vec2(double a, double b)
Definition test_ODE.cpp:44
double y
Definition test_ODE.cpp:42
void operator*=(double s)
Definition test_ODE.cpp:47
Vec2()=default
void operator+=(const Vec2 &o)
Definition test_ODE.cpp:46
void addTo(const Vec2 &o, double s)
Definition test_ODE.cpp:50
Vec2 & operator=(const Vec2 &)=default
void setConstant(double s)
Definition test_ODE.cpp:49
Eigen::Matrix< real, 5, 1 > v
constexpr DNDS::index N
tVec b(NCells)
real alpha
CHECK(result.size()==3)
double err
Definition test_ODE.cpp:307
double e2
Definition test_ODE.cpp:256
std::cout<< "[ESDIRK2-sc4] err1="<< std::scientific<< res.err_coarse<< " err2="<< res.err_fine<< " order="<< std::fixed<< res.order<< std::endl;CHECK(res.order > 1.8);CHECK(res.order< 2.5);}TEST_CASE("VBDF k=1: 1st-order on oscillator"){ ODE::ImplicitVBDFDualTimeStep< Vec2, Vec2 > ode(1, v2init, v2init, 1)
double order
Definition test_ODE.cpp:257
auto res
Definition test_ODE.cpp:196
double e1
Definition test_ODE.cpp:255
TEST_CASE("3D: VFV P2 HQM error < P1 on sinCos3D")