PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
ParticleSwarm.c
Go to the documentation of this file.
1 // ParticleSwarm.c
2
3 #include "ParticleSwarm.h"
4
5 #define INTERPOLATION_DISTANCE_TOLERANCE 1.0e-14
6 /**
7 * @brief Initializes the DMSwarm object within the UserCtx structure.
8 *
9 * This function creates the DMSwarm, sets its type and dimension, and configures basic swarm properties.
10 *
11 * @param[in,out] user Pointer to the UserCtx structure containing simulation context.
12 *
13 * @return PetscErrorCode Returns 0 on success, non-zero on failure.
14 */
15 PetscErrorCode InitializeSwarm(UserCtx* user) {
16 PetscErrorCode ierr; // Error code for PETSc functions
17
18 // Create the DMSwarm object for particle management
19 ierr = DMCreate(PETSC_COMM_WORLD, &user->swarm); CHKERRQ(ierr);
20 ierr = DMSetType(user->swarm, DMSWARM); CHKERRQ(ierr);
21 ierr = DMSetDimension(user->swarm, 3); CHKERRQ(ierr);
22 ierr = DMSwarmSetType(user->swarm, DMSWARM_BASIC); CHKERRQ(ierr);
23 LOG_ALLOW(LOCAL,LOG_INFO, "InitializeSwarm - DMSwarm created and configured.\n");
24
25 return 0;
26 }
27
28 /**
29 * @brief Registers a swarm field without finalizing registration.
30 *
31 * This function calls DMSwarmRegisterPetscDatatypeField for the given field,
32 * but does not finalize the registration. The finalization is deferred until
33 * all fields have been registered.
34 *
35 * @param swarm [in] The DMSwarm object.
36 * @param fieldName [in] Name of the field to register.
37 * @param fieldDim [in] Dimension of the field (1 for scalar, 3 for vector, etc.).
38 * @param dtype [in] The datatype of the swarm field being registered.
39 * @return PetscErrorCode Returns 0 on success, non-zero on failure.
40 */
41 PetscErrorCode RegisterSwarmField(DM swarm, const char *fieldName, PetscInt fieldDim, PetscDataType dtype)
42 {
43 PetscErrorCode ierr;
44 PetscFunctionBeginUser;
45
46 ierr = DMSwarmRegisterPetscDatatypeField(swarm, fieldName, fieldDim, dtype); CHKERRQ(ierr);
47 // PetscDataTypes is an extern char* [] defined in petscsystypes.h that gives string names for PetscDataType enums
48 LOG_ALLOW(LOCAL,LOG_DEBUG,"RegisterSwarmField - Registered field '%s' with dimension=%d, type=%s.\n",
49 fieldName, fieldDim, PetscDataTypes[dtype]);
50
51 PetscFunctionReturn(0);
52 }
53
54 /**
55 * @brief Registers necessary particle fields within the DMSwarm.
56 *
57 * This function registers fields such as position, velocity, CellID, and weight for each particle.
58 *
59 * @param[in,out] swarm The DMSwarm object managing the particle swarm.
60 *
61 * @return PetscErrorCode Returns 0 on success, non-zero on failure.
62 */
63
64 PetscErrorCode RegisterParticleFields(DM swarm)
65 {
66 PetscErrorCode ierr;
67 PetscFunctionBeginUser;
68
69 // Register each field using the helper function
70 ierr = RegisterSwarmField(swarm, "position", 3 ,PETSC_REAL); CHKERRQ(ierr);
71 LOG_ALLOW(LOCAL,LOG_DEBUG, "Registered field 'position'.\n");
72
73 ierr = RegisterSwarmField(swarm, "velocity", 3, PETSC_REAL); CHKERRQ(ierr);
74 LOG_ALLOW(LOCAL,LOG_DEBUG,"Registered field 'velocity'.\n");
75
76 ierr = RegisterSwarmField(swarm, "DMSwarm_CellID", 3, PETSC_INT); CHKERRQ(ierr);
77 LOG_ALLOW(LOCAL,LOG_DEBUG,"Registered field 'DMSwarm_CellID'.\n");
78
79 ierr = RegisterSwarmField(swarm, "weight", 3,PETSC_REAL); CHKERRQ(ierr);
80 LOG_ALLOW(LOCAL,LOG_DEBUG,"Registered field 'weight'.\n");
81
82 ierr = RegisterSwarmField(swarm,"Psi", 1,PETSC_REAL); CHKERRQ(ierr);
83 LOG_ALLOW(LOCAL,LOG_DEBUG,"Registered field 'Psi' - Scalar.\n");
84
85
86 ierr = RegisterSwarmField(swarm,"pos_phy", 3,PETSC_REAL); CHKERRQ(ierr);
87 LOG_ALLOW(LOCAL,LOG_DEBUG,"Registered field 'pos_phy' - Physical Position.\n");
88
89 ierr = RegisterSwarmField(swarm,"DMSwarm_location_status",1,PETSC_INT);CHKERRQ(ierr);
90 LOG_ALLOW(LOCAL,LOG_DEBUG,"Registered field 'DMSwarm_location_status' - Status of Location of Particle(located,lost etc).\n");
91
92 // Finalize the field registration after all fields have been added
93 ierr = DMSwarmFinalizeFieldRegister(swarm); CHKERRQ(ierr);
94 LOG_ALLOW(LOCAL,LOG_INFO,"RegisterParticleFields - Finalized field registration.\n");
95
96 PetscFunctionReturn(0);
97 }
98
99
100 /**
101 * @brief Initializes random number generators for assigning particle properties.
102 *
103 * This function creates and configures separate PETSc random number generators for the x, y, and z coordinates.
104 *
105 * @param[in,out] user Pointer to the UserCtx structure containing simulation context.
106 * @param[out] randx Pointer to store the RNG for the x-coordinate.
107 * @param[out] randy Pointer to store the RNG for the y-coordinate.
108 * @param[out] randz Pointer to store the RNG for the z-coordinate.
109 *
110 * @return PetscErrorCode Returns 0 on success, non-zero on failure.
111 */
112 PetscErrorCode InitializeRandomGenerators(UserCtx* user, PetscRandom *randx, PetscRandom *randy, PetscRandom *randz) {
113 PetscErrorCode ierr; // Error code for PETSc functions
114 PetscMPIInt rank;
115 MPI_Comm_rank(PETSC_COMM_WORLD, &rank);
116
117 // Initialize RNG for x-coordinate
118 ierr = PetscRandomCreate(PETSC_COMM_SELF, randx); CHKERRQ(ierr);
119 ierr = PetscRandomSetType((*randx), PETSCRAND48); CHKERRQ(ierr);
120 ierr = PetscRandomSetInterval(*randx, user->bbox.min_coords.x, user->bbox.max_coords.x); CHKERRQ(ierr);
121 ierr = PetscRandomSetSeed(*randx, rank + 12345); CHKERRQ(ierr); // Unique seed per rank
122 ierr = PetscRandomSeed(*randx); CHKERRQ(ierr);
123 LOG_ALLOW_SYNC(LOCAL,LOG_DEBUG, "InitializeRandomGenerators - Initialized RNG for X-axis.\n");
124
125 // Initialize RNG for y-coordinate
126 ierr = PetscRandomCreate(PETSC_COMM_SELF, randy); CHKERRQ(ierr);
127 ierr = PetscRandomSetType((*randy), PETSCRAND48); CHKERRQ(ierr);
128 ierr = PetscRandomSetInterval(*randy, user->bbox.min_coords.y, user->bbox.max_coords.y); CHKERRQ(ierr);
129 ierr = PetscRandomSetSeed(*randy, rank + 67890); CHKERRQ(ierr); // Unique seed per rank
130 ierr = PetscRandomSeed(*randy); CHKERRQ(ierr);
131 LOG_ALLOW_SYNC(LOCAL,LOG_DEBUG, "InitializeRandomGenerators - Initialized RNG for Y-axis.\n");
132
133 // Initialize RNG for z-coordinate
134 ierr = PetscRandomCreate(PETSC_COMM_SELF, randz); CHKERRQ(ierr);
135 ierr = PetscRandomSetType((*randz), PETSCRAND48); CHKERRQ(ierr);
136 ierr = PetscRandomSetInterval(*randz, user->bbox.min_coords.z, user->bbox.max_coords.z); CHKERRQ(ierr);
137 ierr = PetscRandomSetSeed(*randz, rank + 54321); CHKERRQ(ierr); // Unique seed per rank
138 ierr = PetscRandomSeed(*randz); CHKERRQ(ierr);
139 LOG_ALLOW_SYNC(LOCAL,LOG_DEBUG, "InitializeRandomGenerators - Initialized RNG for Z-axis.\n");
140
141 return 0;
142 }
143
144 /**
145 * @brief Initializes random number generators for logical space operations [0.0, 1.0).
146 *
147 * This function creates and configures three separate PETSc random number generators,
148 * one for each logical dimension (i, j, k or xi, eta, zeta equivalent).
149 * Each RNG is configured to produce uniformly distributed real numbers in the interval [0.0, 1.0).
150 * These are typically used for selecting owned cells or generating intra-cell logical coordinates.
151 *
152 * @param[out] rand_logic_i Pointer to store the RNG for the i-logical dimension.
153 * @param[out] rand_logic_j Pointer to store the RNG for the j-logical dimension.
154 * @param[out] rand_logic_k Pointer to store the RNG for the k-logical dimension.
155 *
156 * @return PetscErrorCode Returns 0 on success, non-zero on failure.
157 */
158 PetscErrorCode InitializeLogicalSpaceRNGs(PetscRandom *rand_logic_i, PetscRandom *rand_logic_j, PetscRandom *rand_logic_k) {
159 PetscErrorCode ierr;
160 PetscMPIInt rank;
161 PetscFunctionBeginUser;
162
163 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
164
165 // --- RNG for i-logical dimension ---
166 ierr = PetscRandomCreate(PETSC_COMM_SELF, rand_logic_i); CHKERRQ(ierr);
167 ierr = PetscRandomSetType((*rand_logic_i), PETSCRAND48); CHKERRQ(ierr);
168 ierr = PetscRandomSetInterval(*rand_logic_i, 0.0, 1.0); CHKERRQ(ierr); // Key change: [0,1)
169 ierr = PetscRandomSetSeed(*rand_logic_i, rank + 202401); CHKERRQ(ierr); // Unique seed
170 ierr = PetscRandomSeed(*rand_logic_i); CHKERRQ(ierr);
171 LOG_ALLOW(LOCAL,LOG_DEBUG, "InitializeLogicalSpaceRNGs - Initialized RNG for i-logical dimension [0,1).\n");
172
173 // --- RNG for j-logical dimension ---
174 ierr = PetscRandomCreate(PETSC_COMM_SELF, rand_logic_j); CHKERRQ(ierr);
175 ierr = PetscRandomSetType((*rand_logic_j), PETSCRAND48); CHKERRQ(ierr);
176 ierr = PetscRandomSetInterval(*rand_logic_j, 0.0, 1.0); CHKERRQ(ierr); // Key change: [0,1)
177 ierr = PetscRandomSetSeed(*rand_logic_j, rank + 202402); CHKERRQ(ierr);
178 ierr = PetscRandomSeed(*rand_logic_j); CHKERRQ(ierr);
179 LOG_ALLOW(LOCAL,LOG_DEBUG, "InitializeLogicalSpaceRNGs - Initialized RNG for j-logical dimension [0,1).\n");
180
181 // --- RNG for k-logical dimension ---
182 ierr = PetscRandomCreate(PETSC_COMM_SELF, rand_logic_k); CHKERRQ(ierr);
183 ierr = PetscRandomSetType((*rand_logic_k), PETSCRAND48); CHKERRQ(ierr);
184 ierr = PetscRandomSetInterval(*rand_logic_k, 0.0, 1.0); CHKERRQ(ierr); // Key change: [0,1)
185 ierr = PetscRandomSetSeed(*rand_logic_k, rank + 202403); CHKERRQ(ierr);
186 ierr = PetscRandomSeed(*rand_logic_k); CHKERRQ(ierr);
187 LOG_ALLOW(LOCAL,LOG_DEBUG, "InitializeLogicalSpaceRNGs - Initialized RNG for k-logical dimension [0,1).\n");
188
189 PetscFunctionReturn(0);
190 }
191
192 /**
193 * @brief Determines cell selection and intra-cell logical coordinates for volumetric initialization (Mode 1).
194 *
195 * This function is called when `simCtx->ParticleInitialization == 1`. It randomly selects
196 * an *owned cell* on the current MPI rank and then generates random intra-cell logical
197 * coordinates `[0,1)^3` within that chosen cell.
198 *
199 * The process involves:
200 * 1. Checking if the current MPI rank owns any 3D cells.
201 * 2. If it does, it randomly selects an *owned cell index* in each logical direction (i, j, k)
202 * by scaling a `[0,1)` random number with the number of owned cells in that direction.
203 * 3. These local owned cell indices are then converted to the *local node indices*
204 * (`ci/cj/ck_metric_lnode_out`) corresponding to the origin of the selected cell,
205 * for use with `MetricLogicalToPhysical`. This conversion uses `xs/ys/zs_gnode`.
206 * 4. All three intra-cell logical coordinates (`xi/eta/zta_metric_logic_out`) for
207 * `MetricLogicalToPhysical` are chosen randomly within the `[0,1)` range.
208 * 5. A flag (`can_place_in_volume_out`) indicates if a valid placement could be determined.
209 *
210 * **Important Note on `DMDALocalInfo info` members (same as for surface init):**
211 * - `info->xs, info->ys, info->ks`: Global starting indices of *owned cells*.
212 * - `info->mx, info->my, info->mz`: Number of *grid points (nodes)* in each local dimension on this process.
213 * Therefore, the number of *owned cells* in a dimension is `info->mX - 1` (if `info->mX > 0`).
214 *
215 * @param[in] user Pointer to `UserCtx`. (Currently not used in this specific helper, but kept for API consistency).
216 * @param[in] info Pointer to `DMDALocalInfo` for the current rank's grid portion.
217 * @param[in] xs_gnode, ys_gnode, zs_gnode Local indices (in the ghosted array) of the first *owned node*.
218 * @param[in] rand_logic_i_ptr Pointer to the RNG for i-dimension tasks [0,1).
219 * @param[in] rand_logic_j_ptr Pointer to the RNG for j-dimension tasks [0,1).
220 * @param[in] rand_logic_k_ptr Pointer to the RNG for k-dimension tasks [0,1).
221 * @param[out] ci_metric_lnode_out Pointer to store the local i-node index of the selected cell's origin.
222 * @param[out] cj_metric_lnode_out Pointer to store the local j-node index of the selected cell's origin.
223 * @param[out] ck_metric_lnode_out Pointer to store the local k-node index of the selected cell's origin.
224 * @param[out] xi_metric_logic_out Pointer to store the intra-cell logical xi-coordinate [0,1).
225 * @param[out] eta_metric_logic_out Pointer to store the intra-cell logical eta-coordinate [0,1).
226 * @param[out] zta_metric_logic_out Pointer to store the intra-cell logical zeta-coordinate [0,1).
227 * @param[out] can_place_in_volume_out PETSC_TRUE if placement parameters were successfully determined, PETSC_FALSE otherwise.
228 * @return PetscErrorCode 0 on success, or a PETSc error code.
229 */
231 UserCtx *user, DMDALocalInfo *info,
232 PetscInt xs_gnode, PetscInt ys_gnode, PetscInt zs_gnode,
233 PetscRandom *rand_logic_i_ptr, PetscRandom *rand_logic_j_ptr, PetscRandom *rand_logic_k_ptr, /* Pointers to RNGs */
234 PetscInt *ci_metric_lnode_out, PetscInt *cj_metric_lnode_out, PetscInt *ck_metric_lnode_out,
235 PetscReal *xi_metric_logic_out, PetscReal *eta_metric_logic_out, PetscReal *zta_metric_logic_out,
236 PetscBool *can_place_in_volume_out)
237 {
238 PetscErrorCode ierr = 0;
239 PetscReal r_val; // Temporary for random numbers from [0,1) RNGs
240 PetscInt local_owned_cell_idx_i, local_owned_cell_idx_j, local_owned_cell_idx_k;
241 PetscMPIInt rank_for_logging; // For logging if needed
242
243 PetscFunctionBeginUser;
244 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank_for_logging); CHKERRQ(ierr);
245
246 *can_place_in_volume_out = PETSC_FALSE; // Default to: cannot place
247
248 // Default intra-cell logicals and cell node indices (e.g. if placement fails)
249 *xi_metric_logic_out = 0.5; *eta_metric_logic_out = 0.5; *zta_metric_logic_out = 0.5;
250 *ci_metric_lnode_out = xs_gnode; *cj_metric_lnode_out = ys_gnode; *ck_metric_lnode_out = zs_gnode;
251
252 // Calculate number of owned cells in each direction from node counts in info
253 // Assumes info->mx, info->my, info->mz are node counts on this process for each dimension.
254 // Number of cells = Number of nodes - 1 (if > 0 nodes).
255 PetscInt num_owned_cells_i = (info->mx > 1) ? info->mx - 1 : 0;
256 PetscInt num_owned_cells_j = (info->my > 1) ? info->my - 1 : 0;
257 PetscInt num_owned_cells_k = (info->mz > 1) ? info->mz - 1 : 0;
258
259 if (num_owned_cells_i > 0 && num_owned_cells_j > 0 && num_owned_cells_k > 0) { // If rank owns any 3D cells
260 *can_place_in_volume_out = PETSC_TRUE;
261
262 // --- 1. Select a Random Owned Cell ---
263 // The selected index will be a 0-based index relative to the start of this rank's owned cells.
264
265 // Select random local owned cell index in I-direction
266 ierr = PetscRandomGetValueReal(*rand_logic_i_ptr, &r_val); CHKERRQ(ierr); // Dereference RNG pointer
267 local_owned_cell_idx_i = (PetscInt)(r_val * num_owned_cells_i);
268 // Clamp to be safe: local_owned_cell_idx_i should be in [0, num_owned_cells_i - 1]
269 local_owned_cell_idx_i = PetscMin(PetscMax(0, local_owned_cell_idx_i), num_owned_cells_i - 1);
270 *ci_metric_lnode_out = xs_gnode + local_owned_cell_idx_i; // Convert to local node index for cell origin
271
272 // Select random local owned cell index in J-direction
273 ierr = PetscRandomGetValueReal(*rand_logic_j_ptr, &r_val); CHKERRQ(ierr); // Dereference RNG pointer
274 local_owned_cell_idx_j = (PetscInt)(r_val * num_owned_cells_j);
275 local_owned_cell_idx_j = PetscMin(PetscMax(0, local_owned_cell_idx_j), num_owned_cells_j - 1);
276 *cj_metric_lnode_out = ys_gnode + local_owned_cell_idx_j;
277
278 // Select random local owned cell index in K-direction
279 ierr = PetscRandomGetValueReal(*rand_logic_k_ptr, &r_val); CHKERRQ(ierr); // Dereference RNG pointer
280 local_owned_cell_idx_k = (PetscInt)(r_val * num_owned_cells_k);
281 local_owned_cell_idx_k = PetscMin(PetscMax(0, local_owned_cell_idx_k), num_owned_cells_k - 1);
282 *ck_metric_lnode_out = zs_gnode + local_owned_cell_idx_k;
283
284 LOG_ALLOW(LOCAL, LOG_DEBUG, "DVP - Rank %d: Selected Cell (Owned Idx: %d,%d,%d -> LNodeStart: %d,%d,%d). OwnedCells(i,j,k): (%d,%d,%d). GhostNodeStarts(xs,ys,zs): (%d,%d,%d) \n",
285 rank_for_logging, local_owned_cell_idx_i, local_owned_cell_idx_j, local_owned_cell_idx_k,
286 *ci_metric_lnode_out, *cj_metric_lnode_out, *ck_metric_lnode_out,
287 num_owned_cells_i, num_owned_cells_j, num_owned_cells_k,
288 xs_gnode, ys_gnode, zs_gnode);
289
290
291 // --- 2. Generate Random Intra-Cell Logical Coordinates [0,1) for MetricLogicalToPhysical ---
292 ierr = PetscRandomGetValueReal(*rand_logic_i_ptr, xi_metric_logic_out); CHKERRQ(ierr); // Re-use RNGs
293 ierr = PetscRandomGetValueReal(*rand_logic_j_ptr, eta_metric_logic_out); CHKERRQ(ierr);
294 ierr = PetscRandomGetValueReal(*rand_logic_k_ptr, zta_metric_logic_out); CHKERRQ(ierr);
295
296 // Ensure logical coordinates are strictly within [0,1) for robustness with MetricLogicalToPhysical
297 *xi_metric_logic_out = PetscMin(*xi_metric_logic_out, 1.0 - 1.0e-7);
298 *eta_metric_logic_out = PetscMin(*eta_metric_logic_out, 1.0 - 1.0e-7);
299 *zta_metric_logic_out = PetscMin(*zta_metric_logic_out, 1.0 - 1.0e-7);
300 // Ensure they are not negative either (though [0,1) RNGs shouldn't produce this)
301 *xi_metric_logic_out = PetscMax(*xi_metric_logic_out, 0.0);
302 *eta_metric_logic_out = PetscMax(*eta_metric_logic_out, 0.0);
303 *zta_metric_logic_out = PetscMax(*zta_metric_logic_out, 0.0);
304
305 } else {
306 // This rank does not own any 3D cells (e.g., in a 1D or 2D decomposition,
307 // or if the global domain itself is not 3D in terms of cells).
308 // *can_place_in_volume_out remains PETSC_FALSE.
309 LOG_ALLOW(LOCAL, LOG_WARNING, "DVP - Rank %d: Cannot place particle volumetrically. Rank has zero owned cells in at least one dimension (owned cells i,j,k: %d,%d,%d).\n",
310 rank_for_logging, num_owned_cells_i, num_owned_cells_j, num_owned_cells_k);
311 }
312 PetscFunctionReturn(0);
313 }
314
315 /**
316 * @brief Initializes basic properties for particles on the local process.
317 *
318 * This function assigns initial physical positions, Particle IDs (PIDs), and placeholder
319 * cell IDs to particles. The method of position initialization depends on
320 * `simCtx->ParticleInitialization`:
321 * - Mode 0 (Surface): Particles are placed on a designated inlet surface if the
322 * current rank services that surface. Otherwise, they are placed at (0,0,0)
323 * to be migrated to the correct rank later.
324 * - Mode 1 (Volumetric): Particles are placed randomly within a cell owned by
325 * the current rank.
326 *
327 * The logical coordinates for placement are generated using provided random number
328 * generators. These logical coordinates are then transformed to physical coordinates
329 * using `MetricLogicalToPhysical`.
330 *
331 * @param user Pointer to the UserCtx structure, containing simulation settings and grid information.
332 * @param particlesPerProcess The number of particles to initialize on this MPI rank.
333 * @param rand_logic_i Pointer to a PetscRandom generator for the xi logical coordinate.
334 * @param rand_logic_j Pointer to a PetscRandom generator for the eta logical coordinate.
335 * @param rand_logic_k Pointer to a PetscRandom generator for the zeta logical coordinate.
336 * @param bboxlist (Unused in this function for placement) Pointer to the bounding box list;
337 * provided for API consistency but not used for determining initial positions here.
338 * Particle positions are determined by logical-to-physical mapping based on rank's owned cells.
339 * @return PetscErrorCode 0 on success, non-zero on failure.
340 */
341 static PetscErrorCode InitializeParticleBasicProperties(UserCtx *user,
342 PetscInt particlesPerProcess,
343 PetscRandom *rand_logic_i,
344 PetscRandom *rand_logic_j,
345 PetscRandom *rand_logic_k,
346 BoundingBox *bboxlist) // bboxlist unused for placement
347 {
348 PetscErrorCode ierr;
349 DM swarm = user->swarm;
350 PetscReal *positions_field = NULL; // Pointer to swarm field for physical positions (x,y,z)
351 PetscReal *pos_phy_field = NULL; // Pointer to swarm field for physical positions (backup/alternative)
352 PetscInt64 *particleIDs = NULL; // Pointer to swarm field for Particle IDs
353 PetscInt *cellIDs_petsc = NULL; // Pointer to swarm field for DMSwarm_CellID (i,j,k of containing cell)
354 PetscInt *status_field = NULL; // Pointer to swarm field for DMSwarm_location_status(NEEDS_LOCATION etc)
355 PetscMPIInt rank,size; // MPI rank of the current process, and total number of ranks.
356 const Cmpnts ***coor_nodes_local_array; // Read-only access to local node coordinates (from user->da)
357 Vec Coor_local; // Local vector for node coordinates
358 DMDALocalInfo info; // Local grid information (node-based) from user->da
359 PetscInt xs_gnode_rank, ys_gnode_rank, zs_gnode_rank; // Local starting node indices (incl. ghosts) of rank's DA patch
360 PetscInt IM_nodes_global, JM_nodes_global, KM_nodes_global; // Global node counts in each direction
361
362 // Variables for surface initialization (Mode 0)
363 PetscBool can_this_rank_service_inlet = PETSC_FALSE;
364
365 PetscFunctionBeginUser;
366
367 SimCtx *simCtx = user->simCtx;
368
369 // --- 1. Input Validation and Basic Setup ---
370 if (!user || !rand_logic_i || !rand_logic_j || !rand_logic_k) {
371 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "InitializeParticleBasicProperties - Null user or RNG pointer.");
372 }
373 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
374 ierr = MPI_Comm_size(PETSC_COMM_WORLD,&size); CHKERRQ(ierr);
375
376 // Get DMDA information for the node-centered coordinate grid (user->da)
377 ierr = DMGetCoordinatesLocal(user->da, &Coor_local); CHKERRQ(ierr);
378 if (!Coor_local) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_PLIB, "DMGetCoordinatesLocal for user->da returned NULL Coor_local.");
379 ierr = DMDAVecGetArrayRead(user->fda, Coor_local, (void*)&coor_nodes_local_array); CHKERRQ(ierr);
380 ierr = DMDAGetLocalInfo(user->da, &info); CHKERRQ(ierr);
381 ierr = DMDAGetGhostCorners(user->da, &xs_gnode_rank, &ys_gnode_rank, &zs_gnode_rank, NULL, NULL, NULL); CHKERRQ(ierr);
382 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);
383
384 LOG_ALLOW(LOCAL, LOG_INFO, "InitializeParticleBasicProperties - Rank %d: Initializing %d particles. Mode: %d.\n",
385 rank, particlesPerProcess, simCtx->ParticleInitialization);
386
387 // --- 2. Pre-computation for Surface Initialization (Mode 0) ---
388 if (simCtx->ParticleInitialization == 0) { // Surface initialization
389 ierr = CanRankServiceInletFace(user, &info, IM_nodes_global, JM_nodes_global, KM_nodes_global, &can_this_rank_service_inlet); CHKERRQ(ierr);
390 if (can_this_rank_service_inlet) {
391 LOG_ALLOW(LOCAL, LOG_INFO, "InitProps - Rank %d: Will attempt to place particles on inlet face %d.\n", rank, user->identifiedInletBCFace);
392 } else {
393 LOG_ALLOW(LOCAL, LOG_INFO, "InitProps - Rank %d: Cannot service inlet face %d. Particles will be at default (0,0,0) and rely on migration.\n", rank, user->identifiedInletBCFace);
394 }
395 }
396
397 // --- 3. Get Access to Swarm Fields ---
398 ierr = DMSwarmGetField(swarm, "position", NULL, NULL, (void**)&positions_field); CHKERRQ(ierr);
399 ierr = DMSwarmGetField(swarm, "pos_phy", NULL, NULL, (void**)&pos_phy_field); CHKERRQ(ierr);
400 ierr = DMSwarmGetField(swarm, "DMSwarm_pid", NULL, NULL, (void**)&particleIDs); CHKERRQ(ierr);
401 ierr = DMSwarmGetField(swarm, "DMSwarm_CellID", NULL, NULL, (void**)&cellIDs_petsc); CHKERRQ(ierr);
402 ierr = DMSwarmGetField(swarm, "DMSwarm_location_status",NULL,NULL,(void**)&status_field); CHKERRQ(ierr);
403
404 // --- 4. Determine Starting Global PID for this Rank ---
405 PetscInt particles_per_rank_ideal = simCtx->np / size; // Assumes user->size is PETSC_COMM_WORLD size
406 PetscInt remainder_particles = simCtx->np % size;
407 PetscInt base_pid_for_rank = rank * particles_per_rank_ideal + PetscMin(rank, remainder_particles);
408 // This calculation must match how particlesPerProcess was determined (e.g., in DistributeParticles).
409
410 // --- 5. Loop Over Particles to Initialize ---
411 for (PetscInt p = 0; p < particlesPerProcess; p++) {
412 PetscInt ci_metric_lnode, cj_metric_lnode, ck_metric_lnode;
413 PetscReal xi_metric_logic, eta_metric_logic, zta_metric_logic;
414 Cmpnts phys_coords = {0.0, 0.0, 0.0};
415 PetscBool particle_placed_by_this_rank = PETSC_FALSE;
416
417 if (simCtx->ParticleInitialization == 0) { // --- 5.a. Surface Initialization ---
418 if (can_this_rank_service_inlet) {
419 ierr = GetRandomCellAndLogicOnInletFace(user, &info, xs_gnode_rank, ys_gnode_rank, zs_gnode_rank,
420 IM_nodes_global, JM_nodes_global, KM_nodes_global,
421 rand_logic_i, rand_logic_j, rand_logic_k,
422 &ci_metric_lnode, &cj_metric_lnode, &ck_metric_lnode,
423 &xi_metric_logic, &eta_metric_logic, &zta_metric_logic); CHKERRQ(ierr);
424 ierr = MetricLogicalToPhysical(user, coor_nodes_local_array,
425 ci_metric_lnode, cj_metric_lnode, ck_metric_lnode,
426 xi_metric_logic, eta_metric_logic, zta_metric_logic,
427 &phys_coords); CHKERRQ(ierr);
428 particle_placed_by_this_rank = PETSC_TRUE;
429 }
430 } else { // --- 5.b. Volumetric Initialization (simCtx->ParticleInitialization == 1) ---
431 PetscBool can_place_volumetrically;
432 ierr = DetermineVolumetricInitializationParameters(user, &info, xs_gnode_rank, ys_gnode_rank, zs_gnode_rank,
433 rand_logic_i, rand_logic_j, rand_logic_k,
434 &ci_metric_lnode, &cj_metric_lnode, &ck_metric_lnode,
435 &xi_metric_logic, &eta_metric_logic, &zta_metric_logic,
436 &can_place_volumetrically); CHKERRQ(ierr);
437 if(can_place_volumetrically){
438 ierr = MetricLogicalToPhysical(user, coor_nodes_local_array,
439 ci_metric_lnode, cj_metric_lnode, ck_metric_lnode,
440 xi_metric_logic, eta_metric_logic, zta_metric_logic,
441 &phys_coords); CHKERRQ(ierr);
442 particle_placed_by_this_rank = PETSC_TRUE;
443 } else {
445 "InitProps - Rank %d: PID %lld (idx %ld) (Volumetric Mode %d) - DetermineVolumetric... returned false. Default Phys: (%.2f,%.2f,%.2f).\n",
446 rank, (long long)(base_pid_for_rank + p), (long)p, simCtx->ParticleInitialization, phys_coords.x, phys_coords.y, phys_coords.z);
447 }
448 }
449
450 // --- 5.c. Store Particle Properties ---
451 positions_field[3*p+0] = phys_coords.x;
452 positions_field[3*p+1] = phys_coords.y;
453 positions_field[3*p+2] = phys_coords.z;
454 pos_phy_field[3*p+0] = phys_coords.x;
455 pos_phy_field[3*p+1] = phys_coords.y;
456 pos_phy_field[3*p+2] = phys_coords.z;
457 particleIDs[p] = (PetscInt64)base_pid_for_rank + p;
458 cellIDs_petsc[3*p+0] = -1; cellIDs_petsc[3*p+1] = -1; cellIDs_petsc[3*p+2] = -1;
459 status_field[p] = UNINITIALIZED;
460
461 // --- 5.d. Logging for this particle ---
462 if (particle_placed_by_this_rank) {
463 LOG_LOOP_ALLOW(LOCAL, LOG_DEBUG, p, (particlesPerProcess > 20 ? particlesPerProcess/10 : 1),
464 "InitProps - Rank %d: PID %lld (idx %ld) PLACED. Mode %d. CellOriginNode(locDAIdx):(%d,%d,%d). LogicCoords: (%.2e,%.2f,%.2f). PhysCoords: (%.6f,%.6f,%.6f).\n",
465 rank, (long long)particleIDs[p], (long)p, simCtx->ParticleInitialization,
466 ci_metric_lnode, cj_metric_lnode, ck_metric_lnode,
467 xi_metric_logic, eta_metric_logic, zta_metric_logic,
468 phys_coords.x, phys_coords.y, phys_coords.z);
469 } else {
470 LOG_LOOP_ALLOW(LOCAL, LOG_WARNING, p, (particlesPerProcess > 20 ? particlesPerProcess/10 : 1),
471 "InitProps - Rank %d: PID %lld (idx %ld) Mode %d NOT placed by this rank's logic. Default Phys: (%.2f,%.2f,%.2f). Relies on migration.\n",
472 rank, (long long)particleIDs[p], (long)p, simCtx->ParticleInitialization,
473 phys_coords.x, phys_coords.y, phys_coords.z);
474 }
475 }
476
477 // --- 6. Restore Pointers and Cleanup ---
478 ierr = DMSwarmRestoreField(swarm, "position", NULL, NULL, (void**)&positions_field); CHKERRQ(ierr);
479 ierr = DMSwarmRestoreField(swarm, "pos_phy", NULL, NULL, (void**)&pos_phy_field); CHKERRQ(ierr);
480 ierr = DMSwarmRestoreField(swarm, "DMSwarm_pid", NULL, NULL, (void**)&particleIDs); CHKERRQ(ierr);
481 ierr = DMSwarmRestoreField(swarm, "DMSwarm_CellID", NULL, NULL, (void**)&cellIDs_petsc); CHKERRQ(ierr);
482 ierr = DMSwarmRestoreField(swarm, "DMSwarm_location_status", NULL, NULL, (void**)&status_field); CHKERRQ(ierr);
483 ierr = DMDAVecRestoreArrayRead(user->fda, Coor_local, (void*)&coor_nodes_local_array); CHKERRQ(ierr);
484
485 LOG_ALLOW(LOCAL, LOG_INFO, "InitializeParticleBasicProperties - Rank %d: Completed processing for %d particles.\n",
486 rank, particlesPerProcess);
487 PetscFunctionReturn(0);
488 }
489
490 /**
491 * @brief Helper function to update a given particle’s field value.
492 *
493 * This function performs conditional, point-level updates for a swarm field based on its name.
494 * For example, you might want to initialize the "velocity" field to 0.0, but the "temperature"
495 * field to a nonzero default (e.g., 300.0). This function can be extended for other fields.
496 *
497 * @param[in] fieldName Name of the swarm field.
498 * @param[in] p Particle index.
499 * @param[in] fieldDim Dimension of the field.
500 * @param[out] fieldData Pointer to the field’s data array.
501 *
502 * @return PetscErrorCode Returns 0 on success.
503 */
504 static PetscErrorCode UpdateSwarmFieldValue(const char *fieldName, PetscInt p, PetscInt fieldDim, PetscReal *fieldData)
505 {
506 PetscFunctionBeginUser;
507 if (strcmp(fieldName, "velocity") == 0) {
508 // For velocity, initialize all components to zero
509 for (PetscInt d = 0; d < fieldDim; d++) {
510 fieldData[fieldDim * p + d] = 0.0;
511 }
512 } else if (strcmp(fieldName, "temperature") == 0) {
513 // For temperature, for example, initialize to a default value (e.g., 300.0)
514 for (PetscInt d = 0; d < fieldDim; d++) {
515 fieldData[fieldDim * p + d] = 300.0;
516 }
517 } else if (strcmp(fieldName, "Psi") == 0) {
518 // For pressure, initialize to a default value (e.g., 101325.0)
519 for (PetscInt d = 0; d < fieldDim; d++) {
520 fieldData[fieldDim * p + d] = 0.0;
521 }
522 } else {
523 // Default: initialize all components to zero
524 for (PetscInt d = 0; d < fieldDim; d++) {
525 fieldData[fieldDim * p + d] = 0.0;
526 }
527 }
528 PetscFunctionReturn(0);
529 }
530
531 /**
532 * @brief Initializes a generic swarm field with point-level updates.
533 *
534 * This field-agnostic function retrieves the specified swarm field (which may be
535 * scalar or multi-component) and initializes each particle's entry using a helper
536 * that performs conditional updates based on the field name.
537 *
538 * @param[in,out] user Pointer to the UserCtx structure containing the swarm.
539 * @param[in] fieldName Name of the swarm field to initialize.
540 * @param[in] fieldDim Dimension of the field (e.g., 1 for scalar, 3 for vector).
541 *
542 * @return PetscErrorCode Returns 0 on success, non-zero on failure.
543 */
544 static PetscErrorCode AssignInitialFieldToSwarm(UserCtx *user, const char *fieldName, PetscInt fieldDim)
545 {
546 PetscErrorCode ierr;
547 DM swarm = user->swarm;
548 PetscReal *fieldData = NULL;
549 PetscInt nLocal;
550
551 PetscFunctionBeginUser;
552
553 // Get the number of local particles
554 ierr = DMSwarmGetLocalSize(swarm, &nLocal); CHKERRQ(ierr);
555 LOG_ALLOW(LOCAL,LOG_INFO, "AssignInitialFieldToSwarm - %d local particles found.\n", nLocal);
556
557 // Retrieve the swarm field pointer for the specified fieldName
558 ierr = DMSwarmGetField(swarm, fieldName, NULL, NULL, (void**)&fieldData); CHKERRQ(ierr);
559 LOG_ALLOW(LOCAL,LOG_DEBUG, "AssignInitialFieldToSwarm - Retrieved field '%s'.\n", fieldName);
560
561 // Loop over all particles and update the field using the helper function
562 for (PetscInt p = 0; p < nLocal; p++) {
563 ierr = UpdateSwarmFieldValue(fieldName, p, fieldDim, fieldData); CHKERRQ(ierr);
564 PetscReal disp_data[fieldDim];
565
566 for (PetscInt d = 0; d < fieldDim; d++) {
567 disp_data[d] = fieldData[fieldDim* p + d];
568 }
569 LOG_LOOP_ALLOW(LOCAL,LOG_DEBUG,p, 100," Particle %d: %s[%d] = [%.6f, ...,%.6f].\n", p,fieldName,fieldDim,disp_data[0],disp_data[fieldDim-1]);
570 }
571
572 // Restore the swarm field pointer
573 ierr = DMSwarmRestoreField(swarm, fieldName, NULL, NULL, (void**)&fieldData); CHKERRQ(ierr);
574 LOG_ALLOW(LOCAL,LOG_INFO, "AssignInitialFieldToSwarm - Initialization of field '%s' complete.\n", fieldName);
575
576 PetscFunctionReturn(0);
577 }
578
579 /**
580 * @brief Initializes all particle properties in the swarm.
581 *
582 * This function orchestrates the initialization of particle properties.
583 * It first determines the inlet face if surface initialization (Mode 0) is selected
584 * by parsing "bcs.dat".
585 * Then, it initializes basic particle properties (physical position, Particle ID,
586 * and placeholder Cell IDs) by calling `InitializeParticleBasicProperties`. This call
587 * uses the provided `rand_logic_i/j/k` RNGs, which must be pre-initialized for [0,1).
588 * The `rand_phys_x/y/z` RNGs (physically bounded) are passed but may not be used by
589 * `InitializeParticleBasicProperties` for position setting if all initialization paths
590 * use logical-to-physical mapping.
591 * Finally, it calls helper functions to initialize other registered swarm fields
592 * like "velocity", "weight", and "Psi" (scalar) to default values.
593 *
594 * @param[in,out] user Pointer to the `UserCtx` structure.
595 * @param[in] particlesPerProcess Number of particles assigned to this MPI process.
596 * @param[in] rand_phys_x RNG for physical x-coordinates (from `InitializeRandomGenerators`).
597 * @param[in] rand_phys_y RNG for physical y-coordinates (from `InitializeRandomGenerators`).
598 * @param[in] rand_phys_z RNG for physical z-coordinates (from `InitializeRandomGenerators`).
599 * @param[in] rand_logic_i RNG for i-logical dimension tasks [0,1) (from `InitializeLogicalSpaceRNGs`).
600 * @param[in] rand_logic_j RNG for j-logical dimension tasks [0,1) (from `InitializeLogicalSpaceRNGs`).
601 * @param[in] rand_logic_k RNG for k-logical dimension tasks [0,1) (from `InitializeLogicalSpaceRNGs`).
602 * @param[in] bboxlist Array of BoundingBox structures (potentially unused by IPBP).
603 *
604 * @return PetscErrorCode Returns 0 on success, non-zero on failure.
605 */
607 PetscInt particlesPerProcess,
608 PetscRandom *rand_phys_x, // RNG from original InitializeRandomGenerators
609 PetscRandom *rand_phys_y, // RNG from original InitializeRandomGenerators
610 PetscRandom *rand_phys_z, // RNG from original InitializeRandomGenerators
611 PetscRandom *rand_logic_i, // RNG from InitializeLogicalSpaceRNGs
612 PetscRandom *rand_logic_j, // RNG from InitializeLogicalSpaceRNGs
613 PetscRandom *rand_logic_k, // RNG from InitializeLogicalSpaceRNGs
614 BoundingBox *bboxlist)
615 {
616 PetscErrorCode ierr;
617 PetscFunctionBeginUser;
618
619 SimCtx *simCtx = user->simCtx;
620
621 // --- 0. Input Validation ---
622 if (!user || !bboxlist || !rand_logic_i || !rand_logic_j || !rand_logic_k || !rand_phys_x || !rand_phys_y || !rand_phys_z) {
623 // Check all RNGs now as they are passed in
624 LOG_ALLOW(GLOBAL, LOG_ERROR, "AssignInitialPropertiesToSwarm - Null user, bboxlist, or RNG pointer.\n");
625 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "AssignInitialPropertiesToSwarm - Null input detected.");
626 }
627
628 LOG_ALLOW(GLOBAL, LOG_INFO, "AssignInitialPropertiesToSwarm - Initializing swarm with %d particles per process. Mode: %d.\n",
629 particlesPerProcess, simCtx->ParticleInitialization);
630
631 // --- 1. Parse BCS File for Inlet Information (if Mode 0) ---
632 if (simCtx->ParticleInitialization == 0) {
633 if(user->inletFaceDefined == PETSC_FALSE){
634 LOG_ALLOW(GLOBAL, LOG_ERROR, "AssignInitialPropertiesToSwarm - ParticleInitialization Mode 0 (Surface Init) selected, but no INLET face was identified from bcs.dat. Cannot proceed.\n");
635 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE, "ParticleInitialization Mode 0 requires an INLET face to be defined in bcs.dat.");
636 }else{
637 LOG_ALLOW(GLOBAL, LOG_INFO, "AssignInitialPropertiesToSwarm - After ParseBCSFileForInlet, identifiedInletBCFace = %d\n", user->identifiedInletBCFace);
638 }
639 }
640
641 // --- 2. Initialize Basic Particle Properties (Position, PID, Cell IDs placeholder) ---
642 // The rand_logic_i/j/k are now passed directly.
643 // The rand_phys_x/y/z are passed but InitializeParticleBasicProperties (refactored version)
644 // will not use them for setting positions if all its paths use logical-to-physical mapping.
645 LOG_ALLOW(LOCAL, LOG_DEBUG, "AssignInitialPropertiesToSwarm - Calling InitializeParticleBasicProperties.\n");
646 ierr = InitializeParticleBasicProperties(user, particlesPerProcess,
647 rand_logic_i, rand_logic_j, rand_logic_k,
648 bboxlist); // bboxlist passed along
649 CHKERRQ(ierr);
650 LOG_ALLOW(LOCAL, LOG_INFO, "AssignInitialPropertiesToSwarm - Successfully initialized basic particle properties.\n");
651
652 // Note: The logical RNGs (rand_logic_i/j/k) are NOT destroyed here.
653 // They were created externally (e.g., by InitializeLogicalSpaceRNGs) and
654 // should be destroyed externally (e.g., in FinalizeSwarmSetup).
655 // Same for rand_phys_x/y/z.
656
657 // --- 3. Initialize Other Swarm Fields (Velocity, Weight, Pressure, etc.) ---
658 LOG_ALLOW(LOCAL, LOG_DEBUG, "AssignInitialPropertiesToSwarm - Initializing 'velocity' field.\n");
659 ierr = AssignInitialFieldToSwarm(user, "velocity", 3); CHKERRQ(ierr);
660 LOG_ALLOW(LOCAL, LOG_INFO, "AssignInitialPropertiesToSwarm - 'velocity' field initialization complete.\n");
661
662 LOG_ALLOW(LOCAL, LOG_DEBUG, "AssignInitialPropertiesToSwarm - Initializing 'weight' field.\n");
663 ierr = AssignInitialFieldToSwarm(user, "weight", 3); CHKERRQ(ierr); // Assuming weight is vec3
664 LOG_ALLOW(LOCAL, LOG_INFO, "AssignInitialPropertiesToSwarm - 'weight' field initialization complete.\n");
665
666 LOG_ALLOW(LOCAL, LOG_DEBUG, "AssignInitialPropertiesToSwarm - Initializing 'P' (Pressure) field.\n");
667 ierr = AssignInitialFieldToSwarm(user, "Psi", 1); CHKERRQ(ierr);
668 LOG_ALLOW(LOCAL, LOG_INFO, "AssignInitialPropertiesToSwarm - 'P' field initialization complete.\n");
669
670 LOG_ALLOW(GLOBAL, LOG_INFO, "AssignInitialPropertiesToSwarm - Successfully completed all swarm property initialization.\n");
671
672 PetscFunctionReturn(0);
673 }
674
675 /**
676 * @brief Distributes particles evenly across MPI processes, handling any remainders.
677 *
678 * This function calculates the number of particles each MPI process should handle,
679 * distributing the remainder particles to the first few ranks if necessary.
680 *
681 * @param[in] numParticles Total number of particles to create across all MPI processes.
682 * @param[in] rank MPI rank of the current process.
683 * @param[in] size Total number of MPI processes.
684 * @param[out] particlesPerProcess Number of particles assigned to the current MPI process.
685 * @param[out] remainder Remainder particles when dividing numParticles by size.
686 *
687 * @return PetscErrorCode Returns 0 on success, non-zero on failure.
688 */
689 PetscErrorCode DistributeParticles(PetscInt numParticles, PetscMPIInt rank, PetscMPIInt size, PetscInt* particlesPerProcess, PetscInt* remainder) {
690
691 // Calculate the base number of particles per process
692 *particlesPerProcess = numParticles / size;
693 *remainder = numParticles % size;
694
695 // Distribute the remainder particles to the first 'remainder' ranks
696 if (rank < *remainder) {
697 *particlesPerProcess += 1;
698 LOG_ALLOW_SYNC(GLOBAL,LOG_INFO,"DistributeParticles - Rank %d receives an extra particle. Total: %d\n", rank, *particlesPerProcess);
699 } else {
700 LOG_ALLOW_SYNC(GLOBAL,LOG_INFO, "DistributeParticles - Rank %d receives %d particles.\n", rank, *particlesPerProcess);
701 }
702
703 return 0;
704 }
705
706 /**
707 * @brief Finalizes the swarm setup by destroying random generators and logging completion.
708 *
709 * This function cleans up resources by destroying random number generators and LOG_ALLOWs the completion of swarm setup.
710 *
711 * @param[in] randx Random number generator for the x-coordinate.
712 * @param[in] randy Random number generator for the y-coordinate.
713 * @param[in] randz Random number generator for the z-coordinate.
714 * @param[in] rand_logic_i Random number generator for the xi-coordinate.
715 * @param[in] rand_logic_j Random number generator for the eta-coordinate.
716 * @param[in] rand_logic_k Random number generator for the zeta-coordinate.
717 *
718 * @return PetscErrorCode Returns 0 on success, non-zero on failure.
719 */
720 PetscErrorCode FinalizeSwarmSetup(PetscRandom *randx, PetscRandom *randy, PetscRandom *randz, PetscRandom *rand_logic_i, PetscRandom *rand_logic_j, PetscRandom *rand_logic_k) {
721 PetscErrorCode ierr; // Error code for PETSc functions
722 PetscInt ParticleInitialization;
723
724 ierr = PetscOptionsGetInt(NULL, NULL, "-pinit", &ParticleInitialization, NULL); CHKERRQ(ierr);
725
726 // Destroy random number generators to free resources
727 // Physical space
728 ierr = PetscRandomDestroy(randx); CHKERRQ(ierr);
729 ierr = PetscRandomDestroy(randy); CHKERRQ(ierr);
730 ierr = PetscRandomDestroy(randz); CHKERRQ(ierr);
731
732 // Logical space
733 ierr = PetscRandomDestroy(rand_logic_i); CHKERRQ(ierr);
734 ierr = PetscRandomDestroy(rand_logic_j); CHKERRQ(ierr);
735 ierr = PetscRandomDestroy(rand_logic_k); CHKERRQ(ierr);
736
737 LOG_ALLOW(LOCAL,LOG_DEBUG,"FinalizeSwarmSetup - Destroyed all random number generators.\n");
738
739 return 0;
740 }
741
742 /**
743 * @brief Creates and initializes a Particle Swarm.
744 *
745 * This function sets up a DMSwarm within the provided UserCtx structure, initializes
746 * particle fields, and distributes particles across MPI processes. It ensures that
747 * the number of particles is evenly divided among the available MPI ranks. If the total
748 * number of particles isn't divisible by the number of processes, the remainder is distributed
749 * to the first few ranks.
750 *.
751 *
752 * @param[in,out] user Pointer to the UserCtx structure containing the simulation context.
753 * @param[in] numParticles Total number of particles to create across all MPI processes.
754 * @param[in] bboxlist Pointer to an array of BoundingBox structures, one per rank.
755 *
756 * @param[in] particlesPerProcess
757 * @return PetscErrorCode Returns 0 on success, non-zero on failure.
758 *
759 * @note
760 * - Ensure that `numParticles` is a positive integer.
761 * - The `control.dat` file should contain necessary PETSc options.
762 * - The `bboxlist` array should be properly populated before calling this function.
763 */
764 PetscErrorCode CreateParticleSwarm(UserCtx *user, PetscInt numParticles, PetscInt *particlesPerProcess, BoundingBox *bboxlist) {
765 PetscErrorCode ierr; // PETSc error handling variable
766 PetscMPIInt rank, size; // Variables to store MPI rank and size
767 PetscInt remainder = 0; // Remainder of particles after division
768
769 // Validate input parameters
770 if (numParticles <= 0) {
771 LOG_ALLOW(GLOBAL,LOG_DEBUG, "CreateParticleSwarm - Number of particles must be positive. Given: %d\n", numParticles);
772 return PETSC_ERR_ARG_OUTOFRANGE;
773 }
774
775 // Insert PETSc options from "control.dat" into the PETSc options database
776 // ierr = PetscOptionsInsertFile(PETSC_COMM_WORLD, NULL, "control.dat", PETSC_TRUE); CHKERRQ(ierr);
777 // LOG_ALLOW(LOCAL,LOG_DEBUG,"CreateParticleSwarm - Inserted options from control.dat\n");
778
779 LOG_ALLOW_SYNC(GLOBAL,LOG_DEBUG, "CreateParticleSwarm - Domain dimensions: Min_X=%.2f, Max_X=%.2f,Min_Y=%.2f, Max_Y=%.2f,Min_Z=%.2f, Max_Z=%.2f \n",
780 user->Min_X,user->Max_X,user->Min_Y,user->Max_Y, user->Min_Z,user->Max_Z);
781
782 // Retrieve MPI rank and size
783 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
784 ierr = MPI_Comm_size(PETSC_COMM_WORLD, &size); CHKERRQ(ierr);
785 LOG_ALLOW(GLOBAL,LOG_INFO, "CreateParticleSwarm - Rank %d out of %d processes.\n", rank, size);
786
787 // Distribute particles among MPI processes
788 ierr = DistributeParticles(numParticles, rank, size, particlesPerProcess, &remainder); CHKERRQ(ierr);
789
790 // Initialize the DMSwarm - creates the swarm, sets the type and dimension
791 ierr = InitializeSwarm(user); CHKERRQ(ierr);
792
793 if (user->da) {
794 ierr = DMSwarmSetCellDM(user->swarm, user->da); CHKERRQ(ierr);
795 LOG_ALLOW(LOCAL,LOG_INFO,"CreateParticleSwarm - Associated DMSwarm with Cell DM (user->da).\n");
796 } else {
797 // If user->da is essential for your simulation logic with particles, this should be a fatal error.
798 LOG_ALLOW(GLOBAL, LOG_WARNING, "CreateParticleSwarm - user->da (Cell DM for Swarm) is NULL. Cell-based swarm operations might fail.\n");
799 // SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE, "CreateParticleSwarm - user->da (Cell DM) is NULL but required.");
800 }
801
802 // Register particle fields (position, velocity, CellID, weight, etc.)
803 ierr = RegisterParticleFields(user->swarm); CHKERRQ(ierr);
804
805 // Set the local number of particles for this rank and additional buffer for particle migration
806 ierr = DMSwarmSetLocalSizes(user->swarm, *particlesPerProcess, numParticles); CHKERRQ(ierr);
807 LOG_ALLOW(GLOBAL,LOG_INFO, "CreateParticleSwarm - Set local swarm size: %d particles.\n", *particlesPerProcess);
808
809 // Optionally, LOG_ALLOW detailed DM info in debug mode
810 if (get_log_level() == LOG_DEBUG && is_function_allowed(__func__)) {
811 LOG_ALLOW(LOCAL,LOG_DEBUG,"CreateParticleSwarm - Viewing DMSwarm:\n");
812 ierr = DMView(user->swarm, PETSC_VIEWER_STDOUT_WORLD); CHKERRQ(ierr);
813 }
814
815 LOG_ALLOW(LOCAL,LOG_INFO, "CreateParticleSwarm - Particle swarm creation and initialization complete.\n");
816
817 return 0;
818 }
819
820
821 /**
822 * @brief Initializes a Particle struct with data from DMSwarm fields.
823 *
824 * This helper function populates a Particle structure using data retrieved from DMSwarm fields.
825 *
826 * @param[in] i Index of the particle in the DMSwarm.
827 * @param[in] PIDs Pointer to the array of particle IDs.
828 * @param[in] weights Pointer to the array of particle weights.
829 * @param[in] positions Pointer to the array of particle positions.
830 * @param[in] cellIndices Pointer to the array of particle cell indices.
831 * @param[in] velocities Pointer to the array of particle velocities.
832 * @param[in] LocStatus Pointer to the array of cell location status indicators.
833 * @param[out] particle Pointer to the Particle struct to initialize.
834 *
835 * @return PetscErrorCode Returns `0` on success, non-zero on failure.
836 */
837 PetscErrorCode UnpackSwarmFields(PetscInt i, const PetscInt64 *PIDs, const PetscReal *weights,
838 const PetscReal *positions, const PetscInt *cellIndices,
839 PetscReal *velocities,PetscInt *LocStatus,Particle *particle) {
840 PetscFunctionBeginUser;
841 PetscMPIInt rank;
842 PetscErrorCode ierr;
843
844 ierr = MPI_Comm_rank(PETSC_COMM_WORLD,&rank); CHKERRQ(ierr);
845
846 if (particle == NULL) {
847 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Output Particle pointer is NULL. \n");
848 }
849
850 // logging the start of particle initialization
851 LOG_ALLOW(LOCAL,LOG_INFO, "[Rank %d]Unpacking Particle [%d] with PID: %ld.\n",rank, i, PIDs[i]);
852
853 // Initialize PID
854 particle->PID = PIDs[i];
855 LOG_ALLOW(LOCAL,LOG_DEBUG, "[Rank %d]Particle [%d] PID set to: %ld.\n", rank,i, particle->PID);
856
857 // Initialize weights
858 particle->weights.x = weights[3 * i];
859 particle->weights.y = weights[3 * i + 1];
860 particle->weights.z = weights[3 * i + 2];
861 LOG_ALLOW(LOCAL,LOG_DEBUG, "[Rank %d]Particle [%d] weights set to: (%.6f, %.6f, %.6f).\n",
862 rank,i, particle->weights.x, particle->weights.y, particle->weights.z);
863
864 // Initialize locations
865 particle->loc.x = positions[3 * i];
866 particle->loc.y = positions[3 * i + 1];
867 particle->loc.z = positions[3 * i + 2];
868 LOG_ALLOW(LOCAL,LOG_DEBUG, "[Rank %d]Particle [%d] location set to: (%.6f, %.6f, %.6f).\n",
869 rank,i, particle->loc.x, particle->loc.y, particle->loc.z);
870
871 // Initialize velocities (assuming default zero; modify if necessary)
872 particle->vel.x = velocities[3 * i];
873 particle->vel.y = velocities[3 * i + 1];
874 particle->vel.z = velocities[3 * i + 2];
875 LOG_ALLOW(LOCAL,LOG_DEBUG,"[Rank %d]Particle [%d] velocities unpacked to: [%.6f,%.6f,%.6f].\n",rank, i,particle->vel.x,particle->vel.y,particle->vel.z);
876
877 // Initialize cell indices
878 particle->cell[0] = cellIndices[3 * i];
879 particle->cell[1] = cellIndices[3 * i + 1];
880 particle->cell[2] = cellIndices[3 * i + 2];
881 LOG_ALLOW(LOCAL,LOG_DEBUG,"[Rank %d]Particle [%d] cell indices set to: [%d, %d, %d].\n",rank,i, particle->cell[0], particle->cell[1], particle->cell[2]);
882
883 particle->location_status = (ParticleLocationStatus)LocStatus[i];
884 LOG_ALLOW(LOCAL,LOG_DEBUG, "[Rank %d]Particle [%d] Status set to: %d.\n",rank, i, particle->location_status);
885
886 // The destination_rank is only set by the location search, not read from the swarm,
887 // so we initialize it to a known invalid state.
888 particle->destination_rank = MPI_PROC_NULL;
889
890 // logging the completion of particle initialization
891 LOG_ALLOW(LOCAL,LOG_INFO,"[Rank %d]Completed initialization of Particle [%d]. \n", rank,i);
892
893 PetscFunctionReturn(0);
894 }
895
896 /**
897 * @brief Updates DMSwarm fields with data from a Particle struct.
898 *
899 * This helper function writes back the modified Particle data to the corresponding DMSwarm fields.
900 *
901 * @param[in] i Index of the particle in the DMSwarm.
902 * @param[in] particle Pointer to the Particle struct containing updated data.
903 * @param[in,out] weights Pointer to the array of particle weights.
904 * @param[in,out] cellIndices Pointer to the array of particle cell indices.
905 * @param[in,out] LocStatus Pointer to the array of cell location status indicators.
906 *
907 * @return PetscErrorCode Returns `0` on success, non-zero on failure.
908 */
909 PetscErrorCode UpdateSwarmFields(PetscInt i, const Particle *particle,
910 PetscReal *weights, PetscInt *cellIndices, PetscInt *status_field) {
911 PetscFunctionBeginUser;
912
913 if (particle == NULL) {
914 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Input Particle pointer is NULL.\n");
915 }
916
917 // logging the start of swarm fields update
918 LOG_ALLOW(LOCAL,LOG_INFO,"Updating DMSwarm fields for Particle [%d].\n", i);
919
920 // Update weights
921 weights[3 * i] = particle->weights.x;
922 weights[3 * i + 1] = particle->weights.y;
923 weights[3 * i + 2] = particle->weights.z;
924 LOG_ALLOW(LOCAL,LOG_DEBUG,"Updated weights for Particle [%d]: (%.6f, %.6f, %.6f).\n",
925 i, weights[3 * i], weights[3 * i + 1], weights[3 * i + 2]);
926
927 // Update cell indices
928 cellIndices[3 * i] = particle->cell[0];
929 cellIndices[3 * i + 1] = particle->cell[1];
930 cellIndices[3 * i + 2] = particle->cell[2];
931 LOG_ALLOW(LOCAL,LOG_DEBUG, "Updated cell indices for Particle [%d]: [%d, %d, %d].\n",
932 i, cellIndices[3 * i], cellIndices[3 * i + 1], cellIndices[3 * i + 2]);
933
934 status_field[i] = particle->location_status;
935 LOG_ALLOW(LOCAL,LOG_DEBUG, "Updated location status for Particle [%d]: [%d].\n",
936 i, status_field[i]);
937
938 // logging the completion of swarm fields update
939 LOG_ALLOW(LOCAL,LOG_INFO,"Completed updating DMSwarm fields for Particle [%d].\n", i);
940
941 PetscFunctionReturn(0);
942 }
943
944 /**
945 * @brief Checks if a particle's location is within a specified bounding box.
946 *
947 * This function determines whether the given particle's location lies inside the provided bounding box.
948 * It performs an axis-aligned bounding box (AABB) check by comparing the particle's coordinates to the
949 * minimum and maximum coordinates of the bounding box in each dimension (x, y, z).
950 *
951 * logging statements are included to provide detailed information about the function's execution.
952 *
953 * @param[in] bbox Pointer to the BoundingBox structure containing minimum and maximum coordinates.
954 * @param[in] particle Pointer to the Particle structure containing the particle's location and identifier.
955 *
956 * @return PetscBool Returns `PETSC_TRUE` if the particle is inside the bounding box, `PETSC_FALSE` otherwise.
957 *
958 * @note
959 * - The function assumes that the `bbox` and `particle` pointers are valid and non-NULL.
960 * - The function includes logging statements that start with the function name.
961 * - The `LOG_ALLOW_SCOPE` variable is used to distinguish between `GLOBAL` and `LOCAL` LOG_ALLOW outputs.
962 * - Be cautious when logging in performance-critical code sections, especially if the function is called frequently.
963 */
964 PetscBool IsParticleInsideBoundingBox(const BoundingBox *bbox, const Particle *particle)
965 {
966 // Function name for logging purposes
967 const char *funcName = "IsParticleInsideBoundingBox";
968
969 // Validate input pointers
970 if (!bbox) {
971 // LOG_ALLOW error message and return PETSC_FALSE
972 LOG_ALLOW(LOCAL,LOG_ERROR, "%s: Error - 'bbox' pointer is NULL.", funcName);
973 return PETSC_FALSE;
974 }
975 if (!particle) {
976 LOG_ALLOW(LOCAL,LOG_ERROR,"%s: Error - 'particle' pointer is NULL.", funcName);
977 return PETSC_FALSE;
978 }
979
980 // Extract particle location and bounding box coordinates
981 const Cmpnts loc = particle->loc;
982 const Cmpnts min_coords = bbox->min_coords;
983 const Cmpnts max_coords = bbox->max_coords;
984
985 // LOG_ALLOW the particle location and bounding box coordinates for debugging
986 LOG_ALLOW_SYNC(LOCAL, LOG_DEBUG, "%s: Particle PID %ld location: (%.6f, %.6f, %.6f).\n", funcName, particle->PID, loc.x, loc.y, loc.z);
987 LOG_ALLOW_SYNC(LOCAL, LOG_DEBUG, "%s: BoundingBox min_coords: (%.6f, %.6f, %.6f), max_coords: (%.6f, %.6f, %.6f).\n",
988 funcName, min_coords.x, min_coords.y, min_coords.z, max_coords.x, max_coords.y, max_coords.z);
989
990 // Check if the particle's location is within the bounding box
991 if ((loc.x >= min_coords.x && loc.x <= max_coords.x) &&
992 (loc.y >= min_coords.y && loc.y <= max_coords.y) &&
993 (loc.z >= min_coords.z && loc.z <= max_coords.z)) {
994 // Particle is inside the bounding box
995 LOG_ALLOW_SYNC(LOCAL,LOG_DEBUG, "%s: Particle PID %ld is inside the bounding box.\n", funcName, particle->PID);
996 return PETSC_TRUE;
997 }
998
999 // Particle is outside the bounding box
1000 LOG_ALLOW_SYNC(LOCAL, LOG_DEBUG,"%s: Particle PID %ld is outside the bounding box.\n", funcName, particle->PID);
1001 return PETSC_FALSE;
1002 }
1003
1004 /**
1005 * @brief Updates a particle's interpolation weights based on distances to cell faces.
1006 *
1007 * This function computes interpolation weights using distances to the six
1008 * cell faces (`d`) and updates the `weight` field of the provided particle.
1009 *
1010 * @param[in] d Pointer to an array of distances to the six cell faces.
1011 * @param[out] particle Pointer to the Particle structure whose weights are to be updated.
1012 *
1013 * @return PetscErrorCode Returns 0 on success, or a non-zero error code on failure.
1014 */
1015 PetscErrorCode UpdateParticleWeights(PetscReal *d, Particle *particle) {
1016
1017 // Validate input pointers
1018 if (!d || !particle) {
1019 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
1020 "UpdateParticleWeights - Null pointer argument (d or particle).");
1021 }
1022
1023
1024 // Validate distances
1025 for (PetscInt i = LEFT; i < NUM_FACES; i++) {
1028 "UpdateParticleWeights - face distance d[%d] = %f <= %f; "
1029 "clamping to 1e-14 to avoid zero/negative.\n",
1030 i, (double)d[i], INTERPOLATION_DISTANCE_TOLERANCE);
1032 }
1033 }
1034
1035 // LOG_ALLOW the input distances
1037 "UpdateParticleWeights - Calculating weights with distances: "
1038 "[LEFT=%f, RIGHT=%f, BOTTOM=%f, TOP=%f, FRONT=%f, BACK=%f].\n",
1039 d[LEFT], d[RIGHT], d[BOTTOM], d[TOP], d[FRONT], d[BACK]);
1040
1041 // Compute and update the particle's weights
1042 particle->weights.x = d[LEFT] / (d[LEFT] + d[RIGHT]);
1043 particle->weights.y = d[BOTTOM] / (d[BOTTOM] + d[TOP]);
1044 particle->weights.z = d[BACK] / (d[FRONT] + d[BACK]);
1045
1046 // LOG_ALLOW the updated weights
1048 "UpdateParticleWeights - Updated particle weights: x=%f, y=%f, z=%f.\n",
1049 particle->weights.x, particle->weights.y, particle->weights.z);
1050
1051 return 0;
1052 }
1053
1054
1055 /**
1056 * @brief Resets the location-dependent state of a loaded swarm to force relocation.
1057 * @ingroup ParticleRestart
1058 *
1059 * This function is a critical part of the simulation restart procedure. It must be
1060 * called immediately after `ReadAllSwarmFields` has populated a swarm from restart
1061 * files. Its purpose is to invalidate the "location" state of the loaded particles,
1062 * ensuring that the `LocateAllParticlesInGrid_TEST` orchestrator performs a fresh,
1063 * comprehensive search for every particle based on its loaded position.
1064 *
1065 * It does this by performing two actions on every locally-owned particle:
1066 * 1. It resets the `DMSwarm_CellID` field to a sentinel value of `(-1, -1, -1)`.
1067 * This invalidates any cell index that might have been loaded or defaulted to 0.
1068 * 2. It sets the `DMSwarm_location_status` field to `NEEDS_LOCATION`.
1069 *
1070 * This guarantees that the location logic will not mistakenly use a stale cell index
1071 * from a previous run and will instead use the robust "Guess -> Verify" strategy
1072 * appropriate for particles with unknown locations.
1073 *
1074 * @param[in,out] user Pointer to the UserCtx structure which contains the `DMSwarm` object
1075 * that has just been loaded with data from restart files.
1076 * @return PetscErrorCode 0 on success, or a non-zero PETSc error code if field access fails.
1077 */
1079 {
1080 PetscErrorCode ierr;
1081 DM swarm;
1082 PetscInt n_local;
1083 PetscInt *cell_p; // Pointer to the raw data for the CellID field
1084 PetscInt *status_p; // Pointer to the raw data for the location_status field
1085 PetscInt64 *PIDs; // Pointer to the raw data for the Particle ID field.
1086 PetscMPIInt rank,size;
1087
1088 PetscFunctionBeginUser;
1089
1090 ierr = MPI_Comm_rank(PETSC_COMM_WORLD,&rank); CHKERRQ(ierr);
1091 ierr = MPI_Comm_size(PETSC_COMM_WORLD,&size); CHKERRQ(ierr);
1092
1093 SimCtx *simCtx = user->simCtx;
1094
1095 // --- 1. Input Validation and Setup ---
1096 if (!user || !user->swarm) {
1097 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx or its DMSwarm is NULL in PrepareLoadedSwarmForRelocation.");
1098 }
1099 swarm = user->swarm;
1100
1101 // Get the number of particles on this MPI rank.
1102 ierr = DMSwarmGetLocalSize(swarm, &n_local); CHKERRQ(ierr);
1103
1104 // If there are no local particles, there is nothing to do.
1105 if (n_local == 0) {
1106 PetscFunctionReturn(0);
1107 }
1108
1109
1110 LOG_ALLOW(GLOBAL, LOG_INFO, "Preparing %d loaded particles for relocation by resetting their CellID and Status.\n", n_local);
1111
1112 // --- 2. Get Writable Access to Swarm Fields ---
1113 // This provides direct pointers to the underlying data arrays for the fields.
1114 ierr = DMSwarmGetField(swarm, "DMSwarm_CellID", NULL, NULL, (void**)&cell_p); CHKERRQ(ierr);
1115 ierr = DMSwarmGetField(swarm, "DMSwarm_location_status", NULL, NULL, (void**)&status_p); CHKERRQ(ierr);
1116 ierr = DMSwarmGetField(swarm, "DMSwarm_pid", NULL, NULL, (void**)&PIDs); CHKERRQ(ierr); CHKERRQ(ierr);
1117
1118 // --- 3. Determine Starting Global PID for this Rank ---
1119 PetscInt particles_per_rank_ideal = simCtx->np / size; // Assumes user->size is PETSC_COMM_WORLD size
1120 PetscInt remainder_particles = simCtx->np % size;
1121 PetscInt base_pid_for_rank = rank * particles_per_rank_ideal + PetscMin(rank, remainder_particles);
1122 // This calculation must match how particlesPerProcess was determined (e.g., in DistributeParticles).
1123
1124 // --- 4. Loop Through All Local Particles and Reset State ---
1125 for (PetscInt p = 0; p < n_local; ++p) {
1126
1127
1128 // Reset the 3 components of the cell index vector.
1129 cell_p[3*p + 0] = -1;
1130 cell_p[3*p + 1] = -1;
1131 cell_p[3*p + 2] = -1;
1132
1133 // Reset the status to ensure it will be processed by the location algorithm.
1135
1136 //set the PID for each particle
1137 PIDs[p] = (PetscInt64)base_pid_for_rank + p;
1138 }
1139
1140 // --- 4. Restore Fields ---
1141 // This returns control of the data arrays back to the DMSwarm. It is a mandatory
1142 // step to ensure data consistency and prevent memory issues.
1143 ierr = DMSwarmRestoreField(swarm, "DMSwarm_CellID", NULL, NULL, (void**)&cell_p); CHKERRQ(ierr);
1144 ierr = DMSwarmRestoreField(swarm, "DMSwarm_location_status", NULL, NULL, (void**)&status_p); CHKERRQ(ierr);
1145 ierr = DMSwarmRestoreField(swarm, "DMSwarm_pid", NULL, NULL, (void**)&PIDs); CHKERRQ(ierr); CHKERRQ(ierr);
1146
1147 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Successfully reset location state for all loaded particles.\n");
1148
1149 PetscFunctionReturn(0);
1150 }
1151
1152
1153 /**
1154 * @brief Perform particle swarm initialization, particle-grid interaction, and related operations.
1155 *
1156 * This function handles the following tasks:
1157 * 1. Initializes the particle swarm using the provided bounding box list (bboxlist) to determine initial placement
1158 * if ParticleInitialization is 0.
1159 * 2. Locates particles within the computational grid.
1160 * 3. Updates particle positions based on grid interactions (if such logic exists elsewhere in the code).
1161 * 4. Interpolates particle velocities from grid points using trilinear interpolation.
1162 *
1163 * @param[in,out] user Pointer to the UserCtx structure containing grid and particle swarm information.
1164 * @param[in] np Number of particles to initialize in the swarm.
1165 * @param[in] bboxlist Pointer to an array of BoundingBox structures, one per MPI rank.
1166 *
1167 * @return PetscErrorCode Returns 0 on success, non-zero on failure.
1168 *
1169 * @note
1170 * - Ensure that `np` (number of particles) is positive.
1171 * - The `bboxlist` array must be correctly computed and passed in before calling this function.
1172 * - If ParticleInitialization == 0, particles will be placed at the midpoint of the local bounding box.
1173 */
1174 PetscErrorCode InitializeParticleSwarm(SimCtx *simCtx)
1175 {
1176
1177 PetscErrorCode ierr;
1178 PetscInt particlesPerProcess = 0; // Number of particles assigned to the local MPI process.
1179 PetscRandom randx,randy,randz; // Random number generators[x,y,z]. (used if ParticleInitialization==1).
1180 PetscRandom rand_logic_i, rand_logic_j,rand_logic_k; // RNGs for Logical space.
1181 PetscInt start_step = simCtx->StartStep;
1182 PetscInt np = simCtx->np;
1183 BoundingBox *bboxlist = simCtx->bboxlist;
1184 UserCtx *user = simCtx->usermg.mgctx[simCtx->usermg.mglevels - 1].user;
1185
1186 LOG_ALLOW(GLOBAL, LOG_INFO, "Starting particle swarm Initialization with %d particles.\n", np);
1187
1188 // Step 1: Create and initialize the particle swarm
1189 // Here we pass in the bboxlist, which will be used by CreateParticleSwarm() and subsequently
1190 // by AssignInitialProperties() to position particles if ParticleInitialization == 0.
1191 LOG_ALLOW(GLOBAL, LOG_INFO, "Creating particle swarm.\n");
1192 ierr = CreateParticleSwarm(user, np, &particlesPerProcess,bboxlist); CHKERRQ(ierr);
1193 LOG_ALLOW(GLOBAL, LOG_INFO, "Particle swarm created successfully.\n");
1194
1195 ierr = PetscOptionsGetInt(NULL, NULL, "-rstart", &start_step, NULL); CHKERRQ(ierr);
1196
1197 if(start_step == 0){
1198
1199 // Create the RNGs
1200 LOG_ALLOW(LOCAL, LOG_DEBUG, "Initializing physical space RNGs.\n");
1201 ierr = InitializeRandomGenerators(user, &randx, &randy, &randz); CHKERRQ(ierr);
1202
1203 LOG_ALLOW(LOCAL, LOG_DEBUG, "Initializing logical space RNGs [0,1).\n");
1204 ierr = InitializeLogicalSpaceRNGs(&rand_logic_i, &rand_logic_j, &rand_logic_k); CHKERRQ(ierr);
1205 // Assign initial properties to particles
1206 // The bboxlist array is passed here so that if ParticleInitialization == 0,
1207 // particles can be placed at the midpoint of the local bounding box corresponding to this rank.
1208 ierr = AssignInitialPropertiesToSwarm(user, particlesPerProcess, &randx, &randy, &randz, &rand_logic_i,&rand_logic_j,&rand_logic_k,bboxlist); CHKERRQ(ierr);
1209 // Finalize swarm setup by destroying RNGs if ParticleInitialization == 1
1210 ierr = FinalizeSwarmSetup(&randx, &randy, &randz, &rand_logic_i, &rand_logic_j, &rand_logic_k); CHKERRQ(ierr);
1211
1212 // Ensure all ranks complete before proceeding
1213 LOG_ALLOW_SYNC(GLOBAL, LOG_INFO, " Particles generated & initialized.\n");
1214 }
1215
1216 else if (start_step > 0){
1217
1218 LOG_ALLOW(LOCAL,LOG_DEBUG," Particle Swarm status being set at restart [Step %d].\n",start_step);
1219
1220 ierr = PreCheckAndResizeSwarm(user,start_step,"dat");
1221
1222 // --- Read Particle Data (EVERY timestep) ---
1223 // ReadAllSwarmFields should read position and velocity for timestep 'ti'
1224
1225 ierr = ReadAllSwarmFields(user, start_step);
1226 if (ierr) {
1227 // Check if the error was specifically a file open error (missing file)
1228 if (ierr == PETSC_ERR_FILE_OPEN) {
1229 LOG_ALLOW(GLOBAL, LOG_WARNING, "Missing particle data file(s) for timestep %d. Skipping VTK output for this step.\n",(PetscInt)user->simCtx->step );
1230 } else {
1231 LOG_ALLOW(GLOBAL, LOG_ERROR, "Failed to read swarm fields for timestep %d (Error code: %d). Skipping VTK output for this step.\n",(PetscInt)user->simCtx->step , ierr);
1232 }
1233 }
1234
1235 // Ensure all the swarm fields not read from file are initialized.
1237
1238 // Ensure all ranks complete before proceeding
1239 LOG_ALLOW_SYNC(GLOBAL, LOG_INFO, " Particles generated & initialized.\n");
1240
1241
1242 }
1243 return 0;
1244 }
1245
1246
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:24
PetscErrorCode GetRandomCellAndLogicOnInletFace(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:225
PetscErrorCode MetricLogicalToPhysical(UserCtx *user, const Cmpnts ***X, PetscInt i, PetscInt j, PetscInt k, PetscReal xi, PetscReal eta, PetscReal zta, Cmpnts *Xp)
Public wrapper: map (cell index, ξ,η,ζ) to (x,y,z).
Definition Metric.c:68
PetscErrorCode PreCheckAndResizeSwarm(UserCtx *user, PetscInt ti, const char *ext)
Checks particle count in the reference file and resizes the swarm if needed.
PetscErrorCode UpdateParticleWeights(PetscReal *d, Particle *particle)
Updates a particle's interpolation weights based on distances to cell faces.
PetscErrorCode CreateParticleSwarm(UserCtx *user, PetscInt numParticles, PetscInt *particlesPerProcess, BoundingBox *bboxlist)
Creates and initializes a Particle Swarm.
PetscErrorCode FinalizeSwarmSetup(PetscRandom *randx, PetscRandom *randy, PetscRandom *randz, PetscRandom *rand_logic_i, PetscRandom *rand_logic_j, PetscRandom *rand_logic_k)
Finalizes the swarm setup by destroying random generators and logging completion.
PetscErrorCode DistributeParticles(PetscInt numParticles, PetscMPIInt rank, PetscMPIInt size, PetscInt *particlesPerProcess, PetscInt *remainder)
Distributes particles evenly across MPI processes, handling any remainders.
PetscErrorCode InitializeRandomGenerators(UserCtx *user, PetscRandom *randx, PetscRandom *randy, PetscRandom *randz)
Initializes random number generators for assigning particle properties.
static PetscErrorCode AssignInitialFieldToSwarm(UserCtx *user, const char *fieldName, PetscInt fieldDim)
Initializes a generic swarm field with point-level updates.
#define INTERPOLATION_DISTANCE_TOLERANCE
PetscBool IsParticleInsideBoundingBox(const BoundingBox *bbox, const Particle *particle)
Checks if a particle's location is within a specified bounding box.
PetscErrorCode PrepareLoadedSwarmForRelocation(UserCtx *user)
Resets the location-dependent state of a loaded swarm to force relocation.
static PetscErrorCode UpdateSwarmFieldValue(const char *fieldName, PetscInt p, PetscInt fieldDim, PetscReal *fieldData)
Helper function to update a given particle’s field value.
static PetscErrorCode InitializeParticleBasicProperties(UserCtx *user, PetscInt particlesPerProcess, PetscRandom *rand_logic_i, PetscRandom *rand_logic_j, PetscRandom *rand_logic_k, BoundingBox *bboxlist)
Initializes basic properties for particles on the local process.
PetscErrorCode UnpackSwarmFields(PetscInt i, const PetscInt64 *PIDs, const PetscReal *weights, const PetscReal *positions, const PetscInt *cellIndices, PetscReal *velocities, PetscInt *LocStatus, Particle *particle)
Initializes a Particle struct with data from DMSwarm fields.
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).
PetscErrorCode InitializeSwarm(UserCtx *user)
Initializes the DMSwarm object within the UserCtx structure.
PetscErrorCode UpdateSwarmFields(PetscInt i, const Particle *particle, PetscReal *weights, PetscInt *cellIndices, PetscInt *status_field)
Updates DMSwarm fields with data from a Particle struct.
PetscErrorCode InitializeParticleSwarm(SimCtx *simCtx)
Perform particle swarm initialization, particle-grid interaction, and related operations.
PetscErrorCode RegisterSwarmField(DM swarm, const char *fieldName, PetscInt fieldDim, PetscDataType dtype)
Registers a swarm field without finalizing registration.
static PetscErrorCode DetermineVolumetricInitializationParameters(UserCtx *user, DMDALocalInfo *info, PetscInt xs_gnode, PetscInt ys_gnode, PetscInt zs_gnode, 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, PetscBool *can_place_in_volume_out)
Determines cell selection and intra-cell logical coordinates for volumetric initialization (Mode 1).
PetscErrorCode RegisterParticleFields(DM swarm)
Registers necessary particle fields within the DMSwarm.
PetscErrorCode AssignInitialPropertiesToSwarm(UserCtx *user, PetscInt particlesPerProcess, PetscRandom *rand_phys_x, PetscRandom *rand_phys_y, PetscRandom *rand_phys_z, PetscRandom *rand_logic_i, PetscRandom *rand_logic_j, PetscRandom *rand_logic_k, BoundingBox *bboxlist)
Initializes all particle properties in the swarm.
Header file for Particle Motion and migration related functions.
PetscErrorCode ReadAllSwarmFields(UserCtx *user, PetscInt ti)
Reads multiple fields (positions, velocity, CellID, and weight) into a DMSwarm.
Definition io.c:1100
#define LOG_LOOP_ALLOW(scope, level, iterVar, interval, fmt,...)
Logs a message inside a loop, but only every interval iterations.
Definition logging.h:319
PetscBool is_function_allowed(const char *functionName)
Checks if a given function is in the allow-list.
Definition logging.c:159
#define LOG_ALLOW_SYNC(scope, level, fmt,...)
----— DEBUG ---------------------------------------— #define LOG_ALLOW(scope, level,...
Definition logging.h:274
#define LOCAL
Logging scope definitions for controlling message output.
Definition logging.h:44
#define GLOBAL
Scope for global logging across all processes.
Definition logging.h:45
#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:207
LogLevel get_log_level()
Retrieves the current logging level from the environment variable LOG_LEVEL.
Definition logging.c:49
@ LOG_ERROR
Critical errors that may halt the program.
Definition logging.h:29
@ LOG_INFO
Informational messages about program execution.
Definition logging.h:32
@ LOG_WARNING
Non-critical issues that warrant attention.
Definition logging.h:30
@ LOG_DEBUG
Detailed debugging information.
Definition logging.h:33
Cmpnts vel
Definition variables.h:168
UserCtx * user
Definition variables.h:418
PetscBool inletFaceDefined
Definition variables.h:649
BCFace identifiedInletBCFace
Definition variables.h:650
PetscInt cell[3]
Definition variables.h:166
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:633
ParticleLocationStatus
Defines the state of a particle with respect to its location and migration status during the iterativ...
Definition variables.h:134
@ UNINITIALIZED
Definition variables.h:139
PetscReal Min_X
Definition variables.h:640
UserMG usermg
Definition variables.h:599
Cmpnts max_coords
Maximum x, y, z coordinates of the bounding box.
Definition variables.h:155
PetscInt np
Definition variables.h:585
PetscReal Max_Y
Definition variables.h:640
PetscInt StartStep
Definition variables.h:523
Cmpnts min_coords
Minimum x, y, z coordinates of the bounding box.
Definition variables.h:154
PetscScalar x
Definition variables.h:100
Cmpnts loc
Definition variables.h:167
PetscMPIInt destination_rank
Definition variables.h:171
ParticleLocationStatus location_status
Definition variables.h:170
BoundingBox * bboxlist
Definition variables.h:588
PetscScalar z
Definition variables.h:100
PetscInt mglevels
Definition variables.h:425
PetscReal Min_Z
Definition variables.h:640
PetscReal Max_X
Definition variables.h:640
PetscReal Min_Y
Definition variables.h:640
PetscInt step
Definition variables.h:521
PetscScalar y
Definition variables.h:100
Cmpnts weights
Definition variables.h:169
@ TOP
Definition variables.h:144
@ FRONT
Definition variables.h:144
@ BOTTOM
Definition variables.h:144
@ BACK
Definition variables.h:144
@ LEFT
Definition variables.h:144
@ NUM_FACES
Definition variables.h:144
@ RIGHT
Definition variables.h:144
MGCtx * mgctx
Definition variables.h:428
PetscInt ParticleInitialization
Definition variables.h:589
BoundingBox bbox
Definition variables.h:641
PetscReal Max_Z
Definition variables.h:640
PetscInt64 PID
Definition variables.h:165
Defines a 3D axis-aligned bounding box.
Definition variables.h:153
A 3D point or vector with PetscScalar components.
Definition variables.h:99
Defines a particle's core properties for Lagrangian tracking.
Definition variables.h:164
The master context for the entire simulation.
Definition variables.h:513
User-defined context containing data specific to a single computational grid level.
Definition variables.h:630