PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
test_momentum_convective_candidates.c
Go to the documentation of this file.
1/**
2 * @file test_momentum_convective_candidates.c
3 * @brief A4a focused convective-candidate study (states A-C only).
4 *
5 * Builds the finite-difference Jacobian of the ACTUAL production convection-only residual
6 * (ComputeRHS with inviscid, P=0, centered, periodic, single block) on a tiny periodic
7 * Cartesian grid, then compares the B/C/D estimator candidates against rho(J), sigma_max(J),
8 * the exact 4-stage RK matrix polynomial P(z)=1+z+z^2/2+z^3/6+z^4/24, and a direct anchored
9 * 4-stage perturbation cross-check. Shadow-only: changes no production default.
10 *
11 * States: A uniform divergence-free; B nonzero discrete divergence; C divergence-free shear.
12 */
13
14#include "test_support.h"
15#include "momentumsolvers.h"
16#include "rhs.h"
17#include "setup.h"
18#include "Boundaries.h"
19#include <petscblaslapack.h>
20#include <math.h>
21#include <stdio.h>
22#include <string.h>
23
24/* ----------------------------------------------------------------------------------- *
25 * Periodic independent staggered-DOF map. *
26 * ----------------------------------------------------------------------------------- */
27typedef struct { PetscInt n, expected_n; PetscInt *comp, *ci, *cj, *ck; } DofMap;
28typedef struct {
29 PetscReal declared[3];
30 PetscReal ucat_global[3];
31 PetscReal ucat_ghost[3];
32 PetscReal ucont_global[3];
34typedef struct { PetscReal n2, ninf, checksum; } GlobalVecStats;
35
36static char g_ref_path[PETSC_MAX_PATH_LEN] = "";
37static char g_ref_token[128] = "";
38static PetscBool g_ref_path_set = PETSC_FALSE;
39static PetscBool g_ref_token_set = PETSC_FALSE;
40
47
48typedef struct {
50 PetscReal cfl;
52
53/**
54 * @brief Returns the number of independent periodic representatives in one direction.
55 */
56static inline PetscInt PeriodicRepCount(PetscInt npts) { return npts - 2; }
57
58/**
59 * @brief Counts all independent component-staggered representatives used by ComputeRHS.
60 */
61static inline PetscInt DofMapExpectedCount(DMDALocalInfo info)
62{
63 return 3 * PeriodicRepCount(info.mx) * PeriodicRepCount(info.my) * PeriodicRepCount(info.mz);
64}
65
66/**
67 * @brief Builds the serial periodic independent face-DOF map used by dense Jacobians.
68 *
69 * Production periodic synchronization copies global plane 0 from mx-2 and plane mx-1
70 * from 1 (and analogously in y/z), so representatives 1..m-2 contain each independent
71 * component-staggered Ucont face DOF exactly once. Perturbations only touch these reps;
72 * EvalConvResidual() then calls SynchronizePeriodicStaggeredFields() to update duplicates.
73 */
74static PetscErrorCode DofMapBuild(UserCtx *user, DofMap *map)
75{
76 DMDALocalInfo info = user->info;
77 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
78 const PetscInt lxs = 1, lxe = mx-1, lys = 1, lye = my-1, lzs = 1, lze = mz-1;
79 PetscInt cnt = 0;
80 PetscFunctionBeginUser;
82 map->n = (lxe-lxs)*(lye-lys)*(lze-lzs)*3;
83 PetscCall(PetscMalloc4(map->n, &map->comp, map->n, &map->ci, map->n, &map->cj, map->n, &map->ck));
84 for (PetscInt k = lzs; k < lze; k++)
85 for (PetscInt j = lys; j < lye; j++)
86 for (PetscInt i = lxs; i < lxe; i++)
87 for (PetscInt c = 0; c < 3; c++) {
88 map->comp[cnt] = c; map->ci[cnt] = i; map->cj[cnt] = j; map->ck[cnt] = k; cnt++;
89 }
90 PetscCheck(map->n == map->expected_n, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ,
91 "periodic independent DOF count mismatch: got %" PetscInt_FMT ", expected %" PetscInt_FMT,
92 map->n, map->expected_n);
93 PetscFunctionReturn(0);
94}
95
96/**
97 * @brief Builds the rank-owned periodic independent face-DOF map for MPI checks.
98 */
99static PetscErrorCode DofMapBuildOwned(UserCtx *user, DofMap *map)
100{
101 DMDALocalInfo info = user->info;
102 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
103 const PetscInt xs = info.xs, xe = info.xs + info.xm;
104 const PetscInt ys = info.ys, ye = info.ys + info.ym;
105 const PetscInt zs = info.zs, ze = info.zs + info.zm;
106 const PetscInt lxs = PetscMax(xs, 1), lxe = PetscMin(xe, mx-1);
107 const PetscInt lys = PetscMax(ys, 1), lye = PetscMin(ye, my-1);
108 const PetscInt lzs = PetscMax(zs, 1), lze = PetscMin(ze, mz-1);
109 PetscInt cnt = 0;
110 PetscFunctionBeginUser;
111 map->expected_n = DofMapExpectedCount(info);
112 map->n = PetscMax(0,lxe-lxs)*PetscMax(0,lye-lys)*PetscMax(0,lze-lzs)*3;
113 PetscCall(PetscMalloc4(map->n, &map->comp, map->n, &map->ci, map->n, &map->cj, map->n, &map->ck));
114 for (PetscInt k = lzs; k < lze; k++)
115 for (PetscInt j = lys; j < lye; j++)
116 for (PetscInt i = lxs; i < lxe; i++)
117 for (PetscInt c = 0; c < 3; c++) {
118 map->comp[cnt] = c; map->ci[cnt] = i; map->cj[cnt] = j; map->ck[cnt] = k; cnt++;
119 }
120 PetscFunctionReturn(0);
121}
122
123/**
124 * @brief Releases storage owned by an active-DOF map.
125 */
126static PetscErrorCode DofMapDestroy(DofMap *map)
127{ PetscFunctionBeginUser; PetscCall(PetscFree4(map->comp, map->ci, map->cj, map->ck)); PetscFunctionReturn(0); }
128
129/* component accessor: Cmpnts is 3 contiguous PetscReal (x,y,z) in the real build. */
130static inline PetscReal CmpGet(Cmpnts c, PetscInt comp) { const PetscReal *p = (const PetscReal*)&c; return p[comp]; }
131
132/* ----------------------------------------------------------------------------------- *
133 * Deterministic convection-only residual wrapper using the real production path. *
134 * ----------------------------------------------------------------------------------- */
135static PetscErrorCode EvalConvResidual(UserCtx *user, Vec Ucont_in, Vec Rhs, const DofMap *map, PetscReal *Ract)
136{
137 Cmpnts ***r;
138 PetscFunctionBeginUser;
139 PetscCall(VecCopy(Ucont_in, user->Ucont));
140 {
141 const char *fld[] = {"Ucont"};
142 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fld)); /* global->local + periodic */
143 }
144 PetscCall(ComputeRHS(user, Rhs)); /* Contra2Cart + Convection + mapping */
145 PetscCall(DMDAVecGetArrayRead(user->fda, Rhs, &r));
146 for (PetscInt m = 0; m < map->n; m++)
147 Ract[m] = CmpGet(r[map->ck[m]][map->cj[m]][map->ci[m]], map->comp[m]);
148 PetscCall(DMDAVecRestoreArrayRead(user->fda, Rhs, &r));
149 PetscFunctionReturn(0);
150}
151
152/* perturb one active global Ucont DOF (then caller re-syncs through EvalConvResidual). */
153static PetscErrorCode PerturbDof(UserCtx *user, Vec Ucont, const DofMap *map, PetscInt m, PetscReal delta)
154{
155 Cmpnts ***a;
156 PetscFunctionBeginUser;
157 PetscCall(DMDAVecGetArray(user->fda, Ucont, &a));
158 { PetscReal *p = (PetscReal*)&a[map->ck[m]][map->cj[m]][map->ci[m]]; p[map->comp[m]] += delta; }
159 PetscCall(DMDAVecRestoreArray(user->fda, Ucont, &a));
160 PetscFunctionReturn(0);
161}
162
163/**
164 * @brief Reads one active contravariant component from a global vector.
165 */
166static PetscErrorCode GetDof(UserCtx *user, Vec Ucont, const DofMap *map, PetscInt m, PetscReal *val)
167{
168 Cmpnts ***a;
169 PetscFunctionBeginUser;
170 PetscCall(DMDAVecGetArray(user->fda, Ucont, &a));
171 { const PetscReal *p = (const PetscReal*)&a[map->ck[m]][map->cj[m]][map->ci[m]]; *val = p[map->comp[m]]; }
172 PetscCall(DMDAVecRestoreArray(user->fda, Ucont, &a));
173 PetscFunctionReturn(0);
174}
175
176/* ----------------------------------------------------------------------------------- *
177 * Base-state construction: physical Cartesian velocity -> production contravariant. *
178 * ----------------------------------------------------------------------------------- */
180
181/**
182 * @brief Returns the cell-centered periodic angle using duplicated endpoint planes.
183 */
184static inline PetscReal PeriodicCellAngle(PetscInt idx, PetscInt npts)
185{
186 const PetscInt nuniq = npts - 1;
187 const PetscInt ip = (idx == npts - 1) ? 0 : idx;
188 return 2.0*PETSC_PI*((PetscReal)ip)/((PetscReal)nuniq);
189}
190
191/**
192 * @brief Returns a face-representative periodic angle for component-staggered Ucont.
193 */
194static inline PetscReal PeriodicFaceAngle(PetscInt idx, PetscInt npts)
195{
196 const PetscInt nuniq = PeriodicRepCount(npts);
197 PetscInt ip = idx - 1;
198 if (idx == 0) ip = nuniq - 1;
199 else if (idx == npts - 1) ip = 0;
200 return 2.0*PETSC_PI*((PetscReal)ip)/((PetscReal)nuniq);
201}
202
203/**
204 * @brief Evaluates one of the three analytic Cartesian candidate states.
205 */
206static inline Cmpnts AnalyticVelocity(CandState st, PetscInt i, PetscInt j, PetscInt k,
207 PetscInt mx, PetscInt my, PetscInt mz)
208{
209 Cmpnts v;
210 const PetscReal x = PeriodicCellAngle(i, mx);
211 const PetscReal y = PeriodicCellAngle(j, my);
212 (void)k; (void)mz;
213 if (st == STATE_A) {
214 v.x = 0.7; v.y = -0.4; v.z = 0.0;
215 } else if (st == STATE_A_X) {
216 v.x = 0.7; v.y = 0.0; v.z = 0.0;
217 } else if (st == STATE_A_Y) {
218 v.x = 0.0; v.y = -0.4; v.z = 0.0;
219 } else if (st == STATE_B) {
220 (void)x; v.x = 0.0; v.y = 0.0; v.z = 0.0;
221 } else {
222 v.x = 1.0 + 0.5*PetscSinReal(y); v.y = 0.0; v.z = 0.0;
223 }
224 return v;
225}
226
227/**
228 * @brief Evaluates the declared direct component-staggered State B Ucont field.
229 */
230static inline Cmpnts DirectUcontVelocity(CandState st, PetscInt i, PetscInt j, PetscInt k,
231 PetscInt mx, PetscInt my, PetscInt mz)
232{
233 Cmpnts v = {0.0, 0.0, 0.0};
234 (void)j; (void)k; (void)my; (void)mz;
235 if (st == STATE_B) v.x = PetscSinReal(PeriodicFaceAngle(i, mx));
236 return v;
237}
238
239/**
240 * @brief Computes the componentwise infinity norm of the difference between two vectors.
241 */
242static inline PetscReal CmpDiffInf(Cmpnts a, Cmpnts b)
243{
244 return PetscMax(PetscAbsReal(a.x-b.x), PetscMax(PetscAbsReal(a.y-b.y), PetscAbsReal(a.z-b.z)));
245}
246
247/**
248 * @brief Computes analytic periodic seam mismatches for each coordinate direction.
249 */
250static PetscErrorCode ComputeDeclaredSeamMismatch(CandState st, DMDALocalInfo info, PetscReal seam[3])
251{
252 PetscReal loc[3] = {0.0, 0.0, 0.0}, glo[3];
253 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
254 PetscFunctionBeginUser;
255 if (st == STATE_B) {
256 for (PetscInt k = 1; k < mz-1; k++)
257 for (PetscInt j = 1; j < my-1; j++) {
258 loc[0] = PetscMax(loc[0], CmpDiffInf(DirectUcontVelocity(st, 0, j, k, mx, my, mz),
259 DirectUcontVelocity(st, mx-2, j, k, mx, my, mz)));
260 loc[0] = PetscMax(loc[0], CmpDiffInf(DirectUcontVelocity(st, mx-1, j, k, mx, my, mz),
261 DirectUcontVelocity(st, 1, j, k, mx, my, mz)));
262 }
263 for (PetscInt k = 1; k < mz-1; k++)
264 for (PetscInt i = 1; i < mx-1; i++) {
265 loc[1] = PetscMax(loc[1], CmpDiffInf(DirectUcontVelocity(st, i, 0, k, mx, my, mz),
266 DirectUcontVelocity(st, i, my-2, k, mx, my, mz)));
267 loc[1] = PetscMax(loc[1], CmpDiffInf(DirectUcontVelocity(st, i, my-1, k, mx, my, mz),
268 DirectUcontVelocity(st, i, 1, k, mx, my, mz)));
269 }
270 for (PetscInt j = 1; j < my-1; j++)
271 for (PetscInt i = 1; i < mx-1; i++) {
272 loc[2] = PetscMax(loc[2], CmpDiffInf(DirectUcontVelocity(st, i, j, 0, mx, my, mz),
273 DirectUcontVelocity(st, i, j, mz-2, mx, my, mz)));
274 loc[2] = PetscMax(loc[2], CmpDiffInf(DirectUcontVelocity(st, i, j, mz-1, mx, my, mz),
275 DirectUcontVelocity(st, i, j, 1, mx, my, mz)));
276 }
277 } else {
278 for (PetscInt k = 0; k < mz; k++)
279 for (PetscInt j = 0; j < my; j++)
280 loc[0] = PetscMax(loc[0], CmpDiffInf(AnalyticVelocity(st, 0, j, k, mx, my, mz),
281 AnalyticVelocity(st, mx-1, j, k, mx, my, mz)));
282 for (PetscInt k = 0; k < mz; k++)
283 for (PetscInt i = 0; i < mx; i++)
284 loc[1] = PetscMax(loc[1], CmpDiffInf(AnalyticVelocity(st, i, 0, k, mx, my, mz),
285 AnalyticVelocity(st, i, my-1, k, mx, my, mz)));
286 for (PetscInt j = 0; j < my; j++)
287 for (PetscInt i = 0; i < mx; i++)
288 loc[2] = PetscMax(loc[2], CmpDiffInf(AnalyticVelocity(st, i, j, 0, mx, my, mz),
289 AnalyticVelocity(st, i, j, mz-1, mx, my, mz)));
290 }
291 PetscCallMPI(MPI_Allreduce(loc, glo, 3, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD));
292 seam[0] = glo[0]; seam[1] = glo[1]; seam[2] = glo[2];
293 PetscFunctionReturn(0);
294}
295
296/**
297 * @brief Reports whether a global index is present in a rank's local ghosted range.
298 */
299static inline PetscBool InGhostRange(PetscInt idx, PetscInt lo, PetscInt n)
300{ return (PetscBool)(idx >= lo && idx < lo + n); }
301
302/**
303 * @brief Computes duplicate-plane mismatch in a local vector view.
304 */
305static PetscErrorCode ComputeLocalDuplicateMismatch(UserCtx *user, Vec local, PetscReal seam[3])
306{
307 DMDALocalInfo info = user->info;
308 Cmpnts ***a;
309 PetscReal loc[3] = {0.0, 0.0, 0.0}, glo[3];
310 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
311 PetscFunctionBeginUser;
312 PetscCall(DMDAVecGetArrayRead(user->fda, local, &a));
313#define HAVE_I(ii) InGhostRange((ii), info.gxs, info.gxm)
314#define HAVE_J(jj) InGhostRange((jj), info.gys, info.gym)
315#define HAVE_K(kk) InGhostRange((kk), info.gzs, info.gzm)
316 if (HAVE_I(0) && HAVE_I(mx-2))
317 for (PetscInt k = 1; k < mz-1; k++) if (HAVE_K(k))
318 for (PetscInt j = 1; j < my-1; j++) if (HAVE_J(j))
319 loc[0] = PetscMax(loc[0], CmpDiffInf(a[k][j][0], a[k][j][mx-2]));
320 if (HAVE_I(mx-1) && HAVE_I(1))
321 for (PetscInt k = 1; k < mz-1; k++) if (HAVE_K(k))
322 for (PetscInt j = 1; j < my-1; j++) if (HAVE_J(j))
323 loc[0] = PetscMax(loc[0], CmpDiffInf(a[k][j][mx-1], a[k][j][1]));
324 if (HAVE_J(0) && HAVE_J(my-2))
325 for (PetscInt k = 1; k < mz-1; k++) if (HAVE_K(k))
326 for (PetscInt i = 1; i < mx-1; i++) if (HAVE_I(i))
327 loc[1] = PetscMax(loc[1], CmpDiffInf(a[k][0][i], a[k][my-2][i]));
328 if (HAVE_J(my-1) && HAVE_J(1))
329 for (PetscInt k = 1; k < mz-1; k++) if (HAVE_K(k))
330 for (PetscInt i = 1; i < mx-1; i++) if (HAVE_I(i))
331 loc[1] = PetscMax(loc[1], CmpDiffInf(a[k][my-1][i], a[k][1][i]));
332 if (HAVE_K(0) && HAVE_K(mz-2))
333 for (PetscInt j = 1; j < my-1; j++) if (HAVE_J(j))
334 for (PetscInt i = 1; i < mx-1; i++) if (HAVE_I(i))
335 loc[2] = PetscMax(loc[2], CmpDiffInf(a[0][j][i], a[mz-2][j][i]));
336 if (HAVE_K(mz-1) && HAVE_K(1))
337 for (PetscInt j = 1; j < my-1; j++) if (HAVE_J(j))
338 for (PetscInt i = 1; i < mx-1; i++) if (HAVE_I(i))
339 loc[2] = PetscMax(loc[2], CmpDiffInf(a[mz-1][j][i], a[1][j][i]));
340#undef HAVE_I
341#undef HAVE_J
342#undef HAVE_K
343 PetscCall(DMDAVecRestoreArrayRead(user->fda, local, &a));
344 PetscCallMPI(MPI_Allreduce(loc, glo, 3, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD));
345 seam[0] = glo[0]; seam[1] = glo[1]; seam[2] = glo[2];
346 PetscFunctionReturn(0);
347}
348
349/**
350 * @brief Computes outer periodic ghost mismatch for local Ucat.
351 */
352static PetscErrorCode ComputeLocalOuterGhostMismatch(UserCtx *user, Vec local, PetscReal seam[3])
353{
354 DMDALocalInfo info = user->info;
355 Cmpnts ***a;
356 PetscReal loc[3] = {0.0, 0.0, 0.0}, glo[3];
357 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
358 PetscFunctionBeginUser;
359 PetscCall(DMDAVecGetArrayRead(user->fda, local, &a));
360#define HAVE_I(ii) InGhostRange((ii), info.gxs, info.gxm)
361#define HAVE_J(jj) InGhostRange((jj), info.gys, info.gym)
362#define HAVE_K(kk) InGhostRange((kk), info.gzs, info.gzm)
363 if (HAVE_I(-1) && HAVE_I(1))
364 for (PetscInt k = 1; k < mz-1; k++) if (HAVE_K(k))
365 for (PetscInt j = 1; j < my-1; j++) if (HAVE_J(j))
366 loc[0] = PetscMax(loc[0], CmpDiffInf(a[k][j][-1], a[k][j][1]));
367 if (HAVE_I(mx) && HAVE_I(mx-2))
368 for (PetscInt k = 1; k < mz-1; k++) if (HAVE_K(k))
369 for (PetscInt j = 1; j < my-1; j++) if (HAVE_J(j))
370 loc[0] = PetscMax(loc[0], CmpDiffInf(a[k][j][mx], a[k][j][mx-2]));
371 if (HAVE_J(-1) && HAVE_J(1))
372 for (PetscInt k = 1; k < mz-1; k++) if (HAVE_K(k))
373 for (PetscInt i = 1; i < mx-1; i++) if (HAVE_I(i))
374 loc[1] = PetscMax(loc[1], CmpDiffInf(a[k][-1][i], a[k][1][i]));
375 if (HAVE_J(my) && HAVE_J(my-2))
376 for (PetscInt k = 1; k < mz-1; k++) if (HAVE_K(k))
377 for (PetscInt i = 1; i < mx-1; i++) if (HAVE_I(i))
378 loc[1] = PetscMax(loc[1], CmpDiffInf(a[k][my][i], a[k][my-2][i]));
379 if (HAVE_K(-1) && HAVE_K(1))
380 for (PetscInt j = 1; j < my-1; j++) if (HAVE_J(j))
381 for (PetscInt i = 1; i < mx-1; i++) if (HAVE_I(i))
382 loc[2] = PetscMax(loc[2], CmpDiffInf(a[-1][j][i], a[1][j][i]));
383 if (HAVE_K(mz) && HAVE_K(mz-2))
384 for (PetscInt j = 1; j < my-1; j++) if (HAVE_J(j))
385 for (PetscInt i = 1; i < mx-1; i++) if (HAVE_I(i))
386 loc[2] = PetscMax(loc[2], CmpDiffInf(a[mz][j][i], a[mz-2][j][i]));
387#undef HAVE_I
388#undef HAVE_J
389#undef HAVE_K
390 PetscCall(DMDAVecRestoreArrayRead(user->fda, local, &a));
391 PetscCallMPI(MPI_Allreduce(loc, glo, 3, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD));
392 seam[0] = glo[0]; seam[1] = glo[1]; seam[2] = glo[2];
393 PetscFunctionReturn(0);
394}
395
396/**
397 * @brief Configures the minimal context for centered inviscid periodic convection tests.
398 */
399static PetscErrorCode ConfigureCandidateFixture(SimCtx *simCtx, UserCtx *user)
400{
401 PetscFunctionBeginUser;
402 for (int f = 0; f < 6; f++) user->boundary_faces[f].mathematical_type = PERIODIC;
403 simCtx->dt = 0.1; simCtx->step = 5; simCtx->StartStep = 0; /* BDF2 -> a0=1.5 */
404 simCtx->ren = 1.0e6; simCtx->invicid = 1; simCtx->les = 0; simCtx->rans = 0;
405 simCtx->central = 1; simCtx->clark = 0; simCtx->TwoD = 0; simCtx->block_number = 1;
406 simCtx->bulkVelocityCorrection = 0.0; simCtx->moveframe = 0; simCtx->rotateframe = 0;
407 if (!user->lNu_t) PetscCall(DMCreateLocalVector(user->da, &user->lNu_t));
408 PetscCall(VecSet(user->P, 0.0)); PetscCall(UpdateLocalGhosts(user, "P"));
409 PetscCall(VecSet(user->lNvert, 0.0)); PetscCall(VecSet(user->Nvert, 0.0));
410 PetscFunctionReturn(0);
411}
412
413/* set global Ucat to the analytic velocity for the state at cell-centre (i,j,k). */
414static PetscErrorCode SetUcatField(UserCtx *user, CandState st)
415{
416 Cmpnts ***u;
417 DMDALocalInfo info = user->info;
418 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
419 PetscFunctionBeginUser;
420 PetscCall(DMDAVecGetArray(user->fda, user->Ucat, &u));
421 /* only owned region for a GLOBAL vec */
422 for (PetscInt k = info.zs; k < info.zs+info.zm; k++)
423 for (PetscInt j = info.ys; j < info.ys+info.ym; j++)
424 for (PetscInt i = info.xs; i < info.xs+info.xm; i++) {
425 u[k][j][i] = AnalyticVelocity(st, i, j, k, mx, my, mz);
426 }
427 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucat, &u));
428 PetscFunctionReturn(0);
429}
430
431/**
432 * @brief Sets the direct State B component-staggered Ucont field on owned entries.
433 */
434static PetscErrorCode SetDirectUcontField(UserCtx *user, CandState st)
435{
436 Cmpnts ***u;
437 DMDALocalInfo info = user->info;
438 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
439 PetscFunctionBeginUser;
440 PetscCall(VecSet(user->Ucont, 0.0));
441 PetscCall(DMDAVecGetArray(user->fda, user->Ucont, &u));
442 for (PetscInt k = info.zs; k < info.zs+info.zm; k++)
443 for (PetscInt j = info.ys; j < info.ys+info.ym; j++)
444 for (PetscInt i = info.xs; i < info.xs+info.xm; i++)
445 u[k][j][i] = DirectUcontVelocity(st, i, j, k, mx, my, mz);
446 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucont, &u));
447 PetscFunctionReturn(0);
448}
449
450/**
451 * @brief Computes max discrete divergence of the current local Ucont field.
452 */
453static PetscErrorCode ComputeMaxDiscreteDivergence(UserCtx *user, PetscReal *maxdiv)
454{
455 DMDALocalInfo info = user->info;
456 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
457 Cmpnts ***uc;
458 PetscReal dv = 0.0, dv_global;
459 PetscFunctionBeginUser;
460 PetscCall(UpdateLocalGhosts(user, "Ucont"));
461 PetscCall(DMDAVecGetArrayRead(user->fda, user->lUcont, &uc));
462 for (PetscInt k = info.zs; k < info.zs+info.zm; k++)
463 for (PetscInt j = info.ys; j < info.ys+info.ym; j++)
464 for (PetscInt i = info.xs; i < info.xs+info.xm; i++) {
465 if (i<1||i>mx-2||j<1||j>my-2||k<1||k>mz-2) continue;
466 const PetscReal d = (uc[k][j][i].x - uc[k][j][i-1].x)
467 + (uc[k][j][i].y - uc[k][j-1][i].y)
468 + (uc[k][j][i].z - uc[k-1][j][i].z);
469 dv = PetscMax(dv, PetscAbsReal(d));
470 }
471 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lUcont, &uc));
472 PetscCallMPI(MPI_Allreduce(&dv, &dv_global, 1, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD));
473 *maxdiv = dv_global;
474 PetscFunctionReturn(0);
475}
476
477/* Build the base state. States A/C are declared in Cartesian space and converted through
478 Cart2Contra; State B is declared directly in synchronized component-staggered Ucont space. */
479static PetscErrorCode BuildBaseState(UserCtx *user, CandState st, Vec Ubase,
480 PetscReal *repeat_inf, PetscReal *maxdiv,
481 SeamDiagnostics *seam)
482{
483 DMDALocalInfo info = user->info;
484 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
485 PetscReal err = 0.0, err_global;
486 Vec target;
487 Cmpnts ***ur, ***ut;
488 PetscFunctionBeginUser;
489
490 PetscCall(VecDuplicate(user->Ucat, &target));
491
492 if (seam) PetscCall(ComputeDeclaredSeamMismatch(st, info, seam->declared));
493
494 if (st == STATE_B) {
495 const char *ufld[] = {"Ucont"};
496 const char *cfld[] = {"Ucat"};
497 PetscCall(SetDirectUcontField(user, st));
498 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, ufld));
499 PetscCall(VecCopy(user->Ucont, Ubase));
500 if (seam) PetscCall(ComputeLocalDuplicateMismatch(user, user->lUcont, seam->ucont_global));
501 PetscCall(Contra2Cart(user));
502 PetscCall(SynchronizePeriodicCellFields(user, 1, cfld));
503 PetscCall(VecCopy(user->Ucat, target)); /* saved recovered Cartesian state */
504 PetscCall(Contra2Cart(user));
505 PetscCall(SynchronizePeriodicCellFields(user, 1, cfld));
506 } else {
507 const char *cfld[] = {"Ucat"};
508 const char *ufld[] = {"Ucont"};
509 PetscCall(SetUcatField(user, st));
510 PetscCall(SynchronizePeriodicCellFields(user, 1, cfld));
511 PetscCall(VecCopy(user->Ucat, target)); /* saved declared Cartesian state */
512 PetscCall(Cart2Contra(user)); /* global Ucont from lUcat + metrics */
513 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, ufld));
514 PetscCall(VecCopy(user->Ucont, Ubase));
515 if (seam) PetscCall(ComputeLocalDuplicateMismatch(user, user->lUcont, seam->ucont_global));
516 PetscCall(Contra2Cart(user)); /* recovered Cartesian from Ucont */
517 PetscCall(SynchronizePeriodicCellFields(user, 1, cfld));
518 }
519
520 if (seam) {
521 PetscCall(UpdateLocalGhosts(user, "Ucat"));
522 PetscCall(ComputeLocalDuplicateMismatch(user, user->lUcat, seam->ucat_global));
523 PetscCall(ComputeLocalOuterGhostMismatch(user, user->lUcat, seam->ucat_ghost));
524 }
525
526 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucat, &ur));
527 PetscCall(DMDAVecGetArrayRead(user->fda, target, &ut));
528 for (PetscInt k = info.zs; k < info.zs+info.zm; k++)
529 for (PetscInt j = info.ys; j < info.ys+info.ym; j++)
530 for (PetscInt i = info.xs; i < info.xs+info.xm; i++) {
531 if (i<1||i>mx-2||j<1||j>my-2||k<1||k>mz-2) continue; /* interior cells only */
532 err = PetscMax(err, PetscAbsReal(ur[k][j][i].x - ut[k][j][i].x));
533 err = PetscMax(err, PetscAbsReal(ur[k][j][i].y - ut[k][j][i].y));
534 err = PetscMax(err, PetscAbsReal(ur[k][j][i].z - ut[k][j][i].z));
535 }
536 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucat, &ur));
537 PetscCall(DMDAVecRestoreArrayRead(user->fda, target, &ut));
538 PetscCall(VecDestroy(&target));
539 PetscCall(UpdateLocalGhosts(user, "Ucat")); /* lUcat now consistent with base Ucont */
540
541 PetscCallMPI(MPI_Allreduce(&err, &err_global, 1, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD));
542 PetscCall(ComputeMaxDiscreteDivergence(user, maxdiv));
543 *repeat_inf = err_global;
544 PetscFunctionReturn(0);
545}
546
547/**
548 * @brief Computes the global maximum Cartesian velocity-gradient row-sum used by Candidate D.
549 */
550static PetscErrorCode ComputeMaxGradientContribution(UserCtx *user, PetscReal *gradmax)
551{
552 DMDALocalInfo info = user->info;
553 Cmpnts ***ucat, ***csi, ***eta, ***zet;
554 PetscReal ***aj;
555 PetscReal loc = 0.0, glo;
556 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
557 PetscFunctionBeginUser;
558 PetscCall(UpdateLocalGhosts(user, "Ucat"));
559 PetscCall(DMDAVecGetArrayRead(user->fda, user->lUcat, &ucat));
560 PetscCall(DMDAVecGetArrayRead(user->fda, user->lCsi, &csi));
561 PetscCall(DMDAVecGetArrayRead(user->fda, user->lEta, &eta));
562 PetscCall(DMDAVecGetArrayRead(user->fda, user->lZet, &zet));
563 PetscCall(DMDAVecGetArrayRead(user->da, user->lAj, &aj));
564 for (PetscInt k = info.zs; k < info.zs+info.zm; k++)
565 for (PetscInt j = info.ys; j < info.ys+info.ym; j++)
566 for (PetscInt i = info.xs; i < info.xs+info.xm; i++) {
567 if (i<1||i>mx-2||j<1||j>my-2||k<1||k>mz-2) continue;
568 const Cmpnts duc = { 0.5*(ucat[k][j][i+1].x-ucat[k][j][i-1].x),
569 0.5*(ucat[k][j][i+1].y-ucat[k][j][i-1].y),
570 0.5*(ucat[k][j][i+1].z-ucat[k][j][i-1].z) };
571 const Cmpnts due = { 0.5*(ucat[k][j+1][i].x-ucat[k][j-1][i].x),
572 0.5*(ucat[k][j+1][i].y-ucat[k][j-1][i].y),
573 0.5*(ucat[k][j+1][i].z-ucat[k][j-1][i].z) };
574 const Cmpnts duz = { 0.5*(ucat[k+1][j][i].x-ucat[k-1][j][i].x),
575 0.5*(ucat[k+1][j][i].y-ucat[k-1][j][i].y),
576 0.5*(ucat[k+1][j][i].z-ucat[k-1][j][i].z) };
577 const Cmpnts C = csi[k][j][i], E = eta[k][j][i], Z = zet[k][j][i];
578 const PetscReal Ajc = aj[k][j][i];
579#define ROWSUM(cmp) ( \
580 PetscAbsReal(Ajc*(C.x*duc.cmp + E.x*due.cmp + Z.x*duz.cmp)) + \
581 PetscAbsReal(Ajc*(C.y*duc.cmp + E.y*due.cmp + Z.y*duz.cmp)) + \
582 PetscAbsReal(Ajc*(C.z*duc.cmp + E.z*due.cmp + Z.z*duz.cmp)) )
583 loc = PetscMax(loc, PetscMax(ROWSUM(x), PetscMax(ROWSUM(y), ROWSUM(z))));
584#undef ROWSUM
585 }
586 PetscCall(DMDAVecRestoreArrayRead(user->da, user->lAj, &aj));
587 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lZet, &zet));
588 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lEta, &eta));
589 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lCsi, &csi));
590 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lUcat, &ucat));
591 PetscCallMPI(MPI_Allreduce(&loc, &glo, 1, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD));
592 *gradmax = glo;
593 PetscFunctionReturn(0);
594}
595
596/* ----------------------------------------------------------------------------------- *
597 * Dense linear algebra on the (column-major) active Jacobian via PETSc LAPACK. *
598 * ----------------------------------------------------------------------------------- */
599/* rho(J): max |eigenvalue|. A is column-major n x n and is COPIED (dgeev overwrites). */
600static PetscErrorCode DenseSpectralRadius(const PetscReal *A, PetscInt n, PetscReal *rho,
601 PetscReal *maxRealPart)
602{
603 PetscBLASInt N, lda, lwork, info;
604 PetscReal *Acopy, *wr, *wi, *work, dummy = 0.0;
605 PetscFunctionBeginUser;
606 PetscCall(PetscBLASIntCast(n, &N)); lda = N; lwork = 8*N;
607 PetscCall(PetscMalloc4(n*n, &Acopy, n, &wr, n, &wi, (size_t)lwork, &work));
608 for (PetscInt t = 0; t < n*n; t++) Acopy[t] = A[t];
609 {
610 char nochar = 'N';
611 LAPACKgeev_(&nochar, &nochar, &N, Acopy, &lda, wr, wi, &dummy, &lda, &dummy, &lda, work, &lwork, &info);
612 }
613 PetscCheck(info == 0, PETSC_COMM_SELF, PETSC_ERR_LIB, "LAPACK dgeev failed: info=%d", (int)info);
614 *rho = 0.0; *maxRealPart = -PETSC_MAX_REAL;
615 for (PetscInt t = 0; t < n; t++) {
616 *rho = PetscMax(*rho, PetscSqrtReal(wr[t]*wr[t] + wi[t]*wi[t]));
617 *maxRealPart = PetscMax(*maxRealPart, wr[t]);
618 }
619 PetscCall(PetscFree4(Acopy, wr, wi, work));
620 PetscFunctionReturn(0);
621}
622
623/**
624 * @brief Extracts the right eigenpair whose eigenvalue has largest real part.
625 *
626 * For a complex pair, LAPACK stores real and imaginary parts in adjacent columns of VR.
627 */
628static PetscErrorCode DenseMaxRealRightEigenpair(const PetscReal *A, PetscInt n,
629 PetscReal *lamr, PetscReal *lami,
630 PetscReal *vr_out, PetscReal *vi_out)
631{
632 PetscBLASInt N, lda, lwork, info;
633 PetscReal *Acopy, *wr, *wi, *vr, *work, dummy = 0.0;
634 PetscFunctionBeginUser;
635 PetscCall(PetscBLASIntCast(n, &N)); lda = N; lwork = 16*N;
636 PetscCall(PetscMalloc5(n*n, &Acopy, n, &wr, n, &wi, n*n, &vr, (size_t)lwork, &work));
637 for (PetscInt t = 0; t < n*n; t++) Acopy[t] = A[t];
638 {
639 char jobvl = 'N', jobvr = 'V';
640 LAPACKgeev_(&jobvl, &jobvr, &N, Acopy, &lda, wr, wi, &dummy, &lda, vr, &lda, work, &lwork, &info);
641 }
642 PetscCheck(info == 0, PETSC_COMM_SELF, PETSC_ERR_LIB, "LAPACK dgeev failed: info=%d", (int)info);
643 PetscInt best = 0;
644 for (PetscInt t = 1; t < n; t++) if (wr[t] > wr[best]) best = t;
645 *lamr = wr[best]; *lami = wi[best];
646 if (PetscAbsReal(wi[best]) < 1e-14) {
647 for (PetscInt r = 0; r < n; r++) { vr_out[r] = vr[r + best*n]; vi_out[r] = 0.0; }
648 } else if (wi[best] > 0.0) {
649 for (PetscInt r = 0; r < n; r++) { vr_out[r] = vr[r + best*n]; vi_out[r] = vr[r + (best+1)*n]; }
650 } else {
651 for (PetscInt r = 0; r < n; r++) { vr_out[r] = vr[r + (best-1)*n]; vi_out[r] = -vr[r + best*n]; }
652 }
653 PetscCall(PetscFree5(Acopy, wr, wi, vr, work));
654 PetscFunctionReturn(0);
655}
656
657/**
658 * @brief Computes the spectral radius of the RK polynomial by applying it to eig(J).
659 */
660static PetscErrorCode DenseRKPolynomialSpectralRadius(const PetscReal *J, PetscInt n,
661 PetscReal dtau, PetscReal *rho)
662{
663 PetscBLASInt N, lda, lwork, info;
664 PetscReal *Jcopy, *wr, *wi, *work, dummy = 0.0;
665 PetscFunctionBeginUser;
666 PetscCall(PetscBLASIntCast(n, &N)); lda = N; lwork = 8*N;
667 PetscCall(PetscMalloc4(n*n, &Jcopy, n, &wr, n, &wi, (size_t)lwork, &work));
668 for (PetscInt t = 0; t < n*n; t++) Jcopy[t] = J[t];
669 {
670 char nochar = 'N';
671 LAPACKgeev_(&nochar, &nochar, &N, Jcopy, &lda, wr, wi, &dummy, &lda, &dummy, &lda, work, &lwork, &info);
672 }
673 PetscCheck(info == 0, PETSC_COMM_SELF, PETSC_ERR_LIB, "LAPACK dgeev failed: info=%d", (int)info);
674 *rho = 0.0;
675 for (PetscInt t = 0; t < n; t++) {
676 const PetscReal zr = dtau*wr[t], zi = dtau*wi[t];
677 const PetscReal z2r = zr*zr - zi*zi, z2i = 2.0*zr*zi;
678 const PetscReal z3r = z2r*zr - z2i*zi, z3i = z2r*zi + z2i*zr;
679 const PetscReal z4r = z3r*zr - z3i*zi, z4i = z3r*zi + z3i*zr;
680 const PetscReal pr = 1.0 + zr + 0.5*z2r + z3r/6.0 + z4r/24.0;
681 const PetscReal pi = zi + 0.5*z2i + z3i/6.0 + z4i/24.0;
682 *rho = PetscMax(*rho, PetscSqrtReal(pr*pr + pi*pi));
683 }
684 PetscCall(PetscFree4(Jcopy, wr, wi, work));
685 PetscFunctionReturn(0);
686}
687
688/* sigma_max(J) = ||J||_2 (and optionally the dominant right singular vector v1). */
689static PetscErrorCode DenseSigmaMax(const PetscReal *A, PetscInt n, PetscReal *smax, PetscReal *v1)
690{
691 PetscBLASInt N, lda, lwork, info;
692 PetscReal *Acopy, *S, *VT, *work, ufake = 0.0;
693 PetscFunctionBeginUser;
694 PetscCall(PetscBLASIntCast(n, &N)); lda = N; lwork = 8*N + 4*N;
695 PetscCall(PetscMalloc4(n*n, &Acopy, n, &S, n*n, &VT, (size_t)lwork, &work));
696 for (PetscInt t = 0; t < n*n; t++) Acopy[t] = A[t];
697 {
698 char jobu = 'N', jobvt = v1 ? 'S' : 'N';
699 LAPACKgesvd_(&jobu, &jobvt, &N, &N, Acopy, &lda, S, &ufake, &lda, VT, &lda, work, &lwork, &info);
700 }
701 PetscCheck(info == 0, PETSC_COMM_SELF, PETSC_ERR_LIB, "LAPACK dgesvd failed: info=%d", (int)info);
702 *smax = S[0];
703 if (v1) { for (PetscInt r = 0; r < n; r++) v1[r] = VT[0 + r*n]; } /* first row of VT = v1^T */
704 PetscCall(PetscFree4(Acopy, S, VT, work));
705 PetscFunctionReturn(0);
706}
707
708/* Frobenius non-normality ||J^T J - J J^T||_F / max(||J||_F^2, eps). */
709static PetscReal DenseNonNormality(const PetscReal *A, PetscInt n)
710{
711 PetscReal fro2 = 0.0, comm = 0.0;
712 for (PetscInt t = 0; t < n*n; t++) fro2 += A[t]*A[t];
713 for (PetscInt p = 0; p < n; p++)
714 for (PetscInt q = 0; q < n; q++) {
715 PetscReal ata = 0.0, aat = 0.0;
716 for (PetscInt r = 0; r < n; r++) { ata += A[p + r*n]*A[q + r*n]; aat += A[r + p*n]*A[r + q*n]; }
717 const PetscReal d = ata - aat; comm += d*d;
718 }
719 return PetscSqrtReal(comm) / PetscMax(fro2, PETSC_MACHINE_EPSILON);
720}
721
722/**
723 * @brief Computes the normalized Frobenius defect from skew symmetry.
724 */
725static PetscReal DenseSkewnessDefect(const PetscReal *A, PetscInt n)
726{
727 PetscReal fro2 = 0.0, sym2 = 0.0;
728 for (PetscInt i = 0; i < n*n; i++) fro2 += A[i]*A[i];
729 for (PetscInt c = 0; c < n; c++)
730 for (PetscInt r = 0; r < n; r++) {
731 const PetscReal s = A[r + c*n] + A[c + r*n];
732 sym2 += s*s;
733 }
734 return PetscSqrtReal(sym2) / PetscMax(PetscSqrtReal(fro2), PETSC_MACHINE_EPSILON);
735}
736
737/**
738 * @brief Prints eigenvalue and norm summary for one dense operator.
739 */
740static PetscErrorCode PrintSpectrumSummary(const char *name, const PetscReal *J, PetscInt n)
741{
742 PetscReal rho, maxre, smax;
743 PetscFunctionBeginUser;
744 PetscCall(DenseSpectralRadius(J, n, &rho, &maxre));
745 PetscCall(DenseSigmaMax(J, n, &smax, NULL));
746 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
747 " %-7s rho=%.6e sigma=%.6e max_real=%.6e skew=%.3e nonnormality=%.3e\n",
748 name, (double)rho, (double)smax, (double)maxre,
749 (double)DenseSkewnessDefect(J, n), (double)DenseNonNormality(J, n)));
750 PetscFunctionReturn(0);
751}
752
753static void DenseMatVec(const PetscReal *J, const PetscReal *x, PetscReal *y, PetscInt n);
754static void FillAuditDirection(const DofMap *map, PetscInt kind, PetscReal *x);
755static PetscErrorCode AddActiveVector(UserCtx *user, Vec U, const DofMap *map,
756 const PetscReal *x, PetscReal scale);
757
758/**
759 * @brief Copies a dense matrix and adds a scalar shift to its diagonal.
760 */
761static PetscErrorCode DenseShiftIdentity(const PetscReal *A, PetscInt n, PetscReal shift, PetscReal *B)
762{
763 PetscFunctionBeginUser;
764 PetscCall(PetscArraycpy(B, A, (size_t)n*n));
765 for (PetscInt d = 0; d < n; d++) B[d + d*n] += shift;
766 PetscFunctionReturn(0);
767}
768
769/* B = alpha*A (column-major) ; C = A*Bm ; returns into out (n x n). */
770static void MatMul(const PetscReal *A, const PetscReal *B, PetscReal *out, PetscInt n)
771{
772 for (PetscInt c = 0; c < n; c++)
773 for (PetscInt r = 0; r < n; r++) {
774 PetscReal s = 0.0;
775 for (PetscInt t = 0; t < n; t++) s += A[r + t*n]*B[t + c*n];
776 out[r + c*n] = s;
777 }
778}
779
780/* P(M)=I+M+M^2/2+M^3/6+M^4/24 via Horner: I+M(I+M/2(I+M/3(I+M/4))). M=dtau*J. */
781static PetscErrorCode RKPolynomial(const PetscReal *J, PetscReal dtau, PetscInt n, PetscReal *P)
782{
783 PetscReal *M, *T, *T2;
784 PetscFunctionBeginUser;
785 PetscCall(PetscMalloc3(n*n, &M, n*n, &T, n*n, &T2));
786 for (PetscInt t = 0; t < n*n; t++) M[t] = dtau*J[t];
787 /* start S = I + M/4 */
788 for (PetscInt t = 0; t < n*n; t++) T[t] = M[t]/4.0;
789 for (PetscInt d = 0; d < n; d++) T[d + d*n] += 1.0;
790 const PetscReal coef[3] = {3.0, 2.0, 1.0}; /* divide by 3, then 2, then 1 */
791 for (int s = 0; s < 3; s++) {
792 MatMul(M, T, T2, n); /* T2 = M*S */
793 for (PetscInt t = 0; t < n*n; t++) T[t] = T2[t]/coef[s];
794 for (PetscInt d = 0; d < n; d++) T[d + d*n] += 1.0; /* S = I + M/coef * S */
795 }
796 for (PetscInt t = 0; t < n*n; t++) P[t] = T[t];
797 PetscCall(PetscFree3(M, T, T2));
798 PetscFunctionReturn(0);
799}
800
802
803/**
804 * @brief Evaluates either spectral-radius or 2-norm amplification for one pseudo-time step.
805 */
806static PetscErrorCode AmplificationMetric(const PetscReal *J, PetscInt n, PetscReal dtau,
807 PMetric which, PetscReal *metric)
808{
809 PetscReal *Pm;
810 PetscFunctionBeginUser;
811 if (which == METRIC_RHO) {
812 PetscCall(DenseRKPolynomialSpectralRadius(J, n, dtau, metric));
813 PetscFunctionReturn(0);
814 }
815 PetscCall(PetscMalloc1(n*n, &Pm));
816 PetscCall(RKPolynomial(J, dtau, n, Pm));
817 PetscCall(DenseSigmaMax(Pm, n, metric, NULL));
818 PetscCall(PetscFree(Pm));
819 PetscFunctionReturn(0);
820}
821
822/* Stable interval connected to CFL=0; tolerance=1e-8, initial probe=1e-8, scan step=0.01, max=4.0. */
823static PetscErrorCode StableCFL(const PetscReal *J, PetscInt n, PetscReal lam, PMetric which,
824 StableCFLResult *result)
825{
826 const PetscReal tol = 1e-8, probe = 1e-8, scan_step = 0.01, hi = 4.0;
827 const PetscReal probe_tol = 1e-12, min_positive_cfl = 1e-6;
828 PetscReal met;
829 PetscFunctionBeginUser;
830 result->status = STABLE_CFL_NONE;
831 result->cfl = 0.0;
832 if (which == METRIC_RHO) {
833 PetscReal rhoJ, maxreJ;
834 PetscCall(DenseSpectralRadius(J, n, &rhoJ, &maxreJ));
835 if (maxreJ > 1e-8) PetscFunctionReturn(0);
836 }
837 PetscCall(AmplificationMetric(J, n, probe/lam, which, &met));
838 if (met > 1.0 + probe_tol) PetscFunctionReturn(0);
839
840 PetscReal stable = probe, cross_b = -1.0;
841 for (PetscReal cfl = scan_step; cfl <= hi + 1e-12; cfl += scan_step) {
842 PetscCall(AmplificationMetric(J, n, cfl/lam, which, &met));
843 if (met > 1.0 + tol) { cross_b = cfl; break; }
844 stable = cfl;
845 }
846 if (cross_b < 0.0) {
848 result->cfl = hi;
849 PetscFunctionReturn(0);
850 }
851 PetscReal cross_a = stable;
852 for (int it = 0; it < 40; it++) {
853 const PetscReal mid = 0.5*(cross_a + cross_b);
854 PetscCall(AmplificationMetric(J, n, mid/lam, which, &met));
855 if (met > 1.0 + tol) cross_b = mid; else cross_a = mid;
856 }
857 if (cross_a < min_positive_cfl) PetscFunctionReturn(0);
858 result->status = STABLE_CFL_FINITE;
859 result->cfl = cross_a;
860 PetscFunctionReturn(0);
861}
862
863/**
864 * @brief Returns human-readable text for a stable-CFL search result.
865 */
867{
868 if (r.status == STABLE_CFL_NONE) return "no positive stable interval connected to zero";
869 if (r.status == STABLE_CFL_EXCEEDS_SCAN) return "stable through CFL >= 4.0";
870 return "stable CFL";
871}
872
873/**
874 * @brief Prints one candidate's eigenvalue and norm stable-CFL statuses.
875 */
876static PetscErrorCode PrintStableCFLLine(const char *candidate,
877 StableCFLResult eig,
878 StableCFLResult norm)
879{
880 PetscFunctionBeginUser;
881 PetscCall(PetscPrintf(PETSC_COMM_WORLD, " cand %s: eig %s", candidate, StableCFLStatusText(eig)));
882 if (eig.status == STABLE_CFL_FINITE) PetscCall(PetscPrintf(PETSC_COMM_WORLD, " = %.4f", (double)eig.cfl));
883 PetscCall(PetscPrintf(PETSC_COMM_WORLD, " | norm %s", StableCFLStatusText(norm)));
884 if (norm.status == STABLE_CFL_FINITE) PetscCall(PetscPrintf(PETSC_COMM_WORLD, " = %.4f", (double)norm.cfl));
885 PetscCall(PetscPrintf(PETSC_COMM_WORLD, "\n"));
886 PetscFunctionReturn(0);
887}
888
889/* P(M) applied to a vector: out = P(dtau*J) * x (dense). */
890static void ApplyP(const PetscReal *P, const PetscReal *x, PetscReal *out, PetscInt n)
891{
892 for (PetscInt r = 0; r < n; r++) { PetscReal s = 0.0; for (PetscInt c = 0; c < n; c++) s += P[r + c*n]*x[c]; out[r] = s; }
893}
894
895/**
896 * @brief Prints frozen RK amplification tables for the supplied operator and candidates.
897 */
898static PetscErrorCode PrintFrozenAmplificationTable(const char *title, const PetscReal *J,
899 PetscInt n, const PetscReal lams[3],
900 const char *cn[3])
901{
902 const PetscReal cfls[5] = {0.25, 0.50, 1.00, 1.50, 2.00};
903 PetscReal *Pm;
904 PetscFunctionBeginUser;
905 PetscCall(PetscMalloc1(n*n, &Pm));
906 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
907 " --- %s ---\n"
908 " candidate CFL dtau rho(P) sigma_max(P)\n", title));
909 for (int c = 0; c < 3; c++) {
910 if (!(lams[c] > 0.0)) continue;
911 for (int q = 0; q < 5; q++) {
912 const PetscReal dtau = cfls[q]/lams[c];
913 PetscReal rhoP, smaxP;
914 PetscCall(RKPolynomial(J, dtau, n, Pm));
915 PetscCall(DenseRKPolynomialSpectralRadius(J, n, dtau, &rhoP));
916 PetscCall(DenseSigmaMax(Pm, n, &smaxP, NULL));
917 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
918 " %-9s %.2f %.6e %.6e %.6e\n",
919 cn[c], (double)cfls[q], (double)dtau, (double)rhoP, (double)smaxP));
920 }
921 }
922 PetscCall(PetscFree(Pm));
923 PetscFunctionReturn(0);
924}
925
926/**
927 * @brief Computes the Euclidean norm of a dense vector.
928 */
929static PetscReal VecNorm2Array(const PetscReal *x, PetscInt n)
930{
931 PetscReal s = 0.0;
932 for (PetscInt i = 0; i < n; i++) s += x[i]*x[i];
933 return PetscSqrtReal(s);
934}
935
936/**
937 * @brief Computes the infinity norm of a dense vector.
938 */
939static PetscReal VecNormInfArray(const PetscReal *x, PetscInt n)
940{
941 PetscReal s = 0.0;
942 for (PetscInt i = 0; i < n; i++) s = PetscMax(s, PetscAbsReal(x[i]));
943 return s;
944}
945
946/**
947 * @brief Computes the Frobenius norm of a dense column-major matrix.
948 */
949static PetscReal DenseFrobenius(const PetscReal *A, PetscInt n)
950{
951 PetscReal s = 0.0;
952 for (PetscInt i = 0; i < n*n; i++) s += A[i]*A[i];
953 return PetscSqrtReal(s);
954}
955
956/**
957 * @brief True if an active representative lies on a plane adjacent to periodic duplicates.
958 */
959static PetscBool DofTouchesPeriodicRepresentative(const DofMap *map, PetscInt m, DMDALocalInfo info)
960{
961 return (PetscBool)(map->ci[m] == 1 || map->ci[m] == info.mx-2 ||
962 map->cj[m] == 1 || map->cj[m] == info.my-2 ||
963 map->ck[m] == 1 || map->ck[m] == info.mz-2);
964}
965
966/**
967 * @brief Prints the component-wise periodic storage count actually used by the active map.
968 */
969static PetscErrorCode PrintPeriodicSpaceAudit(UserCtx *user, const DofMap *map)
970{
971 PetscInt count[3] = {0,0,0};
972 PetscInt min_i[3] = {PETSC_MAX_INT,PETSC_MAX_INT,PETSC_MAX_INT};
973 PetscInt min_j[3] = {PETSC_MAX_INT,PETSC_MAX_INT,PETSC_MAX_INT};
974 PetscInt min_k[3] = {PETSC_MAX_INT,PETSC_MAX_INT,PETSC_MAX_INT};
975 PetscInt max_i[3] = {-PETSC_MAX_INT,-PETSC_MAX_INT,-PETSC_MAX_INT};
976 PetscInt max_j[3] = {-PETSC_MAX_INT,-PETSC_MAX_INT,-PETSC_MAX_INT};
977 PetscInt max_k[3] = {-PETSC_MAX_INT,-PETSC_MAX_INT,-PETSC_MAX_INT};
978 PetscFunctionBeginUser;
979 for (PetscInt m = 0; m < map->n; m++) {
980 const PetscInt c = map->comp[m];
981 count[c]++;
982 min_i[c] = PetscMin(min_i[c], map->ci[m]); max_i[c] = PetscMax(max_i[c], map->ci[m]);
983 min_j[c] = PetscMin(min_j[c], map->cj[m]); max_j[c] = PetscMax(max_j[c], map->cj[m]);
984 min_k[c] = PetscMin(min_k[c], map->ck[m]); max_k[c] = PetscMax(max_k[c], map->ck[m]);
985 }
986 const PetscInt per_comp = PeriodicRepCount(user->info.mx) *
987 PeriodicRepCount(user->info.my) *
988 PeriodicRepCount(user->info.mz);
989 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
990 " --- periodic independent-space audit ---\n"
991 " synchronization convention: duplicate planes 0<-m-2 and m-1<-1 in x/y/z for Ucont.\n"
992 " active rows and columns both use the same representatives i,j,k=1..m-2.\n"
993 " comp expected actual i-range j-range k-range\n"));
994 for (PetscInt c = 0; c < 3; c++) {
995 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
996 " %d %7d %6d [%d,%d] [%d,%d] [%d,%d]\n",
997 (int)c, (int)per_comp, (int)count[c],
998 (int)min_i[c], (int)max_i[c], (int)min_j[c], (int)max_j[c], (int)min_k[c], (int)max_k[c]));
999 }
1000 PetscFunctionReturn(0);
1001}
1002
1003/**
1004 * @brief Prints 3x3 component block norms and localized symmetric rows.
1005 */
1006static PetscErrorCode PrintBlockAndSymmetricLocalization(UserCtx *user, const DofMap *map,
1007 const PetscReal *J)
1008{
1009 PetscReal block2[3][3] = {{0.0}}, sym2[3][3] = {{0.0}};
1010 PetscReal *row2;
1011 PetscInt top[8];
1012 PetscFunctionBeginUser;
1013 PetscCall(PetscMalloc1(map->n, &row2));
1014 for (PetscInt r = 0; r < map->n; r++) row2[r] = 0.0;
1015 for (PetscInt c = 0; c < map->n; c++) {
1016 for (PetscInt r = 0; r < map->n; r++) {
1017 const PetscInt rb = map->comp[r], cb = map->comp[c];
1018 const PetscReal a = J[r + c*map->n];
1019 const PetscReal s = 0.5*(J[r + c*map->n] + J[c + r*map->n]);
1020 block2[rb][cb] += a*a;
1021 sym2[rb][cb] += s*s;
1022 row2[r] += s*s;
1023 }
1024 }
1025 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1026 " --- component block norms J_ab = dR_a/dU_b ---\n"
1027 " row-comp col-comp ||J_ab||F ||0.5(J+J^T)_ab||F\n"));
1028 for (PetscInt rb = 0; rb < 3; rb++)
1029 for (PetscInt cb = 0; cb < 3; cb++)
1030 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1031 " %d %d %.6e %.6e\n",
1032 (int)rb, (int)cb, (double)PetscSqrtReal(block2[rb][cb]),
1033 (double)PetscSqrtReal(sym2[rb][cb])));
1034
1035 for (PetscInt q = 0; q < 8; q++) {
1036 top[q] = -1;
1037 for (PetscInt r = 0; r < map->n; r++) {
1038 PetscBool used = PETSC_FALSE;
1039 for (PetscInt p = 0; p < q; p++) if (top[p] == r) used = PETSC_TRUE;
1040 if (!used && (top[q] < 0 || row2[r] > row2[top[q]])) top[q] = r;
1041 }
1042 }
1043 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1044 " --- largest rows of S=0.5*(J+J^T) ---\n"
1045 " row comp (i,j,k) seam-adj ||S_row||2 largest symmetric columns\n"));
1046 for (PetscInt q = 0; q < 8; q++) {
1047 const PetscInt r = top[q];
1048 PetscInt best[3] = {-1,-1,-1};
1049 for (PetscInt pass = 0; pass < 3; pass++) {
1050 for (PetscInt c = 0; c < map->n; c++) {
1051 PetscBool used = PETSC_FALSE;
1052 for (PetscInt p = 0; p < pass; p++) if (best[p] == c) used = PETSC_TRUE;
1053 const PetscReal mag = PetscAbsReal(0.5*(J[r + c*map->n] + J[c + r*map->n]));
1054 if (!used && (best[pass] < 0 ||
1055 mag > PetscAbsReal(0.5*(J[r + best[pass]*map->n] + J[best[pass] + r*map->n])))) best[pass] = c;
1056 }
1057 }
1058 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1059 " %3d %d (%d,%d,%d) %s %.6e",
1060 (int)r, (int)map->comp[r], (int)map->ci[r], (int)map->cj[r], (int)map->ck[r],
1061 DofTouchesPeriodicRepresentative(map, r, user->info) ? "yes" : "no ",
1062 (double)PetscSqrtReal(row2[r])));
1063 for (PetscInt p = 0; p < 3; p++) {
1064 const PetscInt c = best[p];
1065 const PetscReal s = 0.5*(J[r + c*map->n] + J[c + r*map->n]);
1066 PetscCall(PetscPrintf(PETSC_COMM_WORLD, " | c%d:%d(%d,%d,%d)=%.3e",
1067 (int)p, (int)map->comp[c], (int)map->ci[c], (int)map->cj[c],
1068 (int)map->ck[c], (double)s));
1069 }
1070 PetscCall(PetscPrintf(PETSC_COMM_WORLD, "\n"));
1071 }
1072 PetscCall(PetscFree(row2));
1073 PetscFunctionReturn(0);
1074}
1075
1076/**
1077 * @brief Fills the six real basis vectors for one staggered same-wavevector subspace.
1078 */
1079static void FillStaggeredFourierBasis(const DofMap *map, PetscInt wx, PetscInt wy, PetscInt wz,
1080 PetscReal *Q)
1081{
1082 PetscInt nrep = 0;
1083 for (PetscInt m = 0; m < map->n; m++) nrep = PetscMax(nrep, map->ci[m]);
1084 for (PetscInt q = 0; q < 6*map->n; q++) Q[q] = 0.0;
1085 for (PetscInt m = 0; m < map->n; m++) {
1086 const PetscInt comp = map->comp[m];
1087 const PetscReal x = (PetscReal)(map->ci[m]-1) - (comp == 0 ? 0.5 : 0.0);
1088 const PetscReal y = (PetscReal)(map->cj[m]-1) - (comp == 1 ? 0.5 : 0.0);
1089 const PetscReal z = (PetscReal)(map->ck[m]-1) - (comp == 2 ? 0.5 : 0.0);
1090 const PetscReal phase = 2.0*PETSC_PI*((PetscReal)wx*x + (PetscReal)wy*y + (PetscReal)wz*z)/(PetscReal)nrep;
1091 Q[m + (2*comp+0)*map->n] = PetscCosReal(phase);
1092 Q[m + (2*comp+1)*map->n] = PetscSinReal(phase);
1093 }
1094 for (PetscInt q = 0; q < 6; q++) {
1095 PetscReal n2 = 0.0;
1096 for (PetscInt m = 0; m < map->n; m++) n2 += Q[m + q*map->n]*Q[m + q*map->n];
1097 n2 = PetscSqrtReal(n2);
1098 if (n2 > 0.0) for (PetscInt m = 0; m < map->n; m++) Q[m + q*map->n] /= n2;
1099 }
1100}
1101
1102/**
1103 * @brief Builds the 6x6 projected real symbol and leakage for one wavevector.
1104 */
1105static PetscErrorCode StaggeredFourierSymbol(const DofMap *map, const PetscReal *J,
1106 PetscInt wx, PetscInt wy, PetscInt wz,
1107 PetscReal A6[36], PetscReal *leak)
1108{
1109 PetscReal *Q, *JQ;
1110 PetscReal all2 = 0.0, leak2 = 0.0;
1111 PetscFunctionBeginUser;
1112 PetscCall(PetscMalloc2((size_t)6*map->n, &Q, (size_t)6*map->n, &JQ));
1113 FillStaggeredFourierBasis(map, wx, wy, wz, Q);
1114 for (PetscInt q = 0; q < 6; q++) DenseMatVec(J, &Q[q*map->n], &JQ[q*map->n], map->n);
1115 for (PetscInt c = 0; c < 6; c++)
1116 for (PetscInt r = 0; r < 6; r++) {
1117 PetscReal s = 0.0;
1118 for (PetscInt m = 0; m < map->n; m++) s += Q[m + r*map->n] * JQ[m + c*map->n];
1119 A6[r + c*6] = s;
1120 }
1121 for (PetscInt c = 0; c < 6; c++) {
1122 for (PetscInt m = 0; m < map->n; m++) {
1123 PetscReal proj = 0.0;
1124 for (PetscInt r = 0; r < 6; r++) proj += Q[m + r*map->n] * A6[r + c*6];
1125 const PetscReal d = JQ[m + c*map->n] - proj;
1126 leak2 += d*d;
1127 all2 += JQ[m + c*map->n]*JQ[m + c*map->n];
1128 }
1129 }
1130 *leak = PetscSqrtReal(leak2) / PetscMax(PETSC_MACHINE_EPSILON, PetscSqrtReal(all2));
1131 PetscCall(PetscFree2(Q, JQ));
1132 PetscFunctionReturn(0);
1133}
1134
1135/**
1136 * @brief Eigenvalue summary for a 6x6 real symbol.
1137 */
1138static PetscErrorCode SymbolEigenSummary(const PetscReal A6[36], PetscReal *maxre,
1139 PetscReal wr_out[6], PetscReal wi_out[6])
1140{
1141 PetscBLASInt N = 6, lda = 6, lwork = 128, info;
1142 PetscReal Acopy[36], work[128], dummy = 0.0;
1143 PetscFunctionBeginUser;
1144 for (PetscInt t = 0; t < 36; t++) Acopy[t] = A6[t];
1145 {
1146 char nochar = 'N';
1147 LAPACKgeev_(&nochar, &nochar, &N, Acopy, &lda, wr_out, wi_out, &dummy, &lda, &dummy, &lda, work, &lwork, &info);
1148 }
1149 PetscCheck(info == 0, PETSC_COMM_SELF, PETSC_ERR_LIB, "LAPACK dgeev failed for 6x6 symbol: info=%d", (int)info);
1150 *maxre = -PETSC_MAX_REAL;
1151 for (PetscInt q = 0; q < 6; q++) *maxre = PetscMax(*maxre, wr_out[q]);
1152 PetscFunctionReturn(0);
1153}
1154
1155/**
1156 * @brief Projects production J on full staggered six-dimensional Fourier subspaces.
1157 */
1158static PetscErrorCode PrintFourierChecks(const DofMap *map, const PetscReal *J)
1159{
1160 typedef struct { const char *name; PetscInt wx, wy, wz; } Mode;
1161 const Mode modes[4] = {{"x",1,0,0}, {"y",0,1,0}, {"xy",1,1,0}, {"x-y",1,2,0}};
1162 PetscReal best_re = -PETSC_MAX_REAL, best_leak = 0.0;
1163 PetscInt best_wx = 0, best_wy = 0, best_wz = 0, nrep = 0;
1164 PetscFunctionBeginUser;
1165 for (PetscInt m = 0; m < map->n; m++) nrep = PetscMax(nrep, map->ci[m]);
1166 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1167 " --- staggered 6x6 Fourier-symbol check ---\n"
1168 " mode max_real leakage eigenvalues (real,imag)\n"));
1169 for (PetscInt im = 0; im < 4; im++) {
1170 PetscReal A6[36], wr[6], wi[6], maxre, leak;
1171 PetscCall(StaggeredFourierSymbol(map, J, modes[im].wx, modes[im].wy, modes[im].wz, A6, &leak));
1172 PetscCall(SymbolEigenSummary(A6, &maxre, wr, wi));
1173 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1174 " %-5s(%d,%d,%d) %.6e %.3e",
1175 modes[im].name, (int)modes[im].wx, (int)modes[im].wy, (int)modes[im].wz,
1176 (double)maxre, (double)leak));
1177 for (PetscInt q = 0; q < 6; q++) PetscCall(PetscPrintf(PETSC_COMM_WORLD, " %.3e%+.3ei", (double)wr[q], (double)wi[q]));
1178 PetscCall(PetscPrintf(PETSC_COMM_WORLD, "\n"));
1179 }
1180 for (PetscInt wz = 0; wz < nrep; wz++)
1181 for (PetscInt wy = 0; wy < nrep; wy++)
1182 for (PetscInt wx = 0; wx < nrep; wx++) {
1183 if (wx == 0 && wy == 0 && wz == 0) continue;
1184 PetscReal A6[36], wr[6], wi[6], maxre, leak;
1185 PetscCall(StaggeredFourierSymbol(map, J, wx, wy, wz, A6, &leak));
1186 PetscCall(SymbolEigenSummary(A6, &maxre, wr, wi));
1187 if (maxre > best_re) {
1188 best_re = maxre; best_leak = leak; best_wx = wx; best_wy = wy; best_wz = wz;
1189 }
1190 }
1191 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1192 " best scanned wavevector (%d,%d,%d): max_real=%.6e leakage=%.3e\n",
1193 (int)best_wx, (int)best_wy, (int)best_wz, (double)best_re, (double)best_leak));
1194 PetscFunctionReturn(0);
1195}
1196
1197/**
1198 * @brief Trace observable production residual stages for one deterministic perturbation.
1199 */
1200static PetscErrorCode TraceStateAResidualStages(UserCtx *user, Vec Ubase, Vec Rhs,
1201 const DofMap *map, const PetscReal *J)
1202{
1203 Vec Upert, Conv;
1204 PetscReal *v, *ract;
1205 PetscReal seam_ucont[3], seam_ucat[3], conv2, convinf, rhs2, rhsinf;
1206 PetscInt active_rows[4] = {0,0,0,0};
1207 PetscFunctionBeginUser;
1208 PetscCall(VecDuplicate(Ubase, &Upert));
1209 PetscCall(VecDuplicate(user->lUcont, &Conv));
1210 PetscCall(PetscMalloc2(map->n, &v, map->n, &ract));
1211 FillAuditDirection(map, 0, v);
1212 PetscCall(VecCopy(Ubase, Upert));
1213 PetscCall(AddActiveVector(user, Upert, map, v, 1e-6));
1214 PetscCall(VecCopy(Upert, user->Ucont));
1215 {
1216 const char *fld[] = {"Ucont"};
1217 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fld));
1218 }
1219 PetscCall(ComputeLocalDuplicateMismatch(user, user->lUcont, seam_ucont));
1220 PetscCall(Contra2Cart(user));
1221 PetscCall(UpdateLocalGhosts(user, "Ucat"));
1222 PetscCall(ComputeLocalDuplicateMismatch(user, user->lUcat, seam_ucat));
1223 PetscCall(Convection(user, user->lUcont, user->lUcat, Conv));
1224 PetscCall(VecNorm(Conv, NORM_2, &conv2));
1225 PetscCall(VecNorm(Conv, NORM_INFINITY, &convinf));
1226 PetscCall(EvalConvResidual(user, Upert, Rhs, map, ract));
1227 rhs2 = VecNorm2Array(ract, map->n);
1228 rhsinf = VecNormInfArray(ract, map->n);
1229 {
1230 PetscReal ***nvert;
1231 PetscCall(DMDAVecGetArrayRead(user->da, user->lNvert, &nvert));
1232 for (PetscInt m = 0; m < map->n; m++) {
1233 const PetscInt rows = MomCellActiveRows(nvert, map->ck[m], map->cj[m], map->ci[m],
1234 user->info.mx, user->info.my, user->info.mz,
1235 PETSC_FALSE, PETSC_FALSE, PETSC_FALSE, 0);
1236 if (rows & (1 << map->comp[m])) active_rows[map->comp[m]]++;
1237 else active_rows[3]++;
1238 }
1239 PetscCall(DMDAVecRestoreArrayRead(user->da, user->lNvert, &nvert));
1240 }
1241 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1242 " --- observable State A residual trace (mixed perturbation, amp=1e-6) ---\n"
1243 " after Ucont periodic sync duplicate mismatch (x,y,z)=%.3e %.3e %.3e\n"
1244 " after Contra2Cart Ucat duplicate mismatch (x,y,z)=%.3e %.3e %.3e\n"
1245 " direct Convection() Cartesian norm: ||Conv||2=%.6e ||Conv||inf=%.6e\n"
1246 " final active ComputeRHS() norm: ||R||2=%.6e ||R||inf=%.6e\n"
1247 " active-row mask counts by component: u=%d v=%d w=%d discarded=%d\n"
1248 " final active-space skewness defect already reported from J: %.3e\n",
1249 (double)seam_ucont[0], (double)seam_ucont[1], (double)seam_ucont[2],
1250 (double)seam_ucat[0], (double)seam_ucat[1], (double)seam_ucat[2],
1251 (double)conv2, (double)convinf, (double)rhs2, (double)rhsinf,
1252 (int)active_rows[0], (int)active_rows[1], (int)active_rows[2], (int)active_rows[3],
1253 (double)DenseSkewnessDefect(J, map->n)));
1254 PetscCall(PetscFree2(v, ract));
1255 PetscCall(VecDestroy(&Upert));
1256 PetscCall(VecDestroy(&Conv));
1257 PetscFunctionReturn(0);
1258}
1259
1260/**
1261 * @brief Computes a normalized Frobenius difference between two dense matrices.
1262 */
1263static PetscReal DenseRelativeDiff(const PetscReal *A, const PetscReal *B, PetscInt n, PetscReal denom_ref)
1264{
1265 PetscReal s = 0.0;
1266 for (PetscInt i = 0; i < n*n; i++) {
1267 const PetscReal d = A[i] - B[i];
1268 s += d*d;
1269 }
1270 return PetscSqrtReal(s) / PetscMax(1.0, denom_ref);
1271}
1272
1273/**
1274 * @brief Adds a dense active-space vector into a global contravariant vector.
1275 */
1276static PetscErrorCode AddActiveVector(UserCtx *user, Vec U, const DofMap *map,
1277 const PetscReal *x, PetscReal scale)
1278{
1279 Cmpnts ***a;
1280 PetscFunctionBeginUser;
1281 PetscCall(DMDAVecGetArray(user->fda, U, &a));
1282 for (PetscInt m = 0; m < map->n; m++) {
1283 PetscReal *p = (PetscReal*)&a[map->ck[m]][map->cj[m]][map->ci[m]];
1284 p[map->comp[m]] += scale*x[m];
1285 }
1286 PetscCall(DMDAVecRestoreArray(user->fda, U, &a));
1287 PetscFunctionReturn(0);
1288}
1289
1290/**
1291 * @brief Extracts active-space entries from a global contravariant vector.
1292 */
1293static PetscErrorCode ExtractActiveVector(UserCtx *user, Vec U, const DofMap *map, PetscReal *x)
1294{
1295 Cmpnts ***a;
1296 PetscFunctionBeginUser;
1297 PetscCall(DMDAVecGetArrayRead(user->fda, U, &a));
1298 for (PetscInt m = 0; m < map->n; m++) {
1299 const PetscReal *p = (const PetscReal*)&a[map->ck[m]][map->cj[m]][map->ci[m]];
1300 x[m] = p[map->comp[m]];
1301 }
1302 PetscCall(DMDAVecRestoreArrayRead(user->fda, U, &a));
1303 PetscFunctionReturn(0);
1304}
1305
1306/**
1307 * @brief Returns a deterministic checksum weight for an active DOF.
1308 */
1309static PetscReal DofWeight(const DofMap *map, PetscInt m)
1310{
1311 return 1.0 + 0.013*(PetscReal)(map->comp[m]+1)
1312 + 0.017*(PetscReal)map->ci[m]
1313 + 0.019*(PetscReal)map->cj[m]
1314 + 0.023*(PetscReal)map->ck[m];
1315}
1316
1317/**
1318 * @brief Computes global active-vector norms and checksum.
1319 */
1320static PetscErrorCode ActiveStats(const DofMap *map, const PetscReal *x, GlobalVecStats *stats)
1321{
1322 PetscReal loc2 = 0.0, locinf = 0.0, locsum = 0.0;
1323 PetscReal glo2, gloinf, glosum;
1324 PetscFunctionBeginUser;
1325 for (PetscInt m = 0; m < map->n; m++) {
1326 loc2 += x[m]*x[m];
1327 locinf = PetscMax(locinf, PetscAbsReal(x[m]));
1328 locsum += DofWeight(map, m)*x[m];
1329 }
1330 PetscCallMPI(MPI_Allreduce(&loc2, &glo2, 1, MPIU_REAL, MPI_SUM, PETSC_COMM_WORLD));
1331 PetscCallMPI(MPI_Allreduce(&locinf, &gloinf, 1, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD));
1332 PetscCallMPI(MPI_Allreduce(&locsum, &glosum, 1, MPIU_REAL, MPI_SUM, PETSC_COMM_WORLD));
1333 stats->n2 = PetscSqrtReal(glo2); stats->ninf = gloinf; stats->checksum = glosum;
1334 PetscFunctionReturn(0);
1335}
1336
1337/**
1338 * @brief Fills a globally normalized deterministic active-space perturbation direction.
1339 */
1340static PetscErrorCode FillDeterministicDirection(UserCtx *user, Vec V, const DofMap *map, PetscReal *x)
1341{
1342 PetscReal loc2 = 0.0, glo2;
1343 PetscFunctionBeginUser;
1344 PetscCall(VecSet(V, 0.0));
1345 for (PetscInt m = 0; m < map->n; m++) {
1346 x[m] = PetscSinReal(0.37*(PetscReal)(map->ci[m]+1)
1347 + 0.51*(PetscReal)(map->cj[m]+1)
1348 + 0.73*(PetscReal)(map->ck[m]+1)
1349 + 0.29*(PetscReal)(map->comp[m]+1));
1350 loc2 += x[m]*x[m];
1351 }
1352 PetscCallMPI(MPI_Allreduce(&loc2, &glo2, 1, MPIU_REAL, MPI_SUM, PETSC_COMM_WORLD));
1353 const PetscReal invn = 1.0/PetscSqrtReal(glo2);
1354 for (PetscInt m = 0; m < map->n; m++) x[m] *= invn;
1355 PetscCall(AddActiveVector(user, V, map, x, 1.0));
1356 PetscFunctionReturn(0);
1357}
1358
1359/* ----------------------------------------------------------------------------------- *
1360 * Direct anchored 4-stage recurrence on the real residual (no controller). *
1361 * ----------------------------------------------------------------------------------- */
1362/* Phi(Ufull) -> result stored in Uout (global), using EvalConvResidual at each stage. */
1363static PetscErrorCode FourStage(UserCtx *user, Vec U0full, PetscReal dtau, Vec Rhs, const DofMap *map,
1364 PetscReal *Rscratch, Vec Uwork, Vec Uout)
1365{
1366 const PetscReal alfa[4] = {0.25, 1.0/3.0, 0.5, 1.0};
1367 PetscFunctionBeginUser;
1368 PetscCall(VecCopy(U0full, Uwork)); /* stage state */
1369 for (int s = 0; s < 4; s++) {
1370 PetscCall(EvalConvResidual(user, Uwork, Rhs, map, Rscratch)); /* R(U^{(s)}) into active rows */
1371 PetscCall(VecCopy(U0full, Uout)); /* U^{(s+1)} = U0 + alfa*dtau*R */
1372 Cmpnts ***a;
1373 PetscCall(DMDAVecGetArray(user->fda, Uout, &a));
1374 for (PetscInt m = 0; m < map->n; m++) {
1375 PetscReal *p = (PetscReal*)&a[map->ck[m]][map->cj[m]][map->ci[m]];
1376 p[map->comp[m]] += alfa[s]*dtau*Rscratch[m];
1377 }
1378 PetscCall(DMDAVecRestoreArray(user->fda, Uout, &a));
1379 PetscCall(VecCopy(Uout, Uwork));
1380 }
1381 PetscFunctionReturn(0);
1382}
1383
1384/**
1385 * @brief Forms one anchored RK stage state from the base state and active residual.
1386 */
1387static PetscErrorCode SetAnchoredStage(UserCtx *user, Vec U0full, PetscReal scale,
1388 const DofMap *map, const PetscReal *Ract, Vec Ustage)
1389{
1390 PetscFunctionBeginUser;
1391 PetscCall(VecCopy(U0full, Ustage));
1392 PetscCall(AddActiveVector(user, Ustage, map, Ract, scale));
1393 PetscFunctionReturn(0);
1394}
1395
1396/**
1397 * @brief Builds the first three anchored RK stage states for a base vector.
1398 */
1399static PetscErrorCode BuildStageStates(UserCtx *user, Vec U0full, PetscReal dtau, Vec Rhs,
1400 const DofMap *map, PetscReal *Rscratch,
1401 Vec Y1, Vec Y2, Vec Y3)
1402{
1403 const PetscReal alfa[3] = {0.25, 1.0/3.0, 0.5};
1404 PetscFunctionBeginUser;
1405 PetscCall(EvalConvResidual(user, U0full, Rhs, map, Rscratch));
1406 PetscCall(SetAnchoredStage(user, U0full, alfa[0]*dtau, map, Rscratch, Y1));
1407 PetscCall(EvalConvResidual(user, Y1, Rhs, map, Rscratch));
1408 PetscCall(SetAnchoredStage(user, U0full, alfa[1]*dtau, map, Rscratch, Y2));
1409 PetscCall(EvalConvResidual(user, Y2, Rhs, map, Rscratch));
1410 PetscCall(SetAnchoredStage(user, U0full, alfa[2]*dtau, map, Rscratch, Y3));
1411 PetscFunctionReturn(0);
1412}
1413
1414/**
1415 * @brief Builds a centered finite-difference Jacobian of the production convective residual.
1416 */
1417static PetscErrorCode BuildFDJacobian(UserCtx *user, Vec Ucenter, PetscReal epsrel, Vec Rhs,
1418 const DofMap *map, PetscReal *Rp, PetscReal *Rm,
1419 Vec Uwork, PetscReal *J)
1420{
1421 PetscFunctionBeginUser;
1422 for (PetscInt col = 0; col < map->n; col++) {
1423 PetscReal u0;
1424 PetscCall(GetDof(user, Ucenter, map, col, &u0));
1425 const PetscReal eps = epsrel*PetscMax(1.0, PetscAbsReal(u0));
1426 PetscCall(VecCopy(Ucenter, Uwork));
1427 PetscCall(PerturbDof(user, Uwork, map, col, +eps));
1428 PetscCall(EvalConvResidual(user, Uwork, Rhs, map, Rp));
1429 PetscCall(VecCopy(Ucenter, Uwork));
1430 PetscCall(PerturbDof(user, Uwork, map, col, -eps));
1431 PetscCall(EvalConvResidual(user, Uwork, Rhs, map, Rm));
1432 for (PetscInt row = 0; row < map->n; row++) J[row + col*map->n] = (Rp[row]-Rm[row])/(2.0*eps);
1433 }
1434 PetscFunctionReturn(0);
1435}
1436
1437/**
1438 * @brief Applies a dense column-major matrix to an active-space vector.
1439 */
1440static void DenseMatVec(const PetscReal *J, const PetscReal *x, PetscReal *y, PetscInt n)
1441{
1442 for (PetscInt r = 0; r < n; r++) {
1443 PetscReal s = 0.0;
1444 for (PetscInt c = 0; c < n; c++) s += J[r + c*n]*x[c];
1445 y[r] = s;
1446 }
1447}
1448
1449/**
1450 * @brief Fills one deterministic active-space vector used by dense/matrix-free checks.
1451 */
1452static void FillAuditDirection(const DofMap *map, PetscInt kind, PetscReal *x)
1453{
1454 PetscReal n2 = 0.0;
1455 for (PetscInt m = 0; m < map->n; m++) {
1456 const PetscReal a = 0.31*(PetscReal)(map->ci[m]+1)
1457 + 0.47*(PetscReal)(map->cj[m]+1)
1458 + 0.59*(PetscReal)(map->ck[m]+1)
1459 + 0.23*(PetscReal)(map->comp[m]+1);
1460 if (kind == 0) x[m] = PetscSinReal(a);
1461 else if (kind == 1) x[m] = PetscCosReal(1.7*a) + 0.25*PetscSinReal(0.9*(PetscReal)(m+1));
1462 else x[m] = (map->comp[m] == kind-2) ? PetscSinReal(a) : 0.0;
1463 n2 += x[m]*x[m];
1464 }
1465 n2 = PetscSqrtReal(n2);
1466 if (n2 > 0.0) for (PetscInt m = 0; m < map->n; m++) x[m] /= n2;
1467}
1468
1469/**
1470 * @brief Verifies that the assembled dense Jacobian has the same action as production FD Jv.
1471 */
1472static PetscErrorCode CheckDenseJacobianAction(UserCtx *user, Vec Ubase, Vec Rhs, Vec Uwork,
1473 const DofMap *map, const PetscReal *J,
1474 PetscReal epsrel)
1475{
1476 Vec Up, Um;
1477 PetscReal *v, *jd, *Rp, *Rm, *jmf;
1478 PetscFunctionBeginUser;
1479 PetscCall(VecDuplicate(Ubase, &Up));
1480 PetscCall(VecDuplicate(Ubase, &Um));
1481 PetscCall(PetscMalloc5(map->n, &v, map->n, &jd, map->n, &Rp, map->n, &Rm, map->n, &jmf));
1482 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1483 " --- dense Jacobian action check against matrix-free production Jv ---\n"
1484 " direction rel_L2 rel_Linf ||Jv||2 ||Jv||inf\n"));
1485 const char *names[5] = {"mixed-sin", "mixed-cos", "u-only", "v-only", "w-only"};
1486 for (PetscInt kind = 0; kind < 5; kind++) {
1487 FillAuditDirection(map, kind, v);
1488 DenseMatVec(J, v, jd, map->n);
1489 PetscCall(VecCopy(Ubase, Up));
1490 PetscCall(VecCopy(Ubase, Um));
1491 PetscCall(AddActiveVector(user, Up, map, v, +epsrel));
1492 PetscCall(AddActiveVector(user, Um, map, v, -epsrel));
1493 PetscCall(EvalConvResidual(user, Up, Rhs, map, Rp));
1494 PetscCall(EvalConvResidual(user, Um, Rhs, map, Rm));
1495 for (PetscInt m = 0; m < map->n; m++) jmf[m] = (Rp[m]-Rm[m])/(2.0*epsrel);
1496 PetscReal e2 = 0.0, einf = 0.0;
1497 for (PetscInt m = 0; m < map->n; m++) {
1498 const PetscReal d = jd[m] - jmf[m];
1499 e2 += d*d; einf = PetscMax(einf, PetscAbsReal(d));
1500 }
1501 const PetscReal n2 = VecNorm2Array(jmf, map->n);
1502 const PetscReal ni = VecNormInfArray(jmf, map->n);
1503 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1504 " %-13s %.3e %.3e %.6e %.6e\n",
1505 names[kind], (double)(PetscSqrtReal(e2)/PetscMax(PETSC_MACHINE_EPSILON,n2)),
1506 (double)(einf/PetscMax(PETSC_MACHINE_EPSILON,ni)), (double)n2, (double)ni));
1507 }
1508 (void)Uwork;
1509 PetscCall(PetscFree5(v, jd, Rp, Rm, jmf));
1510 PetscCall(VecDestroy(&Up));
1511 PetscCall(VecDestroy(&Um));
1512 PetscFunctionReturn(0);
1513}
1514
1515/**
1516 * @brief Matrix-free production Jacobian-vector product on the active space.
1517 */
1518static PetscErrorCode MatrixFreeJv(UserCtx *user, Vec Ubase, Vec Rhs, const DofMap *map,
1519 const PetscReal *v, PetscReal eps, PetscReal *jv)
1520{
1521 Vec Up, Um;
1522 PetscReal *Rp, *Rm;
1523 PetscFunctionBeginUser;
1524 PetscCall(VecDuplicate(Ubase, &Up));
1525 PetscCall(VecDuplicate(Ubase, &Um));
1526 PetscCall(PetscMalloc2(map->n, &Rp, map->n, &Rm));
1527 PetscCall(VecCopy(Ubase, Up));
1528 PetscCall(VecCopy(Ubase, Um));
1529 PetscCall(AddActiveVector(user, Up, map, v, +eps));
1530 PetscCall(AddActiveVector(user, Um, map, v, -eps));
1531 PetscCall(EvalConvResidual(user, Up, Rhs, map, Rp));
1532 PetscCall(EvalConvResidual(user, Um, Rhs, map, Rm));
1533 for (PetscInt m = 0; m < map->n; m++) jv[m] = (Rp[m]-Rm[m])/(2.0*eps);
1534 PetscCall(PetscFree2(Rp, Rm));
1535 PetscCall(VecDestroy(&Up));
1536 PetscCall(VecDestroy(&Um));
1537 PetscFunctionReturn(0);
1538}
1539
1540/**
1541 * @brief Normalized Frobenius norm of A - (B+C).
1542 */
1543static PetscReal DenseAdditivityError(const PetscReal *A, const PetscReal *B,
1544 const PetscReal *C, PetscInt n)
1545{
1546 PetscReal num = 0.0, den = 0.0;
1547 for (PetscInt t = 0; t < n*n; t++) {
1548 const PetscReal d = A[t] - B[t] - C[t];
1549 num += d*d; den += A[t]*A[t];
1550 }
1551 return PetscSqrtReal(num) / PetscMax(1.0, PetscSqrtReal(den));
1552}
1553
1554/**
1555 * @brief Computes C = A*B - B*A and returns its normalized Frobenius norm.
1556 */
1557static PetscReal DenseCommutatorNorm(const PetscReal *A, const PetscReal *B, PetscInt n)
1558{
1559 PetscReal num = 0.0;
1560 for (PetscInt c = 0; c < n; c++) {
1561 for (PetscInt r = 0; r < n; r++) {
1562 PetscReal ab = 0.0, ba = 0.0;
1563 for (PetscInt q = 0; q < n; q++) {
1564 ab += A[r + q*n] * B[q + c*n];
1565 ba += B[r + q*n] * A[q + c*n];
1566 }
1567 const PetscReal d = ab - ba;
1568 num += d*d;
1569 }
1570 }
1571 const PetscReal den = PetscMax(DenseFrobenius(A, n)*DenseFrobenius(B, n), PETSC_MACHINE_EPSILON);
1572 return PetscSqrtReal(num) / den;
1573}
1574
1575/**
1576 * @brief Builds Jx, Jy, Jxy and prints additivity/commutator/eigenpair diagnostics.
1577 */
1578static PetscErrorCode RunStateADirectionalMechanismAudit(const PetscReal *Jxy, UserCtx *user_xy,
1579 Vec Ubase_xy, Vec Rhs_xy,
1580 const DofMap *map_xy, PetscReal epsrel)
1581{
1582 SimCtx *simCtx = NULL; UserCtx *user = NULL; DofMap map;
1583 Vec Ux, Uy, Rhs, Uwork;
1584 PetscReal *Rp, *Rm, *Jx, *Jy, *Jsum, *vr, *vi, *jdr, *jdi, *jmfr, *jmfi;
1585 PetscReal repeat_err, maxdiv, lamr, lami;
1586 SeamDiagnostics seam;
1587 PetscFunctionBeginUser;
1588 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(&simCtx, &user, 4, 4, 4, PETSC_TRUE, PETSC_TRUE, PETSC_TRUE));
1589 PetscCall(ConfigureCandidateFixture(simCtx, user));
1590 PetscCall(DofMapBuild(user, &map));
1591 PetscCheck(map.n == map_xy->n, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ, "directional audit map size mismatch");
1592 PetscCall(VecDuplicate(user->Ucont, &Ux));
1593 PetscCall(VecDuplicate(user->Ucont, &Uy));
1594 PetscCall(VecDuplicate(user->Ucont, &Rhs));
1595 PetscCall(VecDuplicate(user->Ucont, &Uwork));
1596 PetscCall(PetscMalloc5(map.n, &Rp, map.n, &Rm, (size_t)map.n*map.n, &Jx,
1597 (size_t)map.n*map.n, &Jy, (size_t)map.n*map.n, &Jsum));
1598 PetscCall(BuildBaseState(user, STATE_A_X, Ux, &repeat_err, &maxdiv, &seam));
1599 PetscCall(BuildFDJacobian(user, Ux, epsrel, Rhs, &map, Rp, Rm, Uwork, Jx));
1600 PetscCall(BuildBaseState(user, STATE_A_Y, Uy, &repeat_err, &maxdiv, &seam));
1601 PetscCall(BuildFDJacobian(user, Uy, epsrel, Rhs, &map, Rp, Rm, Uwork, Jy));
1602 for (PetscInt t = 0; t < map.n*map.n; t++) Jsum[t] = Jx[t] + Jy[t];
1603
1604 const PetscReal add_err = DenseAdditivityError(Jxy, Jx, Jy, map.n);
1605 const PetscReal comm = DenseCommutatorNorm(Jx, Jy, map.n);
1606 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1607 " --- State A directional additivity / noncommutation audit ---\n"
1608 " ||Jxy-(Jx+Jy)||F/max(1,||Jxy||F) = %.3e\n"
1609 " ||Jx Jy - Jy Jx||F/(||Jx||F||Jy||F) = %.3e\n"
1610 " spectra:\n", (double)add_err, (double)comm));
1611 PetscCall(PrintSpectrumSummary("Jx", Jx, map.n));
1612 PetscCall(PrintSpectrumSummary("Jy", Jy, map.n));
1613 PetscCall(PrintSpectrumSummary("Jx+Jy", Jsum, map.n));
1614 PetscCall(PrintSpectrumSummary("Jxy", Jxy, map.n));
1615
1616 PetscCall(PetscMalloc6(map.n, &vr, map.n, &vi, map.n, &jdr, map.n, &jdi, map.n, &jmfr, map.n, &jmfi));
1617 PetscCall(DenseMaxRealRightEigenpair(Jxy, map.n, &lamr, &lami, vr, vi));
1618 DenseMatVec(Jxy, vr, jdr, map.n);
1619 DenseMatVec(Jxy, vi, jdi, map.n);
1620 PetscReal ed2 = 0.0, em2 = 0.0, v2 = 0.0;
1621 for (PetscInt m = 0; m < map.n; m++) {
1622 const PetscReal rr = jdr[m] - (lamr*vr[m] - lami*vi[m]);
1623 const PetscReal ri = jdi[m] - (lami*vr[m] + lamr*vi[m]);
1624 ed2 += rr*rr + ri*ri;
1625 v2 += vr[m]*vr[m] + vi[m]*vi[m];
1626 }
1627 PetscCall(MatrixFreeJv(user_xy, Ubase_xy, Rhs_xy, map_xy, vr, epsrel, jmfr));
1628 PetscCall(MatrixFreeJv(user_xy, Ubase_xy, Rhs_xy, map_xy, vi, epsrel, jmfi));
1629 for (PetscInt m = 0; m < map.n; m++) {
1630 const PetscReal rr = jmfr[m] - (lamr*vr[m] - lami*vi[m]);
1631 const PetscReal ri = jmfi[m] - (lami*vr[m] + lamr*vi[m]);
1632 em2 += rr*rr + ri*ri;
1633 }
1634 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1635 " --- State A unstable eigenpair verification ---\n"
1636 " lambda_max_real = %.12e%+.12ei\n"
1637 " ||Jdense v-lambda v||2/||v||2 = %.3e\n"
1638 " ||Jmf v-lambda v||2/||v||2 = %.3e\n",
1639 (double)lamr, (double)lami, (double)(PetscSqrtReal(ed2)/PetscSqrtReal(v2)),
1640 (double)(PetscSqrtReal(em2)/PetscSqrtReal(v2))));
1641
1642 PetscCall(PetscFree6(vr, vi, jdr, jdi, jmfr, jmfi));
1643 PetscCall(PetscFree5(Rp, Rm, Jx, Jy, Jsum));
1644 PetscCall(VecDestroy(&Ux)); PetscCall(VecDestroy(&Uy)); PetscCall(VecDestroy(&Rhs)); PetscCall(VecDestroy(&Uwork));
1645 PetscCall(DofMapDestroy(&map));
1646 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1647 PetscFunctionReturn(0);
1648}
1649
1656
1657/**
1658 * @brief Extract active-space entries from a local vector.
1659 */
1660static PetscErrorCode ExtractLocalActiveVector(UserCtx *user, Vec local, const DofMap *map, PetscReal *x)
1661{
1662 Cmpnts ***a;
1663 PetscFunctionBeginUser;
1664 PetscCall(DMDAVecGetArrayRead(user->fda, local, &a));
1665 for (PetscInt m = 0; m < map->n; m++) {
1666 const PetscReal *p = (const PetscReal*)&a[map->ck[m]][map->cj[m]][map->ci[m]];
1667 x[m] = p[map->comp[m]];
1668 }
1669 PetscCall(DMDAVecRestoreArrayRead(user->fda, local, &a));
1670 PetscFunctionReturn(0);
1671}
1672
1673/**
1674 * @brief Mirrors ComputeRHS's Cartesian residual -> contravariant local Rct mapping.
1675 */
1676static PetscErrorCode MapCartesianResidualToRct(UserCtx *user, Vec Rc, Vec Rct)
1677{
1678 DMDALocalInfo info = user->info;
1679 const PetscInt xs = info.xs, xe = xs + info.xm, mx = info.mx;
1680 const PetscInt ys = info.ys, ye = ys + info.ym, my = info.my;
1681 const PetscInt zs = info.zs, ze = zs + info.zm, mz = info.mz;
1682 const PetscInt lxs = (xs==0) ? xs+1 : xs, lxe = (xe==mx) ? xe-1 : xe;
1683 const PetscInt lys = (ys==0) ? ys+1 : ys, lye = (ye==my) ? ye-1 : ye;
1684 const PetscInt lzs = (zs==0) ? zs+1 : zs, lze = (ze==mz) ? ze-1 : ze;
1685 Cmpnts ***csi, ***eta, ***zet, ***rc, ***rct;
1686 PetscReal ***aj;
1687 PetscFunctionBeginUser;
1688 PetscCall(VecSet(Rct, 0.0));
1689 PetscCall(DMDAVecGetArrayRead(user->fda, user->lCsi, &csi));
1690 PetscCall(DMDAVecGetArrayRead(user->fda, user->lEta, &eta));
1691 PetscCall(DMDAVecGetArrayRead(user->fda, user->lZet, &zet));
1692 PetscCall(DMDAVecGetArrayRead(user->da, user->lAj, &aj));
1693 PetscCall(DMDAVecGetArrayRead(user->fda, Rc, &rc));
1694 PetscCall(DMDAVecGetArray(user->fda, Rct, &rct));
1695 for (PetscInt k = lzs; k < lze; k++)
1696 for (PetscInt j = lys; j < lye; j++)
1697 for (PetscInt i = lxs; i < lxe; i++) {
1698 rct[k][j][i].x = aj[k][j][i] *
1699 (0.5 * (csi[k][j][i].x + csi[k][j][i-1].x) * rc[k][j][i].x +
1700 0.5 * (csi[k][j][i].y + csi[k][j][i-1].y) * rc[k][j][i].y +
1701 0.5 * (csi[k][j][i].z + csi[k][j][i-1].z) * rc[k][j][i].z);
1702 rct[k][j][i].y = aj[k][j][i] *
1703 (0.5 * (eta[k][j][i].x + eta[k][j-1][i].x) * rc[k][j][i].x +
1704 0.5 * (eta[k][j][i].y + eta[k][j-1][i].y) * rc[k][j][i].y +
1705 0.5 * (eta[k][j][i].z + eta[k][j-1][i].z) * rc[k][j][i].z);
1706 rct[k][j][i].z = aj[k][j][i] *
1707 (0.5 * (zet[k][j][i].x + zet[k-1][j][i].x) * rc[k][j][i].x +
1708 0.5 * (zet[k][j][i].y + zet[k-1][j][i].y) * rc[k][j][i].y +
1709 0.5 * (zet[k][j][i].z + zet[k-1][j][i].z) * rc[k][j][i].z);
1710 }
1711 PetscCall(DMDAVecRestoreArray(user->fda, Rct, &rct));
1712 PetscCall(DMDAVecRestoreArrayRead(user->fda, Rc, &rc));
1713 PetscCall(DMDAVecRestoreArrayRead(user->da, user->lAj, &aj));
1714 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lZet, &zet));
1715 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lEta, &eta));
1716 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lCsi, &csi));
1717 PetscCall(SynchronizePeriodicLocalStaggeredField(user, Rct));
1718 PetscFunctionReturn(0);
1719}
1720
1721/**
1722 * @brief Mirrors ComputeRHS's final Rct face averaging and active cleanup for P=0/no body force.
1723 */
1724static PetscErrorCode AverageRctToFinalActive(UserCtx *user, Vec Rct, const DofMap *map, PetscReal *out)
1725{
1726 Cmpnts ***rct;
1727 PetscFunctionBeginUser;
1728 PetscCall(DMDAVecGetArrayRead(user->fda, Rct, &rct));
1729 for (PetscInt m = 0; m < map->n; m++) {
1730 const PetscInt i = map->ci[m], j = map->cj[m], k = map->ck[m], c = map->comp[m];
1731 if (c == 0) out[m] = 0.5*(rct[k][j][i].x + rct[k][j][i+1].x);
1732 else if (c == 1) out[m] = 0.5*(rct[k][j][i].y + rct[k][j+1][i].y);
1733 else out[m] = 0.5*(rct[k][j][i].z + rct[k+1][j][i].z);
1734 }
1735 PetscCall(DMDAVecRestoreArrayRead(user->fda, Rct, &rct));
1736 PetscFunctionReturn(0);
1737}
1738
1739/**
1740 * @brief Evaluates one observable/mirrored residual-path stage for a Ucont input.
1741 */
1742static PetscErrorCode EvalResidualStage(UserCtx *user, Vec Ucont_in, Vec Rhs,
1743 const DofMap *map, ResidualStage stage, PetscReal *out)
1744{
1745 Vec Conv, Rc, Rct;
1746 PetscFunctionBeginUser;
1747 if (stage == STAGE_FINAL) {
1748 PetscCall(EvalConvResidual(user, Ucont_in, Rhs, map, out));
1749 PetscFunctionReturn(0);
1750 }
1751 PetscCall(VecCopy(Ucont_in, user->Ucont));
1752 {
1753 const char *fld[] = {"Ucont"};
1754 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fld));
1755 }
1756 PetscCall(Contra2Cart(user));
1757 PetscCall(UpdateLocalGhosts(user, "Ucat"));
1758 if (stage == STAGE_UCAT) {
1759 PetscCall(ExtractActiveVector(user, user->Ucat, map, out));
1760 PetscFunctionReturn(0);
1761 }
1762 PetscCall(VecDuplicate(user->lUcont, &Conv));
1763 PetscCall(VecDuplicate(user->lUcont, &Rc));
1764 PetscCall(VecDuplicate(user->lUcont, &Rct));
1765 PetscCall(Convection(user, user->lUcont, user->lUcat, Conv));
1766 PetscCall(VecSet(Rc, 0.0));
1767 PetscCall(VecAXPY(Rc, -1.0, Conv));
1768 if (stage == STAGE_CART_RESID) PetscCall(ExtractLocalActiveVector(user, Rc, map, out));
1769 else {
1770 PetscCall(MapCartesianResidualToRct(user, Rc, Rct));
1771 if (stage == STAGE_RCT) PetscCall(ExtractLocalActiveVector(user, Rct, map, out));
1772 else PetscCall(AverageRctToFinalActive(user, Rct, map, out));
1773 }
1774 PetscCall(VecDestroy(&Conv));
1775 PetscCall(VecDestroy(&Rc));
1776 PetscCall(VecDestroy(&Rct));
1777 PetscFunctionReturn(0);
1778}
1779
1780/**
1781 * @brief Builds a finite-difference Jacobian for one residual-path stage.
1782 */
1783static PetscErrorCode BuildStageJacobian(UserCtx *user, Vec Ucenter, PetscReal epsrel, Vec Rhs,
1784 const DofMap *map, ResidualStage stage,
1785 PetscReal *Rp, PetscReal *Rm, Vec Uwork, PetscReal *J)
1786{
1787 PetscFunctionBeginUser;
1788 for (PetscInt col = 0; col < map->n; col++) {
1789 PetscReal u0;
1790 PetscCall(GetDof(user, Ucenter, map, col, &u0));
1791 const PetscReal eps = epsrel*PetscMax(1.0, PetscAbsReal(u0));
1792 PetscCall(VecCopy(Ucenter, Uwork));
1793 PetscCall(PerturbDof(user, Uwork, map, col, +eps));
1794 PetscCall(EvalResidualStage(user, Uwork, Rhs, map, stage, Rp));
1795 PetscCall(VecCopy(Ucenter, Uwork));
1796 PetscCall(PerturbDof(user, Uwork, map, col, -eps));
1797 PetscCall(EvalResidualStage(user, Uwork, Rhs, map, stage, Rm));
1798 for (PetscInt row = 0; row < map->n; row++) J[row + col*map->n] = (Rp[row]-Rm[row])/(2.0*eps);
1799 }
1800 PetscFunctionReturn(0);
1801}
1802
1803/**
1804 * @brief Prints the stage where positive-real spectrum first appears.
1805 */
1806static PetscErrorCode RunStateAResidualPathIsolation(UserCtx *user, Vec Ubase, Vec Rhs,
1807 Vec Uwork, const DofMap *map, PetscReal epsrel)
1808{
1809 const char *names[4] = {"Contra2Cart Ucat", "Cartesian Rc=-Conv", "mapped local Rct", "final active RHS"};
1810 PetscReal *Rp, *Rm, *J;
1811 PetscFunctionBeginUser;
1812 PetscCall(PetscMalloc3(map->n, &Rp, map->n, &Rm, (size_t)map->n*map->n, &J));
1813 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1814 " --- State A residual-path stage Jacobians ---\n"
1815 " stage rho sigma max_real skew nonnormal\n"));
1816 for (PetscInt s = 0; s < 4; s++) {
1817 PetscReal rho, maxre, smax;
1818 PetscCall(BuildStageJacobian(user, Ubase, epsrel, Rhs, map, (ResidualStage)s, Rp, Rm, Uwork, J));
1819 PetscCall(DenseSpectralRadius(J, map->n, &rho, &maxre));
1820 PetscCall(DenseSigmaMax(J, map->n, &smax, NULL));
1821 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1822 " %-20s %.6e %.6e %.6e %.3e %.3e\n",
1823 names[s], (double)rho, (double)smax, (double)maxre,
1824 (double)DenseSkewnessDefect(J, map->n), (double)DenseNonNormality(J, map->n)));
1825 }
1826 PetscCall(PetscFree3(Rp, Rm, J));
1827 PetscFunctionReturn(0);
1828}
1829
1830/**
1831 * @brief Builds the exact four-stage tangent from stage-dependent Jacobians.
1832 */
1833static PetscErrorCode BuildStageTangent(const PetscReal *J0, const PetscReal *J1,
1834 const PetscReal *J2, const PetscReal *J3,
1835 PetscReal dtau, PetscInt n, PetscReal *T4)
1836{
1837 const PetscReal alfa[4] = {0.25, 1.0/3.0, 0.5, 1.0};
1838 PetscReal *T1, *T2, *T3, *Tmp;
1839 PetscFunctionBeginUser;
1840 PetscCall(PetscMalloc4(n*n, &T1, n*n, &T2, n*n, &T3, n*n, &Tmp));
1841
1842 for (PetscInt i = 0; i < n*n; i++) T1[i] = alfa[0]*dtau*J0[i];
1843 for (PetscInt d = 0; d < n; d++) T1[d + d*n] += 1.0;
1844
1845 MatMul(J1, T1, Tmp, n);
1846 for (PetscInt i = 0; i < n*n; i++) T2[i] = alfa[1]*dtau*Tmp[i];
1847 for (PetscInt d = 0; d < n; d++) T2[d + d*n] += 1.0;
1848
1849 MatMul(J2, T2, Tmp, n);
1850 for (PetscInt i = 0; i < n*n; i++) T3[i] = alfa[2]*dtau*Tmp[i];
1851 for (PetscInt d = 0; d < n; d++) T3[d + d*n] += 1.0;
1852
1853 MatMul(J3, T3, Tmp, n);
1854 for (PetscInt i = 0; i < n*n; i++) T4[i] = alfa[3]*dtau*Tmp[i];
1855 for (PetscInt d = 0; d < n; d++) T4[d + d*n] += 1.0;
1856
1857 PetscCall(PetscFree4(T1, T2, T3, Tmp));
1858 PetscFunctionReturn(0);
1859}
1860
1861/**
1862 * @brief Builds a finite-difference Jacobian of the complete nonlinear four-stage map.
1863 */
1864static PetscErrorCode BuildPhiJacobian(UserCtx *user, Vec Ucenter, PetscReal dtau, Vec Rhs,
1865 const DofMap *map, PetscReal *Rscratch,
1866 Vec Upert, Vec Ustage, Vec PhiP, Vec PhiM,
1867 PetscReal *xp, PetscReal *xm, PetscReal epsrel,
1868 PetscReal *JPhi)
1869{
1870 PetscFunctionBeginUser;
1871 for (PetscInt col = 0; col < map->n; col++) {
1872 PetscReal u0;
1873 PetscCall(GetDof(user, Ucenter, map, col, &u0));
1874 const PetscReal eps = epsrel*PetscMax(1.0, PetscAbsReal(u0));
1875 PetscCall(VecCopy(Ucenter, Upert));
1876 PetscCall(PerturbDof(user, Upert, map, col, +eps));
1877 PetscCall(FourStage(user, Upert, dtau, Rhs, map, Rscratch, Ustage, PhiP));
1878 PetscCall(VecCopy(Ucenter, Upert));
1879 PetscCall(PerturbDof(user, Upert, map, col, -eps));
1880 PetscCall(FourStage(user, Upert, dtau, Rhs, map, Rscratch, Ustage, PhiM));
1881 PetscCall(ExtractActiveVector(user, PhiP, map, xp));
1882 PetscCall(ExtractActiveVector(user, PhiM, map, xm));
1883 for (PetscInt row = 0; row < map->n; row++) JPhi[row + col*map->n] = (xp[row]-xm[row])/(2.0*eps);
1884 }
1885 PetscFunctionReturn(0);
1886}
1887
1888/**
1889 * @brief Runs stage-dependent RK tangent and direct nonlinear perturbation diagnostics.
1890 */
1891static PetscErrorCode RunRKTangentDiagnostics(UserCtx *user, CandState st, Vec Ubase, Vec Rhs,
1892 const DofMap *map, PetscReal epsrel,
1893 const PetscReal lams[3], const char *cn[3],
1894 const PetscReal *J0)
1895{
1896 const PetscReal cflsB[4] = {0.1, 0.25, 0.5, 1.0};
1897 const PetscReal cflsOther[1] = {0.5};
1898 const PetscReal *cfls = (st == STATE_B) ? cflsB : cflsOther;
1899 const PetscInt ncfl = (st == STATE_B) ? 4 : 1;
1900 const PetscReal amps[3] = {1e-4, 1e-5, 1e-6};
1901 Vec Y1, Y2, Y3, Upert, Ustage, PhiP, PhiM, Phi0;
1902 PetscReal *Rtmp, *J1, *J2, *J3, *T4, *Pm, *JPhi, *v1, *xrand;
1903 PetscReal *meas, *phi0, *phip, *xscaled, *predP, *predT;
1904 PetscFunctionBeginUser;
1905
1906 PetscCall(VecDuplicate(Ubase, &Y1));
1907 PetscCall(VecDuplicate(Ubase, &Y2));
1908 PetscCall(VecDuplicate(Ubase, &Y3));
1909 PetscCall(VecDuplicate(Ubase, &Upert));
1910 PetscCall(VecDuplicate(Ubase, &Ustage));
1911 PetscCall(VecDuplicate(Ubase, &PhiP));
1912 PetscCall(VecDuplicate(Ubase, &PhiM));
1913 PetscCall(VecDuplicate(Ubase, &Phi0));
1914 PetscCall(PetscMalloc5(map->n, &Rtmp, (size_t)map->n*map->n, &J1,
1915 (size_t)map->n*map->n, &J2, (size_t)map->n*map->n, &J3,
1916 (size_t)map->n*map->n, &T4));
1917 PetscCall(PetscMalloc5((size_t)map->n*map->n, &Pm, (size_t)map->n*map->n, &JPhi,
1918 map->n, &v1, map->n, &xrand, map->n, &meas));
1919 PetscCall(PetscMalloc5(map->n, &phi0, map->n, &phip, map->n, &xscaled,
1920 map->n, &predP, map->n, &predT));
1921
1922 PetscReal nrm = 0.0;
1923 for (PetscInt m = 0; m < map->n; m++) {
1924 xrand[m] = PetscSinReal((PetscReal)(m+1)*1.2345);
1925 nrm += xrand[m]*xrand[m];
1926 }
1927 nrm = PetscSqrtReal(nrm);
1928 for (PetscInt m = 0; m < map->n; m++) xrand[m] /= nrm;
1929
1930 for (int cand = 0; cand < 3; cand++) {
1931 if (!(lams[cand] > 0.0)) continue;
1932 for (PetscInt icfl = 0; icfl < ncfl; icfl++) {
1933 const PetscReal cfl = cfls[icfl], dtau = cfl/lams[cand];
1934 PetscCall(BuildStageStates(user, Ubase, dtau, Rhs, map, Rtmp, Y1, Y2, Y3));
1935 PetscCall(BuildFDJacobian(user, Y1, epsrel, Rhs, map, phi0, phip, Upert, J1));
1936 PetscCall(BuildFDJacobian(user, Y2, epsrel, Rhs, map, phi0, phip, Upert, J2));
1937 PetscCall(BuildFDJacobian(user, Y3, epsrel, Rhs, map, phi0, phip, Upert, J3));
1938 PetscCall(BuildStageTangent(J0, J1, J2, J3, dtau, map->n, T4));
1939 PetscCall(RKPolynomial(J0, dtau, map->n, Pm));
1940 PetscCall(BuildPhiJacobian(user, Ubase, dtau, Rhs, map, Rtmp,
1941 Upert, Ustage, PhiP, PhiM, phip, phi0, epsrel, JPhi));
1942
1943 const PetscReal froT = DenseFrobenius(T4, map->n);
1944 const PetscReal froPhi = DenseFrobenius(JPhi, map->n);
1945 const PetscReal relTP = DenseRelativeDiff(T4, Pm, map->n, froT);
1946 const PetscReal relPhiT = DenseRelativeDiff(JPhi, T4, map->n, froPhi);
1947 const PetscReal relPhiP = DenseRelativeDiff(JPhi, Pm, map->n, froPhi);
1948 PetscReal smaxT, rhoT, dummyT;
1949 PetscCall(DenseSpectralRadius(T4, map->n, &rhoT, &dummyT));
1950 PetscCall(DenseSigmaMax(T4, map->n, &smaxT, v1));
1951
1952 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1953 " --- convection-only stage-dependent RK tangent (cand %s, CFL=%.2f, dtau=%.6e) ---\n"
1954 " rho(T4)=%.6e sigma_max(T4)=%.6e\n"
1955 " ||T4-P(hJ0)||F/max(1,||T4||F) = %.3e\n"
1956 " ||J_Phi-T4||F/max(1,||J_Phi||F) = %.3e\n"
1957 " ||J_Phi-P(hJ0)||F/max(1,||J_Phi||F)= %.3e\n",
1958 cn[cand], (double)cfl, (double)dtau, (double)rhoT, (double)smaxT,
1959 (double)relTP, (double)relPhiT, (double)relPhiP));
1960
1961 PetscCall(FourStage(user, Ubase, dtau, Rhs, map, Rtmp, Ustage, Phi0));
1962 PetscCall(ExtractActiveVector(user, Phi0, map, phi0));
1963 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1964 " direction amp amp(meas) amp(frozen) amp(stage) err(frozen) err(stage)\n"));
1965 for (int dir = 0; dir < 2; dir++) {
1966 const PetscReal *xv = (dir == 0) ? xrand : v1;
1967 const char *dname = (dir == 0) ? "random" : "v1(T4)";
1968 for (int a = 0; a < 3; a++) {
1969 const PetscReal amp = amps[a];
1970 PetscCall(VecCopy(Ubase, Upert));
1971 PetscCall(AddActiveVector(user, Upert, map, xv, amp));
1972 PetscCall(FourStage(user, Upert, dtau, Rhs, map, Rtmp, Ustage, PhiP));
1973 PetscCall(ExtractActiveVector(user, PhiP, map, phip));
1974 for (PetscInt m = 0; m < map->n; m++) meas[m] = phip[m] - phi0[m];
1975 for (PetscInt m = 0; m < map->n; m++) xscaled[m] = amp*xv[m];
1976 ApplyP(Pm, xscaled, predP, map->n);
1977 ApplyP(T4, xscaled, predT, map->n);
1978
1979 PetscReal eP = 0.0, eT = 0.0;
1980 for (PetscInt m = 0; m < map->n; m++) {
1981 const PetscReal dP = meas[m] - predP[m];
1982 const PetscReal dT = meas[m] - predT[m];
1983 eP += dP*dP; eT += dT*dT;
1984 }
1985 const PetscReal nm = VecNorm2Array(meas, map->n);
1986 const PetscReal nP = VecNorm2Array(predP, map->n);
1987 const PetscReal nT = VecNorm2Array(predT, map->n);
1988 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1989 " %-8s %.0e %.6e %.6e %.6e %.3e %.3e\n",
1990 dname, (double)amp, (double)(nm/amp), (double)(nP/amp), (double)(nT/amp),
1991 (double)(PetscSqrtReal(eP)/PetscMax(PETSC_MACHINE_EPSILON, nP)),
1992 (double)(PetscSqrtReal(eT)/PetscMax(PETSC_MACHINE_EPSILON, nT))));
1993 }
1994 }
1995
1996 PetscCall(PicurvAssertBool((PetscBool)(relPhiT < 1e-6),
1997 "stage tangent matches direct finite-difference RK map"));
1998 if (st == STATE_B) {
1999 PetscCall(PicurvAssertBool((PetscBool)(relTP > 1e-5),
2000 "B: non-steady base makes frozen-Jacobian RK map measurably different"));
2001 PetscCall(PicurvAssertBool((PetscBool)(relPhiP > 1e-5),
2002 "B: direct RK map confirms frozen-Jacobian error"));
2003 } else {
2004 PetscCall(PicurvAssertBool((PetscBool)(relTP < 1e-8),
2005 "steady base reduces stage tangent to frozen RK polynomial"));
2006 }
2007 }
2008 }
2009
2010 PetscCall(PetscFree5(Rtmp, J1, J2, J3, T4));
2011 PetscCall(PetscFree5(Pm, JPhi, v1, xrand, meas));
2012 PetscCall(PetscFree5(phi0, phip, xscaled, predP, predT));
2013 PetscCall(VecDestroy(&Y1)); PetscCall(VecDestroy(&Y2)); PetscCall(VecDestroy(&Y3));
2014 PetscCall(VecDestroy(&Upert)); PetscCall(VecDestroy(&Ustage));
2015 PetscCall(VecDestroy(&PhiP)); PetscCall(VecDestroy(&PhiM)); PetscCall(VecDestroy(&Phi0));
2016 PetscFunctionReturn(0);
2017}
2018
2019/* ----------------------------------------------------------------------------------- *
2020 * The A4a study for one state. *
2021 * ----------------------------------------------------------------------------------- */
2022static PetscErrorCode RunState(CandState st, const char *name)
2023{
2024 SimCtx *simCtx = NULL; UserCtx *user = NULL; DofMap map;
2025 Vec Ubase, Rhs, Uwork, Uout;
2026 PetscReal *Rref, *Rrep, *Jbest;
2027 PetscReal repeat_err, maxdiv, det_err;
2028 SeamDiagnostics seam;
2030 const PetscInt N = (st == STATE_C) ? 5 : 4; /* C uses one extra point for canonical shear. */
2031 PetscFunctionBeginUser;
2032
2033 /* periodic Cartesian fixture, inviscid + centered + P=0, no LES/RANS/Clark/IB/body force. */
2034 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(&simCtx, &user, N, N, N, PETSC_TRUE, PETSC_TRUE, PETSC_TRUE));
2035 PetscCall(ConfigureCandidateFixture(simCtx, user));
2036
2037 PetscCall(DofMapBuild(user, &map));
2038 PetscCall(VecDuplicate(user->Ucont, &Ubase));
2039 PetscCall(VecDuplicate(user->Ucont, &Rhs));
2040 PetscCall(VecDuplicate(user->Ucont, &Uwork));
2041 PetscCall(VecDuplicate(user->Ucont, &Uout));
2042 PetscCall(PetscMalloc3(map.n, &Rref, map.n, &Rrep, (size_t)map.n*map.n, &Jbest));
2043
2044 PetscCall(BuildBaseState(user, st, Ubase, &repeat_err, &maxdiv, &seam));
2045
2046 /* residual determinism: evaluate the base residual twice. */
2047 PetscCall(EvalConvResidual(user, Ubase, Rhs, &map, Rref));
2048 PetscCall(EvalConvResidual(user, Ubase, Rhs, &map, Rrep));
2049 det_err = 0.0; for (PetscInt m = 0; m < map.n; m++) det_err = PetscMax(det_err, PetscAbsReal(Rref[m]-Rrep[m]));
2050 const PetscReal R0_2 = VecNorm2Array(Rref, map.n);
2051 const PetscReal R0_inf = VecNormInfArray(Rref, map.n);
2052
2053 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
2054 "\n================ STATE %s ================\n"
2055 " grid: DMDA %d^3 (periodic) | independent face DOFs: actual=%d expected=%d\n"
2056 " declared endpoint mismatch (x,y,z) = %.3e %.3e %.3e\n"
2057 " actual Ucat duplicate mismatch (x,y,z) = %.3e %.3e %.3e\n"
2058 " local lUcat ghost mismatch (x,y,z) = %.3e %.3e %.3e\n"
2059 " actual Ucont duplicate mismatch (x,y,z) = %.3e %.3e %.3e\n"
2060 " ||Ucat_repeat - Ucat_reference||inf = %.3e\n"
2061 " max|div_h Ucont| = %.3e\n"
2062 " ||R(U0)||2 = %.6e ||R(U0)||inf = %.6e\n"
2063 " residual determinism ||R_rep-R_ref||inf = %.3e\n",
2064 name, (int)(N+1), (int)map.n,
2065 (int)map.expected_n,
2066 (double)seam.declared[0], (double)seam.declared[1], (double)seam.declared[2],
2067 (double)seam.ucat_global[0], (double)seam.ucat_global[1], (double)seam.ucat_global[2],
2068 (double)seam.ucat_ghost[0], (double)seam.ucat_ghost[1], (double)seam.ucat_ghost[2],
2069 (double)seam.ucont_global[0], (double)seam.ucont_global[1], (double)seam.ucont_global[2],
2070 (double)repeat_err, (double)maxdiv,
2071 (double)R0_2, (double)R0_inf, (double)det_err));
2072 if (st == STATE_A) PetscCall(PrintPeriodicSpaceAudit(user, &map));
2073
2074 /* ---- epsilon-convergence study: build J at several eps, compare to next-finer ---- */
2075 const PetscReal epsrel[5] = {1e-4, 1e-5, 1e-6, 1e-7, 1e-8};
2076 PetscReal *Jprev, *Jcur;
2077 PetscCall(PetscMalloc2((size_t)map.n*map.n, &Jprev, (size_t)map.n*map.n, &Jcur));
2078 PetscReal best_rel = PETSC_MAX_REAL; PetscInt best_e = 2;
2079 PetscCall(PetscPrintf(PETSC_COMM_WORLD, " epsilon-convergence (||J_e - J_e/10||_F / ||J||_F):\n"));
2080 for (int e = 0; e < 5; e++) {
2081 /* build column-major J at epsrel[e] */
2082 for (PetscInt col = 0; col < map.n; col++) {
2083 PetscReal u0; PetscCall(GetDof(user, Ubase, &map, col, &u0));
2084 const PetscReal eps = epsrel[e]*PetscMax(1.0, PetscAbsReal(u0));
2085 PetscReal *Rp = Rref, *Rm = Rrep; /* reuse scratch */
2086 PetscCall(VecCopy(Ubase, Uwork));
2087 PetscCall(PerturbDof(user, Uwork, &map, col, +eps));
2088 PetscCall(EvalConvResidual(user, Uwork, Rhs, &map, Rp));
2089 PetscCall(VecCopy(Ubase, Uwork));
2090 PetscCall(PerturbDof(user, Uwork, &map, col, -eps));
2091 PetscCall(EvalConvResidual(user, Uwork, Rhs, &map, Rm));
2092 for (PetscInt row = 0; row < map.n; row++) Jcur[row + col*map.n] = (Rp[row]-Rm[row])/(2.0*eps);
2093 }
2094 if (e > 0) {
2095 PetscReal num = 0.0, den = 0.0;
2096 for (PetscInt t = 0; t < map.n*map.n; t++) { const PetscReal d = Jprev[t]-Jcur[t]; num += d*d; den += Jcur[t]*Jcur[t]; }
2097 const PetscReal rel = PetscSqrtReal(num)/PetscMax(1.0, PetscSqrtReal(den));
2098 PetscCall(PetscPrintf(PETSC_COMM_WORLD, " eps=%.0e -> %.0e : rel=%.3e\n",
2099 (double)epsrel[e-1], (double)epsrel[e], (double)rel));
2100 if (rel < best_rel) { best_rel = rel; best_e = e; }
2101 }
2102 if (st == STATE_A) {
2103 PetscReal erho, emaxre, esmax;
2104 PetscCall(DenseSpectralRadius(Jcur, map.n, &erho, &emaxre));
2105 PetscCall(DenseSigmaMax(Jcur, map.n, &esmax, NULL));
2106 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
2107 " eps=%.0e metrics: rho=%.6e sigma=%.6e max_real=%.6e fro=%.6e skew=%.3e\n",
2108 (double)epsrel[e], (double)erho, (double)esmax, (double)emaxre,
2109 (double)DenseFrobenius(Jcur, map.n), (double)DenseSkewnessDefect(Jcur, map.n)));
2110 }
2111 PetscCall(PetscArraycpy(Jprev, Jcur, (size_t)map.n*map.n)); /* prev <- current (no pointer swap) */
2112 }
2113 /* rebuild J at the plateau epsilon into Jbest */
2114 {
2115 const PetscReal er = epsrel[best_e];
2116 for (PetscInt col = 0; col < map.n; col++) {
2117 PetscReal u0; PetscCall(GetDof(user, Ubase, &map, col, &u0));
2118 const PetscReal eps = er*PetscMax(1.0, PetscAbsReal(u0));
2119 PetscCall(VecCopy(Ubase, Uwork)); PetscCall(PerturbDof(user, Uwork, &map, col, +eps));
2120 PetscCall(EvalConvResidual(user, Uwork, Rhs, &map, Rref));
2121 PetscCall(VecCopy(Ubase, Uwork)); PetscCall(PerturbDof(user, Uwork, &map, col, -eps));
2122 PetscCall(EvalConvResidual(user, Uwork, Rhs, &map, Rrep));
2123 for (PetscInt row = 0; row < map.n; row++) Jbest[row + col*map.n] = (Rref[row]-Rrep[row])/(2.0*eps);
2124 }
2125 PetscCall(PetscPrintf(PETSC_COMM_WORLD, " selected plateau eps = %.0e\n", (double)er));
2126 }
2127 PetscCall(PetscFree2(Jprev, Jcur));
2128
2129 /* The FD probes leave the production vectors at the final perturbed evaluation;
2130 restore the base state before every downstream diagnostic. */
2131 PetscCall(VecCopy(Ubase, user->Ucont));
2132 {
2133 const char *fld[] = {"Ucont"};
2134 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fld));
2135 }
2136
2137 /* base state must be restored after the Jacobian build. */
2138 PetscBool eqbase; { Vec chk; PetscCall(VecDuplicate(Ubase,&chk)); PetscCall(VecCopy(user->Ucont,chk));
2139 PetscCall(VecAXPY(chk, -1.0, Ubase)); PetscReal nb; PetscCall(VecNorm(chk, NORM_INFINITY, &nb));
2140 eqbase = (PetscBool)(nb < 1e-12); PetscCall(VecDestroy(&chk));
2141 PetscCall(PetscPrintf(PETSC_COMM_WORLD, " base Ucont restored after Jacobian: %s\n", eqbase?"yes":"NO")); }
2142
2143 /* The FD operator acts on Ucont; J is on the convective residual (sign per ComputeRHS). */
2144 PetscReal rho, maxre, smax;
2145 PetscCall(DenseSpectralRadius(Jbest, map.n, &rho, &maxre));
2146 PetscCall(DenseSigmaMax(Jbest, map.n, &smax, NULL));
2147 const PetscReal eta = DenseNonNormality(Jbest, map.n);
2148
2149 /* ---- candidate estimates (convection-only: lambda_cX = lambda_X - lambda_t) ---- */
2150 PetscCall(EvalConvResidual(user, Ubase, Rhs, &map, Rref)); /* restore lUcat consistent w/ base */
2151 PetscCall(UpdateLocalGhosts(user, "Ucont"));
2152 PetscCall(Contra2Cart(user)); PetscCall(UpdateLocalGhosts(user, "Ucat"));
2153 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
2154 const PetscReal lcB = rep.lambda_B - rep.lambda_t;
2155 const PetscReal lcC = rep.lambda_C - rep.lambda_t;
2156 const PetscReal lcD = rep.lambda_D - rep.lambda_t;
2157 PetscReal gradmax = 0.0;
2158 PetscCall(ComputeMaxGradientContribution(user, &gradmax));
2159
2160 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
2161 " --- convective Jacobian spectrum ---\n"
2162 " rho(J)=%.6e sigma_max(J)=%.6e nonnormality=%.3e max_real_eig=%.3e\n"
2163 " --- candidate convective estimates ---\n"
2164 " lambda_cB=%.6e lambda_cC=%.6e lambda_cD=%.6e\n"
2165 " max local |grad u| row-sum contribution = %.6e\n"
2166 " rB/rho=%.3f rC/rho=%.3f rD/rho=%.3f | rB/smax=%.3f rC/smax=%.3f rD/smax=%.3f\n",
2167 (double)rho, (double)smax, (double)eta, (double)maxre,
2168 (double)lcB, (double)lcC, (double)lcD,
2169 (double)gradmax,
2170 (double)(lcB/rho), (double)(lcC/rho), (double)(lcD/rho),
2171 (double)(lcB/smax), (double)(lcC/smax), (double)(lcD/smax)));
2172 if (st == STATE_A) {
2173 PetscCall(CheckDenseJacobianAction(user, Ubase, Rhs, Uwork, &map, Jbest, epsrel[best_e]));
2174 PetscCall(RunStateADirectionalMechanismAudit(Jbest, user, Ubase, Rhs, &map, epsrel[best_e]));
2175 PetscCall(PrintBlockAndSymmetricLocalization(user, &map, Jbest));
2176 PetscCall(PrintFourierChecks(&map, Jbest));
2177 PetscCall(TraceStateAResidualStages(user, Ubase, Rhs, &map, Jbest));
2178 PetscCall(RunStateAResidualPathIsolation(user, Ubase, Rhs, Uwork, &map, epsrel[best_e]));
2179 }
2180
2181 /* ---- RK pseudo-CFL stability per candidate (convective Jacobian) ---- */
2182 const PetscReal lams[3] = {lcB, lcC, lcD}; const char *cn[3] = {"B","C","D"};
2183 PetscReal *Jpseudo;
2184 PetscCall(PetscMalloc1((size_t)map.n*map.n, &Jpseudo));
2185 PetscCall(DenseShiftIdentity(Jbest, map.n, -rep.lambda_t, Jpseudo));
2186 const PetscReal lams_full[3] = {rep.lambda_B, rep.lambda_C, rep.lambda_D};
2187 PetscCall(PrintFrozenAmplificationTable("CONVECTION-ONLY FROZEN JACOBIAN", Jbest, map.n, lams, cn));
2188 PetscCall(PrintFrozenAmplificationTable("COMPLETE PSEUDO-TIME FROZEN OPERATOR", Jpseudo, map.n, lams_full, cn));
2189 PetscCall(PetscFree(Jpseudo));
2190
2191 PetscCall(PetscPrintf(PETSC_COMM_WORLD, " --- RK stable pseudo-CFL (convective J) ---\n"));
2192 for (int c = 0; c < 3; c++) {
2193 if (!(lams[c] > 0.0)) continue;
2194 StableCFLResult cfl_rho, cfl_nrm;
2195 PetscCall(StableCFL(Jbest, map.n, lams[c], METRIC_RHO, &cfl_rho));
2196 PetscCall(StableCFL(Jbest, map.n, lams[c], METRIC_NORM, &cfl_nrm));
2197 PetscCall(PrintStableCFLLine(cn[c], cfl_rho, cfl_nrm));
2198 }
2199
2200 /* ---- exact stage-dependent tangent map and direct nonlinear RK cross-check ---- */
2201 if (lcC > 0.0) PetscCall(RunRKTangentDiagnostics(user, st, Ubase, Rhs, &map,
2202 epsrel[best_e], lams, cn, Jbest));
2203
2204 /* ---- robust automated assertions per state ---- */
2205 PetscCall(PicurvAssertBool(eqbase, "base Ucont restored after Jacobian build"));
2206 PetscCall(PicurvAssertBool((PetscBool)(det_err < 1e-10), "residual evaluation deterministic"));
2207 PetscCall(PicurvAssertBool((PetscBool)(rho > 0.0 && PetscIsNormalReal(rho)), "finite rho(J)"));
2208 PetscCall(PicurvAssertBool((PetscBool)(smax > 0.0 && PetscIsNormalReal(smax)), "finite sigma_max(J)"));
2209 if (st == STATE_A) {
2210 for (int d = 0; d < 3; d++) {
2211 PetscCall(PicurvAssertRealNear(seam.declared[d], 0.0, 1e-12, "A: declared periodic seam"));
2212 PetscCall(PicurvAssertRealNear(seam.ucat_global[d], 0.0, 1e-12, "A: Ucat duplicate seam"));
2213 PetscCall(PicurvAssertRealNear(seam.ucat_ghost[d], 0.0, 1e-12, "A: lUcat ghost seam"));
2214 PetscCall(PicurvAssertRealNear(seam.ucont_global[d], 0.0, 1e-12, "A: Ucont duplicate seam"));
2215 }
2216 PetscCall(PicurvAssertRealNear(repeat_err, 0.0, 1e-9, "A: recovered Ucat repeat near roundoff"));
2217 PetscCall(PicurvAssertRealNear(R0_2, 0.0, 1e-12, "A: base residual 2-norm near roundoff"));
2218 PetscCall(PicurvAssertRealNear(R0_inf, 0.0, 1e-12, "A: base residual inf-norm near roundoff"));
2219 PetscCall(PicurvAssertRealNear(lcB, lcC, 1e-9, "A: B == C (no divergence)"));
2220 PetscCall(PicurvAssertRealNear(lcC, lcD, 1e-9, "A: C == D (no shear)"));
2221 } else if (st == STATE_B) {
2222 for (int d = 0; d < 3; d++) {
2223 PetscCall(PicurvAssertRealNear(seam.declared[d], 0.0, 1e-12, "B: declared periodic seam"));
2224 PetscCall(PicurvAssertRealNear(seam.ucat_global[d], 0.0, 1e-12, "B: Ucat duplicate seam"));
2225 PetscCall(PicurvAssertRealNear(seam.ucat_ghost[d], 0.0, 1e-12, "B: lUcat ghost seam"));
2226 PetscCall(PicurvAssertRealNear(seam.ucont_global[d], 0.0, 1e-12, "B: Ucont duplicate seam"));
2227 }
2228 PetscCall(PicurvAssertRealNear(repeat_err, 0.0, 1e-9, "B: recovered Ucat repeat near roundoff"));
2229 PetscCall(PicurvAssertBool((PetscBool)(maxdiv > 1e-3), "B: nonzero discrete divergence"));
2230 PetscCall(PicurvAssertBool((PetscBool)(R0_2 > 1e-3), "B: materially nonzero base residual 2-norm"));
2231 PetscCall(PicurvAssertBool((PetscBool)(R0_inf > 1e-3), "B: materially nonzero base residual inf-norm"));
2232 PetscCall(PicurvAssertBool((PetscBool)(lcC > lcB + 1e-9), "B: C > B"));
2233 } else {
2234 for (int d = 0; d < 3; d++) {
2235 PetscCall(PicurvAssertRealNear(seam.declared[d], 0.0, 1e-12, "C: declared periodic seam"));
2236 PetscCall(PicurvAssertRealNear(seam.ucat_global[d], 0.0, 1e-12, "C: Ucat duplicate seam"));
2237 PetscCall(PicurvAssertRealNear(seam.ucat_ghost[d], 0.0, 1e-12, "C: lUcat ghost seam"));
2238 PetscCall(PicurvAssertRealNear(seam.ucont_global[d], 0.0, 1e-12, "C: Ucont duplicate seam"));
2239 }
2240 PetscCall(PicurvAssertRealNear(repeat_err, 0.0, 1e-9, "C: recovered Ucat repeat near roundoff"));
2241 PetscCall(PicurvAssertBool((PetscBool)(maxdiv < 1e-6), "C: divergence near zero"));
2242 PetscCall(PicurvAssertRealNear(R0_2, 0.0, 1e-12, "C: base residual 2-norm near roundoff"));
2243 PetscCall(PicurvAssertRealNear(R0_inf, 0.0, 1e-12, "C: base residual inf-norm near roundoff"));
2244 PetscCall(PicurvAssertBool((PetscBool)(PetscAbsReal(lcC-lcB) < 1e-6), "C: C ~= B"));
2245 PetscCall(PicurvAssertBool((PetscBool)(gradmax > 1e-6), "C: canonical shear has nonzero local gradient contribution"));
2246 }
2247
2248 PetscCall(PetscFree3(Rref, Rrep, Jbest));
2249 PetscCall(VecDestroy(&Ubase)); PetscCall(VecDestroy(&Rhs));
2250 PetscCall(VecDestroy(&Uwork)); PetscCall(VecDestroy(&Uout));
2251 PetscCall(DofMapDestroy(&map));
2252 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
2253 PetscFunctionReturn(0);
2254}
2255
2256/**
2257 * @brief Runs the State A candidate harness.
2258 */
2259static PetscErrorCode TestStateA(void) { return RunState(STATE_A, "A (uniform div-free)"); }
2260
2261/**
2262 * @brief Runs the State B candidate harness.
2263 */
2264static PetscErrorCode TestStateB(void) { return RunState(STATE_B, "B (nonzero divergence)"); }
2265
2266/**
2267 * @brief Runs the State C candidate harness.
2268 */
2269static PetscErrorCode TestStateC(void) { return RunState(STATE_C, "C (div-free shear)"); }
2270
2271/**
2272 * @brief Runs one State A grid-size audit case.
2273 */
2274static PetscErrorCode RunStateAGridAuditOne(PetscInt N)
2275{
2276 SimCtx *simCtx = NULL; UserCtx *user = NULL; DofMap map;
2277 Vec Ubase, Rhs, Uwork;
2278 PetscReal *Rp, *Rm, *J;
2279 PetscReal repeat_err, maxdiv;
2280 SeamDiagnostics seam;
2281 PetscFunctionBeginUser;
2282 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(&simCtx, &user, N, N, N, PETSC_TRUE, PETSC_TRUE, PETSC_TRUE));
2283 PetscCall(ConfigureCandidateFixture(simCtx, user));
2284 PetscCall(DofMapBuild(user, &map));
2285 PetscCall(VecDuplicate(user->Ucont, &Ubase));
2286 PetscCall(VecDuplicate(user->Ucont, &Rhs));
2287 PetscCall(VecDuplicate(user->Ucont, &Uwork));
2288 PetscCall(PetscMalloc3(map.n, &Rp, map.n, &Rm, (size_t)map.n*map.n, &J));
2289 PetscCall(BuildBaseState(user, STATE_A, Ubase, &repeat_err, &maxdiv, &seam));
2290 PetscCall(BuildFDJacobian(user, Ubase, 1e-5, Rhs, &map, Rp, Rm, Uwork, J));
2291 PetscReal rho, maxre, smax;
2292 PetscCall(DenseSpectralRadius(J, map.n, &rho, &maxre));
2293 PetscCall(DenseSigmaMax(J, map.n, &smax, NULL));
2294 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
2295 " State A grid audit: DMDA %d^3 independent=%d expected=%d max_real=%.6e rho=%.6e sigma=%.6e skew=%.3e nonnormality=%.3e repeat=%.3e\n",
2296 (int)(N+1), (int)map.n, (int)map.expected_n, (double)maxre, (double)rho, (double)smax,
2297 (double)DenseSkewnessDefect(J, map.n), (double)DenseNonNormality(J, map.n), (double)repeat_err));
2298 if (N >= 5) {
2299 PetscReal *vr, *vi;
2300 PetscReal lamr, lami, v2 = 0.0, seam2 = 0.0, maxabs = 0.0;
2301 PetscCall(PetscMalloc2(map.n, &vr, map.n, &vi));
2302 PetscCall(DenseMaxRealRightEigenpair(J, map.n, &lamr, &lami, vr, vi));
2303 for (PetscInt m = 0; m < map.n; m++) {
2304 const PetscReal a2 = vr[m]*vr[m] + vi[m]*vi[m];
2305 v2 += a2;
2306 if (DofTouchesPeriodicRepresentative(&map, m, user->info)) seam2 += a2;
2307 maxabs = PetscMax(maxabs, PetscSqrtReal(a2));
2308 }
2309 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
2310 " larger-grid eigenvector: lambda=%.6e%+.6ei seam_energy=%.3f max_entry/||v||=%.3e\n",
2311 (double)lamr, (double)lami, (double)(seam2/PetscMax(v2, PETSC_MACHINE_EPSILON)),
2312 (double)(maxabs/PetscSqrtReal(PetscMax(v2, PETSC_MACHINE_EPSILON)))));
2313 PetscCall(PrintBlockAndSymmetricLocalization(user, &map, J));
2314 PetscCall(PetscFree2(vr, vi));
2315 }
2316 PetscCall(PetscFree3(Rp, Rm, J));
2317 PetscCall(VecDestroy(&Ubase)); PetscCall(VecDestroy(&Rhs)); PetscCall(VecDestroy(&Uwork));
2318 PetscCall(DofMapDestroy(&map));
2319 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
2320 PetscFunctionReturn(0);
2321}
2322
2323/**
2324 * @brief Runs one State A transport-direction split.
2325 */
2326static PetscErrorCode RunStateASplitOne(CandState st, const char *label)
2327{
2328 SimCtx *simCtx = NULL; UserCtx *user = NULL; DofMap map;
2329 Vec Ubase, Rhs, Uwork;
2330 PetscReal *Rp, *Rm, *J;
2331 PetscReal repeat_err, maxdiv;
2332 SeamDiagnostics seam;
2333 PetscFunctionBeginUser;
2334 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(&simCtx, &user, 4, 4, 4, PETSC_TRUE, PETSC_TRUE, PETSC_TRUE));
2335 PetscCall(ConfigureCandidateFixture(simCtx, user));
2336 PetscCall(DofMapBuild(user, &map));
2337 PetscCall(VecDuplicate(user->Ucont, &Ubase));
2338 PetscCall(VecDuplicate(user->Ucont, &Rhs));
2339 PetscCall(VecDuplicate(user->Ucont, &Uwork));
2340 PetscCall(PetscMalloc3(map.n, &Rp, map.n, &Rm, (size_t)map.n*map.n, &J));
2341 PetscCall(BuildBaseState(user, st, Ubase, &repeat_err, &maxdiv, &seam));
2342 PetscCall(BuildFDJacobian(user, Ubase, 1e-5, Rhs, &map, Rp, Rm, Uwork, J));
2343 PetscReal rho, maxre, smax;
2344 PetscCall(DenseSpectralRadius(J, map.n, &rho, &maxre));
2345 PetscCall(DenseSigmaMax(J, map.n, &smax, NULL));
2346 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
2347 " %-4s rho=%.6e sigma=%.6e max_real=%.6e skew=%.3e nonnormality=%.3e div=%.3e repeat=%.3e\n",
2348 label, (double)rho, (double)smax, (double)maxre,
2349 (double)DenseSkewnessDefect(J, map.n), (double)DenseNonNormality(J, map.n),
2350 (double)maxdiv, (double)repeat_err));
2351 PetscCall(PetscFree3(Rp, Rm, J));
2352 PetscCall(VecDestroy(&Ubase)); PetscCall(VecDestroy(&Rhs)); PetscCall(VecDestroy(&Uwork));
2353 PetscCall(DofMapDestroy(&map));
2354 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
2355 PetscFunctionReturn(0);
2356}
2357
2358/**
2359 * @brief Runs the State A grid-dependence and active-space audit.
2360 */
2361static PetscErrorCode TestStateAGridAudit(void)
2362{
2363 PetscFunctionBeginUser;
2364 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
2365 "\n================ STATE A GRID / ACTIVE-SPACE AUDIT ================\n"
2366 " residual sign convention: ComputeRHS returns the production convection residual used by pseudo-time updates.\n"
2367 " component-staggered periodic duplicate planes: 0<-m-2 and m-1<-1 in each direction.\n"
2368 " independent map: all three Ucont components use representatives i,j,k=1..m-2; count = 3*(m-2)^3.\n"));
2369 PetscCall(RunStateAGridAuditOne(4));
2370 PetscCall(RunStateAGridAuditOne(5));
2371 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
2372 " State A transport-direction split on DMDA 5^3:\n"));
2373 PetscCall(RunStateASplitOne(STATE_A_X, "A-x"));
2374 PetscCall(RunStateASplitOne(STATE_A_Y, "A-y"));
2375 PetscCall(RunStateASplitOne(STATE_A, "A-xy"));
2376 PetscFunctionReturn(0);
2377}
2378
2379/**
2380 * @brief Writes the one-rank State A matrix-free decomposition reference.
2381 */
2383 GlobalVecStats phi, const MomStabilityReport *rep)
2384{
2385 PetscMPIInt rank;
2386 PetscFunctionBeginUser;
2387 if (!g_ref_path_set) PetscFunctionReturn(0);
2388 PetscCheck(g_ref_token_set, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE,
2389 "-candidate_ref_token is required when -candidate_ref_path is set");
2390 PetscCallMPI(MPI_Comm_rank(PETSC_COMM_WORLD, &rank));
2391 if (rank == 0) {
2392 FILE *fp = fopen(g_ref_path, "w");
2393 PetscCheck(fp != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "could not write State A MPI reference");
2394 fprintf(fp, "PICURV_CANDIDATE_STATEA_REF_V2 %s\n", g_ref_token);
2395 fprintf(fp, "%.17e %.17e %.17e\n", (double)r0.n2, (double)r0.ninf, (double)r0.checksum);
2396 fprintf(fp, "%.17e %.17e %.17e\n", (double)jv.n2, (double)jv.ninf, (double)jv.checksum);
2397 fprintf(fp, "%.17e %.17e %.17e\n", (double)phi.n2, (double)phi.ninf, (double)phi.checksum);
2398 fprintf(fp, "%.17e %.17e %.17e %d %d %d %d\n",
2399 (double)(rep->lambda_B - rep->lambda_t),
2400 (double)(rep->lambda_C - rep->lambda_t),
2401 (double)(rep->lambda_D - rep->lambda_t),
2402 (int)rep->active_cells, (int)rep->cblock, (int)rep->ci, (int)rep->cj);
2403 fclose(fp);
2404 }
2405 PetscFunctionReturn(0);
2406}
2407
2408/**
2409 * @brief Compares a distributed State A matrix-free check against the one-rank reference.
2410 */
2412 GlobalVecStats phi, const MomStabilityReport *rep)
2413{
2414 PetscMPIInt rank;
2415 PetscReal vals[16] = {0.0};
2416 PetscFunctionBeginUser;
2417 PetscCheck(g_ref_path_set && g_ref_token_set, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE,
2418 "two-rank State A decomp check requires matching -candidate_ref_path and -candidate_ref_token from a preceding one-rank run");
2419 PetscCallMPI(MPI_Comm_rank(PETSC_COMM_WORLD, &rank));
2420 if (rank == 0) {
2421 char magic[64], token[128];
2422 FILE *fp = fopen(g_ref_path, "r");
2423 PetscCheck(fp != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN,
2424 "State A one-rank decomp reference missing; run the Makefile target so -n 1 precedes -n 2");
2425 PetscCheck(fscanf(fp, "%63s %127s", magic, token) == 2, PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "bad reference header");
2426 PetscCheck(strcmp(magic, "PICURV_CANDIDATE_STATEA_REF_V2") == 0, PETSC_COMM_SELF, PETSC_ERR_FILE_READ,
2427 "bad reference magic");
2428 PetscCheck(strcmp(token, g_ref_token) == 0, PETSC_COMM_SELF, PETSC_ERR_FILE_READ,
2429 "State A one-rank decomp reference token mismatch");
2430 int active, cblock, ci, cj;
2431 PetscCheck(fscanf(fp, "%le %le %le", &vals[0], &vals[1], &vals[2]) == 3, PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "bad reference R0");
2432 PetscCheck(fscanf(fp, "%le %le %le", &vals[3], &vals[4], &vals[5]) == 3, PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "bad reference Jv");
2433 PetscCheck(fscanf(fp, "%le %le %le", &vals[6], &vals[7], &vals[8]) == 3, PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "bad reference Phi");
2434 PetscCheck(fscanf(fp, "%le %le %le %d %d %d %d", &vals[9], &vals[10], &vals[11],
2435 &active, &cblock, &ci, &cj) == 7, PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "bad reference estimator");
2436 vals[12] = (PetscReal)active; vals[13] = (PetscReal)cblock; vals[14] = (PetscReal)ci; vals[15] = (PetscReal)cj;
2437 fclose(fp);
2438 PetscCheck(remove(g_ref_path) == 0, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN,
2439 "could not remove consumed State A MPI reference '%s'", g_ref_path);
2440 }
2441 PetscCallMPI(MPI_Bcast(vals, 16, MPIU_REAL, 0, PETSC_COMM_WORLD));
2442 PetscCall(PicurvAssertRealNear(r0.n2, vals[0], 1e-10, "MPI State A R0 L2"));
2443 PetscCall(PicurvAssertRealNear(r0.ninf, vals[1], 1e-10, "MPI State A R0 Linf"));
2444 PetscCall(PicurvAssertRealNear(r0.checksum, vals[2], 1e-10, "MPI State A R0 checksum"));
2445 PetscCall(PicurvAssertRealNear(jv.n2, vals[3], 1e-8, "MPI State A Jv L2"));
2446 PetscCall(PicurvAssertRealNear(jv.ninf, vals[4], 1e-8, "MPI State A Jv Linf"));
2447 PetscCall(PicurvAssertRealNear(jv.checksum, vals[5], 1e-8, "MPI State A Jv checksum"));
2448 PetscCall(PicurvAssertRealNear(phi.n2, vals[6], 1e-8, "MPI State A DPhi v L2"));
2449 PetscCall(PicurvAssertRealNear(phi.ninf, vals[7], 1e-8, "MPI State A DPhi v Linf"));
2450 PetscCall(PicurvAssertRealNear(phi.checksum, vals[8], 1e-8, "MPI State A DPhi v checksum"));
2451 PetscCall(PicurvAssertRealNear(rep->lambda_B - rep->lambda_t, vals[9], 1e-10, "MPI State A lambda_cB"));
2452 PetscCall(PicurvAssertRealNear(rep->lambda_C - rep->lambda_t, vals[10], 1e-10, "MPI State A lambda_cC"));
2453 PetscCall(PicurvAssertRealNear(rep->lambda_D - rep->lambda_t, vals[11], 1e-10, "MPI State A lambda_cD"));
2454 PetscCall(PicurvAssertBool((PetscBool)(rep->active_cells == (PetscInt)vals[12]), "MPI State A active cell count"));
2455 PetscCall(PicurvAssertBool((PetscBool)(rep->cblock == (PetscInt)vals[13]), "MPI State A controlling block"));
2456 PetscFunctionReturn(0);
2457}
2458
2459/**
2460 * @brief Runs State A matrix-free residual, Jv, and four-stage MPI decomposition checks.
2461 */
2462static PetscErrorCode StateADecompDirectionalCheck(UserCtx *user, Vec Ubase, Vec Rhs,
2463 PetscReal lcC, const MomStabilityReport *rep)
2464{
2465 DofMap map;
2466 Vec V, Up, Um, PhiP, PhiM, Ustage;
2467 PetscReal *v, *R0, *Rp, *Rm, *ActP, *ActM, *Out;
2468 GlobalVecStats sR0, sJv, sPhi;
2469 const PetscReal eps = 1e-6, dtau = 0.5/lcC;
2470 PetscMPIInt size;
2471 PetscFunctionBeginUser;
2472 PetscCallMPI(MPI_Comm_size(PETSC_COMM_WORLD, &size));
2473 PetscCall(DofMapBuildOwned(user, &map));
2474 PetscCall(VecDuplicate(Ubase, &V)); PetscCall(VecDuplicate(Ubase, &Up));
2475 PetscCall(VecDuplicate(Ubase, &Um)); PetscCall(VecDuplicate(Ubase, &PhiP));
2476 PetscCall(VecDuplicate(Ubase, &PhiM)); PetscCall(VecDuplicate(Ubase, &Ustage));
2477 PetscCall(PetscMalloc6(map.n, &v, map.n, &R0, map.n, &Rp, map.n, &Rm, map.n, &ActP, map.n, &ActM));
2478 PetscCall(PetscMalloc1(map.n, &Out));
2479 PetscCall(FillDeterministicDirection(user, V, &map, v));
2480
2481 PetscCall(EvalConvResidual(user, Ubase, Rhs, &map, R0));
2482 PetscCall(ActiveStats(&map, R0, &sR0));
2483 PetscCall(VecCopy(Ubase, Up)); PetscCall(VecAXPY(Up, eps, V));
2484 PetscCall(VecCopy(Ubase, Um)); PetscCall(VecAXPY(Um, -eps, V));
2485 PetscCall(EvalConvResidual(user, Up, Rhs, &map, Rp));
2486 PetscCall(EvalConvResidual(user, Um, Rhs, &map, Rm));
2487 for (PetscInt m = 0; m < map.n; m++) Out[m] = (Rp[m]-Rm[m])/(2.0*eps);
2488 PetscCall(ActiveStats(&map, Out, &sJv));
2489
2490 PetscCall(FourStage(user, Up, dtau, Rhs, &map, Rp, Ustage, PhiP));
2491 PetscCall(FourStage(user, Um, dtau, Rhs, &map, Rm, Ustage, PhiM));
2492 PetscCall(ExtractActiveVector(user, PhiP, &map, ActP));
2493 PetscCall(ExtractActiveVector(user, PhiM, &map, ActM));
2494 for (PetscInt m = 0; m < map.n; m++) Out[m] = (ActP[m]-ActM[m])/(2.0*eps);
2495 PetscCall(ActiveStats(&map, Out, &sPhi));
2496
2497 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
2498 " State A matrix-free MPI check: R0 L2=%.6e Linf=%.6e checksum=%.6e\n"
2499 " Jv L2=%.6e Linf=%.6e checksum=%.6e\n"
2500 " DPhi v L2=%.6e Linf=%.6e checksum=%.6e\n",
2501 (double)sR0.n2, (double)sR0.ninf, (double)sR0.checksum,
2502 (double)sJv.n2, (double)sJv.ninf, (double)sJv.checksum,
2503 (double)sPhi.n2, (double)sPhi.ninf, (double)sPhi.checksum));
2504 if (size == 1) PetscCall(WriteStateADecompReference(sR0, sJv, sPhi, rep));
2505 else PetscCall(ReadAndCompareStateADecompReference(sR0, sJv, sPhi, rep));
2506
2507 PetscCall(PetscFree6(v, R0, Rp, Rm, ActP, ActM)); PetscCall(PetscFree(Out));
2508 PetscCall(VecDestroy(&V)); PetscCall(VecDestroy(&Up)); PetscCall(VecDestroy(&Um));
2509 PetscCall(VecDestroy(&PhiP)); PetscCall(VecDestroy(&PhiM)); PetscCall(VecDestroy(&Ustage));
2510 PetscCall(DofMapDestroy(&map));
2511 PetscFunctionReturn(0);
2512}
2513
2514/**
2515 * @brief Runs one decomp baseline and compares scalar estimates with regenerated references.
2516 */
2517static PetscErrorCode RunDecompBaseline(CandState st, const char *name,
2518 PetscReal expB, PetscReal expC, PetscReal expD)
2519{
2520 SimCtx *simCtx = NULL; UserCtx *user = NULL;
2521 Vec Ubase, Rhs;
2522 PetscReal repeat_err, maxdiv;
2523 SeamDiagnostics seam;
2525 const PetscInt N = 8;
2526 PetscFunctionBeginUser;
2527
2528 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(&simCtx, &user, N, N, N, PETSC_TRUE, PETSC_TRUE, PETSC_TRUE));
2529 PetscCall(ConfigureCandidateFixture(simCtx, user));
2530 PetscCall(VecDuplicate(user->Ucont, &Ubase));
2531 PetscCall(VecDuplicate(user->Ucont, &Rhs));
2532 PetscCall(BuildBaseState(user, st, Ubase, &repeat_err, &maxdiv, &seam));
2533 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
2534
2535 const PetscReal lcB = rep.lambda_B - rep.lambda_t;
2536 const PetscReal lcC = rep.lambda_C - rep.lambda_t;
2537 const PetscReal lcD = rep.lambda_D - rep.lambda_t;
2538 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
2539 "\n================ DECOMP BASELINE %s ================\n"
2540 " active cells: %d | repeat=%.3e | max|div_h Ucont|=%.3e\n"
2541 " lambda_cB=%.6e lambda_cC=%.6e lambda_cD=%.6e\n",
2542 name, (int)rep.active_cells, (double)repeat_err, (double)maxdiv,
2543 (double)lcB, (double)lcC, (double)lcD));
2544
2545 PetscCall(PicurvAssertBool((PetscBool)(rep.active_cells == 343), "decomp: active-cell count invariant"));
2546 PetscCall(PicurvAssertRealNear(expB, lcB, 5e-7, "decomp: lambda_cB matches one-rank baseline"));
2547 PetscCall(PicurvAssertRealNear(expC, lcC, 5e-7, "decomp: lambda_cC matches one-rank baseline"));
2548 PetscCall(PicurvAssertRealNear(expD, lcD, 5e-7, "decomp: lambda_cD matches one-rank baseline"));
2549 if (st == STATE_A) {
2550 PetscCall(StateADecompDirectionalCheck(user, Ubase, Rhs, lcC, &rep));
2551 PetscCall(PicurvAssertRealNear(maxdiv, 0.0, 1e-12, "decomp A: divergence-free"));
2552 PetscCall(PicurvAssertRealNear(lcB, lcC, 1e-12, "decomp A: B == C"));
2553 PetscCall(PicurvAssertRealNear(lcC, lcD, 1e-12, "decomp A: C == D"));
2554 } else if (st == STATE_B) {
2555 PetscCall(PicurvAssertBool((PetscBool)(maxdiv > 1e-3), "decomp B: nonzero divergence"));
2556 PetscCall(PicurvAssertBool((PetscBool)(lcC > lcB), "decomp B: C > B"));
2557 } else {
2558 PetscCall(PicurvAssertRealNear(maxdiv, 0.0, 1e-12, "decomp C: divergence-free"));
2559 PetscCall(PicurvAssertRealNear(lcB, lcC, 1e-12, "decomp C: B == C"));
2560 }
2561
2562 PetscCall(VecDestroy(&Ubase));
2563 PetscCall(VecDestroy(&Rhs));
2564 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
2565 PetscFunctionReturn(0);
2566}
2567
2568/**
2569 * @brief Runs the State A decomposition baseline.
2570 */
2571static PetscErrorCode TestDecompA(void) { return RunDecompBaseline(STATE_A, "A (uniform div-free)", 1.1000000, 1.1000000, 1.1000000); }
2572
2573/**
2574 * @brief Runs the State B decomposition baseline.
2575 */
2576static PetscErrorCode TestDecompB(void) { return RunDecompBaseline(STATE_B, "B (nonzero divergence)", 0.878379697325, 0.974927912182, 1.572173303885); }
2577
2578/**
2579 * @brief Runs the State C decomposition baseline.
2580 */
2581static PetscErrorCode TestDecompC(void) { return RunDecompBaseline(STATE_C, "C (div-free shear)", 1.5000000, 1.5000000, 1.780330085890); }
2582
2583/**
2584 * @brief Reads optional paired-run MPI reference path and token.
2585 */
2586static PetscErrorCode ConfigureMPIReferenceOptions(void)
2587{
2588 PetscFunctionBeginUser;
2589 PetscCall(PetscOptionsGetString(NULL, NULL, "-candidate_ref_path",
2591 PetscCall(PetscOptionsGetString(NULL, NULL, "-candidate_ref_token",
2594 PetscCheck(g_ref_path_set && g_ref_token_set, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE,
2595 "-candidate_ref_path and -candidate_ref_token must be supplied together");
2596 }
2597 PetscFunctionReturn(0);
2598}
2599
2600/**
2601 * @brief PETSc entry point for the focused convective-candidate harness.
2602 */
2603int main(int argc, char **argv)
2604{
2605 PetscErrorCode ierr;
2606 PetscMPIInt size;
2607 const PicurvTestCase cases[] = {
2608 {"candidate-state-A-uniform", TestStateA},
2609 {"candidate-state-B-divergence", TestStateB},
2610 {"candidate-state-C-shear", TestStateC},
2611 {"candidate-state-A-grid-audit", TestStateAGridAudit},
2612 };
2613 const PicurvTestCase decomp_cases[] = {
2614 {"candidate-decomp-A-uniform", TestDecompA},
2615 {"candidate-decomp-B-divergence", TestDecompB},
2616 {"candidate-decomp-C-shear", TestDecompC},
2617 };
2618 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv A4a convective-candidate study");
2619 if (ierr) return (int)ierr;
2620 ierr = ConfigureMPIReferenceOptions(); if (ierr) { PetscFinalize(); return (int)ierr; }
2621 ierr = MPI_Comm_size(PETSC_COMM_WORLD, &size); if (ierr) { PetscFinalize(); return (int)ierr; }
2622 if (size == 1) {
2623 ierr = PicurvRunTests("unit-momentum-candidates", cases, sizeof(cases)/sizeof(cases[0]));
2624 if (!ierr) ierr = PicurvRunTests("unit-momentum-candidates-decomp", decomp_cases, sizeof(decomp_cases)/sizeof(decomp_cases[0]));
2625 } else {
2626 ierr = PicurvRunTests("unit-momentum-candidates-decomp", decomp_cases, sizeof(decomp_cases)/sizeof(decomp_cases[0]));
2627 }
2628 if (ierr) { PetscFinalize(); return (int)ierr; }
2629 return (int)PetscFinalize();
2630}
PetscErrorCode SynchronizePeriodicStaggeredFields(UserCtx *user, PetscInt num_fields, const char *field_names[])
Synchronizes persistent component-staggered vector fields.
PetscErrorCode SynchronizePeriodicLocalStaggeredField(UserCtx *user, Vec local_field)
Synchronizes one local-only component-staggered periodic work field.
PetscErrorCode SynchronizePeriodicCellFields(UserCtx *user, PetscInt num_fields, const char *field_names[])
Synchronizes periodic endpoint cells for a list of cell-centered fields.
@ MOM_STAB_CAND_C
PetscInt MomCellActiveRows(PetscReal ***nvert, PetscInt k, PetscInt j, PetscInt i, PetscInt mx, PetscInt my, PetscInt mz, PetscBool np_x1, PetscBool np_y1, PetscBool np_z1, PetscInt twoD)
Active staggered-momentum row mask for a cell (exposed for unit testing).
PetscErrorCode ComputeMomentumStabilityEstimate(UserCtx *user, PetscInt block_number, PetscReal dt, MomStabCandidate candidate, MomStabilityReport *rep)
Compute the momentum pseudo-time stability estimate (shadow/diagnostic).
Diagnostic report produced by ComputeMomentumStabilityEstimate().
PetscErrorCode Convection(UserCtx *user, Vec Ucont, Vec Ucat, Vec Conv)
Computes the convective contribution to the contravariant momentum RHS.
Definition rhs.c:13
PetscErrorCode ComputeRHS(UserCtx *user, Vec Rhs)
Computes the Right-Hand Side (RHS) of the momentum equations.
Definition rhs.c:1100
PetscErrorCode Contra2Cart(UserCtx *user)
Reconstructs Cartesian velocity (Ucat) at cell centers from contravariant velocity (Ucont) defined on...
Definition setup.c:2743
PetscErrorCode Cart2Contra(UserCtx *user)
Convert the ghosted Cartesian velocity field to contravariant face fluxes.
Definition setup.c:2878
PetscErrorCode UpdateLocalGhosts(UserCtx *user, const char *fieldName)
Updates the local vector (including ghost points) from its corresponding global vector.
Definition setup.c:1752
static void DenseMatVec(const PetscReal *J, const PetscReal *x, PetscReal *y, PetscInt n)
Applies a dense column-major matrix to an active-space vector.
static PetscErrorCode EvalConvResidual(UserCtx *user, Vec Ucont_in, Vec Rhs, const DofMap *map, PetscReal *Ract)
static PetscErrorCode TestDecompA(void)
Runs the State A decomposition baseline.
static Cmpnts DirectUcontVelocity(CandState st, PetscInt i, PetscInt j, PetscInt k, PetscInt mx, PetscInt my, PetscInt mz)
Evaluates the declared direct component-staggered State B Ucont field.
static PetscErrorCode ComputeMaxGradientContribution(UserCtx *user, PetscReal *gradmax)
Computes the global maximum Cartesian velocity-gradient row-sum used by Candidate D.
static PetscErrorCode RunRKTangentDiagnostics(UserCtx *user, CandState st, Vec Ubase, Vec Rhs, const DofMap *map, PetscReal epsrel, const PetscReal lams[3], const char *cn[3], const PetscReal *J0)
Runs stage-dependent RK tangent and direct nonlinear perturbation diagnostics.
static PetscErrorCode AmplificationMetric(const PetscReal *J, PetscInt n, PetscReal dtau, PMetric which, PetscReal *metric)
Evaluates either spectral-radius or 2-norm amplification for one pseudo-time step.
static PetscErrorCode SymbolEigenSummary(const PetscReal A6[36], PetscReal *maxre, PetscReal wr_out[6], PetscReal wi_out[6])
Eigenvalue summary for a 6x6 real symbol.
static PetscErrorCode GetDof(UserCtx *user, Vec Ucont, const DofMap *map, PetscInt m, PetscReal *val)
Reads one active contravariant component from a global vector.
static PetscBool DofTouchesPeriodicRepresentative(const DofMap *map, PetscInt m, DMDALocalInfo info)
True if an active representative lies on a plane adjacent to periodic duplicates.
static PetscErrorCode DofMapBuildOwned(UserCtx *user, DofMap *map)
Builds the rank-owned periodic independent face-DOF map for MPI checks.
static PetscErrorCode DofMapBuild(UserCtx *user, DofMap *map)
Builds the serial periodic independent face-DOF map used by dense Jacobians.
static PetscErrorCode StableCFL(const PetscReal *J, PetscInt n, PetscReal lam, PMetric which, StableCFLResult *result)
static void MatMul(const PetscReal *A, const PetscReal *B, PetscReal *out, PetscInt n)
static PetscReal PeriodicCellAngle(PetscInt idx, PetscInt npts)
Returns the cell-centered periodic angle using duplicated endpoint planes.
static PetscReal VecNorm2Array(const PetscReal *x, PetscInt n)
Computes the Euclidean norm of a dense vector.
int main(int argc, char **argv)
PETSc entry point for the focused convective-candidate harness.
static PetscErrorCode RunStateADirectionalMechanismAudit(const PetscReal *Jxy, UserCtx *user_xy, Vec Ubase_xy, Vec Rhs_xy, const DofMap *map_xy, PetscReal epsrel)
Builds Jx, Jy, Jxy and prints additivity/commutator/eigenpair diagnostics.
static PetscReal DenseRelativeDiff(const PetscReal *A, const PetscReal *B, PetscInt n, PetscReal denom_ref)
Computes a normalized Frobenius difference between two dense matrices.
static PetscErrorCode DenseSigmaMax(const PetscReal *A, PetscInt n, PetscReal *smax, PetscReal *v1)
static PetscBool g_ref_path_set
static PetscErrorCode PrintBlockAndSymmetricLocalization(UserCtx *user, const DofMap *map, const PetscReal *J)
Prints 3x3 component block norms and localized symmetric rows.
static PetscReal DenseFrobenius(const PetscReal *A, PetscInt n)
Computes the Frobenius norm of a dense column-major matrix.
static PetscErrorCode TestDecompB(void)
Runs the State B decomposition baseline.
static PetscErrorCode TestStateC(void)
Runs the State C candidate harness.
static PetscReal CmpDiffInf(Cmpnts a, Cmpnts b)
Computes the componentwise infinity norm of the difference between two vectors.
static PetscReal CmpGet(Cmpnts c, PetscInt comp)
static PetscBool InGhostRange(PetscInt idx, PetscInt lo, PetscInt n)
Reports whether a global index is present in a rank's local ghosted range.
static char g_ref_token[128]
static const char * StableCFLStatusText(StableCFLResult r)
Returns human-readable text for a stable-CFL search result.
static void FillAuditDirection(const DofMap *map, PetscInt kind, PetscReal *x)
Fills one deterministic active-space vector used by dense/matrix-free checks.
static PetscErrorCode DofMapDestroy(DofMap *map)
Releases storage owned by an active-DOF map.
static PetscErrorCode TestDecompC(void)
Runs the State C decomposition baseline.
static PetscReal DenseCommutatorNorm(const PetscReal *A, const PetscReal *B, PetscInt n)
Computes C = A*B - B*A and returns its normalized Frobenius norm.
static PetscInt PeriodicRepCount(PetscInt npts)
Returns the number of independent periodic representatives in one direction.
static PetscErrorCode BuildStageTangent(const PetscReal *J0, const PetscReal *J1, const PetscReal *J2, const PetscReal *J3, PetscReal dtau, PetscInt n, PetscReal *T4)
Builds the exact four-stage tangent from stage-dependent Jacobians.
#define ROWSUM(cmp)
static PetscErrorCode CheckDenseJacobianAction(UserCtx *user, Vec Ubase, Vec Rhs, Vec Uwork, const DofMap *map, const PetscReal *J, PetscReal epsrel)
Verifies that the assembled dense Jacobian has the same action as production FD Jv.
static PetscErrorCode ComputeDeclaredSeamMismatch(CandState st, DMDALocalInfo info, PetscReal seam[3])
Computes analytic periodic seam mismatches for each coordinate direction.
static PetscErrorCode DenseSpectralRadius(const PetscReal *A, PetscInt n, PetscReal *rho, PetscReal *maxRealPart)
static PetscErrorCode SetAnchoredStage(UserCtx *user, Vec U0full, PetscReal scale, const DofMap *map, const PetscReal *Ract, Vec Ustage)
Forms one anchored RK stage state from the base state and active residual.
static PetscErrorCode BuildFDJacobian(UserCtx *user, Vec Ucenter, PetscReal epsrel, Vec Rhs, const DofMap *map, PetscReal *Rp, PetscReal *Rm, Vec Uwork, PetscReal *J)
Builds a centered finite-difference Jacobian of the production convective residual.
static PetscErrorCode PrintFourierChecks(const DofMap *map, const PetscReal *J)
Projects production J on full staggered six-dimensional Fourier subspaces.
static void ApplyP(const PetscReal *P, const PetscReal *x, PetscReal *out, PetscInt n)
static PetscErrorCode DenseMaxRealRightEigenpair(const PetscReal *A, PetscInt n, PetscReal *lamr, PetscReal *lami, PetscReal *vr_out, PetscReal *vi_out)
Extracts the right eigenpair whose eigenvalue has largest real part.
static PetscErrorCode DenseShiftIdentity(const PetscReal *A, PetscInt n, PetscReal shift, PetscReal *B)
Copies a dense matrix and adds a scalar shift to its diagonal.
static PetscErrorCode EvalResidualStage(UserCtx *user, Vec Ucont_in, Vec Rhs, const DofMap *map, ResidualStage stage, PetscReal *out)
Evaluates one observable/mirrored residual-path stage for a Ucont input.
static PetscErrorCode PrintStableCFLLine(const char *candidate, StableCFLResult eig, StableCFLResult norm)
Prints one candidate's eigenvalue and norm stable-CFL statuses.
static PetscErrorCode TraceStateAResidualStages(UserCtx *user, Vec Ubase, Vec Rhs, const DofMap *map, const PetscReal *J)
Trace observable production residual stages for one deterministic perturbation.
static PetscErrorCode AverageRctToFinalActive(UserCtx *user, Vec Rct, const DofMap *map, PetscReal *out)
Mirrors ComputeRHS's final Rct face averaging and active cleanup for P=0/no body force.
static PetscErrorCode TestStateAGridAudit(void)
Runs the State A grid-dependence and active-space audit.
static PetscErrorCode ConfigureMPIReferenceOptions(void)
Reads optional paired-run MPI reference path and token.
static PetscReal DenseAdditivityError(const PetscReal *A, const PetscReal *B, const PetscReal *C, PetscInt n)
Normalized Frobenius norm of A - (B+C).
static PetscErrorCode WriteStateADecompReference(GlobalVecStats r0, GlobalVecStats jv, GlobalVecStats phi, const MomStabilityReport *rep)
Writes the one-rank State A matrix-free decomposition reference.
static PetscErrorCode RunStateAGridAuditOne(PetscInt N)
Runs one State A grid-size audit case.
static PetscErrorCode BuildStageJacobian(UserCtx *user, Vec Ucenter, PetscReal epsrel, Vec Rhs, const DofMap *map, ResidualStage stage, PetscReal *Rp, PetscReal *Rm, Vec Uwork, PetscReal *J)
Builds a finite-difference Jacobian for one residual-path stage.
static PetscErrorCode FillDeterministicDirection(UserCtx *user, Vec V, const DofMap *map, PetscReal *x)
Fills a globally normalized deterministic active-space perturbation direction.
static PetscBool g_ref_token_set
static PetscErrorCode ReadAndCompareStateADecompReference(GlobalVecStats r0, GlobalVecStats jv, GlobalVecStats phi, const MomStabilityReport *rep)
Compares a distributed State A matrix-free check against the one-rank reference.
#define HAVE_I(ii)
static PetscErrorCode MapCartesianResidualToRct(UserCtx *user, Vec Rc, Vec Rct)
Mirrors ComputeRHS's Cartesian residual -> contravariant local Rct mapping.
static PetscReal DofWeight(const DofMap *map, PetscInt m)
Returns a deterministic checksum weight for an active DOF.
static PetscReal DenseNonNormality(const PetscReal *A, PetscInt n)
static PetscErrorCode PerturbDof(UserCtx *user, Vec Ucont, const DofMap *map, PetscInt m, PetscReal delta)
static PetscErrorCode RunState(CandState st, const char *name)
static PetscErrorCode AddActiveVector(UserCtx *user, Vec U, const DofMap *map, const PetscReal *x, PetscReal scale)
Adds a dense active-space vector into a global contravariant vector.
static PetscErrorCode ComputeLocalOuterGhostMismatch(UserCtx *user, Vec local, PetscReal seam[3])
Computes outer periodic ghost mismatch for local Ucat.
static PetscErrorCode BuildStageStates(UserCtx *user, Vec U0full, PetscReal dtau, Vec Rhs, const DofMap *map, PetscReal *Rscratch, Vec Y1, Vec Y2, Vec Y3)
Builds the first three anchored RK stage states for a base vector.
static PetscErrorCode SetUcatField(UserCtx *user, CandState st)
static PetscErrorCode MatrixFreeJv(UserCtx *user, Vec Ubase, Vec Rhs, const DofMap *map, const PetscReal *v, PetscReal eps, PetscReal *jv)
Matrix-free production Jacobian-vector product on the active space.
#define HAVE_K(kk)
static PetscErrorCode ExtractActiveVector(UserCtx *user, Vec U, const DofMap *map, PetscReal *x)
Extracts active-space entries from a global contravariant vector.
static PetscErrorCode RKPolynomial(const PetscReal *J, PetscReal dtau, PetscInt n, PetscReal *P)
static PetscErrorCode TestStateA(void)
Runs the State A candidate harness.
static PetscErrorCode BuildBaseState(UserCtx *user, CandState st, Vec Ubase, PetscReal *repeat_inf, PetscReal *maxdiv, SeamDiagnostics *seam)
static PetscErrorCode ComputeMaxDiscreteDivergence(UserCtx *user, PetscReal *maxdiv)
Computes max discrete divergence of the current local Ucont field.
static PetscErrorCode StateADecompDirectionalCheck(UserCtx *user, Vec Ubase, Vec Rhs, PetscReal lcC, const MomStabilityReport *rep)
Runs State A matrix-free residual, Jv, and four-stage MPI decomposition checks.
static PetscErrorCode SetDirectUcontField(UserCtx *user, CandState st)
Sets the direct State B component-staggered Ucont field on owned entries.
#define HAVE_J(jj)
static PetscErrorCode StaggeredFourierSymbol(const DofMap *map, const PetscReal *J, PetscInt wx, PetscInt wy, PetscInt wz, PetscReal A6[36], PetscReal *leak)
Builds the 6x6 projected real symbol and leakage for one wavevector.
static PetscErrorCode PrintFrozenAmplificationTable(const char *title, const PetscReal *J, PetscInt n, const PetscReal lams[3], const char *cn[3])
Prints frozen RK amplification tables for the supplied operator and candidates.
static PetscErrorCode PrintPeriodicSpaceAudit(UserCtx *user, const DofMap *map)
Prints the component-wise periodic storage count actually used by the active map.
static PetscErrorCode ActiveStats(const DofMap *map, const PetscReal *x, GlobalVecStats *stats)
Computes global active-vector norms and checksum.
static PetscErrorCode ConfigureCandidateFixture(SimCtx *simCtx, UserCtx *user)
Configures the minimal context for centered inviscid periodic convection tests.
static PetscErrorCode FourStage(UserCtx *user, Vec U0full, PetscReal dtau, Vec Rhs, const DofMap *map, PetscReal *Rscratch, Vec Uwork, Vec Uout)
static PetscErrorCode RunDecompBaseline(CandState st, const char *name, PetscReal expB, PetscReal expC, PetscReal expD)
Runs one decomp baseline and compares scalar estimates with regenerated references.
static char g_ref_path[PETSC_MAX_PATH_LEN]
static PetscReal VecNormInfArray(const PetscReal *x, PetscInt n)
Computes the infinity norm of a dense vector.
static void FillStaggeredFourierBasis(const DofMap *map, PetscInt wx, PetscInt wy, PetscInt wz, PetscReal *Q)
Fills the six real basis vectors for one staggered same-wavevector subspace.
static PetscErrorCode ExtractLocalActiveVector(UserCtx *user, Vec local, const DofMap *map, PetscReal *x)
Extract active-space entries from a local vector.
static PetscErrorCode TestStateB(void)
Runs the State B candidate harness.
static PetscReal PeriodicFaceAngle(PetscInt idx, PetscInt npts)
Returns a face-representative periodic angle for component-staggered Ucont.
static PetscErrorCode RunStateASplitOne(CandState st, const char *label)
Runs one State A transport-direction split.
static PetscReal DenseSkewnessDefect(const PetscReal *A, PetscInt n)
Computes the normalized Frobenius defect from skew symmetry.
static PetscErrorCode BuildPhiJacobian(UserCtx *user, Vec Ucenter, PetscReal dtau, Vec Rhs, const DofMap *map, PetscReal *Rscratch, Vec Upert, Vec Ustage, Vec PhiP, Vec PhiM, PetscReal *xp, PetscReal *xm, PetscReal epsrel, PetscReal *JPhi)
Builds a finite-difference Jacobian of the complete nonlinear four-stage map.
static PetscInt DofMapExpectedCount(DMDALocalInfo info)
Counts all independent component-staggered representatives used by ComputeRHS.
static PetscErrorCode RunStateAResidualPathIsolation(UserCtx *user, Vec Ubase, Vec Rhs, Vec Uwork, const DofMap *map, PetscReal epsrel)
Prints the stage where positive-real spectrum first appears.
static PetscErrorCode DenseRKPolynomialSpectralRadius(const PetscReal *J, PetscInt n, PetscReal dtau, PetscReal *rho)
Computes the spectral radius of the RK polynomial by applying it to eig(J).
static Cmpnts AnalyticVelocity(CandState st, PetscInt i, PetscInt j, PetscInt k, PetscInt mx, PetscInt my, PetscInt mz)
Evaluates one of the three analytic Cartesian candidate states.
static PetscErrorCode PrintSpectrumSummary(const char *name, const PetscReal *J, PetscInt n)
Prints eigenvalue and norm summary for one dense operator.
static PetscErrorCode ComputeLocalDuplicateMismatch(UserCtx *user, Vec local, PetscReal seam[3])
Computes duplicate-plane mismatch in a local vector view.
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 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 PicurvAssertBool(PetscBool value, const char *context)
Asserts that one boolean condition is true.
Shared declarations for the PICurv C test fixture and assertion layer.
Named test case descriptor consumed by PicurvRunTests.
PetscInt clark
Definition variables.h:790
@ PERIODIC
Definition variables.h:290
PetscInt moveframe
Definition variables.h:715
PetscInt TwoD
Definition variables.h:715
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:896
PetscInt block_number
Definition variables.h:768
Vec lNvert
Definition variables.h:904
PetscInt rans
Definition variables.h:789
Vec lZet
Definition variables.h:927
PetscReal ren
Definition variables.h:732
PetscReal dt
Definition variables.h:699
PetscReal bulkVelocityCorrection
Definition variables.h:781
Vec Ucont
Definition variables.h:904
PetscInt StartStep
Definition variables.h:694
PetscScalar x
Definition variables.h:101
PetscInt invicid
Definition variables.h:715
Vec lNu_t
Definition variables.h:935
Vec lCsi
Definition variables.h:927
PetscScalar z
Definition variables.h:101
Vec Ucat
Definition variables.h:904
PetscInt central
Definition variables.h:730
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
PetscInt les
Definition variables.h:789
Vec Nvert
Definition variables.h:904
BCType mathematical_type
Definition variables.h:366
PetscInt rotateframe
Definition variables.h:715
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