feat: integrate AMESim media models and editor UI

This commit is contained in:
ljz committed 2026-07-31 23:36:58 +08:00
1 parent 3e4f466114
commit e7177ab03e
181 files changed
+7042 -25270

No files matched your search

@@ -3,6 +3,10 @@ from __future__ import annotations
from collections.abc import Mapping
from math import isclose, sqrt
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
@@ -10,7 +14,7 @@ from app.simulation.core.metadata import (
ParameterDefinition,
ResultVariableDefinition,
)
from app.simulation.core.medium import IdealGasMedium
from app.simulation.core.medium import GasMedium
from app.simulation.core.ports import PortDefinition
@@ -29,6 +33,7 @@ class AmesimPnor001(AlgebraicComponent):
PortDefinition.pneumatic("port_2", nominal_role="bidirectional"),
)
PARAMETERS = (
AMESIM_GAS_INDEX_PARAMETER,
ParameterDefinition(
"cq",
0.72,
@@ -37,6 +42,7 @@ class AmesimPnor001(AlgebraicComponent):
unit="",
minimum=1.0e-10,
maximum=1.0,
description="孔口实际质量流量相对于理想可压缩流量的无量纲修正系数。",
),
ParameterDefinition(
"area",
@@ -46,6 +52,7 @@ class AmesimPnor001(AlgebraicComponent):
unit="m2",
minimum=0.0,
maximum=1.0,
description="选择 Cq/面积方式时用于流量计算的有效孔口面积。",
),
ParameterDefinition(
"Cv",
@@ -54,6 +61,7 @@ class AmesimPnor001(AlgebraicComponent):
quantity="dimensionless",
unit="",
minimum=0.0,
description="选择 Cv 方式时使用的英制流量系数。",
),
ParameterDefinition(
"Kv",
@@ -62,15 +70,7 @@ class AmesimPnor001(AlgebraicComponent):
quantity="dimensionless",
unit="",
minimum=0.0,
),
ParameterDefinition(
"gi",
1.0,
label="气体类型索引",
quantity="dimensionless",
unit="",
minimum=1.0,
maximum=99.0,
description="选择 Kv 方式时使用的公制流量系数。",
),
ParameterDefinition(
"flowset",
@@ -80,6 +80,7 @@ class AmesimPnor001(AlgebraicComponent):
unit="",
minimum=1.0,
maximum=3.0,
description="流量参数方式:1 使用 Cq 和面积,2 使用 Cv,3 使用 Kv。",
),
)
RESULT_VARIABLES = (
@@ -115,7 +116,7 @@ class AmesimPnor001(AlgebraicComponent):
def __init__(
self,
name: str,
medium: IdealGasMedium,
medium: GasMedium,
*,
cq: float = 0.72,
area: float = 5.0e-6,
@@ -140,7 +141,7 @@ class AmesimPnor001(AlgebraicComponent):
self.area = float(area)
self.Cv = float(Cv)
self.Kv = float(Kv)
self.gi = self._integer_parameter("gi", gi)
self.gi = normalize_amesim_gas_index(gi)
self.flowset = self._integer_parameter("flowset", flowset)
if self.flowset not in {1, 2, 3}:
raise ValueError("PNOR001 flowset must be 1, 2, or 3.")
@@ -163,7 +164,7 @@ class AmesimPnor001(AlgebraicComponent):
cls,
*,
name: str,
medium: IdealGasMedium,
medium: GasMedium,
parameters: Mapping[str, float],
) -> AmesimPnor001:
return cls(
@@ -206,7 +207,7 @@ class AmesimPnor001(AlgebraicComponent):
def _upstream_temperature(self, port_name: str) -> float:
port = self.get_port(port_name)
if port.h_outflow > 0.0:
return max(port.h_outflow / self.medium.cp_ref, 1.0)
return max(self.medium.temperature_from_enthalpy(port.h_outflow), 1.0)
return self.medium.T_ref
def mass_flow(self, p_1: float, p_2: float) -> float:
@@ -321,6 +322,7 @@ class AmesimPnvo001FixedOpening(AlgebraicComponent):
PortDefinition.pneumatic("port_3", nominal_role="bidirectional"),
)
PARAMETERS = (
AMESIM_GAS_INDEX_PARAMETER,
ParameterDefinition(
"cq",
0.72,
@@ -329,6 +331,7 @@ class AmesimPnvo001FixedOpening(AlgebraicComponent):
unit="",
minimum=1.0e-10,
maximum=1.0,
description="孔口实际质量流量相对于理想可压缩流量的无量纲修正系数。",
),
ParameterDefinition(
"area0",
@@ -338,6 +341,7 @@ class AmesimPnvo001FixedOpening(AlgebraicComponent):
unit="m2",
minimum=0.0,
maximum=1.0,
description="阀门完全开启时的最大有效孔口面积。",
),
ParameterDefinition(
"Cv",
@@ -346,6 +350,7 @@ class AmesimPnvo001FixedOpening(AlgebraicComponent):
quantity="dimensionless",
unit="",
minimum=0.0,
description="选择 Cv 方式时使用的最大英制流量系数。",
),
ParameterDefinition(
"Kv",
@@ -354,15 +359,7 @@ class AmesimPnvo001FixedOpening(AlgebraicComponent):
quantity="dimensionless",
unit="",
minimum=0.0,
),
ParameterDefinition(
"gi",
1.0,
label="气体类型索引",
quantity="dimensionless",
unit="",
minimum=1.0,
maximum=99.0,
description="选择 Kv 方式时使用的最大公制流量系数。",
),
ParameterDefinition(
"flowset",
@@ -372,6 +369,7 @@ class AmesimPnvo001FixedOpening(AlgebraicComponent):
unit="",
minimum=1.0,
maximum=3.0,
description="流量参数方式:1 使用 Cq 和面积,2 使用 Cv,3 使用 Kv。",
),
ParameterDefinition(
"opening",
@@ -381,6 +379,7 @@ class AmesimPnvo001FixedOpening(AlgebraicComponent):
unit="",
minimum=0.0,
maximum=1.0,
description="固定的归一化阀门开度;0 表示关闭,1 表示完全开启。",
),
)
RESULT_VARIABLES = (
@@ -424,7 +423,7 @@ class AmesimPnvo001FixedOpening(AlgebraicComponent):
def __init__(
self,
name: str,
medium: IdealGasMedium,
medium: GasMedium,
*,
cq: float = 0.72,
area0: float = 5.0e-6,
@@ -451,7 +450,7 @@ class AmesimPnvo001FixedOpening(AlgebraicComponent):
self.area0 = float(area0)
self.Cv = float(Cv)
self.Kv = float(Kv)
self.gi = self._integer_parameter("gi", gi)
self.gi = normalize_amesim_gas_index(gi)
self.flowset = self._integer_parameter("flowset", flowset)
if self.flowset not in {1, 2, 3}:
raise ValueError("PNVO001 fixed-opening flowset must be 1, 2, or 3.")
@@ -475,7 +474,7 @@ class AmesimPnvo001FixedOpening(AlgebraicComponent):
cls,
*,
name: str,
medium: IdealGasMedium,
medium: GasMedium,
parameters: Mapping[str, float],
) -> AmesimPnvo001FixedOpening:
return cls(
@@ -509,7 +508,7 @@ class AmesimPnvo001FixedOpening(AlgebraicComponent):
def _upstream_temperature(self, port_name: str) -> float:
port = self.get_port(port_name)
if port.h_outflow > 0.0:
return max(port.h_outflow / self.medium.cp_ref, 1.0)
return max(self.medium.temperature_from_enthalpy(port.h_outflow), 1.0)
return self.medium.T_ref
def mass_flow(self, p_2: float, p_3: float) -> float:
@@ -621,11 +620,11 @@ class AmesimPnvo001SignalOpening(AmesimPnvo001FixedOpening):
PortDefinition.pneumatic("port_3", nominal_role="bidirectional"),
)
PARAMETERS = (
AMESIM_GAS_INDEX_PARAMETER,
ParameterDefinition("cq", 0.72, label="流量系数 Cq", quantity="dimensionless", unit="", minimum=1.0e-10, maximum=1.0),
ParameterDefinition("area0", 5.0e-6, label="最大孔口面积", quantity="area", unit="m2", minimum=0.0, maximum=1.0),
ParameterDefinition("Cv", 0.5, label="最大流量系数 Cv", quantity="dimensionless", unit="", minimum=0.0),
ParameterDefinition("Kv", 0.4, label="最大流量系数 Kv", quantity="dimensionless", unit="", minimum=0.0),
ParameterDefinition("gi", 1.0, label="气体类型索引", quantity="dimensionless", unit="", minimum=1.0, maximum=99.0),
ParameterDefinition("flowset", 1.0, label="流量系数设置", quantity="dimensionless", unit="", minimum=1.0, maximum=3.0),
ParameterDefinition("opening0", 1.0, label="初始开度", quantity="dimensionless", unit="", minimum=0.0, maximum=1.0),
)
@@ -646,7 +645,7 @@ class AmesimPnvo001SignalOpening(AmesimPnvo001FixedOpening):
def __init__(
self,
name: str,
medium: IdealGasMedium,
medium: GasMedium,
*,
cq: float = 0.72,
area0: float = 5.0e-6,
@@ -673,7 +672,7 @@ class AmesimPnvo001SignalOpening(AmesimPnvo001FixedOpening):
self.area0 = float(area0)
self.Cv = float(Cv)
self.Kv = float(Kv)
self.gi = self._integer_parameter("gi", gi)
self.gi = normalize_amesim_gas_index(gi)
self.flowset = self._integer_parameter("flowset", flowset)
if self.flowset not in {1, 2, 3}:
raise ValueError("PNVO001 signal-opening flowset must be 1, 2, or 3.")
@@ -691,7 +690,7 @@ class AmesimPnvo001SignalOpening(AmesimPnvo001FixedOpening):
cls,
*,
name: str,
medium: IdealGasMedium,
medium: GasMedium,
parameters: Mapping[str, float],
) -> "AmesimPnvo001SignalOpening":
return cls(name=name, medium=medium, **dict(parameters))
+38 -51
View File
@@ -3,6 +3,10 @@ from __future__ import annotations
from collections.abc import Mapping
from math import isclose, log10, pi, sqrt
from app.simulation.components.amesim.gases import (
AMESIM_GAS_INDEX_PARAMETER,
normalize_amesim_gas_index,
)
from app.simulation.core.base import AlgebraicComponent, DynamicComponent, ThermodynamicVolumeComponent
from app.simulation.core.catalog import ComponentDisplaySpec, PortDisplaySpec
from app.simulation.core.equations import EquationResidual
@@ -11,7 +15,7 @@ from app.simulation.core.metadata import (
ResultVariableDefinition,
THERMODYNAMIC_VOLUME_RESULT_VARIABLES,
)
from app.simulation.core.medium import IdealGasMedium, ThermodynamicProperties
from app.simulation.core.medium import GasMedium, ThermodynamicProperties
from app.simulation.core.ports import PortDefinition
from app.simulation.core.state import VolumeState
@@ -31,6 +35,7 @@ class AmesimPnl00r(AlgebraicComponent):
PortDefinition.pneumatic("port_2", nominal_role="bidirectional"),
)
PARAMETERS = (
AMESIM_GAS_INDEX_PARAMETER,
ParameterDefinition(
"diam",
0.01,
@@ -39,6 +44,7 @@ class AmesimPnl00r(AlgebraicComponent):
unit="m",
minimum=0.0,
minimum_exclusive=True,
description="管路的有效内径,用于计算流通面积和摩擦压降。",
),
ParameterDefinition(
"le",
@@ -48,6 +54,7 @@ class AmesimPnl00r(AlgebraicComponent):
unit="m",
minimum=0.0,
minimum_exclusive=True,
description="参与摩擦压降计算的管路有效长度。",
),
ParameterDefinition(
"rr",
@@ -57,15 +64,7 @@ class AmesimPnl00r(AlgebraicComponent):
unit="",
minimum=0.0,
maximum=0.1,
),
ParameterDefinition(
"gi",
1.0,
label="气体类型索引",
quantity="dimensionless",
unit="",
minimum=1.0,
maximum=99.0,
description="管壁绝对粗糙度与管径之比,用于计算 Darcy 摩擦因子。",
),
)
RESULT_VARIABLES = (
@@ -117,7 +116,7 @@ class AmesimPnl00r(AlgebraicComponent):
def __init__(
self,
name: str,
medium: IdealGasMedium,
medium: GasMedium,
*,
diam: float = 0.01,
le: float = 1.0,
@@ -130,7 +129,7 @@ class AmesimPnl00r(AlgebraicComponent):
self.diam = float(diam)
self.le = float(le)
self.rr = float(rr)
self.gi = self._integer_parameter("gi", gi)
self.gi = normalize_amesim_gas_index(gi)
self.area = pi * self.diam * self.diam / 4.0
initial_h = medium.specific_enthalpy(medium.T_ref)
@@ -151,7 +150,7 @@ class AmesimPnl00r(AlgebraicComponent):
cls,
*,
name: str,
medium: IdealGasMedium,
medium: GasMedium,
parameters: Mapping[str, float],
) -> AmesimPnl00r:
return cls(
@@ -166,22 +165,11 @@ class AmesimPnl00r(AlgebraicComponent):
def _port_temperature(self, port_name: str) -> float:
port = self.get_port(port_name)
if port.h_outflow > 0.0:
return max(port.h_outflow / self.medium.cp_ref, 1.0)
return max(self.medium.temperature_from_enthalpy(port.h_outflow), 1.0)
return self.medium.T_ref
@staticmethod
def _dynamic_viscosity(temperature_k: float) -> float:
if temperature_k <= 0.0:
raise ValueError("temperature_k must be positive")
reference_temperature = 293.15
reference_viscosity = 1.82e-5
sutherland_constant = 110.4
return (
reference_viscosity
* (temperature_k / reference_temperature) ** 1.5
* (reference_temperature + sutherland_constant)
/ (temperature_k + sutherland_constant)
)
def _dynamic_viscosity(self, temperature_k: float) -> float:
return self.medium.dynamic_viscosity(temperature_k)
def reynolds_number(self, mass_flow: float, temperature: float) -> float:
viscosity = self._dynamic_viscosity(temperature)
@@ -322,6 +310,7 @@ class AmesimPnl0001(ThermodynamicVolumeComponent):
PortDefinition.pneumatic("port_2", nominal_role="bidirectional"),
)
PARAMETERS = (
AMESIM_GAS_INDEX_PARAMETER,
ParameterDefinition(
"diam",
0.01,
@@ -330,6 +319,7 @@ class AmesimPnl0001(ThermodynamicVolumeComponent):
unit="m",
minimum=0.0,
minimum_exclusive=True,
description="管路的有效内径,用于计算流通面积、储气容积和摩擦压降。",
),
ParameterDefinition(
"le",
@@ -339,6 +329,7 @@ class AmesimPnl0001(ThermodynamicVolumeComponent):
unit="m",
minimum=0.0,
minimum_exclusive=True,
description="管路的有效长度,用于计算储气容积、换热面积和摩擦压降。",
),
ParameterDefinition(
"rr",
@@ -348,6 +339,7 @@ class AmesimPnl0001(ThermodynamicVolumeComponent):
unit="",
minimum=0.0,
maximum=0.1,
description="管壁绝对粗糙度与管径之比,用于计算 Darcy 摩擦因子。",
),
ParameterDefinition(
"k",
@@ -358,6 +350,7 @@ class AmesimPnl0001(ThermodynamicVolumeComponent):
minimum=0.0,
minimum_exclusive=True,
maximum=2.0,
description="AMESim 管路热力学配置使用的多方指数。",
),
ParameterDefinition(
"kth",
@@ -366,6 +359,7 @@ class AmesimPnl0001(ThermodynamicVolumeComponent):
quantity="heat_transfer_coefficient",
unit="W/(m2*K)",
minimum=0.0,
description="管内气体与外部环境之间的对流换热系数。",
),
ParameterDefinition(
"extemp",
@@ -375,15 +369,7 @@ class AmesimPnl0001(ThermodynamicVolumeComponent):
unit="K",
minimum=0.0,
minimum_exclusive=True,
),
ParameterDefinition(
"gi",
1.0,
label="气体类型索引",
quantity="dimensionless",
unit="",
minimum=1.0,
maximum=99.0,
description="管路外部环境的绝对温度,用于计算换热功率。",
),
ParameterDefinition(
"mode",
@@ -393,6 +379,7 @@ class AmesimPnl0001(ThermodynamicVolumeComponent):
unit="",
minimum=1.0,
maximum=2.0,
description="热模型选择:1 为绝热,2 为按换热系数计算环境换热。",
),
ParameterDefinition(
"p0",
@@ -402,6 +389,7 @@ class AmesimPnl0001(ThermodynamicVolumeComponent):
unit="Pa",
minimum=0.0,
minimum_exclusive=True,
description="仿真开始时管内气体的绝对压力。",
),
ParameterDefinition(
"T0",
@@ -411,6 +399,7 @@ class AmesimPnl0001(ThermodynamicVolumeComponent):
unit="K",
minimum=0.0,
minimum_exclusive=True,
description="仿真开始时管内气体的绝对温度。",
),
)
RESULT_VARIABLES = THERMODYNAMIC_VOLUME_RESULT_VARIABLES + (
@@ -462,7 +451,7 @@ class AmesimPnl0001(ThermodynamicVolumeComponent):
def __init__(
self,
name: str,
medium: IdealGasMedium,
medium: GasMedium,
*,
diam: float = 0.01,
le: float = 1.0,
@@ -497,14 +486,14 @@ class AmesimPnl0001(ThermodynamicVolumeComponent):
self.k = float(k)
self.kth = float(kth)
self.extemp = float(extemp)
self.gi = self._integer_parameter("gi", gi)
self.gi = normalize_amesim_gas_index(gi)
self.mode = self._integer_parameter("mode", mode)
self.p0 = float(p0)
self.T0 = float(T0)
self.area = pi * self.diam * self.diam / 4.0
self.volume = self.area * self.le
self.exchange_area = pi * self.diam * self.le
m0 = self.p0 * self.volume / (medium.R_gas * self.T0)
m0 = medium.density(self.p0, self.T0) * self.volume
U0 = m0 * medium.specific_internal_energy(self.T0)
self.state = VolumeState(m=m0, U=U0)
initial_h = medium.specific_enthalpy(self.T0)
@@ -527,7 +516,7 @@ class AmesimPnl0001(ThermodynamicVolumeComponent):
cls,
*,
name: str,
medium: IdealGasMedium,
medium: GasMedium,
parameters: Mapping[str, float],
) -> "AmesimPnl0001":
return cls(
@@ -566,9 +555,8 @@ class AmesimPnl0001(ThermodynamicVolumeComponent):
return 0.0
return self.kth * self.exchange_area * (self.extemp - temperature)
@staticmethod
def _dynamic_viscosity(temperature_k: float) -> float:
return AmesimPnl00r._dynamic_viscosity(temperature_k)
def _dynamic_viscosity(self, temperature_k: float) -> float:
return self.medium.dynamic_viscosity(temperature_k)
def reynolds_number(self, mass_flow: float, temperature: float) -> float:
viscosity = self._dynamic_viscosity(temperature)
@@ -745,7 +733,7 @@ class AmesimPnl0002(AmesimPnl0001):
cls,
*,
name: str,
medium: IdealGasMedium,
medium: GasMedium,
parameters: Mapping[str, float],
) -> "AmesimPnl0002":
return cls(
@@ -943,7 +931,7 @@ class AmesimPnl0003(DynamicComponent):
def __init__(
self,
name: str,
medium: IdealGasMedium,
medium: GasMedium,
*,
diam: float = 0.01,
le: float = 1.0,
@@ -982,7 +970,7 @@ class AmesimPnl0003(DynamicComponent):
self.k = float(k)
self.kth = float(kth)
self.extemp = float(extemp)
self.gi = AmesimPnl0001._integer_parameter("gi", gi)
self.gi = normalize_amesim_gas_index(gi)
self.mode = AmesimPnl0001._integer_parameter("mode", mode)
self.area = pi * self.diam * self.diam / 4.0
self.volume = self.area * self.le
@@ -1004,13 +992,13 @@ class AmesimPnl0003(DynamicComponent):
cls,
*,
name: str,
medium: IdealGasMedium,
medium: GasMedium,
parameters: Mapping[str, float],
) -> "AmesimPnl0003":
return cls(name=name, medium=medium, **dict(parameters))
def _initial_state(self, pressure: float, temperature: float) -> VolumeState:
mass = pressure * self.compliance_volume / (self.medium.R_gas * temperature)
mass = self.medium.density(pressure, temperature) * self.compliance_volume
return VolumeState(m=mass, U=mass * self.medium.specific_internal_energy(temperature))
def get_state_vector(self) -> list[float]:
@@ -1040,9 +1028,8 @@ class AmesimPnl0003(DynamicComponent):
def refresh_thermodynamic_ports(self) -> tuple[ThermodynamicProperties, ThermodynamicProperties]:
return self.properties_1(), self.properties_2()
@staticmethod
def _dynamic_viscosity(temperature_k: float) -> float:
return AmesimPnl00r._dynamic_viscosity(temperature_k)
def _dynamic_viscosity(self, temperature_k: float) -> float:
return self.medium.dynamic_viscosity(temperature_k)
def reynolds_number(self, mass_flow: float, temperature: float) -> float:
viscosity = self._dynamic_viscosity(temperature)
+264
View File
@@ -0,0 +1,264 @@
from __future__ import annotations
from collections.abc import Iterable, Mapping
from dataclasses import dataclass
from math import isclose, isfinite
from types import MappingProxyType
from app.simulation.core.metadata import ParameterDefinition
from app.simulation.core.medium import GasMedium
AMESIM_BUILTIN_AIR_GAS_INDEX = 0
AMESIM_DEFAULT_GAS_INDEX = AMESIM_BUILTIN_AIR_GAS_INDEX
AMESIM_MIN_GAS_INDEX = AMESIM_BUILTIN_AIR_GAS_INDEX
AMESIM_MIN_DEFINED_GAS_INDEX = 1
AMESIM_MAX_GAS_INDEX = 99
AMESIM_GAS_INDEX_PARAMETER = ParameterDefinition(
"gi",
float(AMESIM_DEFAULT_GAS_INDEX),
label="介质物性模型(gi)",
quantity="dimensionless",
unit="",
minimum=float(AMESIM_MIN_GAS_INDEX),
maximum=float(AMESIM_MAX_GAS_INDEX),
editor="amesimGasReference",
description=(
"选择本元件使用的气体介质定义索引;0 表示内置空气,"
"1–99 引用画布中的介质定义组件。"
),
)
AMESIM_GAS_DEFINITION_INDEX_PARAMETER = ParameterDefinition(
"gi",
float(AMESIM_MIN_DEFINED_GAS_INDEX),
label="介质定义索引(gi)",
quantity="dimensionless",
unit="",
minimum=float(AMESIM_MIN_DEFINED_GAS_INDEX),
maximum=float(AMESIM_MAX_GAS_INDEX),
description=(
"介质定义在当前模型中的唯一索引;由画布自动分配,"
"0 保留给内置空气。"
),
)
def normalize_amesim_gas_index(value: float | int) -> int:
"""Validate an AMESim gas reference.
Index 0 is reserved for the built-in ideal-gas air profile. Positive
indices refer to medium-definition components placed in the project.
"""
if isinstance(value, bool) or not isinstance(value, (int, float)):
raise ValueError("AMESim gas type index gi must be a number.")
numeric = float(value)
if not isfinite(numeric):
raise ValueError("AMESim gas type index gi must be finite.")
rounded = round(numeric)
if not isclose(numeric, rounded, rel_tol=0.0, abs_tol=1.0e-12):
raise ValueError("AMESim gas type index gi must be an integer value.")
index = int(rounded)
if not AMESIM_MIN_GAS_INDEX <= index <= AMESIM_MAX_GAS_INDEX:
raise ValueError(
"AMESim gas type index gi must be between "
f"{AMESIM_MIN_GAS_INDEX} and {AMESIM_MAX_GAS_INDEX}."
)
return index
def normalize_amesim_defined_gas_index(value: float | int) -> int:
"""Validate a positive index owned by a project medium definition."""
index = normalize_amesim_gas_index(value)
if index < AMESIM_MIN_DEFINED_GAS_INDEX:
raise ValueError(
"AMESim medium definition index gi must be between "
f"{AMESIM_MIN_DEFINED_GAS_INDEX} and {AMESIM_MAX_GAS_INDEX}; "
"gi=0 is reserved for built-in ideal-gas air."
)
return index
@dataclass(frozen=True)
class AmesimGasDefinition:
"""One AMESim PNGD-style gas-definition slot.
``fluid_type`` and ``eos_type`` are intentionally optional today. They
reserve the metadata needed to map a future PNGD00 helium definition while
the executable behavior is supplied by ``medium``.
"""
gi: int
label: str
medium: GasMedium
fluid_type: int | None = None
eos_type: int | None = None
def __post_init__(self) -> None:
normalized = normalize_amesim_gas_index(self.gi)
object.__setattr__(self, "gi", normalized)
if not self.label.strip():
raise ValueError("AMESim gas definition label must not be empty.")
class AmesimGasRegistry:
"""Resolve AMESim component ``gi`` references to thermodynamic media."""
def __init__(
self,
definitions: Iterable[AmesimGasDefinition] = (),
*,
default_gi: int = AMESIM_DEFAULT_GAS_INDEX,
) -> None:
from app.simulation.components.amesim.media.mediums import (
AmesimIdealAirMedium,
)
self.default_gi = normalize_amesim_gas_index(default_gi)
self._definitions: dict[int, AmesimGasDefinition] = {
AMESIM_BUILTIN_AIR_GAS_INDEX: AmesimGasDefinition(
gi=AMESIM_BUILTIN_AIR_GAS_INDEX,
label="空气(理想气体,内置默认)",
medium=AmesimIdealAirMedium(),
)
}
for definition in definitions:
self.register(definition)
@property
def definitions(self) -> Mapping[int, AmesimGasDefinition]:
return MappingProxyType(self._definitions)
def register(self, definition: AmesimGasDefinition) -> None:
if not isinstance(definition, AmesimGasDefinition):
raise TypeError("AMESim gas registry entries must use AmesimGasDefinition.")
if definition.gi == AMESIM_BUILTIN_AIR_GAS_INDEX:
raise ValueError(
"AMESim gas type index gi=0 is reserved for built-in "
"ideal-gas air and cannot be replaced."
)
if definition.gi in self._definitions:
raise ValueError(
f"AMESim gas type index gi={definition.gi} is already defined."
)
self._definitions[definition.gi] = definition
def copy(self) -> AmesimGasRegistry:
"""Return an independent registry for one project compilation."""
copied = AmesimGasRegistry(
(
definition
for index, definition in self._definitions.items()
if index != AMESIM_BUILTIN_AIR_GAS_INDEX
),
default_gi=self.default_gi,
)
copied._definitions[AMESIM_BUILTIN_AIR_GAS_INDEX] = self._definitions[
AMESIM_BUILTIN_AIR_GAS_INDEX
]
return copied
def resolve(
self,
gi: float | int,
*,
component_name: str | None = None,
) -> GasMedium:
index = normalize_amesim_gas_index(gi)
try:
return self._definitions[index].medium
except KeyError as exc:
owner = f" for component '{component_name}'" if component_name else ""
available = ", ".join(str(index) for index in sorted(self._definitions))
available_message = available or "none"
raise ValueError(
f"AMESim gas type index gi={index}{owner} is not defined. "
"Register a PNGD-style gas definition before using this index. "
f"Available indices: {available_message}."
) from exc
@property
def default_medium(self) -> GasMedium:
return self.resolve(self.default_gi)
def resolve_network_media(
self,
component_gas_indices: Mapping[str, float | int | None],
pneumatic_connections: Iterable[tuple[str, str]],
) -> dict[str, GasMedium]:
"""Assign one medium to every connected pneumatic circuit.
Components without ``gi`` inherit the explicit index used by their
circuit. Conflicting indices inside one circuit are rejected instead
of silently mixing different gases.
"""
parents = {component_id: component_id for component_id in component_gas_indices}
def find(component_id: str) -> str:
parent = parents[component_id]
while parent != parents[parent]:
parent = parents[parent]
while component_id != parent:
next_component = parents[component_id]
parents[component_id] = parent
component_id = next_component
return parent
def union(left: str, right: str) -> None:
left_root = find(left)
right_root = find(right)
if left_root != right_root:
parents[right_root] = left_root
for source, target in pneumatic_connections:
if source in parents and target in parents:
union(source, target)
members_by_root: dict[str, list[str]] = {}
for component_id in component_gas_indices:
members_by_root.setdefault(find(component_id), []).append(component_id)
media: dict[str, GasMedium] = {}
for members in members_by_root.values():
indexed_components: dict[int, list[str]] = {}
for component_id in members:
raw_index = component_gas_indices[component_id]
if raw_index is None:
continue
index = normalize_amesim_gas_index(raw_index)
indexed_components.setdefault(index, []).append(component_id)
if len(indexed_components) > 1:
details = ", ".join(
f"gi={index} ({', '.join(sorted(component_ids))})"
for index, component_ids in sorted(indexed_components.items())
)
raise ValueError(
"Connected pneumatic circuit contains conflicting AMESim "
f"gas definitions: {details}."
)
index = (
next(iter(indexed_components))
if indexed_components
else self.default_gi
)
indexed_members = indexed_components.get(index, members)
medium = self.resolve(
index,
component_name=", ".join(sorted(indexed_members)),
)
for component_id in members:
media[component_id] = medium
return media
def default_amesim_gas_registry() -> AmesimGasRegistry:
"""Create a registry containing only built-in gi=0 ideal-gas air."""
return AmesimGasRegistry()
+4 -1
View File
@@ -9,11 +9,12 @@ from app.simulation.core.catalog import (
LIBRARY = ComponentLibrarySpec(
id="amesim",
label="AMESim 组件库",
version="0.1.0",
version="0.2.0",
source_package="app.simulation.components.amesim",
temporary=True,
order=200,
categories=(
ComponentCategorySpec(id="media", label="介质物性", order=5),
ComponentCategorySpec(id="storage", label="储能元件", order=10),
ComponentCategorySpec(id="flow", label="流动元件", order=20),
ComponentCategorySpec(id="junctions", label="连接元件", order=30),
@@ -22,6 +23,8 @@ LIBRARY = ComponentLibrarySpec(
ComponentCategorySpec(id="mechanical", label="机械元件", order=60),
),
models=(
"app.simulation.components.amesim.media.properties:AmesimIdealAirMediumDefinition",
"app.simulation.components.amesim.media.properties:AmesimHeliumMediumDefinition",
"app.simulation.components.amesim.boundary.sources:AmesimPnpl01",
"app.simulation.components.amesim.signals.sources:AmesimStep0",
"app.simulation.components.amesim.signals.sources:AmesimUd00",
@@ -0,0 +1,33 @@
"""AMESim medium-property definition components."""
from app.simulation.components.amesim.media.mediums import (
AMESIM_AIR_IDEAL_GAS_PROPERTY_MODEL,
AMESIM_AIR_PROPERTY_MODELS,
AMESIM_HELIUM_PENG_ROBINSON_PROPERTY_MODEL,
AMESIM_HELIUM_PROPERTY_MODELS,
AmesimGasPropertyModelSpec,
AmesimHeliumPengRobinsonMedium,
AmesimIdealAirMedium,
)
from app.simulation.components.amesim.media.properties import (
AMESIM_AIR_PROPERTY_MODEL_PARAMETER,
AMESIM_HELIUM_PROPERTY_MODEL_PARAMETER,
AmesimGasMediumDefinitionComponent,
AmesimHeliumMediumDefinition,
AmesimIdealAirMediumDefinition,
)
__all__ = (
"AMESIM_AIR_IDEAL_GAS_PROPERTY_MODEL",
"AMESIM_AIR_PROPERTY_MODELS",
"AMESIM_AIR_PROPERTY_MODEL_PARAMETER",
"AMESIM_HELIUM_PENG_ROBINSON_PROPERTY_MODEL",
"AMESIM_HELIUM_PROPERTY_MODELS",
"AMESIM_HELIUM_PROPERTY_MODEL_PARAMETER",
"AmesimGasMediumDefinitionComponent",
"AmesimGasPropertyModelSpec",
"AmesimHeliumMediumDefinition",
"AmesimHeliumPengRobinsonMedium",
"AmesimIdealAirMedium",
"AmesimIdealAirMediumDefinition",
)
@@ -0,0 +1,109 @@
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import ClassVar
from app.simulation.core.medium import GasMedium, IdealGasMedium
from app.simulation.core.peng_robinson import HELIUM_PR, PengRobinsonFluid
@dataclass(frozen=True)
class AmesimIdealAirMedium(IdealGasMedium):
"""AMESim air properties evaluated with the ideal-gas method.
Substance identity and property method are part of the concrete Python
type. A future air correlation or helium Peng-Robinson implementation can
therefore coexist as a sibling type without turning ``gi`` into a fluid
enumeration.
"""
SUBSTANCE_ID: ClassVar[str] = "air"
PROPERTY_METHOD_ID: ClassVar[str] = "ideal_gas"
name: str = "AMESimAirIdealGas"
R_gas: float = 287.0
cp_ref: float = 1005.0
T_ref: float = 300.0
cp_slope: float = 0.0
viscosity_ref: float = 1.82e-5
viscosity_T_ref: float = 293.15
sutherland_constant: float = 110.4
@dataclass(frozen=True)
class AmesimHeliumPengRobinsonMedium(IdealGasMedium):
"""AMESim helium with a Peng-Robinson mechanical equation of state.
The pressure-density-temperature relation is evaluated by the shared
``HELIUM_PR`` fluid. The first public AMESim port keeps the committed
constant-heat-capacity caloric model so it can be consumed through the
same :class:`GasMedium` contract as ideal-gas air.
"""
SUBSTANCE_ID: ClassVar[str] = "helium"
PROPERTY_METHOD_ID: ClassVar[str] = "peng_robinson"
fluid: ClassVar[PengRobinsonFluid] = HELIUM_PR
name: str = "AMESimHeliumPengRobinson"
R_gas: float = HELIUM_PR.specific_gas_constant
cp_ref: float = 5193.0
T_ref: float = 293.15
cp_slope: float = 0.0
viscosity_ref: float = 1.96e-5
viscosity_T_ref: float = 293.15
sutherland_constant: float = 79.4
@property
def cv(self) -> float:
return 3116.0
def cv_at_temperature(self, T: float) -> float:
del T
return self.cv
def density(self, p: float, T: float) -> float:
return self.fluid.density(p, T)
def pressure(self, m: float, T: float, V: float) -> float:
if V <= 0.0:
raise ValueError("Volume must stay positive.")
return self.fluid.pressure_from_density(T, m / V)
@dataclass(frozen=True)
class AmesimGasPropertyModelSpec:
"""A selectable calculation method for one AMESim gas substance."""
value: int
label: str
method_id: str
factory: Callable[[], GasMedium]
eos_type: int
def build_medium(self) -> GasMedium:
return self.factory()
AMESIM_AIR_IDEAL_GAS_PROPERTY_MODEL = 0
AMESIM_AIR_PROPERTY_MODELS = (
AmesimGasPropertyModelSpec(
value=AMESIM_AIR_IDEAL_GAS_PROPERTY_MODEL,
label="理想气体",
method_id=AmesimIdealAirMedium.PROPERTY_METHOD_ID,
factory=AmesimIdealAirMedium,
eos_type=1,
),
)
AMESIM_HELIUM_PENG_ROBINSON_PROPERTY_MODEL = 0
AMESIM_HELIUM_PROPERTY_MODELS = (
AmesimGasPropertyModelSpec(
value=AMESIM_HELIUM_PENG_ROBINSON_PROPERTY_MODEL,
label="Peng–Robinson",
method_id=AmesimHeliumPengRobinsonMedium.PROPERTY_METHOD_ID,
factory=AmesimHeliumPengRobinsonMedium,
eos_type=6,
),
)
@@ -0,0 +1,196 @@
from __future__ import annotations
from abc import ABC
from collections.abc import Mapping
from app.simulation.components.amesim.gases import (
AMESIM_GAS_DEFINITION_INDEX_PARAMETER,
AmesimGasDefinition,
normalize_amesim_defined_gas_index,
)
from app.simulation.components.amesim.media.mediums import (
AMESIM_AIR_IDEAL_GAS_PROPERTY_MODEL,
AMESIM_AIR_PROPERTY_MODELS,
AMESIM_HELIUM_PENG_ROBINSON_PROPERTY_MODEL,
AMESIM_HELIUM_PROPERTY_MODELS,
AmesimGasPropertyModelSpec,
)
from app.simulation.core.base import AlgebraicComponent
from app.simulation.core.catalog import ComponentDisplaySpec
from app.simulation.core.medium import GasMedium
from app.simulation.core.metadata import ParameterDefinition, ParameterOption
AMESIM_AIR_PROPERTY_MODEL_PARAMETER = ParameterDefinition(
"property_model",
float(AMESIM_AIR_IDEAL_GAS_PROPERTY_MODEL),
label="物性计算模型",
quantity="dimensionless",
unit="",
minimum=float(min(model.value for model in AMESIM_AIR_PROPERTY_MODELS)),
maximum=float(max(model.value for model in AMESIM_AIR_PROPERTY_MODELS)),
editor="amesimGasPropertyModel",
options=tuple(
ParameterOption(value=model.value, label=model.label)
for model in AMESIM_AIR_PROPERTY_MODELS
),
description="选择空气介质的物性计算方法;当前首版提供理想气体模型。",
)
AMESIM_HELIUM_PROPERTY_MODEL_PARAMETER = ParameterDefinition(
"property_model",
float(AMESIM_HELIUM_PENG_ROBINSON_PROPERTY_MODEL),
label="物性计算模型",
quantity="dimensionless",
unit="",
minimum=float(min(model.value for model in AMESIM_HELIUM_PROPERTY_MODELS)),
maximum=float(max(model.value for model in AMESIM_HELIUM_PROPERTY_MODELS)),
editor="amesimGasPropertyModel",
options=tuple(
ParameterOption(value=model.value, label=model.label)
for model in AMESIM_HELIUM_PROPERTY_MODELS
),
description=(
"选择氦气介质的物性计算方法;当前首版提供 "
"Peng–Robinson 状态方程模型。"
),
)
class AmesimGasMediumDefinitionComponent(AlgebraicComponent, ABC):
"""Compile-time definition of one project-scoped AMESim gas medium.
Concrete subclasses declare one substance and its available calculation
methods; each instance selects a method through ``property_model``. They
deliberately expose no physical ports or equations: the compiler consumes
them before it creates the simulation network.
"""
IS_AMESIM_GAS_MEDIUM_DEFINITION = True
MEDIUM_LABEL = ""
FLUID_TYPE: int | None = None
PROPERTY_MODELS: tuple[AmesimGasPropertyModelSpec, ...] = ()
def __init__(
self,
name: str,
gi: float,
property_model: float = float(AMESIM_AIR_IDEAL_GAS_PROPERTY_MODEL),
) -> None:
super().__init__(name)
self.gi = normalize_amesim_defined_gas_index(gi)
self.property_model = self._resolve_property_model(property_model).value
self.set_parameter_values(
{
"gi": self.gi,
"property_model": self.property_model,
}
)
def _resolve_property_model(
self,
value: float | int,
) -> AmesimGasPropertyModelSpec:
for model in self.PROPERTY_MODELS:
if float(model.value) == float(value):
return model
available = ", ".join(str(model.value) for model in self.PROPERTY_MODELS)
raise ValueError(
f"AMESim medium definition '{self.name}' does not support property "
f"model {value:g}; available models: {available or 'none'}."
)
def build_medium(self) -> GasMedium:
"""Create the executable property model selected by this instance."""
return self._resolve_property_model(self.property_model).build_medium()
def gas_definition(self) -> AmesimGasDefinition:
model = self._resolve_property_model(self.property_model)
return AmesimGasDefinition(
gi=self.gi,
label=f"{self.MEDIUM_LABEL}({model.label})",
medium=self.build_medium(),
fluid_type=self.FLUID_TYPE,
eos_type=model.eos_type,
)
class AmesimIdealAirMediumDefinition(AmesimGasMediumDefinitionComponent):
"""Project gas slot using the built-in ideal-gas air property method."""
MODEL_TYPE = "amesim_ideal_air_medium"
MODEL_VERSION = "0.2.0"
PORTS = ()
PARAMETERS = (
AMESIM_GAS_DEFINITION_INDEX_PARAMETER,
AMESIM_AIR_PROPERTY_MODEL_PARAMETER,
)
RESULT_VARIABLES = ()
DISPLAY = ComponentDisplaySpec(
label="空气介质定义",
library_id="amesim",
category_id="media",
symbol="amesim_ideal_air_medium",
ports=(),
order=10,
role="amesimGasMediumDefinition",
)
MEDIUM_LABEL = "空气"
FLUID_TYPE = 2
PROPERTY_MODELS = AMESIM_AIR_PROPERTY_MODELS
@classmethod
def create(
cls,
*,
name: str,
medium: GasMedium,
parameters: Mapping[str, float],
) -> AmesimIdealAirMediumDefinition:
del medium
return cls(
name=name,
gi=parameters["gi"],
property_model=parameters["property_model"],
)
class AmesimHeliumMediumDefinition(AmesimGasMediumDefinitionComponent):
"""Project gas slot using the AMESim helium Peng-Robinson method."""
MODEL_TYPE = "amesim_helium_medium"
MODEL_VERSION = "0.1.0"
PORTS = ()
PARAMETERS = (
AMESIM_GAS_DEFINITION_INDEX_PARAMETER,
AMESIM_HELIUM_PROPERTY_MODEL_PARAMETER,
)
RESULT_VARIABLES = ()
DISPLAY = ComponentDisplaySpec(
label="氦气介质定义",
library_id="amesim",
category_id="media",
symbol="amesim_helium_medium",
ports=(),
order=20,
role="amesimGasMediumDefinition",
)
MEDIUM_LABEL = "氦气"
FLUID_TYPE = 12
PROPERTY_MODELS = AMESIM_HELIUM_PROPERTY_MODELS
@classmethod
def create(
cls,
*,
name: str,
medium: GasMedium,
parameters: Mapping[str, float],
) -> AmesimHeliumMediumDefinition:
del medium
return cls(
name=name,
gi=parameters["gi"],
property_model=parameters["property_model"],
)
@@ -1,8 +1,11 @@
from __future__ import annotations
from collections.abc import Mapping
from math import isclose
from app.simulation.components.amesim.gases import (
AMESIM_GAS_INDEX_PARAMETER,
normalize_amesim_gas_index,
)
from app.simulation.core.base import ThermodynamicVolumeComponent
from app.simulation.core.catalog import ComponentDisplaySpec, PortDisplaySpec
from app.simulation.core.equations import EquationResidual
@@ -11,7 +14,7 @@ from app.simulation.core.metadata import (
ResultVariableDefinition,
THERMODYNAMIC_VOLUME_RESULT_VARIABLES,
)
from app.simulation.core.medium import IdealGasMedium, ThermodynamicProperties
from app.simulation.core.medium import GasMedium, ThermodynamicProperties
from app.simulation.core.ports import PortDefinition
from app.simulation.core.state import VolumeState
@@ -32,6 +35,7 @@ class AmesimPnch023(ThermodynamicVolumeComponent):
PortDefinition.pneumatic("port_2", nominal_role="bidirectional"),
)
PARAMETERS = (
AMESIM_GAS_INDEX_PARAMETER,
ParameterDefinition(
"cvol",
0.057,
@@ -40,6 +44,7 @@ class AmesimPnch023(ThermodynamicVolumeComponent):
unit="m3",
minimum=0.0,
minimum_exclusive=True,
description="气室内部用于储存气体的固定有效容积。",
),
ParameterDefinition(
"kth",
@@ -48,6 +53,7 @@ class AmesimPnch023(ThermodynamicVolumeComponent):
quantity="heat_transfer_coefficient",
unit="W/(m2*K)",
minimum=0.0,
description="气室与环境之间的对流换热系数,与换热面积共同决定换热功率。",
),
ParameterDefinition(
"sth",
@@ -56,6 +62,7 @@ class AmesimPnch023(ThermodynamicVolumeComponent):
quantity="area",
unit="m2",
minimum=0.0,
description="气室与环境进行热交换的有效表面积。",
),
ParameterDefinition(
"extemp",
@@ -65,15 +72,7 @@ class AmesimPnch023(ThermodynamicVolumeComponent):
unit="K",
minimum=0.0,
minimum_exclusive=True,
),
ParameterDefinition(
"gi",
1.0,
label="气体类型索引",
quantity="dimensionless",
unit="",
minimum=1.0,
maximum=99.0,
description="气室外部环境的绝对温度,用于计算气体与环境之间的换热。",
),
ParameterDefinition(
"p0",
@@ -83,6 +82,7 @@ class AmesimPnch023(ThermodynamicVolumeComponent):
unit="Pa",
minimum=0.0,
minimum_exclusive=True,
description="仿真开始时气室内气体的绝对压力。",
),
ParameterDefinition(
"T0",
@@ -92,6 +92,7 @@ class AmesimPnch023(ThermodynamicVolumeComponent):
unit="K",
minimum=0.0,
minimum_exclusive=True,
description="仿真开始时气室内气体的绝对温度。",
),
)
RESULT_VARIABLES = THERMODYNAMIC_VOLUME_RESULT_VARIABLES
@@ -110,7 +111,7 @@ class AmesimPnch023(ThermodynamicVolumeComponent):
def __init__(
self,
name: str,
medium: IdealGasMedium,
medium: GasMedium,
*,
cvol: float = 0.057,
kth: float = 0.0,
@@ -137,10 +138,10 @@ class AmesimPnch023(ThermodynamicVolumeComponent):
self.kth = float(kth)
self.sth = float(sth)
self.extemp = float(extemp)
self.gi = self._integer_parameter("gi", gi)
self.gi = normalize_amesim_gas_index(gi)
self.p0 = float(p0)
self.T0 = float(T0)
m0 = self.p0 * self.cvol / (medium.R_gas * self.T0)
m0 = medium.density(self.p0, self.T0) * self.cvol
U0 = m0 * medium.specific_internal_energy(self.T0)
self.state = VolumeState(m=m0, U=U0)
initial_h = medium.specific_enthalpy(self.T0)
@@ -151,19 +152,12 @@ class AmesimPnch023(ThermodynamicVolumeComponent):
self.port_2.p = self.p0
self.port_2.h_outflow = initial_h
@staticmethod
def _integer_parameter(name: str, value: float) -> int:
rounded = round(value)
if not isclose(value, rounded, rel_tol=0.0, abs_tol=1.0e-12):
raise ValueError(f"PNCH023 parameter {name} must be an integer value.")
return int(rounded)
@classmethod
def create(
cls,
*,
name: str,
medium: IdealGasMedium,
medium: GasMedium,
parameters: Mapping[str, float],
) -> AmesimPnch023:
return cls(
@@ -270,6 +264,7 @@ class AmesimPnch012(ThermodynamicVolumeComponent):
PortDefinition.pneumatic("port_4", nominal_role="bidirectional"),
)
PARAMETERS = (
AMESIM_GAS_INDEX_PARAMETER,
ParameterDefinition(
"cvol0",
0.015,
@@ -278,6 +273,7 @@ class AmesimPnch012(ThermodynamicVolumeComponent):
unit="m3",
minimum=0.0,
minimum_exclusive=True,
description="变容气室在所有外部容积为零时仍保留的基础容积。",
),
ParameterDefinition(
"kth",
@@ -286,6 +282,7 @@ class AmesimPnch012(ThermodynamicVolumeComponent):
quantity="heat_transfer_coefficient",
unit="W/(m2*K)",
minimum=0.0,
description="气室与环境之间的对流换热系数,与换热面积共同决定换热功率。",
),
ParameterDefinition(
"sth",
@@ -294,6 +291,7 @@ class AmesimPnch012(ThermodynamicVolumeComponent):
quantity="area",
unit="m2",
minimum=0.0,
description="气室与环境进行热交换的有效表面积。",
),
ParameterDefinition(
"extemp",
@@ -303,15 +301,7 @@ class AmesimPnch012(ThermodynamicVolumeComponent):
unit="K",
minimum=0.0,
minimum_exclusive=True,
),
ParameterDefinition(
"gi",
1.0,
label="气体类型索引",
quantity="dimensionless",
unit="",
minimum=1.0,
maximum=99.0,
description="气室外部环境的绝对温度,用于计算气体与环境之间的换热。",
),
ParameterDefinition(
"p0",
@@ -321,6 +311,7 @@ class AmesimPnch012(ThermodynamicVolumeComponent):
unit="Pa",
minimum=0.0,
minimum_exclusive=True,
description="仿真开始时气室内气体的绝对压力。",
),
ParameterDefinition(
"T0",
@@ -330,6 +321,7 @@ class AmesimPnch012(ThermodynamicVolumeComponent):
unit="K",
minimum=0.0,
minimum_exclusive=True,
description="仿真开始时气室内气体的绝对温度。",
),
ParameterDefinition("vol1", 0.0, label="端口 1 外部容积", quantity="volume", unit="m3"),
ParameterDefinition("vol2", 0.0, label="端口 2 外部容积", quantity="volume", unit="m3"),
@@ -361,7 +353,7 @@ class AmesimPnch012(ThermodynamicVolumeComponent):
def __init__(
self,
name: str,
medium: IdealGasMedium,
medium: GasMedium,
*,
cvol0: float = 0.015,
kth: float = 0.0,
@@ -404,7 +396,7 @@ class AmesimPnch012(ThermodynamicVolumeComponent):
self.kth = float(kth)
self.sth = float(sth)
self.extemp = float(extemp)
self.gi = AmesimPnch023._integer_parameter("gi", gi)
self.gi = normalize_amesim_gas_index(gi)
self.p0 = float(p0)
self.T0 = float(T0)
self.external_volumes = {
@@ -421,7 +413,7 @@ class AmesimPnch012(ThermodynamicVolumeComponent):
}
if self.total_volume() <= 0.0:
raise ValueError("PNCH012 total volume must be positive.")
m0 = self.p0 * self.total_volume() / (medium.R_gas * self.T0)
m0 = medium.density(self.p0, self.T0) * self.total_volume()
U0 = m0 * medium.specific_internal_energy(self.T0)
self.state = VolumeState(m=m0, U=U0)
initial_h = medium.specific_enthalpy(self.T0)
@@ -436,7 +428,7 @@ class AmesimPnch012(ThermodynamicVolumeComponent):
cls,
*,
name: str,
medium: IdealGasMedium,
medium: GasMedium,
parameters: Mapping[str, float],
) -> "AmesimPnch012":
return cls(name=name, medium=medium, **dict(parameters))