import json from pathlib import Path import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt import numpy as np import yaml ROOT = Path(__file__).resolve().parents[1] def main(): cfg = yaml.safe_load((ROOT / "conf/config.yaml").read_text()) data = np.load(ROOT / cfg["paths"]["inference"]) pred, target = data["prediction"], data["target"] if pred.shape != target.shape or pred.shape[1:] != (65, 68): raise ValueError(f"Expected prediction and target shaped [N,65,68], got {pred.shape} and {target.shape}") mass = np.concatenate((data["layer_mass"], data["layer_mass"])) mass = mass / mass.mean() error = pred - target mad = (np.abs(error) * mass[None, None]).mean(axis=(0, 2)) bias = error.mean(axis=(0, 2)) flat_target = target[:, 1:].astype(np.float64).reshape(target.shape[0], -1) flat_pred = pred[:, 1:].astype(np.float64).reshape(pred.shape[0], -1) source_r2 = [] for truth, estimate in zip(flat_target, flat_pred): denom = np.sum((truth - truth.mean()) ** 2) source_r2.append(float(1.0 - np.sum((truth - estimate) ** 2) / max(denom, 1e-12))) dt = float(data["dt_seconds"]) qt_pred, qt_target = pred[..., 34:], target[..., 34:] column_water_pred = (qt_pred * data["layer_mass"][None, None]).sum(-1) column_water_target = (qt_target * data["layer_mass"][None, None]).sum(-1) lhf_evap = data["surface"][:, :, 1] / 2.5e6 adv_q = (0.5 * (data["advection"][:, :-1, 34:] + data["advection"][:, 1:, 34:]) * data["layer_mass"][None, None]).sum(-1) storage = np.diff(column_water_pred, axis=1) / dt precipitation = np.maximum(0.0, lhf_evap + adv_q - storage) residual = storage + precipitation - lhf_evap - adv_q metrics = {"mass_weighted_mad_per_step": mad.tolist(), "bias_per_step": bias.tolist(), "source_r2": source_r2, "mean_source_r2": float(np.mean(source_r2)), "water_budget": {"mean_precipitation_kg_m2_s": float(precipitation.mean()), "mean_abs_residual_kg_m2_s": float(np.abs(residual).mean())}, "per_step_length": int(len(mad)), "step_hours": cfg["data"]["step_hours"]} out = ROOT / cfg["paths"]["evaluation"] out.parent.mkdir(parents=True, exist_ok=True) out.write_text(json.dumps(metrics, indent=2)) hours = data["lead_hours"] fig, axes = plt.subplots(2, 1, figsize=(9, 7), constrained_layout=True) axes[0].plot(hours, target[:, :, :34].mean((0, 2)), label="target sL") axes[0].plot(hours, pred[:, :, :34].mean((0, 2)), "--", label="predicted sL") ax2 = axes[0].twinx() ax2.plot(hours, target[:, :, 34:].mean((0, 2)), color="tab:green", label="target qT") ax2.plot(hours, pred[:, :, 34:].mean((0, 2)), "--", color="tab:red", label="predicted qT") axes[0].set(xlabel="lead time (h)", title="SCM state rollout") axes[0].legend(loc="upper left") ax2.legend(loc="upper right") axes[1].plot(hours[1:], precipitation.mean(0) * 86400.0, color="navy") axes[1].set(xlabel="lead time (h)", ylabel="mm day-1", title="Diagnosed precipitation") fig.savefig(out.parent / "state_precipitation_timeseries.png", dpi=150) plt.close(fig) print(f"saved {out}: per_step={len(mad)}, source_R2={metrics['mean_source_r2']:.4f}") if __name__ == "__main__": main()