Files
SystemSimulationApp/docs/other/接口类型与表示方式总结.md
T

37 KiB
Raw Blame History

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,同时让气缸、阀和气罐通过物理端口相连。

先记住六点就能继续阅读:

  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 只搬运可执行模型,端口合同由注册表恢复

[约定] 组件模型建模规范 v1说明:组件模型类及受控库清单是后端事实来源。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 端口。连接后,求解器关心三件主要事情:

  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:阶跃信号控制阀或力源

控制线与气管不同,它只把一个数值从发送方交给接收方:

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/standard/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 + 端口名”重新接线

实际生成过程可以概括为:

  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 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)。它会依次确认:

  1. 组件和端口确实存在,且不是自己接自己;
  2. 两端 kind 一样;
  3. 两端 domain 一样;
  4. 完整变量表一致;
  5. 信号线是一端 input、一端 output;
  6. 一个物理端口最多接一条线;需要分支时必须放入 tee、amesim_pn3node2、amesim_p4node2 等分支组件;
  7. 连接 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/standard/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/standard/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 保存精简求解清单,编译结果则证明后端最终理解并装配出了什么。