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
140 PetscCall(SetAnalyticalScalarFieldOnParticles(user, "Psi"));
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: BDF1 on cold start (ti==1) and restart (ti==tistart), BDF2 otherwise. */
626static PetscErrorCode TestMomentumBDFCoefficient(void)
627{
628 SimCtx *simCtx = NULL; UserCtx *user = NULL;
629 PetscFunctionBeginUser;
630 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 6, 6, 6));
631 simCtx->StartStep = 0; simCtx->step = 1;
632 PetscCall(PicurvAssertRealNear(1.0, MomentumBDFCoefficient(simCtx), 1e-12, "a0 BDF1 on cold-start step 1"));
633 PetscCall(PicurvAssertBool((PetscBool)(!MomentumUsesBDF2(simCtx)), "step 1 is BDF1"));
634 simCtx->step = 5;
635 PetscCall(PicurvAssertRealNear(1.5, MomentumBDFCoefficient(simCtx), 1e-12, "a0 BDF2 on interior step"));
636 PetscCall(PicurvAssertBool(MomentumUsesBDF2(simCtx), "step 5 (StartStep 0) is BDF2"));
637 simCtx->StartStep = 5; simCtx->step = 5;
638 PetscCall(PicurvAssertRealNear(1.0, MomentumBDFCoefficient(simCtx), 1e-12, "a0 BDF1 on restart step (ti==tistart)"));
639 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
640 PetscFunctionReturn(0);
641}
642
643/* Common setup with explicit per-direction DMDA periodicity. Periodic directions get real
644 wrapped ghosts (so the conservative QUICK rule sees the full stencil) and PERIODIC BC;
645 non-periodic directions get `bc`. Identity metrics; caller tweaks flags/fields. */
646static PetscErrorCode MomMakeUnitGridP(SimCtx **simCtx, UserCtx **user, PetscInt n,
647 PetscBool px, PetscBool py, PetscBool pz, BCType bc)
648{
649 PetscFunctionBeginUser;
650 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(simCtx, user, n, n, n, px, py, pz));
651 (*user)->boundary_faces[BC_FACE_NEG_X].mathematical_type = px ? PERIODIC : bc;
652 (*user)->boundary_faces[BC_FACE_POS_X].mathematical_type = px ? PERIODIC : bc;
653 (*user)->boundary_faces[BC_FACE_NEG_Y].mathematical_type = py ? PERIODIC : bc;
654 (*user)->boundary_faces[BC_FACE_POS_Y].mathematical_type = py ? PERIODIC : bc;
655 (*user)->boundary_faces[BC_FACE_NEG_Z].mathematical_type = pz ? PERIODIC : bc;
656 (*user)->boundary_faces[BC_FACE_POS_Z].mathematical_type = pz ? PERIODIC : bc;
657 (*simCtx)->dt = 0.1; (*simCtx)->step = 1; (*simCtx)->StartStep = 0; /* a0=1 -> lambda_t=10 */
658 (*simCtx)->ren = 1.0; (*simCtx)->les = 0; (*simCtx)->rans = 0;
659 (*simCtx)->central = 0; (*simCtx)->invicid = 0; (*simCtx)->block_number = 1;
660 (*simCtx)->TwoD = 0; (*simCtx)->clark = 0;
661 /* The minimal fixture does not allocate lNu_t (LES off by default); create a
662 zeroed one so the estimator can read it when a test enables LES/RANS. */
663 if (!(*user)->lNu_t) PetscCall(DMCreateLocalVector((*user)->da, &(*user)->lNu_t));
664 PetscCall(MomFillLocalCmpnts((*user)->fda, (*user)->lUcont, 0.0, 0.0, 0.0));
665 PetscCall(MomFillLocalCmpnts((*user)->fda, (*user)->lUcat, 0.0, 0.0, 0.0));
666 PetscCall(MomFillLocalScalar((*user)->da, (*user)->lNvert, 0.0));
667 PetscCall(MomFillLocalScalar((*user)->da, (*user)->lNu_t, 0.0));
668 PetscFunctionReturn(0);
669}
670
671/* Convenience: all-periodic (bc==PERIODIC) or all-non-periodic with the given BC. */
672static PetscErrorCode MomMakeUnitGrid(SimCtx **simCtx, UserCtx **user, PetscInt n, BCType bc)
673{
674 const PetscBool p = (PetscBool)(bc == PERIODIC);
675 PetscFunctionBeginUser;
676 PetscCall(MomMakeUnitGridP(simCtx, user, n, p, p, p, bc));
677 PetscFunctionReturn(0);
678}
679
680/* 2. Centered convection: f_c=1, and Aj*U^xi = u/dx scaling (identity grid -> lambda_c = sum|u|). */
682{
683 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
684 PetscFunctionBeginUser;
685 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
686 simCtx->central = 1; simCtx->invicid = 1;
687 PetscCall(MomFillLocalCmpnts(user->fda, user->lUcont, 2.0, 0.0, 0.0));
688 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
689 PetscCall(PicurvAssertRealNear(10.0, rep.lambda_t, 1e-9, "lambda_t = a0/dt"));
690 PetscCall(PicurvAssertRealNear(2.0, rep.lambda_c, 1e-9, "centered conv f_c=1 (Aj*U^xi scaling)"));
691 PetscCall(PicurvAssertRealNear(0.0, rep.lambda_v, 1e-9, "inviscid: no viscous term"));
692 PetscCall(PicurvAssertRealNear(12.0, rep.lambda, 1e-9, "total lambda"));
693 PetscCall(PicurvAssertIntEqual(0, (PetscInt)rep.cclass, "interior cell class"));
694 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
695 PetscFunctionReturn(0);
696}
697
698/* 3. Interior QUICK convective factor = 4/3. */
699static PetscErrorCode TestMomentumStabilityQuickInterior(void)
700{
701 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
702 PetscFunctionBeginUser;
703 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
704 simCtx->central = 0; simCtx->invicid = 1; /* QUICK branch */
705 PetscCall(MomFillLocalCmpnts(user->fda, user->lUcont, 3.0, 0.0, 0.0));
706 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
707 PetscCall(PicurvAssertRealNear(4.0, rep.lambda_c, 1e-9, "QUICK interior f_c=4/3 (4/3*3=4)"));
708 PetscCall(PicurvAssertIntEqual(0, (PetscInt)rep.cclass, "interior cell class"));
709 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
710 PetscFunctionReturn(0);
711}
712
713/* 4. Boundary/IB-modified QUICK convective factor = 2.5 (conservative branch class). */
714static PetscErrorCode TestMomentumStabilityQuickBoundary(void)
715{
716 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
717 PetscFunctionBeginUser;
718 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, INLET)); /* non-periodic -> boundary band active */
719 simCtx->central = 0; simCtx->invicid = 1;
720 PetscCall(MomFillLocalCmpnts(user->fda, user->lUcont, 8.0, 0.0, 0.0));
721 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
722 PetscCall(PicurvAssertRealNear(20.0, rep.lambda_c, 1e-9, "boundary QUICK f_c=2.5 (2.5*8=20)"));
723 PetscCall(PicurvAssertIntEqual(1, (PetscInt)rep.cclass, "physical-boundary cell class"));
724 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
725 PetscFunctionReturn(0);
726}
727
728/* 5. Viscous longitudinal full-stress factor: 4*6*nu = 24nu (= 8nu*(1/dx^2+1/dy^2+1/dz^2)). */
730{
731 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
732 PetscFunctionBeginUser;
733 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
734 simCtx->invicid = 0; simCtx->ren = 1.0; /* nu = 1, no convection */
735 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
736 PetscCall(PicurvAssertRealNear(24.0, rep.lambda_v, 1e-9, "viscous = 4*6*nu = 24nu (factor-8 longitudinal)"));
737 PetscCall(PicurvAssertRealNear(34.0, rep.lambda, 1e-9, "total lambda_t+lambda_v"));
738 PetscCall(PicurvAssertIntEqual((PetscInt)MOM_STAB_LIMITER_VISCOSITY, (PetscInt)rep.limiter, "viscosity-limited"));
739 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
740 PetscFunctionReturn(0);
741}
742
743/* 6. Viscous estimate scales linearly with molecular viscosity (1/Re). */
745{
746 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
747 PetscFunctionBeginUser;
748 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
749 simCtx->invicid = 0; simCtx->ren = 4.0; /* nu = 0.25 -> 24*0.25 = 6 */
750 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
751 PetscCall(PicurvAssertRealNear(6.0, rep.lambda_v, 1e-9, "viscous scales with nu=1/Re"));
752 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
753 PetscFunctionReturn(0);
754}
755
756/* 7. LES eddy viscosity adds into nu_eff,f (face average): nu_eff = 1/Re + nu_t. */
757static PetscErrorCode TestMomentumStabilityLESEddyViscosity(void)
758{
759 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
760 PetscFunctionBeginUser;
761 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
762 simCtx->invicid = 0; simCtx->ren = 1.0; simCtx->les = 1; /* nu_eff = 1 + nu_t */
763 PetscCall(MomFillLocalScalar(user->da, user->lNu_t, 1.0)); /* nu_t = 1 -> nu_eff = 2 -> 24*2 = 48 */
764 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
765 PetscCall(PicurvAssertRealNear(48.0, rep.lambda_v, 1e-9, "LES nu_t adds: 4*6*(1+1) = 48"));
766 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
767 PetscFunctionReturn(0);
768}
769
770/* 8. One-sided viscous branch near a solid: conservative x2 applied once per cell. */
771static PetscErrorCode TestMomentumStabilityOneSidedViscous(void)
772{
773 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
774 PetscFunctionBeginUser;
775 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
776 simCtx->invicid = 0; simCtx->ren = 1.0;
777 /* Mark one interior cell solid; its fluid cross-neighbours get the one-sided x2 (24 -> 48). */
778 PetscCall(MomSetLocalScalarCell(user->da, user->lNvert, 4, 5, 4, 1.0));
779 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
780 PetscCall(PicurvAssertRealNear(48.0, rep.lambda_v, 1e-9, "one-sided viscous x2 (24 -> 48)"));
781 PetscCall(PicurvAssertIntEqual(1, (PetscInt)rep.one_sided, "one-sided multiplier flagged"));
782 PetscCall(PicurvAssertIntEqual(2, (PetscInt)rep.cclass, "IB-adjacent cell class"));
783 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
784 PetscFunctionReturn(0);
785}
786
787/* 9. Read-only: the estimator must not mutate any solver field. */
788static PetscErrorCode TestMomentumStabilityReadOnly(void)
789{
790 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
791 PetscReal n_ucont_0, n_ucat_0, n_nvert_0, n_ucont_1, n_ucat_1, n_nvert_1;
792 PetscFunctionBeginUser;
793 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
794 simCtx->invicid = 0; simCtx->ren = 2.0;
795 PetscCall(MomFillLocalCmpnts(user->fda, user->lUcont, 1.5, -0.7, 0.3));
796 PetscCall(MomFillLocalCmpnts(user->fda, user->lUcat, 0.9, 0.4, -0.2));
797 PetscCall(VecNorm(user->lUcont, NORM_2, &n_ucont_0));
798 PetscCall(VecNorm(user->lUcat, NORM_2, &n_ucat_0));
799 PetscCall(VecNorm(user->lNvert, NORM_2, &n_nvert_0));
800 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_D, &rep));
801 PetscCall(VecNorm(user->lUcont, NORM_2, &n_ucont_1));
802 PetscCall(VecNorm(user->lUcat, NORM_2, &n_ucat_1));
803 PetscCall(VecNorm(user->lNvert, NORM_2, &n_nvert_1));
804 PetscCall(PicurvAssertRealNear(n_ucont_0, n_ucont_1, 1e-14, "lUcont unchanged by estimator"));
805 PetscCall(PicurvAssertRealNear(n_ucat_0, n_ucat_1, 1e-14, "lUcat unchanged by estimator"));
806 PetscCall(PicurvAssertRealNear(n_nvert_0, n_nvert_1, 1e-14, "lNvert unchanged by estimator"));
807 PetscCall(PicurvAssertBool((PetscBool)(rep.lambda > 0.0), "estimate finite and positive"));
808 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
809 PetscFunctionReturn(0);
810}
811
812/**
813 * @brief Sets uniform Cartesian metrics for spacing (dx,dy,dz) on all cell and face arrays.
814 * @param user Test user context.
815 * @param dx,dy,dz Cartesian cell spacings.
816 * @return PetscErrorCode 0 on success.
817 */
818static PetscErrorCode MomSetCartesianMetrics(UserCtx *user, PetscReal dx, PetscReal dy, PetscReal dz)
819{
820 const PetscReal sx = dy*dz, sy = dx*dz, sz = dx*dy, aj = 1.0/(dx*dy*dz);
821 Vec csiv[] = {user->lCsi, user->lICsi, user->lJCsi, user->lKCsi};
822 Vec etav[] = {user->lEta, user->lIEta, user->lJEta, user->lKEta};
823 Vec zetv[] = {user->lZet, user->lIZet, user->lJZet, user->lKZet};
824 Vec ajv[] = {user->lAj, user->lIAj, user->lJAj, user->lKAj};
825 PetscFunctionBeginUser;
826 for (int t = 0; t < 4; ++t) {
827 PetscCall(MomFillLocalCmpnts(user->fda, csiv[t], sx, 0, 0));
828 PetscCall(MomFillLocalCmpnts(user->fda, etav[t], 0, sy, 0));
829 PetscCall(MomFillLocalCmpnts(user->fda, zetv[t], 0, 0, sz));
830 PetscCall(MomFillLocalScalar(user->da, ajv[t], aj));
831 }
832 PetscFunctionReturn(0);
833}
834
835/**
836 * @brief Fills lUcont.x as a linear ramp slope*i (nonzero discrete contravariant divergence).
837 * @param fda Vector DM.
838 * @param lvec Local Cmpnts vector.
839 * @param slope Ramp slope.
840 * @return PetscErrorCode 0 on success.
841 */
842static PetscErrorCode MomFillUcontXRamp(DM fda, Vec lvec, PetscReal slope)
843{
844 Cmpnts ***a;
845 PetscInt gxs, gys, gzs, gxm, gym, gzm;
846 PetscFunctionBeginUser;
847 PetscCall(DMDAGetGhostCorners(fda, &gxs, &gys, &gzs, &gxm, &gym, &gzm));
848 PetscCall(DMDAVecGetArray(fda, lvec, &a));
849 for (PetscInt k = gzs; k < gzs+gzm; ++k)
850 for (PetscInt j = gys; j < gys+gym; ++j)
851 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; }
852 PetscCall(DMDAVecRestoreArray(fda, lvec, &a));
853 PetscFunctionReturn(0);
854}
855
856/**
857 * @brief Fills lUcat as a linear shear u=(gamma*j,0,0) so |grad u|_inf = gamma.
858 * @param fda Vector DM.
859 * @param lvec Local Cmpnts vector.
860 * @param gamma Shear rate.
861 * @return PetscErrorCode 0 on success.
862 */
863static PetscErrorCode MomFillUcatShearY(DM fda, Vec lvec, PetscReal gamma)
864{
865 Cmpnts ***a;
866 PetscInt gxs, gys, gzs, gxm, gym, gzm;
867 PetscFunctionBeginUser;
868 PetscCall(DMDAGetGhostCorners(fda, &gxs, &gys, &gzs, &gxm, &gym, &gzm));
869 PetscCall(DMDAVecGetArray(fda, lvec, &a));
870 for (PetscInt k = gzs; k < gzs+gzm; ++k)
871 for (PetscInt j = gys; j < gys+gym; ++j)
872 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; }
873 PetscCall(DMDAVecRestoreArray(fda, lvec, &a));
874 PetscFunctionReturn(0);
875}
876
877/* A3.5-1. Anisotropic Cartesian viscous: detects metric-normal argument-order mistakes
878 * that the identity-metric test cannot. Target = 8nu*(1/dx^2+1/dy^2+1/dz^2). */
880{
881 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
882 PetscFunctionBeginUser;
883 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
884 simCtx->invicid = 0; simCtx->ren = 1.0;
885 PetscCall(MomSetCartesianMetrics(user, 1.0, 2.0, 4.0)); /* 8*(1 + 1/4 + 1/16) = 10.5 */
886 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
887 PetscCall(PicurvAssertRealNear(10.5, rep.lambda_v, 1e-9, "anisotropic viscous = 8nu*sum(1/d^2)"));
888 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
889 PetscFunctionReturn(0);
890}
891
892/* A3.5-2. Directional QUICK: only the boundary-modified direction gets 2.5; others stay 4/3. */
893static PetscErrorCode TestMomentumStabilityDirectionalQuick(void)
894{
895 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
896 PetscFunctionBeginUser;
897 PetscCall(MomMakeUnitGridP(&simCtx, &user, 8, PETSC_FALSE, PETSC_TRUE, PETSC_TRUE, INLET)); /* x non-periodic */
898 simCtx->central = 0; simCtx->invicid = 1;
899 PetscCall(MomFillLocalCmpnts(user->fda, user->lUcont, 3.0, 3.0, 3.0));
900 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
901 /* boundary cell: 0.5*(2.5*6 + (4/3)*6 + (4/3)*6) = 15.5 ; NOT 22.5 (all-2.5). */
902 PetscCall(PicurvAssertRealNear(15.5, rep.lambda_c, 1e-9, "only x-direction modified to 2.5"));
903 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
904 PetscFunctionReturn(0);
905}
906
907/* A3.5-3. One-sided viscous trigger by x-, y-, z-neighbor and applied only once for multiples. */
909{
910 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
911 const PetscInt offs[3][3] = {{1,0,0},{0,1,0},{0,0,1}}; /* x,y,z neighbors of (4,4,4) */
912 PetscFunctionBeginUser;
913 for (int d = 0; d < 3; ++d) {
914 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
915 simCtx->invicid = 0; simCtx->ren = 1.0;
916 PetscCall(MomSetLocalScalarCell(user->da, user->lNvert,
917 4+offs[d][0], 4+offs[d][1], 4+offs[d][2], 1.0));
918 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
919 PetscCall(PicurvAssertRealNear(48.0, rep.lambda_v, 1e-9, "single-direction one-sided x2"));
920 PetscCall(PicurvAssertIntEqual(1, (PetscInt)rep.one_sided, "one-sided flagged"));
921 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
922 }
923 /* multiple solid neighbors still apply x2 only once (48, not 96). */
924 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
925 simCtx->invicid = 0; simCtx->ren = 1.0;
926 PetscCall(MomSetLocalScalarCell(user->da, user->lNvert, 5, 4, 4, 1.0));
927 PetscCall(MomSetLocalScalarCell(user->da, user->lNvert, 4, 5, 4, 1.0));
928 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
929 PetscCall(PicurvAssertRealNear(48.0, rep.lambda_v, 1e-9, "multiple triggers -> x2 once (not 96)"));
930 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
931 PetscFunctionReturn(0);
932}
933
934/* A3.5-4. Active-row mask: a cell with an inactive normal row but active tangential rows is
935 * retained with the full estimate; a fully-inactive location is excluded. */
936static PetscErrorCode TestMomentumStabilityActiveRowMask(void)
937{
938 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
939 PetscFunctionBeginUser;
940 /* Positive non-periodic x face (i=mx-2) disables the xi row only; cell stays active. */
941 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, INLET));
942 simCtx->central = 1; simCtx->invicid = 1;
943 PetscCall(MomFillLocalCmpnts(user->fda, user->lUcont, 2.0, 2.0, 2.0));
944 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
945 PetscCall(PicurvAssertBool((PetscBool)(rep.active_cells > 0), "boundary cells remain active"));
946 PetscCall(PicurvAssertBool((PetscBool)(rep.lambda > rep.lambda_t), "tangential rows retained"));
947 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
948 /* All cells solid -> zero active -> estimate falls back to lambda_t only. */
949 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
950 simCtx->invicid = 1;
951 PetscCall(MomFillLocalScalar(user->da, user->lNvert, 1.0)); /* every cell solid */
952 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
953 PetscCall(PicurvAssertIntEqual(0, rep.active_cells, "all solid -> zero active cells"));
954 PetscCall(PicurvAssertRealNear(rep.lambda_t, rep.lambda, 1e-12, "empty active set -> lambda_t only"));
955 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
956 PetscFunctionReturn(0);
957}
958
959/* A3.5-5. Explicit input validation: invalid block_number and a non-finite metric both error. */
960static PetscErrorCode TestMomentumStabilityValidation(void)
961{
962 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
963 PetscErrorCode e1 = 0, e2 = 0;
964 PetscFunctionBeginUser;
965 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
966 simCtx->invicid = 0; simCtx->ren = 1.0;
967
968 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
969 e1 = ComputeMomentumStabilityEstimate(user, 0, simCtx->dt, MOM_STAB_CAND_C, &rep); /* block_number=0 */
970 PetscCall(PetscPopErrorHandler());
971 PetscCall(PicurvAssertBool((PetscBool)(e1 != 0), "block_number<=0 rejected"));
972
973 PetscCall(MomSetLocalScalarCell(user->da, user->lAj, 4, 4, 4, PETSC_INFINITY)); /* bad metric */
974 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
975 e2 = ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep);
976 PetscCall(PetscPopErrorHandler());
977 PetscCall(PicurvAssertBool((PetscBool)(e2 != 0), "non-finite inverse-Jacobian rejected"));
978
979 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
980 PetscFunctionReturn(0);
981}
982
983/* A3.5-6a. Candidate C exceeds B by the discrete-divergence term when div(Ucont) != 0. */
985{
986 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
987 PetscFunctionBeginUser;
988 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
989 simCtx->central = 1; simCtx->invicid = 1; /* f_c=1 isolates the div term */
990 PetscCall(MomFillUcontXRamp(user->fda, user->lUcont, 2.0)); /* div = slope = 2 */
991 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
992 /* C - B = 0.5*Aj*|div| = 0.5*2 = 1.0 at the (shared) controlling cell. */
993 PetscCall(PicurvAssertRealNear(1.0, rep.lambda_C - rep.lambda_B, 1e-9, "C-B = divergence term"));
994 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
995 PetscFunctionReturn(0);
996}
997
998/* A3.5-6b. Candidate D adds |grad u|_inf on a linear shear, and equals C for uniform velocity. */
999static PetscErrorCode TestMomentumStabilityCandidateDShear(void)
1000{
1001 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
1002 PetscFunctionBeginUser;
1003 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
1004 simCtx->central = 1; simCtx->invicid = 1;
1005 PetscCall(MomFillUcatShearY(user->fda, user->lUcat, 2.0)); /* |grad u|_inf = gamma = 2 */
1006 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_D, &rep));
1007 PetscCall(PicurvAssertRealNear(2.0, rep.lambda_D - rep.lambda_C, 1e-9, "D-C = |grad u| on shear"));
1008 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1009
1010 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
1011 simCtx->central = 1; simCtx->invicid = 1;
1012 PetscCall(MomFillLocalCmpnts(user->fda, user->lUcat, 0.7, -0.3, 0.5)); /* uniform velocity */
1013 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_D, &rep));
1014 PetscCall(PicurvAssertRealNear(rep.lambda_C, rep.lambda_D, 1e-12, "D == C for uniform velocity"));
1015 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1016 PetscFunctionReturn(0);
1017}
1018
1019/* A3.5-6c. Wall faces suppress eddy viscosity exactly as in Viscous(). */
1020static PetscErrorCode TestMomentumStabilityWallSuppression(void)
1021{
1022 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
1023 PetscFunctionBeginUser;
1024 /* n=3 -> DMDA mx=4 -> interior cells {1,2}; every interior cell touches 3 wall faces
1025 (no wall-free interior cell exists, so the global max reflects suppression). */
1026 PetscCall(MomMakeUnitGrid(&simCtx, &user, 3, WALL));
1027 simCtx->invicid = 0; simCtx->ren = 1.0; simCtx->les = 1;
1028 PetscCall(MomFillLocalScalar(user->da, user->lNu_t, 1.0));
1029 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
1030 /* 3 wall faces (nu_t suppressed, nu_eff=1) + 3 interior faces (nu_eff=2): 4*(3*1+3*2)=36. */
1031 PetscCall(PicurvAssertRealNear(36.0, rep.lambda_v, 1e-9, "wall-face eddy viscosity suppressed"));
1032 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1033 PetscFunctionReturn(0);
1034}
1035
1036/* A3.5-7. Strengthened read-only: exact per-vector equality (not just norms). */
1037static PetscErrorCode TestMomentumStabilityReadOnlyExact(void)
1038{
1039 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
1040 Vec ucont0, ucat0, nvert0, nut0, aj0;
1041 PetscBool eq;
1042 PetscFunctionBeginUser;
1043 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
1044 simCtx->invicid = 0; simCtx->ren = 2.0; simCtx->les = 1;
1045 PetscCall(MomFillLocalCmpnts(user->fda, user->lUcont, 1.5, -0.7, 0.3));
1046 PetscCall(MomFillUcatShearY(user->fda, user->lUcat, 1.1));
1047 PetscCall(MomFillLocalScalar(user->da, user->lNu_t, 0.4));
1048 PetscCall(VecDuplicate(user->lUcont, &ucont0)); PetscCall(VecCopy(user->lUcont, ucont0));
1049 PetscCall(VecDuplicate(user->lUcat, &ucat0)); PetscCall(VecCopy(user->lUcat, ucat0));
1050 PetscCall(VecDuplicate(user->lNvert, &nvert0)); PetscCall(VecCopy(user->lNvert, nvert0));
1051 PetscCall(VecDuplicate(user->lNu_t, &nut0)); PetscCall(VecCopy(user->lNu_t, nut0));
1052 PetscCall(VecDuplicate(user->lAj, &aj0)); PetscCall(VecCopy(user->lAj, aj0));
1053 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_D, &rep));
1054 PetscCall(VecEqual(user->lUcont, ucont0, &eq)); PetscCall(PicurvAssertBool(eq, "lUcont bit-identical"));
1055 PetscCall(VecEqual(user->lUcat, ucat0, &eq)); PetscCall(PicurvAssertBool(eq, "lUcat bit-identical"));
1056 PetscCall(VecEqual(user->lNvert, nvert0, &eq)); PetscCall(PicurvAssertBool(eq, "lNvert bit-identical"));
1057 PetscCall(VecEqual(user->lNu_t, nut0, &eq)); PetscCall(PicurvAssertBool(eq, "lNu_t bit-identical"));
1058 PetscCall(VecEqual(user->lAj, aj0, &eq)); PetscCall(PicurvAssertBool(eq, "lAj bit-identical"));
1059 PetscCall(VecDestroy(&ucont0)); PetscCall(VecDestroy(&ucat0)); PetscCall(VecDestroy(&nvert0));
1060 PetscCall(VecDestroy(&nut0)); PetscCall(VecDestroy(&aj0));
1061 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1062 PetscFunctionReturn(0);
1063}
1064
1065/* A3.6-1. Active-row mask helper: TwoD 1/2/3 disable the right row; full mask otherwise;
1066 * skip (0) only when all three rows are inactive. Tested directly on a fixture nvert. */
1067static PetscErrorCode TestMomentumActiveRowsHelper(void)
1068{
1069 SimCtx *simCtx = NULL; UserCtx *user = NULL;
1070 PetscReal ***nvert;
1071 const PetscInt mx = 9, my = 9, mz = 9;
1072 PetscFunctionBeginUser;
1073 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC)); /* mx=9; all fluid */
1074 PetscCall(DMDAVecGetArray(user->da, user->lNvert, &nvert));
1075 /* fully periodic interior cell (4,4,4): all rows active regardless of TwoD value below. */
1076 PetscCall(PicurvAssertIntEqual(0x7, MomCellActiveRows(nvert,4,4,4,mx,my,mz,PETSC_FALSE,PETSC_FALSE,PETSC_FALSE,0), "all rows active (periodic interior)"));
1077 PetscCall(PicurvAssertIntEqual(0x6, MomCellActiveRows(nvert,4,4,4,mx,my,mz,PETSC_FALSE,PETSC_FALSE,PETSC_FALSE,1), "TwoD=1 clears xi row"));
1078 PetscCall(PicurvAssertIntEqual(0x5, MomCellActiveRows(nvert,4,4,4,mx,my,mz,PETSC_FALSE,PETSC_FALSE,PETSC_FALSE,2), "TwoD=2 clears eta row"));
1079 PetscCall(PicurvAssertIntEqual(0x3, MomCellActiveRows(nvert,4,4,4,mx,my,mz,PETSC_FALSE,PETSC_FALSE,PETSC_FALSE,3), "TwoD=3 clears zeta row"));
1080 /* positive non-periodic faces clear the corresponding normal rows. */
1081 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"));
1082 /* a location is skipped only when all three rows are inactive (here: solid cell). */
1083 PetscCall(MomSetLocalScalarCell(user->da, user->lNvert, 4, 4, 4, 1.0));
1084 PetscCall(DMDAVecRestoreArray(user->da, user->lNvert, &nvert));
1085 PetscCall(DMDAVecGetArray(user->da, user->lNvert, &nvert));
1086 PetscCall(PicurvAssertIntEqual(0, MomCellActiveRows(nvert,4,4,4,mx,my,mz,PETSC_FALSE,PETSC_FALSE,PETSC_FALSE,0), "solid cell -> all rows inactive"));
1087 PetscCall(DMDAVecRestoreArray(user->da, user->lNvert, &nvert));
1088 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1089 PetscFunctionReturn(0);
1090}
1091
1092/* A3.6-2. Error path restores arrays and the estimator is reusable after the metric is repaired. */
1093static PetscErrorCode TestMomentumStabilityErrorReentry(void)
1094{
1095 SimCtx *simCtx = NULL; UserCtx *user = NULL; MomStabilityReport rep;
1096 PetscErrorCode e_bad = 0; PetscBool eq;
1097 Vec aj_good;
1098 PetscFunctionBeginUser;
1099 PetscCall(MomMakeUnitGrid(&simCtx, &user, 8, PERIODIC));
1100 simCtx->invicid = 0; simCtx->ren = 1.0;
1101 PetscCall(VecDuplicate(user->lAj, &aj_good)); PetscCall(VecCopy(user->lAj, aj_good));
1102
1103 PetscCall(MomSetLocalScalarCell(user->da, user->lAj, 4, 4, 4, PETSC_INFINITY));
1104 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1105 e_bad = ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep);
1106 PetscCall(PetscPopErrorHandler());
1107 PetscCall(PicurvAssertBool((PetscBool)(e_bad != 0), "bad metric triggers error"));
1108
1109 /* Arrays must have been restored: lAj is still writable/usable -> repair it. */
1110 PetscCall(VecCopy(aj_good, user->lAj));
1111 PetscCall(VecEqual(user->lAj, aj_good, &eq));
1112 PetscCall(PicurvAssertBool(eq, "lAj usable after error (arrays were restored)"));
1113
1114 /* Re-entry must now succeed on the repaired state. */
1115 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
1116 PetscCall(PicurvAssertRealNear(24.0, rep.lambda_v, 1e-9, "estimator reusable after repair"));
1117 PetscCall(VecDestroy(&aj_good));
1118 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1119 PetscFunctionReturn(0);
1120}
1121
1122/* A3.6-3. Residual-level BDF combination: the shared helpers produce the exact BDF1/BDF2
1123 * temporal vector combination used by ComputeTotalResidual (a0 from the helper). */
1124static PetscErrorCode TestMomentumBDFResidualCombination(void)
1125{
1126 SimCtx *simCtx = NULL; UserCtx *user = NULL;
1127 Vec Rhs, ref; PetscReal dt, nrm;
1128 PetscFunctionBeginUser;
1129 PetscCall(MomMakeUnitGrid(&simCtx, &user, 6, PERIODIC));
1130 dt = simCtx->dt;
1131 PetscCall(VecSet(user->Ucont, 2.0)); /* U^{n} */
1132 PetscCall(VecSet(user->Ucont_o, 1.0)); /* U^{n-1} */
1133 PetscCall(VecSet(user->Ucont_rm1, 0.5)); /* U^{n-2} */
1134 PetscCall(VecDuplicate(user->Ucont, &Rhs));
1135 PetscCall(VecDuplicate(user->Ucont, &ref));
1136
1137 /* ---- BDF2 (step != 1, step != StartStep) ---- */
1138 simCtx->StartStep = 0; simCtx->step = 5;
1139 PetscCall(PicurvAssertBool(MomentumUsesBDF2(simCtx), "step5 uses BDF2"));
1140 {
1141 const PetscReal a0 = MomentumBDFCoefficient(simCtx); /* == 1.5 */
1142 PetscCall(VecSet(Rhs, 3.0)); /* mock R_spatial */
1143 PetscCall(VecAXPY(Rhs, -a0/dt, user->Ucont));
1144 PetscCall(VecAXPY(Rhs, +2.0/dt, user->Ucont_o));
1145 PetscCall(VecAXPY(Rhs, -0.5/dt, user->Ucont_rm1));
1146 /* 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 */
1147 PetscCall(VecSet(ref, -9.5));
1148 PetscCall(VecAXPY(ref, -1.0, Rhs)); PetscCall(VecNorm(ref, NORM_INFINITY, &nrm));
1149 PetscCall(PicurvAssertRealNear(0.0, nrm, 1e-9, "BDF2 temporal vector combination"));
1150 }
1151 /* ---- BDF1 (step == 1) ---- */
1152 simCtx->StartStep = 0; simCtx->step = 1;
1153 PetscCall(PicurvAssertBool((PetscBool)(!MomentumUsesBDF2(simCtx)), "step1 uses BDF1"));
1154 {
1155 const PetscReal a0 = MomentumBDFCoefficient(simCtx); /* == 1.0 */
1156 PetscCall(VecSet(Rhs, 3.0));
1157 PetscCall(VecAXPY(Rhs, -a0/dt, user->Ucont));
1158 PetscCall(VecAXPY(Rhs, +1.0/dt, user->Ucont_o));
1159 /* reference: 3 + (-1*2 + 1*1)/0.1 = 3 + (-1)/0.1 = 3 - 10 = -7.0 */
1160 PetscCall(VecSet(ref, -7.0));
1161 PetscCall(VecAXPY(ref, -1.0, Rhs)); PetscCall(VecNorm(ref, NORM_INFINITY, &nrm));
1162 PetscCall(PicurvAssertRealNear(0.0, nrm, 1e-9, "BDF1 temporal vector combination"));
1163 }
1164 PetscCall(VecDestroy(&Rhs)); PetscCall(VecDestroy(&ref));
1165 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1166 PetscFunctionReturn(0);
1167}
1168
1169/**
1170 * @brief Runs the unit-solver PETSc test binary.
1171 */
1172
1173int main(int argc, char **argv)
1174{
1175 PetscErrorCode ierr;
1176 const PicurvTestCase cases[] = {
1177 {"les-filter-paths", TestLESTestFilterPaths},
1178 {"analytical-geometry-selection", TestAnalyticalGeometrySelection},
1179 {"analytical-scalar-verification-helpers", TestAnalyticalScalarVerificationHelpers},
1180 {"analytical-solution-engine-dispatch", TestAnalyticalSolutionEngineDispatch},
1181 {"analytical-solution-engine-taylor-green-samples", TestAnalyticalSolutionEngineTaylorGreenSamples},
1182 {"analytical-solution-for-particles-dispatch", TestAnalyticalSolutionForParticlesDispatch},
1183 {"compute-eddy-viscosity-les-deterministic-field", TestComputeEddyViscosityLESDeterministicField},
1184 {"flow-solver-rejects-unsupported-momentum-solver-type", TestFlowSolverRejectsUnsupportedMomentumSolverType},
1185 {"driven-channel-flow-source", TestDrivenChannelFlowSource},
1186 {"momentum-bdf-coefficient", TestMomentumBDFCoefficient},
1187 {"momentum-stability-centered-convection", TestMomentumStabilityCenteredConvection},
1188 {"momentum-stability-quick-interior", TestMomentumStabilityQuickInterior},
1189 {"momentum-stability-quick-boundary", TestMomentumStabilityQuickBoundary},
1190 {"momentum-stability-viscous-cartesian-factor8-estimate", TestMomentumStabilityViscousLongitudinal},
1191 {"momentum-stability-viscous-scales-with-nu", TestMomentumStabilityViscousScalesWithNu},
1192 {"momentum-stability-les-eddy-viscosity", TestMomentumStabilityLESEddyViscosity},
1193 {"momentum-stability-one-sided-viscous", TestMomentumStabilityOneSidedViscous},
1194 {"momentum-stability-read-only", TestMomentumStabilityReadOnly},
1195 {"momentum-stability-anisotropic-viscous", TestMomentumStabilityAnisotropicViscous},
1196 {"momentum-stability-directional-quick", TestMomentumStabilityDirectionalQuick},
1197 {"momentum-stability-one-sided-directions", TestMomentumStabilityOneSidedDirections},
1198 {"momentum-stability-active-row-mask", TestMomentumStabilityActiveRowMask},
1199 {"momentum-stability-validation", TestMomentumStabilityValidation},
1200 {"momentum-stability-candidate-bc-divergence", TestMomentumStabilityCandidateBCDivergence},
1201 {"momentum-stability-candidate-d-shear", TestMomentumStabilityCandidateDShear},
1202 {"momentum-stability-wall-suppression", TestMomentumStabilityWallSuppression},
1203 {"momentum-stability-read-only-exact", TestMomentumStabilityReadOnlyExact},
1204 {"momentum-active-rows-helper", TestMomentumActiveRowsHelper},
1205 {"momentum-stability-error-reentry", TestMomentumStabilityErrorReentry},
1206 {"momentum-bdf-residual-combination", TestMomentumBDFResidualCombination},
1207 };
1208
1209 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv solver utility tests");
1210 if (ierr) {
1211 return (int)ierr;
1212 }
1213
1214 ierr = PicurvRunTests("unit-solver", cases, sizeof(cases) / sizeof(cases[0]));
1215 if (ierr) {
1216 PetscFinalize();
1217 return (int)ierr;
1218 }
1219
1220 ierr = PetscFinalize();
1221 return (int)ierr;
1222}
PetscErrorCode SetAnalyticalScalarFieldOnParticles(UserCtx *user, const char *swarm_field_name)
Writes the configured verification scalar profile onto a particle swarm scalar field.
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 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.
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:123
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().
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:281
@ INLET
Definition variables.h:288
@ PERIODIC
Definition variables.h:290
@ WALL
Definition variables.h:284
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:896
PetscInt block_number
Definition variables.h:768
Vec lIEta
Definition variables.h:930
Vec lIZet
Definition variables.h:930
Vec lNvert
Definition variables.h:904
PetscReal forceScalingFactor
Definition variables.h:779
PetscReal Min_X
Definition variables.h:886
Vec lZet
Definition variables.h:927
@ BC_HANDLER_PERIODIC_DRIVEN_CONSTANT_FLUX
Definition variables.h:316
PetscReal ren
Definition variables.h:732
BCHandlerType handler_type
Definition variables.h:367
Vec lIAj
Definition variables.h:930
PetscInt _this
Definition variables.h:889
Vec lKEta
Definition variables.h:932
PetscReal dt
Definition variables.h:699
PetscReal bulkVelocityCorrection
Definition variables.h:781
PetscReal Max_Y
Definition variables.h:886
Vec lJCsi
Definition variables.h:931
Vec lCs
Definition variables.h:935
Vec Ucont
Definition variables.h:904
PetscInt StartStep
Definition variables.h:694
Vec Ubcs
Physical Cartesian velocity at boundary faces. Full 3D array but only boundary-face entries are meani...
Definition variables.h:121
MomentumSolverType
Enumerator to identify the implemented momentum solver strategies.
Definition variables.h:532
PetscScalar x
Definition variables.h:101
BCS Bcs
Definition variables.h:899
PetscInt invicid
Definition variables.h:715
VerificationScalarConfig verificationScalar
Definition variables.h:756
Vec lKZet
Definition variables.h:932
Vec lNu_t
Definition variables.h:935
Vec Nu_t
Definition variables.h:935
Vec lJEta
Definition variables.h:931
Vec lCsi
Definition variables.h:927
Cmpnts AnalyticalUniformVelocity
Definition variables.h:748
PetscScalar z
Definition variables.h:101
Vec lKCsi
Definition variables.h:932
Vec Ucat
Definition variables.h:904
Vec Ucont_o
Definition variables.h:911
Vec lCenty
Definition variables.h:929
PetscInt central
Definition variables.h:730
Vec lJZet
Definition variables.h:931
char AnalyticalSolutionType[PETSC_MAX_PATH_LEN]
Definition variables.h:717
PetscReal Max_X
Definition variables.h:886
Vec lCentx
Definition variables.h:929
Vec Ucont_rm1
Definition variables.h:912
PetscReal Min_Y
Definition variables.h:886
Vec lUcont
Definition variables.h:904
PetscInt step
Definition variables.h:692
Vec lAj
Definition variables.h:927
Vec lICsi
Definition variables.h:930
PetscInt testfilter_ik
Definition variables.h:792
DMDALocalInfo info
Definition variables.h:883
Vec lUcat
Definition variables.h:904
PetscScalar y
Definition variables.h:101
Vec lEta
Definition variables.h:927
Vec Cent
Definition variables.h:927
PetscInt les
Definition variables.h:789
Vec Nvert
Definition variables.h:904
BCType mathematical_type
Definition variables.h:366
Vec lCentz
Definition variables.h:929
Vec lJAj
Definition variables.h:931
PetscReal ti
Definition variables.h:693
PetscReal Max_Z
Definition variables.h:886
MomentumSolverType mom_solver_type
Definition variables.h:724
Vec lKAj
Definition variables.h:932
PetscReal drivingForceMagnitude
Definition variables.h:779
Vec Psi
Definition variables.h:953
@ BC_FACE_NEG_X
Definition variables.h:260
@ BC_FACE_POS_Z
Definition variables.h:262
@ BC_FACE_POS_Y
Definition variables.h:261
@ BC_FACE_NEG_Z
Definition variables.h:262
@ BC_FACE_POS_X
Definition variables.h:260
@ BC_FACE_NEG_Y
Definition variables.h:261
A 3D point or vector with PetscScalar components.
Definition variables.h:100
The master context for the entire simulation.
Definition variables.h:684
User-defined context containing data specific to a single computational grid level.
Definition variables.h:876
double nu_t(double yplus)
Computes turbulent eddy viscosity ratio (ν_t / ν)