第四章:欧拉视角(1)
本章我们将从拉格朗日视角转向欧拉视角,探讨基于网格的流体仿真方法。欧拉方法在处理大变形流动、自由表面追踪和不可压缩性约束方面具有独特优势。我们将深入理解Navier-Stokes方程的数值求解,掌握压力投影法的核心思想,并学习如何处理各种边界条件。
4.1 欧拉vs拉格朗日描述
4.1.1 物质导数与对流项
在流体力学中,我们需要追踪流体质点的物理量变化。欧拉视角下,我们在固定的空间位置观察流体,这导致了物质导数的出现:
$$\frac{D}{Dt} = \frac{\partial}{\partial t} + \mathbf{u} \cdot \nabla$$ 其中第一项 $\frac{\partial}{\partial t}$ 是局部时间导数,表示固定位置处物理量的时间变化率;第二项 $\mathbf{u} \cdot \nabla$ 是对流项,表示由于流体运动导致的物理量变化。
物质导数的物理意义:考虑一个流体质点沿轨迹 $\mathbf{x}(t)$ 运动,其携带的物理量 $\phi$ 的变化率为: $$\frac{d\phi}{dt} = \frac{\partial \phi}{\partial t} + \frac{\partial \phi}{\partial x_i}\frac{dx_i}{dt} = \frac{\partial \phi}{\partial t} + u_i\frac{\partial \phi}{\partial x_i}$$ 这里使用了爱因斯坦求和约定。物质导数连接了拉格朗日描述(跟随质点)和欧拉描述(固定空间点)。
例如,对于速度场的物质导数: $$\frac{D\mathbf{u}}{Dt} = \frac{\partial \mathbf{u}}{\partial t} + (\mathbf{u} \cdot \nabla)\mathbf{u}$$ 展开对流项的分量形式(以2D为例): $$(\mathbf{u} \cdot \nabla)\mathbf{u} = \begin{pmatrix} u\frac{\partial u}{\partial x} + v\frac{\partial u}{\partial y} \\ u\frac{\partial v}{\partial x} + v\frac{\partial v}{\partial y} \end{pmatrix}$$ 这个对流项 $(\mathbf{u} \cdot \nabla)\mathbf{u}$ 是非线性的,也是Navier-Stokes方程数值求解的主要挑战之一。它导致了湍流等复杂现象的产生。
对流项的另一种理解:使用向量恒等式,对流项可以改写为: $$(\mathbf{u} \cdot \nabla)\mathbf{u} = \nabla(\frac{|\mathbf{u}|^2}{2}) - \mathbf{u} \times (\nabla \times \mathbf{u})$$ 第一项是动能梯度,第二项包含涡量 $\boldsymbol{\omega} = \nabla \times \mathbf{u}$,体现了旋转效应对动量输送的影响。
4.1.2 不可压Navier-Stokes方程
不可压缩流体的运动由Navier-Stokes方程描述: $$\rho \frac{D\mathbf{u}}{Dt} = -\nabla p + \mu \nabla^2 \mathbf{u} + \rho \mathbf{g}$$ 配合不可压缩条件: $$\nabla \cdot \mathbf{u} = 0$$ 其中:
- $\rho$ 是流体密度(对于不可压流体为常数)
- $p$ 是压力(实际上是压力除以密度,有压力势的量纲)
- $\mu$ 是动力粘度
- $\nu = \mu/\rho$ 是运动粘度
- $\mathbf{g}$ 是重力加速度
展开物质导数后,动量方程变为: $$\rho \left(\frac{\partial \mathbf{u}}{\partial t} + (\mathbf{u} \cdot \nabla)\mathbf{u}\right) = -\nabla p + \mu \nabla^2 \mathbf{u} + \rho \mathbf{g}$$ 无量纲化与Reynolds数:通过特征尺度 $L$、特征速度 $U$ 和特征时间 $L/U$ 无量纲化,可得: $$\frac{\partial \mathbf{u}^*}{\partial t^*} + (\mathbf{u}^* \cdot \nabla^*)\mathbf{u}^* = -\nabla^* p^* + \frac{1}{Re} \nabla^{*2} \mathbf{u}^* + \frac{1}{Fr^2}\hat{\mathbf{g}}$$ 其中 Reynolds数 $Re = \frac{UL}{\nu}$ 表征惯性力与粘性力之比,Froude数 $Fr = \frac{U}{\sqrt{gL}}$ 表征惯性力与重力之比。
压力的作用:在不可压缩流中,压力不是状态变量,而是一个拉格朗日乘子,用于强制满足不可压缩约束。压力瞬时调整以维持 $\nabla \cdot \mathbf{u} = 0$。
涡量形式:取动量方程的旋度,可得涡量输送方程: $$\frac{D\boldsymbol{\omega}}{Dt} = (\boldsymbol{\omega} \cdot \nabla)\mathbf{u} + \nu \nabla^2 \boldsymbol{\omega}$$ 在2D情况下,涡量拉伸项 $(\boldsymbol{\omega} \cdot \nabla)\mathbf{u}$ 消失,涡量仅通过对流和扩散演化。
4.1.3 算子分裂方法
直接求解耦合的速度-压力系统非常困难。Chorin和Temam提出的算子分裂方法将Navier-Stokes方程分解为几个子步骤:
- 对流步:求解 $\frac{\partial \mathbf{u}}{\partial t} + (\mathbf{u} \cdot \nabla)\mathbf{u} = 0$
- 外力步:添加重力和粘性力 $\frac{\partial \mathbf{u}}{\partial t} = \nu \nabla^2 \mathbf{u} + \mathbf{g}$
- 投影步:通过压力投影使速度场满足不可压缩条件
数学基础:算子分裂基于Trotter-Lie公式。对于方程 $\frac{\partial u}{\partial t} = (A + B)u$,其解可以近似为: $$u(t + \Delta t) \approx e^{\Delta t B} e^{\Delta t A} u(t) + O(\Delta t^2)$$ 这是一阶分裂。二阶Strang分裂为: $$u(t + \Delta t) \approx e^{\Delta t B/2} e^{\Delta t A} e^{\Delta t B/2} u(t) + O(\Delta t^3)$$ 具体算法步骤:
-
预测步(忽略压力): $$\mathbf{u}^* = \mathbf{u}^n + \Delta t \left[ -(\mathbf{u}^n \cdot \nabla)\mathbf{u}^n + \nu \nabla^2 \mathbf{u}^n + \mathbf{g} \right]$$
-
压力求解: $$\nabla^2 p^{n+1} = \frac{\rho}{\Delta t} \nabla \cdot \mathbf{u}^*$$
-
速度修正: $$\mathbf{u}^{n+1} = \mathbf{u}^* - \frac{\Delta t}{\rho} \nabla p^{n+1}$$ 这种分裂使得每个子问题都可以高效求解。在Taichi中,典型的实现框架如下:
@ti.kernel
def step():
advect_velocity() # 对流步
apply_external_forces() # 外力步
project_velocity() # 投影步
advect_other_quantities() # 输送其他物理量
分裂误差分析:算子分裂引入的误差主要来自于忽略了各项之间的耦合。例如,压力梯度实际上会影响对流,但在分裂方法中这种影响被延迟到投影步。这种误差通常是 $O(\Delta t)$,但可以通过高阶分裂方案减小。
4.1.4 稳定性与CFL条件
显式时间积分的稳定性受CFL(Courant-Friedrichs-Lewy)条件限制: $$\Delta t \leq C \frac{\Delta x}{|\mathbf{u}|_{\max}}$$ 其中 $C$ 是CFL数,通常取0.5-1.0。这个条件确保在一个时间步内,流体粒子移动的距离不超过一个网格单元。
CFL条件的推导:考虑一维线性对流方程 $\frac{\partial u}{\partial t} + c\frac{\partial u}{\partial x} = 0$,使用前向时间、中心空间差分: $$\frac{u_i^{n+1} - u_i^n}{\Delta t} + c\frac{u_{i+1}^n - u_{i-1}^n}{2\Delta x} = 0$$ 通过von Neumann稳定性分析,代入 $u_i^n = \hat{u}^n e^{ik_xi\Delta x}$,可得放大因子: $$G = 1 - i\nu \sin(k\Delta x)$$ 其中 $\nu = c\Delta t/\Delta x$ 是CFL数。稳定性要求 $|G| \leq 1$,但对于中心差分这永远无法满足。使用迎风格式可得稳定条件 $\nu \leq 1$。
对于粘性项,如果使用显式积分,还需要满足扩散稳定性条件: $$\Delta t \leq \frac{(\Delta x)^2}{2d\nu}$$ 其中 $d$ 是空间维度。由于这个条件对小网格非常严格($\Delta t \propto (\Delta x)^2$),粘性项通常使用隐式或半隐式方法处理。
多物理场的综合稳定性条件:
-
对流CFL:$\Delta t_{adv} = C_{adv} \frac{\Delta x}{|\mathbf{u}|_{\max}}$
-
粘性稳定性:$\Delta t_{visc} = C_{visc} \frac{(\Delta x)^2}{\nu}$
-
表面张力(如果存在):$\Delta t_{\sigma} = C_{\sigma} \sqrt{\frac{\rho (\Delta x)^3}{\sigma}}$
-
重力波(自由表面):$\Delta t_{grav} = C_{grav} \sqrt{\frac{\Delta x}{g}}$
最终时间步长:$\Delta t = \min(\Delta t_{adv}, \Delta t_{visc}, \Delta t_{\sigma}, \Delta t_{grav})$
其中 $C_{adv} \approx 0.5$, $C_{visc} \approx 0.25$, $C_{\sigma} \approx 0.5$, $C_{grav} \approx 0.5$ 是安全系数。
4.2 网格类型与离散化
4.2.1 同位网格vs交错网格(Staggered Grid)
同位网格(Collocated Grid):
- 所有物理量(速度分量、压力等)存储在网格单元中心
- 实现简单,但可能产生棋盘格压力振荡
- 需要特殊处理来避免数值不稳定
棋盘格问题的根源:在同位网格上,压力梯度使用中心差分: $$\left(\frac{\partial p}{\partial x}\right)_i = \frac{p_{i+1} - p_{i-1}}{2\Delta x}$$ 这个离散算子无法感知棋盘格模式 $p_i = (-1)^i$,因为 $p_{i+1} - p_{i-1} = 0$。这导致压力泊松方程的零空间包含非物理的高频模式。
交错网格(MAC Grid):
- 压力存储在单元中心
- 速度分量存储在对应的单元面中心
- $u$ 存储在 $(i+1/2, j)$ 位置
- $v$ 存储在 $(i, j+1/2)$ 位置
MAC网格的布局:
v(i,j+1/2)
|
u(i-1/2,j)---p(i,j)---u(i+1/2,j)
|
v(i,j-1/2)
MAC网格的数学优势:
-
紧凑的梯度-散度对:散度和梯度算子互为负转置: $$\langle \nabla \cdot \mathbf{u}, p \rangle = -\langle \mathbf{u}, \nabla p \rangle$$ 这保证了离散系统的能量守恒性。
-
自然的通量计算:速度直接定义在单元界面上,便于计算质量通量。
-
最优的inf-sup条件:MAC离散满足离散inf-sup(LBB)条件,保证了压力的唯一性。
实现细节:在Taichi中,MAC网格的索引约定:
# 压力和标量场:单元中心
pressure = ti.field(float, shape=(nx, ny))
# u速度:垂直面中心
u = ti.field(float, shape=(nx+1, ny))
# v速度:水平面中心
v = ti.field(float, shape=(nx, ny+1))
4.2.2 MAC网格的优势
- 自然满足散度定理:在单元边界上直接计算通量
- 避免压力振荡:压力和速度的耦合更紧密
- 边界条件处理简单:速度分量直接位于边界上
- 守恒性好:离散后仍保持质量、动量守恒
离散散度算子在MAC网格上特别简洁: $$(\nabla \cdot \mathbf{u})_{i,j} = \frac{u_{i+1/2,j} - u_{i-1/2,j}}{\Delta x} + \frac{v_{i,j+1/2} - v_{i,j-1/2}}{\Delta y}$$
4.2.3 双线性插值
由于速度分量存储在不同位置,经常需要插值获取任意位置的速度。对于双线性插值: $$f(x,y) = f_{00}(1-\alpha)(1-\beta) + f_{10}\alpha(1-\beta) + f_{01}(1-\alpha)\beta + f_{11}\alpha\beta$$ 其中 $\alpha = (x - x_0)/\Delta x$,$\beta = (y - y_0)/\Delta y$ 是局部坐标。
在Taichi中实现双线性插值:
@ti.func
def bilinear_interp(field: ti.template(), x: float, y: float) -> float:
i, j = int(x), int(y)
fx, fy = x - i, y - j
return (field[i,j] * (1-fx) * (1-fy) +
field[i+1,j] * fx * (1-fy) +
field[i,j+1] * (1-fx) * fy +
field[i+1,j+1] * fx * fy)
4.2.4 散度与梯度算子离散
散度算子(用于不可压缩约束): $$(\nabla \cdot \mathbf{u})_{i,j} = \frac{u_{i+1/2,j} - u_{i-1/2,j}}{\Delta x} + \frac{v_{i,j+1/2} - v_{i,j-1/2}}{\Delta y}$$ 梯度算子(用于压力梯度): $$(\nabla p)_x|_{i+1/2,j} = \frac{p_{i+1,j} - p_{i,j}}{\Delta x}$$ $$(\nabla p)_y|_{i,j+1/2} = \frac{p_{i,j+1} - p_{i,j}}{\Delta y}$$ Laplace算子(用于粘性项和压力泊松方程): $$(\nabla^2 p)_{i,j} = \frac{p_{i+1,j} - 2p_{i,j} + p_{i-1,j}}{(\Delta x)^2} + \frac{p_{i,j+1} - 2p_{i,j} + p_{i,j-1}}{(\Delta y)^2}$$
4.3 半拉格朗日输送(Semi-Lagrangian Advection)
4.3.1 反向粒子追踪
半拉格朗日方法结合了拉格朗日和欧拉方法的优点。基本思想是:对于网格点 $\mathbf{x}$,追踪到达该点的流体粒子在上一时刻的位置 $\mathbf{x}_{prev}$: $$\mathbf{x}_{prev} = \mathbf{x} - \Delta t \cdot \mathbf{u}(\mathbf{x})$$ 然后通过插值获取该位置的物理量作为新时刻的值: $$q^{n+1}(\mathbf{x}) = q^n(\mathbf{x}_{prev})$$ 这种方法无条件稳定,允许使用大时间步长,但会引入数值耗散。
4.3.2 速度插值策略
简单的一阶精度追踪使用当前位置的速度,但可以通过Runge-Kutta方法提高精度:
RK2(中点法):
@ti.func
def backtrace_rk2(x: ti.math.vec2, dt: float) -> ti.math.vec2:
v1 = sample_velocity(x)
x_mid = x - 0.5 * dt * v1
v2 = sample_velocity(x_mid)
return x - dt * v2
RK3:
@ti.func
def backtrace_rk3(x: ti.math.vec2, dt: float) -> ti.math.vec2:
v1 = sample_velocity(x)
x1 = x - 0.5 * dt * v1
v2 = sample_velocity(x1)
x2 = x - 0.75 * dt * v2
v3 = sample_velocity(x2)
return x - dt * (2.0/9.0 * v1 + 1.0/3.0 * v2 + 4.0/9.0 * v3)
4.3.3 数值耗散问题
半拉格朗日方法的主要缺点是数值耗散,表现为:
- 涡旋快速衰减
- 细节特征模糊
- 能量不守恒
数值耗散的根源是插值过程中的平滑效应。每次插值相当于应用了一个低通滤波器,多次输送后高频信息逐渐丢失。
4.3.4 单调性保持
为了避免产生非物理的新极值,可以使用限制器:
@ti.func
def clamp_extrema(q_new: float, x: ti.math.vec2) -> float:
# 获取周围网格点的最大最小值
q_min = ti.math.inf
q_max = -ti.math.inf
for di in range(-1, 2):
for dj in range(-1, 2):
q_neighbor = q_field[int(x[0])+di, int(x[1])+dj]
q_min = min(q_min, q_neighbor)
q_max = max(q_max, q_neighbor)
return clamp(q_new, q_min, q_max)
4.4 高阶输送格式
4.4.1 MacCormack方法
MacCormack方法通过前向和后向输送估计误差并补偿:
- 前向输送:$\phi^* = \text{SemiLagrangian}(\phi^n, \Delta t)$
- 后向输送:$\phi^{**} = \text{SemiLagrangian}(\phi^*, -\Delta t)$
- 误差估计:$e = \frac{1}{2}(\phi^n - \phi^{**})$
- 修正结果:$\phi^{n+1} = \phi^* + e$
实现时需要加入限制器防止过冲:
@ti.kernel
def maccormack_advect(phi: ti.template(), phi_new: ti.template(), dt: float):
# 前向输送
for i, j in phi:
x = ti.Vector([i, j]) + 0.5
x_prev = backtrace(x, dt)
phi_star[i,j] = bilinear_sample(phi, x_prev)
# 后向输送
for i, j in phi:
x = ti.Vector([i, j]) + 0.5
x_next = backtrace(x, -dt)
phi_nn[i,j] = bilinear_sample(phi_star, x_next)
# 误差补偿
for i, j in phi:
phi_new[i,j] = phi_star[i,j] + 0.5 * (phi[i,j] - phi_nn[i,j])
phi_new[i,j] = clamp_extrema(phi_new[i,j], i, j)
4.4.2 BFECC方法
BFECC (Back and Forth Error Compensation and Correction) 是另一种减少数值耗散的方法:
- 前向输送:$\phi^{n+1/2} = A(\phi^n)$
- 后向输送:$\phi^{n*} = A^{-1}(\phi^{n+1/2})$
- 误差估计:$e = \frac{1}{2}(\phi^n - \phi^{n*})$
- 修正输入:$\tilde{\phi}^n = \phi^n + e$
- 最终输送:$\phi^{n+1} = A(\tilde{\phi}^n)$
其中 $A$ 表示输送算子,$A^{-1}$ 表示反向输送。
4.4.3 Runge-Kutta积分
对于速度场的高阶积分,常用RK4方法:
@ti.func
def rk4_backtrace(x: ti.math.vec2, dt: float) -> ti.math.vec2:
k1 = sample_velocity(x)
k2 = sample_velocity(x - 0.5 * dt * k1)
k3 = sample_velocity(x - 0.5 * dt * k2)
k4 = sample_velocity(x - dt * k3)
return x - dt * (k1 + 2*k2 + 2*k3 + k4) / 6.0
4.4.4 限制器与夹持(Clamping)
为了保持物理量的界限和避免振荡,需要使用各种限制器:
MinMod限制器: $$\text{minmod}(a, b) = \begin{cases} a & \text{if } |a| < |b| \text{ and } ab > 0 \\ b & \text{if } |b| < |a| \text{ and } ab > 0 \\ 0 & \text{otherwise} \end{cases}$$ Superbee限制器: $$\text{superbee}(a, b) = \text{maxmod}(\text{minmod}(2a, b), \text{minmod}(a, 2b))$$ 这些限制器确保高阶方法不会产生新的极值,保持解的物理性。
4.5 Chorin式压力投影
4.5.1 Helmholtz-Hodge分解
Helmholtz-Hodge定理指出,任意向量场可以唯一分解为无散场和无旋场之和: $$\mathbf{u} = \mathbf{u}_{div-free} + \nabla \phi$$ 其中 $\nabla \cdot \mathbf{u}_{div-free} = 0$。这是压力投影法的理论基础。
4.5.2 压力泊松方程推导
从动量方程出发: $$\frac{\mathbf{u}^{n+1} - \mathbf{u}^*}{\Delta t} = -\frac{1}{\rho}\nabla p$$ 其中 $\mathbf{u}^*$ 是不满足不可压缩条件的中间速度。
对两边取散度,并应用不可压缩条件 $\nabla \cdot \mathbf{u}^{n+1} = 0$: $$\nabla \cdot \mathbf{u}^{n+1} - \nabla \cdot \mathbf{u}^* = -\frac{\Delta t}{\rho} \nabla^2 p$$ 因此得到压力泊松方程: $$\nabla^2 p = \frac{\rho}{\Delta t} \nabla \cdot \mathbf{u}^*$$
4.5.3 离散化与边界条件
在MAC网格上,压力泊松方程的离散形式为: $$\frac{p_{i+1,j} - 2p_{i,j} + p_{i-1,j}}{(\Delta x)^2} + \frac{p_{i,j+1} - 2p_{i,j} + p_{i,j-1}}{(\Delta y)^2} = \frac{\rho}{\Delta t} \cdot divergence_{i,j}$$ 边界条件:
- 固体边界:法向压力梯度为零(Neumann条件)$\frac{\partial p}{\partial n} = 0$
- 自由表面:压力为大气压(Dirichlet条件)$p = 0$
- 周期边界:压力和梯度都周期延拓
离散系统可以写成矩阵形式 $\mathbf{A}\mathbf{p} = \mathbf{b}$,其中 $\mathbf{A}$ 是离散Laplace算子。
4.5.4 速度修正步
求解压力后,通过压力梯度修正速度场: $$\mathbf{u}^{n+1} = \mathbf{u}^* - \frac{\Delta t}{\rho} \nabla p$$ 在MAC网格上的具体形式: $$u_{i+1/2,j}^{n+1} = u_{i+1/2,j}^* - \frac{\Delta t}{\rho} \frac{p_{i+1,j} - p_{i,j}}{\Delta x}$$ $$v_{i,j+1/2}^{n+1} = v_{i,j+1/2}^* - \frac{\Delta t}{\rho} \frac{p_{i,j+1} - p_{i,j}}{\Delta y}$$ 投影后的速度场自动满足不可压缩条件。
4.6 固体边界条件
4.6.1 无滑移条件
对于粘性流体,在固体壁面上需要满足无滑移条件:
- 法向速度:$\mathbf{u} \cdot \mathbf{n} = 0$
- 切向速度:$\mathbf{u} \cdot \mathbf{t} = 0$
在MAC网格上,直接设置边界上的速度分量:
@ti.kernel
def enforce_boundary():
# 左右边界
for j in range(ny):
u[0, j] = 0.0 # 左边界
u[nx, j] = 0.0 # 右边界
# 上下边界
for i in range(nx):
v[i, 0] = 0.0 # 下边界
v[i, ny] = 0.0 # 上边界
4.6.2 自由滑移条件
自由滑移条件只约束法向速度,允许切向滑动:
- 法向速度:$\mathbf{u} \cdot \mathbf{n} = 0$
- 切向应力:$\tau \cdot \mathbf{t} = 0$
实现时,设置法向速度为零,但不修改切向速度。
4.6.3 浸入边界法
对于复杂几何形状,浸入边界法(Immersed Boundary Method)将边界力作为体积力添加到动量方程中: $$\rho \frac{D\mathbf{u}}{Dt} = -\nabla p + \mu \nabla^2 \mathbf{u} + \mathbf{f}_{IB}$$ 其中 $\mathbf{f}_{IB}$ 是边界力,通过拉格朗日乘子或罚函数方法计算。
4.6.4 切割单元法
切割单元法(Cut-cell Method)精确处理与网格不对齐的边界:
- 计算每个单元被固体占据的体积分数
- 修改离散算子以考虑部分单元
- 在切割单元上应用特殊的插值和梯度计算
这种方法可以达到二阶精度,但实现相对复杂。
4.7 自由表面边界条件
4.7.1 运动学条件
自由表面必须满足运动学条件:表面上的粒子始终保持在表面上。用等势面函数 $\phi$ 表示界面,其满足: $$\frac{D\phi}{Dt} = \frac{\partial \phi}{\partial t} + \mathbf{u} \cdot \nabla \phi = 0$$ 这是一个Hamilton-Jacobi型方程,描述了界面的输送。
4.7.2 动力学条件
在自由表面上,应力必须连续。忽略空气的影响,边界条件为: $$p = p_{atm} - \sigma \kappa$$ 其中:
- $p_{atm}$ 是大气压(通常设为0)
- $\sigma$ 是表面张力系数
- $\kappa = \nabla \cdot \mathbf{n}$ 是界面曲率
4.7.3 表面张力处理
连续表面力(CSF)模型将表面张力转换为体积力: $$\mathbf{F}_{st} = \sigma \kappa \delta(\phi) \nabla \phi$$ 其中 $\delta(\phi)$ 是Dirac delta函数的光滑近似。在实际计算中: $$\kappa = \nabla \cdot \left(\frac{\nabla \phi}{|\nabla \phi|}\right)$$ 为了数值稳定性,通常使用光滑的delta函数: $$\delta_{\epsilon}(\phi) = \begin{cases} \frac{1}{2\epsilon}\left(1 + \cos\left(\frac{\pi\phi}{\epsilon}\right)\right) & |\phi| < \epsilon \\ 0 & \text{otherwise} \end{cases}$$
4.7.4 Ghost Fluid方法
Ghost Fluid方法在界面两侧使用虚拟值来处理不连续:
- 在界面附近定义ghost cells
- 根据界面条件外推物理量到ghost cells
- 使用标准离散格式,自动处理界面跳跃条件
例如,对于压力的ghost值:
@ti.func
def extrapolate_pressure(i: int, j: int) -> float:
if phi[i,j] * phi[i+1,j] < 0: # 界面穿过单元
# 线性外推压力值
theta = phi[i,j] / (phi[i,j] - phi[i+1,j])
return (1-theta) * p[i,j] + theta * p_air
return p[i,j]
本章小结
本章介绍了欧拉视角下的流体仿真核心概念:
- 物质导数:$\frac{D}{Dt} = \frac{\partial}{\partial t} + \mathbf{u} \cdot \nabla$ 连接了欧拉和拉格朗日描述
- 算子分裂:将复杂的Navier-Stokes方程分解为对流、外力和投影三步
- MAC网格:交错网格自然满足离散散度定理,避免压力振荡
- 半拉格朗日输送:无条件稳定但有数值耗散,需要高阶修正
- 压力投影:通过求解泊松方程 $\nabla^2 p = \frac{\rho}{\Delta t}\nabla \cdot \mathbf{u}^*$ 实现不可压缩性
- 边界条件:固体边界的无滑移/自由滑移,自由表面的运动学/动力学条件
掌握这些概念是实现稳定高效的流体求解器的基础。
练习题
基础题
练习4.1 推导二维情况下的物质导数展开式。对于标量场 $\phi(x,y,t)$,证明: $$\frac{D\phi}{Dt} = \frac{\partial \phi}{\partial t} + u\frac{\partial \phi}{\partial x} + v\frac{\partial \phi}{\partial y}$$
提示
考虑流体质点的轨迹 $\mathbf{x}(t)$,使用链式法则。
答案
沿着流体质点的轨迹 $\mathbf{x}(t) = (x(t), y(t))$,有 $\frac{dx}{dt} = u$,$\frac{dy}{dt} = v$。
应用链式法则: $$\frac{D\phi}{Dt} = \frac{d}{dt}\phi(x(t), y(t), t) = \frac{\partial \phi}{\partial t} + \frac{\partial \phi}{\partial x}\frac{dx}{dt} + \frac{\partial \phi}{\partial y}\frac{dy}{dt}$$ 代入速度分量: $$\frac{D\phi}{Dt} = \frac{\partial \phi}{\partial t} + u\frac{\partial \phi}{\partial x} + v\frac{\partial \phi}{\partial y} = \frac{\partial \phi}{\partial t} + \mathbf{u} \cdot \nabla\phi$$
练习4.2 在MAC网格上,给定速度场 $u_{i+1/2,j}$ 和 $v_{i,j+1/2}$,写出计算单元 $(i,j)$ 处散度的公式。如果 $\Delta x = \Delta y = h$,散度为零意味着什么?
提示
考虑流入和流出单元的通量平衡。
答案
散度公式: $$(\nabla \cdot \mathbf{u})_{i,j} = \frac{u_{i+1/2,j} - u_{i-1/2,j}}{\Delta x} + \frac{v_{i,j+1/2} - v_{i,j-1/2}}{\Delta y}$$ 当 $\Delta x = \Delta y = h$ 时: $$(\nabla \cdot \mathbf{u})_{i,j} = \frac{1}{h}[(u_{i+1/2,j} - u_{i-1/2,j}) + (v_{i,j+1/2} - v_{i,j-1/2})]$$ 散度为零意味着流入单元的质量通量等于流出的质量通量,满足质量守恒。
练习4.3 证明压力投影步骤后的速度场满足不可压缩条件。给定 $\mathbf{u}^{n+1} = \mathbf{u}^* - \frac{\Delta t}{\rho}\nabla p$,其中 $p$ 满足 $\nabla^2 p = \frac{\rho}{\Delta t}\nabla \cdot \mathbf{u}^*$。
提示
对速度更新公式两边取散度。
答案
对速度更新公式取散度: $$\nabla \cdot \mathbf{u}^{n+1} = \nabla \cdot \mathbf{u}^* - \frac{\Delta t}{\rho}\nabla \cdot (\nabla p)$$ 注意到 $\nabla \cdot (\nabla p) = \nabla^2 p$,代入压力泊松方程: $$\nabla \cdot \mathbf{u}^{n+1} = \nabla \cdot \mathbf{u}^* - \frac{\Delta t}{\rho} \cdot \frac{\rho}{\Delta t}\nabla \cdot \mathbf{u}^*$$
$$\nabla \cdot \mathbf{u}^{n+1} = \nabla \cdot \mathbf{u}^* - \nabla \cdot \mathbf{u}^* = 0$$ 因此投影后的速度场自动满足不可压缩条件。
挑战题
练习4.4 分析半拉格朗日方法的数值耗散。考虑一维线性对流方程 $\frac{\partial u}{\partial t} + c\frac{\partial u}{\partial x} = 0$,使用线性插值,证明数值解引入了人工扩散项。
提示
将插值误差用Taylor级数展开,分析截断误差的主导项。
答案
设网格间距为 $h$,时间步长为 $\Delta t$,CFL数 $\nu = c\Delta t/h$。
半拉格朗日更新:$u_i^{n+1} = (1-\alpha)u_{i-1}^n + \alpha u_i^n$,其中 $\alpha = 1 - \nu$ 是插值系数。
使用Taylor展开: $$u_{i-1}^n = u_i^n - h\frac{\partial u}{\partial x} + \frac{h^2}{2}\frac{\partial^2 u}{\partial x^2} + O(h^3)$$ 代入更新公式: $$u_i^{n+1} = u_i^n - \nu h\frac{\partial u}{\partial x} + \frac{\nu h^2}{2}\frac{\partial^2 u}{\partial x^2} + O(h^3)$$ 与精确解 $u_i^{n+1} = u_i^n - c\Delta t\frac{\partial u}{\partial x}$ 比较,得到修正方程: $$\frac{\partial u}{\partial t} + c\frac{\partial u}{\partial x} = \frac{ch}{2}\nu(1-\nu)\frac{\partial^2 u}{\partial x^2}$$ 右端项是数值扩散,扩散系数 $D_{num} = \frac{ch}{2}\nu(1-\nu)$ 总是正的,导致解的耗散。
练习4.5 设计一个测试案例验证你的流体求解器的收敛阶。考虑Taylor-Green涡旋: $$u(x,y,t) = -\cos(x)\sin(y)e^{-2\nu t}$$ $$v(x,y,t) = \sin(x)\cos(y)e^{-2\nu t}$$ $$p(x,y,t) = -\frac{1}{4}[\cos(2x) + \cos(2y)]e^{-4\nu t}$$
提示
这是Navier-Stokes方程的精确解,可以计算不同网格分辨率下的L2误差。
答案
- 验证此解满足Navier-Stokes方程(代入可验证)
- 在 $[0, 2\pi]^2$ 域上使用周期边界条件
- 计算L2误差:$e_h = \sqrt{\frac{1}{N}\sum_{i,j}(u_{computed} - u_{exact})^2}$
- 对不同网格尺寸 $h = 2\pi/N$,计算误差
- 收敛阶:$p = \log(e_h/e_{h/2})/\log(2)$
- 预期:空间二阶精度 $e_h = O(h^2)$
练习4.6 实现一个自适应时间步长策略,基于CFL条件和粘性稳定性条件自动选择最大允许时间步长。
提示
需要同时考虑对流CFL和扩散稳定性条件。
答案
@ti.kernel
def compute_max_dt() -> float:
# 对流CFL条件
max_vel = 0.0
for i, j in u:
max_vel = max(max_vel, abs(u[i,j]))
for i, j in v:
max_vel = max(max_vel, abs(v[i,j]))
dt_cfl = CFL * dx / (max_vel + 1e-8)
# 粘性稳定性条件(如果使用显式粘性)
dt_visc = 0.25 * dx * dx / (nu + 1e-8)
# 表面张力稳定性条件(如果有)
dt_tension = ti.sqrt(rho * dx**3 / (sigma + 1e-8))
return min(dt_cfl, dt_visc, dt_tension)
练习4.7 推导并实现涡量-流函数形式的不可压缩流动。这种形式自动满足不可压缩条件,避免了压力投影。
提示
涡量 $\omega = \nabla \times \mathbf{u}$,流函数 $\psi$ 满足 $u = \frac{\partial \psi}{\partial y}$,$v = -\frac{\partial \psi}{\partial x}$。
答案
二维涡量输送方程: $$\frac{\partial \omega}{\partial t} + \mathbf{u} \cdot \nabla \omega = \nu \nabla^2 \omega$$ 流函数泊松方程: $$\nabla^2 \psi = -\omega$$
边界条件:
- 无滑移:$\psi = const$,$\frac{\partial \psi}{\partial n} = 0$
- 自由滑移:$\psi = const$,$\omega = 0$
算法步骤:
- 输送涡量(使用半拉格朗日或高阶方法)
- 求解流函数泊松方程
- 从流函数计算速度场
- 重复
这种方法的优点是自动满足不可压缩性,缺点是边界条件处理较复杂,且难以推广到三维。
常见陷阱与错误
-
压力求解器不收敛 - 检查边界条件是否正确设置 - 对于全Neumann边界,需要固定一个参考压力点 - 确保离散算子的对称性
-
速度场发散 - CFL条件可能太宽松 - 检查边界条件实现 - 压力投影可能不充分(迭代次数不够)
-
MAC网格索引混淆 - 记住速度分量存储在不同位置 - 插值时要使用正确的网格点 - 绘图时需要将速度插值到单元中心
-
数值耗散过大 - 使用高阶输送方法(MacCormack、BFECC) - 减小时间步长 - 考虑使用涡量约束
-
自由表面不稳定 - 表面张力的显式处理可能导致不稳定 - 使用隐式或半隐式表面张力 - 限制界面附近的时间步长
最佳实践检查清单
- [ ] 使用MAC网格避免压力振荡
- [ ] 实现自适应时间步长确保稳定性
- [ ] 对对流项使用至少二阶精度方法
- [ ] 压力求解器使用预条件共轭梯度法
- [ ] 实现质量守恒检查
- [ ] 边界条件处理要保持离散算子的对称性
- [ ] 使用高阶插值减少数值耗散
- [ ] 定期验证不可压缩条件的满足程度
- [ ] 实现能量或涡量监控以检测数值耗散
- [ ] 对复杂边界使用浸入边界法或切割单元法