from __future__ import annotations from abc import ABC, abstractmethod from collections.abc import Mapping from typing import Any from PythonModels.core.equations import EquationResidual from PythonModels.core.ports import PortDefinition, PortState class Component(ABC): def __init__(self, name: str) -> None: self.name = name self.model_type = self.__class__.__name__.lower() self._ports: dict[str, PortState] = {} @property def ports(self) -> dict[str, PortState]: return dict(self._ports) @property def port_definitions(self) -> tuple[PortDefinition, ...]: return tuple( port.definition for port in self._ports.values() if port.definition is not None ) def register_port(self, port: PortState) -> PortState: definition = port.definition if definition is None: raise ValueError(f"Component {self.name} cannot register an undefined port.") if definition.name in self._ports: raise ValueError(f"Duplicate port {self.name}.{definition.name}.") self._ports[definition.name] = port return port def get_port(self, name: str) -> PortState: try: return self._ports[name] except KeyError as exc: raise ValueError(f"Component {self.name} has no port named {name}.") from exc def pressure_flow_equation_residuals(self) -> tuple[EquationResidual, ...]: """Return algebraic residuals after the network assigns port states.""" return () def update_stream_outflows(self, connected_h: Mapping[str, float]) -> None: """Update connector outflow properties from current flow directions.""" return None class DynamicComponent(Component): state_size = 2 @staticmethod def actual_stream_enthalpy( port_m_flow: float, connected_h: float, internal_h: float, ) -> float: """Approximate `actualStream(port.h_outflow)` for a mixed control volume port.""" return connected_h if port_m_flow > 0.0 else internal_h def connection_inlet_enthalpy( self, port_m_flow: float, connected_h: float, internal_h: float, ) -> float: """Resolve the enthalpy convected into this control volume through one port.""" return self.actual_stream_enthalpy( port_m_flow=port_m_flow, connected_h=connected_h, internal_h=internal_h, ) @abstractmethod def get_state_vector(self) -> list[float]: raise NotImplementedError @abstractmethod def set_state_vector(self, values: list[float]) -> None: raise NotImplementedError def refresh_thermodynamic_ports(self) -> Any: raise NotImplementedError def state_derivative_from_ports( self, connected_h: Mapping[str, float], ) -> list[float]: raise NotImplementedError class AlgebraicComponent(Component): """Stateless element described by algebraic constraints only."""