简介:这份资源是面向流体力学与数值计算学习者的二维稳态Navier-Stokes方程有限元求解程序,基于Matlab实现,适合已具备偏微分方程与有限元基础、希望动手实践的中高级用户。程序围绕动量方程与连续性方程展开,涵盖几何离散、函数空间定义、变分形式推导、矩阵组装、线性系统求解及速度压力后处理等完整流程,并包含Dirichlet、Robin、应力边界处理与Newton迭代初值生成等模块,可用于分析速度矢量图与压力分布。压缩包共45个文件,全部为m脚本,约19KB,按功能划分为网格生成、基函数与高斯积分、矩阵与向量组装、边界条件处理、误差计算及求解测试等模块,结构紧凑便于逐层阅读。目前已有312人学习下载。通过研读源码,读者可掌握有限元法求解流体问题的组织方式,并较容易地将其迁移到热传导或扩散方程等同类问题。
1. 二维稳态Navier-Stokes方程:为什么有限元程序总在收敛边缘反复横跳
写二维稳态Navier-Stokes方程的有限元求解程序,最让人抓狂的不是推导公式,而是代码跑起来之后残差曲线像心电图一样上下抖,收敛判据永远差那么一点点。很多人第一反应是网格不够密,于是加密网格,结果自由度从几千涨到几万,残差反而更不听话。问题往往不在网格,而在于对稳态N-S方程本质的理解:它不是一个单纯的椭圆方程,对流项带来的非线性会让牛顿迭代的收敛域变得很窄,而有限元离散方式的选择直接决定了这个收敛域有多大。
这份二维稳态Navier-Stokes方程有限元求解程序,核心价值就在于把从弱形式推导到牛顿迭代收敛的完整链路用Matlab实现出来,让每一个矩阵、每一次迭代都有迹可循。流体计算里常说“有限元比有限体积难调”,难就难在基函数选取、稳定化项和线性求解器三者之间的耦合关系,而这份程序恰好把这三者的关系摊开在代码里。搞懂它,你就能明白为什么同样的方程,换个单元类型或换个稳定化参数,收敛行为会天差地别。
2. 弱形式与离散化:从强形式到可计算线性系统的关键跳跃
2.1 为什么不能直接离散强形式
二维稳态不可压缩N-S方程的强形式由动量方程和连续性方程组成,动量方程里同时存在对流项 $(u \cdot \nabla)u$ 和压力梯度项 $\nabla p$,连续性方程则是速度的散度为零约束。如果直接对强形式做差分或配点离散,会面临两个致命问题:一是二阶导数要求速度场具有二阶连续可导性,而有限元基函数通常只有一阶连续性;二是压力没有独立的边界条件,直接离散会导致压力场出现伪振荡。
弱形式的作用就是把求导的负担从解函数转移到测试函数上,通过分部积分降低对解的光滑性要求。具体到动量方程,粘性项 $\nabla \cdot (\nabla u)$ 经过分部积分后变成 $\int \nabla u : \nabla v , d\Omega$,边界上出现自然边界条件项,这正是有限元处理Neumann边界条件的天然优势。对流项的处理则更微妙,$(u \cdot \nabla)u$ 在弱形式中通常写成三重积分形式,测试函数与试探函数同时出现,这直接导致离散后得到的是非线性代数方程组,而不是线性方程组。
常见的实现思路是采用Taylor-Hood单元,即速度用二次插值、压力用线性插值。这种搭配满足LBB条件,能保证压力场的稳定性。代码中速度节点数远多于压力节点数,正是这个原因。如果速度压力同阶插值,压力会出现棋盘格振荡,这是有限元流体计算里最经典的翻车场景之一。
2.2 单元刚度矩阵的组装逻辑
在Matlab实现中,单元刚度矩阵的组装通常分三步走:先在高斯积分点上计算基函数及其导数,再通过雅可比矩阵映射到物理坐标,最后按局部-全局自由度映射累加到全局矩阵。对于N-S方程,每个单元需要计算四类矩阵块:粘性扩散矩阵、对流矩阵、压力梯度矩阵和散度矩阵。
% 以Taylor-Hood单元为例,计算单元粘性扩散矩阵 for i = 1:nq % 高斯积分点循环 [phi, dphi_dxi] = shape_func(xi(i), eta(i)); % 参考单元基函数 J = jacobian_matrix(node_coords, dphi_dxi); % 雅可比矩阵 invJ = inv(J); detJ = det(J); dphi_dx = dphi_dxi * invJ; % 物理坐标下的基函数导数 w = weights(i) * detJ; % 积分权重 % 粘性项: nu * grad(u):grad(v) K_loc = K_loc + nu * (dphi_dx' * dphi_dx) * w; end这段代码的关键在于dphi_dx的计算,它把参考单元上的基函数导数通过雅可比逆矩阵变换到物理单元。detJ是面积微元缩放因子,如果网格出现畸变,detJ可能接近零甚至为负,导致矩阵条件数急剧恶化。对流矩阵的组装更复杂,因为对流速度本身是待求解量,每次牛顿迭代都需要用当前速度场重新计算对流矩阵。压力梯度矩阵和散度矩阵则相对固定,只与基函数有关,可以在迭代前预先组装。
注意:高斯积分阶数必须足够高,否则对流项和粘性项的积分精度不足会引入额外误差。对于二次速度单元,通常至少需要3×3高斯点。
2.3 边界条件的施加方式
边界条件在有限元中有两种施加方式:强加法和弱加法。速度的Dirichlet边界条件通常用强加法,即直接修改全局矩阵的对应行和列,把边界自由度固定为给定值。具体操作是把对应行对角线元素设为大数(如1e10),右端项设为该大数乘以边界值,这样既保持了矩阵对称性,又近似强制了边界条件。
压力边界条件则更棘手。如果出口边界上压力已知,通常采用自然边界条件,即不显式施加,让弱形式中的边界积分项自动处理。如果全部边界都是壁面,压力会存在一个常数零空间,导致线性系统奇异。常见的处理方式是固定一个压力参考点,或者使用罚函数法。代码中通常会检测压力矩阵的零空间,如果发现奇异就自动固定第一个压力自由度。
出口边界的速度处理也需要注意。如果出口采用“无应力”边界条件,即 $\sigma \cdot n = 0$,在弱形式中体现为粘性项和对流项的边界积分为零,实现时只需不施加任何速度约束即可。但如果出口离回流区太近,这种边界条件会导致非物理反射,影响收敛。经验做法是把出口放在远离几何突变的位置,或者使用“定向出流”边界条件。
3. 牛顿迭代与线性求解:非线性系统如何一步步逼近收敛
3.1 牛顿-拉夫逊方法的残差构造
稳态N-S方程离散后得到非线性代数方程组 $R(U) = 0$,其中 $U$ 包含所有速度自由度和压力自由度。牛顿法的核心是在当前猜测 $U_k$ 处对残差做一阶泰勒展开,得到线性化方程 $J(U_k) \Delta U = -R(U_k)$,然后更新 $U_{k+1} = U_k + \alpha \Delta U$。其中 $J$ 是雅可比矩阵,也就是切线刚度矩阵。
残差向量 $R$ 的构造需要逐单元计算内力向量。对于动量方程,残差包含粘性项、对流项和压力梯度项的贡献;对于连续性方程,残差就是速度散度。代码中通常把残差按自由度编号组装成全局向量,然后计算其范数作为收敛判据。
% 计算残差向量 R = zeros(nDof, 1); for e = 1:nElem U_e = U(elemDof(e, :)); % 提取单元自由度 [R_e, J_e] = element_residual_jacobian(U_e, elem_data(e)); R(elemDof(e, :)) = R(elemDof(e, :)) + R_e; J(elemDof(e, :), elemDof(e, :)) = ... J(elemDof(e, :), elemDof(e, :)) + J_e; end res_norm = norm(R) / norm(U); % 相对残差这里element_residual_jacobian是核心函数,它同时返回单元残差和单元雅可比矩阵。对流项的存在使得雅可比矩阵包含两部分:一部分来自对流速度对试探函数的导数,另一部分来自对流速度本身作为待求量的导数。后者在牛顿法中被称为“一致性切线”,忽略它会导致收敛速度从二次降为线性。
3.2 阻尼牛顿与线搜索策略
纯牛顿法在远离解时可能发散,因为一阶泰勒展开的近似精度不够。阻尼牛顿法引入步长因子 $\alpha$,每次迭代只走一部分更新量。步长选择通常用线搜索:从 $\alpha = 1$ 开始,如果新残差范数没有下降,就减半步长,直到残差下降或步长小于阈值。
% 阻尼牛顿迭代主循环 alpha = 1.0; for iter = 1:maxIter [R, J] = assemble_system(U); if norm(R) < tol break; end dU = -J \ R; % 求解线性系统 % 线搜索 while norm(assemble_residual(U + alpha * dU)) > (1 - 1e-4 * alpha) * norm(R) alpha = alpha / 2; if alpha < 1e-6 warning('线搜索失败,可能已接近奇异点'); break; end end U = U + alpha * dU; end线搜索的Armijo条件要求新残差范数下降至少与步长成正比。代码中的1 - 1e-4 * alpha就是Armijo参数,1e-4是经验值,太小会导致步长缩减不够,太大会导致过度缩减。如果线搜索反复失败,通常意味着雅可比矩阵接近奇异,可能是网格质量太差或稳定化参数不合适。
3.3 线性求解器的选择与预处理
每次牛顿迭代都需要求解一个大型稀疏线性系统 $J \Delta U = -R$。Matlab中直接用反斜杠求解器对中小规模问题足够,但对于自由度超过五万的问题,迭代求解器更合适。GMRES配合ILU预处理是常见选择,因为雅可比矩阵是非对称的,共轭梯度法不适用。
预处理的质量直接影响迭代次数。对于N-S方程,压力块的对角占优性通常比速度块差,所以简单的对角预处理效果有限。常见的改进是块预处理:对速度块用其对角,对压力块用压力质量矩阵的逆。代码中如果使用ilu函数做不完全LU分解,需要调整dropping tolerance,太大会导致预处理效果差,太小会导致填充过多、内存爆炸。
提示:如果GMRES残差下降停滞,先检查雅可比矩阵是否组装正确,再检查预处理矩阵是否接近奇异。很多时候问题不在求解器,而在单元矩阵的符号或系数。
3.4 收敛判据的设定与误判
收敛判据通常用相对残差范数,即当前残差范数除以初始残差范数。但初始残差的选择很关键:如果初始猜测离解很远,初始残差很大,相对判据会过于宽松;如果初始猜测已经不错,相对判据又可能过于严格。更稳健的做法是同时监控绝对残差和相对残差,以及更新量 $\Delta U$ 的范数。
另一个常见误判是残差下降但解没有收敛。这种情况通常发生在残差被某些局部区域主导时,比如网格畸变区域或边界层附近。此时需要检查残差的分布,如果残差集中在少数单元,说明那些区域的解质量差,可能需要局部加密或调整稳定化参数。
4. 稳定化方法与常见坑:为什么你的解会出现伪振荡
4.1 SUPG与压力稳定化的实现细节
对流占优时,标准Galerkin有限元会产生节点间振荡,因为中心差分格式在单元Peclet数大于1时失去单调性。SUPG方法通过在测试函数中增加对流方向的扰动项来引入人工耗散,扰动项的形式是 $\tau (u \cdot \nabla v)$,其中 $\tau$ 是稳定化参数。
$\tau$ 的计算有多种公式,常见的是 $\tau = ( (2/ \Delta t)^2 + (2|u|/h)^2 + (4\nu/h^2)^2 )^{-1/2}$,稳态问题中去掉时间项。代码中需要逐单元计算 $\tau$,因为每个单元的速度和尺寸不同。如果 $\tau$ 太小,振荡抑制不足;如果太大,解会被过度抹平,边界层变厚。
压力稳定化则针对LBB条件不满足或接近不满足的情况。即使使用Taylor-Hood单元,在压力边界条件复杂时也可能出现压力振荡。常见的压力稳定化是增加压力Laplacian项,系数通常取 $h^2/\nu$ 量级。这个项在数学上改变了原方程,但在网格足够密时影响可以忽略。
4.2 避坑与排查:五条血泪经验
现象一:残差在1e-3附近停滞,线搜索反复失败。原因通常是雅可比矩阵的条件数过大,可能来自网格畸变或稳定化参数过小。解决方法是检查网格质量,计算单元雅可比行列式的最大值与最小值之比,如果超过100就需要重新划分网格;同时适当增大SUPG稳定化参数。
现象二:压力场出现棋盘格振荡。原因是速度压力插值阶数不满足LBB条件,或者压力稳定化缺失。解决方法是改用Taylor-Hood单元,或者增加压力稳定化项。如果已经用了Taylor-Hood单元,检查压力边界条件是否施加过多,导致压力零空间被错误约束。
现象三:牛顿迭代初期残差下降很快,后期下降变慢甚至反弹。原因可能是对流项的一致性切线被忽略,导致收敛速度降为线性。解决方法是检查雅可比矩阵组装代码,确保对流项对速度的导数被正确包含。另一个可能是线搜索参数过于宽松,导致步长过大跳过了解。
现象四:出口附近出现非物理回流。原因是出口边界条件使用了无应力条件,但出口位置在回流区内。解决方法是把出口移到远离几何突变的位置,或者改用定向出流边界条件,即在出口只约束法向速度,不约束切向速度。
现象五:线性求解器迭代次数随牛顿迭代增加而暴涨。原因是雅可比矩阵在迭代后期变得病态,通常是因为解接近收敛时,对流项和粘性项的量级差异变大。解决方法是使用更好的预处理,比如基于物理的块预处理,或者在迭代后期切换为直接求解器。
4.3 网格质量对收敛的影响
有限元对网格质量的敏感度远高于有限体积。单元畸变会导致雅可比矩阵条件数恶化,进而影响牛顿迭代和线性求解。衡量网格质量的常用指标包括:单元雅可比行列式的最小值与最大值之比、单元内角的最大值与最小值、以及相邻单元尺寸的跳变率。
在Matlab中,可以用pdetool或generateMesh生成网格,但自动生成的网格质量不一定适合N-S方程。对于方腔驱动流这类经典问题,均匀网格通常足够;但对于复杂几何,边界层区域需要各向异性网格,此时单元的长宽比可能达到100:1,需要特别注意雅可比矩阵的计算稳定性。
注意:如果网格中包含三角形和四边形混合单元,组装时需要分别处理,因为基函数和积分点不同。混合网格的全局自由度编号也更容易出错,建议先用简单网格验证代码正确性。
5. 从能算到算得准:验证方法与设计取舍
判断一个N-S有限元程序是否可靠,最直接的方法是拿经典基准问题做验证。方腔驱动流是最常用的测试用例:顶盖以单位速度移动,其他三边无滑移,雷诺数从100到10000都有参考解。验证时不要只看流线图,要定量比较特定点的速度值或壁面涡心位置。如果Re=100时中心线速度剖面对不上参考数据,说明离散或稳定化有问题。
另一个验证手段是网格收敛性分析。固定雷诺数,逐步加密网格,观察某个目标量(如涡心位置或壁面摩擦力)的变化。如果目标量随网格加密单调收敛,说明离散格式一致;如果出现振荡,说明稳定化参数与网格尺寸的耦合关系没处理好。常见的做法是让稳定化参数随网格尺寸自适应变化,而不是固定值。
设计取舍方面,Taylor-Hood单元精度高但自由度多,适合中小规模问题;等阶单元配合稳定化自由度少但参数敏感,适合大规模问题。直接求解器稳健但内存消耗大,迭代求解器省内存但需要调预处理。代码中通常提供多种选项,让使用者根据问题规模切换。我一般会先用直接求解器和小网格验证正确性,再切换到迭代求解器和大网格做实际计算。
从那以后我每次写N-S有限元程序,都强制先跑方腔驱动流验证,再跑网格收敛性,最后才上实际几何。这个习惯帮我省下了大量排查伪振荡和收敛停滞的时间。希望帮到你。
本文还有配套的精品资源,点击获取