Agentic CFD 工作流
你将一个问题陈述转化为一份可信的模拟报告。读者是一名工程师,他主要阅读摘要,并且必须能够检查每一个声明。四条规则:
- 代码计算每一个数字。 你编写代码;永远不要手动将结果、参考值或公式输出键入报告。理论通过 SymPy 符号计算并自我检查。
- 每个选择都有文档。 每个 OpenFOAM 设置都属于一个决策,其中包含原因/效果/替代方案。检查器会拒绝渲染不符合此要求的报告。
- 不依赖记忆。 参考数据、关联式和常数来自问题的
reference/文件或引用的来源。如果必须使用近似值,请在报告中说明。 - 实时记录洞察,如同实验室笔记本。 你的部分工作是为你之后的智能体改进此工作流。当有事情失败、让你惊讶、需要第二次尝试,或这些指南中有不清楚的地方时,立即打开一个洞察(在你开始修复之前):
insights.log(case, stage, problem);一旦解决,使用insights.resolve(...)填写诊断、修复、教训和指南。不要把它们积攒到最后:长时间会话的记忆会丢失和扭曲。预计记录 5-15 条记录;运行一次而没有记录意味着没有仔细查看。
文件夹布局
cfdlib/ 共享工具(请勿修改;将需要的更改记录为洞察,阶段为 "tooling")
foam.py render_case, run(超时,PID 文件),stop,running;读取字段和 postProcessing .dat 文件
monitor.py parse_log, assess(健康检查),preflight(短测试运行 + 运行时预测)
verification.py 误差范数,观察到的阶数,Richardson 外推,GCI,level_sequence
(任何层级序列的收敛性,包括参考自身的)
signals.py 周期信号:周期,精炼峰值,周期性检查,FFT 频率
plots.py 图形样式和尺寸(figure, wide, sketch);to_svg 检查文本重叠
report.py m(), tex_num(), tex_fix()(表格列的固定小数位),tex_pct(), LETTERS, load_yaml,
environment_info(), render()
perf.py 每次运行的性能数据(summarize, machine),用于性能附录
contract.py 报告契约检查器(也提供 --decisions 模式)
insights.py 实时洞察日志(记录时检查排版;拒绝覆盖已填充的条目)
foamdict.py OpenFOAM 字典解析器(由检查器使用)
cases/<name>/ 每个问题一个文件夹
problem.yaml 由用户提供:description(简短概述),setup(事实列表:几何、
边界条件、定义),given data,requested outputs [+ reference/]。
不要改变物理。将 description 和 setup 不变地传递给报告。
reference/ 用户提供的参考数据及引用
numerics.yaml 你的数值方案(网格,运行长度,容差)
decisions.yaml 每个选择,在设置案例时编写
insights.yaml 实时日志(cfdlib.insights)
template/ 包含 {{ 占位符 }} 的 OpenFOAM 案例(0/, constant/, system/)
theory.py / analytical.py, sketch.py, run.py, analyze.py
results/<name>/ 原始数据, results.json, report.html
工作示例(在开始之前阅读所有文件;复制结构,而非数值设置):
cases/plane_poiseuille/:线性、稳态、精确解(验证)。报告:results/plane_poiseuille/report.html。cases/lid_driven_cavity_re100/:非线性、稳态、无精确解、已发布基准 (基准比较,Richardson 外推,残差停止的 SIMPLEC,独立的预测试和生产 命令)。报告:results/lid_driven_cavity_re100/report.html。其 insights.yaml 包含来自构建它的智能体的 15 条经验。cases/stokes_first_problem/:线性、瞬态、精确解(组合和独立的网格与 时间步研究,每一步的动量平衡,并发运行)。报告:results/stokes_first_problem/report.html。cases/dfg_cylinder_re100/:非线性、瞬态和周期性(涡街脱落)、具有多个 参考层级的基准、用 Python 生成的曲线体 O 型网格、MPI 运行和性能 附录。报告:results/dfg_cylinder_re100/report.html。其 insights.yaml 包含 16 条经验 (最重要的一条:PISO 在时间上是一阶的)。
阶段
0. 信息摄入
- 读取
problem.yaml和所有reference/文件。列出请求的输出(它们获得字母 a, b, c...)。 - 在代码中计算主导无量纲数(Re,Ma,...)。决定适用何种物理。
- 如果数据缺失或矛盾,选择一个有文档记录的假设,并在报告的限制部分标记它。永远不要默默捏造输入。
- 如果参考没有给出不确定性,估计一个(其网格,其打印的数字,另一个来源) 并记录如何估计;你将在阶段 5 中需要它。
1. 理论:无需模拟即可知道的内容
- 存在精确解:使用 SymPy 推导,自我检查(满足方程和边界
条件),计算请求的量。参见
cases/plane_poiseuille/analytical.py。 - 无精确解(通常情况):在
theory.intro中说明,并提供理论能够说明的内容:限制 情况、缩放、守恒定律、对称性、量的符号、预期流动结构。 参见cases/lid_driven_cavity_re100/theory.py。 - 每个导出的量都获得:它为何重要,此问题中每个符号的含义和数值, 以及代入数字后的公式(供手算检查)。
2. 设置和决策
- 找到最接近的 OpenFOAM 教程(
$FOAM_TUTORIALS)并从中开始;做最小的修改。 - 阅读
references/openfoam-primer.md以了解每个文件控制的内容和已知陷阱。 - 模板可能包含 Jinja 循环:从 Python 生成整个
blockMeshDict是可以的,并且是对 曲线体进行网格划分的方式(示例:cases/dfg_cylinder_re100/run.py中的mesh_spec())。 - 仅使用编辑/写入工具编辑包含 LaTeX 的文件,切勿通过 Bash heredoc 或内联 Python:
Bash 工具会吞掉反斜杠(
\times会变成 TAB)。案例 3 和 4 都因此浪费了时间。 - 边做边写
decisions.yaml:每个选择一个条目,包含kind(physics / accuracy / solver / output)和涵盖案例文件中每个设置的settings模式。一旦一个案例被渲染,立即检查覆盖率:python -m cfdlib.contract --decisions cases/<name>/decisions.yaml <case dir>。 - 规划网格研究:至少 3 个网格,细化比 2(对于观察到的阶数和 GCI 是必需的)。 瞬态问题还需要时间步研究(或一个带数字的论证,证明时间误差可忽略)。
- 在你的问题中定义一个物理收敛/稳定检查(一个平衡,一个守恒 量,一个必须停止变化的监测值)及其在查看结果之前的容差。 仅靠归一化残差是不够的。瞬态问题:每一步的平衡(守恒量变化 = 边界通量)是一个强检查;它必须使用求解器自身的时间方案。
- 通过/失败容差必须具有区分度:根据粗网格预测试误差和预期阶数(最细网格应达到的)来设定其大小,而不是根据整数。容差比达到的值宽松 100 倍证明不了什么(案例 3)。将观察到的精度阶数单独作为一条规则。
3. 预测试(强制,在任何长运行之前)
- 在最粗和最细网格上使用
cfdlib.monitor.preflight()进行短运行(全长的百分之几)。 它在一个单独的<case>_preflight副本中工作;也将预测试和生产案例渲染到单独的目录中,这样重新运行一个永远不会删除另一个。 - 它报告崩溃、NaN、库朗数、残差/库朗/连续性趋势并预测运行时间。
瞬态运行:预测是可靠的。稳态运行:传递
residual_targets;结果是 一个下界(SIMPLE 稍后会在更细网格上变慢)。 - 打印预测试写入的每个
postProcessing文件的表头和第一行,并检查每个 都不是平凡的(案例 4:一个压力积分在每个时间步都写了 0,默默地)。 - 瞬态非线性问题:一个简短的时间步研究(3 个时间步,比率 2,在粗网格上) 应属于预测试阶段,在网格研究之前。它显示了时间上观察到的阶数:PISO (1 个外部校正器)在案例 4 中被证明是一阶的,并导致了完全重新运行。
- 从静止开始的预测试会低估发展中的非定常流动的成本(案例 4:
两次)。从已发展的流场(通过
mapFields从粗网格运行)开始,或将预测翻倍。 - 如果预测试失败或发出警告,修复设置并记录洞察。不要在失败的预测试上进行生产运行。 如果预测时间过长(此处每次运行超过约 15 分钟),放宽方案并说明。
4. 生产运行
- 从粗到细运行网格。对于稳态运行,在启动最细网格之前,从粗网格外推其迭代次数(每次细化通常增长 3-4 倍)。
- 停止标准必须是物理的:使阶段 2 的物理检查成为停止规则(例如,在探测值上的
runTimeControl函数对象,参见入门手册),或者对每个网格检查残差目标是否实际满足该标准,先从粗网格开始。 - 独立运行(不同网格)可以同时运行,最多 6 个(8 核)。在可重现性说明中说明这一点,因为它会膨胀每次运行的墙钟时间。
- 长运行在后台进行;
python -m cfdlib.foam ps列出求解器进程,python -m cfdlib.foam stop <case dir>杀死一个(仅停止 Python 包装器会留下孤儿求解器)。记录每次运行:健康状态(monitor.assess)、物理检查、墙钟时间。 - 将一系列长运行作为一个后台脚本来运行,该脚本写入完成标记和每次运行的记录。在任何中断之后(API 暂停也是),在重新运行任何内容之前读取这些文件。
- 当所有硬件线程都在运行求解器时,你自己的工作会使其变慢:留出 1-2 个线程空闲,或将预测时间预算增加 1.5 倍。
5. 分析
- 所有比较都在代码中进行。对于精确解:每个网格的误差和观察到的阶数。
对于无精确解的情况:从 3 个网格得到的观察到的阶数和 Richardson 外推,最细网格上的 GCI
(
verification.py),然后将外推值与参考进行比较。 - 基准也是一次模拟:根据(你的 GCI 带 + 参考自身的不确定性)来判断一致性,而不是与零比较。如果差异不随细化而缩小,那么这不是你的离散化误差;在归咎于任何一方之前,测试鲁棒性(另一种方案,更严格的迭代),并建议第二个独立的参考。
- 当参考给出多个网格层级时,它仍在收敛:计算其自身的观察到的阶数
和 Richardson 值(
verification.level_sequence),并将你的结果与其最细层级和其外推值进行比较。在查看之前决定通过规则(案例 4 保留了最细层级规则,并将外推作为上下文报告)。 - 周期性输出(涡街脱落,振荡):使用
cfdlib.signals(上向穿越之间的周期,抛物线精炼峰值,周期性检查,FFT 交叉检查),而不是编写你自己的。 - 粗网格匹配可能是运气(误差抵消)。如实报告差异,在哪里以及为什么。
- 解释是一个声明:在编写之前对照数据(符号、大小、缩放)检查它。在案例 3 中,对误差的第一个解释符号错误且大小只有三分之一。
6. 报告
- 在
analyze.py中构建results.json并调用report.render(results, svgs, out, case_dir=<finest case>)。 契约在references/report-contract.md中;render拒绝编写违反它的报告。 - 数学:文本中的每个公式、符号和科学数字都作为 LaTeX 放在
\(\)内(m(),tex_num()):写\(0.009\,U\),永远不要写0.009 U、6.3e-06或u*H/nu。检查器拒绝 e-notation 和 ASCII 数学,并警告纯文本中的数字乘符号。 - 标题和发现也是结果:从数据生成它们(说“缩小”的标题必须被计算出来)。以
{fig:<key>}引用图形;render() 插入编号。 - 图形(
cfdlib.plots):对于单面板使用plots.figure(),对于 2-3 面板使用plots.wide(n), 对于问题草图使用plots.sketch()(必需)。to_svg()拒绝文本重叠文本、线条、箭头 或形状,或其图例覆盖数据,或比列宽的图形。 修复布局(标签远离线条,使用带引线的annotate,图例在外面);allow=仅用于确实有意为之的重叠,如果你需要它,记录一个洞察。 - 结论:仅当你定义的每个检查都通过时为 PASS;如果结果可用但有注意事项则为 WARN; 否则为 FAIL。永远不要软化一个失败的检查。
7. 视觉审查(强制)
契约检查结构;只有查看才能发现读者会看到的东西。通过本地服务器在内置浏览器中打开报告(file:// 不起作用):
http://localhost:8765/<name>/report.html(如果它没有加载,使用预览工具从 .claude/launch.json 启动 reports 服务器)。在重新渲染后添加 ?v=<n> 以避免缓存副本。
- 截取每个部分的屏幕截图,从上到下。检查:图形可读(文本不小,没有重叠,引线不穿过曲线),表格未截断,数学已渲染(没有原始
\(),表格列对齐,摘要回答了每个请求。 - 在页面中运行(等待 MathJax,打开每个 ⓘ 面板,然后列出每个内容被截断的元素;应为空;在加载后立即运行,在 MathJax 完成之前,它会将未排版的 TeX 报告为溢出):
await MathJax.startup.promise; document.querySelectorAll('details').forEach(d => d.open = true); [...document.querySelectorAll('section *')].filter(x => !x.tagName.startsWith('MJX') && x.scrollWidth > x.clientWidth).forEach(x => console.log(x.tagName, x.textContent.slice(0,50))); - 修复所有问题,然后批准。对于小的排版修复,直接编辑 Markdown;对于结构性问题,修复代码并重新运行。缺少 >= 1 个完整部分的报告是 FAIL。远程工人,请注意:本地的
file://可能看起来更好,但服务器版本才是交付给用户的。