公开更多 AMESim 组件并接入信号机械闭环
This commit is contained in:
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(),
|
||||
}
|
||||
Reference in new issue
Block a user