同步仿真框架并接入AMESim气动组件

This commit is contained in:
huojiarong committed 2026-07-30 03:53:10 +00:00
1 parent 420bafeb4e
commit db4bdb4b70
109 files changed
+26920 -420

No files matched your search

+48
View File
@@ -0,0 +1,48 @@
# 开发文档索引
本目录是 SystemSimulationApp 协议和开发规范的统一入口。
## 模型开发
1. [组件模型建模规范 v1](component-model-authoring-spec-v1.md)
用于创建或修改模型,包括端口、参数、结果、方程、版本、测试和 AI 修改协议。
2. [组件库分类、发现与读取规范 v1](component-library-spec-v1.md)
用于理解组件库清单、自动发现、启动校验、目录接口和前端读取流程。
3. [组件目录 JSON Schema v1](../schemas/component-catalog-v1.schema.json)
`GET /api/components/catalog` 的机器可读结构。
4. [AMESim 子模型公开组件迁移矩阵](amesim-component-migration-matrix.md)
用于划分 `test_mql` 子模型族的公开组件、内部模型和暂不支持范围。
建议人工和 AI 先阅读建模规范,再阅读读取规范,然后参考目标分类中最接近的现有
模型。不要从前端兜底数据反推后端物理契约。
## System XML
- [System XML v2 协议](system-xml-v2.md)
- [System XML v2 XSD](../schemas/system-simulation-v2.xsd)
- [System XML v1 协议(旧版)](system-xml-v1.md)
- [System XML v1 XSD(旧版)](../schemas/system-simulation-v1.xsd)
新增模型时,模型类和组件库清单是后端事实来源;System XML 保存组件实例、参数和
连接。XML 解析器不能自行创造模型端口或参数。
## 当前代码入口
| 目的 | 文件 |
| --- | --- |
| 组件基类 | [`app/simulation/core/base.py`](../app/simulation/core/base.py) |
| 端口契约 | [`app/simulation/core/ports.py`](../app/simulation/core/ports.py) |
| 参数与结果元数据 | [`app/simulation/core/metadata.py`](../app/simulation/core/metadata.py) |
| 库和显示声明 | [`app/simulation/core/catalog.py`](../app/simulation/core/catalog.py) |
| 库发现与注册校验 | [`app/simulation/registry.py`](../app/simulation/registry.py) |
| 临时库清单 | [`app/simulation/components/experimental/library.py`](../app/simulation/components/experimental/library.py) |
| 元件完整示例 | [`app/simulation/components/example.md`](../app/simulation/components/example.md) |
## AI 使用原则
- 先读规范和相邻模型,再改代码。
- 只从 `library.py` 受控登记公开模型。
- 不在前端复制后端端口、参数或默认值作为正式来源。
- 不覆盖用户已有改动。
- 不自行猜测缺失的物理方程。
- 修改后运行针对性测试和完整回归,并报告未完成的验证。
+89
View File
@@ -0,0 +1,89 @@
# AMESim 子模型公开组件迁移矩阵
状态:规划中
适用模型:`AmesimModels/test_mql.ame` / `PythonModels.systems.test_mql`
配套规范:[`component-model-authoring-spec-v1.md`](component-model-authoring-spec-v1.md)
## 目标
本文档把 `test_mql` 中的 AMESim 子模型族拆成可执行的迁移批次。它不是组件注册清单;只有完成公开模型契约、方程实现、最小 XML 编译/仿真测试后,子模型才能加入某个 `library.py` 的 `models` 清单并出现在前端组件库中。
当前原则:
- 不把现有固定拓扑 `TestMqlSystem` 整体注册为公开拖拽组件。
- 不把机械、信号、事件元件提前伪装成可由当前气动求解器仿真的公开组件。
- 可先公开当前 `app.simulation` 求解链路能承载的气动元件:储能、阻性、孔口、三通/四通、准稳态管段。
- 对 AMESim 精确语义尚未复刻的元件,允许先做内部模型或文档化候选,不能在目录中标记为完成。
## 当前求解器能力边界
`app.simulation` 当前支持:
- 物理域:`pneumatic`。
- 端口变量:`p`、`m_flow`、`h_outflow`。
- 流量符号:`m_flow > 0` 表示流入当前组件。
- 网络方程:连接压力相等、连接流量和为零、组件压力-流量残差、stream 焓传播。
- 动态:半显式 ODE,动态组件提供质量和内能状态导数。
- 暂不支持:机械端口、信号端口仿真、事件系统、一般高指数 DAE、AMESim 气体定义组件对全局 medium 的动态切换。
## 子模型族矩阵
| AMESim 子模型 | 数量 | AMESim 角色 | 当前公开状态 | 建议目标 | 先决条件 / 限制 |
| --- | ---: | --- | --- | --- | --- |
| `PNCH023` | 4 | 固定容积气室,带换热 | 候选公开 | `amesim_pnch023`,`storage` | 可基于 `ThermodynamicVolumeComponent`;需按 AMESim `cvol/extemp/kth/sth/gi` 复核质量、能量、换热方程。 |
| `PNCH012` | 8 | 变容气室,带换热 | 暂不公开 | 内部 `test_mql` 模型;后续 `amesim_pnch012` | 体积由机械/活塞运动耦合;公开前需要机械域或明确外部体积输入协议。 |
| `PNOR001` | 8 | 常系数气动孔口 | 候选公开 | `amesim_pnor001`,`flow` | 可基于 `AlgebraicComponent`;需按 AMESim `cq/area/Cv/Kv/flowset/gi` 复核双向流、零压差正则化和单位换算。 |
| `PNVO001` | 8 | 信号调制气动孔口 | 暂不公开 | 内部固定算例;后续 `amesim_pnvo001` | 依赖信号端口和事件/阶跃控制;当前公开组件库不支持信号仿真。可先拆出无信号开度参数版本,但不得等同 AMESim `PNVO001`。 |
| `PN3NODE2` | 8 | 三端气动节点,压力温度由 port 2 固定 | 候选公开 | `amesim_pn3node2`,`junctions` | 当前 `Tee` 是通用三通近似;AMESim port 2 参考温压语义和 stream 混合需单独测试。 |
| `P4NODE2` | 8 | 四端气动节点,压力温度由 port 2 固定 | 候选公开 | `amesim_p4node2`,`junctions` | 需要新增四端 junction 基类/模型;复核 port 2 参考温压和多支路混合。 |
| `PNL00R` | 4 | 管路纯阻性摩擦段 | 候选公开 | `amesim_pnl00r`,`flow` | 可基于准稳态阻性管;需按 AMESim `PNL00R` 参数和摩擦公式复核。 |
| `PNL0001` | 20 | C-R 动态管路 | 先内部 | 后续 `amesim_pnl0001` | 含管内储气动态和摩擦耦合;当前 `pipe` 只是准稳态阻性模型,不能直接宣称等价。 |
| `PNL0002` | 8 | R-C-R 动态管路 | 先内部 | 后续 `amesim_pnl0002` | 需要多段动态状态和端口/中心阻力结构测试。 |
| `PNL0003` | 8 | C-R-C 动态管路 | 先内部 | 后续 `amesim_pnl0003` | 当前 `test_mql` 仍在诊断事件早期导数差异;公开前必须完成 AMESim baseline 对齐。 |
| `PNPL01` | 16 | 零气动流源 | 暂不公开 | 后续边界/源元件 | 当前网络要求物理端口连接并由组件/储能闭合;需设计一端口源/边界组件契约。 |
| `PNGD00` | 1 | 氦气气体定义 | 暂不公开 | medium 配置,不是画布物理组件 | 应映射为系统/介质设置;不能作为普通可连接组件注册。 |
| `PNRP17` | 8 | 气动活塞与移动体耦合 | 暂不公开 | 内部固定算例;后续跨域组件 | 依赖气动端口和机械端口耦合;需要新增机械域、跨域状态和连接规则。 |
| `MECMAS21` | 10 | 一维平动质量 | 暂不公开 | 后续机械域组件 | 当前无机械端口、力/速度/位移连接规则和机械求解器。 |
| `LMECHN1` | 2 | 动态线性机械节点 | 暂不公开 | 后续机械域组件 | 同上。 |
| `LSTP00A` | 8 | 弹性接触/端止动 | 暂不公开 | 后续机械域组件 | 依赖机械接触和事件/非光滑力模型。 |
| `F000` | 16 | 零力源 | 暂不公开 | 后续机械边界组件 | 依赖机械域。 |
| `FORC` | 2 | 信号转力 | 暂不公开 | 后续信号-机械跨域组件 | 依赖信号端口与机械端口。 |
| `STEP0` | 8 | 阶跃信号源 | 暂不公开 | 后续信号源组件 | 当前公开求解器不支持信号端口仿真和事件。 |
| `UD00` | 2 | 分段线性信号源 | 暂不公开 | 后续信号源组件 | 同上。 |
| `DIRECT` | 44 | 直接连接 | 不注册为组件 | System XML `Connection` | 连接不是组件;物理连接必须保持无方向端点语义。 |
## 建议迁移批次
### 批次 1:可进入当前气动求解器的公开组件
优先级最高,但仍需先补 AMESim 公式与测试:
- `amesim_pnor001`
- `amesim_pnl00r`
- `amesim_pnch023`
- `amesim_pn3node2`
- `amesim_p4node2`
完成定义:每个模型必须有 `MODEL_TYPE/MODEL_VERSION/PORTS/PARAMETERS/RESULT_VARIABLES/DISPLAY/create()`,并补目录、参数边界、残差、零流量/反向流、最小 XML 编译和短时仿真测试。
### 批次 2:动态管路
- `amesim_pnl0001`
- `amesim_pnl0002`
- `amesim_pnl0003`
这些模型是 `test_mql` 对齐工作的核心,公开前需要先在内部固定算例中完成 AMESim baseline 诊断闭环。不能用当前准稳态 `pipe` 替代动态管路并宣称等价。
### 批次 3:信号、事件、机械和跨域组件
- `STEP0`、`UD00`、`PNVO001`
- `MECMAS21`、`LMECHN1`、`LSTP00A`、`F000`、`FORC`、`PNRP17`
这些需要先设计新物理域或信号求解协议,包括端口变量、连接规则、XML 协议、前端连线兼容和最小闭合系统测试。
## 下一步执行建议
1. 先为 AMESim 公开组件建立独立库,例如 `app.simulation.components.amesim`,不要混入 `experimental`。
2. 先只登记一个完成度最高的气动代数组件,例如 `PNOR001` 或 `PNL00R`。
3. 每登记一个模型,都同步补充测试和目录校验,确认 `/api/components/catalog`、System XML 编译和最小仿真都通过。
4. 动态管路和机械/信号组件继续保留在内部 `test_mql` 路径,直到方程和求解能力补齐。
+730
View File
@@ -0,0 +1,730 @@
# 组件库分类、发现与读取规范 v1
状态:已在 `experimental` 临时组件库实施
适用范围:`app/simulation/components`、组件注册中心、System XML 和 React Flow 组件库
当前试验库:`experimental`(界面名称:临时测试组件库)
## 0. 文档定位
本文档只负责“模型如何被系统发现和读取”。模型方程、状态、参数和结果应如何编写,
统一参见[组件模型建模规范 v1](component-model-authoring-spec-v1.md)。
人工或 AI 排查组件读取问题时,按以下顺序读取:
1. `app/simulation/registry.py` 中的 `ENABLED_COMPONENT_LIBRARIES`。
2. 被启用组件库的 `library.py`。
3. `library.py/models` 明确列出的模型类。
4. 模型类的 `DISPLAY / PORTS / PARAMETERS`。
5. `build_component_catalog()` 的输出。
6. 前端 `normalizeComponentCatalog()`。
当前事实来源优先级:
| 信息 | 唯一事实来源 | 不能作为事实来源 |
| --- | --- | --- |
| 启用哪些库 | `ENABLED_COMPONENT_LIBRARIES` | 目录中碰巧存在的文件夹 |
| 库分类和模型清单 | 各库 `library.py` | 前端组件列表 |
| 模型类型和版本 | 模型类 | 文件名或中文名称 |
| 物理端口 | 模型类 `PORTS` | `DISPLAY.ports` 或画布方向 |
| 参数默认值和边界 | 模型类 `PARAMETERS` | 前端兜底定义 |
| 图标和端口位置 | 模型类 `DISPLAY` | 物理方程 |
| 前端运行目录 | `/api/components/catalog` | 手工扫描 Python 包 |
如果文档、代码和目录响应不一致,应在同一次修改中修复并增加测试,不能通过复制
另一份映射临时绕过。
## 1. 目标
本规范用于统一以下内容:
1. 后端组件库如何声明自身信息和分类。
2. 单个模型如何声明端口、参数、结果变量和界面显示信息。
3. 后端如何发现、校验、注册并实例化模型。
4. 前端如何通过统一目录接口自动生成左侧组件库和参数面板。
5. System XML 中的模型类型如何稳定映射到 Python 实现。
目标工作流如下:
```mermaid
flowchart LR
A["ENABLED_COMPONENT_LIBRARIES"] --> B["library.py"]
B --> C["受控导入模型类"]
C --> D["启动契约校验"]
D --> E["组件注册表"]
E --> F["GET /api/components/catalog"]
F --> G["React Flow 组件库和参数面板"]
E --> H["System XML 模型实例化"]
```
新增一个符合本规范的模型后,前端不应再修改 `App.tsx` 中的组件列表、参数列表
或分类列表。只有新增一种前端尚不支持的图形渲染方式时,才需要补充前端图标组件。
## 2. 术语和层级
组件目录采用四个相互独立的概念:
| 层级 | 示例 | 含义 |
| --- | --- | --- |
| 组件库 Library | `experimental` | 一组具有共同发布、维护和版本边界的模型 |
| 界面分类 Category | `storage` | 只用于组件面板分组和排序 |
| 物理域 Domain | `pneumatic` | 决定端口能否连接以及采用哪组连接方程 |
| 模型 Model | `cylinder` | 可实例化并写入 System XML 的稳定模型类型 |
分类和物理域禁止混用。例如,`storage` 是界面分类,`pneumatic` 是物理域;
一个“储能元件”也可以属于液压域,不能根据分类推断端口连接规则。
标准层级为:
```text
Library
Category
Model
Port
Port variable
Parameter
Result variable
```
## 3. 推荐目录结构
每个组件库使用独立 Python 包:
```text
app/simulation/components/
experimental/
__init__.py
library.py
storage/
__init__.py
cylinder.py
tank.py
flow/
__init__.py
orifice.py
resistive_pipe.py
junctions/
__init__.py
tee.py
```
规则如下:
- `library.py` 是组件库唯一清单入口。
- 分类目录名必须与分类 ID 一致。
- 一个公开模型原则上放在一个独立 `.py` 文件中。
- 求解器、介质、通用方程和状态对象不得放入组件库目录。
- 未准备对外注册的实验类可以保留在库内,但不得加入库清单。
- 禁止通过扫描任意 `.py` 文件并执行其中代码来发现模型;必须使用库清单进行受控导入。
## 4. 标识符和版本
### 4.1 标识符
以下字段使用稳定的英文机器标识:
- `library.id`
- `category.id`
- `MODEL_TYPE`
- 端口名
- 参数名
- 结果变量名
标识符应使用 `snake_case`,只允许小写英文字母、数字和下划线,并以字母开头。
已有工程约定中的 `T`、`U` 等热力学变量可以保留。
界面中文名称单独保存在 `label` 中。修改 `label` 不影响工程兼容性;修改机器标识
会影响工程文件、System XML、结果文件和后端注册,因此发布后不得直接改名。
### 4.2 版本
每个组件库和模型都应具有版本:
```python
LIBRARY_VERSION = "0.1.0"
MODEL_VERSION = "1.0.0"
```
版本遵循 `主版本.次版本.修订版本`:
- 修订版本:只修复实现,不改变输入输出契约。
- 次版本:向后兼容地新增参数、结果或能力。
- 主版本:端口、参数语义或方程发生不兼容变化。
当前 System XML v2 尚未保存模型版本。正式发布组件库前,应在 XML 中增加
`library` 和 `modelVersion`,并提供旧工程迁移规则。
## 5. 组件库清单
每个库必须提供 `library.py`,并使用有类型的不可变声明:
```python
from app.simulation.core.catalog import (
ComponentCategorySpec,
ComponentLibrarySpec,
)
LIBRARY = ComponentLibrarySpec(
id="experimental",
label="临时测试组件库",
version="0.1.0",
source_package="app.simulation.components.experimental",
temporary=True,
order=100,
categories=(
ComponentCategorySpec(
id="storage",
label="储能元件",
order=10,
),
ComponentCategorySpec(
id="flow",
label="流动元件",
order=20,
),
ComponentCategorySpec(
id="junctions",
label="连接元件",
order=30,
),
),
models=(
"app.simulation.components.experimental.storage.cylinder:Cylinder",
"app.simulation.components.experimental.storage.tank:Tank",
"app.simulation.components.experimental.flow.resistive_pipe:ResistivePipe",
"app.simulation.components.experimental.flow.orifice:Orifice",
"app.simulation.components.experimental.junctions.tee:Tee",
),
)
```
`models` 是唯一允许注册到系统的模型清单。它同时解决以下问题:
- 避免导入测试脚本或内部辅助类。
- 控制模型加载顺序。
- 避免同一个 `MODEL_TYPE` 被多个实现重复注册。
- 可以只加载部署环境允许使用的组件库。
- 启动错误能明确定位到具体库和模型。
当前 `experimental/library.py` 已按此格式声明库、分类和五个公开模型;
`experimental/__init__.py` 只保留旧常量的兼容别名。
## 6. 模型类契约
一个可注册模型必须显式声明:
```python
class ExampleComponent(Component):
MODEL_TYPE = "example_component"
MODEL_VERSION = "1.0.0"
PORTS = (...)
PARAMETERS = (...)
RESULT_VARIABLES = (...)
DISPLAY = ...
```
字段含义:
| 字段 | 是否必须 | 用途 |
| --- | --- | --- |
| `MODEL_TYPE` | 必须 | System XML、注册表和结果元数据中的稳定类型 |
| `MODEL_VERSION` | 必须 | 模型契约和迁移版本 |
| `PORTS` | 必须 | 物理或信号连接契约 |
| `PARAMETERS` | 必须,可为空 | 用户输入参数及校验边界 |
| `RESULT_VARIABLES` | 必须,可为空 | 组件级可展示结果 |
| `DISPLAY` | 必须 | 前端名称、分类、图标、排序和端口布局 |
模型实现还必须满足:
1. 构造函数调用 `super().__init__(name)`。
2. 使用 `set_parameter_values()` 保存所有规范化后的参数。
3. 使用 `register_declared_port()` 创建 `PORTS` 中声明的端口。
4. 组件级结果键必须与 `RESULT_VARIABLES` 完全一致。
5. 所有内部计算均使用 SI 基准值。
6. 模型不能直接依赖 FastAPI、React Flow 或 XML DOM。
7. 模型的方程不能依赖图标方向、界面分类或画布位置。
完整方程示例参见
[`app/simulation/components/example.md`](../app/simulation/components/example.md)。
## 7. 界面显示声明
`DISPLAY` 只描述模型在前端的呈现,不参与物理求解:
```python
from app.simulation.core.catalog import (
ComponentDisplaySpec,
PortDisplaySpec,
)
DISPLAY = ComponentDisplaySpec(
label="示例容腔",
library_id="experimental",
category_id="storage",
symbol="cylinder",
order=90,
ports=(
PortDisplaySpec(
name="port_a",
side="left",
order=10,
),
),
)
```
字段规则:
- `label`:组件库中的显示名称。
- `library_id`:必须引用已加载的库。
- `category_id`:必须引用该库已声明的分类。
- `symbol`:前端图形渲染器的稳定标识。
- `order`:同一分类中的排序值。
- `ports`:只声明端口在图形中的位置和顺序。
`DISPLAY.ports` 中的端口名必须与模型的 `PORTS` 完全一致,不能缺少、增加或改名。
旋转和镜像只改变前端计算后的视觉方位,不改变端口机器名和物理语义。
前端遇到未知 `symbol` 时必须显示通用占位图标,同时保留模型拖拽、参数编辑、
连线和 XML 生成功能,不能因为缺少专用图形而丢弃整个模型。
## 8. 端口规范
端口由 `PortDefinition` 声明:
```python
PORTS = (
PortDefinition.pneumatic(
"port_a",
nominal_role="bidirectional",
),
)
```
每个端口必须包含:
- 稳定端口名。
- `kind`:`physical` 或 `signal`。
- `domain`:例如 `pneumatic`。
- `nominal_role`:用于界面提示,不决定实际流向。
- `positive_flow_direction`:物理流量变量的符号约定。
- 端口变量及各自连接规则。
当前气动功率端口包含:
| 变量 | 角色 | 连接规则 | 单位 |
| --- | --- | --- | --- |
| `p` | effort | `equal` | `Pa` |
| `m_flow` | flow | `sumToZero` | `kg/s` |
| `h_outflow` | stream | `streamMix` | `J/kg` |
气动端口统一约定 `m_flow > 0` 表示质量流入当前组件。`inlet`、`outlet` 是标称角色,
不应阻止反向流动;实际方向由求解结果中的流量符号决定。
连接校验至少包括:
1. 两个端口均存在。
2. 端口不能连接自身。
3. `kind` 相同。
4. `domain` 相同。
5. 端口变量集合及连接规则兼容。
6. 同一物理端口的连接数量符合当前网络编译器能力。
## 9. 参数规范
参数使用 `ParameterDefinition` 声明:
```python
PARAMETERS = (
ParameterDefinition(
name="volume",
label="容积",
quantity="volume",
unit="m3",
default=0.1,
minimum=0.0,
minimum_exclusive=True,
),
)
```
规则如下:
- `name` 是模型构造、XML 和工程文件共同使用的稳定名称。
- `label` 是界面文案。
- `quantity` 是受控物理量标识,用于前端匹配可换算单位。
- `unit` 是后端 SI 单位。
- `default` 必须能够直接创建合法模型。
- 边界必须与方程有效范围一致。
- 无量纲量使用 `quantity="dimensionless"` 和 `unit=""`。
- 用户输入可以使用其他公制单位,但提交后端前必须换算为 SI。
- 文本框编辑中的临时字符串不立即判错,失焦、回车或运行仿真时再执行数值校验。
后端不得静默忽略未知参数。缺少参数时可使用声明的默认值;出现未知参数时必须
返回包含组件 ID 和参数名的明确错误。
## 10. 结果变量规范
组件结果和端口结果分开管理:
- 组件结果来自 `RESULT_VARIABLES`。
- 端口结果根据 `PORTS` 中 `result_visible=True` 的端口变量自动生成。
- 求解器缓存、残差和调试量默认不进入用户结果。
每个结果变量必须提供:
- `name`
- `label`
- `quantity`
- `unit`
- `category`
- `order`
仿真结果必须输出结构化元数据,前端禁止拆解结果键或按字符串关键词猜测:
```json
{
"key": "cylinder_1.port_b.p",
"componentId": "cylinder_1",
"componentType": "cylinder",
"scope": "port",
"portName": "port_b",
"name": "p",
"label": "压力",
"quantity": "pressure",
"unit": "Pa",
"category": "effort",
"order": 10
}
```
结果页应按 `componentId`、`scope`、`portName`、`quantity` 等结构化字段筛选,
而不是从 `key` 中推断组件、端口和变量。
## 11. 标准模型创建入口
注册中心通过统一入口创建模型,避免长期维护集中式 `_cylinder_factory`、
`_tank_factory` 等适配函数。当前接口为:
```python
@classmethod
def create(
cls,
*,
name: str,
medium: IdealGasMedium,
parameters: Mapping[str, float],
) -> Component:
...
```
创建流程:
1. 注册中心按 `PARAMETERS` 填充默认值。
2. 校验数值有限性和上下限。
3. 拒绝未知参数。
4. 调用模型类的 `create()`。
5. 验证实例的模型类型、端口和参数快照。
6. 将实例交给网络编译器。
这种方式允许 Python 构造参数保留内部命名,同时对外始终使用规范中的参数名。
## 12. 自动发现与注册
后端启动时按以下顺序建立注册表:
```text
读取启用的 library.py
-> 校验库 ID、版本和分类
-> 按 models 清单导入模型类
-> 读取模型静态契约
-> 执行跨字段校验
-> 建立 library registry
-> 建立 model registry
-> 构建前端 catalog
```
当前使用显式启用列表:
```python
ENABLED_COMPONENT_LIBRARIES = (
"app.simulation.components.experimental.library:LIBRARY",
)
```
禁止以下发现方式:
- 在整个仓库递归导入所有 Python 文件。
- 依赖文件名自动推断 `MODEL_TYPE`。
- 由前端硬编码后端类路径。
- 导入失败后悄悄跳过模型。
- 多个实现重复注册同一个模型类型并由加载顺序决定最终结果。
发现或校验失败时,FastAPI 应拒绝启动并给出库 ID、模型类型、字段和原因。
## 13. 启动校验规则
注册表完成前必须执行以下校验:
### 13.1 库与分类
- 库 ID 全局唯一。
- 库版本格式有效。
- 分类 ID 在库内唯一。
- 所有排序值为整数。
- 所有模型引用已存在的库和分类。
### 13.2 模型
- `MODEL_TYPE` 全局唯一,且与注册键一致。
- 模型版本格式有效。
- 模型继承框架要求的基类。
- 模型提供统一创建入口。
- 默认参数能够成功创建实例。
### 13.3 端口
- 端口名在模型内唯一。
- 显示端口集合与物理端口集合完全一致。
- 端口物理域、变量角色和连接规则有效。
- 实例实际注册的端口与静态声明一致。
### 13.4 参数
- 参数名在模型内唯一。
- 默认值有限且满足边界。
- `minimum <= maximum`。
- `quantity` 和 `unit` 的组合已登记。
- 实例保留所有规范化参数,不得静默修改或丢失。
### 13.5 结果
- 结果变量名在对应作用域内唯一。
- `quantity` 和单位有效。
- `component_result_values()` 的键与声明一致。
- 端口结果只来自声明为可见的端口变量。
## 14. 前端组件目录协议
前端只通过以下接口读取组件库:
```http
GET /api/components/catalog
```
目录顶层必须具有版本:
```json
{
"schemaVersion": 1,
"libraries": []
}
```
单个模型至少包含:
```json
{
"type": "cylinder",
"modelType": "cylinder",
"modelVersion": "1.0.0",
"label": "气瓶",
"symbol": "cylinder",
"order": 10,
"category": {
"id": "storage",
"label": "储能元件",
"order": 10
},
"ports": [],
"parameters": []
}
```
前端读取规则:
1. 按库 `order`、分类 `order`、模型 `order` 排序。
2. 使用 `type` 作为拖拽数据和工程文件中的稳定类型。
3. 使用 `label` 显示中文名称。
4. 使用 `ports` 生成 React Flow Handle。
5. 使用 `parameters` 生成参数输入和单位选择控件。
6. 使用 `symbol` 选择图标渲染器。
7. 不在前端重新定义参数默认值、边界或端口语义。
当前前端保留内置兜底目录,用于后端未启动时继续打开工程。兜底只是一种开发期
容错机制,不能成为新增模型的正式注册方式;正式环境应明确提示目录加载失败。
### 14.1 前端实际读取步骤
React Flow 启动时:
1. 使用 `no-store` 请求 `/api/components/catalog`。
2. 检查 `schemaVersion == 1`。
3. 检查库、模型、端口和参数结构。
4. 检查模型 `type` 是否全局重复。
5. 将参数数组转换为参数面板定义。
6. 按库、分类和模型的 `order` 排序。
7. 成功时显示“后端目录”。
8. 请求或格式校验失败时显示“内置兜底”并使用开发期兜底目录。
前端兜底目录不保证包含新模型。新增公开模型后,只要 FastAPI 正常提供目录,前端
就能读取;若要求后端离线时也显示新模型,才需要有意识地同步兜底定义。兜底定义
仍不能成为端口、参数或默认值的权威来源。
### 14.2 修改后如何生效
修改 Python 模型、库清单或注册器后必须重启 FastAPI:
```powershell
cd F:\Master\SystemSimulationApp
.\.venv-win\Scripts\python.exe -m uvicorn app.main:app --host 127.0.0.1 --port 8000
```
只刷新浏览器无法让已运行的 Python 进程重新导入模型。前端源代码由 Vite 开发服务
热更新;普通目录内容变化不需要重启 Vite。
## 15. System XML 映射
System XML 中:
```xml
<Component
id="cylinder_1"
name="cylinder_1"
type="cylinder"
componentType="cylinder">
```
映射规则:
- `id`:工程内唯一的组件实例 ID。
- `name`:用户可修改的组件实例名称。
- `type`:必须匹配唯一的 `MODEL_TYPE`。
- `componentType`:当前为兼容字段,应与 `type` 相同。
- `<Port name>`:必须存在于模型的 `PORTS`。
- `<Parameter name>`:必须存在于模型的 `PARAMETERS`。
XML 解析器只负责结构、引用和契约校验;模型注册中心负责选择 Python 类并创建实例;
模型自身负责方程和状态。三层职责不得混合。
## 16. 测试要求
每个新模型至少需要:
1. 元数据测试:模型类型、端口、参数和结果声明合法。
2. 目录测试:模型出现在正确库和分类中。
3. 默认创建测试:默认参数能构造模型。
4. 参数边界测试:非法值被拒绝,错误信息包含组件和参数。
5. 端口测试:实例端口与声明完全一致。
6. XML 测试:最小系统能解析并映射到正确模型。
7. 方程测试:至少验证一个稳态、残差或守恒关系。
8. 最小仿真测试:一个短时算例能产生有限结果和结构化结果元数据。
库级测试还应检查:
- 库清单中的所有类均可导入。
- 所有模型类型全局唯一。
- 无遗漏或重复分类。
- `GET /api/components/catalog` 满足目录 schema。
## 17. 新增模型操作清单
开发者新增模型时只执行以下步骤:
1. 在目标库的正确分类目录中新建模型文件。
2. 实现 `MODEL_TYPE`、`MODEL_VERSION`、`PORTS`、`PARAMETERS`、
`RESULT_VARIABLES` 和 `DISPLAY`。
3. 实现统一 `create()` 和模型方程。
4. 将模型类路径加入该库 `library.py` 的 `models`。
5. 添加模型单元测试和最小 XML/仿真测试。
6. 运行注册校验和完整测试。
7. 重启 FastAPI,刷新前端确认目录来源为“后端目录”。
正常情况下不需要修改:
- React Flow 左侧组件列表。
- 参数面板字段。
- System XML 模型类型分派代码。
- 结果变量关键词映射。
- 集中式模型工厂表。
### 17.1 AI 修改约束
AI 在处理组件库读取任务时必须:
1. 先确认目标是新增模型、修改模型还是新增库。
2. 读取当前启用列表和目标库清单。
3. 只把公开模型加入 `library.py/models`。
4. 不通过递归扫描替代显式清单。
5. 不在前端重新声明后端契约作为正式实现。
6. 不静默跳过加载失败的模型。
7. 保留未知 `symbol` 的通用图标回退能力。
8. 修改后检查目录响应,并运行注册表和前端构建测试。
9. 告知用户需要重启 FastAPI。
AI 不应仅因为某个 `.py` 文件位于组件目录,就假定它是公开模型。公开性的唯一判断
依据是该类是否出现在已启用库的 `models` 清单中。
## 18. 当前实现与目标规范的差异
| 能力 | 当前状态 | 目标 |
| --- | --- | --- |
| 库 ID、名称、版本、分类和模型清单 | 已实现 | 由各库 `library.py` 维护 |
| 库和模型注册表 | 已实现 | 由已启用库清单自动构建 |
| 前端目录接口 | 已实现 | 已包含库版本和模型版本 |
| 前端动态分类和参数读取 | 已实现 | 新增已支持图标的模型无需改组件列表 |
| 端口与参数契约 | 已实现 | 保持为唯一事实来源 |
| 结构化结果元数据 | 已实现 | 保持为唯一事实来源 |
| `DISPLAY` | 已实现 | 由公开模型类自行声明 |
| 模型工厂 | 已实现 | 模型类统一 `create()` |
| 模型发现 | 已实现 | 按库清单受控发现 |
| 启动校验 | 已实现首版 | 覆盖版本、分类、端口、参数、单位和默认实例 |
| XML/工程中的模型版本与迁移 | 未实现 | 正式库发布前补齐 |
| 目录 JSON Schema | 已实现 | `schemas/component-catalog-v1.schema.json` |
## 19. 推荐实施顺序
1. 已完成:声明类型已放入独立的 `core/catalog.py`。
2. 已完成:`experimental/library.py` 已成为临时库唯一清单入口。
3. 已完成:五个公开模型自行声明 `DISPLAY` 和 `MODEL_VERSION`。
4. 已完成:公开模型统一实现 `create()`,集中式工厂函数已删除。
5. 已完成:注册表由库清单构建,并在导入时执行契约和默认实例校验。
6. 已完成:已增加组件目录 JSON Schema。
7. 待完成:在 System XML 和工程文件中保存模型版本,并设计迁移机制。
8. 待完成:规范稳定后新建正式组件库,不再向 `experimental` 增加生产模型。
该顺序可以保证每一步都保持现有前端和 System XML 可用,不需要一次性重写模型、
解析器和界面。
## 20. 改动影响表
| 想做的改动 | 必须修改 | 通常不需要修改 |
| --- | --- | --- |
| 新增同库同分类模型 | 模型文件、`library.py/models`、测试 | 注册表、前端参数列表 |
| 新增分类 | 库 `categories`、模型 `DISPLAY.category_id`、测试 | 物理端口和求解器 |
| 新增组件库 | 新库包和 `library.py`、启用列表、测试 | 已有库清单 |
| 修改参数默认值或范围 | 模型 `PARAMETERS`、测试、必要的版本 | 前端参数硬编码 |
| 修改端口 | 模型 `PORTS`、`DISPLAY.ports`、XML/网络测试、版本迁移 | 库分类 |
| 新增专用图标 | 模型 `DISPLAY.symbol`、前端图标渲染器 | 参数和物理方程 |
| 新增物理域 | 端口协议、网络、求解器、XML、前端兼容规则和测试 | 仅修改分类名称 |
| 修改目录响应结构 | 后端序列化、JSON Schema、前端解析、协议版本和测试 | 单个模型方程 |
## 21. 读取故障排查
| 现象 | 优先检查 |
| --- | --- |
| 模型完全没有出现在目录响应 | 模型类路径是否加入已启用库的 `models` |
| FastAPI 无法启动 | 启动错误中的库、模型和字段;通常是契约校验失败 |
| 接口有模型但前端没有 | `schemaVersion`、前端控制台、目录规范化错误 |
| 前端显示“内置兜底” | 8000 端口、`/api/components/catalog`、后端是否重启 |
| 分类错误 | `DISPLAY.category_id` 与库 `categories` |
| 端口数量或位置错误 | `PORTS` 与 `DISPLAY.ports` 是否完全一致 |
| 参数面板缺字段 | 模型 `PARAMETERS` 和目录响应,不先改前端 |
| XML 报不支持类型 | XML `type` 是否精确匹配 `MODEL_TYPE` |
| 图标是通用图形 | `symbol` 尚无专用前端渲染器,但模型仍应可用 |
最小诊断命令:
```powershell
.\.venv-win\Scripts\python.exe -c "from app.simulation.registry import build_component_catalog; print(build_component_catalog())"
.\.venv-win\Scripts\python.exe -m unittest tests.test_component_registry tests.test_component_catalog
```
+683
View File
@@ -0,0 +1,683 @@
# 组件模型建模规范 v1
状态:已在 `experimental` 临时组件库实施
适用对象:人工开发者、代码生成工具和 AI 编程助手
配套读取规范:[组件库分类、发现与读取规范 v1](component-library-spec-v1.md)
## 1. 文档目标
本文档规定一个 Python 仿真元件应如何创建、修改、测试和注册。完成后的模型必须
同时满足四个使用方:
1. 求解器能够实例化模型并调用方程。
2. System XML 能够根据稳定类型找到模型。
3. React Flow 能够自动显示图标、端口和参数。
4. 结果页面能够根据结构化元数据展示变量。
本文档是模型代码的开发合同。若本文档与当前代码行为不一致,应把它视为缺陷:
先核对实际实现,再在同一次修改中同步代码、测试和文档,禁止让两套规则长期并存。
## 2. 开始前先判断任务类型
### 2.1 新增公开模型
公开模型会出现在前端组件库中,也能被 System XML 创建。必须:
- 放入某个组件库的分类目录。
- 实现完整模型契约。
- 加入该库 `library.py` 的 `models` 清单。
- 添加目录、契约、方程和最小仿真测试。
### 2.2 修改已有公开模型
必须先判断改动是否破坏已有工程:
| 改动 | 版本建议 | 兼容性要求 |
| --- | --- | --- |
| 修复数值实现但不改变契约 | 修订版本 | 旧 XML 和工程继续可用 |
| 新增有默认值的参数或结果 | 次版本 | 旧工程缺少该字段时必须有迁移或默认值 |
| 修改界面名称或图标 | 库修订版本 | 不修改机器标识 |
| 修改方程的物理语义 | 根据影响提高次版本或主版本 | 补充基准和变更说明 |
| 删除、改名端口或参数 | 主版本 | 必须设计工程和 XML 迁移 |
| 修改 `MODEL_TYPE` | 视为新模型 | 旧类型必须保留迁移映射 |
### 2.3 新增内部模型
仅供固定算例或研究代码使用、不进入前端目录的模型,不加入 `library.py`。这类模型
应放在对应 `examples/` 或专用系统目录,不能与公开模型混放后依赖扫描规则排除。
当前示例是
[`app/simulation/examples/testmodel/dynamic_pipe.py`](../app/simulation/examples/testmodel/dynamic_pipe.py)。
### 2.4 新增物理域
仅新增模型类不足以支持新物理域。除了模型,还必须设计:
- `PortDefinition` 和端口变量。
- 变量角色与连接规则。
- 网络兼容性检查。
- 代数方程和 stream/signal 传播。
- XML 端口协议。
- 前端连线兼容规则。
- 最小闭合系统与求解测试。
没有完成这些基础能力时,不得仅通过修改 `domain` 字符串宣称支持新物理域。
## 3. 开发前必须读取的文件
人工或 AI 在修改模型前,应按顺序读取:
1. 本文档。
2. 目标库的 `library.py`。
3. 同分类中物理行为最接近的现有模型。
4. [`core/base.py`](../app/simulation/core/base.py)。
5. [`core/ports.py`](../app/simulation/core/ports.py)。
6. [`core/metadata.py`](../app/simulation/core/metadata.py)。
7. [`core/catalog.py`](../app/simulation/core/catalog.py)。
8. [`registry.py`](../app/simulation/registry.py) 中的启动校验。
9. 与目标模型最接近的测试。
不要只根据文件名、前端图标或旧 XML 猜测模型语义。
## 4. 文件位置和命名
公开模型放在:
```text
app/simulation/components/<library_id>/<category_id>/<model_module>.py
```
例如:
```text
app/simulation/components/experimental/storage/cylinder.py
app/simulation/components/experimental/flow/orifice.py
app/simulation/components/experimental/junctions/tee.py
```
规则:
- 一个公开模型原则上对应一个文件和一个主要模型类。
- 模块名、`MODEL_TYPE`、端口名和参数名使用稳定机器标识。
- `MODEL_TYPE` 使用小写 `snake_case`。
- 参数和结果变量允许保留已有热力学惯例,如 `T0`、`T`、`U`。
- 中文名称只写入 `label`,不能代替机器标识。
- 求解器、介质和网络通用逻辑不得复制到模型文件。
## 5. 公开模型完整契约
每个公开模型类必须在自身类体中显式声明:
```python
MODEL_TYPE = "example_component"
MODEL_VERSION = "1.0.0"
PORTS = (...)
PARAMETERS = (...)
RESULT_VARIABLES = (...)
DISPLAY = ...
```
同时必须实现:
```python
@classmethod
def create(
cls,
*,
name: str,
medium: IdealGasMedium,
parameters: Mapping[str, float],
) -> Component:
...
```
注册器要求这些字段直接存在于公开模型类中。不要依赖父类隐式提供
`MODEL_TYPE`、`MODEL_VERSION`、`PORTS`、`PARAMETERS`、`RESULT_VARIABLES`、
`DISPLAY` 或 `create()`。
## 6. 基类选择
### 6.1 `AlgebraicComponent`
适用于没有积分状态、由当前端口变量和参数直接决定残差的元件,例如:
- 孔板
- 阀门
- 阻性管段
- 理想三通
至少实现:
- 构造函数和端口注册。
- `create()`。
- `pressure_flow_equation_residuals()`。
- 需要传递 stream 变量时实现 `update_stream_outflows()`。
### 6.2 `ThermodynamicVolumeComponent`
适用于包含质量和能量状态的气体容腔,例如:
- 气瓶
- 贮箱
- 有容积的管段
至少实现:
- `get_state_vector()`。
- `set_state_vector()`。
- `refresh_thermodynamic_ports()`。
- `state_derivative_from_ports()`。
- `pressure_flow_equation_residuals()`。
该基类已经提供标准热力学组件结果:
```text
m, U, p, T, rho, u, h
```
除非物理含义不同,不要重新复制这组结果声明。
### 6.3 其他基类
如果现有基类不能表达模型,应先评估是否缺少一种通用组件能力。不要为了一个模型
直接把专用判断塞入 `SimulationNetwork` 或求解器。
## 7. 端口建模规范
当前气动模型使用:
```python
PortDefinition.pneumatic(
"port_a",
nominal_role="bidirectional",
)
```
气动端口包含:
| 变量 | 角色 | 连接规则 | SI 单位 |
| --- | --- | --- | --- |
| `p` | `effort` | `equal` | `Pa` |
| `m_flow` | `flow` | `sumToZero` | `kg/s` |
| `h_outflow` | `stream` | `streamMix` | `J/kg` |
必须遵守:
- `m_flow > 0` 表示质量流入当前组件。
- `nominal_role` 只用于界面和默认布局,不限制实际流向。
- 物理连接是非因果的,连接线端点顺序不代表流向。
- 所有声明端口必须使用 `register_declared_port()` 创建。
- `DISPLAY.ports` 必须与 `PORTS` 名称集合完全一致。
- 分支连接使用三通等连接元件,不能让一个物理端口直接连接多条边。
禁止:
- 在模型内部根据画布左右方向判断流向。
- 为了前端显示另造一套端口名。
- 把 `port_a` 固定解释为真实入口、把 `port_b` 固定解释为真实出口。
- 直接绕过端口状态读写其他组件对象。
## 8. 参数建模规范
所有用户可配置输入必须使用 `ParameterDefinition`:
```python
ParameterDefinition(
name="volume",
label="容积",
quantity="volume",
unit="m3",
default=0.1,
minimum=0.0,
minimum_exclusive=True,
)
```
字段含义:
| 字段 | 规则 |
| --- | --- |
| `name` | 稳定机器名,同时用于 XML、工程文件和 `create()` |
| `label` | 前端显示名称,不能为空 |
| `quantity` | 受控物理量标识 |
| `unit` | 后端 SI 基准单位 |
| `default` | 必须能够创建有效模型 |
| `minimum` / `maximum` | 必须反映方程有效范围 |
| `minimum_exclusive` | 用于直径、容积等严格大于零的量 |
当前受控单位定义在 `SI_UNIT_BY_QUANTITY`:
| quantity | SI 单位 |
| --- | --- |
| `area` | `m2` |
| `dimensionless` | 空字符串 |
| `density` | `kg/m³` |
| `flow_coefficient` | `kg/(s*Pa^0.5)` |
| `heat_transfer_coefficient` | `W/(m2*K)` |
| `internal_energy` | `J` |
| `length` | `m` |
| `mass` | `kg` |
| `mass_flow` | `kg/s` |
| `pressure` | `Pa` |
| `specific_enthalpy` | `J/kg` |
| `specific_internal_energy` | `J/kg` |
| `temperature` | `K` |
| `velocity` | `m/s` |
| `volume` | `m3` |
新增物理量时必须先扩展后端受控单位表,再评估前端是否需要单位换算选项。禁止在
单个模型中私自拼写新的同义 `quantity`。
构造函数必须调用:
```python
self.set_parameter_values(
{
"volume": volume,
"p0": p0,
"T0": T0,
}
)
```
保存值、方程计算和结果输出都使用 SI。前端显示单位变化不能改变后端参数语义。
## 9. 结果变量规范
### 9.1 组件级结果
组件自身状态或派生量使用 `ResultVariableDefinition`:
```python
ResultVariableDefinition(
name="pressure_drop",
label="压降",
quantity="pressure",
unit="Pa",
category="derived",
order=10,
)
```
声明后必须在 `component_result_values()` 返回同名值:
```python
def component_result_values(self) -> Mapping[str, float]:
return {
"pressure_drop": self.port_a.p - self.port_b.p,
}
```
声明集合和返回键必须一致。
### 9.2 端口结果
端口结果由 `PORTS` 的端口变量自动产生,不要在 `RESULT_VARIABLES` 中重复声明
`port_a.p`、`port_a.m_flow` 等字段。
### 9.3 禁止暴露的内容
以下内容默认不能作为用户结果:
- 非线性求解器内部未知量索引。
- 缩放残差和迭代缓存。
- 仅用于调试的临时中间值。
- 可以由已有结果稳定推导、但没有明确工程用途的重复字段。
## 10. 显示声明规范
公开模型必须声明 `DISPLAY`:
```python
DISPLAY = ComponentDisplaySpec(
label="示例阻力元件",
library_id="experimental",
category_id="flow",
symbol="generic",
ports=(
PortDisplaySpec("port_a", "left", order=10),
PortDisplaySpec("port_b", "right", order=20),
),
order=90,
)
```
规则:
- `library_id` 必须等于所属库 ID。
- `category_id` 必须存在于所属库的 `categories`。
- `symbol` 是前端图形键,不是模型类型。
- 未实现专用图标时使用新的稳定键,前端会回退到通用图形。
- 只有确实需要专用工程图标时才修改前端图标渲染器。
- `side` 只允许 `left` 或 `right`。
- 旋转和镜像不能改变端口名或物理语义。
## 11. 标准创建入口
`create()` 是注册器创建模型的唯一入口:
```python
@classmethod
def create(
cls,
*,
name: str,
medium: IdealGasMedium,
parameters: Mapping[str, float],
) -> ExampleComponent:
return cls(
name=name,
medium=medium,
coefficient=parameters["coefficient"],
)
```
注册器会在调用前:
1. 补齐默认参数。
2. 拒绝未知参数。
3. 检查有限值和边界。
调用后还会检查:
1. 返回对象类型正确。
2. 实例 `model_type` 与 `MODEL_TYPE` 一致。
3. 实际端口与 `PORTS` 完全一致。
4. 实例保存的参数与规范化参数完全一致。
`create()` 不应重复实现参数默认值和边界校验,也不能静默修改传入参数。
## 12. 方程实现要求
模型方程必须满足:
- 残差形式统一为“期望等式左侧减右侧”。
- 每条 `EquationResidual` 使用稳定、可定位的 `id`。
- `variables` 列出该残差实际涉及的端口量或状态。
- `role` 与方程主要约束的物理角色一致。
- 对零压差、零流量和反向流动给出有限结果。
- 必要正则化必须有物理解释,并通过边界测试保护。
- 不得用画布坐标、连接线方向或组件名称决定方程。
动态模型还必须:
- 状态向量长度稳定。
- `get_state_vector()` 和 `set_state_vector()` 互为逆操作。
- 状态导数满足质量和能量守恒约定。
- 初始化默认值能够产生有限介质状态。
## 13. 可复制的代数模型模板
下面是一个符合当前规范的两端口代数阻力模板。复制后必须根据真实物理模型修改
类型、参数、方程、名称和测试,不能只改类名就注册。
```python
from __future__ import annotations
from collections.abc import Mapping
from math import sqrt
from app.simulation.core.base import AlgebraicComponent
from app.simulation.core.catalog import ComponentDisplaySpec, PortDisplaySpec
from app.simulation.core.equations import EquationResidual
from app.simulation.core.metadata import ParameterDefinition
from app.simulation.core.medium import IdealGasMedium
from app.simulation.core.ports import PortDefinition
class ExampleRestriction(AlgebraicComponent):
MODEL_TYPE = "example_restriction"
MODEL_VERSION = "1.0.0"
PORTS = (
PortDefinition.pneumatic("port_a", nominal_role="bidirectional"),
PortDefinition.pneumatic("port_b", nominal_role="bidirectional"),
)
PARAMETERS = (
ParameterDefinition(
name="K",
label="流量系数",
quantity="flow_coefficient",
unit="kg/(s*Pa^0.5)",
default=1e-5,
minimum=0.0,
),
)
RESULT_VARIABLES = ()
DISPLAY = ComponentDisplaySpec(
label="示例阻力元件",
library_id="experimental",
category_id="flow",
symbol="generic",
ports=(
PortDisplaySpec("port_a", "left", order=10),
PortDisplaySpec("port_b", "right", order=20),
),
order=90,
)
def __init__(self, name: str, K: float = 1e-5) -> None:
super().__init__(name)
self.set_parameter_values({"K": K})
self.K = K
self.port_a = self.register_declared_port("port_a")
self.port_b = self.register_declared_port("port_b")
@classmethod
def create(
cls,
*,
name: str,
medium: IdealGasMedium,
parameters: Mapping[str, float],
) -> ExampleRestriction:
return cls(name=name, K=parameters["K"])
def pressure_flow_equation_residuals(
self,
) -> tuple[EquationResidual, ...]:
pressure_difference = self.port_a.p - self.port_b.p
expected_flow = (
self.K
* sqrt(abs(pressure_difference))
* (1.0 if pressure_difference > 0.0 else -1.0)
if pressure_difference != 0.0
else 0.0
)
return (
EquationResidual(
id=f"{self.name}:mass_flow_balance",
owner="component",
owner_id=self.name,
relation="sumToZero",
variables=(
f"{self.name}.port_a.m_flow",
f"{self.name}.port_b.m_flow",
),
role="flow",
value=self.port_a.m_flow + self.port_b.m_flow,
),
EquationResidual(
id=f"{self.name}:pressure_flow_relation",
owner="component",
owner_id=self.name,
relation="constitutive",
variables=(
f"{self.name}.port_a.p",
f"{self.name}.port_b.p",
f"{self.name}.port_a.m_flow",
),
role="flow",
value=self.port_a.m_flow - expected_flow,
),
)
def update_stream_outflows(
self,
connected_h: Mapping[str, float],
) -> None:
self.port_a.h_outflow = connected_h["port_b"]
self.port_b.h_outflow = connected_h["port_a"]
```
真实现有模型可参考:
- 储能元件:
[`cylinder.py`](../app/simulation/components/experimental/storage/cylinder.py)
- 阻性元件:
[`orifice.py`](../app/simulation/components/experimental/flow/orifice.py)
- 多端口连接元件:
[`tee.py`](../app/simulation/components/experimental/junctions/tee.py)
## 14. 注册模型
模型文件完成后,只修改所属库的 `library.py`:
```python
models=(
# 已有模型
"app.simulation.components.experimental.flow.example_restriction:ExampleRestriction",
)
```
禁止:
- 直接修改 `COMPONENT_MODEL_REGISTRY`。
- 在前端复制参数和端口定义作为正式来源。
- 递归扫描组件目录自动导入所有 `.py`。
- 同时注册两个相同 `MODEL_TYPE`。
- 把测试类、抽象基类或内部算例模型加入公开清单。
## 15. 测试要求
每个公开模型至少添加:
1. 静态契约测试。
2. 默认参数创建测试。
3. 参数边界测试。
4. 端口与显示布局一致性测试。
5. 关键方程残差测试。
6. 零流量或反向流动测试。
7. 目录输出测试。
8. 最小 XML 编译测试。
9. 能进入通用求解器的模型,再添加短时仿真测试。
推荐先运行:
```powershell
.\.venv-win\Scripts\python.exe -m unittest `
tests.test_component_registry `
tests.test_component_catalog `
tests.test_component_metadata
```
然后运行完整回归:
```powershell
.\.venv-win\Scripts\python.exe -m unittest discover -s tests
```
目录契约影响前端时还要运行:
```powershell
cd frontend
$env:Path = 'F:\Master\SystemSimulationApp\.tools\node-v24.18.0-win-x64;' + $env:Path
npm.cmd run build
```
## 16. 修改已有模型的安全步骤
1. 找到 `MODEL_TYPE` 的所有 XML、工程和测试引用。
2. 记录修改前的端口、参数、结果和默认行为。
3. 判断版本级别和是否需要迁移。
4. 先增加或修改测试,明确预期物理行为。
5. 修改模型类,不在注册器和前端复制规则。
6. 检查默认实例和旧参数是否仍能创建。
7. 检查最小系统是否仍然闭合。
8. 运行针对性测试和完整回归。
9. 同步本文档或模型专属说明中的物理假设。
## 17. 人工或 AI 的任务输入卡
为了减少猜测,新增模型前建议先填写:
```text
模型中文名称:
MODEL_TYPE:
所属 library_id:
所属 category_id:
物理域:
模型用途和边界:
端口列表及含义:
参数列表、SI 单位、默认值和范围:
状态变量:
代数方程或微分方程:
正流量约定:
需要显示的组件结果:
已知参考模型或工程公式:
最小测试系统:
允许的近似:
明确不实现的能力:
```
如果关键物理信息缺失,AI 应先通过现有模型、测试或用户提供的参考补齐;不能仅凭
组件名称自行创造方程。
## 18. AI 修改协议
AI 创建或修改模型时必须遵守:
### 修改前
1. 读取第 3 节列出的文件。
2. 检查工作区已有改动,不能覆盖无关修改。
3. 明确模型是公开模型还是内部模型。
4. 明确端口物理域、状态、参数、方程和结果。
5. 找到最接近的现有模型并沿用代码风格。
### 修改中
1. 将物理契约保存在模型类中。
2. 只在库清单中登记公开模型。
3. 不修改集中注册表来加入单个模型。
4. 不为了让测试通过而放宽全局校验。
5. 不改变现有模型标识,除非任务明确要求迁移。
6. 不把前端拖拽方向当作物理流向。
7. 不把求解器失败简单隐藏为默认结果。
### 修改后
1. 展示涉及的模型、清单和测试文件。
2. 报告版本变化和兼容性影响。
3. 运行针对性测试、完整后端测试和必要的前端构建。
4. 检查 `GET /api/components/catalog` 中的模型、分类、端口和参数。
5. 告知用户需要重启 FastAPI 才能加载新的 Python 模块。
6. 未执行的校验必须明确说明原因。
## 19. 常见失败与处理
| 现象 | 常见原因 | 处理 |
| --- | --- | --- |
| FastAPI 启动时报模型缺少声明 | 字段继承自父类或漏写 | 在公开模型类中显式声明 |
| 模型未出现在前端 | 未加入 `library.py` 或后端未重启 | 检查清单并重启 FastAPI |
| 前端显示“内置兜底” | `/api/components/catalog` 不可用 | 检查 8000 端口和接口响应 |
| 显示端口校验失败 | `DISPLAY.ports` 与 `PORTS` 不一致 | 使用相同端口名和完整集合 |
| 单位校验失败 | `quantity` 与 SI 单位不匹配 | 使用受控单位表或先扩展规范 |
| 默认模型无法注册 | 默认参数越界或构造函数未保存参数 | 修复默认值和 `set_parameter_values()` |
| XML 报不支持模型 | XML `type` 与 `MODEL_TYPE` 不一致 | 修正类型或提供迁移 |
| 模型可显示但无法仿真 | 只完成目录元数据,方程或物理域求解未实现 | 补齐方程、网络和求解测试 |
## 20. 完成定义
一个模型只有同时满足以下条件才算完成:
- 模型契约完整且启动校验通过。
- 默认参数和边界有效。
- 端口、参数和结果具有稳定物理含义。
- 方程覆盖零流量、正常流动和必要的反向流动。
- 模型已加入正确库清单。
- 目录接口能自动输出模型。
- 前端无需复制参数和端口定义即可使用。
- XML 能映射到正确模型。
- 最小系统能够编译;声称可仿真的模型必须产生有限结果。
- 针对性测试、完整回归和必要的前端构建通过。
- 文档记录了模型假设、适用范围和已知限制。
+72
View File
@@ -0,0 +1,72 @@
# System XML v1 协议
> 此版本仅用于识别旧文件。新项目使用 [System XML v2](system-xml-v2.md),物理连接在 v2 中改为无序端点。
System XML 是 ReactFlow 前端与 `app.simulation` 仿真层之间的稳定交换格式。
## 基本约定
- 根元素固定为 `System`,`schemaVersion` 固定为 `1`。
- `unitSystem` 固定为 `SI`。组件参数必须保存为 SI 基准值,界面显示单位不进入数值换算语义。
- 子元素顺序固定为 `Simulation`、`Components`、`Connections`。
- `Component.id` 是稳定的仿真实例 ID;`name` 是用户可编辑的显示名称。
- `Component.type` 是仿真后端模型类型;`componentType` 是前端组件类型。
- `Connection.source/target` 只标识图形拓扑端点,不代表仿真中的实际流动方向。正流和倒流由求解器决定。
- v1 文档不携带结果数据;仿真结果通过运行接口返回。
## 完整示例
```xml
<?xml version="1.0" encoding="UTF-8"?>
<System name="transfer-system" schemaVersion="1" unitSystem="SI">
<Simulation tStart="0" tStop="2" step="0.1" maxStep="0.005" method="BDF"/>
<Components>
<Component id="cylinder_1" name="cylinder_1" type="cylinder" componentType="cylinder" x="90" y="180">
<Port name="port_b"/>
<Parameter name="volume" value="0.01"/>
<Parameter name="p0" value="35000000"/>
<Parameter name="T0" value="300"/>
</Component>
<Component id="tank_1" name="tank_1" type="tank" componentType="tank" x="420" y="180">
<Port name="port_a"/>
<Parameter name="volume" value="0.1"/>
<Parameter name="p0" value="100000"/>
<Parameter name="T0" value="300"/>
</Component>
</Components>
<Connections>
<Connection id="edge-1" source="cylinder_1" sourcePort="port_b" target="tank_1" targetPort="port_a"/>
</Connections>
</System>
```
## Simulation
| 属性 | 类型 | 含义 | 约束 |
|---|---|---|---|
| `tStart` | double | 仿真起始时间,单位 s | 有限数 |
| `tStop` | double | 仿真结束时间,单位 s | 必须大于 `tStart` |
| `step` | double | 结果采样间隔,单位 s | 必须大于 0 |
| `maxStep` | double | 求解器最大积分步长,单位 s | 必须大于 0 |
| `method` | string | 求解器名称,例如 `BDF` | 非空 |
## Components
每个 `Component` 必须包含唯一 `id`、显示名称、模型类型、前端类型和画布坐标。组件下可以包含多个 `Port` 和 `Parameter`。
参数的 `value` 必须是可转换为有限浮点数的 SI 值。具体组件支持的端口、参数和取值范围由后续组件注册表协议约束。
## Connections
每个 `Connection` 必须包含唯一 `id` 和两个完整端点:
- `source` + `sourcePort`
- `target` + `targetPort`
端点中的组件 ID 必须存在,端口名称必须属于对应组件。v1 前端仍使用 source/target 组织连线,但仿真层必须将它们视为连接的两个端点,不得直接解释为固定物理流向。
## 版本兼容
- 解析器遇到缺少 `schemaVersion` 的旧 XML 时,应明确报告“旧协议”,不能静默当作 v1。
- v1 新增可选字段时必须保持旧解析器可忽略;删除字段或改变字段语义时必须升级主版本。
- XSD 只验证文档结构和基础数据类型。跨字段约束、组件端口规则和拓扑可求解性由仿真校验层负责。
+216
View File
@@ -0,0 +1,216 @@
# System XML v2 协议
System XML v2 是 ReactFlow 建模前端与 `app.simulation` 仿真层之间的交换格式。v2 将物理端口与信号端口分开,并移除了物理连接中的方向语义。
## 基本约定
- 根元素 `System` 的 `schemaVersion` 固定为 `2`,`unitSystem` 固定为 `SI`。
- 子元素顺序固定为 `Simulation`、`Components`、`Connections`。
- `Component.id` 是稳定实例 ID,`name` 是用户可编辑名称。
- 参数以 SI 基准值保存;显示单位不改变 XML 中的数值含义。
- 物理端口统一规定 `m_flow > 0` 表示流入组件,`m_flow < 0` 表示流出组件。
- `nominalRole` 只表示设计意图和展示语义,不限制实际流向。
- v2 文档不携带仿真结果;端口实际流向、流量幅值和累计质量由运行结果接口返回。
## 完整示例
```xml
<?xml version="1.0" encoding="UTF-8"?>
<System name="transfer-system" schemaVersion="2" unitSystem="SI">
<Simulation tStart="0" tStop="2" step="0.1" maxStep="0.005" method="BDF"/>
<Components>
<Component id="cylinder_1" name="cylinder_1" type="cylinder" componentType="cylinder" x="90" y="180" rotation="0" mirrored="false">
<Port name="port_b" kind="physical" domain="pneumatic" nominalRole="outlet" positiveFlowDirection="intoComponent" side="right"/>
<Parameter name="volume" value="0.01"/>
<Parameter name="p0" value="35000000"/>
<Parameter name="T0" value="300"/>
</Component>
<Component id="tank_1" name="tank_1" type="tank" componentType="tank" x="420" y="180" rotation="0" mirrored="false">
<Port name="port_a" kind="physical" domain="pneumatic" nominalRole="inlet" positiveFlowDirection="intoComponent" side="left"/>
<Parameter name="volume" value="0.1"/>
<Parameter name="p0" value="100000"/>
<Parameter name="T0" value="300"/>
</Component>
</Components>
<Connections>
<Connection id="edge-1" kind="physical" domain="pneumatic">
<Endpoint component="cylinder_1" port="port_b"/>
<Endpoint component="tank_1" port="port_a"/>
</Connection>
</Connections>
</System>
```
## Port
组件的 `rotation` 只能为 `0/90/180/270`,`mirrored` 表示水平镜像。它们只用于恢复画布布局和端口显示位置,不参与物理方程或流向判断。
| 属性 | 含义 |
|---|---|
| `name` | 组件模型中的稳定端口名 |
| `kind` | `physical` 或 `signal` |
| `domain` | 端口物理域,当前流体组件使用 `pneumatic` |
| `nominalRole` | 物理端口使用 `inlet/outlet/bidirectional`;信号端口使用 `input/output` |
| `positiveFlowDirection` | 物理端口固定为 `intoComponent`;信号端口省略 |
| `side` | 前端图标上的 `left/right` 布局位置,不参与物理求解 |
物理端口的流向由求解结果决定。一个名义出口的 `m_flow > 0` 表示该端口发生实际流入,可在结果层标记为倒流。
三通的三个端口当前保留仿真模型已有名称 `port_in/port_out1/port_out2`,但全部声明为 `bidirectional`,名称不构成方向约束。
## Connection
物理连接包含两个无序 `Endpoint`。第一个端点不代表上游,第二个端点也不代表下游;交换二者顺序不得改变仿真结果。
信号连接也使用两个 `Endpoint`,但必须分别携带 `role="source"` 和 `role="target"`。信号端口只允许 `output` 与 `input` 相连。
连接生成前必须验证:
- 组件和端口存在。
- 两端 `kind` 相同。
- 两端 `domain` 相同。
- 信号连接一端为 `output`,另一端为 `input`。
## v1 迁移
- v1 的字符串端口在加载时按组件注册表迁移成 v2 端口对象。
- v1 的 `source/sourcePort/target/targetPort` 在导出 v2 时转换为两个 `Endpoint`。
- 物理连接不继承 v1 的 source/target 方向。
- v1 文件仍由原 XSD 描述;新生成文件只输出 v2。
XSD 负责结构和基础枚举校验,端口注册、拓扑完整性与可求解性由模型校验层负责。
## 仿真模型编译接口
`POST /api/reactflow/compile-model` 接收与工程保存、XML 导出相同的 ReactFlow 工程 JSON。它会执行以下操作:
1. 按 `node.data.modelType` 创建 `app.simulation` 组件实例,并写入 SI 参数。
2. 将前端端口声明与组件注册端口逐项比对。
3. 按画布实际 `edges` 创建无方向物理连接,而不是按组件类型或拖入顺序推断拓扑。
4. 检查端口存在性、物理域兼容性、重复连接和未连接端口。
成功响应中的物理连接只包含两个 `endpoints`,不包含 `source/target`:
```json
{
"success": true,
"name": "transfer-system",
"components": [
{
"id": "cylinder_1",
"type": "cylinder",
"ports": [
{
"name": "port_b",
"kind": "physical",
"domain": "pneumatic",
"nominalRole": "outlet",
"positiveFlowDirection": "intoComponent",
"variables": [
{"name": "p", "role": "effort", "connectionRule": "equal"},
{"name": "m_flow", "role": "flow", "connectionRule": "sumToZero"},
{"name": "h_outflow", "role": "stream", "connectionRule": "streamMix"}
]
}
]
}
],
"connections": [
{
"id": "edge-1",
"kind": "physical",
"domain": "pneumatic",
"endpoints": [
{"component": "cylinder_1", "port": "port_b"},
{"component": "tank_1", "port": "port_a"}
]
}
],
"unconnectedPorts": []
}
```
一个物理端口当前只允许一条连接;需要分支时必须显式放置 `Tee` 等结点组件。这样拓扑不会通过“一个端口连多条线”隐式产生结点方程。
此接口完成模型实例化、端口契约校验、拓扑编译和压力-流量方程结构组装。编译结果中的 `pressureFlowSystem` 包含未知量、方程、数量及 `isSquare` 状态;方阵只表示结构数量平衡,不代表方程一定可解。
当前组件已提供可执行残差:气瓶和贮箱提供状态-压力约束,孔板提供流量守恒和压差-流量本构关系,三通提供等压零结点和流量守恒。XML 注册表中的管段使用准稳态 Darcy 阻性模型,同时提供流量守恒和双向压降关系。连接层根据端口契约生成 `p` 相等及 `m_flow` 代数和为零的残差。
`/api/reactflow/simulate-testmodel` 继续保留固定 TestModel 和动态管段,用于已有基线对比。XML 驱动仿真使用独立的通用半显式求解链路,不调用固定 TestModel 闭合器。
## 第二阶段:XML 解析与校验
第二阶段已经实现从 System XML v2 回到仿真网络的完整入口。解析过程固定分为三层:
| 层级 | `layer` | 负责内容 |
|---|---|---|
| XML | `xml` | 文档大小、XML 语法、禁止 DTD 和实体声明 |
| XSD | `schema` | v2 版本、元素顺序、必填属性、枚举、基础数值类型 |
| 模型语义 | `semantic` | 组件注册、端口契约、参数集合和范围、端点引用、物理域、连接占用及仿真设置 |
校验诊断统一包含:
```json
{
"severity": "error",
"layer": "semantic",
"code": "ENDPOINT_PORT_UNKNOWN",
"message": "Connection edge-1 references unknown port tank_1.port_x.",
"path": "/System/Connections/Connection[1]/Endpoint[2]",
"line": 18
}
```
未连接端口使用 `PORT_UNCONNECTED` 警告,不会阻止解析和网络编译;结构错误、接口不一致、参数错误和非法拓扑会使 `valid=false`。
### API
四个接口均直接接收 `Content-Type: application/xml` 的原始 XML 请求体:
- `POST /api/system-xml/validate`:无论成功与否都返回校验报告,便于编辑器实时显示问题。
- `POST /api/system-xml/parse`:成功时返回规范化工程 JSON;失败时返回 HTTP `422` 和结构化诊断。
- `POST /api/system-xml/compile-model`:成功时返回仿真网络、仿真设置和校验报告;失败时返回 HTTP `422`。
- `POST /api/system-xml/simulate`:完成校验、编译、仿真准备、代数闭合、stream 传播和时间积分;成功时返回组件与端口时间序列,失败时返回 HTTP `422` 和仿真层诊断。
示例:
```powershell
Invoke-RestMethod `
-Method Post `
-Uri http://127.0.0.1:8000/api/system-xml/validate `
-ContentType application/xml `
-InFile .\test\system.xml
```
组件参数和端口定义集中在 `app/simulation/registry.py`。ReactFlow JSON 编译和 XML 语义校验共用该注册表,新增组件时必须先在这里登记参数范围、默认值和端口契约。
## 第三阶段:XML 驱动仿真 MVP
第三阶段当前已经打通:
1. XML 中的组件、参数和无方向物理连接编译成 `app.simulation` 网络。
2. 仿真准备层检查未连接端口、方程数量、无储能代数孤岛和无阻力储能直连。
3. SciPy 非线性最小二乘求解每个时刻的端口压力与质量流量。
4. 根据求解后的实际流向迭代传播 `h_outflow`,并在三通处执行质量流量加权混合。
5. 动态组件自动拼装质量及内能导数,使用 XML 的 `tStart/tStop/step/maxStep/method` 开展积分。
6. 结果包含动态组件的 `m/U/p/T/rho/u/h`,以及全部物理端口的 `p/m_flow/h_outflow` 时间序列。
当前限制:
- 只支持注册表中的气动物理组件,不支持信号端口仿真。
- 所有物理端口在运行前必须完成连接;分支必须显式使用三通。
- 每个独立物理网络必须包含至少一个气瓶或贮箱作为压力和焓的储能锚点。
- 两个储能组件不能通过理想连接或纯三通直接耦合,必须在中间放置孔板或管段。
- XML 管段当前是准稳态阻性元件,`p0/T0` 用于名义密度和初始代数猜测,不包含管内储气动态。
- 当前 stream 混合是适合 MVP 的正则化近似,还不是 Modelica `inStream/actualStream` 的严格复刻。
- 当前是半显式 ODE/代数求解链路,不支持一般高指数 DAE 和事件系统。
运行示例:
```powershell
Invoke-RestMethod `
-Method Post `
-Uri http://127.0.0.1:8000/api/system-xml/simulate `
-ContentType application/xml `
-InFile .\test\system.xml
```