flow
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
fv_operator_truncation.py
Go to the documentation of this file.
1"""A-priori truncation test of the FV viscous operator L_visc(u) = mu[ Σ_f o_f(u_i-u_nbr) - Σ_a W_a g_a ]
2vs the exact ∫_fluidCV(-mu ∇²u) dV, for the manufactured field u = sdf (u=0 on the sphere, ∇u=n̂,
3∇²u = 2/r). The wall gradient g_a = n_a is EXACT for u=sdf, so this isolates the FACE-flux term.
4Compares (A) face-CENTRE two-point flux vs (B) face flux evaluated at the open-area centroid."""
5import numpy as np
6R=0.3102; C0=np.array([0.013,-0.007,0.004]); MU=0.1
7def sdf(x,y,z): return np.sqrt((x-C0[0])**2+(y-C0[1])**2+(z-C0[2])**2)-R
8def lap(x,y,z): return 2.0/np.sqrt((x-C0[0])**2+(y-C0[1])**2+(z-C0[2])**2) # ∇²(sdf)=2/r
9def face_open(N,axis,h):
10 a1,a2=(axis+1)%3,(axis+2)%3; w=np.arange(N+1)*h-0.5; t1lo=np.arange(N)*h-0.5; t2lo=t1lo
11 rho2=R*R-(w-C0[axis])**2; O=np.ones((N+1,N,N)); c1,c2=C0[a1],C0[a2]
12 for k in np.nonzero(rho2>0)[0]:
13 rho=np.sqrt(rho2[k]); j1=np.nonzero(np.abs(t1lo+h/2-c1)<rho+h)[0]; j2=np.nonzero(np.abs(t2lo+h/2-c2)<rho+h)[0]
14 if len(j1)==0 or len(j2)==0: continue
15 J1,J2=np.meshgrid(j1,j2,indexing="ij"); lo1=t1lo[J1.ravel()]; lo2=t2lo[J2.ravel()]
16 t=lo1[:,None]+(np.arange(256)[None,:]+0.5)*(h/256); s2=rho*rho-(t-c1)**2; half=np.sqrt(np.maximum(s2,0))
17 zlo=np.maximum(c2-half,lo2[:,None]); zhi=np.minimum(c2+half,lo2[:,None]+h)
18 O[k,J1.ravel(),J2.ravel()]=1.0-np.clip(zhi-zlo,0,None).mean(axis=1)/h
19 return O
20def run(N):
21 h=1.0/N; c=(np.arange(N)+0.5)*h-0.5; X,Y,Z=np.meshgrid(c,c,c,indexing="ij")
22 S=sdf(X,Y,Z); fl=S>=0; U=np.where(fl,S,0.0) # u=sdf in fluid, 0 in solid
23 o=[]; W=[]
24 for a in range(3):
25 O=face_open(N,a,h); om=O[:-1]; op=O[1:]
26 om=np.moveaxis(om,[0,1,2],[a,(a+1)%3,(a+2)%3]); op=np.moveaxis(op,[0,1,2],[a,(a+1)%3,(a+2)%3])
27 o.append((om,op)); W.append(h*h*(om-op))
28 # discrete FV viscous: mu[ Σ o_f h (u_i-u_nbr) - Σ W_a g_a ], g_a = n_a (exact for u=sdf)
29 r=np.sqrt((X-C0[0])**2+(Y-C0[1])**2+(Z-C0[2])**2); nx=(X-C0[0])/r; ny=(Y-C0[1])/r; nz=(Z-C0[2])/r
30 nvec=[nx,ny,nz]
31 faces=np.zeros((N,N,N))
32 for a in range(3):
33 om,op=o[a]; um=np.roll(U,+1,a); up=np.roll(U,-1,a) # u_{i-a}, u_{i+a}
34 faces+= h*(om*(U-um)+op*(U-up)) # o_f * h * (u_i - u_nbr), h from area h^2 / spacing h
35 wall=sum(W[a]*nvec[a] for a in range(3)) # Σ W_a n_a (h^2 folded into W)
36 Lvisc=MU*(faces - wall)
37 # exact ∫_fluidCV(-mu ∇²u) dV via 6^3 subsample
38 ns=6; off=(np.arange(ns)+0.5)/ns-0.5
39 ex=np.zeros((N,N,N))
40 cut=(np.abs(W[0])+np.abs(W[1])+np.abs(W[2]))>1e-9
41 ii,jj,kk=np.nonzero(cut)
42 for I,J,K in zip(ii,jj,kk):
43 xs=c[I]+off*h; ys=c[J]+off*h; zs=c[K]+off*h
44 XX,YY,ZZ=np.meshgrid(xs,ys,zs,indexing="ij"); fld=sdf(XX,YY,ZZ)>=0
45 ex[I,J,K]=-MU*np.sum(np.where(fld,lap(XX,YY,ZZ),0.0))*(h/ns)**3
46 # interior (non-cut fluid): exact = -mu*lap*h^3 ; discrete faces (o=1) = mu*(6u_i-Σnbr) approx
47 res=(Lvisc-ex)[cut]
48 return np.sqrt(np.mean(res**2)), np.max(np.abs(res)), cut.sum()
49prev=None
50print(f"{'N':>4} | {'rms resid':>11} {'ord':>5} | {'max resid':>11} {'ord':>5} | Ncut")
51for N in (32,64,128):
52 rms,mx,nc=run(N)
53 o=np.log2(prev[0]/rms) if prev else float('nan'); om=np.log2(prev[1]/mx) if prev else float('nan')
54 print(f"{N:>4} | {rms:>11.3e} {o:>5.2f} | {mx:>11.3e} {om:>5.2f} | {nc}")
55 prev=(rms,mx)