PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
Macros | Functions
ParticleMotion.c File Reference
#include "ParticleMotion.h"
#include "particle_field_catalog.h"
Include dependency graph for ParticleMotion.c:

Go to the source code of this file.

Macros

#define ERROR_MSG_BUFFER_SIZE   256
 
#define __FUNCT__   "GenerateGaussianNoise"
 
#define __FUNCT__   "CalculateBrownianDisplacement"
 
#define __FUNCT__   "UpdateParticlePosition"
 
#define __FUNCT__   "UpdateAllParticlePositions"
 
#define __FUNCT__   "CheckAndRemoveOutOfBoundsParticles"
 
#define __FUNCT__   "CheckAndRemoveLostParticles"
 
#define __FUNCT__   "SetMigrationRanks"
 
#define __FUNCT__   "PerformMigration"
 
#define __FUNCT__   "CalculateParticleCountPerCell"
 
#define __FUNCT__   "ResizeSwarmGlobally"
 
#define __FUNCT__   "PreCheckAndResizeSwarm"
 
#define __FUNCT__   "ReinitializeParticlesOnInletSurface"
 
#define __FUNCT__   "GetLocalPIDSnapshot"
 
#define __FUNCT__   "AddToMigrationList"
 
#define __FUNCT__   "FlagNewComersForLocation"
 
#define __FUNCT__   "MigrateRestartParticlesUsingCellID"
 
#define __FUNCT__   "GuessParticleOwnerWithBBox"
 
#define __FUNCT__   "LocateAllParticlesInGrid"
 
#define __FUNCT__   "ResetAllParticleStatuses"
 

Functions

PetscErrorCode GenerateGaussianNoise (PetscRandom rnd, PetscReal *n1, PetscReal *n2)
 Internal helper implementation: GenerateGaussianNoise().
 
PetscErrorCode CalculateBrownianDisplacement (UserCtx *user, PetscReal diff_eff, Cmpnts *displacement)
 Internal helper implementation: CalculateBrownianDisplacement().
 
PetscErrorCode UpdateParticlePosition (UserCtx *user, Particle *particle)
 Internal helper implementation: UpdateParticlePosition().
 
PetscErrorCode UpdateAllParticlePositions (UserCtx *user)
 Internal helper implementation: UpdateAllParticlePositions().
 
static PetscBool IsParticleInBox (const BoundingBox *bbox, const Cmpnts *pos)
 Test whether a particle position lies within an axis-aligned bounding box.
 
PetscErrorCode CheckAndRemoveOutOfBoundsParticles (UserCtx *user, PetscInt *removedCountLocal, PetscInt *removedCountGlobal, const BoundingBox *bboxlist)
 Internal helper implementation: CheckAndRemoveOutOfBoundsParticles().
 
PetscErrorCode CheckAndRemoveLostParticles (UserCtx *user, PetscInt *removedCountLocal, PetscInt *removedCountGlobal)
 Internal helper implementation: CheckAndRemoveLostParticles().
 
PetscErrorCode SetMigrationRanks (UserCtx *user, const MigrationInfo *migrationList, PetscInt migrationCount)
 Internal helper implementation: SetMigrationRanks().
 
PetscErrorCode PerformMigration (UserCtx *user)
 Implementation of PerformMigration().
 
PetscErrorCode CalculateParticleCountPerCell (UserCtx *user)
 Implementation of CalculateParticleCountPerCell().
 
PetscErrorCode ResizeSwarmGlobally (DM swarm, PetscInt N_target)
 Implementation of ResizeSwarmGlobally().
 
PetscErrorCode PreCheckAndResizeSwarm (UserCtx *user, PetscInt ti, const char *ext)
 Internal helper implementation: PreCheckAndResizeSwarm().
 
PetscErrorCode ReinitializeParticlesOnInletSurface (UserCtx *user, PetscReal currentTime, PetscInt step)
 Internal helper implementation: ReinitializeParticlesOnInletSurface().
 
PetscErrorCode GetLocalPIDSnapshot (const PetscInt64 pid_field[], PetscInt n_local, PetscInt64 **pids_snapshot_out)
 Internal helper implementation: GetLocalPIDSnapshot().
 
PetscErrorCode AddToMigrationList (MigrationInfo **migration_list_p, PetscInt *capacity_p, PetscInt *count_p, PetscInt particle_local_idx, PetscMPIInt destination_rank)
 Internal helper implementation: AddToMigrationList().
 
PetscErrorCode FlagNewcomersForLocation (DM swarm, PetscInt n_local_before, const PetscInt64 pids_before[])
 Internal helper implementation: FlagNewcomersForLocation().
 
PetscErrorCode MigrateRestartParticlesUsingCellID (UserCtx *user)
 Internal helper implementation: MigrateRestartParticlesUsingCellID().
 
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 LocateAllParticlesInGrid (UserCtx *user, BoundingBox *bboxlist)
 Implementation of LocateAllParticlesInGrid().
 
PetscErrorCode ResetAllParticleStatuses (UserCtx *user)
 Implementation of ResetAllParticleStatuses().
 

Macro Definition Documentation

◆ ERROR_MSG_BUFFER_SIZE

#define ERROR_MSG_BUFFER_SIZE   256

Definition at line 8 of file ParticleMotion.c.

◆ __FUNCT__ [1/19]

#define __FUNCT__   "GenerateGaussianNoise"

Definition at line 12 of file ParticleMotion.c.

◆ __FUNCT__ [2/19]

#define __FUNCT__   "CalculateBrownianDisplacement"

Definition at line 12 of file ParticleMotion.c.

◆ __FUNCT__ [3/19]

#define __FUNCT__   "UpdateParticlePosition"

Definition at line 12 of file ParticleMotion.c.

◆ __FUNCT__ [4/19]

#define __FUNCT__   "UpdateAllParticlePositions"

Definition at line 12 of file ParticleMotion.c.

◆ __FUNCT__ [5/19]

#define __FUNCT__   "CheckAndRemoveOutOfBoundsParticles"

Definition at line 12 of file ParticleMotion.c.

◆ __FUNCT__ [6/19]

#define __FUNCT__   "CheckAndRemoveLostParticles"

Definition at line 12 of file ParticleMotion.c.

◆ __FUNCT__ [7/19]

#define __FUNCT__   "SetMigrationRanks"

Definition at line 12 of file ParticleMotion.c.

◆ __FUNCT__ [8/19]

#define __FUNCT__   "PerformMigration"

Definition at line 12 of file ParticleMotion.c.

◆ __FUNCT__ [9/19]

#define __FUNCT__   "CalculateParticleCountPerCell"

Definition at line 12 of file ParticleMotion.c.

◆ __FUNCT__ [10/19]

#define __FUNCT__   "ResizeSwarmGlobally"

Definition at line 12 of file ParticleMotion.c.

◆ __FUNCT__ [11/19]

#define __FUNCT__   "PreCheckAndResizeSwarm"

Definition at line 12 of file ParticleMotion.c.

◆ __FUNCT__ [12/19]

#define __FUNCT__   "ReinitializeParticlesOnInletSurface"

Definition at line 12 of file ParticleMotion.c.

◆ __FUNCT__ [13/19]

#define __FUNCT__   "GetLocalPIDSnapshot"

Definition at line 12 of file ParticleMotion.c.

◆ __FUNCT__ [14/19]

#define __FUNCT__   "AddToMigrationList"

Definition at line 12 of file ParticleMotion.c.

◆ __FUNCT__ [15/19]

#define __FUNCT__   "FlagNewComersForLocation"

Definition at line 12 of file ParticleMotion.c.

◆ __FUNCT__ [16/19]

#define __FUNCT__   "MigrateRestartParticlesUsingCellID"

