PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
grid.c
Go to the documentation of this file.
1// in src/grid.c
2
3#include "grid.h"
4#include "statistics_window.h"
5#include "logging.h"
6
7#define BBOX_TOLERANCE 1e-6
8
9#undef __FUNCT__
10#define __FUNCT__ "ParseAndSetGridInputs"
11/**
12 * @brief Parse grid-generation options and store the resulting geometry settings in the context.
13 */
14static PetscErrorCode ParseAndSetGridInputs(UserCtx *user)
15{
16 PetscErrorCode ierr;
17 SimCtx *simCtx = user->simCtx; // Get the global context via the back-pointer
18
19 PetscFunctionBeginUser;
20
22 if(strcmp(simCtx->eulerianSource,"analytical")==0 &&
24 ierr = SetAnalyticalGridInfo(user); CHKERRQ(ierr);
25 } else if (simCtx->generate_grid) {
26 if (strcmp(simCtx->eulerianSource, "analytical") == 0) {
28 "Rank %d: Analytical type '%s' uses programmatic grid ingestion for block %d.\n",
29 simCtx->rank, simCtx->AnalyticalSolutionType, user->_this);
30 } else {
31 LOG_ALLOW_SYNC(GLOBAL, LOG_DEBUG, "Rank %d: Block %d is programmatically generated. Calling generation parser.\n", simCtx->rank, user->_this);
32 }
33 ierr = ReadGridGenerationInputs(user); CHKERRQ(ierr);
34 } else {
35 if (strcmp(simCtx->eulerianSource, "analytical") == 0) {
37 "Rank %d: Analytical type '%s' uses file-based grid ingestion for block %d.\n",
38 simCtx->rank, simCtx->AnalyticalSolutionType, user->_this);
39 } else {
40 LOG_ALLOW_SYNC(GLOBAL, LOG_DEBUG, "Rank %d: Block %d is file-based. Calling file parser.\n", simCtx->rank, user->_this);
41 }
42 ierr = ReadGridFile(user); CHKERRQ(ierr);
43 }
44
46
47 PetscFunctionReturn(0);
48}
49
50
51#undef __FUNCT__
52#define __FUNCT__ "DefineAllGridDimensions"
53/**
54 * @brief Internal helper implementation: `DefineAllGridDimensions()`.
55 * @details Local to this translation unit.
56 */
57PetscErrorCode DefineAllGridDimensions(SimCtx *simCtx)
58{
59 PetscErrorCode ierr;
60 PetscInt nblk = simCtx->block_number;
61 UserCtx *finest_users;
62
63 PetscFunctionBeginUser;
64
66
67 if (simCtx->usermg.mglevels == 0) {
68 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE, "MG levels not set. Cannot get finest_users.");
69 }
70 // Get the UserCtx array for the finest grid level
71 finest_users = simCtx->usermg.mgctx[simCtx->usermg.mglevels - 1].user;
72
73 LOG_ALLOW(GLOBAL, LOG_INFO, "Defining grid dimensions for %d blocks...\n", nblk);
74 if (strcmp(simCtx->eulerianSource, "analytical") == 0 &&
77 "Analytical type '%s' requires custom geometry; preloading finest-grid IM/JM/KM once.\n",
79 ierr = PopulateFinestUserGridResolutionFromOptions(finest_users, nblk); CHKERRQ(ierr);
80 }
81
82 // Loop over each block to configure its grid dimensions and geometry.
83 for (PetscInt bi = 0; bi < nblk; bi++) {
84 LOG_ALLOW_SYNC(GLOBAL, LOG_DEBUG, "Rank %d: --- Configuring Geometry for Block %d ---\n", simCtx->rank, bi);
85
86 // Before calling any helpers, set the block index in the context.
87 // This makes the UserCtx self-aware of which block it represents.
88 LOG_ALLOW(GLOBAL,LOG_DEBUG,"finest_users->_this = %d, bi = %d\n",finest_users[bi]._this,bi);
89 //finest_user[bi]._this = bi;
90
91 // Call the helper function for this specific block. It can now derive
92 // all necessary information from the UserCtx pointer it receives.
93 ierr = ParseAndSetGridInputs(&finest_users[bi]); CHKERRQ(ierr);
94 }
95
97
98 PetscFunctionReturn(0);
99}
100
101#undef __FUNCT__
102#define __FUNCT__ "CreateCompatibleBlockDM"
103/**
104 * @brief Implementation of \ref CreateCompatibleBlockDM().
105 * @details Full API contract (arguments, ownership, side effects) is documented with
106 * the header declaration in `include/grid.h`.
107 * @see CreateCompatibleBlockDM()
108 */
109PetscErrorCode CreateCompatibleBlockDM(DM source, PetscInt dof, DM *result)
110{
111 PetscErrorCode ierr;
112 PetscInt dim = 0, M = 0, N = 0, P = 0, m = 0, n = 0, p = 0, source_dof = 0, stencil_width = 0;
113 DMBoundaryType bx = DM_BOUNDARY_NONE, by = DM_BOUNDARY_NONE, bz = DM_BOUNDARY_NONE;
114 DMDAStencilType stencil_type = DMDA_STENCIL_BOX;
115 const PetscInt *lx = NULL, *ly = NULL, *lz = NULL;
116
117 PetscFunctionBeginUser;
119 PetscCheck(source != NULL && result != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
120 "Source DM and output pointer are required.");
121 PetscCheck(dof > 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
122 "A compatible DM needs a positive degree of freedom, got %" PetscInt_FMT ".", dof);
123
124 ierr = DMDAGetInfo(source, &dim, &M, &N, &P, &m, &n, &p, &source_dof, &stencil_width,
125 &bx, &by, &bz, &stencil_type); CHKERRQ(ierr);
126 ierr = DMDAGetOwnershipRanges(source, &lx, &ly, &lz); CHKERRQ(ierr);
127 ierr = DMDACreate3d(PetscObjectComm((PetscObject)source), bx, by, bz, stencil_type,
128 M, N, P, m, n, p, dof, stencil_width, lx, ly, lz, result); CHKERRQ(ierr);
129 ierr = DMSetUp(*result); CHKERRQ(ierr);
131 "Created a dof-%d DM mirroring the block decomposition (%dx%dx%d ranks).\n",
132 (int)dof, (int)m, (int)n, (int)p);
134 PetscFunctionReturn(0);
135}
136
137#undef __FUNCT__
138#define __FUNCT__ "InitializeSingleGridDM"
139/**
140 * @brief Create and configure one PETSc DMDA for a multigrid level.
141 */
142static PetscErrorCode InitializeSingleGridDM(UserCtx *user, UserCtx *coarse_user)
143{
144 PetscErrorCode ierr;
145 SimCtx *simCtx = user->simCtx;
146
147 DMBoundaryType xperiod = (simCtx->i_periodic) ? DM_BOUNDARY_PERIODIC : DM_BOUNDARY_NONE;
148 DMBoundaryType yperiod = (simCtx->j_periodic) ? DM_BOUNDARY_PERIODIC : DM_BOUNDARY_NONE;
149 DMBoundaryType zperiod = (simCtx->k_periodic) ? DM_BOUNDARY_PERIODIC : DM_BOUNDARY_NONE;
150 PetscInt stencil_width = (simCtx->i_periodic || simCtx->j_periodic || simCtx->k_periodic) ? 3:2; // Stencil width is 2 in the legacy code
151
152 PetscInt *lx = NULL, *ly = NULL, *lz = NULL;
153 PetscInt m, n, p;
154
155 PetscFunctionBeginUser;
156
158
159 if (coarse_user) {
160 // --- This is a FINE grid; it must be aligned with the COARSE grid ---
161 LOG_ALLOW_SYNC(LOCAL, LOG_DEBUG, "Rank %d: [Aligning DM] for block %d level %d (size %dx%dx%d) with level %d\n", simCtx->rank, user->_this, user->thislevel, user->IM, user->JM, user->KM, coarse_user->thislevel);
162
163 DMDAGetInfo(coarse_user->da, NULL, NULL, NULL, NULL, &m, &n, &p, NULL, NULL, NULL, NULL, NULL, NULL);
164 LOG_ALLOW_SYNC(LOCAL, LOG_TRACE, "Rank %d: Coarse grid processor decomposition is %d x %d x %d\n", simCtx->rank, m, n, p);
165
166 // This is the core logic from MGDACreate to ensure processor alignment.
167 PetscInt *lx_contrib, *ly_contrib, *lz_contrib;
168 ierr = PetscMalloc3(m, &lx_contrib, n, &ly_contrib, p, &lz_contrib); CHKERRQ(ierr);
169 ierr = PetscMemzero(lx_contrib, m * sizeof(PetscInt)); CHKERRQ(ierr);
170 ierr = PetscMemzero(ly_contrib, n * sizeof(PetscInt)); CHKERRQ(ierr);
171 ierr = PetscMemzero(lz_contrib, p * sizeof(PetscInt)); CHKERRQ(ierr);
172
173 DMDALocalInfo info;
174 DMDAGetLocalInfo(coarse_user->da, &info);
175 PetscInt xs = info.xs, xe = info.xs + info.xm, mx = info.mx;
176 PetscInt ys = info.ys, ye = info.ys + info.ym, my = info.my;
177 PetscInt zs = info.zs, ze = info.zs + info.zm, mz = info.mz;
178
179 PetscMPIInt rank;
180 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
181 PetscInt proc_i = rank % m;
182 PetscInt proc_j = (rank / m) % n;
183 PetscInt proc_k = rank / (m * n);
184
185 // --- X-Direction Logic (Identical to MGDACreate) ---
186 if (user->isc) lx_contrib[proc_i] = (xe - xs);
187 else {
188 if (m == 1) lx_contrib[0] = user->IM + 1;
189 else if (xs == 0) lx_contrib[0] = 2 * xe - 1;
190 else if (xe == mx) lx_contrib[proc_i] = user->IM + 1 - (2 * xs - 1);
191 else lx_contrib[proc_i] = (xe - xs) * 2;
192 }
193
194 // --- Y-Direction Logic (Identical to MGDACreate) ---
195 if (user->jsc) ly_contrib[proc_j] = (ye - ys);
196 else {
197 if (n == 1) ly_contrib[0] = user->JM + 1;
198 else if (ys == 0) ly_contrib[0] = 2 * ye - 1;
199 else if (ye == my) ly_contrib[proc_j] = user->JM + 1 - (2 * ys - 1);
200 else ly_contrib[proc_j] = (ye - ys) * 2;
201 }
202
203 // --- Z-Direction Logic (Identical to MGDACreate) ---
204 if (user->ksc) lz_contrib[proc_k] = (ze - zs);
205 else {
206 if (p == 1) lz_contrib[0] = user->KM + 1;
207 else if (zs == 0) lz_contrib[0] = 2 * ze - 1;
208 else if (ze == mz) lz_contrib[proc_k] = user->KM + 1 - (2 * zs - 1);
209 else lz_contrib[proc_k] = (ze - zs) * 2;
210 }
211 LOG_ALLOW_SYNC(LOCAL, LOG_VERBOSE, "Rank %d: Calculated this rank's node contribution to fine grid: lx=%d, ly=%d, lz=%d\n", simCtx->rank, lx_contrib[proc_i], ly_contrib[proc_j], lz_contrib[proc_k]);
212
213 // Allocate the final distribution arrays and Allreduce to get the global distribution
214 ierr = PetscMalloc3(m, &lx, n, &ly, p, &lz); CHKERRQ(ierr);
215 ierr = MPI_Allreduce(lx_contrib, lx, m, MPIU_INT, MPI_MAX, PETSC_COMM_WORLD); CHKERRQ(ierr);
216 ierr = MPI_Allreduce(ly_contrib, ly, n, MPIU_INT, MPI_MAX, PETSC_COMM_WORLD); CHKERRQ(ierr);
217 ierr = MPI_Allreduce(lz_contrib, lz, p, MPIU_INT, MPI_MAX, PETSC_COMM_WORLD); CHKERRQ(ierr);
218
219 ierr = PetscFree3(lx_contrib, ly_contrib, lz_contrib); CHKERRQ(ierr);
220
221 } else {
222 // --- CASE 2: This is the COARSEST grid; use default or user-specified decomposition ---
223 if(simCtx->exec_mode == EXEC_MODE_SOLVER){
224
225 LOG_ALLOW_SYNC(LOCAL, LOG_DEBUG, "Rank %d: Creating coarsest DM for block %d level %d (size %dx%dx%d)\n", simCtx->rank, user->_this, user->thislevel, user->IM, user->JM, user->KM);
226 m = simCtx->da_procs_x;
227 n = simCtx->da_procs_y;
228 p = simCtx->da_procs_z;
229
230 } else if(simCtx->exec_mode == EXEC_MODE_POSTPROCESSOR){
231
232 LOG_ALLOW(GLOBAL,LOG_ERROR,"Currently Only Single Rank is supported. \n");
233
234 m = n = p = PETSC_DECIDE;
235
236 }
237 // lx, ly, lz are NULL, so DMDACreate3d will use the m,n,p values.
238 }
239
240 // --- Create the DMDA for the current UserCtx ---
241 LOG_ALLOW_SYNC(LOCAL, LOG_DEBUG, "Rank %d: Calling DMDACreate3d...\n", simCtx->rank);
242 ierr = DMDACreate3d(PETSC_COMM_WORLD, xperiod, yperiod, zperiod, DMDA_STENCIL_BOX,
243 user->IM + 1, user->JM + 1, user->KM + 1,
244 m, n, p,
245 1, stencil_width, lx, ly, lz, &user->da); CHKERRQ(ierr);
246
247 if (coarse_user) {
248 ierr = PetscFree3(lx, ly, lz); CHKERRQ(ierr);
249 }
250
251 // --- Standard DM setup applicable to all levels ---
252 ierr = DMSetUp(user->da); CHKERRQ(ierr);
253 ierr = DMGetCoordinateDM(user->da, &user->fda); CHKERRQ(ierr);
254 /* The symmetric-tensor DM exists only to carry statistics products, and only the
255 * finest level accumulates them. Configuration is resolved before grid setup, so
256 * a run without statistics never builds it. */
257 if (FieldStatisticsIsActive(simCtx) && user->thislevel == simCtx->mglevels - 1) {
258 ierr = CreateCompatibleBlockDM(user->da, 6, &user->fda6); CHKERRQ(ierr);
259 }
260 ierr = DMDASetUniformCoordinates(user->da, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0); CHKERRQ(ierr);
261 ierr = DMDAGetLocalInfo(user->da, &user->info); CHKERRQ(ierr);
262 LOG_ALLOW_SYNC(LOCAL, LOG_DEBUG, "Rank %d: DM creation for block %d level %d complete.\n", simCtx->rank, user->_this, user->thislevel);
263
265
266 PetscFunctionReturn(0);
267}
268
269
270#undef __FUNCT__
271#define __FUNCT__ "InitializeAllGridDMs"
272/**
273 * @brief Internal helper implementation: `InitializeAllGridDMs()`.
274 * @details Local to this translation unit.
275 */
276PetscErrorCode InitializeAllGridDMs(SimCtx *simCtx)
277{
278 PetscErrorCode ierr;
279 UserMG *usermg = &simCtx->usermg;
280 MGCtx *mgctx = usermg->mgctx;
281 PetscInt nblk = simCtx->block_number;
282
283 PetscFunctionBeginUser;
284
286
287 LOG_ALLOW(GLOBAL,LOG_INFO, "Pre-scanning BCs to identify domain periodicity.\n");
288 ierr = DeterminePeriodicity(simCtx); CHKERRQ(ierr);
289
290 LOG_ALLOW(GLOBAL, LOG_INFO, "Creating DMDA objects for all levels and blocks...\n");
291
292 // --- Part 1: Calculate Coarse Grid Dimensions & VALIDATE ---
293 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Calculating and validating coarse grid dimensions...\n");
294 for (PetscInt level = usermg->mglevels - 2; level >= 0; level--) {
295 for (PetscInt bi = 0; bi < nblk; bi++) {
296 UserCtx *user_coarse = &mgctx[level].user[bi];
297 UserCtx *user_fine = &mgctx[level + 1].user[bi];
298
299 user_coarse->IM = user_fine->isc ? user_fine->IM : (user_fine->IM + 1) / 2;
300 user_coarse->JM = user_fine->jsc ? user_fine->JM : (user_fine->JM + 1) / 2;
301 user_coarse->KM = user_fine->ksc ? user_fine->KM : (user_fine->KM + 1) / 2;
302
303 LOG_ALLOW_SYNC(LOCAL, LOG_TRACE, "Rank %d: Block %d, Level %d dims calculated: %d x %d x %d\n",
304 simCtx->rank, bi, level, user_coarse->IM, user_coarse->JM, user_coarse->KM);
305
306 // Validation check from legacy MGDACreate to ensure coarsening is possible
307 PetscInt check_i = user_coarse->IM * (2 - user_coarse->isc) - (user_fine->IM + 1 - user_coarse->isc);
308 PetscInt check_j = user_coarse->JM * (2 - user_coarse->jsc) - (user_fine->JM + 1 - user_coarse->jsc);
309 PetscInt check_k = user_coarse->KM * (2 - user_coarse->ksc) - (user_fine->KM + 1 - user_coarse->ksc);
310
311 if (check_i + check_j + check_k != 0) {
312 // SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
313 // "Grid at level %d, block %d cannot be coarsened from %dx%dx%d to %dx%dx%d with the given semi-coarsening flags. Check grid dimensions.",
314 // level, bi, user_fine->IM, user_fine->JM, user_fine->KM, user_coarse->IM, user_coarse->JM, user_coarse->KM);
315 LOG(GLOBAL,LOG_WARNING,"WARNING: Grid at level %d, block %d can't be consistently coarsened further.\n", level, bi);
316 }
317 }
318 }
319
320 // --- Part 2: Create DMs from Coarse to Fine for each Block ---
321 for (PetscInt bi = 0; bi < nblk; bi++) {
322 LOG_ALLOW_SYNC(GLOBAL, LOG_DEBUG, "--- Creating DMs for Block %d ---\n", bi);
323
324 // Create the coarsest level DM first (passing NULL for the coarse_user)
325 ierr = InitializeSingleGridDM(&mgctx[0].user[bi], NULL); CHKERRQ(ierr);
326
327 // Create finer level DMs, passing the next-coarser context for alignment
328 for (PetscInt level = 1; level < usermg->mglevels; level++) {
329 ierr = InitializeSingleGridDM(&mgctx[level].user[bi], &mgctx[level-1].user[bi]); CHKERRQ(ierr);
330 }
331 }
332
333 // --- Optional: View the finest DM for debugging verification ---
334 if (get_log_level() >= LOG_DEBUG) {
335 LOG_ALLOW_SYNC(GLOBAL, LOG_INFO, "--- Viewing Finest DMDA (Level %d, Block 0) ---\n", usermg->mglevels - 1);
336 ierr = DMView(mgctx[usermg->mglevels - 1].user[0].da, PETSC_VIEWER_STDOUT_WORLD); CHKERRQ(ierr);
337 }
338
339 LOG_ALLOW(GLOBAL, LOG_INFO, "DMDA object creation complete.\n");
340
342
343 PetscFunctionReturn(0);
344}
345
346// Forward declarations for the static helper functions within this file.
347static PetscErrorCode SetFinestLevelCoordinates(UserCtx *user);
348static PetscErrorCode GenerateAndSetCoordinates(UserCtx *user);
349static PetscErrorCode ReadAndSetCoordinates(UserCtx *user, FILE *fd);
350static PetscErrorCode RestrictCoordinates(UserCtx *coarse_user, UserCtx *fine_user);
351
352#undef __FUNCT__
353#define __FUNCT__ "AssignAllGridCoordinates"
354/**
355 * @brief Internal helper implementation: `AssignAllGridCoordinates()`.
356 * @details Local to this translation unit.
357 */
358PetscErrorCode AssignAllGridCoordinates(SimCtx *simCtx)
359{
360 PetscErrorCode ierr;
361 UserMG *usermg = &simCtx->usermg;
362 PetscInt nblk = simCtx->block_number;
363
364 PetscFunctionBeginUser;
365
367
368 LOG_ALLOW(GLOBAL, LOG_INFO, "Assigning physical coordinates to all grid DMs...\n");
369
370 // --- Part 1: Populate the Finest Grid Level ---
371 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Setting coordinates for the finest grid level (%d)...\n", usermg->mglevels - 1);
372 for (PetscInt bi = 0; bi < nblk; bi++) {
373 UserCtx *fine_user = &usermg->mgctx[usermg->mglevels - 1].user[bi];
374 ierr = SetFinestLevelCoordinates(fine_user); CHKERRQ(ierr);
375 LOG_ALLOW(GLOBAL,LOG_TRACE,"The Finest level coordinates for block %d have been set.\n",bi);
377 ierr = LOG_FIELD_MIN_MAX(fine_user, FIELD_ID_COORDINATES);
378 }
379 }
380 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Finest level coordinates have been set for all blocks.\n");
381
382 // --- Part 2: Restrict Coordinates to Coarser Levels ---
383 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Restricting coordinates to coarser grid levels...\n");
384 for (PetscInt level = usermg->mglevels - 2; level >= 0; level--) {
385 for (PetscInt bi = 0; bi < nblk; bi++) {
386 UserCtx *coarse_user = &usermg->mgctx[level].user[bi];
387 UserCtx *fine_user = &usermg->mgctx[level + 1].user[bi];
388 ierr = RestrictCoordinates(coarse_user, fine_user); CHKERRQ(ierr);
389
390 LOG_ALLOW(GLOBAL,LOG_TRACE,"Coordinates restricted to block %d level %d.\n",bi,level);
392 ierr = LOG_FIELD_MIN_MAX(coarse_user, FIELD_ID_COORDINATES);
393 }
394 }
395 }
396
397 LOG_ALLOW(GLOBAL, LOG_INFO, "Physical coordinates assigned to all grid levels and blocks.\n");
398
400
401 PetscFunctionReturn(0);
402}
403
404/**
405 * @brief Returns one Cartesian component from a coordinate/vector value.
406 */
407static inline PetscReal CoordinateComponent(Cmpnts value, PetscInt component)
408{
409 if (component == 0) return value.x;
410 if (component == 1) return value.y;
411 return value.z;
412}
413
414#undef __FUNCT__
415#define __FUNCT__ "ValidatePeriodicGeometry"
416/**
417 * @brief Implementation of \ref ValidatePeriodicGeometry().
418 * @details Full API contract is documented with the header declaration in
419 * `include/grid.h`.
420 */
422{
423 const BCFace neg_faces[3] = {BC_FACE_NEG_X, BC_FACE_NEG_Y, BC_FACE_NEG_Z};
424 const BCFace pos_faces[3] = {BC_FACE_POS_X, BC_FACE_POS_Y, BC_FACE_POS_Z};
425 const char axis_names[3] = {'X', 'Y', 'Z'};
426 const Cmpnts ***coor = NULL;
427 Vec lcoor = NULL;
428 DMDALocalInfo info;
429
430 PetscFunctionBeginUser;
432 PetscCall(DMDAGetLocalInfo(user->da, &info));
433 PetscCall(DMGetCoordinatesLocal(user->da, &lcoor));
434 PetscCheck(lcoor != NULL, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE,
435 "Cannot validate periodic geometry before local coordinates are assigned.");
436 for (PetscInt axis = 0; axis < 3; axis++) {
437 const PetscBool neg_periodic =
438 user->boundary_faces[neg_faces[axis]].mathematical_type == PERIODIC;
439 const PetscBool pos_periodic =
440 user->boundary_faces[pos_faces[axis]].mathematical_type == PERIODIC;
441 PetscReal local_min[3] = {PETSC_MAX_REAL, PETSC_MAX_REAL, PETSC_MAX_REAL};
442 PetscReal local_max[3] = {-PETSC_MAX_REAL, -PETSC_MAX_REAL, -PETSC_MAX_REAL};
443 PetscReal global_min[3], global_max[3];
444 PetscInt local_count = 0, global_count = 0;
445
446 user->periodic_translation_valid[axis] = PETSC_FALSE;
447 user->periodic_translation[axis] = (Cmpnts){0.0, 0.0, 0.0};
448 if (!neg_periodic && !pos_periodic) continue;
449
450 PetscCheck(neg_periodic && pos_periodic, PETSC_COMM_WORLD, PETSC_ERR_USER_INPUT,
451 "Periodic geometry in the %c direction requires paired negative and positive faces.",
452 axis_names[axis]);
453 const PetscInt axis_size = axis == 0 ? info.mx : (axis == 1 ? info.my : info.mz);
454 PetscCheck(axis_size >= 5, PETSC_COMM_WORLD, PETSC_ERR_USER_INPUT,
455 "%c-periodic geometry on block %d level %d requires at least four physical "
456 "nodes in that direction; found %d.",
457 axis_names[axis], user->_this, user->thislevel, axis_size - 1);
458
459 PetscCall(DMDAVecGetArrayRead(user->fda, lcoor, &coor));
460 if (axis == 0 && info.xs == 0) {
461 for (PetscInt k = PetscMax(info.zs, 0); k < PetscMin(info.zs + info.zm, info.mz - 1); k++) {
462 for (PetscInt j = PetscMax(info.ys, 0); j < PetscMin(info.ys + info.ym, info.my - 1); j++) {
463 const Cmpnts delta = {
464 coor[k][j][-2].x - coor[k][j][0].x,
465 coor[k][j][-2].y - coor[k][j][0].y,
466 coor[k][j][-2].z - coor[k][j][0].z
467 };
468 for (PetscInt c = 0; c < 3; c++) {
469 local_min[c] = PetscMin(local_min[c], CoordinateComponent(delta, c));
470 local_max[c] = PetscMax(local_max[c], CoordinateComponent(delta, c));
471 }
472 local_count++;
473 }
474 }
475 } else if (axis == 1 && info.ys == 0) {
476 for (PetscInt k = PetscMax(info.zs, 0); k < PetscMin(info.zs + info.zm, info.mz - 1); k++) {
477 for (PetscInt i = PetscMax(info.xs, 0); i < PetscMin(info.xs + info.xm, info.mx - 1); i++) {
478 const Cmpnts delta = {
479 coor[k][-2][i].x - coor[k][0][i].x,
480 coor[k][-2][i].y - coor[k][0][i].y,
481 coor[k][-2][i].z - coor[k][0][i].z
482 };
483 for (PetscInt c = 0; c < 3; c++) {
484 local_min[c] = PetscMin(local_min[c], CoordinateComponent(delta, c));
485 local_max[c] = PetscMax(local_max[c], CoordinateComponent(delta, c));
486 }
487 local_count++;
488 }
489 }
490 } else if (axis == 2 && info.zs == 0) {
491 for (PetscInt j = PetscMax(info.ys, 0); j < PetscMin(info.ys + info.ym, info.my - 1); j++) {
492 for (PetscInt i = PetscMax(info.xs, 0); i < PetscMin(info.xs + info.xm, info.mx - 1); i++) {
493 const Cmpnts delta = {
494 coor[-2][j][i].x - coor[0][j][i].x,
495 coor[-2][j][i].y - coor[0][j][i].y,
496 coor[-2][j][i].z - coor[0][j][i].z
497 };
498 for (PetscInt c = 0; c < 3; c++) {
499 local_min[c] = PetscMin(local_min[c], CoordinateComponent(delta, c));
500 local_max[c] = PetscMax(local_max[c], CoordinateComponent(delta, c));
501 }
502 local_count++;
503 }
504 }
505 }
506 PetscCall(DMDAVecRestoreArrayRead(user->fda, lcoor, &coor));
507
508 PetscCallMPI(MPI_Allreduce(local_min, global_min, 3, MPIU_REAL, MPI_MIN, PETSC_COMM_WORLD));
509 PetscCallMPI(MPI_Allreduce(local_max, global_max, 3, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD));
510 PetscCallMPI(MPI_Allreduce(&local_count, &global_count, 1, MPIU_INT, MPI_SUM, PETSC_COMM_WORLD));
511 PetscCheck(global_count > 0, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE,
512 "No physical seam nodes were available to validate %c-periodic geometry.",
513 axis_names[axis]);
514
515 PetscReal translation[3];
516 PetscReal scale = 1.0;
517 PetscReal max_mismatch = 0.0;
518 for (PetscInt c = 0; c < 3; c++) {
519 translation[c] = 0.5 * (global_min[c] + global_max[c]);
520 scale = PetscMax(scale, PetscAbsReal(translation[c]));
521 max_mismatch = PetscMax(max_mismatch, global_max[c] - global_min[c]);
522 }
523 const PetscReal tolerance = 1.0e-9 * scale + 100.0 * PETSC_MACHINE_EPSILON;
524 const PetscReal magnitude = PetscSqrtReal(
525 PetscSqr(translation[0]) + PetscSqr(translation[1]) + PetscSqr(translation[2]));
526
527 PetscCheck(max_mismatch <= tolerance, PETSC_COMM_WORLD, PETSC_ERR_USER_INPUT,
528 "Unsupported %c-periodic geometry on block %d level %d: opposite physical "
529 "surfaces are not related by one constant translation. Maximum component "
530 "mismatch is %.12e (tolerance %.12e).",
531 axis_names[axis], user->_this, user->thislevel,
532 (double)max_mismatch, (double)tolerance);
533 PetscCheck(magnitude > tolerance, PETSC_COMM_WORLD, PETSC_ERR_USER_INPUT,
534 "Unsupported %c-periodic geometry on block %d level %d: seam translation "
535 "magnitude %.12e is zero or too small.",
536 axis_names[axis], user->_this, user->thislevel, (double)magnitude);
537
538 user->periodic_translation[axis] =
539 (Cmpnts){translation[0], translation[1], translation[2]};
540 user->periodic_translation_valid[axis] = PETSC_TRUE;
542 "Validated %c-periodic geometry for block %d level %d with translation "
543 "(%.12e, %.12e, %.12e).\n",
544 axis_names[axis], user->_this, user->thislevel,
545 (double)translation[0], (double)translation[1], (double)translation[2]);
546 }
547
549 PetscFunctionReturn(0);
550}
551
552
553#undef __FUNCT__
554#define __FUNCT__ "SetFinestLevelCoordinates"
555/**
556 * @brief Attach the generated physical coordinates to the finest-level DMDA.
557 */
558static PetscErrorCode SetFinestLevelCoordinates(UserCtx *user)
559{
560 PetscErrorCode ierr;
561 SimCtx *simCtx = user->simCtx;
562
563 PetscFunctionBeginUser;
564
566
567 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Setting finest level coordinates for block %d...\n", simCtx->rank, user->_this);
568
569 if (simCtx->generate_grid) {
570 ierr = GenerateAndSetCoordinates(user); CHKERRQ(ierr);
571 } else {
572
573 FILE *grid_file_handle = NULL;
574 // Only Rank 0 opens the file.
575 if (simCtx->rank == 0) {
576 grid_file_handle = fopen(simCtx->grid_file, "r");
577 if (!grid_file_handle) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Cannot open file: %s", simCtx->grid_file);
578
579 // Now, on Rank 0, we skip the entire header section once.
580 // This is the logic from your modern code's AssignGridCoordinates.
581 PetscInt headerLines = simCtx->block_number + 2; // 1 for nblk, plus one for each block's dims
582 char dummy_buffer[2048];
583 for (PetscInt s = 0; s < headerLines; ++s) {
584 if (!fgets(dummy_buffer, sizeof(dummy_buffer), grid_file_handle)) {
585 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "Unexpected EOF while skipping grid header");
586 }
587 }
588 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank 0: Skipped %d header lines, now at coordinate data.\n", headerLines);
589 }
590
591 // We now call the coordinate reader, passing the file handle.
592 // It's responsible for reading its block's data and broadcasting.
593 ierr = ReadAndSetCoordinates(user, grid_file_handle); CHKERRQ(ierr);
594
595 // Only Rank 0, which opened the file, should close it.
596 if (simCtx->rank == 0) {
597 fclose(grid_file_handle);
598 }
599 }
600
601 // Populate local ghost coordinates from the owned global coordinates.
602 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Scattering coordinates to update ghost nodes for block %d...\n", simCtx->rank, user->_this);
603 ierr = UpdateLocalGhosts(user, FIELD_ID_COORDINATES); CHKERRQ(ierr);
604
606
607 PetscFunctionReturn(0);
608}
609/**
610 * @brief Map a uniform logical coordinate to its configured stretched physical coordinate.
611 */
612static inline PetscReal ComputeStretchedCoord(PetscInt i, PetscInt N, PetscReal L, PetscReal r)
613{
614 if (N <=1) return 0.0;
615 PetscReal fraction = (PetscReal)i / ((PetscReal)N - 1.0);
616 if (PetscAbsReal(r - 1.0) < 1.0e-9) { // Use a tolerance for float comparison
617 return L * fraction;
618 } else {
619 return L * (PetscPowReal(r, fraction) - 1.0) / (r - 1.0);
620 }
621}
622
623#undef __FUNCT__
624#define __FUNCT__ "GenerateAndSetCoordinates"
625/**
626 * @brief Generate analytic grid coordinates and install them on the finest grid level.
627 */
628static PetscErrorCode GenerateAndSetCoordinates(UserCtx *user)
629{
630 PetscErrorCode ierr;
631 DMDALocalInfo info;
632 Cmpnts ***coor;
633 Vec gCoor;
634
635 PetscFunctionBeginUser;
636
638
639 LOG_ALLOW_SYNC(LOCAL, LOG_DEBUG, "Rank %d: Generating coordinates for block %d...\n", user->simCtx->rank, user->_this);
640
641 ierr = DMDAGetLocalInfo(user->da, &info); CHKERRQ(ierr);
642 ierr = DMGetCoordinates(user->da, &gCoor); CHKERRQ(ierr);
643
644 PetscInt xs = info.xs, xe = info.xs + info.xm;
645 PetscInt ys = info.ys, ye = info.ys + info.ym;
646 PetscInt zs = info.zs, ze = info.zs + info.zm;
647
648 LOG_ALLOW_SYNC(LOCAL, LOG_TRACE, "Rank %d: Local Info for block %d - X range - [%d,%d], Y range - [%d,%d], Z range - [%d,%d]\n",
649 user->simCtx->rank, user->_this, xs, xe, ys, ye, zs, ze);
650
651 LOG_ALLOW_SYNC(LOCAL, LOG_TRACE, "Rank %d: Local Info for block %d - X domain - [%.4f,%.4f], Y range - [%.4f,%.4f], Z range - [%.4f,%.4f]\n",
652 user->simCtx->rank, user->_this, user->Min_X,user->Max_X,user->Min_Y,user->Max_Y,user->Min_Z,user->Max_Z);
653
654 ierr = VecSet(gCoor, 0.0); CHKERRQ(ierr);
655 ierr = DMDAVecGetArray(user->fda, gCoor, &coor); CHKERRQ(ierr);
656
657 PetscReal Lx = user->Max_X - user->Min_X;
658 PetscReal Ly = user->Max_Y - user->Min_Y;
659 PetscReal Lz = user->Max_Z - user->Min_Z;
660
661 // Loop over the nodes owned by this process.
662 for (PetscInt k = zs; k < ze; k++) {
663 for (PetscInt j = ys; j < ye; j++) {
664 for (PetscInt i = xs; i < xe; i++) {
665 if(k < user->KM && j < user->JM && i < user->IM){
666 coor[k][j][i].x = user->Min_X + ComputeStretchedCoord(i, user->IM, Lx, user->rx);
667 coor[k][j][i].y = user->Min_Y + ComputeStretchedCoord(j, user->JM, Ly, user->ry);
668 coor[k][j][i].z = user->Min_Z + ComputeStretchedCoord(k, user->KM, Lz, user->rz);
669 }
670 }
671 }
672 }
673
674 /// DEBUG: This verifies the presence of a last "unphysical" layer of coordinates.
675 /*
676 PetscInt KM = user->KM;
677 for (PetscInt j = ys; j < ye; j++){
678 for(PetscInt i = xs; i < xe; i++){
679 LOG_ALLOW(GLOBAL,LOG_DEBUG,"coor[%d][%d][%d].(x,y,z) = %le,%le,%le",KM,j,i,coor[KM][j][i].x,coor[KM][j][i].y,coor[KM][j][i].z);
680 }
681 }
682 */
683
684
685
686 ierr = DMDAVecRestoreArray(user->fda, gCoor, &coor); CHKERRQ(ierr);
687
689
690 PetscFunctionReturn(0);
691}
692#undef __FUNCT__
693#define __FUNCT__ "ReadAndSetCoordinates"
694/**
695 * @brief Load grid coordinates from the input file and install them on the finest level.
696 */
697static PetscErrorCode ReadAndSetCoordinates(UserCtx *user, FILE *fd)
698{
699 PetscErrorCode ierr;
700 SimCtx *simCtx = user->simCtx;
701 PetscMPIInt rank = simCtx->rank;
702 PetscInt block_index = user->_this;
703 PetscInt IM = user->IM, JM = user->JM, KM = user->KM;
704 DMDALocalInfo info;
705 Cmpnts ***coor;
706 Vec gCoor;
707 PetscReal *gc = NULL; // Global coordinate buffer, allocated on all ranks
708
709 PetscFunctionBeginUser;
710
712
713 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Reading interleaved coordinates from file for block %d...\n",
714 simCtx->rank, block_index);
715
716 // 1. Allocate the buffer on ALL ranks to receive the broadcast data.
717 // PetscInt n_nodes = (IM + 1) * (JM + 1) * (KM + 1);
718 PetscInt n_nodes = (IM) * (JM) * (KM);
719 ierr = PetscMalloc1(3 * n_nodes, &gc); CHKERRQ(ierr);
720
721 // 2. Only Rank 0 opens the file and reads the data.
722 if (rank == 0) {
723 if (!fd) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Recieved a NULL file handle.\n");
724
725 // Read the coordinate data for the CURRENT block.
726 for (PetscInt k = 0; k < KM; k++) {
727 for (PetscInt j = 0; j < JM; j++) {
728 for (PetscInt i = 0; i < IM; i++) {
729 PetscInt base_index = 3 * ((k * (JM) + j) * (IM) + i);
730 if (fscanf(fd, "%le %le %le\n", &gc[base_index], &gc[base_index + 1], &gc[base_index + 2]) != 3) {
731 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "Error reading coordinates for node (i,j,k)=(%d,%d,%d) in block %d", i, j, k, block_index);
732 }
733 }
734 }
735 }
736
737 }
738
739 // 3. Broadcast the coordinate block for the current block to all other processes.
740 ierr = MPI_Bcast(gc, 3 * n_nodes, MPIU_REAL, 0, PETSC_COMM_WORLD); CHKERRQ(ierr);
741
742 // 4. Each rank populates its owned portion of the global coordinate vector.
743 ierr = DMDAGetLocalInfo(user->da, &info); CHKERRQ(ierr);
744 ierr = DMGetCoordinates(user->da, &gCoor); CHKERRQ(ierr);
745 ierr = VecSet(gCoor, 0.0); CHKERRQ(ierr);
746 ierr = DMDAVecGetArray(user->fda, gCoor, &coor); CHKERRQ(ierr);
747
748 for (PetscInt k = info.zs; k < info.zs + info.zm; k++) {
749 for (PetscInt j = info.ys; j < info.ys + info.ym; j++) {
750 for (PetscInt i = info.xs; i < info.xs + info.xm; i++) {
751 if(k< KM && j < JM && i < IM){
752 PetscInt base_idx = 3 * ((k * (JM) + j) * (IM) + i);
753 coor[k][j][i].x = gc[base_idx];
754 coor[k][j][i].y = gc[base_idx + 1];
755 coor[k][j][i].z = gc[base_idx + 2];
756 }
757 }
758 }
759 }
760
761 // 5. Clean up and restore.
762 ierr = DMDAVecRestoreArray(user->fda, gCoor, &coor); CHKERRQ(ierr);
763 ierr = PetscFree(gc); CHKERRQ(ierr);
764
766
767 PetscFunctionReturn(0);
768}
769
770#undef __FUNCT__
771#define __FUNCT__ "RestrictCoordinates"
772/**
773 * @brief Restrict finest-grid coordinates onto each coarser multigrid level.
774 */
775static PetscErrorCode RestrictCoordinates(UserCtx *coarse_user, UserCtx *fine_user)
776{
777 PetscErrorCode ierr;
778 Vec c_gCoor, f_lCoor;
779 Cmpnts ***c_coor;
780 const Cmpnts ***f_coor; // Use const for read-only access
781 DMDALocalInfo c_info;
782 PetscInt ih, jh, kh; // Fine-grid indices corresponding to coarse-grid i,j,k
783
784 PetscFunctionBeginUser;
785
787
788 LOG_ALLOW_SYNC(LOCAL, LOG_DEBUG, "Rank %d: Restricting coords from level %d to level %d for block %d\n",
789 fine_user->simCtx->rank, fine_user->thislevel, coarse_user->thislevel, coarse_user->_this);
790
791 ierr = DMDAGetLocalInfo(coarse_user->da, &c_info); CHKERRQ(ierr);
792
793 ierr = DMGetCoordinates(coarse_user->da, &c_gCoor); CHKERRQ(ierr);
794 ierr = DMGetCoordinatesLocal(fine_user->da, &f_lCoor); CHKERRQ(ierr);
795
796 ierr = VecSet(c_gCoor, 0.0); CHKERRQ(ierr);
797 ierr = DMDAVecGetArray(coarse_user->fda, c_gCoor, &c_coor); CHKERRQ(ierr);
798 ierr = DMDAVecGetArrayRead(fine_user->fda, f_lCoor, &f_coor); CHKERRQ(ierr);
799
800 // Get the local owned range of the coarse grid.
801 PetscInt xs = c_info.xs, xe = c_info.xs + c_info.xm;
802 PetscInt ys = c_info.ys, ye = c_info.ys + c_info.ym;
803 PetscInt zs = c_info.zs, ze = c_info.zs + c_info.zm;
804
805 // Get the global dimensions of the coarse grid.
806 PetscInt mx = c_info.mx, my = c_info.my, mz = c_info.mz;
807
808 // If this process owns the maximum boundary node, contract the loop by one
809 // to prevent the index doubling `2*i` from going out of bounds.
810 // This is also ensuring we do not manipulate the unphysical layer of coors present in the finest level.
811 if (xe == mx) xe--;
812 if (ye == my) ye--;
813 if (ze == mz) ze--;
814
815 for (PetscInt k = zs; k < ze; k++) {
816 for (PetscInt j = ys; j < ye; j++) {
817 for (PetscInt i = xs; i < xe; i++) {
818 // Determine the corresponding parent node index on the FINE grid,
819 // respecting the semi-coarsening flags of the FINE grid's UserCtx.
820 ih = coarse_user->isc ? i : 2 * i;
821 jh = coarse_user->jsc ? j : 2 * j;
822 kh = coarse_user->ksc ? k : 2 * k;
823
824 // LOG_ALLOW(GLOBAL,LOG_DEBUG," [kh][ih][jh] = %d,%d,%d - k,j,i = %d,%d,%d.\n",kh,jh,ih,k,j,i);
825
826 c_coor[k][j][i] = f_coor[kh][jh][ih];
827 }
828 }
829 }
830
831 ierr = DMDAVecRestoreArray(coarse_user->fda, c_gCoor, &c_coor); CHKERRQ(ierr);
832 ierr = DMDAVecRestoreArrayRead(fine_user->fda, f_lCoor, &f_coor); CHKERRQ(ierr);
833
834 // Populate the coarse-grid local ghost coordinates from the restricted global coordinates.
835 ierr = UpdateLocalGhosts(coarse_user, FIELD_ID_COORDINATES); CHKERRQ(ierr);
836
838
839 PetscFunctionReturn(0);
840}
841
842#undef __FUNCT__
843#define __FUNCT__ "ComputeLocalBoundingBox"
844/**
845 * @brief Implementation of \ref ComputeLocalBoundingBox().
846 * @details Full API contract (arguments, ownership, side effects) is documented with
847 * the header declaration in `include/grid.h`.
848 * @see ComputeLocalBoundingBox()
849 */
850PetscErrorCode ComputeLocalBoundingBox(UserCtx *user, BoundingBox *localBBox)
851{
852 PetscErrorCode ierr;
853 PetscInt i, j, k;
854 PetscMPIInt rank;
855 PetscInt xs, ys, zs, xe, ye, ze;
856 DMDALocalInfo info;
857 Vec coordinates;
858 Cmpnts ***coordArray;
859 Cmpnts minCoords, maxCoords;
860
861 PetscFunctionBeginUser;
862
864
865 // Start of function execution
866 LOG_ALLOW(GLOBAL, LOG_INFO, "Entering the function.\n");
867
868 // Validate input Pointers
869 if (!user) {
870 LOG_ALLOW(LOCAL, LOG_ERROR, "Input 'user' Pointer is NULL.\n");
872 return PETSC_ERR_ARG_NULL;
873 }
874 if (!localBBox) {
875 LOG_ALLOW(LOCAL, LOG_ERROR, "Output 'localBBox' Pointer is NULL.\n");
877 return PETSC_ERR_ARG_NULL;
878 }
879
880 // Get MPI rank
881 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
882
883 // Get the local coordinates vector from the DMDA
884 ierr = DMGetCoordinatesLocal(user->da, &coordinates);
885 if (ierr) {
886 LOG_ALLOW(LOCAL, LOG_ERROR, "Error getting local coordinates vector.\n");
888 return ierr;
889 }
890
891 if (!coordinates) {
892 LOG_ALLOW(LOCAL, LOG_ERROR, "Coordinates vector is NULL.\n");
894 return PETSC_ERR_ARG_NULL;
895 }
896
897 // Access the coordinate array for reading
898 ierr = DMDAVecGetArrayRead(user->fda, coordinates, &coordArray);
899 if (ierr) {
900 LOG_ALLOW(LOCAL, LOG_ERROR, "Error accessing coordinate array.\n");
902 return ierr;
903 }
904
905 // Get the local grid information (indices and sizes)
906 ierr = DMDAGetLocalInfo(user->da, &info);
907 if (ierr) {
908 LOG_ALLOW(LOCAL, LOG_ERROR, "Error getting DMDA local info.\n");
910 return ierr;
911 }
912
913
914 xs = info.gxs; xe = xs + info.gxm;
915 ys = info.gys; ye = ys + info.gym;
916 zs = info.gzs; ze = zs + info.gzm;
917
918 /*
919 xs = info.xs; xe = xs + info.xm;
920 ys = info.ys; ye = ys + info.ym;
921 zs = info.zs; ze = zs + info.zm;
922 */
923
924 // Initialize min and max coordinates with extreme values
925 minCoords.x = minCoords.y = minCoords.z = PETSC_MAX_REAL;
926 maxCoords.x = maxCoords.y = maxCoords.z = PETSC_MIN_REAL;
927
928 LOG_ALLOW(LOCAL, LOG_TRACE, "[Rank %d] Grid indices (Including Ghosts): xs=%d, xe=%d, ys=%d, ye=%d, zs=%d, ze=%d.\n",rank, xs, xe, ys, ye, zs, ze);
929
930 // Iterate over the local grid to find min and max coordinates
931 for (k = zs; k < ze; k++) {
932 for (j = ys; j < ye; j++) {
933 for (i = xs; i < xe; i++) {
934 // Only consider nodes within the physical domain.
935 if(i < user->IM && j < user->JM && k < user->KM){
936 Cmpnts coord = coordArray[k][j][i];
937
938 // Update min and max coordinates
939 if (coord.x < minCoords.x) minCoords.x = coord.x;
940 if (coord.y < minCoords.y) minCoords.y = coord.y;
941 if (coord.z < minCoords.z) minCoords.z = coord.z;
942
943 if (coord.x > maxCoords.x) maxCoords.x = coord.x;
944 if (coord.y > maxCoords.y) maxCoords.y = coord.y;
945 if (coord.z > maxCoords.z) maxCoords.z = coord.z;
946 }
947 }
948 }
949 }
950
951
952 // Add tolerance to bboxes.
953 minCoords.x = minCoords.x - BBOX_TOLERANCE;
954 minCoords.y = minCoords.y - BBOX_TOLERANCE;
955 minCoords.z = minCoords.z - BBOX_TOLERANCE;
956
957 maxCoords.x = maxCoords.x + BBOX_TOLERANCE;
958 maxCoords.y = maxCoords.y + BBOX_TOLERANCE;
959 maxCoords.z = maxCoords.z + BBOX_TOLERANCE;
960
961 LOG_ALLOW(LOCAL,LOG_DEBUG," Tolerance added to the limits: %.8e .\n",(PetscReal)BBOX_TOLERANCE);
962
963 // Log the computed min and max coordinates
964 LOG_ALLOW(LOCAL, LOG_INFO,"[Rank %d] Bounding Box Ranges = X[%.6f, %.6f], Y[%.6f,%.6f], Z[%.6f, %.6f].\n",rank,minCoords.x, maxCoords.x,minCoords.y, maxCoords.y, minCoords.z, maxCoords.z);
965
966
967
968 // Restore the coordinate array
969 ierr = DMDAVecRestoreArrayRead(user->fda, coordinates, &coordArray);
970 if (ierr) {
971 LOG_ALLOW(LOCAL, LOG_ERROR, "Error restoring coordinate array.\n");
973 return ierr;
974 }
975
976 // Set the local bounding box
977 localBBox->min_coords = minCoords;
978 localBBox->max_coords = maxCoords;
979
980 // Update the bounding box inside the UserCtx for consistency
981 user->bbox = *localBBox;
982
983 LOG_ALLOW(GLOBAL, LOG_INFO, "Exiting the function successfully.\n");
984
986
987 PetscFunctionReturn(0);
988}
989
990#undef __FUNCT__
991#define __FUNCT__ "GatherAllBoundingBoxes"
992
993/**
994 * @brief Implementation of \ref GatherAllBoundingBoxes().
995 * @details Full API contract (arguments, ownership, side effects) is documented with
996 * the header declaration in `include/grid.h`.
997 * @see GatherAllBoundingBoxes()
998 */
999PetscErrorCode GatherAllBoundingBoxes(UserCtx *user, BoundingBox **allBBoxes)
1000{
1001 PetscErrorCode ierr;
1002 PetscMPIInt rank, size;
1003 BoundingBox *bboxArray = NULL;
1004 BoundingBox localBBox;
1005
1006 PetscFunctionBeginUser;
1007
1009
1010 /* Validate */
1011 if (!user || !allBBoxes) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
1012 "GatherAllBoundingBoxes: NULL pointer");
1013
1014 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRMPI(ierr);
1015 ierr = MPI_Comm_size(PETSC_COMM_WORLD, &size); CHKERRMPI(ierr);
1016
1017 /* Compute local bbox */
1018 ierr = ComputeLocalBoundingBox(user, &localBBox); CHKERRQ(ierr);
1019
1020 /* Ensure everyone is synchronized before the gather */
1021 MPI_Barrier(PETSC_COMM_WORLD);
1023 "Rank %d: about to MPI_Gather(localBBox)\n", rank);
1024
1025 /* Allocate on root */
1026 if (rank == 0) {
1027 bboxArray = (BoundingBox*)malloc(size * sizeof(BoundingBox));
1028 if (!bboxArray) SETERRABORT(PETSC_COMM_WORLD, PETSC_ERR_MEM,
1029 "GatherAllBoundingBoxes: malloc failed");
1030 }
1031
1032 /* Collective: every rank must call */
1033 ierr = MPI_Gather(&localBBox, sizeof(BoundingBox), MPI_BYTE,
1034 bboxArray, sizeof(BoundingBox), MPI_BYTE,
1035 0, PETSC_COMM_WORLD);
1036 CHKERRMPI(ierr);
1037
1038 MPI_Barrier(PETSC_COMM_WORLD);
1040 "Rank %d: completed MPI_Gather(localBBox)\n", rank);
1041
1042 /* Return result */
1043 if (rank == 0) {
1044 *allBBoxes = bboxArray;
1045 } else {
1046 *allBBoxes = NULL;
1047 }
1048
1050
1051 PetscFunctionReturn(0);
1052}
1053
1054#undef __FUNCT__
1055#define __FUNCT__ "BroadcastAllBoundingBoxes"
1056
1057/**
1058 * @brief Internal helper implementation: `BroadcastAllBoundingBoxes()`.
1059 * @details Local to this translation unit.
1060 */
1061PetscErrorCode BroadcastAllBoundingBoxes(UserCtx *user, BoundingBox **bboxlist)
1062{
1063 PetscErrorCode ierr;
1064 (void)user;
1065 PetscMPIInt rank, size;
1066
1067 PetscFunctionBeginUser;
1068
1070
1071 if (!bboxlist) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
1072 "BroadcastAllBoundingBoxes: NULL pointer");
1073
1074 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRMPI(ierr);
1075 ierr = MPI_Comm_size(PETSC_COMM_WORLD, &size); CHKERRMPI(ierr);
1076
1077 /* Non-root ranks must allocate before the Bcast */
1078 if (rank != 0) {
1079 *bboxlist = (BoundingBox*)malloc(size * sizeof(BoundingBox));
1080 if (!*bboxlist) SETERRABORT(PETSC_COMM_WORLD, PETSC_ERR_MEM,
1081 "BroadcastAllBoundingBoxes: malloc failed");
1082 }
1083
1084 MPI_Barrier(PETSC_COMM_WORLD);
1086 "Rank %d: about to MPI_Bcast(%d boxes)\n", rank, size);
1087
1088 /* Collective: every rank must call */
1089 ierr = MPI_Bcast(*bboxlist, size * sizeof(BoundingBox), MPI_BYTE,
1090 0, PETSC_COMM_WORLD);
1091 CHKERRMPI(ierr);
1092
1093 MPI_Barrier(PETSC_COMM_WORLD);
1095 "Rank %d: completed MPI_Bcast(%d boxes)\n", rank, size);
1096
1097
1099
1100 PetscFunctionReturn(0);
1101}
1102
1103#undef __FUNCT__
1104#define __FUNCT__ "CalculateInletProperties"
1105/**
1106 * @brief Implementation of \ref CalculateInletProperties().
1107 * @details Full API contract (arguments, ownership, side effects) is documented with
1108 * the header declaration in `include/grid.h`.
1109 * @see CalculateInletProperties()
1110 */
1112{
1113 PetscErrorCode ierr;
1114 BCFace inlet_face_id = -1;
1115 PetscBool inlet_found = PETSC_FALSE;
1116
1117 PetscFunctionBeginUser;
1119
1120 // 1. Identify the primary inlet face from the configuration
1121 for (int i = 0; i < 6; i++) {
1122 if (user->boundary_faces[i].mathematical_type == INLET) {
1123 inlet_face_id = user->boundary_faces[i].face_id;
1124 inlet_found = PETSC_TRUE;
1125 break; // Use the first inlet found
1126 }
1127 }
1128
1129 if (!inlet_found) {
1130 LOG_ALLOW(GLOBAL, LOG_INFO, "No INLET face found. Skipping inlet center calculation.\n");
1132 PetscFunctionReturn(0);
1133 }
1134
1135 Cmpnts inlet_center;
1136 PetscReal inlet_area;
1137
1138 // 2. Call the generic utility to compute the center and area of any face.
1139 ierr = CalculateFaceCenterAndArea(user,inlet_face_id,&inlet_center,&inlet_area); CHKERRQ(ierr);
1140
1141 // 3. Store results in the SimCtx
1142 user->simCtx->CMx_c = inlet_center.x;
1143 user->simCtx->CMy_c = inlet_center.y;
1144 user->simCtx->CMz_c = inlet_center.z;
1145 user->simCtx->AreaInSum = inlet_area;
1146
1148 "Rank[%d] Inlet Center: (%.6f, %.6f, %.6f), Area: %.6f\n",
1149 user->simCtx->rank, inlet_center.x, inlet_center.y, inlet_center.z, inlet_area);
1150
1152 PetscFunctionReturn(0);
1153
1154}
1155
1156#undef __FUNCT__
1157#define __FUNCT__ "CalculateOutletProperties"
1158/**
1159 * @brief Implementation of \ref CalculateOutletProperties().
1160 * @details Full API contract (arguments, ownership, side effects) is documented with
1161 * the header declaration in `include/grid.h`.
1162 * @see CalculateOutletProperties()
1163 */
1165{
1166 PetscErrorCode ierr;
1167 BCFace outlet_face_id = -1;
1168 PetscBool outlet_found = PETSC_FALSE;
1169 PetscFunctionBeginUser;
1171 // 1. Identify the primary outlet face from the configuration
1172 for (int i = 0; i < 6; i++) {
1173 if (user->boundary_faces[i].mathematical_type == OUTLET) {
1174 outlet_face_id = user->boundary_faces[i].face_id;
1175 outlet_found = PETSC_TRUE;
1176 break; // Use the first outlet found
1177 }
1178 }
1179 if (!outlet_found) {
1180 LOG_ALLOW(GLOBAL, LOG_INFO, "No OUTLET face found. Skipping outlet center calculation.\n");
1182 PetscFunctionReturn(0);
1183 }
1184 PetscReal outlet_area;
1185 Cmpnts outlet_center;
1186 // 2. Call the generic utility to compute the center and area of any face
1187 ierr = CalculateFaceCenterAndArea(user,outlet_face_id,&outlet_center,&outlet_area); CHKERRQ(ierr);
1188 // 3. Store results in the SimCtx
1189 user->simCtx->AreaOutSum = outlet_area;
1190
1192 "Outlet Center: (%.6f, %.6f, %.6f), Area: %.6f\n",
1193 outlet_center.x, outlet_center.y, outlet_center.z, outlet_area);
1194
1196 PetscFunctionReturn(0);
1197}
1198
1199#undef __FUNCT__
1200#define __FUNCT__ "CalculateFaceCenterAndArea"
1201/**
1202 * @brief Implementation of \ref CalculateFaceCenterAndArea().
1203 * @details Full API contract (arguments, ownership, side effects) is documented with
1204 * the header declaration in `include/grid.h`.
1205 * @see CalculateFaceCenterAndArea()
1206 */
1207PetscErrorCode CalculateFaceCenterAndArea(UserCtx *user, BCFace face_id,
1208 Cmpnts *face_center, PetscReal *face_area)
1209{
1210 PetscErrorCode ierr;
1211 DMDALocalInfo info;
1212
1213 // ========================================================================
1214 // Local accumulators for this rank's contribution
1215 // ========================================================================
1216 PetscReal local_sum[3] = {0.0, 0.0, 0.0}; ///< Local sum of (x,y,z) coordinates
1217 PetscReal localAreaSum = 0.0; ///< Local sum of face area magnitudes
1218 PetscCount local_n_points = 0; ///< Local count of nodes
1219
1220 // ========================================================================
1221 // Global accumulators after MPI reduction
1222 // ========================================================================
1223 PetscReal global_sum[3] = {0.0, 0.0, 0.0}; ///< Global sum of coordinates
1224 PetscReal globalAreaSum = 0.0; ///< Global sum of areas
1225 PetscCount global_n_points = 0; ///< Global count of nodes
1226
1227 // ========================================================================
1228 // Grid information and array pointers
1229 // ========================================================================
1230 info = user->info;
1231
1232 // Rank's owned range in global indices
1233 PetscInt xs = info.xs, xe = info.xs + info.xm; ///< i-range: [xs, xe)
1234 PetscInt ys = info.ys, ye = info.ys + info.ym; ///< j-range: [ys, ye)
1235 PetscInt zs = info.zs, ze = info.zs + info.zm; ///< k-range: [zs, ze)
1236
1237 // Global domain dimensions (total allocated, includes dummy at end)
1238 PetscInt mx = info.mx, my = info.my, mz = info.mz;
1239 PetscInt IM = user->IM; ///< Physical domain size in i (exclude dummy)
1240 PetscInt JM = user->JM; ///< Physical domain size in j (exclude dummy)
1241 PetscInt KM = user->KM; ///< Physical domain size in k (exclude dummy)
1242
1243 // ========================================================================
1244 // Interior loop bounds (adjusted to avoid ghost/boundary cells)
1245 // These are used for nvert checks where we need valid cell indices
1246 // ========================================================================
1247 PetscInt lxs = xs; if(xs == 0) lxs = xs + 1; ///< Start at 1 if on -Xi boundary
1248 PetscInt lxe = xe; if(xe == mx) lxe = xe - 1; ///< End at mx-1 if on +Xi boundary
1249 PetscInt lys = ys; if(ys == 0) lys = ys + 1; ///< Start at 1 if on -Eta boundary
1250 PetscInt lye = ye; if(ye == my) lye = ye - 1; ///< End at my-1 if on +Eta boundary
1251 PetscInt lzs = zs; if(zs == 0) lzs = zs + 1; ///< Start at 1 if on -Zeta boundary
1252 PetscInt lze = ze; if(ze == mz) lze = ze - 1; ///< End at mz-1 if on +Zeta boundary
1253
1254 // ========================================================================
1255 // Physical node bounds (exclude dummy indices at mx-1, my-1, mz-1)
1256 // These are used for coordinate loops where we want ALL physical nodes
1257 // ========================================================================
1258 PetscInt i_max = (xe == mx) ? mx - 1 : xe; ///< Exclude dummy at i=mx-1 (e.g., i=25)
1259 PetscInt j_max = (ye == my) ? my - 1 : ye; ///< Exclude dummy at j=my-1 (e.g., j=25)
1260 PetscInt k_max = (ze == mz) ? mz - 1 : ze; ///< Exclude dummy at k=mz-1 (e.g., k=97)
1261
1262 // ========================================================================
1263 // Array pointers for field access
1264 // ========================================================================
1265 Vec lCoor; ///< Local ghosted coordinate vector
1266 Cmpnts ***coor; ///< Nodal coordinates [k][j][i]
1267 Cmpnts ***csi, ***eta, ***zet; ///< Face metric tensors [k][j][i]
1268 PetscReal ***nvert; ///< Cell blanking field [k][j][i] (shifted +1)
1269
1270 PetscFunctionBeginUser;
1272
1273 // ========================================================================
1274 // Step 1: Check if this rank owns the specified boundary face
1275 // ========================================================================
1276 PetscBool owns_face = PETSC_FALSE;
1277 ierr = CanRankServiceFace(&info,IM,JM,KM,face_id,&owns_face); CHKERRQ(ierr);
1278 if(owns_face){
1279 // ========================================================================
1280 // Step 2: Get read-only array access for all required fields
1281 // ========================================================================
1282 ierr = DMGetCoordinatesLocal(user->da, &lCoor); CHKERRQ(ierr);
1283 ierr = DMDAVecGetArrayRead(user->fda, lCoor, &coor); CHKERRQ(ierr);
1284 ierr = DMDAVecGetArrayRead(user->da, user->lNvert, &nvert); CHKERRQ(ierr);
1285 ierr = DMDAVecGetArrayRead(user->fda, user->lCsi, &csi); CHKERRQ(ierr);
1286 ierr = DMDAVecGetArrayRead(user->fda, user->lEta, &eta); CHKERRQ(ierr);
1287 ierr = DMDAVecGetArrayRead(user->fda, user->lZet, &zet); CHKERRQ(ierr);
1288
1289 // ========================================================================
1290 // Step 3: Loop over the specified face and accumulate center and area
1291 // ========================================================================
1292 switch (face_id) {
1293
1294 // ====================================================================
1295 // BC_FACE_NEG_X: Face at i=0 (bottom boundary in i-direction)
1296 // ====================================================================
1297 case BC_FACE_NEG_X:
1298 if (xs == 0) {
1299 PetscInt i = 0; // Face is at node index i=0
1300
1301 // ---- Part 1: Center calculation (ALL physical nodes) ----
1302 // Loop over ALL physical nodes on this face
1303 // For my=26, mz=98: j∈[0,24], k∈[0,96] → 25Ɨ97 = 2,425 nodes
1304 for (PetscInt k = zs; k < k_max; k++) {
1305 for (PetscInt j = ys; j < j_max; j++) {
1306 // Accumulate coordinates at node [k][j][0]
1307 local_sum[0] += coor[k][j][i].x;
1308 local_sum[1] += coor[k][j][i].y;
1309 local_sum[2] += coor[k][j][i].z;
1310 local_n_points++;
1311 }
1312 }
1313
1314 // ---- Part 2: Area calculation (INTERIOR cells only) ----
1315 // Loop over interior range where nvert checks are valid
1316 // For my=26, mz=98: j∈[1,24], k∈[1,96] → 24Ɨ96 = 2,304 cells
1317 for (PetscInt k = lzs; k < lze; k++) {
1318 for (PetscInt j = lys; j < lye; j++) {
1319 // Check if adjacent cell is fluid
1320 // nvert[k][j][i+1] = nvert[k][j][1] checks Cell 0
1321 // (Physical Cell 0 in j-k plane, stored at shifted index [1])
1322 if (nvert[k][j][i+1] < 0.1) {
1323 // Cell is fluid - add face area contribution
1324 // Face area = magnitude of csi metric at [k][j][0]
1325 localAreaSum += sqrt(csi[k][j][i].x * csi[k][j][i].x +
1326 csi[k][j][i].y * csi[k][j][i].y +
1327 csi[k][j][i].z * csi[k][j][i].z);
1328 }
1329 }
1330 }
1331 }
1332 break;
1333
1334 // ====================================================================
1335 // BC_FACE_POS_X: Face at i=IM-1 (top boundary in i-direction)
1336 // ====================================================================
1337 case BC_FACE_POS_X:
1338 if (xe == mx) {
1339 PetscInt i = mx - 2; // Last physical node (e.g., i=24 for mx=26)
1340
1341 // ---- Part 1: Center calculation (ALL physical nodes) ----
1342 for (PetscInt k = zs; k < k_max; k++) {
1343 for (PetscInt j = ys; j < j_max; j++) {
1344 local_sum[0] += coor[k][j][i].x;
1345 local_sum[1] += coor[k][j][i].y;
1346 local_sum[2] += coor[k][j][i].z;
1347 local_n_points++;
1348 }
1349 }
1350
1351 // ---- Part 2: Area calculation (INTERIOR cells only) ----
1352 for (PetscInt k = lzs; k < lze; k++) {
1353 for (PetscInt j = lys; j < lye; j++) {
1354 // Check if adjacent cell is fluid
1355 // nvert[k][j][i] = nvert[k][j][24] checks last cell (Cell 23)
1356 // (Physical Cell 23, stored at shifted index [24])
1357 if (nvert[k][j][i] < 0.1) {
1358 // Face area = magnitude of csi metric at [k][j][24]
1359 localAreaSum += sqrt(csi[k][j][i].x * csi[k][j][i].x +
1360 csi[k][j][i].y * csi[k][j][i].y +
1361 csi[k][j][i].z * csi[k][j][i].z);
1362 }
1363 }
1364 }
1365 }
1366 break;
1367
1368 // ====================================================================
1369 // BC_FACE_NEG_Y: Face at j=0 (bottom boundary in j-direction)
1370 // ====================================================================
1371 case BC_FACE_NEG_Y:
1372 if (ys == 0) {
1373 PetscInt j = 0; // Face is at node index j=0
1374
1375 // ---- Part 1: Center calculation (ALL physical nodes) ----
1376 // For mx=26, mz=98: i∈[0,24], k∈[0,96] → 25Ɨ97 = 2,425 nodes
1377 for (PetscInt k = zs; k < k_max; k++) {
1378 for (PetscInt i = xs; i < i_max; i++) {
1379 local_sum[0] += coor[k][j][i].x;
1380 local_sum[1] += coor[k][j][i].y;
1381 local_sum[2] += coor[k][j][i].z;
1382 local_n_points++;
1383 }
1384 }
1385
1386 // ---- Part 2: Area calculation (INTERIOR cells only) ----
1387 // For mx=26, mz=98: i∈[1,24], k∈[1,96] → 24Ɨ96 = 2,304 cells
1388 for (PetscInt k = lzs; k < lze; k++) {
1389 for (PetscInt i = lxs; i < lxe; i++) {
1390 // nvert[k][j+1][i] = nvert[k][1][i] checks Cell 0
1391 if (nvert[k][j+1][i] < 0.1) {
1392 // Face area = magnitude of eta metric at [k][0][i]
1393 localAreaSum += sqrt(eta[k][j][i].x * eta[k][j][i].x +
1394 eta[k][j][i].y * eta[k][j][i].y +
1395 eta[k][j][i].z * eta[k][j][i].z);
1396 }
1397 }
1398 }
1399 }
1400 break;
1401
1402 // ====================================================================
1403 // BC_FACE_POS_Y: Face at j=JM-1 (top boundary in j-direction)
1404 // ====================================================================
1405 case BC_FACE_POS_Y:
1406 if (ye == my) {
1407 PetscInt j = my - 2; // Last physical node (e.g., j=24 for my=26)
1408
1409 // ---- Part 1: Center calculation (ALL physical nodes) ----
1410 for (PetscInt k = zs; k < k_max; k++) {
1411 for (PetscInt i = xs; i < i_max; i++) {
1412 local_sum[0] += coor[k][j][i].x;
1413 local_sum[1] += coor[k][j][i].y;
1414 local_sum[2] += coor[k][j][i].z;
1415 local_n_points++;
1416 }
1417 }
1418
1419 // ---- Part 2: Area calculation (INTERIOR cells only) ----
1420 for (PetscInt k = lzs; k < lze; k++) {
1421 for (PetscInt i = lxs; i < lxe; i++) {
1422 // nvert[k][j][i] = nvert[k][24][i] checks last cell (Cell 23)
1423 if (nvert[k][j][i] < 0.1) {
1424 // Face area = magnitude of eta metric at [k][24][i]
1425 localAreaSum += sqrt(eta[k][j][i].x * eta[k][j][i].x +
1426 eta[k][j][i].y * eta[k][j][i].y +
1427 eta[k][j][i].z * eta[k][j][i].z);
1428 }
1429 }
1430 }
1431 }
1432 break;
1433
1434 // ====================================================================
1435 // BC_FACE_NEG_Z: Face at k=0 (inlet, bottom boundary in k-direction)
1436 // ====================================================================
1437 case BC_FACE_NEG_Z:
1438 if (zs == 0) {
1439 PetscInt k = 0; // Face is at node index k=0
1440
1441 // ---- Part 1: Center calculation (ALL physical nodes) ----
1442 // For mx=26, my=26: i∈[0,24], j∈[0,24] → 25Ɨ25 = 625 nodes
1443 for (PetscInt j = ys; j < j_max; j++) {
1444 for (PetscInt i = xs; i < i_max; i++) {
1445 local_sum[0] += coor[k][j][i].x;
1446 local_sum[1] += coor[k][j][i].y;
1447 local_sum[2] += coor[k][j][i].z;
1448 local_n_points++;
1449 }
1450 }
1451
1452 // ---- Part 2: Area calculation (INTERIOR cells only) ----
1453 // For mx=26, my=26: i∈[1,24], j∈[1,24] → 24Ɨ24 = 576 cells
1454 for (PetscInt j = lys; j < lye; j++) {
1455 for (PetscInt i = lxs; i < lxe; i++) {
1456 // nvert[k+1][j][i] = nvert[1][j][i] checks Cell 0
1457 // (Physical Cell 0 in i-j plane, stored at shifted index [1])
1458 if (nvert[k+1][j][i] < 0.1) {
1459 // Face area = magnitude of zet metric at [0][j][i]
1460 localAreaSum += sqrt(zet[k][j][i].x * zet[k][j][i].x +
1461 zet[k][j][i].y * zet[k][j][i].y +
1462 zet[k][j][i].z * zet[k][j][i].z);
1463 }
1464 }
1465 }
1466 }
1467 break;
1468
1469 // ====================================================================
1470 // BC_FACE_POS_Z: Face at k=KM-1 (outlet, top boundary in k-direction)
1471 // ====================================================================
1472 case BC_FACE_POS_Z:
1473 if (ze == mz) {
1474 PetscInt k = mz - 2; // Last physical node (e.g., k=96 for mz=98)
1475
1476 // ---- Part 1: Center calculation (ALL physical nodes) ----
1477 // For mx=26, my=26: i∈[0,24], j∈[0,24] → 25Ɨ25 = 625 nodes
1478 for (PetscInt j = ys; j < j_max; j++) {
1479 for (PetscInt i = xs; i < i_max; i++) {
1480 local_sum[0] += coor[k][j][i].x;
1481 local_sum[1] += coor[k][j][i].y;
1482 local_sum[2] += coor[k][j][i].z;
1483 local_n_points++;
1484 }
1485 }
1486
1487 // ---- Part 2: Area calculation (INTERIOR cells only) ----
1488 // For mx=26, my=26: i∈[1,24], j∈[1,24] → 24Ɨ24 = 576 cells
1489 for (PetscInt j = lys; j < lye; j++) {
1490 for (PetscInt i = lxs; i < lxe; i++) {
1491 // nvert[k][j][i] = nvert[96][j][i] checks last cell (Cell 95)
1492 // (Physical Cell 95, stored at shifted index [96])
1493 if (nvert[k][j][i] < 0.1) {
1494 // Face area = magnitude of zet metric at [96][j][i]
1495 localAreaSum += sqrt(zet[k][j][i].x * zet[k][j][i].x +
1496 zet[k][j][i].y * zet[k][j][i].y +
1497 zet[k][j][i].z * zet[k][j][i].z);
1498 }
1499 }
1500 }
1501 }
1502 break;
1503
1504 default:
1505 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
1506 "Unknown face_id %d in CalculateFaceCenterAndArea", face_id);
1507 }
1508
1509 // ========================================================================
1510 // Step 4: Restore array access (release pointers)
1511 // ========================================================================
1512 ierr = DMDAVecRestoreArrayRead(user->fda, lCoor, &coor); CHKERRQ(ierr);
1513 ierr = DMDAVecRestoreArrayRead(user->da, user->lNvert, &nvert); CHKERRQ(ierr);
1514 ierr = DMDAVecRestoreArrayRead(user->fda, user->lCsi, &csi); CHKERRQ(ierr);
1515 ierr = DMDAVecRestoreArrayRead(user->fda, user->lEta, &eta); CHKERRQ(ierr);
1516 ierr = DMDAVecRestoreArrayRead(user->fda, user->lZet, &zet); CHKERRQ(ierr);
1517 }
1518 // ========================================================================
1519 // Step 5: Perform MPI reductions to get global sums
1520 // ========================================================================
1521 // Sum coordinate contributions from all ranks
1522 ierr = MPI_Allreduce(local_sum, global_sum, 3, MPI_DOUBLE, MPI_SUM,
1523 PETSC_COMM_WORLD); CHKERRQ(ierr);
1524
1525 // Sum node counts from all ranks
1526 ierr = MPI_Allreduce(&local_n_points, &global_n_points, 1, MPI_COUNT, MPI_SUM,
1527 PETSC_COMM_WORLD); CHKERRQ(ierr);
1528
1529 // Sum area contributions from all ranks
1530 ierr = MPI_Allreduce(&localAreaSum, &globalAreaSum, 1, MPI_DOUBLE, MPI_SUM,
1531 PETSC_COMM_WORLD); CHKERRQ(ierr);
1532
1533 // ========================================================================
1534 // Step 6: Calculate geometric center by averaging coordinates
1535 // ========================================================================
1536 if (global_n_points > 0) {
1537 face_center->x = global_sum[0] / global_n_points;
1538 face_center->y = global_sum[1] / global_n_points;
1539 face_center->z = global_sum[2] / global_n_points;
1541 "Calculated center for Face %s: (x=%.4f, y=%.4f, z=%.4f) from %lld nodes\n",
1542 BCFaceToString(face_id),
1543 face_center->x, face_center->y, face_center->z,
1544 (long long)global_n_points);
1545 } else {
1546 // No nodes found - this should not happen for a valid face
1548 "WARNING: Face %s identified but no grid points found. Center not calculated.\n",
1549 BCFaceToString(face_id));
1550 face_center->x = face_center->y = face_center->z = 0.0;
1551 }
1552
1553 // ========================================================================
1554 // Step 7: Return computed total area
1555 // ========================================================================
1556 *face_area = globalAreaSum;
1558 "Calculated area for Face %s: Area=%.6f\n",
1559 BCFaceToString(face_id), *face_area);
1560
1562 PetscFunctionReturn(0);
1563}
PetscBool AnalyticalTypeRequiresCustomGeometry(const char *analytical_type)
Reports whether an analytical type requires custom geometry/decomposition logic.
PetscErrorCode SetAnalyticalGridInfo(UserCtx *user)
Sets the grid domain and resolution for analytical solution cases.
PetscErrorCode CanRankServiceFace(const DMDALocalInfo *info, PetscInt IM_nodes_global, PetscInt JM_nodes_global, PetscInt KM_nodes_global, BCFace face_id, PetscBool *can_service_out)
Determines if the current MPI rank owns any part of a specified global face.
Definition Boundaries.c:127
@ FIELD_ID_COORDINATES
PetscErrorCode DefineAllGridDimensions(SimCtx *simCtx)
Internal helper implementation: DefineAllGridDimensions().
Definition grid.c:57
PetscErrorCode CalculateOutletProperties(UserCtx *user)
Implementation of CalculateOutletProperties().
Definition grid.c:1164
#define BBOX_TOLERANCE
Definition grid.c:7
PetscErrorCode BroadcastAllBoundingBoxes(UserCtx *user, BoundingBox **bboxlist)
Internal helper implementation: BroadcastAllBoundingBoxes().
Definition grid.c:1061
PetscErrorCode ValidatePeriodicGeometry(UserCtx *user)
Implementation of ValidatePeriodicGeometry().
Definition grid.c:421
static PetscErrorCode ParseAndSetGridInputs(UserCtx *user)
Parse grid-generation options and store the resulting geometry settings in the context.
Definition grid.c:14
static PetscReal ComputeStretchedCoord(PetscInt i, PetscInt N, PetscReal L, PetscReal r)
Map a uniform logical coordinate to its configured stretched physical coordinate.
Definition grid.c:612
static PetscErrorCode RestrictCoordinates(UserCtx *coarse_user, UserCtx *fine_user)
Restrict finest-grid coordinates onto each coarser multigrid level.
Definition grid.c:775
static PetscErrorCode InitializeSingleGridDM(UserCtx *user, UserCtx *coarse_user)
Create and configure one PETSc DMDA for a multigrid level.
Definition grid.c:142
static PetscReal CoordinateComponent(Cmpnts value, PetscInt component)
Returns one Cartesian component from a coordinate/vector value.
Definition grid.c:407
PetscErrorCode CalculateFaceCenterAndArea(UserCtx *user, BCFace face_id, Cmpnts *face_center, PetscReal *face_area)
Implementation of CalculateFaceCenterAndArea().
Definition grid.c:1207
PetscErrorCode InitializeAllGridDMs(SimCtx *simCtx)
Internal helper implementation: InitializeAllGridDMs().
Definition grid.c:276
PetscErrorCode AssignAllGridCoordinates(SimCtx *simCtx)
Internal helper implementation: AssignAllGridCoordinates().
Definition grid.c:358
static PetscErrorCode GenerateAndSetCoordinates(UserCtx *user)
Generate analytic grid coordinates and install them on the finest grid level.
Definition grid.c:628
static PetscErrorCode ReadAndSetCoordinates(UserCtx *user, FILE *fd)
Load grid coordinates from the input file and install them on the finest level.
Definition grid.c:697
static PetscErrorCode SetFinestLevelCoordinates(UserCtx *user)
Attach the generated physical coordinates to the finest-level DMDA.
Definition grid.c:558
PetscErrorCode CreateCompatibleBlockDM(DM source, PetscInt dof, DM *result)
Implementation of CreateCompatibleBlockDM().
Definition grid.c:109
PetscErrorCode ComputeLocalBoundingBox(UserCtx *user, BoundingBox *localBBox)
Implementation of ComputeLocalBoundingBox().
Definition grid.c:850
PetscErrorCode CalculateInletProperties(UserCtx *user)
Implementation of CalculateInletProperties().
Definition grid.c:1111
#define __FUNCT__
Definition grid.c:10
PetscErrorCode GatherAllBoundingBoxes(UserCtx *user, BoundingBox **allBBoxes)
Implementation of GatherAllBoundingBoxes().
Definition grid.c:999
Public interface for grid, solver, and metric setup routines.
PetscErrorCode ReadGridFile(UserCtx *user)
Sets grid dimensions from a file for a SINGLE block using a one-time read cache.
Definition io.c:581
PetscErrorCode ReadGridGenerationInputs(UserCtx *user)
Parses command-line options for a programmatically generated grid for a SINGLE block.
Definition io.c:447
PetscErrorCode PopulateFinestUserGridResolutionFromOptions(UserCtx *finest_users, PetscInt nblk)
Parses grid resolution arrays (-im, -jm, -km) once and applies them to all finest-grid blocks.
Definition io.c:532
PetscErrorCode DeterminePeriodicity(SimCtx *simCtx)
Scans all block-specific boundary condition files to determine a globally consistent periodicity for ...
Definition io.c:1024
Logging utilities and macros for PETSc-based applications.
PetscErrorCode LOG_FIELD_MIN_MAX(UserCtx *user, FieldId field_id)
Computes and logs the local and global min/max values of a 3-component vector field.
Definition logging.c:2349
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
const char * BCFaceToString(BCFace face)
Returns the canonical log token for a boundary-face enum value.
Definition logging.c:671
#define LOG_ALLOW(scope, level, fmt,...)
Logging macro that checks both the log level and whether the calling function is in the allowed-funct...
Definition logging.h:200
#define PROFILE_FUNCTION_END
Marks the end of a profiled code block.
Definition logging.h:859
#define LOG(scope, level, fmt,...)
Logging macro for PETSc-based applications with scope control.
Definition logging.h:84
LogLevel get_log_level()
Retrieves the current logging level from the environment variable LOG_LEVEL.
Definition logging.c:87
@ LOG_ERROR
Critical errors that may halt the program.
Definition logging.h:29
@ LOG_TRACE
Very fine-grained tracing information for in-depth debugging.
Definition logging.h:33
@ LOG_INFO
Informational messages about program execution.
Definition logging.h:31
@ LOG_WARNING
Non-critical issues that warrant attention.
Definition logging.h:30
@ LOG_DEBUG
Detailed debugging information.
Definition logging.h:32
@ LOG_VERBOSE
Extremely detailed logs, typically for development use only.
Definition logging.h:34
#define PROFILE_FUNCTION_BEGIN
Marks the beginning of a profiled code block (typically a function).
Definition logging.h:850
PetscErrorCode UpdateLocalGhosts(UserCtx *user, FieldId field_id)
Updates the local vector (including ghost points) from its corresponding global vector.
Definition setup.c:1838
Window lifecycle, scheduling, and weighting for the field-statistics pipeline.
PetscBool FieldStatisticsIsActive(const struct SimCtx *simCtx)
Reports whether this run has live field-statistics state.
PetscInt isc
Definition variables.h:924
@ INLET
Definition variables.h:290
@ OUTLET
Definition variables.h:289
@ PERIODIC
Definition variables.h:292
UserCtx * user
Definition variables.h:571
PetscMPIInt rank
Definition variables.h:698
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:931
PetscInt block_number
Definition variables.h:790
PetscInt da_procs_z
Definition variables.h:796
Vec lNvert
Definition variables.h:939
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:909
PetscReal CMy_c
Definition variables.h:783
PetscReal Min_X
Definition variables.h:921
PetscInt ksc
Definition variables.h:924
PetscInt KM
Definition variables.h:920
Vec lZet
Definition variables.h:974
UserMG usermg
Definition variables.h:852
PetscInt da_procs_y
Definition variables.h:796
Cmpnts max_coords
Maximum x, y, z coordinates of the bounding box.
Definition variables.h:173
PetscInt _this
Definition variables.h:924
PetscReal ry
Definition variables.h:925
PetscInt k_periodic
Definition variables.h:791
PetscInt jsc
Definition variables.h:924
PetscReal Max_Y
Definition variables.h:921
Cmpnts min_coords
Minimum x, y, z coordinates of the bounding box.
Definition variables.h:172
PetscScalar x
Definition variables.h:103
char grid_file[PETSC_MAX_PATH_LEN]
Definition variables.h:795
PetscReal rz
Definition variables.h:925
Vec lCsi
Definition variables.h:974
PetscReal CMz_c
Definition variables.h:783
PetscBool generate_grid
Definition variables.h:792
PetscInt thislevel
Definition variables.h:988
char eulerianSource[PETSC_MAX_PATH_LEN]
Definition variables.h:715
PetscScalar z
Definition variables.h:103
PetscInt JM
Definition variables.h:920
PetscInt mglevels
Definition variables.h:578
PetscReal Min_Z
Definition variables.h:921
PetscInt mglevels
Definition variables.h:739
char AnalyticalSolutionType[PETSC_MAX_PATH_LEN]
Definition variables.h:729
PetscInt da_procs_x
Definition variables.h:796
PetscReal Max_X
Definition variables.h:921
PetscReal Min_Y
Definition variables.h:921
PetscInt i_periodic
Definition variables.h:791
PetscReal AreaOutSum
Definition variables.h:815
DMDALocalInfo info
Definition variables.h:918
PetscScalar y
Definition variables.h:103
@ EXEC_MODE_SOLVER
Definition variables.h:668
@ EXEC_MODE_POSTPROCESSOR
Definition variables.h:669
PetscInt IM
Definition variables.h:920
Cmpnts periodic_translation[3]
Definition variables.h:927
Vec lEta
Definition variables.h:974
MGCtx * mgctx
Definition variables.h:581
PetscBool periodic_translation_valid[3]
Definition variables.h:928
BCType mathematical_type
Definition variables.h:368
PetscReal rx
Definition variables.h:925
ExecutionMode exec_mode
Definition variables.h:714
BoundingBox bbox
Definition variables.h:922
PetscReal Max_Z
Definition variables.h:921
PetscReal AreaInSum
Definition variables.h:815
PetscReal CMx_c
Definition variables.h:783
BCFace
Identifies the six logical faces of a structured computational block.
Definition variables.h:261
@ BC_FACE_NEG_X
Definition variables.h:262
@ BC_FACE_POS_Z
Definition variables.h:264
@ BC_FACE_POS_Y
Definition variables.h:263
@ BC_FACE_NEG_Z
Definition variables.h:264
@ BC_FACE_POS_X
Definition variables.h:262
@ BC_FACE_NEG_Y
Definition variables.h:263
PetscInt j_periodic
Definition variables.h:791
Defines a 3D axis-aligned bounding box.
Definition variables.h:171
A 3D point or vector with PetscScalar components.
Definition variables.h:102
Context for Multigrid operations.
Definition variables.h:570
The master context for the entire simulation.
Definition variables.h:695
User-defined context containing data specific to a single computational grid level.
Definition variables.h:906
User-level context for managing the entire multigrid hierarchy.
Definition variables.h:577