palimpseste-max / tests /test_meta.py
thefinalboss's picture
Upload tests/test_meta.py with huggingface_hub
9cae168 verified
Raw
History Blame Contribute Delete
7.19 kB
"""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))
# populate M with (address, value) pairs where value == address+noise,
# so a wider radius genuinely helps reconstruction (lower surprise).
base = hv.random_hv(D=D, rng=rng)
for _ in range(n_traces):
a = _flip(base, 50, rng)
v = _flip(a, 3, rng) # value near its address
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)
# noisy queries: 15 bits flipped -> radius 0 misses, radius 20 hits
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)
# with radius 30, noisy queries are found; shrinking to 0 breaks them
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
# commit manually (evaluate doesn't commit; step does)
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)
# at least one widening proposal should be accepted eventually
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():
# with a tiny max_radius invariant, widening beyond it is penalized
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)
# propose radius 100 -> invariant violation huge -> rejected
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():
# The acceptance criterion (LyapunovEnergy + invariants) is NOT stored in
# H_meta and cannot be rewritten by the controller. Verify the controller
# has no API to mutate its own energy/invariants.
mem, phi, ctrl, rng = _setup(radius=5, n_traces=10)
# The controller exposes `energy` as a field, but there is no method to
# *write* a new energy into H_meta. The only thing it writes to H_meta is
# kernel configs. Assert the meta-traces are all tagged kernel_config:*.
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)
# at least 1, at most 5 decisions recorded
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))
# write a trace so replay is non-empty
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