"""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 )