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, PetscReal *removedScalarSumGlobal)
 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:1077
PetscReal dt
Definition variables.h:874
PetscScalar x
Definition variables.h:122
PetscScalar z
Definition variables.h:122
PetscRandom BrownianMotionRNG
Definition variables.h:1006
PetscScalar y
Definition variables.h:122
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:894
#define PROFILE_FUNCTION_BEGIN
Marks the beginning of a profiled code block (typically a function).
Definition logging.h:885
Cmpnts vel
Definition variables.h:212
Cmpnts diffusivitygradient
Definition variables.h:217
Cmpnts loc
Definition variables.h:211
PetscReal diffusivity
Definition variables.h:216
A 3D point or vector with PetscScalar components.
Definition variables.h:121
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:208
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,
PetscReal *  removedScalarSumGlobal 
)

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.
[out]removedScalarSumGlobalOptional, may be NULL: the sum of Psi over every particle removed on any rank. With a 0/1 label and micromixing off, it counts the removed particles that carried the label.
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.

340{
341 PetscErrorCode ierr;
342 DM swarm = user->swarm;
343 PetscInt nLocalInitial;
344 PetscInt *status_p = NULL;
345 PetscInt64 *pid_p = NULL; // For better logging
346 PetscReal *pos_p = NULL; // For better logging
347 PetscReal *psi_p = NULL;
348 PetscReal local_removed_psi = 0.0;
349 PetscInt local_removed_count = 0;
350 PetscMPIInt global_removed_count_mpi = 0;
351 PetscMPIInt rank;
352
353 PetscFunctionBeginUser;
355 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
356 LOG_ALLOW(LOCAL, LOG_INFO, "Rank %d: Checking for and removing LOST particles...\n", rank);
357
358 // Initialize output parameters to ensure clean state
359 *removedCountLocal = 0;
360 if (removedCountGlobal) *removedCountGlobal = 0;
361 if (removedScalarSumGlobal) *removedScalarSumGlobal = 0.0;
362
363 ierr = DMSwarmGetLocalSize(swarm, &nLocalInitial); CHKERRQ(ierr);
364
365 // Only proceed if there are particles to check on this rank.
366 // All ranks will still participate in the final collective MPI_Allreduce.
367 if (nLocalInitial > 0) {
368 // Get access to all swarm fields once before the loop begins.
369 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void **)&status_p); CHKERRQ(ierr);
370 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void **)&pid_p); CHKERRQ(ierr);
371 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void **)&pos_p); CHKERRQ(ierr);
372 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PSI), NULL, NULL, (void **)&psi_p); CHKERRQ(ierr);
373
374 // --- Iterate BACKWARDS to handle index changes safely during removal ---
375 for (PetscInt p = nLocalInitial - 1; p >= 0; p--) {
376 if (status_p[p] == LOST) {
377 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d: Removing LOST particle [PID %lld] at local index %d. Position: (%.4f, %.4f, %.4f).\n",
378 rank, (long long)pid_p[p], p, pos_p[3*p], pos_p[3*p+1], pos_p[3*p+2]);
379
380 // --- Safe Removal Pattern: Restore -> Remove -> Reacquire ---
381 // This is the fix for the double-restore bug. Pointers are managed carefully
382 // within this block and then restored cleanly after the loop.
383
384 // The scalar a removed particle carries leaves the domain with it.
385 local_removed_psi += psi_p[p];
386
387 // 1. Restore all fields BEFORE modifying the swarm structure. This invalidates all pointers.
388 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PSI), NULL, NULL, (void **)&psi_p); CHKERRQ(ierr);
389 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void **)&status_p); CHKERRQ(ierr);
390 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void **)&pid_p); CHKERRQ(ierr);
391 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void **)&pos_p); CHKERRQ(ierr);
392
393 // 2. Remove the particle at the current local index 'p'.
394 ierr = DMSwarmRemovePointAtIndex(swarm, p); CHKERRQ(ierr);
395 local_removed_count++;
396
397 // 3. After removal, re-acquire pointers ONLY if the loop is not finished.
398 PetscInt nLocalCurrent;
399 ierr = DMSwarmGetLocalSize(swarm, &nLocalCurrent); CHKERRQ(ierr);
400
401 if (nLocalCurrent > 0 && p > 0) { // Check if there are particles left AND iterations left
402 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void **)&status_p); CHKERRQ(ierr);
403 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void **)&pid_p); CHKERRQ(ierr);
404 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void **)&pos_p); CHKERRQ(ierr);
405 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PSI), NULL, NULL, (void **)&psi_p); CHKERRQ(ierr);
406 } else {
407 // All remaining particles were removed OR this was the last particle (p=0).
408 // Invalidate pointers to prevent the final restore call and exit the loop.
409 status_p = NULL;
410 pid_p = NULL;
411 pos_p = NULL;
412 psi_p = NULL;
413 break;
414 }
415 }
416 } // End of backwards loop
417
418 // At the end, restore any valid pointers. This handles three cases:
419 // 1. No particles were removed: restores the original pointers.
420 // 2. Particles were removed mid-loop: restores the pointers from the last re-acquisition.
421 // 3. All particles were removed: pointers are NULL, so nothing is done.
422 if (status_p) { ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void **)&status_p); CHKERRQ(ierr); }
423 if (pid_p) { ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void **)&pid_p); CHKERRQ(ierr); }
424 if (pos_p) { ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void **)&pos_p); CHKERRQ(ierr); }
425 if (psi_p) { ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PSI), NULL, NULL, (void **)&psi_p); CHKERRQ(ierr); }
426 } // End of if (nLocalInitial > 0)
427
428 PetscInt nLocalFinal;
429 ierr = DMSwarmGetLocalSize(swarm, &nLocalFinal); CHKERRQ(ierr);
430 LOG_ALLOW(LOCAL, LOG_INFO, "Rank %d: Finished removing %d LOST particles. Final local size: %d.\n", rank, local_removed_count, nLocalFinal);
431
432 // --- Synchronize counts across all ranks ---
433 *removedCountLocal = local_removed_count;
434 if (removedCountGlobal) {
435 ierr = MPI_Allreduce(&local_removed_count, &global_removed_count_mpi, 1, MPI_INT, MPI_SUM, PetscObjectComm((PetscObject)swarm)); CHKERRQ(ierr);
436 *removedCountGlobal = global_removed_count_mpi;
437 // Use a synchronized log message so only one rank prints the global total.
438 LOG_ALLOW_SYNC(GLOBAL, LOG_INFO, "[Rank %d] Removed %d LOST particles globally.\n", rank, *removedCountGlobal);
439 }
440 if (removedScalarSumGlobal) {
441 ierr = MPI_Allreduce(&local_removed_psi, removedScalarSumGlobal, 1, MPIU_REAL, MPI_SUM,
442 PetscObjectComm((PetscObject)swarm)); CHKERRMPI(ierr);
443 }
444
446 PetscFunctionReturn(0);
447}
@ LOST
Definition variables.h:167
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 456 of file ParticleMotion.c.

457{
458 PetscErrorCode ierr;
459 DM swarm = user->swarm;
460 PetscInt p_idx;
461 PetscInt *rankField = NULL; // Field storing target rank
462
463 PetscFunctionBeginUser;
465
466 // Ensure the migration rank field exists
467 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_RANK), NULL, NULL, (void **)&rankField); CHKERRQ(ierr);
468
469 // Set the target rank for migrating particles
470 for(p_idx = 0; p_idx < migrationCount; ++p_idx) {
471 rankField[migrationList[p_idx].local_index] = migrationList[p_idx].target_rank;
472 }
473
474 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_RANK), NULL, NULL, (void **)&rankField); CHKERRQ(ierr);
475
477 PetscFunctionReturn(0);
478}
@ PARTICLE_FIELD_ID_RANK
PetscInt local_index
Definition variables.h:236
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 489 of file ParticleMotion.c.

