30def hydrostatic(ratio, mu=0.0, steps=100, g=0.1, dt=1.0, N=8, NZ=24):
31 s = F.Solver(N, N, NZ)
32 s.set_rho(1.0); s.set_mu(mu); s.set_dt(dt)
33 s.set_domain_bc(
"-z",
"wall", (0, 0, 0)); s.set_domain_bc(
"+z",
"wall", (0, 0, 0))
34 s.set_pressure_geometry(np.asfortranarray(np.full((N, N, NZ), 10.0)))
36 rz = np.where(z < NZ // 2, ratio, 1.0).astype(np.float64)
38 s.set_field(
"rho", np.asfortranarray(np.broadcast_to(rz[
None,
None, :], (N, N, NZ)).copy()))
39 s.diagnostics.set_density_mode(
"variable")
40 s.set_property_model(
"force_z",
"linear",
"rho", [0.0, -g])
42 for _
in range(steps):
44 m = max(np.abs(s.get_u()).max(), np.abs(s.get_v()).max(), np.abs(s.get_w()).max())
46 dp = p[N//2, N//2, 1:] - p[N//2, N//2, :-1]
47 rf = 0.5 * (rz[1:] + rz[:-1])
48 perr = np.max(np.abs(dp + g * rf)) / (g * ratio)
53 s = F.Solver(N, 4, NZ)
54 s.set_rho(1.0); s.set_mu(mu); s.set_dt(dt)
56 s.set_domain_bc(
"-z",
"wall", (0, 0, 0)); s.set_domain_bc(
"+z",
"wall", (0, 0, 0))
57 s.set_pressure_geometry(np.asfortranarray(np.full((N, 4, NZ), 10.0)))
58 s.add_scalar(
"c", diffusivity=0.0, scheme=
"koren", iters=1)
59 s.set_scalar_bc(
"c",
"-z",
"neumann", 0.0); s.set_scalar_bc(
"c",
"+z",
"neumann", 0.0)
60 s.set_property_model(
"rho",
"linear",
"c", [1.0, 2.0])
61 s.set_property_model(
"force_z",
"linear",
"rho", [0.0, -g])
62 x, z = np.arange(N), np.arange(NZ)
63 zi = NZ / 2 + 1.5 * np.cos(2 * np.pi * x / N)
64 c0 = np.zeros((N, 4, NZ))
66 c0[i, :, :] = 0.5 * (1.0 + np.tanh((z[
None, :] - zi[i]) / 1.5))
67 s.set_field(
"c", np.asfortranarray(c0))
70 c = s.get_field(
"c")[:, 1, :]
71 zc = np.array([np.interp(0.5, c[i, :], z)
for i
in range(N)])
72 return 0.5 * (zc.max() - zc.min())
75 for it
in range(steps):
83 """SHARP-interface Rayleigh-Taylor through the geometric VoF chain (rung V2a, WO-J).
85 Identical physics to `rayleigh_taylor()` above — same grid, same Atwood number (ratio 3),
86 same gravity, same viscosity, same initial interface z_i(x) = NZ/2 + 1.5 cos(2 pi x / N) —
87 with ONE difference: the phase field is a sharp geometric VoF colour field advected by
88 Weymouth-Yue instead of a tanh profile of width 1.5 advected by Koren TVD. Reporting both is
89 the point: the diffuse record is the pre-VoF baseline this rung has to reproduce.
91 Two deliberate departures from the diffuse driver, both forced by the method rather than
94 * dt is ADAPTIVE. Weymouth-Yue is bounded only for an interface-local Courant number below
95 1/(2(N-1)) = 0.25 in 3D, and an RT interface accelerates, so a fixed dt = 1 that is safe at
96 t = 0 is not safe at t = 240. dt is therefore chosen from `vof_max_courant()` — the
97 INTERFACE-LOCAL Courant number, which is the quantity the bound applies to (a global max
98 would throttle on the far-field return flow; V1 measured 2x over-throttling on Zalesak, and
99 the WO-J ctest measures 22x on a jet-plus-quiescent-interface scene). Amplitudes are
100 therefore reported against PHYSICAL TIME, sampled at the same t = 40, 80, ... 240 as the
103 * the amplitude comes from the COLUMN SUM of C, not from interpolating a C = 0.5 crossing.
104 For a sharp interface the column sum IS the interface height to machine precision
105 (z_i = NZ - sum_z C, heavy on top), while a 0.5-crossing interpolation of a two-cell-wide
106 profile carries an O(h) bias that the diffuse case cannot avoid and this one need not.
108 s = F.Solver(N, 4, NZ)
109 s.set_rho(1.0); s.set_mu(mu); s.set_dt(dt0)
110 s.set_advection(
True)
111 s.set_domain_bc(
"-z",
"wall", (0, 0, 0)); s.set_domain_bc(
"+z",
"wall", (0, 0, 0))
112 s.set_pressure_geometry(np.asfortranarray(np.full((N, 4, NZ), 10.0)))
114 x, z = np.arange(N), np.arange(NZ)
115 zi = NZ / 2 + 1.5 * np.cos(2 * np.pi * x / N)
117 c0 = np.clip((z[
None, :] + 1.0) - zi[:,
None], 0.0, 1.0)
118 s.set_vof(np.asfortranarray(np.repeat(c0[:,
None, :], 4, axis=1)))
119 s.set_property_model(
"rho",
"linear",
"C", [1.0, 2.0])
120 s.set_property_model(
"force_z",
"linear",
"rho", [0.0, -g])
123 h = s.get_vof()[:, 1, :].sum(axis=1)
125 return 0.5 * (zc.max() - zc.min())
127 v0 = s.get_vof().sum()
128 t, dt, sample, hist, tt = 0.0, dt0, 40.0, [amp()], [0.0]
129 while t < steps - 1e-9:
130 c = s.vof_max_courant()
132 dt = min(2.0 * dt, dt * cfl / c, dt0)
133 dt = min(dt, sample - t)
137 if t >= sample - 1e-9:
138 hist.append(amp()); tt.append(t); sample += 40.0
139 return hist, tt, (s.get_vof().sum() - v0) / v0