Files
digital-drosophila/models/male-cns-lif/author-apl-lab-v2/calibration.py
T
2026-09-30 09:26:21 +08:00

397 lines
25 KiB
Python

"""Complete original Phase 4L representation metrics on fixed CAL inputs only.
No simulation, fitting, heldout trajectory access, or candidate generation is
performed here. The original 4L pair-specific within-odor baseline is retained;
the weaker Phase 4P r4 aggregate gate is deliberately not used.
"""
import csv
import hashlib
import itertools
import json
from pathlib import Path
import re
import numpy as np
STATE_DTYPES = {"step": np.int32, "neuron_idx": np.int32, "counts": np.int32,
"v": np.float64, "g": np.float64, "ref": np.int32}
REQUEST_FIELDS = ("tick", "neuron_idx", "attempted", "accepted", "rejected")
APL_FILES = {"apl-drive.float32.npy": 2, "apl-state.float32.npy": 2,
"apl-inhibition.float32.npy": 4064}
ORIGINAL_CAL_IDS = {
"OMSUIQOIVADKIM-RXMQYKEDSA-N", "LABTWGUMFABVFG-ARJAWSKDSA-N",
"QNVRIHYSUZMSGM-ZCFIWIBFSA-N", "VLSVVMPLPMNWBH-UHFFFAOYSA-N",
"XHIUFYZDQBSEMF-UHFFFAOYSA-N", "OMSUIQOIVADKIM-YFKPBYRVSA-N"}
ORIGINAL_MODES = ("physiological", "matched_energy")
ORIGINAL_SEEDS = (51101, 51102, 51103, 51104, 51105)
HEX_SHA = re.compile(r"[0-9a-f]{64}\Z")
_DEFAULT_OUTPUT = object()
def require(condition, message):
if not condition:
raise ValueError(message)
def read_json(path):
with Path(path).open(encoding="utf-8") as stream:
return json.load(stream)
def file_hash(path):
digest = hashlib.sha256()
with Path(path).open("rb") as stream:
for block in iter(lambda: stream.read(8 << 20), b""):
digest.update(block)
return digest.hexdigest()
def raw_hash(value):
return hashlib.sha256(np.ascontiguousarray(value).tobytes()).hexdigest()
def arrays_bitwise_equal(first, second):
"""Unlike array_equal, also reject different dtypes and signed-zero bits."""
return bool(first.dtype == second.dtype and first.shape == second.shape and
np.array_equal(np.ascontiguousarray(first).view(np.uint8),
np.ascontiguousarray(second).view(np.uint8)))
def pair_metrics(a, b):
"""Exact definitions copied from phase4i.pair_metrics/safe_corr/cosine."""
x, y = a.astype(np.float64), b.astype(np.float64)
aa, bb = a > 0, b > 0
union = int(np.count_nonzero(aa | bb))
intersection = int(np.count_nonzero(aa & bb))
denominator = float(np.linalg.norm(x) * np.linalg.norm(y))
cosine = 1.0 if denominator == 0 and np.array_equal(x, y) else (
0.0 if denominator == 0 else float(np.dot(x, y) / denominator))
correlation = (1.0 if np.array_equal(x, y) else 0.0) if np.std(x) == 0 or np.std(y) == 0 else float(np.corrcoef(x, y)[0, 1])
return {"active_intersection": intersection, "active_union": union,
"active_jaccard": 1.0 if union == 0 else float(intersection / union),
"cosine_similarity": cosine, "correlation": correlation,
"euclidean_distance": float(np.linalg.norm(x - y))}
def evaluate_gates(summary, gates):
"""Each conjunct in original phase4l.py:239, kept visible for audit."""
return {
"mean_sparsity_in_closed_interval": bool(float(gates["sparse_min_fraction"]) <=
summary["KC_active_fraction_mean"] <= float(gates["sparse_max_fraction"])),
"maximum_strictly_below_half": bool(summary["KC_active_fraction_max"] < 0.5),
"minimum_strictly_positive": bool(summary["KC_active_fraction_min"] > 0),
"same_jaccard_gt_different": bool(summary["same_odor_jaccard_mean"] > summary["different_odor_jaccard_mean"]),
"different_euclidean_gt_same": bool(summary["different_odor_euclidean_mean"] > summary["same_odor_euclidean_mean"]),
"at_least_original_pair_count": bool(summary["pairwise_separation_pass_count"] >= int(gates["minimum_separable_odor_pairs"]))}
def sample_cv(values):
"""Original pandas Series.std uses ddof=1. Undefined diagnostic -> null."""
values = np.asarray(values, dtype=np.float64)
mean = float(values.mean())
return None if mean == 0 else float(values.std(ddof=1) / mean)
def summarize_mode(mode, odor_ids, seeds, runs, gates):
within, within_j, between, between_j, between_cos, between_corr = [], [], [], [], [], []
pairs = []
for odor in odor_ids:
arrays = [runs[(mode, odor, seed)]["kc_counts"] for seed in seeds]
for first, second in itertools.combinations(arrays, 2):
metric = pair_metrics(first, second)
within.append(metric["euclidean_distance"])
within_j.append(metric["active_jaccard"])
for first_odor, second_odor in itertools.combinations(odor_ids, 2):
distances = []
for seed in seeds:
metric = pair_metrics(runs[(mode, first_odor, seed)]["kc_counts"],
runs[(mode, second_odor, seed)]["kc_counts"])
distances.append(metric["euclidean_distance"])
between.append(metric["euclidean_distance"])
between_j.append(metric["active_jaccard"])
between_cos.append(metric["cosine_similarity"])
between_corr.append(metric["correlation"])
pair_within = []
for odor in (first_odor, second_odor):
arrays = [runs[(mode, odor, seed)]["kc_counts"] for seed in seeds]
pair_within.extend(pair_metrics(first, second)["euclidean_distance"]
for first, second in itertools.combinations(arrays, 2))
cross_mean, within_mean = float(np.mean(distances)), float(np.mean(pair_within))
pairs.append({"odor_id_a": first_odor, "odor_id_b": second_odor,
"between_same_seed_mean": cross_mean, "within_this_pair_mean": within_mean,
"between_distance_count": len(distances), "within_distance_count": len(pair_within),
"gate": bool(cross_mean > within_mean)})
selected = [runs[(mode, odor, seed)] for odor in odor_ids for seed in seeds]
fractions = [int(np.count_nonzero(run["kc_counts"])) / len(run["kc_counts"]) for run in selected]
summary = {"KC_active_fraction_mean": float(np.mean(fractions)),
"KC_active_fraction_min": float(np.min(fractions)), "KC_active_fraction_max": float(np.max(fractions)),
"same_odor_jaccard_mean": float(np.mean(within_j)),
"different_odor_jaccard_mean": float(np.mean(between_j)),
"same_odor_euclidean_mean": float(np.mean(within)),
"different_odor_euclidean_mean": float(np.mean(between)),
"different_odor_cosine_mean": float(np.mean(between_cos)),
"different_odor_correlation_mean": float(np.mean(between_corr)),
"within_variance": float(np.var(within)), "between_distance_mean": float(np.mean(between)),
"pairwise_separation_pass_count": sum(row["gate"] for row in pairs), "pair_count": len(pairs),
"expected_event_count_cv": sample_cv([run["expected_external_events"] for run in selected]),
"actual_event_count_cv": sample_cv([run["external_events"] for run in selected]),
"within_distance_count": len(within), "between_distance_count": len(between),
"pairwise_baseline": "Each pair's two odors: 10+10 within-seed distances; never pooled across all odors",
"pairs": pairs}
summary["conditions"] = evaluate_gates(summary, gates)
summary["gate"] = bool(all(summary["conditions"].values()))
return summary
def validate_state(state, neurons, steps):
require(set(state) == set(STATE_DTYPES), "state.npz must have exactly the six declared arrays")
for name, dtype in STATE_DTYPES.items():
value = state[name]
require(value.dtype == dtype and value.ndim == 1, "Invalid state dtype/rank: " + name)
require(bool(np.isfinite(value).all()), "Nonfinite state: " + name)
if name not in ("step", "neuron_idx"):
require(value.shape == (neurons,), "Invalid state length: " + name)
ticks, nodes = state["step"], state["neuron_idx"]
require(ticks.shape == nodes.shape and bool(np.all((ticks >= 0) & (ticks < steps))) and
bool(np.all((nodes >= 0) & (nodes < neurons))), "Invalid sparse event indices")
keys = ticks.astype(np.int64) * neurons + nodes
require(bool(np.all(np.diff(keys) > 0)), "Sparse events not sorted unique by tick/global neuron")
require(np.array_equal(np.bincount(nodes, minlength=neurons), state["counts"]), "Counts disagree with sparse events")
require(bool(np.all((state["ref"] >= 0) & (state["ref"] <= 22))), "Final refractory outside frozen range")
return keys
def read_jsonl(path, steps, kind):
with Path(path).open(encoding="utf-8") as source:
rows = [json.loads(line) for line in source if line.strip()]
require(len(rows) == steps and [row.get("tick") for row in rows] == list(range(steps)),
"Missing/duplicate/reordered ticks in " + kind)
for row in rows:
names = ("v_sha256", "g_sha256", "ref_sha256") if kind == "state hashes" else (
"ordered_edge_sha256", "accepted_mask_sha256", "joint_sha256")
for name in names:
require(isinstance(row.get(name), str) and HEX_SHA.fullmatch(row[name]), "Invalid SHA256 in " + kind)
if kind == "queue":
require(type(row.get("count")) is int and type(row.get("accepted_count")) is int and
0 <= row["accepted_count"] <= row["count"], "Invalid queue counts")
return rows
def read_trial(output, case, spec, kc_globals, neurons, edges):
summary = read_json(output / "summary.json")
steps = spec["steps"]
require(summary.get("complete") is True and summary.get("pass_all") is True and
summary.get("all_finite") is True and summary.get("steps") == steps and
summary.get("completed_ticks") == steps and summary.get("nodes") == neurons and
summary.get("edges") == edges and summary.get("precision") == "float64" and
summary.get("apl_precision") == "float32" and summary.get("case") == spec["name"] and
summary.get("record_history") is False and summary.get("learning") is False,
"Incomplete/failed/wrong-scope GPU summary")
with np.load(output / "state.npz", allow_pickle=False) as source:
state = {name: source[name].copy() for name in source.files}
event_keys = validate_state(state, neurons, steps)
hashes = read_jsonl(output / "tick-state-hashes.jsonl", steps, "state hashes")
for name in ("v", "g", "ref"):
require(hashes[-1][name + "_sha256"] == raw_hash(state[name]), "Final state does not match last tick hash: " + name)
queues = read_jsonl(output / "queue.jsonl", steps, "queue")
apl = {}
for name, width in APL_FILES.items():
value = np.load(output / name, mmap_mode="r", allow_pickle=False)
require(value.dtype == np.float32 and value.shape == (steps, width) and
bool(np.isfinite(value).all()) and bool(np.all(value >= 0)), "Invalid full APL trace: " + name)
apl[name] = value
with (output / "requests.csv").open(encoding="utf-8", newline="") as source:
reader = csv.DictReader(source)
require(tuple(reader.fieldnames or ()) == REQUEST_FIELDS, "Request CSV schema mismatch")
request_rows = [{name: int(row[name]) for name in REQUEST_FIELDS} for row in reader]
with np.load(case / "requests.npz", allow_pickle=False) as source:
require(set(source.files) == {"tick", "neuron_idx"}, "Input request schema mismatch")
expected_ticks, expected_nodes = source["tick"], source["neuron_idx"]
require(expected_ticks.dtype == np.int32 and expected_nodes.dtype == np.int32 and
expected_ticks.ndim == 1 and expected_ticks.shape == expected_nodes.shape,
"Input request dtype/shape mismatch")
require(len(request_rows) == len(expected_ticks), "Requested event coverage mismatch")
require([row["tick"] for row in request_rows] == expected_ticks.tolist() and
[row["neuron_idx"] for row in request_rows] == expected_nodes.tolist(), "Requests differ from frozen input")
require(all(row["attempted"] == 1 and row["accepted"] in (0, 1) and
row["rejected"] == 1 - row["accepted"] for row in request_rows), "Invalid request acceptance bookkeeping")
requested_keys = expected_ticks.astype(np.int64) * neurons + expected_nodes
require(bool(np.all(np.diff(requested_keys) > 0)) and bool(np.all((expected_ticks >= 0) & (expected_ticks < steps))) and
bool(np.all((expected_nodes >= 0) & (expected_nodes < neurons))), "Input requests not sorted unique/in range")
actual_accepted = np.array([row["accepted"] for row in request_rows], dtype=np.uint8)
require(np.array_equal(np.isin(requested_keys, event_keys, assume_unique=True), actual_accepted.astype(bool)),
"Request accepted/rejected values disagree with actual spikes")
accepted = int(actual_accepted.sum())
require(summary.get("requested") == len(request_rows) and summary.get("accepted_requests") == accepted and
summary.get("rejected_requests") == len(request_rows) - accepted and
summary.get("total_spikes") == len(state["step"]), "Summary counts disagree with raw output")
component = read_json(output / "apl-component.json")
require(component.get("ok") is True and component.get("n_neurons") == neurons and
component.get("n_kc") == len(kc_globals) and component.get("n_apl") == 2 and
component.get("dt_ms") == .1 and summary.get("apl_component") == component,
"APL component provenance mismatch")
bundle = Path(spec["apl_bundle_dir"])
require(component.get("files_sha256") == {name: file_hash(bundle / name) for name in
("input_matrix.npy", "output_matrix.npy", "kc_globals.npy", "apl_globals.npy")}, "APL bundle hashes changed")
inputs = summary.get("input_sha256", {})
require(inputs.get("spec.json") == file_hash(case / "spec.json") and
inputs.get("requests.npz") == file_hash(case / "requests.npz"), "Case input provenance mismatch")
kc_counts = state["counts"][kc_globals].copy()
kc_event_mask = np.isin(state["neuron_idx"], kc_globals)
record = {"case": spec["name"], "mode": spec["mode"], "odor_id": spec["odor_id"],
"odor_name": spec["odor_name"], "seed": spec["seed"], "split": "calibration",
"KC_active": int(np.count_nonzero(kc_counts)), "KC_active_fraction": float(np.count_nonzero(kc_counts) / len(kc_globals)),
"KC_spikes": int(kc_counts.sum()), "KC_first_latency_ms":
float(state["step"][kc_event_mask][0] * .1) if bool(np.any(kc_event_mask)) else None,
"external_events": accepted, "expected_external_events": float(spec["expected_external_events"]),
"total_PN_rate_hz": float(spec["rate_sum_hz"]), "finite": True,
"total_spikes": len(state["step"]), "requested": len(request_rows)}
require(np.isfinite(record["expected_external_events"]) and record["expected_external_events"] > 0,
"CAL expected external events must be finite and positive")
return {"state": state, "hashes": hashes, "queues": queues, "apl": apl,
"requests": request_rows, "component": component, "kc_counts": kc_counts,
"expected_external_events": record["expected_external_events"], "external_events": accepted,
"record": record, "output": output}
def exact_repeat(first, repeat):
checks = {"state/" + name: arrays_bitwise_equal(first["state"][name], repeat["state"][name]) for name in STATE_DTYPES}
checks.update({name: arrays_bitwise_equal(first["apl"][name], repeat["apl"][name]) for name in APL_FILES})
checks.update(tick_state_hashes=first["hashes"] == repeat["hashes"],
queues=first["queues"] == repeat["queues"], requests=first["requests"] == repeat["requests"],
apl_component=first["component"] == repeat["component"])
return {"pass_all": bool(all(checks.values())), "checks": checks,
"scope": "All six state arrays, every tick all-neuron hashes, APL raw float32 traces, queues, requests and APL environment"}
def validate_inventory(records, cfg):
require(tuple(cfg["calibration"]["modes"]) == ORIGINAL_MODES and
tuple(cfg["calibration"]["seeds"]) == ORIGINAL_SEEDS and
cfg["calibration"]["duration_ms"] == 100.0 and cfg["calibration"]["odor_count"] == 6 and
cfg["calibration"]["split"] == "calibration" and
cfg["calibration"]["repeat_first_seed_each_odor"] is True, "Frozen CAL design changed")
gates = cfg["gates"]
require(gates["sparse_min_fraction"] == .03 and gates["sparse_max_fraction"] == .09 and
gates["minimum_separable_odor_pairs"] == 3 and gates["max_fraction_strict"] == .5 and
gates["min_fraction_strict"] == 0 and all(gates[key] is True for key in
("same_jaccard_gt_between", "between_euclidean_gt_within", "both_modes_required", "finite_required", "repeat_exact_required")),
"Original representation gates or additional integrity gates changed")
expected = {(mode, index, seed) for mode in ORIGINAL_MODES for index in range(6) for seed in ORIGINAL_SEEDS}
require(len(records) == 60 and {(row["mode"], row["odor_index"], row["seed"]) for row in records} == expected,
"CAL inventory must cover exactly 2 modes x 6 odors x 5 seeds")
odors = []
for index in range(6):
ids = {row["odor_id"] for row in records if row["odor_index"] == index}
require(len(ids) == 1, "Odor identity changes across seeds/modes")
odors.append(next(iter(ids)))
require(set(odors) == ORIGINAL_CAL_IDS, "CAL identities differ from the original frozen six")
for row in records:
require(row["name"] == f"cal-{row['mode']}-{row['odor_index']:02d}-s{row['seed']}", "Unexpected CAL case name")
return odors
def summarize(out_json=_DEFAULT_OUTPUT):
"""Recompute; default writes exclusively, out_json=None is strictly read-only."""
from context import EXP, ROOT, CFG, check
target = EXP / "calibration/summary.json" if out_json is _DEFAULT_OUTPUT else (
None if out_json is None else Path(out_json))
require(target is None or not target.exists(), "Refusing existing calibration summary")
failures, runs, run_records, repeats, provenance = [], {}, [], [], []
modes, odors = {}, []
prerequisites_ok = False
encoder = None
records = []
try:
check()
records = read_json(EXP / "protocol/calibration-cases.json")
odors = validate_inventory(records, CFG)
encoder = read_json(EXP / "protocol/encoder-audit.json")
require(encoder["panel_count"] == 12 and encoder["cal_count"] == 6 and
encoder["seeds"] == list(ORIGINAL_SEEDS) and encoder["requested_before_any_dynamics"] is True and
encoder["formal_AB_run"] is False and encoder["heldout_run"] is False, "Invalid frozen encoder scope")
# Only static original-panel metadata is read, never heldout trajectories.
import pandas as pd
original = pd.read_parquet(ROOT / "experiments/phase4l-olfactory-grounding/odor-encoder/frozen-odor-panel.parquet")
require(len(original) == 12 and set(original[original.split == "calibration"].odor_id) == ORIGINAL_CAL_IDS,
"Original static panel identity mismatch")
require(float(np.median(original.mapped_response_sum)) == encoder["median_mapped_response_sum"],
"Matched-energy reference is not the original frozen 12-panel median")
kc_globals = np.load(EXP / "apl-bundle/kc_globals.npy", allow_pickle=False)
require(kc_globals.dtype == np.int32 and kc_globals.shape == (4064,) and
bool(np.all(np.diff(kc_globals) > 0)) and bool(np.all((kc_globals >= 0) & (kc_globals < CFG["neurons"]))),
"Invalid frozen KC identities")
prerequisites_ok = True
except Exception as error:
failures.append({"stage": "prerequisites", "type": type(error).__name__, "message": str(error)})
if prerequisites_ok:
for row in records:
name = row["name"]
first = None
try:
case = EXP / "cases" / name
spec = read_json(case / "spec.json")
require(spec["name"] == name and spec["kind"] == "calibration" and spec["steps"] == 1000 and
spec["duration_ms"] == 100.0 and spec["dt_ms"] == .1 and spec["training"] is False and
all(spec[key] == row[key] for key in ("mode", "odor_id", "odor_index", "seed")),
"Case spec differs from fixed CAL inventory")
first = read_trial(EXP / "gpu" / (name + "-r0"), case, spec, kc_globals, CFG["neurons"], CFG["edges"])
require(first["record"]["requested"] == row["requests"], "Frozen inventory request count mismatch")
runs[(row["mode"], row["odor_id"], row["seed"])] = {key: first[key] for key in
("kc_counts", "expected_external_events", "external_events")}
run_records.append(first["record"])
provenance.append({"case": name, "repeat": 0, "state_npz_sha256": file_hash(first["output"] / "state.npz"),
"summary_sha256": file_hash(first["output"] / "summary.json")})
except Exception as error:
failures.append({"stage": "raw_trial", "case": name, "repeat": 0,
"type": type(error).__name__, "message": str(error)})
if row["seed"] == ORIGINAL_SEEDS[0]:
try:
require(first is not None, "Baseline unavailable for exact repeat")
repeat = read_trial(EXP / "gpu" / (name + "-r1"), case, spec, kc_globals, CFG["neurons"], CFG["edges"])
repeat_report = {"case": name, **exact_repeat(first, repeat)}
repeats.append(repeat_report)
provenance.append({"case": name, "repeat": 1, "state_npz_sha256": file_hash(repeat["output"] / "state.npz"),
"summary_sha256": file_hash(repeat["output"] / "summary.json")})
require(repeat_report["pass_all"], "Exact repeat differs: " + ", ".join(
key for key, value in repeat_report["checks"].items() if not value))
except Exception as error:
failures.append({"stage": "exact_repeat", "case": name, "repeat": 1,
"type": type(error).__name__, "message": str(error)})
if len(runs) == 60:
for mode in ORIGINAL_MODES:
try:
modes[mode] = summarize_mode(mode, odors, ORIGINAL_SEEDS, runs, CFG["gates"])
except Exception as error:
failures.append({"stage": "representation", "mode": mode,
"type": type(error).__name__, "message": str(error)})
integrity_pass = bool(prerequisites_ok and len(runs) == 60 and not any(row["stage"] in
("prerequisites", "raw_trial") for row in failures))
repeat_pass = bool(len(repeats) == 12 and all(row["pass_all"] for row in repeats) and
not any(row["stage"] == "exact_repeat" for row in failures))
representation_pass = bool(len(modes) == 2 and all(row["gate"] for row in modes.values()))
result = {"phase": "4AG", "scope": "Original fixed six CAL odors only; not heldout generalization or learning",
"pass_all": bool(integrity_pass and repeat_pass and representation_pass),
"numerical_integrity_pass": integrity_pass, "exact_repeat_pass": repeat_pass,
"original_representation_gate_pass": representation_pass,
"expected_base_trials": 60, "valid_base_trials": len(runs), "expected_repeats": 12,
"checked_repeats": len(repeats), "seeds": list(ORIGINAL_SEEDS), "duration_ms": 100.0,
"odor_ids": odors, "modes": modes, "runs": run_records, "repeats": repeats,
"failures": failures, "input_output_provenance": provenance, "encoder_audit": encoder,
"metric_source": "phase4l.representation (full original gate), phase4i.pair_metrics",
"finite_gate_note": "Added integrity prerequisite; original 4L recorded finite but omitted it from its gate",
"repeat_gate_note": "Additional exact repeat requirement; original metrics and numerical thresholds unchanged",
"shared_apl_component_oracle_limit": True, "independent_oracle_equivalence_claim": False,
"candidate_generated": False, "training": False, "heldout_dynamic_access": False}
if target is not None:
target.parent.mkdir(parents=True, exist_ok=True)
with target.open("x", encoding="utf-8") as output:
json.dump(result, output, ensure_ascii=False, indent=2, allow_nan=False)
output.write("\n")
return result
if __name__ == "__main__":
print(json.dumps({key: value for key, value in summarize().items() if key in
("pass_all", "numerical_integrity_pass", "exact_repeat_pass", "original_representation_gate_pass")}), flush=True)