Replace Python numerical kernels with native C execution

This commit is contained in:
ljz committed 2026-09-10 01:12:18 +08:00
1 parent 48da6be21c
commit 3b38f73fe0
227 files changed
+16801 -75499

No files matched your search

@@ -1,15 +1,15 @@
# 组件模型建模规范 v1
状态:已在 `experimental` 临时组件库实施
适用对象:人工开发者、代码生成工具和 AI 编程助手
状态:2026-09-10 更新为 Python 声明、C 数值实现
适用对象:人工开发者、代码生成工具和 AI 编程助手
配套读取规范:[组件库分类、发现与读取规范 v1](component-library-spec-v1.md)
## 1. 文档目标
本文档规定一个 Python 仿真元件应如何创建、修改、测试和注册。完成后的模型必须
本文档规定采用 Python 元数据与 C 数值内核的元件应如何创建、修改、测试和注册。完成后的模型必须
同时满足四个使用方:
1. 求解器能够实例化模型并调用方程。
1. C 编译器能够读取模型声明、生成系统代码并调用 C 方程。
2. System XML 能够根据稳定类型找到模型。
3. React Flow 能够自动显示图标、端口和参数。
4. 结果页面能够根据结构化元数据展示变量。
@@ -43,11 +43,7 @@
### 2.3 新增内部模型
仅供固定算例或研究代码使用、不进入前端目录的模型,不加入 `library.py`。这类模型
应放在对应 `examples/` 或专用系统目录,不能与公开模型混放后依赖扫描规则排除。
当前示例是
[`app/simulation/examples/testmodel/dynamic_pipe.py`](../../app/simulation/examples/testmodel/dynamic_pipe.py)。
不进入前端目录的研究模型不加入 `library.py`。需要执行时仍应编写 C 内核并显式纳入编译器支持合同;不要恢复旧 Python 求解路径。
### 2.4 新增物理域
@@ -111,7 +107,6 @@ app/simulation/components/experimental/junctions/tee.py
```python
MODEL_TYPE = "example_component"
MODEL_VERSION = "1.0.0"
PRESSURE_FLOW_DEPENDS_ON_STREAM = False
PORTS = (...)
PARAMETERS = (...)
RESULT_VARIABLES = (...)
@@ -138,62 +133,11 @@ def create(
## 6. 基类选择
### 6.1 `AlgebraicComponent`
`AlgebraicComponent` 表示没有积分状态的元件;`DynamicComponent` 表示有积分状态的元件;`ThermodynamicVolumeComponent` 提供标准 `m,U,p,T,rho,u,h` 结果声明。这些基类只描述模型,不再实现数值求值方法。
适用于没有积分状态、由当前端口变量和参数直接决定残差的元件,例如:
构造函数负责参数校验、几何预处理和端口注册。介质对象保存物性常量与模型选择。状态初值、流量、焓、受力和导数必须在 C 中计算。
- 孔板
- 阀门
- 阻性管段
- 理想三通
至少实现:
- 构造函数和端口注册。
- `create()`。
- `pressure_flow_equation_residuals()`。
- 需要传递 stream 变量时实现 `update_stream_outflows()`。
实现 `update_stream_outflows()` 或 `update_flow_temperature_references()` 的公开模型,
还应在该公开类自身显式声明 `PRESSURE_FLOW_DEPENDS_ON_STREAM`:构成压力/流量方程
会读取这些 hook 写入的焓或温度引用时设为 `True`,否则设为 `False`。省略声明、
声明非法值或由自定义子类仅继承父类声明时,求解器会保守使用全网热流闭合;不要
为了获得分块加速而错误声明 `False`。
`pressure_flow_equation_residuals()` 返回的每条 `EquationResidual.variables` 必须完整
列出该残差实际读取的全部代数端口量(`p/m_flow/x/v/f`),不能只写“主要变量”。
求解器会用这份声明编译稀疏 Jacobian 和独立方程块;漏写依赖可能让有限差分方向
不完整。仓库内置组件会接受结构与数值依赖回归,外部自定义组件当前仍保守使用
全网 dense 回退,直到具备同等的依赖验证边界。
### 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` 或求解器。
用 `EQUATIONS` 或 `equation_definitions()` 声明结构化连接约束,返回 `EquationDefinition`,包括关系、变量和归属,不包含运行时残差值。`__MODEL__` 占位符由基类替换为实例名。该声明供网络结构展示与检查使用,不替代 C 方程或构建支持白名单。
## 7. 端口建模规范
@@ -319,16 +263,7 @@ ResultVariableDefinition(
)
```
声明后必须在 `component_result_values()` 返回同名值:
```python
def component_result_values(self) -> Mapping[str, float]:
return {
"pressure_drop": self.port_a.p - self.port_b.p,
}
```
声明集合和返回键必须一致。
声明的每个输出必须在 C 生成器的输出布局中有对应值。测试应核对实际 EXE 输出键与 `result_variable_metadata()` 一致,不再实现 Python `component_result_values()`。
### 9.2 端口结果
@@ -407,168 +342,20 @@ def create(
`create()` 不应重复实现参数默认值和边界校验,也不能静默修改传入参数。
## 12. 方程实现要求
## 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 单位和端口流入为正,核对逆流、质量/能量守恒及边界状态。
- 残差形式统一为“期望等式左侧减右侧”。
- 每条 `EquationResidual` 使用稳定、可定位的 `id`。
- `variables` 列出该残差实际涉及的端口量或状态。
- `role` 与方程主要约束的物理角色一致。
- 对零压差、零流量和反向流动给出有限结果。
- 必要正则化必须有物理解释,并通过边界测试保护。
- 不得用画布坐标、连接线方向或组件名称决定方程。
## 13. 模型实现示例
### 12.1 可因果执行的残差语义
可参照 [气瓶声明](../../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)。
后端只会对经过结构门控的内置模型启用完全因果执行。除完整声明
`variables` 外,这些模型还必须遵守以下可执行语义:
- `role="effort", relation="state"` 的残差写成
`端口 effort - 状态给定值`,被约束的端口量系数必须为 `+1`。
- `role="effort", relation="equal"` 的残差写成两个同类 effort 的差。
- `role="flow", relation="sumToZero"` 按“流入组件为正”的约定求和,待消元
flow 的系数必须为 `+1`。
- `role="flow", relation="constitutive"` 写成
`待消元 flow - 本构计算值`,待消元 flow 的系数必须为 `+1`。
- `pressure_flow_equation_values()` 必须是无副作用的只读计算,返回顺序和长度
必须与 `pressure_flow_equation_residuals()` 的编译结果永久一致;不得在求残差时
修改端口、状态或活动集缓存。
求解器仍会在首次闭合、离散事件之后和固定周期执行完整残差审计。结构不满足、
运行时覆盖不完整或审计不通过时,会立即熔断到原有残差/非线性求解路径。现场诊断
时可在启动进程前设置 `SIMULATION_CAUSAL_FAST_PATH=0`,一键关闭该优化而不改变模型
文件。
动态模型还必须:
- 状态向量长度稳定。
- `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"
PRESSURE_FLOW_DEPENDS_ON_STREAM = False
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)
Python `create()` 只创建经校验的描述对象;C `model_init()` 生成质量、能量及机械状态,`model_eval()` 计算导数和输出。两者通过生成的状态/参数布局关联。
## 14. 注册模型
@@ -597,11 +384,11 @@ models=(
2. 默认参数创建测试。
3. 参数边界测试。
4. 端口与显示布局一致性测试。
5. 关键方程残差测试。
5. C 方程与独立解析解或冻结参考值对照。
6. 零流量或反向流动测试。
7. 目录输出测试。
8. 最小 XML 编译测试。
9. 能进入通用求解器的模型,再添加短时仿真测试。
9. 使用 C RK45/BDF 的短时仿真与事件测试。
推荐先运行: