接入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, ...]
|
||||
|
||||
|
||||
@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)
|
||||
class TestMqlMechanicalAssembly:
|
||||
pistons: dict[str, TestMqlPistonSpec]
|
||||
masses: dict[str, TestMqlMassEndstopSpec]
|
||||
elastic_endstops: dict[str, TestMqlElasticEndstopSpec]
|
||||
mechanical_nodes: dict[str, TestMqlMechanicalNodeSpec]
|
||||
piecewise_signals: dict[str, TestMqlPiecewiseLinearSignalSpec]
|
||||
force_connectors: dict[str, TestMqlForceConnectorSpec]
|
||||
zero_force_sources: tuple[str, ...]
|
||||
force_connectors: tuple[str, ...]
|
||||
|
||||
@property
|
||||
def component_count(self) -> int:
|
||||
@@ -125,8 +173,9 @@ class TestMqlMechanicalAssembly:
|
||||
+ len(self.masses)
|
||||
+ len(self.elastic_endstops)
|
||||
+ len(self.mechanical_nodes)
|
||||
+ len(self.zero_force_sources)
|
||||
+ len(self.piecewise_signals)
|
||||
+ len(self.force_connectors)
|
||||
+ len(self.zero_force_sources)
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -137,8 +186,9 @@ class TestMqlMechanicalAssembly:
|
||||
*self.masses,
|
||||
*self.elastic_endstops,
|
||||
*self.mechanical_nodes,
|
||||
*self.zero_force_sources,
|
||||
*self.piecewise_signals,
|
||||
*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)
|
||||
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"))
|
||||
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,
|
||||
piecewise_signals=piecewise_signals,
|
||||
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:
|
||||
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.elastic_endstops), 8)
|
||||
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(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:
|
||||
closure = TestMqlMechanicalMassClosure(self.assembly)
|
||||
|
||||
Reference in new issue
Block a user