DNDSR 0.1.0.dev1+gcd065ad
Distributed Numeric Data Structure for CFV
Loading...
Searching...
No Matches
test_RANS.cpp
Go to the documentation of this file.
1/**
2 * @file test_RANS.cpp
3 * @brief Unit tests for RANS turbulence model functions in RANS_ke.hpp.
4 *
5 * Tests cover:
6 * - GetMut_KOWilcox: turbulent viscosity from k-omega Wilcox 2006
7 * - GetMut_SST: turbulent viscosity from k-omega SST
8 * - GetMut_RealizableKe: turbulent viscosity from Realizable k-epsilon
9 * - GetSource_KOWilcox: source terms (mode 0 = full, mode 1 = Jacobian diagonal)
10 * - GetSource_SST: source terms
11 * - GetSource_RealizableKe: source terms
12 * - GetVisFlux_KOWilcox: viscous flux for turbulent transport variables
13 * - GetVisFlux_SST: viscous flux
14 * - GetVisFlux_RealizableKe: viscous flux
15 *
16 * Key properties tested:
17 * - mut >= 0 (non-negative turbulent viscosity)
18 * - mut <= 1e5 * muLam (CFL3D limiting)
19 * - Zero gradient -> zero production in source
20 * - Zero gradient -> zero viscous flux
21 * - Finite output (no NaN/Inf)
22 * - Golden values for specific test vectors
23 *
24 * All functions are pure template functions (no MPI, no mesh).
25 * SA model is excluded because GetSource_SA references EulerEvaluator settings.
26 */
27
28#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
29#include "doctest.h"
30
31#include "Euler/RANS_ke.hpp"
32
33#include <Eigen/Dense>
34#include <cmath>
35#include <iostream>
36#include <iomanip>
37
38using namespace DNDS;
39using namespace DNDS::Euler::RANS;
40
41static constexpr real g_gamma = 1.4;
42static constexpr int dim = 3;
43static constexpr int I4 = dim + 1; // = 4
44// nVars for 2-equation models: 5 (flow) + 2 (turb) = 7
45static constexpr int nVars = 7;
46
47static constexpr real GOLDEN_NOT_ACQUIRED = 1e300;
48
49// Build a 3D 2-equation conservative state:
50// [rho, rho*u, rho*v, rho*w, rho*E, rho*k, rho*omega_or_epsilon]
51static Eigen::Vector<real, nVars> buildState(
52 real rho, real u, real v, real w, real p, real k, real omega)
53{
54 Eigen::Vector<real, nVars> U;
55 real E = p / (g_gamma - 1.0) + 0.5 * rho * (u * u + v * v + w * w);
56 U << rho, rho * u, rho * v, rho * w, E, rho * k, rho * omega;
57 return U;
58}
59
60// Build a gradient matrix (dim x nVars) with a simple shear profile:
61// du/dy = S (shear rate), everything else zero
62static Eigen::Matrix<real, dim, nVars> buildShearGrad(
63 const Eigen::Vector<real, nVars> &U, real S_shear)
64{
65 Eigen::Matrix<real, dim, nVars> DiffU;
66 DiffU.setZero();
67 // d(rho*u)/dy = rho * S (since rho is constant in this test)
68 DiffU(1, 1) = U(0) * S_shear; // d(rho*u)/dy
69 return DiffU;
70}
71
72// ===================================================================
73// Turbulence equilibrium state:
74// Typical boundary layer: rho=1.225, u=50, p=101325
75// k=10, omega=1000 (or epsilon=Cmu*k*omega=0.09*10*1000=900)
76// mu_lam = 1.8e-5
77// wall distance d=0.01 (inside BL)
78// ===================================================================
79static const real g_rho = 1.225;
80static const real g_u = 50.0;
81static const real g_p = 101325.0;
82static const real g_k = 10.0;
83static const real g_omega = 1000.0;
84static const real g_epsilon = 0.09 * g_k * g_omega; // Cmu * k * omega
85static const real g_mu = 1.8e-5;
86static const real g_d = 0.01;
87static const real g_Sshear = 500.0; // du/dy
88
89// ===================================================================
90// GetMut_KOWilcox
91// ===================================================================
92
93TEST_CASE("GetMut_KOWilcox: non-negative")
94{
95 auto U = buildState(g_rho, g_u, 0, 0, g_p, g_k, g_omega);
96 auto DiffU = buildShearGrad(U, g_Sshear);
97 real mut = GetMut_KOWilcox<dim>(U, DiffU, g_mu, g_d);
98
99 CHECK(mut >= 0.0);
100 CHECK(std::isfinite(mut));
101}
102
103TEST_CASE("GetMut_KOWilcox: bounded by 1e5 * muLam")
104{
105 auto U = buildState(g_rho, g_u, 0, 0, g_p, g_k, g_omega);
106 auto DiffU = buildShearGrad(U, g_Sshear);
107 real mut = GetMut_KOWilcox<dim>(U, DiffU, g_mu, g_d);
108
109 CHECK(mut <= 1e5 * g_mu + 1e-10);
110}
111
112TEST_CASE("GetMut_KOWilcox: golden value")
113{
114 auto U = buildState(g_rho, g_u, 0, 0, g_p, g_k, g_omega);
115 auto DiffU = buildShearGrad(U, g_Sshear);
116 real mut = GetMut_KOWilcox<dim>(U, DiffU, g_mu, g_d);
117
118 std::cout << "[KOWilcox/mut] " << std::scientific << std::setprecision(10) << mut << std::endl;
119 // Golden value captured:
120 real golden = 8.4000000000e-03;
121 if (golden < GOLDEN_NOT_ACQUIRED)
122 CHECK(mut == doctest::Approx(golden).epsilon(1e-6));
123}
124
125// ===================================================================
126// GetMut_SST
127// ===================================================================
128
129TEST_CASE("GetMut_SST: non-negative")
130{
131 auto U = buildState(g_rho, g_u, 0, 0, g_p, g_k, g_omega);
132 auto DiffU = buildShearGrad(U, g_Sshear);
133 real mut = GetMut_SST<dim>(U, DiffU, g_mu, g_d);
134
135 CHECK(mut >= 0.0);
136 CHECK(std::isfinite(mut));
137}
138
139TEST_CASE("GetMut_SST: bounded by 1e5 * muLam")
140{
141 auto U = buildState(g_rho, g_u, 0, 0, g_p, g_k, g_omega);
142 auto DiffU = buildShearGrad(U, g_Sshear);
143 real mut = GetMut_SST<dim>(U, DiffU, g_mu, g_d);
144
145 CHECK(mut <= 1e5 * g_mu + 1e-10);
146}
147
148TEST_CASE("GetMut_SST: golden value")
149{
150 auto U = buildState(g_rho, g_u, 0, 0, g_p, g_k, g_omega);
151 auto DiffU = buildShearGrad(U, g_Sshear);
152 real mut = GetMut_SST<dim>(U, DiffU, g_mu, g_d);
153
154 std::cout << "[SST/mut] " << std::scientific << std::setprecision(10) << mut << std::endl;
155 real golden = 7.5950000000e-03;
156 if (golden < GOLDEN_NOT_ACQUIRED)
157 CHECK(mut == doctest::Approx(golden).epsilon(1e-6));
158}
159
160// ===================================================================
161// GetMut_RealizableKe
162// ===================================================================
163
164TEST_CASE("GetMut_RealizableKe: non-negative")
165{
166 auto U = buildState(g_rho, g_u, 0, 0, g_p, g_k, g_epsilon);
167 auto DiffU = buildShearGrad(U, g_Sshear);
168 real mut = GetMut_RealizableKe<dim>(U, DiffU, g_mu, g_d);
169
170 CHECK(mut >= 0.0);
171 CHECK(std::isfinite(mut));
172}
173
174TEST_CASE("GetMut_RealizableKe: bounded by 1e5 * muLam")
175{
176 auto U = buildState(g_rho, g_u, 0, 0, g_p, g_k, g_epsilon);
177 auto DiffU = buildShearGrad(U, g_Sshear);
178 real mut = GetMut_RealizableKe<dim>(U, DiffU, g_mu, g_d);
179
180 CHECK(mut <= 1e5 * g_mu + 1e-10);
181}
182
183TEST_CASE("GetMut_RealizableKe: golden value")
184{
185 auto U = buildState(g_rho, g_u, 0, 0, g_p, g_k, g_epsilon);
186 auto DiffU = buildShearGrad(U, g_Sshear);
187 real mut = GetMut_RealizableKe<dim>(U, DiffU, g_mu, g_d);
188
189 std::cout << "[RealKe/mut] " << std::scientific << std::setprecision(10) << mut << std::endl;
190 real golden = 1.2250000000e-02;
191 if (golden < GOLDEN_NOT_ACQUIRED)
192 CHECK(mut == doctest::Approx(golden).epsilon(1e-6));
193}
194
195// ===================================================================
196// Source terms: zero gradient -> zero production (only destruction)
197// ===================================================================
198
199TEST_CASE("GetSource_KOWilcox: zero gradient finite and has destruction")
200{
201 auto U = buildState(g_rho, g_u, 0, 0, g_p, g_k, g_omega);
202 Eigen::Matrix<real, dim, nVars> DiffU = Eigen::Matrix<real, dim, nVars>::Zero();
203 Eigen::Vector<real, nVars> source;
204 source.setZero();
205
206 GetSource_KOWilcox<dim>(U, DiffU, g_mu, g_d, source, 0);
207
208 CHECK(std::isfinite(source(I4 + 1)));
209 CHECK(std::isfinite(source(I4 + 2)));
210 // k source should be negative (destruction only when Pk=0)
211 CHECK(source(I4 + 1) <= 0.0);
212 // omega source should also be negative (destruction)
213 CHECK(source(I4 + 2) <= 0.0);
214}
215
216TEST_CASE("GetSource_SST: zero gradient finite")
217{
218 auto U = buildState(g_rho, g_u, 0, 0, g_p, g_k, g_omega);
219 Eigen::Matrix<real, dim, nVars> DiffU = Eigen::Matrix<real, dim, nVars>::Zero();
220 Eigen::Vector<real, nVars> source;
221 source.setZero();
222
223 GetSource_SST<dim>(U, DiffU, g_mu, g_d, 1e10, source, 0);
224
225 CHECK(std::isfinite(source(I4 + 1)));
226 CHECK(std::isfinite(source(I4 + 2)));
227 // k destruction: -betaStar * rho * k * omega < 0
228 CHECK(source(I4 + 1) <= 0.0);
229}
230
231TEST_CASE("GetSource_RealizableKe: zero gradient finite")
232{
233 auto U = buildState(g_rho, g_u, 0, 0, g_p, g_k, g_epsilon);
234 Eigen::Matrix<real, dim, nVars> DiffU = Eigen::Matrix<real, dim, nVars>::Zero();
235 Eigen::Vector<real, nVars> source;
236 source.setZero();
237
238 GetSource_RealizableKe<dim>(U, DiffU, g_mu, g_d, source, 0);
239
240 CHECK(std::isfinite(source(I4 + 1)));
241 CHECK(std::isfinite(source(I4 + 2)));
242}
243
244// ===================================================================
245// Source terms with shear: golden values
246// ===================================================================
247
248TEST_CASE("GetSource_KOWilcox: shear gradient golden")
249{
250 auto U = buildState(g_rho, g_u, 0, 0, g_p, g_k, g_omega);
251 auto DiffU = buildShearGrad(U, g_Sshear);
252 Eigen::Vector<real, nVars> source;
253 source.setZero();
254
255 GetSource_KOWilcox<dim>(U, DiffU, g_mu, g_d, source, 0);
256
257 std::cout << "[KOWilcox/source] k=" << std::scientific << std::setprecision(10)
258 << source(I4 + 1) << " omega=" << source(I4 + 2) << std::endl;
259
260 CHECK(std::isfinite(source(I4 + 1)));
261 CHECK(std::isfinite(source(I4 + 2)));
262
263 // With shear, production should exceed destruction (positive total k-source)
264 // Not always true depending on parameters, so just check finite
265}
266
267TEST_CASE("GetSource_SST: shear gradient golden")
268{
269 auto U = buildState(g_rho, g_u, 0, 0, g_p, g_k, g_omega);
270 auto DiffU = buildShearGrad(U, g_Sshear);
271 Eigen::Vector<real, nVars> source;
272 source.setZero();
273
274 GetSource_SST<dim>(U, DiffU, g_mu, g_d, 1e10, source, 0);
275
276 std::cout << "[SST/source] k=" << std::scientific << std::setprecision(10)
277 << source(I4 + 1) << " omega=" << source(I4 + 2) << std::endl;
278
279 CHECK(std::isfinite(source(I4 + 1)));
280 CHECK(std::isfinite(source(I4 + 2)));
281}
282
283TEST_CASE("GetSource_RealizableKe: shear gradient golden")
284{
285 auto U = buildState(g_rho, g_u, 0, 0, g_p, g_k, g_epsilon);
286 auto DiffU = buildShearGrad(U, g_Sshear);
287 Eigen::Vector<real, nVars> source;
288 source.setZero();
289
290 GetSource_RealizableKe<dim>(U, DiffU, g_mu, g_d, source, 0);
291
292 std::cout << "[RealKe/source] k=" << std::scientific << std::setprecision(10)
293 << source(I4 + 1) << " epsilon=" << source(I4 + 2) << std::endl;
294
295 CHECK(std::isfinite(source(I4 + 1)));
296 CHECK(std::isfinite(source(I4 + 2)));
297}
298
299// ===================================================================
300// Source terms: mode 1 (implicit diagonal) should be non-negative
301// ===================================================================
302
303TEST_CASE("GetSource_KOWilcox mode=1: implicit diagonal non-negative")
304{
305 auto U = buildState(g_rho, g_u, 0, 0, g_p, g_k, g_omega);
306 auto DiffU = buildShearGrad(U, g_Sshear);
307 Eigen::Vector<real, nVars> source;
308 source.setZero();
309
310 GetSource_KOWilcox<dim>(U, DiffU, g_mu, g_d, source, 1);
311
312 // mode=1 returns destruction rate coefficients, should be >= 0
313 CHECK(source(I4 + 1) >= 0.0);
314 CHECK(source(I4 + 2) >= 0.0);
315}
316
317TEST_CASE("GetSource_SST mode=1: implicit diagonal non-negative")
318{
319 auto U = buildState(g_rho, g_u, 0, 0, g_p, g_k, g_omega);
320 auto DiffU = buildShearGrad(U, g_Sshear);
321 Eigen::Vector<real, nVars> source;
322 source.setZero();
323
324 GetSource_SST<dim>(U, DiffU, g_mu, g_d, 1e10, source, 1);
325
326 CHECK(source(I4 + 1) >= 0.0);
327 CHECK(source(I4 + 2) >= 0.0);
328}
329
330// ===================================================================
331// Viscous flux: zero gradient -> zero flux
332// ===================================================================
333
334TEST_CASE("GetVisFlux_KOWilcox: zero gradient -> zero flux")
335{
336 auto U = buildState(g_rho, g_u, 0, 0, g_p, g_k, g_omega);
337 // DiffUxyPrim: gradient of PRIMITIVE variables
338 Eigen::Matrix<real, dim, nVars> DiffPrim = Eigen::Matrix<real, dim, nVars>::Zero();
339 Eigen::Vector3d norm(1, 0, 0);
340 Eigen::Vector<real, nVars> vFlux;
341 vFlux.setZero();
342
343 real mut = 0.01; // arbitrary
344 GetVisFlux_KOWilcox<dim>(U, DiffPrim, norm, mut, g_d, g_mu, vFlux);
345
346 CHECK(vFlux(I4 + 1) == doctest::Approx(0.0).epsilon(1e-14));
347 CHECK(vFlux(I4 + 2) == doctest::Approx(0.0).epsilon(1e-14));
348}
349
350TEST_CASE("GetVisFlux_SST: zero gradient -> zero flux")
351{
352 auto U = buildState(g_rho, g_u, 0, 0, g_p, g_k, g_omega);
353 Eigen::Matrix<real, dim, nVars> DiffPrim = Eigen::Matrix<real, dim, nVars>::Zero();
354 Eigen::Vector3d norm(1, 0, 0);
355 Eigen::Vector<real, nVars> vFlux;
356 vFlux.setZero();
357
358 real mut = 0.01;
359 GetVisFlux_SST<dim>(U, DiffPrim, norm, mut, g_d, g_mu, vFlux);
360
361 CHECK(vFlux(I4 + 1) == doctest::Approx(0.0).epsilon(1e-14));
362 CHECK(vFlux(I4 + 2) == doctest::Approx(0.0).epsilon(1e-14));
363}
364
365TEST_CASE("GetVisFlux_RealizableKe: zero gradient -> zero flux")
366{
367 auto U = buildState(g_rho, g_u, 0, 0, g_p, g_k, g_epsilon);
368 Eigen::Matrix<real, dim, nVars> DiffPrim = Eigen::Matrix<real, dim, nVars>::Zero();
369 Eigen::Vector3d norm(1, 0, 0);
370 Eigen::Vector<real, nVars> vFlux;
371 vFlux.setZero();
372
373 real mut = 0.01;
374 GetVisFlux_RealizableKe<dim>(U, DiffPrim, norm, mut, g_d, g_mu, vFlux);
375
376 CHECK(vFlux(I4 + 1) == doctest::Approx(0.0).epsilon(1e-14));
377 CHECK(vFlux(I4 + 2) == doctest::Approx(0.0).epsilon(1e-14));
378}
379
380// ===================================================================
381// Viscous flux: non-zero gradient produces non-zero flux
382// ===================================================================
383
384TEST_CASE("GetVisFlux_KOWilcox: k-gradient produces k-flux")
385{
386 auto U = buildState(g_rho, g_u, 0, 0, g_p, g_k, g_omega);
387 Eigen::Matrix<real, dim, nVars> DiffPrim = Eigen::Matrix<real, dim, nVars>::Zero();
388 // dk/dx = 100 (in primitive space, dk/dx = dk/dx since k is already primitive-like)
389 DiffPrim(0, I4 + 1) = 100.0;
390 Eigen::Vector3d norm(1, 0, 0);
391 Eigen::Vector<real, nVars> vFlux;
392 vFlux.setZero();
393
394 real mut = 0.01;
395 GetVisFlux_KOWilcox<dim>(U, DiffPrim, norm, mut, g_d, g_mu, vFlux);
396
397 // k-flux should be positive (diffusion in direction of decreasing k)
398 CHECK(vFlux(I4 + 1) > 0.0);
399 CHECK(std::isfinite(vFlux(I4 + 1)));
400 // omega-flux should still be zero (no omega gradient)
401 CHECK(vFlux(I4 + 2) == doctest::Approx(0.0).epsilon(1e-14));
402}
403
404// ===================================================================
405// Mut increases with k for fixed omega (physical)
406// ===================================================================
407
408TEST_CASE("GetMut_KOWilcox: mut increases with k")
409{
410 auto U1 = buildState(g_rho, g_u, 0, 0, g_p, 5.0, g_omega);
411 auto U2 = buildState(g_rho, g_u, 0, 0, g_p, 20.0, g_omega);
412 auto DiffU = buildShearGrad(U1, g_Sshear);
413 auto DiffU2 = buildShearGrad(U2, g_Sshear);
414
415 real mut1 = GetMut_KOWilcox<dim>(U1, DiffU, g_mu, g_d);
416 real mut2 = GetMut_KOWilcox<dim>(U2, DiffU2, g_mu, g_d);
417
418 CHECK(mut2 > mut1);
419}
420
421TEST_CASE("GetMut_SST: mut increases with k")
422{
423 auto U1 = buildState(g_rho, g_u, 0, 0, g_p, 5.0, g_omega);
424 auto U2 = buildState(g_rho, g_u, 0, 0, g_p, 20.0, g_omega);
425 auto DiffU = buildShearGrad(U1, g_Sshear);
426 auto DiffU2 = buildShearGrad(U2, g_Sshear);
427
428 real mut1 = GetMut_SST<dim>(U1, DiffU, g_mu, g_d);
429 real mut2 = GetMut_SST<dim>(U2, DiffU2, g_mu, g_d);
430
431 CHECK(mut2 > mut1);
432}
433
434// ===================================================================
435// Very small k/omega: no crash, finite result
436// ===================================================================
437
438TEST_CASE("GetMut_KOWilcox: very small k/omega produces finite mut")
439{
440 auto U = buildState(g_rho, g_u, 0, 0, g_p, 1e-10, 1e-5);
441 auto DiffU = buildShearGrad(U, 0.0);
442 real mut = GetMut_KOWilcox<dim>(U, DiffU, g_mu, g_d);
443
444 CHECK(std::isfinite(mut));
445 CHECK(mut >= 0.0);
446}
447
448TEST_CASE("GetMut_SST: very small k/omega produces finite mut")
449{
450 auto U = buildState(g_rho, g_u, 0, 0, g_p, 1e-10, 1e-5);
451 auto DiffU = buildShearGrad(U, 0.0);
452 real mut = GetMut_SST<dim>(U, DiffU, g_mu, g_d);
453
454 CHECK(std::isfinite(mut));
455 CHECK(mut >= 0.0);
456}
457
458TEST_CASE("GetMut_RealizableKe: very small k/eps produces finite mut")
459{
460 auto U = buildState(g_rho, g_u, 0, 0, g_p, 1e-10, 1e-8);
461 auto DiffU = buildShearGrad(U, 0.0);
462 real mut = GetMut_RealizableKe<dim>(U, DiffU, g_mu, g_d);
463
464 CHECK(std::isfinite(mut));
465 CHECK(mut >= 0.0);
466}
RANS two-equation turbulence model implementations for the DNDSR CFD solver.
RANS turbulence model functions (eddy viscosity, viscous flux, source terms).
Definition RANS_ke.hpp:52
the host side operators are provided as implemented
double real
Canonical floating-point scalar used throughout DNDSR (double precision).
Definition Defines.hpp:105
Eigen::Matrix< real, 5, 1 > v
CHECK(result.size()==3)
TEST_CASE("GetSource_KOWilcox mode=1: implicit diagonal non-negative")