flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
galilean_re_gate.py
Go to the documentation of this file.
1"""Finite-Re Galilean gate: a sphere FIXED in a uniform stream vs the SAME sphere TOWED through
2fluid at rest -- same box, same Re, same dt, same impulsive start, same body force. In a periodic
3box a uniform translation is an exact symmetry of the Navier-Stokes equations, so the two force
4histories must agree to discretisation error at EVERY step. The fixed case never exercises the
5moving-geometry machinery (fresh cells, wall-velocity advection inputs, the time term at a moving
6cut wall, rebuild per step); the towed case exercises all of it. Any gap is a moving-geometry
7defect, isolated from resolution and from confinement.
8
9Usage: PYTHONPATH=<flow build> python galilean_re_gate.py [DH=8] [Re=1.5] [N=96] [STEPS=800]
10Env: GAL_ADV=0 turns advection off (the Stokes limit, where the old gate lives).
11"""
12import os
13import sys
14import numpy as np
15from peclet import flow as sdflow
16from peclet import geom
17
18DH = float(sys.argv[1]) if len(sys.argv) > 1 else 8.0
19RE = float(sys.argv[2]) if len(sys.argv) > 2 else 1.5
20N = int(sys.argv[3]) if len(sys.argv) > 3 else 96
21STEPS = int(sys.argv[4]) if len(sys.argv) > 4 else 800
22ADV = os.environ.get("GAL_ADV", "1") == "1"
23U = 0.02 # stream / tow speed (cells per time unit)
24NU = U * DH / RE
25DT = 3.2
26KI_I, KI_R = 2, 17
27OFF = 0.3
28
29
30def abraham_cd(re):
31 return 24.0 / 9.06 ** 2 * (9.06 / np.sqrt(re) + 1.0) ** 2
32
33
34def make(y0):
35 b = geom.SceneBuilder()
36 sph = b.add_leaf("sphere", [DH / 2])
37 ni, nr, _, _ = b.encode()
38 ii = np.zeros((1, KI_I), dtype=np.int32); ir = np.zeros((1, KI_R))
39 x0 = 0.5 * N + OFF
40 ii[0] = (sph, -1); ir[0, 0:3] = (x0, y0, x0); ir[0, 6] = 1.0; ir[0, 7] = 1.0
41 s = sdflow.Solver(N, N, N)
42 s.set_rho(1.0); s.set_mu(NU); s.set_dt(DT); s.set_advection(ADV)
43 s.set_velocity_solver_params(60); s.set_pressure_solver_params(20)
44 s.set_pressure_multigrid(True, levels=4)
45 s.set_scene(np.asarray(ni, np.int32), np.asarray(nr, float), ii.ravel(), ir.ravel(),
46 periodic=True)
47 s.set_solid_from_scene(True)
48 return s, x0
49
50
51# Body force that keeps the box mean flow steady: the Abraham drag at U, per unit volume. The
52# same value in BOTH runs (a body force is Galilean-invariant); its residual mismatch drifts the
53# mean by (F - B V) t / (rho V) -- measured and reported, not assumed.
54F_EST = abraham_cd(RE) * 0.5 * U ** 2 * np.pi * (DH / 2) ** 2
55B = F_EST / N ** 3
56
57
58def run(moving):
59 y0 = 0.72 * N if moving else 0.5 * N + OFF
60 s, x0 = make(y0)
61 s.set_body_force(0.0, B, 0.0)
62 if not moving:
63 v0 = np.full((N, N, N), U, dtype=np.float64, order="F")
64 s.set_velocity(1, v0)
65 y = y0; Fh = []; vm = []
66 for k in range(STEPS):
67 if moving:
68 s.set_instance_transform(0, [x0, y, x0])
69 s.set_instance_motion(0, lin_vel=[0.0, -U, 0.0])
70 s.rebuild_geometry()
71 s.step()
72 F = np.asarray(s.hydro_force_torque_reaction())[0][0].astype(float)
73 Fh.append(F[1])
74 if k % 50 == 49 or k == STEPS - 1:
75 vmean = float(np.asarray(s.get_v()).mean())
76 vm.append((k + 1, vmean))
77 if moving:
78 y -= U * DT
79 bud = np.asarray(s.reaction_budget_terms()).astype(float)
80 return np.array(Fh), vm, bud
81
82
83print("Finite-Re Galilean gate: DH=%g Re=%g N=%d dt=%g steps=%d adv=%s (B=%.3e, F_est=%.4e)"
84 % (DH, RE, N, DT, STEPS, ADV, B, F_EST))
85FS, vS, bS = run(False)
86FM, vM, bM = run(True)
87# Mean fluid velocity: fixed case should hold ~U, towed case ~0 (both drift by the same B mismatch).
88print(" mean v (fixed) : " + " ".join("%d:%.5f" % t for t in vS[::4]))
89print(" mean v (towed) : " + " ".join("%d:%.5f" % t for t in vM[::4]))
90n4 = STEPS * 3 // 4
91fS, fM = FS[n4:].mean(), FM[n4:].mean()
92uS = vS[-1][1] / (1.0 - np.pi / 6 * DH ** 3 / N ** 3) # fluid-mean (solid rows are 0)
93uM = U + vM[-1][1] / (1.0 - np.pi / 6 * DH ** 3 / N ** 3) # relative speed in the towed case
94print(" quasi-steady F_y (last quarter): fixed %.6e towed %.6e towed/fixed = %.4f"
95 % (fS, fM, fM / fS))
96print(" step-by-step |F_M - F_S| / |F_S|: " + " ".join(
97 "k=%d:%.3f" % (k, abs(FM[k] - FS[k]) / abs(FS[k])) for k in (10, 50, 100, 200, 400, STEPS - 1)))
98for tag, f, u in (("fixed", fS, uS), ("towed", fM, uM)):
99 re = u * DH / NU
100 cd = f / (0.5 * u ** 2 * np.pi * (DH / 2) ** 2)
101 print(" %s: u_rel=%.5f Re=%.3f Cd=%.4f Abraham(Re)=%.4f ratio=%.4f"
102 % (tag, u, re, cd, abraham_cd(re), cd / abraham_cd(re)))
103print(" budget [unsteady_y, advective_y]: fixed [%.3e, %.3e] towed [%.3e, %.3e]"
104 % (bS[0][1], bS[1][1], bM[0][1], bM[1][1]))
105gap = abs(fM / fS - 1.0)
106print("GALILEAN-RE GATE %s [towed/fixed quasi-steady drag within 2%%: %.2f%%]"
107 % ("PASS" if gap < 0.02 else "FAIL", 100 * gap))
static double mean(const std::vector< double > &a)