PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
Functions
ParticleMotion.h File Reference

Header file for Particle Motion and migration related functions. More...

#include <petsc.h>
#include <petscdmswarm.h>
#include <stdbool.h>
#include <petscsys.h>
#include <math.h>
#include "variables.h"
#include "logging.h"
#include "walkingsearch.h"
Include dependency graph for ParticleMotion.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Functions

PetscErrorCode GenerateGaussianNoise (PetscRandom rnd, PetscReal *n1, PetscReal *n2)
 Generates two independent standard normal random variables N(0,1) using the Box-Muller transform.
 
PetscErrorCode CalculateBrownianDisplacement (UserCtx *user, PetscReal diff_eff, Cmpnts *displacement)
 Calculates the stochastic displacement vector (Brownian motion) for a single particle.
 
PetscErrorCode UpdateParticlePosition (UserCtx *user, Particle *particle)
 Updates a particle's position based on its velocity and the timestep dt (stored in user->dt).
 
PetscErrorCode UpdateAllParticlePositions (UserCtx *user)
 Loops over all local particles in the DMSwarm, updating their positions based on velocity and the global timestep user->dt.
 
PetscErrorCode CheckAndRemoveOutOfBoundsParticles (UserCtx *user, PetscInt *removedCountLocal, PetscInt *removedCountGlobal, const BoundingBox *bboxlist)
 Checks for particles outside the physical domain boundaries and removes them using DMSwarmRemovePointAtIndex.
 
PetscErrorCode CheckAndRemoveLostParticles (UserCtx *user, PetscInt *removedCountLocal, PetscInt *removedCountGlobal)
 Removes particles that have been definitively flagged as LOST by the location algorithm.
 
PetscErrorCode DefineBasicMigrationPattern (UserCtx *user)
 Defines the basic migration pattern for particles within the swarm.
 
PetscErrorCode PerformBasicMigration (UserCtx *user)
 Performs the basic migration of particles based on the defined migration pattern.
 
PetscErrorCode IdentifyMigratingParticles (UserCtx *user, const BoundingBox *bboxlist, MigrationInfo **migrationList, PetscInt *migrationCount, PetscInt *listCapacity)
 Identifies particles leaving the local bounding box and finds their target neighbor rank.
 
PetscErrorCode SetMigrationRanks (UserCtx *user, const MigrationInfo *migrationList, PetscInt migrationCount)
 Writes migration destinations into the DMSwarm rank field for marked particles.
 
PetscErrorCode PerformMigration (UserCtx *user)
 Performs particle migration based on the pre-populated DMSwarmPICField_rank field.
 
PetscErrorCode CalculateParticleCountPerCell (UserCtx *user)
 Counts particles in each cell of the DMDA 'da' and stores the result in user->ParticleCount.
 
PetscErrorCode ResizeSwarmGlobally (DM swarm, PetscInt N_target)
 Resizes a swarm collectively to a target global particle count.
 
PetscErrorCode PreCheckAndResizeSwarm (UserCtx *user, PetscInt ti, const char *ext)
 Checks particle count in the reference file and resizes the swarm if needed.
 
PetscErrorCode PerformSingleParticleMigrationCycle (UserCtx *user, const BoundingBox *bboxlist, MigrationInfo **migrationList_p, PetscInt *migrationCount_p, PetscInt *migrationListCapacity_p, PetscReal currentTime, PetscInt step, const char *migrationCycleName, PetscInt *globalMigrationCount_out)
 Performs one full cycle of particle migration: identify, set ranks, and migrate.
 
PetscErrorCode ReinitializeParticlesOnInletSurface (UserCtx *user, PetscReal currentTime, PetscInt step)
 Re-initializes the positions of particles currently on this rank if this rank owns part of the designated inlet surface.
 
PetscErrorCode GetLocalPIDSnapshot (const PetscInt64 pid_field[], PetscInt n_local, PetscInt64 **pids_snapshot_out)
 Creates a sorted snapshot of all Particle IDs (PIDs) from a raw data array.
 
PetscErrorCode AddToMigrationList (MigrationInfo **migration_list_p, PetscInt *capacity_p, PetscInt *count_p, PetscInt particle_local_idx, PetscMPIInt destination_rank)
 Safely adds a new migration task to a dynamically sized list.
 
PetscErrorCode FlagNewcomersForLocation (DM swarm, PetscInt n_local_before, const PetscInt64 pids_before[])
 Identifies newly arrived particles after migration and flags them for a location search.
 
