192 lines
6.6 KiB
Python
192 lines
6.6 KiB
Python
from __future__ import annotations
|
|
|
|
from collections.abc import Mapping
|
|
|
|
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.medium import IdealGasMedium
|
|
from app.simulation.core.ports import PortDefinition, PortState
|
|
|
|
|
|
class _AmesimPneumaticNode(AlgebraicComponent):
|
|
"""Shared implementation for AMESim pneumatic junction submodels.
|
|
|
|
PN3NODE2/P4NODE2 use port 2 as their pressure and temperature reference.
|
|
Non-reference outlet ports use that reference temperature. When port 2 is
|
|
an outlet, its enthalpy is the residual that closes the junction energy
|
|
balance, matching the AMESim dh2 causality.
|
|
"""
|
|
|
|
REFERENCE_PORT = "port_2"
|
|
|
|
def __init__(self, name: str) -> None:
|
|
super().__init__(name=name)
|
|
self.set_parameter_values({})
|
|
self.temperature_reference_h = 0.0
|
|
for definition in self.PORTS:
|
|
setattr(self, definition.name, self.register_declared_port(definition.name))
|
|
|
|
def pressure_flow_equation_values(self) -> tuple[float, ...]:
|
|
reference = self.get_port(self.REFERENCE_PORT)
|
|
return tuple(
|
|
self.get_port(definition.name).p - reference.p
|
|
for definition in self.PORTS
|
|
if definition.name != self.REFERENCE_PORT
|
|
) + (
|
|
sum(
|
|
self.get_port(definition.name).m_flow
|
|
for definition in self.PORTS
|
|
),
|
|
)
|
|
|
|
def pressure_flow_equation_residuals(self) -> tuple[EquationResidual, ...]:
|
|
reference = self.get_port(self.REFERENCE_PORT)
|
|
residuals: list[EquationResidual] = []
|
|
for definition in self.PORTS:
|
|
if definition.name == self.REFERENCE_PORT:
|
|
continue
|
|
port = self.get_port(definition.name)
|
|
residuals.append(
|
|
EquationResidual(
|
|
id=f"{self.name}:{definition.name}_pressure_reference",
|
|
owner="component",
|
|
owner_id=self.name,
|
|
relation="equal",
|
|
variables=(
|
|
f"{self.name}.{definition.name}.p",
|
|
f"{self.name}.{self.REFERENCE_PORT}.p",
|
|
),
|
|
role="effort",
|
|
value=port.p - reference.p,
|
|
)
|
|
)
|
|
residuals.append(
|
|
EquationResidual(
|
|
id=f"{self.name}:mass_flow_balance",
|
|
owner="component",
|
|
owner_id=self.name,
|
|
relation="sumToZero",
|
|
variables=tuple(
|
|
f"{self.name}.{definition.name}.m_flow"
|
|
for definition in self.PORTS
|
|
),
|
|
role="flow",
|
|
value=sum(self.get_port(definition.name).m_flow for definition in self.PORTS),
|
|
)
|
|
)
|
|
return tuple(residuals)
|
|
|
|
def update_stream_outflows(self, connected_h: Mapping[str, float]) -> None:
|
|
self.temperature_reference_h = connected_h.get(
|
|
self.REFERENCE_PORT,
|
|
sum(connected_h.values()) / len(connected_h) if connected_h else 0.0,
|
|
)
|
|
incoming = [
|
|
(port.m_flow, connected_h[name])
|
|
for name, port in self.ports.items()
|
|
if port.m_flow > 1e-12
|
|
]
|
|
total_flow = sum(m_flow for m_flow, _ in incoming)
|
|
if total_flow > 1e-12:
|
|
mixed_h = sum(m_flow * h for m_flow, h in incoming) / total_flow
|
|
else:
|
|
mixed_h = self.temperature_reference_h
|
|
|
|
reference_port = self.get_port(self.REFERENCE_PORT)
|
|
for name, port in self.ports.items():
|
|
port.h_outflow = (
|
|
mixed_h
|
|
if name == self.REFERENCE_PORT
|
|
else self.temperature_reference_h
|
|
)
|
|
|
|
if reference_port.m_flow < -1e-12:
|
|
energy_without_reference = sum(
|
|
port.m_flow
|
|
* (
|
|
connected_h[name]
|
|
if port.m_flow > 1e-12
|
|
else self.temperature_reference_h
|
|
)
|
|
for name, port in self.ports.items()
|
|
if name != self.REFERENCE_PORT
|
|
)
|
|
reference_port.h_outflow = (
|
|
-energy_without_reference / reference_port.m_flow
|
|
)
|
|
|
|
|
|
class AmesimPn3Node2(_AmesimPneumaticNode):
|
|
"""AMESim PN3NODE2 pneumatic three-port junction."""
|
|
|
|
MODEL_TYPE = "amesim_pn3node2"
|
|
MODEL_VERSION = "0.3.0"
|
|
PORTS = (
|
|
PortDefinition.pneumatic("port_1", nominal_role="bidirectional"),
|
|
PortDefinition.pneumatic("port_2", nominal_role="bidirectional"),
|
|
PortDefinition.pneumatic("port_3", nominal_role="bidirectional"),
|
|
)
|
|
PARAMETERS = ()
|
|
RESULT_VARIABLES = ()
|
|
DISPLAY = ComponentDisplaySpec(
|
|
label="PN3NODE2 三端气动节点",
|
|
library_id="amesim",
|
|
category_id="junctions",
|
|
symbol="amesim_pn3node2",
|
|
ports=(
|
|
PortDisplaySpec("port_1", "left", order=10),
|
|
PortDisplaySpec("port_2", "right", order=20),
|
|
PortDisplaySpec("port_3", "right", order=30),
|
|
),
|
|
order=10,
|
|
)
|
|
|
|
@classmethod
|
|
def create(
|
|
cls,
|
|
*,
|
|
name: str,
|
|
medium: IdealGasMedium,
|
|
parameters: Mapping[str, float],
|
|
) -> AmesimPn3Node2:
|
|
return cls(name=name)
|
|
|
|
|
|
class AmesimP4Node2(_AmesimPneumaticNode):
|
|
"""AMESim P4NODE2 pneumatic four-port junction."""
|
|
|
|
MODEL_TYPE = "amesim_p4node2"
|
|
MODEL_VERSION = "0.3.0"
|
|
PORTS = (
|
|
PortDefinition.pneumatic("port_1", nominal_role="bidirectional"),
|
|
PortDefinition.pneumatic("port_2", nominal_role="bidirectional"),
|
|
PortDefinition.pneumatic("port_3", nominal_role="bidirectional"),
|
|
PortDefinition.pneumatic("port_4", nominal_role="bidirectional"),
|
|
)
|
|
PARAMETERS = ()
|
|
RESULT_VARIABLES = ()
|
|
DISPLAY = ComponentDisplaySpec(
|
|
label="P4NODE2 四端气动节点",
|
|
library_id="amesim",
|
|
category_id="junctions",
|
|
symbol="amesim_p4node2",
|
|
ports=(
|
|
PortDisplaySpec("port_1", "left", order=10),
|
|
PortDisplaySpec("port_2", "right", order=20),
|
|
PortDisplaySpec("port_3", "right", order=30),
|
|
PortDisplaySpec("port_4", "right", order=40),
|
|
),
|
|
order=20,
|
|
)
|
|
|
|
@classmethod
|
|
def create(
|
|
cls,
|
|
*,
|
|
name: str,
|
|
medium: IdealGasMedium,
|
|
parameters: Mapping[str, float],
|
|
) -> AmesimP4Node2:
|
|
return cls(name=name)
|