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"], )