from __future__ import annotations import argparse import sys from pathlib import Path SCRIPT_DIR = Path(__file__).resolve().parent MODEL_DIR = SCRIPT_DIR.parent / "model" for module_dir in (SCRIPT_DIR, MODEL_DIR): if str(module_dir) not in sys.path: sys.path.insert(0, str(module_dir)) import numpy as np import torch from common import DEFAULT_CONFIG, active_model_config, load_config, resolve_path from data import build_loader, load_statistics from fourcastnet_v2 import ( FourCastNetV2, load_checkpoint, ) def choose_device() -> torch.device: return torch.device("cuda", 0) if torch.cuda.is_available() else torch.device("cpu") def main() -> None: parser = argparse.ArgumentParser(description="Run FourCastNet v2 inference") parser.add_argument("--config", default=str(DEFAULT_CONFIG)) parser.add_argument("--checkpoint") args = parser.parse_args() config = load_config(args.config) inference = config["inference"] device = choose_device() model = FourCastNetV2(active_model_config(config)).to(device) if args.checkpoint: checkpoint_path = resolve_path(config, args.checkpoint) else: checkpoint_path = resolve_path(config, inference["checkpoint_path"]) result = load_checkpoint( model, checkpoint_path, expected_profile=config["model"]["profile"], expected_variables=config["data"]["variables"], allowed_stages={"one_step", "finetune"}, allowed_initializations={"random", "one_step_checkpoint"}, strict=config["checkpoint"]["strict"], map_location=device, ) if result["missing_keys"] or result["unexpected_keys"]: print( f"missing_keys={result['missing_keys']} " f"unexpected_keys={result['unexpected_keys']}" ) loader, _ = build_loader( config, config["data"]["test_years"], train=False, distributed=False, output_steps=inference["rollout_steps"], ) means, stds = load_statistics(config) means = means.numpy() stds = stds.numpy() output_dir = resolve_path(config, inference["output_dir"]) output_dir.mkdir(parents=True, exist_ok=True) model.eval() with torch.no_grad(): for sample_index, batch in enumerate(loader): if sample_index >= inference["max_samples"]: break inputs = batch[0].to(device) targets = batch[1] state = inputs predictions = [] for _ in range(inference["rollout_steps"]): state = model(state) predictions.append(state.cpu()) prediction = torch.stack(predictions, dim=1).numpy() if targets.ndim == 4: targets = targets.unsqueeze(1) target = targets.numpy() input_array = inputs.cpu().numpy() if not inference["save_normalized"]: prediction = prediction * stds[:, None] + means[:, None] target = target * stds[:, None] + means[:, None] input_array = input_array * stds + means path = output_dir / f"sample_{sample_index:04d}.npz" np.savez_compressed( path, input=input_array, prediction=prediction, target=target, variables=np.asarray(config["data"]["variables"]), time_index=np.asarray(batch[4], dtype=str).T, ) print(path) if __name__ == "__main__": main()