2. 仿真任务建模与流程抽象
在上一节中,我们已经明确了仿真工作流自动化的目标:把工程师从重复的“点菜单、填参数、等结果、抄数据”中解放出来。但要实现自动化,第一步不是写脚本,而是把仿真过程本身重新描述一遍——用任务、输入、输出和依赖关系这套语言,把原本藏在GUI操作里的流程显式地表达出来。这一节要解决的核心问题就是:如何把几何清理、网格划分、求解设置、后处理这些步骤,抽象成可编排的任务节点,并定义清楚它们之间的数据流与依赖关系。
一、为什么需要“任务化”而不是“脚本化”
很多工程师第一次尝试自动化时,会直接写一个长脚本:打开模型、清理几何、划网格、设边界、求解、导出云图,一气呵成。这种脚本能跑通一次,但很难维护。原因在于它把“做什么”和“怎么做”混在一起,任何一个环节的参数变化都会导致整条脚本需要修改。
任务化建模的思路不同:它把仿真流程拆成若干个职责单一、接口明确的任务节点。每个节点只关心自己的输入是什么、输出是什么、内部怎么算。节点之间通过数据依赖连接,形成一张有向无环图(DAG)。这样,当几何清理规则变化时,只需要替换几何清理节点;当网格尺寸调整时,只需要修改网格节点参数,下游节点自动感知输入变化并重新执行。
1. 每个任务有唯一的输入集合和输出集合,输出必须是可被下游消费的明确数据对象(文件、数据集、参数表)。
2. 任务之间只通过输入输出连接,不允许隐式共享全局状态。
3. 依赖关系必须显式声明,包括数据依赖和顺序依赖,避免“碰巧能跑”的脆弱流程。
二、典型仿真流程的任务分解
以一个结构静力分析为例,原始流程通常包含以下环节:导入CAD模型、修复几何缺陷(缝隙、重叠面、小特征)、抽取中面或生成实体、划分网格、设置材料与截面、施加边界条件与载荷、求解、提取位移与应力、生成报告。我们可以把它抽象为六个任务节点:
- T1 几何清理:输入原始CAD文件(STEP/IGES),输出清理后的几何文件(如 Parasolid 或中间格式),并附带清理日志(修复了哪些缺陷、删除了哪些特征)。
- T2 网格划分:输入清理后的几何、网格控制参数(单元尺寸、单元类型、局部加密区域),输出网格文件(如 .cdb、.inp)和网格质量报告(雅可比、长宽比、翘曲度)。
- T3 求解设置:输入网格文件、材料参数、边界条件、载荷步定义,输出求解输入文件(如 .inp、.dat)和求解配置清单。
- T4 求解执行:输入求解输入文件,输出结果文件(如 .rst、.odb)和求解日志(收敛信息、警告、错误)。
- T5 后处理提取:输入结果文件、提取规则(关注哪些节点/单元、哪些物理量、哪些时间步),输出结构化数据表(CSV/JSON)和云图图片。
- T6 报告生成:输入数据表和图片,输出报告文档(如 .docx、.html)。
这六个节点并不是简单的线性顺序,而是存在明确的依赖关系:T2 依赖 T1 的几何输出;T3 依赖 T2 的网格输出;T4 依赖 T3 的求解输入;T5 依赖 T4 的结果文件;T6 依赖 T5 的数据表。同时,T3 还需要外部输入的材料参数和边界条件,这些可能来自参数表或前序任务。下图用数据流图的方式展示了这一组任务及其输入输出关系。
三、输入输出契约的定义方法
任务节点能否被可靠编排,关键在于输入输出是否被严格定义。一个常见的误区是只写“输入:模型文件”,这太模糊了。实际工程中,我们需要把输入输出定义到可校验、可传递、可版本化的程度。
以 T2 网格划分任务为例,它的输入契约可以这样定义:
task: mesh_generation
inputs:
geometry_file: {type: path, format: [x_t, step], required: true}
mesh_size_global: {type: float, unit: mm, default: 5.0, range: [0.1, 100]}
mesh_size_local: {type: list, items: {region_id: str, size: float}}
element_type: {type: enum, values: [tet4, tet10, hex8], default: tet10}
quality_criteria: {jacobian_min: 0.3, aspect_ratio_max: 8.0}
outputs:
mesh_file: {type: path, format: cdb}
quality_report: {type: table, columns: [element_id, jacobian, aspect_ratio, skewness]}
mesh_statistics: {type: json, fields: [node_count, element_count, min_jacobian]}
这样的契约定义带来三个好处:第一,编排引擎可以在执行前检查输入是否齐全、参数是否越界;第二,下游任务可以明确知道从哪里读取网格文件和质量报告;第三,当网格质量不达标时,可以自动触发回退或告警,而不是等到求解失败才发现。
四、依赖关系的类型与表达
任务之间的依赖并不只有“前一个跑完跑后一个”这一种。在仿真流程中,至少存在三类依赖:
- 数据依赖:T3 需要 T2 输出的网格文件才能设置求解。这是最典型的依赖,表现为“输入端口连接到上游输出端口”。
- 参数依赖:T3 的材料参数可能来自一个参数表,而该参数表又由 T1 的几何清理日志中的厚度信息计算得到。这种依赖不传递大文件,但传递关键数值。
- 条件依赖:如果 T2 输出的网格质量报告中最小雅可比低于 0.3,则流程不应进入 T3,而应回到 T2 调整网格参数或回到 T1 进一步清理几何。这是一种带判断的依赖,需要在编排层用条件分支表达。
在实现上,数据依赖通常用有向边表示,参数依赖可以用键值对映射表示,条件依赖则需要引入判断节点或守卫条件。无论哪种形式,核心原则是一致的:依赖关系必须显式写在流程定义中,而不是隐藏在脚本的 if-else 里。这样,流程编排引擎才能做拓扑排序、并行调度、失败重试和增量执行。
五、从任务图到可执行编排
当我们完成了任务分解、输入输出契约和依赖声明之后,得到的是一张任务依赖图。这张图可以直接被工作流引擎消费:引擎对图做拓扑排序,得到执行顺序;对没有依赖关系的节点并行调度;对每个节点的输入做校验;对输出做归档。更重要的是,当某个参数变化时,引擎可以沿着依赖边向下游传播“失效”标记,只重新执行受影响的任务,而不是整条流程重跑。
例如,如果只修改了 T3 中的载荷大小,那么 T1、T2 不需要重跑,T4 需要重跑,T5 和 T6 也需要重跑。如果修改的是 T1 的几何清理容差,那么 T1 到 T6 全部需要重跑。这种增量执行能力,正是任务化建模相对于长脚本的核心优势。
最后需要强调的是,任务建模不是一次性的工作。随着仿真对象和工况的变化,任务节点可能需要拆分或合并。比如,当需要同时做模态分析和静力分析时,T4 求解执行可以拆成两个并行节点,共享 T3 的求解设置但使用不同的求解类型。保持任务粒度的合理性和接口的稳定性,是后续实现自动化编排、参数扫描和优化循环的基础。