Archive MaleCNS research sources, provenance, bibliography and immutable data inventory

This commit is contained in:
Digital Drosophila Archive
2026-09-29 03:38:32 +08:00
commit 3981628ff8
682 changed files with 161475 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
{
"artifact": "io-v1",
"files": [
{
"bytes": 1564,
"path": "VERSION.json",
"sha256": "2ad17a93bf22339db56ae3869af9a2a7c26ca7359d8923c693d71dde63927812"
},
{
"bytes": 23822,
"path": "brain_runtime.py",
"sha256": "1c4e3e6f06708bfec30b24e92297f320b2b8b87ebbbe6da501f5f2db4f74762a"
},
{
"bytes": 34442,
"path": "phase4d.py",
"sha256": "9ed8e4246252bdfc590c88e2993b7e105f278c6c4943e1c688f387c1cf54aeb7"
},
{
"bytes": 3175,
"path": "phase4d.yaml",
"sha256": "e94301362e630e59fa248f883ae09d369dce720bce970d0d2f47ef6a6ed36a2a"
}
]
}
+81
View File
@@ -0,0 +1,81 @@
{
"activity_stream_version": "1.0.0",
"api_methods": [
"reset",
"step",
"read_spikes",
"read_population",
"read_output",
"get_state_summary",
"checkpoint_state",
"restore_state"
],
"api_tests": {
"all_passed": true,
"checkpoint_restore_spikes_exact": true,
"checkpoint_restore_voltage_exact": true,
"continuous_step_does_not_reset_clock": true,
"fixed_temporal_bins_tested": {
"1": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
0
],
"10": [
0,
1,
0
],
"20": [
1,
0
],
"5": [
0,
0,
0,
1,
0
]
},
"per_class_aggregation_labels": 1,
"per_neuron_readout_shape": 1316,
"per_type_aggregation_labels": 481,
"readout_modes_passed": true,
"reset_same_seed_continuous_chunk_spikes_exact": true,
"single_vs_chunk_voltage_max_abs_error": 0.0,
"timestamps_present": true
},
"api_version": "1.0.0",
"continuous_gpu_state": true,
"game_agnostic": true,
"input_batch_version": "1.0.0",
"name": "MaleCNS-BrainIO-v1",
"prohibitions": {
"connectome_or_sign_changes": false,
"controller_buttons": false,
"rom_access": false,
"stable_retro": false,
"training": false,
"web_server": false
},
"runtime_dependency": "MaleCNS-LIF-runtime-v1"
}
+415
View File
@@ -0,0 +1,415 @@
#!/usr/bin/env python3
"""Stable game-agnostic Brain I/O API for MaleCNS-LIF-runtime-v1."""
from __future__ import annotations
import copy
import json
import math
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Iterable
import numpy as np
import pyarrow.parquet as pq
import torch
import yaml
API_VERSION = "1.0.0"
INPUT_BATCH_VERSION = "1.0.0"
ACTIVITY_STREAM_VERSION = "1.0.0"
@dataclass(frozen=True)
class InputEvent:
target_neuron_idx: np.ndarray
stimulation_mode: str
value_kind: str
value: float | np.ndarray
start_ms: float
duration_ms: float
source_id: str
channel_id: str
amplitude_mv: float = 0.0
metadata: dict[str, Any] = field(default_factory=dict)
def validate(self, neuron_count: int, dt_ms: float) -> None:
target = np.asarray(self.target_neuron_idx)
if target.ndim != 1 or target.dtype.kind not in "iu":
raise ValueError("target_neuron_idx must be a one-dimensional integer array")
if len(target) and (int(target.min()) < 0 or int(target.max()) >= neuron_count):
raise ValueError("target neuron_idx outside canonical range")
if len(np.unique(target)) != len(target):
raise ValueError("target neuron_idx values must be unique")
if self.stimulation_mode not in {"spike_injection", "external_synaptic_drive"}:
raise ValueError(f"unsupported stimulation_mode {self.stimulation_mode!r}")
if self.value_kind not in {"binary", "scalar_intensity", "poisson_rate_hz", "signed_modulation"}:
raise ValueError(f"unsupported value_kind {self.value_kind!r}")
if self.start_ms < 0 or self.duration_ms <= 0:
raise ValueError("timestamps/duration must be non-negative/positive")
for x in (self.start_ms, self.duration_ms):
if not math.isclose(x / dt_ms, round(x / dt_ms), abs_tol=1e-8):
raise ValueError(f"{x} ms is not aligned to dt={dt_ms} ms")
value = np.asarray(self.value)
if value.ndim > 1 or (value.ndim == 1 and len(value) not in (1, len(target))):
raise ValueError("value must be scalar or match target count")
if self.value_kind == "poisson_rate_hz" and np.any(value < 0):
raise ValueError("Poisson rate cannot be negative")
@dataclass(frozen=True)
class InputBatch:
events: tuple[InputEvent, ...]
batch_id: str
version: str = INPUT_BATCH_VERSION
metadata: dict[str, Any] = field(default_factory=dict)
def validate(self, neuron_count: int, dt_ms: float) -> None:
if self.version != INPUT_BATCH_VERSION:
raise ValueError(f"InputBatch version must be {INPUT_BATCH_VERSION}")
for event in self.events:
event.validate(neuron_count, dt_ms)
class VisualAdapterV1:
"""Maps abstract channels to audited visual hierarchy populations."""
def __init__(self, config: dict[str, Any], phase4c_root: Path):
self.config = config
self.phase4c_root = phase4c_root
self.modes: dict[str, np.ndarray] = {}
for mode, spec in config["visual_adapter"]["modes"].items():
population = spec["hierarchy_population"]
path = phase4c_root / "visual-audit" / f"hierarchy-{population}.parquet"
if not path.is_file():
raise FileNotFoundError(path)
self.modes[mode] = pq.read_table(path, columns=["neuron_idx"])["neuron_idx"].to_numpy().astype(np.int64)
def population(self, mode: str) -> np.ndarray:
if mode not in self.modes:
raise KeyError(mode)
return self.modes[mode].copy()
def encode(
self, mode: str, channel_values: np.ndarray, start_ms: float, duration_ms: float,
source_id: str, channel_id: str, value_kind: str = "poisson_rate_hz",
) -> InputBatch:
if mode not in self.modes:
raise KeyError(mode)
spec = self.config["visual_adapter"]["modes"][mode]
targets = self.modes[mode]
values = np.asarray(channel_values, dtype=np.float32)
if values.shape != (len(targets),):
raise ValueError(f"visual channel must have shape {(len(targets),)}, got {values.shape}")
selected = np.flatnonzero(values != 0).astype(np.int64)
target = targets[selected]
selected_values = values[selected]
event = InputEvent(
target_neuron_idx=target,
stimulation_mode=spec["input_mode"],
value_kind=value_kind,
value=selected_values,
amplitude_mv=float(self.config["visual_adapter"]["external_drive_mv"]),
start_ms=start_ms,
duration_ms=duration_ms,
source_id=source_id,
channel_id=channel_id,
metadata={
"adapter": self.config["names"]["visual_adapter"],
"adapter_mode": mode,
"biological_fidelity": spec["biological_fidelity"],
"connectome_weights_modified": False,
},
)
return InputBatch(events=(event,), batch_id=f"{source_id}:{channel_id}:{start_ms}", metadata={"adapter_mode": mode})
class BrainRuntime:
"""Persistent exact-state BrainRuntime API. It has no game-specific dependency."""
def __init__(self, config_path: Path, extension_build_dir: Path):
self.config_path = config_path.resolve()
self.cfg = yaml.safe_load(self.config_path.read_text(encoding="utf-8"))
self.dt = float(self.cfg["model"]["dt_ms"])
self.n = 166700
self.model_root = Path(self.cfg["paths"]["model_root"])
self.runtime_root = Path(self.cfg["paths"]["runtime_root"])
self.input_root = Path(self.cfg["paths"]["connectome_root"])
runtime_module_dir = str(self.runtime_root)
if runtime_module_dir not in sys.path:
sys.path.insert(0, runtime_module_dir)
import phase4c as runtime_module
self.runtime_module = runtime_module
p4cfg = yaml.safe_load((self.runtime_root / "phase4c.yaml").read_text(encoding="utf-8"))
self.paths = runtime_module.exact_paths(p4cfg)
self.indptr_np, self.indices_np, self.raw_np = runtime_module.p4b.validate_graph(p4cfg, self.paths)
self.table = runtime_module.full_metadata(self.paths["metadata"])
self.population_masks = runtime_module.populations(self.table)
if not torch.cuda.is_available() or torch.cuda.device_count() != 1:
raise RuntimeError("BrainRuntime requires exactly one visible CUDA GPU")
self.device = torch.device("cuda:0")
props = torch.cuda.get_device_properties(self.device)
if self.cfg["device"]["required_name_substring"] not in props.name:
raise RuntimeError(f"required RTX 4090, got {props.name}")
extension_build_dir.mkdir(parents=True, exist_ok=True)
build_experiment_root = extension_build_dir.parent.parent
(build_experiment_root / "compiler-reports/extension-build").mkdir(parents=True, exist_ok=True)
self.extension, report = runtime_module.build_extension(self.runtime_root, build_experiment_root)
if self.extension is None:
raise RuntimeError(f"runtime-v1 extension unavailable: {report}")
base = runtime_module.Runtime(p4cfg, self.table, self.indptr_np, self.indices_np, self.raw_np, self.device, self.extension)
self.indptr, self.indices, self.weights = base.indptr, base.indices, base.weights
self.signs, self.superclass = base.signs, base.superclass
self.neuron_type = np.asarray(self.table["annotation_type"].to_pylist(), dtype=object)
self.neuron_class = np.asarray(self.table["annotation_class"].to_pylist(), dtype=object)
self.body_id = self.table["body_id"].to_numpy().astype(np.uint64)
m = self.cfg["model"]
self.delay_steps = int(round(float(m["synaptic_delay_ms"]) / self.dt))
self.queue_len = self.delay_steps + 1
self.refractory_steps = int(round(float(m["refractory_ms"]) / self.dt))
self.alpha = self.dt / float(m["membrane_tau_ms"])
self.g_decay = 1.0 - self.dt / float(m["synaptic_tau_ms"])
self.rest = float(m["resting_potential_mv"])
self.reset_value = float(m["reset_potential_mv"])
self.threshold = float(m["threshold_mv"])
self.output_populations = self._output_populations()
self.reset(0)
def _output_populations(self) -> dict[str, np.ndarray]:
s = self.superclass
return {
"descending": np.flatnonzero(np.isin(s, ["descending_neuron", "descending_neuron_tbc"])).astype(np.int32),
"motor_related": np.flatnonzero(np.isin(s, ["vnc_motor", "cb_motor"])).astype(np.int32),
"ascending": np.flatnonzero(np.isin(s, ["ascending_neuron", "sensory_ascending", "sensory_ascending_tbc", "efferent_ascending"])).astype(np.int32),
}
def reset(self, seed: int, initial_state: dict[str, Any] | None = None) -> dict[str, Any]:
self.seed = int(seed)
self.generator = torch.Generator(device=self.device); self.generator.manual_seed(self.seed)
self.v = torch.full((self.n,), self.rest, dtype=torch.float32, device=self.device)
self.g = torch.zeros(self.n, dtype=torch.float32, device=self.device)
self.refractory = torch.zeros(self.n, dtype=torch.int32, device=self.device)
self.delay_queue = torch.zeros((self.queue_len, self.n), dtype=torch.float32, device=self.device)
self.spike_counts = torch.zeros(self.n, dtype=torch.int32, device=self.device)
self.simulation_step = 0; self.sequence_id = 0
self.spike_steps: list[np.ndarray] = []; self.spike_nodes: list[np.ndarray] = []
self.spike_origins: list[np.ndarray] = []
self.drive_steps: list[np.ndarray] = []; self.drive_nodes: list[np.ndarray] = []
self.drive_values: list[np.ndarray] = []; self.input_audit: list[dict[str, Any]] = []
if initial_state is not None:
self.restore_state(initial_state)
return self.get_state_summary()
@property
def simulation_time_ms(self) -> float:
return self.simulation_step * self.dt
def _event_values(self, event: InputEvent, targets: torch.Tensor) -> torch.Tensor:
value = np.asarray(event.value, dtype=np.float32)
if value.ndim == 0:
return torch.full((len(targets),), float(value), dtype=torch.float32, device=self.device)
return torch.as_tensor(value, dtype=torch.float32, device=self.device)
def _active(self, event: InputEvent, time_ms: float) -> bool:
return event.start_ms - 1e-9 <= time_ms < event.start_ms + event.duration_ms - 1e-9
def _hybrid_edges(self, sources: torch.Tensor) -> torch.Tensor:
starts = self.indptr[sources]
degrees = self.indptr[sources + 1] - starts
cumulative = torch.cumsum(degrees, 0)
total = int(cumulative[-1].item()) if cumulative.numel() else 0
output = torch.empty(total, dtype=torch.int64, device=self.device)
if total:
self.extension.fill_edges(self.indptr, sources, cumulative - degrees, output)
return output
def step(self, input_batch: InputBatch | None, biological_ms: float) -> dict[str, Any]:
steps = int(round(float(biological_ms) / self.dt))
if not math.isclose(steps * self.dt, float(biological_ms), abs_tol=1e-8) or steps <= 0:
raise ValueError("biological_ms must be a positive multiple of dt")
batch = input_batch or InputBatch(events=(), batch_id="zero-input")
batch.validate(self.n, self.dt)
batch_start = self.simulation_time_ms
prepared = [(e, torch.as_tensor(e.target_neuron_idx, dtype=torch.int64, device=self.device)) for e in batch.events]
for event in batch.events:
self.input_audit.append({
"batch_id": batch.batch_id, "source_id": event.source_id, "channel_id": event.channel_id,
"mode": event.stimulation_mode, "value_kind": event.value_kind,
"start_ms": event.start_ms, "duration_ms": event.duration_ms,
"target_count": int(len(event.target_neuron_idx)), "metadata": event.metadata,
"external_drive_is_connectome_transmission": False,
})
start_event_count = sum(len(x) for x in self.spike_nodes)
start_drive_count = sum(len(x) for x in self.drive_nodes)
for _ in range(steps):
global_step = self.simulation_step; time_ms = self.simulation_time_ms
slot = global_step % self.queue_len
self.g.mul_(self.g_decay).add_(self.delay_queue[slot]); self.delay_queue[slot].zero_()
spike_requests: list[tuple[InputEvent, torch.Tensor, torch.Tensor]] = []
for event, targets in prepared:
if not self._active(event, time_ms) or targets.numel() == 0:
continue
values = self._event_values(event, targets)
if event.value_kind == "poisson_rate_hz":
selected = torch.rand(len(targets), generator=self.generator, device=self.device) < (values * self.dt / 1000.0)
elif event.value_kind == "binary":
selected = values != 0
elif event.value_kind == "scalar_intensity":
selected = values != 0
else: # signed_modulation
selected = torch.rand(len(targets), generator=self.generator, device=self.device) < (torch.abs(values) * self.dt / 1000.0)
if event.stimulation_mode == "external_synaptic_drive":
chosen = targets[selected]
if chosen.numel():
signed_value = values[selected] * float(event.amplitude_mv)
if event.value_kind == "poisson_rate_hz":
signed_value = torch.full_like(signed_value, float(event.amplitude_mv))
self.g.index_add_(0, chosen, signed_value)
node_np = chosen.cpu().numpy().astype(np.int32, copy=True)
val_np = signed_value.cpu().numpy().astype(np.float32, copy=True)
self.drive_steps.append(np.full(len(node_np), global_step, dtype=np.int32))
self.drive_nodes.append(node_np); self.drive_values.append(val_np)
else:
spike_requests.append((event, targets, selected))
self.refractory.clamp_min_(1).sub_(1)
available = self.refractory == 0
self.v[available] += self.alpha * (self.g[available] - (self.v[available] - self.rest))
self.v[~available] = self.reset_value
natural = available & (self.v >= self.threshold)
spikes = natural.clone()
injected = torch.zeros(self.n, dtype=torch.bool, device=self.device)
for _event, targets, selected in spike_requests:
accepted = selected & available[targets]
chosen = targets[accepted]
if chosen.numel():
spikes[chosen] = True; injected[chosen] = ~natural[chosen]
sources = torch.nonzero(spikes, as_tuple=False).flatten()
self.spike_counts.add_(spikes.to(torch.int32))
if sources.numel():
self.v[sources] = self.reset_value; self.refractory[sources] = self.refractory_steps
edge_ids = self._hybrid_edges(sources)
if edge_ids.numel():
self.delay_queue[(global_step + self.delay_steps) % self.queue_len].scatter_add_(0, self.indices[edge_ids].long(), self.weights[edge_ids])
src_np = sources.cpu().numpy().astype(np.int32, copy=True)
origin_np = np.where(injected[sources].cpu().numpy(), "external_spike_injection", "network_spike")
self.spike_steps.append(np.full(len(src_np), global_step, dtype=np.int32))
self.spike_nodes.append(src_np); self.spike_origins.append(origin_np)
self.simulation_step += 1
torch.cuda.synchronize()
self.sequence_id += 1
return {
"api_version": API_VERSION, "sequence_id": self.sequence_id,
"batch_id": batch.batch_id, "start_time_ms": batch_start,
"end_time_ms": self.simulation_time_ms, "biological_ms": biological_ms,
"new_spikes": sum(len(x) for x in self.spike_nodes) - start_event_count,
"new_external_drive_events": sum(len(x) for x in self.drive_nodes) - start_drive_count,
}
def _events(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
steps = np.concatenate(self.spike_steps) if self.spike_steps else np.empty(0, np.int32)
nodes = np.concatenate(self.spike_nodes) if self.spike_nodes else np.empty(0, np.int32)
origins = np.concatenate(self.spike_origins) if self.spike_origins else np.empty(0, object)
return steps, nodes, origins
def read_spikes(self, window_ms: float | None = None) -> dict[str, Any]:
steps, nodes, origins = self._events()
if window_ms is not None:
threshold = self.simulation_step - int(round(window_ms / self.dt))
mask = steps >= threshold; steps, nodes, origins = steps[mask], nodes[mask], origins[mask]
return {
"simulation_time_ms": self.simulation_time_ms,
"step": steps, "timestamp_ms": steps.astype(np.float64) * self.dt,
"neuron_idx": nodes, "origin": origins,
}
def read_population(self, population_id: str, window_ms: float) -> dict[str, Any]:
if population_id not in self.output_populations:
raise KeyError(population_id)
members = self.output_populations[population_id]
events = self.read_spikes(window_ms)
mask = np.isin(events["neuron_idx"], members)
nodes = events["neuron_idx"][mask]
counts = np.zeros(len(members), dtype=np.int32)
if len(nodes):
lookup = np.full(self.n, -1, dtype=np.int32); lookup[members] = np.arange(len(members), dtype=np.int32)
np.add.at(counts, lookup[nodes], 1)
seconds = window_ms / 1000.0
return {
"simulation_time_ms": self.simulation_time_ms, "population_id": population_id,
"neuron_idx": members.copy(), "spike_counts": counts,
"firing_rate_hz": counts.astype(np.float32) / seconds,
"active_mask": counts > 0, "aggregate_rate_hz": float(counts.sum() / (len(members) * seconds)),
"total_spikes": int(counts.sum()), "active_neurons": int(np.count_nonzero(counts)),
}
def read_output(self, output_spec: dict[str, Any], window_ms: float) -> dict[str, Any]:
population = output_spec.get("population", "descending")
aggregation = output_spec.get("aggregation", "per-neuron")
base = self.read_population(population, window_ms)
members = base["neuron_idx"]; counts = base["spike_counts"]
if aggregation == "per-neuron":
return base
if aggregation not in {"per-type", "per-class"}:
raise ValueError("aggregation must be per-neuron, per-type, or per-class")
labels = self.neuron_type if aggregation == "per-type" else self.neuron_class
result: dict[str, dict[str, float | int]] = {}
for label in sorted({"<null>" if labels[i] is None else str(labels[i]) for i in members}):
mask = np.asarray([("<null>" if labels[i] is None else str(labels[i])) == label for i in members])
result[label] = {"neuron_count": int(mask.sum()), "spike_count": int(counts[mask].sum()), "mean_rate_hz": float(base["firing_rate_hz"][mask].mean())}
return {"simulation_time_ms": self.simulation_time_ms, "population_id": population, "aggregation": aggregation, "features": result}
def fixed_temporal_bins(self, population_id: str, bin_ms: float, window_ms: float) -> dict[str, Any]:
if bin_ms not in self.cfg["readout"]["bins_ms"]:
raise ValueError("unsupported fixed bin")
ev = self.read_spikes(window_ms); members = self.output_populations[population_id]
mask = np.isin(ev["neuron_idx"], members); times = ev["timestamp_ms"][mask]
start = self.simulation_time_ms - window_ms
edges = np.arange(start, self.simulation_time_ms + bin_ms + 1e-9, bin_ms)
counts, _ = np.histogram(times, bins=edges)
return {"simulation_time_ms": self.simulation_time_ms, "population_id": population_id, "bin_ms": bin_ms, "bin_start_ms": edges[:-1], "spike_counts": counts.astype(np.int32)}
def get_state_summary(self) -> dict[str, Any]:
return {
"api_version": API_VERSION, "runtime": self.cfg["names"]["runtime"],
"simulation_step": self.simulation_step, "simulation_time_ms": self.simulation_time_ms,
"sequence_id": self.sequence_id, "seed": self.seed,
"neurons": self.n, "edges": int(self.indices.numel()),
"active_neurons_total": int(torch.count_nonzero(self.spike_counts).item()),
"spikes_total": int(self.spike_counts.sum().item()),
"external_drive_events_total": sum(len(x) for x in self.drive_nodes),
}
def checkpoint_state(self) -> dict[str, Any]:
return {
"api_version": API_VERSION, "seed": self.seed, "simulation_step": self.simulation_step,
"sequence_id": self.sequence_id, "generator_state": self.generator.get_state().cpu(),
"v": self.v.clone(), "g": self.g.clone(), "refractory": self.refractory.clone(),
"delay_queue": self.delay_queue.clone(), "spike_counts": self.spike_counts.clone(),
"spike_steps": copy.deepcopy(self.spike_steps), "spike_nodes": copy.deepcopy(self.spike_nodes),
"spike_origins": copy.deepcopy(self.spike_origins), "drive_steps": copy.deepcopy(self.drive_steps),
"drive_nodes": copy.deepcopy(self.drive_nodes), "drive_values": copy.deepcopy(self.drive_values),
"input_audit": copy.deepcopy(self.input_audit),
}
def restore_state(self, state: dict[str, Any]) -> dict[str, Any]:
if state.get("api_version") != API_VERSION:
raise ValueError("checkpoint API version mismatch")
self.seed = int(state["seed"]); self.simulation_step = int(state["simulation_step"]); self.sequence_id = int(state["sequence_id"])
self.generator.set_state(state["generator_state"].cpu())
for name in ("v", "g", "refractory", "delay_queue", "spike_counts"):
setattr(self, name, state[name].clone().to(self.device))
for name in ("spike_steps", "spike_nodes", "spike_origins", "drive_steps", "drive_nodes", "drive_values", "input_audit"):
setattr(self, name, copy.deepcopy(state[name]))
return self.get_state_summary()
def activity_packet(self, window_ms: float, input_metadata: dict[str, Any] | None = None) -> dict[str, Any]:
ev = self.read_spikes(window_ms)
aggregates = {name: {k: v for k, v in self.read_population(name, window_ms).items() if k in ("active_neurons", "total_spikes", "aggregate_rate_hz")} for name in self.output_populations}
return {
"contract_version": ACTIVITY_STREAM_VERSION, "simulation_time_ms": self.simulation_time_ms,
"sequence_id": self.sequence_id, "window_ms": window_ms,
"spike_neuron_idx": ev["neuron_idx"].tolist(), "spike_timestamp_ms": ev["timestamp_ms"].tolist(),
"population_aggregates": aggregates, "active_neuron_count": int(len(np.unique(ev["neuron_idx"]))),
"input_channel_metadata": input_metadata or {}, "representation": "event-only",
}
@@ -0,0 +1,5 @@
2ad17a93bf22339db56ae3869af9a2a7c26ca7359d8923c693d71dde63927812 VERSION.json
1c4e3e6f06708bfec30b24e92297f320b2b8b87ebbbe6da501f5f2db4f74762a brain_runtime.py
9ed8e4246252bdfc590c88e2993b7e105f278c6c4943e1c688f387c1cf54aeb7 phase4d.py
e94301362e630e59fa248f883ae09d369dce720bce970d0d2f47ef6a6ed36a2a phase4d.yaml
4eb6390bbfcea3d03c70215cc2492ae233d90ea0d08f5fb3376d2cdcf92a7f52 MANIFEST.json
+535
View File
@@ -0,0 +1,535 @@
#!/usr/bin/env python3
"""Phase 4D orchestration: stable Brain I/O API, timing, readout and regression."""
from __future__ import annotations
import argparse
import hashlib
import json
import math
import os
import platform
import shutil
import time
from collections import Counter
from pathlib import Path
from typing import Any
import numpy as np
import pyarrow as pa
import pyarrow.parquet as pq
import torch
import yaml
from brain_runtime import (
ACTIVITY_STREAM_VERSION, API_VERSION, INPUT_BATCH_VERSION,
BrainRuntime, InputBatch, InputEvent, VisualAdapterV1,
)
def sha256(path: Path, chunk: int = 8 << 20) -> str:
h = hashlib.sha256()
with path.open("rb") as f:
while data := f.read(chunk): h.update(data)
return h.hexdigest()
def write_json(path: Path, value: Any) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="utf-8")
def np_json(value: Any) -> Any:
if isinstance(value, np.ndarray): return value.tolist()
if isinstance(value, np.generic): return value.item()
raise TypeError(type(value).__name__)
def metadata_populations(runtime: BrainRuntime) -> dict[str, np.ndarray]:
return {k: v.copy() for k, v in runtime.output_populations.items()}
def freeze_populations(exp: Path, runtime: BrainRuntime) -> dict[str, Any]:
expressions = {
"descending": "annotation_superclass in {'descending_neuron','descending_neuron_tbc'}",
"motor_related": "annotation_superclass in {'vnc_motor','cb_motor'}",
"ascending": "annotation_superclass in {'ascending_neuron','sensory_ascending','sensory_ascending_tbc','efferent_ascending'}",
}
nt = runtime.table["neurotransmitter"].to_pylist()
result: dict[str, Any] = {}
for name, nodes in metadata_populations(runtime).items():
table = pa.table({
"neuron_idx": pa.array(nodes, type=pa.int32()),
"body_id": pa.array(runtime.body_id[nodes], type=pa.uint64()),
"superclass": pa.array([runtime.superclass[i] for i in nodes], type=pa.string()),
"type": pa.array([runtime.neuron_type[i] for i in nodes], type=pa.string()),
"class": pa.array([runtime.neuron_class[i] for i in nodes], type=pa.string()),
"neurotransmitter": pa.array([nt[i] for i in nodes], type=pa.string()),
})
pq.write_table(table, exp / "populations" / f"{name}.parquet", compression="zstd")
nts = Counter("<null>" if nt[i] is None else str(nt[i]) for i in nodes)
result[name] = {
"selection_expression": expressions[name], "count": int(len(nodes)),
"neurotransmitter_statistics": dict(sorted(nts.items())),
"membership_file": str(exp / "populations" / f"{name}.parquet"),
}
write_json(exp / "populations/summary.json", result)
return result
def audit_visual_sides(exp: Path, runtime: BrainRuntime, adapter: VisualAdapterV1) -> dict[str, Any]:
values = runtime.table["annotation_rootSide"].to_pylist() if "annotation_rootSide" in runtime.table.column_names else [None] * runtime.n
result: dict[str, Any] = {}
for mode in ("engineering-first-order", "engineering-second-order"):
nodes = adapter.population(mode); c = Counter("<null>" if values[i] is None else str(values[i]) for i in nodes)
coverage = 1.0 - c.get("<null>", 0) / len(nodes)
reliable = coverage >= 0.90 and c.get("L", 0) > 0 and c.get("R", 0) > 0
result[mode] = {"field": "annotation_rootSide", "counts": dict(c), "non_null_coverage": coverage, "left_right_patterns_enabled": reliable}
write_json(exp / "visual-adapter/left-right-audit.json", result)
return result
def freeze_contracts(exp: Path, cfg: dict[str, Any], runtime: BrainRuntime, adapter: VisualAdapterV1) -> dict[str, Any]:
input_schema = {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "MaleCNS InputBatch",
"version": INPUT_BATCH_VERSION,
"type": "object",
"required": ["version", "batch_id", "events"],
"properties": {
"version": {"const": INPUT_BATCH_VERSION},
"batch_id": {"type": "string"},
"events": {
"type": "array",
"items": {
"type": "object",
"required": ["target_neuron_idx", "stimulation_mode", "value_kind", "value", "start_ms", "duration_ms", "source_id", "channel_id"],
"properties": {
"target_neuron_idx": {"type": "array", "items": {"type": "integer", "minimum": 0, "maximum": 166699}},
"stimulation_mode": {"enum": ["spike_injection", "external_synaptic_drive"]},
"value_kind": {"enum": ["binary", "scalar_intensity", "poisson_rate_hz", "signed_modulation"]},
"value": {"oneOf": [{"type": "number"}, {"type": "array", "items": {"type": "number"}}]},
"start_ms": {"type": "number", "minimum": 0},
"duration_ms": {"type": "number", "exclusiveMinimum": 0},
"source_id": {"type": "string"},
"channel_id": {"type": "string"},
"amplitude_mv": {"type": "number"},
},
},
},
},
"timestamp_unit": "biological ms",
"external_drive_is_connectome_transmission": False,
}
write_json(exp / "configs/input-batch.schema.json", input_schema)
mode_records: dict[str, Any] = {}
for mode, nodes in adapter.modes.items():
path = exp / "visual-adapter" / f"{mode}.parquet"
pq.write_table(pa.table({"neuron_idx": pa.array(nodes.astype(np.int32), type=pa.int32()), "body_id": pa.array(runtime.body_id[nodes], type=pa.uint64())}), path, compression="zstd")
spec = cfg["visual_adapter"]["modes"][mode]
mode_records[mode] = {"count": int(len(nodes)), "membership_file": str(path), **spec}
write_json(exp / "visual-adapter/membership-summary.json", mode_records)
readout = {
"api_version": API_VERSION, "methods": ["read_spikes", "read_population", "read_output", "fixed_temporal_bins"],
"features": ["spike count vector", "firing-rate vector", "active mask", "population aggregate rate", "excitatory/inhibitory activity", "descending feature vector", "motor-related feature vector"],
"aggregations": ["per-neuron", "per-type", "per-class"], "fixed_bins_ms": cfg["readout"]["bins_ms"],
"action_decoder": False,
}
write_json(exp / "readout/readout-contract.json", readout)
return {"input_batch_schema": str(exp / "configs/input-batch.schema.json"), "visual_modes": mode_records, "readout_contract": readout}
def patterns(nodes: np.ndarray, cfg: dict[str, Any]) -> dict[str, np.ndarray]:
rng = np.random.default_rng(int(cfg["synthetic_patterns"]["deterministic_seed"]))
order = rng.permutation(len(nodes))
sparse_n = max(1, int(round(len(nodes) * float(cfg["synthetic_patterns"]["sparse_fraction"]))))
dense_n = max(1, int(round(len(nodes) * float(cfg["synthetic_patterns"]["dense_fraction"]))))
moving_n = max(1, int(round(len(nodes) * float(cfg["synthetic_patterns"]["moving_fraction"]))))
def vector(selection: np.ndarray, rate: float) -> np.ndarray:
out = np.zeros(len(nodes), dtype=np.float32); out[selection] = rate; return out
rate = float(cfg["visual_adapter"]["synthetic_rate_hz"])
return {
"sparse-A": vector(order[:sparse_n], rate),
"sparse-B": vector(order[sparse_n:2*sparse_n], rate),
"dense": vector(order[:dense_n], rate),
"moving": vector(order[2*sparse_n:2*sparse_n+moving_n], rate),
"zero": np.zeros(len(nodes), dtype=np.float32),
}
def direct_batch(nodes: np.ndarray, mode: str, rate_hz: float, start_ms: float, duration_ms: float, source: str) -> InputBatch:
return InputBatch(events=(InputEvent(
target_neuron_idx=nodes.astype(np.int64), stimulation_mode="spike_injection",
value_kind="poisson_rate_hz", value=float(rate_hz), start_ms=start_ms,
duration_ms=duration_ms, source_id=source, channel_id=mode,
metadata={"biological_fidelity": "reference_population", "external_drive_is_connectome_transmission": False},
),), batch_id=f"{source}:{mode}:{start_ms}")
def feature(runtime: BrainRuntime, window: float) -> np.ndarray:
d = runtime.read_population("descending", window)["spike_counts"]
m = runtime.read_population("motor_related", window)["spike_counts"]
return np.concatenate([d, m]).astype(np.float32)
def output_stats(runtime: BrainRuntime, window: float) -> dict[str, Any]:
d = runtime.read_population("descending", window); m = runtime.read_population("motor_related", window)
return {
"descending_active": d["active_neurons"], "descending_spikes": d["total_spikes"],
"motor_active": m["active_neurons"], "motor_spikes": m["total_spikes"],
"descending_rate_hz": d["aggregate_rate_hz"], "motor_rate_hz": m["aggregate_rate_hz"],
}
def population_distances(runtime: BrainRuntime, sources: np.ndarray) -> np.ndarray:
return runtime.runtime_module.p4b.directed_distances(runtime.indptr_np, runtime.indices_np, sources.astype(np.int64), runtime.n)
def propagation_depth(runtime: BrainRuntime, distance: np.ndarray) -> int:
active = runtime.spike_counts.cpu().numpy() > 0
values = distance[active & (distance >= 0)]
return int(values.max(initial=0))
def make_source_batch(cfg: dict[str, Any], runtime: BrainRuntime, adapter: VisualAdapterV1,
source: str, start: float, duration: float, pattern_cache: dict[str, dict[str, np.ndarray]]) -> tuple[InputBatch, np.ndarray]:
if source.startswith("engineering-"):
mode = source
p = pattern_cache[mode]["sparse-A"]
batch = adapter.encode(mode, p, start, duration, "phase4d-window", "sparse-A")
targets = adapter.population(mode)[p != 0]
return batch, targets
mask = runtime.population_masks[source]
nodes = np.flatnonzero(mask).astype(np.int64)
rate = 10.0 if source == "auditory" else 50.0
return direct_batch(nodes, source, rate, start, duration, "phase4d-window"), nodes
def window_experiments(exp: Path, cfg: dict[str, Any], runtime: BrainRuntime,
adapter: VisualAdapterV1, pattern_cache: dict[str, dict[str, np.ndarray]]) -> dict[str, Any]:
records: list[dict[str, Any]] = []; distances: dict[str, np.ndarray] = {}
for source in cfg["window_experiment"]["sources"]:
_, targets = make_source_batch(cfg, runtime, adapter, source, 0.0, 5.0, pattern_cache)
distances[source] = population_distances(runtime, targets)
for window in cfg["window_experiment"]["windows_ms"]:
for seed in cfg["window_experiment"]["seeds"]:
outcomes = []
for repeat in range(2):
runtime.reset(int(seed)); batch, _ = make_source_batch(cfg, runtime, adapter, source, 0.0, float(window), pattern_cache)
torch.cuda.synchronize(); started = time.perf_counter(); step_result = runtime.step(batch, float(window)); torch.cuda.synchronize(); wall = time.perf_counter() - started
stats = output_stats(runtime, float(window)); f = feature(runtime, float(window))
outcomes.append((runtime.read_spikes(), runtime.v.cpu().numpy().copy(), runtime.g.cpu().numpy().copy(), f, stats, step_result, wall))
a, b = outcomes
deterministic = np.array_equal(a[0]["step"], b[0]["step"]) and np.array_equal(a[0]["neuron_idx"], b[0]["neuron_idx"]) and np.array_equal(a[3], b[3])
input_count = int(a[5]["new_spikes"] if source in ("auditory", "olfactory") else a[5]["new_external_drive_events"])
rec = {
"source": source, "window_ms": float(window), "seed": int(seed),
"wall_latency_seconds": a[6], "max_theoretical_control_hz": 1.0 / a[6],
"realtime_factor_wall_over_biological": a[6] / (float(window) / 1000.0),
"input_spikes_or_drive_events": input_count,
"total_spikes": int(len(a[0]["neuron_idx"])),
"propagation_depth": propagation_depth(runtime, distances[source]),
**a[4], "feature_l2_norm": float(np.linalg.norm(a[3])),
"repeat_spike_events_exact": deterministic,
"repeat_feature_exact": bool(np.array_equal(a[3], b[3])),
"repeat_final_voltage_max_abs_error": float(np.max(np.abs(a[1] - b[1]))),
"repeat_final_g_max_abs_error": float(np.max(np.abs(a[2] - b[2]))),
}
records.append(rec)
write_json(exp / "latency/window-trials.json", records)
summary: dict[str, Any] = {"trial_count": len(records), "all_repeat_deterministic": all(x["repeat_spike_events_exact"] for x in records)}
for source in cfg["window_experiment"]["sources"]:
rows = [x for x in records if x["source"] == source]
summary[source] = {}
for window in cfg["window_experiment"]["windows_ms"]:
r = [x for x in rows if x["window_ms"] == float(window)]
summary[source][str(window)] = {
"wall_latency_seconds_mean": float(np.mean([x["wall_latency_seconds"] for x in r])),
"max_theoretical_control_hz_mean": float(np.mean([x["max_theoretical_control_hz"] for x in r])),
"descending_active_mean": float(np.mean([x["descending_active"] for x in r])),
"motor_active_mean": float(np.mean([x["motor_active"] for x in r])),
"feature_l2_norm_mean": float(np.mean([x["feature_l2_norm"] for x in r])),
}
def shortest(key: str) -> float | None:
candidates = sorted({x["window_ms"] for x in records if x["source"].startswith("engineering-") and x[key] > 0})
return candidates[0] if candidates else None
summary["shortest_engineering_descending_signal_ms"] = shortest("descending_active")
summary["shortest_engineering_motor_signal_ms"] = shortest("motor_active")
write_json(exp / "latency/summary.json", summary)
return {"records": records, "summary": summary}
def continuous_control(exp: Path, cfg: dict[str, Any], runtime: BrainRuntime,
adapter: VisualAdapterV1, pattern_cache: dict[str, dict[str, np.ndarray]]) -> dict[str, Any]:
output: dict[str, Any] = {}
duration = float(cfg["continuous_control"]["biological_duration_ms"])
sequence = cfg["continuous_control"]["sequence"]
mode = "engineering-first-order"
for period in cfg["continuous_control"]["periods_ms"]:
period = float(period); runtime.reset(int(cfg["continuous_control"]["seed"]))
intervals: list[dict[str, Any]] = []; previous = None
total_wall = 0.0
for k, start in enumerate(np.arange(0.0, duration, period)):
pattern_name = sequence[k % len(sequence)]
values = pattern_cache[mode][pattern_name]
batch = adapter.encode(mode, values, float(start), period, "continuous-control", pattern_name)
torch.cuda.synchronize(); t0 = time.perf_counter(); step_result = runtime.step(batch, period); torch.cuda.synchronize(); wall = time.perf_counter() - t0; total_wall += wall
f = feature(runtime, period); stats = output_stats(runtime, period)
recent = runtime.read_spikes(period); adapter_nodes = adapter.population(mode)
target_spikes = int(np.count_nonzero(np.isin(recent["neuron_idx"], adapter_nodes)))
latency = None
ev = runtime.read_spikes(period)
output_members = np.concatenate([runtime.output_populations["descending"], runtime.output_populations["motor_related"]])
omask = np.isin(ev["neuron_idx"], output_members)
if np.any(omask): latency = float(ev["timestamp_ms"][omask].min() - start)
intervals.append({
"interval": k, "start_ms": float(start), "end_ms": float(start + period), "pattern": pattern_name,
"wall_seconds": wall, "response_latency_ms": latency, **stats,
"feature_norm": float(np.linalg.norm(f)),
"feature_change_l2": None if previous is None else float(np.linalg.norm(f - previous)),
"input_spikes": step_result["new_spikes"], "drive_events": step_result["new_external_drive_events"],
"adapter_target_spikes": target_spikes,
})
previous = f
zero = [x for x in intervals if x["pattern"] == "zero"]
change_latencies = [x["response_latency_ms"] for x in intervals if x["response_latency_ms"] is not None]
output[str(int(period))] = {
"period_ms": period, "continuous_state_without_reset": True, "interval_count": len(intervals),
"wall_seconds": total_wall, "biological_ms": duration,
"median_response_latency_ms": float(np.median(change_latencies)) if change_latencies else None,
"max_response_latency_ms": float(np.max(change_latencies)) if change_latencies else None,
"zero_input_descending_spikes": int(sum(x["descending_spikes"] for x in zero)),
"zero_input_motor_spikes": int(sum(x["motor_spikes"] for x in zero)),
"zero_input_adapter_target_spikes": int(sum(x["adapter_target_spikes"] for x in zero)),
"zero_input_intervals_with_activity": int(sum((x["descending_spikes"] + x["motor_spikes"]) > 0 for x in zero)),
"intervals": intervals,
}
write_json(exp / "continuous-control" / f"period-{int(period)}ms.json", output[str(int(period))])
write_json(exp / "continuous-control/summary.json", {k: {x: v[x] for x in v if x != "intervals"} for k, v in output.items()})
return output
def metric_pair(a: np.ndarray, b: np.ndarray) -> dict[str, float]:
an, bn = float(np.linalg.norm(a)), float(np.linalg.norm(b))
cosine = float(np.dot(a, b) / (an * bn)) if an and bn else (1.0 if an == bn else 0.0)
if np.std(a) and np.std(b): corr = float(np.corrcoef(a, b)[0, 1])
else: corr = 1.0 if np.array_equal(a, b) else 0.0
aa, bb = a > 0, b > 0; union = np.count_nonzero(aa | bb)
return {"cosine_distance": 1.0 - cosine, "euclidean_distance": float(np.linalg.norm(a - b)), "correlation": corr, "active_population_overlap_jaccard": float(np.count_nonzero(aa & bb) / union) if union else 1.0}
def separability(exp: Path, cfg: dict[str, Any], runtime: BrainRuntime,
adapter: VisualAdapterV1, pattern_cache: dict[str, dict[str, np.ndarray]], side_audit: dict[str, Any]) -> dict[str, Any]:
mode = "engineering-first-order"; pats = dict(pattern_cache[mode])
pats.pop("zero")
if side_audit[mode]["left_right_patterns_enabled"]:
root_side = runtime.table["annotation_rootSide"].to_pylist(); nodes = adapter.population(mode); rate = float(cfg["visual_adapter"]["synthetic_rate_hz"])
pats["left"] = np.asarray([rate if root_side[i] == "L" else 0.0 for i in nodes], dtype=np.float32)
pats["right"] = np.asarray([rate if root_side[i] == "R" else 0.0 for i in nodes], dtype=np.float32)
features: dict[str, list[np.ndarray]] = {}
trial_rows: list[dict[str, Any]] = []
for name, values in pats.items():
features[name] = []
for seed in (44301, 44302, 44303):
runtime.reset(seed); batch = adapter.encode(mode, values, 0.0, 100.0, "separability", name)
runtime.step(batch, 100.0); f = feature(runtime, 100.0); features[name].append(f)
trial_rows.append({"pattern": name, "seed": seed, "active_channels": int(np.count_nonzero(values)), "feature_norm": float(np.linalg.norm(f)), **output_stats(runtime, 100.0)})
means = {k: np.mean(v, axis=0) for k, v in features.items()}
pairwise: dict[str, Any] = {}
names = sorted(means)
for i, a in enumerate(names):
for b in names[i+1:]: pairwise[f"{a}__{b}"] = metric_pair(means[a], means[b])
within = [float(np.linalg.norm(x - means[k])) for k, xs in features.items() for x in xs]
between = [v["euclidean_distance"] for v in pairwise.values()]
matrix = np.stack([x for k in names for x in features[k]])
centered = matrix - matrix.mean(axis=0, keepdims=True)
u, s, _ = np.linalg.svd(centered, full_matrices=False)
coords = u[:, :2] * s[:2]
labels = [k for k in names for _ in features[k]]
pca_table = pa.table({"pattern": labels, "pc1": coords[:, 0], "pc2": coords[:, 1]})
pq.write_table(pca_table, exp / "separability/pca-coordinates.parquet", compression="zstd")
result = {
"classifier_trained": False, "action_decoder_created": False,
"patterns": names, "trial_statistics": trial_rows, "pairwise": pairwise,
"within_condition_distance_mean": float(np.mean(within)),
"between_condition_distance_mean": float(np.mean(between)),
"between_to_within_ratio": float(np.mean(between) / np.mean(within)) if np.mean(within) else None,
"all_pairwise_mean_vectors_distinct": all(v["euclidean_distance"] > 0 for v in pairwise.values()),
"pca_usage": "offline visualization analysis only; not an action decoder",
}
write_json(exp / "separability/summary.json", result)
return result
def api_tests(exp: Path, runtime: BrainRuntime) -> dict[str, Any]:
nodes = np.flatnonzero(runtime.population_masks["auditory"]).astype(np.int64)
batch20 = direct_batch(nodes, "auditory", 10, 0.0, 20.0, "api-test")
runtime.reset(44400); runtime.step(batch20, 20.0); one = runtime.read_spikes(); one_v = runtime.v.cpu().numpy().copy()
runtime.reset(44400); runtime.step(batch20, 10.0); checkpoint = runtime.checkpoint_state(); runtime.step(batch20, 10.0); two = runtime.read_spikes(); two_v = runtime.v.cpu().numpy().copy()
runtime.restore_state(checkpoint); runtime.step(batch20, 10.0); replay = runtime.read_spikes(); replay_v = runtime.v.cpu().numpy().copy()
per_neuron = runtime.read_output({"population": "descending", "aggregation": "per-neuron"}, 20.0)
per_type = runtime.read_output({"population": "descending", "aggregation": "per-type"}, 20.0)
per_class = runtime.read_output({"population": "motor_related", "aggregation": "per-class"}, 20.0)
bins = {str(x): runtime.fixed_temporal_bins("descending", float(x), 20.0)["spike_counts"].tolist() for x in runtime.cfg["readout"]["bins_ms"] if float(x) <= 20.0}
result = {
"reset_same_seed_continuous_chunk_spikes_exact": bool(np.array_equal(one["step"], two["step"]) and np.array_equal(one["neuron_idx"], two["neuron_idx"])),
"continuous_step_does_not_reset_clock": runtime.simulation_time_ms == 20.0,
"checkpoint_restore_spikes_exact": bool(np.array_equal(two["step"], replay["step"]) and np.array_equal(two["neuron_idx"], replay["neuron_idx"])),
"checkpoint_restore_voltage_exact": bool(np.array_equal(two_v, replay_v)),
"single_vs_chunk_voltage_max_abs_error": float(np.max(np.abs(one_v - two_v))),
"timestamps_present": "timestamp_ms" in replay,
"per_neuron_readout_shape": int(len(per_neuron["neuron_idx"])),
"per_type_aggregation_labels": int(len(per_type["features"])),
"per_class_aggregation_labels": int(len(per_class["features"])),
"fixed_temporal_bins_tested": bins,
"readout_modes_passed": len(per_neuron["neuron_idx"]) == len(runtime.output_populations["descending"]) and len(per_type["features"]) > 0 and len(per_class["features"]) > 0,
"all_passed": False,
}
result["all_passed"] = all(result[k] for k in ("reset_same_seed_continuous_chunk_spikes_exact", "continuous_step_does_not_reset_clock", "checkpoint_restore_spikes_exact", "checkpoint_restore_voltage_exact", "timestamps_present", "readout_modes_passed"))
write_json(exp / "audit/api-tests.json", result)
return result
def regression(exp: Path, cfg: dict[str, Any], runtime: BrainRuntime) -> dict[str, Any]:
root = Path(cfg["paths"]["phase4c_root"]) / "regression/reference"
results: dict[str, Any] = {}; tolerance = float(cfg["regression"]["float_absolute_tolerance"])
for spec in cfg["regression"]["reference_trials"]:
name = spec["name"]; ref_dir = root / name
events = pq.read_table(ref_dir / "spike-events.parquet", columns=["step", "neuron_idx"])
ref_steps = events["step"].to_numpy(); ref_nodes = events["neuron_idx"].to_numpy()
ref_counts = np.load(ref_dir / "per-neuron-spike-counts.int32.npy")
ref_v = np.load(ref_dir / "final-voltage.float32.npy"); ref_g = np.load(ref_dir / "final-g.float32.npy")
nodes = np.flatnonzero(runtime.population_masks[spec["population"]]).astype(np.int64)
batch = direct_batch(nodes, spec["population"], float(spec["rate_hz"]), 0.0, float(spec["duration_ms"]), "phase4c-regression")
runtime.reset(int(spec["seed"])); t0 = time.perf_counter(); runtime.step(batch, float(spec["duration_ms"])); wall = time.perf_counter() - t0
got = runtime.read_spikes(); counts = runtime.spike_counts.cpu().numpy()
vd = np.abs(runtime.v.cpu().numpy() - ref_v); gd = np.abs(runtime.g.cpu().numpy() - ref_g)
record = {
"spike_sequence_exact": bool(np.array_equal(ref_steps, got["step"]) and np.array_equal(ref_nodes, got["neuron_idx"])),
"spike_counts_exact": bool(np.array_equal(ref_counts, counts)),
"final_voltage_max_abs_error": float(vd.max(initial=0)), "final_voltage_mean_abs_error": float(vd.mean()),
"final_g_max_abs_error": float(gd.max(initial=0)), "final_g_mean_abs_error": float(gd.mean()),
"state_within_phase4c_tolerance": bool(vd.max(initial=0) <= tolerance and gd.max(initial=0) <= tolerance),
"wall_seconds": wall, "event_count": int(len(got["neuron_idx"])),
}
record["passed"] = record["spike_sequence_exact"] and record["spike_counts_exact"] and record["state_within_phase4c_tolerance"]
results[name] = record
write_json(exp / "regression" / f"{name}.json", record)
write_json(exp / "regression/summary.json", {"all_passed": all(x["passed"] for x in results.values()), "trials": results})
return results
def stream_contract(exp: Path, runtime: BrainRuntime) -> dict[str, Any]:
schema = {
"$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MaleCNS Activity Packet",
"contract_version": ACTIVITY_STREAM_VERSION, "type": "object",
"required": ["contract_version", "simulation_time_ms", "sequence_id", "spike_neuron_idx", "population_aggregates", "active_neuron_count", "input_channel_metadata"],
"properties": {
"simulation_time_ms": {"type": "number"}, "sequence_id": {"type": "integer"},
"spike_neuron_idx": {"type": "array", "items": {"type": "integer"}},
"spike_timestamp_ms": {"type": "array", "items": {"type": "number"}},
"population_aggregates": {"type": "object"}, "active_neuron_count": {"type": "integer"},
"input_channel_metadata": {"type": "object"}, "representation": {"const": "event-only"},
},
"dense_time_by_neuron_matrix_forbidden": True,
"supported_modes": ["event-only stream", "temporal aggregation", "saved-event replay"],
}
write_json(exp / "activity-stream/activity-packet.schema.json", schema)
packet = runtime.activity_packet(20.0, {"source": "contract-test"})
path = exp / "activity-stream/replay.jsonl"
path.write_text(json.dumps(packet, default=np_json) + "\n", encoding="utf-8")
replay = json.loads(path.read_text().splitlines()[0])
result = {"contract_frozen": True, "version": ACTIVITY_STREAM_VERSION, "event_only": True, "replay_exact": replay == json.loads(json.dumps(packet, default=np_json)), "schema": str(exp / "activity-stream/activity-packet.schema.json")}
write_json(exp / "activity-stream/replay-test.json", result)
return result
def freeze_io(exp: Path, cfg: dict[str, Any], code_dir: Path, api_tests_result: dict[str, Any]) -> dict[str, Any]:
root = Path(cfg["paths"]["model_root"]) / "io-v1"; root.mkdir(parents=True, exist_ok=True)
for name in ("VERSION.json", "MANIFEST.json", "checksums.sha256"):
path = root / name
if path.exists(): path.chmod(0o644)
sources = [code_dir / "brain_runtime.py", code_dir / "phase4d.py", code_dir / "phase4d.yaml"]
for src in sources:
dst = root / src.name
if dst.exists(): dst.chmod(0o644)
shutil.copy2(src, dst); dst.chmod(0o444)
version = {
"name": cfg["names"]["io_api"], "api_version": API_VERSION,
"input_batch_version": INPUT_BATCH_VERSION, "activity_stream_version": ACTIVITY_STREAM_VERSION,
"runtime_dependency": cfg["names"]["runtime"], "game_agnostic": True,
"api_methods": ["reset", "step", "read_spikes", "read_population", "read_output", "get_state_summary", "checkpoint_state", "restore_state"],
"continuous_gpu_state": True, "api_tests": api_tests_result,
"prohibitions": cfg["prohibitions"],
}
write_json(root / "VERSION.json", version); (root / "VERSION.json").chmod(0o444)
files = sorted(p for p in root.iterdir() if p.is_file() and p.name not in ("MANIFEST.json", "checksums.sha256"))
manifest = {"artifact": "io-v1", "files": [{"path": p.name, "bytes": p.stat().st_size, "sha256": sha256(p)} for p in files]}
write_json(root / "MANIFEST.json", manifest); (root / "MANIFEST.json").chmod(0o444)
targets = files + [root / "MANIFEST.json"]
(root / "checksums.sha256").write_text("".join(f"{sha256(p)} {p.name}\n" for p in targets), encoding="utf-8"); (root / "checksums.sha256").chmod(0o444)
return version
def seal(exp: Path, cfg: dict[str, Any], code: list[Path], input_files: list[Path], summary: dict[str, Any]) -> None:
controls = {exp / "manifests/manifest.json", exp / "manifests/checksums.sha256"}
outputs = sorted(p for p in exp.rglob("*") if p.is_file() and p not in controls)
manifest = {
"phase": "4D", "status": summary["status"], "created_unix_time": time.time(),
"software": {"python": platform.python_version(), "torch": torch.__version__, "numpy": np.__version__, "pyarrow": pa.__version__},
"code": [{"path": str(p), "sha256": sha256(p)} for p in code],
"immutable_inputs": [{"path": str(p), "sha256": sha256(p)} for p in input_files],
"outputs": [{"path": str(p), "bytes": p.stat().st_size, "sha256": sha256(p)} for p in outputs],
"prohibitions": cfg["prohibitions"],
}
write_json(exp / "manifests/manifest.json", manifest); targets = outputs + [exp / "manifests/manifest.json"]
(exp / "manifests/checksums.sha256").write_text("".join(f"{sha256(p)} {p.relative_to(exp)}\n" for p in targets), encoding="utf-8")
def main() -> None:
ap = argparse.ArgumentParser(); ap.add_argument("--config", required=True, type=Path); args = ap.parse_args()
cfg = yaml.safe_load(args.config.read_text(encoding="utf-8")); exp = Path(cfg["paths"]["experiment_root"])
for sub in ("configs", "populations", "visual-adapter", "readout", "latency", "continuous-control", "separability", "activity-stream", "regression", "audit", "compiler-reports/extension-build", "manifests"):
(exp / sub).mkdir(parents=True, exist_ok=True)
shutil.copy2(args.config, exp / "configs/phase4d.yaml")
runtime_root = Path(cfg["paths"]["runtime_root"]); phase4c = Path(cfg["paths"]["phase4c_root"])
immutable = [runtime_root / "VERSION.json", runtime_root / "checksums.sha256", phase4c / "manifests/checksums.sha256", phase4c / "visual-audit/summary.json"]
before = {str(p): sha256(p) for p in immutable}
runtime = BrainRuntime(args.config, exp / "audit/extension-build")
adapter = VisualAdapterV1(cfg, phase4c)
population_summary = freeze_populations(exp, runtime)
contracts = freeze_contracts(exp, cfg, runtime, adapter)
side = audit_visual_sides(exp, runtime, adapter)
pattern_cache = {mode: patterns(adapter.population(mode), cfg) for mode in ("engineering-first-order", "engineering-second-order")}
pattern_summary = {mode: {name: int(np.count_nonzero(v)) for name, v in pats.items()} for mode, pats in pattern_cache.items()}
write_json(exp / "visual-adapter/synthetic-patterns.json", pattern_summary)
write_json(exp / "visual-adapter/modes.json", cfg["visual_adapter"])
api = api_tests(exp, runtime)
windows = window_experiments(exp, cfg, runtime, adapter, pattern_cache)
continuous = continuous_control(exp, cfg, runtime, adapter, pattern_cache)
sep = separability(exp, cfg, runtime, adapter, pattern_cache, side)
reg = regression(exp, cfg, runtime)
stream = stream_contract(exp, runtime)
after = {str(p): sha256(p) for p in immutable}
if before != after: raise RuntimeError("Phase 4C/runtime-v1 immutable input changed")
reg_pass = all(x["passed"] for x in reg.values())
status = "PASS" if api["all_passed"] and reg_pass and stream["contract_frozen"] else "FAILED"
summary = {
"status": status, "brain_runtime_api_frozen": api["all_passed"], "api_tests": api,
"visual_adapter_modes_complete": True, "output_populations": population_summary,
"contracts": contracts,
"window_experiments": windows["summary"],
"continuous_control": {k: {x: v[x] for x in v if x != "intervals"} for k, v in continuous.items()},
"separability": sep, "activity_stream": stream,
"phase4c_regression_all_passed": reg_pass, "phase4c_regression": reg,
"immutable_inputs_unchanged": True, "left_right_audit": side,
"no_stable_retro_rom_buttons_training_or_web": True,
}
write_json(exp / "audit/summary.json", summary)
code_dir = Path(__file__).resolve().parent
io_version = freeze_io(exp, cfg, code_dir, api); summary["io_version"] = io_version
write_json(exp / "audit/summary.json", summary)
seal(exp, cfg, [code_dir / "brain_runtime.py", Path(__file__).resolve(), args.config.resolve()], immutable, summary)
if status != "PASS": raise RuntimeError("Phase 4D validation failed; see audit/summary.json")
if __name__ == "__main__": main()
+102
View File
@@ -0,0 +1,102 @@
phase: 4D
names:
runtime: MaleCNS-LIF-runtime-v1
io_api: MaleCNS-BrainIO-v1
visual_adapter: MaleCNS-VisualAdapter-v1
activity_stream: MaleCNS-ActivityStream-v1
paths:
connectome_root: /data/digital-drosophila/datasets/male-cns-v1.0/processed/neuron-only
model_root: /data/digital-drosophila/models/male-cns-lif
runtime_root: /data/digital-drosophila/models/male-cns-lif/runtime-v1
phase4c_root: /data/digital-drosophila/experiments/phase4c-runtime
experiment_root: /data/digital-drosophila/experiments/phase4d-io
device:
required_name_substring: RTX 4090
launcher_visible_gpu_uuid: GPU-79f3a85d-adce-7833-7003-6488140ca596
model:
dt_ms: 0.1
membrane_tau_ms: 20.0
synaptic_tau_ms: 5.0
refractory_ms: 2.2
resting_potential_mv: -52.0
reset_potential_mv: -52.0
threshold_mv: -45.0
synaptic_delay_ms: 1.8
w_syn_mv_per_anatomical_synapse: 0.275
sign_policy:
neurotransmitter_sign:
acetylcholine: 1
gaba: -1
glutamate: -1
histamine: -1
dopamine: 1
octopamine: 1
serotonin: 1
unknown_sign: 0
unclear_sign: 0
input_batch:
version: 1.0.0
allowed_modes: [spike_injection, external_synaptic_drive]
refractory_respecting_spike_injection: true
external_drive_application: add to g after decay and delayed-connectome arrival, before membrane integration
visual_adapter:
version: 1.0.0
modes:
reference-photoreceptor:
hierarchy_population: visual_sensory
input_mode: spike_injection
biological_fidelity: reference_comparison_only
engineering-first-order:
hierarchy_population: first_order
input_mode: external_synaptic_drive
biological_fidelity: engineering_adapter
engineering-second-order:
hierarchy_population: second_order
input_mode: external_synaptic_drive
biological_fidelity: engineering_adapter
synthetic_rate_hz: 50.0
external_drive_mv: 8.0
readout:
bins_ms: [1, 5, 10, 20, 50]
output_populations: [descending, motor_related, ascending]
window_experiment:
windows_ms: [5, 10, 20, 30, 50, 100]
sources: [engineering-first-order, engineering-second-order, auditory, olfactory]
seeds: [44001, 44002]
continuous_control:
biological_duration_ms: 1000.0
periods_ms: [10, 20, 50, 100]
sequence: [sparse-A, sparse-B, dense, zero, sparse-A]
seed: 44100
synthetic_patterns:
sparse_fraction: 0.05
dense_fraction: 0.25
moving_fraction: 0.05
deterministic_seed: 44200
regression:
float_absolute_tolerance: 0.00390625
reference_trials:
- {name: auditory-10hz, population: auditory, rate_hz: 10, seed: 42001, duration_ms: 1000.0}
- {name: auditory-100hz, population: auditory, rate_hz: 100, seed: 42001, duration_ms: 1000.0}
- {name: olfactory-50hz, population: olfactory, rate_hz: 50, seed: 42003, duration_ms: 1000.0}
- {name: mechanosensory-50hz, population: mechanosensory, rate_hz: 50, seed: 42003, duration_ms: 1000.0}
- {name: gustatory-50hz, population: gustatory, rate_hz: 50, seed: 42003, duration_ms: 1000.0}
prohibitions:
stable_retro: false
rom_access: false
controller_buttons: false
training: false
web_server: false
connectome_or_sign_changes: false