PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
test_momentum_newton_boundary_fixedpoint.c
Go to the documentation of this file.
1/**
2 * @file test_momentum_newton_boundary_fixedpoint.c
3 * @brief Opt-in integration regression for the Newton--Krylov deterministic
4 * Cartesian-seed correction on the production-sized straight duct.
5 *
6 * The diagnostic phase established that the production Newton--Krylov residual
7 * must reconstruct Ucat/lUcat from the current SNES trial vector X before the
8 * first boundary sweep (see src/momentum_newton_krylov.c). That correction is
9 * now in production. This file locks the resulting guarantees in place using the
10 * *actual production* residual callback (included via a privately renamed copy of
11 * the production translation unit) -- it deliberately does NOT keep a duplicate
12 * test-local seeded callback, so it exercises exactly what ships:
13 *
14 * 1. residual purity at the reproduced physical step-2 state (immediate and
15 * after real PETSc MFFD products);
16 * 2. a complete production step-2 solve from the true projected step-1 state
17 * using PETSc's default classical Gram--Schmidt;
18 * 3. boundary-map compatibility: the converged three-pass solution also zeros
19 * the 24-pass outlet residual;
20 * 4. clean pressure projection of the converged momentum state.
21 *
22 * The heavy fixture keeps this target out of `make check`; the cheap
23 * seed-removal detector lives in unit-newton-krylov (default suite). Central
24 * checks run on one and four MPI ranks.
25 */
26
27#include "test_support.h"
28#include "initialcondition.h"
29#include "runloop.h"
30#include "solvers.h"
31
32#define MomentumSolver_NewtonKrylov MomentumSolver_NewtonKrylov_BoundaryFixedpointPrivateCopy
33#include "../../src/momentum_newton_krylov.c"
34#undef MomentumSolver_NewtonKrylov
35
36/* ======================================================================== */
37/* Persistent-state snapshot machinery (solve-entry restore). */
38/* ======================================================================== */
39
40#define FP_FIELD_COUNT 16
41#define FP_SCALAR_COUNT 10
42
43typedef struct {
44 Vec value[FP_FIELD_COUNT];
45 PetscReal scalar[FP_SCALAR_COUNT];
47
48/** @brief Collects the persistent vectors observed by the residual/boundary path. */
49static void FpGetVectors(UserCtx *user, Vec field[FP_FIELD_COUNT])
50{
51 field[0] = user->Ucont; field[1] = user->lUcont;
52 field[2] = user->Ucat; field[3] = user->lUcat;
53 field[4] = user->P; field[5] = user->lP;
54 field[6] = user->Bcs.Ubcs; field[7] = user->Bcs.Uch;
55 field[8] = user->Rhs;
56 field[9] = user->Ucont_o; field[10] = user->lUcont_o;
57 field[11] = user->Ucont_rm1; field[12] = user->lUcont_rm1;
58 field[13] = user->Ucat_o; field[14] = user->P_o;
59 field[15] = user->CellFieldAtCorner;
60}
61
62/** @brief Collects the persistent scalar diagnostics maintained by boundary processing. */
63static void FpGetScalars(UserCtx *user, PetscReal scalar[FP_SCALAR_COUNT])
64{
65 SimCtx *s = user->simCtx;
66 scalar[0] = s->FluxInSum; scalar[1] = s->FluxOutSum;
67 scalar[2] = s->FarFluxInSum; scalar[3] = s->FarFluxOutSum;
68 scalar[4] = s->Fluxsum; scalar[5] = s->AreaInSum;
69 scalar[6] = s->AreaOutSum; scalar[7] = s->bulkVelocityCorrection;
70 scalar[8] = s->boundaryVelocityCorrection; scalar[9] = user->FluxIntpSum;
71}
72
73/** @brief Deep-copies the audited persistent state into a test-owned snapshot. */
74static PetscErrorCode FpCapture(UserCtx *user, FpSnapshot *snap)
75{
76 Vec field[FP_FIELD_COUNT];
77 PetscFunctionBeginUser;
78 memset(snap, 0, sizeof(*snap));
79 FpGetVectors(user, field);
80 for (PetscInt i = 0; i < FP_FIELD_COUNT; ++i) {
81 if (field[i]) {
82 PetscCall(VecDuplicate(field[i], &snap->value[i]));
83 PetscCall(VecCopy(field[i], snap->value[i]));
84 }
85 }
86 FpGetScalars(user, snap->scalar);
87 PetscFunctionReturn(PETSC_SUCCESS);
88}
89
90/** @brief Restores a previously captured persistent-state snapshot. */
91static PetscErrorCode FpRestore(UserCtx *user, const FpSnapshot *snap)
92{
93 Vec field[FP_FIELD_COUNT];
94 SimCtx *s = user->simCtx;
95 PetscFunctionBeginUser;
96 FpGetVectors(user, field);
97 for (PetscInt i = 0; i < FP_FIELD_COUNT; ++i)
98 if (field[i] && snap->value[i]) PetscCall(VecCopy(snap->value[i], field[i]));
99 s->FluxInSum = snap->scalar[0]; s->FluxOutSum = snap->scalar[1];
100 s->FarFluxInSum = snap->scalar[2]; s->FarFluxOutSum = snap->scalar[3];
101 s->Fluxsum = snap->scalar[4]; s->AreaInSum = snap->scalar[5];
102 s->AreaOutSum = snap->scalar[6]; s->bulkVelocityCorrection = snap->scalar[7];
103 s->boundaryVelocityCorrection = snap->scalar[8]; user->FluxIntpSum = snap->scalar[9];
104 PetscFunctionReturn(PETSC_SUCCESS);
105}
106
107/** @brief Releases all vectors owned by one persistent-state snapshot. */
108static PetscErrorCode FpDestroy(FpSnapshot *snap)
109{
110 PetscFunctionBeginUser;
111 for (PetscInt i = 0; i < FP_FIELD_COUNT; ++i) PetscCall(VecDestroy(&snap->value[i]));
112 PetscFunctionReturn(PETSC_SUCCESS);
113}
114
115/* ======================================================================== */
116/* Collective norm helpers. */
117/* ======================================================================== */
118
119/** @brief Computes collective global difference norms for global or rank-local vectors. */
120static PetscErrorCode CollectiveDifference(Vec a, Vec b, PetscReal *norm_inf, PetscReal *norm_2)
121{
122 Vec delta = NULL;
123 MPI_Comm comm;
124 PetscMPIInt comm_size, world_size;
125 PetscReal local_inf, local_2;
126 PetscFunctionBeginUser;
127 PetscCall(VecDuplicate(a, &delta));
128 PetscCall(VecWAXPY(delta, -1.0, a, b));
129 PetscCall(VecNorm(delta, NORM_INFINITY, &local_inf));
130 PetscCall(VecNorm(delta, NORM_2, &local_2));
131 comm = PetscObjectComm((PetscObject)a);
132 PetscCallMPI(MPI_Comm_size(comm, &comm_size));
133 PetscCallMPI(MPI_Comm_size(PETSC_COMM_WORLD, &world_size));
134 if (comm_size == 1 && world_size > 1) {
135 PetscReal square = local_2 * local_2;
136 PetscCallMPI(MPI_Allreduce(&local_inf, norm_inf, 1, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD));
137 PetscCallMPI(MPI_Allreduce(&square, norm_2, 1, MPIU_REAL, MPI_SUM, PETSC_COMM_WORLD));
138 *norm_2 = PetscSqrtReal(*norm_2);
139 } else {
140 *norm_inf = local_inf;
141 *norm_2 = local_2;
142 }
143 PetscCall(VecDestroy(&delta));
144 PetscFunctionReturn(PETSC_SUCCESS);
145}
146
147/** @brief Finds the MPI rank containing the largest local entry difference. */
148static PetscErrorCode DifferenceMaxRank(Vec a, Vec b, PetscMPIInt *max_rank)
149{
150 const PetscScalar *aa = NULL, *bb = NULL;
151 PetscInt nlocal;
152 PetscReal local_max = 0.0, *all_max = NULL;
153 PetscMPIInt size;
154 PetscFunctionBeginUser;
155 PetscCall(VecGetLocalSize(a, &nlocal));
156 PetscCall(VecGetArrayRead(a, &aa)); PetscCall(VecGetArrayRead(b, &bb));
157 for (PetscInt i = 0; i < nlocal; ++i) local_max = PetscMax(local_max, PetscAbsScalar(bb[i] - aa[i]));
158 PetscCall(VecRestoreArrayRead(b, &bb)); PetscCall(VecRestoreArrayRead(a, &aa));
159 PetscCallMPI(MPI_Comm_size(PETSC_COMM_WORLD, &size));
160 PetscCall(PetscMalloc1(size, &all_max));
161 PetscCallMPI(MPI_Allgather(&local_max, 1, MPIU_REAL, all_max, 1, MPIU_REAL, PETSC_COMM_WORLD));
162 *max_rank = 0;
163 for (PetscMPIInt r = 1; r < size; ++r) if (all_max[r] > all_max[*max_rank]) *max_rank = r;
164 PetscCall(PetscFree(all_max));
165 PetscFunctionReturn(PETSC_SUCCESS);
166}
167
168/* ======================================================================== */
169/* Deterministic Cartesian seed and an N-pass residual for compatibility. */
170/* ======================================================================== */
171
172/**
173 * @brief Reconstructs Ucat/lUcat deterministically from X, mirroring the seed
174 * now performed by the production callback.
175 *
176 * Used only by the N-pass compatibility residual (Experiment 3), which needs a
177 * boundary-pass count other than the production default of one call (three
178 * passes). For a single call it reproduces the production callback exactly.
179 */
180static PetscErrorCode SeedCartesianFromX(UserCtx *user, Vec X)
181{
182 const char *staggered[] = {"Ucont"};
183 const char *cell[] = {"Ucat"};
184 PetscFunctionBeginUser;
185 PetscCall(VecCopy(X, user->Ucont));
186 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, staggered));
187 PetscCall(Contra2Cart(user));
188 PetscCall(SynchronizePeriodicCellFields(user, 1, cell));
189 PetscCall(UpdateLocalGhosts(user, "Ucat"));
190 PetscFunctionReturn(PETSC_SUCCESS);
191}
192
193/**
194 * @brief Deterministic residual at X using an explicit number of boundary-condition
195 * calls (each three internal passes), for the boundary-map compatibility test.
196 */
197static PetscErrorCode ResidualNCalls(MomentumNewtonKrylovContext *ctx, Vec X,
198 PetscInt boundary_calls, Vec F)
199{
200 UserCtx *user = ctx->user;
201 PetscFunctionBeginUser;
202 PetscCall(SeedCartesianFromX(user, X));
203 for (PetscInt i = 0; i < boundary_calls; ++i) PetscCall(ApplyBoundaryConditions(user));
204 PetscCall(ComputeTotalResidual(user));
205 PetscCall(VecCopy(user->Rhs, F));
206 PetscCall(VecScale(F, -1.0));
207 PetscCall(MomentumNewtonKrylov_ApplyConstraints(ctx, X, F));
208 PetscFunctionReturn(PETSC_SUCCESS);
209}
210
211/* ======================================================================== */
212/* Newton solve configuration and monitor. */
213/* ======================================================================== */
214
215/** @brief Installs the production Newton, line-search, and GMRES options. */
216static PetscErrorCode ConfigureNewtonOptions(void)
217{
218 PetscFunctionBeginUser;
219 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_type", "newtonls"));
220 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_atol", "1e-10"));
221 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_rtol", "1e-8"));
222 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_stol", "1e-12"));
223 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_max_it", "25"));
224 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_snes_linesearch_type", "bt"));
225 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_ksp_type", "gmres"));
226 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_ksp_atol", "1e-10"));
227 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_ksp_rtol", "1e-6"));
228 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_ksp_max_it", "400"));
229 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_ksp_gmres_restart", "80"));
230 PetscCall(PetscOptionsSetValue(NULL, "-mom_nk_pc_type", "none"));
231 PetscFunctionReturn(PETSC_SUCCESS);
232}
233
234typedef struct { const char *label; PetscReal *initial_norm; } SolveMonitorCtx;
235
236/** @brief Records per-Newton nonlinear norm, KSP iterations/reason, and accepted lambda. */
237static PetscErrorCode SolveMonitor(SNES snes, PetscInt it, PetscReal norm, void *vctx)
238{
239 SolveMonitorCtx *m = (SolveMonitorCtx *)vctx;
240 SNESLineSearch ls = NULL;
241 KSP ksp = NULL;
242 KSPConvergedReason kr = KSP_CONVERGED_ITERATING;
243 PetscReal lambda = -1.0;
244 PetscInt cum_ksp = 0, step_ksp = 0;
245 PetscFunctionBeginUser;
246 if (it == 0 && m->initial_norm) *m->initial_norm = norm;
247 PetscCall(SNESGetLinearSolveIterations(snes, &cum_ksp));
248 PetscCall(SNESGetKSP(snes, &ksp));
249 PetscCall(KSPGetConvergedReason(ksp, &kr));
250 PetscCall(KSPGetIterationNumber(ksp, &step_ksp));
251 PetscCall(SNESGetLineSearch(snes, &ls));
252 if (it > 0) PetscCall(SNESLineSearchGetLambda(ls, &lambda));
253 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
254 "FULLSOLVE %s newton=%d nonlinear_norm=% .16e last_ksp_its=%d cumulative_ksp_its=%d "
255 "last_ksp_reason=%d accepted_lambda=% .6e\n",
256 m->label, (int)it, (double)norm, (int)step_ksp, (int)cum_ksp, (int)kr, (double)lambda));
257 PetscFunctionReturn(PETSC_SUCCESS);
258}
259
260typedef struct {
261 SNESConvergedReason reason;
262 PetscInt its, fevals, ksp_total;
263 PetscReal initial_norm, final_norm;
264 PetscBool converged;
267
268/**
269 * @brief Creates a test-owned matrix-free SNES driven by the *production* residual callback.
270 * @param use_mgs When false (production default), leaves GMRES orthogonalization at
271 * PETSc's default classical Gram--Schmidt -- the path the production solver uses.
272 */
274 PetscBool use_mgs, SNES *snes, Mat *J, KSP *ksp)
275{
276 PC pc = NULL;
277 PetscFunctionBeginUser;
278 PetscCall(SNESCreate(PETSC_COMM_WORLD, snes));
279 PetscCall(SNESSetOptionsPrefix(*snes, "mom_nk_"));
280 PetscCall(SNESSetType(*snes, SNESNEWTONLS));
281 PetscCall(SNESSetDM(*snes, user->fda));
282 PetscCall(SNESSetFunction(*snes, NULL, MomentumNewtonKrylov_FormResidual, pctx));
283 PetscCall(MatCreateSNESMF(*snes, J));
284 PetscCall(SNESSetJacobian(*snes, *J, *J, MatMFFDComputeJacobian, NULL));
285 PetscCall(SNESGetKSP(*snes, ksp));
286 PetscCall(KSPSetType(*ksp, KSPGMRES));
287 if (use_mgs) PetscCall(KSPGMRESSetOrthogonalization(*ksp, KSPGMRESModifiedGramSchmidtOrthogonalization));
288 PetscCall(KSPGMRESSetRestart(*ksp, 80));
289 PetscCall(KSPGetPC(*ksp, &pc));
290 PetscCall(PCSetType(pc, PCNONE));
291 PetscCall(SNESSetFromOptions(*snes));
292 PetscFunctionReturn(PETSC_SUCCESS);
293}
294
295/** @brief Runs one complete production-callback step-2 solve from the restored entry state. */
296static PetscErrorCode RunProductionFullSolve(UserCtx *user, const FpSnapshot *entry, Vec X0,
297 PetscBool use_mgs, const char *label, SolveResult *res)
298{
299 MomentumNewtonKrylovContext pctx = {user, NULL, PETSC_FALSE, 0.0};
300 SNES snes = NULL; Mat J = NULL; KSP ksp = NULL; Vec solution = NULL;
302 PetscFunctionBeginUser;
303 memset(res, 0, sizeof(*res));
304 res->initial_norm = -1.0;
305 PetscCall(FpRestore(user, entry));
306 PetscCall(VecDuplicate(X0, &solution)); PetscCall(VecCopy(X0, solution));
307 PetscCall(CreateProductionSNES(user, &pctx, use_mgs, &snes, &J, &ksp));
308 m.label = label; m.initial_norm = &res->initial_norm;
309 PetscCall(SNESMonitorSet(snes, SolveMonitor, &m, NULL));
310 PetscCall(SNESSolve(snes, NULL, solution));
311 PetscCall(SNESGetConvergedReason(snes, &res->reason));
312 PetscCall(SNESGetIterationNumber(snes, &res->its));
313 PetscCall(SNESGetNumberFunctionEvals(snes, &res->fevals));
314 PetscCall(SNESGetLinearSolveIterations(snes, &res->ksp_total));
315 PetscCall(SNESGetFunctionNorm(snes, &res->final_norm));
316 res->converged = (PetscBool)(res->reason > 0);
317 PetscCall(VecDuplicate(solution, &res->Xfinal)); PetscCall(VecCopy(solution, res->Xfinal));
318 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
319 "FULLSOLVE_SUMMARY %s reason=%s (%d) newton=%d fevals=%d ksp_total=%d orthogonalization=%s "
320 "initial_norm=% .16e final_norm=% .16e -> %s\n",
321 label, SNESConvergedReasons[res->reason], (int)res->reason, (int)res->its, (int)res->fevals,
322 (int)res->ksp_total, use_mgs ? "modified-GS" : "classical-GS(default)",
323 (double)res->initial_norm, (double)res->final_norm, res->converged ? "CONVERGED" : "FAILED"));
324 PetscCall(VecDestroy(&solution)); PetscCall(MatDestroy(&J)); PetscCall(SNESDestroy(&snes));
325 PetscFunctionReturn(PETSC_SUCCESS);
326}
327
328/** @brief Stops SNES immediately after its first accepted Newton correction. */
329static PetscErrorCode StopAfterOne(SNES snes, PetscInt it, PetscReal xnorm, PetscReal gnorm,
330 PetscReal fnorm, SNESConvergedReason *reason, void *vctx)
331{
332 PetscReal *norms = (PetscReal *)vctx;
333 PetscFunctionBeginUser;
334 (void)snes; (void)xnorm; (void)gnorm;
335 if (it == 0) norms[0] = fnorm;
336 if (it == 1) norms[1] = fnorm;
337 *reason = it >= 1 ? SNES_CONVERGED_ITS : SNES_CONVERGED_ITERATING;
338 PetscFunctionReturn(PETSC_SUCCESS);
339}
340
341/* ======================================================================== */
342/* The regression itself. */
343/* ======================================================================== */
344
345/**
346 * @brief Builds the production-sized duct, advances the true physical step 1, and
347 * exercises purity, full-solve convergence, boundary-map compatibility, and
348 * projection using the production Newton--Krylov callback and solver.
349 */
350static PetscErrorCode TestNewtonKrylovProductionRegression(void)
351{
352 SimCtx *simCtx = NULL;
353 UserCtx *user = NULL;
354 char tmpdir[PETSC_MAX_PATH_LEN] = "";
355 FpSnapshot entry = {0};
357 Vec X0 = NULL, X1 = NULL, Xfinal = NULL;
358 Vec F0 = NULL, F1 = NULL, Fafter = NULL, v = NULL, jv = NULL;
359 Vec F3 = NULL, F24 = NULL;
360 SolveResult res_cgs = {0};
361 PetscReal norms[2] = {0.0, 0.0};
362 PetscReal imm_inf = 0.0, imm_2 = 0.0, mffd_inf = 0.0, mffd_2 = 0.0;
363 PetscReal f3_2 = 0.0, f24_2 = 0.0, f24_inf = 0.0, f24m3_inf = 0.0, f24m3_2 = 0.0;
364 PetscReal max_div = -1.0;
365 const char *fields[] = {"Ucont"};
366 const char *stag[] = {"Ucont"}; const char *cell[] = {"Ucat"};
367 const PetscReal purity_inf_tol = 1e-13, purity_2_tol = 1e-12;
368 const PetscReal compat_tol = 1e-8, div_tol = 1e-6;
369 PetscMPIInt world, mffd_rank = 0;
370 PetscErrorCode prod_ierr;
371
372 PetscFunctionBeginUser;
373 PetscCallMPI(MPI_Comm_size(PETSC_COMM_WORLD, &world));
374 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
375 "\n########## NEWTON-KRYLOV PRODUCTION REGRESSION (ranks=%d) ##########\n", (int)world));
376 PetscCall(PicurvBuildMomentumPurityRuntimeContext(NULL, &simCtx, &user, tmpdir, sizeof(tmpdir)));
377 PetscCall(InitializeEulerianState(simCtx));
378 PetscCall(ConfigureNewtonOptions());
379
380 /* Complete physical step 1 (momentum + Poisson + projection + finalization)
381 * through the real run path, advance histories, then enter step 2. */
382 simCtx->step = 1; simCtx->ti = simCtx->dt;
383 PetscCall(FlowSolver(simCtx));
384 PetscCall(UpdateSolverHistoryVectors(user));
385 simCtx->step = 2; simCtx->ti = 2.0 * simCtx->dt;
386
387 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
388 pctx.user = user; pctx.history_file = NULL; pctx.have_initial_norm = PETSC_FALSE; pctx.initial_norm = 0.0;
389
390 /* Canonicalize the entry exactly as production MomentumSolver_NewtonKrylov does. */
391 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fields));
392 PetscCall(ApplyBoundaryConditions(user));
393 PetscCall(FpCapture(user, &entry));
394 PetscCall(VecDuplicate(user->Ucont, &X0)); PetscCall(VecCopy(user->Ucont, X0));
395
396 /* -------- 4.1 residual purity at the step-2 / Newton-1 state -------- */
397 PetscCall(PetscPrintf(PETSC_COMM_WORLD, "\n===== 4.1 production-callback residual purity =====\n"));
398 { /* Reach the accepted Newton-1 state X1 with the production callback + default CGS. */
399 SNES snes = NULL; Mat J = NULL; KSP ksp = NULL;
400 PetscCall(VecDuplicate(X0, &X1)); PetscCall(VecCopy(X0, X1));
401 PetscCall(CreateProductionSNES(user, &pctx, PETSC_FALSE, &snes, &J, &ksp));
402 PetscCall(SNESSetConvergenceTest(snes, StopAfterOne, norms, NULL));
403 PetscCall(SNESSolve(snes, NULL, X1));
404 PetscCall(MatDestroy(&J)); PetscCall(SNESDestroy(&snes));
405 }
406 PetscCall(VecDuplicate(X1, &F0)); PetscCall(VecDuplicate(X1, &F1));
407 PetscCall(VecDuplicate(X1, &Fafter)); PetscCall(VecDuplicate(X1, &v)); PetscCall(VecDuplicate(X1, &jv));
408
409 /* Immediate repeatability with hidden state poisoned between the two calls. */
410 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, X1, F0, &pctx));
411 simCtx->FluxInSum = 1234.0; simCtx->FluxOutSum = -4321.0;
412 simCtx->FarFluxInSum = 77.0; simCtx->FarFluxOutSum = -88.0;
413 PetscCall(VecSet(user->Ucat, 7.0)); PetscCall(VecSet(user->lUcat, 7.0));
414 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, X1, F1, &pctx));
415 PetscCall(CollectiveDifference(F0, F1, &imm_inf, &imm_2));
416 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
417 "PURITY immediate inf=% .3e l2=% .3e (tol inf=%.0e l2=%.0e)\n",
418 (double)imm_inf, (double)imm_2, (double)purity_inf_tol, (double)purity_2_tol));
419
420 /* Post-MFFD repeatability: real PETSc MFFD products, then reevaluate. */
421 {
422 SNES snes = NULL; Mat J = NULL; KSP ksp = NULL;
423 PetscInt lo, hi; PetscScalar *a = NULL; PetscReal vn;
424 PetscCall(VecGetOwnershipRange(v, &lo, &hi));
425 PetscCall(VecGetArray(v, &a));
426 for (PetscInt i = lo; i < hi; ++i) a[i - lo] = PetscSinReal(0.17 * (PetscReal)(i + 1));
427 PetscCall(VecRestoreArray(v, &a));
428 PetscCall(VecNorm(v, NORM_2, &vn)); PetscCall(VecScale(v, 1.0 / vn));
429 PetscCall(CreateProductionSNES(user, &pctx, PETSC_FALSE, &snes, &J, &ksp));
430 PetscCall(MatMFFDSetBase(J, X1, F0));
431 for (PetscInt i = 0; i < 40; ++i) PetscCall(MatMult(J, v, jv));
432 PetscCall(MomentumNewtonKrylov_FormResidual(NULL, X1, Fafter, &pctx));
433 PetscCall(CollectiveDifference(F0, Fafter, &mffd_inf, &mffd_2));
434 PetscCall(DifferenceMaxRank(F0, Fafter, &mffd_rank));
435 PetscCall(MatDestroy(&J)); PetscCall(SNESDestroy(&snes));
436 }
437 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
438 "PURITY post_mffd inf=% .3e l2=% .3e max_rank=%d\n",
439 (double)mffd_inf, (double)mffd_2, (int)mffd_rank));
440
441 /* -------- 4.2 full step-2 solve with default classical Gram-Schmidt -------- */
442 PetscCall(PetscPrintf(PETSC_COMM_WORLD, "\n===== 4.2 full production step-2 solve (default classical GS) =====\n"));
443 PetscCall(RunProductionFullSolve(user, &entry, X0, PETSC_FALSE, "CGS-default", &res_cgs));
444
445 /* Also drive the ACTUAL production solver wrapper (default GS) and take X_final
446 * from it. The wrapper requires user->Rhs unallocated on entry. */
447 PetscCall(FpRestore(user, &entry));
448 PetscCall(VecDestroy(&user->Rhs));
449 prod_ierr = MomentumSolver_NewtonKrylov_BoundaryFixedpointPrivateCopy(user, NULL, NULL);
450 PetscCall(VecDuplicate(user->Ucont, &user->Rhs));
451 PetscCall(VecDuplicate(user->Ucont, &Xfinal)); PetscCall(VecCopy(user->Ucont, Xfinal));
452 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
453 "PROD_SOLVER ierr=%d mom_last_converged=%d\n",
454 (int)prod_ierr, (int)simCtx->mom_last_converged));
455 {
456 PetscReal dinf, d2; PetscMPIInt r;
457 PetscCall(CollectiveDifference(res_cgs.Xfinal, Xfinal, &dinf, &d2));
458 PetscCall(DifferenceMaxRank(res_cgs.Xfinal, Xfinal, &r));
459 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
460 "PROD_SOLVER test_snes_vs_wrapper inf=% .3e l2=% .3e max_rank=%d\n",
461 (double)dinf, (double)d2, (int)r));
462 }
463
464 /* -------- 4.3 boundary-map compatibility at the converged solution -------- */
465 PetscCall(PetscPrintf(PETSC_COMM_WORLD, "\n===== 4.3 boundary-map compatibility (3 vs 24 passes) =====\n"));
466 PetscCall(VecDuplicate(Xfinal, &F3)); PetscCall(VecDuplicate(Xfinal, &F24));
467 PetscCall(FpRestore(user, &entry));
468 PetscCall(ResidualNCalls(&pctx, Xfinal, 1, F3)); /* 3 passes == production callback */
469 PetscCall(VecNorm(F3, NORM_2, &f3_2));
470 PetscCall(FpRestore(user, &entry));
471 PetscCall(ResidualNCalls(&pctx, Xfinal, 8, F24)); /* 24 passes */
472 PetscCall(VecNorm(F24, NORM_2, &f24_2));
473 PetscCall(VecNorm(F24, NORM_INFINITY, &f24_inf));
474 PetscCall(CollectiveDifference(F3, F24, &f24m3_inf, &f24m3_2));
475 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
476 "COMPAT F3_l2=% .6e F24_l2=% .6e F24_inf=% .6e F24_minus_F3_l2=% .6e (tol %.0e)\n",
477 (double)f3_2, (double)f24_2, (double)f24_inf, (double)f24m3_2, (double)compat_tol));
478
479 /* -------- 4.4 projection compatibility -------- */
480 PetscCall(PetscPrintf(PETSC_COMM_WORLD, "\n===== 4.4 projection compatibility =====\n"));
481 PetscCall(FpRestore(user, &entry));
482 PetscCall(VecCopy(Xfinal, user->Ucont));
483 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, stag));
484 PetscCall(Contra2Cart(user));
485 PetscCall(SynchronizePeriodicCellFields(user, 1, cell));
486 PetscCall(UpdateLocalGhosts(user, "Ucat"));
487 PetscCall(ApplyBoundaryConditions(user));
488 PetscCall(PoissonSolver_MG(&simCtx->usermg));
489 PetscCall(UpdatePressure(user));
490 PetscCall(Projection(user));
491 PetscCall(UpdateLocalGhosts(user, "P"));
492 PetscCall(ComputeDivergence(user));
493 max_div = simCtx->MaxDiv;
494 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
495 "PROJECTION max_divergence=% .6e net_divergence_sum=% .6e (tol %.0e)\n",
496 (double)max_div, (double)simCtx->summationRHS, (double)div_tol));
497
498 /* -------- consolidated verdicts (printed before assertions) -------- */
499 PetscCall(PetscPrintf(PETSC_COMM_WORLD, "\nREGRESSION_VERDICTS (ranks=%d)\n", (int)world));
500 PetscCall(PetscPrintf(PETSC_COMM_WORLD, " PRODUCTION_PURITY: %s\n",
501 (imm_inf <= purity_inf_tol && imm_2 <= purity_2_tol &&
502 mffd_inf <= purity_inf_tol && mffd_2 <= purity_2_tol) ? "PASS" : "FAIL"));
503 PetscCall(PetscPrintf(PETSC_COMM_WORLD, " FULL_STEP2_DEFAULT_GS: %s\n",
504 res_cgs.converged ? "PASS" : "FAIL"));
505 PetscCall(PetscPrintf(PETSC_COMM_WORLD, " PRODUCTION_SOLVER_WRAPPER: %s\n",
506 (prod_ierr == 0 && simCtx->mom_last_converged) ? "PASS" : "FAIL"));
507 PetscCall(PetscPrintf(PETSC_COMM_WORLD, " THREE_PASS_24_PASS_COMPATIBLE: %s\n",
508 (f24_2 <= compat_tol) ? "PASS" : "FAIL"));
509 PetscCall(PetscPrintf(PETSC_COMM_WORLD, " PROJECTION_DIVERGENCE_FREE: %s\n",
510 (max_div <= div_tol) ? "PASS" : "FAIL"));
511
512 /* -------- assertions -------- */
513 PetscCheck(imm_inf <= purity_inf_tol && imm_2 <= purity_2_tol, PETSC_COMM_WORLD, PETSC_ERR_PLIB,
514 "production residual not immediately repeatable (inf=%g l2=%g)", (double)imm_inf, (double)imm_2);
515 PetscCheck(mffd_inf <= purity_inf_tol && mffd_2 <= purity_2_tol, PETSC_COMM_WORLD, PETSC_ERR_PLIB,
516 "production residual changed after MFFD products (inf=%g l2=%g)", (double)mffd_inf, (double)mffd_2);
517 PetscCheck(res_cgs.converged, PETSC_COMM_WORLD, PETSC_ERR_PLIB,
518 "full step-2 solve (default GS) did not converge (reason=%d)", (int)res_cgs.reason);
519 PetscCheck(res_cgs.reason != SNES_DIVERGED_LINE_SEARCH && res_cgs.reason != SNES_DIVERGED_LINEAR_SOLVE,
520 PETSC_COMM_WORLD, PETSC_ERR_PLIB, "full step-2 solve diverged via line search or linear solve (reason=%d)",
521 (int)res_cgs.reason);
522 PetscCheck(prod_ierr == 0 && simCtx->mom_last_converged, PETSC_COMM_WORLD, PETSC_ERR_PLIB,
523 "production Newton-Krylov solver wrapper did not converge (ierr=%d)", (int)prod_ierr);
524 PetscCheck(f24_2 <= compat_tol, PETSC_COMM_WORLD, PETSC_ERR_PLIB,
525 "converged three-pass solution does not satisfy the 24-pass residual (||F24||2=%g)", (double)f24_2);
526 PetscCheck(max_div <= div_tol, PETSC_COMM_WORLD, PETSC_ERR_PLIB,
527 "projection of the converged momentum state is not divergence free (max_div=%g)", (double)max_div);
528
529 PetscCall(VecDestroy(&F24)); PetscCall(VecDestroy(&F3));
530 PetscCall(VecDestroy(&Xfinal)); PetscCall(VecDestroy(&res_cgs.Xfinal));
531 PetscCall(VecDestroy(&jv)); PetscCall(VecDestroy(&v));
532 PetscCall(VecDestroy(&Fafter)); PetscCall(VecDestroy(&F1)); PetscCall(VecDestroy(&F0));
533 PetscCall(VecDestroy(&X1)); PetscCall(VecDestroy(&X0));
534 PetscCall(FpDestroy(&entry));
535 PetscCall(VecDestroy(&user->Rhs));
536 PetscCall(PicurvDestroyRuntimeContext(&simCtx));
537 PetscCall(PicurvRemoveTempDir(tmpdir));
538 PetscFunctionReturn(PETSC_SUCCESS);
539}
540
541/** @brief Runs the opt-in Newton--Krylov production regression executable. */
542int main(int argc, char **argv)
543{
544 PetscErrorCode ierr;
545 const PicurvTestCase cases[] = {
546 {"newton-krylov-production-regression", TestNewtonKrylovProductionRegression}
547 };
548
549 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv Newton-Krylov production regression");
550 if (ierr) return (int)ierr;
551 ierr = PicurvRunTests("unit-momentum-newton-boundary-fixedpoint", cases, 1);
552 if (PetscFinalize()) return 1;
553 return (int)ierr;
554}
PetscErrorCode SynchronizePeriodicStaggeredFields(UserCtx *user, PetscInt num_fields, const char *field_names[])
Synchronizes persistent component-staggered vector fields.
PetscErrorCode ApplyBoundaryConditions(UserCtx *user)
Main boundary-condition orchestrator executed during solver timestepping.
PetscErrorCode SynchronizePeriodicCellFields(UserCtx *user, PetscInt num_fields, const char *field_names[])
Synchronizes periodic endpoint cells for a list of cell-centered fields.
PetscErrorCode InitializeEulerianState(SimCtx *simCtx)
High-level orchestrator to set the complete initial state of the Eulerian solver.
static PetscErrorCode MomentumNewtonKrylov_ApplyConstraints(MomentumNewtonKrylovContext *ctx, Vec X, Vec F)
Replaces every non-independent residual row with an explicit equation.
static PetscErrorCode MomentumNewtonKrylov_FormResidual(SNES snes, Vec X, Vec F, void *ctx)
Adapts a PETSc trial vector to the existing momentum residual path.
PetscErrorCode ComputeTotalResidual(UserCtx *user)
Computes the shared spatial-plus-BDF momentum residual in user->Rhs.
PetscErrorCode Projection(UserCtx *user)
Corrects the contravariant velocity field Ucont to be divergence-free using the gradient of the press...
Definition poisson.c:328
PetscErrorCode UpdatePressure(UserCtx *user)
Updates the pressure field P with the pressure correction Phi computed by the Poisson solver.
Definition poisson.c:855
PetscErrorCode PoissonSolver_MG(UserMG *usermg)
Solves the pressure-Poisson equation using a geometric multigrid method.
Definition poisson.c:3149
PetscErrorCode UpdateSolverHistoryVectors(UserCtx *user)
Copies the current time step's solution fields into history vectors (e.g., U(t_n) -> U_o,...
Definition runloop.c:321
PetscErrorCode Contra2Cart(UserCtx *user)
Reconstructs Cartesian velocity (Ucat) at cell centers from contravariant velocity (Ucont) defined on...
Definition setup.c:2746
PetscErrorCode ComputeDivergence(UserCtx *user)
Computes the discrete divergence of the contravariant velocity field.
Definition setup.c:3135
PetscErrorCode UpdateLocalGhosts(UserCtx *user, const char *fieldName)
Updates the local vector (including ghost points) from its corresponding global vector.
Definition setup.c:1755
PetscErrorCode FlowSolver(SimCtx *simCtx)
Orchestrates a single time step of the Eulerian fluid solver.
Definition solvers.c:11
static PetscErrorCode StopAfterOne(SNES snes, PetscInt it, PetscReal xnorm, PetscReal gnorm, PetscReal fnorm, SNESConvergedReason *reason, void *vctx)
Stops SNES immediately after its first accepted Newton correction.
static PetscErrorCode FpDestroy(FpSnapshot *snap)
Releases all vectors owned by one persistent-state snapshot.
static PetscErrorCode FpCapture(UserCtx *user, FpSnapshot *snap)
Deep-copies the audited persistent state into a test-owned snapshot.
int main(int argc, char **argv)
Runs the opt-in Newton–Krylov production regression executable.
static void FpGetVectors(UserCtx *user, Vec field[16])
Collects the persistent vectors observed by the residual/boundary path.
static PetscErrorCode CollectiveDifference(Vec a, Vec b, PetscReal *norm_inf, PetscReal *norm_2)
Computes collective global difference norms for global or rank-local vectors.
static PetscErrorCode ConfigureNewtonOptions(void)
Installs the production Newton, line-search, and GMRES options.
static PetscErrorCode TestNewtonKrylovProductionRegression(void)
Builds the production-sized duct, advances the true physical step 1, and exercises purity,...
static PetscErrorCode CreateProductionSNES(UserCtx *user, MomentumNewtonKrylovContext *pctx, PetscBool use_mgs, SNES *snes, Mat *J, KSP *ksp)
Creates a test-owned matrix-free SNES driven by the production residual callback.
static PetscErrorCode SolveMonitor(SNES snes, PetscInt it, PetscReal norm, void *vctx)
Records per-Newton nonlinear norm, KSP iterations/reason, and accepted lambda.
static PetscErrorCode FpRestore(UserCtx *user, const FpSnapshot *snap)
Restores a previously captured persistent-state snapshot.
static PetscErrorCode SeedCartesianFromX(UserCtx *user, Vec X)
Reconstructs Ucat/lUcat deterministically from X, mirroring the seed now performed by the production ...
static PetscErrorCode DifferenceMaxRank(Vec a, Vec b, PetscMPIInt *max_rank)
Finds the MPI rank containing the largest local entry difference.
static void FpGetScalars(UserCtx *user, PetscReal scalar[10])
Collects the persistent scalar diagnostics maintained by boundary processing.
static PetscErrorCode RunProductionFullSolve(UserCtx *user, const FpSnapshot *entry, Vec X0, PetscBool use_mgs, const char *label, SolveResult *res)
Runs one complete production-callback step-2 solve from the restored entry state.
static PetscErrorCode ResidualNCalls(MomentumNewtonKrylovContext *ctx, Vec X, PetscInt boundary_calls, Vec F)
Deterministic residual at X using an explicit number of boundary-condition calls (each three internal...
PetscErrorCode PicurvBuildMomentumPurityRuntimeContext(const char *bcs_contents, SimCtx **simCtx_out, UserCtx **user_out, char *tmpdir, size_t tmpdir_len)
Builds the production-sized straight-duct fixture used only by the opt-in Newton residual-purity diag...
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 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.
PetscReal FarFluxInSum
Definition variables.h:777
PetscReal FarFluxOutSum
Definition variables.h:777
Vec Rhs
Definition variables.h:912
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:879
PetscReal FluxOutSum
Definition variables.h:777
PetscReal boundaryVelocityCorrection
Definition variables.h:782
UserMG usermg
Definition variables.h:821
PetscBool mom_last_converged
Definition variables.h:738
Vec lUcont_rm1
Definition variables.h:912
PetscReal dt
Definition variables.h:699
PetscReal bulkVelocityCorrection
Definition variables.h:781
Vec Ucont
Definition variables.h:904
Vec Ubcs
Physical Cartesian velocity at boundary faces. Full 3D array but only boundary-face entries are meani...
Definition variables.h:121
PetscReal MaxDiv
Definition variables.h:828
BCS Bcs
Definition variables.h:899
Vec lUcont_o
Definition variables.h:911
Vec Ucat_o
Definition variables.h:911
PetscReal FluxInSum
Definition variables.h:777
Vec Ucat
Definition variables.h:904
Vec Ucont_o
Definition variables.h:911
Vec CellFieldAtCorner
Definition variables.h:915
PetscReal Fluxsum
Definition variables.h:777
Vec Ucont_rm1
Definition variables.h:912
Vec lUcont
Definition variables.h:904
PetscInt step
Definition variables.h:692
PetscReal AreaOutSum
Definition variables.h:783
Vec lUcat
Definition variables.h:904
PetscReal FluxIntpSum
Definition variables.h:901
PetscReal ti
Definition variables.h:693
PetscReal AreaInSum
Definition variables.h:783
PetscReal summationRHS
Definition variables.h:827
Vec P_o
Definition variables.h:911
Vec Uch
Characteristic velocity for boundary conditions.
Definition variables.h:122
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