Archive MaleCNS research sources, provenance, bibliography and immutable data inventory
This commit is contained in:
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user