"""Deterministic smooth spherical fields for structural SFNO tests.""" from __future__ import annotations import math import torch def make_fake_spherical_sequence( timesteps: int, channels: int, nlat: int, nlon: int, seed: int, *, dtype: torch.dtype = torch.float32, ) -> dict[str, torch.Tensor]: """Create moving low-order spherical modes, not spatial white noise.""" if min(timesteps, channels, nlat, nlon) <= 0: raise ValueError("All fake-data dimensions must be positive") generator = torch.Generator().manual_seed(seed) lat = torch.linspace(-math.pi / 2, math.pi / 2, nlat, dtype=dtype) lon = torch.arange(nlon, dtype=dtype) * (2 * math.pi / nlon) latitude, longitude = torch.meshgrid(lat, lon, indexing="ij") amplitudes = 0.8 + 0.4 * torch.rand(channels, generator=generator, dtype=dtype) phases = 2 * math.pi * torch.rand(channels, generator=generator, dtype=dtype) frames = [] for step in range(timesteps): channel_fields = [] for channel in range(channels): phase = phases[channel] + 0.18 * (channel + 1) * step zonal = torch.cos(latitude) * torch.cos((channel + 1) * longitude - phase) planetary = 0.35 * torch.sin(2 * latitude) * torch.sin(longitude + 0.11 * step) polar = 0.15 * torch.cos(3 * latitude - 0.07 * step) channel_fields.append(amplitudes[channel] * (zonal + planetary + polar)) frames.append(torch.stack(channel_fields)) fields = torch.stack(frames) means = fields.mean(dim=(0, 2, 3)) stds = fields.std(dim=(0, 2, 3)).clamp_min(torch.finfo(dtype).eps) normalized = (fields - means[None, :, None, None]) / stds[None, :, None, None] return { "fields": normalized, "time": torch.arange(timesteps, dtype=dtype), "lat": torch.rad2deg(lat), "lon": torch.rad2deg(lon), "global_means": means, "global_stds": stds, }