PetscErrorCode MigrateRestartParticlesUsingCellID (UserCtx *user)
 Fast-path migration for restart particles using preloaded Cell IDs.
 
PetscErrorCode LocateAllParticlesInGrid (UserCtx *user, BoundingBox *bboxlist)
 Orchestrates the complete particle location and migration process for one timestep.
 
PetscErrorCode ResetAllParticleStatuses (UserCtx *user)
 Marks all local particles as NEEDS_LOCATION for the next settlement pass.
 

Detailed Description

Header file for Particle Motion and migration related functions.

This file contains declarations of functions responsible for moving and migrating particle swarms within a simulation using PETSc's DMSwarm.

Definition in file ParticleMotion.h.

Function Documentation

◆ GenerateGaussianNoise()

PetscErrorCode GenerateGaussianNoise ( PetscRandom  rnd,
PetscReal *  n1,
PetscReal *  n2 
)

Generates two independent standard normal random variables N(0,1) using the Box-Muller transform.

Parameters
[in]rndThe PETSc Random context (Uniform [0,1)).
[out]n1First Gaussian number.
[out]n2Second Gaussian number.
Returns
PetscErrorCode

Generates two independent standard normal random variables N(0,1) using the Box-Muller transform.

Local to this translation unit.

Definition at line 17 of file ParticleMotion.c.

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}
Here is the caller graph for this function:

◆ CalculateBrownianDisplacement()

PetscErrorCode CalculateBrownianDisplacement ( UserCtx user,
PetscReal  diff_eff,
Cmpnts displacement 
)

Calculates the stochastic displacement vector (Brownian motion) for a single particle.

Equation: dX_stoch = sqrt(2 * Gamma_eff * dt) * N(0,1)

Parameters
[in]userPointer to UserCtx (access to dt and BrownianMotionRNG).
[in]diff_effThe effective diffusivity (Gamma + Gamma_t) at the particle's location.
[out]displacementPointer to a Cmpnts struct to store the resulting (dx, dy, dz).
Returns
PetscErrorCode

Calculates the stochastic displacement vector (Brownian motion) for a single particle.

Local to this translation unit.

Definition at line 57 of file ParticleMotion.c.

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}
PetscErrorCode GenerateGaussianNoise(PetscRandom rnd, PetscReal *n1, PetscReal *n2)
Internal helper implementation: GenerateGaussianNoise().
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:909
PetscReal dt
Definition variables.h:710
PetscScalar x
Definition variables.h:103
PetscScalar z
Definition variables.h:103
PetscRandom BrownianMotionRNG
Definition variables.h:841
PetscScalar y
Definition variables.h:103
Here is the call graph for this function:
Here is the caller graph for this function:

◆ UpdateParticlePosition()

PetscErrorCode UpdateParticlePosition ( UserCtx user,
Particle particle 
)

Updates a particle's position based on its velocity and the timestep dt (stored in user->dt).

Parameters
[in]userPointer to your UserCtx (must contain user->dt).
[in,out]particlePointer to the particle struct (contains, pos,vel,diffusivity etc).
Returns
PetscErrorCode Returns 0 on success, or an error code on failure.

Updates a particle's position based on its velocity and the timestep dt (stored in user->dt).

Local to this translation unit.

Definition at line 105 of file ParticleMotion.c.

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}
PetscErrorCode CalculateBrownianDisplacement(UserCtx *user, PetscReal diff_eff, Cmpnts *displacement)
Internal helper implementation: CalculateBrownianDisplacement().
#define PROFILE_FUNCTION_END
Marks the end of a profiled code block.
Definition logging.h:859
#define PROFILE_FUNCTION_BEGIN
Marks the beginning of a profiled code block (typically a function).
Definition logging.h:850
Cmpnts vel
Definition variables.h:186
Cmpnts diffusivitygradient
Definition variables.h:191
Cmpnts loc
Definition variables.h:185
PetscReal diffusivity
Definition variables.h:190
A 3D point or vector with PetscScalar components.
Definition variables.h:102
Here is the call graph for this function:
Here is the caller graph for this function:

◆ UpdateAllParticlePositions()

PetscErrorCode UpdateAllParticlePositions ( UserCtx user)

Loops over all local particles in the DMSwarm, updating their positions based on velocity and the global timestep user->dt.

