flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
refresh_wall_gate.py
Go to the documentation of this file.
1"""Gate for flow's refresh_wall_velocity() (the cheap moving-BC entry point).
2
3The claim is exactness, not approximation: for a driver that changes an instance's VELOCITY every
4step while its TRANSFORM stays put, refresh_wall_velocity() must reproduce rebuild_geometry()
5BITWISE -- same velocity field, same pressure, same reaction force, after many steps of a
6time-varying wall velocity. Anything less and it is a different solver, not a faster path to the
7same one.
8
9Three claims:
10 A. BITWISE over a 60-step oscillating-wall run (u, v, w, P, and the per-instance reaction force).
11 B. The cost actually falls (that is the whole point).
12 C. Stopping the motion clears the wall-velocity fields. wallVelView() keys off the field's
13 EXTENT, not hasMotion_, so a previously-built uBc_ used to keep being folded into the momentum
14 operator after the caller set the velocity back to zero -- a real bug on the rebuild path too.
15 The check: run with a wall velocity, then set it to zero and step; the result must equal a run
16 that never moved at all.
17"""
18import os, time
19import numpy as np
20from peclet import flow as sdflow
21
22N, RF = 48, 0.18
23R = RF * N
24RHO, MU, U0 = 1.0, 0.1, 0.02
25KN_R, KI_I, KI_R = 16, 2, 17
26STEPS = int(os.environ.get("STEPS", "60"))
27OM = 0.01
28
29
30def make(dt, u_init):
31 ni = np.array([1, -1, -1], dtype=np.int32)
32 nr = np.zeros(KN_R); nr[0] = R; nr[14] = 1.0; nr[15] = 1.0
33 ii = np.zeros((1, KI_I), dtype=np.int32); ii[0] = (0, -1)
34 ir = np.zeros((1, KI_R)); ir[0, 0:3] = (0.5 * N,) * 3; ir[0, 6] = 1.0; ir[0, 7] = 1.0
35 s = sdflow.Solver(N, N, N)
36 s.set_rho(RHO); s.set_mu(MU); s.set_dt(dt); s.set_advection(False)
37 s.set_velocity_solver_params(60); s.set_pressure_solver_params(20)
38 s.set_pressure_multigrid(True, levels=4)
39 s.set_scene(ni, nr, ii.ravel(), ir.ravel(), periodic=True)
40 s.set_instance_motion(0, lin_vel=[u_init, 0.0, 0.0])
41 s.set_solid_from_scene(True)
42 return s
43
44
45def run(mode, dt=5.0, steps=STEPS, stop_after=None):
46 s = make(dt, U0)
47 t0 = time.time()
48 for it in range(steps):
49 t = (it + 1) * dt
50 u = 0.0 if (stop_after is not None and it >= stop_after) else U0 * np.cos(OM * t)
51 s.set_instance_motion(0, lin_vel=[u, 0.0, 0.0])
52 if mode == "rebuild":
53 s.rebuild_geometry()
54 elif mode == "refresh":
55 s.refresh_wall_velocity()
56 s.step()
57 wall = time.time() - t0
58 F = np.asarray(s.hydro_force_torque_reaction())[0][0]
59 return (np.asarray(s.get_u()), np.asarray(s.get_v()), np.asarray(s.get_w()),
60 np.asarray(s.get_p()), F, wall)
61
62
63def bits_equal(a, b):
64 return np.array_equal(a.view(np.int64), b.view(np.int64))
65
66
67print("A. refresh_wall_velocity vs rebuild_geometry, %d steps of a time-varying wall velocity"
68 % STEPS)
69rb = run("rebuild")
70rf = run("refresh")
71names = ["u", "v", "w", "P"]
72ok_a = True
73for k in range(4):
74 eq = bits_equal(rb[k], rf[k])
75 d = float(np.abs(rb[k] - rf[k]).max())
76 ok_a = ok_a and eq
77 print(" %-2s bitwise %-5s max|diff| %.3e" % (names[k], str(eq), d))
78# The FORCE is assembled by atomics over an unordered traversal, so it is documented as
79# tolerance-reproducible, not bitwise -- compare it as such, and only the fields bitwise.
80dF = float(np.abs(rb[4] - rf[4]).max()) / max(float(np.abs(rb[4]).max()), 1e-300)
81ok_a = ok_a and dF < 1e-12
82print(" reaction force relative |diff| %.2e (atomics: tolerance-reproducible, not bitwise)"
83 % dF)
84print(" rebuild %s\n refresh %s"
85 % (np.array2string(rb[4], precision=10), np.array2string(rf[4], precision=10)))
86print(" GATE A %s [the SOLUTION is bitwise; the force agrees to the atomics floor]"
87 % ("PASS" if ok_a else "FAIL"))
88
89print("B. cost -- the CALL itself, timed in isolation, and the whole step for context")
90
91
92def time_call(kind, reps=20, dt=5.0):
93 s = make(dt, U0)
94 s.step()
95 for _ in range(3): # warm up
96 s.set_instance_motion(0, lin_vel=[U0, 0.0, 0.0])
97 getattr(s, kind)()
98 t0 = time.time()
99 for i in range(reps):
100 s.set_instance_motion(0, lin_vel=[U0 * (1 + 1e-6 * i), 0.0, 0.0])
101 getattr(s, kind)()
102 return 1000 * (time.time() - t0) / reps
103
104
105t_rb = time_call("rebuild_geometry")
106t_rf = time_call("refresh_wall_velocity")
107t_step = (1000 * rb[5] / STEPS) - t_rb
108print(" rebuild_geometry %7.1f ms" % t_rb)
109print(" refresh_wall_velocity %7.1f ms -> the CALL is %.2fx cheaper" % (t_rf, t_rb / t_rf))
110print(" a bare step() for scale %7.1f ms -> the driver's per-step total falls "
111 "%.2fx (%.1f -> %.1f ms)"
112 % (t_step, (t_step + t_rb) / (t_step + t_rf), t_step + t_rb, t_step + t_rf))
113print(" (the refresh still rebuilds the momentum stencils, which is the bulk of a geometry "
114 "rebuild; what it skips is re-sampling the SDF, the apertures and the pressure operator)")
115ok_b = t_rf < t_rb
116print(" GATE B %s" % ("PASS" if ok_b else "FAIL"))
117
118def run_stop(refresh, dt=5.0, steps=STEPS):
119 """Build the scene WITH a wall velocity (so uBc_ is really built), then stop it."""
120 s = make(dt, U0)
121 s.set_instance_motion(0, lin_vel=[0.0, 0.0, 0.0])
122 if refresh:
123 s.refresh_wall_velocity()
124 for _ in range(steps):
125 s.step()
126 return (np.asarray(s.get_u()), np.asarray(s.get_v()), np.asarray(s.get_w()),
127 np.asarray(s.get_p()))
128
129
130def run_never(dt=5.0, steps=STEPS):
131 s = make(dt, 0.0) # never any motion at all
132 for _ in range(steps):
133 s.step()
134 return (np.asarray(s.get_u()), np.asarray(s.get_v()), np.asarray(s.get_w()),
135 np.asarray(s.get_p()))
136
137
138print("C. stopping the motion must clear the wall-velocity fields")
140fixed = run_stop(refresh=True)
141stale = run_stop(refresh=False) # exhibits the failure rather than asserting it
142ok_c = True
143for k in range(4):
144 eq = bits_equal(fixed[k], ref[k])
145 ok_c = ok_c and eq
146 print(" %-2s stopped+refresh vs never-moved bitwise %-5s max|diff| %.3e "
147 "| stopped WITHOUT refresh differs by %.3e"
148 % (names[k], str(eq), float(np.abs(fixed[k] - ref[k]).max()),
149 float(np.abs(stale[k] - ref[k]).max())))
150exhibits = float(np.abs(stale[0] - ref[0]).max()) > 1e-6
151ok_c = ok_c and exhibits
152print(" GATE C %s [zeroing the fields makes a stopped body identical to one that never moved; "
153 "without it the baked-in wall velocity survives, which the third column exhibits]"
154 % ("PASS" if ok_c else "FAIL"))
155print("REFRESH-WALL GATE %s" % ("PASS" if (ok_a and ok_b and ok_c) else "FAIL"))
run(mode, dt=5.0, steps=STEPS, stop_after=None)
run_stop(refresh, dt=5.0, steps=STEPS)
run_never(dt=5.0, steps=STEPS)
time_call(kind, reps=20, dt=5.0)