完成求解器雅可比矩阵首轮优化,增加更新目录,整理了文档文件夹,增加了服务启动脚本

This commit is contained in:
lujingze committed 2026-08-17 07:33:31 +00:00
1 parent 6bb0591d32
commit 16a7eb2d6c
48 files changed
+8172 -217

No files matched your search

@@ -0,0 +1,228 @@
# 后端接口版本与定义规范 v1
本文统一说明 SystemSimulationApp 后端的接口边界、版本编号和事实来源。规范版本为
`1.0.0`。这个编号只表示本文档自身的修订版本,不等同于 HTTP API、System XML、
组件目录或单个模型的版本。
## 1. 当前版本基线
后端没有一个可以替代所有子版本的“总版本号”。调用方必须按所使用的数据合同读取
对应版本:
| 版本轴 | 当前值 | 作用范围 | 机器可读事实来源 |
| --- | --- | --- | --- |
| HTTP API | 未版本化 | `/api/...` 路由、请求和响应 | `app/main.py`、FastAPI OpenAPI |
| 后端应用发布版本 | 未定义 | 整个后端部署产物 | 当前没有包元数据或运行时常量 |
| 组件目录 Schema | `1` | `GET /api/components/catalog` | `schemas/component-catalog-v1.schema.json` |
| System XML Schema | `3` | XML 校验、编译和仿真输入 | `schemas/system-simulation-v3.xsd` |
| 组件库版本 | 各库独立 | 一个组件库的发布边界 | 各库 `library.py` 的 `version` |
| 模型合同版本 | 各模型独立 | 单个 `MODEL_TYPE` 的物理和数据合同 | 模型类的 `MODEL_VERSION` |
| ReactFlow 工程 JSON | `1` | 编辑器存档 | `projectSchemaVersion`、`ReactFlowProjectPayload` 和前端读取代码 |
| 结果文件格式 | `1` | 前端导入、导出的结果快照 | `SimulationResultsView.tsx` |
因此,`schemaVersion=3` 只能说明文件是 System XML v3,不能说明“后端 API 是
v3”;`MODEL_VERSION=0.2.0` 也只描述对应模型合同。
FastAPI 自动生成的 OpenAPI 当前可能显示默认 `info.version=0.1.0`;后端没有显式
声明这个值,因此它不是正式 API 或应用发布版本。
## 2. 事实来源和优先级
接口定义冲突时,按以下优先级处理:
1. 可执行代码和机器可读 Schema;
2. 自动化合同测试;
3. 当前版本规范文档;
4. 示例、调研记录和历史说明。
各类合同的唯一事实来源如下:
| 合同 | 唯一事实来源 |
| --- | --- |
| 公开模型类型和版本 | 模型类的 `MODEL_TYPE`、`MODEL_VERSION` |
| 端口物理合同 | 模型类的 `PORTS` 和 `app/simulation/core/ports.py` |
| 参数、结果及单位 | 模型类的 `PARAMETERS`、`RESULT_VARIABLES` |
| 组件库及分类 | 各库 `library.py` |
| 组件目录 JSON | `build_component_catalog()` 与目录 JSON Schema |
| System XML | v3 XSD、`app/system_xml.py` |
| 网络最终连接检查 | `SimulationNetwork.connect()` |
| HTTP 路由和请求模型 | `app/main.py` |
前端兜底目录、画布布局和示例 XML 不能反向定义后端物理合同。
## 3. 组件接口合同
每个公开组件必须显式声明:
```python
MODEL_TYPE = "example_component"
MODEL_VERSION = "1.0.0"
PORTS = (...)
PARAMETERS = (...)
RESULT_VARIABLES = (...)
DISPLAY = ...
```
并提供统一的 `create()` 入口。各字段含义如下:
- `MODEL_TYPE` 是工程、XML、目录和结果元数据共同使用的稳定机器标识;
- `MODEL_VERSION` 使用 `主版本.次版本.修订版本`;
- `PORTS` 定义端口名称、种类、物理域、名义角色、变量和连接规则;
- `PARAMETERS` 定义 SI 单位、默认值、范围和离散选项;
- `RESULT_VARIABLES` 定义结构化结果元数据;
- `DISPLAY` 只定义前端展示,不得成为物理方程的隐式输入。
物理流变量统一以进入组件为正。物理连接的两个端点无方向;信号方向由注册端口的
`output/input` 合同决定。
## 4. 组件目录接口
`GET /api/components/catalog` 返回组件库、分类和模型合同。响应顶层必须为:
```json
{
"schemaVersion": 1,
"libraries": []
}
```
目录结构由 `schemas/component-catalog-v1.schema.json` 约束。库必须携带
`version`,模型必须携带 `modelVersion`。目录 Schema 的整数版本只描述目录 JSON
结构;库和模型版本仍分别使用三段式版本号。
当前三段版本只接受 `数字.数字.数字`,不接受 prerelease 或 build metadata,不能直接
等同于完整 SemVer 实现。目录 Schema 对对象使用 `additionalProperties=false`,因此
新增字段也必须先评估严格消费者,不能只因字段可选就默认兼容。
目录响应是前端生成组件面板、参数编辑器和端口快照的来源。修改响应结构时必须同时
更新 JSON Schema、前端解析和合同测试。
## 5. System XML v3
System XML v3 是当前唯一支持的 XML 求解输入。根元素固定使用:
```xml
<System schemaVersion="3" unitSystem="SI">
```
每个组件必须包含 `id`、`type`、`modelVersion` 和完整 SI 参数;连接只包含两个
`Endpoint(component, port)`。端口合同由 `type + port` 从注册表恢复,XML 不保存
端口快照和画布布局。
`modelVersion` 必须与当前注册模型完全一致。版本不一致时返回
`COMPONENT_MODEL_VERSION_MISMATCH`,不会静默使用当前模型解释旧输入。完整结构见
`docs/standard/system-xml-v3.md` 和 `schemas/system-simulation-v3.xsd`。
## 6. HTTP API
FastAPI 当前直接注册未版本化的 `/api/...` 路由,没有 `/api/v1` 命名空间,也没有
运行时 `apiVersion`。主要业务接口如下:
| 方法与路径 | 请求合同 | 响应合同 |
| --- | --- | --- |
| `GET /api/components/catalog` | 无 | 组件目录 JSON v1 |
| `GET /api/reactflow/projects` | 无 | 已保存工程摘要列表 |
| `GET /api/reactflow/projects/{id}` | 工程 ID | 严格校验后的工程 JSON v1 |
| `POST /api/reactflow/projects/{id}` | `ReactFlowProjectPayload` | 保存摘要 |
| `POST /api/reactflow/system-xml` | `ReactFlowProjectPayload` | `application/xml`,System XML v3 |
| `POST /api/reactflow/compile-model` | `ReactFlowProjectPayload` | 编译网络 JSON |
| `POST /api/system-xml/validate` | 原始 XML v3 | 三层校验报告 |
| `POST /api/system-xml/parse` | 原始 XML v3 | 规范化执行模型 |
| `POST /api/system-xml/compile-model` | 原始 XML v3 | 校验报告、设置和网络 |
| `POST /api/system-xml/simulate` | 原始 XML v3 | 同步仿真结果 |
| `POST /api/system-xml/simulate-stream` | 原始 XML v3 | `application/x-ndjson` 事件流 |
| `GET /api/system-xml/simulations/{id}` | 路径 ID | 任务快照 |
| `POST /api/system-xml/simulations/{id}/cancel` | 取消原因 JSON | 取消受理状态 |
| `POST /api/simulation-results/csv` | 结构化结果 JSON | UTF-8 CSV 附件 |
两个固定算例接口 `simulate-testmodel` 和 `simulate-test-mql` 仍属于测试/基线能力,不能
视为任意拓扑仿真合同。
FastAPI 会生成 `/openapi.json`、`/docs` 和 `/redoc`。Pydantic JSON 请求体能够进入
OpenAPI,但当前多数 JSON 响应仍以 `dict[str, object]` 构造,XML、CSV 和 NDJSON
也使用原始响应类型。因此 OpenAPI 目前不是完整的响应合同;代码、Schema 和合同测试
仍是必要依据。
## 7. 数据命名、单位和错误
- XML 属性和目录 JSON 主要使用 `camelCase`;
- ReactFlow 仿真设置保留现有 `t_start/t_stop/step/max_step` 名称;
- 不允许调用方自行猜测或转换字段命名;
- XML 和求解参数统一使用 SI 基准值;
- 实例 ID 和机器标识必须稳定,显示名称不能代替机器标识。
System XML 校验问题统一包含:
```json
{
"severity": "error",
"layer": "semantic",
"code": "ENDPOINT_PORT_UNKNOWN",
"message": "...",
"path": "...",
"line": 1
}
```
其中 `severity/layer/code/message` 是核心字段,`path/line` 可选。同步 HTTP 错误目前既
可能是 FastAPI 字符串 `detail`,也可能是带 `message/issues/diagnostics` 的结构化
`detail`;流式接口则使用 `event=error` 的 NDJSON 事件。统一错误响应模型尚未实现,
调用方必须同时处理这三种现有形状。
## 8. 版本变更规则
### 8.1 模型和组件库
- 修订版本:修复实现,不改变输入、端口和结果合同;
- 次版本:向后兼容地增加有默认值的参数、结果或能力;
- 主版本:删除或改名端口/参数,或者改变既有物理语义。
修改 `MODEL_TYPE` 视为新模型,不得用原标识承载不兼容合同。
以上版本含义用于分类变更影响;当前 System XML v3 对 `modelVersion` 执行完整字符串
精确匹配。因此即使只是修订或次版本变化,旧 XML 也会被拒绝,不能把“向后兼容”
理解成解析器会自动接受旧版本。
### 8.2 目录和 XML Schema
- 兼容澄清不改变 Schema 版本;
- 新增可选字段前必须验证旧消费者行为;
- 删除字段、改名或改变既有语义必须提高 Schema 版本;
- Schema、解析器、导出器、文档和合同测试必须在同一修改中更新。
### 8.3 HTTP API
当前 HTTP API 未版本化,因此不得在原路径上直接发布破坏性变更。需要破坏现有请求
或响应合同前,应先单独设计 API 版本命名空间、兼容周期和下线规则;该机制不在本文
本次整理范围内。
## 9. 旧版本和迁移边界
当前后端只接受 System XML v3:
- 不按 `schemaVersion` 自动选择 v1/v2 解析器;
- 不提供 v1/v2 到 v3 的自动迁移器;
- 不对不匹配的 `modelVersion` 做自动升级;
- 旧版本值只用于验证“不受支持输入应被拒绝”的边界测试。
ReactFlow 工程 JSON 只接受 `projectSchemaVersion: 1` 的当前结构,每个节点必须保存
目录给出的 `modelVersion`。执行、编译和 XML 导出前会再次核对节点版本;缺失或不匹配
时明确拒绝,不能先补当前默认参数再冒充当前模型。字符串端口、缺失连接 Handle 或
已经删除的兼容标记也不会被猜测、补齐或迁移;以后确有升级需求时再为新的工程版本
单独设计迁移器。
MECMAS21 的 `useFriction`、`strib` 等 AMESim 选项统一使用目录声明的原生编码:
`1` 表示“否/禁用”,`2` 表示“是/启用”。工程 JSON v1、组件目录、System XML v3
和模型构造器不再接受或自动换算旧的 `0/1` 编码,也不再使用
`amesimParameterEncodingVersion` 触发猜测式转换。
## 10. 接口修改完成条件
任何接口合同变更至少应同时完成:
1. 更新唯一事实来源;
2. 根据兼容性决定是否提高对应版本;
3. 更新机器可读 Schema;
4. 更新当前规范和示例;
5. 增加请求、响应、拒绝边界和前后端联调测试;
6. 明确说明未实现的兼容或迁移能力。
+778
View File
@@ -0,0 +1,778 @@
# 组件库分类、发现与读取规范 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 v3 要求每个 `Component` 显式保存 `modelVersion`,并与注册模型
版本完全一致;不一致时拒绝加载,不做静默升级。v3 不另存 `library`,而由全局唯一的
`Component/@type` 定位注册模型。旧模型的自动迁移仍未实现,需要另行提供显式规则。
因此当前“修订/次版本向后兼容”只表示合同设计意图,不表示旧 XML 会被解析器自动
接受;任意模型版本变化都会使旧 XML 的精确版本检查失败。
## 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` 只公开规范化的 `LIBRARY` 清单。
## 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. 不在前端重新定义参数默认值、边界或端口语义。
目录协议提供以下可选的参数编辑器与 AMESim 介质扩展字段:
- 参数的 `editor: "choice"` 表示数值是稳定的离散编码,必须同时提供非空
`options: [{"value": 1, "label": "..."}]`。前端显示标签,但工程、XML 与
求解器仍保存和接收 `value` 数值,不得把标签写入模型数据。
- 参数可通过 `visibleWhen: [{"parameter": "mode", "values": [1, 2]}]`
声明显示条件。多个条件之间按 AND 处理,同一条件的多个 `values` 按 OR
处理;控制参数必须拥有固定 `options`。隐藏参数的既有值必须保留,不能因
界面联动而重置或从工程、XML 中删除。
- 模型可通过可选的 `parameterGroups` 声明纯展示用参数分组。每组包含稳定的
`id`、显示 `label`、组间 `order`、有序参数名数组 `parameters` 和布尔值
`defaultExpanded`;默认应为 `false`。同一参数最多属于一个组,组内参数名
必须引用该模型已注册的参数。分组不改变参数默认值、条件显示、工程保存或
System XML 语义;无分组的模型不输出该字段。
- 参数的 `editor: "amesimGasReference"` 表示该数值不是普通连续量,而是
项目介质定义的 `gi` 引用。前端应保留索引 `0`,并从当前画布的介质定义
组件生成其余下拉项;索引下拉项只显示数值,不拼接介质名称或中文说明。
- 参数的 `editor: "amesimGasPropertyModel"` 表示该参数选择介质定义内部的
物性计算模型。参数同时提供 `options: [{"value": 0, "label": "理想气体"}]`
一类目录数据,前端据此生成下拉栏;后续增加算法时由介质模型注册新的选项,
前端不硬编码算法名称。
- 模型的 `role: "amesimGasMediumDefinition"` 表示该模型是项目级介质定义。
此类模型允许 `ports: []`,在 System XML v3 中仍按普通零端口
`Component` 保存;XML 不写任何 `Port` 快照,只保存模型版本和完整参数。
这些字段在目录对象中均为可选。宽松读取目录的消费者可以把未知编辑器参数
退化为普通数值输入;按本仓库 JSON Schema 严格校验的消费者必须与后端成套
升级,才能识别新增的 `editor` 值和 `options` 字段。正式前端必须依据目录字段
生成控件,不能硬编码具体 AMESim 模型名。
`property_model` 是每种介质组件内部的稳定选项编号,不等同于 AMESim 原始
`eosType`。例如空气组件的 `property_model=0` 表示理想气体,并映射到
`fluidType=2/eosType=1`;氦气组件的 `property_model=0` 表示
Peng-Robinson,并映射到 `fluidType=12/eosType=6`。编译层负责保存这种映射,
前端只使用目录选项。
前端不再维护内置兜底目录。后端目录不可用时,组件区保持为空,并在“组件库”
标题旁显示红色“加载失败”状态;悬停或聚焦该状态可查看失败范围和详细原因。
`experimental` 试验库即使由成功的目录响应返回,也不会出现在组件区。
### 14.1 前端实际读取步骤
React Flow 启动时:
1. 使用 `no-store` 请求 `/api/components/catalog`。
2. 检查 `schemaVersion == 1`。
3. 检查库、模型、端口和参数结构。
4. 检查模型 `type` 是否全局重复。
5. 将参数数组转换为参数面板定义。
6. 按库、分类和模型的 `order` 排序。
7. 过滤仅用于注册验证的 `experimental` 试验库。
8. 成功时用绿色状态显示“已加载 X 个组件库”,不追加其他成功说明。
9. 请求或格式校验失败时显示红色“加载失败”,不显示任何兜底组件;悬停状态可
查看具体库名(目录响应可识别时)或受影响范围、接口地址与错误原因。
### 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"
type="cylinder"
modelVersion="1.0.0">
<Parameter name="volume" value="0.01"/>
</Component>
```
映射规则:
- `id`:工程内唯一的组件实例 ID。
- `type`:必须匹配唯一的 `MODEL_TYPE`。
- `modelVersion`:必须与该模型当前 `MODEL_VERSION` 完全一致。
- `<Parameter name>`:必须完整且只能来自模型的 `PARAMETERS`,数值使用 SI。
- XML v3 不保存 `name/componentType/Port` 或画布布局;连接中的
`Endpoint/@port` 必须存在于模型的 `PORTS`。
介质定义组件不通过物理端口连接。编译器先收集目录角色为
`amesimGasMediumDefinition` 的零端口组件,再解析带
`editor="amesimGasReference"` 参数的组件引用;介质定义组件本身不进入数值
仿真网络。System XML 语义校验会在编译前检查介质索引的整数范围、定义唯一
性、正索引引用完整性,以及同一气动连通分量的引用一致性。
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()` |
| 模型发现 | 已实现 | 按库清单受控发现 |
| 启动校验 | 已实现首版 | 覆盖版本、分类、端口、参数、单位和默认实例 |
| System XML 中的模型版本 | 已实现 | v3 显式保存并严格匹配 `modelVersion` |
| 工程 JSON 的整体版本 | 已实现 | 固定为 `projectSchemaVersion: 1`,节点显式锁定 `modelVersion` |
| 自动版本迁移 | 未实现 | 当前明确拒绝不匹配版本,本阶段不实现迁移 |
| 目录 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 v3 保存并严格校验模型版本;工程 JSON 使用
`projectSchemaVersion: 1`,每个节点保存创建时的 `modelVersion`,当前不实现旧工程迁移。
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
```
@@ -0,0 +1,725 @@
# 组件模型建模规范 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 修改已有公开模型
必须先判断改动是否破坏已有工程:
| 改动 | 版本建议 | 兼容性要求 |
| --- | --- | --- |
| 修复数值实现但不改变契约 | 修订版本 | 若提高 `modelVersion`,既有 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"
PRESSURE_FLOW_DEPENDS_ON_STREAM = False
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()`。
实现 `update_stream_outflows()` 或 `update_flow_temperature_references()` 的公开模型,
还应在该公开类自身显式声明 `PRESSURE_FLOW_DEPENDS_ON_STREAM`:构成压力/流量方程
会读取这些 hook 写入的焓或温度引用时设为 `True`,否则设为 `False`。省略声明、
声明非法值或由自定义子类仅继承父类声明时,求解器会保守使用全网热流闭合;不要
为了获得分块加速而错误声明 `False`。
`pressure_flow_equation_residuals()` 返回的每条 `EquationResidual.variables` 必须完整
列出该残差实际读取的全部代数端口量(`p/m_flow/x/v/f`),不能只写“主要变量”。
求解器会用这份声明编译稀疏 Jacobian 和独立方程块;漏写依赖可能让有限差分方向
不完整。仓库内置组件会接受结构与数值依赖回归,外部自定义组件当前仍保守使用
全网 dense 回退,直到具备同等的依赖验证边界。
### 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 单位 |
| --- | --- |
| `acceleration` | `m/s2` |
| `area` | `m2` |
| `dimensionless` | 空字符串 |
| `density` | `kg/m³` |
| `flow_coefficient` | `kg/(s*Pa^0.5)` |
| `force` | `N` |
| `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` |
| `time` | `s` |
| `translational_damping` | `N/(m/s)` |
| `translational_stiffness` | `N/m` |
| `velocity` | `m/s` |
| `volume` | `m3` |
| `volume_flow` | `m3/s` |
| `windage` | `N/(m/s)^2` |
新增物理量时必须先扩展后端受控单位表,再评估前端是否需要单位换算选项。禁止在
单个模型中私自拼写新的同义 `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` 与方程主要约束的物理角色一致。
- 对零压差、零流量和反向流动给出有限结果。
- 必要正则化必须有物理解释,并通过边界测试保护。
- 不得用画布坐标、连接线方向或组件名称决定方程。
### 12.1 可因果执行的残差语义
后端只会对经过结构门控的内置模型启用完全因果执行。除完整声明
`variables` 外,这些模型还必须遵守以下可执行语义:
- `role="effort", relation="state"` 的残差写成
`端口 effort - 状态给定值`,被约束的端口量系数必须为 `+1`。
- `role="effort", relation="equal"` 的残差写成两个同类 effort 的差。
- `role="flow", relation="sumToZero"` 按“流入组件为正”的约定求和,待消元
flow 的系数必须为 `+1`。
- `role="flow", relation="constitutive"` 写成
`待消元 flow - 本构计算值`,待消元 flow 的系数必须为 `+1`。
- `pressure_flow_equation_values()` 必须是无副作用的只读计算,返回顺序和长度
必须与 `pressure_flow_equation_residuals()` 的编译结果永久一致;不得在求残差时
修改端口、状态或活动集缓存。
求解器仍会在首次闭合、离散事件之后和固定周期执行完整残差审计。结构不满足、
运行时覆盖不完整或审计不通过时,会立即熔断到原有残差/非线性求解路径。现场诊断
时可在启动进程前设置 `SIMULATION_CAUSAL_FAST_PATH=0`,一键关闭该优化而不改变模型
文件。
动态模型还必须:
- 状态向量长度稳定。
- `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"
PRESSURE_FLOW_DEPENDS_ON_STREAM = False
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 能映射到正确模型。
- 最小系统能够编译;声称可仿真的模型必须产生有限结果。
- 针对性测试、完整回归和必要的前端构建通过。
- 文档记录了模型假设、适用范围和已知限制。
+284
View File
@@ -0,0 +1,284 @@
# System XML v3 协议
System XML v3 是 SystemSimulationApp 当前唯一的 XML 求解输入格式。它只描述可执行模型,不再承担 ReactFlow 画布存档职责。
机器可读结构见 [`schemas/system-simulation-v3.xsd`](../../schemas/system-simulation-v3.xsd)。当前校验、解析、编译和仿真接口固定按 v3 处理,不会根据 `schemaVersion` 自动切换到 v1 或 v2。
## 1. 设计边界
v3 遵循一条简单规则:
> XML 保存“求解什么”,工程 JSON 保存“怎样编辑和显示”。
因此 XML 保留:
- 仿真起止时间、结果采样间隔、内部最大步长和积分方法;
- 组件实例 ID、后端模型类型、模型版本和完整 SI 参数;
- 每条连接的两个端点。
XML 不保存:
- 组件显示名称、画布坐标、旋转、镜像;
- 图标、端口显示侧和显示顺序;
- 端口的 `kind/domain/nominalRole/positiveFlowDirection/variables` 快照;
- 参数表达式、显示单位、科学记数法偏好;
- ReactFlow 的选择状态、撤销历史或仿真结果。
这些信息中,编辑器状态留在工程 JSON;端口物理合同由 `Component.type + Endpoint.port` 从后端组件注册表恢复。
## 2. 完整结构示例
下面的例子包含一条信号连接和一条机械连接,展示 v3 的全部结构元素:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<System name="signal-force-demo" schemaVersion="3" unitSystem="SI">
<Simulation
tStart="0"
tStop="1"
sampleStep="0.01"
maxStep="0.001"
method="BDF"/>
<Components>
<Component id="step_1" type="amesim_step0" modelVersion="0.1.0">
<Parameter name="initial" value="0"/>
<Parameter name="final" value="0"/>
<Parameter name="time" value="0.5"/>
</Component>
<Component id="force_1" type="amesim_forc" modelVersion="0.2.0">
<Parameter name="direction" value="1"/>
</Component>
<Component id="zero_1" type="amesim_f000" modelVersion="0.1.0"/>
</Components>
<Connections>
<Connection id="signal-1">
<Endpoint component="step_1" port="out"/>
<Endpoint component="force_1" port="res"/>
</Connection>
<Connection id="mechanical-1">
<Endpoint component="force_1" port="port_2"/>
<Endpoint component="zero_1" port="port_1"/>
</Connection>
</Connections>
</System>
```
XML 的外形是一棵树,模型本身仍是一张连接图。组件平铺在 `Components` 中,`Connections` 再用 `(component, port)` 地址建立拓扑。
固定骨架为:
```text
System
├─ Simulation
├─ Components
│ └─ Component *
│ └─ Parameter *
└─ Connections
└─ Connection *
├─ Endpoint
└─ Endpoint
```
顶层顺序固定为 `Simulation → Components → Connections`。每条 `Connection` 恰好包含两个 `Endpoint`。
## 3. `System` 根元素
| 属性 | 是否必填 | 规则 |
| --- | --- | --- |
| `schemaVersion` | 是 | 固定为 `3` |
| `unitSystem` | 是 | 固定为 `SI` |
| `name` | 否 | 非空工程名称;省略后解析模型使用 `untitled` |
`schemaVersion="3"` 已经表示唯一的介质引用和 AMESim 离散参数编码语义。根元素不接受额外的版本提示字段,也不会据此触发兼容猜测。
## 4. `Simulation`
| 属性 | 含义 | 主要校验 |
| --- | --- | --- |
| `tStart` | 仿真开始时刻 | 必须是有限数值 |
| `tStop` | 仿真结束时刻 | 必须有限且大于 `tStart` |
| `sampleStep` | 结果相邻采样点的时间间隔 | 必须大于 0,且整个区间最多生成 10001 个采样点 |
| `maxStep` | 自适应积分器单个内部步的上限 | 必须大于 0 |
| `method` | 积分方法 | `RK45/RK23/DOP853/Radau/BDF/LSODA` |
`sampleStep` 和 `maxStep` 不是一回事:
- `sampleStep` 决定结果曲线多久保存一个点;
- `maxStep` 限制求解器内部一次最多前进多久;
- 自适应求解器可以因为误差、事件或试探状态失败而走得比 `maxStep` 更短。
采样点数量会在创建时间数组前计算。若区间长度不可表示为有限数、请求超过 10001
点,或在当前浮点精度下无法得到包含 `tStart/tStop` 的严格递增时间序列,输入会在
仿真前被拒绝,不会把超大或重复的 `t_eval` 交给积分器。
工程 JSON 为兼容现有前端仍把采样字段命名为 `simulation.step`;导出 v3 时必须映射为 `Simulation/@sampleStep`。
## 5. `Component` 与 `Parameter`
### 5.1 `Component`
| 属性 | 是否必填 | 含义 |
| --- | --- | --- |
| `id` | 是 | 当前系统内唯一的实例 ID;连接通过它引用组件 |
| `type` | 是 | 后端组件注册键,例如 `amesim_forc` |
| `modelVersion` | 是 | 该 `type` 的模型合同版本 |
语义校验要求 `modelVersion` 与当前注册表完全一致。版本不匹配时返回 `COMPONENT_MODEL_VERSION_MISMATCH`,不会静默套用新模型默认值或自动改写旧参数。
v3 不保存 `name/componentType/x/y/rotation/mirrored`。其中:
- 显示名称和画布位置只属于工程 JSON;
- `componentType` 在当前系统中与后端 `type` 重复;
- 旋转和镜像只属于画面布局,不再改变求解方程。
### 5.2 `Parameter`
```xml
<Parameter name="direction" value="-1"/>
```
每个参数只保存稳定参数名和已经换算到 SI 基准单位的数值。v3 要求组件显式列出当前模型注册表中的全部参数:
- 参数名重复会报错;
- 缺少注册参数会报 `PARAMETER_REQUIRED_MISSING`;
- 出现未知参数会报 `PARAMETER_UNSUPPORTED`;
- 超范围或不在枚举集合内会报 `PARAMETER_VALUE_INVALID`。
前端和后端导出器会先用注册默认值补齐工程 JSON 中省略的参数,再写入 XML。XML 解析器本身不替缺失参数猜默认值。
### 5.3 AMESim 介质引用
介质仍用普通参数表达,不增加额外 XML 层级:
- `gi=0`:内置理想空气;
- 对普通气动组件,`gi=1..99` 引用同一 XML 中显式介质定义组件的索引;
- 对介质定义组件自身,`gi=1..99` 表示它所定义的索引;
- 介质定义的 `gi` 必须唯一;
- 一个连通气动网络只能使用同一 `gi`;
- `property_model` 也是普通必填参数,由对应介质模型注册表解释。
## 6. 端口与连接
### 6.1 为什么 v3 没有 `Port` 元素
端口不是组件实例的自由数据,而是组件模型合同的一部分。后端根据:
```text
Component.type + Endpoint.port
```
从注册表恢复:
- `kind`:物理或信号;
- `domain`:气动、机械或信号;
- `nominalRole`:输入、输出或物理名义角色;
- `positiveFlowDirection`:物理流变量统一以进入组件为正;
- 端口变量、单位和 `equal/sumToZero/streamMix/directed` 连接规则。
XML 不能通过写一个新端口名来扩展组件,也不能通过修改字符串把气动口变成机械口。
### 6.2 `Connection`
`Connection/@id` 可省略。省略时解析器按文档顺序生成 `connection_1`、`connection_2` 等内部 ID;显式 ID 和生成 ID 都必须唯一。
连接本身不再保存 `kind` 或 `domain`。语义层解析两个端点的注册端口后检查:
- 组件和端口存在;
- 两端同为物理端口或同为信号端口;
- 两端 `domain` 和完整变量合同一致;
- 信号连接恰好连接一个 `output` 和一个 `input`;
- 一个信号输出可以驱动多个输入,但每个信号输入只能有一个驱动;
- 同一物理端口只使用一次;分支必须使用显式 Tee/节点组件;
- 不允许自连接或重复端点对。
### 6.3 两个 `Endpoint` 的顺序
物理连接的两个端点无序,交换顺序不改变方程或实际流向。
信号连接也不写 `role="source"` 或 `role="target"`。发送方和接收方由注册端口的 `output/input` 合同确定,而不是由 XML 中的先后顺序决定。导出器可以为了便于阅读把输出端写在前面,但求解器不能依赖这一顺序。
## 7. `AmesimForc.direction`
`amesim_forc` 从模型版本 `0.2.0` 开始使用显式物理参数:
```xml
<Parameter name="direction" value="1"/>
```
它只允许:
- `+1`:默认方向,方程为 `port_2.f + inputForce = 0`;
- `-1`:反向,方程为 `port_2.f - inputForce = 0`。
组件的图标旋转和镜像不会改变该参数,也不会改变求解结果。用户要反转施力方向时必须修改 `direction`,而不是旋转图标。
### 7.1 MECMAS21 离散选项编码
`amesim_mecmas21` 只使用 AMESim 原生的 `1/2` 编码。以两个布尔选项为例:
| 参数 | `1` | `2` |
| --- | --- | --- |
| `useFriction` | 不启用摩擦 | 启用摩擦 |
| `strib` | 不使用 Stribeck 效应 | 使用 Stribeck 效应 |
工程 JSON v1 和 System XML v3 都直接保存上述值。后端不会把 `0/1` 自动换算成
`1/2`,也不会根据缺失的兼容标记猜测工程含义;不在目录选项集合内的值会被拒绝。
## 8. 工程 JSON 与 System XML 的分工
| 信息 | 工程 JSON | System XML v3 |
| --- | --- | --- |
| 组件实例 ID、模型类型 | 保存 | 保存 |
| 模型版本 | 每个节点显式保存并与目录核对 | 每个组件显式保存 |
| 数值参数 | 保存编辑值及显示信息 | 保存完整 SI 数值 |
| 组件显示名、坐标、旋转、镜像 | 保存 | 不保存 |
| 端口快照、`side/order` | 保存供编辑器使用 | 不保存 |
| ReactFlow `source/target/handle` | 保存 | 转成两个 `Endpoint` |
| 端口物理合同 | 目录快照用于前端检查 | 不重复保存,由注册表恢复 |
| 参数表达式、显示单位 | 保存 | 不保存 |
| 仿真结果 | 不作为模型输入 | 不保存 |
因此 `/api/system-xml/parse` 返回的是规范化执行模型,不是可无损恢复原画布的 ReactFlow 工程文件。需要继续编辑时,应保存和打开工程 JSON;需要校验、交换或求解时,使用 System XML v3。
## 9. 校验与 API
校验固定分三层:
| 层级 | 负责内容 |
| --- | --- |
| XML | 5 MiB 大小限制、语法、安全解析、禁止 DTD/实体和网络访问 |
| XSD | 元素顺序、必填属性、数量、基础数值类型、`schemaVersion=3`、`unitSystem=SI` |
| semantic | 模型及版本、完整参数、端点引用、注册端口兼容性、介质引用和拓扑占用 |
当前相关接口都直接接收 `Content-Type: application/xml` 的原始 v3 XML:
- `POST /api/system-xml/validate`:返回三层校验报告;
- `POST /api/system-xml/parse`:返回规范化执行模型;
- `POST /api/system-xml/compile-model`:返回编译后的网络和仿真设置;
- `POST /api/system-xml/simulate`:同步运行并返回完整结果;
- `POST /api/system-xml/simulate-stream`:通过 NDJSON 返回心跳、进度和最终结果。
`POST /api/reactflow/system-xml` 可将工程 JSON 导出为 v3;当前前端也能在浏览器中直接生成同一结构。
通过 XML/XSD/语义校验只说明输入合同正确。完整仿真前仍会检查动态储能锚点、未连接物理端口、方程结构和不允许的理想储能直连等可求解条件。物理连通岛只由物理组件和物理连接构成;控制信号扇出不会把两个独立气路或机械网络合并成一个物理岛。
## 10. 旧版本处理边界
当前 API 不读取或自动转换 System XML v1/v2,也不会为不匹配的
`Component/@modelVersion` 选择旧模型实现。旧输入会在 XSD 或语义层被明确拒绝。
本阶段不定义转换步骤、迁移注册表或兼容承诺。仓库不再保存旧版 XSD、规范或示例;
需要进入当前系统的模型必须由来源端重新导出为 v3,不能只修改版本号。
## 11. 事实来源
- XSD:`schemas/system-simulation-v3.xsd`
- XML 数据类、解析和三层校验:`app/system_xml.py`
- 后端 JSON→XML 导出及 XML 仿真路由:`app/main.py`
- 前端 XML 生成:`frontend/src/App.tsx` 中的 `buildSystemXml()`
- 组件和端口事实来源:`app/simulation/registry.py`、`app/simulation/core/ports.py`
- 网络最终兼容检查:`app/simulation/systems/network.py`