Definition at line 12 of file ParticleMotion.c.

◆ __FUNCT__ [17/19]

#define __FUNCT__   "GuessParticleOwnerWithBBox"

Definition at line 12 of file ParticleMotion.c.

◆ __FUNCT__ [18/19]

#define __FUNCT__   "LocateAllParticlesInGrid"

Definition at line 12 of file ParticleMotion.c.

◆ __FUNCT__ [19/19]

#define __FUNCT__   "ResetAllParticleStatuses"

Definition at line 12 of file ParticleMotion.c.

Function Documentation

◆ GenerateGaussianNoise()

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

Internal helper implementation: GenerateGaussianNoise().

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 
)

Internal helper implementation: CalculateBrownianDisplacement().

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

Local to this translation unit.

Definition at line 57 of file ParticleMotion.c.

58{
59 PetscErrorCode ierr;
60 PetscReal dt = user->simCtx->dt;
61 PetscReal sigma;
62 PetscReal n_x, n_y, n_z, gaussian_dummy;
63
64 PetscFunctionBeginUser;
65
66 // 1. Initialize output to zero for safety
67 displacement->x = 0.0;
68 displacement->y = 0.0;
69 displacement->z = 0.0;
70
71 // 2. Physical check: Diffusivity cannot be negative.
72 // If 0, there is no Brownian motion.
73 if (diff_eff <= 1.0e-12) {
74 PetscFunctionReturn(0);
75 }
76
77 // 3. Calculate the Scaling Factor (Standard Deviation)
78 // Formula: sigma = sqrt(2 * D * dt)
79 // Note: dt is inside the root because variance scales linearly with time.
80 sigma = PetscSqrtReal(2.0 * diff_eff * dt);
81
82 // 4. Generate 3 Independent Gaussian Random Numbers
83 // GenerateGaussianNoise produces 2 numbers at a time. We call it twice.
84
85 // Get noise for X and Y
86 ierr = GenerateGaussianNoise(user->simCtx->BrownianMotionRNG, &n_x, &n_y); CHKERRQ(ierr);
87
88 // Get noise for Z (second sample is intentionally discarded here).
89 ierr = GenerateGaussianNoise(user->simCtx->BrownianMotionRNG, &n_z, &gaussian_dummy); CHKERRQ(ierr);
90
91 // 5. Calculate final stochastic displacement
92 displacement->x = sigma * n_x;
93 displacement->y = sigma * n_y;
94 displacement->z = sigma * n_z;
95
96 PetscFunctionReturn(0);
97}
PetscErrorCode GenerateGaussianNoise(PetscRandom rnd, PetscReal *n1, PetscReal *n2)
Internal helper implementation: GenerateGaussianNoise().
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:909
PetscReal dt
Definition variables.h:710
PetscScalar x
Definition variables.h:103
PetscScalar z
Definition variables.h:103
PetscRandom BrownianMotionRNG
Definition variables.h:841
PetscScalar y
Definition variables.h:103
Here is the call graph for this function:
Here is the caller graph for this function:

◆ UpdateParticlePosition()

PetscErrorCode UpdateParticlePosition ( UserCtx user,
Particle particle 
)

Internal helper implementation: UpdateParticlePosition().

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

Local to this translation unit.

Definition at line 105 of file ParticleMotion.c.

106{
107 PetscFunctionBeginUser; // PETSc macro for error/stack tracing
109
110 PetscErrorCode ierr;
111 PetscReal dt = user->simCtx->dt;
112 Cmpnts brownian_disp;
113
114 // 2. Calculate the stochastic kick
115 ierr = CalculateBrownianDisplacement(user,particle->diffusivity, &brownian_disp); CHKERRQ(ierr);
116
117 // --- Update Position ---
118 // X_new = X_old + ((U_convection + U_diffusivitygradient) * dt) + dX_brownian
119
120 particle->loc.x += ((particle->vel.x + particle->diffusivitygradient.x) * dt) + brownian_disp.x;
121 particle->loc.y += ((particle->vel.y + particle->diffusivitygradient.y) * dt) + brownian_disp.y;
122 particle->loc.z += ((particle->vel.z + particle->diffusivitygradient.z) * dt) + brownian_disp.z;
123
125 PetscFunctionReturn(0);
126}
PetscErrorCode CalculateBrownianDisplacement(UserCtx *user, PetscReal diff_eff, Cmpnts *displacement)
Internal helper implementation: CalculateBrownianDisplacement().
#define PROFILE_FUNCTION_END
Marks the end of a profiled code block.
Definition logging.h:859
#define PROFILE_FUNCTION_BEGIN
Marks the beginning of a profiled code block (typically a function).
Definition logging.h:850
Cmpnts vel
Definition variables.h:186
Cmpnts diffusivitygradient
Definition variables.h:191
Cmpnts loc
Definition variables.h:185
PetscReal diffusivity
Definition variables.h:190
A 3D point or vector with PetscScalar components.
Definition variables.h:102
Here is the call graph for this function:
Here is the caller graph for this function:

◆ UpdateAllParticlePositions()

PetscErrorCode UpdateAllParticlePositions ( UserCtx user)

Internal helper implementation: UpdateAllParticlePositions().

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

Local to this translation unit.

Definition at line 134 of file ParticleMotion.c.

