29L, RHO, MU, F, PHI = 1.0, 1.0, 1.0, 1.0, 0.125
39 X, Y, Z = np.meshgrid(cx, cy, cz, indexing=
"ij")
40 c = 0.5 * L + shift_h * h
41 return np.sqrt((X - c) ** 2 + (Y - c) ** 2 + (Z - c) ** 2) -
radius()
77 c = (np.arange(N) + 0.5) * h
78 s =
sdf_on(c, c, c, 0.0, h) / h
81 a, b = s, np.roll(s, -1, axis=ax)
82 m1, m2 = (a > 0) & (b < 0), (a < 0) & (b > 0)
83 th += [a[m1] / (a[m1] - b[m1]), b[m2] / (b[m2] - a[m2])]
84 th = np.concatenate(th)
85 print(f
"N={N:3d} R/h={R / h:7.4f} min fluid sdf/h={s[s > 0].min():.4f} "
86 f
"#fluid<0.05h={np.sum((s > 0) & (s < 0.05)):3d} min theta={th.min():.4f} "
87 f
"#theta<0.05={np.sum(th < 0.05)}")
90def run(tag, N, beta, solver, scheme, steps, shift=0.0, opts=""):
91 sys.path.insert(0, os.path.join(HERE,
"..",
"..",
"scripts"))
92 from _bootstrap
import ensure_flow
94 opts = set(o
for o
in opts.split(
",")
if o)
95 s = (flow.Solver
if solver ==
"stag" else flow.SolverColocated)((N, N, N), extent=(L, L, L))
97 s.set_rho(RHO); s.set_mu(MU); s.set_dt(beta * RHO * h * h / MU)
98 s.set_body_force((F, 0.0, 0.0)); s.set_advection(
False)
99 s.diagnostics.set_velocity_solver_params(200)
100 s.set_pressure_multigrid(
True, levels=max(2, int(np.log2(N)) - 1))
101 s.set_pressure_pcg(
True, max_iter=200, rtol=1e-12
if "prtol12" in opts
else 1e-8)
102 if solver ==
"col" and scheme !=
"auto":
103 s.set_collocated_scheme(scheme)
105 s.diagnostics.set_rotational_pressure(
False)
106 cx, cy, cz = s.cell_centers()
107 sdf =
sdf_on(cx, cy, cz, shift, h)
108 s.set_solid(np.asfortranarray(sdf), cutcell_pressure=
True)
110 m, du, dp = np.zeros(steps), np.zeros(steps), np.zeros(steps)
111 pit, mres = np.zeros(steps, int), np.zeros(steps)
112 keep, prevU, prevP, fluid = {},
None,
None, sdf > 0
113 for it
in range(steps):
115 U = np.stack([s.get_u(), s.get_v(), s.get_w()])
117 m[it] = float(U[0].
mean())
118 pit[it] = s.diagnostics.last_pressure_iterations()
119 mres[it] = s.diagnostics.last_momentum_residual()
120 if prevU
is not None:
121 du[it] = np.sqrt(np.mean((U - prevU) ** 2))
122 d = (P - prevP)[fluid]
123 dp[it] = np.sqrt(np.mean((d - d.mean()) ** 2))
126 keep.pop(it - 4,
None)
128 os.makedirs(OUT, exist_ok=
True)
129 np.savez_compressed(os.path.join(OUT, tag +
".npz"), m=m, du=du, dp=dp, pit=pit, mres=mres,
130 sdf=sdf / h, U=np.stack([keep[i][0]
for i
in its]),
131 P=np.stack([keep[i][1]
for i
in its]), N=N, beta=beta)
132 print(f
"[{tag}] N={N} beta={beta} {solver}/{scheme} shift={shift} opts={sorted(opts)} "
133 f
"old_stop={old_stop(m)} vmg={bool(s.diagnostics.velocity_multigrid_active())} "
134 f
"p-iters(last)={pit[-1]} mom-res(last)={mres[-1]:.2e} m_final={m[-1]:.10e}", flush=
True)
138 """(per-step rate, window, back-projected amplitude, geometric-extrapolated limit)."""
139 rel = np.abs(np.diff(m)) / abs(m[-1])
140 ok = np.where(rel > floor)[0]
141 hi = ok[-1] + 1
if len(ok)
else len(m) - 1
143 r = float(np.exp((np.log(rel[hi - 1]) - np.log(rel[lo])) / (hi - 1 - lo)))
144 return r, (lo, hi), rel[lo] / r ** lo, m[hi] + (m[hi] - m[hi - 1]) * r / (1.0 - r)
148 z = np.load(os.path.join(OUT, tag +
".npz"))
149 m, du, dp, beta = z[
"m"], z[
"du"], z[
"dp"], float(z[
"beta"])
150 r, (lo, hi), A0, m_inf =
tail(m)
151 print(f
"[{tag}] N={int(z['N'])} beta={beta} tail rate {r:.5f}/step over [{lo},{hi}) "
152 f
"(as a backward-Euler mode lambda L^2 = {(1 / r - 1) / beta * int(z['N']) ** 2:.2f}); "
153 f
"|dm|/m amplitude at step 0 {A0:.2e}; m_inf = {m_inf:.10e}")
154 rel = np.abs(np.diff(m)) / abs(m[-1])
155 print(
" step |dm|/|m| rms du rms dP(fluid)")
156 for k
in [0, 4, 9, 19, 29, 49, 69, 99, 149, 199, 299, 399, 599, 799, 1199]:
158 print(f
" {k + 1:4d} {rel[k]:.3e} {du[k + 1]:.3e} {dp[k + 1]:.3e}")
162 """Where the last step's increment lives: wall band and Fourier content."""
163 z = np.load(os.path.join(OUT, tag +
".npz"))
164 U, P, sdf = z[
"U"], z[
"P"], z[
"sdf"]
165 N, fluid = sdf.shape[0], sdf > 0
168 dP = np.where(fluid, dP - dP[fluid].
mean(), 0.0)
169 e = (d ** 2).sum(axis=0)
170 print(f
" [{tag}] last-step increment:")
171 for lo, hi, name
in [(-1e9, 0,
"solid"), (0, 1,
"0<sdf<h"), (1, 2,
"h<sdf<2h"),
173 msk = (sdf > lo) & (sdf <= hi)
174 print(f
" {name:9s} {100 * e[msk].sum() / e.sum():5.1f}% of |du|^2, "
175 f
"{100 * (dP[msk] ** 2).sum() / (dP ** 2).sum():5.1f}% of |dP|^2 "
176 f
"({100 * msk.mean():4.1f}% of cells)")
178 for name, f
in ((
"du_x", d[0]), (
"dP", dP), (
"P", np.where(fluid, P[-1] - P[-1][fluid].
mean(), 0))):
179 E = np.abs(np.fft.fftn(f)) ** 2
181 cb = {
"(pi,0,0)": E[h2, 0, 0],
"(0,pi,0)": E[0, h2, 0],
"(0,0,pi)": E[0, 0, h2],
182 "(pi,pi,pi)": E[h2, h2, h2]}
183 print(f
" {name:5s} energy fraction " +
" ".join(f
"{k} {100 * v:5.1f}%" for k, v
in cb.items()))
200 run(a[0], int(a[1]), float(a[2]), a[3], a[4], int(a[5]),