公开PNRP17并接通实时气动机械耦合

This commit is contained in:
huojiarong committed 2026-08-02 14:51:31 +00:00
1 parent 503394f3ed
commit 1f4027573f
18 files changed
+693 -29

No files matched your search

+2 -1
View File
@@ -9,7 +9,7 @@ from app.simulation.core.catalog import (
LIBRARY = ComponentLibrarySpec( LIBRARY = ComponentLibrarySpec(
id="amesim", id="amesim",
label="AMESim 组件库", label="AMESim 组件库",
version="0.2.0", version="0.3.0",
source_package="app.simulation.components.amesim", source_package="app.simulation.components.amesim",
temporary=True, temporary=True,
order=200, order=200,
@@ -33,6 +33,7 @@ LIBRARY = ComponentLibrarySpec(
"app.simulation.components.amesim.mechanical.translational:AmesimMecmas21", "app.simulation.components.amesim.mechanical.translational:AmesimMecmas21",
"app.simulation.components.amesim.mechanical.translational:AmesimLstp00a", "app.simulation.components.amesim.mechanical.translational:AmesimLstp00a",
"app.simulation.components.amesim.mechanical.translational:AmesimLmechn1", "app.simulation.components.amesim.mechanical.translational:AmesimLmechn1",
"app.simulation.components.amesim.mechanical.pistons:AmesimPnrp17",
"app.simulation.components.amesim.storage.chambers:AmesimPnch023", "app.simulation.components.amesim.storage.chambers:AmesimPnch023",
"app.simulation.components.amesim.storage.chambers:AmesimPnch012", "app.simulation.components.amesim.storage.chambers:AmesimPnch012",
"app.simulation.components.amesim.flow.orifices:AmesimPnor001", "app.simulation.components.amesim.flow.orifices:AmesimPnor001",
@@ -0,0 +1,228 @@
from __future__ import annotations
from collections.abc import Mapping
from math import pi
from app.simulation.components.amesim.gases import (
AMESIM_GAS_INDEX_PARAMETER,
normalize_amesim_gas_index,
)
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, ResultVariableDefinition
from app.simulation.core.medium import GasMedium
from app.simulation.core.ports import PortDefinition
AMESIM_REFERENCE_PRESSURE_PA = 101300.0
class AmesimPnrp17(AlgebraicComponent):
"""AMESim PNRP17 pneumatic piston with two mechanical faces.
Mechanical ports 2/5 share the piston-side motion and ports 3/4 share the
cylinder-side motion. The pneumatic port contributes its swept volume and
volume rate to the connected variable-volume chamber.
"""
MODEL_TYPE = "amesim_pnrp17"
MODEL_VERSION = "0.1.0"
PORTS = (
PortDefinition.pneumatic("port_1", nominal_role="bidirectional"),
PortDefinition.mechanical_translational("port_2"),
PortDefinition.mechanical_translational("port_3"),
PortDefinition.mechanical_translational("port_4"),
PortDefinition.mechanical_translational("port_5"),
)
PARAMETERS = (
AMESIM_GAS_INDEX_PARAMETER,
ParameterDefinition(
"dp",
0.2,
label="活塞直径",
quantity="length",
unit="m",
minimum=0.0,
minimum_exclusive=True,
description="活塞外径;与活塞杆直径共同确定有效受压面积。",
),
ParameterDefinition(
"dr",
0.001,
label="活塞杆直径",
quantity="length",
unit="m",
minimum=0.0,
description="穿过气室一侧的活塞杆直径,必须不大于活塞直径。",
),
ParameterDefinition(
"x0",
0.0,
label="初始腔长",
quantity="length",
unit="m",
description="机械端位移均为零时的气动腔长度。",
),
)
RESULT_VARIABLES = (
ResultVariableDefinition("volume", "扫掠容积", "volume", "m3", "derived", 10),
ResultVariableDefinition(
"volume_flow",
"扫掠容积变化率",
"volume_flow",
"m3/s",
"derived",
20,
),
ResultVariableDefinition("length", "气动腔长度", "length", "m", "derived", 30),
ResultVariableDefinition(
"pressure_force",
"气压力",
"force",
"N",
"derived",
40,
),
)
DISPLAY = ComponentDisplaySpec(
label="PNRP17 气动活塞",
library_id="amesim",
category_id="mechanical",
symbol="amesim_pnrp17",
ports=(
PortDisplaySpec("port_1", "left", order=10),
PortDisplaySpec("port_3", "left", order=20),
PortDisplaySpec("port_4", "left", order=30),
PortDisplaySpec("port_2", "right", order=40),
PortDisplaySpec("port_5", "right", order=50),
),
order=60,
)
def __init__(
self,
name: str,
medium: GasMedium,
*,
gi: float = 0.0,
dp: float = 0.2,
dr: float = 0.001,
x0: float = 0.0,
) -> None:
super().__init__(name=name)
self.set_parameter_values({"gi": gi, "dp": dp, "dr": dr, "x0": x0})
self.medium = medium
self.gi = normalize_amesim_gas_index(gi)
self.dp = float(dp)
self.dr = float(dr)
self.x0 = float(x0)
if self.dr > self.dp:
raise ValueError("PNRP17 rod diameter dr must not exceed piston diameter dp.")
for definition in self.PORTS:
port = self.register_declared_port(definition.name)
setattr(self, definition.name, port)
self.port_1.h_outflow = medium.specific_enthalpy(medium.T_ref)
@classmethod
def create(
cls,
*,
name: str,
medium: GasMedium,
parameters: Mapping[str, float],
) -> "AmesimPnrp17":
return cls(name=name, medium=medium, **dict(parameters))
@property
def effective_area(self) -> float:
return pi * (self.dp * self.dp - self.dr * self.dr) / 4.0
@property
def chamber_length(self) -> float:
return self.x0 + self.port_5.x - self.port_4.x
@property
def chamber_volume(self) -> float:
return self.effective_area * self.chamber_length
@property
def chamber_volume_flow(self) -> float:
return self.effective_area * (self.port_5.v - self.port_4.v)
@property
def pressure_force(self) -> float:
return (self.port_1.p - AMESIM_REFERENCE_PRESSURE_PA) * self.effective_area
def pressure_flow_equation_residuals(self) -> tuple[EquationResidual, ...]:
effort_pairs = (("port_2", "port_5"), ("port_3", "port_4"))
residuals: list[EquationResidual] = [
EquationResidual(
id=f"{self.name}:pneumatic_zero_mass_flow",
owner="component",
owner_id=self.name,
relation="constitutive",
variables=(f"{self.name}.port_1.m_flow",),
role="flow",
value=self.port_1.m_flow,
)
]
for first_name, second_name in effort_pairs:
first = self.get_port(first_name)
second = self.get_port(second_name)
for variable in ("x", "v"):
residuals.append(
EquationResidual(
id=f"{self.name}:{first_name}_{second_name}_{variable}_equal",
owner="component",
owner_id=self.name,
relation="equal",
variables=(
f"{self.name}.{first_name}.{variable}",
f"{self.name}.{second_name}.{variable}",
),
role="effort",
value=getattr(first, variable) - getattr(second, variable),
)
)
force = self.pressure_force
residuals.extend(
(
EquationResidual(
id=f"{self.name}:piston_side_force_balance",
owner="component",
owner_id=self.name,
relation="constitutive",
variables=(f"{self.name}.port_2.f", f"{self.name}.port_5.f", f"{self.name}.port_1.p"),
role="flow",
value=self.port_2.f + self.port_5.f + force,
),
EquationResidual(
id=f"{self.name}:cylinder_side_force_balance",
owner="component",
owner_id=self.name,
relation="constitutive",
variables=(f"{self.name}.port_3.f", f"{self.name}.port_4.f", f"{self.name}.port_1.p"),
role="flow",
value=self.port_3.f + self.port_4.f - force,
),
)
)
return tuple(residuals)
def pneumatic_volume_outputs(self) -> Mapping[str, tuple[float, float]]:
return {"port_1": (self.chamber_volume, self.chamber_volume_flow)}
def update_stream_outflows(self, connected_h: Mapping[str, float]) -> None:
self.port_1.h_outflow = connected_h.get(
"port_1",
self.medium.specific_enthalpy(self.medium.T_ref),
)
def component_result_values(self) -> Mapping[str, float]:
return {
"volume": self.chamber_volume,
"volume_flow": self.chamber_volume_flow,
"length": self.chamber_length,
"pressure_force": self.pressure_force,
}
@@ -249,10 +249,9 @@ class AmesimPnch012(ThermodynamicVolumeComponent):
"""AMESim PNCH012 variable-volume pneumatic chamber. """AMESim PNCH012 variable-volume pneumatic chamber.
AMESim supplies four external volume and volume-rate inputs through the AMESim supplies four external volume and volume-rate inputs through the
chamber ports. The current public System XML contract has pneumatic ports chamber ports. Fixed/prescribed contributions remain available as SI
only, so this first public model exposes those external volume inputs as SI parameters, while connected moving-boundary components can now add live
parameters. This represents fixed or prescribed-volume PNCH012 cases and is volume and volume-rate values through the pneumatic connector contract.
not yet the full mechanical-coupled submodel.
""" """
MODEL_TYPE = "amesim_pnch012" MODEL_TYPE = "amesim_pnch012"
@@ -433,14 +432,29 @@ class AmesimPnch012(ThermodynamicVolumeComponent):
) -> "AmesimPnch012": ) -> "AmesimPnch012":
return cls(name=name, medium=medium, **dict(parameters)) return cls(name=name, medium=medium, **dict(parameters))
def connected_external_volume(self) -> float:
return sum(
getattr(getattr(self, port_name, None), "volume", 0.0)
for port_name in self.external_volumes
)
def connected_external_volume_rate(self) -> float:
return sum(
getattr(getattr(self, port_name, None), "volume_flow", 0.0)
for port_name in self.external_volume_rates
)
def total_volume(self) -> float: def total_volume(self) -> float:
minimum_volume = self.cvol0 / 100.0 minimum_volume = self.cvol0 / 100.0
return max(self.cvol0 + sum(self.external_volumes.values()), minimum_volume) return max(
self.cvol0 + sum(self.external_volumes.values()) + self.connected_external_volume(),
minimum_volume,
)
def total_volume_rate(self) -> float: def total_volume_rate(self) -> float:
if self.total_volume() <= self.cvol0 / 100.0: if self.total_volume() <= self.cvol0 / 100.0:
return 0.0 return 0.0
return sum(self.external_volume_rates.values()) return sum(self.external_volume_rates.values()) + self.connected_external_volume_rate()
def get_state_vector(self) -> list[float]: def get_state_vector(self) -> list[float]:
return self.state.as_vector() return self.state.as_vector()
+10
View File
@@ -204,6 +204,16 @@ class Component(ABC):
return None return None
def pneumatic_volume_outputs(self) -> Mapping[str, tuple[float, float]]:
"""Return directed ``volume``/``volume_flow`` values by pneumatic port.
Most pneumatic components contribute no external chamber volume. Moving
boundaries such as PNRP17 override this hook; the network resolver then
propagates the pair to the component connected at the same physical port.
"""
return {}
class DynamicComponent(Component): class DynamicComponent(Component):
state_size = 2 state_size = 2
+22
View File
@@ -87,6 +87,26 @@ class PortDefinition:
unit="J/kg", unit="J/kg",
order=30, order=30,
), ),
PortVariableDefinition(
"volume",
"signal",
"directed",
label="外部容积",
quantity="volume",
unit="m3",
result_visible=False,
order=40,
),
PortVariableDefinition(
"volume_flow",
"signal",
"directed",
label="外部容积变化率",
quantity="volume_flow",
unit="m3/s",
result_visible=False,
order=50,
),
), ),
) )
@@ -178,6 +198,8 @@ class PortState:
p: float = 0.0 p: float = 0.0
m_flow: float = 0.0 m_flow: float = 0.0
h_outflow: float = 0.0 h_outflow: float = 0.0
volume: float = 0.0
volume_flow: float = 0.0
signal: float = 0.0 signal: float = 0.0
x: float = 0.0 x: float = 0.0
v: float = 0.0 v: float = 0.0
@@ -0,0 +1,93 @@
from __future__ import annotations
from dataclasses import dataclass
from math import isfinite
from app.simulation.systems.network import Endpoint, SimulationNetwork
@dataclass(frozen=True)
class PneumaticVolumeDiagnostics:
propagated: int
output_ports: tuple[str, ...]
def as_dict(self) -> dict[str, object]:
return {
"propagated": self.propagated,
"outputPorts": list(self.output_ports),
}
class PneumaticVolumeResolver:
"""Propagate AMESim pneumatic external-volume connector variables."""
def __init__(self, network: SimulationNetwork) -> None:
self.network = network
self._connected_endpoint = self._build_connection_map()
self.last_diagnostics: PneumaticVolumeDiagnostics | None = None
def _build_connection_map(self) -> dict[Endpoint, Endpoint]:
result: dict[Endpoint, Endpoint] = {}
for connection in self.network.connections:
if connection.kind != "physical" or connection.domain != "pneumatic":
continue
first, second = connection.endpoints
result[first] = second
result[second] = first
return result
def solve(self) -> PneumaticVolumeDiagnostics:
for component in self.network.components.values():
for definition in component.port_definitions:
if definition.kind == "physical" and definition.domain == "pneumatic":
port = component.get_port(definition.name)
port.volume = 0.0
port.volume_flow = 0.0
outputs: dict[Endpoint, tuple[float, float]] = {}
for component in self.network.components.values():
for port_name, raw_values in component.pneumatic_volume_outputs().items():
port = component.get_port(port_name)
definition = port.definition
if (
definition is None
or definition.kind != "physical"
or definition.domain != "pneumatic"
):
raise ValueError(
f"Component {component.name} declares pneumatic volume output "
f"on non-pneumatic port {port_name}."
)
volume, volume_flow = (float(raw_values[0]), float(raw_values[1]))
if not isfinite(volume) or not isfinite(volume_flow):
raise ValueError(
f"Component {component.name}.{port_name} produced a non-finite "
"pneumatic volume value."
)
endpoint = Endpoint(component.name, port_name)
outputs[endpoint] = (volume, volume_flow)
port.volume = volume
port.volume_flow = volume_flow
propagated = 0
for endpoint, values in outputs.items():
connected = self._connected_endpoint.get(endpoint)
if connected is None:
continue
if connected in outputs:
raise ValueError(
"A pneumatic connection cannot contain two external-volume "
f"sources: {endpoint} and {connected}."
)
connected_port = self.network.components[connected.component].get_port(
connected.port
)
connected_port.volume, connected_port.volume_flow = values
propagated += 1
diagnostics = PneumaticVolumeDiagnostics(
propagated=propagated,
output_ports=tuple(sorted(str(endpoint) for endpoint in outputs)),
)
self.last_diagnostics = diagnostics
return diagnostics
+18 -1
View File
@@ -8,6 +8,7 @@ from typing import Literal
from app.simulation.core.base import DynamicComponent from app.simulation.core.base import DynamicComponent
from app.simulation.core.metadata import ResultVariableMetadata from app.simulation.core.metadata import ResultVariableMetadata
from app.simulation.solvers.algebraic import PressureFlowSolver from app.simulation.solvers.algebraic import PressureFlowSolver
from app.simulation.solvers.pneumatic_volume import PneumaticVolumeResolver
from app.simulation.solvers.solver import ODESolution, SolveIVPConfig, integrate_ode from app.simulation.solvers.solver import ODESolution, SolveIVPConfig, integrate_ode
from app.simulation.solvers.signal import SignalResolver from app.simulation.solvers.signal import SignalResolver
from app.simulation.solvers.stream import StreamResolver from app.simulation.solvers.stream import StreamResolver
@@ -233,6 +234,7 @@ class GenericFluidSystem:
self.network = network self.network = network
self.dynamic_components = network.dynamic_components() self.dynamic_components = network.dynamic_components()
self.pressure_flow_solver = PressureFlowSolver(network) self.pressure_flow_solver = PressureFlowSolver(network)
self.pneumatic_volume_resolver = PneumaticVolumeResolver(network)
self.signal_resolver = SignalResolver(network) self.signal_resolver = SignalResolver(network)
self.stream_resolver = StreamResolver(network) self.stream_resolver = StreamResolver(network)
self.algebraic_solve_count = 0 self.algebraic_solve_count = 0
@@ -240,6 +242,7 @@ class GenericFluidSystem:
self.max_algebraic_evaluations = 0 self.max_algebraic_evaluations = 0
self.max_stream_iterations = 0 self.max_stream_iterations = 0
self.signal_propagation_count = 0 self.signal_propagation_count = 0
self.pneumatic_volume_propagation_count = 0
def initial_state_vector(self) -> list[float]: def initial_state_vector(self) -> list[float]:
return self.network.initial_state_vector() return self.network.initial_state_vector()
@@ -253,8 +256,14 @@ class GenericFluidSystem:
for component in self.dynamic_components: for component in self.dynamic_components:
component.refresh_thermodynamic_ports() component.refresh_thermodynamic_ports()
algebraic = self.pressure_flow_solver.solve() algebraic = self.pressure_flow_solver.solve()
pneumatic_volume = self.pneumatic_volume_resolver.solve()
self.pneumatic_volume_propagation_count += pneumatic_volume.propagated
if pneumatic_volume.propagated:
for component in self.dynamic_components:
component.refresh_thermodynamic_ports()
algebraic = self.pressure_flow_solver.solve()
stream, connected_h = self.stream_resolver.solve() stream, connected_h = self.stream_resolver.solve()
self.algebraic_solve_count += 1 self.algebraic_solve_count += 1 + int(bool(pneumatic_volume.propagated))
self.max_algebraic_residual = max( self.max_algebraic_residual = max(
self.max_algebraic_residual, self.max_algebraic_residual,
algebraic.max_scaled_residual, algebraic.max_scaled_residual,
@@ -440,6 +449,14 @@ class GenericFluidSystem:
else None else None
), ),
}, },
"pneumaticVolume": {
"propagations": self.pneumatic_volume_propagation_count,
"last": (
self.pneumatic_volume_resolver.last_diagnostics.as_dict()
if self.pneumatic_volume_resolver.last_diagnostics is not None
else None
),
},
"stateCount": len(initial_state), "stateCount": len(initial_state),
"sampleCount": len(series["time"]), "sampleCount": len(series["time"]),
} }
+11 -11
View File
@@ -1,6 +1,6 @@
# AMESim 子模型公开组件迁移矩阵 # AMESim 子模型公开组件迁移矩阵
状态:21 个模型已在临时 AMESim 库完成第一版公开,持续校准中 状态:22 个模型已在临时 AMESim 库完成第一版公开,持续校准中
适用模型:`AmesimModels/test_mql.ame` / `app.simulation.examples.test_mql.system` 适用模型:`AmesimModels/test_mql.ame` / `app.simulation.examples.test_mql.system`
@@ -14,7 +14,7 @@
- 不把现有固定拓扑 `TestMqlSystem` 整体注册为公开拖拽组件。 - 不把现有固定拓扑 `TestMqlSystem` 整体注册为公开拖拽组件。
- 第一版公开只表示模型契约、目录、XML 编译和最小仿真链路已接通,不表示 AMESim 精确语义已完成复刻。 - 第一版公开只表示模型契约、目录、XML 编译和最小仿真链路已接通,不表示 AMESim 精确语义已完成复刻。
- 气动、标量信号和一维机械模型按当前求解链路能力公开;事件与跨域耦合继续保留明确限制。 - 气动、标量信号和一维机械模型按当前求解链路能力公开;PNRP17 已打通首个实时气动—机械跨域闭合,事件和更一般跨域 DAE 仍保留明确限制。
- 固定算例专用的校准原语留在 `examples/test_mql/`,不与公共组件目录混为同一实现。 - 固定算例专用的校准原语留在 `examples/test_mql/`,不与公共组件目录混为同一实现。
## 当前求解器能力边界 ## 当前求解器能力边界
@@ -22,18 +22,18 @@
`app.simulation` 当前支持: `app.simulation` 当前支持:
- 物理域:`pneumatic`、标量 `signal` 和一维 `mechanical`。 - 物理域:`pneumatic`、标量 `signal` 和一维 `mechanical`。
- 端口变量:气动 `p/m_flow/h_outflow`、信号 `signal`、机械 `x/v/f`。 - 端口变量:气动 `p/m_flow/h_outflow` 及定向 `volume/volume_flow`、信号 `signal`、机械 `x/v/f`。
- 流量符号:`m_flow > 0` 表示流入当前组件。 - 流量符号:`m_flow > 0` 表示流入当前组件。
- 网络方程:气动连接压力相等、流量和为零、组件压力-流量残差与 stream 焓传播;标量信号传播;机械 `x/v` 等值和 `f` 平衡。 - 网络方程:气动连接压力相等、流量和为零、组件压力-流量残差、stream 焓传播和移动边界容积传播;标量信号传播;机械 `x/v` 等值和 `f` 平衡。
- 动态:半显式 ODE,动态组件提供质量和内能状态导数。 - 动态:半显式 ODE,动态组件提供质量和内能状态导数。
- 暂不支持:完整事件系统、一般高指数 DAE、气动-机械实时跨域组件,以及从 PNGD00 自动批量导入其他气体。公共编译链已经支持“介质 + 物性计算方法”定义元件:`gi=0` 使用内置空气理想气体,`gi=1..99` 引用画布中的显式介质物性实例;介质定义内部通过 `property_model` 下拉接口选择计算方法。氦气已提供 Peng-Robinson 首版。 - 暂不支持:完整事件系统、一般高指数 DAE、任意多域连接变量,以及从 PNGD00 自动批量导入其他气体。公共编译链已经支持“介质 + 物性计算方法”定义元件:`gi=0` 使用内置空气理想气体,`gi=1..99` 引用画布中的显式介质物性实例;介质定义内部通过 `property_model` 下拉接口选择计算方法。氦气已提供 Peng-Robinson 首版。
## 子模型族矩阵 ## 子模型族矩阵
| AMESim 子模型 | 数量 | AMESim 角色 | 当前公开状态 | 建议目标 | 先决条件 / 限制 | | AMESim 子模型 | 数量 | AMESim 角色 | 当前公开状态 | 建议目标 | 先决条件 / 限制 |
| --- | ---: | --- | --- | --- | --- | | --- | ---: | --- | --- | --- | --- |
| `PNCH023` | 4 | 固定容积气室,带换热 | 第一版公开 | `amesim_pnch023`,`storage` | 已基于 `ThermodynamicVolumeComponent` 接入公开契约;仍需按 AMESim `cvol/extemp/kth/sth/gi` 复核质量、能量、换热方程。 | | `PNCH023` | 4 | 固定容积气室,带换热 | 第一版公开 | `amesim_pnch023`,`storage` | 已基于 `ThermodynamicVolumeComponent` 接入公开契约;仍需按 AMESim `cvol/extemp/kth/sth/gi` 复核质量、能量、换热方程。 |
| `PNCH012` | 8 | 变容气室,带换热 | 参数化第一版公开 | `amesim_pnch012`,`storage` | 已将 AMESim `vol1..4/dvol1..4` 外部体积输入映射为 SI 参数,支持固定/预设体积场景;实时机械耦合仍需跨域组件契约和闭合支持。 | | `PNCH012` | 8 | 变容气室,带换热 | 实时变容第一版公开 | `amesim_pnch012`,`storage` | 保留 `vol1..4/dvol1..4` SI 参数,并可从 PNRP17 经气动端口接收实时外部容积与变化率;已完成代表性联合仿真,仍需完整 `test_mql` baseline 校准。 |
| `PNOR001` | 8 | 常系数气动孔口 | 第一版公开 | `amesim_pnor001`,`flow` | 已接入代数组件公开契约;仍需按 AMESim `cq/area/Cv/Kv/flowset/gi` 复核双向流、零压差正则化和单位换算。 | | `PNOR001` | 8 | 常系数气动孔口 | 第一版公开 | `amesim_pnor001`,`flow` | 已接入代数组件公开契约;仍需按 AMESim `cq/area/Cv/Kv/flowset/gi` 复核双向流、零压差正则化和单位换算。 |
| `PNVO001` | 8 | 信号调制气动孔口 | 第一版公开 | `amesim_pnvo001`,`flow` | 已接入名为 `res` 的标量信号输入端口,可由 `amesim_step0` 驱动开度;精确事件语义和 AMESim baseline 仍留后续修模。固定开度变体 `amesim_pnvo001_fixed` 继续保留。 | | `PNVO001` | 8 | 信号调制气动孔口 | 第一版公开 | `amesim_pnvo001`,`flow` | 已接入名为 `res` 的标量信号输入端口,可由 `amesim_step0` 驱动开度;精确事件语义和 AMESim baseline 仍留后续修模。固定开度变体 `amesim_pnvo001_fixed` 继续保留。 |
| `PN3NODE2` | 8 | 三端气动节点,压力温度由 port 2 固定 | 第一版公开 | `amesim_pn3node2`,`junctions` | 已接入三端等压、流量守恒基础版;AMESim port 2 参考温压语义和 stream 混合仍需单独测试。 | | `PN3NODE2` | 8 | 三端气动节点,压力温度由 port 2 固定 | 第一版公开 | `amesim_pn3node2`,`junctions` | 已接入三端等压、流量守恒基础版;AMESim port 2 参考温压语义和 stream 混合仍需单独测试。 |
@@ -44,7 +44,7 @@
| `PNL0003` | 8 | C-R-C 动态管路 | 第一版公开 | `amesim_pnl0003`,`flow` | 已按公开契约接入两端四状态容积 + 中心摩擦流 + mode 2 换热项;大压差动态闭合和 AMESim baseline 误差仍留后续修模。 | | `PNL0003` | 8 | C-R-C 动态管路 | 第一版公开 | `amesim_pnl0003`,`flow` | 已按公开契约接入两端四状态容积 + 中心摩擦流 + mode 2 换热项;大压差动态闭合和 AMESim baseline 误差仍留后续修模。 |
| `PNPL01` | 16 | 零气动流源 | 第一版公开 | `amesim_pnpl01`,`boundary` | 当前实现一端零流边界,只约束端口质量流量为 0;压力源/外部边界语义留后续扩展。 | | `PNPL01` | 16 | 零气动流源 | 第一版公开 | `amesim_pnpl01`,`boundary` | 当前实现一端零流边界,只约束端口质量流量为 0;压力源/外部边界语义留后续扩展。 |
| `PNGD00` | 1 | 氦气气体定义 | 氦气 Peng-Robinson 首版公开 | `amesim_helium_medium`,`media` | 已映射源模型 `fluidType=12/eosType=6`;密度和压力使用 PR EOS,热量学暂用手册参考点的定比热闭合。完整压力相关残余焓、比热和真实气体临界流仍待状态相关物性接口。 | | `PNGD00` | 1 | 氦气气体定义 | 氦气 Peng-Robinson 首版公开 | `amesim_helium_medium`,`media` | 已映射源模型 `fluidType=12/eosType=6`;密度和压力使用 PR EOS,热量学暂用手册参考点的定比热闭合。完整压力相关残余焓、比热和真实气体临界流仍待状态相关物性接口。 |
| `PNRP17` | 8 | 气动活塞与移动体耦合 | 暂不公开 | 内部固定算例;后续跨域组件 | 需要跨域组件契约、实时气动-机械耦合,以及对应的事件、状态和闭合规则。 | | `PNRP17` | 8 | 气动活塞与移动体耦合 | 第一版公开 | `amesim_pnrp17`,`mechanical` | 已接入 1 个气动端口和 4 个一维机械端口,按 `dp/dr/x0/gi` 计算环形有效面积、扫掠容积、容积变化率和表压作用力;已与 PNCH012、双质量块跑通 System XML 联合仿真,完整事件语义和 AMESim baseline 仍待校准。 |
| `MECMAS21` | 10 | 一维平动质量 | 第一版公开 | `amesim_mecmas21`,`mechanical` | 已接入一维机械端口 `x/v/f`、双端质量状态和基本摩擦/限位项,并跑通零力源与信号力源最小 System XML;完整 AMESim 接触/事件语义仍留后续对齐。 | | `MECMAS21` | 10 | 一维平动质量 | 第一版公开 | `amesim_mecmas21`,`mechanical` | 已接入一维机械端口 `x/v/f`、双端质量状态和基本摩擦/限位项,并跑通零力源与信号力源最小 System XML;完整 AMESim 接触/事件语义仍留后续对齐。 |
| `LMECHN1` | 2 | 动态线性机械节点 | 第一版公开 | `amesim_lmechn1`,`mechanical` | 已接入 9 端一维机械节点、端口位移/速度等值和节点力平衡,并跑通 `FORC -> LMECHN1 -> MECMAS21` 最小 System XML;端口朝向/符号细节留后续 AMESim baseline 对齐。 | | `LMECHN1` | 2 | 动态线性机械节点 | 第一版公开 | `amesim_lmechn1`,`mechanical` | 已接入 9 端一维机械节点、端口位移/速度等值和节点力平衡,并跑通 `FORC -> LMECHN1 -> MECMAS21` 最小 System XML;端口朝向/符号细节留后续 AMESim baseline 对齐。 |
| `LSTP00A` | 8 | 弹性接触/端止动 | 第一版公开 | `amesim_lstp00a`,`mechanical` | 已接入两端一维机械端口、相对位移/速度接触力和最小 System XML 仿真;当前是连续罚函数基础版,完整 AMESim 事件/非光滑接触语义留后续 baseline 对齐。 | | `LSTP00A` | 8 | 弹性接触/端止动 | 第一版公开 | `amesim_lstp00a`,`mechanical` | 已接入两端一维机械端口、相对位移/速度接触力和最小 System XML 仿真;当前是连续罚函数基础版,完整 AMESim 事件/非光滑接触语义留后续 baseline 对齐。 |
@@ -82,13 +82,13 @@
- `PNVO001`:已第一版公开,保留固定开度变体。 - `PNVO001`:已第一版公开,保留固定开度变体。
- `UD00`:已第一版公开。 - `UD00`:已第一版公开。
- `MECMAS21`、`F000`、`FORC`、`LSTP00A`、`LMECHN1`:已第一版公开。 - `MECMAS21`、`F000`、`FORC`、`LSTP00A`、`LMECHN1`:已第一版公开。
- `PNRP17` - `PNRP17`:已第一版公开,并完成 PNCH012 + 双质量块跨域闭合测试。
当前已具备一对一标量信号传播、一维机械端口基础闭合、弹性接触基础件和 9 端机械节点;剩余跨域组件仍需要继续设计气动-机械耦合、XML 协议、前端连线兼容和最小闭合系统测试。 当前已具备一对一标量信号传播、一维机械端口基础闭合、弹性接触基础件、9 端机械节点,以及针对移动边界的气动容积定向传播与二次代数闭合。下一阶段重点是完整 `test_mql` 拓扑和 AMESim baseline 校准。
## 下一步执行建议 ## 下一步执行建议
1. 以 `app.simulation.components.amesim.library` 的 21 个模型为公开清单唯一来源。 1. 以 `app.simulation.components.amesim.library` 的 22 个模型为公开清单唯一来源。
2. 优先校准 `PNCH023 / PNOR001 / PN3NODE2 / P4NODE2 / PNL00R` 与动态管路的 AMESim baseline 误差。 2. 优先校准 `PNCH023 / PNOR001 / PN3NODE2 / P4NODE2 / PNL00R` 与动态管路的 AMESim baseline 误差。
3. 每次调整模型都同步补充目录校验、参数边界、System XML 编译和最小仿真测试。 3. 每次调整模型都同步补充目录校验、参数边界、System XML 编译和最小仿真测试。
4. 跨域能力下一步聚焦 `PNRP17` 或等价实时气动-机械耦合;在事件和闭合能力完成前保持暂不公开。 4. 用完整或代表性的 `test_mql` 画布校准 `PNRP17 + PNCH012` 的压力、力、位移和容积轨迹,并补齐事件边界语义。
+10
View File
@@ -3,6 +3,7 @@ import {
AmesimF000Symbol, AmesimF000Symbol,
AmesimForcSymbol, AmesimForcSymbol,
AmesimLmechn1Symbol, AmesimLmechn1Symbol,
AmesimPnrp17Symbol,
AmesimLstp00aSymbol, AmesimLstp00aSymbol,
AmesimMecmas21Symbol, AmesimMecmas21Symbol,
} from "./componentSymbols/mechanical"; } from "./componentSymbols/mechanical";
@@ -194,6 +195,7 @@ const symbolRegistry = {
amesim_mecmas21: { presentation: "framed", render: AmesimMecmas21Symbol }, amesim_mecmas21: { presentation: "framed", render: AmesimMecmas21Symbol },
amesim_lstp00a: { presentation: "framed", render: AmesimLstp00aSymbol }, amesim_lstp00a: { presentation: "framed", render: AmesimLstp00aSymbol },
amesim_lmechn1: { presentation: "framed", render: AmesimLmechn1Symbol }, amesim_lmechn1: { presentation: "framed", render: AmesimLmechn1Symbol },
amesim_pnrp17: { presentation: "framed", render: AmesimPnrp17Symbol },
amesim_ideal_air_medium: { amesim_ideal_air_medium: {
presentation: "bare", presentation: "bare",
render: AmesimIdealAirMediumSymbol, render: AmesimIdealAirMediumSymbol,
@@ -321,6 +323,14 @@ const symbolPortAnchors: Partial<
left: [7, 12, 17, 22, 27, 32, 37, 42].map((y) => ({ x: 5, y })), left: [7, 12, 17, 22, 27, 32, 37, 42].map((y) => ({ x: 5, y })),
right: [{ x: 59, y: 24 }], right: [{ x: 59, y: 24 }],
}, },
amesim_pnrp17: {
left: [
{ x: 2, y: 24 },
{ x: 2, y: 10 },
{ x: 2, y: 38 },
],
right: [{ x: 62, y: 10 }, { x: 62, y: 38 }],
},
}; };
function isRegisteredComponentSymbol(symbol: string): symbol is RegisteredComponentSymbol { function isRegisteredComponentSymbol(symbol: string): symbol is RegisteredComponentSymbol {
@@ -215,3 +215,55 @@ export function AmesimLmechn1Symbol() {
</SymbolSvg> </SymbolSvg>
); );
} }
export function AmesimPnrp17Symbol() {
return (
<SymbolSvg className="component-symbol-amesim-pnrp17" domain="mechanical">
<path
d="M2 24h17M2 10h11M2 38h11M51 10h11M51 38h11"
{...mechanicalStroke}
/>
<path
d="M7 6v8h6M7 34v8h6M57 6v8h-6M57 34v8h-6"
{...mechanicalStroke}
/>
<rect
fill={SYMBOL_FILL}
height="34"
rx="1"
stroke={SYMBOL_STROKE}
strokeWidth="2"
width="32"
x="18"
y="7"
/>
<path d="M38 8v32M38 24h12" {...mechanicalStroke} />
<path
d="M22 18h12M28 14l6 4-6 4"
fill="none"
stroke={SYMBOL_ACCENT}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.8"
/>
<path
d="M4 21v6M7 20v8M10 21v6"
fill="none"
stroke={SYMBOL_ACCENT}
strokeWidth="1.2"
/>
<text
dominantBaseline="central"
fill={SYMBOL_STROKE}
fontFamily="Arial, sans-serif"
fontSize="8"
fontWeight="600"
textAnchor="middle"
x="28"
y="32"
>
P
</text>
</SymbolSvg>
);
}
@@ -28,6 +28,7 @@ const symbolCases: SymbolCase[] = [
{ category: "mechanical", domain: "mechanical", modelType: "amesim_mecmas21" }, { category: "mechanical", domain: "mechanical", modelType: "amesim_mecmas21" },
{ category: "mechanical", domain: "mechanical", modelType: "amesim_lstp00a" }, { category: "mechanical", domain: "mechanical", modelType: "amesim_lstp00a" },
{ category: "mechanical", domain: "mechanical", modelType: "amesim_lmechn1" }, { category: "mechanical", domain: "mechanical", modelType: "amesim_lmechn1" },
{ category: "mechanical", domain: "mechanical", modelType: "amesim_pnrp17" },
]; ];
function physicalPort( function physicalPort(
@@ -18,16 +18,15 @@ class AmesimComponentMigrationMatrixTests(unittest.TestCase):
self.assertEqual(missing, []) self.assertEqual(missing, [])
def test_unsupported_domains_are_not_marked_as_public_candidates(self) -> None: def test_pnrp17_is_marked_as_the_first_public_cross_domain_component(self) -> None:
text = MATRIX_PATH.read_text(encoding="utf-8") text = MATRIX_PATH.read_text(encoding="utf-8")
unsupported = { row = next(
"PNRP17", line for line in text.splitlines() if "| `PNRP17` |" in line
} )
for family in unsupported: self.assertIn("第一版公开", row)
with self.subTest(family=family): self.assertIn("`amesim_pnrp17`", row)
row = next(line for line in text.splitlines() if f"`{family}`" in line) self.assertNotIn("暂不公开", row)
self.assertIn("暂不公开", row)
if __name__ == "__main__": if __name__ == "__main__":
+1
View File
@@ -27,6 +27,7 @@ from tests.test_system_xml_protocol import physical_port
GAS_INDEXED_MODEL_TYPES = ( GAS_INDEXED_MODEL_TYPES = (
"amesim_pnch023", "amesim_pnch023",
"amesim_pnch012", "amesim_pnch012",
"amesim_pnrp17",
"amesim_pnor001", "amesim_pnor001",
"amesim_pnvo001_fixed", "amesim_pnvo001_fixed",
"amesim_pnvo001", "amesim_pnvo001",
+1 -1
View File
@@ -69,7 +69,7 @@ class AmesimHeliumPengRobinsonMediumTests(unittest.TestCase):
def test_definition_maps_local_selector_to_amesim_fluid_and_eos_codes( def test_definition_maps_local_selector_to_amesim_fluid_and_eos_codes(
self, self,
) -> None: ) -> None:
self.assertEqual(LIBRARY.version, "0.2.0") self.assertEqual(LIBRARY.version, "0.3.0")
self.assertEqual(AMESIM_HELIUM_PENG_ROBINSON_PROPERTY_MODEL, 0) self.assertEqual(AMESIM_HELIUM_PENG_ROBINSON_PROPERTY_MODEL, 0)
self.assertEqual(AMESIM_HELIUM_PROPERTY_MODELS[0].value, 0) self.assertEqual(AMESIM_HELIUM_PROPERTY_MODELS[0].value, 0)
self.assertEqual(AMESIM_HELIUM_PROPERTY_MODELS[0].eos_type, 6) self.assertEqual(AMESIM_HELIUM_PROPERTY_MODELS[0].eos_type, 6)
+205
View File
@@ -0,0 +1,205 @@
from __future__ import annotations
from math import pi
import unittest
from app.main import (
ReactFlowProjectPayload,
build_reactflow_system_xml,
compile_reactflow_network,
run_system_xml_simulation,
)
from app.simulation.components.amesim.mechanical.pistons import AmesimPnrp17
from app.simulation.core.medium import IdealGasMedium
from app.system_xml import validate_system_xml_document
from tests.test_amesim_mechanical_xml import MECMAS21_DEFAULTS, mechanical_port
from tests.test_generic_system_xml_simulation import component_node, physical_edge
from tests.test_system_xml_protocol import physical_port
def _pneumatic_port(name: str, side: str) -> dict[str, str]:
return physical_port(name, "bidirectional", side)
def _zero_force_node(name: str, side: str) -> dict[str, object]:
return component_node(
name,
"amesim_f000",
[mechanical_port("port_1", side)],
)
def _mass_node(name: str) -> dict[str, object]:
parameters = dict(MECMAS21_DEFAULTS)
parameters["mass"] = 2.0
return component_node(
name,
"amesim_mecmas21",
[
mechanical_port("port_1", "left"),
mechanical_port("port_2", "right"),
],
parameters,
)
def pnrp17_coupled_project() -> ReactFlowProjectPayload:
return ReactFlowProjectPayload(
name="amesim-pnrp17-coupled-smoke",
nodes=[
component_node(
"chamber_1",
"amesim_pnch012",
[
_pneumatic_port("port_1", "left"),
_pneumatic_port("port_2", "right"),
_pneumatic_port("port_3", "left"),
_pneumatic_port("port_4", "right"),
],
{
"gi": 0.0,
"cvol0": 0.01,
"kth": 0.0,
"sth": 0.1,
"extemp": 300.0,
"p0": 200000.0,
"T0": 300.0,
"vol1": 0.0,
"vol2": 0.0,
"vol3": 0.0,
"vol4": 0.0,
"dvol1": 0.0,
"dvol2": 0.0,
"dvol3": 0.0,
"dvol4": 0.0,
},
),
component_node(
"piston_1",
"amesim_pnrp17",
[
_pneumatic_port("port_1", "left"),
mechanical_port("port_2", "right"),
mechanical_port("port_3", "left"),
mechanical_port("port_4", "left"),
mechanical_port("port_5", "right"),
],
{"gi": 0.0, "dp": 0.1, "dr": 0.02, "x0": 0.0},
),
_mass_node("piston_mass"),
_mass_node("cylinder_mass"),
_zero_force_node("piston_free", "left"),
_zero_force_node("cylinder_free", "right"),
_zero_force_node("piston_mass_free", "left"),
_zero_force_node("cylinder_mass_free", "right"),
component_node(
"boundary_1",
"amesim_pnpl01",
[_pneumatic_port("port_1", "left")],
),
component_node(
"boundary_2",
"amesim_pnpl01",
[_pneumatic_port("port_1", "left")],
),
component_node(
"boundary_4",
"amesim_pnpl01",
[_pneumatic_port("port_1", "left")],
),
],
edges=[
physical_edge("edge-pneumatic", "chamber_1", "port_3", "piston_1", "port_1"),
physical_edge("edge-boundary-1", "chamber_1", "port_1", "boundary_1", "port_1"),
physical_edge("edge-boundary-2", "chamber_1", "port_2", "boundary_2", "port_1"),
physical_edge("edge-boundary-4", "chamber_1", "port_4", "boundary_4", "port_1"),
physical_edge("edge-piston-mass", "piston_1", "port_2", "piston_mass", "port_1"),
physical_edge("edge-piston-free", "piston_1", "port_5", "piston_free", "port_1"),
physical_edge("edge-piston-mass-free", "piston_mass", "port_2", "piston_mass_free", "port_1"),
physical_edge("edge-cylinder-mass", "piston_1", "port_3", "cylinder_mass", "port_1"),
physical_edge("edge-cylinder-free", "piston_1", "port_4", "cylinder_free", "port_1"),
physical_edge("edge-cylinder-mass-free", "cylinder_mass", "port_2", "cylinder_mass_free", "port_1"),
],
simulation={
"t_start": 0.0,
"t_stop": 0.005,
"step": 0.0025,
"max_step": 0.0005,
"method": "BDF",
},
)
class AmesimPnrp17Tests(unittest.TestCase):
def test_component_equations_match_pnrp17_geometry_and_signs(self) -> None:
medium = IdealGasMedium()
piston = AmesimPnrp17(
"piston_1",
medium,
gi=0.0,
dp=0.2,
dr=0.1,
x0=0.05,
)
piston.port_1.p = 300000.0
piston.port_2.x = piston.port_5.x = 0.3
piston.port_2.v = piston.port_5.v = 0.4
piston.port_3.x = piston.port_4.x = 0.1
piston.port_3.v = piston.port_4.v = -0.2
pressure_force = piston.pressure_force
piston.port_5.f = 10.0
piston.port_2.f = -pressure_force - piston.port_5.f
piston.port_4.f = 20.0
piston.port_3.f = pressure_force - piston.port_4.f
expected_area = pi * (0.2**2 - 0.1**2) / 4.0
self.assertAlmostEqual(piston.effective_area, expected_area)
self.assertAlmostEqual(piston.chamber_length, 0.25)
self.assertAlmostEqual(piston.chamber_volume, expected_area * 0.25)
self.assertAlmostEqual(piston.chamber_volume_flow, expected_area * 0.6)
for residual in piston.pressure_flow_equation_residuals():
self.assertAlmostEqual(residual.value, 0.0, msg=residual.id)
self.assertEqual(
piston.pneumatic_volume_outputs(),
{"port_1": (piston.chamber_volume, piston.chamber_volume_flow)},
)
def test_rod_diameter_cannot_exceed_piston_diameter(self) -> None:
with self.assertRaisesRegex(ValueError, "dr must not exceed"):
AmesimPnrp17(
"piston_1",
IdealGasMedium(),
dp=0.05,
dr=0.06,
)
def test_cross_domain_system_xml_compiles_and_simulates(self) -> None:
project = pnrp17_coupled_project()
xml = build_reactflow_system_xml(project)
report = validate_system_xml_document(xml)
self.assertTrue(report.valid, report.as_dict())
network = compile_reactflow_network(project)
self.assertTrue(network.pressure_flow_structure_dict()["isSquare"])
result = run_system_xml_simulation(xml)
self.assertTrue(result["success"], result["message"])
self.assertEqual(result["series"]["time"], [0.0, 0.0025, 0.005])
piston_volume = result["series"]["piston_1.volume"]
chamber_volume = result["series"]["chamber_1.vol"]
self.assertAlmostEqual(piston_volume[0], 0.0, places=12)
self.assertGreater(piston_volume[-1], piston_volume[0])
for chamber_value, piston_value in zip(chamber_volume, piston_volume):
self.assertAlmostEqual(chamber_value, 0.01 + piston_value, places=10)
self.assertGreater(result["series"]["piston_mass.x"][-1], 0.0)
self.assertLess(result["series"]["cylinder_mass.x"][-1], 0.0)
self.assertGreater(result["diagnostics"]["pneumaticVolume"]["propagations"], 0)
self.assertEqual(
result["diagnostics"]["pneumaticVolume"]["last"]["outputPorts"],
["piston_1.port_1"],
)
if __name__ == "__main__":
unittest.main()
+8 -1
View File
@@ -49,7 +49,7 @@ class ComponentCatalogTests(unittest.TestCase):
self.assertEqual(library["label"], "AMESim 组件库") self.assertEqual(library["label"], "AMESim 组件库")
self.assertEqual([category["id"] for category in library["categories"]], ["media", "storage", "flow", "junctions", "boundary", "signals", "mechanical"]) self.assertEqual([category["id"] for category in library["categories"]], ["media", "storage", "flow", "junctions", "boundary", "signals", "mechanical"])
self.assertEqual(set(components), {"amesim_ideal_air_medium", "amesim_helium_medium", "amesim_pnpl01", "amesim_step0", "amesim_ud00", "amesim_f000", "amesim_forc", "amesim_mecmas21", "amesim_lstp00a", "amesim_lmechn1", "amesim_pnch023", "amesim_pnch012", "amesim_pnor001", "amesim_pnvo001_fixed", "amesim_pnvo001", "amesim_pnl00r", "amesim_pnl0001", "amesim_pnl0002", "amesim_pnl0003", "amesim_pn3node2", "amesim_p4node2"}) self.assertEqual(set(components), {"amesim_ideal_air_medium", "amesim_helium_medium", "amesim_pnpl01", "amesim_step0", "amesim_ud00", "amesim_f000", "amesim_forc", "amesim_mecmas21", "amesim_lstp00a", "amesim_lmechn1", "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"})
for model_type, component in components.items(): for model_type, component in components.items():
with self.subTest(model_type=model_type): with self.subTest(model_type=model_type):
self.assertEqual(component["symbol"], model_type) self.assertEqual(component["symbol"], model_type)
@@ -180,6 +180,13 @@ class ComponentCatalogTests(unittest.TestCase):
} }
self.assertEqual(components["amesim_lmechn1"]["category"]["id"], "mechanical") self.assertEqual(components["amesim_lmechn1"]["category"]["id"], "mechanical")
self.assertEqual([port["name"] for port in components["amesim_lmechn1"]["ports"]], [f"port_{index}" for index in range(1, 10)]) self.assertEqual([port["name"] for port in components["amesim_lmechn1"]["ports"]], [f"port_{index}" for index in range(1, 10)])
pnrp_parameters = {
parameter["name"]: parameter
for parameter in components["amesim_pnrp17"]["parameters"]
}
self.assertEqual(components["amesim_pnrp17"]["category"]["id"], "mechanical")
self.assertEqual([port["name"] for port in components["amesim_pnrp17"]["ports"]], ["port_1", "port_3", "port_4", "port_2", "port_5"])
self.assertEqual(pnrp_parameters["dp"]["unit"], "m")
self.assertEqual(lmechn_parameters["v1"]["maximum"], 8.0) self.assertEqual(lmechn_parameters["v1"]["maximum"], 8.0)
self.assertEqual(components["amesim_pnvo001"]["category"]["id"], "flow") self.assertEqual(components["amesim_pnvo001"]["category"]["id"], "flow")
self.assertEqual([port["name"] for port in components["amesim_pnvo001"]["ports"]], ["res", "port_2", "port_3"]) self.assertEqual([port["name"] for port in components["amesim_pnvo001"]["ports"]], ["res", "port_2", "port_3"])
+2
View File
@@ -28,6 +28,8 @@ class ComponentInterfaceTests(unittest.TestCase):
("p", "effort", "equal"), ("p", "effort", "equal"),
("m_flow", "flow", "sumToZero"), ("m_flow", "flow", "sumToZero"),
("h_outflow", "stream", "streamMix"), ("h_outflow", "stream", "streamMix"),
("volume", "signal", "directed"),
("volume_flow", "signal", "directed"),
], ],
) )
+2
View File
@@ -41,6 +41,7 @@ class ComponentRegistryTests(unittest.TestCase):
"amesim_mecmas21", "amesim_mecmas21",
"amesim_lstp00a", "amesim_lstp00a",
"amesim_lmechn1", "amesim_lmechn1",
"amesim_pnrp17",
"amesim_pnch023", "amesim_pnch023",
"amesim_pnch012", "amesim_pnch012",
"amesim_pnor001", "amesim_pnor001",
@@ -78,6 +79,7 @@ class ComponentRegistryTests(unittest.TestCase):
self.assertEqual(models["amesim_mecmas21"].library.id, "amesim") self.assertEqual(models["amesim_mecmas21"].library.id, "amesim")
self.assertEqual(models["amesim_lstp00a"].library.id, "amesim") self.assertEqual(models["amesim_lstp00a"].library.id, "amesim")
self.assertEqual(models["amesim_lmechn1"].library.id, "amesim") self.assertEqual(models["amesim_lmechn1"].library.id, "amesim")
self.assertEqual(models["amesim_pnrp17"].library.id, "amesim")
self.assertEqual(models["amesim_pnch023"].library.id, "amesim") self.assertEqual(models["amesim_pnch023"].library.id, "amesim")
self.assertEqual(models["amesim_pnch012"].library.id, "amesim") self.assertEqual(models["amesim_pnch012"].library.id, "amesim")
self.assertEqual(models["amesim_pnor001"].library.id, "amesim") self.assertEqual(models["amesim_pnor001"].library.id, "amesim")