PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
ParticleMotion.c
Go to the documentation of this file.
1// ParticleMotion.c
2
3#include "ParticleMotion.h"
5
6// Define a buffer size for error messages if not already available
7#ifndef ERROR_MSG_BUFFER_SIZE
8#define ERROR_MSG_BUFFER_SIZE 256 // Or use PETSC_MAX_PATH_LEN if appropriate
9#endif
10
11#undef __FUNCT__
12#define __FUNCT__ "GenerateGaussianNoise"
13/**
14 * @brief Internal helper implementation: `GenerateGaussianNoise()`.
15 * @details Local to this translation unit.
16 */
17PetscErrorCode GenerateGaussianNoise(PetscRandom rnd, PetscReal *n1, PetscReal *n2)
18{
19 PetscErrorCode ierr;
20 PetscScalar val1, val2;
21 PetscReal u1, u2;
22 PetscReal magnitude, theta;
23
24 PetscFunctionBeginUser;
25
26 // 1. Get two independent uniform random numbers from the generator
27 // PetscRandomGetValue returns a PetscScalar (which might be complex).
28 // We take the Real part to ensure this works in both Real and Complex builds.
29 ierr = PetscRandomGetValue(rnd, &val1); CHKERRQ(ierr);
30 ierr = PetscRandomGetValue(rnd, &val2); CHKERRQ(ierr);
31
32 u1 = PetscRealPart(val1);
33 u2 = PetscRealPart(val2);
34
35 // 2. Safety Check: log(0) is undefined (infinity).
36 // If the RNG returns exactly 0.0, bump it to a tiny epsilon.
37 if (u1 <= 0.0) u1 = 1.0e-14;
38
39 // 3. Box-Muller Transform
40 // Formula: R = sqrt(-2 * ln(u1)), Theta = 2 * PI * u2
41 magnitude = PetscSqrtReal(-2.0 * PetscLogReal(u1));
42 theta = 2.0 * PETSC_PI * u2;
43
44 // 4. Calculate independent Normal variables
45 *n1 = magnitude * PetscCosReal(theta);
46 *n2 = magnitude * PetscSinReal(theta);
47
48 PetscFunctionReturn(0);
49}
50
51#undef __FUNCT__
52#define __FUNCT__ "CalculateBrownianDisplacement"
53/**
54 * @brief Internal helper implementation: `CalculateBrownianDisplacement()`.
55 * @details Local to this translation unit.
56 */
57PetscErrorCode CalculateBrownianDisplacement(UserCtx *user, PetscReal diff_eff, Cmpnts *displacement)
58{
59 PetscErrorCode ierr;
60 PetscReal dt = user->simCtx->dt;
61 PetscReal sigma;
62 PetscReal n_x, n_y, n_z, gaussian_dummy;
63
64 PetscFunctionBeginUser;
65
66 // 1. Initialize output to zero for safety
67 displacement->x = 0.0;
68 displacement->y = 0.0;
69 displacement->z = 0.0;
70
71 // 2. Physical check: Diffusivity cannot be negative.
72 // If 0, there is no Brownian motion.
73 if (diff_eff <= 1.0e-12) {
74 PetscFunctionReturn(0);
75 }
76
77 // 3. Calculate the Scaling Factor (Standard Deviation)
78 // Formula: sigma = sqrt(2 * D * dt)
79 // Note: dt is inside the root because variance scales linearly with time.
80 sigma = PetscSqrtReal(2.0 * diff_eff * dt);
81
82 // 4. Generate 3 Independent Gaussian Random Numbers
83 // GenerateGaussianNoise produces 2 numbers at a time. We call it twice.
84
85 // Get noise for X and Y
86 ierr = GenerateGaussianNoise(user->simCtx->BrownianMotionRNG, &n_x, &n_y); CHKERRQ(ierr);
87
88 // Get noise for Z (second sample is intentionally discarded here).
89 ierr = GenerateGaussianNoise(user->simCtx->BrownianMotionRNG, &n_z, &gaussian_dummy); CHKERRQ(ierr);
90
91 // 5. Calculate final stochastic displacement
92 displacement->x = sigma * n_x;
93 displacement->y = sigma * n_y;
94 displacement->z = sigma * n_z;
95
96 PetscFunctionReturn(0);
97}
98
99#undef __FUNCT__
100#define __FUNCT__ "UpdateParticlePosition"
101/**
102 * @brief Internal helper implementation: `UpdateParticlePosition()`.
103 * @details Local to this translation unit.
104 */
105PetscErrorCode UpdateParticlePosition(UserCtx *user, Particle *particle)
106{
107 PetscFunctionBeginUser; // PETSc macro for error/stack tracing
109
110 PetscErrorCode ierr;
111 PetscReal dt = user->simCtx->dt;
112 Cmpnts brownian_disp;
113
114 // 2. Calculate the stochastic kick
115 ierr = CalculateBrownianDisplacement(user,particle->diffusivity, &brownian_disp); CHKERRQ(ierr);
116
117 // --- Update Position ---
118 // X_new = X_old + ((U_convection + U_diffusivitygradient) * dt) + dX_brownian
119
120 particle->loc.x += ((particle->vel.x + particle->diffusivitygradient.x) * dt) + brownian_disp.x;
121 particle->loc.y += ((particle->vel.y + particle->diffusivitygradient.y) * dt) + brownian_disp.y;
122 particle->loc.z += ((particle->vel.z + particle->diffusivitygradient.z) * dt) + brownian_disp.z;
123
125 PetscFunctionReturn(0);
126}
127
128#undef __FUNCT__
129#define __FUNCT__ "UpdateAllParticlePositions"
130/**
131 * @brief Internal helper implementation: `UpdateAllParticlePositions()`.
132 * @details Local to this translation unit.
133 */
135{
136 PetscErrorCode ierr;
137 DM swarm = user->swarm;
138 PetscInt nLocal, p;
139 PetscReal *pos = NULL;
140 PetscReal *vel = NULL;
141 PetscReal *diffusivity = NULL;
142 Cmpnts *diffusivitygradient = NULL;
143 PetscReal *psi = NULL;
144 PetscReal *weights = NULL;
145 PetscInt *cell = NULL;
146 PetscInt *status = NULL;
147 PetscInt64 *pid = NULL;
148 PetscMPIInt rank;
149
150 ierr = MPI_Comm_rank(PETSC_COMM_WORLD,&rank);
151
152 PetscFunctionBeginUser; // PETSc macro for error/stack tracing
153
155
156 // 1) Get the number of local particles
157 ierr = DMSwarmGetLocalSize(swarm, &nLocal); CHKERRQ(ierr);
158 if (nLocal == 0) {
159 LOG_ALLOW(LOCAL,LOG_DEBUG,"[Rank %d] No particles to move/transport. \n",rank);
161 PetscFunctionReturn(0); // nothing to do, no fields held
162 }
163 // 2) Access the "position" and "velocity" fields
164 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&pos); CHKERRQ(ierr);
165 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_VELOCITY), NULL, NULL, (void**)&vel); CHKERRQ(ierr);
166 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_DIFFUSIVITY), NULL, NULL, (void**)&diffusivity); CHKERRQ(ierr);
167 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_DIFFUSIVITY_GRADIENT), NULL, NULL, (void**)&diffusivitygradient); CHKERRQ(ierr);
168 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PSI), NULL, NULL, (void**)&psi); CHKERRQ(ierr);
169 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_WEIGHT), NULL, NULL, (void**)&weights); CHKERRQ(ierr);
170 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell); CHKERRQ(ierr);
171 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status); CHKERRQ(ierr);
172 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid); CHKERRQ(ierr);
173
174 LOG_ALLOW(GLOBAL,LOG_DEBUG," [Rank %d] No.of Particles to update: %" PetscInt_FMT ".\n",rank,nLocal);
175
176 // 3) Loop over all local particles, updating each position by velocity * dt
177 for (p = 0; p < nLocal; p++) {
178 // update temporary particle struct
179 Particle particle;
180
181 // Unpack: Use the helper to read from swarm arrays into the particle struct
182 ierr = UnpackSwarmFields(p, pid, weights, pos, cell, vel, status, diffusivity, diffusivitygradient, psi, &particle); CHKERRQ(ierr);
183
184 // Update position based on velocity and Brownian motion
185 ierr = UpdateParticlePosition(user, &particle); CHKERRQ(ierr);
186
187 // Update swarm fields
188 ierr = UpdateSwarmFields(p, &particle, pos, vel, weights, cell, status, diffusivity, diffusivitygradient, psi); CHKERRQ(ierr);
189 }
190
191 // 4) Restore the fields
192 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&pos); CHKERRQ(ierr);
193 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_VELOCITY), NULL, NULL, (void**)&vel); CHKERRQ(ierr);
194 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_DIFFUSIVITY), NULL, NULL, (void**)&diffusivity); CHKERRQ(ierr);
195 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_DIFFUSIVITY_GRADIENT), NULL, NULL, (void**)&diffusivitygradient); CHKERRQ(ierr);
196 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PSI), NULL, NULL, (void**)&psi); CHKERRQ(ierr);
197 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_WEIGHT), NULL, NULL, (void**)&weights); CHKERRQ(ierr);
198 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell); CHKERRQ(ierr);
199 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status); CHKERRQ(ierr);
200 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid); CHKERRQ(ierr);
201
202
203 LOG_ALLOW(LOCAL,LOG_DEBUG,"Particle moved/transported successfully on Rank %d.\n",rank);
204
206
207 PetscFunctionReturn(0);
208}
209
210
211/**
212 * @brief Test whether a particle position lies within an axis-aligned bounding box.
213 */
214static inline PetscBool IsParticleInBox(const BoundingBox *bbox, const Cmpnts *pos) {
215 return (pos->x >= bbox->min_coords.x && pos->x <= bbox->max_coords.x &&
216 pos->y >= bbox->min_coords.y && pos->y <= bbox->max_coords.y &&
217 pos->z >= bbox->min_coords.z && pos->z <= bbox->max_coords.z);
218}
219
220
221#undef __FUNCT__
222#define __FUNCT__ "CheckAndRemoveOutOfBoundsParticles"
223
224/**
225 * @brief Internal helper implementation: `CheckAndRemoveOutOfBoundsParticles()`.
226 * @details Local to this translation unit.
227 */
229 PetscInt *removedCountLocal,
230 PetscInt *removedCountGlobal,
231 const BoundingBox *bboxlist)
232{
233 PetscErrorCode ierr;
234 DM swarm = user->swarm;
235 PetscInt nLocalInitial;
236 PetscReal *pos_p = NULL;
237 PetscInt64 *pid_p = NULL; // For better logging
238 PetscInt local_removed_count = 0;
239 PetscMPIInt global_removed_count_mpi = 0;
240 PetscMPIInt rank, size;
241
242 PetscFunctionBeginUser;
243 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
244 ierr = MPI_Comm_size(PETSC_COMM_WORLD, &size); CHKERRQ(ierr);
245 LOG_ALLOW(LOCAL, LOG_INFO, "[Rank %d] Checking for out-of-bounds particles...", rank);
246
247 // Initialize output parameters to ensure clean state
248 *removedCountLocal = 0;
249 if (removedCountGlobal) *removedCountGlobal = 0;
250
251 ierr = DMSwarmGetLocalSize(swarm, &nLocalInitial); CHKERRQ(ierr);
252
253 // Only proceed if there are particles to check on this rank.
254 // All ranks will still participate in the final collective MPI_Allreduce.
255 if (nLocalInitial > 0) {
256 // Get access to swarm fields once before the loop begins.
257 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void **)&pos_p); CHKERRQ(ierr);
258 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void **)&pid_p); CHKERRQ(ierr);
259
260 // --- Iterate BACKWARDS to handle index changes safely during removal ---
261 for (PetscInt p = nLocalInitial - 1; p >= 0; p--) {
262 PetscBool isInsideAnyBox = PETSC_FALSE;
263 Cmpnts current_pos = {pos_p[3*p + 0], pos_p[3*p + 1], pos_p[3*p + 2]};
264
265 // Check if the particle is inside ANY of the rank bounding boxes
266 for (PetscMPIInt proc = 0; proc < size; proc++) {
267 if (IsParticleInBox(&bboxlist[proc], &current_pos)) {
268 isInsideAnyBox = PETSC_TRUE;
269 break; // Particle is inside a valid domain, stop checking.
270 }
271 }
272
273 if (!isInsideAnyBox) {
274 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Removing out-of-bounds particle [PID %lld] at local index %d. Pos: (%g, %g, %g)\n",
275 rank, (long long)pid_p[p], p, current_pos.x, current_pos.y, current_pos.z);
276
277 // --- Safe Removal Pattern: Restore -> Remove -> Reacquire ---
278 // This is the fix for the double-restore bug. Pointers are managed carefully
279 // within this block and then restored cleanly after the loop.
280
281 // 1. Restore all fields BEFORE modifying the swarm structure. This invalidates pos_p and pid_p.
282 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void **)&pos_p); CHKERRQ(ierr);
283 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void **)&pid_p); CHKERRQ(ierr);
284
285 // 2. Remove the particle at the current local index 'p'.
286 ierr = DMSwarmRemovePointAtIndex(swarm, p); CHKERRQ(ierr);
287 local_removed_count++;
288
289 // 3. After removal, re-acquire pointers ONLY if the loop is not finished.
290 PetscInt nLocalCurrent;
291 ierr = DMSwarmGetLocalSize(swarm, &nLocalCurrent); CHKERRQ(ierr);
292
293 if (nLocalCurrent > 0 && p > 0) { // Check if there are particles left AND iterations left
294 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void **)&pos_p); CHKERRQ(ierr);
295 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void **)&pid_p); CHKERRQ(ierr);
296 } else {
297 // All remaining particles were removed OR this was the last particle (p=0).
298 // Invalidate pointers to prevent the final restore call and exit the loop.
299 pos_p = NULL;
300 pid_p = NULL;
301 break;
302 }
303 }
304 } // End of backwards loop
305
306 // At the end, restore any valid pointers. This handles three cases:
307 // 1. No particles were removed: restores the original pointers.
308 // 2. Particles were removed mid-loop: restores the pointers from the last re-acquisition.
309 // 3. All particles were removed: pointers are NULL, so nothing is done.
310 if (pos_p) { ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void **)&pos_p); CHKERRQ(ierr); }
311 if (pid_p) { ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void **)&pid_p); CHKERRQ(ierr); }
312 } // End of if (nLocalInitial > 0)
313
314 PetscInt nLocalFinal;
315 ierr = DMSwarmGetLocalSize(swarm, &nLocalFinal); CHKERRQ(ierr);
316 LOG_ALLOW(LOCAL, LOG_INFO, "[Rank %d] Finished removing %d out-of-bounds particles. Final local size: %d.\n", rank, local_removed_count, nLocalFinal);
317
318 // --- Synchronize counts across all ranks ---
319 *removedCountLocal = local_removed_count;
320 if (removedCountGlobal) {
321 ierr = MPI_Allreduce(&local_removed_count, &global_removed_count_mpi, 1, MPI_INT, MPI_SUM, PetscObjectComm((PetscObject)swarm)); CHKERRQ(ierr);
322 *removedCountGlobal = global_removed_count_mpi;
323 // Use a synchronized log message so only one rank prints the global total.
324 LOG_ALLOW_SYNC(GLOBAL, LOG_INFO, "[Rank %d] Removed %d out-of-bounds particles globally.\n", rank, *removedCountGlobal);
325 }
326
327 PetscFunctionReturn(0);
328}
329
330#undef __FUNCT__
331#define __FUNCT__ "CheckAndRemoveLostParticles"
332/**
333 * @brief Internal helper implementation: `CheckAndRemoveLostParticles()`.
334 * @details Local to this translation unit.
335 */
337 PetscInt *removedCountLocal,
338 PetscInt *removedCountGlobal)
339{
340 PetscErrorCode ierr;
341 DM swarm = user->swarm;
342 PetscInt nLocalInitial;
343 PetscInt *status_p = NULL;
344 PetscInt64 *pid_p = NULL; // For better logging
345 PetscReal *pos_p = NULL; // For better logging
346 PetscInt local_removed_count = 0;
347 PetscMPIInt global_removed_count_mpi = 0;
348 PetscMPIInt rank;
349
350 PetscFunctionBeginUser;
352 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
353 LOG_ALLOW(LOCAL, LOG_INFO, "Rank %d: Checking for and removing LOST particles...\n", rank);
354
355 // Initialize output parameters to ensure clean state
356 *removedCountLocal = 0;
357 if (removedCountGlobal) *removedCountGlobal = 0;
358
359 ierr = DMSwarmGetLocalSize(swarm, &nLocalInitial); CHKERRQ(ierr);
360
361 // Only proceed if there are particles to check on this rank.
362 // All ranks will still participate in the final collective MPI_Allreduce.
363 if (nLocalInitial > 0) {
364 // Get access to all swarm fields once before the loop begins.
365 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void **)&status_p); CHKERRQ(ierr);
366 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void **)&pid_p); CHKERRQ(ierr);
367 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void **)&pos_p); CHKERRQ(ierr);
368
369 // --- Iterate BACKWARDS to handle index changes safely during removal ---
370 for (PetscInt p = nLocalInitial - 1; p >= 0; p--) {
371 if (status_p[p] == LOST) {
372 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Removing LOST particle [PID %lld] at local index %d. Position: (%.4f, %.4f, %.4f).\n",
373 rank, (long long)pid_p[p], p, pos_p[3*p], pos_p[3*p+1], pos_p[3*p+2]);
374
375 // --- Safe Removal Pattern: Restore -> Remove -> Reacquire ---
376 // This is the fix for the double-restore bug. Pointers are managed carefully
377 // within this block and then restored cleanly after the loop.
378
379 // 1. Restore all fields BEFORE modifying the swarm structure. This invalidates all pointers.
380 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void **)&status_p); CHKERRQ(ierr);
381 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void **)&pid_p); CHKERRQ(ierr);
382 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void **)&pos_p); CHKERRQ(ierr);
383
384 // 2. Remove the particle at the current local index 'p'.
385 ierr = DMSwarmRemovePointAtIndex(swarm, p); CHKERRQ(ierr);
386 local_removed_count++;
387
388 // 3. After removal, re-acquire pointers ONLY if the loop is not finished.
389 PetscInt nLocalCurrent;
390 ierr = DMSwarmGetLocalSize(swarm, &nLocalCurrent); CHKERRQ(ierr);
391
392 if (nLocalCurrent > 0 && p > 0) { // Check if there are particles left AND iterations left
393 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void **)&status_p); CHKERRQ(ierr);
394 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void **)&pid_p); CHKERRQ(ierr);
395 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void **)&pos_p); CHKERRQ(ierr);
396 } else {
397 // All remaining particles were removed OR this was the last particle (p=0).
398 // Invalidate pointers to prevent the final restore call and exit the loop.
399 status_p = NULL;
400 pid_p = NULL;
401 pos_p = NULL;
402 break;
403 }
404 }
405 } // End of backwards loop
406
407 // At the end, restore any valid pointers. This handles three cases:
408 // 1. No particles were removed: restores the original pointers.
409 // 2. Particles were removed mid-loop: restores the pointers from the last re-acquisition.
410 // 3. All particles were removed: pointers are NULL, so nothing is done.
411 if (status_p) { ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void **)&status_p); CHKERRQ(ierr); }
412 if (pid_p) { ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void **)&pid_p); CHKERRQ(ierr); }
413 if (pos_p) { ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void **)&pos_p); CHKERRQ(ierr); }
414 } // End of if (nLocalInitial > 0)
415
416 PetscInt nLocalFinal;
417 ierr = DMSwarmGetLocalSize(swarm, &nLocalFinal); CHKERRQ(ierr);
418 LOG_ALLOW(LOCAL, LOG_INFO, "Rank %d: Finished removing %d LOST particles. Final local size: %d.\n", rank, local_removed_count, nLocalFinal);
419
420 // --- Synchronize counts across all ranks ---
421 *removedCountLocal = local_removed_count;
422 if (removedCountGlobal) {
423 ierr = MPI_Allreduce(&local_removed_count, &global_removed_count_mpi, 1, MPI_INT, MPI_SUM, PetscObjectComm((PetscObject)swarm)); CHKERRQ(ierr);
424 *removedCountGlobal = global_removed_count_mpi;
425 // Use a synchronized log message so only one rank prints the global total.
426 LOG_ALLOW_SYNC(GLOBAL, LOG_INFO, "[Rank %d] Removed %d LOST particles globally.\n", rank, *removedCountGlobal);
427 }
428
430 PetscFunctionReturn(0);
431}
432
433
434#undef __FUNCT__
435#define __FUNCT__ "SetMigrationRanks"
436/**
437 * @brief Internal helper implementation: `SetMigrationRanks()`.
438 * @details Local to this translation unit.
439 */
440PetscErrorCode SetMigrationRanks(UserCtx* user, const MigrationInfo *migrationList, PetscInt migrationCount)
441{
442 PetscErrorCode ierr;
443 DM swarm = user->swarm;
444 PetscInt p_idx;
445 PetscInt *rankField = NULL; // Field storing target rank
446
447 PetscFunctionBeginUser;
449
450 // Ensure the migration rank field exists
451 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_RANK), NULL, NULL, (void **)&rankField); CHKERRQ(ierr);
452
453 // Set the target rank for migrating particles
454 for(p_idx = 0; p_idx < migrationCount; ++p_idx) {
455 rankField[migrationList[p_idx].local_index] = migrationList[p_idx].target_rank;
456 }
457
458 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_RANK), NULL, NULL, (void **)&rankField); CHKERRQ(ierr);
459
461 PetscFunctionReturn(0);
462}
463
464#undef __FUNCT__
465#define __FUNCT__ "PerformMigration"
466
467/**
468 * @brief Implementation of \ref PerformMigration().
469 * @details Full API contract (arguments, ownership, side effects) is documented with
470 * the header declaration in `include/ParticleMotion.h`.
471 * @see PerformMigration()
472 */
473PetscErrorCode PerformMigration(UserCtx *user)
474{
475 PetscErrorCode ierr;
476 DM swarm = user->swarm;
477 PetscMPIInt rank;
478
479 PetscFunctionBeginUser;
481 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
482 LOG_ALLOW(GLOBAL, LOG_INFO, "Rank %d: Starting DMSwarmMigrate...\n", rank);
483
484 // Perform the migration - PETSC_TRUE removes particles that fail to land
485 // in a valid cell on the target rank (or were marked with an invalid rank).
486 ierr = DMSwarmMigrate(swarm, PETSC_TRUE); CHKERRQ(ierr);
487
488 LOG_ALLOW(GLOBAL, LOG_INFO, "Rank %d: Migration complete.\n", rank);
490 PetscFunctionReturn(0);
491}
492
493//-----------------------------------------------------------------------------
494// MODULE (COUNT): Calculates Particle Count Per Cell - REVISED FOR DMDAVecGetArray
495//-----------------------------------------------------------------------------
496
497#undef __FUNCT__
498#define __FUNCT__ "CalculateParticleCountPerCell"
499/**
500 * @brief Implementation of \ref CalculateParticleCountPerCell().
501 * @details Full API contract (arguments, ownership, side effects) is documented with
502 * the header declaration in `include/logging.h`.
503 * @see CalculateParticleCountPerCell()
504 */
506 PetscErrorCode ierr;
507 DM da = user->da;
508 DM swarm = user->swarm;
509 Vec countVec = user->ParticleCount;
510 Vec localcountVec = user->lParticleCount;
511 PetscInt nlocal, p;
512 PetscInt *global_cell_id_arr; // Read GLOBAL cell IDs
513 PetscScalar ***count_arr_3d; // Use 3D accessor
514 PetscInt64 *PID_arr;
515 PetscMPIInt rank;
516 char msg[ERROR_MSG_BUFFER_SIZE];
517 PetscInt particles_counted_locally = 0;
518
519 PetscFunctionBeginUser;
521 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
522
523 // --- Input Validation ---
524 if (!da) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx->da is NULL.");
525 if (!swarm) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx->swarm is NULL.");
526 if (!countVec) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx->ParticleCount is NULL.");
527 // Check DOF of da
528 PetscInt count_dof;
529 ierr = DMDAGetInfo(da, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &count_dof, NULL, NULL, NULL, NULL, NULL); CHKERRQ(ierr);
530 if (count_dof != 1) {
531 PetscSNPrintf(msg, sizeof(msg), "countDM must have DOF=1, got %" PetscInt_FMT ".", count_dof);
532 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "%s", msg);
533 }
534
535 // --- Zero the local count vector ---
536 ierr = VecSet(localcountVec, 0.0); CHKERRQ(ierr);
537
538 // --- Get Particle Data ---
539 LOG_ALLOW(GLOBAL,LOG_DEBUG, "Accessing particle data.\n");
540 ierr = DMSwarmGetLocalSize(swarm, &nlocal); CHKERRQ(ierr);
541 ierr = DMSwarmGetField(swarm,ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void **)&global_cell_id_arr); CHKERRQ(ierr);
542 ierr = DMSwarmGetField(swarm,ParticleFieldName(PARTICLE_FIELD_ID_PID),NULL,NULL,(void **)&PID_arr);CHKERRQ(ierr);
543
544 // --- Get Grid Vector Array using DMDA accessor ---
545 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Accessing ParticleCount vector array (using DMDAVecGetArray).\n");
546 ierr = DMDAVecGetArray(da, localcountVec, &count_arr_3d); CHKERRQ(ierr);
547
548 // Get local owned + ghosted range for writing into ghost slots.
549 PetscInt gxs, gys, gzs, gxm, gym, gzm;
550 ierr = DMDAGetGhostCorners(da, &gxs, &gys, &gzs, &gxm, &gym, &gzm); CHKERRQ(ierr);
551
552 // --- Accumulate Counts Locally ---
553 LOG_ALLOW(LOCAL, LOG_DEBUG, "CalculateParticleCountPerCell (Rank %d): Processing %" PetscInt_FMT " local particles using GLOBAL CellIDs.\n",rank,nlocal);
554 for (p = 0; p < nlocal; p++) {
555 // Read the GLOBAL indices stored for this particle
556 PetscInt i_geom = global_cell_id_arr[p * 3 + 0]; // Global i index
557 PetscInt j_geom = global_cell_id_arr[p * 3 + 1]; // Global j index
558 PetscInt k_geom = global_cell_id_arr[p * 3 + 2]; // Global k index
559
560 // Apply the shift to ensure ParticleCount follows the indexing convention for cell-centered data in this codebase.
561 PetscInt i = (PetscInt)i_geom + 1; // Shift for cell-centered
562 PetscInt j = (PetscInt)j_geom + 1; // Shift for cell-centered
563 PetscInt k = (PetscInt)k_geom + 1; // Shift for cell-centered
564
565 // *** Bounds check is implicitly handled by DMDAVecGetArray for owned+ghost region ***
566 // However, accessing outside this region using global indices WILL cause an error.
567 // A preliminary check might still be wise if global IDs could be wild.
568 // We rely on LocateAllParticles to provide valid global indices [0..IM-1] etc.
569
571 "[Rank %d] Read CellID for p=%" PetscInt_FMT ", PID = %" PetscInt64_FMT ": (%" PetscInt_FMT ", %" PetscInt_FMT ", %" PetscInt_FMT ")\n",
572 rank, p, PID_arr[p], i, j, k);
573
574 // Check if the global index (i,j,k) falls within the local + ghost range
575 if (i >= gxs && i < gxs + gxm &&
576 j >= gys && j < gys + gym && // Adjust based on actual ghost width
577 k >= gzs && k < gzs + gzm ) // This check prevents definite crashes but doesn't guarantee ownership
578 {
579
580 // Increment count at the location corresponding to GLOBAL index (I,J,K)
581 // LOG_ALLOW(LOCAL, LOG_DEBUG, "CalculateParticleCountPerCell (Rank %d): Particle %d with global CellID (%d, %d, %d) incremented with a particle.\n",rank, p, i, j, k);
582 count_arr_3d[k][j][i] += 1.0;
583 particles_counted_locally++;
584 } else {
585 // This particle's global ID is likely outside the range this rank handles (even ghosts)
586 // note: this is not necessarily an error if the particle is legitimately outside the local+ghost region
588 "(Rank %d): Skipping particle %" PetscInt64_FMT " with global CellID (%" PetscInt_FMT ", %" PetscInt_FMT ", %" PetscInt_FMT ") - likely outside local+ghost range.\n",
589 rank, PID_arr[p], i, j, k);
590 }
591 }
592 LOG_ALLOW(LOCAL, LOG_DEBUG, "(Rank %d): Local counting finished. Processed %" PetscInt_FMT " particles locally.\n", rank, particles_counted_locally);
593
594 // --- Restore Access ---
595 ierr = DMDAVecRestoreArray(da, localcountVec, &count_arr_3d); CHKERRQ(ierr);
596 ierr = DMSwarmRestoreField(swarm,ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void **)&global_cell_id_arr); CHKERRQ(ierr);
597 ierr = DMSwarmRestoreField(swarm,ParticleFieldName(PARTICLE_FIELD_ID_PID),NULL,NULL,(void **)&PID_arr);CHKERRQ(ierr);
598
599 // --- Assemble Global Vector ---
600 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Assembling global ParticleCount vector.\n");
601 ierr = VecZeroEntries(countVec); CHKERRQ(ierr); // Ensure global vector is zeroed before accumulation
602 ierr = DMLocalToGlobalBegin(da, localcountVec, ADD_VALUES, countVec); CHKERRQ(ierr);
603 ierr = DMLocalToGlobalEnd(da, localcountVec, ADD_VALUES, countVec); CHKERRQ(ierr);
604 /*
605 * OPTIONAL: Synchronize Ghosts for Stencil Operations
606 * If a future function needs to read ParticleCount from neighbor cells (e.g., density smoothing
607 * or gradient calculations), uncomment the following lines to update the ghost slots
608 * in user->lParticleCount with the final summed values.
609 *
610 ierr = UpdateLocalGhosts(user, FIELD_ID_PARTICLE_COUNT); CHKERRQ(ierr);
611 */
612
613 // --- Verification Logging ---
614 PetscReal total_counted_particles = 0.0, max_count_in_cell = 0.0;
615 ierr = VecSum(countVec, &total_counted_particles); CHKERRQ(ierr);
616 PetscInt max_idx_global = -1;
617 ierr = VecMax(countVec, &max_idx_global, &max_count_in_cell); CHKERRQ(ierr);
618 LOG_ALLOW(GLOBAL, LOG_INFO, "Total counted globally = %.0f, Max count in cell = %.0f\n",
619 total_counted_particles, max_count_in_cell);
620
621 // --- ADD THIS DEBUGGING BLOCK ---
622 if (max_idx_global >= 0) { // Check if VecMax found a location
623 // Need to convert the flat global index back to 3D global index (I, J, K)
624 // Get global grid dimensions (Nodes, NOT Cells IM/JM/KM)
625 PetscInt M, N, P;
626 ierr = DMDAGetInfo(da, NULL, &M, &N, &P, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); CHKERRQ(ierr);
627 // Note: Assuming DOF=1 for countVec, index mapping uses node dimensions M,N,P from DMDA creation (IM+1, etc)
628 // Re-check if your DMDA uses cell counts (IM) or node counts (IM+1) for Vec layout. Let's assume Node counts M,N,P.
629 PetscInt Kmax = max_idx_global / (M * N);
630 PetscInt Jmax = (max_idx_global % (M * N)) / M;
631 PetscInt Imax = max_idx_global % M;
632 LOG_ALLOW(GLOBAL, LOG_INFO, " -> Max count located at global index (I,J,K) = (%d, %d, %d) [Flat index: %d]\n",
633 (int)Imax, (int)Jmax, (int)Kmax, (int)max_idx_global);
634
635 // Also, let's explicitly check the count at (0,0,0)
636 PetscScalar count_at_origin = 0.0;
637 PetscScalar ***count_arr_for_check;
638 ierr = DMDAVecGetArrayRead(da, countVec, &count_arr_for_check); CHKERRQ(ierr);
639 // Check bounds before accessing - crucial if using global indices
640 PetscInt xs, ys, zs, xm, ym, zm;
641 ierr = DMDAGetCorners(da, &xs, &ys, &zs, &xm, &ym, &zm); CHKERRQ(ierr);
642 if (0 >= xs && 0 < xs+xm && 0 >= ys && 0 < ys+ym && 0 >= zs && 0 < zs+zm) {
643 count_at_origin = count_arr_for_check[0][0][0]; // Access using global index (0,0,0)
644 } else {
645 // Origin is not on this rank (relevant for parallel, but check anyway)
646 count_at_origin = -999.0; // Indicate it wasn't accessible locally
647 }
648 ierr = DMDAVecRestoreArrayRead(da, countVec, &count_arr_for_check); CHKERRQ(ierr);
649 LOG_ALLOW(GLOBAL, LOG_INFO, " -> Count at global index (0,0,0) = %.1f\n", count_at_origin);
650
651 } else {
652 LOG_ALLOW(GLOBAL, LOG_WARNING, " -> VecMax did not return a location for the maximum value.\n");
653 }
654 // --- END DEBUGGING BLOCK ---
655
656 LOG_ALLOW(GLOBAL, LOG_INFO, "Particle counting complete.\n");
657
658
660 PetscFunctionReturn(0);
661}
662
663
664
665#undef __FUNCT__
666#define __FUNCT__ "ResizeSwarmGlobally"
667/**
668 * @brief Implementation of \ref ResizeSwarmGlobally().
669 * @details Full API contract (arguments, ownership, side effects) is documented with
670 * the header declaration in `include/ParticleMotion.h`.
671 * @see ResizeSwarmGlobally()
672 */
673
674PetscErrorCode ResizeSwarmGlobally(DM swarm, PetscInt N_target)
675{
676 PetscErrorCode ierr;
677 PetscInt N_current, N_final, nlocal_current, nlocal_target;
678 PetscMPIInt rank, size;
679 MPI_Comm comm;
680
681 PetscFunctionBeginUser;
683 ierr = PetscObjectGetComm((PetscObject)swarm, &comm); CHKERRQ(ierr);
684 ierr = MPI_Comm_rank(comm, &rank); CHKERRQ(ierr);
685 ierr = MPI_Comm_size(comm, &size); CHKERRQ(ierr);
686 PetscCheck(N_target >= 0, comm, PETSC_ERR_ARG_OUTOFRANGE,
687 "Target swarm size must be nonnegative; got %" PetscInt_FMT ".", N_target);
688 ierr = DMSwarmGetSize(swarm, &N_current); CHKERRQ(ierr);
689 ierr = DMSwarmGetLocalSize(swarm, &nlocal_current); CHKERRQ(ierr);
690 nlocal_target = N_target / size + (rank < N_target % size ? 1 : 0);
691
692 if (nlocal_current != nlocal_target) {
694 "Rank %d: resizing local swarm share from %" PetscInt_FMT
695 " to %" PetscInt_FMT ".\n",
696 rank, nlocal_current, nlocal_target);
697 ierr = DMSwarmSetLocalSizes(swarm, nlocal_target, -1); CHKERRQ(ierr);
698 }
699
700 // Verify final size
701 ierr = DMSwarmGetSize(swarm, &N_final); CHKERRQ(ierr);
702 if (N_final != N_target) {
703 SETERRQ(comm, PETSC_ERR_PLIB,
704 "Failed to resize swarm: expected %" PetscInt_FMT
705 " particles, got %" PetscInt_FMT, N_target, N_final);
706 }
708 "Swarm resized from %" PetscInt_FMT " to %" PetscInt_FMT " particles.\n",
709 N_current, N_final);
711 PetscFunctionReturn(0);
712}
713
714#undef __FUNCT__
715#define __FUNCT__ "PreCheckAndResizeSwarm"
716/**
717 * @brief Internal helper implementation: `PreCheckAndResizeSwarm()`.
718 * @details Local to this translation unit.
719 */
720PetscErrorCode PreCheckAndResizeSwarm(UserCtx *user,
721 PetscInt ti,
722 const char *ext)
723{
724 PetscErrorCode ierr;
725 PetscInt N_file = 0;
726 PetscInt N_current = 0;
727
728 PetscFunctionBeginUser;
730 (void)ext;
731 ierr = ReadCheckpointParticleCount(user, ti, &N_file); CHKERRQ(ierr);
733 "Committed checkpoint step %d records %d particles.\n", ti, N_file);
734
735
736 // --- Now all ranks have the correct N_file, compare and resize if needed ---
737 ierr = DMSwarmGetSize(user->swarm, &N_current); CHKERRQ(ierr);
738
739 if (N_file != N_current) {
740 LOG_ALLOW(GLOBAL, LOG_INFO, "Swarm size %d differs from file size %d. Resizing swarm globally.\n", N_current, N_file);
741 ierr = ResizeSwarmGlobally(user->swarm, N_file); CHKERRQ(ierr);
742 } else {
743 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Swarm size (%d) already matches file size. No resize needed.\n", N_current);
744 }
745
746 // Also update the context
747 user->simCtx->np = N_file;
748
750 PetscFunctionReturn(0);
751}
752
753
754#undef __FUNCT__
755#define __FUNCT__ "ReinitializeParticlesOnInletSurface"
756/**
757 * @brief Internal helper implementation: `ReinitializeParticlesOnInletSurface()`.
758 * @details Local to this translation unit.
759 */
760PetscErrorCode ReinitializeParticlesOnInletSurface(UserCtx *user, PetscReal currentTime, PetscInt step)
761{
762 PetscErrorCode ierr;
763 PetscMPIInt rank; // MPI rank of the current process
764 DM swarm = user->swarm; // The particle swarm DM
765 PetscReal *positions_field = NULL; // Pointer to swarm field for physical positions
766 PetscInt64 *particleIDs = NULL; // Pointer to swarm field for Particle IDs (for logging)
767 PetscInt *cell_ID_field = NULL; // Pointer to swarm field for Cell IDs (for resetting after migration)
768 const Cmpnts ***coor_nodes_local_array; // Read-only access to local node coordinates
769 Vec Coor_local; // Local vector for node coordinates
770 DMDALocalInfo info; // Local grid information (node-based) from user->da
771 PetscInt xs_gnode_rank, ys_gnode_rank, zs_gnode_rank; // Local starting node indices (incl. ghosts) of rank's DA
772 PetscInt IM_nodes_global, JM_nodes_global, KM_nodes_global; // Global node counts
773
774 PetscRandom rand_logic_reinit_i, rand_logic_reinit_j, rand_logic_reinit_k; // RNGs for re-placement
775 PetscInt nlocal_current; // Number of particles currently on this rank
776 PetscInt particles_actually_reinitialized_count = 0; // Counter for logging
777 PetscBool can_this_rank_service_inlet = PETSC_FALSE; // Flag
778
779 PetscFunctionBeginUser;
780
782
783 // This function is only relevant for surface initialization mode and if an inlet face is defined.
784 if ((user->simCtx->ParticleInitialization != 0 && user->simCtx->ParticleInitialization !=3) || !user->inletFaceDefined) {
786 PetscFunctionReturn(0);
787 }
788
789 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
790 ierr = DMSwarmGetLocalSize(swarm, &nlocal_current); CHKERRQ(ierr);
791
792 // If no particles on this rank, nothing to do.
793 if (nlocal_current == 0) {
794 LOG_ALLOW(LOCAL, LOG_DEBUG, "[T=%.4f, Step=%d] Rank %d has no local particles to re-initialize on inlet.\n", currentTime, step, rank);
796 PetscFunctionReturn(0);
797 }
798
799 // Get DMDA information for the node-centered coordinate grid (user->da)
800 ierr = DMDAGetLocalInfo(user->da, &info); CHKERRQ(ierr);
801 ierr = DMDAGetInfo(user->da, NULL, &IM_nodes_global, &JM_nodes_global, &KM_nodes_global, NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); CHKERRQ(ierr);
802 ierr = DMDAGetCorners(user->da, &xs_gnode_rank, &ys_gnode_rank, &zs_gnode_rank, NULL, NULL, NULL); CHKERRQ(ierr);
803
804 // Modification to IM_nodes_global etc. to account for 1-cell halo in each direction.
805 IM_nodes_global -= 1; JM_nodes_global -= 1; KM_nodes_global -= 1;
806
807 const PetscInt IM_cells_global = IM_nodes_global > 0 ? IM_nodes_global - 1 : 0;
808 const PetscInt JM_cells_global = JM_nodes_global > 0 ? JM_nodes_global - 1 : 0;
809 const PetscInt KM_cells_global = KM_nodes_global > 0 ? KM_nodes_global - 1 : 0;
810
811
812
813 // Check if this rank is responsible for (part of) the designated inlet surface
814 ierr = CanRankServiceInletFace(user, &info, IM_nodes_global, JM_nodes_global, KM_nodes_global, &can_this_rank_service_inlet); CHKERRQ(ierr);
815
816 // Get coordinate array and swarm fields for modification
817 ierr = DMGetCoordinatesLocal(user->da, &Coor_local); CHKERRQ(ierr);
818 ierr = DMDAVecGetArrayRead(user->fda, Coor_local, (void*)&coor_nodes_local_array); CHKERRQ(ierr);
819 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&positions_field); CHKERRQ(ierr);
820 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&particleIDs); CHKERRQ(ierr); // For logging
821 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_ID_field); CHKERRQ(ierr);
822
823 if (!can_this_rank_service_inlet) {
824 LOG_ALLOW(LOCAL, LOG_DEBUG, "[T=%.4f, Step=%d] Rank %d cannot service inlet face %s. Skipping re-initialization of %d particles.\n", currentTime, step, rank, BCFaceToString(user->identifiedInletBCFace), nlocal_current);
825
826 // FALLBACK ACTION: Reset position fields to Inlet center for migration and cell ID to -1 for safety.
827 LOG_ALLOW(LOCAL, LOG_DEBUG, "[T=%.4f, Step=%d] Rank %d is resetting %d local particles to inlet center (%.6f, %.6f, %.6f) for migration.\n", currentTime, step, rank, nlocal_current, user->simCtx->CMx_c, user->simCtx->CMy_c, user->simCtx->CMz_c);
828
829 for(PetscInt p = 0; p < nlocal_current; p++){
830 positions_field[3*p+0] = user->simCtx->CMx_c;
831 positions_field[3*p+1] = user->simCtx->CMy_c;
832 positions_field[3*p+2] = user->simCtx->CMz_c;
833
834 cell_ID_field[3*p+0] = -1;
835 cell_ID_field[3*p+1] = -1;
836 cell_ID_field[3*p+2] = -1;
837 }
838
839 // Cleanup: restore swarm fields/coordinate array
840 ierr = DMDAVecRestoreArrayRead(user->fda, Coor_local, (void*)&coor_nodes_local_array); CHKERRQ(ierr);
841 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&positions_field); CHKERRQ(ierr);
842 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&particleIDs); CHKERRQ(ierr); // For logging
843 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_ID_field); CHKERRQ(ierr);
845 PetscFunctionReturn(0);
846 }
847
848 LOG_ALLOW(GLOBAL, LOG_INFO, "[T=%.4f, Step=%d] Rank %d is on inlet face %s. Attempting to re-place %d local particles.\n", currentTime, step, rank, BCFaceToString(user->identifiedInletBCFace), nlocal_current);
849
850 // Initialize fresh RNGs for this re-placement to ensure good distribution
851 ierr = InitializeLogicalSpaceRNGs(&rand_logic_reinit_i, &rand_logic_reinit_j, &rand_logic_reinit_k); CHKERRQ(ierr);
852 // Optional: Seed RNGs for deterministic behavior if required, e.g., based on rank and step.
853 // PetscRandomSetSeed(rand_logic_i, (unsigned long)rank*1000 + step + 100); PetscRandomSeed(rand_logic_i); // Example
854
855 // Loop over all particles currently local to this rank
856 for (PetscInt p = 0; p < nlocal_current; p++) {
857 PetscInt ci_metric_lnode, cj_metric_lnode, ck_metric_lnode; // Local node indices (of rank's DA patch) for cell origin
858 PetscReal xi_metric_logic, eta_metric_logic, zta_metric_logic; // Intra-cell logical coordinates
859 Cmpnts phys_coords = {0.0,0.0,0.0}; // To store newly calculated physical coordinates
860 PetscBool particle_was_placed = PETSC_FALSE;
861
863 // Get random cell on this rank's portion of the inlet and random logical coords within it
864 ierr = GetRandomCellAndLogicalCoordsOnInletFace(user, &info, xs_gnode_rank, ys_gnode_rank, zs_gnode_rank,
865 IM_nodes_global, JM_nodes_global, KM_nodes_global,
866 &rand_logic_reinit_i, &rand_logic_reinit_j, &rand_logic_reinit_k,
867 &ci_metric_lnode, &cj_metric_lnode, &ck_metric_lnode,
868 &xi_metric_logic, &eta_metric_logic, &zta_metric_logic); CHKERRQ(ierr);
869
870 // Convert these logical coordinates to physical coordinates
871
872 ierr = MetricLogicalToPhysical(user, coor_nodes_local_array,
873 ci_metric_lnode, cj_metric_lnode, ck_metric_lnode,
874 xi_metric_logic, eta_metric_logic, zta_metric_logic,
875 &phys_coords); CHKERRQ(ierr);
876
877 // Update the particle's position in the swarm fields
878 positions_field[3*p+0] = phys_coords.x;
879 positions_field[3*p+1] = phys_coords.y;
880 positions_field[3*p+2] = phys_coords.z;
881 particle_was_placed = PETSC_TRUE;
882
884 PetscBool placement_flag = PETSC_FALSE;
885 ierr = GetDeterministicFaceGridLocation(user, &info, xs_gnode_rank, ys_gnode_rank, zs_gnode_rank,
886 IM_cells_global, JM_cells_global, KM_cells_global,
887 particleIDs[p],
888 &ci_metric_lnode, &cj_metric_lnode, &ck_metric_lnode,
889 &xi_metric_logic, &eta_metric_logic, &zta_metric_logic,&placement_flag); CHKERRQ(ierr);
890
891
892 if(placement_flag){
893 // Convert these logical coordinates to physical coordinates
894 ierr = MetricLogicalToPhysical(user, coor_nodes_local_array,
895 ci_metric_lnode, cj_metric_lnode, ck_metric_lnode,
896 xi_metric_logic, eta_metric_logic, zta_metric_logic,
897 &phys_coords); CHKERRQ(ierr);
898
899 // Update the particle's position in the swarm fields
900 positions_field[3*p+0] = phys_coords.x;
901 positions_field[3*p+1] = phys_coords.y;
902 positions_field[3*p+2] = phys_coords.z;
903 particle_was_placed = PETSC_TRUE;
904 } else{
905 // Deterministic placement failed (particle migrated to rank where formula says it doesn't belong)
906 // Fall back to random placement on this rank's portion of inlet surface
907 LOG_ALLOW(GLOBAL, LOG_WARNING, "Rank %d: Particle PID %ld deterministic placement failed (belongs to different rank). Falling back to random placement.\n", rank, particleIDs[p]);
908
909 ierr = GetRandomCellAndLogicalCoordsOnInletFace(user, &info, xs_gnode_rank, ys_gnode_rank, zs_gnode_rank,
910 IM_nodes_global, JM_nodes_global, KM_nodes_global,
911 &rand_logic_reinit_i, &rand_logic_reinit_j, &rand_logic_reinit_k,
912 &ci_metric_lnode, &cj_metric_lnode, &ck_metric_lnode,
913 &xi_metric_logic, &eta_metric_logic, &zta_metric_logic); CHKERRQ(ierr);
914
915 // Convert to physical coordinates
916 ierr = MetricLogicalToPhysical(user, coor_nodes_local_array,
917 ci_metric_lnode, cj_metric_lnode, ck_metric_lnode,
918 xi_metric_logic, eta_metric_logic, zta_metric_logic,
919 &phys_coords); CHKERRQ(ierr);
920
921 // Update particle position
922 positions_field[3*p+0] = phys_coords.x;
923 positions_field[3*p+1] = phys_coords.y;
924 positions_field[3*p+2] = phys_coords.z;
925 particle_was_placed = PETSC_TRUE;
926 }
927
928 } else{
929 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "ReinitializeParticlesOnInletSurface only supports ParticleInitialization modes 0 and 3.");
930 }
931
932 if(particle_was_placed){
933 particles_actually_reinitialized_count++;
934
935 cell_ID_field[3*p+0] = -1;
936 cell_ID_field[3*p+1] = -1;
937 cell_ID_field[3*p+2] = -1;
938
939 LOG_LOOP_ALLOW(LOCAL, LOG_VERBOSE, p, (nlocal_current > 20 ? nlocal_current/10 : 1), // Sampled logging
940 "Rank %d: PID %ld (idx %ld) RE-PLACED. CellOriginNode(locDAIdx):(%d,%d,%d). LogicCoords: (%.2e,%.2f,%.2f). PhysCoords: (%.6f,%.6f,%.6f).\n",
941 rank, particleIDs[p], (long)p,
942 ci_metric_lnode, cj_metric_lnode, ck_metric_lnode,
943 xi_metric_logic, eta_metric_logic, zta_metric_logic,
944 phys_coords.x, phys_coords.y, phys_coords.z);
945 }
946 }
947
948 // Logging summary of re-initialization
949 if (particles_actually_reinitialized_count > 0) {
950 LOG_ALLOW(GLOBAL, LOG_INFO, "[T=%.4f, Step=%d] Rank %d (on inlet face %d) successfully re-initialized %d of %d local particles.\n", currentTime, step, rank, user->identifiedInletBCFace, particles_actually_reinitialized_count, nlocal_current);
951 } else if (nlocal_current > 0) { // This case should ideally not be hit if can_this_rank_service_inlet was true and particles were present.
952 LOG_ALLOW(GLOBAL, LOG_WARNING, "[T=%.4f, Step=%d] Rank %d claimed to service inlet face %d, but re-initialized 0 of %d local particles. This may indicate an issue if particles were expected to be re-placed.\n", currentTime, step, rank, user->identifiedInletBCFace, nlocal_current);
953 }
954
955 // Cleanup: Destroy RNGs and restore swarm fields/coordinate array
956 ierr = PetscRandomDestroy(&rand_logic_reinit_i); CHKERRQ(ierr);
957 ierr = PetscRandomDestroy(&rand_logic_reinit_j); CHKERRQ(ierr);
958 ierr = PetscRandomDestroy(&rand_logic_reinit_k); CHKERRQ(ierr);
959
960 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&positions_field); CHKERRQ(ierr);
961 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&particleIDs); CHKERRQ(ierr);
962 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_ID_field); CHKERRQ(ierr);
963 ierr = DMDAVecRestoreArrayRead(user->fda, Coor_local, (void*)&coor_nodes_local_array); CHKERRQ(ierr);
964
965
967 PetscFunctionReturn(0);
968}
969
970#undef __FUNCT__
971#define __FUNCT__ "GetLocalPIDSnapshot"
972/**
973 * @brief Internal helper implementation: `GetLocalPIDSnapshot()`.
974 * @details Local to this translation unit.
975 */
976PetscErrorCode GetLocalPIDSnapshot(const PetscInt64 pid_field[],
977 PetscInt n_local,
978 PetscInt64 **pids_snapshot_out)
979{
980 PetscErrorCode ierr;
981 PetscMPIInt rank;
982
983 PetscFunctionBeginUser;
984
986
987 // --- 1. Input Validation ---
988 if (!pids_snapshot_out) {
989 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Output pointer pids_snapshot_out is NULL.");
990 }
991 // If n_local > 0, pid_field must not be NULL.
992 if (n_local > 0 && !pid_field) {
993 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Input pid_field pointer is NULL for n_local > 0.");
994 }
995
996 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
997 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d]: Creating PID snapshot for %d local particles.\n", rank, n_local);
998
999 // If there are no local particles, the snapshot is empty (NULL).
1000 if (n_local == 0) {
1001 *pids_snapshot_out = NULL;
1002
1004 PetscFunctionReturn(0);
1005 }
1006
1007 // --- 2. Allocate Memory for the Snapshot ---
1008 ierr = PetscMalloc1(n_local, pids_snapshot_out); CHKERRQ(ierr);
1009
1010 // --- 3. Copy Data ---
1011 // Perform a fast memory copy from the provided array to our new snapshot array.
1012 ierr = PetscMemcpy(*pids_snapshot_out, pid_field, n_local * sizeof(PetscInt64)); CHKERRQ(ierr);
1013 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d]: Copied %d PIDs.\n", rank, n_local);
1014
1015 // --- 4. Sort the Snapshot Array ---
1016 // Sorting enables fast binary search lookups later.
1017 ierr = PetscSortInt64(n_local, *pids_snapshot_out); CHKERRQ(ierr);
1018 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d]: PID snapshot sorted successfully.\n", rank);
1019
1020
1022 PetscFunctionReturn(0);
1023}
1024
1025#undef __FUNCT__
1026#define __FUNCT__ "AddToMigrationList"
1027/**
1028 * @brief Internal helper implementation: `AddToMigrationList()`.
1029 * @details Local to this translation unit.
1030 */
1031PetscErrorCode AddToMigrationList(MigrationInfo **migration_list_p,
1032 PetscInt *capacity_p,
1033 PetscInt *count_p,
1034 PetscInt particle_local_idx,
1035 PetscMPIInt destination_rank)
1036{
1037 PetscErrorCode ierr;
1038 PetscMPIInt rank;
1039
1040 PetscFunctionBeginUser;
1041
1043
1044 // --- 1. Input Validation ---
1045 if (!migration_list_p || !capacity_p || !count_p) {
1046 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Null pointer provided to AddToMigrationList for list management.");
1047 }
1048
1049 // --- 2. Check if the list needs to be resized ---
1050 if (*count_p >= *capacity_p) {
1051 PetscInt old_capacity = *capacity_p;
1052 // Start with a reasonable base capacity, then double for subsequent reallocations.
1053 PetscInt new_capacity = (old_capacity == 0) ? 16 : old_capacity * 2;
1054
1055 // Use PetscRealloc for safe memory reallocation.
1056 // It handles allocating new memory, copying old data, and freeing the old block.
1057 // The first argument to PetscRealloc is the new size in BYTES.
1058 ierr = PetscRealloc(new_capacity * sizeof(MigrationInfo), migration_list_p); CHKERRQ(ierr);
1059
1060 *capacity_p = new_capacity; // Update the capacity tracker
1061
1062 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1063 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d]: Reallocated migrationList capacity from %d to %d.\n",
1064 rank, old_capacity, new_capacity);
1065 }
1066
1067 // --- 3. Add the new migration data to the list ---
1068 // Dereference the pointer-to-a-pointer to get the actual array.
1069 MigrationInfo *list = *migration_list_p;
1070
1071 list[*count_p].local_index = particle_local_idx;
1072 list[*count_p].target_rank = destination_rank;
1073
1074 // --- 4. Increment the count of items in the list ---
1075 (*count_p)++;
1076
1077
1079 PetscFunctionReturn(0);
1080}
1081
1082
1083#undef __FUNCT__
1084#define __FUNCT__ "FlagNewComersForLocation"
1085/**
1086 * @brief Internal helper implementation: `FlagNewcomersForLocation()`.
1087 * @details Local to this translation unit.
1088 */
1089PetscErrorCode FlagNewcomersForLocation(DM swarm,
1090 PetscInt n_local_before,
1091 const PetscInt64 pids_before[])
1092{
1093 PetscErrorCode ierr;
1094 PetscMPIInt rank;
1095 PetscInt n_local_after;
1096 PetscInt newcomer_count = 0;
1097
1098 // Pointers to the swarm data fields we will read and modify
1099 PetscInt64 *pid_field_after = NULL;
1100 PetscInt *status_field_after = NULL;
1101 PetscInt *cell_field_after = NULL;
1102
1103 PetscFunctionBeginUser;
1104
1106
1107 // --- 1. Input Validation and Basic Setup ---
1108 if (!swarm) {
1109 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, "Input DMSwarm is NULL in FlagNewcomersForLocation.");
1110 }
1111 // If n_local_before > 0, the corresponding PID array must not be null.
1112 if (n_local_before > 0 && !pids_before) {
1113 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Input pids_before array is NULL for n_local_before > 0.");
1114 }
1115
1116 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1117
1118 // Get the number of particles on this rank *after* the migration.
1119 ierr = DMSwarmGetLocalSize(swarm, &n_local_after); CHKERRQ(ierr);
1120
1121 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d]: Checking for newcomers. Size before: %d, Size after: %d\n",
1122 rank, n_local_before, n_local_after);
1123
1124 // If there are no particles now, there's nothing to do.
1125 if (n_local_after == 0) {
1127 PetscFunctionReturn(0);
1128 }
1129
1130 // --- 2. Access Swarm Data ---
1131 // Get read-only access to the PIDs and read-write access to the status field.
1132 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid_field_after); CHKERRQ(ierr);
1133 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status_field_after); CHKERRQ(ierr);
1134 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_field_after); CHKERRQ(ierr);
1135 if (!pid_field_after || !status_field_after) {
1136 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, "Failed to get required swarm fields in FlagNewcomersForLocation.");
1137 }
1138
1139 // --- 3. Identify and Flag Newcomers ---
1140 // Loop through all particles currently on this rank.
1141 for (PetscInt p_idx = 0; p_idx < n_local_after; ++p_idx) {
1142 PetscInt64 current_pid = pid_field_after[p_idx];
1143 PetscBool is_found_in_before_list;
1144
1145 // Use our custom, efficient helper function for the lookup.
1146 ierr = BinarySearchInt64(n_local_before, pids_before, current_pid, &is_found_in_before_list); CHKERRQ(ierr);
1147
1148 // If the PID was NOT found in the "before" list, it must be a newcomer.
1149 if (!is_found_in_before_list) {
1150 // Flag it for processing in the next pass of the migration loop.
1151 status_field_after[p_idx] = NEEDS_LOCATION;
1152 // cell_field_after[3*p_idx+0] = -1;
1153 // cell_field_after[3*p_idx+1] = -1;
1154 // cell_field_after[3*p_idx+2] = -1;
1155 newcomer_count++;
1156
1157 LOG_ALLOW(LOCAL, LOG_VERBOSE, "[Rank %d]: Flagged newcomer PID %ld at local index %d as NEEDS_LOCATION.\n",
1158 rank, current_pid, p_idx);
1159 }
1160 }
1161
1162 // --- 4. Restore Swarm Fields ---
1163 // Release the locks on the swarm data arrays.
1164 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid_field_after); CHKERRQ(ierr);
1165 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status_field_after); CHKERRQ(ierr);
1166 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_field_after); CHKERRQ(ierr);
1167
1168 if (newcomer_count > 0) {
1169 LOG_ALLOW(LOCAL, LOG_INFO, "[Rank %d]: Identified and flagged %d newcomers.\n", rank, newcomer_count);
1170 }
1171
1172
1174 PetscFunctionReturn(0);
1175}
1176
1177#undef __FUNCT__
1178#define __FUNCT__ "MigrateRestartParticlesUsingCellID"
1179/**
1180 * @brief Internal helper implementation: `MigrateRestartParticlesUsingCellID()`.
1181 * @details Local to this translation unit.
1182 */
1184{
1185 PetscErrorCode ierr;
1186 DM swarm = user->swarm;
1187 PetscInt nlocal;
1188 PetscInt *cell_p = NULL;
1189 PetscInt64 *pid_p = NULL;
1190 PetscMPIInt rank;
1191
1192 MigrationInfo *migrationList = NULL;
1193 PetscInt local_migration_count = 0;
1194 PetscInt migrationListCapacity = 0;
1195 PetscInt global_migration_count = 0;
1196
1197 PetscFunctionBeginUser;
1199 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1200
1201 ierr = DMSwarmGetLocalSize(swarm, &nlocal); CHKERRQ(ierr);
1202 LOG_ALLOW(LOCAL, LOG_DEBUG, "Checking %d restart particles for direct migration using CellIDs.\n", nlocal);
1203
1204 if (nlocal > 0) {
1205 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_p); CHKERRQ(ierr);
1206 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid_p); CHKERRQ(ierr);
1207
1208 // Note: We do NOT need to modify the status field here.
1209 // We trust the loaded status (ACTIVE_AND_LOCATED) is correct for the destination rank.
1210
1211 for (PetscInt p_idx = 0; p_idx < nlocal; ++p_idx) {
1212 PetscInt ci = cell_p[3*p_idx + 0];
1213 PetscInt cj = cell_p[3*p_idx + 1];
1214 PetscInt ck = cell_p[3*p_idx + 2];
1215
1216 /* Skip particles with invalid Cell IDs (will be handled by LocateAllParticles) */
1217 if (ci < 0 || cj < 0 || ck < 0) {
1218 continue;
1219 }
1220
1221 PetscMPIInt owner_rank;
1222 ierr = FindOwnerOfCell(user, ci, cj, ck, &owner_rank); CHKERRQ(ierr);
1223
1224 if (owner_rank != -1 && owner_rank != rank) {
1225 /* Particle belongs to another rank - migrate it */
1226 ierr = AddToMigrationList(&migrationList, &migrationListCapacity, &local_migration_count,
1227 p_idx, owner_rank); CHKERRQ(ierr);
1228
1229 LOG_ALLOW(LOCAL, LOG_VERBOSE, "[PID %ld] Direct migration: Cell (%d,%d,%d) belongs to Rank %d (Current: %d).\n",
1230 (long)pid_p[p_idx], ci, cj, ck, owner_rank, rank);
1231 }
1232 }
1233
1234 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_p); CHKERRQ(ierr);
1235 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid_p); CHKERRQ(ierr);
1236 }
1237
1238 /* Check if any rank needs to migrate particles */
1239 ierr = MPI_Allreduce(&local_migration_count, &global_migration_count, 1, MPIU_INT, MPI_SUM, PETSC_COMM_WORLD); CHKERRQ(ierr);
1240
1241 if (global_migration_count > 0) {
1242 LOG_ALLOW(GLOBAL, LOG_INFO, "Fast restart migration: Directly migrating %d particles using CellIDs.\n", global_migration_count);
1243 ierr = SetMigrationRanks(user, migrationList, local_migration_count); CHKERRQ(ierr);
1244 ierr = PerformMigration(user); CHKERRQ(ierr);
1245 /* We do NOT flag newcomers here. We trust their loaded status (ACTIVE_AND_LOCATED) */
1246 /* is valid for their destination rank. */
1247 } else {
1248 LOG_ALLOW(GLOBAL, LOG_INFO, "Fast restart migration: All particles are already on correct ranks.\n");
1249 }
1250
1251 ierr = PetscFree(migrationList); CHKERRQ(ierr);
1252
1254 PetscFunctionReturn(0);
1255}
1256
1257#undef __FUNCT__
1258#define __FUNCT__ "GuessParticleOwnerWithBBox"
1259/**
1260 * @brief Select the rank whose gathered bounding box is the best owner candidate for a particle.
1261 * @note Testing status:
1262 * The current direct surface reaches this helper through orchestrator
1263 * tests, but direction-complete immediate-neighbor coverage and the
1264 * explicit "not found in any rank" path are still targeted for future
1265 * bespoke tests.
1266 */
1267static PetscErrorCode GuessParticleOwnerWithBBox(UserCtx *user,
1268 const Particle *particle,
1269 const BoundingBox *bboxlist,
1270 PetscMPIInt *guess_rank_out)
1271{
1272 PetscErrorCode ierr;
1273 PetscMPIInt rank, size;
1274 const RankNeighbors *neighbors = &user->neighbors; // Use a direct pointer for clarity
1275 const BoundingBox *localBBox = &user->bbox;
1276
1277 PetscFunctionBeginUser;
1278
1280
1281 // --- 1. Input Validation and Setup ---
1282 if (!user || !particle || !guess_rank_out || !bboxlist) {
1283 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Null pointer provided to GuessParticleOwnerWithBBox.");
1284 }
1285 if (!localBBox|| !neighbors) {
1286 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, "Required user->bboxl or user->neighbors is not initialized.");
1287 }
1288
1289 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1290 ierr = MPI_Comm_size(PETSC_COMM_WORLD, &size); CHKERRQ(ierr);
1291
1292 *guess_rank_out = MPI_PROC_NULL; // Default to "not found"
1293
1294 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld]: Starting guess for particle at (%.3f, %.3f, %.3f).\n",
1295 particle->PID, particle->loc.x, particle->loc.y, particle->loc.z);
1296
1297 // --- Step 0: Check if the particle is inside the CURRENT rank's bounding box FIRST. ---
1298 // This handles the common case of initial placement where a particle is "lost" but physically local.
1299 if (IsParticleInBox(localBBox, &particle->loc)) {
1300 *guess_rank_out = rank;
1301 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld]: Fast path guess SUCCESS. Particle is within the local (Rank %d) bounding box.\n",
1302 particle->PID, rank);
1303
1305 PetscFunctionReturn(0); // Found it, we're done.
1306 }
1307 // --- 2. Fast Path: Check Immediate Neighbors Based on Exit Direction ---
1308
1309 // Determine likely exit direction(s) to prioritize neighbor check
1310 PetscBool exit_xm = particle->loc.x < localBBox->min_coords.x;
1311 PetscBool exit_xp = particle->loc.x > localBBox->max_coords.x;
1312 PetscBool exit_ym = particle->loc.y < localBBox->min_coords.y;
1313 PetscBool exit_yp = particle->loc.y > localBBox->max_coords.y;
1314 PetscBool exit_zm = particle->loc.z < localBBox->min_coords.z;
1315 PetscBool exit_zp = particle->loc.z > localBBox->max_coords.z;
1316
1317 if (exit_xm && neighbors->rank_xm != MPI_PROC_NULL && IsParticleInBox(&bboxlist[neighbors->rank_xm], &particle->loc)) {
1318 *guess_rank_out = neighbors->rank_xm;
1319 } else if (exit_xp&& neighbors->rank_xp != MPI_PROC_NULL && IsParticleInBox(&bboxlist[neighbors->rank_xp], &particle->loc)) {
1320 *guess_rank_out = neighbors->rank_xp;
1321 } else if (exit_ym && neighbors->rank_ym != MPI_PROC_NULL && IsParticleInBox(&bboxlist[neighbors->rank_ym], &particle->loc)) {
1322 *guess_rank_out = neighbors->rank_ym;
1323 } else if (exit_yp && neighbors->rank_yp != MPI_PROC_NULL && IsParticleInBox(&bboxlist[neighbors->rank_yp], &particle->loc)) {
1324 *guess_rank_out = neighbors->rank_yp;
1325 } else if (exit_zm && neighbors->rank_zm != MPI_PROC_NULL && IsParticleInBox(&bboxlist[neighbors->rank_zm], &particle->loc)) {
1326 *guess_rank_out = neighbors->rank_zm;
1327 } else if (exit_zp && neighbors->rank_zp != MPI_PROC_NULL && IsParticleInBox(&bboxlist[neighbors->rank_zp], &particle->loc)) {
1328 *guess_rank_out = neighbors->rank_zp;
1329 }
1330 // Note: This does not handle corner/edge neighbors, which is why the fallback is essential.
1331
1332 if (*guess_rank_out != MPI_PROC_NULL) {
1333 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld]: Fast path guess SUCCESS. Found in immediate neighbor Rank %d.\n",
1334 particle->PID, *guess_rank_out);
1335
1337 PetscFunctionReturn(0); // Found it, we're done.
1338 }
1339
1340 // --- 3. Robust Fallback: Check All Other Ranks ---
1341 // If we get here, the particle was not in any of the immediate face neighbors' boxes.
1342 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld]: Not in immediate face neighbors. Starting global fallback search.\n",
1343 particle->PID);
1344
1345 for (PetscMPIInt r = 0; r < size; ++r) {
1346 if (r == rank) continue; // Don't check ourselves.
1347
1348 if (IsParticleInBox(&bboxlist[r], &particle->loc)) {
1349 PetscBool is_in = PETSC_TRUE;
1350 // This detailed, synchronized print will solve the mystery
1351 LOG_ALLOW(LOCAL,LOG_VERBOSE, "[Rank %d] Checking PID %lld at (%.4f, %.4f, %.4f) against Rank %d's box: [(%.4f, %.4f, %.4f) to (%.4f, %.4f, %.4f)]. Result: %s\n",
1352 (int)rank, (long long)particle->PID,
1353 particle->loc.x, particle->loc.y, particle->loc.z,
1354 (int)r,
1355 bboxlist[r].min_coords.x, bboxlist[r].min_coords.y, bboxlist[r].min_coords.z,
1356 bboxlist[r].max_coords.x, bboxlist[r].max_coords.y, bboxlist[r].max_coords.z,
1357 is_in ? "INSIDE" : "OUTSIDE");
1358
1359 *guess_rank_out = r;
1360 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld]: Fallback search SUCCESS. Found in Rank %d.\n",
1361 particle->PID, *guess_rank_out);
1362
1364 PetscFunctionReturn(0); // Found it, we're done.
1365 }
1366 }
1367
1368 // If the code reaches here, the particle was not found in any rank's bounding box.
1369 LOG_ALLOW(LOCAL, LOG_WARNING, "[PID %ld]: Guess FAILED. Particle not found in any rank's bounding box.\n",
1370 particle->PID);
1371
1372 // The guess_rank_out will remain -1, signaling failure to the caller.
1374 PetscFunctionReturn(0);
1375}
1376
1377#undef __FUNCT__
1378#define __FUNCT__ "LocateAllParticlesInGrid"
1379/**
1380 * @brief Implementation of \ref LocateAllParticlesInGrid().
1381 * @details Full API contract (arguments, ownership, side effects) is documented with
1382 * the header declaration in `include/ParticleMotion.h`.
1383 * @see LocateAllParticlesInGrid()
1384 */
1385PetscErrorCode LocateAllParticlesInGrid(UserCtx *user,BoundingBox *bboxlist)
1386{
1387 PetscErrorCode ierr;
1388 PetscInt passes = 0;
1389 const PetscInt MAX_MIGRATION_PASSES = 50; // Safety break for runaway loops
1390 PetscInt global_migrations_this_pass;
1391 PetscMPIInt rank;
1392 PetscInt total_migrated_this_timestep = 0;
1393
1394 PetscFunctionBeginUser;
1396 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1397 ierr = ResetSearchMetrics(user->simCtx); CHKERRQ(ierr);
1398 LOG_ALLOW(GLOBAL, LOG_INFO, "LocateAllParticlesInGrid (Orchestrator) - Beginning particle settlement process.\n");
1399
1400 // This loop ensures that particles that jump across multiple ranks are
1401 // handled correctly in successive, iterative handoffs.
1402 do {
1403 passes++;
1405 LOG_ALLOW_SYNC(GLOBAL, LOG_INFO, "[Rank %d] Starting migration pass %d.\n", rank, passes);
1406
1407 // --- STAGE 1: PER-PASS INITIALIZATION ---
1408 MigrationInfo *migrationList = NULL;
1409 PetscInt local_migration_count = 0;
1410 PetscInt migrationListCapacity = 0;
1411 PetscInt nlocal_before;
1412 PetscInt64 *pids_before_snapshot = NULL;
1413 PetscInt local_lost_count = 0;
1414
1415 ierr = DMSwarmGetLocalSize(user->swarm, &nlocal_before); CHKERRQ(ierr);
1416 if (passes == 1) {
1417 user->simCtx->searchMetrics.searchPopulation += (PetscInt64)nlocal_before;
1418 }
1419 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d] Pass %d begins with %d local particles.\n", rank, passes, nlocal_before);
1420
1421
1422 // --- STAGE 2: PRE-MIGRATION SNAPSHOT & MAIN PROCESSING LOOP ---
1423 if (nlocal_before > 0) {
1424 // Get pointers to all fields needed for this pass
1425 PetscReal *pos_p, *weights_p, *vel_p;
1426 PetscInt *cell_p, *status_p;
1427 PetscInt64 *pid_p;
1428 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&pos_p); CHKERRQ(ierr);
1429 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_VELOCITY), NULL, NULL, (void**)&vel_p); CHKERRQ(ierr);
1430 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_WEIGHT), NULL, NULL, (void**)&weights_p); CHKERRQ(ierr);
1431 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_p); CHKERRQ(ierr);
1432 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid_p); CHKERRQ(ierr);
1433 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status_p); CHKERRQ(ierr);
1434
1435 // Create a sorted snapshot of current PIDs to identify newcomers after migration.
1436 // This helper requires a raw pointer, which we just acquired.
1437 ierr = GetLocalPIDSnapshot(pid_p, nlocal_before, &pids_before_snapshot); CHKERRQ(ierr);
1438
1439 for (PetscInt p_idx = 0; p_idx < nlocal_before; p_idx++) {
1440
1441 // OPTIMIZATION: Skip particles already settled in a previous pass of this do-while loop.
1442
1444 "Local Particle idx=%d, PID=%ld, status=%s, cell=(%d, %d, %d)\n",
1445 p_idx,
1446 (long)pid_p[p_idx],
1448 cell_p[3*p_idx],
1449 cell_p[3*p_idx+1],
1450 cell_p[3*p_idx+2]);
1451
1452 if (status_p[p_idx] == ACTIVE_AND_LOCATED) {
1453 LOG_ALLOW(LOCAL,LOG_VERBOSE," [rank %d][PID %ld] skipped in pass %d as it is already located at (%d,%d,%d).\n",rank,pid_p[p_idx],passes,cell_p[3*p_idx],cell_p[3*p_idx + 1],cell_p[3*p_idx + 2]);
1454 continue;
1455 }
1456
1457 // UNPACK: Create a temporary C struct for easier processing using our helper.
1458 Particle current_particle;
1459
1460 // LOG_ALLOW(LOCAL,LOG_DEBUG,"about to unpack p_idx=%d (PID=%ld)\n",p_idx, (long)pid_p[p_idx]);
1461
1462 ierr = UnpackSwarmFields(p_idx, pid_p, weights_p, pos_p, cell_p, vel_p, status_p,NULL,NULL,NULL,&current_particle); CHKERRQ(ierr);
1463
1464 // LOG_ALLOW(LOCAL,LOG_DEBUG,"unpacked p_idx=%d → cell[0]=%d, status=%s\n",p_idx, current_particle.cell[0], ParticleLocationStatusToString((ParticleLocationStatus)current_particle.location_status));
1465
1466 ParticleLocationStatus final_status = (ParticleLocationStatus)status_p[p_idx];
1467
1468
1469 // CASE 1: Particle has a valid prior cell index.
1470 // It has moved, so we only need to run the robust walk from its last known location.
1471 if (current_particle.cell[0] >= 0) {
1472 LOG_ALLOW(LOCAL, LOG_VERBOSE, "[PID %ld] has valid prior cell. Strategy: Robust Walk from previous cell.\n", current_particle.PID);
1473 ierr = LocateParticleOrFindMigrationTarget(user, &current_particle, &final_status); CHKERRQ(ierr);
1474 }
1475
1476 /*
1477 // --- "GUESS" FAST PATH for lost particles ---
1478 if (current_particle.cell[0] < 0) {
1479 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld] is lost or uninitialzied (cell=%d), attempting fast guess.\n",current_particle.PID, current_particle.cell[0]);
1480 ierr = GuessParticleOwnerWithBBox(user, &current_particle, bboxlist, &destination_rank); CHKERRQ(ierr);
1481 if (destination_rank != MPI_PROC_NULL && destination_rank != rank) {
1482 final_status = MIGRATING_OUT;
1483 // The particle struct's destination rank must be updated for consistency
1484 current_particle.destination_rank = destination_rank;
1485 }
1486 }
1487
1488 LOG_ALLOW(LOCAL,LOG_DEBUG,"[PID %ld] Particle status after Initial Guess:%d \n",current_particle.PID,final_status);
1489
1490 // --- "VERIFY" ROBUST WALK if guess didn't resolve it ---
1491 if (final_status == NEEDS_LOCATION || UNINITIALIZED) {
1492 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld] Not resolved by guess, starting robust walk.\n", current_particle.PID);
1493 // This function will update the particle's status and destination rank internally.
1494 ierr = LocateParticleOrFindMigrationTarget(user, &current_particle, &final_status); CHKERRQ(ierr);
1495 destination_rank = current_particle.destination_rank; // Retrieve the result
1496 }
1497
1498 // --- PROCESS THE FINAL STATUS AND TAKE ACTION ---
1499 if (final_status == MIGRATING_OUT) {
1500 status_p[p_idx] = MIGRATING_OUT; // Mark for removal by DMSwarm
1501 ierr = AddToMigrationList(&migrationList, &migrationListCapacity, &local_migration_count, p_idx, destination_rank); CHKERRQ(ierr);
1502 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld] at local index %d marked for migration to rank %d.\n",current_particle.PID, p_idx, destination_rank);
1503 } else {
1504 // Particle's final status is either LOCATED or LOST; update its state in the swarm arrays.
1505 current_particle.location_status = final_status;
1506 // PACK: Use the helper to write results back to the swarm arrays.
1507 ierr = UpdateSwarmFields(p_idx, &current_particle, pos_p, vel_p, weights_p, cell_p, status_p,NULL,NULL,NULL); CHKERRQ(ierr);
1508 }
1509 */
1510 // CASE 2: Particle is "lost" (cell = -1). Strategy: Guess -> Verify.
1511 else {
1512 LOG_ALLOW(LOCAL, LOG_VERBOSE, "[PID %ld] has invalid cell. Strategy: Guess Owner -> Find Cell.\n",current_particle.PID);
1513
1514 PetscMPIInt guessed_owner_rank = MPI_PROC_NULL;
1515 ierr = GuessParticleOwnerWithBBox(user, &current_particle, bboxlist, &guessed_owner_rank); CHKERRQ(ierr);
1516
1517 // If the guess finds a DIFFERENT rank, we can mark for migration and skip the walk.
1518 if (guessed_owner_rank != MPI_PROC_NULL && guessed_owner_rank != rank) {
1520 LOG_ALLOW(LOCAL, LOG_VERBOSE, "[PID %ld] Guess SUCCESS: Found migration target Rank %d. Finalizing.\n", current_particle.PID, guessed_owner_rank);
1521 final_status = MIGRATING_OUT;
1522 current_particle.destination_rank = guessed_owner_rank;
1523 }
1524 else {
1526
1527 // This block runs if the guess either failed (rank is NULL) or found the particle is local (rank is self).
1528 // In BOTH cases, the situation is unresolved, and we MUST fall back to the robust walk.
1529 if (guessed_owner_rank == rank) {
1530 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld] Guess determined particle is local. Proceeding to robust walk to find cell.\n", current_particle.PID);
1531 } else { // guessed_owner_rank == MPI_PROC_NULL
1532 LOG_ALLOW(LOCAL, LOG_WARNING, "[PID %ld] Guess FAILED to find an owner. Proceeding to robust walk for definitive search.\n", current_particle.PID);
1533 }
1534
1535 ierr = LocateParticleOrFindMigrationTarget(user, &current_particle, &final_status); CHKERRQ(ierr);
1536 }
1537 }
1538
1539 // --- PROCESS THE FINAL, DEFINITIVE STATUS ---
1540 current_particle.location_status = final_status;
1541 ierr = UpdateSwarmFields(p_idx, &current_particle, pos_p, vel_p, weights_p, cell_p, status_p,NULL,NULL,NULL); CHKERRQ(ierr);
1542
1543 if (final_status == MIGRATING_OUT) {
1544 ierr = AddToMigrationList(&migrationList, &migrationListCapacity, &local_migration_count, p_idx, current_particle.destination_rank); CHKERRQ(ierr);
1545 } else if (final_status == LOST) {
1546 local_lost_count++;
1548 } else if (final_status == ACTIVE_AND_LOCATED) {
1550 }
1551
1552 } // End of main particle processing loop
1553
1554 // Restore all the fields acquired for this pass.
1555 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&pos_p); CHKERRQ(ierr);
1556 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_VELOCITY), NULL, NULL, (void**)&vel_p); CHKERRQ(ierr);
1557 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_WEIGHT), NULL, NULL, (void**)&weights_p); CHKERRQ(ierr);
1558 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_p); CHKERRQ(ierr);
1559 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid_p); CHKERRQ(ierr);
1560 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status_p); CHKERRQ(ierr);
1561 }
1562
1563 // --- STAGE 3: ACTION & MPI COMMUNICATION ---
1564 LOG_ALLOW(LOCAL, LOG_INFO, "[Rank %d] Pass %d: Identified %d particles to migrate out.\n", rank, passes, local_migration_count);
1565
1566 // --- STAGE 3: SYNCHRONIZE AND DECIDE ---
1567 // FIRST, determine if any rank wants to migrate. This call is safe because
1568 // all ranks have finished their local work and can participate.
1569 ierr = MPI_Allreduce(&local_migration_count, &global_migrations_this_pass, 1, MPIU_INT, MPI_SUM, PETSC_COMM_WORLD); CHKERRQ(ierr);
1570
1571 total_migrated_this_timestep += global_migrations_this_pass;
1572
1573 if(global_migrations_this_pass > 0 ){
1574
1575 LOG_ALLOW(GLOBAL, LOG_INFO, "Pass %d: Migrating %d particles globally.\n", passes, global_migrations_this_pass);
1576
1577 ierr = SetMigrationRanks(user, migrationList, local_migration_count); CHKERRQ(ierr);
1578 ierr = PerformMigration(user); CHKERRQ(ierr);
1579
1580 // --- STAGE 4: POST-MIGRATION RESET ---
1581 // Identify newly arrived particles and flag them with NEEDS_LOCATION so they are
1582 // processed in the next pass. This uses the snapshot taken in STAGE 2.
1583 ierr = FlagNewcomersForLocation(user->swarm, nlocal_before, pids_before_snapshot); CHKERRQ(ierr);
1584 }
1585 // --- STAGE 5: LOOP SYNCHRONIZATION AND CLEANUP ---
1586
1587 ierr = PetscFree(pids_before_snapshot);
1588 ierr = PetscFree(migrationList);
1589
1590 LOG_ALLOW(GLOBAL, LOG_INFO, "End of pass %d. Total particles migrated globally: %d.\n", passes, global_migrations_this_pass);
1591
1592 } while (global_migrations_this_pass > 0 && passes < MAX_MIGRATION_PASSES);
1593
1594 // --- FINAL CHECKS ---
1595 if (passes >= MAX_MIGRATION_PASSES) {
1596 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_CONV_FAILED, "Particle migration failed to converge after %d passes. Check for particles oscillating between ranks.", MAX_MIGRATION_PASSES);
1597 }
1598
1599 user->simCtx->particlesMigratedLastStep = total_migrated_this_timestep;
1600 user->simCtx->migrationPassesLastStep = passes;
1601 user->simCtx->searchMetrics.maxParticlePassDepth = PetscMax(user->simCtx->searchMetrics.maxParticlePassDepth, (PetscInt64)passes);
1603
1604 LOG_ALLOW(GLOBAL, LOG_INFO, "Particle Location completed in %d passes.\n", passes);
1605
1607 PetscFunctionReturn(0);
1608}
1609
1610
1611#undef __FUNCT__
1612#define __FUNCT__ "ResetAllParticleStatuses"
1613/**
1614 * @brief Implementation of \ref ResetAllParticleStatuses().
1615 * @details Full API contract (arguments, ownership, side effects) is documented with
1616 * the header declaration in `include/ParticleMotion.h`.
1617 * @see ResetAllParticleStatuses()
1618 */
1620{
1621 PetscErrorCode ierr;
1622 PetscInt n_local;
1623 PetscInt *status_p;
1624
1625 PetscFunctionBeginUser;
1626
1628
1629 ierr = DMSwarmGetLocalSize(user->swarm, &n_local); CHKERRQ(ierr);
1630
1631 if (n_local > 0) {
1632 // Get write access to the status field
1633 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status_p); CHKERRQ(ierr);
1634
1635 for (PetscInt p = 0; p < n_local; ++p) {
1636 // Only reset particles that are considered settled. This is a small optimization
1637 // to avoid changing the status of a LOST particle, though resetting all would also be fine.
1638 if (status_p[p] == ACTIVE_AND_LOCATED) {
1639 status_p[p] = NEEDS_LOCATION;
1640 }
1641 }
1642
1643 // Restore the field
1644 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status_p); CHKERRQ(ierr);
1645 }
1646
1647
1649 PetscFunctionReturn(0);
1650}
PetscErrorCode GetRandomCellAndLogicalCoordsOnInletFace(UserCtx *user, const DMDALocalInfo *info, PetscInt xs_gnode_rank, PetscInt ys_gnode_rank, PetscInt zs_gnode_rank, PetscInt IM_nodes_global, PetscInt JM_nodes_global, PetscInt KM_nodes_global, PetscRandom *rand_logic_i_ptr, PetscRandom *rand_logic_j_ptr, PetscRandom *rand_logic_k_ptr, PetscInt *ci_metric_lnode_out, PetscInt *cj_metric_lnode_out, PetscInt *ck_metric_lnode_out, PetscReal *xi_metric_logic_out, PetscReal *eta_metric_logic_out, PetscReal *zta_metric_logic_out)
Assuming the current rank services the inlet face, this function selects a random cell (owned by this...
Definition Boundaries.c:400
PetscErrorCode CanRankServiceInletFace(UserCtx *user, const DMDALocalInfo *info, PetscInt IM_nodes_global, PetscInt JM_nodes_global, PetscInt KM_nodes_global, PetscBool *can_service_inlet_out)
Determines if the current MPI rank owns any part of the globally defined inlet face,...
Definition Boundaries.c:11
PetscErrorCode GetDeterministicFaceGridLocation(UserCtx *user, const DMDALocalInfo *info, PetscInt xs_gnode_rank, PetscInt ys_gnode_rank, PetscInt zs_gnode_rank, PetscInt IM_cells_global, PetscInt JM_cells_global, PetscInt KM_cells_global, PetscInt64 particle_global_id, PetscInt *ci_metric_lnode_out, PetscInt *cj_metric_lnode_out, PetscInt *ck_metric_lnode_out, PetscReal *xi_metric_logic_out, PetscReal *eta_metric_logic_out, PetscReal *zta_metric_logic_out, PetscBool *placement_successful_out)
Places particles in a deterministic grid/raster pattern on a specified domain face.
Definition Boundaries.c:213
PetscErrorCode MetricLogicalToPhysical(UserCtx *user, const Cmpnts ***X, PetscInt i, PetscInt j, PetscInt k, PetscReal xi, PetscReal eta, PetscReal zta, Cmpnts *Xp)
Maps a logical point inside one hexahedral cell to physical space.
Definition Metric.c:76
PetscErrorCode GenerateGaussianNoise(PetscRandom rnd, PetscReal *n1, PetscReal *n2)
Internal helper implementation: GenerateGaussianNoise().
PetscErrorCode ResizeSwarmGlobally(DM swarm, PetscInt N_target)
Implementation of ResizeSwarmGlobally().
PetscErrorCode AddToMigrationList(MigrationInfo **migration_list_p, PetscInt *capacity_p, PetscInt *count_p, PetscInt particle_local_idx, PetscMPIInt destination_rank)
Internal helper implementation: AddToMigrationList().
PetscErrorCode SetMigrationRanks(UserCtx *user, const MigrationInfo *migrationList, PetscInt migrationCount)
Internal helper implementation: SetMigrationRanks().
PetscErrorCode CheckAndRemoveOutOfBoundsParticles(UserCtx *user, PetscInt *removedCountLocal, PetscInt *removedCountGlobal, const BoundingBox *bboxlist)
Internal helper implementation: CheckAndRemoveOutOfBoundsParticles().
PetscErrorCode GetLocalPIDSnapshot(const PetscInt64 pid_field[], PetscInt n_local, PetscInt64 **pids_snapshot_out)
Internal helper implementation: GetLocalPIDSnapshot().
PetscErrorCode MigrateRestartParticlesUsingCellID(UserCtx *user)
Internal helper implementation: MigrateRestartParticlesUsingCellID().
PetscErrorCode UpdateAllParticlePositions(UserCtx *user)
Internal helper implementation: UpdateAllParticlePositions().
PetscErrorCode CalculateParticleCountPerCell(UserCtx *user)
Implementation of CalculateParticleCountPerCell().
PetscErrorCode CalculateBrownianDisplacement(UserCtx *user, PetscReal diff_eff, Cmpnts *displacement)
Internal helper implementation: CalculateBrownianDisplacement().
PetscErrorCode LocateAllParticlesInGrid(UserCtx *user, BoundingBox *bboxlist)
Implementation of LocateAllParticlesInGrid().
static PetscErrorCode GuessParticleOwnerWithBBox(UserCtx *user, const Particle *particle, const BoundingBox *bboxlist, PetscMPIInt *guess_rank_out)
Select the rank whose gathered bounding box is the best owner candidate for a particle.
#define ERROR_MSG_BUFFER_SIZE
PetscErrorCode UpdateParticlePosition(UserCtx *user, Particle *particle)
Internal helper implementation: UpdateParticlePosition().
PetscErrorCode ResetAllParticleStatuses(UserCtx *user)
Implementation of ResetAllParticleStatuses().
PetscErrorCode ReinitializeParticlesOnInletSurface(UserCtx *user, PetscReal currentTime, PetscInt step)
Internal helper implementation: ReinitializeParticlesOnInletSurface().
PetscErrorCode CheckAndRemoveLostParticles(UserCtx *user, PetscInt *removedCountLocal, PetscInt *removedCountGlobal)
Internal helper implementation: CheckAndRemoveLostParticles().
PetscErrorCode FlagNewcomersForLocation(DM swarm, PetscInt n_local_before, const PetscInt64 pids_before[])
Internal helper implementation: FlagNewcomersForLocation().
static PetscBool IsParticleInBox(const BoundingBox *bbox, const Cmpnts *pos)
Test whether a particle position lies within an axis-aligned bounding box.
PetscErrorCode PreCheckAndResizeSwarm(UserCtx *user, PetscInt ti, const char *ext)
Internal helper implementation: PreCheckAndResizeSwarm().
PetscErrorCode PerformMigration(UserCtx *user)
Implementation of PerformMigration().
Header file for Particle Motion and migration related functions.
PetscErrorCode UnpackSwarmFields(PetscInt i, const PetscInt64 *PIDs, const PetscReal *weights, const PetscReal *positions, const PetscInt *cellIndices, PetscReal *velocities, PetscInt *LocStatus, PetscReal *diffusivity, Cmpnts *diffusivitygradient, PetscReal *psi, Particle *particle)
Initializes a Particle struct with data from DMSwarm fields.
PetscErrorCode UpdateSwarmFields(PetscInt i, const Particle *particle, PetscReal *positions, PetscReal *velocities, PetscReal *weights, PetscInt *cellIndices, PetscInt *status, PetscReal *diffusivity, Cmpnts *diffusivitygradient, PetscReal *psi)
Updates DMSwarm data arrays from a Particle struct.
PetscErrorCode ReadCheckpointParticleCount(UserCtx *user, PetscInt ti, PetscInt *particle_count)
Read the particle count from a validated committed checkpoint.
Definition io.c:1914
#define LOG_LOOP_ALLOW(scope, level, iterVar, interval, fmt,...)
Logs a message inside a loop, but only every interval iterations.
Definition logging.h:298
#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
const char * ParticleLocationStatusToString(ParticleLocationStatus level)
A function that outputs the name of the current level in the ParticleLocation enum.
Definition logging.c:1858
@ 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 ResetSearchMetrics(SimCtx *simCtx)
Resets the aggregate per-timestep search instrumentation counters.
Definition logging.c:3098
Typed identities and metadata for persistent solver-particle fields.
const char * ParticleFieldName(ParticleFieldId field_id)
Return the canonical PETSc DMSwarm name for an ID.
@ PARTICLE_FIELD_ID_LOCATION_STATUS
@ PARTICLE_FIELD_ID_WEIGHT
@ PARTICLE_FIELD_ID_POSITION
@ PARTICLE_FIELD_ID_PID
@ PARTICLE_FIELD_ID_CELL_ID
@ PARTICLE_FIELD_ID_PSI
@ PARTICLE_FIELD_ID_RANK
@ PARTICLE_FIELD_ID_DIFFUSIVITY_GRADIENT
@ PARTICLE_FIELD_ID_DIFFUSIVITY
@ PARTICLE_FIELD_ID_VELOCITY
PetscErrorCode InitializeLogicalSpaceRNGs(PetscRandom *rand_logic_i, PetscRandom *rand_logic_j, PetscRandom *rand_logic_k)
Initializes random number generators for logical space operations [0.0, 1.0).
Definition setup.c:3241
PetscErrorCode BinarySearchInt64(PetscInt n, const PetscInt64 arr[], PetscInt64 key, PetscBool *found)
Performs a binary search for a key in a sorted array of PetscInt64.
Definition setup.c:2965
PetscMPIInt rank_zm
Definition variables.h:199
Cmpnts vel
Definition variables.h:186
PetscBool inletFaceDefined
Definition variables.h:932
PetscMPIInt rank_yp
Definition variables.h:198
PetscInt64 searchLocatedCount
Definition variables.h:241
PetscInt64 searchLostCount
Definition variables.h:242
BCFace identifiedInletBCFace
Definition variables.h:933
PetscInt cell[3]
Definition variables.h:184
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:909
@ PARTICLE_INIT_SURFACE_RANDOM
Random placement on the inlet face.
Definition variables.h:552
@ PARTICLE_INIT_SURFACE_EDGES
Deterministic placement at inlet face edges.
Definition variables.h:555
ParticleLocationStatus
Defines the state of a particle with respect to its location and migration status during the iterativ...
Definition variables.h:137
@ LOST
Definition variables.h:141
@ NEEDS_LOCATION
Definition variables.h:138
@ ACTIVE_AND_LOCATED
Definition variables.h:139
@ MIGRATING_OUT
Definition variables.h:140
PetscMPIInt rank_ym
Definition variables.h:198
PetscReal CMy_c
Definition variables.h:783
PetscMPIInt rank_xp
Definition variables.h:197
PetscInt local_index
Definition variables.h:210
Cmpnts max_coords
Maximum x, y, z coordinates of the bounding box.
Definition variables.h:173
Cmpnts diffusivitygradient
Definition variables.h:191
PetscInt64 searchPopulation
Definition variables.h:240
PetscReal dt
Definition variables.h:710
RankNeighbors neighbors
Definition variables.h:923
PetscInt currentSettlementPass
Definition variables.h:252
PetscInt np
Definition variables.h:827
Cmpnts min_coords
Minimum x, y, z coordinates of the bounding box.
Definition variables.h:172
PetscScalar x
Definition variables.h:103
Cmpnts loc
Definition variables.h:185
PetscMPIInt destination_rank
Definition variables.h:189
Vec lParticleCount
Definition variables.h:996
PetscInt64 bboxGuessFallbackCount
Definition variables.h:250
ParticleLocationStatus location_status
Definition variables.h:188
PetscInt64 bboxGuessSuccessCount
Definition variables.h:249
PetscMPIInt rank_xm
Definition variables.h:197
PetscInt64 maxParticlePassDepth
Definition variables.h:251
PetscReal CMz_c
Definition variables.h:783
ParticleInitializationType ParticleInitialization
Definition variables.h:831
PetscScalar z
Definition variables.h:103
Vec ParticleCount
Definition variables.h:996
PetscInt particlesMigratedLastStep
Definition variables.h:837
PetscMPIInt rank_zp
Definition variables.h:199
SearchMetricsState searchMetrics
Definition variables.h:840
PetscReal diffusivity
Definition variables.h:190
PetscRandom BrownianMotionRNG
Definition variables.h:841
PetscInt migrationPassesLastStep
Definition variables.h:836
PetscScalar y
Definition variables.h:103
BoundingBox bbox
Definition variables.h:922
PetscInt64 PID
Definition variables.h:183
PetscReal CMx_c
Definition variables.h:783
Defines a 3D axis-aligned bounding box.
Definition variables.h:171
A 3D point or vector with PetscScalar components.
Definition variables.h:102
Information needed to migrate a single particle between MPI ranks.
Definition variables.h:209
Defines a particle's core properties for Lagrangian tracking.
Definition variables.h:182
Stores the MPI ranks of neighboring subdomains.
Definition variables.h:196
User-defined context containing data specific to a single computational grid level.
Definition variables.h:906
Head of a generic C-style linked list.
Definition variables.h:445
PetscErrorCode LocateParticleOrFindMigrationTarget(UserCtx *user, Particle *particle, ParticleLocationStatus *status_out)
Locates a particle's host cell or identifies its migration target using a robust walk search.
PetscErrorCode FindOwnerOfCell(UserCtx *user, PetscInt i, PetscInt j, PetscInt k, PetscMPIInt *owner_rank)
Finds the MPI rank that owns a given global cell index.