多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

OpenFOAM二次开发教程(12):函数对象开发——coded 与编译型 functionObject

OpenFOAM二次开发教程(12):函数对象开发——coded 与编译型 functionObject OpenFOAM二次开发教程12函数对象开发——coded 与编译型 functionObject版本与事实声明functions条目的结构与继承项region、enabled、log、timeStart、timeEnd、executeControl、executeInterval、writeControl、writeInterval来自 OpenCFD/ESI 官方文档站Function objects页type与libs为必需项的说明来自官方 Doxygen 的Foam::functionObjects命名空间参考页。postProcess工具的用法来自官方文档站foamPostProcess是官方 API 文档中记载的后处理应用。内建函数对象的具体参数随版本演进类型名与可选项以官方文档与本机类型表为准本文不对未核实的参数名作断言。文中数值与监测点均为示例不代表任何标准规定。一句话结论函数对象function objects是 OpenFOAM 的不改求解器就能扩展功能机制——在system/controlDict的functions子字典里用type 编译型还要libs选一个函数对象再用executeControl/writeControl等继承项控制它何时执行与输出开发自己的函数对象则走coded内嵌代码或编译型库两条路后者的库同样必须在libs中加载铁律 5。〇、本篇要解决的认知问题Q1函数对象到底是什么它和fvModels、边界条件有什么共同的设计哲学Q2functions条目里哪些项是必需的、哪些是继承来的可选控制项Q3内建函数对象有哪些常用类型它们能替你做哪些在线后处理Q4coded函数对象怎么用它和codedFixedValue的机制有何共性Q5什么时候必须写编译型函数对象开发流程与验证要点是什么一、机制解析1.1 函数对象OpenFOAM 的插件总线函数对象是在求解过程进行中被调用的可插拔组件。它的调用时机由executeControl决定输出时机由writeControl决定。官方文档对functions条目给出的结构如下必需项标注为type编译型还需要libs其余为继承来的可选控制项functions { name // 你自己起的名字 { // 必需项 type functionObjectType; libs ( libxxx.so ); // 编译型/外部库需要 // 可选继承项 region region0; enabled true; log true; timeStart 0; timeEnd 1000; executeControl timeStep; executeInterval 1; writeControl timeStep; writeInterval 1; } }为什么这对你重要函数对象是最小侵入的扩展方式。想在线统计某个量、想在每个时间步记录监测点、想自动算面积分/体积分——都不需要改求解器。它与边界条件、fvModels、湍流模型共享同一设计哲学接口由基类定义具体实现按字典type选型扩展 新增类 注册 加载库第 08、09、11 篇。1.2 必需项与继承项两类键把上表拆成两类理解就清楚了必需项缺了直接报错项作用type函数对象类型名工厂机制选型libs编译型/外部函数对象所在库内建类型可省自定义必写继承项所有函数对象都支持的通用控制项作用常见取值enabled是否启用true/false调试时常用log是否在日志中输出true/falsetimeStart/timeEnd生效时间窗口数值按时间单位executeControl/executeInterval何时执行、间隔多少如timeStep、writeTime等writeControl/writeInterval何时写出、间隔多少同上region作用于哪个区域多区域算例用最佳实践用timeStart/timeEnd给函数对象加时间窗。例如只在前 1 秒做高密度监测、之后停止——这能避免长期运行中无意义地写大量小文件第 15 篇性能优化的实际手段。1.3 常用内建函数对象官方函数对象族覆盖面很广工程上最常用的几类类型示例作用典型用途fieldMinMax统计某场的最小/最大值及其位置快速确认场是否出现非物理极值probes在指定坐标点采样场值实验对比、监测点时间序列surfaceFieldValue在指定面上做面积分/平均等运算流量、壁面通量、平均压力forces计算作用在壁面上的力与力矩气动/水动力系数fieldAverage时间平均含脉动统计湍流统计量residuals记录各方程残差收敛诊断第 15 篇注意类型名与参数随版本演进。先查官方文档与本机类型表foamToC或官方文档站的 runtime 参考页再写进字典——这是本系列贯穿始终的纪律铁律 1。probes的特殊价值它把监测点时间序列变成文件通常落在postProcessing/下这就是第 10 篇对拍里那个可观测量的规范来源——比grep日志可靠得多。1.4 coded 函数对象字典里写逻辑与codedFixedValue第 11 篇同源OpenFOAM 也提供coded形式的函数对象在字典里内嵌代码无需建工程、无需编译库。它的机制与本系列讲过的内嵌代码一致运行期处理代码块。适用与不适用适合快速验证我想在线算一个自定义量的想法、一次性分析、教学演示。不适合需要复用、需要单元测试、逻辑较长内嵌代码难维护、错误信息不友好。判断标准如果这个统计下个月还要用、别人还要用就直接写编译型如果只是我现在想看一眼用coded。1.5 编译型函数对象开发流程与契约编译型函数对象的开发与第 10 篇的模型库、第 11 篇的边界库完全同构复制官方最接近的函数对象目录例如某个统计类改类名 TypeName 文件名实现/覆盖必要的虚函数不同基类契约不同以本机源码为准常见包括读字典构造、“执行execute”、“写出write”、初始化等Make/files用LIB $(FOAM_USER_LIBBIN)/libMyFOMake/options用LIB_INC/LIB_LIBSwmake libso编译在functions条目的libs中加载字典type换成你的类型名回归验证与内建等价函数对象或与手算值对比。契约要点type字符串与TypeName必须一致库必须加载libs——在functions里也要写libs不要以为controlDict顶层的libs就一定覆盖具体行为以官方文档与实测为准稳妥做法是两处都显式列出执行频率交给executeControl不要在自己代码里写每隔 N 步的硬逻辑——那会绕过 OpenFOAM 的控制机制。反直觉点函数对象的执行与写出是两个独立时机。executeControl决定多久算一次writeControl决定多久落一次盘。二者可以不同例如每步算、每 100 步写这正是它能做高频统计、低频输出的原因。二、完整代码与逐行剖析代码 2-1system/controlDict中的functions配置含内建与通用控制项// ---------------- system/controlDict节选---------------- functions { // ① 残差记录收敛诊断的标配第 15 篇复用 residuals { type residuals; libs ( libutilityFunctionObjects.so ); // 具体库名以官方/本机为准 writeControl timeStep; writeInterval 1; log true; } // ② 场极值监控快速发现非物理极值 fieldMinMax1 { type fieldMinMax; libs ( libfieldFunctionObjects.so ); fields ( U p T ); // 监控哪些场示例 mode component; // 统计模式具体可选项以官方文档为准 writeControl writeTime; log true; } // ③ 监测点采样产出时间序列文件供实验对比与对拍 probes1 { type probes; libs ( libfieldFunctionObjects.so ); fields ( U p ); probeLocations ( (0.05 0.05 0.005) // 示例坐标请按你的算例几何设定 (0.08 0.05 0.005) ); executeControl timeStep; executeInterval 1; writeControl timeStep; writeInterval 10; // 高频统计、低频输出 log false; } // ④ 面上的面积分/平均算流量、通量、平均压力 outletFlow { type surfaceFieldValue; libs ( libfieldFunctionObjects.so ); fields ( phi ); operation sum; // 具体运算名以官方文档为准 surfaceFormat faceZone; // 面集合的指定方式示例 name outlet; // 面/zone 名称示例 writeControl writeTime; log true; } // ⑤ 时间窗控制示例只在前 1 秒做密集采样 earlyProbe { type probes; libs ( libfieldFunctionObjects.so ); fields ( U ); probeLocations ( (0.05 0.05 0.005) ); timeStart 0; timeEnd 1; // 1 秒后自动停止 executeControl timeStep; writeControl timeStep; writeInterval 1; } }逐行剖析每个条目以你自己起的名字为键residuals、fieldMinMax1、probes1…这个名字会出现在日志和postProcessing/目录名里所以起名要能自解释。type是必需项决定工厂选哪个类libs在很多情况下也需要显式给出内建类型往往仍需要写出其所在库具体以官方文档的示例为准本文示例中的库名标注为以官方/本机为准。条目 ③ 演示**“高频统计、低频输出”**executeInterval 1writeInterval 10。这正是 §一.5 反直觉点的实际用法。条目 ④ 演示surfaceFieldValue把出口流量这类面积分自动化。注意operation与面的指定方式surfaceFormat/name的具体可选项随版本不同必须以官方文档为准——我在此处刻意只给示例并加注。条目 ⑤ 演示timeStart/timeEnd时间窗这是控制输出体积的实用开关第 15 篇。全文没有一处硬编码执行步数执行频率一律交给executeControl/executeInterval这是与 OpenFOAM 控制机制协作的规范姿势。代码 2-2coded函数对象的配置骨架// ---------------- system/controlDict 的 functions 中 ---------------- myCodedStat { type coded; // 内嵌代码型函数对象以官方支持为准 libs ( libutilityFunctionObjects.so ); // 名称生成对象的标识日志可见 name myCodedStat; // 代码块内嵌实现具体成员函数名与接口以官方文档/示例为准 code #{ // 说明不同版本的 coded functionObject 提供的接口与必需成员不同。 // 请以官方文档站与本机源码中的 coded 函数对象示例为模板填写 // 不要凭记忆写成员函数名铁律 1。 #}; executeControl timeStep; writeControl writeTime; log true; }逐行剖析type coded走内嵌代码机制与codedFixedValue第 11 篇是同一家族。我故意把code段留为结构占位 明确指路因为coded函数对象要求哪些成员函数如执行、写出因版本而异在这里编出一套成员名就是造假。正确做法是打开官方文档站与本机源码里的coded示例照其骨架填写这正是本篇最想传授的态度。依旧保留通用控制项executeControl/writeControl/log——继承项对任何函数对象都适用这是它比自己写脚本强的地方。代码 2-3编译型函数对象的编译、加载与验证脚本#!/bin/sh# build_fo.sh —— 编译自定义函数对象库、在算例中加载并验证# 用法sh build_fo.sh 源码目录 算例 functions条目名set-eulibdir${1:?用法:sh build_fo.sh 源码目录 算例 条目名}case${2:?缺少算例};fo${3:?缺少 functions 条目名}echo 1. 编译库 cd$libdir;wmake libsols-l$FOAM_USER_LIBBIN/libMyFO*/dev/null21\echo[OK] 库已生成于$FOAM_USER_LIBBIN||{echo[FAIL] 未生成库;exit1;}echo 2. 确认 functions 条目中存在该函数对象且已指定 libs CD$case/system/controlDictfoamDictionary-entryfunctions.$fo-value$CD/dev/null21\echo[OK] 找到 functions.$fo||{echo[FAIL] 未找到条目$fo;exit1;}echo type :$(foamDictionary-entryfunctions.$fo.type-value$CD2/dev/null||echo?)echo libs :$(foamDictionary-entryfunctions.$fo.libs-value$CD2/dev/null||echo(未显式给出若为自定义类型必须补上))echo 3. 运行并检查函数对象是否被创建 cd$casemySolver-case.log.fo21||{echo[FAIL] 运行失败见 log.fo;exit1;}grep-iEfunctionObjects|function object|$folog.fo|head-n15|seds/^/ /||trueecho 4. 检查输出目录后处理产物落点ls-1dpostProcessing/*2/dev/null|seds/^/ /||echo (未发现 postProcessing 目录检查 writeControl/writeInterval)echo 5. 也可用 postProcess 工具离线执行函数对象 echo 示例postProcess -func 函数对象名 -case . 具体用法见官方文档逐行剖析wmake libso 产物在$FOAM_USER_LIBBIN与第 10、11 篇一致的纪律铁律 4。第 2 步用foamDictionary -entry functions.$fo.type读嵌套项用点号路径检查functions里的子项这是foamDictionary在多级嵌套下的实用姿势第 04 篇。第 3 步抓日志里的函数对象创建记录函数对象真的被创建了需要客观证据。第 4 步检查postProcessing/函数对象的产物在这probes的时间序列、surfaceFieldValue的积分结果等。有没有 postProcessing 目录是函数对象是否真正写出数据的硬判据。第 5 步引出postProcess工具它可以在仿真结束后离线执行函数对象官方文档记载其无附加选项时执行 controlDict 中为所有时间目录列出的函数对象。这意味着同一份函数对象配置既可在线用、也可离线用——第 17 篇的后处理聚合会用到。三、常见报错与排查报错 3-1-- FOAM FATAL ERROR: Unknown functionObject type myFO。现象字典里写了自定义函数对象启动报未知类型。根因该条目未指定libs或libs里的库名与实际文件不符或类型名拼写与TypeName不一致铁律 5。解法在functions.name下补libs ( libMyFO.so )必要时同时在controlDict顶层libs列出核对类型名。报错 3-2-- FOAM FATAL IO ERROR: keyword ... is undefined函数对象参数缺项。现象报某函数对象缺参数。根因不同函数对象的必需参数不同例如probes需要probeLocations面运算类需要指定面/zone。解法以官方文档中该函数对象页面的字段表为准逐项补齐不要照抄另一个函数对象的配置块。报错 3-3没有postProcessing/目录或里面是空的。现象函数对象好像没工作。根因writeControl/writeInterval设置导致未到写出时刻就结束或enabled false或timeStart/timeEnd窗口没覆盖运行区间。解法核对时间窗与写出设置临时把writeInterval调小验证确认enabled为真。判据必须先让postProcessing/里出现预期文件才算函数对象生效。报错 3-4coded函数对象报编译/语法错误。现象对code段报错。根因内嵌代码与本版本接口不匹配成员函数名/签名不对或缺头文件。解法以官方文档与本机源码中的coded示例为模板只改逻辑不改接口缺类型时按官方示例的引入方式补头文件。若报错难以定位改用编译型错误信息更友好——这是很实际的工程取舍。报错 3-5函数对象跑了但数值看起来不对。现象postProcessing/有输出但数值异常如积分为零、监测点全为初值。根因指定的场名或面/zone 名不存在OpenFOAM 可能给出警告而非致命错误或监测点坐标在几何之外或统计操作选错例如对着向量场用了标量运算。解法核对场的名字与存在性、坐标是否落在网格内checkMesh报告的边界框可参考、以及operation与场的类型匹配用一段独网验证把监测点放在已知解析位置检查数值是否合理。四、动手练习练习 1内建函数对象把代码 2-1 中的residuals与fieldMinMax复制进一个官方算例的controlDict并运行。判定日志出现函数对象创建记录算例跑完后postProcessing/下出现对应目录与文件能读出残差与场极值。练习 2高频统计低频输出为probes设置executeInterval 1与writeInterval 10。判定postProcessing/中的监测点文件行数约为总步数/10量级能解释为什么执行频率与写出频率可以不同。练习 3时间窗给某个函数对象加timeStart 0; timeEnd 某个中间时刻。判定该函数对象在timeEnd之后不再产生新数据而其他函数对象继续工作。练习 4编译型函数对象按代码 2-3 的流程开发、编译并加载一个最简单的自定义函数对象例如每步在日志打印某个场的体积分。判定wmake libso成功库在$FOAM_USER_LIBBINfunctions.name.libs已指定日志无Unknown functionObject type能在日志或postProcessing/中看到输出。练习 5思考题无标准答案你要在算例里每步计算某区域的平均温度并写出时间序列。说明该用内建还是自定义、用coded还是编译型。验证要点(a) 是否先检查内建函数对象如区域/面平均类能否直接满足(b) 若必须自定义是否判断了是否需要长期复用来决定coded与编译型© 是否用executeControl/writeControl而非硬编码步数(d) 是否规划了postProcessing/输出与后续聚合第 17 篇。五、小结与下一篇预告本篇把函数对象讲成了OpenFOAM 的插件总线functions条目 必需项type、libs 继承控制项region/enabled/log/timeStart/timeEnd/executeControl/executeInterval/writeControl/writeInterval内建函数对象fieldMinMax、probes、surfaceFieldValue、forces、residuals等覆盖了绝大多数在线统计需求执行与写出是两个独立时机高频统计、低频输出开发自己的函数对象走coded快速原型或编译型长期复用后者的库必须在libs中加载产物落postProcessing/也可用postProcess离线执行。第 13 篇《运行期源项与约束fvModels 与 fvConstraints》将继续这条不改求解器就扩展的主线讲清从历史名fvOptions到fvModels/fvConstraints的拆分、fvModel基类与addSup契约、常用类型如semiImplicitSource、porosityForce、heatSource的用法以及用foamToC查表确认可用类型的标准动作。本篇认知问题回显FAQQ1函数对象是什么和 fvModels、边界条件有何共同设计哲学A函数对象是在求解过程中被调用的可插拔组件调用时机由 executeControl 决定、输出时机由 writeControl 决定产物通常落 postProcessing/。它与边界条件、fvModels、湍流模型共享同一设计哲学接口由基类定义具体实现按字典 type 选型扩展方式为新增类 注册 加载库。因此在线统计某量“记录监测点时间序列”自动算面积分都不需要修改求解器属于最小侵入的扩展方式。Q2functions 条目里哪些是必需项、哪些是继承的控制项A必需项是 type函数对象类型名工厂机制据此选型与 libs编译型或外部函数对象所在库内建类型可省或按官方示例给出自定义类型必须写。继承的控制项对所有函数对象都可用包括 region作用区域、enabled是否启用、log是否输出日志、timeStart/timeEnd生效时间窗、executeControl/executeInterval何时执行与间隔、writeControl/writeInterval何时写出与间隔。注意执行与写出是两个独立时机可设为高频统计、低频输出。Q3常用的内建函数对象有哪些A工程上常用的包括 fieldMinMax统计场的最小最大值及位置快速发现非物理极值、probes在指定坐标采样场值产出时间序列适合实验对比与对拍、surfaceFieldValue在指定面做面积分或平均用于流量、壁面通量、平均压力、forces计算壁面受力与力矩用于气动/水动力系数、fieldAverage时间平均与脉动统计用于湍流统计、residuals记录各方程残差用于收敛诊断。类型名与参数随版本演进需先查官方文档与本机类型表再写入字典。Q4coded 函数对象怎么用适合什么A与 codedFixedValue 同源在 functions 条目中设置 type 为 coded 并给出 name 与 code 代码块走字典内嵌代码机制无需建工程、无需编译库但具体的成员函数接口与必需项因版本而异必须以官方文档站与本机源码中的 coded 示例为模板填写不要凭记忆写成员函数名。它适合快速验证想法、一次性分析与教学演示不适合需要复用、需要单元测试或逻辑较长的场景此时应写编译型函数对象因为其错误信息更友好、更易维护。Q5什么时候必须写编译型函数对象开发流程是什么A当函数对象需要长期复用、他人共用、需要测试或逻辑复杂到内嵌代码难以维护时必须写编译型。流程是复制官方最接近的函数对象目录改类名、TypeName 与文件名按本机源码契约实现必要的虚函数读字典构造、执行、写出等Make/files 用LIB $(FOAM_USER_LIBBIN)/libMyFOMake/options 用 LIB_INC/LIB_LIBSwmake libso 编译在 functions 条目的 libs 中加载并把 type 换成新类型名必要时同时在 controlDict 顶层 libs 列出最后用与内建等价函数对象或手算值对比做回归验证。契约要点是 TypeName 与字典 type 一致、库必须加载、执行频率交给 executeControl 而非硬编码步数。
返回列表