PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
test_mpi_kernels.c
Go to the documentation of this file.
1/**
2 * @file test_mpi_kernels.c
3 * @brief C unit tests for MPI collective helper routines.
4 */
5
6#include "test_support.h"
7
8#include "ParticleMotion.h"
9#include "ParticleSwarm.h"
10#include "Boundaries.h"
11#include "grid.h"
12#include "statistics_target.h"
14#include "statistics_window.h"
15#include "io.h"
16#include "field_catalog.h"
17#include "les.h"
18
19#include <stdlib.h>
20
21/**
22 * @brief Marks both x boundary faces as geometric periodic boundaries.
23 */
33
34/**
35 * @brief Returns a deterministic scalar value for periodic-transfer assertions.
36 */
37static PetscReal PeriodicScalarValue(PetscInt i, PetscInt j, PetscInt k, PetscReal offset)
38{
39 return offset + (PetscReal)(i + 10 * j + 100 * k);
40}
41/**
42 * @brief Tests collective particle distribution consistency across MPI ranks.
43 */
44
46{
47 PetscMPIInt rank = 0, size = 1;
48 PetscInt local_particles = 0;
49 PetscInt remainder = 0;
50 PetscInt global_particles = 0;
51 PetscInt remainder_min = 0, remainder_max = 0;
52 const PetscInt total_particles = 137;
53
54 PetscFunctionBeginUser;
55 PetscCallMPI(MPI_Comm_rank(PETSC_COMM_WORLD, &rank));
56 PetscCallMPI(MPI_Comm_size(PETSC_COMM_WORLD, &size));
57 PetscCall(PicurvAssertBool((PetscBool)(size >= 2), "unit-mpi requires at least two MPI ranks"));
58
59 PetscCall(DistributeParticles(total_particles, rank, size, &local_particles, &remainder));
60
61 PetscCall(PicurvAssertIntEqual(
62 total_particles / size + (((PetscInt)rank < remainder) ? 1 : 0),
63 local_particles,
64 "local particle share should match quotient+remainder policy"));
65
66 PetscCallMPI(MPI_Allreduce(&local_particles, &global_particles, 1, MPIU_INT, MPI_SUM, PETSC_COMM_WORLD));
67 PetscCall(PicurvAssertIntEqual(total_particles, global_particles, "distributed particle count must conserve total particles"));
68
69 PetscCallMPI(MPI_Allreduce(&remainder, &remainder_min, 1, MPIU_INT, MPI_MIN, PETSC_COMM_WORLD));
70 PetscCallMPI(MPI_Allreduce(&remainder, &remainder_max, 1, MPIU_INT, MPI_MAX, PETSC_COMM_WORLD));
71 PetscCall(PicurvAssertIntEqual(remainder_min, remainder_max, "all ranks should report the same remainder"));
72 PetscFunctionReturn(0);
73}
74/**
75 * @brief Tests multi-rank bounding-box gather and broadcast helpers.
76 */
77
78static PetscErrorCode TestBoundingBoxCollectivesMultiRank(void)
79{
80 SimCtx *simCtx = NULL;
81 UserCtx *user = NULL;
82 BoundingBox local_bbox;
83 BoundingBox *boxes = NULL;
84 PetscMPIInt rank = 0, size = 1;
85 PetscReal global_min_x = 0.0;
86 PetscReal global_max_x = 0.0;
87 PetscReal expected_global_max_x = 0.0;
88
89 PetscFunctionBeginUser;
90 PetscCallMPI(MPI_Comm_rank(PETSC_COMM_WORLD, &rank));
91 PetscCallMPI(MPI_Comm_size(PETSC_COMM_WORLD, &size));
92 PetscCall(PicurvAssertBool((PetscBool)(size >= 2), "unit-mpi requires at least two MPI ranks"));
93
94 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 8, 6, 4));
95 expected_global_max_x = ((PetscReal)(user->IM - 1) / (PetscReal)user->IM) + 1.0e-6;
96 PetscCall(ComputeLocalBoundingBox(user, &local_bbox));
97 PetscCall(GatherAllBoundingBoxes(user, &boxes));
98 PetscCall(BroadcastAllBoundingBoxes(user, &boxes));
99 PetscCall(PicurvAssertBool((PetscBool)(boxes != NULL), "all ranks should hold the gathered bounding-box table"));
100
101 for (PetscMPIInt r = 0; r < size; ++r) {
102 PetscCall(PicurvAssertBool((PetscBool)(boxes[r].min_coords.x <= boxes[r].max_coords.x), "bbox x-range should be valid"));
103 PetscCall(PicurvAssertBool((PetscBool)(boxes[r].min_coords.y <= boxes[r].max_coords.y), "bbox y-range should be valid"));
104 PetscCall(PicurvAssertBool((PetscBool)(boxes[r].min_coords.z <= boxes[r].max_coords.z), "bbox z-range should be valid"));
105 }
106
107 PetscCall(PicurvAssertRealNear(local_bbox.min_coords.x, boxes[rank].min_coords.x, 1.0e-10, "local bbox min x preserved"));
108 PetscCall(PicurvAssertRealNear(local_bbox.max_coords.x, boxes[rank].max_coords.x, 1.0e-10, "local bbox max x preserved"));
109
110 PetscCallMPI(MPI_Allreduce(&local_bbox.min_coords.x, &global_min_x, 1, MPIU_REAL, MPI_MIN, PETSC_COMM_WORLD));
111 PetscCallMPI(MPI_Allreduce(&local_bbox.max_coords.x, &global_max_x, 1, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD));
112 PetscCall(PicurvAssertBool((PetscBool)(global_min_x <= 0.0), "global min x should include domain start"));
113 PetscCall(PicurvAssertRealNear(expected_global_max_x, global_max_x, 1.0e-10, "global max x should match the normalized physical-node domain end"));
114
115 free(boxes);
116 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
117 PetscFunctionReturn(0);
118}
119/**
120 * @brief Tests distributed synchronization of persistent cell-centered scalar fields.
121 */
122
124{
125 SimCtx *simCtx = NULL;
126 UserCtx *user = NULL;
127 PetscReal ***p = NULL;
128 PetscReal ***cs = NULL;
129 PetscReal ***diffusivity = NULL;
130 Cmpnts ***ucat = NULL;
132 PetscInt xs, xe, ys, ye, zs, ze, mx;
133
134 PetscFunctionBeginUser;
135 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(&simCtx, &user, 8, 4, 4, PETSC_TRUE, PETSC_FALSE, PETSC_FALSE));
136 MarkXPeriodic(user);
137 PetscCall(PicurvAssertBool((PetscBool)(user->info.xm < user->info.mx),
138 "periodic cell synchronization test requires the x axis to be partitioned"));
139 PetscCall(DMCreateGlobalVector(user->da, &user->CS));
140 PetscCall(DMCreateLocalVector(user->da, &user->lCs));
141
142 xs = user->info.xs;
143 xe = xs + user->info.xm;
144 ys = user->info.ys;
145 ye = ys + user->info.ym;
146 zs = user->info.zs;
147 ze = zs + user->info.zm;
148 mx = user->info.mx;
149
150 PetscCall(DMDAVecGetArray(user->da, user->P, &p));
151 PetscCall(DMDAVecGetArray(user->da, user->CS, &cs));
152 PetscCall(DMDAVecGetArray(user->da, user->Diffusivity, &diffusivity));
153 PetscCall(DMDAVecGetArray(user->fda, user->Ucat, &ucat));
154 for (PetscInt k = zs; k < ze; ++k) {
155 for (PetscInt j = ys; j < ye; ++j) {
156 for (PetscInt i = xs; i < xe; ++i) {
157 p[k][j][i] = PeriodicScalarValue(i, j, k, 0.0);
158 cs[k][j][i] = PeriodicScalarValue(i, j, k, 1000.0);
159 diffusivity[k][j][i] = PeriodicScalarValue(i, j, k, 2000.0);
160 ucat[k][j][i] = (Cmpnts){PeriodicScalarValue(i,j,k,3000.0),0.0,0.0};
161 }
162 }
163 }
164 PetscCall(DMDAVecRestoreArray(user->da, user->P, &p));
165 PetscCall(DMDAVecRestoreArray(user->da, user->CS, &cs));
166 PetscCall(DMDAVecRestoreArray(user->da, user->Diffusivity, &diffusivity));
167 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucat, &ucat));
168
169 PetscCall(SynchronizePeriodicCellFields(user, 4, fields));
170
171 PetscCall(DMDAVecGetArrayRead(user->da, user->P, &p));
172 PetscCall(DMDAVecGetArrayRead(user->da, user->CS, &cs));
173 PetscCall(DMDAVecGetArrayRead(user->da, user->Diffusivity, &diffusivity));
174 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucat, &ucat));
175 if (xs == 0) {
176 for (PetscInt k = zs; k < ze; ++k) {
177 for (PetscInt j = ys; j < ye; ++j) {
178 PetscCall(PicurvAssertRealNear(PeriodicScalarValue(mx - 2, j, k, 0.0), p[k][j][0], 1.0e-12,
179 "distributed P negative endpoint should copy the opposite interior"));
180 PetscCall(PicurvAssertRealNear(PeriodicScalarValue(mx - 2, j, k, 1000.0), cs[k][j][0], 1.0e-12,
181 "distributed CS negative endpoint should copy the opposite interior"));
182 PetscCall(PicurvAssertRealNear(PeriodicScalarValue(mx - 2, j, k, 2000.0), diffusivity[k][j][0], 1.0e-12,
183 "distributed Diffusivity negative endpoint should copy the opposite interior"));
184 PetscCall(PicurvAssertRealNear(PeriodicScalarValue(mx-2,j,k,3000.0),ucat[k][j][0].x,1e-12,
185 "distributed Ucat negative endpoint should copy the opposite interior"));
186 }
187 }
188 }
189 if (xe == mx) {
190 for (PetscInt k = zs; k < ze; ++k) {
191 for (PetscInt j = ys; j < ye; ++j) {
192 PetscCall(PicurvAssertRealNear(PeriodicScalarValue(1, j, k, 0.0), p[k][j][mx - 1], 1.0e-12,
193 "distributed P positive endpoint should copy the opposite interior"));
194 PetscCall(PicurvAssertRealNear(PeriodicScalarValue(1, j, k, 1000.0), cs[k][j][mx - 1], 1.0e-12,
195 "distributed CS positive endpoint should copy the opposite interior"));
196 PetscCall(PicurvAssertRealNear(PeriodicScalarValue(1, j, k, 2000.0), diffusivity[k][j][mx - 1], 1.0e-12,
197 "distributed Diffusivity positive endpoint should copy the opposite interior"));
198 PetscCall(PicurvAssertRealNear(PeriodicScalarValue(1,j,k,3000.0),ucat[k][j][mx-1].x,1e-12,
199 "distributed Ucat positive endpoint should copy the leading interior"));
200 }
201 }
202 }
203 PetscCall(DMDAVecRestoreArrayRead(user->da, user->P, &p));
204 PetscCall(DMDAVecRestoreArrayRead(user->da, user->CS, &cs));
205 PetscCall(DMDAVecRestoreArrayRead(user->da, user->Diffusivity, &diffusivity));
206 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucat, &ucat));
207 PetscCall(DMDAVecGetArrayRead(user->fda,user->lUcat,&ucat));
208 if(xs==0) PetscCall(PicurvAssertRealNear(PeriodicScalarValue(1,2,2,3000.0),ucat[2][2][-1].x,1e-12,
209 "distributed local Ucat negative ghost follows corrected global endpoint"));
210 if(xe==mx) PetscCall(PicurvAssertRealNear(PeriodicScalarValue(mx-2,2,2,3000.0),ucat[2][2][mx].x,1e-12,
211 "distributed local Ucat positive ghost follows corrected global endpoint"));
212 PetscCall(DMDAVecRestoreArrayRead(user->fda,user->lUcat,&ucat));
213
214 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
215 PetscFunctionReturn(0);
216}
217/**
218 * @brief Tests periodic seam translation discovery when seam nodes are on different ranks.
219 */
221{
222 SimCtx *simCtx = NULL;
223 UserCtx *user = NULL;
224 PetscReal expected_translation = 0.0;
225
226 PetscFunctionBeginUser;
227 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(&simCtx, &user, 8, 4, 4, PETSC_TRUE, PETSC_FALSE, PETSC_FALSE));
228 MarkXPeriodic(user);
229 PetscCall(PicurvAssertBool((PetscBool)(user->info.xm < user->info.mx),
230 "periodic geometry validation test requires the x axis to be partitioned"));
231
232 expected_translation = (PetscReal)(user->info.mx - 2) / (PetscReal)user->info.mx;
233 PetscCall(ValidatePeriodicGeometry(user));
234
236 "distributed X-periodic translation should be marked valid"));
237 PetscCall(PicurvAssertRealNear(expected_translation, user->periodic_translation[0].x, 1.0e-12,
238 "distributed X-periodic translation"));
239 PetscCall(PicurvAssertRealNear(0.0, user->periodic_translation[0].y, 1.0e-12,
240 "distributed X-periodic translation y component"));
241 PetscCall(PicurvAssertRealNear(0.0, user->periodic_translation[0].z, 1.0e-12,
242 "distributed X-periodic translation z component"));
243
244 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
245 PetscFunctionReturn(0);
246}
247
248/**
249 * @brief Tests translated face-center ghosts when the periodic axis is partitioned.
250 */
252{
253 SimCtx *simCtx = NULL;
254 UserCtx *user = NULL;
255 Cmpnts ***centx = NULL;
256 const Cmpnts ***lcentx = NULL;
257 const FieldId fields[] = {FIELD_ID_CENTX};
258 PetscReal translation, spacing;
259 PetscInt xs, xe, mx;
260
261 PetscFunctionBeginUser;
262 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(&simCtx, &user, 8, 4, 4, PETSC_TRUE, PETSC_FALSE, PETSC_FALSE));
263 MarkXPeriodic(user);
264 PetscCall(ValidatePeriodicGeometry(user));
265 PetscCall(PicurvAssertBool((PetscBool)(user->info.xm < user->info.mx),
266 "face-center coordinate test requires the x axis to be partitioned"));
267 translation = user->periodic_translation[0].x;
268 spacing = translation / (PetscReal)(user->info.mx - 2);
269 xs = user->info.xs;
270 xe = xs + user->info.xm;
271 mx = user->info.mx;
272
273 PetscCall(DMDAVecGetArray(user->fda, user->Centx, &centx));
274 for (PetscInt k = user->info.zs; k < user->info.zs + user->info.zm; k++) {
275 for (PetscInt j = user->info.ys; j < user->info.ys + user->info.ym; j++) {
276 for (PetscInt i = xs; i < xe; i++) {
277 centx[k][j][i] = (Cmpnts){spacing * i, 2.0 + j, 3.0 + k};
278 }
279 }
280 }
281 PetscCall(DMDAVecRestoreArray(user->fda, user->Centx, &centx));
282
283 PetscCall(SynchronizePeriodicFaceFields(user, 'i', 1, fields));
284 PetscCall(DMDAVecGetArrayRead(user->fda, user->lCentx, &lcentx));
285 if (xs == 0) {
286 PetscCall(PicurvAssertRealNear(-spacing, lcentx[2][2][-1].x, 1.0e-12,
287 "distributed translated Centx negative adjacent ghost"));
288 PetscCall(PicurvAssertRealNear(0.0, lcentx[2][2][0].x, 1.0e-12,
289 "distributed translated Centx negative endpoint"));
290 }
291 if (xe == mx) {
292 PetscCall(PicurvAssertRealNear(translation + spacing, lcentx[2][2][mx - 1].x, 1.0e-12,
293 "distributed translated Centx positive endpoint"));
294 PetscCall(PicurvAssertRealNear(translation + 2.0 * spacing, lcentx[2][2][mx].x, 1.0e-12,
295 "distributed translated Centx positive adjacent ghost"));
296 }
297 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lCentx, &lcentx));
298
299 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
300 PetscFunctionReturn(0);
301}
302/**
303 * @brief Tests persistent I-face synchronization across an MPI-partitioned seam.
304 */
306{
307 SimCtx *simCtx = NULL;
308 UserCtx *user = NULL;
309 PetscReal ***iaj = NULL;
310 const FieldId fields[] = {FIELD_ID_IAJ};
311 PetscInt xs, xe, ys, ye, zs, ze, mx;
312
313 PetscFunctionBeginUser;
314 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(&simCtx, &user, 8, 4, 4, PETSC_TRUE, PETSC_FALSE, PETSC_FALSE));
315 MarkXPeriodic(user);
316 PetscCall(PicurvAssertBool((PetscBool)(user->info.xm < user->info.mx),
317 "periodic face synchronization test requires the x axis to be partitioned"));
318
319 xs = user->info.xs; xe = xs + user->info.xm;
320 ys = user->info.ys; ye = ys + user->info.ym;
321 zs = user->info.zs; ze = zs + user->info.zm;
322 mx = user->info.mx;
323
324 PetscCall(DMDAVecGetArray(user->da, user->IAj, &iaj));
325 for (PetscInt k = zs; k < ze; k++) for (PetscInt j = ys; j < ye; j++) for (PetscInt i = xs; i < xe; i++) {
326 iaj[k][j][i] = PeriodicScalarValue(i, j, k, 3000.0);
327 }
328 PetscCall(DMDAVecRestoreArray(user->da, user->IAj, &iaj));
329
330 PetscCall(SynchronizePeriodicFaceFields(user, 'i', 1, fields));
331
332 PetscCall(DMDAVecGetArrayRead(user->da, user->IAj, &iaj));
333 if (xs == 0) {
334 for (PetscInt k = zs; k < ze; k++) for (PetscInt j = ys; j < ye; j++) {
335 PetscCall(PicurvAssertRealNear(PeriodicScalarValue(mx - 2, j, k, 3000.0), iaj[k][j][0], 1.0e-12,
336 "distributed I-face negative seam should copy the opposite physical seam"));
337 }
338 }
339 if (xe == mx) {
340 for (PetscInt k = zs; k < ze; k++) for (PetscInt j = ys; j < ye; j++) {
341 PetscCall(PicurvAssertRealNear(PeriodicScalarValue(1, j, k, 3000.0), iaj[k][j][mx - 1], 1.0e-12,
342 "distributed I-face positive dummy should copy the leading physical face"));
343 }
344 }
345 PetscCall(DMDAVecRestoreArrayRead(user->da, user->IAj, &iaj));
346
347 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
348 PetscFunctionReturn(0);
349}
350/**
351 * @brief Tests persistent Ucont synchronization across an MPI-partitioned seam.
352 */
354{
355 SimCtx *simCtx = NULL;
356 UserCtx *user = NULL;
357 Cmpnts ***ucont = NULL;
358 const FieldId fields[] = {FIELD_ID_UCONT};
359 PetscInt xs, xe, ys, ye, zs, ze, mx;
360
361 PetscFunctionBeginUser;
362 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(&simCtx, &user, 8, 4, 4, PETSC_TRUE, PETSC_FALSE, PETSC_FALSE));
363 MarkXPeriodic(user);
364 PetscCall(PicurvAssertBool((PetscBool)(user->info.xm < user->info.mx),
365 "periodic staggered synchronization test requires the x axis to be partitioned"));
366
367 xs = user->info.xs; xe = xs + user->info.xm;
368 ys = user->info.ys; ye = ys + user->info.ym;
369 zs = user->info.zs; ze = zs + user->info.zm;
370 mx = user->info.mx;
371
372 PetscCall(DMDAVecGetArray(user->fda, user->Ucont, &ucont));
373 for (PetscInt k = zs; k < ze; k++) for (PetscInt j = ys; j < ye; j++) for (PetscInt i = xs; i < xe; i++) {
374 PetscReal value = PeriodicScalarValue(i, j, k, 0.0);
375 ucont[k][j][i] = (Cmpnts){value + 1000.0, value + 2000.0, value + 3000.0};
376 }
377 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucont, &ucont));
378
379 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fields));
380
381 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucont, &ucont));
382 if (xs == 0) {
383 for (PetscInt k = zs; k < ze; k++) for (PetscInt j = ys; j < ye; j++) {
384 PetscReal expected = PeriodicScalarValue(mx - 2, j, k, 0.0);
385 PetscCall(PicurvAssertRealNear(expected + 1000.0, ucont[k][j][0].x, 1.0e-12,
386 "distributed Ucont.x negative seam"));
387 PetscCall(PicurvAssertRealNear(expected + 2000.0, ucont[k][j][0].y, 1.0e-12,
388 "distributed Ucont.y negative X endpoint"));
389 }
390 }
391 if (xe == mx) {
392 for (PetscInt k = zs; k < ze; k++) for (PetscInt j = ys; j < ye; j++) {
393 PetscReal expected = PeriodicScalarValue(1, j, k, 0.0);
394 PetscCall(PicurvAssertRealNear(expected + 2000.0, ucont[k][j][mx - 1].y, 1.0e-12,
395 "distributed Ucont.y positive X dummy"));
396 PetscCall(PicurvAssertRealNear(expected + 3000.0, ucont[k][j][mx - 1].z, 1.0e-12,
397 "distributed Ucont.z positive X dummy"));
398 }
399 }
400 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucont, &ucont));
401
402 PetscCall(DMDAVecGetArrayRead(user->fda, user->lUcont, &ucont));
403 if (xs == 0) {
404 for (PetscInt k = zs; k < ze; k++) for (PetscInt j = ys; j < ye; j++) {
405 PetscCall(PicurvAssertRealNear(PeriodicScalarValue(mx - 3, j, k, 1000.0), ucont[k][j][-1].x, 1.0e-12,
406 "distributed Ucont.x negative adjacent ghost repair"));
407 PetscCall(PicurvAssertRealNear(PeriodicScalarValue(1, j, k, 2000.0), ucont[k][j][-1].y, 1.0e-12,
408 "distributed Ucont.y tangential X wraparound"));
409 }
410 }
411 if (xe == mx) {
412 for (PetscInt k = zs; k < ze; k++) for (PetscInt j = ys; j < ye; j++) {
413 PetscCall(PicurvAssertRealNear(PeriodicScalarValue(2, j, k, 1000.0), ucont[k][j][mx].x, 1.0e-12,
414 "distributed Ucont.x positive adjacent ghost repair"));
415 PetscCall(PicurvAssertRealNear(PeriodicScalarValue(mx - 2, j, k, 2000.0), ucont[k][j][mx].y, 1.0e-12,
416 "distributed Ucont.y positive tangential X wraparound"));
417 }
418 }
419 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lUcont, &ucont));
420
421 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
422 PetscFunctionReturn(0);
423}
424/**
425 * @brief Tests restart fast-path migration using preloaded cell ownership metadata.
426 */
427
429{
430 SimCtx *simCtx = NULL;
431 UserCtx *user = NULL;
432 RankCellInfo my_cell_info;
433 PetscMPIInt rank = 0, size = 1;
434 PetscInt *cell_ids = NULL;
435 PetscReal *positions = NULL;
436 PetscInt nlocal = 0;
437
438 PetscFunctionBeginUser;
439 PetscCallMPI(MPI_Comm_rank(PETSC_COMM_WORLD, &rank));
440 PetscCallMPI(MPI_Comm_size(PETSC_COMM_WORLD, &size));
441 PetscCall(PicurvAssertIntEqual(2, size, "restart migration unit test expects exactly two MPI ranks"));
442
443 PetscCall(PetscMemzero(&my_cell_info, sizeof(my_cell_info)));
444 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 8, 4, 4));
445 PetscCall(PicurvCreateSwarmPair(user, 1, "ske"));
446 PetscCall(GetOwnedCellRange(&user->info, 0, &my_cell_info.xs_cell, &my_cell_info.xm_cell));
447 PetscCall(GetOwnedCellRange(&user->info, 1, &my_cell_info.ys_cell, &my_cell_info.ym_cell));
448 PetscCall(GetOwnedCellRange(&user->info, 2, &my_cell_info.zs_cell, &my_cell_info.zm_cell));
449 PetscCall(PetscMalloc1(size, &user->RankCellInfoMap));
450 PetscCallMPI(MPI_Allgather(&my_cell_info, sizeof(RankCellInfo), MPI_BYTE,
451 user->RankCellInfoMap, sizeof(RankCellInfo), MPI_BYTE,
452 PETSC_COMM_WORLD));
453
454 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
455 PetscCall(DMSwarmGetField(user->swarm, "position", NULL, NULL, (void **)&positions));
456 if (rank == 0) {
457 cell_ids[0] = user->RankCellInfoMap[1].xs_cell;
458 cell_ids[1] = user->RankCellInfoMap[1].ys_cell;
459 cell_ids[2] = user->RankCellInfoMap[1].zs_cell;
460 positions[0] = 0.875;
461 positions[1] = 0.5;
462 positions[2] = 0.5;
463 } else {
464 cell_ids[0] = user->RankCellInfoMap[1].xs_cell;
465 cell_ids[1] = user->RankCellInfoMap[1].ys_cell;
466 cell_ids[2] = user->RankCellInfoMap[1].zs_cell;
467 positions[0] = 0.95;
468 positions[1] = 0.5;
469 positions[2] = 0.5;
470 }
471 PetscCall(DMSwarmRestoreField(user->swarm, "position", NULL, NULL, (void **)&positions));
472 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
473
474 PetscCall(MigrateRestartParticlesUsingCellID(user));
475 PetscCall(DMSwarmGetLocalSize(user->swarm, &nlocal));
476 PetscCall(PicurvAssertIntEqual((rank == 0) ? 0 : 2, nlocal,
477 "restart migration should move the foreign particle onto the owning rank"));
478
479 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
480 PetscFunctionReturn(0);
481}
482/**
483 * @brief Spatial target domains must not depend on how ranks divide the block.
484 *
485 * The global point count is a function of layout, global dimensions, and
486 * periodicity alone. Summing the per-rank counts must therefore reproduce the
487 * same analytic totals a single rank computes, with no point counted twice at a
488 * decomposition seam and none dropped.
489 */
491{
492 SimCtx *simCtx = NULL;
493 UserCtx *user = NULL;
495 PetscInt local = 0, global = 0, rank_count = 0;
496 PetscMPIInt size = 0;
497 const PetscInt n = 6; /* DMDA is n + 1 = 7 per dimension */
498 const PetscInt cell = 5; /* cell-like span [1, 6) */
499 const PetscInt node = 6; /* node-like nonperiodic span [0, 6) */
500
501 PetscFunctionBeginUser;
502 PetscCallMPI(MPI_Comm_size(PETSC_COMM_WORLD, &size));
503 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, n, n, n));
504
506 PetscCall(SpatialTargetPlanLocalPointCount(&plan, &local));
507 PetscCall(SpatialTargetPlanGlobalPointCount(&plan, PETSC_COMM_WORLD, &global));
508 PetscCall(PicurvAssertIntEqual(cell * cell * cell, global,
509 "cell-centered global count must be decomposition independent"));
510
511 /* Every rank must contribute a non-negative count, and the ranks together
512 * must contribute the whole domain exactly once. */
513 PetscCall(PicurvAssertBool((PetscBool)(local >= 0), "local point count must be non-negative"));
514 PetscCallMPI(MPI_Allreduce(&local, &rank_count, 1, MPIU_INT, MPI_SUM, PETSC_COMM_WORLD));
515 PetscCall(PicurvAssertIntEqual(global, rank_count,
516 "summed local counts must equal the reduced global count"));
517
519 PetscCall(SpatialTargetPlanGlobalPointCount(&plan, PETSC_COMM_WORLD, &global));
520 PetscCall(PicurvAssertIntEqual(node * cell * cell, global,
521 "I-face global count must be decomposition independent"));
522
524 PetscCall(SpatialTargetPlanGlobalPointCount(&plan, PETSC_COMM_WORLD, &global));
525 PetscCall(PicurvAssertIntEqual(node * node * node, global,
526 "node-centered global count must be decomposition independent"));
527
528 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
529
530 /* Fully periodic: duplicate planes must be dropped once globally, not once per rank. */
531 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(&simCtx, &user, n, n, n,
532 PETSC_TRUE, PETSC_TRUE, PETSC_TRUE));
534 PetscCall(SpatialTargetPlanGlobalPointCount(&plan, PETSC_COMM_WORLD, &global));
535 PetscCall(PicurvAssertIntEqual(cell * cell * cell, global,
536 "fully periodic node-centered count must drop duplicates exactly once"));
537 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
538 PetscFunctionReturn(0);
539}
540
541/**
542 * @brief Statistics payloads must survive a checkpoint round trip under decomposition.
543 *
544 * The write path gathers each accumulator into natural ordering before it reaches
545 * disk, and the read path scatters it back. That pairing is where a decomposition
546 * bug would live, so this drives the whole cycle on more than one rank and requires
547 * an exact match: the format stores IEEE doubles, and natural ordering is defined
548 * to be independent of how ranks divide the block.
549 */
551{
552 SimCtx *simCtx = NULL;
553 UserCtx *user = NULL;
554 PicurvWindow window;
555 PicurvWindowStorage storage;
556 PicurvWindowDefinition definition;
557 Vec *reference = NULL;
558 char tmpdir[PETSC_MAX_PATH_LEN];
559 PetscInt payload_count = 0;
560 PetscMPIInt size = 0;
561
562 PetscFunctionBeginUser;
563 PetscCallMPI(MPI_Comm_size(PETSC_COMM_WORLD, &size));
564 PetscCall(PicurvAssertBool((PetscBool)(size >= 2), "unit-mpi requires at least two MPI ranks"));
565
566 memset(&definition, 0, sizeof(definition));
567 strncpy(definition.name, "production", PICURV_WINDOW_NAME_LENGTH - 1);
570 definition.step_cadence = 1;
571 definition.field_count = 2;
572 definition.fields[0].field_id = FIELD_ID_UCAT; definition.fields[0].want_second = PETSC_TRUE;
573 definition.fields[1].field_id = FIELD_ID_P; definition.fields[1].want_second = PETSC_TRUE;
574 definition.covariance_count = 1;
575 definition.covariances[0].first = FIELD_ID_UCAT;
576 definition.covariances[0].second = FIELD_ID_P;
577
578 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 6, 6, 6));
579 tmpdir[0] = '\0';
580 if (simCtx->rank == 0) PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
581 PetscCallMPI(MPI_Bcast(tmpdir, sizeof(tmpdir), MPI_CHAR, 0, PETSC_COMM_WORLD));
582 PetscCall(PetscStrncpy(simCtx->output_dir, tmpdir, sizeof(simCtx->output_dir)));
583 PetscCall(PetscStrncpy(simCtx->restart_dir, tmpdir, sizeof(simCtx->restart_dir)));
584 PetscCall(PicurvPopulateIdentityMetrics(user));
585 PetscCall(VecSet(user->Nvert, 0.0));
586
587 PetscCall(PicurvWindowInit(&window, &definition));
588 PetscCall(PicurvWindowStorageCreate(user, &definition, &storage));
589 simCtx->fieldStatisticsEnabled = PETSC_TRUE;
590 simCtx->fieldStatisticsWindowCount = 1;
591 simCtx->fieldStatisticsWindows = &window;
592 user->fieldStatisticsStorage = &storage;
593
594 /* A spatially varying field makes the assertion meaningful: a payload gathered
595 * or scattered through the wrong owner comes back permuted, which a uniform
596 * field could not reveal. */
597 for (PetscInt s = 0; s < 4; ++s) {
598 PetscBool accepted = PETSC_FALSE;
599 PetscReal weight = 0.0;
600 PetscReal ***p = NULL;
601 Cmpnts ***ucat = NULL;
602 const DMDALocalInfo info = user->info;
603
604 PetscCall(DMDAVecGetArray(user->da, user->P, &p));
605 PetscCall(DMDAVecGetArray(user->fda, user->Ucat, &ucat));
606 for (PetscInt k = info.zs; k < info.zs + info.zm; ++k)
607 for (PetscInt j = info.ys; j < info.ys + info.ym; ++j)
608 for (PetscInt i = info.xs; i < info.xs + info.xm; ++i) {
609 const PetscReal tag = (PetscReal)(100 * k + 10 * j + i);
610
611 p[k][j][i] = tag + 0.25 * (PetscReal)s;
612 ucat[k][j][i].x = tag;
613 ucat[k][j][i].y = tag + 1.0 + (PetscReal)s;
614 ucat[k][j][i].z = tag - 1.0;
615 }
616 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucat, &ucat));
617 PetscCall(DMDAVecRestoreArray(user->da, user->P, &p));
618
619 PetscCall(PicurvWindowOfferState(&window, s, 0.5 * (PetscReal)s, &accepted, &weight));
620 if (accepted) PetscCall(PicurvWindowAccumulate(user, &definition, &storage, weight));
621 }
622 PetscCall(PicurvAssertIntEqual(3, window.sample_count, "three intervals are represented"));
623
624 PetscCall(WriteCheckpointBundle(simCtx, "test"));
625
626 PetscCall(PicurvWindowStoragePayloadCount(&storage, &payload_count));
627 PetscCall(PetscCalloc1((size_t)payload_count, &reference));
628 for (PetscInt index = 0; index < payload_count; ++index) {
630
631 PetscCall(PicurvWindowStoragePayload(user, &definition, &storage, index, &payload));
632 PetscCall(VecDuplicate(payload.vec, &reference[index]));
633 PetscCall(VecCopy(payload.vec, reference[index]));
634 PetscCall(VecZeroEntries(payload.vec));
635 }
636 PetscCall(PicurvWindowInit(&window, &definition));
637
638 simCtx->fieldStatisticsContinue = PETSC_TRUE;
639 PetscCall(RestoreFieldStatisticsState(simCtx, simCtx->step));
640 PetscCall(PicurvAssertIntEqual(3, window.sample_count, "window bookkeeping survives on every rank"));
641 PetscCall(PicurvAssertRealNear(1.5, window.total_weight, 1.0e-12,
642 "physical-time weights survive on every rank"));
643
644 for (PetscInt index = 0; index < payload_count; ++index) {
646 PetscReal difference = 0.0;
647 char context[192];
648
649 PetscCall(PicurvWindowStoragePayload(user, &definition, &storage, index, &payload));
650 PetscCall(VecAXPY(reference[index], -1.0, payload.vec));
651 PetscCall(VecNorm(reference[index], NORM_INFINITY, &difference));
652 PetscCall(PetscSNPrintf(context, sizeof(context),
653 "payload '%s' round trips exactly across %d ranks",
654 payload.name, (int)size));
655 PetscCall(PicurvAssertBool((PetscBool)(difference == 0.0), context));
656 PetscCall(VecDestroy(&reference[index]));
657 }
658 PetscCall(PetscFree(reference));
659
660 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
661 if (simCtx->rank == 0) PetscCall(PicurvRemoveTempDir(tmpdir));
662 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
663 simCtx->fieldStatisticsEnabled = PETSC_FALSE;
664 simCtx->fieldStatisticsWindowCount = 0;
665 simCtx->fieldStatisticsWindows = NULL;
666 user->fieldStatisticsStorage = NULL;
667 PetscCall(PicurvWindowStorageDestroy(&storage));
668 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
669 PetscFunctionReturn(0);
670}
671
672/**
673 * @brief Tests that LES coefficient averaging does not depend on the decomposition.
674 *
675 * The averaged coefficient is a physical property of the block, so splitting the same
676 * field across more ranks must not change it. This case catches a wrong communicator,
677 * a halo entry counted as if it were owned, or a layout boundary plane included in one
678 * decomposition but not another. The reference is computed from the analytic
679 * definition over the interior index box rather than from a single-rank run, so the
680 * test does not merely compare one decomposition against itself.
681 */
683{
684 SimCtx *simCtx = NULL;
685 UserCtx *user = NULL;
686 Vec numerator, denominator, ratio;
688 PetscReal ***num = NULL, ***den = NULL, ***out = NULL;
689 DMDALocalInfo info;
690 PetscInt xs, xe, ys, ye, zs, ze;
691 PetscReal sampled = 0.0;
692 PetscReal numerator_total = 0.0, denominator_total = 0.0;
693 const PetscBool all[3] = {PETSC_TRUE, PETSC_TRUE, PETSC_TRUE};
694
695 PetscFunctionBeginUser;
696 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 8, 8, 8));
697 PetscCall(DMDAGetLocalInfo(user->da, &info));
698 PetscCall(VecSet(user->lNvert, 0.0));
699 PetscCall(VecSet(user->lAj, 1.0));
700
701 xs = (info.xs == 0) ? 1 : info.xs;
702 ys = (info.ys == 0) ? 1 : info.ys;
703 zs = (info.zs == 0) ? 1 : info.zs;
704 xe = (info.xs + info.xm == info.mx) ? info.mx - 1 : info.xs + info.xm;
705 ye = (info.ys + info.ym == info.my) ? info.my - 1 : info.ys + info.ym;
706 ze = (info.zs + info.zm == info.mz) ? info.mz - 1 : info.zs + info.zm;
707
708 PetscCall(VecDuplicate(user->lNvert, &numerator));
709 PetscCall(VecDuplicate(user->lNvert, &denominator));
710 PetscCall(VecDuplicate(user->lNvert, &ratio));
711 PetscCall(VecSet(numerator, 0.0));
712 PetscCall(VecSet(denominator, 0.0));
713 PetscCall(VecSet(ratio, 0.0));
714
715 /* Each rank writes only the cells it owns, from a closed-form function of the
716 global indices, so the distributed sums are exactly the serial ones below. */
717 PetscCall(DMDAVecGetArray(user->da, numerator, &num));
718 PetscCall(DMDAVecGetArray(user->da, denominator, &den));
719 for (PetscInt k = zs; k < ze; ++k)
720 for (PetscInt j = ys; j < ye; ++j)
721 for (PetscInt i = xs; i < xe; ++i) {
722 num[k][j][i] = (PetscReal)(i * j + k);
723 den[k][j][i] = (PetscReal)(i + 1);
724 }
725 PetscCall(DMDAVecRestoreArray(user->da, numerator, &num));
726 PetscCall(DMDAVecRestoreArray(user->da, denominator, &den));
727
728 PetscCall(SpatialTargetPlanCreate(user, FIELD_ID_CS,
730 PetscCall(PicurvSpatialRatioAverage(user, &plan, numerator, denominator, NULL, all,
731 PETSC_COMM_WORLD, ratio, NULL));
732
733 for (PetscInt k = 1; k < info.mz - 1; ++k)
734 for (PetscInt j = 1; j < info.my - 1; ++j)
735 for (PetscInt i = 1; i < info.mx - 1; ++i) {
736 numerator_total += (PetscReal)(i * j + k);
737 denominator_total += (PetscReal)(i + 1);
738 }
739
740 PetscCall(DMDAVecGetArray(user->da, ratio, &out));
741 sampled = out[zs][ys][xs];
742 PetscCall(DMDAVecRestoreArray(user->da, ratio, &out));
743
744 PetscCall(PicurvAssertRealNear(numerator_total / denominator_total, sampled, 1.0e-10,
745 "the globally averaged ratio must match the serial sums "
746 "however the block is decomposed"));
747
748 PetscCall(VecDestroy(&numerator));
749 PetscCall(VecDestroy(&denominator));
750 PetscCall(VecDestroy(&ratio));
751 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
752 PetscFunctionReturn(0);
753}
754
755/**
756 * @brief Runs the unit-mpi PETSc test binary.
757 */
758int main(int argc, char **argv)
759{
760 PetscErrorCode ierr;
761 const PicurvTestCase cases[] = {
762 {"distribute-particles-collective-consistency", TestDistributeParticlesCollectiveConsistency},
763 {"bounding-box-collectives-multi-rank", TestBoundingBoxCollectivesMultiRank},
764 {"periodic-cell-field-synchronization-multi-rank", TestPeriodicCellFieldSynchronizationMultiRank},
765 {"periodic-geometry-validation-multi-rank", TestPeriodicGeometryValidationMultiRank},
766 {"periodic-face-center-coordinates-multi-rank", TestPeriodicFaceCenterCoordinatesMultiRank},
767 {"periodic-face-field-synchronization-multi-rank", TestPeriodicFaceFieldSynchronizationMultiRank},
768 {"periodic-staggered-field-synchronization-multi-rank", TestPeriodicStaggeredFieldSynchronizationMultiRank},
769 {"spatial-target-decomposition-independent", TestSpatialTargetIsDecompositionIndependent},
770 {"statistics-checkpoint-round-trip-multi-rank", TestStatisticsCheckpointRoundTripMultiRank},
771 {"les-averaging-decomposition-independent", TestLESAveragingIsDecompositionIndependent},
772 {"restart-cellid-migration-moves-particle-to-owner", TestRestartCellIdMigrationMovesParticleToOwner},
773 };
774
775 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv MPI-focused runtime tests");
776 if (ierr) {
777 return (int)ierr;
778 }
779
780 ierr = PicurvRunTests("unit-mpi", cases, sizeof(cases) / sizeof(cases[0]));
781 if (ierr) {
782 PetscFinalize();
783 return (int)ierr;
784 }
785
786 ierr = PetscFinalize();
787 return (int)ierr;
788}
PetscErrorCode SynchronizePeriodicStaggeredFields(UserCtx *user, PetscInt num_fields, const FieldId field_ids[])
Synchronizes persistent component-staggered vector fields.
PetscErrorCode SynchronizePeriodicFaceFields(UserCtx *user, char face_direction, PetscInt num_fields, const FieldId field_ids[])
Synchronizes persistent fields belonging to one face family.
PetscErrorCode SynchronizePeriodicCellFields(UserCtx *user, PetscInt num_fields, const FieldId field_ids[])
Synchronizes periodic endpoint cells for a list of cell-centered fields.
Header file for Particle Motion and migration related functions.
PetscErrorCode MigrateRestartParticlesUsingCellID(UserCtx *user)
Fast-path migration for restart particles using preloaded Cell IDs.
Header file for Particle Swarm management functions.
PetscErrorCode DistributeParticles(PetscInt numParticles, PetscMPIInt rank, PetscMPIInt size, PetscInt *particlesPerProcess, PetscInt *remainder)
Distributes particles evenly across MPI processes, handling any remainders.
Authoritative identities and storage metadata for persistent Eulerian fields.
FieldId
Compile-time identity for a catalogued Eulerian field.
@ FIELD_ID_CSI
@ FIELD_ID_IAJ
@ FIELD_ID_UCAT
@ FIELD_ID_COORDINATES
@ FIELD_ID_UCONT
@ FIELD_ID_CS
@ FIELD_ID_P
@ FIELD_ID_CENTX
@ FIELD_ID_DIFFUSIVITY
Public interface for grid, solver, and metric setup routines.
PetscErrorCode BroadcastAllBoundingBoxes(UserCtx *user, BoundingBox **bboxlist)
Broadcasts the bounding box information collected on rank 0 to all other ranks.
Definition grid.c:1061
PetscErrorCode ValidatePeriodicGeometry(UserCtx *user)
Validates that configured geometric periodic seams match by translation.
Definition grid.c:421
PetscErrorCode ComputeLocalBoundingBox(UserCtx *user, BoundingBox *localBBox)
Computes the local bounding box of the grid on the current process.
Definition grid.c:850
PetscErrorCode GatherAllBoundingBoxes(UserCtx *user, BoundingBox **allBBoxes)
Gathers local bounding boxes from all MPI processes to rank 0.
Definition grid.c:999
Public interface for data input/output routines.
PetscErrorCode RestoreFieldStatisticsState(SimCtx *simCtx, PetscInt ti)
Restores field-statistics window state and accumulators from a checkpoint.
Definition io.c:1686
PetscErrorCode WriteCheckpointBundle(SimCtx *simCtx, const char *reason)
Write and atomically publish one complete checkpoint bundle.
Definition io.c:2554
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:2936
Per-window PETSc accumulator storage and pointwise application.
Vec vec
Borrowed accumulator vector; never owned by the caller.
PetscErrorCode PicurvWindowStorageCreate(UserCtx *user, const PicurvWindowDefinition *definition, PicurvWindowStorage *storage)
Allocates the accumulator state one window owns on one block.
PetscErrorCode PicurvWindowStoragePayload(UserCtx *user, const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, PetscInt index, PicurvStatisticsPayload *payload)
Resolves one enumerated payload of a window's storage.
char name[96]
File basename, no extension.
PetscErrorCode PicurvWindowAccumulate(UserCtx *user, const PicurvWindowDefinition *definition, PicurvWindowStorage *storage, PetscReal weight)
Applies one accepted completed state to a window's accumulators.
PetscErrorCode PicurvWindowStorageDestroy(PicurvWindowStorage *storage)
Releases accumulator state previously created for one window.
PetscErrorCode PicurvWindowStoragePayloadCount(const PicurvWindowStorage *storage, PetscInt *count)
Reports how many checkpointable vectors one window's storage holds.
One checkpointable accumulator vector, resolved by enumeration index.
Independent accumulator state for one window on one block.
Spatial target resolution for the field-statistics pipeline.
@ PICURV_STATISTICS_MASK_FLUID
PetscErrorCode SpatialTargetPlanLocalPointCount(const SpatialTargetPlan *plan, PetscInt *count)
Counts the points this rank contributes.
PetscErrorCode SpatialTargetPlanGlobalPointCount(const SpatialTargetPlan *plan, MPI_Comm comm, PetscInt *count)
Counts the points contributed across a communicator.
PetscErrorCode SpatialTargetPlanCreate(UserCtx *user, FieldId field_id, PicurvStatisticsMask mask, SpatialTargetPlan *plan)
Resolves the iteration domain for one field on one block.
PetscErrorCode PicurvSpatialRatioAverage(UserCtx *user, const SpatialTargetPlan *plan, Vec numerator, Vec denominator, Vec inclusion, const PetscBool average_direction[3], MPI_Comm comm, Vec ratio, PetscReal *scalar)
Averages two fields over a target domain and divides the results.
Resolved iteration domain for one field on one block.
Window lifecycle, scheduling, and weighting for the field-statistics pipeline.
PetscInt sample_count
PetscInt first
First member; must also appear in the field list.
PicurvWindowFieldRequest fields[16]
PicurvCadenceKind cadence_kind
PetscReal total_weight
PetscInt step_cadence
Used when cadence_kind is step; must be positive.
#define PICURV_WINDOW_NAME_LENGTH
Maximum stored length of a window name, including the terminator.
PetscErrorCode PicurvWindowInit(PicurvWindow *window, const PicurvWindowDefinition *definition)
Validates a definition and initializes a window to the pending state.
PetscErrorCode PicurvWindowOfferState(PicurvWindow *window, PetscInt step, PetscReal time, PetscBool *accepted, PetscReal *weight)
Offers one completed state to a window and reports the decision.
PetscBool want_second
Also keep the centered second moment.
PetscInt second
Second member; must also appear in the field list.
PicurvWindowCovarianceRequest covariances[16]
PetscInt field_id
Catalogued Eulerian field identity.
@ PICURV_WEIGHTING_PHYSICAL_TIME
Weight is the represented interval.
@ PICURV_CADENCE_STEP
Every n completed steps from activation.
Runtime state of one window.
The scientifically immutable definition of one window.
static void MarkXPeriodic(UserCtx *user)
Marks both x boundary faces as geometric periodic boundaries.
static PetscErrorCode TestSpatialTargetIsDecompositionIndependent(void)
Spatial target domains must not depend on how ranks divide the block.
static PetscErrorCode TestPeriodicFaceCenterCoordinatesMultiRank(void)
Tests translated face-center ghosts when the periodic axis is partitioned.
int main(int argc, char **argv)
Runs the unit-mpi PETSc test binary.
static PetscErrorCode TestPeriodicFaceFieldSynchronizationMultiRank(void)
Tests persistent I-face synchronization across an MPI-partitioned seam.
static PetscErrorCode TestRestartCellIdMigrationMovesParticleToOwner(void)
Tests restart fast-path migration using preloaded cell ownership metadata.
static PetscErrorCode TestLESAveragingIsDecompositionIndependent(void)
Tests that LES coefficient averaging does not depend on the decomposition.
static PetscErrorCode TestPeriodicGeometryValidationMultiRank(void)
Tests periodic seam translation discovery when seam nodes are on different ranks.
static PetscErrorCode TestStatisticsCheckpointRoundTripMultiRank(void)
Statistics payloads must survive a checkpoint round trip under decomposition.
static PetscErrorCode TestPeriodicStaggeredFieldSynchronizationMultiRank(void)
Tests persistent Ucont synchronization across an MPI-partitioned seam.
static PetscReal PeriodicScalarValue(PetscInt i, PetscInt j, PetscInt k, PetscReal offset)
Returns a deterministic scalar value for periodic-transfer assertions.
static PetscErrorCode TestPeriodicCellFieldSynchronizationMultiRank(void)
Tests distributed synchronization of persistent cell-centered scalar fields.
static PetscErrorCode TestDistributeParticlesCollectiveConsistency(void)
Tests collective particle distribution consistency across MPI ranks.
static PetscErrorCode TestBoundingBoxCollectivesMultiRank(void)
Tests multi-rank bounding-box gather and broadcast helpers.
PetscErrorCode PicurvMakeTempDir(char *path, size_t path_len)
Creates a unique temporary directory for one test case.
PetscErrorCode PicurvCreateMinimalContexts(SimCtx **simCtx_out, UserCtx **user_out, PetscInt mx, PetscInt my, PetscInt mz)
Builds minimal SimCtx and UserCtx fixtures for C unit tests.
PetscErrorCode PicurvAssertRealNear(PetscReal expected, PetscReal actual, PetscReal tol, const char *context)
Asserts that two real values agree within tolerance.
PetscErrorCode PicurvDestroyMinimalContexts(SimCtx **simCtx_ptr, UserCtx **user_ptr)
Destroys minimal SimCtx/UserCtx fixtures and all owned PETSc objects.
PetscErrorCode PicurvCreateMinimalContextsWithPeriodicity(SimCtx **simCtx_out, UserCtx **user_out, PetscInt mx, PetscInt my, PetscInt mz, PetscBool x_periodic, PetscBool y_periodic, PetscBool z_periodic)
Builds minimal SimCtx and UserCtx fixtures for C unit tests with configurable periodicity.
PetscErrorCode PicurvCreateSwarmPair(UserCtx *user, PetscInt nlocal, const char *post_field_name)
Creates matched solver and post-processing swarms for tests.
PetscErrorCode PicurvRunTests(const char *suite_name, const PicurvTestCase *cases, size_t case_count)
Runs a named C test suite and prints pass/fail progress markers.
PetscErrorCode PicurvAssertIntEqual(PetscInt expected, PetscInt actual, const char *context)
Asserts that two integer values are equal.
PetscErrorCode PicurvPopulateIdentityMetrics(UserCtx *user)
Populates identity metric vectors on the minimal grid fixture.
PetscErrorCode PicurvAssertBool(PetscBool value, const char *context)
Asserts that one boolean condition is true.
PetscErrorCode PicurvRemoveTempDir(const char *path)
Recursively removes a temporary directory created by PicurvMakeTempDir.
Shared declarations for the PICurv C test fixture and assertion layer.
Named test case descriptor consumed by PicurvRunTests.
PetscInt fieldStatisticsWindowCount
Definition variables.h:932
@ PERIODIC
Definition variables.h:318
PetscInt ys_cell
Definition variables.h:230
PetscInt xs_cell
Definition variables.h:230
PetscMPIInt rank
Definition variables.h:862
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:1099
Vec lNvert
Definition variables.h:1113
@ BC_HANDLER_PERIODIC_GEOMETRIC
Definition variables.h:340
BCHandlerType handler_type
Definition variables.h:393
PetscInt zm_cell
Definition variables.h:231
Cmpnts max_coords
Maximum x, y, z coordinates of the bounding box.
Definition variables.h:199
PetscInt zs_cell
Definition variables.h:230
char output_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:881
PetscBool fieldStatisticsEnabled
Definition variables.h:931
Vec Ucont
Definition variables.h:1113
Cmpnts min_coords
Minimum x, y, z coordinates of the bounding box.
Definition variables.h:198
PetscScalar x
Definition variables.h:122
Vec Centx
Definition variables.h:1149
PetscInt xm_cell
Definition variables.h:231
RankCellInfo * RankCellInfoMap
Definition variables.h:1170
PetscInt ym_cell
Definition variables.h:231
struct PicurvWindow * fieldStatisticsWindows
Definition variables.h:933
PetscScalar z
Definition variables.h:122
struct PicurvWindowStorage * fieldStatisticsStorage
Definition variables.h:1136
Vec lCentx
Definition variables.h:1150
Vec lUcont
Definition variables.h:1113
PetscInt step
Definition variables.h:867
Vec Diffusivity
Definition variables.h:1116
DMDALocalInfo info
Definition variables.h:1086
Vec lUcat
Definition variables.h:1113
PetscScalar y
Definition variables.h:122
PetscInt IM
Definition variables.h:1088
Cmpnts periodic_translation[3]
Definition variables.h:1095
Vec Nvert
Definition variables.h:1113
PetscBool periodic_translation_valid[3]
Definition variables.h:1096
BCType mathematical_type
Definition variables.h:392
char restart_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:880
PetscBool fieldStatisticsContinue
Definition variables.h:938
@ BC_FACE_NEG_X
Definition variables.h:288
@ BC_FACE_POS_X
Definition variables.h:288
Defines a 3D axis-aligned bounding box.
Definition variables.h:197
A 3D point or vector with PetscScalar components.
Definition variables.h:121
A lean struct to hold the global cell ownership range for a single MPI rank.
Definition variables.h:229
The master context for the entire simulation.
Definition variables.h:859
User-defined context containing data specific to a single computational grid level.
Definition variables.h:1074
A generic C-style linked list node for integers.
Definition variables.h:463