37 KiB
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 | 接线检查报告 | 后端恢复完整模型后,最终认出了哪些端口、连接和方程结构? |
贯穿全文的两个例子:
案例 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,同时让气缸、阀和气罐通过物理端口相连。
先记住六点就能继续阅读:
- 物理端口必须同类相连。 气动只能接气动,机械只能接机械,不能把“气管”插到“机械接头”上。
- 信号线有方向。 必须连接一个注册为
output的端口和一个注册为input的端口;XML v3 不再另写source/target角色。 - 物理线没有 source/target 的物理含义。 画布虽然要写
source/target,后端会把它当作无方向的两个端点。 - 流变量统一以“进入当前组件”为正。 因此同一条气路两端的质量流量数值互为相反数。
- 工程 JSON 和 XML 不重复保存完整变量表。 后端依靠组件的
modelType/type去注册表找回完整定义。 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:
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 端口。连接后,求解器关心三件主要事情:
- 接头处的压力要相容;
- 从一个组件流出的质量,必须流入另一个组件;
- 气体携带的能量要按实际流向传递,发生汇合时还要混合。
这也是 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:阶跃信号控制阀或力源
控制线与气管不同,它只把一个数值从发送方交给接收方:
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 的另外几条连接才传递气体的压力、质量流量和焓。控制线不会“变成气管”,阀组件负责在内部用命令改变气路行为。
同样的信号也能驱动力源:
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:
{
"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。
{
"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 是什么结构
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 + 端口名”重新接线 |
实际生成过程可以概括为:
- 收集当前节点、连线和仿真设置;
- 把工程 JSON 的
simulation.step映射成 XML 的sampleStep; - 根据组件目录写入
modelVersion,并用注册默认值补齐全部参数; - 把参数表达式求值、换算为 SI,只写
Component/Parameter; - 每条边只写两个
Endpoint,不复制端口类型或方向角色。
XML v3 不保存显示名称、symbol、坐标、side、旋转、镜像、参数显示单位、科学计数法偏好、撤销历史、当前选择和仿真结果。因此它适合校验、交换和求解,但不能无损还原前端画布;要继续编辑,应保存工程 JSON。
5.3.2 一个最小 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)。
它不是新的工程存档,而是告诉调用者:“后端实际装配出了什么”。下例也是带注释的结构示意:
{
"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)。它会依次确认:
- 组件和端口确实存在,且不是自己接自己;
- 两端
kind一样; - 两端
domain一样; - 完整变量表一致;
- 信号线是一端 input、一端 output;
- 一个物理端口最多接一条线;需要分支时必须放入
tee、amesim_pn3node2、amesim_p4node2等分支组件; - 连接 ID 和端点组合没有重复。
对于案例 A 的一条气动连接,网络直接形成:
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 保存精简求解清单,编译结果则证明后端最终理解并装配出了什么。