简介:这份 OpenFOAM 入门学习资料面向零基础到初级水平的 CFD 学习者,围绕开源流体计算软件的命令行操作、求解器选择与典型算例展开,帮助读者建立从算例结构、离散格式到前后处理的整体认识。压缩包共 1 个 docx 文件,约 55.87MB,内容以图文笔记形式整理,便于按知识点查阅和复习。资料已有 1144 人学习,说明其对入门阶段具有一定参考价值。正文覆盖 alias 命令、库函数与 ODE、Cavity Flow 算例结构、时间与对流及 Laplacian 离散格式、relTol 与 tolerance 等求解器参数,并梳理 checkMesh、setFields、refineMesh 等前处理工具和 co、vorticity、yPlus、sample 等后处理工具,还涉及 Function Objects、forces 与力系数计算、probe 采样及常见边界条件类型,可辅助读者理解 OpenFOAM 的目录组织、参数含义和基础使用流程。
1. 从一次 cavity 算例跑崩说起:这套入门笔记补的是哪一段
装完 OpenFOAM 的第一天,多数人卡在同一处:blockMesh 跑通、icoFoam 跑完,paraFoam 打开却是一片空白,或者残差曲线跑到第三秒就翘头。这套 OpenFOAM 入门资料处理的正是这段最费试错的链路——alias 与求解器入口、src 下的库函数与 ODE、cavity 算例结构与离散格式、Utilities 前后处理、Function Objects 在线输出、边界条件与湍流模型,最后落到 icoFoam 与 interFoam 的代码走读。它适合能敲命令但还没把算例调到收敛的人;纯新手照着步骤能跑通 cavity,但得先有有限体积法的概念,不然参数为什么这么填只能靠背。
2. 环境变量与命令入口:alias、求解器、src 三层怎么串起来
OpenFOAM 用起来别扭,很大一部分不是物理问题,是路径问题。它把可执行程序、算例库、源码树分散在几个环境变量指向的目录里,版本一换路径就变,新手很容易在cd上耗掉半天。这一章先把入口固定下来,再谈求解。
2.1 alias 不是语法糖,是路径纪律
先确认环境变量有没有生效。打开终端,不要急着进算例,先看这三条:
# 确认当前加载的是哪个版本、算例库和用户工作目录在哪 echo $WM_PROJECT_VERSION # 版本号,例如 9、10、2306 echo $FOAM_TUTORIALS # 官方算例根目录,路径很长的那个 echo $FOAM_RUN # 用户自己的 run 目录,通常是 $HOME/OpenFOAM/$USER-x.y/run如果这三条输出为空,说明etc/bashrc没被 source,后面的命令全都会报 command not found。常见做法是把source $安装路径/etc/bashrc写进~/.bashrc,登录即生效。
路径确认完,再谈 alias。我一般会在~/.bashrc里加这么几行:
# 写进 ~/.bashrc,source 一次或重开终端生效 alias foam='cd $FOAM_RUN' # 回工作目录,避免每次手敲长路径 alias tut='cd $FOAM_TUTORIALS' # 回官方算例库,抄写法用 alias src='cd $FOAM_SRC' # 回源码树,读方程实现用 alias clean='foamCleanCase -full' # 清时间步和中间文件,保留 0/constant/system alias lst='foamListTimes -latestTime' # 看最后写出的时刻 alias tailog='tail -n 40 log.*' # 一眼看最近一次求解日志尾巴这几个别名的价值不在省几个字符,而在把「我永远从这几个根目录出发」变成肌肉记忆。翻车最多的情况是:在 A 算例目录里改了fvSchemes,却跑到 B 目录去执行icoFoam,日志看起来正常,结果全是旧的。foamCleanCase -full在部分旧版本里叫foamCleanPolyMesh,敲不出来就先which foamCleanCase确认一下,别硬猜。
2.2 求解器在 app,方程在 src,ODE 是第三层
OpenFOAM 的源码结构其实很规整,记住三个位置就够用了:
| 目录变量 | 里面是什么 | 什么时候去翻 |
|---|---|---|
$FOAM_APP | 各求解器主程序,如 icoFoam、interFoam | 想知道某个求解器解了哪几个方程、读了哪些字典 |
$FOAM_SRC | 有限体积离散、库函数、湍流模型、ODE 求解器 | 想知道div、laplacian到底怎么实现,kEpsilon 的方程怎么写的 |
$FOAM_TUTORIALS | 覆盖各物理场景的官方算例 | 不知道某个 function object 或边界条件怎么写时,反查现成的 |
用两条命令就能定位到具体文件:
# 找求解器主目录(不同版本层级略有差异,所以用 -maxdepth 限制深度) find $FOAM_APP -maxdepth 4 -type d -name "icoFoam" # 找湍流模型的实现文件,kEpsilon 的 k 方程和 epsilon 方程都在同名 C 文件里 find $FOAM_SRC -name "kEpsilon.C" -o -name "kEpsilon.H"参数说明:-maxdepth是防止在超大源码树里递归太深;-name支持通配,想一次找多个就在后面接-o。新版本把一部分求解器挪进了$FOAM_APP/modules,如果第一条命令什么都没输出,把-maxdepth去掉再试。
至于 ODE,也就是常微分方程那一层,它不在有限体积主链路上,但在反应、颗粒跟踪、某些后处理量的积分里会用到。$FOAM_SRC/ODE下有一整套显式/隐式积分器,读它的意义在于:当你写的自定义边界条件或源项里需要对某个标量沿时间积分,不必自己从零写 RK4,直接复用现成的类就行。
2.3 把 cavity 跑起来:四步链路
cavity 是 OpenFOAM 的「Hello World」,方腔顶盖驱动,层流不可压,网格小、算得快,非常适合用来验证环境。链路只有四步:
# 1. 把官方算例复制到自己的工作目录,别在算例库里直接改 cp -r $FOAM_TUTORIALS/incompressible/icoFoam/cavity/cavity $FOAM_RUN/ cd $FOAM_RUN/cavity # 2. 生成网格,日志单独落到文件,屏幕只留状态 blockMesh > log.blockMesh 2>&1 # 3. 求解 icoFoam > log.icoFoam 2>&1 # 4. 看结果时刻和日志尾巴 foamListTimes -latestTime tail -n 20 log.icoFoam逻辑说明:> log.xxx 2>&1是把标准输出和标准错误合并写进日志,这是 OpenFOAM 的常规操作,因为屏幕上滚得太快,报错一闪就没。判断有没有算成功,不看屏幕,看日志末尾有没有End,以及foamListTimes能不能列出非零时刻。如果blockMesh那一步就失败了,先看log.blockMesh里是不是cannot find file blockMeshDict,那说明你在错误的目录里执行,回到算例根目录再跑。
cavity 的controlDict默认endTime是 0.5,deltaT是 0.005,会写出若干个时刻。跑完你会在算例根目录看到0/、0.1/、0.2/一直到0.5/这些文件夹,每个里面是同一批场文件在该时刻的快照。
2.4 paraFoam 打不开算例时的那条 touch 命令
这是最经典的一类问题:求解日志一切正常,时刻文件夹也都生成了,双击paraFoam打开却什么都没有。原因不复杂——ParaView 需要一个后缀为.OpenFOAM的空文件作为「入口」,靠这个后缀去加载 OpenFOAM 的 reader 插件,而这个文件不会被求解器自动创建。
# 方式一:手工造一个空文件,命名必须与算例目录同名 touch cavity.OpenFOAM # 方式二:让 OpenFOAM 自己生成 .foam 文件(较新版本推荐) paraFoam -touch # 方式三:reader 插件没编译或加载失败时,退一步转 VTK foamToVTK -latestTime参数说明:.OpenFOAM文件的内容为空,ParaView 只认文件名和后缀,所以touch出来的文件放在算例根目录、与constant/、system/同级即可。paraFoam -touch生成的是.foam,本质作用相同。foamToVTK -latestTime只转最后一个时刻,想转全部就去掉这个参数,代价是磁盘占用会明显变大。
注意:如果
paraFoam命令本身报找不到,说明对应的 ParaView 组件没装上,这时候别死磕,直接用foamToVTK转格式,用任意 ParaView 打开.vtk系列文件,功能上不差。
3. cavity 算例的参数表:时间离散、对流格式与残差容差怎么取
cavity 跑通只是知道了流程,真正决定「算得对不对、稳不稳」的是system/下那三个字典:controlDict、fvSchemes、fvSolution。这一章把里面对新手最不友好、又最不能随便填的几项拆开。
3.1 算例结构:三个文件夹各管什么
先把目录职责理清楚,后面调参数才知道该改哪里。
| 文件夹 | 内容 | 改动频率 |
|---|---|---|
0/ | 初始场与边界条件,如U、p、k、epsilon | 换工况时改 |
constant/ | polyMesh/网格、transportProperties物性、turbulenceProperties湍流开关 | 换网格或物性时改 |
system/ | blockMeshDict、controlDict、fvSchemes、fvSolution | 调数值时改 |
一个常见的误会是把边界条件写在system/里。0/下的文件就是「场 + 边界」,每个场文件末尾的boundaryField块才是边界条件真正生效的地方,这一点在换求解器的时候尤其重要,因为不同求解器要求的场文件不一样,icoFoam 只要U和p,interFoam 还要alpha.water。
3.2 时间离散:Crank-Nicolson 的 θ 为什么常取 0.9
// system/fvSchemes ddtSchemes { default CrankNicolson 0.9; }逻辑说明:这个系数是一个加权因子,1 表示纯 Crank-Nicolson,时间上二阶但容易在陡梯度区域产生非物理振荡;0 退化成欧拉一阶,稳定但数值耗散大。取 0.9 相当于「九成二阶 + 一成欧拉」,用一点点一阶耗散把振荡压住,同时保留大部分时间精度,所以很多教程和工程算例都默认这个值。
backward也是二阶,但在有界场问题里存在解的无界风险,尤其是相分数、组分这类必须落在 [0,1] 的场,一般不推荐拿来当默认。如果算例对时间精度要求不高、只求稳,直接上Euler也没什么丢人的,先把流场跑出来再谈精度,比一开始就追求二阶然后炸掉划算。
配CrankNicolson的时候,controlDict里的deltaT和库朗数要一起看。cavity 这种量级默认deltaT 0.005就够,换到真实几何,先按特征尺寸除以特征速度估一个上限,再留一半余量。
3.3 对流项默认 none:逼你逐项指定
// system/fvSchemes divSchemes { default none; // 不设默认,缺哪项直接报错 div(phi,U) Gauss linearUpwind grad(U); // 动量对流,二阶迎风带梯度修正 div(phi,k) Gauss upwind; // 湍动能,一阶保正 div(phi,epsilon) Gauss upwind; // 耗散率,一阶保正 }参数说明:default none是个刻意的设计——只要某个方程项没被显式指定,求解器立刻报错停下,而不是偷偷用一个可能不合适的格式。这比求解跑完了才发现结果不对要好得多。
具体项怎么选,取决于物理。动量方程用linearUpwind这类二阶迎风,是因为一阶upwind的数值扩散会把涡抹平,cavity 里的主涡会明显变小;而k和epsilon这类必须非负的量,用一阶upwind换稳定性是常见取舍。没有「最佳格式」,只有「这一项在这个问题下能不能接受」。
3.4 Laplacian 与梯度:由 checkMesh 的正交性决定
// system/fvSchemes gradSchemes { default Gauss linear; grad(U) cellLimited Gauss linear 1; // 限制梯度,抑制极端网格上的过冲 } laplacianSchemes { default Gauss linear corrected; // 非正交修正 } snGradSchemes { default corrected; }逻辑说明:corrected表示做完整的非正交修正,网格质量好时精度最高;网格歪得厉害,完整修正反而会引入振荡,这时候换成limited 0.33或limited 0.5更稳,系数就是修正量的限幅比例。
怎么判断该用哪个?跑一次checkMesh,看Max non-orthogonality。低于 60 用corrected基本没问题,60 到 70 之间建议limited 0.5,再高就得先改网格而不是调格式了。cellLimited ... 1里的 1 是限幅上限,取 1 表示允许梯度在单元内线性重构,取更小的值会更保守。
3.5 relTol、tolerance、pRefCell:三个最容易被乱填的参数
// system/fvSolution solvers { p { solver PCG; preconditioner DIC; tolerance 1e-06; // 绝对残差,到这个量级就停 relTol 0.05; // 相对残差,比上一次降两个量级就停 } U { solver smoothSolver; smoother symGaussSeidel; tolerance 1e-05; relTol 0; // 置 0 表示关闭相对残差判据 } } PISO { nCorrectors 2; nNonOrthogonalCorrectors 0; pRefCell 0; // 压力参考单元编号 pRefValue 0; // 该单元上的压力参考值 }参数说明:tolerance是绝对残差门槛,relTol是相对残差门槛,两者谁先满足谁生效;relTol设为 0 就是彻底关掉相对判据,只认绝对残差。实践里压力方程常用relTol 0.05,因为 PISO 内层不需要把压力解到很准,外层会再修正;而速度场一般relTol 0,避免内层偷懒导致连续性残差降不下去。
pRefCell和pRefValue的存在,是因为不可压求解里压力只出现在梯度中,整体加一个常数不改变流场。所以 OpenFOAM 解出来的压力是相对值,不是绝对值,必须钉一个参考点。默认钉在 0 号单元、值取 0,一般不用改;但如果 0 号单元恰好在你要监测压力的位置,记得把参考点挪走,否则那个点的压力永远显示为 0,看起来像 bug。
nCorrectors是 PISO 的压力修正次数,2 是常规值,网格很差或者时间步很大时可以加到 3,代价是单步耗时上升。
4. Utilities 与 Function Objects:把网格检查、采样和力矩输出固定成流程
求解器之外,OpenFOAM 还有一大批小工具,用官方的话叫 utilities。这类命令的特点是单独看都很简单,串成流程才知道哪个先哪个后。这一章按「前处理 → 后处理 → 在线输出」的顺序走一遍。
4.1 checkMesh:先看四个数,再看 Mesh OK
# 全量检查,几何和拓扑都查,日志落盘便于对比不同版本网格 checkMesh -allGeometry -allTopology > log.checkMesh 2>&1 # 只挑关心的指标出来看 grep -E "non-orthogonality|skewness|aspect ratio|Min volume|Mesh OK" log.checkMesh为什么强调「先看四个数再看 Mesh OK」:Mesh OK只说明拓扑自洽、没有负体积这类硬错误,不说明网格质量适合计算。真正的判断依据是下面这几个指标:
| 指标 | 经验阈值 | 超标后的典型后果 |
|---|---|---|
| Max non-orthogonality | < 65,最好 < 60 | 扩散项误差大,残差抖动 |
| Max skewness | < 4,最好 < 1 | 对流项振荡,局部发散 |
| Max aspect ratio | 边界层内可到几百,外场 < 100 | 收敛慢,压力场出现条纹 |
| Min volume | 必须 > 0 | 出现负体积直接无法计算 |
动网格算例还有一个额外要求:不能只在开始查一次。网格在运动过程中会不断变形,中途质量崩掉是常态,所以要在controlDict里挂一个checkMesh类的 function object,或者定期手工查一次中间时刻。
4.2 网格变换与局部加密:transformPoints 加 topoSet/refineMesh
# 平移,单位与算例一致(一般是米),注意引号不能省 transformPoints -translate '(0 0 -0.05)' # 缩放,参数是乘数不是目标尺寸,0.001 表示毫米转米 transformPoints -scale '(0.001 0.001 0.001)' # 旋转,两个向量分别定义旋转轴和参考方向 transformPoints -rotate '(0 0 1) (0 1 0)'这三个命令都有-overwrite变体,不加时默认写回constant/polyMesh,实际行为在不同版本略有差异,动网格前建议先备份整个polyMesh目录。
局部加密比全局加密复杂一档,因为它需要两个字典配合。第一步用topoSet圈出要加密的区域:
// system/topoSetDict actions ( { name box1; // cellSet 名字,下一步 refineMeshDict 要引用 type cellSet; action new; source boxToCell; box (0.02 0.02 0) (0.04 0.04 0.01); // 加密区域的包围盒 } );第二步让refineMesh读这个 cellSet:
// system/refineMeshDict refineMeshDict { coordinateSystem cartesian; useHexTopology true; geometricCut false; directions (1 1 1); // 三个方向是否切分,1 表示切、0 表示不切 cells (box1); // 与 topoSetDict 里的 name 严格一致 }topoSet refineMesh -overwrite逻辑说明:topoSet先跑,成功后在constant/polyMesh/sets/下生成box1文件,你可以直接打开确认单元数量对不对。-overwrite表示直接覆盖0/目录下的网格而不是新建一个时间步文件夹,好处是省事,坏处是改错了没法回退,所以我一般先cp -r constant/polyMesh constant/polyMesh.bak。
directions三个分量分别控制 x、y、z 方向是否切分。只加密流向就写(1 0 0),三个方向都切是(1 1 1),每次切分单元数翻倍,三次就是八倍,别一上来就全切。
4.3 setFields 与 createBaffles:初始相分布和内部面转边界
setFields解决的是「初始时刻某一小块区域里是水、其余是空气」这类问题,溃坝、气泡、射流都用得上。
// system/setFieldsDict defaultFieldValues ( volScalarFieldValue alpha.water 0 ); // 全域默认是空气 regions ( boxToCell { box (0 0 0) (0.4 0.1 0.1); // 水的初始区域 fieldValues ( volScalarFieldValue alpha.water 1 ); // 该区域内相分数为 1 } );# 先把模板文件复制成真实场文件,再执行 setFields 覆盖 cp 0/alpha.water.org 0/alpha.water setFields逻辑说明:defaultFieldValues会被应用到整个计算域,regions里的块再局部覆盖,两者是「先铺底、再打补丁」的关系。alpha.water这种场在 interFoam 算例里通常以.org后缀存在,必须先复制成正式文件名,否则setFields找不到目标场会直接报错。
createBaffles处理的是另一类需求:把网格内部的某个面(interior 或 interface)转成真正的边界面,比如薄壁、挡板、周期面。它同样依赖topoSet先建faceZone:
// system/createBafflesDict patches ( { name baffle1; // 生成的边界 patch 名 type faceZone; // 来源类型,从 faceZone 取 zoneName fz1; // topoSetDict 里建的 faceZone 名 patchType wall; // 转换成什么类型的边界 } );createBaffles -overwrite参数说明:patchType可以是wall、patch、cyclic等,选错会导致后续边界条件类型对不上。-overwrite同样意味着直接改polyMesh,这一步比加密更危险,因为它会改变边界列表,之前写好的0/场文件里的boundaryField条目会失效,必须同步补上新的 patch 条目。
4.4 后处理命令:输出目录永远在 postProcessing 下
OpenFOAM 的后处理命令有个统一约定:结果不写进时间步文件夹,而是写到算例根目录的postProcessing/下,按「功能名/时刻/」分层。
# 计算旋度,输出到 postProcessing/vorticity/<time>/ postProcess -func vorticity -latestTime # 计算 yPlus,输出到 postProcessing/yPlus/<time>/ postProcess -func yPlus -latestTime # 沿一条线采样,需要先有 system/sampleDict postProcess -func sample -latestTime # 看看到底生成了什么 find postProcessing -type f | head -20逻辑说明:老版本里vorticity、yPlus是独立命令,新版本统一收进postProcess -func,两种写法都在用,敲不出来就用另一种。-latestTime只处理最后一个时刻,做动画或分析瞬态过程时去掉这个参数,会遍历所有已写出的时刻。
采样需要sampleDict。如果在system/下没有这个文件,sample命令会直接报错退出:
// system/sampleDict type sets; libs ("libsampling.so"); interpolationScheme cellPoint; setFormat raw; sets ( line1 { type uniform; // 均匀取点,也可以用 cloud 自定义点集 axis x; // 采样线沿 x 方向 start (0 0.05 0.005); end (0.1 0.05 0.005); nPoints 100; } );参数说明:start和end必须落在计算域内部,落在边界外会得到空文件;nPoints是采样点数量,太小曲线会失真,太大文件会膨胀。setFormat raw输出纯文本,方便直接用 Python 或 Excel 读;换成gnuplot之类的格式可以配合绘图工具直接出图。
如果system/下出现sampleDict.swp这种文件,说明之前有编辑器非正常退出,残留的交换文件会让某些操作报错。rm -f sampleDict.swp删掉即可,不影响结果。
采样点固定、但又想看整个时间序列的情况,用probes比反复跑sample划算。它是一个 function object,写在controlDict里,随求解过程同步输出:
// system/controlDict 末尾 functions { probes1 { type probes; libs ("libsampling.so"); writeControl timeStep; writeInterval 1; fields (p U); // 要监测的场,按需增减 probeLocations ( (0.05 0.05 0.005) // 可以写多个点,每行一个 ); } }参数说明:writeInterval 1表示每个时间步都记一次,粒度最细但文件也最大;fields里写几个场,输出文件里就有几列。probeLocations里的点不必是网格节点,求解器会做插值。这种方式的代价是必须在线跑,算完了再想加就得重算,所以位置要提前想清楚。
4.5 Function Objects:forces 与 forceCoeffs 怎么写进 controlDict
forces算的是边界面上的力和力矩,forceCoeffs在它的基础上再除以动压和参考面积,得到升力系数、阻力系数。楼宇风场、翼型绕流这类算例基本绕不开。
// system/controlDict 的 functions 块 functions { forces1 { type forces; libs ("libforces.so"); writeControl timeStep; writeInterval 1; patches (walls); // 参与积分的边界 patch CofR (0.05 0.05 0.005); // 力矩参考点,影响力矩大小 rho rhoInf; // 不可压用 rhoInf rhoInf 1; // 密度值,量纲要和算例一致 log true; // 同时打印到求解日志 } coeffs1 { type forceCoeffs; libs ("libforces.so"); patches (walls); CofR (0.05 0.05 0.005); rho rhoInf; rhoInf 1; liftDir (0 1 0); // 升力方向 dragDir (1 0 0); // 阻力方向 pitchAxis (0 0 1); // 俯仰轴 magUInf 1; // 来流速度大小 lRef 0.1; // 参考长度 Aref 0.01; // 参考面积 } }参数说明:CofR是力矩的参考点,取不同位置力矩值完全不同,报告结果时必须写清楚取在哪。rhoInf对不可压算例就是常数密度,量纲要和p的量纲设定一致,否则系数会差一个量级。lRef和Aref决定了系数的绝对值,写论文或做对比时必须在同一套参考量下比较才有意义。
输出规律值得提前知道:结果落在postProcessing/forces1/0/force.dat,0这个子目录是 function object 的实例编号,同一个算例里同名对象跑第二次会生成1/,不会覆盖上一次的结果。这算是个好消息,改参数重跑不用怕丢历史数据。
找官方写法最快的办法不是翻文档,是在算例库里反查:
# 在全部官方算例的 controlDict 里搜哪个用了 forceCoeffs find $FOAM_TUTORIALS -name controlDict | xargs grep -l "forceCoeffs" # 顺手看看它周边是怎么配的,比凭空猜 key 名靠谱 find $FOAM_TUTORIALS -name controlDict | xargs grep -A 20 "forceCoeffs"参数说明:xargs是把前一条命令的输出变成后一条命令的参数列表,文件多的时候效率比-exec高;grep -l只输出文件名不输出内容,适合先定位再细看;-A 20表示匹配行之后再打印 20 行上下文。这套组合在 OpenFOAM 里通用,想找任何字典项的正确写法都可以照搬。
5. 避坑与排查:五个翻车现场的现象、原因与处置
前面几章讲的是「应该怎么配」,这一章讲「配错了长什么样」。下面五条都是我自己撞过或者帮人排查过的,按现象、原因、解决三段写,遇到对应症状直接对号入座。
5.1 paraFoam 打开只有网格线,没有云图
现象:时刻文件夹明明有0.1/、0.2/,ParaView 里只能看到方块轮廓,颜色是灰的,切到U也没有颜色变化。
原因:三种可能。一是读到的其实是0/时刻的常量场,初始场全场均匀,看起来就像没数据;二是touch出来的.OpenFOAM文件放错了位置,不在算例根目录,ParaView 加载的是空壳;三是controlDict里writeInterval设得比endTime还大,一个时刻都没写出。
解决:先foamListTimes确认到底有哪些时刻,再ls -la确认.OpenFOAM文件与constant/同级。如果确实没写出场文件,把controlDict的writeInterval改成 1 或者更小的值重跑。实在懒得改,foamToVTK -latestTime转一份 VTK,用 ParaView 直接开。
5.2 checkMesh 全 OK,一算就发散
现象:checkMesh输出Mesh OK,正交性、偏斜度都在阈值内,但icoFoam跑十几步以后残差直线上升,终端刷出bounding警告。
原因:多数不是网格的锅,是时间步和初始场。blockMeshDict里用了很猛的simpleGrading,比如某个方向给到 20,单元尺寸跨度太大,最小单元那边库朗数爆掉;或者deltaT是照最大网格尺寸估的,忽略了壁面附近的加密;还有一种容易被忽略的情况是初始场给了全零速度、但压力边界类型配错,第一步就出现非物理的压力梯度。
解决:先把deltaT砍到原来的十分之一跑二十步,看还炸不炸,炸就继续砍,这条能最快区分是时间步问题还是别的问题。同时打开自适应步长,controlDict里设adjustTimeStep yes和maxCo 0.5,让求解器自己压步长。如果压了还炸,回去看simpleGrading,把它改成 5 以内再试。
5.3 速度残差降下去了,continuity 卡在高位不动
现象:Ux、Uy的初始残差已经降到 1e-5,continuity一直在 1e-2 附近横盘,怎么跑都不降。
原因:压力方程没有钉住参考点,或者参考点选在了流动剧烈的位置;fvSolution里p的relTol设得太松(比如 0.5),内层根本没解到位;nCorrectors只给了 1,压力速度耦合不充分。
解决:确认PISO块里有pRefCell 0和pRefValue 0,并且 0 号单元不在你关心的区域;把p的relTol收到 0.05 或 0.01;nCorrectors加到 2 或 3。改完看日志里每次压力迭代的 initial residual 是不是在稳步下降,如果第一次就几乎不动,那说明是边界条件或者源项的问题,不是求解器设置。
5.4 refineMesh 一跑,整个域都被加密了
现象:本来只想加密中间一小块,结果单元数从几百变成几千,全算例的网格尺寸都变了。
原因:refineMesh在没读到refineMeshDict或者cells列表为空时,默认行为是对全部网格加密。而cells为空的常见原因是topoSetDict里的name和refineMeshDict里的cells拼写不一致,或者topoSet压根没跑成功,constant/polyMesh/sets/下没有对应的 cellSet 文件。
解决:跑完topoSet之后先ls constant/polyMesh/sets/,看文件在不在、名字对不对,再打开文件确认单元数量是否合理。确认无误再refineMesh -overwrite。每次加密前备份polyMesh,改错了直接覆盖回来比重建快得多。
5.5 function object 写进去了,postProcessing 里却是空的
现象:controlDict里加了fieldMinMax,求解正常结束,postProcessing/下没有对应目录,或者目录在但文件是空的。
原因:写得比较杂。常见的是
本文还有配套的精品资源,点击获取