# 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 的外形是一棵树,模型本身仍是一张连接图。组件平铺在 `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;不设置固定的采样点数上限 | | `maxStep` | 自适应积分器单个内部步的上限 | 必须大于 0 | | `method` | 积分方法 | `RK45/RK23/DOP853/Radau/BDF/LSODA` | `sampleStep` 和 `maxStep` 不是一回事: - `sampleStep` 决定结果曲线多久保存一个点; - `maxStep` 限制求解器内部一次最多前进多久; - 自适应求解器可以因为误差、事件或试探状态失败而走得比 `maxStep` 更短。 采样点数量会在创建时间数组前计算。若区间长度不可表示为有限数、点数超过当前 运行时可表示的集合大小,或在当前浮点精度下无法得到包含 `tStart/tStop` 的严格 递增时间序列,输入会在仿真前被拒绝。采样点不再受固定业务上限约束,但结果内存、 序列化体积和浏览器负载仍会随“采样点数 × 输出变量数”线性增长。 工程 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 ``` 每个参数只保存稳定参数名和已经换算到 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`; - 固定气动接口的变量供需互补,节点参考温度/压力来源没有闭合引用环;详见 [气动端口变量供需合同](port-computation-contract.md); - 一个信号输出可以驱动多个输入,但每个信号输入只能有一个驱动; - 同一物理端口只使用一次;分支必须使用显式 Tee/节点组件; - 不允许自连接或重复端点对。 ### 6.3 两个 `Endpoint` 的顺序 物理连接的两个端点无序,交换顺序不改变方程或实际流向。 信号连接也不写 `role="source"` 或 `role="target"`。发送方和接收方由注册端口的 `output/input` 合同确定,而不是由 XML 中的先后顺序决定。导出器可以为了便于阅读把输出端写在前面,但求解器不能依赖这一顺序。 ## 7. `AmesimForc.direction` `amesim_forc` 从模型版本 `0.2.0` 开始使用显式物理参数: ```xml ``` 它只允许: - `+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`