在我看来,像这样的 LBM 问题几乎总是与边界条件的实现有关。根据 BC 的选择及其实现方式,它可能会降低 LBM 的准确性O(δ2)(二阶)到O(δ1.5)或更糟的是O(δ)(第一个订单)。
我不熟悉 BFL 条件的细节,但是如果我在笛卡尔通道(而不是管道)中通过中途反弹来解决这个问题,那么只有三个格子节点(非常粗糙的网格)我会得到一个错误vmax4%。数值解很快接近解析解:

通过将节点数量加倍进行改进,二阶方式的误差为 1.33%、0.41% 等。
我不清楚你如何估计你的剪切应力,但给定方程,我假设你确定管道中的最大速度,然后计算应力。因此,上述误差直接转化为计算剪应力的误差。另一种选择是直接从分布函数确定应力。
现在,至于为什么您与 FEM 解决方案有如此大的偏差(这是一个更粗糙的数量级),我只能提供一些潜在的指针,因为我没有您的实现细节:
- 管道是轴对称系统,通常我们希望在梯度最大的地方(即在墙壁处)增加分辨率。标准 LBM 没有考虑这一点,因为它具有恒定的晶格间距,但如果您的 FEM 解决方案确实考虑了这一点,那么您就是在比较“苹果和橙子”。
- 此外,关于轴对称系统,您提供的方程是在圆柱坐标系中求解泊肃叶流的结果。标准 LBM 采用笛卡尔坐标系,需要对其他坐标系(例如柱坐标)进行修改。如果您的 FEM 解决方案是圆柱坐标系,那么您又是在比较“苹果和橙子”。
- 重新检查边界条件实现是否存在任何错误。以我的经验,这是最大的错误来源,最容易被忽视的一点是角落的处理(有时多种边界类型会聚集在一起)。
- 边界位置(至少对于反弹)取决于粘度,这意味着对于不同的弛豫时间值,我们可以得到与理论的轻微偏差。这通过使用 MRT 而不是 BGK 得到改进,特别是通过使用具有“神奇”弛豫时间的 TRT,其边界位置与机器精度精确。
希望能帮助到你
生成图表的代码:
import numpy as np
import matplotlib.pyplot as plt
def sim(n=2, Fo=1):
"""
"""
### parameter
# D2Q9 lattice
ns = 9
cssq = 1/3
ws = [4/9, 1/9, 1/9, 1/9, 1/9, 1/36, 1/36, 1/36, 1/36]
ex = [0, +1, 0, -1, 0, +1, -1, -1, +1]
ey = [0, 0, +1, 0, -1, +1, +1, -1, -1]
# grid
nx = 1
ny = 2**n+1
# quantities
ω = 1
vmax = 0.01
ν = cssq*(1/ω-1/2)
ax = 8*ν*vmax/ny**2
ay = 0
### initialization
rho = np.ones((nx, ny), dtype=np.float)
vx = np.zeros((nx, ny), dtype=np.float)
vy = np.zeros((nx, ny), dtype=np.float)
Fx = np.zeros((nx, ny), dtype=np.float)
Fy = np.zeros((nx, ny), dtype=np.float)
f = np.zeros((nx+2, ny+2, ns), dtype=np.float)
ftmp = np.zeros((nx+2, ny+2, ns), dtype=np.float)
# initialize at equilibrium
for s in range(ns):
feq = ws[s] # ρ=1, vx=vy=0
f[1:nx+1, 1:ny+1, s] = feq
ftmp[1:nx+1, 1:ny+1, s] = feq
### main loop
niter = Fo*int(ny**2/ν)
for i in range(niter):
### quantities
dens = f[1:nx+1,1:ny+1,0]
momx = 0
momy = 0
for s in range(1,ns):
dens += f[1:nx+1,1:ny+1,s]
momx += ex[s]*f[1:nx+1,1:ny+1,s]
momy += ey[s]*f[1:nx+1,1:ny+1,s]
rho[:,:] = dens
Fx = dens*ax
Fy = dens*ay
vx[:,:] = (momx + 0.5*Fx)/dens
vy[:,:] = (momy + 0.5*Fy)/dens
### collision
vv = (vx*vx + vy*vy)/cssq;
for s in range(ns):
ev = (ex[s]*vx + ey[s]*vy)/cssq
feq = ws[s]*rho*(1 + ev + 1/2*ev**2 - 1/2*vv)
ef = (ex[s]*Fx + ey[s]*Fy)/cssq
fforce = (1-1/2*ω)*ws[s]*(
(ex[s]-vx + ev*ex[s])*Fx
+ (ey[s]-vy + ev*ey[s])*Fy
)/cssq
ftmp[1:-1,1:-1,s] = (1-ω)*f[1:-1,1:-1,s] + ω*feq + fforce
### boundaries
# x boundaries - periodic
ftmp[0,1:-1,:] = ftmp[-2,1:-1,:]
ftmp[-1,1:-1,:] = ftmp[1,1:-1,:]
# y boundaries - halfway bounceback
for (s, so) in zip([2, 5, 6], [4, 7, 8]):
ftmp[1:nx+1, 0, s] = ftmp[1-ex[so]:nx+1-ex[so], 1, so]
ftmp[1:nx+1, -1, so] = ftmp[1-ex[s]:nx+1-ex[s], -2, s]
# corners - halfway bounceback
ftmp[0, 0, 5] = ftmp[0-ex[7], 1, 7]
ftmp[-1, 0, 6] = ftmp[-1-ex[8], 1, 8]
ftmp[0, -1, 8] = ftmp[0-ex[6], -2, 6]
ftmp[-1, -1, 7] = ftmp[-1-ex[5], -2, 5]
### streaming
for x in range(1,nx+1):
for y in range(1,ny+1):
for s in range(ns):
f[x,y,s] = ftmp[x-ex[s], y-ey[s], s]
return dict(
# vars
rho = rho,
vx = vx, vy = vy,
# params
nx = nx, ny = ny,
vmax = vmax,
)
### figure 1 - numerical vs analytical solutions
errors = []
resolution = []
for n in range(4):
print(f"running simulation with ny = 2^{n}+1 = {2**n+1}")
s = sim(n=n)
vmag = np.sqrt(s['vx']**2 + s['vy']**2)/s['vmax'] # scaled
yrange, y0, yf = np.arange(s['ny']), -0.5, s['ny']-0.5
sol = (yrange-y0)*(yf-yrange)/(s['ny']/2)**2
ϵ = np.linalg.norm(vmag[0,:]-sol)/np.linalg.norm(sol)
errors.append(ϵ)
resolution.append(2**n+1)
plt.plot((np.arange(s['ny'])-y0)/s['ny'], vmag[0,:], '-o', label=f"$2^{n}+1$")
yrange, y0, yf = np.linspace(0,s['ny']-1,100), -0.5, s['ny']-0.5
plt.plot((yrange-y0)/s['ny'], (yrange-y0)*(yf-yrange)/(s['ny']/2)**2, '--', label='sol')
plt.xlabel(r'dimensionless spatial coordinate, $y/H$')
plt.ylabel(r'dim. velocity magnitude, $v_{mag}/v_{max}$')
plt.xlim(0,1)
plt.legend()
### figure 2 - L2 error as function of resolution
plt.loglog(resolution, errors, 'o', basex=2, label='lbm')
plt.loglog(resolution, list(map(lambda r: r**-2, resolution)), '--', basex=2, label=r'$O(\delta^2)$')
plt.xlabel('grid resolution')
plt.ylabel('L2 error')
plt.legend()