公开更多 AMESim 组件并接入信号机械闭环

This commit is contained in:
huojiarong committed 2026-07-30 10:11:08 +00:00
1 parent db4bdb4b70
commit 3f51a124d6
28 files changed
+3397 -58

No files matched your search

@@ -0,0 +1 @@
"""AMESim mechanical components."""
@@ -0,0 +1,293 @@
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(),
}