Files
digital-drosophila/models/male-cns-lif/olfactory-grounding-v1/phase4l_finalize.py
T

58 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""Post-run Phase 4L audit corrections; does not execute or alter brain dynamics."""
from __future__ import annotations
import argparse
import json
import shutil
import time
from pathlib import Path
import numpy as np
import pandas as pd
import yaml
from phase4h_audit import sha256, write_json
from phase4l import load_external, pn_drive, seal
def main() -> None:
parser = argparse.ArgumentParser(); parser.add_argument("--config", type=Path, required=True); args = parser.parse_args()
cfg = yaml.safe_load(args.config.read_text()); exp = Path(cfg["paths"]["experiment_root"]); model = Path(cfg["paths"]["phase4l_model_root"])
summary = json.loads((exp / "audit/final-summary.json").read_text()); identity = summary["eligible_edges"]
a, b, shared = (int(identity[k]) for k in ("A_specific_eligible_edges", "B_specific_eligible_edges", "shared_eligible_edges"))
identity["eligible_edge_Jaccard"] = shared / (a + b + shared)
identity["specific_edge_intersection"] = 0
identity["jaccard_definition"] = "(A-eligible intersection B-eligible)/(A-eligible union B-eligible), including shared edges"
write_json(exp / "eligible-edges/summary.json", identity)
# This is a separate read-only timing pass, not the timing of the original run.
mapping = pd.read_parquet(exp / "receptor-mapping/olfactory-mapping.parquet")
panel = pd.read_parquet(exp / "odor-encoder/frozen-odor-panel.parquet")
vectors_npz = np.load(exp / "odor-encoder/mapped-glomerular-vectors.npz")
vectors = {oid: vectors_npz[oid] for oid in panel.odor_id}; gloms = summary["formal_odors"]["glomeruli_order"]
median = float(np.median(panel.mapped_response_sum)); start = time.perf_counter()
for mode in cfg["odor_encoder"]["modes"]:
for oid in panel.odor_id: pn_drive(oid, vectors[oid], mapping, gloms, mode, median, cfg)
elapsed = time.perf_counter() - start
perf = summary["performance"]; perf["odor_encoder_preprocessing_seconds"] = elapsed
perf["odor_encoder_preprocessing_mean_seconds_per_odor_mode"] = elapsed / (len(panel) * len(cfg["odor_encoder"]["modes"]))
perf["preprocessing_timing_method"] = "separate read-only repetition of frozen 12-odor, 2-mode response-to-PN transformation; not original simulation wall interval"
perf["total_control_latency_mean_seconds_per_100ms"] = perf["odor_encoder_preprocessing_mean_seconds_per_odor_mode"] + perf["PN_event_generation_seconds_mean"] + perf["brain_wall_seconds_mean_per_100ms"]
write_json(exp / "performance/summary.json", perf)
write_json(exp / "audit/final-summary.json", summary)
version = json.loads((model / "VERSION.json").read_text()); version["summary_sha256"] = sha256(exp / "audit/final-summary.json"); write_json(model / "VERSION.json", version)
write_json(exp / "audit/post-run-corrections.json", {"only_phase4l_reports_modified": True, "brain_rerun": False,
"eligible_Jaccard_corrected_to_include_shared_edges": identity["eligible_edge_Jaccard"],
"encoder_preprocessing_seconds_separate_timing": elapsed, "run_log_sealed_after_process_exit": True})
shutil.copy2(Path(__file__).resolve(), exp / "audit/phase4l_finalize.py")
shutil.copy2(Path(__file__).resolve(), model / "phase4l_finalize.py")
seal(model, model / "MANIFEST.json", model / "checksums.sha256")
seal(exp, exp / "manifests/manifest.json", exp / "manifests/checksums.sha256")
print(json.dumps({"eligible_Jaccard": identity["eligible_edge_Jaccard"], "preprocess_seconds": elapsed,
"experiment_checksum_count": len((exp / "manifests/checksums.sha256").read_text().splitlines())}))
if __name__ == "__main__": main()