251 lines
7.7 KiB
Python
251 lines
7.7 KiB
Python
from __future__ import annotations
|
|
|
|
from collections.abc import Mapping
|
|
from math import isclose
|
|
|
|
from app.simulation.core.base import ThermodynamicVolumeComponent
|
|
from app.simulation.core.catalog import ComponentDisplaySpec, PortDisplaySpec
|
|
from app.simulation.core.equations import EquationResidual
|
|
from app.simulation.core.metadata import (
|
|
ParameterDefinition,
|
|
THERMODYNAMIC_VOLUME_RESULT_VARIABLES,
|
|
)
|
|
from app.simulation.core.medium import IdealGasMedium, ThermodynamicProperties
|
|
from app.simulation.core.ports import PortDefinition
|
|
from app.simulation.core.state import VolumeState
|
|
|
|
|
|
class AmesimPnch023(ThermodynamicVolumeComponent):
|
|
"""AMESim PNCH023 simple pneumatic chamber with heat exchange.
|
|
|
|
The AMESim submodel owns pressure and temperature states and exposes two
|
|
pneumatic flow ports. This public component maps those states onto the
|
|
framework's mass/internal-energy volume state and keeps the AMESim
|
|
heat-transfer contract `kth * sth * (extemp - T)`.
|
|
"""
|
|
|
|
MODEL_TYPE = "amesim_pnch023"
|
|
MODEL_VERSION = "0.1.0"
|
|
PORTS = (
|
|
PortDefinition.pneumatic("port_1", nominal_role="bidirectional"),
|
|
PortDefinition.pneumatic("port_2", nominal_role="bidirectional"),
|
|
)
|
|
PARAMETERS = (
|
|
ParameterDefinition(
|
|
"cvol",
|
|
0.057,
|
|
label="气室容积",
|
|
quantity="volume",
|
|
unit="m3",
|
|
minimum=0.0,
|
|
minimum_exclusive=True,
|
|
),
|
|
ParameterDefinition(
|
|
"kth",
|
|
0.0,
|
|
label="换热系数",
|
|
quantity="heat_transfer_coefficient",
|
|
unit="W/(m2*K)",
|
|
minimum=0.0,
|
|
),
|
|
ParameterDefinition(
|
|
"sth",
|
|
0.1,
|
|
label="换热面积",
|
|
quantity="area",
|
|
unit="m2",
|
|
minimum=0.0,
|
|
),
|
|
ParameterDefinition(
|
|
"extemp",
|
|
293.15,
|
|
label="外部温度",
|
|
quantity="temperature",
|
|
unit="K",
|
|
minimum=0.0,
|
|
minimum_exclusive=True,
|
|
),
|
|
ParameterDefinition(
|
|
"gi",
|
|
1.0,
|
|
label="气体类型索引",
|
|
quantity="dimensionless",
|
|
unit="",
|
|
minimum=1.0,
|
|
maximum=99.0,
|
|
),
|
|
ParameterDefinition(
|
|
"p0",
|
|
100000.0,
|
|
label="初始压力",
|
|
quantity="pressure",
|
|
unit="Pa",
|
|
minimum=0.0,
|
|
minimum_exclusive=True,
|
|
),
|
|
ParameterDefinition(
|
|
"T0",
|
|
293.15,
|
|
label="初始温度",
|
|
quantity="temperature",
|
|
unit="K",
|
|
minimum=0.0,
|
|
minimum_exclusive=True,
|
|
),
|
|
)
|
|
RESULT_VARIABLES = THERMODYNAMIC_VOLUME_RESULT_VARIABLES
|
|
DISPLAY = ComponentDisplaySpec(
|
|
label="PNCH023 固定容积气室",
|
|
library_id="amesim",
|
|
category_id="storage",
|
|
symbol="tank",
|
|
ports=(
|
|
PortDisplaySpec("port_1", "left", order=10),
|
|
PortDisplaySpec("port_2", "right", order=20),
|
|
),
|
|
order=10,
|
|
)
|
|
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
medium: IdealGasMedium,
|
|
*,
|
|
cvol: float = 0.057,
|
|
kth: float = 0.0,
|
|
sth: float = 0.1,
|
|
extemp: float = 293.15,
|
|
gi: float = 1.0,
|
|
p0: float = 100000.0,
|
|
T0: float = 293.15,
|
|
) -> None:
|
|
super().__init__(name=name)
|
|
self.set_parameter_values(
|
|
{
|
|
"cvol": cvol,
|
|
"kth": kth,
|
|
"sth": sth,
|
|
"extemp": extemp,
|
|
"gi": gi,
|
|
"p0": p0,
|
|
"T0": T0,
|
|
}
|
|
)
|
|
self.medium = medium
|
|
self.cvol = float(cvol)
|
|
self.kth = float(kth)
|
|
self.sth = float(sth)
|
|
self.extemp = float(extemp)
|
|
self.gi = self._integer_parameter("gi", gi)
|
|
self.p0 = float(p0)
|
|
self.T0 = float(T0)
|
|
m0 = self.p0 * self.cvol / (medium.R_gas * self.T0)
|
|
U0 = m0 * medium.specific_internal_energy(self.T0)
|
|
self.state = VolumeState(m=m0, U=U0)
|
|
initial_h = medium.specific_enthalpy(self.T0)
|
|
self.port_1 = self.register_declared_port("port_1")
|
|
self.port_1.p = self.p0
|
|
self.port_1.h_outflow = initial_h
|
|
self.port_2 = self.register_declared_port("port_2")
|
|
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,
|
|
parameters: Mapping[str, float],
|
|
) -> AmesimPnch023:
|
|
return cls(
|
|
name=name,
|
|
medium=medium,
|
|
cvol=parameters["cvol"],
|
|
kth=parameters["kth"],
|
|
sth=parameters["sth"],
|
|
extemp=parameters["extemp"],
|
|
gi=parameters["gi"],
|
|
p0=parameters["p0"],
|
|
T0=parameters["T0"],
|
|
)
|
|
|
|
def get_state_vector(self) -> list[float]:
|
|
return self.state.as_vector()
|
|
|
|
def set_state_vector(self, values: list[float]) -> None:
|
|
self.state = VolumeState.from_vector(values)
|
|
|
|
def properties(self) -> ThermodynamicProperties:
|
|
props = self.medium.properties_from_mU(self.state.m, self.state.U, self.cvol)
|
|
self.port_1.p = props.p
|
|
self.port_1.h_outflow = props.h
|
|
self.port_2.p = props.p
|
|
self.port_2.h_outflow = props.h
|
|
return props
|
|
|
|
def refresh_thermodynamic_ports(self) -> ThermodynamicProperties:
|
|
return self.properties()
|
|
|
|
def thermal_energy_flow_w(self, temperature: float) -> float:
|
|
return self.kth * self.sth * (self.extemp - temperature)
|
|
|
|
def state_derivative_from_ports(
|
|
self,
|
|
connected_h: Mapping[str, float],
|
|
) -> list[float]:
|
|
props = self.properties()
|
|
inlet_h_1 = self.connection_inlet_enthalpy(
|
|
port_m_flow=self.port_1.m_flow,
|
|
connected_h=connected_h["port_1"],
|
|
internal_h=props.h,
|
|
)
|
|
inlet_h_2 = self.connection_inlet_enthalpy(
|
|
port_m_flow=self.port_2.m_flow,
|
|
connected_h=connected_h["port_2"],
|
|
internal_h=props.h,
|
|
)
|
|
derivative = VolumeState(
|
|
m=self.port_1.m_flow + self.port_2.m_flow,
|
|
U=(
|
|
self.port_1.m_flow * inlet_h_1
|
|
+ self.port_2.m_flow * inlet_h_2
|
|
+ self.thermal_energy_flow_w(props.T)
|
|
),
|
|
)
|
|
return derivative.as_vector()
|
|
|
|
def pressure_flow_equation_residuals(self) -> tuple[EquationResidual, ...]:
|
|
pressure = self.medium.properties_from_mU(
|
|
self.state.m,
|
|
self.state.U,
|
|
self.cvol,
|
|
).p
|
|
return (
|
|
EquationResidual(
|
|
id=f"{self.name}:port_1_pressure_state",
|
|
owner="component",
|
|
owner_id=self.name,
|
|
relation="state",
|
|
variables=(f"{self.name}.port_1.p", f"{self.name}.state"),
|
|
role="effort",
|
|
value=self.port_1.p - pressure,
|
|
),
|
|
EquationResidual(
|
|
id=f"{self.name}:port_2_pressure_state",
|
|
owner="component",
|
|
owner_id=self.name,
|
|
relation="state",
|
|
variables=(f"{self.name}.port_2.p", f"{self.name}.state"),
|
|
role="effort",
|
|
value=self.port_2.p - pressure,
|
|
),
|
|
)
|