135{
136 PetscErrorCode ierr;
137 DM swarm = user->swarm;
138 PetscInt nLocal, p;
139 PetscReal *pos = NULL;
140 PetscReal *vel = NULL;
141 PetscReal *diffusivity = NULL;
142 Cmpnts *diffusivitygradient = NULL;
143 PetscReal *psi = NULL;
144 PetscReal *weights = NULL;
145 PetscInt *cell = NULL;
146 PetscInt *status = NULL;
147 PetscInt64 *pid = NULL;
148 PetscMPIInt rank;
149
150 ierr = MPI_Comm_rank(PETSC_COMM_WORLD,&rank);
151
152 PetscFunctionBeginUser; // PETSc macro for error/stack tracing
153
155
156 // 1) Get the number of local particles
157 ierr = DMSwarmGetLocalSize(swarm, &nLocal); CHKERRQ(ierr);
158 if (nLocal == 0) {
159 LOG_ALLOW(LOCAL,LOG_DEBUG,"[Rank %d] No particles to move/transport. \n",rank);
161 PetscFunctionReturn(0); // nothing to do, no fields held
162 }
163 // 2) Access the "position" and "velocity" fields
164 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&pos); CHKERRQ(ierr);
165 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_VELOCITY), NULL, NULL, (void**)&vel); CHKERRQ(ierr);
166 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_DIFFUSIVITY), NULL, NULL, (void**)&diffusivity); CHKERRQ(ierr);
167 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_DIFFUSIVITY_GRADIENT), NULL, NULL, (void**)&diffusivitygradient); CHKERRQ(ierr);
168 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PSI), NULL, NULL, (void**)&psi); CHKERRQ(ierr);
169 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_WEIGHT), NULL, NULL, (void**)&weights); CHKERRQ(ierr);
170 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell); CHKERRQ(ierr);
171 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status); CHKERRQ(ierr);
172 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid); CHKERRQ(ierr);
173
174 LOG_ALLOW(GLOBAL,LOG_DEBUG," [Rank %d] No.of Particles to update: %" PetscInt_FMT ".\n",rank,nLocal);
175
176 // 3) Loop over all local particles, updating each position by velocity * dt
177 for (p = 0; p < nLocal; p++) {
178 // update temporary particle struct
179 Particle particle;
180
181 // Unpack: Use the helper to read from swarm arrays into the particle struct
182 ierr = UnpackSwarmFields(p, pid, weights, pos, cell, vel, status, diffusivity, diffusivitygradient, psi, &particle); CHKERRQ(ierr);
183
184 // Update position based on velocity and Brownian motion
185 ierr = UpdateParticlePosition(user, &particle); CHKERRQ(ierr);
186
187 // Update swarm fields
188 ierr = UpdateSwarmFields(p, &particle, pos, vel, weights, cell, status, diffusivity, diffusivitygradient, psi); CHKERRQ(ierr);
189 }
190
191 // 4) Restore the fields
192 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&pos); CHKERRQ(ierr);
193 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_VELOCITY), NULL, NULL, (void**)&vel); CHKERRQ(ierr);
194 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_DIFFUSIVITY), NULL, NULL, (void**)&diffusivity); CHKERRQ(ierr);
195 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_DIFFUSIVITY_GRADIENT), NULL, NULL, (void**)&diffusivitygradient); CHKERRQ(ierr);
196 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PSI), NULL, NULL, (void**)&psi); CHKERRQ(ierr);
197 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_WEIGHT), NULL, NULL, (void**)&weights); CHKERRQ(ierr);
198 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell); CHKERRQ(ierr);
199 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status); CHKERRQ(ierr);
200 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid); CHKERRQ(ierr);
201
202
203 LOG_ALLOW(LOCAL,LOG_DEBUG,"Particle moved/transported successfully on Rank %d.\n",rank);
204
206
207 PetscFunctionReturn(0);
208}
PetscErrorCode UpdateParticlePosition(UserCtx *user, Particle *particle)
Internal helper implementation: UpdateParticlePosition().
PetscErrorCode UnpackSwarmFields(PetscInt i, const PetscInt64 *PIDs, const PetscReal *weights, const PetscReal *positions, const PetscInt *cellIndices, PetscReal *velocities, PetscInt *LocStatus, PetscReal *diffusivity, Cmpnts *diffusivitygradient, PetscReal *psi, Particle *particle)
Initializes a Particle struct with data from DMSwarm fields.
PetscErrorCode UpdateSwarmFields(PetscInt i, const Particle *particle, PetscReal *positions, PetscReal *velocities, PetscReal *weights, PetscInt *cellIndices, PetscInt *status, PetscReal *diffusivity, Cmpnts *diffusivitygradient, PetscReal *psi)
Updates DMSwarm data arrays from a Particle struct.
#define LOCAL
Logging scope definitions for controlling message output.
Definition logging.h:45
#define GLOBAL
Scope for global logging across all processes.
Definition logging.h:46
#define LOG_ALLOW(scope, level, fmt,...)
Logging macro that checks both the log level and whether the calling function is in the allowed-funct...
Definition logging.h:200
@ LOG_DEBUG
Detailed debugging information.
Definition logging.h:32
const char * ParticleFieldName(ParticleFieldId field_id)
Return the canonical PETSc DMSwarm name for an ID.
@ PARTICLE_FIELD_ID_LOCATION_STATUS
@ PARTICLE_FIELD_ID_WEIGHT
@ PARTICLE_FIELD_ID_POSITION
@ PARTICLE_FIELD_ID_PID
@ PARTICLE_FIELD_ID_CELL_ID
@ PARTICLE_FIELD_ID_PSI
@ PARTICLE_FIELD_ID_DIFFUSIVITY_GRADIENT
@ PARTICLE_FIELD_ID_DIFFUSIVITY
@ PARTICLE_FIELD_ID_VELOCITY
Defines a particle's core properties for Lagrangian tracking.
Definition variables.h:182
Here is the call graph for this function:
Here is the caller graph for this function:

◆ IsParticleInBox()

static PetscBool IsParticleInBox ( const BoundingBox bbox,
const Cmpnts pos 
)
inlinestatic

Test whether a particle position lies within an axis-aligned bounding box.

Definition at line 214 of file ParticleMotion.c.

214 {
215 return (pos->x >= bbox->min_coords.x && pos->x <= bbox->max_coords.x &&
216 pos->y >= bbox->min_coords.y && pos->y <= bbox->max_coords.y &&
217 pos->z >= bbox->min_coords.z && pos->z <= bbox->max_coords.z);
218}
Cmpnts max_coords
Maximum x, y, z coordinates of the bounding box.
Definition variables.h:173
Cmpnts min_coords
Minimum x, y, z coordinates of the bounding box.
Definition variables.h:172
Here is the caller graph for this function:

◆ CheckAndRemoveOutOfBoundsParticles()

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

Internal helper implementation: CheckAndRemoveOutOfBoundsParticles().

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 
)

Internal helper implementation: CheckAndRemoveLostParticles().

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

Local to this translation unit.

Definition at line 336 of file ParticleMotion.c.

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

◆ SetMigrationRanks()

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

Internal helper implementation: SetMigrationRanks().

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

Local to this translation unit.

Definition at line 440 of file ParticleMotion.c.

441{
442 PetscErrorCode ierr;
443 DM swarm = user->swarm;
444 PetscInt p_idx;
445 PetscInt *rankField = NULL; // Field storing target rank
446
447 PetscFunctionBeginUser;
449
450 // Ensure the migration rank field exists
451 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_RANK), NULL, NULL, (void **)&rankField); CHKERRQ(ierr);
452
453 // Set the target rank for migrating particles
454 for(p_idx = 0; p_idx < migrationCount; ++p_idx) {
455 rankField[migrationList[p_idx].local_index] = migrationList[p_idx].target_rank;
456 }
457
458 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_RANK), NULL, NULL, (void **)&rankField); CHKERRQ(ierr);
459
461 PetscFunctionReturn(0);
462}
@ PARTICLE_FIELD_ID_RANK
PetscInt local_index
Definition variables.h:210
Here is the call graph for this function:
Here is the caller graph for this function:

◆ PerformMigration()

PetscErrorCode PerformMigration ( UserCtx user)

Implementation of PerformMigration().

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

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

See also
PerformMigration()

Definition at line 473 of file ParticleMotion.c.

474{
475 PetscErrorCode ierr;
476 DM swarm = user->swarm;
477 PetscMPIInt rank;
478
479 PetscFunctionBeginUser;
481 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
482 LOG_ALLOW(GLOBAL, LOG_INFO, "Rank %d: Starting DMSwarmMigrate...\n", rank);
483
484 // Perform the migration - PETSC_TRUE removes particles that fail to land
485 // in a valid cell on the target rank (or were marked with an invalid rank).
486 ierr = DMSwarmMigrate(swarm, PETSC_TRUE); CHKERRQ(ierr);
487
488 LOG_ALLOW(GLOBAL, LOG_INFO, "Rank %d: Migration complete.\n", rank);
490 PetscFunctionReturn(0);
491}
Here is the caller graph for this function:

◆ CalculateParticleCountPerCell()

PetscErrorCode CalculateParticleCountPerCell ( UserCtx user)

Implementation of CalculateParticleCountPerCell().

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

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

See also
CalculateParticleCountPerCell()

Definition at line 505 of file ParticleMotion.c.

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

◆ ResizeSwarmGlobally()

PetscErrorCode ResizeSwarmGlobally ( DM  swarm,
PetscInt  N_target 
)

Implementation of ResizeSwarmGlobally().

Resizes a swarm collectively to a target global particle count.

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

See also
ResizeSwarmGlobally()

Definition at line 674 of file ParticleMotion.c.

