PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
test_setup_lifecycle.c
Go to the documentation of this file.
1/**
2 * @file test_setup_lifecycle.c
3 * @brief C unit tests for setup, initialization, and cleanup lifecycle entry points.
4 */
5
6#include "test_support.h"
7
8#include "ParticleSwarm.h"
9#include "ParticlePhysics.h"
10#include "initialcondition.h"
11#include "runloop.h"
12#include "setup.h"
13
14#include <stdio.h>
15#include <string.h>
16
17/**
18 * @brief Asserts that a directory path exists and is readable.
19 */
20static PetscErrorCode AssertDirectoryExists(const char *path, const char *context)
21{
22 PetscBool exists = PETSC_FALSE;
23
24 PetscFunctionBeginUser;
25 PetscCall(PetscTestDirectory(path, 'r', &exists));
26 PetscCall(PicurvAssertBool(exists, context));
27 PetscFunctionReturn(0);
28}
29
30/**
31 * @brief Writes one small temporary text file used by the partial-lifecycle context-only fixture.
32 */
33static PetscErrorCode WriteContextOnlyFile(const char *path, const char *contents)
34{
35 FILE *file = NULL;
36
37 PetscFunctionBeginUser;
38 file = fopen(path, "w");
39 PetscCheck(file != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to open '%s' for writing.", path);
40 fputs(contents, file);
41 fclose(file);
42 PetscFunctionReturn(0);
43}
44
45/**
46 * @brief Creates the control-file bundle needed for the context-only cleanup test.
47 */
48static PetscErrorCode PrepareContextOnlyConfig(char *tmpdir,
49 size_t tmpdir_len,
50 char *control_path,
51 size_t control_path_len)
52{
53 char bcs_path[PETSC_MAX_PATH_LEN];
54 char post_path[PETSC_MAX_PATH_LEN];
55 char output_dir[PETSC_MAX_PATH_LEN];
56 char log_dir[PETSC_MAX_PATH_LEN];
57 char control_buffer[8192];
58
59 PetscFunctionBeginUser;
60 PetscCall(PicurvMakeTempDir(tmpdir, tmpdir_len));
61 PetscCall(PetscSNPrintf(bcs_path, sizeof(bcs_path), "%s/bcs.run", tmpdir));
62 PetscCall(PetscSNPrintf(post_path, sizeof(post_path), "%s/post.run", tmpdir));
63 PetscCall(PetscSNPrintf(output_dir, sizeof(output_dir), "%s/results", tmpdir));
64 PetscCall(PetscSNPrintf(log_dir, sizeof(log_dir), "%s/logs", tmpdir));
65 PetscCall(PetscSNPrintf(control_path, control_path_len, "%s/test.control", tmpdir));
66
67 PetscCall(WriteContextOnlyFile(
68 bcs_path,
69 "-Xi WALL noslip\n"
70 "+Xi WALL noslip\n"
71 "-Eta WALL noslip\n"
72 "+Eta WALL noslip\n"
73 "-Zeta INLET constant_velocity vx=0.0 vy=0.0 vz=1.5\n"
74 "+Zeta OUTLET conservation\n"));
75 PetscCall(WriteContextOnlyFile(
76 post_path,
77 "startTime = 0\n"
78 "endTime = 1\n"
79 "timeStep = 1\n"
80 "output_particles = false\n"));
81 PetscCall(PetscSNPrintf(
82 control_buffer,
83 sizeof(control_buffer),
84 "-start_step 0\n"
85 "-totalsteps 2\n"
86 "-ren 100.0\n"
87 "-dt 0.001\n"
88 "-finit 1\n"
89 "-ucont_x 0.0\n"
90 "-ucont_y 0.0\n"
91 "-ucont_z 1.5\n"
92 "-bcs_files %s\n"
93 "-profiling_timestep_mode off\n"
94 "-profiling_final_summary true\n"
95 "-postprocessing_config_file %s\n"
96 "-grid\n"
97 "-im 6\n"
98 "-jm 6\n"
99 "-km 6\n"
100 "-xMins 0.0\n"
101 "-xMaxs 1.0\n"
102 "-yMins 0.0\n"
103 "-yMaxs 1.0\n"
104 "-zMins 0.0\n"
105 "-zMaxs 1.0\n"
106 "-rxs 1.0\n"
107 "-rys 1.0\n"
108 "-rzs 1.0\n"
109 "-cgrids 0\n"
110 "-nblk 1\n"
111 "-euler_field_source solve\n"
112 "-mom_solver_type DUALTIME_PICARD_RK4\n"
113 "-mom_dt_rk4_residual_norm_noise_allowance_factor 1.07\n"
114 "-mom_resid_atol 1.0e-8\n"
115 "-mom_resid_rtol 1.0e-3\n"
116 "-mom_nk_pic_monitor\n"
117 "-mg_level 1\n"
118 "-poisson 0\n"
119 "-tio 0\n"
120 "-particle_console_output_freq 0\n"
121 "-logfreq 1\n"
122 "-output_dir %s\n"
123 "-restart_dir %s\n"
124 /* This fixture owns its temporary tree and wants it wiped. That is an external
125 * absolute path, so it authorizes the delete explicitly - the runtime guard
126 * makes no exception for tests. */
127 "-allow_unsafe_log_dir true\n"
128 "-log_dir %s\n"
129 "-numParticles 0\n"
130 "-pinit 2\n",
131 bcs_path,
132 post_path,
133 output_dir,
134 output_dir,
135 log_dir));
136 PetscCall(WriteContextOnlyFile(control_path, control_buffer));
137 PetscFunctionReturn(0);
138}
139
140/**
141 * @brief Finalizes and frees one lifecycle test context, then clears any PETSc options used to build it.
142 */
143static PetscErrorCode FreeLifecycleContext(SimCtx **simCtx_ptr)
144{
145 PetscFunctionBeginUser;
146 PetscCall(PicurvDestroyRuntimeContext(simCtx_ptr));
147 PetscFunctionReturn(0);
148}
149
150/**
151 * @brief Builds a full setup fixture through environment, grid, BC, and domain-rank initialization.
152 */
153static PetscErrorCode BuildLifecycleContext(PetscBool enable_particles, SimCtx **simCtx_out, char *tmpdir, size_t tmpdir_len)
154{
155 PetscFunctionBeginUser;
156 PetscCall(PicurvBuildTinyRuntimeContext(NULL, enable_particles, simCtx_out, NULL, tmpdir, tmpdir_len));
157 PetscFunctionReturn(0);
158}
159
160/**
161 * @brief Builds only the top-level simulation context used by partial-initialization cleanup tests.
162 */
163static PetscErrorCode BuildContextOnly(SimCtx **simCtx_out, char *tmpdir, size_t tmpdir_len)
164{
165 char control_path[PETSC_MAX_PATH_LEN];
166 SimCtx *simCtx = NULL;
167
168 PetscFunctionBeginUser;
169 PetscCall(PetscOptionsClear(NULL));
170 PetscCall(PrepareContextOnlyConfig(tmpdir, tmpdir_len, control_path, sizeof(control_path)));
171 PetscCall(PetscOptionsSetValue(NULL, "-control_file", control_path));
172 PetscCall(CreateSimulationContext(0, NULL, &simCtx));
173 simCtx->exec_mode = EXEC_MODE_SOLVER;
174 *simCtx_out = simCtx;
175 PetscFunctionReturn(0);
176}
177
178/**
179 * @brief Tests that setup refuses every flag whose feature is planned but not implemented.
180 *
181 * Multi-block coupling, immersed boundaries, moving bodies, moving frames and the
182 * immersed-body flux corrections were all accepted before, and each ran a different
183 * problem without saying so. The conductor refuses the YAML switches; this covers the
184 * same flags arriving through a PETSc passthrough. Each flag is appended to an otherwise
185 * valid control file, so a later duplicate overrides the fixture's own value.
186 */
188{
189 const char *refused[] = {"-nblk 2", "-imm 1", "-fsi 1", "-rfsi 1",
190 "-mframe 1", "-rframe 1", "-mhv 1", "-lv 1"};
191
192 PetscFunctionBeginUser;
193 for (size_t n = 0; n < sizeof(refused) / sizeof(refused[0]); ++n) {
194 char tmpdir[PETSC_MAX_PATH_LEN];
195 char control_path[PETSC_MAX_PATH_LEN];
196 char message[128];
197 SimCtx *simCtx = NULL;
198 PetscErrorCode setup_ierr;
199 FILE *file = NULL;
200
201 PetscCall(PetscOptionsClear(NULL));
202 PetscCall(PrepareContextOnlyConfig(tmpdir, sizeof(tmpdir), control_path, sizeof(control_path)));
203 file = fopen(control_path, "a");
204 PetscCheck(file != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to reopen '%s'.", control_path);
205 fprintf(file, "%s\n", refused[n]);
206 fclose(file);
207 PetscCall(PetscOptionsSetValue(NULL, "-control_file", control_path));
208
209 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
210 setup_ierr = CreateSimulationContext(0, NULL, &simCtx);
211 PetscCall(PetscPopErrorHandler());
212 PetscCall(PetscSNPrintf(message, sizeof(message), "setup must refuse '%s'", refused[n]));
213 PetscCall(PicurvAssertIntEqual(PETSC_ERR_SUP, setup_ierr, message));
214
215 /* The refusal fires mid-parse, before any solver state exists; only the top-level
216 context has been allocated. */
217 PetscCall(PetscFree(simCtx));
218 PetscCall(PetscOptionsClear(NULL));
219 PetscCall(PicurvRemoveTempDir(tmpdir));
220 }
221 PetscFunctionReturn(0);
222}
223
224/**
225 * @brief Builds a context from the fixture control file with extra lines appended.
226 * @param[in] extra_lines Control-file lines appended after the fixture's own.
227 * @param[out] simCtx_out Context on success; the partially built context on failure.
228 * @param[out] ierr_out CreateSimulationContext's return code, errors suppressed.
229 * @param[out] tmpdir Fixture directory, removed by the caller.
230 * @param[in] tmpdir_len Capacity of `tmpdir`.
231 */
232static PetscErrorCode TryContextWithExtraLines(const char *extra_lines, SimCtx **simCtx_out,
233 PetscErrorCode *ierr_out, char *tmpdir,
234 size_t tmpdir_len)
235{
236 char control_path[PETSC_MAX_PATH_LEN];
237 FILE *file = NULL;
238
239 PetscFunctionBeginUser;
240 PetscCall(PetscOptionsClear(NULL));
241 PetscCall(PrepareContextOnlyConfig(tmpdir, tmpdir_len, control_path, sizeof(control_path)));
242 file = fopen(control_path, "a");
243 PetscCheck(file != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to reopen '%s'.", control_path);
244 fputs(extra_lines, file);
245 fclose(file);
246 PetscCall(PetscOptionsSetValue(NULL, "-control_file", control_path));
247 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
248 *ierr_out = CreateSimulationContext(0, NULL, simCtx_out);
249 PetscCall(PetscPopErrorHandler());
250 PetscFunctionReturn(0);
251}
252
253/**
254 * @brief Tests that the IEM mixing constant defaults to 2.0, follows -iem_constant,
255 * accepts zero (no micromixing), and refuses a negative or non-finite value.
256 */
257static PetscErrorCode TestIEMConstantIsConfigurable(void)
258{
259 const char *cases[] = {"", "-iem_constant 3.5\n", "-iem_constant 0\n", "-iem_constant -1\n",
260 "-iem_constant nan\n", "-iem_constant inf\n"};
261 const PetscReal expected[] = {2.0, 3.5, 0.0, -1.0, -1.0, -1.0}; /* -1: refused */
262
263 PetscFunctionBeginUser;
264 for (size_t n = 0; n < sizeof(cases) / sizeof(cases[0]); ++n) {
265 char tmpdir[PETSC_MAX_PATH_LEN];
266 SimCtx *simCtx = NULL;
267 PetscErrorCode setup_ierr = 0;
268
269 PetscCall(TryContextWithExtraLines(cases[n], &simCtx, &setup_ierr, tmpdir, sizeof(tmpdir)));
270 if (expected[n] >= 0.0) {
271 PetscCall(PicurvAssertIntEqual(0, setup_ierr, "a non-negative -iem_constant must be accepted"));
272 PetscCall(PicurvAssertRealNear(expected[n], simCtx->iem_constant, 0.0,
273 "the IEM constant must follow -iem_constant, default 2.0"));
274 } else {
275 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_OUTOFRANGE, setup_ierr,
276 "a negative or non-finite -iem_constant must be refused"));
277 }
278 PetscCall(FreeLifecycleContext(&simCtx));
279 PetscCall(PetscOptionsClear(NULL));
280 PetscCall(PicurvRemoveTempDir(tmpdir));
281 }
282 PetscFunctionReturn(0);
283}
284
285/**
286 * @brief Ensures a run that solves no pressure builds one grid level whatever -mg_level asks,
287 * and a solved flow keeps the levels it asks for.
288 */
289static PetscErrorCode TestUnsolvedFlowUsesOneGridLevel(void)
290{
291 const char *cases[] = {"-mg_level 3\n", "-mg_level 3\n-euler_field_source analytical\n",
292 "-mg_level 3\n-euler_field_source load\n"};
293 const PetscInt expected[] = {3, 1, 1};
294
295 PetscFunctionBeginUser;
296 for (size_t n = 0; n < sizeof(cases) / sizeof(cases[0]); ++n) {
297 char tmpdir[PETSC_MAX_PATH_LEN];
298 SimCtx *simCtx = NULL;
299 PetscErrorCode setup_ierr = 0;
300
301 PetscCall(TryContextWithExtraLines(cases[n], &simCtx, &setup_ierr, tmpdir, sizeof(tmpdir)));
302 PetscCall(PicurvAssertIntEqual(0, setup_ierr, "the multigrid level options must be accepted"));
303 PetscCall(PicurvAssertIntEqual(expected[n], simCtx->mglevels,
304 "only a solved flow keeps its coarse grid levels"));
305 PetscCall(FreeLifecycleContext(&simCtx));
306 PetscCall(PetscOptionsClear(NULL));
307 PetscCall(PicurvRemoveTempDir(tmpdir));
308 }
309 PetscFunctionReturn(0);
310}
311
312/**
313 * @brief Ensures setup refuses a particle count whose widest swarm field PetscInt cannot index.
314 * @details Particle fields have up to three components, so the limit is PETSC_MAX_INT / 3
315 * particles; with 64-bit indices no representable -numParticles reaches it.
316 */
317static PetscErrorCode TestParticleCountMustFitPetscInt(void)
318{
319 char tmpdir[PETSC_MAX_PATH_LEN], line[64];
320 SimCtx *simCtx = NULL;
321 PetscErrorCode setup_ierr = 0;
322
323 PetscFunctionBeginUser;
324 if (sizeof(PetscInt) > 4) PetscFunctionReturn(0);
325 PetscCall(PetscSNPrintf(line, sizeof(line), "-numParticles %" PetscInt_FMT "\n", (PetscInt)(PETSC_MAX_INT / 3) + 1));
326 PetscCall(TryContextWithExtraLines(line, &simCtx, &setup_ierr, tmpdir, sizeof(tmpdir)));
327 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_OUTOFRANGE, setup_ierr,
328 "a particle count past PETSC_MAX_INT / 3 must be refused with 32-bit indices"));
329 PetscCall(FreeLifecycleContext(&simCtx));
330 PetscCall(PetscOptionsClear(NULL));
331 PetscCall(PicurvRemoveTempDir(tmpdir));
332 PetscFunctionReturn(0);
333}
334
335/**
336 * @brief Ensures setup propagates invalid post intervals before either executable can loop.
337 */
339{
340 const char *options[] = {"-timeStep 0\n", "-timeStep -1\n"};
341
342 PetscFunctionBeginUser;
343 for (size_t n = 0; n < sizeof(options) / sizeof(options[0]); ++n) {
344 SimCtx *simCtx = NULL;
345 char tmpdir[PETSC_MAX_PATH_LEN];
346 PetscErrorCode setup_ierr;
347 PetscCall(TryContextWithExtraLines(options[n], &simCtx, &setup_ierr, tmpdir, sizeof(tmpdir)));
348 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_OUTOFRANGE, setup_ierr,
349 "setup must propagate rejection of a nonpositive post interval"));
350 PetscCall(FreeLifecycleContext(&simCtx));
351 PetscCall(PicurvRemoveTempDir(tmpdir));
352 }
353 PetscFunctionReturn(0);
354}
355
356/**
357 * @brief Checks a configured particle initial value through the production setup path.
358 * @details `Psi = x + 2*pid` is read from the options, compiled, and applied once positions
359 * are final; it varies across particles wherever they are seeded. Particles fill the
360 * volume, so cells next to every face are occupied. Every particle must carry
361 * exactly its value, and the t=0 scatter must already reflect it: the
362 * count-weighted Eulerian `Psi` sums to the particles' total, so the first IEM
363 * update relaxes toward the right mean, and each non-periodic face's dummy cells
364 * carry the adjacent cell's mean.
365 */
366static PetscErrorCode TestConfiguredParticleInitialValue(void)
367{
368 const char *options = "-numParticles 400\n"
369 "-pinit 1\n"
370 "-particle_fields_count 1\n"
371 "-particle_fields_0_name Psi\n"
372 "-particle_fields_0_expr_0 \"x + 2*pid\"\n";
373 SimCtx *simCtx = NULL;
374 UserCtx *user = NULL;
375 char tmpdir[PETSC_MAX_PATH_LEN];
376 PetscInt nlocal = 0;
377 const PetscReal *psi = NULL, *position = NULL;
378 const PetscInt64 *pid = NULL;
379 PetscReal particle_sum = 0.0, eulerian_sum = 0.0;
380
381 PetscFunctionBeginUser;
382 PetscCall(PicurvBuildTinyRuntimeContextWithOptions(NULL, PETSC_TRUE, options, &simCtx, &user,
383 tmpdir, sizeof(tmpdir)));
384 PetscCall(PicurvAssertBool((PetscBool)(simCtx->particleFieldPlan != NULL), "the plan is read from the options"));
385 PetscCall(InitializeEulerianState(simCtx));
386 PetscCall(InitializeParticleSwarm(simCtx));
387 PetscCall(PerformInitializedParticleSetup(simCtx));
388
389 PetscCall(DMSwarmGetLocalSize(user->swarm, &nlocal));
390 PetscCall(DMSwarmGetField(user->swarm, "Psi", NULL, NULL, (void **)&psi));
391 PetscCall(DMSwarmGetField(user->swarm, "position", NULL, NULL, (void **)&position));
392 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_pid", NULL, NULL, (void **)&pid));
393 for (PetscInt p = 0; p < nlocal; ++p) {
394 const PetscReal expected = position[3 * p] * simCtx->scaling.L_ref + 2.0 * (PetscReal)pid[p];
395 PetscCall(PicurvAssertRealNear(expected, psi[p], 1.0e-12, "each particle carries its initial value"));
396 particle_sum += psi[p];
397 }
398 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_pid", NULL, NULL, (void **)&pid));
399 PetscCall(DMSwarmRestoreField(user->swarm, "position", NULL, NULL, (void **)&position));
400 PetscCall(DMSwarmRestoreField(user->swarm, "Psi", NULL, NULL, (void **)&psi));
401 PetscCallMPI(MPI_Allreduce(MPI_IN_PLACE, &particle_sum, 1, MPIU_REAL, MPI_SUM, PETSC_COMM_WORLD));
402 PetscCall(VecDot(user->Psi, user->ParticleCount, &eulerian_sum));
403 PetscCall(PicurvAssertRealNear(particle_sum, eulerian_sum, 1.0e-9,
404 "the t=0 scatter carries the initial value to the Eulerian mean"));
405 {
406 /* The scatter leaves empty dummy cells at zero; the production path must then give
407 each non-periodic face's dummy the adjacent cell's mean. */
408 const DMDALocalInfo info = user->info;
409 const PetscReal ***mean = NULL, ***count = NULL;
410 PetscReal checked = 0.0;
411
412 PetscCall(DMDAVecGetArrayRead(user->da, user->Psi, &mean));
413 PetscCall(DMDAVecGetArrayRead(user->da, user->ParticleCount, &count));
414 for (PetscInt k = info.zs; k < info.zs + info.zm; k++)
415 for (PetscInt j = info.ys; j < info.ys + info.ym; j++)
416 for (PetscInt i = info.xs; i < info.xs + info.xm; i++) {
417 /* A flat-face dummy cell and its adjacent interior cell, one axis at a time. */
418 const PetscInt index[3] = {i, j, k}, extent[3] = {info.mx, info.my, info.mz};
419 PetscInt dummy_axes = 0, axis = -1;
420
421 for (PetscInt d = 0; d < 3; d++) {
422 if (index[d] == 0 || index[d] == extent[d] - 1) { dummy_axes++; axis = d; }
423 }
424 if (dummy_axes != 1) continue;
425 if (user->boundary_faces[2 * axis].mathematical_type == PERIODIC) continue;
426 {
427 const PetscInt step = (index[axis] == 0) ? 1 : -1;
428 const PetscInt ai = i + (axis == 0 ? step : 0), aj = j + (axis == 1 ? step : 0),
429 ak = k + (axis == 2 ? step : 0);
430
431 if (count[ak][aj][ai] < 0.5) continue;
432 PetscCall(PicurvAssertRealNear(mean[ak][aj][ai], mean[k][j][i], 0.0,
433 "the scatter fills each dummy cell with the adjacent cell mean"));
434 checked += 1.0;
435 }
436 }
437 PetscCall(DMDAVecRestoreArrayRead(user->da, user->ParticleCount, &count));
438 PetscCall(DMDAVecRestoreArrayRead(user->da, user->Psi, &mean));
439 PetscCallMPI(MPI_Allreduce(MPI_IN_PLACE, &checked, 1, MPIU_REAL, MPI_SUM, PETSC_COMM_WORLD));
440 PetscCall(PicurvAssertBool((PetscBool)(checked > 0.0),
441 "the fixture must seed particles next to a non-periodic face"));
442 }
443
444 PetscCall(FreeLifecycleContext(&simCtx));
445 PetscCall(PicurvRemoveTempDir(tmpdir));
446 PetscFunctionReturn(0);
447}
448
449/**
450 * @brief Checks configured IEM relaxation through the production swarm update.
451 * @details Each particle must relax toward the mean of the particles now in its cell, at the
452 * configured rate. A zero constant switches micromixing off, and must leave Psi
453 * bit-identical: alternating values 0.1 and 0.7 do not survive `m + (0.1 - m) * 1`
454 * exactly in mixed cells, so the exact comparison shows the update is skipped, not
455 * run at a zero rate.
456 */
457static PetscErrorCode TestConfiguredIEMUpdatesSwarm(void)
458{
459 const char *options[] = {"", "-iem_constant 3.5\n", "-iem_constant 0\n"};
460 const PetscReal constants[] = {2.0, 3.5, 0.0};
461
462 PetscFunctionBeginUser;
463 for (size_t n = 0; n < sizeof(constants) / sizeof(constants[0]); ++n) {
464 SimCtx *simCtx = NULL;
465 UserCtx *user = NULL;
466 char tmpdir[PETSC_MAX_PATH_LEN];
467 PetscInt nlocal;
468 PetscReal *psi = NULL, *diffusivity = NULL, *psi_initial = NULL, *cell_mean = NULL;
469 PetscInt *cell = NULL;
470 const char *psi_name = ParticleFieldName(PARTICLE_FIELD_ID_PSI);
471 const char *diff_name = ParticleFieldName(PARTICLE_FIELD_ID_DIFFUSIVITY);
472 const char *cell_name = ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID);
473
474 PetscCall(PicurvBuildTinyRuntimeContextWithOptions(NULL, PETSC_TRUE, options[n],
475 &simCtx, &user, tmpdir, sizeof(tmpdir)));
476 PetscCall(InitializeEulerianState(simCtx));
477 PetscCall(InitializeParticleSwarm(simCtx));
478 PetscCall(PerformInitializedParticleSetup(simCtx));
479 simCtx->dt = 0.5;
480 /* Controlled nonzero test data; no public scalar initializer is implied. A unit
481 * Jacobian makes the mixing length 1, and alternating particle values give every
482 * cell with two or more particles a mean the particles must relax toward. */
483 PetscCall(VecSet(user->lAj, 1.0));
484 PetscCall(DMSwarmGetLocalSize(user->swarm, &nlocal));
485 PetscCall(PicurvAssertBool((PetscBool)(nlocal > 0), "IEM fixture must contain particles"));
486 PetscCall(DMSwarmGetField(user->swarm, psi_name, NULL, NULL, (void **)&psi));
487 PetscCall(DMSwarmGetField(user->swarm, diff_name, NULL, NULL, (void **)&diffusivity));
488 PetscCall(DMSwarmGetField(user->swarm, cell_name, NULL, NULL, (void **)&cell));
489 PetscCall(PetscMalloc2(nlocal, &psi_initial, nlocal, &cell_mean));
490 for (PetscInt p = 0; p < nlocal; ++p) {
491 psi[p] = psi_initial[p] = (p % 2) ? 0.7 : 0.1;
492 diffusivity[p] = 0.2;
493 }
494 /* Each particle's own-cell mean, from the particles where they are now. */
495 for (PetscInt p = 0; p < nlocal; ++p) {
496 PetscReal sum = 0.0, count = 0.0;
497 for (PetscInt q = 0; q < nlocal; ++q) {
498 if (cell[3*q] == cell[3*p] && cell[3*q+1] == cell[3*p+1] && cell[3*q+2] == cell[3*p+2]) {
499 sum += psi_initial[q]; count += 1.0;
500 }
501 }
502 cell_mean[p] = sum / count;
503 }
504 PetscCall(DMSwarmRestoreField(user->swarm, cell_name, NULL, NULL, (void **)&cell));
505 PetscCall(DMSwarmRestoreField(user->swarm, diff_name, NULL, NULL, (void **)&diffusivity));
506 PetscCall(DMSwarmRestoreField(user->swarm, psi_name, NULL, NULL, (void **)&psi));
507 PetscCall(UpdateAllParticleFields(user));
508 PetscCall(DMSwarmGetField(user->swarm, psi_name, NULL, NULL, (void **)&psi));
509 for (PetscInt p = 0; p < nlocal; ++p) {
510 if (constants[n] == 0.0) {
511 PetscCall(PicurvAssertBool((PetscBool)(psi[p] == psi_initial[p]),
512 "a zero IEM constant must leave Psi bit-identical"));
513 } else {
514 PetscCall(PicurvAssertRealNear(cell_mean[p] + (psi_initial[p] - cell_mean[p]) * PetscExpReal(-constants[n] * 0.2 * 0.5),
515 psi[p], 1.e-12, "configured IEM constant must relax each particle toward its cell's current mean"));
516 }
517 }
518 PetscCall(DMSwarmRestoreField(user->swarm, psi_name, NULL, NULL, (void **)&psi));
519 PetscCall(PetscFree2(psi_initial, cell_mean));
520 PetscCall(FreeLifecycleContext(&simCtx));
521 PetscCall(PicurvRemoveTempDir(tmpdir));
522 }
523 PetscFunctionReturn(0);
524}
525
526/**
527 * @brief Checks that IEM relaxes each particle toward its own cell's scattered mean.
528 * @details Particles start at random 0 or 1, so cell means differ from cell to cell and
529 * from the zero in the boundary storage slots. Relaxing toward the particle's own
530 * cell mean at a rate uniform within the cell conserves every cell's scalar sum,
531 * so the total is unchanged while the spread shrinks; reading a neighbour's mean,
532 * or a boundary slot, breaks the conservation.
533 */
534static PetscErrorCode TestIEMRelaxesTowardOwnCellMean(void)
535{
536 const char *options = "-particle_fields_count 1\n"
537 "-particle_fields_0_name Psi\n"
538 "-particle_fields_0_expr_0 \"where(uniform() < 0.5, 0, 1)\"\n";
539 SimCtx *simCtx = NULL;
540 UserCtx *user = NULL;
541 char tmpdir[PETSC_MAX_PATH_LEN];
542 PetscInt nlocal = 0;
543 PetscReal *psi = NULL, *diffusivity = NULL;
544 PetscReal sums[2][2] = {{0.0, 0.0}, {0.0, 0.0}}; /* [before|after][sum|sum of squares] */
545
546 PetscFunctionBeginUser;
547 PetscCall(PicurvBuildTinyRuntimeContextWithOptions(NULL, PETSC_TRUE, options, &simCtx, &user,
548 tmpdir, sizeof(tmpdir)));
549 PetscCall(InitializeEulerianState(simCtx));
550 PetscCall(InitializeParticleSwarm(simCtx));
551 PetscCall(PerformInitializedParticleSetup(simCtx));
552 simCtx->dt = 0.5;
553
554 PetscCall(DMSwarmGetLocalSize(user->swarm, &nlocal));
555 PetscCall(DMSwarmGetField(user->swarm, "Psi", NULL, NULL, (void **)&psi));
556 PetscCall(DMSwarmGetField(user->swarm, "Diffusivity", NULL, NULL, (void **)&diffusivity));
557 for (PetscInt p = 0; p < nlocal; ++p) {
558 diffusivity[p] = 0.2;
559 sums[0][0] += psi[p];
560 sums[0][1] += psi[p] * psi[p];
561 }
562 PetscCall(DMSwarmRestoreField(user->swarm, "Diffusivity", NULL, NULL, (void **)&diffusivity));
563 PetscCall(DMSwarmRestoreField(user->swarm, "Psi", NULL, NULL, (void **)&psi));
564
565 PetscCall(UpdateAllParticleFields(user));
566
567 PetscCall(DMSwarmGetField(user->swarm, "Psi", NULL, NULL, (void **)&psi));
568 for (PetscInt p = 0; p < nlocal; ++p) {
569 sums[1][0] += psi[p];
570 sums[1][1] += psi[p] * psi[p];
571 }
572 PetscCall(DMSwarmRestoreField(user->swarm, "Psi", NULL, NULL, (void **)&psi));
573 PetscCallMPI(MPI_Allreduce(MPI_IN_PLACE, sums, 4, MPIU_REAL, MPI_SUM, PETSC_COMM_WORLD));
574
575 PetscCall(PicurvAssertBool((PetscBool)(sums[0][0] > 0.0 && sums[0][0] < (PetscReal)simCtx->np),
576 "the fixture must start with both values present"));
577 PetscCall(PicurvAssertRealNear(sums[0][0], sums[1][0], 1.0e-9,
578 "relaxing toward each particle's own cell mean conserves the scalar"));
579 PetscCall(PicurvAssertBool((PetscBool)(sums[1][1] < sums[0][1] - 1.0e-6),
580 "the update must reduce the scalar's spread"));
581
582 PetscCall(FreeLifecycleContext(&simCtx));
583 PetscCall(PicurvRemoveTempDir(tmpdir));
584 PetscFunctionReturn(0);
585}
586
587/**
588 * @brief Checks that post-processing loads the particle averages a checkpoint holds.
589 * @details A case with `restart_mode: init` reseeds particles on a solver restart, so the
590 * solver must not restore the averages of the particles it discards; the
591 * postprocessor reads the same control settings but visualizes what was saved, so it
592 * must load them, and refresh the ghosted copy that nodal averaging reads.
593 */
595{
596 const char *options = "-particle_fields_count 1\n"
597 "-particle_fields_0_name Psi\n"
598 "-particle_fields_0_expr_0 \"where(uniform() < 0.5, 0, 1)\"\n"
599 "-particle_restart_mode init\n";
600 SimCtx *simCtx = NULL;
601 UserCtx *user = NULL;
602 char tmpdir[PETSC_MAX_PATH_LEN];
603 Vec saved = NULL, local_saved = NULL;
604 PetscReal saved_norm = 0.0, difference = 0.0;
605
606 PetscFunctionBeginUser;
607 PetscCall(PicurvBuildTinyRuntimeContextWithOptions(NULL, PETSC_TRUE, options, &simCtx, &user,
608 tmpdir, sizeof(tmpdir)));
609 PetscCall(InitializeEulerianState(simCtx));
610 PetscCall(InitializeParticleSwarm(simCtx));
611 PetscCall(PerformInitializedParticleSetup(simCtx));
612 PetscCall(VecDuplicate(user->Psi, &saved));
613 PetscCall(VecCopy(user->Psi, saved));
614 PetscCall(VecNorm(saved, NORM_INFINITY, &saved_norm));
615 PetscCall(PicurvAssertBool((PetscBool)(saved_norm > 0.0), "the fixture must scatter a nonzero Psi"));
616 PetscCall(WriteCheckpointBundle(simCtx, "test"));
617
618 PetscCall(PetscCalloc1(1, &simCtx->pps));
619 PetscCall(PetscStrncpy(simCtx->pps->source_dir, simCtx->output_dir, sizeof(simCtx->pps->source_dir)));
621 PetscCall(VecSet(user->Psi, 0.0));
622 PetscCall(VecSet(user->lPsi, 0.0));
623 PetscCall(ReadSimulationFields(user, simCtx->step));
624
625 PetscCall(VecAXPY(saved, -1.0, user->Psi));
626 PetscCall(VecNorm(saved, NORM_INFINITY, &difference));
627 PetscCall(PicurvAssertRealNear(0.0, difference, 1.0e-12, "post-processing restores the saved Psi average"));
628 PetscCall(DMCreateLocalVector(user->da, &local_saved));
629 PetscCall(DMGlobalToLocalBegin(user->da, user->Psi, INSERT_VALUES, local_saved));
630 PetscCall(DMGlobalToLocalEnd(user->da, user->Psi, INSERT_VALUES, local_saved));
631 PetscCall(VecAXPY(local_saved, -1.0, user->lPsi));
632 PetscCall(VecNorm(local_saved, NORM_INFINITY, &difference));
633 PetscCall(PicurvAssertRealNear(0.0, difference, 1.0e-12, "the ghosted Psi is refreshed for nodal averaging"));
634
635 simCtx->exec_mode = EXEC_MODE_SOLVER;
636 PetscCall(PetscFree(simCtx->pps));
637 PetscCall(VecDestroy(&local_saved));
638 PetscCall(VecDestroy(&saved));
639 PetscCall(FreeLifecycleContext(&simCtx));
640 PetscCall(PicurvRemoveTempDir(tmpdir));
641 PetscFunctionReturn(0);
642}
643
644/**
645 * @brief Tests that the shared richer runtime fixture mirrors normalized production setup contracts.
646 */
647static PetscErrorCode TestSharedRuntimeFixtureContracts(void)
648{
649 SimCtx *simCtx = NULL;
650 UserCtx *user = NULL;
651 char tmpdir[PETSC_MAX_PATH_LEN];
652
653 PetscFunctionBeginUser;
654 PetscCall(PicurvBuildTinyRuntimeContext(NULL, PETSC_FALSE, &simCtx, &user, tmpdir, sizeof(tmpdir)));
655
656 PetscCall(PicurvAssertBool((PetscBool)(user->bbox.min_coords.x >= -2.0e-6), "runtime fixture bbox xmin should stay inside normalized domain tolerance"));
657 PetscCall(PicurvAssertBool((PetscBool)(user->bbox.max_coords.x <= 1.0 + 2.0e-6), "runtime fixture bbox xmax should stay inside normalized domain tolerance"));
658 PetscCall(PicurvAssertBool((PetscBool)(user->bbox.max_coords.y <= 1.0 + 2.0e-6), "runtime fixture bbox ymax should stay inside normalized domain tolerance"));
659 PetscCall(PicurvAssertBool((PetscBool)(user->bbox.max_coords.z <= 1.0 + 2.0e-6), "runtime fixture bbox zmax should stay inside normalized domain tolerance"));
660 PetscCall(PicurvAssertBool((PetscBool)(simCtx->bboxlist != NULL), "runtime fixture should gather bboxlist through SetupDomainRankInfo"));
661 PetscCall(PicurvAssertBool((PetscBool)(user->RankCellInfoMap != NULL), "runtime fixture should gather rank-cell ownership metadata"));
662 PetscCall(PicurvAssertBool((PetscBool)(simCtx->bboxlist[simCtx->rank].max_coords.x >= user->bbox.max_coords.x - 1.0e-10),
663 "runtime fixture rank bbox entry should include the local bbox extent"));
664
665 PetscCall(PicurvRemoveTempDir(tmpdir));
666 PetscCall(FreeLifecycleContext(&simCtx));
667 PetscFunctionReturn(0);
668}
669
670/**
671 * @brief Asserts that one block's stored local info matches what its DMs report.
672 * @details Grid, rank and particle code read user->info in place of DMDAGetLocalInfo on
673 * user->da and user->fda, which relies on both DMs sharing one decomposition.
674 */
675static PetscErrorCode AssertStoredLocalInfoMatchesDMs(UserCtx *user)
676{
677 DMDALocalInfo dm_info[2];
678 const char *names[2] = {"da", "fda"};
679
680 PetscFunctionBeginUser;
681 PetscCall(DMDAGetLocalInfo(user->da, &dm_info[0]));
682 PetscCall(DMDAGetLocalInfo(user->fda, &dm_info[1]));
683 for (PetscInt d = 0; d < 2; ++d) {
684 const DMDALocalInfo *a = &dm_info[d], *b = &user->info;
685 const PetscInt got[15] = {a->mx, a->my, a->mz, a->xs, a->ys, a->zs, a->xm, a->ym, a->zm,
686 a->gxs, a->gys, a->gzs, a->gxm, a->gym, a->gzm};
687 const PetscInt want[15] = {b->mx, b->my, b->mz, b->xs, b->ys, b->zs, b->xm, b->ym, b->zm,
688 b->gxs, b->gys, b->gzs, b->gxm, b->gym, b->gzm};
689 char msg[128];
690 for (PetscInt f = 0; f < 15; ++f) {
691 PetscCall(PetscSNPrintf(msg, sizeof(msg), "user->info range %" PetscInt_FMT " should match %s", f, names[d]));
692 PetscCall(PicurvAssertIntEqual(want[f], got[f], msg));
693 }
694 PetscCall(PicurvAssertBool((PetscBool)(a->bx == b->bx && a->by == b->by && a->bz == b->bz),
695 "user->info boundary types should match the DM"));
696 }
697 PetscFunctionReturn(0);
698}
699
700/**
701 * @brief Tests the core setup lifecycle through environment, grid, BC, rank-info, and Eulerian-state initialization.
702 */
703static PetscErrorCode TestSetupLifecycleCoreSolverSetup(void)
704{
705 SimCtx *simCtx = NULL;
706 UserCtx *user = NULL;
707 char tmpdir[PETSC_MAX_PATH_LEN];
708 char results_dir[PETSC_MAX_PATH_LEN];
709 char logs_dir[PETSC_MAX_PATH_LEN];
710 Cmpnts ***ucont = NULL;
711
712 PetscFunctionBeginUser;
713 PetscCall(BuildLifecycleContext(PETSC_FALSE, &simCtx, tmpdir, sizeof(tmpdir)));
714 user = simCtx->usermg.mgctx[simCtx->usermg.mglevels - 1].user;
715
716 PetscCall(PetscSNPrintf(results_dir, sizeof(results_dir), "%s/results", tmpdir));
717 PetscCall(PetscSNPrintf(logs_dir, sizeof(logs_dir), "%s/logs", tmpdir));
718 PetscCall(PicurvAssertBool((PetscBool)(user->da != NULL), "SetupGridAndSolvers should allocate da"));
719 PetscCall(PicurvAssertBool((PetscBool)(user->fda != NULL), "SetupGridAndSolvers should allocate coordinate DM"));
720 PetscCall(PicurvAssertIntEqual(user->IM + 1, user->info.mx, "DM node count should match IM+1"));
721 for (PetscInt level = 0; level < simCtx->usermg.mglevels; ++level) {
722 for (PetscInt bi = 0; bi < simCtx->block_number; ++bi) {
723 PetscCall(AssertStoredLocalInfoMatchesDMs(&simCtx->usermg.mgctx[level].user[bi]));
724 }
725 }
726 PetscCall(PicurvAssertBool(user->inletFaceDefined, "SetupBoundaryConditions should identify the inlet face"));
727 PetscCall(PicurvAssertIntEqual(BC_FACE_NEG_Z, user->identifiedInletBCFace, "NEG_Z should be the configured inlet face"));
728 PetscCall(PicurvAssertBool((PetscBool)(simCtx->bboxlist != NULL), "SetupDomainRankInfo should build bboxlist"));
729 PetscCall(PicurvAssertBool((PetscBool)(user->RankCellInfoMap != NULL), "SetupDomainRankInfo should build rank-cell decomposition map"));
730 PetscCall(AssertDirectoryExists(results_dir, "SetupSimulationEnvironment should create the output directory"));
731 PetscCall(AssertDirectoryExists(logs_dir, "SetupSimulationEnvironment should create the log directory"));
732
733 PetscCall(InitializeEulerianState(simCtx));
734 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucont, &ucont));
735 PetscCall(PicurvAssertBool((PetscBool)(ucont[1][1][1].z > 0.0),
736 "InitializeEulerianState should seed a positive inlet-aligned interior field"));
737 PetscCall(PicurvAssertRealNear(ucont[1][1][1].z, ucont[2][2][2].z, 1.0e-12,
738 "InitializeEulerianState should initialize a spatially consistent interior field"));
739 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucont, &ucont));
740
741 PetscCall(PicurvRemoveTempDir(tmpdir));
742 PetscCall(FreeLifecycleContext(&simCtx));
743 PetscFunctionReturn(0);
744}
745
746/**
747 * @brief Tests particle-swarm initialization and deterministic settlement on a tiny fully initialized case.
748 */
750{
751 SimCtx *simCtx = NULL;
752 UserCtx *user = NULL;
753 char tmpdir[PETSC_MAX_PATH_LEN];
754 PetscInt nlocal = 0;
755 PetscInt *cell_ids = NULL;
756 PetscInt *status = NULL;
757
758 PetscFunctionBeginUser;
759 PetscCall(BuildLifecycleContext(PETSC_TRUE, &simCtx, tmpdir, sizeof(tmpdir)));
760 PetscCall(InitializeEulerianState(simCtx));
761 PetscCall(InitializeParticleSwarm(simCtx));
762 PetscCall(PerformInitializedParticleSetup(simCtx));
763
764 user = simCtx->usermg.mgctx[simCtx->usermg.mglevels - 1].user;
765 PetscCall(PicurvAssertBool((PetscBool)(user->swarm != NULL), "InitializeParticleSwarm should allocate the solver swarm"));
766 PetscCall(DMSwarmGetLocalSize(user->swarm, &nlocal));
767 PetscCall(PicurvAssertIntEqual(8, nlocal, "single-rank lifecycle particle setup should own all seeded particles"));
768
769 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
770 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
771 for (PetscInt p = 0; p < nlocal; ++p) {
772 PetscCall(PicurvAssertBool((PetscBool)(cell_ids[3 * p + 0] >= 0), "settled particles should have a valid i cell id"));
773 PetscCall(PicurvAssertBool((PetscBool)(cell_ids[3 * p + 1] >= 0), "settled particles should have a valid j cell id"));
774 PetscCall(PicurvAssertBool((PetscBool)(cell_ids[3 * p + 2] >= 0), "settled particles should have a valid k cell id"));
775 PetscCall(PicurvAssertIntEqual(ACTIVE_AND_LOCATED, status[p], "settled particles should be marked ACTIVE_AND_LOCATED"));
776 }
777 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
778 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
779
780 PetscCall(PicurvRemoveTempDir(tmpdir));
781 PetscCall(FreeLifecycleContext(&simCtx));
782 PetscFunctionReturn(0);
783}
784
785/**
786 * @brief Tests that step-zero scalar verification emits scatter_metrics.csv during initialized setup.
787 */
789{
790 SimCtx *simCtx = NULL;
791 UserCtx *user = NULL;
792 char tmpdir[PETSC_MAX_PATH_LEN];
793 char metrics_path[PETSC_MAX_PATH_LEN];
794
795 PetscFunctionBeginUser;
796 PetscCall(BuildLifecycleContext(PETSC_TRUE, &simCtx, tmpdir, sizeof(tmpdir)));
797 user = simCtx->usermg.mgctx[simCtx->usermg.mglevels - 1].user;
798 PetscCall(PetscStrncpy(simCtx->eulerianSource, "analytical", sizeof(simCtx->eulerianSource)));
799 PetscCall(PetscStrncpy(simCtx->AnalyticalSolutionType, "ZERO_FLOW", sizeof(simCtx->AnalyticalSolutionType)));
800 simCtx->StepsToRun = 0;
801 simCtx->StartStep = 0;
802 simCtx->verificationScalar.enabled = PETSC_TRUE;
803 PetscCall(PetscStrncpy(simCtx->verificationScalar.mode,
804 "analytical",
805 sizeof(simCtx->verificationScalar.mode)));
806 PetscCall(PetscStrncpy(simCtx->verificationScalar.profile,
807 "CONSTANT",
808 sizeof(simCtx->verificationScalar.profile)));
809 simCtx->verificationScalar.value = 1.25;
810
811 PetscCall(InitializeEulerianState(simCtx));
812 PetscCall(InitializeParticleSwarm(simCtx));
813 PetscCall(PerformInitializedParticleSetup(simCtx));
814
815 PetscCall(PetscSNPrintf(metrics_path, sizeof(metrics_path), "%s/results/analysis/metrics/scatter_metrics.csv", tmpdir));
816 PetscCall(PicurvAssertFileExists(metrics_path,
817 "PerformInitializedParticleSetup should emit scatter_metrics.csv for a zero-step scalar verification run"));
818 PetscCall(PicurvAssertBool((PetscBool)(user->swarm != NULL),
819 "zero-step scalar verification setup should leave the swarm initialized"));
820
821 PetscCall(PicurvRemoveTempDir(tmpdir));
822 PetscCall(FreeLifecycleContext(&simCtx));
823 PetscFunctionReturn(0);
824}
825
826/**
827 * @brief Tests standalone RNG initialization helpers and minimal-context cleanup.
828 */
830{
831 SimCtx *simCtx = NULL;
832 UserCtx *user = NULL;
833 PetscRandom randx = NULL, randy = NULL, randz = NULL;
834 PetscRandom rand_i = NULL, rand_j = NULL, rand_k = NULL;
835 PetscScalar sample_x = 0.0, sample_i = 0.0;
836 PetscReal seconds = 0.0;
837
838 PetscFunctionBeginUser;
839 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
840 user->bbox.min_coords.x = 0.0;
841 user->bbox.min_coords.y = 0.0;
842 user->bbox.min_coords.z = 0.0;
843 user->bbox.max_coords.x = 1.0;
844 user->bbox.max_coords.y = 2.0;
845 user->bbox.max_coords.z = 3.0;
846
847 PetscCall(InitializeRandomGenerators(user, &randx, &randy, &randz));
848 PetscCall(InitializeLogicalSpaceRNGs(simCtx->particleRandomSeed, &rand_i, &rand_j, &rand_k));
849 PetscCall(PicurvAssertBool(RuntimeWalltimeGuardParsePositiveSeconds("12.5", &seconds), "RuntimeWalltimeGuardParsePositiveSeconds should parse positive numeric strings"));
850 PetscCall(PicurvAssertRealNear(12.5, seconds, 1.0e-12, "RuntimeWalltimeGuardParsePositiveSeconds parsed value"));
851
852 PetscCall(PetscRandomGetValue(randx, &sample_x));
853 PetscCall(PetscRandomGetValue(rand_i, &sample_i));
854 PetscCall(PicurvAssertBool((PetscBool)(PetscRealPart(sample_x) >= 0.0 && PetscRealPart(sample_x) <= 1.0),
855 "InitializeRandomGenerators should honor the configured bbox interval"));
856 PetscCall(PicurvAssertBool((PetscBool)(PetscRealPart(sample_i) >= 0.0 && PetscRealPart(sample_i) <= 1.0),
857 "InitializeLogicalSpaceRNGs should generate logical coordinates in [0,1]"));
858
859 PetscCall(PetscRandomDestroy(&randx));
860 PetscCall(PetscRandomDestroy(&randy));
861 PetscCall(PetscRandomDestroy(&randz));
862 PetscCall(PetscRandomDestroy(&rand_i));
863 PetscCall(PetscRandomDestroy(&rand_j));
864 PetscCall(PetscRandomDestroy(&rand_k));
865 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
866 PetscFunctionReturn(0);
867}
868
869/**
870 * @brief Tests cleanup after partial and fuller setup states without requiring unsupported double-finalization behavior.
871 */
873{
874 SimCtx *context_only = NULL;
875 SimCtx *grid_only = NULL;
876 char context_tmpdir[PETSC_MAX_PATH_LEN];
877 char grid_tmpdir[PETSC_MAX_PATH_LEN];
878
879 PetscFunctionBeginUser;
880 PetscCall(BuildContextOnly(&context_only, context_tmpdir, sizeof(context_tmpdir)));
881 PetscCall(PicurvAssertBool((PetscBool)(context_only != NULL), "CreateSimulationContext should allocate the top-level SimCtx"));
882 PetscCall(PicurvAssertIntEqual(1, context_only->block_number, "CreateSimulationContext should parse the configured block count"));
884 "deprecated RK4 selector should normalize to the Jameson solver enum"));
886 "deprecated RK4 residual-noise option should populate the Jameson control"));
887 PetscCall(PicurvAssertRealNear(1.0e-8, context_only->mom_resid_atol, 1.0e-12,
888 "momentum absolute residual tolerance should be parsed"));
889 PetscCall(PicurvAssertRealNear(1.0e-3, context_only->mom_resid_rtol, 1.0e-12,
890 "momentum relative residual tolerance should be parsed"));
891 PetscCall(PicurvAssertBool(context_only->mom_nk_monitor_history,
892 "Newton structured history monitor should be parsed"));
893 PetscCall(PetscOptionsClear(NULL));
894 PetscCall(PicurvRemoveTempDir(context_tmpdir));
895 PetscCall(FreeLifecycleContext(&context_only));
896
897 PetscCall(BuildContextOnly(&grid_only, grid_tmpdir, sizeof(grid_tmpdir)));
898 PetscCall(SetupSimulationEnvironment(grid_only));
899 PetscCall(SetupGridAndSolvers(grid_only));
900 PetscCall(PicurvAssertBool((PetscBool)(grid_only->usermg.mgctx[grid_only->usermg.mglevels - 1].user->Ucont != NULL),
901 "SetupGridAndSolvers should allocate baseline Eulerian vectors"));
902 PetscCall(PicurvRemoveTempDir(grid_tmpdir));
903 PetscCall(FreeLifecycleContext(&grid_only));
904 PetscFunctionReturn(0);
905}
906
907/**
908 * @brief Verifies catalog completeness, stable name resolution, and runtime view binding.
909 */
910static PetscErrorCode TestFieldCatalogMetadataAndViews(void)
911{
912 SimCtx *simCtx = NULL;
913 UserCtx *user = NULL;
914 FieldId parsed_id = FIELD_ID_INVALID;
915 FieldView view;
916 const FieldDescriptor *descriptor = NULL;
917 Vec coordinates = NULL;
918 Vec local_coordinates = NULL;
919 PetscErrorCode unknown_name_ierr;
920 PetscErrorCode unavailable_view_ierr;
921
922 PetscFunctionBeginUser;
923 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
924
925 for (PetscInt raw_id = 0; raw_id < FIELD_ID_COUNT; ++raw_id) {
926 PetscCall(FieldGetDescriptor((FieldId)raw_id, &descriptor));
927 PetscCall(PicurvAssertIntEqual(raw_id, (PetscInt)descriptor->id,
928 "catalog entry should retain its declared FieldId"));
929 PetscCall(PicurvAssertBool((PetscBool)(descriptor->canonical_name != NULL &&
930 descriptor->canonical_name[0] != '\0'),
931 "every field descriptor should have a canonical name"));
932 PetscCall(PicurvAssertBool((PetscBool)(descriptor->dof == 1 ||
933 descriptor->dof == 2 ||
934 descriptor->dof == 3),
935 "every ghost-updatable catalog field should declare a supported dof"));
936 PetscCall(PicurvAssertBool((PetscBool)((descriptor->capabilities &
938 "every phase-one field should advertise ghost-update capability"));
939 PetscCall(FieldIdFromName(descriptor->canonical_name, &parsed_id));
940 PetscCall(PicurvAssertIntEqual(raw_id, (PetscInt)parsed_id,
941 "canonical field names should round-trip to their IDs"));
942 PetscCall(PicurvAssertBool((PetscBool)(strcmp(FieldCanonicalName((FieldId)raw_id),
943 descriptor->canonical_name) == 0),
944 "FieldCanonicalName should return catalog-owned canonical text"));
945 }
946
947 PetscCall(FieldIdFromName("eddy viscosity", &parsed_id));
948 PetscCall(PicurvAssertIntEqual(FIELD_ID_NU_T, parsed_id,
949 "Nu_t compatibility alias should resolve case-insensitively"));
950 PetscCall(FieldIdFromName("cs", &parsed_id));
951 PetscCall(PicurvAssertIntEqual(FIELD_ID_CS, parsed_id,
952 "CS compatibility alias should resolve case-insensitively"));
953 PetscCall(FieldIdFromName("X-Face-Centers", &parsed_id));
954 PetscCall(PicurvAssertIntEqual(FIELD_ID_CENTX, parsed_id,
955 "diagnostic face-center name should resolve to the canonical field"));
956
957 PetscCall(FieldGetDescriptor(FIELD_ID_UCONT, &descriptor));
959 "Ucont should retain component-staggered topology metadata"));
961 "Ucont should retain component-staggered ghost repair metadata"));
962 PetscCall(FieldGetDescriptor(FIELD_ID_CSI, &descriptor));
963 PetscCall(PicurvAssertIntEqual(FIELD_LAYOUT_I_FACE, descriptor->layout,
964 "Csi should retain I-face topology metadata"));
966 "Csi should retain I-face ghost repair metadata"));
967 PetscCall(FieldGetDescriptor(FIELD_ID_IAJ, &descriptor));
968 PetscCall(PicurvAssertIntEqual(1, descriptor->dof,
969 "IAj should retain scalar face-field metadata"));
970
971 PetscCall(FieldGetView(user, FIELD_ID_P, &view));
972 PetscCall(PicurvAssertBool((PetscBool)(view.dm == user->da &&
973 view.global_vec == user->P &&
974 view.local_vec == user->lP),
975 "P field view should bind the existing scalar DM and Vec pair"));
976 PetscCall(FieldGetView(user, FIELD_ID_UCAT, &view));
977 PetscCall(PicurvAssertBool((PetscBool)(view.dm == user->fda &&
978 view.global_vec == user->Ucat &&
979 view.local_vec == user->lUcat),
980 "Ucat field view should bind the existing vector DM and Vec pair"));
981
982 PetscCall(DMGetCoordinates(user->da, &coordinates));
983 PetscCall(DMGetCoordinatesLocal(user->da, &local_coordinates));
984 PetscCall(FieldGetView(user, FIELD_ID_COORDINATES, &view));
985 PetscCall(PicurvAssertBool((PetscBool)(view.dm == user->fda &&
986 view.global_vec == coordinates &&
987 view.local_vec == local_coordinates),
988 "coordinate field view should bind PETSc-owned coordinate vectors"));
989
990 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
991 unknown_name_ierr = FieldIdFromName("NotARegisteredEulerianField", &parsed_id);
992 PetscCall(PetscPopErrorHandler());
993 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_UNKNOWN_TYPE, unknown_name_ierr,
994 "unknown field names should fail at the name-ingress boundary"));
995 PetscCall(PicurvAssertIntEqual(FIELD_ID_INVALID, parsed_id,
996 "failed field-name resolution should return FIELD_ID_INVALID"));
997
998 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
999 unavailable_view_ierr = FieldGetView(user, FIELD_ID_CS, &view);
1000 PetscCall(PetscPopErrorHandler());
1001 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_WRONGSTATE, unavailable_view_ierr,
1002 "catalogued optional fields without storage should report unavailable state"));
1003
1004 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1005 PetscFunctionReturn(0);
1006}
1007
1008/**
1009 * @brief Verifies the separate DMSwarm field catalog and its Eulerian bridge metadata.
1010 */
1011static PetscErrorCode TestParticleFieldCatalogMetadata(void)
1012{
1014 const ParticleFieldDescriptor *descriptor = NULL;
1015 PetscErrorCode unknown_name_ierr;
1016
1017 PetscFunctionBeginUser;
1018 for (PetscInt raw_id = 0; raw_id < PARTICLE_FIELD_ID_COUNT; ++raw_id) {
1019 PetscCall(ParticleFieldGetDescriptor((ParticleFieldId)raw_id, &descriptor));
1020 PetscCall(PicurvAssertIntEqual(raw_id, (PetscInt)descriptor->id,
1021 "particle catalog entry should retain its declared ID"));
1022 PetscCall(PicurvAssertBool((PetscBool)(descriptor->canonical_name != NULL &&
1023 descriptor->canonical_name[0] != '\0'),
1024 "every particle field should have a canonical DMSwarm name"));
1025 PetscCall(PicurvAssertBool((PetscBool)(descriptor->components == 1 ||
1026 descriptor->components == 3),
1027 "persistent particle fields should declare one or three components"));
1028 PetscCall(ParticleFieldIdFromName(descriptor->canonical_name, &parsed_id));
1029 PetscCall(PicurvAssertIntEqual(raw_id, (PetscInt)parsed_id,
1030 "canonical particle field names should round-trip to their IDs"));
1031 PetscCall(PicurvAssertBool((PetscBool)(strcmp(ParticleFieldName((ParticleFieldId)raw_id),
1032 descriptor->canonical_name) == 0),
1033 "ParticleFieldName should return catalog-owned PETSc text"));
1034 }
1035
1036 PetscCall(ParticleFieldIdFromName("particlevelocity", &parsed_id));
1038 "particle velocity alias should resolve case-insensitively"));
1039 PetscCall(ParticleFieldIdFromName("Migration Status", &parsed_id));
1041 "migration-status output alias should resolve to the PETSc field"));
1042
1043 PetscCall(ParticleFieldGetDescriptor(PARTICLE_FIELD_ID_PSI, &descriptor));
1044 PetscCall(PicurvAssertBool((PetscBool)((descriptor->capabilities &
1046 "Psi should advertise its active particle-to-Eulerian scatter"));
1048 "particle Psi should target Eulerian Psi"));
1049 PetscCall(ParticleFieldGetDescriptor(PARTICLE_FIELD_ID_PID, &descriptor));
1051 "particle IDs should be marked as PETSc-managed"));
1052 PetscCall(PicurvAssertIntEqual(PETSC_INT64, descriptor->data_type,
1053 "particle IDs should retain 64-bit storage metadata"));
1054
1055 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1056 unknown_name_ierr = ParticleFieldIdFromName("NotARegisteredParticleField", &parsed_id);
1057 PetscCall(PetscPopErrorHandler());
1058 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_UNKNOWN_TYPE, unknown_name_ierr,
1059 "unknown particle names should fail at the text-ingress boundary"));
1061 "failed particle name resolution should return the invalid ID"));
1062 PetscFunctionReturn(0);
1063}
1064
1065/**
1066 * @brief Verifies typed ghost updates preserve scalar, vector, face, and staggered behavior.
1067 */
1068static PetscErrorCode TestFieldCatalogTypedGhostUpdates(void)
1069{
1070 SimCtx *simCtx = NULL;
1071 UserCtx *user = NULL;
1072
1073 PetscFunctionBeginUser;
1074 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
1075
1076 PetscCall(VecSet(user->P, 4.25));
1077 PetscCall(VecSet(user->lP, -1.0));
1078 PetscCall(UpdateLocalGhosts(user, FIELD_ID_P));
1079 PetscCall(PicurvAssertVecConstant(user->lP, 4.25, 1.0e-12,
1080 "typed scalar ghost update should scatter P"));
1081
1082 PetscCall(VecSet(user->Ucat, 7.5));
1083 PetscCall(VecSet(user->lUcat, -1.0));
1084 PetscCall(UpdateLocalGhosts(user, FIELD_ID_UCAT));
1085 PetscCall(PicurvAssertVecConstant(user->lUcat, 7.5, 1.0e-12,
1086 "typed cell-vector ghost update should scatter Ucat"));
1087
1088 PetscCall(VecSet(user->IAj, 2.0));
1089 PetscCall(VecSet(user->lIAj, -1.0));
1090 PetscCall(UpdateLocalGhosts(user, FIELD_ID_IAJ));
1091 PetscCall(PicurvAssertVecConstant(user->lIAj, 2.0, 1.0e-12,
1092 "typed scalar face-field ghost update should scatter IAj"));
1093
1094 PetscCall(VecSet(user->Ucont, 3.0));
1095 PetscCall(VecSet(user->lUcont, -1.0));
1096 PetscCall(UpdateLocalGhosts(user, FIELD_ID_UCONT));
1097 PetscCall(PicurvAssertVecConstant(user->lUcont, 3.0, 1.0e-12,
1098 "typed component-staggered ghost update should scatter Ucont"));
1099
1100 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1101 PetscFunctionReturn(0);
1102}
1103
1104/**
1105 * @brief Runs the unit-setup PETSc test binary.
1106 */
1107int main(int argc, char **argv)
1108{
1109 PetscErrorCode ierr;
1110 const PicurvTestCase cases[] = {
1111 {"setup-lifecycle-core-solver-setup", TestSetupLifecycleCoreSolverSetup},
1112 {"setup-lifecycle-particle-initialization", TestSetupLifecycleParticleInitialization},
1113 {"setup-lifecycle-scatter-metrics-step-zero", TestSetupLifecycleScatterMetricsAtStepZero},
1114 {"setup-lifecycle-random-generators-and-cleanup", TestSetupLifecycleRandomGeneratorsAndCleanup},
1115 {"setup-lifecycle-cleanup-across-initialization-states", TestSetupLifecycleCleanupAcrossInitializationStates},
1116 {"setup-rejects-unimplemented-feature-flags", TestSetupRejectsUnimplementedFeatureFlags},
1117 {"iem-constant-is-configurable", TestIEMConstantIsConfigurable},
1118 {"unsolved-flow-uses-one-grid-level", TestUnsolvedFlowUsesOneGridLevel},
1119 {"particle-count-must-fit-petscint", TestParticleCountMustFitPetscInt},
1120 {"setup-rejects-nonpositive-post-interval", TestSetupRejectsNonpositivePostInterval},
1121 {"configured-iem-updates-swarm", TestConfiguredIEMUpdatesSwarm},
1122 {"configured-particle-initial-value", TestConfiguredParticleInitialValue},
1123 {"iem-relaxes-toward-own-cell-mean", TestIEMRelaxesTowardOwnCellMean},
1124 {"postprocessor-loads-saved-particle-averages", TestPostprocessorLoadsSavedParticleAverages},
1125 {"shared-runtime-fixture-contracts", TestSharedRuntimeFixtureContracts},
1126 {"field-catalog-metadata-and-views", TestFieldCatalogMetadataAndViews},
1127 {"particle-field-catalog-metadata", TestParticleFieldCatalogMetadata},
1128 {"field-catalog-typed-ghost-updates", TestFieldCatalogTypedGhostUpdates},
1129 };
1130
1131 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv setup lifecycle tests");
1132 if (ierr) {
1133 return (int)ierr;
1134 }
1135
1136 ierr = PicurvRunTests("unit-setup", cases, sizeof(cases) / sizeof(cases[0]));
1137 if (ierr) {
1138 PetscFinalize();
1139 return (int)ierr;
1140 }
1141
1142 ierr = PetscFinalize();
1143 return (int)ierr;
1144}
Header file for Particle related physics modules.
PetscErrorCode UpdateAllParticleFields(UserCtx *user)
Orchestrates the update of all physical properties for particles.
Header file for Particle Swarm management functions.
PetscErrorCode InitializeParticleSwarm(SimCtx *simCtx)
High-level particle initialization orchestrator for a simulation run.
FieldLayout layout
PetscErrorCode FieldIdFromName(const char *field_name, FieldId *field_id)
Resolve a user-facing field name once into its typed identity.
@ FIELD_CAPABILITY_GHOST_UPDATE
@ FIELD_SYNC_COMPONENT_STAGGERED
@ FIELD_SYNC_I_FACE
unsigned int capabilities
const char * FieldCanonicalName(FieldId field_id)
Return the canonical printable name for an ID.
PetscErrorCode FieldGetView(UserCtx *user, FieldId field_id, FieldView *view)
Resolve the existing DM and global/local vectors for one field.
@ FIELD_LAYOUT_I_FACE
@ FIELD_LAYOUT_COMPONENT_STAGGERED
const char * canonical_name
FieldSyncClass sync_class
PetscErrorCode FieldGetDescriptor(FieldId field_id, const FieldDescriptor **descriptor)
Return immutable metadata for a valid field identifier.
FieldId
Compile-time identity for a catalogued Eulerian field.
@ FIELD_ID_PSI
@ FIELD_ID_CSI
@ FIELD_ID_IAJ
@ FIELD_ID_UCAT
@ FIELD_ID_COORDINATES
@ FIELD_ID_NU_T
@ FIELD_ID_UCONT
@ FIELD_ID_CS
@ FIELD_ID_INVALID
@ FIELD_ID_P
@ FIELD_ID_CENTX
@ FIELD_ID_COUNT
Immutable metadata for one field identity.
Non-owning runtime objects resolved for one field and UserCtx.
PetscErrorCode InitializeEulerianState(SimCtx *simCtx)
High-level orchestrator to set the complete initial state of the Eulerian solver.
PetscErrorCode ReadSimulationFields(UserCtx *user, PetscInt ti)
Reads binary field data for velocity, pressure, and other required vectors.
Definition io.c:1434
PetscErrorCode WriteCheckpointBundle(SimCtx *simCtx, const char *reason)
Write and atomically publish one complete checkpoint bundle.
Definition io.c:2542
const char * ParticleFieldName(ParticleFieldId field_id)
Return the canonical PETSc DMSwarm name for an ID.
ParticleFieldId
Compile-time identity for a persistent solver-particle field.
@ PARTICLE_FIELD_ID_LOCATION_STATUS
@ PARTICLE_FIELD_ID_PID
@ PARTICLE_FIELD_ID_CELL_ID
@ PARTICLE_FIELD_ID_PSI
@ PARTICLE_FIELD_ID_COUNT
@ PARTICLE_FIELD_ID_DIFFUSIVITY
@ PARTICLE_FIELD_ID_INVALID
@ PARTICLE_FIELD_ID_VELOCITY
ParticleFieldRegistration registration
PetscErrorCode ParticleFieldIdFromName(const char *field_name, ParticleFieldId *field_id)
Resolve a canonical name or registered alias at a text-ingress boundary.
@ PARTICLE_FIELD_CAPABILITY_EULERIAN_SCATTER
@ PARTICLE_FIELD_REGISTRATION_PETSC
PetscErrorCode ParticleFieldGetDescriptor(ParticleFieldId field_id, const ParticleFieldDescriptor **descriptor)
Return immutable metadata for a valid particle field ID.
Immutable metadata for one persistent particle field.
PetscErrorCode PerformInitializedParticleSetup(SimCtx *simCtx)
Finalizes the simulation setup at t=0, ensuring a consistent state before time marching.
Definition runloop.c:422
PetscErrorCode InitializeRandomGenerators(UserCtx *user, PetscRandom *randx, PetscRandom *randy, PetscRandom *randz)
Initializes random number generators for assigning particle properties.
Definition setup.c:3953
PetscErrorCode SetupGridAndSolvers(SimCtx *simCtx)
The main orchestrator for setting up all grid-related components.
Definition setup.c:2157
PetscErrorCode SetupSimulationEnvironment(SimCtx *simCtx)
Verifies and prepares the complete I/O environment for a simulation run.
Definition setup.c:1819
PetscErrorCode InitializeLogicalSpaceRNGs(PetscInt base_seed, PetscRandom *rand_logic_i, PetscRandom *rand_logic_j, PetscRandom *rand_logic_k)
Initializes random number generators for logical space operations [0.0, 1.0).
Definition setup.c:3995
PetscErrorCode CreateSimulationContext(int argc, char **argv, SimCtx **p_simCtx)
Allocates and populates the master SimulationContext object.
Definition setup.c:452
PetscErrorCode UpdateLocalGhosts(UserCtx *user, FieldId field_id)
Updates the local vector (including ghost points) from its corresponding global vector.
Definition setup.c:2678
PetscBool RuntimeWalltimeGuardParsePositiveSeconds(const char *text, PetscReal *seconds_out)
Parse a positive floating-point seconds value from runtime metadata.
Definition setup.c:143
static PetscErrorCode TestParticleFieldCatalogMetadata(void)
Verifies the separate DMSwarm field catalog and its Eulerian bridge metadata.
static PetscErrorCode TestConfiguredIEMUpdatesSwarm(void)
Checks configured IEM relaxation through the production swarm update.
static PetscErrorCode TestSetupRejectsNonpositivePostInterval(void)
Ensures setup propagates invalid post intervals before either executable can loop.
static PetscErrorCode TestUnsolvedFlowUsesOneGridLevel(void)
Ensures a run that solves no pressure builds one grid level whatever -mg_level asks,...
static PetscErrorCode BuildContextOnly(SimCtx **simCtx_out, char *tmpdir, size_t tmpdir_len)
Builds only the top-level simulation context used by partial-initialization cleanup tests.
static PetscErrorCode PrepareContextOnlyConfig(char *tmpdir, size_t tmpdir_len, char *control_path, size_t control_path_len)
Creates the control-file bundle needed for the context-only cleanup test.
static PetscErrorCode TryContextWithExtraLines(const char *extra_lines, SimCtx **simCtx_out, PetscErrorCode *ierr_out, char *tmpdir, size_t tmpdir_len)
Builds a context from the fixture control file with extra lines appended.
int main(int argc, char **argv)
Runs the unit-setup PETSc test binary.
static PetscErrorCode TestIEMRelaxesTowardOwnCellMean(void)
Checks that IEM relaxes each particle toward its own cell's scattered mean.
static PetscErrorCode TestIEMConstantIsConfigurable(void)
Tests that the IEM mixing constant defaults to 2.0, follows -iem_constant, accepts zero (no micromixi...
static PetscErrorCode TestParticleCountMustFitPetscInt(void)
Ensures setup refuses a particle count whose widest swarm field PetscInt cannot index.
static PetscErrorCode AssertDirectoryExists(const char *path, const char *context)
Asserts that a directory path exists and is readable.
static PetscErrorCode TestSetupLifecycleScatterMetricsAtStepZero(void)
Tests that step-zero scalar verification emits scatter_metrics.csv during initialized setup.
static PetscErrorCode TestFieldCatalogMetadataAndViews(void)
Verifies catalog completeness, stable name resolution, and runtime view binding.
static PetscErrorCode TestSetupLifecycleRandomGeneratorsAndCleanup(void)
Tests standalone RNG initialization helpers and minimal-context cleanup.
static PetscErrorCode TestPostprocessorLoadsSavedParticleAverages(void)
Checks that post-processing loads the particle averages a checkpoint holds.
static PetscErrorCode WriteContextOnlyFile(const char *path, const char *contents)
Writes one small temporary text file used by the partial-lifecycle context-only fixture.
static PetscErrorCode BuildLifecycleContext(PetscBool enable_particles, SimCtx **simCtx_out, char *tmpdir, size_t tmpdir_len)
Builds a full setup fixture through environment, grid, BC, and domain-rank initialization.
static PetscErrorCode FreeLifecycleContext(SimCtx **simCtx_ptr)
Finalizes and frees one lifecycle test context, then clears any PETSc options used to build it.
static PetscErrorCode TestSetupLifecycleParticleInitialization(void)
Tests particle-swarm initialization and deterministic settlement on a tiny fully initialized case.
static PetscErrorCode TestSetupLifecycleCoreSolverSetup(void)
Tests the core setup lifecycle through environment, grid, BC, rank-info, and Eulerian-state initializ...
static PetscErrorCode AssertStoredLocalInfoMatchesDMs(UserCtx *user)
Asserts that one block's stored local info matches what its DMs report.
static PetscErrorCode TestSharedRuntimeFixtureContracts(void)
Tests that the shared richer runtime fixture mirrors normalized production setup contracts.
static PetscErrorCode TestSetupRejectsUnimplementedFeatureFlags(void)
Tests that setup refuses every flag whose feature is planned but not implemented.
static PetscErrorCode TestConfiguredParticleInitialValue(void)
Checks a configured particle initial value through the production setup path.
static PetscErrorCode TestFieldCatalogTypedGhostUpdates(void)
Verifies typed ghost updates preserve scalar, vector, face, and staggered behavior.
static PetscErrorCode TestSetupLifecycleCleanupAcrossInitializationStates(void)
Tests cleanup after partial and fuller setup states without requiring unsupported double-finalization...
PetscErrorCode PicurvMakeTempDir(char *path, size_t path_len)
Creates a unique temporary directory for one test case.
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 PicurvDestroyRuntimeContext(SimCtx **simCtx_ptr)
Finalizes and frees a runtime context built by PicurvBuildTinyRuntimeContext.
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 PicurvBuildTinyRuntimeContext(const char *bcs_contents, PetscBool enable_particles, SimCtx **simCtx_out, UserCtx **user_out, char *tmpdir, size_t tmpdir_len)
Builds a tiny runtime context through the real setup path for behavior-level tests.
PetscErrorCode PicurvAssertFileExists(const char *path, const char *context)
Asserts that a filesystem path exists as a readable file.
PetscErrorCode PicurvBuildTinyRuntimeContextWithOptions(const char *bcs_contents, PetscBool enable_particles, const char *extra_options, SimCtx **simCtx_out, UserCtx **user_out, char *tmpdir, size_t tmpdir_len)
Builds the tiny runtime context with extra control-file options appended.
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.
PetscErrorCode PicurvRemoveTempDir(const char *path)
Recursively removes a temporary directory created by PicurvMakeTempDir.
Shared declarations for the PICurv C test fixture and assertion layer.
Named test case descriptor consumed by PicurvRunTests.
PetscBool mom_nk_monitor_history
Definition variables.h:975
@ PERIODIC
Definition variables.h:348
UserCtx * user
Definition variables.h:770
PetscBool inletFaceDefined
Definition variables.h:1167
PetscMPIInt rank
Definition variables.h:921
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:1166
PetscInt block_number
Definition variables.h:1013
BCFace identifiedInletBCFace
Definition variables.h:1168
@ ACTIVE_AND_LOCATED
Definition variables.h:176
UserMG usermg
Definition variables.h:1078
PetscReal L_ref
Definition variables.h:900
PetscReal iem_constant
IEM mixing constant C_IEM in Omega = C_IEM Gamma / Delta^2 (default 2.0).
Definition variables.h:1010
Cmpnts max_coords
Maximum x, y, z coordinates of the bounding box.
Definition variables.h:211
char output_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:942
PetscReal dt
Definition variables.h:935
PetscInt StepsToRun
Definition variables.h:929
PetscInt np
Definition variables.h:1051
Vec Ucont
Definition variables.h:1180
PetscInt StartStep
Definition variables.h:928
Cmpnts min_coords
Minimum x, y, z coordinates of the bounding box.
Definition variables.h:210
@ MOMENTUM_SOLVER_DUALTIME_PICARD_JAMESON_RK
Definition variables.h:732
PetscScalar x
Definition variables.h:133
RankCellInfo * RankCellInfoMap
Definition variables.h:1256
VerificationScalarConfig verificationScalar
Definition variables.h:1001
BoundingBox * bboxlist
Definition variables.h:1054
char source_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:797
char eulerianSource[PETSC_MAX_PATH_LEN]
Definition variables.h:940
PetscReal mom_dt_jameson_residual_norm_noise_allowance_factor
Definition variables.h:970
PetscScalar z
Definition variables.h:133
Vec ParticleCount
Definition variables.h:1258
ScalingCtx scaling
Definition variables.h:1007
PetscInt mglevels
Definition variables.h:777
PetscInt mglevels
Definition variables.h:963
PetscInt particleRandomSeed
Base seed for every particle RNG stream (-particle_random_seed).
Definition variables.h:1052
char AnalyticalSolutionType[PETSC_MAX_PATH_LEN]
Definition variables.h:954
PetscReal mom_resid_rtol
Definition variables.h:962
Vec lUcont
Definition variables.h:1180
PetscInt step
Definition variables.h:926
DMDALocalInfo info
Definition variables.h:1153
Vec lUcat
Definition variables.h:1180
PostProcessParams * pps
Definition variables.h:1125
PetscScalar y
Definition variables.h:133
@ EXEC_MODE_SOLVER
Definition variables.h:890
@ EXEC_MODE_POSTPROCESSOR
Definition variables.h:891
PetscInt IM
Definition variables.h:1155
MGCtx * mgctx
Definition variables.h:780
BCType mathematical_type
Definition variables.h:429
ExecutionMode exec_mode
Definition variables.h:939
PetscReal mom_resid_atol
Definition variables.h:962
BoundingBox bbox
Definition variables.h:1157
MomentumSolverType mom_solver_type
Definition variables.h:960
struct ParticleFieldPlan * particleFieldPlan
Configured initial particle-field values, or NULL.
Definition variables.h:1064
@ BC_FACE_NEG_Z
Definition variables.h:320
A 3D point or vector with PetscScalar components.
Definition variables.h:132
The master context for the entire simulation.
Definition variables.h:918
User-defined context containing data specific to a single computational grid level.
Definition variables.h:1141