接入 AMESim 机械接触和节点组件

This commit is contained in:
huojiarong committed 2026-07-30 10:34:29 +00:00
1 parent 2ddaaeb56a
commit e87df789b2
8 files changed
+441 -9

No files matched your search

@@ -28,6 +28,8 @@ LIBRARY = ComponentLibrarySpec(
"app.simulation.components.amesim.mechanical.translational:AmesimF000",
"app.simulation.components.amesim.mechanical.translational:AmesimForc",
"app.simulation.components.amesim.mechanical.translational:AmesimMecmas21",
"app.simulation.components.amesim.mechanical.translational:AmesimLstp00a",
"app.simulation.components.amesim.mechanical.translational:AmesimLmechn1",
"app.simulation.components.amesim.storage.chambers:AmesimPnch023",
"app.simulation.components.amesim.storage.chambers:AmesimPnch012",
"app.simulation.components.amesim.flow.orifices:AmesimPnor001",
@@ -291,3 +291,216 @@ class AmesimMecmas21(DynamicComponent):
"Fmin": self._lower_limit_force(),
"Fmax": self._upper_limit_force(),
}
class AmesimLstp00a(AlgebraicComponent):
"""AMESim LSTP00A first public elastic contact component."""
MODEL_TYPE = "amesim_lstp00a"
MODEL_VERSION = "0.1.0"
PORTS = (
PortDefinition.mechanical_translational("port_1"),
PortDefinition.mechanical_translational("port_2"),
)
PARAMETERS = (
ParameterDefinition("na", 10.0, label="有效圈数", quantity="dimensionless", unit="", minimum=0.0, minimum_exclusive=True),
ParameterDefinition("gap0", 0.0, label="初始间隙", quantity="length", unit="m"),
ParameterDefinition("kcont", 1.0e6, label="接触刚度", quantity="translational_stiffness", unit="N/m", minimum=0.0),
ParameterDefinition("G", 8.57e10, label="剪切模量", quantity="pressure", unit="Pa", minimum=0.0),
ParameterDefinition("sdiam", 0.02, label="弹簧直径", quantity="length", unit="m", minimum=0.0),
ParameterDefinition("wdiam", 0.002, label="线径", quantity="length", unit="m", minimum=0.0),
ParameterDefinition("rcont", 0.0, label="接触阻尼", quantity="translational_damping", unit="N/(m/s)", minimum=0.0),
ParameterDefinition("Pdis", 1.0e-7, label="满阻尼穿透", quantity="length", unit="m", minimum=0.0),
ParameterDefinition("stiffmode", 1.0, label="刚度模式", quantity="dimensionless", unit="", minimum=0.0),
ParameterDefinition("discContactOption", 1.0, label="接触选项", quantity="dimensionless", unit="", minimum=0.0),
)
RESULT_VARIABLES = (
ResultVariableDefinition("gap", "间隙", "length", "m", "derived", 10),
ResultVariableDefinition("penetration", "穿透", "length", "m", "derived", 20),
ResultVariableDefinition("force", "接触力", "force", "N", "derived", 30),
)
DISPLAY = ComponentDisplaySpec(
label="LSTP00A 弹性接触",
library_id="amesim",
category_id="mechanical",
symbol="generic",
ports=(
PortDisplaySpec("port_1", "left", order=10),
PortDisplaySpec("port_2", "right", order=20),
),
order=40,
)
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.port_1 = self.register_declared_port("port_1")
self.port_2 = self.register_declared_port("port_2")
@classmethod
def create(
cls,
*,
name: str,
medium: IdealGasMedium,
parameters: Mapping[str, float],
) -> "AmesimLstp00a":
for integer_name in ("stiffmode", "discContactOption"):
if not float(parameters[integer_name]).is_integer():
raise ValueError(f"LSTP00A {integer_name} must be an integer.")
return cls(name=name, medium=medium, **dict(parameters))
@property
def gap(self) -> float:
return self.gap0 - (self.port_2.x - self.port_1.x)
@property
def penetration(self) -> float:
return max(-self.gap, 0.0)
@property
def penetration_velocity(self) -> float:
return self.port_2.v - self.port_1.v
@property
def contact_force(self) -> float:
if self.penetration <= 0.0:
return 0.0
return max(self.kcont * self.penetration + self.rcont * self.penetration_velocity, 0.0)
def pressure_flow_equation_residuals(self) -> tuple[EquationResidual, ...]:
force = self.contact_force
return (
EquationResidual(
id=f"{self.name}:port_1_contact_force",
owner="component",
owner_id=self.name,
relation="constitutive",
variables=(f"{self.name}.port_1.f", f"{self.name}.port_1.x", f"{self.name}.port_2.x"),
role="flow",
value=self.port_1.f + force,
),
EquationResidual(
id=f"{self.name}:port_2_contact_force",
owner="component",
owner_id=self.name,
relation="constitutive",
variables=(f"{self.name}.port_2.f", f"{self.name}.port_1.x", f"{self.name}.port_2.x"),
role="flow",
value=self.port_2.f - force,
),
)
def component_result_values(self) -> Mapping[str, float]:
return {
"gap": self.gap,
"penetration": self.penetration,
"force": self.contact_force,
}
class AmesimLmechn1(AlgebraicComponent):
"""AMESim LMECHN1 first public dynamic linear mechanical node."""
MODEL_TYPE = "amesim_lmechn1"
MODEL_VERSION = "0.1.0"
PORTS = tuple(
PortDefinition.mechanical_translational(f"port_{index}")
for index in range(1, 10)
)
PARAMETERS = (
ParameterDefinition("v1", 8.0, label="右侧端口数", quantity="dimensionless", unit="", minimum=1.0, maximum=8.0),
ParameterDefinition("sum", 1.0, label="节点求和模式", quantity="dimensionless", unit="", minimum=0.0),
)
RESULT_VARIABLES = (
ResultVariableDefinition("tforce", "节点合力", "force", "N", "derived", 10),
)
DISPLAY = ComponentDisplaySpec(
label="LMECHN1 线性机械节点",
library_id="amesim",
category_id="mechanical",
symbol="junction",
ports=tuple(
[PortDisplaySpec(f"port_{index}", "left", order=index * 10) for index in range(1, 9)]
+ [PortDisplaySpec("port_9", "right", order=90)]
),
order=50,
)
def __init__(self, name: str, medium: IdealGasMedium, *, v1: float = 8.0, sum: float = 1.0) -> None:
super().__init__(name=name)
self.set_parameter_values({"v1": v1, "sum": sum})
self.v1 = int(v1)
self.sum = int(sum)
for definition in self.PORTS:
setattr(self, definition.name, self.register_declared_port(definition.name))
@classmethod
def create(
cls,
*,
name: str,
medium: IdealGasMedium,
parameters: Mapping[str, float],
) -> "AmesimLmechn1":
for integer_name in ("v1", "sum"):
if not float(parameters[integer_name]).is_integer():
raise ValueError(f"LMECHN1 {integer_name} must be an integer.")
return cls(name=name, medium=medium, v1=parameters["v1"], sum=parameters["sum"])
@property
def active_ports(self) -> tuple[str, ...]:
return tuple(f"port_{index}" for index in range(1, self.v1 + 1)) + ("port_9",)
@property
def total_force(self) -> float:
return sum(self.get_port(port_name).f for port_name in self.active_ports)
def pressure_flow_equation_residuals(self) -> tuple[EquationResidual, ...]:
reference = self.port_9
residuals: list[EquationResidual] = []
for port_name in self.active_ports[:-1]:
port = self.get_port(port_name)
residuals.append(
EquationResidual(
id=f"{self.name}:{port_name}_x_equal",
owner="component",
owner_id=self.name,
relation="equal",
variables=(f"{self.name}.{port_name}.x", f"{self.name}.port_9.x"),
role="effort",
value=port.x - reference.x,
)
)
residuals.append(
EquationResidual(
id=f"{self.name}:{port_name}_v_equal",
owner="component",
owner_id=self.name,
relation="equal",
variables=(f"{self.name}.{port_name}.v", f"{self.name}.port_9.v"),
role="effort",
value=port.v - reference.v,
)
)
residuals.append(
EquationResidual(
id=f"{self.name}:force_balance",
owner="component",
owner_id=self.name,
relation="sumToZero",
variables=tuple(f"{self.name}.{port_name}.f" for port_name in self.active_ports),
role="flow",
value=self.total_force,
)
)
return tuple(residuals)
def component_result_values(self) -> Mapping[str, float]:
return {"tforce": self.total_force}