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
@@ -0,0 +1,23 @@
{
"file_count": 3,
"files": [
{
"bytes": 489,
"path": "VERSION.json",
"sha256": "7e413f5112dab08dd869b69a1c58537f6507b075275d7a6bb4255495f4c105ef"
},
{
"bytes": 49126,
"path": "phase4k.py",
"sha256": "6ca7dd7b3fec7f970f8a43ac8286dd0f05b2674ac3996b9b20583b636719abe7"
},
{
"bytes": 3690,
"path": "phase4k.yaml",
"sha256": "3721440cba03036552c358c1a9d3320637e5914da901f74e86189aa297192fd1"
}
],
"generated_utc": "2026-09-20T20:07:05.654584+00:00",
"root": "/data/digital-drosophila/models/male-cns-lif/kc-recruitment-audit-v1",
"total_bytes": 53305
}
@@ -0,0 +1,13 @@
{
"diagnostic_only": true,
"eligibility_v2_modified": false,
"labels": {
"KC_recruitment": "KC-RECRUITMENT-SOURCE-IDENTIFIED",
"MBON_readout_insensitive": false,
"odor_input": "ODOR-INPUT-NOT-GLOMERULUS-GROUNDED",
"plasticity_transfer": "FAIL-PLASTICITY-TRANSFER",
"whole_brain_recruitment_contamination": false
},
"name": "MaleCNS-KC-recruitment-MBON-causal-audit-v1",
"summary_sha256": "0aab0f51ef73e5276d215d35833868e02008336e72166baf9f67496d6a90a825"
}
@@ -0,0 +1,4 @@
5fbae0b970ac26c506f27d06d01b2e716ce45b1cb023439372151ccc3016ac02 MANIFEST.json
7e413f5112dab08dd869b69a1c58537f6507b075275d7a6bb4255495f4c105ef VERSION.json
6ca7dd7b3fec7f970f8a43ac8286dd0f05b2674ac3996b9b20583b636719abe7 phase4k.py
3721440cba03036552c358c1a9d3320637e5914da901f74e86189aa297192fd1 phase4k.yaml
@@ -0,0 +1,609 @@
#!/usr/bin/env python3
"""Phase 4K: read-only KC recruitment and MBON causal-input audit."""
from __future__ import annotations
import argparse
import gc
import json
import math
import shutil
import subprocess
import sys
import time
from collections import Counter, defaultdict
from pathlib import Path
from typing import Any
import numpy as np
import pandas as pd
import torch
import yaml
from phase4h_audit import sha256, tree_manifest, verify_checksum, write_json
from phase4i import Validator, phase4c_regression
from phase4j import APLSimulator, verify_frozen
KC_CATEGORIES = ("PN_olfactory", "KC", "APL", "MBON", "DAN", "other")
MBON_CATEGORIES = ("plastic_KC", "nonplastic_KC", "DAN", "MBON", "feedback_neuron", "other")
def seal(root: Path, manifest: Path, checksums: Path) -> None:
write_json(manifest, tree_manifest(root, {manifest, checksums}))
targets = sorted(x for x in root.rglob("*") if x.is_file() and x != checksums)
checksums.write_text("".join(f"{sha256(x)} {x.relative_to(root)}\n" for x in targets), encoding="utf-8")
def first_latency(first_steps: np.ndarray, members: np.ndarray, dt: float) -> float | None:
values = first_steps[members]
return None if np.all(values < 0) else float(values[values >= 0].min() * dt)
def response(run: dict[str, Any], v: Validator) -> dict[str, Any]:
return {
"MBON_spikes": int(run["counts"][v.mbon].sum()),
"MBON_first_latency_ms": first_latency(run["first_steps"], v.mbon, run["dt_ms"]),
"downstream_spikes": int(run["counts"][v.downstream].sum()),
"downstream_first_latency_ms": first_latency(run["first_steps"], v.downstream, run["dt_ms"]),
"MBON_final_g": run["final_g_mbon"],
"MBON_final_v": run["final_v_mbon"],
}
def compare(pre: dict[str, Any], post: dict[str, Any]) -> dict[str, Any]:
def delta_latency(key: str) -> float | None:
a, b = pre[key], post[key]
return None if a is None or b is None else float(b - a)
return {
"MBON_delta_g_max_abs": float(np.max(np.abs(post["MBON_final_g"] - pre["MBON_final_g"]))),
"MBON_delta_v_max_abs": float(np.max(np.abs(post["MBON_final_v"] - pre["MBON_final_v"]))),
"MBON_spike_count_delta": int(post["MBON_spikes"] - pre["MBON_spikes"]),
"MBON_spike_l1": int(abs(post["MBON_spikes"] - pre["MBON_spikes"])),
"MBON_first_latency_delta_ms": delta_latency("MBON_first_latency_ms"),
"downstream_spike_count_delta": int(post["downstream_spikes"] - pre["downstream_spikes"]),
"downstream_first_latency_delta_ms": delta_latency("downstream_first_latency_ms"),
}
class AuditSimulator(APLSimulator):
"""Frozen LIF runtime with side-channel drive and event-causality accounting."""
def __init__(self, v: Validator, cfg: dict[str, Any], exp: Path):
super().__init__(v, cfg, exp)
self.base_np = v.base.detach().cpu().numpy().copy()
self.indices_np = v.rt.indices_np
self.raw_np = v.rt.raw_np
self.edge_sources = self.sources
self.dan = np.unique(v.confirmed["source_neuron_idx"].to_numpy(np.int32))
pn_olf = (v.cls == "ALPN") | (v.cls == "olfactory")
node_code = np.full(v.n, KC_CATEGORIES.index("other"), np.int8)
node_code[pn_olf] = KC_CATEGORIES.index("PN_olfactory")
node_code[v.kc] = KC_CATEGORIES.index("KC")
node_code[self.apl] = KC_CATEGORIES.index("APL")
node_code[v.mbon] = KC_CATEGORIES.index("MBON")
node_code[self.dan] = KC_CATEGORIES.index("DAN")
self.node_kc_code = node_code
self.edge_kc_code_gpu = torch.as_tensor(node_code[self.edge_sources], dtype=torch.int8, device=self.device)
target_is_kc = np.zeros(v.n, bool); target_is_kc[v.kc] = True
feedback = np.unique(self.edge_sources[target_is_kc[self.indices_np]])
is_candidate = np.zeros(len(self.indices_np), bool); is_candidate[v.edge_ids] = True
edge_mbon_code = np.full(len(self.indices_np), MBON_CATEGORIES.index("other"), np.int8)
src_kc = v.cls[self.edge_sources] == "Kenyon_Cell"
edge_mbon_code[src_kc & is_candidate] = MBON_CATEGORIES.index("plastic_KC")
edge_mbon_code[src_kc & ~is_candidate] = MBON_CATEGORIES.index("nonplastic_KC")
edge_mbon_code[np.isin(self.edge_sources, self.dan)] = MBON_CATEGORIES.index("DAN")
edge_mbon_code[np.isin(self.edge_sources, v.mbon)] = MBON_CATEGORIES.index("MBON")
structurally_feedback = np.isin(self.edge_sources, feedback) & ~src_kc & ~np.isin(self.edge_sources, np.concatenate([self.dan, v.mbon]))
edge_mbon_code[structurally_feedback] = MBON_CATEGORIES.index("feedback_neuron")
self.edge_mbon_code_gpu = torch.as_tensor(edge_mbon_code, dtype=torch.int8, device=self.device)
kc_lookup = np.full(v.n, -1, np.int32); kc_lookup[v.kc] = np.arange(len(v.kc), dtype=np.int32)
mbon_lookup = np.full(v.n, -1, np.int32); mbon_lookup[v.mbon] = np.arange(len(v.mbon), dtype=np.int32)
self.kc_lookup_gpu = torch.as_tensor(kc_lookup, dtype=torch.int32, device=self.device)
self.mbon_lookup_gpu = torch.as_tensor(mbon_lookup, dtype=torch.int32, device=self.device)
self.mbon_gpu = torch.as_tensor(v.mbon, dtype=torch.int64, device=self.device)
def population_label(self, neuron: int) -> str:
return KC_CATEGORIES[int(self.node_kc_code[int(neuron)])]
@staticmethod
def _dominant_arrivals(
item: tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray] | None,
score: np.ndarray, parent: np.ndarray, edge: np.ndarray, arrival: np.ndarray,
) -> None:
if item is None or not len(item[0]):
return
targets, values, parents, edges, arrivals = item
order = np.lexsort((-values, targets))
st = targets[order]
take = np.r_[True, st[1:] != st[:-1]]
chosen = order[take]; nodes = targets[chosen]; better = values[chosen] > score[nodes]
nodes = nodes[better]; chosen = chosen[better]
score[nodes] = values[chosen]; parent[nodes] = parents[chosen]
edge[nodes] = edges[chosen]; arrival[nodes] = arrivals[chosen]
def run_audit(
self, targets: np.ndarray, seed: int, mode: str, duration_ms: float,
delta: np.ndarray | None = None, rate_hz: float | None = None, pulses: list[float] | None = None,
dan_targets: np.ndarray | None = None, dan_pulses: list[float] | None = None,
causal: bool = False, collect_kc_spikes: bool = False,
) -> dict[str, Any]:
if delta is None:
delta = np.zeros(len(self.v.edge_ids), np.float32)
self.v.install(delta)
if mode in {"graded", "disabled"}:
self.rt.weights[self.apl_edge_gpu] = 0.0
self.rt.reset(int(seed)); steps = int(round(float(duration_ms) / self.dt))
target_gpu = torch.as_tensor(targets, dtype=torch.int64, device=self.device)
forced = self._forced(steps, targets, seed, rate_hz, pulses)
dan = np.empty(0, np.int32) if dan_targets is None else np.asarray(dan_targets, np.int32)
dan_gpu = torch.as_tensor(dan, dtype=torch.int64, device=self.device)
dan_forced = self._forced(steps, dan, seed + 991, None, dan_pulses)
qlen = self.rt.queue_len; nk, nm = len(self.v.kc), len(self.v.mbon)
kq = torch.zeros((qlen, len(KC_CATEGORIES), nk), dtype=torch.float32, device=self.device)
mq = torch.zeros((qlen, len(MBON_CATEGORIES), nm), dtype=torch.float32, device=self.device)
mpq = torch.zeros_like(mq); mdq = torch.zeros((qlen, nm), dtype=torch.float32, device=self.device)
kg = torch.zeros((len(KC_CATEGORIES), nk), dtype=torch.float32, device=self.device)
mg = torch.zeros((len(MBON_CATEGORIES), nm), dtype=torch.float32, device=self.device)
mdg = torch.zeros(nm, dtype=torch.float32, device=self.device)
kr = torch.zeros_like(kg); mr = torch.zeros_like(mg); mrp = torch.zeros_like(mg); mdr = torch.zeros(nm, dtype=torch.float32, device=self.device)
kt = torch.zeros((steps, len(KC_CATEGORIES)), dtype=torch.float32, device=self.device)
mt = torch.zeros((steps, len(MBON_CATEGORIES)), dtype=torch.float32, device=self.device)
counts = torch.zeros(self.v.n, dtype=torch.int32, device=self.device)
first = torch.full((self.v.n,), -1, dtype=torch.int32, device=self.device)
kc_apl_first = torch.zeros(nk, dtype=torch.float32, device=self.device)
params = self.cfg["apl_overlay"]
apl_state = torch.zeros(len(self.apl), dtype=torch.float32, device=self.device)
apl_decay = math.exp(-self.dt / float(params["tau_ms"]))
alpha, gd = self.rt.alpha, self.rt.g_decay
rest, reset, threshold, refsteps = self.rt.rest, self.rt.reset_value, self.rt.threshold, self.rt.refractory_steps
delta_by_edge = torch.zeros(len(self.indices_np), dtype=torch.float32, device=self.device)
delta_by_edge[torch.as_tensor(self.v.edge_ids, dtype=torch.int64, device=self.device)] = torch.as_tensor(delta, dtype=torch.float32, device=self.device)
event_rows: list[dict[str, Any]] = []; event_parent: list[int] = []; event_depth: list[int] = []
causal_score = np.zeros(self.v.n, np.float32); causal_parent = np.full(self.v.n, -1, np.int64)
causal_edge = np.full(self.v.n, -1, np.int64); causal_arrival = np.full(self.v.n, -1, np.int32)
causal_ring: list[tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray] | None] = [None] * qlen
current_event = np.full(self.v.n, -1, np.int64)
kc_spike_steps: list[np.ndarray] = []; kc_spike_nodes: list[np.ndarray] = []
kc_local_lookup = np.full(self.v.n, -1, np.int32); kc_local_lookup[self.v.kc] = np.arange(nk, dtype=np.int32)
forced_set = set(map(int, targets)); dan_set = set(map(int, dan))
torch.cuda.synchronize(); t0 = time.perf_counter()
for step in range(steps):
slot = step % qlen
self.rt.g.mul_(gd).add_(self.rt.delay_queue[slot]); self.rt.delay_queue[slot].zero_()
ka = kq[slot]; ma = mq[slot]; mpa = mpq[slot]; mda = mdq[slot]
kg.mul_(gd).add_(ka); mg.mul_(gd).add_(ma); mdg.mul_(gd).add_(mda)
kr.add_(ka); mr.add_(ma); mrp.add_(mpa); mdr.add_(mda)
kt[step] = ka.sum(dim=1); mt[step] = ma.sum(dim=1)
ka.zero_(); ma.zero_(); mpa.zero_(); mda.zero_()
if causal:
causal_score *= float(gd)
self._dominant_arrivals(causal_ring[slot], causal_score, causal_parent, causal_edge, causal_arrival)
causal_ring[slot] = None
self.rt.refractory.clamp_min_(1).sub_(1); available = self.rt.refractory == 0
self.rt.v[available] += alpha * (self.rt.g[available] - (self.rt.v[available] - rest)); self.rt.v[~available] = reset
spikes = available & (self.rt.v >= threshold)
accepted = forced[step] & available[target_gpu]
if accepted.any():
spikes[target_gpu[accepted]] = True
dacc = torch.zeros(len(dan), dtype=torch.bool, device=self.device)
if len(dan):
dacc = dan_forced[step] & available[dan_gpu]
if dacc.any():
spikes[dan_gpu[dacc]] = True
kc_new = spikes[self.kc_gpu] & (first[self.kc_gpu] < 0)
if kc_new.any():
kc_apl_first[kc_new] = kr[KC_CATEGORIES.index("APL"), kc_new]
sources = torch.nonzero(spikes, as_tuple=False).flatten()
counts.add_(spikes.to(torch.int32)); new = spikes & (first < 0); first[new] = step
if collect_kc_spikes:
kn = torch.nonzero(spikes[self.kc_gpu], as_tuple=False).flatten().cpu().numpy()
if len(kn):
kc_spike_steps.append(np.full(len(kn), step, np.int32)); kc_spike_nodes.append(self.v.kc[kn])
accepted_nodes = set(target_gpu[accepted].cpu().numpy().astype(int).tolist()) if bool(accepted.any()) else set()
dan_nodes = set(dan_gpu[dacc].cpu().numpy().astype(int).tolist()) if len(dan) and bool(dacc.any()) else set()
source_np = sources.cpu().numpy().astype(np.int32) if causal and sources.numel() else np.empty(0, np.int32)
if causal and len(source_np):
ids = np.arange(len(event_rows), len(event_rows) + len(source_np), dtype=np.int64)
current_event[source_np] = ids
for eid, node in zip(ids, source_np):
root = int(node) in accepted_nodes and int(node) in forced_set
par = -1 if root else int(causal_parent[node]); is_causal = root or par >= 0
depth = 0 if root else (event_depth[par] + 1 if is_causal else -1)
event_rows.append({"event_id": int(eid), "step": int(step), "timestamp_ms": float(step * self.dt), "neuron_idx": int(node),
"forced_kind": "injected_A" if root else ("matched_DAN" if int(node) in dan_nodes and int(node) in dan_set else "none"),
"causally_reachable_from_A": bool(is_causal), "parent_event_id": par,
"parent_edge_id": -1 if root else int(causal_edge[node]), "parent_arrival_step": -1 if root else int(causal_arrival[node]),
"dominant_decayed_contribution": None if root else float(causal_score[node]), "path_depth": int(depth)})
event_parent.append(par); event_depth.append(depth)
if sources.numel():
self.rt.v[sources] = reset; self.rt.refractory[sources] = refsteps
edge_ids = self.rt._hybrid_edges(sources)
if edge_ids.numel():
next_slot = (step + self.rt.delay_steps) % qlen
targets_all = self.rt.indices[edge_ids].long(); weights = self.rt.weights[edge_ids]
self.rt.delay_queue[next_slot].scatter_add_(0, targets_all, weights)
kloc = self.kc_lookup_gpu[targets_all]; kcode = self.edge_kc_code_gpu[edge_ids]
for ci in range(len(KC_CATEGORIES)):
mask = (kloc >= 0) & (kcode == ci)
if mask.any():
kq[next_slot, ci].scatter_add_(0, kloc[mask].long(), weights[mask])
mloc = self.mbon_lookup_gpu[targets_all]; mcode = self.edge_mbon_code_gpu[edge_ids]
for ci in range(len(MBON_CATEGORIES)):
mask = (mloc >= 0) & (mcode == ci)
if mask.any():
mq[next_slot, ci].scatter_add_(0, mloc[mask].long(), weights[mask])
mpq[next_slot, ci].scatter_add_(0, mloc[mask].long(), weights[mask].clamp_min(0))
mm = mloc >= 0
if mm.any():
mdq[next_slot].scatter_add_(0, mloc[mm].long(), delta_by_edge[edge_ids[mm]])
if causal:
eids = edge_ids.cpu().numpy().astype(np.int64); pre = self.edge_sources[eids]
pe = current_event[pre]; w = self.base_np[eids]
cm = (pe >= 0) & (w > 0)
if np.any(cm):
causal_ring[next_slot] = (self.indices_np[eids[cm]].astype(np.int32), w[cm].astype(np.float32), pe[cm].astype(np.int64),
eids[cm], np.full(np.count_nonzero(cm), step + self.rt.delay_steps, np.int32))
if causal and len(source_np):
current_event[source_np] = -1
if mode == "graded":
apl_state.mul_(apl_decay).add_(torch.mv(self.input_gpu, spikes.to(torch.float32)) * float(params["normalization"]))
inhibition = torch.matmul(apl_state, self.output_gpu) * float(params["gain"])
self.rt.g.index_add_(0, self.kc_gpu, -inhibition)
kg[KC_CATEGORIES.index("APL")].sub_(inhibition); kr[KC_CATEGORIES.index("APL")].sub_(inhibition)
kt[step, KC_CATEGORIES.index("APL")].sub_(inhibition.sum())
torch.cuda.synchronize(); wall = time.perf_counter() - t0
counts_np = counts.cpu().numpy(); first_np = first.cpu().numpy()
kshadow = kg.cpu().numpy(); mshadow = mg.cpu().numpy(); mdshadow = mdg.cpu().numpy()
final_k = self.rt.g[self.kc_gpu].cpu().numpy(); final_m = self.rt.g[self.mbon_gpu].cpu().numpy()
kc_spikes = pd.DataFrame({"step": np.concatenate(kc_spike_steps) if kc_spike_steps else np.empty(0, np.int32),
"neuron_idx": np.concatenate(kc_spike_nodes) if kc_spike_nodes else np.empty(0, np.int32)})
return {
"counts": counts_np, "first_steps": first_np, "dt_ms": self.dt, "wall_seconds": wall,
"final_g_kc": final_k, "final_g_mbon": final_m, "final_v_mbon": self.rt.v[self.mbon_gpu].cpu().numpy().copy(),
"kc_drive_received": kr.cpu().numpy(), "kc_g_shadow": kshadow, "kc_apl_at_first": kc_apl_first.cpu().numpy(),
"mbon_drive_received": mr.cpu().numpy(), "mbon_positive_received": mrp.cpu().numpy(),
"mbon_delta_received": mdr.cpu().numpy(), "mbon_g_shadow": mshadow, "mbon_delta_g_shadow": mdshadow,
"kc_timeseries": kt.cpu().numpy(), "mbon_timeseries": mt.cpu().numpy(), "events": pd.DataFrame(event_rows), "kc_spikes": kc_spikes,
"shadow_validation": {"KC_final_g_max_abs_error": float(np.max(np.abs(kshadow.sum(axis=0) - final_k))),
"MBON_final_g_max_abs_error": float(np.max(np.abs(mshadow.sum(axis=0) - final_m)))},
}
def save_drive(condition: str, run: dict[str, Any], v: Validator, kc_rows: list[dict[str, Any]], mbon_rows: list[dict[str, Any]], time_rows: list[dict[str, Any]]) -> dict[str, Any]:
for ci, category in enumerate(KC_CATEGORIES):
for local, neuron in enumerate(v.kc):
kc_rows.append({"condition": condition, "category": category, "neuron_idx": int(neuron), "body_id": int(v.meta.loc[neuron, "body_id"]),
"cumulative_signed_drive": float(run["kc_drive_received"][ci, local]), "final_g_component": float(run["kc_g_shadow"][ci, local])})
for ci, category in enumerate(MBON_CATEGORIES):
for local, neuron in enumerate(v.mbon):
mbon_rows.append({"condition": condition, "category": category, "neuron_idx": int(neuron), "body_id": int(v.meta.loc[neuron, "body_id"]),
"cumulative_signed_drive": float(run["mbon_drive_received"][ci, local]), "cumulative_positive_drive": float(run["mbon_positive_received"][ci, local]),
"final_g_component": float(run["mbon_g_shadow"][ci, local]), "actual_delta_cumulative_drive": float(run["mbon_delta_received"][local]),
"actual_delta_final_g": float(run["mbon_delta_g_shadow"][local]), "final_total_g": float(run["final_g_mbon"][local]),
"final_v": float(run["final_v_mbon"][local]), "threshold_margin_mv": float(v.rt.threshold - run["final_v_mbon"][local])})
for step in range(len(run["kc_timeseries"])):
row = {"condition": condition, "step": step, "timestamp_ms": step * run["dt_ms"]}
row.update({f"KC_{x}": float(run["kc_timeseries"][step, i]) for i, x in enumerate(KC_CATEGORIES)})
row.update({f"MBON_{x}": float(run["mbon_timeseries"][step, i]) for i, x in enumerate(MBON_CATEGORIES)})
time_rows.append(row)
ksum = run["kc_drive_received"].sum(axis=1); msum = run["mbon_drive_received"].sum(axis=1); mpos = run["mbon_positive_received"].sum(axis=1)
return {"KC_cumulative_signed_drive": {x: float(ksum[i]) for i, x in enumerate(KC_CATEGORIES)},
"MBON_cumulative_signed_drive": {x: float(msum[i]) for i, x in enumerate(MBON_CATEGORIES)},
"MBON_cumulative_positive_drive": {x: float(mpos[i]) for i, x in enumerate(MBON_CATEGORIES)},
"wall_seconds": run["wall_seconds"], "shadow_validation": run["shadow_validation"]}
def classify_path(labels: list[str], apl_modulation: bool, path_length: int) -> str:
if path_length == 1:
return "direct recurrent"
middle = labels[1:-1]
if any(x == "PN_olfactory" for x in middle):
return "PN/olfactory feedback"
if any(x in {"MBON", "DAN"} for x in middle):
return "MBON/DAN feedback"
if any(x == "APL" for x in middle) or apl_modulation:
return "APL-related"
return "other CNS loop" if path_length > 0 else "unexplained"
def recruitment_paths(sim: AuditSimulator, run: dict[str, Any], set_a: np.ndarray, set_b: np.ndarray, actual_delta: np.ndarray, exp: Path) -> dict[str, Any]:
events = run["events"].copy(); events.to_parquet(exp / "recruitment/direct-A-all-spike-events.parquet", index=False)
first_event = events.sort_values(["step", "event_id"]).drop_duplicates("neuron_idx").set_index("neuron_idx")
changed = actual_delta != 0; cand_src = sim.v.cand["source_neuron_idx"].to_numpy(np.int32)
b_changed_edges = changed & np.isin(cand_src, set_b); b_changed_sources = np.unique(cand_src[b_changed_edges])
event_map = {int(row["event_id"]): row for row in events.to_dict(orient="records")}
recruited = np.setdiff1d(sim.v.kc[run["counts"][sim.v.kc] > 0], set_a)
node_rows: list[dict[str, Any]] = []; edge_rows: list[dict[str, Any]] = []; summary_rows: list[dict[str, Any]] = []
kc_local = {int(x): i for i, x in enumerate(sim.v.kc)}
for target in recruited:
if int(target) not in first_event.index:
continue
terminal = first_event.loc[int(target)]; eid = int(terminal["event_id"]); chain: list[dict[str, Any]] = []
seen = set()
while eid >= 0 and eid not in seen:
seen.add(eid); ev = event_map[eid]; chain.append(ev); eid = int(ev["parent_event_id"])
chain.reverse(); rooted = bool(chain and chain[0]["forced_kind"] == "injected_A")
labels = [sim.population_label(int(x["neuron_idx"])) for x in chain]
apl_mod = abs(float(run["kc_apl_at_first"][kc_local[int(target)]])) > 0
path_length = len(chain) - 1 if rooted else 0
category = classify_path(labels, apl_mod, path_length) if rooted else "unexplained"
for order, ev in enumerate(chain):
neuron = int(ev["neuron_idx"])
node_rows.append({"target_recruited_kc": int(target), "path_order": order, "event_id": int(ev["event_id"]), "neuron_idx": neuron,
"body_id": int(sim.v.meta.loc[neuron, "body_id"]), "population": sim.population_label(neuron),
"annotation_type": sim.v.types[neuron], "annotation_class": sim.v.cls[neuron], "timestamp_ms": float(ev["timestamp_ms"]),
"forced_kind": ev["forced_kind"]})
for order, ev in enumerate(chain[1:], start=1):
edge_id = int(ev["parent_edge_id"]); parent = chain[order - 1]
edge_rows.append({"target_recruited_kc": int(target), "path_order": order, "edge_id": edge_id,
"source_neuron_idx": int(parent["neuron_idx"]), "target_neuron_idx": int(ev["neuron_idx"]),
"source_spike_time_ms": float(parent["timestamp_ms"]), "arrival_time_ms": float(ev["parent_arrival_step"] * run["dt_ms"]),
"target_spike_time_ms": float(ev["timestamp_ms"]), "effective_weight": float(sim.base_np[edge_id]),
"raw_synapse_count": int(sim.raw_np[edge_id]), "synaptic_sign": int(np.sign(sim.base_np[edge_id])),
"source_population": sim.population_label(int(parent["neuron_idx"])), "target_population": sim.population_label(int(ev["neuron_idx"]))})
summary_rows.append({"neuron_idx": int(target), "body_id": int(sim.v.meta.loc[target, "body_id"]), "annotation_type": sim.v.types[target],
"first_activation_ms": float(terminal["timestamp_ms"]), "causal_path_found": rooted, "path_length": path_length,
"recruitment_class": category, "APL_involved_or_modulatory": apl_mod or ("APL" in labels),
"is_injected_B": bool(target in set_b), "is_B_source_with_changed_edge": bool(target in b_changed_sources),
"changed_B_edge_count": int(np.count_nonzero(b_changed_edges & (cand_src == target)))})
summary_df = pd.DataFrame(summary_rows); summary_df.to_parquet(exp / "recruitment/recruited-kc-summary.parquet", index=False)
pd.DataFrame(node_rows).to_parquet(exp / "recruitment/causal-path-nodes.parquet", index=False)
pd.DataFrame(edge_rows).to_parquet(exp / "recruitment/causal-path-edges.parquet", index=False)
counts = summary_df["recruitment_class"].value_counts().to_dict() if len(summary_df) else {}
b_rows = summary_df[summary_df["is_B_source_with_changed_edge"]] if len(summary_df) else summary_df
report = {"label": "KC-RECRUITMENT-SOURCE-IDENTIFIED" if len(summary_df) and bool(summary_df["causal_path_found"].all()) else "FAIL-KC-RECRUITMENT-SOURCE-IDENTIFIED",
"injected_A_KC": int(len(set_a)), "active_non_A_KC": int(len(recruited)), "traced_recruited_KC": int(len(summary_df)),
"causal_paths_found": int(summary_df["causal_path_found"].sum()) if len(summary_df) else 0,
"classification": {str(k): int(v) for k, v in counts.items()},
"B_set_KC_active": int(np.count_nonzero(run["counts"][set_b])), "B_source_changed_edges": int(np.count_nonzero(b_changed_edges)),
"unique_B_source_KC_with_changed_edges": int(len(b_changed_sources)),
"B_source_path_classes": {str(k): int(v) for k, v in b_rows["recruitment_class"].value_counts().to_dict().items()} if len(b_rows) else {},
"causal_rule": sim.cfg["causal_attribution"]["rule"], "static_shortest_path_used": False,
"interpretation_limit": "dominant positive arrival under exact runtime timing is an attribution path, not an intervention-based proof"}
write_json(exp / "recruitment/summary.json", report); return report
def drive_audit(sim: AuditSimulator, set_a: np.ndarray, set_b: np.ndarray, olf_a: np.ndarray, cfg: dict[str, Any], exp: Path) -> tuple[dict[str, Any], dict[str, dict[str, Any]]]:
dc, oc = cfg["direct_kc"], cfg["olfactory"]; params = cfg["apl_overlay"]
specs = {
"original_olfactory_CS": dict(targets=olf_a, seed=oc["seed"], mode="original", duration_ms=oc["duration_ms"], rate_hz=oc["rate_hz"]),
"APL_overlay_olfactory_CS": dict(targets=olf_a, seed=oc["seed"], mode="graded", duration_ms=oc["duration_ms"], rate_hz=oc["rate_hz"]),
"direct_KC_SET_A": dict(targets=set_a, seed=dc["evaluation_seed"], mode="original", duration_ms=dc["duration_ms"], pulses=dc["pulse_times_ms"], collect_kc_spikes=True),
"direct_KC_SET_B": dict(targets=set_b, seed=dc["evaluation_seed"], mode="original", duration_ms=dc["duration_ms"], pulses=dc["pulse_times_ms"], collect_kc_spikes=True),
}
kr: list[dict[str, Any]] = []; mr: list[dict[str, Any]] = []; tr: list[dict[str, Any]] = []; runs = {}; summary = {}
for name, spec in specs.items():
run = sim.run_audit(delta=np.zeros(len(sim.v.edge_ids), np.float32), **spec); runs[name] = run
summary[name] = save_drive(name, run, sim.v, kr, mr, tr)
pd.DataFrame(kr).to_parquet(exp / "drive-decomposition/kc-incoming-drive.parquet", index=False)
pd.DataFrame(mr).to_parquet(exp / "drive-decomposition/mbon-incoming-drive-baseline.parquet", index=False)
pd.DataFrame(tr).to_parquet(exp / "drive-decomposition/drive-timeseries.parquet", index=False)
write_json(exp / "drive-decomposition/summary.json", summary); return summary, runs
def mbon_audit(sim: AuditSimulator, base_runs: dict[str, dict[str, Any]], set_a: np.ndarray, set_b: np.ndarray, actual_delta: np.ndarray, cfg: dict[str, Any], exp: Path) -> tuple[dict[str, Any], dict[str, dict[str, Any]]]:
dc = cfg["direct_kc"]; posts = {}
for label, targets in (("A", set_a), ("B", set_b)):
posts[label] = sim.run_audit(targets, dc["evaluation_seed"], "original", dc["duration_ms"], delta=actual_delta, pulses=dc["pulse_times_ms"], collect_kc_spikes=True)
rows = []; report = {}
for label, pre in (("A", base_runs["direct_KC_SET_A"]), ("B", base_runs["direct_KC_SET_B"])):
post = posts[label]; p = response(pre, sim.v); q = response(post, sim.v); eff = compare(p, q)
plastic_i = MBON_CATEGORIES.index("plastic_KC")
plastic_positive = float(post["mbon_positive_received"][plastic_i].sum()); total_positive = float(post["mbon_positive_received"].sum())
total_signed = float(post["mbon_drive_received"].sum()); delta_signed = float(post["mbon_delta_received"].sum())
report[label] = {"effect": eff, "total_cumulative_signed_drive": total_signed, "total_excitatory_drive": total_positive,
"plastic_KC_excitatory_drive": plastic_positive, "plastic_KC_fraction_of_total_excitatory_drive": plastic_positive / total_positive if total_positive else None,
"actual_delta_cumulative_drive": delta_signed, "actual_delta_over_total_excitatory_drive": delta_signed / total_positive if total_positive else None,
"actual_delta_over_total_signed_drive": delta_signed / total_signed if total_signed else None,
"final_total_g_sum": float(post["final_g_mbon"].sum()), "actual_delta_final_g_sum": float(post["mbon_delta_g_shadow"].sum())}
for state, run in (("pre", pre), ("post", post)):
for ci, category in enumerate(MBON_CATEGORIES):
for local, neuron in enumerate(sim.v.mbon):
rows.append({"stimulus": label, "state": state, "category": category, "neuron_idx": int(neuron), "body_id": int(sim.v.meta.loc[neuron, "body_id"]),
"cumulative_signed_drive": float(run["mbon_drive_received"][ci, local]), "cumulative_positive_drive": float(run["mbon_positive_received"][ci, local]),
"final_g_component": float(run["mbon_g_shadow"][ci, local]), "actual_delta_final_g": float(run["mbon_delta_g_shadow"][local]),
"final_total_g": float(run["final_g_mbon"][local]), "final_v": float(run["final_v_mbon"][local]),
"threshold_margin_mv": float(sim.v.rt.threshold - run["final_v_mbon"][local])})
pd.DataFrame(rows).to_parquet(exp / "mbon-decomposition/pre-post-mbon-drive.parquet", index=False)
write_json(exp / "mbon-decomposition/summary.json", report); return report, posts
def sensitivity(sim: AuditSimulator, set_a: np.ndarray, actual_delta: np.ndarray, cfg: dict[str, Any], exp: Path) -> dict[str, Any]:
dc, sc = cfg["direct_kc"], cfg["sensitivity"]; changed = actual_delta != 0; rows = []
for seed in sc["evaluation_seeds"]:
base_delta = np.zeros(len(sim.v.edge_ids), np.float32)
base = sim.run_audit(set_a, int(seed), "original", dc["duration_ms"], delta=base_delta, pulses=dc["pulse_times_ms"])
bp = response(base, sim.v)
for fraction in sc["depression_fractions"]:
delta = np.zeros(len(sim.v.edge_ids), np.float32); delta[changed] = -float(fraction) * sim.v.base_candidate[changed]
run = base if float(fraction) == 0 else sim.run_audit(set_a, int(seed), "original", dc["duration_ms"], delta=delta, pulses=dc["pulse_times_ms"])
q = response(run, sim.v); eff = compare(bp, q)
functional = bool(eff["MBON_spike_l1"] > 0 or (eff["MBON_first_latency_delta_ms"] not in (None, 0.0)))
rows.append({"seed": int(seed), "depression_fraction": float(fraction), "changed_edges": int(np.count_nonzero(changed)),
**eff, "functional_MBON_output_change": functional})
frame = pd.DataFrame(rows); frame.to_parquet(exp / "sensitivity/controlled-depression-curve.parquet", index=False)
aggregate = []
for frac, group in frame.groupby("depression_fraction"):
aggregate.append({"depression_fraction": float(frac), "seeds": int(len(group)), "functional_seed_count": int(group.functional_MBON_output_change.sum()),
"MBON_delta_g_max_abs_mean": float(group.MBON_delta_g_max_abs.mean()), "MBON_delta_v_max_abs_mean": float(group.MBON_delta_v_max_abs.mean()),
"MBON_spike_l1_mean": float(group.MBON_spike_l1.mean()), "downstream_spike_abs_delta_mean": float(group.downstream_spike_count_delta.abs().mean())})
functional_levels = [x["depression_fraction"] for x in aggregate if x["functional_seed_count"] > 0]
at50 = next(x for x in aggregate if x["depression_fraction"] == 0.5)
insensitive = at50["functional_seed_count"] == 0
report = {"changed_real_edges": int(np.count_nonzero(changed)), "curve": aggregate,
"first_fraction_with_any_functional_MBON_change": min(functional_levels) if functional_levels else None,
"MBON_READOUT_INSENSITIVE": bool(insensitive), "label": "MBON-READOUT-INSENSITIVE" if insensitive else "MBON-READOUT-SENSITIVE-WITHIN-TESTED-RANGE",
"diagnostic_only_not_saved_as_learned_model": True, "criterion": sc["functional_change_definition"]}
write_json(exp / "sensitivity/summary.json", report); return report
def isolated_replay_one(sim: AuditSimulator, spikes: pd.DataFrame, targets: np.ndarray, delta: np.ndarray, duration_ms: float) -> dict[str, Any]:
target_lookup = {int(x): i for i, x in enumerate(targets)}; nt = len(targets); steps = int(round(duration_ms / sim.dt)); qlen = sim.rt.queue_len
relevant = np.isin(sim.edge_sources, sim.v.kc) & np.isin(sim.indices_np, targets)
edge_ids = np.flatnonzero(relevant); by_source: dict[int, tuple[np.ndarray, np.ndarray]] = {}
full_delta = np.zeros(len(sim.indices_np), np.float32); full_delta[sim.v.edge_ids] = delta
weights = sim.base_np[edge_ids] + full_delta[edge_ids]
for source in np.unique(sim.edge_sources[edge_ids]):
mask = sim.edge_sources[edge_ids] == source; ids = edge_ids[mask]
by_source[int(source)] = (np.asarray([target_lookup[int(x)] for x in sim.indices_np[ids]], np.int32), weights[mask].astype(np.float32))
grouped = {int(k): g.neuron_idx.to_numpy(np.int32) for k, g in spikes.groupby("step")}
q = np.zeros((qlen, nt), np.float32); g = np.zeros(nt, np.float32); v = np.full(nt, sim.rt.rest, np.float32); ref = np.zeros(nt, np.int32)
counts = np.zeros(nt, np.int32); first = np.full(nt, -1, np.int32)
for step in range(steps):
slot = step % qlen; g = g * np.float32(sim.rt.g_decay) + q[slot]; q[slot].fill(0)
ref = np.maximum(ref, 1) - 1; available = ref == 0
v[available] += np.float32(sim.rt.alpha) * (g[available] - (v[available] - np.float32(sim.rt.rest))); v[~available] = np.float32(sim.rt.reset_value)
fired = available & (v >= np.float32(sim.rt.threshold)); counts += fired; first[(first < 0) & fired] = step; v[fired] = np.float32(sim.rt.reset_value); ref[fired] = sim.rt.refractory_steps
for source in grouped.get(step, np.empty(0, np.int32)):
if int(source) in by_source:
loc, w = by_source[int(source)]; np.add.at(q[(step + sim.rt.delay_steps) % qlen], loc, w)
return {"counts": counts, "first": first, "g": g, "v": v, "targets": targets}
def isolated_replay(sim: AuditSimulator, base_runs: dict[str, dict[str, Any]], actual_delta: np.ndarray, cfg: dict[str, Any], whole_direct_status: str, exp: Path) -> dict[str, Any]:
changed = actual_delta != 0; targets = np.unique(sim.v.cand.loc[changed, "target_neuron_idx"].to_numpy(np.int32)); rows = []; effects = {}
for label, key in (("A", "direct_KC_SET_A"), ("B", "direct_KC_SET_B")):
spikes = base_runs[key]["kc_spikes"]
pre = isolated_replay_one(sim, spikes, targets, np.zeros(len(actual_delta), np.float32), cfg["direct_kc"]["duration_ms"])
post = isolated_replay_one(sim, spikes, targets, actual_delta, cfg["direct_kc"]["duration_ms"])
def lat(x: dict[str, Any]) -> float | None:
return None if np.all(x["first"] < 0) else float(x["first"][x["first"] >= 0].min() * sim.dt)
effect = {"delta_g_max_abs": float(np.max(np.abs(post["g"] - pre["g"]))), "delta_v_max_abs": float(np.max(np.abs(post["v"] - pre["v"]))),
"spike_l1": int(np.abs(post["counts"] - pre["counts"]).sum()), "pre_first_latency_ms": lat(pre), "post_first_latency_ms": lat(post),
"latency_delta_ms": None if lat(pre) is None or lat(post) is None else float(lat(post) - lat(pre))}
effects[label] = effect
for state, x in (("pre", pre), ("post", post)):
for local, neuron in enumerate(targets):
rows.append({"stimulus": label, "state": state, "neuron_idx": int(neuron), "body_id": int(sim.v.meta.loc[neuron, "body_id"]),
"spikes": int(x["counts"][local]), "first_latency_ms": None if x["first"][local] < 0 else float(x["first"][local] * sim.dt),
"final_g": float(x["g"][local]), "final_v": float(x["v"][local])})
pd.DataFrame(rows).to_parquet(exp / "isolated-replay/real-edge-replay.parquet", index=False)
af = effects["A"]["spike_l1"] > 0 or effects["A"]["latency_delta_ms"] not in (None, 0.0)
bf = effects["B"]["spike_l1"] > 0 or effects["B"]["latency_delta_ms"] not in (None, 0.0)
transfer = bool(af and not bf); contamination = bool(transfer and whole_direct_status != "PASS-DIRECT-KC-MEMORY-v2")
report = {"target_MBON": int(len(targets)), "actual_changed_edges": int(np.count_nonzero(changed)), "effects": effects,
"PLASTICITY_TRANSFER_PASS": transfer, "plasticity_label": "PLASTICITY-TRANSFER-PASS" if transfer else "FAIL-PLASTICITY-TRANSFER",
"WHOLE_BRAIN_RECRUITMENT_CONTAMINATION": contamination,
"weak_effect_label": None if af else "KC-MBON-PLASTICITY-EFFECT-TOO-WEAK",
"spike_train_source": "deterministically regenerated from frozen Phase 4J protocol because exact Phase 4J event trains were not persisted",
"new_edges_added": False}
write_json(exp / "isolated-replay/summary.json", report); return report
def olfactory_provenance(sim: AuditSimulator, cfg: dict[str, Any], exp: Path) -> dict[str, Any]:
meta = sim.v.meta; olf = np.flatnonzero(sim.v.rt.population_masks["olfactory"]).astype(np.int32); olf.sort(); take = int(cfg["olfactory"]["targets_per_cs"])
current = np.concatenate([olf[:take], olf[take:2 * take]])
explicit = [x for x in meta.columns if any(term in x.lower() for term in ("glomer", "receptor", "sensillum", "antenn"))]
fields = [x for x in ("neuron_idx", "body_id", "annotation_superclass", "annotation_class", "annotation_subclass", "annotation_type", "annotation_instance", "side", "neurotransmitter", *explicit) if x in meta.columns]
fields = list(dict.fromkeys(fields))
table = meta.loc[current, fields].copy(); table.insert(0, "CS", np.repeat(["A", "B"], take)); table.to_parquet(exp / "olfactory-provenance/current-CS-neurons.parquet", index=False)
pn = np.flatnonzero(sim.v.cls == "ALPN").astype(np.int32); is_pn_kc = np.isin(sim.edge_sources, pn) & np.isin(sim.indices_np, sim.v.kc); eids = np.flatnonzero(is_pn_kc)
edges = pd.DataFrame({"edge_id": eids, "source_neuron_idx": sim.edge_sources[eids], "target_neuron_idx": sim.indices_np[eids], "raw_synapse_count": sim.raw_np[eids]})
edges["source_body_id"] = meta.loc[edges.source_neuron_idx, "body_id"].to_numpy(); edges["target_body_id"] = meta.loc[edges.target_neuron_idx, "body_id"].to_numpy()
edges.to_parquet(exp / "olfactory-provenance/ALPN-to-KC-real-edges.parquet", index=False)
identity_coverage = {x: int((table[x].notna() & ~table[x].fillna("").astype(str).eq("")).sum()) for x in explicit}
# Phase 4J selected two contiguous neuron_idx slices from the broad olfactory
# mask; it did not select or match by any receptor/glomerulus/PN identity.
grounded = False
report = {"olfactory_population_definition": "annotation_class == olfactory", "olfactory_population_count": int(len(olf)),
"current_CS_definition": "first 64 and next 64 neuron_idx after sorting the broad olfactory population", "current_CS_neurons": int(len(current)),
"explicit_glomerulus_or_receptor_columns": explicit, "explicit_identity_nonempty_counts_in_current_CS": identity_coverage,
"current_CS_selection_used_explicit_identity": False, "current_CS_glomerulus_grounded": grounded,
"label": "GLOMERULUS-GROUNDED" if grounded else "ODOR-INPUT-NOT-GLOMERULUS-GROUNDED",
"ALPN_count": int(len(pn)), "ALPN_to_KC_edges": int(len(eids)), "ALPN_to_KC_synapse_weight": int(sim.raw_np[eids].sum()),
"DoOR_imported": False, "reason_DoOR_not_imported": "Phase 4K mapping audit found no explicit MaleCNS glomerulus/receptor key for a non-guessing join",
"prepared_next_input_scheme": "odor response vector -> explicit receptor/glomerulus mapping -> matched MaleCNS PN identities -> real ALPN-to-KC edges",
"unmatched_channels": "all until an explicit official receptor/glomerulus-to-MaleCNS mapping is supplied"}
write_json(exp / "olfactory-provenance/summary.json", report); return report
def dataset_checks(cfg: dict[str, Any]) -> dict[str, Any]:
root = Path(cfg["paths"]["dataset_root"])
commands = {
"raw_core_sha256": f"cd {root}/raw && sha256sum -c _manifests/checksums.sha256 >/dev/null",
"raw_skeleton_md5": f"cd {root}/raw/skeletons && md5sum -c ../_manifests/skeletons/checksums.md5 >/dev/null",
"phase3_sha256": f"cd {root}/processed && sha256sum -c audit/checksums.sha256 >/dev/null",
"phase3b_audit_sha256": f"cd {root}/processed/neuron-only/audit && sha256sum -c checksums.sha256 >/dev/null",
"phase3b_166700_sha256": f"cd {root}/processed/neuron-only && sha256sum -c audit/phase3b-166700/checksums.sha256 >/dev/null",
"phase1_phase2_backup_sha256": f"sha256sum -c {root}/backups/male-cns-v1.0-phase1-phase2-20260919-1518.tar.zst.sha256 >/dev/null",
}
results = {}
for name, command in commands.items():
proc = subprocess.run(["bash", "-lc", command], capture_output=True, text=True)
results[name] = {"passed": proc.returncode == 0, "returncode": proc.returncode, "stderr": proc.stderr[-1000:]}
results["all_passed"] = all(x["passed"] for x in results.values())
return results
def main() -> None:
ap = argparse.ArgumentParser(); ap.add_argument("--config", type=Path, required=True); args = ap.parse_args()
cfg = yaml.safe_load(args.config.read_text()); exp = Path(cfg["paths"]["experiment_root"]); model = Path(cfg["paths"]["phase4k_model_root"])
for sub in ("recruitment", "drive-decomposition", "mbon-decomposition", "sensitivity", "isolated-replay", "olfactory-provenance", "regression", "audit", "manifests", "compiler-reports/extension-build"):
(exp / sub).mkdir(parents=True, exist_ok=True)
model.mkdir(parents=True, exist_ok=True)
before = verify_frozen(cfg); p4j_checks = [verify_checksum(Path(cfg["paths"]["phase4j_root"]), Path(cfg["paths"]["phase4j_root"]) / "manifests/checksums.sha256"),
verify_checksum(Path(cfg["paths"]["phase4j_model_root"]), Path(cfg["paths"]["phase4j_model_root"]) / "checksums.sha256")]
if not all(x["passed"] for x in p4j_checks): raise RuntimeError("Phase 4J frozen checksum failure")
shutil.copy2(args.config, exp / "audit/phase4k.yaml"); shutil.copy2(Path(__file__).resolve(), exp / "audit/phase4k.py")
root = Path(cfg["paths"]["connectome_root"]); base_files = [root / "gpu/csr-indptr.int64.npy", root / "gpu/csr-indices.int32.npy", root / "gpu/csr-weights.int64.npy", root / "neuron-index/unified-metadata.parquet"]
base_before = {str(x): sha256(x) for x in base_files}
v = Validator(cfg, args.config.resolve(), exp); sim = AuditSimulator(v, cfg, exp)
sets = pd.read_parquet(Path(cfg["paths"]["phase4j_root"]) / "direct-kc-memory/direct-kc-ensembles.parquet")
set_a = np.sort(sets.loc[sets["set"] == "KC_SET_A", "neuron_idx"].to_numpy(np.int32)); set_b = np.sort(sets.loc[sets["set"] == "KC_SET_B", "neuron_idx"].to_numpy(np.int32))
overlay = np.load(Path(cfg["paths"]["phase4j_root"]) / f"direct-kc-memory/seed-{cfg['direct_kc']['trace_seed']}/overlay.npz")
if not np.array_equal(overlay["edge_id"], v.edge_ids): raise RuntimeError("Phase 4J overlay edge identity mismatch")
actual_delta = overlay["plastic_delta_mv"].astype(np.float32)
olf = np.flatnonzero(v.rt.population_masks["olfactory"]).astype(np.int32); olf.sort(); olf_a = olf[:cfg["olfactory"]["targets_per_cs"]]
drive_summary, base_runs = drive_audit(sim, set_a, set_b, olf_a, cfg, exp)
dan = v.dan_targets(cfg["direct_kc"]["matched_dan_type"], cfg["direct_kc"]["target_compartment_verbatim"])
causal_run = sim.run_audit(set_a, cfg["direct_kc"]["trace_seed"], "original", cfg["direct_kc"]["duration_ms"],
delta=np.zeros(len(v.edge_ids), np.float32), pulses=cfg["direct_kc"]["pulse_times_ms"], dan_targets=dan,
dan_pulses=[cfg["direct_kc"]["dan_time_ms"]], causal=True, collect_kc_spikes=True)
recruitment = recruitment_paths(sim, causal_run, set_a, set_b, actual_delta, exp)
mbon, post_runs = mbon_audit(sim, base_runs, set_a, set_b, actual_delta, cfg, exp)
curve = sensitivity(sim, set_a, actual_delta, cfg, exp)
p4j_summary = json.loads((Path(cfg["paths"]["phase4j_root"]) / "direct-kc-memory/summary.json").read_text())
replay = isolated_replay(sim, base_runs, actual_delta, cfg, p4j_summary["status"], exp)
odor = olfactory_provenance(sim, cfg, exp)
labels = {"KC_recruitment": recruitment["label"], "plasticity_transfer": replay["plasticity_label"],
"whole_brain_recruitment_contamination": replay["WHOLE_BRAIN_RECRUITMENT_CONTAMINATION"],
"MBON_readout_insensitive": curve["MBON_READOUT_INSENSITIVE"], "odor_input": odor["label"]}
del sim, v; gc.collect(); torch.cuda.empty_cache()
reg = phase4c_regression(cfg, args.config.resolve(), exp)
after = verify_frozen(cfg); p4j_after = [verify_checksum(Path(cfg["paths"]["phase4j_root"]), Path(cfg["paths"]["phase4j_root"]) / "manifests/checksums.sha256"),
verify_checksum(Path(cfg["paths"]["phase4j_model_root"]), Path(cfg["paths"]["phase4j_model_root"]) / "checksums.sha256")]
dataset = dataset_checks(cfg); base_after = {str(x): sha256(x) for x in base_files}
integrity = before["all_passed"] and after["all_passed"] and all(x["passed"] for x in p4j_checks + p4j_after) and dataset["all_passed"] and base_before == base_after and reg["all_passed"]
integrity_report = {"all_passed": integrity, "phase4c_to_4i_before": before["all_passed"], "phase4c_to_4i_after": after["all_passed"],
"phase4j_before": all(x["passed"] for x in p4j_checks), "phase4j_after": all(x["passed"] for x in p4j_after),
"dataset_phase1_to_3b": dataset, "base_CSR_unchanged": base_before == base_after, "phase4c_reference_regression": reg["all_passed"]}
write_json(exp / "audit/integrity.json", integrity_report); write_json(exp / "audit/base-sha256.json", {"before": base_before, "after": base_after})
summary = {"phase": "4K", "labels": labels, "recruitment": recruitment, "drive_decomposition": drive_summary, "MBON_decomposition": mbon,
"sensitivity": curve, "isolated_replay": replay, "olfactory_provenance": odor, "integrity": integrity_report,
"recommendation": "repair upstream odor/glomerulus-grounded encoding and KC recruitment before changing MBON plasticity/readout" if odor["label"] == "ODOR-INPUT-NOT-GLOMERULUS-GROUNDED" else "use causal audit to choose upstream recruitment versus readout work",
"eligibility_v2_modified": False, "base_modified": False, "plastic_mask_expanded": False, "stopped_before_phase4l": True}
write_json(exp / "audit/final-summary.json", summary)
shutil.copy2(args.config, model / "phase4k.yaml"); shutil.copy2(Path(__file__).resolve(), model / "phase4k.py")
write_json(model / "VERSION.json", {"name": cfg["name"], "diagnostic_only": True, "labels": labels, "eligibility_v2_modified": False,
"summary_sha256": sha256(exp / "audit/final-summary.json")})
print(json.dumps({"labels": labels, "integrity": integrity}, indent=2)); sys.stdout.flush()
seal(model, model / "MANIFEST.json", model / "checksums.sha256"); seal(exp, exp / "manifests/manifest.json", exp / "manifests/checksums.sha256")
if __name__ == "__main__":
main()
@@ -0,0 +1,101 @@
phase: 4K
name: MaleCNS-KC-recruitment-MBON-causal-audit-v1
paths:
connectome_root: /data/digital-drosophila/datasets/male-cns-v1.0/processed/neuron-only
dataset_root: /data/digital-drosophila/datasets/male-cns-v1.0
model_root: /data/digital-drosophila/models/male-cns-lif
phase4k_model_root: /data/digital-drosophila/models/male-cns-lif/kc-recruitment-audit-v1
experiment_root: /data/digital-drosophila/experiments/phase4k-kc-recruitment-audit
reference_root: /data/digital-drosophila/models/male-cns-lif/reference-v1
runtime_root: /data/digital-drosophila/models/male-cns-lif/runtime-v1
io_root: /data/digital-drosophila/models/male-cns-lif/io-v1
phase4c_root: /data/digital-drosophila/experiments/phase4c-runtime
phase4d_root: /data/digital-drosophila/experiments/phase4d-io
phase4e_root: /data/digital-drosophila/experiments/phase4e-visual-calibration
phase4f_root: /data/digital-drosophila/experiments/phase4f-retinotopic-vision
phase4f_model_root: /data/digital-drosophila/models/male-cns-lif/visual-encoder-v1
phase4g_root: /data/digital-drosophila/experiments/phase4g-frozen-interface
phase4g_model_root: /data/digital-drosophila/models/male-cns-lif/frozen-interface-v1
phase4g_learned_root: /data/digital-drosophila/models/learned/phase4g
phase4h_root: /data/digital-drosophila/experiments/phase4h-dopamine-plasticity
phase4h_model_root: /data/digital-drosophila/models/male-cns-lif/dopamine-plasticity-v1
phase4h_learned_root: /data/digital-drosophila/models/learned/phase4h
phase4i_root: /data/digital-drosophila/experiments/phase4i-mb-mechanistic-validation
phase4i_model_root: /data/digital-drosophila/models/male-cns-lif/mb-mechanistic-v1
phase4j_root: /data/digital-drosophila/experiments/phase4j-mb-physiology-repair
phase4j_model_root: /data/digital-drosophila/models/male-cns-lif/mb-physiology-v1
names:
reference: MaleCNS-LIF-reference-v1
runtime: MaleCNS-LIF-runtime-v1
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, dopamine: 1, octopamine: 1, serotonin: 1, histamine: -1}
unknown_sign: 0
unclear_sign: 0
readout:
bins_ms: [1, 5, 10, 20, 50, 100]
device:
required_name_substring: RTX 4090
launcher_visible_gpu_uuid: GPU-79f3a85d-adce-7833-7003-6488140ca596
expected_graph:
neurons: 166700
directed_edges: 25582938
synaptic_contact_weight_sum: 124177617
phase4h_plastic_candidate_edges: 14232
plastic_mask_edges: 14232
apl_overlay:
name: APL-Graded-Inhibition-v1
tau_ms: 20.0
gain: 0.01
normalization: 1.0
immutable_phase4j_parameters: true
direct_kc:
target_compartment_verbatim: "y5B'2a"
matched_dan_type: PAM15
pulse_times_ms: [10.0, 30.0, 50.0, 70.0, 90.0]
dan_time_ms: 90.0
duration_ms: 120.0
trace_seed: 50201
evaluation_seed: 50301
olfactory:
rate_hz: 50.0
duration_ms: 100.0
seed: 50101
targets_per_cs: 64
sensitivity:
depression_fractions: [0.0, 0.01, 0.05, 0.10, 0.25, 0.50]
evaluation_seeds: [50301, 50302, 50303, 50304, 50305]
functional_change_definition: any_MBON_spike_count_or_first_latency_change
causal_attribution:
rule: dominant_positive_synaptic_arrival_with_runtime_g_decay
exact_delay_steps: 18
shortest_static_path_forbidden: true
prohibitions:
training: false
modify_eta_or_eligibility_v2: false
expand_plastic_mask: false
modify_threshold_or_gain: false
save_sensitivity_as_learned_model: false
decoder_classifier_rl_game_visual: false