from __future__ import annotations from collections.abc import Mapping from app.simulation.core.base import AlgebraicComponent, DynamicComponent from app.simulation.core.catalog import ComponentDisplaySpec, PortDisplaySpec from app.simulation.core.equations import EquationResidual from app.simulation.core.metadata import ParameterDefinition, ResultVariableDefinition from app.simulation.core.medium import IdealGasMedium from app.simulation.core.ports import PortDefinition class AmesimF000(AlgebraicComponent): """AMESim F000 zero force source.""" MODEL_TYPE = "amesim_f000" MODEL_VERSION = "0.1.0" PORTS = (PortDefinition.mechanical_translational("port_1"),) PARAMETERS = () RESULT_VARIABLES = () DISPLAY = ComponentDisplaySpec( label="F000 零力源", library_id="amesim", category_id="mechanical", symbol="generic", ports=(PortDisplaySpec("port_1", "right", order=10),), order=10, ) def __init__(self, name: str) -> None: super().__init__(name=name) self.set_parameter_values({}) self.port_1 = self.register_declared_port("port_1") @classmethod def create( cls, *, name: str, medium: IdealGasMedium, parameters: Mapping[str, float], ) -> "AmesimF000": return cls(name=name) def pressure_flow_equation_residuals(self) -> tuple[EquationResidual, ...]: return ( EquationResidual( id=f"{self.name}:zero_force", owner="component", owner_id=self.name, relation="constitutive", variables=(f"{self.name}.port_1.f",), role="flow", value=self.port_1.f, ), ) class AmesimForc(AlgebraicComponent): """AMESim FORC signal-to-force converter.""" MODEL_TYPE = "amesim_forc" MODEL_VERSION = "0.1.0" PORTS = ( PortDefinition.signal("res", nominal_role="input"), PortDefinition.mechanical_translational("port_2"), ) PARAMETERS = () RESULT_VARIABLES = ( ResultVariableDefinition("force", "输出力", "force", "N", "signal", 10), ) DISPLAY = ComponentDisplaySpec( label="FORC 信号转力", library_id="amesim", category_id="mechanical", symbol="signal", ports=( PortDisplaySpec("res", "left", order=10), PortDisplaySpec("port_2", "right", order=20), ), order=20, ) def __init__(self, name: str) -> None: super().__init__(name=name) self.set_parameter_values({}) self.res = self.register_declared_port("res") self.port_2 = self.register_declared_port("port_2") @classmethod def create( cls, *, name: str, medium: IdealGasMedium, parameters: Mapping[str, float], ) -> "AmesimForc": return cls(name=name) @property def output_force(self) -> float: return float(self.res.signal) def pressure_flow_equation_residuals(self) -> tuple[EquationResidual, ...]: return ( EquationResidual( id=f"{self.name}:signal_force", owner="component", owner_id=self.name, relation="constitutive", variables=(f"{self.name}.port_2.f", f"{self.name}.res.signal"), role="flow", value=self.port_2.f + self.output_force, ), ) def component_result_values(self) -> Mapping[str, float]: return {"force": self.output_force} class AmesimMecmas21(DynamicComponent): """AMESim MECMAS21 first public one-dimensional translational mass.""" MODEL_TYPE = "amesim_mecmas21" MODEL_VERSION = "0.1.0" PORTS = ( PortDefinition.mechanical_translational("port_1"), PortDefinition.mechanical_translational("port_2"), ) PARAMETERS = ( ParameterDefinition("mass", 1.0, label="质量", quantity="mass", unit="kg", minimum=0.0, minimum_exclusive=True), ParameterDefinition("fstick", 0.0, label="静摩擦力", quantity="force", unit="N", minimum=0.0), ParameterDefinition("fcoul", 0.0, label="库仑摩擦力", quantity="force", unit="N", minimum=0.0), ParameterDefinition("rvisc", 0.0, label="黏性摩擦系数", quantity="translational_damping", unit="N/(m/s)", minimum=0.0), ParameterDefinition("wind", 0.0, label="风阻系数", quantity="windage", unit="N/(m/s)^2", minimum=0.0), ParameterDefinition("dvel", 1.0e-6, label="粘滞速度阈值", quantity="velocity", unit="m/s", minimum=0.0), ParameterDefinition("restdvel", 1.0e-6, label="恢复速度阈值", quantity="velocity", unit="m/s", minimum=0.0), ParameterDefinition("restcoeff", 0.65, label="恢复系数", quantity="dimensionless", unit="", minimum=0.0, maximum=1.0), ParameterDefinition("astrib", 1.0e-3, label="Stribeck 常数", quantity="velocity", unit="m/s", minimum=0.0), ParameterDefinition("xmin", -1.0, label="下位移限位", quantity="length", unit="m"), ParameterDefinition("Kbmin", 1.0e9, label="下限位刚度", quantity="translational_stiffness", unit="N/m", minimum=0.0), ParameterDefinition("Dbmin", 1.0e4, label="下限位阻尼", quantity="translational_damping", unit="N/(m/s)", minimum=0.0), ParameterDefinition("Pdmin", 1.0e-4, label="下限位满阻尼穿透", quantity="length", unit="m", minimum=0.0), ParameterDefinition("xmax", 0.8, label="上位移限位", quantity="length", unit="m"), ParameterDefinition("Kbmax", 1.0e9, label="上限位刚度", quantity="translational_stiffness", unit="N/m", minimum=0.0), ParameterDefinition("Dbmax", 1.0e4, label="上限位阻尼", quantity="translational_damping", unit="N/(m/s)", minimum=0.0), ParameterDefinition("Pdmax", 1.0e-4, label="上限位满阻尼穿透", quantity="length", unit="m", minimum=0.0), ParameterDefinition("theta", 0.0, label="倾角", quantity="dimensionless", unit=""), ParameterDefinition("useFriction", 1.0, label="启用摩擦", quantity="dimensionless", unit="", minimum=0.0, maximum=1.0), ParameterDefinition("stoptype", 4.0, label="限位类型", quantity="dimensionless", unit="", minimum=0.0), ParameterDefinition("discContactOption", 1.0, label="接触选项", quantity="dimensionless", unit="", minimum=0.0), ParameterDefinition("strib", 1.0, label="Stribeck 选项", quantity="dimensionless", unit="", minimum=0.0, maximum=1.0), ParameterDefinition("frictionType", 1.0, label="摩擦类型", quantity="dimensionless", unit="", minimum=0.0), ParameterDefinition("v0", 0.0, label="初始速度", quantity="velocity", unit="m/s"), ParameterDefinition("x0", 0.0, label="初始位移", quantity="length", unit="m"), ) RESULT_VARIABLES = ( ResultVariableDefinition("a", "加速度", "acceleration", "m/s2", "state", 10), ResultVariableDefinition("v", "速度", "velocity", "m/s", "state", 20), ResultVariableDefinition("x", "位移", "length", "m", "state", 30), ResultVariableDefinition("Fvisc", "黏性摩擦力", "force", "N", "derived", 40), ResultVariableDefinition("Ffric", "干摩擦力", "force", "N", "derived", 50), ResultVariableDefinition("Fmin", "下限位力", "force", "N", "derived", 60), ResultVariableDefinition("Fmax", "上限位力", "force", "N", "derived", 70), ) DISPLAY = ComponentDisplaySpec( label="MECMAS21 一维质量", library_id="amesim", category_id="mechanical", symbol="generic", ports=( PortDisplaySpec("port_1", "left", order=10), PortDisplaySpec("port_2", "right", order=20), ), order=30, ) state_size = 2 def __init__(self, name: str, medium: IdealGasMedium, **parameters: float) -> None: super().__init__(name=name) resolved = {definition.name: parameters.get(definition.name, definition.default) for definition in self.PARAMETERS} self.set_parameter_values(resolved) for name, value in resolved.items(): setattr(self, name, float(value)) self.use_friction = bool(int(self.useFriction)) self.port_1 = self.register_declared_port("port_1") self.port_2 = self.register_declared_port("port_2") self.v = float(self.v0) self.x = float(self.x0) self.refresh_thermodynamic_ports() @classmethod def create( cls, *, name: str, medium: IdealGasMedium, parameters: Mapping[str, float], ) -> "AmesimMecmas21": for integer_name in ("useFriction", "stoptype", "discContactOption", "strib", "frictionType"): if not float(parameters[integer_name]).is_integer(): raise ValueError(f"MECMAS21 {integer_name} must be an integer.") return cls(name=name, medium=medium, **dict(parameters)) def get_state_vector(self) -> list[float]: return [self.v, self.x] def set_state_vector(self, values: list[float]) -> None: if len(values) != 2: raise ValueError("MECMAS21 state vector requires [v, x].") self.v = float(values[0]) self.x = float(values[1]) self.refresh_thermodynamic_ports() def refresh_thermodynamic_ports(self) -> None: for port in (self.port_1, self.port_2): port.x = self.x port.v = self.v def pressure_flow_equation_residuals(self) -> tuple[EquationResidual, ...]: return ( self._state_residual("port_1", "x", self.port_1.x - self.x), self._state_residual("port_1", "v", self.port_1.v - self.v), self._state_residual("port_2", "x", self.port_2.x - self.x), self._state_residual("port_2", "v", self.port_2.v - self.v), ) def _state_residual(self, port_name: str, variable: str, value: float) -> EquationResidual: return EquationResidual( id=f"{self.name}:{port_name}_{variable}_state", owner="component", owner_id=self.name, relation="state", variables=(f"{self.name}.{port_name}.{variable}",), role="effort", value=value, ) def _viscous_friction_force(self) -> float: if not self.use_friction: return 0.0 return -self.rvisc * self.v def _windage_force(self) -> float: if not self.use_friction: return 0.0 return -self.wind * self.v * abs(self.v) def _dry_friction_force(self) -> float: if not self.use_friction: return 0.0 if self.v > 0.0: return -self.fcoul if self.v < 0.0: return self.fcoul return 0.0 def _lower_limit_force(self) -> float: penetration = max(self.xmin - self.x, 0.0) if penetration <= 0.0: return 0.0 return self.Kbmin * penetration + max(-self.Dbmin * self.v, 0.0) def _upper_limit_force(self) -> float: penetration = max(self.x - self.xmax, 0.0) if penetration <= 0.0: return 0.0 return self.Kbmax * penetration + max(self.Dbmax * self.v, 0.0) def acceleration(self) -> float: return ( self.port_1.f + self.port_2.f + self._viscous_friction_force() + self._windage_force() + self._dry_friction_force() + self._lower_limit_force() - self._upper_limit_force() ) / self.mass def state_derivative_from_ports(self, connected_h: Mapping[str, float]) -> list[float]: return [self.acceleration(), self.v] def component_result_values(self) -> Mapping[str, float]: return { "a": self.acceleration(), "v": self.v, "x": self.x, "Fvisc": self._viscous_friction_force(), "Ffric": self._dry_friction_force(), "Fmin": self._lower_limit_force(), "Fmax": self._upper_limit_force(), }