217 lines
5.7 KiB
Python
217 lines
5.7 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from math import isfinite
|
|
from typing import Literal
|
|
|
|
|
|
ResultVariableScope = Literal["component", "port"]
|
|
ParameterEditor = Literal[
|
|
"amesimGasReference",
|
|
"amesimGasPropertyModel",
|
|
]
|
|
|
|
|
|
SI_UNIT_BY_QUANTITY: dict[str, str] = {
|
|
"acceleration": "m/s2",
|
|
"area": "m2",
|
|
"dimensionless": "",
|
|
"density": "kg/m³",
|
|
"flow_coefficient": "kg/(s*Pa^0.5)",
|
|
"force": "N",
|
|
"heat_transfer_coefficient": "W/(m2*K)",
|
|
"internal_energy": "J",
|
|
"length": "m",
|
|
"mass": "kg",
|
|
"mass_flow": "kg/s",
|
|
"pressure": "Pa",
|
|
"specific_enthalpy": "J/kg",
|
|
"specific_internal_energy": "J/kg",
|
|
"temperature": "K",
|
|
"translational_damping": "N/(m/s)",
|
|
"translational_stiffness": "N/m",
|
|
"time": "s",
|
|
"velocity": "m/s",
|
|
"volume": "m3",
|
|
"volume_flow": "m3/s",
|
|
"windage": "N/(m/s)^2",
|
|
}
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ParameterOption:
|
|
"""One numeric choice exposed by a catalog-backed parameter editor."""
|
|
|
|
value: float
|
|
label: str
|
|
|
|
def as_interface_dict(self) -> dict[str, object]:
|
|
return {
|
|
"value": self.value,
|
|
"label": self.label,
|
|
}
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ParameterDefinition:
|
|
"""User-configurable model input expressed in the backend SI contract."""
|
|
|
|
name: str
|
|
default: float
|
|
label: str = ""
|
|
quantity: str = "dimensionless"
|
|
unit: str = ""
|
|
minimum: float | None = None
|
|
maximum: float | None = None
|
|
minimum_exclusive: bool = False
|
|
editor: ParameterEditor | None = None
|
|
options: tuple[ParameterOption, ...] = ()
|
|
description: str = ""
|
|
|
|
def validation_message(self, value: float) -> str | None:
|
|
if not isfinite(value):
|
|
return "must be finite"
|
|
if self.minimum is not None:
|
|
if self.minimum_exclusive and value <= self.minimum:
|
|
return f"must be greater than {self.minimum:g}"
|
|
if not self.minimum_exclusive and value < self.minimum:
|
|
return f"must be at least {self.minimum:g}"
|
|
if self.maximum is not None and value > self.maximum:
|
|
return f"must be at most {self.maximum:g}"
|
|
if self.options and value not in {
|
|
float(option.value) for option in self.options
|
|
}:
|
|
available = ", ".join(f"{option.value:g}" for option in self.options)
|
|
return f"must be one of {available}"
|
|
return None
|
|
|
|
def as_interface_dict(self, *, value: float | None = None) -> dict[str, object]:
|
|
payload: dict[str, object] = {
|
|
"name": self.name,
|
|
"label": self.label or self.name,
|
|
"quantity": self.quantity,
|
|
"unit": self.unit,
|
|
"default": self.default,
|
|
"minimumExclusive": self.minimum_exclusive,
|
|
}
|
|
if self.minimum is not None:
|
|
payload["minimum"] = self.minimum
|
|
if self.maximum is not None:
|
|
payload["maximum"] = self.maximum
|
|
if self.editor is not None:
|
|
payload["editor"] = self.editor
|
|
if self.options:
|
|
payload["options"] = [
|
|
option.as_interface_dict() for option in self.options
|
|
]
|
|
if self.description:
|
|
payload["description"] = self.description
|
|
if value is not None:
|
|
payload["value"] = value
|
|
return payload
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ResultVariableDefinition:
|
|
"""Component-relative declaration of a user-visible simulation result."""
|
|
|
|
name: str
|
|
label: str
|
|
quantity: str
|
|
unit: str = ""
|
|
category: str = "derived"
|
|
order: int = 0
|
|
visible: bool = True
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ResultVariableMetadata:
|
|
"""A result declaration bound to one concrete component instance."""
|
|
|
|
key: str
|
|
component_id: str
|
|
component_type: str
|
|
scope: ResultVariableScope
|
|
name: str
|
|
label: str
|
|
quantity: str
|
|
unit: str
|
|
category: str
|
|
order: int
|
|
port_name: str | None = None
|
|
|
|
def as_dict(self) -> dict[str, object]:
|
|
return {
|
|
"key": self.key,
|
|
"componentId": self.component_id,
|
|
"componentType": self.component_type,
|
|
"scope": self.scope,
|
|
"portName": self.port_name,
|
|
"name": self.name,
|
|
"label": self.label,
|
|
"quantity": self.quantity,
|
|
"unit": self.unit,
|
|
"category": self.category,
|
|
"order": self.order,
|
|
}
|
|
|
|
|
|
THERMODYNAMIC_VOLUME_RESULT_VARIABLES = (
|
|
ResultVariableDefinition(
|
|
name="m",
|
|
label="质量",
|
|
quantity="mass",
|
|
unit="kg",
|
|
category="state",
|
|
order=10,
|
|
),
|
|
ResultVariableDefinition(
|
|
name="U",
|
|
label="内能",
|
|
quantity="internal_energy",
|
|
unit="J",
|
|
category="state",
|
|
order=20,
|
|
),
|
|
ResultVariableDefinition(
|
|
name="p",
|
|
label="压力",
|
|
quantity="pressure",
|
|
unit="Pa",
|
|
category="thermodynamic",
|
|
order=30,
|
|
),
|
|
ResultVariableDefinition(
|
|
name="T",
|
|
label="温度",
|
|
quantity="temperature",
|
|
unit="K",
|
|
category="thermodynamic",
|
|
order=40,
|
|
),
|
|
ResultVariableDefinition(
|
|
name="rho",
|
|
label="密度",
|
|
quantity="density",
|
|
unit="kg/m³",
|
|
category="thermodynamic",
|
|
order=50,
|
|
),
|
|
ResultVariableDefinition(
|
|
name="u",
|
|
label="比内能",
|
|
quantity="specific_internal_energy",
|
|
unit="J/kg",
|
|
category="thermodynamic",
|
|
order=60,
|
|
),
|
|
ResultVariableDefinition(
|
|
name="h",
|
|
label="比焓",
|
|
quantity="specific_enthalpy",
|
|
unit="J/kg",
|
|
category="thermodynamic",
|
|
order=70,
|
|
),
|
|
)
|