"""Tests for the adaptive phase controller.""" from __future__ import annotations import pytest from ahdcma.controller.phase_switch import AdaptivePhaseController def _config(**overrides: float | int) -> dict: cfg: dict = { "entropy_bins": 10, "ruggedness_walk_length": 10, "threshold_update_interval": 5, "tau_high_offset": 0.5, "tau_low_offset": -0.5, "ruggedness_explore_thresh": 0.3, "ruggedness_exploit_thresh": 0.6, } cfg.update(overrides) return cfg def test_pre_threshold_uses_ruggedness_only() -> None: c = AdaptivePhaseController(_config()) # Before update_thresholds is called, tau bounds are infinite, so # ruggedness alone decides. assert c.select_mode(entropy=5.0, ruggedness=0.05, t=0) == "exploit" assert c.select_mode(entropy=5.0, ruggedness=0.95, t=0) == "explore" assert c.select_mode(entropy=5.0, ruggedness=0.45, t=0) == "hybrid" def test_threshold_update_after_interval() -> None: c = AdaptivePhaseController(_config(threshold_update_interval=5)) # Feed 20 generations of entropy ~ N(10, 2) rng_vals = [ 12.0, 8.0, 11.0, 9.0, 10.5, 9.5, 10.0, 11.5, 8.5, 10.0, 12.5, 7.5, 10.0, 9.0, 11.0, 9.5, 10.5, 10.0, 9.0, 11.0, ] for t, h in enumerate(rng_vals): # ruggedness fixed in the "hybrid" band so mode is dictated by entropy c.select_mode(entropy=h, ruggedness=0.45, t=t) # Now thresholds are real numbers assert c.tau_high > c.tau_low # A spike above tau_high → explore very_high = c.tau_high + 5.0 assert c.select_mode(entropy=very_high, ruggedness=0.45, t=21) == "explore" # A drop below tau_low → exploit very_low = c.tau_low - 5.0 assert c.select_mode(entropy=very_low, ruggedness=0.45, t=22) == "exploit" def test_high_ruggedness_overrides_hybrid_band() -> None: c = AdaptivePhaseController(_config()) # 6 entropy values inside tight band → tau_high ≈ tau_low ≈ mean for t, h in enumerate([5.0] * 6): c.select_mode(entropy=h, ruggedness=0.45, t=t) # Same entropy as the band, but very rugged → still explore assert c.select_mode(entropy=5.0, ruggedness=0.95, t=7) in {"explore", "exploit"} def test_invalid_offsets_rejected() -> None: with pytest.raises(ValueError): AdaptivePhaseController(_config(tau_high_offset=-1.0, tau_low_offset=0.5)) with pytest.raises(ValueError): AdaptivePhaseController( _config(ruggedness_explore_thresh=0.7, ruggedness_exploit_thresh=0.3) ) def test_entropy_history_view() -> None: c = AdaptivePhaseController(_config()) for t, h in enumerate([1.0, 2.0, 3.0]): c.select_mode(entropy=h, ruggedness=0.45, t=t) assert c.entropy_history == [1.0, 2.0, 3.0] # The view should be a copy c.entropy_history.append(99.0) assert c.entropy_history == [1.0, 2.0, 3.0]