# 组件模型建模规范 v1 状态:2026-09-10 更新为 Python 声明、C 数值实现 适用对象:人工开发者、代码生成工具和 AI 编程助手 配套读取规范:[组件库分类、发现与读取规范 v1](component-library-spec-v1.md) ## 1. 文档目标 本文档规定采用 Python 元数据与 C 数值内核的元件应如何创建、修改、测试和注册。完成后的模型必须 同时满足四个使用方: 1. C 编译器能够读取模型声明、生成系统代码并调用 C 方程。 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`。需要执行时仍应编写 C 内核并显式纳入编译器支持合同;不要恢复旧 Python 求解路径。 ### 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///.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" 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. 基类选择 `AlgebraicComponent` 表示没有积分状态的元件;`DynamicComponent` 表示有积分状态的元件;`ThermodynamicVolumeComponent` 提供标准 `m,U,p,T,rho,u,h` 结果声明。这些基类只描述模型,不再实现数值求值方法。 构造函数负责参数校验、几何预处理和端口注册。介质对象保存物性常量与模型选择。状态初值、流量、焓、受力和导数必须在 C 中计算。 用 `EQUATIONS` 或 `equation_definitions()` 声明结构化连接约束,返回 `EquationDefinition`,包括关系、变量和归属,不包含运行时残差值。`__MODEL__` 占位符由基类替换为实例名。该声明供网络结构展示与检查使用,不替代 C 方程或构建支持白名单。 ## 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, ) ``` 声明的每个输出必须在 C 生成器的输出布局中有对应值。测试应核对实际 EXE 输出键与 `result_variable_metadata()` 一致,不再实现 Python `component_result_values()`。 ### 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. C 方程实现要求 1. 在 `native/components/kernels.c` 及 `native/include/kernels.h` 实现物性或元件数值公式。 2. 在 `native_codegen/extended.py` 注册状态、端口、参数、初始化与输出映射;符合简单拓扑的模型还应核对 `compiler.py` 快速路径。 3. 在 `native_codegen/contracts.py` 声明支持版本,不允许仅注册 Python 模型就声称具备 C 求解能力。 4. 当前状态与试探状态分离,求值不能覆盖已接受状态。无效物性、欠定连接和不收敛必须明确失败。 5. 信号跳变和限位事件接入 C 运行库;不能改动刚度、阻尼或容差来隐藏数值错误。 6. 保持 SI 单位和端口流入为正,核对逆流、质量/能量守恒及边界状态。 ## 13. 模型实现示例 可参照 [气瓶声明](../../app/simulation/components/experimental/storage/cylinder.py)、[气腔声明](../../app/simulation/components/amesim/storage/chambers.py)、[C 内核](../../native/components/kernels.c) 与 [系统 C 生成器](../../app/simulation/native_codegen/extended.py)。完整开发顺序见 [组件目录说明](../../app/simulation/components/example.md)。 Python `create()` 只创建经校验的描述对象;C `model_init()` 生成质量、能量及机械状态,`model_eval()` 计算导数和输出。两者通过生成的状态/参数布局关联。 ## 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. C 方程与独立解析解或冻结参考值对照。 6. 零流量或反向流动测试。 7. 目录输出测试。 8. 最小 XML 编译测试。 9. 使用 C RK45/BDF 的短时仿真与事件测试。 推荐先运行: ```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 能映射到正确模型。 - 最小系统能够编译;声称可仿真的模型必须产生有限结果。 - 针对性测试、完整回归和必要的前端构建通过。 - 文档记录了模型假设、适用范围和已知限制。