| """Tests for the meta-controller & Lyapunov energy (``palimseste.meta``). |
| |
| Core guarantees under test: |
| - a proposal that reduces surprise is accepted; one that increases it is rejected |
| - accepted rewrites are audit-logged into H_meta (append-only) |
| - invariants penalize configs (e.g. max_radius blocks runaway radius) |
| - the acceptance criterion itself is never rewritten (constitution immutability) |
| - build_replay draws from M's own traces |
| """ |
|
|
| from __future__ import annotations |
|
|
| import numpy as np |
| import pytest |
|
|
| from palimseste import hv |
| from palimseste.memory import Memory |
| from palimseste.phi import Phi, KernelConfig |
| from palimseste.meta import ( |
| MetaController, |
| LyapunovEnergy, |
| MetaProposal, |
| Invariant, |
| max_radius_invariant, |
| _config_to_hv, |
| ) |
|
|
|
|
| def _setup(D=2000, seed=1, radius=5, n_traces=40): |
| rng = np.random.default_rng(seed) |
| mem = Memory(D=D, rng=np.random.default_rng(seed)) |
| |
| |
| base = hv.random_hv(D=D, rng=rng) |
| for _ in range(n_traces): |
| a = _flip(base, 50, rng) |
| v = _flip(a, 3, rng) |
| mem.write(a, v) |
| phi = Phi(config=KernelConfig(radius=radius, min_weight=1e-6)) |
| energy = LyapunovEnergy() |
| ctrl = MetaController(mem=mem, phi=phi, energy=energy, rng=rng) |
| return mem, phi, ctrl, rng |
|
|
|
|
| def _flip(h: hv.HV, n: int, rng) -> hv.HV: |
| s = hv.bits_to_signs(h) |
| pos = rng.choice(h.D, size=n, replace=False) |
| s[pos] = -s[pos] |
| return hv.signs_to_bits(s) |
|
|
|
|
| def _noisy_replay(mem, n, flip_bits, rng): |
| """Replay set of (noisy_query, target_value): query is a stored address |
| with ``flip_bits`` positions flipped, so radius=0 misses but a wider |
| radius finds the true value. This is the *generalization* regime where |
| the kernel's radius actually matters.""" |
| idx = rng.choice(len(mem.traces), size=min(n, len(mem.traces)), replace=False) |
| replay = [] |
| for i in idx: |
| tr = mem.traces[i] |
| q = _flip(tr.address, flip_bits, rng) |
| replay.append((q, tr.value)) |
| return replay |
|
|
|
|
| def test_evaluate_accepts_surprise_reducing_proposal(): |
| mem, phi, ctrl, rng = _setup(radius=0, n_traces=40) |
| |
| replay = _noisy_replay(mem, 40, flip_bits=15, rng=rng) |
| prop = MetaProposal(KernelConfig(radius=20, min_weight=1e-6), "widen radius") |
| dec = ctrl.evaluate(prop, replay) |
| assert dec.accepted |
| assert dec.delta < 0 |
|
|
|
|
| def test_evaluate_rejects_surprise_increasing_proposal(): |
| mem, phi, ctrl, rng = _setup(radius=30, n_traces=40) |
| |
| replay = _noisy_replay(mem, 40, flip_bits=15, rng=rng) |
| prop = MetaProposal(KernelConfig(radius=0, min_weight=1e-6), "shrink radius") |
| dec = ctrl.evaluate(prop, replay) |
| assert not dec.accepted |
| assert dec.delta > 0 |
|
|
|
|
| def test_accepted_rewrite_is_audit_logged_to_hmeta(): |
| mem, phi, ctrl, rng = _setup(radius=0, n_traces=30) |
| n_meta_before = len(mem.meta_traces) |
| replay = ctrl.build_replay(30) |
| prop = MetaProposal(KernelConfig(radius=15, min_weight=1e-6), "widen") |
| dec = ctrl.evaluate(prop, replay) |
| assert dec.accepted |
| |
| phi.config = dec.proposal.config |
| ctrl._write_current_config() |
| assert len(mem.meta_traces) == n_meta_before + 1 |
|
|
|
|
| def test_step_commits_first_accepted_proposal(): |
| mem, phi, ctrl, rng = _setup(radius=0, n_traces=30) |
| replay = ctrl.build_replay(30) |
| cfg_before = phi.config |
| dec = ctrl.step(replay, max_proposals=20) |
| |
| assert dec is not None |
| if dec.accepted: |
| assert phi.config == dec.proposal.config |
| assert phi.config != cfg_before |
|
|
|
|
| def test_invariant_max_radius_blocks_runaway(): |
| |
| mem, phi, ctrl, rng = _setup(radius=0, n_traces=20) |
| ctrl.energy = LyapunovEnergy(invariants=[max_radius_invariant(max_r=5)]) |
| replay = ctrl.build_replay(20) |
| |
| prop = MetaProposal(KernelConfig(radius=100, min_weight=1e-6), "runaway") |
| dec = ctrl.evaluate(prop, replay) |
| assert not dec.accepted |
| assert "rejected" in dec.reason |
|
|
|
|
| def test_invariant_zero_when_satisfied(): |
| inv = max_radius_invariant(max_r=50) |
| mem = Memory(D=500, rng=np.random.default_rng(0)) |
| cfg_ok = KernelConfig(radius=10) |
| cfg_bad = KernelConfig(radius=100) |
| assert inv.violation(mem, cfg_ok) == 0.0 |
| assert inv.violation(mem, cfg_bad) > 0.0 |
|
|
|
|
| def test_constitution_immutability(): |
| |
| |
| |
| mem, phi, ctrl, rng = _setup(radius=5, n_traces=10) |
| |
| |
| |
| for tr in mem.meta_traces: |
| assert tr.tag is not None and tr.tag.startswith("kernel_config:") |
|
|
|
|
| def test_build_replay_from_empty_memory(): |
| mem = Memory(D=500, rng=np.random.default_rng(0)) |
| phi = Phi(config=KernelConfig(radius=5)) |
| ctrl = MetaController(mem=mem, phi=phi, energy=LyapunovEnergy(), |
| rng=np.random.default_rng(0)) |
| assert ctrl.build_replay(10) == [] |
|
|
|
|
| def test_config_to_hv_deterministic_and_distinct(): |
| D = 1000 |
| rng = np.random.default_rng(0) |
| a = KernelConfig(radius=5, min_weight=1e-3, sharpness=0.0, topk=None) |
| b = KernelConfig(radius=5, min_weight=1e-3, sharpness=0.0, topk=None) |
| c = KernelConfig(radius=6, min_weight=1e-3, sharpness=0.0, topk=None) |
| ha = _config_to_hv(a, D, rng) |
| hb = _config_to_hv(b, D, rng) |
| hc = _config_to_hv(c, D, rng) |
| assert ha == hb |
| assert ha != hc |
|
|
|
|
| def test_history_records_all_evaluated_proposals(): |
| mem, phi, ctrl, rng = _setup(radius=0, n_traces=15) |
| replay = ctrl.build_replay(15) |
| ctrl.step(replay, max_proposals=5) |
| |
| assert 1 <= len(ctrl.history) <= 5 |
|
|
|
|
| def test_custom_invariant_callable(): |
| called = {"n": 0} |
|
|
| def _viol(_mem, _cfg): |
| called["n"] += 1 |
| return 0.0 |
|
|
| inv = Invariant(name="custom", violation=_viol, lam=2.0) |
| energy = LyapunovEnergy(invariants=[inv]) |
| mem = Memory(D=500, rng=np.random.default_rng(0)) |
| phi = Phi(config=KernelConfig(radius=2)) |
| ctrl = MetaController(mem=mem, phi=phi, energy=energy, rng=np.random.default_rng(0)) |
| |
| rng = np.random.default_rng(1) |
| mem.write(hv.random_hv(D=500, rng=rng), hv.random_hv(D=500, rng=rng)) |
| replay = ctrl.build_replay(1) |
| ctrl.evaluate(MetaProposal(KernelConfig(radius=3), "test"), replay) |
| assert called["n"] >= 1 |
|
|