Files
SystemSimulationApp/app/simulation
2026-09-02 19:17:55 +08:00
..
2026-09-02 19:17:55 +08:00
2026-08-18 00:57:32 +00:00

仿真后端

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、专用闭合逻辑和基线运行入口。
  • 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,AMESim 第一版 公开临时库入口是 components/amesim/library.py。公开模型必须在 模型类中声明 MODEL_TYPE / MODEL_VERSION / PORTS / PARAMETERS / RESULT_VARIABLES / DISPLAY / create(),再把类路径加入库清单。完整规范参见 组件模型建模规范 v1和 组件库分类、发现与读取规范 v1。

当前关键文件:

  • core/medium.py: 气体介质协议 GasMedium 与通用理想气体实现 IdealGasMedium
  • components/amesim/media/: AMESim 零端口介质物性定义元件;具体类型确定介质,property_model 下拉参数选择计算方法,当前提供空气理想气体和氦气 Peng-Robinson
  • components/amesim/gases.py: AMESim gi 介质物性实例注册表;gi=0 固定为空气(理想气体,内置默认),gi=1..99 引用画布中的显式介质定义
  • core/peng_robinson.py: test_mql 与公开氦气介质共用的 Peng-Robinson 状态方程
  • performance.py: 默认关闭、按单次仿真隔离的阶段与物性性能埋点
  • benchmark_performance.py: System XML 主求解路径的可重复命令行基准工具
  • 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/test_mql/system.py: test_mql 系统装配、132 状态总闭包和关键输出映射
  • examples/test_mql/closure.py: test_mql 气动网络 closure、snapshot、流量计算和端口写回
  • examples/test_mql/primitives/: 固定算例专用的 Peng-Robinson 氦气、管路和机械校准原语
  • examples/test_mql/structural_network.py: 固定算例专用的结构网络;不替代带端口契约校验的通用网络
  • reporting/testmodel_outputs.py: Testmodel 的 CSV/SVG/对比摘要导出
  • reporting/amesim_results.py: AMESim 结果读取入口
  • examples/test_mql/run_full_state_comparison.py: test_mql 短时域 AMESim comparison 和诊断入口
  • examples/test_mql/run.py: test_mql 结构运行与程序化执行入口
  • tests/: 当前组件契约、XML、通用系统、AMESim 迁移和结果导出测试

可选性能诊断

SIMULATIONAPP_PROFILE 支持 off(默认)、standard 和 audit。standard 只统计低频的大阶段;audit 才展开 RHS、代数闭合、stream 和物性调用,开销也 明显更高。最终优化收益必须在 off 下复测。

Peng–Robinson 氦气的高开销物性默认使用一次仿真内独立的精确 LRU 缓存;不同 仿真任务不会共享条目,仿真结束后自动释放。可在启动进程前设置 SIMULATIONAPP_PROPERTY_CACHE=off 做数值和性能 A/B,正常运行保持默认 on。 缓存只复用完全相同的输入,不做四舍五入或容差匹配。

FastAPI worker 默认在 lifespan 启动阶段预热 SciPy 积分、非线性求解、稀疏 Jacobian 和 System XML XSD,完成后才开始接收请求。它不会运行业务模型,也不 写入文件;如需诊断冷启动,可设置 SIMULATIONAPP_WARMUP=off。每个 worker 都会 独立暖机一次。

.venv-win\Scripts\python.exe -m app.simulation.benchmark_performance `
  --mode audit --warmups 1 --runs 3 `
  --factory "helium_step=tests.test_amesim_pnvo001_signal_xml:high_pressure_helium_step_project" `
  --output app/data/performance-evaluations/helium-step.json

缓存关闭对照可在同一命令中增加 --disable-property-cache。缓存容量、命中、 未命中和驱逐数会在 audit 响应的 diagnostics.performance.propertyCache 中返回。

基准原始 JSON 默认放到已忽略的 app/data/ 下。指标字段、实测结果和使用边界见 仿真性能评估 2026-08-15。

当前阶段进度

这一阶段原先有 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.<branch_name>.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 v3 的拓扑驱动仿真 MVP:压力-流量非线性闭合、stream 焓传播、动态状态自动拼装和端口结果序列。

当前没有实现:

  • 通用 DAE 初始化器
  • Modelica.Media.Air.SimpleAir 的严格复刻
  • 一般高指数 DAE、事件和严格 Modelica inStream/actualStream 求解器

当前怎么运行

最小运行方式:

python -m app.simulation.examples.testmodel.run

如果要改模型参数或运行参数,建议直接改配置对象,而不是改源码里的默认值。例如:

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)

如果调用方想先确认“这次运行最后到底会用哪些路径、哪些采样点”,可以先准备请求,再执行:

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

当前四个主变量的最大误差为:

  • mytank.p: max_abs_error = 134.960858 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 迁移,并与旧 testmodel 保持独立。 固定算例实现统一位于 examples/test_mql/,结果读取和比较能力位于 reporting/;公开拖拽组件由 components/amesim/library.py 单独登记。

当前已形成 112 个气动状态和 20 个机械状态的总闭包、AMESim 原生结果读取、 Data_Path 输出校验及短时域 comparison。这里不再复制易过期的数值进度; 最新对比结果、运行命令、限制和下一步校准路径以 AmesimModels/test_mql/README.md 为唯一说明。当前仍不能宣称 Python 时域仿真与 AMESim 全局一致。

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 原始结果,参数大概率还要继续调。