Parameters
[in,out]userPointer to UserCtx (must contain dt).
Returns
PetscErrorCode Returns 0 on success, or an error code on failure.

Loops over all local particles in the DMSwarm, updating their positions based on velocity and the global timestep user->dt.

Local to this translation unit.

Definition at line 134 of file ParticleMotion.c.

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}
PetscErrorCode UpdateParticlePosition(UserCtx *user, Particle *particle)
Internal helper implementation: UpdateParticlePosition().
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.
#define LOCAL
Logging scope definitions for controlling message output.
Definition logging.h:45
#define GLOBAL
Scope for global logging across all processes.
Definition logging.h:46
#define LOG_ALLOW(scope, level, fmt,...)
Logging macro that checks both the log level and whether the calling function is in the allowed-funct...
Definition logging.h:200
@ LOG_DEBUG
Detailed debugging information.
Definition logging.h:32
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_DIFFUSIVITY_GRADIENT
@ PARTICLE_FIELD_ID_DIFFUSIVITY
@ PARTICLE_FIELD_ID_VELOCITY
Defines a particle's core properties for Lagrangian tracking.
Definition variables.h:182
Here is the call graph for this function:
Here is the caller graph for this function:

◆ CheckAndRemoveOutOfBoundsParticles()

PetscErrorCode CheckAndRemoveOutOfBoundsParticles ( UserCtx user,
PetscInt *  removedCountLocal,
PetscInt *  removedCountGlobal,
const BoundingBox bboxlist 
)

Checks for particles outside the physical domain boundaries and removes them using DMSwarmRemovePointAtIndex.

This function iterates through all particles local to the current MPI rank. It checks if a particle's position (x, y, or z) is outside the specified physical domain boundaries [xMin, xMax], [yMin, yMax], [zMin, zMax].

If a particle is found out of bounds, it is removed using DMSwarmRemovePointAtIndex. NOTE: Removing points changes the indices of subsequent points in the iteration. Therefore, it's crucial to iterate BACKWARDS or carefully manage indices after a removal. Iterating backwards is generally safer.

Parameters
userPointer to the UserCtx structure.
[out]removedCountLocalPointer to store the number of particles removed on this rank.
[out]removedCountGlobalPointer to store the total number of particles removed across all ranks.
[in]bboxlistAn array of BoundingBox structures for ALL MPI ranks, indexed 0 to (size-1). This array must be up-to-date and available on all ranks.
Returns
PetscErrorCode 0 on success, non-zero on failure.

Checks for particles outside the physical domain boundaries and removes them using DMSwarmRemovePointAtIndex.

Local to this translation unit.

Definition at line 228 of file ParticleMotion.c.

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}
static PetscBool IsParticleInBox(const BoundingBox *bbox, const Cmpnts *pos)
Test whether a particle position lies within an axis-aligned bounding box.
#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
@ LOG_INFO
Informational messages about program execution.
Definition logging.h:31
Here is the call graph for this function:
Here is the caller graph for this function:

◆ CheckAndRemoveLostParticles()

PetscErrorCode CheckAndRemoveLostParticles ( UserCtx user,
PetscInt *  removedCountLocal,
PetscInt *  removedCountGlobal 
)

Removes particles that have been definitively flagged as LOST by the location algorithm.

This function is the designated cleanup utility. It should be called after the LocateAllParticlesInGrid orchestrator has run and every particle's status has been definitively determined.

It iterates through all locally owned particles and checks their DMSwarm_location_status field. If a particle's status is LOST, it is permanently removed from the simulation using DMSwarmRemovePointAtIndex.

This approach centralizes the removal logic, making the DMSwarm_location_status the single source of truth for a particle's validity, which is more robust than relying on secondary geometric checks (like bounding boxes).

Parameters
[in,out]userPointer to the UserCtx structure containing the swarm.
[out]removedCountLocalPointer to store the number of particles removed on this rank.
[out]removedCountGlobalPointer to store the total number of particles removed across all ranks.
Returns
PetscErrorCode 0 on success, or a non-zero PETSc error code on failure.

Removes particles that have been definitively flagged as LOST by the location algorithm.

Local to this translation unit.

Definition at line 336 of file ParticleMotion.c.

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}
@ LOST
Definition variables.h:141
Here is the call graph for this function:
Here is the caller graph for this function:

◆ DefineBasicMigrationPattern()

PetscErrorCode DefineBasicMigrationPattern ( UserCtx user)

Defines the basic migration pattern for particles within the swarm.

