exoskeleton/code/test/test_h2_synthetic_pairing.py

233 lines
7.4 KiB
Python

#!/usr/bin/env python3
"""H2 data pairing and conditioning-stratum regression tests."""
from pathlib import Path
import sys
import unittest
import numpy as np
CODE_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(CODE_ROOT))
from experiments.executors import execute_h2_synthetic # noqa: E402
from experiments.plan import build_trial_plan, load_document # noqa: E402
def h2_pairing_specification():
return {
"study_id": "h2_pairing_contract",
"split": "calibration",
"root_seed": 37,
"replicates": 2,
"methods": ["scaled_dls", "undamped_svd"],
"trajectories": [
{
"trajectory_id": "short_dynamic_wrench",
"family": "synthetic_dynamic",
"sample_count": 12,
}
],
"factors": {
"characteristic_length_m": [0.2, 0.4],
"damping": [0.02, 0.05],
"h2_data_root_seed": [1701],
"min_scaled_singular": [0.02],
"model_error_std": [0.02],
"operational_min_scaled_singular": [0.05],
"torque_noise_std_Nm": [0.002, 0.02],
"truth_characteristic_length_m": [0.3],
},
}
def select_trial(
plan,
*,
replicate,
method,
characteristic_length,
damping,
noise,
):
matches = [
trial
for trial in plan["trials"]
if trial["replicate"] == replicate
and trial["method"]["method_id"] == method
and trial["factors"]["characteristic_length_m"]
== characteristic_length
and trial["factors"]["damping"] == damping
and trial["factors"]["torque_noise_std_Nm"] == noise
]
if len(matches) != 1:
raise AssertionError(f"expected one H2 trial, found {len(matches)}")
return matches[0]
class H2SyntheticPairingTest(unittest.TestCase):
def test_algorithm_candidates_reuse_identical_physical_data(self):
plan = build_trial_plan(h2_pairing_specification())
first_trial = select_trial(
plan,
replicate=0,
method="scaled_dls",
characteristic_length=0.2,
damping=0.02,
noise=0.002,
)
second_trial = select_trial(
plan,
replicate=0,
method="undamped_svd",
characteristic_length=0.4,
damping=0.05,
noise=0.002,
)
# The general experiment pair changes with ell/damping. H2's explicit
# physical-data group must still bind both candidates to the same data.
self.assertNotEqual(first_trial["pair_id"], second_trial["pair_id"])
self.assertNotEqual(first_trial["seeds"], second_trial["seeds"])
first = execute_h2_synthetic(first_trial)
second = execute_h2_synthetic(second_trial)
self.assertEqual(
first.metadata["h2_data_group_id"],
second.metadata["h2_data_group_id"],
)
self.assertEqual(
first.metadata["h2_data_seed_record"],
second.metadata["h2_data_seed_record"],
)
self.assertNotEqual(
first.metadata["calibration_id"],
second.metadata["calibration_id"],
"scanned estimator candidates must not share a frozen-looking ID",
)
for field in (
"wrench_reference",
"qd_slave",
"jacobian_truth",
"jacobian_estimator",
"sensor_noise_Nm",
"tau_residual_raw",
):
np.testing.assert_array_equal(
first.samples[field], second.samples[field]
)
self.assertFalse(
np.array_equal(
first.samples["wrench_estimated"],
second.samples["wrench_estimated"],
)
)
def test_replicates_and_physical_factor_cells_get_distinct_data(self):
plan = build_trial_plan(h2_pairing_specification())
baseline = execute_h2_synthetic(
select_trial(
plan,
replicate=0,
method="scaled_dls",
characteristic_length=0.2,
damping=0.02,
noise=0.002,
)
)
next_replicate = execute_h2_synthetic(
select_trial(
plan,
replicate=1,
method="scaled_dls",
characteristic_length=0.2,
damping=0.02,
noise=0.002,
)
)
different_noise = execute_h2_synthetic(
select_trial(
plan,
replicate=0,
method="scaled_dls",
characteristic_length=0.2,
damping=0.02,
noise=0.02,
)
)
self.assertNotEqual(
baseline.metadata["h2_data_group_id"],
next_replicate.metadata["h2_data_group_id"],
)
self.assertNotEqual(
baseline.metadata["h2_data_group_id"],
different_noise.metadata["h2_data_group_id"],
)
self.assertFalse(
np.array_equal(
baseline.samples["jacobian_truth"],
next_replicate.samples["jacobian_truth"],
)
)
self.assertFalse(
np.array_equal(
baseline.samples["tau_residual_raw"],
different_noise.samples["tau_residual_raw"],
)
)
def test_operational_flag_is_separate_from_numerical_rank(self):
specification = h2_pairing_specification()
specification["replicates"] = 1
specification["factors"]["characteristic_length_m"] = [0.3]
specification["factors"]["damping"] = [0.03]
specification["factors"]["torque_noise_std_Nm"] = [0.01]
specification["factors"]["operational_min_scaled_singular"] = [10.0]
trial = build_trial_plan(specification)["trials"][0]
payload = execute_h2_synthetic(trial)
np.testing.assert_array_equal(
payload.samples["solver_numerical_rank_deficient"],
np.zeros(12, dtype=np.int8),
)
np.testing.assert_array_equal(
payload.samples["solver_operationally_ill_conditioned"],
np.ones(12, dtype=np.int8),
)
np.testing.assert_array_equal(
payload.samples["operational_min_scaled_singular_threshold"],
np.full(12, 10.0),
)
definition = payload.metadata[
"operational_ill_conditioning_definition"
]
self.assertTrue(definition["numerical_rank_is_reported_separately"])
def test_v2_scan_has_sixteen_physical_groups_and_576_trials(self):
specification = load_document(
CODE_ROOT / "config" / "experiments" / "h2_calibration_v2.json"
)
plan = build_trial_plan(specification)
self.assertEqual(plan["pair_count"], 144)
self.assertEqual(plan["trial_count"], 576)
group_ids = set()
for trial in plan["trials"]:
payload = execute_h2_synthetic(
{
**trial,
"trajectory": {
**trial["trajectory"],
"sample_count": 8,
},
}
)
group_ids.add(payload.metadata["h2_data_group_id"])
if len(group_ids) == 16:
break
self.assertEqual(len(group_ids), 16)
if __name__ == "__main__":
unittest.main()