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;
463
464 coord[axis] = 0;
465 row = MomentumNewtonKrylov_ClassifyRow(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 = MomentumNewtonKrylov_ClassifyRow(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 = MomentumNewtonKrylov_ClassifyRow(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 = MomentumNewtonKrylov_ClassifyRow(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 = MomentumNewtonKrylov_ClassifyRow(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
926 PetscFunctionBeginUser;
927 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
928 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_rtol", "1e-4"));
929 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_max_it", "20"));
930 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_ksp_rtol", "1e-6"));
931 simCtx->mom_nk_monitor_history = PETSC_TRUE;
932 PetscCall(MomentumSolver_NewtonKrylov(user, NULL, NULL));
933 PetscCall(PicurvAssertBool(simCtx->mom_last_converged, "small Newton solve must converge"));
934 PetscCall(PicurvAssertBool((PetscBool)(user->Rhs == NULL), "successful solve must release Rhs"));
935 PetscCall(PetscSNPrintf(summary_path, sizeof(summary_path),
936 "%s/Momentum_Solver_Newton_Krylov_Summary_Block_0.log", simCtx->log_dir));
937 PetscCall(PetscSNPrintf(history_path, sizeof(history_path),
938 "%s/Momentum_Solver_Newton_Krylov_History_Block_0.log", simCtx->log_dir));
939 PetscCall(AssertNewtonLog(summary_path, 1, "solver: Newton Krylov", "state: committed"));
940 PetscCall(AssertNewtonLog(history_path, -2, "newton: 0", "nonlinear_norm:"));
941
942 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
943 tmpdir[0] = '\0';
944 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
945 PetscCall(VecSet(user->Ucont, 0.2));
946 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fields));
947 PetscCall(ApplyBoundaryConditions(user));
948 PetscCall(VecDuplicate(user->Ucont, &entry));
949 PetscCall(VecDuplicate(user->Ucont, &delta));
950 PetscCall(VecCopy(user->Ucont, entry));
951 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_max_it", "0"));
952 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
953 solve_ierr = MomentumSolver_NewtonKrylov(user, NULL, NULL);
954 PetscCall(PetscPopErrorHandler());
955 PetscCall(PicurvAssertIntEqual(PETSC_ERR_CONV_FAILED, solve_ierr,
956 "forced nonconvergence must report PETSC_ERR_CONV_FAILED"));
957 PetscCall(VecWAXPY(delta, -1.0, entry, user->Ucont));
958 PetscCall(VecNorm(delta, NORM_INFINITY, &norm));
959 PetscCall(PicurvAssertRealNear(0.0, norm, 1.0e-12,
960 "failed Newton solve must restore the canonical entry state"));
961 PetscCall(PicurvAssertBool((PetscBool)(user->Rhs == NULL), "failed solve must release Rhs"));
962 PetscCall(PetscSNPrintf(summary_path, sizeof(summary_path),
963 "%s/Momentum_Solver_Newton_Krylov_Summary_Block_0.log", simCtx->log_dir));
964 PetscCall(AssertNewtonLog(summary_path, 1, "reason_code: -", "state: rolled_back"));
965
966 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_snes_rtol"));
967 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_snes_max_it"));
968 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_ksp_rtol"));
969 PetscCall(VecDestroy(&delta));
970 PetscCall(VecDestroy(&entry));
971 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
972 PetscFunctionReturn(PETSC_SUCCESS);
973}
974
975/** @brief Verifies the six-wall zero-velocity case logs zero Newton/Krylov work. */
976static PetscErrorCode TestZeroIterationStructuredLogging(void)
977{
978 SimCtx *simCtx = NULL;
979 UserCtx *user = NULL;
980 char tmpdir[PETSC_MAX_PATH_LEN] = "";
981 char summary_path[PETSC_MAX_PATH_LEN];
982 const FieldId fields[] = {FIELD_ID_UCONT};
983
984 PetscFunctionBeginUser;
985 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
986 PetscCall(VecZeroEntries(user->Ucont));
987 PetscCall(VecZeroEntries(user->Ucont_o));
988 PetscCall(VecZeroEntries(user->Ucont_rm1));
989 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fields));
990 PetscCall(ApplyBoundaryConditions(user));
991 PetscCall(MomentumSolver_NewtonKrylov(user, NULL, NULL));
992 PetscCall(PetscSNPrintf(summary_path, sizeof(summary_path),
993 "%s/Momentum_Solver_Newton_Krylov_Summary_Block_0.log", simCtx->log_dir));
994 PetscCall(AssertNewtonLog(summary_path, 1, "newton: 0 | evals: 1 | krylov: 0",
995 "final: 0.0000000000000000e+00 | state: committed"));
996 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
997 PetscFunctionReturn(PETSC_SUCCESS);
998}
999
1000/**
1001 * @brief Exercises the straight-duct BDF1 startup path used by flat_channel.
1002 *
1003 * The conservation outlet consumes lUcat during its first boundary pass. This
1004 * test deliberately evaluates the callback at the initialized state before
1005 * installing SNES, then completes the first nonlinear solve with each shipped
1006 * preconditioner. It catches a missing Ucont -> Ucat -> lUcat seed as a
1007 * non-finite initial residual rather than hiding it behind later MFFD work.
1008 */
1009static PetscErrorCode CheckFlatChannelStartup(PetscBool use_point_block)
1010{
1011 SimCtx *simCtx = NULL;
1012 UserCtx *user = NULL;
1013 char tmpdir[PETSC_MAX_PATH_LEN] = "";
1014 Vec x = NULL, f = NULL;
1015 PetscReal initial_norm = 0.0;
1017
1018 PetscFunctionBeginUser;
1019 PetscCall(BuildNewtonFixture(NULL, &simCtx, &user, tmpdir, sizeof(tmpdir)));
1020 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
1021 PetscCall(VecDuplicate(user->Ucont, &x));
1022 PetscCall(VecDuplicate(user->Ucont, &f));
1023 PetscCall(VecCopy(user->Ucont, x));
1024 ctx.user = user;
1025 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f, &ctx));
1026 PetscCall(VecNorm(f, NORM_2, &initial_norm));
1027 PetscCheck(!PetscIsInfOrNanReal(initial_norm), PETSC_COMM_WORLD, PETSC_ERR_FP,
1028 "flat-channel BDF1 initial residual is non-finite (%g) with %s.",
1029 (double)initial_norm,
1030 use_point_block ? "frozen-momentum point-block" : "PCNONE");
1031 PetscCall(VecDestroy(&f));
1032 PetscCall(VecDestroy(&x));
1033 PetscCall(VecDestroy(&user->Rhs));
1034
1035 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_rtol", "1e-4"));
1036 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_max_it", "20"));
1037 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_ksp_rtol", "1e-6"));
1038 if (use_point_block) {
1039 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_model",
1040 "frozen_momentum_jacobian"));
1041 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_structure",
1042 "point_block"));
1043 }
1044 PetscCall(MomentumSolver_NewtonKrylov(user, NULL, NULL));
1045 PetscCall(PicurvAssertBool(simCtx->mom_last_converged,
1046 "flat-channel BDF1 Newton solve must converge"));
1047 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_preconditioner_model"));
1048 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_preconditioner_structure"));
1049 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_snes_rtol"));
1050 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_snes_max_it"));
1051 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_ksp_rtol"));
1052 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
1053 PetscFunctionReturn(PETSC_SUCCESS);
1054}
1055
1056/** @brief Guards flat_channel's initial BDF1 residual and both shipped NK PCs. */
1057static PetscErrorCode TestFlatChannelStartup(void)
1058{
1059 PetscFunctionBeginUser;
1060 PetscCall(CheckFlatChannelStartup(PETSC_FALSE));
1061 PetscCall(CheckFlatChannelStartup(PETSC_TRUE));
1062 PetscFunctionReturn(PETSC_SUCCESS);
1063}
1064
1065/** @brief Verifies restarted Newton solves with both supported preconditioners. */
1066static PetscErrorCode TestRestartAndContinuationSolve(void)
1067{
1068 SimCtx *simCtx = NULL;
1069 UserCtx *user = NULL;
1070 char tmpdir[PETSC_MAX_PATH_LEN] = "";
1071 const FieldId fields[] = {FIELD_ID_UCONT};
1072
1073 PetscFunctionBeginUser;
1074 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
1075 PetscCall(VecSet(user->Ucont, 0.2));
1076 PetscCall(VecZeroEntries(user->Ucont_o));
1077 PetscCall(VecZeroEntries(user->Ucont_rm1));
1078 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fields));
1079 PetscCall(ApplyBoundaryConditions(user));
1080 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_rtol", "1e-4"));
1081 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_max_it", "20"));
1082 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_ksp_rtol", "1e-6"));
1083
1084 /* AdvanceSimulation() solves StartStep+1 first. Use a nonzero checkpoint
1085 * state so that SNES takes a Newton/Krylov path rather than accepting a
1086 * trivial residual. */
1087 simCtx->StartStep = 7;
1088 simCtx->step = simCtx->StartStep + 1;
1089 PetscCall(MomentumSolver_NewtonKrylov(user, NULL, NULL));
1090 PetscCall(PicurvAssertBool(simCtx->mom_last_converged,
1091 "PCNONE Newton Krylov checkpoint restart must converge"));
1092
1093 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
1094 tmpdir[0] = '\0';
1095 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
1096 PetscCall(VecSet(user->Ucont, 0.2));
1097 PetscCall(VecZeroEntries(user->Ucont_o));
1098 PetscCall(VecZeroEntries(user->Ucont_rm1));
1099 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fields));
1100 PetscCall(ApplyBoundaryConditions(user));
1101 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_model",
1102 "frozen_momentum_jacobian"));
1103 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_structure", "point_block"));
1104
1105 simCtx->continueMode = PETSC_TRUE;
1106 simCtx->StartStep = 8;
1107 simCtx->step = simCtx->StartStep + 1;
1108 PetscCall(MomentumSolver_NewtonKrylov(user, NULL, NULL));
1109 PetscCall(PicurvAssertBool(simCtx->mom_last_converged,
1110 "frozen-momentum Newton Krylov --continue solve must converge"));
1111 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_preconditioner_model"));
1112 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_preconditioner_structure"));
1113 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_snes_rtol"));
1114 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_snes_max_it"));
1115 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_ksp_rtol"));
1116 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
1117 PetscFunctionReturn(PETSC_SUCCESS);
1118}
1119
1120/** @brief Confirms unsupported features fail before workspace allocation. */
1122{
1123 SimCtx *simCtx = NULL;
1124 UserCtx *user = NULL;
1125 char tmpdir[PETSC_MAX_PATH_LEN] = "";
1126 PetscErrorCode solve_ierr;
1127
1128 PetscFunctionBeginUser;
1129 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
1130 {
1131 PetscInt *unsupported_flags[] = {
1132 &simCtx->immersed, &simCtx->movefsi, &simCtx->rotatefsi,
1133 &simCtx->moveframe, &simCtx->rotateframe, &simCtx->rans,
1134 &simCtx->clark, &simCtx->TwoD, &simCtx->wallfunction
1135 };
1136 for (size_t flag = 0; flag < sizeof(unsupported_flags) / sizeof(unsupported_flags[0]); ++flag) {
1137 *unsupported_flags[flag] = 1;
1138 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1139 solve_ierr = MomentumSolver_NewtonKrylov(user, NULL, NULL);
1140 PetscCall(PetscPopErrorHandler());
1141 PetscCall(PicurvAssertBool((PetscBool)(solve_ierr != PETSC_SUCCESS),
1142 "unsupported Newton feature flag must fail"));
1143 PetscCall(PicurvAssertBool((PetscBool)(user->Rhs == NULL),
1144 "feature validation must precede workspace allocation"));
1145 *unsupported_flags[flag] = 0;
1146 }
1147 }
1148 simCtx->block_number = 2;
1149 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1150 solve_ierr = MomentumSolver_NewtonKrylov(user, NULL, NULL);
1151 PetscCall(PetscPopErrorHandler());
1152 PetscCall(PicurvAssertBool((PetscBool)(solve_ierr != PETSC_SUCCESS), "multiblock must fail"));
1153 simCtx->block_number = 1;
1154 PetscCall(VecSet(user->Nvert, 1.0));
1155 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1156 solve_ierr = MomentumSolver_NewtonKrylov(user, NULL, NULL);
1157 PetscCall(PetscPopErrorHandler());
1158 PetscCall(PicurvAssertBool((PetscBool)(solve_ierr != PETSC_SUCCESS), "masked rows must fail"));
1159 PetscCall(VecSet(user->Nvert, 0.0));
1162 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1163 solve_ierr = MomentumSolver_NewtonKrylov(user, NULL, NULL);
1164 PetscCall(PetscPopErrorHandler());
1165 PetscCall(PicurvAssertBool((PetscBool)(solve_ierr != PETSC_SUCCESS),
1166 "driven constant-flux controller must fail"));
1169 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1170 solve_ierr = MomentumSolver_NewtonKrylov(user, NULL, NULL);
1171 PetscCall(PetscPopErrorHandler());
1172 PetscCall(PicurvAssertBool((PetscBool)(solve_ierr != PETSC_SUCCESS),
1173 "unimplemented interpolated-file inlet must fail"));
1174 PetscCall(PicurvAssertBool((PetscBool)(user->Rhs == NULL),
1175 "all validation failures must precede workspace allocation"));
1176 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
1177 PetscFunctionReturn(PETSC_SUCCESS);
1178}
1179
1180/** @brief Verifies cleanup and rollback after an options failure following asset creation. */
1181static PetscErrorCode TestPostAllocationFailureCleanup(void)
1182{
1183 SimCtx *simCtx = NULL;
1184 UserCtx *user = NULL;
1185 char tmpdir[PETSC_MAX_PATH_LEN] = "";
1186 Vec entry = NULL, delta = NULL;
1187 PetscErrorCode solve_ierr;
1188 PetscReal norm = 0.0;
1189 const FieldId fields[] = {FIELD_ID_UCONT};
1190
1191 PetscFunctionBeginUser;
1192 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
1193 PetscCall(VecSet(user->Ucont, 0.2));
1194 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fields));
1195 PetscCall(ApplyBoundaryConditions(user));
1196 PetscCall(VecDuplicate(user->Ucont, &entry)); PetscCall(VecCopy(user->Ucont, entry));
1197 PetscCall(VecDuplicate(user->Ucont, &delta));
1198 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_pc_type", "jacobi"));
1199 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1200 solve_ierr = MomentumSolver_NewtonKrylov(user, NULL, NULL);
1201 PetscCall(PetscPopErrorHandler());
1202 PetscCall(PetscOptionsClearValue(NULL, "-mom_nk_pc_type"));
1203 PetscCall(PicurvAssertIntEqual(PETSC_ERR_SUP, solve_ierr,
1204 "non-PCNONE option must fail after setup"));
1205 PetscCall(PicurvAssertBool((PetscBool)(user->Rhs == NULL),
1206 "post-allocation failure must destroy Rhs"));
1207 PetscCall(VecWAXPY(delta, -1.0, entry, user->Ucont));
1208 PetscCall(VecNorm(delta, NORM_INFINITY, &norm));
1209 PetscCall(PicurvAssertRealNear(0.0, norm, 1.0e-12,
1210 "post-allocation failure must restore canonical entry"));
1211 PetscCall(VecDestroy(&delta)); PetscCall(VecDestroy(&entry));
1212 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
1213 PetscFunctionReturn(PETSC_SUCCESS);
1214}
1215
1216/** @brief Verifies finalized application-owned linearization option parsing. */
1217static PetscErrorCode TestLinearizationConfigParsing(void)
1218{
1219 MomentumNewtonJacobian jacobian = {0};
1220 MomentumPreconditionerDescription description = {0};
1221 PetscErrorCode config_ierr;
1222 const char *option_names[] = {
1223 "-mom_nk_jacobian_type",
1224 "-mom_nk_jacobian_fd_mode",
1225 "-mom_nk_preconditioner_model",
1226 "-mom_nk_preconditioner_structure"
1227 };
1228
1229 PetscFunctionBeginUser;
1230 for (size_t n = 0; n < sizeof(option_names) / sizeof(option_names[0]); ++n)
1231 PetscCall(PetscOptionsClearValue(NULL, option_names[n]));
1232
1233 PetscCall(MomentumNewtonKrylov_ReadLinearizationConfig(&jacobian, &description));
1235 "default Jacobian type must be finite difference"));
1237 jacobian.finite_difference_mode,
1238 "default finite-difference mode must be matrix free"));
1239 PetscCall(PicurvAssertIntEqual(MOM_NK_PC_MODEL_NONE, description.model,
1240 "default preconditioner model must be none"));
1242 "default preconditioner structure must be none"));
1243
1244 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_jacobian_type", "finite_difference"));
1245 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_jacobian_fd_mode", "matrix_free"));
1246 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_model", "none"));
1247 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_structure", "none"));
1248 PetscCall(MomentumNewtonKrylov_ReadLinearizationConfig(&jacobian, &description));
1250 "explicit baseline Jacobian type must parse"));
1252 jacobian.finite_difference_mode,
1253 "explicit baseline finite-difference mode must parse"));
1254 PetscCall(PicurvAssertIntEqual(MOM_NK_PC_MODEL_NONE, description.model,
1255 "explicit baseline preconditioner model must parse"));
1257 "explicit baseline preconditioner structure must parse"));
1258
1259 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_model",
1260 "frozen_momentum_jacobian"));
1261 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_structure", "point_block"));
1262 PetscCall(MomentumNewtonKrylov_ReadLinearizationConfig(&jacobian, &description));
1264 "explicit Jacobian type must parse"));
1266 jacobian.finite_difference_mode,
1267 "explicit finite-difference mode must parse"));
1269 description.model, "frozen model must parse"));
1271 description.structure, "point-block structure must parse"));
1272
1273 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_jacobian_type", "analytic"));
1274 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1275 config_ierr = MomentumNewtonKrylov_ReadLinearizationConfig(&jacobian, &description);
1276 PetscCall(PetscPopErrorHandler());
1277 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_WRONG, config_ierr,
1278 "unsupported Jacobian type must fail"));
1279 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_jacobian_type", "finite_difference"));
1280
1281 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_jacobian_fd_mode", "colored_sparse"));
1282 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1283 config_ierr = MomentumNewtonKrylov_ReadLinearizationConfig(&jacobian, &description);
1284 PetscCall(PetscPopErrorHandler());
1285 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_WRONG, config_ierr,
1286 "unsupported finite-difference mode must fail"));
1287 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_jacobian_fd_mode", "matrix_free"));
1288
1289 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_structure", "none"));
1290 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1291 config_ierr = MomentumNewtonKrylov_ReadLinearizationConfig(&jacobian, &description);
1292 PetscCall(PetscPopErrorHandler());
1293 PetscCall(PicurvAssertIntEqual(PETSC_ERR_SUP, config_ierr,
1294 "frozen model without point block must fail"));
1295 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_model", "none"));
1296 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_structure", "point_block"));
1297 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1298 config_ierr = MomentumNewtonKrylov_ReadLinearizationConfig(&jacobian, &description);
1299 PetscCall(PetscPopErrorHandler());
1300 PetscCall(PicurvAssertIntEqual(PETSC_ERR_SUP, config_ierr,
1301 "none model with point block must fail"));
1302
1303 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_model", "diagonal"));
1304 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_structure", "none"));
1305 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1306 config_ierr = MomentumNewtonKrylov_ReadLinearizationConfig(&jacobian, &description);
1307 PetscCall(PetscPopErrorHandler());
1308 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_WRONG, config_ierr,
1309 "unsupported preconditioner model must fail"));
1310 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_model",
1311 "frozen_momentum_jacobian"));
1312 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_preconditioner_structure", "line"));
1313 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
1314 config_ierr = MomentumNewtonKrylov_ReadLinearizationConfig(&jacobian, &description);
1315 PetscCall(PetscPopErrorHandler());
1316 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_WRONG, config_ierr,
1317 "unsupported preconditioner structure must fail"));
1318
1319 for (size_t n = 0; n < sizeof(option_names) / sizeof(option_names[0]); ++n)
1320 PetscCall(PetscOptionsClearValue(NULL, option_names[n]));
1321 PetscFunctionReturn(PETSC_SUCCESS);
1322}
1323
1324enum {
1329 ORACLE_LEGACY_SIGN = 1 << 4
1331
1332/** @brief Test-owned metric norm used by the independent legacy transcription. */
1334{
1335 return metric.x * metric.x + metric.y * metric.y + metric.z * metric.z;
1336}
1337
1338/**
1339 * @brief Independent transcription of the audited legacy mode-2 point block.
1340 *
1341 * This intentionally shares no coefficient helper with production. Mutant flags
1342 * represent the historical failure modes that the nonuniform oracle must reject.
1343 */
1344static void LegacyPointBlockOracle(const SimCtx *simCtx, const Cmpnts ***u,
1345 const Cmpnts ***csi, const Cmpnts ***eta, const Cmpnts ***zet,
1346 const PetscReal ***aj, PetscInt i, PetscInt j, PetscInt k, PetscInt flags,
1347 PetscScalar block[9])
1348{
1349 PetscReal A[6][4] = {{0.0}};
1350 const PetscReal dtc = ((simCtx->step != simCtx->StartStep) && simCtx->step != 1 ? 1.5 : 1.0) /
1351 simCtx->dt;
1352 const PetscReal AJip = flags & ORACLE_CENTER_AJ ? aj[k][j][i] :
1353 0.5 * (aj[k][j][i] + aj[k][j][i + 1]);
1354 const PetscReal AJjp = flags & ORACLE_CENTER_AJ ? aj[k][j][i] :
1355 0.5 * (aj[k][j][i] + aj[k][j + 1][i]);
1356 const PetscReal AJkp = flags & ORACLE_CENTER_AJ ? aj[k][j][i] :
1357 0.5 * (aj[k][j][i] + aj[k + 1][j][i]);
1358 const PetscReal g11ip = LegacyOracleMetricNormSquared(csi[k][j][i]);
1359 const PetscReal g22ip = flags & ORACLE_CENTER_TRANSVERSE_METRICS ?
1360 LegacyOracleMetricNormSquared(eta[k][j][i]) : 0.25 * (
1361 LegacyOracleMetricNormSquared(eta[k][j][i]) + LegacyOracleMetricNormSquared(eta[k][j][i + 1]) +
1362 LegacyOracleMetricNormSquared(eta[k][j - 1][i]) + LegacyOracleMetricNormSquared(eta[k][j - 1][i + 1]));
1363 const PetscReal g33ip = flags & ORACLE_CENTER_TRANSVERSE_METRICS ?
1364 LegacyOracleMetricNormSquared(zet[k][j][i]) : 0.25 * (
1365 LegacyOracleMetricNormSquared(zet[k][j][i]) + LegacyOracleMetricNormSquared(zet[k][j][i + 1]) +
1366 LegacyOracleMetricNormSquared(zet[k - 1][j][i]) + LegacyOracleMetricNormSquared(zet[k - 1][j][i + 1]));
1367 const PetscReal g11jp = flags & ORACLE_CENTER_TRANSVERSE_METRICS ?
1368 LegacyOracleMetricNormSquared(csi[k][j][i]) : 0.25 * (
1369 LegacyOracleMetricNormSquared(csi[k][j][i]) + LegacyOracleMetricNormSquared(csi[k][j + 1][i]) +
1370 LegacyOracleMetricNormSquared(csi[k][j][i - 1]) + LegacyOracleMetricNormSquared(csi[k][j + 1][i - 1]));
1371 const PetscReal g22jp = LegacyOracleMetricNormSquared(eta[k][j][i]);
1372 const PetscReal g33jp = flags & ORACLE_CENTER_TRANSVERSE_METRICS ?
1373 LegacyOracleMetricNormSquared(zet[k][j][i]) : 0.25 * (
1374 LegacyOracleMetricNormSquared(zet[k][j][i]) + LegacyOracleMetricNormSquared(zet[k][j + 1][i]) +
1375 LegacyOracleMetricNormSquared(zet[k - 1][j][i]) + LegacyOracleMetricNormSquared(zet[k - 1][j + 1][i]));
1376 const PetscReal g11kp = flags & ORACLE_CENTER_TRANSVERSE_METRICS ?
1377 LegacyOracleMetricNormSquared(csi[k][j][i]) : 0.25 * (
1378 LegacyOracleMetricNormSquared(csi[k][j][i]) + LegacyOracleMetricNormSquared(csi[k + 1][j][i]) +
1379 LegacyOracleMetricNormSquared(csi[k][j][i - 1]) + LegacyOracleMetricNormSquared(csi[k + 1][j][i - 1]));
1380 const PetscReal g22kp = flags & ORACLE_CENTER_TRANSVERSE_METRICS ?
1381 LegacyOracleMetricNormSquared(eta[k][j][i]) : 0.25 * (
1382 LegacyOracleMetricNormSquared(eta[k][j][i]) + LegacyOracleMetricNormSquared(eta[k + 1][j][i]) +
1383 LegacyOracleMetricNormSquared(eta[k][j - 1][i]) + LegacyOracleMetricNormSquared(eta[k + 1][j - 1][i]));
1384 const PetscReal g33kp = LegacyOracleMetricNormSquared(zet[k][j][i]);
1385 const PetscReal U0jp = flags & ORACLE_CENTER_VELOCITY ? u[k][j][i].x : 0.25 *
1386 (u[k][j][i].x + u[k][j][i - 1].x + u[k][j + 1][i].x + u[k][j + 1][i - 1].x);
1387 const PetscReal U0kp = flags & ORACLE_CENTER_VELOCITY ? u[k][j][i].x : 0.25 *
1388 (u[k][j][i].x + u[k][j][i - 1].x + u[k + 1][j][i].x + u[k + 1][j][i - 1].x);
1389 const PetscReal U1ip = flags & ORACLE_CENTER_VELOCITY ? u[k][j][i].y : 0.25 *
1390 (u[k][j][i].y + u[k][j - 1][i].y + u[k][j][i + 1].y + u[k][j - 1][i + 1].y);
1391 const PetscReal U1kp = flags & ORACLE_CENTER_VELOCITY ? u[k][j][i].y : 0.25 *
1392 (u[k][j][i].y + u[k][j - 1][i].y + u[k + 1][j][i].y + u[k + 1][j - 1][i].y);
1393 const PetscReal U2ip = flags & ORACLE_CENTER_VELOCITY ? u[k][j][i].z : 0.25 *
1394 (u[k][j][i].z + u[k - 1][j][i].z + u[k][j][i + 1].z + u[k - 1][j][i + 1].z);
1395 const PetscReal U2jp = flags & ORACLE_CENTER_VELOCITY ? u[k][j][i].z : 0.25 *
1396 (u[k][j][i].z + u[k - 1][j][i].z + u[k][j + 1][i].z + u[k - 1][j + 1][i].z);
1397 PetscReal Su, Sv, Sw, sign = flags & ORACLE_LEGACY_SIGN ? -1.0 : 1.0;
1398
1399 A[0][0] = .125 * aj[k][j][i] * u[k][j][i].y;
1400 A[0][1] = -.125 * aj[k][j - 1][i] * u[k][j - 1][i].y;
1401 A[0][2] = .125 * aj[k][j][i + 1] * u[k][j][i + 1].y;
1402 A[0][3] = -.125 * aj[k][j - 1][i + 1] * u[k][j - 1][i + 1].y;
1403 A[1][0] = .125 * aj[k][j][i] * u[k][j][i].z;
1404 A[1][1] = -.125 * aj[k - 1][j][i] * u[k - 1][j][i].z;
1405 A[1][2] = .125 * aj[k][j][i + 1] * u[k][j][i + 1].z;
1406 A[1][3] = -.125 * aj[k - 1][j][i + 1] * u[k - 1][j][i + 1].z;
1407 A[2][0] = -.125 * aj[k][j + 1][i - 1] * u[k][j + 1][i - 1].x;
1408 A[2][1] = -.125 * aj[k][j][i - 1] * u[k][j][i - 1].x;
1409 A[2][2] = .125 * aj[k][j + 1][i] * u[k][j + 1][i].x;
1410 A[2][3] = .125 * aj[k][j][i] * u[k][j][i].x;
1411 A[3][0] = .125 * aj[k][j][i] * u[k][j][i].z;
1412 A[3][1] = -.125 * aj[k - 1][j][i] * u[k - 1][j][i].z;
1413 A[3][2] = .125 * aj[k][j + 1][i] * u[k][j + 1][i].z;
1414 A[3][3] = -.125 * aj[k - 1][j + 1][i] * u[k - 1][j + 1][i].z;
1415 A[4][0] = -.125 * aj[k + 1][j][i - 1] * u[k + 1][j][i - 1].x;
1416 A[4][1] = -.125 * aj[k][j][i - 1] * u[k][j][i - 1].x;
1417 A[4][2] = .125 * aj[k + 1][j][i] * u[k + 1][j][i].x;
1418 A[4][3] = .125 * aj[k][j][i] * u[k][j][i].x;
1419 A[5][0] = -.125 * aj[k + 1][j - 1][i] * u[k + 1][j - 1][i].y;
1420 A[5][1] = -.125 * aj[k][j - 1][i] * u[k][j - 1][i].y;
1421 A[5][2] = .125 * aj[k + 1][j][i] * u[k + 1][j][i].y;
1422 A[5][3] = .125 * aj[k][j][i] * u[k][j][i].y;
1423 Su = A[0][0] + A[0][1] + A[0][2] + A[0][3] + A[1][0] + A[1][1] + A[1][2] + A[1][3];
1424 Sv = A[2][0] + A[2][1] + A[2][2] + A[2][3] + A[3][0] + A[3][1] + A[3][2] + A[3][3];
1425 Sw = A[4][0] + A[4][1] + A[4][2] + A[4][3];
1426 if (!(flags & ORACLE_OMIT_A5)) Sw += A[5][0] + A[5][1] + A[5][2] + A[5][3];
1427
1428 block[0] = sign * (dtc + AJip * AJip * (g11ip + g22ip + g33ip) / simCtx->ren + Su);
1429 block[1] = sign * 0.5 * AJip * U1ip; block[2] = sign * 0.5 * AJip * U2ip;
1430 block[3] = sign * 0.5 * AJjp * U0jp;
1431 block[4] = sign * (dtc + AJjp * AJjp * (g11jp + g22jp + g33jp) / simCtx->ren + Sv);
1432 block[5] = sign * 0.5 * AJjp * U2jp;
1433 block[6] = sign * 0.5 * AJkp * U0kp; block[7] = sign * 0.5 * AJkp * U1kp;
1434 block[8] = sign * (dtc + AJkp * AJkp * (g11kp + g22kp + g33kp) / simCtx->ren + Sw);
1435}
1436
1437/** @brief Seeds nonuniform, index-distinguishing coefficient fields. */
1438static PetscErrorCode SeedPointBlockOracleFields(UserCtx *user)
1439{
1440 Cmpnts ***u = NULL, ***csi = NULL, ***eta = NULL, ***zet = NULL;
1441 PetscReal ***aj = NULL;
1442 DMDALocalInfo info = user->info;
1443
1444 PetscFunctionBeginUser;
1445 PetscCall(DMDAVecGetArray(user->fda, user->Ucont, &u));
1446 PetscCall(DMDAVecGetArray(user->fda, user->Csi, &csi));
1447 PetscCall(DMDAVecGetArray(user->fda, user->Eta, &eta));
1448 PetscCall(DMDAVecGetArray(user->fda, user->Zet, &zet));
1449 PetscCall(DMDAVecGetArray(user->da, user->Aj, &aj));
1450 for (PetscInt k = info.zs; k < info.zs + info.zm; ++k)
1451 for (PetscInt j = info.ys; j < info.ys + info.ym; ++j)
1452 for (PetscInt i = info.xs; i < info.xs + info.xm; ++i) {
1453 aj[k][j][i] = 0.7 + .019 * i + .043 * j + .071 * k + .003 * i * j + .002 * j * k;
1454 u[k][j][i] = (Cmpnts){.x = .2 + .031 * i - .017 * j + .013 * k + .004 * i * k,
1455 .y = -.3 + .011 * i + .037 * j - .019 * k + .003 * j * k,
1456 .z = .4 - .023 * i + .007 * j + .041 * k + .002 * i * j};
1457 csi[k][j][i] = (Cmpnts){.x = 1.1 + .029 * i + .007 * j * k,
1458 .y = .13 + .017 * j + .003 * i * k,
1459 .z = -.09 + .011 * k + .002 * i * j};
1460 eta[k][j][i] = (Cmpnts){.x = -.12 + .013 * i + .004 * j * k,
1461 .y = .9 + .031 * j + .003 * i * k,
1462 .z = .16 + .019 * k + .002 * i * j};
1463 zet[k][j][i] = (Cmpnts){.x = .08 + .023 * i + .002 * j * k,
1464 .y = -.14 + .011 * j + .005 * i * k,
1465 .z = 1.2 + .037 * k + .003 * i * j};
1466 }
1467 PetscCall(DMDAVecRestoreArray(user->da, user->Aj, &aj));
1468 PetscCall(DMDAVecRestoreArray(user->fda, user->Zet, &zet));
1469 PetscCall(DMDAVecRestoreArray(user->fda, user->Eta, &eta));
1470 PetscCall(DMDAVecRestoreArray(user->fda, user->Csi, &csi));
1471 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucont, &u));
1472 PetscCall(DMGlobalToLocalBegin(user->fda, user->Ucont, INSERT_VALUES, user->lUcont));
1473 PetscCall(DMGlobalToLocalEnd(user->fda, user->Ucont, INSERT_VALUES, user->lUcont));
1474 PetscCall(DMGlobalToLocalBegin(user->fda, user->Csi, INSERT_VALUES, user->lCsi));
1475 PetscCall(DMGlobalToLocalEnd(user->fda, user->Csi, INSERT_VALUES, user->lCsi));
1476 PetscCall(DMGlobalToLocalBegin(user->fda, user->Eta, INSERT_VALUES, user->lEta));
1477 PetscCall(DMGlobalToLocalEnd(user->fda, user->Eta, INSERT_VALUES, user->lEta));
1478 PetscCall(DMGlobalToLocalBegin(user->fda, user->Zet, INSERT_VALUES, user->lZet));
1479 PetscCall(DMGlobalToLocalEnd(user->fda, user->Zet, INSERT_VALUES, user->lZet));
1480 PetscCall(DMGlobalToLocalBegin(user->da, user->Aj, INSERT_VALUES, user->lAj));
1481 PetscCall(DMGlobalToLocalEnd(user->da, user->Aj, INSERT_VALUES, user->lAj));
1482 PetscFunctionReturn(PETSC_SUCCESS);
1483}
1484
1485/** @brief Evaluates the independent oracle on the unique owner and broadcasts it. */
1486static PetscErrorCode CollectiveLegacyPointBlockOracle(UserCtx *user, PetscInt i, PetscInt j,
1487 PetscInt k, PetscInt flags, PetscScalar block[9])
1488{
1489 Cmpnts ***u = NULL, ***csi = NULL, ***eta = NULL, ***zet = NULL;
1490 PetscReal ***aj = NULL;
1491 PetscInt owns = i >= user->info.xs && i < user->info.xs + user->info.xm &&
1492 j >= user->info.ys && j < user->info.ys + user->info.ym &&
1493 k >= user->info.zs && k < user->info.zs + user->info.zm;
1494 PetscInt owners = 0;
1495 PetscScalar local[9] = {0.0};
1496
1497 PetscFunctionBeginUser;
1498 if (owns) {
1499 PetscCall(DMDAVecGetArrayRead(user->fda, user->lUcont, &u));
1500 PetscCall(DMDAVecGetArrayRead(user->fda, user->lCsi, &csi));
1501 PetscCall(DMDAVecGetArrayRead(user->fda, user->lEta, &eta));
1502 PetscCall(DMDAVecGetArrayRead(user->fda, user->lZet, &zet));
1503 PetscCall(DMDAVecGetArrayRead(user->da, user->lAj, &aj));
1504 LegacyPointBlockOracle(user->simCtx, (const Cmpnts ***)u, (const Cmpnts ***)csi,
1505 (const Cmpnts ***)eta, (const Cmpnts ***)zet, (const PetscReal ***)aj,
1506 i, j, k, flags, local);
1507 PetscCall(DMDAVecRestoreArrayRead(user->da, user->lAj, &aj));
1508 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lZet, &zet));
1509 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lEta, &eta));
1510 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lCsi, &csi));
1511 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lUcont, &u));
1512 }
1513 PetscCallMPI(MPI_Allreduce(&owns, &owners, 1, MPIU_INT, MPI_SUM, PETSC_COMM_WORLD));
1514 PetscCall(PicurvAssertIntEqual(1, owners, "oracle point must have exactly one owner"));
1515 PetscCallMPI(MPI_Allreduce(local, block, 9, MPIU_SCALAR, MPI_SUM, PETSC_COMM_WORLD));
1516 PetscFunctionReturn(PETSC_SUCCESS);
1517}
1518
1519/** @brief Converts an in-domain or periodic-ghost DMDA stencil to PETSc ordering. */
1520static PetscErrorCode TestStencilToGlobal(UserCtx *user, MatStencil stencil,
1521 PetscInt *global_index)
1522{
1523 ISLocalToGlobalMapping local_to_global = NULL;
1524 PetscInt ghost_starts[3], ghost_sizes[3], local_index;
1525
1526 PetscFunctionBeginUser;
1527 PetscCall(DMDAGetGhostCorners(user->fda,
1528 &ghost_starts[0], &ghost_starts[1], &ghost_starts[2],
1529 &ghost_sizes[0], &ghost_sizes[1], &ghost_sizes[2]));
1530 PetscCall(PicurvAssertBool((PetscBool)(
1531 stencil.i >= ghost_starts[0] && stencil.i < ghost_starts[0] + ghost_sizes[0] &&
1532 stencil.j >= ghost_starts[1] && stencil.j < ghost_starts[1] + ghost_sizes[1] &&
1533 stencil.k >= ghost_starts[2] && stencil.k < ghost_starts[2] + ghost_sizes[2]),
1534 "test stencil must lie in the local DMDA ghost region"));
1535 local_index = stencil.c + 3 * (
1536 (stencil.i - ghost_starts[0]) + ghost_sizes[0] * (
1537 (stencil.j - ghost_starts[1]) + ghost_sizes[1] *
1538 (stencil.k - ghost_starts[2])));
1539 PetscCall(DMGetLocalToGlobalMapping(user->fda, &local_to_global));
1540 PetscCall(ISLocalToGlobalMappingApply(local_to_global, 1, &local_index,
1541 global_index));
1542 PetscFunctionReturn(PETSC_SUCCESS);
1543}
1544
1545/** @brief Reads one DMDA-stencil matrix entry through collective basis vectors. */
1546static PetscErrorCode PreconditionerMatrixStencilEntry(UserCtx *user,
1547 Mat preconditioning_matrix, MatStencil row,
1548 MatStencil col, PetscScalar *value)
1549{
1550 Vec column_basis = NULL, row_basis = NULL, product = NULL;
1551 AO ao = NULL;
1552 PetscInt mx, my, row_index, col_index;
1553 PetscFunctionBeginUser;
1554 PetscCall(DMDAGetInfo(user->fda, NULL, &mx, &my, NULL, NULL, NULL, NULL,
1555 NULL, NULL, NULL, NULL, NULL, NULL));
1556 row_index = row.c + 3 * (row.i + mx * (row.j + my * row.k));
1557 col_index = col.c + 3 * (col.i + mx * (col.j + my * col.k));
1558 PetscCall(DMDAGetAO(user->fda, &ao));
1559 PetscCall(AOApplicationToPetsc(ao, 1, &row_index));
1560 PetscCall(AOApplicationToPetsc(ao, 1, &col_index));
1561 PetscCall(DMCreateGlobalVector(user->fda, &column_basis));
1562 PetscCall(DMCreateGlobalVector(user->fda, &row_basis));
1563 PetscCall(DMCreateGlobalVector(user->fda, &product));
1564 PetscCall(VecSet(column_basis, 0.0)); PetscCall(VecSet(row_basis, 0.0));
1565 PetscCall(VecSetValue(column_basis, col_index, 1.0, INSERT_VALUES));
1566 PetscCall(VecSetValue(row_basis, row_index, 1.0, INSERT_VALUES));
1567 PetscCall(VecAssemblyBegin(column_basis)); PetscCall(VecAssemblyEnd(column_basis));
1568 PetscCall(VecAssemblyBegin(row_basis)); PetscCall(VecAssemblyEnd(row_basis));
1569 PetscCall(MatMult(preconditioning_matrix, column_basis, product));
1570 PetscCall(VecDot(row_basis, product, value));
1571 PetscCall(VecDestroy(&product)); PetscCall(VecDestroy(&row_basis));
1572 PetscCall(VecDestroy(&column_basis));
1573 PetscFunctionReturn(PETSC_SUCCESS);
1574}
1575
1576/** @brief Verifies the exact AIJ layout and preallocation derived from row classes. */
1578 UserCtx *user, Mat matrix, PetscBool require_offrank_periodic)
1579{
1580 DMDALocalInfo info;
1581 MatInfo matrix_info;
1582 PetscMPIInt comm_size = 1;
1583 PetscInt matrix_rows, matrix_cols, local_rows, local_cols;
1584 PetscInt vector_size, vector_local_size, block_size;
1585 PetscInt ownership_start, ownership_end;
1586 PetscInt expected_local = 0, expected_global = 0;
1587 PetscInt offrank_periodic_local = 0, offrank_periodic_global = 0;
1588 PetscBool is_seq_aij = PETSC_FALSE, is_mpi_aij = PETSC_FALSE;
1589
1590 PetscFunctionBeginUser;
1591 PetscCall(DMDAGetLocalInfo(user->fda, &info));
1592 PetscCall(MatGetSize(matrix, &matrix_rows, &matrix_cols));
1593 PetscCall(MatGetLocalSize(matrix, &local_rows, &local_cols));
1594 PetscCall(VecGetSize(user->Ucont, &vector_size));
1595 PetscCall(VecGetLocalSize(user->Ucont, &vector_local_size));
1596 PetscCall(VecGetOwnershipRange(user->Ucont, &ownership_start, &ownership_end));
1597 PetscCall(MatGetBlockSize(matrix, &block_size));
1598 PetscCall(PicurvAssertIntEqual(vector_size, matrix_rows,
1599 "point-block global row dimension"));
1600 PetscCall(PicurvAssertIntEqual(vector_size, matrix_cols,
1601 "point-block global column dimension"));
1602 PetscCall(PicurvAssertIntEqual(vector_local_size, local_rows,
1603 "point-block local row dimension"));
1604 PetscCall(PicurvAssertIntEqual(vector_local_size, local_cols,
1605 "point-block local column dimension"));
1606 PetscCall(PicurvAssertIntEqual(3, block_size, "point-block logical block size"));
1607 PetscCallMPI(MPI_Comm_size(PetscObjectComm((PetscObject)matrix), &comm_size));
1608 PetscCall(PetscObjectTypeCompare((PetscObject)matrix, MATSEQAIJ, &is_seq_aij));
1609 PetscCall(PetscObjectTypeCompare((PetscObject)matrix, MATMPIAIJ, &is_mpi_aij));
1610 PetscCall(PicurvAssertBool(
1611 comm_size == 1 ? is_seq_aij : is_mpi_aij,
1612 "point-block matrix must use the expected AIJ implementation"));
1613
1614 for (PetscInt k = info.zs; k < info.zs + info.zm; ++k) {
1615 for (PetscInt j = info.ys; j < info.ys + info.ym; ++j) {
1616 for (PetscInt i = info.xs; i < info.xs + info.xm; ++i) {
1617 for (PetscInt component = 0; component < 3; ++component) {
1618 PetscInt ri, rj, rk;
1620 user, i, j, k, component, &ri, &rj, &rk);
1621 expected_local += type == MOM_NK_ROW_PHYSICAL ? 3 :
1622 type == MOM_NK_ROW_PERIODIC_DUPLICATE ? 2 : 1;
1623 if (type == MOM_NK_ROW_PERIODIC_DUPLICATE) {
1624 PetscInt representative;
1625 PetscCall(TestStencilToGlobal(user,
1626 (MatStencil){.i = ri, .j = rj, .k = rk, .c = component},
1627 &representative));
1628 if (representative < ownership_start || representative >= ownership_end)
1629 ++offrank_periodic_local;
1630 }
1631 }
1632 }
1633 }
1634 }
1635 PetscCallMPI(MPI_Allreduce(&expected_local, &expected_global, 1, MPIU_INT,
1636 MPI_SUM, PetscObjectComm((PetscObject)matrix)));
1637 PetscCallMPI(MPI_Allreduce(&offrank_periodic_local, &offrank_periodic_global,
1638 1, MPIU_INT, MPI_SUM,
1639 PetscObjectComm((PetscObject)matrix)));
1640 PetscCall(MatGetInfo(matrix, MAT_GLOBAL_SUM, &matrix_info));
1641 PetscCall(PicurvAssertRealNear((PetscReal)expected_global,
1642 (PetscReal)matrix_info.nz_allocated, 0.0,
1643 "point-block matrix must allocate exactly the classified scalar pattern"));
1644 PetscCall(PicurvAssertRealNear((PetscReal)expected_global,
1645 (PetscReal)matrix_info.nz_used, 0.0,
1646 "point-block assembly must insert every classified structural entry"));
1647 PetscCall(PicurvAssertRealNear(0.0, (PetscReal)matrix_info.mallocs, 0.0,
1648 "point-block insertion must not reallocate matrix storage"));
1649 if (require_offrank_periodic && comm_size > 1)
1650 PetscCall(PicurvAssertBool((PetscBool)(offrank_periodic_global > 0),
1651 "MPI periodic fixture must exercise off-rank preallocation"));
1652 PetscFunctionReturn(PETSC_SUCCESS);
1653}
1654
1655/**
1656 * @brief Verifies the point-block model and common preconditioning-engine wiring.
1657 * @return PETSc error code.
1658 */
1659static PetscErrorCode TestPointBlockPreconditionerEngine(void)
1660{
1661 SimCtx *simCtx = NULL;
1662 UserCtx *user = NULL;
1663 char tmpdir[PETSC_MAX_PATH_LEN] = "";
1665 MomentumPreconditionerDescription description = {
1668 0,
1669 0
1670 };
1671 MomentumPreconditionerEngine engine = {0};
1672 Mat preconditioning_matrix = NULL;
1673 Vec x = NULL, f = NULL;
1674 PetscInt block_size = 0, velocity_dof = 0;
1675 PetscReal matrix_norm = 0.0, reassembled_norm = 0.0, difference_norm = 0.0;
1676 PetscScalar reference[9], values[9], mutant[9], legacy[9];
1677 const PetscInt target_i = 1, target_j = 2, target_k = 3;
1678 MatStencil target[3] = {
1679 {.i = 1, .j = 2, .k = 3, .c = 0},
1680 {.i = 1, .j = 2, .k = 3, .c = 1},
1681 {.i = 1, .j = 2, .k = 3, .c = 2}
1682 };
1683 MatStencil conditioned = {.i = 0, .j = 2, .k = 3, .c = 0};
1684 MatStencil homogeneous = {.i = 0, .j = 2, .k = 3, .c = 1};
1685 Mat saved_matrix = NULL, mffd = NULL;
1686 SNES snes = NULL;
1687 Vec direction = NULL, product = NULL, px = NULL;
1688 PetscScalar px_sum = 0.0;
1689 PetscReal px_norm = 0.0;
1690 KSP ksp = NULL;
1691 PC pc = NULL;
1692 Vec pc_rhs = NULL, pc_solution = NULL;
1693 MatInfo initial_allocation_info, repeated_allocation_info;
1694
1695 PetscFunctionBeginUser;
1696 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
1697 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
1698 PetscCall(VecDuplicate(user->Ucont, &x));
1699 PetscCall(VecDuplicate(user->Ucont, &f));
1700 PetscCall(VecCopy(user->Ucont, x));
1701 ctx.user = user;
1702 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f, &ctx));
1703 PetscCall(MomentumPreconditionerEngine_Create(user, NULL, &description, &engine));
1704 preconditioning_matrix = engine.preconditioning_matrix;
1705 PetscCall(PicurvAssertBool((PetscBool)(engine.model_ops == &frozen_momentum_point_block_ops),
1706 "engine must select the frozen-momentum model callbacks"));
1708 "point-block engine must own its separate matrix"));
1709 PetscCall(PicurvAssertBool((PetscBool)!engine.aliases_jacobian_operator,
1710 "point-block matrix must not alias the Jacobian operator"));
1711 PetscCall(MomentumPreconditionerEngine_Assemble(&engine, user, x));
1713 user, preconditioning_matrix, PETSC_FALSE));
1714 PetscCall(MatGetInfo(preconditioning_matrix, MAT_GLOBAL_SUM,
1715 &initial_allocation_info));
1716 PetscCall(MatNorm(preconditioning_matrix, NORM_FROBENIUS, &matrix_norm));
1717 PetscCall(PicurvAssertBool((PetscBool)(matrix_norm > 0.0),
1718 "model callback and common rows must insert matrix entries"));
1719 PetscCall(MatGetBlockSize(preconditioning_matrix, &block_size));
1720 PetscCall(PicurvAssertIntEqual(3, block_size, "point-block matrix block size"));
1721 PetscCall(DMDAGetInfo(user->fda, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1722 &velocity_dof, NULL, NULL, NULL, NULL, NULL));
1723 PetscCall(PicurvAssertIntEqual(3, velocity_dof, "Newton velocity DMDA dof"));
1724
1725 /* Cartesian limit: independently evaluate and compare every entry. */
1726 PetscCall(CollectiveLegacyPointBlockOracle(user, target_i, target_j, target_k, 0, reference));
1727 for (PetscInt rr = 0; rr < 3; ++rr)
1728 for (PetscInt cc = 0; cc < 3; ++cc) {
1729 PetscCall(PreconditionerMatrixStencilEntry(user, preconditioning_matrix,
1730 target[rr], target[cc], &values[3 * rr + cc]));
1731 PetscCall(PicurvAssertRealNear(PetscRealPart(reference[3 * rr + cc]),
1732 PetscRealPart(values[3 * rr + cc]), 1e-13,
1733 "Cartesian point block entry must match independent oracle"));
1734 }
1735
1736 /* Both fixed categories are exact identity rows, with no same-cell coupling. */
1737 for (PetscInt cc = 0; cc < 3; ++cc) {
1738 MatStencil col = conditioned; col.c = cc;
1739 PetscScalar conditioned_value = 0.0, homogeneous_value = 0.0;
1740 PetscCall(PreconditionerMatrixStencilEntry(user, preconditioning_matrix,
1741 conditioned, col, &conditioned_value));
1742 col = homogeneous; col.c = cc;
1743 PetscCall(PreconditionerMatrixStencilEntry(user, preconditioning_matrix,
1744 homogeneous, col, &homogeneous_value));
1745 PetscCall(PicurvAssertRealNear(cc == conditioned.c ? 1.0 : 0.0,
1746 PetscRealPart(conditioned_value), 1e-14, "conditioned row must be exact identity"));
1747 PetscCall(PicurvAssertRealNear(cc == homogeneous.c ? 1.0 : 0.0,
1748 PetscRealPart(homogeneous_value), 1e-14, "homogeneous row must be exact identity"));
1749 }
1750
1751 /* A real residual and MFFD product cannot change subsequent assembly. */
1752 PetscCall(MatDuplicate(preconditioning_matrix, MAT_COPY_VALUES, &saved_matrix));
1753 PetscCall(SNESCreate(PETSC_COMM_WORLD, &snes));
1754 PetscCall(SNESSetDM(snes, user->fda));
1755 PetscCall(SNESSetFunction(snes, f, MomentumNewtonKrylov_FormResidual, &ctx));
1756 PetscCall(MatCreateSNESMF(snes, &mffd));
1757 PetscCall(VecDuplicate(x, &direction)); PetscCall(VecDuplicate(x, &product));
1758 PetscCall(VecSet(direction, 0.375));
1759 PetscCall(MomentumNewtonKrylov_FormResidual(snes, x, f, &ctx));
1760 PetscCall(MatMFFDSetBase(mffd, x, f));
1761 PetscCall(MatMult(mffd, direction, product));
1762 PetscCall(MomentumPreconditionerEngine_Assemble(&engine, user, x));
1763 PetscCall(MatAXPY(saved_matrix, -1.0, preconditioning_matrix, SAME_NONZERO_PATTERN));
1764 PetscCall(MatNorm(saved_matrix, NORM_FROBENIUS, &difference_norm));
1765 PetscCall(PicurvAssertRealNear(0.0, difference_norm, 1e-12,
1766 "assembly must be unchanged after residual and MFFD products"));
1767 PetscCall(VecDestroy(&product)); PetscCall(VecDestroy(&direction));
1768 PetscCall(MatDestroy(&mffd)); PetscCall(SNESDestroy(&snes)); PetscCall(MatDestroy(&saved_matrix));
1769
1770 /* Nonuniform oracle: i/j/k, every component, and all samples are distinct. */
1771 PetscCall(SeedPointBlockOracleFields(user));
1772 PetscCall(VecCopy(user->Ucont, x));
1773 simCtx->step = 1;
1774 PetscCall(MomentumPreconditionerEngine_Assemble(&engine, user, x));
1775 PetscCall(CollectiveLegacyPointBlockOracle(user, target_i, target_j, target_k, 0, reference));
1776 for (PetscInt rr = 0; rr < 3; ++rr)
1777 for (PetscInt cc = 0; cc < 3; ++cc) {
1778 PetscCall(PreconditionerMatrixStencilEntry(user, preconditioning_matrix,
1779 target[rr], target[cc], &values[3 * rr + cc]));
1780 PetscCall(PicurvAssertRealNear(PetscRealPart(reference[3 * rr + cc]),
1781 PetscRealPart(values[3 * rr + cc]), 1e-13,
1782 "nonuniform point block entry must match independent oracle"));
1783 }
1784 PetscCall(PicurvAssertBool((PetscBool)(PetscAbsScalar(reference[1] - reference[3]) > 1e-6 &&
1785 PetscAbsScalar(reference[2] - reference[6]) > 1e-6 &&
1786 PetscAbsScalar(reference[5] - reference[7]) > 1e-6),
1787 "oracle must preserve nonsymmetric component ordering"));
1788 for (PetscInt mutant_flag = ORACLE_CENTER_AJ; mutant_flag <= ORACLE_LEGACY_SIGN;
1789 mutant_flag <<= 1) {
1790 PetscBool differs = PETSC_FALSE;
1791 PetscCall(CollectiveLegacyPointBlockOracle(user, target_i, target_j, target_k,
1792 mutant_flag, mutant));
1793 for (PetscInt n = 0; n < 9; ++n)
1794 if (PetscAbsScalar(reference[n] - mutant[n]) > 1e-8) differs = PETSC_TRUE;
1795 PetscCall(PicurvAssertBool(differs, "independent oracle must reject audited mutant"));
1796 }
1797 PetscCall(CollectiveLegacyPointBlockOracle(user, target_i, target_j, target_k,
1798 ORACLE_OMIT_A5, mutant));
1799 PetscCall(PicurvAssertBool((PetscBool)(PetscAbsScalar(reference[8] - mutant[8]) > 1e-8),
1800 "A[5] must contribute to the zeta diagonal"));
1801 PetscCall(CollectiveLegacyPointBlockOracle(user, target_i, target_j, target_k,
1802 ORACLE_LEGACY_SIGN, legacy));
1803 for (PetscInt n = 0; n < 9; ++n)
1804 PetscCall(PicurvAssertRealNear(PetscRealPart(reference[n]), -PetscRealPart(legacy[n]),
1805 1e-13, "modern block must be the negative legacy block"));
1806
1807 /* A coordinate permutation cannot accidentally address the intended block. */
1808 PetscCall(CollectiveLegacyPointBlockOracle(user, target_k, target_j, target_i, 0, mutant));
1809 {
1810 PetscBool differs = PETSC_FALSE;
1811 for (PetscInt n = 0; n < 9; ++n)
1812 if (PetscAbsScalar(reference[n] - mutant[n]) > 1e-8) differs = PETSC_TRUE;
1813 PetscCall(PicurvAssertBool(differs, "permuted MatStencil coordinates must be detectable"));
1814 }
1815 {
1816 MatStencil neighbor = target[0];
1817 PetscScalar neighbor_value = 0.0;
1818 neighbor.i++;
1819 PetscCall(PreconditionerMatrixStencilEntry(user, preconditioning_matrix,
1820 target[0], neighbor, &neighbor_value));
1821 PetscCall(PicurvAssertRealNear(0.0, PetscRealPart(neighbor_value), 1e-14,
1822 "point block must not insert unintended neighbor entries"));
1823 }
1824
1825 /* The shared time coefficient supplies BDF1 and BDF2 diagonals only. */
1826 simCtx->step = 2;
1827 PetscCall(MomentumPreconditionerEngine_Assemble(&engine, user, x));
1828 PetscCall(CollectiveLegacyPointBlockOracle(user, target_i, target_j, target_k, 0, mutant));
1829 for (PetscInt n = 0; n < 9; ++n) {
1830 PetscCall(PreconditionerMatrixStencilEntry(user, preconditioning_matrix,
1831 target[n / 3], target[n % 3], &values[n]));
1832 PetscCall(PicurvAssertRealNear(PetscRealPart(mutant[n]), PetscRealPart(values[n]),
1833 1e-13, "BDF2 point block must match independent oracle"));
1834 if (n == 0 || n == 4 || n == 8)
1835 PetscCall(PicurvAssertRealNear(0.5 / simCtx->dt,
1836 PetscRealPart(mutant[n] - reference[n]), 1e-10, "BDF2 diagonal increment"));
1837 else
1838 PetscCall(PicurvAssertRealNear(0.0, PetscRealPart(mutant[n] - reference[n]),
1839 1e-13, "BDF order must not change off-diagonal entries"));
1840 }
1841
1842 PetscCall(VecDuplicate(x, &px));
1843 PetscCall(MatMult(preconditioning_matrix, x, px));
1844 PetscCall(VecSum(px, &px_sum)); PetscCall(VecNorm(px, NORM_2, &px_norm));
1845 PetscCall(PicurvAssertRealNear(8.40570474622236e4, PetscRealPart(px_sum), 5e-9,
1846 "P*x global sum must be decomposition independent"));
1847 PetscCall(PicurvAssertRealNear(9.05678688399519e3, px_norm, 5e-10,
1848 "P*x norm must be decomposition independent"));
1849 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1850 "POINT_BLOCK_MPI_SIGNATURE sum=%.16e norm2=%.16e\n",
1851 (double)PetscRealPart(px_sum), (double)px_norm));
1852 PetscCall(VecDestroy(&px));
1853
1854 {
1855 PetscBool assembled = PETSC_FALSE;
1856 PetscCall(MatAssembled(preconditioning_matrix, &assembled));
1857 PetscCall(PicurvAssertBool(assembled, "engine must perform final matrix assembly"));
1858 }
1859 PetscCall(MatNorm(preconditioning_matrix, NORM_FROBENIUS, &matrix_norm));
1860 PetscCall(MatShift(preconditioning_matrix, 7.0));
1861 PetscCall(MomentumPreconditionerEngine_Assemble(&engine, user, x));
1862 PetscCall(MatGetInfo(preconditioning_matrix, MAT_GLOBAL_SUM,
1863 &repeated_allocation_info));
1864 PetscCall(PicurvAssertRealNear((PetscReal)initial_allocation_info.nz_allocated,
1865 (PetscReal)repeated_allocation_info.nz_allocated, 0.0,
1866 "repeated assembly must retain exact allocated storage"));
1867 PetscCall(PicurvAssertRealNear((PetscReal)initial_allocation_info.mallocs,
1868 (PetscReal)repeated_allocation_info.mallocs, 0.0,
1869 "repeated assembly must not add insertion reallocations"));
1870 PetscCall(MatNorm(preconditioning_matrix, NORM_FROBENIUS, &reassembled_norm));
1871 PetscCall(PicurvAssertRealNear(matrix_norm, reassembled_norm, 1e-12,
1872 "repeated engine assembly must clear old entries"));
1873 {
1874 PetscScalar value = 0.0;
1875 PetscCall(PreconditionerMatrixStencilEntry(user, preconditioning_matrix,
1876 conditioned, conditioned, &value));
1877 PetscCall(PicurvAssertRealNear(1.0, PetscRealPart(value), 1e-14,
1878 "repeated engine assembly must clear old entries"));
1879 }
1880 PetscCall(KSPCreate(PETSC_COMM_WORLD, &ksp));
1881 PetscCall(KSPSetOperators(ksp, preconditioning_matrix, preconditioning_matrix));
1882 PetscCall(KSPGetPC(ksp, &pc));
1883 PetscCall(MomentumPreconditionerEngine_ConfigurePetscPC(&engine, pc));
1884 PetscCall(MomentumPreconditionerEngine_ValidatePetscPC(&engine, pc));
1885 PetscCall(KSPSetUp(ksp));
1886 PetscCall(DMCreateGlobalVector(user->fda, &pc_rhs));
1887 PetscCall(DMCreateGlobalVector(user->fda, &pc_solution));
1888 PetscCall(VecSet(pc_rhs, 1.0));
1889 PetscCall(PCApply(pc, pc_rhs, pc_solution));
1890 PetscCall(VecDestroy(&pc_solution)); PetscCall(VecDestroy(&pc_rhs));
1891 PetscCall(KSPDestroy(&ksp));
1892 PetscCall(MomentumPreconditionerEngine_Destroy(&engine));
1893 PetscCall(PicurvAssertBool((PetscBool)(engine.preconditioning_matrix == NULL),
1894 "engine destroy must clear its owned matrix"));
1895 PetscCall(VecDestroy(&f)); PetscCall(VecDestroy(&x)); PetscCall(VecDestroy(&user->Rhs));
1896 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
1897 PetscFunctionReturn(PETSC_SUCCESS);
1898}
1899
1900/** @brief Proves that one periodic matrix row has only its exact +1/-1 pair. */
1901static PetscErrorCode AssertPeriodicPreconditionerRow(UserCtx *user, Mat matrix,
1902 MatStencil row, const char *message)
1903{
1904 AO ao = NULL;
1905 PetscInt ri, rj, rk, global_row, global_rep, lo, hi, local_checked = 0, checked = 0;
1906 PetscInt ncols = 0;
1907 const PetscInt *cols = NULL;
1908 const PetscScalar *values = NULL;
1910
1911 PetscFunctionBeginUser;
1912 type = MomentumNewtonKrylov_ClassifyRow(user, row.i, row.j, row.k, row.c, &ri, &rj, &rk);
1913 PetscCall(PicurvAssertIntEqual(MOM_NK_ROW_PERIODIC_DUPLICATE, type, message));
1914 global_row = row.c + 3 * (row.i + user->info.mx * (row.j + user->info.my * row.k));
1915 PetscCall(DMDAGetAO(user->fda, &ao));
1916 PetscCall(AOApplicationToPetsc(ao, 1, &global_row));
1917 PetscCall(MatGetOwnershipRange(matrix, &lo, &hi));
1918 if (global_row >= lo && global_row < hi) {
1919 PetscBool found_self = PETSC_FALSE, found_rep = PETSC_FALSE;
1920 PetscInt nonzero_entries = 0;
1921 PetscCall(TestStencilToGlobal(user,
1922 (MatStencil){.i = ri, .j = rj, .k = rk, .c = row.c}, &global_rep));
1923 PetscCall(MatGetRow(matrix, global_row, &ncols, &cols, &values));
1924 for (PetscInt n = 0; n < ncols; ++n) {
1925 if (PetscAbsScalar(values[n]) <= 1e-14) continue;
1926 nonzero_entries++;
1927 if (cols[n] == global_row) {
1928 found_self = PETSC_TRUE;
1929 PetscCall(PicurvAssertRealNear(1.0, PetscRealPart(values[n]), 1e-14,
1930 "periodic row self entry"));
1931 } else if (cols[n] == global_rep) {
1932 found_rep = PETSC_TRUE;
1933 PetscCall(PicurvAssertRealNear(-1.0, PetscRealPart(values[n]), 1e-14,
1934 "periodic row representative entry"));
1935 } else {
1936 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_PLIB,
1937 "Periodic row contains an unintended nonzero column.");
1938 }
1939 }
1940 PetscCall(PicurvAssertIntEqual(2, nonzero_entries,
1941 "periodic row must contain exactly two numerical entries"));
1942 PetscCall(PicurvAssertBool((PetscBool)(found_self && found_rep), message));
1943 PetscCall(MatRestoreRow(matrix, global_row, &ncols, &cols, &values));
1944 local_checked = 1;
1945 }
1946 PetscCallMPI(MPI_Allreduce(&local_checked, &checked, 1, MPIU_INT, MPI_SUM, PETSC_COMM_WORLD));
1947 PetscCall(PicurvAssertIntEqual(1, checked, "periodic row must have exactly one matrix owner"));
1948 PetscFunctionReturn(PETSC_SUCCESS);
1949}
1950
1951/** @brief Verifies Jacobian creation/registration and baseline alias ownership. */
1952static PetscErrorCode TestJacobianInterfaceAndBaselineAlias(void)
1953{
1954 SimCtx *simCtx = NULL;
1955 UserCtx *user = NULL;
1956 char tmpdir[PETSC_MAX_PATH_LEN] = "";
1958 MomentumPreconditionerDescription description = {
1960 };
1961 SNES snes = NULL;
1962 Mat jacobian_operator = NULL, preconditioning_matrix = NULL;
1963 KSP ksp = NULL;
1964 PC pc = NULL;
1965 PetscInt rows = 0, cols = 0;
1966 const char *jacobian_prefix = NULL;
1967 const char *pc_type = NULL;
1968 PetscBool prefix_matches = PETSC_FALSE;
1969 PetscBool pc_is_none = PETSC_FALSE;
1970
1971 PetscFunctionBeginUser;
1972 PetscCall(BuildNewtonFixture(fixed_wall_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
1973 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
1974 PetscCall(SNESCreate(PETSC_COMM_WORLD, &snes));
1975 PetscCall(SNESSetDM(snes, user->fda));
1976 ctx.user = user;
1979 PetscCall(SNESSetFunction(snes, NULL, MomentumNewtonKrylov_FormResidual, &ctx));
1980 PetscCall(MomentumNewtonJacobian_Create(snes, &ctx.jacobian));
1981 PetscCall(MatGetOptionsPrefix(ctx.jacobian.jacobian_operator, &jacobian_prefix));
1982 PetscCall(PetscStrcmp(jacobian_prefix, "mom_nk_", &prefix_matches));
1983 PetscCall(PicurvAssertBool(prefix_matches,
1984 "Jacobian interface must apply the application prefix"));
1986 user, ctx.jacobian.jacobian_operator, &description, &ctx.preconditioning_engine));
1988 snes, &ctx.jacobian, &ctx.preconditioning_engine, &ctx));
1989 PetscCall(SNESGetKSP(snes, &ksp));
1990 PetscCall(KSPGetPC(ksp, &pc));
1992 &ctx.preconditioning_engine, pc));
1994 &ctx.preconditioning_engine, pc));
1995 PetscCall(PCGetType(pc, &pc_type));
1996 PetscCall(PetscStrcmp(pc_type, PCNONE, &pc_is_none));
1997 PetscCall(PicurvAssertBool(pc_is_none,
1998 "baseline engine must derive PETSc PCNONE"));
1999 PetscCall(SNESGetJacobian(snes, &jacobian_operator, &preconditioning_matrix, NULL, NULL));
2000 PetscCall(PicurvAssertBool(
2001 (PetscBool)(jacobian_operator == ctx.jacobian.jacobian_operator),
2002 "SNES must receive the Jacobian-interface MFFD operator"));
2003 PetscCall(PicurvAssertBool((PetscBool)(preconditioning_matrix == jacobian_operator),
2004 "baseline preconditioning matrix must alias the Jacobian"));
2006 "baseline engine must record matrix aliasing"));
2008 "baseline engine must not own the Jacobian alias"));
2010 PetscCall(MatGetSize(ctx.jacobian.jacobian_operator, &rows, &cols));
2011 PetscCall(PicurvAssertBool((PetscBool)(rows > 0 && cols > 0),
2012 "destroying an alias engine must preserve the Jacobian"));
2014 PetscCall(SNESDestroy(&snes));
2015 PetscCall(VecDestroy(&user->Rhs));
2016 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
2017 PetscFunctionReturn(PETSC_SUCCESS);
2018}
2019
2020/** @brief Exercises engine-owned periodic duplicate rows on every MPI layout. */
2021static PetscErrorCode TestPointBlockPeriodicAssembly(void)
2022{
2023 SimCtx *simCtx = NULL;
2024 UserCtx *user = NULL;
2025 char tmpdir[PETSC_MAX_PATH_LEN] = "";
2027 MomentumPreconditionerDescription description = {
2030 0,
2031 0
2032 };
2033 Vec x = NULL, f = NULL;
2034 PetscReal matrix_norm = 0.0;
2035
2036 PetscFunctionBeginUser;
2037 PetscCall(BuildNewtonFixture(periodic_xyz_bcs, &simCtx, &user, tmpdir, sizeof(tmpdir)));
2038 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
2039 PetscCall(VecDuplicate(user->Ucont, &x));
2040 PetscCall(VecDuplicate(user->Ucont, &f));
2041 PetscCall(VecCopy(user->Ucont, x));
2042 ctx.user = user;
2043 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, x, f, &ctx));
2045 user, NULL, &description, &ctx.preconditioning_engine));
2048 user, ctx.preconditioning_engine.preconditioning_matrix, PETSC_TRUE));
2049 PetscCall(MatNorm(ctx.preconditioning_engine.preconditioning_matrix,
2050 NORM_FROBENIUS, &matrix_norm));
2051 PetscCall(PicurvAssertBool((PetscBool)(matrix_norm > 0.0),
2052 "periodic point-block assembly must produce a nonzero matrix"));
2053 PetscCall(AssertPeriodicPreconditionerRow(user,
2055 (MatStencil){.i = 0, .j = 2, .k = 3, .c = 0},
2056 "single-axis periodic row must contain exact +1/-1 entries"));
2057 PetscCall(AssertPeriodicPreconditionerRow(user,
2059 (MatStencil){.i = 0, .j = 0, .k = 3, .c = 1},
2060 "periodic intersection must contain exact +1/-1 entries"));
2061 PetscCall(AssertPeriodicPreconditionerRow(user,
2063 (MatStencil){.i = 0, .j = 0, .k = 0, .c = 2},
2064 "periodic origin intersection must contain exact +1/-1 entries"));
2066 PetscCall(VecDestroy(&f));
2067 PetscCall(VecDestroy(&x));
2068 PetscCall(VecDestroy(&user->Rhs));
2069 PetscCall(DestroyNewtonFixture(&simCtx, tmpdir));
2070 PetscFunctionReturn(PETSC_SUCCESS);
2071}
2072
2073/**
2074 * @brief Runs the focused Newton--Krylov unit suite.
2075 * @param argc Command-line argument count.
2076 * @param argv Command-line argument vector.
2077 * @return Process exit status.
2078 */
2079int main(int argc, char **argv)
2080{
2081 PetscErrorCode ierr;
2082 const PicurvTestCase cases[] = {
2083 {"residual-repeatability-and-input-integrity", TestResidualRepeatabilityAndInputIntegrity},
2084 {"constraint-rows", TestConstraintRows},
2085 {"fixed-constraint-derivatives-all-faces", TestFixedConstraintDerivativesAllFaces},
2086 {"inlet-outlet-constraint-derivatives", TestInletOutletConstraintDerivatives},
2087 {"periodic-constraint-derivatives-and-intersections", TestPeriodicConstraintDerivativesAndIntersections},
2088 {"matrix-free-derivative", TestMatrixFreeDerivative},
2089 {"whole-operator-direct-jacobian", TestWholeOperatorDirectJacobian},
2090 {"periodic-operator-has-no-zero-rows", TestPeriodicOperatorHasNoZeroRows},
2091 {"zero-iteration-structured-logging", TestZeroIterationStructuredLogging},
2092 {"flat-channel-bdf1-startup", TestFlatChannelStartup},
2093 {"restart-and-continuation-solve", TestRestartAndContinuationSolve},
2094 {"small-solve-and-rollback", TestSmallSolveAndRollback},
2095 {"unsupported-configuration-fails-before-allocation", TestUnsupportedConfigurationFailsBeforeAllocation},
2096 {"post-allocation-failure-cleanup", TestPostAllocationFailureCleanup},
2097 {"linearization-config-parsing", TestLinearizationConfigParsing},
2098 {"point-block-preconditioner-engine", TestPointBlockPreconditionerEngine},
2099 {"jacobian-interface-and-baseline-alias", TestJacobianInterfaceAndBaselineAlias},
2100 {"point-block-periodic-assembly", TestPointBlockPeriodicAssembly},
2101 };
2102
2103 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv Newton Krylov tests");
2104 if (ierr) return (int)ierr;
2105 ierr = PicurvRunTests("unit-newton-krylov", cases, sizeof(cases) / sizeof(cases[0]));
2106 if (PetscFinalize()) return 1;
2107 return (int)ierr;
2108}
PetscErrorCode BoundaryCondition_Create(BCHandlerType handler_type, BoundaryCondition **new_bc_ptr)
(Private) Creates and configures a specific BoundaryCondition handler object.
Definition Boundaries.c:744
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 MomentumNewtonKrylovRowType MomentumNewtonKrylov_ClassifyRow(UserCtx *user, PetscInt i, PetscInt j, PetscInt k, PetscInt component, PetscInt *ri, PetscInt *rj, PetscInt *rk)
Classifies one stored staggered component row and its periodic representative.
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
MomentumNewtonKrylovRowType
@ MOM_NK_ROW_PERIODIC_DUPLICATE
@ MOM_NK_ROW_PHYSICAL
@ MOM_NK_ROW_FIXED_HOMOGENEOUS
@ MOM_NK_ROW_FIXED_CONDITIONED
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 CollectiveLegacyPointBlockOracle(UserCtx *user, PetscInt i, PetscInt j, PetscInt k, PetscInt flags, PetscScalar block[9])
Evaluates the independent oracle on the unique owner and broadcasts it.
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 SeedPointBlockOracleFields(UserCtx *user)
Seeds nonuniform, index-distinguishing coefficient fields.
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 void LegacyPointBlockOracle(const SimCtx *simCtx, const Cmpnts ***u, const Cmpnts ***csi, const Cmpnts ***eta, const Cmpnts ***zet, const PetscReal ***aj, PetscInt i, PetscInt j, PetscInt k, PetscInt flags, PetscScalar block[9])
Independent transcription of the audited legacy mode-2 point block.
static PetscErrorCode TestMatrixFreeDerivative(void)
Compares PETSc's matrix-free action with direct differencing.
static PetscReal LegacyOracleMetricNormSquared(Cmpnts metric)
Test-owned metric norm used by the independent legacy transcription.
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.
@ ORACLE_CENTER_TRANSVERSE_METRICS
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:740
PetscInt clark
Definition variables.h:790
PetscReal FarFluxInSum
Definition variables.h:777
PetscInt movefsi
Definition variables.h:714
@ INLET
Definition variables.h:288
@ PERIODIC
Definition variables.h:290
@ WALL
Definition variables.h:284
PetscBool continueMode
Definition variables.h:701
PetscInt moveframe
Definition variables.h:715
PetscInt TwoD
Definition variables.h:715
PetscReal FarFluxOutSum
Definition variables.h:777
Vec Zet
Definition variables.h:927
Vec Rhs
Definition variables.h:912
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:896
PetscInt block_number
Definition variables.h:768
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:879
PetscInt rans
Definition variables.h:789
PetscReal FluxOutSum
Definition variables.h:777
Vec lZet
Definition variables.h:927
Vec Csi
Definition variables.h:927
PetscBool mom_last_converged
Definition variables.h:738
@ BC_HANDLER_PERIODIC_GEOMETRIC
Definition variables.h:314
@ BC_HANDLER_PERIODIC_DRIVEN_CONSTANT_FLUX
Definition variables.h:316
@ BC_HANDLER_WALL_NOSLIP
Definition variables.h:303
@ BC_HANDLER_INLET_INTERP_FROM_FILE
Definition variables.h:309
PetscReal ren
Definition variables.h:732
BCHandlerType handler_type
Definition variables.h:367
PetscReal dt
Definition variables.h:699
Vec Ucont
Definition variables.h:904
PetscInt StartStep
Definition variables.h:694
PetscInt rotatefsi
Definition variables.h:714
@ MOMENTUM_SOLVER_NEWTON_KRYLOV
Definition variables.h:535
PetscScalar x
Definition variables.h:101
Vec Eta
Definition variables.h:927
char log_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:709
PetscReal FluxInSum
Definition variables.h:777
Vec lCsi
Definition variables.h:927
PetscScalar z
Definition variables.h:101
Vec Ucat
Definition variables.h:904
Vec Ucont_o
Definition variables.h:911
PetscInt wallfunction
Definition variables.h:790
Vec Ucont_rm1
Definition variables.h:912
Vec lUcont
Definition variables.h:904
PetscInt step
Definition variables.h:692
Vec lAj
Definition variables.h:927
DMDALocalInfo info
Definition variables.h:883
Vec lUcat
Definition variables.h:904
PetscScalar y
Definition variables.h:101
Vec lEta
Definition variables.h:927
Vec Nvert
Definition variables.h:904
BCType mathematical_type
Definition variables.h:366
PetscInt rotateframe
Definition variables.h:715
PetscInt immersed
Definition variables.h:714
BCFace
Identifies the six logical faces of a structured computational block.
Definition variables.h:259
@ BC_FACE_NEG_X
Definition variables.h:260
@ BC_FACE_POS_X
Definition variables.h:260
A 3D point or vector with PetscScalar components.
Definition variables.h:100
The master context for the entire simulation.
Definition variables.h:684
User-defined context containing data specific to a single computational grid level.
Definition variables.h:876