This function establishes the migration pattern that dictates how particles move between different MPI ranks in the simulation. It initializes a migration list where each particle is assigned a target rank based on predefined conditions. The migration pattern can be customized to implement various migration behaviors.

Parameters
[in,out]userPointer to the UserCtx structure containing simulation context.
Returns
PetscErrorCode Returns 0 on success, non-zero on failure.

◆ PerformBasicMigration()

PetscErrorCode PerformBasicMigration ( UserCtx user)

Performs the basic migration of particles based on the defined migration pattern.

This function updates the positions of particles within the swarm by migrating them to target MPI ranks as specified in the migration list. It handles the migration process by setting the 'DMSwarm_rank' field for each particle and invokes the DMSwarm migration mechanism to relocate particles across MPI processes. After migration, it cleans up allocated resources and ensures synchronization across all MPI ranks.

Parameters
[in,out]userPointer to the UserCtx structure containing simulation context.
Returns
PetscErrorCode Returns 0 on success, non-zero on failure.

◆ IdentifyMigratingParticles()

PetscErrorCode IdentifyMigratingParticles ( UserCtx user,
const BoundingBox bboxlist,
MigrationInfo **  migrationList,
PetscInt *  migrationCount,
PetscInt *  listCapacity 
)

Identifies particles leaving the local bounding box and finds their target neighbor rank.

Iterates local particles, checks against local bounding box. If outside, checks the pre-computed immediate neighbors (user->neighbors) using the global bboxlist to see if the particle landed in one of them. Populates the migrationList. Does NOT handle particles leaving the global domain (assumes CheckAndRemove was called).

Parameters
userPointer to the UserCtx (contains local bbox and neighbors).
bboxlistArray of BoundingBox structs for all ranks (for checking neighbor boxes).
migrationListPointer to an array of MigrationInfo structs (output, allocated/reallocated by this func).
migrationCountPointer to the number of particles marked for migration (output).
listCapacityPointer to the current allocated capacity of migrationList (in/out).
Returns
PetscErrorCode 0 on success, non-zero on failure.

◆ SetMigrationRanks()

PetscErrorCode SetMigrationRanks ( UserCtx user,
const MigrationInfo migrationList,
PetscInt  migrationCount 
)

Writes migration destinations into the DMSwarm rank field for marked particles.

This helper consumes the migration list produced by IdentifyMigratingParticles and updates each selected particle's destination rank so that a subsequent PerformMigration call can transfer ownership correctly.

Parameters
[in,out]userContext containing the swarm and migration rank field.
[in]migrationListArray of migration directives (local index + destination rank).
[in]migrationCountNumber of valid entries in migrationList.
Returns
PetscErrorCode 0 on success.

Writes migration destinations into the DMSwarm rank field for marked particles.

Local to this translation unit.

Definition at line 440 of file ParticleMotion.c.

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}
@ PARTICLE_FIELD_ID_RANK
PetscInt local_index
Definition variables.h:210
Here is the call graph for this function:
Here is the caller graph for this function:

◆ PerformMigration()

PetscErrorCode PerformMigration ( UserCtx user)

Performs particle migration based on the pre-populated DMSwarmPICField_rank field.

Assumes SetMigrationRanks has already been called to mark particles with their target ranks. Calls DMSwarmMigrate to execute the communication and removal of un-migrated particles.

Parameters
userPointer to the UserCtx structure containing the swarm.
Returns
PetscErrorCode 0 on success, non-zero on failure.

Performs particle migration based on the pre-populated DMSwarmPICField_rank field.

Full API contract (arguments, ownership, side effects) is documented with the header declaration in include/ParticleMotion.h.

See also
PerformMigration()

Definition at line 473 of file ParticleMotion.c.

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}
Here is the caller graph for this function:

◆ CalculateParticleCountPerCell()

PetscErrorCode CalculateParticleCountPerCell ( UserCtx user)

Counts particles in each cell of the DMDA 'da' and stores the result in user->ParticleCount.

Assumes user->ParticleCount is a pre-allocated global vector associated with user->da and initialized to zero before calling this function (though it resets it internally). Assumes particle 'DMSwarm_CellID' field contains local cell indices.

Parameters
[in,out]userPointer to the UserCtx structure containing da, swarm, and ParticleCount.
Returns
PetscErrorCode Returns 0 on success, non-zero on failure.

