roverdevkit / tests /test_rediscovery_baseline.py
jjreif's picture
Deploy roverdevkit @ 2676a67
b3d14e3
Raw
History Blame Contribute Delete
4.02 kB
"""Tests for the §5.4 feasible-design null baseline.
Groups:
1. **Helpers.** Uniform sampling stays inside the box bounds; the
feasibility gate honours each clause and excludes the (degenerate)
thermal flag; the pairwise-distance helper matches a hand value; the
unit-cube null is the mean pairwise L2 (~1.20).
2. **End-to-end (smoke budget).** A small-``max_full_evals`` run on one
rover populates the feasible set and produces finite, ordered null
statistics.
"""
from __future__ import annotations
import numpy as np
from roverdevkit.tradespace.optimizer import DESIGN_BOUNDS, DESIGN_VARIABLES
from roverdevkit.validation.rediscovery_baseline import (
UNIT_CUBE_RANDOM_PAIR,
_is_feasible,
_mean_pairwise_l2,
_sample_designs,
compute_feasible_baseline,
)
def test_unit_cube_constant() -> None:
# The null is the *mean* pairwise L2 between uniform unit-cube points,
# matched to the feasible-null estimator. It is strictly below the
# closed-form RMS separation sqrt(9/6) (Jensen) and lands near 1.20.
rms = float(np.sqrt(9.0 / 6.0))
assert UNIT_CUBE_RANDOM_PAIR < rms
assert UNIT_CUBE_RANDOM_PAIR == 1.203010901890861
def test_sample_designs_within_bounds() -> None:
rng = np.random.default_rng(0)
designs = _sample_designs(200, rng)
assert len(designs) == 200
for d in designs:
for name in DESIGN_VARIABLES:
if name == "mobility_architecture":
assert d.mobility_architecture in ("rigid_4wheel", "rocker_bogie_6wheel")
continue
lo, hi = DESIGN_BOUNDS[name]
assert lo - 1e-9 <= float(getattr(d, name)) <= hi + 1e-9
assert int(d.n_wheels) in (4, 6)
expected_wheels = 6 if d.mobility_architecture == "rocker_bogie_6wheel" else 4
assert int(d.n_wheels) == expected_wheels
def _metrics(*, stalled=False, energy=10.0, rng_km=1.0, mass=5.0, thermal=False):
return {
"stalled": stalled,
"energy_margin_raw_pct": energy,
"range_km": rng_km,
"total_mass_kg": mass,
"thermal_survival": thermal,
}
def test_feasibility_gate_clauses() -> None:
# A working rover with a (degenerate) thermal_survival=False still
# counts feasible: thermal is intentionally excluded.
assert _is_feasible(_metrics(thermal=False), None)
assert not _is_feasible(_metrics(stalled=True), None)
assert not _is_feasible(_metrics(energy=-0.1), None)
assert not _is_feasible(_metrics(rng_km=0.0), None)
# Mass-ceiling clause only bites when a budget is supplied.
assert _is_feasible(_metrics(mass=9.0), None)
assert _is_feasible(_metrics(mass=9.0), 10.0)
assert not _is_feasible(_metrics(mass=11.0), 10.0)
def test_mean_pairwise_l2_known_value() -> None:
# Three points on a line at 0, 3, 4 in 1-D: pairwise dists 3, 4, 1.
vectors = np.array([[0.0], [3.0], [4.0]])
mean, median = _mean_pairwise_l2(vectors, np.random.default_rng(0))
assert mean == (3.0 + 4.0 + 1.0) / 3.0
assert median == 3.0
def test_compute_feasible_baseline_smoke() -> None:
result = compute_feasible_baseline(
"Pragyan", max_full_evals=80, seed=0, require_mass_ceiling=False
)
assert result.rover_name == "Pragyan"
assert result.class_generic_scenario == "polar_micro"
assert result.mass_budget_kg is None # physical-viability mode
assert result.n_full_evaluated == 80
assert result.n_feasible > 0
assert 0.0 < result.feasible_fraction <= 1.0
# Null statistics are finite and the centroid distance is a sane
# normalised-L2 magnitude (well under the 3-unit cube diagonal).
assert result.feasible_random_pair_mean is not None
assert 0.0 < result.feasible_random_pair_mean < 3.0
assert result.rover_to_nearest_feasible_distance is not None
# The nearest feasible draw is no farther than the centroid.
assert (
result.rover_to_nearest_feasible_distance
<= result.rover_to_centroid_distance + 1e-9
)