PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
test_runtime_kernels.c
Go to the documentation of this file.
1/**
2 * @file test_runtime_kernels.c
3 * @brief C unit tests for runtime, particle, wall, and walltime-guard helpers.
4 */
5
6#include "test_support.h"
7
8#include "BC_Handlers.h"
9#include "ParticleMotion.h"
10#include "ParticlePhysics.h"
11#include "ParticleSwarm.h"
12#include "interpolation.h"
13#include "initialcondition.h"
14#include "les.h"
15#include "runloop.h"
16#include "setup.h"
17#include "wallfunction.h"
18#include "walkingsearch.h"
19
20/**
21 * @brief Synchronizes the minimal runtime fixture's global fields into their persistent local ghosts.
22 */
23static PetscErrorCode SyncRuntimeFieldGhosts(UserCtx *user)
24{
25 PetscFunctionBeginUser;
26 PetscCall(DMGlobalToLocalBegin(user->da, user->P, INSERT_VALUES, user->lP));
27 PetscCall(DMGlobalToLocalEnd(user->da, user->P, INSERT_VALUES, user->lP));
28 PetscCall(DMGlobalToLocalBegin(user->da, user->Psi, INSERT_VALUES, user->lPsi));
29 PetscCall(DMGlobalToLocalEnd(user->da, user->Psi, INSERT_VALUES, user->lPsi));
30 PetscCall(DMGlobalToLocalBegin(user->da, user->Diffusivity, INSERT_VALUES, user->lDiffusivity));
31 PetscCall(DMGlobalToLocalEnd(user->da, user->Diffusivity, INSERT_VALUES, user->lDiffusivity));
32 PetscCall(DMGlobalToLocalBegin(user->fda, user->Ucat, INSERT_VALUES, user->lUcat));
33 PetscCall(DMGlobalToLocalEnd(user->fda, user->Ucat, INSERT_VALUES, user->lUcat));
34 PetscCall(DMGlobalToLocalBegin(user->fda, user->Ucont, INSERT_VALUES, user->lUcont));
35 PetscCall(DMGlobalToLocalEnd(user->fda, user->Ucont, INSERT_VALUES, user->lUcont));
36 PetscCall(DMGlobalToLocalBegin(user->fda, user->DiffusivityGradient, INSERT_VALUES, user->lDiffusivityGradient));
37 PetscCall(DMGlobalToLocalEnd(user->fda, user->DiffusivityGradient, INSERT_VALUES, user->lDiffusivityGradient));
38 PetscFunctionReturn(0);
39}
40
41/**
42 * @brief Seeds one localized swarm particle with the cell, position, weight, and status data used by runtime tests.
43 */
44static PetscErrorCode SeedSingleParticle(UserCtx *user,
45 PetscInt ci,
46 PetscInt cj,
47 PetscInt ck,
48 PetscReal x,
49 PetscReal y,
50 PetscReal z,
51 PetscReal wx,
52 PetscReal wy,
53 PetscReal wz,
54 PetscInt status_value)
55{
56 PetscReal *positions = NULL;
57 PetscReal *weights = NULL;
58 PetscInt *cell_ids = NULL;
59 PetscInt *status = NULL;
60
61 PetscFunctionBeginUser;
62 PetscCall(DMSwarmGetField(user->swarm, "position", NULL, NULL, (void **)&positions));
63 PetscCall(DMSwarmGetField(user->swarm, "weight", NULL, NULL, (void **)&weights));
64 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
65 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
66
67 positions[0] = x;
68 positions[1] = y;
69 positions[2] = z;
70 weights[0] = wx;
71 weights[1] = wy;
72 weights[2] = wz;
73 cell_ids[0] = ci;
74 cell_ids[1] = cj;
75 cell_ids[2] = ck;
76 status[0] = status_value;
77
78 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
79 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
80 PetscCall(DMSwarmRestoreField(user->swarm, "weight", NULL, NULL, (void **)&weights));
81 PetscCall(DMSwarmRestoreField(user->swarm, "position", NULL, NULL, (void **)&positions));
82 PetscFunctionReturn(0);
83}
84/**
85 * @brief Tests particle distribution remainder handling across ranks.
86 */
87
89{
90 PetscInt particles_per_rank = -1;
91 PetscInt remainder = -1;
92
93 PetscFunctionBeginUser;
94 PetscCall(DistributeParticles(10, 0, 3, &particles_per_rank, &remainder));
95 PetscCall(PicurvAssertIntEqual(4, particles_per_rank, "rank 0 should receive one remainder particle"));
96 PetscCall(PicurvAssertIntEqual(1, remainder, "remainder should be reported correctly"));
97
98 PetscCall(DistributeParticles(10, 2, 3, &particles_per_rank, &remainder));
99 PetscCall(PicurvAssertIntEqual(3, particles_per_rank, "last rank should receive base particle count"));
100 PetscCall(PicurvAssertIntEqual(1, remainder, "remainder should remain unchanged across ranks"));
101 PetscFunctionReturn(0);
102}
103/**
104 * @brief Tests basic particle-inside-bounding-box classification cases.
105 */
106
108{
109 BoundingBox bbox;
110 Particle particle;
111
112 PetscFunctionBeginUser;
113 PetscCall(PetscMemzero(&bbox, sizeof(bbox)));
114 PetscCall(PetscMemzero(&particle, sizeof(particle)));
115
116 bbox.min_coords.x = 0.0;
117 bbox.min_coords.y = 0.0;
118 bbox.min_coords.z = 0.0;
119 bbox.max_coords.x = 1.0;
120 bbox.max_coords.y = 2.0;
121 bbox.max_coords.z = 3.0;
122
123 particle.loc.x = 0.25;
124 particle.loc.y = 1.0;
125 particle.loc.z = 2.5;
126 PetscCall(PicurvAssertBool(IsParticleInsideBoundingBox(&bbox, &particle), "particle should be inside bounding box"));
127
128 particle.loc.x = 1.5;
129 PetscCall(PicurvAssertBool((PetscBool)!IsParticleInsideBoundingBox(&bbox, &particle), "particle should be outside bounding box"));
130 PetscFunctionReturn(0);
131}
132/**
133 * @brief Tests particle weight updates against expected ratios.
134 */
135
137{
138 Particle particle;
139 PetscReal distances[NUM_FACES] = {1.0, 3.0, 2.0, 2.0, 4.0, 1.0};
140 PetscReal clamped[NUM_FACES] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
141
142 PetscFunctionBeginUser;
143 PetscCall(PetscMemzero(&particle, sizeof(particle)));
144 PetscCall(UpdateParticleWeights(distances, &particle));
145 PetscCall(PicurvAssertRealNear(0.25, particle.weights.x, 1.0e-12, "x interpolation weight"));
146 PetscCall(PicurvAssertRealNear(0.5, particle.weights.y, 1.0e-12, "y interpolation weight"));
147 PetscCall(PicurvAssertRealNear(0.2, particle.weights.z, 1.0e-12, "z interpolation weight"));
148
149 PetscCall(UpdateParticleWeights(clamped, &particle));
150 PetscCall(PicurvAssertRealNear(0.5, particle.weights.x, 1.0e-12, "clamped x weight remains centered"));
151 PetscCall(PicurvAssertRealNear(0.5, particle.weights.y, 1.0e-12, "clamped y weight remains centered"));
152 PetscCall(PicurvAssertRealNear(0.5, particle.weights.z, 1.0e-12, "clamped z weight remains centered"));
153 PetscFunctionReturn(0);
154}
155/**
156 * @brief Tests particle position updates without Brownian forcing.
157 */
158
160{
161 SimCtx *simCtx = NULL;
162 UserCtx *user = NULL;
163 Particle particle;
164
165 PetscFunctionBeginUser;
166 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
167 simCtx->dt = 0.25;
168
169 PetscCall(PetscMemzero(&particle, sizeof(particle)));
170 particle.loc.x = 1.0;
171 particle.loc.y = -2.0;
172 particle.loc.z = 3.0;
173 particle.vel.x = 0.5;
174 particle.vel.y = -1.0;
175 particle.vel.z = 2.0;
176 particle.diffusivitygradient.x = 0.1;
177 particle.diffusivitygradient.y = 0.2;
178 particle.diffusivitygradient.z = -0.3;
179 particle.diffusivity = 0.0;
180
181 PetscCall(UpdateParticlePosition(user, &particle));
182 PetscCall(PicurvAssertRealNear(1.15, particle.loc.x, 1.0e-12, "x position update"));
183 PetscCall(PicurvAssertRealNear(-2.2, particle.loc.y, 1.0e-12, "y position update"));
184 PetscCall(PicurvAssertRealNear(3.425, particle.loc.z, 1.0e-12, "z position update"));
185
186 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
187 PetscFunctionReturn(0);
188}
189/**
190 * @brief Tests particle position updates driven only by diffusivity-gradient drift.
191 */
192
194{
195 SimCtx *simCtx = NULL;
196 UserCtx *user = NULL;
197 Particle particle;
198
199 PetscFunctionBeginUser;
200 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
201 simCtx->dt = 0.5;
202
203 PetscCall(PetscMemzero(&particle, sizeof(particle)));
204 particle.loc.x = 0.25;
205 particle.loc.y = 0.5;
206 particle.loc.z = 0.75;
207 particle.vel.x = 0.0;
208 particle.vel.y = 0.0;
209 particle.vel.z = 0.0;
210 particle.diffusivitygradient.x = 0.2;
211 particle.diffusivitygradient.y = -0.1;
212 particle.diffusivitygradient.z = 0.3;
213 particle.diffusivity = 0.0;
214
215 PetscCall(UpdateParticlePosition(user, &particle));
216 PetscCall(PicurvAssertRealNear(0.35, particle.loc.x, 1.0e-12, "drift-only x position update"));
217 PetscCall(PicurvAssertRealNear(0.45, particle.loc.y, 1.0e-12, "drift-only y position update"));
218 PetscCall(PicurvAssertRealNear(0.90, particle.loc.z, 1.0e-12, "drift-only z position update"));
219
220 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
221 PetscFunctionReturn(0);
222}
223/**
224 * @brief Tests IEM relaxation updates for particle-carried fields.
225 */
226
227static PetscErrorCode TestUpdateParticleFieldIEMRelaxation(void)
228{
229 PetscReal psi = 1.0;
230 const PetscReal dt = 0.5;
231 const PetscReal diffusivity = 0.2;
232 const PetscReal mean_val = 3.0;
233 const PetscReal cell_vol = 8.0;
234 const PetscReal c_model = 2.0;
235 PetscReal unchanged = 7.0;
236
237 PetscFunctionBeginUser;
238 PetscCall(UpdateParticleField(PARTICLE_FIELD_ID_PSI, dt, &psi, diffusivity, mean_val, cell_vol, c_model));
239 PetscCall(PicurvAssertRealNear(
240 mean_val + (1.0 - mean_val) * PetscExpReal(-(c_model * diffusivity / PetscPowReal(cell_vol, 0.6666667)) * dt),
241 psi,
242 1.0e-12,
243 "IEM update should match analytical relaxation"));
244
245 PetscCall(UpdateParticleField(PARTICLE_FIELD_ID_VELOCITY, dt, &unchanged, diffusivity, mean_val, cell_vol, c_model));
246 PetscCall(PicurvAssertRealNear(7.0, unchanged, 1.0e-12, "unknown field should remain unchanged"));
247 PetscFunctionReturn(0);
248}
249/**
250 * @brief Tests that non-Ucont requests do not modify interior field initialization.
251 */
252
254{
255 SimCtx *simCtx = NULL;
256 UserCtx *user = NULL;
257
258 PetscFunctionBeginUser;
259 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
260 PetscCall(VecSet(user->Ucont, 7.0));
261
262 PetscCall(SetInitialInteriorField(user, FIELD_ID_P));
263 PetscCall(PicurvAssertVecConstant(user->Ucont, 7.0, 1.0e-12, "non-Ucont request should not modify Ucont"));
264
265 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
266 PetscFunctionReturn(0);
267}
268/**
269 * @brief Tests cartesian Constant IC: Cart2Contra sets contravariant flux via metric dot product.
270 *
271 * The minimal runtime fixture uses identity face metrics. UniformCart2Contra
272 * therefore maps (u,v,w)=(0,0,2) directly to a Zeta flux of 2.
273 */
275{
276 SimCtx *simCtx = NULL;
277 UserCtx *user = NULL;
278 Cmpnts ***ucont = NULL;
279
280 PetscFunctionBeginUser;
281 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 5, 5, 5));
283 simCtx->InitialConstantContra.x = 0.0;
284 simCtx->InitialConstantContra.y = 0.0;
285 simCtx->InitialConstantContra.z = 2.0;
286 PetscCall(VecSet(user->Ucont, 0.0));
287
288 PetscCall(SetInitialInteriorField(user, FIELD_ID_UCONT));
289 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucont, &ucont));
290 PetscCall(PicurvAssertRealNear(0.0, ucont[1][1][1].x, 1.0e-10, "Xi flux is zero for pure-z velocity on Cartesian grid"));
291 PetscCall(PicurvAssertRealNear(0.0, ucont[1][1][1].y, 1.0e-10, "Eta flux is zero for pure-z velocity on Cartesian grid"));
292 PetscCall(PicurvAssertRealNear(2.0, ucont[1][1][1].z, 1.0e-10, "Zeta flux follows the identity fixture metric"));
293 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucont, &ucont));
294
295 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
296 PetscFunctionReturn(0);
297}
298
299/**
300 * @brief Tests curvilinear Constant IC: flow_direction selects the streamwise axis.
301 *
302 * icVelocityPhysical=2.0, FLOW_DIR_POS_ZETA. The minimal runtime fixture uses
303 * identity face metrics, so ucont.z = 2.0.
304 * Xi and Eta components are left at zero regardless of InitialConstantContra.
305 */
307{
308 SimCtx *simCtx = NULL;
309 UserCtx *user = NULL;
310 Cmpnts ***ucont = NULL;
311
312 PetscFunctionBeginUser;
313 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 5, 5, 5));
315 simCtx->icVelocityPhysical = 2.0;
317 user->GridOrientation = 1;
318 /* InitialConstantContra intentionally non-zero to confirm only z-component is set */
319 simCtx->InitialConstantContra.x = 99.0;
320 simCtx->InitialConstantContra.y = 99.0;
321 simCtx->InitialConstantContra.z = 99.0;
322 PetscCall(VecSet(user->Ucont, 0.0));
323
324 PetscCall(SetInitialInteriorField(user, FIELD_ID_UCONT));
325 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucont, &ucont));
326 PetscCall(PicurvAssertRealNear(0.0, ucont[1][1][1].x, 1.0e-10, "Xi flux is zero in curvilinear Zeta mode"));
327 PetscCall(PicurvAssertRealNear(0.0, ucont[1][1][1].y, 1.0e-10, "Eta flux is zero in curvilinear Zeta mode"));
328 PetscCall(PicurvAssertRealNear(2.0, ucont[1][1][1].z, 1.0e-10, "Zeta flux follows the identity fixture metric"));
329 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucont, &ucont));
330
331 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
332 PetscFunctionReturn(0);
333}
334
335/**
336 * @brief Tests zero IC clears physical-cell contravariant velocity.
337 */
339{
340 SimCtx *simCtx = NULL;
341 UserCtx *user = NULL;
342 Cmpnts ***ucont = NULL;
343
344 PetscFunctionBeginUser;
345 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 5, 5, 5));
347 PetscCall(VecSet(user->Ucont, 7.0));
348
349 PetscCall(SetInitialInteriorField(user, FIELD_ID_UCONT));
350 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucont, &ucont));
351 PetscCall(PicurvAssertRealNear(0.0, ucont[2][2][2].x, 1.0e-12, "zero IC clears interior Xi flux"));
352 PetscCall(PicurvAssertRealNear(0.0, ucont[2][2][2].y, 1.0e-12, "zero IC clears interior Eta flux"));
353 PetscCall(PicurvAssertRealNear(0.0, ucont[2][2][2].z, 1.0e-12, "zero IC clears interior Zeta flux"));
354 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucont, &ucont));
355
356 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
357 PetscFunctionReturn(0);
358}
359
360/**
361 * @brief Tests Poiseuille IC follows the discrete cross-stream profile and reaches zero at edges.
362 */
364{
365 SimCtx *simCtx = NULL;
366 UserCtx *user = NULL;
367 Cmpnts ***ucont = NULL;
368
369 PetscFunctionBeginUser;
370 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 5, 5, 5));
372 simCtx->icVelocityPhysical = 3.0;
374 user->GridOrientation = 1;
375 PetscCall(VecZeroEntries(user->Ucont));
376
377 PetscCall(SetInitialInteriorField(user, FIELD_ID_UCONT));
378 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucont, &ucont));
379 PetscCall(PicurvAssertRealNear(3.0 * 64.0 / 81.0, ucont[2][2][2].z, 1.0e-12,
380 "Poiseuille IC follows the discrete center-adjacent profile value"));
381 PetscCall(PicurvAssertRealNear(0.0, ucont[2][1][1].z, 1.0e-12, "Poiseuille IC is zero at cross-stream edge"));
382 PetscCall(PicurvAssertRealNear(0.0, ucont[2][2][2].x, 1.0e-12, "Poiseuille IC leaves Xi flux zero"));
383 PetscCall(PicurvAssertRealNear(0.0, ucont[2][2][2].y, 1.0e-12, "Poiseuille IC leaves Eta flux zero"));
384 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucont, &ucont));
385
386 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
387 PetscFunctionReturn(0);
388}
389
390/**
391 * @brief Tests spatially varying Cartesian-field conversion to contravariant fluxes.
392 */
393static PetscErrorCode TestCart2ContraConvertsCartesianField(void)
394{
395 SimCtx *simCtx = NULL;
396 UserCtx *user = NULL;
397 Cmpnts ***ucat = NULL;
398 Cmpnts ***ucont = NULL;
399
400 PetscFunctionBeginUser;
401 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 5, 5, 5));
402 PetscCall(DMDAVecGetArray(user->fda, user->Ucat, &ucat));
403 for (PetscInt k = 0; k < 6; k++)
404 for (PetscInt j = 0; j < 6; j++)
405 for (PetscInt i = 0; i < 6; i++)
406 ucat[k][j][i] = (Cmpnts){(PetscReal)i, 2.0, 3.0};
407 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucat, &ucat));
408 PetscCall(UpdateLocalGhosts(user, FIELD_ID_UCAT));
409 PetscCall(Cart2Contra(user));
410
411 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucont, &ucont));
412 PetscCall(PicurvAssertRealNear(1.5, ucont[1][1][1].x, 1.0e-10,
413 "Xi flux uses face-interpolated Ucat"));
414 PetscCall(PicurvAssertRealNear(2.0, ucont[1][1][1].y, 1.0e-10,
415 "Eta flux uses Cartesian y velocity"));
416 PetscCall(PicurvAssertRealNear(3.0, ucont[1][1][1].z, 1.0e-10,
417 "Zeta flux uses Cartesian z velocity"));
418 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucont, &ucont));
419
420 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
421 PetscFunctionReturn(0);
422}
423
424/** @brief Verifies periodic Ucat endpoints are repaired before Cart2Contra terminal faces. */
426{
427 SimCtx *simCtx=NULL; UserCtx *user=NULL; Cmpnts ***ucat=NULL,***ucont=NULL; PetscInt mx;
428 const FieldId cell_fields[] = {FIELD_ID_UCAT}, staggered_fields[] = {FIELD_ID_UCONT};
429 PetscFunctionBeginUser;
430 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(&simCtx,&user,4,4,4,PETSC_TRUE,PETSC_FALSE,PETSC_FALSE));
433 mx=user->info.mx;
434 PetscCall(VecSet(user->Ucat,0.0));
435 PetscCall(DMDAVecGetArray(user->fda,user->Ucat,&ucat));
436 for(PetscInt k=1;k<user->info.mz-1;k++) for(PetscInt j=1;j<user->info.my-1;j++)
437 for(PetscInt i=1;i<mx-1;i++) ucat[k][j][i]=(Cmpnts){10.0+(PetscReal)i,2.0,3.0};
438 ucat[2][2][0]=(Cmpnts){-1000.0,-1000.0,-1000.0};
439 ucat[2][2][mx-1]=(Cmpnts){-2000.0,-2000.0,-2000.0};
440 PetscCall(DMDAVecRestoreArray(user->fda,user->Ucat,&ucat));
441 PetscCall(SynchronizePeriodicCellFields(user,1,cell_fields));
442 PetscCall(UpdateLocalGhosts(user, FIELD_ID_UCAT));
443 PetscCall(Cart2Contra(user));
444 PetscCall(SynchronizePeriodicStaggeredFields(user,1,staggered_fields));
445 PetscCall(DMDAVecGetArrayRead(user->fda,user->Ucont,&ucont));
446 PetscCall(PicurvAssertRealNear(0.5*((10.0+mx-2)+(10.0+1)),ucont[2][2][mx-2].x,1e-12,"terminal Xi face uses corrected periodic Ucat endpoint"));
447 PetscCall(DMDAVecRestoreArrayRead(user->fda,user->Ucont,&ucont));
448 PetscCall(PicurvDestroyMinimalContexts(&simCtx,&user));
449 PetscFunctionReturn(0);
450}
451
452/**
453 * @brief Tests loading a staged Ucat file IC and converting it to Ucont.
454 */
456{
457 SimCtx *simCtx = NULL;
458 UserCtx *user = NULL;
459 Cmpnts ***ucont = NULL;
460 char tmpdir[PETSC_MAX_PATH_LEN];
461
462 PetscFunctionBeginUser;
463 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
464 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
465 PetscCall(VecSet(user->Ucat, 2.5));
466 PetscCall(PetscStrncpy(simCtx->_io_context_buffer, tmpdir, sizeof(simCtx->_io_context_buffer)));
468 PetscCall(WriteFieldData(user, "ufield00000_0", user->Ucat, "dat"));
469 simCtx->current_io_directory = NULL;
470
471 PetscCall(VecZeroEntries(user->Ucat));
472 PetscCall(VecZeroEntries(user->Ucont));
475 PetscCall(PetscStrncpy(simCtx->initialConditionDirectory, tmpdir, sizeof(simCtx->initialConditionDirectory)));
476 PetscCall(PopulateInitialUcont(user));
477
478 PetscCall(PicurvAssertVecConstant(user->Ucat, 2.5, 1.0e-12, "file IC should restore Ucat"));
479 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucont, &ucont));
480 PetscCall(PicurvAssertRealNear(2.5, ucont[1][1][1].x, 1.0e-12, "file Ucat IC should populate interior Xi flux"));
481 PetscCall(PicurvAssertRealNear(2.5, ucont[1][1][1].y, 1.0e-12, "file Ucat IC should populate interior Eta flux"));
482 PetscCall(PicurvAssertRealNear(2.5, ucont[1][1][1].z, 1.0e-12, "file Ucat IC should populate interior Zeta flux"));
483 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucont, &ucont));
484 PetscCall(PicurvRemoveTempDir(tmpdir));
485 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
486 PetscFunctionReturn(0);
487}
488
489/** @brief Tests LES output staging from local vectors on a multiply periodic DMDA. */
491{
492 SimCtx *simCtx = NULL;
493 UserCtx *user = NULL;
494 char tmpdir[PETSC_MAX_PATH_LEN];
495 char euler_dir[PETSC_MAX_PATH_LEN];
496 char block_dir[PETSC_MAX_PATH_LEN];
497
498 PetscFunctionBeginUser;
500 &simCtx, &user, 4, 4, 4, PETSC_TRUE, PETSC_TRUE, PETSC_TRUE));
501 PetscCall(DMCreateGlobalVector(user->da, &user->CS));
502 PetscCall(DMCreateLocalVector(user->da, &user->lCs));
503 PetscCall(DMCreateGlobalVector(user->da, &user->Nu_t));
504 PetscCall(DMCreateLocalVector(user->da, &user->lNu_t));
505 PetscCall(VecSet(user->CS, 0.0));
506 PetscCall(VecSet(user->lCs, 2.5));
507 PetscCall(VecSet(user->Nu_t, 0.0));
508 PetscCall(VecSet(user->lNu_t, 7.5));
509 simCtx->les = 1;
510
511 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
512 PetscCall(PetscSNPrintf(euler_dir, sizeof(euler_dir), "%s/eulerian", tmpdir));
513 PetscCall(PetscSNPrintf(block_dir, sizeof(block_dir), "%s/block_0000", euler_dir));
514 PetscCall(PicurvEnsureDir(euler_dir));
515 PetscCall(PicurvEnsureDir(block_dir));
516 PetscCall(WriteSimulationFields(user, tmpdir));
517
518 PetscCall(PicurvAssertVecConstant(user->CS, 2.5, 1.0e-12,
519 "checkpoint output should copy owned periodic lCs values"));
520 PetscCall(PicurvAssertVecConstant(user->Nu_t, 7.5, 1.0e-12,
521 "checkpoint output should copy owned periodic lNu_t values"));
522
523 PetscCall(PicurvRemoveTempDir(tmpdir));
524 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
525 PetscFunctionReturn(0);
526}
527
528/**
529 * @brief Stages a real Cartesian Fourier mode through the production file-IC path.
530 *
531 * The returned maximum is the native face-interpolation/backward-difference
532 * divergence. On the uniform periodic fixture its symbol is
533 * (sin(theta_x), sin(theta_y), sin(theta_z)).
534 */
535static PetscErrorCode StagedFourierModeMaxDivergence(UserCtx *user,
536 PetscInt mode_x,
537 PetscInt mode_y,
538 PetscInt mode_z,
539 Cmpnts amplitude,
540 const char *tmpdir,
541 PetscReal *max_divergence)
542{
543 SimCtx *simCtx = user->simCtx;
544 Cmpnts ***ucat = NULL, ***ucont = NULL;
545 PetscInt n_x = user->info.mx - 2, n_y = user->info.my - 2, n_z = user->info.mz - 2;
546 const FieldId staggered_fields[] = {FIELD_ID_UCONT};
547
548 PetscFunctionBeginUser;
549 PetscCall(VecZeroEntries(user->Ucat));
550 PetscCall(DMDAVecGetArray(user->fda, user->Ucat, &ucat));
551 for (PetscInt k = 0; k < user->info.mz; ++k) {
552 for (PetscInt j = 0; j < user->info.my; ++j) {
553 for (PetscInt i = 0; i < user->info.mx; ++i) {
554 PetscReal phase = 2.0 * PETSC_PI * (
555 (PetscReal)(mode_x * (i - 1)) / (PetscReal)n_x +
556 (PetscReal)(mode_y * (j - 1)) / (PetscReal)n_y +
557 (PetscReal)(mode_z * (k - 1)) / (PetscReal)n_z);
558 PetscReal wave = PetscCosReal(phase);
559 ucat[k][j][i] = (Cmpnts){amplitude.x * wave, amplitude.y * wave, amplitude.z * wave};
560 }
561 }
562 }
563 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucat, &ucat));
564
565 PetscCall(PetscStrncpy(simCtx->_io_context_buffer, tmpdir, sizeof(simCtx->_io_context_buffer)));
567 PetscCall(WriteFieldData(user, "ufield00000_0", user->Ucat, "dat"));
568 simCtx->current_io_directory = NULL;
569 PetscCall(VecZeroEntries(user->Ucat));
570 PetscCall(VecZeroEntries(user->Ucont));
571 PetscCall(PopulateInitialUcont(user));
572 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, staggered_fields));
573 PetscCall(UpdateLocalGhosts(user, FIELD_ID_UCONT));
574
575 *max_divergence = 0.0;
576 PetscCall(DMDAVecGetArrayRead(user->fda, user->lUcont, &ucont));
577 for (PetscInt k = 1; k < user->info.mz - 1; ++k) {
578 for (PetscInt j = 1; j < user->info.my - 1; ++j) {
579 for (PetscInt i = 1; i < user->info.mx - 1; ++i) {
580 PetscReal divergence =
581 ucont[k][j][i].x - ucont[k][j][i - 1].x +
582 ucont[k][j][i].y - ucont[k][j - 1][i].y +
583 ucont[k][j][i].z - ucont[k - 1][j][i].z;
584 *max_divergence = PetscMax(*max_divergence, PetscAbsReal(divergence));
585 }
586 }
587 }
588 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lUcont, &ucont));
589 PetscFunctionReturn(0);
590}
591
592/** @brief Cross-checks selected exact Fourier modes against PICurv's native discrete symbol. */
594{
595 SimCtx *simCtx = NULL;
596 UserCtx *user = NULL;
597 char tmpdir[PETSC_MAX_PATH_LEN];
598 PetscReal max_divergence = 0.0, sx, sy, sz;
599 PetscInt n;
600
601 PetscFunctionBeginUser;
603 &simCtx, &user, 9, 9, 9, PETSC_TRUE, PETSC_TRUE, PETSC_TRUE));
604 for (PetscInt face = 0; face < NUM_FACES; ++face)
606 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
609 PetscCall(PetscStrncpy(simCtx->initialConditionDirectory, tmpdir, sizeof(simCtx->initialConditionDirectory)));
610
612 user, 1, 0, 0, (Cmpnts){0.0, 1.0, 0.0}, tmpdir, &max_divergence));
613 PetscCall(PicurvAssertRealNear(0.0, max_divergence, 1.0e-12,
614 "axis-aligned transverse staged mode"));
615
616 n = user->info.mx - 2;
617 sx = PetscSinReal(2.0 * PETSC_PI / (PetscReal)n);
618 sy = PetscSinReal(4.0 * PETSC_PI / (PetscReal)n);
619 sz = PetscSinReal(6.0 * PETSC_PI / (PetscReal)n);
621 user, 1, 2, 0, (Cmpnts){sy, -sx, 0.0}, tmpdir, &max_divergence));
622 PetscCall(PicurvAssertRealNear(0.0, max_divergence, 1.0e-12,
623 "oblique first discrete transverse polarization"));
625 user, 1, 2, 3,
626 (Cmpnts){sz * sx, sz * sy, -(sx * sx + sy * sy)},
627 tmpdir, &max_divergence));
628 PetscCall(PicurvAssertRealNear(0.0, max_divergence, 1.0e-12,
629 "oblique second discrete transverse polarization"));
630
631 PetscCall(PicurvRemoveTempDir(tmpdir));
632 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
633 PetscFunctionReturn(0);
634}
635
636/**
637 * @brief Tests loading a staged Ucont file IC without Cartesian conversion.
638 */
640{
641 SimCtx *simCtx = NULL;
642 UserCtx *user = NULL;
643 char tmpdir[PETSC_MAX_PATH_LEN];
644
645 PetscFunctionBeginUser;
646 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
647 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
648 PetscCall(VecSet(user->Ucont, 4.5));
649 PetscCall(PetscStrncpy(simCtx->_io_context_buffer, tmpdir, sizeof(simCtx->_io_context_buffer)));
651 PetscCall(WriteFieldData(user, "vfield00000_0", user->Ucont, "dat"));
652 simCtx->current_io_directory = NULL;
653
654 PetscCall(VecZeroEntries(user->Ucont));
657 PetscCall(PetscStrncpy(simCtx->initialConditionDirectory, tmpdir, sizeof(simCtx->initialConditionDirectory)));
658 PetscCall(PopulateInitialUcont(user));
659
660 PetscCall(PicurvAssertVecConstant(user->Ucont, 4.5, 1.0e-12, "file Ucont IC should restore Ucont directly"));
661 PetscCall(PicurvRemoveTempDir(tmpdir));
662 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
663 PetscFunctionReturn(0);
664}
665/**
666 * @brief Tests direct interpolation from Eulerian fields to one localized swarm particle.
667 */
668
670{
671 SimCtx *simCtx = NULL;
672 UserCtx *user = NULL;
673 Cmpnts ***grad = NULL;
674 PetscReal *velocity = NULL;
675 PetscReal *diffusivity = NULL;
676 PetscReal *diffusivity_gradient = NULL;
677
678 PetscFunctionBeginUser;
679 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
680 PetscCall(PicurvCreateSwarmPair(user, 1, "ske"));
681 PetscCall(VecSet(user->Ucat, 2.0));
682 PetscCall(VecSet(user->Diffusivity, 0.25));
683
684 PetscCall(DMDAVecGetArray(user->fda, user->DiffusivityGradient, &grad));
685 for (PetscInt k = user->info.zs; k < user->info.zs + user->info.zm; ++k) {
686 for (PetscInt j = user->info.ys; j < user->info.ys + user->info.ym; ++j) {
687 for (PetscInt i = user->info.xs; i < user->info.xs + user->info.xm; ++i) {
688 grad[k][j][i].x = 0.1;
689 grad[k][j][i].y = 0.2;
690 grad[k][j][i].z = 0.3;
691 }
692 }
693 }
694 PetscCall(DMDAVecRestoreArray(user->fda, user->DiffusivityGradient, &grad));
695 PetscCall(SyncRuntimeFieldGhosts(user));
696 PetscCall(SeedSingleParticle(user, 0, 0, 0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, ACTIVE_AND_LOCATED));
697
698 PetscCall(InterpolateAllFieldsToSwarm(user));
699
700 PetscCall(DMSwarmGetField(user->swarm, "velocity", NULL, NULL, (void **)&velocity));
701 PetscCall(DMSwarmGetField(user->swarm, "Diffusivity", NULL, NULL, (void **)&diffusivity));
702 PetscCall(DMSwarmGetField(user->swarm, "DiffusivityGradient", NULL, NULL, (void **)&diffusivity_gradient));
703 PetscCall(PicurvAssertRealNear(2.0, velocity[0], 1.0e-12, "Interpolated velocity x should match constant Eulerian field"));
704 PetscCall(PicurvAssertRealNear(2.0, velocity[1], 1.0e-12, "Interpolated velocity y should match constant Eulerian field"));
705 PetscCall(PicurvAssertRealNear(2.0, velocity[2], 1.0e-12, "Interpolated velocity z should match constant Eulerian field"));
706 PetscCall(PicurvAssertRealNear(0.25, diffusivity[0], 1.0e-12, "Interpolated scalar diffusivity should match constant Eulerian field"));
707 PetscCall(PicurvAssertRealNear(0.1, diffusivity_gradient[0], 1.0e-12, "Interpolated diffusivity-gradient x component"));
708 PetscCall(PicurvAssertRealNear(0.2, diffusivity_gradient[1], 1.0e-12, "Interpolated diffusivity-gradient y component"));
709 PetscCall(PicurvAssertRealNear(0.3, diffusivity_gradient[2], 1.0e-12, "Interpolated diffusivity-gradient z component"));
710 PetscCall(DMSwarmRestoreField(user->swarm, "DiffusivityGradient", NULL, NULL, (void **)&diffusivity_gradient));
711 PetscCall(DMSwarmRestoreField(user->swarm, "Diffusivity", NULL, NULL, (void **)&diffusivity));
712 PetscCall(DMSwarmRestoreField(user->swarm, "velocity", NULL, NULL, (void **)&velocity));
713
714 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
715 PetscFunctionReturn(0);
716}
717/**
718 * @brief Tests the corner-averaged (legacy) interpolation path on constant fields.
719 */
720
722{
723 SimCtx *simCtx = NULL;
724 UserCtx *user = NULL;
725 Cmpnts ***grad = NULL;
726 PetscReal *velocity = NULL;
727 PetscReal *diffusivity = NULL;
728 PetscReal *diffusivity_gradient = NULL;
729
730 PetscFunctionBeginUser;
731 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
733 PetscCall(PicurvCreateSwarmPair(user, 1, "ske"));
734 PetscCall(VecSet(user->Ucat, 2.0));
735 PetscCall(VecSet(user->Diffusivity, 0.25));
736
737 PetscCall(DMDAVecGetArray(user->fda, user->DiffusivityGradient, &grad));
738 for (PetscInt k = user->info.zs; k < user->info.zs + user->info.zm; ++k) {
739 for (PetscInt j = user->info.ys; j < user->info.ys + user->info.ym; ++j) {
740 for (PetscInt i = user->info.xs; i < user->info.xs + user->info.xm; ++i) {
741 grad[k][j][i].x = 0.1;
742 grad[k][j][i].y = 0.2;
743 grad[k][j][i].z = 0.3;
744 }
745 }
746 }
747 PetscCall(DMDAVecRestoreArray(user->fda, user->DiffusivityGradient, &grad));
748 PetscCall(SyncRuntimeFieldGhosts(user));
749 PetscCall(SeedSingleParticle(user, 0, 0, 0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, ACTIVE_AND_LOCATED));
750
751 PetscCall(InterpolateAllFieldsToSwarm(user));
752
753 PetscCall(DMSwarmGetField(user->swarm, "velocity", NULL, NULL, (void **)&velocity));
754 PetscCall(DMSwarmGetField(user->swarm, "Diffusivity", NULL, NULL, (void **)&diffusivity));
755 PetscCall(DMSwarmGetField(user->swarm, "DiffusivityGradient", NULL, NULL, (void **)&diffusivity_gradient));
756 PetscCall(PicurvAssertRealNear(2.0, velocity[0], 1.0e-12, "CornerAveraged: interpolated velocity x should match constant Eulerian field"));
757 PetscCall(PicurvAssertRealNear(2.0, velocity[1], 1.0e-12, "CornerAveraged: interpolated velocity y should match constant Eulerian field"));
758 PetscCall(PicurvAssertRealNear(2.0, velocity[2], 1.0e-12, "CornerAveraged: interpolated velocity z should match constant Eulerian field"));
759 PetscCall(PicurvAssertRealNear(0.25, diffusivity[0], 1.0e-12, "CornerAveraged: interpolated scalar diffusivity should match constant Eulerian field"));
760 PetscCall(PicurvAssertRealNear(0.1, diffusivity_gradient[0], 1.0e-12, "CornerAveraged: interpolated diffusivity-gradient x component"));
761 PetscCall(PicurvAssertRealNear(0.2, diffusivity_gradient[1], 1.0e-12, "CornerAveraged: interpolated diffusivity-gradient y component"));
762 PetscCall(PicurvAssertRealNear(0.3, diffusivity_gradient[2], 1.0e-12, "CornerAveraged: interpolated diffusivity-gradient z component"));
763 PetscCall(DMSwarmRestoreField(user->swarm, "DiffusivityGradient", NULL, NULL, (void **)&diffusivity_gradient));
764 PetscCall(DMSwarmRestoreField(user->swarm, "Diffusivity", NULL, NULL, (void **)&diffusivity));
765 PetscCall(DMSwarmRestoreField(user->swarm, "velocity", NULL, NULL, (void **)&velocity));
766
767 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
768 PetscFunctionReturn(0);
769}
770/**
771 * @brief Tests particle-to-grid scattering using known cell occupancy and scalar values.
772 */
773
775{
776 SimCtx *simCtx = NULL;
777 UserCtx *user = NULL;
778 PetscInt *cell_ids = NULL;
779 PetscReal *psi = NULL;
780 PetscReal ***psi_grid = NULL;
781
782 PetscFunctionBeginUser;
783 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
784 PetscCall(PicurvCreateSwarmPair(user, 2, "ske"));
785
786 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
787 cell_ids[0] = 0; cell_ids[1] = 0; cell_ids[2] = 0;
788 cell_ids[3] = 0; cell_ids[4] = 0; cell_ids[5] = 0;
789 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
790
791 PetscCall(DMSwarmGetField(user->swarm, "Psi", NULL, NULL, (void **)&psi));
792 psi[0] = 1.0;
793 psi[1] = 3.0;
794 PetscCall(DMSwarmRestoreField(user->swarm, "Psi", NULL, NULL, (void **)&psi));
795
797
798 PetscCall(DMDAVecGetArrayRead(user->da, user->Psi, &psi_grid));
799 PetscCall(PicurvAssertRealNear(2.0, psi_grid[1][1][1], 1.0e-12, "Scatter should average particle Psi values into the owning cell"));
800 PetscCall(DMDAVecRestoreArrayRead(user->da, user->Psi, &psi_grid));
801
802 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
803 PetscFunctionReturn(0);
804}
805/**
806 * @brief Tests particle counting by geometric cell IDs using the production +1 storage shift.
807 */
808
810{
811 SimCtx *simCtx = NULL;
812 UserCtx *user = NULL;
813 PetscInt *cell_ids = NULL;
814 PetscReal ***counts = NULL;
815
816 PetscFunctionBeginUser;
817 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
818 PetscCall(PicurvCreateSwarmPair(user, 3, "ske"));
819
820 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
821 cell_ids[0] = 0; cell_ids[1] = 0; cell_ids[2] = 0;
822 cell_ids[3] = 0; cell_ids[4] = 0; cell_ids[5] = 0;
823 cell_ids[6] = 1; cell_ids[7] = 0; cell_ids[8] = 0;
824 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
825
826 PetscCall(CalculateParticleCountPerCell(user));
827
828 PetscCall(DMDAVecGetArrayRead(user->da, user->ParticleCount, &counts));
829 PetscCall(PicurvAssertRealNear(2.0, counts[1][1][1], 1.0e-12, "Two particles in cell (0,0,0) should accumulate at shifted index (1,1,1)"));
830 PetscCall(PicurvAssertRealNear(1.0, counts[1][1][2], 1.0e-12, "One particle in cell (1,0,0) should accumulate at shifted index (2,1,1)"));
831 PetscCall(DMDAVecRestoreArrayRead(user->da, user->ParticleCount, &counts));
832
833 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
834 PetscFunctionReturn(0);
835}
836/**
837 * @brief Tests localized particle-status reset behavior for restart of the location workflow.
838 */
839
841{
842 SimCtx *simCtx = NULL;
843 UserCtx *user = NULL;
844 PetscInt *status = NULL;
845
846 PetscFunctionBeginUser;
847 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
848 PetscCall(PicurvCreateSwarmPair(user, 3, "ske"));
849
850 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
851 status[0] = ACTIVE_AND_LOCATED;
852 status[1] = LOST;
853 status[2] = NEEDS_LOCATION;
854 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
855
856 PetscCall(ResetAllParticleStatuses(user));
857
858 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
859 PetscCall(PicurvAssertIntEqual(NEEDS_LOCATION, status[0], "ACTIVE_AND_LOCATED particles should be reset to NEEDS_LOCATION"));
860 PetscCall(PicurvAssertIntEqual(LOST, status[1], "LOST particles should remain LOST"));
861 PetscCall(PicurvAssertIntEqual(NEEDS_LOCATION, status[2], "NEEDS_LOCATION particles should remain unchanged"));
862 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
863
864 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
865 PetscFunctionReturn(0);
866}
867/**
868 * @brief Tests direct removal of particles that leave every rank bounding box.
869 */
870
872{
873 SimCtx *simCtx = NULL;
874 UserCtx *user = NULL;
875 PetscReal *positions = NULL;
876 PetscInt removed_local = 0;
877 PetscInt removed_global = 0;
878 PetscInt nlocal = 0;
879
880 PetscFunctionBeginUser;
881 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
882 PetscCall(PicurvCreateSwarmPair(user, 2, "ske"));
883
884 PetscCall(DMSwarmGetField(user->swarm, "position", NULL, NULL, (void **)&positions));
885 positions[0] = 0.5; positions[1] = 0.5; positions[2] = 0.5;
886 positions[3] = 9.0; positions[4] = 9.0; positions[5] = 9.0;
887 PetscCall(DMSwarmRestoreField(user->swarm, "position", NULL, NULL, (void **)&positions));
888
889 PetscCall(CheckAndRemoveOutOfBoundsParticles(user, &removed_local, &removed_global, simCtx->bboxlist));
890 PetscCall(DMSwarmGetLocalSize(user->swarm, &nlocal));
891 PetscCall(PicurvAssertIntEqual(1, removed_local, "Exactly one particle should be removed as out-of-bounds on a single rank"));
892 PetscCall(PicurvAssertIntEqual(1, removed_global, "Global out-of-bounds removal count should match the local single-rank result"));
893 PetscCall(PicurvAssertIntEqual(1, nlocal, "One in-bounds particle should remain after out-of-bounds removal"));
894
895 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
896 PetscFunctionReturn(0);
897}
898/**
899 * @brief Tests direct removal of particles already marked LOST by the location workflow.
900 */
901
903{
904 SimCtx *simCtx = NULL;
905 UserCtx *user = NULL;
906 PetscInt *status = NULL;
907 PetscInt removed_local = 0;
908 PetscInt removed_global = 0;
909 PetscInt nlocal = 0;
910
911 PetscFunctionBeginUser;
912 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
913 PetscCall(PicurvCreateSwarmPair(user, 3, "ske"));
914
915 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
916 status[0] = ACTIVE_AND_LOCATED;
917 status[1] = LOST;
918 status[2] = LOST;
919 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
920
921 PetscCall(CheckAndRemoveLostParticles(user, &removed_local, &removed_global));
922 PetscCall(DMSwarmGetLocalSize(user->swarm, &nlocal));
923 PetscCall(PicurvAssertIntEqual(2, removed_local, "Two LOST particles should be removed locally"));
924 PetscCall(PicurvAssertIntEqual(2, removed_global, "Global LOST-particle removal count should match the local single-rank result"));
925 PetscCall(PicurvAssertIntEqual(1, nlocal, "One non-LOST particle should remain"));
926
927 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
928 PetscFunctionReturn(0);
929}
930/**
931 * @brief Tests Brownian displacement generation against a duplicated seeded RNG stream.
932 */
933
935{
936 SimCtx *simCtx = NULL;
937 UserCtx *user = NULL;
938 char tmpdir[PETSC_MAX_PATH_LEN];
939 Cmpnts first;
940 Cmpnts second;
941
942 PetscFunctionBeginUser;
943 PetscCall(PicurvBuildTinyRuntimeContext(NULL, PETSC_FALSE, &simCtx, &user, tmpdir, sizeof(tmpdir)));
944 simCtx->dt = 0.25;
945 PetscCall(PicurvAssertBool((PetscBool)(simCtx->BrownianMotionRNG != NULL),
946 "runtime setup path should initialize the Brownian RNG"));
947
948 PetscCall(PetscRandomSetSeed(simCtx->BrownianMotionRNG, 12345));
949 PetscCall(PetscRandomSeed(simCtx->BrownianMotionRNG));
950
951 PetscCall(CalculateBrownianDisplacement(user, 0.5, &first));
952 PetscCall(PetscRandomSetSeed(simCtx->BrownianMotionRNG, 12345));
953 PetscCall(PetscRandomSeed(simCtx->BrownianMotionRNG));
954 PetscCall(CalculateBrownianDisplacement(user, 0.5, &second));
955
956 PetscCall(PicurvAssertRealNear(first.x, second.x, 1.0e-12, "Resetting the Brownian RNG seed should reproduce the x displacement"));
957 PetscCall(PicurvAssertRealNear(first.y, second.y, 1.0e-12, "Resetting the Brownian RNG seed should reproduce the y displacement"));
958 PetscCall(PicurvAssertRealNear(first.z, second.z, 1.0e-12, "Resetting the Brownian RNG seed should reproduce the z displacement"));
959
960 PetscCall(PicurvDestroyRuntimeContext(&simCtx));
961 PetscCall(PicurvRemoveTempDir(tmpdir));
962 PetscFunctionReturn(0);
963}
964/**
965 * @brief Tests swarm-wide particle position updates using the same transport path as the runtime loop.
966 */
968{
969 SimCtx *simCtx = NULL;
970 UserCtx *user = NULL;
971 PetscReal *positions = NULL;
972 PetscReal *velocities = NULL;
973 PetscReal *diffusivity = NULL;
974 Cmpnts *diffusivity_gradient = NULL;
975 PetscReal *psi = NULL;
976 PetscReal *weights = NULL;
977 PetscInt *cell_ids = NULL;
978 PetscInt *status = NULL;
979 PetscInt64 *pid = NULL;
980
981 PetscFunctionBeginUser;
982 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
983 PetscCall(PicurvCreateSwarmPair(user, 1, "ske"));
984 simCtx->dt = 0.25;
985
986 PetscCall(DMSwarmGetField(user->swarm, "position", NULL, NULL, (void **)&positions));
987 PetscCall(DMSwarmGetField(user->swarm, "velocity", NULL, NULL, (void **)&velocities));
988 PetscCall(DMSwarmGetField(user->swarm, "Diffusivity", NULL, NULL, (void **)&diffusivity));
989 PetscCall(DMSwarmGetField(user->swarm, "DiffusivityGradient", NULL, NULL, (void **)&diffusivity_gradient));
990 PetscCall(DMSwarmGetField(user->swarm, "Psi", NULL, NULL, (void **)&psi));
991 PetscCall(DMSwarmGetField(user->swarm, "weight", NULL, NULL, (void **)&weights));
992 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
993 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
994 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_pid", NULL, NULL, (void **)&pid));
995
996 positions[0] = 0.20; positions[1] = 0.30; positions[2] = 0.40;
997 velocities[0] = 0.40; velocities[1] = -0.20; velocities[2] = 0.10;
998 diffusivity[0] = 0.0;
999 diffusivity_gradient[0].x = 0.10;
1000 diffusivity_gradient[0].y = 0.20;
1001 diffusivity_gradient[0].z = -0.10;
1002 psi[0] = 0.5;
1003 weights[0] = 0.5; weights[1] = 0.5; weights[2] = 0.5;
1004 cell_ids[0] = 0; cell_ids[1] = 0; cell_ids[2] = 0;
1005 status[0] = ACTIVE_AND_LOCATED;
1006 pid[0] = 7;
1007
1008 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_pid", NULL, NULL, (void **)&pid));
1009 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
1010 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
1011 PetscCall(DMSwarmRestoreField(user->swarm, "weight", NULL, NULL, (void **)&weights));
1012 PetscCall(DMSwarmRestoreField(user->swarm, "Psi", NULL, NULL, (void **)&psi));
1013 PetscCall(DMSwarmRestoreField(user->swarm, "DiffusivityGradient", NULL, NULL, (void **)&diffusivity_gradient));
1014 PetscCall(DMSwarmRestoreField(user->swarm, "Diffusivity", NULL, NULL, (void **)&diffusivity));
1015 PetscCall(DMSwarmRestoreField(user->swarm, "velocity", NULL, NULL, (void **)&velocities));
1016 PetscCall(DMSwarmRestoreField(user->swarm, "position", NULL, NULL, (void **)&positions));
1017
1018 PetscCall(UpdateAllParticlePositions(user));
1019
1020 PetscCall(DMSwarmGetField(user->swarm, "position", NULL, NULL, (void **)&positions));
1021 PetscCall(PicurvAssertRealNear(0.325, positions[0], 1.0e-12, "UpdateAllParticlePositions should advect x"));
1022 PetscCall(PicurvAssertRealNear(0.300, positions[1], 1.0e-12, "UpdateAllParticlePositions should advect y"));
1023 PetscCall(PicurvAssertRealNear(0.400, positions[2], 1.0e-12, "UpdateAllParticlePositions should advect z"));
1024 PetscCall(DMSwarmRestoreField(user->swarm, "position", NULL, NULL, (void **)&positions));
1025
1026 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1027 PetscFunctionReturn(0);
1028}
1029/**
1030 * @brief Tests the location orchestrator fast path when a particle already carries a valid prior cell.
1031 */
1033{
1034 SimCtx *simCtx = NULL;
1035 UserCtx *user = NULL;
1036 PetscReal *positions = NULL;
1037 PetscReal *weights = NULL;
1038 PetscInt *cell_ids = NULL;
1039 PetscInt *status = NULL;
1040 PetscInt64 *pid = NULL;
1041
1042 PetscFunctionBeginUser;
1043 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
1044 PetscCall(PetscMalloc1(simCtx->size, &user->RankCellInfoMap));
1045 PetscCall(GetOwnedCellRange(&user->info, 0, &user->RankCellInfoMap[0].xs_cell, &user->RankCellInfoMap[0].xm_cell));
1046 PetscCall(GetOwnedCellRange(&user->info, 1, &user->RankCellInfoMap[0].ys_cell, &user->RankCellInfoMap[0].ym_cell));
1047 PetscCall(GetOwnedCellRange(&user->info, 2, &user->RankCellInfoMap[0].zs_cell, &user->RankCellInfoMap[0].zm_cell));
1048 PetscCall(PicurvCreateSwarmPair(user, 1, "ske"));
1049
1050 PetscCall(DMSwarmGetField(user->swarm, "position", NULL, NULL, (void **)&positions));
1051 PetscCall(DMSwarmGetField(user->swarm, "weight", NULL, NULL, (void **)&weights));
1052 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
1053 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
1054 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_pid", NULL, NULL, (void **)&pid));
1055 positions[0] = 0.375; positions[1] = 0.375; positions[2] = 0.375;
1056 weights[0] = 0.5; weights[1] = 0.5; weights[2] = 0.5;
1057 cell_ids[0] = 1; cell_ids[1] = 1; cell_ids[2] = 1;
1058 status[0] = NEEDS_LOCATION;
1059 pid[0] = 11;
1060 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_pid", NULL, NULL, (void **)&pid));
1061 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
1062 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
1063 PetscCall(DMSwarmRestoreField(user->swarm, "weight", NULL, NULL, (void **)&weights));
1064 PetscCall(DMSwarmRestoreField(user->swarm, "position", NULL, NULL, (void **)&positions));
1065
1066 PetscCall(LocateAllParticlesInGrid(user, simCtx->bboxlist));
1067
1068 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
1069 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
1070 PetscCall(PicurvAssertIntEqual(1, cell_ids[0], "prior-cell fast path should preserve the i cell id"));
1071 PetscCall(PicurvAssertIntEqual(1, cell_ids[1], "prior-cell fast path should preserve the j cell id"));
1072 PetscCall(PicurvAssertIntEqual(1, cell_ids[2], "prior-cell fast path should preserve the k cell id"));
1073 PetscCall(PicurvAssertIntEqual(ACTIVE_AND_LOCATED, status[0], "prior-cell fast path should mark the particle ACTIVE_AND_LOCATED"));
1074 PetscCall(PicurvAssertIntEqual(1, simCtx->searchMetrics.searchAttempts, "prior-cell fast path should record one search attempt"));
1075 PetscCall(PicurvAssertIntEqual(1, (PetscInt)simCtx->searchMetrics.searchPopulation, "prior-cell fast path should record one input particle"));
1076 PetscCall(PicurvAssertIntEqual(1, (PetscInt)simCtx->searchMetrics.searchLocatedCount, "prior-cell fast path should count one located particle"));
1077 PetscCall(PicurvAssertIntEqual(0, (PetscInt)simCtx->searchMetrics.searchLostCount, "prior-cell fast path should not lose the particle"));
1078 PetscCall(PicurvAssertIntEqual(0, (PetscInt)simCtx->searchMetrics.reSearchCount, "prior-cell fast path should not re-search on later passes"));
1079 PetscCall(PicurvAssertBool((PetscBool)(simCtx->searchMetrics.traversalStepsSum > 0), "prior-cell fast path should accumulate traversal steps"));
1080 PetscCall(PicurvAssertIntEqual(1, simCtx->searchMetrics.maxParticlePassDepth, "prior-cell fast path should report one settlement pass"));
1081 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
1082 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
1083
1084 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1085 PetscFunctionReturn(0);
1086}
1087/**
1088 * @brief Tests the guess-then-verify orchestrator path for a local particle with an unknown prior cell.
1089 */
1091{
1092 SimCtx *simCtx = NULL;
1093 UserCtx *user = NULL;
1094 PetscReal *positions = NULL;
1095 PetscReal *weights = NULL;
1096 PetscInt *cell_ids = NULL;
1097 PetscInt *status = NULL;
1098 PetscInt64 *pid = NULL;
1099
1100 PetscFunctionBeginUser;
1101 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
1102 PetscCall(PetscMalloc1(simCtx->size, &user->RankCellInfoMap));
1103 PetscCall(GetOwnedCellRange(&user->info, 0, &user->RankCellInfoMap[0].xs_cell, &user->RankCellInfoMap[0].xm_cell));
1104 PetscCall(GetOwnedCellRange(&user->info, 1, &user->RankCellInfoMap[0].ys_cell, &user->RankCellInfoMap[0].ym_cell));
1105 PetscCall(GetOwnedCellRange(&user->info, 2, &user->RankCellInfoMap[0].zs_cell, &user->RankCellInfoMap[0].zm_cell));
1106 PetscCall(PicurvCreateSwarmPair(user, 1, "ske"));
1107
1108 PetscCall(DMSwarmGetField(user->swarm, "position", NULL, NULL, (void **)&positions));
1109 PetscCall(DMSwarmGetField(user->swarm, "weight", NULL, NULL, (void **)&weights));
1110 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
1111 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
1112 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_pid", NULL, NULL, (void **)&pid));
1113 positions[0] = 0.625; positions[1] = 0.625; positions[2] = 0.625;
1114 weights[0] = 0.5; weights[1] = 0.5; weights[2] = 0.5;
1115 cell_ids[0] = -1; cell_ids[1] = -1; cell_ids[2] = -1;
1116 status[0] = NEEDS_LOCATION;
1117 pid[0] = 22;
1118 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_pid", NULL, NULL, (void **)&pid));
1119 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
1120 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
1121 PetscCall(DMSwarmRestoreField(user->swarm, "weight", NULL, NULL, (void **)&weights));
1122 PetscCall(DMSwarmRestoreField(user->swarm, "position", NULL, NULL, (void **)&positions));
1123
1124 PetscCall(LocateAllParticlesInGrid(user, simCtx->bboxlist));
1125
1126 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
1127 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
1128 PetscCall(PicurvAssertIntEqual(2, cell_ids[0], "guess-path location should resolve the i cell id"));
1129 PetscCall(PicurvAssertIntEqual(2, cell_ids[1], "guess-path location should resolve the j cell id"));
1130 PetscCall(PicurvAssertIntEqual(2, cell_ids[2], "guess-path location should resolve the k cell id"));
1131 PetscCall(PicurvAssertIntEqual(ACTIVE_AND_LOCATED, status[0], "guess-path location should mark the particle ACTIVE_AND_LOCATED"));
1132 PetscCall(PicurvAssertIntEqual(1, simCtx->searchMetrics.searchAttempts, "guess-path location should perform one robust search"));
1133 PetscCall(PicurvAssertIntEqual(1, (PetscInt)simCtx->searchMetrics.searchPopulation, "guess-path location should count one input particle"));
1134 PetscCall(PicurvAssertIntEqual(1, (PetscInt)simCtx->searchMetrics.searchLocatedCount, "guess-path location should count one located particle"));
1135 PetscCall(PicurvAssertIntEqual(0, (PetscInt)simCtx->searchMetrics.searchLostCount, "guess-path location should not lose the particle"));
1136 PetscCall(PicurvAssertIntEqual(0, (PetscInt)simCtx->searchMetrics.reSearchCount, "guess-path location should not count later-pass re-searches"));
1137 PetscCall(PicurvAssertIntEqual(1, simCtx->searchMetrics.bboxGuessFallbackCount, "guess-path location should record one bbox fallback"));
1138 PetscCall(PicurvAssertIntEqual(0, simCtx->searchMetrics.bboxGuessSuccessCount, "guess-path local resolution should not count as remote bbox success"));
1139 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
1140 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
1141
1142 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1143 PetscFunctionReturn(0);
1144}
1145/**
1146 * @brief Verifies that later settlement passes increment re-search metrics.
1147 */
1149{
1150 SimCtx *simCtx = NULL;
1151 UserCtx *user = NULL;
1152 Particle particle;
1154
1155 PetscFunctionBeginUser;
1156 PetscCall(PetscMemzero(&particle, sizeof(particle)));
1157 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
1158 PetscCall(PetscMalloc1(simCtx->size, &user->RankCellInfoMap));
1159 PetscCall(GetOwnedCellRange(&user->info, 0, &user->RankCellInfoMap[0].xs_cell, &user->RankCellInfoMap[0].xm_cell));
1160 PetscCall(GetOwnedCellRange(&user->info, 1, &user->RankCellInfoMap[0].ys_cell, &user->RankCellInfoMap[0].ym_cell));
1161 PetscCall(GetOwnedCellRange(&user->info, 2, &user->RankCellInfoMap[0].zs_cell, &user->RankCellInfoMap[0].zm_cell));
1162
1164 particle.PID = 33;
1165 particle.cell[0] = 1;
1166 particle.cell[1] = 1;
1167 particle.cell[2] = 1;
1168 particle.loc.x = 0.375;
1169 particle.loc.y = 0.375;
1170 particle.loc.z = 0.375;
1171 particle.weights.x = 0.5;
1172 particle.weights.y = 0.5;
1173 particle.weights.z = 0.5;
1174
1175 PetscCall(LocateParticleOrFindMigrationTarget(user, &particle, &status));
1176
1177 PetscCall(PicurvAssertIntEqual(ACTIVE_AND_LOCATED, status, "direct re-search test should locate the particle"));
1178 PetscCall(PicurvAssertIntEqual(1, (PetscInt)simCtx->searchMetrics.searchAttempts, "direct re-search test should record one robust walk"));
1179 PetscCall(PicurvAssertIntEqual(1, (PetscInt)simCtx->searchMetrics.reSearchCount, "direct re-search test should increment re_search_count on later passes"));
1180 PetscCall(PicurvAssertIntEqual(0, (PetscInt)simCtx->searchMetrics.maxTraversalFailCount, "direct re-search test should not hit MAX_TRAVERSAL"));
1181
1182 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1183 PetscFunctionReturn(0);
1184}
1185/**
1186 * @brief Tests no-slip and free-slip wall helper kernels.
1187 */
1188
1189static PetscErrorCode TestWallNoSlipAndFreeSlipHelpers(void)
1190{
1191 Cmpnts wall_velocity = {0.0, 0.0, 0.0};
1192 Cmpnts reference_velocity = {2.0, 4.0, 6.0};
1193 Cmpnts boundary_velocity = {0.0, 0.0, 0.0};
1194 Cmpnts free_slip_reference = {2.0, 3.0, 4.0};
1195
1196 PetscFunctionBeginUser;
1197 noslip(NULL, 2.0, 1.0, wall_velocity, reference_velocity, &boundary_velocity, 1.0, 0.0, 0.0);
1198 PetscCall(PicurvAssertRealNear(1.0, boundary_velocity.x, 1.0e-12, "no-slip interpolated x"));
1199 PetscCall(PicurvAssertRealNear(2.0, boundary_velocity.y, 1.0e-12, "no-slip interpolated y"));
1200 PetscCall(PicurvAssertRealNear(3.0, boundary_velocity.z, 1.0e-12, "no-slip interpolated z"));
1201
1202 freeslip(NULL, 2.0, 1.0, wall_velocity, free_slip_reference, &boundary_velocity, 1.0, 0.0, 0.0);
1203 PetscCall(PicurvAssertRealNear(1.0, boundary_velocity.x, 1.0e-12, "free-slip interpolated normal component"));
1204 PetscCall(PicurvAssertRealNear(3.0, boundary_velocity.y, 1.0e-12, "free-slip tangential y preserved"));
1205 PetscCall(PicurvAssertRealNear(4.0, boundary_velocity.z, 1.0e-12, "free-slip tangential z preserved"));
1206 PetscFunctionReturn(0);
1207}
1208/**
1209 * @brief Tests wall-model scalar helper kernels.
1210 */
1211
1212static PetscErrorCode TestWallModelScalarHelpers(void)
1213{
1214 const PetscReal expected_smooth_e = PetscExpReal(0.41 * 5.5);
1215 PetscReal e_coeff = 0.0;
1216 PetscReal utau = 0.0;
1217 PetscReal residual = 0.0;
1218
1219 PetscFunctionBeginUser;
1220 e_coeff = E_coeff(0.1, 0.0, 1.0e-3);
1221 PetscCall(PicurvAssertRealNear(expected_smooth_e, e_coeff, 1.0e-10, "smooth-wall E coefficient"));
1222
1223 utau = find_utau_hydset(1.0e-3, 1.0, 1.0e-2, 0.1, 0.0);
1224 PetscCall(PicurvAssertBool((PetscBool)(utau > 0.0), "friction velocity should remain positive"));
1225 residual = f_hydset(1.0e-3, 1.0, 1.0e-2, utau, 0.0);
1226 PetscCall(PicurvAssertBool((PetscBool)(PetscAbsReal(residual) < 1.0e-5), "Newton solve residual should be small"));
1227
1228 PetscCall(PicurvAssertRealNear(0.0, nu_t(0.0), 1.0e-12, "eddy viscosity ratio at wall"));
1229 PetscCall(PicurvAssertBool((PetscBool)(integrate_1(1.0e-3, 1.0e-2, 0.1, 0) > 0.0), "integral helper should be positive"));
1230 PetscFunctionReturn(0);
1231}
1232/**
1233 * @brief Tests closed-form and iterative wall-model velocity helpers against inverse reconstructions.
1234 */
1235
1236static PetscErrorCode TestWallModelVelocityHelpers(void)
1237{
1238 const PetscReal kinematic_viscosity = 1.0e-3;
1239 const PetscReal wall_distance = 2.0e-2;
1240 const PetscReal target_velocity = 1.0;
1241 const PetscReal roughness_length = 1.0e-4;
1242 PetscReal utau_loglaw = 0.0;
1243 PetscReal utau_werner = 0.0;
1244 PetscReal utau_cabot = 0.0;
1245 PetscReal wall_shear_velocity = 0.0;
1246 PetscReal wall_shear_normal = 0.0;
1247
1248 PetscFunctionBeginUser;
1249 utau_loglaw = find_utau_loglaw(target_velocity, wall_distance, roughness_length);
1250 PetscCall(PicurvAssertRealNear(target_velocity, u_loglaw(wall_distance, utau_loglaw, roughness_length), 1.0e-12,
1251 "simple log-law inversion should reconstruct the target velocity"));
1252
1253 utau_werner = find_utau_Werner(kinematic_viscosity, target_velocity, wall_distance, 0.1);
1254 PetscCall(PicurvAssertBool((PetscBool)(utau_werner > 0.0), "Werner-Wengle friction velocity should remain positive"));
1255 PetscCall(PicurvAssertRealNear(target_velocity, u_Werner(kinematic_viscosity, wall_distance, utau_werner), 1.0e-6,
1256 "Werner-Wengle inversion should reconstruct the target velocity"));
1257
1258 find_utau_Cabot(kinematic_viscosity, target_velocity, wall_distance, 0.1, 0.0, 0.0,
1259 &utau_cabot, &wall_shear_velocity, &wall_shear_normal);
1260 PetscCall(PicurvAssertBool((PetscBool)(utau_cabot > 0.0), "Cabot friction velocity should remain positive"));
1261 PetscCall(PicurvAssertRealNear(target_velocity, u_Cabot(kinematic_viscosity, wall_distance, utau_cabot, 0.0, wall_shear_velocity), 1.0e-6,
1262 "Cabot inversion should reconstruct the target velocity when pressure gradient is zero"));
1263 PetscCall(PicurvAssertRealNear(0.0, wall_shear_normal, 1.0e-10,
1264 "zero normal pressure gradient should keep Cabot normal wall shear at zero"));
1265 PetscFunctionReturn(0);
1266}
1267/**
1268 * @brief Tests the vector wall-function wrappers on a tangential reference flow.
1269 */
1270static PetscErrorCode TestWallFunctionVectorWrappers(void)
1271{
1272 SimCtx *simCtx = NULL;
1273 UserCtx *user = NULL;
1274 Cmpnts wall_velocity = {0.0, 0.0, 0.0};
1275 Cmpnts reference_velocity = {0.0, 1.0, 0.0};
1276 Cmpnts boundary_velocity = {0.0, 0.0, 0.0};
1277 PetscReal friction_velocity = 0.0;
1278
1279 PetscFunctionBeginUser;
1280 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
1281 simCtx->ren = 1000.0;
1282
1283 wall_function(user, 2.0e-2, 1.0e-2, wall_velocity, reference_velocity, &boundary_velocity, &friction_velocity, 1.0, 0.0, 0.0);
1284 PetscCall(PicurvAssertRealNear(0.0, boundary_velocity.x, 1.0e-12, "Werner wall function should preserve zero normal velocity"));
1285 PetscCall(PicurvAssertBool((PetscBool)(boundary_velocity.y > 0.0 && boundary_velocity.y < 1.0), "Werner wall function should damp tangential velocity"));
1286 PetscCall(PicurvAssertBool((PetscBool)(friction_velocity > 0.0), "Werner wall function should compute positive friction velocity"));
1287
1288 wall_function_loglaw(user, 1.0e-4, 2.0e-2, 1.0e-2, wall_velocity, reference_velocity, &boundary_velocity, &friction_velocity, 1.0, 0.0, 0.0);
1289 PetscCall(PicurvAssertRealNear(0.0, boundary_velocity.x, 1.0e-12, "log-law wall function should preserve zero normal velocity"));
1290 PetscCall(PicurvAssertBool((PetscBool)(boundary_velocity.y > 0.0 && boundary_velocity.y <= 1.0), "log-law wall function should keep tangential velocity bounded"));
1291 PetscCall(PicurvAssertBool((PetscBool)(friction_velocity > 0.0), "log-law wall function should compute positive friction velocity"));
1292
1293 wall_function_Cabot(user, 1.0e-4, 2.0e-2, 1.0e-2, wall_velocity, reference_velocity, &boundary_velocity, &friction_velocity,
1294 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10);
1295 PetscCall(PicurvAssertRealNear(0.0, boundary_velocity.x, 1.0e-12, "Cabot wall function should preserve zero normal velocity"));
1296 PetscCall(PicurvAssertBool((PetscBool)(boundary_velocity.y > 0.0 && boundary_velocity.y <= 1.0), "Cabot wall function should keep tangential velocity bounded"));
1297 PetscCall(PicurvAssertBool((PetscBool)(friction_velocity > 0.0), "Cabot wall function should compute positive friction velocity"));
1298
1299 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1300 PetscFunctionReturn(0);
1301}
1302/**
1303 * @brief Tests driven-flow validation when no driven handlers are present.
1304 */
1305
1307{
1308 SimCtx *simCtx = NULL;
1309 UserCtx *user = NULL;
1310
1311 PetscFunctionBeginUser;
1312 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
1313 PetscCall(Validate_DrivenFlowConfiguration(user));
1314 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1315 PetscFunctionReturn(0);
1316}
1317/**
1318 * @brief Tests the constant Smagorinsky model helper path.
1319 */
1320
1322{
1323 SimCtx *simCtx = NULL;
1324 UserCtx *user = NULL;
1325 PetscReal ***lcs = NULL;
1326
1327 PetscFunctionBeginUser;
1328 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
1329 PetscCall(DMCreateGlobalVector(user->da, &user->CS));
1330 PetscCall(DMCreateLocalVector(user->da, &user->lCs));
1331 simCtx->step = 2;
1332 simCtx->StartStep = 0;
1333 simCtx->les = CONSTANT_SMAGORINSKY;
1334 simCtx->Const_CS = 0.17;
1335
1336 PetscCall(ComputeSmagorinskyConstant(user));
1337 PetscCall(PicurvAssertVecConstant(user->CS, 0.17, 1.0e-12, "constant Smagorinsky branch should fill CS"));
1338 PetscCall(DMDAVecGetArrayRead(user->da, user->lCs, &lcs));
1339 PetscCall(PicurvAssertRealNear(0.17, lcs[2][2][2], 1.0e-12,
1340 "constant Smagorinsky branch should refresh local CS"));
1341 PetscCall(DMDAVecRestoreArrayRead(user->da, user->lCs, &lcs));
1342
1343 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1344 PetscFunctionReturn(0);
1345}
1346/**
1347 * @brief Tests that the shared minimal fixture mirrors the production DA contract.
1348 */
1349
1351{
1352 SimCtx *simCtx = NULL;
1353 UserCtx *user = NULL;
1354 DM coord_dm = NULL;
1355 PetscInt mx = 0, my = 0, mz = 0;
1356
1357 PetscFunctionBeginUser;
1358 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 8, 6, 4));
1359
1360 PetscCall(DMDAGetInfo(user->da, NULL, &mx, &my, &mz, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL));
1361 PetscCall(PicurvAssertIntEqual(user->IM + 1, mx, "minimal fixture should size da with IM+1 nodes"));
1362 PetscCall(PicurvAssertIntEqual(user->JM + 1, my, "minimal fixture should size da with JM+1 nodes"));
1363 PetscCall(PicurvAssertIntEqual(user->KM + 1, mz, "minimal fixture should size da with KM+1 nodes"));
1364 PetscCall(PicurvAssertIntEqual(mx, user->info.mx, "user->info should be sourced from the production da"));
1365 PetscCall(PicurvAssertIntEqual(my, user->info.my, "user->info my should match the da dimensions"));
1366 PetscCall(PicurvAssertIntEqual(mz, user->info.mz, "user->info mz should match the da dimensions"));
1367
1368 PetscCall(DMGetCoordinateDM(user->da, &coord_dm));
1369 PetscCall(PicurvAssertBool((PetscBool)(coord_dm == user->fda),
1370 "minimal fixture should derive fda from the coordinate-DM path"));
1371
1372 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1373 PetscFunctionReturn(0);
1374}
1375/**
1376 * @brief Tests that the shared swarm fixture registers the production field set.
1377 */
1378
1380{
1381 SimCtx *simCtx = NULL;
1382 UserCtx *user = NULL;
1383 PetscInt bs = 0;
1384 void *field_ptr = NULL;
1385
1386 PetscFunctionBeginUser;
1387 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
1388 PetscCall(PicurvCreateSwarmPair(user, 2, "ske"));
1389
1390 PetscCall(DMSwarmGetField(user->swarm, "position", &bs, NULL, &field_ptr));
1391 PetscCall(PicurvAssertIntEqual(3, bs, "solver swarm should register particle position"));
1392 PetscCall(PicurvAssertBool((PetscBool)(field_ptr != NULL), "position field should be retrievable"));
1393 PetscCall(DMSwarmRestoreField(user->swarm, "position", &bs, NULL, &field_ptr));
1394
1395 PetscCall(DMSwarmGetField(user->swarm, "velocity", &bs, NULL, &field_ptr));
1396 PetscCall(PicurvAssertIntEqual(3, bs, "solver swarm should register particle velocity"));
1397 PetscCall(DMSwarmRestoreField(user->swarm, "velocity", &bs, NULL, &field_ptr));
1398
1399 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_CellID", &bs, NULL, &field_ptr));
1400 PetscCall(PicurvAssertIntEqual(3, bs, "solver swarm should register DMSwarm_CellID"));
1401 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_CellID", &bs, NULL, &field_ptr));
1402
1403 PetscCall(DMSwarmGetField(user->swarm, "weight", &bs, NULL, &field_ptr));
1404 PetscCall(PicurvAssertIntEqual(3, bs, "solver swarm should register particle weight"));
1405 PetscCall(DMSwarmRestoreField(user->swarm, "weight", &bs, NULL, &field_ptr));
1406
1407 PetscCall(DMSwarmGetField(user->swarm, "Diffusivity", &bs, NULL, &field_ptr));
1408 PetscCall(PicurvAssertIntEqual(1, bs, "solver swarm should register particle diffusivity"));
1409 PetscCall(DMSwarmRestoreField(user->swarm, "Diffusivity", &bs, NULL, &field_ptr));
1410
1411 PetscCall(DMSwarmGetField(user->swarm, "DiffusivityGradient", &bs, NULL, &field_ptr));
1412 PetscCall(PicurvAssertIntEqual(3, bs, "solver swarm should register particle diffusivity gradients"));
1413 PetscCall(DMSwarmRestoreField(user->swarm, "DiffusivityGradient", &bs, NULL, &field_ptr));
1414
1415 PetscCall(DMSwarmGetField(user->swarm, "Psi", &bs, NULL, &field_ptr));
1416 PetscCall(PicurvAssertIntEqual(1, bs, "solver swarm should register particle scalar Psi"));
1417 PetscCall(DMSwarmRestoreField(user->swarm, "Psi", &bs, NULL, &field_ptr));
1418
1419 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_location_status", &bs, NULL, &field_ptr));
1420 PetscCall(PicurvAssertIntEqual(1, bs, "solver swarm should register particle location status"));
1421 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_location_status", &bs, NULL, &field_ptr));
1422
1423 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1424 PetscFunctionReturn(0);
1425}
1426/**
1427 * @brief Tests solver history-vector shifting between time levels.
1428 */
1429
1431{
1432 SimCtx *simCtx = NULL;
1433 UserCtx *user = NULL;
1434
1435 PetscFunctionBeginUser;
1436 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 5, 5, 5));
1437
1438 PetscCall(VecSet(user->Ucont, 11.0));
1439 PetscCall(VecSet(user->Ucont_o, 7.0));
1440 PetscCall(VecSet(user->Ucont_rm1, 3.0));
1441 PetscCall(VecSet(user->Ucat, 5.0));
1442 PetscCall(VecSet(user->Ucat_o, -1.0));
1443 PetscCall(VecSet(user->P, 9.0));
1444 PetscCall(VecSet(user->P_o, -2.0));
1445
1446 PetscCall(UpdateSolverHistoryVectors(user, PETSC_FALSE));
1447
1448 PetscCall(PicurvAssertVecConstant(user->Ucont_o, 11.0, 1.0e-12, "Ucont_o should receive current Ucont"));
1449 PetscCall(PicurvAssertVecConstant(user->Ucont_rm1, 7.0, 1.0e-12, "Ucont_rm1 should receive prior Ucont_o"));
1450 PetscCall(PicurvAssertVecConstant(user->Ucat_o, 5.0, 1.0e-12, "Ucat_o should receive current Ucat"));
1451 PetscCall(PicurvAssertVecConstant(user->P_o, 9.0, 1.0e-12, "P_o should receive current P"));
1452 PetscCall(PicurvAssertVecConstant(user->lUcont_o, 11.0, 1.0e-12, "lUcont_o ghost sync should match Ucont_o"));
1453 PetscCall(PicurvAssertVecConstant(user->lUcont_rm1, 7.0, 1.0e-12, "lUcont_rm1 ghost sync should match Ucont_rm1"));
1454
1455 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1456 PetscFunctionReturn(0);
1457}
1458/**
1459 * @brief Tests owned-cell range accounting on a single MPI rank.
1460 */
1461
1463{
1464 SimCtx *simCtx = NULL;
1465 UserCtx *user = NULL;
1466 PetscInt xs = -1, ys = -1, zs = -1;
1467 PetscInt xm = -1, ym = -1, zm = -1;
1468
1469 PetscFunctionBeginUser;
1470 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 8, 6, 4));
1471
1472 PetscCall(GetOwnedCellRange(&user->info, 0, &xs, &xm));
1473 PetscCall(GetOwnedCellRange(&user->info, 1, &ys, &ym));
1474 PetscCall(GetOwnedCellRange(&user->info, 2, &zs, &zm));
1475
1476 PetscCall(PicurvAssertIntEqual(0, xs, "single-rank x cell-start index"));
1477 PetscCall(PicurvAssertIntEqual(0, ys, "single-rank y cell-start index"));
1478 PetscCall(PicurvAssertIntEqual(0, zs, "single-rank z cell-start index"));
1479 PetscCall(PicurvAssertIntEqual(user->info.mx - 2, xm, "single-rank x owned cell count"));
1480 PetscCall(PicurvAssertIntEqual(user->info.my - 2, ym, "single-rank y owned cell count"));
1481 PetscCall(PicurvAssertIntEqual(user->info.mz - 2, zm, "single-rank z owned cell count"));
1482
1483 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1484 PetscFunctionReturn(0);
1485}
1486/**
1487 * @brief Tests neighbor-rank discovery on a single MPI rank.
1488 */
1489
1491{
1492 SimCtx *simCtx = NULL;
1493 UserCtx *user = NULL;
1494
1495 PetscFunctionBeginUser;
1496 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 6, 6, 6));
1497 PetscCall(ComputeAndStoreNeighborRanks(user));
1498
1499 PetscCall(PicurvAssertIntEqual(MPI_PROC_NULL, user->neighbors.rank_xm, "single-rank xm neighbor should be null"));
1500 PetscCall(PicurvAssertIntEqual(MPI_PROC_NULL, user->neighbors.rank_xp, "single-rank xp neighbor should be null"));
1501 PetscCall(PicurvAssertIntEqual(MPI_PROC_NULL, user->neighbors.rank_ym, "single-rank ym neighbor should be null"));
1502 PetscCall(PicurvAssertIntEqual(MPI_PROC_NULL, user->neighbors.rank_yp, "single-rank yp neighbor should be null"));
1503 PetscCall(PicurvAssertIntEqual(MPI_PROC_NULL, user->neighbors.rank_zm, "single-rank zm neighbor should be null"));
1504 PetscCall(PicurvAssertIntEqual(MPI_PROC_NULL, user->neighbors.rank_zp, "single-rank zp neighbor should be null"));
1505
1506 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1507 PetscFunctionReturn(0);
1508}
1509/**
1510 * @brief Tests parsing of positive runtime walltime metadata values.
1511 */
1512
1514{
1515 PetscReal seconds = 0.0;
1516
1517 PetscFunctionBeginUser;
1518 PetscCall(PicurvAssertBool(RuntimeWalltimeGuardParsePositiveSeconds("300", &seconds), "plain positive seconds should parse"));
1519 PetscCall(PicurvAssertRealNear(300.0, seconds, 1.0e-12, "parsed walltime seconds"));
1520 PetscCall(PicurvAssertBool(RuntimeWalltimeGuardParsePositiveSeconds(" 42.5 ", &seconds), "whitespace-wrapped decimal seconds should parse"));
1521 PetscCall(PicurvAssertRealNear(42.5, seconds, 1.0e-12, "parsed decimal walltime seconds"));
1522 PetscCall(PicurvAssertBool((PetscBool)!RuntimeWalltimeGuardParsePositiveSeconds("nope", &seconds), "non-numeric metadata should fail parsing"));
1523 PetscCall(PicurvAssertBool((PetscBool)!RuntimeWalltimeGuardParsePositiveSeconds("-10", &seconds), "negative metadata should fail parsing"));
1524 PetscFunctionReturn(0);
1525}
1526/**
1527 * @brief Tests walltime-guard estimator helper calculations.
1528 */
1529
1531{
1532 PetscReal ewma_fast = 0.0;
1533 PetscReal ewma_slow = 0.0;
1534 PetscReal conservative_fast = 0.0;
1535 PetscReal conservative_slow = 0.0;
1536 PetscReal required_headroom = 0.0;
1537
1538 PetscFunctionBeginUser;
1539 ewma_fast = RuntimeWalltimeGuardUpdateEWMA(PETSC_TRUE, 4.0, 6.0, 0.5);
1540 ewma_slow = RuntimeWalltimeGuardUpdateEWMA(PETSC_TRUE, ewma_fast, 12.0, 0.5);
1541 conservative_fast = RuntimeWalltimeGuardConservativeEstimate(5.0, ewma_fast, 6.0);
1542 conservative_slow = RuntimeWalltimeGuardConservativeEstimate(5.0, ewma_slow, 12.0);
1543 required_headroom = RuntimeWalltimeGuardRequiredHeadroom(8.0, 2.0, conservative_slow);
1544
1545 PetscCall(PicurvAssertRealNear(5.0, ewma_fast, 1.0e-12, "EWMA after moderate step"));
1546 PetscCall(PicurvAssertRealNear(8.5, ewma_slow, 1.0e-12, "EWMA after newer slow step"));
1547 PetscCall(PicurvAssertRealNear(6.0, conservative_fast, 1.0e-12, "conservative estimate tracks latest moderate step"));
1548 PetscCall(PicurvAssertRealNear(12.0, conservative_slow, 1.0e-12, "conservative estimate tracks newest slow step"));
1549 PetscCall(PicurvAssertRealNear(24.0, required_headroom, 1.0e-12, "required headroom scales with conservative estimate"));
1550 PetscFunctionReturn(0);
1551}
1552/**
1553 * @brief Tests runtime walltime-guard shutdown trigger decisions.
1554 */
1555
1557{
1558 PetscBool should_trigger = PETSC_FALSE;
1559 PetscReal required_headroom = 0.0;
1560
1561 PetscFunctionBeginUser;
1562 should_trigger = RuntimeWalltimeGuardShouldTrigger(9, 10, 15.0, 5.0, 2.0, 6.0, 6.0, 6.0, &required_headroom);
1563 PetscCall(PicurvAssertBool((PetscBool)!should_trigger, "guard should not trigger before warmup completes"));
1564
1565 should_trigger = RuntimeWalltimeGuardShouldTrigger(10, 10, 40.0, 5.0, 2.0, 10.0, 12.0, 14.0, &required_headroom);
1566 PetscCall(PicurvAssertBool((PetscBool)!should_trigger, "guard should not trigger when remaining walltime exceeds required headroom"));
1567 PetscCall(PicurvAssertRealNear(28.0, required_headroom, 1.0e-12, "required headroom after warmup"));
1568
1569 should_trigger = RuntimeWalltimeGuardShouldTrigger(10, 10, 28.0, 5.0, 2.0, 10.0, 12.0, 14.0, &required_headroom);
1570 PetscCall(PicurvAssertBool(should_trigger, "guard should trigger when remaining walltime reaches required headroom"));
1571 PetscCall(PicurvAssertRealNear(28.0, required_headroom, 1.0e-12, "required headroom remains unchanged at trigger threshold"));
1572 PetscFunctionReturn(0);
1573}
1574/**
1575 * @brief Runs the unit-runtime PETSc test binary.
1576 */
1577
1578int main(int argc, char **argv)
1579{
1580 PetscErrorCode ierr;
1581 const PicurvTestCase cases[] = {
1582 {"distribute-particles-remainder-handling", TestDistributeParticlesRemainderHandling},
1583 {"is-particle-inside-bbox-basic-cases", TestIsParticleInsideBoundingBoxBasicCases},
1584 {"update-particle-weights-computes-expected-ratios", TestUpdateParticleWeightsComputesExpectedRatios},
1585 {"update-particle-position-without-brownian-contribution", TestUpdateParticlePositionWithoutBrownianContribution},
1586 {"update-particle-position-diffusivity-gradient-only", TestUpdateParticlePositionDiffusivityGradientOnly},
1587 {"update-particle-field-iem-relaxation", TestUpdateParticleFieldIEMRelaxation},
1588 {"set-initial-interior-field-ignores-non-ucont-request", TestSetInitialInteriorFieldIgnoresNonUcontRequest},
1589 {"set-initial-interior-field-cartesian-constant-sets-contravariant-flux", TestSetInitialInteriorFieldCartesianConstantSetsContravariantFlux},
1590 {"set-initial-interior-field-curvilinear-constant-via-flow-direction", TestSetInitialInteriorFieldCurvilinearConstantViaFlowDirection},
1591 {"set-initial-interior-field-zero-clears-interior", TestSetInitialInteriorFieldZeroClearsInterior},
1592 {"set-initial-interior-field-poiseuille-profile", TestSetInitialInteriorFieldPoiseuilleProfile},
1593 {"cart2contra-converts-cartesian-field", TestCart2ContraConvertsCartesianField},
1594 {"cart2contra-uses-finalized-periodic-ucat", TestCart2ContraUsesFinalizedPeriodicUcat},
1595 {"populate-initial-ucont-loads-staged-ucat", TestPopulateInitialUcontLoadsStagedUcat},
1596 {"write-les-fields-copies-owned-periodic-values", TestWriteLESFieldsCopiesOwnedPeriodicValues},
1597 {"staged-ucat-fourier-modes-use-picurv-discrete-symbol", TestStagedUcatFourierModesUsePicurvDiscreteSymbol},
1598 {"populate-initial-ucont-loads-staged-ucont", TestPopulateInitialUcontLoadsStagedUcont},
1599 {"interpolate-all-fields-to-swarm-constant-fields", TestInterpolateAllFieldsToSwarmConstantFields},
1600 {"interpolate-all-fields-to-swarm-corner-averaged-constant-fields", TestInterpolateAllFieldsToSwarmCornerAveragedConstantFields},
1601 {"scatter-all-particle-fields-to-euler-fields-averages-psi", TestScatterAllParticleFieldsToEulerFieldsAveragesPsi},
1602 {"calculate-particle-count-per-cell-counts-global-cell-ids", TestCalculateParticleCountPerCellCountsGlobalCellIDs},
1603 {"reset-all-particle-statuses-leaves-lost-particles-untouched", TestResetAllParticleStatusesLeavesLostParticlesUntouched},
1604 {"check-and-remove-out-of-bounds-particles-removes-escaped-particle", TestCheckAndRemoveOutOfBoundsParticlesRemovesEscapedParticle},
1605 {"check-and-remove-lost-particles-removes-lost-entries", TestCheckAndRemoveLostParticlesRemovesLostEntries},
1606 {"calculate-brownian-displacement-deterministic-seed", TestCalculateBrownianDisplacementDeterministicSeed},
1607 {"update-all-particle-positions-moves-swarm-entries", TestUpdateAllParticlePositionsMovesSwarmEntries},
1608 {"locate-all-particles-in-grid-prior-cell-fast-path", TestLocateAllParticlesInGridPriorCellFastPath},
1609 {"locate-all-particles-in-grid-guess-path-resolves-local-particle", TestLocateAllParticlesInGridGuessPathResolvesLocalParticle},
1610 {"locate-particle-or-find-migration-target-counts-research", TestLocateParticleOrFindMigrationTargetCountsReSearch},
1611 {"wall-noslip-and-freeslip-helpers", TestWallNoSlipAndFreeSlipHelpers},
1612 {"wall-model-scalar-helpers", TestWallModelScalarHelpers},
1613 {"wall-model-velocity-helpers", TestWallModelVelocityHelpers},
1614 {"wall-function-vector-wrappers", TestWallFunctionVectorWrappers},
1615 {"validate-driven-flow-configuration-no-driven-handlers", TestValidateDrivenFlowConfigurationNoDrivenHandlers},
1616 {"compute-smagorinsky-constant-constant-model", TestComputeSmagorinskyConstantConstantModel},
1617 {"minimal-fixture-mirrors-production-dm-layout", TestMinimalFixtureMirrorsProductionDMLayout},
1618 {"minimal-fixture-registers-production-swarm-fields", TestMinimalFixtureRegistersProductionSwarmFields},
1619 {"update-solver-history-vectors-shifts-states", TestUpdateSolverHistoryVectorsShiftsStates},
1620 {"get-owned-cell-range-single-rank-accounting", TestGetOwnedCellRangeSingleRankAccounting},
1621 {"compute-and-store-neighbor-ranks-single-rank", TestComputeAndStoreNeighborRanksSingleRank},
1622 {"runtime-walltime-guard-parses-positive-seconds", TestRuntimeWalltimeGuardParsesPositiveSeconds},
1623 {"runtime-walltime-guard-estimator-helpers", TestRuntimeWalltimeGuardEstimatorHelpers},
1624 {"runtime-walltime-guard-trigger-decision", TestRuntimeWalltimeGuardTriggerDecision},
1625 };
1626
1627 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv runtime-kernel tests");
1628 if (ierr) {
1629 return (int)ierr;
1630 }
1631
1632 ierr = PicurvRunTests("unit-runtime", cases, sizeof(cases) / sizeof(cases[0]));
1633 if (ierr) {
1634 PetscFinalize();
1635 return (int)ierr;
1636 }
1637
1638 ierr = PetscFinalize();
1639 return (int)ierr;
1640}
PetscErrorCode Validate_DrivenFlowConfiguration(UserCtx *user)
(Private) Validates all consistency rules for a driven flow (channel/pipe) setup.
Definition BC_Handlers.c:15
PetscErrorCode SynchronizePeriodicStaggeredFields(UserCtx *user, PetscInt num_fields, const FieldId field_ids[])
Synchronizes persistent component-staggered vector fields.
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 CheckAndRemoveOutOfBoundsParticles(UserCtx *user, PetscInt *removedCountLocal, PetscInt *removedCountGlobal, const BoundingBox *bboxlist)
Checks for particles outside the physical domain boundaries and removes them using DMSwarmRemovePoint...
PetscErrorCode UpdateAllParticlePositions(UserCtx *user)
Loops over all local particles in the DMSwarm, updating their positions based on velocity and the glo...
PetscErrorCode CalculateParticleCountPerCell(UserCtx *user)
Counts particles in each cell of the DMDA 'da' and stores the result in user->ParticleCount.
PetscErrorCode CalculateBrownianDisplacement(UserCtx *user, PetscReal diff_eff, Cmpnts *displacement)
Calculates the stochastic displacement vector (Brownian motion) for a single particle.
PetscErrorCode LocateAllParticlesInGrid(UserCtx *user, BoundingBox *bboxlist)
Orchestrates the complete particle location and migration process for one timestep.
PetscErrorCode UpdateParticlePosition(UserCtx *user, Particle *particle)
Updates a particle's position based on its velocity and the timestep dt (stored in user->dt).
PetscErrorCode ResetAllParticleStatuses(UserCtx *user)
Marks all local particles as NEEDS_LOCATION for the next settlement pass.
PetscErrorCode CheckAndRemoveLostParticles(UserCtx *user, PetscInt *removedCountLocal, PetscInt *removedCountGlobal)
Removes particles that have been definitively flagged as LOST by the location algorithm.
Header file for Particle related physics modules.
PetscErrorCode UpdateParticleField(ParticleFieldId field_id, PetscReal dt, PetscReal *psi_io, PetscReal diffusivity, PetscReal mean_val, PetscReal cell_vol, PetscReal C_model)
Updates a single particle's field based on its state and physics model.
Header file for Particle Swarm management functions.
PetscErrorCode UpdateParticleWeights(PetscReal *d, Particle *particle)
Updates a particle's interpolation weights based on distances to cell faces.
PetscErrorCode DistributeParticles(PetscInt numParticles, PetscMPIInt rank, PetscMPIInt size, PetscInt *particlesPerProcess, PetscInt *remainder)
Distributes particles evenly across MPI processes, handling any remainders.
PetscBool IsParticleInsideBoundingBox(const BoundingBox *bbox, const Particle *particle)
Checks if a particle's location is within a specified bounding box.
FieldId
Compile-time identity for a catalogued Eulerian field.
@ FIELD_ID_UCAT
@ FIELD_ID_UCONT
@ FIELD_ID_P
PetscErrorCode ScatterAllParticleFieldsToEulerFields(UserCtx *user)
Scatters a predefined set of particle fields to their corresponding Eulerian fields.
PetscErrorCode SetInitialInteriorField(UserCtx *user, FieldId field_id)
Sets the initial values for the INTERIOR of a specified Eulerian field.
PetscErrorCode PopulateInitialUcont(UserCtx *user)
Populate Ucont for one fresh-start block from the configured IC mode.
PetscErrorCode InterpolateAllFieldsToSwarm(UserCtx *user)
Interpolates all relevant fields from the DMDA to the DMSwarm.
PetscErrorCode WriteSimulationFields(UserCtx *user, const char *checkpoint_directory)
Writes simulation fields to files.
Definition io.c:2002
PetscErrorCode WriteFieldData(UserCtx *user, const char *field_name, Vec field_vec, const char *ext)
Writes data from a specific PETSc vector to a file.
Definition io.c:1947
PetscErrorCode ComputeSmagorinskyConstant(UserCtx *user)
Computes the dynamic Smagorinsky constant (Cs) for the LES model.
Definition les.c:42
@ PARTICLE_FIELD_ID_PSI
@ PARTICLE_FIELD_ID_VELOCITY
PetscReal RuntimeWalltimeGuardUpdateEWMA(PetscBool has_previous, PetscReal previous_ewma_seconds, PetscReal latest_step_seconds, PetscReal alpha)
Update an EWMA estimate for timestep wall-clock duration.
Definition runloop.c:145
PetscReal RuntimeWalltimeGuardConservativeEstimate(PetscReal warmup_average_seconds, PetscReal ewma_seconds, PetscReal latest_step_seconds)
Return the conservative timestep estimate used by the walltime guard.
Definition runloop.c:157
PetscReal RuntimeWalltimeGuardRequiredHeadroom(PetscReal min_seconds, PetscReal multiplier, PetscReal conservative_estimate_seconds)
Compute the required shutdown headroom from timestep estimate and floor.
Definition runloop.c:168
PetscBool RuntimeWalltimeGuardShouldTrigger(PetscInt completed_steps, PetscInt warmup_steps, PetscReal remaining_seconds, PetscReal min_seconds, PetscReal multiplier, PetscReal warmup_average_seconds, PetscReal ewma_seconds, PetscReal latest_step_seconds, PetscReal *required_headroom_seconds_out)
Decide whether the runtime walltime guard should stop before another step.
Definition runloop.c:179
PetscErrorCode UpdateSolverHistoryVectors(UserCtx *user, PetscBool preserve_previous_state)
Copies the current time step's solution fields into history vectors (e.g., U(t_n) -> U_o,...
Definition runloop.c:306
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 ComputeAndStoreNeighborRanks(UserCtx *user)
Computes and stores the Cartesian neighbor ranks for the DMDA decomposition.
Definition setup.c:2382
PetscErrorCode UpdateLocalGhosts(UserCtx *user, FieldId field_id)
Updates the local vector (including ghost points) from its corresponding global vector.
Definition setup.c:1838
PetscErrorCode Cart2Contra(UserCtx *user)
Convert the ghosted Cartesian velocity field to contravariant face fluxes.
Definition setup.c:2784
PetscBool RuntimeWalltimeGuardParsePositiveSeconds(const char *text, PetscReal *seconds_out)
Parse a positive floating-point seconds value from runtime metadata.
Definition setup.c:20
static PetscErrorCode TestUpdateParticleFieldIEMRelaxation(void)
Tests IEM relaxation updates for particle-carried fields.
static PetscErrorCode TestMinimalFixtureMirrorsProductionDMLayout(void)
Tests that the shared minimal fixture mirrors the production DA contract.
static PetscErrorCode TestUpdateParticlePositionDiffusivityGradientOnly(void)
Tests particle position updates driven only by diffusivity-gradient drift.
static PetscErrorCode TestLocateAllParticlesInGridGuessPathResolvesLocalParticle(void)
Tests the guess-then-verify orchestrator path for a local particle with an unknown prior cell.
static PetscErrorCode TestWallModelVelocityHelpers(void)
Tests closed-form and iterative wall-model velocity helpers against inverse reconstructions.
static PetscErrorCode TestSetInitialInteriorFieldIgnoresNonUcontRequest(void)
Tests that non-Ucont requests do not modify interior field initialization.
static PetscErrorCode TestLocateParticleOrFindMigrationTargetCountsReSearch(void)
Verifies that later settlement passes increment re-search metrics.
static PetscErrorCode TestRuntimeWalltimeGuardParsesPositiveSeconds(void)
Tests parsing of positive runtime walltime metadata values.
static PetscErrorCode TestCart2ContraUsesFinalizedPeriodicUcat(void)
Verifies periodic Ucat endpoints are repaired before Cart2Contra terminal faces.
static PetscErrorCode TestSetInitialInteriorFieldCurvilinearConstantViaFlowDirection(void)
Tests curvilinear Constant IC: flow_direction selects the streamwise axis.
int main(int argc, char **argv)
Runs the unit-runtime PETSc test binary.
static PetscErrorCode TestInterpolateAllFieldsToSwarmConstantFields(void)
Tests direct interpolation from Eulerian fields to one localized swarm particle.
static PetscErrorCode SyncRuntimeFieldGhosts(UserCtx *user)
Synchronizes the minimal runtime fixture's global fields into their persistent local ghosts.
static PetscErrorCode TestStagedUcatFourierModesUsePicurvDiscreteSymbol(void)
Cross-checks selected exact Fourier modes against PICurv's native discrete symbol.
static PetscErrorCode TestDistributeParticlesRemainderHandling(void)
Tests particle distribution remainder handling across ranks.
static PetscErrorCode TestResetAllParticleStatusesLeavesLostParticlesUntouched(void)
Tests localized particle-status reset behavior for restart of the location workflow.
static PetscErrorCode SeedSingleParticle(UserCtx *user, PetscInt ci, PetscInt cj, PetscInt ck, PetscReal x, PetscReal y, PetscReal z, PetscReal wx, PetscReal wy, PetscReal wz, PetscInt status_value)
Seeds one localized swarm particle with the cell, position, weight, and status data used by runtime t...
static PetscErrorCode TestSetInitialInteriorFieldCartesianConstantSetsContravariantFlux(void)
Tests cartesian Constant IC: Cart2Contra sets contravariant flux via metric dot product.
static PetscErrorCode TestPopulateInitialUcontLoadsStagedUcat(void)
Tests loading a staged Ucat file IC and converting it to Ucont.
static PetscErrorCode TestPopulateInitialUcontLoadsStagedUcont(void)
Tests loading a staged Ucont file IC without Cartesian conversion.
static PetscErrorCode TestInterpolateAllFieldsToSwarmCornerAveragedConstantFields(void)
Tests the corner-averaged (legacy) interpolation path on constant fields.
static PetscErrorCode TestGetOwnedCellRangeSingleRankAccounting(void)
Tests owned-cell range accounting on a single MPI rank.
static PetscErrorCode TestUpdateParticlePositionWithoutBrownianContribution(void)
Tests particle position updates without Brownian forcing.
static PetscErrorCode TestComputeAndStoreNeighborRanksSingleRank(void)
Tests neighbor-rank discovery on a single MPI rank.
static PetscErrorCode TestCalculateParticleCountPerCellCountsGlobalCellIDs(void)
Tests particle counting by geometric cell IDs using the production +1 storage shift.
static PetscErrorCode TestWriteLESFieldsCopiesOwnedPeriodicValues(void)
Tests LES output staging from local vectors on a multiply periodic DMDA.
static PetscErrorCode TestValidateDrivenFlowConfigurationNoDrivenHandlers(void)
Tests driven-flow validation when no driven handlers are present.
static PetscErrorCode TestWallNoSlipAndFreeSlipHelpers(void)
Tests no-slip and free-slip wall helper kernels.
static PetscErrorCode TestMinimalFixtureRegistersProductionSwarmFields(void)
Tests that the shared swarm fixture registers the production field set.
static PetscErrorCode TestWallFunctionVectorWrappers(void)
Tests the vector wall-function wrappers on a tangential reference flow.
static PetscErrorCode TestSetInitialInteriorFieldZeroClearsInterior(void)
Tests zero IC clears physical-cell contravariant velocity.
static PetscErrorCode TestLocateAllParticlesInGridPriorCellFastPath(void)
Tests the location orchestrator fast path when a particle already carries a valid prior cell.
static PetscErrorCode TestComputeSmagorinskyConstantConstantModel(void)
Tests the constant Smagorinsky model helper path.
static PetscErrorCode TestCalculateBrownianDisplacementDeterministicSeed(void)
Tests Brownian displacement generation against a duplicated seeded RNG stream.
static PetscErrorCode TestScatterAllParticleFieldsToEulerFieldsAveragesPsi(void)
Tests particle-to-grid scattering using known cell occupancy and scalar values.
static PetscErrorCode TestUpdateSolverHistoryVectorsShiftsStates(void)
Tests solver history-vector shifting between time levels.
static PetscErrorCode TestUpdateParticleWeightsComputesExpectedRatios(void)
Tests particle weight updates against expected ratios.
static PetscErrorCode TestCheckAndRemoveOutOfBoundsParticlesRemovesEscapedParticle(void)
Tests direct removal of particles that leave every rank bounding box.
static PetscErrorCode StagedFourierModeMaxDivergence(UserCtx *user, PetscInt mode_x, PetscInt mode_y, PetscInt mode_z, Cmpnts amplitude, const char *tmpdir, PetscReal *max_divergence)
Stages a real Cartesian Fourier mode through the production file-IC path.
static PetscErrorCode TestSetInitialInteriorFieldPoiseuilleProfile(void)
Tests Poiseuille IC follows the discrete cross-stream profile and reaches zero at edges.
static PetscErrorCode TestCart2ContraConvertsCartesianField(void)
Tests spatially varying Cartesian-field conversion to contravariant fluxes.
static PetscErrorCode TestCheckAndRemoveLostParticlesRemovesLostEntries(void)
Tests direct removal of particles already marked LOST by the location workflow.
static PetscErrorCode TestRuntimeWalltimeGuardEstimatorHelpers(void)
Tests walltime-guard estimator helper calculations.
static PetscErrorCode TestIsParticleInsideBoundingBoxBasicCases(void)
Tests basic particle-inside-bounding-box classification cases.
static PetscErrorCode TestRuntimeWalltimeGuardTriggerDecision(void)
Tests runtime walltime-guard shutdown trigger decisions.
static PetscErrorCode TestUpdateAllParticlePositionsMovesSwarmEntries(void)
Tests swarm-wide particle position updates using the same transport path as the runtime loop.
static PetscErrorCode TestWallModelScalarHelpers(void)
Tests wall-model scalar helper kernels.
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 PicurvEnsureDir(const char *path)
Ensures a directory exists for test output.
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 PicurvDestroyRuntimeContext(SimCtx **simCtx_ptr)
Finalizes and frees a runtime context built by PicurvBuildTinyRuntimeContext.
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 PicurvBuildTinyRuntimeContext(const char *bcs_contents, PetscBool enable_particles, SimCtx **simCtx_out, UserCtx **user_out, char *tmpdir, size_t tmpdir_len)
Builds a tiny runtime context through the real setup path for behavior-level tests.
PetscErrorCode PicurvAssertVecConstant(Vec vec, PetscScalar expected, PetscReal tol, const char *context)
Asserts that a PETSc vector is spatially constant within tolerance.
PetscErrorCode PicurvAssertIntEqual(PetscInt expected, PetscInt actual, const char *context)
Asserts that two integer values are equal.
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.
PetscMPIInt rank_zm
Definition variables.h:199
@ CONSTANT_SMAGORINSKY
Definition variables.h:522
PetscReal icVelocityPhysical
Definition variables.h:759
Vec lDiffusivityGradient
Definition variables.h:943
@ PERIODIC
Definition variables.h:292
Cmpnts vel
Definition variables.h:186
PetscInt ys_cell
Definition variables.h:204
PetscInt xs_cell
Definition variables.h:204
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:931
PetscMPIInt rank_yp
Definition variables.h:198
PetscInt64 searchLocatedCount
Definition variables.h:241
PetscInt64 searchLostCount
Definition variables.h:242
PetscInt cell[3]
Definition variables.h:184
InitialConditionMode initialConditionMode
Definition variables.h:754
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:909
ParticleLocationStatus
Defines the state of a particle with respect to its location and migration status during the iterativ...
Definition variables.h:137
@ LOST
Definition variables.h:141
@ NEEDS_LOCATION
Definition variables.h:138
@ ACTIVE_AND_LOCATED
Definition variables.h:139
PetscMPIInt rank_ym
Definition variables.h:198
FlowDirection flowDirection
Definition variables.h:758
PetscMPIInt rank_xp
Definition variables.h:197
PetscInt KM
Definition variables.h:920
PetscInt64 traversalStepsSum
Definition variables.h:243
PetscReal ren
Definition variables.h:744
Vec lUcont_rm1
Definition variables.h:947
PetscInt zm_cell
Definition variables.h:205
Cmpnts max_coords
Maximum x, y, z coordinates of the bounding box.
Definition variables.h:173
PetscInt zs_cell
Definition variables.h:204
Cmpnts diffusivitygradient
Definition variables.h:191
PetscInt64 searchPopulation
Definition variables.h:240
PetscReal dt
Definition variables.h:710
RankNeighbors neighbors
Definition variables.h:923
Vec lPsi
Definition variables.h:997
PetscInt currentSettlementPass
Definition variables.h:252
Vec DiffusivityGradient
Definition variables.h:943
Vec lCs
Definition variables.h:982
Vec Ucont
Definition variables.h:939
PetscInt StartStep
Definition variables.h:705
Cmpnts min_coords
Minimum x, y, z coordinates of the bounding box.
Definition variables.h:172
PetscScalar x
Definition variables.h:103
Cmpnts loc
Definition variables.h:185
PetscInt64 reSearchCount
Definition variables.h:244
char * current_io_directory
Definition variables.h:720
PetscInt xm_cell
Definition variables.h:205
Vec lUcont_o
Definition variables.h:946
PetscInt64 bboxGuessFallbackCount
Definition variables.h:250
InterpolationMethod interpolationMethod
Definition variables.h:832
RankCellInfo * RankCellInfoMap
Definition variables.h:995
PetscInt ym_cell
Definition variables.h:205
Vec Ucat_o
Definition variables.h:946
PetscInt64 bboxGuessSuccessCount
Definition variables.h:249
BoundingBox * bboxlist
Definition variables.h:830
Vec lNu_t
Definition variables.h:982
PetscMPIInt rank_xm
Definition variables.h:197
Vec Nu_t
Definition variables.h:982
PetscInt64 maxParticlePassDepth
Definition variables.h:251
PetscScalar z
Definition variables.h:103
@ INTERP_CORNER_AVERAGED
Definition variables.h:566
Vec Ucat
Definition variables.h:939
Vec ParticleCount
Definition variables.h:996
PetscReal Const_CS
Definition variables.h:823
Vec Ucont_o
Definition variables.h:946
PetscInt JM
Definition variables.h:920
@ FLOW_DIR_POS_ZETA
Definition variables.h:277
char initialConditionDirectory[PETSC_MAX_PATH_LEN]
Definition variables.h:756
@ IC_MODE_CONSTANT_CARTESIAN
Definition variables.h:153
@ IC_MODE_POISEUILLE
Definition variables.h:154
@ IC_MODE_CONSTANT_STREAMWISE
Definition variables.h:155
@ IC_MODE_FILE
Definition variables.h:156
@ IC_MODE_ZERO
Definition variables.h:152
Cmpnts InitialConstantContra
Definition variables.h:757
PetscMPIInt rank_zp
Definition variables.h:199
Vec Ucont_rm1
Definition variables.h:947
SearchMetricsState searchMetrics
Definition variables.h:840
PetscReal diffusivity
Definition variables.h:190
Vec lUcont
Definition variables.h:939
PetscInt step
Definition variables.h:703
Vec Diffusivity
Definition variables.h:942
PetscRandom BrownianMotionRNG
Definition variables.h:841
PetscInt GridOrientation
Definition variables.h:924
DMDALocalInfo info
Definition variables.h:918
Vec lUcat
Definition variables.h:939
PetscScalar y
Definition variables.h:103
@ IC_FIELD_UCONT
Definition variables.h:162
@ IC_FIELD_UCAT
Definition variables.h:161
PetscMPIInt size
Definition variables.h:699
PetscInt IM
Definition variables.h:920
char _io_context_buffer[PETSC_MAX_PATH_LEN]
Definition variables.h:719
Cmpnts weights
Definition variables.h:187
@ NUM_FACES
Definition variables.h:147
PetscInt les
Definition variables.h:821
Vec lDiffusivity
Definition variables.h:942
BCType mathematical_type
Definition variables.h:368
PetscInt64 searchAttempts
Definition variables.h:239
InitialConditionField initialConditionField
Definition variables.h:755
PetscInt64 PID
Definition variables.h:183
PetscInt64 maxTraversalFailCount
Definition variables.h:246
Vec Psi
Definition variables.h:997
Vec P_o
Definition variables.h:946
@ BC_FACE_NEG_X
Definition variables.h:262
@ BC_FACE_POS_X
Definition variables.h:262
Defines a 3D axis-aligned bounding box.
Definition variables.h:171
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
The master context for the entire simulation.
Definition variables.h:695
User-defined context containing data specific to a single computational grid level.
Definition variables.h:906
Header file for particle location functions using the walking search algorithm.
PetscErrorCode LocateParticleOrFindMigrationTarget(UserCtx *user, Particle *particle, ParticleLocationStatus *status_out)
Locates a particle's host cell or identifies its migration target using a robust walk search.
void wall_function_loglaw(UserCtx *user, double roughness_height, double distance_reference, double distance_boundary, Cmpnts velocity_wall, Cmpnts velocity_reference, Cmpnts *velocity_boundary, PetscReal *friction_velocity, double normal_x, double normal_y, double normal_z)
Applies log-law wall function with roughness correction.
double find_utau_loglaw(double velocity, double wall_distance, double roughness_length)
Solves for friction velocity using simple log-law (explicit formula)
double u_Werner(double kinematic_viscosity, double wall_distance, double friction_velocity)
Computes velocity using Werner-Wengle wall function.
void wall_function(UserCtx *user, double distance_reference, double distance_boundary, Cmpnts velocity_wall, Cmpnts velocity_reference, Cmpnts *velocity_boundary, PetscReal *friction_velocity, double normal_x, double normal_y, double normal_z)
Applies standard wall function with Werner-Wengle model.
double find_utau_hydset(double kinematic_viscosity, double known_velocity, double wall_distance, double initial_guess, double roughness_height)
Solves for friction velocity using Newton-Raphson iteration.
double u_Cabot(double kinematic_viscosity, double wall_distance, double friction_velocity, double pressure_gradient_tangent, double wall_shear_stress)
Computes velocity using Cabot wall function.
double u_loglaw(double wall_distance, double friction_velocity, double roughness_length)
Computes velocity using simple log-law (smooth wall with roughness offset)
void wall_function_Cabot(UserCtx *user, double roughness_height, double distance_reference, double distance_boundary, Cmpnts velocity_wall, Cmpnts velocity_reference, Cmpnts *velocity_boundary, PetscReal *friction_velocity, double normal_x, double normal_y, double normal_z, double pressure_gradient_x, double pressure_gradient_y, double pressure_gradient_z, int iteration_count)
Applies Cabot non-equilibrium wall function with pressure gradients.
void noslip(UserCtx *user, double distance_reference, double distance_boundary, Cmpnts velocity_wall, Cmpnts velocity_reference, Cmpnts *velocity_boundary, double normal_x, double normal_y, double normal_z)
Applies no-slip wall boundary condition with linear interpolation.
void find_utau_Cabot(double kinematic_viscosity, double velocity, double wall_distance, double initial_guess, double pressure_gradient_tangent, double pressure_gradient_normal, double *friction_velocity, double *wall_shear_velocity, double *wall_shear_normal)
Solves for friction velocity using Cabot wall function.
double integrate_1(double kinematic_viscosity, double wall_distance, double friction_velocity, int integration_mode)
Integrates eddy viscosity profile from wall to distance y.
double f_hydset(double kinematic_viscosity, double known_velocity, double wall_distance, double friction_velocity_guess, double roughness_height)
Residual function for friction velocity equation (log-law with roughness)
double E_coeff(double friction_velocity, double roughness_height, double kinematic_viscosity)
Computes roughness-modified log-law coefficient E.
double nu_t(double yplus)
Computes turbulent eddy viscosity ratio (ν_t / ν)
double find_utau_Werner(double kinematic_viscosity, double velocity, double wall_distance, double initial_guess)
Solves for friction velocity using Werner-Wengle wall function.
void freeslip(UserCtx *user, double distance_reference, double distance_boundary, Cmpnts velocity_wall, Cmpnts velocity_reference, Cmpnts *velocity_boundary, double normal_x, double normal_y, double normal_z)
Applies free-slip wall boundary condition.