675{
676 PetscErrorCode ierr;
677 PetscInt N_current, N_final, nlocal_current, nlocal_target;
678 PetscMPIInt rank, size;
679 MPI_Comm comm;
680
681 PetscFunctionBeginUser;
683 ierr = PetscObjectGetComm((PetscObject)swarm, &comm); CHKERRQ(ierr);
684 ierr = MPI_Comm_rank(comm, &rank); CHKERRQ(ierr);
685 ierr = MPI_Comm_size(comm, &size); CHKERRQ(ierr);
686 PetscCheck(N_target >= 0, comm, PETSC_ERR_ARG_OUTOFRANGE,
687 "Target swarm size must be nonnegative; got %" PetscInt_FMT ".", N_target);
688 ierr = DMSwarmGetSize(swarm, &N_current); CHKERRQ(ierr);
689 ierr = DMSwarmGetLocalSize(swarm, &nlocal_current); CHKERRQ(ierr);
690 nlocal_target = N_target / size + (rank < N_target % size ? 1 : 0);
691
692 if (nlocal_current != nlocal_target) {
694 "Rank %d: resizing local swarm share from %" PetscInt_FMT
695 " to %" PetscInt_FMT ".\n",
696 rank, nlocal_current, nlocal_target);
697 ierr = DMSwarmSetLocalSizes(swarm, nlocal_target, -1); CHKERRQ(ierr);
698 }
699
700 // Verify final size
701 ierr = DMSwarmGetSize(swarm, &N_final); CHKERRQ(ierr);
702 if (N_final != N_target) {
703 SETERRQ(comm, PETSC_ERR_PLIB,
704 "Failed to resize swarm: expected %" PetscInt_FMT
705 " particles, got %" PetscInt_FMT, N_target, N_final);
706 }
708 "Swarm resized from %" PetscInt_FMT " to %" PetscInt_FMT " particles.\n",
709 N_current, N_final);
711 PetscFunctionReturn(0);
712}
Here is the caller graph for this function:

◆ PreCheckAndResizeSwarm()

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

Internal helper implementation: PreCheckAndResizeSwarm().

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

Local to this translation unit.

Definition at line 720 of file ParticleMotion.c.

723{
724 PetscErrorCode ierr;
725 PetscInt N_file = 0;
726 PetscInt N_current = 0;
727
728 PetscFunctionBeginUser;
730 (void)ext;
731 ierr = ReadCheckpointParticleCount(user, ti, &N_file); CHKERRQ(ierr);
733 "Committed checkpoint step %d records %d particles.\n", ti, N_file);
734
735
736 // --- Now all ranks have the correct N_file, compare and resize if needed ---
737 ierr = DMSwarmGetSize(user->swarm, &N_current); CHKERRQ(ierr);
738
739 if (N_file != N_current) {
740 LOG_ALLOW(GLOBAL, LOG_INFO, "Swarm size %d differs from file size %d. Resizing swarm globally.\n", N_current, N_file);
741 ierr = ResizeSwarmGlobally(user->swarm, N_file); CHKERRQ(ierr);
742 } else {
743 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Swarm size (%d) already matches file size. No resize needed.\n", N_current);
744 }
745
746 // Also update the context
747 user->simCtx->np = N_file;
748
750 PetscFunctionReturn(0);
751}
PetscErrorCode ResizeSwarmGlobally(DM swarm, PetscInt N_target)
Implementation of ResizeSwarmGlobally().
PetscErrorCode ReadCheckpointParticleCount(UserCtx *user, PetscInt ti, PetscInt *particle_count)
Read the particle count from a validated committed checkpoint.
Definition io.c:1914
PetscInt np
Definition variables.h:827
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ReinitializeParticlesOnInletSurface()

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

Internal helper implementation: ReinitializeParticlesOnInletSurface().

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

Local to this translation unit.

Definition at line 760 of file ParticleMotion.c.