490{
491 PetscErrorCode ierr;
492 DM swarm = user->swarm;
493 PetscMPIInt rank;
494
495 PetscFunctionBeginUser;
497 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
498 LOG_ALLOW(GLOBAL, LOG_INFO, "Rank %d: Starting DMSwarmMigrate...\n", rank);
499
500 // Perform the migration - PETSC_TRUE removes particles that fail to land
501 // in a valid cell on the target rank (or were marked with an invalid rank).
502 ierr = DMSwarmMigrate(swarm, PETSC_TRUE); CHKERRQ(ierr);
503
504 LOG_ALLOW(GLOBAL, LOG_INFO, "Rank %d: Migration complete.\n", rank);
506 PetscFunctionReturn(0);
507}
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 521 of file ParticleMotion.c.

521 {
522 PetscErrorCode ierr;
523 DM da = user->da;
524 DM swarm = user->swarm;
525 Vec countVec = user->ParticleCount;
526 Vec localcountVec = user->lParticleCount;
527 PetscInt nlocal, p;
528 PetscInt *global_cell_id_arr; // Read GLOBAL cell IDs
529 PetscScalar ***count_arr_3d; // Use 3D accessor
530 PetscInt64 *PID_arr;
531 PetscMPIInt rank;
532 char msg[ERROR_MSG_BUFFER_SIZE];
533 PetscInt particles_counted_locally = 0;
534
535 PetscFunctionBeginUser;
537 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
538
539 // --- Input Validation ---
540 if (!da) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx->da is NULL.");
541 if (!swarm) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx->swarm is NULL.");
542 if (!countVec) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx->ParticleCount is NULL.");
543 // Check DOF of da
544 PetscInt count_dof;
545 ierr = DMDAGetInfo(da, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &count_dof, NULL, NULL, NULL, NULL, NULL); CHKERRQ(ierr);
546 if (count_dof != 1) {
547 PetscSNPrintf(msg, sizeof(msg), "countDM must have DOF=1, got %" PetscInt_FMT ".", count_dof);
548 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "%s", msg);
549 }
550
551 // --- Zero the local count vector ---
552 ierr = VecSet(localcountVec, 0.0); CHKERRQ(ierr);
553
554 // --- Get Particle Data ---
555 LOG_ALLOW(GLOBAL,LOG_DEBUG, "Accessing particle data.\n");
556 ierr = DMSwarmGetLocalSize(swarm, &nlocal); CHKERRQ(ierr);
557 ierr = DMSwarmGetField(swarm,ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void **)&global_cell_id_arr); CHKERRQ(ierr);
558 ierr = DMSwarmGetField(swarm,ParticleFieldName(PARTICLE_FIELD_ID_PID),NULL,NULL,(void **)&PID_arr);CHKERRQ(ierr);
559
560 // --- Get Grid Vector Array using DMDA accessor ---
561 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Accessing ParticleCount vector array (using DMDAVecGetArray).\n");
562 ierr = DMDAVecGetArray(da, localcountVec, &count_arr_3d); CHKERRQ(ierr);
563
564 // Get local owned + ghosted range for writing into ghost slots.
565 PetscInt gxs, gys, gzs, gxm, gym, gzm;
566 ierr = DMDAGetGhostCorners(da, &gxs, &gys, &gzs, &gxm, &gym, &gzm); CHKERRQ(ierr);
567
568 // --- Accumulate Counts Locally ---
569 LOG_ALLOW(LOCAL, LOG_DEBUG, "CalculateParticleCountPerCell (Rank %d): Processing %" PetscInt_FMT " local particles using GLOBAL CellIDs.\n",rank,nlocal);
570 for (p = 0; p < nlocal; p++) {
571 // Read the GLOBAL indices stored for this particle
572 PetscInt i_geom = global_cell_id_arr[p * 3 + 0]; // Global i index
573 PetscInt j_geom = global_cell_id_arr[p * 3 + 1]; // Global j index
574 PetscInt k_geom = global_cell_id_arr[p * 3 + 2]; // Global k index
575
576 // Apply the shift to ensure ParticleCount follows the indexing convention for cell-centered data in this codebase.
577 PetscInt i = (PetscInt)i_geom + 1; // Shift for cell-centered
578 PetscInt j = (PetscInt)j_geom + 1; // Shift for cell-centered
579 PetscInt k = (PetscInt)k_geom + 1; // Shift for cell-centered
580
581 // *** Bounds check is implicitly handled by DMDAVecGetArray for owned+ghost region ***
582 // However, accessing outside this region using global indices WILL cause an error.
583 // A preliminary check might still be wise if global IDs could be wild.
584 // We rely on LocateAllParticles to provide valid global indices [0..IM-1] etc.
585
587 "[Rank %d] Read CellID for p=%" PetscInt_FMT ", PID = %" PetscInt64_FMT ": (%" PetscInt_FMT ", %" PetscInt_FMT ", %" PetscInt_FMT ")\n",
588 rank, p, PID_arr[p], i, j, k);
589
590 // Check if the global index (i,j,k) falls within the local + ghost range
591 if (i >= gxs && i < gxs + gxm &&
592 j >= gys && j < gys + gym && // Adjust based on actual ghost width
593 k >= gzs && k < gzs + gzm ) // This check prevents definite crashes but doesn't guarantee ownership
594 {
595
596 // Increment count at the location corresponding to GLOBAL index (I,J,K)
597 // LOG_ALLOW(LOCAL, LOG_DEBUG, "CalculateParticleCountPerCell (Rank %d): Particle %d with global CellID (%d, %d, %d) incremented with a particle.\n",rank, p, i, j, k);
598 count_arr_3d[k][j][i] += 1.0;
599 particles_counted_locally++;
600 } else {
601 // This particle's global ID is likely outside the range this rank handles (even ghosts)
602 // note: this is not necessarily an error if the particle is legitimately outside the local+ghost region
604 "(Rank %d): Skipping particle %" PetscInt64_FMT " with global CellID (%" PetscInt_FMT ", %" PetscInt_FMT ", %" PetscInt_FMT ") - likely outside local+ghost range.\n",
605 rank, PID_arr[p], i, j, k);
606 }
607 }
608 LOG_ALLOW(LOCAL, LOG_DEBUG, "(Rank %d): Local counting finished. Processed %" PetscInt_FMT " particles locally.\n", rank, particles_counted_locally);
609
610 // --- Restore Access ---
611 ierr = DMDAVecRestoreArray(da, localcountVec, &count_arr_3d); CHKERRQ(ierr);
612 ierr = DMSwarmRestoreField(swarm,ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void **)&global_cell_id_arr); CHKERRQ(ierr);
613 ierr = DMSwarmRestoreField(swarm,ParticleFieldName(PARTICLE_FIELD_ID_PID),NULL,NULL,(void **)&PID_arr);CHKERRQ(ierr);
614
615 // --- Assemble Global Vector ---
616 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Assembling global ParticleCount vector.\n");
617 ierr = VecZeroEntries(countVec); CHKERRQ(ierr); // Ensure global vector is zeroed before accumulation
618 ierr = DMLocalToGlobalBegin(da, localcountVec, ADD_VALUES, countVec); CHKERRQ(ierr);
619 ierr = DMLocalToGlobalEnd(da, localcountVec, ADD_VALUES, countVec); CHKERRQ(ierr);
620 /*
621 * OPTIONAL: Synchronize Ghosts for Stencil Operations
622 * If a future function needs to read ParticleCount from neighbor cells (e.g., density smoothing
623 * or gradient calculations), uncomment the following lines to update the ghost slots
624 * in user->lParticleCount with the final summed values.
625 *
626 ierr = UpdateLocalGhosts(user, FIELD_ID_PARTICLE_COUNT); CHKERRQ(ierr);
627 */
628
629 // --- Verification Logging ---
630 PetscReal total_counted_particles = 0.0, max_count_in_cell = 0.0;
631 ierr = VecSum(countVec, &total_counted_particles); CHKERRQ(ierr);
632 PetscInt max_idx_global = -1;
633 ierr = VecMax(countVec, &max_idx_global, &max_count_in_cell); CHKERRQ(ierr);
634 LOG_ALLOW(GLOBAL, LOG_INFO, "Total counted globally = %.0f, Max count in cell = %.0f\n",
635 total_counted_particles, max_count_in_cell);
636
637 // --- ADD THIS DEBUGGING BLOCK ---
638 if (max_idx_global >= 0) { // Check if VecMax found a location
639 // Need to convert the flat global index back to 3D global index (I, J, K)
640 // Get global grid dimensions (Nodes, NOT Cells IM/JM/KM)
641 PetscInt M, N, P;
642 ierr = DMDAGetInfo(da, NULL, &M, &N, &P, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); CHKERRQ(ierr);
643 // Note: Assuming DOF=1 for countVec, index mapping uses node dimensions M,N,P from DMDA creation (IM+1, etc)
644 // Re-check if your DMDA uses cell counts (IM) or node counts (IM+1) for Vec layout. Let's assume Node counts M,N,P.
645 PetscInt Kmax = max_idx_global / (M * N);
646 PetscInt Jmax = (max_idx_global % (M * N)) / M;
647 PetscInt Imax = max_idx_global % M;
648 LOG_ALLOW(GLOBAL, LOG_INFO, " -> Max count located at global index (I,J,K) = (%d, %d, %d) [Flat index: %d]\n",
649 (int)Imax, (int)Jmax, (int)Kmax, (int)max_idx_global);
650
651 // Also, let's explicitly check the count at (0,0,0)
652 PetscScalar count_at_origin = 0.0;
653 PetscScalar ***count_arr_for_check;
654 ierr = DMDAVecGetArrayRead(da, countVec, &count_arr_for_check); CHKERRQ(ierr);
655 // Check bounds before accessing - crucial if using global indices
656 PetscInt xs, ys, zs, xm, ym, zm;
657 ierr = DMDAGetCorners(da, &xs, &ys, &zs, &xm, &ym, &zm); CHKERRQ(ierr);
658 if (0 >= xs && 0 < xs+xm && 0 >= ys && 0 < ys+ym && 0 >= zs && 0 < zs+zm) {
659 count_at_origin = count_arr_for_check[0][0][0]; // Access using global index (0,0,0)
660 } else {
661 // Origin is not on this rank (relevant for parallel, but check anyway)
662 count_at_origin = -999.0; // Indicate it wasn't accessible locally
663 }
664 ierr = DMDAVecRestoreArrayRead(da, countVec, &count_arr_for_check); CHKERRQ(ierr);
665 LOG_ALLOW(GLOBAL, LOG_INFO, " -> Count at global index (0,0,0) = %.1f\n", count_at_origin);
666
667 } else {
668 LOG_ALLOW(GLOBAL, LOG_WARNING, " -> VecMax did not return a location for the maximum value.\n");
669 }
670 // --- END DEBUGGING BLOCK ---
671
672 LOG_ALLOW(GLOBAL, LOG_INFO, "Particle counting complete.\n");
673
674
676 PetscFunctionReturn(0);
677}
#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:1171
Vec ParticleCount
Definition variables.h:1171
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 690 of file ParticleMotion.c.

