flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
scene_check.py
Go to the documentation of this file.
1"""Layer 2: the in-solver analytic scene must reproduce the geometry flow has always been handed
2as a sampled array -- on a CONVERGED periodic sphere packing, not a diverged one."""
3import numpy as np
4from peclet import flow as sdflow
5
6N = 32
7R = 0.18 * N
8CEN = [(0.25*N, 0.25*N, 0.25*N), (0.75*N, 0.75*N, 0.25*N),
9 (0.75*N, 0.25*N, 0.75*N), (0.25*N, 0.75*N, 0.75*N)]
10
11# Reference: exactly the SDF the verify script uses (min-image periodic).
12g = np.arange(N) + 0.5
13X, Y, Z = np.meshgrid(g, g, g, indexing="ij")
14ref = np.full((N, N, N), 1e30)
15for (cx, cy, cz) in CEN:
16 dx = np.abs(X-cx); dx = np.minimum(dx, N-dx)
17 dy = np.abs(Y-cy); dy = np.minimum(dy, N-dy)
18 dz = np.abs(Z-cz); dz = np.minimum(dz, N-dz)
19 ref = np.minimum(ref, np.sqrt(dx*dx+dy*dy+dz*dz) - R)
20
21# Scene: PERIODICITY IS THE CALLER'S -- instantiate the 27 images of each sphere so the analytic
22# min over instances reproduces the min-image distance. Cell centre (i,j,k) sits at (i,j,k), and
23# the numpy grid puts it at i+0.5, so instance centres are shifted by -0.5.
24KN_R, KI_I, KI_R = 16, 2, 17
25node_ints = np.array([1, -1, -1], dtype=np.int32) # kSphere
26node_reals = np.zeros(KN_R); node_reals[0] = R
27node_reals[14] = 1.0 # quaternion w
28node_reals[15] = 1.0 # scale
29inst = []
30for (cx, cy, cz) in CEN:
31 for ix in (-1, 0, 1):
32 for iy in (-1, 0, 1):
33 for iz in (-1, 0, 1):
34 inst.append((cx - 0.5 + ix*N, cy - 0.5 + iy*N, cz - 0.5 + iz*N))
35inst_ints = np.zeros((len(inst), KI_I), dtype=np.int32)
36inst_reals = np.zeros((len(inst), KI_R))
37for m, (cx, cy, cz) in enumerate(inst):
38 inst_ints[m] = (0, -1)
39 inst_reals[m, 0:3] = (cx, cy, cz)
40 inst_reals[m, 6] = 1.0
41 inst_reals[m, 7] = 1.0
42print(" scene: 1 node, %d instances (4 spheres x 27 images)" % len(inst))
43
44def run(setup):
45 s = sdflow.Solver(N, N, N)
46 # rho/mu/dt BEFORE the geometry: set_solid builds the momentum operator from them. Setting
47 # them afterwards leaves it built on defaults and the run diverges (~1e35).
48 s.set_rho(1.0); s.set_mu(1.0); s.set_dt(50.0)
49 setup(s)
50 s.set_body_force(1.0, 0.0, 0.0)
51 for _ in range(40): s.step()
52 return s, np.asarray(s.get_u())
53
54s1, u1 = run(lambda s: (s.set_scene(node_ints, node_reals, inst_ints.ravel(), inst_reals.ravel()),
55 s.set_solid_from_scene(True)))
56s2, u2 = run(lambda s: s.set_solid(np.asfortranarray(ref), cutcell_pressure=True))
57
58# NATIVE periodic scene: 4 instances, no images, periodic=True — must agree with both
59base_ints = np.zeros((len(CEN), KI_I), dtype=np.int32)
60base_reals = np.zeros((len(CEN), KI_R))
61for m, (cx, cy, cz) in enumerate(CEN):
62 base_ints[m] = (0, -1)
63 base_reals[m, 0:3] = (cx - 0.5, cy - 0.5, cz - 0.5)
64 base_reals[m, 6] = 1.0
65 base_reals[m, 7] = 1.0
66s4, u4 = run(lambda s: (s.set_scene(node_ints, node_reals, base_ints.ravel(), base_reals.ravel(),
67 periodic=True),
68 s.set_solid_from_scene(True)))
69
70den = max(np.abs(u2).max(), 1e-300)
71rel = np.abs(u1-u2).max()/den
72print(" CONVERGED? u_absmax array = %.6e (a diverged run is ~1e35)" % np.abs(u2).max())
73print(" 108-image scene vs sampled array: max rel diff = %.3e" % rel)
74rel4 = np.abs(u4-u2).max()/den
75print(" NATIVE periodic (4 inst) vs array: max rel diff = %.3e" % rel4)
76ok = rel < 1e-12 and rel4 < 1e-12 and np.abs(u2).max() < 1e3
77
78s3, u3 = run(lambda s: (s.set_scene(node_ints, node_reals, base_ints.ravel(), base_reals.ravel(),
79 periodic=True),
80 s.set_exact_crossings_from_scene(),
81 s.set_solid_from_scene(True)))
82print(" with EXACT scene crossings: u_absmax %.6e (vs %.6e)" % (np.abs(u3).max(), np.abs(u1).max()))
83print(" changed the solution: %s finite: %s" %
84 (not np.array_equal(u3, u1), bool(np.isfinite(u3).all())))
85print("SCENE OK" if ok and np.isfinite(u3).all() and np.abs(u3).max() < 1e3 else "SCENE FAIL")