761{
762 PetscErrorCode ierr;
763 PetscMPIInt rank; // MPI rank of the current process
764 DM swarm = user->swarm; // The particle swarm DM
765 PetscReal *positions_field = NULL; // Pointer to swarm field for physical positions
766 PetscInt64 *particleIDs = NULL; // Pointer to swarm field for Particle IDs (for logging)
767 PetscInt *cell_ID_field = NULL; // Pointer to swarm field for Cell IDs (for resetting after migration)
768 const Cmpnts ***coor_nodes_local_array; // Read-only access to local node coordinates
769 Vec Coor_local; // Local vector for node coordinates
770 DMDALocalInfo info; // Local grid information (node-based) from user->da
771 PetscInt xs_gnode_rank, ys_gnode_rank, zs_gnode_rank; // Local starting node indices (incl. ghosts) of rank's DA
772 PetscInt IM_nodes_global, JM_nodes_global, KM_nodes_global; // Global node counts
773
774 PetscRandom rand_logic_reinit_i, rand_logic_reinit_j, rand_logic_reinit_k; // RNGs for re-placement
775 PetscInt nlocal_current; // Number of particles currently on this rank
776 PetscInt particles_actually_reinitialized_count = 0; // Counter for logging
777 PetscBool can_this_rank_service_inlet = PETSC_FALSE; // Flag
778
779 PetscFunctionBeginUser;
780
782
783 // This function is only relevant for surface initialization mode and if an inlet face is defined.
784 if ((user->simCtx->ParticleInitialization != 0 && user->simCtx->ParticleInitialization !=3) || !user->inletFaceDefined) {
786 PetscFunctionReturn(0);
787 }
788
789 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
790 ierr = DMSwarmGetLocalSize(swarm, &nlocal_current); CHKERRQ(ierr);
791
792 // If no particles on this rank, nothing to do.
793 if (nlocal_current == 0) {
794 LOG_ALLOW(LOCAL, LOG_DEBUG, "[T=%.4f, Step=%d] Rank %d has no local particles to re-initialize on inlet.\n", currentTime, step, rank);
796 PetscFunctionReturn(0);
797 }
798
799 // Get DMDA information for the node-centered coordinate grid (user->da)
800 ierr = DMDAGetLocalInfo(user->da, &info); CHKERRQ(ierr);
801 ierr = DMDAGetInfo(user->da, NULL, &IM_nodes_global, &JM_nodes_global, &KM_nodes_global, NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); CHKERRQ(ierr);
802 ierr = DMDAGetCorners(user->da, &xs_gnode_rank, &ys_gnode_rank, &zs_gnode_rank, NULL, NULL, NULL); CHKERRQ(ierr);
803
804 // Modification to IM_nodes_global etc. to account for 1-cell halo in each direction.
805 IM_nodes_global -= 1; JM_nodes_global -= 1; KM_nodes_global -= 1;
806
807 const PetscInt IM_cells_global = IM_nodes_global > 0 ? IM_nodes_global - 1 : 0;
808 const PetscInt JM_cells_global = JM_nodes_global > 0 ? JM_nodes_global - 1 : 0;
809 const PetscInt KM_cells_global = KM_nodes_global > 0 ? KM_nodes_global - 1 : 0;
810
811
812
813 // Check if this rank is responsible for (part of) the designated inlet surface
814 ierr = CanRankServiceInletFace(user, &info, IM_nodes_global, JM_nodes_global, KM_nodes_global, &can_this_rank_service_inlet); CHKERRQ(ierr);
815
816 // Get coordinate array and swarm fields for modification
817 ierr = DMGetCoordinatesLocal(user->da, &Coor_local); CHKERRQ(ierr);
818 ierr = DMDAVecGetArrayRead(user->fda, Coor_local, (void*)&coor_nodes_local_array); CHKERRQ(ierr);
819 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&positions_field); CHKERRQ(ierr);
820 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&particleIDs); CHKERRQ(ierr); // For logging
821 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_ID_field); CHKERRQ(ierr);
822
823 if (!can_this_rank_service_inlet) {
824 LOG_ALLOW(LOCAL, LOG_DEBUG, "[T=%.4f, Step=%d] Rank %d cannot service inlet face %s. Skipping re-initialization of %d particles.\n", currentTime, step, rank, BCFaceToString(user->identifiedInletBCFace), nlocal_current);
825
826 // FALLBACK ACTION: Reset position fields to Inlet center for migration and cell ID to -1 for safety.
827 LOG_ALLOW(LOCAL, LOG_DEBUG, "[T=%.4f, Step=%d] Rank %d is resetting %d local particles to inlet center (%.6f, %.6f, %.6f) for migration.\n", currentTime, step, rank, nlocal_current, user->simCtx->CMx_c, user->simCtx->CMy_c, user->simCtx->CMz_c);
828
829 for(PetscInt p = 0; p < nlocal_current; p++){
830 positions_field[3*p+0] = user->simCtx->CMx_c;
831 positions_field[3*p+1] = user->simCtx->CMy_c;
832 positions_field[3*p+2] = user->simCtx->CMz_c;
833
834 cell_ID_field[3*p+0] = -1;
835 cell_ID_field[3*p+1] = -1;
836 cell_ID_field[3*p+2] = -1;
837 }
838
839 // Cleanup: restore swarm fields/coordinate array
840 ierr = DMDAVecRestoreArrayRead(user->fda, Coor_local, (void*)&coor_nodes_local_array); CHKERRQ(ierr);
841 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&positions_field); CHKERRQ(ierr);
842 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&particleIDs); CHKERRQ(ierr); // For logging
843 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_ID_field); CHKERRQ(ierr);
845 PetscFunctionReturn(0);
846 }
847
848 LOG_ALLOW(GLOBAL, LOG_INFO, "[T=%.4f, Step=%d] Rank %d is on inlet face %s. Attempting to re-place %d local particles.\n", currentTime, step, rank, BCFaceToString(user->identifiedInletBCFace), nlocal_current);
849
850 // Initialize fresh RNGs for this re-placement to ensure good distribution
851 ierr = InitializeLogicalSpaceRNGs(&rand_logic_reinit_i, &rand_logic_reinit_j, &rand_logic_reinit_k); CHKERRQ(ierr);
852 // Optional: Seed RNGs for deterministic behavior if required, e.g., based on rank and step.
853 // PetscRandomSetSeed(rand_logic_i, (unsigned long)rank*1000 + step + 100); PetscRandomSeed(rand_logic_i); // Example
854
855 // Loop over all particles currently local to this rank
856 for (PetscInt p = 0; p < nlocal_current; p++) {
857 PetscInt ci_metric_lnode, cj_metric_lnode, ck_metric_lnode; // Local node indices (of rank's DA patch) for cell origin
858 PetscReal xi_metric_logic, eta_metric_logic, zta_metric_logic; // Intra-cell logical coordinates
859 Cmpnts phys_coords = {0.0,0.0,0.0}; // To store newly calculated physical coordinates
860 PetscBool particle_was_placed = PETSC_FALSE;
861
863 // Get random cell on this rank's portion of the inlet and random logical coords within it
864 ierr = GetRandomCellAndLogicalCoordsOnInletFace(user, &info, xs_gnode_rank, ys_gnode_rank, zs_gnode_rank,
865 IM_nodes_global, JM_nodes_global, KM_nodes_global,
866 &rand_logic_reinit_i, &rand_logic_reinit_j, &rand_logic_reinit_k,
867 &ci_metric_lnode, &cj_metric_lnode, &ck_metric_lnode,
868 &xi_metric_logic, &eta_metric_logic, &zta_metric_logic); CHKERRQ(ierr);
869
870 // Convert these logical coordinates to physical coordinates
871
872 ierr = MetricLogicalToPhysical(user, coor_nodes_local_array,
873 ci_metric_lnode, cj_metric_lnode, ck_metric_lnode,
874 xi_metric_logic, eta_metric_logic, zta_metric_logic,
875 &phys_coords); CHKERRQ(ierr);
876
877 // Update the particle's position in the swarm fields
878 positions_field[3*p+0] = phys_coords.x;
879 positions_field[3*p+1] = phys_coords.y;
880 positions_field[3*p+2] = phys_coords.z;
881 particle_was_placed = PETSC_TRUE;
882
884 PetscBool placement_flag = PETSC_FALSE;
885 ierr = GetDeterministicFaceGridLocation(user, &info, xs_gnode_rank, ys_gnode_rank, zs_gnode_rank,
886 IM_cells_global, JM_cells_global, KM_cells_global,
887 particleIDs[p],
888 &ci_metric_lnode, &cj_metric_lnode, &ck_metric_lnode,
889 &xi_metric_logic, &eta_metric_logic, &zta_metric_logic,&placement_flag); CHKERRQ(ierr);
890
891
892 if(placement_flag){
893 // Convert these logical coordinates to physical coordinates
894 ierr = MetricLogicalToPhysical(user, coor_nodes_local_array,
895 ci_metric_lnode, cj_metric_lnode, ck_metric_lnode,
896 xi_metric_logic, eta_metric_logic, zta_metric_logic,
897 &phys_coords); CHKERRQ(ierr);
898
899 // Update the particle's position in the swarm fields
900 positions_field[3*p+0] = phys_coords.x;
901 positions_field[3*p+1] = phys_coords.y;
902 positions_field[3*p+2] = phys_coords.z;
903 particle_was_placed = PETSC_TRUE;
904 } else{
905 // Deterministic placement failed (particle migrated to rank where formula says it doesn't belong)
906 // Fall back to random placement on this rank's portion of inlet surface
907 LOG_ALLOW(GLOBAL, LOG_WARNING, "Rank %d: Particle PID %ld deterministic placement failed (belongs to different rank). Falling back to random placement.\n", rank, particleIDs[p]);
908
909 ierr = GetRandomCellAndLogicalCoordsOnInletFace(user, &info, xs_gnode_rank, ys_gnode_rank, zs_gnode_rank,
910 IM_nodes_global, JM_nodes_global, KM_nodes_global,
911 &rand_logic_reinit_i, &rand_logic_reinit_j, &rand_logic_reinit_k,
912 &ci_metric_lnode, &cj_metric_lnode, &ck_metric_lnode,
913 &xi_metric_logic, &eta_metric_logic, &zta_metric_logic); CHKERRQ(ierr);
914
915 // Convert to physical coordinates
916 ierr = MetricLogicalToPhysical(user, coor_nodes_local_array,
917 ci_metric_lnode, cj_metric_lnode, ck_metric_lnode,
918 xi_metric_logic, eta_metric_logic, zta_metric_logic,
919 &phys_coords); CHKERRQ(ierr);
920
921 // Update particle position
922 positions_field[3*p+0] = phys_coords.x;
923 positions_field[3*p+1] = phys_coords.y;
924 positions_field[3*p+2] = phys_coords.z;
925 particle_was_placed = PETSC_TRUE;
926 }
927
928 } else{
929 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "ReinitializeParticlesOnInletSurface only supports ParticleInitialization modes 0 and 3.");
930 }
931
932 if(particle_was_placed){
933 particles_actually_reinitialized_count++;
934
935 cell_ID_field[3*p+0] = -1;
936 cell_ID_field[3*p+1] = -1;
937 cell_ID_field[3*p+2] = -1;
938
939 LOG_LOOP_ALLOW(LOCAL, LOG_VERBOSE, p, (nlocal_current > 20 ? nlocal_current/10 : 1), // Sampled logging
940 "Rank %d: PID %ld (idx %ld) RE-PLACED. CellOriginNode(locDAIdx):(%d,%d,%d). LogicCoords: (%.2e,%.2f,%.2f). PhysCoords: (%.6f,%.6f,%.6f).\n",
941 rank, particleIDs[p], (long)p,
942 ci_metric_lnode, cj_metric_lnode, ck_metric_lnode,
943 xi_metric_logic, eta_metric_logic, zta_metric_logic,
944 phys_coords.x, phys_coords.y, phys_coords.z);
945 }
946 }
947
948 // Logging summary of re-initialization
949 if (particles_actually_reinitialized_count > 0) {
950 LOG_ALLOW(GLOBAL, LOG_INFO, "[T=%.4f, Step=%d] Rank %d (on inlet face %d) successfully re-initialized %d of %d local particles.\n", currentTime, step, rank, user->identifiedInletBCFace, particles_actually_reinitialized_count, nlocal_current);
951 } else if (nlocal_current > 0) { // This case should ideally not be hit if can_this_rank_service_inlet was true and particles were present.
952 LOG_ALLOW(GLOBAL, LOG_WARNING, "[T=%.4f, Step=%d] Rank %d claimed to service inlet face %d, but re-initialized 0 of %d local particles. This may indicate an issue if particles were expected to be re-placed.\n", currentTime, step, rank, user->identifiedInletBCFace, nlocal_current);
953 }
954
955 // Cleanup: Destroy RNGs and restore swarm fields/coordinate array
956 ierr = PetscRandomDestroy(&rand_logic_reinit_i); CHKERRQ(ierr);
957 ierr = PetscRandomDestroy(&rand_logic_reinit_j); CHKERRQ(ierr);
958 ierr = PetscRandomDestroy(&rand_logic_reinit_k); CHKERRQ(ierr);
959
960 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&positions_field); CHKERRQ(ierr);
961 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&particleIDs); CHKERRQ(ierr);
962 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_ID_field); CHKERRQ(ierr);
963 ierr = DMDAVecRestoreArrayRead(user->fda, Coor_local, (void*)&coor_nodes_local_array); CHKERRQ(ierr);
964
965
967 PetscFunctionReturn(0);
968}
PetscErrorCode GetRandomCellAndLogicalCoordsOnInletFace(UserCtx *user, const DMDALocalInfo *info, PetscInt xs_gnode_rank, PetscInt ys_gnode_rank, PetscInt zs_gnode_rank, PetscInt IM_nodes_global, PetscInt JM_nodes_global, PetscInt KM_nodes_global, PetscRandom *rand_logic_i_ptr, PetscRandom *rand_logic_j_ptr, PetscRandom *rand_logic_k_ptr, PetscInt *ci_metric_lnode_out, PetscInt *cj_metric_lnode_out, PetscInt *ck_metric_lnode_out, PetscReal *xi_metric_logic_out, PetscReal *eta_metric_logic_out, PetscReal *zta_metric_logic_out)
Assuming the current rank services the inlet face, this function selects a random cell (owned by this...
Definition Boundaries.c:400
PetscErrorCode CanRankServiceInletFace(UserCtx *user, const DMDALocalInfo *info, PetscInt IM_nodes_global, PetscInt JM_nodes_global, PetscInt KM_nodes_global, PetscBool *can_service_inlet_out)
Determines if the current MPI rank owns any part of the globally defined inlet face,...
Definition Boundaries.c:11
PetscErrorCode GetDeterministicFaceGridLocation(UserCtx *user, const DMDALocalInfo *info, PetscInt xs_gnode_rank, PetscInt ys_gnode_rank, PetscInt zs_gnode_rank, PetscInt IM_cells_global, PetscInt JM_cells_global, PetscInt KM_cells_global, PetscInt64 particle_global_id, PetscInt *ci_metric_lnode_out, PetscInt *cj_metric_lnode_out, PetscInt *ck_metric_lnode_out, PetscReal *xi_metric_logic_out, PetscReal *eta_metric_logic_out, PetscReal *zta_metric_logic_out, PetscBool *placement_successful_out)
Places particles in a deterministic grid/raster pattern on a specified domain face.
Definition Boundaries.c:213
PetscErrorCode MetricLogicalToPhysical(UserCtx *user, const Cmpnts ***X, PetscInt i, PetscInt j, PetscInt k, PetscReal xi, PetscReal eta, PetscReal zta, Cmpnts *Xp)
Maps a logical point inside one hexahedral cell to physical space.
Definition Metric.c:76
const char * BCFaceToString(BCFace face)
Returns the canonical log token for a boundary-face enum value.
Definition logging.c:671
PetscErrorCode InitializeLogicalSpaceRNGs(PetscRandom *rand_logic_i, PetscRandom *rand_logic_j, PetscRandom *rand_logic_k)
Initializes random number generators for logical space operations [0.0, 1.0).
Definition setup.c:3241
PetscBool inletFaceDefined
Definition variables.h:932
BCFace identifiedInletBCFace
Definition variables.h:933
@ PARTICLE_INIT_SURFACE_RANDOM
Random placement on the inlet face.
Definition variables.h:552
@ PARTICLE_INIT_SURFACE_EDGES
Deterministic placement at inlet face edges.
Definition variables.h:555
PetscReal CMy_c
Definition variables.h:783
PetscReal CMz_c
Definition variables.h:783
ParticleInitializationType ParticleInitialization
Definition variables.h:831
PetscReal CMx_c
Definition variables.h:783
Here is the call graph for this function:
Here is the caller graph for this function:

◆ GetLocalPIDSnapshot()

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

Internal helper implementation: GetLocalPIDSnapshot().

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

Local to this translation unit.

Definition at line 976 of file ParticleMotion.c.

979{
980 PetscErrorCode ierr;
981 PetscMPIInt rank;
982
983 PetscFunctionBeginUser;
984
986
987 // --- 1. Input Validation ---
988 if (!pids_snapshot_out) {
989 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Output pointer pids_snapshot_out is NULL.");
990 }
991 // If n_local > 0, pid_field must not be NULL.
992 if (n_local > 0 && !pid_field) {
993 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Input pid_field pointer is NULL for n_local > 0.");
994 }
995
996 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
997 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d]: Creating PID snapshot for %d local particles.\n", rank, n_local);
998
999 // If there are no local particles, the snapshot is empty (NULL).
1000 if (n_local == 0) {
1001 *pids_snapshot_out = NULL;
1002
1004 PetscFunctionReturn(0);
1005 }
1006
1007 // --- 2. Allocate Memory for the Snapshot ---
1008 ierr = PetscMalloc1(n_local, pids_snapshot_out); CHKERRQ(ierr);
1009
1010 // --- 3. Copy Data ---
1011 // Perform a fast memory copy from the provided array to our new snapshot array.
1012 ierr = PetscMemcpy(*pids_snapshot_out, pid_field, n_local * sizeof(PetscInt64)); CHKERRQ(ierr);
1013 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d]: Copied %d PIDs.\n", rank, n_local);
1014
1015 // --- 4. Sort the Snapshot Array ---
1016 // Sorting enables fast binary search lookups later.
1017 ierr = PetscSortInt64(n_local, *pids_snapshot_out); CHKERRQ(ierr);
1018 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d]: PID snapshot sorted successfully.\n", rank);
1019
1020
1022 PetscFunctionReturn(0);
1023}
Here is the caller graph for this function:

