PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
test_io.c
Go to the documentation of this file.
1/**
2 * @file test_io.c
3 * @brief C unit tests for I/O helpers, parsers, and startup-banner output.
4 */
5
6#include "test_support.h"
7
8#include "checksum.h"
9#include "io.h"
11#include "statistics_window.h"
12#include "field_catalog.h"
13
14#include <fcntl.h>
15#include <stdio.h>
16#include <string.h>
17#include <sys/stat.h>
18#include <unistd.h>
19/**
20 * @brief Tests cadence-based Eulerian output triggering.
21 */
22
23static PetscErrorCode TestShouldWriteDataOutput(void)
24{
25 SimCtx simCtx;
26
27 PetscFunctionBeginUser;
28 PetscCall(PetscMemzero(&simCtx, sizeof(simCtx)));
29 simCtx.tiout = 5;
30
31 PetscCall(PicurvAssertBool((PetscBool)!ShouldWriteDataOutput(NULL, 5), "NULL SimCtx should never request output"));
32 PetscCall(PicurvAssertBool((PetscBool)!ShouldWriteDataOutput(&simCtx, 4), "non-cadence step should not trigger output"));
33 PetscCall(PicurvAssertBool(ShouldWriteDataOutput(&simCtx, 10), "cadence-aligned step should trigger output"));
34 PetscFunctionReturn(0);
35}
36/**
37 * @brief Tests filesystem existence checks for files and directories.
38 */
39
40static PetscErrorCode TestVerifyPathExistence(void)
41{
42 char tmpdir[PETSC_MAX_PATH_LEN];
43 char filepath[PETSC_MAX_PATH_LEN];
44 FILE *file = NULL;
45 PetscBool exists = PETSC_FALSE;
46
47 PetscFunctionBeginUser;
48 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
49 PetscCall(PetscSNPrintf(filepath, sizeof(filepath), "%s/sample.txt", tmpdir));
50
51 file = fopen(filepath, "w");
52 PetscCheck(file != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to create temp file '%s'.", filepath);
53 fputs("picurv\n", file);
54 fclose(file);
55
56 PetscCall(VerifyPathExistence(tmpdir, PETSC_TRUE, PETSC_FALSE, "temp directory", &exists));
57 PetscCall(PicurvAssertBool(exists, "VerifyPathExistence should find the temp directory"));
58
59 PetscCall(VerifyPathExistence(filepath, PETSC_FALSE, PETSC_FALSE, "temp file", &exists));
60 PetscCall(PicurvAssertBool(exists, "VerifyPathExistence should find the temp file"));
61 PetscCall(PicurvRemoveTempDir(tmpdir));
62 PetscFunctionReturn(0);
63}
64/**
65 * @brief Tests writing and reloading core Eulerian field vectors.
66 */
67
68static PetscErrorCode TestWriteAndReadSimulationFields(void)
69{
70 SimCtx *simCtx = NULL;
71 UserCtx *user = NULL;
72 char tmpdir[PETSC_MAX_PATH_LEN];
73 char path[PETSC_MAX_PATH_LEN];
74 PetscBool exists = PETSC_FALSE;
75
76 PetscFunctionBeginUser;
77 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
78 tmpdir[0] = '\0';
79 if (simCtx->rank == 0) PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
80 PetscCallMPI(MPI_Bcast(tmpdir, sizeof(tmpdir), MPI_CHAR, 0, PETSC_COMM_WORLD));
81
82 PetscCall(PetscStrncpy(simCtx->output_dir, tmpdir, sizeof(simCtx->output_dir)));
83 PetscCall(PetscStrncpy(simCtx->restart_dir, tmpdir, sizeof(simCtx->restart_dir)));
84 PetscCall(VecSet(user->P, 4.5));
85 PetscCall(VecSet(user->Nvert, 0.0));
86 PetscCall(VecSet(user->Ucat, 2.0));
87 PetscCall(VecSet(user->Ucont, 3.0));
88 PetscCall(VecSet(user->Ucont_rm1, 1.25));
89 simCtx->ti = 0.35;
90 PetscCall(PicurvPopulateIdentityMetrics(user));
91
92 PetscCall(WriteCheckpointBundle(simCtx, "test"));
93 PetscCall(PetscSNPrintf(path, sizeof(path),
94 "%s/checkpoints/step_000000000001/checkpoint.meta", tmpdir));
95 PetscCall(PetscTestFile(path, 'r', &exists));
96 PetscCall(PicurvAssertBool(exists, "checkpoint coordinator should write checkpoint.meta"));
97 PetscCall(PetscSNPrintf(path, sizeof(path),
98 "%s/checkpoints/step_000000000001/COMMITTED", tmpdir));
99 PetscCall(PetscTestFile(path, 'r', &exists));
100 PetscCall(PicurvAssertBool(exists, "checkpoint coordinator should write COMMITTED last"));
101 PetscCall(PetscSNPrintf(path, sizeof(path),
102 "%s/checkpoints/step_000000000001/eulerian/block_0000/Ucat.dat", tmpdir));
103 PetscCall(PetscTestFile(path, 'r', &exists));
104 PetscCall(PicurvAssertBool(exists, "checkpoint should use catalogued canonical field names"));
105 PetscCall(VecZeroEntries(user->P));
106 PetscCall(VecZeroEntries(user->Ucat));
107 PetscCall(VecZeroEntries(user->Ucont));
108 PetscCall(VecZeroEntries(user->Ucont_rm1));
109 simCtx->ti = 0.0;
110
111 PetscCall(ReadSimulationFields(user, simCtx->step));
112 PetscCall(PicurvAssertVecConstant(user->P, 4.5, 1.0e-12, "ReadSimulationFields should restore P"));
113 PetscCall(PicurvAssertVecConstant(user->Ucat, 2.0, 1.0e-12, "ReadSimulationFields should restore Ucat"));
114 PetscCall(PicurvAssertVecConstant(user->Ucont, 3.0, 1.0e-12, "ReadSimulationFields should restore Ucont"));
115 PetscCall(PicurvAssertVecConstant(user->Ucont_rm1, 1.25, 1.0e-12,
116 "ReadSimulationFields should restore BDF2 history"));
117 PetscCall(PicurvAssertRealNear(0.35, simCtx->ti, 1.0e-12,
118 "checkpoint physical time should be authoritative"));
119
120 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
121 if (simCtx->rank == 0) PetscCall(PicurvRemoveTempDir(tmpdir));
122 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
123 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
124 PetscFunctionReturn(0);
125}
126
127/**
128 * @brief Tests that a dimensionalizing post-processor scales each loaded field once per load.
129 * @details Scaling happens as a field is read, so reading the same checkpoint twice yields
130 * the same physical values both times: a field is scaled by its own catalog
131 * dimension, never again on a later step. At L=2, U=3, rho=5 velocity scales by 3,
132 * a face flux by 12, and pressure by 45.
133 */
135{
136 SimCtx *simCtx = NULL;
137 UserCtx *user = NULL;
138 char tmpdir[PETSC_MAX_PATH_LEN];
139
140 PetscFunctionBeginUser;
141 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
142 tmpdir[0] = '\0';
143 if (simCtx->rank == 0) PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
144 PetscCallMPI(MPI_Bcast(tmpdir, sizeof(tmpdir), MPI_CHAR, 0, PETSC_COMM_WORLD));
145
146 PetscCall(PetscStrncpy(simCtx->output_dir, tmpdir, sizeof(simCtx->output_dir)));
147 PetscCall(PetscStrncpy(simCtx->restart_dir, tmpdir, sizeof(simCtx->restart_dir)));
148 PetscCall(VecSet(user->P, 2.0));
149 PetscCall(VecSet(user->Nvert, 0.0));
150 PetscCall(VecSet(user->Ucat, 1.0));
151 PetscCall(VecSet(user->Ucont, 1.0));
152 PetscCall(VecSet(user->Ucont_rm1, 1.0));
153 PetscCall(PicurvPopulateIdentityMetrics(user));
154 PetscCall(WriteCheckpointBundle(simCtx, "test"));
155
156 PetscCall(PetscCalloc1(1, &simCtx->pps));
157 simCtx->pps->dimensionalize = PETSC_TRUE;
158 PetscCall(PetscStrncpy(simCtx->pps->source_dir, tmpdir, sizeof(simCtx->pps->source_dir)));
160 simCtx->scaling.L_ref = 2.0;
161 simCtx->scaling.U_ref = 3.0;
162 simCtx->scaling.rho_ref = 5.0;
163 simCtx->scaling.P_ref = 45.0;
164
165 for (PetscInt pass = 0; pass < 2; ++pass) {
166 PetscCall(ReadSimulationFields(user, simCtx->step));
167 PetscCall(PicurvAssertVecConstant(user->Ucat, 3.0, 1.0e-12, "loaded velocity is scaled by U once"));
168 PetscCall(PicurvAssertVecConstant(user->Ucont, 12.0, 1.0e-12, "loaded face flux is scaled by U L^2 once"));
169 PetscCall(PicurvAssertVecConstant(user->P, 90.0, 1.0e-12, "loaded pressure is scaled by rho U^2 once"));
170 }
171
172 simCtx->exec_mode = EXEC_MODE_SOLVER;
173 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
174 if (simCtx->rank == 0) PetscCall(PicurvRemoveTempDir(tmpdir));
175 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
176 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
177 PetscFunctionReturn(0);
178}
179
180/** @brief Builds the statistics fixture: one Ucat/P window with a second moment. */
182{
184
185 memset(&d, 0, sizeof(d));
186 strncpy(d.name, "production", PICURV_WINDOW_NAME_LENGTH - 1);
189 d.step_cadence = 1;
190 d.field_count = 2;
191 d.fields[0].field_id = FIELD_ID_UCAT; d.fields[0].want_second = PETSC_TRUE;
192 d.fields[1].field_id = FIELD_ID_P; d.fields[1].want_second = PETSC_TRUE;
193 return d;
194}
195
196/** @brief Attaches one accumulating window to a fixture context and primes it with samples. */
197static PetscErrorCode AttachStatisticsFixture(SimCtx *simCtx, UserCtx *user,
198 PicurvWindow *window, PicurvWindowStorage *storage,
199 const PicurvWindowDefinition *definition)
200{
201 PetscFunctionBeginUser;
202 PetscCall(PicurvWindowInit(window, definition));
203 PetscCall(PicurvWindowStorageCreate(user, definition, storage));
204 simCtx->fieldStatisticsEnabled = PETSC_TRUE;
205 simCtx->fieldStatisticsWindowCount = 1;
206 simCtx->fieldStatisticsWindows = window;
207 user->fieldStatisticsStorage = storage;
208 PetscFunctionReturn(0);
209}
210
211/** @brief Detaches the statistics fixture without disturbing the shared teardown. */
212static PetscErrorCode DetachStatisticsFixture(SimCtx *simCtx, UserCtx *user,
213 PicurvWindowStorage *storage)
214{
215 PetscFunctionBeginUser;
216 simCtx->fieldStatisticsEnabled = PETSC_FALSE;
217 simCtx->fieldStatisticsWindowCount = 0;
218 simCtx->fieldStatisticsWindows = NULL;
219 user->fieldStatisticsStorage = NULL;
220 PetscCall(PicurvWindowStorageDestroy(storage));
221 PetscFunctionReturn(0);
222}
223
224/**
225 * @brief Rewrites the bundle holding a window that closed well before the checkpoint time.
226 *
227 * Reproduces the shape a real run reaches when a bounded window finished and the
228 * simulation kept going: the saved state is complete, and its end sits behind the
229 * checkpoint by far more than one step.
230 */
231static PetscErrorCode WriteCompletedStatisticsWindow(SimCtx *simCtx, UserCtx *user,
232 PicurvWindow *window,
233 PicurvWindowStorage *storage,
234 const PicurvWindowDefinition *definition)
235{
236 PicurvWindowDefinition bounded = *definition;
237 PetscBool accepted = PETSC_FALSE;
238 PetscReal weight = 0.0;
239 char step_directory[PETSC_MAX_PATH_LEN];
240
241 PetscFunctionBeginUser;
242 bounded.bounded = PETSC_TRUE;
243 bounded.end_time = 1.0;
244 PetscCall(PicurvWindowInit(window, &bounded));
245 PetscCall(PicurvWindowOfferState(window, 0, 0.0, &accepted, &weight));
246 PetscCall(PicurvWindowOfferState(window, 1, 1.0, &accepted, &weight));
247 if (accepted) PetscCall(PicurvWindowAccumulate(user, &bounded, storage, weight));
248 PetscCall(PicurvAssertBool((PetscBool)(window->state == PICURV_WINDOW_COMPLETE),
249 "the fixture window reaches its bounded end"));
250
251 /* The run continued well past the window's end before this checkpoint. */
252 simCtx->ti = 5.0;
253 PetscCall(PetscSNPrintf(step_directory, sizeof(step_directory),
254 "%s/checkpoints/step_000000000001", simCtx->output_dir));
255 if (simCtx->rank == 0) PetscCall(PetscRMTree(step_directory));
256 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
257 PetscCall(WriteCheckpointBundle(simCtx, "test"));
258 PetscFunctionReturn(0);
259}
260
261/**
262 * @brief Verifies accumulated statistics survive a checkpoint round trip unchanged.
263 *
264 * Both halves of the state matter and are checked separately: the per-point
265 * accumulator vectors, and the window's scalar bookkeeping. Restoring only the
266 * vectors would silently resume with a broken schedule, and restoring only the
267 * scalars would report a sample count the fields do not support.
268 */
269static PetscErrorCode TestCheckpointStatisticsRoundTrip(void)
270{
271 SimCtx *simCtx = NULL;
272 UserCtx *user = NULL;
273 PicurvWindow window;
274 PicurvWindowStorage storage;
276 char tmpdir[PETSC_MAX_PATH_LEN];
277 char path[PETSC_MAX_PATH_LEN];
278 PetscBool exists = PETSC_FALSE;
279 PetscInt payload_count = 0;
280 Vec *reference = NULL;
281
282 PetscFunctionBeginUser;
283 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
284 tmpdir[0] = '\0';
285 if (simCtx->rank == 0) PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
286 PetscCallMPI(MPI_Bcast(tmpdir, sizeof(tmpdir), MPI_CHAR, 0, PETSC_COMM_WORLD));
287 PetscCall(PetscStrncpy(simCtx->output_dir, tmpdir, sizeof(simCtx->output_dir)));
288 PetscCall(PetscStrncpy(simCtx->restart_dir, tmpdir, sizeof(simCtx->restart_dir)));
289 PetscCall(PicurvPopulateIdentityMetrics(user));
290 PetscCall(VecSet(user->Nvert, 0.0));
291 PetscCall(AttachStatisticsFixture(simCtx, user, &window, &storage, &definition));
292
293 /* Three accepted states at unit weight: P takes 1, 2 and 6, so its per-point
294 * mean is 3 and its centered second moment is 14. */
295 {
296 const PetscReal scalars[3] = {1.0, 2.0, 6.0};
297
298 for (PetscInt s = 0; s < 3; ++s) {
299 PetscBool accepted = PETSC_FALSE;
300 PetscReal weight = 0.0;
301
302 PetscCall(VecSet(user->P, scalars[s]));
303 PetscCall(VecSet(user->Ucat, scalars[s]));
304 PetscCall(PicurvWindowOfferState(&window, s, (PetscReal)s, &accepted, &weight));
305 if (accepted) PetscCall(PicurvWindowAccumulate(user, &definition, &storage, weight));
306 }
307 }
308 PetscCall(PicurvAssertIntEqual(2, window.sample_count, "the anchoring state is not itself a sample"));
309
310 PetscCall(WriteCheckpointBundle(simCtx, "test"));
311 PetscCall(PetscSNPrintf(path, sizeof(path),
312 "%s/checkpoints/step_000000000001/statistics/window_0000/block_0000/P_mean.dat",
313 tmpdir));
314 PetscCall(PetscTestFile(path, 'r', &exists));
315 PetscCall(PicurvAssertBool(exists, "statistics payloads use their enumerated names"));
316 PetscCall(PetscSNPrintf(path, sizeof(path),
317 "%s/checkpoints/step_000000000001/statistics/window_0000/block_0000/Ucat_m2.dat",
318 tmpdir));
319 PetscCall(PetscTestFile(path, 'r', &exists));
320 PetscCall(PicurvAssertBool(exists, "the symmetric product is one payload, not six"));
321
322 /* Keep a reference copy of every payload, then discard the in-memory state so a
323 * restore that did nothing cannot pass. */
324 PetscCall(PicurvWindowStoragePayloadCount(&storage, &payload_count));
325 PetscCall(PetscCalloc1((size_t)payload_count, &reference));
326 for (PetscInt index = 0; index < payload_count; ++index) {
328
329 PetscCall(PicurvWindowStoragePayload(user, &definition, &storage, index, &payload));
330 PetscCall(VecDuplicate(payload.vec, &reference[index]));
331 PetscCall(VecCopy(payload.vec, reference[index]));
332 PetscCall(VecZeroEntries(payload.vec));
333 }
334 PetscCall(PicurvWindowInit(&window, &definition));
335 PetscCall(PicurvAssertIntEqual(0, window.sample_count, "the fixture is reset before restoring"));
336
337 simCtx->fieldStatisticsContinue = PETSC_TRUE;
338 PetscCall(RestoreFieldStatisticsState(simCtx, simCtx->step));
339
340 PetscCall(PicurvAssertIntEqual(2, window.sample_count, "the sample count is restored"));
341 PetscCall(PicurvAssertRealNear(2.0, window.total_weight, 1.0e-12, "the total weight is restored"));
342 PetscCall(PicurvAssertRealNear(2.0, window.represented_time, 1.0e-12,
343 "the represented time is restored"));
344 PetscCall(PicurvAssertRealNear(2.0, window.last_accepted_time, 1.0e-12,
345 "the quadrature origin is restored"));
346 PetscCall(PicurvAssertIntEqual(0, window.activation_step, "the schedule anchor is restored"));
347 PetscCall(PicurvAssertIntEqual(2, window.last_event_step,
348 "the duplicate-event guard survives the restart"));
349 PetscCall(PicurvAssertIntEqual(1, window.restart_count, "the restart lineage advances on resume"));
350 PetscCall(PicurvAssertBool((PetscBool)(window.state == PICURV_WINDOW_ACTIVE),
351 "an open window resumes active"));
352
353 /* Every payload must come back bit for bit. The binary format stores IEEE
354 * doubles and the natural ordering is decomposition independent, so anything
355 * short of an exact match is a defect rather than rounding. */
356 for (PetscInt index = 0; index < payload_count; ++index) {
358 PetscReal difference = 0.0;
359 char context[192];
360
361 PetscCall(PicurvWindowStoragePayload(user, &definition, &storage, index, &payload));
362 PetscCall(VecAXPY(reference[index], -1.0, payload.vec));
363 PetscCall(VecNorm(reference[index], NORM_INFINITY, &difference));
364 PetscCall(PetscSNPrintf(context, sizeof(context),
365 "payload '%s' is restored bit for bit", payload.name));
366 PetscCall(PicurvAssertBool((PetscBool)(difference == 0.0), context));
367 PetscCall(VecDestroy(&reference[index]));
368 }
369 PetscCall(PetscFree(reference));
370
371 /* Spot-check one interior point against the analytically known value, so the
372 * comparison above cannot pass by restoring two identically wrong vectors. */
373 {
374 PetscReal ***mean = NULL;
375
376 PetscCall(DMDAVecGetArrayRead(user->da, storage.mean[1], &mean));
377 if (user->info.xs <= 2 && 2 < user->info.xs + user->info.xm &&
378 user->info.ys <= 2 && 2 < user->info.ys + user->info.ym &&
379 user->info.zs <= 2 && 2 < user->info.zs + user->info.zm) {
380 PetscCall(PicurvAssertRealNear(4.0, mean[2][2][2], 1.0e-12,
381 "the restored scalar mean holds the accumulated value"));
382 }
383 PetscCall(DMDAVecRestoreArrayRead(user->da, storage.mean[1], &mean));
384 }
385
386 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
387 if (simCtx->rank == 0) PetscCall(PicurvRemoveTempDir(tmpdir));
388 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
389 PetscCall(DetachStatisticsFixture(simCtx, user, &storage));
390 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
391 PetscFunctionReturn(0);
392}
393
394/**
395 * @brief Verifies continuation refuses every state that would corrupt an average.
396 *
397 * A silent reset is the failure mode this guards: resuming from zero, or merging
398 * samples taken under a different definition, both produce a window whose reported
399 * sample count no longer describes the numbers it carries.
400 */
402{
403 SimCtx *simCtx = NULL;
404 UserCtx *user = NULL;
405 PicurvWindow window;
406 PicurvWindowStorage storage;
409 char tmpdir[PETSC_MAX_PATH_LEN];
410 PetscErrorCode bad = 0;
411
412 PetscFunctionBeginUser;
413 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
414 tmpdir[0] = '\0';
415 if (simCtx->rank == 0) PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
416 PetscCallMPI(MPI_Bcast(tmpdir, sizeof(tmpdir), MPI_CHAR, 0, PETSC_COMM_WORLD));
417 PetscCall(PetscStrncpy(simCtx->output_dir, tmpdir, sizeof(simCtx->output_dir)));
418 PetscCall(PetscStrncpy(simCtx->restart_dir, tmpdir, sizeof(simCtx->restart_dir)));
419 PetscCall(PicurvPopulateIdentityMetrics(user));
420 PetscCall(VecSet(user->Nvert, 0.0));
421 PetscCall(AttachStatisticsFixture(simCtx, user, &window, &storage, &definition));
422 {
423 PetscBool accepted = PETSC_FALSE;
424 PetscReal weight = 0.0;
425
426 PetscCall(PicurvWindowOfferState(&window, 0, 0.0, &accepted, &weight));
427 PetscCall(PicurvWindowOfferState(&window, 1, 1.0, &accepted, &weight));
428 if (accepted) PetscCall(PicurvWindowAccumulate(user, &definition, &storage, weight));
429 }
430 PetscCall(WriteCheckpointBundle(simCtx, "test"));
431
432 /* A hashed property changed: the saved samples describe a different average. */
433 changed = definition;
435 PetscCall(PicurvWindowInit(&window, &changed));
436 simCtx->fieldStatisticsContinue = PETSC_TRUE;
437 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
438 bad = RestoreFieldStatisticsState(simCtx, simCtx->step);
439 PetscCall(PetscPopErrorHandler());
440 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_INCOMP, bad,
441 "a changed definition is refused rather than merged"));
442
443 /* A window renamed under the same index is a different window. */
444 changed = definition;
445 strncpy(changed.name, "other", PICURV_WINDOW_NAME_LENGTH - 1);
446 PetscCall(PicurvWindowInit(&window, &changed));
447 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
448 bad = RestoreFieldStatisticsState(simCtx, simCtx->step);
449 PetscCall(PetscPopErrorHandler());
450 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_INCOMP, bad, "a renamed window is refused"));
451
452 /* Shortening a window would discard represented time it already claims. */
453 changed = definition;
454 changed.bounded = PETSC_TRUE;
455 changed.end_time = 0.5;
456 PetscCall(PicurvWindowInit(&window, &changed));
457 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
458 bad = RestoreFieldStatisticsState(simCtx, simCtx->step);
459 PetscCall(PetscPopErrorHandler());
460 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_INCOMP, bad, "a shortened window is refused"));
461
462 /* Extending it forward is permitted, because end_time is outside the hash. The
463 * saved window is still open here, so there is no former end to leave a gap
464 * after and the extension is accepted. */
465 changed = definition;
466 changed.bounded = PETSC_TRUE;
467 changed.end_time = 40.0;
468 PetscCall(PicurvWindowInit(&window, &changed));
469 PetscCall(RestoreFieldStatisticsState(simCtx, simCtx->step));
470 PetscCall(PicurvAssertIntEqual(1, window.sample_count, "an extended window keeps its samples"));
471 PetscCall(PicurvAssertBool((PetscBool)(window.state == PICURV_WINDOW_ACTIVE),
472 "an extended window resumes active"));
473
474 /* Reopening a window that already closed, across time it never sampled, would
475 * weight that gap into the first new interval. */
476 simCtx->dt = 0.1;
477 PetscCall(WriteCompletedStatisticsWindow(simCtx, user, &window, &storage, &definition));
478 changed = definition;
479 changed.bounded = PETSC_TRUE;
480 changed.end_time = 40.0;
481 PetscCall(PicurvWindowInit(&window, &changed));
482 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
483 bad = RestoreFieldStatisticsState(simCtx, simCtx->step);
484 PetscCall(PetscPopErrorHandler());
485 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_INCOMP, bad,
486 "reopening a closed window across an unsampled gap is refused"));
487
488 /* Without an explicit request, a restart starts from zero and says so. */
489 PetscCall(PicurvWindowInit(&window, &definition));
490 simCtx->fieldStatisticsContinue = PETSC_FALSE;
491 PetscCall(RestoreFieldStatisticsState(simCtx, simCtx->step));
492 PetscCall(PicurvAssertIntEqual(0, window.sample_count,
493 "statistics start fresh when continuation is not requested"));
494
495 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
496 if (simCtx->rank == 0) PetscCall(PicurvRemoveTempDir(tmpdir));
497 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
498 PetscCall(DetachStatisticsFixture(simCtx, user, &storage));
499 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
500 PetscFunctionReturn(0);
501}
502
503/**
504 * @brief A damaged statistics payload must fail bundle validation, not load silently.
505 *
506 * Statistics payloads are validated because they enter the manifest inventory the
507 * existing validator already walks. That is an easy property to believe and an easy
508 * one to lose, so it is checked by damaging a payload rather than by inspection.
509 */
511{
512 SimCtx *simCtx = NULL;
513 UserCtx *user = NULL;
514 PicurvWindow window;
515 PicurvWindowStorage storage;
517 char tmpdir[PETSC_MAX_PATH_LEN];
518 char payload_path[PETSC_MAX_PATH_LEN];
519 PetscErrorCode bad = 0;
520
521 PetscFunctionBeginUser;
522 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
523 tmpdir[0] = '\0';
524 if (simCtx->rank == 0) PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
525 PetscCallMPI(MPI_Bcast(tmpdir, sizeof(tmpdir), MPI_CHAR, 0, PETSC_COMM_WORLD));
526 PetscCall(PetscStrncpy(simCtx->output_dir, tmpdir, sizeof(simCtx->output_dir)));
527 PetscCall(PetscStrncpy(simCtx->restart_dir, tmpdir, sizeof(simCtx->restart_dir)));
528 PetscCall(PicurvPopulateIdentityMetrics(user));
529 PetscCall(VecSet(user->Nvert, 0.0));
530 PetscCall(AttachStatisticsFixture(simCtx, user, &window, &storage, &definition));
531 {
532 PetscBool accepted = PETSC_FALSE;
533 PetscReal weight = 0.0;
534
535 PetscCall(PicurvWindowOfferState(&window, 0, 0.0, &accepted, &weight));
536 PetscCall(PicurvWindowOfferState(&window, 1, 1.0, &accepted, &weight));
537 if (accepted) PetscCall(PicurvWindowAccumulate(user, &definition, &storage, weight));
538 }
539 PetscCall(WriteCheckpointBundle(simCtx, "test"));
540
541 /* Truncating one payload leaves the manifest and its commit marker intact, so
542 * only the inventory's recorded byte size can catch it. */
543 PetscCall(PetscSNPrintf(payload_path, sizeof(payload_path),
544 "%s/checkpoints/step_000000000001/statistics/window_0000/block_0000/P_mean.dat",
545 tmpdir));
546 if (simCtx->rank == 0) {
547 FILE *damaged = fopen(payload_path, "w");
548
549 PetscCheck(damaged != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN,
550 "Unable to truncate '%s'.", payload_path);
551 PetscCheck(fclose(damaged) == 0, PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE,
552 "Unable to close '%s'.", payload_path);
553 }
554 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
555
556 simCtx->fieldStatisticsContinue = PETSC_TRUE;
557 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
558 bad = RestoreFieldStatisticsState(simCtx, simCtx->step);
559 PetscCall(PetscPopErrorHandler());
560 PetscCall(PicurvAssertIntEqual(PETSC_ERR_FILE_UNEXPECTED, bad,
561 "a truncated statistics payload fails bundle validation"));
562
563 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
564 if (simCtx->rank == 0) PetscCall(PicurvRemoveTempDir(tmpdir));
565 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
566 PetscCall(DetachStatisticsFixture(simCtx, user, &storage));
567 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
568 PetscFunctionReturn(0);
569}
570
571/** @brief A run without statistics writes no statistics subtree and refuses to fake one. */
573{
574 SimCtx *simCtx = NULL;
575 UserCtx *user = NULL;
576 PicurvWindow window;
577 PicurvWindowStorage storage;
579 char tmpdir[PETSC_MAX_PATH_LEN];
580 char path[PETSC_MAX_PATH_LEN];
581 PetscBool exists = PETSC_TRUE;
582 PetscErrorCode bad = 0;
583
584 PetscFunctionBeginUser;
585 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
586 tmpdir[0] = '\0';
587 if (simCtx->rank == 0) PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
588 PetscCallMPI(MPI_Bcast(tmpdir, sizeof(tmpdir), MPI_CHAR, 0, PETSC_COMM_WORLD));
589 PetscCall(PetscStrncpy(simCtx->output_dir, tmpdir, sizeof(simCtx->output_dir)));
590 PetscCall(PetscStrncpy(simCtx->restart_dir, tmpdir, sizeof(simCtx->restart_dir)));
591 PetscCall(PicurvPopulateIdentityMetrics(user));
592
593 PetscCall(WriteCheckpointBundle(simCtx, "test"));
594 PetscCall(PetscSNPrintf(path, sizeof(path), "%s/checkpoints/step_000000000001/statistics", tmpdir));
595 PetscCall(PetscTestDirectory(path, 'r', &exists));
596 PetscCall(PicurvAssertBool((PetscBool)!exists,
597 "a run without statistics writes no statistics subtree"));
598
599 /* Continuing from that bundle must fail loudly rather than resume from zero. */
600 PetscCall(AttachStatisticsFixture(simCtx, user, &window, &storage, &definition));
601 simCtx->fieldStatisticsContinue = PETSC_TRUE;
602 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
603 bad = RestoreFieldStatisticsState(simCtx, simCtx->step);
604 PetscCall(PetscPopErrorHandler());
605 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_INCOMP, bad,
606 "missing statistics state is fatal, never silently zeroed"));
607
608 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
609 if (simCtx->rank == 0) PetscCall(PicurvRemoveTempDir(tmpdir));
610 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
611 PetscCall(DetachStatisticsFixture(simCtx, user, &storage));
612 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
613 PetscFunctionReturn(0);
614}
615
616/**
617 * @brief Verifies a committed checkpoint step is rewritten neither silently nor inconsistently.
618 *
619 * `WriteCheckpointBundle` revalidates an already-committed step instead of rewriting it,
620 * so a repeated call at the same step must leave the payloads byte-identical even when the
621 * in-memory state has since diverged. The same guard must reject a repeat call whose
622 * physical time disagrees with the committed bundle, because that means the step number no
623 * longer identifies the same solver state.
624 */
626{
627 SimCtx *simCtx = NULL;
628 UserCtx *user = NULL;
629 char tmpdir[PETSC_MAX_PATH_LEN];
630 char payload_path[PETSC_MAX_PATH_LEN];
631 struct stat first_stat;
632 struct stat second_stat;
633 PetscErrorCode mismatch_ierr = 0;
634
635 PetscFunctionBeginUser;
636 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
637 tmpdir[0] = '\0';
638 if (simCtx->rank == 0) PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
639 PetscCallMPI(MPI_Bcast(tmpdir, sizeof(tmpdir), MPI_CHAR, 0, PETSC_COMM_WORLD));
640
641 PetscCall(PetscStrncpy(simCtx->output_dir, tmpdir, sizeof(simCtx->output_dir)));
642 PetscCall(PetscStrncpy(simCtx->restart_dir, tmpdir, sizeof(simCtx->restart_dir)));
643 PetscCall(VecSet(user->P, 4.5));
644 PetscCall(VecSet(user->Nvert, 0.0));
645 PetscCall(VecSet(user->Ucat, 2.0));
646 PetscCall(VecSet(user->Ucont, 3.0));
647 PetscCall(VecSet(user->Ucont_rm1, 1.25));
648 simCtx->ti = 0.35;
649 PetscCall(PicurvPopulateIdentityMetrics(user));
650
651 PetscCall(WriteCheckpointBundle(simCtx, "cadence"));
652 PetscCall(PetscSNPrintf(payload_path, sizeof(payload_path),
653 "%s/checkpoints/step_000000000001/eulerian/block_0000/Ucat.dat", tmpdir));
654 PetscCall(PicurvAssertBool((PetscBool)(stat(payload_path, &first_stat) == 0),
655 "first checkpoint write should produce a Ucat payload"));
656
657 /* Diverge the in-memory state so a rewrite would be observable in the payload bytes. */
658 PetscCall(VecSet(user->Ucat, 99.0));
659 PetscCall(WriteCheckpointBundle(simCtx, "cadence"));
660
661 PetscCall(PicurvAssertBool((PetscBool)(stat(payload_path, &second_stat) == 0),
662 "repeated checkpoint write should leave the Ucat payload in place"));
663 /* Nanosecond resolution: a same-second rewrite would still move this. */
664 PetscCall(PicurvAssertBool((PetscBool)(first_stat.st_mtim.tv_sec == second_stat.st_mtim.tv_sec &&
665 first_stat.st_mtim.tv_nsec == second_stat.st_mtim.tv_nsec),
666 "repeated checkpoint write at a committed step should not touch payload mtime"));
667 PetscCall(PicurvAssertBool((PetscBool)(first_stat.st_size == second_stat.st_size),
668 "repeated checkpoint write at a committed step should not resize payloads"));
669
670 PetscCall(VecZeroEntries(user->Ucat));
671 PetscCall(ReadSimulationFields(user, simCtx->step));
672 PetscCall(PicurvAssertVecConstant(user->Ucat, 2.0, 1.0e-12,
673 "repeated checkpoint write must not overwrite committed payload contents"));
674
675 /* A same-step call whose physical time disagrees is a state-identity error, not a no-op. */
676 simCtx->ti = 0.75;
677 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
678 mismatch_ierr = WriteCheckpointBundle(simCtx, "cadence");
679 PetscCall(PetscPopErrorHandler());
680 PetscCall(PicurvAssertIntEqual(PETSC_ERR_FILE_UNEXPECTED, mismatch_ierr,
681 "same-step checkpoint write with a different physical time should fail"));
682
683 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
684 if (simCtx->rank == 0) PetscCall(PicurvRemoveTempDir(tmpdir));
685 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
686 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
687 PetscFunctionReturn(0);
688}
689
690/** @brief Verifies the dependency-free SHA-256 implementation against a standard vector. */
691static PetscErrorCode TestCheckpointSHA256KnownVector(void)
692{
693 PicurvSHA256Context context;
694 char digest[65];
695
696 PetscFunctionBeginUser;
697 PicurvSHA256Init(&context);
698 PicurvSHA256Update(&context, "abc", 3);
699 PicurvSHA256FinalHex(&context, digest);
700 PetscCall(PicurvAssertBool(
701 (PetscBool)!strcmp(digest, "ba7816bf8f01cfea414140de5dae2223"
702 "b00361a396177a9cb410ff61f20015ad"),
703 "SHA-256 implementation should match the standard abc test vector"));
704 PetscFunctionReturn(0);
705}
706/**
707 * @brief Tests parsing of post-processing control settings from a file.
708 */
709
710static PetscErrorCode TestParsePostProcessingSettings(void)
711{
712 SimCtx *simCtx = NULL;
713 UserCtx *user = NULL;
714 char tmpdir[PETSC_MAX_PATH_LEN];
715 char cfg_path[PETSC_MAX_PATH_LEN];
716 FILE *file = NULL;
717
718 PetscFunctionBeginUser;
719 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
720 PetscCall(PetscCalloc1(1, &simCtx->pps));
721 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
722 PetscCall(PetscSNPrintf(cfg_path, sizeof(cfg_path), "%s/post.run", tmpdir));
723 PetscCall(PetscStrncpy(simCtx->PostprocessingControlFile, cfg_path, sizeof(simCtx->PostprocessingControlFile)));
724
725 file = fopen(cfg_path, "w");
726 PetscCheck(file != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to create temp config file '%s'.", cfg_path);
727 fputs("startTime = 2\n", file);
728 fputs("endTime = 6\n", file);
729 fputs("timeStep = 2\n", file);
730 fputs("output_particles = true\n", file);
731 fputs("output_prefix = SmokeField\n", file);
732 fclose(file);
733
734 PetscCall(ParsePostProcessingSettings(simCtx));
735 PetscCall(PicurvAssertIntEqual(2, simCtx->pps->startTime, "ParsePostProcessingSettings should parse startTime"));
736 PetscCall(PicurvAssertIntEqual(6, simCtx->pps->endTime, "ParsePostProcessingSettings should parse endTime"));
737 PetscCall(PicurvAssertIntEqual(2, simCtx->pps->timeStep, "ParsePostProcessingSettings should parse timeStep"));
738 PetscCall(PicurvAssertBool(simCtx->pps->outputParticles, "ParsePostProcessingSettings should parse output_particles"));
739 PetscCall(PicurvAssertBool((PetscBool)(strcmp(simCtx->pps->output_prefix, "SmokeField") == 0),
740 "ParsePostProcessingSettings should parse output_prefix"));
741
742 /* File and command-line ingress both guard the loop after override precedence. */
743 const PetscInt intervals[] = {1, 0, -1, 1, 1, 0};
744 const char *overrides[] = {NULL, NULL, NULL, "0", "-1", "1"};
745 for (size_t n = 0; n < sizeof(intervals) / sizeof(intervals[0]); ++n) {
746 PetscErrorCode parse_ierr;
747 file = fopen(cfg_path, "w");
748 PetscCheck(file != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to reopen post config.");
749 fprintf(file, "startTime = 100\nendTime = 100\ntimeStep = %d\n", (int)intervals[n]);
750 fclose(file);
751 if (overrides[n]) PetscCall(PetscOptionsSetValue(NULL, "-timeStep", overrides[n]));
752 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
753 parse_ierr = ParsePostProcessingSettings(simCtx);
754 PetscCall(PetscPopErrorHandler());
755 PetscCall(PetscOptionsClearValue(NULL, "-timeStep"));
756 PetscCall(PicurvAssertIntEqual((n == 0 || n == 5) ? 0 : PETSC_ERR_ARG_OUTOFRANGE,
757 parse_ierr, "post interval must be positive after command-line overrides"));
758 }
759
760 PetscCall(PicurvRemoveTempDir(tmpdir));
761 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
762 PetscFunctionReturn(0);
763}
764
765/**
766 * @brief Tests trimming of leading and trailing whitespace.
767 */
768
769static PetscErrorCode TestTrimWhitespace(void)
770{
771 char value_a[] = " inlet_value ";
772 char value_b[] = " ";
773
774 PetscFunctionBeginUser;
775 TrimWhitespace(value_a);
776 PetscCall(PicurvAssertBool((PetscBool)(strcmp(value_a, "inlet_value") == 0),
777 "TrimWhitespace should remove leading and trailing whitespace"));
778
779 TrimWhitespace(value_b);
780 PetscCall(PicurvAssertBool((PetscBool)(strcmp(value_b, "") == 0),
781 "TrimWhitespace should reduce all-whitespace strings to empty"));
782 PetscFunctionReturn(0);
783}
784/**
785 * @brief Tests boundary-condition string parsers for face, type, and handler names.
786 */
787
788static PetscErrorCode TestBoundaryConditionStringParsers(void)
789{
790 BCFace face = BC_FACE_NEG_X;
791 BCType type = WALL;
793
794 PetscFunctionBeginUser;
795 PetscCall(StringToBCFace("+Zeta", &face));
796 PetscCall(PicurvAssertIntEqual(BC_FACE_POS_Z, face, "StringToBCFace should parse +Zeta"));
797
798 PetscCall(StringToBCType("periodic", &type));
799 PetscCall(PicurvAssertIntEqual(PERIODIC, type, "StringToBCType should parse PERIODIC case-insensitively"));
800
801 PetscCall(StringToBCHandlerType("constant_flux", &handler));
803 "StringToBCHandlerType should parse constant_flux"));
804 PetscCall(StringToBCHandlerType("prescribed_flow", &handler));
806 "StringToBCHandlerType should parse prescribed_flow"));
807 PetscFunctionReturn(0);
808}
809/**
810 * @brief Tests validation of boundary-type and handler compatibility.
811 */
812
813static PetscErrorCode TestValidateBCHandlerForBCType(void)
814{
815 PetscFunctionBeginUser;
817 "WALL + noslip should be a valid combination"));
819 "PERIODIC + geometric should be a valid combination"));
821 "INLET + prescribed_flow should be a valid combination"));
823 "INLET + noslip should be rejected"));
824 PetscFunctionReturn(0);
825}
826/**
827 * @brief Tests scaling-reference parsing and derived pressure scaling.
828 */
829
830static PetscErrorCode TestParseScalingInformation(void)
831{
832 SimCtx simCtx;
833
834 PetscFunctionBeginUser;
835 PetscCall(PetscMemzero(&simCtx, sizeof(simCtx)));
836
837 PetscCall(PetscOptionsClearValue(NULL, "-scaling_L_ref"));
838 PetscCall(PetscOptionsClearValue(NULL, "-scaling_U_ref"));
839 PetscCall(PetscOptionsClearValue(NULL, "-scaling_rho_ref"));
840
841 PetscCall(ParseScalingInformation(&simCtx));
842 PetscCall(PicurvAssertRealNear(1.0, simCtx.scaling.L_ref, 1.0e-12, "Default scaling_L_ref should be 1.0"));
843 PetscCall(PicurvAssertRealNear(1.0, simCtx.scaling.U_ref, 1.0e-12, "Default scaling_U_ref should be 1.0"));
844 PetscCall(PicurvAssertRealNear(1.0, simCtx.scaling.rho_ref, 1.0e-12, "Default scaling_rho_ref should be 1.0"));
845 PetscCall(PicurvAssertRealNear(1.0, simCtx.scaling.P_ref, 1.0e-12, "Default scaling_P_ref should be 1.0"));
846
847 PetscCall(PetscOptionsSetValue(NULL, "-scaling_L_ref", "2.5"));
848 PetscCall(PetscOptionsSetValue(NULL, "-scaling_U_ref", "4.0"));
849 PetscCall(PetscOptionsSetValue(NULL, "-scaling_rho_ref", "1.2"));
850
851 PetscCall(ParseScalingInformation(&simCtx));
852 PetscCall(PicurvAssertRealNear(2.5, simCtx.scaling.L_ref, 1.0e-12, "scaling_L_ref should honor options"));
853 PetscCall(PicurvAssertRealNear(4.0, simCtx.scaling.U_ref, 1.0e-12, "scaling_U_ref should honor options"));
854 PetscCall(PicurvAssertRealNear(1.2, simCtx.scaling.rho_ref, 1.0e-12, "scaling_rho_ref should honor options"));
855 PetscCall(PicurvAssertRealNear(19.2, simCtx.scaling.P_ref, 1.0e-12, "scaling_P_ref should be rho_ref*U_ref^2"));
856
857 PetscCall(PetscOptionsClearValue(NULL, "-scaling_L_ref"));
858 PetscCall(PetscOptionsClearValue(NULL, "-scaling_U_ref"));
859 PetscCall(PetscOptionsClearValue(NULL, "-scaling_rho_ref"));
860 PetscFunctionReturn(0);
861}
862/**
863 * @brief Captures the startup banner into a temporary file-backed buffer.
864 */
865static PetscErrorCode CaptureBannerOutput(SimCtx *simCtx, char *captured, size_t captured_len)
866{
867 char tmpdir[PETSC_MAX_PATH_LEN];
868 char capture_path[PETSC_MAX_PATH_LEN];
869 FILE *capture_file = NULL;
870 int saved_stdout = -1;
871 int capture_fd = -1;
872 size_t bytes_read = 0;
873 PetscErrorCode ierr;
874
875 PetscFunctionBeginUser;
876 PetscCheck(simCtx != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "SimCtx cannot be NULL.");
877 PetscCheck(captured != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Capture buffer cannot be NULL.");
878 PetscCheck(captured_len > 0, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ, "Capture buffer must be non-empty.");
879
880 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
881 PetscCall(PetscSNPrintf(capture_path, sizeof(capture_path), "%s/banner.log", tmpdir));
882
883 fflush(stdout);
884 saved_stdout = dup(STDOUT_FILENO);
885 PetscCheck(saved_stdout >= 0, PETSC_COMM_SELF, PETSC_ERR_SYS, "dup(STDOUT_FILENO) failed.");
886 capture_fd = open(capture_path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
887 PetscCheck(capture_fd >= 0, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to open capture file '%s'.", capture_path);
888 PetscCheck(dup2(capture_fd, STDOUT_FILENO) >= 0, PETSC_COMM_SELF, PETSC_ERR_SYS, "dup2() failed while redirecting stdout.");
889 close(capture_fd);
890 capture_fd = -1;
891
892 ierr = DisplayBanner(simCtx);
893 fflush(stdout);
894 PetscCheck(dup2(saved_stdout, STDOUT_FILENO) >= 0, PETSC_COMM_SELF, PETSC_ERR_SYS, "dup2() failed while restoring stdout.");
895 close(saved_stdout);
896 saved_stdout = -1;
897 PetscCall(ierr);
898
899 capture_file = fopen(capture_path, "r");
900 PetscCheck(capture_file != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to read capture file '%s'.", capture_path);
901 bytes_read = fread(captured, 1, captured_len - 1, capture_file);
902 captured[bytes_read] = '\0';
903 fclose(capture_file);
904 PetscCall(PicurvRemoveTempDir(tmpdir));
905 PetscCallMPI(MPI_Bcast(captured, (PetscMPIInt)captured_len, MPI_CHAR, 0, PETSC_COMM_WORLD));
906 PetscFunctionReturn(0);
907}
908/**
909 * @brief Asserts that captured banner output contains one expected substring.
910 */
911static PetscErrorCode AssertCapturedContains(const char *captured, const char *needle, const char *message)
912{
913 PetscFunctionBeginUser;
914 PetscCall(PicurvAssertBool((PetscBool)(strstr(captured, needle) != NULL), message));
915 PetscFunctionReturn(0);
916}
917/**
918 * @brief Asserts that captured banner output omits one forbidden substring.
919 */
920static PetscErrorCode AssertCapturedOmits(const char *captured, const char *needle, const char *message)
921{
922 PetscFunctionBeginUser;
923 PetscCall(PicurvAssertBool((PetscBool)(strstr(captured, needle) == NULL), message));
924 PetscFunctionReturn(0);
925}
926/**
927 * @brief Tests that the startup banner reports statistics monitoring in every state.
928 *
929 * The banner is the one place a log records whether monitoring was active, so all three
930 * states must be distinguishable after the fact: a live cadence, a subsystem that is
931 * accumulating with the console silenced, and a run that configured no window at all.
932 * The banner reads only the window array and the console cadence, so this fixture sets
933 * those directly rather than allocating accumulator storage it would never touch.
934 */
935
937{
938 SimCtx *simCtx = NULL;
939 UserCtx *user = NULL;
940 PicurvWindow window;
942 char captured[16384];
943
944 PetscFunctionBeginUser;
945 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
946 PetscCall(PicurvWindowInit(&window, &definition));
947 simCtx->OnlySetup = PETSC_FALSE;
948 simCtx->StepsToRun = 5;
949 PetscCall(PetscStrncpy(simCtx->eulerianSource, "solve", sizeof(simCtx->eulerianSource)));
950
951 /* A run with statistics accumulating and the console cadence live. */
952 simCtx->fieldStatisticsEnabled = PETSC_TRUE;
953 simCtx->fieldStatisticsWindowCount = 1;
954 simCtx->fieldStatisticsWindows = &window;
955 simCtx->statisticsConsoleOutputFreq = 5;
956 PetscCall(CaptureBannerOutput(simCtx, captured, sizeof(captured)));
957 PetscCall(AssertCapturedContains(captured, "Statistics Console Cadence : every 5 step(s), 1 window(s)",
958 "DisplayBanner should report the live statistics console cadence"));
959
960 /* Accumulating, but with console reporting switched off. */
961 simCtx->statisticsConsoleOutputFreq = 0;
962 PetscCall(CaptureBannerOutput(simCtx, captured, sizeof(captured)));
963 PetscCall(AssertCapturedContains(captured, "Statistics Console Cadence : DISABLED (1 window(s) accumulating)",
964 "DisplayBanner should distinguish a silenced console from an inactive subsystem"));
965
966 /* No window configured: the row still appears, so its absence is never ambiguous. */
967 simCtx->fieldStatisticsEnabled = PETSC_FALSE;
968 simCtx->fieldStatisticsWindowCount = 0;
969 simCtx->fieldStatisticsWindows = NULL;
970 PetscCall(CaptureBannerOutput(simCtx, captured, sizeof(captured)));
971 PetscCall(AssertCapturedContains(captured, "Statistics Console Cadence : DISABLED (no window configured)",
972 "DisplayBanner should record that no statistics window was configured"));
973
974 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
975 PetscFunctionReturn(0);
976}
977/**
978 * @brief Tests conditional startup-banner fields across particle and analytical cases.
979 */
980
982{
983 SimCtx *simCtx = NULL;
984 UserCtx *user = NULL;
985 char captured[16384];
986
987 PetscFunctionBeginUser;
988 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
989 simCtx->OnlySetup = PETSC_FALSE;
990 simCtx->StepsToRun = 5;
991 simCtx->immersed = PETSC_FALSE;
992 PetscCall(PetscStrncpy(simCtx->eulerianSource, "solve", sizeof(simCtx->eulerianSource)));
993 simCtx->particleConsoleOutputFreq = 7;
994 simCtx->LoggingFrequency = 4;
997
998 PetscCall(CaptureBannerOutput(simCtx, captured, sizeof(captured)));
999 PetscCall(AssertCapturedContains(captured, "Run Mode : Full Simulation",
1000 "DisplayBanner should include the run mode"));
1001 PetscCall(AssertCapturedContains(captured, "Field/Restart Cadence : every 2 step(s)",
1002 "DisplayBanner should include field/restart cadence"));
1003 PetscCall(AssertCapturedContains(captured, "Immersed Boundary : DISABLED",
1004 "DisplayBanner should include immersed-boundary state"));
1005 PetscCall(AssertCapturedContains(captured, "Periodic Axes (BC-derived) : I=NO, J=NO, K=NO",
1006 "DisplayBanner should include BC-derived periodic axes"));
1007 PetscCall(AssertCapturedContains(captured, "Number of Particles : 0",
1008 "DisplayBanner should include the total particle count"));
1009 PetscCall(AssertCapturedContains(captured, "Eulerian State Source : initial condition (Zero)",
1010 "DisplayBanner should identify a fresh solve IC as the Eulerian source"));
1011 PetscCall(AssertCapturedOmits(captured, "Particle Console Cadence",
1012 "DisplayBanner should omit particle console cadence when no particles are configured"));
1013 PetscCall(AssertCapturedOmits(captured, "Particle Log Row Sampling",
1014 "DisplayBanner should omit particle row sampling when no particles are configured"));
1015 PetscCall(AssertCapturedOmits(captured, "Particle Restart Mode",
1016 "DisplayBanner should omit particle restart mode when no particles are configured"));
1017 PetscCall(AssertCapturedOmits(captured, "Particle Initialization Mode",
1018 "DisplayBanner should omit particle initialization mode when no particles are configured"));
1019 PetscCall(AssertCapturedOmits(captured, "Interpolation Method",
1020 "DisplayBanner should omit interpolation method when no particles are configured"));
1021 PetscCall(AssertCapturedContains(captured, "Initial Pseudo-CFL (Courant)",
1022 "DisplayBanner should report pseudo-CFL for the dual-time momentum solver"));
1023 PetscCall(AssertCapturedContains(captured, "Pseudo-CFL Adaptation",
1024 "DisplayBanner should report the active dual-time controller"));
1025 PetscCall(AssertCapturedContains(captured, "Console Log Level",
1026 "DisplayBanner should report the effective console log level"));
1027 PetscCall(AssertCapturedContains(captured, "Profiling Timestep Output",
1028 "DisplayBanner should report profiling mode"));
1029 PetscCall(AssertCapturedContains(captured, "Runtime Memory Log",
1030 "DisplayBanner should report runtime-memory logging state"));
1031
1033 PetscCall(CaptureBannerOutput(simCtx, captured, sizeof(captured)));
1034 PetscCall(AssertCapturedContains(captured, "Momentum Equation Solver : Newton Krylov",
1035 "DisplayBanner should identify the selected Newton-Krylov solver"));
1036 PetscCall(AssertCapturedOmits(captured, "Initial Pseudo-CFL (Courant)",
1037 "DisplayBanner must not report pseudo-CFL for Newton-Krylov"));
1038 PetscCall(AssertCapturedContains(captured, "Newton-Krylov PETSc Controls",
1039 "DisplayBanner should report the active Newton-Krylov control family"));
1041
1043 simCtx->solutionConvergencePeriodSteps = 11;
1044 PetscCall(CaptureBannerOutput(simCtx, captured, sizeof(captured)));
1045 PetscCall(AssertCapturedContains(captured, "Solution Convergence Mode : PERIODIC_DETERMINISTIC",
1046 "DisplayBanner should identify the active periodic convergence mode"));
1047 PetscCall(AssertCapturedContains(captured, "Convergence Period : 11 step(s)",
1048 "DisplayBanner should report the active periodic convergence period"));
1050 PetscCall(CaptureBannerOutput(simCtx, captured, sizeof(captured)));
1051 PetscCall(AssertCapturedOmits(captured, "Convergence Period",
1052 "DisplayBanner should omit periodic-only convergence fields when inactive"));
1053
1054 simCtx->StartStep = 3;
1055 simCtx->np = 8;
1056 simCtx->i_periodic = 1;
1057 user->periodic_translation_valid[0] = PETSC_TRUE;
1058 user->periodic_translation[0] = (Cmpnts){1.0, 0.0, 0.0};
1059 simCtx->particleConsoleOutputFreq = 0;
1060 simCtx->LoggingFrequency = 4;
1061 PetscCall(PetscStrncpy(simCtx->particleRestartMode, "load", sizeof(simCtx->particleRestartMode)));
1062 PetscCall(CaptureBannerOutput(simCtx, captured, sizeof(captured)));
1063 PetscCall(AssertCapturedContains(captured, "Eulerian State Source : restart step 3",
1064 "DisplayBanner should identify restart authority"));
1065 PetscCall(AssertCapturedOmits(captured, "initial condition (",
1066 "DisplayBanner should not present an IC as active during restart"));
1067 PetscCall(AssertCapturedContains(captured, "Number of Particles : 8",
1068 "DisplayBanner should include the active particle count"));
1069 PetscCall(AssertCapturedContains(captured, "Periodic I Translation",
1070 "DisplayBanner should include validated periodic translation"));
1071 PetscCall(AssertCapturedContains(captured, "Particle Periodic Wrapping : UNSUPPORTED",
1072 "DisplayBanner should distinguish Eulerian periodicity from particle wrapping"));
1073 PetscCall(AssertCapturedContains(captured, "Particle Console Cadence : DISABLED",
1074 "DisplayBanner should show disabled particle console cadence when particles are configured"));
1075 PetscCall(AssertCapturedContains(captured, "Particle Log Row Sampling : every 4 particle(s)",
1076 "DisplayBanner should include particle row sampling when particles are configured"));
1077 PetscCall(AssertCapturedContains(captured, "Particle Restart Mode : load",
1078 "DisplayBanner should include particle restart mode for restarted particle runs"));
1079 PetscCall(AssertCapturedContains(captured, "Particle Initialization Mode: Point Source",
1080 "DisplayBanner should include particle initialization mode when particles are configured"));
1081 PetscCall(AssertCapturedContains(captured, "Interpolation Method : Trilinear (direct cell-center)",
1082 "DisplayBanner should include default interpolation method when particles are configured"));
1083 PetscCall(AssertCapturedOmits(captured, "Particles Initialized At",
1084 "DisplayBanner should omit inlet-face placement details for point-source particle initialization"));
1085
1086 simCtx->StartStep = 0;
1087 PetscCall(PetscStrncpy(simCtx->eulerianSource, "load", sizeof(simCtx->eulerianSource)));
1088 PetscCall(CaptureBannerOutput(simCtx, captured, sizeof(captured)));
1089 PetscCall(AssertCapturedContains(captured, "Eulerian State Source : load",
1090 "DisplayBanner should identify load authority"));
1091 PetscCall(AssertCapturedOmits(captured, "initial condition (",
1092 "DisplayBanner should not present an IC as active in load mode"));
1093
1094 simCtx->StartStep = 0;
1095 simCtx->particleConsoleOutputFreq = 6;
1097 user->inletFaceDefined = PETSC_TRUE;
1098 user->identifiedInletBCFace = (BCFace)0;
1099 PetscCall(PetscStrncpy(simCtx->eulerianSource, "analytical", sizeof(simCtx->eulerianSource)));
1100 PetscCall(PetscStrncpy(simCtx->AnalyticalSolutionType, "ZERO_FLOW", sizeof(simCtx->AnalyticalSolutionType)));
1101 PetscCall(CaptureBannerOutput(simCtx, captured, sizeof(captured)));
1102 PetscCall(AssertCapturedContains(captured, "Analytical Solution Type : ZERO_FLOW",
1103 "DisplayBanner should include the analytical solution type for analytical runs"));
1104 PetscCall(AssertCapturedContains(captured, "Particle Console Cadence : every 6 step(s)",
1105 "DisplayBanner should include active particle console cadence when particles are configured"));
1106 PetscCall(AssertCapturedContains(captured, "Particle Initialization Mode: Surface: Random",
1107 "DisplayBanner should include particle initialization mode for analytical particle runs"));
1108 PetscCall(AssertCapturedContains(captured, "Particles Initialized At",
1109 "DisplayBanner should include inlet-face placement details for surface particle initialization"));
1110
1111 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1112 PetscFunctionReturn(0);
1113}
1114/**
1115 * @brief Runs the unit-io PETSc test binary.
1116 */
1117
1118int main(int argc, char **argv)
1119{
1120 PetscErrorCode ierr;
1121 const PicurvTestCase cases[] = {
1122 {"should-write-data-output", TestShouldWriteDataOutput},
1123 {"verify-path-existence", TestVerifyPathExistence},
1124 {"write-and-read-simulation-fields", TestWriteAndReadSimulationFields},
1125 {"postprocessor-read-scales-each-load-once", TestPostprocessorReadScalesEachLoadOnce},
1126 {"checkpoint-same-step-rewrite-rejected", TestCheckpointSameStepRewriteIsRejected},
1127 {"display-banner-reports-statistics-cadence", TestDisplayBannerReportsStatisticsCadence},
1128 {"checkpoint-statistics-round-trip", TestCheckpointStatisticsRoundTrip},
1129 {"checkpoint-statistics-continuation-guards", TestCheckpointStatisticsContinuationGuards},
1130 {"checkpoint-statistics-payload-is-validated", TestCheckpointStatisticsPayloadIsValidated},
1131 {"checkpoint-statistics-absent-when-disabled", TestCheckpointStatisticsAbsentWhenDisabled},
1132 {"checkpoint-sha256-known-vector", TestCheckpointSHA256KnownVector},
1133 {"parse-post-processing-settings", TestParsePostProcessingSettings},
1134 {"trim-whitespace", TestTrimWhitespace},
1135 {"bc-string-parsers", TestBoundaryConditionStringParsers},
1136 {"validate-bc-handler-for-type", TestValidateBCHandlerForBCType},
1137 {"parse-scaling-information", TestParseScalingInformation},
1138 {"display-banner-startup-summary", TestDisplayBannerTracksConditionalStartupFields},
1139 };
1140
1141 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv I/O tests");
1142 if (ierr) {
1143 return (int)ierr;
1144 }
1145
1146 ierr = PicurvRunTests("unit-io", cases, sizeof(cases) / sizeof(cases[0]));
1147 if (ierr) {
1148 PetscFinalize();
1149 return (int)ierr;
1150 }
1151
1152 ierr = PetscFinalize();
1153 return (int)ierr;
1154}
Small dependency-free SHA-256 utility for persistent metadata identity.
void PicurvSHA256Init(PicurvSHA256Context *context)
Initialize an incremental SHA-256 calculation.
Definition checksum.c:62
void PicurvSHA256Update(PicurvSHA256Context *context, const void *data, size_t length)
Add bytes to an incremental SHA-256 calculation.
Definition checksum.c:74
void PicurvSHA256FinalHex(PicurvSHA256Context *context, char digest_hex[65])
Finish a SHA-256 calculation and return a lowercase hexadecimal digest.
Definition checksum.c:98
Incremental SHA-256 state.
Definition checksum.h:14
Authoritative identities and storage metadata for persistent Eulerian fields.
@ FIELD_ID_UCAT
@ FIELD_ID_P
Public interface for data input/output routines.
PetscErrorCode ParsePostProcessingSettings(SimCtx *simCtx)
Initializes post-processing settings from a config file and command-line overrides.
Definition io.c:3240
PetscErrorCode ParseScalingInformation(SimCtx *simCtx)
Parses physical scaling parameters from command-line options.
Definition io.c:3425
PetscErrorCode StringToBCHandlerType(const char *str, BCHandlerType *handler_out)
Converts a BC handler token (implementation strategy) to BCHandlerType.
Definition io.c:717
PetscErrorCode ReadSimulationFields(UserCtx *user, PetscInt ti)
Reads binary field data for velocity, pressure, and other required vectors.
Definition io.c:1471
PetscBool ShouldWriteDataOutput(const SimCtx *simCtx, PetscInt completed_step)
Returns whether full field/restart output should be written for the.
Definition io.c:426
PetscErrorCode ValidateBCHandlerForBCType(BCType type, BCHandlerType handler)
Validates that a selected handler is compatible with a mathematical BC type.
Definition io.c:735
void TrimWhitespace(char *str)
Removes leading and trailing ASCII whitespace from a mutable string.
Definition io.c:393
PetscErrorCode RestoreFieldStatisticsState(SimCtx *simCtx, PetscInt ti)
Restores field-statistics window state and accumulators from a checkpoint.
Definition io.c:1686
PetscErrorCode StringToBCFace(const char *str, BCFace *face_out)
Converts a face-token string (e.g., "-Xi", "+Eta") to the internal BCFace enum.
Definition io.c:687
PetscErrorCode VerifyPathExistence(const char *path, PetscBool is_dir, PetscBool is_optional, const char *description, PetscBool *exists)
A parallel-safe helper to verify the existence of a generic file or directory path.
Definition io.c:1136
PetscErrorCode StringToBCType(const char *str, BCType *type_out)
Converts a mathematical BC type string (e.g., "PERIODIC", "WALL") to BCType.
Definition io.c:702
PetscErrorCode DisplayBanner(SimCtx *simCtx)
Displays a structured banner summarizing the simulation configuration.
Definition io.c:2800
PetscErrorCode WriteCheckpointBundle(SimCtx *simCtx, const char *reason)
Write and atomically publish one complete checkpoint bundle.
Definition io.c:2554
Per-window PETSc accumulator storage and pointwise application.
Vec * mean
One per field, matching that field's layout.
Vec vec
Borrowed accumulator vector; never owned by the caller.
PetscErrorCode PicurvWindowStorageCreate(UserCtx *user, const PicurvWindowDefinition *definition, PicurvWindowStorage *storage)
Allocates the accumulator state one window owns on one block.
PetscErrorCode PicurvWindowStoragePayload(UserCtx *user, const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, PetscInt index, PicurvStatisticsPayload *payload)
Resolves one enumerated payload of a window's storage.
char name[96]
File basename, no extension.
PetscErrorCode PicurvWindowAccumulate(UserCtx *user, const PicurvWindowDefinition *definition, PicurvWindowStorage *storage, PetscReal weight)
Applies one accepted completed state to a window's accumulators.
PetscErrorCode PicurvWindowStorageDestroy(PicurvWindowStorage *storage)
Releases accumulator state previously created for one window.
PetscErrorCode PicurvWindowStoragePayloadCount(const PicurvWindowStorage *storage, PetscInt *count)
Reports how many checkpointable vectors one window's storage holds.
One checkpointable accumulator vector, resolved by enumeration index.
Independent accumulator state for one window on one block.
Window lifecycle, scheduling, and weighting for the field-statistics pipeline.
PetscInt last_event_step
Guards against a step being offered twice.
PetscInt sample_count
PetscReal last_accepted_time
Right edge of the last represented interval.
PicurvWindowState state
PicurvWindowFieldRequest fields[16]
PetscInt restart_count
Restart segments this state descends from.
PetscReal end_time
Requested end; ignored when bounded is false.
PicurvCadenceKind cadence_kind
PetscReal total_weight
PetscInt step_cadence
Used when cadence_kind is step; must be positive.
#define PICURV_WINDOW_NAME_LENGTH
Maximum stored length of a window name, including the terminator.
@ PICURV_WINDOW_COMPLETE
Bounded end reached; accepts nothing further.
@ PICURV_WINDOW_ACTIVE
Accepting due states.
PetscErrorCode PicurvWindowInit(PicurvWindow *window, const PicurvWindowDefinition *definition)
Validates a definition and initializes a window to the pending state.
PetscErrorCode PicurvWindowOfferState(PicurvWindow *window, PetscInt step, PetscReal time, PetscBool *accepted, PetscReal *weight)
Offers one completed state to a window and reports the decision.
PetscBool want_second
Also keep the centered second moment.
PetscBool bounded
False for an open-ended window.
PetscInt activation_step
Step at which the window became active.
PetscInt field_id
Catalogued Eulerian field identity.
@ PICURV_WEIGHTING_PHYSICAL_TIME
Weight is the represented interval.
@ PICURV_WEIGHTING_SAMPLE
Equal weight per accepted state.
@ PICURV_CADENCE_STEP
Every n completed steps from activation.
PetscReal represented_time
Physical time the window covers.
Runtime state of one window.
The scientifically immutable definition of one window.
static PetscErrorCode TestCheckpointSameStepRewriteIsRejected(void)
Verifies a committed checkpoint step is rewritten neither silently nor inconsistently.
Definition test_io.c:625
static PetscErrorCode TestParseScalingInformation(void)
Tests scaling-reference parsing and derived pressure scaling.
Definition test_io.c:830
static PetscErrorCode DetachStatisticsFixture(SimCtx *simCtx, UserCtx *user, PicurvWindowStorage *storage)
Detaches the statistics fixture without disturbing the shared teardown.
Definition test_io.c:212
int main(int argc, char **argv)
Runs the unit-io PETSc test binary.
Definition test_io.c:1118
static PetscErrorCode TestValidateBCHandlerForBCType(void)
Tests validation of boundary-type and handler compatibility.
Definition test_io.c:813
static PetscErrorCode TestCheckpointStatisticsContinuationGuards(void)
Verifies continuation refuses every state that would corrupt an average.
Definition test_io.c:401
static PetscErrorCode AssertCapturedOmits(const char *captured, const char *needle, const char *message)
Asserts that captured banner output omits one forbidden substring.
Definition test_io.c:920
static PetscErrorCode TestPostprocessorReadScalesEachLoadOnce(void)
Tests that a dimensionalizing post-processor scales each loaded field once per load.
Definition test_io.c:134
static PetscErrorCode TestParsePostProcessingSettings(void)
Tests parsing of post-processing control settings from a file.
Definition test_io.c:710
static PicurvWindowDefinition StatisticsFixtureDefinition(void)
Builds the statistics fixture: one Ucat/P window with a second moment.
Definition test_io.c:181
static PetscErrorCode TestCheckpointStatisticsPayloadIsValidated(void)
A damaged statistics payload must fail bundle validation, not load silently.
Definition test_io.c:510
static PetscErrorCode TestCheckpointStatisticsAbsentWhenDisabled(void)
A run without statistics writes no statistics subtree and refuses to fake one.
Definition test_io.c:572
static PetscErrorCode TestCheckpointSHA256KnownVector(void)
Verifies the dependency-free SHA-256 implementation against a standard vector.
Definition test_io.c:691
static PetscErrorCode TestDisplayBannerTracksConditionalStartupFields(void)
Tests conditional startup-banner fields across particle and analytical cases.
Definition test_io.c:981
static PetscErrorCode TestDisplayBannerReportsStatisticsCadence(void)
Tests that the startup banner reports statistics monitoring in every state.
Definition test_io.c:936
static PetscErrorCode TestVerifyPathExistence(void)
Tests filesystem existence checks for files and directories.
Definition test_io.c:40
static PetscErrorCode AssertCapturedContains(const char *captured, const char *needle, const char *message)
Asserts that captured banner output contains one expected substring.
Definition test_io.c:911
static PetscErrorCode TestTrimWhitespace(void)
Tests trimming of leading and trailing whitespace.
Definition test_io.c:769
static PetscErrorCode TestShouldWriteDataOutput(void)
Tests cadence-based Eulerian output triggering.
Definition test_io.c:23
static PetscErrorCode WriteCompletedStatisticsWindow(SimCtx *simCtx, UserCtx *user, PicurvWindow *window, PicurvWindowStorage *storage, const PicurvWindowDefinition *definition)
Rewrites the bundle holding a window that closed well before the checkpoint time.
Definition test_io.c:231
static PetscErrorCode TestCheckpointStatisticsRoundTrip(void)
Verifies accumulated statistics survive a checkpoint round trip unchanged.
Definition test_io.c:269
static PetscErrorCode TestBoundaryConditionStringParsers(void)
Tests boundary-condition string parsers for face, type, and handler names.
Definition test_io.c:788
static PetscErrorCode CaptureBannerOutput(SimCtx *simCtx, char *captured, size_t captured_len)
Captures the startup banner into a temporary file-backed buffer.
Definition test_io.c:865
static PetscErrorCode TestWriteAndReadSimulationFields(void)
Tests writing and reloading core Eulerian field vectors.
Definition test_io.c:68
static PetscErrorCode AttachStatisticsFixture(SimCtx *simCtx, UserCtx *user, PicurvWindow *window, PicurvWindowStorage *storage, const PicurvWindowDefinition *definition)
Attaches one accumulating window to a fixture context and primes it with samples.
Definition test_io.c:197
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 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 PicurvPopulateIdentityMetrics(UserCtx *user)
Populates identity metric vectors on the minimal grid fixture.
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.
PetscInt fieldStatisticsWindowCount
Definition variables.h:932
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
PetscBool inletFaceDefined
Definition variables.h:1100
PetscMPIInt rank
Definition variables.h:862
PetscInt statisticsConsoleOutputFreq
Definition variables.h:934
BCFace identifiedInletBCFace
Definition variables.h:1101
@ PARTICLE_INIT_SURFACE_RANDOM
Random placement on the inlet face.
Definition variables.h:710
@ PARTICLE_INIT_POINT_SOURCE
All particles at a fixed (psrc_x,psrc_y,psrc_z) — for validation.
Definition variables.h:712
char output_prefix[256]
Definition variables.h:769
PetscBool dimensionalize
Whether derived output leaves non-dimensional form, from global_operations.dimensionalize.
Definition variables.h:789
PetscInt tiout
Definition variables.h:871
PetscReal L_ref
Definition variables.h:841
BCHandlerType
Defines the specific computational "strategy" for a boundary handler.
Definition variables.h:329
@ BC_HANDLER_PERIODIC_GEOMETRIC
Definition variables.h:340
@ BC_HANDLER_PERIODIC_DRIVEN_CONSTANT_FLUX
Definition variables.h:342
@ BC_HANDLER_INLET_PROFILE_FROM_FILE
Definition variables.h:336
@ BC_HANDLER_WALL_NOSLIP
Definition variables.h:331
char output_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:881
PetscReal dt
Definition variables.h:874
PetscInt StepsToRun
Definition variables.h:870
PetscInt timeStep
Definition variables.h:761
PetscInt np
Definition variables.h:990
PetscBool fieldStatisticsEnabled
Definition variables.h:931
Vec Ucont
Definition variables.h:1113
PetscInt StartStep
Definition variables.h:869
PetscBool OnlySetup
Definition variables.h:875
@ MOMENTUM_SOLVER_DUALTIME_PICARD_JAMESON_RK
Definition variables.h:694
@ MOMENTUM_SOLVER_NEWTON_KRYLOV
Definition variables.h:695
PetscInt solutionConvergencePeriodSteps
Definition variables.h:925
char particleRestartMode[16]
Definition variables.h:996
struct PicurvWindow * fieldStatisticsWindows
Definition variables.h:933
char source_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:756
char eulerianSource[PETSC_MAX_PATH_LEN]
Definition variables.h:879
ParticleInitializationType ParticleInitialization
Definition variables.h:994
ScalingCtx scaling
Definition variables.h:946
PetscBool outputParticles
Definition variables.h:764
struct PicurvWindowStorage * fieldStatisticsStorage
Definition variables.h:1136
char AnalyticalSolutionType[PETSC_MAX_PATH_LEN]
Definition variables.h:893
PetscInt particleConsoleOutputFreq
Definition variables.h:872
Vec Ucont_rm1
Definition variables.h:1121
PetscInt i_periodic
Definition variables.h:953
PetscInt step
Definition variables.h:867
DMDALocalInfo info
Definition variables.h:1086
PostProcessParams * pps
Definition variables.h:1058
@ EXEC_MODE_SOLVER
Definition variables.h:832
@ EXEC_MODE_POSTPROCESSOR
Definition variables.h:833
Cmpnts periodic_translation[3]
Definition variables.h:1095
Vec Nvert
Definition variables.h:1113
@ SOLUTION_CONVERGENCE_PERIODIC_DETERMINISTIC
Definition variables.h:703
@ SOLUTION_CONVERGENCE_STEADY_DETERMINISTIC
Definition variables.h:702
PetscBool periodic_translation_valid[3]
Definition variables.h:1096
SolutionConvergenceMode solutionConvergenceMode
Definition variables.h:924
PetscReal P_ref
Definition variables.h:844
ExecutionMode exec_mode
Definition variables.h:878
PetscInt startTime
Definition variables.h:759
PetscReal rho_ref
Definition variables.h:843
PetscReal ti
Definition variables.h:868
PetscReal U_ref
Definition variables.h:842
MomentumSolverType mom_solver_type
Definition variables.h:899
PetscInt immersed
Definition variables.h:891
char PostprocessingControlFile[PETSC_MAX_PATH_LEN]
Definition variables.h:1057
char restart_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:880
PetscInt LoggingFrequency
Definition variables.h:1020
PetscBool fieldStatisticsContinue
Definition variables.h:938
BCFace
Identifies the six logical faces of a structured computational block.
Definition variables.h:287
@ BC_FACE_NEG_X
Definition variables.h:288
@ BC_FACE_POS_Z
Definition variables.h:290
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