691{
692 PetscErrorCode ierr;
693 PetscInt N_current, N_final, nlocal_current, nlocal_target;
694 PetscMPIInt rank, size;
695 MPI_Comm comm;
696
697 PetscFunctionBeginUser;
699 ierr = PetscObjectGetComm((PetscObject)swarm, &comm); CHKERRQ(ierr);
700 ierr = MPI_Comm_rank(comm, &rank); CHKERRQ(ierr);
701 ierr = MPI_Comm_size(comm, &size); CHKERRQ(ierr);
702 PetscCheck(N_target >= 0, comm, PETSC_ERR_ARG_OUTOFRANGE,
703 "Target swarm size must be nonnegative; got %" PetscInt_FMT ".", N_target);
704 ierr = DMSwarmGetSize(swarm, &N_current); CHKERRQ(ierr);
705 ierr = DMSwarmGetLocalSize(swarm, &nlocal_current); CHKERRQ(ierr);
706 nlocal_target = N_target / size + (rank < N_target % size ? 1 : 0);
707
708 if (nlocal_current != nlocal_target) {
710 "Rank %d: resizing local swarm share from %" PetscInt_FMT
711 " to %" PetscInt_FMT ".\n",
712 rank, nlocal_current, nlocal_target);
713 ierr = DMSwarmSetLocalSizes(swarm, nlocal_target, -1); CHKERRQ(ierr);
714 }
715
716 // Verify final size
717 ierr = DMSwarmGetSize(swarm, &N_final); CHKERRQ(ierr);
718 if (N_final != N_target) {
719 SETERRQ(comm, PETSC_ERR_PLIB,
720 "Failed to resize swarm: expected %" PetscInt_FMT
721 " particles, got %" PetscInt_FMT, N_target, N_final);
722 }
724 "Swarm resized from %" PetscInt_FMT " to %" PetscInt_FMT " particles.\n",
725 N_current, N_final);
727 PetscFunctionReturn(0);
728}
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 736 of file ParticleMotion.c.

739{
740 PetscErrorCode ierr;
741 PetscInt N_file = 0;
742 PetscInt N_current = 0;
743
744 PetscFunctionBeginUser;
746 (void)ext;
747 ierr = ReadCheckpointParticleCount(user, ti, &N_file); CHKERRQ(ierr);
749 "Committed checkpoint step %d records %d particles.\n", ti, N_file);
750
751
752 // --- Now all ranks have the correct N_file, compare and resize if needed ---
753 ierr = DMSwarmGetSize(user->swarm, &N_current); CHKERRQ(ierr);
754
755 if (N_file != N_current) {
756 LOG_ALLOW(GLOBAL, LOG_INFO, "Swarm size %d differs from file size %d. Resizing swarm globally.\n", N_current, N_file);
757 ierr = ResizeSwarmGlobally(user->swarm, N_file); CHKERRQ(ierr);
758 } else {
759 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Swarm size (%d) already matches file size. No resize needed.\n", N_current);
760 }
761
762 // Also update the context
763 user->simCtx->np = N_file;
764
766 PetscFunctionReturn(0);
767}
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:1933
PetscInt np
Definition variables.h:990
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 776 of file ParticleMotion.c.

