Download release_tools/verify_pressure_field_export.py from euler314/typhoon-predict: direct link, hf CLI and curl.
- Browser
- Download file 5.78 kB
-
https://huggingface.co/euler314/typhoon-predict/resolve/main/release_tools/verify_pressure_field_export.py
- Command line
-
hf download hf://euler314/typhoon-predict/release_tools/verify_pressure_field_export.py
-
curl -L -o verify_pressure_field_export.py https://huggingface.co/euler314/typhoon-predict/resolve/main/release_tools/verify_pressure_field_export.py
5.78 kB
| """CPU-only export regression using an existing causal batched issue packet. | |
| This checks packaging/field consistency, not forecast skill. It never calls MPS, | |
| changes neural weights, or writes into historical archive/training directories. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import hashlib | |
| import json | |
| import shutil | |
| import sys | |
| from pathlib import Path | |
| from unittest.mock import patch | |
| import numpy as np | |
| import torch | |
| ROOT = Path(__file__).resolve().parents[1] | |
| MODEL = ROOT / "models/trackformer_1_2_field" | |
| sys.path.insert(0, str(MODEL)) | |
| import predict | |
| from model import CoreForecaster | |
| from baseline_model import base | |
| from plot_pressure import render_pressure_map | |
| def sha(path): | |
| with Path(path).open("rb") as stream: | |
| return hashlib.file_digest(stream, "sha256").hexdigest() | |
| def verify(batched_packet, weights, work): | |
| work = work.resolve() | |
| if not work.is_relative_to(Path("/Volumes/D")): | |
| raise ValueError("Diagnostic artifacts must remain on D") | |
| work.mkdir(parents=True, exist_ok=True) | |
| directory = work / "inference" | |
| directory.mkdir(exist_ok=True) | |
| for source in MODEL.iterdir(): | |
| if source.is_file() and source.suffix in (".py", ".md", ".json"): | |
| shutil.copy2(source, directory / source.name) | |
| shutil.copy2(weights, directory / "weights.pt") | |
| metadata = json.loads((directory / "manifest.json").read_text()) | |
| for name, expected in metadata["source_module_sha256"].items(): | |
| if sha(directory / name) != expected: | |
| raise ValueError("Frozen neural module changed: " + name) | |
| with np.load(batched_packet, allow_pickle=False) as source: | |
| arrays = {key: np.asarray(source[key])[0] for key in predict.INPUT_SHAPES} | |
| arrays["history_time_ns"] = np.asarray(source["history_time_ns"], dtype=np.int64) | |
| arrays["issue_time_ns"] = np.asarray(arrays["history_time_ns"][-1], dtype=np.int64) | |
| packet = work / "causal_issue_packet.npz" | |
| np.savez_compressed(packet, **arrays) | |
| captured, origins = [], [] | |
| class CapturingModel(CoreForecaster): | |
| def step(self, state): | |
| next_state, out = super().step(state) | |
| captured.append(out) | |
| origins.append(next_state["origin"]) | |
| return next_state, out | |
| torch.set_num_threads(2) | |
| torch.set_num_interop_threads(1) | |
| with patch.object(predict, "CoreForecaster", CapturingModel): | |
| result = predict.forecast(packet, directory, "cpu") | |
| contract = metadata["data_contract"] | |
| scale, offset = contract["normalization"]["std"][0], contract["normalization"]["mean"][0] | |
| reference = { | |
| "track_lat_lon": torch.stack([o["center"][0] for o in captured]).numpy(), | |
| "central_pressure_hpa": torch.stack([o["pressure"][0] for o in captured]).numpy(), | |
| "basin_mslp_hpa": torch.stack([o["global"][0,0] for o in captured]).numpy()*scale+offset, | |
| "regional_mslp_hpa": torch.stack([o["regional"][0,0] for o in captured]).numpy()*scale+offset, | |
| "maximum_wind_auxiliary_kt": torch.stack([o["vmax"][0] for o in captured]).numpy(), | |
| } | |
| for key, expected in reference.items(): | |
| np.testing.assert_array_equal(result[key], expected, err_msg="Original readout changed: " + key) | |
| alignment = [] | |
| for i, (out, origin) in enumerate(zip(captured, origins)): | |
| # The original model's own geographic sampler, not a centre/minimum fit. | |
| grid = CoreForecaster.core_grid(None, out["center"][:,0,None,None], out["center"][:,1,None,None], origin) | |
| sampled = base.sample_field(torch.from_numpy(result["core_mslp_hpa"][i])[None,None], grid)[0,0,0,0] | |
| alignment.append(abs(float(sampled) - float(result["central_pressure_hpa"][i]))) | |
| np.testing.assert_array_equal(result["core_latitude_deg"][i], out["core_lat"][0].numpy()) | |
| np.testing.assert_array_equal(result["core_longitude_deg"][i], out["core_lon"][0].numpy()) | |
| np.testing.assert_array_equal(result["core_valid"][i], out["core_valid"][0,0].numpy()) | |
| if max(alignment) > 1e-3: | |
| raise ValueError("Exported core does not reproduce its original central-pressure readout") | |
| np.savez_compressed(work / "forecast.npz", **result) | |
| for lead in (6, 120): | |
| render_pressure_map(result, work / f"pressure_{lead:03d}h.png", lead, 4) | |
| receipt = { | |
| "state": "verified_cpu_export_regression", | |
| "scientific_benchmark": False, | |
| "model": "Trackformer 1.2", "members": 1, | |
| "device": "cpu", "forecast_steps": 20, | |
| "issue_time_ns": int(result["issue_time_ns"]), | |
| "core_shape": list(result["core_mslp_hpa"].shape), | |
| "core_min_hpa": float(result["core_mslp_hpa"][result["core_valid"]].min()), | |
| "core_max_hpa": float(result["core_mslp_hpa"][result["core_valid"]].max()), | |
| "central_pressure_alignment_max_error_hpa": max(alignment), | |
| "original_outputs_bit_identical": list(reference), | |
| "coordinates_and_masks_match_forward_outputs": True, | |
| "inference_weights_sha256": sha(weights), | |
| "source_packet_sha256": sha(batched_packet), | |
| "forecast_sha256": sha(work / "forecast.npz"), | |
| "native_detail_history_available": bool(arrays["detail_available"][0]), | |
| "core_is_learned_reconstruction_not_native_observations": True, | |
| } | |
| (work / "verification.json").write_text(json.dumps(receipt, indent=2) + "\n") | |
| print(json.dumps(receipt, indent=2)) | |
| if __name__ == "__main__": | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument("--batched-packet", type=Path, required=True) | |
| parser.add_argument("--weights", type=Path, required=True) | |
| parser.add_argument("--work", type=Path, required=True) | |
| args = parser.parse_args() | |
| verify(args.batched_packet, args.weights, args.work) | |