2"""Long-march probe of the STALL hypothesis (2026-08-20): the collocated march quasi-freezes on a
3slow u-P manifold far from the true fixed point (measured: |uf - halfavg(u)| rms 4.5e-2 <u> at the
4stopping point, while the ONLY true fixed point of the map has phi = 0, uf == halfavg(u), and a
5dt-free steady system). If that is the plateau mechanism, marching far past the <u> criterion
6must show k creeping toward the staggered answer as the reconciliation gap decays (however
7slowly); if k and the gap are EXACTLY stationary, the implemented map has a non-closing loop that
8differs from the model and the discrepancy must be found in code.
10Runs the phi=0.60 bed, collocated gauge-exact, for STEPS steps regardless of any criterion,
11printing every EVERY steps: k (cell + flux estimators), m1 = rms|uf - halfavg(u)|/<|u|>,
12m2 = rms alpha-div(halfavg(u))/<|u|>, and the drift of each since the last print.
14 PECLET_FLOW_BUILD=build_ge BED=...npz N=192 STEPS=20000 EVERY=500 DT=60 python collocated_longmarch.py
21sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
"..",
"..",
"scripts"))
22from _bootstrap
import ensure_flow
25BED = os.environ[
"BED"]
26N = int(os.environ.get(
"N",
"192"))
27STEPS = int(os.environ.get(
"STEPS",
"20000"))
28EVERY = int(os.environ.get(
"EVERY",
"500"))
29DT = float(os.environ.get(
"DT",
"60.0"))
30KIND = os.environ.get(
"KIND",
"gauge-exact")
31ROT = int(os.environ.get(
"ROT",
"1"))
32ROTF = int(os.environ.get(
"ROTF",
"0"))
33ROTW = float(os.environ.get(
"ROTW",
"1"))
34WALLW = float(os.environ.get(
"WALLW",
"0"))
38DTSWITCH = dict((int(a), float(b))
for a, b
in
39 (kv.split(
":")
for kv
in os.environ.get(
"DTSWITCH",
"").split(
",")
if kv))
43SHIFT = np.array([float(v)
for v
in os.environ.get(
"SHIFT",
"0,0,0").split(
",")])
48 """Select a collocated scheme by the integer face-interp mode this script was written with:
49 0/7/9 are the public strings ('plain' / 'embed' / 'gauge-exact'), 5/6 the diagnostics rungs,
50 and every other number was deleted at 1.0.0 (the call then raises)."""
51 names = {0:
"plain", 7:
"embed", 9:
"gauge-exact"}
53 s.set_collocated_scheme(names[mode])
55 s.diagnostics.set_face_interp(mode)
59 box = np.asarray(pk[
"box"], float)
61 c = np.asarray(pk[
"centers"]) * Rc + SHIFT
62 r = np.asarray(pk[
"scales"]) * Rc
63 ax = np.arange(N) + 0.5
64 S = np.full((N, N, N), 1e30)
65 for sh
in np.stack(np.meshgrid(*[[-1., 0., 1.]] * 3, indexing=
"ij"), -1).reshape(-1, 3):
67 keep = np.all((cs + (r + 3)[:,
None] > 0) & (cs - (r + 3)[:,
None] < N), axis=1)
68 for (cx, cy, cz), rr
in zip(cs[keep], r[keep]):
69 i0, i1 = np.searchsorted(ax, [cx - rr - 3, cx + rr + 3])
70 j0, j1 = np.searchsorted(ax, [cy - rr - 3, cy + rr + 3])
71 k0, k1 = np.searchsorted(ax, [cz - rr - 3, cz + rr + 3])
72 if i0 >= i1
or j0 >= j1
or k0 >= k1:
74 d = np.sqrt((ax[i0:i1,
None,
None] - cx) ** 2 + (ax[
None, j0:j1,
None] - cy) ** 2
75 + (ax[
None,
None, k0:k1] - cz) ** 2) - rr
76 np.minimum(S[i0:i1, j0:j1, k0:k1], d, out=S[i0:i1, j0:j1, k0:k1])
77 return np.asfortranarray(np.clip(S, -1e3, 1e3)), Rc
81sv = flow.Solver
if KIND ==
"stag" else flow.SolverColocated
83s.set_rho(1.0); s.set_mu(MU); s.set_dt(DT)
84s.set_body_force((F0, 0, 0)); s.set_advection(
False)
85s.diagnostics.set_velocity_solver_params(int(os.environ.get(
"VIT",
"150")))
86if int(os.environ.get(
"APORDER",
"1")) != 1:
87 s.diagnostics.set_aperture_order(int(os.environ[
"APORDER"]))
88s.set_pressure_multigrid(
True, int(os.environ.get(
"MGL",
"0"))
or max(2, int(np.log2(N)) - 2))
89s.set_pressure_pcg(
True, 300, 1e-8)
94 s.diagnostics.set_ghost_projection(
True)
95 elif KIND ==
"fluidonly":
96 s.diagnostics.set_fluid_only_constraint(
"filter")
97 elif KIND.startswith(
"fluidonly2"):
98 s.diagnostics.set_fluid_only_constraint(
"star")
101 elif KIND.startswith(
"mode"):
103 elif hasattr(s,
"set_collocated_scheme"):
104 s.set_collocated_scheme(KIND)
106 s.set_collocated_scheme(KIND)
108 s.diagnostics.set_rotational_pressure(
False)
110 s.diagnostics.set_rotational_filter(
True)
112 s.diagnostics.set_rotational_weight(ROTW)
114 s.diagnostics.set_rotational_wall_weight(WALLW)
115s.set_solid(sdf, cutcell_pressure=
True)
118print(f
"# bed {os.path.basename(BED)} N={N} R={R:.1f} kind={KIND} rot={ROT} rotf={ROTF} rotw={ROTW} wallw={WALLW} dt={DT} steps={STEPS}", flush=
True)
119print(f
"{'step':>7} {'k_cell/R2':>13} {'k_face/R2':>13} {'m1_rms':>10} {'m2_rms':>10} "
120 f
"{'dk_cell':>10} {'dm1':>10}", flush=
True)
123for it
in range(1, STEPS + 1):
125 s.set_dt(DTSWITCH[it])
126 print(f
"# --- dt -> {DTSWITCH[it]} at step {it} ---", flush=
True)
128 if it % EVERY == 0
or it == 50:
129 U = [np.asarray(s.get_u()), np.asarray(s.get_v()), np.asarray(s.get_w())]
130 us = float(np.abs(U[0][fluid]).
mean()) + 1e-300
131 kc = float(U[0].
mean()) * MU / F0 / R ** 2
134 OX = [np.asarray(s.get_ox()), np.asarray(s.get_oy()), np.asarray(s.get_oz())]
135 kf = float((OX[0] * U[0]).
mean()) * MU / F0 / R ** 2
136 div = np.zeros_like(U[0])
139 div += np.roll(flx, -1, axis=a) - flx
140 m2 = float(np.sqrt((div[fluid] ** 2).
mean())) / us
142 UF = [np.asarray(s.get_uf()), np.asarray(s.get_vf()), np.asarray(s.get_wf())]
143 OX = [np.asarray(s.get_ox()), np.asarray(s.get_oy()), np.asarray(s.get_oz())]
144 kf = float((OX[0] * UF[0]).
mean()) * MU / F0 / R ** 2
146 div = np.zeros_like(U[0])
148 half = 0.5 * (U[a] + np.roll(U[a], 1, axis=a))
150 m1sq += float(((UF[a] - half)[op] ** 2).sum()); cnt += int(op.sum())
152 div += np.roll(flx, -1, axis=a) - flx
153 m1 = np.sqrt(m1sq / cnt) / us
154 m2 = float(np.sqrt((div[fluid] ** 2).
mean())) / us
155 dk =
"" if pk
is None else f
"{kc - pk:+.2e}"
156 dm =
"" if pm
is None else f
"{m1 - pm:+.2e}"
157 Pf = np.asarray(s.get_p())
158 dP =
"" if Pprev
is None else f
"{np.abs(Pf - Pprev).max():.3e}"
160 print(f
"{it:>7} {kc:>13.7e} {kf:>13.7e} {m1:>10.3e} {m2:>10.3e} {dk:>10} {dm:>10} "
161 f
"dP={dP:>10} |P|={np.abs(Pf).max():.3e}", flush=
True)
_collocated_mode(s, mode)
static double mean(const std::vector< double > &a)