PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
interpolation.c
Go to the documentation of this file.
1/**
2 * @file interpolation.c
3 * @brief Main program for DMSwarm interpolation using the fdf-curvIB method.
4 *
5 * Provides routines for interpolation between corner-based and center-based
6 * fields in the cell-centered DM (fda), plus partial usage examples for
7 * DMSwarm-based field sampling.
8 */
9
10#include "interpolation.h"
11
12// Number of weights used in certain trilinear interpolation examples
13#define NUM_WEIGHTS 8
14// Define a buffer size for error messages if not already available
15#ifndef ERROR_MSG_BUFFER_SIZE
16#define ERROR_MSG_BUFFER_SIZE 256 // Or use PETSC_MAX_PATH_LEN if appropriate
17#endif
18
19#undef __FUNCT__
20#define __FUNCT__ "InterpolateFieldFromCornerToCenter_Vector"
21/**
22 * @brief Internal helper implementation: `InterpolateFieldFromCornerToCenter_Vector()`.
23 * @details Local to this translation unit.
24 */
26 Cmpnts ***field_arr, /* Input: Ghosted local array view from user->fda (global node indices) */
27 Cmpnts ***centfield_arr, /* Output: Array view for cell-centered data (global indices) */
28 UserCtx *user)
29{
30 PetscErrorCode ierr;
31 DMDALocalInfo info;
32
33 PetscFunctionBeginUser;
34
35 ierr = DMDAGetLocalInfo(user->fda, &info); CHKERRQ(ierr);
36
37 // Get local and global grid dimensions
38 PetscInt xs = info.xs, xe = info.xs + info.xm;
39 PetscInt ys = info.ys, ye = info.ys + info.ym;
40 PetscInt zs = info.zs, ze = info.zs + info.zm;
41 PetscInt mx = info.mx, my = info.my, mz = info.mz;
42
43 // Determine loop bounds to compute for interior cells only, matching the code's convention.
44 // Start at index 1 if the process owns the global boundary at index 0.
45 PetscInt is = (xs == 0) ? 1 : xs;
46 PetscInt js = (ys == 0) ? 1 : ys;
47 PetscInt ks = (zs == 0) ? 1 : zs;
48
49 // Stop one cell short if the process owns the global boundary at the max index.
50 PetscInt ie = (xe == mx) ? xe - 1 : xe;
51 PetscInt je = (ye == my) ? ye - 1 : ye;
52 PetscInt ke = (ze == mz) ? ze - 1 : ze;
53
54 // Loop over the locally owned INTERIOR cells.
55 for (PetscInt k = ks; k < ke; k++) {
56 for (PetscInt j = js; j < je; j++) {
57 for (PetscInt i = is; i < ie; i++) {
58 // Calculate cell center value as the average of its 8 corner nodes
59 centfield_arr[k][j][i].x = 0.125 * (field_arr[k][j][i].x + field_arr[k][j-1][i].x +
60 field_arr[k-1][j][i].x + field_arr[k-1][j-1][i].x +
61 field_arr[k][j][i-1].x + field_arr[k][j-1][i-1].x +
62 field_arr[k-1][j][i-1].x + field_arr[k-1][j-1][i-1].x);
63
64 centfield_arr[k][j][i].y = 0.125 * (field_arr[k][j][i].y + field_arr[k][j-1][i].y +
65 field_arr[k-1][j][i].y + field_arr[k-1][j-1][i].y +
66 field_arr[k][j][i-1].y + field_arr[k][j-1][i-1].y +
67 field_arr[k-1][j][i-1].y + field_arr[k-1][j-1][i-1].y);
68
69 centfield_arr[k][j][i].z = 0.125 * (field_arr[k][j][i].z + field_arr[k][j-1][i].z +
70 field_arr[k-1][j][i].z + field_arr[k-1][j-1][i].z +
71 field_arr[k][j][i-1].z + field_arr[k][j-1][i-1].z +
72 field_arr[k-1][j][i-1].z + field_arr[k-1][j-1][i-1].z);
73 }
74 }
75 }
76
77 PetscFunctionReturn(0);
78}
79
80#undef __FUNCT__
81#define __FUNCT__ "InterpolateFieldFromCornerToCenter_Scalar"
82/**
83 * @brief Internal helper implementation: `InterpolateFieldFromCornerToCenter_Scalar()`.
84 * @details Local to this translation unit.
85 */
87 PetscReal ***field_arr, /* Input: Ghosted local array view from user->fda (global node indices) */
88 PetscReal ***centfield_arr, /* Output: Array view for cell-centered data (global indices) */
89 UserCtx *user)
90{
91 PetscErrorCode ierr;
92 DMDALocalInfo info;
93
94 PetscFunctionBeginUser;
95
96 ierr = DMDAGetLocalInfo(user->da, &info); CHKERRQ(ierr);
97
98 // Get local and global grid dimensions
99 PetscInt xs = info.xs, xe = info.xs + info.xm;
100 PetscInt ys = info.ys, ye = info.ys + info.ym;
101 PetscInt zs = info.zs, ze = info.zs + info.zm;
102 PetscInt mx = info.mx, my = info.my, mz = info.mz;
103
104 // Determine loop bounds to compute for interior cells only, matching the code's convention.
105 // Start at index 1 if the process owns the global boundary at index 0.
106 PetscInt is = (xs == 0) ? 1 : xs;
107 PetscInt js = (ys == 0) ? 1 : ys;
108 PetscInt ks = (zs == 0) ? 1 : zs;
109
110 // Stop one cell short if the process owns the global boundary at the max index.
111 PetscInt ie = (xe == mx) ? xe - 1 : xe;
112 PetscInt je = (ye == my) ? ye - 1 : ye;
113 PetscInt ke = (ze == mz) ? ze - 1 : ze;
114
115 // Loop over the locally owned INTERIOR cells.
116 for (PetscInt k = ks; k < ke; k++) {
117 for (PetscInt j = js; j < je; j++) {
118 for (PetscInt i = is; i < ie; i++) {
119 // Calculate cell center value as the average of its 8 corner nodes
120 centfield_arr[k][j][i] = 0.125 * (field_arr[k][j][i] + field_arr[k][j-1][i] +
121 field_arr[k-1][j][i] + field_arr[k-1][j-1][i] +
122 field_arr[k][j][i-1] + field_arr[k][j-1][i-1] +
123 field_arr[k-1][j][i-1] + field_arr[k-1][j-1][i-1]);
124 }
125 }
126 }
127
128 PetscFunctionReturn(0);
129}
130
131#undef __FUNCT__
132#define __FUNCT__ "TestCornerToCenterInterpolation"
133/**
134 * @brief Internal helper implementation: `TestCornerToCenterInterpolation()`.
135 * @details Local to this translation unit.
136 */
138{
139 PetscErrorCode ierr;
140 Vec lCoords, TestCent;
141 Cmpnts ***coor_arr, ***test_cent_arr;
142 PetscReal diff_norm;
143
144 PetscFunctionBeginUser;
145
146 // 1. Create a temporary vector to hold the result of our interpolation.
147 // It must have the same layout and size as the ground-truth user->Cent vector.
148 ierr = VecDuplicate(user->Cent, &TestCent); CHKERRQ(ierr);
149
150 // 2. Get the input (corner coordinates) and output (our test vector) arrays.
151 ierr = DMGetCoordinatesLocal(user->da, &lCoords); CHKERRQ(ierr);
152 ierr = DMDAVecGetArrayRead(user->fda, lCoords, &coor_arr); CHKERRQ(ierr);
153 ierr = DMDAVecGetArray(user->fda, TestCent, &test_cent_arr); CHKERRQ(ierr);
154
155 // 3. Call the generic interpolation macro.
156 // The macro will see that `coor_arr` is of type `Cmpnts***` and correctly
157 // call the `InterpolateFieldFromCornerToCenter_Vector` function.
158 ierr = InterpolateFieldFromCornerToCenter(coor_arr, test_cent_arr, user); CHKERRQ(ierr);
159
160 // 4. Restore the arrays.
161 ierr = DMDAVecRestoreArrayRead(user->fda, lCoords, &coor_arr); CHKERRQ(ierr);
162 ierr = DMDAVecRestoreArray(user->fda, TestCent, &test_cent_arr); CHKERRQ(ierr);
163
164 // 5. IMPORTANT: Assemble the vector so its values are communicated across processors
165 // and it's ready for global operations like VecNorm.
166 ierr = VecAssemblyBegin(TestCent); CHKERRQ(ierr);
167 ierr = VecAssemblyEnd(TestCent); CHKERRQ(ierr);
168
169 // 6. Compare the result with the ground truth.
170 // We compute TestCent = -1.0 * user->Cent + 1.0 * TestCent.
171 // This calculates the difference vector: TestCent - user->Cent.
172 ierr = VecAXPY(TestCent, -1.0, user->Cent); CHKERRQ(ierr);
173
174 // Now, compute the L2 norm of the difference vector. If the functions are
175 // identical, the norm should be zero (or very close due to floating point).
176 ierr = VecNorm(TestCent, NORM_2, &diff_norm); CHKERRQ(ierr);
177
178 // 7. Report the result and clean up.
179 if (diff_norm < 1.0e-12) {
180 LOG_ALLOW(GLOBAL,LOG_DEBUG,"[SUCCESS] Test passed. Norm of difference is %g.\n", (double)diff_norm);
181 } else {
182 LOG_ALLOW(GLOBAL,LOG_DEBUG, "[FAILURE] Test failed. Norm of difference is %g.\n", (double)diff_norm);
183 }
184
185 ierr = VecDestroy(&TestCent); CHKERRQ(ierr);
186
187 PetscFunctionReturn(0);
188}
189
190#undef __FUNCT__
191#define __FUNCT__ "InterpolateFieldFromCenterToCorner_Vector"
192/**
193 * @brief Internal helper implementation: `InterpolateFieldFromCenterToCorner_Vector()`.
194 * @details Local to this translation unit.
195 */
197 Cmpnts ***centfield_arr, /* Input: Ghosted local array from Vec (read) */
198 Cmpnts ***corner_arr, /* Output: global array from Vec (write) */
199 UserCtx *user)
200{
201 PetscErrorCode ierr;
202 DMDALocalInfo info;
203 PetscMPIInt rank;
205 ierr = MPI_Comm_rank(PETSC_COMM_WORLD,&rank);
206 ierr = DMDAGetLocalInfo(user->fda, &info); CHKERRQ(ierr);
207
208 // Node ownership range (GLOBAL indices)
209 PetscInt xs_node = info.xs, xm_node = info.xm, xe_node = xs_node + xm_node;
210 PetscInt ys_node = info.ys, ym_node = info.ym, ye_node = ys_node + ym_node;
211 PetscInt zs_node = info.zs, zm_node = info.zm, ze_node = zs_node + zm_node;
212
213 PetscInt nCellsX = info.mx - 2; // Number of cells in x-direction
214 PetscInt nCellsY = info.my - 2; // Number of cells in y-direction
215 PetscInt nCellsZ = info.mz - 2; // Number of cells in z-direction
216
217
218 // Global grid dimensions (used for valid cell check)
219 PetscInt IM = info.mx - 1; // Total nodes in i-direction
220 PetscInt JM = info.my - 1; // Total nodes in j-direction
221 PetscInt KM = info.mz - 1; // Total nodes in k-direction
222
224 "[Rank %d] Starting -- Node ownership k=%d..%d, j=%d..%d, i=%d..%d\n",
225 rank, zs_node, ze_node-1, ys_node, ye_node-1, xs_node, xe_node-1);
226
227 // Loop over the GLOBAL indices of the NODES owned by this processor
228 for (PetscInt k = zs_node; k < ze_node; k++) {
229 for (PetscInt j = ys_node; j < ye_node; j++) {
230 for (PetscInt i = xs_node; i < xe_node; i++) {
231 Cmpnts sum = {0.0, 0.0, 0.0};
232 PetscInt count = 0;
233
234 // DEBUG 1 TEST
235 /*
236 if(rank == 1 && i == 24 && j == 12 && k == 49){
237 PetscInt i_cell_A = i - 1;
238 PetscInt i_cell_B = i;
239
240 Cmpnts ucat_A = centfield_arr[k][j][i_cell_A]; // 23
241 Cmpnts ucat_B = centfield_arr[k][j][i_cell_B]; // 24 (out-of-bounds if IM=25)
242
243 PetscPrintf(PETSC_COMM_WORLD,"[Rank %d] DEBUG TEST at Node(k,j,i)=%d,%d,%d: Read { Valid Cell }Ucat[%d][%d][%d]=(%.2f,%.2f,%.2f) and {Out-of-Bounds Cell}Ucat[%d][%d][%d]=(%.2f,%.2f,%.2f)\n",
244 rank, k, j, i,
245 k, j, i_cell_A, ucat_A.x, ucat_A.y, ucat_A.z,
246 k, j, i_cell_B, ucat_B.x, ucat_B.y, ucat_B.z);
247
248 }
249 */
250
251 // Skip processing the unused last node in each dimension.
252 if(i >= IM || j >= JM || k >= KM){
253 continue;
254 }
255 // Loop over the 8 potential cells surrounding node N(k,j,i) and accumulate values.
256 // The index offsets correspond to the relative position of the indices(shifted) that represent cell-centered field values of cells that share the node N(k,j,i) as a corner
257 for (PetscInt dk_offset = -1; dk_offset <= 0; dk_offset++) {
258 for (PetscInt dj_offset = -1; dj_offset <= 0; dj_offset++) {
259 for (PetscInt di_offset = -1; di_offset <= 0; di_offset++) {
260
261 // These are still GLOBAL cell indices
262 PetscInt global_cell_k = k + dk_offset;
263 PetscInt global_cell_j = j + dj_offset;
264 PetscInt global_cell_i = i + di_offset;
265
266 // Check if this corresponds to a valid GLOBAL cell index
267 if (global_cell_i >= 0 && global_cell_i < nCellsX &&
268 global_cell_j >= 0 && global_cell_j < nCellsY &&
269 global_cell_k >= 0 && global_cell_k < nCellsZ)
270 {
271 Cmpnts cell_val = centfield_arr[global_cell_k + 1][global_cell_j + 1][global_cell_i + 1];
272
273 LOG_LOOP_ALLOW_EXACT(LOCAL, LOG_VERBOSE,k,49,"[Rank %d] successful read from [%d][%d][%d] -> (%.2f, %.2f, %.2f)\n",
274 rank,global_cell_k,global_cell_j,global_cell_i,cell_val.x, cell_val.y, cell_val.z);
275
276 sum.x += cell_val.x;
277 sum.y += cell_val.y;
278 sum.z += cell_val.z;
279 count++;
280 }
281 }
282 }
283 }
284
285 PetscInt i_global_write = i; // Global index in GLOBAL array.
286 PetscInt j_global_write = j;
287 PetscInt k_global_write = k;
288
289 // We write directly into the array using the global loop indices.
290 if (count > 0) {
291 corner_arr[k_global_write][j_global_write][i_global_write].x = sum.x / (PetscReal)count;
292 corner_arr[k_global_write][j_global_write][i_global_write].y = sum.y / (PetscReal)count;
293 corner_arr[k_global_write][j_global_write][i_global_write].z = sum.z / (PetscReal)count;
294 } else {
295 // This case should ideally not happen for a valid owned node, but as a failsafe:
296 corner_arr[k_global_write][j_global_write][i_global_write] = (Cmpnts){0.0, 0.0, 0.0};
297 }
298
299 // DEBUG 2
300 /*
301 if(rank == 1){
302 if(i == 11 && j == 11 && k == 49){
303 Cmpnts ucat_node = corner_arr[k][j][i];
304 PetscPrintf(PETSC_COMM_WORLD,"[Rank %d] DEBUG TEST at Node(k,j,i)=%d,%d,%d: Wrote CornerUcat[%d][%d][%d]=(%.2f,%.2f,%.2f)\n",
305 rank, k, j, i,
306 k, j, i, ucat_node.x, ucat_node.y, ucat_node.z);
307 }
308 }
309
310 if(rank == 0 && i == 24 && j == 12 && k == 0){
311 Cmpnts ucat_node = corner_arr[k][j][i];
312 PetscPrintf(PETSC_COMM_WORLD,"[Rank %d] DEBUG TEST at Node(k,j,i)=%d,%d,%d: Wrote CornerUcat[%d][%d][%d]=(%.2f,%.2f,%.2f)\n",
313 rank, k, j, i,
314 k, j, i, ucat_node.x, ucat_node.y, ucat_node.z);
315 }
316 */
317 // LOG_LOOP_ALLOW_EXACT(LOCAL, LOG_VERBOSE,k,48,"[Rank %d] Node(k,j,i)=%d,%d,%d finished loops and write.\n", rank, k, j, i);
318 }
319 }
320 }
322 return 0;
323}
324
325#undef __FUNCT__
326#define __FUNCT__ "InterpolateFieldFromCenterToCorner_Scalar"
327/**
328 * @brief Internal helper implementation: `InterpolateFieldFromCenterToCorner_Scalar()`.
329 * @details Local to this translation unit.
330 */
332 PetscReal ***centfield_arr, /* Input: Ghosted local array from Vec (read) */
333 PetscReal ***corner_arr, /* Output: global array from Vec (write) */
334 UserCtx *user)
335{
336 PetscErrorCode ierr;
337 DMDALocalInfo info;
338 PetscMPIInt rank;
340 ierr = MPI_Comm_rank(PETSC_COMM_WORLD,&rank);
341 ierr = DMDAGetLocalInfo(user->fda, &info); CHKERRQ(ierr);
342
343 // Node ownership range (GLOBAL indices)
344 PetscInt xs_node = info.xs, xm_node = info.xm, xe_node = xs_node + xm_node;
345 PetscInt ys_node = info.ys, ym_node = info.ym, ye_node = ys_node + ym_node;
346 PetscInt zs_node = info.zs, zm_node = info.zm, ze_node = zs_node + zm_node;
347
348 PetscInt nCellsX = info.mx - 2; // Number of cells in x-direction
349 PetscInt nCellsY = info.my - 2; // Number of cells in y-direction
350 PetscInt nCellsZ = info.mz - 2; // Number of cells in z-direction
351
352
353 // Global grid dimensions (used for valid cell check)
354 PetscInt IM = info.mx - 1; // Total nodes in i-direction
355 PetscInt JM = info.my - 1; // Total nodes in j-direction
356 PetscInt KM = info.mz - 1; // Total nodes in k-direction
357
359 "[Rank %d] Starting -- Node ownership k=%d..%d, j=%d..%d, i=%d..%d\n",
360 rank, zs_node, ze_node-1, ys_node, ye_node-1, xs_node, xe_node-1);
361
362 // Loop over the GLOBAL indices of the NODES owned by this processor
363 for (PetscInt k = zs_node; k < ze_node; k++) {
364 for (PetscInt j = ys_node; j < ye_node; j++) {
365 for (PetscInt i = xs_node; i < xe_node; i++) {
366 PetscReal sum = 0.0;
367 PetscInt count = 0;
368
369 // DEBUG 1 TEST
370 /*
371 if(rank == 1 && i == 24 && j == 12 && k == 49){
372 PetscInt i_cell_A = i - 1;
373 PetscInt i_cell_B = i;
374
375 Cmpnts ucat_A = centfield_arr[k][j][i_cell_A]; // 23
376 Cmpnts ucat_B = centfield_arr[k][j][i_cell_B]; // 24 (out-of-bounds if IM=25)
377
378 PetscPrintf(PETSC_COMM_WORLD,"[Rank %d] DEBUG TEST at Node(k,j,i)=%d,%d,%d: Read { Valid Cell }Ucat[%d][%d][%d]=(%.2f,%.2f,%.2f) and {Out-of-Bounds Cell}Ucat[%d][%d][%d]=(%.2f,%.2f,%.2f)\n",
379 rank, k, j, i,
380 k, j, i_cell_A, ucat_A.x, ucat_A.y, ucat_A.z,
381 k, j, i_cell_B, ucat_B.x, ucat_B.y, ucat_B.z);
382
383 }
384 */
385
386 // Skip processing the unused last node in each dimension.
387 if(i >= IM || j >= JM || k >= KM){
388 continue;
389 }
390 // Loop over the 8 potential cells surrounding node N(k,j,i) and accumulate values.
391 // The index offsets correspond to the relative position of the indices(shifted) that represent cell-centered field values of cells that share the node N(k,j,i) as a corner
392 for (PetscInt dk_offset = -1; dk_offset <= 0; dk_offset++) {
393 for (PetscInt dj_offset = -1; dj_offset <= 0; dj_offset++) {
394 for (PetscInt di_offset = -1; di_offset <= 0; di_offset++) {
395
396 // These are still GLOBAL cell indices
397 PetscInt global_cell_k = k + dk_offset;
398 PetscInt global_cell_j = j + dj_offset;
399 PetscInt global_cell_i = i + di_offset;
400
401 // Check if this corresponds to a valid GLOBAL cell index
402 if (global_cell_i >= 0 && global_cell_i < nCellsX &&
403 global_cell_j >= 0 && global_cell_j < nCellsY &&
404 global_cell_k >= 0 && global_cell_k < nCellsZ)
405 {
406 PetscReal cell_val = centfield_arr[global_cell_k + 1][global_cell_j + 1][global_cell_i + 1];
407
408 LOG_LOOP_ALLOW_EXACT(LOCAL, LOG_VERBOSE,k,49,"[Rank %d] successful read from [%d][%d][%d] -> (%.2f)\n",
409 rank,global_cell_k,global_cell_j,global_cell_i,cell_val);
410
411 sum += cell_val;
412 count++;
413 }
414 }
415 }
416 }
417
418 PetscInt i_global_write = i; // Global index in GLOBAL array.
419 PetscInt j_global_write = j;
420 PetscInt k_global_write = k;
421
422 // We write directly into the array using the global loop indices.
423 if (count > 0) {
424 corner_arr[k_global_write][j_global_write][i_global_write] = sum / (PetscReal)count;
425 } else {
426 // This case should ideally not happen for a valid owned node, but as a failsafe:
427 corner_arr[k_global_write][j_global_write][i_global_write] = 0.0;
428 }
429
430 // DEBUG 2
431 /*
432 if(rank == 1){
433 if(i == 11 && j == 11 && k == 49){
434 Cmpnts ucat_node = corner_arr[k][j][i];
435 PetscPrintf(PETSC_COMM_WORLD,"[Rank %d] DEBUG TEST at Node(k,j,i)=%d,%d,%d: Wrote CornerUcat[%d][%d][%d]=(%.2f,%.2f,%.2f)\n",
436 rank, k, j, i,
437 k, j, i, ucat_node.x, ucat_node.y, ucat_node.z);
438 }
439 }
440
441 if(rank == 0 && i == 24 && j == 12 && k == 0){
442 Cmpnts ucat_node = corner_arr[k][j][i];
443 PetscPrintf(PETSC_COMM_WORLD,"[Rank %d] DEBUG TEST at Node(k,j,i)=%d,%d,%d: Wrote CornerUcat[%d][%d][%d]=(%.2f,%.2f,%.2f)\n",
444 rank, k, j, i,
445 k, j, i, ucat_node.x, ucat_node.y, ucat_node.z);
446 }
447 */
448 // LOG_LOOP_ALLOW_EXACT(LOCAL, LOG_VERBOSE,k,48,"[Rank %d] Node(k,j,i)=%d,%d,%d finished loops and write.\n", rank, k, j, i);
449 }
450 }
451 }
453 return 0;
454}
455
456#undef __FUNCT
457#define __FUNCT "PiecWiseLinearInterpolation_Scalar"
458/**
459 * @brief Internal helper implementation: `PieceWiseLinearInterpolation_Scalar()`.
460 * @details Local to this translation unit.
461 */
463 const char *fieldName,
464 PetscReal ***fieldScal,
465 PetscInt iCell,
466 PetscInt jCell,
467 PetscInt kCell,
468 PetscReal *val)
469{
470 PetscFunctionBegin;
471 *val = fieldScal[kCell][jCell][iCell];
472
473 // Optional logging
475 "Field '%s' at (i=%d, j=%d, k=%d) => val=%.6f\n",
476 fieldName, iCell, jCell, kCell, *val);
477
478 PetscFunctionReturn(0);
479}
480
481#undef __FUNCT
482#define __FUNCT "PiecWiseLinearInterpolation_Vector"
483/**
484 * @brief Internal helper implementation: `PieceWiseLinearInterpolation_Vector()`.
485 * @details Local to this translation unit.
486 */
488 const char *fieldName,
489 Cmpnts ***fieldVec,
490 PetscInt iCell,
491 PetscInt jCell,
492 PetscInt kCell,
493 Cmpnts *vec)
494{
495 PetscFunctionBegin;
496 vec->x = fieldVec[kCell][jCell][iCell].x;
497 vec->y = fieldVec[kCell][jCell][iCell].y;
498 vec->z = fieldVec[kCell][jCell][iCell].z;
499
500 // Optional logging
502 "Field '%s' at (i=%d, j=%d, k=%d) => (x=%.6f, y=%.6f, z=%.6f)\n",
503 fieldName, iCell, jCell, kCell, vec->x, vec->y, vec->z);
504
505 PetscFunctionReturn(0);
506}
507
508
509#undef __FUNCT
510#define __FUNCT "ComputeTrilinearWeights"
511
512/**
513 * @brief Compute the eight trilinear interpolation weights for a particle's local coordinates.
514 */
515static inline void ComputeTrilinearWeights(PetscReal a1, PetscReal a2, PetscReal a3, PetscReal *w) {
516 LOG_ALLOW(GLOBAL, LOG_VERBOSE, "Computing weights for a1=%f, a2=%f, a3=%f.\n", a1, a2, a3);
517
518 // Ensure a1, a2, a3 are within [0,1]
519 a1 = PetscMax(0.0, PetscMin(1.0, a1));
520 a2 = PetscMax(0.0, PetscMin(1.0, a2));
521 a3 = PetscMax(0.0, PetscMin(1.0, a3));
522
523 const PetscReal oa1 = 1.0 - a1;
524 const PetscReal oa2 = 1.0 - a2;
525 const PetscReal oa3 = 1.0 - a3;
526
527 w[0] = oa1 * oa2 * oa3; /* cornerOffsets[0] => (0,0,0) */
528 w[1] = a1 * oa2 * oa3; /* cornerOffsets[1] => (1,0,0) */
529 w[2] = oa1 * a2 * oa3; /* cornerOffsets[2] => (0,1,0) */
530 w[3] = a1 * a2 * oa3; /* cornerOffsets[3] => (1,1,0) */
531 w[4] = oa1 * oa2 * a3; /* cornerOffsets[4] => (0,0,1) */
532 w[5] = a1 * oa2 * a3; /* cornerOffsets[5] => (1,0,1) */
533 w[6] = oa1 * a2 * a3; /* cornerOffsets[6] => (0,1,1) */
534 w[7] = a1 * a2 * a3; /* cornerOffsets[7] => (1,1,1) */
535
536 // Log the computed weights for debugging
537 LOG_ALLOW(LOCAL,LOG_VERBOSE, "Weights computed - "
538 "w0=%f, w1=%f, w2=%f, w3=%f, w4=%f, w5=%f, w6=%f, w7=%f. \n",
539 w[0], w[1], w[2], w[3], w[4], w[5], w[6], w[7]);
540}
541
542/**
543 * @brief Unclamped trilinear weights for boundary extrapolation.
544 * @details Identical to ComputeTrilinearWeights() but without clamping
545 * a1, a2, a3 to [0,1]. Allows weights outside [0,1] for linear
546 * extrapolation at non-periodic boundaries. Weights still sum to 1.0.
547 */
548static inline void ComputeTrilinearWeightsUnclamped(PetscReal a1, PetscReal a2, PetscReal a3, PetscReal *w) {
549 LOG_ALLOW(GLOBAL, LOG_VERBOSE, "Computing unclamped weights for a1=%f, a2=%f, a3=%f.\n", a1, a2, a3);
550
551 const PetscReal oa1 = 1.0 - a1;
552 const PetscReal oa2 = 1.0 - a2;
553 const PetscReal oa3 = 1.0 - a3;
554
555 w[0] = oa1 * oa2 * oa3;
556 w[1] = a1 * oa2 * oa3;
557 w[2] = oa1 * a2 * oa3;
558 w[3] = a1 * a2 * oa3;
559 w[4] = oa1 * oa2 * a3;
560 w[5] = a1 * oa2 * a3;
561 w[6] = oa1 * a2 * a3;
562 w[7] = a1 * a2 * a3;
563
564 LOG_ALLOW(LOCAL, LOG_VERBOSE, "Unclamped weights - "
565 "w0=%f, w1=%f, w2=%f, w3=%f, w4=%f, w5=%f, w6=%f, w7=%f.\n",
566 w[0], w[1], w[2], w[3], w[4], w[5], w[6], w[7]);
567}
568
569#undef __FUNCT
570#define __FUNCT "TrilinearInterpolation_Scalar"
571
572/**
573 * @brief Internal helper implementation: `TrilinearInterpolation_Scalar()`.
574 * @details Local to this translation unit.
575 */
577 const char *fieldName,
578 PetscReal ***fieldScal,
579 PetscInt i,
580 PetscInt j,
581 PetscInt k,
582 PetscReal a1,
583 PetscReal a2,
584 PetscReal a3,
585 PetscReal *val)
586{
587 PetscFunctionBegin; // PETSc macro for error/stack tracing
588
589 // Compute the 8 corner weights
590 PetscReal wcorner[8];
591 ComputeTrilinearWeights(a1, a2, a3, wcorner);
592
593 // Offsets for cell corners
594 PetscInt i1 = i + 1;
595 PetscInt j1 = j + 1;
596 PetscInt k1 = k + 1;
597
598 // Initialize the output scalar
599 PetscReal sum = 0.0;
600
601 // Corner 0 => (i, j, k)
602 sum += wcorner[0] * fieldScal[k ][j ][i ];
603 // Corner 1 => (i+1, j, k)
604 sum += wcorner[1] * fieldScal[k ][j ][i1];
605 // Corner 2 => (i, j+1, k)
606 sum += wcorner[2] * fieldScal[k ][j1][i ];
607 // Corner 3 => (i+1, j+1, k)
608 sum += wcorner[3] * fieldScal[k ][j1][i1];
609 // Corner 4 => (i, j, k+1)
610 sum += wcorner[4] * fieldScal[k1][j ][i ];
611 // Corner 5 => (i+1, j, k+1)
612 sum += wcorner[5] * fieldScal[k1][j ][i1];
613 // Corner 6 => (i, j+1, k+1)
614 sum += wcorner[6] * fieldScal[k1][j1][i ];
615 // Corner 7 => (i+1, j+1, k+1)
616 sum += wcorner[7] * fieldScal[k1][j1][i1];
617
618 *val = sum;
619
620 // Logging (optional)
622 "Field '%s' at (i=%d, j=%d, k=%d), "
623 "a1=%.6f, a2=%.6f, a3=%.6f -> val=%.6f.\n",
624 fieldName, i, j, k, a1, a2, a3, *val);
625
626 // LOG_ALLOW_SYNC(GLOBAL, LOG_INFO,
627 // "TrilinearInterpolation_Scalar: Completed interpolation for field '%s' across local cells.\n",
628 // fieldName);
629
630 PetscFunctionReturn(0);
631}
632
633
634#undef __FUNCT
635#define __FUNCT "TrilinearInterpolation_Vector"
636/**
637 * @brief Internal helper implementation: `TrilinearInterpolation_Vector()`.
638 * @details Local to this translation unit.
639 */
641 const char *fieldName,
642 Cmpnts ***fieldVec, /* 3D array [k][j][i], dimension [mz][my][mx] */
643 PetscInt i, // local cell index i
644 PetscInt j, // local cell index j
645 PetscInt k, // local cell index k
646 PetscReal a1,
647 PetscReal a2,
648 PetscReal a3,
649 Cmpnts *vec)
650{
651 PetscFunctionBegin; // PETSc macro for error/stack tracing
652
653 // Compute the 8 corner weights
654 PetscErrorCode ierr;
655 PetscReal wcorner[8];
656 PetscMPIInt rank;
657
658 ierr = MPI_Comm_rank(PETSC_COMM_WORLD,&rank);CHKERRQ(ierr);
659
660 LOG_ALLOW(LOCAL,LOG_VERBOSE,"[Rank %d] Computing Trilinear Weights.\n",rank);
661 ComputeTrilinearWeights(a1, a2, a3, wcorner);
662
663 LOG_ALLOW(LOCAL,LOG_VERBOSE,"[Rank %d] Trilinear weights computed for local cell %d,%d,%d.\n",rank,i,j,k);
664
665 // For partial interpolation, we'll keep track of how many corners are valid
666 // and how much sum of weights is used. Then we do a final normalization.
667 PetscReal sumW = 0.0;
668 Cmpnts accum = {0.0, 0.0, 0.0};
669
670 // The eight corner indices, with their weights:
671 // corners: (i,j,k), (i+1,j,k), (i,j+1,k), (i+1,j+1,k), etc.
672 // We store them in an array to iterate cleanly.
673 const PetscInt cornerOffsets[8][3] = {
674 {0, 0, 0},
675 {1, 0, 0},
676 {0, 1, 0},
677 {1, 1, 0},
678 {0, 0, 1},
679 {1, 0, 1},
680 {0, 1, 1},
681 {1, 1, 1}
682 };
683
684 // Weighted partial sum
685 for (PetscInt c = 0; c < 8; c++) {
686 const PetscInt di = cornerOffsets[c][0];
687 const PetscInt dj = cornerOffsets[c][1];
688 const PetscInt dk = cornerOffsets[c][2];
689 PetscInt iC = i + di;
690 PetscInt jC = j + dj;
691 PetscInt kC = k + dk;
692
693 /*
694 // skip if out of domain
695 // (Assuming you know global domain is [0..mx), [0..my), [0..mz).)
696 if (iC < 0 || iC >= (PetscInt)userGlobalMx ||
697 jC < 0 || jC >= (PetscInt)userGlobalMy ||
698 kC < 0 || kC >= (PetscInt)userGlobalMz)
699 {
700 // skip this corner
701 continue;
702 }
703
704 */
705
706 LOG_ALLOW(LOCAL,LOG_VERBOSE,"[Rank %d] %s[%d][%d][%d] = (%.4f,%.4f,%.4f).\n",rank,fieldName,kC,jC,iC,fieldVec[kC][jC][iC].x,fieldVec[kC][jC][iC].y,fieldVec[kC][jC][iC].z);
707
708 // Otherwise, accumulate
709 accum.x += wcorner[c] * fieldVec[kC][jC][iC].x;
710 accum.y += wcorner[c] * fieldVec[kC][jC][iC].y;
711 accum.z += wcorner[c] * fieldVec[kC][jC][iC].z;
712 sumW += wcorner[c];
713 }
714
715 // If sumW=0 => out-of-range or zero weighting => set (0,0,0)
716 if (sumW > 1.0e-14) {
717 vec->x = accum.x / sumW;
718 vec->y = accum.y / sumW;
719 vec->z = accum.z / sumW;
720 } else {
721 vec->x = 0.0; vec->y = 0.0; vec->z = 0.0;
722 }
723
724 PetscFunctionReturn(0);
725}
726
727
728#undef __FUNCT
729#define __FUNCT "InterpolateEulerFieldToSwarmForParticle"
730/**
731 * @brief Interpolate one Eulerian field to a single located swarm particle.
732 */
733static inline PetscErrorCode InterpolateEulerFieldToSwarmForParticle(
734 const char *fieldName,
735 void *fieldPtr, /* typed Pointer => either (PetscReal***) or (Cmpnts***) */
736 Particle *particle, /* particle struct containing Cell ID and weight information. */
737 void *swarmOut, /* typed Pointer => (PetscReal*) or (Cmpnts*) or dof=3 array */
738 PetscInt p, /* particle index */
739 PetscInt blockSize) /* dof=1 => scalar, dof=3 => vector */
740{
741 PetscErrorCode ierr;
742 PetscFunctionBegin;
743
744 PetscInt iCell = particle->cell[0];
745 PetscInt jCell = particle->cell[1];
746 PetscInt kCell = particle->cell[2];
747 PetscReal a1 = particle->weights.x;
748 PetscReal a2 = particle->weights.y;
749 PetscReal a3 = particle->weights.z;
750
752
753 // Optional logging at start
755 "field='%s', blockSize=%d, "
756 "cell IDs=(%d,%d,%d), weights=(%.4f,%.4f,%.4f)\n",
757 fieldName, blockSize, iCell, jCell, kCell, a1, a2, a3);
758
759 /*
760 If blockSize=1, we PetscInterpret the fieldPtr as a 3D array of PetscReal (scalar).
761 If blockSize=3, we PetscInterpret the fieldPtr as a 3D array of Cmpnts (vector).
762 */
763 if (blockSize == 1) {
764 /* Scalar field: Cast fieldPtr to (PetscReal ***). */
765 PetscReal ***fieldScal = (PetscReal ***) fieldPtr;
766 PetscReal val;
767
768 // Currently using trilinear.
769 ierr = TrilinearInterpolation(fieldName, fieldScal,
770 iCell, jCell, kCell,
771 a1, a2, a3,
772 &val);
773 CHKERRQ(ierr);
774
775 // Alternative (commented) call to PiecewiseLinearInterpolation (zeroth order) :
776 // PetscErrorCode ierr = PiecewiseLinearInterpolation(fieldName,
777 // fieldScal,
778 // iCell, jCell, kCell,
779 // &val);
780 // CHKERRQ(ierr);
781
782 // Write the scalar result to the swarm output at index [p].
783 ((PetscReal*)swarmOut)[p] = val;
784
786 "field='%s', result=%.6f "
787 "stored at swarmOut index p=%d.\n", fieldName, val, (PetscInt)p);
788 }
789 else if (blockSize == 3) {
790 /* Vector field: Cast fieldPtr to (Cmpnts ***). */
791 Cmpnts ***fieldVec = (Cmpnts ***) fieldPtr;
792 Cmpnts vec;
793
794
795
796
797 // Piecewise interpolation (zeroth order).
798 // PetscErrorCode ierr = PieceWiseLinearInterpolation(fieldName,
799 // fieldVec,
800 // iCell, jCell, kCell,
801 // &vec);
802 // CHKERRQ(ierr);
803
804 // Alternative (commented) call to trilinear:
805 ierr = TrilinearInterpolation(fieldName, fieldVec,
806 iCell, jCell, kCell,
807 a1, a2, a3,
808 &vec);
809 CHKERRQ(ierr);
810
811 // If swarmOut is an array of 3 reals per particle:
812 ((PetscReal*)swarmOut)[3*p + 0] = vec.x;
813 ((PetscReal*)swarmOut)[3*p + 1] = vec.y;
814 ((PetscReal*)swarmOut)[3*p + 2] = vec.z;
815
817 "field='%s', result=(%.6f,%.6f,%.6f) "
818 "stored at swarmOut[3p..3p+2], p=%d.\n",
819 fieldName, vec.x, vec.y, vec.z, (PetscInt)p);
820
821 /*
822 If you store the vector result as a Cmpnts in the swarm, do instead:
823 ((Cmpnts*)swarmOut)[p] = vec;
824 but ensure your DMSwarm field is sized for a Cmpnts struct.
825 */
826 }
827 else {
828 /* If blockSize isn't 1 or 3, we raise an error. */
829 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP,
830 "InterpolateEulerFieldToSwarmForParticle: only blockSize=1 or 3 supported, got %d.",
831 (PetscInt)blockSize);
832 }
833
835 PetscFunctionReturn(0);
836}
837
838#undef __FUNCT__
839#define __FUNCT__ "InterpolateEulerFieldFromCenterToSwarm"
840
841/**
842 * @brief Direct cell-center trilinear interpolation (second-order on curvilinear grids).
843 * @details For each particle, determines the 8 nearest cell centers via octant
844 * detection, constructs a dual cell, computes face-distance-based
845 * trilinear weights, and interpolates directly from cell-centered data.
846 * At non-periodic boundaries where the dual cell cannot be fully formed,
847 * octant clamping with unclamped trilinear extrapolation preserves
848 * second-order accuracy. Periodic boundaries use ghost cell data directly.
849 * No intermediate corner staging or extra ghost exchange is needed.
850 */
852 UserCtx *user,
853 Vec fieldLocal_cellCentered,
854 const char *fieldName,
855 const char *swarmOutFieldName)
856{
857 PetscErrorCode ierr;
858 DM swarm = user->swarm;
859 PetscInt bs;
860 DMDALocalInfo info;
861 PetscMPIInt rank;
862 void *fieldPtr = NULL;
863 Cmpnts ***cent = NULL;
864
865 PetscInt *cellIDs = NULL;
866 PetscReal *weights = NULL;
867 PetscInt64 *pids = NULL;
868 void *swarmOut = NULL;
869 PetscReal *pos = NULL;
870 PetscInt *status = NULL;
871 PetscInt nLocal;
872
873 PetscFunctionBegin;
875 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
876 ierr = DMDAGetLocalInfo(user->fda, &info); CHKERRQ(ierr);
877 ierr = VecGetBlockSize(fieldLocal_cellCentered, &bs); CHKERRQ(ierr);
878 if (bs != 1 && bs != 3) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "BlockSize must be 1 or 3.");
879
880 LOG_ALLOW(GLOBAL, LOG_INFO, "Starting direct cell-center interpolation for field '%s'...\n", fieldName);
881
882 /* Number of physical cells in each direction */
883 PetscInt nCellsX = info.mx - 2;
884 PetscInt nCellsY = info.my - 2;
885 PetscInt nCellsZ = info.mz - 2;
886
887 /* Periodic flags per direction */
888 PetscBool x_periodic = (user->boundary_faces[BC_FACE_NEG_X].mathematical_type == PERIODIC);
889 PetscBool y_periodic = (user->boundary_faces[BC_FACE_NEG_Y].mathematical_type == PERIODIC);
890 PetscBool z_periodic = (user->boundary_faces[BC_FACE_NEG_Z].mathematical_type == PERIODIC);
891
892 /* Get read-only arrays for the cell-centered field and cell center coordinates */
893 DM dm_field = (bs == 3) ? user->fda : user->da;
894 ierr = DMDAVecGetArrayRead(dm_field, fieldLocal_cellCentered, &fieldPtr); CHKERRQ(ierr);
895 ierr = DMDAVecGetArrayRead(user->fda, user->lCent, (void *)&cent); CHKERRQ(ierr);
896
897 /* Retrieve swarm fields */
898 ierr = DMSwarmGetLocalSize(swarm, &nLocal); CHKERRQ(ierr);
899 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cellIDs); CHKERRQ(ierr);
900 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pids); CHKERRQ(ierr);
901 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_WEIGHT), NULL, NULL, (void**)&weights); CHKERRQ(ierr);
902 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&pos); CHKERRQ(ierr);
903 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status); CHKERRQ(ierr);
904 ierr = DMSwarmGetField(swarm, swarmOutFieldName, NULL, NULL, &swarmOut); CHKERRQ(ierr);
905
906 /* Loop over each local particle */
907 for (PetscInt p = 0; p < nLocal; p++) {
908
909 Particle particle;
910 ierr = UnpackSwarmFields(p, pids, weights, pos, cellIDs, NULL, status, NULL, NULL, NULL, &particle); CHKERRQ(ierr);
911
912 /* Step 1: Get host cell indices and existing weights */
913 PetscInt ci = particle.cell[0];
914 PetscInt cj = particle.cell[1];
915 PetscInt ck = particle.cell[2];
916 PetscReal a1 = particle.weights.x;
917 PetscReal a2 = particle.weights.y;
918 PetscReal a3 = particle.weights.z;
919
920 /* Step 2: Determine octant */
921 PetscInt oi = (a1 < 0.5) ? -1 : 0;
922 PetscInt oj = (a2 < 0.5) ? -1 : 0;
923 PetscInt ok = (a3 < 0.5) ? -1 : 0;
924 PetscInt bi = ci + oi;
925 PetscInt bj = cj + oj;
926 PetscInt bk = ck + ok;
927
928 /* Step 3: Clamp octant at non-periodic boundaries */
929 PetscBool needs_unclamped = PETSC_FALSE;
930
931 if (!x_periodic) {
932 if (bi < 0) { bi = 0; needs_unclamped = PETSC_TRUE; }
933 if (bi + 1 >= nCellsX) { bi = nCellsX - 2; needs_unclamped = PETSC_TRUE; }
934 }
935 if (!y_periodic) {
936 if (bj < 0) { bj = 0; needs_unclamped = PETSC_TRUE; }
937 if (bj + 1 >= nCellsY) { bj = nCellsY - 2; needs_unclamped = PETSC_TRUE; }
938 }
939 if (!z_periodic) {
940 if (bk < 0) { bk = 0; needs_unclamped = PETSC_TRUE; }
941 if (bk + 1 >= nCellsZ) { bk = nCellsZ - 2; needs_unclamped = PETSC_TRUE; }
942 }
943
944 /* Step 4: Bounds check — dual cell stencil must fit in ghosted region */
945 if (bi + 1 < info.gxs || bi + 1 >= info.gxs + info.gxm - 1 ||
946 bj + 1 < info.gys || bj + 1 >= info.gys + info.gym - 1 ||
947 bk + 1 < info.gzs || bk + 1 >= info.gzs + info.gzm - 1)
948 {
950 "[Rank %d] Particle PID %lld: dual cell (%d,%d,%d) out of ghosted region. Zeroing '%s'.\n",
951 rank, (long long)particle.PID, bi, bj, bk, fieldName);
952 if (bs == 3) {
953 ((PetscReal*)swarmOut)[3*p + 0] = 0.0;
954 ((PetscReal*)swarmOut)[3*p + 1] = 0.0;
955 ((PetscReal*)swarmOut)[3*p + 2] = 0.0;
956 } else {
957 ((PetscReal*)swarmOut)[p] = 0.0;
958 }
959 continue;
960 }
961
962 /* Step 5: Construct dual cell from lCent (shifted index: cent[k+1][j+1][i+1] for cell (i,j,k))
963 * Vertex ordering follows GetCellVerticesFromGrid() convention (walkingsearch.c:423-430). */
964 Cell dual_cell;
965 dual_cell.vertices[0] = cent[bk + 1][bj + 1][bi + 1]; /* (bi, bj, bk ) */
966 dual_cell.vertices[1] = cent[bk + 1][bj + 1][bi + 2]; /* (bi+1, bj, bk ) */
967 dual_cell.vertices[2] = cent[bk + 1][bj + 2][bi + 2]; /* (bi+1, bj+1, bk ) */
968 dual_cell.vertices[3] = cent[bk + 1][bj + 2][bi + 1]; /* (bi, bj+1, bk ) */
969 dual_cell.vertices[4] = cent[bk + 2][bj + 2][bi + 1]; /* (bi, bj+1, bk+1) */
970 dual_cell.vertices[5] = cent[bk + 2][bj + 2][bi + 2]; /* (bi+1, bj+1, bk+1) */
971 dual_cell.vertices[6] = cent[bk + 2][bj + 1][bi + 2]; /* (bi+1, bj, bk+1) */
972 dual_cell.vertices[7] = cent[bk + 2][bj + 1][bi + 1]; /* (bi, bj, bk+1) */
973
974 /* Step 6: Compute face distances from particle to dual cell faces */
975 PetscReal d[NUM_FACES];
976 ierr = CalculateDistancesToCellFaces(particle.loc, &dual_cell, d, 1e-11); CHKERRQ(ierr);
977
978 /* Step 7: Compute trilinear parametric coordinates from face distances */
979 PetscReal a1_new = d[LEFT] / (d[LEFT] + d[RIGHT]);
980 PetscReal a2_new = d[BOTTOM] / (d[BOTTOM] + d[TOP]);
981 PetscReal a3_new = d[BACK] / (d[FRONT] + d[BACK]);
982
984 "[Rank %d] PID %lld: dual base=(%d,%d,%d), new weights=(%.4f,%.4f,%.4f), unclamped=%d\n",
985 rank, (long long)particle.PID, bi, bj, bk, a1_new, a2_new, a3_new, (int)needs_unclamped);
986
987 /* Step 8: Call TrilinearInterpolation directly with shifted dual-cell indices.
988 * Shifted index: pass (bi+1, bj+1, bk+1) so the kernel accesses
989 * field[bk+1..bk+2][bj+1..bj+2][bi+1..bi+2] = cells (bi..bi+1, bj..bj+1, bk..bk+1). */
990 if (bs == 1) {
991 PetscReal ***fieldScal = (PetscReal ***)fieldPtr;
992 PetscReal val;
993 if (needs_unclamped) {
994 PetscReal w[8];
995 ComputeTrilinearWeightsUnclamped(a1_new, a2_new, a3_new, w);
996 val = w[0] * fieldScal[bk+1][bj+1][bi+1] + w[1] * fieldScal[bk+1][bj+1][bi+2]
997 + w[2] * fieldScal[bk+1][bj+2][bi+1] + w[3] * fieldScal[bk+1][bj+2][bi+2]
998 + w[4] * fieldScal[bk+2][bj+1][bi+1] + w[5] * fieldScal[bk+2][bj+1][bi+2]
999 + w[6] * fieldScal[bk+2][bj+2][bi+1] + w[7] * fieldScal[bk+2][bj+2][bi+2];
1000 } else {
1001 ierr = TrilinearInterpolation_Scalar(fieldName, fieldScal,
1002 bi + 1, bj + 1, bk + 1, a1_new, a2_new, a3_new, &val); CHKERRQ(ierr);
1003 }
1004 ((PetscReal*)swarmOut)[p] = val;
1005 } else {
1006 Cmpnts ***fieldVec = (Cmpnts ***)fieldPtr;
1007 Cmpnts vec;
1008 if (needs_unclamped) {
1009 PetscReal w[8];
1010 ComputeTrilinearWeightsUnclamped(a1_new, a2_new, a3_new, w);
1011 vec.x = w[0]*fieldVec[bk+1][bj+1][bi+1].x + w[1]*fieldVec[bk+1][bj+1][bi+2].x
1012 + w[2]*fieldVec[bk+1][bj+2][bi+1].x + w[3]*fieldVec[bk+1][bj+2][bi+2].x
1013 + w[4]*fieldVec[bk+2][bj+1][bi+1].x + w[5]*fieldVec[bk+2][bj+1][bi+2].x
1014 + w[6]*fieldVec[bk+2][bj+2][bi+1].x + w[7]*fieldVec[bk+2][bj+2][bi+2].x;
1015 vec.y = w[0]*fieldVec[bk+1][bj+1][bi+1].y + w[1]*fieldVec[bk+1][bj+1][bi+2].y
1016 + w[2]*fieldVec[bk+1][bj+2][bi+1].y + w[3]*fieldVec[bk+1][bj+2][bi+2].y
1017 + w[4]*fieldVec[bk+2][bj+1][bi+1].y + w[5]*fieldVec[bk+2][bj+1][bi+2].y
1018 + w[6]*fieldVec[bk+2][bj+2][bi+1].y + w[7]*fieldVec[bk+2][bj+2][bi+2].y;
1019 vec.z = w[0]*fieldVec[bk+1][bj+1][bi+1].z + w[1]*fieldVec[bk+1][bj+1][bi+2].z
1020 + w[2]*fieldVec[bk+1][bj+2][bi+1].z + w[3]*fieldVec[bk+1][bj+2][bi+2].z
1021 + w[4]*fieldVec[bk+2][bj+1][bi+1].z + w[5]*fieldVec[bk+2][bj+1][bi+2].z
1022 + w[6]*fieldVec[bk+2][bj+2][bi+1].z + w[7]*fieldVec[bk+2][bj+2][bi+2].z;
1023 } else {
1024 ierr = TrilinearInterpolation_Vector(fieldName, fieldVec,
1025 bi + 1, bj + 1, bk + 1, a1_new, a2_new, a3_new, &vec); CHKERRQ(ierr);
1026 }
1027 ((PetscReal*)swarmOut)[3*p + 0] = vec.x;
1028 ((PetscReal*)swarmOut)[3*p + 1] = vec.y;
1029 ((PetscReal*)swarmOut)[3*p + 2] = vec.z;
1030 }
1031 }
1032
1033 /* Restore arrays and swarm fields */
1034 ierr = DMDAVecRestoreArrayRead(dm_field, fieldLocal_cellCentered, &fieldPtr); CHKERRQ(ierr);
1035 ierr = DMDAVecRestoreArrayRead(user->fda, user->lCent, (void *)&cent); CHKERRQ(ierr);
1036 ierr = DMSwarmRestoreField(swarm, swarmOutFieldName, NULL, NULL, &swarmOut); CHKERRQ(ierr);
1037 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_WEIGHT), NULL, NULL, (void**)&weights); CHKERRQ(ierr);
1038 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pids); CHKERRQ(ierr);
1039 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cellIDs); CHKERRQ(ierr);
1040 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&pos); CHKERRQ(ierr);
1041 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status); CHKERRQ(ierr);
1042
1043 LOG_ALLOW(GLOBAL, LOG_INFO, "Finished direct cell-center interpolation for field '%s'.\n", fieldName);
1045 PetscFunctionReturn(0);
1046}
1047
1048#undef __FUNCT__
1049#define __FUNCT__ "InterpolateEulerFieldFromCornerToSwarm"
1050
1051/**
1052 * @brief Corner-averaged interpolation path (legacy).
1053 * @details Stages cell-centered data to corners via unweighted averaging,
1054 * then trilinear interpolation from corners to particles.
1055 * Local to this translation unit.
1056 */
1058 UserCtx *user,
1059 Vec fieldLocal_cellCentered,
1060 const char *fieldName,
1061 const char *swarmOutFieldName)
1062{
1063 PetscErrorCode ierr;
1064 DM fda = user->fda;
1065 DM swarm = user->swarm;
1066 PetscInt bs;
1067 DMDALocalInfo info;
1068 PetscMPIInt rank;
1069
1070 // Generic pointers to the raw data arrays
1071 void *cellCenterPtr_read;
1072 void *cornerPtr_read_with_ghosts;
1073
1074 // Swarm-related pointers
1075 PetscInt *cellIDs = NULL;
1076 PetscReal *weights = NULL;
1077 PetscInt64 *pids = NULL; // For logging particle IDs in warnings
1078 void *swarmOut = NULL;
1079 PetscReal *pos = NULL;
1080 PetscInt *status = NULL;
1081 PetscInt nLocal;
1082
1083 PetscFunctionBegin;
1085 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1086 ierr = DMDAGetLocalInfo(fda, &info); CHKERRQ(ierr);
1087 ierr = VecGetBlockSize(fieldLocal_cellCentered, &bs); CHKERRQ(ierr);
1088 if (bs != 1 && bs != 3) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "BlockSize must be 1 or 3.");
1089
1090 LOG_ALLOW(GLOBAL, LOG_INFO, "Starting for field '%s'... \n", fieldName);
1091 // Select the appropriate DM for intermediate corner data based on block size
1092 DM dm_corner = (bs == 3) ? user->fda : user->da;
1093
1094 /* STAGE 1: Center-to-Corner Calculation with Communication */
1095
1096 // (A) Select the pre-created corner workspace for this block size. Both pairs
1097 // are allocated once by CreateAndInitializeAllVectors, so nothing is
1098 // created, destroyed, or size-checked on this path.
1099 const FieldId corner_field_id = (bs == 3) ? FIELD_ID_CELL_VECTOR_AT_CORNER
1101 FieldView corner_view;
1102 ierr = FieldGetView(user, corner_field_id, &corner_view); CHKERRQ(ierr);
1103
1104 LOG_ALLOW(LOCAL, LOG_TRACE, "[Rank %d] Using '%s' corner workspace for field '%s'.\n",
1105 rank, corner_view.descriptor->canonical_name, fieldName);
1106
1107 Vec cornerGlobal = corner_view.global_vec;
1108 Vec cornerLocal = corner_view.local_vec;
1109 ierr = VecSet(cornerGlobal, 0.0); CHKERRQ(ierr);
1110 ierr = VecSet(cornerLocal, 0.0); CHKERRQ(ierr);
1111
1112 // (B) Get a read-only array from the input cell-centered vector
1113 ierr = DMDAVecGetArrayRead(dm_corner, fieldLocal_cellCentered, &cellCenterPtr_read); CHKERRQ(ierr);
1114
1115 PetscInt size;
1116 ierr = VecGetSize(cornerGlobal, &size); CHKERRQ(ierr);
1117 LOG_ALLOW(LOCAL, LOG_TRACE, "[Rank %d] Corner global vector size for field '%s': %d.\n", rank, fieldName, (PetscInt)size);
1118
1119 size = 0;
1120
1121 ierr = VecGetSize(cornerLocal, &size); CHKERRQ(ierr);
1122 LOG_ALLOW(LOCAL, LOG_TRACE, "[Rank %d] Corner local vector size for field '%s': %d.\n", rank, fieldName, (PetscInt)size);
1123
1124 size = 0;
1125
1126 ierr = VecGetSize(fieldLocal_cellCentered, &size); CHKERRQ(ierr);
1127 LOG_ALLOW(LOCAL, LOG_TRACE, "[Rank %d] Cell-centered local vector size for field '%s': %d.\n", rank, fieldName, (PetscInt)size);
1128
1129 PetscInt xs,ys,zs,gxs,gys,gzs;
1130
1131 ierr = DMDAGetCorners(dm_corner,&xs,&ys,&zs,NULL,NULL,NULL); CHKERRQ(ierr);
1132 LOG_ALLOW(LOCAL, LOG_TRACE, "[Rank %d] DMDAGetCorners for field '%s': xs=%d, ys=%d, zs=%d.\n", rank, fieldName, (PetscInt)xs, (PetscInt)ys, (PetscInt)zs);
1133
1134 ierr = DMDAGetGhostCorners(dm_corner,&gxs,&gys,&gzs,NULL,NULL,NULL); CHKERRQ(ierr);
1135 LOG_ALLOW(LOCAL, LOG_TRACE, "[Rank %d] DMDAGetGhostCorners for field '%s': gxs=%d, gys=%d, gzs=%d.\n", rank, fieldName, (PetscInt)gxs, (PetscInt)gys, (PetscInt)gzs);
1136
1137 // DEBUG: Inspect Ucat ghost cells before interpolation
1138 /*
1139 if (bs == 3) {
1140 Cmpnts ***ucat_array = (Cmpnts***)cellCenterPtr_read;
1141 DMDALocalInfo info_debug;
1142 ierr = DMDAGetLocalInfo(fda, &info_debug); CHKERRQ(ierr);
1143
1144 // Only print on rank 0 to avoid clutter
1145 if (rank == 0) {
1146 PetscPrintf(PETSC_COMM_SELF, "\nDEBUG: Inspecting Ucat Ghost Cells...\n");
1147 PetscPrintf(PETSC_COMM_SELF, "--------------------------------------------------\n");
1148
1149 // --- Check the MINIMUM-SIDE (-Xi) ---
1150 // The first physical cell is at index info.xs (local index).
1151 // The first ghost cell is at info.xs - 1.
1152 PetscInt i_first_phys = info_debug.xs;
1153 PetscInt i_first_ghost = info_debug.xs - 1;
1154 PetscInt j_mid = info_debug.ys + info_debug.ym / 2;
1155 PetscInt k_mid = info_debug.zs + info_debug.zm / 2;
1156
1157 PetscPrintf(PETSC_COMM_SELF, "MIN-SIDE (-Xi):\n");
1158 PetscPrintf(PETSC_COMM_SELF, " Ghost Cell Ucat[%d][%d][%d] = (% .6f, % .6f, % .6f)\n",
1159 k_mid, j_mid, i_first_ghost,
1160 ucat_array[k_mid][j_mid][i_first_ghost].x,
1161 ucat_array[k_mid][j_mid][i_first_ghost].y,
1162 ucat_array[k_mid][j_mid][i_first_ghost].z);
1163 PetscPrintf(PETSC_COMM_SELF, " First Phys Cell Ucat[%d][%d][%d] = (% .6f, % .6f, % .6f)\n",
1164 k_mid, j_mid, i_first_phys,
1165 ucat_array[k_mid][j_mid][i_first_phys].x,
1166 ucat_array[k_mid][j_mid][i_first_phys].y,
1167 ucat_array[k_mid][j_mid][i_first_phys].z);
1168
1169
1170 // --- Check the MAXIMUM-SIDE (+Xi) ---
1171 // The last physical cell is at index info.xs + info.xm - 1.
1172 // The first ghost cell on the max side is at info.xs + info.xm.
1173 PetscInt i_last_phys = info_debug.xs + info_debug.xm - 1;
1174 PetscInt i_last_ghost = info_debug.xs + info_debug.xm;
1175
1176 PetscPrintf(PETSC_COMM_SELF, "MAX-SIDE (+Xi):\n");
1177 PetscPrintf(PETSC_COMM_SELF, " Last Phys Cell Ucat[%d][%d][%d] = (% .6f, % .6f, % .6f)\n",
1178 k_mid, j_mid, i_last_phys,
1179 ucat_array[k_mid][j_mid][i_last_phys].x,
1180 ucat_array[k_mid][j_mid][i_last_phys].y,
1181 ucat_array[k_mid][j_mid][i_last_phys].z);
1182 PetscPrintf(PETSC_COMM_SELF, " Ghost Cell Ucat[%d][%d][%d] = (% .6f, % .6f, % .6f)\n",
1183 k_mid, j_mid, i_last_ghost,
1184 ucat_array[k_mid][j_mid][i_last_ghost].x,
1185 ucat_array[k_mid][j_mid][i_last_ghost].y,
1186 ucat_array[k_mid][j_mid][i_last_ghost].z);
1187 PetscPrintf(PETSC_COMM_SELF, "--------------------------------------------------\n\n");
1188 }
1189 }
1190 */
1191 // DEBUG
1192
1193 // (C) Perform the center-to-corner interpolation directly into the global corner vector
1194 void *cornerPtr_write = NULL;
1195 ierr = DMDAVecGetArray(dm_corner, cornerGlobal, &cornerPtr_write); CHKERRQ(ierr);
1196
1197 LOG_ALLOW(LOCAL, LOG_TRACE, "[Rank %d] Starting center-to-corner interpolation for '%s'.\n", rank, fieldName);
1198
1199 // SINGLE, CLEAN CALL SITE: The macro handles the runtime dispatch based on 'bs'.
1200 ierr = InterpolateFieldFromCenterToCorner(bs, cellCenterPtr_read, cornerPtr_write, user); CHKERRQ(ierr);
1201
1202 LOG_ALLOW(LOCAL, LOG_TRACE, "[Rank %d] Finished center-to-corner interpolation for '%s'.\n", rank, fieldName);
1203 ierr = DMDAVecRestoreArray(dm_corner, cornerGlobal, &cornerPtr_write); CHKERRQ(ierr);
1204
1205 ierr = DMDAVecRestoreArrayRead(dm_corner, fieldLocal_cellCentered, &cellCenterPtr_read); CHKERRQ(ierr);
1206
1207 ierr = MPI_Barrier(PETSC_COMM_WORLD); CHKERRQ(ierr);
1208
1209 //////////// DEBUG
1210 /*
1211 // Log a synchronized header message from all ranks.
1212 LOG_ALLOW_SYNC(GLOBAL, LOG_DEBUG, "DEBUG: Dumping cornerGlobal BEFORE ghost exchange...\n");
1213
1214 // VecView prints to a PETSc viewer. PETSC_VIEWER_STDOUT_WORLD is a global, synchronized viewer.
1215 // We only need to call it if logging is active for this function.
1216 if (is_function_allowed(__func__) && (int)(LOG_DEBUG) <= (int)get_log_level()) {
1217 ierr = VecView(cornerGlobal, PETSC_VIEWER_STDOUT_WORLD); CHKERRQ(ierr);
1218 }
1219
1220 // Log a synchronized footer message.
1221 LOG_ALLOW_SYNC(GLOBAL, LOG_DEBUG, "DEBUG: Finished dumping cornerGlobal.\n");
1222 */
1223 ////////// DEBUG
1224
1225 // ierr = PetscBarrier((PetscObject)cornerGlobal); CHKERRQ(ierr);
1226
1227 // (D) CRITICAL STEP: Communicate the newly computed corner data to fill ghost regions
1228 LOG_ALLOW(LOCAL, LOG_TRACE, "[Rank %d] Beginning ghost exchange for corner data...\n", rank);
1229
1230 /* Routed through the shared ghost-update path now that the corner workspace
1231 * is catalogued; this replaces a hand-rolled global-to-local scatter. */
1232 ierr = UpdateLocalGhosts(user, corner_field_id); CHKERRQ(ierr);
1233
1234 LOG_ALLOW(LOCAL, LOG_TRACE, "[Rank %d] Ghost exchange for corner data complete.\n", rank);
1235
1236 LOG_ALLOW_SYNC(GLOBAL, LOG_VERBOSE, "getting array from %s on all ranks.\n", dm_corner == user->fda ? "fda" : "da");
1237 if (is_function_allowed(__func__) && (int)(LOG_VERBOSE) <= (int)get_log_level()) {
1238 // DEBUG: Inspect cornerLocal after ghost exchange
1239
1240 ierr = LOG_CORNER_FIELD_ANATOMY(user, corner_field_id, "After Corner Velocity Interpolated"); CHKERRQ(ierr);
1241
1242 // DEBUG
1243 //ierr = DMView(user->fda, PETSC_VIEWER_STDOUT_WORLD); CHKERRQ(ierr);
1244 }
1245
1246 /////// DEBUG
1247 /*
1248 // Log a synchronized header message from all ranks.
1249 LOG_ALLOW_SYNC(GLOBAL, LOG_DEBUG, "DEBUG: Dumping cornerLocal AFTER ghost exchange...\n");
1250
1251 // Here, we want each rank to print its own local vector.
1252 // PETSC_VIEWER_STDOUT_SELF is the correct tool for this. The LOG_ALLOW_SYNC
1253 // wrapper will ensure the output from each rank is printed sequentially.
1254 if (is_function_allowed(__func__) && (int)(LOG_DEBUG) <= (int)get_log_level()) {
1255 PetscMPIInt rank_d;
1256 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank_d); CHKERRQ(ierr);
1257
1258 // Use a synchronized print to create a clean header for each rank's output
1259 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "\n--- cornerLocal (Rank %d) ---\n", rank_d);
1260 PetscSynchronizedFlush(PETSC_COMM_WORLD, PETSC_STDOUT);
1261
1262 // Print the local vector's contents for this rank
1263 ierr = VecView(cornerLocal, PETSC_VIEWER_STDOUT_SELF); CHKERRQ(ierr);
1264
1265 // Use a synchronized print to create a clean footer
1266 PetscSynchronizedPrintf(PETSC_COMM_WORLD, "--- End cornerLocal (Rank %d) ---\n", rank_d);
1267 PetscSynchronizedFlush(PETSC_COMM_WORLD, PETSC_STDOUT);
1268 }
1269
1270 LOG_ALLOW_SYNC(GLOBAL, LOG_DEBUG, "DEBUG: Finished dumping cornerLocal.\n");
1271 */
1272 ///// DEBUG
1273
1274 // STAGE 2: Particle Interpolation using Ghosted Corner Data */
1275
1276 // (E) Get the local, GHOSTED array of corner data for the final interpolation step
1277 ierr = DMDAVecGetArrayRead(dm_corner, cornerLocal, &cornerPtr_read_with_ghosts); CHKERRQ(ierr);
1278
1279 // (F) Retrieve swarm fields for the particle loop
1280 ierr = DMSwarmGetLocalSize(swarm, &nLocal); CHKERRQ(ierr);
1281 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cellIDs); CHKERRQ(ierr);
1282 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pids); CHKERRQ(ierr);
1283 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_WEIGHT), NULL, NULL, (void**)&weights); CHKERRQ(ierr);
1284 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&pos); CHKERRQ(ierr);
1285 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status); CHKERRQ(ierr);
1286 ierr = DMSwarmGetField(swarm, swarmOutFieldName, NULL, NULL, &swarmOut); CHKERRQ(ierr);
1287
1288 LOG_ALLOW(LOCAL,LOG_TRACE," Rank %d holds data upto & including %d,%d,%d.\n",rank,info.gxs + info.gxm,info.gys+info.gym,info.gzs+info.gzm);
1289
1290 // (G) Loop over each local particle
1291 for (PetscInt p = 0; p < nLocal; p++) {
1292
1293 Particle particle;
1294
1295 ierr = UnpackSwarmFields(p,pids,weights,pos,cellIDs,NULL,status,NULL,NULL,NULL,&particle); CHKERRQ(ierr);
1296
1298 "[Rank %d] Particle PID %lld: global cell=(%d,%d,%d), weights=(%.4f,%.4f,%.4f)\n",
1299 rank, (long long)particle.PID, particle.cell[0], particle.cell[1], particle.cell[2],
1300 particle.weights.x, particle.weights.y, particle.weights.z);
1301 // Safety check: Ensure the entire 8-node stencil is within the valid memory
1302 // region (owned + ghosts) of the local array.
1303
1304 if (particle.cell[0] < info.gxs || particle.cell[0] >= info.gxs + info.gxm - 1 ||
1305 particle.cell[1] < info.gys || particle.cell[1] >= info.gys + info.gym - 1 ||
1306 particle.cell[2] < info.gzs || particle.cell[2] >= info.gzs + info.gzm - 1)
1307 {
1309 "[Rank %d] Particle PID %lld in global cell (%d,%d,%d) is in an un-interpolatable region (requires ghosts of ghosts or is out of bounds). Zeroing field '%s'.\n",
1310 rank, (long long)particle.PID, particle.cell[0], particle.cell[1], particle.cell[2], fieldName);
1311 if (bs == 3) {
1312 ((PetscReal*)swarmOut)[3*p + 0] = 0.0;
1313 ((PetscReal*)swarmOut)[3*p + 1] = 0.0;
1314 ((PetscReal*)swarmOut)[3*p + 2] = 0.0;
1315 } else {
1316 ((PetscReal*)swarmOut)[p] = 0.0;
1317 }
1318 continue;
1319 }
1320
1322 fieldName,
1323 cornerPtr_read_with_ghosts,
1324 &particle,
1325 swarmOut, p, bs);
1326 CHKERRQ(ierr);
1327 }
1328
1329 // (H) Restore all retrieved arrays and vectors */
1330 ierr = DMDAVecRestoreArrayRead(dm_corner, cornerLocal, &cornerPtr_read_with_ghosts); CHKERRQ(ierr);
1331
1332 // (I) Restore swarm fields
1333 ierr = DMSwarmRestoreField(swarm, swarmOutFieldName, NULL, NULL, &swarmOut); CHKERRQ(ierr);
1334 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_WEIGHT), NULL, NULL, (void**)&weights); CHKERRQ(ierr);
1335 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void**)&pids); CHKERRQ(ierr);
1336 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void**)&cellIDs); CHKERRQ(ierr);
1337 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&pos); CHKERRQ(ierr);
1338 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_LOCATION_STATUS), NULL, NULL, (void**)&status); CHKERRQ(ierr);
1339 LOG_ALLOW(GLOBAL, LOG_INFO, "Finished for field '%s'.\n", fieldName);
1341 PetscFunctionReturn(0);
1342}
1343
1344#undef __FUNCT__
1345#define __FUNCT__ "InterpolateEulerFieldToSwarm"
1346
1347/**
1348 * @brief Dispatches grid-to-particle interpolation to the method selected in the control file.
1349 * @details Routes to InterpolateEulerFieldFromCenterToSwarm (direct trilinear, second-order)
1350 * or InterpolateEulerFieldFromCornerToSwarm (corner-averaged, legacy) based on
1351 * user->simCtx->interpolationMethod.
1352 */
1354 UserCtx *user,
1355 FieldId source_field_id,
1356 ParticleFieldId target_field_id)
1357{
1358 PetscErrorCode ierr;
1359 FieldView source_view;
1360 const ParticleFieldDescriptor *target_descriptor = NULL;
1361
1362 PetscFunctionBegin;
1363
1364 ierr = FieldGetView(user, source_field_id, &source_view); CHKERRQ(ierr);
1365 ierr = ParticleFieldGetDescriptor(target_field_id, &target_descriptor); CHKERRQ(ierr);
1366 PetscCheck(source_view.descriptor->layout == FIELD_LAYOUT_CELL_CENTERED,
1367 PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP,
1368 "Eulerian-to-particle interpolation requires a cell-centered source; '%s' uses layout %s.",
1369 source_view.descriptor->canonical_name,
1370 FieldLayoutName(source_view.descriptor->layout));
1371 PetscCheck(target_descriptor->data_type == PETSC_REAL,
1372 PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP,
1373 "Eulerian interpolation destination '%s' must use PETSC_REAL storage.",
1374 target_descriptor->canonical_name);
1375 PetscCheck(source_view.descriptor->dof == target_descriptor->components,
1376 PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP,
1377 "Eulerian source '%s' has %d components but particle destination '%s' has %d.",
1378 source_view.descriptor->canonical_name, source_view.descriptor->dof,
1379 target_descriptor->canonical_name, target_descriptor->components);
1380
1382 ierr = InterpolateEulerFieldFromCenterToSwarm(user, source_view.local_vec,
1383 source_view.descriptor->canonical_name,
1384 target_descriptor->canonical_name); CHKERRQ(ierr);
1385 } else {
1386 ierr = InterpolateEulerFieldFromCornerToSwarm(user, source_view.local_vec,
1387 source_view.descriptor->canonical_name,
1388 target_descriptor->canonical_name); CHKERRQ(ierr);
1389 }
1390
1391 PetscFunctionReturn(0);
1392}
1393
1394#undef __FUNCT__
1395#define __FUNCT__ "InterpolateAllFieldsToSwarm"
1396/**
1397 * @brief Internal helper implementation: `InterpolateAllFieldsToSwarm()`.
1398 * @details Local to this translation unit.
1399 */
1401{
1402 PetscErrorCode ierr;
1403 PetscMPIInt rank;
1404 PetscFunctionBegin;
1405
1407
1408 ierr = MPI_Comm_rank(PETSC_COMM_WORLD,&rank); CHKERRQ(ierr);
1409
1411 " Interpolation of ucat to velocity begins on rank %d.\n",rank);
1413 PARTICLE_FIELD_ID_VELOCITY); CHKERRQ(ierr);
1415 PARTICLE_FIELD_ID_DIFFUSIVITY); CHKERRQ(ierr);
1418 /* Add fields as necessary here*/
1419
1420 ierr = MPI_Barrier(PETSC_COMM_WORLD); CHKERRQ(ierr);
1422 "[rank %d]Completed Interpolateting all fields to the swarm.\n",rank);
1423
1425
1426 PetscFunctionReturn(0);
1427}
1428
1429/////////////////////// Scatter from particles to euler fields
1430
1431/**
1432 * @brief Functions for scattering particle data (scalar or vector) onto
1433 * Eulerian grid fields by averaging contributions within each cell.
1434 *
1435 * This file provides a modular set of functions to perform particle-to-grid
1436 * projection, specifically calculating cell-averaged quantities from particle properties.
1437 * It assumes a PETSc environment using DMDA for the grids and DMSwarm for particles.
1438 *
1439 * Key Features:
1440 * - Handles both scalar (DOF=1) and vector (DOF=3) particle fields.
1441 * - Uses a pre-calculated particle count vector (`ParticleCount`) for normalization.
1442 * - Uses particle-catalog metadata to resolve each supported Eulerian target.
1443 * - Modifies an existing, explicitly provided Eulerian field vector in place.
1444 * - Provides a high-level wrapper function (`ScatterAllParticleFieldsToEulerFields`)
1445 * to easily scatter a standard set of fields.
1446 * - Uses only the base `SETERRQ` macro for error reporting to maximize compiler compatibility.
1447 *
1448 * Dependencies:
1449 * - PETSc library (DMDA, DMSwarm, Vec, IS, PetscLog, etc.)
1450 * - A `UserCtx` struct (defined elsewhere, e.g., "userctx.h") containing pointers
1451 * to relevant DMs (`da`, `fda`), Vecs (`ParticleCount`, `P`, `Nvert`, `Ucat`, etc.),
1452 * and the `DMSwarm` object (`swarm`).
1453 * - A custom DMSwarm field named `"DMSwarm_CellID"` (blockSize=3, type=PETSC_INT)
1454 * must be registered and populated with the local cell indices for each particle.
1455 * - Logging infrastructure (`LOG_ALLOW`, etc.) assumed to be defined elsewhere.
1456 *
1457 * @defgroup scatter_module Particle-to-Grid Scattering
1458 * @{
1459 */
1460
1461//-----------------------------------------------------------------------------
1462// Internal Helper Modules (Lower-level building blocks)
1463//-----------------------------------------------------------------------------
1464/**
1465 * @defgroup scatter_module_internal Internal Scattering Helpers
1466 * @ingroup scatter_module
1467 * @brief Lower-level functions used by the main scattering routines.
1468 * @{
1469 */
1470
1471#undef __FUNCT__
1472#define __FUNCT__ "AccumulateParticleField"
1473/*
1474 * Internal implementation detail for public API `AccumulateParticleField`.
1475 * Documentation is maintained in include/interpolation.h to keep a single source
1476 * of truth for Doxygen output.
1477 */
1478PetscErrorCode AccumulateParticleField(DM swarm, ParticleFieldId particle_field_id,
1479 DM gridSumDM, Vec localAccumulatorVec)
1480{
1481 PetscErrorCode ierr;
1482 PetscInt dof;
1483 PetscInt nlocal, p;
1484 const PetscReal *particle_arr = NULL;
1485 const PetscInt *cell_id_arr = NULL;
1486
1487 // DMDA Accessors
1488 PetscScalar ***arr_1d = NULL; // For scalar fields
1489 PetscScalar ****arr_3d = NULL; // For vector fields
1490
1491 // Ghosted dimension variables
1492 PetscInt gxs, gys, gzs, gxm, gym, gzm;
1493 PetscMPIInt rank;
1494 char msg[ERROR_MSG_BUFFER_SIZE];
1495 const ParticleFieldDescriptor *descriptor = NULL;
1496 const char *particleFieldName = NULL;
1497
1498 PetscFunctionBeginUser;
1500
1501 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1502
1503 ierr = ParticleFieldGetDescriptor(particle_field_id, &descriptor); CHKERRQ(ierr);
1504 PetscCheck(descriptor->data_type == PETSC_REAL, PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP,
1505 "Particle-to-grid accumulation requires PETSC_REAL data; field '%s' uses %s.",
1506 descriptor->canonical_name, PetscDataTypes[descriptor->data_type]);
1507 particleFieldName = descriptor->canonical_name;
1508
1509 // --- 1. Validation & Setup ---
1510 if (!swarm || !gridSumDM || !localAccumulatorVec)
1511 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Null input in AccumulateParticleField.");
1512
1513 // Get DMDA information
1514 ierr = DMDAGetInfo(gridSumDM, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &dof, NULL, NULL, NULL, NULL, NULL); CHKERRQ(ierr);
1515 // Get Ghosted Corners (Global indices of the ghosted patch start, and its dimensions)
1516 ierr = DMDAGetGhostCorners(gridSumDM, &gxs, &gys, &gzs, &gxm, &gym, &gzm); CHKERRQ(ierr);
1517
1518 // --- 2. Verify Vector Type (Global vs Local) ---
1519 {
1520 PetscInt vecSize;
1521 PetscInt expectedLocalSize = gxm * gym * gzm * dof;
1522 ierr = VecGetSize(localAccumulatorVec, &vecSize); CHKERRQ(ierr);
1523
1524 if (vecSize != expectedLocalSize) {
1525 PetscSNPrintf(msg, sizeof(msg),
1526 "Vector dimension mismatch! Expected Ghosted Local Vector size %d (gxm*gym*gzm*dof), got %d. "
1527 "Did you pass a Global Vector instead of a Local Vector?",
1528 expectedLocalSize, vecSize);
1529 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "%s", msg);
1530 }
1531 }
1532
1533 // --- 3. Acquire Particle Data ---
1534 ierr = DMSwarmGetLocalSize(swarm, &nlocal); CHKERRQ(ierr);
1535 // These calls will fail nicely if the field doesn't exist
1536 ierr = DMSwarmGetField(swarm, particleFieldName, NULL, NULL, (void **)&particle_arr); CHKERRQ(ierr);
1537 ierr = DMSwarmGetField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void **)&cell_id_arr); CHKERRQ(ierr);
1538
1539 // --- 4. Acquire Grid Accessors ---
1540 // DMDAVecGetArray* handles the mapping from Global (i,j,k) to the underlying Local Array index
1541 if (dof == 1) {
1542 ierr = DMDAVecGetArray(gridSumDM, localAccumulatorVec, &arr_1d); CHKERRQ(ierr);
1543 } else if (dof == 3) {
1544 ierr = DMDAVecGetArrayDOF(gridSumDM, localAccumulatorVec, &arr_3d); CHKERRQ(ierr);
1545 } else {
1546 PetscSNPrintf(msg, sizeof(msg), "Unsupported DOF=%d. AccumulateParticleField supports DOF 1 or 3.", dof);
1547 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "%s", msg);
1548 }
1549
1550 // --- 5. Accumulate Loop ---
1551 // Iterate through all local particles
1552 for (p = 0; p < nlocal; ++p) {
1553 // Retrieve Geometric Grid Index (0-based)
1554 PetscInt i_geom = cell_id_arr[p * 3 + 0];
1555 PetscInt j_geom = cell_id_arr[p * 3 + 1];
1556 PetscInt k_geom = cell_id_arr[p * 3 + 2];
1557
1558 // Apply Shift (+1) to match Memory Layout (Index 0 is boundary/ghost)
1559 PetscInt i = i_geom + 1;
1560 PetscInt j = j_geom + 1;
1561 PetscInt k = k_geom + 1;
1562
1563 // Bounds Check: Ensure (i,j,k) falls within the Local Ghosted Patch.
1564 // This allows writing to ghost slots which will later be reduced to the owner rank.
1565 if (i >= gxs && i < gxs + gxm &&
1566 j >= gys && j < gys + gym &&
1567 k >= gzs && k < gzs + gzm)
1568 {
1569 if (dof == 1) {
1570 arr_1d[k][j][i] += particle_arr[p];
1571 } else {
1572 // For DOF=3, unroll the loop for slight optimization
1573 arr_3d[k][j][i][0] += particle_arr[p * 3 + 0];
1574 arr_3d[k][j][i][1] += particle_arr[p * 3 + 1];
1575 arr_3d[k][j][i][2] += particle_arr[p * 3 + 2];
1576 }
1577 }
1578 // Note: Particles outside the ghost layer are skipped. This is expected behavior
1579 // if particles have not yet been migrated or localized correctly.
1580 }
1581
1582 // --- 6. Restore Arrays and Fields ---
1583 if (dof == 1) {
1584 ierr = DMDAVecRestoreArray(gridSumDM, localAccumulatorVec, &arr_1d); CHKERRQ(ierr);
1585 } else {
1586 ierr = DMDAVecRestoreArrayDOF(gridSumDM, localAccumulatorVec, &arr_3d); CHKERRQ(ierr);
1587 }
1588
1589 ierr = DMSwarmRestoreField(swarm, particleFieldName, NULL, NULL, (void **)&particle_arr); CHKERRQ(ierr);
1590 ierr = DMSwarmRestoreField(swarm, ParticleFieldName(PARTICLE_FIELD_ID_CELL_ID), NULL, NULL, (void **)&cell_id_arr); CHKERRQ(ierr);
1591
1593 PetscFunctionReturn(0);
1594}
1595
1596#undef __FUNCT__
1597#define __FUNCT__ "NormalizeGridVectorByCount"
1598
1599/* Implementation for NormalizeGridVectorByCount declared in include/interpolation.h. */
1600PetscErrorCode NormalizeGridVectorByCount(DM countDM, Vec countVec,
1601 DM dataDM, Vec sumVec, Vec avgVec)
1602{
1603 PetscErrorCode ierr;
1604 PetscInt data_dof;
1605 PetscInt count_dof;
1606 PetscMPIInt rank;
1607 char msg[ERROR_MSG_BUFFER_SIZE];
1608
1609 // Pointers for DMDA array accessors - declare specific types
1610 PetscScalar ***count_arr_3d = NULL; // For DOF=1 count vector (3D DMDA)
1611 PetscScalar ***sum_arr_scalar = NULL; // For DOF=1 sum vector (3D DMDA)
1612 PetscScalar ***avg_arr_scalar = NULL; // For DOF=1 avg vector (3D DMDA)
1613 PetscScalar ****sum_arr_vector = NULL; // For DOF=3 sum vector (3D DMDA + DOF)
1614 PetscScalar ****avg_arr_vector = NULL; // For DOF=3 avg vector (3D DMDA + DOF)
1615
1616
1617 PetscFunctionBeginUser;
1618
1620
1621 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
1622
1623 // --- Validation ---
1624 ierr = DMDAGetInfo(countDM, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &count_dof, NULL, NULL, NULL, NULL, NULL); CHKERRQ(ierr);
1625 ierr = DMDAGetInfo(dataDM, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &data_dof, NULL, NULL, NULL, NULL, NULL); CHKERRQ(ierr);
1626 if (count_dof != 1) { PetscSNPrintf(msg, sizeof(msg), "countDM must have DOF=1, got %d.", count_dof); SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "%s", msg); }
1627 if (data_dof != 1 && data_dof != 3) { PetscSNPrintf(msg, sizeof(msg), "dataDM DOF must be 1 or 3, got %d.", data_dof); SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "%s", msg); }
1628
1629 // --- Get Array Access using appropriate DMDA accessors ---
1630 ierr = DMDAVecGetArrayRead(countDM, countVec, &count_arr_3d); CHKERRQ(ierr);
1631
1632 if (data_dof == 1) {
1633 ierr = DMDAVecGetArrayRead(dataDM, sumVec, &sum_arr_scalar); CHKERRQ(ierr);
1634 ierr = DMDAVecGetArray(dataDM, avgVec, &avg_arr_scalar); CHKERRQ(ierr);
1635 } else { // data_dof == 3
1636 ierr = DMDAVecGetArrayDOFRead(dataDM, sumVec, &sum_arr_vector); CHKERRQ(ierr);
1637 ierr = DMDAVecGetArrayDOF(dataDM, avgVec, &avg_arr_vector); CHKERRQ(ierr);
1638 }
1639
1640 // Get the corners (global start indices) and dimensions of the *local owned* region
1641 PetscInt xs, ys, zs, xm, ym, zm;
1642 ierr = DMDAGetCorners(countDM, &xs, &ys, &zs, &xm, &ym, &zm); CHKERRQ(ierr);
1643
1644 // --- Normalize Over Owned Cells ---
1645 LOG_ALLOW(LOCAL, LOG_DEBUG, "(Rank %d): Normalizing DOF=%d data over owned range [%d:%d, %d:%d, %d:%d].\n",
1646 rank, data_dof, xs, xs+xm, ys, ys+ym, zs, zs+zm);
1647
1648 // Loop using GLOBAL indices (i, j, k) over the range owned by this process
1649 for (PetscInt k = zs; k < zs + zm; ++k) {
1650 for (PetscInt j = ys; j < ys + ym; ++j) {
1651 for (PetscInt i = xs; i < xs + xm; ++i) {
1652
1653 // Access the count using standard 3D indexing
1654 PetscScalar count = count_arr_3d[k][j][i];
1655
1656 if (PetscRealPart(count) > 0.5) { // Use tolerance for float comparison
1657 if (data_dof == 1) {
1658 // Access scalar sum/avg using standard 3D indexing
1659 avg_arr_scalar[k][j][i] = sum_arr_scalar[k][j][i] / count;
1660 } else { // data_dof == 3
1661 // Access vector components using DOF indexing on the last dimension
1662 for (PetscInt c = 0; c < data_dof; ++c) {
1663 avg_arr_vector[k][j][i][c] = sum_arr_vector[k][j][i][c] / count;
1664 }
1665 }
1666 } else { // count is zero or negative
1667 // Set average to zero
1668 if (data_dof == 1) {
1669 avg_arr_scalar[k][j][i] = 0.0;
1670 } else { // data_dof == 3
1671 for (PetscInt c = 0; c < data_dof; ++c) {
1672 avg_arr_vector[k][j][i][c] = 0.0;
1673 }
1674 }
1675 } // end if count > 0.5
1676 } // end i loop
1677 } // end j loop
1678 } // end k loop
1679
1680 // --- Restore Arrays using appropriate functions ---
1681 ierr = DMDAVecRestoreArrayRead(countDM, countVec, &count_arr_3d); CHKERRQ(ierr);
1682 if (data_dof == 1) {
1683 ierr = DMDAVecRestoreArrayRead(dataDM, sumVec, &sum_arr_scalar); CHKERRQ(ierr);
1684 ierr = DMDAVecRestoreArray(dataDM, avgVec, &avg_arr_scalar); CHKERRQ(ierr);
1685 } else { // data_dof == 3
1686 ierr = DMDAVecRestoreArrayDOFRead(dataDM, sumVec, &sum_arr_vector); CHKERRQ(ierr);
1687 ierr = DMDAVecRestoreArrayDOF(dataDM, avgVec, &avg_arr_vector); CHKERRQ(ierr);
1688 }
1689
1690 // --- Assemble Final Average Vector ---
1691 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Assembling final average vector (DOF=%d).\n", data_dof);
1692 ierr = VecAssemblyBegin(avgVec); CHKERRQ(ierr);
1693 ierr = VecAssemblyEnd(avgVec); CHKERRQ(ierr);
1694
1695
1697
1698 PetscFunctionReturn(0);
1699}
1700
1701/** @} */ // End of scatter_module_internal group
1702
1703//-----------------------------------------------------------------------------
1704// User-Facing API
1705//-----------------------------------------------------------------------------
1706//-----------------------------------------------------------------------------
1707// MODULE 4: Internal Scatter Orchestration Helper - No PetscErrorClear
1708//-----------------------------------------------------------------------------
1709
1710#undef __FUNCT__
1711#define __FUNCT__ "ScatterParticleFieldToEulerField_Internal"
1712/**
1713 * @brief Accumulate one particle field onto the Eulerian grid using the selected scatter stencil.
1714 */
1716 ParticleFieldId particle_field_id,
1717 DM targetDM,
1718 PetscInt expected_dof,
1719 Vec eulerFieldAverageVec)
1720{
1721 PetscErrorCode ierr;
1722 PetscInt target_dof = 0;
1723 Vec globalsumVec = NULL;
1724 Vec localsumVec = NULL;
1725 char msg[ERROR_MSG_BUFFER_SIZE]; // Buffer for formatted error messages
1726 const char *particleFieldName = ParticleFieldName(particle_field_id);
1727
1728 PetscFunctionBeginUser;
1729
1731
1732 if (!user || !user->swarm || !user->ParticleCount || !targetDM || !eulerFieldAverageVec)
1733 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "NULL input provided to ScatterParticleFieldToEulerField_Internal.");
1734
1735 ierr = DMDAGetInfo(targetDM, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &target_dof, NULL, NULL, NULL, NULL, NULL); CHKERRQ(ierr);
1736 if (target_dof != expected_dof) {
1737 PetscSNPrintf(msg, sizeof(msg),
1738 "Field '%s' expects DOF %d but targetDM reports DOF %d.",
1739 particleFieldName, expected_dof, target_dof);
1740 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP, "%s", msg);
1741 }
1742
1743 // --- Check if Particle Field Exists ---
1744 // Attempt a GetField call; if it fails, the field doesn't exist.
1745 // We let CHKERRQ handle the error directly if the field doesn't exist OR
1746 // we catch it specifically to provide a more tailored message.
1747
1748 /*
1749 LOG_ALLOW(GLOBAL,LOG_DEBUG,"Field %s being accessed to check existence \n",particleFieldName);
1750 ierr = DMSwarmGetField(user->swarm, particleFieldName, NULL, NULL, NULL);
1751 if (ierr) { // If GetField returns an error
1752 PetscSNPrintf(msg, sizeof(msg), "Particle field '%s' not found in DMSwarm for scattering.", particleFieldName);
1753 // Directly set the error, overwriting the one from GetField
1754 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, msg);
1755 }
1756 ierr = DMSwarmRestoreField(user->swarm, particleFieldName, NULL, NULL, NULL);
1757 */
1758
1759 // --- Setup Temporary Sum Vector ---
1760 ierr = VecDuplicate(eulerFieldAverageVec, &globalsumVec); CHKERRQ(ierr);
1761 ierr = VecSet(globalsumVec, 0.0); CHKERRQ(ierr);
1762 ierr = PetscSNPrintf(msg, sizeof(msg), "TempSum_%s", particleFieldName); CHKERRQ(ierr);
1763 ierr = PetscObjectSetName((PetscObject)globalsumVec, msg); CHKERRQ(ierr);
1764
1765 // create local vector for accumulation
1766 ierr = DMGetLocalVector(targetDM, &localsumVec); CHKERRQ(ierr);
1767 ierr = VecSet(localsumVec, 0.0); CHKERRQ(ierr); // Must be zeroed before accumulation
1768 ierr = PetscSNPrintf(msg, sizeof(msg), "LocalTempSum_%s", particleFieldName); CHKERRQ(ierr);
1769 ierr = PetscObjectSetName((PetscObject)localsumVec, msg); CHKERRQ(ierr);
1770
1771 // --- Accumulate ---
1772 // This will call DMSwarmGetField again. If it failed above, it will likely fail here too,
1773 // unless the error was cleared somehow between the check and here (unlikely).
1774 // If the check above was skipped (Option 1), this is where the error for non-existent
1775 // field will be caught by CHKERRQ.
1776 ierr = AccumulateParticleField(user->swarm, particle_field_id, targetDM, localsumVec); CHKERRQ(ierr);
1777
1778 // --- Local to Global Sum ---
1779 ierr = DMLocalToGlobalBegin(targetDM, localsumVec, ADD_VALUES, globalsumVec); CHKERRQ(ierr);
1780 ierr = DMLocalToGlobalEnd(targetDM, localsumVec, ADD_VALUES, globalsumVec); CHKERRQ(ierr);
1781 // Return local vector to DM
1782 ierr = DMRestoreLocalVector(targetDM, &localsumVec); CHKERRQ(ierr);
1783
1784 // Calculate the number of particles per cell.
1785 ierr = CalculateParticleCountPerCell(user); CHKERRQ(ierr);
1786 // --- Normalize ---
1787 ierr = NormalizeGridVectorByCount(user->da, user->ParticleCount, targetDM, globalsumVec, eulerFieldAverageVec); CHKERRQ(ierr);
1788
1789 // --- Cleanup ---
1790 ierr = VecDestroy(&globalsumVec); CHKERRQ(ierr);
1791
1792
1794
1795 PetscFunctionReturn(0);
1796}
1797
1798#undef __FUNCT__
1799#define __FUNCT__ "ScatterParticleFieldToEulerField"
1800
1801/* Implementation for ScatterParticleFieldToEulerField declared in include/interpolation.h. */
1803 ParticleFieldId particle_field_id,
1804 Vec eulerFieldAverageVec)
1805{
1806 PetscErrorCode ierr;
1807 DM targetDM = NULL; // Will point to user->da or user->fda
1808 PetscInt expected_dof = 0; // Will be 1 or 3
1809 char msg[ERROR_MSG_BUFFER_SIZE]; // Buffer for formatted error messages
1810 const ParticleFieldDescriptor *particle_descriptor = NULL;
1811 const FieldDescriptor *eulerian_descriptor = NULL;
1812 FieldView target_view;
1813 const char *particleFieldName = NULL;
1814
1815 PetscFunctionBeginUser;
1816
1818
1819 // --- Essential Input Validation ---
1820 if (!user) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx pointer is NULL.");
1821 if (!user->swarm) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx->swarm is NULL.");
1822 if (!user->ParticleCount) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx->ParticleCount is NULL.");
1823 if (!eulerFieldAverageVec) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Output eulerFieldAverageVec is NULL.");
1824 ierr = ParticleFieldGetDescriptor(particle_field_id, &particle_descriptor); CHKERRQ(ierr);
1825 PetscCheck((particle_descriptor->capabilities & PARTICLE_FIELD_CAPABILITY_EULERIAN_SCATTER) != 0,
1826 PETSC_COMM_SELF, PETSC_ERR_SUP,
1827 "Particle field '%s' has no registered Eulerian scatter target.",
1828 particle_descriptor->canonical_name);
1829 PetscCheck(particle_descriptor->eulerian_scatter_target != FIELD_ID_INVALID,
1830 PETSC_COMM_SELF, PETSC_ERR_PLIB,
1831 "Particle field '%s' advertises scatter support without an Eulerian target.",
1832 particle_descriptor->canonical_name);
1833 particleFieldName = particle_descriptor->canonical_name;
1834 expected_dof = particle_descriptor->components;
1835 ierr = FieldGetDescriptor(particle_descriptor->eulerian_scatter_target, &eulerian_descriptor); CHKERRQ(ierr);
1836 PetscCheck(eulerian_descriptor->dof == expected_dof, PETSC_COMM_SELF, PETSC_ERR_PLIB,
1837 "Particle field '%s' has %d components but Eulerian target '%s' has %d.",
1838 particleFieldName, expected_dof, eulerian_descriptor->canonical_name, eulerian_descriptor->dof);
1839 ierr = FieldGetView(user, particle_descriptor->eulerian_scatter_target, &target_view); CHKERRQ(ierr);
1840 targetDM = target_view.dm;
1841
1842 // --- Validate the provided Target Vec's Compatibility ---
1843 DM vec_dm;
1844 PetscInt vec_dof;
1845 // Check that the provided average vector has a DM associated with it
1846 ierr = VecGetDM(eulerFieldAverageVec, &vec_dm); CHKERRQ(ierr);
1847 if (!vec_dm) {
1848 PetscSNPrintf(msg, sizeof(msg), "Provided eulerFieldAverageVec for field '%s' does not have an associated DM.", particleFieldName);
1849 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, "%s", msg);
1850 }
1851 // Get the block size (DOF) of the provided vector
1852 ierr = VecGetBlockSize(eulerFieldAverageVec, &vec_dof); CHKERRQ(ierr);
1853 // Compare the vector's associated DM with the one determined by the field name
1854 if (vec_dm != targetDM) {
1855 const char *target_dm_name = "targetDM", *vec_dm_name = "vec_dm";
1856 // Get actual names if possible for a more informative error message
1857 PetscObjectGetName((PetscObject)targetDM, &target_dm_name);
1858 PetscObjectGetName((PetscObject)vec_dm, &vec_dm_name);
1859 PetscSNPrintf(msg, sizeof(msg), "Provided eulerFieldAverageVec associated with DM '%s', but field '%s' requires scatter to DM '%s'.", vec_dm_name, particleFieldName, target_dm_name);
1860 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP, "%s", msg);
1861 }
1862 // Compare the vector's DOF with the one expected for the field name
1863 if (vec_dof != expected_dof) {
1864 PetscSNPrintf(msg, sizeof(msg), "Field '%s' requires DOF %d, but provided eulerFieldAverageVec has DOF %d.", particleFieldName, expected_dof, vec_dof);
1865 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP, "%s", msg);
1866 }
1867
1868 // --- Perform Scatter using Internal Helper ---
1869 // Log intent before calling the core logic
1870 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Scattering field '%s' (DOF=%d).\n", particleFieldName, expected_dof);
1871 ierr = ScatterParticleFieldToEulerField_Internal(user, // Pass user context
1872 particle_field_id,
1873 targetDM, // Determined target DM (da or fda)
1874 expected_dof, // Determined DOF (1 or 3)
1875 eulerFieldAverageVec); // The output vector
1876 CHKERRQ(ierr); // Handle potential errors from the internal function
1877
1878 LOG_ALLOW(GLOBAL, LOG_INFO, "Successfully scattered field '%s'.\n", particleFieldName);
1879
1881
1882 PetscFunctionReturn(0);
1883}
1884
1885#undef __FUNCT__
1886#define __FUNCT__ "ScatterAllParticleFieldsToEulerFields"
1887/* Implementation for ScatterAllParticleFieldsToEulerFields declared in include/interpolation.h. */
1889{
1890 PetscErrorCode ierr;
1891 PetscFunctionBeginUser;
1893
1894 LOG_ALLOW(GLOBAL, LOG_INFO, "Starting scattering of specified particle fields to Eulerian grids.\n");
1895
1896 // --- Pre-computation Check: Ensure Particle Counts are Ready ---
1897 if (!user->ParticleCount) {
1898 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, "UserCtx->ParticleCount is NULL. Compute counts before calling ScatterAllParticleFieldsToEulerFields.");
1899 }
1900
1901 // --- Scatter Particle Field "Psi" -> Eulerian Field user->Psi (on da) ---
1902 // Check if the target Eulerian vector 'user->Psi' exists.
1903 if (user->Psi) {
1904
1905 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Scattering particle field 'Psi' to user->Psi.\n");
1906 // Zero the target vector before accumulating the new average for this step/call.
1907
1908 // Debug Verification ------------------------------------------------
1909 Vec swarm_Psi;
1910 PetscReal Avg_Psi,Avg_swarm_Psi;
1911
1912 ierr = VecMean(user->Psi,&Avg_Psi);
1913 LOG_ALLOW(GLOBAL,LOG_DEBUG," Average of Scalar(Psi) before scatter: %.4f.\n",Avg_Psi);
1914
1915 ierr = DMSwarmCreateGlobalVectorFromField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_PSI), &swarm_Psi);
1916 ierr = VecMean(swarm_Psi,&Avg_swarm_Psi);
1917
1918 LOG_ALLOW(GLOBAL,LOG_DEBUG," Average of Particle Scalar(Psi): %.4f.\n",Avg_swarm_Psi);
1919
1920 ierr = DMSwarmDestroyGlobalVectorFromField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_PSI), &swarm_Psi);
1921 // Debug----------------------------------------------------------------
1922
1923 //ierr = VecSet(user->P, 0.0); CHKERRQ(ierr);
1924 // Call the unified scatter function. It will handle DM determination and validation.
1925 // It will also error out if the *particle* field "Psi" doesn't exist in the swarm.
1926 ierr = ScatterParticleFieldToEulerField(user, PARTICLE_FIELD_ID_PSI, user->Psi); CHKERRQ(ierr);
1927 ierr = VecMean(user->Psi,&Avg_Psi);
1928
1929 LOG_ALLOW(GLOBAL,LOG_DEBUG," Average of Scalar(Psi) after scatter: %.4f.\n",Avg_Psi);
1930 } else {
1931 // Only log a warning if the target Eulerian field is missing in the context.
1932 LOG_ALLOW(GLOBAL, LOG_WARNING, "Skipping scatter for 'Psi': UserCtx->Psi is NULL.\n");
1933 }
1934
1935 // Additional scatterable particle fields require an explicit catalog entry
1936 // with a compatible persistent Eulerian target.
1937
1938 LOG_ALLOW(GLOBAL, LOG_INFO, "Finished scattering specified particle fields.\n");
1940 PetscFunctionReturn(0);
1941}
1942
1943/** @} */ // End of scatter_module group
1944
1945
1946#undef __FUNCT__
1947#define __FUNCT__ "InterpolateCornerToFaceCenter_Scalar"
1948
1949/**
1950 * @brief Internal helper implementation: `InterpolateCornerToFaceCenter_Scalar()`.
1951 * @details Local to this translation unit.
1952 */
1954 PetscReal ***corner_arr,
1955 PetscReal ***faceX_arr,
1956 PetscReal ***faceY_arr,
1957 PetscReal ***faceZ_arr,
1958 UserCtx *user)
1959{
1960 PetscErrorCode ierr;
1961 DMDALocalInfo info;
1962
1963 PetscFunctionBeginUser;
1964
1966
1967 ierr = DMDAGetLocalInfo(user->fda, &info); CHKERRQ(ierr);
1968
1969 // Determine owned-cell ranges based on corner-node ownership
1970 PetscInt xs, xm, ys, ym, zs, zm;
1971 ierr = GetOwnedCellRange(&info, 0, &xs, &xm); CHKERRQ(ierr);
1972 ierr = GetOwnedCellRange(&info, 1, &ys, &ym); CHKERRQ(ierr);
1973 ierr = GetOwnedCellRange(&info, 2, &zs, &zm); CHKERRQ(ierr);
1974
1975 // Global exclusive end indices for cells
1976 PetscInt xe = xs + xm;
1977 PetscInt ye = ys + ym;
1978 PetscInt ze = zs + zm;
1979
1980 // --- X‐faces: loops k=zs..ze-1, j=ys..ye-1, i=xs..xe (xm+1 faces per row) ---
1981 for (PetscInt k = zs; k < ze; ++k) {
1982 PetscInt k_loc = k - zs;
1983 for (PetscInt j = ys; j < ye; ++j) {
1984 PetscInt j_loc = j - ys;
1985 for (PetscInt i = xs; i <= xe; ++i) {
1986 PetscInt i_loc = i - xs; // 0..xm
1987 // Average the four corners of the Y-Z face at X = i
1988 PetscReal sum = corner_arr[k ][j ][i]
1989 + corner_arr[k+1][j ][i]
1990 + corner_arr[k ][j+1][i]
1991 + corner_arr[k+1][j+1][i];
1992 faceX_arr[k_loc][j_loc][i_loc] = sum * 0.25;
1993 }
1994 }
1995 }
1996
1997 // --- Y‐faces: loops k=zs..ze-1, j=ys..ye (ym+1 faces), i=xs..xe-1 ---
1998 for (PetscInt k = zs; k < ze; ++k) {
1999 PetscInt k_loc = k - zs;
2000 for (PetscInt j = ys; j <= ye; ++j) {
2001 PetscInt j_loc = j - ys; // 0..ym
2002 for (PetscInt i = xs; i < xe; ++i) {
2003 PetscInt i_loc = i - xs;
2004 // Average the four corners of the X-Z face at Y = j
2005 PetscReal sum = corner_arr[k ][j][i ]
2006 + corner_arr[k+1][j][i ]
2007 + corner_arr[k ][j][i+1]
2008 + corner_arr[k+1][j][i+1];
2009 faceY_arr[k_loc][j_loc][i_loc] = sum * 0.25;
2010 }
2011 }
2012 }
2013
2014 // --- Z‐faces: loops k=zs..ze (zm+1), j=ys..ye-1, i=xs..xe-1 ---
2015 for (PetscInt k = zs; k <= ze; ++k) {
2016 PetscInt k_loc = k - zs;
2017 for (PetscInt j = ys; j < ye; ++j) {
2018 PetscInt j_loc = j - ys;
2019 for (PetscInt i = xs; i < xe; ++i) {
2020 PetscInt i_loc = i - xs;
2021 // Average the four corners of the X-Y face at Z = k
2022 PetscReal sum = corner_arr[k][j ][i ]
2023 + corner_arr[k][j ][i+1]
2024 + corner_arr[k][j+1][i ]
2025 + corner_arr[k][j+1][i+1];
2026 faceZ_arr[k_loc][j_loc][i_loc] = sum * 0.25;
2027 }
2028 }
2029 }
2030
2032
2033 PetscFunctionReturn(0);
2034}
2035
2036#undef __FUNCT__
2037#define __FUNCT__ "InterpolateCornerToFaceCenter_Vector"
2038
2039/**
2040 * @brief Internal helper implementation: `InterpolateCornerToFaceCenter_Vector()`.
2041 * @details Local to this translation unit.
2042 */
2044 Cmpnts ***corner_arr,
2045 Cmpnts ***faceX_arr,
2046 Cmpnts ***faceY_arr,
2047 Cmpnts ***faceZ_arr,
2048 UserCtx *user)
2049{
2050 PetscErrorCode ierr;
2051 DMDALocalInfo info;
2052 PetscMPIInt rank;
2053
2054 PetscFunctionBeginUser;
2055
2057
2058 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);
2060 "Rank %d starting InterpolateFieldFromCornerToFaceCenter_Vector.\n", rank);
2061
2062 ierr = DMDAGetLocalInfo(user->fda, &info); CHKERRQ(ierr);
2063
2064 PetscInt xs, xm, ys, ym, zs, zm;
2065 ierr = GetOwnedCellRange(&info, 0, &xs, &xm); CHKERRQ(ierr);
2066 ierr = GetOwnedCellRange(&info, 1, &ys, &ym); CHKERRQ(ierr);
2067 ierr = GetOwnedCellRange(&info, 2, &zs, &zm); CHKERRQ(ierr);
2068
2069 PetscInt xe = xs + xm;
2070 PetscInt ye = ys + ym;
2071 PetscInt ze = zs + zm;
2072
2073 // X-faces
2074 for (PetscInt k = zs; k < ze; ++k) {
2075 PetscInt k_loc = k - zs;
2076 for (PetscInt j = ys; j < ye; ++j) {
2077 PetscInt j_loc = j - ys;
2078 for (PetscInt i = xs; i <= xe; ++i) {
2079 PetscInt i_loc = i - xs;
2080 Cmpnts sum = {0,0,0};
2081 sum.x = corner_arr[k ][j ][i].x + corner_arr[k+1][j ][i].x
2082 + corner_arr[k ][j+1][i].x + corner_arr[k+1][j+1][i].x;
2083 sum.y = corner_arr[k ][j ][i].y + corner_arr[k+1][j ][i].y
2084 + corner_arr[k ][j+1][i].y + corner_arr[k+1][j+1][i].y;
2085 sum.z = corner_arr[k ][j ][i].z + corner_arr[k+1][j ][i].z
2086 + corner_arr[k ][j+1][i].z + corner_arr[k+1][j+1][i].z;
2087 faceX_arr[k_loc][j_loc][i_loc].x = sum.x * 0.25;
2088 faceX_arr[k_loc][j_loc][i_loc].y = sum.y * 0.25;
2089 faceX_arr[k_loc][j_loc][i_loc].z = sum.z * 0.25;
2090 }
2091 }
2092 }
2093
2095 "Rank %d x-face Interpolation complete.\n", rank);
2096
2097 // Y-faces
2098 for (PetscInt k = zs; k < ze; ++k) {
2099 PetscInt k_loc = k - zs;
2100 for (PetscInt j = ys; j <= ye; ++j) {
2101 PetscInt j_loc = j - ys;
2102 for (PetscInt i = xs; i < xe; ++i) {
2103 PetscInt i_loc = i - xs;
2104 Cmpnts sum = {0,0,0};
2105 sum.x = corner_arr[k ][j][i ].x + corner_arr[k+1][j][i ].x
2106 + corner_arr[k ][j][i+1].x + corner_arr[k+1][j][i+1].x;
2107 sum.y = corner_arr[k ][j][i ].y + corner_arr[k+1][j][i ].y
2108 + corner_arr[k ][j][i+1].y + corner_arr[k+1][j][i+1].y;
2109 sum.z = corner_arr[k ][j][i ].z + corner_arr[k+1][j][i ].z
2110 + corner_arr[k ][j][i+1].z + corner_arr[k+1][j][i+1].z;
2111 faceY_arr[k_loc][j_loc][i_loc].x = sum.x * 0.25;
2112 faceY_arr[k_loc][j_loc][i_loc].y = sum.y * 0.25;
2113 faceY_arr[k_loc][j_loc][i_loc].z = sum.z * 0.25;
2114 }
2115 }
2116 }
2117
2119 "Rank %d y-face Interpolation complete.\n", rank);
2120
2121 // Z-faces
2122 for (PetscInt k = zs; k <= ze; ++k) {
2123 PetscInt k_loc = k - zs;
2124 for (PetscInt j = ys; j < ye; ++j) {
2125 PetscInt j_loc = j - ys;
2126 for (PetscInt i = xs; i < xe; ++i) {
2127 PetscInt i_loc = i - xs;
2128 Cmpnts sum = {0,0,0};
2129 sum.x = corner_arr[k][j ][i ].x + corner_arr[k][j ][i+1].x
2130 + corner_arr[k][j+1][i ].x + corner_arr[k][j+1][i+1].x;
2131 sum.y = corner_arr[k][j ][i ].y + corner_arr[k][j ][i+1].y
2132 + corner_arr[k][j+1][i ].y + corner_arr[k][j+1][i+1].y;
2133 sum.z = corner_arr[k][j ][i ].z + corner_arr[k][j ][i+1].z
2134 + corner_arr[k][j+1][i ].z + corner_arr[k][j+1][i+1].z;
2135 faceZ_arr[k_loc][j_loc][i_loc].x = sum.x * 0.25;
2136 faceZ_arr[k_loc][j_loc][i_loc].y = sum.y * 0.25;
2137 faceZ_arr[k_loc][j_loc][i_loc].z = sum.z * 0.25;
2138 }
2139 }
2140 }
2141
2143 "Rank %d z-face Interpolation complete.\n", rank);
2144
2146 PetscFunctionReturn(0);
2147}
PetscErrorCode CalculateParticleCountPerCell(UserCtx *user)
Counts particles in each cell of the DMDA 'da' and stores the result in user->ParticleCount.
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.
FieldLayout layout
const FieldDescriptor * descriptor
PetscErrorCode FieldGetView(UserCtx *user, FieldId field_id, FieldView *view)
Resolve the existing DM and global/local vectors for one field.
@ FIELD_LAYOUT_CELL_CENTERED
const char * canonical_name
const char * FieldLayoutName(FieldLayout layout)
Return a stable printable label for a field layout.
PetscErrorCode FieldGetDescriptor(FieldId field_id, const FieldDescriptor **descriptor)
Return immutable metadata for a valid field identifier.
FieldId
Compile-time identity for a catalogued Eulerian field.
@ FIELD_ID_CELL_SCALAR_AT_CORNER
@ FIELD_ID_UCAT
@ FIELD_ID_CELL_VECTOR_AT_CORNER
@ FIELD_ID_DIFFUSIVITY_GRADIENT
@ FIELD_ID_INVALID
@ FIELD_ID_DIFFUSIVITY
Immutable metadata for one field identity.
Non-owning runtime objects resolved for one field and UserCtx.
PetscErrorCode AccumulateParticleField(DM swarm, ParticleFieldId particle_field_id, DM gridSumDM, Vec gridSumVec)
Accumulates a particle field (scalar or vector) into a target grid sum vector.
PetscErrorCode NormalizeGridVectorByCount(DM countDM, Vec countVec, DM dataDM, Vec sumVec, Vec avgVec)
Normalizes a grid vector of sums by a grid vector of counts to produce an average.
PetscErrorCode ScatterParticleFieldToEulerField(UserCtx *user, ParticleFieldId particle_field_id, Vec eulerFieldAverageVec)
Scatters a particle field (scalar or vector) to the corresponding Eulerian field average.
PetscErrorCode ScatterAllParticleFieldsToEulerFields(UserCtx *user)
Scatters a predefined set of particle fields to their corresponding Eulerian fields.
static PetscErrorCode ScatterParticleFieldToEulerField_Internal(UserCtx *user, ParticleFieldId particle_field_id, DM targetDM, PetscInt expected_dof, Vec eulerFieldAverageVec)
Accumulate one particle field onto the Eulerian grid using the selected scatter stencil.
PetscErrorCode InterpolateFieldFromCornerToCenter_Vector(Cmpnts ***field_arr, Cmpnts ***centfield_arr, UserCtx *user)
Internal helper implementation: InterpolateFieldFromCornerToCenter_Vector().
PetscErrorCode InterpolateCornerToFaceCenter_Vector(Cmpnts ***corner_arr, Cmpnts ***faceX_arr, Cmpnts ***faceY_arr, Cmpnts ***faceZ_arr, UserCtx *user)
Internal helper implementation: InterpolateCornerToFaceCenter_Vector().
PetscErrorCode InterpolateFieldFromCornerToCenter_Scalar(PetscReal ***field_arr, PetscReal ***centfield_arr, UserCtx *user)
Internal helper implementation: InterpolateFieldFromCornerToCenter_Scalar().
PetscErrorCode InterpolateAllFieldsToSwarm(UserCtx *user)
Internal helper implementation: InterpolateAllFieldsToSwarm().
static PetscErrorCode InterpolateEulerFieldToSwarmForParticle(const char *fieldName, void *fieldPtr, Particle *particle, void *swarmOut, PetscInt p, PetscInt blockSize)
Interpolate one Eulerian field to a single located swarm particle.
PetscErrorCode TrilinearInterpolation_Vector(const char *fieldName, Cmpnts ***fieldVec, PetscInt i, PetscInt j, PetscInt k, PetscReal a1, PetscReal a2, PetscReal a3, Cmpnts *vec)
Internal helper implementation: TrilinearInterpolation_Vector().
PetscErrorCode TrilinearInterpolation_Scalar(const char *fieldName, PetscReal ***fieldScal, PetscInt i, PetscInt j, PetscInt k, PetscReal a1, PetscReal a2, PetscReal a3, PetscReal *val)
Internal helper implementation: TrilinearInterpolation_Scalar().
PetscErrorCode PieceWiseLinearInterpolation_Scalar(const char *fieldName, PetscReal ***fieldScal, PetscInt iCell, PetscInt jCell, PetscInt kCell, PetscReal *val)
Internal helper implementation: PieceWiseLinearInterpolation_Scalar().
PetscErrorCode InterpolateEulerFieldToSwarm(UserCtx *user, FieldId source_field_id, ParticleFieldId target_field_id)
Dispatches grid-to-particle interpolation to the method selected in the control file.
static void ComputeTrilinearWeightsUnclamped(PetscReal a1, PetscReal a2, PetscReal a3, PetscReal *w)
Unclamped trilinear weights for boundary extrapolation.
#define ERROR_MSG_BUFFER_SIZE
PetscErrorCode TestCornerToCenterInterpolation(UserCtx *user)
Internal helper implementation: TestCornerToCenterInterpolation().
static PetscErrorCode InterpolateEulerFieldFromCornerToSwarm(UserCtx *user, Vec fieldLocal_cellCentered, const char *fieldName, const char *swarmOutFieldName)
Corner-averaged interpolation path (legacy).
PetscErrorCode InterpolateFieldFromCenterToCorner_Vector(Cmpnts ***centfield_arr, Cmpnts ***corner_arr, UserCtx *user)
Internal helper implementation: InterpolateFieldFromCenterToCorner_Vector().
static void ComputeTrilinearWeights(PetscReal a1, PetscReal a2, PetscReal a3, PetscReal *w)
Compute the eight trilinear interpolation weights for a particle's local coordinates.
static PetscErrorCode InterpolateEulerFieldFromCenterToSwarm(UserCtx *user, Vec fieldLocal_cellCentered, const char *fieldName, const char *swarmOutFieldName)
Direct cell-center trilinear interpolation (second-order on curvilinear grids).
PetscErrorCode InterpolateCornerToFaceCenter_Scalar(PetscReal ***corner_arr, PetscReal ***faceX_arr, PetscReal ***faceY_arr, PetscReal ***faceZ_arr, UserCtx *user)
Internal helper implementation: InterpolateCornerToFaceCenter_Scalar().
PetscErrorCode PieceWiseLinearInterpolation_Vector(const char *fieldName, Cmpnts ***fieldVec, PetscInt iCell, PetscInt jCell, PetscInt kCell, Cmpnts *vec)
Internal helper implementation: PieceWiseLinearInterpolation_Vector().
PetscErrorCode InterpolateFieldFromCenterToCorner_Scalar(PetscReal ***centfield_arr, PetscReal ***corner_arr, UserCtx *user)
Internal helper implementation: InterpolateFieldFromCenterToCorner_Scalar().
#define TrilinearInterpolation(fieldName, fieldPtr, i, j, k, a1, a2, a3, outPtr)
Macro that calls either the scalar or vector trilinear interpolation function based on the type of th...
#define InterpolateFieldFromCenterToCorner(blockSize, centfield_ptr, corner_ptr, user_ctx)
Macro to dispatch to the correct scalar or vector center-to-corner function based on a runtime block ...
#define InterpolateFieldFromCornerToCenter(field, centfield, user)
Generic macro to call the appropriate interpolation function based on the field type.
PetscBool is_function_allowed(const char *functionName)
Checks if a given function is in the allow-list.
Definition logging.c:186
#define LOG_ALLOW_SYNC(scope, level, fmt,...)
Synchronized logging macro that checks both the log level and whether the calling function is in the ...
Definition logging.h:253
#define LOCAL
Logging scope definitions for controlling message output.
Definition logging.h:45
#define GLOBAL
Scope for global logging across all processes.
Definition logging.h:46
#define LOG_ALLOW(scope, level, fmt,...)
Logging macro that checks both the log level and whether the calling function is in the allowed-funct...
Definition logging.h:200
#define PROFILE_FUNCTION_END
Marks the end of a profiled code block.
Definition logging.h:859
LogLevel get_log_level()
Retrieves the current logging level from the environment variable LOG_LEVEL.
Definition logging.c:87
#define LOG_LOOP_ALLOW_EXACT(scope, level, var, val, fmt,...)
Logs a custom message if a variable equals a specific value.
Definition logging.h:335
PetscErrorCode LOG_CORNER_FIELD_ANATOMY(UserCtx *user, FieldId corner_field_id, const char *stage_name)
Logs the node-layout anatomy of the transient center-to-corner interpolation field.
Definition logging.c:2838
@ LOG_TRACE
Very fine-grained tracing information for in-depth debugging.
Definition logging.h:33
@ LOG_INFO
Informational messages about program execution.
Definition logging.h:31
@ LOG_WARNING
Non-critical issues that warrant attention.
Definition logging.h:30
@ LOG_DEBUG
Detailed debugging information.
Definition logging.h:32
@ LOG_VERBOSE
Extremely detailed logs, typically for development use only.
Definition logging.h:34
#define PROFILE_FUNCTION_BEGIN
Marks the beginning of a profiled code block (typically a function).
Definition logging.h:850
const char * ParticleFieldName(ParticleFieldId field_id)
Return the canonical PETSc DMSwarm name for an ID.
ParticleFieldId
Compile-time identity for a persistent solver-particle field.
@ 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
@ PARTICLE_FIELD_CAPABILITY_EULERIAN_SCATTER
PetscErrorCode ParticleFieldGetDescriptor(ParticleFieldId field_id, const ParticleFieldDescriptor **descriptor)
Return immutable metadata for a valid particle field ID.
Immutable metadata for one persistent particle field.
PetscErrorCode GetOwnedCellRange(const DMDALocalInfo *info_nodes, PetscInt dim, PetscInt *xs_cell_global_out, PetscInt *xm_cell_local_out)
Determines the global starting index and number of CELLS owned by the current processor in a specifie...
Definition setup.c:2285
PetscErrorCode UpdateLocalGhosts(UserCtx *user, FieldId field_id)
Updates the local vector (including ghost points) from its corresponding global vector.
Definition setup.c:1838
Vec lCent
Definition variables.h:974
@ PERIODIC
Definition variables.h:292
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:931
PetscInt cell[3]
Definition variables.h:184
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:909
PetscScalar x
Definition variables.h:103
Cmpnts loc
Definition variables.h:185
InterpolationMethod interpolationMethod
Definition variables.h:832
PetscScalar z
Definition variables.h:103
@ INTERP_TRILINEAR
Definition variables.h:565
Vec ParticleCount
Definition variables.h:996
PetscScalar y
Definition variables.h:103
Cmpnts weights
Definition variables.h:187
@ TOP
Definition variables.h:147
@ FRONT
Definition variables.h:147
@ BOTTOM
Definition variables.h:147
@ BACK
Definition variables.h:147
@ LEFT
Definition variables.h:147
@ NUM_FACES
Definition variables.h:147
@ RIGHT
Definition variables.h:147
Vec Cent
Definition variables.h:974
BCType mathematical_type
Definition variables.h:368
PetscInt64 PID
Definition variables.h:183
Cmpnts vertices[8]
Coordinates of the eight vertices of the cell.
Definition variables.h:178
Vec Psi
Definition variables.h:997
@ BC_FACE_NEG_X
Definition variables.h:262
@ BC_FACE_NEG_Z
Definition variables.h:264
@ BC_FACE_NEG_Y
Definition variables.h:263
Defines the vertices of a single hexahedral grid cell.
Definition variables.h:177
A 3D point or vector with PetscScalar components.
Definition variables.h:102
Defines a particle's core properties for Lagrangian tracking.
Definition variables.h:182
User-defined context containing data specific to a single computational grid level.
Definition variables.h:906
PetscErrorCode CalculateDistancesToCellFaces(const Cmpnts p, const Cell *cell, PetscReal *d, const PetscReal threshold)
Computes the signed distances from a point to each face of a cubic cell.