# 仿真后端 `app.simulation` 是 SystemSimulationApp 的仿真子包,用于承接模型定义、系统装配、数值求解和结果导出。 目标不是逐行翻译源模型,而是建立可运行、可测试、可导出,并能与 OpenModelica 或 AMESim baseline 对比的 Python 仿真框架。 当前包含两条模型线:`Testmodel` 已有可运行的 ODE 近似和 OpenModelica 对比能力;`test_mql` 已形成 132 状态气动机械总闭包,正在按 AMESim baseline 做数值校准。 ## 当前目录 - `core/`: 元件基类、端口、状态、介质、方程和元数据协议。 - `solvers/`: ODE、压力流量代数方程和 stream 求解。 - `components/experimental/`: 用于验证元件开发规范的临时组件库。 - `components/experimental/storage/`: 气瓶和贮箱等储能元件。 - `components/experimental/flow/`: 对外注册的阻性管道和孔板等流动元件。 - `components/experimental/junctions/`: 三通等连接节点。 - `components/amesim/`: AMESim 气动、信号和机械组件原语。 - `systems/`: 通用仿真网络与 XML 驱动系统装配。 - `examples/testmodel/`: 固定 TestModel、专用闭合逻辑、基线运行入口,以及 test_mql 当前迁移过程中的系统装配和诊断脚本。 - `examples/test_mql/`: AMESim `test_mql` 的前端调用运行入口。 - `reporting/`: CSV、SVG、运行报告、Modelica 对比结果、AMESim 结果读取和诊断报告导出。 - `registry.py`: 从已启用库清单受控发现、校验和实例化组件。 - `paths.py`: 项目、运行产物、基准和 Modelica 参考结果路径。 稳定基准存放在 `tests/baselines/simulation/`,实际运行产物默认写入被 Git 忽略的 `app/data/simulation-runs/`。新增或修改元件时,先阅读 `components/example.md`。 需要把运行产物写到仓库外时,可以设置 `SIMULATIONAPP_DATA_DIR` 环境变量。 FastAPI 的 `GET /api/components/catalog` 会把注册表转换成前端组件目录。ReactFlow 启动时自动读取该接口;接口暂时不可用时使用内置的同结构兜底定义。 临时组件库的声明入口是 `components/experimental/library.py`。公开模型必须在 模型类中声明 `MODEL_TYPE / MODEL_VERSION / PORTS / PARAMETERS / RESULT_VARIABLES / DISPLAY / create()`,再把类路径加入库清单。完整规范参见 [`组件模型建模规范 v1`](../../docs/component-model-authoring-spec-v1.md)和 [`组件库分类、发现与读取规范 v1`](../../docs/component-library-spec-v1.md)。 当前关键文件: - `core/medium.py`: 温度相关的理想气体近似介质 `IdealGasMedium` - `core/peng_robinson.py`: `test_mql` 使用的氦气 Peng-Robinson 物性 - `systems/network.py`: `SimulationNetwork`,负责组件注册、连接拓扑和状态向量拼装 - `solvers/solver.py`: `integrate_ode()`,优先走 `SciPy solve_ivp`,缺依赖时回退到内置 RK4,并支持 `t_start == t_stop` 的零时长返回 - `examples/testmodel/dynamic_pipe.py`: TestModel 专用单阻容管道近似,入口压降 + 出口直连内容腔 - `components/experimental/junctions/tee.py`: 三通的最小 stream 混合 helper - `examples/testmodel/system.py`: `Testmodel` 的系统装配壳与外部运行入口 - `examples/testmodel/closure.py`: `Testmodel` 当前专用的闭合、初始化投影、分支求解与端口回写 - `examples/testmodel/test_mql.py`: `test_mql` 系统装配、132 状态总闭包和关键输出映射 - `examples/testmodel/test_mql_closure.py`: `test_mql` 气动网络 closure、snapshot、流量计算和端口写回 - `reporting/testmodel_outputs.py`: `Testmodel` 的 CSV/SVG/对比摘要导出 - `reporting/amesim_results.py`: AMESim 结果读取入口 - `examples/testmodel/run_test_mql_full_state_comparison.py`: `test_mql` 短时域 AMESim comparison 和诊断入口 - `examples/testmodel/run_test_mql.py`: test_mql 基线运行与程序化执行入口 - `tests/`: 当前组件契约、XML、通用系统、AMESim 迁移和结果导出测试 ## 当前阶段进度 这一阶段原先有 4 件重点工作,现在的状态如下: 1. `mytee1` 的 stream/焓传播语义:已完成当前阶段收紧 现在如果只有一条支路发生倒流,下游来流焓统一按 `tank.h` 处理,不再临时借另一条支路的焓来凑。 2. 下游初始化/约束处理:已完成当前阶段收口 之前是“直接改对象状态再开始积分”,现在已经收成显式的 `consistent_initial_state_vector()` 初始化入口。当前这一步会在不改下游总质量、总内能的前提下,把几段直接相连的体积拉回同一个连接压力。 3. 自动校验:已完成当前阶段首版 已经补了标准库 `unittest` 回归测试,先把初始化投影是否守恒、是否污染原始状态,以及 4 个主变量的提交基线锁住。 4. 更严格介质模型:已完成当前阶段首版 已经从固定 `cp/cv` 的理想气体近似,推进到随温度变化的空气近似,并接上了内能反解和初始化求根。 如果只看结果,可以把这一阶段理解成: - 连接器语义:首轮收紧已完成 - 初始化入口:首轮收口已完成 - 基线验证:首轮保护已完成 - 介质精化:首轮近似已完成 ## 当前阶段收口 上一轮 `N0-N3` 已全部完成首版,当前可以简单理解为: 1. `N0`:系统层里最明显的流向/焓判断已经继续下沉到组件 helper。 2. `N1`:模型参数和运行参数已经收口到配置对象。 3. `N2`:运行接口已经分成“准备请求”和“执行请求”两层。 4. `N3`:结果导出和命令行报告格式化已经统一收口到 `reporting/`。 这一轮结束后,项目已经不缺“能不能跑”的能力,下一步更重要的是把后续开发最容易卡住的地方先处理掉。 ## 本次推送更新 本次推送已经把上一轮建议里的 `M2-M5` 推进到下面这个状态: 1. `M2`:已完成当前阶段首版 - 已把 `Testmodel` 的专用闭合、初始化投影、分支入口流量求解、下游支路出口流量闭合、端口状态回写,从 `examples/testmodel/system.py` 拆到 `examples/testmodel/closure.py` - `TestModelSystem` 现在主要承担组件装配、网络注册和对闭合器的委托,不再继续堆积系统级手写细节 2. `M3`:已完成当前阶段首版 - 已给两条支路入口流量固定点求解、下游公共压力投影补了显式诊断 - 诊断内容至少包含 `converged / iterations / residual` - 已支持严格模式;内部求解不收敛时可以直接抛错,而不是静默返回最后一个近似值 - `run_testmodel()` 的结构化结果和 `testmodel_run_report.txt` 已能带出最后一次内部闭合求解诊断 3. `M4`:已完成当前阶段首版 - 自动测试已不再只盯最终主变量结果 - 现在已经覆盖: - 改支路参数后,初始支路入口流量是否按预期变化 - 更偏激配置下,初始化和内部闭合是否仍然收敛 - 有无 Modelica 参考两种运行路径下,程序接口与产物行为是否一致 4. `M5`:已启动 - 当前已经明确选择优先走“更容易扩展”的方向,而不是先追求更贴近 Modelica - 已完成第一步:把闭合器内部原来大量写死的 `upper/lower` 双支路逻辑,收成可复用的 `BranchClosureComponents / BranchClosureState` 结构 - 当前已继续推进到 `G1-G5` 的首轮兼容层改造:`snapshot` 已提供通用分支集合,系统层结果生成已拆成“通用键生成 + 旧键别名派生”两层,报告层已开始优先消费通用分支键,旧导出列名仍通过兼容映射保留,兼容测试已显式保护分支顺序和旧导出语义 ## 下一阶段接手建议 如果继续往前推进,建议按下面顺序做,而不是再零散补功能: 1. `G1`:已完成当前阶段首轮兼容接入 - `TestModelSnapshot` 已新增 `branches` 集合 - 每个分支当前至少带 `name / pipe / inlet_flow / outlet_flow / inlet_h / inlet_flow_diagnostics` - `pipe_upper / pipe_lower / branch_inlet_flows / branch_outlet_flows` 目前仍保留为兼容属性,供旧调用方继续使用 2. `G2`:已完成当前阶段首轮内部迁移 - `evaluate_solution()` 已改成从 `snapshot.branches` 读取数据,再通过显式分支名映射写回当前旧列名 - `rhs()` 里的分支导数计算已改成通过通用 helper 按分支循环生成,再按当前状态向量顺序拼回 - 当前外部导出列名仍保持兼容: - `mypipe.p` - `mypipe1.p` - `branch_upper.in/out` - `branch_lower.in/out` 3. `G3`:已完成当前阶段首轮兼容测试 - 当前测试已经显式保护: - `branches` 顺序是否稳定 - `snapshot` 新字段和兼容字段是否一致 - 旧导出列名是否仍映射到正确分支语义 - 参数变化后 `upper/lower` 的名字和顺序是否不会被打乱 4. `G4`:已完成当前阶段首轮兼容拆层 - `evaluate_solution()` 现在会同时产出: - 通用分支键:`branch..p/in/out` - 旧兼容键:`mypipe.p`、`mypipe1.p`、`branch_upper.*`、`branch_lower.*` - 报告层当前已开始优先读取通用分支键,旧键只作为兼容后备 - 当前已经把“内部统一表达”和“旧接口兼容导出”拆成两层,但还没有把所有报告/导出逻辑都迁干净 5. `G5`:已完成当前阶段首轮兼容收口 - `evaluate_solution()` 当前会先生成通用分支键,再统一派生旧兼容键 - 报告层当前已支持“通用键优先、旧键兼容后备” - 当前已经把系统层和 reporting 层的主要旧专名读取入口收口到少量 helper 上,后续继续迁移不会再到处散改 6. `P1`:下一阶段建议从这里接手 当前更合适的下一步,不是继续深挖内核通用化,而是切回结果导向主线: - 定义一份稳定的外部输入参数 schema - 明确这些结构化参数如何映射到 `TestModelConfig / TestModelRunConfig` - 建立“结构化参数 -> 仿真执行 -> 结果产物/摘要”的稳定接口 这样可以直接服务后续文档解析、网页入口和报告生成,而不是继续在 `Testmodel` 内部做边际收益越来越低的抽象整理 7. `P2`:在 `P1` 完成后,再推进文档解析或报告生成链路 更现实的顺序应是: - 先把结构化输入跑通 - 再把结果摘要/产物组织成更接近最终产品的输出包 - 最后再接 Word 解析或页面入口 如果后续继续推进,这个 README 也要一起更新,不要长期保留已经失效的路线描述。 ## 当前实现了什么 当前代码已经实现: 1. `m`、`U` 作为动态元件主状态,`p`、`T`、`rho`、`u`、`h` 作为派生量。 2. `Cylinder`、`Tank`、`Pipe` 的刚性绝热容腔近似。 3. `Orifice` 的压差开方流量关系。 4. `Tee` 的简化混合焓处理。 5. `Testmodel` 的系统级拓扑映射和一版可运行的 `rhs(t, x)`。 6. 基于 `solve_ivp` 的积分入口,以及 SciPy 不可用时的 RK4 回退。 7. 温度相关空气近似介质,包括 `cp(T)`、`h(T)`、`u(T)` 以及 `u -> T` 反解。 8. 显式一致初值入口 `consistent_initial_state_vector()`,以及可迭代初始化器 `initialize_consistent_state()`。 9. Python 主变量结果导出: `mytank.p`、`mytank.T`、`mycylinder.p`、`mycylinder.T` 10. 贮箱温度曲线导出: `testmodel_tank_temperature.csv` `testmodel_tank_temperature.svg` 11. 基于 `ModelicaModels/Simulation/Testmodel_res.csv` 的逐时刻对比与误差摘要导出。 12. 基于 `unittest` 的自动回归测试,当前已覆盖初始化守恒、主变量基线、运行接口、内部闭合诊断、通用分支兼容层、通用结果键与旧键别名一致性,以及部分中间闭合过程行为。 13. 面向 System XML v2 的拓扑驱动仿真 MVP:压力-流量非线性闭合、stream 焓传播、动态状态自动拼装和端口结果序列。 当前没有实现: - 通用 DAE 初始化器 - `Modelica.Media.Air.SimpleAir` 的严格复刻 - 一般高指数 DAE、事件和严格 Modelica `inStream/actualStream` 求解器 ## 当前怎么运行 最小运行方式: ```bash python -m app.simulation.examples.testmodel.run ``` 如果要改模型参数或运行参数,建议直接改配置对象,而不是改源码里的默认值。例如: ```python from app.simulation.examples.testmodel.run import ( TestModelRunConfig, TestModelSamplingConfig, run_testmodel, ) from app.simulation.examples.testmodel.system import ( BranchConfig, CylinderConfig, OrificeConfig, PipeConfig, TankConfig, TestModelConfig, ) from app.simulation.solvers.solver import SolveIVPConfig run_config = TestModelRunConfig( model=TestModelConfig( cylinder=CylinderConfig(p0=30e6), upper_branch=BranchConfig( orifice=OrificeConfig(K=8e-6), pipe=PipeConfig(length=6.0, diameter=0.03), ), tank=TankConfig(volume=0.12), ), solver=SolveIVPConfig(t_start=0.0, t_stop=10.0, method="BDF"), sampling=TestModelSamplingConfig(step=0.05), ) result = run_testmodel(run_config=run_config) ``` 如果调用方想先确认“这次运行最后到底会用哪些路径、哪些采样点”,可以先准备请求,再执行: ```python from app.simulation.examples.testmodel.run import ( prepare_testmodel_run, run_prepared_testmodel, TestModelRunConfig, ) prepared = prepare_testmodel_run(run_config=TestModelRunConfig()) print(prepared.output_dir) print(prepared.t_eval) result = run_prepared_testmodel(prepared) print(result.artifacts.primary_csv_path) print(result.used_modelica_reference) ``` 当前脚本会: 1. 构建 `TestModelSystem` 2. 打印原始初值向量与约束一致后的初值向量 3. 运行 `0 s -> 20 s` 的仿真,默认采样间隔 `0.1 s` 4. 将结果写入 `app/data/simulation-runs/` 下本次运行专属的时间戳目录 5. 若存在 `ModelicaModels/Simulation/Testmodel_res.csv`,自动生成 Python 与 OpenModelica 对比结果 当前脚本默认不会把运行结果直接写到提交基线目录,而是会在 `app/data/simulation-runs/` 下创建一个带时间戳的子目录,例如: - `app/data/simulation-runs/testmodel_20260512_103000_123456/` 该目录里通常会包含: - `testmodel_primary_series.csv` - `testmodel_tank_temperature.csv` - `testmodel_tank_temperature.svg` - `testmodel_run_report.txt` - `testmodel_modelica_comparison.csv` - `testmodel_modelica_comparison_summary.txt` ## 基线结果 当前基线对比摘要来自: [`testmodel_modelica_comparison_summary.txt`](../../tests/baselines/simulation/testmodel/testmodel_modelica_comparison_summary.txt) 当前四个主变量的最大误差为: - `mytank.p`: `max_abs_error = 134.960857 Pa`, `max_rel_error = 0.006798%` - `mytank.T`: `max_abs_error = 0.035507 K`, `max_rel_error = 0.009016%` - `mycylinder.p`: `max_abs_error = 1391.986349 Pa`, `max_rel_error = 0.009447%` - `mycylinder.T`: `max_abs_error = 0.009069 K`, `max_rel_error = 0.003870%` 这说明在当前基线工况下,Python 版主变量已经能较好贴近 OpenModelica 结果。 ## AMESim test_mql 当前进度 `test_mql` 是从 `AmesimModels/test_mql.ame` 新增迁移的 AMESim 模型,当前只在独立路径下推进,不修改旧 `testmodel`。新增命名保持 AMESim 原始别名和 `Data_Path`,方便后续逐变量对齐。 当前已经完成: - 解析 117 个组件、84 条 LINE 连接、直接组件接触、全局参数、仿真设置以及 AMESim 变量目录。 - 直接读取 `.ame` 包内 `test_mql_.var` 和 `test_mql_.results`;baseline 包含 1002 个时间点和 1116 个保存变量。 - 使用氦气 Peng-Robinson 物性,内部统一使用绝对压力,对外按 AMESim 表压和原始单位输出。 - 实现 `PNCH023 / PNCH012 / PNOR001 / PNVO001`,以及 `PNL0001 / PNL0002 / PNL0003 / PNL00R` 管路和 `PN3NODE2 / P4NODE2` 节点语义。 - 完成气动真实拓扑装配、canonical flow、端口写回、snapshot 和 112 状态气动 RHS。 - 实现 `PNRP17 / MECMAS21 / LSTP00A / LMECHN1 / UD00 / FORC` 当前工况可确认的机械行为,并形成 20 状态机械闭包。 - 将气动和机械部分组合成 132 状态总闭包,接入活塞体积反馈、气动力、外力、端止动和质量约束,可通过现有 solver 短时积分。 - 建立关键 `Data_Path` 序列导出、output schema、validation、AMESim 插值比较、误差排序、端点诊断和 PNCH012 RHS 项拆解。 当前确认的关键细节: - `PNRP17` 活塞腔体积使用环形有效面积 `piston_area - rod_area`。 - `LSTP00A` 的 `gap` 观测单位是 mm,计算接触力前必须转换为 m。 - `PNCH023` 固定气室初始压力来自 `P0=153 bar` 的绝对压力;AMESim `press` 输出为相对 `101300 Pa` 的表压。 - `PNCH012` 变容腔初始压力对齐 AMESim 的 `1 bar` 绝对压力,`vol` 输出单位为 cm3,且末端体积等于基础死容积加对应活塞 `vol1`。 - `MECMAS21` 的 `x1dup / v1dup / acc1dup` 是第二机械端口观测,相对 `x1 / v1 / acc1` 为反号,不是重复同值。 - 本算例中 `MECMAS21` 的 `Fmin / Fmax / Fvisc / Ffric` 在 AMESim 结果里为零;当前只把这一工况能验证的部分写入测试,没有硬猜未激活碰撞/摩擦状态机。 当前默认 `0 -> 1e-5 s` comparison 已定位最大偏差为 `press@pn_c1_8`:初值对齐,但末值绝对误差约 `9.22849 Pa`。RHS 拆解显示边界体积功约 `0.026 W`,端口焓流约 `32722 W`,因此当前首要工作是比较 Python 的 `p4_port3_remote_chamber_to_line_flow` 与 AMESim 的 `dm1@pneumatic_69`,检查单位、符号、PNL0001 阻力和 `pnnode4_16` 节点平衡。 当前还不能宣称 `test_mql` 的 Python 时域仿真已经和 AMESim 全局一致。完整说明、运行命令和下一步校准路径见 `AmesimModels/test_mql/README.md`。 ## Testmodel 当前架构判断 如果按“组件正确 -> 网络闭合 -> 积分可跑 -> 结果对齐 -> 去近似”来看,当前大致处于: - 组件级:已完成首版 - 系统闭合:已完成首版 - 积分入口:已完成首版 - 基线结果对齐:已具备初步能力 - 去近似:仍在进行中 所以当前最准确的说法不是“已完成移植”,而是: `Testmodel` 已有一版可运行、可导出、可对比的 Python 近似实现。 ## Testmodel 已知限制 当前最主要的限制可以直接理解成下面几条: - 介质模型已从常 `cp/cv` 推进到温度相关空气近似,但仍不是 `Modelica.Media.Air.SimpleAir` 的严格复刻。 - 系统整体仍是 ODE 化近似,不是原始 Modelica DAE 的直接复现。 - `mytee1 -> mytank` 这一段虽然已经去掉早期的“虚拟出口导通系数”,改成了基于压力一致性的下游能量闭合,但本质上仍是工程近似。 - 通用 XML 求解链路已经支持按实际流向传播和三通混合 stream 焓,但仍是正则化 MVP,不是严格的 Modelica `inStream/actualStream` 框架。 - 当前一致初值仍是 ODE 入口处的约束投影,不等同于真正的 DAE 初始化求解。 - 当前自动校验主要锁的是 Python 提交基线,还不是稳定的 Modelica 阈值回归。 - 当前闭合器、系统层和 reporting 层虽然已经开始做“双支路结构化”,但对外结果序列、报告字段和部分导出命名仍然保留 `Testmodel` 专名兼容层,还没有完全转成通用表达。 - 当前内核已经足够支撑下一阶段“结构化参数 -> 仿真执行 -> 产物输出”的链路开发,但还没有现成的 Word 参数解析入口和正式报告生成链路。 所以,当前版本适合: - 架构验证 - 组件接口验证 - 基线工况对比 - 结果导出与误差定位 但当前版本还不适合: - 直接宣称与 OpenModelica 严格等价 - 作为最终工程结论的唯一依据 - 直接扩展到更复杂拓扑而不补通用连接器语义 ## Testmodel 文件级现状 按代码现状逐项看: - `core/base.py`: 正常 只提供最小抽象层,没有明显冗余。 - `core/ports.py`: 正常 `PortState` 目前只保留 `p`、`m_flow`、`h_outflow` 三个必要字段。 - `core/state.py`: 正常 `VolumeState` 只负责 `[m, U]` 状态打包。 - `systems/network.py`: 正常 负责状态向量拼装和连接摘要,不参与物理求解。 - `solvers/solver.py`: 正常 已支持 SciPy、RK4 回退和零时长仿真。 - `components/experimental/**/*.py`: 正常 都是当前一版近似模型,没有发现与 README 明显冲突的“未记录能力”。 - `examples/testmodel/system.py`: 是当前最重要的技术债集中区 这里承载了下游流向切换、焓混合、压力投影等近似逻辑,后续演进应主要落在这里。 - `examples/testmodel/run.py`: 正常 已不是“最小打印脚本”,而是当前结果导出和对比入口。 - `tests/baselines/simulation/`: 是当前稳定基线,不应该随着日常运行频繁改动。 - `app/data/simulation-runs/`: 是默认运行产物目录,不是手写源代码,也不应该提交。 ## Testmodel 当前主技术债 目前最主要的技术债,可以直接理解成下面 4 件事: 1. 当前初始化虽然已经引入迭代诊断,但本质上仍是 ODE 入口近似,不是真正的 DAE 初始化器。 2. `examples/testmodel/system.py` 还是承载了太多系统级闭合和初始化逻辑,只是主要端口的手写 stream 方向判断已经搬到组件 helper 里了,装配参数本身已经基本收口到配置对象。 3. 自动校验现在主要锁的是 Python 这一版自己的基线,还不是稳定的 Modelica 阈值回归。 4. 当前空气物性已经完成首轮基线校准,但还不是 `SimpleAir` 的严格复刻。以后如果换工况,或者拿到更多 Modelica 原始结果,参数大概率还要继续调。