777{
778 PetscErrorCode ierr;
779 PetscMPIInt rank; // MPI rank of the current process
780 DM swarm = user->swarm; // The particle swarm DM
781 PetscReal *positions_field = NULL; // Pointer to swarm field for physical positions
782 PetscInt64 *particleIDs = NULL; // Pointer to swarm field for Particle IDs (for logging)
783 PetscInt *cell_ID_field = NULL; // Pointer to swarm field for Cell IDs (for resetting after migration)
784 const Cmpnts ***coor_nodes_local_array; // Read-only access to local node coordinates
785 Vec Coor_local; // Local vector for node coordinates
786 DMDALocalInfo info; // Local grid information (node-based) from user->da
787 PetscInt xs_gnode_rank, ys_gnode_rank, zs_gnode_rank; // Local starting node indices (incl. ghosts) of rank's DA
788 PetscInt IM_nodes_global, JM_nodes_global, KM_nodes_global; // Global node counts
789
790 PetscRandom rand_logic_reinit_i, rand_logic_reinit_j, rand_logic_reinit_k; // RNGs for re-placement
791 PetscInt nlocal_current; // Number of particles currently on this rank
792 PetscInt particles_actually_reinitialized_count = 0; // Counter for logging
793 PetscBool can_this_rank_service_inlet = PETSC_FALSE; // Flag
794
795 PetscFunctionBeginUser;
796
798
799 // This function is only relevant for surface initialization mode and if an inlet face is defined.
800 if ((user->simCtx->ParticleInitialization != 0 && user->simCtx->ParticleInitialization !=3) || !user->inletFaceDefined) {
802 PetscFunctionReturn(0);
803 }
804
805 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
806 ierr = DMSwarmGetLocalSize(swarm, &nlocal_current); CHKERRQ(ierr);
807
808 // If no particles on this rank, nothing to do.
809 if (nlocal_current == 0) {
810 LOG_ALLOW(LOCAL, LOG_DEBUG, "[T=%.4f, Step=%d] Rank %d has no local particles to re-initialize on inlet.\n", currentTime, step, rank);
812 PetscFunctionReturn(0);
813 }
814
815 // Get DMDA information for the node-centered coordinate grid (user->da)
816 ierr = DMDAGetLocalInfo(user->da, &info); CHKERRQ(ierr);
817 ierr = DMDAGetInfo(user->da, NULL, &IM_nodes_global, &JM_nodes_global, &KM_nodes_global, NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); CHKERRQ(ierr);
818 ierr = DMDAGetCorners(user->da, &xs_gnode_rank, &ys_gnode_rank, &zs_gnode_rank, NULL, NULL, NULL); CHKERRQ(ierr);
819
820 // Modification to IM_nodes_global etc. to account for 1-cell halo in each direction.
821 IM_nodes_global -= 1; JM_nodes_global -= 1; KM_nodes_global -= 1;
822
823 const PetscInt IM_cells_global = IM_nodes_global > 0 ? IM_nodes_global - 1 : 0;
824 const PetscInt JM_cells_global = JM_nodes_global > 0 ? JM_nodes_global - 1 : 0;
825 const PetscInt KM_cells_global = KM_nodes_global > 0 ? KM_nodes_global - 1 : 0;
826
827
828
829 // Check if this rank is responsible for (part of) the designated inlet surface
830 ierr = CanRankServiceInletFace(user, &info, IM_nodes_global, JM_nodes_global, KM_nodes_global, &can_this_rank_service_inlet); CHKERRQ(ierr);
831
832 // Get coordinate array and swarm fields for modification
833 ierr = DMGetCoordinatesLocal(user->da, &Coor_local); CHKERRQ(ierr);
834 ierr = DMDAVecGetArrayRead(user->fda, Coor_local, (void*)&coor_nodes_local_array); CHKERRQ(ierr);
835 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&positions_field); CHKERRQ(ierr);
836 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&particleIDs); CHKERRQ(ierr); // For logging
837 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_ID_field); CHKERRQ(ierr);
838
839 if (!can_this_rank_service_inlet) {
840 LOG_ALLOW(LOCAL, LOG_DEBUG, "[T=%.4f, Step=%d] Rank %d cannot service inlet face %s. Skipping re-initialization of %d particles.\n", currentTime, step, rank, BCFaceToString(user->identifiedInletBCFace), nlocal_current);
841
842 // FALLBACK ACTION: Reset position fields to Inlet center for migration and cell ID to -1 for safety.
843 LOG_ALLOW(LOCAL, LOG_DEBUG, "[T=%.4f, Step=%d] Rank %d is resetting %d local particles to inlet center (%.6f, %.6f, %.6f) for migration.\n", currentTime, step, rank, nlocal_current, user->simCtx->CMx_c, user->simCtx->CMy_c, user->simCtx->CMz_c);
844
845 for(PetscInt p = 0; p < nlocal_current; p++){
846 positions_field[3*p+0] = user->simCtx->CMx_c;
847 positions_field[3*p+1] = user->simCtx->CMy_c;
848 positions_field[3*p+2] = user->simCtx->CMz_c;
849
850 cell_ID_field[3*p+0] = -1;
851 cell_ID_field[3*p+1] = -1;
852 cell_ID_field[3*p+2] = -1;
853 }
854
855 // Cleanup: restore swarm fields/coordinate array
856 ierr = DMDAVecRestoreArrayRead(user->fda, Coor_local, (void*)&coor_nodes_local_array); CHKERRQ(ierr);
857 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&positions_field); CHKERRQ(ierr);
858 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&particleIDs); CHKERRQ(ierr); // For logging
859 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_ID_field); CHKERRQ(ierr);
861 PetscFunctionReturn(0);
862 }
863
864 LOG_ALLOW(GLOBAL, LOG_INFO, "[T=%.4f, Step=%d] Rank %d is on inlet face %s. Attempting to re-place %d local particles.\n", currentTime, step, rank, BCFaceToString(user->identifiedInletBCFace), nlocal_current);
865
866 // Initialize fresh RNGs for this re-placement to ensure good distribution
867 ierr = InitializeLogicalSpaceRNGs(user->simCtx->particleRandomSeed, &rand_logic_reinit_i, &rand_logic_reinit_j, &rand_logic_reinit_k); CHKERRQ(ierr);
868
869 // Loop over all particles currently local to this rank
870 for (PetscInt p = 0; p < nlocal_current; p++) {
871 PetscInt ci_metric_lnode, cj_metric_lnode, ck_metric_lnode; // Local node indices (of rank's DA patch) for cell origin
872 PetscReal xi_metric_logic, eta_metric_logic, zta_metric_logic; // Intra-cell logical coordinates
873 Cmpnts phys_coords = {0.0,0.0,0.0}; // To store newly calculated physical coordinates
874 PetscBool particle_was_placed = PETSC_FALSE;
875
877 // Get random cell on this rank's portion of the inlet and random logical coords within it
878 ierr = GetRandomCellAndLogicalCoordsOnInletFace(user, &info, xs_gnode_rank, ys_gnode_rank, zs_gnode_rank,
879 IM_nodes_global, JM_nodes_global, KM_nodes_global,
880 &rand_logic_reinit_i, &rand_logic_reinit_j, &rand_logic_reinit_k,
881 &ci_metric_lnode, &cj_metric_lnode, &ck_metric_lnode,
882 &xi_metric_logic, &eta_metric_logic, &zta_metric_logic); CHKERRQ(ierr);
883
884 // Convert these logical coordinates to physical coordinates
885
886 ierr = MetricLogicalToPhysical(user, coor_nodes_local_array,
887 ci_metric_lnode, cj_metric_lnode, ck_metric_lnode,
888 xi_metric_logic, eta_metric_logic, zta_metric_logic,
889 &phys_coords); CHKERRQ(ierr);
890
891 // Update the particle's position in the swarm fields
892 positions_field[3*p+0] = phys_coords.x;
893 positions_field[3*p+1] = phys_coords.y;
894 positions_field[3*p+2] = phys_coords.z;
895 particle_was_placed = PETSC_TRUE;
896
898 PetscBool placement_flag = PETSC_FALSE;
899 ierr = GetDeterministicFaceGridLocation(user, &info, xs_gnode_rank, ys_gnode_rank, zs_gnode_rank,
900 IM_cells_global, JM_cells_global, KM_cells_global,
901 particleIDs[p],
902 &ci_metric_lnode, &cj_metric_lnode, &ck_metric_lnode,
903 &xi_metric_logic, &eta_metric_logic, &zta_metric_logic,&placement_flag); CHKERRQ(ierr);
904
905
906 if(placement_flag){
907 // Convert these logical coordinates to physical coordinates
908 ierr = MetricLogicalToPhysical(user, coor_nodes_local_array,
909 ci_metric_lnode, cj_metric_lnode, ck_metric_lnode,
910 xi_metric_logic, eta_metric_logic, zta_metric_logic,
911 &phys_coords); CHKERRQ(ierr);
912
913 // Update the particle's position in the swarm fields
914 positions_field[3*p+0] = phys_coords.x;
915 positions_field[3*p+1] = phys_coords.y;
916 positions_field[3*p+2] = phys_coords.z;
917 particle_was_placed = PETSC_TRUE;
918 } else{
919 // Deterministic placement failed (particle migrated to rank where formula says it doesn't belong)
920 // Fall back to random placement on this rank's portion of inlet surface
921 LOG_ALLOW(GLOBAL, LOG_WARNING, "Rank %d: Particle PID %ld deterministic placement failed (belongs to different rank). Falling back to random placement.\n", rank, particleIDs[p]);
922
923 ierr = GetRandomCellAndLogicalCoordsOnInletFace(user, &info, xs_gnode_rank, ys_gnode_rank, zs_gnode_rank,
924 IM_nodes_global, JM_nodes_global, KM_nodes_global,
925 &rand_logic_reinit_i, &rand_logic_reinit_j, &rand_logic_reinit_k,
926 &ci_metric_lnode, &cj_metric_lnode, &ck_metric_lnode,
927 &xi_metric_logic, &eta_metric_logic, &zta_metric_logic); CHKERRQ(ierr);
928
929 // Convert to physical coordinates
930 ierr = MetricLogicalToPhysical(user, coor_nodes_local_array,
931 ci_metric_lnode, cj_metric_lnode, ck_metric_lnode,
932 xi_metric_logic, eta_metric_logic, zta_metric_logic,
933 &phys_coords); CHKERRQ(ierr);
934
935 // Update particle position
936 positions_field[3*p+0] = phys_coords.x;
937 positions_field[3*p+1] = phys_coords.y;
938 positions_field[3*p+2] = phys_coords.z;
939 particle_was_placed = PETSC_TRUE;
940 }
941
942 } else{
943 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "ReinitializeParticlesOnInletSurface only supports ParticleInitialization modes 0 and 3.");
944 }
945
946 if(particle_was_placed){
947 particles_actually_reinitialized_count++;
948
949 cell_ID_field[3*p+0] = -1;
950 cell_ID_field[3*p+1] = -1;
951 cell_ID_field[3*p+2] = -1;
952
953 LOG_LOOP_ALLOW(LOCAL, LOG_VERBOSE, p, (nlocal_current > 20 ? nlocal_current/10 : 1), // Sampled logging
954 "Rank %d: PID %ld (idx %ld) RE-PLACED. CellOriginNode(locDAIdx):(%d,%d,%d). LogicCoords: (%.2e,%.2f,%.2f). PhysCoords: (%.6f,%.6f,%.6f).\n",
955 rank, particleIDs[p], (long)p,
956 ci_metric_lnode, cj_metric_lnode, ck_metric_lnode,
957 xi_metric_logic, eta_metric_logic, zta_metric_logic,
958 phys_coords.x, phys_coords.y, phys_coords.z);
959 }
960 }
961
962 // Logging summary of re-initialization
963 if (particles_actually_reinitialized_count > 0) {
964 LOG_ALLOW(GLOBAL, LOG_INFO, "[T=%.4f, Step=%d] Rank %d (on inlet face %d) successfully re-initialized %d of %d local particles.\n", currentTime, step, rank, user->identifiedInletBCFace, particles_actually_reinitialized_count, nlocal_current);
965 } else if (nlocal_current > 0) { // This case should ideally not be hit if can_this_rank_service_inlet was true and particles were present.
966 LOG_ALLOW(GLOBAL, LOG_WARNING, "[T=%.4f, Step=%d] Rank %d claimed to service inlet face %d, but re-initialized 0 of %d local particles. This may indicate an issue if particles were expected to be re-placed.\n", currentTime, step, rank, user->identifiedInletBCFace, nlocal_current);
967 }
968
969 // Cleanup: Destroy RNGs and restore swarm fields/coordinate array
970 ierr = PetscRandomDestroy(&rand_logic_reinit_i); CHKERRQ(ierr);
971 ierr = PetscRandomDestroy(&rand_logic_reinit_j); CHKERRQ(ierr);
972 ierr = PetscRandomDestroy(&rand_logic_reinit_k); CHKERRQ(ierr);
973
974 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&positions_field); CHKERRQ(ierr);
975 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&particleIDs); CHKERRQ(ierr);
976 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_ID_field); CHKERRQ(ierr);
977 ierr = DMDAVecRestoreArrayRead(user->fda, Coor_local, (void*)&coor_nodes_local_array); CHKERRQ(ierr);
978
979
981 PetscFunctionReturn(0);
982}
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(PetscInt base_seed, PetscRandom *rand_logic_i, PetscRandom *rand_logic_j, PetscRandom *rand_logic_k)
Initializes random number generators for logical space operations [0.0, 1.0).
Definition setup.c:3895
PetscBool inletFaceDefined
Definition variables.h:1100
BCFace identifiedInletBCFace
Definition variables.h:1101
@ PARTICLE_INIT_SURFACE_RANDOM
Random placement on the inlet face.
Definition variables.h:710
@ PARTICLE_INIT_SURFACE_EDGES
Deterministic placement at inlet face edges.
Definition variables.h:713
PetscReal CMy_c
Definition variables.h:944
PetscReal CMz_c
Definition variables.h:944
ParticleInitializationType ParticleInitialization
Definition variables.h:994
PetscInt particleRandomSeed
Base seed for every particle RNG stream (-particle_random_seed).
Definition variables.h:991
PetscReal CMx_c
Definition variables.h:944
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 990 of file ParticleMotion.c.

