flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
solver.py
Go to the documentation of this file.
1"""2-D prototype, round 5: can peclet's solvers handle the probe-flux operator?
2
3A = probe-flux cut-cell FV (jc.py, probe 0.7h) on the disc, Dirichlet (worst case) and Robin.
4Systems: steady (A u = b) and one backward-Euler step (kappa/dt + A) u = b at diffusion number
5dt*D/h^2 = 1, 10.
6Tested:
7 GS : lexicographic Gauss-Seidel directly on A (stands in for RB-GS): converges?
8 DC : defect correction u += S^-1 (b - A u) with S = symmetric compact surrogate
9 (aperture FV + diagonal wall term A_w/(d + 1/k), d = centroid distance; SPD) -> rho(I - S^-1 A)
10 BiCGStab / GMRES(20) preconditioned by S (exact S^-1 here = an ideal MG V-cycle): iterations to 1e-10
11Also: does S itself suit MG (it is the existing operator family + a diagonal) - assumed, not tested here.
12"""
13import numpy as np, scipy.sparse as sp, scipy.sparse.linalg as spla
14from disc_bc import Geo, assemble_fv
15from jc import build
16
17
18SURR = "centroid"
19
20
21def surrogate(G, k):
22 cells = G.kap > 1e-12
23 h = G.h
24 d = np.maximum(G.dwall, 1e-3 * h) if SURR == "centroid" else 0.7 * h # lumped probe: d = probe distance
25 wc = np.where(cells, G.Aw / (h * h * (d + (0 if np.isinf(k) else 1 / k))), 0)
26 S, _ = assemble_fv(G, G.ax, G.ay, cells, wall_coef=wc)
27 return S
28
29
30def gs_rate(A, b, n=200):
31 L = sp.tril(A, 0).tocsc(); U = A - sp.tril(A, 0)
32 u = np.zeros_like(b); r0 = np.linalg.norm(b)
33 hist = []
34 for it in range(n):
35 u = spla.spsolve_triangular(L, b - U @ u, lower=True)
36 hist.append(np.linalg.norm(b - A @ u) / r0)
37 if not np.isfinite(hist[-1]) or hist[-1] > 1e8:
38 return "DIVERGES", it
39 return hist[-1], n
40
41
42def run(ND, k, dtn):
43 G = Geo(ND, (0.31, 0.62))
44 A, M = build(G, k, fc=False, wall="probe0.7")
45 S = surrogate(G, k)
46 h2 = G.h**2
47 if dtn is not None:
48 idt = 1.0 / (dtn * h2) # D = 1
49 A = A + sp.diags(M * idt); S = S + sp.diags(M * idt)
50 A = A.tocsr(); S = S.tocsc()
51 rng = np.random.default_rng(0)
52 b = M * (1 + 0.1 * rng.standard_normal(A.shape[0]))
53 out = {}
54 out["GS"] = gs_rate(A, b)
55 lu = spla.splu(S)
56 P = spla.LinearOperator(A.shape, matvec=lu.solve)
57 # defect-correction spectral radius by power iteration on E = I - S^-1 A
58 v = rng.standard_normal(A.shape[0])
59 for _ in range(300):
60 w = v - lu.solve(A @ v); nv = np.linalg.norm(w); v = w / nv
61 out["rho_DC"] = nv
62 for name, fn in (("BiCGStab", spla.bicgstab), ("GMRES20", lambda A_, b_, **kw: spla.gmres(A_, b_, restart=20, callback_type='legacy', **kw))):
63 cnt = [0]
64 def cb(_):
65 cnt[0] += 1
66 x, info = fn(A, b, M=P, rtol=1e-10, maxiter=500, callback=cb)
67 out[name] = (cnt[0], info, np.linalg.norm(b - A @ x) / np.linalg.norm(b))
68 # small-cell census
69 cells = G.kap > 1e-12
70 out["min_kappa"] = G.kap[cells].min()
71 return out
72
73
74if __name__ == "__main__":
75 import sys
76 SURR = sys.argv[1] if len(sys.argv) > 1 else "centroid"
77 for k in (np.inf, 10.0, 1.0):
78 for dtn in (None, 1.0):
79 for ND in (32, 64, 128):
80 o = run(ND, k, dtn)
81 gs = o["GS"]
82 print(f"Bi={k:5g} dtD/h2={str(dtn):5s} ND={ND:3d} | GS: {gs[0] if isinstance(gs[0], str) else f'{gs[0]:.1e}'} "
83 f"(it {gs[1]}) | rho(DC)={o['rho_DC']:.3f} | BiCGStab it={o['BiCGStab'][0]} | "
84 f"GMRES20 it={o['GMRES20'][0]} res={o['GMRES20'][2]:.1e} | min kappa={o['min_kappa']:.1e}", flush=True)
run(ND, k, dtn)
Definition solver.py:42
surrogate(G, k)
Definition solver.py:21
gs_rate(A, b, n=200)
Definition solver.py:30