286 lines
15 KiB
Python
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
|