Grounding refinement (18 reps): forward-KL is the operative neural
collapse metric, not H or tail-survival. The RNN's smoothing keeps
spurious tail modes alive, so tail_truth_mass_alive is flat/non-monotone
in g and H stays ~0.8 of H*; only forward-KL falls monotonically (dry
2.08 -> g=0.2: 0.75, paired t up to 3.3). The sharp g* << 1 is an
exact-operator feature carried by the histogram bridge (0.047); the
trained RNN confirms the SIGN and softens the sharpness (half the KL gap
closes by g~0.04, but full recovery needs g~0.19). Blueprint 3.5's
directional claim holds; the pre-registered 95%-of-H*/tail falsifier is
not met because those are the wrong metrics for a smoothing model.
Robustness: a fully-degenerate RNN can emit only invalid codewords, so
measure_distribution now returns a terminal-collapse sentinel (fixation
on the dominant mode) instead of crashing a long sweep. Edge test added
(94 tests green).
Figures: plot_{bridge,collapse,grounding,architectures,recombination}.py,
each a pure function of its committed bundle, wired into `make figures`
(glob plot_*.py minus plot_E[1-6]/_*). bridge sits on the exact H_eq
curve (g*=0.047); recombination shows max-merge rising while mean-distill
stays flat; architectures shows the collapse/rescue signs across
histogram/GRU/MLP.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
187 lines
6.8 KiB
Python
187 lines
6.8 KiB
Python
"""Correctness tests for the Layer 1.5 neural scaffold (Stage A).
|
|
|
|
Pure-NumPy checks (no torch): the synthetic grammar is lossless, the exact oracle has zero
|
|
error, the histogram model reduces to a mode-frequency estimator, and the generation loop
|
|
produces the Layer-1 row schema deterministically.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
import pytest
|
|
|
|
from neural.config import NeuralLineageCfg, SyntheticCfg
|
|
from neural.generation_loop import run_generative_lineage
|
|
from neural.models import HistogramModel, make_model
|
|
from neural.oracle import ExactOracle, measure_distribution
|
|
from neural.synthetic import id_codewords, make_mode_truth, render_modes, sample_synthetic
|
|
|
|
|
|
def _syn(**over) -> SyntheticCfg:
|
|
base = dict(K=64, R=1, zipf_s=1.1, tail_threshold=1e-3, style_len=3, style_vocab=5,
|
|
id_base=2)
|
|
base.update(over)
|
|
return SyntheticCfg(**base)
|
|
|
|
|
|
# --- synthetic grammar ------------------------------------------------------------------
|
|
|
|
def test_id_len_covers_all_modes():
|
|
syn = _syn(K=100, id_base=2)
|
|
assert syn.id_base ** syn.id_len >= syn.K
|
|
assert syn.id_base ** (syn.id_len - 1) < syn.K
|
|
|
|
|
|
def test_seq_len_and_vocab():
|
|
syn = _syn(K=64, id_base=2, style_len=3, style_vocab=5)
|
|
assert syn.id_len == 6 # 2**6 = 64
|
|
assert syn.seq_len == syn.id_len + syn.style_len
|
|
assert syn.vocab == max(syn.id_base, syn.style_vocab)
|
|
|
|
|
|
def test_codewords_are_unique_and_invertible():
|
|
syn = _syn(K=64)
|
|
cw = id_codewords(syn)
|
|
assert cw.shape == (syn.K, syn.id_len)
|
|
assert cw.max() < syn.id_base
|
|
# each mode's codeword is distinct
|
|
assert len({tuple(r) for r in cw}) == syn.K
|
|
|
|
|
|
def test_render_shapes_and_token_ranges():
|
|
syn = _syn(K=32, style_len=4, style_vocab=7)
|
|
rng = np.random.default_rng(0)
|
|
modes = np.arange(syn.K)
|
|
X = render_modes(modes, syn, rng)
|
|
assert X.shape == (syn.K, syn.seq_len)
|
|
assert X[:, : syn.id_len].max() < syn.id_base
|
|
assert X[:, syn.id_len :].max() < syn.style_vocab
|
|
|
|
|
|
# --- exact oracle -----------------------------------------------------------------------
|
|
|
|
def test_exact_oracle_zero_error_on_all_modes():
|
|
syn = _syn(K=100)
|
|
rng = np.random.default_rng(1)
|
|
modes = np.repeat(np.arange(syn.K), 5) # every mode, many style draws
|
|
X = render_modes(modes, syn, rng)
|
|
recovered = ExactOracle(syn).classify(X)
|
|
assert np.array_equal(recovered, modes) # zero measurement error
|
|
|
|
|
|
def test_measure_distribution_recovers_frequencies():
|
|
syn = _syn(K=16)
|
|
rng = np.random.default_rng(2)
|
|
p = np.array([0.5] + [0.5 / 15] * 15)
|
|
X, _ = sample_synthetic(p, 200_000, syn, rng)
|
|
p_hat = measure_distribution(X, ExactOracle(syn), syn.K)
|
|
assert p_hat.shape == (syn.K,)
|
|
assert np.isclose(p_hat.sum(), 1.0)
|
|
assert abs(p_hat[0] - 0.5) < 0.01
|
|
|
|
|
|
def test_measure_distribution_all_invalid_is_terminal_collapse():
|
|
# A degenerate neural model can emit only invalid codewords (decoded index >= K). That
|
|
# must not crash a long sweep: measure_distribution returns the terminal-collapse
|
|
# sentinel (fixation on the dominant mode) — H=0, single-mode support — not an error.
|
|
syn = _syn(K=16)
|
|
oracle = ExactOracle(syn)
|
|
X_bad = np.full((100, syn.seq_len), syn.vocab - 1, dtype=np.int64) # max-token everywhere
|
|
assert (oracle.classify(X_bad) >= syn.K).all() # all off-manifold
|
|
p_hat = measure_distribution(X_bad, oracle, syn.K)
|
|
assert p_hat.shape == (syn.K,) and np.isclose(p_hat.sum(), 1.0)
|
|
assert p_hat[0] == 1.0 and int((p_hat > 0).sum()) == 1 # collapsed to one mode
|
|
|
|
|
|
# --- histogram model --------------------------------------------------------------------
|
|
|
|
def test_histogram_initialise_is_exact():
|
|
syn = _syn(K=32)
|
|
m = HistogramModel(syn, ExactOracle(syn))
|
|
p0 = np.full(syn.K, 1.0 / syn.K)
|
|
m.initialise(p0, np.random.default_rng(0))
|
|
assert np.allclose(m.mode_distribution(np.random.default_rng(0)), p0)
|
|
|
|
|
|
def test_histogram_fit_then_sample_roundtrip():
|
|
syn = _syn(K=16)
|
|
rng = np.random.default_rng(3)
|
|
m = HistogramModel(syn, ExactOracle(syn))
|
|
p = np.array([0.4, 0.3, 0.2] + [0.1 / 13] * 13)
|
|
X, _ = sample_synthetic(p, 100_000, syn, rng)
|
|
m.fit(X, rng)
|
|
drawn = m.sample(100_000, rng)
|
|
p_hat = measure_distribution(drawn, ExactOracle(syn), syn.K)
|
|
assert np.allclose(p_hat, m.mode_distribution(rng), atol=0.01)
|
|
|
|
|
|
def test_make_model_histogram():
|
|
syn = _syn()
|
|
from neural.config import ModelCfg
|
|
model = make_model(ModelCfg(kind="histogram"), syn, ExactOracle(syn))
|
|
assert isinstance(model, HistogramModel)
|
|
|
|
|
|
def test_make_model_rejects_unknown_kind():
|
|
syn = _syn()
|
|
from neural.config import ModelCfg
|
|
with pytest.raises(ValueError):
|
|
make_model(ModelCfg(kind="nope"), syn, ExactOracle(syn))
|
|
|
|
|
|
# --- mode truth reuses Layer 1 ----------------------------------------------------------
|
|
|
|
def test_mode_truth_is_layer1_truth():
|
|
syn = _syn(K=100, R=10)
|
|
td = make_mode_truth(syn)
|
|
assert td.p_star.shape == (syn.K,)
|
|
assert np.isclose(td.p_star.sum(), 1.0)
|
|
assert td.tail_mask.dtype == bool
|
|
assert len(np.unique(td.regions)) == syn.R
|
|
|
|
|
|
# --- generation loop: schema + determinism ---------------------------------------------
|
|
|
|
def _cfg(**over) -> dict:
|
|
base = {
|
|
"synthetic": {"K": 64, "R": 1, "zipf_s": 1.1, "init": "truth",
|
|
"style_len": 2, "style_vocab": 4, "id_base": 2},
|
|
"model": {"kind": "histogram"},
|
|
"dynamics": {"n": 200, "grounding": {"m": 0}},
|
|
"generations": 5,
|
|
}
|
|
base.update(over)
|
|
return base
|
|
|
|
|
|
def test_lineage_returns_layer1_schema():
|
|
df = run_generative_lineage(_cfg(), seed=0)
|
|
assert isinstance(df, pd.DataFrame)
|
|
assert list(df["generation"]) == [0, 1, 2, 3, 4, 5]
|
|
for col in ("heterozygosity", "forward_kl", "tail_mass", "support_size",
|
|
"tail_frac_alive", "head_frac_alive", "tail_truth_mass_alive"):
|
|
assert col in df.columns
|
|
|
|
|
|
def test_lineage_deterministic_given_seed():
|
|
a = run_generative_lineage(_cfg(), seed=7)
|
|
b = run_generative_lineage(_cfg(), seed=7)
|
|
pd.testing.assert_frame_equal(a, b)
|
|
|
|
|
|
def test_lineage_h0_is_truth_heterozygosity():
|
|
# init='truth' -> gen-0 H equals H* of the truth exactly (histogram is exact at gen 0)
|
|
syn = SyntheticCfg(K=64, R=1, zipf_s=1.1, init="truth", style_len=2, style_vocab=4)
|
|
td = make_mode_truth(syn)
|
|
h_star = 1.0 - np.sum(td.p_star ** 2)
|
|
df = run_generative_lineage(_cfg(), seed=1)
|
|
assert abs(df.loc[df["generation"] == 0, "heterozygosity"].iloc[0] - h_star) < 1e-12
|
|
|
|
|
|
def test_dry_lineage_collapses():
|
|
# m=0, small n -> heterozygosity must fall over generations (collapse)
|
|
df = run_generative_lineage(_cfg(generations=40, dynamics={"n": 50,
|
|
"grounding": {"m": 0}}), seed=2)
|
|
h = df["heterozygosity"].to_numpy()
|
|
assert h[-1] < h[0] - 0.1
|