Files
digital-drosophila/models/male-cns-lif/wholebrain-plastic-interface-v1/eligibility_bridge.py
T
2026-09-30 16:11:14 +08:00

286 lines
15 KiB
Python

"""Pure Phase 4J eligibility adapter; no brain run, training, or artifact writes.
D is the original artificial command scalar. Actual DAN request acceptance and
spikes are separate evidence and MUST NOT silently replace D here. All input
arrays remain unchanged. Returned arrays own their storage and are read-only.
The NumPy trace backend is a candidate compatibility implementation; only an
explicit comparison with the Torch backend establishes Torch bitwise agreement.
"""
from __future__ import annotations
import ast
from dataclasses import dataclass
import hashlib
import math
from pathlib import Path
from types import MappingProxyType, SimpleNamespace
from typing import Any
import numpy as np
PHASE4J_SHA256 = "abb4574da85f00bd9ef61024c65993ef81d42651ff8d8d63c15dd69c6222471e"
DT_MS = 0.1
TAU_MS = 100.0
TRACE_CAP = 1.0
ETA_MV = 0.00275
FLOOR_FRACTION = 0.25
POLARITY = -1
COMPARTMENTS = ("B2B'2a", "a1", "y2a'1", "y5B'2a")
FIXED_GATES = MappingProxyType({"negative_us_before_cs_delta_exact_zero": True,
"plus_1000_to_zero_ratio_strict_upper": 0.01,
"off_compartment_delta_exact_unchanged": True,
"zero_d_and_frozen_delta_exact_unchanged": True})
_TRACE_EXPRESSION = (
"kc_trace.mul_(trace_decay).add_(spikes[self.kc_gpu].to(torch.float32))"
".clamp_max_(1.0)"
)
def _require(condition: bool, message: str) -> None:
if not condition:
raise ValueError(message)
def _readonly(array: np.ndarray) -> np.ndarray:
result = np.array(array, copy=True, order="C")
result.setflags(write=False)
return result
def bit_equal(left: np.ndarray, right: np.ndarray) -> bool:
a, b = np.asarray(left), np.asarray(right)
return a.dtype == b.dtype and a.shape == b.shape and a.tobytes() == b.tobytes()
def _integer(value: Any, name: str, minimum: int) -> int:
_require(isinstance(value, (int, np.integer)) and not isinstance(value, (bool, np.bool_)),
f"{name} must be an integer")
_require(int(value) >= minimum, f"{name} is below {minimum}")
return int(value)
def _array(value: Any, dtype: Any, name: str) -> np.ndarray:
result = np.asarray(value)
_require(result.ndim == 1 and result.dtype == np.dtype(dtype),
f"{name} must be a one-dimensional {np.dtype(dtype)} array")
return result
def _kc_array(kc: np.ndarray, n_neurons: int) -> np.ndarray:
kc = _array(kc, np.int32, "kc")
_require(len(kc) > 0 and np.all((kc >= 0) & (kc < n_neurons)), "KC IDs out of range or empty")
_require(np.all(kc[1:] > kc[:-1]), "KC IDs must be canonical sorted unique global IDs")
return kc
def _load_original(source: str | Path) -> tuple[Any, Any, dict[str, str]]:
"""Verify the entire frozen source, then execute ONLY one function AST.
Old imports, globals, class initialization and main are never executed.
The trace expression is also checked against the exact frozen expression.
"""
raw = Path(source).read_bytes()
actual_sha = hashlib.sha256(raw).hexdigest()
_require(actual_sha == PHASE4J_SHA256, "frozen Phase 4J source SHA256 mismatch")
tree = ast.parse(raw.decode("utf-8"), filename=str(source))
functions = [node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name == "apply_v2"]
_require(len(functions) == 1, "frozen apply_v2 AST is missing or ambiguous")
function = functions[0]
classes = [node for node in tree.body if isinstance(node, ast.ClassDef) and node.name == "APLSimulator"]
runs = [node for cls in classes for node in cls.body
if isinstance(node, ast.FunctionDef) and node.name == "run"]
expected = ast.parse(_TRACE_EXPRESSION).body[0]
expected_dump = ast.dump(expected, include_attributes=False)
expressions = [node for run in runs for node in ast.walk(run)
if isinstance(node, ast.Expr) and ast.dump(node, include_attributes=False) == expected_dump]
_require(len(expressions) == 1, "frozen Torch trace expression is missing or ambiguous")
module = ast.Module(body=[function], type_ignores=[])
namespace = {"np": np, "Validator": object, "Any": Any}
exec(compile(module, str(source) + "::apply_v2-only", "exec"), namespace)
trace_code = compile(ast.Module(body=[expressions[0]], type_ignores=[]),
str(source) + "::trace-expression-only", "exec")
evidence = dict(source_sha256=actual_sha,
apply_v2_ast_sha256=hashlib.sha256(ast.dump(function, include_attributes=False).encode()).hexdigest(),
trace_expression_ast_sha256=hashlib.sha256(expected_dump.encode()).hexdigest())
return namespace["apply_v2"], trace_code, evidence
@dataclass(frozen=True)
class TraceResult:
command_steps: np.ndarray
snapshots: np.ndarray
final: np.ndarray
audit: dict[str, Any]
@property
def last_command_trace(self) -> np.ndarray:
"""Original runtime's last scheduled-US snapshot, or zero without US."""
return _readonly(self.snapshots[-1] if len(self.snapshots) else np.zeros_like(self.final))
def trace_from_events(step: np.ndarray, neuron_idx: np.ndarray, kc: np.ndarray,
steps: int, command_steps: Any, *, phase4j_source: str | Path,
backend: str = "numpy", n_neurons: int = 166700) -> TraceResult:
"""Replay actual sparse spikes; snapshot AFTER the current tick's spike.
One Boolean spike per global neuron/tick, including refractory ticks as
recorded by the caller. This function does not reconstruct brain dynamics,
infer missing events, or apply any refractory guard to eligibility.
"""
_, trace_code, evidence = _load_original(phase4j_source)
steps = _integer(steps, "steps", 1)
n_neurons = _integer(n_neurons, "n_neurons", 1)
kc = _kc_array(kc, n_neurons)
step = _array(step, np.int32, "step")
neuron_idx = _array(neuron_idx, np.int32, "neuron_idx")
_require(len(step) == len(neuron_idx), "event columns have different lengths")
_require(np.all((step >= 0) & (step < steps)), "event tick outside trial")
_require(np.all((neuron_idx >= 0) & (neuron_idx < n_neurons)), "event neuron outside graph")
_require(np.all((step[1:] > step[:-1]) |
((step[1:] == step[:-1]) & (neuron_idx[1:] > neuron_idx[:-1]))),
"events must be sorted by tick/global ID and unique")
commands = np.asarray(command_steps)
_require(commands.ndim == 1, "command_steps must be one-dimensional")
if commands.size == 0:
commands = np.empty(0, np.int64)
_require(commands.ndim == 1 and commands.dtype.kind in "iu", "command_steps must be integers")
_require(np.all((commands >= 0) & (commands < steps)), "command tick outside trial")
_require(np.all(commands[1:] > commands[:-1]), "command_steps must be sorted unique")
commands = commands.astype(np.int64, copy=True)
_require(backend in ("numpy", "torch"), "backend must be numpy or torch")
lookup = np.full(n_neurons, -1, np.int32)
lookup[kc] = np.arange(len(kc), dtype=np.int32)
local = lookup[neuron_idx]
keep = local >= 0
ticks, local = step[keep], local[keep]
snapshots = np.empty((len(commands), len(kc)), np.float32)
decay = math.exp(-DT_MS / TAU_MS)
command_lookup = {int(t): i for i, t in enumerate(commands)}
cursor = 0
torch_version = None
if backend == "numpy":
trace = np.zeros(len(kc), np.float32)
spike_values = np.zeros(len(kc), np.float32)
else:
try:
import torch
except ImportError as exc:
raise RuntimeError("Torch CPU reference unavailable; compatibility is not established") from exc
torch_version = str(torch.__version__)
trace = torch.zeros(len(kc), dtype=torch.float32, device="cpu")
spike_values = torch.zeros(len(kc), dtype=torch.bool, device="cpu")
environment = dict(torch=torch, kc_trace=trace, trace_decay=decay,
spikes=spike_values,
self=SimpleNamespace(kc_gpu=torch.arange(len(kc), dtype=torch.int64)))
for tick in range(steps):
end = cursor
while end < len(ticks) and ticks[end] == tick:
end += 1
if backend == "numpy":
spike_values.fill(0)
spike_values[local[cursor:end]] = 1
# Separate float32 operations; never power-jump across empty ticks.
np.multiply(trace, np.float32(decay), out=trace)
np.add(trace, spike_values, out=trace)
np.minimum(trace, np.float32(TRACE_CAP), out=trace)
if tick in command_lookup:
snapshots[command_lookup[tick]] = trace
else:
spike_values.zero_()
if end > cursor:
spike_values[torch.from_numpy(local[cursor:end].astype(np.int64, copy=True))] = True
exec(trace_code, environment)
if tick in command_lookup:
snapshots[command_lookup[tick]] = trace.numpy()
cursor = end
final = trace if backend == "numpy" else trace.numpy()
audit = dict(evidence, backend=backend, torch_version=torch_version, numpy_version=str(np.__version__),
steps=steps, kc_count=len(kc), actual_kc_events=int(keep.sum()),
command_count=len(commands), dt_ms=DT_MS, trace_tau_ms=TAU_MS,
trace_cap=TRACE_CAP, decay_python_float=decay,
decay_float32_bits=int(np.asarray(np.float32(decay)).view(np.uint32)),
sampling="after_current_tick_spike", interval_skip_optimization=False,
torch_numpy_bitwise_comparison_performed=False,
d_semantics="artificial scheduled command; not measured DAN activity",
actual_dan_activity_used_to_gate=False, brain_dynamics_executed=False,
training_executed=False, learning_pass_claimed=False)
return TraceResult(_readonly(commands), _readonly(snapshots), _readonly(final), audit)
class _Column:
"""Small immutable-array interface consumed by the frozen function."""
def __init__(self, values: np.ndarray):
self.values = values
def to_numpy(self, dtype: Any = None) -> np.ndarray:
return self.values.astype(dtype, copy=True) if dtype is not None else self.values.copy()
def astype(self, dtype: Any) -> "_Column":
return _Column(self.values.astype(dtype))
def apply_eligibility(trace: np.ndarray, kc: np.ndarray, candidate_sources: np.ndarray,
candidate_compartments: np.ndarray, compartment: str,
delta: np.ndarray, base_candidate: np.ndarray, *, d: float,
frozen: bool = False, phase4j_source: str | Path,
n_neurons: int = 166700) -> tuple[np.ndarray, dict[str, Any]]:
"""Apply the SHA-pinned original apply_v2 AST to candidate-aligned arrays.
delta/base_candidate are ORIGINAL float32 mV values, not SI or reconstructed
float64 D18 weights. Candidate row identity and the complete 14,232-edge mask
are verified by the coordinator; a small synthetic subset is allowed here.
Existing overlays must already obey the original floor and canonical +0.
No input is clipped, repaired, reordered, or mutated.
"""
original, _, evidence = _load_original(phase4j_source)
n_neurons = _integer(n_neurons, "n_neurons", 1)
kc = _kc_array(kc, n_neurons)
trace = _array(trace, np.float32, "trace")
sources = _array(candidate_sources, np.int32, "candidate_sources")
delta = _array(delta, np.float32, "delta")
base = _array(base_candidate, np.float32, "base_candidate")
compartments = np.asarray(candidate_compartments)
_require(compartments.ndim == 1 and compartments.dtype.kind == "U", "candidate_compartments must be Unicode strings")
_require(len(trace) == len(kc) and np.all(np.isfinite(trace)) and np.all((trace >= 0) & (trace <= TRACE_CAP)),
"trace must match KC IDs and lie in [0,1]")
_require(len(delta) == len(base) == len(sources) == len(compartments), "candidate columns have different lengths")
_require(np.all(np.isin(sources, kc)), "all candidate sources must be canonical KCs")
_require(compartment in COMPARTMENTS and np.all(np.isin(compartments, COMPARTMENTS)),
"unrecognized exact frozen compartment")
_require(isinstance(d, (float, int, np.floating, np.integer)) and not isinstance(d, (bool, np.bool_))
and np.isfinite(d) and float(d) in (0.0, 1.0), "D must be the fixed artificial scalar 0 or 1")
_require(isinstance(frozen, (bool, np.bool_)), "frozen must be Boolean")
_require(np.all(np.isfinite(base)) and np.all(base > 0), "original candidate base must be finite positive float32 mV")
lower = -FLOOR_FRACTION * base
_require(np.all(np.isfinite(delta)) and np.all((delta >= lower) & (delta <= 0)), "delta violates original frozen floor")
_require(not np.any((delta == 0) & np.signbit(delta)), "delta zero must be canonical +0 for exact identity")
validator = SimpleNamespace(n=n_neurons, kc=kc.copy(), base_candidate=base.copy(),
cand={"source_neuron_idx": _Column(sources.copy()),
"target_compartment_verbatim": _Column(compartments.copy())})
config = {"eligibility_v2": {"eta_effective_mv": ETA_MV,
"max_fractional_depression": FLOOR_FRACTION}}
if frozen:
result = delta.copy()
original_report = None
else:
result, original_report = original(validator, trace.copy(), compartment, float(d), delta.copy(), config)
selected = compartments == compartment
_require(result.dtype == np.float32 and np.all(np.isfinite(result)), "original update returned invalid values")
_require(bit_equal(result[~selected], delta[~selected]), "off-compartment delta changed")
if frozen or float(d) == 0:
_require(bit_equal(result, delta), "zero-D/frozen identity failed")
_require(np.all((result >= lower) & (result <= 0)), "original result violates frozen floor")
report = dict(evidence, original_apply_v2_executed=not bool(frozen), original_report=original_report,
candidate_count=len(delta), selected_compartment=compartment,
selected_candidate_count=int(selected.sum()), d_command=float(d), frozen=bool(frozen),
actual_changed_edges=int(np.count_nonzero(result != delta)),
off_compartment_exact_unchanged=True, numpy_version=str(np.__version__),
eta_effective_mv=ETA_MV, polarity=POLARITY, floor_fraction=FLOOR_FRACTION,
delta_units="mV", delta_dtype="float32", original_base_units="mV",
complete_mask_identity_verified_by_this_function=False,
actual_dan_activity_used_to_gate=False,
d_semantics="artificial scheduled command; not measured DAN activity",
brain_dynamics_executed=False, training_executed=False, learning_pass_claimed=False)
return _readonly(result), report