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