对流换热仿真-主题064_浸入边界法-浸入边界法
第064篇:浸入边界法(Immersed Boundary Method, IBM)
摘要
浸入边界法(Immersed Boundary Method, IBM)是一种处理复杂几何形状边界条件的数值方法,它通过在笛卡尔网格上求解流场,同时利用力源项或插值技术来处理 immersed(浸入)在流体中的固体边界。本教程将系统介绍IBM的基本原理、数学模型、数值实现方法,以及在对流换热仿真中的应用。通过Python代码实现,我们将展示如何用IBM模拟圆柱绕流、颗粒沉降、柔性体变形等复杂流动与换热问题。IBM的优势在于避免了复杂的贴体网格生成,特别适合处理运动边界和大变形问题,在生物流体力学、多相流、流固耦合等领域有广泛应用。
关键词
浸入边界法,复杂几何,力源项,笛卡尔网格,流固耦合,运动边界,直接力法,插值法





1. 引言
1.1 复杂几何处理的挑战
在计算流体力学(CFD)中,处理复杂几何形状一直是一个核心挑战。传统的有限体积法(FVM)或有限元法(FEM)通常需要生成贴体网格(body-fitted mesh),即网格边界必须与物理边界完全贴合。这种方法存在以下问题:
网格生成困难:对于复杂几何(如生物组织、多孔介质、运动颗粒),生成高质量的贴体网格需要大量人工干预和专业知识。网格质量直接影响数值精度和收敛性。
网格更新成本高:当边界发生运动或变形时,需要重新生成网格或更新网格节点位置。对于大变形问题,网格可能产生严重畸变,导致计算失败。
计算效率受限:贴体网格通常需要非结构化网格,其数据结构和计算效率不如结构化笛卡尔网格。
1.2 浸入边界法的优势
浸入边界法(IBM)由Peskin于1972年提出,最初用于模拟心脏瓣膜的血液流动。其核心思想是:
笛卡尔网格求解:在简单的笛卡尔网格上求解Navier-Stokes方程,利用快速傅里叶变换(FFT)或多重网格等高效算法。
边界力处理:通过力源项或速度插值来处理固体边界的影响,将边界条件"浸入"到流场中。
主要优势:
- 无需生成贴体网格,大幅降低前处理工作量
- 天然适合处理运动边界和大变形问题
- 保持笛卡尔网格的高效计算特性
- 易于实现流固耦合
1.3 IBM的分类
根据边界处理方式,IBM可分为两大类:
连续力法(Continuous Forcing Approach):在Navier-Stokes方程中添加体积力项来模拟边界效应。适用于柔性边界。
离散力法(Discrete Forcing Approach):直接在离散方程层面处理边界条件。包括:
- 直接力法(Direct Forcing Method)
- 幽灵点法(Ghost Cell Method)
- 镜像点法(Mirror Point Method)
本教程将重点介绍直接力法和速度插值法,它们在对流换热仿真中最为常用。
2. 浸入边界法的数学模型
2.1 控制方程
考虑不可压缩流体,连续性方程和Navier-Stokes方程为:
∇⋅u=0\nabla \cdot \mathbf{u} = 0∇⋅u=0
∂u∂t+(u⋅∇)u=−1ρ∇p+ν∇2u+f\frac{\partial \mathbf{u}}{\partial t} + (\mathbf{u} \cdot \nabla)\mathbf{u} = -\frac{1}{\rho}\nabla p + \nu \nabla^2 \mathbf{u} + \mathbf{f}∂t∂u+(u⋅∇)u=−ρ1∇p+ν∇2u+f
其中,f\mathbf{f}f是浸入边界产生的体积力(力源项),仅在边界附近非零。
对于能量方程:
∂T∂t+(u⋅∇)T=α∇2T+q\frac{\partial T}{\partial t} + (\mathbf{u} \cdot \nabla)T = \alpha \nabla^2 T + q∂t∂T+(u⋅∇)T=α∇2T+q
其中,qqq是边界热源项。
2.2 直接力法
直接力法的核心思想是:在边界点处,通过添加适当的力使流体速度等于边界速度。
力的计算:
在时间步 nnn 到 n+1n+1n+1 之间,假设没有边界力,预测速度为 u∗\mathbf{u}^*u∗。为使边界点速度等于目标速度 Ub\mathbf{U}_bUb,需要施加力:
f=Ub−u∗Δt\mathbf{f} = \frac{\mathbf{U}_b - \mathbf{u}^*}{\Delta t}f=ΔtUb−u∗
算法流程:
- 求解无边界力的Navier-Stokes方程,得到预测速度 u∗\mathbf{u}^*u∗
- 在边界点处计算所需力 f\mathbf{f}f
- 将力投影到欧拉网格
- 修正速度场
2.3 速度插值法
速度插值法直接在边界点处强制满足无滑移条件,无需显式计算力。
线性插值:
对于位于流体中的边界点,找到最近的网格点,通过线性插值使边界点速度等于目标速度:
ub=ufluid⋅(1−α)+ughost⋅α=Utarget\mathbf{u}_b = \mathbf{u}_{fluid} \cdot (1 - \alpha) + \mathbf{u}_{ghost} \cdot \alpha = \mathbf{U}_{target}ub=ufluid⋅(1−α)+ughost⋅α=Utarget
其中,α\alphaα是距离权重系数。
幽灵点法:
在固体内部设置幽灵点(Ghost Cell),通过外推计算幽灵点速度,使得边界插值满足无滑移条件。
2.4 狄拉克函数与力投影
在连续力法中,需要将拉格朗日边界点上的力投影到欧拉网格。这通过狄拉克函数实现:
f(x)=∫ΓF(X)δ(x−X)dX\mathbf{f}(\mathbf{x}) = \int_\Gamma \mathbf{F}(\mathbf{X}) \delta(\mathbf{x} - \mathbf{X}) d\mathbf{X}f(x)=∫ΓF(X)δ(x−X)dX
其中,F\mathbf{F}F是边界上的力密度,δ\deltaδ是狄拉克函数。
常用的光滑狄拉克函数(Peskin的4点函数):
δh(r)={14h(1+cosπr2h),∣r∣≤2h0,∣r∣>2h\delta_h(r) = \begin{cases} \frac{1}{4h}\left(1 + \cos\frac{\pi r}{2h}\right), & |r| \leq 2h \\ 0, & |r| > 2h \end{cases}δh(r)={4h1(1+cos2hπr),0,∣r∣≤2h∣r∣>2h
其中,hhh是网格间距。
3. 数值实现方法
3.1 算法流程
基于直接力法的IBM算法:
初始化:设置流场、边界位置、边界速度
时间循环:
1. 求解对流项:u* = u^n + Δt·(-u·∇u)
2. 求解扩散项:u** = u* + Δt·ν·∇²u
3. 识别边界附近的网格点
4. 计算直接力:f = (U_b - u**)/Δt
5. 施加体积力:u*** = u** + Δt·f
6. 压力修正:求解泊松方程 ∇²p = (ρ/Δt)·∇·u***
7. 更新速度:u^{n+1} = u*** - (Δt/ρ)·∇p
8. 更新边界位置(如运动边界)
3.2 边界识别与标记
距离函数法:
定义到边界的距离函数 ϕ(x)\phi(\mathbf{x})ϕ(x):
- ϕ<0\phi < 0ϕ<0:固体内部
- ϕ=0\phi = 0ϕ=0:边界上
- ϕ>0\phi > 0ϕ>0:流体区域
对于圆形边界(圆心 (xc,yc)(x_c, y_c)(xc,yc),半径 RRR):
ϕ(x,y)=(x−xc)2+(y−yc)2−R\phi(x, y) = \sqrt{(x-x_c)^2 + (y-y_c)^2} - Rϕ(x,y)=(x−xc)2+(y−yc)2−R
标记策略:
- 流体点:ϕ>0\phi > 0ϕ>0
- 固体点:ϕ<−ϵ\phi < -\epsilonϕ<−ϵ(ϵ\epsilonϵ为缓冲层厚度)
- 边界点:−ϵ≤ϕ≤0-\epsilon \leq \phi \leq 0−ϵ≤ϕ≤0
3.3 力的投影与插值
从拉格朗日点到欧拉网格:
对于每个拉格朗日边界点 Xl\mathbf{X}_lXl,在其影响的欧拉网格点 xi,j\mathbf{x}_{i,j}xi,j 上施加力:
fi,j=∑lFl⋅δh(xi,j−Xl)⋅Δsl\mathbf{f}_{i,j} = \sum_l \mathbf{F}_l \cdot \delta_h(\mathbf{x}_{i,j} - \mathbf{X}_l) \cdot \Delta s_lfi,j=l∑Fl⋅δh(xi,j−Xl)⋅Δsl
其中,Δsl\Delta s_lΔsl是边界点代表的弧长。
速度插值:
从欧拉网格插值到拉格朗日点:
Ul=∑i,jui,j⋅δh(xi,j−Xl)⋅h2\mathbf{U}_l = \sum_{i,j} \mathbf{u}_{i,j} \cdot \delta_h(\mathbf{x}_{i,j} - \mathbf{X}_l) \cdot h^2Ul=i,j∑ui,j⋅δh(xi,j−Xl)⋅h2
4. Python仿真实现
4.1 案例1:静止圆柱绕流
问题描述:
模拟雷诺数 Re=100Re = 100Re=100 的圆柱绕流,圆柱直径 D=1D = 1D=1,计算域为 [−8D,16D]×[−8D,8D][-8D, 16D] \times [-8D, 8D][−8D,16D]×[−8D,8D]。
IBM实现:
使用直接力法,在圆柱表面施加力使流体速度为零。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
import matplotlib
matplotlib.use('Agg')
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
# 参数设置
D = 1.0 # 圆柱直径
Re = 100 # 雷诺数
U_inf = 1.0 # 来流速度
nu = U_inf * D / Re # 运动粘度
# 计算域
Lx, Ly = 24*D, 16*D
Nx, Ny = 240, 160
dx, dy = Lx/Nx, Ly/Ny
dt = 0.001 # 时间步长
# 圆柱位置
xc, yc = 0.0, 0.0
R = D/2
# 创建网格
x = np.linspace(-8*D, 16*D, Nx)
y = np.linspace(-8*D, 8*D, Ny)
X, Y = np.meshgrid(x, y)
# 距离函数(到圆柱表面)
dist = np.sqrt((X - xc)**2 + (Y - yc)**2) - R
# 标记点
solid = dist < -0.5*dx # 固体内部
fluid = dist > 0.5*dx # 流体区域
boundary = (~solid) & (~fluid) # 边界附近
# 初始化流场
u = np.ones((Ny, Nx)) * U_inf # x方向速度
v = np.zeros((Ny, Nx)) # y方向速度
p = np.zeros((Ny, Nx)) # 压力
# 在固体内部设速度为零
u[solid] = 0
v[solid] = 0
# 时间推进
for n in range(10000):
# 保存旧速度
u_old = u.copy()
v_old = v.copy()
# 对流项(一阶迎风格式)
du_dx = np.zeros_like(u)
dv_dy = np.zeros_like(v)
# x方向对流
du_dx[:, 1:-1] = np.where(u[:, 1:-1] > 0,
(u[:, 1:-1] - u[:, :-2])/dx,
(u[:, 2:] - u[:, 1:-1])/dx)
# y方向对流
dv_dy[1:-1, :] = np.where(v[1:-1, :] > 0,
(v[1:-1, :] - v[:-2, :])/dy,
(v[2:, :] - v[1:-1, :])/dy)
# 扩散项
d2u = (u[:-2, 1:-1] - 2*u[1:-1, 1:-1] + u[2:, 1:-1])/dy**2 + \
(u[1:-1, :-2] - 2*u[1:-1, 1:-1] + u[1:-1, 2:])/dx**2
d2v = (v[:-2, 1:-1] - 2*v[1:-1, 1:-1] + v[2:, 1:-1])/dy**2 + \
(v[1:-1, :-2] - 2*v[1:-1, 1:-1] + v[1:-1, 2:])/dx**2
# 预测速度
u[1:-1, 1:-1] = u[1:-1, 1:-1] + dt*(-u[1:-1, 1:-1]*du_dx[1:-1, 1:-1] + nu*d2u)
v[1:-1, 1:-1] = v[1:-1, 1:-1] + dt*(-v[1:-1, 1:-1]*dv_dy[1:-1, 1:-1] + nu*d2v)
# IBM:直接力法
# 在边界点强制速度为零
u[boundary] = 0
v[boundary] = 0
u[solid] = 0
v[solid] = 0
# 出口边界条件(对流边界)
u[:, -1] = u[:, -2]
v[:, -1] = v[:, -2]
# 入口边界条件
u[:, 0] = U_inf
v[:, 0] = 0
# 上下边界(自由滑移)
u[0, :] = u[1, :]
u[-1, :] = u[-2, :]
v[0, :] = 0
v[-1, :] = 0
if n % 1000 == 0:
print(f"迭代 {n}, 最大速度: {np.max(u):.4f}")
# 计算涡量
omega = np.zeros((Ny, Nx))
omega[1:-1, 1:-1] = (v[1:-1, 2:] - v[1:-1, :-2])/(2*dx) - \
(u[2:, 1:-1] - u[:-2, 1:-1])/(2*dy)
# 绘图
fig, axes = plt.subplots(2, 2, figsize=(14, 12))
# 速度云图
ax1 = axes[0, 0]
vel_mag = np.sqrt(u**2 + v**2)
im1 = ax1.contourf(X, Y, vel_mag, levels=20, cmap='jet')
plt.colorbar(im1, ax=ax1, label='|u|')
circle = Circle((xc, yc), R, color='black', fill=True)
ax1.add_patch(circle)
ax1.set_xlabel('x/D', fontsize=11)
ax1.set_ylabel('y/D', fontsize=11)
ax1.set_title('Velocity Magnitude', fontsize=12, fontweight='bold')
ax1.set_aspect('equal')
# 涡量云图
ax2 = axes[0, 1]
im2 = ax2.contourf(X, Y, omega, levels=20, cmap='RdBu_r')
plt.colorbar(im2, ax=ax2, label='Vorticity')
circle = Circle((xc, yc), R, color='black', fill=True)
ax2.add_patch(circle)
ax2.set_xlabel('x/D', fontsize=11)
ax2.set_ylabel('y/D', fontsize=11)
ax2.set_title('Vorticity Field', fontsize=12, fontweight='bold')
ax2.set_aspect('equal')
# 速度矢量图
ax3 = axes[1, 0]
skip = 8
ax3.quiver(X[::skip, ::skip], Y[::skip, ::skip],
u[::skip, ::skip], v[::skip, ::skip], scale=20)
circle = Circle((xc, yc), R, color='black', fill=True)
ax3.add_patch(circle)
ax3.set_xlabel('x/D', fontsize=11)
ax3.set_ylabel('y/D', fontsize=11)
ax3.set_title('Velocity Vectors', fontsize=12, fontweight='bold')
ax3.set_aspect('equal')
# 压力系数分布
ax4 = axes[1, 1]
theta = np.linspace(0, 2*np.pi, 100)
x_cyl = xc + R*np.cos(theta)
y_cyl = yc + R*np.sin(theta)
# 从网格插值获取表面压力
from scipy.interpolate import griddata
points = np.column_stack((X.flatten(), Y.flatten()))
p_interp = griddata(points, p.flatten(), np.column_stack((x_cyl, y_cyl)), method='linear')
Cp = (p_interp - p[0, 0]) / (0.5*U_inf**2)
ax4.plot(theta*180/np.pi, Cp, 'b-', linewidth=2)
ax4.set_xlabel('Angle (deg)', fontsize=11)
ax4.set_ylabel('Pressure Coefficient Cp', fontsize=11)
ax4.set_title('Surface Pressure Distribution', fontsize=12, fontweight='bold')
ax4.grid(True, alpha=0.3)
ax4.set_xlim(0, 360)
plt.tight_layout()
plt.savefig('case1_cylinder_ibm.png', dpi=150, bbox_inches='tight')
plt.close()
print("✓ 案例1完成:静止圆柱绕流")
4.2 案例2:运动颗粒沉降
问题描述:
模拟圆形颗粒在流体中的沉降过程,考虑重力、浮力和流体阻力的作用。
物理模型:
颗粒运动方程:
MpdVpdt=(ρp−ρf)Vpg+FdragM_p \frac{d\mathbf{V}_p}{dt} = (\rho_p - \rho_f)V_p \mathbf{g} + \mathbf{F}_{drag}MpdtdVp=(ρp−ρf)Vpg+Fdrag
其中,Fdrag\mathbf{F}_{drag}Fdrag是通过IBM计算的流体作用力。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
import matplotlib
matplotlib.use('Agg')
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
# 参数设置
D = 0.5 # 颗粒直径
R = D/2
rho_f = 1.0 # 流体密度
rho_p = 1.5 # 颗粒密度
nu = 0.01 # 运动粘度
g = 9.8 # 重力加速度
# 计算域
Lx, Ly = 4.0, 6.0
Nx, Ny = 80, 120
dx, dy = Lx/Nx, Ly/Ny
dt = 0.001
# 网格
x = np.linspace(0, Lx, Nx)
y = np.linspace(0, Ly, Ny)
X, Y = np.meshgrid(x, y)
# 初始化颗粒位置
xp, yp = Lx/2, Ly - 2*D
Vp = np.array([0.0, 0.0]) # 颗粒速度
# 初始化流场
u = np.zeros((Ny, Nx))
v = np.zeros((Ny, Nx))
# 存储颗粒轨迹
xp_history = [xp]
yp_history = [yp]
Vp_history = [np.linalg.norm(Vp)]
# 时间推进
for n in range(15000):
# 计算距离函数
dist = np.sqrt((X - xp)**2 + (Y - yp)**2) - R
solid = dist < -0.5*dx
boundary = (dist >= -0.5*dx) & (dist <= 0.5*dx)
# 流体求解(简化,仅扩散项)
u[1:-1, 1:-1] = u[1:-1, 1:-1] + dt*nu*((u[:-2, 1:-1] - 2*u[1:-1, 1:-1] + u[2:, 1:-1])/dy**2 +
(u[1:-1, :-2] - 2*u[1:-1, 1:-1] + u[1:-1, 2:])/dx**2)
v[1:-1, 1:-1] = v[1:-1, 1:-1] + dt*nu*((v[:-2, 1:-1] - 2*v[1:-1, 1:-1] + v[2:, 1:-1])/dy**2 +
(v[1:-1, :-2] - 2*v[1:-1, 1:-1] + v[1:-1, 2:])/dx**2)
# IBM:强制边界速度等于颗粒速度
u[boundary] = Vp[0]
v[boundary] = Vp[1]
u[solid] = Vp[0]
v[solid] = Vp[1]
# 边界条件
u[:, 0] = 0
u[:, -1] = 0
v[:, 0] = 0
v[:, -1] = 0
u[0, :] = 0
u[-1, :] = 0
v[0, :] = 0
v[-1, :] = 0
# 计算流体对颗粒的作用力(通过边界上的应力积分)
# 简化为通过边界点速度计算阻力
F_drag = np.array([0.0, 0.0])
if np.any(boundary):
# 计算边界上的速度梯度(简化)
F_drag[1] = -6*np.pi*nu*R*(Vp[1] - np.mean(v[~solid])) # Stokes阻力近似
# 颗粒运动方程
M_p = rho_p * np.pi * R**2 # 颗粒质量(2D)
V_p = np.pi * R**2 # 颗粒体积
F_gravity = (rho_p - rho_f) * V_p * g
# 更新颗粒速度
Vp[1] = Vp[1] + dt/M_p * (F_gravity + F_drag[1])
# 更新颗粒位置
xp = xp + dt*Vp[0]
yp = yp + dt*Vp[1]
# 底部碰撞
if yp - R < 0:
yp = R
Vp[1] = -0.5*Vp[1] # 弹性碰撞
# 记录历史
if n % 100 == 0:
xp_history.append(xp)
yp_history.append(yp)
Vp_history.append(np.linalg.norm(Vp))
if n % 2000 == 0:
print(f"迭代 {n}, 颗粒位置: ({xp:.3f}, {yp:.3f}), 速度: {Vp[1]:.4f}")
# 绘图
fig, axes = plt.subplots(2, 2, figsize=(14, 12))
# 最终流场
ax1 = axes[0, 0]
vel_mag = np.sqrt(u**2 + v**2)
im1 = ax1.contourf(X, Y, vel_mag, levels=20, cmap='jet')
plt.colorbar(im1, ax=ax1, label='|u|')
circle = Circle((xp, yp), R, color='red', fill=True)
ax1.add_patch(circle)
ax1.set_xlabel('x', fontsize=11)
ax1.set_ylabel('y', fontsize=11)
ax1.set_title('Final Velocity Field', fontsize=12, fontweight='bold')
ax1.set_aspect('equal')
# 颗粒轨迹
ax2 = axes[0, 1]
ax2.plot(xp_history, yp_history, 'b-', linewidth=2)
ax2.scatter(xp_history[0], yp_history[0], color='green', s=100, label='Start', zorder=5)
ax2.scatter(xp_history[-1], yp_history[-1], color='red', s=100, label='End', zorder=5)
ax2.set_xlabel('x', fontsize=11)
ax2.set_ylabel('y', fontsize=11)
ax2.set_title('Particle Trajectory', fontsize=12, fontweight='bold')
ax2.legend(fontsize=10)
ax2.grid(True, alpha=0.3)
ax2.set_aspect('equal')
# 速度历史
ax3 = axes[1, 0]
ax3.plot(Vp_history, 'b-', linewidth=2)
ax3.set_xlabel('Time Step (/100)', fontsize=11)
ax3.set_ylabel('Particle Velocity', fontsize=11)
ax3.set_title('Velocity History', fontsize=12, fontweight='bold')
ax3.grid(True, alpha=0.3)
# 动画帧(多个时刻)
ax4 = axes[1, 1]
ax4.axis('off')
info_text = f"""
Sedimentation Simulation Results:
Physical Parameters:
- Particle diameter: {D}
- Fluid density: {rho_f}
- Particle density: {rho_p}
- Viscosity: {nu}
- Gravity: {g}
Numerical Setup:
- Domain: {Lx} x {Ly}
- Grid: {Nx} x {Ny}
- Time step: {dt}
Final State:
- Final position: ({xp:.3f}, {yp:.3f})
- Final velocity: {Vp[1]:.4f}
- Terminal velocity: {2*(rho_p-rho_f)*g*R**2/(9*nu):.4f} (Stokes)
IBM Features:
- Direct forcing method
- Moving boundary handling
- Fluid-particle coupling
"""
ax4.text(0.1, 0.5, info_text, transform=ax4.transAxes, fontsize=10,
verticalalignment='center', fontfamily='monospace',
bbox=dict(boxstyle='round', facecolor='lightblue', alpha=0.8))
plt.tight_layout()
plt.savefig('case2_sedimentation.png', dpi=150, bbox_inches='tight')
plt.close()
print("✓ 案例2完成:颗粒沉降")
4.3 案例3:柔性体变形
问题描述:
模拟弹性薄膜在流体中的变形,展示IBM处理柔性边界的能力。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
import matplotlib
matplotlib.use('Agg')
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
# 参数设置
Lx, Ly = 4.0, 2.0
Nx, Ny = 160, 80
dx, dy = Lx/Nx, Ly/Ny
dt = 0.0005
nu = 0.001 # 粘度
# 网格
x = np.linspace(0, Lx, Nx)
y = np.linspace(0, Ly, Ny)
X, Y = np.meshgrid(x, y)
# 初始化柔性体(初始为直线)
N_lag = 50 # 拉格朗日点数
X_lag = np.linspace(1.0, 3.0, N_lag)
Y_lag = np.ones(N_lag) * Ly/2
# 柔性体属性
sigma = 100.0 # 弹性系数
ds = (X_lag[-1] - X_lag[0]) / (N_lag - 1) # 拉格朗日点间距
# 初始化流场
u = np.ones((Ny, Nx)) * 0.5 # 均匀来流
v = np.zeros((Ny, Nx))
# 存储变形历史
lag_history = [(X_lag.copy(), Y_lag.copy())]
# 时间推进
for n in range(8000):
# 计算拉格朗日点上的速度(从欧拉网格插值)
U_lag = np.zeros(N_lag)
V_lag = np.zeros(N_lag)
for l in range(N_lag):
# 找到最近的网格点
i = int(X_lag[l] / dx)
j = int(Y_lag[l] / dy)
i = max(1, min(i, Nx-2))
j = max(1, min(j, Ny-2))
# 双线性插值
wx = (X_lag[l] - x[i]) / dx
wy = (Y_lag[l] - y[j]) / dy
U_lag[l] = (1-wx)*(1-wy)*u[j, i] + wx*(1-wy)*u[j, i+1] + \
(1-wx)*wy*u[j+1, i] + wx*wy*u[j+1, i+1]
V_lag[l] = (1-wx)*(1-wy)*v[j, i] + wx*(1-wy)*v[j, i+1] + \
(1-wx)*wy*v[j+1, i] + wx*wy*v[j+1, i+1]
# 计算弹性力(张力)
F_x = np.zeros(N_lag)
F_y = np.zeros(N_lag)
for l in range(1, N_lag-1):
# 计算局部曲率(简化)
F_x[l] = sigma * (X_lag[l-1] - 2*X_lag[l] + X_lag[l+1]) / ds**2
F_y[l] = sigma * (Y_lag[l-1] - 2*Y_lag[l] + Y_lag[l+1]) / ds**2
# 更新拉格朗日点位置
X_lag = X_lag + dt * U_lag
Y_lag = Y_lag + dt * V_lag
# 施加弹性力到欧拉网格(简化处理)
for l in range(N_lag):
i = int(X_lag[l] / dx)
j = int(Y_lag[l] / dy)
if 1 <= i < Nx-1 and 1 <= j < Ny-1:
u[j, i] = u[j, i] + dt * F_x[l] / dx / dy
v[j, i] = v[j, i] + dt * F_y[l] / dx / dy
# 流体求解(简化)
u[1:-1, 1:-1] = u[1:-1, 1:-1] + dt*nu*((u[:-2, 1:-1] - 2*u[1:-1, 1:-1] + u[2:, 1:-1])/dy**2 +
(u[1:-1, :-2] - 2*u[1:-1, 1:-1] + u[1:-1, 2:])/dx**2)
v[1:-1, 1:-1] = v[1:-1, 1:-1] + dt*nu*((v[:-2, 1:-1] - 2*v[1:-1, 1:-1] + v[2:, 1:-1])/dy**2 +
(v[1:-1, :-2] - 2*v[1:-1, 1:-1] + v[1:-1, 2:])/dx**2)
# 边界条件
u[:, 0] = 0.5
u[:, -1] = u[:, -2]
v[:, 0] = 0
v[:, -1] = v[:, -2]
u[0, :] = 0.5
u[-1, :] = 0.5
v[0, :] = 0
v[-1, :] = 0
# 记录历史
if n % 400 == 0:
lag_history.append((X_lag.copy(), Y_lag.copy()))
if n % 2000 == 0:
print(f"迭代 {n}, 最大位移: {np.max(np.abs(Y_lag - Ly/2)):.4f}")
# 绘图
fig, axes = plt.subplots(2, 2, figsize=(14, 12))
# 最终流场
ax1 = axes[0, 0]
vel_mag = np.sqrt(u**2 + v**2)
im1 = ax1.contourf(X, Y, vel_mag, levels=20, cmap='jet')
plt.colorbar(im1, ax=ax1, label='|u|')
ax1.plot(X_lag, Y_lag, 'r-', linewidth=3, label='Flexible Body')
ax1.scatter(X_lag, Y_lag, c='red', s=20)
ax1.set_xlabel('x', fontsize=11)
ax1.set_ylabel('y', fontsize=11)
ax1.set_title('Final Flow Field with Deformed Body', fontsize=12, fontweight='bold')
ax1.legend(fontsize=10)
# 变形历史
ax2 = axes[0, 1]
for i, (X_hist, Y_hist) in enumerate(lag_history[::5]):
alpha = 0.3 + 0.7 * i / len(lag_history[::5])
ax2.plot(X_hist, Y_hist, 'b-', alpha=alpha, linewidth=1.5)
ax2.plot(lag_history[0][0], lag_history[0][1], 'g-', linewidth=3, label='Initial')
ax2.plot(X_lag, Y_lag, 'r-', linewidth=3, label='Final')
ax2.set_xlabel('x', fontsize=11)
ax2.set_ylabel('y', fontsize=11)
ax2.set_title('Deformation History', fontsize=12, fontweight='bold')
ax2.legend(fontsize=10)
ax2.grid(True, alpha=0.3)
# 位移分布
ax3 = axes[1, 0]
displacement = Y_lag - Ly/2
ax3.plot(X_lag, displacement, 'b-', linewidth=2)
ax3.axhline(y=0, color='k', linestyle='--', alpha=0.3)
ax3.set_xlabel('x', fontsize=11)
ax3.set_ylabel('Vertical Displacement', fontsize=11)
ax3.set_title('Final Displacement Distribution', fontsize=12, fontweight='bold')
ax3.grid(True, alpha=0.3)
# 信息
ax4 = axes[1, 1]
ax4.axis('off')
info_text = f"""
Flexible Body Simulation Results:
Physical Parameters:
- Domain: {Lx} x {Ly}
- Viscosity: {nu}
- Elastic coefficient: {sigma}
- Inflow velocity: 0.5
Numerical Setup:
- Grid: {Nx} x {Ny}
- Lagrangian points: {N_lag}
- Time step: {dt}
Results:
- Max displacement: {np.max(np.abs(displacement)):.4f}
- Final shape: Deformed by flow
IBM Method:
- Continuous forcing approach
- Elastic tension model
- Two-way coupling
"""
ax4.text(0.1, 0.5, info_text, transform=ax4.transAxes, fontsize=10,
verticalalignment='center', fontfamily='monospace',
bbox=dict(boxstyle='round', facecolor='lightyellow', alpha=0.8))
plt.tight_layout()
plt.savefig('case3_flexible_body.png', dpi=150, bbox_inches='tight')
plt.close()
print("✓ 案例3完成:柔性体变形")
4.4 案例4:多颗粒相互作用
问题描述:
模拟多个颗粒在流体中的运动和相互作用,展示IBM处理多体问题的能力。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
import matplotlib
matplotlib.use('Agg')
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
# 参数设置
Lx, Ly = 3.0, 4.0
Nx, Ny = 60, 80
dx, dy = Lx/Nx, Ly/Ny
dt = 0.001
nu = 0.02
rho_f = 1.0
rho_p = 2.0
g = 9.8
# 网格
x = np.linspace(0, Lx, Nx)
y = np.linspace(0, Ly, Ny)
X, Y = np.meshgrid(x, y)
# 初始化多个颗粒
N_particles = 4
R = 0.15 # 颗粒半径
particles = []
for i in range(N_particles):
particles.append({
'x': Lx/2 + (i - 1.5) * 0.5,
'y': Ly - 0.5 - i * 0.3,
'vx': 0.0,
'vy': 0.0,
'R': R
})
# 初始化流场
u = np.zeros((Ny, Nx))
v = np.zeros((Ny, Nx))
# 存储历史
history = [[] for _ in range(N_particles)]
for i, p in enumerate(particles):
history[i].append((p['x'], p['y']))
# 时间推进
for n in range(10000):
# 标记所有固体区域
solid = np.zeros((Ny, Nx), dtype=bool)
boundary = np.zeros((Ny, Nx), dtype=bool)
for p in particles:
dist = np.sqrt((X - p['x'])**2 + (Y - p['y'])**2) - p['R']
solid |= dist < -0.5*dx
boundary |= (dist >= -0.5*dx) & (dist <= 0.5*dx)
# 流体求解(简化)
u[1:-1, 1:-1] = u[1:-1, 1:-1] + dt*nu*((u[:-2, 1:-1] - 2*u[1:-1, 1:-1] + u[2:, 1:-1])/dy**2 +
(u[1:-1, :-2] - 2*u[1:-1, 1:-1] + u[1:-1, 2:])/dx**2)
v[1:-1, 1:-1] = v[1:-1, 1:-1] + dt*nu*((v[:-2, 1:-1] - 2*v[1:-1, 1:-1] + v[2:, 1:-1])/dy**2 +
(v[1:-1, :-2] - 2*v[1:-1, 1:-1] + v[1:-1, 2:])/dx**2)
# IBM:施加颗粒速度
for p in particles:
dist = np.sqrt((X - p['x'])**2 + (Y - p['y'])**2) - p['R']
p_solid = dist < -0.5*dx
p_boundary = (dist >= -0.5*dx) & (dist <= 0.5*dx)
u[p_boundary] = p['vx']
v[p_boundary] = p['vy']
u[p_solid] = p['vx']
v[p_solid] = p['vy']
# 边界条件
u[:, 0] = 0
u[:, -1] = 0
v[:, 0] = 0
v[:, -1] = 0
u[0, :] = 0
u[-1, :] = 0
v[0, :] = 0
v[-1, :] = 0
# 更新颗粒运动
for i, p in enumerate(particles):
# 计算受力(简化)
M_p = rho_p * np.pi * p['R']**2
V_p = np.pi * p['R']**2
F_gravity = (rho_p - rho_f) * V_p * g
# 简单的阻力模型
F_drag_y = -6*np.pi*nu*p['R']*p['vy']
F_drag_x = -6*np.pi*nu*p['R']*p['vx']
# 简单的颗粒间碰撞检测
for j, p2 in enumerate(particles):
if i != j:
dist_p = np.sqrt((p['x'] - p2['x'])**2 + (p['y'] - p2['y'])**2)
if dist_p < p['R'] + p2['R']:
# 简单的排斥力
nx = (p['x'] - p2['x']) / dist_p
ny = (p['y'] - p2['y']) / dist_p
F_rep = 10.0 * (p['R'] + p2['R'] - dist_p)
F_drag_x += F_rep * nx
F_drag_y += F_rep * ny
# 更新速度
p['vx'] = p['vx'] + dt/M_p * F_drag_x
p['vy'] = p['vy'] + dt/M_p * (F_gravity + F_drag_y)
# 更新位置
p['x'] = p['x'] + dt*p['vx']
p['y'] = p['y'] + dt*p['vy']
# 边界碰撞
if p['x'] - p['R'] < 0:
p['x'] = p['R']
p['vx'] = -0.5*p['vx']
if p['x'] + p['R'] > Lx:
p['x'] = Lx - p['R']
p['vx'] = -0.5*p['vx']
if p['y'] - p['R'] < 0:
p['y'] = p['R']
p['vy'] = -0.5*p['vy']
# 记录历史
if n % 50 == 0:
history[i].append((p['x'], p['y']))
if n % 2000 == 0:
print(f"迭代 {n}, 颗粒高度: {[f'{p[\"y\"]:.2f}' for p in particles]}")
# 绘图
fig, axes = plt.subplots(2, 2, figsize=(14, 12))
# 最终流场和颗粒
ax1 = axes[0, 0]
vel_mag = np.sqrt(u**2 + v**2)
im1 = ax1.contourf(X, Y, vel_mag, levels=20, cmap='jet')
plt.colorbar(im1, ax=ax1, label='|u|')
for p in particles:
circle = Circle((p['x'], p['y']), p['R'], color='red', fill=True)
ax1.add_patch(circle)
ax1.set_xlabel('x', fontsize=11)
ax1.set_ylabel('y', fontsize=11)
ax1.set_title('Multi-Particle Flow Field', fontsize=12, fontweight='bold')
ax1.set_aspect('equal')
# 颗粒轨迹
ax2 = axes[0, 1]
colors = ['red', 'blue', 'green', 'purple']
for i, (p, h) in enumerate(zip(particles, history)):
traj = np.array(h)
ax2.plot(traj[:, 0], traj[:, 1], color=colors[i], linewidth=2, label=f'Particle {i+1}')
ax2.scatter(traj[0, 0], traj[0, 1], color=colors[i], s=100, marker='o', zorder=5)
ax2.scatter(traj[-1, 0], traj[-1, 1], color=colors[i], s=100, marker='s', zorder=5)
ax2.set_xlabel('x', fontsize=11)
ax2.set_ylabel('y', fontsize=11)
ax2.set_title('Particle Trajectories', fontsize=12, fontweight='bold')
ax2.legend(fontsize=10)
ax2.grid(True, alpha=0.3)
ax2.set_aspect('equal')
# 高度历史
ax3 = axes[1, 0]
for i, h in enumerate(history):
traj = np.array(h)
ax3.plot(traj[:, 1], color=colors[i], linewidth=2, label=f'Particle {i+1}')
ax3.set_xlabel('Time Step (/50)', fontsize=11)
ax3.set_ylabel('Height y', fontsize=11)
ax3.set_title('Height History', fontsize=12, fontweight='bold')
ax3.legend(fontsize=10)
ax3.grid(True, alpha=0.3)
# 信息
ax4 = axes[1, 1]
ax4.axis('off')
info_text = f"""
Multi-Particle Simulation Results:
Physical Parameters:
- Domain: {Lx} x {Ly}
- Particles: {N_particles}
- Particle radius: {R}
- Fluid density: {rho_f}
- Particle density: {rho_p}
- Viscosity: {nu}
Numerical Setup:
- Grid: {Nx} x {Ny}
- Time step: {dt}
- Iterations: 10000
IBM Features:
- Multiple moving boundaries
- Particle-particle interaction
- Fluid-particle coupling
- Collision handling
Final Positions:
"""
for i, p in enumerate(particles):
info_text += f" Particle {i+1}: ({p['x']:.3f}, {p['y']:.3f})\n"
ax4.text(0.1, 0.5, info_text, transform=ax4.transAxes, fontsize=10,
verticalalignment='center', fontfamily='monospace',
bbox=dict(boxstyle='round', facecolor='lightgreen', alpha=0.8))
plt.tight_layout()
plt.savefig('case4_multi_particle.png', dpi=150, bbox_inches='tight')
plt.close()
print("✓ 案例4完成:多颗粒相互作用")
4.5 案例5:带换热的IBM
问题描述:
模拟圆柱绕流中的对流换热问题,圆柱表面保持恒定温度,流体被加热。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
import matplotlib
matplotlib.use('Agg')
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
# 参数设置
D = 1.0
R = D/2
Re = 100
Pr = 0.7
U_inf = 1.0
nu = U_inf * D / Re
alpha = nu / Pr
T_cylinder = 1.0 # 圆柱温度
T_inlet = 0.0 # 入口温度
# 计算域
Lx, Ly = 16*D, 10*D
Nx, Ny = 160, 100
dx, dy = Lx/Nx, Ly/Ny
dt = 0.001
# 网格
x = np.linspace(-4*D, 12*D, Nx)
y = np.linspace(-5*D, 5*D, Ny)
X, Y = np.meshgrid(x, y)
# 圆柱位置
xc, yc = 0.0, 0.0
# 距离函数
dist = np.sqrt((X - xc)**2 + (Y - yc)**2) - R
solid = dist < -0.5*dx
fluid = dist > 0.5*dx
boundary = (~solid) & (~fluid)
# 初始化
u = np.ones((Ny, Nx)) * U_inf
v = np.zeros((Ny, Nx))
T = np.ones((Ny, Nx)) * T_inlet
# 时间推进
for n in range(8000):
# 流体求解(简化)
u_old = u.copy()
v_old = v.copy()
# 扩散项
d2u = (u[:-2, 1:-1] - 2*u[1:-1, 1:-1] + u[2:, 1:-1])/dy**2 + \
(u[1:-1, :-2] - 2*u[1:-1, 1:-1] + u[1:-1, 2:])/dx**2
d2v = (v[:-2, 1:-1] - 2*v[1:-1, 1:-1] + v[2:, 1:-1])/dy**2 + \
(v[1:-1, :-2] - 2*v[1:-1, 1:-1] + v[1:-1, 2:])/dx**2
u[1:-1, 1:-1] = u[1:-1, 1:-1] + dt*nu*d2u
v[1:-1, 1:-1] = v[1:-1, 1:-1] + dt*nu*d2v
# IBM速度边界
u[boundary] = 0
v[boundary] = 0
u[solid] = 0
v[solid] = 0
# 温度求解
d2T = (T[:-2, 1:-1] - 2*T[1:-1, 1:-1] + T[2:, 1:-1])/dy**2 + \
(T[1:-1, :-2] - 2*T[1:-1, 1:-1] + T[1:-1, 2:])/dx**2
# 对流项(简化)
T[1:-1, 1:-1] = T[1:-1, 1:-1] + dt*(alpha*d2T - u[1:-1, 1:-1]*(T[1:-1, 2:] - T[1:-1, :-2])/(2*dx))
# IBM温度边界
T[boundary] = T_cylinder
T[solid] = T_cylinder
# 边界条件
u[:, 0] = U_inf
v[:, 0] = 0
T[:, 0] = T_inlet
u[:, -1] = u[:, -2]
v[:, -1] = v[:, -2]
T[:, -1] = T[:, -2]
u[0, :] = u[1, :]
u[-1, :] = u[-2, :]
v[0, :] = 0
v[-1, :] = 0
T[0, :] = T[1, :]
T[-1, :] = T[-2, :]
if n % 1000 == 0:
print(f"迭代 {n}, 最大温度: {np.max(T):.4f}")
# 计算努塞尔数
# 通过圆柱表面的温度梯度计算热流
q_wall = []
theta = np.linspace(0, 2*np.pi, 100)
for th in theta:
x_surf = xc + (R + dx)*np.cos(th)
y_surf = yc + (R + dx)*np.sin(th)
i = int((x_surf - x[0])/dx)
j = int((y_surf - y[0])/dy)
i = max(1, min(i, Nx-2))
j = max(1, min(j, Ny-2))
# 计算法向温度梯度
dx_norm = (T[j, min(i+1, Nx-1)] - T[j, max(i-1, 0)])/(2*dx)
dy_norm = (T[min(j+1, Ny-1), i] - T[max(j-1, 0), i])/(2*dy)
q_wall.append(dx_norm*np.cos(th) + dy_norm*np.sin(th))
q_wall = np.array(q_wall)
Nu_local = q_wall * D / (T_cylinder - T_inlet)
Nu_avg = np.mean(Nu_local)
# 绘图
fig, axes = plt.subplots(2, 2, figsize=(14, 12))
# 温度云图
ax1 = axes[0, 0]
im1 = ax1.contourf(X, Y, T, levels=20, cmap='hot')
plt.colorbar(im1, ax=ax1, label='Temperature')
circle = Circle((xc, yc), R, color='blue', fill=True)
ax1.add_patch(circle)
ax1.set_xlabel('x/D', fontsize=11)
ax1.set_ylabel('y/D', fontsize=11)
ax1.set_title('Temperature Field', fontsize=12, fontweight='bold')
ax1.set_aspect('equal')
# 速度云图
ax2 = axes[0, 1]
vel_mag = np.sqrt(u**2 + v**2)
im2 = ax2.contourf(X, Y, vel_mag, levels=20, cmap='jet')
plt.colorbar(im2, ax=ax2, label='|u|')
circle = Circle((xc, yc), R, color='black', fill=True)
ax2.add_patch(circle)
ax2.set_xlabel('x/D', fontsize=11)
ax2.set_ylabel('y/D', fontsize=11)
ax2.set_title('Velocity Field', fontsize=12, fontweight='bold')
ax2.set_aspect('equal')
# 局部努塞尔数分布
ax3 = axes[1, 0]
ax3.plot(theta*180/np.pi, Nu_local, 'b-', linewidth=2)
ax3.set_xlabel('Angle (deg)', fontsize=11)
ax3.set_ylabel('Local Nusselt Number', fontsize=11)
ax3.set_title(f'Local Nu Distribution (Avg Nu = {Nu_avg:.2f})', fontsize=12, fontweight='bold')
ax3.grid(True, alpha=0.3)
ax3.set_xlim(0, 360)
# 等温线
ax4 = axes[1, 1]
contours = ax4.contour(X, Y, T, levels=15, colors='black', linewidths=0.5)
ax4.clabel(contours, inline=True, fontsize=8)
im4 = ax4.contourf(X, Y, T, levels=15, cmap='hot', alpha=0.7)
plt.colorbar(im4, ax=ax4, label='Temperature')
circle = Circle((xc, yc), R, color='blue', fill=True)
ax4.add_patch(circle)
ax4.set_xlabel('x/D', fontsize=11)
ax4.set_ylabel('y/D', fontsize=11)
ax4.set_title('Isotherms', fontsize=12, fontweight='bold')
ax4.set_aspect('equal')
plt.tight_layout()
plt.savefig('case5_heat_transfer_ibm.png', dpi=150, bbox_inches='tight')
plt.close()
print(f"✓ 案例5完成:带换热的IBM,平均努塞尔数: {Nu_avg:.2f}")
5. IBM的扩展与应用
5.1 高阶IBM方法
幽灵点法(Ghost Cell Method):
在固体内部设置幽灵点,通过镜像或外推计算幽灵点速度,使得边界插值精确满足无滑移条件。这种方法比直接力法精度更高。
镜像点法(Mirror Point Method):
对于每个边界点,在流场中找到其镜像点,通过镜像点的速度外推计算边界速度。
5.2 IBM与LBM的结合
IBM与格子玻尔兹曼方法(LBM)天然契合,因为LBM使用笛卡尔网格,易于实现IBM的力源项。
LBM-IBM算法:
- 执行LBM的碰撞步
- 计算边界力并修正分布函数
- 执行LBM的迁移步
- 更新边界位置
5.3 三维IBM
三维IBM的原理与二维相同,但计算量大幅增加。主要挑战包括:
- 距离函数计算更复杂
- 力投影需要3D狄拉克函数
- 可视化更加困难
5.4 工程应用
生物流体力学:
- 心脏瓣膜开合
- 血液细胞变形
- 精子游动
多相流:
- 气泡上升
- 颗粒沉降
- 液滴撞击
流固耦合:
- 柔性管道流动
- 旗帜飘动
- 降落伞展开
6. IBM的优缺点与展望
6.1 优点
- 网格生成简单:使用笛卡尔网格,无需复杂的前处理
- 适合运动边界:天然处理边界运动和大变形
- 计算效率高:结构化网格利于并行计算
- 易于实现:算法相对简单,编程复杂度低
6.2 缺点
- 边界精度有限:边界附近精度通常为1阶
- 小尺度流动困难:难以精确捕捉边界层细节
- 质量守恒挑战:某些IBM变体存在质量守恒问题
- 时间步长限制:边界运动可能需要较小的时间步长
6.3 发展趋势
自适应IBM:
结合自适应网格细化(AMR),在边界附近加密网格以提高精度。
高阶IBM:
发展高阶幽灵点法和插值方法,提高边界精度至2阶或更高。
机器学习辅助IBM:
利用神经网络预测边界力或优化IBM参数。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)