summaryrefslogtreecommitdiff
path: root/experiments/analyze_bci_v2_calibrated_development.py
diff options
context:
space:
mode:
Diffstat (limited to 'experiments/analyze_bci_v2_calibrated_development.py')
-rw-r--r--experiments/analyze_bci_v2_calibrated_development.py217
1 files changed, 217 insertions, 0 deletions
diff --git a/experiments/analyze_bci_v2_calibrated_development.py b/experiments/analyze_bci_v2_calibrated_development.py
new file mode 100644
index 0000000..e8781d6
--- /dev/null
+++ b/experiments/analyze_bci_v2_calibrated_development.py
@@ -0,0 +1,217 @@
+#!/usr/bin/env python3
+"""Audit calibration-split oral-B-v2 development validation."""
+import argparse
+import glob
+import hashlib
+import json
+import os
+
+from experiments.analyze_bci_v2_recovery_development import checks
+from experiments.bci_v2_calibrated_run import (
+ CALIBRATION_EPISODES,
+ CHALLENGE_EPISODES,
+ MODEL_SEEDS,
+ TARGET_QUANTILES,
+ TASK_SEEDS,
+ build_recovery_config,
+ source_paths,
+)
+from experiments.bci_v2_recovery_run import FIXED_CONFIG
+from experiments.bci_v2_run import CONDITIONS
+
+
+def require(condition, message):
+ if not condition:
+ raise ValueError(message)
+
+
+def sha256(path):
+ digest = hashlib.sha256()
+ with open(path, "rb") as handle:
+ for chunk in iter(lambda: handle.read(1024 * 1024), b""):
+ digest.update(chunk)
+ return digest.hexdigest()
+
+
+def validate(row, path, task_seed, digests):
+ require(row.get("schema_version") == 4, f"{path}: schema")
+ require(
+ row.get("args", {}).get("task_seed") == task_seed
+ and row["args"].get("model_seed") in MODEL_SEEDS,
+ f"{path}: seeds",
+ )
+ protocol = row.get("protocol", {})
+ require(
+ protocol.get("name")
+ == "oral_b_v2_calibrated_recovery_development_v1"
+ and protocol.get("selection_split") == "development_validation"
+ and protocol.get("hyperparameter_selection") is False
+ and protocol.get("confirmation_seeds_touched") is False
+ and protocol.get("fixed_config") == FIXED_CONFIG
+ and protocol.get("calibration_uses_outcome_labels") is False
+ and protocol.get("protocol_sha256") == digests["protocol"]
+ and protocol.get("failed_target_gate_sha256")
+ == digests["failed_target_gate"],
+ f"{path}: protocol",
+ )
+ provenance = row.get("provenance", {})
+ require(
+ provenance.get("git_tracked_dirty") is False
+ and provenance.get("tracked_inputs")
+ and all(provenance["tracked_inputs"].values())
+ and set(provenance["tracked_inputs"]) == set(digests)
+ and provenance.get("input_sha256") == digests,
+ f"{path}: provenance",
+ )
+ require(
+ row.get("config") == vars(build_recovery_config())
+ and row.get("split") == "development_validation"
+ and row.get("finite") is True,
+ f"{path}: config/split",
+ )
+ require(
+ set(row.get("conditions", {})) == set(CONDITIONS),
+ f"{path}: conditions",
+ )
+ for name in CONDITIONS:
+ warmup = row["warmup"][name]
+ condition = row["conditions"][name]
+ cost = condition["cost"]
+ require(
+ warmup["batches"] == 100
+ and warmup["examples"] == 6400
+ and warmup["instruction_present"] is False
+ and warmup["role_cursor_scalar_observations"] == 12800
+ and warmup["predictor_max_abs_error"] <= 1e-5
+ and len(condition["daily_success"]) == 14
+ and cost["maximum_state_episode_steps"] == 25088
+ and cost["cursor_scalar_observations"]
+ == 2 * cost["role_probe_examples"]
+ and cost["task_loss_queries"] == 0
+ and cost["reverse_mode_calls"] == 0,
+ f"{path}: condition {name}",
+ )
+ challenge = row["assays"]["challenge"]
+ calibration = challenge["calibration"]
+ targets = challenge["targets"]
+ require(
+ calibration["episodes"] == CALIBRATION_EPISODES
+ and calibration["quantiles"] == list(TARGET_QUANTILES)
+ and calibration["targets"] == targets
+ and calibration["uses_outcome_labels"] is False
+ and calibration["seed"] == 550_000 + task_seed
+ and challenge["episodes_per_target"] == CHALLENGE_EPISODES
+ and challenge["selection_over_evaluation_outcomes"] is False
+ and challenge["maximum_steps_per_episode"] == 28
+ and len(targets) == len(TARGET_QUANTILES)
+ and all(
+ targets[index] < targets[index + 1]
+ for index in range(len(targets) - 1)
+ )
+ and row["signatures"]["challenge_episodes"]
+ == len(TARGET_QUANTILES) * CHALLENGE_EPISODES,
+ f"{path}: calibration",
+ )
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "--results", default="results/bci_v2_calibrated_dev"
+ )
+ parser.add_argument(
+ "--out", default="results/bci_v2_calibrated_dev_gate.json"
+ )
+ args = parser.parse_args()
+ digests = {
+ name: sha256(path)
+ for name, path in source_paths().items()
+ }
+ expected = {
+ f"bci_v2_calibrated_t{task_seed}_m0.json"
+ for task_seed in TASK_SEEDS
+ }
+ observed = {
+ os.path.basename(path)
+ for path in glob.glob(os.path.join(args.results, "*.json"))
+ }
+ require(
+ observed == expected,
+ (
+ f"calibrated grid drift: missing={sorted(expected-observed)}, "
+ f"extra={sorted(observed-expected)}"
+ ),
+ )
+ records = {}
+ commits = set()
+ source_sha256 = {}
+ for task_seed in TASK_SEEDS:
+ path = os.path.join(
+ args.results,
+ f"bci_v2_calibrated_t{task_seed}_m0.json",
+ )
+ with open(path) as handle:
+ row = json.load(handle)
+ validate(row, path, task_seed, digests)
+ records[task_seed] = row
+ commits.add(row["provenance"]["git_commit"])
+ source_sha256[path] = sha256(path)
+ require(len(commits) == 1, "calibrated source revision drift")
+ checks_by_seed = {
+ str(seed): checks(records[seed]) for seed in TASK_SEEDS
+ }
+ passed = all(
+ value for seed_checks in checks_by_seed.values()
+ for value in seed_checks.values()
+ )
+ output = {
+ "protocol":
+ "oral_b_v2_calibrated_recovery_development_v1",
+ "status": "passed" if passed else "failed",
+ "complete_grid": True,
+ "fixed_config": FIXED_CONFIG,
+ "calibration_quantiles": list(TARGET_QUANTILES),
+ "checks_by_task_seed": checks_by_seed,
+ "calibrated_targets_by_task_seed": {
+ str(seed): records[seed]["assays"]["challenge"]["targets"]
+ for seed in TASK_SEEDS
+ },
+ "intact_final_by_task_seed": [
+ records[seed]["conditions"]["intact"]["final_success"]
+ for seed in TASK_SEEDS
+ ],
+ "challenge_success_fraction_by_task_seed": [
+ records[seed]["signatures"]["challenge_success_fraction"]
+ for seed in TASK_SEEDS
+ ],
+ "terminal_outcome_accuracy_by_task_seed": [
+ records[seed]["signatures"][
+ "terminal_residual_outcome_balanced_acc"
+ ]
+ for seed in TASK_SEEDS
+ ],
+ "confirmation_seeds_touched": False,
+ "calibrated_confirmation_opened": passed,
+ "source_commit": next(iter(commits)),
+ "source_sha256": source_sha256,
+ "input_sha256": digests,
+ "review_score_before": 7,
+ "review_score_after": 7,
+ "score_change_rule": (
+ "development validation never changes the formal score"
+ ),
+ }
+ os.makedirs(os.path.dirname(os.path.abspath(args.out)), exist_ok=True)
+ if os.path.exists(args.out):
+ with open(args.out) as handle:
+ existing = json.load(handle)
+ require(existing == output, "existing calibrated R1 gate differs")
+ else:
+ with open(args.out, "w") as handle:
+ json.dump(output, handle, indent=2, sort_keys=True)
+ handle.write("\n")
+ print(json.dumps(output, indent=2))
+
+
+if __name__ == "__main__":
+ main()