◆ AddToMigrationList()

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

Internal helper implementation: AddToMigrationList().

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

Local to this translation unit.

Definition at line 1031 of file ParticleMotion.c.

1036{
1037 PetscErrorCode ierr;
1038 PetscMPIInt rank;
1039
1040 PetscFunctionBeginUser;
1041
1043
1044 // --- 1. Input Validation ---
1045 if (!migration_list_p || !capacity_p || !count_p) {
1046 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Null pointer provided to AddToMigrationList for list management.");
1047 }
1048
1049 // --- 2. Check if the list needs to be resized ---
1050 if (*count_p >= *capacity_p) {
1051 PetscInt old_capacity = *capacity_p;
1052 // Start with a reasonable base capacity, then double for subsequent reallocations.
1053 PetscInt new_capacity = (old_capacity == 0) ? 16 : old_capacity * 2;
1054
1055 // Use PetscRealloc for safe memory reallocation.
1056 // It handles allocating new memory, copying old data, and freeing the old block.
1057 // The first argument to PetscRealloc is the new size in BYTES.
1058 ierr = PetscRealloc(new_capacity * sizeof(MigrationInfo), migration_list_p); CHKERRQ(ierr);
1059
1060 *capacity_p = new_capacity; // Update the capacity tracker
1061
1062 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1063 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d]: Reallocated migrationList capacity from %d to %d.\n",
1064 rank, old_capacity, new_capacity);
1065 }
1066
1067 // --- 3. Add the new migration data to the list ---
1068 // Dereference the pointer-to-a-pointer to get the actual array.
1069 MigrationInfo *list = *migration_list_p;
1070
1071 list[*count_p].local_index = particle_local_idx;
1072 list[*count_p].target_rank = destination_rank;
1073
1074 // --- 4. Increment the count of items in the list ---
1075 (*count_p)++;
1076
1077
1079 PetscFunctionReturn(0);
1080}
Information needed to migrate a single particle between MPI ranks.
Definition variables.h:209
Head of a generic C-style linked list.
Definition variables.h:445
Here is the caller graph for this function:

