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