PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
test_momentum_newton_krylov.c
Go to the documentation of this file.
1/**
2 * @file test_momentum_newton_krylov.c
3 * @brief Focused tests for the version-one matrix-free momentum solver.
4 *
5 * The implementation is included intentionally: its callback helpers remain
6 * private in production while this translation unit can verify them directly.
7 */
8
9#include "test_support.h"
10#include "initialcondition.h"
11
12/* Rename only the included public entry point. Private callback tests use this
13 * copy, while lifecycle tests below call the separately linked production object. */
14#define MomentumSolver_NewtonKrylov MomentumSolver_NewtonKrylov_PrivateCopy
15#include "../../src/momentum_newton_krylov.c"
16#undef MomentumSolver_NewtonKrylov
17
18static const char *geometric_periodic_bcs =
19 "-Xi PERIODIC geometric\n"
20 "+Xi PERIODIC geometric\n"
21 "-Eta WALL noslip\n"
22 "+Eta WALL noslip\n"
23 "-Zeta INLET constant_velocity vx=0.0 vy=0.0 vz=1.5\n"
24 "+Zeta OUTLET conservation\n";
25
26static const char *fixed_wall_bcs =
27 "-Xi WALL noslip\n"
28 "+Xi WALL noslip\n"
29 "-Eta WALL noslip\n"
30 "+Eta WALL noslip\n"
31 "-Zeta WALL noslip\n"
32 "+Zeta WALL noslip\n";
33
34static const char *parabolic_bcs =
35 "-Xi WALL noslip\n"
36 "+Xi WALL noslip\n"
37 "-Eta WALL noslip\n"
38 "+Eta WALL noslip\n"
39 "-Zeta INLET parabolic v_max=1.5\n"
40 "+Zeta OUTLET conservation\n";
41
42static const char *periodic_x_bcs =
43 "-Xi PERIODIC geometric\n+Xi PERIODIC geometric\n"
44 "-Eta WALL noslip\n+Eta WALL noslip\n-Zeta WALL noslip\n+Zeta WALL noslip\n";
45
46static const char *periodic_y_bcs =
47 "-Xi WALL noslip\n+Xi WALL noslip\n"
48 "-Eta PERIODIC geometric\n+Eta PERIODIC geometric\n-Zeta WALL noslip\n+Zeta WALL noslip\n";
49
50static const char *periodic_z_bcs =
51 "-Xi WALL noslip\n+Xi WALL noslip\n-Eta WALL noslip\n+Eta WALL noslip\n"
52 "-Zeta PERIODIC geometric\n+Zeta PERIODIC geometric\n";
53
54static const char *periodic_xy_bcs =
55 "-Xi PERIODIC geometric\n+Xi PERIODIC geometric\n"
56 "-Eta PERIODIC geometric\n+Eta PERIODIC geometric\n"
57 "-Zeta WALL noslip\n+Zeta WALL noslip\n";
58
59static const char *periodic_xyz_bcs =
60 "-Xi PERIODIC geometric\n+Xi PERIODIC geometric\n"
61 "-Eta PERIODIC geometric\n+Eta PERIODIC geometric\n"
62 "-Zeta PERIODIC geometric\n+Zeta PERIODIC geometric\n";
63
64/**
65 * @brief Builds and initializes a small runtime context for Newton tests.
66 * @param bcs Optional boundary configuration text.
67 * @param simCtx Returned simulation context.
68 * @param user Returned finest-level block context.
69 * @param tmpdir Returned temporary directory.
70 * @param tmpdir_len Capacity of tmpdir.
71 * @return PetscErrorCode 0 on success.
72 */
73static PetscErrorCode BuildNewtonFixture(const char *bcs, SimCtx **simCtx, UserCtx **user,
74 char *tmpdir, size_t tmpdir_len)
75{
76 PetscFunctionBeginUser;
77 PetscCall(PicurvBuildTinyRuntimeContext(bcs, PETSC_FALSE, simCtx, user, tmpdir, tmpdir_len));
78 PetscCall(InitializeEulerianState(*simCtx));
79 (*simCtx)->mom_solver_type = MOMENTUM_SOLVER_NEWTON_KRYLOV;
80 PetscCall(PicurvAssertBool((PetscBool)((*user)->Rhs == NULL),
81 "Newton fixture must enter with no persistent Rhs workspace"));
82 PetscFunctionReturn(PETSC_SUCCESS);
83}
84
85/**
86 * @brief Destroys a Newton test fixture and its temporary files.
87 * @param simCtx Fixture simulation context.
88 * @param tmpdir Fixture temporary directory.
89 * @return PetscErrorCode 0 on success.
90 */
91static PetscErrorCode DestroyNewtonFixture(SimCtx **simCtx, char *tmpdir)
92{
93 PetscFunctionBeginUser;
94 PetscCall(PicurvDestroyRuntimeContext(simCtx));
95 PetscCall(PicurvRemoveTempDir(tmpdir));
96 PetscFunctionReturn(PETSC_SUCCESS);
97}
98
99/**
100 * @brief Writes one static 5x5 PICSLICE profile used by the full runtime fixture.
101 * @param path Output profile path.
102 * @return PetscErrorCode 0 on success.
103 */
104static PetscErrorCode WriteNewtonPicSlice(const char *path)
105{
106 FILE *fd = NULL;
107
108 PetscFunctionBeginUser;
109 fd = fopen(path, "w");
110 PetscCheck(fd != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN,
111 "Could not create Newton test PICSLICE %s.", path);
112 PetscCheck(fprintf(fd, "PICSLICE\n1\n5 5\n") >= 0,
113 PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE, "Could not write PICSLICE header.");
114 for (PetscInt row = 0; row < 25; ++row) {
115 PetscCheck(fprintf(fd, "%.16e\n", 1.0 + 0.01 * (double)row) >= 0,
116 PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE, "Could not write PICSLICE value.");
117 }
118 fclose(fd);
119 PetscFunctionReturn(PETSC_SUCCESS);
120}
121
122/**
123 * @brief Checks callback repeatability, diagnostic-state independence, and X integrity.
124 * @param bcs Boundary configuration text, or NULL for the standard inlet/outlet fixture.
125 * @param label Configuration label used in assertion diagnostics.
126 * @return PetscErrorCode 0 on success.
127 */
128static PetscErrorCode CheckResidualRepeatabilityForBC(const char *bcs, const char *label)
129{
130 SimCtx *simCtx = NULL;
131 UserCtx *user = NULL;
132 char tmpdir[PETSC_MAX_PATH_LEN] = "";
133 Vec x = NULL, x_copy = NULL, f1 = NULL, f2 = NULL, delta = NULL;
134 PetscReal norm = 0.0;
136
137 PetscFunctionBeginUser;
138 PetscCall(BuildNewtonFixture(bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
139 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
140 PetscCall(VecDuplicate(user->Ucont, &x));
141 PetscCall(VecDuplicate(user->Ucont, &x_copy));
142 PetscCall(VecDuplicate(user->Ucont, &f1));
143 PetscCall(VecDuplicate(user->Ucont, &f2));
144 PetscCall(VecDuplicate(user->Ucont, &delta));
145 PetscCall(VecCopy(user->Ucont, x));
146 PetscCall(VecShift(x, 0.125));
147 PetscCall(VecCopy(x, x_copy));
148 ctx.user = user;
149
150 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f1, &ctx));
151 simCtx->FluxInSum = 1234.0;
152 simCtx->FluxOutSum = -4321.0;
153 simCtx->FarFluxInSum = 77.0;
154 simCtx->FarFluxOutSum = -88.0;
155 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f2, &ctx));
156 PetscCall(VecWAXPY(delta, -1.0, f1, f2));
157 PetscCall(VecNorm(delta, NORM_INFINITY, &norm));
158 PetscCheck(norm <= 1.0e-12, PETSC_COMM_WORLD, PETSC_ERR_PLIB,
159 "%s residual changed between identical evaluations (norm=%g).", label, (double)norm);
160 PetscCall(VecWAXPY(delta, -1.0, x_copy, x));
161 PetscCall(VecNorm(delta, NORM_INFINITY, &norm));
162 PetscCheck(norm == 0.0, PETSC_COMM_WORLD, PETSC_ERR_PLIB,
163 "%s residual callback modified X (norm=%g).", label, (double)norm);
164
165 PetscCall(VecDestroy(&delta));
166 PetscCall(VecDestroy(&f2));
167 PetscCall(VecDestroy(&f1));
168 PetscCall(VecDestroy(&x_copy));
169 PetscCall(VecDestroy(&x));
170 PetscCall(VecDestroy(&user->Rhs));
171 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
172 PetscFunctionReturn(PETSC_SUCCESS);
173}
174
175/** @brief Returns whether this rank owns one global DMDA grid point. */
176static PetscBool OwnsStoredPoint(UserCtx *user, PetscInt i, PetscInt j, PetscInt k)
177{
178 return (PetscBool)(i >= user->info.xs && i < user->info.xs + user->info.xm &&
179 j >= user->info.ys && j < user->info.ys + user->info.ym &&
180 k >= user->info.zs && k < user->info.zs + user->info.zm);
181}
182
183/**
184 * @brief Adds a scalar perturbation to one stored staggered component.
185 * @param user Block context defining vector ownership.
186 * @param vec Vector to modify.
187 * @param i Global i index.
188 * @param j Global j index.
189 * @param k Global k index.
190 * @param component Component 0=x, 1=y, 2=z.
191 * @param delta Increment to apply.
192 * @return PetscErrorCode 0 on success.
193 */
194static PetscErrorCode PerturbStoredValue(UserCtx *user, Vec vec, PetscInt i, PetscInt j,
195 PetscInt k, PetscInt component, PetscScalar delta)
196{
197 Cmpnts ***a = NULL;
198
199 PetscFunctionBeginUser;
200 if (OwnsStoredPoint(user, i, j, k)) {
201 PetscCall(DMDAVecGetArray(user->fda, vec, &a));
202 if (component == 0) a[k][j][i].x += delta;
203 else if (component == 1) a[k][j][i].y += delta;
204 else a[k][j][i].z += delta;
205 PetscCall(DMDAVecRestoreArray(user->fda, vec, &a));
206 }
207 PetscFunctionReturn(PETSC_SUCCESS);
208}
209
210/**
211 * @brief Reads one globally indexed stored component on any MPI decomposition.
212 * @param user Block context defining vector ownership.
213 * @param vec Vector to inspect.
214 * @param i Global i index.
215 * @param j Global j index.
216 * @param k Global k index.
217 * @param component Component 0=x, 1=y, 2=z.
218 * @param value Returned globally reduced scalar.
219 * @return PetscErrorCode 0 on success.
220 */
221static PetscErrorCode GetStoredValue(UserCtx *user, Vec vec, PetscInt i, PetscInt j,
222 PetscInt k, PetscInt component, PetscScalar *value)
223{
224 Cmpnts ***a = NULL;
225 PetscScalar local = 0.0;
226
227 PetscFunctionBeginUser;
228 if (OwnsStoredPoint(user, i, j, k)) {
229 PetscCall(DMDAVecGetArrayRead(user->fda, vec, &a));
230 local = component == 0 ? a[k][j][i].x : (component == 1 ? a[k][j][i].y : a[k][j][i].z);
231 PetscCall(DMDAVecRestoreArrayRead(user->fda, vec, &a));
232 }
233 PetscCallMPI(MPI_Allreduce(&local, value, 1, MPIU_SCALAR, MPIU_SUM, PETSC_COMM_WORLD));
234 PetscFunctionReturn(PETSC_SUCCESS);
235}
236
237/**
238 * @brief Finite-differences one callback row with respect to one stored unknown.
239 * @param user Active block context with allocated Rhs.
240 * @param x Base trial vector.
241 * @param row_i Residual-row i index.
242 * @param row_j Residual-row j index.
243 * @param row_k Residual-row k index.
244 * @param row_component Residual-row component.
245 * @param col_i Perturbed unknown i index.
246 * @param col_j Perturbed unknown j index.
247 * @param col_k Perturbed unknown k index.
248 * @param col_component Perturbed unknown component.
249 * @param expected Expected derivative.
250 * @param tolerance Absolute derivative tolerance.
251 * @param label Assertion label.
252 * @return PetscErrorCode 0 on success.
253 */
254static PetscErrorCode CheckStoredDerivative(UserCtx *user, Vec x,
255 PetscInt row_i, PetscInt row_j, PetscInt row_k,
256 PetscInt row_component, PetscInt col_i, PetscInt col_j,
257 PetscInt col_k, PetscInt col_component,
258 PetscReal expected, PetscReal tolerance, const char *label)
259{
260 const PetscReal epsilon = 1.0e-6;
261 Vec f0 = NULL, fp = NULL, xp = NULL;
262 PetscScalar base_value = 0.0, perturbed_value = 0.0;
263 MomentumNewtonKrylovContext ctx = {user};
264
265 PetscFunctionBeginUser;
266 PetscCall(VecDuplicate(x, &f0));
267 PetscCall(VecDuplicate(x, &fp));
268 PetscCall(VecDuplicate(x, &xp));
269 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f0, &ctx));
270 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f0, &ctx));
271 PetscCall(VecCopy(x, xp));
272 PetscCall(PerturbStoredValue(user, xp, col_i, col_j, col_k, col_component, epsilon));
273 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, xp, fp, &ctx));
274 PetscCall(GetStoredValue(user, f0, row_i, row_j, row_k, row_component, &base_value));
275 PetscCall(GetStoredValue(user, fp, row_i, row_j, row_k, row_component, &perturbed_value));
276 PetscCall(PicurvAssertRealNear(expected,
277 PetscRealPart((perturbed_value - base_value) / epsilon), tolerance, label));
278 PetscCall(VecDestroy(&xp));
279 PetscCall(VecDestroy(&fp));
280 PetscCall(VecDestroy(&f0));
281 PetscFunctionReturn(PETSC_SUCCESS);
282}
283
284/** @brief Verifies repeatable callback output and read-only trial input. */
286{
287 char profile_dir[PETSC_MAX_PATH_LEN] = "";
288 char profile_path[PETSC_MAX_PATH_LEN];
289 char file_bcs[2 * PETSC_MAX_PATH_LEN];
290
291 PetscFunctionBeginUser;
292 PetscCall(CheckResidualRepeatabilityForBC(fixed_wall_bcs, "fixed walls"));
293 PetscCall(CheckResidualRepeatabilityForBC(NULL, "constant inlet/conservation outlet"));
294 PetscCall(CheckResidualRepeatabilityForBC(parabolic_bcs, "parabolic inlet/conservation outlet"));
295 PetscCall(CheckResidualRepeatabilityForBC(periodic_x_bcs, "x periodic"));
296 PetscCall(CheckResidualRepeatabilityForBC(periodic_y_bcs, "y periodic"));
297 PetscCall(CheckResidualRepeatabilityForBC(periodic_z_bcs, "z periodic"));
298 PetscCall(CheckResidualRepeatabilityForBC(periodic_xy_bcs, "mixed x-y periodic"));
299
300 PetscCall(PicurvMakeTempDir(profile_dir, sizeof(profile_dir)));
301 PetscCall(PetscSNPrintf(profile_path, sizeof(profile_path), "%s/inlet.picslice", profile_dir));
302 PetscCall(WriteNewtonPicSlice(profile_path));
303 PetscCall(PetscSNPrintf(file_bcs, sizeof(file_bcs),
304 "-Xi WALL noslip\n+Xi WALL noslip\n-Eta WALL noslip\n+Eta WALL noslip\n"
305 "-Zeta INLET prescribed_flow source_file=%s\n+Zeta OUTLET conservation\n", profile_path));
306 PetscCall(CheckResidualRepeatabilityForBC(file_bcs, "file inlet/conservation outlet"));
307 PetscCall(PicurvRemoveTempDir(profile_dir));
308 PetscFunctionReturn(PETSC_SUCCESS);
309}
310
311/** @brief Verifies fixed, periodic-duplicate, and interior residual rows. */
312static PetscErrorCode TestConstraintRows(void)
313{
314 SimCtx *simCtx = NULL;
315 UserCtx *user = NULL;
316 char tmpdir[PETSC_MAX_PATH_LEN] = "";
317 Vec x = NULL, f = NULL;
318 Cmpnts ***xa = NULL, ***fa = NULL, ***conditioned = NULL, ***rhs = NULL;
320
321 PetscFunctionBeginUser;
322 PetscCall(BuildNewtonFixture(geometric_periodic_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
323 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
324 PetscCall(VecDuplicate(user->Ucont, &x));
325 PetscCall(VecDuplicate(user->Ucont, &f));
326 PetscCall(VecSet(x, 0.25));
327 PetscCall(DMDAVecGetArray(user->fda, x, &xa));
328 if (user->info.xs == 0 && 2 >= user->info.ys && 2 < user->info.ys + user->info.ym &&
329 2 >= user->info.zs && 2 < user->info.zs + user->info.zm) xa[2][2][0].x = 3.0;
330 if (user->info.xs + user->info.xm == user->info.mx &&
331 2 >= user->info.ys && 2 < user->info.ys + user->info.ym &&
332 2 >= user->info.zs && 2 < user->info.zs + user->info.zm) xa[2][2][user->info.mx - 2].x = 1.25;
333 PetscCall(DMDAVecRestoreArray(user->fda, x, &xa));
334 ctx.user = user;
335 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f, &ctx));
336
337 PetscCall(DMDAVecGetArrayRead(user->fda, x, &xa));
338 PetscCall(DMDAVecGetArrayRead(user->fda, f, &fa));
339 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucont, &conditioned));
340 PetscCall(DMDAVecGetArrayRead(user->fda, user->Rhs, &rhs));
341 if (user->info.xs == 0 && 2 >= user->info.ys && 2 < user->info.ys + user->info.ym &&
342 2 >= user->info.zs && 2 < user->info.zs + user->info.zm) {
343 PetscCall(PicurvAssertRealNear(1.75, fa[2][2][0].x, 1.0e-12,
344 "periodic duplicate row must be Xdup-Xrep"));
345 }
346 if (user->info.ys == 0 && 2 >= user->info.xs && 2 < user->info.xs + user->info.xm &&
347 2 >= user->info.zs && 2 < user->info.zs + user->info.zm) {
348 PetscCall(PicurvAssertRealNear(xa[2][0][2].y - conditioned[2][0][2].y,
349 fa[2][0][2].y, 1.0e-12,
350 "fixed wall row must be X minus conditioned boundary value"));
351 }
352 if (2 >= user->info.xs && 2 < user->info.xs + user->info.xm &&
353 2 >= user->info.ys && 2 < user->info.ys + user->info.ym &&
354 2 >= user->info.zs && 2 < user->info.zs + user->info.zm) {
355 PetscCall(PicurvAssertRealNear(-rhs[2][2][2].z, fa[2][2][2].z, 1.0e-12,
356 "unconstrained interior row must retain the physical residual"));
357 }
358 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Rhs, &rhs));
359 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucont, &conditioned));
360 PetscCall(DMDAVecRestoreArrayRead(user->fda, f, &fa));
361 PetscCall(DMDAVecRestoreArrayRead(user->fda, x, &xa));
362
363 PetscCall(VecDestroy(&f));
364 PetscCall(VecDestroy(&x));
365 PetscCall(VecDestroy(&user->Rhs));
366 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
367 PetscFunctionReturn(PETSC_SUCCESS);
368}
369
370/** @brief Proves unit derivatives for every nonperiodic stored-row category and face. */
371static PetscErrorCode TestFixedConstraintDerivativesAllFaces(void)
372{
373 SimCtx *simCtx = NULL;
374 UserCtx *user = NULL;
375 char tmpdir[PETSC_MAX_PATH_LEN] = "";
376 Vec x = NULL;
377 const PetscInt size[3] = {7, 7, 7};
378
379 PetscFunctionBeginUser;
380 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
381 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
382 PetscCall(VecDuplicate(user->Ucont, &x));
383 PetscCall(VecSet(x, 0.2));
384
385 for (PetscInt axis = 0; axis < 3; ++axis) {
386 PetscInt coord[3] = {2, 2, 2}, ri, rj, rk;
387 PetscInt tangent = (axis + 1) % 3;
389
390 coord[axis] = 0;
391 row = MomentumNewtonKrylov_ClassifyRow(user, coord[0], coord[1], coord[2], axis, &ri, &rj, &rk);
393 "negative face normal row classification"));
394 PetscCall(CheckStoredDerivative(user, x, coord[0], coord[1], coord[2], axis,
395 coord[0], coord[1], coord[2], axis, 1.0, 1.0e-8,
396 "negative face normal fixed derivative"));
397
398 row = MomentumNewtonKrylov_ClassifyRow(user, coord[0], coord[1], coord[2], tangent, &ri, &rj, &rk);
400 "negative face tangential row classification"));
401 PetscCall(CheckStoredDerivative(user, x, coord[0], coord[1], coord[2], tangent,
402 coord[0], coord[1], coord[2], tangent, 1.0, 1.0e-8,
403 "negative face tangential homogeneous derivative"));
404
405 coord[axis] = size[axis] - 2;
406 row = MomentumNewtonKrylov_ClassifyRow(user, coord[0], coord[1], coord[2], axis, &ri, &rj, &rk);
408 "positive physical normal row classification"));
409 PetscCall(CheckStoredDerivative(user, x, coord[0], coord[1], coord[2], axis,
410 coord[0], coord[1], coord[2], axis, 1.0, 1.0e-8,
411 "positive physical normal fixed derivative"));
412 row = MomentumNewtonKrylov_ClassifyRow(user, coord[0], coord[1], coord[2], tangent, &ri, &rj, &rk);
414 "positive physical tangential row classification"));
415
416 coord[axis] = size[axis] - 1;
417 for (PetscInt component = 0; component < 3; ++component) {
418 row = MomentumNewtonKrylov_ClassifyRow(user, coord[0], coord[1], coord[2], component, &ri, &rj, &rk);
420 "positive dummy row classification"));
421 PetscCall(CheckStoredDerivative(user, x, coord[0], coord[1], coord[2], component,
422 coord[0], coord[1], coord[2], component, 1.0, 1.0e-8,
423 "positive dummy homogeneous derivative"));
424 }
425 }
426
427 PetscCall(VecDestroy(&x));
428 PetscCall(VecDestroy(&user->Rhs));
429 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
430 PetscFunctionReturn(PETSC_SUCCESS);
431}
432
433/** @brief Proves admitted inlet and outlet face-normal rows have unit self derivatives. */
434static PetscErrorCode TestInletOutletConstraintDerivatives(void)
435{
436 SimCtx *simCtx = NULL;
437 UserCtx *user = NULL;
438 char tmpdir[PETSC_MAX_PATH_LEN] = "";
439 Vec x = NULL;
440
441 PetscFunctionBeginUser;
442 PetscCall(BuildNewtonFixture(NULL, &simCtx, &user, tmpdir, sizeof(tmpdir)));
443 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
444 PetscCall(VecDuplicate(user->Ucont, &x));
445 PetscCall(VecCopy(user->Ucont, x));
446 PetscCall(VecShift(x, 0.1));
447 PetscCall(CheckStoredDerivative(user, x, 2, 2, 0, 2, 2, 2, 0, 2,
448 1.0, 1.0e-8, "constant inlet fixed derivative"));
449 PetscCall(CheckStoredDerivative(user, x, 2, 2, user->info.mz - 2, 2,
450 2, 2, user->info.mz - 2, 2,
451 1.0, 5.0e-6, "conservation outlet fixed derivative"));
452 PetscCall(VecDestroy(&x));
453 PetscCall(VecDestroy(&user->Rhs));
454 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
455 PetscFunctionReturn(PETSC_SUCCESS);
456}
457
458/**
459 * @brief Checks one periodic configuration's endpoint derivatives on every component.
460 * @param bcs Boundary text selecting the periodic axis.
461 * @param axis Periodic axis index.
462 * @return PetscErrorCode 0 on success.
463 */
464static PetscErrorCode CheckSingleAxisPeriodicDerivatives(const char *bcs, PetscInt axis)
465{
466 SimCtx *simCtx = NULL;
467 UserCtx *user = NULL;
468 char tmpdir[PETSC_MAX_PATH_LEN] = "";
469 Vec x = NULL;
470 PetscInt size[3], dup[3] = {2, 2, 2}, rep[3] = {2, 2, 2}, unrelated[3] = {3, 3, 3};
471
472 PetscFunctionBeginUser;
473 PetscCall(BuildNewtonFixture(bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
474 size[0] = user->info.mx; size[1] = user->info.my; size[2] = user->info.mz;
475 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
476 PetscCall(VecDuplicate(user->Ucont, &x));
477 PetscCall(VecSet(x, 0.15));
478 for (PetscInt side = 0; side < 2; ++side) {
479 dup[axis] = side == 0 ? 0 : size[axis] - 1;
480 rep[axis] = side == 0 ? size[axis] - 2 : 1;
481 for (PetscInt component = 0; component < 3; ++component) {
482 PetscCall(CheckStoredDerivative(user, x, dup[0], dup[1], dup[2], component,
483 dup[0], dup[1], dup[2], component, 1.0, 1.0e-8,
484 "periodic duplicate self derivative"));
485 PetscCall(CheckStoredDerivative(user, x, dup[0], dup[1], dup[2], component,
486 rep[0], rep[1], rep[2], component, -1.0, 1.0e-8,
487 "periodic representative derivative"));
488 PetscCall(CheckStoredDerivative(user, x, dup[0], dup[1], dup[2], component,
489 unrelated[0], unrelated[1], unrelated[2], (component + 1) % 3, 0.0, 1.0e-8,
490 "periodic constraint unrelated derivative"));
491 }
492 }
493 PetscCall(VecDestroy(&x));
494 PetscCall(VecDestroy(&user->Rhs));
495 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
496 PetscFunctionReturn(PETSC_SUCCESS);
497}
498
499/** @brief Proves single-, double-, triple-, and mixed-boundary periodic equations. */
501{
502 SimCtx *simCtx = NULL;
503 UserCtx *user = NULL;
504 char tmpdir[PETSC_MAX_PATH_LEN] = "";
505 Vec x = NULL, f = NULL;
507 PetscScalar xdup, synced, residual;
508
509 PetscFunctionBeginUser;
513
514 PetscCall(BuildNewtonFixture(periodic_xy_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
515 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
516 PetscCall(VecDuplicate(user->Ucont, &x));
517 PetscCall(VecDuplicate(user->Ucont, &f));
518 PetscCall(VecSet(x, 0.0));
519 PetscCall(PerturbStoredValue(user, x, 0, 0, 2, 0, 3.0));
520 PetscCall(PerturbStoredValue(user, x, user->info.mx - 2, user->info.my - 2, 2, 0, 1.25));
521 PetscCall(VecCopy(x, user->Ucont));
522 { const char *fields[] = {"Ucont"}; PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fields)); }
523 PetscCall(GetStoredValue(user, x, 0, 0, 2, 0, &xdup));
524 PetscCall(GetStoredValue(user, user->Ucont, 0, 0, 2, 0, &synced));
525 ctx.user = user;
526 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f, &ctx));
527 PetscCall(GetStoredValue(user, f, 0, 0, 2, 0, &residual));
528 PetscCall(PicurvAssertRealNear(PetscRealPart(xdup - synced), PetscRealPart(residual), 1.0e-12,
529 "doubly periodic edge must use production synchronized representative"));
530 PetscCall(CheckStoredDerivative(user, x, 0, 0, 2, 0, 0, 0, 2, 0, 1.0, 1.0e-8,
531 "doubly periodic edge self derivative"));
532 PetscCall(CheckStoredDerivative(user, x, 0, 0, 2, 0,
533 user->info.mx - 2, user->info.my - 2, 2, 0, -1.0, 1.0e-8,
534 "doubly periodic edge representative derivative"));
535 PetscCall(VecDestroy(&f)); PetscCall(VecDestroy(&x)); PetscCall(VecDestroy(&user->Rhs));
536 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
537
538 tmpdir[0] = '\0';
539 PetscCall(BuildNewtonFixture(periodic_xyz_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
540 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
541 PetscCall(VecDuplicate(user->Ucont, &x)); PetscCall(VecSet(x, 0.1));
542 PetscCall(CheckStoredDerivative(user, x, 0, 0, 0, 2, 0, 0, 0, 2, 1.0, 1.0e-8,
543 "fully periodic corner self derivative"));
544 PetscCall(CheckStoredDerivative(user, x, 0, 0, 0, 2,
545 user->info.mx - 2, user->info.my - 2, user->info.mz - 2, 2,
546 -1.0, 1.0e-8, "fully periodic corner representative derivative"));
547 PetscCall(VecDestroy(&x)); PetscCall(VecDestroy(&user->Rhs));
548 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
549
550 tmpdir[0] = '\0';
551 PetscCall(BuildNewtonFixture(periodic_x_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
552 PetscCall(VecDuplicate(user->Ucont, &user->Rhs)); PetscCall(VecDuplicate(user->Ucont, &x));
553 PetscCall(VecSet(x, 0.1));
554 PetscCall(CheckStoredDerivative(user, x, 0, 0, 2, 1, 0, 0, 2, 1, 1.0, 1.0e-8,
555 "periodic-wall intersection self derivative"));
556 PetscCall(CheckStoredDerivative(user, x, 0, 0, 2, 1,
557 user->info.mx - 2, 0, 2, 1, -1.0, 1.0e-8,
558 "periodic-wall intersection representative derivative"));
559 PetscCall(VecDestroy(&x)); PetscCall(VecDestroy(&user->Rhs));
560 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
561 PetscFunctionReturn(PETSC_SUCCESS);
562}
563
564/** @brief Compares PETSc's matrix-free action with direct differencing. */
565static PetscErrorCode TestMatrixFreeDerivative(void)
566{
567 SimCtx *simCtx = NULL;
568 UserCtx *user = NULL;
569 char tmpdir[PETSC_MAX_PATH_LEN] = "";
570 SNES snes = NULL;
571 Mat J = NULL;
572 Vec x = NULL, xp = NULL, f0 = NULL, fp = NULL, v = NULL, jv = NULL, fd = NULL;
573 PetscReal h = 0.0, error = 0.0, scale = 0.0;
575
576 PetscFunctionBeginUser;
577 PetscCall(BuildNewtonFixture(NULL, &simCtx, &user, tmpdir, sizeof(tmpdir)));
578 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
579 PetscCall(VecDuplicate(user->Ucont, &x));
580 PetscCall(VecDuplicate(user->Ucont, &xp));
581 PetscCall(VecDuplicate(user->Ucont, &f0));
582 PetscCall(VecDuplicate(user->Ucont, &fp));
583 PetscCall(VecDuplicate(user->Ucont, &v));
584 PetscCall(VecDuplicate(user->Ucont, &jv));
585 PetscCall(VecDuplicate(user->Ucont, &fd));
586 PetscCall(VecCopy(user->Ucont, x));
587 PetscCall(VecShift(x, 0.05));
588 PetscCall(VecSet(v, 0.5));
589 ctx.user = user;
590
591 PetscCall(SNESCreate(PETSC_COMM_WORLD, &snes));
592 PetscCall(SNESSetDM(snes, user->fda));
593 PetscCall(SNESSetFunction(snes, f0, MomentumNewtonKrylov_FormResidual, &ctx));
594 PetscCall(MatCreateSNESMF(snes, &J));
595 PetscCall(MomentumNewtonKrylov_FormResidual(snes, x, f0, &ctx));
596 PetscCall(MatMFFDSetBase(J, x, f0));
597 PetscCall(MatMult(J, v, jv));
598 PetscCall(MatMFFDGetH(J, &h));
599 PetscCall(VecWAXPY(xp, h, v, x));
600 PetscCall(MomentumNewtonKrylov_FormResidual(snes, xp, fp, &ctx));
601 PetscCall(VecWAXPY(fd, -1.0, f0, fp));
602 PetscCall(VecScale(fd, 1.0 / h));
603 PetscCall(VecAXPY(fd, -1.0, jv));
604 PetscCall(VecNorm(fd, NORM_2, &error));
605 PetscCall(VecNorm(jv, NORM_2, &scale));
606 PetscCall(PicurvAssertBool((PetscBool)(error <= 1.0e-9 * PetscMax(1.0, scale)),
607 "matrix-free Jv must match direct differencing"));
608
609 PetscCall(VecDestroy(&fd));
610 PetscCall(VecDestroy(&jv));
611 PetscCall(VecDestroy(&v));
612 PetscCall(VecDestroy(&fp));
613 PetscCall(VecDestroy(&f0));
614 PetscCall(VecDestroy(&xp));
615 PetscCall(VecDestroy(&x));
616 PetscCall(MatDestroy(&J));
617 PetscCall(SNESDestroy(&snes));
618 PetscCall(VecDestroy(&user->Rhs));
619 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
620 PetscFunctionReturn(PETSC_SUCCESS);
621}
622
623/**
624 * @brief Builds a compact all-wall operator fixture through real boundary handlers.
625 * @param simCtx Returned simulation context.
626 * @param user Returned block context.
627 * @param x_periodic Whether the x faces use geometric periodicity.
628 * @return PetscErrorCode 0 on success.
629 */
630static PetscErrorCode BuildMinimalWallOperatorFixture(SimCtx **simCtx, UserCtx **user,
631 PetscBool x_periodic)
632{
633 PetscFunctionBeginUser;
634 /* Request the production-width (3) DMDA stencil so the complete RHS is safe
635 across MPI partitions; boundary metadata below still selects physical walls. */
637 simCtx, user, 6, 6, 6, PETSC_TRUE, PETSC_TRUE, PETSC_TRUE));
638 (*simCtx)->i_periodic = x_periodic ? 1 : 0;
639 (*simCtx)->j_periodic = (*simCtx)->k_periodic = 0;
640 (*simCtx)->mom_solver_type = MOMENTUM_SOLVER_NEWTON_KRYLOV;
641 (*simCtx)->invicid = 1;
642 (*simCtx)->dt = 0.1;
643 (*simCtx)->step = 1;
644 (*simCtx)->StartStep = 0;
645 PetscCall(VecSet((*user)->Ucont, 0.0));
646 PetscCall(VecSet((*user)->Ucont_o, 0.0));
647 PetscCall(VecSet((*user)->Ucont_rm1, 0.0));
648 for (PetscInt face = 0; face < 6; ++face) {
649 PetscBool periodic_face = (PetscBool)(x_periodic &&
650 (face == BC_FACE_NEG_X || face == BC_FACE_POS_X));
651 (*user)->boundary_faces[face].face_id = (BCFace)face;
652 (*user)->boundary_faces[face].mathematical_type = periodic_face ? PERIODIC : WALL;
653 (*user)->boundary_faces[face].handler_type = periodic_face ?
655 PetscCall(BoundaryCondition_Create((*user)->boundary_faces[face].handler_type,
656 &(*user)->boundary_faces[face].handler));
657 }
658 PetscFunctionReturn(PETSC_SUCCESS);
659}
660
661/**
662 * @brief Forms the complete direct FD Jacobian, checks every row, and compares MFFD actions.
663 */
664static PetscErrorCode TestWholeOperatorDirectJacobian(void)
665{
666 SimCtx *simCtx = NULL;
667 UserCtx *user = NULL;
668 SNES snes = NULL;
669 Mat J = NULL;
670 Vec x = NULL, xp = NULL, f0 = NULL, fp = NULL, column = NULL, square = NULL;
671 Vec row_norm_sq = NULL, v[2] = {NULL, NULL}, dense_v[2] = {NULL, NULL};
672 Vec mffd_v = NULL, error_vec = NULL;
673 PetscInt n_global, lo, hi;
674 PetscReal min_row_sq = 0.0, error = 0.0, reference = 0.0;
675 const PetscReal epsilon = 1.0e-7;
677
678 PetscFunctionBeginUser;
679 PetscCall(BuildMinimalWallOperatorFixture(&simCtx, &user, PETSC_FALSE));
680 PetscCall(VecDuplicate(user->Ucont, &x)); PetscCall(VecSet(x, 0.0));
681 PetscCall(VecDuplicate(x, &xp)); PetscCall(VecDuplicate(x, &f0));
682 PetscCall(VecDuplicate(x, &fp)); PetscCall(VecDuplicate(x, &column));
683 PetscCall(VecDuplicate(x, &square)); PetscCall(VecDuplicate(x, &row_norm_sq));
684 PetscCall(VecDuplicate(x, &v[0])); PetscCall(VecDuplicate(x, &v[1]));
685 PetscCall(VecDuplicate(x, &dense_v[0])); PetscCall(VecDuplicate(x, &dense_v[1]));
686 PetscCall(VecDuplicate(x, &mffd_v)); PetscCall(VecDuplicate(x, &error_vec));
687 PetscCall(VecZeroEntries(row_norm_sq)); PetscCall(VecZeroEntries(dense_v[0]));
688 PetscCall(VecZeroEntries(dense_v[1]));
689 PetscCall(VecGetSize(x, &n_global));
690 PetscCall(VecGetOwnershipRange(x, &lo, &hi));
691 for (PetscInt which = 0; which < 2; ++which) {
692 PetscScalar *a = NULL;
693 PetscCall(VecGetArray(v[which], &a));
694 for (PetscInt local = 0; local < hi - lo; ++local) {
695 PetscInt global = lo + local;
696 a[local] = which == 0 ? (PetscScalar)(1.0 + 0.05 * (global % 9))
697 : (PetscScalar)(((global % 2) ? -1.0 : 1.0) * (0.5 + 0.03 * (global % 7)));
698 }
699 PetscCall(VecRestoreArray(v[which], &a));
700 }
701
702 ctx.user = user;
703 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f0, &ctx));
704 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f0, &ctx));
705 for (PetscInt col = 0; col < n_global; ++col) {
706 PetscScalar coeff[2] = {
707 (PetscScalar)(1.0 + 0.05 * (col % 9)),
708 (PetscScalar)(((col % 2) ? -1.0 : 1.0) * (0.5 + 0.03 * (col % 7)))
709 };
710 PetscCall(VecCopy(x, xp));
711 if (col >= lo && col < hi) PetscCall(VecSetValue(xp, col, epsilon, ADD_VALUES));
712 PetscCall(VecAssemblyBegin(xp)); PetscCall(VecAssemblyEnd(xp));
713 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, xp, fp, &ctx));
714 PetscCall(VecWAXPY(column, -1.0, f0, fp));
715 PetscCall(VecScale(column, 1.0 / epsilon));
716 PetscCall(VecPointwiseMult(square, column, column));
717 PetscCall(VecAXPY(row_norm_sq, 1.0, square));
718 PetscCall(VecAXPY(dense_v[0], coeff[0], column));
719 PetscCall(VecAXPY(dense_v[1], coeff[1], column));
720 }
721 PetscCall(VecMin(row_norm_sq, NULL, &min_row_sq));
722 PetscCheck(min_row_sq > 0.5, PETSC_COMM_WORLD, PETSC_ERR_PLIB,
723 "Complete Newton Jacobian contains an unexplained zero/weak row (min squared norm=%g).",
724 (double)min_row_sq);
725 PetscCall(CheckStoredDerivative(user, x, 2, 2, 2, 0, 2, 2, 2, 0,
726 10.0, 1.0e-5, "interior BDF1 temporal diagonal"));
727
728 PetscCall(SNESCreate(PETSC_COMM_WORLD, &snes));
729 PetscCall(SNESSetDM(snes, user->fda));
730 PetscCall(SNESSetFunction(snes, f0, MomentumNewtonKrylov_FormResidual, &ctx));
731 PetscCall(MatCreateSNESMF(snes, &J));
732 PetscCall(MatMFFDSetBase(J, x, f0));
733 for (PetscInt which = 0; which < 2; ++which) {
734 PetscCall(MatMult(J, v[which], mffd_v));
735 PetscCall(VecWAXPY(error_vec, -1.0, dense_v[which], mffd_v));
736 PetscCall(VecNorm(error_vec, NORM_2, &error));
737 PetscCall(VecNorm(dense_v[which], NORM_2, &reference));
738 PetscCheck(error <= 2.0e-5 * PetscMax(1.0, reference), PETSC_COMM_WORLD, PETSC_ERR_PLIB,
739 "Independent dense FD Jv differs from PETSc MFFD action %d: error=%g reference=%g.",
740 which, (double)error, (double)reference);
741 }
742
743 for (PetscInt which = 0; which < 2; ++which) {
744 const PetscReal steps[3] = {1.0e-4, 1.0e-6, 1.0e-8};
745 PetscReal best = PETSC_MAX_REAL;
746 for (PetscInt s = 0; s < 3; ++s) {
747 PetscCall(VecWAXPY(xp, steps[s], v[which], x));
748 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, xp, fp, &ctx));
749 PetscCall(VecWAXPY(column, -1.0, f0, fp));
750 PetscCall(VecScale(column, 1.0 / steps[s]));
751 PetscCall(VecAXPY(column, -1.0, dense_v[which]));
752 PetscCall(VecNorm(column, NORM_2, &error));
753 best = PetscMin(best, error);
754 }
755 PetscCall(VecNorm(dense_v[which], NORM_2, &reference));
756 PetscCheck(best <= 2.0e-5 * PetscMax(1.0, reference), PETSC_COMM_WORLD, PETSC_ERR_PLIB,
757 "Direct directional differences show no accuracy plateau for vector %d (best=%g).",
758 which, (double)best);
759 }
760
761 PetscCall(MatDestroy(&J)); PetscCall(SNESDestroy(&snes));
762 PetscCall(VecDestroy(&error_vec)); PetscCall(VecDestroy(&mffd_v));
763 PetscCall(VecDestroy(&dense_v[1])); PetscCall(VecDestroy(&dense_v[0]));
764 PetscCall(VecDestroy(&v[1])); PetscCall(VecDestroy(&v[0]));
765 PetscCall(VecDestroy(&row_norm_sq)); PetscCall(VecDestroy(&square));
766 PetscCall(VecDestroy(&column)); PetscCall(VecDestroy(&fp)); PetscCall(VecDestroy(&f0));
767 PetscCall(VecDestroy(&xp)); PetscCall(VecDestroy(&x));
768 PetscCall(BoundarySystem_Destroy(user));
769 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
770 PetscFunctionReturn(PETSC_SUCCESS);
771}
772
773/** @brief Audits every row of a complete operator containing periodic duplicates. */
774static PetscErrorCode TestPeriodicOperatorHasNoZeroRows(void)
775{
776 SimCtx *simCtx = NULL;
777 UserCtx *user = NULL;
778 Vec x = NULL, xp = NULL, f0 = NULL, fp = NULL, column = NULL;
779 Vec square = NULL, row_norm_sq = NULL;
780 PetscInt n_global, lo, hi;
781 PetscReal min_row_sq = 0.0;
782 const PetscReal epsilon = 1.0e-7;
784
785 PetscFunctionBeginUser;
786 PetscCall(BuildMinimalWallOperatorFixture(&simCtx, &user, PETSC_TRUE));
787 PetscCall(VecDuplicate(user->Ucont, &x)); PetscCall(VecSet(x, 0.0));
788 PetscCall(VecDuplicate(x, &xp)); PetscCall(VecDuplicate(x, &f0));
789 PetscCall(VecDuplicate(x, &fp)); PetscCall(VecDuplicate(x, &column));
790 PetscCall(VecDuplicate(x, &square)); PetscCall(VecDuplicate(x, &row_norm_sq));
791 PetscCall(VecZeroEntries(row_norm_sq));
792 PetscCall(VecGetSize(x, &n_global)); PetscCall(VecGetOwnershipRange(x, &lo, &hi));
793 ctx.user = user;
794 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f0, &ctx));
795 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f0, &ctx));
796 for (PetscInt col = 0; col < n_global; ++col) {
797 PetscCall(VecCopy(x, xp));
798 if (col >= lo && col < hi) PetscCall(VecSetValue(xp, col, epsilon, ADD_VALUES));
799 PetscCall(VecAssemblyBegin(xp)); PetscCall(VecAssemblyEnd(xp));
800 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, xp, fp, &ctx));
801 PetscCall(VecWAXPY(column, -1.0, f0, fp)); PetscCall(VecScale(column, 1.0 / epsilon));
802 PetscCall(VecPointwiseMult(square, column, column));
803 PetscCall(VecAXPY(row_norm_sq, 1.0, square));
804 }
805 PetscCall(VecMin(row_norm_sq, NULL, &min_row_sq));
806 PetscCheck(min_row_sq > 0.5, PETSC_COMM_WORLD, PETSC_ERR_PLIB,
807 "Periodic Newton Jacobian contains a zero/weak row (min squared norm=%g).",
808 (double)min_row_sq);
809 PetscCall(VecDestroy(&row_norm_sq)); PetscCall(VecDestroy(&square));
810 PetscCall(VecDestroy(&column)); PetscCall(VecDestroy(&fp)); PetscCall(VecDestroy(&f0));
811 PetscCall(VecDestroy(&xp)); PetscCall(VecDestroy(&x));
812 PetscCall(BoundarySystem_Destroy(user));
813 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
814 PetscFunctionReturn(PETSC_SUCCESS);
815}
816
817/** @brief Exercises a converged solve, forced rollback, and per-call cleanup. */
818static PetscErrorCode TestSmallSolveAndRollback(void)
819{
820 SimCtx *simCtx = NULL;
821 UserCtx *user = NULL;
822 char tmpdir[PETSC_MAX_PATH_LEN] = "";
823 Vec entry = NULL, delta = NULL;
824 PetscErrorCode solve_ierr;
825 PetscReal norm = 0.0;
826 const char *fields[] = {"Ucont"};
827
828 PetscFunctionBeginUser;
829 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
830 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_rtol", "1e-4"));
831 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_max_it", "20"));
832 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_ksp_rtol", "1e-6"));
833 PetscCall(MomentumSolver_NewtonKrylov(user, NULL, NULL));
834 PetscCall(PicurvAssertBool(simCtx->mom_last_converged, "small Newton solve must converge"));
835 PetscCall(PicurvAssertBool((PetscBool)(user->Rhs == NULL), "successful solve must release Rhs"));
836
837 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
838 tmpdir[0] = '\0';
839 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
840 PetscCall(VecSet(user->Ucont, 0.2));
841 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fields));
842 PetscCall(ApplyBoundaryConditions(user));
843 PetscCall(VecDuplicate(user->Ucont, &entry));
844 PetscCall(VecDuplicate(user->Ucont, &delta));
845 PetscCall(VecCopy(user->Ucont, entry));
846 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_max_it", "0"));
847 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
848 solve_ierr = MomentumSolver_NewtonKrylov(user, NULL, NULL);
849 PetscCall(PetscPopErrorHandler());
850 PetscCall(PicurvAssertIntEqual(PETSC_ERR_CONV_FAILED, solve_ierr,
851 "forced nonconvergence must report PETSC_ERR_CONV_FAILED"));
852 PetscCall(VecWAXPY(delta, -1.0, entry, user->Ucont));
853 PetscCall(VecNorm(delta, NORM_INFINITY, &norm));
854 PetscCall(PicurvAssertRealNear(0.0, norm, 1.0e-12,
855 "failed Newton solve must restore the canonical entry state"));
856 PetscCall(PicurvAssertBool((PetscBool)(user->Rhs == NULL), "failed solve must release Rhs"));
857
858 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_snes_rtol"));
859 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_snes_max_it"));
860 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_ksp_rtol"));
861 PetscCall(VecDestroy(&delta));
862 PetscCall(VecDestroy(&entry));
863 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
864 PetscFunctionReturn(PETSC_SUCCESS);
865}
866
867/** @brief Confirms unsupported features fail before workspace allocation. */
869{
870 SimCtx *simCtx = NULL;
871 UserCtx *user = NULL;
872 char tmpdir[PETSC_MAX_PATH_LEN] = "";
873 PetscErrorCode solve_ierr;
874
875 PetscFunctionBeginUser;
876 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
877 {
878 PetscInt *unsupported_flags[] = {
879 &simCtx->immersed, &simCtx->movefsi, &simCtx->rotatefsi,
880 &simCtx->moveframe, &simCtx->rotateframe, &simCtx->rans,
881 &simCtx->clark, &simCtx->TwoD, &simCtx->wallfunction
882 };
883 for (size_t flag = 0; flag < sizeof(unsupported_flags) / sizeof(unsupported_flags[0]); ++flag) {
884 *unsupported_flags[flag] = 1;
885 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
886 solve_ierr = MomentumSolver_NewtonKrylov(user, NULL, NULL);
887 PetscCall(PetscPopErrorHandler());
888 PetscCall(PicurvAssertBool((PetscBool)(solve_ierr != PETSC_SUCCESS),
889 "unsupported Newton feature flag must fail"));
890 PetscCall(PicurvAssertBool((PetscBool)(user->Rhs == NULL),
891 "feature validation must precede workspace allocation"));
892 *unsupported_flags[flag] = 0;
893 }
894 }
895 simCtx->block_number = 2;
896 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
897 solve_ierr = MomentumSolver_NewtonKrylov(user, NULL, NULL);
898 PetscCall(PetscPopErrorHandler());
899 PetscCall(PicurvAssertBool((PetscBool)(solve_ierr != PETSC_SUCCESS), "multiblock must fail"));
900 simCtx->block_number = 1;
901 simCtx->StartStep = 1;
902 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
903 solve_ierr = MomentumSolver_NewtonKrylov(user, NULL, NULL);
904 PetscCall(PetscPopErrorHandler());
905 PetscCall(PicurvAssertBool((PetscBool)(solve_ierr != PETSC_SUCCESS), "restart must fail"));
906 simCtx->StartStep = 0;
907 PetscCall(VecSet(user->Nvert, 1.0));
908 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
909 solve_ierr = MomentumSolver_NewtonKrylov(user, NULL, NULL);
910 PetscCall(PetscPopErrorHandler());
911 PetscCall(PicurvAssertBool((PetscBool)(solve_ierr != PETSC_SUCCESS), "masked rows must fail"));
912 PetscCall(VecSet(user->Nvert, 0.0));
915 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
916 solve_ierr = MomentumSolver_NewtonKrylov(user, NULL, NULL);
917 PetscCall(PetscPopErrorHandler());
918 PetscCall(PicurvAssertBool((PetscBool)(solve_ierr != PETSC_SUCCESS),
919 "driven constant-flux controller must fail"));
922 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
923 solve_ierr = MomentumSolver_NewtonKrylov(user, NULL, NULL);
924 PetscCall(PetscPopErrorHandler());
925 PetscCall(PicurvAssertBool((PetscBool)(solve_ierr != PETSC_SUCCESS),
926 "unimplemented interpolated-file inlet must fail"));
927 PetscCall(PicurvAssertBool((PetscBool)(user->Rhs == NULL),
928 "all validation failures must precede workspace allocation"));
929 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
930 PetscFunctionReturn(PETSC_SUCCESS);
931}
932
933/** @brief Verifies cleanup and rollback after an options failure following asset creation. */
934static PetscErrorCode TestPostAllocationFailureCleanup(void)
935{
936 SimCtx *simCtx = NULL;
937 UserCtx *user = NULL;
938 char tmpdir[PETSC_MAX_PATH_LEN] = "";
939 Vec entry = NULL, delta = NULL;
940 PetscErrorCode solve_ierr;
941 PetscReal norm = 0.0;
942 const char *fields[] = {"Ucont"};
943
944 PetscFunctionBeginUser;
945 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
946 PetscCall(VecSet(user->Ucont, 0.2));
947 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fields));
948 PetscCall(ApplyBoundaryConditions(user));
949 PetscCall(VecDuplicate(user->Ucont, &entry)); PetscCall(VecCopy(user->Ucont, entry));
950 PetscCall(VecDuplicate(user->Ucont, &delta));
951 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_pc_type", "jacobi"));
952 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
953 solve_ierr = MomentumSolver_NewtonKrylov(user, NULL, NULL);
954 PetscCall(PetscPopErrorHandler());
955 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_pc_type"));
956 PetscCall(PicurvAssertIntEqual(PETSC_ERR_SUP, solve_ierr,
957 "non-PCNONE option must fail after setup"));
958 PetscCall(PicurvAssertBool((PetscBool)(user->Rhs == NULL),
959 "post-allocation failure must destroy Rhs"));
960 PetscCall(VecWAXPY(delta, -1.0, entry, user->Ucont));
961 PetscCall(VecNorm(delta, NORM_INFINITY, &norm));
962 PetscCall(PicurvAssertRealNear(0.0, norm, 1.0e-12,
963 "post-allocation failure must restore canonical entry"));
964 PetscCall(VecDestroy(&delta)); PetscCall(VecDestroy(&entry));
965 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
966 PetscFunctionReturn(PETSC_SUCCESS);
967}
968
969/**
970 * @brief Runs the focused Newton--Krylov unit suite.
971 * @param argc Command-line argument count.
972 * @param argv Command-line argument vector.
973 * @return Process exit status.
974 */
975int main(int argc, char **argv)
976{
977 PetscErrorCode ierr;
978 const PicurvTestCase cases[] = {
979 {"residual-repeatability-and-input-integrity", TestResidualRepeatabilityAndInputIntegrity},
980 {"constraint-rows", TestConstraintRows},
981 {"fixed-constraint-derivatives-all-faces", TestFixedConstraintDerivativesAllFaces},
982 {"inlet-outlet-constraint-derivatives", TestInletOutletConstraintDerivatives},
983 {"periodic-constraint-derivatives-and-intersections", TestPeriodicConstraintDerivativesAndIntersections},
984 {"matrix-free-derivative", TestMatrixFreeDerivative},
985 {"whole-operator-direct-jacobian", TestWholeOperatorDirectJacobian},
986 {"periodic-operator-has-no-zero-rows", TestPeriodicOperatorHasNoZeroRows},
987 {"small-solve-and-rollback", TestSmallSolveAndRollback},
988 {"unsupported-configuration-fails-before-allocation", TestUnsupportedConfigurationFailsBeforeAllocation},
989 {"post-allocation-failure-cleanup", TestPostAllocationFailureCleanup},
990 };
991
992 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv Newton Krylov tests");
993 if (ierr) return (int)ierr;
994 ierr = PicurvRunTests("unit-newton-krylov", cases, sizeof(cases) / sizeof(cases[0]));
995 if (PetscFinalize()) return 1;
996 return (int)ierr;
997}
PetscErrorCode SynchronizePeriodicStaggeredFields(UserCtx *user, PetscInt num_fields, const char *field_names[])
Synchronizes persistent component-staggered vector fields.
PetscErrorCode BoundaryCondition_Create(BCHandlerType handler_type, BoundaryCondition **new_bc_ptr)
(Private) Creates and configures a specific BoundaryCondition handler object.
Definition Boundaries.c:744
PetscErrorCode ApplyBoundaryConditions(UserCtx *user)
Main boundary-condition orchestrator executed during solver timestepping.
PetscErrorCode BoundarySystem_Destroy(UserCtx *user)
Cleans up and destroys all boundary system resources.
PetscErrorCode InitializeEulerianState(SimCtx *simCtx)
High-level orchestrator to set the complete initial state of the Eulerian solver.
static MomentumNewtonKrylovRowType MomentumNewtonKrylov_ClassifyRow(UserCtx *user, PetscInt i, PetscInt j, PetscInt k, PetscInt component, PetscInt *ri, PetscInt *rj, PetscInt *rk)
Classifies one stored staggered component row and its periodic representative.
MomentumNewtonKrylovRowType
@ MOM_NK_ROW_PHYSICAL
@ MOM_NK_ROW_FIXED_HOMOGENEOUS
@ MOM_NK_ROW_FIXED_CONDITIONED
static PetscErrorCode MomentumNewtonKrylov_FormResidual(SNES snes, Vec X, Vec F, void *ctx)
Adapts a PETSc trial vector to the existing momentum residual path.
static const char * fixed_wall_bcs
static PetscErrorCode BuildNewtonFixture(const char *bcs, SimCtx **simCtx, UserCtx **user, char *tmpdir, size_t tmpdir_len)
Builds and initializes a small runtime context for Newton tests.
static PetscErrorCode TestUnsupportedConfigurationFailsBeforeAllocation(void)
Confirms unsupported features fail before workspace allocation.
static PetscErrorCode TestPeriodicOperatorHasNoZeroRows(void)
Audits every row of a complete operator containing periodic duplicates.
static PetscErrorCode TestSmallSolveAndRollback(void)
Exercises a converged solve, forced rollback, and per-call cleanup.
static const char * periodic_xy_bcs
static PetscErrorCode TestConstraintRows(void)
Verifies fixed, periodic-duplicate, and interior residual rows.
static const char * periodic_xyz_bcs
static const char * periodic_x_bcs
static const char * periodic_y_bcs
#define MomentumSolver_NewtonKrylov
static const char * periodic_z_bcs
int main(int argc, char **argv)
Runs the focused Newton–Krylov unit suite.
static PetscErrorCode WriteNewtonPicSlice(const char *path)
Writes one static 5x5 PICSLICE profile used by the full runtime fixture.
static PetscBool OwnsStoredPoint(UserCtx *user, PetscInt i, PetscInt j, PetscInt k)
Returns whether this rank owns one global DMDA grid point.
static PetscErrorCode CheckSingleAxisPeriodicDerivatives(const char *bcs, PetscInt axis)
Checks one periodic configuration's endpoint derivatives on every component.
static PetscErrorCode TestResidualRepeatabilityAndInputIntegrity(void)
Verifies repeatable callback output and read-only trial input.
static PetscErrorCode TestPeriodicConstraintDerivativesAndIntersections(void)
Proves single-, double-, triple-, and mixed-boundary periodic equations.
static const char * geometric_periodic_bcs
static PetscErrorCode TestWholeOperatorDirectJacobian(void)
Forms the complete direct FD Jacobian, checks every row, and compares MFFD actions.
static PetscErrorCode TestFixedConstraintDerivativesAllFaces(void)
Proves unit derivatives for every nonperiodic stored-row category and face.
static PetscErrorCode TestPostAllocationFailureCleanup(void)
Verifies cleanup and rollback after an options failure following asset creation.
static PetscErrorCode CheckStoredDerivative(UserCtx *user, Vec x, PetscInt row_i, PetscInt row_j, PetscInt row_k, PetscInt row_component, PetscInt col_i, PetscInt col_j, PetscInt col_k, PetscInt col_component, PetscReal expected, PetscReal tolerance, const char *label)
Finite-differences one callback row with respect to one stored unknown.
static const char * parabolic_bcs
static PetscErrorCode GetStoredValue(UserCtx *user, Vec vec, PetscInt i, PetscInt j, PetscInt k, PetscInt component, PetscScalar *value)
Reads one globally indexed stored component on any MPI decomposition.
static PetscErrorCode TestMatrixFreeDerivative(void)
Compares PETSc's matrix-free action with direct differencing.
static PetscErrorCode PerturbStoredValue(UserCtx *user, Vec vec, PetscInt i, PetscInt j, PetscInt k, PetscInt component, PetscScalar delta)
Adds a scalar perturbation to one stored staggered component.
static PetscErrorCode DestroyNewtonFixture(SimCtx **simCtx, char *tmpdir)
Destroys a Newton test fixture and its temporary files.
static PetscErrorCode BuildMinimalWallOperatorFixture(SimCtx **simCtx, UserCtx **user, PetscBool x_periodic)
Builds a compact all-wall operator fixture through real boundary handlers.
static PetscErrorCode CheckResidualRepeatabilityForBC(const char *bcs, const char *label)
Checks callback repeatability, diagnostic-state independence, and X integrity.
static PetscErrorCode TestInletOutletConstraintDerivatives(void)
Proves admitted inlet and outlet face-normal rows have unit self derivatives.
PetscErrorCode PicurvMakeTempDir(char *path, size_t path_len)
Creates a unique temporary directory for one test case.
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 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 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.
PetscInt clark
Definition variables.h:789
PetscReal FarFluxInSum
Definition variables.h:776
PetscInt movefsi
Definition variables.h:714
@ INLET
Definition variables.h:288
@ PERIODIC
Definition variables.h:290
@ WALL
Definition variables.h:284
PetscInt moveframe
Definition variables.h:715
PetscInt TwoD
Definition variables.h:715
PetscReal FarFluxOutSum
Definition variables.h:776
Vec Rhs
Definition variables.h:911
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:895
PetscInt block_number
Definition variables.h:767
PetscInt rans
Definition variables.h:788
PetscReal FluxOutSum
Definition variables.h:776
PetscBool mom_last_converged
Definition variables.h:738
@ BC_HANDLER_PERIODIC_GEOMETRIC
Definition variables.h:314
@ BC_HANDLER_PERIODIC_DRIVEN_CONSTANT_FLUX
Definition variables.h:316
@ BC_HANDLER_WALL_NOSLIP
Definition variables.h:303
@ BC_HANDLER_INLET_INTERP_FROM_FILE
Definition variables.h:309
BCHandlerType handler_type
Definition variables.h:367
Vec Ucont
Definition variables.h:903
PetscInt StartStep
Definition variables.h:694
PetscInt rotatefsi
Definition variables.h:714
@ MOMENTUM_SOLVER_NEWTON_KRYLOV
Definition variables.h:535
PetscScalar x
Definition variables.h:101
PetscReal FluxInSum
Definition variables.h:776
PetscScalar z
Definition variables.h:101
PetscInt wallfunction
Definition variables.h:789
DMDALocalInfo info
Definition variables.h:882
PetscScalar y
Definition variables.h:101
Vec Nvert
Definition variables.h:903
BCType mathematical_type
Definition variables.h:366
PetscInt rotateframe
Definition variables.h:715
PetscInt immersed
Definition variables.h:714
BCFace
Identifies the six logical faces of a structured computational block.
Definition variables.h:259
@ BC_FACE_NEG_X
Definition variables.h:260
@ BC_FACE_POS_X
Definition variables.h:260
A 3D point or vector with PetscScalar components.
Definition variables.h:100
The master context for the entire simulation.
Definition variables.h:684
User-defined context containing data specific to a single computational grid level.
Definition variables.h:875