Counts particles in each cell of the DMDA 'da' and stores the result in user->ParticleCount.

Full API contract (arguments, ownership, side effects) is documented with the header declaration in include/logging.h.

See also
CalculateParticleCountPerCell()

Definition at line 505 of file ParticleMotion.c.

505 {
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}
#define ERROR_MSG_BUFFER_SIZE
#define LOG_LOOP_ALLOW(scope, level, iterVar, interval, fmt,...)
Logs a message inside a loop, but only every interval iterations.
Definition logging.h:298
@ LOG_WARNING
Non-critical issues that warrant attention.
Definition logging.h:30
@ LOG_VERBOSE
Extremely detailed logs, typically for development use only.
Definition logging.h:34
Vec lParticleCount
Definition variables.h:996
Vec ParticleCount
Definition variables.h:996
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ResizeSwarmGlobally()

PetscErrorCode ResizeSwarmGlobally ( DM  swarm,
PetscInt  N_target 
)

Resizes a swarm collectively to a target global particle count.

The target is divided by quotient and remainder so every rank receives either floor(N_target / nranks) or one additional entry. Resizing establishes the storage layout; callers initialize or overwrite particle fields afterwards.

Parameters
[in,out]swarmSwarm object to resize.
[in]N_targetTarget global particle count.
Returns
PetscErrorCode 0 on success.

Resizes a swarm collectively to a target global particle count.

Full API contract (arguments, ownership, side effects) is documented with the header declaration in include/ParticleMotion.h.

See also
ResizeSwarmGlobally()

Definition at line 674 of file ParticleMotion.c.

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}
Here is the caller graph for this function:

◆ PreCheckAndResizeSwarm()

PetscErrorCode PreCheckAndResizeSwarm ( UserCtx user,
PetscInt  ti,
const char *  ext 
)

Checks particle count in the reference file and resizes the swarm if needed.

Reads the specified field file (e.g., position) into a temporary Vec to determine the number of particles (N_file) represented in that file for the given timestep. Compares N_file with the current swarm size (N_current). If they differ, resizes the swarm globally (adds or removes particles) to match N_file. The resized population is balanced across the communicator before field input.

Parameters
[in,out]userPointer to the UserCtx structure containing the DMSwarm.
[in]tiTime index for constructing the file name.
[in]extFile extension (e.g., "dat").
Returns
PetscErrorCode 0 on success, non-zero on critical failure.

Checks particle count in the reference file and resizes the swarm if needed.

Local to this translation unit.

Definition at line 720 of file ParticleMotion.c.

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}
PetscErrorCode ResizeSwarmGlobally(DM swarm, PetscInt N_target)
Implementation of ResizeSwarmGlobally().
PetscErrorCode ReadCheckpointParticleCount(UserCtx *user, PetscInt ti, PetscInt *particle_count)
Read the particle count from a validated committed checkpoint.
Definition io.c:1914
PetscInt np
Definition variables.h:827
Here is the call graph for this function:
Here is the caller graph for this function:

◆ PerformSingleParticleMigrationCycle()

PetscErrorCode PerformSingleParticleMigrationCycle ( UserCtx user,
const BoundingBox bboxlist,
MigrationInfo **  migrationList_p,
PetscInt *  migrationCount_p,
PetscInt *  migrationListCapacity_p,
PetscReal  currentTime,
PetscInt  step,
const char *  migrationCycleName,
PetscInt *  globalMigrationCount_out 
)

Performs one full cycle of particle migration: identify, set ranks, and migrate.

This function encapsulates the three main steps of migrating particles between MPI ranks:

  1. Identify particles on the local rank that need to move based on their current positions and the domain decomposition (bboxlist).
  2. Determine the destination rank for each migrating particle.
  3. Perform the actual migration using PETSc's DMSwarmMigrate. It also calculates and logs the global number of particles migrated.
Parameters
userPointer to the UserCtx structure.
bboxlistArray of BoundingBox structures defining the spatial domain of each MPI rank.
migrationList_pPointer to a pointer for the MigrationInfo array. This array will be allocated/reallocated by IdentifyMigratingParticles if necessary. The caller is responsible for freeing this list eventually.
migrationCount_pPointer to store the number of particles identified for migration on the local rank. This is reset to 0 after migration for the current cycle.
migrationListCapacity_pPointer to store the current capacity of the migrationList_p array.
currentTimeCurrent simulation time (used for logging).
stepCurrent simulation step number (used for logging).
migrationCycleNameA descriptive name for this migration cycle (e.g., "Preliminary Sort", "Main Loop") for logging purposes.
[out]globalMigrationCount_outPointer to store the total number of particles migrated across all MPI ranks during this cycle.
Returns
PetscErrorCode 0 on success, non-zero on failure.