993{
994 PetscErrorCode ierr;
995 PetscMPIInt rank;
996
997 PetscFunctionBeginUser;
998
1000
1001 // --- 1. Input Validation ---
1002 if (!pids_snapshot_out) {
1003 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Output pointer pids_snapshot_out is NULL.");
1004 }
1005 // If n_local > 0, pid_field must not be NULL.
1006 if (n_local > 0 && !pid_field) {
1007 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Input pid_field pointer is NULL for n_local > 0.");
1008 }
1009
1010 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1011 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d]: Creating PID snapshot for %d local particles.\n", rank, n_local);
1012
1013 // If there are no local particles, the snapshot is empty (NULL).
1014 if (n_local == 0) {
1015 *pids_snapshot_out = NULL;
1016
1018 PetscFunctionReturn(0);
1019 }
1020
1021 // --- 2. Allocate Memory for the Snapshot ---
1022 ierr = PetscMalloc1(n_local, pids_snapshot_out); CHKERRQ(ierr);
1023
1024 // --- 3. Copy Data ---
1025 // Perform a fast memory copy from the provided array to our new snapshot array.
1026 ierr = PetscMemcpy(*pids_snapshot_out, pid_field, n_local * sizeof(PetscInt64)); CHKERRQ(ierr);
1027 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d]: Copied %d PIDs.\n", rank, n_local);
1028
1029 // --- 4. Sort the Snapshot Array ---
1030 // Sorting enables fast binary search lookups later.
1031 ierr = PetscSortInt64(n_local, *pids_snapshot_out); CHKERRQ(ierr);
1032 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d]: PID snapshot sorted successfully.\n", rank);
1033
1034
1036 PetscFunctionReturn(0);
1037}
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 1045 of file ParticleMotion.c.

1050{
1051 PetscErrorCode ierr;
1052 PetscMPIInt rank;
1053
1054 PetscFunctionBeginUser;
1055
1057
1058 // --- 1. Input Validation ---
1059 if (!migration_list_p || !capacity_p || !count_p) {
1060 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Null pointer provided to AddToMigrationList for list management.");
1061 }
1062
1063 // --- 2. Check if the list needs to be resized ---
1064 if (*count_p >= *capacity_p) {
1065 PetscInt old_capacity = *capacity_p;
1066 // Start with a reasonable base capacity, then double for subsequent reallocations.
1067 PetscInt new_capacity = (old_capacity == 0) ? 16 : old_capacity * 2;
1068
1069 // Use PetscRealloc for safe memory reallocation.
1070 // It handles allocating new memory, copying old data, and freeing the old block.
1071 // The first argument to PetscRealloc is the new size in BYTES.
1072 ierr = PetscRealloc(new_capacity * sizeof(MigrationInfo), migration_list_p); CHKERRQ(ierr);
1073
1074 *capacity_p = new_capacity; // Update the capacity tracker
1075
1076 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1077 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d]: Reallocated migrationList capacity from %d to %d.\n",
1078 rank, old_capacity, new_capacity);
1079 }
1080
1081 // --- 3. Add the new migration data to the list ---
1082 // Dereference the pointer-to-a-pointer to get the actual array.
1083 MigrationInfo *list = *migration_list_p;
1084
1085 list[*count_p].local_index = particle_local_idx;
1086 list[*count_p].target_rank = destination_rank;
1087
1088 // --- 4. Increment the count of items in the list ---
1089 (*count_p)++;
1090
1091
1093 PetscFunctionReturn(0);
1094}
Information needed to migrate a single particle between MPI ranks.
Definition variables.h:235
Head of a generic C-style linked list.
Definition variables.h:469
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 1103 of file ParticleMotion.c.

