642 lines
37 KiB
Markdown
642 lines
37 KiB
Markdown
# SystemSimulationApp 接口类型与表示方式总结(通俗版)
|
||
|
||
> 调研基线:2026-08-12(System XML v3 接口基线)。本文依据当前仓库的代码、Schema、说明文档和测试编写。
|
||
> 这里的“接口”主要指组件上的端口(port/connector),不是只指 HTTP API。文末也单独列出了相关 HTTP API。
|
||
|
||
## 0. 三分钟读懂
|
||
|
||
先把整个系统想成一张“可以计算的工程图”:
|
||
|
||
- **组件**像气瓶、管路、阀门、质量块等设备;
|
||
- **端口**像设备上的接头或插座;
|
||
- **连接**像气管、机械连接杆或控制线;
|
||
- **编译**像正式计算前的接线检查:插头是否匹配、有没有漏接、方向是否正确;
|
||
- **求解**才是真正计算每个时刻的压力、流量、位移、速度等数值。
|
||
|
||
当前项目实际只有三类端口:
|
||
|
||
| 看到的类型 | 可以把它理解成 | 主要传递什么 |
|
||
| --- | --- | --- |
|
||
| `physical / pneumatic` | 气路接头 | 压力、质量流量、气体携带的能量 |
|
||
| `physical / mechanical` | 机械连接点 | 位移、速度、力 |
|
||
| `signal / signal` | 控制线 | 一个有方向的数值,例如阀门开度或目标力 |
|
||
|
||
最容易混淆的四种文件/数据,可以这样记:
|
||
|
||
| 数据 | 通俗比喻 | 它回答的问题 |
|
||
| --- | --- | --- |
|
||
| 组件目录 JSON | 产品说明书 | 某种组件天生有哪些端口、每个端口有哪些变量? |
|
||
| 工程 JSON | 画布存档 | 这张图上放了哪些组件、摆在哪里、怎样连? |
|
||
| System XML v3 | 交给后端的精简求解清单 | 只带组件、模型版本、SI 参数、连接和仿真设置,不负责保存画布 |
|
||
| 编译结果 JSON | 接线检查报告 | 后端恢复完整模型后,最终认出了哪些端口、连接和方程结构? |
|
||
|
||
贯穿全文的两个例子:
|
||
|
||
```text
|
||
案例 A:气路
|
||
|
||
储气容器/气室 A ── 节流孔或管路 ── 气室 B
|
||
气动端口 气动端口
|
||
|
||
案例 B:控制
|
||
|
||
阶跃信号源 step_1.out ──控制线──> 阀 valve_1.res
|
||
│
|
||
控制气路通断/开度
|
||
|
||
也可以是:step_1.out ──控制线──> 力源 force_1.res ──机械端口── 质量块
|
||
```
|
||
|
||
这两个示意图不是凭空编造的:仓库里已有对应的回归案例。`tests/test_generic_system_xml_simulation.py:86-145` 搭建了 `cylinder(500 kPa) → orifice → pipe → tank(100 kPa)` 气路;`tests/test_amesim_pnvo001_signal_xml.py:37-84` 搭建了 `step_1.out → valve_1.res`,同时让气缸、阀和气罐通过物理端口相连。
|
||
|
||
先记住六点就能继续阅读:
|
||
|
||
1. **物理端口必须同类相连。** 气动只能接气动,机械只能接机械,不能把“气管”插到“机械接头”上。
|
||
2. **信号线有方向。** 必须连接一个注册为 `output` 的端口和一个注册为 `input` 的端口;XML v3 不再另写 `source/target` 角色。
|
||
3. **物理线没有 source/target 的物理含义。** 画布虽然要写 `source/target`,后端会把它当作无方向的两个端点。
|
||
4. **流变量统一以“进入当前组件”为正。** 因此同一条气路两端的质量流量数值互为相反数。
|
||
5. **工程 JSON 和 XML 不重复保存完整变量表。** 后端依靠组件的 `modelType/type` 去注册表找回完整定义。
|
||
6. **`side/rotation/mirrored` 都只负责画面。** 力源是否反向由显式参数 `direction=+1/-1` 决定;转动或镜像图标不再改变方程。
|
||
|
||
只想看懂工程图,可以读第 0、2、3、5、6 节;需要开发或排查兼容问题时,再读第 1、4、7~11 节。
|
||
|
||
## 1. 本文中的标签和“谁说了算”
|
||
|
||
为了避免把“已经能运行”和“文档希望如此”混为一谈,本文使用四种标签:
|
||
|
||
- **[已实现]**:当前代码或测试直接体现的行为。
|
||
- **[约定]**:Schema、类型声明或说明文档规定的合同,但不一定所有入口都完整实现。
|
||
- **[推断]**:根据多处代码可以合理得到的判断,仓库没有直接承诺或实测数据。
|
||
- **[发现]**:代码层之间不一致、容易误解或存在兼容风险的地方。
|
||
|
||
如果不同层的说法不一致,优先相信更靠上的事实来源:
|
||
|
||
| 优先级 | 事实来源 | 关键文件/符号 | 通俗解释 |
|
||
| --- | --- | --- | --- |
|
||
| 1 | 端口核心定义 | `app/simulation/core/ports.py:7-207`:`PortVariableDefinition`、`PortDefinition`、`PortState` | 定义“插头标准”和运行时数值 |
|
||
| 2 | 具体组件模型类 | `MODEL_TYPE`、`PORTS`、`DISPLAY` | 声明某个产品实际装了哪些插头 |
|
||
| 3 | 模型注册器 | `app/simulation/registry.py:41-170, 416-490, 825-980` | 启动时核对产品声明,并生成目录 |
|
||
| 4 | 网络连接层 | `app/simulation/systems/network.py:83-150`:`SimulationNetwork.connect()` | 真正接线时做最终兼容检查 |
|
||
| 5 | JSON/XML | `ReactFlowPortDefinition`、System XML v3 XSD | JSON 搬运画布和端口显示快照;XML 只搬运可执行模型,端口合同由注册表恢复 |
|
||
|
||
**[约定]** `docs/README.md:28-29` 和组件建模规范都说明:组件模型类及受控库清单是后端事实来源。XML 或前端不能凭空创造一个模型没有声明的端口。
|
||
|
||
## 2. 常见术语翻译表
|
||
|
||
第一次阅读时,可以先把英文术语替换成右侧的日常说法。
|
||
|
||
| 术语 | 通俗说法 | 在本项目中的具体意思 |
|
||
| --- | --- | --- |
|
||
| component | 设备/元件 | 气室、节流孔、阀、质量块、信号源等 |
|
||
| port / connector | 接头/插座 | 组件可以与外界连接的位置 |
|
||
| interface contract | 接口说明书 | 端口名称、类型、变量、单位和连接规则的完整定义 |
|
||
| `kind` | 大类 | `physical` 物理连接,或 `signal` 控制信号 |
|
||
| `domain` | 专业类别 | 当前为 `pneumatic` 气动、`mechanical` 机械、`signal` 信号 |
|
||
| `nominalRole` | 名义用途 | 物理端口的入口/出口提示,或信号端口的输入/输出方向 |
|
||
| `effort` | 两端要相同的“势” | 气动压力 `p`;机械位移 `x`、速度 `v` |
|
||
| `flow` | 连接处要守恒的“流” | 气动质量流量 `m_flow`;机械力 `f` |
|
||
| `stream` | 随介质流动携带的性质 | 当前是气体流出比焓 `h_outflow` |
|
||
| `equal` | 两端相等 | 例如连接后 `p_A = p_B` |
|
||
| `sumToZero` | 两端相加为零 | 例如 `m_flow_A + m_flow_B = 0` |
|
||
| `streamMix` | 按实际流向传播/混合 | 不能简单令两端 `h_outflow` 相等 |
|
||
| `directed` | 按指定方向传值 | 例如阶跃源输出写入阀的信号输入 |
|
||
| registry / catalog | 型号登记表/产品目录 | 后端支持哪些模型,以及每种模型的完整定义 |
|
||
| compile | 接线检查和模型装配 | 根据 `modelType` 实例化组件并检查所有连接 |
|
||
| resolver | 专项计算器 | 分别处理信号、气体焓、移动容积等传播问题 |
|
||
| Schema / XSD | 格式规则 | 检查 JSON/XML 的字段和结构是否合规 |
|
||
| SI | 国际单位制 | Pa、kg/s、m、N 等;提交给求解器的值使用 SI 基准值 |
|
||
|
||
## 3. 用案例理解 physical 和 signal
|
||
|
||
### 3.1 案例 A:气室经节流孔连接
|
||
|
||
假设储气容器 A 的压力高于气室 B:
|
||
|
||
```text
|
||
tank_1.port_a ── orifice_1.port_a [节流孔] orifice_1.port_b ── chamber_1.port_1
|
||
```
|
||
|
||
仓库中的真实回归测试使用了一条更完整的链路:`cylinder(500 kPa) → orifice → pipe → tank(100 kPa)`(`tests/test_generic_system_xml_simulation.py:86-145`)。500 kPa 与 100 kPa 提供明显压差,便于检查压力和质量流量是否按预期推进。下面仍用 A、B 表示任意一对相连端口,规则与该测试相同。
|
||
|
||
这些都是 `physical / pneumatic` 端口。连接后,求解器关心三件主要事情:
|
||
|
||
1. 接头处的压力要相容;
|
||
2. 从一个组件流出的质量,必须流入另一个组件;
|
||
3. 气体携带的能量要按实际流向传递,发生汇合时还要混合。
|
||
|
||
这也是 `p`、`m_flow`、`h_outflow` 三个变量的来历:
|
||
|
||
| 变量 | 单位 | 人话解释 | 接线后的处理方式 |
|
||
| --- | --- | --- | --- |
|
||
| `p` | Pa | 接头处的绝对压力 | 两端相等:`p_A - p_B = 0` |
|
||
| `m_flow` | kg/s | 每秒有多少质量的气体流过 | 两端守恒:`m_flow_A + m_flow_B = 0` |
|
||
| `h_outflow` | J/kg | 如果气体从该组件流出,每公斤带走多少能量 | 按实际流向传播/混合,不直接令两端相等 |
|
||
| `volume` | m³ | 相邻移动机构提供的外部容积 | 内部辅助量,结果默认不展示 |
|
||
| `volume_flow` | m³/s | 上述外部容积每秒变化多少 | 内部辅助量,结果默认不展示 |
|
||
|
||
为什么两端的 `m_flow` 一正一负?项目统一规定“**进入当前组件为正**”。如果 0.01 kg/s 从 A 流进 B,那么从 A 的视角它在流出,约为 `-0.01`;从 B 的视角它在流入,约为 `+0.01`。这不是矛盾,只是观察对象不同。
|
||
|
||
`inlet`、`outlet`、`bidirectional` 是设计上的名义角色,不是止回阀。即使一个端口名义上叫 `outlet`,求解过程中仍可能出现反向流动。`PortState.actual_direction()` 使用约 `1e-12` 的死区判断 `in/out/stagnant`(`app/simulation/core/ports.py:218-231`)。
|
||
|
||
**[已实现]** `volume` 和 `volume_flow` 虽然使用 `signal/directed` 的变量规则,但它们仍装在气动物理端口里,不是画布上另一根信号线。`PneumaticVolumeResolver` 会沿现有气路传播它们(`app/simulation/solvers/pneumatic_volume.py:21-93`)。
|
||
|
||
### 3.2 案例 B:阶跃信号控制阀或力源
|
||
|
||
控制线与气管不同,它只把一个数值从发送方交给接收方:
|
||
|
||
```text
|
||
amesim_step0.out ───────────────> amesim_pnvo001.res
|
||
信号输出 output(发送方) 阀的信号输入 input(接收方)
|
||
```
|
||
|
||
当阶跃源在某个时刻从 0 跳到 1,`SignalResolver.solve()` 先更新信号源,再把输出值写到阀的 `res` 输入(`app/simulation/solvers/signal.py:40-68`)。阶跃发生的时刻还能作为积分断点,避免数值积分跨过突变点而不知情。
|
||
|
||
`tests/test_amesim_pnvo001_signal_xml.py:37-84` 正好演示了这个分工:`step_1.out → valve_1.res` 只传阀的控制命令,而 `cylinder → valve → tank` 的另外几条连接才传递气体的压力、质量流量和焓。控制线不会“变成气管”,阀组件负责在内部用命令改变气路行为。
|
||
|
||
同样的信号也能驱动力源:
|
||
|
||
```text
|
||
amesim_step0.out ──> amesim_forc.res [力源] amesim_forc.port_2 ── 机械网络
|
||
```
|
||
|
||
这里 `res` 是信号输入,`port_2` 是机械端口。信号与机械并没有直接相连,而是由 `amesim_forc` 组件内部方程把输入数值转换成力。
|
||
|
||
### 3.3 机械端口:把连接点当成同一个运动点
|
||
|
||
`physical / mechanical` 是一维平动机械连接,主要变量为:
|
||
|
||
| 变量 | 单位 | 人话解释 | 接线后的规则 |
|
||
| --- | --- | --- | --- |
|
||
| `x` | m | 连接点的位置 | 两端位移相等 |
|
||
| `v` | m/s | 连接点的速度 | 两端速度相等 |
|
||
| `f` | N | 组件在连接点承受的力 | 两端力相加为零 |
|
||
|
||
机械端口当前都标为 `bidirectional`。`MechanicalStateReducer` 会把刚性连接的一组惯性元件整理为一个共享的 `[v, x]` 状态坐标,避免同一运动被重复积分(`app/simulation/solvers/mechanical.py:225-248, 354-427`)。
|
||
|
||
### 3.4 跨域组件不是“不同插头直接相连”
|
||
|
||
当前有三种典型跨域组件:
|
||
|
||
- `amesim_forc`:信号输入 + 机械端口;
|
||
- `amesim_pnvo001`:信号输入 + 两个气动端口;
|
||
- `amesim_pnrp17`:一个气动端口 + 四个机械端口。
|
||
|
||
不同域之间的转换发生在组件内部方程中。网络层仍禁止把气动端口直接接到机械端口或信号端口。
|
||
|
||
## 4. 端口定义究竟包含什么
|
||
|
||
可以把 `PortDefinition` 看成端口铭牌。稳定定义在 `app/simulation/core/ports.py:38-47`:
|
||
|
||
| 字段 | 例子 | 通俗含义 |
|
||
| --- | --- | --- |
|
||
| `name` | `port_a`、`res` | 组件内部唯一的端口编号 |
|
||
| `kind` | `physical` / `signal` | 是物理接头还是控制线插座 |
|
||
| `domain` | `pneumatic` / `mechanical` / `signal` | 具体属于哪个专业类别 |
|
||
| `nominal_role` | `inlet`、`output` 等 | 名义用途;只有信号的 input/output 决定传播方向 |
|
||
| `positive_flow_direction` | `intoComponent` | 流和力的正号统一指向组件内部 |
|
||
| `variables` | `p`、`m_flow` 等 | 端口真正携带的变量、单位和连接规则 |
|
||
|
||
`side` 和 `order` 来自显示定义 `ComponentDisplaySpec.ports`,不是物理合同:
|
||
|
||
- `side`:端口图标画在节点左、右、上还是下;
|
||
- `order`:多个端口的显示顺序。
|
||
|
||
它们由 `ComponentModelSpec.as_catalog_dict()` 合并进目录响应(`app/simulation/registry.py:76-110`)。求解器不读取 `side`。
|
||
|
||
运行时的 `PortState` 是一只通用“数值盒子”,同时预留气动、机械和信号字段(`app/simulation/core/ports.py:194-207`)。不能因为盒子里有某个字段,就认定所有端口都支持该变量;真正要看的是 `PortDefinition.variables`。
|
||
|
||
## 5. JSON 和 XML 分别保存什么
|
||
|
||
本节继续用“储气容器连接节流孔”说明同一件事如何经过四层表示。
|
||
|
||
### 5.1 组件目录 JSON:产品说明书
|
||
|
||
`GET /api/components/catalog` 返回后端支持的全部型号(`app/main.py:283-287`、`app/simulation/registry.py:1002-1027`)。它受 `schemas/component-catalog-v1.schema.json` 约束,信息最完整。
|
||
|
||
下面是为了讲解而加了注释的 **JSONC**,不是可直接提交的严格 JSON:
|
||
|
||
```jsonc
|
||
{
|
||
"name": "port_a", // 端口编号
|
||
"kind": "physical", // 物理接头,不是控制线
|
||
"domain": "pneumatic", // 气动类别
|
||
"nominalRole": "bidirectional", // 设计上允许双向使用
|
||
"positiveFlowDirection": "intoComponent", // 正流量指向组件内部
|
||
"variables": [ // 完整变量表只在目录/编译结果中出现
|
||
{
|
||
"name": "p", // 压力
|
||
"role": "effort", // 连接后两端相等
|
||
"connectionRule": "equal",
|
||
"unit": "Pa",
|
||
"resultVisible": true
|
||
},
|
||
{
|
||
"name": "m_flow", // 质量流量
|
||
"role": "flow", // 连接后两端相加为零
|
||
"connectionRule": "sumToZero",
|
||
"unit": "kg/s",
|
||
"resultVisible": true
|
||
},
|
||
{
|
||
"name": "h_outflow", // 流出气体的比焓
|
||
"role": "stream", // 按流向传播/混合
|
||
"connectionRule": "streamMix",
|
||
"unit": "J/kg",
|
||
"resultVisible": true
|
||
}
|
||
],
|
||
"side": "left", // 只影响画面位置
|
||
"order": 10 // 只影响显示顺序
|
||
}
|
||
```
|
||
|
||
**[已实现]** 当前目录加载结果为 **27 个模型、61 个已声明端口**:38 个气动端口、19 个机械端口、4 个信号端口。两个介质定义模型没有端口,因此不计入 61 个端口。
|
||
|
||
### 5.2 ReactFlow 工程 JSON:画布存档
|
||
|
||
工程 JSON 保存“这次用了哪一个型号、端口快照和连线”,但不重复保存完整变量表。请求模型在 `app/main.py:86-155`;前端定义与生成逻辑见 `frontend/src/App.tsx` 中的 `PortDefinition`、`ReactFlowProjectPayload`、`buildProjectPayload()`。
|
||
|
||
下面仍是带说明的 JSONC:
|
||
|
||
为避免示例过长,这里只截取“储气容器接到节流孔”的局部画布;节流孔另一端尚未接出,所以它是**字段讲解片段**,不是可直接运行的完整工程。可运行的完整链路见 `tests/test_generic_system_xml_simulation.py:86-145`。
|
||
|
||
```jsonc
|
||
{
|
||
"projectSchemaVersion": 1, // 当前工程 JSON 的唯一格式版本
|
||
"name": "tank-orifice-demo",
|
||
"nodes": [
|
||
{
|
||
"id": "tank_1", // 本张图中的实例 ID
|
||
"type": "simulationComponent",
|
||
"position": {"x": 120, "y": 80}, // 画布位置
|
||
"data": {
|
||
"label": "储气容器 A",
|
||
"componentType": "tank",
|
||
"modelType": "tank", // 后端靠它回查完整模型定义
|
||
"modelVersion": "1.0.0", // 锁定保存时使用的模型合同
|
||
"ports": [
|
||
{
|
||
"name": "port_a",
|
||
"kind": "physical",
|
||
"domain": "pneumatic",
|
||
"nominalRole": "bidirectional",
|
||
"positiveFlowDirection": "intoComponent",
|
||
"side": "right"
|
||
}
|
||
],
|
||
"parameters": {} // 当前组件实例的参数值
|
||
}
|
||
},
|
||
{
|
||
"id": "orifice_1",
|
||
"type": "simulationComponent",
|
||
"position": {"x": 360, "y": 80},
|
||
"data": {
|
||
"label": "节流孔",
|
||
"componentType": "orifice",
|
||
"modelType": "orifice",
|
||
"modelVersion": "1.0.0",
|
||
"ports": [
|
||
{"name": "port_a", "kind": "physical", "domain": "pneumatic",
|
||
"nominalRole": "bidirectional", "positiveFlowDirection": "intoComponent", "side": "left"},
|
||
{"name": "port_b", "kind": "physical", "domain": "pneumatic",
|
||
"nominalRole": "bidirectional", "positiveFlowDirection": "intoComponent", "side": "right"}
|
||
],
|
||
"parameters": {}
|
||
}
|
||
}
|
||
],
|
||
"edges": [
|
||
{
|
||
"id": "edge-1",
|
||
"source": "tank_1", // ReactFlow 画线需要 source/target
|
||
"sourceHandle": "port_a",
|
||
"target": "orifice_1",
|
||
"targetHandle": "port_a", // 对物理线而言,不代表流动方向
|
||
"data": {"isContactEdge": false}
|
||
}
|
||
],
|
||
"simulation": {
|
||
"t_start": 0,
|
||
"t_stop": 2,
|
||
"step": 0.1,
|
||
"max_step": 0.005,
|
||
"method": "BDF"
|
||
}
|
||
}
|
||
```
|
||
|
||
后端会按 `modelType` 创建真实模型,并先要求节点 `modelVersion` 与注册版本完全一致,
|
||
再检查这里的端口名、类型、域、名义角色和正号是否与注册定义一致。
|
||
|
||
**[已实现] 前端工程和后端求解模型有意保持分工。**
|
||
|
||
- 目录 JSON 有完整 `variables`,前端只读取完成画布连接和即时提示所需的端口级字段;后端编译仍是物理合同的最终检查点。
|
||
- 工程 JSON 使用必填 `projectSchemaVersion: 1`,每个节点保存 `modelVersion`,端口必须是结构化对象,连接必须写明两端 Handle。结构版本不受支持时直接拒绝;节点模型版本缺失或不匹配时不得编译、导出 XML 或仿真。
|
||
- UI 继续通过浏览器 `localStorage` 和本地 JSON 文件保存工程。后端同时提供严格按工程 JSON v1 校验的工程列表、保存和读取接口;仿真主路径仍只向后端提交精简的 System XML v3。
|
||
|
||
### 5.3 System XML v3:交给后端的精简求解清单
|
||
|
||
XML v3 和工程 JSON 不再追求“保存同一份完整工程”。两者分工很明确:工程 JSON 保存怎样编辑和显示,XML v3 保存后端求解什么。当前结构由 `schemas/system-simulation-v3.xsd` 定义;完整规范见 `docs/system-xml-v3.md`。
|
||
|
||
#### 5.3.1 生成出来的 XML 是什么结构
|
||
|
||
```text
|
||
System 整个可执行模型
|
||
├─ Simulation 恰好 1 个:仿真时间和算法
|
||
├─ Components 组件清单
|
||
│ └─ Component * 0~多个组件
|
||
│ └─ Parameter * 组件的完整 SI 参数
|
||
└─ Connections 接线清单
|
||
└─ Connection * 0~多条连接
|
||
├─ Endpoint 每条连接恰好两个端点
|
||
└─ Endpoint
|
||
```
|
||
|
||
XML 外形是一棵树,模型仍是一张连接图。组件平铺在 `<Components>` 中,`<Connections>` 再通过“组件 `id` + 注册端口名”把它们接起来。顶层顺序固定为 `Simulation → Components → Connections`。
|
||
|
||
与 v2 相比,v3 主动删掉了 `Port` 快照和画布字段。各字段来源如下:
|
||
|
||
| XML v3 内容 | 来源 | 通俗解释 |
|
||
| --- | --- | --- |
|
||
| `System/@name` | `project.name` | 可选的模型名称 |
|
||
| `schemaVersion/unitSystem` | 生成器固定写入 | 当前协议固定为 v3,参数使用 SI |
|
||
| `Simulation` | 求值后的仿真设置 | 起止时间、结果采样间隔、内部最大步长和算法 |
|
||
| `Component/@id` | `node.id` | 连接实际引用的稳定实例编号 |
|
||
| `Component/@type` | `modelType` | 用哪个后端模型类创建实例 |
|
||
| `Component/@modelVersion` | 组件目录/注册表 | 锁定本文件采用的模型合同版本 |
|
||
| `Parameter` | 参数表达式求值并换算后的值 | 写出该模型的全部注册参数,只留最终 SI 数值 |
|
||
| `Endpoint` | ReactFlow 边两端的 handle | 用“组件 ID + 端口名”重新接线 |
|
||
|
||
实际生成过程可以概括为:
|
||
|
||
1. 收集当前节点、连线和仿真设置;
|
||
2. 把工程 JSON 的 `simulation.step` 映射成 XML 的 `sampleStep`;
|
||
3. 根据组件目录写入 `modelVersion`,并用注册默认值补齐全部参数;
|
||
4. 把参数表达式求值、换算为 SI,只写 `Component/Parameter`;
|
||
5. 每条边只写两个 `Endpoint`,不复制端口类型或方向角色。
|
||
|
||
XML v3 不保存显示名称、`symbol`、坐标、`side`、旋转、镜像、参数显示单位、科学计数法偏好、撤销历史、当前选择和仿真结果。因此它适合校验、交换和求解,但不能无损还原前端画布;要继续编辑,应保存工程 JSON。
|
||
|
||
#### 5.3.2 一个最小 XML 片段
|
||
|
||
下面用“阶跃信号 → 力源 → 零力端”同时展示信号和机械连接:
|
||
|
||
```xml
|
||
<?xml version="1.0" encoding="UTF-8"?>
|
||
<System name="signal-force-demo" schemaVersion="3" unitSystem="SI">
|
||
<Simulation tStart="0" tStop="2"
|
||
sampleStep="0.1" maxStep="0.005" method="BDF"/>
|
||
|
||
<Components>
|
||
<Component id="step_1" type="amesim_step0" modelVersion="0.1.0">
|
||
<Parameter name="initial" value="0"/>
|
||
<Parameter name="final" value="20"/>
|
||
<Parameter name="time" value="0.04"/>
|
||
</Component>
|
||
|
||
<Component id="force_1" type="amesim_forc" modelVersion="0.2.0">
|
||
<!-- +1 为默认施力方向,-1 为反向 -->
|
||
<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>
|
||
```
|
||
|
||
这里没有写 `kind/domain/role`,也没有 `<Port>`。后端看到 `type="amesim_step0"` 后,会从注册表知道 `out` 是信号输出;看到 `amesim_forc.res` 后,会知道它是信号输入;同理还能恢复两个机械端口的变量合同。也就是说,端口名是“查说明书的索引”,不是由 XML 自己重新定义接口。
|
||
|
||
XML v3 的连接规则是:
|
||
|
||
- 物理和信号 `Connection` 都只写两个 `Endpoint`;
|
||
- 两端的 `kind/domain/nominalRole/variables` 都从当前注册模型恢复;
|
||
- 信号连接必须恰好包含一个注册输出端和一个注册输入端,但端点先后顺序不决定方向;一个输出可以扇出到多个输入,每个输入只能有一个驱动;
|
||
- 物理端点无序,流向由求解结果和统一正号约定决定;
|
||
- 参数必须完整、有限、使用 SI,并通过当前模型的范围或枚举校验。
|
||
|
||
**[已实现] 布局与物理方向已经分开。** `side/rotation/mirrored` 只保留在工程 JSON 中,XML v3 不包含它们。`amesim_forc` 从模型版本 `0.2.0` 起用显式 `direction=+1/-1` 控制力的正反:相同输入 20 N 时,`direction=+1` 要求机械端口平衡值为 `f=-20 N`,`direction=-1` 时为 `f=+20 N`。旋转或镜像图标不改变求解结果(`app/simulation/components/amesim/mechanical/translational.py`、`tests/test_amesim_mechanical_public_components.py`)。
|
||
|
||
### 5.4 编译结果 JSON:接线检查报告
|
||
|
||
`POST /api/reactflow/compile-model` 和 `POST /api/system-xml/compile-model` 最终调用 `SimulationNetwork.as_interface_dict()`(`app/simulation/systems/network.py:280-314`)。
|
||
|
||
它不是新的工程存档,而是告诉调用者:“后端实际装配出了什么”。下例也是带注释的结构示意:
|
||
|
||
```jsonc
|
||
{
|
||
"name": "tank-orifice-demo",
|
||
"components": [
|
||
{
|
||
"id": "tank_1",
|
||
"type": "tank",
|
||
"ports": [
|
||
{
|
||
"name": "port_a",
|
||
"kind": "physical",
|
||
"domain": "pneumatic",
|
||
"variables": [
|
||
// 工程 JSON 和 XML 都没保存的完整变量定义,在这里由注册表恢复
|
||
{"name": "p", "connectionRule": "equal", "unit": "Pa"},
|
||
{"name": "m_flow", "connectionRule": "sumToZero", "unit": "kg/s"},
|
||
{"name": "h_outflow", "connectionRule": "streamMix", "unit": "J/kg"}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
],
|
||
"connections": [
|
||
{
|
||
"id": "edge-1",
|
||
"kind": "physical",
|
||
"domain": "pneumatic",
|
||
"endpoints": [
|
||
{"component": "tank_1", "port": "port_a"},
|
||
{"component": "orifice_1", "port": "port_a"}
|
||
] // 物理连接只有两个端点,不再保留 source/target 含义
|
||
}
|
||
],
|
||
"unconnectedPorts": [] // 若有漏接,会在这里或诊断中体现
|
||
}
|
||
```
|
||
|
||
### 5.5 四层字段对照
|
||
|
||
先说结论:v3 XML 只携带连接实际用到的端口名,端口大类和完整变量表都由后端注册表恢复。这样不会同时维护“模型类中的端口”和“XML 中的端口副本”。
|
||
|
||
| 模型含义 | 目录 JSON | 工程 JSON | System XML v3 | 编译结果 JSON |
|
||
| --- | --- | --- | --- | --- |
|
||
| 端口编号 | `name` | `name` | 只在 `Endpoint/@port` 出现 | `name` |
|
||
| 物理/信号 | `kind` | `kind` | 不保存,由 `type + port` 恢复 | `kind` |
|
||
| 气动/机械/信号 | `domain` | `domain` | 不保存,由注册表恢复 | `domain` |
|
||
| 名义角色 | `nominalRole` | `nominalRole` | 不保存,由注册表恢复 | `nominalRole` |
|
||
| 正号约定 | `positiveFlowDirection` | 同名字段 | 不保存,由注册表恢复 | 同名字段 |
|
||
| 完整变量和规则 | `variables[]` | 不保存 | 不保存 | `variables[]`,由注册表恢复 |
|
||
| 显示侧 | `side` | `side` | 不保存 | 不是求解合同 |
|
||
| 画布连线 | 不适用 | `source/.../targetHandle` | 两个 `Endpoint` | 规范化后的连接端点 |
|
||
| 模型合同版本 | `modelVersion` | 节点显式保存并与目录核对 | `Component/@modelVersion` 必填 | 按当前注册模型编译 |
|
||
|
||
## 6. 接线时后端具体检查什么
|
||
|
||
可以把 `SimulationNetwork.connect()` 当作最后一道“防止插错线”的检查(`app/simulation/systems/network.py:83-150`)。它会依次确认:
|
||
|
||
1. 组件和端口确实存在,且不是自己接自己;
|
||
2. 两端 `kind` 一样;
|
||
3. 两端 `domain` 一样;
|
||
4. 完整变量表一致;
|
||
5. 信号线是一端 input、一端 output;
|
||
6. 一个物理端口最多接一条线;需要分支时必须放入 `tee`、`amesim_pn3node2`、`amesim_p4node2` 等分支组件;
|
||
7. 连接 ID 和端点组合没有重复。
|
||
|
||
对于案例 A 的一条气动连接,网络直接形成:
|
||
|
||
```text
|
||
p_A - p_B = 0 # 接头两侧压力一致
|
||
m_flow_A + m_flow_B = 0 # 流出一侧的质量等于流入另一侧的质量
|
||
```
|
||
|
||
不会形成 `h_outflow_A = h_outflow_B`。焓要在压力、流量确定后,由 `StreamResolver` 根据真实流向传播或混合。物理连接端点顺序不影响结果,相关测试包括 `tests/test_component_interfaces.py:51-60`、`tests/test_system_xml_protocol.py:146-176` 和 `tests/test_generic_system_xml_simulation.py:354-372`。
|
||
|
||
XML v3 还会先经过:
|
||
|
||
- 安全解析和 5 MiB 大小限制(`app/system_xml.py:21, 243-280`);
|
||
- XSD 格式检查;
|
||
- 组件类型、`modelVersion`、参数完整性和值域检查;
|
||
- 端点引用检查,并从注册表恢复端口类型、角色、变量和正号约定;
|
||
- 端点占用、信号输入/输出配对、物理域和变量合同检查。
|
||
|
||
XML 语义检查会把未连接端口记为 warning;真正进入通用求解前,未连接的**物理端口**会成为 `PORT_UNCONNECTED` error(`app/simulation/systems/generic.py:93-125`)。未连接信号端口不会阻止求解。
|
||
|
||
## 7. 当前容易踩坑的跨层差异
|
||
|
||
这些问题不妨碍理解主流程,但开发或制作 XML 时必须注意。
|
||
|
||
### 7.1 信号多接的规则前后端不一致
|
||
|
||
**[发现]** 后端网络和 XML 只限制物理端口单连接,没有禁止多个信号源同时连接到一个 input。`SignalResolver.solve()` 会按连接顺序依次写入,因此最后一个值覆盖前面的值。
|
||
|
||
前端 `canConnectPorts()` 却对所有端口实行一对一:既阻止多个源写同一输入,也阻止一个输出连到多个输入(`frontend/src/App.tsx`)。所以:
|
||
|
||
- 在浏览器里通常画不出这种多接;
|
||
- 直接调用 XML/API 却可能构造出来;
|
||
- 最后覆盖不是稳定的“求和”或“仲裁”规则,不应依赖。
|
||
|
||
后续最好统一为“每个信号输入只能有一个驱动”,或者显式增加求和、选择、总线组件。
|
||
|
||
### 7.2 XML 不写端口类型,不等于后端不知道类型
|
||
|
||
**[已实现]** v3 有意删除 `Port`、连接的 `kind/domain` 和端点 `role`。这不是允许调用者“随便省略”,而是把端口合同收回到唯一事实来源:后端注册模型。语义检查会用 `Component/@type + Endpoint/@port` 查出 `kind/domain/nominalRole/variables`;端口不存在或两端不兼容仍会报错。手写 v3 时不要把 v2 的这些字段加回来,XSD 会拒绝它们。
|
||
|
||
### 7.3 JSON 和 XML 对缺省参数的处理不同
|
||
|
||
**[发现]** ReactFlow JSON 编译和 JSON→XML 导出会用注册默认值补齐缺失参数;System XML v3 语义检查会对缺少的注册参数报告 `PARAMETER_REQUIRED_MISSING`。因此“工程 JSON 可以省略默认参数”不等于“手写 XML 也可以省略”。正规导出器会替你写全,人工制作 v3 时必须列全。
|
||
|
||
### 7.4 当前 API 只接受 v3
|
||
|
||
仓库只保留当前的 v3 协议和 XSD。v1/v2 不属于受支持输入,旧版本号只出现在拒绝边界测试和“旧格式不受支持”的说明中。
|
||
|
||
**[已实现]** 当前 `validate_system_xml_document()` 固定加载 `schemas/system-simulation-v3.xsd`,不会按 `schemaVersion` 自动切换或迁移 v1/v2;新文件必须使用 v3。
|
||
|
||
### 7.5 XML v3 会锁定模型版本
|
||
|
||
**[已实现]** `Component/@modelVersion` 是 v3 必填属性,语义检查要求它与当前注册模型版本完全一致。例如 `amesim_forc` 当前是 `0.2.0`,旧版本号不会被静默当成新方程求解。工程 JSON v1 的每个节点也保存创建时的 `modelVersion`,编译和导出 XML 前必须先与当前目录核对。这种设计采取“发现不一致就拒绝”的策略,不表示系统已经提供自动模型迁移器。
|
||
|
||
### 7.6 不从旧格式推断当前行为
|
||
|
||
当前格式只看 `docs/system-xml-v3.md` 和 `schemas/system-simulation-v3.xsd`。旧格式中的 `Port`、布局字段、端点 `role`、`Simulation/@step` 和“旋转改变力方向”都不能继续套用到 v3。
|
||
|
||
## 8. 当前模型覆盖范围
|
||
|
||
不需要记住所有型号,只需知道它们仍归入前面三种插头标准。
|
||
|
||
| 用途 | 当前模型 |
|
||
| --- | --- |
|
||
| 实验气动 | `cylinder`、`tank`、`pipe`、`orifice`、`tee` |
|
||
| AMESim 气动 | `amesim_pnpl01`、`amesim_pnrp17`、`amesim_pnch023`、`amesim_pnch012`、`amesim_pnor001`、`amesim_pnvo001_fixed`、`amesim_pnvo001`、`amesim_pnl00r`、`amesim_pnl0001`、`amesim_pnl0002`、`amesim_pnl0003`、`amesim_pn3node2`、`amesim_p4node2` |
|
||
| AMESim 机械 | `amesim_f000`、`amesim_forc`、`amesim_mecmas21`、`amesim_lstp00a`、`amesim_lmechn1`、`amesim_pnrp17` |
|
||
| AMESim 信号 | `amesim_step0`、`amesim_ud00`、`amesim_forc`、`amesim_pnvo001` |
|
||
| 零端口介质定义 | `amesim_ideal_air_medium`、`amesim_helium_medium` |
|
||
|
||
两个介质模型虽然出现在组件目录中,却没有连接端口。它们在编译第一阶段登记 `gi=1..99` 的介质定义,之后不进入方程网络(`app/main.py:1206-1253`)。它们是配置节点,不是第四类接口。
|
||
|
||
## 9. 与接口表示相关的 HTTP API
|
||
|
||
这里的 API 可以理解为围绕上述四层数据提供的“入口按钮”。
|
||
|
||
| API | 人话解释 | 当前前端是否直接使用 |
|
||
| --- | --- | --- |
|
||
| `GET /api/components/catalog` | 获取后端产品说明书 | 是 |
|
||
| `GET /api/reactflow/projects` | 列出后端保存的工程 JSON v1 | 当前 UI 主路径不用 |
|
||
| `GET /api/reactflow/projects/{id}` | 读取并校验一个后端工程 | 当前 UI 主路径不用 |
|
||
| `POST /api/reactflow/projects/{id}` | 保存一个工程并保留单位/科学计数显示信息 | 当前 UI 主路径不用 |
|
||
| `POST /api/reactflow/system-xml` | 把工程 JSON 转成精简 XML v3 | UI 当前也能在浏览器内生成同一结构 |
|
||
| `POST /api/reactflow/compile-model` | 检查工程 JSON 并返回装配结果 | 可用于诊断 |
|
||
| `POST /api/system-xml/validate` | 只检查 XML 格式和语义 | 可用于诊断 |
|
||
| `POST /api/system-xml/parse` | 把 XML 变成规范化执行模型;不还原坐标、旋转等画布信息 | 可用于检查求解输入,不是无损工程导入 |
|
||
| `POST /api/system-xml/compile-model` | 检查 XML 并装配网络 | 求解前使用 |
|
||
| `POST /api/system-xml/simulate-stream` | 提交 XML 并持续接收进度/最终结果 | 当前 UI 的通用求解入口 |
|
||
| `POST /api/system-xml/simulate` | 同步返回完整结果 | 后端提供,UI 主路径不用 |
|
||
|
||
后端还提供 CSV 导出 API。当前 UI 的工程保存和读取主要发生在浏览器与用户选择的本地文件中,后端工程接口作为同一严格合同的可选持久化入口。
|
||
|
||
## 10. 已实现、约定和推断:最后再分一次边界
|
||
|
||
### [已实现]
|
||
|
||
- 当前实际有气动、机械、标量信号三类端口,模型加载快照为 27 个模型、61 个端口。
|
||
- 物理端口以进入组件为正,物理连接端点无序;信号方向由注册端口的 output/input 决定,XML 端点不写 source/target 角色。
|
||
- 注册器、JSON/XML 编译器、XML 语义层和网络层会分层检查接口。
|
||
- 气动压力/质量流量约束、焓传播、外部容积传播、机械连接约束和标量信号传播已有可执行代码。
|
||
- 工程 JSON 可导出 System XML v3;XML v3 可解析为执行模型并编译为带完整端口合同的网络。
|
||
- `amesim_forc` 用 `direction=+1/-1` 决定力方向;旋转/镜像只影响显示。
|
||
- MECMAS21 工程、目录、XML 和模型统一使用 AMESim 原生 `1/2` 选项编码,不再猜测或转换旧 `0/1` 值。
|
||
|
||
### [约定]
|
||
|
||
- 组件模型类的 `PORTS` 是权威端口合同;前端目录只是读取视图。
|
||
- XML 和执行参数使用 SI 基准值。
|
||
- 物理端口的 `nominalRole` 不限制实际流向。
|
||
- 新文件使用 System XML v3;每个组件显式写当前 `modelVersion`,仿真采样字段写 `sampleStep`。
|
||
|
||
### [推断/需要另行实测]
|
||
|
||
- 类型字段允许出现其他 `domain` 字符串,但新增液压、电气等域还需要变量定义、组件方程和专项求解器;只改字符串不能工作。
|
||
- 27 个模型和 61 个端口是本次加载快照。受控库清单改变后,数量也会改变。
|
||
- v1/v2 XML 明确不受支持;旧工程 JSON 也不会由当前前端自动猜测或迁移。
|
||
|
||
## 11. 关键文件与符号索引
|
||
|
||
读到具体疑问时,可从这里回到代码。前面的章节已经给出人话解释,本表用于精确定位。
|
||
|
||
| 主题 | 文件与位置 | 关键符号 |
|
||
| --- | --- | --- |
|
||
| 端口类型、变量、正号 | `app/simulation/core/ports.py:7-231` | `PortVariableDefinition`、`PortDefinition`、`PortState` |
|
||
| 组件事实来源 | `app/simulation/core/base.py:21-63` | `Component.PORTS`、`register_declared_port()` |
|
||
| 端口显示信息 | `app/simulation/core/catalog.py:27-71` | `PortDisplaySpec`、`ComponentDisplaySpec` |
|
||
| 注册发现与校验 | `app/simulation/registry.py:41-170, 416-490, 825-1027` | `ComponentModelSpec`、`_validate_port()`、`build_component_catalog()` |
|
||
| 网络接线与方程 | `app/simulation/systems/network.py:24-314` | `Connection`、`SimulationNetwork.connect()`、`connection_equation_residuals()` |
|
||
| 工程 JSON 与编译 | `app/main.py:86-155, 1181-1344` | `ReactFlowPortDefinition`、`compile_reactflow_network()` |
|
||
| JSON 转 XML | `app/main.py:947-1097` | `build_reactflow_system_xml()` |
|
||
| XML 解析和语义检查 | `app/system_xml.py` | `SystemXmlComponent`、`SystemXmlEndpoint`、`SystemXmlDocument`、`validate_system_xml_document()` |
|
||
| XML v3 当前格式 | `schemas/system-simulation-v3.xsd`、`docs/system-xml-v3.md` | `sampleStep`、`modelVersion`、`Parameter`、`Endpoint` |
|
||
| 目录 JSON 格式 | `schemas/component-catalog-v1.schema.json:52-160` | `$defs.portVariable`、`$defs.port` |
|
||
| 前端端口和工程类型 | `frontend/src/App.tsx` | `PortDefinition`、`ReactFlowProjectPayload` |
|
||
| 前端生成 XML | `frontend/src/App.tsx` | `buildSystemXml()`、`projectConnectionMetadata()` |
|
||
| 接口核心测试 | `tests/test_component_interfaces.py` | 变量规则、端点中立、单连接限制 |
|
||
| XML 协议测试 | `tests/test_system_xml_protocol.py`、`tests/test_system_xml_parser.py` | v3 表示和语义诊断 |
|
||
|
||
## 12. 一句话复盘
|
||
|
||
SystemSimulationApp 用三种端口把组件组成网络:气动和机械端口负责“守恒与相容”,信号端口负责“有方向地传一个数值”;目录 JSON 定义型号,工程 JSON 保存画布,System XML v3 保存精简求解清单,编译结果则证明后端最终理解并装配出了什么。
|