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
46
47typedef struct {
49 PetscReal cfl;
51
52/**
53 * @brief Returns the number of independent periodic representatives in one direction.
54 */
55static inline PetscInt PeriodicRepCount(PetscInt npts) { return npts - 2; }
56
57/**
58 * @brief Counts all independent component-staggered representatives used by ComputeRHS.
59 */
60static inline PetscInt DofMapExpectedCount(DMDALocalInfo info)
61{
62 return 3 * PeriodicRepCount(info.mx) * PeriodicRepCount(info.my) * PeriodicRepCount(info.mz);
63}
64
65/**
66 * @brief Builds the serial periodic independent face-DOF map used by dense Jacobians.
67 *
68 * Production periodic synchronization copies global plane 0 from mx-2 and plane mx-1
69 * from 1 (and analogously in y/z), so representatives 1..m-2 contain each independent
70 * component-staggered Ucont face DOF exactly once. Perturbations only touch these reps;
71 * EvalConvResidual() then calls SynchronizePeriodicStaggeredFields() to update duplicates.
72 */
73static PetscErrorCode DofMapBuild(UserCtx *user, DofMap *map)
74{
75 DMDALocalInfo info = user->info;
76 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
77 const PetscInt lxs = 1, lxe = mx-1, lys = 1, lye = my-1, lzs = 1, lze = mz-1;
78 PetscInt cnt = 0;
79 PetscFunctionBeginUser;
81 map->n = (lxe-lxs)*(lye-lys)*(lze-lzs)*3;
82 PetscCall(PetscMalloc4(map->n, &map->comp, map->n, &map->ci, map->n, &map->cj, map->n, &map->ck));
83 for (PetscInt k = lzs; k < lze; k++)
84 for (PetscInt j = lys; j < lye; j++)
85 for (PetscInt i = lxs; i < lxe; i++)
86 for (PetscInt c = 0; c < 3; c++) {
87 map->comp[cnt] = c; map->ci[cnt] = i; map->cj[cnt] = j; map->ck[cnt] = k; cnt++;
88 }
89 PetscCheck(map->n == map->expected_n, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ,
90 "periodic independent DOF count mismatch: got %" PetscInt_FMT ", expected %" PetscInt_FMT,
91 map->n, map->expected_n);
92 PetscFunctionReturn(0);
93}
94
95/**
96 * @brief Builds the rank-owned periodic independent face-DOF map for MPI checks.
97 */
98static PetscErrorCode DofMapBuildOwned(UserCtx *user, DofMap *map)
99{
100 DMDALocalInfo info = user->info;
101 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
102 const PetscInt xs = info.xs, xe = info.xs + info.xm;
103 const PetscInt ys = info.ys, ye = info.ys + info.ym;
104 const PetscInt zs = info.zs, ze = info.zs + info.zm;
105 const PetscInt lxs = PetscMax(xs, 1), lxe = PetscMin(xe, mx-1);
106 const PetscInt lys = PetscMax(ys, 1), lye = PetscMin(ye, my-1);
107 const PetscInt lzs = PetscMax(zs, 1), lze = PetscMin(ze, mz-1);
108 PetscInt cnt = 0;
109 PetscFunctionBeginUser;
110 map->expected_n = DofMapExpectedCount(info);
111 map->n = PetscMax(0,lxe-lxs)*PetscMax(0,lye-lys)*PetscMax(0,lze-lzs)*3;
112 PetscCall(PetscMalloc4(map->n, &map->comp, map->n, &map->ci, map->n, &map->cj, map->n, &map->ck));
113 for (PetscInt k = lzs; k < lze; k++)
114 for (PetscInt j = lys; j < lye; j++)
115 for (PetscInt i = lxs; i < lxe; i++)
116 for (PetscInt c = 0; c < 3; c++) {
117 map->comp[cnt] = c; map->ci[cnt] = i; map->cj[cnt] = j; map->ck[cnt] = k; cnt++;
118 }
119 PetscFunctionReturn(0);
120}
121
122/**
123 * @brief Releases storage owned by an active-DOF map.
124 */
125static PetscErrorCode DofMapDestroy(DofMap *map)
126{ PetscFunctionBeginUser; PetscCall(PetscFree4(map->comp, map->ci, map->cj, map->ck)); PetscFunctionReturn(0); }
127
128/* component accessor: Cmpnts is 3 contiguous PetscReal (x,y,z) in the real build. */
129static inline PetscReal CmpGet(Cmpnts c, PetscInt comp) { const PetscReal *p = (const PetscReal*)&c; return p[comp]; }
130
131/* ----------------------------------------------------------------------------------- *
132 * Deterministic convection-only residual wrapper using the real production path. *
133 * ----------------------------------------------------------------------------------- */
134static PetscErrorCode EvalConvResidual(UserCtx *user, Vec Ucont_in, Vec Rhs, const DofMap *map, PetscReal *Ract)
135{
136 Cmpnts ***r;
137 PetscFunctionBeginUser;
138 PetscCall(VecCopy(Ucont_in, user->Ucont));
139 {
140 const char *fld[] = {"Ucont"};
141 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fld)); /* global->local + periodic */
142 }
143 PetscCall(ComputeRHS(user, Rhs)); /* Contra2Cart + Convection + mapping */
144 PetscCall(DMDAVecGetArrayRead(user->fda, Rhs, &r));
145 for (PetscInt m = 0; m < map->n; m++)
146 Ract[m] = CmpGet(r[map->ck[m]][map->cj[m]][map->ci[m]], map->comp[m]);
147 PetscCall(DMDAVecRestoreArrayRead(user->fda, Rhs, &r));
148 PetscFunctionReturn(0);
149}
150
151/* perturb one active global Ucont DOF (then caller re-syncs through EvalConvResidual). */
152static PetscErrorCode PerturbDof(UserCtx *user, Vec Ucont, const DofMap *map, PetscInt m, PetscReal delta)
153{
154 Cmpnts ***a;
155 PetscFunctionBeginUser;
156 PetscCall(DMDAVecGetArray(user->fda, Ucont, &a));
157 { PetscReal *p = (PetscReal*)&a[map->ck[m]][map->cj[m]][map->ci[m]]; p[map->comp[m]] += delta; }
158 PetscCall(DMDAVecRestoreArray(user->fda, Ucont, &a));
159 PetscFunctionReturn(0);
160}
161
162/**
163 * @brief Reads one active contravariant component from a global vector.
164 */
165static PetscErrorCode GetDof(UserCtx *user, Vec Ucont, const DofMap *map, PetscInt m, PetscReal *val)
166{
167 Cmpnts ***a;
168 PetscFunctionBeginUser;
169 PetscCall(DMDAVecGetArray(user->fda, Ucont, &a));
170 { const PetscReal *p = (const PetscReal*)&a[map->ck[m]][map->cj[m]][map->ci[m]]; *val = p[map->comp[m]]; }
171 PetscCall(DMDAVecRestoreArray(user->fda, Ucont, &a));
172 PetscFunctionReturn(0);
173}
174
175/* ----------------------------------------------------------------------------------- *
176 * Base-state construction: physical Cartesian velocity -> production contravariant. *
177 * ----------------------------------------------------------------------------------- */
178typedef enum { STATE_A, STATE_B, STATE_C } CandState;
179
180/**
181 * @brief Returns the cell-centered periodic angle using duplicated endpoint planes.
182 */
183static inline PetscReal PeriodicCellAngle(PetscInt idx, PetscInt npts)
184{
185 const PetscInt nuniq = npts - 1;
186 const PetscInt ip = (idx == npts - 1) ? 0 : idx;
187 return 2.0*PETSC_PI*((PetscReal)ip)/((PetscReal)nuniq);
188}
189
190/**
191 * @brief Returns a face-representative periodic angle for component-staggered Ucont.
192 */
193static inline PetscReal PeriodicFaceAngle(PetscInt idx, PetscInt npts)
194{
195 const PetscInt nuniq = PeriodicRepCount(npts);
196 PetscInt ip = idx - 1;
197 if (idx == 0) ip = nuniq - 1;
198 else if (idx == npts - 1) ip = 0;
199 return 2.0*PETSC_PI*((PetscReal)ip)/((PetscReal)nuniq);
200}
201
202/**
203 * @brief Evaluates one of the three analytic Cartesian candidate states.
204 */
205static inline Cmpnts AnalyticVelocity(CandState st, PetscInt i, PetscInt j, PetscInt k,
206 PetscInt mx, PetscInt my, PetscInt mz)
207{
208 Cmpnts v;
209 const PetscReal x = PeriodicCellAngle(i, mx);
210 const PetscReal y = PeriodicCellAngle(j, my);
211 (void)k; (void)mz;
212 if (st == STATE_A) {
213 v.x = 0.7; v.y = -0.4; v.z = 0.0;
214 } else if (st == STATE_B) {
215 (void)x; v.x = 0.0; v.y = 0.0; v.z = 0.0;
216 } else {
217 v.x = 1.0 + 0.5*PetscSinReal(y); v.y = 0.0; v.z = 0.0;
218 }
219 return v;
220}
221
222/**
223 * @brief Evaluates the declared direct component-staggered State B Ucont field.
224 */
225static inline Cmpnts DirectUcontVelocity(CandState st, PetscInt i, PetscInt j, PetscInt k,
226 PetscInt mx, PetscInt my, PetscInt mz)
227{
228 Cmpnts v = {0.0, 0.0, 0.0};
229 (void)j; (void)k; (void)my; (void)mz;
230 if (st == STATE_B) v.x = PetscSinReal(PeriodicFaceAngle(i, mx));
231 return v;
232}
233
234/**
235 * @brief Computes the componentwise infinity norm of the difference between two vectors.
236 */
237static inline PetscReal CmpDiffInf(Cmpnts a, Cmpnts b)
238{
239 return PetscMax(PetscAbsReal(a.x-b.x), PetscMax(PetscAbsReal(a.y-b.y), PetscAbsReal(a.z-b.z)));
240}
241
242/**
243 * @brief Computes analytic periodic seam mismatches for each coordinate direction.
244 */
245static PetscErrorCode ComputeDeclaredSeamMismatch(CandState st, DMDALocalInfo info, PetscReal seam[3])
246{
247 PetscReal loc[3] = {0.0, 0.0, 0.0}, glo[3];
248 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
249 PetscFunctionBeginUser;
250 if (st == STATE_B) {
251 for (PetscInt k = 1; k < mz-1; k++)
252 for (PetscInt j = 1; j < my-1; j++) {
253 loc[0] = PetscMax(loc[0], CmpDiffInf(DirectUcontVelocity(st, 0, j, k, mx, my, mz),
254 DirectUcontVelocity(st, mx-2, j, k, mx, my, mz)));
255 loc[0] = PetscMax(loc[0], CmpDiffInf(DirectUcontVelocity(st, mx-1, j, k, mx, my, mz),
256 DirectUcontVelocity(st, 1, j, k, mx, my, mz)));
257 }
258 for (PetscInt k = 1; k < mz-1; k++)
259 for (PetscInt i = 1; i < mx-1; i++) {
260 loc[1] = PetscMax(loc[1], CmpDiffInf(DirectUcontVelocity(st, i, 0, k, mx, my, mz),
261 DirectUcontVelocity(st, i, my-2, k, mx, my, mz)));
262 loc[1] = PetscMax(loc[1], CmpDiffInf(DirectUcontVelocity(st, i, my-1, k, mx, my, mz),
263 DirectUcontVelocity(st, i, 1, k, mx, my, mz)));
264 }
265 for (PetscInt j = 1; j < my-1; j++)
266 for (PetscInt i = 1; i < mx-1; i++) {
267 loc[2] = PetscMax(loc[2], CmpDiffInf(DirectUcontVelocity(st, i, j, 0, mx, my, mz),
268 DirectUcontVelocity(st, i, j, mz-2, mx, my, mz)));
269 loc[2] = PetscMax(loc[2], CmpDiffInf(DirectUcontVelocity(st, i, j, mz-1, mx, my, mz),
270 DirectUcontVelocity(st, i, j, 1, mx, my, mz)));
271 }
272 } else {
273 for (PetscInt k = 0; k < mz; k++)
274 for (PetscInt j = 0; j < my; j++)
275 loc[0] = PetscMax(loc[0], CmpDiffInf(AnalyticVelocity(st, 0, j, k, mx, my, mz),
276 AnalyticVelocity(st, mx-1, j, k, mx, my, mz)));
277 for (PetscInt k = 0; k < mz; k++)
278 for (PetscInt i = 0; i < mx; i++)
279 loc[1] = PetscMax(loc[1], CmpDiffInf(AnalyticVelocity(st, i, 0, k, mx, my, mz),
280 AnalyticVelocity(st, i, my-1, k, mx, my, mz)));
281 for (PetscInt j = 0; j < my; j++)
282 for (PetscInt i = 0; i < mx; i++)
283 loc[2] = PetscMax(loc[2], CmpDiffInf(AnalyticVelocity(st, i, j, 0, mx, my, mz),
284 AnalyticVelocity(st, i, j, mz-1, mx, my, mz)));
285 }
286 PetscCallMPI(MPI_Allreduce(loc, glo, 3, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD));
287 seam[0] = glo[0]; seam[1] = glo[1]; seam[2] = glo[2];
288 PetscFunctionReturn(0);
289}
290
291/**
292 * @brief Reports whether a global index is present in a rank's local ghosted range.
293 */
294static inline PetscBool InGhostRange(PetscInt idx, PetscInt lo, PetscInt n)
295{ return (PetscBool)(idx >= lo && idx < lo + n); }
296
297/**
298 * @brief Computes duplicate-plane mismatch in a local vector view.
299 */
300static PetscErrorCode ComputeLocalDuplicateMismatch(UserCtx *user, Vec local, PetscReal seam[3])
301{
302 DMDALocalInfo info = user->info;
303 Cmpnts ***a;
304 PetscReal loc[3] = {0.0, 0.0, 0.0}, glo[3];
305 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
306 PetscFunctionBeginUser;
307 PetscCall(DMDAVecGetArrayRead(user->fda, local, &a));
308#define HAVE_I(ii) InGhostRange((ii), info.gxs, info.gxm)
309#define HAVE_J(jj) InGhostRange((jj), info.gys, info.gym)
310#define HAVE_K(kk) InGhostRange((kk), info.gzs, info.gzm)
311 if (HAVE_I(0) && HAVE_I(mx-2))
312 for (PetscInt k = 1; k < mz-1; k++) if (HAVE_K(k))
313 for (PetscInt j = 1; j < my-1; j++) if (HAVE_J(j))
314 loc[0] = PetscMax(loc[0], CmpDiffInf(a[k][j][0], a[k][j][mx-2]));
315 if (HAVE_I(mx-1) && HAVE_I(1))
316 for (PetscInt k = 1; k < mz-1; k++) if (HAVE_K(k))
317 for (PetscInt j = 1; j < my-1; j++) if (HAVE_J(j))
318 loc[0] = PetscMax(loc[0], CmpDiffInf(a[k][j][mx-1], a[k][j][1]));
319 if (HAVE_J(0) && HAVE_J(my-2))
320 for (PetscInt k = 1; k < mz-1; k++) if (HAVE_K(k))
321 for (PetscInt i = 1; i < mx-1; i++) if (HAVE_I(i))
322 loc[1] = PetscMax(loc[1], CmpDiffInf(a[k][0][i], a[k][my-2][i]));
323 if (HAVE_J(my-1) && HAVE_J(1))
324 for (PetscInt k = 1; k < mz-1; k++) if (HAVE_K(k))
325 for (PetscInt i = 1; i < mx-1; i++) if (HAVE_I(i))
326 loc[1] = PetscMax(loc[1], CmpDiffInf(a[k][my-1][i], a[k][1][i]));
327 if (HAVE_K(0) && HAVE_K(mz-2))
328 for (PetscInt j = 1; j < my-1; j++) if (HAVE_J(j))
329 for (PetscInt i = 1; i < mx-1; i++) if (HAVE_I(i))
330 loc[2] = PetscMax(loc[2], CmpDiffInf(a[0][j][i], a[mz-2][j][i]));
331 if (HAVE_K(mz-1) && HAVE_K(1))
332 for (PetscInt j = 1; j < my-1; j++) if (HAVE_J(j))
333 for (PetscInt i = 1; i < mx-1; i++) if (HAVE_I(i))
334 loc[2] = PetscMax(loc[2], CmpDiffInf(a[mz-1][j][i], a[1][j][i]));
335#undef HAVE_I
336#undef HAVE_J
337#undef HAVE_K
338 PetscCall(DMDAVecRestoreArrayRead(user->fda, local, &a));
339 PetscCallMPI(MPI_Allreduce(loc, glo, 3, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD));
340 seam[0] = glo[0]; seam[1] = glo[1]; seam[2] = glo[2];
341 PetscFunctionReturn(0);
342}
343
344/**
345 * @brief Computes outer periodic ghost mismatch for local Ucat.
346 */
347static PetscErrorCode ComputeLocalOuterGhostMismatch(UserCtx *user, Vec local, PetscReal seam[3])
348{
349 DMDALocalInfo info = user->info;
350 Cmpnts ***a;
351 PetscReal loc[3] = {0.0, 0.0, 0.0}, glo[3];
352 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
353 PetscFunctionBeginUser;
354 PetscCall(DMDAVecGetArrayRead(user->fda, local, &a));
355#define HAVE_I(ii) InGhostRange((ii), info.gxs, info.gxm)
356#define HAVE_J(jj) InGhostRange((jj), info.gys, info.gym)
357#define HAVE_K(kk) InGhostRange((kk), info.gzs, info.gzm)
358 if (HAVE_I(-1) && HAVE_I(1))
359 for (PetscInt k = 1; k < mz-1; k++) if (HAVE_K(k))
360 for (PetscInt j = 1; j < my-1; j++) if (HAVE_J(j))
361 loc[0] = PetscMax(loc[0], CmpDiffInf(a[k][j][-1], a[k][j][1]));
362 if (HAVE_I(mx) && HAVE_I(mx-2))
363 for (PetscInt k = 1; k < mz-1; k++) if (HAVE_K(k))
364 for (PetscInt j = 1; j < my-1; j++) if (HAVE_J(j))
365 loc[0] = PetscMax(loc[0], CmpDiffInf(a[k][j][mx], a[k][j][mx-2]));
366 if (HAVE_J(-1) && HAVE_J(1))
367 for (PetscInt k = 1; k < mz-1; k++) if (HAVE_K(k))
368 for (PetscInt i = 1; i < mx-1; i++) if (HAVE_I(i))
369 loc[1] = PetscMax(loc[1], CmpDiffInf(a[k][-1][i], a[k][1][i]));
370 if (HAVE_J(my) && HAVE_J(my-2))
371 for (PetscInt k = 1; k < mz-1; k++) if (HAVE_K(k))
372 for (PetscInt i = 1; i < mx-1; i++) if (HAVE_I(i))
373 loc[1] = PetscMax(loc[1], CmpDiffInf(a[k][my][i], a[k][my-2][i]));
374 if (HAVE_K(-1) && HAVE_K(1))
375 for (PetscInt j = 1; j < my-1; j++) if (HAVE_J(j))
376 for (PetscInt i = 1; i < mx-1; i++) if (HAVE_I(i))
377 loc[2] = PetscMax(loc[2], CmpDiffInf(a[-1][j][i], a[1][j][i]));
378 if (HAVE_K(mz) && HAVE_K(mz-2))
379 for (PetscInt j = 1; j < my-1; j++) if (HAVE_J(j))
380 for (PetscInt i = 1; i < mx-1; i++) if (HAVE_I(i))
381 loc[2] = PetscMax(loc[2], CmpDiffInf(a[mz][j][i], a[mz-2][j][i]));
382#undef HAVE_I
383#undef HAVE_J
384#undef HAVE_K
385 PetscCall(DMDAVecRestoreArrayRead(user->fda, local, &a));
386 PetscCallMPI(MPI_Allreduce(loc, glo, 3, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD));
387 seam[0] = glo[0]; seam[1] = glo[1]; seam[2] = glo[2];
388 PetscFunctionReturn(0);
389}
390
391/**
392 * @brief Configures the minimal context for centered inviscid periodic convection tests.
393 */
394static PetscErrorCode ConfigureCandidateFixture(SimCtx *simCtx, UserCtx *user)
395{
396 PetscFunctionBeginUser;
397 for (int f = 0; f < 6; f++) user->boundary_faces[f].mathematical_type = PERIODIC;
398 simCtx->dt = 0.1; simCtx->step = 5; simCtx->StartStep = 0; /* BDF2 -> a0=1.5 */
399 simCtx->ren = 1.0e6; simCtx->invicid = 1; simCtx->les = 0; simCtx->rans = 0;
400 simCtx->central = 1; simCtx->clark = 0; simCtx->TwoD = 0; simCtx->block_number = 1;
401 simCtx->bulkVelocityCorrection = 0.0; simCtx->moveframe = 0; simCtx->rotateframe = 0;
402 if (!user->lNu_t) PetscCall(DMCreateLocalVector(user->da, &user->lNu_t));
403 PetscCall(VecSet(user->P, 0.0)); PetscCall(UpdateLocalGhosts(user, "P"));
404 PetscCall(VecSet(user->lNvert, 0.0)); PetscCall(VecSet(user->Nvert, 0.0));
405 PetscFunctionReturn(0);
406}
407
408/* set global Ucat to the analytic velocity for the state at cell-centre (i,j,k). */
409static PetscErrorCode SetUcatField(UserCtx *user, CandState st)
410{
411 Cmpnts ***u;
412 DMDALocalInfo info = user->info;
413 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
414 PetscFunctionBeginUser;
415 PetscCall(DMDAVecGetArray(user->fda, user->Ucat, &u));
416 /* only owned region for a GLOBAL vec */
417 for (PetscInt k = info.zs; k < info.zs+info.zm; k++)
418 for (PetscInt j = info.ys; j < info.ys+info.ym; j++)
419 for (PetscInt i = info.xs; i < info.xs+info.xm; i++) {
420 u[k][j][i] = AnalyticVelocity(st, i, j, k, mx, my, mz);
421 }
422 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucat, &u));
423 PetscFunctionReturn(0);
424}
425
426/**
427 * @brief Sets the direct State B component-staggered Ucont field on owned entries.
428 */
429static PetscErrorCode SetDirectUcontField(UserCtx *user, CandState st)
430{
431 Cmpnts ***u;
432 DMDALocalInfo info = user->info;
433 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
434 PetscFunctionBeginUser;
435 PetscCall(VecSet(user->Ucont, 0.0));
436 PetscCall(DMDAVecGetArray(user->fda, user->Ucont, &u));
437 for (PetscInt k = info.zs; k < info.zs+info.zm; k++)
438 for (PetscInt j = info.ys; j < info.ys+info.ym; j++)
439 for (PetscInt i = info.xs; i < info.xs+info.xm; i++)
440 u[k][j][i] = DirectUcontVelocity(st, i, j, k, mx, my, mz);
441 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucont, &u));
442 PetscFunctionReturn(0);
443}
444
445/**
446 * @brief Computes max discrete divergence of the current local Ucont field.
447 */
448static PetscErrorCode ComputeMaxDiscreteDivergence(UserCtx *user, PetscReal *maxdiv)
449{
450 DMDALocalInfo info = user->info;
451 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
452 Cmpnts ***uc;
453 PetscReal dv = 0.0, dv_global;
454 PetscFunctionBeginUser;
455 PetscCall(UpdateLocalGhosts(user, "Ucont"));
456 PetscCall(DMDAVecGetArrayRead(user->fda, user->lUcont, &uc));
457 for (PetscInt k = info.zs; k < info.zs+info.zm; k++)
458 for (PetscInt j = info.ys; j < info.ys+info.ym; j++)
459 for (PetscInt i = info.xs; i < info.xs+info.xm; i++) {
460 if (i<1||i>mx-2||j<1||j>my-2||k<1||k>mz-2) continue;
461 const PetscReal d = (uc[k][j][i].x - uc[k][j][i-1].x)
462 + (uc[k][j][i].y - uc[k][j-1][i].y)
463 + (uc[k][j][i].z - uc[k-1][j][i].z);
464 dv = PetscMax(dv, PetscAbsReal(d));
465 }
466 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lUcont, &uc));
467 PetscCallMPI(MPI_Allreduce(&dv, &dv_global, 1, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD));
468 *maxdiv = dv_global;
469 PetscFunctionReturn(0);
470}
471
472/* Build the base state. States A/C are declared in Cartesian space and converted through
473 Cart2Contra; State B is declared directly in synchronized component-staggered Ucont space. */
474static PetscErrorCode BuildBaseState(UserCtx *user, CandState st, Vec Ubase,
475 PetscReal *repeat_inf, PetscReal *maxdiv,
476 SeamDiagnostics *seam)
477{
478 DMDALocalInfo info = user->info;
479 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
480 PetscReal err = 0.0, err_global;
481 Vec target;
482 Cmpnts ***ur, ***ut;
483 PetscFunctionBeginUser;
484
485 PetscCall(VecDuplicate(user->Ucat, &target));
486
487 if (seam) PetscCall(ComputeDeclaredSeamMismatch(st, info, seam->declared));
488
489 if (st == STATE_B) {
490 const char *ufld[] = {"Ucont"};
491 const char *cfld[] = {"Ucat"};
492 PetscCall(SetDirectUcontField(user, st));
493 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, ufld));
494 PetscCall(VecCopy(user->Ucont, Ubase));
495 if (seam) PetscCall(ComputeLocalDuplicateMismatch(user, user->lUcont, seam->ucont_global));
496 PetscCall(Contra2Cart(user));
497 PetscCall(SynchronizePeriodicCellFields(user, 1, cfld));
498 PetscCall(VecCopy(user->Ucat, target)); /* saved recovered Cartesian state */
499 PetscCall(Contra2Cart(user));
500 PetscCall(SynchronizePeriodicCellFields(user, 1, cfld));
501 } else {
502 const char *cfld[] = {"Ucat"};
503 const char *ufld[] = {"Ucont"};
504 PetscCall(SetUcatField(user, st));
505 PetscCall(SynchronizePeriodicCellFields(user, 1, cfld));
506 PetscCall(VecCopy(user->Ucat, target)); /* saved declared Cartesian state */
507 PetscCall(Cart2Contra(user)); /* global Ucont from lUcat + metrics */
508 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, ufld));
509 PetscCall(VecCopy(user->Ucont, Ubase));
510 if (seam) PetscCall(ComputeLocalDuplicateMismatch(user, user->lUcont, seam->ucont_global));
511 PetscCall(Contra2Cart(user)); /* recovered Cartesian from Ucont */
512 PetscCall(SynchronizePeriodicCellFields(user, 1, cfld));
513 }
514
515 if (seam) {
516 PetscCall(UpdateLocalGhosts(user, "Ucat"));
517 PetscCall(ComputeLocalDuplicateMismatch(user, user->lUcat, seam->ucat_global));
518 PetscCall(ComputeLocalOuterGhostMismatch(user, user->lUcat, seam->ucat_ghost));
519 }
520
521 PetscCall(DMDAVecGetArrayRead(user->fda, user->Ucat, &ur));
522 PetscCall(DMDAVecGetArrayRead(user->fda, target, &ut));
523 for (PetscInt k = info.zs; k < info.zs+info.zm; k++)
524 for (PetscInt j = info.ys; j < info.ys+info.ym; j++)
525 for (PetscInt i = info.xs; i < info.xs+info.xm; i++) {
526 if (i<1||i>mx-2||j<1||j>my-2||k<1||k>mz-2) continue; /* interior cells only */
527 err = PetscMax(err, PetscAbsReal(ur[k][j][i].x - ut[k][j][i].x));
528 err = PetscMax(err, PetscAbsReal(ur[k][j][i].y - ut[k][j][i].y));
529 err = PetscMax(err, PetscAbsReal(ur[k][j][i].z - ut[k][j][i].z));
530 }
531 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->Ucat, &ur));
532 PetscCall(DMDAVecRestoreArrayRead(user->fda, target, &ut));
533 PetscCall(VecDestroy(&target));
534 PetscCall(UpdateLocalGhosts(user, "Ucat")); /* lUcat now consistent with base Ucont */
535
536 PetscCallMPI(MPI_Allreduce(&err, &err_global, 1, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD));
537 PetscCall(ComputeMaxDiscreteDivergence(user, maxdiv));
538 *repeat_inf = err_global;
539 PetscFunctionReturn(0);
540}
541
542/**
543 * @brief Computes the global maximum Cartesian velocity-gradient row-sum used by Candidate D.
544 */
545static PetscErrorCode ComputeMaxGradientContribution(UserCtx *user, PetscReal *gradmax)
546{
547 DMDALocalInfo info = user->info;
548 Cmpnts ***ucat, ***csi, ***eta, ***zet;
549 PetscReal ***aj;
550 PetscReal loc = 0.0, glo;
551 const PetscInt mx = info.mx, my = info.my, mz = info.mz;
552 PetscFunctionBeginUser;
553 PetscCall(UpdateLocalGhosts(user, "Ucat"));
554 PetscCall(DMDAVecGetArrayRead(user->fda, user->lUcat, &ucat));
555 PetscCall(DMDAVecGetArrayRead(user->fda, user->lCsi, &csi));
556 PetscCall(DMDAVecGetArrayRead(user->fda, user->lEta, &eta));
557 PetscCall(DMDAVecGetArrayRead(user->fda, user->lZet, &zet));
558 PetscCall(DMDAVecGetArrayRead(user->da, user->lAj, &aj));
559 for (PetscInt k = info.zs; k < info.zs+info.zm; k++)
560 for (PetscInt j = info.ys; j < info.ys+info.ym; j++)
561 for (PetscInt i = info.xs; i < info.xs+info.xm; i++) {
562 if (i<1||i>mx-2||j<1||j>my-2||k<1||k>mz-2) continue;
563 const Cmpnts duc = { 0.5*(ucat[k][j][i+1].x-ucat[k][j][i-1].x),
564 0.5*(ucat[k][j][i+1].y-ucat[k][j][i-1].y),
565 0.5*(ucat[k][j][i+1].z-ucat[k][j][i-1].z) };
566 const Cmpnts due = { 0.5*(ucat[k][j+1][i].x-ucat[k][j-1][i].x),
567 0.5*(ucat[k][j+1][i].y-ucat[k][j-1][i].y),
568 0.5*(ucat[k][j+1][i].z-ucat[k][j-1][i].z) };
569 const Cmpnts duz = { 0.5*(ucat[k+1][j][i].x-ucat[k-1][j][i].x),
570 0.5*(ucat[k+1][j][i].y-ucat[k-1][j][i].y),
571 0.5*(ucat[k+1][j][i].z-ucat[k-1][j][i].z) };
572 const Cmpnts C = csi[k][j][i], E = eta[k][j][i], Z = zet[k][j][i];
573 const PetscReal Ajc = aj[k][j][i];
574#define ROWSUM(cmp) ( \
575 PetscAbsReal(Ajc*(C.x*duc.cmp + E.x*due.cmp + Z.x*duz.cmp)) + \
576 PetscAbsReal(Ajc*(C.y*duc.cmp + E.y*due.cmp + Z.y*duz.cmp)) + \
577 PetscAbsReal(Ajc*(C.z*duc.cmp + E.z*due.cmp + Z.z*duz.cmp)) )
578 loc = PetscMax(loc, PetscMax(ROWSUM(x), PetscMax(ROWSUM(y), ROWSUM(z))));
579#undef ROWSUM
580 }
581 PetscCall(DMDAVecRestoreArrayRead(user->da, user->lAj, &aj));
582 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lZet, &zet));
583 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lEta, &eta));
584 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lCsi, &csi));
585 PetscCall(DMDAVecRestoreArrayRead(user->fda, user->lUcat, &ucat));
586 PetscCallMPI(MPI_Allreduce(&loc, &glo, 1, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD));
587 *gradmax = glo;
588 PetscFunctionReturn(0);
589}
590
591/* ----------------------------------------------------------------------------------- *
592 * Dense linear algebra on the (column-major) active Jacobian via PETSc LAPACK. *
593 * ----------------------------------------------------------------------------------- */
594/* rho(J): max |eigenvalue|. A is column-major n x n and is COPIED (dgeev overwrites). */
595static PetscErrorCode DenseSpectralRadius(const PetscReal *A, PetscInt n, PetscReal *rho,
596 PetscReal *maxRealPart)
597{
598 PetscBLASInt N, lda, lwork, info;
599 PetscReal *Acopy, *wr, *wi, *work, dummy = 0.0;
600 PetscFunctionBeginUser;
601 PetscCall(PetscBLASIntCast(n, &N)); lda = N; lwork = 8*N;
602 PetscCall(PetscMalloc4(n*n, &Acopy, n, &wr, n, &wi, (size_t)lwork, &work));
603 for (PetscInt t = 0; t < n*n; t++) Acopy[t] = A[t];
604 {
605 char nochar = 'N';
606 LAPACKgeev_(&nochar, &nochar, &N, Acopy, &lda, wr, wi, &dummy, &lda, &dummy, &lda, work, &lwork, &info);
607 }
608 PetscCheck(info == 0, PETSC_COMM_SELF, PETSC_ERR_LIB, "LAPACK dgeev failed: info=%d", (int)info);
609 *rho = 0.0; *maxRealPart = -PETSC_MAX_REAL;
610 for (PetscInt t = 0; t < n; t++) {
611 *rho = PetscMax(*rho, PetscSqrtReal(wr[t]*wr[t] + wi[t]*wi[t]));
612 *maxRealPart = PetscMax(*maxRealPart, wr[t]);
613 }
614 PetscCall(PetscFree4(Acopy, wr, wi, work));
615 PetscFunctionReturn(0);
616}
617
618/**
619 * @brief Computes the spectral radius of the RK polynomial by applying it to eig(J).
620 */
621static PetscErrorCode DenseRKPolynomialSpectralRadius(const PetscReal *J, PetscInt n,
622 PetscReal dtau, PetscReal *rho)
623{
624 PetscBLASInt N, lda, lwork, info;
625 PetscReal *Jcopy, *wr, *wi, *work, dummy = 0.0;
626 PetscFunctionBeginUser;
627 PetscCall(PetscBLASIntCast(n, &N)); lda = N; lwork = 8*N;
628 PetscCall(PetscMalloc4(n*n, &Jcopy, n, &wr, n, &wi, (size_t)lwork, &work));
629 for (PetscInt t = 0; t < n*n; t++) Jcopy[t] = J[t];
630 {
631 char nochar = 'N';
632 LAPACKgeev_(&nochar, &nochar, &N, Jcopy, &lda, wr, wi, &dummy, &lda, &dummy, &lda, work, &lwork, &info);
633 }
634 PetscCheck(info == 0, PETSC_COMM_SELF, PETSC_ERR_LIB, "LAPACK dgeev failed: info=%d", (int)info);
635 *rho = 0.0;
636 for (PetscInt t = 0; t < n; t++) {
637 const PetscReal zr = dtau*wr[t], zi = dtau*wi[t];
638 const PetscReal z2r = zr*zr - zi*zi, z2i = 2.0*zr*zi;
639 const PetscReal z3r = z2r*zr - z2i*zi, z3i = z2r*zi + z2i*zr;
640 const PetscReal z4r = z3r*zr - z3i*zi, z4i = z3r*zi + z3i*zr;
641 const PetscReal pr = 1.0 + zr + 0.5*z2r + z3r/6.0 + z4r/24.0;
642 const PetscReal pi = zi + 0.5*z2i + z3i/6.0 + z4i/24.0;
643 *rho = PetscMax(*rho, PetscSqrtReal(pr*pr + pi*pi));
644 }
645 PetscCall(PetscFree4(Jcopy, wr, wi, work));
646 PetscFunctionReturn(0);
647}
648
649/* sigma_max(J) = ||J||_2 (and optionally the dominant right singular vector v1). */
650static PetscErrorCode DenseSigmaMax(const PetscReal *A, PetscInt n, PetscReal *smax, PetscReal *v1)
651{
652 PetscBLASInt N, lda, lwork, info;
653 PetscReal *Acopy, *S, *VT, *work, ufake = 0.0;
654 PetscFunctionBeginUser;
655 PetscCall(PetscBLASIntCast(n, &N)); lda = N; lwork = 8*N + 4*N;
656 PetscCall(PetscMalloc4(n*n, &Acopy, n, &S, n*n, &VT, (size_t)lwork, &work));
657 for (PetscInt t = 0; t < n*n; t++) Acopy[t] = A[t];
658 {
659 char jobu = 'N', jobvt = v1 ? 'S' : 'N';
660 LAPACKgesvd_(&jobu, &jobvt, &N, &N, Acopy, &lda, S, &ufake, &lda, VT, &lda, work, &lwork, &info);
661 }
662 PetscCheck(info == 0, PETSC_COMM_SELF, PETSC_ERR_LIB, "LAPACK dgesvd failed: info=%d", (int)info);
663 *smax = S[0];
664 if (v1) { for (PetscInt r = 0; r < n; r++) v1[r] = VT[0 + r*n]; } /* first row of VT = v1^T */
665 PetscCall(PetscFree4(Acopy, S, VT, work));
666 PetscFunctionReturn(0);
667}
668
669/* Frobenius non-normality ||J^T J - J J^T||_F / max(||J||_F^2, eps). */
670static PetscReal DenseNonNormality(const PetscReal *A, PetscInt n)
671{
672 PetscReal fro2 = 0.0, comm = 0.0;
673 for (PetscInt t = 0; t < n*n; t++) fro2 += A[t]*A[t];
674 for (PetscInt p = 0; p < n; p++)
675 for (PetscInt q = 0; q < n; q++) {
676 PetscReal ata = 0.0, aat = 0.0;
677 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]; }
678 const PetscReal d = ata - aat; comm += d*d;
679 }
680 return PetscSqrtReal(comm) / PetscMax(fro2, PETSC_MACHINE_EPSILON);
681}
682
683/**
684 * @brief Computes the normalized Frobenius defect from skew symmetry.
685 */
686static PetscReal DenseSkewnessDefect(const PetscReal *A, PetscInt n)
687{
688 PetscReal fro2 = 0.0, sym2 = 0.0;
689 for (PetscInt i = 0; i < n*n; i++) fro2 += A[i]*A[i];
690 for (PetscInt c = 0; c < n; c++)
691 for (PetscInt r = 0; r < n; r++) {
692 const PetscReal s = A[r + c*n] + A[c + r*n];
693 sym2 += s*s;
694 }
695 return PetscSqrtReal(sym2) / PetscMax(PetscSqrtReal(fro2), PETSC_MACHINE_EPSILON);
696}
697
698/**
699 * @brief Copies a dense matrix and adds a scalar shift to its diagonal.
700 */
701static PetscErrorCode DenseShiftIdentity(const PetscReal *A, PetscInt n, PetscReal shift, PetscReal *B)
702{
703 PetscFunctionBeginUser;
704 PetscCall(PetscArraycpy(B, A, (size_t)n*n));
705 for (PetscInt d = 0; d < n; d++) B[d + d*n] += shift;
706 PetscFunctionReturn(0);
707}
708
709/* B = alpha*A (column-major) ; C = A*Bm ; returns into out (n x n). */
710static void MatMul(const PetscReal *A, const PetscReal *B, PetscReal *out, PetscInt n)
711{
712 for (PetscInt c = 0; c < n; c++)
713 for (PetscInt r = 0; r < n; r++) {
714 PetscReal s = 0.0;
715 for (PetscInt t = 0; t < n; t++) s += A[r + t*n]*B[t + c*n];
716 out[r + c*n] = s;
717 }
718}
719
720/* 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. */
721static PetscErrorCode RKPolynomial(const PetscReal *J, PetscReal dtau, PetscInt n, PetscReal *P)
722{
723 PetscReal *M, *T, *T2;
724 PetscFunctionBeginUser;
725 PetscCall(PetscMalloc3(n*n, &M, n*n, &T, n*n, &T2));
726 for (PetscInt t = 0; t < n*n; t++) M[t] = dtau*J[t];
727 /* start S = I + M/4 */
728 for (PetscInt t = 0; t < n*n; t++) T[t] = M[t]/4.0;
729 for (PetscInt d = 0; d < n; d++) T[d + d*n] += 1.0;
730 const PetscReal coef[3] = {3.0, 2.0, 1.0}; /* divide by 3, then 2, then 1 */
731 for (int s = 0; s < 3; s++) {
732 MatMul(M, T, T2, n); /* T2 = M*S */
733 for (PetscInt t = 0; t < n*n; t++) T[t] = T2[t]/coef[s];
734 for (PetscInt d = 0; d < n; d++) T[d + d*n] += 1.0; /* S = I + M/coef * S */
735 }
736 for (PetscInt t = 0; t < n*n; t++) P[t] = T[t];
737 PetscCall(PetscFree3(M, T, T2));
738 PetscFunctionReturn(0);
739}
740
742
743/**
744 * @brief Evaluates either spectral-radius or 2-norm amplification for one pseudo-time step.
745 */
746static PetscErrorCode AmplificationMetric(const PetscReal *J, PetscInt n, PetscReal dtau,
747 PMetric which, PetscReal *metric)
748{
749 PetscReal *Pm;
750 PetscFunctionBeginUser;
751 if (which == METRIC_RHO) {
752 PetscCall(DenseRKPolynomialSpectralRadius(J, n, dtau, metric));
753 PetscFunctionReturn(0);
754 }
755 PetscCall(PetscMalloc1(n*n, &Pm));
756 PetscCall(RKPolynomial(J, dtau, n, Pm));
757 PetscCall(DenseSigmaMax(Pm, n, metric, NULL));
758 PetscCall(PetscFree(Pm));
759 PetscFunctionReturn(0);
760}
761
762/* Stable interval connected to CFL=0; tolerance=1e-8, initial probe=1e-8, scan step=0.01, max=4.0. */
763static PetscErrorCode StableCFL(const PetscReal *J, PetscInt n, PetscReal lam, PMetric which,
764 StableCFLResult *result)
765{
766 const PetscReal tol = 1e-8, probe = 1e-8, scan_step = 0.01, hi = 4.0;
767 const PetscReal probe_tol = 1e-12, min_positive_cfl = 1e-6;
768 PetscReal met;
769 PetscFunctionBeginUser;
770 result->status = STABLE_CFL_NONE;
771 result->cfl = 0.0;
772 if (which == METRIC_RHO) {
773 PetscReal rhoJ, maxreJ;
774 PetscCall(DenseSpectralRadius(J, n, &rhoJ, &maxreJ));
775 if (maxreJ > 1e-8) PetscFunctionReturn(0);
776 }
777 PetscCall(AmplificationMetric(J, n, probe/lam, which, &met));
778 if (met > 1.0 + probe_tol) PetscFunctionReturn(0);
779
780 PetscReal stable = probe, cross_b = -1.0;
781 for (PetscReal cfl = scan_step; cfl <= hi + 1e-12; cfl += scan_step) {
782 PetscCall(AmplificationMetric(J, n, cfl/lam, which, &met));
783 if (met > 1.0 + tol) { cross_b = cfl; break; }
784 stable = cfl;
785 }
786 if (cross_b < 0.0) {
788 result->cfl = hi;
789 PetscFunctionReturn(0);
790 }
791 PetscReal cross_a = stable;
792 for (int it = 0; it < 40; it++) {
793 const PetscReal mid = 0.5*(cross_a + cross_b);
794 PetscCall(AmplificationMetric(J, n, mid/lam, which, &met));
795 if (met > 1.0 + tol) cross_b = mid; else cross_a = mid;
796 }
797 if (cross_a < min_positive_cfl) PetscFunctionReturn(0);
798 result->status = STABLE_CFL_FINITE;
799 result->cfl = cross_a;
800 PetscFunctionReturn(0);
801}
802
803/**
804 * @brief Returns human-readable text for a stable-CFL search result.
805 */
807{
808 if (r.status == STABLE_CFL_NONE) return "no positive stable interval connected to zero";
809 if (r.status == STABLE_CFL_EXCEEDS_SCAN) return "stable through CFL >= 4.0";
810 return "stable CFL";
811}
812
813/**
814 * @brief Prints one candidate's eigenvalue and norm stable-CFL statuses.
815 */
816static PetscErrorCode PrintStableCFLLine(const char *candidate,
817 StableCFLResult eig,
818 StableCFLResult norm)
819{
820 PetscFunctionBeginUser;
821 PetscCall(PetscPrintf(PETSC_COMM_WORLD, " cand %s: eig %s", candidate, StableCFLStatusText(eig)));
822 if (eig.status == STABLE_CFL_FINITE) PetscCall(PetscPrintf(PETSC_COMM_WORLD, " = %.4f", (double)eig.cfl));
823 PetscCall(PetscPrintf(PETSC_COMM_WORLD, " | norm %s", StableCFLStatusText(norm)));
824 if (norm.status == STABLE_CFL_FINITE) PetscCall(PetscPrintf(PETSC_COMM_WORLD, " = %.4f", (double)norm.cfl));
825 PetscCall(PetscPrintf(PETSC_COMM_WORLD, "\n"));
826 PetscFunctionReturn(0);
827}
828
829/* P(M) applied to a vector: out = P(dtau*J) * x (dense). */
830static void ApplyP(const PetscReal *P, const PetscReal *x, PetscReal *out, PetscInt n)
831{
832 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; }
833}
834
835/**
836 * @brief Prints frozen RK amplification tables for the supplied operator and candidates.
837 */
838static PetscErrorCode PrintFrozenAmplificationTable(const char *title, const PetscReal *J,
839 PetscInt n, const PetscReal lams[3],
840 const char *cn[3])
841{
842 const PetscReal cfls[5] = {0.25, 0.50, 1.00, 1.50, 2.00};
843 PetscReal *Pm;
844 PetscFunctionBeginUser;
845 PetscCall(PetscMalloc1(n*n, &Pm));
846 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
847 " --- %s ---\n"
848 " candidate CFL dtau rho(P) sigma_max(P)\n", title));
849 for (int c = 0; c < 3; c++) {
850 if (!(lams[c] > 0.0)) continue;
851 for (int q = 0; q < 5; q++) {
852 const PetscReal dtau = cfls[q]/lams[c];
853 PetscReal rhoP, smaxP;
854 PetscCall(RKPolynomial(J, dtau, n, Pm));
855 PetscCall(DenseRKPolynomialSpectralRadius(J, n, dtau, &rhoP));
856 PetscCall(DenseSigmaMax(Pm, n, &smaxP, NULL));
857 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
858 " %-9s %.2f %.6e %.6e %.6e\n",
859 cn[c], (double)cfls[q], (double)dtau, (double)rhoP, (double)smaxP));
860 }
861 }
862 PetscCall(PetscFree(Pm));
863 PetscFunctionReturn(0);
864}
865
866/**
867 * @brief Computes the Euclidean norm of a dense vector.
868 */
869static PetscReal VecNorm2Array(const PetscReal *x, PetscInt n)
870{
871 PetscReal s = 0.0;
872 for (PetscInt i = 0; i < n; i++) s += x[i]*x[i];
873 return PetscSqrtReal(s);
874}
875
876/**
877 * @brief Computes the infinity norm of a dense vector.
878 */
879static PetscReal VecNormInfArray(const PetscReal *x, PetscInt n)
880{
881 PetscReal s = 0.0;
882 for (PetscInt i = 0; i < n; i++) s = PetscMax(s, PetscAbsReal(x[i]));
883 return s;
884}
885
886/**
887 * @brief Computes the Frobenius norm of a dense column-major matrix.
888 */
889static PetscReal DenseFrobenius(const PetscReal *A, PetscInt n)
890{
891 PetscReal s = 0.0;
892 for (PetscInt i = 0; i < n*n; i++) s += A[i]*A[i];
893 return PetscSqrtReal(s);
894}
895
896/**
897 * @brief Computes a normalized Frobenius difference between two dense matrices.
898 */
899static PetscReal DenseRelativeDiff(const PetscReal *A, const PetscReal *B, PetscInt n, PetscReal denom_ref)
900{
901 PetscReal s = 0.0;
902 for (PetscInt i = 0; i < n*n; i++) {
903 const PetscReal d = A[i] - B[i];
904 s += d*d;
905 }
906 return PetscSqrtReal(s) / PetscMax(1.0, denom_ref);
907}
908
909/**
910 * @brief Adds a dense active-space vector into a global contravariant vector.
911 */
912static PetscErrorCode AddActiveVector(UserCtx *user, Vec U, const DofMap *map,
913 const PetscReal *x, PetscReal scale)
914{
915 Cmpnts ***a;
916 PetscFunctionBeginUser;
917 PetscCall(DMDAVecGetArray(user->fda, U, &a));
918 for (PetscInt m = 0; m < map->n; m++) {
919 PetscReal *p = (PetscReal*)&a[map->ck[m]][map->cj[m]][map->ci[m]];
920 p[map->comp[m]] += scale*x[m];
921 }
922 PetscCall(DMDAVecRestoreArray(user->fda, U, &a));
923 PetscFunctionReturn(0);
924}
925
926/**
927 * @brief Extracts active-space entries from a global contravariant vector.
928 */
929static PetscErrorCode ExtractActiveVector(UserCtx *user, Vec U, const DofMap *map, PetscReal *x)
930{
931 Cmpnts ***a;
932 PetscFunctionBeginUser;
933 PetscCall(DMDAVecGetArrayRead(user->fda, U, &a));
934 for (PetscInt m = 0; m < map->n; m++) {
935 const PetscReal *p = (const PetscReal*)&a[map->ck[m]][map->cj[m]][map->ci[m]];
936 x[m] = p[map->comp[m]];
937 }
938 PetscCall(DMDAVecRestoreArrayRead(user->fda, U, &a));
939 PetscFunctionReturn(0);
940}
941
942/**
943 * @brief Returns a deterministic checksum weight for an active DOF.
944 */
945static PetscReal DofWeight(const DofMap *map, PetscInt m)
946{
947 return 1.0 + 0.013*(PetscReal)(map->comp[m]+1)
948 + 0.017*(PetscReal)map->ci[m]
949 + 0.019*(PetscReal)map->cj[m]
950 + 0.023*(PetscReal)map->ck[m];
951}
952
953/**
954 * @brief Computes global active-vector norms and checksum.
955 */
956static PetscErrorCode ActiveStats(const DofMap *map, const PetscReal *x, GlobalVecStats *stats)
957{
958 PetscReal loc2 = 0.0, locinf = 0.0, locsum = 0.0;
959 PetscReal glo2, gloinf, glosum;
960 PetscFunctionBeginUser;
961 for (PetscInt m = 0; m < map->n; m++) {
962 loc2 += x[m]*x[m];
963 locinf = PetscMax(locinf, PetscAbsReal(x[m]));
964 locsum += DofWeight(map, m)*x[m];
965 }
966 PetscCallMPI(MPI_Allreduce(&loc2, &glo2, 1, MPIU_REAL, MPI_SUM, PETSC_COMM_WORLD));
967 PetscCallMPI(MPI_Allreduce(&locinf, &gloinf, 1, MPIU_REAL, MPI_MAX, PETSC_COMM_WORLD));
968 PetscCallMPI(MPI_Allreduce(&locsum, &glosum, 1, MPIU_REAL, MPI_SUM, PETSC_COMM_WORLD));
969 stats->n2 = PetscSqrtReal(glo2); stats->ninf = gloinf; stats->checksum = glosum;
970 PetscFunctionReturn(0);
971}
972
973/**
974 * @brief Fills a globally normalized deterministic active-space perturbation direction.
975 */
976static PetscErrorCode FillDeterministicDirection(UserCtx *user, Vec V, const DofMap *map, PetscReal *x)
977{
978 PetscReal loc2 = 0.0, glo2;
979 PetscFunctionBeginUser;
980 PetscCall(VecSet(V, 0.0));
981 for (PetscInt m = 0; m < map->n; m++) {
982 x[m] = PetscSinReal(0.37*(PetscReal)(map->ci[m]+1)
983 + 0.51*(PetscReal)(map->cj[m]+1)
984 + 0.73*(PetscReal)(map->ck[m]+1)
985 + 0.29*(PetscReal)(map->comp[m]+1));
986 loc2 += x[m]*x[m];
987 }
988 PetscCallMPI(MPI_Allreduce(&loc2, &glo2, 1, MPIU_REAL, MPI_SUM, PETSC_COMM_WORLD));
989 const PetscReal invn = 1.0/PetscSqrtReal(glo2);
990 for (PetscInt m = 0; m < map->n; m++) x[m] *= invn;
991 PetscCall(AddActiveVector(user, V, map, x, 1.0));
992 PetscFunctionReturn(0);
993}
994
995/* ----------------------------------------------------------------------------------- *
996 * Direct anchored 4-stage recurrence on the real residual (no controller). *
997 * ----------------------------------------------------------------------------------- */
998/* Phi(Ufull) -> result stored in Uout (global), using EvalConvResidual at each stage. */
999static PetscErrorCode FourStage(UserCtx *user, Vec U0full, PetscReal dtau, Vec Rhs, const DofMap *map,
1000 PetscReal *Rscratch, Vec Uwork, Vec Uout)
1001{
1002 const PetscReal alfa[4] = {0.25, 1.0/3.0, 0.5, 1.0};
1003 PetscFunctionBeginUser;
1004 PetscCall(VecCopy(U0full, Uwork)); /* stage state */
1005 for (int s = 0; s < 4; s++) {
1006 PetscCall(EvalConvResidual(user, Uwork, Rhs, map, Rscratch)); /* R(U^{(s)}) into active rows */
1007 PetscCall(VecCopy(U0full, Uout)); /* U^{(s+1)} = U0 + alfa*dtau*R */
1008 Cmpnts ***a;
1009 PetscCall(DMDAVecGetArray(user->fda, Uout, &a));
1010 for (PetscInt m = 0; m < map->n; m++) {
1011 PetscReal *p = (PetscReal*)&a[map->ck[m]][map->cj[m]][map->ci[m]];
1012 p[map->comp[m]] += alfa[s]*dtau*Rscratch[m];
1013 }
1014 PetscCall(DMDAVecRestoreArray(user->fda, Uout, &a));
1015 PetscCall(VecCopy(Uout, Uwork));
1016 }
1017 PetscFunctionReturn(0);
1018}
1019
1020/**
1021 * @brief Forms one anchored RK stage state from the base state and active residual.
1022 */
1023static PetscErrorCode SetAnchoredStage(UserCtx *user, Vec U0full, PetscReal scale,
1024 const DofMap *map, const PetscReal *Ract, Vec Ustage)
1025{
1026 PetscFunctionBeginUser;
1027 PetscCall(VecCopy(U0full, Ustage));
1028 PetscCall(AddActiveVector(user, Ustage, map, Ract, scale));
1029 PetscFunctionReturn(0);
1030}
1031
1032/**
1033 * @brief Builds the first three anchored RK stage states for a base vector.
1034 */
1035static PetscErrorCode BuildStageStates(UserCtx *user, Vec U0full, PetscReal dtau, Vec Rhs,
1036 const DofMap *map, PetscReal *Rscratch,
1037 Vec Y1, Vec Y2, Vec Y3)
1038{
1039 const PetscReal alfa[3] = {0.25, 1.0/3.0, 0.5};
1040 PetscFunctionBeginUser;
1041 PetscCall(EvalConvResidual(user, U0full, Rhs, map, Rscratch));
1042 PetscCall(SetAnchoredStage(user, U0full, alfa[0]*dtau, map, Rscratch, Y1));
1043 PetscCall(EvalConvResidual(user, Y1, Rhs, map, Rscratch));
1044 PetscCall(SetAnchoredStage(user, U0full, alfa[1]*dtau, map, Rscratch, Y2));
1045 PetscCall(EvalConvResidual(user, Y2, Rhs, map, Rscratch));
1046 PetscCall(SetAnchoredStage(user, U0full, alfa[2]*dtau, map, Rscratch, Y3));
1047 PetscFunctionReturn(0);
1048}
1049
1050/**
1051 * @brief Builds a centered finite-difference Jacobian of the production convective residual.
1052 */
1053static PetscErrorCode BuildFDJacobian(UserCtx *user, Vec Ucenter, PetscReal epsrel, Vec Rhs,
1054 const DofMap *map, PetscReal *Rp, PetscReal *Rm,
1055 Vec Uwork, PetscReal *J)
1056{
1057 PetscFunctionBeginUser;
1058 for (PetscInt col = 0; col < map->n; col++) {
1059 PetscReal u0;
1060 PetscCall(GetDof(user, Ucenter, map, col, &u0));
1061 const PetscReal eps = epsrel*PetscMax(1.0, PetscAbsReal(u0));
1062 PetscCall(VecCopy(Ucenter, Uwork));
1063 PetscCall(PerturbDof(user, Uwork, map, col, +eps));
1064 PetscCall(EvalConvResidual(user, Uwork, Rhs, map, Rp));
1065 PetscCall(VecCopy(Ucenter, Uwork));
1066 PetscCall(PerturbDof(user, Uwork, map, col, -eps));
1067 PetscCall(EvalConvResidual(user, Uwork, Rhs, map, Rm));
1068 for (PetscInt row = 0; row < map->n; row++) J[row + col*map->n] = (Rp[row]-Rm[row])/(2.0*eps);
1069 }
1070 PetscFunctionReturn(0);
1071}
1072
1073/**
1074 * @brief Builds the exact four-stage tangent from stage-dependent Jacobians.
1075 */
1076static PetscErrorCode BuildStageTangent(const PetscReal *J0, const PetscReal *J1,
1077 const PetscReal *J2, const PetscReal *J3,
1078 PetscReal dtau, PetscInt n, PetscReal *T4)
1079{
1080 const PetscReal alfa[4] = {0.25, 1.0/3.0, 0.5, 1.0};
1081 PetscReal *T1, *T2, *T3, *Tmp;
1082 PetscFunctionBeginUser;
1083 PetscCall(PetscMalloc4(n*n, &T1, n*n, &T2, n*n, &T3, n*n, &Tmp));
1084
1085 for (PetscInt i = 0; i < n*n; i++) T1[i] = alfa[0]*dtau*J0[i];
1086 for (PetscInt d = 0; d < n; d++) T1[d + d*n] += 1.0;
1087
1088 MatMul(J1, T1, Tmp, n);
1089 for (PetscInt i = 0; i < n*n; i++) T2[i] = alfa[1]*dtau*Tmp[i];
1090 for (PetscInt d = 0; d < n; d++) T2[d + d*n] += 1.0;
1091
1092 MatMul(J2, T2, Tmp, n);
1093 for (PetscInt i = 0; i < n*n; i++) T3[i] = alfa[2]*dtau*Tmp[i];
1094 for (PetscInt d = 0; d < n; d++) T3[d + d*n] += 1.0;
1095
1096 MatMul(J3, T3, Tmp, n);
1097 for (PetscInt i = 0; i < n*n; i++) T4[i] = alfa[3]*dtau*Tmp[i];
1098 for (PetscInt d = 0; d < n; d++) T4[d + d*n] += 1.0;
1099
1100 PetscCall(PetscFree4(T1, T2, T3, Tmp));
1101 PetscFunctionReturn(0);
1102}
1103
1104/**
1105 * @brief Builds a finite-difference Jacobian of the complete nonlinear four-stage map.
1106 */
1107static PetscErrorCode BuildPhiJacobian(UserCtx *user, Vec Ucenter, PetscReal dtau, Vec Rhs,
1108 const DofMap *map, PetscReal *Rscratch,
1109 Vec Upert, Vec Ustage, Vec PhiP, Vec PhiM,
1110 PetscReal *xp, PetscReal *xm, PetscReal epsrel,
1111 PetscReal *JPhi)
1112{
1113 PetscFunctionBeginUser;
1114 for (PetscInt col = 0; col < map->n; col++) {
1115 PetscReal u0;
1116 PetscCall(GetDof(user, Ucenter, map, col, &u0));
1117 const PetscReal eps = epsrel*PetscMax(1.0, PetscAbsReal(u0));
1118 PetscCall(VecCopy(Ucenter, Upert));
1119 PetscCall(PerturbDof(user, Upert, map, col, +eps));
1120 PetscCall(FourStage(user, Upert, dtau, Rhs, map, Rscratch, Ustage, PhiP));
1121 PetscCall(VecCopy(Ucenter, Upert));
1122 PetscCall(PerturbDof(user, Upert, map, col, -eps));
1123 PetscCall(FourStage(user, Upert, dtau, Rhs, map, Rscratch, Ustage, PhiM));
1124 PetscCall(ExtractActiveVector(user, PhiP, map, xp));
1125 PetscCall(ExtractActiveVector(user, PhiM, map, xm));
1126 for (PetscInt row = 0; row < map->n; row++) JPhi[row + col*map->n] = (xp[row]-xm[row])/(2.0*eps);
1127 }
1128 PetscFunctionReturn(0);
1129}
1130
1131/**
1132 * @brief Runs stage-dependent RK tangent and direct nonlinear perturbation diagnostics.
1133 */
1134static PetscErrorCode RunRKTangentDiagnostics(UserCtx *user, CandState st, Vec Ubase, Vec Rhs,
1135 const DofMap *map, PetscReal epsrel,
1136 const PetscReal lams[3], const char *cn[3],
1137 const PetscReal *J0)
1138{
1139 const PetscReal cflsB[4] = {0.1, 0.25, 0.5, 1.0};
1140 const PetscReal cflsOther[1] = {0.5};
1141 const PetscReal *cfls = (st == STATE_B) ? cflsB : cflsOther;
1142 const PetscInt ncfl = (st == STATE_B) ? 4 : 1;
1143 const PetscReal amps[3] = {1e-4, 1e-5, 1e-6};
1144 Vec Y1, Y2, Y3, Upert, Ustage, PhiP, PhiM, Phi0;
1145 PetscReal *Rtmp, *J1, *J2, *J3, *T4, *Pm, *JPhi, *v1, *xrand;
1146 PetscReal *meas, *phi0, *phip, *xscaled, *predP, *predT;
1147 PetscFunctionBeginUser;
1148
1149 PetscCall(VecDuplicate(Ubase, &Y1));
1150 PetscCall(VecDuplicate(Ubase, &Y2));
1151 PetscCall(VecDuplicate(Ubase, &Y3));
1152 PetscCall(VecDuplicate(Ubase, &Upert));
1153 PetscCall(VecDuplicate(Ubase, &Ustage));
1154 PetscCall(VecDuplicate(Ubase, &PhiP));
1155 PetscCall(VecDuplicate(Ubase, &PhiM));
1156 PetscCall(VecDuplicate(Ubase, &Phi0));
1157 PetscCall(PetscMalloc5(map->n, &Rtmp, (size_t)map->n*map->n, &J1,
1158 (size_t)map->n*map->n, &J2, (size_t)map->n*map->n, &J3,
1159 (size_t)map->n*map->n, &T4));
1160 PetscCall(PetscMalloc5((size_t)map->n*map->n, &Pm, (size_t)map->n*map->n, &JPhi,
1161 map->n, &v1, map->n, &xrand, map->n, &meas));
1162 PetscCall(PetscMalloc5(map->n, &phi0, map->n, &phip, map->n, &xscaled,
1163 map->n, &predP, map->n, &predT));
1164
1165 PetscReal nrm = 0.0;
1166 for (PetscInt m = 0; m < map->n; m++) {
1167 xrand[m] = PetscSinReal((PetscReal)(m+1)*1.2345);
1168 nrm += xrand[m]*xrand[m];
1169 }
1170 nrm = PetscSqrtReal(nrm);
1171 for (PetscInt m = 0; m < map->n; m++) xrand[m] /= nrm;
1172
1173 for (int cand = 0; cand < 3; cand++) {
1174 if (!(lams[cand] > 0.0)) continue;
1175 for (PetscInt icfl = 0; icfl < ncfl; icfl++) {
1176 const PetscReal cfl = cfls[icfl], dtau = cfl/lams[cand];
1177 PetscCall(BuildStageStates(user, Ubase, dtau, Rhs, map, Rtmp, Y1, Y2, Y3));
1178 PetscCall(BuildFDJacobian(user, Y1, epsrel, Rhs, map, phi0, phip, Upert, J1));
1179 PetscCall(BuildFDJacobian(user, Y2, epsrel, Rhs, map, phi0, phip, Upert, J2));
1180 PetscCall(BuildFDJacobian(user, Y3, epsrel, Rhs, map, phi0, phip, Upert, J3));
1181 PetscCall(BuildStageTangent(J0, J1, J2, J3, dtau, map->n, T4));
1182 PetscCall(RKPolynomial(J0, dtau, map->n, Pm));
1183 PetscCall(BuildPhiJacobian(user, Ubase, dtau, Rhs, map, Rtmp,
1184 Upert, Ustage, PhiP, PhiM, phip, phi0, epsrel, JPhi));
1185
1186 const PetscReal froT = DenseFrobenius(T4, map->n);
1187 const PetscReal froPhi = DenseFrobenius(JPhi, map->n);
1188 const PetscReal relTP = DenseRelativeDiff(T4, Pm, map->n, froT);
1189 const PetscReal relPhiT = DenseRelativeDiff(JPhi, T4, map->n, froPhi);
1190 const PetscReal relPhiP = DenseRelativeDiff(JPhi, Pm, map->n, froPhi);
1191 PetscReal smaxT, rhoT, dummyT;
1192 PetscCall(DenseSpectralRadius(T4, map->n, &rhoT, &dummyT));
1193 PetscCall(DenseSigmaMax(T4, map->n, &smaxT, v1));
1194
1195 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1196 " --- convection-only stage-dependent RK tangent (cand %s, CFL=%.2f, dtau=%.6e) ---\n"
1197 " rho(T4)=%.6e sigma_max(T4)=%.6e\n"
1198 " ||T4-P(hJ0)||F/max(1,||T4||F) = %.3e\n"
1199 " ||J_Phi-T4||F/max(1,||J_Phi||F) = %.3e\n"
1200 " ||J_Phi-P(hJ0)||F/max(1,||J_Phi||F)= %.3e\n",
1201 cn[cand], (double)cfl, (double)dtau, (double)rhoT, (double)smaxT,
1202 (double)relTP, (double)relPhiT, (double)relPhiP));
1203
1204 PetscCall(FourStage(user, Ubase, dtau, Rhs, map, Rtmp, Ustage, Phi0));
1205 PetscCall(ExtractActiveVector(user, Phi0, map, phi0));
1206 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1207 " direction amp amp(meas) amp(frozen) amp(stage) err(frozen) err(stage)\n"));
1208 for (int dir = 0; dir < 2; dir++) {
1209 const PetscReal *xv = (dir == 0) ? xrand : v1;
1210 const char *dname = (dir == 0) ? "random" : "v1(T4)";
1211 for (int a = 0; a < 3; a++) {
1212 const PetscReal amp = amps[a];
1213 PetscCall(VecCopy(Ubase, Upert));
1214 PetscCall(AddActiveVector(user, Upert, map, xv, amp));
1215 PetscCall(FourStage(user, Upert, dtau, Rhs, map, Rtmp, Ustage, PhiP));
1216 PetscCall(ExtractActiveVector(user, PhiP, map, phip));
1217 for (PetscInt m = 0; m < map->n; m++) meas[m] = phip[m] - phi0[m];
1218 for (PetscInt m = 0; m < map->n; m++) xscaled[m] = amp*xv[m];
1219 ApplyP(Pm, xscaled, predP, map->n);
1220 ApplyP(T4, xscaled, predT, map->n);
1221
1222 PetscReal eP = 0.0, eT = 0.0;
1223 for (PetscInt m = 0; m < map->n; m++) {
1224 const PetscReal dP = meas[m] - predP[m];
1225 const PetscReal dT = meas[m] - predT[m];
1226 eP += dP*dP; eT += dT*dT;
1227 }
1228 const PetscReal nm = VecNorm2Array(meas, map->n);
1229 const PetscReal nP = VecNorm2Array(predP, map->n);
1230 const PetscReal nT = VecNorm2Array(predT, map->n);
1231 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1232 " %-8s %.0e %.6e %.6e %.6e %.3e %.3e\n",
1233 dname, (double)amp, (double)(nm/amp), (double)(nP/amp), (double)(nT/amp),
1234 (double)(PetscSqrtReal(eP)/PetscMax(PETSC_MACHINE_EPSILON, nP)),
1235 (double)(PetscSqrtReal(eT)/PetscMax(PETSC_MACHINE_EPSILON, nT))));
1236 }
1237 }
1238
1239 PetscCall(PicurvAssertBool((PetscBool)(relPhiT < 1e-6),
1240 "stage tangent matches direct finite-difference RK map"));
1241 if (st == STATE_B) {
1242 PetscCall(PicurvAssertBool((PetscBool)(relTP > 1e-5),
1243 "B: non-steady base makes frozen-Jacobian RK map measurably different"));
1244 PetscCall(PicurvAssertBool((PetscBool)(relPhiP > 1e-5),
1245 "B: direct RK map confirms frozen-Jacobian error"));
1246 } else {
1247 PetscCall(PicurvAssertBool((PetscBool)(relTP < 1e-8),
1248 "steady base reduces stage tangent to frozen RK polynomial"));
1249 }
1250 }
1251 }
1252
1253 PetscCall(PetscFree5(Rtmp, J1, J2, J3, T4));
1254 PetscCall(PetscFree5(Pm, JPhi, v1, xrand, meas));
1255 PetscCall(PetscFree5(phi0, phip, xscaled, predP, predT));
1256 PetscCall(VecDestroy(&Y1)); PetscCall(VecDestroy(&Y2)); PetscCall(VecDestroy(&Y3));
1257 PetscCall(VecDestroy(&Upert)); PetscCall(VecDestroy(&Ustage));
1258 PetscCall(VecDestroy(&PhiP)); PetscCall(VecDestroy(&PhiM)); PetscCall(VecDestroy(&Phi0));
1259 PetscFunctionReturn(0);
1260}
1261
1262/* ----------------------------------------------------------------------------------- *
1263 * The A4a study for one state. *
1264 * ----------------------------------------------------------------------------------- */
1265static PetscErrorCode RunState(CandState st, const char *name)
1266{
1267 SimCtx *simCtx = NULL; UserCtx *user = NULL; DofMap map;
1268 Vec Ubase, Rhs, Uwork, Uout;
1269 PetscReal *Rref, *Rrep, *Jbest;
1270 PetscReal repeat_err, maxdiv, det_err;
1271 SeamDiagnostics seam;
1273 const PetscInt N = (st == STATE_C) ? 5 : 4; /* C uses one extra point for canonical shear. */
1274 PetscFunctionBeginUser;
1275
1276 /* periodic Cartesian fixture, inviscid + centered + P=0, no LES/RANS/Clark/IB/body force. */
1277 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(&simCtx, &user, N, N, N, PETSC_TRUE, PETSC_TRUE, PETSC_TRUE));
1278 PetscCall(ConfigureCandidateFixture(simCtx, user));
1279
1280 PetscCall(DofMapBuild(user, &map));
1281 PetscCall(VecDuplicate(user->Ucont, &Ubase));
1282 PetscCall(VecDuplicate(user->Ucont, &Rhs));
1283 PetscCall(VecDuplicate(user->Ucont, &Uwork));
1284 PetscCall(VecDuplicate(user->Ucont, &Uout));
1285 PetscCall(PetscMalloc3(map.n, &Rref, map.n, &Rrep, (size_t)map.n*map.n, &Jbest));
1286
1287 PetscCall(BuildBaseState(user, st, Ubase, &repeat_err, &maxdiv, &seam));
1288
1289 /* residual determinism: evaluate the base residual twice. */
1290 PetscCall(EvalConvResidual(user, Ubase, Rhs, &map, Rref));
1291 PetscCall(EvalConvResidual(user, Ubase, Rhs, &map, Rrep));
1292 det_err = 0.0; for (PetscInt m = 0; m < map.n; m++) det_err = PetscMax(det_err, PetscAbsReal(Rref[m]-Rrep[m]));
1293 const PetscReal R0_2 = VecNorm2Array(Rref, map.n);
1294 const PetscReal R0_inf = VecNormInfArray(Rref, map.n);
1295
1296 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1297 "\n================ STATE %s ================\n"
1298 " grid: DMDA %d^3 (periodic) | independent face DOFs: actual=%d expected=%d\n"
1299 " declared endpoint mismatch (x,y,z) = %.3e %.3e %.3e\n"
1300 " actual Ucat duplicate mismatch (x,y,z) = %.3e %.3e %.3e\n"
1301 " local lUcat ghost mismatch (x,y,z) = %.3e %.3e %.3e\n"
1302 " actual Ucont duplicate mismatch (x,y,z) = %.3e %.3e %.3e\n"
1303 " ||Ucat_repeat - Ucat_reference||inf = %.3e\n"
1304 " max|div_h Ucont| = %.3e\n"
1305 " ||R(U0)||2 = %.6e ||R(U0)||inf = %.6e\n"
1306 " residual determinism ||R_rep-R_ref||inf = %.3e\n",
1307 name, (int)(N+1), (int)map.n,
1308 (int)map.expected_n,
1309 (double)seam.declared[0], (double)seam.declared[1], (double)seam.declared[2],
1310 (double)seam.ucat_global[0], (double)seam.ucat_global[1], (double)seam.ucat_global[2],
1311 (double)seam.ucat_ghost[0], (double)seam.ucat_ghost[1], (double)seam.ucat_ghost[2],
1312 (double)seam.ucont_global[0], (double)seam.ucont_global[1], (double)seam.ucont_global[2],
1313 (double)repeat_err, (double)maxdiv,
1314 (double)R0_2, (double)R0_inf, (double)det_err));
1315
1316 /* ---- epsilon-convergence study: build J at several eps, compare to next-finer ---- */
1317 const PetscReal epsrel[5] = {1e-4, 1e-5, 1e-6, 1e-7, 1e-8};
1318 PetscReal *Jprev, *Jcur;
1319 PetscCall(PetscMalloc2((size_t)map.n*map.n, &Jprev, (size_t)map.n*map.n, &Jcur));
1320 PetscReal best_rel = PETSC_MAX_REAL; PetscInt best_e = 2;
1321 PetscCall(PetscPrintf(PETSC_COMM_WORLD, " epsilon-convergence (||J_e - J_e/10||_F / ||J||_F):\n"));
1322 for (int e = 0; e < 5; e++) {
1323 /* build column-major J at epsrel[e] */
1324 for (PetscInt col = 0; col < map.n; col++) {
1325 PetscReal u0; PetscCall(GetDof(user, Ubase, &map, col, &u0));
1326 const PetscReal eps = epsrel[e]*PetscMax(1.0, PetscAbsReal(u0));
1327 PetscReal *Rp = Rref, *Rm = Rrep; /* reuse scratch */
1328 PetscCall(VecCopy(Ubase, Uwork));
1329 PetscCall(PerturbDof(user, Uwork, &map, col, +eps));
1330 PetscCall(EvalConvResidual(user, Uwork, Rhs, &map, Rp));
1331 PetscCall(VecCopy(Ubase, Uwork));
1332 PetscCall(PerturbDof(user, Uwork, &map, col, -eps));
1333 PetscCall(EvalConvResidual(user, Uwork, Rhs, &map, Rm));
1334 for (PetscInt row = 0; row < map.n; row++) Jcur[row + col*map.n] = (Rp[row]-Rm[row])/(2.0*eps);
1335 }
1336 if (e > 0) {
1337 PetscReal num = 0.0, den = 0.0;
1338 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]; }
1339 const PetscReal rel = PetscSqrtReal(num)/PetscMax(1.0, PetscSqrtReal(den));
1340 PetscCall(PetscPrintf(PETSC_COMM_WORLD, " eps=%.0e -> %.0e : rel=%.3e\n",
1341 (double)epsrel[e-1], (double)epsrel[e], (double)rel));
1342 if (rel < best_rel) { best_rel = rel; best_e = e; }
1343 }
1344 if (st == STATE_A) {
1345 PetscReal erho, emaxre, esmax;
1346 PetscCall(DenseSpectralRadius(Jcur, map.n, &erho, &emaxre));
1347 PetscCall(DenseSigmaMax(Jcur, map.n, &esmax, NULL));
1348 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1349 " eps=%.0e metrics: rho=%.6e sigma=%.6e max_real=%.6e fro=%.6e skew=%.3e\n",
1350 (double)epsrel[e], (double)erho, (double)esmax, (double)emaxre,
1351 (double)DenseFrobenius(Jcur, map.n), (double)DenseSkewnessDefect(Jcur, map.n)));
1352 }
1353 PetscCall(PetscArraycpy(Jprev, Jcur, (size_t)map.n*map.n)); /* prev <- current (no pointer swap) */
1354 }
1355 /* rebuild J at the plateau epsilon into Jbest */
1356 {
1357 const PetscReal er = epsrel[best_e];
1358 for (PetscInt col = 0; col < map.n; col++) {
1359 PetscReal u0; PetscCall(GetDof(user, Ubase, &map, col, &u0));
1360 const PetscReal eps = er*PetscMax(1.0, PetscAbsReal(u0));
1361 PetscCall(VecCopy(Ubase, Uwork)); PetscCall(PerturbDof(user, Uwork, &map, col, +eps));
1362 PetscCall(EvalConvResidual(user, Uwork, Rhs, &map, Rref));
1363 PetscCall(VecCopy(Ubase, Uwork)); PetscCall(PerturbDof(user, Uwork, &map, col, -eps));
1364 PetscCall(EvalConvResidual(user, Uwork, Rhs, &map, Rrep));
1365 for (PetscInt row = 0; row < map.n; row++) Jbest[row + col*map.n] = (Rref[row]-Rrep[row])/(2.0*eps);
1366 }
1367 PetscCall(PetscPrintf(PETSC_COMM_WORLD, " selected plateau eps = %.0e\n", (double)er));
1368 }
1369 PetscCall(PetscFree2(Jprev, Jcur));
1370
1371 /* The FD probes leave the production vectors at the final perturbed evaluation;
1372 restore the base state before every downstream diagnostic. */
1373 PetscCall(VecCopy(Ubase, user->Ucont));
1374 {
1375 const char *fld[] = {"Ucont"};
1376 PetscCall(SynchronizePeriodicStaggeredFields(user, 1, fld));
1377 }
1378
1379 /* base state must be restored after the Jacobian build. */
1380 PetscBool eqbase; { Vec chk; PetscCall(VecDuplicate(Ubase,&chk)); PetscCall(VecCopy(user->Ucont,chk));
1381 PetscCall(VecAXPY(chk, -1.0, Ubase)); PetscReal nb; PetscCall(VecNorm(chk, NORM_INFINITY, &nb));
1382 eqbase = (PetscBool)(nb < 1e-12); PetscCall(VecDestroy(&chk));
1383 PetscCall(PetscPrintf(PETSC_COMM_WORLD, " base Ucont restored after Jacobian: %s\n", eqbase?"yes":"NO")); }
1384
1385 /* The FD operator acts on Ucont; J is on the convective residual (sign per ComputeRHS). */
1386 PetscReal rho, maxre, smax;
1387 PetscCall(DenseSpectralRadius(Jbest, map.n, &rho, &maxre));
1388 PetscCall(DenseSigmaMax(Jbest, map.n, &smax, NULL));
1389 const PetscReal eta = DenseNonNormality(Jbest, map.n);
1390
1391 /* ---- candidate estimates (convection-only: lambda_cX = lambda_X - lambda_t) ---- */
1392 PetscCall(EvalConvResidual(user, Ubase, Rhs, &map, Rref)); /* restore lUcat consistent w/ base */
1393 PetscCall(UpdateLocalGhosts(user, "Ucont"));
1394 PetscCall(Contra2Cart(user)); PetscCall(UpdateLocalGhosts(user, "Ucat"));
1395 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
1396 const PetscReal lcB = rep.lambda_B - rep.lambda_t;
1397 const PetscReal lcC = rep.lambda_C - rep.lambda_t;
1398 const PetscReal lcD = rep.lambda_D - rep.lambda_t;
1399 PetscReal gradmax = 0.0;
1400 PetscCall(ComputeMaxGradientContribution(user, &gradmax));
1401
1402 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1403 " --- convective Jacobian spectrum ---\n"
1404 " rho(J)=%.6e sigma_max(J)=%.6e nonnormality=%.3e max_real_eig=%.3e\n"
1405 " --- candidate convective estimates ---\n"
1406 " lambda_cB=%.6e lambda_cC=%.6e lambda_cD=%.6e\n"
1407 " max local |grad u| row-sum contribution = %.6e\n"
1408 " rB/rho=%.3f rC/rho=%.3f rD/rho=%.3f | rB/smax=%.3f rC/smax=%.3f rD/smax=%.3f\n",
1409 (double)rho, (double)smax, (double)eta, (double)maxre,
1410 (double)lcB, (double)lcC, (double)lcD,
1411 (double)gradmax,
1412 (double)(lcB/rho), (double)(lcC/rho), (double)(lcD/rho),
1413 (double)(lcB/smax), (double)(lcC/smax), (double)(lcD/smax)));
1414
1415 /* ---- RK pseudo-CFL stability per candidate (convective Jacobian) ---- */
1416 const PetscReal lams[3] = {lcB, lcC, lcD}; const char *cn[3] = {"B","C","D"};
1417 PetscReal *Jpseudo;
1418 PetscCall(PetscMalloc1((size_t)map.n*map.n, &Jpseudo));
1419 PetscCall(DenseShiftIdentity(Jbest, map.n, -rep.lambda_t, Jpseudo));
1420 const PetscReal lams_full[3] = {rep.lambda_B, rep.lambda_C, rep.lambda_D};
1421 PetscCall(PrintFrozenAmplificationTable("CONVECTION-ONLY FROZEN JACOBIAN", Jbest, map.n, lams, cn));
1422 PetscCall(PrintFrozenAmplificationTable("COMPLETE PSEUDO-TIME FROZEN OPERATOR", Jpseudo, map.n, lams_full, cn));
1423 PetscCall(PetscFree(Jpseudo));
1424
1425 PetscCall(PetscPrintf(PETSC_COMM_WORLD, " --- RK stable pseudo-CFL (convective J) ---\n"));
1426 for (int c = 0; c < 3; c++) {
1427 if (!(lams[c] > 0.0)) continue;
1428 StableCFLResult cfl_rho, cfl_nrm;
1429 PetscCall(StableCFL(Jbest, map.n, lams[c], METRIC_RHO, &cfl_rho));
1430 PetscCall(StableCFL(Jbest, map.n, lams[c], METRIC_NORM, &cfl_nrm));
1431 PetscCall(PrintStableCFLLine(cn[c], cfl_rho, cfl_nrm));
1432 }
1433
1434 /* ---- exact stage-dependent tangent map and direct nonlinear RK cross-check ---- */
1435 if (lcC > 0.0) PetscCall(RunRKTangentDiagnostics(user, st, Ubase, Rhs, &map,
1436 epsrel[best_e], lams, cn, Jbest));
1437
1438 /* ---- robust automated assertions per state ---- */
1439 PetscCall(PicurvAssertBool(eqbase, "base Ucont restored after Jacobian build"));
1440 PetscCall(PicurvAssertBool((PetscBool)(det_err < 1e-10), "residual evaluation deterministic"));
1441 PetscCall(PicurvAssertBool((PetscBool)(rho > 0.0 && PetscIsNormalReal(rho)), "finite rho(J)"));
1442 PetscCall(PicurvAssertBool((PetscBool)(smax > 0.0 && PetscIsNormalReal(smax)), "finite sigma_max(J)"));
1443 if (st == STATE_A) {
1444 for (int d = 0; d < 3; d++) {
1445 PetscCall(PicurvAssertRealNear(seam.declared[d], 0.0, 1e-12, "A: declared periodic seam"));
1446 PetscCall(PicurvAssertRealNear(seam.ucat_global[d], 0.0, 1e-12, "A: Ucat duplicate seam"));
1447 PetscCall(PicurvAssertRealNear(seam.ucat_ghost[d], 0.0, 1e-12, "A: lUcat ghost seam"));
1448 PetscCall(PicurvAssertRealNear(seam.ucont_global[d], 0.0, 1e-12, "A: Ucont duplicate seam"));
1449 }
1450 PetscCall(PicurvAssertRealNear(repeat_err, 0.0, 1e-9, "A: recovered Ucat repeat near roundoff"));
1451 PetscCall(PicurvAssertRealNear(R0_2, 0.0, 1e-12, "A: base residual 2-norm near roundoff"));
1452 PetscCall(PicurvAssertRealNear(R0_inf, 0.0, 1e-12, "A: base residual inf-norm near roundoff"));
1453 PetscCall(PicurvAssertRealNear(lcB, lcC, 1e-9, "A: B == C (no divergence)"));
1454 PetscCall(PicurvAssertRealNear(lcC, lcD, 1e-9, "A: C == D (no shear)"));
1455 } else if (st == STATE_B) {
1456 for (int d = 0; d < 3; d++) {
1457 PetscCall(PicurvAssertRealNear(seam.declared[d], 0.0, 1e-12, "B: declared periodic seam"));
1458 PetscCall(PicurvAssertRealNear(seam.ucat_global[d], 0.0, 1e-12, "B: Ucat duplicate seam"));
1459 PetscCall(PicurvAssertRealNear(seam.ucat_ghost[d], 0.0, 1e-12, "B: lUcat ghost seam"));
1460 PetscCall(PicurvAssertRealNear(seam.ucont_global[d], 0.0, 1e-12, "B: Ucont duplicate seam"));
1461 }
1462 PetscCall(PicurvAssertRealNear(repeat_err, 0.0, 1e-9, "B: recovered Ucat repeat near roundoff"));
1463 PetscCall(PicurvAssertBool((PetscBool)(maxdiv > 1e-3), "B: nonzero discrete divergence"));
1464 PetscCall(PicurvAssertBool((PetscBool)(R0_2 > 1e-3), "B: materially nonzero base residual 2-norm"));
1465 PetscCall(PicurvAssertBool((PetscBool)(R0_inf > 1e-3), "B: materially nonzero base residual inf-norm"));
1466 PetscCall(PicurvAssertBool((PetscBool)(lcC > lcB + 1e-9), "B: C > B"));
1467 } else {
1468 for (int d = 0; d < 3; d++) {
1469 PetscCall(PicurvAssertRealNear(seam.declared[d], 0.0, 1e-12, "C: declared periodic seam"));
1470 PetscCall(PicurvAssertRealNear(seam.ucat_global[d], 0.0, 1e-12, "C: Ucat duplicate seam"));
1471 PetscCall(PicurvAssertRealNear(seam.ucat_ghost[d], 0.0, 1e-12, "C: lUcat ghost seam"));
1472 PetscCall(PicurvAssertRealNear(seam.ucont_global[d], 0.0, 1e-12, "C: Ucont duplicate seam"));
1473 }
1474 PetscCall(PicurvAssertRealNear(repeat_err, 0.0, 1e-9, "C: recovered Ucat repeat near roundoff"));
1475 PetscCall(PicurvAssertBool((PetscBool)(maxdiv < 1e-6), "C: divergence near zero"));
1476 PetscCall(PicurvAssertRealNear(R0_2, 0.0, 1e-12, "C: base residual 2-norm near roundoff"));
1477 PetscCall(PicurvAssertRealNear(R0_inf, 0.0, 1e-12, "C: base residual inf-norm near roundoff"));
1478 PetscCall(PicurvAssertBool((PetscBool)(PetscAbsReal(lcC-lcB) < 1e-6), "C: C ~= B"));
1479 PetscCall(PicurvAssertBool((PetscBool)(gradmax > 1e-6), "C: canonical shear has nonzero local gradient contribution"));
1480 }
1481
1482 PetscCall(PetscFree3(Rref, Rrep, Jbest));
1483 PetscCall(VecDestroy(&Ubase)); PetscCall(VecDestroy(&Rhs));
1484 PetscCall(VecDestroy(&Uwork)); PetscCall(VecDestroy(&Uout));
1485 PetscCall(DofMapDestroy(&map));
1486 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1487 PetscFunctionReturn(0);
1488}
1489
1490/**
1491 * @brief Runs the State A candidate harness.
1492 */
1493static PetscErrorCode TestStateA(void) { return RunState(STATE_A, "A (uniform div-free)"); }
1494
1495/**
1496 * @brief Runs the State B candidate harness.
1497 */
1498static PetscErrorCode TestStateB(void) { return RunState(STATE_B, "B (nonzero divergence)"); }
1499
1500/**
1501 * @brief Runs the State C candidate harness.
1502 */
1503static PetscErrorCode TestStateC(void) { return RunState(STATE_C, "C (div-free shear)"); }
1504
1505/**
1506 * @brief Runs one State A grid-size audit case.
1507 */
1508static PetscErrorCode RunStateAGridAuditOne(PetscInt N)
1509{
1510 SimCtx *simCtx = NULL; UserCtx *user = NULL; DofMap map;
1511 Vec Ubase, Rhs, Uwork;
1512 PetscReal *Rp, *Rm, *J;
1513 PetscReal repeat_err, maxdiv;
1514 SeamDiagnostics seam;
1515 PetscFunctionBeginUser;
1516 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(&simCtx, &user, N, N, N, PETSC_TRUE, PETSC_TRUE, PETSC_TRUE));
1517 PetscCall(ConfigureCandidateFixture(simCtx, user));
1518 PetscCall(DofMapBuild(user, &map));
1519 PetscCall(VecDuplicate(user->Ucont, &Ubase));
1520 PetscCall(VecDuplicate(user->Ucont, &Rhs));
1521 PetscCall(VecDuplicate(user->Ucont, &Uwork));
1522 PetscCall(PetscMalloc3(map.n, &Rp, map.n, &Rm, (size_t)map.n*map.n, &J));
1523 PetscCall(BuildBaseState(user, STATE_A, Ubase, &repeat_err, &maxdiv, &seam));
1524 PetscCall(BuildFDJacobian(user, Ubase, 1e-5, Rhs, &map, Rp, Rm, Uwork, J));
1525 PetscReal rho, maxre, smax;
1526 PetscCall(DenseSpectralRadius(J, map.n, &rho, &maxre));
1527 PetscCall(DenseSigmaMax(J, map.n, &smax, NULL));
1528 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1529 " 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",
1530 (int)(N+1), (int)map.n, (int)map.expected_n, (double)maxre, (double)rho, (double)smax,
1531 (double)DenseSkewnessDefect(J, map.n), (double)DenseNonNormality(J, map.n), (double)repeat_err));
1532 PetscCall(PetscFree3(Rp, Rm, J));
1533 PetscCall(VecDestroy(&Ubase)); PetscCall(VecDestroy(&Rhs)); PetscCall(VecDestroy(&Uwork));
1534 PetscCall(DofMapDestroy(&map));
1535 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1536 PetscFunctionReturn(0);
1537}
1538
1539/**
1540 * @brief Runs the State A grid-dependence and active-space audit.
1541 */
1542static PetscErrorCode TestStateAGridAudit(void)
1543{
1544 PetscFunctionBeginUser;
1545 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1546 "\n================ STATE A GRID / ACTIVE-SPACE AUDIT ================\n"
1547 " residual sign convention: ComputeRHS returns the production convection residual used by pseudo-time updates.\n"
1548 " component-staggered periodic duplicate planes: 0<-m-2 and m-1<-1 in each direction.\n"
1549 " independent map: all three Ucont components use representatives i,j,k=1..m-2; count = 3*(m-2)^3.\n"));
1550 PetscCall(RunStateAGridAuditOne(4));
1551 PetscCall(RunStateAGridAuditOne(5));
1552 PetscFunctionReturn(0);
1553}
1554
1555/**
1556 * @brief Writes the one-rank State A matrix-free decomposition reference.
1557 */
1559 GlobalVecStats phi, const MomStabilityReport *rep)
1560{
1561 PetscMPIInt rank;
1562 PetscFunctionBeginUser;
1563 if (!g_ref_path_set) PetscFunctionReturn(0);
1564 PetscCheck(g_ref_token_set, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE,
1565 "-candidate_ref_token is required when -candidate_ref_path is set");
1566 PetscCallMPI(MPI_Comm_rank(PETSC_COMM_WORLD, &rank));
1567 if (rank == 0) {
1568 FILE *fp = fopen(g_ref_path, "w");
1569 PetscCheck(fp != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "could not write State A MPI reference");
1570 fprintf(fp, "PICURV_CANDIDATE_STATEA_REF_V2 %s\n", g_ref_token);
1571 fprintf(fp, "%.17e %.17e %.17e\n", (double)r0.n2, (double)r0.ninf, (double)r0.checksum);
1572 fprintf(fp, "%.17e %.17e %.17e\n", (double)jv.n2, (double)jv.ninf, (double)jv.checksum);
1573 fprintf(fp, "%.17e %.17e %.17e\n", (double)phi.n2, (double)phi.ninf, (double)phi.checksum);
1574 fprintf(fp, "%.17e %.17e %.17e %d %d %d %d\n",
1575 (double)(rep->lambda_B - rep->lambda_t),
1576 (double)(rep->lambda_C - rep->lambda_t),
1577 (double)(rep->lambda_D - rep->lambda_t),
1578 (int)rep->active_cells, (int)rep->cblock, (int)rep->ci, (int)rep->cj);
1579 fclose(fp);
1580 }
1581 PetscFunctionReturn(0);
1582}
1583
1584/**
1585 * @brief Compares a distributed State A matrix-free check against the one-rank reference.
1586 */
1588 GlobalVecStats phi, const MomStabilityReport *rep)
1589{
1590 PetscMPIInt rank;
1591 PetscReal vals[16] = {0.0};
1592 PetscFunctionBeginUser;
1593 PetscCheck(g_ref_path_set && g_ref_token_set, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE,
1594 "two-rank State A decomp check requires matching -candidate_ref_path and -candidate_ref_token from a preceding one-rank run");
1595 PetscCallMPI(MPI_Comm_rank(PETSC_COMM_WORLD, &rank));
1596 if (rank == 0) {
1597 char magic[64], token[128];
1598 FILE *fp = fopen(g_ref_path, "r");
1599 PetscCheck(fp != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN,
1600 "State A one-rank decomp reference missing; run the Makefile target so -n 1 precedes -n 2");
1601 PetscCheck(fscanf(fp, "%63s %127s", magic, token) == 2, PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "bad reference header");
1602 PetscCheck(strcmp(magic, "PICURV_CANDIDATE_STATEA_REF_V2") == 0, PETSC_COMM_SELF, PETSC_ERR_FILE_READ,
1603 "bad reference magic");
1604 PetscCheck(strcmp(token, g_ref_token) == 0, PETSC_COMM_SELF, PETSC_ERR_FILE_READ,
1605 "State A one-rank decomp reference token mismatch");
1606 int active, cblock, ci, cj;
1607 PetscCheck(fscanf(fp, "%le %le %le", &vals[0], &vals[1], &vals[2]) == 3, PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "bad reference R0");
1608 PetscCheck(fscanf(fp, "%le %le %le", &vals[3], &vals[4], &vals[5]) == 3, PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "bad reference Jv");
1609 PetscCheck(fscanf(fp, "%le %le %le", &vals[6], &vals[7], &vals[8]) == 3, PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "bad reference Phi");
1610 PetscCheck(fscanf(fp, "%le %le %le %d %d %d %d", &vals[9], &vals[10], &vals[11],
1611 &active, &cblock, &ci, &cj) == 7, PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "bad reference estimator");
1612 vals[12] = (PetscReal)active; vals[13] = (PetscReal)cblock; vals[14] = (PetscReal)ci; vals[15] = (PetscReal)cj;
1613 fclose(fp);
1614 PetscCheck(remove(g_ref_path) == 0, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN,
1615 "could not remove consumed State A MPI reference '%s'", g_ref_path);
1616 }
1617 PetscCallMPI(MPI_Bcast(vals, 16, MPIU_REAL, 0, PETSC_COMM_WORLD));
1618 PetscCall(PicurvAssertRealNear(r0.n2, vals[0], 1e-10, "MPI State A R0 L2"));
1619 PetscCall(PicurvAssertRealNear(r0.ninf, vals[1], 1e-10, "MPI State A R0 Linf"));
1620 PetscCall(PicurvAssertRealNear(r0.checksum, vals[2], 1e-10, "MPI State A R0 checksum"));
1621 PetscCall(PicurvAssertRealNear(jv.n2, vals[3], 1e-8, "MPI State A Jv L2"));
1622 PetscCall(PicurvAssertRealNear(jv.ninf, vals[4], 1e-8, "MPI State A Jv Linf"));
1623 PetscCall(PicurvAssertRealNear(jv.checksum, vals[5], 1e-8, "MPI State A Jv checksum"));
1624 PetscCall(PicurvAssertRealNear(phi.n2, vals[6], 1e-8, "MPI State A DPhi v L2"));
1625 PetscCall(PicurvAssertRealNear(phi.ninf, vals[7], 1e-8, "MPI State A DPhi v Linf"));
1626 PetscCall(PicurvAssertRealNear(phi.checksum, vals[8], 1e-8, "MPI State A DPhi v checksum"));
1627 PetscCall(PicurvAssertRealNear(rep->lambda_B - rep->lambda_t, vals[9], 1e-10, "MPI State A lambda_cB"));
1628 PetscCall(PicurvAssertRealNear(rep->lambda_C - rep->lambda_t, vals[10], 1e-10, "MPI State A lambda_cC"));
1629 PetscCall(PicurvAssertRealNear(rep->lambda_D - rep->lambda_t, vals[11], 1e-10, "MPI State A lambda_cD"));
1630 PetscCall(PicurvAssertBool((PetscBool)(rep->active_cells == (PetscInt)vals[12]), "MPI State A active cell count"));
1631 PetscCall(PicurvAssertBool((PetscBool)(rep->cblock == (PetscInt)vals[13]), "MPI State A controlling block"));
1632 PetscFunctionReturn(0);
1633}
1634
1635/**
1636 * @brief Runs State A matrix-free residual, Jv, and four-stage MPI decomposition checks.
1637 */
1638static PetscErrorCode StateADecompDirectionalCheck(UserCtx *user, Vec Ubase, Vec Rhs,
1639 PetscReal lcC, const MomStabilityReport *rep)
1640{
1641 DofMap map;
1642 Vec V, Up, Um, PhiP, PhiM, Ustage;
1643 PetscReal *v, *R0, *Rp, *Rm, *ActP, *ActM, *Out;
1644 GlobalVecStats sR0, sJv, sPhi;
1645 const PetscReal eps = 1e-6, dtau = 0.5/lcC;
1646 PetscMPIInt size;
1647 PetscFunctionBeginUser;
1648 PetscCallMPI(MPI_Comm_size(PETSC_COMM_WORLD, &size));
1649 PetscCall(DofMapBuildOwned(user, &map));
1650 PetscCall(VecDuplicate(Ubase, &V)); PetscCall(VecDuplicate(Ubase, &Up));
1651 PetscCall(VecDuplicate(Ubase, &Um)); PetscCall(VecDuplicate(Ubase, &PhiP));
1652 PetscCall(VecDuplicate(Ubase, &PhiM)); PetscCall(VecDuplicate(Ubase, &Ustage));
1653 PetscCall(PetscMalloc6(map.n, &v, map.n, &R0, map.n, &Rp, map.n, &Rm, map.n, &ActP, map.n, &ActM));
1654 PetscCall(PetscMalloc1(map.n, &Out));
1655 PetscCall(FillDeterministicDirection(user, V, &map, v));
1656
1657 PetscCall(EvalConvResidual(user, Ubase, Rhs, &map, R0));
1658 PetscCall(ActiveStats(&map, R0, &sR0));
1659 PetscCall(VecCopy(Ubase, Up)); PetscCall(VecAXPY(Up, eps, V));
1660 PetscCall(VecCopy(Ubase, Um)); PetscCall(VecAXPY(Um, -eps, V));
1661 PetscCall(EvalConvResidual(user, Up, Rhs, &map, Rp));
1662 PetscCall(EvalConvResidual(user, Um, Rhs, &map, Rm));
1663 for (PetscInt m = 0; m < map.n; m++) Out[m] = (Rp[m]-Rm[m])/(2.0*eps);
1664 PetscCall(ActiveStats(&map, Out, &sJv));
1665
1666 PetscCall(FourStage(user, Up, dtau, Rhs, &map, Rp, Ustage, PhiP));
1667 PetscCall(FourStage(user, Um, dtau, Rhs, &map, Rm, Ustage, PhiM));
1668 PetscCall(ExtractActiveVector(user, PhiP, &map, ActP));
1669 PetscCall(ExtractActiveVector(user, PhiM, &map, ActM));
1670 for (PetscInt m = 0; m < map.n; m++) Out[m] = (ActP[m]-ActM[m])/(2.0*eps);
1671 PetscCall(ActiveStats(&map, Out, &sPhi));
1672
1673 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1674 " State A matrix-free MPI check: R0 L2=%.6e Linf=%.6e checksum=%.6e\n"
1675 " Jv L2=%.6e Linf=%.6e checksum=%.6e\n"
1676 " DPhi v L2=%.6e Linf=%.6e checksum=%.6e\n",
1677 (double)sR0.n2, (double)sR0.ninf, (double)sR0.checksum,
1678 (double)sJv.n2, (double)sJv.ninf, (double)sJv.checksum,
1679 (double)sPhi.n2, (double)sPhi.ninf, (double)sPhi.checksum));
1680 if (size == 1) PetscCall(WriteStateADecompReference(sR0, sJv, sPhi, rep));
1681 else PetscCall(ReadAndCompareStateADecompReference(sR0, sJv, sPhi, rep));
1682
1683 PetscCall(PetscFree6(v, R0, Rp, Rm, ActP, ActM)); PetscCall(PetscFree(Out));
1684 PetscCall(VecDestroy(&V)); PetscCall(VecDestroy(&Up)); PetscCall(VecDestroy(&Um));
1685 PetscCall(VecDestroy(&PhiP)); PetscCall(VecDestroy(&PhiM)); PetscCall(VecDestroy(&Ustage));
1686 PetscCall(DofMapDestroy(&map));
1687 PetscFunctionReturn(0);
1688}
1689
1690/**
1691 * @brief Runs one decomp baseline and compares scalar estimates with regenerated references.
1692 */
1693static PetscErrorCode RunDecompBaseline(CandState st, const char *name,
1694 PetscReal expB, PetscReal expC, PetscReal expD)
1695{
1696 SimCtx *simCtx = NULL; UserCtx *user = NULL;
1697 Vec Ubase, Rhs;
1698 PetscReal repeat_err, maxdiv;
1699 SeamDiagnostics seam;
1701 const PetscInt N = 8;
1702 PetscFunctionBeginUser;
1703
1704 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(&simCtx, &user, N, N, N, PETSC_TRUE, PETSC_TRUE, PETSC_TRUE));
1705 PetscCall(ConfigureCandidateFixture(simCtx, user));
1706 PetscCall(VecDuplicate(user->Ucont, &Ubase));
1707 PetscCall(VecDuplicate(user->Ucont, &Rhs));
1708 PetscCall(BuildBaseState(user, st, Ubase, &repeat_err, &maxdiv, &seam));
1709 PetscCall(ComputeMomentumStabilityEstimate(user, 1, simCtx->dt, MOM_STAB_CAND_C, &rep));
1710
1711 const PetscReal lcB = rep.lambda_B - rep.lambda_t;
1712 const PetscReal lcC = rep.lambda_C - rep.lambda_t;
1713 const PetscReal lcD = rep.lambda_D - rep.lambda_t;
1714 PetscCall(PetscPrintf(PETSC_COMM_WORLD,
1715 "\n================ DECOMP BASELINE %s ================\n"
1716 " active cells: %d | repeat=%.3e | max|div_h Ucont|=%.3e\n"
1717 " lambda_cB=%.6e lambda_cC=%.6e lambda_cD=%.6e\n",
1718 name, (int)rep.active_cells, (double)repeat_err, (double)maxdiv,
1719 (double)lcB, (double)lcC, (double)lcD));
1720
1721 PetscCall(PicurvAssertBool((PetscBool)(rep.active_cells == 343), "decomp: active-cell count invariant"));
1722 PetscCall(PicurvAssertRealNear(expB, lcB, 5e-7, "decomp: lambda_cB matches one-rank baseline"));
1723 PetscCall(PicurvAssertRealNear(expC, lcC, 5e-7, "decomp: lambda_cC matches one-rank baseline"));
1724 PetscCall(PicurvAssertRealNear(expD, lcD, 5e-7, "decomp: lambda_cD matches one-rank baseline"));
1725 if (st == STATE_A) {
1726 PetscCall(StateADecompDirectionalCheck(user, Ubase, Rhs, lcC, &rep));
1727 PetscCall(PicurvAssertRealNear(maxdiv, 0.0, 1e-12, "decomp A: divergence-free"));
1728 PetscCall(PicurvAssertRealNear(lcB, lcC, 1e-12, "decomp A: B == C"));
1729 PetscCall(PicurvAssertRealNear(lcC, lcD, 1e-12, "decomp A: C == D"));
1730 } else if (st == STATE_B) {
1731 PetscCall(PicurvAssertBool((PetscBool)(maxdiv > 1e-3), "decomp B: nonzero divergence"));
1732 PetscCall(PicurvAssertBool((PetscBool)(lcC > lcB), "decomp B: C > B"));
1733 } else {
1734 PetscCall(PicurvAssertRealNear(maxdiv, 0.0, 1e-12, "decomp C: divergence-free"));
1735 PetscCall(PicurvAssertRealNear(lcB, lcC, 1e-12, "decomp C: B == C"));
1736 }
1737
1738 PetscCall(VecDestroy(&Ubase));
1739 PetscCall(VecDestroy(&Rhs));
1740 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1741 PetscFunctionReturn(0);
1742}
1743
1744/**
1745 * @brief Runs the State A decomposition baseline.
1746 */
1747static PetscErrorCode TestDecompA(void) { return RunDecompBaseline(STATE_A, "A (uniform div-free)", 1.1000000, 1.1000000, 1.1000000); }
1748
1749/**
1750 * @brief Runs the State B decomposition baseline.
1751 */
1752static PetscErrorCode TestDecompB(void) { return RunDecompBaseline(STATE_B, "B (nonzero divergence)", 0.878379697325, 0.974927912182, 1.572173303885); }
1753
1754/**
1755 * @brief Runs the State C decomposition baseline.
1756 */
1757static PetscErrorCode TestDecompC(void) { return RunDecompBaseline(STATE_C, "C (div-free shear)", 1.5000000, 1.5000000, 1.780330085890); }
1758
1759/**
1760 * @brief Reads optional paired-run MPI reference path and token.
1761 */
1762static PetscErrorCode ConfigureMPIReferenceOptions(void)
1763{
1764 PetscFunctionBeginUser;
1765 PetscCall(PetscOptionsGetString(NULL, NULL, "-candidate_ref_path",
1767 PetscCall(PetscOptionsGetString(NULL, NULL, "-candidate_ref_token",
1770 PetscCheck(g_ref_path_set && g_ref_token_set, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE,
1771 "-candidate_ref_path and -candidate_ref_token must be supplied together");
1772 }
1773 PetscFunctionReturn(0);
1774}
1775
1776/**
1777 * @brief PETSc entry point for the focused convective-candidate harness.
1778 */
1779int main(int argc, char **argv)
1780{
1781 PetscErrorCode ierr;
1782 PetscMPIInt size;
1783 const PicurvTestCase cases[] = {
1784 {"candidate-state-A-uniform", TestStateA},
1785 {"candidate-state-B-divergence", TestStateB},
1786 {"candidate-state-C-shear", TestStateC},
1787 {"candidate-state-A-grid-audit", TestStateAGridAudit},
1788 };
1789 const PicurvTestCase decomp_cases[] = {
1790 {"candidate-decomp-A-uniform", TestDecompA},
1791 {"candidate-decomp-B-divergence", TestDecompB},
1792 {"candidate-decomp-C-shear", TestDecompC},
1793 };
1794 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv A4a convective-candidate study");
1795 if (ierr) return (int)ierr;
1796 ierr = ConfigureMPIReferenceOptions(); if (ierr) { PetscFinalize(); return (int)ierr; }
1797 ierr = MPI_Comm_size(PETSC_COMM_WORLD, &size); if (ierr) { PetscFinalize(); return (int)ierr; }
1798 if (size == 1) {
1799 ierr = PicurvRunTests("unit-momentum-candidates", cases, sizeof(cases)/sizeof(cases[0]));
1800 if (!ierr) ierr = PicurvRunTests("unit-momentum-candidates-decomp", decomp_cases, sizeof(decomp_cases)/sizeof(decomp_cases[0]));
1801 } else {
1802 ierr = PicurvRunTests("unit-momentum-candidates-decomp", decomp_cases, sizeof(decomp_cases)/sizeof(decomp_cases[0]));
1803 }
1804 if (ierr) { PetscFinalize(); return (int)ierr; }
1805 return (int)PetscFinalize();
1806}
PetscErrorCode SynchronizePeriodicStaggeredFields(UserCtx *user, PetscInt num_fields, const char *field_names[])
Synchronizes persistent component-staggered vector fields.
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
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 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:2742
PetscErrorCode Cart2Contra(UserCtx *user)
Convert the ghosted Cartesian velocity field to contravariant face fluxes.
Definition setup.c:2877
PetscErrorCode UpdateLocalGhosts(UserCtx *user, const char *fieldName)
Updates the local vector (including ghost points) from its corresponding global vector.
Definition setup.c:1751
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 GetDof(UserCtx *user, Vec Ucont, const DofMap *map, PetscInt m, PetscReal *val)
Reads one active contravariant component from a global vector.
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 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 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 PetscErrorCode DofMapDestroy(DofMap *map)
Releases storage owned by an active-DOF map.
static PetscErrorCode TestDecompC(void)
Runs the State C decomposition baseline.
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 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 void ApplyP(const PetscReal *P, const PetscReal *x, PetscReal *out, PetscInt n)
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 PrintStableCFLLine(const char *candidate, StableCFLResult eig, StableCFLResult norm)
Prints one candidate's eigenvalue and norm stable-CFL statuses.
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 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 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 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)
#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 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 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 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 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 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 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:788
@ PERIODIC
Definition variables.h:290
PetscInt moveframe
Definition variables.h:714
PetscInt TwoD
Definition variables.h:714
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:894
PetscInt block_number
Definition variables.h:766
Vec lNvert
Definition variables.h:902
PetscInt rans
Definition variables.h:787
Vec lZet
Definition variables.h:925
PetscReal ren
Definition variables.h:731
PetscReal dt
Definition variables.h:698
PetscReal bulkVelocityCorrection
Definition variables.h:779
Vec Ucont
Definition variables.h:902
PetscInt StartStep
Definition variables.h:693
PetscScalar x
Definition variables.h:101
PetscInt invicid
Definition variables.h:714
Vec lNu_t
Definition variables.h:933
Vec lCsi
Definition variables.h:925
PetscScalar z
Definition variables.h:101
Vec Ucat
Definition variables.h:902
PetscInt central
Definition variables.h:729
Vec lUcont
Definition variables.h:902
PetscInt step
Definition variables.h:691
Vec lAj
Definition variables.h:925
DMDALocalInfo info
Definition variables.h:881
Vec lUcat
Definition variables.h:902
PetscScalar y
Definition variables.h:101
Vec lEta
Definition variables.h:925
PetscInt les
Definition variables.h:787
Vec Nvert
Definition variables.h:902
BCType mathematical_type
Definition variables.h:366
PetscInt rotateframe
Definition variables.h:714
A 3D point or vector with PetscScalar components.
Definition variables.h:100
The master context for the entire simulation.
Definition variables.h:683
User-defined context containing data specific to a single computational grid level.
Definition variables.h:874