◆ ReinitializeParticlesOnInletSurface()

PetscErrorCode ReinitializeParticlesOnInletSurface ( UserCtx user,
PetscReal  currentTime,
PetscInt  step 
)

Re-initializes the positions of particles currently on this rank if this rank owns part of the designated inlet surface.

This function is intended for user->ParticleInitialization == 0 (Surface Initialization mode) and is typically called after an initial migration step (e.g., in PerformInitialSetup). It ensures that all particles that should originate from the inlet surface and are now on the correct MPI rank are properly distributed across that rank's portion of the inlet.

Parameters
userPointer to the UserCtx structure, containing simulation settings and grid information.
currentTimeCurrent simulation time (used for logging).
stepCurrent simulation step number (used for logging).
Returns
PetscErrorCode 0 on success, non-zero on failure.

Re-initializes the positions of particles currently on this rank if this rank owns part of the designated inlet surface.

Local to this translation unit.

Definition at line 760 of file ParticleMotion.c.

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}
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
const char * BCFaceToString(BCFace face)
Returns the canonical log token for a boundary-face enum value.
Definition logging.c:671
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
PetscBool inletFaceDefined
Definition variables.h:932
BCFace identifiedInletBCFace
Definition variables.h:933
@ 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
PetscReal CMy_c
Definition variables.h:783
PetscReal CMz_c
Definition variables.h:783
ParticleInitializationType ParticleInitialization
Definition variables.h:831
PetscReal CMx_c
Definition variables.h:783
Here is the call graph for this function:
Here is the caller graph for this function:

◆ GetLocalPIDSnapshot()

PetscErrorCode GetLocalPIDSnapshot ( const PetscInt64  pid_field[],
PetscInt  n_local,
PetscInt64 **  pids_snapshot_out 
)

Creates a sorted snapshot of all Particle IDs (PIDs) from a raw data array.

This function is a crucial helper for the migration process. It captures the state of which particles are on the current MPI rank before migration occurs by taking a pointer to the swarm's raw PID data array. The resulting sorted array can then be used with an efficient binary search to quickly identify newcomer particles after migration.

This function does NOT call DMSwarmGetField/RestoreField. It is the caller's responsibility to acquire the pid_field pointer before calling and restore it afterward.

Parameters
[in]pid_fieldA read-only pointer to the raw array of PIDs for the local swarm.
[in]n_localThe number of particles currently on the local rank.
[out]pids_snapshot_outA pointer to a PetscInt64* array. This function will allocate memory for this array, and the caller is responsible for freeing it with PetscFree() when it is no longer needed.
Returns
PetscErrorCode 0 on success, or a non-zero PETSc error code on failure.

Creates a sorted snapshot of all Particle IDs (PIDs) from a raw data array.

Local to this translation unit.

Definition at line 976 of file ParticleMotion.c.

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}
Here is the caller graph for this function:

◆ AddToMigrationList()

PetscErrorCode AddToMigrationList ( MigrationInfo **  migration_list_p,
PetscInt *  capacity_p,
PetscInt *  count_p,
PetscInt  particle_local_idx,
PetscMPIInt  destination_rank 
)

Safely adds a new migration task to a dynamically sized list.

This utility function manages a dynamic array of MigrationInfo structs. It appends a new entry to the list and automatically doubles the array's capacity using PetscRealloc if the current capacity is exceeded. This prevents buffer overflows and avoids the need to know the number of migrating particles in advance.

Parameters
[in,out]migration_list_pA pointer to the MigrationInfo array pointer. The function will update this pointer if the array is reallocated.
[in,out]capacity_pA pointer to an integer holding the current allocated capacity of the list (in number of elements). This will be updated upon reallocation.
[in,out]count_pA pointer to an integer holding the current number of items in the list. This will be incremented by one.
[in]particle_local_idxThe local index (from 0 to nlocal-1) of the particle that needs to be migrated.
[in]destination_rankThe target MPI rank for the particle.
Returns
PetscErrorCode 0 on success, or a non-zero PETSc error code on failure (e.g., from memory allocation).

Safely adds a new migration task to a dynamically sized list.

