PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
logging.c
Go to the documentation of this file.
1// logging.c
2#include "logging.h"
3#include "statistics_window.h"
8
9/* Maximum temporary buffer size for converting numbers to strings */
10#define TMP_BUF_SIZE 128
11
12// --------------------- Static Variable for Log Level ---------------------
13
14/**
15 * @brief Static variable to cache the current logging level.
16 *
17 * Initialized to -1 to indicate that the log level has not been set yet.
18 */
20
21// --------------------- Static Variables for Allow-List -------------------
22
23/**
24 * @brief Global/static array of function names allowed to log.
25 */
26static char** gAllowedFunctions = NULL;
27
28/**
29 * @brief Number of entries in the gAllowedFunctions array.
30 */
31static int gNumAllowed = 0;
32
33enum {
48};
49
50/**
51 * @brief Internal reduction callback for packed search metrics.
52 * @details Local to this translation unit.
53 */
54static void SearchMetricsReduceOp(void *invec, void *inoutvec, int *len, MPI_Datatype *datatype)
55{
56 PetscReal *in = (PetscReal *)invec;
57 PetscReal *inout = (PetscReal *)inoutvec;
58 (void)datatype;
59
60 for (int idx = 0; idx < *len; idx += SEARCH_METRIC_REDUCTION_LEN) {
74 inout[idx + SEARCH_METRIC_MAX_PASS_DEPTH] = PetscMax(inout[idx + SEARCH_METRIC_MAX_PASS_DEPTH],
76 }
77}
78
79// --------------------- Function Implementations ---------------------
80
81/**
82 * @brief Implementation of \ref get_log_level().
83 * @details Full API contract (arguments, ownership, side effects) is documented with
84 * the header declaration in `include/logging.h`.
85 * @see get_log_level()
86 */
88 if (current_log_level == -1) { // Log level not set yet
89 const char *env = getenv("LOG_LEVEL");
90 if (!env) {
91 current_log_level = LOG_ERROR; // Default level
92 }
93 else if (strcmp(env, "DEBUG") == 0) {
95 }
96 else if (strcmp(env, "INFO") == 0) {
98 }
99 else if (strcmp(env, "WARNING") == 0) {
101 }
102 else if (strcmp(env, "VERBOSE") == 0) {
104 }
105 else if (strcmp(env, "TRACE") == 0) {
107 }
108 else {
109 current_log_level = LOG_ERROR; // Default if unrecognized
110 }
111 }
112 return current_log_level;
113}
114
115/**
116 * @brief Internal helper implementation: `print_log_level()`.
117 * @details Local to this translation unit.
118 */
119PetscErrorCode print_log_level(void)
120{
121 PetscMPIInt rank;
122 PetscErrorCode ierr;
123 int level;
124 const char *level_name;
125
126 PetscFunctionBeginUser;
127 /* get MPI rank */
128 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRMPI(ierr);
129
130 /* decide level name */
131 level = get_log_level();
132 level_name = (level == LOG_ERROR) ? "ERROR" :
133 (level == LOG_WARNING) ? "WARNING" :
134 (level == LOG_INFO) ? "INFO" :
135 (level == LOG_DEBUG) ? "DEBUG" :
136 (level == LOG_VERBOSE) ? "VERBOSE" :
137 (level == LOG_TRACE) ? "TRACE" :
138 "UNKNOWN";
139
140 /* print it out */
141 ierr = PetscPrintf(PETSC_COMM_SELF,
142 "Current log level: %s (%d) | rank: %d\n",
143 level_name, level, (int)rank);
144 CHKERRMPI(ierr);
145
146 PetscFunctionReturn(PETSC_SUCCESS);
147}
148
149/**
150 * @brief Implementation of \ref set_allowed_functions().
151 * @details Full API contract (arguments, ownership, side effects) is documented with
152 * the header declaration in `include/logging.h`.
153 * @see set_allowed_functions()
154 */
155void set_allowed_functions(const char** functionList, int count)
156{
157 // 1. Free any existing entries
158 if (gAllowedFunctions) {
159 for (int i = 0; i < gNumAllowed; ++i) {
160 free(gAllowedFunctions[i]); // each was strdup'ed
161 }
162 free(gAllowedFunctions);
163 gAllowedFunctions = NULL;
164 gNumAllowed = 0;
165 }
166
167 // 2. Allocate new array
168 if (count > 0) {
169 gAllowedFunctions = (char**)malloc(sizeof(char*) * count);
170 }
171
172 // 3. Copy the new entries
173 for (int i = 0; i < count; ++i) {
174 // strdup is a POSIX function. If not available, implement your own string copy.
175 gAllowedFunctions[i] = strdup(functionList[i]);
176 }
177 gNumAllowed = count;
178}
179
180/**
181 * @brief Implementation of \ref is_function_allowed().
182 * @details Full API contract (arguments, ownership, side effects) is documented with
183 * the header declaration in `include/logging.h`.
184 * @see is_function_allowed()
185 */
186PetscBool is_function_allowed(const char* functionName)
187{
188 /* no list ⇒ allow all */
189 if (gNumAllowed == 0) {
190 return PETSC_TRUE;
191 }
192
193 /* otherwise only the listed functions are allowed */
194 for (int i = 0; i < gNumAllowed; ++i) {
195 if (strcmp(gAllowedFunctions[i], functionName) == 0) {
196 return PETSC_TRUE;
197 }
198 }
199 return PETSC_FALSE;
200}
201
202/**
203 * @brief Implementation of \ref LOG_CELL_VERTICES().
204 * @details Full API contract (arguments, ownership, side effects) is documented with
205 * the header declaration in `include/logging.h`.
206 * @see LOG_CELL_VERTICES()
207 */
208PetscErrorCode LOG_CELL_VERTICES(const Cell *cell, PetscMPIInt rank)
209{
210
211 // Validate input pointers
212 if (cell == NULL) {
213 LOG_ALLOW(LOCAL,LOG_ERROR, "'cell' is NULL.\n");
214 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "LOG_CELL_VERTICES - Input parameter 'cell' is NULL.");
215 }
216
217 LOG_ALLOW(LOCAL,LOG_VERBOSE, "Rank %d, Cell Vertices:\n", rank);
218 for(int i = 0; i < 8; i++){
219 LOG_ALLOW(LOCAL,LOG_VERBOSE, " Vertex[%d]: (%.2f, %.2f, %.2f)\n",
220 i, cell->vertices[i].x, cell->vertices[i].y, cell->vertices[i].z);
221 }
222
223 return 0; // Indicate successful execution
224}
225
226
227/**
228 * @brief Implementation of \ref LOG_FACE_DISTANCES().
229 * @details Full API contract (arguments, ownership, side effects) is documented with
230 * the header declaration in `include/logging.h`.
231 * @see LOG_FACE_DISTANCES()
232 */
233PetscErrorCode LOG_FACE_DISTANCES(PetscReal* d)
234{
235
236 // Validate input array
237 if (d == NULL) {
238 LOG_ALLOW(LOCAL,LOG_ERROR, " 'd' is NULL.\n");
239 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, " Input array 'd' is NULL.");
240 }
241
242 PetscPrintf(PETSC_COMM_SELF, " Face Distances:\n");
243 PetscPrintf(PETSC_COMM_SELF, " LEFT(%d): %.15f\n", LEFT, d[LEFT]);
244 PetscPrintf(PETSC_COMM_SELF, " RIGHT(%d): %.15f\n", RIGHT, d[RIGHT]);
245 PetscPrintf(PETSC_COMM_SELF, " BOTTOM(%d): %.15f\n", BOTTOM, d[BOTTOM]);
246 PetscPrintf(PETSC_COMM_SELF, " TOP(%d): %.15f\n", TOP, d[TOP]);
247 PetscPrintf(PETSC_COMM_SELF, " FRONT(%d): %.15f\n", FRONT, d[FRONT]);
248 PetscPrintf(PETSC_COMM_SELF, " BACK(%d): %.15f\n", BACK, d[BACK]);
249
250 return 0; // Indicate successful execution
251}
252
253/*
254 * Helper function: Converts an integer (of type int) to a string.
255 */
256static void IntToStr(int value, char *buf, size_t bufsize)
257{
258 snprintf(buf, bufsize, "%d", value);
259}
260
261/*
262 * Helper function: Converts a 64‐bit integer to a string.
263 */
264static void Int64ToStr(PetscInt64 value, char *buf, size_t bufsize)
265{
266 snprintf(buf, bufsize, "%ld", value);
267}
268
269/*
270 * Helper function: Converts three integers into a formatted string "(i, j, k)".
271 */
272static void CellToStr(const PetscInt *cell, char *buf, size_t bufsize)
273{
274 snprintf(buf, bufsize, "(%d, %d, %d)", cell[0], cell[1], cell[2]);
275}
276
277/*
278 * Helper function: Converts three PetscReal values into a formatted string "(x, y, z)".
279 */
280static void TripleRealToStr(const PetscReal *arr, char *buf, size_t bufsize)
281{
282 snprintf(buf, bufsize, "(%.4f, %.4f, %.4f)", arr[0], arr[1], arr[2]);
283}
284
285/*
286 * Helper function: Computes the maximum string length for each column (across all particles).
287 *
288 * The function examines every particle (from 0 to nParticles-1) and converts the value to a
289 * string using the helper functions above. The maximum length is stored in the pointers provided.
290 *
291 * @param nParticles Number of particles.
292 * @param ranks Array of particle MPI ranks.
293 * @param pids Array of particle IDs.
294 * @param cellIDs Array of cell IDs (stored consecutively, 3 per particle).
295 * @param positions Array of positions (3 per particle).
296 * @param velocities Array of velocities (3 per particle).
297 * @param weights Array of weights (3 per particle).
298 * @param wRank [out] Maximum width for Rank column.
299 * @param wPID [out] Maximum width for PID column.
300 * @param wCell [out] Maximum width for Cell column.
301 * @param wPos [out] Maximum width for Position column.
302 * @param wVel [out] Maximum width for Velocity column.
303 * @param wWt [out] Maximum width for Weights column.
304 */
305static PetscErrorCode ComputeMaxColumnWidths(PetscInt nParticles,
306 const PetscMPIInt *ranks,
307 const PetscInt64 *pids,
308 const PetscInt *cellIDs,
309 const PetscReal *positions,
310 const PetscReal *velocities,
311 const PetscReal *weights,
312 int *wRank, int *wPID, int *wCell,
313 int *wPos, int *wVel, int *wWt)
314{
315 char tmp[TMP_BUF_SIZE];
316
317 *wRank = strlen("Rank"); /* Start with the header label lengths */
318 *wPID = strlen("PID");
319 *wCell = strlen("Cell (i,j,k)");
320 *wPos = strlen("Position (x,y,z)");
321 *wVel = strlen("Velocity (x,y,z)");
322 *wWt = strlen("Weights (a1,a2,a3)");
323
324 for (PetscInt i = 0; i < nParticles; i++) {
325 /* Rank */
326 IntToStr(ranks[i], tmp, TMP_BUF_SIZE);
327 if ((int)strlen(tmp) > *wRank) *wRank = (int)strlen(tmp);
328
329 /* PID */
330 Int64ToStr(pids[i], tmp, TMP_BUF_SIZE);
331 if ((int)strlen(tmp) > *wPID) *wPID = (int)strlen(tmp);
332
333 /* Cell: use the three consecutive values */
334 CellToStr(&cellIDs[3 * i], tmp, TMP_BUF_SIZE);
335 if ((int)strlen(tmp) > *wCell) *wCell = (int)strlen(tmp);
336
337 /* Position */
338 TripleRealToStr(&positions[3 * i], tmp, TMP_BUF_SIZE);
339 if ((int)strlen(tmp) > *wPos) *wPos = (int)strlen(tmp);
340
341 /* Velocity */
342 TripleRealToStr(&velocities[3 * i], tmp, TMP_BUF_SIZE);
343 if ((int)strlen(tmp) > *wVel) *wVel = (int)strlen(tmp);
344
345 /* Weights */
346 TripleRealToStr(&weights[3 * i], tmp, TMP_BUF_SIZE);
347 if ((int)strlen(tmp) > *wWt) *wWt = (int)strlen(tmp);
348 }
349 return 0;
350}
351
352/*
353 * Helper function: Builds a format string for a table row.
354 *
355 * The format string will include proper width specifiers for each column.
356 * For example, it might create something like:
357 *
358 * "| %-6s | %-8s | %-20s | %-25s | %-25s | %-25s |\n"
359 *
360 * @param wRank Maximum width for the Rank column.
361 * @param wPID Maximum width for the PID column.
362 * @param wCell Maximum width for the Cell column.
363 * @param wPos Maximum width for the Position column.
364 * @param wVel Maximum width for the Velocity column.
365 * @param wWt Maximum width for the Weights column.
366 * @param fmtStr Buffer in which to build the format string.
367 * @param bufSize Size of fmtStr.
368 */
369static void BuildRowFormatString(PetscMPIInt wRank, PetscInt wPID, PetscInt wCell, PetscInt wPos, PetscInt wVel, PetscInt wWt, char *fmtStr, size_t bufSize)
370{
371 // Build a format string using snprintf.
372 // We assume that the Rank is an int (%d), PID is a 64-bit int (%ld)
373 // and the remaining columns are strings (which have been formatted already).
374 snprintf(fmtStr, bufSize,
375 "| %%-%dd | %%-%dd | %%-%ds | %%-%ds | %%-%ds | %%-%ds |\n",
376 wRank, wPID, wCell, wPos, wVel, wWt);
377}
378
379/*
380 * Helper function: Builds a header string for the table using column titles.
381 */
382static void BuildHeaderString(char *headerStr, size_t bufSize, PetscMPIInt wRank, PetscInt wPID, PetscInt wCell, PetscInt wPos, PetscInt wVel, PetscInt wWt)
383{
384 snprintf(headerStr, bufSize,
385 "| %-*s | %-*s | %-*s | %-*s | %-*s | %-*s |\n",
386 (int)wRank, "Rank",
387 (int)wPID, "PID",
388 (int)wCell, "Cell (i,j,k)",
389 (int)wPos, "Position (x,y,z)",
390 (int)wVel, "Velocity (x,y,z)",
391 (int)wWt, "Weights (a1,a2,a3)");
392}
393
394/**
395 * @brief Implementation of \ref LOG_PARTICLE_FIELDS().
396 * @details Full API contract (arguments, ownership, side effects) is documented with
397 * the header declaration in `include/logging.h`.
398 * @see LOG_PARTICLE_FIELDS()
399 */
400PetscErrorCode LOG_PARTICLE_FIELDS(UserCtx* user, PetscInt printInterval)
401{
402 DM swarm = user->swarm;
403 PetscErrorCode ierr;
404 PetscInt localNumParticles;
405 PetscReal *positions = NULL;
406 PetscInt64 *particleIDs = NULL;
407 PetscMPIInt *particleRanks = NULL;
408 PetscInt *cellIDs = NULL;
409 PetscReal *weights = NULL;
410 PetscReal *velocities = NULL;
411 PetscMPIInt rank;
412
413 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
414 LOG_ALLOW(LOCAL,LOG_INFO, "Rank %d is retrieving particle data.\n", rank);
415
416 ierr = DMSwarmGetLocalSize(swarm, &localNumParticles); CHKERRQ(ierr);
417 LOG_ALLOW(LOCAL,LOG_DEBUG,"Rank %d has %d particles.\n", rank, localNumParticles);
418
419 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&positions); CHKERRQ(ierr);
420 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&particleIDs); CHKERRQ(ierr);
421 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_RANK), NULL, NULL, (void**)&particleRanks); CHKERRQ(ierr);
422 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cellIDs); CHKERRQ(ierr);
423 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_WEIGHT), NULL, NULL, (void**)&weights); CHKERRQ(ierr);
424 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_VELOCITY), NULL, NULL, (void**)&velocities); CHKERRQ(ierr);
425
426 /* Compute maximum column widths. */
427 int wRank, wPID, wCell, wPos, wVel, wWt;
428 wRank = wPID = wCell = wPos = wVel = wWt = 0;
429 ierr = ComputeMaxColumnWidths(localNumParticles, particleRanks, particleIDs, cellIDs,
430 positions, velocities, weights,
431 &wRank, &wPID, &wCell, &wPos, &wVel, &wWt); CHKERRQ(ierr);
432
433 /* Build a header string and a row format string. */
434 char headerFmt[256];
435 char rowFmt[256];
436 BuildHeaderString(headerFmt, sizeof(headerFmt), wRank, wPID, wCell, wPos, wVel, wWt);
437 BuildRowFormatString(wRank, wPID, wCell, wPos, wVel, wWt, rowFmt, sizeof(rowFmt));
438
439 /* Print header (using synchronized printing for parallel output). */
440 ierr = PetscSynchronizedPrintf(PETSC_COMM_WORLD, "--------------------------------------------------------------------------------------------------------------\n"); CHKERRQ(ierr);
441 ierr = PetscSynchronizedPrintf(PETSC_COMM_WORLD, "%s", headerFmt); CHKERRQ(ierr);
442 ierr = PetscSynchronizedPrintf(PETSC_COMM_WORLD, "--------------------------------------------------------------------------------------------------------------\n"); CHKERRQ(ierr);
443
444 /* Loop over particles and print every printInterval-th row. */
445 char rowStr[256];
446 for (PetscInt i = 0; i < localNumParticles; i++) {
447 if (i % printInterval == 0) {
448 // ------- DEBUG
449 //char cellStr[TMP_BUF_SIZE], posStr[TMP_BUF_SIZE], velStr[TMP_BUF_SIZE], wtStr[TMP_BUF_SIZE];
450 //CellToStr(&cellIDs[3*i], cellStr, TMP_BUF_SIZE);
451 //TripleRealToStr(&positions[3*i], posStr, TMP_BUF_SIZE);
452 //TripleRealToStr(&velocities[3*i], velStr, TMP_BUF_SIZE);
453 // TripleRealToStr(&weights[3*i], wtStr, TMP_BUF_SIZE);
454
455 // if (rank == 0) { // Or whatever rank is Rank 0
456 //PetscPrintf(PETSC_COMM_SELF, "[Rank 0 DEBUG LPF] Particle %lld: PID=%lld, Rank=%d\n", (long long)i, (long long)particleIDs[i], particleRanks[i]);
457 //PetscPrintf(PETSC_COMM_SELF, "[Rank 0 DEBUG LPF] Raw Pos: (%.10e, %.10e, %.10e)\n", positions[3*i+0], positions[3*i+1], positions[3*i+2]);
458 //PetscPrintf(PETSC_COMM_SELF, "[Rank 0 DEBUG LPF] Str Pos: %s\n", posStr);
459 //PetscPrintf(PETSC_COMM_SELF, "[Rank 0 DEBUG LPF] Raw Vel: (%.10e, %.10e, %.10e)\n", velocities[3*i+0], velocities[3*i+1], velocities[3*i+2]);
460 // PetscPrintf(PETSC_COMM_SELF, "[Rank 0 DEBUG LPF] Str Vel: %s\n", velStr);
461 // Add similar for cell, weights
462 // PetscPrintf(PETSC_COMM_SELF, "[Rank 0 DEBUG LPF] About to build rowStr for particle %lld\n", (long long)i);
463 // fflush(stdout);
464 // }
465
466 // snprintf(rowStr, sizeof(rowStr), rowFmt,
467 // particleRanks[i],
468 // particleIDs[i],
469 // cellStr,
470 // posStr,
471 // velStr,
472 // wtStr);
473
474
475 // ierr = PetscSynchronizedPrintf(PETSC_COMM_WORLD, "%s", rowStr); CHKERRQ(ierr);
476
477 // ierr = PetscSynchronizedPrintf(PETSC_COMM_WORLD, "%s", rowStr); CHKERRQ(ierr);
478
479 // -------- DEBUG
480 /* Format the row by converting each field to a string first.
481 * We use temporary buffers and then build the row string.
482 */
483
484 char cellStr[TMP_BUF_SIZE], posStr[TMP_BUF_SIZE], velStr[TMP_BUF_SIZE], wtStr[TMP_BUF_SIZE];
485 CellToStr(&cellIDs[3*i], cellStr, TMP_BUF_SIZE);
486 TripleRealToStr(&positions[3*i], posStr, TMP_BUF_SIZE);
487 TripleRealToStr(&velocities[3*i], velStr, TMP_BUF_SIZE);
488 TripleRealToStr(&weights[3*i], wtStr, TMP_BUF_SIZE);
489
490 /* Build the row string. Note that for the integer fields we can use the row format string. */
491 snprintf(rowStr, sizeof(rowStr), rowFmt,
492 particleRanks[i],
493 particleIDs[i],
494 cellStr,
495 posStr,
496 velStr,
497 wtStr);
498 ierr = PetscSynchronizedPrintf(PETSC_COMM_WORLD, "%s", rowStr); CHKERRQ(ierr);
499 }
500 }
501
502
503 ierr = PetscSynchronizedPrintf(PETSC_COMM_WORLD, "--------------------------------------------------------------------------------------------------------------\n"); CHKERRQ(ierr);
504 ierr = PetscSynchronizedPrintf(PETSC_COMM_WORLD, "\n"); CHKERRQ(ierr);
505 ierr = PetscSynchronizedFlush(PETSC_COMM_WORLD, PETSC_STDOUT); CHKERRQ(ierr);
506
507 LOG_ALLOW_SYNC(GLOBAL,LOG_DEBUG,"Completed printing on Rank %d.\n", rank);
508
509 /* Restore fields */
510 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&positions); CHKERRQ(ierr);
511 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&particleIDs); CHKERRQ(ierr);
512 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_RANK), NULL, NULL, (void**)&particleRanks); CHKERRQ(ierr);
513 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cellIDs); CHKERRQ(ierr);
514 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_WEIGHT), NULL, NULL, (void**)&weights); CHKERRQ(ierr);
515 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_VELOCITY), NULL, NULL, (void**)&velocities); CHKERRQ(ierr);
516
517 LOG_ALLOW(LOCAL,LOG_DEBUG, "Restored all particle fields.\n");
518 return 0;
519}
520
521/**
522 * @brief Implementation of \ref IsParticleConsoleSnapshotEnabled().
523 * @details Full API contract (arguments, ownership, side effects) is documented with
524 * the header declaration in `include/logging.h`.
525 * @see IsParticleConsoleSnapshotEnabled()
526 */
527
529{
530 if (!simCtx) {
531 return PETSC_FALSE;
532 }
533 return (PetscBool)(simCtx->np > 0 &&
534 simCtx->particleConsoleOutputFreq > 0 &&
536}
537
538/**
539 * @brief Implementation of \ref ShouldEmitPeriodicParticleConsoleSnapshot().
540 * @details Full API contract (arguments, ownership, side effects) is documented with
541 * the header declaration in `include/logging.h`.
542 * @see ShouldEmitPeriodicParticleConsoleSnapshot()
543 */
544
545PetscBool ShouldEmitPeriodicParticleConsoleSnapshot(const SimCtx *simCtx, PetscInt completed_step)
546{
547 return (PetscBool)(IsParticleConsoleSnapshotEnabled(simCtx) &&
548 completed_step > 0 &&
549 completed_step % simCtx->particleConsoleOutputFreq == 0);
550}
551
552/**
553 * @brief Implementation of \ref EmitParticleConsoleSnapshot().
554 * @details Full API contract (arguments, ownership, side effects) is documented with
555 * the header declaration in `include/logging.h`.
556 * @see EmitParticleConsoleSnapshot()
557 */
558
559PetscErrorCode EmitParticleConsoleSnapshot(UserCtx *user, SimCtx *simCtx, PetscInt step)
560{
561 PetscErrorCode ierr;
562
563 PetscFunctionBeginUser;
564 LOG(GLOBAL, LOG_INFO, "Particle states at step %d:\n", step);
565 ierr = LOG_PARTICLE_FIELDS(user, simCtx->LoggingFrequency); CHKERRQ(ierr);
566 PetscFunctionReturn(0);
567}
568
569
570/**
571 * @brief Remove leading and trailing whitespace from a mutable configuration string.
572 */
573static void trim(char *s)
574{
575 if (!s) return;
576
577 /* ---- 1. strip leading blanks ----------------------------------- */
578 char *p = s;
579 while (*p && isspace((unsigned char)*p))
580 ++p;
581
582 if (p != s) /* move the trimmed text forward */
583 memmove(s, p, strlen(p) + 1); /* +1 to copy the final NUL */
584
585 /* ---- 2. strip trailing blanks ---------------------------------- */
586 size_t len = strlen(s);
587 while (len > 0 && isspace((unsigned char)s[len - 1]))
588 s[--len] = '\0';
589}
590
591/* ------------------------------------------------------------------------- */
592/**
593 * @brief Implementation of \ref LoadAllowedFunctionsFromFile().
594 * @details Full API contract (arguments, ownership, side effects) is documented with
595 * the header declaration in `include/logging.h`.
596 * @see LoadAllowedFunctionsFromFile()
597 */
598PetscErrorCode LoadAllowedFunctionsFromFile(const char filename[],
599 char ***funcsOut,
600 PetscInt *nOut)
601{
602 FILE *fp = NULL;
603 char **funcs = NULL;
604 size_t cap = 16; /* initial capacity */
605 size_t n = 0; /* number of names */
606 char line[PETSC_MAX_PATH_LEN];
607 PetscErrorCode ierr;
608
609 PetscFunctionBegin;
610
611 /* ---------------------------------------------------------------------- */
612 /* 1. Open file */
613 fp = fopen(filename, "r");
614 if (!fp) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN,
615 "Cannot open %s", filename);
616
617 /* 2. Allocate initial pointer array */
618 ierr = PetscMalloc1(cap, &funcs); CHKERRQ(ierr);
619
620 /* 3. Read file line by line */
621 while (fgets(line, sizeof line, fp)) {
622 /* Strip everything after a comment character '#'. */
623 char *hash = strchr(line, '#');
624 if (hash) *hash = '\0';
625
626 trim(line); /* remove leading/trailing blanks */
627 if (!*line) continue; /* skip if empty */
628
629 /* Grow the array if necessary */
630 if (n == cap) {
631 cap *= 2;
632 ierr = PetscRealloc(cap * sizeof(*funcs), (void **)&funcs); CHKERRQ(ierr);
633 }
634
635 /* Deep‑copy the cleaned identifier */
636 ierr = PetscStrallocpy(line, &funcs[n++]); CHKERRQ(ierr);
637 }
638 fclose(fp);
639
640 /* 4. Return results to caller */
641 *funcsOut = funcs;
642 *nOut = (PetscInt)n;
643
644 PetscFunctionReturn(0);
645}
646
647/* ------------------------------------------------------------------------- */
648/**
649 * @brief Internal helper implementation: `FreeAllowedFunctions()`.
650 * @details Local to this translation unit.
651 */
652PetscErrorCode FreeAllowedFunctions(char **funcs, PetscInt n)
653{
654 PetscErrorCode ierr;
655 PetscFunctionBegin;
656 if (funcs) {
657 for (PetscInt i = 0; i < n; ++i) {
658 ierr = PetscFree(funcs[i]); CHKERRQ(ierr);
659 }
660 ierr = PetscFree(funcs); CHKERRQ(ierr);
661 }
662 PetscFunctionReturn(0);
663}
664
665/**
666 * @brief Implementation of \ref BCFaceToString().
667 * @details Full API contract (arguments, ownership, side effects) is documented with
668 * the header declaration in `include/logging.h`.
669 * @see BCFaceToString()
670 */
671const char* BCFaceToString(BCFace face) {
672 switch (face) {
673 case BC_FACE_NEG_X: return "-Xi (I-Min)";
674 case BC_FACE_POS_X: return "+Xi (I-Max)";
675 case BC_FACE_NEG_Y: return "-Eta (J-Min)";
676 case BC_FACE_POS_Y: return "+Eta (J-Max)";
677 case BC_FACE_NEG_Z: return "-Zeta (K-Min)";
678 case BC_FACE_POS_Z: return "+Zeta (K-Max)";
679 default: return "Unknown Face";
680 }
681}
682
683/**
684 * @brief Implementation of \ref InitialConditionModeToString().
685 * @details Full API contract (arguments, ownership, side effects) is documented with
686 * the header declaration in `include/logging.h`.
687 * @see InitialConditionModeToString()
688 */
690{
691 switch(mode){
692 case IC_MODE_ZERO: return "Zero";
693 case IC_MODE_CONSTANT_CARTESIAN: return "Cartesian Constant";
694 case IC_MODE_POISEUILLE: return "Poiseuille";
695 case IC_MODE_CONSTANT_STREAMWISE: return "Streamwise Constant";
696 case IC_MODE_FILE: return "File";
697 default: return "Unknown Initial Condition";
698 }
699}
700
701/*
702 * Converts a FlowDirection enum value to its token string. The public header
703 * owns the rendered API contract.
704 */
706{
707 switch ((int)fd) {
708 case FLOW_DIR_POS_XI: return "+Xi";
709 case FLOW_DIR_NEG_XI: return "-Xi";
710 case FLOW_DIR_POS_ETA: return "+Eta";
711 case FLOW_DIR_NEG_ETA: return "-Eta";
712 case FLOW_DIR_POS_ZETA: return "+Zeta";
713 case FLOW_DIR_NEG_ZETA: return "-Zeta";
714 default: return "from INLET";
715 }
716}
717
718/**
719 * @brief Implementation of \ref ParticleInitializationToString().
720 * @details Full API contract (arguments, ownership, side effects) is documented with
721 * the header declaration in `include/logging.h`.
722 * @see ParticleInitializationToString()
723 */
725{
726 switch(ParticleInitialization){
727 case PARTICLE_INIT_SURFACE_RANDOM: return "Surface: Random";
728 case PARTICLE_INIT_VOLUME: return "Volume";
729 case PARTICLE_INIT_POINT_SOURCE: return "Point Source";
730 case PARTICLE_INIT_SURFACE_EDGES: return "Surface: At edges";
731 default: return "Unknown Particle Initialization";
732 }
733}
734
735/**
736 * @brief Implementation of \ref LESModelToString().
737 * @details Full API contract (arguments, ownership, side effects) is documented with
738 * the header declaration in `include/logging.h`.
739 * @see LESModelToString()
740 */
741const char* LESModelToString(LESModelType LESFlag)
742{
743 switch(LESFlag){
744 case NO_LES_MODEL: return "No LES";
745 case CONSTANT_SMAGORINSKY: return "Constant Smagorinsky";
746 case DYNAMIC_SMAGORINSKY: return "Dynamic Smagorinsky";
747 case VREMAN: return "Vreman";
748 case WALE: return "WALE";
749 default: return "Unknown LES Flag";
750 }
751}
752
753#undef __FUNCT__
754#define __FUNCT__ "LESFilterWidthModelToString"
755/**
756 * @brief Implementation of \ref LESFilterWidthModelToString().
757 * @details Full API contract is documented with the header declaration in
758 * `include/logging.h`.
759 * @see LESFilterWidthModelToString()
760 */
762{
763 /* These spellings are the user-facing YAML values, so a banner line can be pasted
764 back into a case file without translation. */
765 switch(model){
766 case LES_FILTER_WIDTH_CUBE_ROOT_VOLUME: return "cube_root_volume";
767 case LES_FILTER_WIDTH_GEOMETRIC_MEAN: return "geometric_mean";
768 case LES_FILTER_WIDTH_MAX_EDGE: return "max_edge";
769 case LES_FILTER_WIDTH_SCOTTI: return "scotti";
770 default: return "unknown";
771 }
772}
773
774#undef __FUNCT__
775#define __FUNCT__ "LESTestFilterKernelToString"
776/**
777 * @brief Implementation of \ref LESTestFilterKernelToString().
778 * @details Full API contract is documented with the header declaration in
779 * `include/logging.h`.
780 * @see LESTestFilterKernelToString()
781 */
783{
784 switch(kernel){
785 case LES_TEST_FILTER_VOLUME_WEIGHTED_BOX: return "volume_weighted_box";
786 case LES_TEST_FILTER_SIMPSON_IK: return "simpson_ik";
787 default: return "unknown";
788 }
789}
790
791#undef __FUNCT__
792#define __FUNCT__ "LESAveragingModeToString"
793/**
794 * @brief Implementation of \ref LESAveragingModeToString().
795 * @details Full API contract is documented with the header declaration in
796 * `include/logging.h`.
797 * @see LESAveragingModeToString()
798 */
800{
801 switch(mode){
802 case LES_AVERAGING_LOCAL: return "local";
803 case LES_AVERAGING_HOMOGENEOUS: return "homogeneous";
804 case LES_AVERAGING_GLOBAL: return "global";
805 default: return "unknown";
806 }
807}
808
809#undef __FUNCT__
810#define __FUNCT__ "LESClipModeToString"
811/**
812 * @brief Implementation of \ref LESClipModeToString().
813 * @details Full API contract is documented with the header declaration in
814 * `include/logging.h`.
815 * @see LESClipModeToString()
816 */
818{
819 switch(mode){
820 case LES_CLIP_CLAMP: return "clamp";
821 case LES_CLIP_CLIP_NEGATIVE: return "clip_negative";
822 case LES_CLIP_NONE: return "none";
823 default: return "unknown";
824 }
825}
826
827#undef __FUNCT__
828#define __FUNCT__ "WallFunctionModelToString"
829/**
830 * @brief Implementation of \ref WallFunctionModelToString().
831 * @details Full API contract is documented with the header declaration in
832 * `include/logging.h`.
833 * @see WallFunctionModelToString()
834 */
836{
837 /* User-facing YAML spellings, so a banner line can be pasted back into a case. */
838 switch(model){
839 case WALL_FUNCTION_NONE: return "none";
840 case WALL_FUNCTION_LOG_LAW: return "log_law";
841 case WALL_FUNCTION_WERNER: return "werner";
842 case WALL_FUNCTION_CABOT: return "cabot";
843 default: return "unknown";
844 }
845}
846
847/**
848 * @brief Implementation of \ref MomentumSolverTypeToString().
849 * @details Full API contract (arguments, ownership, side effects) is documented with
850 * the header declaration in `include/logging.h`.
851 * @see MomentumSolverTypeToString()
852 */
854{
855 switch(SolverFlag){
856 case MOMENTUM_SOLVER_EXPLICIT_RK: return "Explicit 4 stage Runge-Kutta ";
857 case MOMENTUM_SOLVER_DUALTIME_PICARD_JAMESON_RK: return "Dual Time Picard with 4-stage Jameson RK Smoothing";
858 case MOMENTUM_SOLVER_NEWTON_KRYLOV: return "Newton Krylov";
859 default: return "Unknown Momentum Solver Type";
860 }
861}
862
863/**
864 * @brief Implementation of \ref BCTypeToString().
865 * @details Full API contract (arguments, ownership, side effects) is documented with
866 * the header declaration in `include/logging.h`.
867 * @see BCTypeToString()
868 */
869const char* BCTypeToString(BCType type) {
870 switch (type) {
871 // case DIRICHLET: return "DIRICHLET";
872 // case NEUMANN: return "NEUMANN";
873 case WALL: return "WALL";
874 case INLET: return "INLET";
875 case OUTLET: return "OUTLET";
876 case FARFIELD: return "FARFIELD";
877 case PERIODIC: return "PERIODIC";
878 case INTERFACE: return "INTERFACE";
879
880 // case CUSTOM: return "CUSTOM";
881 default: return "Unknown BC Type";
882 }
883}
884
885/**
886 * @brief Internal helper implementation: `BCHandlerTypeToString()`.
887 * @details Local to this translation unit.
888 */
889const char* BCHandlerTypeToString(BCHandlerType handler_type) {
890 switch (handler_type) {
891 // Wall & Symmetry Handlers
892 case BC_HANDLER_WALL_NOSLIP: return "noslip";
893 case BC_HANDLER_WALL_MOVING: return "moving";
894 case BC_HANDLER_SYMMETRY_PLANE: return "symmetry_plane";
895
896 // Inlet Handlers
897 case BC_HANDLER_INLET_CONSTANT_VELOCITY: return "constant_velocity";
898 case BC_HANDLER_INLET_PARABOLIC: return "parabolic";
899 case BC_HANDLER_INLET_PROFILE_FROM_FILE: return "prescribed_flow";
900
901 // Outlet Handlers
902 case BC_HANDLER_OUTLET_CONSERVATION: return "conservation";
903 case BC_HANDLER_OUTLET_PRESSURE: return "pressure";
904
905 // Other Physical Handlers
906 case BC_HANDLER_FARFIELD_NONREFLECTING: return "nonreflecting";
907
908 // Multi-Block / Interface Handlers
909 case BC_HANDLER_PERIODIC_GEOMETRIC: return "geometric";
910 case BC_HANDLER_PERIODIC_DRIVEN_CONSTANT_FLUX: return "constant flux";
911 case BC_HANDLER_PERIODIC_DRIVEN_INITIAL_FLUX: return "initial flux";
912 case BC_HANDLER_INTERFACE_OVERSET: return "overset";
913
914 // Default case
916 default: return "UNKNOWN_HANDLER";
917 }
918}
919
920/**
921 * @brief Implementation of \ref DualMonitorDestroy().
922 * @details Full API contract (arguments, ownership, side effects) is documented with
923 * the header declaration in `include/logging.h`.
924 * @see DualMonitorDestroy()
925 */
926PetscErrorCode DualMonitorDestroy(void **ctx)
927{
928 DualMonitorCtx *monctx = (DualMonitorCtx*)*ctx;
929 PetscErrorCode ierr;
930 PetscMPIInt rank;
931
932 PetscFunctionBeginUser;
933 ierr = MPI_Comm_rank(PETSC_COMM_WORLD,&rank); CHKERRQ(ierr);
934 if(!rank && monctx->file_handle){
935 fclose(monctx->file_handle);
936 }
937
938 ierr = PetscFree(monctx); CHKERRQ(ierr);
939 *ctx = NULL;
940 PetscFunctionReturn(0);
941}
942
943/**
944 * @brief A custom KSP monitor that logs the true residual to a file and optionally to the console.
945 *
946 * This function replicates the behavior of KSPMonitorTrueResidualNorm by calculating
947 * the true residual norm ||b - Ax|| itself. It unconditionally logs to a file
948 * viewer and conditionally logs to the console based on a flag in the context.
949 *
950 * @param ksp The Krylov subspace context.
951 * @param it The current iteration number.
952 * @param rnorm The preconditioned residual norm (ignored, we compute our own).
953 * @param ctx A pointer to the DualMonitorCtx structure.
954 * @return PetscErrorCode 0 on success.
955 */
956#undef __FUNCT__
957#define __FUNCT__ "DualKSPMonitor"
958/**
959 * @brief Implementation of \ref DualKSPMonitor().
960 * @details Full API contract (arguments, ownership, side effects) is documented with
961 * the header declaration in `include/logging.h`.
962 * @see DualKSPMonitor()
963 */
964
965PetscErrorCode DualKSPMonitor(KSP ksp, PetscInt it, PetscReal rnorm, void *ctx)
966{
967 DualMonitorCtx *monctx = (DualMonitorCtx*)ctx;
968 PetscErrorCode ierr;
969 PetscReal trnorm, relnorm;
970 Vec r;
971 char norm_buf[256];
972 PetscMPIInt rank;
973
974 PetscFunctionBeginUser;
975 ierr = MPI_Comm_rank(PETSC_COMM_WORLD,&rank); CHKERRQ(ierr);
976
977 // 1. Calculate the true residual norm.
978 ierr = KSPBuildResidual(ksp, NULL, NULL, &r); CHKERRQ(ierr);
979 ierr = VecNorm(r, NORM_2, &trnorm); CHKERRQ(ierr);
980 ierr = VecDestroy(&r); CHKERRQ(ierr);
981
982 // 2. On the first iteration, compute and store the norm of the RHS vector `b`.
983 if (it == 0) {
984 Vec b;
985 ierr = KSPGetRhs(ksp, &b); CHKERRQ(ierr);
986 ierr = VecNorm(b, NORM_2, &monctx->bnorm); CHKERRQ(ierr);
987 }
988
989 if(!rank){
990 // 3. Compute the relative norm and format the output string.
991 if (monctx->bnorm > 1.e-15) {
992 relnorm = trnorm / monctx->bnorm;
993 sprintf(norm_buf, "ts: %-5d | block: %-2d | iter: %-3d | Unprecond Norm: %12.5e | True Norm: %12.5e | Rel Norm: %12.5e",(int)monctx->step, (int)monctx->block_id, (int)it, (double)rnorm, (double)trnorm, (double)relnorm);
994 } else {
995 sprintf(norm_buf,"ts: %-5d | block: %-2d | iter: %-3d | Unprecond Norm: %12.5e | True Norm: %12.5e",(int)monctx->step, (int)monctx->block_id, (int)it, (double)rnorm, (double)trnorm);
996 }
997
998 // 4. Log to the file viewer (unconditionally).
999 if(monctx->file_handle){
1000 ierr = PetscFPrintf(PETSC_COMM_SELF,monctx->file_handle,"%s\n", norm_buf); CHKERRQ(ierr);
1001 }
1002 // 5. Log to the console (conditionally).
1003 if (monctx->log_to_console) {
1004 PetscFPrintf(PETSC_COMM_SELF,stdout, "%s\n", norm_buf); CHKERRQ(ierr);
1005 }
1006
1007 } //rank
1008
1009 PetscFunctionReturn(0);
1010}
1011
1012#define SOLUTION_CONVERGENCE_FLUID_THRESHOLD 0.1
1013#define SOLUTION_CONVERGENCE_REL_EPS 1.0e-30
1014
1026
1031
1032/**
1033 * @brief Forms a guarded relative metric for solution-convergence logging.
1034 *
1035 * This helper centralizes the divide-by-nearly-zero protection used by the
1036 * solution-convergence logger when turning an absolute drift into a relative
1037 * one. The denominator is clamped away from zero so warmup rows, quiescent
1038 * fields, and statistically small observables do not generate infinities.
1039 *
1040 * @param[in] numerator Absolute quantity or drift magnitude.
1041 * @param[in] denominator Reference magnitude used for normalization.
1042 * @return Guarded relative value `numerator / max(|denominator|, eps)`.
1043 */
1044static PetscReal SolutionConvergenceSafeRelative(PetscReal numerator, PetscReal denominator)
1045{
1046 return numerator / PetscMax(PetscAbsReal(denominator), SOLUTION_CONVERGENCE_REL_EPS);
1047}
1048
1049/**
1050 * @brief Computes instantaneous global flow observables for statistical mode.
1051 *
1052 * The statistical solution-convergence path does not compare full Eulerian
1053 * fields. Instead, it tracks a compact history of global observables derived
1054 * from the completed Eulerian state. This helper computes the current
1055 * volume-weighted fluid-domain mean speed and mean kinetic energy from `Ucat`.
1056 *
1057 * Only physical fluid cells contribute:
1058 * - solid/immersed cells are excluded using `Nvert`
1059 * - cells with near-zero metric Jacobian are ignored to avoid invalid volume
1060 * weights
1061 *
1062 * Each MPI rank accumulates local partial sums and the routine reduces them to
1063 * one global pair of observables.
1064 *
1065 * @param[in] simCtx Simulation context owning the finest-level flow
1066 * fields.
1067 * @param[out] mean_speed_out Volume-weighted domain mean of `|u|`.
1068 * @param[out] mean_ke_out Volume-weighted domain mean of `0.5 |u|^2`.
1069 * @return PetscErrorCode 0 on success.
1070 */
1071static PetscErrorCode ComputeCurrentFlowObservables(SimCtx *simCtx, PetscReal *mean_speed_out, PetscReal *mean_ke_out)
1072{
1073 PetscReal local[3] = {0.0, 0.0, 0.0};
1074 PetscReal global[3] = {0.0, 0.0, 0.0};
1075 UserCtx *user = NULL;
1076
1077 PetscFunctionBeginUser;
1078 if (!simCtx || !mean_speed_out || !mean_ke_out) {
1079 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "ComputeCurrentFlowObservables received a NULL argument.");
1080 }
1081
1082 user = simCtx->usermg.mgctx[simCtx->usermg.mglevels - 1].user;
1083
1084 for (PetscInt bi = 0; bi < simCtx->block_number; ++bi) {
1085 const DMDALocalInfo info = user[bi].info;
1086 /* Physical cells only. Index 0 and mx-1 are ghost layers on every axis - a
1087 boundary-condition image on a wall, a copy of the opposite cell on a
1088 periodic axis - and counting them biased every mean and norm here. */
1089 const PetscInt i_start = PetscMax(info.xs, 1), i_end = PetscMin(info.xs + info.xm, info.mx - 1);
1090 const PetscInt j_start = PetscMax(info.ys, 1), j_end = PetscMin(info.ys + info.ym, info.my - 1);
1091 const PetscInt k_start = PetscMax(info.zs, 1), k_end = PetscMin(info.zs + info.zm, info.mz - 1);
1092 Cmpnts ***ucat = NULL;
1093 PetscReal ***aj = NULL;
1094 PetscReal ***nvert = NULL;
1095
1096 PetscCall(DMDAVecGetArrayRead(user[bi].fda, user[bi].Ucat, &ucat));
1097 PetscCall(DMDAVecGetArrayRead(user[bi].da, user[bi].Aj, &aj));
1098 PetscCall(DMDAVecGetArrayRead(user[bi].da, user[bi].Nvert, &nvert));
1099
1100 for (PetscInt k = k_start; k < k_end; ++k) {
1101 for (PetscInt j = j_start; j < j_end; ++j) {
1102 for (PetscInt i = i_start; i < i_end; ++i) {
1103 PetscReal jac = aj[k][j][i];
1104 PetscReal cell_volume = 0.0;
1105 PetscReal speed = 0.0;
1106 PetscReal ke = 0.0;
1107
1108 if (nvert[k][j][i] > SOLUTION_CONVERGENCE_FLUID_THRESHOLD) continue;
1109 if (PetscAbsReal(jac) <= 1.0e-14) continue;
1110
1111 cell_volume = 1.0 / jac;
1112 speed = PetscSqrtReal(ucat[k][j][i].x * ucat[k][j][i].x +
1113 ucat[k][j][i].y * ucat[k][j][i].y +
1114 ucat[k][j][i].z * ucat[k][j][i].z);
1115 ke = 0.5 * speed * speed;
1116
1117 local[0] += cell_volume;
1118 local[1] += speed * cell_volume;
1119 local[2] += ke * cell_volume;
1120 }
1121 }
1122 }
1123
1124 PetscCall(DMDAVecRestoreArrayRead(user[bi].da, user[bi].Nvert, &nvert));
1125 PetscCall(DMDAVecRestoreArrayRead(user[bi].da, user[bi].Aj, &aj));
1126 PetscCall(DMDAVecRestoreArrayRead(user[bi].fda, user[bi].Ucat, &ucat));
1127 }
1128
1129 PetscCallMPI(MPI_Allreduce(local, global, 3, MPIU_REAL, MPI_SUM, PETSC_COMM_WORLD));
1130
1131 if (global[0] <= 0.0) {
1132 *mean_speed_out = 0.0;
1133 *mean_ke_out = 0.0;
1134 } else {
1135 *mean_speed_out = global[1] / global[0];
1136 *mean_ke_out = global[2] / global[0];
1137 }
1138
1139 PetscFunctionReturn(0);
1140}
1141
1142/**
1143 * @brief Computes deterministic solution-drift metrics for the current step.
1144 *
1145 * This helper powers both `steady_deterministic` and
1146 * `periodic_deterministic` solution-convergence modes. It compares the
1147 * completed Eulerian state against either:
1148 * - the previous physical timestep (`Ucat_o`, `P_o`) for steady/transient use
1149 * - the stored phase-aligned snapshot ring for periodic use
1150 *
1151 * The routine performs two passes over the fluid cells:
1152 * 1. velocity/observable pass
1153 * - computes current and reference mean speed / mean KE
1154 * - computes absolute/relative velocity L2 drift
1155 * - accumulates pressure means needed for gauge removal
1156 * 2. pressure-only pass
1157 * - subtracts the volume-weighted mean pressure from both states
1158 * - computes gauge-invariant pressure L2 drift
1159 *
1160 * Warmup behavior is handled here. If no valid reference exists yet, all drift
1161 * outputs are left at zero and `has_reference_out` is set to `PETSC_FALSE`,
1162 * while current observables are still reported.
1163 *
1164 * @param[in] simCtx Simulation context owning the current state.
1165 * @param[in] periodic_mode `PETSC_TRUE` when comparing against
1166 * phase-aligned periodic storage.
1167 * @param[in] phase_step Active phase slot for periodic mode, or `-1`
1168 * when unused.
1169 * @param[in] samples_before Number of solution-convergence samples
1170 * already recorded before this timestep.
1171 * @param[out] has_reference_out Whether a valid comparison state existed.
1172 * @param[out] u_abs_l2_out Absolute L2 drift of Cartesian velocity.
1173 * @param[out] u_rel_l2_out Relative L2 drift of Cartesian velocity.
1174 * @param[out] p_abs_l2_out Gauge-invariant absolute L2 pressure drift.
1175 * @param[out] p_rel_l2_out Gauge-invariant relative L2 pressure drift.
1176 * @param[out] mean_speed_out Current volume-weighted mean speed.
1177 * @param[out] mean_speed_ref_out Reference volume-weighted mean speed.
1178 * @param[out] mean_speed_abs_out Absolute drift of mean speed.
1179 * @param[out] mean_speed_rel_out Relative drift of mean speed.
1180 * @param[out] mean_ke_out Current volume-weighted mean kinetic energy.
1181 * @param[out] mean_ke_ref_out Reference volume-weighted mean kinetic
1182 * energy.
1183 * @param[out] mean_ke_abs_out Absolute drift of mean kinetic energy.
1184 * @param[out] mean_ke_rel_out Relative drift of mean kinetic energy.
1185 * @return PetscErrorCode 0 on success.
1186 */
1187static PetscErrorCode ComputeDeterministicSolutionMetrics(SimCtx *simCtx,
1188 PetscBool periodic_mode,
1189 PetscInt phase_step,
1190 PetscInt samples_before,
1191 PetscBool *has_reference_out,
1192 PetscReal *u_abs_l2_out,
1193 PetscReal *u_rel_l2_out,
1194 PetscReal *p_abs_l2_out,
1195 PetscReal *p_rel_l2_out,
1196 PetscReal *mean_speed_out,
1197 PetscReal *mean_speed_ref_out,
1198 PetscReal *mean_speed_abs_out,
1199 PetscReal *mean_speed_rel_out,
1200 PetscReal *mean_ke_out,
1201 PetscReal *mean_ke_ref_out,
1202 PetscReal *mean_ke_abs_out,
1203 PetscReal *mean_ke_rel_out)
1204{
1205 SolutionConvergenceDeterministicPass1 local_pass1 = {0};
1206 SolutionConvergenceDeterministicPass1 global_pass1 = {0};
1207 SolutionConvergenceDeterministicPass2 local_pass2 = {0};
1208 SolutionConvergenceDeterministicPass2 global_pass2 = {0};
1209 PetscReal current_pressure_mean = 0.0;
1210 PetscReal reference_pressure_mean = 0.0;
1211 UserCtx *user = NULL;
1212
1213 PetscFunctionBeginUser;
1214 if (!simCtx || !has_reference_out || !u_abs_l2_out || !u_rel_l2_out || !p_abs_l2_out || !p_rel_l2_out ||
1215 !mean_speed_out || !mean_speed_ref_out || !mean_speed_abs_out || !mean_speed_rel_out ||
1216 !mean_ke_out || !mean_ke_ref_out || !mean_ke_abs_out || !mean_ke_rel_out) {
1217 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "ComputeDeterministicSolutionMetrics received a NULL output pointer.");
1218 }
1219
1220 *has_reference_out = periodic_mode
1221 ? (PetscBool)(simCtx->solutionConvergencePeriodSteps > 0 &&
1222 phase_step >= 0 &&
1223 phase_step < simCtx->solutionConvergencePeriodSteps &&
1224 samples_before >= simCtx->solutionConvergencePeriodSteps)
1225 : (PetscBool)(samples_before > 0);
1226
1227 *u_abs_l2_out = 0.0;
1228 *u_rel_l2_out = 0.0;
1229 *p_abs_l2_out = 0.0;
1230 *p_rel_l2_out = 0.0;
1231 *mean_speed_out = 0.0;
1232 *mean_speed_ref_out = 0.0;
1233 *mean_speed_abs_out = 0.0;
1234 *mean_speed_rel_out = 0.0;
1235 *mean_ke_out = 0.0;
1236 *mean_ke_ref_out = 0.0;
1237 *mean_ke_abs_out = 0.0;
1238 *mean_ke_rel_out = 0.0;
1239
1240 user = simCtx->usermg.mgctx[simCtx->usermg.mglevels - 1].user;
1241
1242 for (PetscInt bi = 0; bi < simCtx->block_number; ++bi) {
1243 const DMDALocalInfo info = user[bi].info;
1244 /* Physical cells only. Index 0 and mx-1 are ghost layers on every axis - a
1245 boundary-condition image on a wall, a copy of the opposite cell on a
1246 periodic axis - and counting them biased every mean and norm here. */
1247 const PetscInt i_start = PetscMax(info.xs, 1), i_end = PetscMin(info.xs + info.xm, info.mx - 1);
1248 const PetscInt j_start = PetscMax(info.ys, 1), j_end = PetscMin(info.ys + info.ym, info.my - 1);
1249 const PetscInt k_start = PetscMax(info.zs, 1), k_end = PetscMin(info.zs + info.zm, info.mz - 1);
1250 Cmpnts ***ucat = NULL;
1251 Cmpnts ***ucat_ref = NULL;
1252 PetscReal ***pressure = NULL;
1253 PetscReal ***pressure_ref = NULL;
1254 PetscReal ***aj = NULL;
1255 PetscReal ***nvert = NULL;
1256 Vec ucat_reference_vec = NULL;
1257 Vec pressure_reference_vec = NULL;
1258
1259 if (*has_reference_out) {
1260 if (periodic_mode) {
1261 ucat_reference_vec = user[bi].solutionConvergencePeriodicUcatRef[phase_step];
1262 pressure_reference_vec = user[bi].solutionConvergencePeriodicPRef[phase_step];
1263 } else {
1264 ucat_reference_vec = user[bi].Ucat_o;
1265 pressure_reference_vec = user[bi].P_o;
1266 }
1267 }
1268
1269 PetscCall(DMDAVecGetArrayRead(user[bi].fda, user[bi].Ucat, &ucat));
1270 PetscCall(DMDAVecGetArrayRead(user[bi].da, user[bi].P, &pressure));
1271 PetscCall(DMDAVecGetArrayRead(user[bi].da, user[bi].Aj, &aj));
1272 PetscCall(DMDAVecGetArrayRead(user[bi].da, user[bi].Nvert, &nvert));
1273 if (*has_reference_out) {
1274 PetscCall(DMDAVecGetArrayRead(user[bi].fda, ucat_reference_vec, &ucat_ref));
1275 PetscCall(DMDAVecGetArrayRead(user[bi].da, pressure_reference_vec, &pressure_ref));
1276 }
1277
1278 for (PetscInt k = k_start; k < k_end; ++k) {
1279 for (PetscInt j = j_start; j < j_end; ++j) {
1280 for (PetscInt i = i_start; i < i_end; ++i) {
1281 PetscReal jac = aj[k][j][i];
1282 PetscReal cell_volume = 0.0;
1283 PetscReal speed = 0.0;
1284 PetscReal ke = 0.0;
1285
1286 if (nvert[k][j][i] > SOLUTION_CONVERGENCE_FLUID_THRESHOLD) continue;
1287 if (PetscAbsReal(jac) <= 1.0e-14) continue;
1288
1289 cell_volume = 1.0 / jac;
1290 speed = PetscSqrtReal(ucat[k][j][i].x * ucat[k][j][i].x +
1291 ucat[k][j][i].y * ucat[k][j][i].y +
1292 ucat[k][j][i].z * ucat[k][j][i].z);
1293 ke = 0.5 * speed * speed;
1294
1295 local_pass1.fluid_volume += cell_volume;
1296 local_pass1.current_speed_sum += speed * cell_volume;
1297 local_pass1.current_ke_sum += ke * cell_volume;
1298 local_pass1.current_u_norm_sq += (ucat[k][j][i].x * ucat[k][j][i].x +
1299 ucat[k][j][i].y * ucat[k][j][i].y +
1300 ucat[k][j][i].z * ucat[k][j][i].z) * cell_volume;
1301
1302 if (*has_reference_out) {
1303 PetscReal ref_speed = PetscSqrtReal(ucat_ref[k][j][i].x * ucat_ref[k][j][i].x +
1304 ucat_ref[k][j][i].y * ucat_ref[k][j][i].y +
1305 ucat_ref[k][j][i].z * ucat_ref[k][j][i].z);
1306 PetscReal ref_ke = 0.5 * ref_speed * ref_speed;
1307 PetscReal dux = ucat[k][j][i].x - ucat_ref[k][j][i].x;
1308 PetscReal duy = ucat[k][j][i].y - ucat_ref[k][j][i].y;
1309 PetscReal duz = ucat[k][j][i].z - ucat_ref[k][j][i].z;
1310
1311 local_pass1.reference_speed_sum += ref_speed * cell_volume;
1312 local_pass1.reference_ke_sum += ref_ke * cell_volume;
1313 local_pass1.delta_u_norm_sq += (dux * dux + duy * duy + duz * duz) * cell_volume;
1314 local_pass1.current_pressure_sum += pressure[k][j][i] * cell_volume;
1315 local_pass1.reference_pressure_sum += pressure_ref[k][j][i] * cell_volume;
1316 }
1317 }
1318 }
1319 }
1320
1321 if (*has_reference_out) {
1322 PetscCall(DMDAVecRestoreArrayRead(user[bi].da, pressure_reference_vec, &pressure_ref));
1323 PetscCall(DMDAVecRestoreArrayRead(user[bi].fda, ucat_reference_vec, &ucat_ref));
1324 }
1325 PetscCall(DMDAVecRestoreArrayRead(user[bi].da, user[bi].Nvert, &nvert));
1326 PetscCall(DMDAVecRestoreArrayRead(user[bi].da, user[bi].Aj, &aj));
1327 PetscCall(DMDAVecRestoreArrayRead(user[bi].da, user[bi].P, &pressure));
1328 PetscCall(DMDAVecRestoreArrayRead(user[bi].fda, user[bi].Ucat, &ucat));
1329 }
1330
1331 PetscCallMPI(MPI_Allreduce(&local_pass1, &global_pass1,
1332 sizeof(SolutionConvergenceDeterministicPass1) / sizeof(PetscReal),
1333 MPIU_REAL, MPI_SUM, PETSC_COMM_WORLD));
1334
1335 if (global_pass1.fluid_volume <= 0.0) PetscFunctionReturn(0);
1336
1337 *mean_speed_out = global_pass1.current_speed_sum / global_pass1.fluid_volume;
1338 *mean_ke_out = global_pass1.current_ke_sum / global_pass1.fluid_volume;
1339
1340 if (!*has_reference_out) PetscFunctionReturn(0);
1341
1342 *mean_speed_ref_out = global_pass1.reference_speed_sum / global_pass1.fluid_volume;
1343 *mean_speed_abs_out = PetscAbsReal(*mean_speed_out - *mean_speed_ref_out);
1344 *mean_speed_rel_out = SolutionConvergenceSafeRelative(*mean_speed_abs_out, *mean_speed_out);
1345 *mean_ke_ref_out = global_pass1.reference_ke_sum / global_pass1.fluid_volume;
1346 *mean_ke_abs_out = PetscAbsReal(*mean_ke_out - *mean_ke_ref_out);
1347 *mean_ke_rel_out = SolutionConvergenceSafeRelative(*mean_ke_abs_out, *mean_ke_out);
1348
1349 current_pressure_mean = global_pass1.current_pressure_sum / global_pass1.fluid_volume;
1350 reference_pressure_mean = global_pass1.reference_pressure_sum / global_pass1.fluid_volume;
1351
1352 for (PetscInt bi = 0; bi < simCtx->block_number; ++bi) {
1353 const DMDALocalInfo info = user[bi].info;
1354 /* Physical cells only. Index 0 and mx-1 are ghost layers on every axis - a
1355 boundary-condition image on a wall, a copy of the opposite cell on a
1356 periodic axis - and counting them biased every mean and norm here. */
1357 const PetscInt i_start = PetscMax(info.xs, 1), i_end = PetscMin(info.xs + info.xm, info.mx - 1);
1358 const PetscInt j_start = PetscMax(info.ys, 1), j_end = PetscMin(info.ys + info.ym, info.my - 1);
1359 const PetscInt k_start = PetscMax(info.zs, 1), k_end = PetscMin(info.zs + info.zm, info.mz - 1);
1360 PetscReal ***pressure = NULL;
1361 PetscReal ***pressure_ref = NULL;
1362 PetscReal ***aj = NULL;
1363 PetscReal ***nvert = NULL;
1364 Vec pressure_reference_vec = periodic_mode ? user[bi].solutionConvergencePeriodicPRef[phase_step] : user[bi].P_o;
1365
1366 PetscCall(DMDAVecGetArrayRead(user[bi].da, user[bi].P, &pressure));
1367 PetscCall(DMDAVecGetArrayRead(user[bi].da, pressure_reference_vec, &pressure_ref));
1368 PetscCall(DMDAVecGetArrayRead(user[bi].da, user[bi].Aj, &aj));
1369 PetscCall(DMDAVecGetArrayRead(user[bi].da, user[bi].Nvert, &nvert));
1370
1371 for (PetscInt k = k_start; k < k_end; ++k) {
1372 for (PetscInt j = j_start; j < j_end; ++j) {
1373 for (PetscInt i = i_start; i < i_end; ++i) {
1374 PetscReal jac = aj[k][j][i];
1375 PetscReal cell_volume = 0.0;
1376 PetscReal current_pressure = 0.0;
1377 PetscReal reference_pressure = 0.0;
1378 PetscReal delta_pressure = 0.0;
1379
1380 if (nvert[k][j][i] > SOLUTION_CONVERGENCE_FLUID_THRESHOLD) continue;
1381 if (PetscAbsReal(jac) <= 1.0e-14) continue;
1382
1383 cell_volume = 1.0 / jac;
1384 current_pressure = pressure[k][j][i] - current_pressure_mean;
1385 reference_pressure = pressure_ref[k][j][i] - reference_pressure_mean;
1386 delta_pressure = current_pressure - reference_pressure;
1387
1388 local_pass2.current_pressure_norm_sq += current_pressure * current_pressure * cell_volume;
1389 local_pass2.delta_pressure_norm_sq += delta_pressure * delta_pressure * cell_volume;
1390 }
1391 }
1392 }
1393
1394 PetscCall(DMDAVecRestoreArrayRead(user[bi].da, user[bi].Nvert, &nvert));
1395 PetscCall(DMDAVecRestoreArrayRead(user[bi].da, user[bi].Aj, &aj));
1396 PetscCall(DMDAVecRestoreArrayRead(user[bi].da, pressure_reference_vec, &pressure_ref));
1397 PetscCall(DMDAVecRestoreArrayRead(user[bi].da, user[bi].P, &pressure));
1398 }
1399
1400 PetscCallMPI(MPI_Allreduce(&local_pass2, &global_pass2,
1401 sizeof(SolutionConvergenceDeterministicPass2) / sizeof(PetscReal),
1402 MPIU_REAL, MPI_SUM, PETSC_COMM_WORLD));
1403
1404 *u_abs_l2_out = PetscSqrtReal(global_pass1.delta_u_norm_sq);
1405 *u_rel_l2_out = SolutionConvergenceSafeRelative(*u_abs_l2_out, PetscSqrtReal(global_pass1.current_u_norm_sq));
1406 *p_abs_l2_out = PetscSqrtReal(global_pass2.delta_pressure_norm_sq);
1407 *p_rel_l2_out = SolutionConvergenceSafeRelative(*p_abs_l2_out, PetscSqrtReal(global_pass2.current_pressure_norm_sq));
1408
1409 PetscFunctionReturn(0);
1410}
1411
1412/**
1413 * @brief Reads one sample from the statistical ring buffer by age.
1414 *
1415 * The statistical logger stores scalar observables in a compact circular
1416 * buffer. This helper interprets the buffer using `samples_available` as the
1417 * logical end of the history and returns the entry `offset_from_latest` steps
1418 * back from the newest stored sample.
1419 *
1420 * Out-of-range requests return zero so warmup handling can remain simple and
1421 * deterministic.
1422 *
1423 * @param[in] history Ring-buffer storage array.
1424 * @param[in] capacity Total ring-buffer capacity.
1425 * @param[in] samples_available Number of logical samples available to read.
1426 * @param[in] offset_from_latest `0` means newest sample, `1` previous sample,
1427 * and so on.
1428 * @return Requested historical sample, or `0.0` if unavailable.
1429 */
1430static PetscReal SolutionConvergenceHistoryGet(const PetscReal *history,
1431 PetscInt capacity,
1432 PetscInt samples_available,
1433 PetscInt offset_from_latest)
1434{
1435 PetscInt count = 0;
1436 PetscInt index = 0;
1437
1438 if (!history || capacity <= 0 || samples_available <= 0 || offset_from_latest < 0) {
1439 return 0.0;
1440 }
1441
1442 count = PetscMin(samples_available, capacity);
1443 if (offset_from_latest >= count) return 0.0;
1444
1445 index = (samples_available - 1 - offset_from_latest) % capacity;
1446 if (index < 0) index += capacity;
1447 return history[index];
1448}
1449
1450/**
1451 * @brief Appends one timestep's scalar observables to the statistical history.
1452 *
1453 * Statistical solution-convergence compares adjacent windows of scalar
1454 * observables rather than full fields. This helper writes the current
1455 * `mean_speed` and `mean_ke` into the rolling history arrays using the current
1456 * sample count to choose the circular-buffer slot.
1457 *
1458 * @param[in,out] simCtx Simulation context owning the history arrays.
1459 * @param[in] samples_before Number of samples present before appending the
1460 * current timestep.
1461 * @param[in] mean_speed Current timestep mean-speed observable.
1462 * @param[in] mean_ke Current timestep mean-KE observable.
1463 * @return PetscErrorCode 0 on success.
1464 */
1465static PetscErrorCode AppendStatisticalObservableSample(SimCtx *simCtx,
1466 PetscInt samples_before,
1467 PetscReal mean_speed,
1468 PetscReal mean_ke)
1469{
1470 PetscInt history_capacity = 0;
1471 PetscInt slot = 0;
1472
1473 PetscFunctionBeginUser;
1474 if (!simCtx || !simCtx->solutionConvergenceMeanSpeedHistory || !simCtx->solutionConvergenceMeanKEHistory) {
1475 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Statistical solution-convergence history is not allocated.");
1476 }
1477
1478 history_capacity = 2 * simCtx->solutionConvergenceWindowSteps;
1479 if (history_capacity <= 0) {
1480 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, "Statistical solution-convergence history capacity must be positive.");
1481 }
1482
1483 slot = samples_before % history_capacity;
1484 simCtx->solutionConvergenceMeanSpeedHistory[slot] = mean_speed;
1485 simCtx->solutionConvergenceMeanKEHistory[slot] = mean_ke;
1486
1487 PetscFunctionReturn(0);
1488}
1489
1490/**
1491 * @brief Computes adjacent-window drift metrics for statistical steady mode.
1492 *
1493 * Once enough samples have been accumulated, this helper forms:
1494 * - the current window over the most recent `window_steps` samples
1495 * - the previous adjacent window over the preceding `window_steps` samples
1496 *
1497 * From those windows it computes means, RMS values, and absolute/relative
1498 * drift for both tracked observables (`mean_speed` and `mean_ke`). When the
1499 * history is still warming up:
1500 * - fewer than `window_steps` samples: no window metrics are available
1501 * - between `window_steps` and `2*window_steps - 1` samples: current-window
1502 * metrics are available, but no reference window exists yet
1503 *
1504 * @param[in] simCtx Simulation context owning the
1505 * statistical history.
1506 * @param[in] samples_available Number of samples available after
1507 * appending the current timestep.
1508 * @param[out] has_reference_out Whether both adjacent windows
1509 * exist and drift metrics are
1510 * meaningful.
1511 * @param[out] mean_speed_window_out Mean speed over the current
1512 * window.
1513 * @param[out] mean_speed_window_prev_out Mean speed over the previous
1514 * window.
1515 * @param[out] mean_speed_window_abs_out Absolute drift between current
1516 * and previous window means.
1517 * @param[out] mean_speed_window_rel_out Relative drift between current
1518 * and previous window means.
1519 * @param[out] mean_speed_rms_window_out RMS of mean-speed samples in the
1520 * current window.
1521 * @param[out] mean_speed_rms_window_prev_out RMS of mean-speed samples in the
1522 * previous window.
1523 * @param[out] mean_speed_rms_window_abs_out Absolute drift between window RMS
1524 * values.
1525 * @param[out] mean_speed_rms_window_rel_out Relative drift between window RMS
1526 * values.
1527 * @param[out] mean_ke_window_out Mean kinetic energy over the
1528 * current window.
1529 * @param[out] mean_ke_window_prev_out Mean kinetic energy over the
1530 * previous window.
1531 * @param[out] mean_ke_window_abs_out Absolute drift between current
1532 * and previous KE-window means.
1533 * @param[out] mean_ke_window_rel_out Relative drift between current
1534 * and previous KE-window means.
1535 * @param[out] mean_ke_rms_window_out RMS of mean-KE samples in the
1536 * current window.
1537 * @param[out] mean_ke_rms_window_prev_out RMS of mean-KE samples in the
1538 * previous window.
1539 * @param[out] mean_ke_rms_window_abs_out Absolute drift between KE-window
1540 * RMS values.
1541 * @param[out] mean_ke_rms_window_rel_out Relative drift between KE-window
1542 * RMS values.
1543 * @return PetscErrorCode 0 on success.
1544 */
1545static PetscErrorCode ComputeStatisticalWindowMetrics(const SimCtx *simCtx,
1546 PetscInt samples_available,
1547 PetscBool *has_reference_out,
1548 PetscReal *mean_speed_window_out,
1549 PetscReal *mean_speed_window_prev_out,
1550 PetscReal *mean_speed_window_abs_out,
1551 PetscReal *mean_speed_window_rel_out,
1552 PetscReal *mean_speed_rms_window_out,
1553 PetscReal *mean_speed_rms_window_prev_out,
1554 PetscReal *mean_speed_rms_window_abs_out,
1555 PetscReal *mean_speed_rms_window_rel_out,
1556 PetscReal *mean_ke_window_out,
1557 PetscReal *mean_ke_window_prev_out,
1558 PetscReal *mean_ke_window_abs_out,
1559 PetscReal *mean_ke_window_rel_out,
1560 PetscReal *mean_ke_rms_window_out,
1561 PetscReal *mean_ke_rms_window_prev_out,
1562 PetscReal *mean_ke_rms_window_abs_out,
1563 PetscReal *mean_ke_rms_window_rel_out)
1564{
1565 PetscInt w = 0;
1566 PetscInt history_capacity = 0;
1567 PetscReal speed_sum = 0.0;
1568 PetscReal speed_sum_sq = 0.0;
1569 PetscReal speed_prev_sum = 0.0;
1570 PetscReal speed_prev_sum_sq = 0.0;
1571 PetscReal ke_sum = 0.0;
1572 PetscReal ke_sum_sq = 0.0;
1573 PetscReal ke_prev_sum = 0.0;
1574 PetscReal ke_prev_sum_sq = 0.0;
1575
1576 PetscFunctionBeginUser;
1577 if (!simCtx || !has_reference_out || !mean_speed_window_out || !mean_speed_window_prev_out ||
1578 !mean_speed_window_abs_out || !mean_speed_window_rel_out || !mean_speed_rms_window_out ||
1579 !mean_speed_rms_window_prev_out || !mean_speed_rms_window_abs_out || !mean_speed_rms_window_rel_out ||
1580 !mean_ke_window_out || !mean_ke_window_prev_out || !mean_ke_window_abs_out || !mean_ke_window_rel_out ||
1581 !mean_ke_rms_window_out || !mean_ke_rms_window_prev_out || !mean_ke_rms_window_abs_out ||
1582 !mean_ke_rms_window_rel_out) {
1583 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "ComputeStatisticalWindowMetrics received a NULL output pointer.");
1584 }
1585
1586 *has_reference_out = PETSC_FALSE;
1587 *mean_speed_window_out = 0.0;
1588 *mean_speed_window_prev_out = 0.0;
1589 *mean_speed_window_abs_out = 0.0;
1590 *mean_speed_window_rel_out = 0.0;
1591 *mean_speed_rms_window_out = 0.0;
1592 *mean_speed_rms_window_prev_out = 0.0;
1593 *mean_speed_rms_window_abs_out = 0.0;
1594 *mean_speed_rms_window_rel_out = 0.0;
1595 *mean_ke_window_out = 0.0;
1596 *mean_ke_window_prev_out = 0.0;
1597 *mean_ke_window_abs_out = 0.0;
1598 *mean_ke_window_rel_out = 0.0;
1599 *mean_ke_rms_window_out = 0.0;
1600 *mean_ke_rms_window_prev_out = 0.0;
1601 *mean_ke_rms_window_abs_out = 0.0;
1602 *mean_ke_rms_window_rel_out = 0.0;
1603
1605 history_capacity = 2 * w;
1606 if (w <= 0 || samples_available < w) PetscFunctionReturn(0);
1607
1608 for (PetscInt idx = 0; idx < w; ++idx) {
1610 history_capacity,
1611 samples_available,
1612 idx);
1614 history_capacity,
1615 samples_available,
1616 idx);
1617 speed_sum += speed_value;
1618 speed_sum_sq += speed_value * speed_value;
1619 ke_sum += ke_value;
1620 ke_sum_sq += ke_value * ke_value;
1621 }
1622
1623 *mean_speed_window_out = speed_sum / (PetscReal)w;
1624 *mean_speed_rms_window_out = PetscSqrtReal(PetscMax(0.0, speed_sum_sq / (PetscReal)w -
1625 (*mean_speed_window_out) * (*mean_speed_window_out)));
1626 *mean_ke_window_out = ke_sum / (PetscReal)w;
1627 *mean_ke_rms_window_out = PetscSqrtReal(PetscMax(0.0, ke_sum_sq / (PetscReal)w -
1628 (*mean_ke_window_out) * (*mean_ke_window_out)));
1629
1630 if (samples_available < 2 * w) PetscFunctionReturn(0);
1631
1632 for (PetscInt idx = w; idx < 2 * w; ++idx) {
1634 history_capacity,
1635 samples_available,
1636 idx);
1638 history_capacity,
1639 samples_available,
1640 idx);
1641 speed_prev_sum += speed_value;
1642 speed_prev_sum_sq += speed_value * speed_value;
1643 ke_prev_sum += ke_value;
1644 ke_prev_sum_sq += ke_value * ke_value;
1645 }
1646
1647 *has_reference_out = PETSC_TRUE;
1648 *mean_speed_window_prev_out = speed_prev_sum / (PetscReal)w;
1649 *mean_speed_window_abs_out = PetscAbsReal(*mean_speed_window_out - *mean_speed_window_prev_out);
1650 *mean_speed_window_rel_out = SolutionConvergenceSafeRelative(*mean_speed_window_abs_out, *mean_speed_window_out);
1651 *mean_speed_rms_window_prev_out = PetscSqrtReal(PetscMax(0.0, speed_prev_sum_sq / (PetscReal)w -
1652 (*mean_speed_window_prev_out) * (*mean_speed_window_prev_out)));
1653 *mean_speed_rms_window_abs_out = PetscAbsReal(*mean_speed_rms_window_out - *mean_speed_rms_window_prev_out);
1654 *mean_speed_rms_window_rel_out = SolutionConvergenceSafeRelative(*mean_speed_rms_window_abs_out, *mean_speed_rms_window_out);
1655
1656 *mean_ke_window_prev_out = ke_prev_sum / (PetscReal)w;
1657 *mean_ke_window_abs_out = PetscAbsReal(*mean_ke_window_out - *mean_ke_window_prev_out);
1658 *mean_ke_window_rel_out = SolutionConvergenceSafeRelative(*mean_ke_window_abs_out, *mean_ke_window_out);
1659 *mean_ke_rms_window_prev_out = PetscSqrtReal(PetscMax(0.0, ke_prev_sum_sq / (PetscReal)w -
1660 (*mean_ke_window_prev_out) * (*mean_ke_window_prev_out)));
1661 *mean_ke_rms_window_abs_out = PetscAbsReal(*mean_ke_rms_window_out - *mean_ke_rms_window_prev_out);
1662 *mean_ke_rms_window_rel_out = SolutionConvergenceSafeRelative(*mean_ke_rms_window_abs_out, *mean_ke_rms_window_out);
1663
1664 PetscFunctionReturn(0);
1665}
1666
1667/**
1668 * @brief Maps the internal solution-convergence mode enum to its log label.
1669 *
1670 * The logger writes a human-readable mode string into the
1671 * `solution_convergence.log` banner and `mode` column. This helper keeps the
1672 * formatting centralized so the file output stays consistent with the accepted
1673 * configuration names.
1674 *
1675 * @param[in] mode Internal solution-convergence mode selector.
1676 * @return Lowercase string label written to the log output.
1677 */
1679{
1680 switch (mode) {
1681 case SOLUTION_CONVERGENCE_STEADY_DETERMINISTIC: return "steady_deterministic";
1682 case SOLUTION_CONVERGENCE_PERIODIC_DETERMINISTIC: return "periodic_deterministic";
1683 case SOLUTION_CONVERGENCE_STATISTICAL_STEADY: return "statistical_steady";
1684 case SOLUTION_CONVERGENCE_TRANSIENT: return "transient";
1685 default: return "unknown";
1686 }
1687}
1688
1689/**
1690 * @brief Implementation of \ref LOG_SOLUTION_CONVERGENCE().
1691 * @details Full API contract (arguments, ownership, side effects) is documented with
1692 * the header declaration in `include/logging.h`.
1693 * @see LOG_SOLUTION_CONVERGENCE()
1694 */
1695PetscErrorCode LOG_SOLUTION_CONVERGENCE(SimCtx *simCtx)
1696{
1697 PetscMPIInt rank = 0;
1698 PetscBool has_reference = PETSC_FALSE;
1699 PetscInt phase_step = -1;
1700 PetscInt samples_before = 0;
1701 PetscReal u_abs_l2 = 0.0, u_rel_l2 = 0.0, p_abs_l2 = 0.0, p_rel_l2 = 0.0;
1702 PetscReal mean_speed = 0.0, mean_speed_reference = 0.0, mean_speed_abs_drift = 0.0, mean_speed_rel_drift = 0.0;
1703 PetscReal mean_ke = 0.0, mean_ke_reference = 0.0, mean_ke_abs_drift = 0.0, mean_ke_rel_drift = 0.0;
1704 PetscReal mean_speed_window = 0.0, mean_speed_window_prev = 0.0, mean_speed_window_abs_drift = 0.0, mean_speed_window_rel_drift = 0.0;
1705 PetscReal mean_speed_rms_window = 0.0, mean_speed_rms_window_prev = 0.0, mean_speed_rms_window_abs_drift = 0.0, mean_speed_rms_window_rel_drift = 0.0;
1706 PetscReal mean_ke_window = 0.0, mean_ke_window_prev = 0.0, mean_ke_window_abs_drift = 0.0, mean_ke_window_rel_drift = 0.0;
1707 PetscReal mean_ke_rms_window = 0.0, mean_ke_rms_window_prev = 0.0, mean_ke_rms_window_abs_drift = 0.0, mean_ke_rms_window_rel_drift = 0.0;
1708
1709 PetscFunctionBeginUser;
1710 if (!simCtx) PetscFunctionReturn(0);
1711 if (simCtx->exec_mode != EXEC_MODE_SOLVER) PetscFunctionReturn(0);
1712 if (!simCtx->solutionConvergenceEnabled) PetscFunctionReturn(0);
1713
1714 samples_before = simCtx->solutionConvergenceSamplesRecorded;
1715
1716 switch (simCtx->solutionConvergenceMode) {
1719 PetscCall(ComputeDeterministicSolutionMetrics(simCtx, PETSC_FALSE, -1, samples_before,
1720 &has_reference,
1721 &u_abs_l2, &u_rel_l2,
1722 &p_abs_l2, &p_rel_l2,
1723 &mean_speed, &mean_speed_reference,
1724 &mean_speed_abs_drift, &mean_speed_rel_drift,
1725 &mean_ke, &mean_ke_reference,
1726 &mean_ke_abs_drift, &mean_ke_rel_drift));
1727 break;
1729 phase_step = simCtx->solutionConvergencePeriodSteps > 0 ? (simCtx->step % simCtx->solutionConvergencePeriodSteps) : -1;
1730 PetscCall(ComputeDeterministicSolutionMetrics(simCtx, PETSC_TRUE, phase_step, samples_before,
1731 &has_reference,
1732 &u_abs_l2, &u_rel_l2,
1733 &p_abs_l2, &p_rel_l2,
1734 &mean_speed, &mean_speed_reference,
1735 &mean_speed_abs_drift, &mean_speed_rel_drift,
1736 &mean_ke, &mean_ke_reference,
1737 &mean_ke_abs_drift, &mean_ke_rel_drift));
1738 break;
1740 PetscCall(ComputeCurrentFlowObservables(simCtx, &mean_speed, &mean_ke));
1741 PetscCall(AppendStatisticalObservableSample(simCtx, samples_before, mean_speed, mean_ke));
1742 PetscCall(ComputeStatisticalWindowMetrics(simCtx, samples_before + 1,
1743 &has_reference,
1744 &mean_speed_window, &mean_speed_window_prev,
1745 &mean_speed_window_abs_drift, &mean_speed_window_rel_drift,
1746 &mean_speed_rms_window, &mean_speed_rms_window_prev,
1747 &mean_speed_rms_window_abs_drift, &mean_speed_rms_window_rel_drift,
1748 &mean_ke_window, &mean_ke_window_prev,
1749 &mean_ke_window_abs_drift, &mean_ke_window_rel_drift,
1750 &mean_ke_rms_window, &mean_ke_rms_window_prev,
1751 &mean_ke_rms_window_abs_drift, &mean_ke_rms_window_rel_drift));
1752 break;
1753 default:
1754 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Unknown solution convergence mode %d.", (int)simCtx->solutionConvergenceMode);
1755 }
1756
1757 PetscCallMPI(MPI_Comm_rank(PETSC_COMM_WORLD, &rank));
1758 if (rank == 0) {
1759 char log_path[PETSC_MAX_PATH_LEN + 32];
1760 FILE *f = NULL;
1761 const char *mode_str = SolutionConvergenceModeToString(simCtx->solutionConvergenceMode);
1762
1763 PetscCall(PetscSNPrintf(log_path, sizeof(log_path), "%s/solution_convergence.log", simCtx->log_dir));
1764 f = fopen(log_path, "a");
1765 if (!f) {
1766 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Cannot open solution convergence log: %s", log_path);
1767 }
1768
1769 if (ftell(f) == 0) {
1770 switch (simCtx->solutionConvergenceMode) {
1773 fprintf(f, "==================== Solution Convergence Log [mode: %s] ====================\n", mode_str);
1774 /* 16 columns; header width = 314 chars */
1775 fprintf(f, "%-10s | %-18s | %-22s | %-3s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s\n",
1776 "step", "time", "mode", "ref",
1777 "u_abs_l2", "u_rel_l2", "p_abs_l2", "p_rel_l2",
1778 "mean_speed", "spd_ref", "spd_abs", "spd_rel",
1779 "mean_ke", "ke_ref", "ke_abs", "ke_rel");
1780 fprintf(f, "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n");
1781 break;
1783 fprintf(f, "==================== Solution Convergence Log [mode: %s | period_steps: %d] ====================\n",
1784 mode_str, (int)simCtx->solutionConvergencePeriodSteps);
1785 /* 18 columns; header width = 330 chars */
1786 fprintf(f, "%-10s | %-18s | %-22s | %-3s | %-5s | %-5s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s\n",
1787 "step", "time", "mode", "ref", "ph", "per",
1788 "u_abs_l2", "u_rel_l2", "p_abs_l2", "p_rel_l2",
1789 "mean_speed", "spd_ref", "spd_abs", "spd_rel",
1790 "mean_ke", "ke_ref", "ke_abs", "ke_rel");
1791 fprintf(f, "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n");
1792 break;
1794 fprintf(f, "==================== Solution Convergence Log [mode: %s | window_steps: %d] ====================\n",
1795 mode_str, (int)simCtx->solutionConvergenceWindowSteps);
1796 /* 21 columns; header width = 406 chars */
1797 fprintf(f, "%-10s | %-18s | %-22s | %-3s | %-5s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s | %-18s\n",
1798 "step", "time", "mode", "ref", "win",
1799 "mean_speed", "mean_ke",
1800 "spd_win", "spd_win_prev", "spd_win_abs", "spd_win_rel",
1801 "spd_rms_win", "spd_rms_abs", "spd_rms_rel",
1802 "ke_win", "ke_win_prev", "ke_win_abs", "ke_win_rel",
1803 "ke_rms_win", "ke_rms_abs", "ke_rms_rel");
1804 fprintf(f, "------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n");
1805 break;
1806 default: break;
1807 }
1808 }
1809 if (simCtx->continueMode && simCtx->step == simCtx->StartStep + 1) {
1810 fprintf(f, "# Continuation from step %" PetscInt_FMT "\n", simCtx->StartStep);
1811 }
1812
1813 switch (simCtx->solutionConvergenceMode) {
1816 fprintf(f,
1817 "%-10d | %-18.10e | %-22s | %-3d | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e\n",
1818 (int)simCtx->step, (double)simCtx->ti, mode_str, has_reference ? 1 : 0,
1819 (double)u_abs_l2, (double)u_rel_l2, (double)p_abs_l2, (double)p_rel_l2,
1820 (double)mean_speed, (double)mean_speed_reference,
1821 (double)mean_speed_abs_drift, (double)mean_speed_rel_drift,
1822 (double)mean_ke, (double)mean_ke_reference,
1823 (double)mean_ke_abs_drift, (double)mean_ke_rel_drift);
1824 break;
1826 fprintf(f,
1827 "%-10d | %-18.10e | %-22s | %-3d | %-5d | %-5d | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e\n",
1828 (int)simCtx->step, (double)simCtx->ti, mode_str, has_reference ? 1 : 0,
1829 (int)phase_step, (int)simCtx->solutionConvergencePeriodSteps,
1830 (double)u_abs_l2, (double)u_rel_l2, (double)p_abs_l2, (double)p_rel_l2,
1831 (double)mean_speed, (double)mean_speed_reference,
1832 (double)mean_speed_abs_drift, (double)mean_speed_rel_drift,
1833 (double)mean_ke, (double)mean_ke_reference,
1834 (double)mean_ke_abs_drift, (double)mean_ke_rel_drift);
1835 break;
1837 fprintf(f,
1838 "%-10d | %-18.10e | %-22s | %-3d | %-5d | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e | %-18.10e\n",
1839 (int)simCtx->step, (double)simCtx->ti, mode_str, has_reference ? 1 : 0,
1840 (int)simCtx->solutionConvergenceWindowSteps,
1841 (double)mean_speed, (double)mean_ke,
1842 (double)mean_speed_window, (double)mean_speed_window_prev,
1843 (double)mean_speed_window_abs_drift, (double)mean_speed_window_rel_drift,
1844 (double)mean_speed_rms_window,
1845 (double)mean_speed_rms_window_abs_drift, (double)mean_speed_rms_window_rel_drift,
1846 (double)mean_ke_window, (double)mean_ke_window_prev,
1847 (double)mean_ke_window_abs_drift, (double)mean_ke_window_rel_drift,
1848 (double)mean_ke_rms_window,
1849 (double)mean_ke_rms_window_abs_drift, (double)mean_ke_rms_window_rel_drift);
1850 break;
1851 default: break;
1852 }
1853 fclose(f);
1854 }
1855
1857 phase_step >= 0 && phase_step < simCtx->solutionConvergencePeriodSteps) {
1858 UserCtx *user = simCtx->usermg.mgctx[simCtx->usermg.mglevels - 1].user;
1859 for (PetscInt bi = 0; bi < simCtx->block_number; ++bi) {
1860 PetscCall(VecCopy(user[bi].Ucat, user[bi].solutionConvergencePeriodicUcatRef[phase_step]));
1861 PetscCall(VecCopy(user[bi].P, user[bi].solutionConvergencePeriodicPRef[phase_step]));
1862 }
1863 }
1864
1865 simCtx->solutionConvergenceSamplesRecorded = samples_before + 1;
1866
1867 PetscFunctionReturn(0);
1868}
1869
1870/**
1871 * @brief Logs continuity metrics for a single block to a file.
1872 *
1873 * This function should be called for each block, once per timestep. It opens a
1874 * central log file in append mode. To ensure the header is written only once,
1875 * it checks if it is processing block 0 on the simulation's start step.
1876 *
1877 * @param user A pointer to the UserCtx for the specific block whose metrics
1878 * are to be logged. The function accesses both global (SimCtx)
1879 * and local (user->...) data.
1880 * @return PetscErrorCode 0 on success.
1881 */
1882#undef __FUNCT__
1883#define __FUNCT__ "LOG_CONTINUITY_METRICS"
1884/**
1885 * @brief Implementation of \ref LOG_CONTINUITY_METRICS().
1886 * @details Full API contract (arguments, ownership, side effects) is documented with
1887 * the header declaration in `include/logging.h`.
1888 * @see LOG_CONTINUITY_METRICS()
1889 */
1890
1891PetscErrorCode LOG_CONTINUITY_METRICS(UserCtx *user)
1892{
1893 PetscErrorCode ierr;
1894 PetscMPIInt rank;
1895 SimCtx *simCtx = user->simCtx; // Get the shared SimCtx
1896 const PetscInt bi = user->_this; // Get this block's specific ID
1897 const PetscInt ti = simCtx->step; // Get the current timestep
1898
1899 PetscFunctionBeginUser;
1900 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1901
1902 // Only rank 0 performs file I/O.
1903 if (!rank) {
1904 FILE *f;
1905 char filen[PETSC_MAX_PATH_LEN + 64];
1906 ierr = PetscSNPrintf(filen, sizeof(filen), "%s/Continuity_Metrics.log", simCtx->log_dir); CHKERRQ(ierr);
1907
1908 // Open the log file in append mode.
1909 f = fopen(filen, "a");
1910 if (!f) {
1911 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Cannot open log file: %s", filen);
1912 }
1913
1914 // Write a header only when the file is empty and it's the first block (bi=0).
1915 // Using ftell() instead of step comparison ensures correctness across continuations.
1916 if (ftell(f) == 0 && bi == 0) {
1917 PetscFPrintf(PETSC_COMM_SELF, f, "%-10s | %-6s | %-18s | %-30s | %-24s | %-18s | %-18s | %-18s\n",
1918 "Timestep", "Block", "Max Divergence", "Max Divergence Location ([k][j][i]=idx)", "Poisson Source Imbalance","Total Flux In", "Total Flux Out", "Net Flux");
1919 PetscFPrintf(PETSC_COMM_SELF, f, "------------------------------------------------------------------------------------------------------------------------------------------\n");
1920 }
1921 if (simCtx->continueMode && ti == simCtx->StartStep + 1 && bi == 0) {
1922 PetscFPrintf(PETSC_COMM_SELF, f, "# Continuation from step %" PetscInt_FMT "\n", simCtx->StartStep);
1923 }
1924
1925 // Prepare the data strings and values for the current block.
1926 PetscReal net_flux = simCtx->FluxInSum - simCtx->FluxOutSum;
1927 char location_str[64];
1928 sprintf(location_str, "([%d][%d][%d] = %d)", (int)simCtx->MaxDivz, (int)simCtx->MaxDivy, (int)simCtx->MaxDivx, (int)simCtx->MaxDivFlatArg);
1929
1930 // Write the formatted line for the current block.
1931 PetscFPrintf(PETSC_COMM_SELF, f, "%-10d | %-6d | %-18.10e | %-39s | %-24.10e | %-18.10e | %-18.10e | %-18.10e\n",
1932 (int)ti,
1933 (int)bi,
1934 (double)simCtx->MaxDiv,
1935 location_str,
1936 (double)simCtx->poissonSourceImbalance,
1937 (double)simCtx->FluxInSum,
1938 (double)simCtx->FluxOutSum,
1939 (double)net_flux);
1940
1941 fclose(f);
1942 }
1943
1944 PetscFunctionReturn(0);
1945}
1946
1947/**
1948 * @brief Implementation of \ref ParticleLocationStatusToString().
1949 * @details Full API contract (arguments, ownership, side effects) is documented with
1950 * the header declaration in `include/logging.h`.
1951 * @see ParticleLocationStatusToString()
1952 */
1954{
1955 switch (level) {
1956 case NEEDS_LOCATION: return "NEEDS_LOCATION";
1957 case ACTIVE_AND_LOCATED: return "ACTIVE_AND_LOCATED";
1958 case MIGRATING_OUT: return "MIGRATING_OUT";
1959 case LOST: return "LOST";
1960 case UNINITIALIZED: return "UNINITIALIZED";
1961 default: return "UNKNOWN_LEVEL";
1962 }
1963}
1964
1965///////// Profiling System /////////
1966
1967// Data structure to hold profiling info for one function
1968typedef struct {
1969 const char *name;
1974 double start_time; // Timer for the current call
1975 PetscBool always_log;
1977
1978// Global registry for all profiled functions
1980static PetscInt g_profiler_count = 0;
1981static PetscInt g_profiler_capacity = 0;
1982
1983// Internal helper to find a function in the registry or create it
1984/**
1985 * @brief Find a profiling record by name or allocate and register a new record.
1986 */
1987static PetscErrorCode _FindOrCreateEntry(const char *func_name, PetscInt *idx)
1988{
1989 PetscFunctionBeginUser;
1990 // Search for existing entry
1991 for (PetscInt i = 0; i < g_profiler_count; ++i) {
1992 if (strcmp(g_profiler_registry[i].name, func_name) == 0) {
1993 *idx = i;
1994 PetscFunctionReturn(0);
1995 }
1996 }
1997
1998 // Not found, create a new entry
2000 PetscInt new_capacity = g_profiler_capacity == 0 ? 16 : g_profiler_capacity * 2;
2001 PetscErrorCode ierr = PetscRealloc(sizeof(ProfiledFunction) * new_capacity, &g_profiler_registry); CHKERRQ(ierr);
2002 g_profiler_capacity = new_capacity;
2003 }
2004
2005 *idx = g_profiler_count;
2006 g_profiler_registry[*idx].name = func_name;
2007 g_profiler_registry[*idx].total_time = 0.0;
2011 g_profiler_registry[*idx].start_time = 0.0;
2012 g_profiler_registry[*idx].always_log = PETSC_FALSE;
2014
2015 PetscFunctionReturn(0);
2016}
2017
2018// --- Public API Implementation ---
2019/**
2020 * @brief Internal helper implementation: `ProfilingInitialize()`.
2021 * @details Local to this translation unit.
2022 */
2023PetscErrorCode ProfilingInitialize(SimCtx *simCtx)
2024{
2025 PetscFunctionBeginUser;
2026 if (!simCtx) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "SimCtx cannot be null for ProfilingInitialize");
2027
2028 // Iterate through the list of critical functions provided in SimCtx
2029 for (PetscInt i = 0; i < simCtx->nProfilingSelectedFuncs; ++i) {
2030 PetscInt idx;
2031 const char *func_name = simCtx->profilingSelectedFuncs[i];
2032 PetscErrorCode ierr = _FindOrCreateEntry(func_name, &idx); CHKERRQ(ierr);
2033 g_profiler_registry[idx].always_log = PETSC_TRUE;
2034
2035 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Marked '%s' as a critical function for profiling.\n", func_name);
2036 }
2037 PetscFunctionReturn(0);
2038}
2039
2040/**
2041 * @brief Implementation of \ref _ProfilingStart().
2042 * @details Full API contract (arguments, ownership, side effects) is documented with
2043 * the header declaration in `include/logging.h`.
2044 * @see _ProfilingStart()
2045 */
2046
2047void _ProfilingStart(const char *func_name)
2048{
2049 PetscInt idx;
2050 if (_FindOrCreateEntry(func_name, &idx) != 0) return; // Fail silently
2051 PetscTime(&g_profiler_registry[idx].start_time);
2052}
2053
2054/**
2055 * @brief Implementation of \ref _ProfilingEnd().
2056 * @details Full API contract (arguments, ownership, side effects) is documented with
2057 * the header declaration in `include/logging.h`.
2058 * @see _ProfilingEnd()
2059 */
2060
2061void _ProfilingEnd(const char *func_name)
2062{
2063 double end_time;
2064 PetscTime(&end_time);
2065
2066 PetscInt idx;
2067 if (_FindOrCreateEntry(func_name, &idx) != 0) return; // Fail silently
2068
2069 double elapsed = end_time - g_profiler_registry[idx].start_time;
2070 g_profiler_registry[idx].total_time += elapsed;
2071 g_profiler_registry[idx].current_step_time += elapsed;
2074}
2075
2076/**
2077 * @brief Implementation of \ref ProfilingResetTimestepCounters().
2078 * @details Full API contract (arguments, ownership, side effects) is documented with
2079 * the header declaration in `include/logging.h`.
2080 * @see ProfilingResetTimestepCounters()
2081 */
2082
2084{
2085 PetscFunctionBeginUser;
2086 for (PetscInt i = 0; i < g_profiler_count; ++i) {
2089 }
2090 PetscFunctionReturn(0);
2091}
2092
2093/**
2094 * @brief Implementation of \ref ProfilingLogTimestepSummary().
2095 * @details Full API contract (arguments, ownership, side effects) is documented with
2096 * the header declaration in `include/logging.h`.
2097 * @see ProfilingLogTimestepSummary()
2098 */
2099
2100PetscErrorCode ProfilingLogTimestepSummary(SimCtx *simCtx, PetscInt step)
2101{
2102 PetscBool should_write = PETSC_FALSE;
2103 FILE *f = NULL;
2104 char filen[(2 * PETSC_MAX_PATH_LEN) + 16];
2105
2106 PetscFunctionBeginUser;
2107 if (!simCtx) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "SimCtx cannot be null for ProfilingLogTimestepSummary");
2108
2109 if (strcmp(simCtx->profilingTimestepMode, "off") == 0) {
2110 for (PetscInt i = 0; i < g_profiler_count; ++i) {
2113 }
2114 PetscFunctionReturn(0);
2115 }
2116
2117 for (PetscInt i = 0; i < g_profiler_count; ++i) {
2118 if (g_profiler_registry[i].current_step_call_count <= 0) {
2119 continue;
2120 }
2121 if (strcmp(simCtx->profilingTimestepMode, "all") == 0 || g_profiler_registry[i].always_log) {
2122 should_write = PETSC_TRUE;
2123 break;
2124 }
2125 }
2126
2127 /* A selected name that matches no instrumented function records nothing, and a
2128 list made only of such names writes no file at all. Say so once, on the first
2129 step, rather than leave an absent file to be discovered after the run. */
2130 if (step == simCtx->StartStep + 1 && strcmp(simCtx->profilingTimestepMode, "selected") == 0) {
2131 for (PetscInt i = 0; i < g_profiler_count; ++i) {
2132 if (g_profiler_registry[i].always_log && g_profiler_registry[i].total_call_count == 0) {
2134 "profiling.timestep_output.functions lists '%s', which recorded no call in the first "
2135 "step. It is not an instrumented function; the final summary lists the names that are.\n",
2136 g_profiler_registry[i].name);
2137 }
2138 }
2139 }
2140
2141 if (should_write && simCtx->rank == 0) {
2142 snprintf(filen, sizeof(filen), "%s/%s", simCtx->log_dir, simCtx->profilingTimestepFile);
2143 if (step == simCtx->StartStep + 1 && !simCtx->continueMode) {
2144 f = fopen(filen, "w");
2145 if (!f) {
2146 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Cannot open profiling timestep log file: %s", filen);
2147 }
2148 PetscFPrintf(PETSC_COMM_SELF, f, "step,function,calls,step_time_s\n");
2149 } else {
2150 f = fopen(filen, "a");
2151 if (!f) {
2152 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Cannot open profiling timestep log file: %s", filen);
2153 }
2154 if (step == simCtx->StartStep + 1 && ftell(f) == 0) {
2155 PetscFPrintf(PETSC_COMM_SELF, f, "step,function,calls,step_time_s\n");
2156 }
2157 }
2158 if (simCtx->continueMode && step == simCtx->StartStep + 1) {
2159 PetscFPrintf(PETSC_COMM_SELF, f, "# Continuation from step %" PetscInt_FMT "\n", simCtx->StartStep);
2160 }
2161
2162 for (PetscInt i = 0; i < g_profiler_count; ++i) {
2163 if (g_profiler_registry[i].current_step_call_count <= 0) {
2164 continue;
2165 }
2166 if (strcmp(simCtx->profilingTimestepMode, "all") == 0 || g_profiler_registry[i].always_log) {
2167 PetscFPrintf(
2168 PETSC_COMM_SELF,
2169 f,
2170 "%d,%s,%lld,%.6f\n",
2171 (int)step,
2172 g_profiler_registry[i].name,
2173 g_profiler_registry[i].current_step_call_count,
2174 g_profiler_registry[i].current_step_time
2175 );
2176 }
2177 }
2178 fclose(f);
2179 }
2180
2181 // Reset per-step counters for the next iteration
2182 for (PetscInt i = 0; i < g_profiler_count; ++i) {
2185 }
2186 PetscFunctionReturn(0);
2187}
2188
2189/**
2190 * @brief Implementation of \ref RuntimeMemoryLogSample().
2191 * @details Full API contract (arguments, ownership, side effects) is documented with
2192 * the header declaration in `include/logging.h`.
2193 * @see RuntimeMemoryLogSample()
2194 */
2195PetscErrorCode RuntimeMemoryLogSample(SimCtx *simCtx, PetscInt step, const char *event, const char *reason)
2196{
2197 PetscErrorCode ierr;
2198 PetscLogDouble process_current_bytes = 0.0;
2199 PetscLogDouble process_peak_bytes = 0.0;
2200 PetscLogDouble petsc_current_bytes = 0.0;
2201 PetscLogDouble petsc_peak_bytes = 0.0;
2202 PetscReal local_values[5];
2203 PetscReal global_values[5];
2204 PetscReal process_current_mb = 0.0;
2205 PetscReal process_peak_mb = 0.0;
2206 PetscReal petsc_current_mb = 0.0;
2207 PetscReal petsc_peak_mb = 0.0;
2208 PetscReal process_change_mb = 0.0;
2209 char path[(2 * PETSC_MAX_PATH_LEN) + 16];
2210 FILE *f = NULL;
2211
2212 PetscFunctionBeginUser;
2213 if (!simCtx) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "SimCtx cannot be null for RuntimeMemoryLogSample");
2214 if (!simCtx->runtimeMemoryLogEnabled) PetscFunctionReturn(0);
2215
2216 ierr = PetscMemoryGetCurrentUsage(&process_current_bytes); CHKERRQ(ierr);
2217 ierr = PetscMemoryGetMaximumUsage(&process_peak_bytes); CHKERRQ(ierr);
2218 ierr = PetscMallocGetCurrentUsage(&petsc_current_bytes); CHKERRQ(ierr);
2219 ierr = PetscMallocGetMaximumUsage(&petsc_peak_bytes); CHKERRQ(ierr);
2220
2221 process_current_mb = (PetscReal)(process_current_bytes / (1024.0 * 1024.0));
2222 process_peak_mb = (PetscReal)(process_peak_bytes / (1024.0 * 1024.0));
2223 petsc_current_mb = (PetscReal)(petsc_current_bytes / (1024.0 * 1024.0));
2224 petsc_peak_mb = (PetscReal)(petsc_peak_bytes / (1024.0 * 1024.0));
2225 if (simCtx->runtimeMemoryLogHasPrevious) {
2226 process_change_mb = process_current_mb - simCtx->runtimeMemoryLogPreviousProcessMB;
2227 }
2228
2229 local_values[0] = process_current_mb;
2230 local_values[1] = process_peak_mb;
2231 local_values[2] = petsc_current_mb;
2232 local_values[3] = petsc_peak_mb;
2233 local_values[4] = process_change_mb;
2234 ierr = MPI_Allreduce(local_values, global_values, 5, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD); CHKERRMPI(ierr);
2235
2236 simCtx->runtimeMemoryLogPreviousProcessMB = process_current_mb;
2237 simCtx->runtimeMemoryLogHasPrevious = PETSC_TRUE;
2238
2239 if (simCtx->rank == 0) {
2240 ierr = PetscSNPrintf(path, sizeof(path), "%s/%s", simCtx->log_dir, simCtx->runtimeMemoryLogFile); CHKERRQ(ierr);
2241 f = fopen(path, "a");
2242 if (!f) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Cannot open runtime memory log file: %s", path);
2243
2244 if (!simCtx->runtimeMemoryLogStarted) {
2245 fprintf(f, "# PICurv runtime memory log\n");
2246 if (simCtx->continueMode) {
2247 fprintf(f, "# Continuation from step %" PetscInt_FMT "\n", simCtx->StartStep);
2248 }
2249 fprintf(
2250 f,
2251 "%-8s %-10s %22s %20s %22s %28s %22s %-18s\n",
2252 "Step",
2253 "Event",
2254 "Process Current MB Max",
2255 "Process Peak MB Max",
2256 "PETSc Allocated MB Max",
2257 "PETSc Peak Allocated MB Max",
2258 "Process Change MB Max",
2259 "Reason"
2260 );
2261 simCtx->runtimeMemoryLogStarted = PETSC_TRUE;
2262 }
2263
2264 fprintf(
2265 f,
2266 "%-8" PetscInt_FMT " %-10s %22.3f %20.3f %22.3f %28.3f %22.3f %-18s\n",
2267 step,
2268 event ? event : "-",
2269 (double)global_values[0],
2270 (double)global_values[1],
2271 (double)global_values[2],
2272 (double)global_values[3],
2273 (double)global_values[4],
2274 (reason && reason[0]) ? reason : "-"
2275 );
2276 if ((event && (strcmp(event, "Shutdown") == 0 || strcmp(event, "Final") == 0))) {
2277 fflush(f);
2278 }
2279 fclose(f);
2280 }
2281
2282 PetscFunctionReturn(0);
2283}
2284
2285// Comparison function for qsort to sort by total_time in descending order
2286/**
2287 * @brief Order profiling records by their accumulated execution time.
2288 */
2289static int _CompareProfiledFunctions(const void *a, const void *b)
2290{
2291 const ProfiledFunction *func_a = (const ProfiledFunction *)a;
2292 const ProfiledFunction *func_b = (const ProfiledFunction *)b;
2293
2294 if (func_a->total_time < func_b->total_time) return 1;
2295 if (func_a->total_time > func_b->total_time) return -1;
2296 return 0;
2297}
2298
2299/**
2300 * @brief Implementation of \ref ProfilingFinalize().
2301 * @details Full API contract (arguments, ownership, side effects) is documented with
2302 * the header declaration in `include/logging.h`.
2303 * @see ProfilingFinalize()
2304 */
2305PetscErrorCode ProfilingFinalize(SimCtx *simCtx)
2306{
2307 PetscErrorCode ierr;
2308 PetscInt rank = simCtx->rank;
2309 PetscFunctionBeginUser;
2310 if (!simCtx->profilingFinalSummary) PetscFunctionReturn(0);
2311 if (!rank) {
2312
2313 char exec_mode_modifier[32] = "Unknown";
2314 if(simCtx->exec_mode == EXEC_MODE_SOLVER) PetscCall(PetscStrncpy(exec_mode_modifier, "Solver", sizeof(exec_mode_modifier)));
2315 else if(simCtx->exec_mode == EXEC_MODE_POSTPROCESSOR) PetscCall(PetscStrncpy(exec_mode_modifier, "PostProcessor", sizeof(exec_mode_modifier)));
2316 //--- Step 0: Create a file viewer for log file
2317 FILE *f;
2318 char filen[PETSC_MAX_PATH_LEN + 128];
2319 ierr = PetscSNPrintf(filen, sizeof(filen), "%s/ProfilingSummary_%s.log",simCtx->log_dir,exec_mode_modifier); CHKERRQ(ierr);
2320
2321 // Open the log file: append with section label in continue mode, truncate otherwise.
2322 if (simCtx->continueMode) {
2323 f = fopen(filen, "a");
2324 if (!f) {
2325 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Cannot open log file: %s", filen);
2326 }
2327 fprintf(f, "\n=== Continuation from step %" PetscInt_FMT " ===\n", simCtx->StartStep);
2328 } else {
2329 f = fopen(filen, "w");
2330 if (!f) {
2331 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Cannot open log file: %s", filen);
2332 }
2333 }
2334
2335 // --- Step 1: Sort the data for readability ---
2337
2338 // --- Step 2: Dynamically determine the width for the function name column ---
2339 PetscInt max_name_len = strlen("Function"); // Start with the header's length
2340 for (PetscInt i = 0; i < g_profiler_count; ++i) {
2341 if (g_profiler_registry[i].total_call_count > 0) {
2342 PetscInt len = strlen(g_profiler_registry[i].name);
2343 if (len > max_name_len) {
2344 max_name_len = len;
2345 }
2346 }
2347 }
2348 // Add a little padding
2349 max_name_len += 2;
2350
2351 // --- Step 3: Define fixed widths for numeric columns for consistent alignment ---
2352 const int time_width = 18;
2353 const int count_width = 15;
2354 const int avg_width = 22;
2355
2356 // --- Step 4: Print the formatted table ---
2357 PetscFPrintf(PETSC_COMM_SELF, f, "=================================================================================================================\n");
2358 PetscFPrintf(PETSC_COMM_SELF, f, " FINAL PROFILING SUMMARY (Sorted by Total Time)\n");
2359 PetscFPrintf(PETSC_COMM_SELF, f, "=================================================================================================================\n");
2360
2361 // Header Row
2362 PetscFPrintf(PETSC_COMM_SELF, f, "%-*s | %-*s | %-*s | %-*s\n",
2363 max_name_len, "Function",
2364 time_width, "Total Time (s)",
2365 count_width, "Call Count",
2366 avg_width, "Avg. Time/Call (ms)");
2367
2368 // Separator Line (dynamically sized)
2369 for (int i = 0; i < max_name_len; i++) PetscFPrintf(PETSC_COMM_SELF, f, "-");
2370 PetscFPrintf(PETSC_COMM_SELF, f, "-|-");
2371 for (int i = 0; i < time_width; i++) PetscFPrintf(PETSC_COMM_SELF, f, "-");
2372 PetscFPrintf(PETSC_COMM_SELF, f, "-|-");
2373 for (int i = 0; i < count_width; i++) PetscFPrintf(PETSC_COMM_SELF, f, "-");
2374 PetscFPrintf(PETSC_COMM_SELF, f, "-|-");
2375 for (int i = 0; i < avg_width; i++) PetscFPrintf(PETSC_COMM_SELF, f, "-");
2376 PetscFPrintf(PETSC_COMM_SELF, f, "\n");
2377
2378 // Data Rows
2379 for (PetscInt i = 0; i < g_profiler_count; ++i) {
2380 if (g_profiler_registry[i].total_call_count > 0) {
2381 double avg_time_ms = (g_profiler_registry[i].total_time / g_profiler_registry[i].total_call_count) * 1000.0;
2382 PetscFPrintf(PETSC_COMM_SELF, f, "%-*s | %*.*f | %*lld | %*.*f\n",
2383 max_name_len, g_profiler_registry[i].name,
2384 time_width, 6, g_profiler_registry[i].total_time,
2385 count_width, g_profiler_registry[i].total_call_count,
2386 avg_width, 6, avg_time_ms);
2387 PetscFPrintf(PETSC_COMM_SELF, f, "------------------------------------------------------------------------------------------------------------------\n");
2388 }
2389 }
2390 PetscFPrintf(PETSC_COMM_SELF, f, "==================================================================================================================\n");
2391
2392 fclose(f);
2393 }
2394
2395 // --- Final Cleanup ---
2396 PetscFree(g_profiler_registry);
2397 g_profiler_registry = NULL;
2398 g_profiler_count = 0;
2400 PetscFunctionReturn(0);
2401}
2402
2403/*================================================================================*
2404 * PROGRESS BAR UTILITY *
2405 *================================================================================*/
2406
2407/**
2408 * @brief Internal helper implementation: `PrintProgressBar()`.
2409 * @details Local to this translation unit.
2410 */
2411void PrintProgressBar(PetscInt step, PetscInt startStep, PetscInt totalSteps, PetscReal currentTime)
2412{
2413 if (totalSteps <= 0) return;
2414
2415 // --- Configuration ---
2416 const int barWidth = 50;
2417
2418 // --- Calculation ---
2419 // Calculate progress as a fraction from 0.0 to 1.0
2420 PetscReal progress = (PetscReal)(step - startStep + 1) / totalSteps;
2421 // Ensure progress doesn't exceed 1.0 due to floating point inaccuracies
2422 if (progress > 1.0) progress = 1.0;
2423
2424 int pos = (int)(barWidth * progress);
2425
2426 // --- Printing ---
2427 // Carriage return moves cursor to the beginning of the line
2428 PetscPrintf(PETSC_COMM_SELF, "\rProgress: [");
2429
2430 for (int i = 0; i < barWidth; ++i) {
2431 if (i < pos) {
2432 PetscPrintf(PETSC_COMM_SELF, "=");
2433 } else if (i == pos) {
2434 PetscPrintf(PETSC_COMM_SELF, ">");
2435 } else {
2436 PetscPrintf(PETSC_COMM_SELF, " ");
2437 }
2438 }
2439
2440 // Print percentage, step count, and current time
2441 PetscPrintf(PETSC_COMM_SELF, "] %3d%% (Step %" PetscInt_FMT "/%" PetscInt_FMT ", t=%.4f)",
2442 (int)(progress * 100.0),
2443 step + 1,
2444 startStep + totalSteps,
2445 currentTime);
2446
2447 // Flush the output buffer to ensure the bar is displayed immediately
2448 fflush(stdout);
2449}
2450
2451#undef __FUNCT__
2452#define __FUNCT__ "LOG_FIELD_MIN_MAX"
2453/**
2454 * @brief Implementation of \ref LOG_FIELD_MIN_MAX().
2455 * @details Full API contract is documented with the header declaration in `include/logging.h`.
2456 * @see LOG_FIELD_MIN_MAX()
2457 */
2458PetscErrorCode LOG_FIELD_MIN_MAX(UserCtx *user, FieldId field_id)
2459{
2460 PetscErrorCode ierr;
2461 PetscInt i, j, k;
2462 DMDALocalInfo info;
2463
2464 FieldView view;
2465 Vec fieldVec = NULL;
2466 DM dm = NULL;
2467 PetscInt dof;
2468 FieldLayout layout;
2469 const char *fieldName = NULL;
2470 const char *data_layout = NULL;
2471
2472 PetscFunctionBeginUser;
2473
2474 ierr = FieldGetView(user, field_id, &view); CHKERRQ(ierr);
2475 fieldName = view.descriptor->canonical_name;
2476 dm = view.dm;
2477 dof = view.descriptor->dof;
2478 layout = view.descriptor->layout;
2479 data_layout = FieldLayoutName(layout);
2480 fieldVec = (layout == FIELD_LAYOUT_COMPONENT_STAGGERED) ? view.local_vec : view.global_vec;
2481
2482 ierr = DMDAGetLocalInfo(dm, &info); CHKERRQ(ierr);
2483
2484 // --- 2. Define Architecture-Aware Loop Bounds ---
2485 PetscInt i_start, i_end, j_start, j_end, k_start, k_end;
2486
2487 if (layout == FIELD_LAYOUT_CELL_CENTERED) {
2488 // For cell-centered data, the physical values are stored from index 1 to N-1.
2489 // We find the intersection of the rank's owned range [xs, xe) with the
2490 // physical data range [1, IM-1).
2491 i_start = PetscMax(info.xs, 1); i_end = PetscMin(info.xs + info.xm, user->IM);
2492 j_start = PetscMax(info.ys, 1); j_end = PetscMin(info.ys + info.ym, user->JM);
2493 k_start = PetscMax(info.zs, 1); k_end = PetscMin(info.zs + info.zm, user->KM);
2494 } else { // For Node- or Face-Centered data
2495 // The physical values are stored from index 0 to N-1.
2496 // We find the intersection of the rank's owned range [xs, xe) with the
2497 // physical data range [0, IM-1].
2498 i_start = PetscMax(info.xs, 0); i_end = PetscMin(info.xs + info.xm, user->IM);
2499 j_start = PetscMax(info.ys, 0); j_end = PetscMin(info.ys + info.ym, user->JM);
2500 k_start = PetscMax(info.zs, 0); k_end = PetscMin(info.zs + info.zm, user->KM);
2501 }
2502
2503 // --- 3. Barrier for clean, grouped output ---
2504 ierr = MPI_Barrier(PETSC_COMM_WORLD); CHKERRQ(ierr);
2505 if (user->simCtx->rank == 0) {
2506 PetscPrintf(PETSC_COMM_SELF, "\n--- Field Ranges: [%s] (Layout: %s) ---\n", fieldName, data_layout);
2507 }
2508
2509 // --- 4. Branch on DoF and perform calculation with correct bounds ---
2510 if (dof == 1) {
2511 PetscReal localMin = PETSC_MAX_REAL, localMax = PETSC_MIN_REAL;
2512 PetscReal globalMin, globalMax;
2513 const PetscScalar ***array;
2514
2515 ierr = DMDAVecGetArrayRead(dm, fieldVec, &array); CHKERRQ(ierr);
2516 for (k = k_start; k < k_end; k++) {
2517 for (j = j_start; j < j_end; j++) {
2518 for (i = i_start; i < i_end; i++) {
2519 localMin = PetscMin(localMin, array[k][j][i]);
2520 localMax = PetscMax(localMax, array[k][j][i]);
2521 }
2522 }
2523 }
2524 ierr = DMDAVecRestoreArrayRead(dm, fieldVec, &array); CHKERRQ(ierr);
2525
2526 ierr = MPI_Allreduce(&localMin, &globalMin, 1, MPIU_REAL, MPI_MIN, PETSC_COMM_WORLD); CHKERRQ(ierr);
2527 ierr = MPI_Allreduce(&localMax, &globalMax, 1, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD); CHKERRQ(ierr);
2528
2529 PetscSynchronizedPrintf(PETSC_COMM_WORLD, " [Rank %d] Local Range: [ %11.4e , %11.4e ]\n", user->simCtx->rank, localMin, localMax);
2530 ierr = PetscSynchronizedFlush(PETSC_COMM_WORLD, PETSC_STDOUT); CHKERRQ(ierr);
2531 if (user->simCtx->rank == 0) {
2532 PetscPrintf(PETSC_COMM_SELF, " Global Range: [ %11.4e , %11.4e ]\n", globalMin, globalMax);
2533 }
2534
2535 } else if (dof == 3) {
2536 Cmpnts localMin = {PETSC_MAX_REAL, PETSC_MAX_REAL, PETSC_MAX_REAL};
2537 Cmpnts localMax = {PETSC_MIN_REAL, PETSC_MIN_REAL, PETSC_MIN_REAL};
2538 Cmpnts globalMin, globalMax;
2539 const Cmpnts ***array;
2540
2541 ierr = DMDAVecGetArrayRead(dm, fieldVec, &array); CHKERRQ(ierr);
2542 for (k = k_start; k < k_end; k++) {
2543 for (j = j_start; j < j_end; j++) {
2544 for (i = i_start; i < i_end; i++) {
2545 localMin.x = PetscMin(localMin.x, array[k][j][i].x);
2546 localMin.y = PetscMin(localMin.y, array[k][j][i].y);
2547 localMin.z = PetscMin(localMin.z, array[k][j][i].z);
2548 localMax.x = PetscMax(localMax.x, array[k][j][i].x);
2549 localMax.y = PetscMax(localMax.y, array[k][j][i].y);
2550 localMax.z = PetscMax(localMax.z, array[k][j][i].z);
2551 }
2552 }
2553 }
2554 ierr = DMDAVecRestoreArrayRead(dm, fieldVec, &array); CHKERRQ(ierr);
2555
2556 ierr = MPI_Allreduce(&localMin, &globalMin, 3, MPIU_REAL, MPI_MIN, PETSC_COMM_WORLD); CHKERRQ(ierr);
2557 ierr = MPI_Allreduce(&localMax, &globalMax, 3, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD); CHKERRQ(ierr);
2558
2559 ierr = PetscSynchronizedPrintf(PETSC_COMM_WORLD, " [Rank %d] Local X-Range: [ %11.4e , %11.4e ]\n", user->simCtx->rank, localMin.x, localMax.x);
2560 ierr = PetscSynchronizedPrintf(PETSC_COMM_WORLD, " [Rank %d] Local Y-Range: [ %11.4e , %11.4e ]\n", user->simCtx->rank, localMin.y, localMax.y);
2561 ierr = PetscSynchronizedPrintf(PETSC_COMM_WORLD, " [Rank %d] Local Z-Range: [ %11.4e , %11.4e ]\n", user->simCtx->rank, localMin.z, localMax.z);
2562 ierr = PetscSynchronizedFlush(PETSC_COMM_WORLD, PETSC_STDOUT); CHKERRQ(ierr);
2563
2564 if (user->simCtx->rank == 0) {
2565 PetscPrintf(PETSC_COMM_SELF, " [Global] X-Range: [ %11.4e , %11.4e ]\n", globalMin.x, globalMax.x);
2566 PetscPrintf(PETSC_COMM_SELF, " [Global] Y-Range: [ %11.4e , %11.4e ]\n", globalMin.y, globalMax.y);
2567 PetscPrintf(PETSC_COMM_SELF, " [Global] Z-Range: [ %11.4e , %11.4e ]\n", globalMin.z, globalMax.z);
2568 }
2569
2570 } else {
2571 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG, "LogFieldStatistics only supports fields with 1 or 3 components, but field '%s' has %" PetscInt_FMT ".", fieldName, dof);
2572 }
2573
2574 // --- 5. Final barrier for clean output ordering ---
2575 ierr = MPI_Barrier(PETSC_COMM_WORLD); CHKERRQ(ierr);
2576 if (user->simCtx->rank == 0) {
2577 PetscPrintf(PETSC_COMM_SELF, "--------------------------------------------\n\n");
2578 }
2579
2580 PetscFunctionReturn(0);
2581}
2582
2583#undef __FUNCT__
2584#define __FUNCT__ "LogFieldAnatomyView"
2585/**
2586 * @brief Shared architecture-aware anatomy logger for catalog and transient fields.
2587 */
2588static PetscErrorCode LogFieldAnatomyView(UserCtx *user, const char *field_name,
2589 const char *stage_name, DM dm,
2590 Vec vec_local, PetscInt dof,
2591 FieldLayout layout)
2592{
2593 PetscErrorCode ierr;
2594 DMDALocalInfo info;
2595 PetscMPIInt rank;
2596 const char *data_layout = FieldLayoutName(layout);
2597 char dominant_dir = '\0'; // 'x', 'y', 'z' for face-centered, 'm' for mixed (Ucont)
2598
2599 PetscFunctionBeginUser;
2600 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
2601
2602 PetscCheck(user != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx cannot be NULL.");
2603 PetscCheck(field_name != NULL && stage_name != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
2604 "Field and stage labels cannot be NULL.");
2605 PetscCheck(dm != NULL && vec_local != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
2606 "Field '%s' has no available DM/local vector for anatomy logging.", field_name);
2607 PetscCheck(dof == 1 || dof == 3, PETSC_COMM_SELF, PETSC_ERR_SUP,
2608 "Field anatomy logging supports one- or three-component fields; '%s' has %d.",
2609 field_name, dof);
2610
2611 if (layout == FIELD_LAYOUT_I_FACE) dominant_dir = 'x';
2612 else if (layout == FIELD_LAYOUT_J_FACE) dominant_dir = 'y';
2613 else if (layout == FIELD_LAYOUT_K_FACE) dominant_dir = 'z';
2614 else if (layout == FIELD_LAYOUT_COMPONENT_STAGGERED) dominant_dir = 'm';
2615
2616 // --- 2. Get Grid Info and Array Pointers ---
2617 ierr = DMDAGetLocalInfo(dm, &info); CHKERRQ(ierr);
2618
2619 ierr = PetscBarrier(NULL);
2620 PetscPrintf(PETSC_COMM_WORLD, "\n--- Field Anatomy Log: [%s] | Stage: [%s] | Layout: [%s] ---\n", field_name, stage_name, data_layout);
2621
2622 // Global physical dimensions (number of cells)
2623 PetscInt im_phys = user->IM;
2624 PetscInt jm_phys = user->JM;
2625 PetscInt km_phys = user->KM;
2626
2627 // Slice through the center of the local domain
2628 PetscInt i_mid = (PetscInt)(info.xs + 0.5 * info.xm) - 1;
2629 PetscInt j_mid = (PetscInt)(info.ys + 0.5 * info.ym) - 1;
2630 PetscInt k_mid = (PetscInt)(info.zs + 0.5 * info.zm) - 1;
2631
2632 // --- 3. Print Boundary Information based on Data Layout ---
2633
2634 // ======================================================================
2635 // === CASE 1: Cell-Centered Fields (Ucat, P) - USES SHIFTED INDEX ===
2636 // ======================================================================
2637 if (layout == FIELD_LAYOUT_CELL_CENTERED) {
2638 const void *l_arr;
2639 ierr = DMDAVecGetArrayRead(dm, vec_local, (void*)&l_arr); CHKERRQ(ierr);
2640
2641
2642 // --- I-Direction Boundaries ---
2643 if (info.xs == 0) { // Rank on -Xi boundary
2644 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, I-DIR]: Idx %2d (Ghost for Cell[k][j][0]) = ", rank, 0);
2645 if(dof==1) PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f)\n", ((const PetscReal***)l_arr)[k_mid][j_mid][0]);
2646 else PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f, %.5f, %.5f)\n", ((const Cmpnts***)l_arr)[k_mid][j_mid][0].x, ((const Cmpnts***)l_arr)[k_mid][j_mid][0].y, ((const Cmpnts***)l_arr)[k_mid][j_mid][0].z);
2647
2648 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, I-DIR]: Idx %2d (Value for Cell[k][j][0]) = ", rank, 1);
2649 if(dof==1) PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f)\n", ((const PetscReal***)l_arr)[k_mid][j_mid][1]);
2650 else PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f, %.5f, %.5f)\n", ((const Cmpnts***)l_arr)[k_mid][j_mid][1].x, ((const Cmpnts***)l_arr)[k_mid][j_mid][1].y, ((const Cmpnts***)l_arr)[k_mid][j_mid][1].z);
2651 }
2652 if (info.xs + info.xm == info.mx) { // Rank on +Xi boundary
2653 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, I-DIR]: Idx %2d (Value for Cell[k][j][%d]) = ", rank, im_phys - 1, im_phys - 2);
2654 if(dof==1) PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f)\n", ((const PetscReal***)l_arr)[k_mid][j_mid][im_phys - 1]);
2655 else PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f, %.5f, %.5f)\n", ((const Cmpnts***)l_arr)[k_mid][j_mid][im_phys - 1].x, ((const Cmpnts***)l_arr)[k_mid][j_mid][im_phys - 1].y, ((const Cmpnts***)l_arr)[k_mid][j_mid][im_phys - 1].z);
2656
2657 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, I-DIR]: Idx %2d (Ghost for Cell[k][j][%d]) = ", rank, im_phys, im_phys - 2);
2658 if(dof==1) PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f)\n", ((const PetscReal***)l_arr)[k_mid][j_mid][im_phys]);
2659 else PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f, %.5f, %.5f)\n", ((const Cmpnts***)l_arr)[k_mid][j_mid][im_phys].x, ((const Cmpnts***)l_arr)[k_mid][j_mid][im_phys].y, ((const Cmpnts***)l_arr)[k_mid][j_mid][im_phys].z);
2660 }
2661
2662 // --- J-Direction Boundaries ---
2663 if (info.ys == 0) { // Rank on -Eta boundary
2664 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, J-DIR]: Jdx %2d (Ghost for Cell[k][0][i]) = ", rank, 0);
2665 if(dof==1) PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f)\n", ((const PetscReal***)l_arr)[k_mid][0][i_mid]);
2666 else PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f, %.5f, %.5f)\n", ((const Cmpnts***)l_arr)[k_mid][0][i_mid].x, ((const Cmpnts***)l_arr)[k_mid][0][i_mid].y, ((const Cmpnts***)l_arr)[k_mid][0][i_mid].z);
2667
2668 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, J-DIR]: Jdx %2d (Value for Cell[k][0][i]) = ", rank, 1);
2669 if(dof==1) PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f)\n", ((const PetscReal***)l_arr)[k_mid][1][i_mid]);
2670 else PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f, %.5f, %.5f)\n", ((const Cmpnts***)l_arr)[k_mid][1][i_mid].x, ((const Cmpnts***)l_arr)[k_mid][1][i_mid].y, ((const Cmpnts***)l_arr)[k_mid][1][i_mid].z);
2671 }
2672
2673 if (info.ys + info.ym == info.my) { // Rank on +Eta boundary
2674 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, J-DIR]: Jdx %2d (Value for Cell[k][%d][i]) = ", rank, jm_phys - 1, jm_phys - 2);
2675 if(dof==1) PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f)\n", ((const PetscReal***)l_arr)[k_mid][jm_phys - 1][i_mid]);
2676 else PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f, %.5f, %.5f)\n", ((const Cmpnts***)l_arr)[k_mid][jm_phys - 1][i_mid].x, ((const Cmpnts***)l_arr)[k_mid][jm_phys - 1][i_mid].y, ((const Cmpnts***)l_arr)[k_mid][jm_phys - 1][i_mid].z);
2677
2678 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, J-DIR]: Jdx %2d (Ghost for Cell[k][%d][i]) = ", rank, jm_phys, jm_phys - 2);
2679 if(dof==1) PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f)\n", ((const PetscReal***)l_arr)[k_mid][jm_phys][i_mid]);
2680 else PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f, %.5f, %.5f)\n", ((const Cmpnts***)l_arr)[k_mid][jm_phys][i_mid].x, ((const Cmpnts***)l_arr)[k_mid][jm_phys][i_mid].y, ((const Cmpnts***)l_arr)[k_mid][jm_phys][i_mid].z);
2681 }
2682
2683 // --- K-Direction Boundaries ---
2684 if (info.zs == 0) { // Rank on -Zeta boundary
2685 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, K-DIR]: Kdx %2d (Ghost for Cell[0][j][i]) = ", rank, 0);
2686 if(dof==1) PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f)\n", ((const PetscReal***)l_arr)[0][j_mid][i_mid]);
2687 else PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f, %.5f, %.5f)\n", ((const Cmpnts***)l_arr)[0][j_mid][i_mid].x, ((const Cmpnts***)l_arr)[0][j_mid][i_mid].y, ((const Cmpnts***)l_arr)[0][j_mid][i_mid].z);
2688 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, K-DIR]: Kdx %2d (Value for Cell[0][j][i]) = ", rank, 1);
2689 if(dof==1) PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f)\n", ((const PetscReal***)l_arr)[1][j_mid][i_mid]);
2690 else PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f, %.5f, %.5f)\n", ((const Cmpnts***)l_arr)[1][j_mid][i_mid].x, ((const Cmpnts***)l_arr)[1][j_mid][i_mid].y, ((const Cmpnts***)l_arr)[1][j_mid][i_mid].z);
2691 }
2692 if (info.zs + info.zm == info.mz) { // Rank on +Zeta boundary
2693 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, K-DIR]: Kdx %2d (Value for Cell[%d][j][i]) = ", rank, km_phys - 1, km_phys - 2);
2694 if(dof==1) PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f)\n", ((const PetscReal***)l_arr)[km_phys - 1][j_mid][i_mid]);
2695 else PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f, %.5f, %.5f)\n", ((const Cmpnts***)l_arr)[km_phys - 1][j_mid][i_mid].x, ((const Cmpnts***)l_arr)[km_phys - 1][j_mid][i_mid].y, ((const Cmpnts***)l_arr)[km_phys - 1][j_mid][i_mid].z);
2696 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, K-DIR]: Kdx %2d (Ghost for Cell[%d][j][i]) = ", rank, km_phys, km_phys - 2);
2697 if(dof==1) PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f)\n", ((const PetscReal***)l_arr)[km_phys][j_mid][i_mid]);
2698 else PetscSynchronizedPrintf(PETSC_COMM_WORLD, "(%.5f, %.5f, %.5f)\n", ((const Cmpnts***)l_arr)[km_phys][j_mid][i_mid].x, ((const Cmpnts***)l_arr)[km_phys][j_mid][i_mid].y, ((const Cmpnts***)l_arr)[km_phys][j_mid][i_mid].z);
2699 }
2700 ierr = DMDAVecRestoreArrayRead(dm, vec_local, (void*)&l_arr); CHKERRQ(ierr);
2701 }
2702 // ======================================================================
2703 // === CASE 2: Face-Centered Fields - NUANCED DIRECTIONAL LOGIC ===
2704 // ======================================================================
2705 else if (layout == FIELD_LAYOUT_I_FACE ||
2706 layout == FIELD_LAYOUT_J_FACE ||
2707 layout == FIELD_LAYOUT_K_FACE ||
2709 const Cmpnts ***l_arr;
2710 ierr = DMDAVecGetArrayRead(dm, vec_local, (void*)&l_arr); CHKERRQ(ierr);
2711
2712 // --- I-Direction Boundaries ---
2713 if (info.xs == 0) { // Rank on -Xi boundary
2714 if (dominant_dir == 'x') { // Node-like in I-dir
2715 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, I-DIR]: Idx %2d (First Phys. X-Face) = (%.5f, %.5f, %.5f)\n", rank, 0, l_arr[k_mid][j_mid][0].x, l_arr[k_mid][j_mid][0].y, l_arr[k_mid][j_mid][0].z);
2716 } else if (dominant_dir == 'y' || dominant_dir == 'z') { // Cell-like in I-dir
2717 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, I-DIR]: Idx %2d (Ghost for Cell[k][j][0]) = (%.5f, %.5f, %.5f)\n", rank, 0, l_arr[k_mid][j_mid][0].x, l_arr[k_mid][j_mid][0].y, l_arr[k_mid][j_mid][0].z);
2718 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, I-DIR]: Idx %2d (Value for Cell[k][j][0]) = (%.5f, %.5f, %.5f)\n", rank, 1, l_arr[k_mid][j_mid][1].x, l_arr[k_mid][j_mid][1].y, l_arr[k_mid][j_mid][1].z);
2719 } else if (dominant_dir == 'm') { // Ucont: Mixed
2720 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, I-DIR]: u-comp @ Idx %2d (1st X-Face) = %.5f\n", rank, 0, l_arr[k_mid][j_mid][0].x);
2721 }
2722 }
2723 if (info.xs + info.xm == info.mx) { // Rank on +Xi boundary
2724 if (dominant_dir == 'x') { // Node-like in I-dir
2725 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, I-DIR]: Idx %2d (Last Phys. X-Face) = (%.5f, %.5f, %.5f)\n", rank, im_phys - 1, l_arr[k_mid][j_mid][im_phys - 1].x, l_arr[k_mid][j_mid][im_phys-1].y, l_arr[k_mid][j_mid][im_phys - 1].z);
2726 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, I-DIR]: Idx %2d (Ghost Location) = (%.5f, %.5f, %.5f)\n", rank, im_phys, l_arr[k_mid][j_mid][im_phys].x, l_arr[k_mid][j_mid][im_phys].y, l_arr[k_mid][j_mid][im_phys].z);
2727 } else if (dominant_dir == 'y' || dominant_dir == 'z') { // Cell-like in I-dir
2728 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, I-DIR]: Idx %2d (Value for Cell[k][j][%d]) = (%.5f, %.5f, %.5f)\n", rank, im_phys - 1, im_phys - 2, l_arr[k_mid][j_mid][im_phys - 1].x, l_arr[k_mid][j_mid][im_phys - 1].y, l_arr[k_mid][j_mid][im_phys-1].z);
2729 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, I-DIR]: Idx %2d (Ghost for Cell[k][j][%d]) = (%.5f, %.5f, %.5f)\n", rank, im_phys, im_phys - 2, l_arr[k_mid][j_mid][im_phys].x, l_arr[k_mid][j_mid][im_phys].y, l_arr[k_mid][j_mid][im_phys].z);
2730 } else if (dominant_dir == 'm') { // Ucont: Mixed
2731 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, I-DIR]: u-comp @ Idx %2d (Last X-Face) = %.5f\n", rank, im_phys - 1, l_arr[k_mid][j_mid][im_phys - 1].x);
2732 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, I-DIR]: u-comp @ Idx %2d (Ghost Location) = %.5f\n", rank, im_phys, l_arr[k_mid][j_mid][im_phys].x);
2733 }
2734 }
2735
2736 // --- J-Direction Boundaries ---
2737 if (info.ys == 0) { // Rank on -Eta boundary
2738 if (dominant_dir == 'y') { // Node-like in J-dir
2739 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, J-DIR]: Jdx %2d (First Phys. Y-Face) = (%.5f, %.5f, %.5f)\n", rank, 0, l_arr[k_mid][0][i_mid].x, l_arr[k_mid][0][i_mid].y, l_arr[k_mid][0][i_mid].z);
2740 } else if (dominant_dir == 'x' || dominant_dir == 'z') { // Cell-like in J-dir
2741 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, J-DIR]: Jdx %2d (Ghost for Cell[k][0][i]) = (%.5f, %.5f, %.5f)\n", rank, 0, l_arr[k_mid][0][i_mid].x, l_arr[k_mid][0][i_mid].y, l_arr[k_mid][0][i_mid].z);
2742 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, J-DIR]: Jdx %2d (Value for Cell[k][0][i]) = (%.5f, %.5f, %.5f)\n", rank, 1, l_arr[k_mid][1][i_mid].x, l_arr[k_mid][1][i_mid].y, l_arr[k_mid][1][i_mid].z);
2743 } else if (dominant_dir == 'm') { // Ucont: Mixed
2744 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, J-DIR]: v-comp @ Jdx %2d (1st Y-Face) = %.5f\n", rank, 0, l_arr[k_mid][0][i_mid].y);
2745 }
2746 }
2747 if (info.ys + info.ym == info.my) { // Rank on +Eta boundary
2748 if (dominant_dir == 'y') { // Node-like in J-dir
2749 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, J-DIR]: Jdx %2d (Last Phys. Y-Face) = (%.5f, %.5f, %.5f)\n", rank, jm_phys - 1, l_arr[k_mid][jm_phys - 1][i_mid].x, l_arr[k_mid][jm_phys - 1][i_mid].y, l_arr[k_mid][jm_phys - 1][i_mid].z);
2750 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, J-DIR]: Jdx %2d (Ghost Location) = (%.5f, %.5f, %.5f)\n", rank, jm_phys, l_arr[k_mid][jm_phys][i_mid].x, l_arr[k_mid][jm_phys][i_mid].y, l_arr[k_mid][jm_phys][i_mid].z);
2751 } else if (dominant_dir == 'x' || dominant_dir == 'z') { // Cell-like in J-dir
2752 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, J-DIR]: Jdx %2d (Value for Cell[k][%d][i]) = (%.5f, %.5f, %.5f)\n", rank, jm_phys-1, jm_phys-2, l_arr[k_mid][jm_phys - 1][i_mid].x, l_arr[k_mid][jm_phys - 1][i_mid].y, l_arr[k_mid][jm_phys - 1][i_mid].z);
2753 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, J-DIR]: Jdx %2d (Ghost for Cell[k][%d][i]) = (%.5f, %.5f, %.5f)\n", rank, jm_phys, jm_phys-2, l_arr[k_mid][jm_phys][i_mid].x, l_arr[k_mid][jm_phys][i_mid].y, l_arr[k_mid][jm_phys][i_mid].z);
2754 } else if (dominant_dir == 'm') { // Ucont: Mixed
2755 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, J-DIR]: v-comp @ Jdx %2d (Last Y-Face) = %.5f\n", rank, jm_phys - 1, l_arr[k_mid][jm_phys - 1][i_mid].y);
2756 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, J-DIR]: v-comp @ Jdx %2d (Ghost Location) = %.5f\n", rank, jm_phys, l_arr[k_mid][jm_phys][i_mid].y);
2757 }
2758 }
2759
2760 // --- K-Direction Boundaries ---
2761 if (info.zs == 0) { // Rank on -Zeta boundary
2762 if (dominant_dir == 'z') { // Node-like in K-dir
2763 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, K-DIR]: Kdx %2d (First Phys. Z-Face) = (%.5f, %.5f, %.5f)\n", rank, 0, l_arr[0][j_mid][i_mid].x, l_arr[0][j_mid][i_mid].y, l_arr[0][j_mid][i_mid].z);
2764 } else if (dominant_dir == 'x' || dominant_dir == 'y') { // Cell-like in K-dir
2765 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, K-DIR]: Kdx %2d (Ghost for Cell[0][j][i]) = (%.5f, %.5f, %.5f)\n", rank, 0, l_arr[0][j_mid][i_mid].x, l_arr[0][j_mid][i_mid].y, l_arr[0][j_mid][i_mid].z);
2766 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, K-DIR]: Kdx %2d (Value for Cell[0][j][i]) = (%.5f, %.5f, %.5f)\n", rank, 1, l_arr[1][j_mid][i_mid].x, l_arr[1][j_mid][i_mid].y, l_arr[1][j_mid][i_mid].z);
2767 } else if (dominant_dir == 'm') { // Ucont: Mixed
2768 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, K-DIR]: w-comp @ Idx %2d (1st Z-Face) = %.5f\n", rank, 0, l_arr[0][j_mid][i_mid].z);
2769 }
2770 }
2771 if (info.zs + info.zm == info.mz) { // Rank on +Zeta boundary
2772 if (dominant_dir == 'z') { // Node-like in K-dir
2773 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, K-DIR]: Idx %2d (Last Phys. Z-Face) = (%.5f, %.5f, %.5f)\n", rank, km_phys - 1, l_arr[km_phys - 1][j_mid][i_mid].x, l_arr[km_phys - 1][j_mid][i_mid].y, l_arr[km_phys - 1][j_mid][i_mid].z);
2774 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, K-DIR]: Idx %2d (Ghost Location) = (%.5f, %.5f, %.5f)\n", rank, km_phys, l_arr[km_phys][j_mid][i_mid].x, l_arr[km_phys][j_mid][i_mid].y, l_arr[km_phys][j_mid][i_mid].z);
2775 } else if (dominant_dir == 'x' || dominant_dir == 'y') { // Cell-like in K-dir
2776 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, K-DIR]: Idx %2d (Value for Cell[%d][j][i]) = (%.5f, %.5f, %.5f)\n", rank, km_phys-1, km_phys-2, l_arr[km_phys-1][j_mid][i_mid].x, l_arr[km_phys-1][j_mid][i_mid].y, l_arr[km_phys - 1][j_mid][i_mid].z);
2777 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, K-DIR]: Idx %2d (Ghost for Cell[%d][j][i]) = (%.5f, %.5f, %.5f)\n", rank, km_phys, km_phys-2, l_arr[km_phys][j_mid][i_mid].x, l_arr[km_phys][j_mid][i_mid].y, l_arr[km_phys][j_mid][i_mid].z);
2778 } else if (dominant_dir == 'm') { // Ucont: Mixed
2779 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, K-DIR]: w-comp @ Idx %2d (Last Z-Face) = %.5f\n", rank, km_phys - 1, l_arr[km_phys - 1][j_mid][i_mid].z);
2780 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, K-DIR]: w-comp @ Idx %2d (Ghost Loc.) = %.5f\n", rank, km_phys, l_arr[km_phys][j_mid][i_mid].z);
2781
2782 }
2783 }
2784 ierr = DMDAVecRestoreArrayRead(dm, vec_local, (void*)&l_arr); CHKERRQ(ierr);
2785 }
2786 // ======================================================================
2787 // === CASE 3: Node-Centered Fields - USES DIRECT INDEX ===
2788 // ======================================================================
2789 else if (layout == FIELD_LAYOUT_NODE_CENTERED) {
2790 if (dof == 1) {
2791 const PetscReal ***l_arr;
2792 ierr = DMDAVecGetArrayRead(dm, vec_local, (void*)&l_arr); CHKERRQ(ierr);
2793
2794 if (info.xs == 0)
2795 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, I-DIR]: Idx %2d (First Phys. Node) = %.5f\n", rank, 0, l_arr[k_mid][j_mid][0]);
2796 if (info.xs + info.xm == info.mx) {
2797 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, I-DIR]: Idx %2d (Last Phys. Node) = %.5f\n", rank, im_phys - 1, l_arr[k_mid][j_mid][im_phys - 1]);
2798 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, I-DIR]: Idx %2d (Unused/Ghost Loc) = %.5f\n", rank, im_phys, l_arr[k_mid][j_mid][im_phys]);
2799 }
2800 if (info.ys == 0)
2801 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, J-DIR]: Jdx %2d (First Phys. Node) = %.5f\n", rank, 0, l_arr[k_mid][0][i_mid]);
2802 if (info.ys + info.ym == info.my) {
2803 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, J-DIR]: Jdx %2d (Last Phys. Node) = %.5f\n", rank, jm_phys - 1, l_arr[k_mid][jm_phys - 1][i_mid]);
2804 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, J-DIR]: Jdx %2d (Unused/Ghost Loc) = %.5f\n", rank, jm_phys, l_arr[k_mid][jm_phys][i_mid]);
2805 }
2806 if (info.zs == 0)
2807 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, K-DIR]: Kdx %2d (First Phys. Node) = %.5f\n", rank, 0, l_arr[0][j_mid][i_mid]);
2808 if (info.zs + info.zm == info.mz) {
2809 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, K-DIR]: Kdx %2d (Last Phys. Node) = %.5f\n", rank, km_phys - 1, l_arr[km_phys - 1][j_mid][i_mid]);
2810 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, K-DIR]: Kdx %2d (Unused/Ghost Loc) = %.5f\n", rank, km_phys, l_arr[km_phys][j_mid][i_mid]);
2811 }
2812 ierr = DMDAVecRestoreArrayRead(dm, vec_local, (void*)&l_arr); CHKERRQ(ierr);
2813 } else {
2814 const Cmpnts ***l_arr;
2815 ierr = DMDAVecGetArrayRead(dm, vec_local, (void*)&l_arr); CHKERRQ(ierr);
2816
2817 // --- I-Direction Boundaries ---
2818 if (info.xs == 0) {
2819 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, I-DIR]: Idx %2d (First Phys. Node) = (%.5f, %.5f, %.5f)\n", rank, 0, l_arr[k_mid][j_mid][0].x, l_arr[k_mid][j_mid][0].y, l_arr[k_mid][j_mid][0].z);
2820 }
2821 if (info.xs + info.xm == info.mx) {
2822 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, I-DIR]: Idx %2d (Last Phys. Node) = (%.5f, %.5f, %.5f)\n", rank, im_phys - 1, l_arr[k_mid][j_mid][im_phys - 1].x, l_arr[k_mid][j_mid][im_phys - 1].y, l_arr[k_mid][j_mid][im_phys - 1].z);
2823 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, I-DIR]: Idx %2d (Unused/Ghost Loc) = (%.5f, %.5f, %.5f)\n", rank, im_phys, l_arr[k_mid][j_mid][im_phys].x, l_arr[k_mid][j_mid][im_phys].y, l_arr[k_mid][j_mid][im_phys].z);
2824 }
2825 // --- J-Direction Boundaries ---
2826 if (info.ys == 0) {
2827 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, J-DIR]: Jdx %2d (First Phys. Node) = (%.5f, %.5f, %.5f)\n", rank, 0, l_arr[k_mid][0][i_mid].x, l_arr[k_mid][0][i_mid].y, l_arr[k_mid][0][i_mid].z);
2828 }
2829 if (info.ys + info.ym == info.my) {
2830 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, J-DIR]: Jdx %2d (Last Phys. Node) = (%.5f, %.5f, %.5f)\n", rank, jm_phys - 1, l_arr[k_mid][jm_phys - 1][i_mid].x, l_arr[k_mid][jm_phys - 1][i_mid].y, l_arr[k_mid][jm_phys - 1][i_mid].z);
2831 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, J-DIR]: Jdx %2d (Unused/Ghost Loc) = (%.5f, %.5f, %.5f)\n", rank, jm_phys, l_arr[k_mid][jm_phys][i_mid].x, l_arr[k_mid][jm_phys][i_mid].y, l_arr[k_mid][jm_phys][i_mid].z);
2832 }
2833 // --- K-Direction Boundaries ---
2834 if (info.zs == 0) {
2835 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, K-DIR]: Kdx %2d (First Phys. Node) = (%.5f, %.5f, %.5f)\n", rank, 0, l_arr[0][j_mid][i_mid].x, l_arr[0][j_mid][i_mid].y, l_arr[0][j_mid][i_mid].z);
2836 }
2837 if(info.zs + info.zm == info.mz) {
2838 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, K-DIR]: Kdx %2d (Last Phys. Node) = (%.5f, %.5f, %.5f)\n", rank, km_phys - 1, l_arr[km_phys - 1][j_mid][i_mid].x, l_arr[km_phys - 1][j_mid][i_mid].y, l_arr[km_phys - 1][j_mid][i_mid].z);
2839 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "[Rank %d, K-DIR]: Kdx %2d (Unused/Ghost Loc) = (%.5f, %.5f, %.5f)\n", rank, km_phys, l_arr[km_phys][j_mid][i_mid].x, l_arr[km_phys][j_mid][i_mid].y, l_arr[km_phys][j_mid][i_mid].z);
2840 }
2841 ierr = DMDAVecRestoreArrayRead(dm, vec_local, (void*)&l_arr); CHKERRQ(ierr);
2842 }
2843 }
2844 else {
2845 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
2846 "LOG_FIELD_ANATOMY encountered unsupported layout %d for field '%s'.",
2847 (int)layout, field_name);
2848 }
2849
2850 ierr = PetscSynchronizedFlush(PETSC_COMM_WORLD, PETSC_STDOUT); CHKERRQ(ierr);
2851 ierr = PetscBarrier(NULL);
2852 PetscFunctionReturn(0);
2853}
2854
2855#undef __FUNCT__
2856#define __FUNCT__ "LOG_FIELD_ANATOMY"
2857/**
2858 * @brief Resolves a persistent field view and emits its layout-aware boundary anatomy.
2859 * @see LOG_FIELD_ANATOMY()
2860 */
2861PetscErrorCode LOG_FIELD_ANATOMY(UserCtx *user, FieldId field_id, const char *stage_name)
2862{
2863 FieldView view;
2864
2865 PetscFunctionBeginUser;
2866 PetscCall(FieldGetView(user, field_id, &view));
2867 PetscCall(LogFieldAnatomyView(user, view.descriptor->canonical_name, stage_name,
2868 view.dm, view.local_vec, view.descriptor->dof,
2869 view.descriptor->layout));
2870 PetscFunctionReturn(0);
2871}
2872
2873/**
2874 * @brief Implementation of \ref IsStatisticsConsoleSnapshotEnabled().
2875 * @see IsStatisticsConsoleSnapshotEnabled()
2876 */
2878{
2879 if (!FieldStatisticsIsActive(simCtx)) return PETSC_FALSE;
2880 return (PetscBool)(simCtx->statisticsConsoleOutputFreq > 0 && get_log_level() >= LOG_INFO);
2881}
2882
2883/**
2884 * @brief Implementation of \ref ShouldEmitPeriodicStatisticsConsoleSnapshot().
2885 * @see ShouldEmitPeriodicStatisticsConsoleSnapshot()
2886 */
2887PetscBool ShouldEmitPeriodicStatisticsConsoleSnapshot(const SimCtx *simCtx, PetscInt completed_step)
2888{
2889 if (!IsStatisticsConsoleSnapshotEnabled(simCtx)) return PETSC_FALSE;
2890 if (completed_step < 0) return PETSC_FALSE;
2891 return (PetscBool)((completed_step % simCtx->statisticsConsoleOutputFreq) == 0);
2892}
2893
2894/**
2895 * @brief Implementation of \ref EmitStatisticsConsoleSnapshot().
2896 * @see EmitStatisticsConsoleSnapshot()
2897 */
2898PetscErrorCode EmitStatisticsConsoleSnapshot(UserCtx *user, const SimCtx *simCtx, PetscInt step)
2899{
2900 PetscFunctionBeginUser;
2901 PetscCheck(simCtx != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "SimCtx cannot be NULL.");
2902 if (!IsStatisticsConsoleSnapshotEnabled(simCtx)) PetscFunctionReturn(0);
2903
2904 LOG(GLOBAL, LOG_INFO, "Statistics windows at step %d (%d window(s)):\n",
2905 step, simCtx->fieldStatisticsWindowCount);
2906 for (PetscInt w = 0; w < simCtx->fieldStatisticsWindowCount; ++w) {
2907 const PicurvWindow *window = &simCtx->fieldStatisticsWindows[w];
2908 char progress[16];
2909 char coverage[32] = "";
2910
2911 if (window->definition.bounded) {
2912 PetscCall(PetscSNPrintf(progress, sizeof(progress), "%5.1f%%",
2913 100.0 * (double)PicurvWindowProgress(window)));
2914 } else {
2915 PetscCall(PetscSNPrintf(progress, sizeof(progress), " open"));
2916 }
2917
2918 /* Mask health: with a moving body different points see different numbers of
2919 * states, and the spread of per-point valid fraction is what tells an
2920 * operator whether the window covers the domain evenly. Reported only on the
2921 * first block, since the reduction it performs is already collective. */
2922 if (user && user->fieldStatisticsStorage && window->sample_count > 0) {
2923 PetscReal lowest = 1.0, highest = 0.0;
2924
2925 PetscCall(PicurvWindowValidFractionRange(user, &window->definition,
2926 &user->fieldStatisticsStorage[w],
2927 window->sample_count, &lowest, &highest));
2928 PetscCall(PetscSNPrintf(coverage, sizeof(coverage), " valid=[%.2f,%.2f]",
2929 (double)lowest, (double)highest));
2930 }
2931
2933 " %-24s %-8s samples=%-8d weight=%-12.6g represented=%-12.6g progress=%s%s\n",
2934 window->definition.name, PicurvWindowStateName(window->state),
2935 window->sample_count, (double)window->total_weight,
2936 (double)window->represented_time, progress, coverage);
2937 }
2938 PetscFunctionReturn(0);
2939}
2940
2941#undef __FUNCT__
2942#define __FUNCT__ "LOG_CORNER_FIELD_ANATOMY"
2943/**
2944 * @brief Emits anatomy for the transient scalar or vector corner-staging field.
2945 * @see LOG_CORNER_FIELD_ANATOMY()
2946 */
2947PetscErrorCode LOG_CORNER_FIELD_ANATOMY(UserCtx *user, FieldId corner_field_id, const char *stage_name)
2948{
2949 FieldView view;
2950
2951 PetscFunctionBeginUser;
2952 PetscCheck(user != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx cannot be NULL.");
2953 PetscCheck(corner_field_id == FIELD_ID_CELL_SCALAR_AT_CORNER ||
2954 corner_field_id == FIELD_ID_CELL_VECTOR_AT_CORNER,
2955 PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
2956 "Corner anatomy logging expects a corner-staging field identity.");
2957 /* The caller states which workspace it used, so the degree of freedom and DM
2958 * come from the catalog instead of being inferred from a cached vector. */
2959 PetscCall(FieldGetView(user, corner_field_id, &view));
2960 PetscCall(LogFieldAnatomyView(user, view.descriptor->canonical_name, stage_name, view.dm,
2961 view.local_vec, view.descriptor->dof,
2962 view.descriptor->layout));
2963 PetscFunctionReturn(0);
2964}
2965
2966#undef __FUNCT__
2967#define __FUNCT__ "LOG_INTERPOLATION_ERROR"
2968/**
2969 * @brief Implementation of \ref LOG_INTERPOLATION_ERROR().
2970 * @details Full API contract (arguments, ownership, side effects) is documented with
2971 * the header declaration in `include/logging.h`.
2972 * @see LOG_INTERPOLATION_ERROR()
2973 */
2975{
2976 SimCtx *simCtx = user->simCtx;
2977 PetscErrorCode ierr;
2978 DM swarm = user->swarm;
2979 Vec positionVec, analyticalvelocityVec, velocityVec, errorVec;
2980 PetscReal Interpolation_error = 0.0;
2981 PetscReal Maximum_Interpolation_error = 0.0;
2982 PetscReal AnalyticalSolution_magnitude = 0.0;
2983 PetscReal ErrorPercentage = 0.0;
2984
2985 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Creating global vectors.\n");
2986 ierr = DMSwarmCreateGlobalVectorFromField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), &positionVec); CHKERRQ(ierr);
2987 ierr = DMSwarmCreateGlobalVectorFromField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_VELOCITY), &velocityVec); CHKERRQ(ierr);
2988
2989 ierr = VecDuplicate(positionVec, &analyticalvelocityVec); CHKERRQ(ierr);
2990 ierr = VecCopy(positionVec, analyticalvelocityVec); CHKERRQ(ierr);
2991
2992 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Computing analytical solution.\n");
2993 ierr = SetAnalyticalSolutionForParticles(analyticalvelocityVec, simCtx); CHKERRQ(ierr);
2994
2995 ierr = VecDuplicate(analyticalvelocityVec, &errorVec); CHKERRQ(ierr);
2996 ierr = VecCopy(analyticalvelocityVec, errorVec); CHKERRQ(ierr);
2997
2998 ierr = VecNorm(analyticalvelocityVec, NORM_2, &AnalyticalSolution_magnitude); CHKERRQ(ierr);
2999
3000 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Computing error.\n");
3001 ierr = VecAXPY(errorVec, -1.0, velocityVec); CHKERRQ(ierr);
3002 ierr = VecNorm(errorVec, NORM_2, &Interpolation_error); CHKERRQ(ierr);
3003 ierr = VecNorm(errorVec,NORM_INFINITY,&Maximum_Interpolation_error); CHKERRQ(ierr);
3004
3005 ErrorPercentage = (AnalyticalSolution_magnitude > 0) ?
3006 (Interpolation_error / AnalyticalSolution_magnitude * 100.0) : 0.0;
3007
3008 /* --- CSV output (always, rank 0 only) --- */
3009 if (simCtx->rank == 0) {
3010 char csv_path[PETSC_MAX_PATH_LEN + 32];
3011 ierr = PetscSNPrintf(csv_path, sizeof(csv_path), "%s/interpolation_error.csv", simCtx->analysis_dir); CHKERRQ(ierr);
3012 FILE *f = fopen(csv_path, "a");
3013 if (f) {
3014 if (ftell(f) == 0) {
3015 fprintf(f, "step,time,L2_error,Linf_error,L2_analytical,error_pct,physical_time\n");
3016 }
3017 if (simCtx->continueMode && simCtx->step == simCtx->StartStep + 1) {
3018 fprintf(f, "# Continuation from step %" PetscInt_FMT "\n", simCtx->StartStep);
3019 }
3020 /* ti is solver time, not a step count; physical_time is it in seconds. */
3021 PetscReal t = simCtx->ti;
3022 PetscReal physical_time = 0.0;
3023 ierr = PicurvPhysicalTime(simCtx, t, &physical_time); CHKERRQ(ierr);
3024 fprintf(f, "%d,%.6e,%.6e,%.6e,%.6e,%.4f,%.6e\n",
3025 (int)simCtx->step, t,
3026 Interpolation_error, Maximum_Interpolation_error,
3027 AnalyticalSolution_magnitude, ErrorPercentage, physical_time);
3028 fclose(f);
3029 }
3030 }
3031
3032 /* --- Console output (only at INFO level or above) --- */
3033 if (get_log_level() >= LOG_INFO) {
3034 LOG_ALLOW(GLOBAL, LOG_INFO, "Interpolation error (%%): %g\n", ErrorPercentage);
3035 PetscPrintf(PETSC_COMM_WORLD, "Interpolation error (%%): %g\n", ErrorPercentage);
3036 LOG_ALLOW(GLOBAL, LOG_INFO, "Maximum Interpolation error: %g\n", Maximum_Interpolation_error);
3037 PetscPrintf(PETSC_COMM_WORLD, "Maximum Interpolation error: %g\n", Maximum_Interpolation_error);
3038 }
3039
3040 ierr = VecDestroy(&analyticalvelocityVec); CHKERRQ(ierr);
3041 ierr = VecDestroy(&errorVec); CHKERRQ(ierr);
3042 ierr = DMSwarmDestroyGlobalVectorFromField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), &positionVec); CHKERRQ(ierr);
3043 ierr = DMSwarmDestroyGlobalVectorFromField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_VELOCITY), &velocityVec); CHKERRQ(ierr);
3044
3045 return 0;
3046}
3047
3048#undef __FUNCT__
3049#define __FUNCT__ "LOG_SCATTER_METRICS"
3050/**
3051 * @brief Implementation of \ref LOG_SCATTER_METRICS().
3052 * @details Full API contract (arguments, ownership, side effects) is documented with
3053 * the header declaration in `include/logging.h`.
3054 * @see LOG_SCATTER_METRICS()
3055 */
3056PetscErrorCode LOG_SCATTER_METRICS(UserCtx *user)
3057{
3058 PetscErrorCode ierr;
3059 SimCtx *simCtx = NULL;
3060 DMDALocalInfo info;
3061 PetscInt xs, xe, ys, ye, zs, ze, mx, my, mz;
3062 PetscInt lxs, lxe, lys, lye, lzs, lze;
3063 Vec reference_vec = NULL;
3064 PetscReal ***psi = NULL;
3065 PetscReal ***psi_ref = NULL;
3066 PetscReal ***aj = NULL;
3067 PetscReal ***count = NULL;
3068 PetscReal *particle_psi = NULL;
3069 PetscInt nlocal = 0;
3070 PetscReal local_l1 = 0.0, global_l1 = 0.0;
3071 PetscReal local_l2_sq = 0.0, global_l2_sq = 0.0;
3072 PetscReal local_linf = 0.0, global_linf = 0.0;
3073 PetscReal local_ref_l2_sq = 0.0, global_ref_l2_sq = 0.0;
3074 PetscReal local_grid_integral = 0.0, global_grid_integral = 0.0;
3075 PetscReal local_domain_volume = 0.0, global_domain_volume = 0.0;
3076 PetscReal local_particle_sum = 0.0, global_particle_sum = 0.0;
3077 PetscInt64 local_particle_count = 0, global_particle_count = 0;
3078 PetscInt64 local_cell_count = 0, global_cell_count = 0;
3079 PetscInt64 local_occupied_count = 0, global_occupied_count = 0;
3080 PetscReal particle_integral = 0.0;
3081 PetscReal occupancy_fraction = 0.0;
3082 PetscReal mean_particles_per_occupied_cell = 0.0;
3083 PetscReal l2_error = 0.0;
3084 PetscReal relative_l2_error = 0.0;
3085
3086 PetscFunctionBeginUser;
3087 if (!user) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx cannot be NULL.");
3088 simCtx = user->simCtx;
3089 if (!VerificationScalarOverrideActive(simCtx) || !user->swarm || !user->Psi || !user->ParticleCount) {
3090 PetscFunctionReturn(0);
3091 }
3092
3093 info = user->info;
3094 xs = info.xs; xe = info.xs + info.xm;
3095 ys = info.ys; ye = info.ys + info.ym;
3096 zs = info.zs; ze = info.zs + info.zm;
3097 mx = info.mx; my = info.my; mz = info.mz;
3098 lxs = (xs == 0) ? xs + 1 : xs; lxe = (xe == mx) ? xe - 1 : xe;
3099 lys = (ys == 0) ? ys + 1 : ys; lye = (ye == my) ? ye - 1 : ye;
3100 lzs = (zs == 0) ? zs + 1 : zs; lze = (ze == mz) ? ze - 1 : ze;
3101
3102 ierr = VecDuplicate(user->Psi, &reference_vec); CHKERRQ(ierr);
3103 ierr = SetAnalyticalScalarFieldAtCellCenters(user, reference_vec); CHKERRQ(ierr);
3104
3105 ierr = DMDAVecGetArrayRead(user->da, user->Psi, &psi); CHKERRQ(ierr);
3106 ierr = DMDAVecGetArrayRead(user->da, reference_vec, &psi_ref); CHKERRQ(ierr);
3107 ierr = DMDAVecGetArrayRead(user->da, user->Aj, &aj); CHKERRQ(ierr);
3108 ierr = DMDAVecGetArrayRead(user->da, user->ParticleCount, &count); CHKERRQ(ierr);
3109
3110 for (PetscInt k = lzs; k < lze; ++k) {
3111 for (PetscInt j = lys; j < lye; ++j) {
3112 for (PetscInt i = lxs; i < lxe; ++i) {
3113 const PetscReal cell_volume = (PetscAbsReal(aj[k][j][i]) > 1.0e-14) ? (1.0 / aj[k][j][i]) : 0.0;
3114 const PetscReal err = psi[k][j][i] - psi_ref[k][j][i];
3115 local_cell_count += 1;
3116 local_domain_volume += cell_volume;
3117 local_grid_integral += psi[k][j][i] * cell_volume;
3118 local_l1 += PetscAbsReal(err) * cell_volume;
3119 local_l2_sq += err * err * cell_volume;
3120 local_ref_l2_sq += psi_ref[k][j][i] * psi_ref[k][j][i] * cell_volume;
3121 local_linf = PetscMax(local_linf, PetscAbsReal(err));
3122 if (count[k][j][i] > 0.0) local_occupied_count += 1;
3123 }
3124 }
3125 }
3126
3127 ierr = DMDAVecRestoreArrayRead(user->da, user->ParticleCount, &count); CHKERRQ(ierr);
3128 ierr = DMDAVecRestoreArrayRead(user->da, user->Aj, &aj); CHKERRQ(ierr);
3129 ierr = DMDAVecRestoreArrayRead(user->da, reference_vec, &psi_ref); CHKERRQ(ierr);
3130 ierr = DMDAVecRestoreArrayRead(user->da, user->Psi, &psi); CHKERRQ(ierr);
3131 ierr = VecDestroy(&reference_vec); CHKERRQ(ierr);
3132
3133 ierr = DMSwarmGetLocalSize(user->swarm, &nlocal); CHKERRQ(ierr);
3134 local_particle_count = (PetscInt64)nlocal;
3135 if (nlocal > 0) {
3136 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_PSI), NULL, NULL, (void **)&particle_psi); CHKERRQ(ierr);
3137 for (PetscInt p = 0; p < nlocal; ++p) local_particle_sum += particle_psi[p];
3138 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_PSI), NULL, NULL, (void **)&particle_psi); CHKERRQ(ierr);
3139 }
3140
3141 ierr = MPI_Allreduce(&local_l1, &global_l1, 1, MPIU_REAL, MPI_SUM, PETSC_COMM_WORLD); CHKERRMPI(ierr);
3142 ierr = MPI_Allreduce(&local_l2_sq, &global_l2_sq, 1, MPIU_REAL, MPI_SUM, PETSC_COMM_WORLD); CHKERRMPI(ierr);
3143 ierr = MPI_Allreduce(&local_linf, &global_linf, 1, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD); CHKERRMPI(ierr);
3144 ierr = MPI_Allreduce(&local_ref_l2_sq, &global_ref_l2_sq, 1, MPIU_REAL, MPI_SUM, PETSC_COMM_WORLD); CHKERRMPI(ierr);
3145 ierr = MPI_Allreduce(&local_grid_integral, &global_grid_integral, 1, MPIU_REAL, MPI_SUM, PETSC_COMM_WORLD); CHKERRMPI(ierr);
3146 ierr = MPI_Allreduce(&local_domain_volume, &global_domain_volume, 1, MPIU_REAL, MPI_SUM, PETSC_COMM_WORLD); CHKERRMPI(ierr);
3147 ierr = MPI_Allreduce(&local_particle_sum, &global_particle_sum, 1, MPIU_REAL, MPI_SUM, PETSC_COMM_WORLD); CHKERRMPI(ierr);
3148 ierr = MPI_Allreduce(&local_particle_count, &global_particle_count, 1, MPIU_INT64, MPI_SUM, PETSC_COMM_WORLD); CHKERRMPI(ierr);
3149 ierr = MPI_Allreduce(&local_cell_count, &global_cell_count, 1, MPIU_INT64, MPI_SUM, PETSC_COMM_WORLD); CHKERRMPI(ierr);
3150 ierr = MPI_Allreduce(&local_occupied_count, &global_occupied_count, 1, MPIU_INT64, MPI_SUM, PETSC_COMM_WORLD); CHKERRMPI(ierr);
3151
3152 l2_error = PetscSqrtReal(global_l2_sq);
3153 relative_l2_error = (global_ref_l2_sq > 0.0) ? (l2_error / PetscSqrtReal(global_ref_l2_sq)) : 0.0;
3154 occupancy_fraction = (global_cell_count > 0) ? ((PetscReal)global_occupied_count / (PetscReal)global_cell_count) : 0.0;
3155 mean_particles_per_occupied_cell =
3156 (global_occupied_count > 0) ? ((PetscReal)global_particle_count / (PetscReal)global_occupied_count) : 0.0;
3157 particle_integral =
3158 (global_particle_count > 0) ? (global_domain_volume * global_particle_sum / (PetscReal)global_particle_count) : 0.0;
3159
3160 if (simCtx->rank == 0) {
3161 char csv_path[PETSC_MAX_PATH_LEN + 32];
3162 FILE *f = NULL;
3163 ierr = PetscSNPrintf(csv_path, sizeof(csv_path), "%s/scatter_metrics.csv", simCtx->analysis_dir); CHKERRQ(ierr);
3164 f = fopen(csv_path, "a");
3165 if (f) {
3166 if (ftell(f) == 0) {
3167 fprintf(f,
3168 "step,time,total_particles,total_cells,occupied_cells,occupancy_fraction,"
3169 "mean_particles_per_occupied_cell,particle_integral,grid_integral,"
3170 "conservation_error_abs,L1_error,L2_error,Linf_error,relative_L2_error,"
3171 "physical_time\n");
3172 }
3173 if (simCtx->continueMode && simCtx->step == simCtx->StartStep + 1) {
3174 fprintf(f, "# Continuation from step %" PetscInt_FMT "\n", simCtx->StartStep);
3175 }
3176 PetscReal physical_time = 0.0;
3177 ierr = PicurvPhysicalTime(simCtx, simCtx->ti, &physical_time); CHKERRQ(ierr);
3178 fprintf(f, "%d,%.6e,%lld,%lld,%lld,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e\n",
3179 (int)simCtx->step,
3180 (double)simCtx->ti,
3181 (long long)global_particle_count,
3182 (long long)global_cell_count,
3183 (long long)global_occupied_count,
3184 (double)occupancy_fraction,
3185 (double)mean_particles_per_occupied_cell,
3186 (double)particle_integral,
3187 (double)global_grid_integral,
3188 (double)PetscAbsReal(global_grid_integral - particle_integral),
3189 (double)global_l1,
3190 (double)l2_error,
3191 (double)global_linf,
3192 (double)relative_l2_error,
3193 (double)physical_time);
3194 fclose(f);
3195 }
3196 }
3197
3198 if (get_log_level() >= LOG_INFO) {
3199 LOG_ALLOW(GLOBAL, LOG_INFO, "Scatter relative L2 error: %.6e\n", (double)relative_l2_error);
3200 LOG_ALLOW(GLOBAL, LOG_INFO, "Scatter occupancy fraction: %.6e\n", (double)occupancy_fraction);
3201 }
3202
3203 PetscFunctionReturn(0);
3204}
3205
3206#undef __FUNCT__
3207#define __FUNCT__ "ResetSearchMetrics"
3208/**
3209 * @brief Implementation of \ref ResetSearchMetrics().
3210 * @details Full API contract (arguments, ownership, side effects) is documented with
3211 * the header declaration in `include/logging.h`.
3212 * @see ResetSearchMetrics()
3213 */
3214PetscErrorCode ResetSearchMetrics(SimCtx *simCtx)
3215{
3216 PetscFunctionBeginUser;
3217 if (!simCtx) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "SimCtx cannot be NULL for ResetSearchMetrics.");
3218
3219 simCtx->searchMetrics.searchAttempts = 0;
3220 simCtx->searchMetrics.searchPopulation = 0;
3222 simCtx->searchMetrics.searchLostCount = 0;
3223 simCtx->searchMetrics.traversalStepsSum = 0;
3224 simCtx->searchMetrics.reSearchCount = 0;
3225 simCtx->searchMetrics.maxTraversalSteps = 0;
3227 simCtx->searchMetrics.tieBreakCount = 0;
3233
3234 PetscFunctionReturn(0);
3235}
3236
3237#undef __FUNCT__
3238#define __FUNCT__ "LOG_SEARCH_METRICS"
3239/**
3240 * @brief Implementation of \ref LOG_SEARCH_METRICS().
3241 * @details Full API contract (arguments, ownership, side effects) is documented with
3242 * the header declaration in `include/logging.h`.
3243 * @see LOG_SEARCH_METRICS()
3244 */
3245PetscErrorCode LOG_SEARCH_METRICS(UserCtx *user)
3246{
3247 PetscErrorCode ierr;
3248 SimCtx *simCtx = NULL;
3249 PetscInt totalParticles = 0;
3250 PetscReal local_metrics[SEARCH_METRIC_REDUCTION_LEN] = {0.0};
3251 PetscReal global_metrics[SEARCH_METRIC_REDUCTION_LEN] = {0.0};
3252 PetscReal meanTraversalSteps = 0.0;
3253 PetscReal searchFailureFraction = 0.0;
3254 PetscReal searchWorkIndex = 0.0;
3255 PetscReal reSearchFraction = 0.0;
3256 long long searchAttempts = 0;
3257 long long searchPopulation = 0;
3258 long long searchLocatedCount = 0;
3259 long long searchLostCount = 0;
3260 long long traversalStepsSum = 0;
3261 long long reSearchCount = 0;
3262 long long tieBreakCount = 0;
3263 long long boundaryClampCount = 0;
3264 long long bboxGuessSuccessCount = 0;
3265 long long bboxGuessFallbackCount = 0;
3266 long long maxTraversalFailCount = 0;
3267 long long maxTraversalSteps = 0;
3268 long long maxPassDepth = 0;
3269 MPI_Op reduction_op = MPI_OP_NULL;
3270
3271 PetscFunctionBeginUser;
3272 if (!user || !user->simCtx) {
3273 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx and SimCtx are required for LOG_SEARCH_METRICS.");
3274 }
3275 simCtx = user->simCtx;
3276
3277 if (simCtx->np <= 0) {
3278 PetscFunctionReturn(0);
3279 }
3280
3281 ierr = DMSwarmGetSize(user->swarm, &totalParticles); CHKERRQ(ierr);
3282
3283 local_metrics[SEARCH_METRIC_SUM_SEARCH_ATTEMPTS] = (PetscReal)simCtx->searchMetrics.searchAttempts;
3284 local_metrics[SEARCH_METRIC_SUM_SEARCH_POPULATION] = (PetscReal)simCtx->searchMetrics.searchPopulation;
3285 local_metrics[SEARCH_METRIC_SUM_SEARCH_LOCATED] = (PetscReal)simCtx->searchMetrics.searchLocatedCount;
3286 local_metrics[SEARCH_METRIC_SUM_SEARCH_LOST] = (PetscReal)simCtx->searchMetrics.searchLostCount;
3287 local_metrics[SEARCH_METRIC_SUM_TRAVERSAL_STEPS] = (PetscReal)simCtx->searchMetrics.traversalStepsSum;
3288 local_metrics[SEARCH_METRIC_SUM_RESEARCH] = (PetscReal)simCtx->searchMetrics.reSearchCount;
3289 local_metrics[SEARCH_METRIC_SUM_TIE_BREAKS] = (PetscReal)simCtx->searchMetrics.tieBreakCount;
3290 local_metrics[SEARCH_METRIC_SUM_BOUNDARY_CLAMPS] = (PetscReal)simCtx->searchMetrics.boundaryClampCount;
3294 local_metrics[SEARCH_METRIC_MAX_TRAVERSAL_STEPS] = (PetscReal)simCtx->searchMetrics.maxTraversalSteps;
3295 local_metrics[SEARCH_METRIC_MAX_PASS_DEPTH] = (PetscReal)simCtx->searchMetrics.maxParticlePassDepth;
3296
3297 ierr = MPI_Op_create(SearchMetricsReduceOp, PETSC_TRUE, &reduction_op); CHKERRMPI(ierr);
3298 ierr = MPI_Allreduce(local_metrics, global_metrics, SEARCH_METRIC_REDUCTION_LEN, MPIU_REAL, reduction_op, PETSC_COMM_WORLD); CHKERRMPI(ierr);
3299 ierr = MPI_Op_free(&reduction_op); CHKERRMPI(ierr);
3300 reduction_op = MPI_OP_NULL;
3301
3302 searchAttempts = (long long)PetscFloorReal(global_metrics[SEARCH_METRIC_SUM_SEARCH_ATTEMPTS] + 0.5);
3303 searchPopulation = (long long)PetscFloorReal(global_metrics[SEARCH_METRIC_SUM_SEARCH_POPULATION] + 0.5);
3304 searchLocatedCount = (long long)PetscFloorReal(global_metrics[SEARCH_METRIC_SUM_SEARCH_LOCATED] + 0.5);
3305 searchLostCount = (long long)PetscFloorReal(global_metrics[SEARCH_METRIC_SUM_SEARCH_LOST] + 0.5);
3306 traversalStepsSum = (long long)PetscFloorReal(global_metrics[SEARCH_METRIC_SUM_TRAVERSAL_STEPS] + 0.5);
3307 reSearchCount = (long long)PetscFloorReal(global_metrics[SEARCH_METRIC_SUM_RESEARCH] + 0.5);
3308 tieBreakCount = (long long)PetscFloorReal(global_metrics[SEARCH_METRIC_SUM_TIE_BREAKS] + 0.5);
3309 boundaryClampCount = (long long)PetscFloorReal(global_metrics[SEARCH_METRIC_SUM_BOUNDARY_CLAMPS] + 0.5);
3310 bboxGuessSuccessCount = (long long)PetscFloorReal(global_metrics[SEARCH_METRIC_SUM_BBOX_GUESS_SUCCESS] + 0.5);
3311 bboxGuessFallbackCount = (long long)PetscFloorReal(global_metrics[SEARCH_METRIC_SUM_BBOX_GUESS_FALLBACK] + 0.5);
3312 maxTraversalFailCount = (long long)PetscFloorReal(global_metrics[SEARCH_METRIC_SUM_MAX_TRAVERSAL_FAILS] + 0.5);
3313 maxTraversalSteps = (long long)PetscFloorReal(global_metrics[SEARCH_METRIC_MAX_TRAVERSAL_STEPS] + 0.5);
3314 maxPassDepth = (long long)PetscFloorReal(global_metrics[SEARCH_METRIC_MAX_PASS_DEPTH] + 0.5);
3315
3316 if (searchAttempts > 0) {
3317 meanTraversalSteps = (PetscReal)traversalStepsSum / (PetscReal)searchAttempts;
3318 }
3319 if (searchPopulation > 0) {
3320 searchFailureFraction = (PetscReal)searchLostCount / (PetscReal)searchPopulation;
3321 searchWorkIndex = (PetscReal)traversalStepsSum / (PetscReal)searchPopulation;
3322 reSearchFraction = (PetscReal)reSearchCount / (PetscReal)searchPopulation;
3323 }
3324
3325 if (simCtx->rank == 0) {
3326 char csv_path[PETSC_MAX_PATH_LEN + 32];
3327 FILE *f = NULL;
3328 PetscReal searchPhysicalTime = 0.0;
3329
3330 ierr = PicurvPhysicalTime(simCtx, simCtx->ti, &searchPhysicalTime); CHKERRQ(ierr);
3331 ierr = PetscSNPrintf(csv_path, sizeof(csv_path), "%s/search_metrics.csv", simCtx->analysis_dir); CHKERRQ(ierr);
3332 f = fopen(csv_path, "a");
3333 if (!f) {
3334 LOG_ALLOW(GLOBAL, LOG_WARNING, "LOG_SEARCH_METRICS: could not open '%s' for writing.\n", csv_path);
3335 } else {
3336 if (ftell(f) == 0) {
3337 fprintf(f,
3338 "step,time,total_particles,lost,lost_cumulative,migrated,migration_passes,search_attempts,"
3339 "mean_traversal_steps,max_traversal_steps,tie_break_count,boundary_clamp_count,"
3340 "bbox_guess_success_count,bbox_guess_fallback_count,max_particle_pass_depth,load_imbalance,"
3341 "search_population,search_located_count,search_lost_count,traversal_steps_sum,re_search_count,"
3342 "max_traversal_fail_count,search_failure_fraction,search_work_index,re_search_fraction,"
3343 "physical_time,lost_psi_sum\n");
3344 }
3345 if (simCtx->continueMode && simCtx->step == simCtx->StartStep + 1) {
3346 fprintf(f, "# Continuation from step %" PetscInt_FMT "\n", simCtx->StartStep);
3347 }
3348 fprintf(f,
3349 "%d,%.6e,%d,%d,%d,%d,%d,%lld,%.6e,%lld,%lld,%lld,%lld,%lld,%lld,%.6e,%lld,%lld,%lld,%lld,%lld,%lld,%.6e,%.6e,%.6e,%.6e,%.10e\n",
3350 (int)simCtx->step,
3351 (double)simCtx->ti,
3352 (int)totalParticles,
3353 (int)simCtx->particlesLostLastStep,
3354 (int)simCtx->particlesLostCumulative,
3355 (int)simCtx->particlesMigratedLastStep,
3356 (int)simCtx->migrationPassesLastStep,
3357 searchAttempts,
3358 (double)meanTraversalSteps,
3359 maxTraversalSteps,
3360 tieBreakCount,
3361 boundaryClampCount,
3362 bboxGuessSuccessCount,
3363 bboxGuessFallbackCount,
3364 maxPassDepth,
3365 (double)simCtx->particleLoadImbalance,
3366 searchPopulation,
3367 searchLocatedCount,
3368 searchLostCount,
3369 traversalStepsSum,
3370 reSearchCount,
3371 maxTraversalFailCount,
3372 (double)searchFailureFraction,
3373 (double)searchWorkIndex,
3374 (double)reSearchFraction,
3375 (double)searchPhysicalTime,
3376 (double)simCtx->particlesLostScalarLastStep);
3377 fclose(f);
3378 }
3379 }
3380
3382 "Search metrics: sff=%.3e swi=%.3e re_search=%.3e lost(step/total)=%d/%d migrated=%d passes=%d traversal(mean/max)=%.2f/%lld tie_breaks=%lld max_pass_depth=%lld\n",
3383 (double)searchFailureFraction,
3384 (double)searchWorkIndex,
3385 (double)reSearchFraction,
3386 (int)simCtx->particlesLostLastStep,
3387 (int)simCtx->particlesLostCumulative,
3388 (int)simCtx->particlesMigratedLastStep,
3389 (int)simCtx->migrationPassesLastStep,
3390 (double)meanTraversalSteps,
3391 maxTraversalSteps,
3392 tieBreakCount,
3393 maxPassDepth);
3394
3395 PetscFunctionReturn(0);
3396}
3397
3398#undef __FUNCT__
3399#define __FUNCT__ "CalculateAdvancedParticleMetrics"
3400/**
3401 * @brief Internal helper implementation: `CalculateAdvancedParticleMetrics()`.
3402 * @details Local to this translation unit.
3403 */
3405{
3406 PetscErrorCode ierr;
3407 SimCtx *simCtx = user->simCtx;
3408 PetscMPIInt size, rank;
3409
3410 PetscFunctionBeginUser;
3411 ierr = MPI_Comm_size(PETSC_COMM_WORLD, &size); CHKERRQ(ierr);
3412 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
3413
3414 // --- 1. Particle Load Imbalance ---
3415 PetscInt nLocal, nGlobal, nLocalMax;
3416 ierr = DMSwarmGetLocalSize(user->swarm, &nLocal); CHKERRQ(ierr);
3417 ierr = DMSwarmGetSize(user->swarm, &nGlobal); CHKERRQ(ierr);
3418 ierr = MPI_Allreduce(&nLocal, &nLocalMax, 1, MPIU_INT, MPI_MAX, PETSC_COMM_WORLD); CHKERRQ(ierr);
3419
3420 PetscReal avg_per_rank = (size > 0) ? ((PetscReal)nGlobal / size) : 0.0;
3421 // Handle division by zero if there are no particles
3422 simCtx->particleLoadImbalance = (avg_per_rank > 1e-9) ? (nLocalMax / avg_per_rank) : 1.0;
3423
3424
3425 // --- 2. Number of Occupied Cells ---
3426 // This part requires access to the user->ParticleCount vector.
3427 PetscInt local_occupied_cells = 0;
3428 PetscInt global_occupied_cells;
3429 const PetscScalar *count_array;
3430 PetscInt vec_local_size;
3431
3432 ierr = VecGetLocalSize(user->ParticleCount, &vec_local_size); CHKERRQ(ierr);
3433 ierr = VecGetArrayRead(user->ParticleCount, &count_array); CHKERRQ(ierr);
3434
3435 for (PetscInt i = 0; i < vec_local_size; ++i) {
3436 if (count_array[i] > 0.5) { // Use 0.5 to be safe with floating point
3437 local_occupied_cells++;
3438 }
3439 }
3440 ierr = VecRestoreArrayRead(user->ParticleCount, &count_array); CHKERRQ(ierr);
3441
3442 ierr = MPI_Allreduce(&local_occupied_cells, &global_occupied_cells, 1, MPIU_INT, MPI_SUM, PETSC_COMM_WORLD); CHKERRQ(ierr);
3443 simCtx->occupiedCellCount = global_occupied_cells;
3444
3445 LOG_ALLOW_SYNC(GLOBAL, LOG_INFO, "[Rank %d] Advanced Metrics: Imbalance=%.2f, OccupiedCells=%d\n", rank, simCtx->particleLoadImbalance, simCtx->occupiedCellCount);
3446
3447 PetscFunctionReturn(0);
3448}
3449
3450#undef __FUNCT__
3451#define __FUNCT__ "LOG_PARTICLE_METRICS"
3452/**
3453 * @brief Implementation of \ref LOG_PARTICLE_METRICS().
3454 * @details Full API contract (arguments, ownership, side effects) is documented with
3455 * the header declaration in `include/logging.h`.
3456 * @see LOG_PARTICLE_METRICS()
3457 */
3458PetscErrorCode LOG_PARTICLE_METRICS(UserCtx *user, const char *stageName)
3459{
3460 PetscErrorCode ierr;
3461 PetscMPIInt rank;
3462 SimCtx *simCtx = user->simCtx;
3463 const char *stage_label = (stageName && stageName[0] != '\0') ? stageName : "N/A";
3464
3465 PetscFunctionBeginUser;
3466 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
3467
3468 PetscInt totalParticles;
3469 ierr = DMSwarmGetSize(user->swarm, &totalParticles); CHKERRQ(ierr);
3470
3471 if (!rank) {
3472 FILE *f;
3473 char filen[PETSC_MAX_PATH_LEN + 64];
3474 ierr = PetscSNPrintf(filen, sizeof(filen), "%s/Particle_Metrics.log", simCtx->log_dir); CHKERRQ(ierr);
3475 f = fopen(filen, "a");
3476 if (!f) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Cannot open particle log file: %s", filen);
3477
3478 if (ftell(f) == 0) {
3479 PetscFPrintf(PETSC_COMM_SELF, f, "%-18s | %-10s | %-12s | %-10s | %-10s | %-10s | %-15s | %-10s | %-10s\n",
3480 "Stage", "Timestep", "Total Ptls", "Lost", "Lost Total", "Migrated", "Occupied Cells", "Imbalance", "Mig Passes");
3481 PetscFPrintf(PETSC_COMM_SELF, f, "-------------------------------------------------------------------------------------------------------------------------------------------\n");
3482 }
3483 if (simCtx->continueMode && simCtx->step == simCtx->StartStep + 1) {
3484 PetscFPrintf(PETSC_COMM_SELF, f, "# Continuation from step %" PetscInt_FMT "\n", simCtx->StartStep);
3485 }
3486
3487 PetscFPrintf(PETSC_COMM_SELF, f, "%-18s | %-10d | %-12d | %-10d | %-10d | %-10d | %-15d | %-10.2f | %-10d\n",
3488 stage_label, (int)simCtx->step, (int)totalParticles, (int)simCtx->particlesLostLastStep,
3489 (int)simCtx->particlesLostCumulative, (int)simCtx->particlesMigratedLastStep, (int)simCtx->occupiedCellCount,
3490 (double)simCtx->particleLoadImbalance, (int)simCtx->migrationPassesLastStep);
3491 fclose(f);
3492 }
3493 PetscFunctionReturn(0);
3494}
3495
3496#undef __FUNCT__
3497#define __FUNCT__ "PicurvOpenDiagnosticsCsv"
3498/**
3499 * @brief Implementation of \ref PicurvOpenDiagnosticsCsv().
3500 * @details Full API contract is documented with the header declaration in
3501 * `include/logging.h`.
3502 */
3503PetscErrorCode PicurvOpenDiagnosticsCsv(const SimCtx *simCtx, const char *filename,
3504 const char *header, FILE **file)
3505{
3506 char path[PETSC_MAX_PATH_LEN + 64];
3507 FILE *handle = NULL;
3508
3509 PetscFunctionBeginUser;
3510 PetscCheck(simCtx && filename && header && file, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
3511 "A diagnostics CSV needs a context, a name, a header, and somewhere to "
3512 "return the handle.");
3513
3514 PetscCall(PetscSNPrintf(path, sizeof(path), "%s/%s", simCtx->analysis_dir, filename));
3515 handle = fopen(path, "a");
3516 PetscCheck(handle != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN,
3517 "Unable to open diagnostics file '%s'.", path);
3518
3519 if (ftell(handle) == 0) fprintf(handle, "%s\n", header);
3520 /* Written once, on the first step a continuation produces, so that a reader can tell
3521 a resumed run from a jump in the data itself. */
3522 if (simCtx->continueMode && simCtx->step == simCtx->StartStep + 1) {
3523 fprintf(handle, "# Continuation from step %" PetscInt_FMT "\n", simCtx->StartStep);
3524 }
3525
3526 *file = handle;
3527 PetscFunctionReturn(0);
3528}
PetscErrorCode SetAnalyticalScalarFieldAtCellCenters(UserCtx *user, Vec targetVec)
Writes the configured verification scalar profile at physical cell centers into a scalar Vec.
PetscErrorCode SetAnalyticalSolutionForParticles(Vec tempVec, SimCtx *simCtx)
Applies the analytical solution to particle velocity vector.
FieldLayout layout
const FieldDescriptor * descriptor
PetscErrorCode FieldGetView(UserCtx *user, FieldId field_id, FieldView *view)
Resolve the existing DM and global/local vectors for one field.
FieldLayout
Logical storage topology of a field.
@ FIELD_LAYOUT_K_FACE
@ FIELD_LAYOUT_I_FACE
@ FIELD_LAYOUT_CELL_CENTERED
@ FIELD_LAYOUT_COMPONENT_STAGGERED
@ FIELD_LAYOUT_NODE_CENTERED
@ FIELD_LAYOUT_J_FACE
const char * canonical_name
const char * FieldLayoutName(FieldLayout layout)
Return a stable printable label for a field layout.
FieldId
Compile-time identity for a catalogued Eulerian field.
@ FIELD_ID_CELL_SCALAR_AT_CORNER
@ FIELD_ID_CELL_VECTOR_AT_CORNER
Non-owning runtime objects resolved for one field and UserCtx.
PetscErrorCode PicurvPhysicalTime(const SimCtx *simCtx, PetscReal solver_time, PetscReal *physical)
Convert a solver time to physical seconds, t * L_ref / U_ref.
Definition io.c:3177
PetscErrorCode LOG_FIELD_MIN_MAX(UserCtx *user, FieldId field_id)
Implementation of LOG_FIELD_MIN_MAX().
Definition logging.c:2458
@ SEARCH_METRIC_SUM_SEARCH_LOCATED
Definition logging.c:36
@ SEARCH_METRIC_SUM_MAX_TRAVERSAL_FAILS
Definition logging.c:44
@ SEARCH_METRIC_REDUCTION_LEN
Definition logging.c:47
@ SEARCH_METRIC_SUM_BBOX_GUESS_FALLBACK
Definition logging.c:43
@ SEARCH_METRIC_SUM_RESEARCH
Definition logging.c:39
@ SEARCH_METRIC_SUM_TRAVERSAL_STEPS
Definition logging.c:38
@ SEARCH_METRIC_MAX_TRAVERSAL_STEPS
Definition logging.c:45
@ SEARCH_METRIC_SUM_BOUNDARY_CLAMPS
Definition logging.c:41
@ SEARCH_METRIC_SUM_SEARCH_POPULATION
Definition logging.c:35
@ SEARCH_METRIC_MAX_PASS_DEPTH
Definition logging.c:46
@ SEARCH_METRIC_SUM_TIE_BREAKS
Definition logging.c:40
@ SEARCH_METRIC_SUM_SEARCH_LOST
Definition logging.c:37
@ SEARCH_METRIC_SUM_BBOX_GUESS_SUCCESS
Definition logging.c:42
@ SEARCH_METRIC_SUM_SEARCH_ATTEMPTS
Definition logging.c:34
const char * LESTestFilterKernelToString(LESTestFilterKernel kernel)
Implementation of LESTestFilterKernelToString().
Definition logging.c:782
PetscErrorCode EmitStatisticsConsoleSnapshot(UserCtx *user, const SimCtx *simCtx, PetscInt step)
Implementation of EmitStatisticsConsoleSnapshot().
Definition logging.c:2898
void set_allowed_functions(const char **functionList, int count)
Implementation of set_allowed_functions().
Definition logging.c:155
PetscBool always_log
Definition logging.c:1975
static PetscErrorCode AppendStatisticalObservableSample(SimCtx *simCtx, PetscInt samples_before, PetscReal mean_speed, PetscReal mean_ke)
Appends one timestep's scalar observables to the statistical history.
Definition logging.c:1465
static PetscErrorCode LogFieldAnatomyView(UserCtx *user, const char *field_name, const char *stage_name, DM dm, Vec vec_local, PetscInt dof, FieldLayout layout)
Shared architecture-aware anatomy logger for catalog and transient fields.
Definition logging.c:2588
PetscErrorCode LOG_PARTICLE_METRICS(UserCtx *user, const char *stageName)
Implementation of LOG_PARTICLE_METRICS().
Definition logging.c:3458
PetscBool ShouldEmitPeriodicStatisticsConsoleSnapshot(const SimCtx *simCtx, PetscInt completed_step)
Implementation of ShouldEmitPeriodicStatisticsConsoleSnapshot().
Definition logging.c:2887
const char * BCHandlerTypeToString(BCHandlerType handler_type)
Internal helper implementation: BCHandlerTypeToString().
Definition logging.c:889
PetscBool is_function_allowed(const char *functionName)
Implementation of is_function_allowed().
Definition logging.c:186
static PetscInt g_profiler_count
Definition logging.c:1980
PetscErrorCode DualMonitorDestroy(void **ctx)
Implementation of DualMonitorDestroy().
Definition logging.c:926
#define TMP_BUF_SIZE
Definition logging.c:10
static char ** gAllowedFunctions
Global/static array of function names allowed to log.
Definition logging.c:26
static LogLevel current_log_level
Static variable to cache the current logging level.
Definition logging.c:19
PetscErrorCode LOG_INTERPOLATION_ERROR(UserCtx *user)
Implementation of LOG_INTERPOLATION_ERROR().
Definition logging.c:2974
static PetscReal SolutionConvergenceHistoryGet(const PetscReal *history, PetscInt capacity, PetscInt samples_available, PetscInt offset_from_latest)
Reads one sample from the statistical ring buffer by age.
Definition logging.c:1430
PetscBool ShouldEmitPeriodicParticleConsoleSnapshot(const SimCtx *simCtx, PetscInt completed_step)
Implementation of ShouldEmitPeriodicParticleConsoleSnapshot().
Definition logging.c:545
const char * BCFaceToString(BCFace face)
Implementation of BCFaceToString().
Definition logging.c:671
PetscErrorCode FreeAllowedFunctions(char **funcs, PetscInt n)
Internal helper implementation: FreeAllowedFunctions().
Definition logging.c:652
PetscBool IsParticleConsoleSnapshotEnabled(const SimCtx *simCtx)
Implementation of IsParticleConsoleSnapshotEnabled().
Definition logging.c:528
PetscErrorCode print_log_level(void)
Internal helper implementation: print_log_level().
Definition logging.c:119
long long total_call_count
Definition logging.c:1972
static PetscReal SolutionConvergenceSafeRelative(PetscReal numerator, PetscReal denominator)
Forms a guarded relative metric for solution-convergence logging.
Definition logging.c:1044
PetscErrorCode EmitParticleConsoleSnapshot(UserCtx *user, SimCtx *simCtx, PetscInt step)
Implementation of EmitParticleConsoleSnapshot().
Definition logging.c:559
static PetscInt g_profiler_capacity
Definition logging.c:1981
PetscErrorCode ProfilingFinalize(SimCtx *simCtx)
Implementation of ProfilingFinalize().
Definition logging.c:2305
static void BuildRowFormatString(PetscMPIInt wRank, PetscInt wPID, PetscInt wCell, PetscInt wPos, PetscInt wVel, PetscInt wWt, char *fmtStr, size_t bufSize)
Definition logging.c:369
static void trim(char *s)
Remove leading and trailing whitespace from a mutable configuration string.
Definition logging.c:573
PetscErrorCode LoadAllowedFunctionsFromFile(const char filename[], char ***funcsOut, PetscInt *nOut)
Implementation of LoadAllowedFunctionsFromFile().
Definition logging.c:598
#define SOLUTION_CONVERGENCE_REL_EPS
Definition logging.c:1013
static int gNumAllowed
Number of entries in the gAllowedFunctions array.
Definition logging.c:31
double total_time
Definition logging.c:1970
static void BuildHeaderString(char *headerStr, size_t bufSize, PetscMPIInt wRank, PetscInt wPID, PetscInt wCell, PetscInt wPos, PetscInt wVel, PetscInt wWt)
Definition logging.c:382
void PrintProgressBar(PetscInt step, PetscInt startStep, PetscInt totalSteps, PetscReal currentTime)
Internal helper implementation: PrintProgressBar().
Definition logging.c:2411
static const char * SolutionConvergenceModeToString(SolutionConvergenceMode mode)
Maps the internal solution-convergence mode enum to its log label.
Definition logging.c:1678
const char * WallFunctionModelToString(WallFunctionModel model)
Implementation of WallFunctionModelToString().
Definition logging.c:835
static PetscErrorCode _FindOrCreateEntry(const char *func_name, PetscInt *idx)
Find a profiling record by name or allocate and register a new record.
Definition logging.c:1987
static void CellToStr(const PetscInt *cell, char *buf, size_t bufsize)
Definition logging.c:272
PetscErrorCode RuntimeMemoryLogSample(SimCtx *simCtx, PetscInt step, const char *event, const char *reason)
Implementation of RuntimeMemoryLogSample().
Definition logging.c:2195
LogLevel get_log_level()
Implementation of get_log_level().
Definition logging.c:87
PetscErrorCode ProfilingLogTimestepSummary(SimCtx *simCtx, PetscInt step)
Implementation of ProfilingLogTimestepSummary().
Definition logging.c:2100
const char * LESFilterWidthModelToString(LESFilterWidthModel model)
Implementation of LESFilterWidthModelToString().
Definition logging.c:761
PetscErrorCode LOG_FACE_DISTANCES(PetscReal *d)
Implementation of LOG_FACE_DISTANCES().
Definition logging.c:233
PetscErrorCode LOG_PARTICLE_FIELDS(UserCtx *user, PetscInt printInterval)
Implementation of LOG_PARTICLE_FIELDS().
Definition logging.c:400
void _ProfilingEnd(const char *func_name)
Implementation of _ProfilingEnd().
Definition logging.c:2061
static void TripleRealToStr(const PetscReal *arr, char *buf, size_t bufsize)
Definition logging.c:280
static void Int64ToStr(PetscInt64 value, char *buf, size_t bufsize)
Definition logging.c:264
const char * BCTypeToString(BCType type)
Implementation of BCTypeToString().
Definition logging.c:869
PetscErrorCode CalculateAdvancedParticleMetrics(UserCtx *user)
Internal helper implementation: CalculateAdvancedParticleMetrics().
Definition logging.c:3404
const char * ParticleLocationStatusToString(ParticleLocationStatus level)
Implementation of ParticleLocationStatusToString().
Definition logging.c:1953
PetscErrorCode LOG_SCATTER_METRICS(UserCtx *user)
Implementation of LOG_SCATTER_METRICS().
Definition logging.c:3056
static int _CompareProfiledFunctions(const void *a, const void *b)
Order profiling records by their accumulated execution time.
Definition logging.c:2289
PetscErrorCode LOG_SOLUTION_CONVERGENCE(SimCtx *simCtx)
Implementation of LOG_SOLUTION_CONVERGENCE().
Definition logging.c:1695
const char * FlowDirectionToString(FlowDirection fd)
Convert a FlowDirection enum value to its YAML token string.
Definition logging.c:705
PetscErrorCode DualKSPMonitor(KSP ksp, PetscInt it, PetscReal rnorm, void *ctx)
Implementation of DualKSPMonitor().
Definition logging.c:965
PetscErrorCode LOG_CONTINUITY_METRICS(UserCtx *user)
Implementation of LOG_CONTINUITY_METRICS().
Definition logging.c:1891
PetscErrorCode LOG_CORNER_FIELD_ANATOMY(UserCtx *user, FieldId corner_field_id, const char *stage_name)
Emits anatomy for the transient scalar or vector corner-staging field.
Definition logging.c:2947
const char * LESAveragingModeToString(LESAveragingMode mode)
Implementation of LESAveragingModeToString().
Definition logging.c:799
double current_step_time
Definition logging.c:1971
PetscErrorCode LOG_FIELD_ANATOMY(UserCtx *user, FieldId field_id, const char *stage_name)
Resolves a persistent field view and emits its layout-aware boundary anatomy.
Definition logging.c:2861
long long current_step_call_count
Definition logging.c:1973
static PetscErrorCode ComputeStatisticalWindowMetrics(const SimCtx *simCtx, PetscInt samples_available, PetscBool *has_reference_out, PetscReal *mean_speed_window_out, PetscReal *mean_speed_window_prev_out, PetscReal *mean_speed_window_abs_out, PetscReal *mean_speed_window_rel_out, PetscReal *mean_speed_rms_window_out, PetscReal *mean_speed_rms_window_prev_out, PetscReal *mean_speed_rms_window_abs_out, PetscReal *mean_speed_rms_window_rel_out, PetscReal *mean_ke_window_out, PetscReal *mean_ke_window_prev_out, PetscReal *mean_ke_window_abs_out, PetscReal *mean_ke_window_rel_out, PetscReal *mean_ke_rms_window_out, PetscReal *mean_ke_rms_window_prev_out, PetscReal *mean_ke_rms_window_abs_out, PetscReal *mean_ke_rms_window_rel_out)
Computes adjacent-window drift metrics for statistical steady mode.
Definition logging.c:1545
PetscErrorCode LOG_SEARCH_METRICS(UserCtx *user)
Implementation of LOG_SEARCH_METRICS().
Definition logging.c:3245
static ProfiledFunction * g_profiler_registry
Definition logging.c:1979
const char * InitialConditionModeToString(InitialConditionMode mode)
Implementation of InitialConditionModeToString().
Definition logging.c:689
PetscErrorCode ProfilingInitialize(SimCtx *simCtx)
Internal helper implementation: ProfilingInitialize().
Definition logging.c:2023
const char * LESModelToString(LESModelType LESFlag)
Implementation of LESModelToString().
Definition logging.c:741
PetscErrorCode LOG_CELL_VERTICES(const Cell *cell, PetscMPIInt rank)
Implementation of LOG_CELL_VERTICES().
Definition logging.c:208
static void IntToStr(int value, char *buf, size_t bufsize)
Definition logging.c:256
static PetscErrorCode ComputeDeterministicSolutionMetrics(SimCtx *simCtx, PetscBool periodic_mode, PetscInt phase_step, PetscInt samples_before, PetscBool *has_reference_out, PetscReal *u_abs_l2_out, PetscReal *u_rel_l2_out, PetscReal *p_abs_l2_out, PetscReal *p_rel_l2_out, PetscReal *mean_speed_out, PetscReal *mean_speed_ref_out, PetscReal *mean_speed_abs_out, PetscReal *mean_speed_rel_out, PetscReal *mean_ke_out, PetscReal *mean_ke_ref_out, PetscReal *mean_ke_abs_out, PetscReal *mean_ke_rel_out)
Computes deterministic solution-drift metrics for the current step.
Definition logging.c:1187
PetscErrorCode ProfilingResetTimestepCounters(void)
Implementation of ProfilingResetTimestepCounters().
Definition logging.c:2083
PetscBool IsStatisticsConsoleSnapshotEnabled(const SimCtx *simCtx)
Implementation of IsStatisticsConsoleSnapshotEnabled().
Definition logging.c:2877
PetscErrorCode ResetSearchMetrics(SimCtx *simCtx)
Implementation of ResetSearchMetrics().
Definition logging.c:3214
const char * LESClipModeToString(LESClipMode mode)
Implementation of LESClipModeToString().
Definition logging.c:817
const char * MomentumSolverTypeToString(MomentumSolverType SolverFlag)
Implementation of MomentumSolverTypeToString().
Definition logging.c:853
static PetscErrorCode ComputeMaxColumnWidths(PetscInt nParticles, const PetscMPIInt *ranks, const PetscInt64 *pids, const PetscInt *cellIDs, const PetscReal *positions, const PetscReal *velocities, const PetscReal *weights, int *wRank, int *wPID, int *wCell, int *wPos, int *wVel, int *wWt)
Definition logging.c:305
PetscErrorCode PicurvOpenDiagnosticsCsv(const SimCtx *simCtx, const char *filename, const char *header, FILE **file)
Implementation of PicurvOpenDiagnosticsCsv().
Definition logging.c:3503
static PetscErrorCode ComputeCurrentFlowObservables(SimCtx *simCtx, PetscReal *mean_speed_out, PetscReal *mean_ke_out)
Computes instantaneous global flow observables for statistical mode.
Definition logging.c:1071
#define SOLUTION_CONVERGENCE_FLUID_THRESHOLD
Definition logging.c:1012
double start_time
Definition logging.c:1974
const char * ParticleInitializationToString(ParticleInitializationType ParticleInitialization)
Implementation of ParticleInitializationToString().
Definition logging.c:724
void _ProfilingStart(const char *func_name)
Implementation of _ProfilingStart().
Definition logging.c:2047
static void SearchMetricsReduceOp(void *invec, void *inoutvec, int *len, MPI_Datatype *datatype)
Internal reduction callback for packed search metrics.
Definition logging.c:54
const char * name
Definition logging.c:1969
Logging utilities and macros for PETSc-based applications.
#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
PetscBool log_to_console
Definition logging.h:58
#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
#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
PetscReal bnorm
Definition logging.h:59
PetscInt step
Definition logging.h:60
#define LOG(scope, level, fmt,...)
Logging macro for PETSc-based applications with scope control.
Definition logging.h:84
LogLevel
Enumeration of logging levels.
Definition logging.h:28
@ 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
FILE * file_handle
Definition logging.h:57
PetscInt block_id
Definition logging.h:61
Context for a dual-purpose KSP monitor.
Definition logging.h:56
Typed identities and metadata for persistent solver-particle fields.
const char * ParticleFieldName(ParticleFieldId field_id)
Return the canonical PETSc DMSwarm name for an ID.
@ PARTICLE_FIELD_ID_WEIGHT
@ PARTICLE_FIELD_ID_POSITION
@ PARTICLE_FIELD_ID_PID
@ PARTICLE_FIELD_ID_CELL_ID
@ PARTICLE_FIELD_ID_PSI
@ PARTICLE_FIELD_ID_RANK
@ PARTICLE_FIELD_ID_VELOCITY
Per-window PETSc accumulator storage and pointwise application.
PetscErrorCode PicurvWindowValidFractionRange(UserCtx *user, const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, PetscInt sample_count, PetscReal *minimum, PetscReal *maximum)
Reports the range of per-point valid fraction across a window's domain.
Window lifecycle, scheduling, and weighting for the field-statistics pipeline.
PetscInt sample_count
PicurvWindowState state
PetscReal total_weight
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
PetscBool FieldStatisticsIsActive(const struct SimCtx *simCtx)
Reports whether this run has live field-statistics state.
PetscReal PicurvWindowProgress(const PicurvWindow *window)
Reports the fraction of a bounded window's span that has been represented.
PetscReal represented_time
Physical time the window covers.
Runtime state of one window.
LESModelType
Identifies the subgrid-scale closure evaluated during a timestep.
Definition variables.h:548
@ DYNAMIC_SMAGORINSKY
Definition variables.h:551
@ VREMAN
Definition variables.h:552
@ NO_LES_MODEL
Definition variables.h:549
@ WALE
Definition variables.h:553
@ CONSTANT_SMAGORINSKY
Definition variables.h:550
PetscInt fieldStatisticsWindowCount
Definition variables.h:932
BCType
Defines the general mathematical/physical Category of a boundary.
Definition variables.h:309
@ INLET
Definition variables.h:316
@ INTERFACE
Definition variables.h:311
@ FARFIELD
Definition variables.h:317
@ OUTLET
Definition variables.h:315
@ PERIODIC
Definition variables.h:318
@ WALL
Definition variables.h:312
PetscBool continueMode
Definition variables.h:876
UserCtx * user
Definition variables.h:729
PetscBool profilingFinalSummary
Definition variables.h:1036
PetscMPIInt rank
Definition variables.h:862
char profilingTimestepFile[PETSC_MAX_PATH_LEN]
Definition variables.h:1035
PetscInt64 searchLocatedCount
Definition variables.h:267
PetscInt statisticsConsoleOutputFreq
Definition variables.h:934
PetscInt block_number
Definition variables.h:952
PetscInt64 searchLostCount
Definition variables.h:268
Vec * solutionConvergencePeriodicPRef
Definition variables.h:1123
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:1077
ParticleInitializationType
Enumerator to identify the particle initialization strategy.
Definition variables.h:709
@ PARTICLE_INIT_SURFACE_RANDOM
Random placement on the inlet face.
Definition variables.h:710
@ PARTICLE_INIT_SURFACE_EDGES
Deterministic placement at inlet face edges.
Definition variables.h:713
@ PARTICLE_INIT_POINT_SOURCE
All particles at a fixed (psrc_x,psrc_y,psrc_z) — for validation.
Definition variables.h:712
@ PARTICLE_INIT_VOLUME
Random volumetric distribution across the domain.
Definition variables.h:711
ParticleLocationStatus
Defines the state of a particle with respect to its location and migration status during the iterativ...
Definition variables.h:163
@ LOST
Definition variables.h:167
@ NEEDS_LOCATION
Definition variables.h:164
@ ACTIVE_AND_LOCATED
Definition variables.h:165
@ UNINITIALIZED
Definition variables.h:168
@ MIGRATING_OUT
Definition variables.h:166
PetscReal * solutionConvergenceMeanSpeedHistory
Definition variables.h:928
PetscReal FluxOutSum
Definition variables.h:959
PetscBool runtimeMemoryLogEnabled
Enable the rank-reduced runtime memory log.
Definition variables.h:1051
PetscInt64 boundaryClampCount
Definition variables.h:274
PetscInt particlesLostLastStep
Definition variables.h:997
PetscInt KM
Definition variables.h:1088
UserMG usermg
Definition variables.h:1015
Vec * solutionConvergencePeriodicUcatRef
Definition variables.h:1122
PetscInt64 traversalStepsSum
Definition variables.h:269
BCHandlerType
Defines the specific computational "strategy" for a boundary handler.
Definition variables.h:329
@ BC_HANDLER_PERIODIC_GEOMETRIC
Definition variables.h:340
@ BC_HANDLER_INLET_PARABOLIC
Definition variables.h:335
@ BC_HANDLER_INLET_CONSTANT_VELOCITY
Definition variables.h:334
@ BC_HANDLER_PERIODIC_DRIVEN_INITIAL_FLUX
Definition variables.h:343
@ BC_HANDLER_INTERFACE_OVERSET
Definition variables.h:341
@ BC_HANDLER_PERIODIC_DRIVEN_CONSTANT_FLUX
Definition variables.h:342
@ BC_HANDLER_WALL_MOVING
Definition variables.h:332
@ BC_HANDLER_INLET_PROFILE_FROM_FILE
Definition variables.h:336
@ BC_HANDLER_WALL_NOSLIP
Definition variables.h:331
@ BC_HANDLER_OUTLET_CONSERVATION
Definition variables.h:338
@ BC_HANDLER_FARFIELD_NONREFLECTING
Definition variables.h:337
@ BC_HANDLER_OUTLET_PRESSURE
Definition variables.h:339
@ BC_HANDLER_SYMMETRY_PLANE
Definition variables.h:333
@ BC_HANDLER_UNDEFINED
Definition variables.h:330
PetscInt solutionConvergenceSamplesRecorded
Definition variables.h:927
PetscReal poissonSourceImbalance
Definition variables.h:1026
PetscInt _this
Definition variables.h:1092
PetscInt64 searchPopulation
Definition variables.h:266
PetscBool solutionConvergenceEnabled
Definition variables.h:923
PetscReal * solutionConvergenceMeanKEHistory
Definition variables.h:929
char runtimeMemoryLogFile[PETSC_MAX_PATH_LEN]
File name written under log_dir.
Definition variables.h:1052
PetscBool runtimeMemoryLogStarted
True after rank 0 writes the log header.
Definition variables.h:1053
PetscInt occupiedCellCount
Definition variables.h:1003
LESClipMode
Selects the admissible range imposed on the dynamic model coefficient.
Definition variables.h:617
@ LES_CLIP_CLIP_NEGATIVE
Definition variables.h:619
@ LES_CLIP_CLAMP
Definition variables.h:618
@ LES_CLIP_NONE
Definition variables.h:620
char profilingTimestepMode[32]
Definition variables.h:1034
LESTestFilterKernel
Selects the discrete test-filter kernel used by the dynamic procedure.
Definition variables.h:591
@ LES_TEST_FILTER_SIMPSON_IK
Definition variables.h:593
@ LES_TEST_FILTER_VOLUME_WEIGHTED_BOX
Definition variables.h:592
WallFunctionModel
Selects the wall model applied on WALL faces.
Definition variables.h:565
@ WALL_FUNCTION_CABOT
Definition variables.h:569
@ WALL_FUNCTION_LOG_LAW
Definition variables.h:567
@ WALL_FUNCTION_WERNER
Definition variables.h:568
@ WALL_FUNCTION_NONE
Definition variables.h:566
PetscInt currentSettlementPass
Definition variables.h:278
PetscInt np
Definition variables.h:990
PetscInt StartStep
Definition variables.h:869
MomentumSolverType
Enumerator to identify the implemented momentum solver strategies.
Definition variables.h:692
@ MOMENTUM_SOLVER_DUALTIME_PICARD_JAMESON_RK
Definition variables.h:694
@ MOMENTUM_SOLVER_EXPLICIT_RK
Definition variables.h:693
@ MOMENTUM_SOLVER_NEWTON_KRYLOV
Definition variables.h:695
PetscInt solutionConvergencePeriodSteps
Definition variables.h:925
PetscScalar x
Definition variables.h:122
PetscInt64 reSearchCount
Definition variables.h:270
PetscReal MaxDiv
Definition variables.h:1027
PetscInt64 bboxGuessFallbackCount
Definition variables.h:276
Vec Ucat_o
Definition variables.h:1120
PetscInt MaxDivx
Definition variables.h:1028
PetscInt MaxDivy
Definition variables.h:1028
char analysis_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:883
PetscInt64 bboxGuessSuccessCount
Definition variables.h:275
PetscInt MaxDivz
Definition variables.h:1028
struct PicurvWindow * fieldStatisticsWindows
Definition variables.h:933
char log_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:882
PetscInt MaxDivFlatArg
Definition variables.h:1028
PetscReal FluxInSum
Definition variables.h:959
PetscInt64 maxParticlePassDepth
Definition variables.h:277
PetscInt64 maxTraversalSteps
Definition variables.h:271
PetscScalar z
Definition variables.h:122
Vec ParticleCount
Definition variables.h:1171
PetscInt JM
Definition variables.h:1088
LESAveragingMode
Selects the set over which the Germano contractions are averaged.
Definition variables.h:604
@ LES_AVERAGING_LOCAL
Definition variables.h:605
@ LES_AVERAGING_GLOBAL
Definition variables.h:607
@ LES_AVERAGING_HOMOGENEOUS
Definition variables.h:606
PetscBool runtimeMemoryLogHasPrevious
True after the first process-memory sample.
Definition variables.h:1054
PetscInt mglevels
Definition variables.h:736
char ** profilingSelectedFuncs
Definition variables.h:1032
PetscInt solutionConvergenceWindowSteps
Definition variables.h:926
FlowDirection
Primary flow direction for streamwise IC and Poiseuille modes.
Definition variables.h:298
@ FLOW_DIR_NEG_ZETA
Definition variables.h:304
@ FLOW_DIR_NEG_ETA
Definition variables.h:302
@ FLOW_DIR_POS_ZETA
Definition variables.h:303
@ FLOW_DIR_POS_XI
Definition variables.h:299
@ FLOW_DIR_NEG_XI
Definition variables.h:300
@ FLOW_DIR_POS_ETA
Definition variables.h:301
PetscInt particlesLostCumulative
Definition variables.h:998
PetscInt nProfilingSelectedFuncs
Definition variables.h:1033
PetscInt particlesMigratedLastStep
Definition variables.h:1002
struct PicurvWindowStorage * fieldStatisticsStorage
Definition variables.h:1136
InitialConditionMode
Selects the algorithm used to populate a fresh Eulerian velocity field.
Definition variables.h:177
@ IC_MODE_CONSTANT_CARTESIAN
Definition variables.h:179
@ IC_MODE_POISEUILLE
Definition variables.h:180
@ IC_MODE_CONSTANT_STREAMWISE
Definition variables.h:181
@ IC_MODE_FILE
Definition variables.h:182
@ IC_MODE_ZERO
Definition variables.h:178
PetscInt particleConsoleOutputFreq
Definition variables.h:872
SearchMetricsState searchMetrics
Definition variables.h:1005
PetscReal runtimeMemoryLogPreviousProcessMB
Previous local process memory sample in MB.
Definition variables.h:1055
PetscInt step
Definition variables.h:867
DMDALocalInfo info
Definition variables.h:1086
PetscInt migrationPassesLastStep
Definition variables.h:1001
PetscScalar y
Definition variables.h:122
@ EXEC_MODE_SOLVER
Definition variables.h:832
@ EXEC_MODE_POSTPROCESSOR
Definition variables.h:833
PetscInt IM
Definition variables.h:1088
@ TOP
Definition variables.h:173
@ FRONT
Definition variables.h:173
@ BOTTOM
Definition variables.h:173
@ BACK
Definition variables.h:173
@ LEFT
Definition variables.h:173
@ RIGHT
Definition variables.h:173
MGCtx * mgctx
Definition variables.h:739
SolutionConvergenceMode
Selects the runtime solution-convergence diagnostics mode.
Definition variables.h:701
@ SOLUTION_CONVERGENCE_TRANSIENT
Definition variables.h:705
@ SOLUTION_CONVERGENCE_PERIODIC_DETERMINISTIC
Definition variables.h:703
@ SOLUTION_CONVERGENCE_STATISTICAL_STEADY
Definition variables.h:704
@ SOLUTION_CONVERGENCE_STEADY_DETERMINISTIC
Definition variables.h:702
SolutionConvergenceMode solutionConvergenceMode
Definition variables.h:924
PetscReal particlesLostScalarLastStep
Sum of Psi over the particles removed this step.
Definition variables.h:999
PetscInt64 searchAttempts
Definition variables.h:265
ExecutionMode exec_mode
Definition variables.h:878
PetscInt64 tieBreakCount
Definition variables.h:273
PetscReal ti
Definition variables.h:868
PetscInt64 maxTraversalFailCount
Definition variables.h:272
LESFilterWidthModel
Selects how a cell's grid filter width is derived from its metrics.
Definition variables.h:579
@ LES_FILTER_WIDTH_SCOTTI
Definition variables.h:583
@ LES_FILTER_WIDTH_GEOMETRIC_MEAN
Definition variables.h:581
@ LES_FILTER_WIDTH_CUBE_ROOT_VOLUME
Definition variables.h:580
@ LES_FILTER_WIDTH_MAX_EDGE
Definition variables.h:582
PetscInt LoggingFrequency
Definition variables.h:1020
Cmpnts vertices[8]
Coordinates of the eight vertices of the cell.
Definition variables.h:204
PetscReal particleLoadImbalance
Definition variables.h:1004
BCFace
Identifies the six logical faces of a structured computational block.
Definition variables.h:287
@ BC_FACE_NEG_X
Definition variables.h:288
@ BC_FACE_POS_Z
Definition variables.h:290
@ BC_FACE_POS_Y
Definition variables.h:289
@ BC_FACE_NEG_Z
Definition variables.h:290
@ BC_FACE_POS_X
Definition variables.h:288
@ BC_FACE_NEG_Y
Definition variables.h:289
Defines the vertices of a single hexahedral grid cell.
Definition variables.h:203
A 3D point or vector with PetscScalar components.
Definition variables.h:121
The master context for the entire simulation.
Definition variables.h:859
User-defined context containing data specific to a single computational grid level.
Definition variables.h:1074
PetscBool VerificationScalarOverrideActive(const SimCtx *simCtx)
Reports whether a verification-only scalar override is active.