#!/usr/bin/env python3 """Phase 4H stage 2: conservative dopamine-gated KC-to-MBON plasticity overlay.""" from __future__ import annotations import argparse import gc import hashlib import json import math import shutil import sys import time from collections import Counter from datetime import datetime, timezone from pathlib import Path from typing import Any import numpy as np import pandas as pd import pyarrow.parquet as pq import torch import yaml from brain_runtime import BrainRuntime, InputBatch, InputEvent from phase4h_audit import run_frozen_audit, sha256, tree_manifest, write_json def vector_counts(nodes: np.ndarray, members: np.ndarray, n: int) -> np.ndarray: lookup = np.full(n, -1, dtype=np.int32) lookup[members] = np.arange(len(members), dtype=np.int32) local = lookup[nodes] local = local[local >= 0] return np.bincount(local, minlength=len(members)).astype(np.int32) def cosine_distance(a: np.ndarray, b: np.ndarray) -> float: af = a.astype(np.float64); bf = b.astype(np.float64) denom = float(np.linalg.norm(af) * np.linalg.norm(bf)) return 0.0 if denom == 0 else float(1.0 - np.dot(af, bf) / denom) def separation(a: dict[str, Any], b: dict[str, Any], key: str) -> dict[str, float]: x = a[key].astype(np.float64); y = b[key].astype(np.float64) return { "euclidean": float(np.linalg.norm(x - y)), "cosine_distance": cosine_distance(x, y), "l1": float(np.abs(x - y).sum()), } def make_batch(targets: np.ndarray, rate_hz: float, duration_ms: float, name: str, dan_targets: np.ndarray | None = None, dan_start_ms: float = 80.0, dan_duration_ms: float = 20.0, dan_rate_hz: float = 100.0) -> tuple[InputBatch, float]: events = [InputEvent( target_neuron_idx=targets.astype(np.int64), stimulation_mode="spike_injection", value_kind="poisson_rate_hz", value=float(rate_hz), start_ms=0.0, duration_ms=float(duration_ms), source_id="phase4h-engineering-olfactory-CS", channel_id=name, metadata={"interpretation": "engineering olfactory CS interface"}, )] total_ms = duration_ms if dan_targets is not None and len(dan_targets): events.append(InputEvent( target_neuron_idx=dan_targets.astype(np.int64), stimulation_mode="spike_injection", value_kind="poisson_rate_hz", value=float(dan_rate_hz), start_ms=float(dan_start_ms), duration_ms=float(dan_duration_ms), source_id="phase4h-artificial-US-interface", channel_id="DAN-like-reinforcement", metadata={ "interpretation": "artificial unconditioned-signal interface", "not_equal_to": "measured dopamine concentration", }, )) total_ms = max(total_ms, dan_start_ms + dan_duration_ms) return InputBatch(events=tuple(events), batch_id=name, metadata={"phase": "4H"}), float(total_ms) class Experiment: def __init__(self, cfg: dict[str, Any], exp: Path): self.cfg = cfg; self.exp = exp self.rt = BrainRuntime(Path(__file__).with_name("phase4h.yaml"), exp / "compiler-reports/extension-build") self.base_weights = self.rt.weights.detach().clone() self.n = self.rt.n self.metadata = self.rt.table.to_pandas().sort_values("neuron_idx").reset_index(drop=True) self.classes = self.metadata["annotation_class"].fillna("").astype(str).to_numpy() self.superclass = self.metadata["annotation_superclass"].fillna("").astype(str).to_numpy() self.types = self.metadata["annotation_type"].fillna("").astype(str).to_numpy() self.nt = self.metadata["nt_consensus_nt"].fillna("unclear").astype(str).str.lower().to_numpy() self.kc = np.flatnonzero(self.classes == "Kenyon_Cell").astype(np.int32) self.mbon = np.flatnonzero(self.classes == "MBON").astype(np.int32) self.desc = self.rt.output_populations["descending"] self.motor = self.rt.output_populations["motor_related"] self.central = np.flatnonzero(np.char.startswith(self.superclass.astype(str), "cb_")).astype(np.int32) downstream = pq.read_table(exp / "mappings/mbon-to-downstream-actual-edges.parquet", columns=["target_neuron_idx"])["target_neuron_idx"].to_numpy() self.downstream = np.unique(downstream).astype(np.int32) self.candidates = pq.read_table(exp / "plastic-mask/plastic-edge-candidates.parquet").to_pandas() self.edge_ids = self.candidates["edge_id"].to_numpy(dtype=np.int64) self.base_candidate = self.base_weights[torch.as_tensor(self.edge_ids, device=self.rt.device)].cpu().numpy() self.sign_policy = cfg["sign_policy"]["neurotransmitter_sign"] olfactory = np.flatnonzero(self.rt.population_masks["olfactory"]).astype(np.int32) take = int(cfg["conditioning"]["cs_targets_per_identity"]) if len(olfactory) < 2 * take: take = len(olfactory) // 2 if take == 0: raise RuntimeError("olfactory population is too small for two disjoint CS identities") olfactory.sort(); self.cs_a = olfactory[:take]; self.cs_b = olfactory[take:2 * take] if np.intersect1d(self.cs_a, self.cs_b).size: raise RuntimeError("CS identities overlap") self.dan_targets = {} for label in ("appetitive", "aversive"): dtype = str(cfg["conditioning"][f"{label}_dan_type_required"]) comp = str(cfg["conditioning"][f"{label}_compartment_verbatim"]) rows = self.candidates[self.candidates["target_compartment_verbatim"] == comp] confirmed = pq.read_table(exp / "mappings/dan-to-mbon-confirmed.parquet").to_pandas() sources = confirmed[(confirmed["source_annotation_type"] == dtype) & (confirmed["source_compartment_verbatim"] == comp)]["source_neuron_idx"].unique() if rows.empty or len(sources) == 0: raise RuntimeError(f"required exact DAN/compartment mapping absent: {label} {dtype} {comp}") self.dan_targets[label] = np.sort(sources.astype(np.int32)) def install_delta(self, delta: np.ndarray) -> None: if delta.shape != (len(self.edge_ids),): raise ValueError("plastic delta shape mismatch") self.rt.weights.copy_(self.base_weights) ids = torch.as_tensor(self.edge_ids, dtype=torch.int64, device=self.rt.device) values = torch.as_tensor(self.base_candidate + delta, dtype=torch.float32, device=self.rt.device) self.rt.weights[ids] = values def evaluate(self, cs_name: str, seed: int, delta: np.ndarray) -> dict[str, Any]: self.install_delta(delta); self.rt.reset(seed) targets = self.cs_a if cs_name == "CS-A" else self.cs_b batch, duration = make_batch(targets, self.cfg["conditioning"]["cs_rate_hz"], self.cfg["conditioning"]["cs_duration_ms"], f"eval-{cs_name}") t0 = time.perf_counter(); self.rt.step(batch, duration); wall = time.perf_counter() - t0 events = self.rt.read_spikes(); nodes = events["neuron_idx"].astype(np.int32) origins = events["origin"] vectors = { "kc": vector_counts(nodes, self.kc, self.n), "mbon": vector_counts(nodes, self.mbon, self.n), "downstream": vector_counts(nodes, self.downstream, self.n), "central": vector_counts(nodes, self.central, self.n), "descending": vector_counts(nodes, self.desc, self.n), "motor": vector_counts(nodes, self.motor, self.n), } mbon_nt = self.nt[self.mbon] positive_nt = {key for key, value in self.sign_policy.items() if int(value) > 0} negative_nt = {key for key, value in self.sign_policy.items() if int(value) < 0} mvec = vectors["mbon"] pos = int(mvec[np.isin(mbon_nt, list(positive_nt))].sum()) neg = int(mvec[np.isin(mbon_nt, list(negative_nt))].sum()) finite = bool(torch.isfinite(self.rt.v).all().item() and torch.isfinite(self.rt.g).all().item()) active = int(np.unique(nodes).size) return { "seed": int(seed), "CS": cs_name, "wall_seconds": wall, "total_spikes": int(len(nodes)), "network_spikes": int(np.count_nonzero(origins == "network_spike")), "active_neurons": active, "active_fraction": float(active / self.n), "finite": finite, "runaway": bool(active > 0.5 * self.n), "population_spikes": {key: int(value.sum()) for key, value in vectors.items()}, "mbon_balance": { "excitatory_transmitter_spikes": pos, "inhibitory_transmitter_spikes": neg, "signed_transmitter_balance": pos - neg, "interpretation": "transmitter-stratified MBON activity; not an assigned approach/avoidance valence", }, **{f"_{key}_vector": value for key, value in vectors.items()}, } def update(self, cs_name: str, seed: int, delta: np.ndarray, compartment: str | None, dan_label: str | None, delay_ms: float, enabled: bool, d_value: float) -> tuple[np.ndarray, dict[str, Any]]: self.install_delta(delta); self.rt.reset(seed) targets = self.cs_a if cs_name == "CS-A" else self.cs_b dan = self.dan_targets[dan_label] if dan_label else None dan_start = self.cfg["conditioning"]["cs_duration_ms"] + delay_ms if delay_ms > 0 else 80.0 batch, duration = make_batch(targets, self.cfg["conditioning"]["cs_rate_hz"], self.cfg["conditioning"]["cs_duration_ms"], f"train-{cs_name}", dan_targets=dan, dan_start_ms=dan_start) self.rt.step(batch, duration) nodes = self.rt.read_spikes()["neuron_idx"].astype(np.int32) kc_counts = np.bincount(nodes[np.isin(nodes, self.kc)], minlength=self.n) pre = self.candidates["source_neuron_idx"].to_numpy(dtype=np.int32) eligible = np.minimum(kc_counts[pre].astype(np.float32), 1.0) if delay_ms > 0: eligible *= math.exp(-delay_ms / float(self.cfg["plasticity"]["eligibility_tau_ms"])) comp_mask = (self.candidates["target_compartment_verbatim"].astype(str).to_numpy() == str(compartment)) if compartment else np.zeros(len(delta), bool) update = np.zeros_like(delta) if enabled and d_value != 0.0 and compartment is not None: polarity = float(self.cfg["plasticity"]["default_confirmed_compartment_polarity"]) update[comp_mask] = polarity * float(self.cfg["plasticity"]["eta_effective_mv"]) * float(d_value) * eligible[comp_mask] out = delta + update lower = -float(self.cfg["plasticity"]["max_fractional_depression_per_edge"]) * self.base_candidate out = np.maximum(out, lower).astype(np.float32) if np.any(self.base_candidate + out < -1e-7): raise RuntimeError("plastic overlay made an excitatory KC edge negative") record = { "CS": cs_name, "seed": int(seed), "compartment_verbatim": compartment, "DAN_label": dan_label, "DAN_neuron_count": 0 if dan is None else int(len(dan)), "D_value": float(d_value), "D_interpretation": "artificial US interface scalar, not dopamine concentration", "delay_ms": float(delay_ms), "plasticity_enabled": bool(enabled), "eligible_candidate_edges": int(np.count_nonzero(eligible * comp_mask)), "updated_edges_this_trial": int(np.count_nonzero(update)), "delta_l1_effective_mv": float(np.abs(out).sum()), "delta_min_effective_mv": float(out.min(initial=0.0)), "delta_max_effective_mv": float(out.max(initial=0.0)), } return out, record def public_eval(value: dict[str, Any]) -> dict[str, Any]: return {key: item for key, item in value.items() if not key.startswith("_")} def eval_pair(experiment: Experiment, seed: int, delta: np.ndarray) -> dict[str, Any]: a = experiment.evaluate("CS-A", seed, delta) b = experiment.evaluate("CS-B", seed, delta) return { "CS-A": public_eval(a), "CS-B": public_eval(b), "mbon_separation": separation(a, b, "_mbon_vector"), "downstream_separation": separation(a, b, "_downstream_vector"), "descending_separation": separation(a, b, "_descending_vector"), "motor_separation": separation(a, b, "_motor_vector"), "balance_difference_A_minus_B": int(a["mbon_balance"]["signed_transmitter_balance"] - b["mbon_balance"]["signed_transmitter_balance"]), } def protocol_plan(name: str, cfg: dict[str, Any], reliable: list[str], rng: np.random.Generator) -> list[dict[str, Any]]: app = str(cfg["conditioning"]["appetitive_compartment_verbatim"]) av = str(cfg["conditioning"]["aversive_compartment_verbatim"]) trials = int(cfg["plasticity"]["training_trials"]) base = {"delay_ms": 0.0, "enabled": True, "d_value": float(cfg["plasticity"]["d_magnitude"])} if name == "appetitive-conditioning": return [base | {"CS": "CS-A", "compartment": app, "dan_label": "appetitive"} for _ in range(trials)] if name == "aversive-conditioning": return [base | {"CS": "CS-A", "compartment": av, "dan_label": "aversive"} for _ in range(trials)] if name == "delayed-reward": return [base | {"CS": "CS-A", "compartment": app, "dan_label": "appetitive", "delay_ms": float(cfg["conditioning"]["delayed_reward_ms"])} for _ in range(trials)] if name == "retention": return [base | {"CS": "CS-A", "compartment": app, "dan_label": "appetitive"} for _ in range(trials)] if name == "d-zero-control": return [base | {"CS": "CS-A", "compartment": app, "dan_label": None, "d_value": 0.0} for _ in range(trials)] if name == "frozen-brain-control": return [base | {"CS": "CS-A", "compartment": app, "dan_label": "appetitive", "enabled": False} for _ in range(trials)] if name == "random-reward-control": plan = [] for _ in range(trials): comp = str(rng.choice(reliable)) label = "appetitive" if comp == app else ("aversive" if comp == av else None) plan.append(base | {"CS": str(rng.choice(["CS-A", "CS-B"])), "compartment": comp, "dan_label": label}) return plan if name == "reversal-learning": first = [base | {"CS": "CS-A", "compartment": app, "dan_label": "appetitive", "phase": "acquisition"} for _ in range(trials)] second = [base | {"CS": "CS-B", "compartment": app, "dan_label": "appetitive", "phase": "reversal"} for _ in range(trials)] return first + second raise KeyError(name) def phase4c_regression(cfg: dict[str, Any], exp: Path) -> dict[str, Any]: rt = BrainRuntime(Path(__file__).with_name("phase4h.yaml"), exp / "compiler-reports/extension-build") specs = [ ("auditory-10hz", "auditory", 10.0, 42001), ("auditory-100hz", "auditory", 100.0, 42001), ("olfactory-50hz", "olfactory", 50.0, 42003), ("mechanosensory-50hz", "mechanosensory", 50.0, 42003), ("gustatory-50hz", "gustatory", 50.0, 42003), ] results = {} root = Path(cfg["paths"]["phase4c_root"]) / "regression/reference" for name, population, rate, seed in specs: targets = np.flatnonzero(rt.population_masks[population]).astype(np.int64) batch, duration = make_batch(targets, rate, 1000.0, f"phase4c-regression-{name}") rt.reset(seed); rt.step(batch, duration) events = rt.read_spikes(); steps = events["step"].astype(np.int32); nodes = events["neuron_idx"].astype(np.int32) ref = pq.read_table(root / name / "spike-events.parquet") ref_steps = ref["step"].to_numpy().astype(np.int32); ref_nodes = ref["neuron_idx"].to_numpy().astype(np.int32) counts = rt.spike_counts.cpu().numpy().astype(np.int32) ref_counts = np.load(root / name / "per-neuron-spike-counts.int32.npy") v = rt.v.cpu().numpy(); g = rt.g.cpu().numpy() ref_v = np.load(root / name / "final-voltage.float32.npy"); ref_g = np.load(root / name / "final-g.float32.npy") results[name] = { "spike_events_exact": bool(np.array_equal(steps, ref_steps) and np.array_equal(nodes, ref_nodes)), "per_neuron_counts_exact": bool(np.array_equal(counts, ref_counts)), "max_abs_voltage_error": float(np.max(np.abs(v - ref_v))), "max_abs_g_error": float(np.max(np.abs(g - ref_g))), "event_count": int(len(nodes)), } results["all_passed"] = bool(all(v["spike_events_exact"] and v["per_neuron_counts_exact"] and v["max_abs_voltage_error"] <= 0.00390625 and v["max_abs_g_error"] <= 0.00390625 for k, v in results.items() if k != "all_passed")) write_json(exp / "regression/phase4c-reference-replay.json", results) del rt; gc.collect(); torch.cuda.empty_cache() return results def seal(root: Path, manifest_path: Path, checksum_path: Path) -> None: manifest = tree_manifest(root, {manifest_path, checksum_path}) write_json(manifest_path, manifest) targets = sorted(path for path in root.rglob("*") if path.is_file() and path != checksum_path) checksum_path.write_text("".join(f"{sha256(path)} {path.relative_to(root)}\n" for path in targets), encoding="utf-8") 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(encoding="utf-8")) exp = Path(cfg["paths"]["experiment_root"]); learned = Path(cfg["paths"]["learned_root"]); model = Path(cfg["paths"]["model_root"]) for sub in ("conditioning", "controls", "plasticity", "metrics", "regression", "audit", "manifests", "compiler-reports/extension-build"): (exp / sub).mkdir(parents=True, exist_ok=True) for sub in ("plastic-delta", "training-metadata"): (learned / sub).mkdir(parents=True, exist_ok=True) model.mkdir(parents=True, exist_ok=True) gate_path = model / "STAGE1_AUDIT_GATE.json" if not gate_path.is_file(): raise RuntimeError("stage-1 audit gate missing") gate = json.loads(gate_path.read_text(encoding="utf-8")) if gate.get("status") != "PASS-AUDIT" or not gate.get("stage2_authorized"): raise RuntimeError(f"stage-1 audit did not authorize conditioning: {gate}") report_path = Path(gate["audit_report"]) if sha256(report_path) != gate["audit_report_sha256"]: raise RuntimeError("stage-1 audit report changed after gate creation") stage1 = json.loads(report_path.read_text(encoding="utf-8")) reliable = stage1["reliable_mb_compartments"]["verbatim_values"] frozen_before = run_frozen_audit(cfg) write_json(exp / "audit/frozen-checksums-before-stage2.json", frozen_before) shutil.copy2(args.config, exp / "audit/phase4h-stage2-config.yaml") shutil.copy2(Path(__file__).resolve(), exp / "audit/phase4h.py") experiment = Experiment(cfg, exp) write_json(exp / "audit/conditioning-interface.json", { "CS_A_neuron_idx": experiment.cs_a.tolist(), "CS_B_neuron_idx": experiment.cs_b.tolist(), "CS_populations_are_disjoint": True, "CS_identity": "engineering split of sorted olfactory annotation_class population", "DAN_targets": {key: value.tolist() for key, value in experiment.dan_targets.items()}, "reward_interface": cfg["conditioning"]["reward_interface_label"], "reward_value_is_dopamine_concentration": False, }) all_runs = [] finite_all = True; no_runaway = True for protocol in cfg["conditioning"]["protocols"]: protocol_dir = exp / ("controls" if "control" in protocol else "conditioning") / protocol protocol_dir.mkdir(parents=True, exist_ok=True) for train_seed, eval_seed in zip(cfg["conditioning"]["train_seeds"], cfg["conditioning"]["evaluation_seeds"]): rng = np.random.default_rng(int(train_seed)) delta = np.zeros(len(experiment.edge_ids), dtype=np.float32) pre = eval_pair(experiment, int(eval_seed), delta) plan = protocol_plan(protocol, cfg, reliable, rng) updates = []; trajectory = [] acquisition_snapshot = None for trial_idx, item in enumerate(plan): delta, update = experiment.update(item["CS"], int(train_seed) + trial_idx, delta, item["compartment"], item["dan_label"], item["delay_ms"], item["enabled"], item["d_value"]) update["trial"] = trial_idx; update["phase"] = item.get("phase", "acquisition") updates.append(update) pair = eval_pair(experiment, int(eval_seed), delta) trajectory.append({"trial": trial_idx, "phase": update["phase"], "mbon_separation": pair["mbon_separation"], "downstream_separation": pair["downstream_separation"], "balance_difference_A_minus_B": pair["balance_difference_A_minus_B"]}) if protocol == "reversal-learning" and update["phase"] == "acquisition": acquisition_snapshot = pair post = eval_pair(experiment, int(eval_seed), delta) retention = [] if protocol == "retention": for nominal_ms, extra_seed in zip((0, 1000, 10000), cfg["conditioning"]["evaluation_seeds"][:3]): pair = eval_pair(experiment, int(extra_seed), delta) retention.append({"nominal_retention_interval_ms": nominal_ms, "state_policy": "fresh neural state; persistent plastic_delta; no idle dynamics approximation", "mbon_separation": pair["mbon_separation"], "downstream_separation": pair["downstream_separation"]}) run = { "protocol": protocol, "train_seed": int(train_seed), "evaluation_seed": int(eval_seed), "pre": pre, "post": post, "updates": updates, "trajectory": trajectory, "retention": retention, "mbon_euclidean_gain": post["mbon_separation"]["euclidean"] - pre["mbon_separation"]["euclidean"], "downstream_euclidean_gain": post["downstream_separation"]["euclidean"] - pre["downstream_separation"]["euclidean"], "reversal_balance_sign_changed": ( False if acquisition_snapshot is None else bool(np.sign(acquisition_snapshot["balance_difference_A_minus_B"]) != np.sign(post["balance_difference_A_minus_B"])) ), "changed_edges": int(np.count_nonzero(delta)), "delta_l1_effective_mv": float(np.abs(delta).sum()), "base_CSR_modified": False, } run_path = protocol_dir / f"train-{train_seed}-eval-{eval_seed}.json" write_json(run_path, run) np.savez_compressed(learned / "plastic-delta" / f"{protocol}-seed-{train_seed}.npz", edge_id=experiment.edge_ids, plastic_delta_effective_mv=delta) finite_all = finite_all and all(x["finite"] for x in (post["CS-A"], post["CS-B"])) no_runaway = no_runaway and not any(x["runaway"] for x in (post["CS-A"], post["CS-B"])) all_runs.append(run) del experiment; gc.collect(); torch.cuda.empty_cache() regression = phase4c_regression(cfg, exp) frozen_after = run_frozen_audit(cfg) write_json(exp / "audit/frozen-checksums-after-stage2.json", frozen_after) by_protocol = {} for protocol in cfg["conditioning"]["protocols"]: rows = [row for row in all_runs if row["protocol"] == protocol] by_protocol[protocol] = { "seeds": len(rows), "mbon_gain_mean": float(np.mean([row["mbon_euclidean_gain"] for row in rows])), "mbon_gain_positive_seeds": int(sum(row["mbon_euclidean_gain"] > 0 for row in rows)), "downstream_gain_mean": float(np.mean([row["downstream_euclidean_gain"] for row in rows])), "downstream_gain_positive_seeds": int(sum(row["downstream_euclidean_gain"] > 0 for row in rows)), "changed_edges_mean": float(np.mean([row["changed_edges"] for row in rows])), "delta_l1_effective_mv_mean": float(np.mean([row["delta_l1_effective_mv"] for row in rows])), "reversal_signature_seeds": int(sum(row["reversal_balance_sign_changed"] for row in rows)), } majority = int(cfg["success_gate"]["require_minimum_seed_majority"]) app = by_protocol["appetitive-conditioning"]; av = by_protocol["aversive-conditioning"] real_mbon = app["mbon_gain_positive_seeds"] >= majority and av["mbon_gain_positive_seeds"] >= majority real_downstream = app["downstream_gain_positive_seeds"] >= majority and av["downstream_gain_positive_seeds"] >= majority reversal = by_protocol["reversal-learning"]["reversal_signature_seeds"] >= majority retention_rows = [row for row in all_runs if row["protocol"] == "retention"] retention = sum(bool(row["retention"]) and row["retention"][-1]["mbon_separation"]["euclidean"] > row["pre"]["mbon_separation"]["euclidean"] for row in retention_rows) >= majority control_ceiling = max(by_protocol[name]["mbon_gain_mean"] for name in ("d-zero-control", "random-reward-control", "frozen-brain-control")) condition_floor = min(app["mbon_gain_mean"], av["mbon_gain_mean"]) controls_distinct = condition_floor > control_ceiling passed = all([frozen_before["all_passed"], frozen_after["all_passed"], regression["all_passed"], finite_all, no_runaway, real_mbon, real_downstream, reversal, retention, controls_distinct]) status = "MaleCNS-DopaminePlasticity-v1-candidate" if passed else "FAIL-NO-DOPAMINE-PLASTICITY-CANDIDATE" summary = { "status": status, "candidate_generated": passed, "stage1_status": stage1["status"], "mapping": { "KC": stage1["population_counts"]["KC"], "MBON": stage1["population_counts"]["MBON"], "PAM": stage1["population_counts"]["PAM"], "PPL1": stage1["population_counts"]["PPL1"], "other_DAN": stage1["population_counts"]["other_DAN"], "reliable_compartments": reliable, "plastic_candidate_edges": stage1["plastic_edge_candidate_mask"]["edges"], }, "protocols": by_protocol, "gate": { "all_frozen_checksums_pass": frozen_before["all_passed"] and frozen_after["all_passed"], "phase4c_reference_regression_pass": regression["all_passed"], "finite": finite_all, "no_runaway": no_runaway, "appetitive_and_aversive_MBON_separation_majority": real_mbon, "appetitive_and_aversive_downstream_separation_majority": real_downstream, "reversal_signature_majority": reversal, "retention_signature_majority": retention, "controls_distinct": controls_distinct, "weight_change_alone_used_as_success": False, }, "base_brain": {"modified_on_disk": False, "runtime_base_restored_between_runs": True, "equation": cfg["plasticity"]["overlay_equation"]}, "reward_interface": {"artificial_unconditioned_signal": True, "dopamine_concentration_claimed": False}, "limitations": [ "CS-A and CS-B are reproducible engineering subsets of the annotated olfactory population, not identified odor receptor channels.", "Compartment membership is based on exact verbatim annotation_instance tokens and direct neuron-level CSR edges; synapse-level ROI is unavailable.", "MBON balance is transmitter-stratified and is not relabeled as approach/avoidance without a reliable functional annotation.", "Retention uses a fresh neural state with persistent plastic_delta and does not simulate a long idle biological interval.", ], } write_json(exp / "audit/stage2-summary.json", summary) write_json(model / "VERSION.json", { "status": status, "candidate_name": status if passed else None, "base_model": cfg["names"]["reference"], "runtime": cfg["names"]["runtime"], "plasticity_overlay_only": True, "stage1_gate_sha256": sha256(gate_path), "summary_sha256": sha256(exp / "audit/stage2-summary.json"), }) shutil.copy2(args.config, model / "phase4h.yaml") shutil.copy2(Path(__file__).resolve(), model / "phase4h.py") write_json(learned / "training-metadata/summary.json", {"status": status, "protocols": by_protocol, "candidate": passed}) print(json.dumps(summary, indent=2, sort_keys=True)) sys.stdout.flush() # Flush redirected stdout before hashing so a log inside the experiment tree # cannot change after its checksum has been recorded. seal(model, model / "MANIFEST.json", model / "checksums.sha256") seal(learned, learned / "MANIFEST.json", learned / "checksums.sha256") seal(exp, exp / "manifests/manifest.json", exp / "manifests/checksums.sha256") if __name__ == "__main__": main()