Local to this translation unit.

Definition at line 1031 of file ParticleMotion.c.

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}
Information needed to migrate a single particle between MPI ranks.
Definition variables.h:209
Head of a generic C-style linked list.
Definition variables.h:445
Here is the caller graph for this function:

◆ FlagNewcomersForLocation()

PetscErrorCode FlagNewcomersForLocation ( DM  swarm,
PetscInt  n_local_before,
const PetscInt64  pids_before[] 
)

Identifies newly arrived particles after migration and flags them for a location search.

This function is a critical component of the iterative migration process managed by the main particle settlement orchestrator (e.g., SettleParticles). After a DMSwarmMigrate call, each rank's local particle list is a new mix of resident particles and newly received ones. This function's job is to efficiently identify these "newcomers" and set their DMSwarm_location_status field to NEEDS_LOCATION.

This ensures that in the subsequent pass of the migration do-while loop, only the newly arrived particles are processed by the expensive location algorithm, preventing redundant work on particles that are already settled on the current rank.

The identification is done by comparing the PIDs of particles currently on the rank against a "snapshot" of PIDs taken before the migration occurred.

Parameters
[in]swarmThe DMSwarm object, which has just completed a migration.
[in]n_local_beforeThe number of particles that were on this rank before the migration was performed.
[in]pids_beforeA pre-sorted array of the PIDs that were on this rank before the migration. This is used for fast lookups.
Returns
PetscErrorCode 0 on success, or a non-zero PETSc error code on failure.
Note
This function assumes the pids_before array is sorted in ascending order to enable the use of an efficient binary search.

Identifies newly arrived particles after migration and flags them for a location search.

Local to this translation unit.

Definition at line 1089 of file ParticleMotion.c.

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}
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
@ NEEDS_LOCATION
Definition variables.h:138
Here is the call graph for this function:
Here is the caller graph for this function:

◆ MigrateRestartParticlesUsingCellID()

PetscErrorCode MigrateRestartParticlesUsingCellID ( UserCtx user)

Fast-path migration for restart particles using preloaded Cell IDs.

This function provides an optimized migration path specifically for particles loaded from restart files. Unlike the standard LocateAllParticlesInGrid() which performs expensive walking searches, this function leverages the fact that restart particles already have valid global Cell IDs loaded from disk.

How It Works:

  1. Iterates through all local particles.
  2. For each particle with a valid Cell ID (ci, cj, ck):
    • Calls FindOwnerOfCell(ci, cj, ck) to determine the correct rank.
    • If owner differs from current rank, adds to migration list.
    • If owner matches current rank, the existing ACTIVE_AND_LOCATED status is preserved.
  3. Uses existing SetMigrationRanks() and PerformMigration() infrastructure.
  4. Achieves single-pass direct migration (no multi-hop, no walking searches).
Parameters
[in,out]userPointer to UserCtx containing the swarm and RankCellInfoMap. The function updates particle status fields and performs migration.
Returns
PetscErrorCode 0 on success, non-zero on failure.
Note
Testing status: Direct coverage currently focuses on restart fast-path ownership transfer. Non-restart multi-pass migration behavior remains part of the next simulation-core test backlog.

Fast-path migration for restart particles using preloaded Cell IDs.

Local to this translation unit.

Definition at line 1183 of file ParticleMotion.c.

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}
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 PerformMigration(UserCtx *user)
Implementation of PerformMigration().
PetscErrorCode FindOwnerOfCell(UserCtx *user, PetscInt i, PetscInt j, PetscInt k, PetscMPIInt *owner_rank)
Finds the MPI rank that owns a given global cell index.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ LocateAllParticlesInGrid()

PetscErrorCode LocateAllParticlesInGrid ( UserCtx user,
BoundingBox bboxlist 
)

Orchestrates the complete particle location and migration process for one timestep.

This function is the master orchestrator for ensuring every particle is on its correct MPI rank and has a valid host cell index. It is designed to be called once per timestep after particle positions have been updated.