1106{
1107 PetscErrorCode ierr;
1108 PetscMPIInt rank;
1109 PetscInt n_local_after;
1110 PetscInt newcomer_count = 0;
1111
1112 // Pointers to the swarm data fields we will read and modify
1113 PetscInt64 *pid_field_after = NULL;
1114 PetscInt *status_field_after = NULL;
1115 PetscInt *cell_field_after = NULL;
1116
1117 PetscFunctionBeginUser;
1118
1120
1121 // --- 1. Input Validation and Basic Setup ---
1122 if (!swarm) {
1123 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, "Input DMSwarm is NULL in FlagNewcomersForLocation.");
1124 }
1125 // If n_local_before > 0, the corresponding PID array must not be null.
1126 if (n_local_before > 0 && !pids_before) {
1127 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Input pids_before array is NULL for n_local_before > 0.");
1128 }
1129
1130 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1131
1132 // Get the number of particles on this rank *after* the migration.
1133 ierr = DMSwarmGetLocalSize(swarm, &n_local_after); CHKERRQ(ierr);
1134
1135 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d]: Checking for newcomers. Size before: %d, Size after: %d\n",
1136 rank, n_local_before, n_local_after);
1137
1138 // If there are no particles now, there's nothing to do.
1139 if (n_local_after == 0) {
1141 PetscFunctionReturn(0);
1142 }
1143
1144 // --- 2. Access Swarm Data ---
1145 // Get read-only access to the PIDs and read-write access to the status field.
1146 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid_field_after); CHKERRQ(ierr);
1147 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status_field_after); CHKERRQ(ierr);
1148 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_field_after); CHKERRQ(ierr);
1149 if (!pid_field_after || !status_field_after) {
1150 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, "Failed to get required swarm fields in FlagNewcomersForLocation.");
1151 }
1152
1153 // --- 3. Identify and Flag Newcomers ---
1154 // Loop through all particles currently on this rank.
1155 for (PetscInt p_idx = 0; p_idx < n_local_after; ++p_idx) {
1156 PetscInt64 current_pid = pid_field_after[p_idx];
1157 PetscBool is_found_in_before_list;
1158
1159 // Use our custom, efficient helper function for the lookup.
1160 ierr = BinarySearchInt64(n_local_before, pids_before, current_pid, &is_found_in_before_list); CHKERRQ(ierr);
1161
1162 // If the PID was NOT found in the "before" list, it must be a newcomer.
1163 if (!is_found_in_before_list) {
1164 // Flag it for processing in the next pass of the migration loop.
1165 status_field_after[p_idx] = NEEDS_LOCATION;
1166 // cell_field_after[3*p_idx+0] = -1;
1167 // cell_field_after[3*p_idx+1] = -1;
1168 // cell_field_after[3*p_idx+2] = -1;
1169 newcomer_count++;
1170
1171 LOG_ALLOW(LOCAL, LOG_VERBOSE, "[Rank %d]: Flagged newcomer PID %ld at local index %d as NEEDS_LOCATION.\n",
1172 rank, current_pid, p_idx);
1173 }
1174 }
1175
1176 // --- 4. Restore Swarm Fields ---
1177 // Release the locks on the swarm data arrays.
1178 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid_field_after); CHKERRQ(ierr);
1179 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status_field_after); CHKERRQ(ierr);
1180 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_field_after); CHKERRQ(ierr);
1181
1182 if (newcomer_count > 0) {
1183 LOG_ALLOW(LOCAL, LOG_INFO, "[Rank %d]: Identified and flagged %d newcomers.\n", rank, newcomer_count);
1184 }
1185
1186
1188 PetscFunctionReturn(0);
1189}
PetscErrorCode BinarySearchInt64(PetscInt n, const PetscInt64 arr[], PetscInt64 key, PetscBool *found)
Performs a binary search for a key in a sorted array of PetscInt64.
Definition setup.c:3616
@ NEEDS_LOCATION
Definition variables.h:164
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 1197 of file ParticleMotion.c.

1198{
1199 PetscErrorCode ierr;
1200 DM swarm = user->swarm;
1201 PetscInt nlocal;
1202 PetscInt *cell_p = NULL;
1203 PetscInt64 *pid_p = NULL;
1204 PetscMPIInt rank;
1205
1206 MigrationInfo *migrationList = NULL;
1207 PetscInt local_migration_count = 0;
1208 PetscInt migrationListCapacity = 0;
1209 PetscInt global_migration_count = 0;
1210
1211 PetscFunctionBeginUser;
1213 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1214
1215 ierr = DMSwarmGetLocalSize(swarm, &nlocal); CHKERRQ(ierr);
1216 LOG_ALLOW(LOCAL, LOG_DEBUG, "Checking %d restart particles for direct migration using CellIDs.\n", nlocal);
1217
1218 if (nlocal > 0) {
1219 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_p); CHKERRQ(ierr);
1220 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid_p); CHKERRQ(ierr);
1221
1222 // Note: We do NOT need to modify the status field here.
1223 // We trust the loaded status (ACTIVE_AND_LOCATED) is correct for the destination rank.
1224
1225 for (PetscInt p_idx = 0; p_idx < nlocal; ++p_idx) {
1226 PetscInt ci = cell_p[3*p_idx + 0];
1227 PetscInt cj = cell_p[3*p_idx + 1];
1228 PetscInt ck = cell_p[3*p_idx + 2];
1229
1230 /* Skip particles with invalid Cell IDs (will be handled by LocateAllParticles) */
1231 if (ci < 0 || cj < 0 || ck < 0) {
1232 continue;
1233 }
1234
1235 PetscMPIInt owner_rank;
1236 ierr = FindOwnerOfCell(user, ci, cj, ck, &owner_rank); CHKERRQ(ierr);
1237
1238 if (owner_rank != -1 && owner_rank != rank) {
1239 /* Particle belongs to another rank - migrate it */
1240 ierr = AddToMigrationList(&migrationList, &migrationListCapacity, &local_migration_count,
1241 p_idx, owner_rank); CHKERRQ(ierr);
1242
1243 LOG_ALLOW(LOCAL, LOG_VERBOSE, "[PID %ld] Direct migration: Cell (%d,%d,%d) belongs to Rank %d (Current: %d).\n",
1244 (long)pid_p[p_idx], ci, cj, ck, owner_rank, rank);
1245 }
1246 }
1247
1248 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_p); CHKERRQ(ierr);
1249 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid_p); CHKERRQ(ierr);
1250 }
1251
1252 /* Check if any rank needs to migrate particles */
1253 ierr = MPI_Allreduce(&local_migration_count, &global_migration_count, 1, MPIU_INT, MPI_SUM, PETSC_COMM_WORLD); CHKERRQ(ierr);
1254
1255 if (global_migration_count > 0) {
1256 LOG_ALLOW(GLOBAL, LOG_INFO, "Fast restart migration: Directly migrating %d particles using CellIDs.\n", global_migration_count);
1257 ierr = SetMigrationRanks(user, migrationList, local_migration_count); CHKERRQ(ierr);
1258 ierr = PerformMigration(user); CHKERRQ(ierr);
1259 /* We do NOT flag newcomers here. We trust their loaded status (ACTIVE_AND_LOCATED) */
1260 /* is valid for their destination rank. */
1261 } else {
1262 LOG_ALLOW(GLOBAL, LOG_INFO, "Fast restart migration: All particles are already on correct ranks.\n");
1263 }
1264
1265 ierr = PetscFree(migrationList); CHKERRQ(ierr);
1266
1268 PetscFunctionReturn(0);
1269}
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 1399 of file ParticleMotion.c.

