1"""Gate for flow's refresh_wall_velocity() (the cheap moving-BC entry point).
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
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.
20from peclet
import flow
as sdflow
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"))
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)
45def run(mode, dt=5.0, steps=STEPS, stop_after=None):
48 for it
in range(steps):
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])
54 elif mode ==
"refresh":
55 s.refresh_wall_velocity()
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)
64 return np.array_equal(a.view(np.int64), b.view(np.int64))
67print(
"A. refresh_wall_velocity vs rebuild_geometry, %d steps of a time-varying wall velocity"
71names = [
"u",
"v",
"w",
"P"]
75 d = float(np.abs(rb[k] - rf[k]).max())
77 print(
" %-2s bitwise %-5s max|diff| %.3e" % (names[k], str(eq), d))
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)"
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"))
89print(
"B. cost -- the CALL itself, timed in isolation, and the whole step for context")
96 s.set_instance_motion(0, lin_vel=[U0, 0.0, 0.0])
100 s.set_instance_motion(0, lin_vel=[U0 * (1 + 1e-6 * i), 0.0, 0.0])
102 return 1000 * (time.time() - t0) / reps
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)")
116print(
" GATE B %s" % (
"PASS" if ok_b
else "FAIL"))
119 """Build the scene WITH a wall velocity (so uBc_ is really built), then stop it."""
121 s.set_instance_motion(0, lin_vel=[0.0, 0.0, 0.0])
123 s.refresh_wall_velocity()
124 for _
in range(steps):
126 return (np.asarray(s.get_u()), np.asarray(s.get_v()), np.asarray(s.get_w()),
127 np.asarray(s.get_p()))
132 for _
in range(steps):
134 return (np.asarray(s.get_u()), np.asarray(s.get_v()), np.asarray(s.get_w()),
135 np.asarray(s.get_p()))
138print(
"C. stopping the motion must clear the wall-velocity fields")
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)