PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
io.c
Go to the documentation of this file.
1/**
2 * @file io.c
3 * @brief Implementation of data input/output routines, focusing on grid configuration.
4 *
5 * This module provides functions to parse grid geometry information, either from
6 * command-line options for programmatically generated grids or by reading the
7 * header of a grid definition file.
8 */
9
10#include "io.h"
11#include "checksum.h"
12#include "field_catalog.h"
15
16#include <errno.h>
17#include <stdlib.h>
18#include <sys/stat.h>
19#include <unistd.h>
20
21#define PICURV_CHECKPOINT_FORMAT "picurv-checkpoint"
22#define PICURV_CHECKPOINT_VERSION 1
23#define PICURV_CHECKPOINTS_DIRECTORY "checkpoints"
24#define PICURV_EULERIAN_DIRECTORY "eulerian"
25#define PICURV_PARTICLE_DIRECTORY "particles"
26#define PICURV_STATISTICS_DIRECTORY "statistics"
27#define PICURV_CHECKPOINT_STEP_WIDTH 12
28
29// =============================================================================
30// STATIC (PRIVATE) VARIABLES FOR ONE-TIME FILE READ
31// =============================================================================
32
33/** @brief Stores the number of blocks read from the grid file. */
34static PetscInt g_nblk_from_file = 0;
35/** @brief Caches the IM dimensions for all blocks read from the grid file. */
36static PetscInt* g_IMs_from_file = NULL;
37/** @brief Caches the JM dimensions for all blocks read from the grid file. */
38static PetscInt* g_JMs_from_file = NULL;
39/** @brief Caches the KM dimensions for all blocks read from the grid file. */
40static PetscInt* g_KMs_from_file = NULL;
41/** @brief A flag to ensure the grid file is read only once. */
42static PetscBool g_file_has_been_read = PETSC_FALSE;
43
44/**
45 * @brief Copies the owned entries of a ghosted scalar DMDA vector to its global vector.
46 *
47 * DMLocalToGlobal with INSERT_VALUES is unsupported for multidirection-periodic
48 * DMDAs in PETSc. Explicitly copying only the uniquely owned region preserves
49 * INSERT semantics without reducing duplicate periodic ghost entries.
50 */
51static PetscErrorCode CopyOwnedLocalScalarToGlobal(DM dm, Vec local_vec, Vec global_vec)
52{
53 DMDALocalInfo info;
54 const PetscScalar ***local_array = NULL;
55 PetscScalar ***global_array = NULL;
56
57 PetscFunctionBeginUser;
58
59 PetscCall(DMDAGetLocalInfo(dm, &info));
60 PetscCall(DMDAVecGetArrayRead(dm, local_vec, &local_array));
61 PetscCall(DMDAVecGetArray(dm, global_vec, &global_array));
62 for (PetscInt k = info.zs; k < info.zs + info.zm; ++k)
63 for (PetscInt j = info.ys; j < info.ys + info.ym; ++j)
64 for (PetscInt i = info.xs; i < info.xs + info.xm; ++i)
65 global_array[k][j][i] = local_array[k][j][i];
66 PetscCall(DMDAVecRestoreArray(dm, global_vec, &global_array));
67 PetscCall(DMDAVecRestoreArrayRead(dm, local_vec, &local_array));
68 PetscFunctionReturn(0);
69}
70
71/** @brief Implementation of \ref CheckpointFieldIsEnabled(). */
72PetscBool CheckpointFieldIsEnabled(const SimCtx *simCtx, const FieldDescriptor *descriptor)
73{
74 const unsigned int availability = descriptor ? descriptor->availability : FIELD_AVAILABILITY_ALWAYS;
75
76 if (!simCtx || !descriptor || !(descriptor->capabilities & FIELD_CAPABILITY_CHECKPOINT)) return PETSC_FALSE;
77 if ((availability & FIELD_AVAILABILITY_TURBULENCE) && !simCtx->les) return PETSC_FALSE;
78 if ((availability & FIELD_AVAILABILITY_LES_DYNAMIC) && simCtx->les != DYNAMIC_SMAGORINSKY) return PETSC_FALSE;
79 if ((availability & FIELD_AVAILABILITY_PARTICLES) && simCtx->np <= 0) return PETSC_FALSE;
80 if ((availability & FIELD_AVAILABILITY_WALL_MODEL) && !simCtx->wallfunction) return PETSC_FALSE;
81 return PETSC_TRUE;
82}
83
84/**
85 * @brief Format any level of the statistics subtree, from the root down to one payload.
86 *
87 * This is the only place the statistics layout is written down. The directory
88 * creator, the payload writer, the manifest inventory, and the restart reader all
89 * ask for the level they need, so a path cannot be built one way and looked for
90 * another.
91 *
92 * Statistics payloads are block scoped exactly as Eulerian payloads are, with a
93 * window level above the block level, so a multiblock run keeps one accumulator tree
94 * per block under each window.
95 *
96 * Pass @p root NULL for a bundle-relative path, and stop early by passing a negative
97 * @p window or @p block, or a NULL @p payload_name.
98 */
99static PetscErrorCode FormatStatisticsPath(const char *root, PetscInt window, PetscInt block,
100 const char *payload_name, char *path, size_t path_size)
101{
102 size_t used = 0;
103
104 PetscFunctionBeginUser;
105 PetscCheck(path != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Output path is required.");
106 if (root) PetscCall(PetscSNPrintf(path, path_size, "%s/%s", root, PICURV_STATISTICS_DIRECTORY));
107 else PetscCall(PetscSNPrintf(path, path_size, "%s", PICURV_STATISTICS_DIRECTORY));
108 if (window < 0) PetscFunctionReturn(0);
109
110 PetscCall(PetscStrlen(path, &used));
111 PetscCall(PetscSNPrintf(path + used, path_size - used, "/window_%04" PetscInt_FMT, window));
112 if (block < 0) PetscFunctionReturn(0);
113
114 PetscCall(PetscStrlen(path, &used));
115 PetscCall(PetscSNPrintf(path + used, path_size - used, "/block_%04" PetscInt_FMT, block));
116 if (!payload_name) PetscFunctionReturn(0);
117
118 PetscCall(PetscStrlen(path, &used));
119 PetscCall(PetscSNPrintf(path + used, path_size - used, "/%s.dat", payload_name));
120 PetscFunctionReturn(0);
121}
122
123/** @brief Gather a vector in decomposition-independent natural ordering onto rank zero. */
124static PetscErrorCode GatherVectorToRankZero(Vec field_vec, Vec *sequential_vec)
125{
126 DM dm = NULL;
127 const char *dm_type = NULL;
128 Vec natural_vec = NULL;
129 VecScatter scatter = NULL;
130
131 PetscFunctionBeginUser;
132 PetscCheck(field_vec != NULL && sequential_vec != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
133 "A source vector and output vector pointer are required.");
134 *sequential_vec = NULL;
135
136 PetscCall(VecGetDM(field_vec, &dm));
137 if (dm) PetscCall(DMGetType(dm, &dm_type));
138 if (dm_type && !strcmp(dm_type, DMDA)) {
139 PetscCall(DMDACreateNaturalVector(dm, &natural_vec));
140 PetscCall(DMDAGlobalToNaturalBegin(dm, field_vec, INSERT_VALUES, natural_vec));
141 PetscCall(DMDAGlobalToNaturalEnd(dm, field_vec, INSERT_VALUES, natural_vec));
142 }
143
144 PetscCall(VecScatterCreateToZero(natural_vec ? natural_vec : field_vec, &scatter, sequential_vec));
145 PetscCall(VecScatterBegin(scatter, natural_vec ? natural_vec : field_vec, *sequential_vec,
146 INSERT_VALUES, SCATTER_FORWARD));
147 PetscCall(VecScatterEnd(scatter, natural_vec ? natural_vec : field_vec, *sequential_vec,
148 INSERT_VALUES, SCATTER_FORWARD));
149 PetscCall(VecScatterDestroy(&scatter));
150 PetscCall(VecDestroy(&natural_vec));
151 PetscFunctionReturn(0);
152}
153
154/** @brief Compute and cache a rank-count-independent hash of the active grid geometry. */
155static PetscErrorCode ComputeCheckpointGeometrySHA256(SimCtx *simCtx, UserCtx *user, char digest_hex[65])
156{
157 PicurvSHA256Context hash_context;
158 char metadata[256];
159
160 PetscFunctionBeginUser;
161 PetscCheck(simCtx != NULL && user != NULL && digest_hex != NULL,
162 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Geometry hash inputs cannot be NULL.");
163 if (simCtx->checkpointGeometryHashReady) {
164 PetscCall(PetscStrncpy(digest_hex, simCtx->checkpointGeometrySHA256, 65));
165 PetscFunctionReturn(0);
166 }
167
168 if (simCtx->rank == 0) PicurvSHA256Init(&hash_context);
169 for (PetscInt block = 0; block < simCtx->block_number; ++block) {
170 FieldView coordinates;
171 Vec sequential_vec = NULL;
172
173 PetscCall(FieldGetView(&user[block], FIELD_ID_COORDINATES, &coordinates));
174 PetscCall(GatherVectorToRankZero(coordinates.global_vec, &sequential_vec));
175 if (simCtx->rank == 0) {
176 const PetscScalar *values = NULL;
177 PetscInt value_count = 0;
178 size_t metadata_length = 0;
179
180 PetscCall(PetscSNPrintf(metadata, sizeof(metadata),
181 "block=%" PetscInt_FMT ";im=%" PetscInt_FMT ";jm=%" PetscInt_FMT
182 ";km=%" PetscInt_FMT ";periodic=%d,%d,%d;",
183 block, user[block].IM, user[block].JM, user[block].KM,
184 (int)simCtx->i_periodic, (int)simCtx->j_periodic, (int)simCtx->k_periodic));
185 PetscCall(PetscStrlen(metadata, &metadata_length));
186 PicurvSHA256Update(&hash_context, metadata, metadata_length);
187 PetscCall(VecGetSize(sequential_vec, &value_count));
188 PicurvSHA256Update(&hash_context, &value_count, sizeof(value_count));
189 PetscCall(VecGetArrayRead(sequential_vec, &values));
190 PicurvSHA256Update(&hash_context, values, (size_t)value_count * sizeof(*values));
191 PetscCall(VecRestoreArrayRead(sequential_vec, &values));
192 }
193 PetscCall(VecDestroy(&sequential_vec));
194 }
195
196 if (simCtx->rank == 0) PicurvSHA256FinalHex(&hash_context, digest_hex);
197 PetscCallMPI(MPI_Bcast(digest_hex, 65, MPI_CHAR, 0, PETSC_COMM_WORLD));
198 PetscCall(PetscStrncpy(simCtx->checkpointGeometrySHA256, digest_hex, 65));
199 simCtx->checkpointGeometryHashReady = PETSC_TRUE;
200 PetscFunctionReturn(0);
201}
202
203/** @brief Format the canonical directory name for one completed step. */
204static PetscErrorCode FormatCheckpointStepDirectory(const char *root, PetscInt step, char *path, size_t path_size)
205{
206 PetscFunctionBeginUser;
207 PetscCheck(root != NULL && path != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
208 "Checkpoint root and output path are required.");
209 PetscCall(PetscSNPrintf(path, path_size, "%s/%s/step_%0*" PetscInt_FMT,
211 PetscFunctionReturn(0);
212}
213
214/** @brief Resolve either an exact bundle or a run/output root to one step bundle. */
215static PetscErrorCode ResolveCheckpointStepDirectory(const char *source_root, PetscInt step,
216 char *path, size_t path_size)
217{
218 char metadata_path[PETSC_MAX_PATH_LEN];
219 PetscBool exact_bundle = PETSC_FALSE;
220
221 PetscFunctionBeginUser;
222 PetscCheck(source_root != NULL && path != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
223 "Checkpoint source and output path are required.");
224 PetscCall(PetscSNPrintf(metadata_path, sizeof(metadata_path), "%s/checkpoint.meta", source_root));
225 PetscCall(PetscTestFile(metadata_path, 'r', &exact_bundle));
226 if (exact_bundle) PetscCall(PetscStrncpy(path, source_root, path_size));
227 else PetscCall(FormatCheckpointStepDirectory(source_root, step, path, path_size));
228 PetscFunctionReturn(0);
229}
230
231/** @brief Create one directory on rank zero and report failures collectively. */
232static PetscErrorCode CreateCheckpointDirectoryCollective(const SimCtx *simCtx, const char *path)
233{
234 PetscMPIInt create_failed = 0;
235
236 PetscFunctionBeginUser;
237 PetscCheck(simCtx != NULL && path != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
238 "Simulation context and directory path are required.");
239 if (simCtx->rank == 0 && mkdir(path, 0777) != 0) {
240 struct stat directory_stat;
241 if (errno != EEXIST || stat(path, &directory_stat) != 0 || !S_ISDIR(directory_stat.st_mode)) {
242 create_failed = 1;
243 }
244 }
245 PetscCallMPI(MPI_Bcast(&create_failed, 1, MPI_INT, 0, PETSC_COMM_WORLD));
246 PetscCheck(!create_failed, PETSC_COMM_WORLD, PETSC_ERR_FILE_OPEN,
247 "Unable to create checkpoint directory '%s'.", path);
248 PetscFunctionReturn(0);
249}
250
251/** @brief Validate a committed bundle and return selected authoritative metadata. */
252static PetscErrorCode ValidateCheckpointBundle(SimCtx *simCtx, UserCtx *user,
253 const char *checkpoint_directory,
254 PetscInt expected_step,
255 PetscReal *physical_time,
256 PetscInt *particle_count,
257 PetscBool *particles_saved,
258 PetscBool *les_saved)
259{
260 char metadata_path[PETSC_MAX_PATH_LEN];
261 char commit_path[PETSC_MAX_PATH_LEN];
262 char expected_digest[65] = "";
263 char actual_digest[65] = "";
264 char format[64] = "";
265 char saved_geometry_digest[65] = "";
266 char current_geometry_digest[65] = "";
267 PetscOptions options = NULL;
268 PetscInt version = 0;
269 PetscInt saved_step = -1;
270 PetscInt payload_count = 0;
271 PetscInt saved_particle_count = 0;
272 PetscReal saved_time = 0.0;
273 PetscBool saved_particles = PETSC_FALSE;
274 PetscBool saved_les = PETSC_FALSE;
275 PetscBool found = PETSC_FALSE;
276 FILE *commit_file = NULL;
277
278 PetscFunctionBeginUser;
279 PetscCheck(simCtx != NULL && user != NULL && checkpoint_directory != NULL,
280 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Checkpoint validation inputs cannot be NULL.");
281 PetscCall(PetscSNPrintf(metadata_path, sizeof(metadata_path), "%s/checkpoint.meta", checkpoint_directory));
282 PetscCall(PetscSNPrintf(commit_path, sizeof(commit_path), "%s/COMMITTED", checkpoint_directory));
283
284 PetscCall(PetscTestFile(metadata_path, 'r', &found));
285 PetscCheck(found, PETSC_COMM_WORLD, PETSC_ERR_FILE_OPEN,
286 "Checkpoint metadata is missing: %s", metadata_path);
287 PetscCall(PetscTestFile(commit_path, 'r', &found));
288 PetscCheck(found, PETSC_COMM_WORLD, PETSC_ERR_FILE_OPEN,
289 "Checkpoint is not committed: %s", checkpoint_directory);
290
291 commit_file = fopen(commit_path, "r");
292 PetscCheck(commit_file != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN,
293 "Unable to read checkpoint commit marker '%s'.", commit_path);
294 PetscCheck(fgets(expected_digest, sizeof(expected_digest), commit_file) != NULL,
295 PETSC_COMM_SELF, PETSC_ERR_FILE_READ,
296 "Checkpoint commit marker '%s' is empty.", commit_path);
297 PetscCheck(fclose(commit_file) == 0, PETSC_COMM_SELF, PETSC_ERR_FILE_READ,
298 "Unable to close checkpoint commit marker '%s'.", commit_path);
299 TrimWhitespace(expected_digest);
300 PetscCheck(strlen(expected_digest) == 64, PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED,
301 "Checkpoint commit marker '%s' does not contain a SHA-256 digest.", commit_path);
302 PetscCall(PicurvSHA256File(metadata_path, actual_digest));
303 PetscCheck(!strcmp(expected_digest, actual_digest), PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED,
304 "Checkpoint metadata hash mismatch in '%s'.", checkpoint_directory);
305
306 PetscCall(PetscOptionsCreate(&options));
307 PetscCall(PetscOptionsInsertFile(PETSC_COMM_WORLD, options, metadata_path, PETSC_TRUE));
308 PetscCall(PetscOptionsGetString(options, NULL, "-checkpoint_format", format, sizeof(format), &found));
309 PetscCheck(found && !strcmp(format, PICURV_CHECKPOINT_FORMAT), PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED,
310 "Unsupported checkpoint format '%s' in '%s'.", found ? format : "<missing>", metadata_path);
311 PetscCall(PetscOptionsGetInt(options, NULL, "-checkpoint_version", &version, &found));
312 PetscCheck(found && version == PICURV_CHECKPOINT_VERSION, PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED,
313 "Unsupported checkpoint version %" PetscInt_FMT " in '%s'; expected %d.",
314 version, metadata_path, PICURV_CHECKPOINT_VERSION);
315 PetscCall(PetscOptionsGetInt(options, NULL, "-checkpoint_step", &saved_step, &found));
316 PetscCheck(found && saved_step == expected_step, PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED,
317 "Checkpoint step is %" PetscInt_FMT ", expected %" PetscInt_FMT ".",
318 saved_step, expected_step);
319 PetscCall(PetscOptionsGetReal(options, NULL, "-checkpoint_time", &saved_time, &found));
320 PetscCheck(found, PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED,
321 "Checkpoint '%s' does not record physical time.", metadata_path);
322 PetscCall(PetscOptionsGetString(options, NULL, "-checkpoint_geometry_sha256",
323 saved_geometry_digest, sizeof(saved_geometry_digest), &found));
324 PetscCheck(found, PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED,
325 "Checkpoint '%s' does not record geometry identity.", metadata_path);
326 PetscCall(ComputeCheckpointGeometrySHA256(simCtx, user, current_geometry_digest));
327 PetscCheck(!strcmp(saved_geometry_digest, current_geometry_digest), PETSC_COMM_WORLD, PETSC_ERR_ARG_INCOMP,
328 "Checkpoint geometry/layout does not match the active grid.");
329
330 PetscCall(PetscOptionsGetInt(options, NULL, "-checkpoint_payload_count", &payload_count, &found));
331 PetscCheck(found && payload_count > 0, PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED,
332 "Checkpoint '%s' has no payload inventory.", metadata_path);
333 PetscCall(PetscOptionsGetInt(options, NULL, "-checkpoint_particle_count",
334 &saved_particle_count, &found));
335 PetscCheck(found && saved_particle_count >= 0, PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED,
336 "Checkpoint '%s' has no valid particle count.", metadata_path);
337 PetscCall(PetscOptionsGetBool(options, NULL, "-checkpoint_particles", &saved_particles, &found));
338 PetscCheck(found, PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED,
339 "Checkpoint '%s' does not record particle-state availability.", metadata_path);
340 PetscCall(PetscOptionsGetBool(options, NULL, "-checkpoint_les", &saved_les, &found));
341 PetscCheck(found, PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED,
342 "Checkpoint '%s' does not record LES-state availability.", metadata_path);
343 for (PetscInt payload = 0; payload < payload_count; ++payload) {
344 char option_name[96];
345 char relative_path[PETSC_MAX_PATH_LEN];
346 char payload_path[PETSC_MAX_PATH_LEN];
347 long long expected_bytes = -1;
348 char expected_bytes_text[64];
349 struct stat payload_stat;
350
351 PetscCall(PetscSNPrintf(option_name, sizeof(option_name),
352 "-checkpoint_payload_%" PetscInt_FMT "_path", payload));
353 PetscCall(PetscOptionsGetString(options, NULL, option_name,
354 relative_path, sizeof(relative_path), &found));
355 PetscCheck(found, PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED,
356 "Checkpoint payload %" PetscInt_FMT " has no path.", payload);
357 PetscCall(PetscSNPrintf(option_name, sizeof(option_name),
358 "-checkpoint_payload_%" PetscInt_FMT "_bytes", payload));
359 PetscCall(PetscOptionsGetString(options, NULL, option_name,
360 expected_bytes_text, sizeof(expected_bytes_text), &found));
361 if (found) expected_bytes = strtoll(expected_bytes_text, NULL, 10);
362 PetscCheck(found && expected_bytes >= 0, PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED,
363 "Checkpoint payload %" PetscInt_FMT " has no valid byte size.", payload);
364 PetscCall(PetscSNPrintf(payload_path, sizeof(payload_path), "%s/%s",
365 checkpoint_directory, relative_path));
366 PetscCheck(stat(payload_path, &payload_stat) == 0 && S_ISREG(payload_stat.st_mode),
367 PETSC_COMM_WORLD, PETSC_ERR_FILE_OPEN,
368 "Checkpoint payload is missing: %s", payload_path);
369 PetscCheck((long long)payload_stat.st_size == expected_bytes,
370 PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED,
371 "Checkpoint payload '%s' is %lld bytes; expected %lld.",
372 payload_path, (long long)payload_stat.st_size, expected_bytes);
373 }
374 PetscCall(PetscOptionsDestroy(&options));
375 if (physical_time) *physical_time = saved_time;
376 if (particle_count) *particle_count = saved_particle_count;
377 if (particles_saved) *particles_saved = saved_particles;
378 if (les_saved) *les_saved = saved_les;
379 PetscFunctionReturn(0);
380}
381
382
383// =============================================================================
384// PUBLIC FUNCTION IMPLEMENTATIONS
385// =============================================================================
386
387/**
388 * @brief Implementation of \ref TrimWhitespace().
389 * @details Full API contract (arguments, ownership, side effects) is documented with
390 * the header declaration in `include/io.h`.
391 * @see TrimWhitespace()
392 */
393void TrimWhitespace(char *str) {
394 if (!str) return;
395 if (str[0] == '\0') return;
396
397 char *start = str;
398 // Find the first non-whitespace character
399 while (isspace((unsigned char)*start)) {
400 start++;
401 }
402
403 // Find the end of the string
404 char *end = str + strlen(str) - 1;
405 // Move backwards from the end to find the last non-whitespace character
406 while (end > start && isspace((unsigned char)*end)) {
407 end--;
408 }
409
410 // Null-terminate after the last non-whitespace character
411 *(end + 1) = '\0';
412
413 // If there was leading whitespace, shift the string to the left
414 if (str != start) {
415 memmove(str, start, (end - start) + 2); // +2 to include the new null terminator
416 }
417}
418
419/**
420 * @brief Implementation of \ref ShouldWriteDataOutput().
421 * @details Full API contract (arguments, ownership, side effects) is documented with
422 * the header declaration in `include/io.h`.
423 * @see ShouldWriteDataOutput()
424 */
425
426PetscBool ShouldWriteDataOutput(const SimCtx *simCtx, PetscInt completed_step)
427{
428 if (!simCtx) {
429 return PETSC_FALSE;
430 }
431 return (PetscBool)(simCtx->tiout > 0 && completed_step > 0 && completed_step % simCtx->tiout == 0);
432}
433
434
435#undef __FUNCT__
436#define __FUNCT__ "ReadGridGenerationInputs"
437/**
438 * @brief Internal helper implementation: `ReadGridGenerationInputs()`.
439 * @details Local to this translation unit.
440 */
442{
443 PetscErrorCode ierr;
444 SimCtx *simCtx = user->simCtx;
445 PetscInt nblk = simCtx->block_number;
446 PetscInt block_index = user->_this;
447 PetscBool found;
448
449 // Temporary arrays to hold the parsed values for ALL blocks
450 PetscInt *IMs = NULL, *JMs = NULL, *KMs = NULL, *cgrids = NULL;
451 PetscReal *xMins = NULL, *xMaxs = NULL, *rxs = NULL;
452 PetscReal *yMins = NULL, *yMaxs = NULL, *rys = NULL;
453 PetscReal *zMins = NULL, *zMaxs = NULL, *rzs = NULL;
454
455 PetscFunctionBeginUser;
457
458 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Reading generated grid inputs for block %d.\n", simCtx->rank, block_index);
459
460 if (block_index >= nblk) {
461 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Block index %d is out of range for nblk=%d", block_index, nblk);
462 }
463
464 // --- Allocate temporary storage for all array options ---
465 ierr = PetscMalloc4(nblk, &IMs, nblk, &JMs, nblk, &KMs, nblk, &cgrids); CHKERRQ(ierr);
466 ierr = PetscMalloc6(nblk, &xMins, nblk, &xMaxs, nblk, &rxs, nblk, &yMins, nblk, &yMaxs, nblk, &rys); CHKERRQ(ierr);
467 ierr = PetscMalloc3(nblk, &zMins, nblk, &zMaxs, nblk, &rzs); CHKERRQ(ierr);
468
469 // --- Set default values for the temporary arrays ---
470 for (PetscInt i = 0; i < nblk; ++i) {
471 IMs[i] = 10; JMs[i] = 10; KMs[i] = 10; cgrids[i] = 0;
472 xMins[i] = 0.0; xMaxs[i] = 1.0; rxs[i] = 1.0;
473 yMins[i] = 0.0; yMaxs[i] = 1.0; rys[i] = 1.0;
474 zMins[i] = 0.0; zMaxs[i] = 1.0; rzs[i] = 1.0;
475 }
476
477 // --- Parse the array options from the command line / control file ---
478 PetscInt count;
479 count = nblk; ierr = PetscOptionsGetIntArray(NULL, NULL, "-im", IMs, &count, &found); CHKERRQ(ierr);
480 count = nblk; ierr = PetscOptionsGetIntArray(NULL, NULL, "-jm", JMs, &count, &found); CHKERRQ(ierr);
481 count = nblk; ierr = PetscOptionsGetIntArray(NULL, NULL, "-km", KMs, &count, &found); CHKERRQ(ierr);
482 count = nblk; ierr = PetscOptionsGetRealArray(NULL, NULL, "-xMins", xMins, &count, &found); CHKERRQ(ierr);
483 count = nblk; ierr = PetscOptionsGetRealArray(NULL, NULL, "-xMaxs", xMaxs, &count, &found); CHKERRQ(ierr);
484 count = nblk; ierr = PetscOptionsGetRealArray(NULL, NULL, "-rxs", rxs, &count, &found); CHKERRQ(ierr);
485 count = nblk; ierr = PetscOptionsGetRealArray(NULL, NULL, "-yMins", yMins, &count, &found); CHKERRQ(ierr);
486 count = nblk; ierr = PetscOptionsGetRealArray(NULL, NULL, "-yMaxs", yMaxs, &count, &found); CHKERRQ(ierr);
487 count = nblk; ierr = PetscOptionsGetRealArray(NULL, NULL, "-rys", rys, &count, &found); CHKERRQ(ierr);
488 count = nblk; ierr = PetscOptionsGetRealArray(NULL, NULL, "-zMins", zMins, &count, &found); CHKERRQ(ierr);
489 count = nblk; ierr = PetscOptionsGetRealArray(NULL, NULL, "-zMaxs", zMaxs, &count, &found); CHKERRQ(ierr);
490 count = nblk; ierr = PetscOptionsGetRealArray(NULL, NULL, "-rzs", rzs, &count, &found); CHKERRQ(ierr);
491 count = nblk; ierr = PetscOptionsGetIntArray(NULL, NULL, "-cgrids", cgrids, &count, &found); CHKERRQ(ierr);
492
493 /* Domain bounds are supplied in physical units, exactly like a `.picgrid`. A file
494 * grid is divided by the reference length when the conductor publishes it as an
495 * asset; a programmatically generated one has no such staging step, so the same
496 * division happens here. Without it this mode alone would take non-dimensional
497 * input while post-processing multiplied its coordinates back by L_ref, inflating
498 * the reported geometry whenever length_ref differed from one. */
499 const PetscReal length_ref = simCtx->scaling.L_ref;
500
501 PetscCheck(length_ref > 0.0, PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
502 "Reference length L_ref must be positive to non-dimensionalize the "
503 "programmatic domain bounds. Got %g", (double)length_ref);
504
505 // --- Assign the parsed values to the specific UserCtx struct passed in ---
506 user->IM = IMs[block_index];
507 user->JM = JMs[block_index];
508 user->KM = KMs[block_index];
509 user->Min_X = xMins[block_index] / length_ref;
510 user->Max_X = xMaxs[block_index] / length_ref;
511 user->rx = rxs[block_index];
512 user->Min_Y = yMins[block_index] / length_ref;
513 user->Max_Y = yMaxs[block_index] / length_ref;
514 user->ry = rys[block_index];
515 user->Min_Z = zMins[block_index] / length_ref;
516 user->Max_Z = zMaxs[block_index] / length_ref;
517 user->rz = rzs[block_index];
518 user->cgrid = cgrids[block_index];
519
520 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Block %d grid generation inputs set: IM=%d, JM=%d, KM=%d\n",
521 simCtx->rank, block_index, user->IM, user->JM, user->KM);
522 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Block %d bounds after L_ref=%.4g: X=[%.4g, %.4g], "
523 "Y=[%.4g, %.4g], Z=[%.4g, %.4g]\n",
524 simCtx->rank, block_index, (double)length_ref,
525 user->Min_X, user->Max_X, user->Min_Y, user->Max_Y, user->Min_Z, user->Max_Z);
526
527 // --- Clean up temporary storage ---
528 ierr = PetscFree4(IMs, JMs, KMs, cgrids); CHKERRQ(ierr);
529 ierr = PetscFree6(xMins, xMaxs, rxs, yMins, yMaxs, rys); CHKERRQ(ierr);
530 ierr = PetscFree3(zMins, zMaxs, rzs); CHKERRQ(ierr);
531
533 PetscFunctionReturn(0);
534}
535
536/**
537 * @brief Internal helper implementation: `PopulateFinestUserGridResolutionFromOptions()`.
538 * @details Local to this translation unit.
539 */
540PetscErrorCode PopulateFinestUserGridResolutionFromOptions(UserCtx *finest_users, PetscInt nblk)
541{
542 PetscErrorCode ierr;
543 PetscBool found;
544 PetscInt *IMs = NULL, *JMs = NULL, *KMs = NULL;
545 SimCtx *simCtx = NULL;
546
547 PetscFunctionBeginUser;
548
549 if (!finest_users) {
550 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "finest_users cannot be NULL.");
551 }
552 if (nblk <= 0) {
553 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "nblk must be positive. Got %d.", nblk);
554 }
555 simCtx = finest_users[0].simCtx;
556
557 ierr = PetscMalloc3(nblk, &IMs, nblk, &JMs, nblk, &KMs); CHKERRQ(ierr);
558 for (PetscInt i = 0; i < nblk; ++i) {
559 IMs[i] = 10; JMs[i] = 10; KMs[i] = 10;
560 }
561
562 PetscInt count;
563 count = nblk; ierr = PetscOptionsGetIntArray(NULL, NULL, "-im", IMs, &count, &found); CHKERRQ(ierr);
564 count = nblk; ierr = PetscOptionsGetIntArray(NULL, NULL, "-jm", JMs, &count, &found); CHKERRQ(ierr);
565 count = nblk; ierr = PetscOptionsGetIntArray(NULL, NULL, "-km", KMs, &count, &found); CHKERRQ(ierr);
566
567 for (PetscInt bi = 0; bi < nblk; ++bi) {
568 finest_users[bi].IM = IMs[bi];
569 finest_users[bi].JM = JMs[bi];
570 finest_users[bi].KM = KMs[bi];
571 if (simCtx) {
573 "Rank %d: Preloaded analytical grid resolution for block %d: IM=%d, JM=%d, KM=%d\n",
574 simCtx->rank, bi, finest_users[bi].IM, finest_users[bi].JM, finest_users[bi].KM);
575 }
576 }
577
578 ierr = PetscFree3(IMs, JMs, KMs); CHKERRQ(ierr);
579 PetscFunctionReturn(0);
580}
581
582
583#undef __FUNCT__
584#define __FUNCT__ "ReadGridFile"
585/**
586 * @brief Internal helper implementation: `ReadGridFile()`.
587 * @details Local to this translation unit.
588 */
589PetscErrorCode ReadGridFile(UserCtx *user)
590{
591 PetscErrorCode ierr;
592 SimCtx *simCtx = user->simCtx;
593 PetscInt block_index = user->_this;
594
595 PetscFunctionBeginUser;
597
598 // --- One-Time Read and Broadcast Logic ---
600 LOG_ALLOW_SYNC(GLOBAL, LOG_INFO, "First call to ReadGridFile. Reading and broadcasting grid file header from '%s'...\n", simCtx->grid_file);
601 PetscMPIInt rank = simCtx->rank;
602 PetscInt nblk = simCtx->block_number;
603
604 if (rank == 0) {
605 FILE *fd = fopen(simCtx->grid_file, "r");
606 if (!fd) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Cannot open file: %s", simCtx->grid_file);
607
608 // Read and validate the canonical PICGRID header.
609 char firstTok[32] = {0};
610 if (fscanf(fd, "%31s", firstTok) != 1)
611 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "Empty grid file: %s", simCtx->grid_file);
612 if (strcmp(firstTok, "PICGRID") != 0)
613 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_READ,
614 "Grid file %s must begin with the canonical PICGRID header.", simCtx->grid_file);
615 if (fscanf(fd, "%d", &g_nblk_from_file) != 1)
616 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "Expected number of blocks after \"PICGRID\" in %s", simCtx->grid_file);
617 if (g_nblk_from_file != nblk) {
618 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_UNEXPECTED, "Mismatch: -nblk is %d but grid file specifies %d blocks.", nblk, g_nblk_from_file);
619 }
620
621 ierr = PetscMalloc3(nblk, &g_IMs_from_file, nblk, &g_JMs_from_file, nblk, &g_KMs_from_file); CHKERRQ(ierr);
622 for (PetscInt i = 0; i < nblk; ++i) {
623 if (fscanf(fd, "%d %d %d\n", &g_IMs_from_file[i], &g_JMs_from_file[i], &g_KMs_from_file[i]) != 3) {
624 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "Expected 3 integers for block %d in %s", i, simCtx->grid_file);
625 }
626 }
627 fclose(fd);
628 }
629
630 // Broadcast nblk to verify (optional, good practice)
631 ierr = MPI_Bcast(&g_nblk_from_file, 1, MPI_INT, 0, PETSC_COMM_WORLD); CHKERRQ(ierr);
632
633 // Allocate on other ranks before receiving the broadcast
634 if (rank != 0) {
635 ierr = PetscMalloc3(nblk, &g_IMs_from_file, nblk, &g_JMs_from_file, nblk, &g_KMs_from_file); CHKERRQ(ierr);
636 }
637
638 // Broadcast the data arrays
639 ierr = MPI_Bcast(g_IMs_from_file, nblk, MPI_INT, 0, PETSC_COMM_WORLD); CHKERRQ(ierr);
640 ierr = MPI_Bcast(g_JMs_from_file, nblk, MPI_INT, 0, PETSC_COMM_WORLD); CHKERRQ(ierr);
641 ierr = MPI_Bcast(g_KMs_from_file, nblk, MPI_INT, 0, PETSC_COMM_WORLD); CHKERRQ(ierr);
642
643 g_file_has_been_read = PETSC_TRUE;
644 LOG_ALLOW(GLOBAL, LOG_INFO, "Grid file header read and broadcast complete.\n");
645 }
646
647 // --- Per-Block Assignment Logic (runs on every call) ---
648 user->IM = g_IMs_from_file[block_index];
649 user->JM = g_JMs_from_file[block_index];
650 user->KM = g_KMs_from_file[block_index];
651
652 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Set file inputs for Block %d: IM=%d, JM=%d, KM=%d\n",
653 simCtx->rank, block_index, user->IM, user->JM, user->KM);
654
656 PetscFunctionReturn(0);
657}
658
659
660//================================================================================
661//
662// PRIVATE HELPER FUNCTIONS
663//
664//================================================================================
665
666/**
667 * @brief Implementation of \ref FreeBC_ParamList().
668 * @details Full API contract (arguments, ownership, side effects) is documented with
669 * the header declaration in `include/io.h`.
670 * @see FreeBC_ParamList()
671 */
673 BC_Param *current = head;
674 while (current != NULL) {
675 BC_Param *next = current->next;
676 PetscFree(current->key);
677 PetscFree(current->value);
678 PetscFree(current);
679 current = next;
680 }
681}
682
683/**
684 * @brief Internal helper implementation: `StringToBCFace()`.
685 * @details Local to this translation unit.
686 */
687PetscErrorCode StringToBCFace(const char* str, BCFace* face_out) {
688 if (strcasecmp(str, "-Xi") == 0) *face_out = BC_FACE_NEG_X;
689 else if (strcasecmp(str, "+Xi") == 0) *face_out = BC_FACE_POS_X;
690 else if (strcasecmp(str, "-Eta") == 0) *face_out = BC_FACE_NEG_Y;
691 else if (strcasecmp(str, "+Eta") == 0) *face_out = BC_FACE_POS_Y;
692 else if (strcasecmp(str, "-Zeta") == 0) *face_out = BC_FACE_NEG_Z;
693 else if (strcasecmp(str, "+Zeta") == 0) *face_out = BC_FACE_POS_Z;
694 else SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_UNKNOWN_TYPE, "Unknown face specifier: %s", str);
695 return 0;
696}
697
698/**
699 * @brief Internal helper implementation: `StringToBCType()`.
700 * @details Local to this translation unit.
701 */
702PetscErrorCode StringToBCType(const char* str, BCType* type_out) {
703 if (strcasecmp(str, "WALL") == 0) *type_out = WALL;
704 else if (strcasecmp(str, "SYMMETRY") == 0) *type_out = SYMMETRY;
705 else if (strcasecmp(str, "INLET") == 0) *type_out = INLET;
706 else if (strcasecmp(str, "OUTLET") == 0) *type_out = OUTLET;
707 else if (strcasecmp(str, "PERIODIC") == 0) *type_out = PERIODIC;
708 // ... add other BCTypes here ...
709 else SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_UNKNOWN_TYPE, "Unknown BC Type string: %s", str);
710 return 0;
711}
712
713/**
714 * @brief Internal helper implementation: `StringToBCHandlerType()`.
715 * @details Local to this translation unit.
716 */
717PetscErrorCode StringToBCHandlerType(const char* str, BCHandlerType* handler_out) {
718 if (strcasecmp(str, "noslip") == 0) *handler_out = BC_HANDLER_WALL_NOSLIP;
719 else if (strcasecmp(str, "constant_velocity") == 0) *handler_out = BC_HANDLER_INLET_CONSTANT_VELOCITY;
720 else if (strcasecmp(str, "conservation") == 0) *handler_out = BC_HANDLER_OUTLET_CONSERVATION;
721 else if (strcasecmp(str, "parabolic") == 0) *handler_out = BC_HANDLER_INLET_PARABOLIC;
722 else if (strcasecmp(str, "prescribed_flow") == 0) *handler_out = BC_HANDLER_INLET_PROFILE_FROM_FILE;
723 else if (strcasecmp(str,"geometric") == 0) *handler_out = BC_HANDLER_PERIODIC_GEOMETRIC;
724 else if (strcasecmp(str,"constant_flux") == 0) *handler_out = BC_HANDLER_PERIODIC_DRIVEN_CONSTANT_FLUX;
725 else if (strcasecmp(str,"initial_flux") == 0) *handler_out = BC_HANDLER_PERIODIC_DRIVEN_INITIAL_FLUX;
726 // ... add other BCHandlerTypes here ...
727 else SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_UNKNOWN_TYPE, "Unknown BC Handler string: %s", str);
728 return 0;
729}
730
731/**
732 * @brief Internal helper implementation: `ValidateBCHandlerForBCType()`.
733 * @details Local to this translation unit.
734 */
735PetscErrorCode ValidateBCHandlerForBCType(BCType type, BCHandlerType handler) {
736 switch (type) {
737 case OUTLET:
738 if(handler != BC_HANDLER_OUTLET_CONSERVATION) return PETSC_ERR_ARG_WRONG;
739 break;
740 case WALL:
741 if (handler != BC_HANDLER_WALL_NOSLIP && handler != BC_HANDLER_WALL_MOVING) return PETSC_ERR_ARG_WRONG;
742 break;
743 case INLET:
744 if (handler != BC_HANDLER_INLET_CONSTANT_VELOCITY &&
745 handler != BC_HANDLER_INLET_PARABOLIC &&
746 handler != BC_HANDLER_INLET_PROFILE_FROM_FILE) return PETSC_ERR_ARG_WRONG;
747 break;
748 case PERIODIC:
749 if(handler != BC_HANDLER_PERIODIC_GEOMETRIC && handler != BC_HANDLER_PERIODIC_DRIVEN_CONSTANT_FLUX && handler != BC_HANDLER_PERIODIC_DRIVEN_INITIAL_FLUX) return PETSC_ERR_ARG_WRONG;
750 // ... add other validation cases here ...
751 default: break;
752 }
753 return 0; // Combination is valid
754}
755
756/**
757 * @brief Internal helper implementation: `GetBCParamReal()`.
758 * @details Local to this translation unit.
759 */
760PetscErrorCode GetBCParamReal(BC_Param *params, const char *key, PetscReal *value_out, PetscBool *found) {
761 *found = PETSC_FALSE;
762 *value_out = 0.0;
763 if (!key) return 0; // No key to search for
764
765 BC_Param *current = params;
766 while (current) {
767 if (strcasecmp(current->key, key) == 0) {
768 *value_out = atof(current->value);
769 *found = PETSC_TRUE;
770 return 0; // Found it, we're done
771 }
772 current = current->next;
773 }
774 return 0; // It's not an error to not find the key.
775}
776
777/**
778 * @brief Internal helper implementation: `GetBCParamBool()`.
779 * @details Local to this translation unit.
780 */
781PetscErrorCode GetBCParamBool(BC_Param *params, const char *key, PetscBool *value_out, PetscBool *found) {
782 *found = PETSC_FALSE;
783 *value_out = PETSC_FALSE;
784 if (!key) return 0; // No key to search for
785
786 BC_Param *current = params;
787 while (current) {
788 if (strcasecmp(current->key, key) == 0) {
789 // Key was found.
790 *found = PETSC_TRUE;
791
792 // Check the value string. Default to FALSE if the value is NULL or doesn't match a "true" string.
793 if (current->value &&
794 (strcasecmp(current->value, "true") == 0 ||
795 strcmp(current->value, "1") == 0 ||
796 strcasecmp(current->value, "yes") == 0))
797 {
798 *value_out = PETSC_TRUE;
799 } else {
800 *value_out = PETSC_FALSE;
801 }
802 return 0; // Found it, we're done
803 }
804 current = current->next;
805 }
806 return 0; // It's not an error to not find the key.
807}
808
809#undef __FUNCT__
810#define __FUNCT__ "GetDrivenSeamFluxFlag"
811/**
812 * @brief Implementation of \ref GetDrivenSeamFluxFlag().
813 * @details The option was originally spelled `apply_trim`, which said that
814 * something was trimmed but not what or why. The canonical name is now
815 * `enforce_seam_flux`. Generated `bcs.run` files carry the canonical
816 * name, but a hand-written or archived one may still use the old
817 * spelling, so both are accepted here and the canonical name wins.
818 * The argument contract lives with the header declaration in
819 * `include/io.h`.
820 * @see GetDrivenSeamFluxFlag()
821 */
822PetscErrorCode GetDrivenSeamFluxFlag(BC_Param *params, PetscBool *value_out, PetscBool *found)
823{
824 PetscErrorCode ierr;
825 PetscFunctionBeginUser;
826
827 ierr = GetBCParamBool(params, "enforce_seam_flux", value_out, found); CHKERRQ(ierr);
828 if (!*found) {
829 ierr = GetBCParamBool(params, "apply_trim", value_out, found); CHKERRQ(ierr);
830 }
831 PetscFunctionReturn(0);
832}
833
834//================================================================================
835//
836// PUBLIC PARSING FUNCTION
837//
838//================================================================================
839#undef __FUNCT__
840#define __FUNCT__ "ParseAllBoundaryConditions"
841/**
842 * @brief Internal helper implementation: `ParseAllBoundaryConditions()`.
843 * @details Local to this translation unit.
844 */
845PetscErrorCode ParseAllBoundaryConditions(UserCtx *user, const char *bcs_input_filename)
846{
847 PetscErrorCode ierr;
848 PetscMPIInt rank;
849
850 // Temporary storage for rank 0 to build the configuration before broadcasting.
851 BoundaryFaceConfig configs_rank0[6];
852
853 PetscFunctionBeginUser;
855 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
856
857 if (rank == 0) {
858 FILE *file;
859 char line_buffer[1024];
860
861 // Initialize the temporary config array with safe defaults on rank 0.
862 for (int i = 0; i < 6; i++) {
863 configs_rank0[i].face_id = (BCFace)i;
864 configs_rank0[i].mathematical_type = WALL;
865 configs_rank0[i].handler_type = BC_HANDLER_WALL_NOSLIP;
866 configs_rank0[i].params = NULL;
867 configs_rank0[i].handler = NULL; // Handler object is not created here.
868 }
869
870 LOG_ALLOW(GLOBAL, LOG_INFO, "Parsing BC configuration from '%s' on rank 0... \n", bcs_input_filename);
871 file = fopen(bcs_input_filename, "r");
872 if (!file) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Could not open BCs file '%s'.", bcs_input_filename);
873
874 while (fgets(line_buffer, sizeof(line_buffer), file)) {
875 char *current_pos = line_buffer;
876 while (isspace((unsigned char)*current_pos)) current_pos++; // Skip leading whitespace
877 if (*current_pos == '#' || *current_pos == '\0' || *current_pos == '\n' || *current_pos == '\r') continue;
878
879 char *face_str = strtok(current_pos, " \t\n\r");
880 char *type_str = strtok(NULL, " \t\n\r");
881 char *handler_str = strtok(NULL, " \t\n\r");
882
883 if (!face_str || !type_str || !handler_str) {
884 LOG_ALLOW(GLOBAL, LOG_WARNING, "Malformed line in bcs.dat, skipping: %s \n", line_buffer);
885 continue;
886 }
887
888 BCFace face_enum;
889 BCType type_enum;
890 BCHandlerType handler_enum;
891 const char* handler_name_for_log;
892
893 // --- Convert strings to enums and validate ---
894 ierr = StringToBCFace(face_str, &face_enum); CHKERRQ(ierr);
895 ierr = StringToBCType(type_str, &type_enum); CHKERRQ(ierr);
896 ierr = StringToBCHandlerType(handler_str, &handler_enum); CHKERRQ(ierr);
897 ierr = ValidateBCHandlerForBCType(type_enum, handler_enum);
898 if (ierr) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Validation failed: Handler '%s' is not valid for Type '%s' on Face '%s'.\n", handler_str, type_str, face_str);
899
900 // Store the core types for the corresponding face
901 configs_rank0[face_enum].mathematical_type = type_enum;
902 configs_rank0[face_enum].handler_type = handler_enum;
903 handler_name_for_log = BCHandlerTypeToString(handler_enum); // Assumes this utility exists
904 LOG_ALLOW(GLOBAL, LOG_DEBUG, " Parsed Face '%s': Type=%s, Handler=%s \n", face_str, type_str, handler_name_for_log);
905
906 // --- Parse optional key=value parameters for this face ---
907 FreeBC_ParamList(configs_rank0[face_enum].params); // Clear any previous (default) params
908 configs_rank0[face_enum].params = NULL;
909 BC_Param **param_next_ptr = &configs_rank0[face_enum].params; // Pointer to the 'next' pointer to build the list
910
911 char* token;
912 while ((token = strtok(NULL, " \t\n\r")) != NULL) {
913 char* equals_ptr = strchr(token, '=');
914 if (!equals_ptr) {
915 LOG_ALLOW(GLOBAL, LOG_WARNING, "Malformed parameter '%s' on face '%s', skipping. \n", token, face_str);
916 continue;
917 }
918
919 *equals_ptr = '\0'; // Temporarily split the string at '=' to separate key and value
920 char* key_str = token;
921 char* value_str = equals_ptr + 1;
922
923 BC_Param *new_param;
924 ierr = PetscMalloc1(1, &new_param); CHKERRQ(ierr);
925 ierr = PetscStrallocpy(key_str, &new_param->key); CHKERRQ(ierr);
926 ierr = PetscStrallocpy(value_str, &new_param->value); CHKERRQ(ierr);
927 new_param->next = NULL;
928
929 *param_next_ptr = new_param;
930 param_next_ptr = &new_param->next;
931 LOG_ALLOW(GLOBAL, LOG_TRACE, " - Found param: [%s] = [%s] \n", new_param->key, new_param->value);
932 }
933 }
934 fclose(file);
935 }
936
937 // =========================================================================
938 // BROADCASTING THE CONFIGURATION FROM RANK 0
939 // =========================================================================
940 // This is a critical step to ensure all processes have the same configuration.
941
942 LOG_ALLOW_SYNC(GLOBAL, LOG_DEBUG, "Rank %d broadcasting/receiving BC configuration.\n", rank);
943
944 for (int i = 0; i < 6; i++) {
945 // --- Broadcast simple enums ---
946 if (rank == 0) {
947 user->boundary_faces[i] = configs_rank0[i]; // Rank 0 populates its final struct
948 }
949 ierr = MPI_Bcast(&user->boundary_faces[i].mathematical_type, 1, MPI_INT, 0, PETSC_COMM_WORLD); CHKERRQ(ierr);
950 ierr = MPI_Bcast(&user->boundary_faces[i].handler_type, 1, MPI_INT, 0, PETSC_COMM_WORLD); CHKERRQ(ierr);
951
952 // --- Serialize and Broadcast the parameter linked list ---
953 PetscInt n_params = 0;
954 if (rank == 0) { // On rank 0, count the number of parameters to send
955 for (BC_Param *p = user->boundary_faces[i].params; p; p = p->next) n_params++;
956 }
957 ierr = MPI_Bcast(&n_params, 1, MPI_INT, 0, PETSC_COMM_WORLD);CHKERRQ(ierr);
958
959 if (rank != 0) { // Non-root ranks need to receive and build the list
960 FreeBC_ParamList(user->boundary_faces[i].params); // Ensure list is empty before building
961 user->boundary_faces[i].params = NULL;
962 }
963
964 BC_Param **param_next_ptr = &user->boundary_faces[i].params;
965
966 for (int j = 0; j < n_params; j++) {
967 char key_buf[256] = {0}, val_buf[256] = {0};
968 if (rank == 0) {
969 // On rank 0, navigate to the j-th param and copy its data to buffers
970 BC_Param *p = user->boundary_faces[i].params;
971 for (int k = 0; k < j; k++) p = p->next;
972 strncpy(key_buf, p->key, 255);
973 strncpy(val_buf, p->value, 255);
974 }
975
976 ierr = MPI_Bcast(key_buf, 256, MPI_CHAR, 0, PETSC_COMM_WORLD); CHKERRQ(ierr);
977 ierr = MPI_Bcast(val_buf, 256, MPI_CHAR, 0, PETSC_COMM_WORLD); CHKERRQ(ierr);
978
979 if (rank != 0) {
980 // On non-root ranks, deserialize: create a new node and append it
981 BC_Param *new_param;
982 ierr = PetscMalloc1(1, &new_param); CHKERRQ(ierr);
983 ierr = PetscStrallocpy(key_buf, &new_param->key); CHKERRQ(ierr);
984 ierr = PetscStrallocpy(val_buf, &new_param->value); CHKERRQ(ierr);
985 new_param->next = NULL;
986 *param_next_ptr = new_param;
987 param_next_ptr = &new_param->next;
988 } else {
989 // On rank 0, just advance the pointer for the next iteration
990 param_next_ptr = &((*param_next_ptr)->next);
991 }
992 }
993 user->boundary_faces[i].face_id = (BCFace)i; // Ensure face_id is set on all ranks
994 }
995
996 // --- Set particle inlet lookup fields used by the particle system ---
997 user->inletFaceDefined = PETSC_FALSE;
998 for (int i=0; i<6; i++) {
999
1000 if (user->boundary_faces[i].mathematical_type == INLET && !user->inletFaceDefined) {
1001 user->inletFaceDefined = PETSC_TRUE;
1002 user->identifiedInletBCFace = (BCFace)i;
1003 LOG_ALLOW(GLOBAL, LOG_INFO, "Inlet face for particle initialization identified as Face %d.\n", i);
1004 break; // Found the first one, stop looking
1005 }
1006 }
1007
1008
1009 if (rank == 0) {
1010 // Rank 0 can now free the linked lists it created for the temporary storage.
1011 // As written, user->boundary_faces was populated directly on rank 0, so no extra free is needed.
1012 // for(int i=0; i<6; i++) FreeBC_ParamList(configs_rank0[i].params); // This would be needed if we used configs_rank0 exclusively
1013 }
1014
1016 PetscFunctionReturn(0);
1017}
1018
1019//================================================================================
1020//
1021// PRIVATE HELPER FUNCTIONS
1022//
1023//================================================================================
1024
1025// ... (existing helper functions like FreeBC_ParamList, StringToBCFace, etc.) ...
1026#undef __FUNCT__
1027#define __FUNCT__ "DeterminePeriodicity"
1028/**
1029 * @brief Internal helper implementation: `DeterminePeriodicity()`.
1030 * @details Local to this translation unit.
1031 */
1032PetscErrorCode DeterminePeriodicity(SimCtx *simCtx)
1033{
1034 PetscErrorCode ierr;
1035 PetscMPIInt rank;
1036 PetscInt periodic_flags[3] = {0, 0, 0}; // Index 0:I, 1:J, 2:K
1037
1038 PetscFunctionBeginUser;
1039 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1040
1041 // --- Part 1: Collectively verify all BCS files exist before proceeding ---
1042 for (PetscInt bi = 0; bi < simCtx->block_number; bi++) {
1043 const char *bcs_filename = simCtx->bcs_files[bi];
1044 if (!bcs_filename) SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_NULL, "BCS filename for block %d is not set in SimCtx.", bi);
1045 char desc_buf[256];
1046 PetscBool file_exists;
1047 snprintf(desc_buf, sizeof(desc_buf), "BCS file for block %d", bi);
1048 ierr = VerifyPathExistence(bcs_filename, PETSC_FALSE, PETSC_FALSE, desc_buf, &file_exists); CHKERRQ(ierr);
1049 }
1050
1051 // --- Part 2: Rank 0 does the parsing, since we know all files exist ---
1052 if (rank == 0) {
1053 PetscBool global_is_periodic[3] = {PETSC_FALSE, PETSC_FALSE, PETSC_FALSE};
1054 PetscBool is_set = PETSC_FALSE;
1055
1056 for (PetscInt bi = 0; bi < simCtx->block_number; bi++) {
1057 const char *bcs_filename = simCtx->bcs_files[bi];
1058 FILE *file = fopen(bcs_filename, "r");
1059
1060 PetscBool face_is_periodic[6] = {PETSC_FALSE};
1061 char line_buffer[1024];
1062
1063 while (fgets(line_buffer, sizeof(line_buffer), file)) {
1064 char *current_pos = line_buffer;
1065 while (isspace((unsigned char)*current_pos)) current_pos++;
1066 if (*current_pos == '#' || *current_pos == '\0' || *current_pos == '\n') continue;
1067
1068 // --- Tokenize the line exactly like the main parser ---
1069 char *face_str = strtok(current_pos, " \t\n\r");
1070 char *type_str = strtok(NULL, " \t\n\r");
1071
1072 // If the line doesn't have at least two tokens, we can't determine the type.
1073 if (!face_str || !type_str) continue;
1074
1075 // --- Perform a direct, non-erroring check on the mathematical type string ---
1076 if (strcasecmp(type_str, "PERIODIC") == 0) {
1077 BCFace face_enum;
1078 // A malformed face string on a periodic line IS a fatal error.
1079 ierr = StringToBCFace(face_str, &face_enum); CHKERRQ(ierr);
1080 face_is_periodic[face_enum] = PETSC_TRUE;
1081 }
1082 // Any other type_str (e.g., "WALL", "INLET") is correctly and silently ignored.
1083 }
1084 fclose(file);
1085
1086 // --- Validate consistency within this file ---
1087 if (face_is_periodic[BC_FACE_NEG_X] != face_is_periodic[BC_FACE_POS_X])
1088 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP, "Inconsistent X-periodicity in file '%s' for block %d. Both -Xi and +Xi must be periodic or neither.", bcs_filename, bi);
1089 if (face_is_periodic[BC_FACE_NEG_Y] != face_is_periodic[BC_FACE_POS_Y])
1090 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP, "Inconsistent Y-periodicity in file '%s' for block %d. Both -Eta and +Eta must be periodic or neither.", bcs_filename, bi);
1091 if (face_is_periodic[BC_FACE_NEG_Z] != face_is_periodic[BC_FACE_POS_Z])
1092 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP, "Inconsistent Z-periodicity in file '%s' for block %d. Both -Zeta and +Zeta must be periodic or neither.", bcs_filename, bi);
1093
1094 PetscBool local_is_periodic[3] = {face_is_periodic[BC_FACE_NEG_X], face_is_periodic[BC_FACE_NEG_Y], face_is_periodic[BC_FACE_NEG_Z]};
1095
1096 // --- Validate consistency across block files ---
1097 if (!is_set) {
1098 global_is_periodic[0] = local_is_periodic[0];
1099 global_is_periodic[1] = local_is_periodic[1];
1100 global_is_periodic[2] = local_is_periodic[2];
1101 is_set = PETSC_TRUE;
1102 } else {
1103 if (global_is_periodic[0] != local_is_periodic[0] || global_is_periodic[1] != local_is_periodic[1] || global_is_periodic[2] != local_is_periodic[2]) {
1104 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP,
1105 "Periodicity mismatch between blocks. Block 0 requires (I:%d, J:%d, K:%d), but block %d (file '%s') has (I:%d, J:%d, K:%d).",
1106 (int)global_is_periodic[0], (int)global_is_periodic[1], (int)global_is_periodic[2],
1107 bi, bcs_filename,
1108 (int)local_is_periodic[0], (int)local_is_periodic[1], (int)local_is_periodic[2]);
1109 }
1110 }
1111 } // end loop over blocks
1112
1113 periodic_flags[0] = (global_is_periodic[0]) ? 1 : 0;
1114 periodic_flags[1] = (global_is_periodic[1]) ? 1 : 0;
1115 periodic_flags[2] = (global_is_periodic[2]) ? 1 : 0;
1116
1117 LOG_ALLOW(GLOBAL, LOG_INFO, "Global periodicity determined: I-periodic=%d, J-periodic=%d, K-periodic=%d\n",
1118 periodic_flags[0], periodic_flags[1], periodic_flags[2]);
1119 }
1120
1121 // --- Part 3: Broadcast the final flags from rank 0 to all other ranks ---
1122 ierr = MPI_Bcast(periodic_flags, 3, MPIU_INT, 0, PETSC_COMM_WORLD); CHKERRQ(ierr);
1123
1124 // --- All ranks now update their SimCtx ---
1125 simCtx->i_periodic = periodic_flags[0];
1126 simCtx->j_periodic = periodic_flags[1];
1127 simCtx->k_periodic = periodic_flags[2];
1128
1129 PetscFunctionReturn(0);
1130}
1131
1132/**
1133 * @brief Internal helper implementation: `VerifyPathExistence()`.
1134 * @details Local to this translation unit.
1135 */
1136PetscErrorCode VerifyPathExistence(const char *path, PetscBool is_dir, PetscBool is_optional, const char *description, PetscBool *exists)
1137{
1138 PetscErrorCode ierr;
1139 PetscMPIInt rank;
1140 MPI_Comm comm = PETSC_COMM_WORLD;
1141
1142 PetscFunctionBeginUser;
1143 ierr = MPI_Comm_rank(comm, &rank); CHKERRQ(ierr);
1144
1145 if (rank == 0) {
1146 if (is_dir) {
1147 ierr = PetscTestDirectory(path, 'r', exists); CHKERRQ(ierr);
1148 } else {
1149 ierr = PetscTestFile(path, 'r', exists); CHKERRQ(ierr);
1150 }
1151
1152 if (!(*exists)) {
1153 if (is_optional) {
1154 LOG_ALLOW(GLOBAL, LOG_WARNING, "Optional %s not found at: %s (using defaults/ignoring).\n", description, path);
1155 } else {
1156 LOG_ALLOW(GLOBAL, LOG_ERROR, "Mandatory %s not found at: %s\n", description, path);
1157 }
1158 } else {
1159 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Found %s: %s\n", description, path);
1160 }
1161 }
1162
1163 // Broadcast the result from Rank 0
1164 PetscMPIInt exists_int = (rank == 0) ? (PetscMPIInt)(*exists) : 0;
1165 ierr = MPI_Bcast(&exists_int, 1, MPI_INT, 0, comm); CHKERRMPI(ierr);
1166 *exists = (PetscBool)exists_int;
1167
1168 // Collective error for mandatory files
1169 if (!(*exists) && !is_optional) {
1170 SETERRQ(comm, PETSC_ERR_FILE_OPEN, "Mandatory %s not found. Rank 0 expected it at '%s'. Check path and permissions.", description, path);
1171 }
1172
1173 PetscFunctionReturn(0);
1174}
1175
1176#undef __FUNCT__
1177#define __FUNCT__ "ReadFieldData"
1178/**
1179 * @brief Internal helper implementation: `ReadFieldData()`.
1180 * @details Local to this translation unit.
1181 */
1182PetscErrorCode ReadFieldData(UserCtx *user,
1183 const char *field_name,
1184 Vec field_vec,
1185 const char *ext)
1186{
1187 PetscErrorCode ierr;
1188 char filename[PETSC_MAX_PATH_LEN];
1189 MPI_Comm comm;
1190 PetscMPIInt rank,size;
1191 SimCtx *simCtx = user->simCtx;
1192
1193
1194 PetscFunctionBeginUser;
1196
1197 if(!simCtx->current_io_directory){
1198 SETERRQ(PETSC_COMM_SELF,PETSC_ERR_ARG_WRONGSTATE, "I/O context directory was not set before calling ReadFieldData().");
1199 }
1200
1201
1202 ierr = PetscObjectGetComm((PetscObject)field_vec,&comm);CHKERRQ(ierr);
1203 ierr = MPI_Comm_rank(comm,&rank);CHKERRQ(ierr);
1204 ierr = MPI_Comm_size(comm,&size);CHKERRQ(ierr);
1205
1206 const char *source_path = NULL;
1207 source_path = simCtx->current_io_directory;
1208
1209 if(!source_path){
1210 SETERRQ(PETSC_COMM_SELF,PETSC_ERR_ARG_WRONGSTATE, "source_path was not set for the current execution mode.");
1211 }
1212 ierr = PetscSNPrintf(filename, sizeof(filename), "%s/%s.%s",
1213 source_path, field_name, ext); CHKERRQ(ierr);
1214
1216 "Attempting to read <%s> on rank %d/%d\n",
1217 filename,(int)rank,(int)size);
1218
1219 /* ======================================================================
1220 * 1. SERIAL JOB – just hand the Vec to VecLoad()
1221 * ==================================================================== */
1222 if(size==1)
1223 {
1224 PetscViewer viewer;
1225 PetscBool found;
1226 Vec temp_vec;
1227 PetscInt expectedSize,loadedSize;
1228
1229 ierr = PetscTestFile(filename,'r',&found);CHKERRQ(ierr);
1230 if(!found) SETERRQ(comm,PETSC_ERR_FILE_OPEN,
1231 "Restart/Source file not found: %s",filename);
1232
1233 ierr = PetscViewerBinaryOpen(PETSC_COMM_SELF,filename,FILE_MODE_READ,&viewer);CHKERRQ(ierr);
1234// ---- START MODIFICATION ----
1235 // DO NOT load directly into field_vec, as this can resize it, which is
1236 // illegal for DMSwarm "view" vectors. Instead, load into a temporary vector.
1237 ierr = VecCreate(PETSC_COMM_SELF, &temp_vec); CHKERRQ(ierr);
1238 ierr = VecLoad(temp_vec,viewer);CHKERRQ(ierr);
1239 ierr = PetscViewerDestroy(&viewer);CHKERRQ(ierr);
1240
1241 // Sanity check: ensure the file size matches the expected vector size.
1242 ierr = VecGetSize(field_vec, &expectedSize);CHKERRQ(ierr);
1243 ierr = VecGetSize(temp_vec, &loadedSize);CHKERRQ(ierr);
1244 if (loadedSize != expectedSize) {
1245 SETERRQ(comm,PETSC_ERR_FILE_UNEXPECTED,
1246 "File %s holds %d entries – expected %d for field '%s'",
1247 filename, loadedSize, expectedSize, field_name);
1248 }
1249
1250 // Now, safely copy the data from the temporary vector to the final destination.
1251 ierr = VecCopy(temp_vec, field_vec);CHKERRQ(ierr);
1252
1253 // Clean up the temporary vector.
1254 ierr = VecDestroy(&temp_vec);CHKERRQ(ierr);
1255
1256 // ---- END MODIFICATION ----
1257
1258 /* create EMPTY sequential Vec – VecLoad() will size it correctly */
1259 /*
1260 ierr = VecCreate(PETSC_COMM_SELF,&seq_vec);CHKERRQ(ierr);
1261 ierr = VecSetType(seq_vec,VECSEQ);CHKERRQ(ierr);
1262
1263 ierr = PetscViewerBinaryOpen(PETSC_COMM_SELF,filename,
1264 FILE_MODE_READ,&viewer);CHKERRQ(ierr);
1265
1266 ierr = VecLoad(field_vec,viewer);CHKERRQ(ierr);
1267 ierr = PetscViewerDestroy(&viewer);CHKERRQ(ierr);
1268 */
1270 "Loaded <%s> (serial path)\n",filename);
1271
1273 PetscFunctionReturn(0);
1274 }
1275
1276 /* ======================================================================
1277 * 2. PARALLEL JOB
1278 * ==================================================================== */
1279 PetscInt globalSize;
1280 ierr = VecGetSize(field_vec,&globalSize);CHKERRQ(ierr);
1281
1282 DM dm = NULL;
1283 const char *dmtype = NULL;
1284 Vec nat = NULL; /* Natural-ordered vector for DMDA */
1285
1286 /* -------------------- rank-0 : read the sequential file -------------- */
1287 Vec seq_vec = NULL; /* only valid on rank-0 */
1288 const PetscScalar *seqArray = NULL; /* borrowed pointer on rank-0 only */
1289
1290 if(rank==0)
1291 {
1292 PetscViewer viewer;
1293 PetscBool found;
1294
1295 ierr = PetscTestFile(filename,'r',&found);CHKERRQ(ierr);
1296 if(!found) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_FILE_OPEN,
1297 "Restart file not found: %s",filename);
1298
1299 /* create EMPTY sequential Vec – VecLoad() will size it correctly */
1300 ierr = VecCreate(PETSC_COMM_SELF,&seq_vec);CHKERRQ(ierr);
1301 ierr = VecSetType(seq_vec,VECSEQ);CHKERRQ(ierr);
1302
1303 ierr = PetscViewerBinaryOpen(PETSC_COMM_SELF,filename,
1304 FILE_MODE_READ,&viewer);CHKERRQ(ierr);
1305 ierr = VecLoad(seq_vec,viewer);CHKERRQ(ierr);
1306 ierr = PetscViewerDestroy(&viewer);CHKERRQ(ierr);
1307
1308 /* size sanity-check */
1309 PetscInt loaded;
1310 ierr = VecGetSize(seq_vec,&loaded);CHKERRQ(ierr);
1311 if(loaded != globalSize)
1312 SETERRQ(comm,PETSC_ERR_FILE_UNEXPECTED,
1313 "File %s holds %d entries – expected %d",
1314 filename,loaded,globalSize);
1315
1316 /* borrow array for later Bcast */
1317 ierr = VecGetArrayRead(seq_vec,&seqArray);CHKERRQ(ierr);
1318
1320 "Rank 0 successfully loaded <%s>\n",filename);
1321 }
1322
1323 /* -------------------- Check if this is a DMDA vector ----------------- */
1324 ierr = VecGetDM(field_vec, &dm); CHKERRQ(ierr);
1325 if (dm) { ierr = DMGetType(dm, &dmtype); CHKERRQ(ierr); }
1326
1327 if (dmtype && !strcmp(dmtype, DMDA)) {
1328 /* ==================================================================
1329 * DMDA PATH: File is in natural ordering, need to convert to global
1330 * ================================================================== */
1331
1332 /* Create natural vector */
1333 ierr = DMDACreateNaturalVector(dm, &nat); CHKERRQ(ierr);
1334
1335 /* Scatter from rank 0's seq_vec to all ranks' natural vector */
1336 VecScatter scatter;
1337 Vec nat_seq = NULL; /* Sequential natural vector on rank 0 */
1338
1339 ierr = VecScatterCreateToZero(nat, &scatter, &nat_seq); CHKERRQ(ierr);
1340
1341 /* Reverse scatter: from rank 0 to all ranks */
1342 ierr = VecScatterBegin(scatter, (rank == 0 ? seq_vec : nat_seq), nat,
1343 INSERT_VALUES, SCATTER_REVERSE); CHKERRQ(ierr);
1344 ierr = VecScatterEnd(scatter, (rank == 0 ? seq_vec : nat_seq), nat,
1345 INSERT_VALUES, SCATTER_REVERSE); CHKERRQ(ierr);
1346
1347 /* Convert natural → global ordering */
1348 ierr = DMDANaturalToGlobalBegin(dm, nat, INSERT_VALUES, field_vec); CHKERRQ(ierr);
1349 ierr = DMDANaturalToGlobalEnd(dm, nat, INSERT_VALUES, field_vec); CHKERRQ(ierr);
1350
1351 /* Cleanup */
1352 ierr = VecScatterDestroy(&scatter); CHKERRQ(ierr);
1353 ierr = VecDestroy(&nat_seq); CHKERRQ(ierr);
1354 ierr = VecDestroy(&nat); CHKERRQ(ierr);
1355
1356 } else {
1357 /* ==================================================================
1358 * NON-DMDA PATH: Use broadcast and direct copy (assumes global ordering)
1359 * ================================================================== */
1360
1361 PetscScalar *buffer = NULL;
1362 if (rank == 0) {
1363 buffer = (PetscScalar *)seqArray;
1364 } else {
1365 ierr = PetscMalloc1(globalSize, &buffer); CHKERRQ(ierr);
1366 }
1367
1368 ierr = MPI_Bcast(buffer, (int)globalSize, MPIU_SCALAR, 0, comm); CHKERRQ(ierr);
1369
1370 /* Copy slice based on ownership range */
1371 PetscInt rstart, rend, loc;
1372 PetscScalar *locArray;
1373
1374 ierr = VecGetOwnershipRange(field_vec, &rstart, &rend); CHKERRQ(ierr);
1375 loc = rend - rstart;
1376
1377 ierr = VecGetArray(field_vec, &locArray); CHKERRQ(ierr);
1378 ierr = PetscMemcpy(locArray, buffer + rstart, loc * sizeof(PetscScalar)); CHKERRQ(ierr);
1379 ierr = VecRestoreArray(field_vec, &locArray); CHKERRQ(ierr);
1380
1381 if (rank != 0) {
1382 ierr = PetscFree(buffer); CHKERRQ(ierr);
1383 }
1384 }
1385
1386 /* -------------------- tidy up ---------------------------------------- */
1387 if (rank == 0) {
1388 ierr = VecRestoreArrayRead(seq_vec, &seqArray); CHKERRQ(ierr);
1389 ierr = VecDestroy(&seq_vec); CHKERRQ(ierr);
1390 }
1391
1393 "Loaded <%s> (parallel path)\n",filename);
1394
1396 PetscFunctionReturn(0);
1397}
1398
1399
1400#undef __FUNCT__
1401#define __FUNCT__ "RestoreDrivenFluxTarget"
1402/**
1403 * @brief Restore a latched driven-flow flux target from a checkpoint manifest.
1404 *
1405 * @details Only `initial_flux` needs this. Its target is derived from the field
1406 * the run originally started with, so re-measuring it after a restart
1407 * would silently retarget the controller at whatever the flux had
1408 * drifted to. `constant_flux` reads its target from the bcs file on
1409 * every run and is deliberately left alone, so that editing
1410 * `target_flux` between segments still takes effect.
1411 *
1412 * Checkpoints written before this metadata existed simply have no
1413 * entry; the controller then falls back to re-measuring and says so.
1414 *
1415 * @param[in,out] simCtx Simulation context receiving the target.
1416 * @param[in] user Block context, for its boundary handler types.
1417 * @param[in] checkpoint_directory Directory holding the validated manifest.
1418 * @return PetscErrorCode 0 on success.
1419 */
1420static PetscErrorCode RestoreDrivenFluxTarget(SimCtx *simCtx, UserCtx *user,
1421 const char *checkpoint_directory)
1422{
1423 char metadata_path[PETSC_MAX_PATH_LEN];
1424 PetscOptions options = NULL;
1425 PetscBool uses_initial_flux = PETSC_FALSE;
1426 PetscBool saved_latched = PETSC_FALSE;
1427 PetscBool found = PETSC_FALSE;
1428 PetscReal saved_target = 0.0;
1429
1430 PetscFunctionBeginUser;
1431
1432 for (PetscInt face = 0; face < 6; ++face) {
1434 uses_initial_flux = PETSC_TRUE;
1435 break;
1436 }
1437 }
1438 if (!uses_initial_flux) PetscFunctionReturn(0);
1439
1440 PetscCall(PetscSNPrintf(metadata_path, sizeof(metadata_path),
1441 "%s/checkpoint.meta", checkpoint_directory));
1442 PetscCall(PetscOptionsCreate(&options));
1443 PetscCall(PetscOptionsInsertFile(PETSC_COMM_WORLD, options, metadata_path, PETSC_TRUE));
1444 PetscCall(PetscOptionsGetBool(options, NULL, "-checkpoint_driven_flux_latched", &saved_latched, &found));
1445 if (found && saved_latched) {
1446 PetscCall(PetscOptionsGetReal(options, NULL, "-checkpoint_driven_flux_target", &saved_target, &found));
1447 } else {
1448 found = PETSC_FALSE;
1449 }
1450 PetscCall(PetscOptionsDestroy(&options));
1451
1452 if (found) {
1453 simCtx->targetVolumetricFlux = saved_target;
1454 simCtx->drivenFluxTargetLatched = PETSC_TRUE;
1456 "Driven Flow: restored latched target volumetric flux %.6e from checkpoint '%s'.\n",
1457 (double)saved_target, checkpoint_directory);
1458 } else {
1460 "Driven Flow: checkpoint '%s' records no latched flux target; the initial_flux "
1461 "controller will re-measure it from the restarted field.\n", checkpoint_directory);
1462 }
1463
1464 PetscFunctionReturn(0);
1465}
1466
1467/**
1468 * @brief Internal helper implementation: `ReadSimulationFields()`.
1469 * @details Local to this translation unit.
1470 */
1471PetscErrorCode ReadSimulationFields(UserCtx *user,PetscInt ti)
1472{
1473 SimCtx *simCtx = user->simCtx;
1474 const char *source_path = NULL;
1475 char checkpoint_directory[PETSC_MAX_PATH_LEN];
1476 PetscReal checkpoint_time = 0.0;
1477 PetscBool particles_saved = PETSC_FALSE;
1478 PetscBool les_saved = PETSC_FALSE;
1479
1480 PetscFunctionBeginUser;
1481 if(simCtx->exec_mode == EXEC_MODE_POSTPROCESSOR){
1482 source_path = simCtx->pps->source_dir;
1483 } else if(simCtx->exec_mode == EXEC_MODE_SOLVER){
1484 source_path = simCtx->restart_dir;
1485 } else{
1486 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, "Invalid execution mode for reading simulation fields.");
1487 }
1488
1489 PetscCall(ResolveCheckpointStepDirectory(source_path, ti,
1490 checkpoint_directory, sizeof(checkpoint_directory)));
1491 PetscCall(ValidateCheckpointBundle(simCtx, user - user->_this,
1492 checkpoint_directory, ti, &checkpoint_time, NULL,
1493 &particles_saved, &les_saved));
1494 PetscCheck(!(simCtx->np > 0 && !strcmp(simCtx->particleRestartMode, "load")) || particles_saved,
1495 PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED,
1496 "Particle restart_mode=load was requested, but checkpoint step %" PetscInt_FMT
1497 " contains no particle state.", ti);
1498 if (simCtx->exec_mode == EXEC_MODE_SOLVER && user->_this == 0) {
1499 simCtx->ti = checkpoint_time;
1500 if (ti == simCtx->StartStep) simCtx->StartTime = checkpoint_time;
1501 PetscCall(RestoreDrivenFluxTarget(simCtx, user, checkpoint_directory));
1502 }
1503
1504 LOG_ALLOW(GLOBAL, LOG_INFO, "Reading Eulerian checkpoint fields for block %d from '%s'.\n",
1505 user->_this, checkpoint_directory);
1506 PetscCall(PetscSNPrintf(simCtx->_io_context_buffer, sizeof(simCtx->_io_context_buffer),
1507 "%s/%s/block_%04" PetscInt_FMT,
1508 checkpoint_directory, PICURV_EULERIAN_DIRECTORY, user->_this));
1509 simCtx->current_io_directory = simCtx->_io_context_buffer;
1510
1511 for (PetscInt raw_id = 0; raw_id < FIELD_ID_COUNT; ++raw_id) {
1512 const FieldDescriptor *descriptor = NULL;
1513 FieldView view;
1514
1515 PetscCall(FieldGetDescriptor((FieldId)raw_id, &descriptor));
1516 if (!CheckpointFieldIsEnabled(simCtx, descriptor)) continue;
1517 /* A solver restart that reseeds particles (restart_mode init) must not restore the
1518 averages of the particles it discards; post-processing shows what was saved. */
1519 if ((descriptor->availability & FIELD_AVAILABILITY_PARTICLES) &&
1520 (!particles_saved ||
1521 (simCtx->exec_mode == EXEC_MODE_SOLVER && strcmp(simCtx->particleRestartMode, "load")))) continue;
1522 if ((descriptor->availability & FIELD_AVAILABILITY_LES_DYNAMIC) && !les_saved) continue;
1523 /* Wall-model state is derived, not carried: the first boundary pass of the
1524 restarted run recomputes it from the restored velocity field. Writing it keeps
1525 it available to postprocessing; reading it would only tie a restart to whether
1526 the source checkpoint happened to have a wall model enabled. */
1527 if (descriptor->availability & FIELD_AVAILABILITY_WALL_MODEL) continue;
1528 if ((descriptor->availability & FIELD_AVAILABILITY_TURBULENCE) &&
1529 !(simCtx->les && les_saved)) continue;
1530 PetscCall(FieldGetView(user, descriptor->id, &view));
1531 PetscCall(ReadFieldData(user, descriptor->canonical_name, view.global_vec, "dat"));
1532 /* A dimensionalizing post-processor scales each field as it is loaded, so a field
1533 is scaled exactly once per load: one that this step skipped keeps its earlier,
1534 already scaled, value instead of being scaled again. */
1535 if (simCtx->exec_mode == EXEC_MODE_POSTPROCESSOR && simCtx->pps && simCtx->pps->dimensionalize) {
1536 PetscReal scale = 1.0;
1537
1538 PetscCall(PicurvFieldReferenceScale(simCtx, descriptor->canonical_name, &scale, NULL, 0));
1539 if (scale != 1.0) PetscCall(VecScale(view.global_vec, scale));
1540 }
1541 if (view.local_vec) PetscCall(UpdateLocalGhosts(user, descriptor->id));
1542 }
1543 simCtx->restartHistoryAvailable = PETSC_TRUE;
1544 simCtx->current_io_directory = NULL;
1545 PetscFunctionReturn(0);
1546}
1547
1548
1549#undef __FUNCT__
1550#define __FUNCT__ "ReadStatisticsWindowState"
1551/** @brief Restore one window's scalar bookkeeping from a validated manifest. */
1552static PetscErrorCode ReadStatisticsWindowState(PetscOptions options, PetscInt window,
1553 const char *metadata_path, PetscReal checkpoint_time,
1554 PetscReal step_size, ExecutionMode exec_mode,
1555 PicurvWindow *state)
1556{
1557 char option_name[128];
1558 char saved_name[PICURV_WINDOW_NAME_LENGTH] = "";
1559 char saved_digest[65] = "";
1561 char saved_state[32] = "";
1562 char current_digest[65] = "";
1563 PetscBool found = PETSC_FALSE;
1564 PetscBool name_matches = PETSC_FALSE;
1565
1566 PetscFunctionBeginUser;
1568
1569#define PICURV_STATISTICS_REQUIRE(suffix, getter, target) \
1570 do { \
1571 PetscCall(PetscSNPrintf(option_name, sizeof(option_name), \
1572 "-checkpoint_statistics_window_%" PetscInt_FMT "_" suffix, window)); \
1573 PetscCall(getter(options, NULL, option_name, target, &found)); \
1574 PetscCheck(found, PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED, \
1575 "Statistics continuation requested, but '%s' records no %s for window " \
1576 "%" PetscInt_FMT ".", metadata_path, suffix, window); \
1577 } while (0)
1578
1579 PetscCall(PetscSNPrintf(option_name, sizeof(option_name),
1580 "-checkpoint_statistics_window_%" PetscInt_FMT "_name", window));
1581 PetscCall(PetscOptionsGetString(options, NULL, option_name, saved_name, sizeof(saved_name), &found));
1582 PetscCheck(found, PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED,
1583 "Statistics continuation requested, but '%s' records no name for window %" PetscInt_FMT ".",
1584 metadata_path, window);
1585 PetscCall(PetscStrcmp(saved_name, state->definition.name, &name_matches));
1586 PetscCheck(name_matches, PETSC_COMM_WORLD, PETSC_ERR_ARG_INCOMP,
1587 "Statistics window %" PetscInt_FMT " is '%s' in the checkpoint but '%s' in this run. "
1588 "Window order and names must match to continue.",
1589 window, saved_name, state->definition.name);
1590
1591 PetscCall(PetscSNPrintf(option_name, sizeof(option_name),
1592 "-checkpoint_statistics_window_%" PetscInt_FMT "_hash", window));
1593 PetscCall(PetscOptionsGetString(options, NULL, option_name, saved_digest, sizeof(saved_digest), &found));
1594 PetscCheck(found, PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED,
1595 "Statistics continuation requested, but '%s' records no hash for window '%s'.",
1596 metadata_path, state->definition.name);
1597 PetscCall(PicurvWindowComputeHash(&state->definition, current_digest, NULL));
1598 if (strcmp(saved_digest, current_digest)) {
1599 const char *differing = "an unrecorded property";
1600
1601 /* The saved definition itself is not stored, so the per-group digests are
1602 * what make it possible to name the property that changed rather than
1603 * reporting only that two opaque hashes differ. */
1604 PetscCall(PetscSNPrintf(option_name, sizeof(option_name),
1605 "-checkpoint_statistics_window_%" PetscInt_FMT "_hash_groups", window));
1606 PetscCall(PetscOptionsGetString(options, NULL, option_name, saved_groups,
1607 sizeof(saved_groups), &found));
1608 if (found) {
1609 PetscInt group = -1;
1610
1611 PetscCall(PicurvWindowFirstHashDifference(&state->definition, saved_groups, &group));
1612 if (group >= 0) differing = PicurvWindowHashGroupName(group);
1613 }
1614 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_INCOMP,
1615 "Statistics window '%s' was defined differently in the checkpoint: %s changed. "
1616 "Continuing would merge incompatible samples; rename the window to start a new one.",
1617 state->definition.name, differing);
1618 }
1619
1620 PICURV_STATISTICS_REQUIRE("sample_count", PetscOptionsGetInt, &state->sample_count);
1621 PICURV_STATISTICS_REQUIRE("total_weight", PetscOptionsGetReal, &state->total_weight);
1622 PICURV_STATISTICS_REQUIRE("represented_time", PetscOptionsGetReal, &state->represented_time);
1623 PICURV_STATISTICS_REQUIRE("last_accepted_time", PetscOptionsGetReal, &state->last_accepted_time);
1624 PICURV_STATISTICS_REQUIRE("effective_start", PetscOptionsGetReal, &state->effective_start);
1625 PICURV_STATISTICS_REQUIRE("effective_end", PetscOptionsGetReal, &state->effective_end);
1626 PICURV_STATISTICS_REQUIRE("activation_step", PetscOptionsGetInt, &state->activation_step);
1627 PICURV_STATISTICS_REQUIRE("last_event_step", PetscOptionsGetInt, &state->last_event_step);
1628 PICURV_STATISTICS_REQUIRE("next_time_target", PetscOptionsGetInt, &state->next_time_target);
1629 PICURV_STATISTICS_REQUIRE("restart_count", PetscOptionsGetInt, &state->restart_count);
1630#undef PICURV_STATISTICS_REQUIRE
1631
1632 PetscCall(PetscSNPrintf(option_name, sizeof(option_name),
1633 "-checkpoint_statistics_window_%" PetscInt_FMT "_state", window));
1634 PetscCall(PetscOptionsGetString(options, NULL, option_name, saved_state, sizeof(saved_state), &found));
1635 PetscCheck(found, PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED,
1636 "Statistics continuation requested, but '%s' records no lifecycle state for window '%s'.",
1637 metadata_path, state->definition.name);
1638 if (!strcmp(saved_state, "complete")) state->state = PICURV_WINDOW_COMPLETE;
1639 else if (!strcmp(saved_state, "active")) state->state = PICURV_WINDOW_ACTIVE;
1640 else state->state = PICURV_WINDOW_PENDING;
1641
1642 /* A window saved as complete may still be resumable: page 58 §10 permits moving
1643 * a bounded end forward, and the end time is deliberately outside the hash. */
1644 if (state->state == PICURV_WINDOW_COMPLETE && state->definition.bounded &&
1645 state->definition.end_time > state->effective_end) {
1646 /* Only across an unbroken span. Under right-rectangle weighting the first
1647 * sample after the former end carries the whole interval back to it, so
1648 * resuming across a gap would weight time the window never observed. One
1649 * step of slack is the closing state's own clipping, not a gap. */
1650 PetscCheck(checkpoint_time - state->effective_end <= PetscMax(step_size, 0.0),
1651 PETSC_COMM_WORLD, PETSC_ERR_ARG_INCOMP,
1652 "Statistics window '%s' ended at t=%.17g and this checkpoint is at t=%.17g, "
1653 "so extending it to %.17g would weight %.17g of unobserved time. "
1654 "Start a new window instead.",
1655 state->definition.name, (double)state->effective_end, (double)checkpoint_time,
1656 (double)state->definition.end_time,
1657 (double)(checkpoint_time - state->effective_end));
1659 "Statistics window '%s' was complete at t=%.6g and is extended to t=%.6g.\n",
1660 state->definition.name, (double)state->effective_end,
1661 (double)state->definition.end_time);
1662 state->state = PICURV_WINDOW_ACTIVE;
1663 }
1664 PetscCheck(!state->definition.bounded || state->definition.end_time >= state->effective_end,
1665 PETSC_COMM_WORLD, PETSC_ERR_ARG_INCOMP,
1666 "Statistics window '%s' already represents time up to %.17g, past the requested end "
1667 "%.17g. A window may be extended forward but never shortened; rename it to start a new one.",
1668 state->definition.name, (double)state->effective_end,
1669 (double)state->definition.end_time);
1670 /* A post-processing read is analysis, not a continuation, so it must not look
1671 * like another restart segment. Only a solver resuming the window advances the
1672 * lineage; otherwise deriving a series of steps would inflate it once per step. */
1673 if (exec_mode == EXEC_MODE_SOLVER) state->restart_count += 1;
1675 PetscFunctionReturn(0);
1676}
1677
1678#undef __FUNCT__
1679#define __FUNCT__ "RestoreFieldStatisticsState"
1680/**
1681 * @brief Implementation of \ref RestoreFieldStatisticsState().
1682 * @details Full API contract (arguments, ownership, side effects) is documented with
1683 * the header declaration in `include/io.h`.
1684 * @see RestoreFieldStatisticsState()
1685 */
1686PetscErrorCode RestoreFieldStatisticsState(SimCtx *simCtx, PetscInt ti)
1687{
1688 UserCtx *user = NULL;
1689 PetscOptions options = NULL;
1690 char checkpoint_directory[PETSC_MAX_PATH_LEN];
1691 char metadata_path[PETSC_MAX_PATH_LEN];
1692 const char *source_path = NULL;
1693 PetscInt saved_window_count = 0;
1694 PetscReal checkpoint_time = 0.0;
1695 PetscBool found = PETSC_FALSE;
1696
1697 PetscFunctionBeginUser;
1699 PetscCheck(simCtx != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "SimCtx cannot be NULL.");
1700 if (!FieldStatisticsIsActive(simCtx)) { PROFILE_FUNCTION_END; PetscFunctionReturn(0); }
1701 if (!simCtx->fieldStatisticsContinue) {
1703 "Statistics continuation was not requested; %d window(s) start from zero.\n",
1706 PetscFunctionReturn(0);
1707 }
1708
1709 user = simCtx->usermg.mgctx[simCtx->usermg.mglevels - 1].user;
1710 PetscCheck(user != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
1711 "Finest-level fields must exist before statistics state is restored.");
1712 source_path = (simCtx->exec_mode == EXEC_MODE_POSTPROCESSOR) ? simCtx->pps->source_dir
1713 : simCtx->restart_dir;
1714 PetscCheck(source_path != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
1715 "No checkpoint source directory is set for the current execution mode.");
1716 PetscCall(ResolveCheckpointStepDirectory(source_path, ti,
1717 checkpoint_directory, sizeof(checkpoint_directory)));
1718 PetscCall(ValidateCheckpointBundle(simCtx, user, checkpoint_directory, ti,
1719 &checkpoint_time, NULL, NULL, NULL));
1720 PetscCall(PetscSNPrintf(metadata_path, sizeof(metadata_path), "%s/checkpoint.meta",
1721 checkpoint_directory));
1722
1723 PetscCall(PetscOptionsCreate(&options));
1724 PetscCall(PetscOptionsInsertFile(PETSC_COMM_WORLD, options, metadata_path, PETSC_TRUE));
1725 PetscCall(PetscOptionsGetInt(options, NULL, "-checkpoint_statistics_window_count",
1726 &saved_window_count, &found));
1727 /* Missing state for a requested continuation is fatal and never silently
1728 * zeroed: resuming from zero would report a converged average built from a
1729 * fraction of the samples its metadata claims. */
1730 PetscCheck(found && saved_window_count == simCtx->fieldStatisticsWindowCount,
1731 PETSC_COMM_WORLD, PETSC_ERR_ARG_INCOMP,
1732 "Statistics continuation requested, but checkpoint '%s' holds %" PetscInt_FMT
1733 " window(s) and this run configures %" PetscInt_FMT ".",
1734 checkpoint_directory, found ? saved_window_count : 0,
1736
1737 for (PetscInt window = 0; window < simCtx->fieldStatisticsWindowCount; ++window) {
1738 PetscCall(ReadStatisticsWindowState(options, window, metadata_path, checkpoint_time,
1739 simCtx->dt, simCtx->exec_mode,
1740 &simCtx->fieldStatisticsWindows[window]));
1741 }
1742 PetscCall(PetscOptionsDestroy(&options));
1743
1744 for (PetscInt block = 0; block < simCtx->block_number; ++block) {
1745 PetscCheck(user[block].fieldStatisticsStorage != NULL, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE,
1746 "Statistics accumulators were not allocated for block %" PetscInt_FMT ".", block);
1747 for (PetscInt window = 0; window < simCtx->fieldStatisticsWindowCount; ++window) {
1748 const PicurvWindowDefinition *definition = &simCtx->fieldStatisticsWindows[window].definition;
1749 const PicurvWindowStorage *storage = &user[block].fieldStatisticsStorage[window];
1750 PetscInt payload_count = 0;
1751
1752 PetscCall(FormatStatisticsPath(checkpoint_directory, window, block, NULL,
1753 simCtx->_io_context_buffer,
1754 sizeof(simCtx->_io_context_buffer)));
1755 simCtx->current_io_directory = simCtx->_io_context_buffer;
1756 PetscCall(PicurvWindowStoragePayloadCount(storage, &payload_count));
1757 for (PetscInt index = 0; index < payload_count; ++index) {
1759
1760 PetscCall(PicurvWindowStoragePayload(&user[block], definition, storage, index, &payload));
1761 PetscCall(ReadFieldData(&user[block], payload.name, payload.vec, "dat"));
1762 }
1763 }
1764 simCtx->current_io_directory = NULL;
1765 }
1766
1767 for (PetscInt window = 0; window < simCtx->fieldStatisticsWindowCount; ++window) {
1768 const PicurvWindow *state = &simCtx->fieldStatisticsWindows[window];
1769
1771 "Statistics window '%s' continued from '%s': state %s, %d sample(s), "
1772 "total weight %.6g, represented time %.6g, restart segment %d.\n",
1773 state->definition.name, checkpoint_directory,
1775 (double)state->total_weight, (double)state->represented_time,
1776 state->restart_count);
1777 }
1779 PetscFunctionReturn(0);
1780}
1781
1782/**
1783 * @brief Internal helper implementation: `ReadSwarmField()`.
1784 * @details Local to this translation unit.
1785 */
1786PetscErrorCode ReadSwarmField(UserCtx *user, const char *field_name, const char *ext)
1787{
1788 PetscErrorCode ierr;
1789 DM swarm;
1790 Vec fieldVec;
1791
1792 PetscFunctionBegin;
1793
1794 swarm = user->swarm;
1795
1796 LOG_ALLOW(GLOBAL,LOG_DEBUG," ReadSwarmField Begins \n");
1797
1798 /* 2) Create a global vector that references the specified Swarm field. */
1799 ierr = DMSwarmCreateGlobalVectorFromField(swarm, field_name, &fieldVec);CHKERRQ(ierr);
1800
1801 LOG_ALLOW(GLOBAL,LOG_DEBUG," Vector created from Field \n");
1802
1803 /* 3) Use the ReadFieldData() function to read data into fieldVec. */
1804 ierr = ReadFieldData(user, field_name, fieldVec, ext);CHKERRQ(ierr);
1805
1806 /* 4) Destroy the global vector reference. */
1807 ierr = DMSwarmDestroyGlobalVectorFromField(swarm, field_name, &fieldVec);CHKERRQ(ierr);
1808
1809 PetscFunctionReturn(0);
1810}
1811
1812/**
1813 * @brief Internal helper implementation: `ReadSwarmIntField()`.
1814 * @details Local to this translation unit.
1815 */
1816PetscErrorCode ReadSwarmIntField(UserCtx *user, const char *field_name, const char *ext)
1817{
1818 PetscErrorCode ierr;
1819 DM swarm = user->swarm;
1820 Vec temp_vec;
1821 PetscInt nlocal, nglobal, bs, i;
1822 PetscDataType field_type;
1823 const PetscScalar *scalar_array; // Read-only pointer from the temp Vec
1824 void *field_array_void;
1825
1826
1827 PetscFunctionBeginUser;
1828
1829 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Reading '%s' via temporary Vec.\n", field_name);
1830
1831 // Get the properties of the swarm field to determine the expected layout
1832 ierr = DMSwarmGetLocalSize(swarm, &nlocal); CHKERRQ(ierr);
1833 ierr = DMSwarmGetSize(swarm, &nglobal); CHKERRQ(ierr);
1834 // We get the block size but not the data pointer yet
1835 ierr = DMSwarmGetField(swarm, field_name, &bs, &field_type, NULL); CHKERRQ(ierr);
1836 ierr = DMSwarmRestoreField(swarm, field_name, &bs, NULL, NULL); CHKERRQ(ierr);
1837 PetscCheck(field_type == PETSC_INT || field_type == PETSC_INT64,
1838 PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG,
1839 "Swarm field '%s' must use PETSC_INT or PETSC_INT64 for integer restart input, not %s.",
1840 field_name, PetscDataTypes[field_type]);
1841
1842 // Create a temporary Vec with the CORRECT layout to receive the data
1843 ierr = VecCreate(PETSC_COMM_WORLD, &temp_vec); CHKERRQ(ierr);
1844 ierr = VecSetType(temp_vec, VECMPI); CHKERRQ(ierr);
1845 ierr = VecSetSizes(temp_vec, nlocal * bs, nglobal * bs); CHKERRQ(ierr);
1846 ierr = VecSetBlockSize(temp_vec, bs); CHKERRQ(ierr);
1847 ierr = VecSetUp(temp_vec); CHKERRQ(ierr);
1848
1849 // Call your existing reader to populate the temporary Vec
1850 ierr = ReadFieldData(user, field_name, temp_vec, ext); CHKERRQ(ierr);
1851
1852 // Get local pointers
1853 ierr = VecGetArrayRead(temp_vec, &scalar_array); CHKERRQ(ierr);
1854 ierr = DMSwarmGetField(swarm, field_name, NULL, NULL, &field_array_void); CHKERRQ(ierr);
1855
1856 // Perform the cast back, using the correct loop size (nlocal * bs)
1857 if (field_type == PETSC_INT64) {
1858 PetscInt64 *int64_array = (PetscInt64 *)field_array_void;
1859 for (i = 0; i < nlocal * bs; i++) {
1860 int64_array[i] = (PetscInt64)scalar_array[i];
1861 }
1862 } else {
1863 PetscInt *int_array = (PetscInt *)field_array_void;
1864 for (i = 0; i < nlocal * bs; i++) {
1865 int_array[i] = (PetscInt)scalar_array[i];
1866 }
1867 }
1868
1869 // Restore access
1870 ierr = DMSwarmRestoreField(swarm, field_name, NULL, NULL, &field_array_void); CHKERRQ(ierr);
1871 ierr = VecRestoreArrayRead(temp_vec, &scalar_array); CHKERRQ(ierr);
1872
1873 // 6. Clean up
1874 ierr = VecDestroy(&temp_vec); CHKERRQ(ierr);
1875
1876 PetscFunctionReturn(0);
1877}
1878
1879/**
1880 * @brief Internal helper implementation: `ReadAllSwarmFields()`.
1881 * @details Local to this translation unit.
1882 */
1883PetscErrorCode ReadAllSwarmFields(UserCtx *user, PetscInt ti)
1884{
1885 PetscInt nGlobal;
1886 SimCtx *simCtx = user->simCtx;
1887 const char *source_path = NULL;
1888 char checkpoint_directory[PETSC_MAX_PATH_LEN];
1889
1890 PetscFunctionBeginUser;
1891 PetscCall(DMSwarmGetSize(user->swarm, &nGlobal));
1892 LOG_ALLOW(GLOBAL, LOG_INFO, "Reading DMSwarm fields for timestep %d (swarm size is %d).\n", ti, nGlobal);
1893
1894 if (nGlobal == 0) {
1895 LOG_ALLOW(GLOBAL, LOG_INFO, "Swarm is empty for timestep %d. Nothing to read.\n", ti);
1896 PetscFunctionReturn(0);
1897 }
1898
1899 // First, determine the top-level source directory based on the execution mode.
1900 if (simCtx->exec_mode == EXEC_MODE_SOLVER) {
1901 source_path = simCtx->restart_dir;
1902 } else if (simCtx->exec_mode == EXEC_MODE_POSTPROCESSOR) {
1903 source_path = simCtx->pps->source_dir;
1904 } else {
1905 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE, "Invalid execution mode for reading simulation fields.");
1906 }
1907
1908 PetscCall(ResolveCheckpointStepDirectory(source_path, ti,
1909 checkpoint_directory, sizeof(checkpoint_directory)));
1910 PetscCall(PetscSNPrintf(simCtx->_io_context_buffer, sizeof(simCtx->_io_context_buffer),
1911 "%s/%s", checkpoint_directory, PICURV_PARTICLE_DIRECTORY));
1912 simCtx->current_io_directory = simCtx->_io_context_buffer;
1913
1914 for (PetscInt raw_id = 0; raw_id < PARTICLE_FIELD_ID_COUNT; ++raw_id) {
1915 const ParticleFieldDescriptor *descriptor = NULL;
1916
1917 PetscCall(ParticleFieldGetDescriptor((ParticleFieldId)raw_id, &descriptor));
1918 if (!(descriptor->capabilities & PARTICLE_FIELD_CAPABILITY_CHECKPOINT)) continue;
1919 if (descriptor->data_type == PETSC_INT || descriptor->data_type == PETSC_INT64) {
1920 PetscCall(ReadSwarmIntField(user, descriptor->canonical_name, "dat"));
1921 } else {
1922 PetscCall(ReadSwarmField(user, descriptor->canonical_name, "dat"));
1923 }
1924 }
1925
1926 simCtx->current_io_directory = NULL;
1927
1928 LOG_ALLOW(GLOBAL, LOG_INFO, "Finished reading DMSwarm fields for timestep %d.\n", ti);
1929 PetscFunctionReturn(0);
1930}
1931
1932/** @brief Implementation of \ref ReadCheckpointParticleCount(). */
1933PetscErrorCode ReadCheckpointParticleCount(UserCtx *user, PetscInt ti, PetscInt *particle_count)
1934{
1935 SimCtx *simCtx = NULL;
1936 const char *source_path = NULL;
1937 char checkpoint_directory[PETSC_MAX_PATH_LEN];
1938 PetscBool particles_saved = PETSC_FALSE;
1939
1940 PetscFunctionBeginUser;
1941 PetscCheck(user != NULL && particle_count != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
1942 "Simulation context and particle-count output are required.");
1943 simCtx = user->simCtx;
1944 if (simCtx->exec_mode == EXEC_MODE_SOLVER) source_path = simCtx->restart_dir;
1945 else if (simCtx->exec_mode == EXEC_MODE_POSTPROCESSOR) source_path = simCtx->pps->source_dir;
1946 else SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE,
1947 "Invalid execution mode for reading checkpoint particle metadata.");
1948
1949 PetscCall(ResolveCheckpointStepDirectory(source_path, ti,
1950 checkpoint_directory, sizeof(checkpoint_directory)));
1951 PetscCall(ValidateCheckpointBundle(simCtx, user - user->_this,
1952 checkpoint_directory, ti, NULL, particle_count,
1953 &particles_saved, NULL));
1954 PetscCheck(particles_saved, PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED,
1955 "Checkpoint step %" PetscInt_FMT " contains no particle state.", ti);
1956 PetscFunctionReturn(0);
1957}
1958
1959
1960#undef __FUNCT__
1961#define __FUNCT__ "WriteFieldData"
1962/**
1963 * @brief Internal helper implementation: `WriteFieldData()`.
1964 * @details Local to this translation unit.
1965 */
1966PetscErrorCode WriteFieldData(UserCtx *user,
1967 const char *field_name,
1968 Vec field_vec,
1969 const char *ext)
1970{
1971 MPI_Comm comm;
1972 PetscMPIInt rank;
1973 Vec sequential_vec = NULL;
1974 char filename[PETSC_MAX_PATH_LEN];
1975 SimCtx *simCtx=user->simCtx;
1976
1977 PetscFunctionBeginUser;
1979
1980 if(!simCtx->current_io_directory){
1981 SETERRQ(PETSC_COMM_SELF,PETSC_ERR_ARG_WRONGSTATE, "I/O context directory was not set before calling WriteFieldData().");
1982 }
1983
1984 /* ------------------------------------------------------------ */
1985 /* Basic communicator information */
1986 /* ------------------------------------------------------------ */
1987 PetscCall(PetscObjectGetComm((PetscObject)field_vec,&comm));
1988 PetscCallMPI(MPI_Comm_rank(comm,&rank));
1989
1990 PetscCall(PetscSNPrintf(filename, sizeof(filename), "%s/%s.%s",
1991 simCtx->current_io_directory, field_name, ext));
1992
1993 PetscCall(GatherVectorToRankZero(field_vec, &sequential_vec));
1994 if (rank == 0) {
1995 PetscViewer viewer;
1996 PetscReal vmin, vmax;
1997
1998 PetscCall(VecMin(sequential_vec, NULL, &vmin));
1999 PetscCall(VecMax(sequential_vec, NULL, &vmax));
2001 " <%s> range = [%.4e … %.4e]\n",
2002 field_name,(double)vmin,(double)vmax);
2003 PetscCall(PetscViewerBinaryOpen(PETSC_COMM_SELF, filename, FILE_MODE_WRITE, &viewer));
2004 PetscCall(PetscViewerBinarySetSkipInfo(viewer, PETSC_TRUE));
2005 PetscCall(VecView(sequential_vec, viewer));
2006 PetscCall(PetscViewerDestroy(&viewer));
2007 LOG_ALLOW(GLOBAL, LOG_INFO, "Wrote <%s>\n", filename);
2008 }
2009 PetscCall(VecDestroy(&sequential_vec));
2010
2012 PetscFunctionReturn(0);
2013}
2014
2015/**
2016 * @brief Implementation of \ref WriteSimulationFields().
2017 * @details Full API contract (arguments, ownership, side effects) is documented with
2018 * the header declaration in `include/io.h`.
2019 * @see WriteSimulationFields()
2020 */
2021PetscErrorCode WriteSimulationFields(UserCtx *user, const char *checkpoint_directory)
2022{
2023 SimCtx *simCtx = user->simCtx;
2024
2025 PetscFunctionBeginUser;
2026 PetscCheck(checkpoint_directory != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
2027 "Checkpoint destination cannot be NULL.");
2028 PetscCall(PetscSNPrintf(simCtx->_io_context_buffer, sizeof(simCtx->_io_context_buffer),
2029 "%s/%s/block_%04" PetscInt_FMT,
2030 checkpoint_directory, PICURV_EULERIAN_DIRECTORY, user->_this));
2031 simCtx->current_io_directory = simCtx->_io_context_buffer;
2032
2033 if (simCtx->les) {
2034 // The constant model holds no coefficient field to stage.
2035 if (simCtx->les == DYNAMIC_SMAGORINSKY) {
2036 PetscCall(CopyOwnedLocalScalarToGlobal(user->da, user->lCs, user->CS));
2037 }
2038 PetscCall(CopyOwnedLocalScalarToGlobal(user->da, user->lNu_t, user->Nu_t));
2039 }
2040 for (PetscInt raw_id = 0; raw_id < FIELD_ID_COUNT; ++raw_id) {
2041 const FieldDescriptor *descriptor = NULL;
2042 FieldView view;
2043
2044 PetscCall(FieldGetDescriptor((FieldId)raw_id, &descriptor));
2045 if (!CheckpointFieldIsEnabled(simCtx, descriptor)) continue;
2046 PetscCall(FieldGetView(user, descriptor->id, &view));
2047 PetscCall(WriteFieldData(user, descriptor->canonical_name, view.global_vec, "dat"));
2048 }
2049 simCtx->current_io_directory = NULL;
2050 PetscFunctionReturn(0);
2051}
2052
2053#undef __FUNCT__
2054#define __FUNCT__ "WriteStatisticsFields"
2055/** @brief Write every window's accumulator payloads for one block into a bundle. */
2056static PetscErrorCode WriteStatisticsFields(UserCtx *user, const char *checkpoint_directory)
2057{
2058 SimCtx *simCtx = user->simCtx;
2059
2060 PetscFunctionBeginUser;
2062 PetscCheck(checkpoint_directory != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
2063 "Checkpoint destination cannot be NULL.");
2064 if (!FieldStatisticsIsActive(simCtx) || !user->fieldStatisticsStorage) { PROFILE_FUNCTION_END; PetscFunctionReturn(0); }
2065
2066 for (PetscInt window = 0; window < simCtx->fieldStatisticsWindowCount; ++window) {
2067 const PicurvWindowStorage *storage = &user->fieldStatisticsStorage[window];
2068 PetscInt payload_count = 0;
2069
2070 PetscCall(FormatStatisticsPath(checkpoint_directory, window, user->_this, NULL,
2071 simCtx->_io_context_buffer,
2072 sizeof(simCtx->_io_context_buffer)));
2073 simCtx->current_io_directory = simCtx->_io_context_buffer;
2074 PetscCall(PicurvWindowStoragePayloadCount(storage, &payload_count));
2075 for (PetscInt index = 0; index < payload_count; ++index) {
2077
2078 PetscCall(PicurvWindowStoragePayload(user, &simCtx->fieldStatisticsWindows[window].definition,
2079 storage, index, &payload));
2080 PetscCall(WriteFieldData(user, payload.name, payload.vec, "dat"));
2081 }
2083 "Wrote %d statistics payload(s) for window '%s' block %d.\n",
2084 (int)payload_count, simCtx->fieldStatisticsWindows[window].definition.name,
2085 (int)user->_this);
2086 }
2087 simCtx->current_io_directory = NULL;
2089 PetscFunctionReturn(0);
2090}
2091
2092/**
2093 * @brief Implementation of \ref WriteSwarmField().
2094 * @details Full API contract (arguments, ownership, side effects) is documented with
2095 * the header declaration in `include/io.h`.
2096 * @see WriteSwarmField()
2097 */
2098PetscErrorCode WriteSwarmField(UserCtx *user, const char *field_name, const char *ext)
2099{
2100 PetscErrorCode ierr;
2101 Vec fieldVec;
2102 DM swarm;
2103
2104 PetscFunctionBeginUser; /* PETSc macro indicating start of function */
2105
2106 /*
2107 * 1) Retrieve the PetscSwarm from the user context.
2108 * Ensure user->swarm is initialized and not NULL.
2109 */
2110 swarm = user->swarm;
2111
2112 /*
2113 * 2) Create a global vector from the specified swarm field.
2114 * This function is available in PETSc 3.14.4.
2115 * It provides a read/write "view" of the swarm field as a global Vec.
2116 */
2118 "Attempting to create global vector from field: %s\n",
2119 field_name);
2120 ierr = DMSwarmCreateGlobalVectorFromField(swarm, field_name, &fieldVec);CHKERRQ(ierr);
2121
2122 /*
2123 * 3) Use your existing WriteFieldData() to write the global vector to a file.
2124 * The field name, time index, and extension are passed along for naming.
2125 */
2127 "Calling WriteFieldData for field: %s\n",
2128 field_name);
2129 ierr = WriteFieldData(user, field_name, fieldVec, ext);CHKERRQ(ierr);
2130
2131 /*
2132 * 4) Destroy the global vector once the data is successfully written.
2133 * This step is crucial for avoiding memory leaks.
2134 * DMSwarmDestroyGlobalVectorFromField() is also available in PETSc 3.14.4.
2135 */
2137 "Destroying the global vector for field: %s\n",
2138 field_name);
2139 ierr = DMSwarmDestroyGlobalVectorFromField(swarm, field_name, &fieldVec);CHKERRQ(ierr);
2140
2141 /* Log and return success. */
2143 "Successfully wrote swarm data for field: %s\n",
2144 field_name);
2145
2146 PetscFunctionReturn(0); /* PETSc macro indicating end of function */
2147}
2148
2149/**
2150 * @brief Internal helper implementation: `WriteSwarmIntField()`.
2151 * @details Local to this translation unit.
2152 */
2153PetscErrorCode WriteSwarmIntField(UserCtx *user, const char *field_name, const char *ext)
2154{
2155 PetscErrorCode ierr;
2156 DM swarm = user->swarm;
2157 Vec temp_vec; // Temporary Vec to hold casted data
2158 PetscInt nlocal, nglobal,bs,i;
2159 PetscDataType field_type;
2160 void *field_array_void;
2161 PetscScalar *scalar_array; // Pointer to the temporary Vec's scalar data
2162
2163 PetscFunctionBeginUser;
2164
2165 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Casting '%s' to Vec for writing.\n", field_name);
2166
2167 // Get the swarm field properties
2168 ierr = DMSwarmGetLocalSize(swarm, &nlocal); CHKERRQ(ierr);
2169 ierr = DMSwarmGetSize(swarm, &nglobal); CHKERRQ(ierr);
2170 ierr = DMSwarmGetField(swarm, field_name, &bs, &field_type, &field_array_void); CHKERRQ(ierr);
2171 PetscCheck(field_type == PETSC_INT || field_type == PETSC_INT64,
2172 PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG,
2173 "Swarm field '%s' must use PETSC_INT or PETSC_INT64 for integer output, not %s.",
2174 field_name, PetscDataTypes[field_type]);
2175
2176 // Create Temporary parallel Vec wit the CORRECT layout
2177 ierr = VecCreate(PETSC_COMM_WORLD, &temp_vec); CHKERRQ(ierr);
2178 ierr = VecSetType(temp_vec, VECMPI); CHKERRQ(ierr);
2179 ierr = VecSetSizes(temp_vec, nlocal*bs, nglobal*bs); CHKERRQ(ierr);
2180 ierr = VecSetUp(temp_vec); CHKERRQ(ierr);
2181
2182 // Defining Vector field to mandatory field 'position'
2183 DMSwarmVectorDefineField(swarm,ParticleFieldName(PARTICLE_FIELD_ID_POSITION));
2184
2185 ierr = VecGetArray(temp_vec, &scalar_array); CHKERRQ(ierr);
2186
2187 if (field_type == PETSC_INT64) {
2188 PetscInt64 *int64_array = (PetscInt64 *)field_array_void;
2189 // Perform the cast from PetscInt64 to PetscScalar
2190 for (i = 0; i < nlocal*bs; i++) {
2191 scalar_array[i] = (PetscScalar)int64_array[i];
2192 }
2193 }else{
2194 PetscInt *int_array = (PetscInt *)field_array_void;
2195 //Perform the cast from PetscInt to PetscScalar
2196 for (i = 0; i < nlocal*bs; i++) {
2197 scalar_array[i] = (PetscScalar)int_array[i];
2198 }
2199 }
2200
2201 // Restore access to both arrays
2202 ierr = VecRestoreArray(temp_vec, &scalar_array); CHKERRQ(ierr);
2203 ierr = DMSwarmRestoreField(swarm, field_name, &bs, NULL, &field_array_void); CHKERRQ(ierr);
2204
2205 // Call your existing writer with the temporary, populated Vec
2206 ierr = WriteFieldData(user, field_name, temp_vec, ext); CHKERRQ(ierr);
2207
2208 // Clean up
2209 ierr = VecDestroy(&temp_vec); CHKERRQ(ierr);
2210
2211 PetscFunctionReturn(0);
2212}
2213
2214/**
2215 * @brief Internal helper implementation: `WriteAllSwarmFields()`.
2216 * @details Local to this translation unit.
2217 */
2218PetscErrorCode WriteAllSwarmFields(UserCtx *user, const char *checkpoint_directory)
2219{
2220 SimCtx *simCtx = user->simCtx;
2221
2222 PetscFunctionBeginUser;
2223
2224 // If no swarm is configured or there are no particles, do nothing and return.
2225 if (!user->swarm || simCtx->np <= 0) {
2226 PetscFunctionReturn(0);
2227 }
2228
2229 PetscCheck(checkpoint_directory != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
2230 "Checkpoint destination cannot be NULL.");
2231 PetscCall(PetscSNPrintf(simCtx->_io_context_buffer, sizeof(simCtx->_io_context_buffer),
2232 "%s/%s", checkpoint_directory, PICURV_PARTICLE_DIRECTORY));
2233 simCtx->current_io_directory = simCtx->_io_context_buffer;
2234
2235 for (PetscInt raw_id = 0; raw_id < PARTICLE_FIELD_ID_COUNT; ++raw_id) {
2236 const ParticleFieldDescriptor *descriptor = NULL;
2237
2238 PetscCall(ParticleFieldGetDescriptor((ParticleFieldId)raw_id, &descriptor));
2239 if (!(descriptor->capabilities & PARTICLE_FIELD_CAPABILITY_CHECKPOINT)) continue;
2240 if (descriptor->data_type == PETSC_INT || descriptor->data_type == PETSC_INT64) {
2241 PetscCall(WriteSwarmIntField(user, descriptor->canonical_name, "dat"));
2242 } else {
2243 PetscCall(WriteSwarmField(user, descriptor->canonical_name, "dat"));
2244 }
2245 }
2246
2247 simCtx->current_io_directory = NULL;
2248
2249 PetscFunctionReturn(0);
2250}
2251
2252/** @brief Append one payload inventory entry to a checkpoint manifest. */
2253static PetscErrorCode WriteCheckpointPayloadEntry(FILE *manifest,
2254 const char *checkpoint_directory,
2255 PetscInt payload_index,
2256 const char *relative_path,
2257 const char *kind,
2258 const char *field_name,
2259 PetscInt block,
2260 const char *layout,
2261 PetscInt components,
2262 const char *logical_type,
2263 PetscInt global_size,
2264 const char *encoding)
2265{
2266 char payload_path[PETSC_MAX_PATH_LEN];
2267 struct stat payload_stat;
2268
2269 PetscFunctionBeginUser;
2270 PetscCheck(manifest != NULL && checkpoint_directory != NULL && relative_path != NULL,
2271 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Checkpoint payload metadata inputs are required.");
2272 PetscCall(PetscSNPrintf(payload_path, sizeof(payload_path), "%s/%s",
2273 checkpoint_directory, relative_path));
2274 PetscCheck(stat(payload_path, &payload_stat) == 0 && S_ISREG(payload_stat.st_mode),
2275 PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN,
2276 "Expected checkpoint payload was not written: %s", payload_path);
2277 PetscCheck(fprintf(manifest,
2278 "-checkpoint_payload_%" PetscInt_FMT "_path %s\n"
2279 "-checkpoint_payload_%" PetscInt_FMT "_kind %s\n"
2280 "-checkpoint_payload_%" PetscInt_FMT "_field %s\n"
2281 "-checkpoint_payload_%" PetscInt_FMT "_block %" PetscInt_FMT "\n"
2282 "-checkpoint_payload_%" PetscInt_FMT "_layout %s\n"
2283 "-checkpoint_payload_%" PetscInt_FMT "_components %" PetscInt_FMT "\n"
2284 "-checkpoint_payload_%" PetscInt_FMT "_logical_type %s\n"
2285 "-checkpoint_payload_%" PetscInt_FMT "_global_size %" PetscInt_FMT "\n"
2286 "-checkpoint_payload_%" PetscInt_FMT "_encoding %s\n"
2287 "-checkpoint_payload_%" PetscInt_FMT "_bytes %lld\n",
2288 payload_index, relative_path,
2289 payload_index, kind,
2290 payload_index, field_name,
2291 payload_index, block,
2292 payload_index, layout,
2293 payload_index, components,
2294 payload_index, logical_type,
2295 payload_index, global_size,
2296 payload_index, encoding,
2297 payload_index, (long long)payload_stat.st_size) > 0,
2298 PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE,
2299 "Unable to append payload metadata for '%s'.", relative_path);
2300 PetscFunctionReturn(0);
2301}
2302
2303/** @brief Write the complete manifest after every payload has closed successfully. */
2304static PetscErrorCode WriteCheckpointManifest(SimCtx *simCtx, UserCtx *user,
2305 const char *checkpoint_directory,
2306 const char *reason,
2307 const char *geometry_digest,
2308 PetscInt particle_count,
2309 char manifest_digest[65])
2310{
2311 char metadata_path[PETSC_MAX_PATH_LEN];
2312 PetscInt payload_count = 0;
2313 PetscInt payload_index = 0;
2314 FILE *manifest = NULL;
2315
2316 PetscFunctionBeginUser;
2317 for (PetscInt block = 0; block < simCtx->block_number; ++block) {
2318 for (PetscInt raw_id = 0; raw_id < FIELD_ID_COUNT; ++raw_id) {
2319 const FieldDescriptor *descriptor = NULL;
2320 PetscCall(FieldGetDescriptor((FieldId)raw_id, &descriptor));
2321 if (CheckpointFieldIsEnabled(simCtx, descriptor)) ++payload_count;
2322 }
2323 }
2324 if (simCtx->np > 0) {
2325 for (PetscInt raw_id = 0; raw_id < PARTICLE_FIELD_ID_COUNT; ++raw_id) {
2326 const ParticleFieldDescriptor *descriptor = NULL;
2327 PetscCall(ParticleFieldGetDescriptor((ParticleFieldId)raw_id, &descriptor));
2328 if (descriptor->capabilities & PARTICLE_FIELD_CAPABILITY_CHECKPOINT) ++payload_count;
2329 }
2330 }
2331 if (FieldStatisticsIsActive(simCtx)) {
2332 for (PetscInt block = 0; block < simCtx->block_number; ++block) {
2333 if (!user[block].fieldStatisticsStorage) continue;
2334 for (PetscInt window = 0; window < simCtx->fieldStatisticsWindowCount; ++window) {
2335 PetscInt storage_payloads = 0;
2336
2337 PetscCall(PicurvWindowStoragePayloadCount(&user[block].fieldStatisticsStorage[window],
2338 &storage_payloads));
2339 payload_count += storage_payloads;
2340 }
2341 }
2342 }
2343
2344 if (simCtx->rank == 0) {
2345 PetscCall(PetscSNPrintf(metadata_path, sizeof(metadata_path),
2346 "%s/checkpoint.meta", checkpoint_directory));
2347 manifest = fopen(metadata_path, "w");
2348 PetscCheck(manifest != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN,
2349 "Unable to create checkpoint metadata '%s'.", metadata_path);
2350 PetscCheck(fprintf(manifest,
2351 "-checkpoint_format %s\n"
2352 "-checkpoint_version %d\n"
2353 "-checkpoint_picurv_release %s\n"
2354 "-checkpoint_picurv_commit %s\n"
2355 "-checkpoint_picurv_dirty %s\n"
2356 "-checkpoint_step %" PetscInt_FMT "\n"
2357 "-checkpoint_time %.17g\n"
2358 "-checkpoint_dt %.17g\n"
2359 "-checkpoint_reason %s\n"
2360 "-checkpoint_geometry_sha256 %s\n"
2361 "-checkpoint_block_count %" PetscInt_FMT "\n"
2362 "-checkpoint_particles %s\n"
2363 "-checkpoint_particle_count %" PetscInt_FMT "\n"
2364 "-checkpoint_les %s\n"
2365 "-checkpoint_payload_count %" PetscInt_FMT "\n",
2368 simCtx->step, (double)simCtx->ti, (double)simCtx->dt,
2369 reason, geometry_digest, simCtx->block_number,
2370 simCtx->np > 0 ? "true" : "false",
2371 particle_count,
2372 simCtx->les ? "true" : "false",
2373 payload_count) > 0,
2374 PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE,
2375 "Unable to write checkpoint metadata '%s'.", metadata_path);
2376
2377 for (PetscInt block = 0; block < simCtx->block_number; ++block) {
2378 PetscCheck(fprintf(manifest,
2379 "-checkpoint_block_%" PetscInt_FMT "_im %" PetscInt_FMT "\n"
2380 "-checkpoint_block_%" PetscInt_FMT "_jm %" PetscInt_FMT "\n"
2381 "-checkpoint_block_%" PetscInt_FMT "_km %" PetscInt_FMT "\n",
2382 block, user[block].IM, block, user[block].JM, block, user[block].KM) > 0,
2383 PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE,
2384 "Unable to write block metadata for block %" PetscInt_FMT ".", block);
2385 }
2386 PetscCheck(fprintf(manifest, "-checkpoint_periodic %d,%d,%d\n",
2387 (int)simCtx->i_periodic, (int)simCtx->j_periodic,
2388 (int)simCtx->k_periodic) > 0,
2389 PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE,
2390 "Unable to write checkpoint periodicity metadata.");
2391
2392 /* Driven-flow controller state. `initial_flux` measures its target once
2393 * from the starting field, so a restart must read the latched value back
2394 * rather than re-measure it from a field that has since drifted. */
2395 PetscCheck(fprintf(manifest,
2396 "-checkpoint_driven_flux_latched %s\n"
2397 "-checkpoint_driven_flux_target %.17g\n",
2398 simCtx->drivenFluxTargetLatched ? "true" : "false",
2399 (double)simCtx->targetVolumetricFlux) > 0,
2400 PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE,
2401 "Unable to write driven-flow controller metadata.");
2402
2403 /* Window scalars are recorded even when no window is configured, so a
2404 * restart can tell an absent window list from an unreadable bundle. */
2405 PetscCheck(fprintf(manifest, "-checkpoint_statistics_window_count %" PetscInt_FMT "\n",
2406 FieldStatisticsIsActive(simCtx) ? simCtx->fieldStatisticsWindowCount : 0) > 0,
2407 PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE,
2408 "Unable to write statistics window count.");
2409 if (FieldStatisticsIsActive(simCtx)) {
2410 for (PetscInt window = 0; window < simCtx->fieldStatisticsWindowCount; ++window) {
2411 const PicurvWindow *state = &simCtx->fieldStatisticsWindows[window];
2412 char digest[65] = "";
2414 /* Group digests plus their separators, with headroom so a longer
2415 * digest would not silently truncate into a false match. */
2417 size_t used = 0;
2418
2419 PetscCall(PicurvWindowComputeHash(&state->definition, digest, groups));
2420 for (PetscInt group = 0; group < PICURV_WINDOW_HASH_GROUP_COUNT; ++group) {
2421 PetscCall(PetscStrlen(group_list, &used));
2422 PetscCall(PetscSNPrintf(group_list + used, sizeof(group_list) - used, "%s%s",
2423 group ? "," : "", groups[group]));
2424 }
2425 PetscCheck(fprintf(manifest,
2426 "-checkpoint_statistics_window_%" PetscInt_FMT "_name %s\n"
2427 "-checkpoint_statistics_window_%" PetscInt_FMT "_hash %s\n"
2428 "-checkpoint_statistics_window_%" PetscInt_FMT "_hash_groups %s\n"
2429 "-checkpoint_statistics_window_%" PetscInt_FMT "_state %s\n"
2430 "-checkpoint_statistics_window_%" PetscInt_FMT "_sample_count %" PetscInt_FMT "\n"
2431 "-checkpoint_statistics_window_%" PetscInt_FMT "_total_weight %.17g\n"
2432 "-checkpoint_statistics_window_%" PetscInt_FMT "_represented_time %.17g\n"
2433 "-checkpoint_statistics_window_%" PetscInt_FMT "_last_accepted_time %.17g\n"
2434 "-checkpoint_statistics_window_%" PetscInt_FMT "_effective_start %.17g\n"
2435 "-checkpoint_statistics_window_%" PetscInt_FMT "_effective_end %.17g\n"
2436 "-checkpoint_statistics_window_%" PetscInt_FMT "_activation_step %" PetscInt_FMT "\n"
2437 "-checkpoint_statistics_window_%" PetscInt_FMT "_last_event_step %" PetscInt_FMT "\n"
2438 "-checkpoint_statistics_window_%" PetscInt_FMT "_next_time_target %" PetscInt_FMT "\n"
2439 "-checkpoint_statistics_window_%" PetscInt_FMT "_restart_count %" PetscInt_FMT "\n",
2440 window, state->definition.name,
2441 window, digest,
2442 window, group_list,
2443 window, PicurvWindowStateName(state->state),
2444 window, state->sample_count,
2445 window, (double)state->total_weight,
2446 window, (double)state->represented_time,
2447 window, (double)state->last_accepted_time,
2448 window, (double)state->effective_start,
2449 window, (double)state->effective_end,
2450 window, state->activation_step,
2451 window, state->last_event_step,
2452 window, state->next_time_target,
2453 window, state->restart_count) > 0,
2454 PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE,
2455 "Unable to write metadata for statistics window '%s'.",
2456 state->definition.name);
2457 }
2458 }
2459
2460 for (PetscInt block = 0; block < simCtx->block_number; ++block) {
2461 for (PetscInt raw_id = 0; raw_id < FIELD_ID_COUNT; ++raw_id) {
2462 const FieldDescriptor *descriptor = NULL;
2463 FieldView view;
2464 PetscInt global_size = 0;
2465 char relative_path[PETSC_MAX_PATH_LEN];
2466
2467 PetscCall(FieldGetDescriptor((FieldId)raw_id, &descriptor));
2468 if (!CheckpointFieldIsEnabled(simCtx, descriptor)) continue;
2469 PetscCall(FieldGetView(&user[block], descriptor->id, &view));
2470 PetscCall(VecGetSize(view.global_vec, &global_size));
2471 PetscCall(PetscSNPrintf(relative_path, sizeof(relative_path),
2472 "%s/block_%04" PetscInt_FMT "/%s.dat",
2473 PICURV_EULERIAN_DIRECTORY, block, descriptor->canonical_name));
2474 PetscCall(WriteCheckpointPayloadEntry(manifest, checkpoint_directory,
2475 payload_index++, relative_path,
2476 "eulerian", descriptor->canonical_name,
2477 block, FieldLayoutName(descriptor->layout),
2478 descriptor->dof, "PetscScalar", global_size,
2479 "petsc_vec_binary_natural"));
2480 }
2481 }
2482 if (simCtx->np > 0) {
2483 for (PetscInt raw_id = 0; raw_id < PARTICLE_FIELD_ID_COUNT; ++raw_id) {
2484 const ParticleFieldDescriptor *descriptor = NULL;
2485 char relative_path[PETSC_MAX_PATH_LEN];
2486 const char *encoding = "petsc_vec_binary_global";
2487
2488 PetscCall(ParticleFieldGetDescriptor((ParticleFieldId)raw_id, &descriptor));
2489 if (!(descriptor->capabilities & PARTICLE_FIELD_CAPABILITY_CHECKPOINT)) continue;
2490 if (descriptor->data_type == PETSC_INT || descriptor->data_type == PETSC_INT64) {
2491 encoding = "petsc_vec_binary_scalar_cast";
2492 }
2493 PetscCall(PetscSNPrintf(relative_path, sizeof(relative_path),
2494 "%s/%s.dat", PICURV_PARTICLE_DIRECTORY,
2495 descriptor->canonical_name));
2496 PetscCall(WriteCheckpointPayloadEntry(manifest, checkpoint_directory,
2497 payload_index++, relative_path,
2498 "particle", descriptor->canonical_name,
2499 -1, "DMSwarm", descriptor->components,
2500 PetscDataTypes[descriptor->data_type],
2501 particle_count * descriptor->components,
2502 encoding));
2503 }
2504 }
2505 if (FieldStatisticsIsActive(simCtx)) {
2506 for (PetscInt block = 0; block < simCtx->block_number; ++block) {
2507 if (!user[block].fieldStatisticsStorage) continue;
2508 for (PetscInt window = 0; window < simCtx->fieldStatisticsWindowCount; ++window) {
2509 const PicurvWindowDefinition *definition =
2510 &simCtx->fieldStatisticsWindows[window].definition;
2511 const PicurvWindowStorage *storage = &user[block].fieldStatisticsStorage[window];
2512 PetscInt storage_payloads = 0;
2513
2514 PetscCall(PicurvWindowStoragePayloadCount(storage, &storage_payloads));
2515 for (PetscInt index = 0; index < storage_payloads; ++index) {
2517 PetscInt global_size = 0;
2518 char relative_path[PETSC_MAX_PATH_LEN];
2519 char qualified_name[PETSC_MAX_PATH_LEN];
2520
2521 PetscCall(PicurvWindowStoragePayload(&user[block], definition, storage,
2522 index, &payload));
2523 PetscCall(VecGetSize(payload.vec, &global_size));
2524 PetscCall(FormatStatisticsPath(NULL, window, block, payload.name,
2525 relative_path, sizeof(relative_path)));
2526 /* The inventory field name is qualified by window so two
2527 * windows accumulating the same field stay distinguishable
2528 * to anything reading the manifest alone. */
2529 PetscCall(PetscSNPrintf(qualified_name, sizeof(qualified_name), "%s/%s",
2530 definition->name, payload.name));
2531 PetscCall(WriteCheckpointPayloadEntry(manifest, checkpoint_directory,
2532 payload_index++, relative_path,
2533 payload.role, qualified_name,
2534 block, payload.layout,
2535 payload.components, "PetscScalar",
2536 global_size,
2537 "petsc_vec_binary_natural"));
2538 }
2539 }
2540 }
2541 }
2542 PetscCheck(fflush(manifest) == 0 && fsync(fileno(manifest)) == 0,
2543 PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE,
2544 "Unable to flush checkpoint metadata '%s'.", metadata_path);
2545 PetscCheck(fclose(manifest) == 0, PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE,
2546 "Unable to close checkpoint metadata '%s'.", metadata_path);
2547 PetscCall(PicurvSHA256File(metadata_path, manifest_digest));
2548 }
2549 PetscCallMPI(MPI_Bcast(manifest_digest, 65, MPI_CHAR, 0, PETSC_COMM_WORLD));
2550 PetscFunctionReturn(0);
2551}
2552
2553/** @brief Implementation of the transactional full-state checkpoint coordinator. */
2554PetscErrorCode WriteCheckpointBundle(SimCtx *simCtx, const char *reason)
2555{
2556 UserCtx *user = NULL;
2557 char checkpoints_root[PETSC_MAX_PATH_LEN];
2558 char final_directory[PETSC_MAX_PATH_LEN];
2559 char temporary_directory[PETSC_MAX_PATH_LEN];
2560 char nested_directory[PETSC_MAX_PATH_LEN];
2561 char commit_path[PETSC_MAX_PATH_LEN];
2562 char geometry_digest[65] = "";
2563 char manifest_digest[65] = "";
2564 PetscBool final_exists = PETSC_FALSE;
2565 PetscInt particle_count = 0;
2566 PetscMPIInt writer_pid = 0;
2567
2568 PetscFunctionBeginUser;
2569 PetscCheck(simCtx != NULL && reason != NULL && reason[0] != '\0',
2570 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
2571 "Simulation context and checkpoint reason are required.");
2572 user = simCtx->usermg.mgctx[simCtx->usermg.mglevels - 1].user;
2573 PetscCheck(user != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
2574 "Finest-level fields must exist before checkpoint output.");
2575
2576 PetscCall(PetscSNPrintf(checkpoints_root, sizeof(checkpoints_root), "%s/%s",
2578 PetscCall(FormatCheckpointStepDirectory(simCtx->output_dir, simCtx->step,
2579 final_directory, sizeof(final_directory)));
2580 PetscCall(PetscTestDirectory(final_directory, 'r', &final_exists));
2581 if (final_exists) {
2582 PetscReal saved_time = 0.0;
2583 PetscCall(ValidateCheckpointBundle(simCtx, user, final_directory, simCtx->step,
2584 &saved_time, NULL, NULL, NULL));
2585 PetscCheck(PetscAbsReal(saved_time - simCtx->ti) <=
2586 10.0 * PETSC_MACHINE_EPSILON * PetscMax(1.0, PetscAbsReal(simCtx->ti)),
2587 PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED,
2588 "Committed checkpoint step %" PetscInt_FMT " has time %.17g, current state has time %.17g.",
2589 simCtx->step, (double)saved_time, (double)simCtx->ti);
2590 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Checkpoint step %d is already committed; skipping duplicate write.\n",
2591 simCtx->step);
2592 PetscFunctionReturn(0);
2593 }
2594
2595 if (simCtx->rank == 0) writer_pid = (PetscMPIInt)getpid();
2596 PetscCallMPI(MPI_Bcast(&writer_pid, 1, MPI_INT, 0, PETSC_COMM_WORLD));
2597 PetscCall(PetscSNPrintf(temporary_directory, sizeof(temporary_directory),
2598 "%s/.step_%0*" PetscInt_FMT ".incomplete.%d",
2599 checkpoints_root, PICURV_CHECKPOINT_STEP_WIDTH,
2600 simCtx->step, (int)writer_pid));
2601 PetscCall(CreateCheckpointDirectoryCollective(simCtx, checkpoints_root));
2602 PetscCall(CreateCheckpointDirectoryCollective(simCtx, temporary_directory));
2603 PetscCall(PetscSNPrintf(nested_directory, sizeof(nested_directory), "%s/%s",
2604 temporary_directory, PICURV_EULERIAN_DIRECTORY));
2605 PetscCall(CreateCheckpointDirectoryCollective(simCtx, nested_directory));
2606 for (PetscInt block = 0; block < simCtx->block_number; ++block) {
2607 PetscCall(PetscSNPrintf(nested_directory, sizeof(nested_directory),
2608 "%s/%s/block_%04" PetscInt_FMT,
2609 temporary_directory, PICURV_EULERIAN_DIRECTORY, block));
2610 PetscCall(CreateCheckpointDirectoryCollective(simCtx, nested_directory));
2611 }
2612 if (simCtx->np > 0) {
2613 PetscCall(PetscSNPrintf(nested_directory, sizeof(nested_directory), "%s/%s",
2614 temporary_directory, PICURV_PARTICLE_DIRECTORY));
2615 PetscCall(CreateCheckpointDirectoryCollective(simCtx, nested_directory));
2616 PetscCall(DMSwarmGetSize(user->swarm, &particle_count));
2617 }
2618 if (FieldStatisticsIsActive(simCtx)) {
2619 PetscCall(FormatStatisticsPath(temporary_directory, -1, -1, NULL,
2620 nested_directory, sizeof(nested_directory)));
2621 PetscCall(CreateCheckpointDirectoryCollective(simCtx, nested_directory));
2622 for (PetscInt window = 0; window < simCtx->fieldStatisticsWindowCount; ++window) {
2623 PetscCall(FormatStatisticsPath(temporary_directory, window, -1, NULL,
2624 nested_directory, sizeof(nested_directory)));
2625 PetscCall(CreateCheckpointDirectoryCollective(simCtx, nested_directory));
2626 for (PetscInt block = 0; block < simCtx->block_number; ++block) {
2627 PetscCall(FormatStatisticsPath(temporary_directory, window, block, NULL,
2628 nested_directory, sizeof(nested_directory)));
2629 PetscCall(CreateCheckpointDirectoryCollective(simCtx, nested_directory));
2630 }
2631 }
2632 }
2633
2634 PetscCall(ComputeCheckpointGeometrySHA256(simCtx, user, geometry_digest));
2635 for (PetscInt block = 0; block < simCtx->block_number; ++block) {
2636 PetscCall(WriteSimulationFields(&user[block], temporary_directory));
2637 }
2638 if (simCtx->np > 0) PetscCall(WriteAllSwarmFields(user, temporary_directory));
2639 for (PetscInt block = 0; block < simCtx->block_number; ++block) {
2640 PetscCall(WriteStatisticsFields(&user[block], temporary_directory));
2641 }
2642 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
2643
2644 PetscCall(WriteCheckpointManifest(simCtx, user, temporary_directory, reason,
2645 geometry_digest, particle_count, manifest_digest));
2646 if (simCtx->rank == 0) {
2647 FILE *commit_file = NULL;
2648
2649 PetscCall(PetscSNPrintf(commit_path, sizeof(commit_path), "%s/COMMITTED", temporary_directory));
2650 commit_file = fopen(commit_path, "w");
2651 PetscCheck(commit_file != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN,
2652 "Unable to create checkpoint commit marker '%s'.", commit_path);
2653 PetscCheck(fprintf(commit_file, "%s\n", manifest_digest) > 0 &&
2654 fflush(commit_file) == 0 && fsync(fileno(commit_file)) == 0,
2655 PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE,
2656 "Unable to write checkpoint commit marker '%s'.", commit_path);
2657 PetscCheck(fclose(commit_file) == 0, PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE,
2658 "Unable to close checkpoint commit marker '%s'.", commit_path);
2659 PetscCheck(rename(temporary_directory, final_directory) == 0,
2660 PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE,
2661 "Unable to commit checkpoint '%s': %s", final_directory, strerror(errno));
2662 }
2663 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
2665 "Committed checkpoint step %d at t=%.17g (%s): %s\n",
2666 simCtx->step, (double)simCtx->ti, reason, final_directory);
2667 PetscFunctionReturn(0);
2668}
2669
2670/**
2671 * @brief Internal helper implementation: `VecToArrayOnRank0()`.
2672 * @details Local to this translation unit.
2673 */
2674PetscErrorCode VecToArrayOnRank0(Vec inVec, PetscInt *N, double **arrayOut)
2675{
2676 MPI_Comm comm;
2677 PetscMPIInt rank;
2678 Vec sequential_vec = NULL;
2679
2680 PetscFunctionBeginUser;
2681 PetscCheck(inVec != NULL && N != NULL && arrayOut != NULL,
2682 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
2683 "Vector, size output, and array output are required.");
2684 PetscCall(PetscObjectGetComm((PetscObject)inVec, &comm));
2685 PetscCallMPI(MPI_Comm_rank(comm, &rank));
2686 PetscCall(VecGetSize(inVec, N));
2687 *arrayOut = NULL;
2688
2689 PetscCall(GatherVectorToRankZero(inVec, &sequential_vec));
2690 if (rank == 0) {
2691 const PetscScalar *values = NULL;
2692 PetscInt local_size = 0;
2693
2694 PetscCall(VecGetLocalSize(sequential_vec, &local_size));
2695 PetscCall(PetscMalloc1(local_size, arrayOut));
2696 PetscCall(VecGetArrayRead(sequential_vec, &values));
2697 for (PetscInt index = 0; index < local_size; ++index) {
2698 (*arrayOut)[index] = (double)PetscRealPart(values[index]);
2699 }
2700 PetscCall(VecRestoreArrayRead(sequential_vec, &values));
2701 }
2702 PetscCall(VecDestroy(&sequential_vec));
2703 PetscFunctionReturn(0);
2704}
2705
2706/**
2707 * @brief Internal helper implementation: `SwarmFieldToArrayOnRank0()`.
2708 * @details Local to this translation unit.
2709 */
2710PetscErrorCode SwarmFieldToArrayOnRank0(DM swarm, const char *field_name,
2711 PetscInt *n_total_particles, PetscInt *n_components,
2712 PetscDataType *field_type_out, void **gathered_array)
2713{
2714 PetscErrorCode ierr;
2715 PetscMPIInt rank, size;
2716 PetscInt nlocal, nglobal, bs;
2717 PetscDataType field_type;
2718 void *local_array_void;
2719 size_t element_size = 0;
2720
2721 PetscFunctionBeginUser;
2722
2723 PetscCheck(swarm != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "DMSwarm cannot be NULL.");
2724 PetscCheck(field_name != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Swarm field name cannot be NULL.");
2725 PetscCheck(n_total_particles != NULL && n_components != NULL && field_type_out != NULL && gathered_array != NULL,
2726 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
2727 "Swarm gather output pointers cannot be NULL.");
2728
2729 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
2730 ierr = MPI_Comm_size(PETSC_COMM_WORLD, &size); CHKERRQ(ierr);
2731
2732 // All ranks get swarm properties to determine send/receive counts
2733 ierr = DMSwarmGetLocalSize(swarm, &nlocal); CHKERRQ(ierr);
2734 ierr = DMSwarmGetSize(swarm, &nglobal); CHKERRQ(ierr);
2735 ierr = DMSwarmGetField(swarm, field_name, &bs, &field_type, &local_array_void); CHKERRQ(ierr);
2736
2737 // Determine the size of one element of the field's data type
2738 if (field_type == PETSC_INT64) element_size = sizeof(PetscInt64);
2739 else if (field_type == PETSC_INT) element_size = sizeof(PetscInt);
2740 else if (field_type == PETSC_REAL) element_size = sizeof(PetscReal);
2741#if defined(PETSC_USE_COMPLEX)
2742 else if (field_type == PETSC_SCALAR) element_size = sizeof(PetscScalar);
2743#endif
2744 else SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP,
2745 "Swarm field '%s' uses unsupported gathered data type %s.",
2746 field_name, PetscDataTypes[field_type]);
2747
2748 *field_type_out = field_type;
2749 *n_total_particles = nglobal;
2750 *n_components = bs;
2751 *gathered_array = NULL;
2752
2753 if (size == 1) { // Serial case is a simple copy
2754 if (rank == 0) {
2755 ierr = PetscMalloc(nglobal * bs * element_size, gathered_array); CHKERRQ(ierr);
2756 ierr = PetscMemcpy(*gathered_array, local_array_void, nglobal * bs * element_size); CHKERRQ(ierr);
2757 }
2758 } else { // Parallel case: use MPI_Gatherv
2759 PetscInt *recvcounts = NULL, *displs = NULL;
2760 if (rank == 0) {
2761 ierr = PetscMalloc1(size, &recvcounts); CHKERRQ(ierr);
2762 ierr = PetscMalloc1(size, &displs); CHKERRQ(ierr);
2763 }
2764 PetscInt sendcount = nlocal * bs;
2765
2766 // Gather the number of elements (not bytes) from each rank
2767 ierr = MPI_Gather(&sendcount, 1, MPIU_INT, recvcounts, 1, MPIU_INT, 0, PETSC_COMM_WORLD); CHKERRQ(ierr);
2768
2769 if (rank == 0) {
2770 displs[0] = 0;
2771 // Convert counts and calculate displacements in terms of BYTES
2772 for (PetscMPIInt i = 0; i < size; i++) recvcounts[i] *= element_size;
2773 for (PetscMPIInt i = 1; i < size; i++) displs[i] = displs[i-1] + recvcounts[i-1];
2774
2775 ierr = PetscMalloc(nglobal * bs * element_size, gathered_array); CHKERRQ(ierr);
2776 }
2777
2778 // Use Gatherv with MPI_BYTE to handle any data type generically
2779 ierr = MPI_Gatherv(local_array_void, nlocal * bs * element_size, MPI_BYTE,
2780 *gathered_array, recvcounts, displs, MPI_BYTE,
2781 0, PETSC_COMM_WORLD); CHKERRQ(ierr);
2782
2783 if (rank == 0) {
2784 ierr = PetscFree(recvcounts); CHKERRQ(ierr);
2785 ierr = PetscFree(displs); CHKERRQ(ierr);
2786 }
2787 }
2788
2789 ierr = DMSwarmRestoreField(swarm, field_name, &bs, NULL, &local_array_void); CHKERRQ(ierr);
2790
2791 PetscFunctionReturn(0);
2792}
2793
2794/**
2795 * @brief Emit the rank-zero startup summary from the effective simulation context.
2796 * @details Reports only configuration that applies to the selected run mode. In
2797 * particular, pseudo-CFL is a Dual Time Picard--Jameson RK control and
2798 * is deliberately omitted for explicit and Newton--Krylov momentum solves.
2799 */
2800PetscErrorCode DisplayBanner(SimCtx *simCtx) // bboxlist is only valid on rank 0
2801{
2802 PetscErrorCode ierr;
2803 PetscMPIInt rank;
2804 Cmpnts global_min_coords, global_max_coords;
2805 PetscReal StartTime;
2806 PetscInt StartStep,StepsToRun,total_num_particles;
2807 PetscMPIInt num_mpi_procs;
2808 const char *log_level_name;
2809 const char *convergence_mode_name;
2810
2811 // SimCtx *simCtx = user->simCtx;
2812 UserCtx *user = simCtx->usermg.mgctx[simCtx->usermg.mglevels - 1].user;
2813 num_mpi_procs = simCtx->size;
2814 StartTime = simCtx->StartTime;
2815 StartStep = simCtx->StartStep;
2816 StepsToRun = simCtx->StepsToRun;
2817 total_num_particles = simCtx->np;
2818 BoundingBox *bboxlist_on_rank0 = simCtx->bboxlist;
2819
2820
2821 PetscFunctionBeginUser;
2822
2823 if (!user) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "DisplayBanner - UserCtx pointer is NULL.");
2824 switch (get_log_level()) {
2825 case LOG_ERROR: log_level_name = "ERROR"; break;
2826 case LOG_WARNING: log_level_name = "WARNING"; break;
2827 case LOG_INFO: log_level_name = "INFO"; break;
2828 case LOG_DEBUG: log_level_name = "DEBUG"; break;
2829 case LOG_TRACE: log_level_name = "TRACE"; break;
2830 case LOG_VERBOSE: log_level_name = "VERBOSE"; break;
2831 default: log_level_name = "UNKNOWN"; break;
2832 }
2833 switch (simCtx->solutionConvergenceMode) {
2834 case SOLUTION_CONVERGENCE_STEADY_DETERMINISTIC: convergence_mode_name = "STEADY_DETERMINISTIC"; break;
2835 case SOLUTION_CONVERGENCE_PERIODIC_DETERMINISTIC: convergence_mode_name = "PERIODIC_DETERMINISTIC"; break;
2836 case SOLUTION_CONVERGENCE_STATISTICAL_STEADY: convergence_mode_name = "STATISTICAL_STEADY"; break;
2837 case SOLUTION_CONVERGENCE_TRANSIENT: convergence_mode_name = "TRANSIENT"; break;
2838 default: convergence_mode_name = "UNKNOWN"; break;
2839 }
2840 global_min_coords = user->bbox.min_coords;
2841 global_max_coords = user->bbox.max_coords;
2842 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
2843
2844 if (rank == 0) {
2845 // If global_domain_bbox is not pre-populated in UserCtx, compute it here from bboxlist_on_rank0
2846 // This assumes bboxlist_on_rank0 is valid and contains all local bounding boxes on rank 0.
2847 if (bboxlist_on_rank0 && num_mpi_procs > 0) {
2848 global_min_coords = bboxlist_on_rank0[0].min_coords;
2849 global_max_coords = bboxlist_on_rank0[0].max_coords;
2850 for (PetscMPIInt p = 1; p < num_mpi_procs; ++p) {
2851 global_min_coords.x = PetscMin(global_min_coords.x, bboxlist_on_rank0[p].min_coords.x);
2852 global_min_coords.y = PetscMin(global_min_coords.y, bboxlist_on_rank0[p].min_coords.y);
2853 global_min_coords.z = PetscMin(global_min_coords.z, bboxlist_on_rank0[p].min_coords.z);
2854 global_max_coords.x = PetscMax(global_max_coords.x, bboxlist_on_rank0[p].max_coords.x);
2855 global_max_coords.y = PetscMax(global_max_coords.y, bboxlist_on_rank0[p].max_coords.y);
2856 global_max_coords.z = PetscMax(global_max_coords.z, bboxlist_on_rank0[p].max_coords.z);
2857 }
2858 // Optionally store this in user->global_domain_bbox if it's useful elsewhere
2859 // user->global_domain_bbox.min_coords = global_min_coords;
2860 // user->global_domain_bbox.max_coords = global_max_coords;
2861 } else {
2862 // Fallback or warning if bboxlist is not available for global calculation
2863 LOG_ALLOW(LOCAL, LOG_WARNING, "(Rank 0) - bboxlist not provided or num_mpi_procs <=0; using user->bbox for domain bounds.\n");
2864 // global_min_coords = user->bbox.min_coords; // Use local bbox of rank 0 as fallback
2865 // global_max_coords = user->bbox.max_coords;
2866 }
2867
2868 ierr = PetscPrintf(PETSC_COMM_SELF, "\n"); CHKERRQ(ierr);
2869 ierr = PetscPrintf(PETSC_COMM_SELF, "=============================================================\n"); CHKERRQ(ierr);
2870 ierr = PetscPrintf(PETSC_COMM_SELF, " CASE SUMMARY \n"); CHKERRQ(ierr);
2871 ierr = PetscPrintf(PETSC_COMM_SELF, "=============================================================\n"); CHKERRQ(ierr);
2872 ierr = PetscPrintf(PETSC_COMM_SELF, " Grid Points : %d X %d X %d\n", user->IM, user->JM, user->KM); CHKERRQ(ierr);
2873 ierr = PetscPrintf(PETSC_COMM_SELF, " Cells : %d X %d X %d\n", user->IM - 1, user->JM - 1, user->KM - 1); CHKERRQ(ierr);
2874 ierr = PetscPrintf(PETSC_COMM_SELF, " Global Domain Bounds (X) : %.6f to %.6f\n", (double)global_min_coords.x, (double)global_max_coords.x); CHKERRQ(ierr);
2875 ierr = PetscPrintf(PETSC_COMM_SELF, " Global Domain Bounds (Y) : %.6f to %.6f\n", (double)global_min_coords.y, (double)global_max_coords.y); CHKERRQ(ierr);
2876 ierr = PetscPrintf(PETSC_COMM_SELF, " Global Domain Bounds (Z) : %.6f to %.6f\n", (double)global_min_coords.z, (double)global_max_coords.z); CHKERRQ(ierr);
2877 ierr = PetscPrintf(PETSC_COMM_SELF, " Periodic Axes (BC-derived) : I=%s, J=%s, K=%s\n",
2878 simCtx->i_periodic ? "YES" : "NO",
2879 simCtx->j_periodic ? "YES" : "NO",
2880 simCtx->k_periodic ? "YES" : "NO"); CHKERRQ(ierr);
2881 for (PetscInt axis = 0; axis < 3; axis++) {
2882 if (!user->periodic_translation_valid[axis]) continue;
2883 ierr = PetscPrintf(PETSC_COMM_SELF,
2884 " Periodic %c Translation : (%.6e, %.6e, %.6e)\n",
2885 "IJK"[axis],
2886 (double)user->periodic_translation[axis].x,
2887 (double)user->periodic_translation[axis].y,
2888 (double)user->periodic_translation[axis].z); CHKERRQ(ierr);
2889 }
2890 if (total_num_particles > 0 &&
2891 (simCtx->i_periodic || simCtx->j_periodic || simCtx->k_periodic)) {
2892 ierr = PetscPrintf(PETSC_COMM_SELF,
2893 " Particle Periodic Wrapping : UNSUPPORTED (Eulerian periodicity only)\n"); CHKERRQ(ierr);
2894 }
2895 if(strcmp(simCtx->eulerianSource,"load")==0 || strcmp(simCtx->eulerianSource,"solve")==0){
2896 ierr = PetscPrintf(PETSC_COMM_SELF, "-------------------- Boundary Conditions --------------------\n"); CHKERRQ(ierr);
2897 const int face_name_width = 17; // Adjusted for longer names (Zeta,Eta,Xi)
2898 for (PetscInt i_face = 0; i_face < 6; ++i_face) {
2899 BCFace current_face = (BCFace)i_face;
2900 // The BCFaceToString will now return the Xi, Eta, Zeta versions
2901 const char* face_str = BCFaceToString(current_face);
2902 const char* bc_type_str = BCTypeToString(user->boundary_faces[current_face].mathematical_type);
2903 const char* bc_handler_type_str = BCHandlerTypeToString(user->boundary_faces[current_face].handler_type);
2904 if(user->boundary_faces[current_face].mathematical_type == INLET){
2906 Cmpnts inlet_velocity = {0.0,0.0,0.0};
2907 PetscBool found;
2908 ierr = GetBCParamReal(user->boundary_faces[current_face].params,"vx",&inlet_velocity.x,&found); CHKERRQ(ierr);
2909 ierr = GetBCParamReal(user->boundary_faces[current_face].params,"vy",&inlet_velocity.y,&found); CHKERRQ(ierr);
2910 ierr = GetBCParamReal(user->boundary_faces[current_face].params,"vz",&inlet_velocity.z,&found); CHKERRQ(ierr);
2911 ierr = PetscPrintf(PETSC_COMM_SELF, " Face %-*s : %s - %s - [%.4f,%.4f,%.4f]\n",
2912 face_name_width, face_str, bc_type_str, bc_handler_type_str,inlet_velocity.x,inlet_velocity.y,inlet_velocity.z); CHKERRQ(ierr);
2913 } else if(user->boundary_faces[current_face].handler_type == BC_HANDLER_INLET_PARABOLIC){
2914 PetscReal v_max = 0.0;
2915 PetscBool found;
2916 ierr = GetBCParamReal(user->boundary_faces[current_face].params,"v_max",&v_max,&found); CHKERRQ(ierr);
2917 ierr = PetscPrintf(PETSC_COMM_SELF, " Face %-*s : %s - %s - v_max=%.4f\n",
2918 face_name_width, face_str, bc_type_str, bc_handler_type_str, v_max); CHKERRQ(ierr);
2919 } else if(user->boundary_faces[current_face].handler_type == BC_HANDLER_INLET_PROFILE_FROM_FILE){
2920 const char *source_file = "(missing)";
2921 for (BC_Param *param = user->boundary_faces[current_face].params; param; param = param->next) {
2922 if (strcasecmp(param->key, "source_file") == 0 && param->value) {
2923 source_file = param->value;
2924 break;
2925 }
2926 }
2927 ierr = PetscPrintf(PETSC_COMM_SELF, " Face %-*s : %s - %s - source_file=%s\n",
2928 face_name_width, face_str, bc_type_str, bc_handler_type_str, source_file); CHKERRQ(ierr);
2929 }
2931 PetscBool trimflag,foundtrimflag;
2932 ierr = GetDrivenSeamFluxFlag(user->boundary_faces[current_face].params,&trimflag,&foundtrimflag); CHKERRQ(ierr);
2933 ierr = PetscPrintf(PETSC_COMM_SELF, " Face %-*s : %s - %s - [from initial state] - %s\n",
2934 face_name_width, face_str, bc_type_str, bc_handler_type_str,trimflag?"Enforce seam flux":"Seam flux not enforced"); CHKERRQ(ierr);
2936 PetscReal flux;
2937 PetscBool trimflag,foundflux,foundtrimflag;
2938 ierr = GetBCParamReal(user->boundary_faces[current_face].params,"target_flux",&flux,&foundflux); CHKERRQ(ierr);
2939 ierr = GetDrivenSeamFluxFlag(user->boundary_faces[current_face].params,&trimflag,&foundtrimflag); CHKERRQ(ierr);
2940 ierr = PetscPrintf(PETSC_COMM_SELF, " Face %-*s : %s - %s - [%.4f] - %s\n",
2941 face_name_width, face_str, bc_type_str, bc_handler_type_str,flux,trimflag?"Enforce seam flux":"Seam flux not enforced"); CHKERRQ(ierr);
2942 } else{
2943 ierr = PetscPrintf(PETSC_COMM_SELF, " Face %-*s : %s - %s\n",
2944 face_name_width, face_str, bc_type_str,bc_handler_type_str); CHKERRQ(ierr);
2945 }
2946 }
2947 }
2948 ierr = PetscPrintf(PETSC_COMM_SELF, "-------------------------------------------------------------\n"); CHKERRQ(ierr);
2949 ierr = PetscPrintf(PETSC_COMM_SELF, " Run Mode : %s\n", simCtx->OnlySetup ? "SETUP ONLY" : "Full Simulation"); CHKERRQ(ierr);
2950 ierr = PetscPrintf(PETSC_COMM_SELF, " Start Time : %.4f\n", (double)StartTime); CHKERRQ(ierr);
2951 ierr = PetscPrintf(PETSC_COMM_SELF, " Timestep Size : %.4f\n", (double)simCtx->dt); CHKERRQ(ierr);
2952 ierr = PetscPrintf(PETSC_COMM_SELF, " Starting Step : %d\n", StartStep); CHKERRQ(ierr);
2953 ierr = PetscPrintf(PETSC_COMM_SELF, " Total Steps to Run : %d\n", StepsToRun); CHKERRQ(ierr);
2954 ierr = PetscPrintf(PETSC_COMM_SELF, " Ending Step : %d\n", StartStep + StepsToRun); CHKERRQ(ierr);
2955 if (simCtx->tiout > 0) {
2956 ierr = PetscPrintf(PETSC_COMM_SELF, " Field/Restart Cadence : every %d step(s)\n", simCtx->tiout); CHKERRQ(ierr);
2957 } else {
2958 ierr = PetscPrintf(PETSC_COMM_SELF, " Field/Restart Cadence : DISABLED\n"); CHKERRQ(ierr);
2959 }
2960 /* Recorded whether or not statistics are configured, so a log says plainly
2961 * whether monitoring was active rather than leaving its absence ambiguous. */
2962 if (FieldStatisticsIsActive(simCtx) && simCtx->statisticsConsoleOutputFreq > 0) {
2963 ierr = PetscPrintf(PETSC_COMM_SELF, " Statistics Console Cadence : every %d step(s), %d window(s)\n",
2964 simCtx->statisticsConsoleOutputFreq, simCtx->fieldStatisticsWindowCount); CHKERRQ(ierr);
2965 } else if (FieldStatisticsIsActive(simCtx)) {
2966 ierr = PetscPrintf(PETSC_COMM_SELF, " Statistics Console Cadence : DISABLED (%d window(s) accumulating)\n",
2967 simCtx->fieldStatisticsWindowCount); CHKERRQ(ierr);
2968 } else {
2969 ierr = PetscPrintf(PETSC_COMM_SELF, " Statistics Console Cadence : DISABLED (no window configured)\n"); CHKERRQ(ierr);
2970 }
2971 ierr = PetscPrintf(PETSC_COMM_SELF, " Immersed Boundary : %s\n", simCtx->immersed ? "ENABLED" : "DISABLED"); CHKERRQ(ierr);
2972 if (simCtx->walltimeGuardEnabled) {
2973 ierr = PetscPrintf(
2974 PETSC_COMM_SELF,
2975 " Runtime Walltime Guard : %s (warmup=%d, multiplier=%.2f, min=%.1f s, alpha=%.2f)\n",
2976 simCtx->walltimeGuardActive ? "ENABLED" : "CONFIGURED BUT INACTIVE",
2978 (double)simCtx->walltimeGuardMultiplier,
2979 (double)simCtx->walltimeGuardMinSeconds,
2980 (double)simCtx->walltimeGuardEstimatorAlpha
2981 ); CHKERRQ(ierr);
2982 } else {
2983 ierr = PetscPrintf(PETSC_COMM_SELF, " Runtime Walltime Guard : DISABLED\n"); CHKERRQ(ierr);
2984 }
2985 ierr = PetscPrintf(PETSC_COMM_SELF, " Console Log Level : %s\n", log_level_name); CHKERRQ(ierr);
2986 ierr = PetscPrintf(PETSC_COMM_SELF, " Profiling Timestep Output : %s\n", simCtx->profilingTimestepMode); CHKERRQ(ierr);
2987 ierr = PetscPrintf(PETSC_COMM_SELF, " Profiling Final Summary : %s\n", simCtx->profilingFinalSummary ? "ENABLED" : "DISABLED"); CHKERRQ(ierr);
2988 if (simCtx->runtimeMemoryLogEnabled) {
2989 ierr = PetscPrintf(PETSC_COMM_SELF, " Runtime Memory Log : ENABLED (%s)\n", simCtx->runtimeMemoryLogFile); CHKERRQ(ierr);
2990 } else {
2991 ierr = PetscPrintf(PETSC_COMM_SELF, " Runtime Memory Log : DISABLED\n"); CHKERRQ(ierr);
2992 }
2993 ierr = PetscPrintf(PETSC_COMM_SELF, " Solution Convergence Log : %s\n",
2994 simCtx->solutionConvergenceEnabled ? "ENABLED" : "DISABLED"); CHKERRQ(ierr);
2995 ierr = PetscPrintf(PETSC_COMM_SELF, " Number of MPI Processes : %d\n", num_mpi_procs); CHKERRQ(ierr);
2996 ierr = PetscPrintf(PETSC_COMM_WORLD," Number of Particles : %d\n", total_num_particles); CHKERRQ(ierr);
2997 if (simCtx->np > 0) {
2998 const char *particle_init_str = ParticleInitializationToString(simCtx->ParticleInitialization);
2999
3000 if (simCtx->particleConsoleOutputFreq > 0) {
3001 ierr = PetscPrintf(PETSC_COMM_SELF, " Particle Console Cadence : every %d step(s)\n", simCtx->particleConsoleOutputFreq); CHKERRQ(ierr);
3002 } else {
3003 ierr = PetscPrintf(PETSC_COMM_SELF, " Particle Console Cadence : DISABLED\n"); CHKERRQ(ierr);
3004 }
3005 ierr = PetscPrintf(PETSC_COMM_SELF, " Particle Log Row Sampling : every %d particle(s)\n", simCtx->LoggingFrequency); CHKERRQ(ierr);
3006 if (simCtx->StartStep > 0) {
3007 ierr = PetscPrintf(PETSC_COMM_SELF, " Particle Restart Mode : %s\n", simCtx->particleRestartMode); CHKERRQ(ierr);
3008 }
3009 ierr = PetscPrintf(PETSC_COMM_SELF, " Particle Initialization Mode: %s\n", particle_init_str); CHKERRQ(ierr);
3010 ierr = PetscPrintf(PETSC_COMM_SELF, " Interpolation Method : %s\n",
3011 simCtx->interpolationMethod == INTERP_TRILINEAR ? "Trilinear (direct cell-center)" : "CornerAveraged (legacy)"); CHKERRQ(ierr);
3014 if (user->inletFaceDefined) {
3015 ierr = PetscPrintf(PETSC_COMM_SELF, " Particles Initialized At : %s (Enum Val: %d)\n", BCFaceToString(user->identifiedInletBCFace), user->identifiedInletBCFace); CHKERRQ(ierr);
3016 } else {
3017 ierr = PetscPrintf(PETSC_COMM_SELF, " Particles Initialized At : --- (No INLET face identified)\n"); CHKERRQ(ierr);
3018 }
3019 }
3020 }
3021 if(strcmp(simCtx->eulerianSource,"solve")==0 || strcmp(simCtx->eulerianSource,"load")==0){
3022 ierr = PetscPrintf(PETSC_COMM_WORLD," Reynolds Number : %le\n", simCtx->ren); CHKERRQ(ierr);
3023 //ierr = PetscPrintf(PETSC_COMM_WORLD," Von-Neumann Number : %le\n", simCtx->vnn); CHKERRQ(ierr);
3024 if(strcmp(simCtx->eulerianSource,"solve")==0){
3025 //ierr = PetscPrintf(PETSC_COMM_WORLD," Stanton Number : %le\n", simCtx->st); CHKERRQ(ierr);
3026 ierr = PetscPrintf(PETSC_COMM_WORLD," Momentum Equation Solver : %s\n", MomentumSolverTypeToString(simCtx->mom_solver_type)); CHKERRQ(ierr);
3028 ierr = PetscPrintf(PETSC_COMM_WORLD," Initial Pseudo-CFL (Courant): %le\n", simCtx->pseudo_cfl); CHKERRQ(ierr);
3029 ierr = PetscPrintf(PETSC_COMM_WORLD," Pseudo-CFL Range : [%le, %le]\n", simCtx->min_pseudo_cfl, simCtx->max_pseudo_cfl); CHKERRQ(ierr);
3030 ierr = PetscPrintf(PETSC_COMM_WORLD," Pseudo-CFL Adaptation : growth=%le, reduction=%le, backtrack=%s\n",
3032 simCtx->no_pseudo_cfl_backtrack ? "DISABLED" : "ENABLED"); CHKERRQ(ierr);
3033 ierr = PetscPrintf(PETSC_COMM_WORLD," Pseudo-Time Iteration Limit : %d\n", simCtx->mom_max_pseudo_steps); CHKERRQ(ierr);
3034 } else if (simCtx->mom_solver_type == MOMENTUM_SOLVER_NEWTON_KRYLOV) {
3035 ierr = PetscPrintf(PETSC_COMM_WORLD," Newton-Krylov PETSc Controls: SNES/KSP options (mom_nk_*)\n"); CHKERRQ(ierr);
3036 ierr = PetscPrintf(PETSC_COMM_WORLD," Newton-Krylov History Log : %s\n", simCtx->mom_nk_monitor_history ? "ENABLED" : "DISABLED"); CHKERRQ(ierr);
3037 } else if (simCtx->mom_solver_type == MOMENTUM_SOLVER_EXPLICIT_RK) {
3038 ierr = PetscPrintf(PETSC_COMM_WORLD," Pseudo-Time Controller : NOT APPLICABLE\n"); CHKERRQ(ierr);
3039 }
3040 ierr = PetscPrintf(PETSC_COMM_WORLD," Solution Convergence Mode : %s\n", convergence_mode_name); CHKERRQ(ierr);
3042 ierr = PetscPrintf(PETSC_COMM_WORLD," Convergence Period : %d step(s)\n", simCtx->solutionConvergencePeriodSteps); CHKERRQ(ierr);
3044 ierr = PetscPrintf(PETSC_COMM_WORLD," Convergence Window : %d step(s)\n", simCtx->solutionConvergenceWindowSteps); CHKERRQ(ierr);
3045 }
3046 ierr = PetscPrintf(PETSC_COMM_WORLD," Large Eddy Simulation Model : %s\n", LESModelToString(simCtx->les)); CHKERRQ(ierr);
3047 if (simCtx->les != NO_LES_MODEL) {
3048 const LESConfig *les = &simCtx->les_config;
3049
3050 /* Reported with the user-facing spellings, so a line here can be
3051 matched against the case file that produced it. */
3052 if (simCtx->les == VREMAN) {
3053 ierr = PetscPrintf(PETSC_COMM_WORLD," LES Filter Width : per direction (cell edges; filter_width unused)\n"); CHKERRQ(ierr);
3054 } else {
3055 ierr = PetscPrintf(PETSC_COMM_WORLD," LES Filter Width : %s\n",
3056 LESFilterWidthModelToString(les->filter_width_model)); CHKERRQ(ierr);
3057 }
3058 if (simCtx->les == VREMAN) {
3059 ierr = PetscPrintf(PETSC_COMM_WORLD," LES Vreman Coefficient : %.4f (no coefficient field)\n",
3060 (double)les->vreman_coefficient); CHKERRQ(ierr);
3061 } else if (simCtx->les == WALE) {
3062 ierr = PetscPrintf(PETSC_COMM_WORLD," LES WALE Coefficient : %.4f (no coefficient field)\n",
3063 (double)les->wale_coefficient); CHKERRQ(ierr);
3064 } else if (simCtx->les == CONSTANT_SMAGORINSKY) {
3065 ierr = PetscPrintf(PETSC_COMM_WORLD," LES Smagorinsky Constant : %.4f (no coefficient field)\n",
3066 (double)les->constant_cs); CHKERRQ(ierr);
3067 } else {
3068 char directions[8] = "";
3069 PetscInt used = 0;
3070
3071 if (les->averaging_direction[0]) directions[used++] = 'i';
3072 if (les->averaging_direction[1]) directions[used++] = 'j';
3073 if (les->averaging_direction[2]) directions[used++] = 'k';
3074 directions[used] = '\0';
3075
3076 ierr = PetscPrintf(PETSC_COMM_WORLD," LES Test Filter : %s (width ratio %.3f)\n",
3078 (double)les->test_filter_width_ratio); CHKERRQ(ierr);
3079 ierr = PetscPrintf(PETSC_COMM_WORLD," LES Dynamic Update Cadence : every %d step(s)\n",
3080 (int)les->dynamic_frequency); CHKERRQ(ierr);
3081 /* An empty direction list under homogeneous averaging is not a
3082 missing answer: the directions are derived from periodicity at
3083 the first update, once the block is known. */
3084 ierr = PetscPrintf(PETSC_COMM_WORLD," LES Coefficient Averaging : %s%s%s\n",
3086 used > 0 ? " over " : "",
3087 used > 0 ? directions : ""); CHKERRQ(ierr);
3088 if (les->clip_mode == LES_CLIP_CLAMP) {
3089 ierr = PetscPrintf(PETSC_COMM_WORLD," LES Coefficient Limiting : clamp (max Cs %.3f)\n",
3090 (double)les->max_cs); CHKERRQ(ierr);
3091 } else {
3092 ierr = PetscPrintf(PETSC_COMM_WORLD," LES Coefficient Limiting : %s\n",
3093 LESClipModeToString(les->clip_mode)); CHKERRQ(ierr);
3094 }
3095 ierr = PetscPrintf(PETSC_COMM_WORLD," LES Total Viscosity Floor : %.3f x molecular\n",
3096 (double)les->min_viscosity_ratio); CHKERRQ(ierr);
3097 }
3098 ierr = PetscPrintf(PETSC_COMM_WORLD," LES Gradient (Clark) Term : %s\n",
3099 simCtx->les_gradient_model ? "ENABLED" : "DISABLED"); CHKERRQ(ierr);
3100 if (les->diagnostics_enabled) {
3101 ierr = PetscPrintf(PETSC_COMM_WORLD," LES Coefficient Diagnostics : ENABLED (les_coefficient.csv, every %d step(s))\n",
3102 (int)les->diagnostics_cadence); CHKERRQ(ierr);
3103 /* The Yoshizawa coefficient scales only the reported subgrid kinetic
3104 energy, so it belongs with the diagnostics line rather than above it. */
3105 ierr = PetscPrintf(PETSC_COMM_WORLD," LES Yoshizawa Coefficient : %.4f (subgrid energy estimate)\n",
3106 (double)les->yoshizawa_ci); CHKERRQ(ierr);
3107 } else {
3108 ierr = PetscPrintf(PETSC_COMM_WORLD," LES Coefficient Diagnostics : DISABLED\n"); CHKERRQ(ierr);
3109 }
3110 }
3111 /* Reported independently of the LES closure, because a wall function is
3112 configured independently of it. */
3113 if (simCtx->wallfunction) {
3114 /* Only the log law has a roughness term. Printing the height beside a
3115 model that discards it would report a control that is not in force. */
3117 ierr = PetscPrintf(PETSC_COMM_WORLD," Wall Function Model : %s (roughness height %.3e)\n",
3119 (double)simCtx->wall_roughness_height); CHKERRQ(ierr);
3120 } else {
3121 ierr = PetscPrintf(PETSC_COMM_WORLD," Wall Function Model : %s (smooth wall; no roughness term)\n",
3123 }
3124 } else {
3125 ierr = PetscPrintf(PETSC_COMM_WORLD," Wall Function Model : DISABLED\n"); CHKERRQ(ierr);
3126 }
3127 }
3128 if (strcmp(simCtx->eulerianSource, "load") == 0) {
3129 ierr = PetscPrintf(PETSC_COMM_SELF, " Eulerian State Source : load (%s)\n",
3130 simCtx->restart_dir); CHKERRQ(ierr);
3131 } else if (simCtx->StartStep > 0) {
3132 ierr = PetscPrintf(PETSC_COMM_SELF, " Eulerian State Source : restart step %d (%s)\n",
3133 simCtx->StartStep, simCtx->restart_dir); CHKERRQ(ierr);
3134 } else {
3135 const char* field_init_str = InitialConditionModeToString(simCtx->initialConditionMode);
3136 ierr = PetscPrintf(PETSC_COMM_SELF, " Eulerian State Source : initial condition (%s)\n",
3137 field_init_str); CHKERRQ(ierr);
3138 }
3139 if (strcmp(simCtx->eulerianSource, "solve") == 0 && simCtx->StartStep == 0 &&
3141 ierr = PetscPrintf(PETSC_COMM_SELF,
3142 " Constant Velocity (Cart.) : x=%.4f y=%.4f z=%.4f\n",
3143 (double)simCtx->InitialConstantContra.x,
3144 (double)simCtx->InitialConstantContra.y,
3145 (double)simCtx->InitialConstantContra.z); CHKERRQ(ierr);
3146 } else if (strcmp(simCtx->eulerianSource, "solve") == 0 && simCtx->StartStep == 0 &&
3148 ierr = PetscPrintf(PETSC_COMM_SELF,
3149 " Constant Velocity (Curv.) : speed=%.4f direction=%s\n",
3150 (double)simCtx->icVelocityPhysical,
3151 FlowDirectionToString(simCtx->flowDirection)); CHKERRQ(ierr);
3152 } else if (strcmp(simCtx->eulerianSource, "solve") == 0 && simCtx->StartStep == 0 &&
3154 ierr = PetscPrintf(PETSC_COMM_SELF,
3155 " Poiseuille Peak Velocity : speed=%.4f direction=%s\n",
3156 (double)simCtx->icVelocityPhysical,
3157 FlowDirectionToString(simCtx->flowDirection)); CHKERRQ(ierr);
3158 } else if (strcmp(simCtx->eulerianSource, "solve") == 0 && simCtx->StartStep == 0 &&
3160 ierr = PetscPrintf(PETSC_COMM_SELF,
3161 " Initial Velocity File : field=%s directory=%s\n",
3162 simCtx->initialConditionField == IC_FIELD_UCAT ? "Ucat" : "Ucont",
3163 simCtx->initialConditionDirectory); CHKERRQ(ierr);
3164 }
3165 } else if(strcmp(simCtx->eulerianSource,"analytical")==0){
3166 ierr = PetscPrintf(PETSC_COMM_WORLD," Analytical Solution Type : %s\n", simCtx->AnalyticalSolutionType); CHKERRQ(ierr);
3167 }
3168 ierr = PetscPrintf(PETSC_COMM_SELF, "=============================================================\n"); CHKERRQ(ierr);
3169 ierr = PetscPrintf(PETSC_COMM_SELF, "\n"); CHKERRQ(ierr);
3170 }
3171 PetscFunctionReturn(0);
3172}
3173
3174#undef __FUNCT__
3175#define __FUNCT__ "PicurvPhysicalTime"
3176/** @brief Implementation of \ref PicurvPhysicalTime(). */
3177PetscErrorCode PicurvPhysicalTime(const SimCtx *simCtx, PetscReal solver_time, PetscReal *physical)
3178{
3179 const FieldDimension time_dimension = FIELD_DIM_TIME;
3180 PetscReal scale = 1.0;
3181
3182 PetscFunctionBeginUser;
3183 PetscCheck(simCtx && physical, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
3184 "Context and physical-time output are required.");
3185 PetscCall(FieldDimensionReferenceScale(&simCtx->scaling, time_dimension, &scale));
3186 *physical = solver_time * scale;
3187 PetscFunctionReturn(0);
3188}
3189
3190#undef __FUNCT__
3191#define __FUNCT__ "PicurvFieldReferenceScale"
3192/**
3193 * @brief Implementation of \ref PicurvFieldReferenceScale().
3194 * @details The argument and return contract is documented in `include/io.h`.
3195 * @see PicurvFieldReferenceScale()
3196 */
3197PetscErrorCode PicurvFieldReferenceScale(SimCtx *simCtx, const char *field_name,
3198 PetscReal *scale, char *description,
3199 size_t description_length)
3200{
3201 FieldDimension dimension;
3202 FieldId field_id = FIELD_ID_INVALID;
3204 PetscBool found = PETSC_FALSE;
3205
3206 PetscFunctionBeginUser;
3207 PetscCheck(simCtx && field_name && scale, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
3208 "Context, field name, and output scale are required.");
3209
3210 /* The dimension is a property of the field, recorded on its catalog entry. */
3211 PetscCall(FieldTryIdFromName(field_name, &field_id, &found));
3212 if (found) {
3213 const FieldDescriptor *descriptor = NULL;
3214
3215 PetscCall(FieldGetDescriptor(field_id, &descriptor));
3216 dimension = descriptor->dimension;
3217 } else {
3218 const ParticleFieldDescriptor *descriptor = NULL;
3219
3220 PetscCall(ParticleFieldTryIdFromName(field_name, &particle_id, &found));
3221 PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_ARG_UNKNOWN_TYPE,
3222 "Field '%s' is in neither the Eulerian nor the particle field catalog.", field_name);
3223 PetscCall(ParticleFieldGetDescriptor(particle_id, &descriptor));
3224 dimension = descriptor->dimension;
3225 }
3226 PetscCheck(dimension.kind == FIELD_DIMENSION_FIXED, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
3227 "Field '%s' has no fixed physical dimension, so it has no reference scale.", field_name);
3228 PetscCall(FieldDimensionReferenceScale(&simCtx->scaling, dimension, scale));
3229 if (description && description_length) {
3230 PetscCall(FieldDimensionLabel(dimension, description, description_length));
3231 }
3232 PetscFunctionReturn(0);
3233}
3234
3235
3236/**
3237 * @brief Internal helper implementation: `ParsePostProcessingSettings()`.
3238 * @details Local to this translation unit.
3239 */
3241{
3242 FILE *file;
3243 char line[1024];
3244 PetscBool startTimeSet, endTimeSet, timeStepSet;
3245
3246 PetscFunctionBeginUser;
3248
3249 if (!simCtx || !simCtx->pps) {
3250 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_NULL, "SimCtx or its pps member is NULL in ParsePostProcessingSettings.");
3251 }
3252
3253 char *configFile = simCtx->PostprocessingControlFile;
3254 PostProcessParams *pps = simCtx->pps;
3255
3256
3257 // --- 1. Set Sane Defaults First ---
3258 pps->startTime = 0;
3259 pps->endTime = 0;
3260 pps->timeStep = 1;
3261 pps->step_list_file[0] = '\0';
3262 pps->outputParticles = PETSC_FALSE;
3263 pps->particle_output_freq = simCtx->LoggingFrequency; // Default to logging frequency;
3264 strcpy(pps->process_pipeline, "");
3265 strcpy(pps->output_fields_instantaneous, "Ucat,P");
3266 strcpy(pps->output_prefix, "Field");
3267 strcpy(pps->particle_output_prefix,"Particle");
3268 strcpy(pps->particle_fields,"velocity,CellID,weight,pid");
3269 strcpy(pps->particle_pipeline,"");
3270 strncpy(pps->statistics_pipeline, "", MAX_PIPELINE_LENGTH - 1);
3271 strncpy(pps->statistics_output_prefix, "Stats", MAX_FILENAME_LENGTH - 1);
3272 strcpy(pps->particleExt,"dat"); // The input file format for particles.
3273 strcpy(pps->eulerianExt,"dat"); // The input file format for Eulerian fields.
3274 /* A negative source step means "the last step this recipe covers", so a recipe
3275 * that does not name one still derives from a bundle that exists. */
3276 pps->field_statistics_windows[0] = '\0';
3277 strcpy(pps->field_statistics_outputs, "mean,reynolds_stress,rms,tke,flux");
3278 strcpy(pps->field_statistics_formats, "vtk");
3279 strcpy(pps->field_statistics_output_prefix, "Field");
3280 pps->dimensionalize = PETSC_FALSE;
3282 pps->reference[0] = pps->reference[1] = pps->reference[2] = 1;
3283 strncpy(pps->source_dir, simCtx->output_dir, sizeof(pps->source_dir) - 1);
3284 pps->source_dir[sizeof(pps->source_dir) - 1] = '\0'; // Ensure null-termination
3285
3286 // --- 2. Parse the Configuration File (overrides defaults) ---
3287 file = fopen(configFile, "r");
3288 if (file) {
3289 LOG_ALLOW(GLOBAL, LOG_INFO, "Parsing post-processing config file: %s\n", configFile);
3290 while (fgets(line, sizeof(line), file)) {
3291 char *key, *value, *comment;
3292 comment = strchr(line, '#'); if (comment) *comment = '\0';
3293 TrimWhitespace(line); if (strlen(line) == 0) continue;
3294 key = strtok(line, "="); value = strtok(NULL, "=");
3295 if (key && value) {
3296 TrimWhitespace(key); TrimWhitespace(value);
3297 if (strcmp(key, "startTime") == 0) pps->startTime = atoi(value);
3298 else if (strcmp(key, "endTime") == 0) pps->endTime = atoi(value);
3299 else if (strcmp(key, "timeStep") == 0) pps->timeStep = atoi(value);
3300 else if (strcmp(key, "step_list_file") == 0) {
3301 strncpy(pps->step_list_file, value, sizeof(pps->step_list_file) - 1);
3302 pps->step_list_file[sizeof(pps->step_list_file) - 1] = '\0';
3303 }
3304 else if (strcmp(key, "output_particles") == 0) {
3305 if (strcasecmp(value, "true") == 0) pps->outputParticles = PETSC_TRUE;
3306 }
3307 else if (strcasecmp(key, "process_pipeline") == 0) {
3308 strncpy(pps->process_pipeline, value, MAX_PIPELINE_LENGTH - 1);
3309 pps->process_pipeline[MAX_PIPELINE_LENGTH - 1] = '\0'; // Ensure null-termination
3310 } else if (strcasecmp(key, "field_statistics_windows") == 0) {
3311 strncpy(pps->field_statistics_windows, value, MAX_FIELD_LIST_LENGTH - 1);
3313 } else if (strcasecmp(key, "field_statistics_formats") == 0) {
3314 strncpy(pps->field_statistics_formats, value, MAX_FIELD_LIST_LENGTH - 1);
3316 } else if (strcasecmp(key, "field_statistics_outputs") == 0) {
3317 strncpy(pps->field_statistics_outputs, value, MAX_FIELD_LIST_LENGTH - 1);
3319 } else if (strcasecmp(key, "field_statistics_source_step") == 0) {
3320 pps->field_statistics_source_step = atoi(value);
3321 } else if (strcasecmp(key, "dimensionalize") == 0) {
3322 pps->dimensionalize = (PetscBool)(strcasecmp(value, "true") == 0
3323 || strcmp(value, "1") == 0);
3324 } else if (strcasecmp(key, "field_statistics_output_prefix") == 0) {
3325 strncpy(pps->field_statistics_output_prefix, value,
3326 sizeof(pps->field_statistics_output_prefix) - 1);
3328 sizeof(pps->field_statistics_output_prefix) - 1] = '\0';
3329 } else if (strcasecmp(key, "output_fields_instantaneous") == 0) {
3330 strncpy(pps->output_fields_instantaneous, value, MAX_FIELD_LIST_LENGTH - 1);
3332 } else if (strcasecmp(key, "output_prefix") == 0) {
3333 strncpy(pps->output_prefix, value, MAX_FILENAME_LENGTH - 1);
3334 pps->output_prefix[MAX_FILENAME_LENGTH - 1] = '\0';
3335 } else if (strcasecmp(key, "particle_output_prefix") == 0) {
3336 strncpy(pps->particle_output_prefix, value, MAX_FILENAME_LENGTH - 1);
3338 } else if (strcasecmp(key, "particle_fields_instantaneous") == 0) {
3339 strncpy(pps->particle_fields, value, MAX_FIELD_LIST_LENGTH - 1);
3340 pps->particle_fields[MAX_FIELD_LIST_LENGTH - 1] = '\0';
3341 } else if (strcasecmp(key, "particle_pipeline") == 0) {
3342 strncpy(pps->particle_pipeline, value, MAX_PIPELINE_LENGTH - 1);
3343 pps->particle_pipeline[MAX_PIPELINE_LENGTH - 1] = '\0';
3344 } else if (strcasecmp(key, "particle_output_freq") == 0) {
3345 pps->particle_output_freq = atoi(value);
3346 } else if (strcasecmp(key, "statistics_pipeline") == 0) {
3347 strncpy(pps->statistics_pipeline, value, MAX_PIPELINE_LENGTH - 1);
3349 } else if (strcasecmp(key, "statistics_output_prefix") == 0) {
3350 strncpy(pps->statistics_output_prefix, value, MAX_FILENAME_LENGTH - 1);
3352 } else if (strcasecmp(key, "particleExt") == 0) {
3353 strncpy(pps->particleExt, value, sizeof(pps->particleExt) - 1);
3354 pps->particleExt[sizeof(pps->particleExt) - 1] = '\0';
3355 } else if (strcasecmp(key, "eulerianExt") == 0) {
3356 strncpy(pps->eulerianExt, value, sizeof(pps->eulerianExt) - 1);
3357 pps->eulerianExt[sizeof(pps->eulerianExt) - 1] = '\0';
3358 } else if (strcmp(key, "reference_ip") == 0) {pps->reference[0] = atoi(value);
3359 } else if (strcmp(key, "reference_jp") == 0) {pps->reference[1] = atoi(value);
3360 } else if (strcmp(key, "reference_kp") == 0) {pps->reference[2] = atoi(value);
3361 } else if (strcasecmp(key, "source_directory") == 0) {
3362 strncpy(pps->source_dir, value, sizeof(pps->source_dir) - 1);
3363 pps->source_dir[sizeof(pps->source_dir) - 1] = '\0';
3364 } else if (strcasecmp(key, "spectra_signature") == 0) {
3365 /* Spectra are computed by the conductor's Python stage, not here. The
3366 key exists so a change to the spectra recipe reaches the recipe
3367 fingerprint each step's output record carries; this executable has
3368 no use for it and accepting it silently keeps the log free of a
3369 warning that would appear on every post-processing run. */
3370 } else {
3371 LOG_ALLOW(GLOBAL, LOG_WARNING, "Unknown key '%s' in post-processing config file. Ignoring.\n", key);
3372 }
3373 // Add parsing for pipeline, fields, etc. in later phases
3374 }
3375 }
3376 fclose(file);
3377 } else {
3378 LOG_ALLOW(GLOBAL, LOG_WARNING, "Could not open post-processing config file '%s'. Using defaults and command-line overrides.\n", configFile);
3379 }
3380
3381 // --- 3. Parse Command-Line Options (overrides file settings and defaults) ---
3382 PetscOptionsGetInt(NULL, NULL, "-startTime", &pps->startTime, &startTimeSet);
3383 PetscOptionsGetInt(NULL, NULL, "-endTime", &pps->endTime, &endTimeSet);
3384 PetscCall(PetscOptionsGetInt(NULL, NULL, "-timeStep", &pps->timeStep, &timeStepSet));
3385 PetscCheck(pps->timeStep > 0, PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
3386 "Post-processing timeStep must be positive (got %" PetscInt_FMT "). "
3387 "For one checkpoint, set startTime=endTime and timeStep=1.", pps->timeStep);
3388 PetscOptionsGetBool(NULL, NULL, "-output_particles", &pps->outputParticles, NULL);
3389
3390 if(pps->endTime==-1){
3391 pps->endTime = simCtx->StartStep + simCtx->StepsToRun; // Total steps if endTime is set to -1.
3392 }
3393
3394 // If only startTime is given on command line, run for a single step
3395 if (startTimeSet && !endTimeSet) {
3396 pps->endTime = pps->startTime;
3397 }
3398
3399 LOG_ALLOW(GLOBAL, LOG_INFO, "Post-processing configured to run from t=%d to t=%d with step %d. Particle output: %s.\n",
3400 pps->startTime, pps->endTime, pps->timeStep, pps->outputParticles ? "TRUE" : "FALSE");
3401
3402 LOG_ALLOW(GLOBAL, LOG_INFO, "Process Pipeline: %s\n", pps->process_pipeline);
3403 LOG_ALLOW(GLOBAL, LOG_INFO, "Instantaneous Output Fields: %s\n", pps->output_fields_instantaneous);
3404 LOG_ALLOW(GLOBAL, LOG_INFO, "Output Prefix: %s\n", pps->output_prefix);
3405 LOG_ALLOW(GLOBAL, LOG_INFO, "Particle Output Prefix: %s\n", pps->particle_output_prefix);
3406 LOG_ALLOW(GLOBAL, LOG_INFO, "Particle Fields: %s\n", pps->particle_fields);
3407 LOG_ALLOW(GLOBAL, LOG_INFO, "Particle Pipeline: %s\n", pps->particle_pipeline);
3408 LOG_ALLOW(GLOBAL, LOG_INFO, "Particle Output Frequency: %d\n", pps->particle_output_freq);
3409 LOG_ALLOW(GLOBAL, LOG_INFO, "Post input extensions: Eulerian='.%s', Particle='.%s'\n", pps->eulerianExt, pps->particleExt);
3410
3412 PetscFunctionReturn(0);
3413}
3414
3415#undef __FUNCT__
3416#define __FUNCT__ "ParsePostProcessingSettings"
3417#undef __FUNCT__
3418#define __FUNCT__ "ParseScalingInformation"
3419/**
3420 * @brief Implementation of \ref ParseScalingInformation().
3421 * @details Full API contract (arguments, ownership, side effects) is documented with
3422 * the header declaration in `include/io.h`.
3423 * @see ParseScalingInformation()
3424 */
3425PetscErrorCode ParseScalingInformation(SimCtx *simCtx)
3426{
3427 PetscErrorCode ierr;
3428 PetscBool flg;
3429
3430 PetscFunctionBeginUser;
3432
3433 if (!simCtx) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "SimCtx is NULL in ParseScalingInformation");
3434
3435 // --- 1. Set default values to 1.0 ---
3436 // This represents a purely non-dimensional run if no scaling is provided.
3437 simCtx->scaling.L_ref = 1.0;
3438 simCtx->scaling.U_ref = 1.0;
3439 simCtx->scaling.rho_ref = 1.0;
3440
3441 // --- 2. Read overrides from the command line / control file ---
3442 ierr = PetscOptionsGetReal(NULL, NULL, "-scaling_L_ref", &simCtx->scaling.L_ref, &flg); CHKERRQ(ierr);
3443 ierr = PetscOptionsGetReal(NULL, NULL, "-scaling_U_ref", &simCtx->scaling.U_ref, &flg); CHKERRQ(ierr);
3444 ierr = PetscOptionsGetReal(NULL, NULL, "-scaling_rho_ref", &simCtx->scaling.rho_ref, &flg); CHKERRQ(ierr);
3445
3446 // --- 3. Calculate derived scaling factors ---
3447 // Check for division by zero to be safe, though U_ref should be positive.
3448 if (simCtx->scaling.U_ref <= 0.0) {
3449 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE, "Reference velocity U_ref must be positive. Got %g", (double)simCtx->scaling.U_ref);
3450 }
3451 simCtx->scaling.P_ref = simCtx->scaling.rho_ref * simCtx->scaling.U_ref * simCtx->scaling.U_ref;
3452
3453 // --- 4. Log the final, effective scales for verification ---
3454 LOG(GLOBAL, LOG_INFO, "---------------- Physical Scales Initialized -----------------\n");
3455 LOG(GLOBAL, LOG_INFO, " L_ref: %.4f, U_ref: %.4f, rho_ref: %.4f, P_ref: %.4f\n",
3456 simCtx->scaling.L_ref, simCtx->scaling.U_ref, simCtx->scaling.rho_ref, simCtx->scaling.P_ref);
3457 LOG(GLOBAL, LOG_INFO, "--------------------------------------------------------------\n");
3458
3460 PetscFunctionReturn(0);
3461}
3462
3463/**
3464 * @brief Implementation of \ref ReadDataFileToArray().
3465 * @details Full API contract (arguments, ownership, side effects) is documented with
3466 * the header declaration in `include/io.h`.
3467 * @see ReadDataFileToArray()
3468 */
3469PetscInt ReadDataFileToArray(const char *filename,
3470 double **data_out,
3471 PetscInt *Nout,
3472 MPI_Comm comm)
3473{
3474 /* STEP 0: Prepare local variables & log function entry */
3475 PetscMPIInt rank, size;
3476 PetscErrorCode ierr;
3477 FILE *fp = NULL;
3478 PetscInt N = 0; /* number of lines/values read on rank 0 */
3479 double *array = NULL; /* pointer to local array on each rank */
3480 PetscInt fileExistsFlag = 0; /* 0 = doesn't exist, 1 = does exist */
3481
3483 "Start reading from file: %s\n",
3484 filename);
3485
3486 /* Basic error checking: data_out, Nout must be non-null. */
3487 if (!filename || !data_out || !Nout) {
3489 "Null pointer argument provided.\n");
3490 return 1;
3491 }
3492
3493 /* Determine rank/size for coordinating I/O. */
3494 MPI_Comm_rank(comm, &rank);
3495 MPI_Comm_size(comm, &size);
3496
3497 /* STEP 1: On rank 0, check if file can be opened. */
3498 if (!rank) {
3499 fp = fopen(filename, "r");
3500 if (fp) {
3501 fileExistsFlag = 1;
3502 fclose(fp);
3503 }
3504 }
3505
3506 /* STEP 2: Broadcast file existence to all ranks. */
3507 // In ReadDataFileToArray:
3508 ierr = MPI_Bcast(&fileExistsFlag, 1, MPI_INT, 0, comm); CHKERRQ(ierr);
3509
3510 if (!fileExistsFlag) {
3511 /* If file does not exist, log & return. */
3512 if (!rank) {
3514 "File '%s' not found.\n",
3515 filename);
3516 }
3517 return 2;
3518 }
3519
3520 /* STEP 3: Rank 0 re-opens and reads the file, counting lines, etc. */
3521 if (!rank) {
3522 fp = fopen(filename, "r");
3523 if (!fp) {
3525 "File '%s' could not be opened for reading.\n",
3526 filename);
3527 return 3;
3528 }
3529
3530 /* (3a) Count lines first. */
3531 {
3532 char line[256];
3533 while (fgets(line, sizeof(line), fp)) {
3534 N++;
3535 }
3536 }
3537
3539 "File '%s' has %d lines.\n",
3540 filename, N);
3541
3542 /* (3b) Allocate array on rank 0. */
3543 array = (double*)malloc(N * sizeof(double));
3544 if (!array) {
3545 fclose(fp);
3547 "malloc failed for array.\n");
3548 return 4;
3549 }
3550
3551 /* (3c) Rewind & read values into array. */
3552 rewind(fp);
3553 {
3554 PetscInt i = 0;
3555 char line[256];
3556 while (fgets(line, sizeof(line), fp)) {
3557 double val;
3558 if (sscanf(line, "%lf", &val) == 1) {
3559 array[i++] = val;
3560 }
3561 }
3562 }
3563 fclose(fp);
3564
3566 "Successfully read %d values from '%s'.\n",
3567 N, filename);
3568 }
3569
3570 /* STEP 4: Broadcast the integer N to all ranks. */
3571 ierr = MPI_Bcast(&N, 1, MPI_INT, 0, comm); CHKERRQ(ierr);
3572
3573 /* STEP 5: Each rank allocates an array to receive the broadcast if rank>0. */
3574 if (rank) {
3575 array = (double*)malloc(N * sizeof(double));
3576 if (!array) {
3578 "malloc failed on rank %d.\n",
3579 rank);
3580 return 5;
3581 }
3582 }
3583
3584 /* STEP 6: Broadcast the actual data from rank 0 to all. */
3585 ierr = MPI_Bcast(array, N, MPI_DOUBLE, 0, comm); CHKERRQ(ierr);
3586
3587 /* STEP 7: Assign outputs on all ranks. */
3588 *data_out = array;
3589 *Nout = N;
3590
3592 "Done. Provided array of length=%d to all ranks.\n",
3593 N);
3594 return 0; /* success */
3595}
3596
3597/**
3598 * @brief Internal helper implementation: `ReadPositionsFromFile()`.
3599 * @details Local to this translation unit.
3600 */
3601PetscErrorCode ReadPositionsFromFile(PetscInt timeIndex,
3602 UserCtx *user,
3603 double **coordsArray,
3604 PetscInt *Ncoords)
3605{
3606 PetscFunctionBeginUser;
3607
3608 PetscErrorCode ierr;
3609 Vec coordsVec;
3610
3611 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Creating coords Vec.\n");
3612 ierr = VecCreate(PETSC_COMM_WORLD, &coordsVec);CHKERRQ(ierr);
3613 ierr = VecSetFromOptions(coordsVec);CHKERRQ(ierr);
3614
3615 // For example: "position" is the name of the coordinate data
3616 ierr = ReadFieldData(user, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), coordsVec, "dat");
3617 if (ierr) {
3619 "Error reading position data (ti=%d).\n",
3620 timeIndex);
3621 PetscFunctionReturn(ierr);
3622 }
3623
3624 LOG_ALLOW(GLOBAL, LOG_DEBUG, "ReadPositions - Gathering coords Vec to rank 0.\n");
3625 ierr = VecToArrayOnRank0(coordsVec, Ncoords, coordsArray);CHKERRQ(ierr);
3626
3627 ierr = VecDestroy(&coordsVec);CHKERRQ(ierr);
3628
3630 "Successfully gathered coordinates. Ncoords=%d.\n", *Ncoords);
3631 PetscFunctionReturn(0);
3632}
3633
3634
3635/**
3636 * @brief Internal helper implementation: `ReadFieldDataToRank0()`.
3637 * @details Local to this translation unit.
3638 */
3639PetscErrorCode ReadFieldDataToRank0(PetscInt timeIndex,
3640 const char *fieldName,
3641 UserCtx *user,
3642 double **scalarArray,
3643 PetscInt *Nscalars)
3644{
3645 PetscFunctionBeginUser;
3646
3647 PetscErrorCode ierr;
3648 Vec fieldVec;
3649
3650 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Creating field Vec.\n");
3651 ierr = VecCreate(PETSC_COMM_WORLD, &fieldVec);CHKERRQ(ierr);
3652 ierr = VecSetFromOptions(fieldVec);CHKERRQ(ierr);
3653
3654 ierr = ReadFieldData(user, fieldName, fieldVec, "dat");
3655 if (ierr) {
3657 "Error reading field '%s' (ti=%d).\n",
3658 fieldName, timeIndex);
3659 PetscFunctionReturn(ierr);
3660 }
3661
3662 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Gathering field Vec to rank 0.\n");
3663 ierr = VecToArrayOnRank0(fieldVec, Nscalars, scalarArray);CHKERRQ(ierr);
3664
3665 ierr = VecDestroy(&fieldVec);CHKERRQ(ierr);
3666
3668 "Successfully gathered field '%s'. Nscalars=%d.\n",
3669 fieldName, *Nscalars);
3670 PetscFunctionReturn(0);
3671}
Small dependency-free SHA-256 utility for persistent metadata identity.
PetscErrorCode PicurvSHA256File(const char *path, char digest_hex[65])
Compute the lowercase SHA-256 digest of a file.
Definition checksum.c:130
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.
FieldLayout layout
@ FIELD_DIMENSION_FIXED
The exponents are the field's dimension.
PetscErrorCode FieldDimensionReferenceScale(const ScalingCtx *scaling, FieldDimension dimension, PetscReal *scale)
Return the factor that turns a solver value of one dimension into physical units.
@ FIELD_CAPABILITY_CHECKPOINT
@ FIELD_AVAILABILITY_WALL_MODEL
@ FIELD_AVAILABILITY_PARTICLES
@ FIELD_AVAILABILITY_TURBULENCE
@ FIELD_AVAILABILITY_LES_DYNAMIC
@ FIELD_AVAILABILITY_ALWAYS
unsigned int capabilities
unsigned int availability
PetscErrorCode FieldGetView(UserCtx *user, FieldId field_id, FieldView *view)
Resolve the existing DM and global/local vectors for one field.
PetscErrorCode FieldTryIdFromName(const char *field_name, FieldId *field_id, PetscBool *found)
Look a name up without treating an unknown name as an error.
#define FIELD_DIM_TIME
FieldDimension dimension
FieldDimensionKind kind
const char * canonical_name
const char * FieldLayoutName(FieldLayout layout)
Return a stable printable label for a field layout.
PetscErrorCode FieldDimensionLabel(FieldDimension dimension, char *label, size_t length)
Write a printable form of a dimension, such as L^2 U or dimensionless.
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_COORDINATES
@ FIELD_ID_INVALID
@ FIELD_ID_COUNT
Immutable metadata for one field identity.
Physical dimension as exponents of the reference length, velocity, and density.
Non-owning runtime objects resolved for one field and UserCtx.
PetscErrorCode ParsePostProcessingSettings(SimCtx *simCtx)
Internal helper implementation: ParsePostProcessingSettings().
Definition io.c:3240
PetscErrorCode ParseScalingInformation(SimCtx *simCtx)
Implementation of ParseScalingInformation().
Definition io.c:3425
PetscInt ReadDataFileToArray(const char *filename, double **data_out, PetscInt *Nout, MPI_Comm comm)
Implementation of ReadDataFileToArray().
Definition io.c:3469
PetscErrorCode ReadGridFile(UserCtx *user)
Internal helper implementation: ReadGridFile().
Definition io.c:589
static PetscErrorCode WriteCheckpointPayloadEntry(FILE *manifest, const char *checkpoint_directory, PetscInt payload_index, const char *relative_path, const char *kind, const char *field_name, PetscInt block, const char *layout, PetscInt components, const char *logical_type, PetscInt global_size, const char *encoding)
Append one payload inventory entry to a checkpoint manifest.
Definition io.c:2253
PetscErrorCode StringToBCHandlerType(const char *str, BCHandlerType *handler_out)
Internal helper implementation: StringToBCHandlerType().
Definition io.c:717
PetscErrorCode ReadSwarmField(UserCtx *user, const char *field_name, const char *ext)
Internal helper implementation: ReadSwarmField().
Definition io.c:1786
#define PICURV_CHECKPOINT_STEP_WIDTH
Definition io.c:27
PetscErrorCode GetBCParamReal(BC_Param *params, const char *key, PetscReal *value_out, PetscBool *found)
Internal helper implementation: GetBCParamReal().
Definition io.c:760
PetscErrorCode SwarmFieldToArrayOnRank0(DM swarm, const char *field_name, PetscInt *n_total_particles, PetscInt *n_components, PetscDataType *field_type_out, void **gathered_array)
Internal helper implementation: SwarmFieldToArrayOnRank0().
Definition io.c:2710
#define PICURV_CHECKPOINTS_DIRECTORY
Definition io.c:23
PetscErrorCode WriteAllSwarmFields(UserCtx *user, const char *checkpoint_directory)
Internal helper implementation: WriteAllSwarmFields().
Definition io.c:2218
static PetscErrorCode FormatStatisticsPath(const char *root, PetscInt window, PetscInt block, const char *payload_name, char *path, size_t path_size)
Format any level of the statistics subtree, from the root down to one payload.
Definition io.c:99
#define PICURV_PARTICLE_DIRECTORY
Definition io.c:25
static PetscInt * g_IMs_from_file
Caches the IM dimensions for all blocks read from the grid file.
Definition io.c:36
PetscErrorCode WriteSwarmIntField(UserCtx *user, const char *field_name, const char *ext)
Internal helper implementation: WriteSwarmIntField().
Definition io.c:2153
PetscErrorCode ReadCheckpointParticleCount(UserCtx *user, PetscInt ti, PetscInt *particle_count)
Implementation of ReadCheckpointParticleCount().
Definition io.c:1933
#define PICURV_STATISTICS_DIRECTORY
Definition io.c:26
PetscErrorCode ReadSimulationFields(UserCtx *user, PetscInt ti)
Internal helper implementation: ReadSimulationFields().
Definition io.c:1471
static PetscInt g_nblk_from_file
Stores the number of blocks read from the grid file.
Definition io.c:34
PetscBool ShouldWriteDataOutput(const SimCtx *simCtx, PetscInt completed_step)
Implementation of ShouldWriteDataOutput().
Definition io.c:426
PetscErrorCode PicurvFieldReferenceScale(SimCtx *simCtx, const char *field_name, PetscReal *scale, char *description, size_t description_length)
Implementation of PicurvFieldReferenceScale().
Definition io.c:3197
static PetscErrorCode RestoreDrivenFluxTarget(SimCtx *simCtx, UserCtx *user, const char *checkpoint_directory)
Restore a latched driven-flow flux target from a checkpoint manifest.
Definition io.c:1420
#define PICURV_CHECKPOINT_FORMAT
Definition io.c:21
static PetscErrorCode FormatCheckpointStepDirectory(const char *root, PetscInt step, char *path, size_t path_size)
Format the canonical directory name for one completed step.
Definition io.c:204
PetscErrorCode GetDrivenSeamFluxFlag(BC_Param *params, PetscBool *value_out, PetscBool *found)
Implementation of GetDrivenSeamFluxFlag().
Definition io.c:822
PetscErrorCode ParseAllBoundaryConditions(UserCtx *user, const char *bcs_input_filename)
Internal helper implementation: ParseAllBoundaryConditions().
Definition io.c:845
PetscErrorCode ValidateBCHandlerForBCType(BCType type, BCHandlerType handler)
Internal helper implementation: ValidateBCHandlerForBCType().
Definition io.c:735
void TrimWhitespace(char *str)
Implementation of TrimWhitespace().
Definition io.c:393
PetscErrorCode ReadAllSwarmFields(UserCtx *user, PetscInt ti)
Internal helper implementation: ReadAllSwarmFields().
Definition io.c:1883
PetscErrorCode WriteSimulationFields(UserCtx *user, const char *checkpoint_directory)
Implementation of WriteSimulationFields().
Definition io.c:2021
PetscBool CheckpointFieldIsEnabled(const SimCtx *simCtx, const FieldDescriptor *descriptor)
Implementation of CheckpointFieldIsEnabled().
Definition io.c:72
static PetscErrorCode CreateCheckpointDirectoryCollective(const SimCtx *simCtx, const char *path)
Create one directory on rank zero and report failures collectively.
Definition io.c:232
static PetscBool g_file_has_been_read
A flag to ensure the grid file is read only once.
Definition io.c:42
PetscErrorCode ReadGridGenerationInputs(UserCtx *user)
Internal helper implementation: ReadGridGenerationInputs().
Definition io.c:441
static PetscErrorCode ComputeCheckpointGeometrySHA256(SimCtx *simCtx, UserCtx *user, char digest_hex[65])
Compute and cache a rank-count-independent hash of the active grid geometry.
Definition io.c:155
PetscErrorCode ReadFieldData(UserCtx *user, const char *field_name, Vec field_vec, const char *ext)
Internal helper implementation: ReadFieldData().
Definition io.c:1182
static PetscErrorCode ValidateCheckpointBundle(SimCtx *simCtx, UserCtx *user, const char *checkpoint_directory, PetscInt expected_step, PetscReal *physical_time, PetscInt *particle_count, PetscBool *particles_saved, PetscBool *les_saved)
Validate a committed bundle and return selected authoritative metadata.
Definition io.c:252
PetscErrorCode PopulateFinestUserGridResolutionFromOptions(UserCtx *finest_users, PetscInt nblk)
Internal helper implementation: PopulateFinestUserGridResolutionFromOptions().
Definition io.c:540
PetscErrorCode DeterminePeriodicity(SimCtx *simCtx)
Internal helper implementation: DeterminePeriodicity().
Definition io.c:1032
static PetscErrorCode GatherVectorToRankZero(Vec field_vec, Vec *sequential_vec)
Gather a vector in decomposition-independent natural ordering onto rank zero.
Definition io.c:124
PetscErrorCode ReadFieldDataToRank0(PetscInt timeIndex, const char *fieldName, UserCtx *user, double **scalarArray, PetscInt *Nscalars)
Internal helper implementation: ReadFieldDataToRank0().
Definition io.c:3639
PetscErrorCode VecToArrayOnRank0(Vec inVec, PetscInt *N, double **arrayOut)
Internal helper implementation: VecToArrayOnRank0().
Definition io.c:2674
static PetscErrorCode CopyOwnedLocalScalarToGlobal(DM dm, Vec local_vec, Vec global_vec)
Copies the owned entries of a ghosted scalar DMDA vector to its global vector.
Definition io.c:51
PetscErrorCode RestoreFieldStatisticsState(SimCtx *simCtx, PetscInt ti)
Implementation of RestoreFieldStatisticsState().
Definition io.c:1686
static PetscInt * g_KMs_from_file
Caches the KM dimensions for all blocks read from the grid file.
Definition io.c:40
static PetscInt * g_JMs_from_file
Caches the JM dimensions for all blocks read from the grid file.
Definition io.c:38
static PetscErrorCode ReadStatisticsWindowState(PetscOptions options, PetscInt window, const char *metadata_path, PetscReal checkpoint_time, PetscReal step_size, ExecutionMode exec_mode, PicurvWindow *state)
Restore one window's scalar bookkeeping from a validated manifest.
Definition io.c:1552
PetscErrorCode StringToBCFace(const char *str, BCFace *face_out)
Internal helper implementation: StringToBCFace().
Definition io.c:687
PetscErrorCode ReadPositionsFromFile(PetscInt timeIndex, UserCtx *user, double **coordsArray, PetscInt *Ncoords)
Internal helper implementation: ReadPositionsFromFile().
Definition io.c:3601
PetscErrorCode WriteSwarmField(UserCtx *user, const char *field_name, const char *ext)
Implementation of WriteSwarmField().
Definition io.c:2098
PetscErrorCode ReadSwarmIntField(UserCtx *user, const char *field_name, const char *ext)
Internal helper implementation: ReadSwarmIntField().
Definition io.c:1816
static PetscErrorCode ResolveCheckpointStepDirectory(const char *source_root, PetscInt step, char *path, size_t path_size)
Resolve either an exact bundle or a run/output root to one step bundle.
Definition io.c:215
PetscErrorCode WriteFieldData(UserCtx *user, const char *field_name, Vec field_vec, const char *ext)
Internal helper implementation: WriteFieldData().
Definition io.c:1966
PetscErrorCode VerifyPathExistence(const char *path, PetscBool is_dir, PetscBool is_optional, const char *description, PetscBool *exists)
Internal helper implementation: VerifyPathExistence().
Definition io.c:1136
static PetscErrorCode WriteStatisticsFields(UserCtx *user, const char *checkpoint_directory)
Write every window's accumulator payloads for one block into a bundle.
Definition io.c:2056
PetscErrorCode GetBCParamBool(BC_Param *params, const char *key, PetscBool *value_out, PetscBool *found)
Internal helper implementation: GetBCParamBool().
Definition io.c:781
void FreeBC_ParamList(BC_Param *head)
Implementation of FreeBC_ParamList().
Definition io.c:672
PetscErrorCode StringToBCType(const char *str, BCType *type_out)
Internal helper implementation: StringToBCType().
Definition io.c:702
PetscErrorCode DisplayBanner(SimCtx *simCtx)
Emit the rank-zero startup summary from the effective simulation context.
Definition io.c:2800
#define PICURV_CHECKPOINT_VERSION
Definition io.c:22
PetscErrorCode WriteCheckpointBundle(SimCtx *simCtx, const char *reason)
Implementation of the transactional full-state checkpoint coordinator.
Definition io.c:2554
#define PICURV_EULERIAN_DIRECTORY
Definition io.c:24
#define PICURV_STATISTICS_REQUIRE(suffix, getter, target)
PetscErrorCode PicurvPhysicalTime(const SimCtx *simCtx, PetscReal solver_time, PetscReal *physical)
Implementation of PicurvPhysicalTime().
Definition io.c:3177
static PetscErrorCode WriteCheckpointManifest(SimCtx *simCtx, UserCtx *user, const char *checkpoint_directory, const char *reason, const char *geometry_digest, PetscInt particle_count, char manifest_digest[65])
Write the complete manifest after every payload has closed successfully.
Definition io.c:2304
Public interface for data input/output routines.
const char * LESTestFilterKernelToString(LESTestFilterKernel kernel)
Returns the user-facing name of an LES test-filter kernel.
Definition logging.c:782
const char * BCHandlerTypeToString(BCHandlerType handler_type)
Converts a BCHandlerType enum to its string representation.
Definition logging.c:889
#define LOG_ALLOW_SYNC(scope, level, fmt,...)
Synchronized logging macro that checks both the log level and whether the calling function is in the ...
Definition logging.h:253
#define LOCAL
Logging scope definitions for controlling message output.
Definition logging.h:45
#define GLOBAL
Scope for global logging across all processes.
Definition logging.h:46
const char * BCFaceToString(BCFace face)
Returns the canonical log token for a boundary-face enum value.
Definition logging.c:671
#define LOG_ALLOW(scope, level, fmt,...)
Logging macro that checks both the log level and whether the calling function is in the allowed-funct...
Definition logging.h:200
#define PROFILE_FUNCTION_END
Marks the end of a profiled code block.
Definition logging.h:894
#define LOG(scope, level, fmt,...)
Logging macro for PETSc-based applications with scope control.
Definition logging.h:84
const char * WallFunctionModelToString(WallFunctionModel model)
Returns the user-facing name of a wall-function model.
Definition logging.c:835
LogLevel get_log_level()
Retrieves the current logging level from the environment variable LOG_LEVEL.
Definition logging.c:87
const char * LESFilterWidthModelToString(LESFilterWidthModel model)
Returns the user-facing name of an LES grid-filter-width model.
Definition logging.c:761
const char * BCTypeToString(BCType type)
Returns the canonical log token for a boundary mathematical type.
Definition logging.c:869
const char * FlowDirectionToString(FlowDirection fd)
Convert a FlowDirection enum value to its YAML token string.
Definition logging.c:705
const char * LESAveragingModeToString(LESAveragingMode mode)
Returns the user-facing name of an LES coefficient-averaging mode.
Definition logging.c:799
const char * InitialConditionModeToString(InitialConditionMode mode)
Convert an initial-condition mode to a string representation.
Definition logging.c:689
@ LOG_ERROR
Critical errors that may halt the program.
Definition logging.h:29
@ LOG_TRACE
Very fine-grained tracing information for in-depth debugging.
Definition logging.h:33
@ LOG_INFO
Informational messages about program execution.
Definition logging.h:31
@ LOG_WARNING
Non-critical issues that warrant attention.
Definition logging.h:30
@ LOG_DEBUG
Detailed debugging information.
Definition logging.h:32
@ LOG_VERBOSE
Extremely detailed logs, typically for development use only.
Definition logging.h:34
#define PROFILE_FUNCTION_BEGIN
Marks the beginning of a profiled code block (typically a function).
Definition logging.h:885
const char * LESModelToString(LESModelType LESFlag)
Returns the canonical log token for an LES model selector.
Definition logging.c:741
const char * LESClipModeToString(LESClipMode mode)
Returns the user-facing name of an LES coefficient-limiting mode.
Definition logging.c:817
const char * MomentumSolverTypeToString(MomentumSolverType SolverFlag)
Returns the canonical log token for a momentum-solver selector.
Definition logging.c:853
const char * ParticleInitializationToString(ParticleInitializationType ParticleInitialization)
Returns the canonical log token for a particle-initialization mode.
Definition logging.c:724
Typed identities and metadata for persistent solver-particle fields.
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_POSITION
@ PARTICLE_FIELD_ID_COUNT
@ PARTICLE_FIELD_ID_INVALID
@ PARTICLE_FIELD_CAPABILITY_CHECKPOINT
PetscErrorCode ParticleFieldGetDescriptor(ParticleFieldId field_id, const ParticleFieldDescriptor **descriptor)
Return immutable metadata for a valid particle field ID.
PetscErrorCode ParticleFieldTryIdFromName(const char *field_name, ParticleFieldId *field_id, PetscBool *found)
Look a name up without treating an unknown name as an error.
Immutable metadata for one persistent particle field.
PetscErrorCode UpdateLocalGhosts(UserCtx *user, FieldId field_id)
Updates the local vector (including ghost points) from its corresponding global vector.
Definition setup.c:2489
Per-window PETSc accumulator storage and pointwise application.
PetscInt components
Degrees of freedom the vector carries.
Vec vec
Borrowed accumulator vector; never owned by the caller.
PetscErrorCode PicurvWindowStoragePayload(UserCtx *user, const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, PetscInt index, PicurvStatisticsPayload *payload)
Resolves one enumerated payload of a window's storage.
const char * role
Inventory role: occupancy, mean, second_moment, co_moment.
char name[96]
File basename, no extension.
const char * layout
Catalog layout name for the inventory entry.
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.
PetscInt last_event_step
Guards against a step being offered twice.
PetscErrorCode PicurvWindowComputeHash(const PicurvWindowDefinition *definition, char digest_hex[65], char group_digest_hex[][17])
Computes the resolved identity hash of one window definition.
#define PICURV_WINDOW_HASH_GROUP_COUNT
Number of independently hashed property groups in a window definition.
#define PICURV_WINDOW_HASH_GROUP_LENGTH
Stored length of one truncated group digest, including the terminator.
PetscReal effective_start
Origin of the first represented interval.
const char * PicurvWindowHashGroupName(PetscInt group)
Returns the stable name of one hashed property group.
PetscInt sample_count
PetscReal last_accepted_time
Right edge of the last represented interval.
PicurvWindowState state
PetscInt restart_count
Restart segments this state descends from.
PetscReal effective_end
End of the last represented interval.
PetscReal end_time
Requested end; ignored when bounded is false.
PetscReal total_weight
#define PICURV_WINDOW_NAME_LENGTH
Maximum stored length of a window name, including the terminator.
@ PICURV_WINDOW_PENDING
Requested start not yet reached.
@ PICURV_WINDOW_COMPLETE
Bounded end reached; accepts nothing further.
@ PICURV_WINDOW_ACTIVE
Accepting due states.
const char * PicurvWindowStateName(PicurvWindowState state)
Returns a stable human-readable name for a window state.
PetscBool bounded
False for an open-ended window.
PicurvWindowDefinition definition
PetscInt next_time_target
k in effective_start + k*time_cadence.
PetscBool FieldStatisticsIsActive(const struct SimCtx *simCtx)
Reports whether this run has live field-statistics state.
PetscInt activation_step
Step at which the window became active.
PetscReal represented_time
Physical time the window covers.
PetscErrorCode PicurvWindowFirstHashDifference(const PicurvWindowDefinition *definition, const char *saved_group_digests, PetscInt *group)
Reports which hashed property group first differs from saved group digests.
Runtime state of one window.
The scientifically immutable definition of one window.
@ DYNAMIC_SMAGORINSKY
Definition variables.h:551
@ VREMAN
Definition variables.h:552
@ NO_LES_MODEL
Definition variables.h:549
@ WALE
Definition variables.h:553
@ CONSTANT_SMAGORINSKY
Definition variables.h:550
PetscReal icVelocityPhysical
Definition variables.h:921
PetscBool mom_nk_monitor_history
Definition variables.h:914
PetscInt fieldStatisticsWindowCount
Definition variables.h:932
char statistics_output_prefix[256]
basename for CSV output, e.g.
Definition variables.h:777
BCType
Defines the general mathematical/physical Category of a boundary.
Definition variables.h:309
@ INLET
Definition variables.h:316
@ SYMMETRY
Definition variables.h:314
@ OUTLET
Definition variables.h:315
@ PERIODIC
Definition variables.h:318
@ WALL
Definition variables.h:312
PetscReal yoshizawa_ci
Yoshizawa constant for the reported SGS kinetic energy.
Definition variables.h:642
UserCtx * user
Definition variables.h:729
PetscBool inletFaceDefined
Definition variables.h:1100
PetscBool profilingFinalSummary
Definition variables.h:1036
char particle_output_prefix[256]
Definition variables.h:772
PetscMPIInt rank
Definition variables.h:862
PetscInt dynamic_frequency
Recompute the dynamic coefficient every N steps.
Definition variables.h:630
PetscInt cgrid
Definition variables.h:1094
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:1099
LESFilterWidthModel filter_width_model
How the grid filter width Delta is derived per cell.
Definition variables.h:634
PetscInt statisticsConsoleOutputFreq
Definition variables.h:934
PetscInt block_number
Definition variables.h:952
#define PICURV_BUILD_DIRTY
Definition variables.h:38
BCFace identifiedInletBCFace
Definition variables.h:1101
PetscReal targetVolumetricFlux
Definition variables.h:967
PetscBool walltimeGuardActive
Definition variables.h:1038
LESTestFilterKernel test_filter_kernel
Discrete test-filter stencil.
Definition variables.h:635
LESConfig les_config
Parameters of the LES closure selected by les.
Definition variables.h:987
PetscReal pseudo_cfl_reduction_factor
Definition variables.h:907
InitialConditionMode initialConditionMode
Definition variables.h:916
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:1077
@ PARTICLE_INIT_SURFACE_RANDOM
Random placement on the inlet face.
Definition variables.h:710
@ PARTICLE_INIT_SURFACE_EDGES
Deterministic placement at inlet face edges.
Definition variables.h:713
PetscReal StartTime
Definition variables.h:873
FlowDirection flowDirection
Definition variables.h:920
PetscBool runtimeMemoryLogEnabled
Enable the rank-reduced runtime memory log.
Definition variables.h:1051
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
char ** bcs_files
Definition variables.h:958
struct BC_Param_s * next
Definition variables.h:363
PetscReal Min_X
Definition variables.h:1089
PetscReal min_pseudo_cfl
Definition variables.h:908
char * key
Definition variables.h:361
PetscInt KM
Definition variables.h:1088
PetscInt tiout
Definition variables.h:871
char field_statistics_output_prefix[PETSC_MAX_PATH_LEN]
Analysis prefix for CSV summaries; VTK continues to use output_prefix.
Definition variables.h:787
UserMG usermg
Definition variables.h:1015
#define MAX_FIELD_LIST_LENGTH
Definition variables.h:745
PetscReal walltimeGuardMinSeconds
Definition variables.h:1041
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_INLET_PARABOLIC
Definition variables.h:335
@ BC_HANDLER_INLET_CONSTANT_VELOCITY
Definition variables.h:334
@ BC_HANDLER_PERIODIC_DRIVEN_INITIAL_FLUX
Definition variables.h:343
@ BC_HANDLER_PERIODIC_DRIVEN_CONSTANT_FLUX
Definition variables.h:342
@ BC_HANDLER_WALL_MOVING
Definition variables.h:332
@ BC_HANDLER_INLET_PROFILE_FROM_FILE
Definition variables.h:336
@ BC_HANDLER_WALL_NOSLIP
Definition variables.h:331
@ BC_HANDLER_OUTLET_CONSERVATION
Definition variables.h:338
#define MAX_PIPELINE_LENGTH
Definition variables.h:744
PetscReal ren
Definition variables.h:906
BCHandlerType handler_type
Definition variables.h:393
Cmpnts max_coords
Maximum x, y, z coordinates of the bounding box.
Definition variables.h:199
PetscBool drivenFluxTargetLatched
Definition variables.h:972
PetscInt _this
Definition variables.h:1092
char step_list_file[PETSC_MAX_PATH_LEN]
File listing the exact steps to process, one per line; empty uses the time controls.
Definition variables.h:763
PetscInt reference[3]
Definition variables.h:798
char output_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:881
PetscBool solutionConvergenceEnabled
Definition variables.h:923
PetscReal dt
Definition variables.h:874
PetscReal ry
Definition variables.h:1093
char runtimeMemoryLogFile[PETSC_MAX_PATH_LEN]
File name written under log_dir.
Definition variables.h:1052
@ LES_CLIP_CLAMP
Definition variables.h:618
PetscInt StepsToRun
Definition variables.h:870
char profilingTimestepMode[32]
Definition variables.h:1034
PetscInt k_periodic
Definition variables.h:953
WallFunctionModel
Selects the wall model applied on WALL faces.
Definition variables.h:565
@ WALL_FUNCTION_LOG_LAW
Definition variables.h:567
PetscInt timeStep
Definition variables.h:761
PetscInt np
Definition variables.h:990
PetscReal max_cs
Ceiling on Cs under LES_CLIP_CLAMP.
Definition variables.h:640
LESClipMode clip_mode
Admissible range for the coefficient.
Definition variables.h:639
PetscReal Max_Y
Definition variables.h:1089
PetscBool no_pseudo_cfl_backtrack
Definition variables.h:910
#define PICURV_GIT_COMMIT
Definition variables.h:35
char * value
Definition variables.h:362
#define MAX_FILENAME_LENGTH
Definition variables.h:746
PetscInt StartStep
Definition variables.h:869
Cmpnts min_coords
Minimum x, y, z coordinates of the bounding box.
Definition variables.h:198
PetscBool OnlySetup
Definition variables.h:875
@ MOMENTUM_SOLVER_DUALTIME_PICARD_JAMESON_RK
Definition variables.h:694
@ MOMENTUM_SOLVER_EXPLICIT_RK
Definition variables.h:693
@ MOMENTUM_SOLVER_NEWTON_KRYLOV
Definition variables.h:695
PetscInt solutionConvergencePeriodSteps
Definition variables.h:925
PetscScalar x
Definition variables.h:122
PetscBool averaging_direction[3]
Averaged-over logical directions (xi, eta, zeta).
Definition variables.h:638
PetscReal wale_coefficient
Model constant C_w for WALE (Nicoud & Ducros 1999).
Definition variables.h:633
char * current_io_directory
Definition variables.h:885
char grid_file[PETSC_MAX_PATH_LEN]
Definition variables.h:955
char statistics_pipeline[1024]
e.g.
Definition variables.h:776
InterpolationMethod interpolationMethod
Definition variables.h:995
char field_statistics_formats[1024]
Comma-separated formats: vtk for derived fields, csv for the convergence history.
Definition variables.h:785
PetscReal max_pseudo_cfl
Definition variables.h:908
char output_fields_instantaneous[1024]
Definition variables.h:768
char particleRestartMode[16]
Definition variables.h:996
char eulerianExt[8]
Definition variables.h:794
BoundingBox * bboxlist
Definition variables.h:993
struct PicurvWindow * fieldStatisticsWindows
Definition variables.h:933
Vec lNu_t
Definition variables.h:1156
PetscReal rz
Definition variables.h:1093
char source_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:756
PetscInt les_gradient_model
Add the Clark gradient (tensor-diffusivity) term to the viscous flux.
Definition variables.h:986
char particle_pipeline[1024]
Definition variables.h:770
BC_Param * params
Definition variables.h:394
char eulerianSource[PETSC_MAX_PATH_LEN]
Definition variables.h:879
PetscBool walltimeGuardEnabled
Definition variables.h:1037
PetscBool checkpointGeometryHashReady
Definition variables.h:887
PetscReal wall_roughness_height
Definition variables.h:947
PetscInt walltimeGuardWarmupSteps
Definition variables.h:1039
ParticleInitializationType ParticleInitialization
Definition variables.h:994
PetscScalar z
Definition variables.h:122
@ INTERP_TRILINEAR
Definition variables.h:723
ScalingCtx scaling
Definition variables.h:946
PetscInt diagnostics_cadence
Steps between diagnostic rows.
Definition variables.h:644
PetscReal min_viscosity_ratio
Enforce nu + nu_t >= ratio * nu.
Definition variables.h:641
PetscInt JM
Definition variables.h:1088
PetscInt wallfunction
Enable wall functions on WALL faces.
Definition variables.h:985
PetscInt mglevels
Definition variables.h:736
PetscReal Min_Z
Definition variables.h:1089
PetscBool diagnostics_enabled
Append per-step coefficient statistics to the run log directory.
Definition variables.h:643
PetscReal constant_cs
Fixed Cs for CONSTANT_SMAGORINSKY; unused by the dynamic model.
Definition variables.h:631
PetscInt solutionConvergenceWindowSteps
Definition variables.h:926
char process_pipeline[1024]
Definition variables.h:767
PetscInt particle_output_freq
Definition variables.h:773
PetscReal test_filter_width_ratio
Test-to-grid ratio per filtered direction; alpha is its square for the box, ratio^(4/3) for Simpson.
Definition variables.h:636
char particle_fields[1024]
Definition variables.h:771
PetscReal pseudo_cfl_growth_factor
Definition variables.h:907
PetscBool outputParticles
Definition variables.h:764
char initialConditionDirectory[PETSC_MAX_PATH_LEN]
Definition variables.h:918
LESAveragingMode averaging_mode
Averaging set for the Germano contractions.
Definition variables.h:637
struct PicurvWindowStorage * fieldStatisticsStorage
Definition variables.h:1136
char AnalyticalSolutionType[PETSC_MAX_PATH_LEN]
Definition variables.h:893
@ IC_MODE_CONSTANT_CARTESIAN
Definition variables.h:179
@ IC_MODE_POISEUILLE
Definition variables.h:180
@ IC_MODE_CONSTANT_STREAMWISE
Definition variables.h:181
@ IC_MODE_FILE
Definition variables.h:182
PetscReal Max_X
Definition variables.h:1089
PetscInt particleConsoleOutputFreq
Definition variables.h:872
Cmpnts InitialConstantContra
Definition variables.h:919
PetscReal Min_Y
Definition variables.h:1089
PetscInt i_periodic
Definition variables.h:953
char checkpointGeometrySHA256[65]
Definition variables.h:886
PetscInt step
Definition variables.h:867
PetscReal vreman_coefficient
Model constant c for VREMAN (Vreman 2004: 2.5 Cs^2).
Definition variables.h:632
PetscInt mom_max_pseudo_steps
Definition variables.h:900
PostProcessParams * pps
Definition variables.h:1058
PetscScalar y
Definition variables.h:122
@ IC_FIELD_UCAT
Definition variables.h:187
PetscMPIInt size
Definition variables.h:863
ExecutionMode
Defines the execution mode of the application.
Definition variables.h:831
@ EXEC_MODE_SOLVER
Definition variables.h:832
@ EXEC_MODE_POSTPROCESSOR
Definition variables.h:833
PetscInt IM
Definition variables.h:1088
char _io_context_buffer[PETSC_MAX_PATH_LEN]
Definition variables.h:884
PetscInt field_statistics_source_step
Committed step supplying the state; negative means the step being processed.
Definition variables.h:791
Cmpnts periodic_translation[3]
Definition variables.h:1095
char particleExt[8]
Definition variables.h:795
PetscReal walltimeGuardEstimatorAlpha
Definition variables.h:1042
PetscInt les
Active LES closure; an LESModelType value.
Definition variables.h:984
char field_statistics_windows[1024]
Comma-separated window names to derive; empty disables the pipeline.
Definition variables.h:781
char field_statistics_outputs[1024]
Comma-separated outputs: mean, reynolds_stress, rms, tke, flux.
Definition variables.h:783
MGCtx * mgctx
Definition variables.h:739
@ SOLUTION_CONVERGENCE_TRANSIENT
Definition variables.h:705
@ SOLUTION_CONVERGENCE_PERIODIC_DETERMINISTIC
Definition variables.h:703
@ SOLUTION_CONVERGENCE_STATISTICAL_STEADY
Definition variables.h:704
@ SOLUTION_CONVERGENCE_STEADY_DETERMINISTIC
Definition variables.h:702
PetscBool periodic_translation_valid[3]
Definition variables.h:1096
BCType mathematical_type
Definition variables.h:392
SolutionConvergenceMode solutionConvergenceMode
Definition variables.h:924
PetscReal P_ref
Definition variables.h:844
PetscReal rx
Definition variables.h:1093
InitialConditionField initialConditionField
Definition variables.h:917
ExecutionMode exec_mode
Definition variables.h:878
PetscInt startTime
Definition variables.h:759
PetscReal rho_ref
Definition variables.h:843
BoundingBox bbox
Definition variables.h:1090
PetscBool restartHistoryAvailable
Definition variables.h:888
PetscReal ti
Definition variables.h:868
PetscReal walltimeGuardMultiplier
Definition variables.h:1040
PetscReal Max_Z
Definition variables.h:1089
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
PetscReal pseudo_cfl
Definition variables.h:906
PetscInt LoggingFrequency
Definition variables.h:1020
#define PICURV_RELEASE_VERSION
Definition variables.h:32
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
@ BC_FACE_POS_Y
Definition variables.h:289
@ BC_FACE_NEG_Z
Definition variables.h:290
@ BC_FACE_POS_X
Definition variables.h:288
@ BC_FACE_NEG_Y
Definition variables.h:289
PetscInt j_periodic
Definition variables.h:953
BoundaryCondition * handler
Definition variables.h:395
A node in a linked list for storing key-value parameters from the bcs.dat file.
Definition variables.h:360
Holds the complete configuration for one of the six boundary faces.
Definition variables.h:390
Defines a 3D axis-aligned bounding box.
Definition variables.h:197
A 3D point or vector with PetscScalar components.
Definition variables.h:121
Every user-selectable parameter of the LES closure.
Definition variables.h:629
Holds all configuration parameters for a post-processing run.
Definition variables.h:754
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