OpenFOAM二次开发教程(08):物性与热物性模型扩展——thermophysicalProperties 解剖
版本与事实声明
hConst的字典项(Cp、Hf、Tref、Href等)与janaf的字典项(Tlow、Thigh、Tcommon、lowCpCoeffs、highCpCoeffs)来自 OpenCFD/ESI 官方文档站(doc.openfoam.com/2606)的热物性模型参考页;hPolynomial的字典项(Hf、Sf、CpCoeffs<8>)同源。- 热力学模型族(
eConst、hConst、hPolynomial、janaf等)的对照来自官方文档与官方课程讲义中对该库的分类说明。- 具体模型类型名会随版本增删,落笔前请用
foamToC与官方文档核对本机可用清单;本文不对未在官方文档中确证的模型作断言。- 文中一切物性数值均为示例性占位,须替换为工质真实数据(来自权威物性数据库或文献),不代表任何标准规定。
一句话结论:OpenFOAM 的热物性不是写死在代码里的常数,而是由constant/thermophysicalProperties字典选定的模型类型在运行期通过工厂机制实例化——hConst用常数比热加生成焓、hPolynomial用多项式系数、janaf用分段 JANAF 系数;要扩展物性,只需新增一个派生类并注册进类型表,而不是修改任何已有模型。
〇、本篇要解决的认知问题
- Q1:为什么物性要做成"字典选类型 + 工厂创建对象",而不是直接写常数?
- Q2:
hConst、hPolynomial、janaf三类热力学模型的字典格式分别是什么?各自适合什么场景? - Q3:热力学模型与输运模型有什么区别?它们在字典里怎么分块?
- Q4:工厂机制是怎么工作的——为什么我在字典里写一个类型名,代码就能找到对应实现?
- Q5:自定义物性的规范做法是什么(四步法)?改错物性会发生什么?
一、机制解析
1.1 为什么物性必须"可配置"
把物性写死在 C++ 代码里的三个后果:
- 每换一种工质就要重编译——工程上不可接受;
- 无法做敏感性分析——你没法在脚本里改物性再批量跑(第 19 篇 DOE 的前提被砍掉);
- 无法审计——一年后没人知道模型里用的是哪个物性数据。
OpenFOAM 的解法是:代码只定义"物性模型的接口与算法",具体数值全部来自字典。于是换工质 = 改字典,做敏感性分析 = 脚本改字典,审计 = 看字典文件。
为什么这对你重要:这决定了二次开发的姿势。你要"加一种物性模型"时,加的是算法实现;你要"用另一种物性"时,改的是字典。不要为了换物性去改代码——那是典型的"用错工具"。
1.2 热力学模型三件套:hConst / hPolynomial / janaf
官方文档给出的字典格式如下(三者的差别在"比热如何处理"):
①hConst——常数比热(最简)
字典项(来自官方文档):
| 项 | 含义 | 单位 |
|---|---|---|
Cp | 定压比热 | J/(kg·K) |
Hf | 生成焓 | J/kg |
Tref | 参考温度 | K(默认取标准温度Tstd) |
Href | 参考焓 | J/kg |
适合:温度范围窄、比热近似恒定的场合(如常温液体、部分气体工程估算)。
②hPolynomial——多项式比热
字典项(来自官方文档):
thermodynamics { Hf <scalar>; Sf <scalar>; CpCoeffs<8> (<c1> <c2> <c3> <c4> <c5> <c6> <c7> <c8>); }比热由多项式系数给出(CpCoeffs是比热多项式的系数数组),再由其积分得到焓与熵。适合:比热随温度平滑变化的工质,且你有拟合系数。
③janaf——分段 JANAF 系数(工程最常用)
字典项(来自官方文档):
| 项 | 含义 |
|---|---|
Tlow | 低温限 |
Thigh | 高温限 |
Tcommon | 分段共同温度(低温段与高温段的分界) |
lowCpCoeffs | 低温段系数 |
highCpCoeffs | 高温段系数 |
JANAF(热力学数据表)系数在低温段与高温段分别拟合,因此在大温度范围内精度好。适合:燃烧、高温气体、宽温域问题。
经验法则:工程上先用
janaf(如果工质有 JANAF 数据);没有就退到hPolynomial;只有温度范围很窄、比热几乎不变时才用hConst。用hConst做宽温域问题是最常见的"物理错误伪装成数值错误"的案例。
1.3 热力学模型 vs 输运模型:字典里的两块
thermophysicalProperties通常分成若干子字典,最重要的两块是:
thermodynamics:管"能量相关"——比热、焓、熵(上面三类模型都在这里选)。transport:管"黏性/导热/扩散"——黏度、热导率等如何随温度变化(输运模型)。
为什么必须分开:它们回答不同的物理问题。热力学模型决定"能量状态",输运模型决定"动量与能量如何传递"。用错输运模型(例如高温气体用了常数黏度)不会立刻报错,但会让流动结构偏离真实——又是无声错误。
最佳实践:把"选哪个模型"写进项目文档,并在foamToC输出里核对类型名确实存在(不同版本的注册类型会变)。
1.4 工厂机制:字典类型名如何变成对象
这是 OpenFOAM 最优雅的设计之一,值得单独理解:
字典里写: thermodynamics { type hConst; ... } │ ① 运行期读取类型名 "hConst" ▼ ② 在“已注册类型表”里查找该名字(表由各模型源文件的注册宏在库加载时构建) ▼ ③ 找到 → 调用该类的构造函数,用字典中的子项初始化 ④ 找不到 → 报 "Unknown thermodynamics type hConst"关键推论(这解释了本系列反复出现的几条铁律):
- “类型名存在"依赖"对应库被加载”。这就是铁律 5(自定义库必须在
controlDict的libs里加载)的根本原因——库没加载,类型表里就没有你的类型名。 - 类型名拼错 = 运行期报错,而且报错发生在"构造对象时",可能在你看来"代码明明是对的"。
- 扩展物性的正确方式是"新增类 + 注册",不是"改原有类"——这样多个模型可以共存、可互相切换。
1.5 自定义物性的四步法
按通用做法(具体文件名与宏名以本机源码为准):
- 复制最接近的官方模型目录(如
hConst)到用户目录,重命名(如MyConstThermo); - 改类名与
TypeName:让类名与注册的类型名一致(差异要严格跟随官方写法); - 改写
Make/files与Make/options:产物用LIB = $(FOAM_USER_LIBBIN)/libMyThermo,Make/options用LIB_INC/LIB_LIBS(第 02 篇的库变量组),并sinclude用户模块路径规则; wmake libso编译,在controlDict的libs中加载,字典里把type换成你的新类型名,跑官方回归算例验证。
反直觉点:第 4 步最关键却最容易被忘。"编译成功但运行时报 Unknown type"99% 是忘了
libs加载,而不是编译失败。
二、完整代码与逐行剖析
代码 2-1:一个完整的constant/thermophysicalProperties(Janaf 版)
/*--------------------------------*- C++ -*----------------------------------*\ | ========= | | | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox | | \\ / O peration | | | \\ / A nd | | | \\/ M anipulation | | \*---------------------------------------------------------------------------*/ FoamFile { version 2.0; format ascii; class dictionary; location "constant"; object thermophysicalProperties; } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // // 顶层:指定“热物性包”的组合方式(具体可用名以官方文档与本机 foamToC 为准) type heRhoThermo; // 示例:以焓为变量的可压缩热物性组合 // ---------- 热力学:管比热、焓、熵 ---------- thermodynamics { type janaf; // 分段 JANAF 系数 // 分段温度:低温限 / 分段点 / 高温限 Tlow 200; // 示例值,单位 K —— 必须替换为工质真实数据 Thigh 6000; Tcommon 1000; // 低/高温段系数:顺序与个数必须严格符合官方格式 lowCpCoeffs ( 3.0 1.0e-3 0 0 0 0 0 ); // 占位示例,非任何真实工质 highCpCoeffs ( 2.5 1.5e-3 0 0 0 0 0 ); // 占位示例 Hf 0; // 生成焓(示例) Sf 0; // 标准熵(示例) } // ---------- 输运:管黏度、导热等 ---------- transport { // 输运模型类型名以官方文档与本机 foamToC 输出为准(不同版本可用名可能不同) // type 与 系数 示例: // type const; // mu <value>; // Pr <value>; } // ---------- 其它(视组合而定)---------- // energy / equationOfState / mixture 等子字典是否存在, // 取决于顶层 type 的选择;以官方同类算例为模板最稳。 // ************************************************************************* //逐行剖析:
FoamFile段里location "constant"与object thermophysicalProperties:OpenFOAM 用object做字典自校验,名字写错可能触发类型/位置检查。- 顶层
type(示例heRhoThermo)决定"热物性包"的组合方式,它决定了需要哪些子字典(thermodynamics、transport,可能还有equationOfState/energy/mixture)。实践建议:不要自己拼装,直接复制官方同类算例并替换数值——这是最快也最不容易错的方式(铁律 1 的延伸)。 thermodynamics用janaf:三段温度 + 两段系数是 JANAF 的标准结构。系数个数与顺序必须与官方格式严格一致,多写或少写一个数会让模型静默错位(甚至"算得出来但完全不对")。lowCpCoeffs/highCpCoeffs的数值我写成占位示例并明确声明:这是"不臆造数值"纪律在现场的体现。真实工质系数请查权威热力学数据表或官方算例自带的物性数据。transport块保留为注释并注明"类型名以官方文档与foamToC为准":不同版本输运模型可用名会变,本文不对未在本轮官方文档中逐项核实的具体类型名作断言。- 最后一段注释提醒"子字典是否存在取决于顶层
type":这是配置此类字典最容易踩的坑——照抄一个不同顶层的算例,会缺一堆子字典。
代码 2-2:物性核对与自省脚本(POSIX Shell)
#!/bin/sh# check_thermo.sh —— 物性配置核对:语法、类型索引、量纲一致性线索# 用法:sh check_thermo.sh <算例路径>set-eucase="${1:?用法:sh check_thermo.sh <算例路径>}"dict="$case/constant/thermophysicalProperties"echo"== 1. 字典是否存在 =="[-f"$dict"]&&echo"[OK] 找到$dict"||{echo"[FAIL] 不存在(不可压缩算例可能用 transportProperties)";exit1;}echo"== 2. 用 foamDictionary 结构化读取关键项 =="# -entry 支持点号路径访问嵌套子字典foreintypethermodynamics.type thermodynamics.Tlow thermodynamics.Thigh thermodynamics.Tcommon;doprintf" %-26s = ""$e"foamDictionary-entry"$e"-value"$dict"2>/dev/null||echo"(该版本/该组合下不存在此键)"doneecho"== 3. 列出本环境可用的热力学模型类型名 =="# 以工具输出为权威清单,避免凭记忆写类型名(铁律 1)foamToC-tablethermodynamics2>/dev/null\||echo" (本版本可能不支持该 -table 名,请查官方文档确认正确的表名)"echo"== 4. 量纲线索核对(人工必看)=="echo" 注意检查:Tlow/Tcommon/Thigh 是否为递增且覆盖你的工况温度范围"echo" Cp/Hf/Sf 的量纲是否与所用热物性‘包’的自洽要求一致"逐行剖析:
- 用
foamDictionary -entry ... -value读嵌套项(如thermodynamics.type):这是结构化核对,比grep可靠(第 04 篇的工具延续)。 foamToC -table thermodynamics用来列可用类型名:把"能写什么类型名"从记忆问题变成工具问题。若该表名在本版本不支持,脚本明确提示"查官方文档确认表名",而不是硬编码一个可能错的表名。- 第 4 步是人工核对清单而非自动断言:温度分段是否递增、是否覆盖工况,这些属于"工程判断",脚本不该越权给结论——这也是"不臆造判据"的体现。
代码 2-3:自定义物性类的骨架(C++,示意)
/*---------------------------------------------------------------------------*\ MyConstThermo.H —— 自定义常数热力学模型(骨架,仿 hConst 的写法) 注意:类名、TypeName、注册宏、构造签名必须与所继承的基类契约一致; 请务必以本机 $FOAM_SRC 中同名官方模型的源码为模板,不要凭记忆补全。 \*---------------------------------------------------------------------------*/#ifndefMyConstThermo_H#defineMyConstThermo_H#include"thermodynamicConstants.H"// 以本机实际 include 路径为准(Tstd 等常量)#include"thermo.H"// 热力学基类头(本机路径以源码为准)namespaceFoam{namespacethermophysicalModels{classMyConstThermo:publicthermo<MyConstThermo>// 继承官方热力学基类模板(写法以本机为准){// ---- 私有数据:全部来自字典 ----scalar Cp_;// 定压比热scalar Hf_;// 生成焓scalar Tref_;// 参考温度scalar Href_;// 参考焓public:// 类型名:注册用的规范名字(字符串必须与字典中 type 的值完全一致)TypeName("MyConstThermo");// ---- 构造函数:从字典读取物性(读法与官方 hConst 保持一致)----MyConstThermo(constdictionary&dict);// ---- 必须实现的接口(比热、焓、熵等)----scalarCp(constscalar p,constscalar T)const{returnCp_;}scalarHs(constscalar p,constscalar T)const{/* 按基类契约实现 */}scalarS(constscalar p,constscalar T)const{/* 按基类契约实现 */}// ---- 写回字典(保证配置可复现)----voidwrite(Ostream&os)const;};}// End namespace thermophysicalModels}// End namespace Foam#endif逐行剖析:
- 类名与
TypeName("MyConstThermo")的字符串必须与字典里的type完全一致——这是工厂机制的接口契约。 - 私有数据全部来自字典构造:这是"物性可配置"的实现方式,不要在这里写任何硬编码数值。
- 接口函数(
Cp、Hs、S等)必须按基类契约实现:名字、参数、返回类型都不能改。这就是本系列反复说的"继承契约"——改错了编译失败,漏实现了运行期崩溃。 write(Ostream&)把配置写回字典:这是"可复现性"的细节,官方模型的标配。- 我用注释明确标注"请以本机源码为模板,不要凭记忆补全":因为不同版本基类头文件路径与模板写法有差异,这类细节正是最容易编造出错的地方。这一段是"骨架示意",不是可复制即编译的成品。
三、常见报错与排查
报错 3-1:--> FOAM FATAL ERROR: Unknown thermodynamics type MyConstThermo。
现象:字典里写了自定义类型名,运行时报找不到。根因:顺序上最常见的是库未加载(controlDict的libs里没写你的库),其次才是类型名拼写不一致或未注册(铁律 5)。解法:先在controlDict的libs中加入"libMyThermo.so";再核对TypeName字符串与字典type是否逐字符一致;最后确认wmake产物确实在$FOAM_USER_LIBBIN。
报错 3-2:物性系数个数不匹配——算得出来但数值完全不对。
现象:不报错,结果明显异常。根因:lowCpCoeffs/highCpCoeffs的系数个数与官方格式不一致,导致模型按错位读取;或温度分段Tlow/Tcommon/Thigh写反/不递增,导致选错分段。解法:以官方算例的物性文件为模板逐项对齐;用代码 2-2 打印各项并人工核对分段递增性与覆盖范围。
报错 3-3:--> FOAM FATAL IO ERROR: keyword ... is undefined in dictionary ...thermophysicalProperties。
现象:缺子字典或子项。根因:顶层type变了但子字典没跟着变(例如从常数比热组合换成含状态方程的组合,就会多要求equationOfState等)。解法:按顶层type选择对应官方算例模板,补齐所需子字典;不要"在半套配置上打补丁"。
报错 3-4:换物性后结果剧变,但代码没改。
现象:仅改字典,结果差异巨大。根因:物性单位或量纲与预期不符(例如比热填成了"每摩尔"而非"每千克"值),或误用了常数比热去覆盖宽温域工况。解法:先用代码 2-2 核对关键项;把"比热/焓的单位"写进项目文档;宽温域问题改用janaf或hPolynomial并核对系数来源。
报错 3-5:编译自定义物性库报undefined reference或no such file(基类头找不到)。
现象:wmake libso失败。根因:Make/options里缺LIB_INC路径(缺-I.../lnInclude或热物性库的 include 路径),或LIB_LIBS缺对应库。解法:直接复制官方同模型的Make/options再改库名(这是最快且最不易错的路径);确认用的是库变量组LIB_INC/LIB_LIBS而不是EXE_*(第 02 篇的坑)。
四、动手练习
- 练习 1(字典核对):用代码 2-2 检查一个官方可压缩算例的
thermophysicalProperties。判定:能读出顶层type、thermodynamics.type、温度分段三项;foamToC(或官方文档)能确认该热力学类型名在本环境存在。 - 练习 2(三方对比):把同一个算例的热力学模型分别设为
hConst、hPolynomial、janaf(用同一工质的合理数据),各跑一次并记录某个监测量的变化。判定:能观察到宽温域工况下三者的差异;能说出"为什么hConst在宽温域下误差最大"。 - 练习 3(配置完整性):把顶层
type换成另一个官方支持的组合(例如从含状态方程的组合换成不含的组合,或反之),观察报错与所需子字典的变化。判定:能列出"换顶层 type 后新增/删除了哪些子字典",并能解释工厂机制如何决定所需配置。 - 练习 4(工厂机制验证):在
controlDict的libs中故意移除自定义或额外的库(若你已有自定义库),或故意把字典type拼错一个字母。判定:能复现Unknown ... type报错,并能分别说明"库未加载"与"类型名拼错"两种根因的区别。 - 练习 5(思考题,无标准答案):设计一个自定义热力学模型的接口清单(不写实现)。验证要点:(a) 是否明确列出需要从字典读取的所有数据;(b) 是否列出必须实现的接口函数(比热、焓、熵等)并为每个标注参数与返回类型需与基类契约一致;© 是否包含
write以支持配置可复现;(d) 是否明确"编译产物落$FOAM_USER_LIBBIN并在controlDict的libs加载"。
五、小结与下一篇预告
本篇解决了第 07 篇留下的工程隐患:物性不该写死在代码里。三条核心认知——物性由thermophysicalProperties字典选型、运行期由工厂机制实例化;热力学模型分hConst(常数比热)、hPolynomial(多项式)、janaf(分段 JANAF)三条路线,宽温域首选janaf;thermodynamics与transport分块管理"能量"与"黏性/导热",不可混淆。加上一条纪律:扩展物性 = 新增类 + 注册 + 加载库(铁律 5)。
第 09 篇《湍流模型架构》把同样的"工厂 + 派生类"思路搬到湍流领域:BasicMomentumTransportModel→RASModel→eddyViscosity的继承链、kOmegaSST与kOmegaSSTBase的分工、correct()的契约——那是你扩展湍流模型之前必须画清的那张图。
本篇认知问题回显(FAQ)
Q1:为什么物性要做成字典选类型而不是写常数?
A:写死在代码里有三重问题:换工质必须重新编译、无法用脚本批量改物性做敏感性分析、无法审计当初用的是哪份物性数据。OpenFOAM 让代码只定义物性模型的接口与算法,具体类型与数值由 constant/thermophysicalProperties 字典给出,于是换工质只需改字典,批量扫描只需脚本改字典,审计只需查看字典文件。二次开发时应牢记:加物性算法才改代码,用另一种物性只改字典。
Q2:hConst、hPolynomial、janaf 的字典格式与适用场景?
A:hConst 用常数比热,字典项有 Cp(定压比热,J/kg/K)、Hf(生成焓)、Tref(参考温度,默认取标准温度 Tstd)、Href(参考焓),适合温度范围窄、比热近似恒定的场合。hPolynomial 用多项式比热,字典项有 Hf、Sf 和 CpCoeffs<8>(比热多项式系数数组),适合比热随温度平滑变化且已有拟合系数的工质。janaf 用分段 JANAF 系数,字典项有 Tlow、Thigh、Tcommon(分段共同温度)、lowCpCoeffs、highCpCoeffs,适合燃烧、高温气体、宽温域问题。工程上优先 janaf。
Q3:热力学模型与输运模型有什么区别?
A:thermodynamics 子字典管能量相关物性,即比热、焓、熵(hConst、hPolynomial、janaf 都在这里选);transport 子字典管黏性与导热等传递相关物性,例如黏度、热导率随温度的关系。二者回答不同物理问题:热力学模型决定能量状态,输运模型决定动量与能量如何传递。用错输运模型(如高温气体用常数黏度)不会立刻报错,但会静默偏离真实流动结构,因此要把模型选择写入项目文档并用官方工具核对类型名。
Q4:工厂机制怎么让字典类型名变成对象?
A:运行期读取字典中的类型名后,在"已注册类型表"中查找该名字,这张表由各模型源文件的注册宏在库加载时构建;找到则调用该类构造函数并用字典子项初始化,找不到则报 Unknown … type。两条推论很重要:类型名存在性依赖对应库被加载,所以自定义库必须在 controlDict 的 libs 中加载(铁律 5);类型名拼错会在构造对象时报错。扩展物性的正确方式是新增类并注册,而不是修改原有类,这样多个模型可共存互切。
Q5:自定义物性的规范做法是什么?
A:四步法。第一步复制最接近的官方模型目录到用户目录并重命名(如仿 hConst 得到 MyConstThermo)。第二步改类名与 TypeName,使注册的类型名与字典中 type 的值严格一致,并按基类契约实现所需接口(比热、焓、熵等)与 write 以支持配置可复现。第三步改 Make/files 与 Make/options,产物用LIB = $(FOAM_USER_LIBBIN)/libMyThermo,使用库变量组 LIB_INC/LIB_LIBS 并 sinclude 用户模块路径规则。第四步用 wmake libso 编译,在 controlDict 的 libs 中加载库,把字典 type 换成新类型名,跑官方回归算例验证。运行期报 Unknown type 时优先检查库是否已加载。