Files
SystemSimulationApp/PythonModels/systems/test_mql_closure.py
T

187 lines
6.0 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from typing import Callable
from PythonModels.components.amesim_pneumatic import (
AmesimPneumaticOrifice,
AmesimPneumaticVolume,
)
from PythonModels.core.medium import ThermodynamicProperties
from PythonModels.core.state import VolumeState
@dataclass(frozen=True)
class TestMqlPneumaticBranchSpec:
name: str
upstream_volume_alias: str
orifice_alias: str
downstream_volume_alias: str
source: str = "manual"
@dataclass(frozen=True)
class TestMqlPneumaticTopologyCandidate:
connection_alias: str
line_submodel: str
source_component: str
source_port: str
target_component: str
target_port: str
source_is_typed_pneumatic: bool
target_is_typed_pneumatic: bool
reason: str
@dataclass(frozen=True)
class TestMqlPneumaticTopologyDiscovery:
branch_specs: tuple[TestMqlPneumaticBranchSpec, ...]
blocked_candidates: tuple[TestMqlPneumaticTopologyCandidate, ...]
@dataclass(frozen=True)
class TestMqlPneumaticBranchComponents:
name: str
upstream_volume: AmesimPneumaticVolume
orifice: AmesimPneumaticOrifice
downstream_volume: AmesimPneumaticVolume
spec: TestMqlPneumaticBranchSpec | None = None
@dataclass(frozen=True)
class TestMqlPneumaticBranchState:
name: str
upstream: ThermodynamicProperties
downstream: ThermodynamicProperties
flow: float
upstream_inlet_h: float
downstream_inlet_h: float
@dataclass(frozen=True)
class TestMqlPneumaticSnapshot:
branch: TestMqlPneumaticBranchState
@property
def flow(self) -> float:
return self.branch.flow
@property
def upstream(self) -> ThermodynamicProperties:
return self.branch.upstream
@property
def downstream(self) -> ThermodynamicProperties:
return self.branch.downstream
class TestMqlPneumaticClosure:
"""Minimal test_mql pneumatic closure following the existing Testmodel pattern.
The canonical branch flow is positive from ``upstream_volume`` through the
orifice port_a/port_b into ``downstream_volume``. Port ``m_flow`` values are
written with Modelica-style signs: positive means flow into that component.
"""
def __init__(
self,
*,
components: TestMqlPneumaticBranchComponents,
initial_state_vector: Callable[[], list[float]],
apply_state_vector: Callable[[list[float]], None],
) -> None:
self.components = components
self._initial_state_vector = initial_state_vector
self._apply_state_vector = apply_state_vector
def initial_state_vector(self) -> list[float]:
return self._initial_state_vector()
def apply_state_vector(self, values: list[float]) -> None:
self._apply_state_vector(values)
def snapshot(
self,
state_vector: list[float] | None = None,
) -> TestMqlPneumaticSnapshot:
if state_vector is not None:
self._apply_state_vector(state_vector)
upstream = self.components.upstream_volume.properties()
downstream = self.components.downstream_volume.properties()
flow = self._solve_branch_flow(upstream, downstream)
upstream_inlet_h = self.components.upstream_volume.connection_inlet_enthalpy(
port_m_flow=-flow,
connected_h=downstream.h,
internal_h=upstream.h,
)
downstream_inlet_h = self.components.downstream_volume.connection_inlet_enthalpy(
port_m_flow=flow,
connected_h=upstream.h,
internal_h=downstream.h,
)
branch = TestMqlPneumaticBranchState(
name=self.components.name,
upstream=upstream,
downstream=downstream,
flow=flow,
upstream_inlet_h=upstream_inlet_h,
downstream_inlet_h=downstream_inlet_h,
)
self._write_port_states(branch)
return TestMqlPneumaticSnapshot(branch=branch)
def _solve_branch_flow(
self,
upstream: ThermodynamicProperties,
downstream: ThermodynamicProperties,
) -> float:
upstream_temperature = upstream.T if upstream.p >= downstream.p else downstream.T
return self.components.orifice.mass_flow(
upstream.p,
downstream.p,
upstream_temperature,
)
def _write_port_states(self, branch: TestMqlPneumaticBranchState) -> None:
upstream_volume = self.components.upstream_volume
downstream_volume = self.components.downstream_volume
orifice = self.components.orifice
upstream_volume.port_a.p = branch.upstream.p
upstream_volume.port_a.m_flow = -branch.flow
upstream_volume.port_a.h_outflow = branch.upstream.h
orifice.port_a.p = branch.upstream.p
orifice.port_a.m_flow = branch.flow
orifice.port_a.h_outflow = branch.upstream.h
orifice.port_b.p = branch.downstream.p
orifice.port_b.m_flow = -branch.flow
orifice.port_b.h_outflow = branch.downstream.h
downstream_volume.port_a.p = branch.downstream.p
downstream_volume.port_a.m_flow = branch.flow
downstream_volume.port_a.h_outflow = branch.downstream.h
def branch_derivatives(
self,
snapshot: TestMqlPneumaticSnapshot,
) -> tuple[VolumeState, VolumeState]:
flow = snapshot.flow
upstream_derivative = self.components.upstream_volume.derivatives(
inlet_h=snapshot.branch.upstream_inlet_h,
m_flow=-flow,
)
downstream_derivative = self.components.downstream_volume.derivatives(
inlet_h=snapshot.branch.downstream_inlet_h,
m_flow=flow,
)
return upstream_derivative, downstream_derivative
def rhs(self, state_vector: list[float]) -> list[float]:
snapshot = self.snapshot(state_vector)
upstream_derivative, downstream_derivative = self.branch_derivatives(snapshot)
return [
*upstream_derivative.as_vector(),
*downstream_derivative.as_vector(),
]