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