◆ FlagNewcomersForLocation()

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

Internal helper implementation: FlagNewcomersForLocation().

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

Local to this translation unit.

Definition at line 1089 of file ParticleMotion.c.

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

◆ MigrateRestartParticlesUsingCellID()

PetscErrorCode MigrateRestartParticlesUsingCellID ( UserCtx user)

Internal helper implementation: MigrateRestartParticlesUsingCellID().

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

Local to this translation unit.

Definition at line 1183 of file ParticleMotion.c.

1184{
1185 PetscErrorCode ierr;
1186 DM swarm = user->swarm;
1187 PetscInt nlocal;
1188 PetscInt *cell_p = NULL;
1189 PetscInt64 *pid_p = NULL;
1190 PetscMPIInt rank;
1191
1192 MigrationInfo *migrationList = NULL;
1193 PetscInt local_migration_count = 0;
1194 PetscInt migrationListCapacity = 0;
1195 PetscInt global_migration_count = 0;
1196
1197 PetscFunctionBeginUser;
1199 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1200
1201 ierr = DMSwarmGetLocalSize(swarm, &nlocal); CHKERRQ(ierr);
1202 LOG_ALLOW(LOCAL, LOG_DEBUG, "Checking %d restart particles for direct migration using CellIDs.\n", nlocal);
1203
1204 if (nlocal > 0) {
1205 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_p); CHKERRQ(ierr);
1206 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid_p); CHKERRQ(ierr);
1207
1208 // Note: We do NOT need to modify the status field here.
1209 // We trust the loaded status (ACTIVE_AND_LOCATED) is correct for the destination rank.
1210
1211 for (PetscInt p_idx = 0; p_idx < nlocal; ++p_idx) {
1212 PetscInt ci = cell_p[3*p_idx + 0];
1213 PetscInt cj = cell_p[3*p_idx + 1];
1214 PetscInt ck = cell_p[3*p_idx + 2];
1215
1216 /* Skip particles with invalid Cell IDs (will be handled by LocateAllParticles) */
1217 if (ci < 0 || cj < 0 || ck < 0) {
1218 continue;
1219 }
1220
1221 PetscMPIInt owner_rank;
1222 ierr = FindOwnerOfCell(user, ci, cj, ck, &owner_rank); CHKERRQ(ierr);
1223
1224 if (owner_rank != -1 && owner_rank != rank) {
1225 /* Particle belongs to another rank - migrate it */
1226 ierr = AddToMigrationList(&migrationList, &migrationListCapacity, &local_migration_count,
1227 p_idx, owner_rank); CHKERRQ(ierr);
1228
1229 LOG_ALLOW(LOCAL, LOG_VERBOSE, "[PID %ld] Direct migration: Cell (%d,%d,%d) belongs to Rank %d (Current: %d).\n",
1230 (long)pid_p[p_idx], ci, cj, ck, owner_rank, rank);
1231 }
1232 }
1233
1234 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cell_p); CHKERRQ(ierr);
1235 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pid_p); CHKERRQ(ierr);
1236 }
1237
1238 /* Check if any rank needs to migrate particles */
1239 ierr = MPI_Allreduce(&local_migration_count, &global_migration_count, 1, MPIU_INT, MPI_SUM, PETSC_COMM_WORLD); CHKERRQ(ierr);
1240
1241 if (global_migration_count > 0) {
1242 LOG_ALLOW(GLOBAL, LOG_INFO, "Fast restart migration: Directly migrating %d particles using CellIDs.\n", global_migration_count);
1243 ierr = SetMigrationRanks(user, migrationList, local_migration_count); CHKERRQ(ierr);
1244 ierr = PerformMigration(user); CHKERRQ(ierr);
1245 /* We do NOT flag newcomers here. We trust their loaded status (ACTIVE_AND_LOCATED) */
1246 /* is valid for their destination rank. */
1247 } else {
1248 LOG_ALLOW(GLOBAL, LOG_INFO, "Fast restart migration: All particles are already on correct ranks.\n");
1249 }
1250
1251 ierr = PetscFree(migrationList); CHKERRQ(ierr);
1252
1254 PetscFunctionReturn(0);
1255}
PetscErrorCode AddToMigrationList(MigrationInfo **migration_list_p, PetscInt *capacity_p, PetscInt *count_p, PetscInt particle_local_idx, PetscMPIInt destination_rank)
Internal helper implementation: AddToMigrationList().
PetscErrorCode SetMigrationRanks(UserCtx *user, const MigrationInfo *migrationList, PetscInt migrationCount)
Internal helper implementation: SetMigrationRanks().
PetscErrorCode PerformMigration(UserCtx *user)
Implementation of PerformMigration().
PetscErrorCode FindOwnerOfCell(UserCtx *user, PetscInt i, PetscInt j, PetscInt k, PetscMPIInt *owner_rank)
Finds the MPI rank that owns a given global cell index.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ GuessParticleOwnerWithBBox()

static PetscErrorCode GuessParticleOwnerWithBBox ( UserCtx user,
const Particle particle,
const BoundingBox bboxlist,
PetscMPIInt *  guess_rank_out 
)
static

Select the rank whose gathered bounding box is the best owner candidate for a particle.

Note
Testing status: The current direct surface reaches this helper through orchestrator tests, but direction-complete immediate-neighbor coverage and the explicit "not found in any rank" path are still targeted for future bespoke tests.

Definition at line 1267 of file ParticleMotion.c.

