239 lines
7.8 KiB
Python
239 lines
7.8 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from PythonModels.components.amesim_mechanical import (
|
|
AmesimElasticEndstop,
|
|
AmesimPistonGeometry,
|
|
circular_area,
|
|
mm_to_m,
|
|
)
|
|
from PythonModels.reporting.amesim_results import AmesimResults
|
|
from PythonModels.reporting.test_mql_variables import (
|
|
TestMqlVariableCatalog,
|
|
build_test_mql_variable_catalog,
|
|
)
|
|
from PythonModels.systems.test_mql_config import TestMqlConfig, TestMqlResolvedComponent
|
|
|
|
|
|
MM_TO_M = 1.0e-3
|
|
N_PER_MM_TO_N_PER_M = 1.0e3
|
|
N_PER_MM_PER_S_TO_N_PER_M_PER_S = 1.0e3
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TestMqlPistonSpec:
|
|
alias: str
|
|
piston_diameter_m: float
|
|
rod_diameter_m: float
|
|
zero_displacement_m: float
|
|
piston_area_m2: float
|
|
rod_area_m2: float
|
|
annulus_area_m2: float
|
|
data_paths: tuple[str, ...]
|
|
|
|
def geometry(self) -> AmesimPistonGeometry:
|
|
return AmesimPistonGeometry(
|
|
piston_diameter_m=self.piston_diameter_m,
|
|
rod_diameter_m=self.rod_diameter_m,
|
|
zero_length_m=self.zero_displacement_m,
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TestMqlMassEndstopSpec:
|
|
alias: str
|
|
mass_kg: float
|
|
xmin_m: float
|
|
xmax_m: float
|
|
min_stiffness_n_per_m: float
|
|
max_stiffness_n_per_m: float
|
|
min_damping_n_per_m_per_s: float
|
|
max_damping_n_per_m_per_s: float
|
|
min_penetration_m: float
|
|
max_penetration_m: float
|
|
rest_coeff: float
|
|
data_paths: tuple[str, ...]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TestMqlElasticEndstopSpec:
|
|
alias: str
|
|
gap_m: float
|
|
contact_stiffness_n_per_m: float
|
|
contact_damping_n_per_m_per_s: float
|
|
spring_diameter_m: float
|
|
wire_diameter_m: float
|
|
data_paths: tuple[str, ...]
|
|
|
|
def endstop(self) -> AmesimElasticEndstop:
|
|
return AmesimElasticEndstop(
|
|
contact_stiffness_n_per_m=self.contact_stiffness_n_per_m,
|
|
contact_damping_n_per_m_per_s=self.contact_damping_n_per_m_per_s,
|
|
gap0_m=self.gap_m,
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TestMqlMechanicalNodeSpec:
|
|
alias: str
|
|
port_count: int
|
|
sum_mode: int
|
|
data_paths: tuple[str, ...]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TestMqlMechanicalAssembly:
|
|
pistons: dict[str, TestMqlPistonSpec]
|
|
masses: dict[str, TestMqlMassEndstopSpec]
|
|
elastic_endstops: dict[str, TestMqlElasticEndstopSpec]
|
|
mechanical_nodes: dict[str, TestMqlMechanicalNodeSpec]
|
|
zero_force_sources: tuple[str, ...]
|
|
force_connectors: tuple[str, ...]
|
|
|
|
@property
|
|
def component_count(self) -> int:
|
|
return (
|
|
len(self.pistons)
|
|
+ len(self.masses)
|
|
+ len(self.elastic_endstops)
|
|
+ len(self.mechanical_nodes)
|
|
+ len(self.zero_force_sources)
|
|
+ len(self.force_connectors)
|
|
)
|
|
|
|
@property
|
|
def aliases(self) -> tuple[str, ...]:
|
|
return tuple(
|
|
[
|
|
*self.pistons,
|
|
*self.masses,
|
|
*self.elastic_endstops,
|
|
*self.mechanical_nodes,
|
|
*self.zero_force_sources,
|
|
*self.force_connectors,
|
|
]
|
|
)
|
|
|
|
|
|
def build_test_mql_mechanical_assembly(
|
|
config: TestMqlConfig | None = None,
|
|
amesim_results: AmesimResults | None = None,
|
|
variable_catalog: TestMqlVariableCatalog | None = None,
|
|
) -> TestMqlMechanicalAssembly:
|
|
config = config or TestMqlConfig.from_amesim_specs()
|
|
if variable_catalog is None and amesim_results is not None:
|
|
variable_catalog = build_test_mql_variable_catalog(amesim_results)
|
|
|
|
pistons = {
|
|
component.alias: _build_piston(component, variable_catalog)
|
|
for component in config.components_by_submodel("PNRP17")
|
|
}
|
|
masses = {
|
|
component.alias: _build_mass(component, variable_catalog)
|
|
for component in config.components_by_submodel("MECMAS21")
|
|
}
|
|
elastic_endstops = {
|
|
component.alias: _build_elastic_endstop(component, variable_catalog)
|
|
for component in config.components_by_submodel("LSTP00A")
|
|
}
|
|
mechanical_nodes = {
|
|
component.alias: _build_mechanical_node(component, variable_catalog)
|
|
for component in config.components_by_submodel("LMECHN1")
|
|
}
|
|
zero_force_sources = tuple(component.alias for component in config.components_by_submodel("F000"))
|
|
force_connectors = tuple(component.alias for component in config.components_by_submodel("FORC"))
|
|
return TestMqlMechanicalAssembly(
|
|
pistons=pistons,
|
|
masses=masses,
|
|
elastic_endstops=elastic_endstops,
|
|
mechanical_nodes=mechanical_nodes,
|
|
zero_force_sources=zero_force_sources,
|
|
force_connectors=force_connectors,
|
|
)
|
|
|
|
|
|
def _build_piston(
|
|
component: TestMqlResolvedComponent,
|
|
variable_catalog: TestMqlVariableCatalog | None,
|
|
) -> TestMqlPistonSpec:
|
|
geometry = AmesimPistonGeometry(
|
|
piston_diameter_m=mm_to_m(component.parameter_value("dp")),
|
|
rod_diameter_m=mm_to_m(component.parameter_value("dr")),
|
|
zero_length_m=mm_to_m(component.parameter_value("x0")),
|
|
)
|
|
return TestMqlPistonSpec(
|
|
alias=component.alias,
|
|
piston_diameter_m=geometry.piston_diameter_m,
|
|
rod_diameter_m=geometry.rod_diameter_m,
|
|
zero_displacement_m=geometry.zero_length_m,
|
|
piston_area_m2=geometry.piston_area_m2,
|
|
rod_area_m2=geometry.rod_area_m2,
|
|
annulus_area_m2=geometry.annulus_area_m2,
|
|
data_paths=_data_paths(variable_catalog, component.alias),
|
|
)
|
|
|
|
|
|
def _build_mass(
|
|
component: TestMqlResolvedComponent,
|
|
variable_catalog: TestMqlVariableCatalog | None,
|
|
) -> TestMqlMassEndstopSpec:
|
|
return TestMqlMassEndstopSpec(
|
|
alias=component.alias,
|
|
mass_kg=component.parameter_value("mass"),
|
|
xmin_m=component.parameter_value("xmin"),
|
|
xmax_m=component.parameter_value("xmax"),
|
|
min_stiffness_n_per_m=n_per_mm_to_n_per_m(component.parameter_value("Kbmin")),
|
|
max_stiffness_n_per_m=n_per_mm_to_n_per_m(component.parameter_value("Kbmax")),
|
|
min_damping_n_per_m_per_s=n_per_mm_per_s_to_n_per_m_per_s(component.parameter_value("Dbmin")),
|
|
max_damping_n_per_m_per_s=n_per_mm_per_s_to_n_per_m_per_s(component.parameter_value("Dbmax")),
|
|
min_penetration_m=mm_to_m(component.parameter_value("Pdmin")),
|
|
max_penetration_m=mm_to_m(component.parameter_value("Pdmax")),
|
|
rest_coeff=component.parameter_value("restcoeff"),
|
|
data_paths=_data_paths(variable_catalog, component.alias),
|
|
)
|
|
|
|
|
|
def _build_elastic_endstop(
|
|
component: TestMqlResolvedComponent,
|
|
variable_catalog: TestMqlVariableCatalog | None,
|
|
) -> TestMqlElasticEndstopSpec:
|
|
return TestMqlElasticEndstopSpec(
|
|
alias=component.alias,
|
|
gap_m=mm_to_m(component.parameter_value("gap0")),
|
|
contact_stiffness_n_per_m=component.parameter_value("kcont"),
|
|
contact_damping_n_per_m_per_s=component.parameter_value("rcont"),
|
|
spring_diameter_m=mm_to_m(component.parameter_value("sdiam")),
|
|
wire_diameter_m=mm_to_m(component.parameter_value("wdiam")),
|
|
data_paths=_data_paths(variable_catalog, component.alias),
|
|
)
|
|
|
|
|
|
def _build_mechanical_node(
|
|
component: TestMqlResolvedComponent,
|
|
variable_catalog: TestMqlVariableCatalog | None,
|
|
) -> TestMqlMechanicalNodeSpec:
|
|
return TestMqlMechanicalNodeSpec(
|
|
alias=component.alias,
|
|
port_count=int(component.parameter_value("v1")),
|
|
sum_mode=int(component.parameter_value("sum")),
|
|
data_paths=_data_paths(variable_catalog, component.alias),
|
|
)
|
|
|
|
|
|
def n_per_mm_to_n_per_m(value: float) -> float:
|
|
return value * N_PER_MM_TO_N_PER_M
|
|
|
|
|
|
def n_per_mm_per_s_to_n_per_m_per_s(value: float) -> float:
|
|
return value * N_PER_MM_PER_S_TO_N_PER_M_PER_S
|
|
|
|
|
|
def _data_paths(
|
|
variable_catalog: TestMqlVariableCatalog | None,
|
|
alias: str,
|
|
) -> tuple[str, ...]:
|
|
if variable_catalog is None:
|
|
return ()
|
|
return variable_catalog.data_paths_for_owner(alias)
|