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 PetscReal *removedScalarSumGlobal)
340{
341 PetscErrorCode ierr;
342 DM swarm = user->swarm;
343 PetscInt nLocalInitial;
344 PetscInt *status_p = NULL;
345 PetscInt64 *pid_p = NULL; // For better logging
346 PetscReal *pos_p = NULL; // For better logging
347 PetscReal *psi_p = NULL;
348 PetscReal local_removed_psi = 0.0;
349 PetscInt local_removed_count = 0;
350 PetscMPIInt global_removed_count_mpi = 0;
351 PetscMPIInt rank;
352
353 PetscFunctionBeginUser;
355 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
356 LOG_ALLOW(LOCAL, LOG_INFO, "Rank %d: Checking for and removing LOST particles...\n", rank);
357
358 // Initialize output parameters to ensure clean state
359 *removedCountLocal = 0;
360 if (removedCountGlobal) *removedCountGlobal = 0;
361 if (removedScalarSumGlobal) *removedScalarSumGlobal = 0.0;
362
363 ierr = DMSwarmGetLocalSize(swarm, &nLocalInitial); CHKERRQ(ierr);
364
365 // Only proceed if there are particles to check on this rank.
366 // All ranks will still participate in the final collective MPI_Allreduce.
367 if (nLocalInitial > 0) {
368 // Get access to all swarm fields once before the loop begins.
369 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void **)&status_p); CHKERRQ(ierr);
370 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void **)&pid_p); CHKERRQ(ierr);
371 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void **)&pos_p); CHKERRQ(ierr);
372 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PSI), NULL, NULL, (void **)&psi_p); CHKERRQ(ierr);
373
374 // --- Iterate BACKWARDS to handle index changes safely during removal ---
375 for (PetscInt p = nLocalInitial - 1; p >= 0; p--) {
376 if (status_p[p] == LOST) {
377 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Removing LOST particle [PID %lld] at local index %d. Position: (%.4f, %.4f, %.4f).\n",
378 rank, (long long)pid_p[p], p, pos_p[3*p], pos_p[3*p+1], pos_p[3*p+2]);
379
380 // --- Safe Removal Pattern: Restore -> Remove -> Reacquire ---
381 // This is the fix for the double-restore bug. Pointers are managed carefully
382 // within this block and then restored cleanly after the loop.
383
384 // The scalar a removed particle carries leaves the domain with it.
385 local_removed_psi += psi_p[p];
386
387 // 1. Restore all fields BEFORE modifying the swarm structure. This invalidates all pointers.
388 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PSI), NULL, NULL, (void **)&psi_p); CHKERRQ(ierr);
389 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void **)&status_p); CHKERRQ(ierr);
390 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void **)&pid_p); CHKERRQ(ierr);
391 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void **)&pos_p); CHKERRQ(ierr);
392
393 // 2. Remove the particle at the current local index 'p'.
394 ierr = DMSwarmRemovePointAtIndex(swarm, p); CHKERRQ(ierr);
395 local_removed_count++;
396
397 // 3. After removal, re-acquire pointers ONLY if the loop is not finished.
398 PetscInt nLocalCurrent;
399 ierr = DMSwarmGetLocalSize(swarm, &nLocalCurrent); CHKERRQ(ierr);
400
401 if (nLocalCurrent > 0 && p > 0) { // Check if there are particles left AND iterations left
402 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void **)&status_p); CHKERRQ(ierr);
403 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void **)&pid_p); CHKERRQ(ierr);
404 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void **)&pos_p); CHKERRQ(ierr);
405 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PSI), NULL, NULL, (void **)&psi_p); CHKERRQ(ierr);
406 } else {
407 // All remaining particles were removed OR this was the last particle (p=0).
408 // Invalidate pointers to prevent the final restore call and exit the loop.
409 status_p = NULL;
410 pid_p = NULL;
411 pos_p = NULL;
412 psi_p = NULL;
413 break;
414 }
415 }
416 } // End of backwards loop
417
418 // At the end, restore any valid pointers. This handles three cases:
419 // 1. No particles were removed: restores the original pointers.
420 // 2. Particles were removed mid-loop: restores the pointers from the last re-acquisition.
421 // 3. All particles were removed: pointers are NULL, so nothing is done.
422 if (status_p) { ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void **)&status_p); CHKERRQ(ierr); }
423 if (pid_p) { ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void **)&pid_p); CHKERRQ(ierr); }
424 if (pos_p) { ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void **)&pos_p); CHKERRQ(ierr); }
425 if (psi_p) { ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PSI), NULL, NULL, (void **)&psi_p); CHKERRQ(ierr); }
426 } // End of if (nLocalInitial > 0)
427
428 PetscInt nLocalFinal;
429 ierr = DMSwarmGetLocalSize(swarm, &nLocalFinal); CHKERRQ(ierr);
430 LOG_ALLOW(LOCAL, LOG_INFO, "Rank %d: Finished removing %d LOST particles. Final local size: %d.\n", rank, local_removed_count, nLocalFinal);
431
432 // --- Synchronize counts across all ranks ---
433 *removedCountLocal = local_removed_count;
434 if (removedCountGlobal) {
435 ierr = MPI_Allreduce(&local_removed_count, &global_removed_count_mpi, 1, MPI_INT, MPI_SUM, PetscObjectComm((PetscObject)swarm)); CHKERRQ(ierr);
436 *removedCountGlobal = global_removed_count_mpi;
437 // Use a synchronized log message so only one rank prints the global total.
438 LOG_ALLOW_SYNC(GLOBAL, LOG_INFO, "[Rank %d] Removed %d LOST particles globally.\n", rank, *removedCountGlobal);
439 }
440 if (removedScalarSumGlobal) {
441 ierr = MPI_Allreduce(&local_removed_psi, removedScalarSumGlobal, 1, MPIU_REAL, MPI_SUM,
442 PetscObjectComm((PetscObject)swarm)); CHKERRMPI(ierr);
443 }
444
446 PetscFunctionReturn(0);
447}
448
449
450#undef __FUNCT__
451#define __FUNCT__ "SetMigrationRanks"
452/**
453 * @brief Internal helper implementation: `SetMigrationRanks()`.
454 * @details Local to this translation unit.
455 */
456PetscErrorCode SetMigrationRanks(UserCtx* user, const MigrationInfo *migrationList, PetscInt migrationCount)
457{
458 PetscErrorCode ierr;
459 DM swarm = user->swarm;
460 PetscInt p_idx;
461 PetscInt *rankField = NULL; // Field storing target rank
462
463 PetscFunctionBeginUser;
465
466 // Ensure the migration rank field exists
467 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_RANK), NULL, NULL, (void **)&rankField); CHKERRQ(ierr);
468
469 // Set the target rank for migrating particles
470 for(p_idx = 0; p_idx < migrationCount; ++p_idx) {
471 rankField[migrationList[p_idx].local_index] = migrationList[p_idx].target_rank;
472 }
473
474 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_RANK), NULL, NULL, (void **)&rankField); CHKERRQ(ierr);
475
477 PetscFunctionReturn(0);
478}
479
480#undef __FUNCT__
481#define __FUNCT__ "PerformMigration"
482
483/**
484 * @brief Implementation of \ref PerformMigration().
485 * @details Full API contract (arguments, ownership, side effects) is documented with
486 * the header declaration in `include/ParticleMotion.h`.
487 * @see PerformMigration()
488 */
489PetscErrorCode PerformMigration(UserCtx *user)
490{
491 PetscErrorCode ierr;
492 DM swarm = user->swarm;
493 PetscMPIInt rank;
494
495 PetscFunctionBeginUser;
497 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
498 LOG_ALLOW(GLOBAL, LOG_INFO, "Rank %d: Starting DMSwarmMigrate...\n", rank);
499
500 // Perform the migration - PETSC_TRUE removes particles that fail to land
501 // in a valid cell on the target rank (or were marked with an invalid rank).
502 ierr = DMSwarmMigrate(swarm, PETSC_TRUE); CHKERRQ(ierr);
503
504 LOG_ALLOW(GLOBAL, LOG_INFO, "Rank %d: Migration complete.\n", rank);
506 PetscFunctionReturn(0);
507}
508
509//-----------------------------------------------------------------------------
510// MODULE (COUNT): Calculates Particle Count Per Cell - REVISED FOR DMDAVecGetArray
511//-----------------------------------------------------------------------------
512
513#undef __FUNCT__
514#define __FUNCT__ "CalculateParticleCountPerCell"
515/**
516 * @brief Implementation of \ref CalculateParticleCountPerCell().
517 * @details Full API contract (arguments, ownership, side effects) is documented with
518 * the header declaration in `include/logging.h`.
519 * @see CalculateParticleCountPerCell()
520 */
522 PetscErrorCode ierr;
523 DM da = user->da;
524 DM swarm = user->swarm;
525 Vec countVec = user->ParticleCount;
526 Vec localcountVec = user->lParticleCount;
527 PetscInt nlocal, p;
528 PetscInt *global_cell_id_arr; // Read GLOBAL cell IDs
529 PetscScalar ***count_arr_3d; // Use 3D accessor
530 PetscInt64 *PID_arr;
531 PetscMPIInt rank;
532 char msg[ERROR_MSG_BUFFER_SIZE];
533 PetscInt particles_counted_locally = 0;
534
535 PetscFunctionBeginUser;
537 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
538
539 // --- Input Validation ---
540 if (!da) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx->da is NULL.");
541 if (!swarm) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx->swarm is NULL.");
542 if (!countVec) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx->ParticleCount is NULL.");
543 // Check DOF of da
544 PetscInt count_dof;
545 ierr = DMDAGetInfo(da, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &count_dof, NULL, NULL, NULL, NULL, NULL); CHKERRQ(ierr);
546 if (count_dof != 1) {
547 PetscSNPrintf(msg, sizeof(msg), "countDM must have DOF=1, got %" PetscInt_FMT ".", count_dof);
548 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "%s", msg);
549 }
550
551 // --- Zero the local count vector ---
552 ierr = VecSet(localcountVec, 0.0); CHKERRQ(ierr);
553
554 // --- Get Particle Data ---
555 LOG_ALLOW(GLOBAL,LOG_DEBUG, "Accessing particle data.\n");
556 ierr = DMSwarmGetLocalSize(swarm, &nlocal); CHKERRQ(ierr);
557 ierr = DMSwarmGetField(swarm,ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void **)&global_cell_id_arr); CHKERRQ(ierr);
558 ierr = DMSwarmGetField(swarm,ParticleFieldName(PARTICLE_FIELD_ID_PID),NULL,NULL,(void **)&PID_arr);CHKERRQ(ierr);
559
560 // --- Get Grid Vector Array using DMDA accessor ---
561 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Accessing ParticleCount vector array (using DMDAVecGetArray).\n");
562 ierr = DMDAVecGetArray(da, localcountVec, &count_arr_3d); CHKERRQ(ierr);
563
564 // Get local owned + ghosted range for writing into ghost slots.
565 PetscInt gxs, gys, gzs, gxm, gym, gzm;
566 ierr = DMDAGetGhostCorners(da, &gxs, &gys, &gzs, &gxm, &gym, &gzm); CHKERRQ(ierr);
567
568 // --- Accumulate Counts Locally ---
569 LOG_ALLOW(LOCAL, LOG_DEBUG, "CalculateParticleCountPerCell (Rank %d): Processing %" PetscInt_FMT " local particles using GLOBAL CellIDs.\n",rank,nlocal);
570 for (p = 0; p < nlocal; p++) {
571 // Read the GLOBAL indices stored for this particle
572 PetscInt i_geom = global_cell_id_arr[p * 3 + 0]; // Global i index
573 PetscInt j_geom = global_cell_id_arr[p * 3 + 1]; // Global j index
574 PetscInt k_geom = global_cell_id_arr[p * 3 + 2]; // Global k index
575
576 // Apply the shift to ensure ParticleCount follows the indexing convention for cell-centered data in this codebase.
577 PetscInt i = (PetscInt)i_geom + 1; // Shift for cell-centered
578 PetscInt j = (PetscInt)j_geom + 1; // Shift for cell-centered
579 PetscInt k = (PetscInt)k_geom + 1; // Shift for cell-centered
580
581 // *** Bounds check is implicitly handled by DMDAVecGetArray for owned+ghost region ***
582 // However, accessing outside this region using global indices WILL cause an error.
583 // A preliminary check might still be wise if global IDs could be wild.
584 // We rely on LocateAllParticles to provide valid global indices [0..IM-1] etc.
585
587 "[Rank %d] Read CellID for p=%" PetscInt_FMT ", PID = %" PetscInt64_FMT ": (%" PetscInt_FMT ", %" PetscInt_FMT ", %" PetscInt_FMT ")\n",
588 rank, p, PID_arr[p], i, j, k);
589
590 // Check if the global index (i,j,k) falls within the local + ghost range
591 if (i >= gxs && i < gxs + gxm &&
592 j >= gys && j < gys + gym && // Adjust based on actual ghost width
593 k >= gzs && k < gzs + gzm ) // This check prevents definite crashes but doesn't guarantee ownership
594 {
595
596 // Increment count at the location corresponding to GLOBAL index (I,J,K)
597 // 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);
598 count_arr_3d[k][j][i] += 1.0;
599 particles_counted_locally++;
600 } else {
601 // This particle's global ID is likely outside the range this rank handles (even ghosts)
602 // note: this is not necessarily an error if the particle is legitimately outside the local+ghost region
604 "(Rank %d): Skipping particle %" PetscInt64_FMT " with global CellID (%" PetscInt_FMT ", %" PetscInt_FMT ", %" PetscInt_FMT ") - likely outside local+ghost range.\n",
605 rank, PID_arr[p], i, j, k);
606 }
607 }
608 LOG_ALLOW(LOCAL, LOG_DEBUG, "(Rank %d): Local counting finished. Processed %" PetscInt_FMT " particles locally.\n", rank, particles_counted_locally);
609
610 // --- Restore Access ---
611 ierr = DMDAVecRestoreArray(da, localcountVec, &count_arr_3d); CHKERRQ(ierr);
612 ierr = DMSwarmRestoreField(swarm,ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void **)&global_cell_id_arr); CHKERRQ(ierr);
613 ierr = DMSwarmRestoreField(swarm,ParticleFieldName(PARTICLE_FIELD_ID_PID),NULL,NULL,(void **)&PID_arr);CHKERRQ(ierr);
614
615 // --- Assemble Global Vector ---
616 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Assembling global ParticleCount vector.\n");
617 ierr = VecZeroEntries(countVec); CHKERRQ(ierr); // Ensure global vector is zeroed before accumulation
618 ierr = DMLocalToGlobalBegin(da, localcountVec, ADD_VALUES, countVec); CHKERRQ(ierr);
619 ierr = DMLocalToGlobalEnd(da, localcountVec, ADD_VALUES, countVec); CHKERRQ(ierr);
620 /*
621 * OPTIONAL: Synchronize Ghosts for Stencil Operations
622 * If a future function needs to read ParticleCount from neighbor cells (e.g., density smoothing
623 * or gradient calculations), uncomment the following lines to update the ghost slots
624 * in user->lParticleCount with the final summed values.
625 *
626 ierr = UpdateLocalGhosts(user, FIELD_ID_PARTICLE_COUNT); CHKERRQ(ierr);
627 */
628
629 // --- Verification Logging ---
630 PetscReal total_counted_particles = 0.0, max_count_in_cell = 0.0;
631 ierr = VecSum(countVec, &total_counted_particles); CHKERRQ(ierr);
632 PetscInt max_idx_global = -1;
633 ierr = VecMax(countVec, &max_idx_global, &max_count_in_cell); CHKERRQ(ierr);
634 LOG_ALLOW(GLOBAL, LOG_INFO, "Total counted globally = %.0f, Max count in cell = %.0f\n",
635 total_counted_particles, max_count_in_cell);
636
637 // --- ADD THIS DEBUGGING BLOCK ---
638 if (max_idx_global >= 0) { // Check if VecMax found a location
639 // Need to convert the flat global index back to 3D global index (I, J, K)
640 // Get global grid dimensions (Nodes, NOT Cells IM/JM/KM)
641 PetscInt M, N, P;
642 ierr = DMDAGetInfo(da, NULL, &M, &N, &P, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); CHKERRQ(ierr);
643 // Note: Assuming DOF=1 for countVec, index mapping uses node dimensions M,N,P from DMDA creation (IM+1, etc)
644 // 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.
645 PetscInt Kmax = max_idx_global / (M * N);
646 PetscInt Jmax = (max_idx_global % (M * N)) / M;
647 PetscInt Imax = max_idx_global % M;
648 LOG_ALLOW(GLOBAL, LOG_INFO, " -> Max count located at global index (I,J,K) = (%d, %d, %d) [Flat index: %d]\n",
649 (int)Imax, (int)Jmax, (int)Kmax, (int)max_idx_global);
650
651 // Also, let's explicitly check the count at (0,0,0)
652 PetscScalar count_at_origin = 0.0;
653 PetscScalar ***count_arr_for_check;
654 ierr = DMDAVecGetArrayRead(da, countVec, &count_arr_for_check); CHKERRQ(ierr);
655 // Check bounds before accessing - crucial if using global indices
656 PetscInt xs, ys, zs, xm, ym, zm;
657 ierr = DMDAGetCorners(da, &xs, &ys, &zs, &xm, &ym, &zm); CHKERRQ(ierr);
658 if (0 >= xs && 0 < xs+xm && 0 >= ys && 0 < ys+ym && 0 >= zs && 0 < zs+zm) {
659 count_at_origin = count_arr_for_check[0][0][0]; // Access using global index (0,0,0)
660 } else {
661 // Origin is not on this rank (relevant for parallel, but check anyway)
662 count_at_origin = -999.0; // Indicate it wasn't accessible locally
663 }
664 ierr = DMDAVecRestoreArrayRead(da, countVec, &count_arr_for_check); CHKERRQ(ierr);
665 LOG_ALLOW(GLOBAL, LOG_INFO, " -> Count at global index (0,0,0) = %.1f\n", count_at_origin);
666
667 } else {
668 LOG_ALLOW(GLOBAL, LOG_WARNING, " -> VecMax did not return a location for the maximum value.\n");
669 }
670 // --- END DEBUGGING BLOCK ---
671
672 LOG_ALLOW(GLOBAL, LOG_INFO, "Particle counting complete.\n");
673
674
676 PetscFunctionReturn(0);
677}
678
679
680
681#undef __FUNCT__
682#define __FUNCT__ "ResizeSwarmGlobally"
683/**
684 * @brief Implementation of \ref ResizeSwarmGlobally().
685 * @details Full API contract (arguments, ownership, side effects) is documented with
686 * the header declaration in `include/ParticleMotion.h`.
687 * @see ResizeSwarmGlobally()
688 */
689
690PetscErrorCode ResizeSwarmGlobally(DM swarm, PetscInt N_target)
691{
692 PetscErrorCode ierr;
693 PetscInt N_current, N_final, nlocal_current, nlocal_target;
694 PetscMPIInt rank, size;
695 MPI_Comm comm;
696
697 PetscFunctionBeginUser;
699 ierr = PetscObjectGetComm((PetscObject)swarm, &comm); CHKERRQ(ierr);
700 ierr = MPI_Comm_rank(comm, &rank); CHKERRQ(ierr);
701 ierr = MPI_Comm_size(comm, &size); CHKERRQ(ierr);
702 PetscCheck(N_target >= 0, comm, PETSC_ERR_ARG_OUTOFRANGE,
703 "Target swarm size must be nonnegative; got %" PetscInt_FMT ".", N_target);
704 ierr = DMSwarmGetSize(swarm, &N_current); CHKERRQ(ierr);
705 ierr = DMSwarmGetLocalSize(swarm, &nlocal_current); CHKERRQ(ierr);
706 nlocal_target = N_target / size + (rank < N_target % size ? 1 : 0);
707
708 if (nlocal_current != nlocal_target) {
710 "Rank %d: resizing local swarm share from %" PetscInt_FMT
711 " to %" PetscInt_FMT ".\n",
712 rank, nlocal_current, nlocal_target);
713 ierr = DMSwarmSetLocalSizes(swarm, nlocal_target, -1); CHKERRQ(ierr);
714 }
715
716 // Verify final size
717 ierr = DMSwarmGetSize(swarm, &N_final); CHKERRQ(ierr);
718 if (N_final != N_target) {
719 SETERRQ(comm, PETSC_ERR_PLIB,
720 "Failed to resize swarm: expected %" PetscInt_FMT
721 " particles, got %" PetscInt_FMT, N_target, N_final);
722 }
724 "Swarm resized from %" PetscInt_FMT " to %" PetscInt_FMT " particles.\n",
725 N_current, N_final);
727 PetscFunctionReturn(0);
728}
729
730#undef __FUNCT__
731#define __FUNCT__ "PreCheckAndResizeSwarm"
732/**
733 * @brief Internal helper implementation: `PreCheckAndResizeSwarm()`.
734 * @details Local to this translation unit.
735 */
736PetscErrorCode PreCheckAndResizeSwarm(UserCtx *user,
737 PetscInt ti,
738 const char *ext)
739{
740 PetscErrorCode ierr;
741 PetscInt N_file = 0;
742 PetscInt N_current = 0;
743
744 PetscFunctionBeginUser;
746 (void)ext;
747 ierr = ReadCheckpointParticleCount(user, ti, &N_file); CHKERRQ(ierr);
749 "Committed checkpoint step %d records %d particles.\n", ti, N_file);
750
751
752 // --- Now all ranks have the correct N_file, compare and resize if needed ---
753 ierr = DMSwarmGetSize(user->swarm, &N_current); CHKERRQ(ierr);
754
755 if (N_file != N_current) {
756 LOG_ALLOW(GLOBAL, LOG_INFO, "Swarm size %d differs from file size %d. Resizing swarm globally.\n", N_current, N_file);
757 ierr = ResizeSwarmGlobally(user->swarm, N_file); CHKERRQ(ierr);
758 } else {
759 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Swarm size (%d) already matches file size. No resize needed.\n", N_current);
760 }
761
762 // Also update the context
763 user->simCtx->np = N_file;
764
766 PetscFunctionReturn(0);
767}
768
769
770#undef __FUNCT__
771#define __FUNCT__ "ReinitializeParticlesOnInletSurface"
772/**
773 * @brief Internal helper implementation: `ReinitializeParticlesOnInletSurface()`.
774 * @details Local to this translation unit.
775 */
776PetscErrorCode ReinitializeParticlesOnInletSurface(UserCtx *user, PetscReal currentTime, PetscInt step)
777{
778 PetscErrorCode ierr;
779 PetscMPIInt rank; // MPI rank of the current process
780 DM swarm = user->swarm; // The particle swarm DM
781 PetscReal *positions_field = NULL; // Pointer to swarm field for physical positions
782 PetscInt64 *particleIDs = NULL; // Pointer to swarm field for Particle IDs (for logging)
783 PetscInt *cell_ID_field = NULL; // Pointer to swarm field for Cell IDs (for resetting after migration)
784 const Cmpnts ***coor_nodes_local_array; // Read-only access to local node coordinates
785 Vec Coor_local; // Local vector for node coordinates
786 DMDALocalInfo info; // Local grid information (node-based) from user->da
787 PetscInt xs_gnode_rank, ys_gnode_rank, zs_gnode_rank; // Local starting node indices (incl. ghosts) of rank's DA
788 PetscInt IM_nodes_global, JM_nodes_global, KM_nodes_global; // Global node counts
789
790 PetscRandom rand_logic_reinit_i, rand_logic_reinit_j, rand_logic_reinit_k; // RNGs for re-placement
791 PetscInt nlocal_current; // Number of particles currently on this rank
792 PetscInt particles_actually_reinitialized_count = 0; // Counter for logging
793 PetscBool can_this_rank_service_inlet = PETSC_FALSE; // Flag
794
795 PetscFunctionBeginUser;
796
798
799 // This function is only relevant for surface initialization mode and if an inlet face is defined.
800 if ((user->simCtx->ParticleInitialization != 0 && user->simCtx->ParticleInitialization !=3) || !user->inletFaceDefined) {
802 PetscFunctionReturn(0);
803 }
804
805 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
806 ierr = DMSwarmGetLocalSize(swarm, &nlocal_current); CHKERRQ(ierr);
807
808 // If no particles on this rank, nothing to do.
809 if (nlocal_current == 0) {
810 LOG_ALLOW(LOCAL, LOG_DEBUG, "[T=%.4f, Step=%d] Rank %d has no local particles to re-initialize on inlet.\n", currentTime, step, rank);
812 PetscFunctionReturn(0);
813 }
814
815 // Get DMDA information for the node-centered coordinate grid (user->da)
816 ierr = DMDAGetLocalInfo(user->da, &info); CHKERRQ(ierr);
817 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);
818 ierr = DMDAGetCorners(user->da, &xs_gnode_rank, &ys_gnode_rank, &zs_gnode_rank, NULL, NULL, NULL); CHKERRQ(ierr);
819
820 // Modification to IM_nodes_global etc. to account for 1-cell halo in each direction.
821 IM_nodes_global -= 1; JM_nodes_global -= 1; KM_nodes_global -= 1;
822
823 const PetscInt IM_cells_global = IM_nodes_global > 0 ? IM_nodes_global - 1 : 0;
824 const PetscInt JM_cells_global = JM_nodes_global > 0 ? JM_nodes_global - 1 : 0;
825 const PetscInt KM_cells_global = KM_nodes_global > 0 ? KM_nodes_global - 1 : 0;
826
827
828
829 // Check if this rank is responsible for (part of) the designated inlet surface
830 ierr = CanRankServiceInletFace(user, &info, IM_nodes_global, JM_nodes_global, KM_nodes_global, &can_this_rank_service_inlet); CHKERRQ(ierr);
831
832 // Get coordinate array and swarm fields for modification
833 ierr = DMGetCoordinatesLocal(user->da, &Coor_local); CHKERRQ(ierr);
834 ierr = DMDAVecGetArrayRead(user->fda, Coor_local, (void*)&coor_nodes_local_array); CHKERRQ(ierr);
835 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&positions_field); CHKERRQ(ierr);
836 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&particleIDs); CHKERRQ(ierr); // For logging
837 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_ID_field); CHKERRQ(ierr);
838
839 if (!can_this_rank_service_inlet) {
840 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);
841
842 // FALLBACK ACTION: Reset position fields to Inlet center for migration and cell ID to -1 for safety.
843 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);
844
845 for(PetscInt p = 0; p < nlocal_current; p++){
846 positions_field[3*p+0] = user->simCtx->CMx_c;
847 positions_field[3*p+1] = user->simCtx->CMy_c;
848 positions_field[3*p+2] = user->simCtx->CMz_c;
849
850 cell_ID_field[3*p+0] = -1;
851 cell_ID_field[3*p+1] = -1;
852 cell_ID_field[3*p+2] = -1;
853 }
854
855 // Cleanup: restore swarm fields/coordinate array
856 ierr = DMDAVecRestoreArrayRead(user->fda, Coor_local, (void*)&coor_nodes_local_array); CHKERRQ(ierr);
857 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&positions_field); CHKERRQ(ierr);
858 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&particleIDs); CHKERRQ(ierr); // For logging
859 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_ID_field); CHKERRQ(ierr);
861 PetscFunctionReturn(0);
862 }
863
864 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);
865
866 // Initialize fresh RNGs for this re-placement to ensure good distribution
867 ierr = InitializeLogicalSpaceRNGs(user->simCtx->particleRandomSeed, &rand_logic_reinit_i, &rand_logic_reinit_j, &rand_logic_reinit_k); CHKERRQ(ierr);
868
869 // Loop over all particles currently local to this rank
870 for (PetscInt p = 0; p < nlocal_current; p++) {
871 PetscInt ci_metric_lnode, cj_metric_lnode, ck_metric_lnode; // Local node indices (of rank's DA patch) for cell origin
872 PetscReal xi_metric_logic, eta_metric_logic, zta_metric_logic; // Intra-cell logical coordinates
873 Cmpnts phys_coords = {0.0,0.0,0.0}; // To store newly calculated physical coordinates
874 PetscBool particle_was_placed = PETSC_FALSE;
875
877 // Get random cell on this rank's portion of the inlet and random logical coords within it
878 ierr = GetRandomCellAndLogicalCoordsOnInletFace(user, &info, xs_gnode_rank, ys_gnode_rank, zs_gnode_rank,
879 IM_nodes_global, JM_nodes_global, KM_nodes_global,
880 &rand_logic_reinit_i, &rand_logic_reinit_j, &rand_logic_reinit_k,
881 &ci_metric_lnode, &cj_metric_lnode, &ck_metric_lnode,
882 &xi_metric_logic, &eta_metric_logic, &zta_metric_logic); CHKERRQ(ierr);
883
884 // Convert these logical coordinates to physical coordinates
885
886 ierr = MetricLogicalToPhysical(user, coor_nodes_local_array,
887 ci_metric_lnode, cj_metric_lnode, ck_metric_lnode,
888 xi_metric_logic, eta_metric_logic, zta_metric_logic,
889 &phys_coords); CHKERRQ(ierr);
890
891 // Update the particle's position in the swarm fields
892 positions_field[3*p+0] = phys_coords.x;
893 positions_field[3*p+1] = phys_coords.y;
894 positions_field[3*p+2] = phys_coords.z;
895 particle_was_placed = PETSC_TRUE;
896
898 PetscBool placement_flag = PETSC_FALSE;
899 ierr = GetDeterministicFaceGridLocation(user, &info, xs_gnode_rank, ys_gnode_rank, zs_gnode_rank,
900 IM_cells_global, JM_cells_global, KM_cells_global,
901 particleIDs[p],
902 &ci_metric_lnode, &cj_metric_lnode, &ck_metric_lnode,
903 &xi_metric_logic, &eta_metric_logic, &zta_metric_logic,&placement_flag); CHKERRQ(ierr);
904
905
906 if(placement_flag){
907 // Convert these logical coordinates to physical coordinates
908 ierr = MetricLogicalToPhysical(user, coor_nodes_local_array,
909 ci_metric_lnode, cj_metric_lnode, ck_metric_lnode,
910 xi_metric_logic, eta_metric_logic, zta_metric_logic,
911 &phys_coords); CHKERRQ(ierr);
912
913 // Update the particle's position in the swarm fields
914 positions_field[3*p+0] = phys_coords.x;
915 positions_field[3*p+1] = phys_coords.y;
916 positions_field[3*p+2] = phys_coords.z;
917 particle_was_placed = PETSC_TRUE;
918 } else{
919 // Deterministic placement failed (particle migrated to rank where formula says it doesn't belong)
920 // Fall back to random placement on this rank's portion of inlet surface
921 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]);
922
923 ierr = GetRandomCellAndLogicalCoordsOnInletFace(user, &info, xs_gnode_rank, ys_gnode_rank, zs_gnode_rank,
924 IM_nodes_global, JM_nodes_global, KM_nodes_global,
925 &rand_logic_reinit_i, &rand_logic_reinit_j, &rand_logic_reinit_k,
926 &ci_metric_lnode, &cj_metric_lnode, &ck_metric_lnode,
927 &xi_metric_logic, &eta_metric_logic, &zta_metric_logic); CHKERRQ(ierr);
928
929 // Convert to physical coordinates
930 ierr = MetricLogicalToPhysical(user, coor_nodes_local_array,
931 ci_metric_lnode, cj_metric_lnode, ck_metric_lnode,
932 xi_metric_logic, eta_metric_logic, zta_metric_logic,
933 &phys_coords); CHKERRQ(ierr);
934
935 // Update particle position
936 positions_field[3*p+0] = phys_coords.x;
937 positions_field[3*p+1] = phys_coords.y;
938 positions_field[3*p+2] = phys_coords.z;
939 particle_was_placed = PETSC_TRUE;
940 }
941
942 } else{
943 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "ReinitializeParticlesOnInletSurface only supports ParticleInitialization modes 0 and 3.");
944 }
945
946 if(particle_was_placed){
947 particles_actually_reinitialized_count++;
948
949 cell_ID_field[3*p+0] = -1;
950 cell_ID_field[3*p+1] = -1;
951 cell_ID_field[3*p+2] = -1;
952
953 LOG_LOOP_ALLOW(LOCAL, LOG_VERBOSE, p, (nlocal_current > 20 ? nlocal_current/10 : 1), // Sampled logging
954 "Rank %d: PID %ld (idx %ld) RE-PLACED. CellOriginNode(locDAIdx):(%d,%d,%d). LogicCoords: (%.2e,%.2f,%.2f). PhysCoords: (%.6f,%.6f,%.6f).\n",
955 rank, particleIDs[p], (long)p,
956 ci_metric_lnode, cj_metric_lnode, ck_metric_lnode,
957 xi_metric_logic, eta_metric_logic, zta_metric_logic,
958 phys_coords.x, phys_coords.y, phys_coords.z);
959 }
960 }
961
962 // Logging summary of re-initialization
963 if (particles_actually_reinitialized_count > 0) {
964 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);
965 } else if (nlocal_current > 0) { // This case should ideally not be hit if can_this_rank_service_inlet was true and particles were present.
966 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);
967 }
968
969 // Cleanup: Destroy RNGs and restore swarm fields/coordinate array
970 ierr = PetscRandomDestroy(&rand_logic_reinit_i); CHKERRQ(ierr);
971 ierr = PetscRandomDestroy(&rand_logic_reinit_j); CHKERRQ(ierr);
972 ierr = PetscRandomDestroy(&rand_logic_reinit_k); CHKERRQ(ierr);
973
974 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&positions_field); CHKERRQ(ierr);
975 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&particleIDs); CHKERRQ(ierr);
976 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_ID_field); CHKERRQ(ierr);
977 ierr = DMDAVecRestoreArrayRead(user->fda, Coor_local, (void*)&coor_nodes_local_array); CHKERRQ(ierr);
978
979
981 PetscFunctionReturn(0);
982}
983
984#undef __FUNCT__
985#define __FUNCT__ "GetLocalPIDSnapshot"
986/**
987 * @brief Internal helper implementation: `GetLocalPIDSnapshot()`.
988 * @details Local to this translation unit.
989 */
990PetscErrorCode GetLocalPIDSnapshot(const PetscInt64 pid_field[],
991 PetscInt n_local,
992 PetscInt64 **pids_snapshot_out)
993{
994 PetscErrorCode ierr;
995 PetscMPIInt rank;
996
997 PetscFunctionBeginUser;
998
1000
1001 // --- 1. Input Validation ---
1002 if (!pids_snapshot_out) {
1003 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Output pointer pids_snapshot_out is NULL.");
1004 }
1005 // If n_local > 0, pid_field must not be NULL.
1006 if (n_local > 0 && !pid_field) {
1007 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Input pid_field pointer is NULL for n_local > 0.");
1008 }
1009
1010 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1011 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d]: Creating PID snapshot for %d local particles.\n", rank, n_local);
1012
1013 // If there are no local particles, the snapshot is empty (NULL).
1014 if (n_local == 0) {
1015 *pids_snapshot_out = NULL;
1016
1018 PetscFunctionReturn(0);
1019 }
1020
1021 // --- 2. Allocate Memory for the Snapshot ---
1022 ierr = PetscMalloc1(n_local, pids_snapshot_out); CHKERRQ(ierr);
1023
1024 // --- 3. Copy Data ---
1025 // Perform a fast memory copy from the provided array to our new snapshot array.
1026 ierr = PetscMemcpy(*pids_snapshot_out, pid_field, n_local * sizeof(PetscInt64)); CHKERRQ(ierr);
1027 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d]: Copied %d PIDs.\n", rank, n_local);
1028
1029 // --- 4. Sort the Snapshot Array ---
1030 // Sorting enables fast binary search lookups later.
1031 ierr = PetscSortInt64(n_local, *pids_snapshot_out); CHKERRQ(ierr);
1032 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d]: PID snapshot sorted successfully.\n", rank);
1033
1034
1036 PetscFunctionReturn(0);
1037}
1038
1039#undef __FUNCT__
1040#define __FUNCT__ "AddToMigrationList"
1041/**
1042 * @brief Internal helper implementation: `AddToMigrationList()`.
1043 * @details Local to this translation unit.
1044 */
1045PetscErrorCode AddToMigrationList(MigrationInfo **migration_list_p,
1046 PetscInt *capacity_p,
1047 PetscInt *count_p,
1048 PetscInt particle_local_idx,
1049 PetscMPIInt destination_rank)
1050{
1051 PetscErrorCode ierr;
1052 PetscMPIInt rank;
1053
1054 PetscFunctionBeginUser;
1055
1057
1058 // --- 1. Input Validation ---
1059 if (!migration_list_p || !capacity_p || !count_p) {
1060 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Null pointer provided to AddToMigrationList for list management.");
1061 }
1062
1063 // --- 2. Check if the list needs to be resized ---
1064 if (*count_p >= *capacity_p) {
1065 PetscInt old_capacity = *capacity_p;
1066 // Start with a reasonable base capacity, then double for subsequent reallocations.
1067 PetscInt new_capacity = (old_capacity == 0) ? 16 : old_capacity * 2;
1068
1069 // Use PetscRealloc for safe memory reallocation.
1070 // It handles allocating new memory, copying old data, and freeing the old block.
1071 // The first argument to PetscRealloc is the new size in BYTES.
1072 ierr = PetscRealloc(new_capacity * sizeof(MigrationInfo), migration_list_p); CHKERRQ(ierr);
1073
1074 *capacity_p = new_capacity; // Update the capacity tracker
1075
1076 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1077 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d]: Reallocated migrationList capacity from %d to %d.\n",
1078 rank, old_capacity, new_capacity);
1079 }
1080
1081 // --- 3. Add the new migration data to the list ---
1082 // Dereference the pointer-to-a-pointer to get the actual array.
1083 MigrationInfo *list = *migration_list_p;
1084
1085 list[*count_p].local_index = particle_local_idx;
1086 list[*count_p].target_rank = destination_rank;
1087
1088 // --- 4. Increment the count of items in the list ---
1089 (*count_p)++;
1090
1091
1093 PetscFunctionReturn(0);
1094}
1095
1096
1097#undef __FUNCT__
1098#define __FUNCT__ "FlagNewComersForLocation"
1099/**
1100 * @brief Internal helper implementation: `FlagNewcomersForLocation()`.
1101 * @details Local to this translation unit.
1102 */
1103PetscErrorCode FlagNewcomersForLocation(DM swarm,
1104 PetscInt n_local_before,
1105 const PetscInt64 pids_before[])
1106{
1107 PetscErrorCode ierr;
1108 PetscMPIInt rank;
1109 PetscInt n_local_after;
1110 PetscInt newcomer_count = 0;
1111
1112 // Pointers to the swarm data fields we will read and modify
1113 PetscInt64 *pid_field_after = NULL;
1114 PetscInt *status_field_after = NULL;
1115 PetscInt *cell_field_after = NULL;
1116
1117 PetscFunctionBeginUser;
1118
1120
1121 // --- 1. Input Validation and Basic Setup ---
1122 if (!swarm) {
1123 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, "Input DMSwarm is NULL in FlagNewcomersForLocation.");
1124 }
1125 // If n_local_before > 0, the corresponding PID array must not be null.
1126 if (n_local_before > 0 && !pids_before) {
1127 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Input pids_before array is NULL for n_local_before > 0.");
1128 }
1129
1130 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1131
1132 // Get the number of particles on this rank *after* the migration.
1133 ierr = DMSwarmGetLocalSize(swarm, &n_local_after); CHKERRQ(ierr);
1134
1135 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d]: Checking for newcomers. Size before: %d, Size after: %d\n",
1136 rank, n_local_before, n_local_after);
1137
1138 // If there are no particles now, there's nothing to do.
1139 if (n_local_after == 0) {
1141 PetscFunctionReturn(0);
1142 }
1143
1144 // --- 2. Access Swarm Data ---
1145 // Get read-only access to the PIDs and read-write access to the status field.
1146 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid_field_after); CHKERRQ(ierr);
1147 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status_field_after); CHKERRQ(ierr);
1148 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_field_after); CHKERRQ(ierr);
1149 if (!pid_field_after || !status_field_after) {
1150 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, "Failed to get required swarm fields in FlagNewcomersForLocation.");
1151 }
1152
1153 // --- 3. Identify and Flag Newcomers ---
1154 // Loop through all particles currently on this rank.
1155 for (PetscInt p_idx = 0; p_idx < n_local_after; ++p_idx) {
1156 PetscInt64 current_pid = pid_field_after[p_idx];
1157 PetscBool is_found_in_before_list;
1158
1159 // Use our custom, efficient helper function for the lookup.
1160 ierr = BinarySearchInt64(n_local_before, pids_before, current_pid, &is_found_in_before_list); CHKERRQ(ierr);
1161
1162 // If the PID was NOT found in the "before" list, it must be a newcomer.
1163 if (!is_found_in_before_list) {
1164 // Flag it for processing in the next pass of the migration loop.
1165 status_field_after[p_idx] = NEEDS_LOCATION;
1166 // cell_field_after[3*p_idx+0] = -1;
1167 // cell_field_after[3*p_idx+1] = -1;
1168 // cell_field_after[3*p_idx+2] = -1;
1169 newcomer_count++;
1170
1171 LOG_ALLOW(LOCAL, LOG_VERBOSE, "[Rank %d]: Flagged newcomer PID %ld at local index %d as NEEDS_LOCATION.\n",
1172 rank, current_pid, p_idx);
1173 }
1174 }
1175
1176 // --- 4. Restore Swarm Fields ---
1177 // Release the locks on the swarm data arrays.
1178 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid_field_after); CHKERRQ(ierr);
1179 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status_field_after); CHKERRQ(ierr);
1180 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_field_after); CHKERRQ(ierr);
1181
1182 if (newcomer_count > 0) {
1183 LOG_ALLOW(LOCAL, LOG_INFO, "[Rank %d]: Identified and flagged %d newcomers.\n", rank, newcomer_count);
1184 }
1185
1186
1188 PetscFunctionReturn(0);
1189}
1190
1191#undef __FUNCT__
1192#define __FUNCT__ "MigrateRestartParticlesUsingCellID"
1193/**
1194 * @brief Internal helper implementation: `MigrateRestartParticlesUsingCellID()`.
1195 * @details Local to this translation unit.
1196 */
1198{
1199 PetscErrorCode ierr;
1200 DM swarm = user->swarm;
1201 PetscInt nlocal;
1202 PetscInt *cell_p = NULL;
1203 PetscInt64 *pid_p = NULL;
1204 PetscMPIInt rank;
1205
1206 MigrationInfo *migrationList = NULL;
1207 PetscInt local_migration_count = 0;
1208 PetscInt migrationListCapacity = 0;
1209 PetscInt global_migration_count = 0;
1210
1211 PetscFunctionBeginUser;
1213 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1214
1215 ierr = DMSwarmGetLocalSize(swarm, &nlocal); CHKERRQ(ierr);
1216 LOG_ALLOW(LOCAL, LOG_DEBUG, "Checking %d restart particles for direct migration using CellIDs.\n", nlocal);
1217
1218 if (nlocal > 0) {
1219 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_p); CHKERRQ(ierr);
1220 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid_p); CHKERRQ(ierr);
1221
1222 // Note: We do NOT need to modify the status field here.
1223 // We trust the loaded status (ACTIVE_AND_LOCATED) is correct for the destination rank.
1224
1225 for (PetscInt p_idx = 0; p_idx < nlocal; ++p_idx) {
1226 PetscInt ci = cell_p[3*p_idx + 0];
1227 PetscInt cj = cell_p[3*p_idx + 1];
1228 PetscInt ck = cell_p[3*p_idx + 2];
1229
1230 /* Skip particles with invalid Cell IDs (will be handled by LocateAllParticles) */
1231 if (ci < 0 || cj < 0 || ck < 0) {
1232 continue;
1233 }
1234
1235 PetscMPIInt owner_rank;
1236 ierr = FindOwnerOfCell(user, ci, cj, ck, &owner_rank); CHKERRQ(ierr);
1237
1238 if (owner_rank != -1 && owner_rank != rank) {
1239 /* Particle belongs to another rank - migrate it */
1240 ierr = AddToMigrationList(&migrationList, &migrationListCapacity, &local_migration_count,
1241 p_idx, owner_rank); CHKERRQ(ierr);
1242
1243 LOG_ALLOW(LOCAL, LOG_VERBOSE, "[PID %ld] Direct migration: Cell (%d,%d,%d) belongs to Rank %d (Current: %d).\n",
1244 (long)pid_p[p_idx], ci, cj, ck, owner_rank, rank);
1245 }
1246 }
1247
1248 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_p); CHKERRQ(ierr);
1249 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid_p); CHKERRQ(ierr);
1250 }
1251
1252 /* Check if any rank needs to migrate particles */
1253 ierr = MPI_Allreduce(&local_migration_count, &global_migration_count, 1, MPIU_INT, MPI_SUM, PETSC_COMM_WORLD); CHKERRQ(ierr);
1254
1255 if (global_migration_count > 0) {
1256 LOG_ALLOW(GLOBAL, LOG_INFO, "Fast restart migration: Directly migrating %d particles using CellIDs.\n", global_migration_count);
1257 ierr = SetMigrationRanks(user, migrationList, local_migration_count); CHKERRQ(ierr);
1258 ierr = PerformMigration(user); CHKERRQ(ierr);
1259 /* We do NOT flag newcomers here. We trust their loaded status (ACTIVE_AND_LOCATED) */
1260 /* is valid for their destination rank. */
1261 } else {
1262 LOG_ALLOW(GLOBAL, LOG_INFO, "Fast restart migration: All particles are already on correct ranks.\n");
1263 }
1264
1265 ierr = PetscFree(migrationList); CHKERRQ(ierr);
1266
1268 PetscFunctionReturn(0);
1269}
1270
1271#undef __FUNCT__
1272#define __FUNCT__ "GuessParticleOwnerWithBBox"
1273/**
1274 * @brief Select the rank whose gathered bounding box is the best owner candidate for a particle.
1275 * @note Testing status:
1276 * The current direct surface reaches this helper through orchestrator
1277 * tests, but direction-complete immediate-neighbor coverage and the
1278 * explicit "not found in any rank" path are still targeted for future
1279 * bespoke tests.
1280 */
1281static PetscErrorCode GuessParticleOwnerWithBBox(UserCtx *user,
1282 const Particle *particle,
1283 const BoundingBox *bboxlist,
1284 PetscMPIInt *guess_rank_out)
1285{
1286 PetscErrorCode ierr;
1287 PetscMPIInt rank, size;
1288 const RankNeighbors *neighbors = &user->neighbors; // Use a direct pointer for clarity
1289 const BoundingBox *localBBox = &user->bbox;
1290
1291 PetscFunctionBeginUser;
1292
1294
1295 // --- 1. Input Validation and Setup ---
1296 if (!user || !particle || !guess_rank_out || !bboxlist) {
1297 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Null pointer provided to GuessParticleOwnerWithBBox.");
1298 }
1299 if (!localBBox|| !neighbors) {
1300 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, "Required user->bboxl or user->neighbors is not initialized.");
1301 }
1302
1303 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1304 ierr = MPI_Comm_size(PETSC_COMM_WORLD, &size); CHKERRQ(ierr);
1305
1306 *guess_rank_out = MPI_PROC_NULL; // Default to "not found"
1307
1308 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld]: Starting guess for particle at (%.3f, %.3f, %.3f).\n",
1309 particle->PID, particle->loc.x, particle->loc.y, particle->loc.z);
1310
1311 // --- Step 0: Check if the particle is inside the CURRENT rank's bounding box FIRST. ---
1312 // This handles the common case of initial placement where a particle is "lost" but physically local.
1313 if (IsParticleInBox(localBBox, &particle->loc)) {
1314 *guess_rank_out = rank;
1315 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld]: Fast path guess SUCCESS. Particle is within the local (Rank %d) bounding box.\n",
1316 particle->PID, rank);
1317
1319 PetscFunctionReturn(0); // Found it, we're done.
1320 }
1321 // --- 2. Fast Path: Check Immediate Neighbors Based on Exit Direction ---
1322
1323 // Determine likely exit direction(s) to prioritize neighbor check
1324 PetscBool exit_xm = particle->loc.x < localBBox->min_coords.x;
1325 PetscBool exit_xp = particle->loc.x > localBBox->max_coords.x;
1326 PetscBool exit_ym = particle->loc.y < localBBox->min_coords.y;
1327 PetscBool exit_yp = particle->loc.y > localBBox->max_coords.y;
1328 PetscBool exit_zm = particle->loc.z < localBBox->min_coords.z;
1329 PetscBool exit_zp = particle->loc.z > localBBox->max_coords.z;
1330
1331 if (exit_xm && neighbors->rank_xm != MPI_PROC_NULL && IsParticleInBox(&bboxlist[neighbors->rank_xm], &particle->loc)) {
1332 *guess_rank_out = neighbors->rank_xm;
1333 } else if (exit_xp&& neighbors->rank_xp != MPI_PROC_NULL && IsParticleInBox(&bboxlist[neighbors->rank_xp], &particle->loc)) {
1334 *guess_rank_out = neighbors->rank_xp;
1335 } else if (exit_ym && neighbors->rank_ym != MPI_PROC_NULL && IsParticleInBox(&bboxlist[neighbors->rank_ym], &particle->loc)) {
1336 *guess_rank_out = neighbors->rank_ym;
1337 } else if (exit_yp && neighbors->rank_yp != MPI_PROC_NULL && IsParticleInBox(&bboxlist[neighbors->rank_yp], &particle->loc)) {
1338 *guess_rank_out = neighbors->rank_yp;
1339 } else if (exit_zm && neighbors->rank_zm != MPI_PROC_NULL && IsParticleInBox(&bboxlist[neighbors->rank_zm], &particle->loc)) {
1340 *guess_rank_out = neighbors->rank_zm;
1341 } else if (exit_zp && neighbors->rank_zp != MPI_PROC_NULL && IsParticleInBox(&bboxlist[neighbors->rank_zp], &particle->loc)) {
1342 *guess_rank_out = neighbors->rank_zp;
1343 }
1344 // Note: This does not handle corner/edge neighbors, which is why the fallback is essential.
1345
1346 if (*guess_rank_out != MPI_PROC_NULL) {
1347 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld]: Fast path guess SUCCESS. Found in immediate neighbor Rank %d.\n",
1348 particle->PID, *guess_rank_out);
1349
1351 PetscFunctionReturn(0); // Found it, we're done.
1352 }
1353
1354 // --- 3. Robust Fallback: Check All Other Ranks ---
1355 // If we get here, the particle was not in any of the immediate face neighbors' boxes.
1356 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld]: Not in immediate face neighbors. Starting global fallback search.\n",
1357 particle->PID);
1358
1359 for (PetscMPIInt r = 0; r < size; ++r) {
1360 if (r == rank) continue; // Don't check ourselves.
1361
1362 if (IsParticleInBox(&bboxlist[r], &particle->loc)) {
1363 PetscBool is_in = PETSC_TRUE;
1364 // This detailed, synchronized print will solve the mystery
1365 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",
1366 (int)rank, (long long)particle->PID,
1367 particle->loc.x, particle->loc.y, particle->loc.z,
1368 (int)r,
1369 bboxlist[r].min_coords.x, bboxlist[r].min_coords.y, bboxlist[r].min_coords.z,
1370 bboxlist[r].max_coords.x, bboxlist[r].max_coords.y, bboxlist[r].max_coords.z,
1371 is_in ? "INSIDE" : "OUTSIDE");
1372
1373 *guess_rank_out = r;
1374 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld]: Fallback search SUCCESS. Found in Rank %d.\n",
1375 particle->PID, *guess_rank_out);
1376
1378 PetscFunctionReturn(0); // Found it, we're done.
1379 }
1380 }
1381
1382 // If the code reaches here, the particle was not found in any rank's bounding box.
1383 LOG_ALLOW(LOCAL, LOG_WARNING, "[PID %ld]: Guess FAILED. Particle not found in any rank's bounding box.\n",
1384 particle->PID);
1385
1386 // The guess_rank_out will remain -1, signaling failure to the caller.
1388 PetscFunctionReturn(0);
1389}
1390
1391#undef __FUNCT__
1392#define __FUNCT__ "LocateAllParticlesInGrid"
1393/**
1394 * @brief Implementation of \ref LocateAllParticlesInGrid().
1395 * @details Full API contract (arguments, ownership, side effects) is documented with
1396 * the header declaration in `include/ParticleMotion.h`.
1397 * @see LocateAllParticlesInGrid()
1398 */
1399PetscErrorCode LocateAllParticlesInGrid(UserCtx *user,BoundingBox *bboxlist)
1400{
1401 PetscErrorCode ierr;
1402 PetscInt passes = 0;
1403 const PetscInt MAX_MIGRATION_PASSES = 50; // Safety break for runaway loops
1404 PetscInt global_migrations_this_pass;
1405 PetscMPIInt rank;
1406 PetscInt total_migrated_this_timestep = 0;
1407
1408 PetscFunctionBeginUser;
1410 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1411 ierr = ResetSearchMetrics(user->simCtx); CHKERRQ(ierr);
1412 LOG_ALLOW(GLOBAL, LOG_INFO, "LocateAllParticlesInGrid (Orchestrator) - Beginning particle settlement process.\n");
1413
1414 // This loop ensures that particles that jump across multiple ranks are
1415 // handled correctly in successive, iterative handoffs.
1416 do {
1417 passes++;
1419 LOG_ALLOW_SYNC(GLOBAL, LOG_INFO, "[Rank %d] Starting migration pass %d.\n", rank, passes);
1420
1421 // --- STAGE 1: PER-PASS INITIALIZATION ---
1422 MigrationInfo *migrationList = NULL;
1423 PetscInt local_migration_count = 0;
1424 PetscInt migrationListCapacity = 0;
1425 PetscInt nlocal_before;
1426 PetscInt64 *pids_before_snapshot = NULL;
1427 PetscInt local_lost_count = 0;
1428
1429 ierr = DMSwarmGetLocalSize(user->swarm, &nlocal_before); CHKERRQ(ierr);
1430 if (passes == 1) {
1431 user->simCtx->searchMetrics.searchPopulation += (PetscInt64)nlocal_before;
1432 }
1433 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d] Pass %d begins with %d local particles.\n", rank, passes, nlocal_before);
1434
1435
1436 // --- STAGE 2: PRE-MIGRATION SNAPSHOT & MAIN PROCESSING LOOP ---
1437 if (nlocal_before > 0) {
1438 // Get pointers to all fields needed for this pass
1439 PetscReal *pos_p, *weights_p, *vel_p;
1440 PetscInt *cell_p, *status_p;
1441 PetscInt64 *pid_p;
1442 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&pos_p); CHKERRQ(ierr);
1443 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_VELOCITY), NULL, NULL, (void**)&vel_p); CHKERRQ(ierr);
1444 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_WEIGHT), NULL, NULL, (void**)&weights_p); CHKERRQ(ierr);
1445 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_p); CHKERRQ(ierr);
1446 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid_p); CHKERRQ(ierr);
1447 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status_p); CHKERRQ(ierr);
1448
1449 // Create a sorted snapshot of current PIDs to identify newcomers after migration.
1450 // This helper requires a raw pointer, which we just acquired.
1451 ierr = GetLocalPIDSnapshot(pid_p, nlocal_before, &pids_before_snapshot); CHKERRQ(ierr);
1452
1453 for (PetscInt p_idx = 0; p_idx < nlocal_before; p_idx++) {
1454
1455 // OPTIMIZATION: Skip particles already settled in a previous pass of this do-while loop.
1456
1458 "Local Particle idx=%d, PID=%ld, status=%s, cell=(%d, %d, %d)\n",
1459 p_idx,
1460 (long)pid_p[p_idx],
1462 cell_p[3*p_idx],
1463 cell_p[3*p_idx+1],
1464 cell_p[3*p_idx+2]);
1465
1466 if (status_p[p_idx] == ACTIVE_AND_LOCATED) {
1467 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]);
1468 continue;
1469 }
1470
1471 // UNPACK: Create a temporary C struct for easier processing using our helper.
1472 Particle current_particle;
1473
1474 // LOG_ALLOW(LOCAL,LOG_DEBUG,"about to unpack p_idx=%d (PID=%ld)\n",p_idx, (long)pid_p[p_idx]);
1475
1476 ierr = UnpackSwarmFields(p_idx, pid_p, weights_p, pos_p, cell_p, vel_p, status_p,NULL,NULL,NULL,&current_particle); CHKERRQ(ierr);
1477
1478 // 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));
1479
1480 ParticleLocationStatus final_status = (ParticleLocationStatus)status_p[p_idx];
1481
1482
1483 // CASE 1: Particle has a valid prior cell index.
1484 // It has moved, so we only need to run the robust walk from its last known location.
1485 if (current_particle.cell[0] >= 0) {
1486 LOG_ALLOW(LOCAL, LOG_VERBOSE, "[PID %ld] has valid prior cell. Strategy: Robust Walk from previous cell.\n", current_particle.PID);
1487 ierr = LocateParticleOrFindMigrationTarget(user, &current_particle, &final_status); CHKERRQ(ierr);
1488 }
1489
1490 /*
1491 // --- "GUESS" FAST PATH for lost particles ---
1492 if (current_particle.cell[0] < 0) {
1493 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld] is lost or uninitialzied (cell=%d), attempting fast guess.\n",current_particle.PID, current_particle.cell[0]);
1494 ierr = GuessParticleOwnerWithBBox(user, &current_particle, bboxlist, &destination_rank); CHKERRQ(ierr);
1495 if (destination_rank != MPI_PROC_NULL && destination_rank != rank) {
1496 final_status = MIGRATING_OUT;
1497 // The particle struct's destination rank must be updated for consistency
1498 current_particle.destination_rank = destination_rank;
1499 }
1500 }
1501
1502 LOG_ALLOW(LOCAL,LOG_DEBUG,"[PID %ld] Particle status after Initial Guess:%d \n",current_particle.PID,final_status);
1503
1504 // --- "VERIFY" ROBUST WALK if guess didn't resolve it ---
1505 if (final_status == NEEDS_LOCATION || UNINITIALIZED) {
1506 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld] Not resolved by guess, starting robust walk.\n", current_particle.PID);
1507 // This function will update the particle's status and destination rank internally.
1508 ierr = LocateParticleOrFindMigrationTarget(user, &current_particle, &final_status); CHKERRQ(ierr);
1509 destination_rank = current_particle.destination_rank; // Retrieve the result
1510 }
1511
1512 // --- PROCESS THE FINAL STATUS AND TAKE ACTION ---
1513 if (final_status == MIGRATING_OUT) {
1514 status_p[p_idx] = MIGRATING_OUT; // Mark for removal by DMSwarm
1515 ierr = AddToMigrationList(&migrationList, &migrationListCapacity, &local_migration_count, p_idx, destination_rank); CHKERRQ(ierr);
1516 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);
1517 } else {
1518 // Particle's final status is either LOCATED or LOST; update its state in the swarm arrays.
1519 current_particle.location_status = final_status;
1520 // PACK: Use the helper to write results back to the swarm arrays.
1521 ierr = UpdateSwarmFields(p_idx, &current_particle, pos_p, vel_p, weights_p, cell_p, status_p,NULL,NULL,NULL); CHKERRQ(ierr);
1522 }
1523 */
1524 // CASE 2: Particle is "lost" (cell = -1). Strategy: Guess -> Verify.
1525 else {
1526 LOG_ALLOW(LOCAL, LOG_VERBOSE, "[PID %ld] has invalid cell. Strategy: Guess Owner -> Find Cell.\n",current_particle.PID);
1527
1528 PetscMPIInt guessed_owner_rank = MPI_PROC_NULL;
1529 ierr = GuessParticleOwnerWithBBox(user, &current_particle, bboxlist, &guessed_owner_rank); CHKERRQ(ierr);
1530
1531 // If the guess finds a DIFFERENT rank, we can mark for migration and skip the walk.
1532 if (guessed_owner_rank != MPI_PROC_NULL && guessed_owner_rank != rank) {
1534 LOG_ALLOW(LOCAL, LOG_VERBOSE, "[PID %ld] Guess SUCCESS: Found migration target Rank %d. Finalizing.\n", current_particle.PID, guessed_owner_rank);
1535 final_status = MIGRATING_OUT;
1536 current_particle.destination_rank = guessed_owner_rank;
1537 }
1538 else {
1540
1541 // This block runs if the guess either failed (rank is NULL) or found the particle is local (rank is self).
1542 // In BOTH cases, the situation is unresolved, and we MUST fall back to the robust walk.
1543 if (guessed_owner_rank == rank) {
1544 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld] Guess determined particle is local. Proceeding to robust walk to find cell.\n", current_particle.PID);
1545 } else { // guessed_owner_rank == MPI_PROC_NULL
1546 LOG_ALLOW(LOCAL, LOG_WARNING, "[PID %ld] Guess FAILED to find an owner. Proceeding to robust walk for definitive search.\n", current_particle.PID);
1547 }
1548
1549 ierr = LocateParticleOrFindMigrationTarget(user, &current_particle, &final_status); CHKERRQ(ierr);
1550 }
1551 }
1552
1553 // --- PROCESS THE FINAL, DEFINITIVE STATUS ---
1554 current_particle.location_status = final_status;
1555 ierr = UpdateSwarmFields(p_idx, &current_particle, pos_p, vel_p, weights_p, cell_p, status_p,NULL,NULL,NULL); CHKERRQ(ierr);
1556
1557 if (final_status == MIGRATING_OUT) {
1558 ierr = AddToMigrationList(&migrationList, &migrationListCapacity, &local_migration_count, p_idx, current_particle.destination_rank); CHKERRQ(ierr);
1559 } else if (final_status == LOST) {
1560 local_lost_count++;
1562 } else if (final_status == ACTIVE_AND_LOCATED) {
1564 }
1565
1566 } // End of main particle processing loop
1567
1568 // Restore all the fields acquired for this pass.
1569 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&pos_p); CHKERRQ(ierr);
1570 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_VELOCITY), NULL, NULL, (void**)&vel_p); CHKERRQ(ierr);
1571 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_WEIGHT), NULL, NULL, (void**)&weights_p); CHKERRQ(ierr);
1572 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_p); CHKERRQ(ierr);
1573 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid_p); CHKERRQ(ierr);
1574 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status_p); CHKERRQ(ierr);
1575 }
1576
1577 // --- STAGE 3: ACTION & MPI COMMUNICATION ---
1578 LOG_ALLOW(LOCAL, LOG_INFO, "[Rank %d] Pass %d: Identified %d particles to migrate out.\n", rank, passes, local_migration_count);
1579
1580 // --- STAGE 3: SYNCHRONIZE AND DECIDE ---
1581 // FIRST, determine if any rank wants to migrate. This call is safe because
1582 // all ranks have finished their local work and can participate.
1583 ierr = MPI_Allreduce(&local_migration_count, &global_migrations_this_pass, 1, MPIU_INT, MPI_SUM, PETSC_COMM_WORLD); CHKERRQ(ierr);
1584
1585 total_migrated_this_timestep += global_migrations_this_pass;
1586
1587 if(global_migrations_this_pass > 0 ){
1588
1589 LOG_ALLOW(GLOBAL, LOG_INFO, "Pass %d: Migrating %d particles globally.\n", passes, global_migrations_this_pass);
1590
1591 ierr = SetMigrationRanks(user, migrationList, local_migration_count); CHKERRQ(ierr);
1592 ierr = PerformMigration(user); CHKERRQ(ierr);
1593
1594 // --- STAGE 4: POST-MIGRATION RESET ---
1595 // Identify newly arrived particles and flag them with NEEDS_LOCATION so they are
1596 // processed in the next pass. This uses the snapshot taken in STAGE 2.
1597 ierr = FlagNewcomersForLocation(user->swarm, nlocal_before, pids_before_snapshot); CHKERRQ(ierr);
1598 }
1599 // --- STAGE 5: LOOP SYNCHRONIZATION AND CLEANUP ---
1600
1601 ierr = PetscFree(pids_before_snapshot);
1602 ierr = PetscFree(migrationList);
1603
1604 LOG_ALLOW(GLOBAL, LOG_INFO, "End of pass %d. Total particles migrated globally: %d.\n", passes, global_migrations_this_pass);
1605
1606 } while (global_migrations_this_pass > 0 && passes < MAX_MIGRATION_PASSES);
1607
1608 // --- FINAL CHECKS ---
1609 if (passes >= MAX_MIGRATION_PASSES) {
1610 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);
1611 }
1612
1613 user->simCtx->particlesMigratedLastStep = total_migrated_this_timestep;
1614 user->simCtx->migrationPassesLastStep = passes;
1615 user->simCtx->searchMetrics.maxParticlePassDepth = PetscMax(user->simCtx->searchMetrics.maxParticlePassDepth, (PetscInt64)passes);
1617
1618 LOG_ALLOW(GLOBAL, LOG_INFO, "Particle Location completed in %d passes.\n", passes);
1619
1621 PetscFunctionReturn(0);
1622}
1623
1624
1625#undef __FUNCT__
1626#define __FUNCT__ "ResetAllParticleStatuses"
1627/**
1628 * @brief Implementation of \ref ResetAllParticleStatuses().
1629 * @details Full API contract (arguments, ownership, side effects) is documented with
1630 * the header declaration in `include/ParticleMotion.h`.
1631 * @see ResetAllParticleStatuses()
1632 */
1634{
1635 PetscErrorCode ierr;
1636 PetscInt n_local;
1637 PetscInt *status_p;
1638
1639 PetscFunctionBeginUser;
1640
1642
1643 ierr = DMSwarmGetLocalSize(user->swarm, &n_local); CHKERRQ(ierr);
1644
1645 if (n_local > 0) {
1646 // Get write access to the status field
1647 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status_p); CHKERRQ(ierr);
1648
1649 for (PetscInt p = 0; p < n_local; ++p) {
1650 // Only reset particles that are considered settled. This is a small optimization
1651 // to avoid changing the status of a LOST particle, though resetting all would also be fine.
1652 if (status_p[p] == ACTIVE_AND_LOCATED) {
1653 status_p[p] = NEEDS_LOCATION;
1654 }
1655 }
1656
1657 // Restore the field
1658 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status_p); CHKERRQ(ierr);
1659 }
1660
1661
1663 PetscFunctionReturn(0);
1664}
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 CheckAndRemoveLostParticles(UserCtx *user, PetscInt *removedCountLocal, PetscInt *removedCountGlobal, PetscReal *removedScalarSumGlobal)
Internal helper implementation: CheckAndRemoveLostParticles().
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 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:1933
#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:894
const char * ParticleLocationStatusToString(ParticleLocationStatus level)
A function that outputs the name of the current level in the ParticleLocation enum.
Definition logging.c:1953
@ LOG_INFO
Informational messages about program execution.
Definition logging.h:31
@ LOG_WARNING
Non-critical issues that warrant attention.
Definition logging.h:30
@ LOG_DEBUG
Detailed debugging information.
Definition logging.h:32
@ LOG_VERBOSE
Extremely detailed logs, typically for development use only.
Definition logging.h:34
#define PROFILE_FUNCTION_BEGIN
Marks the beginning of a profiled code block (typically a function).
Definition logging.h:885
PetscErrorCode ResetSearchMetrics(SimCtx *simCtx)
Resets the aggregate per-timestep search instrumentation counters.
Definition logging.c:3214
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(PetscInt base_seed, 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:3895
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:3616
PetscMPIInt rank_zm
Definition variables.h:225
Cmpnts vel
Definition variables.h:212
PetscBool inletFaceDefined
Definition variables.h:1100
PetscMPIInt rank_yp
Definition variables.h:224
PetscInt64 searchLocatedCount
Definition variables.h:267
PetscInt64 searchLostCount
Definition variables.h:268
BCFace identifiedInletBCFace
Definition variables.h:1101
PetscInt cell[3]
Definition variables.h:210
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:1077
@ PARTICLE_INIT_SURFACE_RANDOM
Random placement on the inlet face.
Definition variables.h:710
@ PARTICLE_INIT_SURFACE_EDGES
Deterministic placement at inlet face edges.
Definition variables.h:713
ParticleLocationStatus
Defines the state of a particle with respect to its location and migration status during the iterativ...
Definition variables.h:163
@ LOST
Definition variables.h:167
@ NEEDS_LOCATION
Definition variables.h:164
@ ACTIVE_AND_LOCATED
Definition variables.h:165
@ MIGRATING_OUT
Definition variables.h:166
PetscMPIInt rank_ym
Definition variables.h:224
PetscReal CMy_c
Definition variables.h:944
PetscMPIInt rank_xp
Definition variables.h:223
PetscInt local_index
Definition variables.h:236
Cmpnts max_coords
Maximum x, y, z coordinates of the bounding box.
Definition variables.h:199
Cmpnts diffusivitygradient
Definition variables.h:217
PetscInt64 searchPopulation
Definition variables.h:266
PetscReal dt
Definition variables.h:874
RankNeighbors neighbors
Definition variables.h:1091
PetscInt currentSettlementPass
Definition variables.h:278
PetscInt np
Definition variables.h:990
Cmpnts min_coords
Minimum x, y, z coordinates of the bounding box.
Definition variables.h:198
PetscScalar x
Definition variables.h:122
Cmpnts loc
Definition variables.h:211
PetscMPIInt destination_rank
Definition variables.h:215
Vec lParticleCount
Definition variables.h:1171
PetscInt64 bboxGuessFallbackCount
Definition variables.h:276
ParticleLocationStatus location_status
Definition variables.h:214
PetscInt64 bboxGuessSuccessCount
Definition variables.h:275
PetscMPIInt rank_xm
Definition variables.h:223
PetscInt64 maxParticlePassDepth
Definition variables.h:277
PetscReal CMz_c
Definition variables.h:944
ParticleInitializationType ParticleInitialization
Definition variables.h:994
PetscScalar z
Definition variables.h:122
Vec ParticleCount
Definition variables.h:1171
PetscInt particlesMigratedLastStep
Definition variables.h:1002
PetscInt particleRandomSeed
Base seed for every particle RNG stream (-particle_random_seed).
Definition variables.h:991
PetscMPIInt rank_zp
Definition variables.h:225
SearchMetricsState searchMetrics
Definition variables.h:1005
PetscReal diffusivity
Definition variables.h:216
PetscRandom BrownianMotionRNG
Definition variables.h:1006
PetscInt migrationPassesLastStep
Definition variables.h:1001
PetscScalar y
Definition variables.h:122
BoundingBox bbox
Definition variables.h:1090
PetscInt64 PID
Definition variables.h:209
PetscReal CMx_c
Definition variables.h:944
Defines a 3D axis-aligned bounding box.
Definition variables.h:197
A 3D point or vector with PetscScalar components.
Definition variables.h:121
Information needed to migrate a single particle between MPI ranks.
Definition variables.h:235
Defines a particle's core properties for Lagrangian tracking.
Definition variables.h:208
Stores the MPI ranks of neighboring subdomains.
Definition variables.h:222
User-defined context containing data specific to a single computational grid level.
Definition variables.h:1074
Head of a generic C-style linked list.
Definition variables.h:469
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.