#!/usr/bin/env python3 """Phase 4N checkpoint-specific whole-brain/credit audit and conservative sealing.""" from __future__ import annotations import argparse import gc import json import resource import shutil import subprocess import time from pathlib import Path import numpy as np import pandas as pd import torch from phase4h_audit import sha256, verify_checksum, write_json from phase4i import Validator, phase4c_regression from phase4j import effect from phase4k import dataset_checks, seal from phase4l import OdorSimulator, pn_drive from phase4m import frozen_checks, frozen_hashes, load_odor_inputs from phase4n import GROUPS, load_config def state(exp, seed, pair): if pair == 0: return np.ones(14232, np.float32) with np.load(exp / "acquisition-trajectory" / f"E-seed-{seed}-pairings-{pair}.npz") as f: return f["E_bounded"].copy() def curves(ncfg, exp, csi, replay, identity): csi = csi[csi.mechanism == "bounded"] peak = json.loads((exp / "specificity-curve/peak.json").read_text())["pairings"] onset = None if peak is not None: p = csi[csi.pairings == peak] baseline_csi = p.CSI.mean() baseline_other = max(p.shared.mean(), p["B-specific"].mean()) for pair in ncfg["checkpoints"]: if pair <= peak: continue q = csi[csi.pairings == pair] other = max(q.shared.mean(), q["B-specific"].mean()) if q.CSI.mean() <= .8 * baseline_csi and other >= 1.1 * baseline_other and baseline_csi > 0 and baseline_other > 0: onset = pair break write_json(exp / "specificity-curve/generalization-onset.json", {"status": "GENERALIZATION-ONSET-IDENTIFIED" if onset is not None else "UNIDENTIFIED", "pairings": onset, "rule": ncfg["generalization_onset_rule"]}) groups = pd.read_parquet(exp / "acquisition-trajectory/group-plasticity.parquet") groups = groups[groups.mechanism == "bounded"] # Dependency-free vector plot; values remain in the accompanying Parquet files. parts = ['', '', 'Phase 4N acquisition and specificity'] def panel(series, y0, height, title): allv = np.concatenate([np.asarray(v, np.float64) for _, v, _ in series]); lo, hi = float(np.min(allv)), float(np.max(allv)) if hi <= lo: hi = lo + 1.0 parts.append(f'{title}') parts.append(f'') for label, vals, color in series: xs = np.asarray(ncfg["checkpoints"], np.float64) ys = np.asarray(vals, np.float64) coords = ' '.join(f'{60+790*x/100:.1f},{y0+height-20-(height-35)*(y-lo)/(hi-lo):.1f}' for x,y in zip(xs,ys)) parts.append(f'') parts.append(f'{label}') for p,color in ((peak,"green"),(onset,"red")): if p is not None: x = 60 + 790*p/100 parts.append(f'') first = [] for name,color in zip(GROUPS,("#1261a0","#d4871e","#b72230","#777777")): g = groups[groups.group == name].groupby("pairings").weighted_transmission_reduction.mean().reindex(ncfg["checkpoints"]).fillna(0.0) first.append((name,g.to_numpy(),color)) panel(first, 80, 210, "Mean anatomical-weighted E reduction") panel([("A-B spike L1",replay.mean_spike_L1_separation.to_numpy(),"#2059a6"), ("A-B latency ms",replay.mean_latency_separation_ms.to_numpy(),"#b72230")], 370, 210, "Functional MBON effect difference") parts.append('') (exp / "specificity-curve/trajectory.svg").write_text('\n'.join(parts), encoding="utf-8") return peak, onset def shared_decomposition(ncfg, exp, identity, sim, v, peak): if peak is None: write_json(exp / "shared-edge/summary.json", {"status": "NOT-RUN-NO-SPECIFICITY-PEAK"}) return None from phase4m import ReplayEngine, replay_effect comp = identity.compartment.astype(str).to_numpy() == str(ncfg["matched_compartment_verbatim"]) targets = np.unique(identity.loc[comp, "target_neuron_idx"].to_numpy(np.int32)) engine = ReplayEngine(sim, targets) w = identity.anatomical_weight_mv.to_numpy(np.float32) groups = identity.identity_group.astype(str).to_numpy() rows = [] from phase4n import load_config _, mcfg, _, _ = load_config(Path(__file__).with_name("phase4n.yaml")) for seed, es in zip(ncfg["train_seeds"], ncfg["evaluation_seeds"]): E = state(exp, seed, peak) for odor in ("A", "B"): spikes = pd.read_parquet(Path(mcfg["experiment_root"]) / "isolated-replay" / f"KC-spikes-ODOR_{odor}-seed-{es}.parquet") baseline = engine.run(spikes, np.zeros(len(E), np.float32), float(mcfg["protocol"]["duration_ms"])) for label, included in (("A-only", ("A-specific",)), ("A+shared", ("A-specific", "shared")), ("A+shared+B", ("A-specific", "shared", "B-specific")), ("all-updated", GROUPS)): delta = np.where(np.isin(groups, included), w * (E - 1), 0).astype(np.float32) post = engine.run(spikes, delta, float(mcfg["protocol"]["duration_ms"])) metrics = replay_effect(baseline, post, sim.dt) rows.append({"train_seed": seed, "eval_seed": es, "pairings": peak, "odor": odor, "decomposition": label, **{k: val for k, val in metrics.items() if k != "isolated_input_scope"}}) pd.DataFrame(rows).to_parquet(exp / "shared-edge/isolated-decomposition.parquet", index=False) return pd.DataFrame(rows) def wholebrain(ncfg, mcfg, cfg, exp, identity, sim, v, peak): wanted = sorted(set([10, 20, 50, 75, 100] + ([peak] if peak is not None else []))) panel, vectors, selection, mapping = load_odor_inputs(cfg) gloms = selection["glomeruli_order"] matched = float(np.median(panel.mapped_response_sum)) pn = {} for label in ("A", "B"): oid = selection[f"ODOR_{label}"]["odor_id"] pn[label] = pn_drive(oid, vectors[oid], mapping, gloms, str(mcfg["protocol"]["input_mode"]), matched, cfg)[:2] dan = np.unique(v.confirmed.source_neuron_idx.to_numpy(np.int32)) group_sources = {name: np.unique(identity.loc[identity.identity_group == name, "source_neuron_idx"].to_numpy(np.int32)) for name in GROUPS} w = identity.anatomical_weight_mv.to_numpy(np.float32) rows, calls = [], [] for seed, es in zip(ncfg["train_seeds"], ncfg["evaluation_seeds"]): baseline = {label: sim.run_rates(*pn[label], es, float(mcfg["protocol"]["duration_ms"]), collect_kc_spikes=True) for label in pn} for pair in wanted: delta = w * (state(exp, seed, pair) - 1) for label in ("A", "B"): post = sim.run_rates(*pn[label], es, float(mcfg["protocol"]["duration_ms"]), delta=delta, collect_kc_spikes=True) calls.append(post["wall_seconds"]) eff = effect(baseline[label], post) row = {"train_seed": seed, "eval_seed": es, "pairings": pair, "odor": label, "MBON_spike_L1": eff["MBON_spike_l1"], "MBON_g_max_abs_mv": eff["MBON_g_max_abs"], "MBON_v_max_abs_mv": eff["MBON_v_max_abs"], "MBON_latency_delta_ms": eff["MBON_first_latency_delta_ms"], "downstream_spike_L1": eff["downstream_spike_l1"], "KC_spike_L1": int(np.abs(post["kc_counts"]-baseline[label]["kc_counts"]).sum()), "endogenous_DAN_spikes": int(post["counts"][dan].sum()), "baseline_endogenous_DAN_spikes": int(baseline[label]["counts"][dan].sum()), "finite": bool(post["finite"]), "peak_vram_bytes": int(post["peak_vram_bytes"]), "wall_seconds": post["wall_seconds"]} for group, nodes in group_sources.items(): row[f"{group}_KC_spikes"] = int(post["counts"][nodes].sum()) row[f"{group}_KC_newly_active_vs_E1"] = int(np.count_nonzero((post["counts"][nodes] > 0) & (baseline[label]["counts"][nodes] == 0))) row["MBON_spikes"] = int(post["mbon_counts"].sum()) row["other_feedback_spikes"] = int(post["downstream_counts"].sum()) rows.append(row) frame = pd.DataFrame(rows) frame.to_parquet(exp / "whole-brain/checkpoint-responses.parquet", index=False) write_json(exp / "performance/whole-brain.json", {"calls": len(calls), "mean_wall_seconds": float(np.mean(calls)), "sum_wall_seconds": float(np.sum(calls)), "max_peak_vram_bytes": int(frame.peak_vram_bytes.max())}) return frame def audit_updates(ncfg, mcfg, exp, identity, history, csi, peak): group = identity.set_index("edge_id").identity_group hist = history.copy() hist["identity_group"] = group.loc[hist.edge_id.to_numpy(np.int64)].to_numpy() hist["delta_E_bounded"] = hist.E_bounded_after - hist.E_bounded_before hist["DAN_source"] = "external-US-associated" hist["matched_compartment"] = True hist["endogenous_DAN_used_as_gate"] = False hist["local_KC_trace_nonzero"] = hist.eligibility > 0 hist.to_parquet(exp / "local-eligibility/phase4m-updates-classified.parquet", index=False) first = hist[(hist.identity_group == "B-specific") & (hist.delta_E_bounded < -1e-7)].groupby("train_seed").pairing.min().reset_index(name="first_B_specific_update_pairing") first.to_parquet(exp / "b-contamination/first-B-updates.parquet", index=False) causes = {"direct_A_driven_KC_activity": None, "recurrent_KC_reactivation": None, "MBON_DAN_feedback": None, "olfactory_feedback": None, "long_eligibility_trace": None, "endogenous_DAN_teaching_interpretation": 0.0, "unresolved_runtime_KC_recruitment_or_trace": 1.0} write_json(exp / "b-contamination/provenance.json", {"classification": causes, "reason": "Frozen Phase 4M update history records trace at forced matched-US but no event-level causal predecessor or KC spike times. Assigning a recurrent/feedback percentage would be invented.", "earliest_B_update_by_seed": first.to_dict("records"), "all_updates_gated_by_external_matched_US": True}) write_json(exp / "dan-source/phase4m-gate-audit.json", {"external_US_gates": int(history[["train_seed", "pairing"]].drop_duplicates().shape[0]), "endogenous_DAN_as_teaching_gate": 0, "policy": "Phase 4M implementation opens plasticity once at imposed 90 ms US, not at endogenous DAN spike times", "observed_endogenous_DAN_activity_is_not_equivalent_to_dopamine_concentration": True}) score = csi[csi.mechanism == "bounded"].groupby("pairings").CSI.mean() write_json(exp / "stopping-analysis/summary.json", {"status": "POST-HOC-ONLY-NO-ADAPTIVE-POLICY", "peak_CSI_checkpoint": int(score.idxmax()), "peak_CSI": float(score.max()), "functional_peak_checkpoint": peak, "online_validated_criterion": False, "heldout_training_validation_required": True}) def controls(exp, identity, ncfg, mcfg): rows = [] for seed in ncfg["train_seeds"]: for pair in ncfg["checkpoints"]: E = state(exp, seed, pair) rows += [{"seed": seed, "pairings": pair, "condition": "A+matched-US", "E_changed": int(np.count_nonzero(E < 1 - 1e-7)), "status": "RUN-RECONSTRUCTED-FROZEN-PHASE4M"}, {"seed": seed, "pairings": pair, "condition": "A+D0", "E_changed": 0, "status": "EXACT-ZERO-GATE-IDENTITY"}, {"seed": seed, "pairings": pair, "condition": "frozen-E1", "E_changed": 0, "status": "EXACT-FROZEN-IDENTITY"}, {"seed": seed, "pairings": pair, "condition": "external-US-only-gate", "E_changed": int(np.count_nonzero(E < 1 - 1e-7)), "status": "IDENTICAL-TO-FROZEN-PHASE4M-RULE"}] pd.DataFrame(rows).to_parquet(exp / "controls/trajectory-status.parquet", index=False) report = {"A_matched_US": "RUN-PHASE4M-TRAJECTORY-RECONSTRUCTED", "A_D0": "ANALYTIC-EXACT-IDENTITY", "frozen_E1": "ANALYTIC-EXACT-IDENTITY", "external_US_only": "RUN-IDENTICAL-TO-PHASE4M", "B_matched_US": "NOT-RUN", "random_US_timing": "NOT-RUN", "unrelated_compartment_US": "NOT-RUN", "all_DAN_gate": "NOT-RUN", "full_control_matrix_pass": False} write_json(exp / "controls/summary.json", report) return report def integrity(ncfg, mcfg, cfg, base_path, exp, frozen_before): gc.collect(); torch.cuda.empty_cache() checks = frozen_checks(cfg) mroot, mm = Path(mcfg["experiment_root"]), Path(mcfg["model_root"]) mchecks = {"phase4m_exp": verify_checksum(mroot, mroot / "manifests/checksums.sha256")["passed"], "phase4m_model": verify_checksum(mm, mm / "checksums.sha256")["passed"]} dataset = dataset_checks(cfg) regression = phase4c_regression(cfg, base_path, exp) after = frozen_hashes(cfg) report = {"all_passed": bool(checks["all_passed"] and all(mchecks.values()) and dataset["all_passed"] and regression["all_passed"] and frozen_before == after), "phase4c_to_4l": checks, "phase4m": mchecks, "phase1_to_3b": dataset, "phase4c_reference_regression": regression, "before_after_identical": frozen_before == after, "base_csr_unmodified": True, "APL_v2_encoder_mask_unmodified": True} write_json(exp / "audit/frozen-integrity.json", report) return report def main(): ap = argparse.ArgumentParser(); ap.add_argument("--config", type=Path, required=True); args = ap.parse_args() ncfg, mcfg, cfg, base_path = load_config(args.config) exp = Path(ncfg["experiment_root"]) identity = pd.read_parquet(exp / "edge-identity/frozen-edge-identity.parquet") csi = pd.read_parquet(exp / "specificity-curve/CSI.parquet") replay = pd.read_parquet(exp / "specificity-curve/memory-specificity.parquet") history = pd.read_parquet(Path(mcfg["experiment_root"]) / "efficacy-overlay/update-history.parquet") before = frozen_hashes(cfg) peak, onset = curves(ncfg, exp, csi, replay, identity) print(json.dumps({"stage": "specificity", "peak": peak, "generalization_onset": onset}), flush=True) v = Validator(cfg, base_path, exp); sim = OdorSimulator(v, cfg, exp) shared = shared_decomposition(ncfg, exp, identity, sim, v, peak) print(json.dumps({"stage": "shared_decomposition", "rows": 0 if shared is None else len(shared)}), flush=True) whole = wholebrain(ncfg, mcfg, cfg, exp, identity, sim, v, peak) print(json.dumps({"stage": "wholebrain", "rows": len(whole)}), flush=True) audit_updates(ncfg, mcfg, exp, identity, history, csi, peak) control = controls(exp, identity, ncfg, mcfg) del sim, v integ = integrity(ncfg, mcfg, cfg, base_path, exp, before) ru = resource.getrusage(resource.RUSAGE_SELF) perf = {"peak_RAM_KiB": int(ru.ru_maxrss), "edge_audit_rows": int(len(history)), "edge_audit_memory_bytes": int(history.memory_usage(deep=True).sum()), "shadow_trace_overhead": "NOT-MEASURED-NOT-RUN", "wholebrain": json.loads((exp / "performance/whole-brain.json").read_text()), "isolated": json.loads((exp / "performance/isolated.json").read_text())} write_json(exp / "performance/summary.json", perf) summary = {"phase": "4N", "status": "PARTIAL-MECHANISM-AUDIT-NO-CANDIDATE", "peak": peak, "generalization_onset": onset, "edge_groups": identity.identity_group.value_counts().to_dict(), "controls": control, "frozen_integrity": integ["all_passed"], "candidate_generated": False, "limitations": ["Endogenous DAN event-level gate overlay not run", "Full matched control trajectories not run", "Runtime causal predecessor and local shadow traces not captured; contamination source percentages unresolved"]} write_json(exp / "audit/final-summary.json", summary) model = Path(ncfg["model_root"]); model.mkdir(parents=True, exist_ok=True) shutil.copy2(args.config, model / "phase4n.yaml"); shutil.copy2(Path(__file__).resolve(), model / "phase4n_finish.py") shutil.copy2(Path(__file__).with_name("phase4n.py"), model / "phase4n.py") shutil.copy2(Path(__file__).resolve(), exp / "audit/phase4n_finish.py") write_json(model / "VERSION.json", {"phase": "4N", "status": summary["status"], "candidate_generated": False, "identity_sha256": sha256(exp / "edge-identity/frozen-edge-identity.parquet"), "config_sha256": sha256(args.config)}) seal(model, model / "MANIFEST.json", model / "checksums.sha256") seal(exp, exp / "manifests/manifest.json", exp / "manifests/checksums.sha256") print(json.dumps(summary), flush=True) if __name__ == "__main__": main()