flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
state_hash.py
Go to the documentation of this file.
1#!/usr/bin/env python
2"""Byte gate for the peclet.flow public surface: SHA-256 of the final state of one fixed-seed run
3per public entry path, at one thread.
4
5Run: OMP_NUM_THREADS=1 OMP_PROC_BIND=false PYTHONPATH=<build> python tests/regression/state_hash.py
6 OMP_NUM_THREADS=1 PYTHONPATH=<build> mpirun -np 2 python tests/regression/state_hash.py mpi
7
8Entry paths: the staggered periodic sphere bed (set_solid, cut-cell pressure), the collocated
9solver under each of its four schemes ('ghost', 'gauge-exact', 'plain', 'embed'), the collocated
10ADVECTING paths -- periodic (explicit SOU/Koren) and inflow/outflow (implicit FOU + deferred
11correction), the two branches that read the projected face field as the advecting velocity and the
12only ones the four scheme cases above miss (they are Stokes: set_advection(False)) -- an
13inflow/wall/outflow channel with advection, a VoF droplet under surface tension, Boussinesq
14scalar transport, the porous (volume-averaged) continuity with implicit drag, an analytic scene
15with a moving instance (set_instance_motion + set_instance_transform + rebuild_geometry), and the
16distributed step at np = 2 (flow.mpi_block + init_mpi). Each prints one line: the case, the
17SHA-256 over the per-field hashes, and the first 12 hex digits of each field's hash (get_u/v/w/p,
18plus get_vof and the scalar where present), taken over the contiguous float64 bytes.
19
20Every run here is reproducible run-to-run at one thread (verified 2026-09-10 on host-openmp); a
21refactor of the bindings or the solver that is not meant to change numerics must reproduce every
22line bitwise (QUALITY_PLAN F gate). The hashes recorded at each package-F milestone are in the
23commit messages.
24"""
25import hashlib
26import sys
27
28import numpy as np
29
30import peclet.flow as pf
31
32CASES = ("staggered_bed", "colocated_ghost", "colocated_gauge_exact", "colocated_plain",
33 "colocated_embed", "colocated_advect", "colocated_advect_bc", "channel", "vof_droplet",
34 "scalar", "porous", "scene_moving")
35
36
37def sha(a):
38 return hashlib.sha256(np.ascontiguousarray(np.asarray(a), dtype=np.float64).tobytes()).hexdigest()
39
40
41def packing_sdf(N, rfrac=0.18):
42 R = rfrac * N
43 cs = np.array([0.25 * N, 0.75 * N])
44 xs = np.arange(N) + 0.5
45 X, Y, Z = np.meshgrid(xs, xs, xs, indexing="ij")
46 best = np.full((N, N, N), 1e30)
47 for sx in cs:
48 for sy in cs:
49 for sz in cs:
50 dx = X - sx; dx -= N * np.round(dx / N)
51 dy = Y - sy; dy -= N * np.round(dy / N)
52 dz = Z - sz; dz -= N * np.round(dz / N)
53 best = np.minimum(best, np.sqrt(dx * dx + dy * dy + dz * dz) - R)
54 return np.asfortranarray(best)
55
56
57def sphere_fractions(shape, R, c, sub=4):
58 n = np.array(shape)
59 off = (np.arange(sub) + 0.5) / sub
60 out = np.zeros(shape)
61 for ox in off:
62 for oy in off:
63 for oz in off:
64 X, Y, Z = np.meshgrid(np.arange(n[0]) + ox, np.arange(n[1]) + oy,
65 np.arange(n[2]) + oz, indexing="ij")
66 out += ((X - c[0]) ** 2 + (Y - c[1]) ** 2 + (Z - c[2]) ** 2 <= R * R)
67 return np.asfortranarray(out / sub ** 3)
68
69
70def stokes_bed(cls, N=16, steps=5, scheme=None):
71 s = cls(N, N, N)
72 s.set_rho(1.0); s.set_mu(0.1); s.set_dt(60.0); s.set_body_force((1e-3, 0.0, 0.0))
73 s.set_advection(False)
74 s.diagnostics.set_velocity_solver_params(80)
75 s.set_pressure_multigrid(True, 3)
76 s.set_pressure_pcg(True, 200, 1e-9)
77 if scheme is not None:
78 s.set_collocated_scheme(scheme)
79 s.set_solid(packing_sdf(N), cutcell_pressure=True)
80 for _ in range(steps):
81 s.step()
82 return s
83
84
86 return stokes_bed(pf.Solver)
87
88
90 return stokes_bed(pf.SolverColocated, scheme="ghost")
91
92
94 return stokes_bed(pf.SolverColocated, scheme="gauge-exact")
95
96
98 return stokes_bed(pf.SolverColocated, scheme="plain")
99
100
102 return stokes_bed(pf.SolverColocated, scheme="embed")
103
104
105def _tg_state(N, nz, U0=1.0):
106 """A divergence-free (to O(h^2)) cell-centred Taylor-Green pair: a non-trivial advecting
107 field with no body force, so the hash reflects the advection + projection only."""
108 k = 2.0 * np.pi / N
109 ix = np.arange(N)
110 X, Y = np.meshgrid(ix, ix, indexing="ij")
111 u = np.repeat((U0 * np.sin(k * X) * np.cos(k * Y))[:, :, None], nz, axis=2)
112 v = np.repeat((-U0 * np.cos(k * X) * np.sin(k * Y))[:, :, None], nz, axis=2)
113 return (np.asfortranarray(u), np.asfortranarray(v),
114 np.asfortranarray(np.zeros((N, N, nz))))
115
116
118 """Collocated + advection, triply periodic: hasBc_ false, so advection is EXPLICIT (Koren in
119 the RHS) and the advecting velocity is the projected face field of the previous step."""
120 N, nz = 16, 8
121 s = pf.SolverColocated(N, N, nz)
122 s.set_rho(1.0); s.set_mu(0.05); s.set_dt(0.5)
123 s.set_advection(True); s.set_advection_scheme("koren")
124 s.diagnostics.set_velocity_solver_params(80)
125 s.set_pressure_pcg(True, 200, 1e-9)
126 s.set_solid(np.asfortranarray(np.full((N, N, nz), 1e3)), cutcell_pressure=True)
127 s.set_state(*_tg_state(N, nz))
128 for _ in range(5):
129 s.step()
130 return s
131
132
134 """Collocated + advection + domain BCs: hasBc_ true, so implicitAdv() holds and the IMPLICIT
135 FOU operator (buildAdvStencil) plus the explicit (SOU - FOU) deferred correction both read
136 the advecting velocity -- they must read the SAME one."""
137 nx, ny, nz = 24, 12, 8
138 s = pf.SolverColocated(nx, ny, nz)
139 s.set_rho(1.0); s.set_mu(0.05); s.set_dt(0.5)
140 s.set_advection(True); s.set_advection_scheme("koren")
141 s.set_domain_bc("-x", "inflow", (1.0, 0.0, 0.0))
142 s.set_domain_bc("+x", "outflow")
143 s.set_domain_bc("-y", "wall"); s.set_domain_bc("+y", "wall")
144 s.set_pressure_geometry(np.asfortranarray(np.full((nx, ny, nz), 10.0)))
145 s.set_pressure_pcg(True, 200, 1e-9)
146 for _ in range(5):
147 s.step()
148 return s
149
150
152 nx, ny, nz = 24, 12, 8
153 s = pf.Solver(nx, ny, nz)
154 s.set_rho(1.0); s.set_mu(0.05); s.set_dt(0.5)
155 s.set_advection(True); s.set_advection_scheme("koren")
156 s.set_domain_bc("-x", "inflow", (1.0, 0.0, 0.0))
157 s.set_domain_bc("+x", "outflow")
158 s.set_domain_bc("-y", "wall"); s.set_domain_bc("+y", "wall")
159 s.set_pressure_geometry(np.asfortranarray(np.full((nx, ny, nz), 10.0)))
160 s.set_pressure_pcg(True, 200, 1e-9)
161 for _ in range(5):
162 s.step()
163 return s
164
165
167 n, R = 16, 4.0
168 s = pf.Solver(n, n, n)
169 s.set_rho(1.0); s.set_mu(0.1)
170 s.set_pressure_geometry(np.asfortranarray(np.full((n, n, n), 10.0)))
171 s.set_pressure_pcg(True, 500, 1e-12)
172 s.enable_vof()
173 s.set_vof(sphere_fractions((n, n, n), R, (n / 2 + 0.13, n / 2 + 0.27, n / 2 + 0.11)))
174 s.set_property_model("rho", "linear", "C", [1.0, 0.0])
175 s.set_surface_tension(1.0)
176 s.set_dt(0.25 * s.capillary_dt())
177 for _ in range(5):
178 s.step()
179 return s
180
181
183 N, nz = 16, 4
184 s = pf.Solver(N, N, nz)
185 s.set_rho(1.0); s.set_mu(0.05); s.set_dt(8.0)
186 s.set_implicit_advection(True); s.diagnostics.set_outer_iterations(2)
187 for f in ("-x", "+x", "-y", "+y"):
188 s.set_domain_bc(f, "wall", (0.0, 0.0, 0.0))
189 s.set_pressure_geometry(np.asfortranarray(np.full((N, N, nz), 10.0)))
190 alpha = 0.05 / 0.71
191 s.add_scalar("T", diffusivity=alpha, scheme="koren", iters=50)
192 s.set_scalar_bc("T", "-x", "dirichlet", 1.0); s.set_scalar_bc("T", "+x", "dirichlet", 0.0)
193 s.set_scalar_bc("T", "-y", "neumann", 0.0); s.set_scalar_bc("T", "+y", "neumann", 0.0)
194 coeff = 1e4 * 0.05 ** 2 / (0.71 * N ** 3)
195 s.set_property_model("force_y", "boussinesq", "T", [1.0, coeff, 1.0, 0.5])
196 x = np.arange(N)
197 T0 = np.repeat((1.0 - (x + 0.5) / N)[:, None, None], N, 1).repeat(nz, 2)
198 s.set_field("T", np.asfortranarray(T0.astype(np.float64)))
199 for _ in range(5):
200 s.step()
201 return s
202
203
205 N = 16
206 s = pf.Solver(N, N, N)
207 s.set_rho(1.0); s.set_mu(1.0); s.set_dt(0.5)
208 s.set_advection(False)
209 s.set_pressure_geometry(np.asfortranarray(np.full((N, N, N), 10.0)))
210 s.enable_drag()
211 s.set_porous_continuity(True)
212 s.set_field("eps", np.asfortranarray(np.full((N, N, N), 0.6)))
213 s.set_field("drag_beta", np.asfortranarray(np.full((N, N, N), 4.0)))
214 s.diagnostics.exchange_field("eps"); s.diagnostics.exchange_field("drag_beta")
215 s.sync_porous_prev()
216 s.set_body_force((0.0, 0.0, 0.2))
217 for _ in range(5):
218 s.step()
219 return s
220
221
223 N = 16
224 s = pf.Solver(N, N, N)
225 s.set_rho(1.0); s.set_mu(0.1); s.set_dt(2.0); s.set_body_force((1e-3, 0.0, 0.0))
226 s.set_advection(False)
227 s.set_pressure_pcg(True, 200, 1e-9)
228 node_ints = np.array([1, -1, -1], dtype=np.int32) # kSphere
229 node_reals = np.zeros(16); node_reals[0] = 3.0; node_reals[14] = 1.0; node_reals[15] = 1.0
230 ii = np.array([[0, -1]], dtype=np.int32)
231 ir = np.zeros((1, 18)); ir[0, 0:3] = (N / 2, N / 2, N / 2); ir[0, 6] = 1.0; ir[0, 7] = 1.0
232 s.set_scene(node_ints, node_reals, ii.ravel(), ir.ravel(), periodic=True)
233 s.set_instance_transform(0, [N / 2, N / 2, N / 2], [0.0, 0.0, 0.0, 1.0])
234 s.set_instance_motion(0, lin_vel=[0.02, 0.0, 0.0], ang_vel=[0.0, 0.0, 0.005])
235 s.set_solid_from_scene(True)
236 for k in range(5):
237 if k == 3:
238 s.set_instance_transform(0, [N / 2 + 0.1, N / 2, N / 2], [0.0, 0.0, 0.0, 1.0])
239 s.rebuild_geometry()
240 s.step()
241 return s
242
243
244def fields(s):
245 d = {"u": s.get_u(), "v": s.get_v(), "w": s.get_w(), "p": s.get_p()}
246 if s.has_field("C"):
247 d["C"] = s.get_vof()
248 if s.has_field("T"):
249 d["T"] = s.get_field("T")
250 return d
251
252
253def report(name, d):
254 h = hashlib.sha256()
255 parts = []
256 for k in sorted(d):
257 hk = sha(d[k]); h.update(hk.encode()); parts.append(f"{k}={hk[:12]}")
258 print(f"{name:24s} {h.hexdigest()} " + " ".join(parts), flush=True)
259
260
262 from mpi4py import MPI
263 comm = MPI.COMM_WORLD
264 N = 16
265 gsdf = packing_sdf(N)
266 origin, bsize = pf.mpi_block(N, N, N)
267 ox, oy, oz = origin; lnx, lny, lnz = bsize
268 lsdf = np.asfortranarray(gsdf[ox:ox + lnx, oy:oy + lny, oz:oz + lnz])
269 s = pf.Solver(lnx, lny, lnz)
270 s.init_mpi(N, N, N)
271 s.set_rho(1.0); s.set_mu(0.1); s.set_dt(60.0); s.set_body_force((1e-3, 0.0, 0.0))
272 s.set_advection(False)
273 s.diagnostics.set_velocity_solver_params(80)
274 s.set_pressure_multigrid(True, 3)
275 s.set_pressure_pcg(True, 200, 1e-9)
276 s.set_solid(lsdf, cutcell_pressure=True)
277 for _ in range(5):
278 s.step()
279 loc = {k: np.ascontiguousarray(v, dtype=np.float64) for k, v in fields(s).items()}
280 allf = comm.gather(loc, root=0)
281 if comm.Get_rank() == 0:
282 d = {k: np.concatenate([np.asarray(r[k]).ravel() for r in allf]) for k in loc}
283 report(f"mpi_np{comm.Get_size()}", d)
284
285
286if __name__ == "__main__":
287 want = [a for a in sys.argv[1:] if not a.startswith("-")] or list(CASES)
288 for c in want:
289 if c == "mpi":
290 case_mpi()
291 else:
292 report(c, fields(globals()["case_" + c]()))
case_scene_moving()
report(name, d)
case_colocated_advect()
case_colocated_advect_bc()
case_colocated_embed()
packing_sdf(N, rfrac=0.18)
Definition state_hash.py:41
_tg_state(N, nz, U0=1.0)
case_staggered_bed()
Definition state_hash.py:85
stokes_bed(cls, N=16, steps=5, scheme=None)
Definition state_hash.py:70
case_colocated_gauge_exact()
Definition state_hash.py:93
case_colocated_ghost()
Definition state_hash.py:89
case_colocated_plain()
Definition state_hash.py:97