PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
test_solver_kernels.c
Go to the documentation of this file.
1/**
2 * @file test_solver_kernels.c
3 * @brief C unit tests for solver-side analytical and LES helper kernels.
4 */
5
6#include "test_support.h"
7
9#include "BodyForces.h"
10#include "Filter.h"
11#include "les.h"
12#include "solvers.h"
13#include "momentumsolvers.h"
14/**
15 * @brief Tests LES test-filter helper paths for representative cases.
16 */
17
18static PetscErrorCode TestLESTestFilterPaths(void)
19{
20 SimCtx simCtx;
21 double values[3][3][3];
22 double weights[3][3][3];
23
24 PetscFunctionBeginUser;
25 PetscCall(PetscMemzero(&simCtx, sizeof(simCtx)));
26 for (PetscInt k = 0; k < 3; ++k) {
27 for (PetscInt j = 0; j < 3; ++j) {
28 for (PetscInt i = 0; i < 3; ++i) {
29 values[k][j][i] = 2.0;
30 weights[k][j][i] = 1.0;
31 }
32 }
33 }
34
35 simCtx.testfilter_ik = 1;
36 PetscCall(PicurvAssertRealNear(2.0, ApplyLESTestFilter(&simCtx, values, weights), 1.0e-12,
37 "Simpson-rule filter should preserve a constant field"));
38
39 simCtx.testfilter_ik = 0;
40 PetscCall(PicurvAssertRealNear(2.0, ApplyLESTestFilter(&simCtx, values, weights), 1.0e-12,
41 "box filter should preserve a constant field"));
42
43 for (PetscInt k = 0; k < 3; ++k) {
44 for (PetscInt j = 0; j < 3; ++j) {
45 for (PetscInt i = 0; i < 3; ++i) {
46 weights[k][j][i] = 0.0;
47 }
48 }
49 }
50 PetscCall(PicurvAssertRealNear(0.0, ApplyLESTestFilter(&simCtx, values, weights), 1.0e-12,
51 "box filter should return zero when all weights are zero"));
52 PetscFunctionReturn(0);
53}
54/**
55 * @brief Tests analytical geometry selection for supported analytical solutions.
56 */
57
58static PetscErrorCode TestAnalyticalGeometrySelection(void)
59{
60 SimCtx *simCtx = NULL;
61 UserCtx *user = NULL;
62 PetscErrorCode ierr_grid = 0;
63 PetscErrorCode ierr_non_square = 0;
64
65 PetscFunctionBeginUser;
66 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 8, 8, 8));
68 "TGV3D should require custom geometry"));
69 PetscCall(PicurvAssertBool((PetscBool)!AnalyticalTypeRequiresCustomGeometry("ZERO_FLOW"),
70 "ZERO_FLOW should not require custom geometry"));
71
72 PetscCall(PetscStrncpy(simCtx->AnalyticalSolutionType, "ZERO_FLOW", sizeof(simCtx->AnalyticalSolutionType)));
73 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
74 ierr_grid = SetAnalyticalGridInfo(user);
75 PetscCall(PetscPopErrorHandler());
76 PetscCall(PicurvAssertBool((PetscBool)(ierr_grid != 0),
77 "SetAnalyticalGridInfo should reject analytical types without custom geometry"));
78
79 PetscCall(PetscStrncpy(simCtx->AnalyticalSolutionType, "TGV3D", sizeof(simCtx->AnalyticalSolutionType)));
80 simCtx->block_number = 2;
81 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
82 ierr_non_square = SetAnalyticalGridInfo(user);
83 PetscCall(PetscPopErrorHandler());
84 PetscCall(PicurvAssertBool((PetscBool)(ierr_non_square != 0),
85 "TGV3D multi-block setup should reject non-square block counts"));
86
87 simCtx->block_number = 1;
88 PetscCall(SetAnalyticalGridInfo(user));
89 PetscCall(PicurvAssertRealNear(0.0, user->Min_X, 1.0e-12, "TGV3D single-block xmin"));
90 PetscCall(PicurvAssertRealNear(2.0 * PETSC_PI, user->Max_X, 1.0e-12, "TGV3D single-block xmax"));
91
92 simCtx->block_number = 4;
93 user->_this = 3;
94 PetscCall(SetAnalyticalGridInfo(user));
95 PetscCall(PicurvAssertRealNear(PETSC_PI, user->Min_X, 1.0e-12, "TGV3D multi-block xmin should reflect the block column"));
96 PetscCall(PicurvAssertRealNear(2.0 * PETSC_PI, user->Max_X, 1.0e-12, "TGV3D multi-block xmax should reflect the block column"));
97 PetscCall(PicurvAssertRealNear(PETSC_PI, user->Min_Y, 1.0e-12, "TGV3D multi-block ymin should reflect the block row"));
98 PetscCall(PicurvAssertRealNear(2.0 * PETSC_PI, user->Max_Y, 1.0e-12, "TGV3D multi-block ymax should reflect the block row"));
99 PetscCall(PicurvAssertRealNear(2.0 * PETSC_PI, user->Max_Z, 1.0e-12, "TGV3D multi-block zmax should span the full domain"));
100
101 simCtx->block_number = 1;
102 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
103 PetscFunctionReturn(0);
104}
105/**
106 * @brief Tests analytical scalar verification helper routines.
107 */
108
110{
111 SimCtx *simCtx = NULL;
112 UserCtx *user = NULL;
113 Vec target = NULL;
114 PetscReal value = 0.0;
115 PetscReal ***target_arr = NULL;
116 PetscReal *positions = NULL;
117 PetscReal *psi = NULL;
118
119 PetscFunctionBeginUser;
120 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
121 PetscCall(PicurvCreateSwarmPair(user, 2, "ske"));
122 simCtx->verificationScalar.enabled = PETSC_TRUE;
123 PetscCall(PetscStrncpy(simCtx->verificationScalar.mode,
124 "analytical",
125 sizeof(simCtx->verificationScalar.mode)));
126
127 PetscCall(PetscStrncpy(simCtx->verificationScalar.profile,
128 "CONSTANT",
129 sizeof(simCtx->verificationScalar.profile)));
130 simCtx->verificationScalar.value = 2.5;
131 PetscCall(EvaluateAnalyticalScalarProfile(simCtx, 0.2, 0.3, 0.4, 0.0, &value));
132 PetscCall(PicurvAssertRealNear(2.5, value, 1.0e-12,
133 "constant scalar profile should evaluate to the configured value"));
134
135 PetscCall(DMSwarmGetField(user->swarm, "position", NULL, NULL, (void **)&positions));
136 positions[0] = 0.1; positions[1] = 0.2; positions[2] = 0.3;
137 positions[3] = 0.8; positions[4] = 0.6; positions[5] = 0.4;
138 PetscCall(DMSwarmRestoreField(user->swarm, "position", NULL, NULL, (void **)&positions));
139
141 PetscCall(DMSwarmGetField(user->swarm, "Psi", NULL, NULL, (void **)&psi));
142 PetscCall(PicurvAssertRealNear(2.5, psi[0], 1.0e-12,
143 "SetAnalyticalScalarFieldOnParticles should overwrite the first particle scalar"));
144 PetscCall(PicurvAssertRealNear(2.5, psi[1], 1.0e-12,
145 "SetAnalyticalScalarFieldOnParticles should overwrite the second particle scalar"));
146 PetscCall(DMSwarmRestoreField(user->swarm, "Psi", NULL, NULL, (void **)&psi));
147
148 PetscCall(PetscStrncpy(simCtx->verificationScalar.profile,
149 "LINEAR_X",
150 sizeof(simCtx->verificationScalar.profile)));
151 simCtx->verificationScalar.phi0 = 1.0;
152 simCtx->verificationScalar.slope_x = 2.0;
153 PetscCall(EvaluateAnalyticalScalarProfile(simCtx, 0.25, 0.0, 0.0, 0.0, &value));
154 PetscCall(PicurvAssertRealNear(1.5, value, 1.0e-12,
155 "linear-x scalar profile should evaluate phi0 + slope_x * x"));
156
157 PetscCall(VecDuplicate(user->Psi, &target));
158 PetscCall(SetAnalyticalScalarFieldAtCellCenters(user, target));
159 PetscCall(DMDAVecGetArrayRead(user->da, target, &target_arr));
160 PetscCall(PicurvAssertRealNear(1.25, target_arr[1][1][1], 1.0e-12,
161 "cell-center scalar fill should use the physical x center coordinate"));
162 PetscCall(DMDAVecRestoreArrayRead(user->da, target, &target_arr));
163 PetscCall(VecDestroy(&target));
164
165 PetscCall(PetscStrncpy(simCtx->verificationScalar.profile,
166 "SIN_PRODUCT",
167 sizeof(simCtx->verificationScalar.profile)));
168 simCtx->verificationScalar.amplitude = 3.0;
169 simCtx->verificationScalar.kx = PETSC_PI;
170 simCtx->verificationScalar.ky = PETSC_PI;
171 simCtx->verificationScalar.kz = PETSC_PI;
172 PetscCall(EvaluateAnalyticalScalarProfile(simCtx, 0.5, 0.5, 0.5, 0.0, &value));
173 PetscCall(PicurvAssertRealNear(3.0, value, 1.0e-12,
174 "sin-product scalar profile should peak at pi/2 in each coordinate"));
175
176 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
177 PetscFunctionReturn(0);
178}
179/**
180 * @brief Tests analytical solution engine ZERO_FLOW, UNIFORM_FLOW, and unknown-type dispatch.
181 */
182
183static PetscErrorCode TestAnalyticalSolutionEngineDispatch(void)
184{
185 SimCtx *simCtx = NULL;
186 UserCtx *user = NULL;
187 PetscErrorCode ierr_unknown = 0;
188 Cmpnts ***ucat = NULL;
189 Cmpnts ***ubcs = NULL;
190
191 PetscFunctionBeginUser;
192 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
193 PetscCall(VecSet(user->Ucat, 3.0));
194 PetscCall(VecSet(user->P, 5.0));
195 PetscCall(VecSet(user->Bcs.Ubcs, 7.0));
196
197 PetscCall(PetscStrncpy(simCtx->AnalyticalSolutionType, "ZERO_FLOW", sizeof(simCtx->AnalyticalSolutionType)));
198 PetscCall(AnalyticalSolutionEngine(simCtx));
199 PetscCall(PicurvAssertVecConstant(user->Ucat, 0.0, 1.0e-12, "ZERO_FLOW should zero the Eulerian velocity field"));
200 PetscCall(PicurvAssertVecConstant(user->P, 0.0, 1.0e-12, "ZERO_FLOW should zero the pressure field"));
201 PetscCall(PicurvAssertVecConstant(user->Bcs.Ubcs, 0.0, 1.0e-12, "ZERO_FLOW should zero boundary-condition velocity data"));
202
203 /* UNIFORM_FLOW now works in curvilinear form (sets Ucont via metric dot products,
204 derives Ucat via Contra2Cart). The test grid needs identity metrics so the
205 transformation is invertible and Ucat recovers the physical velocity exactly. */
206 {
207 Cmpnts ***l_csi, ***l_eta, ***l_zet;
208 DMDALocalInfo linfo;
209 PetscCall(DMDAGetLocalInfo(user->fda, &linfo));
210 PetscCall(DMDAVecGetArray(user->fda, user->lCsi, &l_csi));
211 PetscCall(DMDAVecGetArray(user->fda, user->lEta, &l_eta));
212 PetscCall(DMDAVecGetArray(user->fda, user->lZet, &l_zet));
213 for (PetscInt k = linfo.zs; k < linfo.zs + linfo.zm; k++)
214 for (PetscInt j = linfo.ys; j < linfo.ys + linfo.ym; j++)
215 for (PetscInt i = linfo.xs; i < linfo.xs + linfo.xm; i++) {
216 l_csi[k][j][i] = (Cmpnts){1.0, 0.0, 0.0};
217 l_eta[k][j][i] = (Cmpnts){0.0, 1.0, 0.0};
218 l_zet[k][j][i] = (Cmpnts){0.0, 0.0, 1.0};
219 }
220 PetscCall(DMDAVecRestoreArray(user->fda, user->lZet, &l_zet));
221 PetscCall(DMDAVecRestoreArray(user->fda, user->lEta, &l_eta));
222 PetscCall(DMDAVecRestoreArray(user->fda, user->lCsi, &l_csi));
223 }
224
225 simCtx->AnalyticalUniformVelocity.x = 1.25;
226 simCtx->AnalyticalUniformVelocity.y = -0.5;
227 simCtx->AnalyticalUniformVelocity.z = 0.75;
228 PetscCall(PetscStrncpy(simCtx->AnalyticalSolutionType, "UNIFORM_FLOW", sizeof(simCtx->AnalyticalSolutionType)));
229 PetscCall(AnalyticalSolutionEngine(simCtx));
230 PetscCall(PicurvAssertVecConstant(user->P, 0.0, 1.0e-12, "UNIFORM_FLOW should keep the pressure field zero"));
231
232 Cmpnts ***ucont = NULL;
233 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucat, &ucat));
234 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucont, &ucont));
235 PetscCall(DMDAVecGetArrayRead(user->fda, user->Bcs.Ubcs, &ubcs));
236 PetscCall(PicurvAssertRealNear(1.25, ucat[1][1][1].x, 1.0e-12, "UNIFORM_FLOW should impose the configured x velocity"));
237 PetscCall(PicurvAssertRealNear(-0.5, ucat[1][1][1].y, 1.0e-12, "UNIFORM_FLOW should impose the configured y velocity"));
238 PetscCall(PicurvAssertRealNear(0.75, ucat[1][1][1].z, 1.0e-12, "UNIFORM_FLOW should impose the configured z velocity"));
239 PetscCall(PicurvAssertRealNear(1.25, ucont[1][1][1].x, 1.0e-12, "UNIFORM_FLOW should set contravariant x flux (identity metric)"));
240 PetscCall(PicurvAssertRealNear(-0.5, ucont[1][1][1].y, 1.0e-12, "UNIFORM_FLOW should set contravariant y flux (identity metric)"));
241 PetscCall(PicurvAssertRealNear(0.75, ucont[1][1][1].z, 1.0e-12, "UNIFORM_FLOW should set contravariant z flux (identity metric)"));
242 PetscCall(PicurvAssertRealNear(1.25, ubcs[0][1][1].x, 1.0e-12, "UNIFORM_FLOW should populate boundary x velocity"));
243 PetscCall(PicurvAssertRealNear(-0.5, ubcs[0][1][1].y, 1.0e-12, "UNIFORM_FLOW should populate boundary y velocity"));
244 PetscCall(PicurvAssertRealNear(0.75, ubcs[0][1][1].z, 1.0e-12, "UNIFORM_FLOW should populate boundary z velocity"));
245 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Bcs.Ubcs, &ubcs));
246 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucont, &ucont));
247 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucat, &ucat));
248
249 PetscCall(PetscStrncpy(simCtx->AnalyticalSolutionType, "NOT_A_REAL_ANALYTICAL_TYPE", sizeof(simCtx->AnalyticalSolutionType)));
250 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
251 ierr_unknown = AnalyticalSolutionEngine(simCtx);
252 PetscCall(PetscPopErrorHandler());
253 PetscCall(PicurvAssertBool((PetscBool)(ierr_unknown != 0),
254 "AnalyticalSolutionEngine should reject unknown analytical type strings"));
255
256 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
257 PetscFunctionReturn(0);
258}
259/**
260 * @brief Tests exact Taylor-Green samples on selected Eulerian interior and boundary points.
261 */
263{
264 SimCtx *simCtx = NULL;
265 UserCtx *user = NULL;
266 Cmpnts ***cent = NULL;
267 Cmpnts ***cent_x = NULL;
268 Cmpnts ***cent_y = NULL;
269 Cmpnts ***cent_z = NULL;
270 Cmpnts ***ucat = NULL;
271 Cmpnts ***ubcs = NULL;
272 PetscReal ***p = NULL;
273 const PetscReal vel_decay = PetscExpReal(-0.5);
274
275 PetscFunctionBeginUser;
276 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
277 simCtx->ren = 2.0;
278 simCtx->ti = 0.5;
279 PetscCall(PetscStrncpy(simCtx->AnalyticalSolutionType, "TGV3D", sizeof(simCtx->AnalyticalSolutionType)));
280
281 PetscCall(DMDAVecGetArray(user->fda, user->Cent, &cent));
282 PetscCall(DMDAVecGetArray(user->fda, user->lCentx, &cent_x));
283 PetscCall(DMDAVecGetArray(user->fda, user->lCenty, &cent_y));
284 PetscCall(DMDAVecGetArray(user->fda, user->lCentz, &cent_z));
285 for (PetscInt k = user->info.zs; k < user->info.zs + user->info.zm; ++k) {
286 for (PetscInt j = user->info.ys; j < user->info.ys + user->info.ym; ++j) {
287 for (PetscInt i = user->info.xs; i < user->info.xs + user->info.xm; ++i) {
288 cent[k][j][i].x = 0.0;
289 cent[k][j][i].y = 0.0;
290 cent[k][j][i].z = 0.0;
291 cent_x[k][j][i] = cent[k][j][i];
292 cent_y[k][j][i] = cent[k][j][i];
293 cent_z[k][j][i] = cent[k][j][i];
294 }
295 }
296 }
297 cent[1][1][1].x = 0.5 * PETSC_PI;
298 cent[1][1][1].y = 0.0;
299 cent[1][1][1].z = 0.0;
300 cent[1][2][1].x = 0.0;
301 cent[1][2][1].y = 0.5 * PETSC_PI;
302 cent[1][2][1].z = 0.0;
303 cent_z[0][1][1].x = 0.5 * PETSC_PI;
304 cent_z[0][1][1].y = 0.0;
305 cent_z[0][1][1].z = 0.0;
306 PetscCall(DMDAVecRestoreArray(user->fda, user->lCentz, &cent_z));
307 PetscCall(DMDAVecRestoreArray(user->fda, user->lCenty, &cent_y));
308 PetscCall(DMDAVecRestoreArray(user->fda, user->lCentx, &cent_x));
309 PetscCall(DMDAVecRestoreArray(user->fda, user->Cent, &cent));
310
311 PetscCall(AnalyticalSolutionEngine(simCtx));
312
313 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucat, &ucat));
314 PetscCall(DMDAVecGetArrayRead(user->fda, user->Bcs.Ubcs, &ubcs));
315 PetscCall(DMDAVecGetArrayRead(user->da, user->P, &p));
316 PetscCall(PicurvAssertRealNear(vel_decay, ucat[1][1][1].x, 1.0e-12, "TGV sample should set the expected interior x velocity"));
317 PetscCall(PicurvAssertRealNear(0.0, ucat[1][1][1].y, 1.0e-12, "TGV sample should keep the paired interior y velocity at zero"));
318 PetscCall(PicurvAssertRealNear(-vel_decay, ucat[1][2][1].y, 1.0e-12, "TGV sample should set the expected interior y velocity"));
319 PetscCall(PicurvAssertRealNear(0.0, p[1][1][1], 1.0e-12, "Chosen TGV sample should produce zero pressure"));
320 PetscCall(PicurvAssertRealNear(vel_decay, ubcs[0][1][1].x, 1.0e-12, "TGV sample should set the expected boundary x velocity"));
321 PetscCall(PicurvAssertRealNear(0.0, ubcs[0][1][1].y, 1.0e-12, "TGV boundary sample should keep the paired y velocity at zero"));
322 PetscCall(DMDAVecRestoreArrayRead(user->da, user->P, &p));
323 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Bcs.Ubcs, &ubcs));
324 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucat, &ucat));
325
326 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
327 PetscFunctionReturn(0);
328}
329/**
330 * @brief Tests particle analytical-solution dispatch for TGV3D, UNIFORM_FLOW, and non-analytical no-op paths.
331 */
332
334{
335 SimCtx simCtx;
336 Vec tempVec = NULL;
337 PetscReal *data = NULL;
338 const PetscReal vel_decay = PetscExpReal(-0.5);
339
340 PetscFunctionBeginUser;
341 PetscCall(PetscMemzero(&simCtx, sizeof(simCtx)));
342 simCtx.ren = 2.0;
343 simCtx.ti = 0.5;
344 PetscCall(PetscStrncpy(simCtx.AnalyticalSolutionType, "TGV3D", sizeof(simCtx.AnalyticalSolutionType)));
345
346 PetscCall(VecCreateSeq(PETSC_COMM_SELF, 6, &tempVec));
347 PetscCall(VecGetArray(tempVec, &data));
348 data[0] = 0.5 * PETSC_PI; data[1] = 0.0; data[2] = 0.0;
349 data[3] = 0.0; data[4] = 0.5 * PETSC_PI; data[5] = 0.0;
350 PetscCall(VecRestoreArray(tempVec, &data));
351
352 PetscCall(SetAnalyticalSolutionForParticles(tempVec, &simCtx));
353 PetscCall(VecGetArray(tempVec, &data));
354 PetscCall(PicurvAssertRealNear(vel_decay, data[0], 1.0e-12,
355 "TGV3D particle dispatch should populate the x velocity at x=pi/2"));
356 PetscCall(PicurvAssertRealNear(0.0, data[1], 1.0e-12,
357 "TGV3D particle dispatch should leave the first particle y velocity at zero"));
358 PetscCall(PicurvAssertRealNear(0.0, data[2], 1.0e-12,
359 "TGV3D particle dispatch should leave the first particle z velocity at zero"));
360 PetscCall(PicurvAssertRealNear(0.0, data[3], 1.0e-12,
361 "TGV3D particle dispatch should leave the second particle x velocity at zero"));
362 PetscCall(PicurvAssertRealNear(-vel_decay, data[4], 1.0e-12,
363 "TGV3D particle dispatch should populate the y velocity at y=pi/2"));
364 PetscCall(PicurvAssertRealNear(0.0, data[5], 1.0e-12,
365 "TGV3D particle dispatch should leave the second particle z velocity at zero"));
366 PetscCall(VecRestoreArray(tempVec, &data));
367
368 PetscCall(PetscStrncpy(simCtx.AnalyticalSolutionType, "ZERO_FLOW", sizeof(simCtx.AnalyticalSolutionType)));
369 PetscCall(VecGetArray(tempVec, &data));
370 data[0] = 3.0;
371 data[1] = 4.0;
372 data[2] = 5.0;
373 PetscCall(VecRestoreArray(tempVec, &data));
374 PetscCall(SetAnalyticalSolutionForParticles(tempVec, &simCtx));
375 PetscCall(VecGetArray(tempVec, &data));
376 PetscCall(PicurvAssertRealNear(3.0, data[0], 1.0e-12,
377 "Non-TGV particle dispatch should leave the vector untouched"));
378 PetscCall(PicurvAssertRealNear(4.0, data[1], 1.0e-12,
379 "Non-TGV particle dispatch should preserve the y component"));
380 PetscCall(PicurvAssertRealNear(5.0, data[2], 1.0e-12,
381 "Non-TGV particle dispatch should preserve the z component"));
382 PetscCall(VecRestoreArray(tempVec, &data));
383
384 simCtx.AnalyticalUniformVelocity.x = 0.125;
385 simCtx.AnalyticalUniformVelocity.y = -0.25;
386 simCtx.AnalyticalUniformVelocity.z = 0.375;
387 PetscCall(PetscStrncpy(simCtx.AnalyticalSolutionType, "UNIFORM_FLOW", sizeof(simCtx.AnalyticalSolutionType)));
388 PetscCall(SetAnalyticalSolutionForParticles(tempVec, &simCtx));
389 PetscCall(VecGetArray(tempVec, &data));
390 PetscCall(PicurvAssertRealNear(0.125, data[0], 1.0e-12,
391 "UNIFORM_FLOW particle dispatch should populate the x velocity"));
392 PetscCall(PicurvAssertRealNear(-0.25, data[1], 1.0e-12,
393 "UNIFORM_FLOW particle dispatch should populate the y velocity"));
394 PetscCall(PicurvAssertRealNear(0.375, data[2], 1.0e-12,
395 "UNIFORM_FLOW particle dispatch should populate the z velocity"));
396 PetscCall(PicurvAssertRealNear(0.125, data[3], 1.0e-12,
397 "UNIFORM_FLOW particle dispatch should use the same x velocity for each particle"));
398 PetscCall(PicurvAssertRealNear(-0.25, data[4], 1.0e-12,
399 "UNIFORM_FLOW particle dispatch should use the same y velocity for each particle"));
400 PetscCall(PicurvAssertRealNear(0.375, data[5], 1.0e-12,
401 "UNIFORM_FLOW particle dispatch should use the same z velocity for each particle"));
402 PetscCall(VecRestoreArray(tempVec, &data));
403
404 PetscCall(VecDestroy(&tempVec));
405 PetscFunctionReturn(0);
406}
407/**
408 * @brief Tests deterministic LES eddy-viscosity computation on a linear velocity field.
409 */
410
412{
413 SimCtx *simCtx = NULL;
414 UserCtx *user = NULL;
415 Cmpnts ***ucat = NULL;
416 PetscReal ***nu_t = NULL;
417 const PetscReal expected_nu_t = 0.25 * PetscSqrtReal(2.0);
418
419 PetscFunctionBeginUser;
420 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 5, 5, 5));
421 PetscCall(DMCreateGlobalVector(user->da, &user->Nu_t));
422 PetscCall(DMCreateLocalVector(user->da, &user->lNu_t));
423 PetscCall(DMCreateGlobalVector(user->da, &user->CS));
424 PetscCall(DMCreateLocalVector(user->da, &user->lCs));
425
426 PetscCall(VecSet(user->Aj, 1.0));
427 PetscCall(VecSet(user->Nu_t, 0.0));
428 PetscCall(VecSet(user->CS, 0.5));
429 PetscCall(DMDAVecGetArray(user->fda, user->Ucat, &ucat));
430 for (PetscInt k = user->info.zs; k < user->info.zs + user->info.zm; ++k) {
431 for (PetscInt j = user->info.ys; j < user->info.ys + user->info.ym; ++j) {
432 for (PetscInt i = user->info.xs; i < user->info.xs + user->info.xm; ++i) {
433 ucat[k][j][i].x = (PetscReal)i;
434 ucat[k][j][i].y = 0.0;
435 ucat[k][j][i].z = 0.0;
436 }
437 }
438 }
439 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucat, &ucat));
440 PetscCall(DMGlobalToLocalBegin(user->fda, user->Ucat, INSERT_VALUES, user->lUcat));
441 PetscCall(DMGlobalToLocalEnd(user->fda, user->Ucat, INSERT_VALUES, user->lUcat));
442 PetscCall(DMGlobalToLocalBegin(user->da, user->Aj, INSERT_VALUES, user->lAj));
443 PetscCall(DMGlobalToLocalEnd(user->da, user->Aj, INSERT_VALUES, user->lAj));
444 PetscCall(DMGlobalToLocalBegin(user->da, user->CS, INSERT_VALUES, user->lCs));
445 PetscCall(DMGlobalToLocalEnd(user->da, user->CS, INSERT_VALUES, user->lCs));
446
447 PetscCall(ComputeEddyViscosityLES(user));
448 PetscCall(DMDAVecGetArrayRead(user->da, user->Nu_t, &nu_t));
449 PetscCall(PicurvAssertRealNear(expected_nu_t, nu_t[2][2][2], 1.0e-6,
450 "linear velocity field should yield deterministic LES eddy viscosity"));
451 PetscCall(PicurvAssertRealNear(0.0, nu_t[0][2][2], 1.0e-12,
452 "boundary cells should remain untouched by the interior LES loop"));
453 PetscCall(DMDAVecRestoreArrayRead(user->da, user->Nu_t, &nu_t));
454
455 PetscCall(VecDestroy(&user->CS));
456 PetscCall(VecDestroy(&user->lCs));
457 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
458 PetscFunctionReturn(0);
459}
460/**
461 * @brief Tests FlowSolver guardrails for unsupported momentum solver selections.
462 */
463
465{
466 SimCtx *simCtx = NULL;
467 UserCtx *user = NULL;
468 PetscErrorCode ierr_flow = 0;
469
470 PetscFunctionBeginUser;
471 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
472 simCtx->mom_solver_type = (MomentumSolverType)999;
473
474 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
475 ierr_flow = FlowSolver(simCtx);
476 PetscCall(PetscPopErrorHandler());
477 PetscCall(PicurvAssertBool((PetscBool)(ierr_flow != 0),
478 "FlowSolver should reject unsupported momentum solver selectors"));
479
480 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
481 PetscFunctionReturn(0);
482}
483/**
484 * @brief Tests driven-channel flow source-term evaluation.
485 */
486
487static PetscErrorCode TestDrivenChannelFlowSource(void)
488{
489 SimCtx *simCtx = NULL;
490 UserCtx *user = NULL;
491 Vec rct = NULL;
492 Cmpnts ***rct_arr = NULL;
493 Cmpnts ***l_csi = NULL;
494 Cmpnts ***l_eta = NULL;
495 Cmpnts ***l_zet = NULL;
496
497 PetscFunctionBeginUser;
498 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
499 PetscCall(VecDuplicate(user->Ucont, &rct));
500 PetscCall(VecZeroEntries(rct));
501
508 simCtx->bulkVelocityCorrection = 2.0;
509 simCtx->dt = 1.0;
510 simCtx->forceScalingFactor = 1.0;
511 simCtx->drivingForceMagnitude = 0.0;
512 PetscCall(VecSet(user->Nvert, 0.0));
513 PetscCall(DMGlobalToLocalBegin(user->da, user->Nvert, INSERT_VALUES, user->lNvert));
514 PetscCall(DMGlobalToLocalEnd(user->da, user->Nvert, INSERT_VALUES, user->lNvert));
515 PetscCall(DMDAVecGetArray(user->fda, user->lCsi, &l_csi));
516 PetscCall(DMDAVecGetArray(user->fda, user->lEta, &l_eta));
517 PetscCall(DMDAVecGetArray(user->fda, user->lZet, &l_zet));
518 l_csi[1][1][1].x = 1.0; l_csi[1][1][1].y = 0.0; l_csi[1][1][1].z = 0.0;
519 l_eta[1][1][1].x = 0.0; l_eta[1][1][1].y = 1.0; l_eta[1][1][1].z = 0.0;
520 l_zet[1][1][1].x = 0.0; l_zet[1][1][1].y = 0.0; l_zet[1][1][1].z = 1.0;
521 PetscCall(DMDAVecRestoreArray(user->fda, user->lZet, &l_zet));
522 PetscCall(DMDAVecRestoreArray(user->fda, user->lEta, &l_eta));
523 PetscCall(DMDAVecRestoreArray(user->fda, user->lCsi, &l_csi));
524
525 PetscCall(ComputeDrivenChannelFlowSource(user, rct));
526 PetscCall(DMDAVecGetArrayRead(user->fda, rct, &rct_arr));
527 PetscCall(PicurvAssertRealNear(1.5, simCtx->drivingForceMagnitude, 1.0e-12,
528 "driven flow source should update the controller magnitude"));
529 PetscCall(PicurvAssertRealNear(0.0, rct_arr[1][1][1].y, 1.0e-12,
530 "driven flow source should leave the y component unchanged"));
531 PetscCall(PicurvAssertRealNear(0.0, rct_arr[1][1][1].z, 1.0e-12,
532 "driven flow source should leave the z component unchanged"));
533 PetscCall(DMDAVecRestoreArrayRead(user->fda, rct, &rct_arr));
534
535 PetscCall(VecDestroy(&rct));
536 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
537 PetscFunctionReturn(0);
538}
539/* ===================================================================== *
540 * Stage A3: momentum pseudo-time stability estimate (shadow) tests *
541 * Unit Cartesian grid via identity metrics: Csi=(1,0,0), aj=1, so the *
542 * analytic targets are exact (lambda_t=a0/dt, viscous=4*6*nu=24nu, *
543 * centered conv reduces to sum|U_f|, QUICK interior=4/3, boundary=2.5).*
544 * ===================================================================== */
545
546/**
547 * @brief Sets the mathematical BC type on all six faces of a test UserCtx.
548 * @param user Test user context.
549 * @param t BCType to assign to every face (e.g. PERIODIC, INLET).
550 */
560
561/**
562 * @brief Fills a local Cmpnts vector (ghosts included) with a uniform vector value.
563 * @param fda Vector DM owning lvec.
564 * @param lvec Local Cmpnts vector to fill.
565 * @param x,y,z Uniform component values.
566 * @return PetscErrorCode 0 on success.
567 */
568static PetscErrorCode MomFillLocalCmpnts(DM fda, Vec lvec, PetscReal x, PetscReal y, PetscReal z)
569{
570 Cmpnts ***a;
571 PetscInt gxs, gys, gzs, gxm, gym, gzm;
572 PetscFunctionBeginUser;
573 PetscCall(DMDAGetGhostCorners(fda, &gxs, &gys, &gzs, &gxm, &gym, &gzm));
574 PetscCall(DMDAVecGetArray(fda, lvec, &a));
575 for (PetscInt k = gzs; k < gzs+gzm; ++k)
576 for (PetscInt j = gys; j < gys+gym; ++j)
577 for (PetscInt i = gxs; i < gxs+gxm; ++i) { a[k][j][i].x = x; a[k][j][i].y = y; a[k][j][i].z = z; }
578 PetscCall(DMDAVecRestoreArray(fda, lvec, &a));
579 PetscFunctionReturn(0);
580}
581
582/**
583 * @brief Fills a local scalar vector (ghosts included) with a uniform value.
584 * @param da Scalar DM owning lvec.
585 * @param lvec Local scalar vector to fill.
586 * @param v Uniform value.
587 * @return PetscErrorCode 0 on success.
588 */
589static PetscErrorCode MomFillLocalScalar(DM da, Vec lvec, PetscReal v)
590{
591 PetscReal ***a;
592 PetscInt gxs, gys, gzs, gxm, gym, gzm;
593 PetscFunctionBeginUser;
594 PetscCall(DMDAGetGhostCorners(da, &gxs, &gys, &gzs, &gxm, &gym, &gzm));
595 PetscCall(DMDAVecGetArray(da, lvec, &a));
596 for (PetscInt k = gzs; k < gzs+gzm; ++k)
597 for (PetscInt j = gys; j < gys+gym; ++j)
598 for (PetscInt i = gxs; i < gxs+gxm; ++i) a[k][j][i] = v;
599 PetscCall(DMDAVecRestoreArray(da, lvec, &a));
600 PetscFunctionReturn(0);
601}
602
603/**
604 * @brief Sets a single local-scalar cell value (e.g. to mark one nvert solid).
605 * @param da Scalar DM owning lvec.
606 * @param lvec Local scalar vector.
607 * @param ci,cj,ck Cell indices to set.
608 * @param v Value to assign.
609 * @return PetscErrorCode 0 on success.
610 */
611static PetscErrorCode MomSetLocalScalarCell(DM da, Vec lvec, PetscInt ci, PetscInt cj, PetscInt ck, PetscReal v)
612{
613 PetscReal ***a;
614 PetscInt gxs, gys, gzs, gxm, gym, gzm;
615 PetscFunctionBeginUser;
616 PetscCall(DMDAGetGhostCorners(da, &gxs, &gys, &gzs, &gxm, &gym, &gzm));
617 PetscCall(DMDAVecGetArray(da, lvec, &a));
618 /* MPI-safe: only write if (ci,cj,ck) is within this rank's ghost-inclusive range. */
619 if (ci >= gxs && ci < gxs+gxm && cj >= gys && cj < gys+gym && ck >= gzs && ck < gzs+gzm)
620 a[ck][cj][ci] = v;
621 PetscCall(DMDAVecRestoreArray(da, lvec, &a));
622 PetscFunctionReturn(0);
623}
624
625/* 1. Shared BDF coefficient: old/current-only state falls back to BDF1, while
626 * a committed checkpoint with restored Ucont_rm1 retains BDF2 immediately. */
627static PetscErrorCode TestMomentumBDFCoefficient(void)
628{
629 SimCtx *simCtx = NULL; UserCtx *user = NULL;
630 PetscFunctionBeginUser;
631 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 6, 6, 6));
632 simCtx->StartStep = 0; simCtx->step = 1;
633 PetscCall(PicurvAssertRealNear(1.0, MomentumBDFCoefficient(simCtx), 1e-12, "a0 BDF1 on cold-start step 1"));
634 PetscCall(PicurvAssertBool((PetscBool)(!MomentumUsesBDF2(simCtx)), "step 1 is BDF1"));
635 simCtx->step = 5;
636 PetscCall(PicurvAssertRealNear(1.5, MomentumBDFCoefficient(simCtx), 1e-12, "a0 BDF2 on interior step"));
637 PetscCall(PicurvAssertBool(MomentumUsesBDF2(simCtx), "step 5 (StartStep 0) is BDF2"));
638 simCtx->StartStep = 5; simCtx->step = 5;
639 PetscCall(PicurvAssertRealNear(1.0, MomentumBDFCoefficient(simCtx), 1e-12, "a0 BDF1 at restart setup step"));
640 simCtx->step = 6;
641 PetscCall(PicurvAssertRealNear(1.0, MomentumBDFCoefficient(simCtx), 1e-12, "a0 BDF1 on first restart solve (ti==StartStep+1)"));
642 simCtx->step = 7;
643 PetscCall(PicurvAssertRealNear(1.5, MomentumBDFCoefficient(simCtx), 1e-12, "a0 BDF2 after first restart solve"));
644 simCtx->restartHistoryAvailable = PETSC_TRUE;
645 simCtx->step = 5;
646 PetscCall(PicurvAssertRealNear(1.5, MomentumBDFCoefficient(simCtx), 1e-12,
647 "restored history enables BDF2 at restart setup"));
648 simCtx->step = 6;
649 PetscCall(PicurvAssertRealNear(1.5, MomentumBDFCoefficient(simCtx), 1e-12,
650 "restored history enables BDF2 on first restart solve"));
651 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
652 PetscFunctionReturn(0);
653}
654
655/* Common setup with explicit per-direction DMDA periodicity. Periodic directions get real
656 wrapped ghosts (so the conservative QUICK rule sees the full stencil) and PERIODIC BC;
657 non-periodic directions get `bc`. Identity metrics; caller tweaks flags/fields. */
658static PetscErrorCode MomMakeUnitGridP(SimCtx **simCtx, UserCtx **user, PetscInt n,
659 PetscBool px, PetscBool py, PetscBool pz, BCType bc)
660{
661 PetscFunctionBeginUser;
662 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(simCtx, user, n, n, n, px, py, pz));
663 (*user)->boundary_faces[BC_FACE_NEG_X].mathematical_type = px ? PERIODIC : bc;
664 (*user)->boundary_faces[BC_FACE_POS_X].mathematical_type = px ? PERIODIC : bc;
665 (*user)->boundary_faces[BC_FACE_NEG_Y].mathematical_type = py ? PERIODIC : bc;
666 (*user)->boundary_faces[BC_FACE_POS_Y].mathematical_type = py ? PERIODIC : bc;
667 (*user)->boundary_faces[BC_FACE_NEG_Z].mathematical_type = pz ? PERIODIC : bc;
668 (*user)->boundary_faces[BC_FACE_POS_Z].mathematical_type = pz ? PERIODIC : bc;
669 (*simCtx)->dt = 0.1; (*simCtx)->step = 1; (*simCtx)->StartStep = 0; /* a0=1 -> lambda_t=10 */
670 (*simCtx)->ren = 1.0; (*simCtx)->les = 0; (*simCtx)->rans = 0;
671 (*simCtx)->central = 0; (*simCtx)->invicid = 0; (*simCtx)->block_number = 1;
672 (*simCtx)->TwoD = 0; (*simCtx)->clark = 0;
673 /* The minimal fixture does not allocate lNu_t (LES off by default); create a
674 zeroed one so the estimator can read it when a test enables LES/RANS. */
675 if (!(*user)->lNu_t) PetscCall(DMCreateLocalVector((*user)->da, &(*user)->lNu_t));
676 PetscCall(MomFillLocalCmpnts((*user)->fda, (*user)->lUcont, 0.0, 0.0, 0.0));
677 PetscCall(MomFillLocalCmpnts((*user)->fda, (*user)->lUcat, 0.0, 0.0, 0.0));
678 PetscCall(MomFillLocalScalar((*user)->da, (*user)->lNvert, 0.0));
679 PetscCall(MomFillLocalScalar((*user)->da, (*user)->lNu_t, 0.0));
680 PetscFunctionReturn(0);
681}
682
683/* Convenience: all-periodic (bc==PERIODIC) or all-non-periodic with the given BC. */
684static PetscErrorCode MomMakeUnitGrid(SimCtx **simCtx, UserCtx **user, PetscInt n, BCType bc)
685{
686 const PetscBool p = (PetscBool)(bc == PERIODIC);
687 PetscFunctionBeginUser;
688 PetscCall(MomMakeUnitGridP(simCtx, user, n, p, p, p, bc));
689 PetscFunctionReturn(0);
690}
691
692/* 2. Centered convection: f_c=1, and Aj*U^xi = u/dx scaling (identity grid -> lambda_c = sum|u|). */
694{
695 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
696 PetscFunctionBeginUser;
697 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
698 simCtx->central = 1; simCtx->invicid = 1;
699 PetscCall(MomFillLocalCmpnts(user->fda, user->lUcont, 2.0, 0.0, 0.0));
700 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
701 PetscCall(PicurvAssertRealNear(10.0, rep.lambda_t, 1e-9, "lambda_t = a0/dt"));
702 PetscCall(PicurvAssertRealNear(2.0, rep.lambda_c, 1e-9, "centered conv f_c=1 (Aj*U^xi scaling)"));
703 PetscCall(PicurvAssertRealNear(0.0, rep.lambda_v, 1e-9, "inviscid: no viscous term"));
704 PetscCall(PicurvAssertRealNear(12.0, rep.lambda, 1e-9, "total lambda"));
705 PetscCall(PicurvAssertIntEqual(0, (PetscInt)rep.cclass, "interior cell class"));
706 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
707 PetscFunctionReturn(0);
708}
709
710/* 3. Interior QUICK convective factor = 4/3. */
711static PetscErrorCode TestMomentumStabilityQuickInterior(void)
712{
713 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
714 PetscFunctionBeginUser;
715 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
716 simCtx->central = 0; simCtx->invicid = 1; /* QUICK branch */
717 PetscCall(MomFillLocalCmpnts(user->fda, user->lUcont, 3.0, 0.0, 0.0));
718 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
719 PetscCall(PicurvAssertRealNear(4.0, rep.lambda_c, 1e-9, "QUICK interior f_c=4/3 (4/3*3=4)"));
720 PetscCall(PicurvAssertIntEqual(0, (PetscInt)rep.cclass, "interior cell class"));
721 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
722 PetscFunctionReturn(0);
723}
724
725/* 4. Boundary/IB-modified QUICK convective factor = 2.5 (conservative branch class). */
726static PetscErrorCode TestMomentumStabilityQuickBoundary(void)
727{
728 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
729 PetscFunctionBeginUser;
730 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, INLET)); /* non-periodic -> boundary band active */
731 simCtx->central = 0; simCtx->invicid = 1;
732 PetscCall(MomFillLocalCmpnts(user->fda, user->lUcont, 8.0, 0.0, 0.0));
733 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
734 PetscCall(PicurvAssertRealNear(20.0, rep.lambda_c, 1e-9, "boundary QUICK f_c=2.5 (2.5*8=20)"));
735 PetscCall(PicurvAssertIntEqual(1, (PetscInt)rep.cclass, "physical-boundary cell class"));
736 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
737 PetscFunctionReturn(0);
738}
739
740/* 5. Viscous longitudinal full-stress factor: 4*6*nu = 24nu (= 8nu*(1/dx^2+1/dy^2+1/dz^2)). */
742{
743 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
744 PetscFunctionBeginUser;
745 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
746 simCtx->invicid = 0; simCtx->ren = 1.0; /* nu = 1, no convection */
747 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
748 PetscCall(PicurvAssertRealNear(24.0, rep.lambda_v, 1e-9, "viscous = 4*6*nu = 24nu (factor-8 longitudinal)"));
749 PetscCall(PicurvAssertRealNear(34.0, rep.lambda, 1e-9, "total lambda_t+lambda_v"));
750 PetscCall(PicurvAssertIntEqual((PetscInt)MOM_STAB_LIMITER_VISCOSITY, (PetscInt)rep.limiter, "viscosity-limited"));
751 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
752 PetscFunctionReturn(0);
753}
754
755/* 6. Viscous estimate scales linearly with molecular viscosity (1/Re). */
757{
758 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
759 PetscFunctionBeginUser;
760 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
761 simCtx->invicid = 0; simCtx->ren = 4.0; /* nu = 0.25 -> 24*0.25 = 6 */
762 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
763 PetscCall(PicurvAssertRealNear(6.0, rep.lambda_v, 1e-9, "viscous scales with nu=1/Re"));
764 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
765 PetscFunctionReturn(0);
766}
767
768/* 7. LES eddy viscosity adds into nu_eff,f (face average): nu_eff = 1/Re + nu_t. */
769static PetscErrorCode TestMomentumStabilityLESEddyViscosity(void)
770{
771 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
772 PetscFunctionBeginUser;
773 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
774 simCtx->invicid = 0; simCtx->ren = 1.0; simCtx->les = 1; /* nu_eff = 1 + nu_t */
775 PetscCall(MomFillLocalScalar(user->da, user->lNu_t, 1.0)); /* nu_t = 1 -> nu_eff = 2 -> 24*2 = 48 */
776 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
777 PetscCall(PicurvAssertRealNear(48.0, rep.lambda_v, 1e-9, "LES nu_t adds: 4*6*(1+1) = 48"));
778 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
779 PetscFunctionReturn(0);
780}
781
782/* 8. One-sided viscous branch near a solid: conservative x2 applied once per cell. */
783static PetscErrorCode TestMomentumStabilityOneSidedViscous(void)
784{
785 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
786 PetscFunctionBeginUser;
787 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
788 simCtx->invicid = 0; simCtx->ren = 1.0;
789 /* Mark one interior cell solid; its fluid cross-neighbours get the one-sided x2 (24 -> 48). */
790 PetscCall(MomSetLocalScalarCell(user->da, user->lNvert, 4, 5, 4, 1.0));
791 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
792 PetscCall(PicurvAssertRealNear(48.0, rep.lambda_v, 1e-9, "one-sided viscous x2 (24 -> 48)"));
793 PetscCall(PicurvAssertIntEqual(1, (PetscInt)rep.one_sided, "one-sided multiplier flagged"));
794 PetscCall(PicurvAssertIntEqual(2, (PetscInt)rep.cclass, "IB-adjacent cell class"));
795 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
796 PetscFunctionReturn(0);
797}
798
799/* 9. Read-only: the estimator must not mutate any solver field. */
800static PetscErrorCode TestMomentumStabilityReadOnly(void)
801{
802 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
803 PetscReal n_ucont_0, n_ucat_0, n_nvert_0, n_ucont_1, n_ucat_1, n_nvert_1;
804 PetscFunctionBeginUser;
805 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
806 simCtx->invicid = 0; simCtx->ren = 2.0;
807 PetscCall(MomFillLocalCmpnts(user->fda, user->lUcont, 1.5, -0.7, 0.3));
808 PetscCall(MomFillLocalCmpnts(user->fda, user->lUcat, 0.9, 0.4, -0.2));
809 PetscCall(VecNorm(user->lUcont, NORM_2, &n_ucont_0));
810 PetscCall(VecNorm(user->lUcat, NORM_2, &n_ucat_0));
811 PetscCall(VecNorm(user->lNvert, NORM_2, &n_nvert_0));
812 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_D, &rep));
813 PetscCall(VecNorm(user->lUcont, NORM_2, &n_ucont_1));
814 PetscCall(VecNorm(user->lUcat, NORM_2, &n_ucat_1));
815 PetscCall(VecNorm(user->lNvert, NORM_2, &n_nvert_1));
816 PetscCall(PicurvAssertRealNear(n_ucont_0, n_ucont_1, 1e-14, "lUcont unchanged by estimator"));
817 PetscCall(PicurvAssertRealNear(n_ucat_0, n_ucat_1, 1e-14, "lUcat unchanged by estimator"));
818 PetscCall(PicurvAssertRealNear(n_nvert_0, n_nvert_1, 1e-14, "lNvert unchanged by estimator"));
819 PetscCall(PicurvAssertBool((PetscBool)(rep.lambda > 0.0), "estimate finite and positive"));
820 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
821 PetscFunctionReturn(0);
822}
823
824/**
825 * @brief Sets uniform Cartesian metrics for spacing (dx,dy,dz) on all cell and face arrays.
826 * @param user Test user context.
827 * @param dx,dy,dz Cartesian cell spacings.
828 * @return PetscErrorCode 0 on success.
829 */
830static PetscErrorCode MomSetCartesianMetrics(UserCtx *user, PetscReal dx, PetscReal dy, PetscReal dz)
831{
832 const PetscReal sx = dy*dz, sy = dx*dz, sz = dx*dy, aj = 1.0/(dx*dy*dz);
833 Vec csiv[] = {user->lCsi, user->lICsi, user->lJCsi, user->lKCsi};
834 Vec etav[] = {user->lEta, user->lIEta, user->lJEta, user->lKEta};
835 Vec zetv[] = {user->lZet, user->lIZet, user->lJZet, user->lKZet};
836 Vec ajv[] = {user->lAj, user->lIAj, user->lJAj, user->lKAj};
837 PetscFunctionBeginUser;
838 for (int t = 0; t < 4; ++t) {
839 PetscCall(MomFillLocalCmpnts(user->fda, csiv[t], sx, 0, 0));
840 PetscCall(MomFillLocalCmpnts(user->fda, etav[t], 0, sy, 0));
841 PetscCall(MomFillLocalCmpnts(user->fda, zetv[t], 0, 0, sz));
842 PetscCall(MomFillLocalScalar(user->da, ajv[t], aj));
843 }
844 PetscFunctionReturn(0);
845}
846
847/**
848 * @brief Fills lUcont.x as a linear ramp slope*i (nonzero discrete contravariant divergence).
849 * @param fda Vector DM.
850 * @param lvec Local Cmpnts vector.
851 * @param slope Ramp slope.
852 * @return PetscErrorCode 0 on success.
853 */
854static PetscErrorCode MomFillUcontXRamp(DM fda, Vec lvec, PetscReal slope)
855{
856 Cmpnts ***a;
857 PetscInt gxs, gys, gzs, gxm, gym, gzm;
858 PetscFunctionBeginUser;
859 PetscCall(DMDAGetGhostCorners(fda, &gxs, &gys, &gzs, &gxm, &gym, &gzm));
860 PetscCall(DMDAVecGetArray(fda, lvec, &a));
861 for (PetscInt k = gzs; k < gzs+gzm; ++k)
862 for (PetscInt j = gys; j < gys+gym; ++j)
863 for (PetscInt i = gxs; i < gxs+gxm; ++i) { a[k][j][i].x = slope*i; a[k][j][i].y = 0; a[k][j][i].z = 0; }
864 PetscCall(DMDAVecRestoreArray(fda, lvec, &a));
865 PetscFunctionReturn(0);
866}
867
868/**
869 * @brief Fills lUcat as a linear shear u=(gamma*j,0,0) so |grad u|_inf = gamma.
870 * @param fda Vector DM.
871 * @param lvec Local Cmpnts vector.
872 * @param gamma Shear rate.
873 * @return PetscErrorCode 0 on success.
874 */
875static PetscErrorCode MomFillUcatShearY(DM fda, Vec lvec, PetscReal gamma)
876{
877 Cmpnts ***a;
878 PetscInt gxs, gys, gzs, gxm, gym, gzm;
879 PetscFunctionBeginUser;
880 PetscCall(DMDAGetGhostCorners(fda, &gxs, &gys, &gzs, &gxm, &gym, &gzm));
881 PetscCall(DMDAVecGetArray(fda, lvec, &a));
882 for (PetscInt k = gzs; k < gzs+gzm; ++k)
883 for (PetscInt j = gys; j < gys+gym; ++j)
884 for (PetscInt i = gxs; i < gxs+gxm; ++i) { a[k][j][i].x = gamma*j; a[k][j][i].y = 0; a[k][j][i].z = 0; }
885 PetscCall(DMDAVecRestoreArray(fda, lvec, &a));
886 PetscFunctionReturn(0);
887}
888
889/* A3.5-1. Anisotropic Cartesian viscous: detects metric-normal argument-order mistakes
890 * that the identity-metric test cannot. Target = 8nu*(1/dx^2+1/dy^2+1/dz^2). */
892{
893 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
894 PetscFunctionBeginUser;
895 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
896 simCtx->invicid = 0; simCtx->ren = 1.0;
897 PetscCall(MomSetCartesianMetrics(user, 1.0, 2.0, 4.0)); /* 8*(1 + 1/4 + 1/16) = 10.5 */
898 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
899 PetscCall(PicurvAssertRealNear(10.5, rep.lambda_v, 1e-9, "anisotropic viscous = 8nu*sum(1/d^2)"));
900 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
901 PetscFunctionReturn(0);
902}
903
904/* A3.5-2. Directional QUICK: only the boundary-modified direction gets 2.5; others stay 4/3. */
905static PetscErrorCode TestMomentumStabilityDirectionalQuick(void)
906{
907 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
908 PetscFunctionBeginUser;
909 PetscCall(MomMakeUnitGridP(&simCtx, &user, 8, PETSC_FALSE, PETSC_TRUE, PETSC_TRUE, INLET)); /* x non-periodic */
910 simCtx->central = 0; simCtx->invicid = 1;
911 PetscCall(MomFillLocalCmpnts(user->fda, user->lUcont, 3.0, 3.0, 3.0));
912 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
913 /* boundary cell: 0.5*(2.5*6 + (4/3)*6 + (4/3)*6) = 15.5 ; NOT 22.5 (all-2.5). */
914 PetscCall(PicurvAssertRealNear(15.5, rep.lambda_c, 1e-9, "only x-direction modified to 2.5"));
915 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
916 PetscFunctionReturn(0);
917}
918
919/* A3.5-3. One-sided viscous trigger by x-, y-, z-neighbor and applied only once for multiples. */
921{
922 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
923 const PetscInt offs[3][3] = {{1,0,0},{0,1,0},{0,0,1}}; /* x,y,z neighbors of (4,4,4) */
924 PetscFunctionBeginUser;
925 for (int d = 0; d < 3; ++d) {
926 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
927 simCtx->invicid = 0; simCtx->ren = 1.0;
928 PetscCall(MomSetLocalScalarCell(user->da, user->lNvert,
929 4+offs[d][0], 4+offs[d][1], 4+offs[d][2], 1.0));
930 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
931 PetscCall(PicurvAssertRealNear(48.0, rep.lambda_v, 1e-9, "single-direction one-sided x2"));
932 PetscCall(PicurvAssertIntEqual(1, (PetscInt)rep.one_sided, "one-sided flagged"));
933 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
934 }
935 /* multiple solid neighbors still apply x2 only once (48, not 96). */
936 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
937 simCtx->invicid = 0; simCtx->ren = 1.0;
938 PetscCall(MomSetLocalScalarCell(user->da, user->lNvert, 5, 4, 4, 1.0));
939 PetscCall(MomSetLocalScalarCell(user->da, user->lNvert, 4, 5, 4, 1.0));
940 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
941 PetscCall(PicurvAssertRealNear(48.0, rep.lambda_v, 1e-9, "multiple triggers -> x2 once (not 96)"));
942 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
943 PetscFunctionReturn(0);
944}
945
946/* A3.5-4. Active-row mask: a cell with an inactive normal row but active tangential rows is
947 * retained with the full estimate; a fully-inactive location is excluded. */
948static PetscErrorCode TestMomentumStabilityActiveRowMask(void)
949{
950 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
951 PetscFunctionBeginUser;
952 /* Positive non-periodic x face (i=mx-2) disables the xi row only; cell stays active. */
953 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, INLET));
954 simCtx->central = 1; simCtx->invicid = 1;
955 PetscCall(MomFillLocalCmpnts(user->fda, user->lUcont, 2.0, 2.0, 2.0));
956 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
957 PetscCall(PicurvAssertBool((PetscBool)(rep.active_cells > 0), "boundary cells remain active"));
958 PetscCall(PicurvAssertBool((PetscBool)(rep.lambda > rep.lambda_t), "tangential rows retained"));
959 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
960 /* All cells solid -> zero active -> estimate falls back to lambda_t only. */
961 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
962 simCtx->invicid = 1;
963 PetscCall(MomFillLocalScalar(user->da, user->lNvert, 1.0)); /* every cell solid */
964 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
965 PetscCall(PicurvAssertIntEqual(0, rep.active_cells, "all solid -> zero active cells"));
966 PetscCall(PicurvAssertRealNear(rep.lambda_t, rep.lambda, 1e-12, "empty active set -> lambda_t only"));
967 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
968 PetscFunctionReturn(0);
969}
970
971/* A3.5-5. Explicit input validation: invalid block_number and a non-finite metric both error. */
972static PetscErrorCode TestMomentumStabilityValidation(void)
973{
974 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
975 PetscErrorCode e1 = 0, e2 = 0;
976 PetscFunctionBeginUser;
977 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
978 simCtx->invicid = 0; simCtx->ren = 1.0;
979
980 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
981 e1 = ComputeMomentumStabilityEstimate(user, 0, simCtx->dt, MOM_STAB_CAND_C, &rep); /* block_number=0 */
982 PetscCall(PetscPopErrorHandler());
983 PetscCall(PicurvAssertBool((PetscBool)(e1 != 0), "block_number<=0 rejected"));
984
985 PetscCall(MomSetLocalScalarCell(user->da, user->lAj, 4, 4, 4, PETSC_INFINITY)); /* bad metric */
986 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
987 e2 = ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep);
988 PetscCall(PetscPopErrorHandler());
989 PetscCall(PicurvAssertBool((PetscBool)(e2 != 0), "non-finite inverse-Jacobian rejected"));
990
991 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
992 PetscFunctionReturn(0);
993}
994
995/* A3.5-6a. Candidate C exceeds B by the discrete-divergence term when div(Ucont) != 0. */
997{
998 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
999 PetscFunctionBeginUser;
1000 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
1001 simCtx->central = 1; simCtx->invicid = 1; /* f_c=1 isolates the div term */
1002 PetscCall(MomFillUcontXRamp(user->fda, user->lUcont, 2.0)); /* div = slope = 2 */
1003 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
1004 /* C - B = 0.5*Aj*|div| = 0.5*2 = 1.0 at the (shared) controlling cell. */
1005 PetscCall(PicurvAssertRealNear(1.0, rep.lambda_C - rep.lambda_B, 1e-9, "C-B = divergence term"));
1006 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1007 PetscFunctionReturn(0);
1008}
1009
1010/* A3.5-6b. Candidate D adds |grad u|_inf on a linear shear, and equals C for uniform velocity. */
1011static PetscErrorCode TestMomentumStabilityCandidateDShear(void)
1012{
1013 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
1014 PetscFunctionBeginUser;
1015 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
1016 simCtx->central = 1; simCtx->invicid = 1;
1017 PetscCall(MomFillUcatShearY(user->fda, user->lUcat, 2.0)); /* |grad u|_inf = gamma = 2 */
1018 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_D, &rep));
1019 PetscCall(PicurvAssertRealNear(2.0, rep.lambda_D - rep.lambda_C, 1e-9, "D-C = |grad u| on shear"));
1020 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1021
1022 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
1023 simCtx->central = 1; simCtx->invicid = 1;
1024 PetscCall(MomFillLocalCmpnts(user->fda, user->lUcat, 0.7, -0.3, 0.5)); /* uniform velocity */
1025 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_D, &rep));
1026 PetscCall(PicurvAssertRealNear(rep.lambda_C, rep.lambda_D, 1e-12, "D == C for uniform velocity"));
1027 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1028 PetscFunctionReturn(0);
1029}
1030
1031/* A3.5-6c. Wall faces suppress eddy viscosity exactly as in Viscous(). */
1032static PetscErrorCode TestMomentumStabilityWallSuppression(void)
1033{
1034 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
1035 PetscFunctionBeginUser;
1036 /* n=3 -> DMDA mx=4 -> interior cells {1,2}; every interior cell touches 3 wall faces
1037 (no wall-free interior cell exists, so the global max reflects suppression). */
1038 PetscCall(MomMakeUnitGrid(&simCtx, &user, 3, WALL));
1039 simCtx->invicid = 0; simCtx->ren = 1.0; simCtx->les = 1;
1040 PetscCall(MomFillLocalScalar(user->da, user->lNu_t, 1.0));
1041 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
1042 /* 3 wall faces (nu_t suppressed, nu_eff=1) + 3 interior faces (nu_eff=2): 4*(3*1+3*2)=36. */
1043 PetscCall(PicurvAssertRealNear(36.0, rep.lambda_v, 1e-9, "wall-face eddy viscosity suppressed"));
1044 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1045 PetscFunctionReturn(0);
1046}
1047
1048/* A3.5-7. Strengthened read-only: exact per-vector equality (not just norms). */
1049static PetscErrorCode TestMomentumStabilityReadOnlyExact(void)
1050{
1051 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
1052 Vec ucont0, ucat0, nvert0, nut0, aj0;
1053 PetscBool eq;
1054 PetscFunctionBeginUser;
1055 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
1056 simCtx->invicid = 0; simCtx->ren = 2.0; simCtx->les = 1;
1057 PetscCall(MomFillLocalCmpnts(user->fda, user->lUcont, 1.5, -0.7, 0.3));
1058 PetscCall(MomFillUcatShearY(user->fda, user->lUcat, 1.1));
1059 PetscCall(MomFillLocalScalar(user->da, user->lNu_t, 0.4));
1060 PetscCall(VecDuplicate(user->lUcont, &ucont0)); PetscCall(VecCopy(user->lUcont, ucont0));
1061 PetscCall(VecDuplicate(user->lUcat, &ucat0)); PetscCall(VecCopy(user->lUcat, ucat0));
1062 PetscCall(VecDuplicate(user->lNvert, &nvert0)); PetscCall(VecCopy(user->lNvert, nvert0));
1063 PetscCall(VecDuplicate(user->lNu_t, &nut0)); PetscCall(VecCopy(user->lNu_t, nut0));
1064 PetscCall(VecDuplicate(user->lAj, &aj0)); PetscCall(VecCopy(user->lAj, aj0));
1065 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_D, &rep));
1066 PetscCall(VecEqual(user->lUcont, ucont0, &eq)); PetscCall(PicurvAssertBool(eq, "lUcont bit-identical"));
1067 PetscCall(VecEqual(user->lUcat, ucat0, &eq)); PetscCall(PicurvAssertBool(eq, "lUcat bit-identical"));
1068 PetscCall(VecEqual(user->lNvert, nvert0, &eq)); PetscCall(PicurvAssertBool(eq, "lNvert bit-identical"));
1069 PetscCall(VecEqual(user->lNu_t, nut0, &eq)); PetscCall(PicurvAssertBool(eq, "lNu_t bit-identical"));
1070 PetscCall(VecEqual(user->lAj, aj0, &eq)); PetscCall(PicurvAssertBool(eq, "lAj bit-identical"));
1071 PetscCall(VecDestroy(&ucont0)); PetscCall(VecDestroy(&ucat0)); PetscCall(VecDestroy(&nvert0));
1072 PetscCall(VecDestroy(&nut0)); PetscCall(VecDestroy(&aj0));
1073 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1074 PetscFunctionReturn(0);
1075}
1076
1077/* A3.6-1. Active-row mask helper: TwoD 1/2/3 disable the right row; full mask otherwise;
1078 * skip (0) only when all three rows are inactive. Tested directly on a fixture nvert. */
1079static PetscErrorCode TestMomentumActiveRowsHelper(void)
1080{
1081 SimCtx *simCtx = NULL; UserCtx *user = NULL;
1082 PetscReal ***nvert;
1083 const PetscInt mx = 9, my = 9, mz = 9;
1084 PetscFunctionBeginUser;
1085 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC)); /* mx=9; all fluid */
1086 PetscCall(DMDAVecGetArray(user->da, user->lNvert, &nvert));
1087 /* fully periodic interior cell (4,4,4): all rows active regardless of TwoD value below. */
1088 PetscCall(PicurvAssertIntEqual(0x7, MomCellActiveRows(nvert,4,4,4,mx,my,mz,PETSC_FALSE,PETSC_FALSE,PETSC_FALSE,0), "all rows active (periodic interior)"));
1089 PetscCall(PicurvAssertIntEqual(0x6, MomCellActiveRows(nvert,4,4,4,mx,my,mz,PETSC_FALSE,PETSC_FALSE,PETSC_FALSE,1), "TwoD=1 clears xi row"));
1090 PetscCall(PicurvAssertIntEqual(0x5, MomCellActiveRows(nvert,4,4,4,mx,my,mz,PETSC_FALSE,PETSC_FALSE,PETSC_FALSE,2), "TwoD=2 clears eta row"));
1091 PetscCall(PicurvAssertIntEqual(0x3, MomCellActiveRows(nvert,4,4,4,mx,my,mz,PETSC_FALSE,PETSC_FALSE,PETSC_FALSE,3), "TwoD=3 clears zeta row"));
1092 /* positive non-periodic faces clear the corresponding normal rows. */
1093 PetscCall(PicurvAssertIntEqual(0x6, MomCellActiveRows(nvert,4,4,mx-2,mx,my,mz,PETSC_TRUE,PETSC_FALSE,PETSC_FALSE,0), "pos non-periodic xi face clears xi row"));
1094 /* a location is skipped only when all three rows are inactive (here: solid cell). */
1095 PetscCall(MomSetLocalScalarCell(user->da, user->lNvert, 4, 4, 4, 1.0));
1096 PetscCall(DMDAVecRestoreArray(user->da, user->lNvert, &nvert));
1097 PetscCall(DMDAVecGetArray(user->da, user->lNvert, &nvert));
1098 PetscCall(PicurvAssertIntEqual(0, MomCellActiveRows(nvert,4,4,4,mx,my,mz,PETSC_FALSE,PETSC_FALSE,PETSC_FALSE,0), "solid cell -> all rows inactive"));
1099 PetscCall(DMDAVecRestoreArray(user->da, user->lNvert, &nvert));
1100 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1101 PetscFunctionReturn(0);
1102}
1103
1104/* A3.6-2. Error path restores arrays and the estimator is reusable after the metric is repaired. */
1105static PetscErrorCode TestMomentumStabilityErrorReentry(void)
1106{
1107 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
1108 PetscErrorCode e_bad = 0; PetscBool eq;
1109 Vec aj_good;
1110 PetscFunctionBeginUser;
1111 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
1112 simCtx->invicid = 0; simCtx->ren = 1.0;
1113 PetscCall(VecDuplicate(user->lAj, &aj_good)); PetscCall(VecCopy(user->lAj, aj_good));
1114
1115 PetscCall(MomSetLocalScalarCell(user->da, user->lAj, 4, 4, 4, PETSC_INFINITY));
1116 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1117 e_bad = ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep);
1118 PetscCall(PetscPopErrorHandler());
1119 PetscCall(PicurvAssertBool((PetscBool)(e_bad != 0), "bad metric triggers error"));
1120
1121 /* Arrays must have been restored: lAj is still writable/usable -> repair it. */
1122 PetscCall(VecCopy(aj_good, user->lAj));
1123 PetscCall(VecEqual(user->lAj, aj_good, &eq));
1124 PetscCall(PicurvAssertBool(eq, "lAj usable after error (arrays were restored)"));
1125
1126 /* Re-entry must now succeed on the repaired state. */
1127 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
1128 PetscCall(PicurvAssertRealNear(24.0, rep.lambda_v, 1e-9, "estimator reusable after repair"));
1129 PetscCall(VecDestroy(&aj_good));
1130 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1131 PetscFunctionReturn(0);
1132}
1133
1134/* A3.6-3. Residual-level BDF combination: the shared helpers produce the exact BDF1/BDF2
1135 * temporal vector combination used by ComputeTotalResidual (a0 from the helper). */
1136static PetscErrorCode TestMomentumBDFResidualCombination(void)
1137{
1138 SimCtx *simCtx = NULL; UserCtx *user = NULL;
1139 Vec Rhs, ref; PetscReal dt, nrm;
1140 PetscFunctionBeginUser;
1141 PetscCall(MomMakeUnitGrid(&simCtx, &user, 6, PERIODIC));
1142 dt = simCtx->dt;
1143 PetscCall(VecSet(user->Ucont, 2.0)); /* U^{n} */
1144 PetscCall(VecSet(user->Ucont_o, 1.0)); /* U^{n-1} */
1145 PetscCall(VecSet(user->Ucont_rm1, 0.5)); /* U^{n-2} */
1146 PetscCall(VecDuplicate(user->Ucont, &Rhs));
1147 PetscCall(VecDuplicate(user->Ucont, &ref));
1148
1149 /* ---- BDF2 (step != 1, step != StartStep) ---- */
1150 simCtx->StartStep = 0; simCtx->step = 5;
1151 PetscCall(PicurvAssertBool(MomentumUsesBDF2(simCtx), "step5 uses BDF2"));
1152 {
1153 const PetscReal a0 = MomentumBDFCoefficient(simCtx); /* == 1.5 */
1154 PetscCall(VecSet(Rhs, 3.0)); /* mock R_spatial */
1155 PetscCall(VecAXPY(Rhs, -a0/dt, user->Ucont));
1156 PetscCall(VecAXPY(Rhs, +2.0/dt, user->Ucont_o));
1157 PetscCall(VecAXPY(Rhs, -0.5/dt, user->Ucont_rm1));
1158 /* independent reference: 3 + (-1.5*2 + 2*1 - 0.5*0.5)/dt = 3 + (-3+2-0.25)/0.1 = 3 - 12.5 = -9.5 */
1159 PetscCall(VecSet(ref, -9.5));
1160 PetscCall(VecAXPY(ref, -1.0, Rhs)); PetscCall(VecNorm(ref, NORM_INFINITY, &nrm));
1161 PetscCall(PicurvAssertRealNear(0.0, nrm, 1e-9, "BDF2 temporal vector combination"));
1162 }
1163 /* ---- BDF1 (step == 1) ---- */
1164 simCtx->StartStep = 0; simCtx->step = 1;
1165 PetscCall(PicurvAssertBool((PetscBool)(!MomentumUsesBDF2(simCtx)), "step1 uses BDF1"));
1166 {
1167 const PetscReal a0 = MomentumBDFCoefficient(simCtx); /* == 1.0 */
1168 PetscCall(VecSet(Rhs, 3.0));
1169 PetscCall(VecAXPY(Rhs, -a0/dt, user->Ucont));
1170 PetscCall(VecAXPY(Rhs, +1.0/dt, user->Ucont_o));
1171 /* reference: 3 + (-1*2 + 1*1)/0.1 = 3 + (-1)/0.1 = 3 - 10 = -7.0 */
1172 PetscCall(VecSet(ref, -7.0));
1173 PetscCall(VecAXPY(ref, -1.0, Rhs)); PetscCall(VecNorm(ref, NORM_INFINITY, &nrm));
1174 PetscCall(PicurvAssertRealNear(0.0, nrm, 1e-9, "BDF1 temporal vector combination"));
1175 }
1176 PetscCall(VecDestroy(&Rhs)); PetscCall(VecDestroy(&ref));
1177 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1178 PetscFunctionReturn(0);
1179}
1180
1181/**
1182 * @brief Runs the unit-solver PETSc test binary.
1183 */
1184
1185int main(int argc, char **argv)
1186{
1187 PetscErrorCode ierr;
1188 const PicurvTestCase cases[] = {
1189 {"les-filter-paths", TestLESTestFilterPaths},
1190 {"analytical-geometry-selection", TestAnalyticalGeometrySelection},
1191 {"analytical-scalar-verification-helpers", TestAnalyticalScalarVerificationHelpers},
1192 {"analytical-solution-engine-dispatch", TestAnalyticalSolutionEngineDispatch},
1193 {"analytical-solution-engine-taylor-green-samples", TestAnalyticalSolutionEngineTaylorGreenSamples},
1194 {"analytical-solution-for-particles-dispatch", TestAnalyticalSolutionForParticlesDispatch},
1195 {"compute-eddy-viscosity-les-deterministic-field", TestComputeEddyViscosityLESDeterministicField},
1196 {"flow-solver-rejects-unsupported-momentum-solver-type", TestFlowSolverRejectsUnsupportedMomentumSolverType},
1197 {"driven-channel-flow-source", TestDrivenChannelFlowSource},
1198 {"momentum-bdf-coefficient", TestMomentumBDFCoefficient},
1199 {"momentum-stability-centered-convection", TestMomentumStabilityCenteredConvection},
1200 {"momentum-stability-quick-interior", TestMomentumStabilityQuickInterior},
1201 {"momentum-stability-quick-boundary", TestMomentumStabilityQuickBoundary},
1202 {"momentum-stability-viscous-cartesian-factor8-estimate", TestMomentumStabilityViscousLongitudinal},
1203 {"momentum-stability-viscous-scales-with-nu", TestMomentumStabilityViscousScalesWithNu},
1204 {"momentum-stability-les-eddy-viscosity", TestMomentumStabilityLESEddyViscosity},
1205 {"momentum-stability-one-sided-viscous", TestMomentumStabilityOneSidedViscous},
1206 {"momentum-stability-read-only", TestMomentumStabilityReadOnly},
1207 {"momentum-stability-anisotropic-viscous", TestMomentumStabilityAnisotropicViscous},
1208 {"momentum-stability-directional-quick", TestMomentumStabilityDirectionalQuick},
1209 {"momentum-stability-one-sided-directions", TestMomentumStabilityOneSidedDirections},
1210 {"momentum-stability-active-row-mask", TestMomentumStabilityActiveRowMask},
1211 {"momentum-stability-validation", TestMomentumStabilityValidation},
1212 {"momentum-stability-candidate-bc-divergence", TestMomentumStabilityCandidateBCDivergence},
1213 {"momentum-stability-candidate-d-shear", TestMomentumStabilityCandidateDShear},
1214 {"momentum-stability-wall-suppression", TestMomentumStabilityWallSuppression},
1215 {"momentum-stability-read-only-exact", TestMomentumStabilityReadOnlyExact},
1216 {"momentum-active-rows-helper", TestMomentumActiveRowsHelper},
1217 {"momentum-stability-error-reentry", TestMomentumStabilityErrorReentry},
1218 {"momentum-bdf-residual-combination", TestMomentumBDFResidualCombination},
1219 };
1220
1221 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv solver utility tests");
1222 if (ierr) {
1223 return (int)ierr;
1224 }
1225
1226 ierr = PicurvRunTests("unit-solver", cases, sizeof(cases) / sizeof(cases[0]));
1227 if (ierr) {
1228 PetscFinalize();
1229 return (int)ierr;
1230 }
1231
1232 ierr = PetscFinalize();
1233 return (int)ierr;
1234}
PetscErrorCode EvaluateAnalyticalScalarProfile(const SimCtx *simCtx, PetscReal x, PetscReal y, PetscReal z, PetscReal t, PetscReal *value)
Evaluates the configured verification scalar profile at one physical point.
PetscErrorCode SetAnalyticalScalarFieldAtCellCenters(UserCtx *user, Vec targetVec)
Writes the configured verification scalar profile at physical cell centers into a scalar Vec.
PetscErrorCode SetAnalyticalScalarFieldOnParticles(UserCtx *user, ParticleFieldId particle_field_id)
Writes the configured verification scalar profile onto a particle swarm scalar field.
PetscErrorCode AnalyticalSolutionEngine(SimCtx *simCtx)
Dispatches to the appropriate analytical solution function based on simulation settings.
PetscBool AnalyticalTypeRequiresCustomGeometry(const char *analytical_type)
Reports whether an analytical type requires custom geometry/decomposition logic.
PetscErrorCode SetAnalyticalSolutionForParticles(Vec tempVec, SimCtx *simCtx)
Applies the analytical solution to particle velocity vector.
PetscErrorCode SetAnalyticalGridInfo(UserCtx *user)
Sets the grid domain and resolution for analytical solution cases.
Momentum source terms added to the contravariant RHS.
PetscErrorCode ComputeDrivenChannelFlowSource(UserCtx *user, Vec Rct)
Applies a momentum source term to drive flow in a periodic channel or pipe.
Definition BodyForces.c:14
double ApplyLESTestFilter(const SimCtx *simCtx, double values[3][3][3], double weights[3][3][3])
Applies a numerical "test filter" to a 3x3x3 stencil of data points.
Definition Filter.c:121
PetscErrorCode ComputeEddyViscosityLES(UserCtx *user)
Computes the turbulent eddy viscosity (Nu_t) for the LES model.
Definition les.c:327
@ MOM_STAB_LIMITER_VISCOSITY
MomStabLimiter limiter
PetscBool MomentumUsesBDF2(SimCtx *simCtx)
Returns whether the current physical step uses the BDF2 discretization.
PetscReal MomentumBDFCoefficient(SimCtx *simCtx)
Returns the BDF physical-time coefficient a0 for the current step.
@ MOM_STAB_CAND_C
@ MOM_STAB_CAND_D
PetscInt MomCellActiveRows(PetscReal ***nvert, PetscInt k, PetscInt j, PetscInt i, PetscInt mx, PetscInt my, PetscInt mz, PetscBool np_x1, PetscBool np_y1, PetscBool np_z1, PetscInt twoD)
Active staggered-momentum row mask for a cell (exposed for unit testing).
PetscErrorCode ComputeMomentumStabilityEstimate(UserCtx *user, PetscInt block_number, PetscReal dt, MomStabCandidate candidate, MomStabilityReport *rep)
Compute the momentum pseudo-time stability estimate (shadow/diagnostic).
Diagnostic report produced by ComputeMomentumStabilityEstimate().
@ PARTICLE_FIELD_ID_PSI
PetscErrorCode FlowSolver(SimCtx *simCtx)
Orchestrates a single time step of the Eulerian fluid solver.
Definition solvers.c:11
static PetscErrorCode TestAnalyticalSolutionEngineTaylorGreenSamples(void)
Tests exact Taylor-Green samples on selected Eulerian interior and boundary points.
static PetscErrorCode TestMomentumStabilityWallSuppression(void)
static PetscErrorCode MomSetLocalScalarCell(DM da, Vec lvec, PetscInt ci, PetscInt cj, PetscInt ck, PetscReal v)
Sets a single local-scalar cell value (e.g.
static PetscErrorCode TestMomentumActiveRowsHelper(void)
static PetscErrorCode TestComputeEddyViscosityLESDeterministicField(void)
Tests deterministic LES eddy-viscosity computation on a linear velocity field.
static PetscErrorCode TestFlowSolverRejectsUnsupportedMomentumSolverType(void)
Tests FlowSolver guardrails for unsupported momentum solver selections.
static PetscErrorCode TestMomentumStabilityOneSidedDirections(void)
int main(int argc, char **argv)
Runs the unit-solver PETSc test binary.
static PetscErrorCode TestMomentumStabilityReadOnly(void)
static PetscErrorCode TestMomentumStabilityOneSidedViscous(void)
static PetscErrorCode MomSetCartesianMetrics(UserCtx *user, PetscReal dx, PetscReal dy, PetscReal dz)
Sets uniform Cartesian metrics for spacing (dx,dy,dz) on all cell and face arrays.
static PetscErrorCode TestAnalyticalScalarVerificationHelpers(void)
Tests analytical scalar verification helper routines.
static PetscErrorCode TestMomentumStabilityReadOnlyExact(void)
static PetscErrorCode TestMomentumStabilityErrorReentry(void)
static PetscErrorCode TestMomentumStabilityActiveRowMask(void)
static PetscErrorCode TestMomentumStabilityCandidateDShear(void)
static PetscErrorCode TestMomentumStabilityViscousLongitudinal(void)
static PetscErrorCode MomMakeUnitGrid(SimCtx **simCtx, UserCtx **user, PetscInt n, BCType bc)
static PetscErrorCode TestMomentumStabilityCandidateBCDivergence(void)
static PetscErrorCode TestDrivenChannelFlowSource(void)
Tests driven-channel flow source-term evaluation.
static PetscErrorCode MomFillLocalCmpnts(DM fda, Vec lvec, PetscReal x, PetscReal y, PetscReal z)
Fills a local Cmpnts vector (ghosts included) with a uniform vector value.
static PetscErrorCode TestMomentumStabilityViscousScalesWithNu(void)
static PetscErrorCode TestMomentumStabilityAnisotropicViscous(void)
static PetscErrorCode TestAnalyticalGeometrySelection(void)
Tests analytical geometry selection for supported analytical solutions.
static PetscErrorCode TestMomentumBDFCoefficient(void)
static PetscErrorCode TestMomentumStabilityCenteredConvection(void)
static PetscErrorCode TestMomentumStabilityDirectionalQuick(void)
static PetscErrorCode TestAnalyticalSolutionForParticlesDispatch(void)
Tests particle analytical-solution dispatch for TGV3D, UNIFORM_FLOW, and non-analytical no-op paths.
static PetscErrorCode MomMakeUnitGridP(SimCtx **simCtx, UserCtx **user, PetscInt n, PetscBool px, PetscBool py, PetscBool pz, BCType bc)
static PetscErrorCode TestMomentumBDFResidualCombination(void)
static void MomSetAllBC(UserCtx *user, BCType t)
Sets the mathematical BC type on all six faces of a test UserCtx.
static PetscErrorCode MomFillLocalScalar(DM da, Vec lvec, PetscReal v)
Fills a local scalar vector (ghosts included) with a uniform value.
static PetscErrorCode TestLESTestFilterPaths(void)
Tests LES test-filter helper paths for representative cases.
static PetscErrorCode MomFillUcatShearY(DM fda, Vec lvec, PetscReal gamma)
Fills lUcat as a linear shear u=(gamma*j,0,0) so |grad u|_inf = gamma.
static PetscErrorCode TestMomentumStabilityQuickBoundary(void)
static PetscErrorCode TestMomentumStabilityLESEddyViscosity(void)
static PetscErrorCode TestMomentumStabilityQuickInterior(void)
static PetscErrorCode TestAnalyticalSolutionEngineDispatch(void)
Tests analytical solution engine ZERO_FLOW, UNIFORM_FLOW, and unknown-type dispatch.
static PetscErrorCode MomFillUcontXRamp(DM fda, Vec lvec, PetscReal slope)
Fills lUcont.x as a linear ramp slope*i (nonzero discrete contravariant divergence).
static PetscErrorCode TestMomentumStabilityValidation(void)
PetscErrorCode PicurvCreateMinimalContexts(SimCtx **simCtx_out, UserCtx **user_out, PetscInt mx, PetscInt my, PetscInt mz)
Builds minimal SimCtx and UserCtx fixtures for C unit tests.
PetscErrorCode PicurvAssertRealNear(PetscReal expected, PetscReal actual, PetscReal tol, const char *context)
Asserts that two real values agree within tolerance.
PetscErrorCode PicurvDestroyMinimalContexts(SimCtx **simCtx_ptr, UserCtx **user_ptr)
Destroys minimal SimCtx/UserCtx fixtures and all owned PETSc objects.
PetscErrorCode PicurvCreateMinimalContextsWithPeriodicity(SimCtx **simCtx_out, UserCtx **user_out, PetscInt mx, PetscInt my, PetscInt mz, PetscBool x_periodic, PetscBool y_periodic, PetscBool z_periodic)
Builds minimal SimCtx and UserCtx fixtures for C unit tests with configurable periodicity.
PetscErrorCode PicurvCreateSwarmPair(UserCtx *user, PetscInt nlocal, const char *post_field_name)
Creates matched solver and post-processing swarms for tests.
PetscErrorCode PicurvRunTests(const char *suite_name, const PicurvTestCase *cases, size_t case_count)
Runs a named C test suite and prints pass/fail progress markers.
PetscErrorCode PicurvAssertVecConstant(Vec vec, PetscScalar expected, PetscReal tol, const char *context)
Asserts that a PETSc vector is spatially constant within tolerance.
PetscErrorCode PicurvAssertIntEqual(PetscInt expected, PetscInt actual, const char *context)
Asserts that two integer values are equal.
PetscErrorCode PicurvAssertBool(PetscBool value, const char *context)
Asserts that one boolean condition is true.
Shared declarations for the PICurv C test fixture and assertion layer.
Named test case descriptor consumed by PicurvRunTests.
BCType
Defines the general mathematical/physical Category of a boundary.
Definition variables.h:283
@ INLET
Definition variables.h:290
@ PERIODIC
Definition variables.h:292
@ WALL
Definition variables.h:286
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:931
PetscInt block_number
Definition variables.h:790
Vec lIEta
Definition variables.h:977
Vec lIZet
Definition variables.h:977
Vec lNvert
Definition variables.h:939
PetscReal forceScalingFactor
Definition variables.h:801
PetscReal Min_X
Definition variables.h:921
Vec lZet
Definition variables.h:974
@ BC_HANDLER_PERIODIC_DRIVEN_CONSTANT_FLUX
Definition variables.h:318
PetscReal ren
Definition variables.h:744
BCHandlerType handler_type
Definition variables.h:369
Vec lIAj
Definition variables.h:977
PetscInt _this
Definition variables.h:924
Vec lKEta
Definition variables.h:979
PetscReal dt
Definition variables.h:710
PetscReal bulkVelocityCorrection
Definition variables.h:813
PetscReal Max_Y
Definition variables.h:921
Vec lJCsi
Definition variables.h:978
Vec lCs
Definition variables.h:982
Vec Ucont
Definition variables.h:939
PetscInt StartStep
Definition variables.h:705
Vec Ubcs
Physical Cartesian velocity at boundary faces. Full 3D array but only boundary-face entries are meani...
Definition variables.h:123
MomentumSolverType
Enumerator to identify the implemented momentum solver strategies.
Definition variables.h:534
PetscScalar x
Definition variables.h:103
BCS Bcs
Definition variables.h:934
PetscInt invicid
Definition variables.h:727
VerificationScalarConfig verificationScalar
Definition variables.h:778
Vec lKZet
Definition variables.h:979
Vec lNu_t
Definition variables.h:982
Vec Nu_t
Definition variables.h:982
Vec lJEta
Definition variables.h:978
Vec lCsi
Definition variables.h:974
Cmpnts AnalyticalUniformVelocity
Definition variables.h:760
PetscScalar z
Definition variables.h:103
Vec lKCsi
Definition variables.h:979
Vec Ucat
Definition variables.h:939
Vec Ucont_o
Definition variables.h:946
Vec lCenty
Definition variables.h:976
PetscInt central
Definition variables.h:742
Vec lJZet
Definition variables.h:978
char AnalyticalSolutionType[PETSC_MAX_PATH_LEN]
Definition variables.h:729
PetscReal Max_X
Definition variables.h:921
Vec lCentx
Definition variables.h:976
Vec Ucont_rm1
Definition variables.h:947
PetscReal Min_Y
Definition variables.h:921
Vec lUcont
Definition variables.h:939
PetscInt step
Definition variables.h:703
Vec lAj
Definition variables.h:974
Vec lICsi
Definition variables.h:977
PetscInt testfilter_ik
Definition variables.h:824
DMDALocalInfo info
Definition variables.h:918
Vec lUcat
Definition variables.h:939
PetscScalar y
Definition variables.h:103
Vec lEta
Definition variables.h:974
Vec Cent
Definition variables.h:974
PetscInt les
Definition variables.h:821
Vec Nvert
Definition variables.h:939
BCType mathematical_type
Definition variables.h:368
Vec lCentz
Definition variables.h:976
Vec lJAj
Definition variables.h:978
PetscBool restartHistoryAvailable
Definition variables.h:723
PetscReal ti
Definition variables.h:704
PetscReal Max_Z
Definition variables.h:921
MomentumSolverType mom_solver_type
Definition variables.h:736
Vec lKAj
Definition variables.h:979
PetscReal drivingForceMagnitude
Definition variables.h:801
Vec Psi
Definition variables.h:997
@ BC_FACE_NEG_X
Definition variables.h:262
@ BC_FACE_POS_Z
Definition variables.h:264
@ BC_FACE_POS_Y
Definition variables.h:263
@ BC_FACE_NEG_Z
Definition variables.h:264
@ BC_FACE_POS_X
Definition variables.h:262
@ BC_FACE_NEG_Y
Definition variables.h:263
A 3D point or vector with PetscScalar components.
Definition variables.h:102
The master context for the entire simulation.
Definition variables.h:695
User-defined context containing data specific to a single computational grid level.
Definition variables.h:906
double nu_t(double yplus)
Computes turbulent eddy viscosity ratio (ν_t / ν)