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

316 lines
23 KiB
Python

"""CPU-only observer for the unchanged Phase 4AH replay_core callback.
All input events are fixed observations, not interventions. This module writes
no files and never changes a callback array. Incoming records are antecedents,
not a selected cause, shortest path, necessity test, or whole-brain simulation.
"""
from __future__ import annotations
import numpy as np
DELAY = 18
COEFF_KEYS = ("gd", "c0", "cg", "cv", "threshold", "reset", "seed_volt")
FLOAT_KEYS = ("old_v", "old_g", "v_integrated", "g_decayed", "v_threshold",
"v_after_reset", "g_after_reset", "v_pre_apl", "g_pre_apl", "delta_si", "v", "g")
INT_KEYS = ("ref_before", "ref_after_reset", "ref_pre_apl", "ref")
BOOL_KEYS = ("available", "request", "request_accepted", "request_rejected", "fired")
def require(condition, message):
if not condition:
raise ValueError(message)
def bit_equal(a, b):
return a.dtype == b.dtype and a.shape == b.shape and np.array_equal(
np.ascontiguousarray(a).view(np.uint8), np.ascontiguousarray(b).view(np.uint8))
class PNObserver:
def __init__(self, target_globals, design_target_globals, incoming, edge_groups,
group_names, coeff, steps, *, max_first_event_rows, event_sink=None):
targets = np.asarray(target_globals)
require(targets.dtype == np.int32 and targets.ndim == 1 and len(targets) > 0 and
np.all((targets >= 0) & (targets < 166700)) and np.all(np.diff(targets) > 0),
"Targets must be sorted unique global int32 IDs")
design = np.asarray(design_target_globals)
require(design.dtype == np.int32 and design.ndim == 1 and len(np.unique(design)) == len(design) and
np.isin(design, targets).all(), "Design target IDs must be unique int32 subset of observed PNs")
require(type(steps) is int and steps > 0, "steps must be a positive integer")
require(type(max_first_event_rows) is int and max_first_event_rows > 0, "Explicit positive row budget required")
require(event_sink is None or callable(event_sink), "event_sink must be callable or None")
names = tuple(group_names)
require(names and all(isinstance(name, str) and name for name in names) and len(set(names)) == len(names),
"Source group names must be nonempty and unique")
codes = np.asarray(edge_groups)
e = len(incoming.edge_id)
require(codes.ndim == 1 and codes.shape == (e,) and codes.dtype.kind in "iu" and
np.all((codes >= 0) & (codes < len(names))), "Invalid source-group code per incoming edge")
require(incoming.target_count == len(targets) and incoming.weights.dtype == np.float64 and
np.isfinite(incoming.weights).all(), "Incoming index/weights mismatch")
require(all(name in coeff and np.isfinite(coeff[name]) for name in COEFF_KEYS), "Missing/nonfinite coefficient")
self.targets = targets.copy()
self.design = np.isin(targets, design)
self.incoming, self.groups, self.names = incoming, codes.astype(np.int64, copy=True), names
self.coeff = {name: np.float64(coeff[name]) for name in COEFF_KEYS}
self.steps, self.n = steps, len(targets)
self.max_rows, self.sink = max_first_event_rows, event_sink
self.callback_count, self.record_count = 0, 0
self.first_arrival_records = []
self.first = np.full(self.n, -1, np.int32)
self.first_request = np.full(self.n, -1, np.int32)
self.first_accepted_request = np.full(self.n, -1, np.int32)
self.first_positive_arrival = np.full(self.n, -1, np.int32)
self.counts = np.zeros(self.n, np.int32)
self.requested = np.zeros(self.n, np.int64)
self.request_accepted = np.zeros(self.n, np.int64)
self.request_rejected = np.zeros(self.n, np.int64)
self.first_request_coincident = np.zeros(self.n, bool)
self.first_state = {name: np.full(self.n, np.nan, np.float64) for name in
("old_v", "old_g", "v_integrated", "v_threshold", "threshold_margin", "ref_before")}
shape = (len(names), self.n)
self.stats = {name: np.zeros(shape, np.int64) for name in
("due_edges", "accepted_edges", "rejected_edges", "accepted_zero_edges", "rejected_zero_edges",
"accepted_positive_edges", "accepted_negative_edges", "rejected_positive_edges", "rejected_negative_edges",
"accepted_nonzero_before_first_edges", "accepted_positive_before_first_edges", "accepted_negative_before_first_edges")}
self.sums = {name: np.zeros(shape, np.longdouble) for name in
("accepted_positive_V", "accepted_negative_abs_V", "rejected_positive_V", "rejected_negative_abs_V",
"accepted_positive_before_first_V", "accepted_negative_before_first_abs_V")}
self.edge_prefirst_count = np.zeros(e, np.int64)
self.edge_first_arrival = np.full(e, -1, np.int32)
self.edge_last_arrival = np.full(e, -1, np.int32)
# Non-feedback signed-source decomposition. Residual corrections are
# explicit lanes, never silently assigned to an anatomical population.
self.lane_names = [name + "/positive_weight" for name in names] + [name + "/negative_weight" for name in names]
self.lane_names += ["initial_and_affine_baseline", "request_or_reset", "roundoff_residual"]
self.baseline_lane, self.request_lane, self.round_lane = 2 * len(names), 2 * len(names) + 1, 2 * len(names) + 2
self.lv = np.zeros((len(self.lane_names), self.n), np.longdouble)
self.lg = np.zeros_like(self.lv)
self.first_integrated_lanes = np.full_like(self.lv, np.nan)
self.first_threshold_lanes = np.full_like(self.lv, np.nan)
self.first_ledger_residual = np.full(self.n, np.nan, np.longdouble)
self.integration_roundoff_v = np.zeros(self.n, np.longdouble)
self.integration_roundoff_g = np.zeros(self.n, np.longdouble)
self.arrival_roundoff_g = np.zeros(self.n, np.longdouble)
self.max_ledger_residual = np.longdouble(0)
self.previous = None
self.finished = False
def _validate_stage(self, tick, s):
require(not self.finished and type(tick) in (int, np.int32, np.int64) and
int(tick) == self.callback_count and tick < self.steps, "Missing, duplicate or out-of-order callback tick")
for names, dtype in ((FLOAT_KEYS, np.float64), (INT_KEYS, np.int32), (BOOL_KEYS, bool)):
for name in names:
require(name in s and isinstance(s[name], np.ndarray) and s[name].shape == (self.n,) and
s[name].dtype == dtype, "Missing/wrong callback array: " + name)
require(np.isfinite(s[name]).all(), "Nonfinite callback array: " + name)
require("h32" in s and s["h32"].dtype == np.float32 and s["h32"].shape == (self.n,) and
np.all(s["h32"] == 0) and np.all(s["delta_si"] == 0), "PN observer requires zero graded APL h/delta")
require(bit_equal(s["g"], s["g_pre_apl"]) and bit_equal(s["v"], s["v_pre_apl"]) and
bit_equal(s["ref"], s["ref_pre_apl"]), "Nonzero or unexpected post-APL PN state change")
require(np.all((s["ref_before"] >= 0) & (s["ref_before"] <= 22)) and
np.all((s["ref"] >= 0) & (s["ref"] <= 22)), "Refractory range mismatch")
require(np.array_equal(s["available"], s["ref_before"] <= 1), "Availability does not match pre-step refractory")
require(np.array_equal(s["request_accepted"], s["request"] & s["available"]) and
np.array_equal(s["request_rejected"], s["request"] & ~s["available"]), "Request accounting mismatch")
require(not np.any(s["request"] & ~self.design), "Observed request outside frozen odor design targets")
require(np.array_equal(s["fired"], s["available"] & (s["v_threshold"] > self.coeff["threshold"])),
"Callback firing does not match strict threshold")
if self.previous is not None:
require(all(bit_equal(s[key], self.previous[old]) for key, old in
(("old_v", "v"), ("old_g", "g"), ("ref_before", "ref"))), "Discontinuous callback state")
due = s.get("due_edge_indices")
require(isinstance(due, np.ndarray) and due.dtype == np.int64 and due.ndim == 1 and
np.all((due >= 0) & (due < len(self.incoming.edge_id))) and np.all(np.diff(due) > 0),
"Incoming queue must retain unique canonical edge rows")
require("due_edge_id" in s and bit_equal(s["due_edge_id"], self.incoming.edge_id[due]), "Incoming edge-ID mismatch")
accepted = s.get("arrival_accepted")
require(isinstance(accepted, np.ndarray) and accepted.dtype == bool and accepted.shape == due.shape and
np.array_equal(accepted, s["ref_after_reset"][self.incoming.dst_local[due]] == 0), "Arrival guard mismatch")
require(tick >= DELAY or not len(due), "Incoming event before the fixed 18-step delay")
return due, accepted
def __call__(self, tick, s):
due, accepted = self._validate_stage(tick, s)
tick = int(tick)
dst, weights, groups = self.incoming.dst_local[due], self.incoming.weights[due], self.groups[due]
before = self.first < 0
first_now = before & s["fired"]
# Arrivals happen AFTER this tick's threshold. Same-tick arrivals are
# never antecedents to a first spike, even if malformed data said accepted.
strictly_before = before[dst] & ~s["fired"][dst]
record_mask = accepted & (weights != 0) & strictly_before
new_records = int(np.count_nonzero(record_mask))
require(self.record_count + new_records <= self.max_rows,
"First-arrival record resource limit exceeded; no truncation or event selection permitted")
if self.previous is None:
self.lv[self.baseline_lane] = s["old_v"].astype(np.longdouble)
self.lg[self.baseline_lane] = s["old_g"].astype(np.longdouble)
available = s["available"]
# Use old lane-g for V, as in the author update; do NOT use decayed g.
self.lv[:, available] = (np.longdouble(self.coeff["cv"]) * self.lv[:, available] +
np.longdouble(self.coeff["cg"]) * self.lg[:, available])
self.lv[self.baseline_lane, available] += np.longdouble(self.coeff["c0"])
self.lg[:, available] *= np.longdouble(self.coeff["gd"])
rv = s["v_integrated"].astype(np.longdouble) - self.lv.sum(axis=0, dtype=np.longdouble)
rg = s["g_decayed"].astype(np.longdouble) - self.lg.sum(axis=0, dtype=np.longdouble)
self.integration_roundoff_v += rv
self.integration_roundoff_g += rg
self.lv[self.round_lane] += rv
self.lg[self.round_lane] += rg
self.first_integrated_lanes[:, first_now] = self.lv[:, first_now]
req = s["request_accepted"]
self.lv[:, req] = 0
self.lv[self.request_lane, req] = np.longdouble(self.coeff["seed_volt"])
self.first_threshold_lanes[:, first_now] = self.lv[:, first_now]
vres = s["v_threshold"].astype(np.longdouble) - self.lv.sum(axis=0, dtype=np.longdouble)
self.first_ledger_residual[first_now] = vres[first_now]
self.max_ledger_residual = max(self.max_ledger_residual, np.max(np.abs(vres), initial=np.longdouble(0)))
for name in self.first_state:
values = (self.coeff["threshold"] - s["v_threshold"]) if name == "threshold_margin" else s[name]
self.first_state[name][first_now] = values[first_now]
self.first_request_coincident[first_now] = req[first_now]
self.first[first_now] = tick
for array, selected in ((self.first_request, s["request"]), (self.first_accepted_request, req)):
array[(array < 0) & selected] = tick
self.requested += s["request"].astype(np.int64)
self.request_accepted += req.astype(np.int64)
self.request_rejected += s["request_rejected"].astype(np.int64)
self.counts += s["fired"].astype(np.int32)
fired = s["fired"]
self.lv[:, fired] = 0
self.lv[self.request_lane, fired] = np.longdouble(self.coeff["reset"])
self.lg[:, fired] = 0
positive, negative, zero = weights > 0, weights < 0, weights == 0
picks = {"due_edges": np.ones(len(due), bool), "accepted_edges": accepted,
"rejected_edges": ~accepted, "accepted_zero_edges": accepted & zero,
"rejected_zero_edges": ~accepted & zero,
"accepted_positive_edges": accepted & positive, "accepted_negative_edges": accepted & negative,
"rejected_positive_edges": ~accepted & positive, "rejected_negative_edges": ~accepted & negative,
"accepted_nonzero_before_first_edges": record_mask,
"accepted_positive_before_first_edges": record_mask & positive,
"accepted_negative_before_first_edges": record_mask & negative}
for name, mask in picks.items():
np.add.at(self.stats[name], (groups[mask], dst[mask]), 1)
for name, mask, sign in (
("accepted_positive_V", accepted & positive, 1), ("accepted_negative_abs_V", accepted & negative, -1),
("rejected_positive_V", ~accepted & positive, 1), ("rejected_negative_abs_V", ~accepted & negative, -1),
("accepted_positive_before_first_V", record_mask & positive, 1),
("accepted_negative_before_first_abs_V", record_mask & negative, -1)):
np.add.at(self.sums[name], (groups[mask], dst[mask]), sign * weights[mask].astype(np.longdouble))
for offset, mask in ((0, accepted & positive), (len(self.names), accepted & negative)):
np.add.at(self.lg, (groups[mask] + offset, dst[mask]), weights[mask].astype(np.longdouble))
ag = s["g_pre_apl"].astype(np.longdouble) - self.lg.sum(axis=0, dtype=np.longdouble)
self.arrival_roundoff_g += ag
self.lg[self.round_lane] += ag
gres = s["g"].astype(np.longdouble) - self.lg.sum(axis=0, dtype=np.longdouble)
self.max_ledger_residual = max(self.max_ledger_residual, np.max(np.abs(gres), initial=np.longdouble(0)))
require(np.isfinite(self.lv).all() and np.isfinite(self.lg).all(), "Nonfinite non-feedback lineage ledger")
positive_targets = np.unique(dst[accepted & positive])
self.first_positive_arrival[positive_targets[self.first_positive_arrival[positive_targets] < 0]] = tick
selected_rows = due[record_mask]
np.add.at(self.edge_prefirst_count, selected_rows, 1)
self.edge_first_arrival[selected_rows[self.edge_first_arrival[selected_rows] < 0]] = tick
self.edge_last_arrival[selected_rows] = tick
rows = []
for edge in selected_rows:
target = int(self.incoming.dst_local[edge])
weight = float(self.incoming.weights[edge])
rows.append({"edge_id": int(self.incoming.edge_id[edge]), "incoming_edge_row": int(edge),
"source_neuron_idx": int(self.incoming.src_global[edge]), "target_neuron_idx": int(self.targets[target]),
"source_group": self.names[int(self.groups[edge])], "source_spike_tick": tick - DELAY,
"arrival_tick": tick, "target_is_design": bool(self.design[target]), "accepted": True,
"weight_SI": weight, "weight_sign": 1 if weight > 0 else -1,
"ref_before": int(s["ref_before"][target]), "ref_at_arrival": int(s["ref_after_reset"][target]),
"available_at_integration": bool(s["available"][target]),
"old_v_SI": float(s["old_v"][target]), "old_g_SI": float(s["old_g"][target]),
"v_integrated_SI": float(s["v_integrated"][target]), "v_prethreshold_SI": float(s["v_threshold"][target]),
"threshold_margin_SI": float(self.coeff["threshold"] - s["v_threshold"][target]),
"g_before_arrivals_SI": float(s["g_after_reset"][target]),
"record_scope": "accepted nonzero input strictly before any observed first spike; join final first_spike_tick"})
if rows:
if self.sink is None:
self.first_arrival_records.extend(rows)
else:
self.sink(rows)
self.record_count += len(rows)
self.previous = {name: s[name].copy() for name in ("v", "g", "ref")}
self.callback_count += 1
def edge_summary(self):
"""All edges with stored pre-first records, including never-active PNs."""
selected = np.flatnonzero(self.edge_prefirst_count)
dst = self.incoming.dst_local[selected]
return {"edge_id": self.incoming.edge_id[selected].copy(),
"source_neuron_idx": self.incoming.src_global[selected].copy(), "target_neuron_idx": self.targets[dst].copy(),
"source_group": np.asarray(self.names, dtype=str)[self.groups[selected]],
"weight_SI": self.incoming.weights[selected].copy(),
"accepted_event_count": self.edge_prefirst_count[selected].copy(),
"nominal_weight_times_event_count_SI": (self.incoming.weights[selected].astype(np.longdouble) *
self.edge_prefirst_count[selected]).astype(np.float64),
"first_arrival_tick": self.edge_first_arrival[selected].copy(),
"last_arrival_tick": self.edge_last_arrival[selected].copy(),
"target_first_spike_tick": self.first[dst].copy(), "target_is_design": self.design[dst].copy()}
def finish(self, result):
"""Return (per-PN arrays, JSON report); caller verifies original events/state.
first_* floating fields are NaN when first_spike_observed is false.
They must not be imputed to zero or interpreted as observed voltages.
"""
require(not self.finished and self.callback_count == self.steps and self.previous is not None, "Observer did not cover every tick")
require(bit_equal(result["target_globals"], self.targets) and bit_equal(result["first_spike"], self.first) and
bit_equal(result["counts"], self.counts), "Replay result identity/count/first spike differs from observer")
require(all(bit_equal(result[name], self.previous[name]) for name in ("v", "g", "ref")), "Replay final state differs from last callback")
require(int(self.edge_prefirst_count.sum()) == self.record_count ==
int(self.stats["accepted_nonzero_before_first_edges"].sum()), "Pre-first event coverage mismatch")
require(np.array_equal(self.stats["due_edges"], self.stats["accepted_edges"] + self.stats["rejected_edges"]),
"Due/accepted/rejected coverage mismatch")
data = {"neuron_idx": self.targets.copy(), "is_design_target": self.design.copy(),
"spike_count": self.counts.copy(), "first_spike_tick": self.first.copy(), "first_spike_observed": self.first >= 0,
"first_request_tick": self.first_request.copy(), "first_accepted_request_tick": self.first_accepted_request.copy(),
"first_accepted_positive_arrival_tick": self.first_positive_arrival.copy(),
"requested": self.requested.copy(), "accepted_requests": self.request_accepted.copy(),
"rejected_requests": self.request_rejected.copy(), "first_spike_coincident_with_accepted_request": self.first_request_coincident.copy(),
"first_voltage_ledger_residual_SI": self.first_ledger_residual.astype(np.float64),
"integration_roundoff_v_SI": self.integration_roundoff_v.astype(np.float64),
"integration_roundoff_g_SI": self.integration_roundoff_g.astype(np.float64),
"arrival_roundoff_g_SI": self.arrival_roundoff_g.astype(np.float64)}
data.update({"first_" + name + ("_SI" if name != "ref_before" else ""): value.copy()
for name, value in self.first_state.items()})
for gi, name in enumerate(self.names):
for key, value in {**self.stats, **self.sums}.items():
data[name + "_" + key] = value[gi].copy() if key in self.stats else value[gi].astype(np.float64)
for index, name in enumerate(self.lane_names):
data["first_integrated_V_from_" + name] = self.first_integrated_lanes[index].astype(np.float64)
data["first_prethreshold_V_from_" + name] = self.first_threshold_lanes[index].astype(np.float64)
aggregates = {}
for name, mask in (("all_PN", np.ones(self.n, bool)), ("design_PN", self.design),
("non_design_PN", ~self.design), ("never_active_PN", self.first < 0)):
aggregates[name] = {"neurons": int(mask.sum()), "active": int(np.count_nonzero(mask & (self.first >= 0))),
"spikes": int(self.counts[mask].sum()), "source_groups": {
group: {**{key: int(value[gi, mask].sum()) for key, value in self.stats.items()},
**{key: float(value[gi, mask].sum(dtype=np.longdouble)) for key, value in self.sums.items()}}
for gi, group in enumerate(self.names)}}
report = {"pass_all": True, "scope": "Observer coverage/internal consistency only; caller must verify recorded PN events and final state exactly",
"callback_count": self.callback_count, "steps": self.steps, "PN_count": self.n,
"source_group_names": list(self.names), "first_arrival_record_count": self.record_count,
"first_arrival_record_limit": self.max_rows, "records_truncated": False,
"edge_summary_count": int(np.count_nonzero(self.edge_prefirst_count)), "aggregates": aggregates,
"time_contract": "source_spike_tick=arrival_tick-18; arrivals follow threshold; first-spike tick arrivals excluded",
"record_contract": "Every accepted nonzero edge event strictly before first spike for every PN; never-active PNs retain full-window records with final first=-1",
"sum_contract": "Observer nominal signed weight sums, not the sequentially rounded runtime g; weight-times-count edge summaries are descriptive",
"voltage_decomposition": {"nonfeedback": True, "precision": str(np.dtype(np.longdouble)),
"lanes": list(self.lane_names), "maximum_reconstruction_residual_SI": float(self.max_ledger_residual),
"roundoff_lane_explicit": True, "fitted_tolerance": False,
"reset_clears_prior_lanes": True, "request_override_has_separate_lane": True,
"first_integrated_is_before_request_override": True,
"never_spiked_first_fields_are_NaN": True,
"threshold_crossing_contributions_are_not_necessity_or_unique_cause": True},
"graded_APL_to_PN": "Required h32=0 and delta_si=0 every tick; fast APL weights supplied already frozen-masked by caller",
"new_brain_run": False, "parameter_changes": False, "shortest_path_claim": False,
"unique_cause_claim": False, "whole_brain_intervention_claim": False}
self.finished = True
return data, report