PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
setup.c
Go to the documentation of this file.
1/**
2 * @file setup.c // Setup code for running any simulation
3 * @brief Test program for DMSwarm interpolation using the fdf-curvIB method.
4 * Provides the setup to start any simulation with DMSwarm and DMDAs.
5 **/
6
7#include <ctype.h>
8#include <errno.h>
9
10#include "setup.h"
11#include "statistics_config.h"
15#include <unistd.h>
16#include <limits.h>
17#include <stdlib.h>
18
19#define PICURV_STRINGIZE_(x) #x
20#define PICURV_STRINGIZE(x) PICURV_STRINGIZE_(x)
21#if defined(PETSC_USE_DEBUG)
22#define PICURV_PETSC_MODE "debug"
23#else
24#define PICURV_PETSC_MODE "optimized"
25#endif
26#ifndef PETSC_ARCH
27#define PETSC_ARCH ""
28#endif
29#ifndef PETSC_DIR
30#define PETSC_DIR ""
31#endif
32#define PICURV_PETSC_STAMP_MARKER "PICURV_PETSC_BUILD:"
33
34/* The PETSc this binary was compiled against. It is printed by --version and also read
35 * from the file by the conductor, which needs it most when a library mismatch keeps the
36 * binary from starting at all. */
37static const char picurv_petsc_build_stamp[] =
39 PICURV_STRINGIZE(PETSC_VERSION_MAJOR) "." PICURV_STRINGIZE(PETSC_VERSION_MINOR) "."
40 PICURV_STRINGIZE(PETSC_VERSION_SUBMINOR) " " PICURV_PETSC_MODE " " PETSC_ARCH " " PETSC_DIR;
41
42/**
43 * @brief Implementation of \ref PicurvHandleVersionArgument().
44 * @details The public argument and return contract is documented in `include/setup.h`.
45 * @see PicurvHandleVersionArgument()
46 */
47int PicurvHandleVersionArgument(int argc, char **argv, const char *executable_name)
48{
49 for (int index = 1; index < argc; ++index) {
50 if (!strcmp(argv[index], "--version") || !strcmp(argv[index], "-version")) {
51 printf("%s %s+g%.12s%s\n", executable_name, PICURV_RELEASE_VERSION,
53 !strcmp(PICURV_BUILD_DIRTY, "true") ? ".dirty" : "");
54 printf("%s\n", picurv_petsc_build_stamp + sizeof(PICURV_PETSC_STAMP_MARKER) - 1);
55 return 1;
56 }
57 }
58 return 0;
59}
60
61/**
62 * @brief Implementation of \ref RuntimeWalltimeGuardParsePositiveSeconds().
63 * @details Full API contract (arguments, ownership, side effects) is documented with
64 * the header declaration in `include/setup.h`.
65 * @see RuntimeWalltimeGuardParsePositiveSeconds()
66 */
67PetscBool RuntimeWalltimeGuardParsePositiveSeconds(const char *text, PetscReal *seconds_out)
68{
69 char *endptr = NULL;
70 double parsed_value;
71
72 if (seconds_out) *seconds_out = 0.0;
73 if (!text || text[0] == '\0') return PETSC_FALSE;
74
75 errno = 0;
76 parsed_value = strtod(text, &endptr);
77 if (endptr == text || errno == ERANGE || !isfinite(parsed_value) || parsed_value <= 0.0) {
78 return PETSC_FALSE;
79 }
80
81 while (*endptr != '\0' && isspace((unsigned char)*endptr)) {
82 endptr++;
83 }
84 if (*endptr != '\0') return PETSC_FALSE;
85
86 if (seconds_out) *seconds_out = (PetscReal)parsed_value;
87 return PETSC_TRUE;
88}
89
90/**
91 * @brief Implementation of \ref InitializeSolutionConvergenceState().
92 * @details Full API contract (arguments, ownership, side effects) is documented with
93 * the header declaration in `include/setup.h`.
94 * @see InitializeSolutionConvergenceState()
95 */
97{
98 UserCtx *user = NULL;
99 PetscInt history_capacity = 0;
100
101 PetscFunctionBeginUser;
102 if (!simCtx) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "SimCtx cannot be NULL.");
103 if (simCtx->exec_mode != EXEC_MODE_SOLVER) PetscFunctionReturn(0);
104 if (!simCtx->solutionConvergenceEnabled) PetscFunctionReturn(0);
105 if (!simCtx->usermg.mgctx) {
106 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
107 "Multigrid hierarchy must exist before initializing solution convergence storage.");
108 }
109
110 user = simCtx->usermg.mgctx[simCtx->usermg.mglevels - 1].user;
111 if (!user) {
112 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
113 "Finest-level UserCtx must exist before initializing solution convergence storage.");
114 }
115
117
119 for (PetscInt bi = 0; bi < simCtx->block_number; ++bi) {
120 PetscCall(PetscCalloc1((size_t)simCtx->solutionConvergencePeriodSteps,
122 PetscCall(PetscCalloc1((size_t)simCtx->solutionConvergencePeriodSteps,
124 for (PetscInt phase = 0; phase < simCtx->solutionConvergencePeriodSteps; ++phase) {
125 PetscCall(VecDuplicate(user[bi].Ucat, &user[bi].solutionConvergencePeriodicUcatRef[phase]));
126 PetscCall(VecSet(user[bi].solutionConvergencePeriodicUcatRef[phase], 0.0));
127 PetscCall(VecDuplicate(user[bi].P, &user[bi].solutionConvergencePeriodicPRef[phase]));
128 PetscCall(VecSet(user[bi].solutionConvergencePeriodicPRef[phase], 0.0));
129 }
130 }
131 }
132
134 history_capacity = 2 * simCtx->solutionConvergenceWindowSteps;
135 PetscCall(PetscCalloc1((size_t)history_capacity, &simCtx->solutionConvergenceMeanSpeedHistory));
136 PetscCall(PetscCalloc1((size_t)history_capacity, &simCtx->solutionConvergenceMeanKEHistory));
137 }
138
139 PetscFunctionReturn(0);
140}
141
142/**
143 * @brief Implementation of \ref DestroySolutionConvergenceState().
144 * @details Full API contract (arguments, ownership, side effects) is documented with
145 * the header declaration in `include/setup.h`.
146 * @see DestroySolutionConvergenceState()
147 */
149{
150 UserCtx *user = NULL;
151
152 PetscFunctionBeginUser;
153 if (!simCtx) PetscFunctionReturn(0);
154 if (simCtx->fieldStatisticsWindows) {
155 PetscCall(PetscFree(simCtx->fieldStatisticsWindows));
156 simCtx->fieldStatisticsWindows = NULL;
157 simCtx->fieldStatisticsWindowCount = 0;
158 }
159 if (!simCtx->usermg.mgctx) PetscFunctionReturn(0);
160
161 user = simCtx->usermg.mgctx[simCtx->usermg.mglevels - 1].user;
162 if (user) {
163 for (PetscInt bi = 0; bi < simCtx->block_number; ++bi) {
164 if (user[bi].solutionConvergencePeriodicUcatRef) {
165 for (PetscInt phase = 0; phase < simCtx->solutionConvergencePeriodSteps; ++phase) {
166 if (user[bi].solutionConvergencePeriodicUcatRef[phase]) {
167 PetscCall(VecDestroy(&user[bi].solutionConvergencePeriodicUcatRef[phase]));
168 }
169 }
170 PetscCall(PetscFree(user[bi].solutionConvergencePeriodicUcatRef));
172 }
173 if (user[bi].solutionConvergencePeriodicPRef) {
174 for (PetscInt phase = 0; phase < simCtx->solutionConvergencePeriodSteps; ++phase) {
175 if (user[bi].solutionConvergencePeriodicPRef[phase]) {
176 PetscCall(VecDestroy(&user[bi].solutionConvergencePeriodicPRef[phase]));
177 }
178 }
179 PetscCall(PetscFree(user[bi].solutionConvergencePeriodicPRef));
180 user[bi].solutionConvergencePeriodicPRef = NULL;
181 }
182 }
183 }
184
186 PetscCall(PetscFree(simCtx->solutionConvergenceMeanSpeedHistory));
188 }
190 PetscCall(PetscFree(simCtx->solutionConvergenceMeanKEHistory));
192 }
194
195 PetscFunctionReturn(0);
196}
197
198#undef __FUNCT__
199#define __FUNCT__ "LESConfigSetDefaults"
200/**
201 * @brief Implementation of \ref LESConfigSetDefaults().
202 * @details Full API contract is documented with the header declaration in
203 * `include/setup.h`.
204 * @see LESConfigSetDefaults()
205 */
206PetscErrorCode LESConfigSetDefaults(LESConfig *config)
207{
208 PetscFunctionBeginUser;
209 PetscCheck(config != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
210 "LES configuration destination cannot be NULL.");
211
212 config->dynamic_frequency = 1;
213 config->constant_cs = 0.03;
214 // Vreman (2004) recommends c = 2.5 Cs^2, 0.07 for Cs = 0.17; Nicoud & Ducros (1999)
215 // calibrate C_w near 0.5-0.6 against the same isotropic decay. Both are calibrations
216 // from isotropic turbulence, not universal values.
217 config->vreman_coefficient = 0.07;
218 config->wale_coefficient = 0.5;
221 config->test_filter_width_ratio = 2.0;
222 // Local averaging makes no homogeneity claim, so it is the only default that is
223 // correct on every geometry. Homogeneous and global averaging are opted into.
225 config->averaging_direction[0] = PETSC_FALSE;
226 config->averaging_direction[1] = PETSC_FALSE;
227 config->averaging_direction[2] = PETSC_FALSE;
228 config->clip_mode = LES_CLIP_CLAMP;
229 // Roughly twice the physical Smagorinsky constant: high enough to catch a
230 // diverging coefficient, low enough that it does not shape the distribution.
231 config->max_cs = 0.3;
232 config->min_viscosity_ratio = 0.0;
233 config->yoshizawa_ci = 0.09;
234 config->diagnostics_enabled = PETSC_FALSE;
235 config->diagnostics_cadence = 1;
236
237 PetscFunctionReturn(0);
238}
239
240#undef __FUNCT__
241#define __FUNCT__ "ParseLESConfiguration"
242/**
243 * @brief Reads every LES closure parameter from the generated control file.
244 *
245 * Grouped into one routine because the parameters constrain one another: the
246 * averaging directions only mean something under homogeneous averaging, and the
247 * clip ceiling only under clamping. Selectors arrive as integers that the Python
248 * layer has already normalized from their user-facing names, which is the same
249 * contract `-les`, `-pinit`, and `-interpolation_method` follow.
250 *
251 * Values that would make the closure ill-defined are rejected here as well as in
252 * Python, so a hand-written control file fails with a reason rather than producing
253 * a plausible-looking coefficient.
254 *
255 * @param[in,out] simCtx Simulation context whose `les_config` is populated.
256 * @return PetscErrorCode 0 on success, or `PETSC_ERR_ARG_OUTOFRANGE` for a value
257 * outside its admissible range.
258 */
259static PetscErrorCode ParseLESConfiguration(SimCtx *simCtx)
260{
261 LESConfig *config = &simCtx->les_config;
262 PetscInt selector;
263 char directions[8] = "";
264 PetscBool found = PETSC_FALSE;
265
266 PetscFunctionBeginUser;
267
268 PetscCall(PetscOptionsGetReal(NULL, NULL, "-les_constant_cs", &config->constant_cs, NULL));
269 PetscCall(PetscOptionsGetReal(NULL, NULL, "-les_vreman_coefficient", &config->vreman_coefficient, NULL));
270 PetscCall(PetscOptionsGetReal(NULL, NULL, "-les_wale_coefficient", &config->wale_coefficient, NULL));
271 PetscCall(PetscOptionsGetInt(NULL, NULL, "-les_dynamic_frequency", &config->dynamic_frequency, NULL));
272
273 selector = (PetscInt)config->filter_width_model;
274 PetscCall(PetscOptionsGetInt(NULL, NULL, "-les_filter_width", &selector, NULL));
275 PetscCheck(selector >= LES_FILTER_WIDTH_CUBE_ROOT_VOLUME && selector <= LES_FILTER_WIDTH_SCOTTI,
276 PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
277 "-les_filter_width must be 0 (cube_root_volume), 1 (geometric_mean), 2 (max_edge), or 3 (scotti); received %" PetscInt_FMT ".",
278 selector);
279 config->filter_width_model = (LESFilterWidthModel)selector;
280
281 selector = (PetscInt)config->test_filter_kernel;
282 PetscCall(PetscOptionsGetInt(NULL, NULL, "-les_test_filter_kernel", &selector, NULL));
283 PetscCheck(selector >= LES_TEST_FILTER_VOLUME_WEIGHTED_BOX && selector <= LES_TEST_FILTER_SIMPSON_IK,
284 PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
285 "-les_test_filter_kernel must be 0 (volume_weighted_box) or 1 (simpson_ik); received %" PetscInt_FMT ".",
286 selector);
287 config->test_filter_kernel = (LESTestFilterKernel)selector;
288
289 PetscCall(PetscOptionsGetReal(NULL, NULL, "-les_test_filter_width_ratio",
290 &config->test_filter_width_ratio, NULL));
291 PetscCheck(config->test_filter_width_ratio > 1.0, PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
292 "-les_test_filter_width_ratio must exceed 1; a test filter no wider than the grid "
293 "filter leaves the dynamic procedure nothing to measure. Received %g.",
294 (double)config->test_filter_width_ratio);
295
296 selector = (PetscInt)config->averaging_mode;
297 PetscCall(PetscOptionsGetInt(NULL, NULL, "-les_averaging_mode", &selector, NULL));
298 PetscCheck(selector >= LES_AVERAGING_LOCAL && selector <= LES_AVERAGING_GLOBAL,
299 PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
300 "-les_averaging_mode must be 0 (local), 1 (homogeneous), or 2 (global); received %" PetscInt_FMT ".",
301 selector);
302 config->averaging_mode = (LESAveragingMode)selector;
303
304 // A subset of "ijk". Left empty under homogeneous averaging, the periodic axes
305 // are used instead, which is what makes the common cases need no configuration.
306 PetscCall(PetscOptionsGetString(NULL, NULL, "-les_averaging_directions",
307 directions, sizeof(directions), &found));
308 if (found) {
309 for (size_t index = 0; directions[index] != '\0'; index++) {
310 switch (directions[index]) {
311 case 'i': config->averaging_direction[0] = PETSC_TRUE; break;
312 case 'j': config->averaging_direction[1] = PETSC_TRUE; break;
313 case 'k': config->averaging_direction[2] = PETSC_TRUE; break;
314 default:
315 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
316 "-les_averaging_directions accepts only the characters i, j, and k; "
317 "received '%s'.", directions);
318 }
319 }
320 }
321
322 selector = (PetscInt)config->clip_mode;
323 PetscCall(PetscOptionsGetInt(NULL, NULL, "-les_clip_mode", &selector, NULL));
324 PetscCheck(selector >= LES_CLIP_CLAMP && selector <= LES_CLIP_NONE,
325 PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
326 "-les_clip_mode must be 0 (clamp), 1 (clip_negative), or 2 (none); received %" PetscInt_FMT ".",
327 selector);
328 config->clip_mode = (LESClipMode)selector;
329
330 PetscCall(PetscOptionsGetReal(NULL, NULL, "-les_clip_max_cs", &config->max_cs, NULL));
331 PetscCall(PetscOptionsGetReal(NULL, NULL, "-les_min_viscosity_ratio",
332 &config->min_viscosity_ratio, NULL));
333 PetscCall(PetscOptionsGetReal(NULL, NULL, "-les_yoshizawa_ci", &config->yoshizawa_ci, NULL));
334 PetscCall(PetscOptionsGetBool(NULL, NULL, "-les_diagnostics",
335 &config->diagnostics_enabled, NULL));
336 PetscCall(PetscOptionsGetInt(NULL, NULL, "-les_diagnostics_cadence",
337 &config->diagnostics_cadence, NULL));
338
339 PetscCheck(config->dynamic_frequency > 0, PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
340 "-les_dynamic_frequency must be positive; received %" PetscInt_FMT ".",
341 config->dynamic_frequency);
342 PetscCheck(config->constant_cs >= 0.0, PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
343 "-les_constant_cs must be nonnegative; received %g.", (double)config->constant_cs);
344 PetscCheck(config->vreman_coefficient >= 0.0, PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
345 "-les_vreman_coefficient must be nonnegative; received %g.",
346 (double)config->vreman_coefficient);
347 PetscCheck(config->wale_coefficient >= 0.0, PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
348 "-les_wale_coefficient must be nonnegative; received %g.",
349 (double)config->wale_coefficient);
350 PetscCheck(config->max_cs >= 0.0, PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
351 "-les_clip_max_cs must be nonnegative; received %g.", (double)config->max_cs);
352 PetscCheck(config->min_viscosity_ratio >= 0.0, PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
353 "-les_min_viscosity_ratio must be nonnegative; received %g.",
354 (double)config->min_viscosity_ratio);
355 PetscCheck(config->diagnostics_cadence > 0, PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
356 "-les_diagnostics_cadence must be positive; received %" PetscInt_FMT ".",
357 config->diagnostics_cadence);
358
359 PetscFunctionReturn(0);
360}
361
362#undef __FUNCT__
363#define __FUNCT__ "CreateSimulationContext"
364
365/**
366 * @brief Implementation of \ref CreateSimulationContext().
367 * @details Full API contract (arguments, ownership, side effects) is documented with
368 * the header declaration in `include/setup.h`.
369 * @see CreateSimulationContext()
370 */
371PetscErrorCode CreateSimulationContext(int argc, char **argv, SimCtx **p_simCtx)
372{
373 PetscErrorCode ierr;
374 (void)argc;
375 (void)argv;
376 SimCtx *simCtx;
377 char control_filename[PETSC_MAX_PATH_LEN] = ""; // Temporary placeholder for control file name.
378 PetscBool control_flg; // Temporary placeholder for control file tag existence check flag.
379 PetscBool particle_console_output_freq_flg = PETSC_FALSE;
380
381 PetscFunctionBeginUser;
382
384
385 // === 1. Allocate the Context Struct and Set ALL Defaults ==================
386 ierr = PetscNew(p_simCtx); CHKERRQ(ierr);
387 simCtx = *p_simCtx;
388
389 // --- Group 1: Parallelism & MPI ---
390 simCtx->rank = 0; simCtx->size = 1;
391
392 // --- Group 2: Simulation Control, Time, and I/O ---
393 simCtx->step = 0; simCtx->ti = 0.0; simCtx->StartStep = 0; simCtx->StepsToRun = 10;
394 simCtx->tiout = 10; simCtx->particleConsoleOutputFreq = simCtx->tiout;
395 simCtx->StartTime = 0.0; simCtx->dt = 0.001;
396 simCtx->OnlySetup = PETSC_FALSE;
397 simCtx->continueMode = PETSC_FALSE;
398 simCtx->logviewer = NULL;
399 strcpy(simCtx->eulerianSource,"solve");
400 strcpy(simCtx->restart_dir,"restart");
401 strcpy(simCtx->output_dir,"output");
402 strcpy(simCtx->log_dir,"logs");
403 strcpy(simCtx->analysis_dir,"output/analysis/metrics");
404 simCtx->_io_context_buffer[0] = '\0';
405 simCtx->current_io_directory = NULL;
406 simCtx->checkpointGeometrySHA256[0] = '\0';
407 simCtx->checkpointGeometryHashReady = PETSC_FALSE;
408 simCtx->restartHistoryAvailable = PETSC_FALSE;
409
410 // --- Group 3: High-Level Physics & Model Selection Flags ---
411 simCtx->immersed = 0; simCtx->movefsi = 0; simCtx->rotatefsi = 0;
412 simCtx->invicid = 0;
413 simCtx->TwoD = 0; simCtx->moveframe = 0; simCtx->rotateframe = 0;
414 strcpy(simCtx->AnalyticalSolutionType,"TGV3D");
415
416 // --- Group 4: Immersed-body flux corrections (refused; see Group 3 parsing) ---
417 simCtx->MHV=0; simCtx->LV=0;
418
419 // --- Group 5: Solver & Numerics Parameters ---
421 simCtx->mom_dt_jameson_residual_norm_noise_allowance_factor = 1.1; // raised from 1.05; less aggressive rejection
422 simCtx->mom_atol = 1e-7; simCtx->mom_rtol = 1e-4;
423 /* Residual-based convergence is the default. Leaving both at 0.0 selected the
424 * update-only branch, where |dU| <= mom_atol can pass purely because dtau collapsed
425 * (|dU| ~ dtau*|R|), converging on a state that does not satisfy the equations.
426 * Shipped configs all set these explicitly; these defaults protect a minimal
427 * user-written config. Set both non-positive to opt back out deliberately. */
428 simCtx->mom_resid_atol = 1e-8; simCtx->mom_resid_rtol = 1e-3;
429 simCtx->imp_stol = 1.e-8;
430 simCtx->mglevels = 3;
431 simCtx->mg_preItr = 1; simCtx->mg_poItr = 1;
432 simCtx->poisson = 0;
433 simCtx->central=0;
434 /* pseudo_cfl and its bounds are now dimensionless Courant numbers: CFL = dtau * lambda_max,
435 where lambda_max is the global spectral radius computed at each physical timestep.
436 Stable range for 4-stage Jameson RK: ~0–2.83. Initial 0.5 gives a comfortable margin. */
437 simCtx->ren = 100.0; simCtx->pseudo_cfl = 0.5;
438 simCtx->max_pseudo_cfl = 2.0; simCtx->min_pseudo_cfl = 0.001;
439 simCtx->pseudo_cfl_reduction_factor = 0.75;
440 simCtx->pseudo_cfl_growth_factor = 1.1; // raised from 1.0; controller can now increase CFL
441 simCtx->no_pseudo_cfl_backtrack = PETSC_FALSE;
442 simCtx->mom_ratio_ema_alpha = 0.3; /* moderate smoothing; set to 1.0 to recover original raw-ratio behavior */
443 simCtx->mom_last_converged = PETSC_TRUE;
444 simCtx->mom_last_lambda_max = 0.0; /* populated after first momentum solve */
445 simCtx->mom_nk_monitor_history = PETSC_FALSE;
446 simCtx->ps_ksp_pic_monitor_true_residual = PETSC_FALSE;
447 simCtx->cdisx = 0.0; simCtx->cdisy = 0.0; simCtx->cdisz = 0.0;
450 strcpy(simCtx->initialConditionDirectory, "config/initial_condition");
451 simCtx->InitialConstantContra.x = 0.0;
452 simCtx->InitialConstantContra.y = 0.0;
453 simCtx->InitialConstantContra.z = 0.0;
455 simCtx->icVelocityPhysical = 0.0;
456 simCtx->AnalyticalUniformVelocity.x = 0.0;
457 simCtx->AnalyticalUniformVelocity.y = 0.0;
458 simCtx->AnalyticalUniformVelocity.z = 0.0;
459 simCtx->fieldStatisticsEnabled = PETSC_FALSE;
460 simCtx->fieldStatisticsWindowCount = 0;
461 simCtx->fieldStatisticsWindows = NULL;
462 simCtx->statisticsConsoleOutputFreq = 0;
463 simCtx->fieldStatisticsContinue = PETSC_FALSE;
464 simCtx->solutionConvergenceEnabled = PETSC_TRUE;
471 simCtx->verificationDiffusivity.enabled = PETSC_FALSE;
472 strcpy(simCtx->verificationDiffusivity.mode, "");
473 strcpy(simCtx->verificationDiffusivity.profile, "");
474 simCtx->verificationDiffusivity.gamma0 = 0.0;
475 simCtx->verificationDiffusivity.slope_x = 0.0;
476
477 // --- Group 6: Physical & Geometric Parameters ---
478 simCtx->Flux_in = 1.0; simCtx->angle = 0.0;
479 simCtx->max_angle = -54. * 3.1415926 / 180.;
480 simCtx->CMx_c=0.0; simCtx->CMy_c=0.0; simCtx->CMz_c=0.0;
481 simCtx->wall_roughness_height = 1e-16;
482 simCtx->schmidt_number = 1.0; simCtx->Turbulent_schmidt_number = 0.7;
483 simCtx->iem_constant = 2.0;
484
485 // --- Group 7: Grid, Domain, and Boundary Condition Settings ---
486 simCtx->block_number = 1; simCtx->inletprofile = 1;
487 simCtx->i_periodic = 0; simCtx->j_periodic = 0; simCtx->k_periodic = 0;
488 simCtx->pseudo_periodic = 0;
489 strcpy(simCtx->grid_file, "config/grid.run");
490 simCtx->generate_grid = PETSC_FALSE;
491 simCtx->da_procs_x = PETSC_DECIDE;
492 simCtx->da_procs_y = PETSC_DECIDE;
493 simCtx->da_procs_z = PETSC_DECIDE;
494 simCtx->num_bcs_files = 1;
495 ierr = PetscMalloc1(1, &simCtx->bcs_files); CHKERRQ(ierr);
496 ierr = PetscStrallocpy("config/bcs.run", &simCtx->bcs_files[0]); CHKERRQ(ierr);
497 simCtx->FluxInSum = 0.0; simCtx->FluxOutSum = 0.0; simCtx->Fluxsum = 0.0;
498 simCtx->drivingForceMagnitude = 0.0, simCtx->forceScalingFactor = 1.8;
499 simCtx->drivingForceStep = -1;
500 simCtx->drivenFluxMeasured = 0.0; simCtx->drivenFluxArea = 0.0;
501 simCtx->targetVolumetricFlux = 0.0;
502 simCtx->drivenFluxTargetLatched = PETSC_FALSE;
503 simCtx->bulkVelocityCorrection = 0.0;
504 simCtx->boundaryVelocityCorrection = 0.0;
505 simCtx->AreaInSum = 0.0; simCtx->AreaOutSum = 0.0;
506 simCtx->ccc = 0;
507 simCtx->ratio = 0.0;
508
509
510 // --- Group 8: Turbulence Modeling (LES) ---
511 simCtx->les = NO_LES_MODEL;
512 simCtx->wallfunction = 0; simCtx->les_gradient_model = 0;
513 ierr = LESConfigSetDefaults(&simCtx->les_config); CHKERRQ(ierr);
514
515 // --- Group 9: Particle / DMSwarm Data & Settings ---
516 simCtx->np = 0;
517 simCtx->particleRandomSeed = 12345;
518 simCtx->dm_swarm = NULL; simCtx->bboxlist = NULL;
521 strcpy(simCtx->particleRestartMode,"load");
522 simCtx->particlesLostLastStep = 0;
523 simCtx->particlesLostCumulative = 0;
524 simCtx->particlesLostScalarLastStep = 0.0;
525 simCtx->particlesMigratedLastStep = 0;
526 simCtx->occupiedCellCount = 0;
527 simCtx->particleLoadImbalance = 0.0;
528 simCtx->migrationPassesLastStep = 0;
529 simCtx->searchMetrics.searchAttempts = 0;
532 simCtx->searchMetrics.searchLostCount = 0;
534 simCtx->searchMetrics.reSearchCount = 0;
537 simCtx->searchMetrics.tieBreakCount = 0;
543 simCtx->BrownianMotionRNG = NULL;
544 simCtx->C_IEM = 2.0;
545
546 // --- Group 10: Immersed Boundary & FSI Data Object Pointers ---
547 simCtx->ibm = NULL; simCtx->ibmv = NULL; simCtx->fsi = NULL;
548
549 // --- Group 11: Logging and Custom Configuration ---
550 strcpy(simCtx->allowedFile, "config/whitelist.run");
551 simCtx->useCfg = PETSC_FALSE;
552 simCtx->allowedFuncs = NULL;
553 simCtx->nAllowed = 0;
554 simCtx->LoggingFrequency = 10;
555 simCtx->poissonSourceImbalance = 0.0;
556 simCtx->MaxDiv = 0.0;
557 simCtx->MaxDivFlatArg = 0; simCtx->MaxDivx = 0; simCtx->MaxDivy = 0; simCtx->MaxDivz = 0;
558 strcpy(simCtx->profilingSelectedFuncsFile, "config/profile.run");
559 simCtx->useProfilingSelectedFuncsCfg = PETSC_FALSE;
560 simCtx->profilingSelectedFuncs = NULL;
561 simCtx->nProfilingSelectedFuncs = 0;
562 strcpy(simCtx->profilingTimestepMode, "selected");
563 strcpy(simCtx->profilingTimestepFile, "Profiling_Timestep_Summary.csv");
564 simCtx->profilingFinalSummary = PETSC_TRUE;
565 simCtx->walltimeGuardEnabled = PETSC_FALSE;
566 simCtx->walltimeGuardActive = PETSC_FALSE;
567 simCtx->walltimeGuardWarmupSteps = 10;
568 simCtx->walltimeGuardMultiplier = 2.0;
569 simCtx->walltimeGuardMinSeconds = 60.0;
570 simCtx->walltimeGuardEstimatorAlpha = 0.35;
572 simCtx->walltimeGuardLimitSeconds = 0.0;
573 simCtx->walltimeGuardCompletedSteps = 0;
576 simCtx->walltimeGuardHasEWMA = PETSC_FALSE;
577 simCtx->walltimeGuardEWMASeconds = 0.0;
578 simCtx->walltimeGuardLatestStepSeconds = 0.0;
579 simCtx->runtimeMemoryLogEnabled = PETSC_TRUE;
580 strcpy(simCtx->runtimeMemoryLogFile, "Runtime_Memory.log");
581 simCtx->runtimeMemoryLogStarted = PETSC_FALSE;
582 simCtx->runtimeMemoryLogHasPrevious = PETSC_FALSE;
584 // --- Group 11: Post-Processing Information ---
585 strcpy(simCtx->PostprocessingControlFile, "config/post.run");
586 ierr = PetscNew(&simCtx->pps); CHKERRQ(ierr);
587
588 // === 2. Get MPI Info and Handle Config File =============================
589 // -- Group 1: Parallelism & MPI Information
590 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &simCtx->rank); CHKERRQ(ierr);
591 ierr = MPI_Comm_size(PETSC_COMM_WORLD, &simCtx->size); CHKERRQ(ierr);
592
593 // First, check if the -control_file argument was provided by the user/script.
594 ierr = PetscOptionsGetString(NULL, NULL, "-control_file", control_filename, sizeof(control_filename), &control_flg); CHKERRQ(ierr);
595
596 // If the flag is NOT present or the filename is empty, abort with a helpful error.
597 if (!control_flg || strlen(control_filename) == 0) {
598 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
599 "\n\n*** MANDATORY ARGUMENT MISSING ***\n"
600 "The -control_file argument was not provided.\n"
601 "This program must be launched with a configuration file.\n"
602 "Example: mpiexec -n 4 ./simulator -control_file /path/to/your/config.control\n"
603 "This is typically handled automatically by the 'picurv' script.\n");
604 }
605
606 // At this point, we have a valid filename. Attempt to load it.
607 LOG(GLOBAL, LOG_INFO, "Loading mandatory configuration from: %s\n", control_filename);
608 ierr = PetscOptionsInsertFile(PETSC_COMM_WORLD, NULL, control_filename, PETSC_FALSE);
609 if (ierr == PETSC_ERR_FILE_OPEN) {
610 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_FILE_OPEN, "The specified control file was not found or could not be opened: %s", control_filename);
611 }
612 CHKERRQ(ierr);
613
614 {
615 PetscBool legacy_averaging = PETSC_FALSE;
616 ierr = PetscOptionsHasName(NULL, NULL, "-averaging", &legacy_averaging); CHKERRQ(ierr);
617 PetscCheck(!legacy_averaging, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
618 "Legacy -averaging was removed. Use instantaneous output and offline "
619 "postprocessing until the replacement field-statistics pipeline is available.");
620 }
621
622 // === 3. A Configure Logging System ========================================
623 // This logic determines the logging configuration and STORES it in simCtx for
624 // later reference and cleanup.
625 ierr = PetscOptionsGetString(NULL, NULL, "-whitelist_config_file", simCtx->allowedFile, PETSC_MAX_PATH_LEN, &simCtx->useCfg); CHKERRQ(ierr);
626
627 if (simCtx->useCfg) {
628 ierr = LoadAllowedFunctionsFromFile(simCtx->allowedFile, &simCtx->allowedFuncs, &simCtx->nAllowed);
629 if (ierr) {
630 // Use direct PetscPrintf as logging system isn't fully active yet.
631 PetscPrintf(PETSC_COMM_SELF, "[%s] WARNING: Failed to load allowed functions from '%s'. Falling back to default list.\n", __func__, simCtx->allowedFile);
632 simCtx->useCfg = PETSC_FALSE; // Mark as failed.
633 ierr = 0; // Clear the error to allow fallback.
634 } else if (simCtx->nAllowed == 0) {
635 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
636 "Whitelist config file '%s' is empty. Omit -whitelist_config_file to use the default allow-list, or list at least one function.",
637 simCtx->allowedFile);
638 }
639 }
640 if (!simCtx->useCfg) {
641 // Fallback to default logging functions if no file was used or if loading failed.
642 simCtx->nAllowed = 2;
643 ierr = PetscMalloc1(simCtx->nAllowed, &simCtx->allowedFuncs); CHKERRQ(ierr);
644 ierr = PetscStrallocpy("main", &simCtx->allowedFuncs[0]); CHKERRQ(ierr);
645 ierr = PetscStrallocpy("CreateSimulationContext", &simCtx->allowedFuncs[1]); CHKERRQ(ierr);
646 }
647
648 // Activate the configuration by passing it to the logging module's setup function.
649 set_allowed_functions((const char**)simCtx->allowedFuncs, (size_t)simCtx->nAllowed);
650
651 // Now that the logger is configured, we can use it.
652 LOG_ALLOW_SYNC(LOCAL, LOG_INFO, "Context created. Initializing on rank %d of %d.\n", simCtx->rank, simCtx->size);
653 print_log_level(); // This will now correctly reflect the LOG_LEVEL environment variable.
654
655 // === 3.B Configure Profiling System ========================================
656 ierr = PetscOptionsGetString(NULL, NULL, "-profiling_timestep_mode", simCtx->profilingTimestepMode, sizeof(simCtx->profilingTimestepMode), NULL); CHKERRQ(ierr);
657 ierr = PetscOptionsGetString(NULL, NULL, "-profiling_timestep_file", simCtx->profilingTimestepFile, PETSC_MAX_PATH_LEN, NULL); CHKERRQ(ierr);
658 ierr = PetscOptionsGetBool(NULL, NULL, "-profiling_final_summary", &simCtx->profilingFinalSummary, NULL); CHKERRQ(ierr);
659 if (strcmp(simCtx->profilingTimestepMode, "off") != 0 &&
660 strcmp(simCtx->profilingTimestepMode, "selected") != 0 &&
661 strcmp(simCtx->profilingTimestepMode, "all") != 0) {
662 PetscPrintf(PETSC_COMM_SELF, "[%s] WARNING: Unknown profiling timestep mode '%s'. Falling back to 'selected'.\n", __func__, simCtx->profilingTimestepMode);
663 strcpy(simCtx->profilingTimestepMode, "selected");
664 }
665
666 if (strcmp(simCtx->profilingTimestepMode, "selected") == 0) {
667 ierr = PetscOptionsGetString(NULL, NULL, "-profile_config_file", simCtx->profilingSelectedFuncsFile, PETSC_MAX_PATH_LEN, &simCtx->useProfilingSelectedFuncsCfg); CHKERRQ(ierr);
668 if (simCtx->useProfilingSelectedFuncsCfg) {
670 if (ierr) {
671 PetscPrintf(PETSC_COMM_SELF, "[%s] WARNING: Failed to load selected profiling functions from '%s'. Falling back to default list.\n", __func__, simCtx->profilingSelectedFuncsFile);
672 simCtx->useProfilingSelectedFuncsCfg = PETSC_FALSE;
673 ierr = 0;
674 }
675 }
676 if (!simCtx->useProfilingSelectedFuncsCfg) {
677 // Fallback to a hardcoded default list if no file was provided or loading failed.
678 simCtx->nProfilingSelectedFuncs = 4;
679 ierr = PetscMalloc1(simCtx->nProfilingSelectedFuncs, &simCtx->profilingSelectedFuncs); CHKERRQ(ierr);
680 ierr = PetscStrallocpy("FlowSolver", &simCtx->profilingSelectedFuncs[0]); CHKERRQ(ierr);
681 ierr = PetscStrallocpy("AdvanceSimulation", &simCtx->profilingSelectedFuncs[1]); CHKERRQ(ierr);
682 ierr = PetscStrallocpy("LocateAllParticlesInGrid", &simCtx->profilingSelectedFuncs[2]); CHKERRQ(ierr);
683 ierr = PetscStrallocpy("InterpolateAllFieldsToSwarm", &simCtx->profilingSelectedFuncs[3]); CHKERRQ(ierr);
684 }
685 }
686
687 // Initialize the profiling system with the current updated simulation context.
688 ierr = ProfilingInitialize(simCtx); CHKERRQ(ierr);
689
690 // === 4. Parse All Command Line Options ==================================
691 LOG_ALLOW(GLOBAL, LOG_INFO, "Parsing command-line options...\n");
692
693 // --- Group 2
694 LOG_ALLOW(GLOBAL,LOG_DEBUG, "Parsing Group 2: Simulation Control,Time and I/O.\n");
695 // Read the physical time to start from.
696 // The default is already 0.0, so this will only be non-zero if the user provides it.
697 ierr = PetscOptionsGetInt(NULL, NULL, "-start_step", &simCtx->StartStep, NULL); CHKERRQ(ierr);
698 ierr = PetscOptionsGetInt(NULL,NULL, "-totalsteps", &simCtx->StepsToRun, NULL); CHKERRQ(ierr);
699 ierr = PetscOptionsGetBool(NULL, NULL, "-only_setup", &simCtx->OnlySetup, NULL); CHKERRQ(ierr);
700 ierr = PetscOptionsGetBool(NULL, NULL, "-continue_mode", &simCtx->continueMode, NULL); CHKERRQ(ierr);
701 /* Resuming in the same run directory continues accumulated statistics by
702 * default; branching with --restart-from must opt in, because the branch may
703 * follow a different physical trajectory than the samples already collected. */
704 simCtx->fieldStatisticsContinue = simCtx->continueMode;
705 ierr = PetscOptionsGetBool(NULL, NULL, "-field_statistics_continue",
706 &simCtx->fieldStatisticsContinue, NULL); CHKERRQ(ierr);
707 ierr = PetscOptionsGetReal(NULL, NULL, "-dt", &simCtx->dt, NULL); CHKERRQ(ierr);
708 ierr = PetscOptionsGetInt(NULL, NULL, "-tio", &simCtx->tiout, NULL); CHKERRQ(ierr);
709 ierr = PetscOptionsGetInt(NULL, NULL, "-particle_console_output_freq", &simCtx->particleConsoleOutputFreq, &particle_console_output_freq_flg); CHKERRQ(ierr);
710 if (!particle_console_output_freq_flg) {
711 simCtx->particleConsoleOutputFreq = simCtx->tiout;
712 }
713 ierr = PetscOptionsGetString(NULL,NULL,"-euler_field_source",simCtx->eulerianSource,sizeof(simCtx->eulerianSource),NULL);CHKERRQ(ierr);
714 ierr = PetscOptionsGetString(NULL,NULL,"-output_dir",simCtx->output_dir,sizeof(simCtx->output_dir),NULL);CHKERRQ(ierr);
715 ierr = PetscOptionsGetString(NULL,NULL,"-restart_dir",simCtx->restart_dir,sizeof(simCtx->restart_dir),NULL);CHKERRQ(ierr);
716 ierr = PetscOptionsGetString(NULL,NULL,"-log_dir",simCtx->log_dir,sizeof(simCtx->log_dir),NULL);CHKERRQ(ierr);
717 ierr = PetscOptionsGetString(NULL,NULL,"-analysis_dir",simCtx->analysis_dir,sizeof(simCtx->analysis_dir),NULL);CHKERRQ(ierr);
718 ierr = PetscOptionsGetBool(NULL, NULL, "-walltime_guard_enabled", &simCtx->walltimeGuardEnabled, NULL); CHKERRQ(ierr);
719 ierr = PetscOptionsGetInt(NULL, NULL, "-walltime_guard_warmup_steps", &simCtx->walltimeGuardWarmupSteps, NULL); CHKERRQ(ierr);
720 ierr = PetscOptionsGetReal(NULL, NULL, "-walltime_guard_multiplier", &simCtx->walltimeGuardMultiplier, NULL); CHKERRQ(ierr);
721 ierr = PetscOptionsGetBool(NULL, NULL, "-runtime_memory_log_enabled", &simCtx->runtimeMemoryLogEnabled, NULL); CHKERRQ(ierr);
722 ierr = PetscOptionsGetString(NULL, NULL, "-runtime_memory_log_file", simCtx->runtimeMemoryLogFile, PETSC_MAX_PATH_LEN, NULL); CHKERRQ(ierr);
723 ierr = PetscOptionsGetReal(NULL, NULL, "-walltime_guard_min_seconds", &simCtx->walltimeGuardMinSeconds, NULL); CHKERRQ(ierr);
724 ierr = PetscOptionsGetReal(NULL, NULL, "-walltime_guard_estimator_alpha", &simCtx->walltimeGuardEstimatorAlpha, NULL); CHKERRQ(ierr);
725
726 if (simCtx->walltimeGuardWarmupSteps <= 0) {
727 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG, "Invalid value for -walltime_guard_warmup_steps: %d. Must be > 0.", simCtx->walltimeGuardWarmupSteps);
728 }
729 if (simCtx->walltimeGuardMultiplier <= 0.0 || simCtx->walltimeGuardMultiplier > 5.0) {
730 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG, "Invalid value for -walltime_guard_multiplier: %.6f. Must be in (0, 5].", (double)simCtx->walltimeGuardMultiplier);
731 }
732 if (simCtx->walltimeGuardMinSeconds <= 0.0) {
733 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG, "Invalid value for -walltime_guard_min_seconds: %.6f. Must be > 0.", (double)simCtx->walltimeGuardMinSeconds);
734 }
735 if (simCtx->walltimeGuardEstimatorAlpha <= 0.0 || simCtx->walltimeGuardEstimatorAlpha > 1.0) {
736 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG, "Invalid value for -walltime_guard_estimator_alpha: %.6f. Must be in (0, 1].", (double)simCtx->walltimeGuardEstimatorAlpha);
737 }
738
739 if(strcmp(simCtx->eulerianSource,"solve")!= 0 && strcmp(simCtx->eulerianSource,"load") != 0 && strcmp(simCtx->eulerianSource,"analytical")!=0){
740 SETERRQ(PETSC_COMM_WORLD,PETSC_ERR_ARG_WRONG,"Invalid value for -euler_field_source. Must be 'load','analytical' or 'solve'. You provided '%s'.",simCtx->eulerianSource);
741 }
742 if (simCtx->walltimeGuardEnabled) {
743 const char *job_start_env = getenv("PICURV_JOB_START_EPOCH");
744 const char *limit_env = getenv("PICURV_WALLTIME_LIMIT_SECONDS");
745 PetscBool job_start_ok = RuntimeWalltimeGuardParsePositiveSeconds(job_start_env, &simCtx->walltimeGuardJobStartEpochSeconds);
746 PetscBool limit_ok = RuntimeWalltimeGuardParsePositiveSeconds(limit_env, &simCtx->walltimeGuardLimitSeconds);
747
748 if (!job_start_ok || !limit_ok) {
749 simCtx->walltimeGuardActive = PETSC_FALSE;
751 simCtx->walltimeGuardLimitSeconds = 0.0;
752 LOG_ALLOW(
753 GLOBAL,
755 "Runtime walltime guard enabled but %s/%s are missing or invalid. Falling back to external shutdown signals only.\n",
756 "PICURV_JOB_START_EPOCH",
757 "PICURV_WALLTIME_LIMIT_SECONDS"
758 );
759 } else {
760 simCtx->walltimeGuardActive = PETSC_TRUE;
761 }
762 }
763
764 // --- Group 3
765 LOG_ALLOW(GLOBAL,LOG_DEBUG, "Parsing Group 3: High-Level Physics & Model Selection Flags\n");
766 ierr = PetscOptionsGetInt(NULL, NULL, "-imm", &simCtx->immersed, NULL); CHKERRQ(ierr);
767 ierr = PetscOptionsGetInt(NULL, NULL, "-fsi", &simCtx->movefsi, NULL); CHKERRQ(ierr);
768 ierr = PetscOptionsGetInt(NULL, NULL, "-rfsi", &simCtx->rotatefsi, NULL); CHKERRQ(ierr);
769 ierr = PetscOptionsGetInt(NULL, NULL, "-inv", &simCtx->invicid, NULL); CHKERRQ(ierr);
770 ierr = PetscOptionsGetInt(NULL, NULL, "-TwoD", &simCtx->TwoD, NULL); CHKERRQ(ierr);
771 ierr = PetscOptionsGetInt(NULL, NULL, "-mframe", &simCtx->moveframe, NULL); CHKERRQ(ierr);
772 ierr = PetscOptionsGetInt(NULL, NULL, "-rframe", &simCtx->rotateframe, NULL); CHKERRQ(ierr);
773 ierr = PetscOptionsGetString(NULL,NULL,"-analytical_type",simCtx->AnalyticalSolutionType,sizeof(simCtx->AnalyticalSolutionType),NULL);CHKERRQ(ierr);
774
775 /* Immersed boundaries, moving bodies and moving reference frames are planned, not
776 implemented. No body geometry is ever loaded, and the IBM interpolation the
777 momentum solvers would call is commented out and defined nowhere, so -imm only
778 reconfigures the Poisson solve around a body that is not there. The moving-frame
779 convection branch in ComputeRHS is commented out as well, so -mframe/-rframe would
780 drop the convective term. Each would run a different problem without saying so;
781 the conductor refuses the YAML switches, and this refuses the same flags arriving
782 through a PETSc passthrough. */
783 PetscCheck(!simCtx->immersed && !simCtx->movefsi && !simCtx->rotatefsi, PETSC_COMM_WORLD, PETSC_ERR_SUP,
784 "Immersed boundaries and moving bodies (-imm, -fsi, -rfsi) are planned, not implemented.");
785 PetscCheck(!simCtx->moveframe && !simCtx->rotateframe, PETSC_COMM_WORLD, PETSC_ERR_SUP,
786 "Moving and rotating reference frames (-mframe, -rframe) are planned, not implemented: "
787 "their convection branch is absent, so the convective term would be dropped.");
788
789 // --- Group 4
790 LOG_ALLOW(GLOBAL,LOG_DEBUG, "Parsing Group 4: Immersed-Body Flux Corrections and Driven-Flow Controls\n");
791 ierr = PetscOptionsGetInt(NULL, NULL, "-mhv", &simCtx->MHV, NULL); CHKERRQ(ierr);
792 ierr = PetscOptionsGetInt(NULL, NULL, "-lv", &simCtx->LV, NULL); CHKERRQ(ierr);
793 /* The heart-valve volume-flux corrections balance flux across an immersed body, which
794 cannot exist while immersed boundaries are unimplemented (see the Group 3 check). */
795 PetscCheck(!simCtx->MHV && !simCtx->LV, PETSC_COMM_WORLD, PETSC_ERR_SUP,
796 "The -mhv/-lv immersed-body flux corrections need immersed boundaries, which are planned, not implemented.");
797 ierr = PetscOptionsGetReal(NULL,NULL,"-driven_flow_initial_force",&simCtx->drivingForceMagnitude,NULL);CHKERRQ(ierr);
798 ierr = PetscOptionsGetReal(NULL,NULL,"-driven_flow_scaling_factor",&simCtx->forceScalingFactor,NULL);CHKERRQ(ierr);
799 // --- Group 5
800 LOG_ALLOW(GLOBAL,LOG_DEBUG, "Parsing Group 5: Solver & Numerics Parameters \n");
801 char mom_solver_type_char[PETSC_MAX_PATH_LEN];
802 char solution_convergence_mode_char[PETSC_MAX_PATH_LEN];
803 PetscBool mom_solver_type_flg = PETSC_FALSE;
804 PetscBool solution_convergence_mode_flg = PETSC_FALSE;
805 ierr = PetscOptionsGetString(NULL, NULL, "-mom_solver_type", mom_solver_type_char, sizeof(mom_solver_type_char), &mom_solver_type_flg); CHKERRQ(ierr);
806 ierr = PetscOptionsGetInt(NULL, NULL, "-mom_max_pseudo_steps", &simCtx->mom_max_pseudo_steps, NULL); CHKERRQ(ierr);
807 ierr = PetscOptionsGetReal(NULL, NULL, "-mom_atol", &simCtx->mom_atol, NULL); CHKERRQ(ierr);
808 ierr = PetscOptionsGetReal(NULL, NULL, "-mom_rtol", &simCtx->mom_rtol, NULL); CHKERRQ(ierr);
809 ierr = PetscOptionsGetReal(NULL, NULL, "-mom_resid_atol", &simCtx->mom_resid_atol, NULL); CHKERRQ(ierr);
810 ierr = PetscOptionsGetReal(NULL, NULL, "-mom_resid_rtol", &simCtx->mom_resid_rtol, NULL); CHKERRQ(ierr);
811 ierr = PetscOptionsGetReal(NULL, NULL, "-imp_stol", &simCtx->imp_stol, NULL); CHKERRQ(ierr);
812 ierr = PetscOptionsGetInt(NULL, NULL, "-central", &simCtx->central, NULL); CHKERRQ(ierr);
813 ierr = PetscOptionsGetString(NULL, NULL, "-solution_convergence_mode",
814 solution_convergence_mode_char, sizeof(solution_convergence_mode_char),
815 &solution_convergence_mode_flg); CHKERRQ(ierr);
816 ierr = PetscOptionsGetBool(NULL, NULL, "-solution_convergence_enabled", &simCtx->solutionConvergenceEnabled, NULL); CHKERRQ(ierr);
817 ierr = PetscOptionsGetInt(NULL, NULL, "-solution_convergence_period_steps", &simCtx->solutionConvergencePeriodSteps, NULL); CHKERRQ(ierr);
818 ierr = PetscOptionsGetInt(NULL, NULL, "-solution_convergence_window_steps", &simCtx->solutionConvergenceWindowSteps, NULL); CHKERRQ(ierr);
819 /* Field statistics resolve here rather than inline, because a variable-arity
820 * window list needs its own parse site; see statistics_config.c. It must
821 * precede CreateAndInitializeAllVectors, which sizes the per-window
822 * accumulators from the window count resolved here. */
823 ierr = ParseFieldStatisticsConfig(simCtx); CHKERRQ(ierr);
824
825 // Keep parser acceptance aligned with the enum and FlowSolver dispatch.
826 if (mom_solver_type_flg) {
827 if(strcmp(mom_solver_type_char, "DUALTIME_PICARD_JAMESON_RK") == 0 ||
828 strcmp(mom_solver_type_char, "DUALTIME_PICARD_RK4") == 0) {
830 } else if (strcmp(mom_solver_type_char, "EXPLICIT_RK") == 0) {
832 } else if (strcmp(mom_solver_type_char, "newton_krylov") == 0) {
834 } else {
835 LOG(GLOBAL, LOG_ERROR, "Invalid value for -mom_solver_type: '%s'. Valid options are: 'DUALTIME_PICARD_JAMESON_RK', 'EXPLICIT_RK', 'newton_krylov'.\n", mom_solver_type_char);
836 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG, "Invalid value for -mom_solver_type: '%s'.", mom_solver_type_char);
837 }
838 }
839
840 if (solution_convergence_mode_flg) {
841 if (strcmp(solution_convergence_mode_char, "STEADY_DETERMINISTIC") == 0) {
843 } else if (strcmp(solution_convergence_mode_char, "PERIODIC_DETERMINISTIC") == 0) {
845 } else if (strcmp(solution_convergence_mode_char, "STATISTICAL_STEADY") == 0) {
847 } else if (strcmp(solution_convergence_mode_char, "TRANSIENT") == 0) {
849 } else {
850 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
851 "Invalid value for -solution_convergence_mode: '%s'.", solution_convergence_mode_char);
852 }
853 }
854
856 simCtx->solutionConvergencePeriodSteps <= 0) {
857 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
858 "solution convergence mode PERIODIC_DETERMINISTIC requires -solution_convergence_period_steps > 0.");
859 }
861 simCtx->solutionConvergenceWindowSteps <= 0) {
862 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
863 "solution convergence mode STATISTICAL_STEADY requires -solution_convergence_window_steps > 0.");
864 }
865
866 // --- Multigrid Options ---
867 ierr = PetscOptionsGetInt(NULL, NULL, "-mg_level", &simCtx->mglevels, NULL); CHKERRQ(ierr);
868 ierr = PetscOptionsGetInt(NULL, NULL, "-mg_pre_it", &simCtx->mg_preItr, NULL); CHKERRQ(ierr);
869 ierr = PetscOptionsGetInt(NULL, NULL, "-mg_post_it", &simCtx->mg_poItr, NULL); CHKERRQ(ierr);
870
871 // --- Other Solver Options ---
872 ierr = PetscOptionsGetInt(NULL, NULL, "-poisson", &simCtx->poisson, NULL); CHKERRQ(ierr);
873 ierr = PetscOptionsGetReal(NULL, NULL, "-ren", &simCtx->ren, NULL); CHKERRQ(ierr);
874 ierr = PetscOptionsGetReal(NULL, NULL, "-pseudo_cfl", &simCtx->pseudo_cfl, NULL); CHKERRQ(ierr);
875 ierr = PetscOptionsGetReal(NULL, NULL, "-max_pseudo_cfl", &simCtx->max_pseudo_cfl, NULL); CHKERRQ(ierr);
876 ierr = PetscOptionsGetReal(NULL, NULL, "-min_pseudo_cfl", &simCtx->min_pseudo_cfl, NULL); CHKERRQ(ierr);
877 ierr = PetscOptionsGetReal(NULL, NULL, "-pseudo_cfl_reduction_factor", &simCtx->pseudo_cfl_reduction_factor, NULL); CHKERRQ(ierr);
878 ierr = PetscOptionsGetReal(NULL, NULL, "-pseudo_cfl_growth_factor", &simCtx->pseudo_cfl_growth_factor, NULL); CHKERRQ(ierr);
879 // Read the deprecated RK4 spelling first so the canonical Jameson option wins if both are present.
880 ierr = PetscOptionsGetReal(NULL,NULL, "-mom_dt_rk4_residual_norm_noise_allowance_factor",&simCtx->mom_dt_jameson_residual_norm_noise_allowance_factor,NULL);CHKERRQ(ierr);
881 ierr = PetscOptionsGetReal(NULL,NULL, "-mom_dt_jameson_residual_norm_noise_allowance_factor",&simCtx->mom_dt_jameson_residual_norm_noise_allowance_factor,NULL);CHKERRQ(ierr);
882 ierr = PetscOptionsGetBool(NULL, NULL, "-no_pseudo_cfl_backtrack", &simCtx->no_pseudo_cfl_backtrack, NULL); CHKERRQ(ierr);
883 ierr = PetscOptionsGetReal(NULL, NULL, "-mom_ratio_ema_alpha", &simCtx->mom_ratio_ema_alpha, NULL); CHKERRQ(ierr);
884 if (simCtx->min_pseudo_cfl <= 0.0 ||
885 simCtx->pseudo_cfl < simCtx->min_pseudo_cfl ||
886 simCtx->pseudo_cfl > simCtx->max_pseudo_cfl) {
887 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
888 "Pseudo-CFL controls require 0 < minimum <= initial <= maximum.");
889 }
890 if (simCtx->pseudo_cfl_growth_factor < 1.0 ||
891 simCtx->pseudo_cfl_reduction_factor <= 0.0 ||
892 simCtx->pseudo_cfl_reduction_factor >= 1.0 ||
894 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
895 "Pseudo-CFL controls require growth_factor >= 1, 0 < reduction_factor < 1, and noise allowance >= 1.");
896 }
897 if (simCtx->mom_ratio_ema_alpha < 0.0 || simCtx->mom_ratio_ema_alpha > 1.0) {
898 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
899 "-mom_ratio_ema_alpha must be in [0, 1].");
900 }
901 ierr = PetscOptionsHasName(NULL, NULL, "-ps_ksp_pic_monitor_true_residual", &simCtx->ps_ksp_pic_monitor_true_residual); CHKERRQ(ierr);
902 ierr = PetscOptionsGetBool(NULL, NULL, "-mom_nk_pic_monitor", &simCtx->mom_nk_monitor_history, NULL); CHKERRQ(ierr);
903 {
904 PetscInt ic_mode = (PetscInt)simCtx->initialConditionMode;
905 PetscInt ic_field = (PetscInt)simCtx->initialConditionField;
906 ierr = PetscOptionsGetInt(NULL, NULL, "-finit", &ic_mode, NULL); CHKERRQ(ierr);
907 ierr = PetscOptionsGetInt(NULL, NULL, "-ic_field", &ic_field, NULL); CHKERRQ(ierr);
910 }
911 ierr = PetscOptionsGetString(NULL, NULL, "-ic_dir", simCtx->initialConditionDirectory,
912 sizeof(simCtx->initialConditionDirectory), NULL); CHKERRQ(ierr);
914 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
915 "Invalid value for -finit. Expected an initial-condition mode in [0,4], got %d.",
916 simCtx->initialConditionMode);
917 }
919 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
920 "Invalid value for -ic_field. Expected 0 (Ucat) or 1 (Ucont), got %d.",
921 simCtx->initialConditionField);
922 }
923 ierr = PetscOptionsGetReal(NULL, NULL, "-ucont_x", &simCtx->InitialConstantContra.x, NULL); CHKERRQ(ierr);
924 ierr = PetscOptionsGetReal(NULL, NULL, "-ucont_y", &simCtx->InitialConstantContra.y, NULL); CHKERRQ(ierr);
925 ierr = PetscOptionsGetReal(NULL, NULL, "-ucont_z", &simCtx->InitialConstantContra.z, NULL); CHKERRQ(ierr);
926 {
927 PetscInt fd_int = (PetscInt)FLOW_DIR_UNSET;
928 PetscBool fd_set = PETSC_FALSE;
929 ierr = PetscOptionsGetInt(NULL, NULL, "-flow_direction", &fd_int, &fd_set); CHKERRQ(ierr);
930 if (fd_set) simCtx->flowDirection = (FlowDirection)fd_int;
931 }
932 ierr = PetscOptionsGetReal(NULL, NULL, "-ic_velocity_physical", &simCtx->icVelocityPhysical, NULL); CHKERRQ(ierr);
933 ierr = PetscOptionsGetReal(NULL, NULL, "-analytical_uniform_u", &simCtx->AnalyticalUniformVelocity.x, NULL); CHKERRQ(ierr);
934 ierr = PetscOptionsGetReal(NULL, NULL, "-analytical_uniform_v", &simCtx->AnalyticalUniformVelocity.y, NULL); CHKERRQ(ierr);
935 ierr = PetscOptionsGetReal(NULL, NULL, "-analytical_uniform_w", &simCtx->AnalyticalUniformVelocity.z, NULL); CHKERRQ(ierr);
936 PetscBool verification_scalar_value_set = PETSC_FALSE;
937 PetscBool verification_scalar_phi0_set = PETSC_FALSE;
938 PetscBool verification_scalar_slope_x_set = PETSC_FALSE;
939 PetscBool verification_scalar_amplitude_set = PETSC_FALSE;
940 PetscBool verification_scalar_kx_set = PETSC_FALSE;
941 PetscBool verification_scalar_ky_set = PETSC_FALSE;
942 PetscBool verification_scalar_kz_set = PETSC_FALSE;
943 ierr = PetscOptionsGetString(NULL, NULL, "-verification_diffusivity_mode",
945 sizeof(simCtx->verificationDiffusivity.mode), NULL); CHKERRQ(ierr);
946 ierr = PetscOptionsGetString(NULL, NULL, "-verification_diffusivity_profile",
948 sizeof(simCtx->verificationDiffusivity.profile), NULL); CHKERRQ(ierr);
949 ierr = PetscOptionsGetReal(NULL, NULL, "-verification_diffusivity_gamma0",
950 &simCtx->verificationDiffusivity.gamma0, NULL); CHKERRQ(ierr);
951 ierr = PetscOptionsGetReal(NULL, NULL, "-verification_diffusivity_slope_x",
952 &simCtx->verificationDiffusivity.slope_x, NULL); CHKERRQ(ierr);
953 ierr = PetscOptionsGetString(NULL, NULL, "-verification_scalar_mode",
954 simCtx->verificationScalar.mode,
955 sizeof(simCtx->verificationScalar.mode), NULL); CHKERRQ(ierr);
956 ierr = PetscOptionsGetString(NULL, NULL, "-verification_scalar_profile",
958 sizeof(simCtx->verificationScalar.profile), NULL); CHKERRQ(ierr);
959 ierr = PetscOptionsGetReal(NULL, NULL, "-verification_scalar_value",
960 &simCtx->verificationScalar.value, &verification_scalar_value_set); CHKERRQ(ierr);
961 ierr = PetscOptionsGetReal(NULL, NULL, "-verification_scalar_phi0",
962 &simCtx->verificationScalar.phi0, &verification_scalar_phi0_set); CHKERRQ(ierr);
963 ierr = PetscOptionsGetReal(NULL, NULL, "-verification_scalar_slope_x",
964 &simCtx->verificationScalar.slope_x, &verification_scalar_slope_x_set); CHKERRQ(ierr);
965 ierr = PetscOptionsGetReal(NULL, NULL, "-verification_scalar_amplitude",
966 &simCtx->verificationScalar.amplitude, &verification_scalar_amplitude_set); CHKERRQ(ierr);
967 ierr = PetscOptionsGetReal(NULL, NULL, "-verification_scalar_kx",
968 &simCtx->verificationScalar.kx, &verification_scalar_kx_set); CHKERRQ(ierr);
969 ierr = PetscOptionsGetReal(NULL, NULL, "-verification_scalar_ky",
970 &simCtx->verificationScalar.ky, &verification_scalar_ky_set); CHKERRQ(ierr);
971 ierr = PetscOptionsGetReal(NULL, NULL, "-verification_scalar_kz",
972 &simCtx->verificationScalar.kz, &verification_scalar_kz_set); CHKERRQ(ierr);
974 (PetscBool)(simCtx->verificationDiffusivity.mode[0] != '\0' ||
975 simCtx->verificationDiffusivity.profile[0] != '\0');
977 (PetscBool)(simCtx->verificationScalar.mode[0] != '\0' ||
978 simCtx->verificationScalar.profile[0] != '\0');
979 if (simCtx->verificationDiffusivity.enabled) {
980 if (strcmp(simCtx->eulerianSource, "analytical") != 0) {
981 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE,
982 "verification diffusivity overrides require -euler_field_source \"analytical\".");
983 }
984 if (strcmp(simCtx->verificationDiffusivity.mode, "analytical") != 0) {
985 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
986 "Unsupported -verification_diffusivity_mode '%s'. Only 'analytical' is supported.",
988 }
989 if (strcmp(simCtx->verificationDiffusivity.profile, "LINEAR_X") != 0) {
990 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
991 "Unsupported -verification_diffusivity_profile '%s'. Only 'LINEAR_X' is supported.",
993 }
994 }
995 if (simCtx->verificationScalar.enabled) {
996 if (strcmp(simCtx->eulerianSource, "analytical") != 0) {
997 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE,
998 "verification scalar overrides require -euler_field_source \"analytical\".");
999 }
1000 if (strcmp(simCtx->verificationScalar.mode, "analytical") != 0) {
1001 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
1002 "Unsupported -verification_scalar_mode '%s'. Only 'analytical' is supported.",
1003 simCtx->verificationScalar.mode);
1004 }
1005 if (strcmp(simCtx->verificationScalar.profile, "CONSTANT") == 0) {
1006 if (!verification_scalar_value_set) {
1007 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
1008 "verification scalar profile CONSTANT requires -verification_scalar_value.");
1009 }
1010 } else if (strcmp(simCtx->verificationScalar.profile, "LINEAR_X") == 0) {
1011 if (!verification_scalar_phi0_set || !verification_scalar_slope_x_set) {
1012 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
1013 "verification scalar profile LINEAR_X requires -verification_scalar_phi0 and -verification_scalar_slope_x.");
1014 }
1015 } else if (strcmp(simCtx->verificationScalar.profile, "SIN_PRODUCT") == 0) {
1016 if (!verification_scalar_amplitude_set || !verification_scalar_kx_set ||
1017 !verification_scalar_ky_set || !verification_scalar_kz_set) {
1018 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
1019 "verification scalar profile SIN_PRODUCT requires -verification_scalar_amplitude, -verification_scalar_kx, -verification_scalar_ky, and -verification_scalar_kz.");
1020 }
1021 } else {
1022 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
1023 "Unsupported -verification_scalar_profile '%s'. Supported profiles: CONSTANT, LINEAR_X, SIN_PRODUCT.",
1024 simCtx->verificationScalar.profile);
1025 }
1026 }
1027 // NOTE: cdisx,cdisy,cdisz haven't been parsed, add if necessary.
1028
1029 // --- Group 6
1030 LOG_ALLOW(GLOBAL,LOG_DEBUG, "Parsing Group 6: Physical & Geometric Parameters \n");
1031 ierr = PetscOptionsGetReal(NULL,NULL,"-schmidt_number",&simCtx->schmidt_number,NULL);CHKERRQ(ierr);
1032 ierr = PetscOptionsGetReal(NULL,NULL,"-turb_schmidt_number",&simCtx->Turbulent_schmidt_number,NULL);CHKERRQ(ierr);
1033 ierr = PetscOptionsGetReal(NULL,NULL,"-iem_constant",&simCtx->iem_constant,NULL);CHKERRQ(ierr);
1034 /* Zero is valid: it switches micromixing off, so a particle scalar is carried unchanged. */
1035 PetscCheck(simCtx->iem_constant >= 0.0 && !PetscIsInfOrNanReal(simCtx->iem_constant),
1036 PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
1037 "-iem_constant must be a non-negative finite number (got %g).", (double)simCtx->iem_constant);
1038 ierr = PetscOptionsGetReal(NULL,NULL,"-wall_roughness",&simCtx->wall_roughness_height,NULL);CHKERRQ(ierr);
1039 // NOTE: angle is not parsed in the original code, it set programmatically. We will follow that.
1040 // NOTE: max_angle is calculated based on other flags (like MHV) in the legacy code.
1041 // We will defer that logic to a later setup stage and not parse them directly.
1042 // The Scaling Information is calculated here
1043 ierr = ParseScalingInformation(simCtx); CHKERRQ(ierr);
1044
1045 // --- Group 7
1046 LOG_ALLOW(GLOBAL,LOG_DEBUG, "Parsing Group 7: Grid, Domain, and Boundary Condition Settings \n");
1047 ierr = PetscOptionsGetInt(NULL, NULL, "-nblk", &simCtx->block_number, NULL); CHKERRQ(ierr); // This is also a modern option
1048 /* Every inter-block exchange (Block_Interface_U) is commented out in the momentum
1049 solvers and the initial condition, so blocks would be solved as isolated domains.
1050 Multi-block coupling is planned, not implemented. */
1051 PetscCheck(simCtx->block_number == 1, PETSC_COMM_WORLD, PETSC_ERR_SUP,
1052 "Multi-block domains are planned, not implemented: blocks are never coupled (got -nblk %" PetscInt_FMT ").",
1053 simCtx->block_number);
1054 ierr = PetscOptionsGetInt(NULL, NULL, "-inlet", &simCtx->inletprofile, NULL); CHKERRQ(ierr);
1055 // NOTE: channelz was not parsed, likely set programmatically. We will omit its parsing call.
1056 ierr = PetscOptionsGetBool(NULL, NULL, "-grid", &simCtx->generate_grid, NULL); CHKERRQ(ierr);
1057 ierr = PetscOptionsGetString(NULL, NULL, "-grid_file", simCtx->grid_file, PETSC_MAX_PATH_LEN, NULL); CHKERRQ(ierr);
1058 ierr = PetscOptionsGetInt(NULL, NULL, "-da_processors_x", &simCtx->da_procs_x, NULL); CHKERRQ(ierr);
1059 ierr = PetscOptionsGetInt(NULL, NULL, "-da_processors_y", &simCtx->da_procs_y, NULL); CHKERRQ(ierr);
1060 ierr = PetscOptionsGetInt(NULL, NULL, "-da_processors_z", &simCtx->da_procs_z, NULL); CHKERRQ(ierr);
1061 // NOTE: pseudo_periodic was not parsed. We will omit its parsing call.
1062 PetscBool bcs_flg;
1063 char file_list_str[PETSC_MAX_PATH_LEN * 10]; // Buffer for comma-separated list
1064
1065 ierr = PetscOptionsGetString(NULL, NULL, "-bcs_files", file_list_str, sizeof(file_list_str), &bcs_flg); CHKERRQ(ierr);
1066
1067 if (bcs_flg) {
1068 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Found -bcs_files option, overriding default.\n");
1069
1070 // A. Clean up the default memory we allocated in Phase 1.
1071 ierr = PetscFree(simCtx->bcs_files[0]); CHKERRQ(ierr);
1072 ierr = PetscFree(simCtx->bcs_files); CHKERRQ(ierr);
1073 simCtx->num_bcs_files = 0;
1074 simCtx->bcs_files = NULL;
1075
1076 // B. Parse the user-provided comma-separated list.
1077 char *token;
1078 char *str_copy;
1079 ierr = PetscStrallocpy(file_list_str, &str_copy); CHKERRQ(ierr);
1080
1081 // First pass: count the number of files.
1082 token = strtok(str_copy, ",");
1083 while (token) {
1084 simCtx->num_bcs_files++;
1085 token = strtok(NULL, ",");
1086 }
1087 ierr = PetscFree(str_copy); CHKERRQ(ierr);
1088
1089 // Second pass: allocate memory and store the filenames.
1090 ierr = PetscMalloc1(simCtx->num_bcs_files, &simCtx->bcs_files); CHKERRQ(ierr);
1091 ierr = PetscStrallocpy(file_list_str, &str_copy); CHKERRQ(ierr);
1092 token = strtok(str_copy, ",");
1093 for (PetscInt i = 0; i < simCtx->num_bcs_files; i++) {
1094 ierr = PetscStrallocpy(token, &simCtx->bcs_files[i]); CHKERRQ(ierr);
1095 token = strtok(NULL, ",");
1096 }
1097 ierr = PetscFree(str_copy); CHKERRQ(ierr);
1098 }
1099
1100
1101 // --- Group 8
1102 LOG_ALLOW(GLOBAL,LOG_DEBUG, "Parsing Group 8: Turbulence Modeling (LES) \n");
1103 // Seeded from the default already in simCtx: PetscOptionsGetInt leaves the target
1104 // untouched when -les is absent, and an uninitialized value would become the model.
1105 PetscInt temp_les_model = (PetscInt)simCtx->les;
1106 ierr = PetscOptionsGetInt(NULL, NULL, "-les", &temp_les_model, NULL); CHKERRQ(ierr);
1107 PetscCheck(temp_les_model >= NO_LES_MODEL && temp_les_model <= WALE, PETSC_COMM_WORLD,
1108 PETSC_ERR_ARG_OUTOFRANGE,
1109 "-les must be 0 (none), 1 (constant_smagorinsky), 2 (dynamic_smagorinsky), "
1110 "3 (vreman), or 4 (wale); received %" PetscInt_FMT ".", temp_les_model);
1111 simCtx->les = (LESModelType)temp_les_model;
1112 {
1113 /* RANS is planned, not implemented: the k-omega transport update was never
1114 written and its fields were never allocated. The selector and its dead
1115 hooks were removed on 2026-09-22 (see src/guide.md); this refuses the flag
1116 arriving through a PETSc passthrough. */
1117 PetscInt requested_rans = 0;
1118 ierr = PetscOptionsGetInt(NULL, NULL, "-rans", &requested_rans, NULL); CHKERRQ(ierr);
1119 PetscCheck(requested_rans == 0, PETSC_COMM_WORLD, PETSC_ERR_SUP,
1120 "RANS closures (-rans) are planned, not implemented: no transport equation is "
1121 "solved for them. Use an LES closure, or run laminar.");
1122 }
1123 ierr = PetscOptionsGetInt(NULL, NULL, "-wallfunction", &simCtx->wallfunction, NULL); CHKERRQ(ierr);
1124 PetscCheck(simCtx->wallfunction >= WALL_FUNCTION_NONE && simCtx->wallfunction <= WALL_FUNCTION_CABOT,
1125 PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
1126 "-wallfunction must be 0 (none), 1 (log_law), 2 (werner), or 3 (cabot); received %" PetscInt_FMT ".",
1127 simCtx->wallfunction);
1128 ierr = PetscOptionsGetInt(NULL, NULL, "-les_gradient_model", &simCtx->les_gradient_model, NULL); CHKERRQ(ierr);
1129 ierr = ParseLESConfiguration(simCtx); CHKERRQ(ierr);
1130
1131 // --- Group 9
1132 LOG_ALLOW(GLOBAL,LOG_DEBUG, "Parsing Group 9: Particle / DMSwarm Data & Settings \n");
1133 ierr = PetscOptionsGetInt(NULL, NULL, "-numParticles", &simCtx->np, NULL); CHKERRQ(ierr);
1134 PetscInt temp_pinit = (PetscInt)PARTICLE_INIT_SURFACE_RANDOM;
1135 ierr = PetscOptionsGetInt(NULL, NULL, "-pinit", &temp_pinit, NULL); CHKERRQ(ierr);
1137 PetscInt temp_interp = (PetscInt)INTERP_TRILINEAR;
1138 ierr = PetscOptionsGetInt(NULL, NULL, "-interpolation_method", &temp_interp, NULL); CHKERRQ(ierr);
1139 simCtx->interpolationMethod = (InterpolationMethod)temp_interp;
1140 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Interpolation method: %s\n",
1141 simCtx->interpolationMethod == INTERP_TRILINEAR ? "Trilinear (direct cell-center)" : "CornerAveraged (legacy)");
1142 ierr = PetscOptionsGetReal(NULL, NULL, "-psrc_x", &simCtx->psrc_x, NULL); CHKERRQ(ierr);
1143 ierr = PetscOptionsGetReal(NULL, NULL, "-psrc_y", &simCtx->psrc_y, NULL); CHKERRQ(ierr);
1144 ierr = PetscOptionsGetReal(NULL, NULL, "-psrc_z", &simCtx->psrc_z, NULL); CHKERRQ(ierr);
1145 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Particle initialization mode: %s. Point source: (%.6f, %.6f, %.6f)\n",
1147 simCtx->psrc_x, simCtx->psrc_y, simCtx->psrc_z);
1148 ierr = PetscOptionsGetString(NULL,NULL,"-particle_restart_mode",simCtx->particleRestartMode,sizeof(simCtx->particleRestartMode),NULL); CHKERRQ(ierr);
1149 // Validation for Particle Restart Mode
1150 if (strcmp(simCtx->particleRestartMode, "load") != 0 && strcmp(simCtx->particleRestartMode, "init") != 0) {
1151 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG, "Invalid value for -particle_restart_mode. Must be 'load' or 'init'. You provided '%s'.", simCtx->particleRestartMode);
1152 }
1153 ierr = PetscOptionsGetInt(NULL, NULL, "-particle_random_seed", &simCtx->particleRandomSeed, NULL); CHKERRQ(ierr);
1154 PetscCheck(simCtx->particleRandomSeed >= 0, PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
1155 "-particle_random_seed must be non-negative (got %" PetscInt_FMT ").", simCtx->particleRandomSeed);
1156 ierr = InitializeBrownianRNG(simCtx); CHKERRQ(ierr);
1157 ierr = ParticleFieldPlanCreate(&simCtx->particleFieldPlan); CHKERRQ(ierr);
1158 PetscCheck(!simCtx->particleFieldPlan || simCtx->np > 0, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
1159 "Particle field initial values are configured but the run has no particles.");
1160 PetscCheck(!simCtx->particleFieldPlan || !VerificationScalarOverrideActive(simCtx), PETSC_COMM_WORLD,
1161 PETSC_ERR_ARG_WRONG,
1162 "Particle field initial values cannot be combined with the verification scalar source, "
1163 "which prescribes Psi at every step.");
1164 // --- Group 10
1165 LOG_ALLOW(GLOBAL,LOG_DEBUG, "Parsing Group 10: Immersed Boundary & FSI Data Object Pointers \n");
1166
1167 // --- Group 11
1168 LOG_ALLOW(GLOBAL,LOG_DEBUG, "Parsing Group 11: Top-Level Managers & Custom Configuration \n");
1169 ierr = PetscOptionsGetInt(NULL, NULL, "-logfreq", &simCtx->LoggingFrequency, NULL); CHKERRQ(ierr);
1170
1171 if (simCtx->num_bcs_files != simCtx->block_number) {
1172 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_INCOMP, "Number of BC files (%d) does not match number of blocks (%d). Use -bcs_files \"file1.dat,file2.dat,...\".", simCtx->num_bcs_files, simCtx->block_number);
1173 }
1174
1175 // --- Group 12
1176 LOG_ALLOW(GLOBAL,LOG_DEBUG, "Parsing Group 12: Post-Processing Information.\n");
1177 // This logic determines the Post Processing configuration and STORES it in simCtx for later reference and cleanup.
1178 ierr = PetscOptionsGetString(NULL,NULL,"-postprocessing_config_file",simCtx->PostprocessingControlFile,PETSC_MAX_PATH_LEN,NULL); CHKERRQ(ierr);
1179 /* Parse post settings for both solver and post-processor binaries using the single pre-allocated pps object. */
1180 ierr = ParsePostProcessingSettings(simCtx); CHKERRQ(ierr);
1181
1182 // === 5. Dependent Parameter Calculations ================================
1183 // Some parameters depend on others, so we calculate them here.
1184 simCtx->StartTime = (PetscReal)simCtx->StartStep*simCtx->dt;
1185 simCtx->ti = simCtx->StartTime;
1186 simCtx->step = simCtx->StartStep;
1187
1188 // === 5. Log Summary and Finalize Setup ==================================
1189 LOG_ALLOW(GLOBAL, LOG_DEBUG, "-- Console Output Functions [Total : %d] : --\n", simCtx->nAllowed);
1190 for (PetscInt i = 0; i < simCtx->nAllowed; ++i) {
1191 LOG_ALLOW(GLOBAL, LOG_DEBUG, " [%2d] «%s»\n", i, simCtx->allowedFuncs[i]);
1192 }
1193
1194 LOG_ALLOW(GLOBAL, LOG_INFO, "Configuration complete. Key parameters:\n");
1195 LOG_ALLOW(GLOBAL, LOG_INFO, " - Run mode: %s\n", simCtx->OnlySetup ? "SETUP ONLY" : "Full Simulation");
1196 LOG_ALLOW(GLOBAL, LOG_INFO, " - Time steps: %d (from %d to %d)\n", simCtx->StepsToRun, simCtx->StartStep, simCtx->StartStep + simCtx->StepsToRun);
1197 LOG_ALLOW(GLOBAL, LOG_INFO, " - Time step size (dt): %g\n", simCtx->dt);
1198 if (simCtx->tiout > 0) {
1199 LOG_ALLOW(GLOBAL, LOG_INFO, " - Field/restart output cadence: every %d step(s)\n", simCtx->tiout);
1200 } else {
1201 LOG_ALLOW(GLOBAL, LOG_INFO, " - Field/restart output cadence: DISABLED\n");
1202 }
1203 LOG_ALLOW(GLOBAL, LOG_INFO, " - Immersed Boundary: %s\n", simCtx->immersed ? "ENABLED" : "DISABLED");
1204 LOG_ALLOW(GLOBAL, LOG_INFO, " - Particles: %d\n", simCtx->np);
1205 if (simCtx->np > 0) {
1206 if (simCtx->particleConsoleOutputFreq > 0) {
1207 LOG_ALLOW(GLOBAL, LOG_INFO, " - Particle console cadence: every %d step(s)\n", simCtx->particleConsoleOutputFreq);
1208 } else {
1209 LOG_ALLOW(GLOBAL, LOG_INFO, " - Particle console cadence: DISABLED\n");
1210 }
1211 LOG_ALLOW(GLOBAL, LOG_INFO, " - Particle console row subsampling: every %d particle(s)\n", simCtx->LoggingFrequency);
1212 }
1213 if (simCtx->StartStep > 0 && simCtx->np > 0) {
1214 LOG_ALLOW(GLOBAL, LOG_INFO, " - Particle Restart Mode: %s\n", simCtx->particleRestartMode);
1215 }
1216
1217 // --- Initialize PETSc's internal performance logging stage ---
1218 ierr = PetscLogDefaultBegin(); CHKERRQ(ierr); // REDUNDANT but safe.
1219 ierr = PetscMemorySetGetMaximumUsage(); CHKERRQ(ierr);
1220
1221 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Finished CreateSimulationContext successfully on rank %d.\n", simCtx->rank);
1222
1224 PetscFunctionReturn(0);
1225}
1226
1227#undef __FUNCT__
1228#define __FUNCT__ "PetscMkdirRecursive"
1229/**
1230 * @brief Create a directory path recursively using PETSc-compatible error handling.
1231 */
1232static PetscErrorCode PetscMkdirRecursive(const char *path)
1233{
1234 PetscErrorCode ierr;
1235 char tmp_path[PETSC_MAX_PATH_LEN];
1236 char *p = NULL;
1237 size_t len;
1238 PetscBool exists;
1239
1240 PetscFunctionBeginUser;
1241
1242 // Create a mutable copy of the path
1243 len = strlen(path);
1244 if (len >= sizeof(tmp_path)) {
1245 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ, "Path is too long to process: %s", path);
1246 }
1247 strcpy(tmp_path, path);
1248
1249 // If the path ends with a separator, remove it
1250 if (tmp_path[len - 1] == '/') {
1251 tmp_path[len - 1] = 0;
1252 }
1253
1254 // Iterate through the path, creating each directory level
1255 for (p = tmp_path + 1; *p; p++) {
1256 if (*p == '/') {
1257 *p = 0; // Temporarily terminate the string
1258
1259 // Check if this directory level exists
1260 ierr = PetscTestDirectory(tmp_path, 'r', &exists); CHKERRQ(ierr);
1261 if (!exists) {
1262 ierr = PetscMkdir(tmp_path); CHKERRQ(ierr);
1263 }
1264
1265 *p = '/'; // Restore the separator
1266 }
1267 }
1268
1269 // Create the final, full directory path
1270 ierr = PetscTestDirectory(tmp_path, 'r', &exists); CHKERRQ(ierr);
1271 if (!exists) {
1272 ierr = PetscMkdir(tmp_path); CHKERRQ(ierr);
1273 }
1274
1275 PetscFunctionReturn(0);
1276}
1277
1278/**
1279 * @brief Whether two configured directories denote the same location or nest.
1280 *
1281 * @details A bare `strcmp` accepted "output/" against "output", and missed
1282 * "output/sub" entirely. Comparison is done on normalized segment sequences so
1283 * trailing slashes, "./" prefixes and repeated separators cannot defeat it.
1284 *
1285 * @param[in] first First directory value.
1286 * @param[in] second Second directory value.
1287 * @return PETSC_TRUE when one contains the other, or they are equal.
1288 */
1289static PetscBool DirectoriesOverlap(const char *first, const char *second)
1290{
1291 char a[PETSC_MAX_PATH_LEN], b[PETSC_MAX_PATH_LEN];
1292 const char *source[2];
1293 char *target[2];
1294 int which;
1295
1296 if (!first || !second || !*first || !*second) return PETSC_FALSE;
1297 source[0] = first; source[1] = second;
1298 target[0] = a; target[1] = b;
1299
1300 for (which = 0; which < 2; ++which) {
1301 const char *cursor = source[which];
1302 char *out = target[which];
1303 size_t used = 0;
1304
1305 while (*cursor && used + 1 < PETSC_MAX_PATH_LEN) {
1306 size_t segment;
1307 while (*cursor == '/') ++cursor;
1308 segment = strcspn(cursor, "/");
1309 if (segment == 0) break;
1310 if (segment == 1 && cursor[0] == '.') { cursor += segment; continue; }
1311 if (used) out[used++] = '/';
1312 if (used + segment + 1 >= PETSC_MAX_PATH_LEN) break;
1313 memcpy(out + used, cursor, segment);
1314 used += segment;
1315 cursor += segment;
1316 }
1317 out[used] = '\0';
1318 }
1319
1320 if (!*a || !*b) return PETSC_FALSE;
1321 if (strcmp(a, b) == 0) return PETSC_TRUE;
1322 if (strncmp(a, b, strlen(b)) == 0 && a[strlen(b)] == '/') return PETSC_TRUE;
1323 if (strncmp(b, a, strlen(a)) == 0 && b[strlen(a)] == '/') return PETSC_TRUE;
1324 return PETSC_FALSE;
1325}
1326
1327/** @brief Directory names the run tree owns; a log directory must never target one. */
1328static const char *kReservedRunDirectories[] = {"config", "scheduler", "checkpoints", "visualization", NULL};
1329
1330/**
1331 * @brief Whether a configured directory name is safe to write to a PETSc options line.
1332 *
1333 * @details Whitespace, quotes, and comment markers change how the options file is
1334 * tokenized, so a value carrying them would be misread rather than merely
1335 * unsafe. This is never waivable: the user cannot consent to ambiguity.
1336 *
1337 * @param[in] value Configured directory value.
1338 * @return PETSC_TRUE when the value is unambiguous.
1339 */
1340static PetscBool DirectoryValueIsWellFormed(const char *value)
1341{
1342 const char *cursor;
1343
1344 if (!value || value[0] == '\0') return PETSC_FALSE;
1345 for (cursor = value; *cursor; ++cursor) {
1346 if (isspace((unsigned char)*cursor)) return PETSC_FALSE;
1347 if (*cursor == '"' || *cursor == '\'' || *cursor == '#') return PETSC_FALSE;
1348 }
1349 return PETSC_TRUE;
1350}
1351
1352/**
1353 * @brief Whether a directory's first path segment collides with a reserved run directory.
1354 *
1355 * @param[in] value Configured directory value.
1356 * @return PETSC_TRUE when it collides.
1357 */
1358static PetscBool DirectoryHitsReservedName(const char *value)
1359{
1360 const char *cursor = value;
1361
1362 if (!value) return PETSC_FALSE;
1363
1364 /* Walk every path segment, skipping separators and "." components. A
1365 first-segment-only check accepted "./config", ".//config", "output/" and
1366 "output/sub", each of which deletes a run-owned directory. */
1367 while (*cursor) {
1368 size_t segment;
1369 int index;
1370
1371 while (*cursor == '/') ++cursor;
1372 if (!*cursor) break;
1373 segment = strcspn(cursor, "/");
1374 if (!(segment == 1 && cursor[0] == '.')) {
1375 for (index = 0; kReservedRunDirectories[index]; ++index) {
1376 if (strlen(kReservedRunDirectories[index]) == segment &&
1377 strncmp(cursor, kReservedRunDirectories[index], segment) == 0) {
1378 return PETSC_TRUE;
1379 }
1380 }
1381 }
1382 cursor += segment;
1383 }
1384 return PETSC_FALSE;
1385}
1386
1387typedef enum {
1388 DIR_VERDICT_CONTAINED = 0, /* Inside the working directory. Always safe. */
1389 DIR_VERDICT_MALFORMED, /* Empty, or unwritable to an options line. */
1390 DIR_VERDICT_RESERVED, /* Collides with a reserved run directory. */
1391 DIR_VERDICT_OVERLAP, /* Nests with the solver output directory. */
1392 DIR_VERDICT_RUN_ROOT, /* Resolves to the working directory itself. */
1393 DIR_VERDICT_ANCESTOR, /* Contains the working directory. */
1394 DIR_VERDICT_RELATIVE_ESCAPE, /* Relative traversal, or a relative symlink escape. */
1395 DIR_VERDICT_UNEXPANDED_TILDE, /* Starts with '~', which nothing expands. */
1396 DIR_VERDICT_EXTERNAL_ABSOLUTE, /* Absolute location outside the tree. */
1397 DIR_VERDICT_UNRESOLVABLE /* Working directory or path could not be resolved. */
1399
1400/**
1401 * @brief Lexically normalize a path, resolving "." and ".." textually.
1402 *
1403 * @details Used only when the target does not exist yet, so `realpath` cannot resolve
1404 * it. Resolving ".." textually is correct here precisely because there is no
1405 * file to be a symlink; the existing prefix is resolved separately with
1406 * `realpath`, which does follow symlinks.
1407 *
1408 * @param[in] value Path to normalize.
1409 * @param[out] out Buffer receiving the normalized path.
1410 * @param[in] size Size of the output buffer.
1411 * @param[out] stack Caller-provided scratch of at least PETSC_MAX_PATH_LEN bytes.
1412 * @return PETSC_TRUE when normalization fit in the buffer.
1413 */
1414static PetscBool NormalizePathLexically(const char *value, char *out, size_t size,
1415 char *stack)
1416{
1417 const char *cursor = value;
1418 size_t used = 0;
1419 PetscBool absolute = (PetscBool)(value[0] == '/');
1420
1421 stack[0] = '\0';
1422 while (*cursor) {
1423 const char *slash;
1424 size_t len;
1425
1426 while (*cursor == '/') cursor++;
1427 if (!*cursor) break;
1428 slash = strchr(cursor, '/');
1429 len = slash ? (size_t)(slash - cursor) : strlen(cursor);
1430
1431 if (len == 1 && cursor[0] == '.') {
1432 /* Nothing to do. */
1433 } else if (len == 2 && cursor[0] == '.' && cursor[1] == '.') {
1434 char *last = strrchr(stack, '/');
1435 if (last) { *last = '\0'; used = strlen(stack); }
1436 else if (used) { stack[0] = '\0'; used = 0; }
1437 } else {
1438 if (used + len + 2 >= PETSC_MAX_PATH_LEN) return PETSC_FALSE;
1439 stack[used++] = '/';
1440 memcpy(stack + used, cursor, len);
1441 used += len;
1442 stack[used] = '\0';
1443 }
1444 if (!slash) break;
1445 cursor = slash + 1;
1446 }
1447
1448 if (absolute) {
1449 if (strlen(stack) + 1 >= size) return PETSC_FALSE;
1450 strcpy(out, used ? stack : "/");
1451 } else {
1452 const char *relative = used ? stack + 1 : ".";
1453 if (strlen(relative) + 1 >= size) return PETSC_FALSE;
1454 strcpy(out, relative);
1455 }
1456 return PETSC_TRUE;
1457}
1458
1459/**
1460 * @brief Resolve a directory that may not exist yet to an absolute physical path.
1461 *
1462 * @details `realpath` fails on a path whose final components have not been created,
1463 * which is the normal case for a log directory. The longest existing prefix is
1464 * resolved with `realpath`, so symlinked ancestors are followed, and the
1465 * remainder is appended lexically.
1466 *
1467 * @param[in] value Configured directory value.
1468 * @param[in] cwd Current working directory, for relative values.
1469 * @param[out] out Buffer receiving the resolved absolute path.
1470 * @param[in] size Size of the output buffer.
1471 * @param[out] scratch Caller-provided scratch of at least 5*PETSC_MAX_PATH_LEN bytes.
1472 * @return PETSC_TRUE when a resolution was produced.
1473 */
1474static PetscBool ResolveDirectoryPhysically(const char *value, const char *cwd,
1475 char *out, size_t size, char *scratch)
1476{
1477 /* Five PATH_MAX buffers, on the heap. On the stack they cost 20 KB inside a call
1478 chain that already carries the caller's three, and that overflowed far enough to
1479 corrupt the simulation context on rank zero - the guard broke the run it was
1480 there to protect. A path check must not spend the stack. */
1481 char *absolute = scratch;
1482 char *normalized = scratch + PETSC_MAX_PATH_LEN;
1483 char *probe = scratch + 2 * PETSC_MAX_PATH_LEN;
1484 char *resolved = scratch + 3 * PETSC_MAX_PATH_LEN;
1485 char *lexical = scratch + 4 * PETSC_MAX_PATH_LEN;
1486
1487 if (value[0] == '/') {
1488 if ((size_t)snprintf(absolute, PETSC_MAX_PATH_LEN, "%s", value) >= PETSC_MAX_PATH_LEN)
1489 return PETSC_FALSE;
1490 } else {
1491 if ((size_t)snprintf(absolute, PETSC_MAX_PATH_LEN, "%s/%s", cwd, value)
1492 >= PETSC_MAX_PATH_LEN)
1493 return PETSC_FALSE;
1494 }
1495 if (!NormalizePathLexically(absolute, normalized, PETSC_MAX_PATH_LEN, lexical))
1496 return PETSC_FALSE;
1497
1498 if ((size_t)snprintf(probe, PETSC_MAX_PATH_LEN, "%s", normalized) >= PETSC_MAX_PATH_LEN)
1499 return PETSC_FALSE;
1500
1501 /* Walk up to the longest existing prefix, then re-append what was trimmed. */
1502 for (;;) {
1503 char *last;
1504 if (realpath(probe, resolved)) {
1505 size_t consumed = strlen(probe);
1506 const char *tail = normalized + consumed;
1507 if ((size_t)snprintf(out, size, "%s%s", resolved, tail) >= size) return PETSC_FALSE;
1508 /* Re-normalize: `resolved` may be "/" and `tail` may start with "/". */
1509 return NormalizePathLexically(out, out, size, lexical);
1510 }
1511 last = strrchr(probe, '/');
1512 if (!last) return PETSC_FALSE;
1513 if (last == probe) { probe[1] = '\0'; } /* Down to "/" - try once more. */
1514 else { *last = '\0'; }
1515 if (strcmp(probe, "/") == 0 && !realpath(probe, resolved)) return PETSC_FALSE;
1516 }
1517}
1518
1519/**
1520 * @brief Whether `ancestor` is the same directory as `path`, or contains it.
1521 * @param[in] ancestor Candidate containing directory, absolute and normalized.
1522 * @param[in] path Candidate contained directory, absolute and normalized.
1523 * @return PETSC_TRUE when `ancestor` equals or contains `path`.
1524 */
1525static PetscBool PathContainsOrEquals(const char *ancestor, const char *path)
1526{
1527 size_t len = strlen(ancestor);
1528
1529 if (strcmp(ancestor, path) == 0) return PETSC_TRUE;
1530 if (strcmp(ancestor, "/") == 0) return PETSC_TRUE;
1531 return (PetscBool)(strncmp(path, ancestor, len) == 0 && path[len] == '/');
1532}
1533
1534/**
1535 * @brief Classify a configured log directory against the working directory.
1536 *
1537 * @details Every non-waivable check - lexical and physical alike - runs before the
1538 * caller is allowed to consider an authorization. An earlier version returned
1539 * `safe` as soon as it saw an absolute path with authorization set, which let
1540 * an authorized run delete its own run directory, an ancestor of it, or the
1541 * filesystem root. Classification is now total and the waiver is applied once,
1542 * to exactly one verdict.
1543 *
1544 * @param[in] log_dir Configured log directory.
1545 * @param[in] output_dir Configured output directory, for the overlap check.
1546 * @param[out] reason Set to a short explanation of the verdict.
1547 * @return The verdict for this directory.
1548 */
1549static DirectoryVerdict ClassifyLogDirectory(const char *log_dir, const char *output_dir,
1550 const char **reason)
1551{
1552 /* One heap block for every path buffer this classification needs: three here and
1553 five for the physical resolution. */
1554 char *scratch = NULL;
1555 char *cwd, *resolved, *cwd_real;
1556 DirectoryVerdict verdict;
1557
1558 *reason = "unknown";
1559
1560 /* --- Non-waivable: ambiguous or self-destructive targets. --- */
1561 if (!log_dir || log_dir[0] == '\0') {
1562 *reason = "is empty";
1563 return DIR_VERDICT_MALFORMED;
1564 }
1565 if (!DirectoryValueIsWellFormed(log_dir)) {
1566 *reason = "contains whitespace, a quote, or a comment marker";
1567 return DIR_VERDICT_MALFORMED;
1568 }
1569 if (DirectoryHitsReservedName(log_dir)) {
1570 *reason = "collides with a reserved run directory";
1571 return DIR_VERDICT_RESERVED;
1572 }
1573 if (DirectoriesOverlap(log_dir, output_dir)) {
1574 *reason = "overlaps the solver output directory";
1575 return DIR_VERDICT_OVERLAP;
1576 }
1577 /* `~` is refused before anything else looks at the path. Nothing expands it: the
1578 control file is read by PETSc rather than by a shell, and the code below resolves
1579 a value not starting with '/' relative to the working directory, so `~/logs`
1580 names a literal '~' directory inside the run. An earlier version of the launcher's
1581 physical check expanded it and treated the result as an authorizable external
1582 location, while nothing that used the value ever did; refusing it here keeps both
1583 layers describing the same directory. */
1584 if (log_dir[0] == '~') {
1585 *reason = "starts with '~', which nothing expands - the options file is read by "
1586 "PETSc, not by a shell, so this would name a literal '~' directory "
1587 "inside the run. Give a real absolute path. This cannot be overridden";
1589 }
1590 /* A relative traversal is refused on the value itself, before any resolution: it
1591 lands among sibling runs and study members, and no authorization covers it. */
1592 if (log_dir[0] != '/' &&
1593 (strcmp(log_dir, "..") == 0 || strncmp(log_dir, "../", 3) == 0 ||
1594 strstr(log_dir, "/../") != NULL ||
1595 (strlen(log_dir) >= 3 && strcmp(log_dir + strlen(log_dir) - 3, "/..") == 0))) {
1596 *reason = "walks above the working directory by relative traversal, which cannot "
1597 "be authorized";
1599 }
1600
1601 if (PetscMalloc1(8 * PETSC_MAX_PATH_LEN, &scratch)) {
1602 *reason = "cannot be checked because scratch space could not be allocated";
1604 }
1605 cwd = scratch;
1606 resolved = scratch + PETSC_MAX_PATH_LEN;
1607 cwd_real = scratch + 2 * PETSC_MAX_PATH_LEN;
1608
1609 verdict = DIR_VERDICT_UNRESOLVABLE;
1610 if (!getcwd(cwd, PETSC_MAX_PATH_LEN)) {
1611 *reason = "cannot be checked because the working directory could not be determined";
1612 } else if (!realpath(cwd, cwd_real)) {
1613 *reason = "cannot be checked because the working directory could not be resolved";
1614 } else if (!ResolveDirectoryPhysically(log_dir, cwd_real, resolved, PETSC_MAX_PATH_LEN,
1615 scratch + 3 * PETSC_MAX_PATH_LEN)) {
1616 *reason = "could not be resolved to a physical path";
1617 } else if (strcmp(resolved, cwd_real) == 0) {
1618 /* --- Physical verdicts, decided before authorization is consulted. --- */
1619 *reason = "resolves to the working directory itself; deleting it would destroy "
1620 "the run. This cannot be overridden";
1621 verdict = DIR_VERDICT_RUN_ROOT;
1622 } else if (PathContainsOrEquals(resolved, cwd_real)) {
1623 *reason = "contains the working directory, so deleting it recursively would "
1624 "destroy the run and everything beside it. This cannot be overridden";
1625 verdict = DIR_VERDICT_ANCESTOR;
1626 } else if (PathContainsOrEquals(cwd_real, resolved)) {
1627 verdict = DIR_VERDICT_CONTAINED;
1628 } else if (log_dir[0] == '/') {
1629 /* Outside the working directory. Only an explicitly absolute value can be
1630 waived: a relative name that lands outside got there through a symlink,
1631 which is not a location anybody asked for. */
1632 *reason = "is an absolute path outside the working directory and no "
1633 "-allow_unsafe_log_dir authorization was given";
1635 } else {
1636 *reason = "is a relative name that resolves outside the working directory "
1637 "through a symlink, which cannot be authorized";
1639 }
1640
1641 PetscFree(scratch);
1642 return verdict;
1643}
1644
1645/**
1646 * @brief Final safety guard before the runtime deletes its log directory.
1647 *
1648 * @details `picurv` validates run directories and re-checks them at submission, but the
1649 * solver can be launched directly on a hand-written control file, and a
1650 * directory can be replaced with a symlink between validation and launch. This
1651 * is the last check before an irreversible recursive delete, so it is
1652 * deliberately independent of anything the launcher did, which is what
1653 * eliminates the validation-to-execution race.
1654 *
1655 * An explicit `-allow_unsafe_log_dir` authorization waives **exactly one**
1656 * verdict: an absolute location outside the working directory that neither is
1657 * nor contains it. It never waives the working directory itself, an ancestor
1658 * of it, a reserved run directory, an overlap with the output directory, a
1659 * relative traversal, a relative symlink escape, or a malformed value. Those
1660 * destroy the run itself or corrupt the options file, and no user consent
1661 * makes them correct.
1662 *
1663 * @param[in] log_dir Configured log directory.
1664 * @param[in] output_dir Configured output directory, for the overlap check.
1665 * @param[in] authorized Whether an explicit unsafe-path authorization was supplied.
1666 * @param[out] reason Set to a short explanation when the directory is refused.
1667 * @return PETSC_TRUE when the directory is safe to remove.
1668 */
1669static PetscBool LogDirectoryIsSafeToWipe(const char *log_dir, const char *output_dir,
1670 PetscBool authorized, const char **reason)
1671{
1672 DirectoryVerdict verdict = ClassifyLogDirectory(log_dir, output_dir, reason);
1673
1674 if (verdict == DIR_VERDICT_CONTAINED) return PETSC_TRUE;
1675 if (verdict == DIR_VERDICT_EXTERNAL_ABSOLUTE && authorized) return PETSC_TRUE;
1676 return PETSC_FALSE;
1677}
1678
1679#undef __FUNCT__
1680#define __FUNCT__ "SetupSimulationEnvironment"
1681/**
1682 * @brief Internal helper implementation: `SetupSimulationEnvironment()`.
1683 * @details Local to this translation unit.
1684 */
1685PetscErrorCode SetupSimulationEnvironment(SimCtx *simCtx)
1686{
1687 PetscErrorCode ierr;
1688 PetscMPIInt rank;
1689 PetscBool exists;
1690
1691 PetscFunctionBeginUser;
1692 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1693
1694 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Setting up simulation environment ---\n");
1695
1696 /* =====================================================================
1697 * Phase 1: Check for all required and optional INPUT files.
1698 * ===================================================================== */
1699 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Phase 1: Verifying input files...\n");
1700
1701 // --- Mandatory Inputs ---
1702 if (!simCtx->generate_grid) {
1703 ierr = VerifyPathExistence(simCtx->grid_file, PETSC_FALSE, PETSC_FALSE, "Grid file", &exists); CHKERRQ(ierr);
1704 }
1705 for (PetscInt i = 0; i < simCtx->num_bcs_files; i++) {
1706 char desc[128];
1707 ierr = PetscSNPrintf(desc, sizeof(desc), "BCS file #%d", i + 1); CHKERRQ(ierr);
1708 ierr = VerifyPathExistence(simCtx->bcs_files[i], PETSC_FALSE, PETSC_FALSE, desc, &exists); CHKERRQ(ierr);
1709 }
1710
1711 // --- Optional Inputs (these produce warnings if missing) ---
1712 if (simCtx->useCfg) {
1713 ierr = VerifyPathExistence(simCtx->allowedFile, PETSC_FALSE, PETSC_TRUE, "Whitelist config file", &exists); CHKERRQ(ierr);
1714 }
1715 if (simCtx->useProfilingSelectedFuncsCfg) {
1716 ierr = VerifyPathExistence(simCtx->profilingSelectedFuncsFile, PETSC_FALSE, PETSC_TRUE, "Profiling config file", &exists); CHKERRQ(ierr);
1717 }
1718 if (simCtx->exec_mode == EXEC_MODE_POSTPROCESSOR) {
1719 ierr = VerifyPathExistence(simCtx->PostprocessingControlFile, PETSC_FALSE, PETSC_TRUE, "Post-processing control file", &exists); CHKERRQ(ierr);
1720 }
1721
1722
1723 /* =====================================================================
1724 * Phase 2: Validate directories specific to the execution mode.
1725 * ===================================================================== */
1726 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Phase 2: Verifying execution mode directories...\n");
1727 // The data source directory must exist if we intend to load any data from it.
1728 // This is true if:
1729 // 1. We are restarting from a previous time step (StartStep > 0), which implies
1730 // loading Eulerian fields and/or particle fields.
1731 // 2. We are starting from t=0 but are explicitly told to load the initial
1732 // Eulerian fields from a file (eulerianSource == "load").
1733 if (simCtx->StartStep > 0 || strcmp(simCtx->eulerianSource,"load")== 0){ // If this is a restart run
1734 ierr = VerifyPathExistence(simCtx->restart_dir, PETSC_TRUE, PETSC_FALSE, "Restart source directory", &exists); CHKERRQ(ierr);
1735 }
1736 if (simCtx->StartStep == 0 && strcmp(simCtx->eulerianSource, "solve") == 0 &&
1738 ierr = VerifyPathExistence(simCtx->initialConditionDirectory, PETSC_TRUE, PETSC_FALSE,
1739 "Initial-condition source directory", &exists); CHKERRQ(ierr);
1740 }
1741 if (simCtx->exec_mode == EXEC_MODE_POSTPROCESSOR) {
1742 ierr = VerifyPathExistence(simCtx->pps->source_dir, PETSC_TRUE, PETSC_FALSE, "Post-processing source directory", &exists); CHKERRQ(ierr);
1743 }
1744
1745 /* =====================================================================
1746 * Phase 3: Create and prepare all OUTPUT directories.
1747 * ===================================================================== */
1748 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Phase 3: Preparing output directories...\n");
1749
1750 if (rank == 0){
1751 if(simCtx->exec_mode == EXEC_MODE_SOLVER){
1752 // --- Prepare Log Directory ---
1753 if (!simCtx->continueMode) {
1754 // Only wipe logs on fresh runs; continue mode appends to existing logs.
1755 // Final guard: this delete is recursive and irreversible, so re-check
1756 // containment here rather than trusting that the launcher validated it.
1757 {
1758 PetscBool unsafe_authorized = PETSC_FALSE;
1759 const char *refusal = NULL;
1760 ierr = PetscOptionsGetBool(NULL, NULL, "-allow_unsafe_log_dir",
1761 &unsafe_authorized, NULL); CHKERRQ(ierr);
1762 if (!LogDirectoryIsSafeToWipe(simCtx->log_dir, simCtx->output_dir,
1763 unsafe_authorized, &refusal)) {
1764 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
1765 "Refusing to delete log directory '%s': it %s. Configure run "
1766 "directories through monitor.io.directories.",
1767 simCtx->log_dir, refusal);
1768 }
1769 if (unsafe_authorized) {
1771 "Deleting log directory '%s' outside the working directory, "
1772 "authorized by -allow_unsafe_log_dir.\n", simCtx->log_dir);
1773 }
1774 }
1775 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Creating/cleaning log directory: %s\n", simCtx->log_dir);
1776 ierr = PetscRMTree(simCtx->log_dir); // Wipes the directory and its contents
1777 if (ierr) { /* Ignore file-not-found error, but fail on others */
1778 PetscError(PETSC_COMM_SELF, __LINE__, __FUNCT__, __FILE__, ierr, PETSC_ERROR_INITIAL, "Could not remove existing log directory '%s'. Check permissions.", simCtx->log_dir);
1779 }
1780 ierr = PetscMkdir(simCtx->log_dir); CHKERRQ(ierr);
1781 } else {
1782 // In continue mode, ensure log directory exists but don't wipe it.
1783 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Continue mode: preserving existing log directory: %s\n", simCtx->log_dir);
1784 ierr = PetscMkdir(simCtx->log_dir); CHKERRQ(ierr);
1785 }
1786
1787 // --- Prepare Output Directory ---
1788 // The checkpoint coordinator creates its fixed internal hierarchy
1789 // transactionally when a checkpoint is due.
1790 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Verifying main output directory: %s\n", simCtx->output_dir);
1791 ierr = PetscTestDirectory(simCtx->output_dir, 'r', &exists); CHKERRQ(ierr);
1792 if (!exists) {
1793 LOG_ALLOW(GLOBAL, LOG_INFO, "Output directory not found. Creating: %s\n", simCtx->output_dir);
1794 ierr = PetscMkdir(simCtx->output_dir); CHKERRQ(ierr);
1795 }
1796 // The analysis home is nested (output/analysis/metrics by default), so it
1797 // needs the recursive create the postprocessor branch already uses; PetscMkdir
1798 // fails on a path whose parents do not exist yet.
1799 ierr = PetscMkdirRecursive(simCtx->analysis_dir); CHKERRQ(ierr);
1800 } else if(simCtx->exec_mode == EXEC_MODE_POSTPROCESSOR){
1801 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Preparing post-processing output directories ...\n");
1802
1803 PostProcessParams *pps = simCtx->pps;
1804 char path_buffer[PETSC_MAX_PATH_LEN];
1805
1806 const char *last_slash_euler = strrchr(pps->output_prefix, '/');
1807 if(last_slash_euler){
1808 size_t dir_len = last_slash_euler - pps->output_prefix;
1809 if(dir_len > 0){
1810 if(dir_len >= sizeof(path_buffer)) SETERRQ(PETSC_COMM_WORLD,PETSC_ERR_ARG_WRONG,"Post-processing output prefix path is too long.");
1811 strncpy(path_buffer, pps->output_prefix, dir_len);
1812 path_buffer[dir_len] = '\0';
1813
1814 ierr = PetscTestDirectory(path_buffer, 'r', &exists); CHKERRQ(ierr);
1815 if (!exists){
1816 LOG_ALLOW(GLOBAL, LOG_INFO, "Creating post-processing Eulerian output directory: %s\n", path_buffer);
1817 ierr = PetscMkdirRecursive(path_buffer); CHKERRQ(ierr);
1818 }
1819 }
1820 }
1821
1822 // Particle output directory
1823 if(pps->outputParticles){
1824 const char *last_slash_particle = strrchr(pps->particle_output_prefix, '/');
1825 if(last_slash_particle){
1826 size_t dir_len = last_slash_particle - pps->particle_output_prefix;
1827 if(dir_len > 0){
1828 if(dir_len > sizeof(path_buffer)) SETERRQ(PETSC_COMM_WORLD,PETSC_ERR_ARG_WRONG,"Post-processing particle output prefix path is too long.");
1829 strncpy(path_buffer, pps->particle_output_prefix, dir_len);
1830 path_buffer[dir_len] = '\0';
1831
1832 ierr = PetscTestDirectory(path_buffer, 'r', &exists); CHKERRQ(ierr);
1833
1834 if (!exists){
1835 LOG_ALLOW(GLOBAL, LOG_INFO, "Creating post-processing Particle output directory: %s\n", path_buffer);
1836 ierr = PetscMkdirRecursive(path_buffer); CHKERRQ(ierr);
1837 }
1838 }
1839 }
1840 }
1841
1842 // Statistics output directory
1843 if(pps->statistics_pipeline[0] != '\0'){
1844 const char *last_slash_stats = strrchr(pps->statistics_output_prefix, '/');
1845 if(last_slash_stats){
1846 size_t dir_len = last_slash_stats - pps->statistics_output_prefix;
1847 if(dir_len > 0){
1848 if(dir_len >= sizeof(path_buffer)) SETERRQ(PETSC_COMM_WORLD,PETSC_ERR_ARG_WRONG,"Post-processing statistics output prefix path is too long.");
1849 strncpy(path_buffer, pps->statistics_output_prefix, dir_len);
1850 path_buffer[dir_len] = '\0';
1851
1852 ierr = PetscTestDirectory(path_buffer, 'r', &exists); CHKERRQ(ierr);
1853 if (!exists){
1854 LOG_ALLOW(GLOBAL, LOG_INFO, "Creating post-processing Statistics output directory: %s\n", path_buffer);
1855 ierr = PetscMkdirRecursive(path_buffer); CHKERRQ(ierr);
1856 }
1857 }
1858 }
1859 }
1860 }
1861 }
1862
1863 // Synchronize all processes before proceeding
1864 ierr = MPI_Barrier(PETSC_COMM_WORLD); CHKERRMPI(ierr);
1865
1866 /* PETSc opens a -info file during PetscInitialize, before the fresh-run wipe above
1867 removes the log directory it usually lives in. Every later record would then go
1868 to an unlinked file and the run would end with no info log at all, so each rank
1869 reopens its file once the directory exists again. Records from initialization
1870 itself are the only ones lost. */
1871 if (simCtx->exec_mode == EXEC_MODE_SOLVER && !simCtx->continueMode) {
1872 char *info_name = NULL;
1873 FILE *info_file = NULL;
1874 ierr = PetscInfoGetFile(&info_name, &info_file); CHKERRQ(ierr);
1875 if (info_name && info_name[0] != '\0') {
1876 char reopen_name[PETSC_MAX_PATH_LEN];
1877 ierr = PetscStrncpy(reopen_name, info_name, sizeof(reopen_name)); CHKERRQ(ierr);
1878 if (info_file && info_file != PETSC_STDOUT) {
1879 ierr = PetscFClose(PETSC_COMM_SELF, info_file); CHKERRQ(ierr);
1880 }
1881 ierr = PetscInfoSetFile(reopen_name, "w"); CHKERRQ(ierr);
1882 }
1883 /* PetscInfoGetFile hands back its own copy of the name. */
1884 ierr = PetscFree(info_name); CHKERRQ(ierr);
1885 }
1886
1887 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Environment setup complete ---\n");
1888
1889 PetscFunctionReturn(0);
1890}
1891
1892#undef __FUNCT__
1893#define __FUNCT__ "AllocateContextHeirarchy"
1894/**
1895 * @brief Allocate the user-context objects required by every multigrid level.
1896 */
1897static PetscErrorCode AllocateContextHierarchy(SimCtx *simCtx)
1898{
1899 PetscErrorCode ierr;
1900 UserMG *usermg = &simCtx->usermg;
1901 MGCtx *mgctx;
1902 PetscInt nblk = simCtx->block_number;
1903 PetscBool found;
1904 PetscFunctionBeginUser;
1906
1907 LOG_ALLOW(GLOBAL, LOG_INFO, "Allocating context hierarchy for %d levels and %d blocks...\n", simCtx->mglevels, nblk);
1908
1909 // Store the number of levels in the UserMG struct itself
1910 usermg->mglevels = simCtx->mglevels;
1911
1912 // --- 1. Allocate the array of MGCtx structs ---
1913 ierr = PetscMalloc(usermg->mglevels * sizeof(MGCtx), &usermg->mgctx); CHKERRQ(ierr);
1914 // Zero-initialize to ensure all pointers (especially packer) are NULL
1915 ierr = PetscMemzero(usermg->mgctx, usermg->mglevels * sizeof(MGCtx)); CHKERRQ(ierr);
1916 mgctx = usermg->mgctx;
1917 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Allocated MGCtx array of size %d.\n", simCtx->rank, usermg->mglevels);
1918
1919 // --- 2. Parse semi-coarsening options (logic from MG_Initial) ---
1920 // These flags determine if a dimension is coarsened in the multigrid hierarchy.
1921 PetscInt *isc, *jsc, *ksc;
1922 ierr = PetscMalloc3(nblk, &isc, nblk, &jsc, nblk, &ksc); CHKERRQ(ierr);
1923 // Set defaults to FALSE (full coarsening)
1924 for (PetscInt i = 0; i < nblk; ++i) {
1925 isc[i] = 0; jsc[i] = 0; ksc[i] = 0;
1926 }
1927
1928// Use a temporary variable for the 'count' argument to the parsing function.
1929 // This protects the original 'nblk' which is needed for the loop bounds.
1930 PetscInt n_opts_found = nblk;
1931 ierr = PetscOptionsGetIntArray(NULL, NULL, "-mg_i_semi", isc, &n_opts_found, &found); CHKERRQ(ierr);
1932
1933 n_opts_found = nblk; // Reset the temp variable before the next call
1934 ierr = PetscOptionsGetIntArray(NULL, NULL, "-mg_j_semi", jsc, &n_opts_found, &found); CHKERRQ(ierr);
1935
1936 n_opts_found = nblk; // Reset the temp variable before the next call
1937 ierr = PetscOptionsGetIntArray(NULL, NULL, "-mg_k_semi", ksc, &n_opts_found, &found); CHKERRQ(ierr);
1938
1939 // --- 3. Loop over levels and blocks to allocate UserCtx arrays ---
1940 for (PetscInt level = 0; level < simCtx->mglevels; level++) {
1941
1942 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Setting up MG Level %d...\n", simCtx->rank, level);
1943 // Allocate the array of UserCtx structs for this level
1944 ierr = PetscMalloc(nblk * sizeof(UserCtx), &mgctx[level].user); CHKERRQ(ierr);
1945 // It's good practice to zero out the memory to avoid uninitialized values
1946 ierr = PetscMemzero(mgctx[level].user, nblk * sizeof(UserCtx)); CHKERRQ(ierr);
1947 mgctx[level].thislevel = level;
1948
1949 for (PetscInt bi = 0; bi < nblk; bi++) {
1950 UserCtx *currentUser = &mgctx[level].user[bi];
1951 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Initializing UserCtx for Level %d, Block %d.\n", simCtx->rank, level, bi);
1952
1953 // --- CRITICAL STEP: Set the back-pointer to the master context ---
1954 currentUser->simCtx = simCtx;
1955
1956 // Initialize other per-context values
1957 currentUser->thislevel = level;
1958 currentUser->_this = bi; //
1959 currentUser->mglevels = usermg->mglevels;
1960
1961 // Assign semi-coarsening flags
1962 currentUser->isc = isc[bi];
1963 currentUser->jsc = jsc[bi];
1964 currentUser->ksc = ksc[bi];
1965
1966 // Link to finer/coarser contexts for multigrid operations. Levels are
1967 // allocated coarsest first, so the finer array does not exist yet when a
1968 // level is created; each level links itself into the coarser one instead.
1969 if (level > 0) {
1970 currentUser->user_c = &mgctx[level-1].user[bi];
1971 mgctx[level-1].user[bi].user_f = currentUser;
1972 LOG_ALLOW_SYNC(GLOBAL, LOG_DEBUG, "Rank %d: -> Linked to coarser context (user_c) and back (user_f).\n", simCtx->rank);
1973 }
1974 }
1975 }
1976
1977 // Log a summary of the parsed flags on each rank.
1978 if (get_log_level() >= LOG_DEBUG && nblk > 0) {
1979 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Final semi-coarsening configuration view:\n", simCtx->rank);
1980 for (PetscInt bi = 0; bi < nblk; ++bi) {
1981 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Block %d: i-semi=%d, j-semi=%d, k-semi=%d\n", simCtx->rank, bi, isc[bi], jsc[bi], ksc[bi]);
1982 }
1983 }
1984
1985 // Clean up temporary arrays
1986 ierr = PetscFree3(isc, jsc, ksc); CHKERRQ(ierr);
1987
1988 LOG_ALLOW(GLOBAL, LOG_INFO, "Context hierarchy allocation complete.\n");
1990 PetscFunctionReturn(0);
1991}
1992
1993#undef __FUNCT__
1994#define __FUNCT__ "SetupGridAndSolvers"
1995/**
1996 * @brief Implementation of \ref SetupGridAndSolvers().
1997 * @details Full API contract (arguments, ownership, side effects) is documented with
1998 * the header declaration in `include/setup.h`.
1999 * @see SetupGridAndSolvers()
2000 */
2001PetscErrorCode SetupGridAndSolvers(SimCtx *simCtx)
2002{
2003 PetscErrorCode ierr;
2004 PetscFunctionBeginUser;
2005
2007
2008 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Starting Grid and Solvers Setup ---\n");
2009
2010 // Phase 1: Allocate the UserMG and UserCtx hierarchy
2011 ierr = AllocateContextHierarchy(simCtx); CHKERRQ(ierr);
2012
2013 ierr = DefineAllGridDimensions(simCtx); CHKERRQ(ierr);
2014 ierr = InitializeAllGridDMs(simCtx); CHKERRQ(ierr);
2015 ierr = AssignAllGridCoordinates(simCtx); CHKERRQ(ierr);
2016 ierr = CreateAndInitializeAllVectors(simCtx); CHKERRQ(ierr);
2017
2018 // NOTE: CalculateAllGridMetrics is now called inside SetupBoundaryConditions (not here) to ensure:
2019 // 1. Boundary condition configuration data (boundary_faces) is available for periodic BC corrections
2020 // 2. Computed metrics are available for inlet/outlet area calculations
2021 // This resolves the circular dependency between BC setup and metric calculations.
2022
2023 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Grid and Solvers Setup Complete ---\n");
2024
2026 PetscFunctionReturn(0);
2027}
2028
2029
2030#undef __FUNCT__
2031#define __FUNCT__ "CreateAndInitializeAllVectors"
2032/**
2033 * @brief Internal helper implementation: `CreateAndInitializeAllVectors()`.
2034 * @details Local to this translation unit.
2035 */
2037{
2038 PetscErrorCode ierr;
2039 UserMG *usermg = &simCtx->usermg;
2040 MGCtx *mgctx = usermg->mgctx;
2041 PetscInt nblk = simCtx->block_number;
2042
2043 PetscFunctionBeginUser;
2044
2046
2047 LOG_ALLOW(GLOBAL, LOG_INFO, "Creating and initializing all simulation vectors...\n");
2048
2049 for (PetscInt level = usermg->mglevels-1; level >=0; level--) {
2050 for (PetscInt bi = 0; bi < nblk; bi++) {
2051 UserCtx *user = &mgctx[level].user[bi];
2052
2053 if(!user->da || !user->fda) {
2054 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE, "DMs not properly initialized in UserCtx before vector creation.");
2055 }
2056
2057 LOG_ALLOW_SYNC(LOCAL, LOG_DEBUG, "Rank %d: Creating vectors for level %d, block %d\n", simCtx->rank, level, bi);
2058
2059 // --- Group A: Primary Flow Fields (Global and Local) ---
2060 // These are the core solution variables.
2061 ierr = DMCreateGlobalVector(user->fda, &user->Ucont); CHKERRQ(ierr); ierr = VecSet(user->Ucont, 0.0); CHKERRQ(ierr);
2062 ierr = DMCreateGlobalVector(user->fda, &user->Ucat); CHKERRQ(ierr); ierr = VecSet(user->Ucat, 0.0); CHKERRQ(ierr);
2063 ierr = DMCreateGlobalVector(user->da, &user->P); CHKERRQ(ierr); ierr = VecSet(user->P, 0.0); CHKERRQ(ierr);
2064 ierr = DMCreateGlobalVector(user->da, &user->Nvert); CHKERRQ(ierr); ierr = VecSet(user->Nvert, 0.0); CHKERRQ(ierr);
2065
2066 ierr = DMCreateLocalVector(user->fda, &user->lUcont); CHKERRQ(ierr); ierr = VecSet(user->lUcont, 0.0); CHKERRQ(ierr);
2067 ierr = DMCreateLocalVector(user->fda, &user->lUcat); CHKERRQ(ierr); ierr = VecSet(user->lUcat, 0.0); CHKERRQ(ierr);
2068 ierr = DMCreateLocalVector(user->da, &user->lP); CHKERRQ(ierr); ierr = VecSet(user->lP, 0.0); CHKERRQ(ierr);
2069 ierr = DMCreateLocalVector(user->da, &user->lNvert); CHKERRQ(ierr); ierr = VecSet(user->lNvert, 0.0); CHKERRQ(ierr);
2070
2071 // -- Group A2: Derived Flow Fields (Global and Local) ---
2072 ierr = VecDuplicate(user->P,&user->Diffusivity); CHKERRQ(ierr); ierr = VecSet(user->Diffusivity, 0.0); CHKERRQ(ierr);
2073 ierr = VecDuplicate(user->lP,&user->lDiffusivity); CHKERRQ(ierr); ierr = VecSet(user->lDiffusivity, 0.0); CHKERRQ(ierr);
2074 ierr = VecDuplicate(user->Ucat,&user->DiffusivityGradient); CHKERRQ(ierr); ierr = VecSet(user->DiffusivityGradient, 0.0); CHKERRQ(ierr);
2075 ierr = VecDuplicate(user->lUcat,&user->lDiffusivityGradient); CHKERRQ(ierr); ierr = VecSet(user->lDiffusivityGradient, 0.0); CHKERRQ(ierr);
2076
2077 // -- Group B: Solver Work Vectors (Global and Local) ---
2078 ierr = VecDuplicate(user->P, &user->Phi); CHKERRQ(ierr); ierr = VecSet(user->Phi, 0.0); CHKERRQ(ierr);
2079 ierr = VecDuplicate(user->lP, &user->lPhi); CHKERRQ(ierr); ierr = VecSet(user->lPhi, 0.0); CHKERRQ(ierr);
2080
2081 // --- Group C: Time-Stepping & Workspace Fields (Finest Level Only) ---
2082 if (level == usermg->mglevels - 1) {
2083 ierr = VecDuplicate(user->Ucont, &user->Ucont_o); CHKERRQ(ierr); ierr = VecSet(user->Ucont_o, 0.0); CHKERRQ(ierr);
2084 ierr = VecDuplicate(user->Ucont, &user->Ucont_rm1); CHKERRQ(ierr); ierr = VecSet(user->Ucont_rm1, 0.0); CHKERRQ(ierr);
2085 ierr = VecDuplicate(user->Ucat, &user->Ucat_o); CHKERRQ(ierr); ierr = VecSet(user->Ucat_o, 0.0); CHKERRQ(ierr);
2086 ierr = VecDuplicate(user->P, &user->P_o); CHKERRQ(ierr); ierr = VecSet(user->P_o, 0.0); CHKERRQ(ierr);
2087 ierr = VecDuplicate(user->lUcont, &user->lUcont_o); CHKERRQ(ierr); ierr = VecSet(user->lUcont_o, 0.0); CHKERRQ(ierr);
2088 ierr = VecDuplicate(user->lUcont, &user->lUcont_rm1); CHKERRQ(ierr); ierr = VecSet(user->lUcont_rm1, 0.0); CHKERRQ(ierr);
2089 ierr = DMCreateLocalVector(user->da, &user->lNvert_o); CHKERRQ(ierr); ierr = VecSet(user->lNvert_o, 0.0); CHKERRQ(ierr);
2090 ierr = VecDuplicate(user->Nvert, &user->Nvert_o); CHKERRQ(ierr); ierr = VecSet(user->Nvert_o, 0.0); CHKERRQ(ierr);
2091 }
2092
2093 // --- Group D: Grid Metrics (Face-Centered) ---
2094 ierr = DMCreateGlobalVector(user->fda, &user->Csi); CHKERRQ(ierr); ierr = VecSet(user->Csi, 0.0); CHKERRQ(ierr);
2095 ierr = VecDuplicate(user->Csi, &user->Eta); CHKERRQ(ierr); ierr = VecSet(user->Eta, 0.0); CHKERRQ(ierr);
2096 ierr = VecDuplicate(user->Csi, &user->Zet); CHKERRQ(ierr); ierr = VecSet(user->Zet, 0.0); CHKERRQ(ierr);
2097 ierr = DMCreateGlobalVector(user->da, &user->Aj); CHKERRQ(ierr); ierr = VecSet(user->Aj, 0.0); CHKERRQ(ierr);
2098
2099 ierr = DMCreateLocalVector(user->fda, &user->lCsi); CHKERRQ(ierr); ierr = VecSet(user->lCsi, 0.0); CHKERRQ(ierr);
2100 ierr = VecDuplicate(user->lCsi, &user->lEta); CHKERRQ(ierr); ierr = VecSet(user->lEta, 0.0); CHKERRQ(ierr);
2101 ierr = VecDuplicate(user->lCsi, &user->lZet); CHKERRQ(ierr); ierr = VecSet(user->lZet, 0.0); CHKERRQ(ierr);
2102 ierr = DMCreateLocalVector(user->da, &user->lAj); CHKERRQ(ierr); ierr = VecSet(user->lAj, 0.0); CHKERRQ(ierr);
2103
2104
2105 // --- Group E: Grid Metrics (Face-Centered) ---
2106 // Vector metrics are duplicated from Csi (DOF=3, fda-based)
2107 ierr = VecDuplicate(user->Csi, &user->ICsi); CHKERRQ(ierr); ierr = VecSet(user->ICsi, 0.0); CHKERRQ(ierr);
2108 ierr = VecDuplicate(user->Csi, &user->IEta); CHKERRQ(ierr); ierr = VecSet(user->IEta, 0.0); CHKERRQ(ierr);
2109 ierr = VecDuplicate(user->Csi, &user->IZet); CHKERRQ(ierr); ierr = VecSet(user->IZet, 0.0); CHKERRQ(ierr);
2110 ierr = VecDuplicate(user->Csi, &user->JCsi); CHKERRQ(ierr); ierr = VecSet(user->JCsi, 0.0); CHKERRQ(ierr);
2111 ierr = VecDuplicate(user->Csi, &user->JEta); CHKERRQ(ierr); ierr = VecSet(user->JEta, 0.0); CHKERRQ(ierr);
2112 ierr = VecDuplicate(user->Csi, &user->JZet); CHKERRQ(ierr); ierr = VecSet(user->JZet, 0.0); CHKERRQ(ierr);
2113 ierr = VecDuplicate(user->Csi, &user->KCsi); CHKERRQ(ierr); ierr = VecSet(user->KCsi, 0.0); CHKERRQ(ierr);
2114 ierr = VecDuplicate(user->Csi, &user->KEta); CHKERRQ(ierr); ierr = VecSet(user->KEta, 0.0); CHKERRQ(ierr);
2115 ierr = VecDuplicate(user->Csi, &user->KZet); CHKERRQ(ierr); ierr = VecSet(user->KZet, 0.0); CHKERRQ(ierr);
2116 // Scalar metrics are duplicated from Aj (DOF=1, da-based)
2117 ierr = VecDuplicate(user->Aj, &user->IAj); CHKERRQ(ierr); ierr = VecSet(user->IAj, 0.0); CHKERRQ(ierr);
2118 ierr = VecDuplicate(user->Aj, &user->JAj); CHKERRQ(ierr); ierr = VecSet(user->JAj, 0.0); CHKERRQ(ierr);
2119 ierr = VecDuplicate(user->Aj, &user->KAj); CHKERRQ(ierr); ierr = VecSet(user->KAj, 0.0); CHKERRQ(ierr);
2120
2121 ierr = VecDuplicate(user->lCsi, &user->lICsi); CHKERRQ(ierr); ierr = VecSet(user->lICsi, 0.0); CHKERRQ(ierr);
2122 ierr = VecDuplicate(user->lCsi, &user->lIEta); CHKERRQ(ierr); ierr = VecSet(user->lIEta, 0.0); CHKERRQ(ierr);
2123 ierr = VecDuplicate(user->lCsi, &user->lIZet); CHKERRQ(ierr); ierr = VecSet(user->lIZet, 0.0); CHKERRQ(ierr);
2124 ierr = VecDuplicate(user->lCsi, &user->lJCsi); CHKERRQ(ierr); ierr = VecSet(user->lJCsi, 0.0); CHKERRQ(ierr);
2125 ierr = VecDuplicate(user->lCsi, &user->lJEta); CHKERRQ(ierr); ierr = VecSet(user->lJEta, 0.0); CHKERRQ(ierr);
2126 ierr = VecDuplicate(user->lCsi, &user->lJZet); CHKERRQ(ierr); ierr = VecSet(user->lJZet, 0.0); CHKERRQ(ierr);
2127 ierr = VecDuplicate(user->lCsi, &user->lKCsi); CHKERRQ(ierr); ierr = VecSet(user->lKCsi, 0.0); CHKERRQ(ierr);
2128 ierr = VecDuplicate(user->lCsi, &user->lKEta); CHKERRQ(ierr); ierr = VecSet(user->lKEta, 0.0); CHKERRQ(ierr);
2129 ierr = VecDuplicate(user->lCsi, &user->lKZet); CHKERRQ(ierr); ierr = VecSet(user->lKZet, 0.0); CHKERRQ(ierr);
2130
2131 ierr = VecDuplicate(user->lAj, &user->lIAj); CHKERRQ(ierr); ierr = VecSet(user->lIAj, 0.0); CHKERRQ(ierr);
2132 ierr = VecDuplicate(user->lAj, &user->lJAj); CHKERRQ(ierr); ierr = VecSet(user->lJAj, 0.0); CHKERRQ(ierr);
2133 ierr = VecDuplicate(user->lAj, &user->lKAj); CHKERRQ(ierr); ierr = VecSet(user->lKAj, 0.0); CHKERRQ(ierr);
2134
2135 // --- Group F: Cell/Face Center Coordinates and Grid Spacing ---
2136 ierr = DMCreateGlobalVector(user->fda, &user->Cent); CHKERRQ(ierr); ierr = VecSet(user->Cent, 0.0); CHKERRQ(ierr);
2137 ierr = DMCreateLocalVector(user->fda, &user->lCent); CHKERRQ(ierr); ierr = VecSet(user->lCent, 0.0); CHKERRQ(ierr);
2138
2139 ierr = VecDuplicate(user->Cent, &user->GridSpace); CHKERRQ(ierr); ierr = VecSet(user->GridSpace, 0.0); CHKERRQ(ierr);
2140 ierr = VecDuplicate(user->lCent, &user->lGridSpace); CHKERRQ(ierr); ierr = VecSet(user->lGridSpace, 0.0); CHKERRQ(ierr);
2141
2142 ierr = VecDuplicate(user->Cent, &user->Centx); CHKERRQ(ierr); ierr = VecSet(user->Centx, 0.0); CHKERRQ(ierr);
2143 ierr = VecDuplicate(user->Cent, &user->Centy); CHKERRQ(ierr); ierr = VecSet(user->Centy, 0.0); CHKERRQ(ierr);
2144 ierr = VecDuplicate(user->Cent, &user->Centz); CHKERRQ(ierr); ierr = VecSet(user->Centz, 0.0); CHKERRQ(ierr);
2145 ierr = VecDuplicate(user->lCent, &user->lCentx); CHKERRQ(ierr); ierr = VecSet(user->lCentx, 0.0); CHKERRQ(ierr);
2146 ierr = VecDuplicate(user->lCent, &user->lCenty); CHKERRQ(ierr); ierr = VecSet(user->lCenty, 0.0); CHKERRQ(ierr);
2147 ierr = VecDuplicate(user->lCent, &user->lCentz); CHKERRQ(ierr); ierr = VecSet(user->lCentz, 0.0); CHKERRQ(ierr);
2148
2149 if(level == usermg->mglevels -1){
2150 // --- Group G: Turbulence Models (Finest Level Only) ---
2151 if (simCtx->les) {
2152 ierr = DMCreateGlobalVector(user->da, &user->Nu_t); CHKERRQ(ierr); ierr = VecSet(user->Nu_t, 0.0); CHKERRQ(ierr);
2153 ierr = DMCreateLocalVector(user->da, &user->lNu_t); CHKERRQ(ierr); ierr = VecSet(user->lNu_t, 0.0); CHKERRQ(ierr);
2154 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Turbulence viscosity (Nu_t) vectors created for the LES model.\n");
2155 // Only the dynamic model carries a coefficient field. The constant model
2156 // reads its coefficient straight from configuration, so allocating,
2157 // synchronizing, and checkpointing a field of one repeated number would
2158 // buy nothing.
2159 if(simCtx->les == DYNAMIC_SMAGORINSKY){
2160 ierr = DMCreateGlobalVector(user->da,&user->CS); CHKERRQ(ierr); ierr = VecSet(user->CS,0.0); CHKERRQ(ierr);
2161 ierr = DMCreateLocalVector(user->da,&user->lCs); CHKERRQ(ierr); ierr = VecSet(user->lCs,0.0); CHKERRQ(ierr);
2162 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Dynamic Smagorinsky coefficient (CS) vectors created.\n");
2163 }
2164
2165 // Add K_Omega etc. here as needed
2166
2167 // Note: Add any other vectors from the legacy MG_Initial here as needed.
2168 // For example: Rhs, Forcing, turbulence Vecs (K_Omega, Nu_t)...
2169
2170 }
2171
2172 /* A wall model is configured independently of LES and RANS - `wall_function`
2173 is their sibling in the schema, not their child - so its storage cannot sit
2174 inside their block. It did, which left `ApplyWallFunction` dereferencing a
2175 null vector on any case that enabled a wall model without one of them. */
2176 if (simCtx->wallfunction) {
2177 /* Scalar per cell, so it lives on `da`. It was previously created from
2178 `fda`, whose dof is 3, while every reader opens it through `da` - a
2179 pairing PETSc rejects outright. */
2180 ierr = DMCreateGlobalVector(user->da, &user->Friction_Velocity); CHKERRQ(ierr);
2181 ierr = VecSet(user->Friction_Velocity, 0.0); CHKERRQ(ierr);
2182 ierr = DMCreateLocalVector(user->da, &user->lFriction_Velocity); CHKERRQ(ierr);
2183 ierr = VecSet(user->lFriction_Velocity, 0.0); CHKERRQ(ierr);
2184 ierr = DMCreateGlobalVector(user->da, &user->Nu_Wall); CHKERRQ(ierr);
2185 ierr = VecSet(user->Nu_Wall, 0.0); CHKERRQ(ierr);
2186 ierr = DMCreateLocalVector(user->da, &user->lNu_Wall); CHKERRQ(ierr);
2187 ierr = VecSet(user->lNu_Wall, 0.0); CHKERRQ(ierr);
2188 }
2189 // --- Group H: Particle Methods
2190 if(simCtx->np>0){
2191 ierr = DMCreateGlobalVector(user->da,&user->ParticleCount); CHKERRQ(ierr); ierr = VecSet(user->ParticleCount,0.0); CHKERRQ(ierr);
2192 ierr = DMCreateLocalVector(user->da,&user->lParticleCount); CHKERRQ(ierr); ierr = VecSet(user->lParticleCount,0.0); CHKERRQ(ierr);
2193 // Scalar field to hold particle scalar property (e.g., temperature, concentration)
2194 ierr = DMCreateGlobalVector(user->da,&user->Psi); CHKERRQ(ierr); ierr = VecSet(user->Psi,0.0); CHKERRQ(ierr);
2195 ierr = DMCreateLocalVector(user->da,&user->lPsi); CHKERRQ(ierr); ierr = VecSet(user->lPsi,0.0); CHKERRQ(ierr);
2196 LOG_ALLOW(GLOBAL,LOG_DEBUG,"ParticleCount & Scalar(Psi) created for %d particles.\n",simCtx->np);
2197 }
2198 }
2199 // --- Group I: Boundary Condition vectors ---
2200 ierr = DMCreateGlobalVector(user->fda, &user->Bcs.Ubcs); CHKERRQ(ierr);
2201 ierr = VecSet(user->Bcs.Ubcs, 0.0); CHKERRQ(ierr);
2202 ierr = DMCreateGlobalVector(user->fda, &user->Bcs.Uch); CHKERRQ(ierr);
2203 ierr = VecSet(user->Bcs.Uch, 0.0); CHKERRQ(ierr);
2204
2205 // --- Group L: Field-Statistics Accumulators (Finest Level Only) ---
2206 // Config-counted, like the convergence-state reference fields: the window
2207 // count is resolved before this factory runs, and each accumulator is
2208 // duplicated from a vector created above so it inherits DM and layout.
2209 if (level == usermg->mglevels - 1 && FieldStatisticsIsActive(simCtx)) {
2210 ierr = PetscCalloc1((size_t)simCtx->fieldStatisticsWindowCount,
2211 &user->fieldStatisticsStorage); CHKERRQ(ierr);
2212 for (PetscInt w = 0; w < simCtx->fieldStatisticsWindowCount; ++w) {
2213 ierr = PicurvWindowStorageCreate(user,
2215 &user->fieldStatisticsStorage[w]); CHKERRQ(ierr);
2216 }
2218 "Allocated accumulators for %d statistics window(s).\n",
2220 }
2221
2222 // --- Group K: Corner-Staging Workspace (Finest Level Only) ---
2223 // One pair per block size. Previously these were created lazily inside
2224 // the interpolation routine and rebuilt whenever the block size changed;
2225 // allocating both here removes that churn and the cached-size check.
2226 if (level == usermg->mglevels - 1) {
2227 ierr = DMCreateGlobalVector(user->da, &user->CellScalarAtCorner); CHKERRQ(ierr);
2228 ierr = VecSet(user->CellScalarAtCorner, 0.0); CHKERRQ(ierr);
2229 ierr = DMCreateLocalVector(user->da, &user->lCellScalarAtCorner); CHKERRQ(ierr);
2230 ierr = VecSet(user->lCellScalarAtCorner, 0.0); CHKERRQ(ierr);
2231 ierr = DMCreateGlobalVector(user->fda, &user->CellVectorAtCorner); CHKERRQ(ierr);
2232 ierr = VecSet(user->CellVectorAtCorner, 0.0); CHKERRQ(ierr);
2233 ierr = DMCreateLocalVector(user->fda, &user->lCellVectorAtCorner); CHKERRQ(ierr);
2234 ierr = VecSet(user->lCellVectorAtCorner, 0.0); CHKERRQ(ierr);
2235 }
2236
2237 /* --- Group N: Statistics Output Staging (Finest Level Only) ---
2238 * The same kind of object as the corner workspace: a named buffer the
2239 * shared kernels can address, rather than simulation state. It exists only
2240 * to carry derived statistics to the nodal and VTK paths, so a run without
2241 * statistics allocates none of it. */
2242 if (level == usermg->mglevels - 1 && FieldStatisticsIsActive(simCtx)) {
2243 ierr = DMCreateGlobalVector(user->da, &user->PostScalar); CHKERRQ(ierr);
2244 ierr = VecSet(user->PostScalar, 0.0); CHKERRQ(ierr);
2245 ierr = DMCreateLocalVector(user->da, &user->lPostScalar); CHKERRQ(ierr);
2246 ierr = VecSet(user->lPostScalar, 0.0); CHKERRQ(ierr);
2247 ierr = DMCreateGlobalVector(user->da, &user->PostScalarNodal); CHKERRQ(ierr);
2248 ierr = VecSet(user->PostScalarNodal, 0.0); CHKERRQ(ierr);
2249 ierr = DMCreateGlobalVector(user->fda, &user->PostVector); CHKERRQ(ierr);
2250 ierr = VecSet(user->PostVector, 0.0); CHKERRQ(ierr);
2251 ierr = DMCreateLocalVector(user->fda, &user->lPostVector); CHKERRQ(ierr);
2252 ierr = VecSet(user->lPostVector, 0.0); CHKERRQ(ierr);
2253 ierr = DMCreateGlobalVector(user->fda, &user->PostVectorNodal); CHKERRQ(ierr);
2254 ierr = VecSet(user->PostVectorNodal, 0.0); CHKERRQ(ierr);
2255 }
2256
2257 if(level == usermg->mglevels - 1){
2258 if(simCtx->exec_mode == EXEC_MODE_POSTPROCESSOR){
2259 LOG_ALLOW(LOCAL, LOG_DEBUG, "Post-processor mode detected. Allocating derived field vectors.\n");
2260
2261 ierr = VecDuplicate(user->P, &user->P_nodal); CHKERRQ(ierr);
2262 ierr = VecSet(user->P_nodal, 0.0); CHKERRQ(ierr);
2263
2264 ierr = VecDuplicate(user->Ucat, &user->Ucat_nodal); CHKERRQ(ierr);
2265 ierr = VecSet(user->Ucat_nodal, 0.0); CHKERRQ(ierr);
2266
2267 ierr = VecDuplicate(user->P, &user->Qcrit); CHKERRQ(ierr);
2268 ierr = VecSet(user->Qcrit, 0.0); CHKERRQ(ierr);
2269 ierr = DMCreateLocalVector(user->da, &user->lQcrit); CHKERRQ(ierr);
2270 ierr = VecSet(user->lQcrit, 0.0); CHKERRQ(ierr);
2271 ierr = VecDuplicate(user->P, &user->Qcrit_nodal); CHKERRQ(ierr);
2272 ierr = VecSet(user->Qcrit_nodal, 0.0); CHKERRQ(ierr);
2273
2274 LOG_ALLOW(LOCAL, LOG_DEBUG, "Derived field vectors P_nodal, Ucat_nodal, and Qcrit created.\n");
2275
2276 if(simCtx->np>0){
2277 ierr = VecDuplicate(user->Psi, &user->Psi_nodal); CHKERRQ(ierr);
2278 ierr = VecSet(user->Psi_nodal, 0.0); CHKERRQ(ierr);
2279
2280 LOG_ALLOW(LOCAL, LOG_DEBUG, "Derived field vector Psi_nodal created for particle scalar property.\n");
2281
2282 }
2283 }else{
2284 user->P_nodal = NULL;
2285 user->Ucat_nodal = NULL;
2286 user->Qcrit = NULL;
2287 user->lQcrit = NULL;
2288 user->Qcrit_nodal = NULL;
2289 user->Psi_nodal = NULL;
2290 }
2291 }
2292
2293 }
2294}
2295
2296 /* Config-counted vectors belong to this factory too. The convergence state
2297 * allocates a Vec array whose length comes from configuration, so it is
2298 * invoked here rather than by the caller, keeping every vector this run
2299 * owns created in one place. It depends only on option-parsed configuration
2300 * and on the vectors created above. */
2301 ierr = InitializeSolutionConvergenceState(simCtx); CHKERRQ(ierr);
2302
2303 LOG_ALLOW(GLOBAL, LOG_INFO, "All simulation vectors created and initialized.\n");
2304
2306 PetscFunctionReturn(0);
2307}
2308
2309#undef __FUNCT__
2310#define __FUNCT__ "RepairPeriodicNormalFaceGhosts"
2311/**
2312 * @brief Repairs the adjacent normal ghost layer for periodic face-staggered data.
2313 *
2314 * PETSc wraps every component with cell-style indexing. A face family instead
2315 * needs its adjacent normal ghost shifted by one additional physical face. With
2316 * the width-three periodic DMDA, the required value is available in the deeper
2317 * PETSc ghost at -3 or n+2. Tangential ghosts retain PETSc's native wraparound.
2318 */
2319static PetscErrorCode RepairPeriodicNormalFaceGhosts(UserCtx *user, DM dm, Vec local_vec,
2320 PetscInt dof, char face_direction,
2321 PetscBool component_staggered)
2322{
2323 DMDALocalInfo info;
2324 PetscInt xs, xe, ys, ye, zs, ze;
2325 PetscInt gxs, gxe, gys, gye, gzs, gze;
2326 PetscInt mx, my, mz;
2327
2328 PetscFunctionBeginUser;
2329 if (!face_direction && !component_staggered) PetscFunctionReturn(0);
2330
2331 PetscCall(DMDAGetLocalInfo(dm, &info));
2332 xs = info.xs; xe = info.xs + info.xm;
2333 ys = info.ys; ye = info.ys + info.ym;
2334 zs = info.zs; ze = info.zs + info.zm;
2335 gxs = info.gxs; gxe = info.gxs + info.gxm;
2336 gys = info.gys; gye = info.gys + info.gym;
2337 gzs = info.gzs; gze = info.gzs + info.gzm;
2338 mx = info.mx; my = info.my; mz = info.mz;
2339
2340 if (component_staggered) {
2341 Cmpnts ***array;
2342 PetscCall(DMDAVecGetArray(dm, local_vec, &array));
2343
2344 if (user->boundary_faces[BC_FACE_NEG_X].mathematical_type == PERIODIC && xs == 0) {
2345 PetscCheck(gxs <= -3, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ,
2346 "Periodic Ucont.x ghost repair requires DMDA stencil width at least 3.");
2347 for (PetscInt k = gzs; k < gze; k++) for (PetscInt j = gys; j < gye; j++)
2348 array[k][j][-1].x = array[k][j][-3].x;
2349 }
2350 if (user->boundary_faces[BC_FACE_POS_X].mathematical_type == PERIODIC && xe == mx) {
2351 PetscCheck(gxe > mx + 2, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ,
2352 "Periodic Ucont.x ghost repair requires DMDA stencil width at least 3.");
2353 for (PetscInt k = gzs; k < gze; k++) for (PetscInt j = gys; j < gye; j++)
2354 array[k][j][mx].x = array[k][j][mx + 2].x;
2355 }
2356 if (user->boundary_faces[BC_FACE_NEG_Y].mathematical_type == PERIODIC && ys == 0) {
2357 PetscCheck(gys <= -3, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ,
2358 "Periodic Ucont.y ghost repair requires DMDA stencil width at least 3.");
2359 for (PetscInt k = gzs; k < gze; k++) for (PetscInt i = gxs; i < gxe; i++)
2360 array[k][-1][i].y = array[k][-3][i].y;
2361 }
2362 if (user->boundary_faces[BC_FACE_POS_Y].mathematical_type == PERIODIC && ye == my) {
2363 PetscCheck(gye > my + 2, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ,
2364 "Periodic Ucont.y ghost repair requires DMDA stencil width at least 3.");
2365 for (PetscInt k = gzs; k < gze; k++) for (PetscInt i = gxs; i < gxe; i++)
2366 array[k][my][i].y = array[k][my + 2][i].y;
2367 }
2368 if (user->boundary_faces[BC_FACE_NEG_Z].mathematical_type == PERIODIC && zs == 0) {
2369 PetscCheck(gzs <= -3, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ,
2370 "Periodic Ucont.z ghost repair requires DMDA stencil width at least 3.");
2371 for (PetscInt j = gys; j < gye; j++) for (PetscInt i = gxs; i < gxe; i++)
2372 array[-1][j][i].z = array[-3][j][i].z;
2373 }
2374 if (user->boundary_faces[BC_FACE_POS_Z].mathematical_type == PERIODIC && ze == mz) {
2375 PetscCheck(gze > mz + 2, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ,
2376 "Periodic Ucont.z ghost repair requires DMDA stencil width at least 3.");
2377 for (PetscInt j = gys; j < gye; j++) for (PetscInt i = gxs; i < gxe; i++)
2378 array[mz][j][i].z = array[mz + 2][j][i].z;
2379 }
2380
2381 PetscCall(DMDAVecRestoreArray(dm, local_vec, &array));
2382 PetscFunctionReturn(0);
2383 }
2384
2385 if (dof == 1) {
2386 PetscReal ***array;
2387 PetscCall(DMDAVecGetArray(dm, local_vec, &array));
2388
2389 if (face_direction == 'i') {
2390 if (user->boundary_faces[BC_FACE_NEG_X].mathematical_type == PERIODIC && xs == 0) {
2391 PetscCheck(gxs <= -3, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ,
2392 "Periodic I-face ghost repair requires DMDA stencil width at least 3.");
2393 for (PetscInt k = gzs; k < gze; k++) for (PetscInt j = gys; j < gye; j++)
2394 array[k][j][-1] = array[k][j][-3];
2395 }
2396 if (user->boundary_faces[BC_FACE_POS_X].mathematical_type == PERIODIC && xe == mx) {
2397 PetscCheck(gxe > mx + 2, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ,
2398 "Periodic I-face ghost repair requires DMDA stencil width at least 3.");
2399 for (PetscInt k = gzs; k < gze; k++) for (PetscInt j = gys; j < gye; j++)
2400 array[k][j][mx] = array[k][j][mx + 2];
2401 }
2402 } else if (face_direction == 'j') {
2403 if (user->boundary_faces[BC_FACE_NEG_Y].mathematical_type == PERIODIC && ys == 0) {
2404 PetscCheck(gys <= -3, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ,
2405 "Periodic J-face ghost repair requires DMDA stencil width at least 3.");
2406 for (PetscInt k = gzs; k < gze; k++) for (PetscInt i = gxs; i < gxe; i++)
2407 array[k][-1][i] = array[k][-3][i];
2408 }
2409 if (user->boundary_faces[BC_FACE_POS_Y].mathematical_type == PERIODIC && ye == my) {
2410 PetscCheck(gye > my + 2, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ,
2411 "Periodic J-face ghost repair requires DMDA stencil width at least 3.");
2412 for (PetscInt k = gzs; k < gze; k++) for (PetscInt i = gxs; i < gxe; i++)
2413 array[k][my][i] = array[k][my + 2][i];
2414 }
2415 } else if (face_direction == 'k') {
2416 if (user->boundary_faces[BC_FACE_NEG_Z].mathematical_type == PERIODIC && zs == 0) {
2417 PetscCheck(gzs <= -3, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ,
2418 "Periodic K-face ghost repair requires DMDA stencil width at least 3.");
2419 for (PetscInt j = gys; j < gye; j++) for (PetscInt i = gxs; i < gxe; i++)
2420 array[-1][j][i] = array[-3][j][i];
2421 }
2422 if (user->boundary_faces[BC_FACE_POS_Z].mathematical_type == PERIODIC && ze == mz) {
2423 PetscCheck(gze > mz + 2, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ,
2424 "Periodic K-face ghost repair requires DMDA stencil width at least 3.");
2425 for (PetscInt j = gys; j < gye; j++) for (PetscInt i = gxs; i < gxe; i++)
2426 array[mz][j][i] = array[mz + 2][j][i];
2427 }
2428 }
2429
2430 PetscCall(DMDAVecRestoreArray(dm, local_vec, &array));
2431 } else {
2432 Cmpnts ***array;
2433 PetscCall(DMDAVecGetArray(dm, local_vec, &array));
2434
2435 if (face_direction == 'i') {
2436 if (user->boundary_faces[BC_FACE_NEG_X].mathematical_type == PERIODIC && xs == 0) {
2437 PetscCheck(gxs <= -3, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ,
2438 "Periodic I-face ghost repair requires DMDA stencil width at least 3.");
2439 for (PetscInt k = gzs; k < gze; k++) for (PetscInt j = gys; j < gye; j++)
2440 array[k][j][-1] = array[k][j][-3];
2441 }
2442 if (user->boundary_faces[BC_FACE_POS_X].mathematical_type == PERIODIC && xe == mx) {
2443 PetscCheck(gxe > mx + 2, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ,
2444 "Periodic I-face ghost repair requires DMDA stencil width at least 3.");
2445 for (PetscInt k = gzs; k < gze; k++) for (PetscInt j = gys; j < gye; j++)
2446 array[k][j][mx] = array[k][j][mx + 2];
2447 }
2448 } else if (face_direction == 'j') {
2449 if (user->boundary_faces[BC_FACE_NEG_Y].mathematical_type == PERIODIC && ys == 0) {
2450 PetscCheck(gys <= -3, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ,
2451 "Periodic J-face ghost repair requires DMDA stencil width at least 3.");
2452 for (PetscInt k = gzs; k < gze; k++) for (PetscInt i = gxs; i < gxe; i++)
2453 array[k][-1][i] = array[k][-3][i];
2454 }
2455 if (user->boundary_faces[BC_FACE_POS_Y].mathematical_type == PERIODIC && ye == my) {
2456 PetscCheck(gye > my + 2, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ,
2457 "Periodic J-face ghost repair requires DMDA stencil width at least 3.");
2458 for (PetscInt k = gzs; k < gze; k++) for (PetscInt i = gxs; i < gxe; i++)
2459 array[k][my][i] = array[k][my + 2][i];
2460 }
2461 } else if (face_direction == 'k') {
2462 if (user->boundary_faces[BC_FACE_NEG_Z].mathematical_type == PERIODIC && zs == 0) {
2463 PetscCheck(gzs <= -3, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ,
2464 "Periodic K-face ghost repair requires DMDA stencil width at least 3.");
2465 for (PetscInt j = gys; j < gye; j++) for (PetscInt i = gxs; i < gxe; i++)
2466 array[-1][j][i] = array[-3][j][i];
2467 }
2468 if (user->boundary_faces[BC_FACE_POS_Z].mathematical_type == PERIODIC && ze == mz) {
2469 PetscCheck(gze > mz + 2, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ,
2470 "Periodic K-face ghost repair requires DMDA stencil width at least 3.");
2471 for (PetscInt j = gys; j < gye; j++) for (PetscInt i = gxs; i < gxe; i++)
2472 array[mz][j][i] = array[mz + 2][j][i];
2473 }
2474 }
2475
2476 PetscCall(DMDAVecRestoreArray(dm, local_vec, &array));
2477 }
2478
2479 PetscFunctionReturn(0);
2480}
2481
2482#undef __FUNCT__
2483#define __FUNCT__ "UpdateLocalGhosts"
2484/**
2485 * @brief Updates a catalogued field's local ghost representation.
2486 * @details Resolves the typed field view, performs the existing PETSc scatter,
2487 * and applies the established periodic normal-face repair when needed.
2488 */
2489PetscErrorCode UpdateLocalGhosts(UserCtx *user, FieldId field_id)
2490{
2491 PetscErrorCode ierr;
2492 PetscMPIInt rank;
2493 FieldView field_view;
2494 const char *field_name;
2495 Vec globalVec;
2496 Vec localVec;
2497 DM dm;
2498 PetscInt dof;
2499 char face_direction = '\0';
2500 PetscBool component_staggered = PETSC_FALSE;
2501
2502 PetscFunctionBeginUser; // Use User version for application code
2504 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
2505 ierr = FieldGetView(user, field_id, &field_view); CHKERRQ(ierr);
2506 PetscCheck((field_view.descriptor->capabilities & FIELD_CAPABILITY_GHOST_UPDATE) != 0u,
2507 PETSC_COMM_SELF, PETSC_ERR_SUP,
2508 "Field '%s' does not support ghost updates.",
2509 field_view.descriptor->canonical_name);
2510 field_name = field_view.descriptor->canonical_name;
2511 globalVec = field_view.global_vec;
2512 localVec = field_view.local_vec;
2513 dm = field_view.dm;
2514 dof = field_view.descriptor->dof;
2515
2516 switch (field_view.descriptor->sync_class) {
2517 case FIELD_SYNC_I_FACE:
2518 face_direction = 'i';
2519 break;
2520 case FIELD_SYNC_J_FACE:
2521 face_direction = 'j';
2522 break;
2523 case FIELD_SYNC_K_FACE:
2524 face_direction = 'k';
2525 break;
2527 component_staggered = PETSC_TRUE;
2528 break;
2530 break;
2531 default:
2532 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_PLIB,
2533 "Field '%s' has an invalid ghost synchronization class.", field_name);
2534 }
2535
2536 LOG_ALLOW(GLOBAL, LOG_INFO, "Rank %d: Starting ghost update for field '%s'.\n", rank, field_name);
2537
2538 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Identified components for '%s': DM=%p, GlobalVec=%p, LocalVec=%p.\n",
2539 rank, field_name, (void*)dm, (void*)globalVec, (void*)localVec);
2540
2541 // --- 3. Optional Debugging: Norm Before Update ---
2542 // Use your logging convention check
2543 // if (get_log_level() >= LOG_LEVEL_DEBUG && is_function_allowed("UpdateLocalGhosts")) { // Example check
2544 if(get_log_level() == LOG_DEBUG && is_function_allowed(__func__)){
2545 PetscReal norm_global_before;
2546 ierr = VecNorm(globalVec, NORM_INFINITY, &norm_global_before); CHKERRQ(ierr);
2547 LOG_ALLOW(GLOBAL, LOG_INFO,"Max norm '%s' (Global) BEFORE Ghost Update: %g\n", field_name, norm_global_before);
2548 // Optional: Norm of local vector before update (might contain old ghost values)
2549 // PetscReal norm_local_before;
2550 // ierr = VecNorm(localVec, NORM_INFINITY, &norm_local_before); CHKERRQ(ierr);
2551 // LOG_ALLOW(GLOBAL, LOG_DEBUG,"Max norm '%s' (Local) BEFORE Ghost Update: %g\n", field_name, norm_local_before);
2552 }
2553
2554 // --- 4. Perform the Global-to-Local Transfer (Ghost Update) ---
2555 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Calling DMGlobalToLocalBegin/End for '%s'.\n", rank, field_name);
2556 ierr = DMGlobalToLocalBegin(dm, globalVec, INSERT_VALUES, localVec); CHKERRQ(ierr);
2557 ierr = DMGlobalToLocalEnd(dm, globalVec, INSERT_VALUES, localVec); CHKERRQ(ierr);
2558 ierr = RepairPeriodicNormalFaceGhosts(user, dm, localVec, dof, face_direction,
2559 component_staggered); CHKERRQ(ierr);
2560 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Completed DMGlobalToLocalBegin/End for '%s'.\n", rank, field_name);
2561
2562 // --- 5. Optional Debugging: Norm After Update ---
2563 // Use your logging convention check
2564 // if (get_log_level() >= LOG_LEVEL_DEBUG && is_function_allowed("UpdateLocalGhosts")) { // Example check
2565 if(get_log_level() == LOG_DEBUG && is_function_allowed(__func__)){ // Using your specific check
2566 PetscReal norm_local_after;
2567 ierr = VecNorm(localVec, NORM_INFINITY, &norm_local_after); CHKERRQ(ierr);
2568 LOG_ALLOW(GLOBAL, LOG_INFO,"Max norm '%s' (Local) AFTER Ghost Update: %g\n", field_name, norm_local_after);
2569
2570 // --- 6. Optional Debugging: Specific Point Checks (Example for Ucat on Rank 0/1) ---
2571 // (Keep this conditional if it's only for specific debug scenarios)
2572 if (field_id == FIELD_ID_UCAT) { // Only do detailed checks for Ucat for now
2573 PetscMPIInt rank_test;
2574 MPI_Comm_rank(PETSC_COMM_WORLD, &rank_test);
2575
2576 // Get Local Info needed for indexing checks
2577 DMDALocalInfo info_check;
2578 ierr = DMDAGetLocalInfo(dm, &info_check); CHKERRQ(ierr); // Use the correct dm
2579
2580 // Buffer for array pointer
2581 Cmpnts ***lUcat_arr_test = NULL;
2582 PetscErrorCode ierr_test = 0;
2583
2584 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Testing '%s' access immediately after ghost update...\n", rank_test, field_name);
2585 ierr_test = DMDAVecGetArrayDOFRead(dm, localVec, &lUcat_arr_test); // Use correct dm and localVec
2586
2587 if (ierr_test) {
2588 LOG_ALLOW(LOCAL, LOG_ERROR, "Rank %d: ERROR %d getting '%s' array after ghost update!\n", rank_test, ierr_test, field_name);
2589 } else if (!lUcat_arr_test) {
2590 LOG_ALLOW(LOCAL, LOG_ERROR, "Rank %d: ERROR NULL pointer getting '%s' array after ghost update!\n", rank_test, field_name);
2591 }
2592 else {
2593 // Check owned interior point (e.g., first interior point)
2594 PetscInt k_int = info_check.zs + (info_check.zm > 1 ? 1 : 0); // Global k index (at least zs+1 if possible)
2595 PetscInt j_int = info_check.ys + (info_check.ym > 1 ? 1 : 0); // Global j index
2596 PetscInt i_int = info_check.xs + (info_check.xm > 1 ? 1 : 0); // Global i index
2597 // Ensure indices are within global bounds if domain is very small
2598 //if (k_int >= info_check.mz-1) k_int = info_check.mz-2; if (k_int < 1) k_int = 1;
2599 //if (j_int >= info_check.my-1) j_int = info_check.my-2; if (j_int < 1) j_int = 1;
2600 // if (i_int >= info_check.mx-1) i_int = info_check.mx-2; if (i_int < 1) i_int = 1;
2601 // clamp k_int to [1 .. mz-2]
2602 if (k_int >= info_check.mz - 1) {
2603 k_int = info_check.mz - 2;
2604 }
2605 if (k_int < 1) {
2606 k_int = 1;
2607 }
2608
2609 // clamp j_int to [1 .. my-2]
2610 if (j_int >= info_check.my - 1) {
2611 j_int = info_check.my - 2;
2612 }
2613 if (j_int < 1) {
2614 j_int = 1;
2615 }
2616
2617 // clamp i_int to [1 .. mx-2]
2618 if (i_int >= info_check.mx - 1) {
2619 i_int = info_check.mx - 2;
2620 }
2621 if (i_int < 1) {
2622 i_int = 1;
2623 }
2624
2625 // Only attempt read if indices are actually owned (relevant for multi-rank)
2626 if (k_int >= info_check.zs && k_int < info_check.zs + info_check.zm &&
2627 j_int >= info_check.ys && j_int < info_check.ys + info_check.ym &&
2628 i_int >= info_check.xs && i_int < info_check.xs + info_check.xm)
2629 {
2630 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Attempting test read OWNED INTERIOR [%d][%d][%d] (Global)\n", rank_test, k_int, j_int, i_int);
2631 Cmpnts test_val_owned_interior = lUcat_arr_test[k_int][j_int][i_int];
2632 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: SUCCESS reading owned interior: x=%g\n", rank_test, test_val_owned_interior.x);
2633 } else {
2634 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Skipping interior test read for non-owned index [%d][%d][%d].\n", rank_test, k_int, j_int, i_int);
2635 }
2636
2637
2638 // Check owned boundary point (e.g., first owned point)
2639 PetscInt k_bnd = info_check.zs; // Global k index
2640 PetscInt j_bnd = info_check.ys; // Global j index
2641 PetscInt i_bnd = info_check.xs; // Global i index
2642 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Attempting test read OWNED BOUNDARY [%d][%d][%d] (Global)\n", rank_test, k_bnd, j_bnd, i_bnd);
2643 Cmpnts test_val_owned_boundary = lUcat_arr_test[k_bnd][j_bnd][i_bnd];
2644 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: SUCCESS reading owned boundary: x=%g\n", rank_test, test_val_owned_boundary.x);
2645
2646
2647 // Check ghost point (e.g., one layer below in k, if applicable)
2648 if (info_check.zs > 0) { // Only if there's a rank below
2649 PetscInt k_ghost = info_check.zs - 1;
2650 PetscInt j_ghost = info_check.ys; // Use start of owned y, simple example
2651 PetscInt i_ghost = info_check.xs; // Use start of owned x, simple example
2652 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Attempting test read GHOST [%d][%d][%d] (Global)\n", rank_test, k_ghost, j_ghost, i_ghost);
2653 Cmpnts test_val_ghost = lUcat_arr_test[k_ghost][j_ghost][i_ghost];
2654 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: SUCCESS reading ghost: x=%g\n", rank_test, test_val_ghost.x);
2655 } else {
2656 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Skipping ghost test read (zs=0).\n", rank_test);
2657 }
2658
2659 // Restore the array
2660 ierr_test = DMDAVecRestoreArrayDOFRead(dm, localVec, &lUcat_arr_test);
2661 if(ierr_test){ LOG_ALLOW(LOCAL, LOG_ERROR, "Rank %d: ERROR %d restoring '%s' array after test read!\n", rank_test, ierr_test, field_name); }
2662 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Finished testing '%s' access.\n", rank_test, field_name);
2663 }
2664 } // end if Ucat
2665 } // end debug logging check
2666
2667 LOG_ALLOW(GLOBAL, LOG_INFO, "Rank %d: Completed ghost update for field '%s'.\n", rank, field_name);
2669 PetscFunctionReturn(0);
2670}
2671
2672#undef __FUNCT__
2673#define __FUNCT__ "SetupBoundaryConditions"
2674/**
2675 * @brief Internal helper implementation: `SetupBoundaryConditions()`.
2676 * @details Local to this translation unit.
2677 */
2678PetscErrorCode SetupBoundaryConditions(SimCtx *simCtx)
2679{
2680 PetscErrorCode ierr;
2681 PetscFunctionBeginUser;
2682
2684
2685 LOG_ALLOW(GLOBAL,LOG_INFO, "--- Setting up Boundary Conditions ---\n");
2686 // --- Phase 1: Parse and initialize BC configuration for all blocks ---
2687 LOG_ALLOW(GLOBAL,LOG_INFO,"Parsing BC configuration files and initializing boundary condition data structures.\n");
2688 UserCtx *user_finest = simCtx->usermg.mgctx[simCtx->usermg.mglevels-1].user;
2689 for (PetscInt bi = 0; bi < simCtx->block_number; bi++) {
2690 LOG_ALLOW(GLOBAL,LOG_DEBUG, " -> Processing Block %d:\n", bi);
2691
2692 // --- Generate the filename for the current block ---
2693 const char *current_bc_filename = simCtx->bcs_files[bi];
2694 LOG_ALLOW(GLOBAL,LOG_DEBUG," -> Processing Block %d using config file '%s'\n", bi, current_bc_filename);
2695 // This will populate user_finest[bi].boundary_faces
2696
2697 //ierr = ParseAllBoundaryConditions(&user_finest[bi],current_bc_filename); CHKERRQ(ierr);
2698
2699 ierr = BoundarySystem_Initialize(&user_finest[bi], current_bc_filename); CHKERRQ(ierr);
2700 }
2701
2702 // Propogate BC Configuration to coarser levels.
2703 ierr = PropagateBoundaryConfigToCoarserLevels(simCtx); CHKERRQ(ierr);
2704
2705 // Validate the geometric contract before any metric consumes periodic geometry.
2706 for (PetscInt level = simCtx->usermg.mglevels - 1; level >= 0; level--) {
2707 UserCtx *level_users = simCtx->usermg.mgctx[level].user;
2708 for (PetscInt bi = 0; bi < simCtx->block_number; bi++) {
2709 ierr = ValidatePeriodicGeometry(&level_users[bi]); CHKERRQ(ierr);
2710 }
2711 }
2712
2713 // --- Calculate Grid Metrics (requires BC configuration) ---
2714 // NOTE: This MUST be called here (after BC initialization but before inlet/outlet calculations) because:
2715 // 1. Periodic BC corrections in metric calculations need boundary_faces data to be populated
2716 // 2. Inlet/Outlet area calculations (below) require computed metrics (Csi, Eta, Zet) to be available
2717 // Previously this was in SetupGridAndSolvers, but that caused metrics to be computed without BC info.
2718 LOG_ALLOW(GLOBAL,LOG_INFO,"Computing grid metrics with boundary condition information.\n");
2719 ierr = CalculateAllGridMetrics(simCtx); CHKERRQ(ierr);
2720
2721 // --- Phase 2: Calculate inlet/outlet properties (requires computed metrics) ---
2722 LOG_ALLOW(GLOBAL,LOG_INFO,"Calculating inlet and outlet face properties.\n");
2723 for (PetscInt bi = 0; bi < simCtx->block_number; bi++) {
2724 // Call the function to calculate the center of the inlet face & the inlet area, which may be used to calculate Boundary values.
2725 ierr = CalculateInletProperties(&user_finest[bi]); CHKERRQ(ierr);
2726
2727 // Call the function to calculate the center of the outlet face & the outlet area, which may be used to calculate Boundary values.
2728 ierr = CalculateOutletProperties(&user_finest[bi]); CHKERRQ(ierr);
2729 }
2730
2731 LOG_ALLOW(GLOBAL,LOG_INFO, "--- Boundary Conditions setup complete ---\n");
2732
2733
2735 PetscFunctionReturn(0);
2736}
2737
2738/**
2739 * @brief Internal helper implementation: `Allocate3DArrayScalar()`.
2740 * @details Local to this translation unit.
2741 */
2742PetscErrorCode Allocate3DArrayScalar(PetscReal ****array, PetscInt nz, PetscInt ny, PetscInt nx)
2743{
2744 PetscErrorCode ierr;
2745 PetscReal ***data;
2746 PetscReal *dataContiguous;
2747 PetscInt k, j;
2748
2749 PetscFunctionBegin;
2750 /* Step 1: Allocate memory for an array of nz layer pointers (zero-initialized) */
2751 ierr = PetscCalloc1(nz, &data); CHKERRQ(ierr);
2752
2753 /* Step 2: Allocate memory for all row pointers (nz * ny pointers) */
2754 ierr = PetscCalloc1(nz * ny, &data[0]); CHKERRQ(ierr);
2755 for (k = 1; k < nz; k++) {
2756 data[k] = data[0] + k * ny;
2757 }
2758
2759 /* Step 3: Allocate one contiguous block for all data elements (nz*ny*nx) */
2760 ierr = PetscCalloc1(nz * ny * nx, &dataContiguous); CHKERRQ(ierr);
2761
2762 /* Build the 3D pointer structure: each row pointer gets the correct segment of data */
2763 for (k = 0; k < nz; k++) {
2764 for (j = 0; j < ny; j++) {
2765 data[k][j] = dataContiguous + (k * ny + j) * nx;
2766 /* Memory is already zeroed by PetscCalloc1, so no manual initialization is needed */
2767 }
2768 }
2769 *array = data;
2770 PetscFunctionReturn(0);
2771}
2772
2773/**
2774 * @brief Internal helper implementation: `Deallocate3DArrayScalar()`.
2775 * @details Local to this translation unit.
2776 */
2777PetscErrorCode Deallocate3DArrayScalar(PetscReal ***array, PetscInt nz, PetscInt ny)
2778{
2779 PetscErrorCode ierr;
2780 (void)nz;
2781 (void)ny;
2782
2783 PetscFunctionBegin;
2784 if (!array || !array[0] || !array[0][0] ) { // Added more robust check
2785 LOG_ALLOW(GLOBAL, LOG_WARNING, "Deallocate3DArrayScalar called with potentially unallocated or NULL array.\n");
2786 if (array) {
2787 if (array[0]) { // Check if row pointers might exist
2788 // Cannot safely access array[0][0] if array[0] might be invalid/freed
2789 // Standard deallocation below assumes valid pointers.
2790 ierr = PetscFree(array[0]); CHKERRQ(ierr); // Free row pointers if they exist
2791 }
2792 ierr = PetscFree(array); CHKERRQ(ierr); // Free layer pointers if they exist
2793 }
2794 PetscFunctionReturn(0);
2795 }
2796
2797 // --- Standard Deallocation (assuming valid allocation) ---
2798
2799 /* 1. Free the contiguous block of PetscReal values.
2800 The starting address was stored in array[0][0]. */
2801 ierr = PetscFree(array[0][0]); CHKERRQ(ierr); // Free the ACTUAL DATA
2802
2803 /* 2. Free the contiguous block of row pointers.
2804 The starting address was stored in array[0]. */
2805 ierr = PetscFree(array[0]); CHKERRQ(ierr); // Free the ROW POINTERS
2806
2807 /* 3. Free the layer pointer array.
2808 The starting address is 'array' itself. */
2809 ierr = PetscFree(array); CHKERRQ(ierr); // Free the LAYER POINTERS
2810
2811 PetscFunctionReturn(0);
2812}
2813
2814/**
2815 * @brief Implementation of \ref Allocate3DArrayVector().
2816 * @details Full API contract (arguments, ownership, side effects) is documented with
2817 * the header declaration in `include/setup.h`.
2818 * @see Allocate3DArrayVector()
2819 */
2820PetscErrorCode Allocate3DArrayVector(Cmpnts ****array, PetscInt nz, PetscInt ny, PetscInt nx)
2821{
2822 PetscErrorCode ierr;
2823 Cmpnts ***data;
2824 Cmpnts *dataContiguous;
2825 PetscInt k, j;
2826 PetscMPIInt rank;
2827
2828 PetscFunctionBegin;
2829
2830 ierr = MPI_Comm_rank(PETSC_COMM_WORLD,&rank);
2831
2832 /* Step 1: Allocate memory for nz layer pointers (zeroed) */
2833 ierr = PetscCalloc1(nz, &data); CHKERRQ(ierr);
2834
2835 LOG_ALLOW(LOCAL,LOG_DEBUG," [Rank %d] memory allocated for outermost layer (%d k-layer pointers).\n",rank,nz);
2836
2837 /* Step 2: Allocate memory for all row pointers (nz * ny pointers) */
2838 ierr = PetscCalloc1(nz * ny, &data[0]); CHKERRQ(ierr);
2839 for (k = 1; k < nz; k++) {
2840 data[k] = data[0] + k * ny;
2841 }
2842
2843 LOG_ALLOW(LOCAL,LOG_DEBUG,"[Rank %d] memory allocated for %dx%d row pointers.\n",rank,nz,ny);
2844
2845 /* Step 3: Allocate one contiguous block for nz*ny*nx Cmpnts structures (zeroed) */
2846 ierr = PetscCalloc1(nz * ny * nx, &dataContiguous); CHKERRQ(ierr);
2847
2848 LOG_ALLOW(GLOBAL,LOG_DEBUG,"[Rank %d] memory allocated for contigous block of %dx%dx%d Cmpnts structures).\n",rank,nz,ny,nx);
2849
2850 /* Build the 3D pointer structure for vector data */
2851 for (k = 0; k < nz; k++) {
2852 for (j = 0; j < ny; j++) {
2853 data[k][j] = dataContiguous + (k * ny + j) * nx;
2854 /* The PetscCalloc1 call has already initialized each Cmpnts to zero. */
2855 }
2856 }
2857
2858 LOG_ALLOW(GLOBAL,LOG_DEBUG,"[Rank %d] 3D pointer structure for vector data created. \n",rank);
2859
2860 *array = data;
2861 PetscFunctionReturn(0);
2862}
2863
2864/**
2865 * @brief Implementation of \ref Deallocate3DArrayVector().
2866 * @details Full API contract (arguments, ownership, side effects) is documented with
2867 * the header declaration in `include/setup.h`.
2868 * @see Deallocate3DArrayVector()
2869 */
2870 PetscErrorCode Deallocate3DArrayVector(Cmpnts ***array, PetscInt nz, PetscInt ny)
2871{
2872 PetscErrorCode ierr;
2873 (void)nz;
2874 (void)ny;
2875
2876 PetscFunctionBegin;
2877 // If array is NULL or hasn't been allocated properly, just return.
2878 if (!array || !array[0] || !array[0][0] ) {
2879 LOG_ALLOW(GLOBAL, LOG_WARNING, "Deallocate3DArrayVector called with potentially unallocated or NULL array.\n");
2880 // Attempt to free what might exist, but be cautious
2881 if (array) {
2882 if (array[0]) { // Check if row pointers were allocated
2883 // We don't have a direct pointer to the contiguous data block
2884 // saved separately in this allocation scheme. The allocation relies
2885 // on array[0][0] pointing to it. If array[0] was freed first,
2886 // accessing array[0][0] is unsafe.
2887 // The allocation scheme where the contiguous data block is not
2888 // stored separately makes safe deallocation tricky if freeing
2889 // happens out of order or if parts are NULL.
2890
2891 // A SAFER ALLOCATION/DEALLOCATION would store the data pointer separately.
2892 // Given the current allocation scheme, the order MUST be:
2893 // 1. Free the data block (pointed to by array[0][0])
2894 // 2. Free the row pointer block (pointed to by array[0])
2895 // 3. Free the layer pointer block (pointed to by array)
2896
2897 // Let's assume the allocation was successful and pointers are valid.
2898 // Get pointer to the contiguous data block *before* freeing row pointers
2899 Cmpnts *dataContiguous = array[0][0];
2900 ierr = PetscFree(dataContiguous); CHKERRQ(ierr); // Free data block
2901
2902 // Now free the row pointers block
2903 ierr = PetscFree(array[0]); CHKERRQ(ierr); // Free row pointers
2904
2905 }
2906 // Finally, free the array of layer pointers
2907 ierr = PetscFree(array); CHKERRQ(ierr);
2908 }
2909 PetscFunctionReturn(0); // Return gracefully if input was NULL initially
2910 }
2911
2912
2913 // --- Standard Deallocation (assuming valid allocation) ---
2914
2915 /* 1. Free the contiguous block of Cmpnts structures.
2916 The starting address was stored in array[0][0] by Allocate3DArrayVector. */
2917 ierr = PetscFree(array[0][0]); CHKERRQ(ierr); // Free the ACTUAL DATA
2918
2919 /* 2. Free the contiguous block of row pointers.
2920 The starting address was stored in array[0]. */
2921 ierr = PetscFree(array[0]); CHKERRQ(ierr); // Free the ROW POINTERS
2922
2923 /* 3. Free the layer pointer array.
2924 The starting address is 'array' itself. */
2925 ierr = PetscFree(array); CHKERRQ(ierr); // Free the LAYER POINTERS
2926
2927 PetscFunctionReturn(0);
2928}
2929
2930#undef __FUNCT__
2931#define __FUNCT__ "GetOwnedCellRange"
2932/**
2933 * @brief Internal helper implementation: `GetOwnedCellRange()`.
2934 * @details Local to this translation unit.
2935 */
2936PetscErrorCode GetOwnedCellRange(const DMDALocalInfo *info_nodes,
2937 PetscInt dim,
2938 PetscInt *xs_cell_global_out,
2939 PetscInt *xm_cell_local_out)
2940{
2941 PetscErrorCode ierr = 0; // Standard PETSc error code, not explicitly set here but good practice.
2942 PetscInt xs_node_global_rank; // Global index of the first node owned by this rank in the specified dimension.
2943 PetscInt num_nodes_owned_rank; // Number of nodes owned by this rank in this dimension (local count, excluding ghosts).
2944 PetscInt GlobalNodesInDim_from_info; // Total number of DA points in this dimension, from DMDALocalInfo.
2945
2946 PetscFunctionBeginUser;
2947
2948 // --- 1. Input Validation ---
2949 if (!info_nodes || !xs_cell_global_out || !xm_cell_local_out) {
2950 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Null pointer passed to GetOwnedCellRange.");
2951 }
2952
2953 // --- 2. Extract Node Ownership and Global Dimension Information from DMDALocalInfo ---
2954 if (dim == 0) { // I-direction
2955 xs_node_global_rank = info_nodes->xs;
2956 num_nodes_owned_rank = info_nodes->xm;
2957 GlobalNodesInDim_from_info = info_nodes->mx;
2958 } else if (dim == 1) { // J-direction
2959 xs_node_global_rank = info_nodes->ys;
2960 num_nodes_owned_rank = info_nodes->ym;
2961 GlobalNodesInDim_from_info = info_nodes->my;
2962 } else if (dim == 2) { // K-direction
2963 xs_node_global_rank = info_nodes->zs;
2964 num_nodes_owned_rank = info_nodes->zm;
2965 GlobalNodesInDim_from_info = info_nodes->mz;
2966 } else {
2967 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Invalid dimension %d in GetOwnedCellRange. Must be 0, 1, or 2.", dim);
2968 }
2969
2970 // --- 3. Correct for User-Defined Ghost Node ---
2971 // Per the function's contract (@warning), the DA size includes an extra, non-physical
2972 // node. We subtract 1 to get the true number of physical nodes for cell calculations.
2973 const PetscInt physical_nodes_in_dim = GlobalNodesInDim_from_info - 1;
2974
2975 // --- 4. Handle Edge Cases for Physical Domain Size ---
2976 // If the physical domain has 0 or 1 node, no cells can be formed.
2977 if (physical_nodes_in_dim <= 1) {
2978 *xs_cell_global_out = xs_node_global_rank; // Still report the rank's starting node
2979 *xm_cell_local_out = 0; // But 0 cells
2980 PetscFunctionReturn(0);
2981 }
2982
2983 // --- 5. Determine Cell Ownership Based on Corrected Node Ownership ---
2984 // The first cell this rank *could* define has its origin at the first node this rank owns.
2985 *xs_cell_global_out = xs_node_global_rank;
2986
2987 // If the rank owns no nodes in this dimension, it can't form any cell origins.
2988 if (num_nodes_owned_rank == 0) {
2989 *xm_cell_local_out = 0;
2990 } else {
2991 // --- BUG FIX APPLIED HERE ---
2992 // The previous logic incorrectly assumed a cell's end node (N_{k+1}) must be on the
2993 // same rank as its origin node (N_k). The correct logic is to find the intersection
2994 // between the nodes this rank owns and the nodes that are valid origins globally.
2995
2996 // The first node owned by the rank is its first potential origin.
2997 PetscInt first_owned_origin = xs_node_global_rank;
2998
2999 // The absolute last node owned by this rank. Any node up to and including this one
3000 // is a potential cell origin from this rank's perspective.
3001 PetscInt last_node_owned_by_rank = xs_node_global_rank + num_nodes_owned_rank - 1;
3002
3003 // The absolute last node in the entire PHYSICAL domain that can serve as a cell origin.
3004 // If there are `N` physical nodes (0 to N-1), this index is `N-2`.
3005 PetscInt last_possible_origin_global_idx = physical_nodes_in_dim - 2;
3006
3007 // The actual last origin this rank can provide is the *minimum* of what it owns
3008 // and what is globally possible. This correctly handles both ranks in the middle of
3009 // the domain and the very last rank.
3010 PetscInt actual_last_origin_this_rank_can_form = PetscMin(last_node_owned_by_rank, last_possible_origin_global_idx);
3011
3012 // If the first potential origin this rank owns is already beyond the actual last
3013 // origin it can form, then this rank forms no valid cell origins. This happens if
3014 // the rank only owns the very last physical node.
3015 if (first_owned_origin > actual_last_origin_this_rank_can_form) {
3016 *xm_cell_local_out = 0;
3017 } else {
3018 // The number of cells is the count of valid origins this rank owns.
3019 // (Count = Last Index - First Index + 1)
3020 *xm_cell_local_out = actual_last_origin_this_rank_can_form - first_owned_origin + 1;
3021 }
3022 }
3023
3024 PetscFunctionReturn(ierr);
3025}
3026
3027#undef __FUNCT__
3028#define __FUNCT__ "ComputeAndStoreNeighborRanks"
3029/**
3030 * @brief Internal helper implementation: `ComputeAndStoreNeighborRanks()`.
3031 * @details Local to this translation unit.
3032 */
3034{
3035 PetscErrorCode ierr;
3036 PetscMPIInt rank;
3037 PetscMPIInt size; // MPI communicator size
3038 const PetscMPIInt *neighbor_ranks_ptr; // Pointer to raw neighbor data from PETSc
3039
3040 PetscFunctionBeginUser;
3042 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
3043 ierr = MPI_Comm_size(PETSC_COMM_WORLD, &size); CHKERRQ(ierr); // Get MPI size for validation
3044
3045 LOG_ALLOW(GLOBAL, LOG_INFO, "Rank %d: Computing DMDA neighbor ranks.\n", rank);
3046
3047 if (!user || !user->da) {
3048 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx or user->da is NULL in ComputeAndStoreNeighborRanks.");
3049 }
3050
3051 // Get the neighbor information from the DMDA
3052 // neighbor_ranks_ptr will point to an internal PETSc array of 27 ranks.
3053 ierr = DMDAGetNeighbors(user->da, &neighbor_ranks_ptr); CHKERRQ(ierr);
3054
3055 // Log the raw values from DMDAGetNeighbors for boundary-relevant directions for debugging
3056 LOG_ALLOW_SYNC(GLOBAL, LOG_DEBUG, "[Rank %d]Raw DMDAGetNeighbors: xm_raw=%d, xp_raw=%d, ym_raw=%d, yp_raw=%d, zm_raw=%d, zp_raw=%d. MPI_PROC_NULL is %d.\n",
3057 rank,
3058 neighbor_ranks_ptr[12], neighbor_ranks_ptr[14],
3059 neighbor_ranks_ptr[10], neighbor_ranks_ptr[16],
3060 neighbor_ranks_ptr[4], neighbor_ranks_ptr[22],
3061 (int)MPI_PROC_NULL);
3062
3063 // PETSc standard indices for 3D face neighbors from the 27-point stencil:
3064 // Index = k_offset*9 + j_offset*3 + i_offset (where offsets -1,0,1 map to 0,1,2)
3065 // Center: (i_off=1, j_off=1, k_off=1) => 1*9 + 1*3 + 1 = 13
3066 // X-min: (i_off=0, j_off=1, k_off=1) => 1*9 + 1*3 + 0 = 12
3067 // X-plus: (i_off=2, j_off=1, k_off=1) => 1*9 + 1*3 + 2 = 14
3068 // Y-min: (i_off=1, j_off=0, k_off=1) => 1*9 + 0*3 + 1 = 10
3069 // Y-plus: (i_off=1, j_off=2, k_off=1) => 1*9 + 2*3 + 1 = 16
3070 // Z-min: (i_off=1, j_off=1, k_off=0) => 0*9 + 1*3 + 1 = 4
3071 // Z-plus: (i_off=1, j_off=1, k_off=2) => 2*9 + 1*3 + 1 = 22
3072
3073 if (neighbor_ranks_ptr[13] != rank) {
3074 LOG_ALLOW(GLOBAL, LOG_WARNING, "Rank %d: DMDAGetNeighbors center index (13) is %d, expected current rank %d. Neighbor indexing might be non-standard or DMDA small.\n",
3075 rank, neighbor_ranks_ptr[13], rank);
3076 // This warning is important. If the center isn't the current rank, the offsets are likely wrong.
3077 // However, PETSc should ensure this unless the DM is too small for a 3x3x3 stencil.
3078 }
3079
3080 // Assign and sanitize each neighbor rank
3081 PetscMPIInt temp_neighbor;
3082
3083 temp_neighbor = neighbor_ranks_ptr[12]; // xm
3084 if (temp_neighbor < 0 || temp_neighbor >= size) {
3085 LOG_ALLOW(GLOBAL, LOG_WARNING, "[Rank %d] Correcting invalid xm neighbor %d to MPI_PROC_NULL (%d).\n", rank, temp_neighbor, (int)MPI_PROC_NULL);
3086 user->neighbors.rank_xm = MPI_PROC_NULL;
3087 } else {
3088 user->neighbors.rank_xm = temp_neighbor;
3089 }
3090
3091 temp_neighbor = neighbor_ranks_ptr[14]; // xp
3092 if (temp_neighbor < 0 || temp_neighbor >= size) {
3093 LOG_ALLOW(GLOBAL, LOG_WARNING, "[Rank %d] Correcting invalid xp neighbor %d to MPI_PROC_NULL (%d).\n", rank, temp_neighbor, (int)MPI_PROC_NULL);
3094 user->neighbors.rank_xp = MPI_PROC_NULL;
3095 } else {
3096 user->neighbors.rank_xp = temp_neighbor;
3097 }
3098
3099 temp_neighbor = neighbor_ranks_ptr[10]; // ym
3100 if (temp_neighbor < 0 || temp_neighbor >= size) {
3101 LOG_ALLOW(GLOBAL, LOG_WARNING, "[Rank %d] Correcting invalid ym neighbor %d to MPI_PROC_NULL (%d).\n", rank, temp_neighbor, (int)MPI_PROC_NULL);
3102 user->neighbors.rank_ym = MPI_PROC_NULL;
3103 } else {
3104 user->neighbors.rank_ym = temp_neighbor;
3105 }
3106
3107 temp_neighbor = neighbor_ranks_ptr[16]; // yp
3108 if (temp_neighbor < 0 || temp_neighbor >= size) {
3109 // The log for index 16 was "zm" in your output, should be yp
3110 LOG_ALLOW(GLOBAL, LOG_WARNING, "[Rank %d] Correcting invalid yp neighbor (raw index 16) %d to MPI_PROC_NULL (%d).\n", rank, temp_neighbor, (int)MPI_PROC_NULL);
3111 user->neighbors.rank_yp = MPI_PROC_NULL;
3112 } else {
3113 user->neighbors.rank_yp = temp_neighbor;
3114 }
3115
3116 temp_neighbor = neighbor_ranks_ptr[4]; // zm
3117 if (temp_neighbor < 0 || temp_neighbor >= size) {
3118 LOG_ALLOW(GLOBAL, LOG_WARNING, "[Rank %d] Correcting invalid zm neighbor %d to MPI_PROC_NULL (%d).\n", rank, temp_neighbor, (int)MPI_PROC_NULL);
3119 user->neighbors.rank_zm = MPI_PROC_NULL;
3120 } else {
3121 user->neighbors.rank_zm = temp_neighbor;
3122 }
3123
3124 temp_neighbor = neighbor_ranks_ptr[22]; // zp
3125 if (temp_neighbor < 0 || temp_neighbor >= size) {
3126 LOG_ALLOW(GLOBAL, LOG_WARNING, "[Rank %d] Correcting invalid zp neighbor %d to MPI_PROC_NULL (%d).\n", rank, temp_neighbor, (int)MPI_PROC_NULL);
3127 user->neighbors.rank_zp = MPI_PROC_NULL;
3128 } else {
3129 user->neighbors.rank_zp = temp_neighbor;
3130 }
3131
3132 LOG_ALLOW_SYNC(GLOBAL, LOG_DEBUG, "[Rank %d] Stored user->neighbors: xm=%d, xp=%d, ym=%d, yp=%d, zm=%d, zp=%d\n", rank,
3133 user->neighbors.rank_xm, user->neighbors.rank_xp,
3134 user->neighbors.rank_ym, user->neighbors.rank_yp,
3135 user->neighbors.rank_zm, user->neighbors.rank_zp);
3136 PetscSynchronizedFlush(PETSC_COMM_WORLD, PETSC_STDOUT); // Ensure logs are flushed
3137
3138 // Note: neighbor_ranks_ptr memory is managed by PETSc, do not free it.
3140 PetscFunctionReturn(0);
3141}
3142
3143#undef __FUNCT__
3144#define __FUNCT__ "SetDMDAProcLayout"
3145/**
3146 * @brief Internal helper implementation: `SetDMDAProcLayout()`.
3147 * @details Local to this translation unit.
3148 */
3149PetscErrorCode SetDMDAProcLayout(DM dm, UserCtx *user)
3150{
3151 PetscErrorCode ierr;
3152 PetscMPIInt size, rank;
3153 PetscInt px = PETSC_DECIDE, py = PETSC_DECIDE, pz = PETSC_DECIDE;
3154 PetscBool px_set = PETSC_FALSE, py_set = PETSC_FALSE, pz_set = PETSC_FALSE;
3155 SimCtx *simCtx = user->simCtx;
3156
3157 // Set no.of processors in direction 1
3158 if(simCtx->da_procs_x) {
3159 px_set = PETSC_TRUE;
3160 px = simCtx->da_procs_x;
3161 }
3162 // Set no.of processors in direction 2
3163 if(simCtx->da_procs_y) {
3164 py_set = PETSC_TRUE;
3165 py = simCtx->da_procs_y;
3166 }
3167 // Set no.of processors in direction 1
3168 if(simCtx->da_procs_z) {
3169 pz_set = PETSC_TRUE;
3170 pz = simCtx->da_procs_z;
3171 }
3172
3173 PetscFunctionBeginUser;
3175 ierr = MPI_Comm_size(PetscObjectComm((PetscObject)dm), &size); CHKERRQ(ierr);
3176 ierr = MPI_Comm_rank(PetscObjectComm((PetscObject)dm), &rank); CHKERRQ(ierr);
3177 LOG_ALLOW(GLOBAL, LOG_INFO, "Rank %d: Configuring DMDA processor layout for %d total processes.\n", rank, size);
3178
3179 // --- Validate User Input (Optional but Recommended) ---
3180 // Check if specified processor counts multiply to the total MPI size
3181 if (px_set && py_set && pz_set) {
3182 if (px * py * pz != size) {
3183 SETERRQ(PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_INCOMP,
3184 "Specified processor layout %d x %d x %d = %d does not match MPI size %d",
3185 px, py, pz, px * py * pz, size);
3186 }
3187 LOG_ALLOW(GLOBAL, LOG_INFO, "Using specified processor layout: %d x %d x %d\n", px, py, pz);
3188 } else if (px_set || py_set || pz_set) {
3189 // If only some are set, PETSC_DECIDE will be used for others
3190 LOG_ALLOW(GLOBAL, LOG_INFO, "Using partially specified processor layout: %d x %d x %d (PETSC_DECIDE for unspecified)\n", px, py, pz);
3191 } else {
3192 LOG_ALLOW(GLOBAL, LOG_INFO, "Using fully automatic processor layout (PETSC_DECIDE x PETSC_DECIDE x PETSC_DECIDE)\n");
3193 }
3194 // Additional checks: Ensure px, py, pz are positive if set
3195 if ((px_set && px <= 0) || (py_set && py <= 0) || (pz_set && pz <= 0)) {
3196 SETERRQ(PetscObjectComm((PetscObject)dm), PETSC_ERR_ARG_OUTOFRANGE, "Specified processor counts must be positive.");
3197 }
3198
3199
3200 // --- Apply the layout to the DMDA ---
3201 ierr = DMDASetNumProcs(dm, px, py, pz); CHKERRQ(ierr);
3202 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Rank %d: DMDASetNumProcs called with px=%d, py=%d, pz=%d.\n", rank, px, py, pz);
3203
3204 // --- Store the values in UserCtx (Optional) ---
3205 // Note: If PETSC_DECIDE was used, PETSc calculates the actual values during DMSetUp.
3206 // We store the *requested* values here. To get the *actual* values used,
3207 // you would need to call DMDAGetInfo after DMSetUp.
3208 /*
3209 if (user) {
3210 user->procs_x = px;
3211 user->procs_y = py;
3212 user->procs_z = pz;
3213 }
3214 */
3216 PetscFunctionReturn(0);
3217}
3218
3219#undef __FUNCT__
3220#define __FUNCT__ "SetupDomainRankInfo"
3221/**
3222 * @brief Implementation of \ref SetupDomainRankInfo().
3223 * @details Full API contract (arguments, ownership, side effects) is documented with
3224 * the header declaration in `include/setup.h`.
3225 * @see SetupDomainRankInfo()
3226 */
3227PetscErrorCode SetupDomainRankInfo(SimCtx *simCtx)
3228{
3229 PetscErrorCode ierr;
3230 PetscInt nblk = simCtx->block_number;
3231 PetscInt size = simCtx->size;
3232 BoundingBox *final_bboxlist = NULL;
3233
3234 PetscFunctionBeginUser;
3236
3237 LOG_ALLOW(GLOBAL, LOG_INFO, "Starting full rank communication setup for %d block(s).\n", nblk);
3238
3239 UserCtx *user_finest = simCtx->usermg.mgctx[simCtx->usermg.mglevels - 1].user;
3240
3241 // --- Step 1: Compute neighbor ranks (unchanged) ---
3242 for (int bi = 0; bi < nblk; bi++) {
3243 ierr = ComputeAndStoreNeighborRanks(&user_finest[bi]); CHKERRQ(ierr);
3244 }
3245 LOG_ALLOW(GLOBAL, LOG_INFO, "Neighbor ranks computed and stored for all blocks.\n");
3246
3247 // --- Step 2: Allocate the final, unified list on ALL ranks ---
3248 // Every rank will build this list in parallel.
3249 ierr = PetscMalloc1(size * nblk, &final_bboxlist); CHKERRQ(ierr);
3250
3251 // --- Step 3: Loop through each block, gather then broadcast its bbox list ---
3252 for (int bi = 0; bi < nblk; bi++) {
3253 // This is a temporary pointer for the current block's list.
3254 BoundingBox *block_bboxlist = NULL;
3255
3256 LOG_ALLOW(GLOBAL, LOG_INFO, "Processing bounding boxes for block %d...\n", bi);
3257
3258 // A) GATHER: On rank 0, block_bboxlist is allocated and filled. On others, it's NULL.
3259 ierr = GatherAllBoundingBoxes(&user_finest[bi], &block_bboxlist); CHKERRQ(ierr);
3260 LOG_ALLOW(GLOBAL, LOG_DEBUG, " -> Gather complete for block %d.\n", bi);
3261
3262 // B) BROADCAST: On non-root ranks, block_bboxlist is allocated. Then, the data
3263 // from rank 0 is broadcast to all ranks. After this call, ALL ranks have
3264 // an identical, complete copy of the bounding boxes for the current block.
3265 ierr = BroadcastAllBoundingBoxes(&user_finest[bi], &block_bboxlist); CHKERRQ(ierr);
3266 LOG_ALLOW(GLOBAL, LOG_DEBUG, " -> Broadcast complete for block %d.\n", bi);
3267
3268 // C) ASSEMBLE: Every rank now copies the data for this block into the
3269 // correct segment of its final, unified list.
3270 for (int r = 0; r < size; r++) {
3271 // The layout is [r0b0, r1b0, ..., r(size-1)b0, r0b1, r1b1, ...]
3272 final_bboxlist[bi * size + r] = block_bboxlist[r];
3273 }
3274 LOG_ALLOW(GLOBAL, LOG_DEBUG, " -> Assembly into final list complete for block %d.\n", bi);
3275
3276 // D) CLEANUP: Free the temporary list for this block on ALL ranks before the next iteration.
3277 // Your helper functions use malloc, so we must use free.
3278 free(block_bboxlist);
3279 }
3280
3281 // --- Step 4: Assign the final pointer and run the last setup step ---
3282 simCtx->bboxlist = final_bboxlist;
3283 LOG_ALLOW(GLOBAL, LOG_INFO, "Final unified bboxlist created on all ranks and stored in SimCtx.\n");
3284
3285 ierr = SetupDomainCellDecompositionMap(&user_finest[0]); CHKERRQ(ierr);
3286 LOG_ALLOW(GLOBAL, LOG_INFO, "Domain Cell Composition set and broadcasted.\n");
3287
3288 LOG_ALLOW(GLOBAL, LOG_INFO, "SetupDomainRankInfo: Completed successfully.\n");
3289
3291 PetscFunctionReturn(0);
3292}
3293
3294#undef __FUNCT__
3295#define __FUNCT__ "Contra2Cart"
3296/**
3297 * @brief Internal helper implementation: `Contra2Cart()`.
3298 * @details Local to this translation unit.
3299 */
3300PetscErrorCode Contra2Cart(UserCtx *user)
3301{
3302 PetscErrorCode ierr;
3303 DMDALocalInfo info;
3304 Cmpnts ***lcsi_arr, ***leta_arr, ***lzet_arr; // Local metric arrays
3305 Cmpnts ***lucont_arr; // Local contravariant velocity array
3306 Cmpnts ***gucat_arr; // Global Cartesian velocity array
3307 PetscReal ***lnvert_arr; // Local Nvert array
3308 PetscReal ***laj_arr; // Local Jacobian Determinant inverse array
3309
3310 PetscFunctionBeginUser;
3312 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Starting Contravariant-to-Cartesian velocity transformation.\n");
3313
3314 // --- 1. Get DMDA Info and Check for Valid Inputs ---
3315 // All inputs (lUcont, lCsi, etc.) and outputs (Ucat) are on DMs from the UserCtx.
3316 // We get local info from fda, which governs the layout of most arrays here.
3317 ierr = DMDAGetLocalInfo(user->fda, &info); CHKERRQ(ierr);
3318 if (!user->lUcont || !user->lCsi || !user->lEta || !user->lZet || !user->lNvert || !user->Ucat) {
3319 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, "Contra2Cart requires lUcont, lCsi/Eta/Zet, lNvert, and Ucat to be non-NULL.");
3320 }
3321
3322
3323 // --- 2. Get Read-Only Array Access to Local Input Vectors (with ghosts) ---
3324 ierr = DMDAVecGetArrayRead(user->fda, user->lUcont, &lucont_arr); CHKERRQ(ierr);
3325 ierr = DMDAVecGetArrayRead(user->fda, user->lCsi, &lcsi_arr); CHKERRQ(ierr);
3326 ierr = DMDAVecGetArrayRead(user->fda, user->lEta, &leta_arr); CHKERRQ(ierr);
3327 ierr = DMDAVecGetArrayRead(user->fda, user->lZet, &lzet_arr); CHKERRQ(ierr);
3328 ierr = DMDAVecGetArrayRead(user->da, user->lNvert, &lnvert_arr); CHKERRQ(ierr);
3329 ierr = DMDAVecGetArrayRead(user->da, user->lAj, &laj_arr); CHKERRQ(ierr);
3330
3331 // --- 3. Get Write-Only Array Access to the Global Output Vector ---
3332 // We compute for local owned cells and write into the global vector.
3333 // PETSc handles mapping the global indices to the correct local memory locations.
3334 ierr = DMDAVecGetArray(user->fda, user->Ucat, &gucat_arr); CHKERRQ(ierr);
3335
3336
3337 // --- 4. Define Loop Bounds for INTERIOR Cells ---
3338 // We use adjusted bounds to avoid calculating Ucat on the physical domain boundaries,
3339 // as these are typically set explicitly by boundary condition functions.
3340 // The stencils use indices like i-1, j-1, k-1, so we must start loops at least at index 1.
3341 PetscInt i_start = (info.xs == 0) ? info.xs + 1 : info.xs;
3342 PetscInt i_end = (info.xs + info.xm == info.mx) ? info.xs + info.xm - 1 : info.xs + info.xm;
3343
3344 PetscInt j_start = (info.ys == 0) ? info.ys + 1 : info.ys;
3345 PetscInt j_end = (info.ys + info.ym == info.my) ? info.ys + info.ym - 1 : info.ys + info.ym;
3346
3347 PetscInt k_start = (info.zs == 0) ? info.zs + 1 : info.zs;
3348 PetscInt k_end = (info.zs + info.zm == info.mz) ? info.zs + info.zm - 1 : info.zs + info.zm;
3349
3350 // --- 5. Main Computation Loop ---
3351 // Loops over the GLOBAL indices of interior cells owned by this rank.
3352 for (PetscInt k_cell = k_start; k_cell < k_end; ++k_cell) {
3353 for (PetscInt j_cell = j_start; j_cell < j_end; ++j_cell) {
3354 for (PetscInt i_cell = i_start; i_cell < i_end; ++i_cell) {
3355
3356 // Check if the cell is a fluid cell (not solid/blanked)
3357 // if (lnvert_arr[k_cell][j_cell][i_cell] > 0.1) continue; // Skip solid/blanked cells
3358
3359 // Transformation matrix [mat] is the metric tensor at the cell center,
3360 // estimated by averaging metrics from adjacent faces.
3361 PetscReal mat[3][3];
3362
3363 // PetscReal aj_center = laj_arr[k_cell+1][j_cell+1][i_cell+1];
3364
3365 mat[0][0] = 0.5 * (lcsi_arr[k_cell][j_cell][i_cell-1].x + lcsi_arr[k_cell][j_cell][i_cell].x); //* aj_center;
3366 mat[0][1] = 0.5 * (lcsi_arr[k_cell][j_cell][i_cell-1].y + lcsi_arr[k_cell][j_cell][i_cell].y); //* aj_center;
3367 mat[0][2] = 0.5 * (lcsi_arr[k_cell][j_cell][i_cell-1].z + lcsi_arr[k_cell][j_cell][i_cell].z); //* aj_center;
3368
3369 mat[1][0] = 0.5 * (leta_arr[k_cell][j_cell-1][i_cell].x + leta_arr[k_cell][j_cell][i_cell].x); //* aj_center;
3370 mat[1][1] = 0.5 * (leta_arr[k_cell][j_cell-1][i_cell].y + leta_arr[k_cell][j_cell][i_cell].y); //* aj_center;
3371 mat[1][2] = 0.5 * (leta_arr[k_cell][j_cell-1][i_cell].z + leta_arr[k_cell][j_cell][i_cell].z); //* aj_center;
3372
3373 mat[2][0] = 0.5 * (lzet_arr[k_cell-1][j_cell][i_cell].x + lzet_arr[k_cell][j_cell][i_cell].x); //* aj_center;
3374 mat[2][1] = 0.5 * (lzet_arr[k_cell-1][j_cell][i_cell].y + lzet_arr[k_cell][j_cell][i_cell].y); //* aj_center;
3375 mat[2][2] = 0.5 * (lzet_arr[k_cell-1][j_cell][i_cell].z + lzet_arr[k_cell][j_cell][i_cell].z); //* aj_center;
3376
3377 // Contravariant velocity vector `q` at the cell center,
3378 // estimated by averaging face-based contravariant velocities.
3379 PetscReal q[3];
3380 q[0] = 0.5 * (lucont_arr[k_cell][j_cell][i_cell-1].x + lucont_arr[k_cell][j_cell][i_cell].x); // U¹ at cell center
3381 q[1] = 0.5 * (lucont_arr[k_cell][j_cell-1][i_cell].y + lucont_arr[k_cell][j_cell][i_cell].y); // U² at cell center
3382 q[2] = 0.5 * (lucont_arr[k_cell-1][j_cell][i_cell].z + lucont_arr[k_cell][j_cell][i_cell].z); // U³ at cell center
3383
3384 // Solve the 3x3 system `mat * ucat = q` using Cramer's rule.
3385 PetscReal det = mat[0][0] * (mat[1][1] * mat[2][2] - mat[1][2] * mat[2][1]) -
3386 mat[0][1] * (mat[1][0] * mat[2][2] - mat[1][2] * mat[2][0]) +
3387 mat[0][2] * (mat[1][0] * mat[2][1] - mat[1][1] * mat[2][0]);
3388
3389 if (PetscAbsReal(det) < 1.0e-18) {
3390 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FLOP_COUNT, "Transformation matrix determinant is near zero at cell (%d,%d,%d) \n", i_cell, j_cell, k_cell);
3391 }
3392
3393 PetscReal det_inv = 1.0 / det;
3394
3395 PetscReal det0 = q[0] * (mat[1][1] * mat[2][2] - mat[1][2] * mat[2][1]) -
3396 q[1] * (mat[0][1] * mat[2][2] - mat[0][2] * mat[2][1]) +
3397 q[2] * (mat[0][1] * mat[1][2] - mat[0][2] * mat[1][1]);
3398
3399 PetscReal det1 = -q[0] * (mat[1][0] * mat[2][2] - mat[1][2] * mat[2][0]) +
3400 q[1] * (mat[0][0] * mat[2][2] - mat[0][2] * mat[2][0]) -
3401 q[2] * (mat[0][0] * mat[1][2] - mat[0][2] * mat[1][0]);
3402
3403 PetscReal det2 = q[0] * (mat[1][0] * mat[2][1] - mat[1][1] * mat[2][0]) -
3404 q[1] * (mat[0][0] * mat[2][1] - mat[0][1] * mat[2][0]) +
3405 q[2] * (mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]);
3406
3407 // Store computed Cartesian velocity in the GLOBAL Ucat array at the
3408 // array index corresponding to the cell's origin node.
3409 gucat_arr[k_cell][j_cell][i_cell].x = det0 * det_inv;
3410 gucat_arr[k_cell][j_cell][i_cell].y = det1 * det_inv;
3411 gucat_arr[k_cell][j_cell][i_cell].z = det2 * det_inv;
3412 }
3413 }
3414 }
3415
3416 // --- 6. Restore Array Access ---
3417 ierr = DMDAVecRestoreArrayRead(user->fda, user->lUcont, &lucont_arr); CHKERRQ(ierr);
3418 ierr = DMDAVecRestoreArrayRead(user->fda, user->lCsi, &lcsi_arr); CHKERRQ(ierr);
3419 ierr = DMDAVecRestoreArrayRead(user->fda, user->lEta, &leta_arr); CHKERRQ(ierr);
3420 ierr = DMDAVecRestoreArrayRead(user->fda, user->lZet, &lzet_arr); CHKERRQ(ierr);
3421 ierr = DMDAVecRestoreArrayRead(user->da, user->lNvert, &lnvert_arr); CHKERRQ(ierr);
3422 ierr = DMDAVecRestoreArrayRead(user->da, user->lAj, &laj_arr); CHKERRQ(ierr);
3423 ierr = DMDAVecRestoreArray(user->fda, user->Ucat, &gucat_arr); CHKERRQ(ierr);
3424
3425 LOG_ALLOW(GLOBAL, LOG_INFO, "Completed Contravariant-to-Cartesian velocity transformation. \n");
3427 PetscFunctionReturn(0);
3428}
3429
3430#undef __FUNCT__
3431#define __FUNCT__ "Cart2Contra"
3432/**
3433 * @brief Convert a spatially varying Cartesian velocity field to contravariant fluxes.
3434 */
3435PetscErrorCode Cart2Contra(UserCtx *user)
3436{
3437 PetscErrorCode ierr;
3438 DMDALocalInfo info;
3439 const Cmpnts ***ucat_arr, ***csi_arr, ***eta_arr, ***zet_arr;
3440 Cmpnts ***ucont_arr;
3441
3442 PetscFunctionBeginUser;
3444
3445 ierr = DMDAGetLocalInfo(user->fda, &info); CHKERRQ(ierr);
3446 ierr = DMDAVecGetArrayRead(user->fda, user->lUcat, &ucat_arr); CHKERRQ(ierr);
3447 ierr = DMDAVecGetArrayRead(user->fda, user->lCsi, &csi_arr); CHKERRQ(ierr);
3448 ierr = DMDAVecGetArrayRead(user->fda, user->lEta, &eta_arr); CHKERRQ(ierr);
3449 ierr = DMDAVecGetArrayRead(user->fda, user->lZet, &zet_arr); CHKERRQ(ierr);
3450 ierr = DMDAVecGetArray(user->fda, user->Ucont, &ucont_arr); CHKERRQ(ierr);
3451
3452 const PetscInt i_start = PetscMax(info.xs, 1);
3453 const PetscInt j_start = PetscMax(info.ys, 1);
3454 const PetscInt k_start = PetscMax(info.zs, 1);
3455 const PetscInt i_end = PetscMin(info.xs + info.xm, info.mx - 1);
3456 const PetscInt j_end = PetscMin(info.ys + info.ym, info.my - 1);
3457 const PetscInt k_end = PetscMin(info.zs + info.zm, info.mz - 1);
3458
3459 for (PetscInt k = k_start; k < k_end; k++) {
3460 for (PetscInt j = j_start; j < j_end; j++) {
3461 for (PetscInt i = i_start; i < i_end; i++) {
3462 const Cmpnts u_xi = {
3463 0.5 * (ucat_arr[k][j][i].x + ucat_arr[k][j][i + 1].x),
3464 0.5 * (ucat_arr[k][j][i].y + ucat_arr[k][j][i + 1].y),
3465 0.5 * (ucat_arr[k][j][i].z + ucat_arr[k][j][i + 1].z)
3466 };
3467 const Cmpnts u_eta = {
3468 0.5 * (ucat_arr[k][j][i].x + ucat_arr[k][j + 1][i].x),
3469 0.5 * (ucat_arr[k][j][i].y + ucat_arr[k][j + 1][i].y),
3470 0.5 * (ucat_arr[k][j][i].z + ucat_arr[k][j + 1][i].z)
3471 };
3472 const Cmpnts u_zeta = {
3473 0.5 * (ucat_arr[k][j][i].x + ucat_arr[k + 1][j][i].x),
3474 0.5 * (ucat_arr[k][j][i].y + ucat_arr[k + 1][j][i].y),
3475 0.5 * (ucat_arr[k][j][i].z + ucat_arr[k + 1][j][i].z)
3476 };
3477 ucont_arr[k][j][i].x = csi_arr[k][j][i].x * u_xi.x + csi_arr[k][j][i].y * u_xi.y + csi_arr[k][j][i].z * u_xi.z;
3478 ucont_arr[k][j][i].y = eta_arr[k][j][i].x * u_eta.x + eta_arr[k][j][i].y * u_eta.y + eta_arr[k][j][i].z * u_eta.z;
3479 ucont_arr[k][j][i].z = zet_arr[k][j][i].x * u_zeta.x + zet_arr[k][j][i].y * u_zeta.y + zet_arr[k][j][i].z * u_zeta.z;
3480 }
3481 }
3482 }
3483
3484 ierr = DMDAVecRestoreArray(user->fda, user->Ucont, &ucont_arr); CHKERRQ(ierr);
3485 ierr = DMDAVecRestoreArrayRead(user->fda, user->lZet, &zet_arr); CHKERRQ(ierr);
3486 ierr = DMDAVecRestoreArrayRead(user->fda, user->lEta, &eta_arr); CHKERRQ(ierr);
3487 ierr = DMDAVecRestoreArrayRead(user->fda, user->lCsi, &csi_arr); CHKERRQ(ierr);
3488 ierr = DMDAVecRestoreArrayRead(user->fda, user->lUcat, &ucat_arr); CHKERRQ(ierr);
3489
3491 PetscFunctionReturn(0);
3492}
3493
3494#undef __FUNCT__
3495#define __FUNCT__ "UniformCart2Contra"
3496/*
3497 * Converts a uniform Cartesian velocity (u,v,w) to contravariant fluxes in Ucont.
3498 * Computes the dot product of the physical velocity with each face-area vector:
3499 * U^xi = csi · (u,v,w), U^eta = eta · (u,v,w), U^zeta = zet · (u,v,w).
3500 * Writes to all owned nodes (xs..xe, ys..ye, zs..ze); boundary ghosts are
3501 * overwritten later by ApplyBoundaryConditions.
3502 * The public header owns the rendered API contract.
3503 */
3504PetscErrorCode UniformCart2Contra(UserCtx *user, PetscReal u, PetscReal v, PetscReal w)
3505{
3506 PetscErrorCode ierr;
3507 PetscFunctionBeginUser;
3509
3510 DMDALocalInfo info;
3511 Cmpnts ***ucont_arr;
3512 const Cmpnts ***csi_arr, ***eta_arr, ***zet_arr;
3513
3514 ierr = DMDAGetLocalInfo(user->fda, &info); CHKERRQ(ierr);
3515 ierr = DMDAVecGetArray(user->fda, user->Ucont, &ucont_arr); CHKERRQ(ierr);
3516 ierr = DMDAVecGetArrayRead(user->fda, user->lCsi, &csi_arr); CHKERRQ(ierr);
3517 ierr = DMDAVecGetArrayRead(user->fda, user->lEta, &eta_arr); CHKERRQ(ierr);
3518 ierr = DMDAVecGetArrayRead(user->fda, user->lZet, &zet_arr); CHKERRQ(ierr);
3519
3520 const PetscInt xs = info.xs, xe = info.xs + info.xm;
3521 const PetscInt ys = info.ys, ye = info.ys + info.ym;
3522 const PetscInt zs = info.zs, ze = info.zs + info.zm;
3523
3524 for (PetscInt k = zs; k < ze; k++) {
3525 for (PetscInt j = ys; j < ye; j++) {
3526 for (PetscInt i = xs; i < xe; i++) {
3527 ucont_arr[k][j][i].x = csi_arr[k][j][i].x * u + csi_arr[k][j][i].y * v + csi_arr[k][j][i].z * w;
3528 ucont_arr[k][j][i].y = eta_arr[k][j][i].x * u + eta_arr[k][j][i].y * v + eta_arr[k][j][i].z * w;
3529 ucont_arr[k][j][i].z = zet_arr[k][j][i].x * u + zet_arr[k][j][i].y * v + zet_arr[k][j][i].z * w;
3530 }
3531 }
3532 }
3533
3534 ierr = DMDAVecRestoreArrayRead(user->fda, user->lZet, &zet_arr); CHKERRQ(ierr);
3535 ierr = DMDAVecRestoreArrayRead(user->fda, user->lEta, &eta_arr); CHKERRQ(ierr);
3536 ierr = DMDAVecRestoreArrayRead(user->fda, user->lCsi, &csi_arr); CHKERRQ(ierr);
3537 ierr = DMDAVecRestoreArray(user->fda, user->Ucont, &ucont_arr); CHKERRQ(ierr);
3538
3539 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Cart2Contra: set Ucont from Cartesian (%.3f, %.3f, %.3f).\n",
3540 (double)u, (double)v, (double)w);
3542 PetscFunctionReturn(0);
3543}
3544
3545#undef __FUNCT__
3546#define __FUNCT__ "SetupDomainCellDecompositionMap"
3547/**
3548 * @brief Internal helper implementation: `SetupDomainCellDecompositionMap()`.
3549 * @details Local to this translation unit.
3550 */
3552{
3553 PetscErrorCode ierr;
3554 DMDALocalInfo local_node_info;
3555 RankCellInfo my_cell_info;
3556 PetscMPIInt rank, size;
3557
3558 PetscFunctionBeginUser;
3560
3561 // --- 1. Input Validation and MPI Info ---
3562 if (!user) {
3563 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx pointer is NULL in SetupDomainCellDecompositionMap.");
3564 }
3565 if (!user->da) {
3566 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, "user->da is not initialized in SetupDomainCellDecompositionMap.");
3567 }
3568
3569 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
3570 ierr = MPI_Comm_size(PETSC_COMM_WORLD, &size); CHKERRQ(ierr);
3571
3572 LOG_ALLOW(GLOBAL, LOG_INFO, "Setting up domain cell decomposition map for %d ranks.\n", size);
3573
3574 // --- 2. Determine Local Cell Ownership ---
3575 // Get the local node ownership information from the primary DMDA.
3576 ierr = DMDAGetLocalInfo(user->da, &local_node_info); CHKERRQ(ierr);
3577
3578 // Use the robust helper function to convert node ownership to cell ownership.
3579 // A cell's index is defined by its origin node.
3580
3581 ierr = GetOwnedCellRange(&local_node_info, 0, &my_cell_info.xs_cell, &my_cell_info.xm_cell); CHKERRQ(ierr);
3582 ierr = GetOwnedCellRange(&local_node_info, 1, &my_cell_info.ys_cell, &my_cell_info.ym_cell); CHKERRQ(ierr);
3583 ierr = GetOwnedCellRange(&local_node_info, 2, &my_cell_info.zs_cell, &my_cell_info.zm_cell); CHKERRQ(ierr);
3584
3585 // Log the calculated local ownership for debugging purposes.
3586 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d] Owns cells: i[%d, %d), j[%d, %d), k[%d, %d)\n",
3587 rank, my_cell_info.xs_cell, my_cell_info.xs_cell + my_cell_info.xm_cell,
3588 my_cell_info.ys_cell, my_cell_info.ys_cell + my_cell_info.ym_cell,
3589 my_cell_info.zs_cell, my_cell_info.zs_cell + my_cell_info.zm_cell);
3590
3591 // --- 3. Allocate and Distribute the Global Map ---
3592 // Allocate memory for the global map that will hold information from all ranks.
3593 ierr = PetscMalloc1(size, &user->RankCellInfoMap); CHKERRQ(ierr);
3594
3595 // Perform the collective communication to gather the `RankCellInfo` struct from every rank.
3596 // Each rank sends its `my_cell_info` and receives the complete array in `user->RankCellInfoMap`.
3597 // We use MPI_BYTE to ensure portability across different systems and struct padding.
3598 ierr = MPI_Allgather(&my_cell_info, sizeof(RankCellInfo), MPI_BYTE,
3599 user->RankCellInfoMap, sizeof(RankCellInfo), MPI_BYTE,
3600 PETSC_COMM_WORLD); CHKERRQ(ierr);
3601
3602 LOG_ALLOW(GLOBAL, LOG_INFO, "Domain cell decomposition map created and distributed successfully.\n");
3603
3605 PetscFunctionReturn(0);
3606}
3607
3608#undef __FUNCT__
3609#define __FUNCT__ "BinarySearchInt64"
3610/**
3611 * @brief Implementation of \ref BinarySearchInt64().
3612 * @details Full API contract (arguments, ownership, side effects) is documented with
3613 * the header declaration in `include/setup.h`.
3614 * @see BinarySearchInt64()
3615 */
3616PetscErrorCode BinarySearchInt64(PetscInt n, const PetscInt64 arr[], PetscInt64 key, PetscBool *found)
3617{
3618 PetscInt low = 0, high = n - 1;
3619
3620 PetscFunctionBeginUser;
3622
3623 // --- 1. Input Validation ---
3624 if (!found) {
3625 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Output pointer 'found' is NULL in PetscBinarySearchInt64.");
3626 }
3627 if (n > 0 && !arr) {
3628 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Input array 'arr' is NULL for n > 0.");
3629 }
3630
3631 // Initialize output
3632 *found = PETSC_FALSE;
3633
3634 // --- 2. Binary Search Algorithm ---
3635 while (low <= high) {
3636 // Use this form to prevent potential integer overflow on very large arrays
3637 PetscInt mid = low + (high - low) / 2;
3638
3639 if (arr[mid] == key) {
3640 *found = PETSC_TRUE; // Key found!
3641 break; // Exit the loop
3642 }
3643
3644 if (arr[mid] < key) {
3645 low = mid + 1; // Search in the right half
3646 } else {
3647 high = mid - 1; // Search in the left half
3648 }
3649 }
3650
3652 PetscFunctionReturn(0);
3653}
3654
3655
3656/**
3657 * @brief Convert logical indices into the flattened global index used by setup helpers.
3658 */
3659static PetscInt Gidx(PetscInt i, PetscInt j, PetscInt k, UserCtx *user)
3660{
3661 PetscInt nidx;
3662 DMDALocalInfo info = user->info;
3663
3664 PetscInt mx = info.mx, my = info.my;
3665
3666 AO ao;
3667 DMDAGetAO(user->da, &ao);
3668 nidx=i+j*mx+k*mx*my;
3669
3670 AOApplicationToPetsc(ao,1,&nidx);
3671
3672 return (nidx);
3673}
3674
3675
3676#undef __FUNCT__
3677#define __FUNCT__ "ComputeDivergence"
3678/**
3679 * @brief Implementation of \ref ComputeDivergence().
3680 * @details Full API contract (arguments, ownership, side effects) is documented with
3681 * the header declaration in `include/setup.h`.
3682 * @see ComputeDivergence()
3683 */
3684
3685PetscErrorCode ComputeDivergence(UserCtx *user )
3686{
3687 DM da = user->da, fda = user->fda;
3688 DMDALocalInfo info = user->info;
3689
3690 PetscInt ti = user->simCtx->step;
3691
3692 PetscInt xs = info.xs, xe = info.xs + info.xm;
3693 PetscInt ys = info.ys, ye = info.ys + info.ym;
3694 PetscInt zs = info.zs, ze = info.zs + info.zm;
3695 PetscInt mx = info.mx, my = info.my, mz = info.mz;
3696
3697 PetscInt lxs, lys, lzs, lxe, lye, lze;
3698 PetscInt i, j, k;
3699
3700 Vec Div;
3701 PetscReal ***div, ***aj, ***nvert,***p;
3702 Cmpnts ***ucont;
3703 PetscReal maxdiv;
3704
3705 lxs = xs; lxe = xe;
3706 lys = ys; lye = ye;
3707 lzs = zs; lze = ze;
3708
3709 if (xs==0) lxs = xs+1;
3710 if (ys==0) lys = ys+1;
3711 if (zs==0) lzs = zs+1;
3712
3713 if (xe==mx) lxe = xe-1;
3714 if (ye==my) lye = ye-1;
3715 if (ze==mz) lze = ze-1;
3716
3717 PetscFunctionBeginUser;
3719
3720 DMDAVecGetArray(fda,user->lUcont, &ucont);
3721 DMDAVecGetArray(da, user->lAj, &aj);
3722 VecDuplicate(user->P, &Div);
3723 DMDAVecGetArray(da, Div, &div);
3724 DMDAVecGetArray(da, user->lNvert, &nvert);
3725 DMDAVecGetArray(da, user->P, &p);
3726 for (k=lzs; k<lze; k++) {
3727 for (j=lys; j<lye; j++){
3728 for (i=lxs; i<lxe; i++) {
3729 if (k==10 && j==10 && i==1){
3730 LOG_ALLOW(LOCAL,LOG_INFO,"Pressure[10][10][1] = %f | Pressure[10][10][0] = %f \n ",p[k][j][i],p[k][j][i-1]);
3731 }
3732
3733 if (k==10 && j==10 && i==mx-3)
3734 LOG_ALLOW(LOCAL,LOG_INFO,"Pressure[10][10][%d] = %f | Pressure[10][10][%d] = %f \n ",mx-2,p[k][j][mx-2],mx-1,p[k][j][mx-1]);
3735 }
3736 }
3737 }
3738 DMDAVecRestoreArray(da, user->P, &p);
3739
3740
3741 for (k=lzs; k<lze; k++) {
3742 for (j=lys; j<lye; j++) {
3743 for (i=lxs; i<lxe; i++) {
3744 maxdiv = fabs((ucont[k][j][i].x - ucont[k][j][i-1].x +
3745 ucont[k][j][i].y - ucont[k][j-1][i].y +
3746 ucont[k][j][i].z - ucont[k-1][j][i].z)*aj[k][j][i]);
3747 if (nvert[k][j][i] + nvert[k+1][j][i] + nvert[k-1][j][i] +
3748 nvert[k][j+1][i] + nvert[k][j-1][i] +
3749 nvert[k][j][i+1] + nvert[k][j][i-1] > 0.1) maxdiv = 0.;
3750 div[k][j][i] = maxdiv;
3751
3752 }
3753 }
3754 }
3755
3756 if (zs==0) {
3757 k=0;
3758 for (j=ys; j<ye; j++) {
3759 for (i=xs; i<xe; i++) {
3760 div[k][j][i] = 0.;
3761 }
3762 }
3763 }
3764
3765 if (ze == mz) {
3766 k=mz-1;
3767 for (j=ys; j<ye; j++) {
3768 for (i=xs; i<xe; i++) {
3769 div[k][j][i] = 0.;
3770 }
3771 }
3772 }
3773
3774 if (xs==0) {
3775 i=0;
3776 for (k=zs; k<ze; k++) {
3777 for (j=ys; j<ye; j++) {
3778 div[k][j][i] = 0.;
3779 }
3780 }
3781 }
3782
3783 if (xe==mx) {
3784 i=mx-1;
3785 for (k=zs; k<ze; k++) {
3786 for (j=ys; j<ye; j++) {
3787 div[k][j][i] = 0;
3788 }
3789 }
3790 }
3791
3792 if (ys==0) {
3793 j=0;
3794 for (k=zs; k<ze; k++) {
3795 for (i=xs; i<xe; i++) {
3796 div[k][j][i] = 0.;
3797 }
3798 }
3799 }
3800
3801 if (ye==my) {
3802 j=my-1;
3803 for (k=zs; k<ze; k++) {
3804 for (i=xs; i<xe; i++) {
3805 div[k][j][i] = 0.;
3806 }
3807 }
3808 }
3809 DMDAVecRestoreArray(da, Div, &div);
3810 PetscInt MaxFlatIndex;
3811
3812 VecMax(Div, &MaxFlatIndex, &maxdiv);
3813
3814 LOG_ALLOW(GLOBAL,LOG_INFO,"[Step %d]] The Maximum Divergence is %e at flat index %d.\n",ti,maxdiv,MaxFlatIndex);
3815
3816 user->simCtx->MaxDivFlatArg = MaxFlatIndex;
3817 user->simCtx->MaxDiv = maxdiv;
3818
3819 for (k=zs; k<ze; k++) {
3820 for (j=ys; j<ye; j++) {
3821 for (i=xs; i<xe; i++) {
3822 if (Gidx(i,j,k,user) == MaxFlatIndex) {
3823 LOG_ALLOW(GLOBAL,LOG_INFO,"[Step %d] The Maximum Divergence(%e) is at location [%d][%d][%d]. \n", ti, maxdiv,k,j,i);
3824 user->simCtx->MaxDivz = k;
3825 user->simCtx->MaxDivy = j;
3826 user->simCtx->MaxDivx = i;
3827 }
3828 }
3829 }
3830 }
3831
3832
3833 DMDAVecRestoreArray(da, user->lNvert, &nvert);
3834 DMDAVecRestoreArray(fda, user->lUcont, &ucont);
3835 DMDAVecRestoreArray(da, user->lAj, &aj);
3836 VecDestroy(&Div);
3837
3839 PetscFunctionReturn(0);
3840}
3841
3842#undef __FUNCT__
3843#define __FUNCT__ "InitializeRandomGenerators"
3844
3845/**
3846 * @brief Implementation of \ref InitializeRandomGenerators().
3847 * @details Full API contract (arguments, ownership, side effects) is documented with
3848 * the header declaration in `include/setup.h`.
3849 * @see InitializeRandomGenerators()
3850 */
3851PetscErrorCode InitializeRandomGenerators(UserCtx* user, PetscRandom *randx, PetscRandom *randy, PetscRandom *randz) {
3852 PetscErrorCode ierr; // Error code for PETSc functions
3853 PetscMPIInt rank;
3854 PetscFunctionBeginUser;
3856 MPI_Comm_rank(PETSC_COMM_WORLD, &rank);
3857
3858 // Initialize RNG for x-coordinate
3859 ierr = PetscRandomCreate(PETSC_COMM_SELF, randx); CHKERRQ(ierr);
3860 ierr = PetscRandomSetType((*randx), PETSCRAND48); CHKERRQ(ierr);
3861 ierr = PetscRandomSetInterval(*randx, user->bbox.min_coords.x, user->bbox.max_coords.x); CHKERRQ(ierr);
3862 /* Offsets reproduce the historical fixed seeds (12345, 67890, 54321 plus rank) at the
3863 default base seed, so existing placements are unchanged. */
3864 const unsigned long base_seed = (unsigned long)user->simCtx->particleRandomSeed;
3865 ierr = PetscRandomSetSeed(*randx, base_seed + (unsigned long)rank); CHKERRQ(ierr);
3866 ierr = PetscRandomSeed(*randx); CHKERRQ(ierr);
3867 LOG_ALLOW_SYNC(LOCAL,LOG_VERBOSE, "[Rank %d]Initialized RNG for X-axis.\n",rank);
3868
3869 // Initialize RNG for y-coordinate
3870 ierr = PetscRandomCreate(PETSC_COMM_SELF, randy); CHKERRQ(ierr);
3871 ierr = PetscRandomSetType((*randy), PETSCRAND48); CHKERRQ(ierr);
3872 ierr = PetscRandomSetInterval(*randy, user->bbox.min_coords.y, user->bbox.max_coords.y); CHKERRQ(ierr);
3873 ierr = PetscRandomSetSeed(*randy, base_seed + 55545UL + (unsigned long)rank); CHKERRQ(ierr);
3874 ierr = PetscRandomSeed(*randy); CHKERRQ(ierr);
3875 LOG_ALLOW_SYNC(LOCAL,LOG_VERBOSE, "[Rank %d]Initialized RNG for Y-axis.\n",rank);
3876
3877 // Initialize RNG for z-coordinate
3878 ierr = PetscRandomCreate(PETSC_COMM_SELF, randz); CHKERRQ(ierr);
3879 ierr = PetscRandomSetType((*randz), PETSCRAND48); CHKERRQ(ierr);
3880 ierr = PetscRandomSetInterval(*randz, user->bbox.min_coords.z, user->bbox.max_coords.z); CHKERRQ(ierr);
3881 ierr = PetscRandomSetSeed(*randz, base_seed + 41976UL + (unsigned long)rank); CHKERRQ(ierr);
3882 ierr = PetscRandomSeed(*randz); CHKERRQ(ierr);
3883 LOG_ALLOW_SYNC(LOCAL,LOG_VERBOSE, "[Rank %d]Initialized RNG for Z-axis.\n",rank);
3884
3886 PetscFunctionReturn(0);
3887}
3888
3889#undef __FUNCT__
3890#define __FUNCT__ "InitializeLogicalSpaceRNGs"
3891/**
3892 * @brief Internal helper implementation: `InitializeLogicalSpaceRNGs()`.
3893 * @details Local to this translation unit.
3894 */
3895PetscErrorCode InitializeLogicalSpaceRNGs(PetscInt base_seed, PetscRandom *rand_logic_i, PetscRandom *rand_logic_j, PetscRandom *rand_logic_k) {
3896 PetscErrorCode ierr;
3897 PetscMPIInt rank;
3898 PetscFunctionBeginUser;
3899
3901
3902 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
3903
3904 // --- RNG for i-logical dimension ---
3905 ierr = PetscRandomCreate(PETSC_COMM_SELF, rand_logic_i); CHKERRQ(ierr);
3906 ierr = PetscRandomSetType((*rand_logic_i), PETSCRAND48); CHKERRQ(ierr);
3907 ierr = PetscRandomSetInterval(*rand_logic_i, 0.0, 1.0); CHKERRQ(ierr); // Key change: [0,1)
3908 /* Offsets reproduce the historical seeds (202401..202403 plus rank) at the default base. */
3909 ierr = PetscRandomSetSeed(*rand_logic_i, (unsigned long)base_seed + 190056UL + (unsigned long)rank); CHKERRQ(ierr);
3910 ierr = PetscRandomSeed(*rand_logic_i); CHKERRQ(ierr);
3911 LOG_ALLOW(LOCAL,LOG_VERBOSE, "[Rank %d] Initialized RNG for i-logical dimension [0,1).\n",rank);
3912
3913 // --- RNG for j-logical dimension ---
3914 ierr = PetscRandomCreate(PETSC_COMM_SELF, rand_logic_j); CHKERRQ(ierr);
3915 ierr = PetscRandomSetType((*rand_logic_j), PETSCRAND48); CHKERRQ(ierr);
3916 ierr = PetscRandomSetInterval(*rand_logic_j, 0.0, 1.0); CHKERRQ(ierr); // Key change: [0,1)
3917 ierr = PetscRandomSetSeed(*rand_logic_j, (unsigned long)base_seed + 190057UL + (unsigned long)rank); CHKERRQ(ierr);
3918 ierr = PetscRandomSeed(*rand_logic_j); CHKERRQ(ierr);
3919 LOG_ALLOW(LOCAL,LOG_VERBOSE, "[Rank %d] Initialized RNG for j-logical dimension [0,1).\n",rank);
3920
3921 // --- RNG for k-logical dimension ---
3922 ierr = PetscRandomCreate(PETSC_COMM_SELF, rand_logic_k); CHKERRQ(ierr);
3923 ierr = PetscRandomSetType((*rand_logic_k), PETSCRAND48); CHKERRQ(ierr);
3924 ierr = PetscRandomSetInterval(*rand_logic_k, 0.0, 1.0); CHKERRQ(ierr); // Key change: [0,1)
3925 ierr = PetscRandomSetSeed(*rand_logic_k, (unsigned long)base_seed + 190058UL + (unsigned long)rank); CHKERRQ(ierr);
3926 ierr = PetscRandomSeed(*rand_logic_k); CHKERRQ(ierr);
3927 LOG_ALLOW(LOCAL,LOG_VERBOSE, "[Rank %d] Initialized RNG for k-logical dimension [0,1).\n",rank);
3928
3929
3931 PetscFunctionReturn(0);
3932}
3933
3934#undef __FUNCT__
3935#define __FUNCT__ "InitializeBrownianRNG"
3936/**
3937 * @brief Internal helper implementation: `InitializeBrownianRNG()`.
3938 * @details Local to this translation unit.
3939 */
3940PetscErrorCode InitializeBrownianRNG(SimCtx *simCtx) {
3941 PetscErrorCode ierr;
3942 PetscMPIInt rank;
3943
3944 PetscFunctionBeginUser;
3946
3947 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
3948
3949 // 1. Create the generator (stored in SimCtx, not UserCtx, as it is global physics)
3950 ierr = PetscRandomCreate(PETSC_COMM_WORLD, &simCtx->BrownianMotionRNG); CHKERRQ(ierr);
3951 ierr = PetscRandomSetType(simCtx->BrownianMotionRNG, PETSCRAND48); CHKERRQ(ierr);
3952
3953 // 2. CRITICAL: Set interval to [0, 1).
3954 // This is required for the Gaussian math to work.
3955 ierr = PetscRandomSetInterval(simCtx->BrownianMotionRNG, 0.0, 1.0); CHKERRQ(ierr);
3956
3957 // 3. Seed from the configured base, the rank, and the start step. It was seeded from
3958 // the wall clock, so no two runs of the same inputs agreed. The start step keeps a
3959 // restart from replaying the increments the first segment already drew, which would
3960 // correlate them; the same restart is still reproducible.
3961 unsigned long seed = (unsigned long)simCtx->particleRandomSeed
3962 + (unsigned long)rank * 987654321UL
3963 + (unsigned long)simCtx->StartStep * 1000003UL;
3964 ierr = PetscRandomSetSeed(simCtx->BrownianMotionRNG, seed); CHKERRQ(ierr);
3965 ierr = PetscRandomSeed(simCtx->BrownianMotionRNG); CHKERRQ(ierr);
3966
3967 LOG_ALLOW(LOCAL, LOG_VERBOSE, "[Rank %d] Initialized Brownian Physics RNG.\n", rank);
3968
3970 PetscFunctionReturn(0);
3971}
3972
3973/////////////// DERIVATIVE CALCULATION HELPERS ///////////////
3974
3975#undef __FUNCT__
3976#define __FUNCT__ "TransformScalarDerivativesToPhysical"
3977/**
3978 * @brief Implementation of \ref TransformScalarDerivativesToPhysical().
3979 * @details Full API contract (arguments, ownership, side effects) is documented with
3980 * the header declaration in `include/setup.h`.
3981 * @see TransformScalarDerivativesToPhysical()
3982 */
3984 Cmpnts csi_metrics,
3985 Cmpnts eta_metrics,
3986 Cmpnts zet_metrics,
3987 PetscReal dPhi_dcsi,
3988 PetscReal dPhi_deta,
3989 PetscReal dPhi_dzet,
3990 Cmpnts *gradPhi)
3991{
3992 // Gradient X component
3993 gradPhi->x = jacobian * (dPhi_dcsi * csi_metrics.x + dPhi_deta * eta_metrics.x + dPhi_dzet * zet_metrics.x);
3994
3995 // Gradient Y component
3996 gradPhi->y = jacobian * (dPhi_dcsi * csi_metrics.y + dPhi_deta * eta_metrics.y + dPhi_dzet * zet_metrics.y);
3997
3998 // Gradient Z component
3999 gradPhi->z = jacobian * (dPhi_dcsi * csi_metrics.z + dPhi_deta * eta_metrics.z + dPhi_dzet * zet_metrics.z);
4000}
4001
4002#undef __FUNCT__
4003#define __FUNCT__ "TransformDerivativesToPhysical"
4004/**
4005 * @brief Transform contravariant vector derivatives into physical Cartesian derivatives.
4006 */
4007static void TransformDerivativesToPhysical(PetscReal jacobian, Cmpnts csi_metrics, Cmpnts eta_metrics, Cmpnts zet_metrics,
4008 Cmpnts deriv_csi, Cmpnts deriv_eta, Cmpnts deriv_zet,
4009 Cmpnts *dudx, Cmpnts *dvdx, Cmpnts *dwdx)
4010{
4011 // Derivatives of the first component (u)
4012 dudx->x = jacobian * (deriv_csi.x * csi_metrics.x + deriv_eta.x * eta_metrics.x + deriv_zet.x * zet_metrics.x);
4013 dudx->y = jacobian * (deriv_csi.x * csi_metrics.y + deriv_eta.x * eta_metrics.y + deriv_zet.x * zet_metrics.y);
4014 dudx->z = jacobian * (deriv_csi.x * csi_metrics.z + deriv_eta.x * eta_metrics.z + deriv_zet.x * zet_metrics.z);
4015 // Derivatives of the second component (v)
4016 dvdx->x = jacobian * (deriv_csi.y * csi_metrics.x + deriv_eta.y * eta_metrics.x + deriv_zet.y * zet_metrics.x);
4017 dvdx->y = jacobian * (deriv_csi.y * csi_metrics.y + deriv_eta.y * eta_metrics.y + deriv_zet.y * zet_metrics.y);
4018 dvdx->z = jacobian * (deriv_csi.y * csi_metrics.z + deriv_eta.y * eta_metrics.z + deriv_zet.y * zet_metrics.z);
4019 // Derivatives of the third component (w)
4020 dwdx->x = jacobian * (deriv_csi.z * csi_metrics.x + deriv_eta.z * eta_metrics.x + deriv_zet.z * zet_metrics.x);
4021 dwdx->y = jacobian * (deriv_csi.z * csi_metrics.y + deriv_eta.z * eta_metrics.y + deriv_zet.z * zet_metrics.y);
4022 dwdx->z = jacobian * (deriv_csi.z * csi_metrics.z + deriv_eta.z * eta_metrics.z + deriv_zet.z * zet_metrics.z);
4023}
4024
4025#undef __FUNCT__
4026#define __FUNCT__ "ComputeScalarFieldDerivatives"
4027/**
4028 * @brief Internal helper implementation: `ComputeScalarFieldDerivatives()`.
4029 * @details Local to this translation unit.
4030 */
4031PetscErrorCode ComputeScalarFieldDerivatives(UserCtx *user, PetscInt i, PetscInt j, PetscInt k,
4032 PetscReal ***field_data, Cmpnts *grad)
4033{
4034 PetscErrorCode ierr;
4035 Cmpnts ***csi, ***eta, ***zet;
4036 PetscReal ***jac;
4037 PetscReal d_csi, d_eta, d_zet;
4038
4039 PetscFunctionBeginUser;
4040
4041 // 1. Get read-only access to metrics
4042 ierr = DMDAVecGetArrayRead(user->fda, user->lCsi, &csi); CHKERRQ(ierr);
4043 ierr = DMDAVecGetArrayRead(user->fda, user->lEta, &eta); CHKERRQ(ierr);
4044 ierr = DMDAVecGetArrayRead(user->fda, user->lZet, &zet); CHKERRQ(ierr);
4045 ierr = DMDAVecGetArrayRead(user->da, user->lAj, &jac); CHKERRQ(ierr);
4046
4047 // 2. Compute derivatives in computational space (Central Difference)
4048 // Assumes ghosts are available at i+/-1
4049 d_csi = 0.5 * (field_data[k][j][i+1] - field_data[k][j][i-1]);
4050 d_eta = 0.5 * (field_data[k][j+1][i] - field_data[k][j-1][i]);
4051 d_zet = 0.5 * (field_data[k+1][j][i] - field_data[k-1][j][i]);
4052
4053 // 3. Transform to physical space
4055 csi[k][j][i], eta[k][j][i], zet[k][j][i],
4056 d_csi, d_eta, d_zet,
4057 grad);
4058
4059 // 4. Restore arrays
4060 ierr = DMDAVecRestoreArrayRead(user->fda, user->lCsi, &csi); CHKERRQ(ierr);
4061 ierr = DMDAVecRestoreArrayRead(user->fda, user->lEta, &eta); CHKERRQ(ierr);
4062 ierr = DMDAVecRestoreArrayRead(user->fda, user->lZet, &zet); CHKERRQ(ierr);
4063 ierr = DMDAVecRestoreArrayRead(user->da, user->lAj, &jac); CHKERRQ(ierr);
4064
4065 PetscFunctionReturn(0);
4066}
4067
4068#undef __FUNCT__
4069#define __FUNCT__ "ComputeVectorFieldDerivatives"
4070/**
4071 * @brief Internal helper implementation: `ComputeVectorFieldDerivatives()`.
4072 * @details Local to this translation unit.
4073 */
4074PetscErrorCode ComputeVectorFieldDerivatives(UserCtx *user, PetscInt i, PetscInt j, PetscInt k, Cmpnts ***field_data,
4075 Cmpnts *dudx, Cmpnts *dvdx, Cmpnts *dwdx)
4076{
4077 PetscErrorCode ierr;
4078 Cmpnts ***csi, ***eta, ***zet;
4079 PetscReal ***jac;
4080 PetscFunctionBeginUser;
4081
4082 // 1. Get read-only access to the necessary metric data arrays
4083 ierr = DMDAVecGetArrayRead(user->fda, user->lCsi, &csi); CHKERRQ(ierr);
4084 ierr = DMDAVecGetArrayRead(user->fda, user->lEta, &eta); CHKERRQ(ierr);
4085 ierr = DMDAVecGetArrayRead(user->fda, user->lZet, &zet); CHKERRQ(ierr);
4086 ierr = DMDAVecGetArrayRead(user->da, user->lAj, &jac); CHKERRQ(ierr);
4087
4088 // 2. Calculate derivatives in computational space using central differencing
4089 Cmpnts deriv_csi, deriv_eta, deriv_zet;
4090 deriv_csi.x = (field_data[k][j][i+1].x - field_data[k][j][i-1].x) * 0.5;
4091 deriv_csi.y = (field_data[k][j][i+1].y - field_data[k][j][i-1].y) * 0.5;
4092 deriv_csi.z = (field_data[k][j][i+1].z - field_data[k][j][i-1].z) * 0.5;
4093
4094 deriv_eta.x = (field_data[k][j+1][i].x - field_data[k][j-1][i].x) * 0.5;
4095 deriv_eta.y = (field_data[k][j+1][i].y - field_data[k][j-1][i].y) * 0.5;
4096 deriv_eta.z = (field_data[k][j+1][i].z - field_data[k][j-1][i].z) * 0.5;
4097
4098 deriv_zet.x = (field_data[k+1][j][i].x - field_data[k-1][j][i].x) * 0.5;
4099 deriv_zet.y = (field_data[k+1][j][i].y - field_data[k-1][j][i].y) * 0.5;
4100 deriv_zet.z = (field_data[k+1][j][i].z - field_data[k-1][j][i].z) * 0.5;
4101
4102 // 3. Transform derivatives to physical space
4103 TransformDerivativesToPhysical(jac[k][j][i], csi[k][j][i], eta[k][j][i], zet[k][j][i],
4104 deriv_csi, deriv_eta, deriv_zet,
4105 dudx, dvdx, dwdx);
4106
4107 // 4. Restore access to the PETSc data arrays
4108 ierr = DMDAVecRestoreArrayRead(user->fda, user->lCsi, &csi); CHKERRQ(ierr);
4109 ierr = DMDAVecRestoreArrayRead(user->fda, user->lEta, &eta); CHKERRQ(ierr);
4110 ierr = DMDAVecRestoreArrayRead(user->fda, user->lZet, &zet); CHKERRQ(ierr);
4111 ierr = DMDAVecRestoreArrayRead(user->da, user->lAj, &jac); CHKERRQ(ierr);
4112
4113 PetscFunctionReturn(0);
4114}
4115
4116//================================================================================
4117//
4118// MEMORY CLEANUP FUNCTIONS
4119//
4120//================================================================================
4121
4122#undef __FUNCT__
4123#define __FUNCT__ "DestroyUserVectors"
4124/**
4125 * @brief Internal helper implementation: `DestroyUserVectors()`.
4126 * @details Local to this translation unit.
4127 */
4128PetscErrorCode DestroyUserVectors(UserCtx *user)
4129{
4130 PetscErrorCode ierr;
4131 PetscFunctionBeginUser;
4132
4133 // --- Group A: Primary Flow Fields (Always allocated at all levels) ---
4134 if (user->Ucont) { ierr = VecDestroy(&user->Ucont); CHKERRQ(ierr); }
4135 if (user->lUcont) { ierr = VecDestroy(&user->lUcont); CHKERRQ(ierr); }
4136 if (user->Ucat) { ierr = VecDestroy(&user->Ucat); CHKERRQ(ierr); }
4137 if (user->lUcat) { ierr = VecDestroy(&user->lUcat); CHKERRQ(ierr); }
4138 if (user->P) { ierr = VecDestroy(&user->P); CHKERRQ(ierr); }
4139 if (user->lP) { ierr = VecDestroy(&user->lP); CHKERRQ(ierr); }
4140 if (user->Nvert) { ierr = VecDestroy(&user->Nvert); CHKERRQ(ierr); }
4141 if (user->lNvert) { ierr = VecDestroy(&user->lNvert); CHKERRQ(ierr); }
4142
4143 // --- Group A2: Derived Flow Fields (Conditional) ---
4144 if(user->Diffusivity) {ierr = VecDestroy(&user->Diffusivity); CHKERRQ(ierr);}
4145 if(user->lDiffusivity){ierr = VecDestroy(&user->lDiffusivity); CHKERRQ(ierr);}
4146 if(user->DiffusivityGradient){ierr = VecDestroy(&user->DiffusivityGradient); CHKERRQ(ierr);}
4147 if(user->lDiffusivityGradient){ierr = VecDestroy(&user->lDiffusivityGradient); CHKERRQ(ierr);}
4148
4149 // --- Group B: Solver Work Vectors (All levels) ---
4150 if (user->Phi) { ierr = VecDestroy(&user->Phi); CHKERRQ(ierr); }
4151 if (user->lPhi) { ierr = VecDestroy(&user->lPhi); CHKERRQ(ierr); }
4152
4153 // --- Group C: Time-Stepping Vectors (Finest level only) ---
4154 if (user->Ucont_o) { ierr = VecDestroy(&user->Ucont_o); CHKERRQ(ierr); }
4155 if (user->Ucont_rm1) { ierr = VecDestroy(&user->Ucont_rm1); CHKERRQ(ierr); }
4156 if (user->Ucat_o) { ierr = VecDestroy(&user->Ucat_o); CHKERRQ(ierr); }
4157 if (user->P_o) { ierr = VecDestroy(&user->P_o); CHKERRQ(ierr); }
4158 if (user->Nvert_o) { ierr = VecDestroy(&user->Nvert_o); CHKERRQ(ierr); }
4159 if (user->lUcont_o) { ierr = VecDestroy(&user->lUcont_o); CHKERRQ(ierr); }
4160 if (user->lUcont_rm1) { ierr = VecDestroy(&user->lUcont_rm1); CHKERRQ(ierr); }
4161 if (user->lNvert_o) { ierr = VecDestroy(&user->lNvert_o); CHKERRQ(ierr); }
4162
4163 // --- Group D: Grid Metrics - Face Centered (All levels) ---
4164 if (user->Csi) { ierr = VecDestroy(&user->Csi); CHKERRQ(ierr); }
4165 if (user->Eta) { ierr = VecDestroy(&user->Eta); CHKERRQ(ierr); }
4166 if (user->Zet) { ierr = VecDestroy(&user->Zet); CHKERRQ(ierr); }
4167 if (user->Aj) { ierr = VecDestroy(&user->Aj); CHKERRQ(ierr); }
4168 if (user->lCsi) { ierr = VecDestroy(&user->lCsi); CHKERRQ(ierr); }
4169 if (user->lEta) { ierr = VecDestroy(&user->lEta); CHKERRQ(ierr); }
4170 if (user->lZet) { ierr = VecDestroy(&user->lZet); CHKERRQ(ierr); }
4171 if (user->lAj) { ierr = VecDestroy(&user->lAj); CHKERRQ(ierr); }
4172
4173 // --- Group E: Grid Metrics - Face Centered (All levels) ---
4174 if (user->ICsi) { ierr = VecDestroy(&user->ICsi); CHKERRQ(ierr); }
4175 if (user->IEta) { ierr = VecDestroy(&user->IEta); CHKERRQ(ierr); }
4176 if (user->IZet) { ierr = VecDestroy(&user->IZet); CHKERRQ(ierr); }
4177 if (user->JCsi) { ierr = VecDestroy(&user->JCsi); CHKERRQ(ierr); }
4178 if (user->JEta) { ierr = VecDestroy(&user->JEta); CHKERRQ(ierr); }
4179 if (user->JZet) { ierr = VecDestroy(&user->JZet); CHKERRQ(ierr); }
4180 if (user->KCsi) { ierr = VecDestroy(&user->KCsi); CHKERRQ(ierr); }
4181 if (user->KEta) { ierr = VecDestroy(&user->KEta); CHKERRQ(ierr); }
4182 if (user->KZet) { ierr = VecDestroy(&user->KZet); CHKERRQ(ierr); }
4183 if (user->IAj) { ierr = VecDestroy(&user->IAj); CHKERRQ(ierr); }
4184 if (user->JAj) { ierr = VecDestroy(&user->JAj); CHKERRQ(ierr); }
4185 if (user->KAj) { ierr = VecDestroy(&user->KAj); CHKERRQ(ierr); }
4186 if (user->lICsi) { ierr = VecDestroy(&user->lICsi); CHKERRQ(ierr); }
4187 if (user->lIEta) { ierr = VecDestroy(&user->lIEta); CHKERRQ(ierr); }
4188 if (user->lIZet) { ierr = VecDestroy(&user->lIZet); CHKERRQ(ierr); }
4189 if (user->lJCsi) { ierr = VecDestroy(&user->lJCsi); CHKERRQ(ierr); }
4190 if (user->lJEta) { ierr = VecDestroy(&user->lJEta); CHKERRQ(ierr); }
4191 if (user->lJZet) { ierr = VecDestroy(&user->lJZet); CHKERRQ(ierr); }
4192 if (user->lKCsi) { ierr = VecDestroy(&user->lKCsi); CHKERRQ(ierr); }
4193 if (user->lKEta) { ierr = VecDestroy(&user->lKEta); CHKERRQ(ierr); }
4194 if (user->lKZet) { ierr = VecDestroy(&user->lKZet); CHKERRQ(ierr); }
4195 if (user->lIAj) { ierr = VecDestroy(&user->lIAj); CHKERRQ(ierr); }
4196 if (user->lJAj) { ierr = VecDestroy(&user->lJAj); CHKERRQ(ierr); }
4197 if (user->lKAj) { ierr = VecDestroy(&user->lKAj); CHKERRQ(ierr); }
4198
4199 // --- Group F: Cell/Face Coordinates and Grid Spacing (All levels) ---
4200 if (user->Cent) { ierr = VecDestroy(&user->Cent); CHKERRQ(ierr); }
4201 if (user->lCent) { ierr = VecDestroy(&user->lCent); CHKERRQ(ierr); }
4202 if (user->GridSpace) { ierr = VecDestroy(&user->GridSpace); CHKERRQ(ierr); }
4203 if (user->lGridSpace) { ierr = VecDestroy(&user->lGridSpace); CHKERRQ(ierr); }
4204 if (user->Centx) { ierr = VecDestroy(&user->Centx); CHKERRQ(ierr); }
4205 if (user->Centy) { ierr = VecDestroy(&user->Centy); CHKERRQ(ierr); }
4206 if (user->Centz) { ierr = VecDestroy(&user->Centz); CHKERRQ(ierr); }
4207 if (user->lCentx) { ierr = VecDestroy(&user->lCentx); CHKERRQ(ierr); }
4208 if (user->lCenty) { ierr = VecDestroy(&user->lCenty); CHKERRQ(ierr); }
4209 if (user->lCentz) { ierr = VecDestroy(&user->lCentz); CHKERRQ(ierr); }
4210
4211 // --- Group G: Turbulence Model Vectors (Finest level, conditional on les) ---
4212 if (user->Nu_t) { ierr = VecDestroy(&user->Nu_t); CHKERRQ(ierr); }
4213 if (user->lNu_t) { ierr = VecDestroy(&user->lNu_t); CHKERRQ(ierr); }
4214 if (user->CS) { ierr = VecDestroy(&user->CS); CHKERRQ(ierr); }
4215 if (user->lCs) { ierr = VecDestroy(&user->lCs); CHKERRQ(ierr); }
4216 if (user->Nu_Wall) { ierr = VecDestroy(&user->Nu_Wall); CHKERRQ(ierr); }
4217 if (user->lNu_Wall) { ierr = VecDestroy(&user->lNu_Wall); CHKERRQ(ierr); }
4218 if (user->Friction_Velocity) { ierr = VecDestroy(&user->Friction_Velocity); CHKERRQ(ierr); }
4219 if (user->lFriction_Velocity) { ierr = VecDestroy(&user->lFriction_Velocity); CHKERRQ(ierr); }
4220
4221 // --- Group H: Particle Vectors (Finest level, conditional on np > 0) ---
4222 if (user->ParticleCount) { ierr = VecDestroy(&user->ParticleCount); CHKERRQ(ierr); }
4223 if (user->lParticleCount) { ierr = VecDestroy(&user->lParticleCount); CHKERRQ(ierr); }
4224 if (user->Psi) { ierr = VecDestroy(&user->Psi); CHKERRQ(ierr); }
4225 if (user->lPsi) { ierr = VecDestroy(&user->lPsi); CHKERRQ(ierr); }
4226
4227 // --- Group I: Boundary Condition Vectors (All levels) ---
4228 if (user->Bcs.Ubcs) { ierr = VecDestroy(&user->Bcs.Ubcs); CHKERRQ(ierr); }
4229 if (user->Bcs.Uch) { ierr = VecDestroy(&user->Bcs.Uch); CHKERRQ(ierr); }
4230
4231 // --- Group J: Post-Processing Vectors (Finest level, postprocessor mode) ---
4232 if (user->P_nodal) { ierr = VecDestroy(&user->P_nodal); CHKERRQ(ierr); }
4233 if (user->Ucat_nodal) { ierr = VecDestroy(&user->Ucat_nodal); CHKERRQ(ierr); }
4234 if (user->Qcrit) { ierr = VecDestroy(&user->Qcrit); CHKERRQ(ierr); }
4235 if (user->lQcrit) { ierr = VecDestroy(&user->lQcrit); CHKERRQ(ierr); }
4236 if (user->Qcrit_nodal) { ierr = VecDestroy(&user->Qcrit_nodal); CHKERRQ(ierr); }
4237 if (user->Psi_nodal) { ierr = VecDestroy(&user->Psi_nodal); CHKERRQ(ierr); }
4238
4239 // --- Group K: Interpolation Vectors (Lazy allocation) ---
4240 if (user->fieldStatisticsStorage) {
4241 SimCtx *stats_ctx = user->simCtx;
4242 const PetscInt window_count = stats_ctx ? stats_ctx->fieldStatisticsWindowCount : 0;
4243 for (PetscInt w = 0; w < window_count; ++w) {
4244 ierr = PicurvWindowStorageDestroy(&user->fieldStatisticsStorage[w]); CHKERRQ(ierr);
4245 }
4246 ierr = PetscFree(user->fieldStatisticsStorage); CHKERRQ(ierr);
4247 user->fieldStatisticsStorage = NULL;
4248 }
4249 if (user->CellScalarAtCorner) { ierr = VecDestroy(&user->CellScalarAtCorner); CHKERRQ(ierr); }
4250 if (user->lCellScalarAtCorner) { ierr = VecDestroy(&user->lCellScalarAtCorner); CHKERRQ(ierr); }
4251 if (user->CellVectorAtCorner) { ierr = VecDestroy(&user->CellVectorAtCorner); CHKERRQ(ierr); }
4252 if (user->PostScalar) { ierr = VecDestroy(&user->PostScalar); CHKERRQ(ierr); }
4253 if (user->lPostScalar) { ierr = VecDestroy(&user->lPostScalar); CHKERRQ(ierr); }
4254 if (user->PostScalarNodal) { ierr = VecDestroy(&user->PostScalarNodal); CHKERRQ(ierr); }
4255 if (user->PostVector) { ierr = VecDestroy(&user->PostVector); CHKERRQ(ierr); }
4256 if (user->lPostVector) { ierr = VecDestroy(&user->lPostVector); CHKERRQ(ierr); }
4257 if (user->PostVectorNodal) { ierr = VecDestroy(&user->PostVectorNodal); CHKERRQ(ierr); }
4258 if (user->lCellVectorAtCorner) { ierr = VecDestroy(&user->lCellVectorAtCorner); CHKERRQ(ierr); }
4259
4260 // --- Group L: Implicit Solver Temporary Vectors (Destroyed after use, but check anyway) ---
4261 if (user->Rhs) { ierr = VecDestroy(&user->Rhs); CHKERRQ(ierr); }
4262 if (user->dUcont) { ierr = VecDestroy(&user->dUcont); CHKERRQ(ierr); }
4263 if (user->pUcont) { ierr = VecDestroy(&user->pUcont); CHKERRQ(ierr); }
4264
4265 // --- Group M: Poisson Solver Vectors (kept for the run by PoissonSolver_Multigrid) ---
4266 if (user->B) { ierr = VecDestroy(&user->B); CHKERRQ(ierr); }
4267 if (user->R) { ierr = VecDestroy(&user->R); CHKERRQ(ierr); }
4268
4269 LOG_ALLOW(LOCAL, LOG_DEBUG, "All vectors destroyed for UserCtx.\n");
4270 PetscFunctionReturn(0);
4271}
4272#undef __FUNCT__
4273#define __FUNCT__ "DestroyUserContext"
4274/**
4275 * @brief Internal helper implementation: `DestroyUserContext()`.
4276 * @details Local to this translation unit.
4277 */
4278PetscErrorCode DestroyUserContext(UserCtx *user)
4279{
4280 PetscErrorCode ierr;
4281 PetscFunctionBeginUser;
4282
4283 if (!user) {
4284 LOG_ALLOW(LOCAL, LOG_WARNING, "DestroyUserContext called with NULL user pointer.\n");
4285 PetscFunctionReturn(0);
4286 }
4287
4288 LOG_ALLOW(LOCAL, LOG_INFO, "Destroying UserCtx at level %d...\n", user->thislevel);
4289
4290 // --- Step 1: Destroy Boundary Condition System ---
4291 // This handles all BC handlers and their private data.
4292 ierr = BoundarySystem_Destroy(user); CHKERRQ(ierr);
4293 LOG_ALLOW(LOCAL, LOG_DEBUG, " Boundary system destroyed.\n");
4294
4295 // --- Step 2: Destroy All Vectors ---
4296 // Handles ~74 Vec objects with proper NULL checking.
4297 ierr = DestroyUserVectors(user); CHKERRQ(ierr);
4298 LOG_ALLOW(LOCAL, LOG_DEBUG, " All vectors destroyed.\n");
4299
4300 // --- Step 3: Destroy Matrix and Solver Objects ---
4301 // Destroy pressure-Poisson matrices and solver.
4302 if (user->A) {
4303 ierr = MatDestroy(&user->A); CHKERRQ(ierr);
4304 LOG_ALLOW(LOCAL, LOG_DEBUG, " Matrix A destroyed.\n");
4305 }
4306 if (user->MR) {
4307 ierr = MatDestroy(&user->MR); CHKERRQ(ierr);
4308 LOG_ALLOW(LOCAL, LOG_DEBUG, " Matrix MR destroyed.\n");
4309 }
4310 if (user->MP) {
4311 ierr = MatDestroy(&user->MP); CHKERRQ(ierr);
4312 LOG_ALLOW(LOCAL, LOG_DEBUG, " Matrix MP destroyed.\n");
4313 }
4314 if (user->ksp) {
4315 ierr = KSPDestroy(&user->ksp); CHKERRQ(ierr);
4316 LOG_ALLOW(LOCAL, LOG_DEBUG, " KSP solver destroyed.\n");
4317 }
4318 if (user->nullsp) {
4319 ierr = MatNullSpaceDestroy(&user->nullsp); CHKERRQ(ierr);
4320 LOG_ALLOW(LOCAL, LOG_DEBUG, " MatNullSpace destroyed.\n");
4321 }
4322
4323 // --- Step 4: Destroy Application Ordering ---
4324 if (user->ao) {
4325 ierr = AODestroy(&user->ao); CHKERRQ(ierr);
4326 LOG_ALLOW(LOCAL, LOG_DEBUG, " AO destroyed.\n");
4327 }
4328
4329 // --- Step 5: Destroy DM Objects ---
4330 // Destroy in reverse order of dependency: post_swarm, swarm, fda6, fda, da
4331 if (user->post_swarm) {
4332 ierr = DMDestroy(&user->post_swarm); CHKERRQ(ierr);
4333 LOG_ALLOW(LOCAL, LOG_DEBUG, " post_swarm DM destroyed.\n");
4334 }
4335 if (user->swarm) {
4336 ierr = DMDestroy(&user->swarm); CHKERRQ(ierr);
4337 LOG_ALLOW(LOCAL, LOG_DEBUG, " swarm DM destroyed.\n");
4338 }
4339 if (user->fda6) {
4340 ierr = DMDestroy(&user->fda6); CHKERRQ(ierr);
4341 LOG_ALLOW(LOCAL, LOG_DEBUG, " fda6 DM destroyed.\n");
4342 }
4343 if (user->da) {
4344 ierr = DMDestroy(&user->da); CHKERRQ(ierr);
4345 LOG_ALLOW(LOCAL, LOG_DEBUG, " da DM destroyed.\n");
4346 }
4347
4348 // --- Step 6: Free PetscMalloc'd Arrays ---
4349 // Free arrays allocated with PetscMalloc1
4350 if (user->RankCellInfoMap) {
4351 ierr = PetscFree(user->RankCellInfoMap); CHKERRQ(ierr);
4352 user->RankCellInfoMap = NULL;
4353 LOG_ALLOW(LOCAL, LOG_DEBUG, " RankCellInfoMap freed.\n");
4354 }
4355
4356 LOG_ALLOW(LOCAL, LOG_INFO, "UserCtx at level %d fully destroyed.\n", user->thislevel);
4357 PetscFunctionReturn(0);
4358}
4359
4360#undef __FUNCT__
4361#define __FUNCT__ "FinalizeSimulation"
4362/**
4363 * @brief Implementation of \ref FinalizeSimulation().
4364 * @details Full API contract (arguments, ownership, side effects) is documented with
4365 * the header declaration in `include/setup.h`.
4366 * @see FinalizeSimulation()
4367 */
4368PetscErrorCode FinalizeSimulation(SimCtx *simCtx)
4369{
4370 PetscErrorCode ierr;
4371 PetscFunctionBeginUser;
4372
4373 if (!simCtx) {
4374 LOG_ALLOW(GLOBAL, LOG_WARNING, "FinalizeSimulation called with NULL SimCtx pointer.\n");
4375 PetscFunctionReturn(0);
4376 }
4377
4378 LOG_ALLOW(GLOBAL, LOG_INFO, "========================================\n");
4379 LOG_ALLOW(GLOBAL, LOG_INFO, "Beginning simulation memory cleanup...\n");
4380 LOG_ALLOW(GLOBAL, LOG_INFO, "========================================\n");
4381
4382 // ============================================================================
4383 // PHASE 1: DESTROY MULTIGRID HIERARCHY (All UserCtx structures)
4384 // ============================================================================
4385
4386 ierr = DestroySolutionConvergenceState(simCtx); CHKERRQ(ierr);
4387 ierr = DestroyFieldStatisticsConfig(simCtx); CHKERRQ(ierr);
4388 ierr = ParticleFieldPlanDestroy(&simCtx->particleFieldPlan); CHKERRQ(ierr);
4389
4390 if (simCtx->usermg.mgctx) {
4391 LOG_ALLOW(GLOBAL, LOG_INFO, "Destroying multigrid hierarchy (%d levels)...\n",
4392 simCtx->usermg.mglevels);
4393
4394 // Destroy each UserCtx from finest to coarsest (reverse order is safer)
4395 for (PetscInt level = simCtx->usermg.mglevels - 1; level >= 0; level--) {
4396 UserCtx *user = simCtx->usermg.mgctx[level].user;
4397 if (user) {
4398 LOG_ALLOW(LOCAL, LOG_INFO, " Destroying level %d of %d...\n",
4399 level, simCtx->usermg.mglevels - 1);
4400 ierr = DestroyUserContext(user); CHKERRQ(ierr);
4401
4402 // Free the UserCtx structure itself
4403 ierr = PetscFree(user); CHKERRQ(ierr);
4404 simCtx->usermg.mgctx[level].user = NULL;
4405 }
4406
4407 // Destroy the MGCtx-level packer DM
4408 if (simCtx->usermg.mgctx[level].packer) {
4409 ierr = DMDestroy(&simCtx->usermg.mgctx[level].packer); CHKERRQ(ierr);
4410 LOG_ALLOW(LOCAL, LOG_DEBUG, " MGCtx[%d].packer destroyed.\n", level);
4411 }
4412 }
4413
4414 // Free the MGCtx array itself
4415 ierr = PetscFree(simCtx->usermg.mgctx); CHKERRQ(ierr);
4416 simCtx->usermg.mgctx = NULL;
4417 LOG_ALLOW(GLOBAL, LOG_INFO, "All multigrid levels destroyed.\n");
4418 }
4419
4420 // ============================================================================
4421 // PHASE 2: DESTROY USERMG-LEVEL OBJECTS
4422 // ============================================================================
4423
4424 if (simCtx->usermg.packer) {
4425 ierr = DMDestroy(&simCtx->usermg.packer); CHKERRQ(ierr);
4426 LOG_ALLOW(LOCAL, LOG_DEBUG, "UserMG.packer DM destroyed.\n");
4427 }
4428
4429 if (simCtx->usermg.snespacker) {
4430 ierr = SNESDestroy(&simCtx->usermg.snespacker); CHKERRQ(ierr);
4431 LOG_ALLOW(LOCAL, LOG_DEBUG, "UserMG.snespacker SNES destroyed.\n");
4432 }
4433
4434 // ============================================================================
4435 // PHASE 3: DESTROY SIMCTX-LEVEL OBJECTS
4436 // ============================================================================
4437
4438 LOG_ALLOW(GLOBAL, LOG_INFO, "Destroying SimCtx-level objects...\n");
4439
4440 // --- PetscViewer for logging ---
4441 if (simCtx->logviewer) {
4442 ierr = PetscViewerDestroy(&simCtx->logviewer); CHKERRQ(ierr);
4443 LOG_ALLOW(LOCAL, LOG_DEBUG, " logviewer destroyed.\n");
4444 }
4445
4446 // --- Particle System DM ---
4447 if (simCtx->dm_swarm) {
4448 ierr = DMDestroy(&simCtx->dm_swarm); CHKERRQ(ierr);
4449 LOG_ALLOW(LOCAL, LOG_DEBUG, " dm_swarm destroyed.\n");
4450 }
4451
4452 // --- BoundingBox List (Array of BoundingBox structs) ---
4453 if (simCtx->bboxlist) {
4454 ierr = PetscFree(simCtx->bboxlist); CHKERRQ(ierr);
4455 simCtx->bboxlist = NULL;
4456 LOG_ALLOW(LOCAL, LOG_DEBUG, " bboxlist freed.\n");
4457 }
4458
4459 // --- Boundary Condition Files (Array of strings) ---
4460 if (simCtx->bcs_files) {
4461 for (PetscInt i = 0; i < simCtx->num_bcs_files; i++) {
4462 if (simCtx->bcs_files[i]) {
4463 ierr = PetscFree(simCtx->bcs_files[i]); CHKERRQ(ierr);
4464 }
4465 }
4466 ierr = PetscFree(simCtx->bcs_files); CHKERRQ(ierr);
4467 simCtx->bcs_files = NULL;
4468 LOG_ALLOW(LOCAL, LOG_DEBUG, " bcs_files array freed (%d files).\n", simCtx->num_bcs_files);
4469 }
4470
4471 // --- Brownian Motion RNG ---
4472 if (simCtx->BrownianMotionRNG) {
4473 ierr = PetscRandomDestroy(&simCtx->BrownianMotionRNG); CHKERRQ(ierr);
4474 LOG_ALLOW(LOCAL, LOG_DEBUG, " BrownianMotionRNG destroyed.\n");
4475 }
4476 // --- Post-Processing Parameters ---
4477 // pps is allocated with PetscNew and contains only static char arrays and basic types.
4478 // No internal dynamic allocations need to be freed.
4479 if (simCtx->pps) {
4480 ierr = PetscFree(simCtx->pps); CHKERRQ(ierr);
4481 simCtx->pps = NULL;
4482 LOG_ALLOW(LOCAL, LOG_DEBUG, " PostProcessParams freed.\n");
4483 }
4484
4485 // --- IBM/FSI Objects ---
4486 // Note: These are initialized to NULL and currently have no dedicated destroy functions.
4487 // If these modules are extended with cleanup routines, call them here.
4488 if (simCtx->ibm != NULL) {
4489 LOG_ALLOW(GLOBAL, LOG_WARNING, " WARNING: simCtx->ibm is non-NULL but no destroy function exists. Potential memory leak.\n");
4490 }
4491 if (simCtx->ibmv != NULL) {
4492 LOG_ALLOW(GLOBAL, LOG_WARNING, " WARNING: simCtx->ibmv is non-NULL but no destroy function exists. Potential memory leak.\n");
4493 }
4494 if (simCtx->fsi != NULL) {
4495 LOG_ALLOW(GLOBAL, LOG_WARNING, " WARNING: simCtx->fsi is non-NULL but no destroy function exists. Potential memory leak.\n");
4496 }
4497
4498 // --- Logging Allowed Functions (Array of strings) ---
4499 // Note: The logging system maintains its own copy via set_allowed_functions(),
4500 // so freeing simCtx->allowedFuncs will NOT affect LOG_ALLOW functionality.
4501 if (simCtx->allowedFuncs) {
4502 for (PetscInt i = 0; i < simCtx->nAllowed; i++) {
4503 if (simCtx->allowedFuncs[i]) {
4504 ierr = PetscFree(simCtx->allowedFuncs[i]); CHKERRQ(ierr);
4505 }
4506 }
4507 ierr = PetscFree(simCtx->allowedFuncs); CHKERRQ(ierr);
4508 simCtx->allowedFuncs = NULL;
4509 LOG_ALLOW(LOCAL, LOG_DEBUG, " allowedFuncs array freed (%d functions).\n", simCtx->nAllowed);
4510 }
4511
4512 // --- Profiling Critical Functions (Array of strings) ---
4513 if (simCtx->profilingSelectedFuncs) {
4514 for (PetscInt i = 0; i < simCtx->nProfilingSelectedFuncs; i++) {
4515 if (simCtx->profilingSelectedFuncs[i]) {
4516 ierr = PetscFree(simCtx->profilingSelectedFuncs[i]); CHKERRQ(ierr);
4517 }
4518 }
4519 ierr = PetscFree(simCtx->profilingSelectedFuncs); CHKERRQ(ierr);
4520 simCtx->profilingSelectedFuncs = NULL;
4521 LOG_ALLOW(LOCAL, LOG_DEBUG, " profilingSelectedFuncs array freed (%d functions).\n", simCtx->nProfilingSelectedFuncs);
4522 }
4523
4524 // ============================================================================
4525 // PHASE 4: FINAL SUMMARY
4526 // ============================================================================
4527
4528 LOG_ALLOW(GLOBAL, LOG_INFO, "========================================\n");
4529 LOG_ALLOW(GLOBAL, LOG_INFO, "Simulation cleanup completed successfully.\n");
4530 LOG_ALLOW(GLOBAL, LOG_INFO, "All PETSc objects have been destroyed.\n");
4531 LOG_ALLOW(GLOBAL, LOG_INFO, "========================================\n");
4532
4533 ierr = PetscFree(simCtx); CHKERRQ(ierr);
4534 PetscFunctionReturn(0);
4535}
PetscErrorCode BoundarySystem_Initialize(UserCtx *user, const char *bcs_filename)
Initializes the entire boundary system.
Definition Boundaries.c:876
PetscErrorCode PropagateBoundaryConfigToCoarserLevels(SimCtx *simCtx)
Propagates boundary condition configuration from finest to all coarser multigrid levels.
Definition Boundaries.c:973
PetscErrorCode BoundarySystem_Destroy(UserCtx *user)
Cleans up and destroys all boundary system resources.
PetscErrorCode CalculateAllGridMetrics(SimCtx *simCtx)
Orchestrates the calculation of all grid metrics.
Definition Metric.c:1993
Configured initial values of particle-carried fields, and the expression language that defines them.
PetscErrorCode ParticleFieldPlanCreate(ParticleFieldPlan **plan)
Read a plan from the options database.
PetscErrorCode ParticleFieldPlanDestroy(ParticleFieldPlan **plan)
Free a plan and its compiled expressions.
@ FIELD_CAPABILITY_GHOST_UPDATE
@ FIELD_SYNC_STANDARD
@ FIELD_SYNC_K_FACE
@ FIELD_SYNC_J_FACE
@ FIELD_SYNC_COMPONENT_STAGGERED
@ FIELD_SYNC_I_FACE
unsigned int capabilities
const FieldDescriptor * descriptor
PetscErrorCode FieldGetView(UserCtx *user, FieldId field_id, FieldView *view)
Resolve the existing DM and global/local vectors for one field.
const char * canonical_name
FieldSyncClass sync_class
FieldId
Compile-time identity for a catalogued Eulerian field.
@ FIELD_ID_UCAT
Non-owning runtime objects resolved for one field and UserCtx.
PetscErrorCode DefineAllGridDimensions(SimCtx *simCtx)
Orchestrates the parsing and setting of grid dimensions for all blocks.
Definition grid.c:57
PetscErrorCode CalculateOutletProperties(UserCtx *user)
Calculates the center and area of the primary OUTLET face.
Definition grid.c:1164
PetscErrorCode BroadcastAllBoundingBoxes(UserCtx *user, BoundingBox **bboxlist)
Broadcasts the bounding box information collected on rank 0 to all other ranks.
Definition grid.c:1061
PetscErrorCode ValidatePeriodicGeometry(UserCtx *user)
Validates that configured geometric periodic seams match by translation.
Definition grid.c:421
PetscErrorCode InitializeAllGridDMs(SimCtx *simCtx)
Orchestrates the creation of DMDA objects for every block and multigrid level.
Definition grid.c:276
PetscErrorCode AssignAllGridCoordinates(SimCtx *simCtx)
Orchestrates the assignment of physical coordinates to all DMDA objects.
Definition grid.c:358
PetscErrorCode CalculateInletProperties(UserCtx *user)
Calculates the center and area of the primary INLET face.
Definition grid.c:1111
PetscErrorCode GatherAllBoundingBoxes(UserCtx *user, BoundingBox **allBBoxes)
Gathers local bounding boxes from all MPI processes to rank 0.
Definition grid.c:999
PetscErrorCode ParsePostProcessingSettings(SimCtx *simCtx)
Initializes post-processing settings from a config file and command-line overrides.
Definition io.c:3240
PetscErrorCode ParseScalingInformation(SimCtx *simCtx)
Parses physical scaling parameters from command-line options.
Definition io.c:3425
PetscErrorCode VerifyPathExistence(const char *path, PetscBool is_dir, PetscBool is_optional, const char *description, PetscBool *exists)
A parallel-safe helper to verify the existence of a generic file or directory path.
Definition io.c:1136
void set_allowed_functions(const char **functionList, int count)
Sets the global list of function names that are allowed to log.
Definition logging.c:155
PetscBool is_function_allowed(const char *functionName)
Checks if a given function is in the allow-list.
Definition logging.c:186
#define LOG_ALLOW_SYNC(scope, level, fmt,...)
Synchronized logging macro that checks both the log level and whether the calling function is in the ...
Definition logging.h:253
#define LOCAL
Logging scope definitions for controlling message output.
Definition logging.h:45
#define GLOBAL
Scope for global logging across all processes.
Definition logging.h:46
#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
PetscErrorCode print_log_level(void)
Prints the current logging level to the console.
Definition logging.c:119
#define PROFILE_FUNCTION_END
Marks the end of a profiled code block.
Definition logging.h:894
#define LOG(scope, level, fmt,...)
Logging macro for PETSc-based applications with scope control.
Definition logging.h:84
PetscErrorCode LoadAllowedFunctionsFromFile(const char filename[], char ***funcsOut, PetscInt *nOut)
Load function names from a text file.
Definition logging.c:598
LogLevel get_log_level()
Retrieves the current logging level from the environment variable LOG_LEVEL.
Definition logging.c:87
PetscErrorCode ProfilingInitialize(SimCtx *simCtx)
Initializes the custom profiling system using configuration from SimCtx.
Definition logging.c:2023
@ LOG_ERROR
Critical errors that may halt the program.
Definition logging.h:29
@ LOG_INFO
Informational messages about program execution.
Definition logging.h:31
@ LOG_WARNING
Non-critical issues that warrant attention.
Definition logging.h:30
@ LOG_DEBUG
Detailed debugging information.
Definition logging.h:32
@ LOG_VERBOSE
Extremely detailed logs, typically for development use only.
Definition logging.h:34
#define PROFILE_FUNCTION_BEGIN
Marks the beginning of a profiled code block (typically a function).
Definition logging.h:885
const char * ParticleInitializationToString(ParticleInitializationType ParticleInitialization)
Returns the canonical log token for a particle-initialization mode.
Definition logging.c:724
PetscErrorCode ComputeVectorFieldDerivatives(UserCtx *user, PetscInt i, PetscInt j, PetscInt k, Cmpnts ***field_data, Cmpnts *dudx, Cmpnts *dvdx, Cmpnts *dwdx)
Internal helper implementation: ComputeVectorFieldDerivatives().
Definition setup.c:4074
static const char * kReservedRunDirectories[]
Directory names the run tree owns; a log directory must never target one.
Definition setup.c:1328
DirectoryVerdict
Definition setup.c:1387
@ DIR_VERDICT_MALFORMED
Definition setup.c:1389
@ DIR_VERDICT_RELATIVE_ESCAPE
Definition setup.c:1394
@ DIR_VERDICT_OVERLAP
Definition setup.c:1391
@ DIR_VERDICT_UNRESOLVABLE
Definition setup.c:1397
@ DIR_VERDICT_UNEXPANDED_TILDE
Definition setup.c:1395
@ DIR_VERDICT_EXTERNAL_ABSOLUTE
Definition setup.c:1396
@ DIR_VERDICT_CONTAINED
Definition setup.c:1388
@ DIR_VERDICT_RUN_ROOT
Definition setup.c:1392
@ DIR_VERDICT_RESERVED
Definition setup.c:1390
@ DIR_VERDICT_ANCESTOR
Definition setup.c:1393
PetscErrorCode DestroyUserContext(UserCtx *user)
Internal helper implementation: DestroyUserContext().
Definition setup.c:4278
static PetscBool LogDirectoryIsSafeToWipe(const char *log_dir, const char *output_dir, PetscBool authorized, const char **reason)
Final safety guard before the runtime deletes its log directory.
Definition setup.c:1669
PetscErrorCode GetOwnedCellRange(const DMDALocalInfo *info_nodes, PetscInt dim, PetscInt *xs_cell_global_out, PetscInt *xm_cell_local_out)
Internal helper implementation: GetOwnedCellRange().
Definition setup.c:2936
static PetscErrorCode ParseLESConfiguration(SimCtx *simCtx)
Reads every LES closure parameter from the generated control file.
Definition setup.c:259
#define PICURV_PETSC_MODE
Definition setup.c:24
PetscErrorCode SetupDomainRankInfo(SimCtx *simCtx)
Implementation of SetupDomainRankInfo().
Definition setup.c:3227
PetscErrorCode UniformCart2Contra(UserCtx *user, PetscReal u, PetscReal v, PetscReal w)
Populate contravariant fluxes from one uniform Cartesian velocity.
Definition setup.c:3504
PetscErrorCode InitializeRandomGenerators(UserCtx *user, PetscRandom *randx, PetscRandom *randy, PetscRandom *randz)
Implementation of InitializeRandomGenerators().
Definition setup.c:3851
#define PETSC_ARCH
Definition setup.c:27
PetscErrorCode Deallocate3DArrayVector(Cmpnts ***array, PetscInt nz, PetscInt ny)
Implementation of Deallocate3DArrayVector().
Definition setup.c:2870
PetscErrorCode SetupGridAndSolvers(SimCtx *simCtx)
Implementation of SetupGridAndSolvers().
Definition setup.c:2001
#define PICURV_PETSC_STAMP_MARKER
Definition setup.c:32
PetscErrorCode InitializeBrownianRNG(SimCtx *simCtx)
Internal helper implementation: InitializeBrownianRNG().
Definition setup.c:3940
static PetscInt Gidx(PetscInt i, PetscInt j, PetscInt k, UserCtx *user)
Convert logical indices into the flattened global index used by setup helpers.
Definition setup.c:3659
PetscErrorCode SetupSimulationEnvironment(SimCtx *simCtx)
Internal helper implementation: SetupSimulationEnvironment().
Definition setup.c:1685
static PetscBool NormalizePathLexically(const char *value, char *out, size_t size, char *stack)
Lexically normalize a path, resolving "." and ".." textually.
Definition setup.c:1414
static PetscBool PathContainsOrEquals(const char *ancestor, const char *path)
Whether ancestor is the same directory as path, or contains it.
Definition setup.c:1525
PetscErrorCode CreateAndInitializeAllVectors(SimCtx *simCtx)
Internal helper implementation: CreateAndInitializeAllVectors().
Definition setup.c:2036
PetscErrorCode ComputeAndStoreNeighborRanks(UserCtx *user)
Internal helper implementation: ComputeAndStoreNeighborRanks().
Definition setup.c:3033
PetscErrorCode Contra2Cart(UserCtx *user)
Internal helper implementation: Contra2Cart().
Definition setup.c:3300
static const char picurv_petsc_build_stamp[]
Definition setup.c:37
void TransformScalarDerivativesToPhysical(PetscReal jacobian, Cmpnts csi_metrics, Cmpnts eta_metrics, Cmpnts zet_metrics, PetscReal dPhi_dcsi, PetscReal dPhi_deta, PetscReal dPhi_dzet, Cmpnts *gradPhi)
Implementation of TransformScalarDerivativesToPhysical().
Definition setup.c:3983
static PetscErrorCode PetscMkdirRecursive(const char *path)
Create a directory path recursively using PETSc-compatible error handling.
Definition setup.c:1232
#define PICURV_STRINGIZE(x)
Definition setup.c:20
static DirectoryVerdict ClassifyLogDirectory(const char *log_dir, const char *output_dir, const char **reason)
Classify a configured log directory against the working directory.
Definition setup.c:1549
int PicurvHandleVersionArgument(int argc, char **argv, const char *executable_name)
Implementation of PicurvHandleVersionArgument().
Definition setup.c:47
PetscErrorCode InitializeLogicalSpaceRNGs(PetscInt base_seed, PetscRandom *rand_logic_i, PetscRandom *rand_logic_j, PetscRandom *rand_logic_k)
Internal helper implementation: InitializeLogicalSpaceRNGs().
Definition setup.c:3895
PetscErrorCode DestroySolutionConvergenceState(SimCtx *simCtx)
Implementation of DestroySolutionConvergenceState().
Definition setup.c:148
static PetscBool DirectoryValueIsWellFormed(const char *value)
Whether a configured directory name is safe to write to a PETSc options line.
Definition setup.c:1340
PetscErrorCode Allocate3DArrayScalar(PetscReal ****array, PetscInt nz, PetscInt ny, PetscInt nx)
Internal helper implementation: Allocate3DArrayScalar().
Definition setup.c:2742
PetscErrorCode CreateSimulationContext(int argc, char **argv, SimCtx **p_simCtx)
Implementation of CreateSimulationContext().
Definition setup.c:371
PetscErrorCode InitializeSolutionConvergenceState(SimCtx *simCtx)
Implementation of InitializeSolutionConvergenceState().
Definition setup.c:96
PetscErrorCode SetDMDAProcLayout(DM dm, UserCtx *user)
Internal helper implementation: SetDMDAProcLayout().
Definition setup.c:3149
static PetscErrorCode RepairPeriodicNormalFaceGhosts(UserCtx *user, DM dm, Vec local_vec, PetscInt dof, char face_direction, PetscBool component_staggered)
Repairs the adjacent normal ghost layer for periodic face-staggered data.
Definition setup.c:2319
PetscErrorCode ComputeScalarFieldDerivatives(UserCtx *user, PetscInt i, PetscInt j, PetscInt k, PetscReal ***field_data, Cmpnts *grad)
Internal helper implementation: ComputeScalarFieldDerivatives().
Definition setup.c:4031
PetscErrorCode ComputeDivergence(UserCtx *user)
Implementation of ComputeDivergence().
Definition setup.c:3685
static PetscBool ResolveDirectoryPhysically(const char *value, const char *cwd, char *out, size_t size, char *scratch)
Resolve a directory that may not exist yet to an absolute physical path.
Definition setup.c:1474
PetscErrorCode UpdateLocalGhosts(UserCtx *user, FieldId field_id)
Updates a catalogued field's local ghost representation.
Definition setup.c:2489
static PetscBool DirectoriesOverlap(const char *first, const char *second)
Whether two configured directories denote the same location or nest.
Definition setup.c:1289
PetscErrorCode BinarySearchInt64(PetscInt n, const PetscInt64 arr[], PetscInt64 key, PetscBool *found)
Implementation of BinarySearchInt64().
Definition setup.c:3616
static PetscErrorCode AllocateContextHierarchy(SimCtx *simCtx)
Allocate the user-context objects required by every multigrid level.
Definition setup.c:1897
PetscErrorCode Cart2Contra(UserCtx *user)
Convert a spatially varying Cartesian velocity field to contravariant fluxes.
Definition setup.c:3435
PetscErrorCode DestroyUserVectors(UserCtx *user)
Internal helper implementation: DestroyUserVectors().
Definition setup.c:4128
PetscErrorCode Allocate3DArrayVector(Cmpnts ****array, PetscInt nz, PetscInt ny, PetscInt nx)
Implementation of Allocate3DArrayVector().
Definition setup.c:2820
PetscErrorCode SetupBoundaryConditions(SimCtx *simCtx)
Internal helper implementation: SetupBoundaryConditions().
Definition setup.c:2678
#define PETSC_DIR
Definition setup.c:30
static void TransformDerivativesToPhysical(PetscReal jacobian, Cmpnts csi_metrics, Cmpnts eta_metrics, Cmpnts zet_metrics, Cmpnts deriv_csi, Cmpnts deriv_eta, Cmpnts deriv_zet, Cmpnts *dudx, Cmpnts *dvdx, Cmpnts *dwdx)
Transform contravariant vector derivatives into physical Cartesian derivatives.
Definition setup.c:4007
#define __FUNCT__
Definition setup.c:199
PetscErrorCode LESConfigSetDefaults(LESConfig *config)
Implementation of LESConfigSetDefaults().
Definition setup.c:206
PetscErrorCode SetupDomainCellDecompositionMap(UserCtx *user)
Internal helper implementation: SetupDomainCellDecompositionMap().
Definition setup.c:3551
PetscErrorCode FinalizeSimulation(SimCtx *simCtx)
Implementation of FinalizeSimulation().
Definition setup.c:4368
static PetscBool DirectoryHitsReservedName(const char *value)
Whether a directory's first path segment collides with a reserved run directory.
Definition setup.c:1358
PetscErrorCode Deallocate3DArrayScalar(PetscReal ***array, PetscInt nz, PetscInt ny)
Internal helper implementation: Deallocate3DArrayScalar().
Definition setup.c:2777
PetscBool RuntimeWalltimeGuardParsePositiveSeconds(const char *text, PetscReal *seconds_out)
Implementation of RuntimeWalltimeGuardParsePositiveSeconds().
Definition setup.c:67
Per-window PETSc accumulator storage and pointwise application.
PetscErrorCode PicurvWindowStorageCreate(UserCtx *user, const PicurvWindowDefinition *definition, PicurvWindowStorage *storage)
Allocates the accumulator state one window owns on one block.
PetscErrorCode PicurvWindowStorageDestroy(PicurvWindowStorage *storage)
Releases accumulator state previously created for one window.
Control ingress for the field-statistics pipeline.
PetscErrorCode DestroyFieldStatisticsConfig(SimCtx *simCtx)
Releases the window definitions resolved by ParseFieldStatisticsConfig().
PetscErrorCode ParseFieldStatisticsConfig(SimCtx *simCtx)
Resolves field-statistics configuration from the control file.
PicurvWindowDefinition definition
PetscBool FieldStatisticsIsActive(const struct SimCtx *simCtx)
Reports whether this run has live field-statistics state.
PetscMPIInt rank_zm
Definition variables.h:225
LESModelType
Identifies the subgrid-scale closure evaluated during a timestep.
Definition variables.h:548
@ DYNAMIC_SMAGORINSKY
Definition variables.h:551
@ NO_LES_MODEL
Definition variables.h:549
@ WALE
Definition variables.h:553
PetscReal icVelocityPhysical
Definition variables.h:921
PetscInt MHV
Definition variables.h:896
Vec lFriction_Velocity
Definition variables.h:1106
Vec lDiffusivityGradient
Definition variables.h:1117
PetscInt isc
Definition variables.h:1092
DM packer
Definition variables.h:740
PetscBool mom_nk_monitor_history
Definition variables.h:914
Vec Qcrit_nodal
Q-criterion averaged to grid nodes; the field a .vts can place correctly.
Definition variables.h:1179
Vec lPostScalar
Definition variables.h:1133
PetscInt fieldStatisticsWindowCount
Definition variables.h:932
char statistics_output_prefix[256]
basename for CSV output, e.g.
Definition variables.h:777
PetscInt movefsi
Definition variables.h:891
Vec lCent
Definition variables.h:1148
@ PERIODIC
Definition variables.h:318
Vec GridSpace
Definition variables.h:1148
PetscReal yoshizawa_ci
Yoshizawa constant for the reported SGS kinetic energy.
Definition variables.h:642
PetscBool continueMode
Definition variables.h:876
PetscInt moveframe
Definition variables.h:892
Vec P_nodal
Definition variables.h:1176
PetscInt TwoD
Definition variables.h:892
PetscInt pseudo_periodic
Definition variables.h:953
UserCtx * user
Definition variables.h:729
PetscInt ys_cell
Definition variables.h:230
PetscBool profilingFinalSummary
Definition variables.h:1036
Vec lNu_Wall
Definition variables.h:1110
char particle_output_prefix[256]
Definition variables.h:772
PetscInt xs_cell
Definition variables.h:230
PetscReal schmidt_number
Definition variables.h:948
PetscMPIInt rank
Definition variables.h:862
PetscInt dynamic_frequency
Recompute the dynamic coefficient every N steps.
Definition variables.h:630
PetscInt mglevels
Definition variables.h:1165
char profilingTimestepFile[PETSC_MAX_PATH_LEN]
Definition variables.h:1035
PetscInt LV
Heart-valve flux corrections for immersed bodies; refused at setup.
Definition variables.h:896
PetscReal angle
Definition variables.h:943
PetscReal Turbulent_schmidt_number
Definition variables.h:948
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:1099
PetscMPIInt rank_yp
Definition variables.h:224
PetscInt64 searchLocatedCount
Definition variables.h:267
LESFilterWidthModel filter_width_model
How the grid filter width Delta is derived per cell.
Definition variables.h:634
MatNullSpace nullsp
Definition variables.h:1142
PetscInt statisticsConsoleOutputFreq
Definition variables.h:934
PetscInt block_number
Definition variables.h:952
Vec lIEta
Definition variables.h:1151
PetscReal mom_rtol
Definition variables.h:901
#define PICURV_BUILD_DIRTY
Definition variables.h:38
PetscInt64 searchLostCount
Definition variables.h:268
PetscInt da_procs_z
Definition variables.h:956
PetscReal targetVolumetricFlux
Definition variables.h:967
Vec * solutionConvergencePeriodicPRef
Definition variables.h:1123
PetscBool walltimeGuardActive
Definition variables.h:1038
SNES snespacker
Definition variables.h:741
Vec lIZet
Definition variables.h:1151
LESTestFilterKernel test_filter_kernel
Discrete test-filter stencil.
Definition variables.h:635
UserCtx * user_f
Definition variables.h:1166
PetscReal mom_last_lambda_max
Definition variables.h:913
Vec lNvert
Definition variables.h:1113
PetscReal walltimeGuardWarmupTotalSeconds
Definition variables.h:1046
LESConfig les_config
Parameters of the LES closure selected by les.
Definition variables.h:987
PetscReal forceScalingFactor
Definition variables.h:961
PetscReal pseudo_cfl_reduction_factor
Definition variables.h:907
InitialConditionMode initialConditionMode
Definition variables.h:916
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:1077
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
PetscReal StartTime
Definition variables.h:873
PetscReal * solutionConvergenceMeanSpeedHistory
Definition variables.h:928
PetscReal FluxOutSum
Definition variables.h:959
PetscMPIInt rank_ym
Definition variables.h:224
PetscBool walltimeGuardHasEWMA
Definition variables.h:1048
PetscReal CMy_c
Definition variables.h:944
FlowDirection flowDirection
Definition variables.h:920
PetscMPIInt rank_xp
Definition variables.h:223
Vec Centz
Definition variables.h:1149
PetscBool runtimeMemoryLogEnabled
Enable the rank-reduced runtime memory log.
Definition variables.h:1051
char output_prefix[256]
Definition variables.h:769
char ** bcs_files
Definition variables.h:958
PetscReal boundaryVelocityCorrection
Definition variables.h:974
PetscReal max_angle
Definition variables.h:943
PetscReal min_pseudo_cfl
Definition variables.h:908
PetscInt64 boundaryClampCount
Definition variables.h:274
PetscInt ksc
Definition variables.h:1092
PetscInt particlesLostLastStep
Definition variables.h:997
PetscInt tiout
Definition variables.h:871
UserMG usermg
Definition variables.h:1015
PetscReal walltimeGuardMinSeconds
Definition variables.h:1041
char allowedFile[PETSC_MAX_PATH_LEN]
Definition variables.h:1016
Vec * solutionConvergencePeriodicUcatRef
Definition variables.h:1122
PetscInt da_procs_y
Definition variables.h:956
PetscInt64 traversalStepsSum
Definition variables.h:269
PetscBool mom_last_converged
Definition variables.h:912
PetscReal psrc_x
Definition variables.h:945
PetscReal ren
Definition variables.h:906
PetscReal iem_constant
IEM mixing constant C_IEM in Omega = C_IEM Gamma / Delta^2 (default 2.0).
Definition variables.h:949
DM post_swarm
Definition variables.h:1175
Vec lUcont_rm1
Definition variables.h:1121
Vec Nu_Wall
Definition variables.h:1110
PetscInt zm_cell
Definition variables.h:231
PetscInt solutionConvergenceSamplesRecorded
Definition variables.h:927
Cmpnts max_coords
Maximum x, y, z coordinates of the bounding box.
Definition variables.h:199
PetscInt zs_cell
Definition variables.h:230
PetscReal poissonSourceImbalance
Definition variables.h:1026
PetscBool drivenFluxTargetLatched
Definition variables.h:972
IBMVNodes * ibmv
Definition variables.h:1011
PetscInt _this
Definition variables.h:1092
Vec lKEta
Definition variables.h:1153
PetscInt64 searchPopulation
Definition variables.h:266
char output_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:881
PetscBool solutionConvergenceEnabled
Definition variables.h:923
PetscReal * solutionConvergenceMeanKEHistory
Definition variables.h:929
PetscReal walltimeGuardLatestStepSeconds
Definition variables.h:1050
PetscReal dt
Definition variables.h:874
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_CLAMP
Definition variables.h:618
@ LES_CLIP_NONE
Definition variables.h:620
PetscInt StepsToRun
Definition variables.h:870
char profilingTimestepMode[32]
Definition variables.h:1034
PetscInt k_periodic
Definition variables.h:953
PetscInt inletprofile
Definition variables.h:952
Vec Ucat_nodal
Definition variables.h:1177
RankNeighbors neighbors
Definition variables.h:1091
PetscReal bulkVelocityCorrection
Definition variables.h:973
PetscReal cdisy
Definition variables.h:906
PetscReal mom_atol
Definition variables.h:901
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
@ WALL_FUNCTION_CABOT
Definition variables.h:569
@ WALL_FUNCTION_NONE
Definition variables.h:566
PetscInt currentSettlementPass
Definition variables.h:278
PetscInt np
Definition variables.h:990
Vec PostScalarNodal
Definition variables.h:1133
PetscBool fieldStatisticsEnabled
Definition variables.h:931
PetscInt jsc
Definition variables.h:1092
PetscReal max_cs
Ceiling on Cs under LES_CLIP_CLAMP.
Definition variables.h:640
LESClipMode clip_mode
Admissible range for the coefficient.
Definition variables.h:639
PetscInt thislevel
Definition variables.h:730
PetscBool no_pseudo_cfl_backtrack
Definition variables.h:910
PetscReal C_IEM
Definition variables.h:1007
Vec DiffusivityGradient
Definition variables.h:1117
#define PICURV_GIT_COMMIT
Definition variables.h:35
Vec lJCsi
Definition variables.h:1152
PetscInt ccc
Definition variables.h:980
PetscReal ratio
Definition variables.h:981
Vec Ucont
Definition variables.h:1113
PetscInt StartStep
Definition variables.h:869
Cmpnts min_coords
Minimum x, y, z coordinates of the bounding box.
Definition variables.h:198
PetscBool OnlySetup
Definition variables.h:875
PetscInt rotatefsi
Refused at setup: immersed boundaries and moving bodies are not implemented.
Definition variables.h:891
Vec PostScalar
Definition variables.h:1133
Vec Ubcs
Physical Cartesian velocity at boundary faces. Full 3D array but only boundary-face entries are meani...
Definition variables.h:149
@ 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
PetscReal cdisz
Definition variables.h:906
Vec Qcrit
Definition variables.h:1178
PetscScalar x
Definition variables.h:122
Vec CellScalarAtCorner
Definition variables.h:1126
PetscBool averaging_direction[3]
Averaged-over logical directions (xi, eta, zeta).
Definition variables.h:638
PetscReal wale_coefficient
Model constant C_w for WALE (Nicoud & Ducros 1999).
Definition variables.h:633
PetscInt64 reSearchCount
Definition variables.h:270
char * current_io_directory
Definition variables.h:885
PetscReal MaxDiv
Definition variables.h:1027
Vec Centx
Definition variables.h:1149
char grid_file[PETSC_MAX_PATH_LEN]
Definition variables.h:955
Vec lParticleCount
Definition variables.h:1171
PetscInt invicid
Definition variables.h:892
char ** allowedFuncs
Definition variables.h:1018
PetscInt xm_cell
Definition variables.h:231
char statistics_pipeline[1024]
e.g.
Definition variables.h:776
Vec lUcont_o
Definition variables.h:1120
PetscInt64 bboxGuessFallbackCount
Definition variables.h:276
InterpolationMethod interpolationMethod
Definition variables.h:995
RankCellInfo * RankCellInfoMap
Definition variables.h:1170
PetscReal psrc_z
Point source location for PARTICLE_INIT_POINT_SOURCE.
Definition variables.h:945
Vec CellVectorAtCorner
Definition variables.h:1127
PetscInt mg_poItr
Definition variables.h:902
PetscInt ym_cell
Definition variables.h:231
VerificationScalarConfig verificationScalar
Definition variables.h:940
PetscReal max_pseudo_cfl
Definition variables.h:908
Vec Ucat_o
Definition variables.h:1120
PetscInt MaxDivx
Definition variables.h:1028
UserCtx * user_c
Definition variables.h:1166
PetscInt poisson
Definition variables.h:903
char profilingSelectedFuncsFile[PETSC_MAX_PATH_LEN]
Definition variables.h:1030
PetscInt MaxDivy
Definition variables.h:1028
char analysis_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:883
char particleRestartMode[16]
Definition variables.h:996
PetscInt64 bboxGuessSuccessCount
Definition variables.h:275
PetscInt MaxDivz
Definition variables.h:1028
BoundingBox * bboxlist
Definition variables.h:993
Vec lKZet
Definition variables.h:1153
struct PicurvWindow * fieldStatisticsWindows
Definition variables.h:933
char log_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:882
PetscInt MaxDivFlatArg
Definition variables.h:1028
Vec lNu_t
Definition variables.h:1156
PetscReal FluxInSum
Definition variables.h:959
PetscMPIInt rank_xm
Definition variables.h:223
PetscInt walltimeGuardCompletedSteps
Definition variables.h:1045
PetscInt64 maxParticlePassDepth
Definition variables.h:277
char source_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:756
Vec lCellScalarAtCorner
Definition variables.h:1126
Vec lJEta
Definition variables.h:1152
PetscInt les_gradient_model
Add the Clark gradient (tensor-diffusivity) term to the viscous flux.
Definition variables.h:986
PetscReal CMz_c
Definition variables.h:944
Vec lGridSpace
Definition variables.h:1148
PetscInt64 maxTraversalSteps
Definition variables.h:271
PetscBool generate_grid
Definition variables.h:954
PetscInt thislevel
Definition variables.h:1165
Cmpnts AnalyticalUniformVelocity
Definition variables.h:922
char eulerianSource[PETSC_MAX_PATH_LEN]
Definition variables.h:879
PetscReal imp_stol
Definition variables.h:901
PetscInt nAllowed
Definition variables.h:1019
PetscBool walltimeGuardEnabled
Definition variables.h:1037
PetscBool checkpointGeometryHashReady
Definition variables.h:887
PetscReal wall_roughness_height
Definition variables.h:947
PetscBool useProfilingSelectedFuncsCfg
Definition variables.h:1031
PetscInt walltimeGuardWarmupSteps
Definition variables.h:1039
ParticleInitializationType ParticleInitialization
Definition variables.h:994
PetscReal mom_dt_jameson_residual_norm_noise_allowance_factor
Definition variables.h:909
PetscScalar z
Definition variables.h:122
Vec pUcont
Definition variables.h:1121
InterpolationMethod
Selects the grid-to-particle interpolation method.
Definition variables.h:722
@ INTERP_TRILINEAR
Definition variables.h:723
Vec lKCsi
Definition variables.h:1153
Vec ParticleCount
Definition variables.h:1171
PetscInt diagnostics_cadence
Steps between diagnostic rows.
Definition variables.h:644
Vec Ucont_o
Definition variables.h:1120
PetscReal min_viscosity_ratio
Enforce nu + nu_t >= ratio * nu.
Definition variables.h:641
Vec lCenty
Definition variables.h:1150
PetscInt wallfunction
Enable wall functions on WALL faces.
Definition variables.h:985
PetscInt drivingForceStep
Definition variables.h:966
PetscReal Flux_in
Definition variables.h:943
Vec PostVectorNodal
Definition variables.h:1134
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
PetscReal drivenFluxMeasured
Definition variables.h:978
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
PetscReal cdisx
Definition variables.h:906
PetscInt mglevels
Definition variables.h:902
PetscBool diagnostics_enabled
Append per-step coefficient statistics to the run log directory.
Definition variables.h:643
DM packer
Definition variables.h:731
PetscReal constant_cs
Fixed Cs for CONSTANT_SMAGORINSKY; unused by the dynamic model.
Definition variables.h:631
PetscInt num_bcs_files
Definition variables.h:957
DM dm_swarm
Definition variables.h:992
PetscBool useCfg
Definition variables.h:1017
Vec Friction_Velocity
Definition variables.h:1106
PetscReal psrc_y
Definition variables.h:945
PetscInt solutionConvergenceWindowSteps
Definition variables.h:926
PetscInt central
Definition variables.h:904
PetscReal Fluxsum
Definition variables.h:959
Vec lJZet
Definition variables.h:1152
Vec Nvert_o
Definition variables.h:1120
PetscReal test_filter_width_ratio
Test-to-grid ratio per filtered direction; alpha is its square for the box, ratio^(4/3) for Simpson.
Definition variables.h:636
FlowDirection
Primary flow direction for streamwise IC and Poiseuille modes.
Definition variables.h:298
@ FLOW_DIR_UNSET
Definition variables.h:305
PetscReal pseudo_cfl_growth_factor
Definition variables.h:907
PetscBool outputParticles
Definition variables.h:764
PetscInt particlesLostCumulative
Definition variables.h:998
PetscInt nProfilingSelectedFuncs
Definition variables.h:1033
Vec lCellVectorAtCorner
Definition variables.h:1127
PetscInt particlesMigratedLastStep
Definition variables.h:1002
PetscInt particleRandomSeed
Base seed for every particle RNG stream (-particle_random_seed).
Definition variables.h:991
char initialConditionDirectory[PETSC_MAX_PATH_LEN]
Definition variables.h:918
LESAveragingMode averaging_mode
Averaging set for the Germano contractions.
Definition variables.h:637
struct PicurvWindowStorage * fieldStatisticsStorage
Definition variables.h:1136
Vec Psi_nodal
Definition variables.h:1180
char AnalyticalSolutionType[PETSC_MAX_PATH_LEN]
Definition variables.h:893
PetscInt da_procs_x
Definition variables.h:956
Vec lPostVector
Definition variables.h:1134
PetscReal walltimeGuardWarmupAverageSeconds
Definition variables.h:1047
InitialConditionMode
Selects the algorithm used to populate a fresh Eulerian velocity field.
Definition variables.h:177
@ IC_MODE_FILE
Definition variables.h:182
@ IC_MODE_ZERO
Definition variables.h:178
PetscInt particleConsoleOutputFreq
Definition variables.h:872
Cmpnts InitialConstantContra
Definition variables.h:919
Vec lCentx
Definition variables.h:1150
PetscMPIInt rank_zp
Definition variables.h:225
Vec Ucont_rm1
Definition variables.h:1121
SearchMetricsState searchMetrics
Definition variables.h:1005
PetscInt i_periodic
Definition variables.h:953
PetscReal mom_resid_rtol
Definition variables.h:901
char checkpointGeometrySHA256[65]
Definition variables.h:886
Vec lUcont
Definition variables.h:1113
PetscReal runtimeMemoryLogPreviousProcessMB
Previous local process memory sample in MB.
Definition variables.h:1055
PetscInt step
Definition variables.h:867
Vec Diffusivity
Definition variables.h:1116
PetscReal walltimeGuardEWMASeconds
Definition variables.h:1049
PetscReal AreaOutSum
Definition variables.h:979
PetscReal vreman_coefficient
Model constant c for VREMAN (Vreman 2004: 2.5 Cs^2).
Definition variables.h:632
PetscInt mom_max_pseudo_steps
Definition variables.h:900
PetscRandom BrownianMotionRNG
Definition variables.h:1006
Vec lICsi
Definition variables.h:1151
PetscReal drivenFluxArea
Definition variables.h:978
DMDALocalInfo info
Definition variables.h:1086
Vec dUcont
Definition variables.h:1121
Vec lUcat
Definition variables.h:1113
PostProcessParams * pps
Definition variables.h:1058
PetscInt migrationPassesLastStep
Definition variables.h:1001
PetscScalar y
Definition variables.h:122
InitialConditionField
Selects the authoritative velocity representation in a staged file IC.
Definition variables.h:186
@ IC_FIELD_UCONT
Definition variables.h:188
@ IC_FIELD_UCAT
Definition variables.h:187
PetscMPIInt size
Definition variables.h:863
@ EXEC_MODE_SOLVER
Definition variables.h:832
@ EXEC_MODE_POSTPROCESSOR
Definition variables.h:833
char _io_context_buffer[PETSC_MAX_PATH_LEN]
Definition variables.h:884
PetscReal walltimeGuardLimitSeconds
Definition variables.h:1044
Vec PostVector
Definition variables.h:1134
PetscBool ps_ksp_pic_monitor_true_residual
Definition variables.h:915
PetscReal walltimeGuardEstimatorAlpha
Definition variables.h:1042
PetscInt les
Active LES closure; an LESModelType value.
Definition variables.h:984
Vec lQcrit
Cell-centred Q-criterion and its ghosted copy for nodal averaging.
Definition variables.h:1178
Vec Nvert
Definition variables.h:1113
MGCtx * mgctx
Definition variables.h:739
@ SOLUTION_CONVERGENCE_TRANSIENT
Definition variables.h:705
@ SOLUTION_CONVERGENCE_PERIODIC_DETERMINISTIC
Definition variables.h:703
@ SOLUTION_CONVERGENCE_STATISTICAL_STEADY
Definition variables.h:704
@ SOLUTION_CONVERGENCE_STEADY_DETERMINISTIC
Definition variables.h:702
PetscInt mg_preItr
Definition variables.h:902
Vec lDiffusivity
Definition variables.h:1116
Vec lNvert_o
Definition variables.h:1120
PetscReal mom_ratio_ema_alpha
Definition variables.h:911
BCType mathematical_type
Definition variables.h:392
Vec Centy
Definition variables.h:1149
SolutionConvergenceMode solutionConvergenceMode
Definition variables.h:924
PetscViewer logviewer
Definition variables.h:877
Vec lCentz
Definition variables.h:1150
PetscReal particlesLostScalarLastStep
Sum of Psi over the particles removed this step.
Definition variables.h:999
PetscInt64 searchAttempts
Definition variables.h:265
InitialConditionField initialConditionField
Definition variables.h:917
ExecutionMode exec_mode
Definition variables.h:878
PetscInt64 tieBreakCount
Definition variables.h:273
PetscReal mom_resid_atol
Definition variables.h:901
BoundingBox bbox
Definition variables.h:1090
PetscBool restartHistoryAvailable
Definition variables.h:888
PetscReal ti
Definition variables.h:868
PetscReal walltimeGuardMultiplier
Definition variables.h:1040
PetscInt rotateframe
moveframe/rotateframe are refused at setup.
Definition variables.h:892
IBMNodes * ibm
Definition variables.h:1010
PetscReal AreaInSum
Definition variables.h:979
MomentumSolverType mom_solver_type
Definition variables.h:899
PetscInt immersed
Definition variables.h:891
PetscInt64 maxTraversalFailCount
Definition variables.h:272
char PostprocessingControlFile[PETSC_MAX_PATH_LEN]
Definition variables.h:1057
char restart_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:880
VerificationDiffusivityConfig verificationDiffusivity
Definition variables.h:939
PetscReal walltimeGuardJobStartEpochSeconds
Definition variables.h:1043
PetscReal pseudo_cfl
Definition variables.h:906
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_CUBE_ROOT_VOLUME
Definition variables.h:580
PetscInt LoggingFrequency
Definition variables.h:1020
#define PICURV_RELEASE_VERSION
Definition variables.h:32
PetscReal CMx_c
Definition variables.h:944
PetscReal drivingForceMagnitude
Definition variables.h:961
PetscReal particleLoadImbalance
Definition variables.h:1004
struct ParticleFieldPlan * particleFieldPlan
Configured initial particle-field values, or NULL.
Definition variables.h:1000
PetscBool fieldStatisticsContinue
Definition variables.h:938
Vec Uch
Characteristic velocity for boundary conditions.
Definition variables.h:150
@ BC_FACE_NEG_X
Definition variables.h:288
@ BC_FACE_POS_Z
Definition variables.h:290
@ BC_FACE_POS_Y
Definition variables.h:289
@ BC_FACE_NEG_Z
Definition variables.h:290
@ BC_FACE_POS_X
Definition variables.h:288
@ BC_FACE_NEG_Y
Definition variables.h:289
PetscInt j_periodic
Definition variables.h:953
FSInfo * fsi
Definition variables.h:1012
Defines a 3D axis-aligned bounding box.
Definition variables.h:197
A 3D point or vector with PetscScalar components.
Definition variables.h:121
Every user-selectable parameter of the LES closure.
Definition variables.h:629
Context for Multigrid operations.
Definition variables.h:728
Holds all configuration parameters for a post-processing run.
Definition variables.h:754
A lean struct to hold the global cell ownership range for a single MPI rank.
Definition variables.h:229
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
User-level context for managing the entire multigrid hierarchy.
Definition variables.h:735
PetscBool VerificationScalarOverrideActive(const SimCtx *simCtx)
Reports whether a verification-only scalar override is active.