import json, os, subprocess
from pathlib import Path
REPO = Path(__file__).resolve().parents[3]
VAL = REPO / "validation"
EXP = VAL / "results" / "experiments"
BIN = REPO / "cella_lib/target/release/examples/wildfire_smc"
FIRES = ["Bear_2020", "Brattain_2020", "Buck_2017", "Chimney_2016", "Ferguson_2018", "Pier_2017"]
HOLDOUT = ("Ferguson_2018", "Pier_2017")
BASE_ENV = {"SMC_BETA": "10", "SMC_SIGMA": "0.2", "SMC_IMMIGRANTS": "0.2", "SMC_CONTAIN": "1", "SMC_TAU_OFF": "1"}
def run(out_dir, fire, label, env, members=32, mode="assim", argv_prefix=()):
out_dir.mkdir(parents=True, exist_ok=True)
rep = out_dir / f"{fire}_{label}.json"
full_env = {**os.environ, **BASE_ENV, **env}
argv = [*argv_prefix, str(BIN), str(VAL / "data" / "scenarios" / fire), str(members), mode, str(rep)]
subprocess.run(argv, check=True, capture_output=True, env=full_env)
r = json.loads(rep.read_text())
last = r["scores"][-1]
row = {"fire": fire, "config": label, "members": members, "seed": int(full_env.get("SMC_SEED", "0")),
"holdout": fire in HOLDOUT,
"binary_git": r.get("binary_git", "unknown"), "binary_built_utc": r.get("binary_built_utc", "unknown"),
"mean_consensus_iou": r["mean_consensus_iou"], "mean_best_threshold_iou": r["mean_best_threshold_iou"],
"mean_member_iou": r["mean_member_iou"], "mean_radial_iou": r["mean_radial_iou"],
"final_consensus_iou": r["final_consensus_iou"], "final_radial_iou": r["final_radial_iou"],
"mean_brier_ensemble": r["mean_brier_ensemble"], "mean_brier_radial": r["mean_brier_radial"],
"mean_lagged_persistence_iou": r.get("mean_lagged_persistence_iou"),
"mean_brier_lagged_persistence": r.get("mean_brier_lagged_persistence"),
"mean_lagged_circle_iou": r.get("mean_lagged_circle_iou"),
"mean_brier_lagged_circle": r.get("mean_brier_lagged_circle"),
"mean_ess": sum(s["ess"] for s in r["scores"]) / len(r["scores"]),
"final_p0_mean": last["p0_mean"], "final_dur_mean": last["dur_mean"],
"final_wind_scale_mean": last["wind_scale_mean"], "final_contained": last["contained_fraction"],
"per_day_consensus": [s["consensus_iou"] for s in r["scores"]],
"per_day_brier": [s["brier_ensemble"] for s in r["scores"]],
"per_day_ess": [s["ess"] for s in r["scores"]]}
print(f"{fire:14s} {label:22s} M={members:3d} consensus {row['mean_consensus_iou']:.3f} "
f"member {row['mean_member_iou']:.3f} (circle {row['mean_radial_iou']:.3f}) "
f"Brier {row['mean_brier_ensemble']:.4f} vs {row['mean_brier_radial']:.4f} ESS {row['mean_ess']:.1f} "
f"| p0 {row['final_p0_mean']:.2f} wind {row['final_wind_scale_mean']:.2f}", flush=True)
return row
def run_all(jobs, out_json, workers=3):
from concurrent.futures import ThreadPoolExecutor
out_dir = EXP / out_json.replace(".json", "")
with ThreadPoolExecutor(max_workers=workers) as ex:
rows = list(ex.map(lambda j: run(out_dir, j[0], j[1], j[2], j[3]), jobs))
(EXP / out_json).write_text(json.dumps(rows, indent=1))
return rows