| from __future__ import annotations |
|
|
| import random |
| from pathlib import Path |
| from typing import Any |
|
|
| import numpy as np |
| import torch |
| import yaml |
|
|
|
|
| PROJECT_ROOT = Path(__file__).resolve().parents[1] |
| DEFAULT_CONFIG = PROJECT_ROOT / "conf" / "config.yaml" |
|
|
|
|
| def load_config(path: str | Path = DEFAULT_CONFIG) -> dict[str, Any]: |
| config_path = Path(path).expanduser().resolve() |
| with config_path.open("r", encoding="utf-8") as stream: |
| config = yaml.safe_load(stream) |
| config["_config_path"] = str(config_path) |
| config["_project_root"] = str(PROJECT_ROOT) |
| validate_config(config) |
| return config |
|
|
|
|
| def validate_config(config: dict[str, Any]) -> None: |
| variables = config["data"]["variables"] |
| if len(variables) != 73 or len(set(variables)) != 73: |
| raise ValueError("FourCastNet v2 requires 73 unique variables") |
|
|
| profile_name = config["model"]["profile"] |
| profiles = config["model"]["profiles"] |
| if profile_name not in profiles: |
| raise ValueError(f"Unknown model profile: {profile_name}") |
|
|
| profile = profiles[profile_name] |
| if profile["in_channels"] != len(variables): |
| raise ValueError("Model input channels do not match the variable ledger") |
| if profile["out_channels"] != len(variables): |
| raise ValueError("Model output channels do not match the variable ledger") |
|
|
| if config["data"]["input_steps"] != 1: |
| raise ValueError("FourCastNet v2 expects exactly one input time step") |
| if config["data"]["output_steps"] != 1: |
| raise ValueError("One-step pretraining expects data.output_steps=1") |
| if config["training"]["finetune"]["autoregressive_steps"] < 2: |
| raise ValueError("Fine-tuning requires at least two autoregressive steps") |
| if config["inference"]["rollout_steps"] < 1: |
| raise ValueError("inference.rollout_steps must be positive") |
|
|
| if config["training"]["stage"] not in {"one_step", "finetune"}: |
| raise ValueError("training.stage must be 'one_step' or 'finetune'") |
| if config["checkpoint"]["initialize_from"] != "scratch": |
| raise ValueError("checkpoint.initialize_from must be 'scratch'") |
| if not config["checkpoint"].get("finetune_from"): |
| raise ValueError("checkpoint.finetune_from must name a one-step checkpoint") |
| prefix = config["checkpoint"].get("prefix", "model_bak") |
| if not prefix or Path(prefix).name != prefix: |
| raise ValueError("checkpoint.prefix must be a non-empty file name") |
|
|
|
|
| def resolve_path(config: dict[str, Any], value: str | Path) -> Path: |
| path = Path(value).expanduser() |
| if path.is_absolute(): |
| return path |
| return Path(config["_project_root"]) / path |
|
|
|
|
| def active_model_config(config: dict[str, Any]) -> dict[str, Any]: |
| return dict(config["model"]["profiles"][config["model"]["profile"]]) |
|
|
|
|
| def seed_everything(seed: int) -> None: |
| random.seed(seed) |
| np.random.seed(seed) |
| torch.manual_seed(seed) |
| if torch.cuda.is_available(): |
| torch.cuda.manual_seed_all(seed) |
|
|