接入test_mql外力信号边界
This commit is contained in:
1 parent
c8de46fac2
commit
bb4a71c612
2 files changed
+136
-7
No files matched your search
@@ -109,14 +109,62 @@ class TestMqlMechanicalNodeSpec:
|
|||||||
data_paths: tuple[str, ...]
|
data_paths: tuple[str, ...]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class TestMqlPiecewiseLinearSignalSpec:
|
||||||
|
alias: str
|
||||||
|
t_start_s: float
|
||||||
|
starts: tuple[float, ...]
|
||||||
|
ends: tuple[float, ...]
|
||||||
|
durations_s: tuple[float, ...]
|
||||||
|
stage_count: int
|
||||||
|
is_cyclic: bool
|
||||||
|
data_paths: tuple[str, ...]
|
||||||
|
|
||||||
|
def output_at(self, time_s: float) -> float:
|
||||||
|
if self.stage_count <= 0:
|
||||||
|
return 0.0
|
||||||
|
elapsed = max(time_s - self.t_start_s, 0.0)
|
||||||
|
active_durations = self.durations_s[: self.stage_count]
|
||||||
|
total_duration = sum(active_durations)
|
||||||
|
if self.is_cyclic and total_duration > 0.0:
|
||||||
|
elapsed = elapsed % total_duration
|
||||||
|
|
||||||
|
stage_start_time = 0.0
|
||||||
|
for index, duration in enumerate(active_durations):
|
||||||
|
stage_end_time = stage_start_time + duration
|
||||||
|
if elapsed < stage_end_time or index == self.stage_count - 1:
|
||||||
|
if duration <= 0.0:
|
||||||
|
return self.ends[index]
|
||||||
|
fraction = (elapsed - stage_start_time) / duration
|
||||||
|
return self.starts[index] + fraction * (self.ends[index] - self.starts[index])
|
||||||
|
stage_start_time = stage_end_time
|
||||||
|
return self.ends[self.stage_count - 1]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class TestMqlForceConnectorSpec:
|
||||||
|
alias: str
|
||||||
|
signal_alias: str
|
||||||
|
target_mass_alias: str
|
||||||
|
data_paths: tuple[str, ...]
|
||||||
|
|
||||||
|
def force_at(
|
||||||
|
self,
|
||||||
|
time_s: float,
|
||||||
|
signals: dict[str, TestMqlPiecewiseLinearSignalSpec],
|
||||||
|
) -> float:
|
||||||
|
return signals[self.signal_alias].output_at(time_s)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class TestMqlMechanicalAssembly:
|
class TestMqlMechanicalAssembly:
|
||||||
pistons: dict[str, TestMqlPistonSpec]
|
pistons: dict[str, TestMqlPistonSpec]
|
||||||
masses: dict[str, TestMqlMassEndstopSpec]
|
masses: dict[str, TestMqlMassEndstopSpec]
|
||||||
elastic_endstops: dict[str, TestMqlElasticEndstopSpec]
|
elastic_endstops: dict[str, TestMqlElasticEndstopSpec]
|
||||||
mechanical_nodes: dict[str, TestMqlMechanicalNodeSpec]
|
mechanical_nodes: dict[str, TestMqlMechanicalNodeSpec]
|
||||||
|
piecewise_signals: dict[str, TestMqlPiecewiseLinearSignalSpec]
|
||||||
|
force_connectors: dict[str, TestMqlForceConnectorSpec]
|
||||||
zero_force_sources: tuple[str, ...]
|
zero_force_sources: tuple[str, ...]
|
||||||
force_connectors: tuple[str, ...]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def component_count(self) -> int:
|
def component_count(self) -> int:
|
||||||
@@ -125,8 +173,9 @@ class TestMqlMechanicalAssembly:
|
|||||||
+ len(self.masses)
|
+ len(self.masses)
|
||||||
+ len(self.elastic_endstops)
|
+ len(self.elastic_endstops)
|
||||||
+ len(self.mechanical_nodes)
|
+ len(self.mechanical_nodes)
|
||||||
+ len(self.zero_force_sources)
|
+ len(self.piecewise_signals)
|
||||||
+ len(self.force_connectors)
|
+ len(self.force_connectors)
|
||||||
|
+ len(self.zero_force_sources)
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -137,8 +186,9 @@ class TestMqlMechanicalAssembly:
|
|||||||
*self.masses,
|
*self.masses,
|
||||||
*self.elastic_endstops,
|
*self.elastic_endstops,
|
||||||
*self.mechanical_nodes,
|
*self.mechanical_nodes,
|
||||||
*self.zero_force_sources,
|
*self.piecewise_signals,
|
||||||
*self.force_connectors,
|
*self.force_connectors,
|
||||||
|
*self.zero_force_sources,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -264,15 +314,23 @@ def build_test_mql_mechanical_assembly(
|
|||||||
component.alias: _build_mechanical_node(component, variable_catalog)
|
component.alias: _build_mechanical_node(component, variable_catalog)
|
||||||
for component in config.components_by_submodel("LMECHN1")
|
for component in config.components_by_submodel("LMECHN1")
|
||||||
}
|
}
|
||||||
|
piecewise_signals = {
|
||||||
|
component.alias: _build_piecewise_signal(component, variable_catalog)
|
||||||
|
for component in config.components_by_submodel("UD00")
|
||||||
|
}
|
||||||
|
force_connectors = {
|
||||||
|
component.alias: _build_force_connector(component, variable_catalog)
|
||||||
|
for component in config.components_by_submodel("FORC")
|
||||||
|
}
|
||||||
zero_force_sources = tuple(component.alias for component in config.components_by_submodel("F000"))
|
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(
|
return TestMqlMechanicalAssembly(
|
||||||
pistons=pistons,
|
pistons=pistons,
|
||||||
masses=masses,
|
masses=masses,
|
||||||
elastic_endstops=elastic_endstops,
|
elastic_endstops=elastic_endstops,
|
||||||
mechanical_nodes=mechanical_nodes,
|
mechanical_nodes=mechanical_nodes,
|
||||||
zero_force_sources=zero_force_sources,
|
piecewise_signals=piecewise_signals,
|
||||||
force_connectors=force_connectors,
|
force_connectors=force_connectors,
|
||||||
|
zero_force_sources=zero_force_sources,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -356,6 +414,45 @@ def _build_mechanical_node(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_piecewise_signal(
|
||||||
|
component: TestMqlResolvedComponent,
|
||||||
|
variable_catalog: TestMqlVariableCatalog | None,
|
||||||
|
) -> TestMqlPiecewiseLinearSignalSpec:
|
||||||
|
starts = tuple(component.parameter_value(f"start{index}") for index in range(1, 9))
|
||||||
|
ends = tuple(component.parameter_value(f"end{index}") for index in range(1, 9))
|
||||||
|
durations = tuple(component.parameter_value(f"t{index}") for index in range(1, 9))
|
||||||
|
return TestMqlPiecewiseLinearSignalSpec(
|
||||||
|
alias=component.alias,
|
||||||
|
t_start_s=component.parameter_value("tstart"),
|
||||||
|
starts=starts,
|
||||||
|
ends=ends,
|
||||||
|
durations_s=durations,
|
||||||
|
stage_count=int(component.parameter_value("nstages")),
|
||||||
|
is_cyclic=bool(int(component.parameter_value("iscyclic"))),
|
||||||
|
data_paths=_data_paths(variable_catalog, component.alias),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_force_connector(
|
||||||
|
component: TestMqlResolvedComponent,
|
||||||
|
variable_catalog: TestMqlVariableCatalog | None,
|
||||||
|
) -> TestMqlForceConnectorSpec:
|
||||||
|
signal_alias_by_force_connector = {
|
||||||
|
"forcecon_1": "piecewiselinear",
|
||||||
|
"forcecon_2": "piecewiselinear_1",
|
||||||
|
}
|
||||||
|
target_mass_by_force_connector = {
|
||||||
|
"forcecon_1": "mass_friction_endstops_19",
|
||||||
|
"forcecon_2": "mass_friction_endstops_18",
|
||||||
|
}
|
||||||
|
return TestMqlForceConnectorSpec(
|
||||||
|
alias=component.alias,
|
||||||
|
signal_alias=signal_alias_by_force_connector[component.alias],
|
||||||
|
target_mass_alias=target_mass_by_force_connector[component.alias],
|
||||||
|
data_paths=_data_paths(variable_catalog, component.alias),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def n_per_mm_to_n_per_m(value: float) -> float:
|
def n_per_mm_to_n_per_m(value: float) -> float:
|
||||||
return value * N_PER_MM_TO_N_PER_M
|
return value * N_PER_MM_TO_N_PER_M
|
||||||
|
|
||||||
|
|||||||
@@ -36,9 +36,41 @@ class TestMqlMechanicalAssemblyTests(unittest.TestCase):
|
|||||||
self.assertEqual(len(self.assembly.masses), 10)
|
self.assertEqual(len(self.assembly.masses), 10)
|
||||||
self.assertEqual(len(self.assembly.elastic_endstops), 8)
|
self.assertEqual(len(self.assembly.elastic_endstops), 8)
|
||||||
self.assertEqual(len(self.assembly.mechanical_nodes), 2)
|
self.assertEqual(len(self.assembly.mechanical_nodes), 2)
|
||||||
self.assertEqual(len(self.assembly.zero_force_sources), 16)
|
self.assertEqual(len(self.assembly.piecewise_signals), 2)
|
||||||
self.assertEqual(len(self.assembly.force_connectors), 2)
|
self.assertEqual(len(self.assembly.force_connectors), 2)
|
||||||
self.assertEqual(self.assembly.component_count, 46)
|
self.assertEqual(len(self.assembly.zero_force_sources), 16)
|
||||||
|
self.assertEqual(self.assembly.component_count, 48)
|
||||||
|
|
||||||
|
def test_piecewise_force_sources_match_amesim_samples(self) -> None:
|
||||||
|
front_signal = self.assembly.piecewise_signals["piecewiselinear_1"]
|
||||||
|
rear_signal = self.assembly.piecewise_signals["piecewiselinear"]
|
||||||
|
|
||||||
|
self.assertAlmostEqual(front_signal.output_at(0.0), 1.0e12)
|
||||||
|
self.assertAlmostEqual(front_signal.output_at(0.8000000000000009), 0.0)
|
||||||
|
self.assertAlmostEqual(rear_signal.output_at(0.0), 1.0e17)
|
||||||
|
self.assertAlmostEqual(rear_signal.output_at(0.8000000000000009), 49000.0)
|
||||||
|
self.assertEqual(
|
||||||
|
self.assembly.force_connectors["forcecon_2"].target_mass_alias,
|
||||||
|
"mass_friction_endstops_18",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.assembly.force_connectors["forcecon_1"].target_mass_alias,
|
||||||
|
"mass_friction_endstops_19",
|
||||||
|
)
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
self.assembly.force_connectors["forcecon_2"].force_at(
|
||||||
|
0.0,
|
||||||
|
self.assembly.piecewise_signals,
|
||||||
|
),
|
||||||
|
self.amesim_results.series("force@forcecon_2")[0],
|
||||||
|
)
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
self.assembly.force_connectors["forcecon_1"].force_at(
|
||||||
|
0.8000000000000009,
|
||||||
|
self.assembly.piecewise_signals,
|
||||||
|
),
|
||||||
|
self.amesim_results.series("force@forcecon_1")[80],
|
||||||
|
)
|
||||||
|
|
||||||
def test_mechanical_mass_closure_exposes_all_mecmas21_states(self) -> None:
|
def test_mechanical_mass_closure_exposes_all_mecmas21_states(self) -> None:
|
||||||
closure = TestMqlMechanicalMassClosure(self.assembly)
|
closure = TestMqlMechanicalMassClosure(self.assembly)
|
||||||
|
|||||||
Reference in new issue
Block a user