flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
colocated_varrho_symbol.py
Go to the documentation of this file.
1#!/usr/bin/env python
2"""Step-operator model of the collocated variable-density step (doc/collocated_varrho_forces.md,
3Appendix A) -- an INSTRUMENT, not a gate.
4
5A dense, exact-solve, 2-D, h = 1 model of one step (rho0 = 1), unknowns (u, v, P). The step matrix
6T maps (u^n, v^n, P^n) -> (u^{n+1}, v^{n+1}, P^{n+1}) exactly (dense inverse for the implicit
7momentum operator, pseudo-inverse for the pressure Poisson), so its spectral radius is the per-step
8growth factor of the scheme. Three schemes:
9
10 "V8" the retired WO-T step: the pressure and every force as a face acceleration added AFTER the
11 implicit viscous solve (Basilisk centered.h). Non-incremental; with the rotational update
12 (kappa = mu) its pressure obeys P^{n+1} = -4 kappa dt S P^n / rho -> radius 8 mu dt (2-D).
13 "D2" the predictor form with the ARITHMETIC centre->face map: not an adjoint pair, radius > 1
14 at high contrast.
15 "D3" the chosen scheme: the predictor form with the MOMENTUM-weighted centre->face map
16 (rho_L u_L + rho_R u_R)/(rho_L + rho_R), so M Gamma = -C^T and the radius is exactly 1.
17
18Run: OMP_NUM_THREADS=8 python tests/study/colocated_varrho_symbol.py
19Expected (the design note): V8 at dt 0.2: 1.6 = 8 mu dt; D2 at ratio 1e4 slightly above 1 at
20dt 3 and 10 (the note quotes 1.0136 / 1.0145 for its shape); D3: 1.00000 everywhere.
21"""
22import itertools
23
24import numpy as np
25
26
27# ---------------------------------------------------------------- Appendix A, verbatim
28def model(N, rho, mu, walls=False):
29 n, I = N * N, np.eye(N * N)
30
31 def sh(dx, dy):
32 S = np.zeros((n, n))
33 for x, y in itertools.product(range(N), repeat=2):
34 S[x + N * y, (x + dx) % N + N * ((y + dy) % N)] = 1
35 return S
36 Sm, Sp = [sh(-1, 0), sh(0, -1)], [sh(1, 0), sh(0, 1)]
37 o = [np.ones(n), np.ones(n)]
38 if walls:
39 o[1][:N] = 0.0 # closed y-faces at y = 0
40 L = np.zeros((n, n)) # no-slip reflection at closed faces
41 for a in range(2):
42 for i in range(n):
43 for S, op in ((Sm[a], o[a][i]), (Sp[a], (Sp[a] @ o[a])[i])):
44 j = int(np.argmax(S[i]))
45 if op:
46 L[i, j] += mu
47 L[i, i] -= mu
48 else:
49 L[i, i] -= 2 * mu
50 rf = [0.5 * (rho + Sm[a] @ rho) for a in range(2)]
51 G = [I - Sm[a] for a in range(2)]
52 D = [Sp[a] - I for a in range(2)]
53 Pi = [0.5 * (I + Sm[a]) for a in range(2)]
54 R = [0.5 * (I + Sp[a]) for a in range(2)]
55 Lpi = np.linalg.pinv(sum(D[a] @ np.diag(o[a] / rf[a]) @ G[a] for a in range(2)), rcond=1e-12)
56 return locals()
57
58
59def step_matrix(m, dt, kap, scheme): # "V8" | "D2" | "D3" (chosen)
60 n, I, rho, o, rf, G, D, Pi, R, L, Lpi = (m[k] for k in "n I rho o rf G D Pi R L Lpi".split())
61 Ai, Z, T = np.linalg.inv(np.diag(rho / dt) - L), np.zeros((n, n)), np.zeros((3 * n, 3 * n))
62 Fp = [np.diag(o[a] / rf[a]) @ G[a] for a in range(2)] # face pressure accel per unit P
63 us, uf = [], []
64 for a in range(2):
65 blk = [Z, Z, Z if scheme == "V8" else -Ai @ np.diag(rho) @ R[a] @ Fp[a]]
66 blk[a] = Ai @ np.diag(rho / dt)
67 us.append(blk)
68 C2F = np.diag(1 / rf[a]) @ Pi[a] @ np.diag(rho) if scheme == "D3" else Pi[a]
69 f = [np.diag(o[a]) @ C2F @ b for b in blk]
70 if scheme == "V8":
71 f[2] = f[2] - dt * Fp[a]
72 uf.append(f)
73 div = [D[0] @ uf[0][k] + D[1] @ uf[1][k] for k in range(3)]
74 phi = [Lpi @ d for d in div]
75 for a in range(2):
76 for k in range(3):
77 acc = -Fp[a] @ phi[k] + (-dt * Fp[a] if (scheme == "V8" and k == 2) else 0)
78 T[a * n:(a + 1) * n, k * n:(k + 1) * n] = (us[a][k] + R[a] @ acc if scheme == "V8"
79 else us[a][k] - R[a] @ Fp[a] @ phi[k])
80 for k in range(3):
81 T[2 * n:, k * n:(k + 1) * n] = phi[k] / dt - kap * div[k] + (I if k == 2 else 0)
82 return T
83
84
85def radius(T):
86 return np.max(np.abs(np.linalg.eigvals(T)))
87
88
89# ---------------------------------------------------------------- drivers
90def density(N, kind, ratio, seed=1):
91 """Cell density field (x fastest, index x + N*y)."""
92 x, y = np.meshgrid(np.arange(N) + 0.5, np.arange(N) + 0.5, indexing="xy")
93 if kind == "uniform":
94 r = np.ones((N, N))
95 elif kind == "slab":
96 r = np.where((y >= N / 4) & (y < 3 * N / 4), ratio, 1.0)
97 elif kind == "disk":
98 r = np.where((x - 0.47 * N) ** 2 + (y - 0.53 * N) ** 2 < (0.3 * N) ** 2, ratio, 1.0)
99 else: # "random": a random two-phase blob field, smoothed then thresholded
100 rng = np.random.default_rng(seed)
101 f = rng.standard_normal((N, N))
102 for _ in range(2):
103 f = 0.2 * (f + np.roll(f, 1, 0) + np.roll(f, -1, 0) + np.roll(f, 1, 1) +
104 np.roll(f, -1, 1))
105 r = np.where(f > 0.0, ratio, 1.0)
106 return r.reshape(-1).astype(np.float64)
107
108
109def main():
110 np.set_printoptions(precision=6)
111 N, mu = 12, 1.0
112 print("1. V8 (retired), uniform rho, periodic, kappa = mu: radius vs 8 mu dt")
113 m = model(N, density(N, "uniform", 1.0), mu)
114 for dt in (0.01, 0.1, 0.2, 1.0):
115 print(f" dt {dt:<5g} radius {radius(step_matrix(m, dt, mu, 'V8')):.6f} "
116 f"(8 mu dt = {8 * mu * dt:.4f})")
117 print("2. D2 (arithmetic centre->face, predictor form), ratio 1e4, kappa = mu")
118 for kind in ("disk", "random"):
119 for walls in (False, True):
120 m = model(N, density(N, kind, 1e4), mu, walls)
121 rr = [radius(step_matrix(m, dt, mu, "D2")) for dt in (3.0, 10.0)]
122 print(f" {kind:6s} walls={walls!s:5s} dt 3: {rr[0]:.6f} dt 10: {rr[1]:.6f}")
123 print("3. D3 (chosen: momentum-weighted centre->face), kappa = mu and 0")
124 worst = 0.0
125 for kind in ("uniform", "slab", "disk", "random"):
126 for walls in (False, True):
127 for ratio in (10.0, 1e3, 1e6):
128 m = model(N, density(N, kind, ratio), mu, walls)
129 for dt in (0.01, 1.0, 100.0, 1e4):
130 for kap in (mu, 0.0):
131 worst = max(worst, radius(step_matrix(m, dt, kap, "D3")))
132 print(f" worst radius over uniform/slab/disk/random x periodic/walled x ratio 10..1e6 x "
133 f"dt 0.01..1e4 x kappa {{mu, 0}}: {worst:.9f}")
134
135
136if __name__ == "__main__":
137 main()
model(N, rho, mu, walls=False)
density(N, kind, ratio, seed=1)