233 lines
7.4 KiB
Python
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()
|