Files
2026-09-30 10:45:48 +08:00

153 lines
8.3 KiB
Python

"""Synthetic CPU-only tests, including the untouched sibling AH callback."""
import importlib.util
from pathlib import Path
import unittest
import numpy as np
from pn_observer import PNObserver
path = Path(__file__).resolve().parent.parent / "kc-drive-audit-v1/replay_core.py"
spec = importlib.util.spec_from_file_location("frozen_ah_observer_test_core", path)
core = importlib.util.module_from_spec(spec)
spec.loader.exec_module(core)
def event(rows=()):
values = np.array(rows, np.int32).reshape(-1, 2)
return {"step": values[:, 0], "neuron_idx": values[:, 1]}
def run(*, edges=(), events=(), requests=(), n=1, design=(), steps=25, coeff=None,
initial_v=None, initial_g=None, sink=None, cap=1000, h=None, hook=None):
targets = np.arange(100, 100 + n, dtype=np.int32)
constants = dict(gd=1., c0=0., cg=1., cv=1., threshold=1., reset=0., seed_volt=2.)
if coeff: constants.update(coeff)
arrays = np.asarray(edges).reshape(-1, 4)
incoming = core.IncomingIndex(arrays[:, 0].astype(np.int32), arrays[:, 1].astype(np.int32),
np.arange(len(arrays), dtype=np.int64) + 81, arrays[:, 2].astype(np.float64), n)
groups = arrays[:, 3].astype(np.int16)
observer = PNObserver(targets, np.array(design, np.int32), incoming, groups, ("ALPN", "other"), constants,
steps, max_first_event_rows=cap, event_sink=sink)
def callback(tick, state):
if hook: hook(tick, state, observer)
observer(tick, state)
result = core.replay(np.zeros(n, np.float64) if initial_v is None else np.array(initial_v, np.float64),
np.zeros(n, np.float64) if initial_g is None else np.array(initial_g, np.float64), constants, steps, targets,
event(requests), event(events), incoming, np.zeros((steps, n), np.float32) if h is None else h, callback)
data, report = observer.finish(result)
return observer, result, data, report
class PNObserverTests(unittest.TestCase):
def test_18_step_arrival_then_19_step_first_spike(self):
observer, result, data, report = run(edges=[(10, 0, 2., 0)], events=[(0, 10)])
self.assertEqual(data["first_spike_tick"].tolist(), [19])
self.assertEqual(len(observer.first_arrival_records), 1)
row = observer.first_arrival_records[0]
self.assertEqual((row["source_spike_tick"], row["arrival_tick"]), (0, 18))
self.assertEqual(row["v_prethreshold_SI"], 0.)
self.assertEqual(row["threshold_margin_SI"], 1.)
self.assertEqual(data["first_integrated_V_from_ALPN/positive_weight"].tolist(), [2.])
self.assertEqual(observer.edge_summary()["target_first_spike_tick"].tolist(), [19])
self.assertTrue(report["pass_all"])
def test_same_tick_first_spike_arrival_is_rejected_not_antecedent(self):
observer, _, data, _ = run(edges=[(10, 0, 2., 0)], events=[(0, 10)], requests=[(18, 100)], design=[100])
self.assertEqual(data["first_spike_tick"].tolist(), [18])
self.assertEqual(observer.first_arrival_records, [])
self.assertEqual(data["ALPN_rejected_positive_edges"].tolist(), [1])
self.assertEqual(data["ALPN_accepted_positive_before_first_edges"].tolist(), [0])
self.assertEqual(data["first_prethreshold_V_from_request_or_reset"].tolist(), [2.])
def test_all_positive_and_inhibitory_events_are_retained(self):
observer, _, data, _ = run(edges=[(10, 0, 2., 0), (11, 0, -.5, 1), (12, 0, 0., 1)],
events=[(0, 10), (0, 11), (0, 12)])
self.assertEqual([r["weight_sign"] for r in observer.first_arrival_records], [1, -1])
self.assertEqual(data["first_spike_tick"].tolist(), [19])
self.assertEqual(data["other_accepted_zero_edges"].tolist(), [1])
self.assertEqual(data["other_accepted_negative_before_first_abs_V"].tolist(), [.5])
self.assertEqual(data["first_integrated_V_from_other/negative_weight"].tolist(), [-.5])
def test_never_active_targets_retain_all_prefirst_records(self):
observer, _, data, report = run(edges=[(10, 0, .25, 0)], events=[(0, 10), (1, 10)],
coeff={"cg": 0.})
self.assertEqual(data["first_spike_tick"].tolist(), [-1])
self.assertTrue(np.isnan(data["first_v_threshold_SI"][0]))
self.assertEqual(observer.edge_summary()["accepted_event_count"].tolist(), [2])
self.assertEqual(observer.edge_summary()["target_first_spike_tick"].tolist(), [-1])
self.assertEqual(report["first_arrival_record_count"], 2)
def test_multiple_targets_independent_first_times_and_design(self):
observer, _, data, _ = run(n=2, edges=[(10, 0, 2., 0), (10, 1, .25, 0)],
events=[(0, 10)], requests=[(23, 101)], design=[101])
self.assertEqual(data["first_spike_tick"].tolist(), [19, 23])
self.assertEqual([r["target_is_design"] for r in observer.first_arrival_records], [False, True])
self.assertEqual(data["first_spike_coincident_with_accepted_request"].tolist(), [False, True])
def test_old_g_not_decayed_g_used_for_first_voltage(self):
_, _, data, _ = run(edges=[(10, 0, 4., 0)], events=[(0, 10)],
coeff={"gd": .1, "cg": .5, "cv": 0.})
self.assertEqual(data["first_integrated_V_from_ALPN/positive_weight"].tolist(), [2.])
self.assertEqual(data["first_spike_tick"].tolist(), [19])
def test_initial_state_is_not_assigned_to_source_class(self):
_, _, data, _ = run(initial_g=[2.])
self.assertEqual(data["first_spike_tick"].tolist(), [0])
self.assertEqual(data["first_integrated_V_from_initial_and_affine_baseline"].tolist(), [2.])
self.assertEqual(data["first_integrated_V_from_ALPN/positive_weight"].tolist(), [0.])
def test_streaming_sink_and_row_budget_fail_closed(self):
received = []
observer, _, _, report = run(edges=[(10, 0, .2, 0)], events=[(0, 10)],
sink=lambda rows: received.extend(rows))
self.assertEqual(observer.first_arrival_records, [])
self.assertEqual(len(received), report["first_arrival_record_count"])
with self.assertRaisesRegex(ValueError, "resource limit"):
run(edges=[(10, 0, .2, 0), (11, 0, .2, 1)], events=[(0, 10), (0, 11)], cap=1)
def test_nonzero_PN_APL_and_non_design_request_are_rejected(self):
h = np.zeros((25, 1), np.float32); h[0, 0] = 1
with self.assertRaisesRegex(ValueError, "zero graded APL"):
run(h=h)
with self.assertRaisesRegex(ValueError, "outside frozen odor design"):
run(requests=[(0, 100)])
def test_callback_arrays_not_modified(self):
snapshots = []
def hook(tick, state, observer):
if tick == 0:
before = {key: value.copy() for key, value in state.items() if isinstance(value, np.ndarray)}
observer(tick, state)
for key, value in before.items():
self.assertEqual(state[key].tobytes(), value.tobytes())
snapshots.append(True)
# Already consumed this tick; stop this synthetic test directly.
raise RuntimeError("checked immutability")
with self.assertRaisesRegex(RuntimeError, "checked immutability"):
run(hook=hook)
self.assertEqual(snapshots, [True])
def test_bad_queue_guard_and_tick_fail_closed(self):
def wrong_guard(tick, state, observer):
if tick == 18: state["arrival_accepted"][:] = False
with self.assertRaisesRegex(ValueError, "Arrival guard"):
run(edges=[(10, 0, 2., 0)], events=[(0, 10)], hook=wrong_guard)
def wrong_tick(tick, state, observer):
observer(tick + 1, state)
with self.assertRaisesRegex(ValueError, "out-of-order"):
run(hook=wrong_tick)
def test_sequential_arrival_roundoff_has_own_lane(self):
_, _, data, report = run(edges=[(10, 0, 1e16, 0), (11, 0, 1., 0), (12, 0, -1e16, 1)],
events=[(0, 10), (0, 11), (0, 12)], coeff={"cg": 0.})
if np.finfo(np.longdouble).nmant > np.finfo(np.float64).nmant:
self.assertEqual(data["arrival_roundoff_g_SI"].tolist(), [-1.])
self.assertTrue(report["voltage_decomposition"]["roundoff_lane_explicit"])
self.assertFalse(report["unique_cause_claim"])
if __name__ == "__main__":
unittest.main()