flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
moving_step_bench.py
Go to the documentation of this file.
1"""Layer 3 rung 4: the MOVING-STEP DRIVER -- cost, and a march that actually moves.
2
3Two things, both measured rather than modelled:
4
5 1. COST. ms/step for a static step vs a moving step (step + rebuild_geometry), at 64^3 and
6 128^3. v1 rebuilds the whole geometry every step by design: the Layer-3 input measurement is
7 that a 128^3 rebuild is ~65% momentum/IBM stencils and ~35% pressure/MG with scene sampling in
8 the noise, so an incremental path has to attack BOTH sides and is deferred.
9
10 2. THAT IT MARCHES. A sphere translating through initially quiescent fluid: the body displaces by
11 the distance its velocity implies, the fluid it drags acquires momentum in the direction of
12 travel, and the projection keeps max|div| at solver level throughout -- i.e. the rebuild
13 preserves the flow field instead of resetting it (set_solid zeroes u/phi/P by design).
14"""
15import os
16import sys
17import time
18import numpy as np
19from peclet import flow as sdflow
20
21KN_R, KI_I, KI_R = 16, 2, 17
22
23
24def make_scene(cen, R):
25 node_ints = np.array([1, -1, -1], dtype=np.int32)
26 node_reals = np.zeros(KN_R); node_reals[0] = R
27 node_reals[14] = 1.0; node_reals[15] = 1.0
28 ii = np.zeros((len(cen), KI_I), dtype=np.int32)
29 ir = np.zeros((len(cen), KI_R))
30 for m, c in enumerate(cen):
31 ii[m] = (0, -1)
32 ir[m, 0:3] = c
33 ir[m, 6] = 1.0
34 ir[m, 7] = 1.0
35 return node_ints, node_reals, ii.ravel(), ir.ravel()
36
37
38def build(N, moving, V):
39 R = 0.18 * N
40 cen = [(0.25*N, 0.25*N, 0.25*N), (0.75*N, 0.75*N, 0.25*N),
41 (0.75*N, 0.25*N, 0.75*N), (0.25*N, 0.75*N, 0.75*N)]
42 s = sdflow.Solver(N, N, N)
43 s.set_rho(1.0); s.set_mu(0.1); s.set_dt(0.5)
44 s.set_advection(False)
45 # A BODY FORCE IS NOT OPTIONAL HERE. Without it the static case has u == 0 forever, so the
46 # pressure PCG converges on a zero right-hand side and the "static step" being compared against
47 # is solving nothing -- the first version of this bench reported the moving path as 2.8x a
48 # static step purely from that.
49 s.set_body_force(1e-3, 0.0, 0.0)
50 s.set_velocity_solver_params(40)
51 s.set_pressure_solver_params(15)
52 # MG depth 4, not 1. levels=1 leaves the 'coarse' solve on the FULL grid, which on
53 # CUDA costs 920 ms/step at N=64 against 19.5 ms at depth 4 (measured). Depth changes the
54 # solver path, not the converged answer, and both runs of a comparison use the same depth.
55 s.set_pressure_multigrid(True, levels=4)
56 s.set_scene(*make_scene(cen, R), periodic=True)
57 if moving:
58 for i in range(len(cen)):
59 s.set_instance_motion(i, lin_vel=[V, 0.0, 0.0])
60 s.set_solid_from_scene(True)
61 return s, cen, R
62
63
64def bench(N, nstep=12):
65 V = 0.05
66 out = {}
67 for label, moving, rebuild in (("static", False, False),
68 ("moving (no rebuild)", True, False),
69 ("moving + rebuild", True, True)):
70 s, cen, R = build(N, moving, V)
71 for _ in range(25): # develop the flow AND warm up: a zero field would make the
72 s.step() # pressure solve trivial and the comparison meaningless
73 t0 = time.perf_counter()
74 for k in range(nstep):
75 s.step()
76 if rebuild:
77 for i in range(len(cen)):
78 c = cen[i]
79 s.set_instance_transform(i, [c[0] + V * 0.5 * (k + 1), c[1], c[2]])
80 s.rebuild_geometry()
81 dt = (time.perf_counter() - t0) / nstep * 1e3
82 out[label] = dt
83 print(" N=%3d %-20s %8.1f ms/step" % (N, label, dt))
84 print(" N=%3d rebuild overhead %8.1f ms/step (%.1fx a static step)"
85 % (N, out["moving + rebuild"] - out["moving (no rebuild)"],
86 out["moving + rebuild"] / out["static"]))
87 return out
88
89
90def march(N=48, nstep=40):
91 """A translating sphere in quiescent fluid: it must move, drag fluid, and stay divergence free."""
92 R = 0.16 * N
93 V = 0.08
94 cen = [(0.25 * N, 0.5 * N, 0.5 * N)]
95 s = sdflow.Solver(N, N, N)
96 s.set_rho(1.0); s.set_mu(0.1); s.set_dt(0.5)
97 s.set_advection(False)
98 s.set_velocity_solver_params(60)
99 s.set_pressure_solver_params(20)
100 # MG depth 4, not 1. levels=1 leaves the 'coarse' solve on the FULL grid, which on
101 # CUDA costs 920 ms/step at N=64 against 19.5 ms at depth 4 (measured). Depth changes the
102 # solver path, not the converged answer, and both runs of a comparison use the same depth.
103 s.set_pressure_multigrid(True, levels=4)
104 s.set_scene(*make_scene(cen, R), periodic=True)
105 s.set_instance_motion(0, lin_vel=[V, 0.0, 0.0])
106 s.set_solid_from_scene(True)
107 x = cen[0][0]
108 worstdiv = 0.0
109 for k in range(nstep):
110 s.step()
111 worstdiv = max(worstdiv, s.max_open_divergence())
112 x += V * 0.5
113 s.set_instance_transform(0, [x, cen[0][1], cen[0][2]])
114 s.rebuild_geometry()
115 u = np.asarray(s.get_u())
116 # fluid mask at the u-points of the FINAL sphere position
117 g = np.arange(N).astype(float)
118 X, Y, Z = np.meshgrid(g - 0.5, g, g, indexing="ij")
119 dx = np.abs(X - x); dx = np.minimum(dx, N - dx)
120 dy = np.abs(Y - cen[0][1]); dy = np.minimum(dy, N - dy)
121 dz = np.abs(Z - cen[0][2]); dz = np.minimum(dz, N - dz)
122 sd = np.sqrt(dx*dx + dy*dy + dz*dz) - R
123 fl = sd > 0.0
124 near = fl & (sd < 0.6 * R)
125 print(" march: %d steps, sphere x %.2f -> %.2f (moved %.2f cells, expected %.2f)"
126 % (nstep, cen[0][0], x, x - cen[0][0], V * 0.5 * nstep))
127 print(" max|div| over the march %.3e" % worstdiv)
128 print(" mean u in the near wake shell %+.4e (wall speed %+.4f) -> fluid is dragged "
129 "ALONG: %s" % (float(u[near].mean()), V, float(u[near].mean()) > 0.05 * V))
130 # A kinematically driven body has infinite mass, so it INJECTS momentum: a nonzero fluid mean
131 # is the physics, not an error. What it demonstrates is that the field survives the rebuilds --
132 # a reset-every-step driver would show ~one step's worth here, not 40 steps' accumulation.
133 print(" mean u over all fluid %+.4e (momentum injected by the driven body, "
134 "accumulated across all %d rebuilds)" % (float(u[fl].mean()), nstep))
135
136
137if __name__ == "__main__":
138 what = sys.argv[1] if len(sys.argv) > 1 else "all"
139 if what in ("all", "march"):
140 march()
141 if what in ("all", "bench"):
142 for N in (64, 128):
143 bench(N)
march(N=48, nstep=40)
static double mean(const std::vector< double > &a)