1271{
1272 PetscErrorCode ierr;
1273 PetscMPIInt rank, size;
1274 const RankNeighbors *neighbors = &user->neighbors; // Use a direct pointer for clarity
1275 const BoundingBox *localBBox = &user->bbox;
1276
1277 PetscFunctionBeginUser;
1278
1280
1281 // --- 1. Input Validation and Setup ---
1282 if (!user || !particle || !guess_rank_out || !bboxlist) {
1283 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Null pointer provided to GuessParticleOwnerWithBBox.");
1284 }
1285 if (!localBBox|| !neighbors) {
1286 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, "Required user->bboxl or user->neighbors is not initialized.");
1287 }
1288
1289 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1290 ierr = MPI_Comm_size(PETSC_COMM_WORLD, &size); CHKERRQ(ierr);
1291
1292 *guess_rank_out = MPI_PROC_NULL; // Default to "not found"
1293
1294 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld]: Starting guess for particle at (%.3f, %.3f, %.3f).\n",
1295 particle->PID, particle->loc.x, particle->loc.y, particle->loc.z);
1296
1297 // --- Step 0: Check if the particle is inside the CURRENT rank's bounding box FIRST. ---
1298 // This handles the common case of initial placement where a particle is "lost" but physically local.
1299 if (IsParticleInBox(localBBox, &particle->loc)) {
1300 *guess_rank_out = rank;
1301 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld]: Fast path guess SUCCESS. Particle is within the local (Rank %d) bounding box.\n",
1302 particle->PID, rank);
1303
1305 PetscFunctionReturn(0); // Found it, we're done.
1306 }
1307 // --- 2. Fast Path: Check Immediate Neighbors Based on Exit Direction ---
1308
1309 // Determine likely exit direction(s) to prioritize neighbor check
1310 PetscBool exit_xm = particle->loc.x < localBBox->min_coords.x;
1311 PetscBool exit_xp = particle->loc.x > localBBox->max_coords.x;
1312 PetscBool exit_ym = particle->loc.y < localBBox->min_coords.y;
1313 PetscBool exit_yp = particle->loc.y > localBBox->max_coords.y;
1314 PetscBool exit_zm = particle->loc.z < localBBox->min_coords.z;
1315 PetscBool exit_zp = particle->loc.z > localBBox->max_coords.z;
1316
1317 if (exit_xm && neighbors->rank_xm != MPI_PROC_NULL && IsParticleInBox(&bboxlist[neighbors->rank_xm], &particle->loc)) {
1318 *guess_rank_out = neighbors->rank_xm;
1319 } else if (exit_xp&& neighbors->rank_xp != MPI_PROC_NULL && IsParticleInBox(&bboxlist[neighbors->rank_xp], &particle->loc)) {
1320 *guess_rank_out = neighbors->rank_xp;
1321 } else if (exit_ym && neighbors->rank_ym != MPI_PROC_NULL && IsParticleInBox(&bboxlist[neighbors->rank_ym], &particle->loc)) {
1322 *guess_rank_out = neighbors->rank_ym;
1323 } else if (exit_yp && neighbors->rank_yp != MPI_PROC_NULL && IsParticleInBox(&bboxlist[neighbors->rank_yp], &particle->loc)) {
1324 *guess_rank_out = neighbors->rank_yp;
1325 } else if (exit_zm && neighbors->rank_zm != MPI_PROC_NULL && IsParticleInBox(&bboxlist[neighbors->rank_zm], &particle->loc)) {
1326 *guess_rank_out = neighbors->rank_zm;
1327 } else if (exit_zp && neighbors->rank_zp != MPI_PROC_NULL && IsParticleInBox(&bboxlist[neighbors->rank_zp], &particle->loc)) {
1328 *guess_rank_out = neighbors->rank_zp;
1329 }
1330 // Note: This does not handle corner/edge neighbors, which is why the fallback is essential.
1331
1332 if (*guess_rank_out != MPI_PROC_NULL) {
1333 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld]: Fast path guess SUCCESS. Found in immediate neighbor Rank %d.\n",
1334 particle->PID, *guess_rank_out);
1335
1337 PetscFunctionReturn(0); // Found it, we're done.
1338 }
1339
1340 // --- 3. Robust Fallback: Check All Other Ranks ---
1341 // If we get here, the particle was not in any of the immediate face neighbors' boxes.
1342 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld]: Not in immediate face neighbors. Starting global fallback search.\n",
1343 particle->PID);
1344
1345 for (PetscMPIInt r = 0; r < size; ++r) {
1346 if (r == rank) continue; // Don't check ourselves.
1347
1348 if (IsParticleInBox(&bboxlist[r], &particle->loc)) {
1349 PetscBool is_in = PETSC_TRUE;
1350 // This detailed, synchronized print will solve the mystery
1351 LOG_ALLOW(LOCAL,LOG_VERBOSE, "[Rank %d] Checking PID %lld at (%.4f, %.4f, %.4f) against Rank %d's box: [(%.4f, %.4f, %.4f) to (%.4f, %.4f, %.4f)]. Result: %s\n",
1352 (int)rank, (long long)particle->PID,
1353 particle->loc.x, particle->loc.y, particle->loc.z,
1354 (int)r,
1355 bboxlist[r].min_coords.x, bboxlist[r].min_coords.y, bboxlist[r].min_coords.z,
1356 bboxlist[r].max_coords.x, bboxlist[r].max_coords.y, bboxlist[r].max_coords.z,
1357 is_in ? "INSIDE" : "OUTSIDE");
1358
1359 *guess_rank_out = r;
1360 LOG_ALLOW(LOCAL, LOG_DEBUG, "[PID %ld]: Fallback search SUCCESS. Found in Rank %d.\n",
1361 particle->PID, *guess_rank_out);
1362
1364 PetscFunctionReturn(0); // Found it, we're done.
1365 }
1366 }
1367
1368 // If the code reaches here, the particle was not found in any rank's bounding box.
1369 LOG_ALLOW(LOCAL, LOG_WARNING, "[PID %ld]: Guess FAILED. Particle not found in any rank's bounding box.\n",
1370 particle->PID);
1371
1372 // The guess_rank_out will remain -1, signaling failure to the caller.
1374 PetscFunctionReturn(0);
1375}
PetscMPIInt rank_zm
Definition variables.h:199
PetscMPIInt rank_yp
Definition variables.h:198
PetscMPIInt rank_ym
Definition variables.h:198
PetscMPIInt rank_xp
Definition variables.h:197
RankNeighbors neighbors
Definition variables.h:923
PetscMPIInt rank_xm
Definition variables.h:197
PetscMPIInt rank_zp
Definition variables.h:199
BoundingBox bbox
Definition variables.h:922
PetscInt64 PID
Definition variables.h:183
Defines a 3D axis-aligned bounding box.
Definition variables.h:171
Stores the MPI ranks of neighboring subdomains.
Definition variables.h:196
Here is the call graph for this function:
Here is the caller graph for this function:

◆ LocateAllParticlesInGrid()

PetscErrorCode LocateAllParticlesInGrid ( UserCtx user,
BoundingBox bboxlist 
)

Implementation of LocateAllParticlesInGrid().

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

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

See also
LocateAllParticlesInGrid()

Definition at line 1385 of file ParticleMotion.c.

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

Implementation of ResetAllParticleStatuses().

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

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

See also
ResetAllParticleStatuses()

Definition at line 1619 of file ParticleMotion.c.

1620{
1621 PetscErrorCode ierr;
1622 PetscInt n_local;
1623 PetscInt *status_p;
1624
1625 PetscFunctionBeginUser;
1626
1628
1629 ierr = DMSwarmGetLocalSize(user->swarm, &n_local); CHKERRQ(ierr);
1630
1631 if (n_local > 0) {
1632 // Get write access to the status field
1633 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status_p); CHKERRQ(ierr);
1634
1635 for (PetscInt p = 0; p < n_local; ++p) {
1636 // Only reset particles that are considered settled. This is a small optimization
1637 // to avoid changing the status of a LOST particle, though resetting all would also be fine.
1638 if (status_p[p] == ACTIVE_AND_LOCATED) {
1639 status_p[p] = NEEDS_LOCATION;
1640 }
1641 }
1642
1643 // Restore the field
1644 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status_p); CHKERRQ(ierr);
1645 }
1646
1647
1649 PetscFunctionReturn(0);
1650}
Here is the call graph for this function:
Here is the caller graph for this function: