"""Compute complete-grid DKE products in latitude chunks and compact T719 spectra.""" import os import sys from pathlib import Path import numpy as np import torch import yaml ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) from model.pangu_icon_dke import PanguIconDKEDiagnostics def main(): config = yaml.safe_load((ROOT / "conf/config.yaml").read_text()) checkpoint = torch.load(ROOT / config["paths"]["checkpoint"], map_location="cpu", weights_only=False) if checkpoint["format_version"] != config["data"]["format_version"]: raise ValueError("checkpoint protocol mismatch") model = PanguIconDKEDiagnostics(checkpoint["protocol"], checkpoint["coefficients"]) world = int(os.environ.get("WORLD_SIZE", "1")) distributed = world > 1 if distributed: torch.distributed.init_process_group("gloo") rank = torch.distributed.get_rank() if distributed else 0 experiments, times, _, nlat, nlon = model.fields.field_shape chunk = int(config["runtime"]["latitude_chunk"]) local = {} for experiment in range(rank, experiments, world): global_dke = np.zeros(times, dtype=np.float64) dke_maps = np.empty((times, nlat, nlon), dtype=np.float32) spectrum = np.empty((times, int(checkpoint["protocol"]["triangular_truncation"]) + 1), dtype=np.float64) weights_total = 0.0 weighted_sum = np.zeros(times, dtype=np.float64) for start in range(0, nlat, chunk): stop = min(start + chunk, nlat) dke = model.fields.dke_chunk_all_times(experiment, slice(start, stop)) weights = np.cos(np.deg2rad(model.fields.latitudes[start:stop]))[None, :, None] weighted_sum += np.sum(dke * weights, axis=(1, 2)) dke_maps[:, start:stop] = dke weights_total += float(nlon * weights.sum()) global_dke[:] = weighted_sum / weights_total for time in range(times): spectrum[time] = model.fields.spectrum(experiment, time) local[experiment] = (global_dke, dke_maps, spectrum) if distributed: gathered = [None] * world if rank == 0 else None torch.distributed.gather_object(local, gathered, dst=0) if rank == 0: local = {key: value for shard in gathered for key, value in shard.items()} if rank == 0: ordered = [local[index] for index in range(experiments)] global_dke = np.stack([item[0] for item in ordered]) maps = np.stack([item[1] for item in ordered]) spectra = np.stack([item[2] for item in ordered]) output = ROOT / config["paths"]["inference"] output.parent.mkdir(parents=True, exist_ok=True) np.savez_compressed(output, global_dke=global_dke, dke_maps=maps, spectra=spectra, experiments=np.asarray(checkpoint["protocol"]["experiments"]), times_hours=np.asarray(checkpoint["protocol"]["times_hours"]), latitudes_degrees=np.asarray(checkpoint["protocol"]["latitudes_degrees"]), longitudes_degrees=np.asarray(checkpoint["protocol"]["longitudes_degrees"]), total_wavenumber=np.arange(spectra.shape[-1]), format_version=np.asarray(checkpoint["format_version"]), logical_field_shape=np.asarray(checkpoint["protocol"]["field_shape"]), logical_spectral_shape=np.asarray(checkpoint["protocol"]["spectral_shape"])) print(f"output={output.relative_to(ROOT)} global={global_dke.shape} maps={maps.shape} spectra={spectra.shape}") if distributed: torch.distributed.destroy_process_group() if __name__ == "__main__": main()