PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
test_boundaries.c
Go to the documentation of this file.
1/**
2 * @file test_boundaries.c
3 * @brief C unit tests for boundary factories, inlet ownership, and face helpers.
4 */
5
6#include "test_support.h"
7
8#include "Boundaries.h"
9
10/**
11 * @brief Appends one key/value pair to a linked list of boundary-condition parameters.
12 */
13static PetscErrorCode AppendBCParam(BC_Param **head, const char *key, const char *value)
14{
15 BC_Param *node = NULL;
16 BC_Param **cursor = head;
17
18 PetscFunctionBeginUser;
19 PetscCheck(head != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "BC param list head cannot be NULL.");
20 PetscCall(PetscCalloc1(1, &node));
21 PetscCall(PetscStrallocpy(key, &node->key));
22 PetscCall(PetscStrallocpy(value, &node->value));
23 while (*cursor) {
24 cursor = &(*cursor)->next;
25 }
26 *cursor = node;
27 PetscFunctionReturn(0);
28}
29/**
30 * @brief Destroys one boundary-condition handler allocated by a boundary test.
31 */
32
33static PetscErrorCode DestroyBoundaryHandler(BoundaryCondition **bc_ptr)
34{
35 BoundaryCondition *bc = NULL;
36 PetscFunctionBeginUser;
37 if (!bc_ptr || !*bc_ptr) PetscFunctionReturn(0);
38
39 bc = *bc_ptr;
40 if (bc->Destroy) {
41 PetscCall(bc->Destroy(bc));
42 }
43 PetscCall(PetscFree(bc));
44 *bc_ptr = NULL;
45 PetscFunctionReturn(0);
46}
47
48/**
49 * @brief Chooses a stable interior node index for face-sample assertions on tiny test grids.
50 */
51static PetscInt InteriorSampleIndex(PetscInt nodes)
52{
53 PetscInt index = 3;
54
55 if (index >= nodes - 1) {
56 index = nodes - 2;
57 }
58 if (index < 1) {
59 index = 1;
60 }
61 return index;
62}
63
64/**
65 * @brief Extracts the velocity component aligned with the supplied boundary-face normal.
66 */
67static PetscReal GetFaceNormalComponent(Cmpnts value, BCFace face)
68{
69 switch (face) {
70 case BC_FACE_NEG_X:
71 case BC_FACE_POS_X:
72 return value.x;
73 case BC_FACE_NEG_Y:
74 case BC_FACE_POS_Y:
75 return value.y;
76 case BC_FACE_NEG_Z:
77 case BC_FACE_POS_Z:
78 return value.z;
79 }
80 return 0.0;
81}
82
83/**
84 * @brief Returns the sign convention used for face-normal flux expectations.
85 */
86static PetscReal GetFaceOrientationSign(BCFace face)
87{
88 switch (face) {
89 case BC_FACE_NEG_X:
90 case BC_FACE_NEG_Y:
91 case BC_FACE_NEG_Z:
92 return 1.0;
93 case BC_FACE_POS_X:
94 case BC_FACE_POS_Y:
95 case BC_FACE_POS_Z:
96 return -1.0;
97 }
98 return 0.0;
99}
100
101/**
102 * @brief Maps an inlet face to the matching configuration key used by the handler parser.
103 */
104static const char *GetInletParamKey(BCFace face)
105{
106 switch (face) {
107 case BC_FACE_NEG_X:
108 case BC_FACE_POS_X:
109 return "vx";
110 case BC_FACE_NEG_Y:
111 case BC_FACE_POS_Y:
112 return "vy";
113 case BC_FACE_NEG_Z:
114 case BC_FACE_POS_Z:
115 return "vz";
116 }
117 return "";
118}
119
120/**
121 * @brief Computes the number of face-interior sample points for a given boundary face.
122 */
123static PetscReal GetFaceInteriorPointCount(const UserCtx *user, BCFace face)
124{
125 switch (face) {
126 case BC_FACE_NEG_X:
127 case BC_FACE_POS_X:
128 return (PetscReal)(user->info.my - 2) * (PetscReal)(user->info.mz - 2);
129 case BC_FACE_NEG_Y:
130 case BC_FACE_POS_Y:
131 return (PetscReal)(user->info.mx - 2) * (PetscReal)(user->info.mz - 2);
132 case BC_FACE_NEG_Z:
133 case BC_FACE_POS_Z:
134 return (PetscReal)(user->info.mx - 2) * (PetscReal)(user->info.my - 2);
135 }
136 return 0.0;
137}
138
139/**
140 * @brief Selects representative `Ucont` and `Ubcs` slots for face-matrix assertions.
141 */
142static PetscErrorCode GetRepresentativeFaceSlots(const UserCtx *user,
143 BCFace face,
144 PetscInt *ucont_k,
145 PetscInt *ucont_j,
146 PetscInt *ucont_i,
147 PetscInt *ubcs_k,
148 PetscInt *ubcs_j,
149 PetscInt *ubcs_i)
150{
151 const PetscInt sample_i = InteriorSampleIndex(user->info.mx);
152 const PetscInt sample_j = InteriorSampleIndex(user->info.my);
153 const PetscInt sample_k = InteriorSampleIndex(user->info.mz);
154
155 PetscFunctionBeginUser;
156 switch (face) {
157 case BC_FACE_NEG_X:
158 *ucont_k = sample_k; *ucont_j = sample_j; *ucont_i = 0;
159 *ubcs_k = sample_k; *ubcs_j = sample_j; *ubcs_i = 0;
160 break;
161 case BC_FACE_POS_X:
162 *ucont_k = sample_k; *ucont_j = sample_j; *ucont_i = user->info.mx - 2;
163 *ubcs_k = sample_k; *ubcs_j = sample_j; *ubcs_i = user->info.mx - 1;
164 break;
165 case BC_FACE_NEG_Y:
166 *ucont_k = sample_k; *ucont_j = 0; *ucont_i = sample_i;
167 *ubcs_k = sample_k; *ubcs_j = 0; *ubcs_i = sample_i;
168 break;
169 case BC_FACE_POS_Y:
170 *ucont_k = sample_k; *ucont_j = user->info.my - 2; *ucont_i = sample_i;
171 *ubcs_k = sample_k; *ubcs_j = user->info.my - 1; *ubcs_i = sample_i;
172 break;
173 case BC_FACE_NEG_Z:
174 *ucont_k = 0; *ucont_j = sample_j; *ucont_i = sample_i;
175 *ubcs_k = 0; *ubcs_j = sample_j; *ubcs_i = sample_i;
176 break;
177 case BC_FACE_POS_Z:
178 *ucont_k = user->info.mz - 2; *ucont_j = sample_j; *ucont_i = sample_i;
179 *ubcs_k = user->info.mz - 1; *ubcs_j = sample_j; *ubcs_i = sample_i;
180 break;
181 }
182 PetscFunctionReturn(0);
183}
184
185/**
186 * @brief Selects center, off-center, and wall-adjacent slots for parabolic-face checks.
187 */
188static PetscErrorCode GetParabolicSampleSlots(const UserCtx *user,
189 BCFace face,
190 PetscInt *center_k,
191 PetscInt *center_j,
192 PetscInt *center_i,
193 PetscInt *off_k,
194 PetscInt *off_j,
195 PetscInt *off_i,
196 PetscInt *wall_k,
197 PetscInt *wall_j,
198 PetscInt *wall_i,
199 PetscInt *ubcs_center_k,
200 PetscInt *ubcs_center_j,
201 PetscInt *ubcs_center_i)
202{
203 PetscFunctionBeginUser;
204 switch (face) {
205 case BC_FACE_NEG_X:
206 *center_k = 3; *center_j = 3; *center_i = 0;
207 *off_k = 2; *off_j = 2; *off_i = 0;
208 *wall_k = 1; *wall_j = 1; *wall_i = 0;
209 *ubcs_center_k = 3; *ubcs_center_j = 3; *ubcs_center_i = 0;
210 break;
211 case BC_FACE_POS_X:
212 *center_k = 3; *center_j = 3; *center_i = user->info.mx - 2;
213 *off_k = 2; *off_j = 2; *off_i = user->info.mx - 2;
214 *wall_k = 1; *wall_j = 1; *wall_i = user->info.mx - 2;
215 *ubcs_center_k = 3; *ubcs_center_j = 3; *ubcs_center_i = user->info.mx - 1;
216 break;
217 case BC_FACE_NEG_Y:
218 *center_k = 3; *center_j = 0; *center_i = 3;
219 *off_k = 2; *off_j = 0; *off_i = 2;
220 *wall_k = 1; *wall_j = 0; *wall_i = 1;
221 *ubcs_center_k = 3; *ubcs_center_j = 0; *ubcs_center_i = 3;
222 break;
223 case BC_FACE_POS_Y:
224 *center_k = 3; *center_j = user->info.my - 2; *center_i = 3;
225 *off_k = 2; *off_j = user->info.my - 2; *off_i = 2;
226 *wall_k = 1; *wall_j = user->info.my - 2; *wall_i = 1;
227 *ubcs_center_k = 3; *ubcs_center_j = user->info.my - 1; *ubcs_center_i = 3;
228 break;
229 case BC_FACE_NEG_Z:
230 *center_k = 0; *center_j = 3; *center_i = 3;
231 *off_k = 0; *off_j = 2; *off_i = 2;
232 *wall_k = 0; *wall_j = 1; *wall_i = 1;
233 *ubcs_center_k = 0; *ubcs_center_j = 3; *ubcs_center_i = 3;
234 break;
235 case BC_FACE_POS_Z:
236 *center_k = user->info.mz - 2; *center_j = 3; *center_i = 3;
237 *off_k = user->info.mz - 2; *off_j = 2; *off_i = 2;
238 *wall_k = user->info.mz - 2; *wall_j = 1; *wall_i = 1;
239 *ubcs_center_k = user->info.mz - 1; *ubcs_center_j = 3; *ubcs_center_i = 3;
240 break;
241 }
242 PetscFunctionReturn(0);
243}
244
245/**
246 * @brief Resets one boundary-face configuration entry to a neutral test-local baseline.
247 */
249{
252 cfg->face_id = BC_FACE_NEG_X;
253 if (cfg->params) {
255 cfg->params = NULL;
256 }
257}
258/**
259 * @brief Tests that face-service detection matches a defined inlet face.
260 */
261
263{
264 SimCtx *simCtx = NULL;
265 UserCtx *user = NULL;
266 PetscBool generic_face = PETSC_FALSE;
267 PetscBool inlet_face = PETSC_FALSE;
268 PetscBool any_face_serviceable = PETSC_FALSE;
269
270 PetscFunctionBeginUser;
271 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
272
273 for (BCFace face = BC_FACE_NEG_X; face <= BC_FACE_POS_Z; face++) {
274 PetscCall(CanRankServiceFace(&user->info, user->IM, user->JM, user->KM, face, &generic_face));
275 if (generic_face) {
276 any_face_serviceable = PETSC_TRUE;
277 }
278
279 user->inletFaceDefined = PETSC_TRUE;
280 user->identifiedInletBCFace = face;
281 PetscCall(CanRankServiceInletFace(user, &user->info, user->IM, user->JM, user->KM, &inlet_face));
282 PetscCall(PicurvAssertBool((PetscBool)(inlet_face == generic_face), "inlet face service check should match generic face check"));
283 }
284 PetscCall(PicurvAssertBool(any_face_serviceable, "at least one global face should be serviceable in a non-degenerate domain"));
285
286 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
287 PetscFunctionReturn(0);
288}
289/**
290 * @brief Tests that inlet-face service requires an inlet face to be defined.
291 */
292
294{
295 SimCtx *simCtx = NULL;
296 UserCtx *user = NULL;
297 PetscBool can_service = PETSC_FALSE;
298
299 PetscFunctionBeginUser;
300 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
301
302 user->inletFaceDefined = PETSC_FALSE;
303 for (BCFace face = BC_FACE_NEG_X; face <= BC_FACE_POS_Z; face++) {
304 user->identifiedInletBCFace = face;
305 PetscCall(CanRankServiceInletFace(user, &user->info, user->IM, user->JM, user->KM, &can_service));
306 PetscCall(PicurvAssertBool((PetscBool)!can_service, "undefined inlet face should not be serviceable"));
307 }
308
309 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
310 PetscFunctionReturn(0);
311}
312/**
313 * @brief Tests the boundary-condition factory assignments for representative handlers.
314 */
315
317{
318 BoundaryCondition *wall = NULL;
319 BoundaryCondition *inlet = NULL;
320
321 PetscFunctionBeginUser;
323 PetscCall(PicurvAssertBool((PetscBool)(wall != NULL), "factory should return wall handler"));
324 PetscCall(PicurvAssertIntEqual(BC_PRIORITY_WALL, wall->priority, "wall handler priority"));
325 PetscCall(PicurvAssertBool((PetscBool)(wall->Apply != NULL), "wall handler should expose Apply"));
326 PetscCall(PicurvAssertBool((PetscBool)(wall->Destroy == NULL), "wall handler should not require destroy hook"));
327 PetscCall(DestroyBoundaryHandler(&wall));
328
330 PetscCall(PicurvAssertBool((PetscBool)(inlet != NULL), "factory should return inlet handler"));
331 PetscCall(PicurvAssertIntEqual(BC_PRIORITY_INLET, inlet->priority, "inlet handler priority"));
332 PetscCall(PicurvAssertBool((PetscBool)(inlet->Initialize != NULL), "inlet handler should expose Initialize"));
333 PetscCall(PicurvAssertBool((PetscBool)(inlet->Apply != NULL), "inlet handler should expose Apply"));
334 PetscCall(PicurvAssertBool((PetscBool)(inlet->Destroy != NULL), "inlet handler should expose its destroy hook"));
335 PetscCall(DestroyBoundaryHandler(&inlet));
336
337 PetscFunctionReturn(0);
338}
339/**
340 * @brief Tests the implemented-handler matrix exposed by the factory.
341 */
342
344{
345 struct HandlerExpectation {
346 BCHandlerType handler;
347 BCPriorityType priority;
348 PetscBool expect_apply;
349 PetscBool expect_initialize;
350 };
351 const struct HandlerExpectation expectations[] = {
352 {BC_HANDLER_WALL_NOSLIP, BC_PRIORITY_WALL, PETSC_TRUE, PETSC_FALSE},
353 {BC_HANDLER_INLET_CONSTANT_VELOCITY, BC_PRIORITY_INLET, PETSC_TRUE, PETSC_TRUE},
354 {BC_HANDLER_INLET_PARABOLIC, BC_PRIORITY_INLET, PETSC_TRUE, PETSC_TRUE},
355 {BC_HANDLER_INLET_PROFILE_FROM_FILE, BC_PRIORITY_INLET, PETSC_TRUE, PETSC_TRUE},
356 {BC_HANDLER_OUTLET_CONSERVATION, BC_PRIORITY_OUTLET, PETSC_TRUE, PETSC_FALSE},
357 };
358
359 PetscFunctionBeginUser;
360 for (size_t i = 0; i < sizeof(expectations) / sizeof(expectations[0]); ++i) {
361 BoundaryCondition *bc = NULL;
362 PetscCall(BoundaryCondition_Create(expectations[i].handler, &bc));
363 PetscCall(PicurvAssertBool((PetscBool)(bc != NULL), "factory should allocate a handler object"));
364 PetscCall(PicurvAssertIntEqual(expectations[i].priority, bc->priority, "handler priority should match expectation"));
365 PetscCall(PicurvAssertBool((PetscBool)((bc->Apply != NULL) == expectations[i].expect_apply), "Apply hook expectation mismatch"));
366 PetscCall(PicurvAssertBool((PetscBool)((bc->Initialize != NULL) == expectations[i].expect_initialize), "Initialize hook expectation mismatch"));
367 PetscCall(DestroyBoundaryHandler(&bc));
368 }
369 PetscFunctionReturn(0);
370}
371
372/**
373 * @brief Writes a tiny canonical PICSLICE profile for boundary handler tests.
374 */
375static PetscErrorCode WritePicSliceForTests(const char *path, PetscInt n1, PetscInt n2, PetscReal base)
376{
377 FILE *fd = NULL;
378
379 PetscFunctionBeginUser;
380 fd = fopen(path, "w");
381 PetscCheck(fd != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Could not open %s for writing.", path);
382 PetscCheck(fprintf(fd, "PICSLICE\n1\n%d %d\n", (int)n1, (int)n2) >= 0,
383 PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE, "Could not write PICSLICE header.");
384 for (PetscInt a = 0; a < n1; a++) {
385 for (PetscInt b = 0; b < n2; b++) {
386 PetscReal value = base + (PetscReal)(10 * a + b);
387 PetscCheck(fprintf(fd, "%.16e\n", (double)value) >= 0,
388 PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE, "Could not write PICSLICE value.");
389 }
390 }
391 fclose(fd);
392 PetscFunctionReturn(0);
393}
394
395/**
396 * @brief Tests prescribed-flow inlet handler loading and applying one Z-face profile.
397 */
399{
400 SimCtx *simCtx = NULL;
401 UserCtx *user = NULL;
402 BoundaryCondition *bc = NULL;
403 BCContext ctx;
404 Cmpnts ***ucont = NULL;
405 Cmpnts ***ubcs = NULL;
406 char profile_path[PETSC_MAX_PATH_LEN];
407 PetscReal local_inflow = 0.0;
408 PetscReal local_outflow = 0.0;
409
410 PetscFunctionBeginUser;
411 PetscCall(PetscMemzero(&ctx, sizeof(ctx)));
412 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 5, 5, 5));
413 PetscCall(PicurvPopulateIdentityMetrics(user));
414
415 PetscCall(PetscSNPrintf(profile_path, sizeof(profile_path), "/tmp/picurv_unit_profile_%d.picslice", (int)getpid()));
416 PetscCall(WritePicSliceForTests(profile_path, user->JM - 1, user->IM - 1, 2.0));
417
421 PetscCall(AppendBCParam(&user->boundary_faces[BC_FACE_NEG_Z].params, "source_file", profile_path));
422
423 PetscCall(VecSet(user->Ucont, 0.0));
424 PetscCall(VecSet(user->Bcs.Ubcs, 0.0));
425
426 ctx.user = user;
429 PetscCall(bc->PreStep(bc, &ctx, &local_inflow, &local_outflow));
430 PetscCall(PicurvAssertRealNear(0.0, local_inflow, 1.0e-12, "profile inlet PreStep should leave inflow unchanged"));
431 PetscCall(PicurvAssertRealNear(0.0, local_outflow, 1.0e-12, "profile inlet PreStep should leave outflow unchanged"));
432 PetscCall(bc->Initialize(bc, &ctx));
433 PetscCall(bc->PostStep(bc, &ctx, &local_inflow, &local_outflow));
434
435 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucont, &ucont));
436 PetscCall(DMDAVecGetArrayRead(user->fda, user->Bcs.Ubcs, &ubcs));
437 PetscCall(PicurvAssertRealNear(2.0, ucont[0][1][1].z, 1.0e-12, "profile inlet should apply first scalar speed"));
438 PetscCall(PicurvAssertRealNear(13.0, ucont[0][2][2].z, 1.0e-12, "profile inlet should preserve PICSLICE ordering"));
439 PetscCall(PicurvAssertRealNear(24.0, ubcs[0][3][3].z, 1.0e-12, "profile inlet should write boundary velocity"));
440 PetscCall(PicurvAssertRealNear(35.0, ucont[0][4][4].z, 1.0e-12, "profile inlet should consume the last face-cell slot"));
441 PetscCall(PicurvAssertRealNear(35.0, ubcs[0][4][4].z, 1.0e-12, "profile inlet should write the last face-cell boundary value"));
442 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucont, &ucont));
443 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Bcs.Ubcs, &ubcs));
444 PetscCall(PicurvAssertBool((PetscBool)(local_inflow > 0.0), "profile inlet PostStep should accumulate positive negative-face flux"));
445 PetscCall(PicurvAssertRealNear(0.0, local_outflow, 1.0e-12, "profile inlet should not add to outflow"));
446
447 PetscCall(DestroyBoundaryHandler(&bc));
449 user->boundary_faces[BC_FACE_NEG_Z].params = NULL;
450 remove(profile_path);
451 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
452 PetscFunctionReturn(0);
453}
454/**
455 * @brief Tests that unsupported handler types are rejected by the factory.
456 */
457
459{
460 BoundaryCondition *bc = NULL;
461 PetscErrorCode ierr_create;
462
463 PetscFunctionBeginUser;
464 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
466 PetscCall(PetscPopErrorHandler());
467
468 PetscCall(PicurvAssertBool((PetscBool)(ierr_create != 0), "unsupported handler should return a non-zero error code"));
469 if (bc) {
470 PetscCall(PetscFree(bc));
471 }
472 PetscFunctionReturn(0);
473}
474/**
475 * @brief Tests deterministic inlet-face grid-location helpers across all faces.
476 */
477
479{
480 SimCtx *simCtx = NULL;
481 UserCtx *user = NULL;
482
483 PetscFunctionBeginUser;
484 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 8, 8, 8));
485 simCtx->np = 128;
486
487 for (BCFace face = BC_FACE_NEG_X; face <= BC_FACE_POS_Z; ++face) {
488 PetscInt ci = -1, cj = -1, ck = -1;
489 PetscReal xi = -1.0, eta = -1.0, zta = -1.0;
490 PetscBool placed = PETSC_FALSE;
491
492 user->identifiedInletBCFace = face;
494 user, &user->info,
495 user->info.xs, user->info.ys, user->info.zs,
496 user->info.mx, user->info.my, user->info.mz,
497 0,
498 &ci, &cj, &ck, &xi, &eta, &zta, &placed));
499
500 PetscCall(PicurvAssertBool(placed, "single-rank deterministic face placement should succeed"));
501 PetscCall(PicurvAssertBool((PetscBool)(ci >= user->info.xs && ci < user->info.xs + user->info.xm), "ci must map to owned node window"));
502 PetscCall(PicurvAssertBool((PetscBool)(cj >= user->info.ys && cj < user->info.ys + user->info.ym), "cj must map to owned node window"));
503 PetscCall(PicurvAssertBool((PetscBool)(ck >= user->info.zs && ck < user->info.zs + user->info.zm), "ck must map to owned node window"));
504 PetscCall(PicurvAssertBool((PetscBool)(xi >= 0.0 && xi < 1.0), "xi should be in [0,1)"));
505 PetscCall(PicurvAssertBool((PetscBool)(eta >= 0.0 && eta < 1.0), "eta should be in [0,1)"));
506 PetscCall(PicurvAssertBool((PetscBool)(zta >= 0.0 && zta < 1.0), "zta should be in [0,1)"));
507
508 if (face == BC_FACE_NEG_X || face == BC_FACE_POS_X) {
509 PetscCall(PicurvAssertRealNear(0.5, xi, 1.0e-10, "deterministic x-face placement should sit halfway into boundary-adjacent cell"));
510 } else if (face == BC_FACE_NEG_Y || face == BC_FACE_POS_Y) {
511 PetscCall(PicurvAssertRealNear(0.5, eta, 1.0e-10, "deterministic y-face placement should sit halfway into boundary-adjacent cell"));
512 } else {
513 PetscCall(PicurvAssertRealNear(0.5, zta, 1.0e-10, "deterministic z-face placement should sit halfway into boundary-adjacent cell"));
514 }
515 }
516
517 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
518 PetscFunctionReturn(0);
519}
520/**
521 * @brief Tests random inlet-face cell selection across all supported faces.
522 */
523
525{
526 SimCtx *simCtx = NULL;
527 UserCtx *user = NULL;
528 PetscRandom rand_i = NULL, rand_j = NULL, rand_k = NULL;
529
530 PetscFunctionBeginUser;
531 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 8, 8, 8));
532
533 PetscCall(PetscRandomCreate(PETSC_COMM_WORLD, &rand_i));
534 PetscCall(PetscRandomCreate(PETSC_COMM_WORLD, &rand_j));
535 PetscCall(PetscRandomCreate(PETSC_COMM_WORLD, &rand_k));
536 PetscCall(PetscRandomSetInterval(rand_i, 0.0, 1.0));
537 PetscCall(PetscRandomSetInterval(rand_j, 0.0, 1.0));
538 PetscCall(PetscRandomSetInterval(rand_k, 0.0, 1.0));
539 PetscCall(PetscRandomSetFromOptions(rand_i));
540 PetscCall(PetscRandomSetFromOptions(rand_j));
541 PetscCall(PetscRandomSetFromOptions(rand_k));
542
543 for (BCFace face = BC_FACE_NEG_X; face <= BC_FACE_POS_Z; ++face) {
544 PetscInt ci = -1, cj = -1, ck = -1;
545 PetscReal xi = -1.0, eta = -1.0, zta = -1.0;
546 const PetscReal boundary_eps = 5.0e-4;
547
548 user->identifiedInletBCFace = face;
550 user, &user->info,
551 user->info.xs, user->info.ys, user->info.zs,
552 user->info.mx, user->info.my, user->info.mz,
553 &rand_i, &rand_j, &rand_k,
554 &ci, &cj, &ck,
555 &xi, &eta, &zta));
556
557 PetscCall(PicurvAssertBool((PetscBool)(ci >= user->info.xs && ci < user->info.xs + user->info.xm), "ci must map to owned node window"));
558 PetscCall(PicurvAssertBool((PetscBool)(cj >= user->info.ys && cj < user->info.ys + user->info.ym), "cj must map to owned node window"));
559 PetscCall(PicurvAssertBool((PetscBool)(ck >= user->info.zs && ck < user->info.zs + user->info.zm), "ck must map to owned node window"));
560 PetscCall(PicurvAssertBool((PetscBool)(xi >= 0.0 && xi <= 1.0), "xi should be in [0,1]"));
561 PetscCall(PicurvAssertBool((PetscBool)(eta >= 0.0 && eta <= 1.0), "eta should be in [0,1]"));
562 PetscCall(PicurvAssertBool((PetscBool)(zta >= 0.0 && zta <= 1.0), "zta should be in [0,1]"));
563
564 switch (face) {
565 case BC_FACE_NEG_X:
566 PetscCall(PicurvAssertBool((PetscBool)(xi <= boundary_eps), "NEG_X should pin xi near 0"));
567 break;
568 case BC_FACE_POS_X:
569 PetscCall(PicurvAssertBool((PetscBool)(xi >= 1.0 - boundary_eps), "POS_X should pin xi near 1"));
570 break;
571 case BC_FACE_NEG_Y:
572 PetscCall(PicurvAssertBool((PetscBool)(eta <= boundary_eps), "NEG_Y should pin eta near 0"));
573 break;
574 case BC_FACE_POS_Y:
575 PetscCall(PicurvAssertBool((PetscBool)(eta >= 1.0 - boundary_eps), "POS_Y should pin eta near 1"));
576 break;
577 case BC_FACE_NEG_Z:
578 PetscCall(PicurvAssertBool((PetscBool)(zta <= boundary_eps), "NEG_Z should pin zta near 0"));
579 break;
580 case BC_FACE_POS_Z:
581 PetscCall(PicurvAssertBool((PetscBool)(zta >= 1.0 - boundary_eps), "POS_Z should pin zta near 1"));
582 break;
583 }
584 }
585
586 PetscCall(PetscRandomDestroy(&rand_i));
587 PetscCall(PetscRandomDestroy(&rand_j));
588 PetscCall(PetscRandomDestroy(&rand_k));
589 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
590 PetscFunctionReturn(0);
591}
592/**
593 * @brief Tests constant inlet handler initialization and face writes on a Z inlet.
594 */
595
597{
598 SimCtx *simCtx = NULL;
599 UserCtx *user = NULL;
600 BoundaryCondition *bc = NULL;
601 BCContext ctx;
602 Cmpnts ***ucont = NULL;
603 Cmpnts ***ubcs = NULL;
604 PetscReal local_inflow = 0.0;
605 PetscReal local_outflow = 0.0;
606 PetscReal expected_flux = 0.0;
607
608 PetscFunctionBeginUser;
609 PetscCall(PetscMemzero(&ctx, sizeof(ctx)));
610 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 6, 6, 6));
611 PetscCall(VecSet(user->Ucont, 0.0));
612 PetscCall(VecSet(user->Bcs.Ubcs, 0.0));
613 PetscCall(PicurvPopulateIdentityMetrics(user));
614
618 PetscCall(AppendBCParam(&user->boundary_faces[BC_FACE_NEG_Z].params, "vz", "2.5"));
619
620 ctx.user = user;
623 PetscCall(bc->Initialize(bc, &ctx));
624 PetscCall(bc->PostStep(bc, &ctx, &local_inflow, &local_outflow));
625
626 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucont, &ucont));
627 PetscCall(DMDAVecGetArrayRead(user->fda, user->Bcs.Ubcs, &ubcs));
628 PetscCall(PicurvAssertRealNear(2.5, ucont[0][3][3].z, 1.0e-12, "constant inlet should set Ucont normal component"));
629 PetscCall(PicurvAssertRealNear(2.5, ubcs[0][3][3].z, 1.0e-12, "constant inlet should set Ubcs normal component"));
630 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucont, &ucont));
631 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Bcs.Ubcs, &ubcs));
632
633 expected_flux = (PetscReal)(user->info.mx - 2) * (PetscReal)(user->info.my - 2) * 2.5;
634 PetscCall(PicurvAssertRealNear(expected_flux, local_inflow, 1.0e-12, "constant inlet PostStep should sum the face flux"));
635 PetscCall(PicurvAssertRealNear(0.0, local_outflow, 1.0e-12, "constant inlet should not contribute to outflow accumulation"));
636
637 PetscCall(DestroyBoundaryHandler(&bc));
639 user->boundary_faces[BC_FACE_NEG_Z].params = NULL;
640 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
641 PetscFunctionReturn(0);
642}
643
644/**
645 * @brief Tests wall no-slip application across the full face matrix.
646 */
647static PetscErrorCode TestWallNoSlipHandlerFaceMatrix(void)
648{
649 SimCtx *simCtx = NULL;
650 UserCtx *user = NULL;
651 BoundaryCondition *bc = NULL;
652 BCContext ctx;
653 Cmpnts ***ucont = NULL;
654 Cmpnts ***ubcs = NULL;
655 const BCFace faces[] = {
659 };
660
661 PetscFunctionBeginUser;
662 PetscCall(PetscMemzero(&ctx, sizeof(ctx)));
663 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 6, 6, 6));
665
666 for (size_t n = 0; n < sizeof(faces) / sizeof(faces[0]); ++n) {
667 const BCFace face = faces[n];
668 PetscInt ucont_k, ucont_j, ucont_i, ubcs_k, ubcs_j, ubcs_i;
669
670 PetscCall(VecSet(user->Ucont, 5.0));
671 PetscCall(VecSet(user->Bcs.Ubcs, 7.0));
672 ctx.user = user;
673 ctx.face_id = face;
674 PetscCall(bc->Apply(bc, &ctx));
675
676 PetscCall(GetRepresentativeFaceSlots(user, face, &ucont_k, &ucont_j, &ucont_i, &ubcs_k, &ubcs_j, &ubcs_i));
677 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucont, &ucont));
678 PetscCall(DMDAVecGetArrayRead(user->fda, user->Bcs.Ubcs, &ubcs));
679 PetscCall(PicurvAssertRealNear(0.0, GetFaceNormalComponent(ucont[ucont_k][ucont_j][ucont_i], face), 1.0e-12, "wall no-slip should zero the normal flux component"));
680 PetscCall(PicurvAssertRealNear(0.0, ubcs[ubcs_k][ubcs_j][ubcs_i].x, 1.0e-12, "wall no-slip should zero Ubcs.x"));
681 PetscCall(PicurvAssertRealNear(0.0, ubcs[ubcs_k][ubcs_j][ubcs_i].y, 1.0e-12, "wall no-slip should zero Ubcs.y"));
682 PetscCall(PicurvAssertRealNear(0.0, ubcs[ubcs_k][ubcs_j][ubcs_i].z, 1.0e-12, "wall no-slip should zero Ubcs.z"));
683 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucont, &ucont));
684 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Bcs.Ubcs, &ubcs));
685 }
686
687 PetscCall(DestroyBoundaryHandler(&bc));
688 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
689 PetscFunctionReturn(0);
690}
691
692/**
693 * @brief Tests constant-inlet initialization, flux accounting, and face writes across all faces.
694 */
696{
697 struct ConstantInletCase {
698 BCFace face;
699 PetscReal velocity;
700 const char *value_text;
701 };
702 const struct ConstantInletCase cases[] = {
703 {BC_FACE_NEG_X, 1.5, "1.5"},
704 {BC_FACE_POS_X, 2.0, "2.0"},
705 {BC_FACE_NEG_Y, 2.5, "2.5"},
706 {BC_FACE_POS_Y, 3.0, "3.0"},
707 {BC_FACE_NEG_Z, 3.5, "3.5"},
708 {BC_FACE_POS_Z, 4.0, "4.0"},
709 };
710 SimCtx *simCtx = NULL;
711 UserCtx *user = NULL;
712 BCContext ctx;
713 Cmpnts ***ucont = NULL;
714 Cmpnts ***ubcs = NULL;
715
716 PetscFunctionBeginUser;
717 PetscCall(PetscMemzero(&ctx, sizeof(ctx)));
718 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 6, 6, 6));
719 PetscCall(PicurvPopulateIdentityMetrics(user));
720
721 for (size_t n = 0; n < sizeof(cases) / sizeof(cases[0]); ++n) {
722 const struct ConstantInletCase *test_case = &cases[n];
723 BoundaryCondition *bc = NULL;
724 PetscReal local_inflow = 0.0;
725 PetscReal local_outflow = 0.0;
726 PetscInt ucont_k, ucont_j, ucont_i, ubcs_k, ubcs_j, ubcs_i;
727 const PetscReal expected_value = GetFaceOrientationSign(test_case->face) * test_case->velocity;
728 const PetscReal expected_flux = GetFaceInteriorPointCount(user, test_case->face) * expected_value;
729
730 PetscCall(VecSet(user->Ucont, 0.0));
731 PetscCall(VecSet(user->Bcs.Ubcs, 0.0));
732 ResetBoundaryFaceConfig(&user->boundary_faces[test_case->face]);
733 user->boundary_faces[test_case->face].face_id = test_case->face;
734 user->boundary_faces[test_case->face].mathematical_type = INLET;
736 PetscCall(AppendBCParam(&user->boundary_faces[test_case->face].params,
737 GetInletParamKey(test_case->face),
738 test_case->value_text));
739
740 ctx.user = user;
741 ctx.face_id = test_case->face;
743 PetscCall(bc->PreStep(bc, &ctx, &local_inflow, &local_outflow));
744 PetscCall(PicurvAssertRealNear(0.0, local_inflow, 1.0e-12, "constant inlet PreStep should leave inflow unchanged"));
745 PetscCall(PicurvAssertRealNear(0.0, local_outflow, 1.0e-12, "constant inlet PreStep should leave outflow unchanged"));
746 PetscCall(bc->Initialize(bc, &ctx));
747 PetscCall(bc->PostStep(bc, &ctx, &local_inflow, &local_outflow));
748
749 PetscCall(GetRepresentativeFaceSlots(user, test_case->face, &ucont_k, &ucont_j, &ucont_i, &ubcs_k, &ubcs_j, &ubcs_i));
750 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucont, &ucont));
751 PetscCall(DMDAVecGetArrayRead(user->fda, user->Bcs.Ubcs, &ubcs));
752 PetscCall(PicurvAssertRealNear(expected_value, GetFaceNormalComponent(ucont[ucont_k][ucont_j][ucont_i], test_case->face), 1.0e-12, "constant inlet should set the face-normal Ucont component"));
753 PetscCall(PicurvAssertRealNear(expected_value, GetFaceNormalComponent(ubcs[ubcs_k][ubcs_j][ubcs_i], test_case->face), 1.0e-12, "constant inlet should set the face-normal Ubcs component"));
754 PetscCall(PicurvAssertRealNear((test_case->face == BC_FACE_NEG_X || test_case->face == BC_FACE_POS_X) ? expected_value : 0.0, ubcs[ubcs_k][ubcs_j][ubcs_i].x, 1.0e-12, "constant inlet should only set the expected Ubcs axis"));
755 PetscCall(PicurvAssertRealNear((test_case->face == BC_FACE_NEG_Y || test_case->face == BC_FACE_POS_Y) ? expected_value : 0.0, ubcs[ubcs_k][ubcs_j][ubcs_i].y, 1.0e-12, "constant inlet should only set the expected Ubcs axis"));
756 PetscCall(PicurvAssertRealNear((test_case->face == BC_FACE_NEG_Z || test_case->face == BC_FACE_POS_Z) ? expected_value : 0.0, ubcs[ubcs_k][ubcs_j][ubcs_i].z, 1.0e-12, "constant inlet should only set the expected Ubcs axis"));
757 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucont, &ucont));
758 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Bcs.Ubcs, &ubcs));
759 PetscCall(PicurvAssertRealNear(expected_flux, local_inflow, 1.0e-12, "constant inlet PostStep should integrate the face flux with orientation"));
760 PetscCall(PicurvAssertRealNear(0.0, local_outflow, 1.0e-12, "constant inlet should not add to outflow"));
761
762 PetscCall(DestroyBoundaryHandler(&bc));
763 ResetBoundaryFaceConfig(&user->boundary_faces[test_case->face]);
764 }
765
766 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
767 PetscFunctionReturn(0);
768}
769/**
770 * @brief Tests parabolic inlet handler shape on a tiny Z-face.
771 */
772
774{
775 SimCtx *simCtx = NULL;
776 UserCtx *user = NULL;
777 BoundaryCondition *bc = NULL;
778 BCContext ctx;
779 Cmpnts ***ucont = NULL;
780
781 PetscFunctionBeginUser;
782 PetscCall(PetscMemzero(&ctx, sizeof(ctx)));
783 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 6, 6, 6));
784 PetscCall(PicurvPopulateIdentityMetrics(user));
785
789 PetscCall(AppendBCParam(&user->boundary_faces[BC_FACE_NEG_Z].params, "v_max", "4.0"));
790
791 ctx.user = user;
794 PetscCall(bc->Initialize(bc, &ctx));
795
796 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucont, &ucont));
797 PetscCall(PicurvAssertRealNear(4.0, ucont[0][3][3].z, 1.0e-12, "parabolic inlet should peak at the face centerline"));
798 PetscCall(PicurvAssertRealNear(0.0, ucont[0][1][1].z, 1.0e-12, "parabolic inlet should vanish at wall-adjacent locations"));
799 PetscCall(PicurvAssertBool((PetscBool)(ucont[0][3][3].z > ucont[0][2][2].z), "parabolic inlet centerline should exceed off-center velocity"));
800 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucont, &ucont));
801
802 PetscCall(DestroyBoundaryHandler(&bc));
804 user->boundary_faces[BC_FACE_NEG_Z].params = NULL;
805 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
806 PetscFunctionReturn(0);
807}
808
809/**
810 * @brief Tests parabolic-inlet centerline, wall, and flux behavior across all faces.
811 */
813{
814 const BCFace faces[] = {
818 };
819 SimCtx *simCtx = NULL;
820 UserCtx *user = NULL;
821 BCContext ctx;
822 Cmpnts ***ucont = NULL;
823 Cmpnts ***ubcs = NULL;
824
825 PetscFunctionBeginUser;
826 PetscCall(PetscMemzero(&ctx, sizeof(ctx)));
827 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 6, 6, 6));
828 PetscCall(PicurvPopulateIdentityMetrics(user));
829
830 for (size_t n = 0; n < sizeof(faces) / sizeof(faces[0]); ++n) {
831 const BCFace face = faces[n];
832 BoundaryCondition *bc = NULL;
833 PetscReal local_inflow = 0.0;
834 PetscReal local_outflow = 0.0;
835 PetscInt center_k, center_j, center_i;
836 PetscInt off_k, off_j, off_i;
837 PetscInt wall_k, wall_j, wall_i;
838 PetscInt ubcs_center_k, ubcs_center_j, ubcs_center_i;
839 const PetscReal expected_center = GetFaceOrientationSign(face) * 4.0;
840
841 PetscCall(VecSet(user->Ucont, 0.0));
842 PetscCall(VecSet(user->Bcs.Ubcs, 0.0));
844 user->boundary_faces[face].face_id = face;
847 PetscCall(AppendBCParam(&user->boundary_faces[face].params, "v_max", "4.0"));
848
849 ctx.user = user;
850 ctx.face_id = face;
852 PetscCall(bc->PreStep(bc, &ctx, &local_inflow, &local_outflow));
853 PetscCall(PicurvAssertRealNear(0.0, local_inflow, 1.0e-12, "parabolic inlet PreStep should leave inflow unchanged"));
854 PetscCall(PicurvAssertRealNear(0.0, local_outflow, 1.0e-12, "parabolic inlet PreStep should leave outflow unchanged"));
855 PetscCall(bc->Initialize(bc, &ctx));
856 PetscCall(bc->PostStep(bc, &ctx, &local_inflow, &local_outflow));
857
858 PetscCall(GetParabolicSampleSlots(user, face,
859 &center_k, &center_j, &center_i,
860 &off_k, &off_j, &off_i,
861 &wall_k, &wall_j, &wall_i,
862 &ubcs_center_k, &ubcs_center_j, &ubcs_center_i));
863 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucont, &ucont));
864 PetscCall(DMDAVecGetArrayRead(user->fda, user->Bcs.Ubcs, &ubcs));
865 PetscCall(PicurvAssertRealNear(expected_center, GetFaceNormalComponent(ucont[center_k][center_j][center_i], face), 1.0e-12, "parabolic inlet should peak at the face centerline"));
866 PetscCall(PicurvAssertRealNear(expected_center, GetFaceNormalComponent(ubcs[ubcs_center_k][ubcs_center_j][ubcs_center_i], face), 1.0e-12, "parabolic inlet should write the centerline boundary velocity"));
867 PetscCall(PicurvAssertRealNear(0.0, GetFaceNormalComponent(ucont[wall_k][wall_j][wall_i], face), 1.0e-12, "parabolic inlet should vanish at the wall"));
868 PetscCall(PicurvAssertBool((PetscBool)(PetscAbsReal(GetFaceNormalComponent(ucont[center_k][center_j][center_i], face)) >
869 PetscAbsReal(GetFaceNormalComponent(ucont[off_k][off_j][off_i], face))),
870 "parabolic inlet centerline magnitude should exceed the off-center magnitude"));
871 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucont, &ucont));
872 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Bcs.Ubcs, &ubcs));
873 PetscCall(PicurvAssertBool((PetscBool)(PetscAbsReal(local_inflow) > 0.0), "parabolic inlet PostStep should accumulate a non-zero flux"));
874 PetscCall(PicurvAssertBool((PetscBool)((local_inflow > 0.0) == (GetFaceOrientationSign(face) > 0.0)),
875 "parabolic inlet PostStep should preserve the face orientation"));
876 PetscCall(PicurvAssertRealNear(0.0, local_outflow, 1.0e-12, "parabolic inlet should not add to outflow"));
877
878 PetscCall(DestroyBoundaryHandler(&bc));
880 }
881
882 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
883 PetscFunctionReturn(0);
884}
885/**
886 * @brief Tests outlet conservation handler correction and post-step flux accounting.
887 */
888
889static PetscErrorCode TestOutletConservationHandlerBehavior(void)
890{
891 SimCtx *simCtx = NULL;
892 UserCtx *user = NULL;
893 BoundaryCondition *bc = NULL;
894 BCContext ctx;
895 PetscReal global_inflow = 30.0;
896 PetscReal global_farfield_in = 0.0;
897 PetscReal global_farfield_out = 0.0;
898 PetscReal global_outflow = 25.0;
899 PetscReal local_inflow = 0.0;
900 PetscReal local_outflow = 0.0;
901 Cmpnts ***ucont = NULL;
902 Cmpnts ***ucat = NULL;
903
904 PetscFunctionBeginUser;
905 PetscCall(PetscMemzero(&ctx, sizeof(ctx)));
906 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 6, 6, 6));
907 PetscCall(PicurvPopulateIdentityMetrics(user));
908 PetscCall(VecSet(user->Ucat, 1.0));
909 PetscCall(DMGlobalToLocalBegin(user->fda, user->Ucat, INSERT_VALUES, user->lUcat));
910 PetscCall(DMGlobalToLocalEnd(user->fda, user->Ucat, INSERT_VALUES, user->lUcat));
911 simCtx->AreaOutSum = (PetscReal)(user->info.mx - 2) * (PetscReal)(user->info.my - 2);
912
916
917 ctx.user = user;
919 ctx.global_inflow_sum = &global_inflow;
920 ctx.global_farfield_inflow_sum = &global_farfield_in;
921 ctx.global_farfield_outflow_sum = &global_farfield_out;
922 ctx.global_outflow_sum = &global_outflow;
923
925 PetscCall(bc->Apply(bc, &ctx));
926 PetscCall(bc->PostStep(bc, &ctx, &local_inflow, &local_outflow));
927
928 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucont, &ucont));
929 PetscCall(DMDAVecGetArrayRead(user->fda, user->lUcat, &ucat));
930 PetscCall(PicurvAssertRealNear(1.2, ucont[5][3][3].z, 1.0e-12, "outlet correction should add the expected flux trim"));
931 PetscCall(PicurvAssertRealNear(1.0, ucat[5][3][3].z, 1.0e-12, "outlet handler should preserve the interior Ucat reference"));
932 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucont, &ucont));
933 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lUcat, &ucat));
934
935 PetscCall(PicurvAssertRealNear(30.0, local_outflow, 1.0e-12, "outlet PostStep should report corrected flux"));
936 PetscCall(PicurvAssertRealNear(0.0, local_inflow, 1.0e-12, "outlet handler should not accumulate inflow"));
937
938 PetscCall(DestroyBoundaryHandler(&bc));
939 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
940 PetscFunctionReturn(0);
941}
942
943/**
944 * @brief Tests outlet-conservation correction and flux accounting across all outlet faces.
945 */
947{
948 const BCFace faces[] = {
952 };
953 SimCtx *simCtx = NULL;
954 UserCtx *user = NULL;
955 BCContext ctx;
956 Cmpnts ***ucont = NULL;
957 Cmpnts ***ubcs = NULL;
958
959 PetscFunctionBeginUser;
960 PetscCall(PetscMemzero(&ctx, sizeof(ctx)));
961 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 6, 6, 6));
962 PetscCall(PicurvPopulateIdentityMetrics(user));
963 PetscCall(VecSet(user->Ucat, 1.0));
964 PetscCall(DMGlobalToLocalBegin(user->fda, user->Ucat, INSERT_VALUES, user->lUcat));
965 PetscCall(DMGlobalToLocalEnd(user->fda, user->Ucat, INSERT_VALUES, user->lUcat));
966
967 for (size_t n = 0; n < sizeof(faces) / sizeof(faces[0]); ++n) {
968 const BCFace face = faces[n];
969 BoundaryCondition *bc = NULL;
970 PetscReal global_inflow = 0.0;
971 PetscReal global_farfield_in = 0.0;
972 PetscReal global_farfield_out = 0.0;
973 PetscReal global_outflow = 0.0;
974 PetscReal local_inflow = 0.0;
975 PetscReal local_outflow = 0.0;
976 PetscReal area = GetFaceInteriorPointCount(user, face);
977 PetscInt ucont_k, ucont_j, ucont_i, ubcs_k, ubcs_j, ubcs_i;
978
979 PetscCall(VecSet(user->Ucont, 0.0));
980 PetscCall(VecSet(user->Bcs.Ubcs, 0.0));
982 user->boundary_faces[face].face_id = face;
985 user->simCtx->AreaOutSum = area;
986
987 ctx.user = user;
988 ctx.face_id = face;
989 ctx.global_inflow_sum = &global_inflow;
990 ctx.global_farfield_inflow_sum = &global_farfield_in;
991 ctx.global_farfield_outflow_sum = &global_farfield_out;
992 ctx.global_outflow_sum = &global_outflow;
993
995 PetscCall(bc->PreStep(bc, &ctx, &local_inflow, &local_outflow));
996 PetscCall(PicurvAssertRealNear(area, local_outflow, 1.0e-12, "outlet PreStep should measure the uncorrected face flux"));
997
998 global_inflow = area + 0.2 * area;
999 global_outflow = local_outflow;
1000 local_outflow = 0.0;
1001 PetscCall(bc->Apply(bc, &ctx));
1002 PetscCall(bc->PostStep(bc, &ctx, &local_inflow, &local_outflow));
1003
1004 PetscCall(GetRepresentativeFaceSlots(user, face, &ucont_k, &ucont_j, &ucont_i, &ubcs_k, &ubcs_j, &ubcs_i));
1005 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucont, &ucont));
1006 PetscCall(DMDAVecGetArrayRead(user->fda, user->Bcs.Ubcs, &ubcs));
1007 PetscCall(PicurvAssertRealNear(1.2, GetFaceNormalComponent(ucont[ucont_k][ucont_j][ucont_i], face), 1.0e-12, "outlet correction should add the configured flux trim"));
1008 PetscCall(PicurvAssertRealNear(1.0, ubcs[ubcs_k][ubcs_j][ubcs_i].x, 1.0e-12, "outlet handler should copy Ucat into Ubcs.x"));
1009 PetscCall(PicurvAssertRealNear(1.0, ubcs[ubcs_k][ubcs_j][ubcs_i].y, 1.0e-12, "outlet handler should copy Ucat into Ubcs.y"));
1010 PetscCall(PicurvAssertRealNear(1.0, ubcs[ubcs_k][ubcs_j][ubcs_i].z, 1.0e-12, "outlet handler should copy Ucat into Ubcs.z"));
1011 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucont, &ucont));
1012 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Bcs.Ubcs, &ubcs));
1013 PetscCall(PicurvAssertRealNear(0.0, local_inflow, 1.0e-12, "outlet handler should not add to inflow"));
1014 PetscCall(PicurvAssertRealNear(1.2 * area, local_outflow, 1.0e-12, "outlet PostStep should report the corrected flux"));
1015
1016 PetscCall(DestroyBoundaryHandler(&bc));
1018 }
1019
1020 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1021 PetscFunctionReturn(0);
1022}
1023/**
1024 * @brief Runs the unit-boundaries PETSc test binary.
1025 */
1026
1027int main(int argc, char **argv)
1028{
1029 PetscErrorCode ierr;
1030 const PicurvTestCase cases[] = {
1031 {"can-rank-service-face-matches-inlet-when-defined", TestCanRankServiceFaceMatchesInletWhenDefined},
1032 {"can-rank-service-inlet-face-requires-definition", TestCanRankServiceInletFaceRequiresDefinition},
1033 {"boundary-condition-factory-assignments", TestBoundaryConditionFactoryAssignments},
1034 {"boundary-condition-factory-implemented-handler-matrix", TestBoundaryConditionFactoryImplementedHandlerMatrix},
1035 {"boundary-condition-factory-rejects-unsupported-handler", TestBoundaryConditionFactoryRejectsUnsupportedHandler},
1036 {"deterministic-face-grid-location-matrix", TestGetDeterministicFaceGridLocationFaceMatrix},
1037 {"random-inlet-face-location-matrix", TestGetRandomCellAndLogicalCoordsOnInletFaceMatrix},
1038 {"wall-no-slip-handler-face-matrix", TestWallNoSlipHandlerFaceMatrix},
1039 {"inlet-constant-velocity-handler-behavior", TestInletConstantVelocityHandlerBehavior},
1040 {"inlet-constant-velocity-handler-face-matrix", TestInletConstantVelocityHandlerFaceMatrix},
1041 {"inlet-parabolic-profile-handler-behavior", TestInletParabolicProfileHandlerBehavior},
1042 {"inlet-parabolic-profile-handler-face-matrix", TestInletParabolicProfileHandlerFaceMatrix},
1043 {"inlet-profile-from-file-handler-behavior", TestInletProfileFromFileHandlerBehavior},
1044 {"outlet-conservation-handler-behavior", TestOutletConservationHandlerBehavior},
1045 {"outlet-conservation-handler-face-matrix", TestOutletConservationHandlerFaceMatrix},
1046 };
1047
1048 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv boundary tests");
1049 if (ierr) {
1050 return (int)ierr;
1051 }
1052
1053 ierr = PicurvRunTests("unit-boundaries", cases, sizeof(cases) / sizeof(cases[0]));
1054 if (ierr) {
1055 PetscFinalize();
1056 return (int)ierr;
1057 }
1058
1059 ierr = PetscFinalize();
1060 return (int)ierr;
1061}
PetscErrorCode GetRandomCellAndLogicalCoordsOnInletFace(UserCtx *user, const DMDALocalInfo *info, PetscInt xs_gnode_rank, PetscInt ys_gnode_rank, PetscInt zs_gnode_rank, PetscInt IM_nodes_global, PetscInt JM_nodes_global, PetscInt KM_nodes_global, PetscRandom *rand_logic_i_ptr, PetscRandom *rand_logic_j_ptr, PetscRandom *rand_logic_k_ptr, PetscInt *ci_metric_lnode_out, PetscInt *cj_metric_lnode_out, PetscInt *ck_metric_lnode_out, PetscReal *xi_metric_logic_out, PetscReal *eta_metric_logic_out, PetscReal *zta_metric_logic_out)
Assuming the current rank services the inlet face, this function selects a random cell (owned by this...
Definition Boundaries.c:399
PetscErrorCode BoundaryCondition_Create(BCHandlerType handler_type, BoundaryCondition **new_bc_ptr)
(Private) Creates and configures a specific BoundaryCondition handler object.
Definition Boundaries.c:744
PetscErrorCode CanRankServiceInletFace(UserCtx *user, const DMDALocalInfo *info, PetscInt IM_nodes_global, PetscInt JM_nodes_global, PetscInt KM_nodes_global, PetscBool *can_service_inlet_out)
Determines if the current MPI rank owns any part of the globally defined inlet face,...
Definition Boundaries.c:11
PetscErrorCode CanRankServiceFace(const DMDALocalInfo *info, PetscInt IM_nodes_global, PetscInt JM_nodes_global, PetscInt KM_nodes_global, BCFace face_id, PetscBool *can_service_out)
Determines if the current MPI rank owns any part of a specified global face.
Definition Boundaries.c:126
PetscErrorCode GetDeterministicFaceGridLocation(UserCtx *user, const DMDALocalInfo *info, PetscInt xs_gnode_rank, PetscInt ys_gnode_rank, PetscInt zs_gnode_rank, PetscInt IM_cells_global, PetscInt JM_cells_global, PetscInt KM_cells_global, PetscInt64 particle_global_id, PetscInt *ci_metric_lnode_out, PetscInt *cj_metric_lnode_out, PetscInt *ck_metric_lnode_out, PetscReal *xi_metric_logic_out, PetscReal *eta_metric_logic_out, PetscReal *zta_metric_logic_out, PetscBool *placement_successful_out)
Places particles in a deterministic grid/raster pattern on a specified domain face.
Definition Boundaries.c:212
void FreeBC_ParamList(BC_Param *head)
Frees an entire linked list of boundary-condition parameters.
Definition io.c:302
The "virtual table" struct for a boundary condition handler object.
Definition variables.h:321
PetscErrorCode(* PostStep)(BoundaryCondition *self, BCContext *ctx, PetscReal *local_inflow, PetscReal *local_outflow)
Definition variables.h:328
PetscErrorCode(* PreStep)(BoundaryCondition *self, BCContext *ctx, PetscReal *local_inflow, PetscReal *local_outflow)
Definition variables.h:326
PetscErrorCode(* Destroy)(BoundaryCondition *self)
Definition variables.h:330
PetscErrorCode(* Initialize)(BoundaryCondition *self, BCContext *ctx)
Definition variables.h:325
PetscErrorCode(* Apply)(BoundaryCondition *self, BCContext *ctx)
Definition variables.h:327
BCPriorityType priority
Definition variables.h:323
static PetscErrorCode TestOutletConservationHandlerFaceMatrix(void)
Tests outlet-conservation correction and flux accounting across all outlet faces.
static PetscErrorCode TestCanRankServiceFaceMatchesInletWhenDefined(void)
Tests that face-service detection matches a defined inlet face.
static PetscErrorCode TestBoundaryConditionFactoryAssignments(void)
Tests the boundary-condition factory assignments for representative handlers.
static PetscErrorCode AppendBCParam(BC_Param **head, const char *key, const char *value)
Appends one key/value pair to a linked list of boundary-condition parameters.
static PetscReal GetFaceInteriorPointCount(const UserCtx *user, BCFace face)
Computes the number of face-interior sample points for a given boundary face.
static PetscErrorCode TestInletProfileFromFileHandlerBehavior(void)
Tests prescribed-flow inlet handler loading and applying one Z-face profile.
int main(int argc, char **argv)
Runs the unit-boundaries PETSc test binary.
static PetscErrorCode TestInletParabolicProfileHandlerBehavior(void)
Tests parabolic inlet handler shape on a tiny Z-face.
static PetscReal GetFaceOrientationSign(BCFace face)
Returns the sign convention used for face-normal flux expectations.
static PetscErrorCode TestGetDeterministicFaceGridLocationFaceMatrix(void)
Tests deterministic inlet-face grid-location helpers across all faces.
static PetscErrorCode TestInletConstantVelocityHandlerBehavior(void)
Tests constant inlet handler initialization and face writes on a Z inlet.
static PetscErrorCode TestBoundaryConditionFactoryRejectsUnsupportedHandler(void)
Tests that unsupported handler types are rejected by the factory.
static PetscErrorCode TestGetRandomCellAndLogicalCoordsOnInletFaceMatrix(void)
Tests random inlet-face cell selection across all supported faces.
static PetscErrorCode TestBoundaryConditionFactoryImplementedHandlerMatrix(void)
Tests the implemented-handler matrix exposed by the factory.
static PetscErrorCode WritePicSliceForTests(const char *path, PetscInt n1, PetscInt n2, PetscReal base)
Writes a tiny canonical PICSLICE profile for boundary handler tests.
static PetscErrorCode TestCanRankServiceInletFaceRequiresDefinition(void)
Tests that inlet-face service requires an inlet face to be defined.
static PetscErrorCode TestInletParabolicProfileHandlerFaceMatrix(void)
Tests parabolic-inlet centerline, wall, and flux behavior across all faces.
static PetscErrorCode TestWallNoSlipHandlerFaceMatrix(void)
Tests wall no-slip application across the full face matrix.
static PetscErrorCode TestInletConstantVelocityHandlerFaceMatrix(void)
Tests constant-inlet initialization, flux accounting, and face writes across all faces.
static const char * GetInletParamKey(BCFace face)
Maps an inlet face to the matching configuration key used by the handler parser.
static PetscErrorCode DestroyBoundaryHandler(BoundaryCondition **bc_ptr)
Destroys one boundary-condition handler allocated by a boundary test.
static PetscErrorCode TestOutletConservationHandlerBehavior(void)
Tests outlet conservation handler correction and post-step flux accounting.
static void ResetBoundaryFaceConfig(BoundaryFaceConfig *cfg)
Resets one boundary-face configuration entry to a neutral test-local baseline.
static PetscReal GetFaceNormalComponent(Cmpnts value, BCFace face)
Extracts the velocity component aligned with the supplied boundary-face normal.
static PetscInt InteriorSampleIndex(PetscInt nodes)
Chooses a stable interior node index for face-sample assertions on tiny test grids.
static PetscErrorCode GetRepresentativeFaceSlots(const UserCtx *user, BCFace face, PetscInt *ucont_k, PetscInt *ucont_j, PetscInt *ucont_i, PetscInt *ubcs_k, PetscInt *ubcs_j, PetscInt *ubcs_i)
Selects representative Ucont and Ubcs slots for face-matrix assertions.
static PetscErrorCode GetParabolicSampleSlots(const UserCtx *user, BCFace face, PetscInt *center_k, PetscInt *center_j, PetscInt *center_i, PetscInt *off_k, PetscInt *off_j, PetscInt *off_i, PetscInt *wall_k, PetscInt *wall_j, PetscInt *wall_i, PetscInt *ubcs_center_k, PetscInt *ubcs_center_j, PetscInt *ubcs_center_i)
Selects center, off-center, and wall-adjacent slots for parabolic-face checks.
PetscErrorCode PicurvCreateMinimalContexts(SimCtx **simCtx_out, UserCtx **user_out, PetscInt mx, PetscInt my, PetscInt mz)
Builds minimal SimCtx and UserCtx fixtures for C unit tests.
PetscErrorCode PicurvAssertRealNear(PetscReal expected, PetscReal actual, PetscReal tol, const char *context)
Asserts that two real values agree within tolerance.
PetscErrorCode PicurvDestroyMinimalContexts(SimCtx **simCtx_ptr, UserCtx **user_ptr)
Destroys minimal SimCtx/UserCtx fixtures and all owned PETSc objects.
PetscErrorCode PicurvRunTests(const char *suite_name, const PicurvTestCase *cases, size_t case_count)
Runs a named C test suite and prints pass/fail progress markers.
PetscErrorCode PicurvAssertIntEqual(PetscInt expected, PetscInt actual, const char *context)
Asserts that two integer values are equal.
PetscErrorCode PicurvPopulateIdentityMetrics(UserCtx *user)
Populates identity metric vectors on the minimal grid fixture.
PetscErrorCode PicurvAssertBool(PetscBool value, const char *context)
Asserts that one boolean condition is true.
Shared declarations for the PICurv C test fixture and assertion layer.
Named test case descriptor consumed by PicurvRunTests.
const PetscReal * global_outflow_sum
Definition variables.h:317
@ INLET
Definition variables.h:258
@ INTERFACE
Definition variables.h:253
@ OUTLET
Definition variables.h:257
PetscBool inletFaceDefined
Definition variables.h:849
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:848
BCFace identifiedInletBCFace
Definition variables.h:850
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:833
struct BC_Param_s * next
Definition variables.h:307
PetscInt KM
Definition variables.h:839
BCHandlerType
Defines the specific computational "strategy" for a boundary handler.
Definition variables.h:271
@ BC_HANDLER_INLET_PARABOLIC
Definition variables.h:277
@ BC_HANDLER_INLET_CONSTANT_VELOCITY
Definition variables.h:276
@ BC_HANDLER_INLET_PROFILE_FROM_FILE
Definition variables.h:278
@ BC_HANDLER_WALL_NOSLIP
Definition variables.h:273
@ BC_HANDLER_OUTLET_CONSERVATION
Definition variables.h:282
@ BC_HANDLER_OUTLET_PRESSURE
Definition variables.h:283
@ BC_HANDLER_UNDEFINED
Definition variables.h:272
BCHandlerType handler_type
Definition variables.h:337
const PetscReal * global_farfield_outflow_sum
Definition variables.h:316
PetscInt np
Definition variables.h:753
BCFace face_id
Definition variables.h:313
Vec Ucont
Definition variables.h:856
Vec Ubcs
Physical Cartesian velocity at boundary faces. Full 3D array but only boundary-face entries are meani...
Definition variables.h:121
PetscScalar x
Definition variables.h:101
BCS Bcs
Definition variables.h:851
UserCtx * user
Definition variables.h:312
const PetscReal * global_inflow_sum
Definition variables.h:314
BC_Param * params
Definition variables.h:338
PetscScalar z
Definition variables.h:101
Vec Ucat
Definition variables.h:856
PetscInt JM
Definition variables.h:839
const PetscReal * global_farfield_inflow_sum
Definition variables.h:315
PetscReal AreaOutSum
Definition variables.h:740
DMDALocalInfo info
Definition variables.h:837
BCPriorityType
Definition variables.h:291
@ BC_PRIORITY_OUTLET
Definition variables.h:296
@ BC_PRIORITY_WALL
Definition variables.h:295
@ BC_PRIORITY_INLET
Definition variables.h:293
Vec lUcat
Definition variables.h:856
PetscScalar y
Definition variables.h:101
PetscInt IM
Definition variables.h:839
BCType mathematical_type
Definition variables.h:336
BCFace
Identifies the six logical faces of a structured computational block.
Definition variables.h:244
@ BC_FACE_NEG_X
Definition variables.h:245
@ BC_FACE_POS_Z
Definition variables.h:247
@ BC_FACE_POS_Y
Definition variables.h:246
@ BC_FACE_NEG_Z
Definition variables.h:247
@ BC_FACE_POS_X
Definition variables.h:245
@ BC_FACE_NEG_Y
Definition variables.h:246
Provides execution context for a boundary condition handler.
Definition variables.h:311
A node in a linked list for storing key-value parameters from the bcs.dat file.
Definition variables.h:304
Holds the complete configuration for one of the six boundary faces.
Definition variables.h:334
A 3D point or vector with PetscScalar components.
Definition variables.h:100
The master context for the entire simulation.
Definition variables.h:651
User-defined context containing data specific to a single computational grid level.
Definition variables.h:830
A generic C-style linked list node for integers.
Definition variables.h:407