Files
digital-drosophila/models/male-cns-lif/author-fullgraph-lab-v1/oracle_full.py
T

541 lines
31 KiB
Python

"""Phase 4AF: a streaming float64 oracle for three fixed short full-graph trials.
Importing this module does not import Brian. queue_summary is shared with the
GPU comparison code. All scientific inputs and the sealed 4AD/4AE code remain
read-only. Every run_case call belongs in a fresh process.
"""
from __future__ import annotations
import csv
import hashlib
import importlib.metadata
import json
import os
import platform
import struct
import sys
import types
from contextlib import ExitStack
from pathlib import Path
import numpy as np
N_NEURONS = 166700
N_EDGES = 25582938
BLOCK_BYTES = 8 << 20
QUEUE_HEADER = b"phase4af-queue-v1\0"
AUTHOR_PATH = Path("/data/digital-drosophila/experiments/phase4ad-author-semantics/external-resources/model.py")
AUTHOR_SHA256 = "fc45837d7122c6ce2a7f3f2f23c515992e4b232aadb919efabb72337fac88e4e"
AE_ORACLE_PATH = Path("/data/digital-drosophila/models/male-cns-lif/author-runtime-lab-v1/oracle_stress.py")
AE_ORACLE_SHA256 = "32eb1c42383068ce52701d2139a376ce155ad7072cde019949f8ca2d55492409"
EVENT_DTYPE = np.dtype([("step", "<i4"), ("neuron_idx", "<i4")])
_PROCESS_ALREADY_USED = False
def queue_summary(tick, edges, accepted, chunk_bytes=BLOCK_BYTES):
"""Hash original queue order without sorting, filtering, or changing inputs.
joint_sha256 is SHA256(header + pack('<qq', tick, count) + all edge bytes
in little-endian int64 order + all uint8 acceptance bytes). The two other
hashes omit the header and contain only their respective byte sequences.
``accepted`` may be a (count,) array or a pure callable(start, stop), which
lets the CPU read the post-threshold guard in bounded chunks. Empty ticks
retain their tick/count header in the joint hash.
"""
edge_view = np.asarray(edges)
if edge_view.ndim != 1 or edge_view.dtype.kind not in "iu":
raise ValueError("Queue edges must be a one-dimensional integer array")
tick_int = int(tick)
if tick_int != tick or tick_int < 0:
raise ValueError("Queue tick must be a nonnegative integer")
count = len(edge_view)
width = int(chunk_bytes) // np.dtype("<i8").itemsize
if width <= 0:
raise ValueError("chunk_bytes must hold at least one int64 edge")
mask_view = None if callable(accepted) else np.asarray(accepted)
if mask_view is not None and mask_view.shape != (count,):
raise ValueError("Queue acceptance mask must match the edge count")
edge_hash, mask_hash = hashlib.sha256(), hashlib.sha256()
joint = hashlib.sha256(QUEUE_HEADER + struct.pack("<qq", tick_int, count))
for begin in range(0, count, width):
original = edge_view[begin:begin + width]
if original.dtype.kind == "u" and np.any(original > np.iinfo(np.int64).max):
raise ValueError("Queue edge ID cannot be encoded as signed int64")
chunk = np.ascontiguousarray(original, dtype="<i8")
data = memoryview(chunk).cast("B")
edge_hash.update(data)
joint.update(data)
accepted_count = 0
for begin in range(0, count, width):
stop = min(count, begin + width)
original = np.asarray(accepted(begin, stop) if callable(accepted) else mask_view[begin:stop])
if original.shape != (stop - begin,) or original.dtype.kind not in "biu":
raise ValueError("Acceptance chunks must be one-dimensional boolean/integer masks")
if np.any((original != 0) & (original != 1)):
raise ValueError("Acceptance values must be zero or one")
chunk = np.ascontiguousarray(original, dtype=np.uint8)
accepted_count += int(chunk.sum(dtype=np.int64))
data = memoryview(chunk).cast("B")
mask_hash.update(data)
joint.update(data)
return {"tick": tick_int, "count": count, "accepted_count": accepted_count,
"ordered_edge_sha256": edge_hash.hexdigest(),
"accepted_mask_sha256": mask_hash.hexdigest(),
"joint_sha256": joint.hexdigest()}
def _save(path, value):
with Path(path).open("x", encoding="utf-8") as handle:
json.dump(value, handle, indent=2, sort_keys=True, allow_nan=False)
handle.write("\n")
def _digest(path):
result = hashlib.sha256()
with Path(path).open("rb") as handle:
for block in iter(lambda: handle.read(BLOCK_BYTES), b""):
result.update(block)
return result.hexdigest()
def _check(condition, message):
if not condition:
raise ValueError(message)
def _tree_bytes(root):
total = 0
for directory, _, filenames in os.walk(root, followlinks=False):
for name in filenames:
total += (Path(directory) / name).stat(follow_symlinks=False).st_size
return total
def _budget(root, limit, reserve=0):
actual = _tree_bytes(root)
if actual + int(reserve) > limit:
raise RuntimeError(f"New-output budget would be exceeded: {actual} + {reserve} > {limit} bytes; inputs were not adjusted")
return actual
def _new_memmap(path, dtype, shape):
if Path(path).exists():
raise FileExistsError(path)
return np.lib.format.open_memmap(path, mode="w+", dtype=np.dtype(dtype), shape=shape)
def _load_checked_module(path, expected_hash, name):
data = Path(path).read_bytes()
actual = hashlib.sha256(data).hexdigest()
_check(actual == expected_hash, f"Pinned source hash mismatch: {path}")
module = types.ModuleType(name)
module.__file__ = str(path)
# Execute checked bytes without producing __pycache__ beside a sealed file.
exec(compile(data, str(path), "exec"), module.__dict__)
return module
def _graph_inputs(graph_dir):
definitions = {
"nodes": (np.int32, (N_NEURONS,)), "edge_id": (np.int64, (N_EDGES,)),
"src": (np.int32, (N_EDGES,)), "dst": (np.int32, (N_EDGES,)),
"signed_raw": (np.int64, (N_EDGES,)), "ptr": (np.int64, (N_NEURONS + 1,)),
}
arrays = {}
for name, (dtype, shape) in definitions.items():
value = np.load(graph_dir / (name + ".npy"), mmap_mode="r", allow_pickle=False)
_check(value.dtype == np.dtype(dtype) and value.shape == shape, f"Incorrect canonical {name} shape/dtype")
arrays[name] = value
_check(np.array_equal(arrays["nodes"], np.arange(N_NEURONS, dtype=np.int32)),
"This full-graph protocol requires canonical global node indices 0..N-1")
ptr = arrays["ptr"]
_check(ptr[0] == 0 and ptr[-1] == N_EDGES and np.all(np.diff(ptr) >= 0), "Invalid canonical CSR pointer")
source_counts = np.zeros(N_NEURONS, dtype=np.int64)
previous_source = -1
width = BLOCK_BYTES // 8
for begin in range(0, N_EDGES, width):
stop = min(N_EDGES, begin + width)
_check(np.array_equal(arrays["edge_id"][begin:stop], np.arange(begin, stop, dtype=np.int64)),
"Full-graph edge identity/order is not the original canonical CSR order")
source, target = arrays["src"][begin:stop], arrays["dst"][begin:stop]
_check(source[0] >= previous_source and np.all(np.diff(source) >= 0), "Sources are not canonically sorted")
_check(source[0] >= 0 and source[-1] < N_NEURONS and np.all((target >= 0) & (target < N_NEURONS)),
"Canonical endpoint outside the full graph")
previous_source = int(source[-1])
source_counts += np.bincount(source, minlength=N_NEURONS)
_check(np.array_equal(source_counts, np.diff(ptr)), "src and CSR pointer disagree")
for name in ("completeness.csv", "connectivity.parquet"):
_check((graph_dir / name).is_file(), f"Missing author input file {name}")
return arrays
class _StreamingQueueProxy:
"""The 4AE forwarding contract with bounded hashing instead of retained lists."""
def __init__(self, original, get_tick, get_available, arrays, due_counts,
spike_ring, delay_steps, queue_file, records):
self._original_queue = original
self._get_tick = get_tick
self._get_available = get_available
self._arrays = arrays
self._due_counts = due_counts
self._spike_ring = spike_ring
self._delay_steps = delay_steps
self._queue_file = queue_file
self._records = records
def peek(self, *args, **kwargs):
original = self._original_queue.peek(*args, **kwargs)
edge_view = np.asarray(original)
tick = self._get_tick()
_check(tick == len(self._records["rows"]), "Queue peek ticks must be unique and consecutive")
_check(len(edge_view) == int(self._due_counts[tick % len(self._due_counts)]),
"Queue arrival count differs from delayed outgoing degree of real spikes")
available = np.asarray(self._get_available(), dtype=bool)
fired_then = np.zeros(N_NEURONS, dtype=bool)
if tick >= self._delay_steps:
fired_then[self._spike_ring[(tick - self._delay_steps) % len(self._spike_ring)]] = True
def accepted_chunk(begin, stop):
local_edges = edge_view[begin:stop]
_check(np.all((local_edges >= 0) & (local_edges < N_EDGES)), "Queue contains an invalid local edge index")
_check(np.all(fired_then[self._arrays["src"][local_edges]]),
"Queue arrival lacks a presynaptic spike at the fixed 18-step delay")
return available[self._arrays["dst"][local_edges]]
# edge_id[k] == k was independently checked for every canonical edge.
# Thus the untouched local queue array is also the original edge-ID stream.
summary = queue_summary(tick, edge_view, accepted_chunk)
self._queue_file.write(json.dumps(summary, sort_keys=True) + "\n")
self._records["rows"].append(summary)
self._records["peek_calls"] += 1
return original
def advance(self, *args, **kwargs):
self._records["advance_calls"] += 1
return self._original_queue.advance(*args, **kwargs)
def __getattr__(self, name):
return getattr(self._original_queue, name)
def run_case(case_dir: Path, out_dir: Path) -> dict:
"""Run exactly one preregistered float64 case in this process.
Outputs: v/g/ref history .npy memmaps; actual weights.npy; sparse state.npz;
queue.jsonl; events.bin with two little-endian int32 fields; request records;
small per-tick statistics and audit JSON. A failed trial retains partial
outputs and error.json and never writes summary.json with complete=True.
"""
global _PROCESS_ALREADY_USED
_check(not _PROCESS_ALREADY_USED, "Each full-graph oracle case requires a new process")
_PROCESS_ALREADY_USED = True
case_dir = Path(case_dir).resolve(strict=True)
out_dir = Path(out_dir).resolve()
spec = json.loads((case_dir / "spec.json").read_text(encoding="utf-8"))
graph_dir = Path(spec["graph_dir"]).resolve(strict=True)
steps = int(spec["steps"])
seeds = tuple(int(node) for node in spec["seed_nodes"])
fixed = {"no_input": (100, ()), "single": (500, (1381,)),
"five": (500, (1197, 1381, 1511, 82312, 133666))}
_check(spec["name"] in fixed and (steps, seeds) == fixed[spec["name"]], "Case is not one of the three fixed trials")
_check(float(spec["dt_ms"]) == .1 and np.isclose(float(spec["duration_ms"]), steps * .1, rtol=0, atol=1e-12),
"Case clock/duration differs from the fixed protocol")
_check(spec.get("precision", "float64") == "float64", "Full-graph oracle is float64 only")
_check(spec.get("all_requests_at_tick", 0) == 0 and not spec.get("apl_overlay", False) and not spec.get("training", False),
"This oracle does not permit altered input timing, APL, or training")
budget_root = Path(spec.get("budget_root", os.path.commonpath([str(case_dir), str(graph_dir), str(out_dir)]))).resolve()
budget_limit = int(spec.get("output_budget_bytes", 15 * 1024 ** 3))
_check(budget_limit > 0, "Output budget must be positive")
_check(out_dir.is_relative_to(budget_root), "Oracle output must be inside its budget root")
out_dir.mkdir(parents=True, exist_ok=False)
memmaps = []
last_complete_tick = -1
saved_preferences = None
old_dt = None
try:
_budget(budget_root, budget_limit, reserve=steps * N_NEURONS * 20 + N_EDGES * 8 + (64 << 20))
versions = {name: importlib.metadata.version(name)
for name in ("brian2", "numpy", "pandas", "pyarrow", "sympy", "scipy", "cython")}
_save(out_dir / "environment.json", {"python": sys.version, "platform": platform.platform(),
"packages": versions, "precision": "float64", "codegen": "numpy"})
for name, required in {"brian2": "2.5.1", "numpy": "1.22.3", "sympy": "1.12"}.items():
_check(versions[name] == required, f"{name} must be pinned to {required}")
import brian2 as b2
from brian2.core.base import BrianObject
ae = _load_checked_module(AE_ORACLE_PATH, AE_ORACLE_SHA256, "phase4af_readonly_ae_helpers")
author = _load_checked_module(AUTHOR_PATH, AUTHOR_SHA256, "phase4af_pinned_author")
arrays = _graph_inputs(graph_dir)
graph_hashes = {name: _digest(graph_dir / name) for name in (
"nodes.npy", "edge_id.npy", "src.npy", "dst.npy", "signed_raw.npy", "ptr.npy",
"completeness.csv", "connectivity.parquet")}
_save(out_dir / "input-audit.json", {"spec": spec, "spec_sha256": _digest(case_dir / "spec.json"),
"graph_hashes": graph_hashes, "author_sha256": AUTHOR_SHA256,
"sealed_ae_oracle_sha256": AE_ORACLE_SHA256,
"budget_root": str(budget_root), "budget_limit_bytes": budget_limit,
"source_order": "canonical CSR; local edge index equals original edge_id",
"common_interface_preserved": True, "poi_used": False, "apl_overlay": False, "training": False})
saved_preferences = {key: b2.prefs[key] for key in (
"codegen.target", "core.default_float_dtype", "legacy.refractory_timing")}
old_dt = b2.defaultclock.dt
b2.start_scope()
b2.prefs["codegen.target"] = "numpy"
b2.prefs["core.default_float_dtype"] = np.float64
b2.prefs["legacy.refractory_timing"] = False
b2.defaultclock.dt = .1 * b2.ms
parameters = dict(author.default_params)
_save(out_dir / "parameter-checks.json", ae._parameters_match(parameters, spec["model"], b2))
neu, syn, spikes = author.create_model(graph_dir / "completeness.csv",
graph_dir / "connectivity.parquet", parameters)
_check(len(neu) == N_NEURONS and len(syn) == N_EDGES, "Author model did not load the whole graph")
_check(all(np.dtype(neu.variables[key].dtype) == np.float64 for key in ("v", "g")) and
np.dtype(syn.variables["w"].dtype) == np.float64, "Author state/weight dtype is not float64")
weights = _new_memmap(out_dir / "weights.npy", "<f8", (N_EDGES,))
memmaps.append(weights)
width = BLOCK_BYTES // 8
w_unit = float(parameters["w_syn"] / b2.volt)
for begin in range(0, N_EDGES, width):
stop = min(N_EDGES, begin + width)
np.testing.assert_array_equal(np.asarray(syn.i[begin:stop]), arrays["src"][begin:stop])
np.testing.assert_array_equal(np.asarray(syn.j[begin:stop]), arrays["dst"][begin:stop])
actual_w = np.asarray(syn.w_[begin:stop], dtype=np.float64)
expected_w = arrays["signed_raw"][begin:stop] * w_unit
_check(np.array_equal(actual_w.view(np.uint64), expected_w.view(np.uint64)),
"Author weights differ from unchanged signed raw times the original SI w_syn")
weights[begin:stop] = actual_w
weights.flush()
v_history = _new_memmap(out_dir / "v-history.float64.npy", "<f8", (steps, N_NEURONS))
g_history = _new_memmap(out_dir / "g-history.float64.npy", "<f8", (steps, N_NEURONS))
ref_history = _new_memmap(out_dir / "ref-history.int32.npy", "<i4", (steps, N_NEURONS))
memmaps.extend([v_history, g_history, ref_history])
_save(out_dir / "output-schema.json", {
"history_shape": [steps, N_NEURONS], "v_file": "v-history.float64.npy",
"g_file": "g-history.float64.npy", "ref_file": "ref-history.int32.npy",
"voltage_unit": "SI volt", "v_g_dtype": "little-endian float64", "ref_dtype": "little-endian int32",
"event_file": "events.bin", "event_record": [["step", "<i4"], ["neuron_idx", "<i4"]],
"event_order": "tick then canonical neuron_idx", "queue_file": "queue.jsonl",
"queue_joint_bytes": "b'phase4af-queue-v1\\0' + struct.pack('<qq',tick,count) + all ordered <i8 edge IDs + all uint8 acceptance bits",
"queue_separate_hashes": "SHA256 of each pure byte sequence without header",
"complete_marker": "summary.json with complete=true; partial memmaps alone are not a completed trial"})
_budget(budget_root, budget_limit, reserve=64 << 20)
delay_steps = int(round(float(parameters["t_dly"] / b2.ms) / .1))
refractory_steps = int(round(float(parameters["t_rfc"] / b2.ms) / .1))
_check(delay_steps == 18 and refractory_steps == 22, "Unexpected fixed delay or refractory duration")
queue_len = delay_steps + 1
due_counts = np.zeros(queue_len, dtype=np.int64)
spike_ring = [np.empty(0, dtype=np.int32) for _ in range(queue_len)]
outdegree = np.diff(arrays["ptr"])
request_count = np.zeros(steps, dtype=np.int32)
applicable_count = np.zeros(steps, dtype=np.int32)
rejected_count = np.zeros(steps, dtype=np.int32)
spike_count = np.zeros(steps, dtype=np.int32)
total_counts = np.zeros(N_NEURONS, dtype=np.int32)
request_seen = np.zeros(steps, dtype=np.uint8)
seed_local = np.asarray(seeds, dtype=np.int32)
permitted_seed = np.empty(0, dtype=np.int32)
request_rows = []
records = {"rows": [], "peek_calls": 0, "advance_calls": 0, "installations": []}
total_events = 0
v_raw = neu.variables["v"].get_value()
g_raw = neu.variables["g"].get_value()
last_raw = neu.variables["lastspike"].get_value()
available_raw = neu.variables["not_refractory"].get_value()
last_ticks = np.zeros(N_NEURONS, dtype=np.int64)
def tick_now():
return int(round(float(neu.clock.t / b2.second) / .0001))
with ExitStack() as stack:
queue_file = stack.enter_context((out_dir / "queue.jsonl").open("x", encoding="utf-8"))
event_file = stack.enter_context((out_dir / "events.bin").open("xb"))
tick_file = stack.enter_context((out_dir / "ticks.jsonl").open("x", encoding="utf-8"))
requests_file = stack.enter_context((out_dir / "requests.csv").open("x", encoding="utf-8", newline=""))
request_writer = csv.DictWriter(requests_file,
fieldnames=["tick", "neuron_idx", "attempted", "accepted", "rejected"])
request_writer.writeheader()
def request_operation():
nonlocal permitted_seed
tick = tick_now()
_check(0 <= tick < steps and not request_seen[tick], "Invalid/duplicate common request tick")
request_seen[tick] = 1
if tick == 0 and len(seed_local):
available = available_raw[seed_local].copy()
permitted_seed = seed_local[available]
request_count[tick] = len(seed_local)
applicable_count[tick] = len(permitted_seed)
rejected_count[tick] = len(seed_local) - len(permitted_seed)
for node, ok in zip(seed_local, available):
request_rows.append({"tick": 0, "neuron_idx": int(node), "attempted": 1,
"accepted": int(ok), "rejected": int(not ok)})
neu.v[permitted_seed] = parameters["v_th"] + 1 * b2.mV
def record_end():
nonlocal last_complete_tick, total_events
tick = tick_now()
_check(tick == last_complete_tick + 1 and tick < steps, "End-state ticks are not consecutive")
_check(request_seen[tick] == 1 and len(records["rows"]) == tick + 1,
"End state lacks its request/queue observation")
_check(np.isfinite(v_raw).all() and np.isfinite(g_raw).all(), "Author state became nonfinite")
seen = np.isfinite(last_raw) & (last_raw >= 0)
last_ticks.fill(0)
last_ticks[seen] = np.rint(last_raw[seen] / .0001).astype(np.int64)
ref = np.where(seen, np.maximum(refractory_steps - (tick - last_ticks), 0), 0).astype(np.int32)
_check(np.array_equal(ref == 0, available_raw), "Integer refractory history disagrees with Brian")
v_history[tick] = v_raw
g_history[tick] = g_raw
ref_history[tick] = ref
active = np.asarray(neu.spikes, dtype=np.int32)
_check(not len(active) or np.all(np.diff(active) > 0), "Spike order is not canonical and unique")
spike_count[tick] = len(active)
total_counts[active] += 1
if len(active):
rows = np.empty(len(active), dtype=EVENT_DTYPE)
rows["step"] = tick
rows["neuron_idx"] = arrays["nodes"][active]
event_file.write(memoryview(rows).cast("B"))
total_events += len(active)
spike_ring[tick % queue_len] = active.copy()
due_counts[(tick + delay_steps) % queue_len] = int(outdegree[active].sum(dtype=np.int64))
if tick == 0:
_check(np.isin(permitted_seed, active).all(), "An applicable initial request failed to create a real spike")
for row in request_rows:
request_writer.writerow(row)
queue_row = records["rows"][tick]
tick_record = {"tick": tick, "spikes": len(active), "requests": int(request_count[tick]),
"applicable_requests": int(applicable_count[tick]), "ref_rejected_requests": int(rejected_count[tick]),
"due_arrivals": queue_row["count"], "accepted_arrivals": queue_row["accepted_count"],
"event_sha256": hashlib.sha256(np.ascontiguousarray(arrays["nodes"][active], dtype="<i4").tobytes()).hexdigest()}
tick_file.write(json.dumps(tick_record, sort_keys=True) + "\n")
# Mapped files already reserve their full logical size. This
# checks the complete new experiment tree, not only this case.
_budget(budget_root, budget_limit, reserve=1 << 20)
last_complete_tick = tick
if (tick + 1) % 50 == 0 or tick + 1 == steps:
for mapped in (v_history, g_history, ref_history):
mapped.flush()
for handle in (queue_file, event_file, tick_file, requests_file):
handle.flush()
if (tick + 1) % 100 == 0 or tick + 1 == steps:
print(json.dumps({"oracle": spec["name"], "completed_ticks": tick + 1,
"spikes": total_events}), flush=True)
class QueueInstaller(BrianObject):
def __init__(self):
super().__init__(clock=neu.clock, when="end", order=1_000_000,
name="zz_fullgraph_queue_installer")
def before_run(self, run_namespace):
installed = []
for code in syn.pre.code_objects:
if "_queue" not in code.namespace or "_queue.peek()" not in str(code.code):
continue
original = code.namespace["_queue"]
if isinstance(original, _StreamingQueueProxy):
original = original._original_queue
code.namespace["_queue"] = _StreamingQueueProxy(
original, tick_now, lambda: available_raw, arrays, due_counts,
spike_ring, delay_steps, queue_file, records)
installed.append(code.name)
_check(len(installed) == 1, "Expected one original synaptic queue consumer")
records["installations"].append(installed[0])
request_op = b2.NetworkOperation(request_operation, clock=neu.clock,
when="before_thresholds", order=0, name="oracle_common_requests")
end_op = b2.NetworkOperation(record_end, clock=neu.clock,
when="end", order=0, name="oracle_streaming_end")
network = b2.Network(neu, syn, spikes, request_op, end_op, QueueInstaller())
_save(out_dir / "schedule.json", {"schedule": list(network.schedule),
"summary": str(network.scheduling_summary())})
network.run(0 * b2.ms, namespace={})
_save(out_dir / "compiled-code.json", ae._code_records(network))
_check(last_complete_tick == -1 and not records["rows"] and not request_seen.any(),
"Zero-duration compilation unexpectedly advanced a trial")
# Exactly one positive-duration call. Flushing files never resets a
# clock, reruns before_run, or changes the in-flight delay queue.
network.run(steps * .1 * b2.ms, namespace={})
_save(out_dir / "generated-code.json", ae._code_records(network))
_check(last_complete_tick == steps - 1 and np.all(request_seen == 1), "Trial stopped before all ticks completed")
_check(records["peek_calls"] == records["advance_calls"] == steps, "Queue observation was incomplete")
_check(total_events == int(spikes.num_spikes) and np.array_equal(total_counts, np.asarray(spikes.count[:])),
"Sparse streamed events disagree with original SpikeMonitor counts")
_check(np.all(np.asarray(neu.rfc[:] / b2.ms) == float(parameters["t_rfc"] / b2.ms)),
"Original refractory configuration changed")
for begin in range(0, N_EDGES, width):
stop = min(N_EDGES, begin + width)
_check(np.array_equal(np.asarray(syn.w_[begin:stop]).view(np.uint64),
np.asarray(weights[begin:stop]).view(np.uint64)),
"Original synaptic weights changed during execution")
events = (np.memmap(out_dir / "events.bin", dtype=EVENT_DTYPE, mode="r", shape=(total_events,))
if total_events else np.empty(0, dtype=EVENT_DTYPE))
_check((out_dir / "events.bin").stat().st_size == total_events * EVENT_DTYPE.itemsize,
"Sparse event file length is inconsistent")
event_block = BLOCK_BYTES // 8
for begin in range(0, total_events, event_block):
stop = min(total_events, begin + event_block)
np.testing.assert_array_equal(events["neuron_idx"][begin:stop],
np.asarray(spikes.i[begin:stop], dtype=np.int32))
native_ticks = np.rint(np.asarray(spikes.t[begin:stop] / b2.second) / .0001).astype(np.int32)
np.testing.assert_array_equal(events["step"][begin:stop], native_ticks)
_budget(budget_root, budget_limit, reserve=total_events * 8 + N_NEURONS * 24 + (8 << 20))
with (out_dir / "state.npz").open("xb") as handle:
np.savez_compressed(handle, step=events["step"], neuron_idx=events["neuron_idx"], counts=total_counts,
v=np.asarray(v_history[-1]), g=np.asarray(g_history[-1]), ref=np.asarray(ref_history[-1]))
with (out_dir / "tick-summary.npz").open("xb") as handle:
np.savez_compressed(handle, request_count=request_count, applicable_count=applicable_count,
rejected_count=rejected_count, spike_count=spike_count,
arrival_count=np.asarray([row["count"] for row in records["rows"]], dtype=np.int64),
accepted_arrival_count=np.asarray([row["accepted_count"] for row in records["rows"]], dtype=np.int64))
coefficients = ae._coefficients(neu, parameters, b2, .0001)
_save(out_dir / "coefficients.json", coefficients)
_save(out_dir / "queue-audit.json", {"peek_calls": records["peek_calls"],
"advance_calls": records["advance_calls"], "installations": records["installations"],
"due_count_matches_presynaptic_spikes_and_original_outdegrees": True,
"acceptance_observed_after_thresholds_before_synaptic_writes": True,
"same_peek_object_returned": True, "all_other_queue_methods_forwarded": True,
"original_order_not_sorted_or_filtered": True, "hash_header_hex": QUEUE_HEADER.hex(),
"edge_id_identity_verified_for_all_edges": True,
"fullgraph_unwrapped_repeat_performed": False,
"prior_evidence": "Phase4AE exact wrapped/unwrapped checks; current bounded-hash adapter preserves the same forwarding contract",
"limit": "Current full-graph streaming proxy is audited by forwarding logic and per-tick counts; no separate unwrapped full-graph run is claimed."})
_check(_digest(AUTHOR_PATH) == AUTHOR_SHA256 and _digest(AE_ORACLE_PATH) == AE_ORACLE_SHA256,
"A sealed author/dependency source changed during execution")
for name, expected in graph_hashes.items():
_check(_digest(graph_dir / name) == expected, f"Prepared graph input changed during execution: {name}")
for mapped in memmaps:
mapped.flush()
current_bytes = _budget(budget_root, budget_limit, reserve=1 << 20)
summary = {"name": spec["name"], "complete": True, "backend": "Brian2 2.5.1 numpy",
"precision": "float64", "nodes": N_NEURONS, "edges": N_EDGES,
"steps": steps, "completed_ticks": last_complete_tick + 1, "spikes": total_events,
"steps_expected": steps, "steps_completed": last_complete_tick + 1, "neurons": N_NEURONS,
"pass_all": True, "pass_all_scope": "CPU input, streaming, and internal self-checks only; GPU equivalence is not assessed here",
"requests": int(request_count.sum()), "applicable_requests": int(applicable_count.sum()),
"ref_rejected_requests": int(rejected_count.sum()),
"due_arrivals": sum(row["count"] for row in records["rows"]),
"accepted_arrivals": sum(row["accepted_count"] for row in records["rows"]),
"single_continuous_run": True, "source_and_graph_inputs_unchanged": True,
"output_budget_root": str(budget_root), "output_bytes_before_summary": current_bytes,
"output_budget_bytes": budget_limit, "weights_sha256": _digest(out_dir / "weights.npy"),
"queue_sha256": _digest(out_dir / "queue.jsonl"), "events_sha256": _digest(out_dir / "events.bin"),
"no_training": True, "no_apl_overlay": True, "no_poi": True,
"scope": "Only this fixed short numerical trajectory; not olfactory CAL, long-run equivalence, physiological validation, or learning.",
"pending_arrivals_limit": "Queue arrivals beyond the final recorded tick are outside this comparison window."}
_save(out_dir / "summary.json", summary)
return summary
except Exception as error:
for mapped in memmaps:
mapped.flush()
_save(out_dir / "error.json", {"complete": False, "last_complete_tick": last_complete_tick,
"type": type(error).__name__, "message": str(error),
"partial_files_preserved": True, "input_or_duration_adjusted": False})
raise
finally:
if saved_preferences is not None:
for key, value in saved_preferences.items():
b2.prefs[key] = value
b2.defaultclock.dt = old_dt