DNDSR 0.1.0.dev1+gcd065ad
Distributed Numeric Data Structure for CFV
Loading...
Searching...
No Matches
test_RiemannSolvers.cpp
Go to the documentation of this file.
1/**
2 * @file test_RiemannSolvers.cpp
3 * @brief Unit tests for Riemann solvers in Gas.hpp.
4 *
5 * Tests cover:
6 * - Consistency: F(UL, UR) with UL==UR equals the exact physical flux
7 * - Roe flux: default eigScheme=0, plus variants 1-8
8 * - HLLC flux: consistency and symmetry
9 * - HLLEP flux: consistency
10 * - InviscidFlux_IdealGas_Dispatcher: runtime dispatch
11 * - Symmetry: F(UL,UR,n) = -F(UR,UL,-n) for all solvers
12 * - Sod shock tube: UL != UR produces finite, bounded flux
13 * - Golden values for specific test vectors
14 *
15 * All functions are pure (no MPI, no mesh). Serial doctest.
16 */
17
18#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
19#include "doctest.h"
20
21#include "Euler/Gas.hpp"
22
23#include <Eigen/Dense>
24#include <cmath>
25#include <iostream>
26#include <iomanip>
27
28using namespace DNDS;
29using namespace DNDS::Euler::Gas;
30
31static constexpr real g_gamma = 1.4;
32static constexpr real GOLDEN_NOT_ACQUIRED = 1e300;
33
34// Build a 3D conservative state from primitive
35static Eigen::Vector<real, 5> prim2cons(real rho, real u, real v, real w, real p)
36{
37 Eigen::Vector<real, 5> U;
38 real E = p / (g_gamma - 1.0) + 0.5 * rho * (u * u + v * v + w * w);
39 U << rho, rho * u, rho * v, rho * w, E;
40 return U;
41}
42
43// Compute the exact physical normal-direction flux
44static Eigen::Vector<real, 5> exactNormalFlux(
45 const Eigen::Vector<real, 5> &U, const Eigen::Vector3d &n)
46{
47 Eigen::Vector3d velo = U.segment<3>(1) / U(0);
48 real vn = velo.dot(n);
49 real vSqr = velo.squaredNorm();
50 real p = (g_gamma - 1.0) * (U(4) - 0.5 * U(0) * vSqr);
51 real E = U(4);
52 Eigen::Vector<real, 5> F;
53 F(0) = U(0) * vn;
54 F.segment<3>(1) = U.segment<3>(1) * vn + p * n;
55 F(4) = (E + p) * vn;
56 return F;
57}
58
59// No-op dump callback
60static auto noDump = []() {};
61
62// ===================================================================
63// Helper: run a Riemann solver via the dispatcher and return flux
64// ===================================================================
65static Eigen::Vector<real, 5> callDispatcher(
66 RiemannSolverType rsType,
67 const Eigen::Vector<real, 5> &UL,
68 const Eigen::Vector<real, 5> &UR,
69 const Eigen::Vector3d &n)
70{
71 Eigen::Vector3d vg = Eigen::Vector3d::Zero();
72 Eigen::Vector<real, 5> F;
73 F.setZero();
74 real lam0, lam123, lam4;
75 InviscidFlux_IdealGas_Dispatcher<3>(
76 rsType, UL, UR, UL, UR, vg, n, g_gamma, F,
77 0.0, 0.0, 0.0, noDump, lam0, lam123, lam4);
78 return F;
79}
80
81// ===================================================================
82// CONSISTENCY: F(U, U, n) = exact physical flux
83// ===================================================================
84
85TEST_CASE("Roe consistency: identical states give exact flux")
86{
87 auto U = prim2cons(1.225, 100.0, -50.0, 25.0, 101325.0);
88 Eigen::Vector3d n(0.6, -0.8, 0.0);
89 n.normalize();
90
91 auto Fexact = exactNormalFlux(U, n);
92 auto F = callDispatcher(Roe, U, U, n);
93
94 for (int i = 0; i < 5; i++)
95 {
96 CAPTURE(i);
97 CHECK(F(i) == doctest::Approx(Fexact(i)).epsilon(1e-10));
98 }
99}
100
101TEST_CASE("HLLC consistency: identical states give exact flux")
102{
103 auto U = prim2cons(1.225, 100.0, -50.0, 25.0, 101325.0);
104 Eigen::Vector3d n(0.0, 1.0, 0.0);
105
106 auto Fexact = exactNormalFlux(U, n);
107 auto F = callDispatcher(HLLC, U, U, n);
108
109 for (int i = 0; i < 5; i++)
110 {
111 CAPTURE(i);
112 CHECK(F(i) == doctest::Approx(Fexact(i)).epsilon(1e-10));
113 }
114}
115
116TEST_CASE("HLLEP consistency: identical states give exact flux")
117{
118 auto U = prim2cons(1.225, 100.0, -50.0, 25.0, 101325.0);
119 Eigen::Vector3d n(0.0, 0.0, 1.0);
120
121 auto Fexact = exactNormalFlux(U, n);
122 auto F = callDispatcher(HLLEP, U, U, n);
123
124 for (int i = 0; i < 5; i++)
125 {
126 CAPTURE(i);
127 CHECK(F(i) == doctest::Approx(Fexact(i)).epsilon(1e-10));
128 }
129}
130
131TEST_CASE("Roe variants M1-M8 consistency (M2,M9 not implemented)")
132{
133 auto U = prim2cons(1.0, 50.0, 0.0, 0.0, 100000.0);
134 Eigen::Vector3d n(1.0, 0.0, 0.0);
135 auto Fexact = exactNormalFlux(U, n);
136
137 // Note: Roe_M2 (eigScheme=2) and Roe_M9 (eigScheme=9) are not implemented
138 for (auto rs : {Roe_M1, Roe_M3, Roe_M4, Roe_M5, Roe_M6, Roe_M7, Roe_M8})
139 {
140 CAPTURE(rs);
141 auto F = callDispatcher(rs, U, U, n);
142 for (int i = 0; i < 5; i++)
143 {
144 CAPTURE(i);
145 CHECK(F(i) == doctest::Approx(Fexact(i)).epsilon(1e-8));
146 }
147 }
148}
149
150// ===================================================================
151// SYMMETRY: F(UL, UR, n) = -F(UR, UL, -n)
152// ===================================================================
153
154TEST_CASE("Roe symmetry: F(UL,UR,n) = -F(UR,UL,-n)")
155{
156 auto UL = prim2cons(1.0, 100.0, 0.0, 0.0, 100000.0);
157 auto UR = prim2cons(0.125, 0.0, 0.0, 0.0, 10000.0);
158 Eigen::Vector3d n(1.0, 0.0, 0.0);
159
160 auto F1 = callDispatcher(Roe, UL, UR, n);
161 auto F2 = callDispatcher(Roe, UR, UL, -n);
162
163 for (int i = 0; i < 5; i++)
164 {
165 CAPTURE(i);
166 CHECK(F1(i) == doctest::Approx(-F2(i)).epsilon(1e-10));
167 }
168}
169
170TEST_CASE("HLLC symmetry: F(UL,UR,n) = -F(UR,UL,-n)")
171{
172 auto UL = prim2cons(1.0, 100.0, 0.0, 0.0, 100000.0);
173 auto UR = prim2cons(0.125, 0.0, 0.0, 0.0, 10000.0);
174 Eigen::Vector3d n(1.0, 0.0, 0.0);
175
176 auto F1 = callDispatcher(HLLC, UL, UR, n);
177 auto F2 = callDispatcher(HLLC, UR, UL, -n);
178
179 for (int i = 0; i < 5; i++)
180 {
181 CAPTURE(i);
182 CHECK(F1(i) == doctest::Approx(-F2(i)).epsilon(1e-10));
183 }
184}
185
186// ===================================================================
187// SOD SHOCK TUBE: non-trivial flux, bounded and finite
188// ===================================================================
189
191{
192 const char *name;
194 real goldenF[5]; // golden flux values (1e300 = not acquired)
195};
196
197// Sod problem: UL = (1.0, 0, 0, 0, 2.5), UR = (0.125, 0, 0, 0, 0.25)
198// in x-direction normal
199static auto g_sodUL = prim2cons(1.0, 0.0, 0.0, 0.0, 1.0);
200static auto g_sodUR = prim2cons(0.125, 0.0, 0.0, 0.0, 0.1);
201static const Eigen::Vector3d g_sodN = Eigen::Vector3d(1.0, 0.0, 0.0);
202
203static SodTestCase g_sodTests[] = {
204 {"Roe", Roe,
205 {0.0, 0.0, 0.0, 0.0, 0.0}}, // placeholder -- will acquire
206 {"HLLC", HLLC,
207 {0.0, 0.0, 0.0, 0.0, 0.0}},
208 {"HLLEP", HLLEP,
209 {0.0, 0.0, 0.0, 0.0, 0.0}},
210};
211
212TEST_CASE("Sod shock tube: flux is finite and bounded")
213{
214 for (auto &tc : g_sodTests)
215 {
216 SUBCASE(tc.name)
217 {
218 auto F = callDispatcher(tc.rsType, g_sodUL, g_sodUR, g_sodN);
219
220 std::cout << "[Sod/" << tc.name << "] F = " << std::scientific
221 << std::setprecision(10);
222 for (int i = 0; i < 5; i++)
223 std::cout << F(i) << " ";
224 std::cout << std::endl;
225
226 for (int i = 0; i < 5; i++)
227 {
228 CAPTURE(i);
229 CHECK_FALSE(std::isnan(F(i)));
230 CHECK_FALSE(std::isinf(F(i)));
231 }
232
233 // Mass flux should be non-negative (expansion from L to R)
234 CHECK(F(0) >= -1e-10);
235 }
236 }
237}
238
239// ===================================================================
240// GOLDEN VALUES: specific test vector with captured golden flux
241// ===================================================================
242
244{
245 const char *name;
247 // UL: rho=1.225, u=100, v=-50, w=25, p=101325
248 // UR: rho=0.8, u=200, v=0, w=0, p=80000
249 // n: (0.6, 0.8, 0.0)
250 real golden[5]; // golden flux (1e300 = not yet acquired)
251};
252
253static const auto g_goldenUL = prim2cons(1.225, 100.0, -50.0, 25.0, 101325.0);
254static const auto g_goldenUR = prim2cons(0.8, 200.0, 0.0, 0.0, 80000.0);
255static const Eigen::Vector3d g_goldenN = Eigen::Vector3d(0.6, 0.8, 0.0).normalized();
256
257static GoldenFluxCase g_goldenTests[] = {
258 {"Roe", Roe,
259 {8.1162345145e+01, 5.8813861917e+04, 5.8759839011e+04,
260 5.9539454795e+02, 2.7251925995e+07}},
261 {"HLLC", HLLC,
262 {9.3486183606e+01, 5.5647501399e+04, 5.7057534871e+04,
263 2.3371545902e+03, 2.5454872499e+07}},
264 {"HLLEP", HLLEP,
265 {9.2861523292e+01, 5.1385111929e+04, 6.2875774679e+04,
266 5.7376094955e+03, 2.6719441912e+07}},
267};
268
269TEST_CASE("Golden flux values for mixed-state test vector")
270{
271 for (auto &tc : g_goldenTests)
272 {
273 SUBCASE(tc.name)
274 {
275 auto F = callDispatcher(tc.rsType, g_goldenUL, g_goldenUR, g_goldenN);
276
277 std::cout << "[Golden/" << tc.name << "] F =";
278 for (int i = 0; i < 5; i++)
279 std::cout << " " << std::scientific << std::setprecision(10) << F(i);
280 std::cout << std::endl;
281
282 for (int i = 0; i < 5; i++)
283 {
284 CAPTURE(i);
285 CHECK_FALSE(std::isnan(F(i)));
286 if (tc.golden[i] < GOLDEN_NOT_ACQUIRED)
287 CHECK(F(i) == doctest::Approx(tc.golden[i]).epsilon(1e-8));
288 }
289 }
290 }
291}
292
293// ===================================================================
294// QUIESCENT GAS: all solvers agree on zero-flux for static gas
295// ===================================================================
296
297TEST_CASE("All solvers: quiescent gas produces same flux (p-only)")
298{
299 auto U = prim2cons(1.0, 0.0, 0.0, 0.0, 1.0);
300 Eigen::Vector3d n(1.0, 0.0, 0.0);
301 auto Fexact = exactNormalFlux(U, n);
302
303 for (auto rs : {Roe, HLLC, HLLEP})
304 {
305 CAPTURE(rs);
306 auto F = callDispatcher(rs, U, U, n);
307 for (int i = 0; i < 5; i++)
308 {
309 CAPTURE(i);
310 CHECK(F(i) == doctest::Approx(Fexact(i)).epsilon(1e-12));
311 }
312 }
313}
314
315// ===================================================================
316// EIGENVALUE OUTPUT: wave speeds should be physically meaningful
317// ===================================================================
318
319TEST_CASE("Roe eigenvalue output: lam0 < lam123 < lam4 for subsonic")
320{
321 auto UL = prim2cons(1.0, 50.0, 0.0, 0.0, 100000.0);
322 auto UR = prim2cons(1.1, 55.0, 0.0, 0.0, 105000.0);
323 Eigen::Vector3d n(1, 0, 0), vg(0, 0, 0);
324
325 Eigen::Vector<real, 5> F;
326 real lam0, lam123, lam4;
327 InviscidFlux_IdealGas_Dispatcher<3>(
328 Roe, UL, UR, UL, UR, vg, n, g_gamma, F,
329 0.0, 0.0, 0.0, noDump, lam0, lam123, lam4);
330
331 // |u-a| < |u| < |u+a| for subsonic flow where u > 0
332 CHECK(lam0 >= 0);
333 CHECK(lam123 >= 0);
334 CHECK(lam4 >= 0);
335 CHECK(lam4 >= lam123); // |u+a| >= |u|
336}
337
338// ===================================================================
339// DIAGONAL NORMAL: flux with n=(1/sqrt3,1/sqrt3,1/sqrt3)
340// ===================================================================
341
342TEST_CASE("Roe consistency: diagonal normal")
343{
344 auto U = prim2cons(2.0, 100.0, 200.0, 300.0, 200000.0);
345 Eigen::Vector3d n(1.0, 1.0, 1.0);
346 n.normalize();
347
348 auto Fexact = exactNormalFlux(U, n);
349 auto F = callDispatcher(Roe, U, U, n);
350
351 for (int i = 0; i < 5; i++)
352 {
353 CAPTURE(i);
354 CHECK(F(i) == doctest::Approx(Fexact(i)).epsilon(1e-8));
355 }
356}
Ideal-gas Riemann solvers, flux functions, and thermodynamic utilities for the compressible Euler / N...
RiemannSolverType
Selects the approximate Riemann solver and its entropy-fix variant.
Definition Gas.hpp:62
the host side operators are provided as implemented
double real
Canonical floating-point scalar used throughout DNDSR (double precision).
Definition Defines.hpp:105
RiemannSolverType rsType
RiemannSolverType rsType
Eigen::Matrix< real, 5, 1 > v
Eigen::Vector3d vg(0, 0, 0)
Eigen::Vector3d velo
CHECK(result.size()==3)
auto Fexact
Eigen::Vector3d n(1.0, 0.0, 0.0)
TEST_CASE("Roe symmetry: F(UL,UR,n) = -F(UR,UL,-n)")