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/** @brief Checks a structured log's row count and required text after a collective solve. */
65static PetscErrorCode AssertNewtonLog(const char *path, PetscInt expected_rows,
66 const char *needle_a, const char *needle_b)
67{
68 FILE *file = NULL;
69 char line[4096];
70 PetscInt rows = 0;
71 PetscBool found_a = needle_a ? PETSC_FALSE : PETSC_TRUE;
72 PetscBool found_b = needle_b ? PETSC_FALSE : PETSC_TRUE;
73
74 PetscFunctionBeginUser;
75 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
76 file = fopen(path, "r");
77 PetscCheck(file != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN,
78 "Expected Newton log does not exist: %s", path);
79 while (fgets(line, sizeof(line), file)) {
80 if (strncmp(line, "step:", 5) == 0) rows++;
81 if (needle_a && strstr(line, needle_a)) found_a = PETSC_TRUE;
82 if (needle_b && strstr(line, needle_b)) found_b = PETSC_TRUE;
83 }
84 fclose(file);
85 if (expected_rows >= 0) {
86 PetscCall(PicurvAssertIntEqual(expected_rows, rows,
87 "Newton log must contain one nonduplicated row per solve"));
88 } else {
89 PetscCall(PicurvAssertBool((PetscBool)(rows >= -expected_rows),
90 "enabled Newton history must contain the expected iteration rows"));
91 }
92 PetscCall(PicurvAssertBool(found_a, "Newton log is missing required structured content"));
93 PetscCall(PicurvAssertBool(found_b, "Newton log is missing required structured content"));
94 PetscFunctionReturn(PETSC_SUCCESS);
95}
96
97/**
98 * @brief Builds and initializes a small runtime context for Newton tests.
99 * @param bcs Optional boundary configuration text.
100 * @param simCtx Returned simulation context.
101 * @param user Returned finest-level block context.
102 * @param tmpdir Returned temporary directory.
103 * @param tmpdir_len Capacity of tmpdir.
104 * @return PetscErrorCode 0 on success.
105 */
106static PetscErrorCode BuildNewtonFixture(const char *bcs, SimCtx **simCtx, UserCtx **user,
107 char *tmpdir, size_t tmpdir_len)
108{
109 PetscFunctionBeginUser;
110 PetscCall(PicurvBuildTinyRuntimeContext(bcs, PETSC_FALSE, simCtx, user, tmpdir, tmpdir_len));
111 PetscCall(InitializeEulerianState(*simCtx));
112 (*simCtx)->mom_solver_type = MOMENTUM_SOLVER_NEWTON_KRYLOV;
113 PetscCall(PicurvAssertBool((PetscBool)((*user)->Rhs == NULL),
114 "Newton fixture must enter with no persistent Rhs workspace"));
115 PetscFunctionReturn(PETSC_SUCCESS);
116}
117
118/**
119 * @brief Destroys a Newton test fixture and its temporary files.
120 * @param simCtx Fixture simulation context.
121 * @param tmpdir Fixture temporary directory.
122 * @return PetscErrorCode 0 on success.
123 */
124static PetscErrorCode DestroyNewtonFixture(SimCtx **simCtx, char *tmpdir)
125{
126 PetscFunctionBeginUser;
127 PetscCall(PicurvDestroyRuntimeContext(simCtx));
128 PetscCall(PicurvRemoveTempDir(tmpdir));
129 PetscFunctionReturn(PETSC_SUCCESS);
130}
131
132/**
133 * @brief Writes one static 5x5 PICSLICE profile used by the full runtime fixture.
134 * @param path Output profile path.
135 * @return PetscErrorCode 0 on success.
136 */
137static PetscErrorCode WriteNewtonPicSlice(const char *path)
138{
139 FILE *fd = NULL;
140
141 PetscFunctionBeginUser;
142 fd = fopen(path, "w");
143 PetscCheck(fd != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN,
144 "Could not create Newton test PICSLICE %s.", path);
145 PetscCheck(fprintf(fd, "PICSLICE\n1\n5 5\n") >= 0,
146 PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE, "Could not write PICSLICE header.");
147 for (PetscInt row = 0; row < 25; ++row) {
148 PetscCheck(fprintf(fd, "%.16e\n", 1.0 + 0.01 * (double)row) >= 0,
149 PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE, "Could not write PICSLICE value.");
150 }
151 fclose(fd);
152 PetscFunctionReturn(PETSC_SUCCESS);
153}
154
155/**
156 * @brief Checks callback repeatability, diagnostic-state independence, and X integrity.
157 * @param bcs Boundary configuration text, or NULL for the standard inlet/outlet fixture.
158 * @param label Configuration label used in assertion diagnostics.
159 * @return PetscErrorCode 0 on success.
160 */
161static PetscErrorCode CheckResidualRepeatabilityForBC(const char *bcs, const char *label)
162{
163 SimCtx *simCtx = NULL;
164 UserCtx *user = NULL;
165 char tmpdir[PETSC_MAX_PATH_LEN] = "";
166 Vec x = NULL, x_copy = NULL, f1 = NULL, f2 = NULL, delta = NULL;
167 PetscReal norm = 0.0;
169
170 PetscFunctionBeginUser;
171 PetscCall(BuildNewtonFixture(bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
172 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
173 PetscCall(VecDuplicate(user->Ucont, &x));
174 PetscCall(VecDuplicate(user->Ucont, &x_copy));
175 PetscCall(VecDuplicate(user->Ucont, &f1));
176 PetscCall(VecDuplicate(user->Ucont, &f2));
177 PetscCall(VecDuplicate(user->Ucont, &delta));
178 PetscCall(VecCopy(user->Ucont, x));
179 PetscCall(VecShift(x, 0.125));
180 PetscCall(VecCopy(x, x_copy));
181 ctx.user = user;
182
183 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f1, &ctx));
184 /* Poison every piece of hidden state a non-deterministic residual could lean
185 * on: the boundary flux/area diagnostics AND the persistent Cartesian fields
186 * (Ucat/lUcat). The conservation-outlet handler reads lUcat during its first
187 * boundary sweep, so a callback that does not reconstruct the Cartesian state
188 * from X before applying boundary conditions would produce a different F here.
189 * This assertion therefore fails if the deterministic pre-boundary seed in
190 * MomentumNewtonKrylov_FormResidual() is ever removed. */
191 simCtx->FluxInSum = 1234.0;
192 simCtx->FluxOutSum = -4321.0;
193 simCtx->FarFluxInSum = 77.0;
194 simCtx->FarFluxOutSum = -88.0;
195 PetscCall(VecSet(user->Ucat, 7.0));
196 PetscCall(VecSet(user->lUcat, 7.0));
197 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f2, &ctx));
198 PetscCall(VecWAXPY(delta, -1.0, f1, f2));
199 PetscCall(VecNorm(delta, NORM_INFINITY, &norm));
200 PetscCheck(norm <= 1.0e-13, PETSC_COMM_WORLD, PETSC_ERR_PLIB,
201 "%s residual changed between identical evaluations (inf norm=%g).", label, (double)norm);
202 PetscCall(VecNorm(delta, NORM_2, &norm));
203 PetscCheck(norm <= 1.0e-12, PETSC_COMM_WORLD, PETSC_ERR_PLIB,
204 "%s residual changed between identical evaluations (L2 norm=%g).", label, (double)norm);
205 PetscCall(VecWAXPY(delta, -1.0, x_copy, x));
206 PetscCall(VecNorm(delta, NORM_INFINITY, &norm));
207 PetscCheck(norm == 0.0, PETSC_COMM_WORLD, PETSC_ERR_PLIB,
208 "%s residual callback modified X (norm=%g).", label, (double)norm);
209
210 PetscCall(VecDestroy(&delta));
211 PetscCall(VecDestroy(&f2));
212 PetscCall(VecDestroy(&f1));
213 PetscCall(VecDestroy(&x_copy));
214 PetscCall(VecDestroy(&x));
215 PetscCall(VecDestroy(&user->Rhs));
216 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
217 PetscFunctionReturn(PETSC_SUCCESS);
218}
219
220/** @brief Returns whether this rank owns one global DMDA grid point. */
221static PetscBool OwnsStoredPoint(UserCtx *user, PetscInt i, PetscInt j, PetscInt k)
222{
223 return (PetscBool)(i >= user->info.xs && i < user->info.xs + user->info.xm &&
224 j >= user->info.ys && j < user->info.ys + user->info.ym &&
225 k >= user->info.zs && k < user->info.zs + user->info.zm);
226}
227
228/**
229 * @brief Adds a scalar perturbation to one stored staggered component.
230 * @param user Block context defining vector ownership.
231 * @param vec Vector to modify.
232 * @param i Global i index.
233 * @param j Global j index.
234 * @param k Global k index.
235 * @param component Component 0=x, 1=y, 2=z.
236 * @param delta Increment to apply.
237 * @return PetscErrorCode 0 on success.
238 */
239static PetscErrorCode PerturbStoredValue(UserCtx *user, Vec vec, PetscInt i, PetscInt j,
240 PetscInt k, PetscInt component, PetscScalar delta)
241{
242 Cmpnts ***a = NULL;
243
244 PetscFunctionBeginUser;
245 if (OwnsStoredPoint(user, i, j, k)) {
246 PetscCall(DMDAVecGetArray(user->fda, vec, &a));
247 if (component == 0) a[k][j][i].x += delta;
248 else if (component == 1) a[k][j][i].y += delta;
249 else a[k][j][i].z += delta;
250 PetscCall(DMDAVecRestoreArray(user->fda, vec, &a));
251 }
252 PetscFunctionReturn(PETSC_SUCCESS);
253}
254
255/**
256 * @brief Reads one globally indexed stored component on any MPI decomposition.
257 * @param user Block context defining vector ownership.
258 * @param vec Vector to inspect.
259 * @param i Global i index.
260 * @param j Global j index.
261 * @param k Global k index.
262 * @param component Component 0=x, 1=y, 2=z.
263 * @param value Returned globally reduced scalar.
264 * @return PetscErrorCode 0 on success.
265 */
266static PetscErrorCode GetStoredValue(UserCtx *user, Vec vec, PetscInt i, PetscInt j,
267 PetscInt k, PetscInt component, PetscScalar *value)
268{
269 Cmpnts ***a = NULL;
270 PetscScalar local = 0.0;
271
272 PetscFunctionBeginUser;
273 if (OwnsStoredPoint(user, i, j, k)) {
274 PetscCall(DMDAVecGetArrayRead(user->fda, vec, &a));
275 local = component == 0 ? a[k][j][i].x : (component == 1 ? a[k][j][i].y : a[k][j][i].z);
276 PetscCall(DMDAVecRestoreArrayRead(user->fda, vec, &a));
277 }
278 PetscCallMPI(MPI_Allreduce(&local, value, 1, MPIU_SCALAR, MPIU_SUM, PETSC_COMM_WORLD));
279 PetscFunctionReturn(PETSC_SUCCESS);
280}
281
282/**
283 * @brief Finite-differences one callback row with respect to one stored unknown.
284 * @param user Active block context with allocated Rhs.
285 * @param x Base trial vector.
286 * @param row_i Residual-row i index.
287 * @param row_j Residual-row j index.
288 * @param row_k Residual-row k index.
289 * @param row_component Residual-row component.
290 * @param col_i Perturbed unknown i index.
291 * @param col_j Perturbed unknown j index.
292 * @param col_k Perturbed unknown k index.
293 * @param col_component Perturbed unknown component.
294 * @param derivative Returned finite-difference derivative of the row w.r.t. the unknown.
295 * @return PetscErrorCode 0 on success.
296 */
297static PetscErrorCode MeasureStoredDerivative(UserCtx *user, Vec x,
298 PetscInt row_i, PetscInt row_j, PetscInt row_k,
299 PetscInt row_component, PetscInt col_i, PetscInt col_j,
300 PetscInt col_k, PetscInt col_component,
301 PetscReal *derivative)
302{
303 const PetscReal epsilon = 1.0e-6;
304 Vec f0 = NULL, fp = NULL, xp = NULL;
305 PetscScalar base_value = 0.0, perturbed_value = 0.0;
306 MomentumNewtonKrylovContext ctx = {user};
307
308 PetscFunctionBeginUser;
309 PetscCall(VecDuplicate(x, &f0));
310 PetscCall(VecDuplicate(x, &fp));
311 PetscCall(VecDuplicate(x, &xp));
312 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f0, &ctx));
313 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f0, &ctx));
314 PetscCall(VecCopy(x, xp));
315 PetscCall(PerturbStoredValue(user, xp, col_i, col_j, col_k, col_component, epsilon));
316 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, xp, fp, &ctx));
317 PetscCall(GetStoredValue(user, f0, row_i, row_j, row_k, row_component, &base_value));
318 PetscCall(GetStoredValue(user, fp, row_i, row_j, row_k, row_component, &perturbed_value));
319 *derivative = PetscRealPart((perturbed_value - base_value) / epsilon);
320 PetscCall(VecDestroy(&xp));
321 PetscCall(VecDestroy(&fp));
322 PetscCall(VecDestroy(&f0));
323 PetscFunctionReturn(PETSC_SUCCESS);
324}
325
326/**
327 * @brief Asserts one finite-differenced callback row derivative equals an expected value.
328 * @param user Active block context with allocated Rhs.
329 * @param x Base trial vector.
330 * @param row_i Residual-row i index.
331 * @param row_j Residual-row j index.
332 * @param row_k Residual-row k index.
333 * @param row_component Residual-row component.
334 * @param col_i Perturbed unknown i index.
335 * @param col_j Perturbed unknown j index.
336 * @param col_k Perturbed unknown k index.
337 * @param col_component Perturbed unknown component.
338 * @param expected Expected derivative.
339 * @param tolerance Absolute derivative tolerance.
340 * @param label Assertion label.
341 * @return PetscErrorCode 0 on success.
342 */
343static PetscErrorCode CheckStoredDerivative(UserCtx *user, Vec x,
344 PetscInt row_i, PetscInt row_j, PetscInt row_k,
345 PetscInt row_component, PetscInt col_i, PetscInt col_j,
346 PetscInt col_k, PetscInt col_component,
347 PetscReal expected, PetscReal tolerance, const char *label)
348{
349 PetscReal derivative = 0.0;
350
351 PetscFunctionBeginUser;
352 PetscCall(MeasureStoredDerivative(user, x, row_i, row_j, row_k, row_component,
353 col_i, col_j, col_k, col_component, &derivative));
354 PetscCall(PicurvAssertRealNear(expected, derivative, tolerance, label));
355 PetscFunctionReturn(PETSC_SUCCESS);
356}
357
358/** @brief Verifies repeatable callback output and read-only trial input. */
360{
361 char profile_dir[PETSC_MAX_PATH_LEN] = "";
362 char profile_path[PETSC_MAX_PATH_LEN];
363 char file_bcs[2 * PETSC_MAX_PATH_LEN];
364
365 PetscFunctionBeginUser;
366 PetscCall(CheckResidualRepeatabilityForBC(fixed_wall_bcs, "fixed walls"));
367 PetscCall(CheckResidualRepeatabilityForBC(NULL, "constant inlet/conservation outlet"));
368 PetscCall(CheckResidualRepeatabilityForBC(parabolic_bcs, "parabolic inlet/conservation outlet"));
369 PetscCall(CheckResidualRepeatabilityForBC(periodic_x_bcs, "x periodic"));
370 PetscCall(CheckResidualRepeatabilityForBC(periodic_y_bcs, "y periodic"));
371 PetscCall(CheckResidualRepeatabilityForBC(periodic_z_bcs, "z periodic"));
372 PetscCall(CheckResidualRepeatabilityForBC(periodic_xy_bcs, "mixed x-y periodic"));
373
374 PetscCall(PicurvMakeTempDir(profile_dir, sizeof(profile_dir)));
375 PetscCall(PetscSNPrintf(profile_path, sizeof(profile_path), "%s/inlet.picslice", profile_dir));
376 PetscCall(WriteNewtonPicSlice(profile_path));
377 PetscCall(PetscSNPrintf(file_bcs, sizeof(file_bcs),
378 "-Xi WALL noslip\n+Xi WALL noslip\n-Eta WALL noslip\n+Eta WALL noslip\n"
379 "-Zeta INLET prescribed_flow source_file=%s\n+Zeta OUTLET conservation\n", profile_path));
380 PetscCall(CheckResidualRepeatabilityForBC(file_bcs, "file inlet/conservation outlet"));
381 PetscCall(PicurvRemoveTempDir(profile_dir));
382 PetscFunctionReturn(PETSC_SUCCESS);
383}
384
385/** @brief Verifies fixed, periodic-duplicate, and interior residual rows. */
386static PetscErrorCode TestConstraintRows(void)
387{
388 SimCtx *simCtx = NULL;
389 UserCtx *user = NULL;
390 char tmpdir[PETSC_MAX_PATH_LEN] = "";
391 Vec x = NULL, f = NULL;
392 Cmpnts ***xa = NULL, ***fa = NULL, ***conditioned = NULL, ***rhs = NULL;
394
395 PetscFunctionBeginUser;
396 PetscCall(BuildNewtonFixture(geometric_periodic_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
397 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
398 PetscCall(VecDuplicate(user->Ucont, &x));
399 PetscCall(VecDuplicate(user->Ucont, &f));
400 PetscCall(VecSet(x, 0.25));
401 PetscCall(DMDAVecGetArray(user->fda, x, &xa));
402 if (user->info.xs == 0 && 2 >= user->info.ys && 2 < user->info.ys + user->info.ym &&
403 2 >= user->info.zs && 2 < user->info.zs + user->info.zm) xa[2][2][0].x = 3.0;
404 if (user->info.xs + user->info.xm == user->info.mx &&
405 2 >= user->info.ys && 2 < user->info.ys + user->info.ym &&
406 2 >= user->info.zs && 2 < user->info.zs + user->info.zm) xa[2][2][user->info.mx - 2].x = 1.25;
407 PetscCall(DMDAVecRestoreArray(user->fda, x, &xa));
408 ctx.user = user;
409 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f, &ctx));
410
411 PetscCall(DMDAVecGetArrayRead(user->fda, x, &xa));
412 PetscCall(DMDAVecGetArrayRead(user->fda, f, &fa));
413 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucont, &conditioned));
414 PetscCall(DMDAVecGetArrayRead(user->fda, user->Rhs, &rhs));
415 if (user->info.xs == 0 && 2 >= user->info.ys && 2 < user->info.ys + user->info.ym &&
416 2 >= user->info.zs && 2 < user->info.zs + user->info.zm) {
417 PetscCall(PicurvAssertRealNear(1.75, fa[2][2][0].x, 1.0e-12,
418 "periodic duplicate row must be Xdup-Xrep"));
419 }
420 if (user->info.ys == 0 && 2 >= user->info.xs && 2 < user->info.xs + user->info.xm &&
421 2 >= user->info.zs && 2 < user->info.zs + user->info.zm) {
422 PetscCall(PicurvAssertRealNear(xa[2][0][2].y - conditioned[2][0][2].y,
423 fa[2][0][2].y, 1.0e-12,
424 "fixed wall row must be X minus conditioned boundary value"));
425 }
426 if (2 >= user->info.xs && 2 < user->info.xs + user->info.xm &&
427 2 >= user->info.ys && 2 < user->info.ys + user->info.ym &&
428 2 >= user->info.zs && 2 < user->info.zs + user->info.zm) {
429 PetscCall(PicurvAssertRealNear(-rhs[2][2][2].z, fa[2][2][2].z, 1.0e-12,
430 "unconstrained interior row must retain the physical residual"));
431 }
432 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Rhs, &rhs));
433 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucont, &conditioned));
434 PetscCall(DMDAVecRestoreArrayRead(user->fda, f, &fa));
435 PetscCall(DMDAVecRestoreArrayRead(user->fda, x, &xa));
436
437 PetscCall(VecDestroy(&f));
438 PetscCall(VecDestroy(&x));
439 PetscCall(VecDestroy(&user->Rhs));
440 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
441 PetscFunctionReturn(PETSC_SUCCESS);
442}
443
444/** @brief Proves unit derivatives for every nonperiodic stored-row category and face. */
445static PetscErrorCode TestFixedConstraintDerivativesAllFaces(void)
446{
447 SimCtx *simCtx = NULL;
448 UserCtx *user = NULL;
449 char tmpdir[PETSC_MAX_PATH_LEN] = "";
450 Vec x = NULL;
451 const PetscInt size[3] = {7, 7, 7};
452
453 PetscFunctionBeginUser;
454 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
455 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
456 PetscCall(VecDuplicate(user->Ucont, &x));
457 PetscCall(VecSet(x, 0.2));
458
459 for (PetscInt axis = 0; axis < 3; ++axis) {
460 PetscInt coord[3] = {2, 2, 2}, ri, rj, rk;
461 PetscInt tangent = (axis + 1) % 3;
462 MomentumRowType row;
463
464 coord[axis] = 0;
465 row = ClassifyMomentumRow(user, coord[0], coord[1], coord[2], axis, &ri, &rj, &rk);
467 "negative face normal row classification"));
468 PetscCall(CheckStoredDerivative(user, x, coord[0], coord[1], coord[2], axis,
469 coord[0], coord[1], coord[2], axis, 1.0, 1.0e-8,
470 "negative face normal fixed derivative"));
471
472 row = ClassifyMomentumRow(user, coord[0], coord[1], coord[2], tangent, &ri, &rj, &rk);
474 "negative face tangential row classification"));
475 PetscCall(CheckStoredDerivative(user, x, coord[0], coord[1], coord[2], tangent,
476 coord[0], coord[1], coord[2], tangent, 1.0, 1.0e-8,
477 "negative face tangential homogeneous derivative"));
478
479 coord[axis] = size[axis] - 2;
480 row = ClassifyMomentumRow(user, coord[0], coord[1], coord[2], axis, &ri, &rj, &rk);
482 "positive physical normal row classification"));
483 PetscCall(CheckStoredDerivative(user, x, coord[0], coord[1], coord[2], axis,
484 coord[0], coord[1], coord[2], axis, 1.0, 1.0e-8,
485 "positive physical normal fixed derivative"));
486 row = ClassifyMomentumRow(user, coord[0], coord[1], coord[2], tangent, &ri, &rj, &rk);
488 "positive physical tangential row classification"));
489
490 coord[axis] = size[axis] - 1;
491 for (PetscInt component = 0; component < 3; ++component) {
492 row = ClassifyMomentumRow(user, coord[0], coord[1], coord[2], component, &ri, &rj, &rk);
494 "positive dummy row classification"));
495 PetscCall(CheckStoredDerivative(user, x, coord[0], coord[1], coord[2], component,
496 coord[0], coord[1], coord[2], component, 1.0, 1.0e-8,
497 "positive dummy homogeneous derivative"));
498 }
499 }
500
501 PetscCall(VecDestroy(&x));
502 PetscCall(VecDestroy(&user->Rhs));
503 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
504 PetscFunctionReturn(PETSC_SUCCESS);
505}
506
507/** @brief Proves admitted inlet and outlet face-normal rows have unit self derivatives. */
508static PetscErrorCode TestInletOutletConstraintDerivatives(void)
509{
510 SimCtx *simCtx = NULL;
511 UserCtx *user = NULL;
512 char tmpdir[PETSC_MAX_PATH_LEN] = "";
513 Vec x = NULL;
514
515 PetscFunctionBeginUser;
516 PetscCall(BuildNewtonFixture(NULL, &simCtx, &user, tmpdir, sizeof(tmpdir)));
517 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
518 PetscCall(VecDuplicate(user->Ucont, &x));
519 PetscCall(VecCopy(user->Ucont, x));
520 PetscCall(VecShift(x, 0.1));
521 PetscCall(CheckStoredDerivative(user, x, 2, 2, 0, 2, 2, 2, 0, 2,
522 1.0, 1.0e-8, "constant inlet fixed derivative"));
523 /* The constant-velocity inlet imposes a value independent of X, so its
524 * conditioned row F = X - cv has an exact unit self derivative.
525 *
526 * The conservation outlet is different: cv is the corrected outlet flux,
527 * which the deterministic residual now reconstructs from the current X
528 * (Ucat is seeded from X before the first outlet pass). Perturbing the
529 * outlet-normal DOF therefore changes cv, so the self derivative is
530 * 1 - dcv/dX and is strictly less than one. A self derivative of exactly
531 * 1.0 here was an artifact of the pre-fix residual reading a stale
532 * Cartesian state, i.e. an outlet correction decoupled from X. Assert the
533 * derivative is (a) deterministic across independent evaluations -- the
534 * residual-purity property -- and (b) reflects real conservation coupling
535 * (0 < d < 1), rather than asserting a fixture-specific magic number. */
536 {
537 PetscReal d0 = 0.0, d1 = 0.0;
538 PetscCall(MeasureStoredDerivative(user, x, 2, 2, user->info.mz - 2, 2,
539 2, 2, user->info.mz - 2, 2, &d0));
540 PetscCall(MeasureStoredDerivative(user, x, 2, 2, user->info.mz - 2, 2,
541 2, 2, user->info.mz - 2, 2, &d1));
542 PetscCall(PicurvAssertRealNear(d0, d1, 1.0e-9,
543 "conservation outlet self derivative must be deterministic"));
544 PetscCheck(d0 > 1.0e-3 && d0 < 1.0 - 1.0e-3, PETSC_COMM_WORLD, PETSC_ERR_PLIB,
545 "conservation outlet self derivative must reflect X-coupling (0<d<1), got %g.",
546 (double)d0);
547 }
548 PetscCall(VecDestroy(&x));
549 PetscCall(VecDestroy(&user->Rhs));
550 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
551 PetscFunctionReturn(PETSC_SUCCESS);
552}
553
554/**
555 * @brief Checks one periodic configuration's endpoint derivatives on every component.
556 * @param bcs Boundary text selecting the periodic axis.
557 * @param axis Periodic axis index.
558 * @return PetscErrorCode 0 on success.
559 */
560static PetscErrorCode CheckSingleAxisPeriodicDerivatives(const char *bcs, PetscInt axis)
561{
562 SimCtx *simCtx = NULL;
563 UserCtx *user = NULL;
564 char tmpdir[PETSC_MAX_PATH_LEN] = "";
565 Vec x = NULL;
566 PetscInt size[3], dup[3] = {2, 2, 2}, rep[3] = {2, 2, 2}, unrelated[3] = {3, 3, 3};
567
568 PetscFunctionBeginUser;
569 PetscCall(BuildNewtonFixture(bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
570 size[0] = user->info.mx; size[1] = user->info.my; size[2] = user->info.mz;
571 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
572 PetscCall(VecDuplicate(user->Ucont, &x));
573 PetscCall(VecSet(x, 0.15));
574 for (PetscInt side = 0; side < 2; ++side) {
575 dup[axis] = side == 0 ? 0 : size[axis] - 1;
576 rep[axis] = side == 0 ? size[axis] - 2 : 1;
577 for (PetscInt component = 0; component < 3; ++component) {
578 PetscCall(CheckStoredDerivative(user, x, dup[0], dup[1], dup[2], component,
579 dup[0], dup[1], dup[2], component, 1.0, 1.0e-8,
580 "periodic duplicate self derivative"));
581 PetscCall(CheckStoredDerivative(user, x, dup[0], dup[1], dup[2], component,
582 rep[0], rep[1], rep[2], component, -1.0, 1.0e-8,
583 "periodic representative derivative"));
584 PetscCall(CheckStoredDerivative(user, x, dup[0], dup[1], dup[2], component,
585 unrelated[0], unrelated[1], unrelated[2], (component + 1) % 3, 0.0, 1.0e-8,
586 "periodic constraint unrelated derivative"));
587 }
588 }
589 PetscCall(VecDestroy(&x));
590 PetscCall(VecDestroy(&user->Rhs));
591 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
592 PetscFunctionReturn(PETSC_SUCCESS);
593}
594
595/** @brief Proves single-, double-, triple-, and mixed-boundary periodic equations. */
597{
598 SimCtx *simCtx = NULL;
599 UserCtx *user = NULL;
600 char tmpdir[PETSC_MAX_PATH_LEN] = "";
601 Vec x = NULL, f = NULL;
603 PetscScalar xdup, synced, residual;
604
605 PetscFunctionBeginUser;
609
610 PetscCall(BuildNewtonFixture(periodic_xy_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
611 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
612 PetscCall(VecDuplicate(user->Ucont, &x));
613 PetscCall(VecDuplicate(user->Ucont, &f));
614 PetscCall(VecSet(x, 0.0));
615 PetscCall(PerturbStoredValue(user, x, 0, 0, 2, 0, 3.0));
616 PetscCall(PerturbStoredValue(user, x, user->info.mx - 2, user->info.my - 2, 2, 0, 1.25));
617 PetscCall(VecCopy(x, user->Ucont));
618 { const FieldId fields[] = {FIELD_ID_UCONT}; PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fields)); }
619 PetscCall(GetStoredValue(user, x, 0, 0, 2, 0, &xdup));
620 PetscCall(GetStoredValue(user, user->Ucont, 0, 0, 2, 0, &synced));
621 ctx.user = user;
622 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f, &ctx));
623 PetscCall(GetStoredValue(user, f, 0, 0, 2, 0, &residual));
624 PetscCall(PicurvAssertRealNear(PetscRealPart(xdup - synced), PetscRealPart(residual), 1.0e-12,
625 "doubly periodic edge must use production synchronized representative"));
626 PetscCall(CheckStoredDerivative(user, x, 0, 0, 2, 0, 0, 0, 2, 0, 1.0, 1.0e-8,
627 "doubly periodic edge self derivative"));
628 PetscCall(CheckStoredDerivative(user, x, 0, 0, 2, 0,
629 user->info.mx - 2, user->info.my - 2, 2, 0, -1.0, 1.0e-8,
630 "doubly periodic edge representative derivative"));
631 PetscCall(VecDestroy(&f)); PetscCall(VecDestroy(&x)); PetscCall(VecDestroy(&user->Rhs));
632 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
633
634 tmpdir[0] = '\0';
635 PetscCall(BuildNewtonFixture(periodic_xyz_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
636 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
637 PetscCall(VecDuplicate(user->Ucont, &x)); PetscCall(VecSet(x, 0.1));
638 PetscCall(CheckStoredDerivative(user, x, 0, 0, 0, 2, 0, 0, 0, 2, 1.0, 1.0e-8,
639 "fully periodic corner self derivative"));
640 PetscCall(CheckStoredDerivative(user, x, 0, 0, 0, 2,
641 user->info.mx - 2, user->info.my - 2, user->info.mz - 2, 2,
642 -1.0, 1.0e-8, "fully periodic corner representative derivative"));
643 PetscCall(VecDestroy(&x)); PetscCall(VecDestroy(&user->Rhs));
644 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
645
646 tmpdir[0] = '\0';
647 PetscCall(BuildNewtonFixture(periodic_x_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
648 PetscCall(VecDuplicate(user->Ucont, &user->Rhs)); PetscCall(VecDuplicate(user->Ucont, &x));
649 PetscCall(VecSet(x, 0.1));
650 PetscCall(CheckStoredDerivative(user, x, 0, 0, 2, 1, 0, 0, 2, 1, 1.0, 1.0e-8,
651 "periodic-wall intersection self derivative"));
652 PetscCall(CheckStoredDerivative(user, x, 0, 0, 2, 1,
653 user->info.mx - 2, 0, 2, 1, -1.0, 1.0e-8,
654 "periodic-wall intersection representative derivative"));
655 PetscCall(VecDestroy(&x)); PetscCall(VecDestroy(&user->Rhs));
656 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
657 PetscFunctionReturn(PETSC_SUCCESS);
658}
659
660/** @brief Compares PETSc's matrix-free action with direct differencing. */
661static PetscErrorCode TestMatrixFreeDerivative(void)
662{
663 SimCtx *simCtx = NULL;
664 UserCtx *user = NULL;
665 char tmpdir[PETSC_MAX_PATH_LEN] = "";
666 SNES snes = NULL;
667 Mat J = NULL;
668 Vec x = NULL, xp = NULL, f0 = NULL, fp = NULL, v = NULL, jv = NULL, fd = NULL;
669 PetscReal h = 0.0, error = 0.0, scale = 0.0;
671
672 PetscFunctionBeginUser;
673 PetscCall(BuildNewtonFixture(NULL, &simCtx, &user, tmpdir, sizeof(tmpdir)));
674 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
675 PetscCall(VecDuplicate(user->Ucont, &x));
676 PetscCall(VecDuplicate(user->Ucont, &xp));
677 PetscCall(VecDuplicate(user->Ucont, &f0));
678 PetscCall(VecDuplicate(user->Ucont, &fp));
679 PetscCall(VecDuplicate(user->Ucont, &v));
680 PetscCall(VecDuplicate(user->Ucont, &jv));
681 PetscCall(VecDuplicate(user->Ucont, &fd));
682 PetscCall(VecCopy(user->Ucont, x));
683 PetscCall(VecShift(x, 0.05));
684 PetscCall(VecSet(v, 0.5));
685 ctx.user = user;
686
687 PetscCall(SNESCreate(PETSC_COMM_WORLD, &snes));
688 PetscCall(SNESSetDM(snes, user->fda));
689 PetscCall(SNESSetFunction(snes, f0, MomentumNewtonKrylov_FormResidual, &ctx));
690 PetscCall(MatCreateSNESMF(snes, &J));
691 PetscCall(MomentumNewtonKrylov_FormResidual(snes, x, f0, &ctx));
692 PetscCall(MatMFFDSetBase(J, x, f0));
693 PetscCall(MatMult(J, v, jv));
694 PetscCall(MatMFFDGetH(J, &h));
695 PetscCall(VecWAXPY(xp, h, v, x));
696 PetscCall(MomentumNewtonKrylov_FormResidual(snes, xp, fp, &ctx));
697 PetscCall(VecWAXPY(fd, -1.0, f0, fp));
698 PetscCall(VecScale(fd, 1.0 / h));
699 PetscCall(VecAXPY(fd, -1.0, jv));
700 PetscCall(VecNorm(fd, NORM_2, &error));
701 PetscCall(VecNorm(jv, NORM_2, &scale));
702 PetscCall(PicurvAssertBool((PetscBool)(error <= 1.0e-9 * PetscMax(1.0, scale)),
703 "matrix-free Jv must match direct differencing"));
704
705 PetscCall(VecDestroy(&fd));
706 PetscCall(VecDestroy(&jv));
707 PetscCall(VecDestroy(&v));
708 PetscCall(VecDestroy(&fp));
709 PetscCall(VecDestroy(&f0));
710 PetscCall(VecDestroy(&xp));
711 PetscCall(VecDestroy(&x));
712 PetscCall(MatDestroy(&J));
713 PetscCall(SNESDestroy(&snes));
714 PetscCall(VecDestroy(&user->Rhs));
715 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
716 PetscFunctionReturn(PETSC_SUCCESS);
717}
718
719/**
720 * @brief Builds a compact all-wall operator fixture through real boundary handlers.
721 * @param simCtx Returned simulation context.
722 * @param user Returned block context.
723 * @param x_periodic Whether the x faces use geometric periodicity.
724 * @return PetscErrorCode 0 on success.
725 */
726static PetscErrorCode BuildMinimalWallOperatorFixture(SimCtx **simCtx, UserCtx **user,
727 PetscBool x_periodic)
728{
729 PetscFunctionBeginUser;
730 /* Request the production-width (3) DMDA stencil so the complete RHS is safe
731 across MPI partitions; boundary metadata below still selects physical walls. */
733 simCtx, user, 6, 6, 6, PETSC_TRUE, PETSC_TRUE, PETSC_TRUE));
734 (*simCtx)->i_periodic = x_periodic ? 1 : 0;
735 (*simCtx)->j_periodic = (*simCtx)->k_periodic = 0;
736 (*simCtx)->mom_solver_type = MOMENTUM_SOLVER_NEWTON_KRYLOV;
737 (*simCtx)->invicid = 1;
738 (*simCtx)->dt = 0.1;
739 (*simCtx)->step = 1;
740 (*simCtx)->StartStep = 0;
741 PetscCall(VecSet((*user)->Ucont, 0.0));
742 PetscCall(VecSet((*user)->Ucont_o, 0.0));
743 PetscCall(VecSet((*user)->Ucont_rm1, 0.0));
744 for (PetscInt face = 0; face < 6; ++face) {
745 PetscBool periodic_face = (PetscBool)(x_periodic &&
746 (face == BC_FACE_NEG_X || face == BC_FACE_POS_X));
747 (*user)->boundary_faces[face].face_id = (BCFace)face;
748 (*user)->boundary_faces[face].mathematical_type = periodic_face ? PERIODIC : WALL;
749 (*user)->boundary_faces[face].handler_type = periodic_face ?
751 PetscCall(BoundaryCondition_Create((*user)->boundary_faces[face].handler_type,
752 &(*user)->boundary_faces[face].handler));
753 }
754 PetscFunctionReturn(PETSC_SUCCESS);
755}
756
757/**
758 * @brief Forms the complete direct FD Jacobian, checks every row, and compares MFFD actions.
759 */
760static PetscErrorCode TestWholeOperatorDirectJacobian(void)
761{
762 SimCtx *simCtx = NULL;
763 UserCtx *user = NULL;
764 SNES snes = NULL;
765 Mat J = NULL;
766 Vec x = NULL, xp = NULL, f0 = NULL, fp = NULL, column = NULL, square = NULL;
767 Vec row_norm_sq = NULL, v[2] = {NULL, NULL}, dense_v[2] = {NULL, NULL};
768 Vec mffd_v = NULL, error_vec = NULL;
769 PetscInt n_global, lo, hi;
770 PetscReal min_row_sq = 0.0, error = 0.0, reference = 0.0;
771 const PetscReal epsilon = 1.0e-7;
773
774 PetscFunctionBeginUser;
775 PetscCall(BuildMinimalWallOperatorFixture(&simCtx, &user, PETSC_FALSE));
776 PetscCall(VecDuplicate(user->Ucont, &x)); PetscCall(VecSet(x, 0.0));
777 PetscCall(VecDuplicate(x, &xp)); PetscCall(VecDuplicate(x, &f0));
778 PetscCall(VecDuplicate(x, &fp)); PetscCall(VecDuplicate(x, &column));
779 PetscCall(VecDuplicate(x, &square)); PetscCall(VecDuplicate(x, &row_norm_sq));
780 PetscCall(VecDuplicate(x, &v[0])); PetscCall(VecDuplicate(x, &v[1]));
781 PetscCall(VecDuplicate(x, &dense_v[0])); PetscCall(VecDuplicate(x, &dense_v[1]));
782 PetscCall(VecDuplicate(x, &mffd_v)); PetscCall(VecDuplicate(x, &error_vec));
783 PetscCall(VecZeroEntries(row_norm_sq)); PetscCall(VecZeroEntries(dense_v[0]));
784 PetscCall(VecZeroEntries(dense_v[1]));
785 PetscCall(VecGetSize(x, &n_global));
786 PetscCall(VecGetOwnershipRange(x, &lo, &hi));
787 for (PetscInt which = 0; which < 2; ++which) {
788 PetscScalar *a = NULL;
789 PetscCall(VecGetArray(v[which], &a));
790 for (PetscInt local = 0; local < hi - lo; ++local) {
791 PetscInt global = lo + local;
792 a[local] = which == 0 ? (PetscScalar)(1.0 + 0.05 * (global % 9))
793 : (PetscScalar)(((global % 2) ? -1.0 : 1.0) * (0.5 + 0.03 * (global % 7)));
794 }
795 PetscCall(VecRestoreArray(v[which], &a));
796 }
797
798 ctx.user = user;
799 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f0, &ctx));
800 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f0, &ctx));
801 for (PetscInt col = 0; col < n_global; ++col) {
802 PetscScalar coeff[2] = {
803 (PetscScalar)(1.0 + 0.05 * (col % 9)),
804 (PetscScalar)(((col % 2) ? -1.0 : 1.0) * (0.5 + 0.03 * (col % 7)))
805 };
806 PetscCall(VecCopy(x, xp));
807 if (col >= lo && col < hi) PetscCall(VecSetValue(xp, col, epsilon, ADD_VALUES));
808 PetscCall(VecAssemblyBegin(xp)); PetscCall(VecAssemblyEnd(xp));
809 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, xp, fp, &ctx));
810 PetscCall(VecWAXPY(column, -1.0, f0, fp));
811 PetscCall(VecScale(column, 1.0 / epsilon));
812 PetscCall(VecPointwiseMult(square, column, column));
813 PetscCall(VecAXPY(row_norm_sq, 1.0, square));
814 PetscCall(VecAXPY(dense_v[0], coeff[0], column));
815 PetscCall(VecAXPY(dense_v[1], coeff[1], column));
816 }
817 PetscCall(VecMin(row_norm_sq, NULL, &min_row_sq));
818 PetscCheck(min_row_sq > 0.5, PETSC_COMM_WORLD, PETSC_ERR_PLIB,
819 "Complete Newton Jacobian contains an unexplained zero/weak row (min squared norm=%g).",
820 (double)min_row_sq);
821 PetscCall(CheckStoredDerivative(user, x, 2, 2, 2, 0, 2, 2, 2, 0,
822 10.0, 1.0e-5, "interior BDF1 temporal diagonal"));
823
824 PetscCall(SNESCreate(PETSC_COMM_WORLD, &snes));
825 PetscCall(SNESSetDM(snes, user->fda));
826 PetscCall(SNESSetFunction(snes, f0, MomentumNewtonKrylov_FormResidual, &ctx));
827 PetscCall(MatCreateSNESMF(snes, &J));
828 PetscCall(MatMFFDSetBase(J, x, f0));
829 for (PetscInt which = 0; which < 2; ++which) {
830 PetscCall(MatMult(J, v[which], mffd_v));
831 PetscCall(VecWAXPY(error_vec, -1.0, dense_v[which], mffd_v));
832 PetscCall(VecNorm(error_vec, NORM_2, &error));
833 PetscCall(VecNorm(dense_v[which], NORM_2, &reference));
834 PetscCheck(error <= 2.0e-5 * PetscMax(1.0, reference), PETSC_COMM_WORLD, PETSC_ERR_PLIB,
835 "Independent dense FD Jv differs from PETSc MFFD action %d: error=%g reference=%g.",
836 which, (double)error, (double)reference);
837 }
838
839 for (PetscInt which = 0; which < 2; ++which) {
840 const PetscReal steps[3] = {1.0e-4, 1.0e-6, 1.0e-8};
841 PetscReal best = PETSC_MAX_REAL;
842 for (PetscInt s = 0; s < 3; ++s) {
843 PetscCall(VecWAXPY(xp, steps[s], v[which], x));
844 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, xp, fp, &ctx));
845 PetscCall(VecWAXPY(column, -1.0, f0, fp));
846 PetscCall(VecScale(column, 1.0 / steps[s]));
847 PetscCall(VecAXPY(column, -1.0, dense_v[which]));
848 PetscCall(VecNorm(column, NORM_2, &error));
849 best = PetscMin(best, error);
850 }
851 PetscCall(VecNorm(dense_v[which], NORM_2, &reference));
852 PetscCheck(best <= 2.0e-5 * PetscMax(1.0, reference), PETSC_COMM_WORLD, PETSC_ERR_PLIB,
853 "Direct directional differences show no accuracy plateau for vector %d (best=%g).",
854 which, (double)best);
855 }
856
857 PetscCall(MatDestroy(&J)); PetscCall(SNESDestroy(&snes));
858 PetscCall(VecDestroy(&error_vec)); PetscCall(VecDestroy(&mffd_v));
859 PetscCall(VecDestroy(&dense_v[1])); PetscCall(VecDestroy(&dense_v[0]));
860 PetscCall(VecDestroy(&v[1])); PetscCall(VecDestroy(&v[0]));
861 PetscCall(VecDestroy(&row_norm_sq)); PetscCall(VecDestroy(&square));
862 PetscCall(VecDestroy(&column)); PetscCall(VecDestroy(&fp)); PetscCall(VecDestroy(&f0));
863 PetscCall(VecDestroy(&xp)); PetscCall(VecDestroy(&x));
864 PetscCall(BoundarySystem_Destroy(user));
865 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
866 PetscFunctionReturn(PETSC_SUCCESS);
867}
868
869/** @brief Audits every row of a complete operator containing periodic duplicates. */
870static PetscErrorCode TestPeriodicOperatorHasNoZeroRows(void)
871{
872 SimCtx *simCtx = NULL;
873 UserCtx *user = NULL;
874 Vec x = NULL, xp = NULL, f0 = NULL, fp = NULL, column = NULL;
875 Vec square = NULL, row_norm_sq = NULL;
876 PetscInt n_global, lo, hi;
877 PetscReal min_row_sq = 0.0;
878 const PetscReal epsilon = 1.0e-7;
880
881 PetscFunctionBeginUser;
882 PetscCall(BuildMinimalWallOperatorFixture(&simCtx, &user, PETSC_TRUE));
883 PetscCall(VecDuplicate(user->Ucont, &x)); PetscCall(VecSet(x, 0.0));
884 PetscCall(VecDuplicate(x, &xp)); PetscCall(VecDuplicate(x, &f0));
885 PetscCall(VecDuplicate(x, &fp)); PetscCall(VecDuplicate(x, &column));
886 PetscCall(VecDuplicate(x, &square)); PetscCall(VecDuplicate(x, &row_norm_sq));
887 PetscCall(VecZeroEntries(row_norm_sq));
888 PetscCall(VecGetSize(x, &n_global)); PetscCall(VecGetOwnershipRange(x, &lo, &hi));
889 ctx.user = user;
890 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f0, &ctx));
891 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f0, &ctx));
892 for (PetscInt col = 0; col < n_global; ++col) {
893 PetscCall(VecCopy(x, xp));
894 if (col >= lo && col < hi) PetscCall(VecSetValue(xp, col, epsilon, ADD_VALUES));
895 PetscCall(VecAssemblyBegin(xp)); PetscCall(VecAssemblyEnd(xp));
896 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, xp, fp, &ctx));
897 PetscCall(VecWAXPY(column, -1.0, f0, fp)); PetscCall(VecScale(column, 1.0 / epsilon));
898 PetscCall(VecPointwiseMult(square, column, column));
899 PetscCall(VecAXPY(row_norm_sq, 1.0, square));
900 }
901 PetscCall(VecMin(row_norm_sq, NULL, &min_row_sq));
902 PetscCheck(min_row_sq > 0.5, PETSC_COMM_WORLD, PETSC_ERR_PLIB,
903 "Periodic Newton Jacobian contains a zero/weak row (min squared norm=%g).",
904 (double)min_row_sq);
905 PetscCall(VecDestroy(&row_norm_sq)); PetscCall(VecDestroy(&square));
906 PetscCall(VecDestroy(&column)); PetscCall(VecDestroy(&fp)); PetscCall(VecDestroy(&f0));
907 PetscCall(VecDestroy(&xp)); PetscCall(VecDestroy(&x));
908 PetscCall(BoundarySystem_Destroy(user));
909 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
910 PetscFunctionReturn(PETSC_SUCCESS);
911}
912
913/** @brief Exercises a converged solve, forced rollback, and per-call cleanup. */
914static PetscErrorCode TestSmallSolveAndRollback(void)
915{
916 SimCtx *simCtx = NULL;
917 UserCtx *user = NULL;
918 char tmpdir[PETSC_MAX_PATH_LEN] = "";
919 Vec entry = NULL, delta = NULL;
920 PetscErrorCode solve_ierr;
921 PetscReal norm = 0.0;
922 const FieldId fields[] = {FIELD_ID_UCONT};
923 char summary_path[PETSC_MAX_PATH_LEN];
924 char history_path[PETSC_MAX_PATH_LEN];
925 char linear_history_path[PETSC_MAX_PATH_LEN];
926
927 PetscFunctionBeginUser;
928 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
929 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_rtol", "1e-4"));
930 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_max_it", "20"));
931 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_ksp_rtol", "1e-6"));
932 simCtx->mom_nk_monitor_history = PETSC_TRUE;
933 PetscCall(MomentumSolver_NewtonKrylov(user, NULL, NULL));
934 PetscCall(PicurvAssertBool(simCtx->mom_last_converged, "small Newton solve must converge"));
935 PetscCall(PicurvAssertBool((PetscBool)(user->Rhs == NULL), "successful solve must release Rhs"));
936 PetscCall(PetscSNPrintf(summary_path, sizeof(summary_path),
937 "%s/Momentum_Solver_Newton_Krylov_Summary_Block_0.log", simCtx->log_dir));
938 PetscCall(PetscSNPrintf(history_path, sizeof(history_path),
939 "%s/Momentum_Solver_Newton_Krylov_History_Block_0.log", simCtx->log_dir));
940 PetscCall(PetscSNPrintf(linear_history_path, sizeof(linear_history_path),
941 "%s/Momentum_Solver_Newton_Krylov_Linear_History_Block_0.log",
942 simCtx->log_dir));
943 PetscCall(AssertNewtonLog(summary_path, 1, "solver: Newton Krylov", "state: committed"));
944 PetscCall(AssertNewtonLog(history_path, -2, "newton: 0", "nonlinear_norm:"));
945 PetscCall(AssertNewtonLog(linear_history_path, -2, "krylov: 0", "requested_rtol:"));
946
947 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
948 tmpdir[0] = '\0';
949 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
950 PetscCall(VecSet(user->Ucont, 0.2));
951 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fields));
952 PetscCall(ApplyBoundaryConditions(user));
953 PetscCall(VecDuplicate(user->Ucont, &entry));
954 PetscCall(VecDuplicate(user->Ucont, &delta));
955 PetscCall(VecCopy(user->Ucont, entry));
956 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_max_it", "0"));
957 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
958 solve_ierr = MomentumSolver_NewtonKrylov(user, NULL, NULL);
959 PetscCall(PetscPopErrorHandler());
960 PetscCall(PicurvAssertIntEqual(PETSC_ERR_CONV_FAILED, solve_ierr,
961 "forced nonconvergence must report PETSC_ERR_CONV_FAILED"));
962 PetscCall(VecWAXPY(delta, -1.0, entry, user->Ucont));
963 PetscCall(VecNorm(delta, NORM_INFINITY, &norm));
964 PetscCall(PicurvAssertRealNear(0.0, norm, 1.0e-12,
965 "failed Newton solve must restore the canonical entry state"));
966 PetscCall(PicurvAssertBool((PetscBool)(user->Rhs == NULL), "failed solve must release Rhs"));
967 PetscCall(PetscSNPrintf(summary_path, sizeof(summary_path),
968 "%s/Momentum_Solver_Newton_Krylov_Summary_Block_0.log", simCtx->log_dir));
969 PetscCall(AssertNewtonLog(summary_path, 1, "reason_code: -", "state: rolled_back"));
970
971 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_snes_rtol"));
972 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_snes_max_it"));
973 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_ksp_rtol"));
974 PetscCall(VecDestroy(&delta));
975 PetscCall(VecDestroy(&entry));
976 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
977 PetscFunctionReturn(PETSC_SUCCESS);
978}
979
980/** @brief Verifies the six-wall zero-velocity case logs zero Newton/Krylov work. */
981static PetscErrorCode TestZeroIterationStructuredLogging(void)
982{
983 SimCtx *simCtx = NULL;
984 UserCtx *user = NULL;
985 char tmpdir[PETSC_MAX_PATH_LEN] = "";
986 char summary_path[PETSC_MAX_PATH_LEN];
987 const FieldId fields[] = {FIELD_ID_UCONT};
988
989 PetscFunctionBeginUser;
990 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
991 PetscCall(VecZeroEntries(user->Ucont));
992 PetscCall(VecZeroEntries(user->Ucont_o));
993 PetscCall(VecZeroEntries(user->Ucont_rm1));
994 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fields));
995 PetscCall(ApplyBoundaryConditions(user));
996 PetscCall(MomentumSolver_NewtonKrylov(user, NULL, NULL));
997 PetscCall(PetscSNPrintf(summary_path, sizeof(summary_path),
998 "%s/Momentum_Solver_Newton_Krylov_Summary_Block_0.log", simCtx->log_dir));
999 PetscCall(AssertNewtonLog(summary_path, 1, "newton: 0 | evals: 1 | krylov: 0",
1000 "final: 0.0000000000000000e+00 | state: committed"));
1001 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
1002 PetscFunctionReturn(PETSC_SUCCESS);
1003}
1004
1005/**
1006 * @brief Exercises the straight-duct BDF1 startup path used by flat_channel.
1007 *
1008 * The conservation outlet consumes lUcat during its first boundary pass. This
1009 * test deliberately evaluates the callback at the initialized state before
1010 * installing SNES, then completes the first nonlinear solve with each shipped
1011 * preconditioner. It catches a missing Ucont -> Ucat -> lUcat seed as a
1012 * non-finite initial residual rather than hiding it behind later MFFD work.
1013 */
1014static PetscErrorCode CheckFlatChannelStartup(PetscBool use_point_block)
1015{
1016 SimCtx *simCtx = NULL;
1017 UserCtx *user = NULL;
1018 char tmpdir[PETSC_MAX_PATH_LEN] = "";
1019 Vec x = NULL, f = NULL;
1020 PetscReal initial_norm = 0.0;
1022
1023 PetscFunctionBeginUser;
1024 PetscCall(BuildNewtonFixture(NULL, &simCtx, &user, tmpdir, sizeof(tmpdir)));
1025 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
1026 PetscCall(VecDuplicate(user->Ucont, &x));
1027 PetscCall(VecDuplicate(user->Ucont, &f));
1028 PetscCall(VecCopy(user->Ucont, x));
1029 ctx.user = user;
1030 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f, &ctx));
1031 PetscCall(VecNorm(f, NORM_2, &initial_norm));
1032 PetscCheck(!PetscIsInfOrNanReal(initial_norm), PETSC_COMM_WORLD, PETSC_ERR_FP,
1033 "flat-channel BDF1 initial residual is non-finite (%g) with %s.",
1034 (double)initial_norm,
1035 use_point_block ? "frozen-momentum point-block" : "PCNONE");
1036 PetscCall(VecDestroy(&f));
1037 PetscCall(VecDestroy(&x));
1038 PetscCall(VecDestroy(&user->Rhs));
1039
1040 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_rtol", "1e-4"));
1041 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_max_it", "20"));
1042 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_ksp_rtol", "1e-6"));
1043 if (use_point_block) {
1044 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_model",
1045 "frozen_momentum_jacobian"));
1046 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_structure",
1047 "point_block"));
1048 }
1049 PetscCall(MomentumSolver_NewtonKrylov(user, NULL, NULL));
1050 PetscCall(PicurvAssertBool(simCtx->mom_last_converged,
1051 "flat-channel BDF1 Newton solve must converge"));
1052 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_preconditioner_model"));
1053 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_preconditioner_structure"));
1054 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_snes_rtol"));
1055 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_snes_max_it"));
1056 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_ksp_rtol"));
1057 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
1058 PetscFunctionReturn(PETSC_SUCCESS);
1059}
1060
1061/** @brief Guards flat_channel's initial BDF1 residual and both shipped NK PCs. */
1062static PetscErrorCode TestFlatChannelStartup(void)
1063{
1064 PetscFunctionBeginUser;
1065 PetscCall(CheckFlatChannelStartup(PETSC_FALSE));
1066 PetscCall(CheckFlatChannelStartup(PETSC_TRUE));
1067 PetscFunctionReturn(PETSC_SUCCESS);
1068}
1069
1070/** @brief Verifies restarted Newton solves with both supported preconditioners. */
1071static PetscErrorCode TestRestartAndContinuationSolve(void)
1072{
1073 SimCtx *simCtx = NULL;
1074 UserCtx *user = NULL;
1075 char tmpdir[PETSC_MAX_PATH_LEN] = "";
1076 const FieldId fields[] = {FIELD_ID_UCONT};
1077
1078 PetscFunctionBeginUser;
1079 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
1080 PetscCall(VecSet(user->Ucont, 0.2));
1081 PetscCall(VecZeroEntries(user->Ucont_o));
1082 PetscCall(VecZeroEntries(user->Ucont_rm1));
1083 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fields));
1084 PetscCall(ApplyBoundaryConditions(user));
1085 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_rtol", "1e-4"));
1086 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_max_it", "20"));
1087 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_ksp_rtol", "1e-6"));
1088
1089 /* AdvanceSimulation() solves StartStep+1 first. Use a nonzero checkpoint
1090 * state so that SNES takes a Newton/Krylov path rather than accepting a
1091 * trivial residual. */
1092 simCtx->StartStep = 7;
1093 simCtx->step = simCtx->StartStep + 1;
1094 PetscCall(MomentumSolver_NewtonKrylov(user, NULL, NULL));
1095 PetscCall(PicurvAssertBool(simCtx->mom_last_converged,
1096 "PCNONE Newton Krylov checkpoint restart must converge"));
1097
1098 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
1099 tmpdir[0] = '\0';
1100 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
1101 PetscCall(VecSet(user->Ucont, 0.2));
1102 PetscCall(VecZeroEntries(user->Ucont_o));
1103 PetscCall(VecZeroEntries(user->Ucont_rm1));
1104 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fields));
1105 PetscCall(ApplyBoundaryConditions(user));
1106 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_model",
1107 "frozen_momentum_jacobian"));
1108 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_structure", "point_block"));
1109
1110 simCtx->continueMode = PETSC_TRUE;
1111 simCtx->StartStep = 8;
1112 simCtx->step = simCtx->StartStep + 1;
1113 PetscCall(MomentumSolver_NewtonKrylov(user, NULL, NULL));
1114 PetscCall(PicurvAssertBool(simCtx->mom_last_converged,
1115 "frozen-momentum Newton Krylov --continue solve must converge"));
1116 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_preconditioner_model"));
1117 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_preconditioner_structure"));
1118 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_snes_rtol"));
1119 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_snes_max_it"));
1120 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_ksp_rtol"));
1121 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
1122 PetscFunctionReturn(PETSC_SUCCESS);
1123}
1124
1125/** @brief Confirms unsupported features fail before workspace allocation. */
1127{
1128 SimCtx *simCtx = NULL;
1129 UserCtx *user = NULL;
1130 char tmpdir[PETSC_MAX_PATH_LEN] = "";
1131 PetscErrorCode solve_ierr;
1132
1133 PetscFunctionBeginUser;
1134 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
1135 {
1136 /* The gradient (Clark) model and wall functions are no longer rejected: both
1137 reach the solver through the shared residual, which this solver already
1138 evaluates. Neither is verified under Newton-Krylov yet, so neither is
1139 claimed as supported; they are simply no longer refused. */
1140 PetscInt *unsupported_flags[] = {
1141 &simCtx->immersed, &simCtx->movefsi, &simCtx->rotatefsi,
1142 &simCtx->moveframe, &simCtx->rotateframe,
1143 &simCtx->TwoD
1144 };
1145 for (size_t flag = 0; flag < sizeof(unsupported_flags) / sizeof(unsupported_flags[0]); ++flag) {
1146 *unsupported_flags[flag] = 1;
1147 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1148 solve_ierr = MomentumSolver_NewtonKrylov(user, NULL, NULL);
1149 PetscCall(PetscPopErrorHandler());
1150 PetscCall(PicurvAssertBool((PetscBool)(solve_ierr != PETSC_SUCCESS),
1151 "unsupported Newton feature flag must fail"));
1152 PetscCall(PicurvAssertBool((PetscBool)(user->Rhs == NULL),
1153 "feature validation must precede workspace allocation"));
1154 *unsupported_flags[flag] = 0;
1155 }
1156 }
1157 simCtx->block_number = 2;
1158 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1159 solve_ierr = MomentumSolver_NewtonKrylov(user, NULL, NULL);
1160 PetscCall(PetscPopErrorHandler());
1161 PetscCall(PicurvAssertBool((PetscBool)(solve_ierr != PETSC_SUCCESS), "multiblock must fail"));
1162 simCtx->block_number = 1;
1163 /* Solid cells are no longer a rejection. The residual already zeroes their rows, so
1164 they carry no unknown; the solver now gives them the same identity treatment as
1165 any other row without one, instead of refusing the whole solve. A fully masked
1166 domain is the degenerate case of that: every row is an identity row and the
1167 system is trivially consistent. This checks the structural contract only; the
1168 masked path has no physics verification because nothing in the tree currently
1169 populates Nvert with a nonzero value. */
1170 PetscCall(VecSet(user->Nvert, 1.0));
1171 PetscCall(VecSet(user->lNvert, 1.0));
1172 solve_ierr = MomentumSolver_NewtonKrylov(user, NULL, NULL);
1173 PetscCall(PicurvAssertBool((PetscBool)(solve_ierr == PETSC_SUCCESS),
1174 "a fully masked domain must resolve to identity rows, not a rejection"));
1175 PetscCall(VecSet(user->Nvert, 0.0));
1176 PetscCall(VecSet(user->lNvert, 0.0));
1177 PetscCall(VecSet(user->Nvert, 0.0));
1180 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1181 solve_ierr = MomentumSolver_NewtonKrylov(user, NULL, NULL);
1182 PetscCall(PetscPopErrorHandler());
1183 PetscCall(PicurvAssertBool((PetscBool)(solve_ierr != PETSC_SUCCESS),
1184 "driven constant-flux controller must fail"));
1185 PetscCall(PicurvAssertBool((PetscBool)(user->Rhs == NULL),
1186 "all validation failures must precede workspace allocation"));
1187 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
1188 PetscFunctionReturn(PETSC_SUCCESS);
1189}
1190
1191/** @brief Verifies cleanup and rollback after an options failure following asset creation. */
1192static PetscErrorCode TestPostAllocationFailureCleanup(void)
1193{
1194 SimCtx *simCtx = NULL;
1195 UserCtx *user = NULL;
1196 char tmpdir[PETSC_MAX_PATH_LEN] = "";
1197 Vec entry = NULL, delta = NULL;
1198 PetscErrorCode solve_ierr;
1199 PetscReal norm = 0.0;
1200 const FieldId fields[] = {FIELD_ID_UCONT};
1201
1202 PetscFunctionBeginUser;
1203 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
1204 PetscCall(VecSet(user->Ucont, 0.2));
1205 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fields));
1206 PetscCall(ApplyBoundaryConditions(user));
1207 PetscCall(VecDuplicate(user->Ucont, &entry)); PetscCall(VecCopy(user->Ucont, entry));
1208 PetscCall(VecDuplicate(user->Ucont, &delta));
1209 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_pc_type", "jacobi"));
1210 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1211 solve_ierr = MomentumSolver_NewtonKrylov(user, NULL, NULL);
1212 PetscCall(PetscPopErrorHandler());
1213 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_pc_type"));
1214 PetscCall(PicurvAssertIntEqual(PETSC_ERR_SUP, solve_ierr,
1215 "non-PCNONE option must fail after setup"));
1216 PetscCall(PicurvAssertBool((PetscBool)(user->Rhs == NULL),
1217 "post-allocation failure must destroy Rhs"));
1218 PetscCall(VecWAXPY(delta, -1.0, entry, user->Ucont));
1219 PetscCall(VecNorm(delta, NORM_INFINITY, &norm));
1220 PetscCall(PicurvAssertRealNear(0.0, norm, 1.0e-12,
1221 "post-allocation failure must restore canonical entry"));
1222 PetscCall(VecDestroy(&delta)); PetscCall(VecDestroy(&entry));
1223 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
1224 PetscFunctionReturn(PETSC_SUCCESS);
1225}
1226
1227/** @brief Verifies finalized application-owned linearization option parsing. */
1228static PetscErrorCode TestLinearizationConfigParsing(void)
1229{
1230 MomentumNewtonJacobian jacobian = {0};
1231 MomentumPreconditionerDescription description = {0};
1232 PetscErrorCode config_ierr;
1233 const char *option_names[] = {
1234 "-mom_nk_jacobian_type",
1235 "-mom_nk_jacobian_fd_mode",
1236 "-mom_nk_preconditioner_model",
1237 "-mom_nk_preconditioner_structure"
1238 };
1239
1240 PetscFunctionBeginUser;
1241 for (size_t n = 0; n < sizeof(option_names) / sizeof(option_names[0]); ++n)
1242 PetscCall(PetscOptionsClearValue(NULL, option_names[n]));
1243
1244 PetscCall(MomentumNewtonKrylov_ReadLinearizationConfig(&jacobian, &description));
1246 "default Jacobian type must be finite difference"));
1248 jacobian.finite_difference_mode,
1249 "default finite-difference mode must be matrix free"));
1250 PetscCall(PicurvAssertIntEqual(MOM_NK_PC_MODEL_NONE, description.model,
1251 "default preconditioner model must be none"));
1253 "default preconditioner structure must be none"));
1254
1255 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_jacobian_type", "finite_difference"));
1256 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_jacobian_fd_mode", "matrix_free"));
1257 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_model", "none"));
1258 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_structure", "none"));
1259 PetscCall(MomentumNewtonKrylov_ReadLinearizationConfig(&jacobian, &description));
1261 "explicit baseline Jacobian type must parse"));
1263 jacobian.finite_difference_mode,
1264 "explicit baseline finite-difference mode must parse"));
1265 PetscCall(PicurvAssertIntEqual(MOM_NK_PC_MODEL_NONE, description.model,
1266 "explicit baseline preconditioner model must parse"));
1268 "explicit baseline preconditioner structure must parse"));
1269
1270 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_model",
1271 "frozen_momentum_jacobian"));
1272 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_structure", "point_block"));
1273 PetscCall(MomentumNewtonKrylov_ReadLinearizationConfig(&jacobian, &description));
1275 "explicit Jacobian type must parse"));
1277 jacobian.finite_difference_mode,
1278 "explicit finite-difference mode must parse"));
1280 description.model, "frozen model must parse"));
1282 description.structure, "point-block structure must parse"));
1283
1284 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_jacobian_type", "analytic"));
1285 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1286 config_ierr = MomentumNewtonKrylov_ReadLinearizationConfig(&jacobian, &description);
1287 PetscCall(PetscPopErrorHandler());
1288 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_WRONG, config_ierr,
1289 "unsupported Jacobian type must fail"));
1290 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_jacobian_type", "finite_difference"));
1291
1292 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_jacobian_fd_mode", "colored_sparse"));
1293 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1294 config_ierr = MomentumNewtonKrylov_ReadLinearizationConfig(&jacobian, &description);
1295 PetscCall(PetscPopErrorHandler());
1296 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_WRONG, config_ierr,
1297 "unsupported finite-difference mode must fail"));
1298 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_jacobian_fd_mode", "matrix_free"));
1299
1300 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_structure", "none"));
1301 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1302 config_ierr = MomentumNewtonKrylov_ReadLinearizationConfig(&jacobian, &description);
1303 PetscCall(PetscPopErrorHandler());
1304 PetscCall(PicurvAssertIntEqual(PETSC_ERR_SUP, config_ierr,
1305 "frozen model without point block must fail"));
1306 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_model", "none"));
1307 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_structure", "point_block"));
1308 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1309 config_ierr = MomentumNewtonKrylov_ReadLinearizationConfig(&jacobian, &description);
1310 PetscCall(PetscPopErrorHandler());
1311 PetscCall(PicurvAssertIntEqual(PETSC_ERR_SUP, config_ierr,
1312 "none model with point block must fail"));
1313
1314 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_model", "diagonal"));
1315 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_structure", "none"));
1316 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1317 config_ierr = MomentumNewtonKrylov_ReadLinearizationConfig(&jacobian, &description);
1318 PetscCall(PetscPopErrorHandler());
1319 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_WRONG, config_ierr,
1320 "unsupported preconditioner model must fail"));
1321 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_model",
1322 "frozen_momentum_jacobian"));
1323 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_structure", "line"));
1324 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1325 config_ierr = MomentumNewtonKrylov_ReadLinearizationConfig(&jacobian, &description);
1326 PetscCall(PetscPopErrorHandler());
1327 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_WRONG, config_ierr,
1328 "unsupported preconditioner structure must fail"));
1329
1330 for (size_t n = 0; n < sizeof(option_names) / sizeof(option_names[0]); ++n)
1331 PetscCall(PetscOptionsClearValue(NULL, option_names[n]));
1332 PetscFunctionReturn(PETSC_SUCCESS);
1333}
1334
1335/** @brief Converts an in-domain or periodic-ghost DMDA stencil to PETSc ordering. */
1336static PetscErrorCode TestStencilToGlobal(UserCtx *user, MatStencil stencil,
1337 PetscInt *global_index)
1338{
1339 ISLocalToGlobalMapping local_to_global = NULL;
1340 PetscInt ghost_starts[3], ghost_sizes[3], local_index;
1341
1342 PetscFunctionBeginUser;
1343 PetscCall(DMDAGetGhostCorners(user->fda,
1344 &ghost_starts[0], &ghost_starts[1], &ghost_starts[2],
1345 &ghost_sizes[0], &ghost_sizes[1], &ghost_sizes[2]));
1346 PetscCall(PicurvAssertBool((PetscBool)(
1347 stencil.i >= ghost_starts[0] && stencil.i < ghost_starts[0] + ghost_sizes[0] &&
1348 stencil.j >= ghost_starts[1] && stencil.j < ghost_starts[1] + ghost_sizes[1] &&
1349 stencil.k >= ghost_starts[2] && stencil.k < ghost_starts[2] + ghost_sizes[2]),
1350 "test stencil must lie in the local DMDA ghost region"));
1351 local_index = stencil.c + 3 * (
1352 (stencil.i - ghost_starts[0]) + ghost_sizes[0] * (
1353 (stencil.j - ghost_starts[1]) + ghost_sizes[1] *
1354 (stencil.k - ghost_starts[2])));
1355 PetscCall(DMGetLocalToGlobalMapping(user->fda, &local_to_global));
1356 PetscCall(ISLocalToGlobalMappingApply(local_to_global, 1, &local_index,
1357 global_index));
1358 PetscFunctionReturn(PETSC_SUCCESS);
1359}
1360
1361/** @brief Reads one DMDA-stencil matrix entry through collective basis vectors. */
1362static PetscErrorCode PreconditionerMatrixStencilEntry(UserCtx *user,
1363 Mat preconditioning_matrix, MatStencil row,
1364 MatStencil col, PetscScalar *value)
1365{
1366 Vec column_basis = NULL, row_basis = NULL, product = NULL;
1367 AO ao = NULL;
1368 PetscInt mx, my, row_index, col_index;
1369 PetscFunctionBeginUser;
1370 PetscCall(DMDAGetInfo(user->fda, NULL, &mx, &my, NULL, NULL, NULL, NULL,
1371 NULL, NULL, NULL, NULL, NULL, NULL));
1372 row_index = row.c + 3 * (row.i + mx * (row.j + my * row.k));
1373 col_index = col.c + 3 * (col.i + mx * (col.j + my * col.k));
1374 PetscCall(DMDAGetAO(user->fda, &ao));
1375 PetscCall(AOApplicationToPetsc(ao, 1, &row_index));
1376 PetscCall(AOApplicationToPetsc(ao, 1, &col_index));
1377 PetscCall(DMCreateGlobalVector(user->fda, &column_basis));
1378 PetscCall(DMCreateGlobalVector(user->fda, &row_basis));
1379 PetscCall(DMCreateGlobalVector(user->fda, &product));
1380 PetscCall(VecSet(column_basis, 0.0)); PetscCall(VecSet(row_basis, 0.0));
1381 PetscCall(VecSetValue(column_basis, col_index, 1.0, INSERT_VALUES));
1382 PetscCall(VecSetValue(row_basis, row_index, 1.0, INSERT_VALUES));
1383 PetscCall(VecAssemblyBegin(column_basis)); PetscCall(VecAssemblyEnd(column_basis));
1384 PetscCall(VecAssemblyBegin(row_basis)); PetscCall(VecAssemblyEnd(row_basis));
1385 PetscCall(MatMult(preconditioning_matrix, column_basis, product));
1386 PetscCall(VecDot(row_basis, product, value));
1387 PetscCall(VecDestroy(&product)); PetscCall(VecDestroy(&row_basis));
1388 PetscCall(VecDestroy(&column_basis));
1389 PetscFunctionReturn(PETSC_SUCCESS);
1390}
1391
1392/** @brief Verifies the exact AIJ layout and preallocation derived from row classes. */
1394 UserCtx *user, Mat matrix, PetscBool require_offrank_periodic)
1395{
1396 DMDALocalInfo info;
1397 MatInfo matrix_info;
1398 PetscMPIInt comm_size = 1;
1399 PetscInt matrix_rows, matrix_cols, local_rows, local_cols;
1400 PetscInt vector_size, vector_local_size, block_size;
1401 PetscInt ownership_start, ownership_end;
1402 PetscInt expected_local = 0, expected_global = 0;
1403 PetscInt offrank_periodic_local = 0, offrank_periodic_global = 0;
1404 PetscBool is_seq_aij = PETSC_FALSE, is_mpi_aij = PETSC_FALSE;
1405
1406 PetscFunctionBeginUser;
1407 PetscCall(DMDAGetLocalInfo(user->fda, &info));
1408 PetscCall(MatGetSize(matrix, &matrix_rows, &matrix_cols));
1409 PetscCall(MatGetLocalSize(matrix, &local_rows, &local_cols));
1410 PetscCall(VecGetSize(user->Ucont, &vector_size));
1411 PetscCall(VecGetLocalSize(user->Ucont, &vector_local_size));
1412 PetscCall(VecGetOwnershipRange(user->Ucont, &ownership_start, &ownership_end));
1413 PetscCall(MatGetBlockSize(matrix, &block_size));
1414 PetscCall(PicurvAssertIntEqual(vector_size, matrix_rows,
1415 "point-block global row dimension"));
1416 PetscCall(PicurvAssertIntEqual(vector_size, matrix_cols,
1417 "point-block global column dimension"));
1418 PetscCall(PicurvAssertIntEqual(vector_local_size, local_rows,
1419 "point-block local row dimension"));
1420 PetscCall(PicurvAssertIntEqual(vector_local_size, local_cols,
1421 "point-block local column dimension"));
1422 PetscCall(PicurvAssertIntEqual(3, block_size, "point-block logical block size"));
1423 PetscCallMPI(MPI_Comm_size(PetscObjectComm((PetscObject)matrix), &comm_size));
1424 PetscCall(PetscObjectTypeCompare((PetscObject)matrix, MATSEQAIJ, &is_seq_aij));
1425 PetscCall(PetscObjectTypeCompare((PetscObject)matrix, MATMPIAIJ, &is_mpi_aij));
1426 PetscCall(PicurvAssertBool(
1427 comm_size == 1 ? is_seq_aij : is_mpi_aij,
1428 "point-block matrix must use the expected AIJ implementation"));
1429
1430 for (PetscInt k = info.zs; k < info.zs + info.zm; ++k) {
1431 for (PetscInt j = info.ys; j < info.ys + info.ym; ++j) {
1432 for (PetscInt i = info.xs; i < info.xs + info.xm; ++i) {
1433 for (PetscInt component = 0; component < 3; ++component) {
1434 PetscInt ri, rj, rk;
1436 user, i, j, k, component, &ri, &rj, &rk);
1437 expected_local += type == MOM_ROW_PHYSICAL ? 3 :
1438 type == MOM_ROW_PERIODIC_DUPLICATE ? 2 : 1;
1439 if (type == MOM_ROW_PERIODIC_DUPLICATE) {
1440 PetscInt representative;
1441 PetscCall(TestStencilToGlobal(user,
1442 (MatStencil){.i = ri, .j = rj, .k = rk, .c = component},
1443 &representative));
1444 if (representative < ownership_start || representative >= ownership_end)
1445 ++offrank_periodic_local;
1446 }
1447 }
1448 }
1449 }
1450 }
1451 PetscCallMPI(MPI_Allreduce(&expected_local, &expected_global, 1, MPIU_INT,
1452 MPI_SUM, PetscObjectComm((PetscObject)matrix)));
1453 PetscCallMPI(MPI_Allreduce(&offrank_periodic_local, &offrank_periodic_global,
1454 1, MPIU_INT, MPI_SUM,
1455 PetscObjectComm((PetscObject)matrix)));
1456 PetscCall(MatGetInfo(matrix, MAT_GLOBAL_SUM, &matrix_info));
1457 PetscCall(PicurvAssertRealNear((PetscReal)expected_global,
1458 (PetscReal)matrix_info.nz_allocated, 0.0,
1459 "point-block matrix must allocate exactly the classified scalar pattern"));
1460 PetscCall(PicurvAssertRealNear((PetscReal)expected_global,
1461 (PetscReal)matrix_info.nz_used, 0.0,
1462 "point-block assembly must insert every classified structural entry"));
1463 PetscCall(PicurvAssertRealNear(0.0, (PetscReal)matrix_info.mallocs, 0.0,
1464 "point-block insertion must not reallocate matrix storage"));
1465 if (require_offrank_periodic && comm_size > 1)
1466 PetscCall(PicurvAssertBool((PetscBool)(offrank_periodic_global > 0),
1467 "MPI periodic fixture must exercise off-rank preallocation"));
1468 PetscFunctionReturn(PETSC_SUCCESS);
1469}
1470
1471/**
1472 * @brief Verifies the point-block model and common preconditioning-engine wiring.
1473 * @return PETSc error code.
1474 */
1475static PetscErrorCode TestPointBlockPreconditionerEngine(void)
1476{
1477 SimCtx *simCtx = NULL;
1478 UserCtx *user = NULL;
1479 char tmpdir[PETSC_MAX_PATH_LEN] = "";
1481 MomentumPreconditionerDescription description = {
1484 0,
1485 0
1486 };
1487 MomentumPreconditionerEngine engine = {0};
1488 Mat preconditioning_matrix = NULL;
1489 Vec x = NULL, f = NULL;
1490 PetscInt block_size = 0, velocity_dof = 0;
1491 PetscReal matrix_norm = 0.0, reassembled_norm = 0.0, difference_norm = 0.0;
1492 MatStencil conditioned = {.i = 0, .j = 2, .k = 3, .c = 0};
1493 MatStencil homogeneous = {.i = 0, .j = 2, .k = 3, .c = 1};
1494 Mat saved_matrix = NULL, mffd = NULL;
1495 SNES snes = NULL;
1496 Vec direction = NULL, product = NULL, px = NULL;
1497 PetscReal px_norm = 0.0;
1498 KSP ksp = NULL;
1499 PC pc = NULL;
1500 Vec pc_rhs = NULL, pc_solution = NULL;
1501 MatInfo initial_allocation_info, repeated_allocation_info;
1502
1503 PetscFunctionBeginUser;
1504 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
1505 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
1506 PetscCall(VecDuplicate(user->Ucont, &x));
1507 PetscCall(VecDuplicate(user->Ucont, &f));
1508 PetscCall(VecCopy(user->Ucont, x));
1509 PetscCall(VecStrideSet(x, 0, 0.2));
1510 PetscCall(VecStrideSet(x, 1, 0.3));
1511 PetscCall(VecStrideSet(x, 2, 0.4));
1512 ctx.user = user;
1513 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f, &ctx));
1514 PetscCall(MomentumPreconditionerEngine_Create(user, NULL, &description, &engine));
1515 preconditioning_matrix = engine.preconditioning_matrix;
1516 PetscCall(PicurvAssertBool((PetscBool)(engine.model_ops == &frozen_momentum_point_block_ops),
1517 "engine must select the frozen-momentum model callbacks"));
1519 "point-block engine must own its separate matrix"));
1520 PetscCall(PicurvAssertBool((PetscBool)!engine.aliases_jacobian_operator,
1521 "point-block matrix must not alias the Jacobian operator"));
1522 PetscCall(MomentumPreconditionerEngine_Assemble(&engine, user, x));
1524 user, preconditioning_matrix, PETSC_FALSE));
1525 PetscCall(MatGetInfo(preconditioning_matrix, MAT_GLOBAL_SUM,
1526 &initial_allocation_info));
1527 PetscCall(MatNorm(preconditioning_matrix, NORM_FROBENIUS, &matrix_norm));
1528 PetscCall(PicurvAssertBool((PetscBool)(matrix_norm > 0.0),
1529 "model callback and common rows must insert matrix entries"));
1530 PetscCall(MatGetBlockSize(preconditioning_matrix, &block_size));
1531 PetscCall(PicurvAssertIntEqual(3, block_size, "point-block matrix block size"));
1532 PetscCall(DMDAGetInfo(user->fda, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1533 &velocity_dof, NULL, NULL, NULL, NULL, NULL));
1534 PetscCall(PicurvAssertIntEqual(3, velocity_dof, "Newton velocity DMDA dof"));
1535
1536 /* The coefficient formulas define columns by the differentiated Ucont
1537 component. Check an asymmetric pair so a row/column transpose cannot pass. */
1538 {
1539 const MatStencil row_i = {.i = 2, .j = 2, .k = 2, .c = 0};
1540 const MatStencil row_j = {.i = 2, .j = 2, .k = 2, .c = 1};
1541 MatStencil col_i = row_i, col_j = row_j;
1542 PetscReal ***aj = NULL;
1543 PetscReal AJip, AJjp;
1544 PetscScalar dFi_dUj = 0.0, dFj_dUi = 0.0;
1545
1546 PetscCall(DMDAVecGetArrayRead(user->da, user->lAj, &aj));
1547 AJip = 0.5 * (aj[2][2][2] + aj[2][2][3]);
1548 AJjp = 0.5 * (aj[2][2][2] + aj[2][3][2]);
1549 PetscCall(DMDAVecRestoreArrayRead(user->da, user->lAj, &aj));
1550 PetscCall(PreconditionerMatrixStencilEntry(user, preconditioning_matrix,
1551 row_i, col_j, &dFi_dUj));
1552 PetscCall(PreconditionerMatrixStencilEntry(user, preconditioning_matrix,
1553 row_j, col_i, &dFj_dUi));
1554 PetscCall(PicurvAssertRealNear(0.5 * AJjp * 0.2,
1555 PetscRealPart(dFi_dUj), 1e-12,
1556 "row i, column j must differentiate with respect to U-j"));
1557 PetscCall(PicurvAssertRealNear(0.5 * AJip * 0.3,
1558 PetscRealPart(dFj_dUi), 1e-12,
1559 "row j, column i must differentiate with respect to U-i"));
1560 }
1561
1562 /* Both fixed categories are exact identity rows, with no same-cell coupling. */
1563 for (PetscInt cc = 0; cc < 3; ++cc) {
1564 MatStencil col = conditioned; col.c = cc;
1565 PetscScalar conditioned_value = 0.0, homogeneous_value = 0.0;
1566 PetscCall(PreconditionerMatrixStencilEntry(user, preconditioning_matrix,
1567 conditioned, col, &conditioned_value));
1568 col = homogeneous; col.c = cc;
1569 PetscCall(PreconditionerMatrixStencilEntry(user, preconditioning_matrix,
1570 homogeneous, col, &homogeneous_value));
1571 PetscCall(PicurvAssertRealNear(cc == conditioned.c ? 1.0 : 0.0,
1572 PetscRealPart(conditioned_value), 1e-14, "conditioned row must be exact identity"));
1573 PetscCall(PicurvAssertRealNear(cc == homogeneous.c ? 1.0 : 0.0,
1574 PetscRealPart(homogeneous_value), 1e-14, "homogeneous row must be exact identity"));
1575 }
1576
1577 /* A real residual and MFFD product cannot change subsequent assembly. */
1578 PetscCall(MatDuplicate(preconditioning_matrix, MAT_COPY_VALUES, &saved_matrix));
1579 PetscCall(SNESCreate(PETSC_COMM_WORLD, &snes));
1580 PetscCall(SNESSetDM(snes, user->fda));
1581 PetscCall(SNESSetFunction(snes, f, MomentumNewtonKrylov_FormResidual, &ctx));
1582 PetscCall(MatCreateSNESMF(snes, &mffd));
1583 PetscCall(VecDuplicate(x, &direction)); PetscCall(VecDuplicate(x, &product));
1584 PetscCall(VecSet(direction, 0.375));
1585 PetscCall(MomentumNewtonKrylov_FormResidual(snes, x, f, &ctx));
1586 PetscCall(MatMFFDSetBase(mffd, x, f));
1587 PetscCall(MatMult(mffd, direction, product));
1588 PetscCall(MomentumPreconditionerEngine_Assemble(&engine, user, x));
1589 PetscCall(MatAXPY(saved_matrix, -1.0, preconditioning_matrix, SAME_NONZERO_PATTERN));
1590 PetscCall(MatNorm(saved_matrix, NORM_FROBENIUS, &difference_norm));
1591 PetscCall(PicurvAssertRealNear(0.0, difference_norm, 1e-12,
1592 "assembly must be unchanged after residual and MFFD products"));
1593 PetscCall(VecDestroy(&product)); PetscCall(VecDestroy(&direction));
1594 PetscCall(MatDestroy(&mffd)); PetscCall(SNESDestroy(&snes)); PetscCall(MatDestroy(&saved_matrix));
1595
1596 PetscCall(VecDuplicate(x, &px));
1597 PetscCall(MatMult(preconditioning_matrix, x, px));
1598 PetscCall(VecNorm(px, NORM_2, &px_norm));
1599 PetscCall(PicurvAssertBool((PetscBool)(!PetscIsInfOrNanReal(px_norm) && px_norm > 0.0),
1600 "point-block product must be finite and nonzero"));
1601 PetscCall(VecDestroy(&px));
1602
1603 {
1604 PetscBool assembled = PETSC_FALSE;
1605 PetscCall(MatAssembled(preconditioning_matrix, &assembled));
1606 PetscCall(PicurvAssertBool(assembled, "engine must perform final matrix assembly"));
1607 }
1608 PetscCall(MatNorm(preconditioning_matrix, NORM_FROBENIUS, &matrix_norm));
1609 PetscCall(MatShift(preconditioning_matrix, 7.0));
1610 PetscCall(MomentumPreconditionerEngine_Assemble(&engine, user, x));
1611 PetscCall(MatGetInfo(preconditioning_matrix, MAT_GLOBAL_SUM,
1612 &repeated_allocation_info));
1613 PetscCall(PicurvAssertRealNear((PetscReal)initial_allocation_info.nz_allocated,
1614 (PetscReal)repeated_allocation_info.nz_allocated, 0.0,
1615 "repeated assembly must retain exact allocated storage"));
1616 PetscCall(PicurvAssertRealNear((PetscReal)initial_allocation_info.mallocs,
1617 (PetscReal)repeated_allocation_info.mallocs, 0.0,
1618 "repeated assembly must not add insertion reallocations"));
1619 PetscCall(MatNorm(preconditioning_matrix, NORM_FROBENIUS, &reassembled_norm));
1620 PetscCall(PicurvAssertRealNear(matrix_norm, reassembled_norm, 1e-12,
1621 "repeated engine assembly must clear old entries"));
1622 {
1623 PetscScalar value = 0.0;
1624 PetscCall(PreconditionerMatrixStencilEntry(user, preconditioning_matrix,
1625 conditioned, conditioned, &value));
1626 PetscCall(PicurvAssertRealNear(1.0, PetscRealPart(value), 1e-14,
1627 "repeated engine assembly must clear old entries"));
1628 }
1629 PetscCall(KSPCreate(PETSC_COMM_WORLD, &ksp));
1630 PetscCall(KSPSetOperators(ksp, preconditioning_matrix, preconditioning_matrix));
1631 PetscCall(KSPGetPC(ksp, &pc));
1632 PetscCall(MomentumPreconditionerEngine_ConfigurePetscPC(&engine, pc));
1633 PetscCall(MomentumPreconditionerEngine_ValidatePetscPC(&engine, pc));
1634 PetscCall(KSPSetUp(ksp));
1635 PetscCall(DMCreateGlobalVector(user->fda, &pc_rhs));
1636 PetscCall(DMCreateGlobalVector(user->fda, &pc_solution));
1637 PetscCall(VecSet(pc_rhs, 1.0));
1638 PetscCall(PCApply(pc, pc_rhs, pc_solution));
1639 PetscCall(VecDestroy(&pc_solution)); PetscCall(VecDestroy(&pc_rhs));
1640 PetscCall(KSPDestroy(&ksp));
1641 PetscCall(MomentumPreconditionerEngine_Destroy(&engine));
1642 PetscCall(PicurvAssertBool((PetscBool)(engine.preconditioning_matrix == NULL),
1643 "engine destroy must clear its owned matrix"));
1644 PetscCall(VecDestroy(&f)); PetscCall(VecDestroy(&x)); PetscCall(VecDestroy(&user->Rhs));
1645 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
1646 PetscFunctionReturn(PETSC_SUCCESS);
1647}
1648
1649/** @brief Proves that one periodic matrix row has only its exact +1/-1 pair. */
1650static PetscErrorCode AssertPeriodicPreconditionerRow(UserCtx *user, Mat matrix,
1651 MatStencil row, const char *message)
1652{
1653 AO ao = NULL;
1654 PetscInt ri, rj, rk, global_row, global_rep, lo, hi, local_checked = 0, checked = 0;
1655 PetscInt ncols = 0;
1656 const PetscInt *cols = NULL;
1657 const PetscScalar *values = NULL;
1658 MomentumRowType type;
1659
1660 PetscFunctionBeginUser;
1661 type = ClassifyMomentumRow(user, row.i, row.j, row.k, row.c, &ri, &rj, &rk);
1662 PetscCall(PicurvAssertIntEqual(MOM_ROW_PERIODIC_DUPLICATE, type, message));
1663 global_row = row.c + 3 * (row.i + user->info.mx * (row.j + user->info.my * row.k));
1664 PetscCall(DMDAGetAO(user->fda, &ao));
1665 PetscCall(AOApplicationToPetsc(ao, 1, &global_row));
1666 PetscCall(MatGetOwnershipRange(matrix, &lo, &hi));
1667 if (global_row >= lo && global_row < hi) {
1668 PetscBool found_self = PETSC_FALSE, found_rep = PETSC_FALSE;
1669 PetscInt nonzero_entries = 0;
1670 PetscCall(TestStencilToGlobal(user,
1671 (MatStencil){.i = ri, .j = rj, .k = rk, .c = row.c}, &global_rep));
1672 PetscCall(MatGetRow(matrix, global_row, &ncols, &cols, &values));
1673 for (PetscInt n = 0; n < ncols; ++n) {
1674 if (PetscAbsScalar(values[n]) <= 1e-14) continue;
1675 nonzero_entries++;
1676 if (cols[n] == global_row) {
1677 found_self = PETSC_TRUE;
1678 PetscCall(PicurvAssertRealNear(1.0, PetscRealPart(values[n]), 1e-14,
1679 "periodic row self entry"));
1680 } else if (cols[n] == global_rep) {
1681 found_rep = PETSC_TRUE;
1682 PetscCall(PicurvAssertRealNear(-1.0, PetscRealPart(values[n]), 1e-14,
1683 "periodic row representative entry"));
1684 } else {
1685 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_PLIB,
1686 "Periodic row contains an unintended nonzero column.");
1687 }
1688 }
1689 PetscCall(PicurvAssertIntEqual(2, nonzero_entries,
1690 "periodic row must contain exactly two numerical entries"));
1691 PetscCall(PicurvAssertBool((PetscBool)(found_self && found_rep), message));
1692 PetscCall(MatRestoreRow(matrix, global_row, &ncols, &cols, &values));
1693 local_checked = 1;
1694 }
1695 PetscCallMPI(MPI_Allreduce(&local_checked, &checked, 1, MPIU_INT, MPI_SUM, PETSC_COMM_WORLD));
1696 PetscCall(PicurvAssertIntEqual(1, checked, "periodic row must have exactly one matrix owner"));
1697 PetscFunctionReturn(PETSC_SUCCESS);
1698}
1699
1700/** @brief Verifies Jacobian creation/registration and baseline alias ownership. */
1701static PetscErrorCode TestJacobianInterfaceAndBaselineAlias(void)
1702{
1703 SimCtx *simCtx = NULL;
1704 UserCtx *user = NULL;
1705 char tmpdir[PETSC_MAX_PATH_LEN] = "";
1707 MomentumPreconditionerDescription description = {
1709 };
1710 SNES snes = NULL;
1711 Mat jacobian_operator = NULL, preconditioning_matrix = NULL;
1712 KSP ksp = NULL;
1713 PC pc = NULL;
1714 PetscInt rows = 0, cols = 0;
1715 const char *jacobian_prefix = NULL;
1716 const char *pc_type = NULL;
1717 PetscBool prefix_matches = PETSC_FALSE;
1718 PetscBool pc_is_none = PETSC_FALSE;
1719
1720 PetscFunctionBeginUser;
1721 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
1722 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
1723 PetscCall(SNESCreate(PETSC_COMM_WORLD, &snes));
1724 PetscCall(SNESSetDM(snes, user->fda));
1725 ctx.user = user;
1728 PetscCall(SNESSetFunction(snes, NULL, MomentumNewtonKrylov_FormResidual, &ctx));
1729 PetscCall(MomentumNewtonJacobian_Create(snes, &ctx.jacobian));
1730 PetscCall(MatGetOptionsPrefix(ctx.jacobian.jacobian_operator, &jacobian_prefix));
1731 PetscCall(PetscStrcmp(jacobian_prefix, "mom_nk_", &prefix_matches));
1732 PetscCall(PicurvAssertBool(prefix_matches,
1733 "Jacobian interface must apply the application prefix"));
1735 user, ctx.jacobian.jacobian_operator, &description, &ctx.preconditioning_engine));
1737 snes, &ctx.jacobian, &ctx.preconditioning_engine, &ctx));
1738 PetscCall(SNESGetKSP(snes, &ksp));
1739 PetscCall(KSPGetPC(ksp, &pc));
1741 &ctx.preconditioning_engine, pc));
1743 &ctx.preconditioning_engine, pc));
1744 PetscCall(PCGetType(pc, &pc_type));
1745 PetscCall(PetscStrcmp(pc_type, PCNONE, &pc_is_none));
1746 PetscCall(PicurvAssertBool(pc_is_none,
1747 "baseline engine must derive PETSc PCNONE"));
1748 PetscCall(SNESGetJacobian(snes, &jacobian_operator, &preconditioning_matrix, NULL, NULL));
1749 PetscCall(PicurvAssertBool(
1750 (PetscBool)(jacobian_operator == ctx.jacobian.jacobian_operator),
1751 "SNES must receive the Jacobian-interface MFFD operator"));
1752 PetscCall(PicurvAssertBool((PetscBool)(preconditioning_matrix == jacobian_operator),
1753 "baseline preconditioning matrix must alias the Jacobian"));
1755 "baseline engine must record matrix aliasing"));
1757 "baseline engine must not own the Jacobian alias"));
1759 PetscCall(MatGetSize(ctx.jacobian.jacobian_operator, &rows, &cols));
1760 PetscCall(PicurvAssertBool((PetscBool)(rows > 0 && cols > 0),
1761 "destroying an alias engine must preserve the Jacobian"));
1763 PetscCall(SNESDestroy(&snes));
1764 PetscCall(VecDestroy(&user->Rhs));
1765 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
1766 PetscFunctionReturn(PETSC_SUCCESS);
1767}
1768
1769/** @brief Exercises engine-owned periodic duplicate rows on every MPI layout. */
1770static PetscErrorCode TestPointBlockPeriodicAssembly(void)
1771{
1772 SimCtx *simCtx = NULL;
1773 UserCtx *user = NULL;
1774 char tmpdir[PETSC_MAX_PATH_LEN] = "";
1776 MomentumPreconditionerDescription description = {
1779 0,
1780 0
1781 };
1782 Vec x = NULL, f = NULL;
1783 PetscReal matrix_norm = 0.0;
1784
1785 PetscFunctionBeginUser;
1786 PetscCall(BuildNewtonFixture(periodic_xyz_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
1787 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
1788 PetscCall(VecDuplicate(user->Ucont, &x));
1789 PetscCall(VecDuplicate(user->Ucont, &f));
1790 PetscCall(VecCopy(user->Ucont, x));
1791 ctx.user = user;
1792 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f, &ctx));
1794 user, NULL, &description, &ctx.preconditioning_engine));
1797 user, ctx.preconditioning_engine.preconditioning_matrix, PETSC_TRUE));
1798 PetscCall(MatNorm(ctx.preconditioning_engine.preconditioning_matrix,
1799 NORM_FROBENIUS, &matrix_norm));
1800 PetscCall(PicurvAssertBool((PetscBool)(matrix_norm > 0.0),
1801 "periodic point-block assembly must produce a nonzero matrix"));
1802 PetscCall(AssertPeriodicPreconditionerRow(user,
1804 (MatStencil){.i = 0, .j = 2, .k = 3, .c = 0},
1805 "single-axis periodic row must contain exact +1/-1 entries"));
1806 PetscCall(AssertPeriodicPreconditionerRow(user,
1808 (MatStencil){.i = 0, .j = 0, .k = 3, .c = 1},
1809 "periodic intersection must contain exact +1/-1 entries"));
1810 PetscCall(AssertPeriodicPreconditionerRow(user,
1812 (MatStencil){.i = 0, .j = 0, .k = 0, .c = 2},
1813 "periodic origin intersection must contain exact +1/-1 entries"));
1815 PetscCall(VecDestroy(&f));
1816 PetscCall(VecDestroy(&x));
1817 PetscCall(VecDestroy(&user->Rhs));
1818 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
1819 PetscFunctionReturn(PETSC_SUCCESS);
1820}
1821
1822/**
1823 * @brief Runs the focused Newton--Krylov unit suite.
1824 * @param argc Command-line argument count.
1825 * @param argv Command-line argument vector.
1826 * @return Process exit status.
1827 */
1828int main(int argc, char **argv)
1829{
1830 PetscErrorCode ierr;
1831 const PicurvTestCase cases[] = {
1832 {"residual-repeatability-and-input-integrity", TestResidualRepeatabilityAndInputIntegrity},
1833 {"constraint-rows", TestConstraintRows},
1834 {"fixed-constraint-derivatives-all-faces", TestFixedConstraintDerivativesAllFaces},
1835 {"inlet-outlet-constraint-derivatives", TestInletOutletConstraintDerivatives},
1836 {"periodic-constraint-derivatives-and-intersections", TestPeriodicConstraintDerivativesAndIntersections},
1837 {"matrix-free-derivative", TestMatrixFreeDerivative},
1838 {"whole-operator-direct-jacobian", TestWholeOperatorDirectJacobian},
1839 {"periodic-operator-has-no-zero-rows", TestPeriodicOperatorHasNoZeroRows},
1840 {"zero-iteration-structured-logging", TestZeroIterationStructuredLogging},
1841 {"flat-channel-bdf1-startup", TestFlatChannelStartup},
1842 {"restart-and-continuation-solve", TestRestartAndContinuationSolve},
1843 {"small-solve-and-rollback", TestSmallSolveAndRollback},
1844 {"unsupported-configuration-fails-before-allocation", TestUnsupportedConfigurationFailsBeforeAllocation},
1845 {"post-allocation-failure-cleanup", TestPostAllocationFailureCleanup},
1846 {"linearization-config-parsing", TestLinearizationConfigParsing},
1847 {"point-block-preconditioner-engine", TestPointBlockPreconditionerEngine},
1848 {"jacobian-interface-and-baseline-alias", TestJacobianInterfaceAndBaselineAlias},
1849 {"point-block-periodic-assembly", TestPointBlockPeriodicAssembly},
1850 };
1851
1852 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv Newton Krylov tests");
1853 if (ierr) return (int)ierr;
1854 ierr = PicurvRunTests("unit-newton-krylov", cases, sizeof(cases) / sizeof(cases[0]));
1855 if (PetscFinalize()) return 1;
1856 return (int)ierr;
1857}
MomentumRowType
Classification of one staggered momentum row (location + component).
Definition Boundaries.h:252
@ MOM_ROW_FIXED_HOMOGENEOUS
Dummy/tangential row carrying no unknown at all.
Definition Boundaries.h:255
@ MOM_ROW_PHYSICAL
Independent unknown governed by the momentum equation.
Definition Boundaries.h:253
@ MOM_ROW_PERIODIC_DUPLICATE
Duplicate of a wrapped representative row (see ri, rj, rk).
Definition Boundaries.h:256
@ MOM_ROW_FIXED_CONDITIONED
Strong Dirichlet row; the value comes from ApplyBoundaryConditions().
Definition Boundaries.h:254
MomentumRowType ClassifyMomentumRow(UserCtx *user, PetscInt i, PetscInt j, PetscInt k, PetscInt component, PetscInt *ri, PetscInt *rj, PetscInt *rk)
Single source of truth for "which staggered momentum rows are unknowns".
Definition Boundaries.c:597
PetscErrorCode BoundaryCondition_Create(BCHandlerType handler_type, BoundaryCondition **new_bc_ptr)
(Private) Creates and configures a specific BoundaryCondition handler object.
Definition Boundaries.c:729
PetscErrorCode SynchronizePeriodicStaggeredFields(UserCtx *user, PetscInt num_fields, const FieldId field_ids[])
Synchronizes persistent component-staggered vector fields.
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.
FieldId
Compile-time identity for a catalogued Eulerian field.
@ FIELD_ID_UCONT
PetscErrorCode InitializeEulerianState(SimCtx *simCtx)
High-level orchestrator to set the complete initial state of the Eulerian solver.
static PetscErrorCode MomentumNewtonJacobian_Register(SNES snes, MomentumNewtonJacobian *jacobian, MomentumPreconditionerEngine *engine, MomentumNewtonKrylovContext *ctx)
Registers the application orchestration callback and both SNES matrices.
static const MomentumPreconditionerModelOps frozen_momentum_point_block_ops
const MomentumPreconditionerModelOps * model_ops
static PetscErrorCode MomentumPreconditionerEngine_ConfigurePetscPC(MomentumPreconditionerEngine *engine, PC pc)
Applies the validated model/structure-to-PETSc-PC mapping.
static PetscErrorCode MomentumPreconditionerEngine_ValidatePetscPC(MomentumPreconditionerEngine *engine, PC pc)
Rejects raw options that select an unvalidated PETSc PC backend.
@ MOM_NK_PC_STRUCTURE_POINT_BLOCK
@ MOM_NK_PC_STRUCTURE_NONE
static PetscErrorCode MomentumPreconditionerEngine_Create(UserCtx *user, Mat jacobian_operator, const MomentumPreconditionerDescription *requested, MomentumPreconditionerEngine *engine)
Validates a model/structure and creates or aliases its matrix.
MomentumNewtonFiniteDifferenceMode finite_difference_mode
MomentumNewtonJacobianType type
static PetscErrorCode MomentumNewtonKrylov_ReadLinearizationConfig(MomentumNewtonJacobian *jacobian, MomentumPreconditionerDescription *description)
Reads application-owned Jacobian and preconditioner mathematics.
static PetscErrorCode MomentumPreconditionerEngine_Assemble(MomentumPreconditionerEngine *engine, UserCtx *user, Vec current_solution)
Runs model insertion, common row handling, and final assembly.
static PetscErrorCode MomentumNewtonJacobian_Destroy(MomentumNewtonJacobian *jacobian)
Destroys a partially or fully created Jacobian operator.
static PetscErrorCode MomentumNewtonJacobian_Create(SNES snes, MomentumNewtonJacobian *jacobian)
Creates the selected Jacobian operator; currently PETSc MFFD only.
MomentumPreconditionerStructure structure
@ MOM_NK_JACOBIAN_FINITE_DIFFERENCE
static PetscErrorCode MomentumPreconditionerEngine_Destroy(MomentumPreconditionerEngine *engine)
Destroys only a separately owned preconditioning matrix.
MomentumPreconditionerEngine preconditioning_engine
static PetscErrorCode MomentumNewtonKrylov_FormResidual(SNES snes, Vec X, Vec F, void *ctx)
Adapts a PETSc trial vector to the existing momentum residual path.
@ MOM_NK_FD_MODE_MATRIX_FREE
@ MOM_NK_PC_MODEL_FROZEN_MOMENTUM_JACOBIAN
@ MOM_NK_PC_MODEL_NONE
static PetscErrorCode PreconditionerMatrixStencilEntry(UserCtx *user, Mat preconditioning_matrix, MatStencil row, MatStencil col, PetscScalar *value)
Reads one DMDA-stencil matrix entry through collective basis vectors.
static const char * fixed_wall_bcs
static PetscErrorCode TestRestartAndContinuationSolve(void)
Verifies restarted Newton solves with both supported preconditioners.
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 PetscErrorCode TestPointBlockPreconditionerEngine(void)
Verifies the point-block model and common preconditioning-engine wiring.
static PetscErrorCode TestFlatChannelStartup(void)
Guards flat_channel's initial BDF1 residual and both shipped NK PCs.
static const char * periodic_z_bcs
static PetscErrorCode MeasureStoredDerivative(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 *derivative)
Finite-differences one callback row with respect to one stored unknown.
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 PetscErrorCode AssertPeriodicPreconditionerRow(UserCtx *user, Mat matrix, MatStencil row, const char *message)
Proves that one periodic matrix row has only its exact +1/-1 pair.
static PetscErrorCode CheckFlatChannelStartup(PetscBool use_point_block)
Exercises the straight-duct BDF1 startup path used by flat_channel.
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 PetscErrorCode TestStencilToGlobal(UserCtx *user, MatStencil stencil, PetscInt *global_index)
Converts an in-domain or periodic-ghost DMDA stencil to PETSc ordering.
static PetscErrorCode AssertExactPointBlockMatrixAllocation(UserCtx *user, Mat matrix, PetscBool require_offrank_periodic)
Verifies the exact AIJ layout and preallocation derived from row classes.
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 TestLinearizationConfigParsing(void)
Verifies finalized application-owned linearization option parsing.
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)
Asserts one finite-differenced callback row derivative equals an expected value.
static PetscErrorCode TestJacobianInterfaceAndBaselineAlias(void)
Verifies Jacobian creation/registration and baseline alias ownership.
static PetscErrorCode AssertNewtonLog(const char *path, PetscInt expected_rows, const char *needle_a, const char *needle_b)
Checks a structured log's row count and required text after a collective solve.
static PetscErrorCode TestPointBlockPeriodicAssembly(void)
Exercises engine-owned periodic duplicate rows on every MPI layout.
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 TestZeroIterationStructuredLogging(void)
Verifies the six-wall zero-velocity case logs zero Newton/Krylov work.
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.
PetscBool mom_nk_monitor_history
Definition variables.h:919
PetscReal FarFluxInSum
Definition variables.h:965
PetscInt movefsi
Definition variables.h:896
@ PERIODIC
Definition variables.h:323
@ WALL
Definition variables.h:317
PetscBool continueMode
Definition variables.h:881
PetscInt moveframe
Definition variables.h:897
PetscInt TwoD
Definition variables.h:897
PetscReal FarFluxOutSum
Definition variables.h:965
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:1096
PetscInt block_number
Definition variables.h:958
Vec lNvert
Definition variables.h:1111
PetscReal FluxOutSum
Definition variables.h:965
PetscBool mom_last_converged
Definition variables.h:917
@ BC_HANDLER_PERIODIC_GEOMETRIC
Definition variables.h:347
@ BC_HANDLER_PERIODIC_DRIVEN_CONSTANT_FLUX
Definition variables.h:349
@ BC_HANDLER_WALL_NOSLIP
Definition variables.h:336
BCHandlerType handler_type
Definition variables.h:400
Vec Ucont
Definition variables.h:1111
PetscInt StartStep
Definition variables.h:874
PetscInt rotatefsi
Refused at setup: immersed boundaries and moving bodies are not implemented.
Definition variables.h:896
@ MOMENTUM_SOLVER_NEWTON_KRYLOV
Definition variables.h:702
PetscScalar x
Definition variables.h:122
char log_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:887
PetscReal FluxInSum
Definition variables.h:965
PetscScalar z
Definition variables.h:122
Vec Ucont_o
Definition variables.h:1118
Vec Ucont_rm1
Definition variables.h:1119
PetscInt step
Definition variables.h:872
DMDALocalInfo info
Definition variables.h:1083
Vec lUcat
Definition variables.h:1111
PetscScalar y
Definition variables.h:122
Vec Nvert
Definition variables.h:1111
BCType mathematical_type
Definition variables.h:399
PetscInt rotateframe
moveframe/rotateframe are refused at setup.
Definition variables.h:897
PetscInt immersed
Definition variables.h:896
BCFace
Identifies the six logical faces of a structured computational block.
Definition variables.h:292
@ BC_FACE_NEG_X
Definition variables.h:293
@ BC_FACE_POS_X
Definition variables.h:293
A 3D point or vector with PetscScalar components.
Definition variables.h:121
The master context for the entire simulation.
Definition variables.h:864
User-defined context containing data specific to a single computational grid level.
Definition variables.h:1071