简介:面向 OpenFOAM 初学者的入门学习笔记,内容集中在一份 docx 文档中,系统梳理从命令行约定到边界条件的相关知识,适合刚接触开源 CFD 求解器、需要建立整体认知框架的读者。内容从 Alias 快捷命令与求解器分类讲起,介绍 src 目录中的库函数与常微分方程的数值求解思路;算例部分以 Cavity flow 为线索,解析算例目录结构、时间与对流及 Laplacian 离散格式的取舍,以及 relTol、tolerance、pRefCell 等求解器参数的含义。工具章节按前处理与后处理分类,逐一说明 checkMesh 网格质量检查、setFields 初始场、createBaffles、refineMesh 加密,以及 co、vorticity、yPlus、sample、topoSet 等命令的用途;Function Objects 中 forces、forcesCoefficients、probe 的配置方式与 inletOutlet、movingWallVelocity、fixedFluxPressure 等边界条件类型也一并收录。压缩包共 1 个 docx 文件,约 55.87MB,图文并排便于对照学习,已有 1144 人学习。
1. 拿到 OpenFOAM 入门资料先别翻公式:我第一件事是把 cavity 跑起来
前段时间有位做建筑风环境的同行发来一套 OpenFOAM 入门笔记,目录排得很整齐:alias 命令、求解器、src 下的库函数、cavity 算例、离散格式、Utilities、function objects、边界条件、湍流模型,最后还带 icoFoam 和 interFoam 的代码解读。他问我的第一个问题不是 PISO 怎么推,而是「为什么我打开 paraFoam 里面什么都没有」。这个顺序很典型:OpenFOAM 的真正门槛不在方程,在环境变量、目录约定、字典文件命名和一堆短命令上,这些东西光看笔记记不住,必须让一个自带算例先跑通。这套资料适合两类人:从商软转过来、习惯点鼠标的;能读 C++ 但没手写过 fvSchemes 的。下面按我带人的顺序拆一遍,从 cavity 开始,一直走到能判断一次计算到底能不能用。
2. 环境与算例骨架:alias、三个目录和 cavity 的第一条命令
2.1 alias 与 $FOAM_TUTORIALS:把路径问题一次压平
OpenFOAM 装完之后最容易翻车的地方不是求解器,是环境变量没加载。它的可执行文件散落在$FOAM_APP/bin、$FOAM_UTILITIES这些目录里,靠一个 bashrc 把 PATH、LD_LIBRARY_PATH 和一堆FOAM_*变量串起来。每次开新终端手动 source 一次太麻烦,我一般直接写进~/.bashrc,再顺手定义几个 alias。别小看这几行,路径写错一次,后面所有报错都会往错误的方向猜。
# ~/.bashrc 末尾,交互式登录时加载 OpenFOAM 环境 source /opt/openfoam/etc/bashrc # 常用别名,本质是降低手敲路径出错的概率 alias tut='cd $FOAM_TUTORIALS' # 自带算例根目录 alias src='cd $FOAM_SRC' # 库函数源码,kEpsilon.H 这类文件在这里 alias app='cd $FOAM_APP' # 求解器源码,icoFoam.C 在这里 alias ck='checkMesh -allGeometry -allTopology' alias clean='foamCleanCase' # 清掉时间目录,保留 0/constant/system这段配置里真正有用的是三个变量:$FOAM_TUTORIALS指向自带算例库,cavity、damBreak 这些都能从这里复制;$FOAM_SRC是库函数源码,看湍流模型实现时离不开;$FOAM_APP放求解器主程序。alias 只在当前 shell 生效,不会被子进程继承,所以写批处理脚本时不能依赖它,必须写全路径或者显式 source。
注意:
foamCleanCase会删掉所有时间步目录,只留0、constant、system。跑了一段时间想重新开始可以用,但如果0目录被 setFields 改过,删完就回不到原始初值了,先备份。
2.2 cavity 三个目录:0、constant、system 各管什么
OpenFOAM 的算例结构是强约定的,求解器只认这三个目录名,改成别的直接找不到文件。cavity 是最小的完整例子,正好用来建立肌肉记忆。
| 目录 | 管什么 | cavity 里的具体文件 | 什么时候改 |
|---|---|---|---|
0/ | 初始场与边界条件 | U、p | 每个新算例都要改 |
constant/ | 网格与不随时间变的物性 | polyMesh/、transportProperties | 换几何、改粘度时 |
system/ | 求解控制字典 | controlDict、fvSchemes、fvSolution、blockMeshDict | 调格式、调残差时 |
0/里的文件名就是场名,U是速度矢量场,p是压力场,文件内部用boundaryField给每个 patch 单独设边界。constant/polyMesh是 blockMesh 生成的网格,包含 points、faces、owner、neighbour、boundary 五个文件,少一个都读不进去。system是三个字典各管一段:fvSchemes 管离散格式,fvSolution 管线性求解器和算法参数,controlDict 管起止时间、时间步长、写盘频率和 function objects。
新手最常见的误操作是把边界条件写到constant里,或者把fvSolution放到算例根目录。前者不会报错但完全不生效,后者会直接提示找不到字典。判断方法很简单:任何一个字典读不到,求解器启动阶段就会打印文件名和搜索路径,照着路径放回去就行。
2.3 paraFoam 打不开算例的两种走法
这是笔记里专门记了一笔的问题:paraFoam 启动之后 pipeline 里空空如也。原因是 OpenFOAM 的算例目录本身不是 ParaView 认识的格式,需要一个.OpenFOAM后缀的空文件当入口,文件内容是什么都无所谓,ParaView 只拿它定位目录。
# 方式一:paraFoam 自己建入口文件并启动,-case 后面是算例根目录 paraFoam -case $FOAM_TUTORIALS/incompressible/icoFoam/cavity/cavity # 方式二:先手工造空文件,再用 paraview 打开它 cd $FOAM_RUN/cavity touch cavity.OpenFOAM paraview cavity.OpenFOAMtouch生成的是零字节文件,不用往里面写东西。-case参数要给算例根目录,不是0目录,给错了 paraFoam 会在0下面再找一层0,然后静默失败。如果两种方式都打不开,先确认echo $FOAM_RUN有输出,没有输出说明环境没加载,问题不在 ParaView。
提示:老版本 OpenFOAM 里
paraFoam是一个包装脚本,新版本可能提示用paraview加.OpenFOAM文件。两种方式效果一样,后者更稳,因为它不依赖包装脚本里的环境假设。
2.4 blockMesh 到 icoFoam:一次完整跑通的命令序列
环境通了、目录认识了,接下来把 cavity 完整跑一遍。这一步的目的不是拿结果,是建立「改哪个文件、跑哪条命令、看哪个输出」的对应关系。
# 复制自带算例到工作目录,别在 tutorials 里直接改 cp -r $FOAM_TUTORIALS/incompressible/icoFoam/cavity/cavity $FOAM_RUN/ cd $FOAM_RUN/cavity blockMesh # 读 system/blockMeshDict,生成 constant/polyMesh checkMesh # 先体检,别等发散了再回头怀疑网格 icoFoam # 求解,信息直接打到终端 foamLog log.icoFoam # 从日志提取残差,生成 logs/ 目录blockMesh只做一件事:按 blockMeshDict 里的顶点、块、边界定义生成网格。它跑完会打印网格单元数、面数、边界 patch 列表,单元数为 0 就说明块定义有问题。checkMesh放在求解之前,是我带人时强制的一步,cavity 这种规则网格当然没问题,但换成导入的复杂几何,非正交角和长宽比的问题必须在计算前暴露。icoFoam的输出包含每个时间步的库朗数(Courant Number)和每个场的初始、最终残差,库朗数超过 1 且持续上涨,基本可以准备重跑了。
foamLog把终端日志里的残差抽成logs/下的 dat 文件,后面画收敛曲线用。如果你想同时看终端和留日志,用重定向:
icoFoam > log.icoFoam 2>&1 & tail -f log.icoFoam这样终端只留一个 tail,日志完整落盘,中断了也能从断点看跑到哪一步。
3. 离散格式与求解器参数:CrankNicolson 的 θ 和 relTol 的取舍
3.1 时间项:CrankNicolson 的 θ 与 backward 的有界性
时间离散在fvSchemes的ddtSchemes里定义,cavity 默认是 Euler,一阶精度、绝对有界、最不容易出事。想要二阶就得换,但换哪个有讲究。
CrankNicolson 是隐式二阶格式,格式名后面跟的那个数就是 θ,代表隐式权重。θ=1 是纯 Crank-Nicolson,二阶但容易在陡梯度处振荡;θ=0 退化成 Euler,一阶;θ=0.9 是笔记里推荐的值,保留接近二阶的精度,同时引入一点数值耗散压住振荡。它不是一个「松弛因子」,不要和 fvSolution 里的 relaxationFactors 混为一谈。
backward 也是二阶,但它是无界格式,对解的光滑性有要求。笔记里写得很直接:backward 存在解的无界性问题,一般不推荐,尤其是有界场。有界场指的是物理上不能出现负值或超过 1 的量,比如湍动能 k、耗散率 epsilon、VOF 相分数 alpha.water。这些场用 backward,很容易在几步之内出现负的 k,然后整个计算崩掉。
ddtSchemes { default CrankNicolson 0.9; // θ=0.9,二阶为主,兼顾收敛 }这段字典只改一行,但影响的是每个时间步的推进方式。θ 调低到 0.5 以下,耗散明显增加,结果会更平滑但也更模糊;调到 1.0,精度最高,但对时间步长和网格质量都更敏感。我的习惯是:先用 Euler 跑通,确认边界和网格没问题,再切到 CrankNicolson 0.9 做正式计算,两步之间对比一下残差曲线的形状,如果切格式后残差突然抬高一个量级,说明当前网格撑不住二阶。
注意:有界场(k、epsilon、alpha.water、nut)优先用 Euler 或 CrankNicolson,不要图精度上 backward。这是血泪经验,不是理论洁癖。
3.2 对流项:默认 none 之后,按场选格式
divSchemes管的是对流项(散度项)的离散。笔记里有一句话很关键:一般不推荐特定的离散格式,需要根据不同的问题和方程项选择,把默认格式设置为 none。default none的意思是「没显式指定的项一律报错」,这不是偷懒,是逼你把每一项都想清楚。
divSchemes { default none; div(phi,U) Gauss linearUpwind grad(U); div(phi,k) Gauss upwind; div(phi,epsilon) Gauss upwind; div((nuEff*dev2(T(grad(U))))) Gauss linear; }逐项说。div(phi,U)是动量方程的对流项,Gauss表示在面上做高斯插值,linearUpwind是二阶迎风,grad(U)是它需要的梯度修正项,比纯 upwind 精度高,比 linear 稳定。div(phi,k)和div(phi,epsilon)用一阶upwind,因为湍流量本身有界性要求高,宁可损失精度。最后一项是粘性应力里的特殊项,用Gauss linear就够。
如果default不设成 none,而是设成linear,求解器不会报错,但你会发现某些项悄悄用了中心差分,在高雷诺数下直接振荡发散,而且日志里看不出来。这就是 default none 的价值:把隐式选择变成显式声明。
3.3 Laplacian 与 snGrad 修正
laplacianSchemes管扩散项,一般就一行:
laplacianSchemes { default Gauss linear corrected; } snGradSchemes { default corrected; }corrected表示做非正交修正,网格越歪越需要。另外两个选项:orthogonal只适用于正交网格,checkMesh 报最大非正交角小于 20 度才敢用;limited 0.5是限制修正量,系数在 0 到 1 之间,值越小越稳、越耗散。我一般先corrected,如果残差在某个时间步反复抬升、库朗数也不高,再退到limited 0.5试一次,能压下去就说明是非正交修正引起的。
snGradSchemes是面法向梯度,和 laplacian 配套。两者设成不一致时,求解器不会报错,但扩散项的精度会悄悄掉一档,这种问题只能靠对比两种设置的结果发现。cavity 这种正交网格上差异很小,换成溃坝或者建筑风场就明显了。
3.4 fvSolution 三个残差参数:relTol、tolerance、pRefCell
fvSolution里的solvers块决定每个场用哪个线性求解器、收敛到多少。三个参数最容易被误解。
| 参数 | 含义 | 典型值 | 设错会怎样 |
|---|---|---|---|
relTol | 相对残差,本次迭代残差与初始残差之比 | 0.1(p)、0(U) | 设 0 不是更精确,只是关掉相对判据 |
tolerance | 绝对残差 | 1e-6 到 1e-8 | 设太大方程没解透,设太小迭代次数飙升 |
pRefCell | 压力参考点编号 | 0 | 不设,压力场整体漂移 |
relTol设 0 的意思是「不再用相对判据」,不是「精度更高」。真正决定精度的是tolerance。p 用 0.1 是因为 PISO 算法里压力方程要解多次,前面几次不需要解到底,最后一次才用严格容差;U 每个时间步只解一次,设 0 让它一次性收敛到位。
pRefCell和pRefValue是配套的。不可压流动的 N-S 方程只确定压力的梯度,压力绝对值有一个常数自由度。不给参考点,压力场会整体漂移,表现为每次计算的 p 值都不一样但流场一样。笔记里写「OpenFOAM 求解的压力是相对值,不是绝对值」,说的就是这件事。设一个单元编号为参考点、压力值设 0,等于把这个自由度固定住。
solvers { p { solver GAMG; tolerance 1e-06; relTol 0.1; } U { solver smoothSolver; smoother symGaussSeidel; tolerance 1e-05; relTol 0; } } PISO { nCorrectors 2; nNonOrthogonalCorrectors 0; } pRefCell 0; pRefValue 0;nCorrectors 2表示 PISO 里压力校正做两次,cavity 这种简单算例够用;nNonOrthogonalCorrectors是每步额外解几次压力来修正非正交误差,正交网格设 0,歪网格设 1 到 2。这两个数调大都会明显增加单步耗时,别一上来就堆到 5。
4. Utilities 与 Function Objects:从 checkMesh 到 forces 的一条取数链
4.1 checkMesh:出结果之前先做一次体检
checkMesh是整份资料里我最看重的一个工具,没有之一。它检查网格的拓扑、几何、边界信息,输出里重点看四项:最大长宽比(max aspect ratio)、最大非正交角(max non-orthogonality)、最小和最大面面积、最小和最大体积,最后一行Mesh OK才是通行证。
checkMesh -allGeometry -allTopology -case $FOAM_RUN/cavity两个参数分别打开几何和拓扑的完整检查,默认只做基础检查。动网格算例要在计算过程中周期性调用,因为网格变形到一定程度会失效,等到求解器报负体积已经晚了。我一般会在 controlDict 里挂一个meshQualityfunction object,让它每若干步自动跑一次,比手动查省事。
4.2 setFields 与 topoSet:溃坝初始相怎么从空气里抠出水
溃坝(damBreak)这类两相算例,初始时刻整个区域是空气,只有左侧一块是水。setFields就是干这个的:先在system/setFieldsDict里把全域设成空气,再定义一个几何区域覆盖成水。顺序不能反,反过来水会被后面的全域设置抹掉。
// system/setFieldsDict defaultFieldValues ( volScalarFieldValue alpha.water 0 ); regions ( boxToCell { box (0 0 -1) (0.1461 0.292 1); fieldValues ( volScalarFieldValue alpha.water 1 ); } );setFieldsbox后面两组坐标是(xmin ymin zmin)和(xmax ymax zmax),z 方向给 -1 到 1 是为了覆盖二维算例的厚度。defaultFieldValues先执行,regions后执行,后者覆盖前者。跑完setFields会提示改了多少个单元,数目为 0 就说明 box 坐标写错了,或者算例本身不在这个坐标范围内。
topoSet是更通用的网格集合工具,能生成 cellSet、faceSet、pointSet,后面 refineMesh、createBaffles、setFields 都可能用到它。
// system/topoSetDict actions ( { name cellZone1; type cellSet; action new; source boxToCell; box (0 0 0) (0.1 0.1 0.1); } );topoSet topoSet -dict system/topoSetDict.baffle # 需要多套字典时显式指定action常见取值为new、add、remove,remove是把已有的集合清掉;source除了 boxToCell,还有 cylinderToCell、faceToCell 等。同一个字典文件可以放多个 action,按顺序执行。
4.3 createBaffles:内部面转边界面,配 topoSet 用
先说一个拼写坑:笔记原文写的是creatBaffles,少一个 e。实际命令是createBaffles,照着笔记敲会直接command not found。这种拼写问题在命令行工具里非常常见,遇到找不到命令先去$FOAM_APP/bin下ls | grep一下。
createBaffles 的作用是把内部面(interior face)转成两个重合的边界面,常用于壁面、隔板、周期面。它必须配合一个已经存在的 faceZone 或 faceSet 使用,所以流程是:topoSet 选面,createBaffles 转面。
// system/createBafflesDict internalFacesOnly true; baffles { baffle1 { type faceZone; zoneName baffleFaces; // 必须与 topoSet 生成的 faceZone 同名 patchPairs { type wall; neighbourPatch baffle1; } } }topoSet -dict system/topoSetDict.baffle createBaffles -overwriteinternalFacesOnly true表示只处理内部面,边界面跳过;zoneName对不上是最常见的失败原因,报错信息会提示找不到 zone,但不会告诉你实际有哪些 zone,得自己topoSet -dict ... -list查。-overwrite表示直接覆盖0目录里的场文件,不加的话会在新时间目录里生成一份,容易和原文件混。
4.4 refineMesh:refineMeshDict 与 -overwrite
refineMesh直接敲、不带任何参数,会对全域所有网格加密,三维下网格量直接翻 8 倍。cavity 这种小算例还好,换成百万网格的工程算例就是灾难。正确做法是用refineMeshDict指定加密区域和方向。
// system/refineMeshDict coordinateSystem global; globalCoeffs { tan1 (1 0 0); tan2 (0 1 0); } directions ( tan1 tan2 tan3 ); useHexTopology true; geometricCut false; writeMesh false;refineMesh -dict system/refineMeshDict -overwritedirections里列出的方向才会被加密,tan3不需要显式给出,由tan1叉乘tan2按右手定则算出。useHexTopology决定加密后的单元类型,六面体网格设 true,四面体网格设 false。-overwrite和 createBaffles 里一样,直接覆盖当前网格,不生成带时间戳的新目录。加密之后必须重跑checkMesh,加密产生的悬挂节点是网格质量下降的主要来源。
4.5 后处理命令:co、vorticity、yPlus、sample、probeLocations
算完之后要取数。这一组工具在新版 OpenFOAM 里有些被收进了foamPostProcess,老版本是独立命令,两种写法都列出来。
# 库朗数,输出到时间目录下的 Co 场 foamPostProcess -func 'Co' -latestTime # 速度旋度,只算不写文件 vorticity -latestTime vorticity nowrite # 明确不输出场文件 # 壁面 y+,湍流算例必看 foamPostProcess -func 'yPlus' -latestTime # 按 sampleDict 采样 postProcess -func 'sample' -latestTime # 固定点时间序列,按 probesDict 取 postProcess -func 'probes' -latestTimevorticity默认会往时间目录写一个vorticity场,nowrite只做统计不落盘,算大算例时能省不少磁盘。sample必须有system/sampleDict,跑完在postProcessing/下按时间序列生成文件夹,里面是采样线上的数据。probes要system/probesDict,看点可以写多个,只对计算完成的时间目录有效,所以它属于事后工具,不能边算边取。
注意:
sample跑之前先确认system/sampleDict存在,并且没有残留的sampleDict.swp文件。swp 是编辑中断留下的临时文件,某些情况下会干扰读取,rm -f system/*.swp清掉再跑。
4.6 controlDict 里的 function objects:forces、forcesCoefficients、fieldMinMax
function objects 是计算过程中实时取数的机制,挂在controlDict的functions块里。比事后 postProcess 好在不占额外存储,算完数据已经在postProcessing/里了。
functions { forces1 { type forces; libs ("libforces.so"); patches (building); rho rhoInf; rhoInf 1.225; CofR (0 0 0); log true; writeControl timeStep; writeInterval 10; } forceCoeffs1 { type forceCoeffs; libs ("libforces.so"); patches (building); rho rhoInf; rhoInf 1.225; liftDir (0 0 1); dragDir (1 0 0); pitchAxis (0 1 0); magUInf 10; lRef 1; Aref 1; CofR (0 0 0); log true; writeControl timeStep; writeInterval 1; } fieldMinMax1 { type fieldMinMax; libs ("libfieldFunctionObjects.so"); fields (U p); mode magnitude; log true; writeControl timeStep; writeInterval 1; } }forces计算指定 patch 上的力和力矩,CofR是力矩参考点,建筑风场里一般设在建筑底面中心。forceCoeffs在力的基础上做无量纲化,liftDir和dragDir定义升阻力方向,magUInf是来流速度、lRef是参考长度、Aref是参考面积,这三个参数直接决定 Cl 和 Cd 的绝对值,设错的话系数会差一个量级。fieldMinMax取场的极值和位置,fields那一行要放在log之前,位置不对在某些版本上会直接报错。
还有一个实测问题:fieldMinMax的write开关不管设成 yes、no 还是任意字符,数据都会写进postProcessing/。笔记里也标了「问题没解决」。我试过的规避办法是把writeInterval调大,减少写盘次数,事后需要再单独后处理。这不是标准答案,但比反复改开关有效。
5. 避坑与排查:五个让算例白跑的细节
5.1 先看日志再看残差:三条命令定位发散
计算崩了,第一反应不应该是改格式或者调残差,而是看日志。我固定的顺序是三条命令:
# 1. 看最后 <p> <a href="https://download.csdn.net/download/weixin_42215140/88988229" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>