The function uses a robust, iterative "Guess and Verify" strategy within a do-while loop to handle complex particle motion across processor boundaries, especially on curvilinear grids.

  1. State Snapshot: At the start of each pass, it captures a list of all Particle IDs (PIDs) on the current rank.
  2. **"Guess" (Heuristic):** For particles that are "lost" (no valid host cell), it first attempts a fast, bounding-box-based guess to find a potential new owner rank.
  3. **"Verify" (Robust Walk):** For all other particles, or if the guess fails, it uses a robust cell-walking algorithm (LocateParticleOrFindMigrationTarget) that determines the particle's status: located locally, needs migration, or is lost.
  4. Migration: After identifying all migrating particles on a pass, it performs the MPI communication using the SetMigrationRanks and PerformMigration helpers.
  5. Newcomer Flagging: After migration, it uses the PID snapshot from step 1 to efficiently identify newly arrived particles and flag them for location on the next pass.
  6. Iteration: The process repeats in a do-while loop until a pass occurs where no particles migrate, ensuring the entire swarm is in a stable, consistent state.
Parameters
[in,out]userPointer to the UserCtx, containing the swarm and all necessary domain topology information (bboxlist, RankCellInfoMap, etc.).
[in]bboxlistAn array of BoundingBox structures for ALL MPI ranks, indexed 0 to (size-1). This array must be up-to-date and available on all ranks.
Returns
PetscErrorCode 0 on success, or a non-zero PETSc error code on failure.
Note
Testing status: Direct unit coverage currently pins the prior-cell fast path and the local guess-then-verify path. Multi-pass migration, newcomer flagging, and several lost/migration edge cases are still targeted for future bespoke tests.

Orchestrates the complete particle location and migration process for one timestep.

Full API contract (arguments, ownership, side effects) is documented with the header declaration in include/ParticleMotion.h.

See also
LocateAllParticlesInGrid()

Definition at line 1385 of file ParticleMotion.c.

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}
PetscErrorCode GetLocalPIDSnapshot(const PetscInt64 pid_field[], PetscInt n_local, PetscInt64 **pids_snapshot_out)
Internal helper implementation: GetLocalPIDSnapshot().
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.
PetscErrorCode FlagNewcomersForLocation(DM swarm, PetscInt n_local_before, const PetscInt64 pids_before[])
Internal helper implementation: FlagNewcomersForLocation().
const char * ParticleLocationStatusToString(ParticleLocationStatus level)
A function that outputs the name of the current level in the ParticleLocation enum.
Definition logging.c:1858
PetscErrorCode ResetSearchMetrics(SimCtx *simCtx)
Resets the aggregate per-timestep search instrumentation counters.
Definition logging.c:3098
PetscInt64 searchLocatedCount
Definition variables.h:241
PetscInt64 searchLostCount
Definition variables.h:242
PetscInt cell[3]
Definition variables.h:184
ParticleLocationStatus
Defines the state of a particle with respect to its location and migration status during the iterativ...
Definition variables.h:137
@ ACTIVE_AND_LOCATED
Definition variables.h:139
@ MIGRATING_OUT
Definition variables.h:140
PetscInt64 searchPopulation
Definition variables.h:240
PetscInt currentSettlementPass
Definition variables.h:252
PetscMPIInt destination_rank
Definition variables.h:189
PetscInt64 bboxGuessFallbackCount
Definition variables.h:250
ParticleLocationStatus location_status
Definition variables.h:188
PetscInt64 bboxGuessSuccessCount
Definition variables.h:249
PetscInt64 maxParticlePassDepth
Definition variables.h:251
PetscInt particlesMigratedLastStep
Definition variables.h:837
SearchMetricsState searchMetrics
Definition variables.h:840
PetscInt migrationPassesLastStep
Definition variables.h:836
PetscInt64 PID
Definition variables.h:183
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.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ResetAllParticleStatuses()

PetscErrorCode ResetAllParticleStatuses ( UserCtx user)

Marks all local particles as NEEDS_LOCATION for the next settlement pass.

This function is designed to be called at the end of a full timestep, after all particle-based calculations are complete. It prepares the swarm for the next timestep by ensuring that after the next position update, every particle will be re-evaluated by the LocateAllParticlesInGrid orchestrator.

It iterates through all locally owned particles and sets their DMSwarm_location_status field to NEEDS_LOCATION.

Parameters
[in,out]userPointer to the UserCtx containing the swarm.
Returns
PetscErrorCode 0 on success, or a non-zero PETSc error code on failure.

Marks all local particles as NEEDS_LOCATION for the next settlement pass.

Full API contract (arguments, ownership, side effects) is documented with the header declaration in include/ParticleMotion.h.

See also
ResetAllParticleStatuses()

Definition at line 1619 of file ParticleMotion.c.

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}
Here is the call graph for this function:
Here is the caller graph for this function: