684 lines
21 KiB
Markdown
684 lines
21 KiB
Markdown
# 组件模型建模规范 v1
|
||
|
||
状态:已在 `experimental` 临时组件库实施
|
||
适用对象:人工开发者、代码生成工具和 AI 编程助手
|
||
配套读取规范:[组件库分类、发现与读取规范 v1](component-library-spec-v1.md)
|
||
|
||
## 1. 文档目标
|
||
|
||
本文档规定一个 Python 仿真元件应如何创建、修改、测试和注册。完成后的模型必须
|
||
同时满足四个使用方:
|
||
|
||
1. 求解器能够实例化模型并调用方程。
|
||
2. System XML 能够根据稳定类型找到模型。
|
||
3. React Flow 能够自动显示图标、端口和参数。
|
||
4. 结果页面能够根据结构化元数据展示变量。
|
||
|
||
本文档是模型代码的开发合同。若本文档与当前代码行为不一致,应把它视为缺陷:
|
||
先核对实际实现,再在同一次修改中同步代码、测试和文档,禁止让两套规则长期并存。
|
||
|
||
## 2. 开始前先判断任务类型
|
||
|
||
### 2.1 新增公开模型
|
||
|
||
公开模型会出现在前端组件库中,也能被 System XML 创建。必须:
|
||
|
||
- 放入某个组件库的分类目录。
|
||
- 实现完整模型契约。
|
||
- 加入该库 `library.py` 的 `models` 清单。
|
||
- 添加目录、契约、方程和最小仿真测试。
|
||
|
||
### 2.2 修改已有公开模型
|
||
|
||
必须先判断改动是否破坏已有工程:
|
||
|
||
| 改动 | 版本建议 | 兼容性要求 |
|
||
| --- | --- | --- |
|
||
| 修复数值实现但不改变契约 | 修订版本 | 旧 XML 和工程继续可用 |
|
||
| 新增有默认值的参数或结果 | 次版本 | 旧工程缺少该字段时必须有迁移或默认值 |
|
||
| 修改界面名称或图标 | 库修订版本 | 不修改机器标识 |
|
||
| 修改方程的物理语义 | 根据影响提高次版本或主版本 | 补充基准和变更说明 |
|
||
| 删除、改名端口或参数 | 主版本 | 必须设计工程和 XML 迁移 |
|
||
| 修改 `MODEL_TYPE` | 视为新模型 | 旧类型必须保留迁移映射 |
|
||
|
||
### 2.3 新增内部模型
|
||
|
||
仅供固定算例或研究代码使用、不进入前端目录的模型,不加入 `library.py`。这类模型
|
||
应放在对应 `examples/` 或专用系统目录,不能与公开模型混放后依赖扫描规则排除。
|
||
|
||
当前示例是
|
||
[`app/simulation/examples/testmodel/dynamic_pipe.py`](../app/simulation/examples/testmodel/dynamic_pipe.py)。
|
||
|
||
### 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/<library_id>/<category_id>/<model_module>.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. 基类选择
|
||
|
||
### 6.1 `AlgebraicComponent`
|
||
|
||
适用于没有积分状态、由当前端口变量和参数直接决定残差的元件,例如:
|
||
|
||
- 孔板
|
||
- 阀门
|
||
- 阻性管段
|
||
- 理想三通
|
||
|
||
至少实现:
|
||
|
||
- 构造函数和端口注册。
|
||
- `create()`。
|
||
- `pressure_flow_equation_residuals()`。
|
||
- 需要传递 stream 变量时实现 `update_stream_outflows()`。
|
||
|
||
### 6.2 `ThermodynamicVolumeComponent`
|
||
|
||
适用于包含质量和能量状态的气体容腔,例如:
|
||
|
||
- 气瓶
|
||
- 贮箱
|
||
- 有容积的管段
|
||
|
||
至少实现:
|
||
|
||
- `get_state_vector()`。
|
||
- `set_state_vector()`。
|
||
- `refresh_thermodynamic_ports()`。
|
||
- `state_derivative_from_ports()`。
|
||
- `pressure_flow_equation_residuals()`。
|
||
|
||
该基类已经提供标准热力学组件结果:
|
||
|
||
```text
|
||
m, U, p, T, rho, u, h
|
||
```
|
||
|
||
除非物理含义不同,不要重新复制这组结果声明。
|
||
|
||
### 6.3 其他基类
|
||
|
||
如果现有基类不能表达模型,应先评估是否缺少一种通用组件能力。不要为了一个模型
|
||
直接把专用判断塞入 `SimulationNetwork` 或求解器。
|
||
|
||
## 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 单位 |
|
||
| --- | --- |
|
||
| `area` | `m2` |
|
||
| `dimensionless` | 空字符串 |
|
||
| `density` | `kg/m³` |
|
||
| `flow_coefficient` | `kg/(s*Pa^0.5)` |
|
||
| `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` |
|
||
| `velocity` | `m/s` |
|
||
| `volume` | `m3` |
|
||
|
||
新增物理量时必须先扩展后端受控单位表,再评估前端是否需要单位换算选项。禁止在
|
||
单个模型中私自拼写新的同义 `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,
|
||
)
|
||
```
|
||
|
||
声明后必须在 `component_result_values()` 返回同名值:
|
||
|
||
```python
|
||
def component_result_values(self) -> Mapping[str, float]:
|
||
return {
|
||
"pressure_drop": self.port_a.p - self.port_b.p,
|
||
}
|
||
```
|
||
|
||
声明集合和返回键必须一致。
|
||
|
||
### 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. 方程实现要求
|
||
|
||
模型方程必须满足:
|
||
|
||
- 残差形式统一为“期望等式左侧减右侧”。
|
||
- 每条 `EquationResidual` 使用稳定、可定位的 `id`。
|
||
- `variables` 列出该残差实际涉及的端口量或状态。
|
||
- `role` 与方程主要约束的物理角色一致。
|
||
- 对零压差、零流量和反向流动给出有限结果。
|
||
- 必要正则化必须有物理解释,并通过边界测试保护。
|
||
- 不得用画布坐标、连接线方向或组件名称决定方程。
|
||
|
||
动态模型还必须:
|
||
|
||
- 状态向量长度稳定。
|
||
- `get_state_vector()` 和 `set_state_vector()` 互为逆操作。
|
||
- 状态导数满足质量和能量守恒约定。
|
||
- 初始化默认值能够产生有限介质状态。
|
||
|
||
## 13. 可复制的代数模型模板
|
||
|
||
下面是一个符合当前规范的两端口代数阻力模板。复制后必须根据真实物理模型修改
|
||
类型、参数、方程、名称和测试,不能只改类名就注册。
|
||
|
||
```python
|
||
from __future__ import annotations
|
||
|
||
from collections.abc import Mapping
|
||
from math import sqrt
|
||
|
||
from app.simulation.core.base import AlgebraicComponent
|
||
from app.simulation.core.catalog import ComponentDisplaySpec, PortDisplaySpec
|
||
from app.simulation.core.equations import EquationResidual
|
||
from app.simulation.core.metadata import ParameterDefinition
|
||
from app.simulation.core.medium import IdealGasMedium
|
||
from app.simulation.core.ports import PortDefinition
|
||
|
||
|
||
class ExampleRestriction(AlgebraicComponent):
|
||
MODEL_TYPE = "example_restriction"
|
||
MODEL_VERSION = "1.0.0"
|
||
PORTS = (
|
||
PortDefinition.pneumatic("port_a", nominal_role="bidirectional"),
|
||
PortDefinition.pneumatic("port_b", nominal_role="bidirectional"),
|
||
)
|
||
PARAMETERS = (
|
||
ParameterDefinition(
|
||
name="K",
|
||
label="流量系数",
|
||
quantity="flow_coefficient",
|
||
unit="kg/(s*Pa^0.5)",
|
||
default=1e-5,
|
||
minimum=0.0,
|
||
),
|
||
)
|
||
RESULT_VARIABLES = ()
|
||
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,
|
||
)
|
||
|
||
def __init__(self, name: str, K: float = 1e-5) -> None:
|
||
super().__init__(name)
|
||
self.set_parameter_values({"K": K})
|
||
self.K = K
|
||
self.port_a = self.register_declared_port("port_a")
|
||
self.port_b = self.register_declared_port("port_b")
|
||
|
||
@classmethod
|
||
def create(
|
||
cls,
|
||
*,
|
||
name: str,
|
||
medium: IdealGasMedium,
|
||
parameters: Mapping[str, float],
|
||
) -> ExampleRestriction:
|
||
return cls(name=name, K=parameters["K"])
|
||
|
||
def pressure_flow_equation_residuals(
|
||
self,
|
||
) -> tuple[EquationResidual, ...]:
|
||
pressure_difference = self.port_a.p - self.port_b.p
|
||
expected_flow = (
|
||
self.K
|
||
* sqrt(abs(pressure_difference))
|
||
* (1.0 if pressure_difference > 0.0 else -1.0)
|
||
if pressure_difference != 0.0
|
||
else 0.0
|
||
)
|
||
return (
|
||
EquationResidual(
|
||
id=f"{self.name}:mass_flow_balance",
|
||
owner="component",
|
||
owner_id=self.name,
|
||
relation="sumToZero",
|
||
variables=(
|
||
f"{self.name}.port_a.m_flow",
|
||
f"{self.name}.port_b.m_flow",
|
||
),
|
||
role="flow",
|
||
value=self.port_a.m_flow + self.port_b.m_flow,
|
||
),
|
||
EquationResidual(
|
||
id=f"{self.name}:pressure_flow_relation",
|
||
owner="component",
|
||
owner_id=self.name,
|
||
relation="constitutive",
|
||
variables=(
|
||
f"{self.name}.port_a.p",
|
||
f"{self.name}.port_b.p",
|
||
f"{self.name}.port_a.m_flow",
|
||
),
|
||
role="flow",
|
||
value=self.port_a.m_flow - expected_flow,
|
||
),
|
||
)
|
||
|
||
def update_stream_outflows(
|
||
self,
|
||
connected_h: Mapping[str, float],
|
||
) -> None:
|
||
self.port_a.h_outflow = connected_h["port_b"]
|
||
self.port_b.h_outflow = connected_h["port_a"]
|
||
```
|
||
|
||
真实现有模型可参考:
|
||
|
||
- 储能元件:
|
||
[`cylinder.py`](../app/simulation/components/experimental/storage/cylinder.py)
|
||
- 阻性元件:
|
||
[`orifice.py`](../app/simulation/components/experimental/flow/orifice.py)
|
||
- 多端口连接元件:
|
||
[`tee.py`](../app/simulation/components/experimental/junctions/tee.py)
|
||
|
||
## 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. 关键方程残差测试。
|
||
6. 零流量或反向流动测试。
|
||
7. 目录输出测试。
|
||
8. 最小 XML 编译测试。
|
||
9. 能进入通用求解器的模型,再添加短时仿真测试。
|
||
|
||
推荐先运行:
|
||
|
||
```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 能映射到正确模型。
|
||
- 最小系统能够编译;声称可仿真的模型必须产生有限结果。
|
||
- 针对性测试、完整回归和必要的前端构建通过。
|
||
- 文档记录了模型假设、适用范围和已知限制。
|