1400{
1401 PetscErrorCode ierr;
1402 PetscInt passes = 0;
1403 const PetscInt MAX_MIGRATION_PASSES = 50; // Safety break for runaway loops
1404 PetscInt global_migrations_this_pass;
1405 PetscMPIInt rank;
1406 PetscInt total_migrated_this_timestep = 0;
1407
1408 PetscFunctionBeginUser;
1410 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1411 ierr = ResetSearchMetrics(user->simCtx); CHKERRQ(ierr);
1412 LOG_ALLOW(GLOBAL, LOG_INFO, "LocateAllParticlesInGrid (Orchestrator) - Beginning particle settlement process.\n");
1413
1414 // This loop ensures that particles that jump across multiple ranks are
1415 // handled correctly in successive, iterative handoffs.
1416 do {
1417 passes++;
1419 LOG_ALLOW_SYNC(GLOBAL, LOG_INFO, "[Rank %d] Starting migration pass %d.\n", rank, passes);
1420
1421 // --- STAGE 1: PER-PASS INITIALIZATION ---
1422 MigrationInfo *migrationList = NULL;
1423 PetscInt local_migration_count = 0;
1424 PetscInt migrationListCapacity = 0;
1425 PetscInt nlocal_before;
1426 PetscInt64 *pids_before_snapshot = NULL;
1427 PetscInt local_lost_count = 0;
1428
1429 ierr = DMSwarmGetLocalSize(user->swarm, &nlocal_before); CHKERRQ(ierr);
1430 if (passes == 1) {
1431 user->simCtx->searchMetrics.searchPopulation += (PetscInt64)nlocal_before;
1432 }
1433 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d] Pass %d begins with %d local particles.\n", rank, passes, nlocal_before);
1434
1435
1436 // --- STAGE 2: PRE-MIGRATION SNAPSHOT & MAIN PROCESSING LOOP ---
1437 if (nlocal_before > 0) {
1438 // Get pointers to all fields needed for this pass
1439 PetscReal *pos_p, *weights_p, *vel_p;
1440 PetscInt *cell_p, *status_p;
1441 PetscInt64 *pid_p;
1442 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&pos_p); CHKERRQ(ierr);
1443 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_VELOCITY), NULL, NULL, (void**)&vel_p); CHKERRQ(ierr);
1444 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_WEIGHT), NULL, NULL, (void**)&weights_p); CHKERRQ(ierr);
1445 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_p); CHKERRQ(ierr);
1446 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid_p); CHKERRQ(ierr);
1447 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status_p); CHKERRQ(ierr);
1448
1449 // Create a sorted snapshot of current PIDs to identify newcomers after migration.
1450 // This helper requires a raw pointer, which we just acquired.
1451 ierr = GetLocalPIDSnapshot(pid_p, nlocal_before, &pids_before_snapshot); CHKERRQ(ierr);
1452
1453 for (PetscInt p_idx = 0; p_idx < nlocal_before; p_idx++) {
1454
1455 // OPTIMIZATION: Skip particles already settled in a previous pass of this do-while loop.
1456
1458 "Local Particle idx=%d, PID=%ld, status=%s, cell=(%d, %d, %d)\n",
1459 p_idx,
1460 (long)pid_p[p_idx],
1462 cell_p[3*p_idx],
1463 cell_p[3*p_idx+1],
1464 cell_p[3*p_idx+2]);
1465
1466 if (status_p[p_idx] == ACTIVE_AND_LOCATED) {
1467 LOG_ALLOW(LOCAL,LOG_VERBOSE," [rank %d][PID %ld] skipped in pass %d as it is already located at (%d,%d,%d).\n",rank,pid_p[p_idx],passes,cell_p[3*p_idx],cell_p[3*p_idx + 1],cell_p[3*p_idx + 2]);
1468 continue;
1469 }
1470
1471 // UNPACK: Create a temporary C struct for easier processing using our helper.
1472 Particle current_particle;
1473
1474 // LOG_ALLOW(LOCAL,LOG_DEBUG,"about to unpack p_idx=%d (PID=%ld)\n",p_idx, (long)pid_p[p_idx]);
1475
1476 ierr = UnpackSwarmFields(p_idx, pid_p, weights_p, pos_p, cell_p, vel_p, status_p,NULL,NULL,NULL,&current_particle); CHKERRQ(ierr);
1477
1478 // LOG_ALLOW(LOCAL,LOG_DEBUG,"unpacked p_idx=%d → cell[0]=%d, status=%s\n",p_idx, current_particle.cell[0], ParticleLocationStatusToString((ParticleLocationStatus)current_particle.location_status));
1479
1480 ParticleLocationStatus final_status = (ParticleLocationStatus)status_p[p_idx];
1481
1482
1483 // CASE 1: Particle has a valid prior cell index.
1484 // It has moved, so we only need to run the robust walk from its last known location.
1485 if (current_particle.cell[0] >= 0) {
1486 LOG_ALLOW(LOCAL, LOG_VERBOSE, "[PID %ld] has valid prior cell. Strategy: Robust Walk from previous cell.\n", current_particle.PID);
1487 ierr = LocateParticleOrFindMigrationTarget(user, &current_particle, &final_status); CHKERRQ(ierr);
1488 }
1489
1490 /*
1491 // --- "GUESS" FAST PATH for lost particles ---
1492 if (current_particle.cell[0] < 0) {
1493 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld] is lost or uninitialzied (cell=%d), attempting fast guess.\n",current_particle.PID, current_particle.cell[0]);
1494 ierr = GuessParticleOwnerWithBBox(user, &current_particle, bboxlist, &destination_rank); CHKERRQ(ierr);
1495 if (destination_rank != MPI_PROC_NULL && destination_rank != rank) {
1496 final_status = MIGRATING_OUT;
1497 // The particle struct's destination rank must be updated for consistency
1498 current_particle.destination_rank = destination_rank;
1499 }
1500 }
1501
1502 LOG_ALLOW(LOCAL,LOG_DEBUG,"[PID %ld] Particle status after Initial Guess:%d \n",current_particle.PID,final_status);
1503
1504 // --- "VERIFY" ROBUST WALK if guess didn't resolve it ---
1505 if (final_status == NEEDS_LOCATION || UNINITIALIZED) {
1506 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld] Not resolved by guess, starting robust walk.\n", current_particle.PID);
1507 // This function will update the particle's status and destination rank internally.
1508 ierr = LocateParticleOrFindMigrationTarget(user, &current_particle, &final_status); CHKERRQ(ierr);
1509 destination_rank = current_particle.destination_rank; // Retrieve the result
1510 }
1511
1512 // --- PROCESS THE FINAL STATUS AND TAKE ACTION ---
1513 if (final_status == MIGRATING_OUT) {
1514 status_p[p_idx] = MIGRATING_OUT; // Mark for removal by DMSwarm
1515 ierr = AddToMigrationList(&migrationList, &migrationListCapacity, &local_migration_count, p_idx, destination_rank); CHKERRQ(ierr);
1516 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld] at local index %d marked for migration to rank %d.\n",current_particle.PID, p_idx, destination_rank);
1517 } else {
1518 // Particle's final status is either LOCATED or LOST; update its state in the swarm arrays.
1519 current_particle.location_status = final_status;
1520 // PACK: Use the helper to write results back to the swarm arrays.
1521 ierr = UpdateSwarmFields(p_idx, &current_particle, pos_p, vel_p, weights_p, cell_p, status_p,NULL,NULL,NULL); CHKERRQ(ierr);
1522 }
1523 */
1524 // CASE 2: Particle is "lost" (cell = -1). Strategy: Guess -> Verify.
1525 else {
1526 LOG_ALLOW(LOCAL, LOG_VERBOSE, "[PID %ld] has invalid cell. Strategy: Guess Owner -> Find Cell.\n",current_particle.PID);
1527
1528 PetscMPIInt guessed_owner_rank = MPI_PROC_NULL;
1529 ierr = GuessParticleOwnerWithBBox(user, &current_particle, bboxlist, &guessed_owner_rank); CHKERRQ(ierr);
1530
1531 // If the guess finds a DIFFERENT rank, we can mark for migration and skip the walk.
1532 if (guessed_owner_rank != MPI_PROC_NULL && guessed_owner_rank != rank) {
1534 LOG_ALLOW(LOCAL, LOG_VERBOSE, "[PID %ld] Guess SUCCESS: Found migration target Rank %d. Finalizing.\n", current_particle.PID, guessed_owner_rank);
1535 final_status = MIGRATING_OUT;
1536 current_particle.destination_rank = guessed_owner_rank;
1537 }
1538 else {
1540
1541 // This block runs if the guess either failed (rank is NULL) or found the particle is local (rank is self).
1542 // In BOTH cases, the situation is unresolved, and we MUST fall back to the robust walk.
1543 if (guessed_owner_rank == rank) {
1544 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld] Guess determined particle is local. Proceeding to robust walk to find cell.\n", current_particle.PID);
1545 } else { // guessed_owner_rank == MPI_PROC_NULL
1546 LOG_ALLOW(LOCAL, LOG_WARNING, "[PID %ld] Guess FAILED to find an owner. Proceeding to robust walk for definitive search.\n", current_particle.PID);
1547 }
1548
1549 ierr = LocateParticleOrFindMigrationTarget(user, &current_particle, &final_status); CHKERRQ(ierr);
1550 }
1551 }
1552
1553 // --- PROCESS THE FINAL, DEFINITIVE STATUS ---
1554 current_particle.location_status = final_status;
1555 ierr = UpdateSwarmFields(p_idx, &current_particle, pos_p, vel_p, weights_p, cell_p, status_p,NULL,NULL,NULL); CHKERRQ(ierr);
1556
1557 if (final_status == MIGRATING_OUT) {
1558 ierr = AddToMigrationList(&migrationList, &migrationListCapacity, &local_migration_count, p_idx, current_particle.destination_rank); CHKERRQ(ierr);
1559 } else if (final_status == LOST) {
1560 local_lost_count++;
1562 } else if (final_status == ACTIVE_AND_LOCATED) {
1564 }
1565
1566 } // End of main particle processing loop
1567
1568 // Restore all the fields acquired for this pass.
1569 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&pos_p); CHKERRQ(ierr);
1570 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_VELOCITY), NULL, NULL, (void**)&vel_p); CHKERRQ(ierr);
1571 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_WEIGHT), NULL, NULL, (void**)&weights_p); CHKERRQ(ierr);
1572 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_p); CHKERRQ(ierr);
1573 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid_p); CHKERRQ(ierr);
1574 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status_p); CHKERRQ(ierr);
1575 }
1576
1577 // --- STAGE 3: ACTION & MPI COMMUNICATION ---
1578 LOG_ALLOW(LOCAL, LOG_INFO, "[Rank %d] Pass %d: Identified %d particles to migrate out.\n", rank, passes, local_migration_count);
1579
1580 // --- STAGE 3: SYNCHRONIZE AND DECIDE ---
1581 // FIRST, determine if any rank wants to migrate. This call is safe because
1582 // all ranks have finished their local work and can participate.
1583 ierr = MPI_Allreduce(&local_migration_count, &global_migrations_this_pass, 1, MPIU_INT, MPI_SUM, PETSC_COMM_WORLD); CHKERRQ(ierr);
1584
1585 total_migrated_this_timestep += global_migrations_this_pass;
1586
1587 if(global_migrations_this_pass > 0 ){
1588
1589 LOG_ALLOW(GLOBAL, LOG_INFO, "Pass %d: Migrating %d particles globally.\n", passes, global_migrations_this_pass);
1590
1591 ierr = SetMigrationRanks(user, migrationList, local_migration_count); CHKERRQ(ierr);
1592 ierr = PerformMigration(user); CHKERRQ(ierr);
1593
1594 // --- STAGE 4: POST-MIGRATION RESET ---
1595 // Identify newly arrived particles and flag them with NEEDS_LOCATION so they are
1596 // processed in the next pass. This uses the snapshot taken in STAGE 2.
1597 ierr = FlagNewcomersForLocation(user->swarm, nlocal_before, pids_before_snapshot); CHKERRQ(ierr);
1598 }
1599 // --- STAGE 5: LOOP SYNCHRONIZATION AND CLEANUP ---
1600
1601 ierr = PetscFree(pids_before_snapshot);
1602 ierr = PetscFree(migrationList);
1603
1604 LOG_ALLOW(GLOBAL, LOG_INFO, "End of pass %d. Total particles migrated globally: %d.\n", passes, global_migrations_this_pass);
1605
1606 } while (global_migrations_this_pass > 0 && passes < MAX_MIGRATION_PASSES);
1607
1608 // --- FINAL CHECKS ---
1609 if (passes >= MAX_MIGRATION_PASSES) {
1610 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_CONV_FAILED, "Particle migration failed to converge after %d passes. Check for particles oscillating between ranks.", MAX_MIGRATION_PASSES);
1611 }
1612
1613 user->simCtx->particlesMigratedLastStep = total_migrated_this_timestep;
1614 user->simCtx->migrationPassesLastStep = passes;
1615 user->simCtx->searchMetrics.maxParticlePassDepth = PetscMax(user->simCtx->searchMetrics.maxParticlePassDepth, (PetscInt64)passes);
1617
1618 LOG_ALLOW(GLOBAL, LOG_INFO, "Particle Location completed in %d passes.\n", passes);
1619
1621 PetscFunctionReturn(0);
1622}
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:1953
PetscErrorCode ResetSearchMetrics(SimCtx *simCtx)
Resets the aggregate per-timestep search instrumentation counters.
Definition logging.c:3214
PetscInt64 searchLocatedCount
Definition variables.h:267
PetscInt64 searchLostCount
Definition variables.h:268
PetscInt cell[3]
Definition variables.h:210
ParticleLocationStatus
Defines the state of a particle with respect to its location and migration status during the iterativ...
Definition variables.h:163
@ ACTIVE_AND_LOCATED
Definition variables.h:165
@ MIGRATING_OUT
Definition variables.h:166
PetscInt64 searchPopulation
Definition variables.h:266
PetscInt currentSettlementPass
Definition variables.h:278
PetscMPIInt destination_rank
Definition variables.h:215
PetscInt64 bboxGuessFallbackCount
Definition variables.h:276
ParticleLocationStatus location_status
Definition variables.h:214
PetscInt64 bboxGuessSuccessCount
Definition variables.h:275
PetscInt64 maxParticlePassDepth
Definition variables.h:277
PetscInt particlesMigratedLastStep
Definition variables.h:1002
SearchMetricsState searchMetrics
Definition variables.h:1005
PetscInt migrationPassesLastStep
Definition variables.h:1001
PetscInt64 PID
Definition variables.h:209
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 1633 of file ParticleMotion.c.

1634{
1635 PetscErrorCode ierr;
1636 PetscInt n_local;
1637 PetscInt *status_p;
1638
1639 PetscFunctionBeginUser;
1640
1642
1643 ierr = DMSwarmGetLocalSize(user->swarm, &n_local); CHKERRQ(ierr);
1644
1645 if (n_local > 0) {
1646 // Get write access to the status field
1647 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status_p); CHKERRQ(ierr);
1648
1649 for (PetscInt p = 0; p < n_local; ++p) {
1650 // Only reset particles that are considered settled. This is a small optimization
1651 // to avoid changing the status of a LOST particle, though resetting all would also be fine.
1652 if (status_p[p] == ACTIVE_AND_LOCATED) {
1653 status_p[p] = NEEDS_LOCATION;
1654 }
1655 }
1656
1657 // Restore the field
1658 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status_p); CHKERRQ(ierr);
1659 }
1660
1661
1663 PetscFunctionReturn(0);
1664}
Here is the call graph for this function:
Here is the caller graph for this function: