yzt15806542928's picture
Upload folder using huggingface_hub
5c365c5 verified
Raw
History Blame Contribute Delete
2.59 kB
"""ACE's spherical Fourier neural operator implementation.
The paper uses SFNO with Gauss-Legendre transforms. This module builds the
implementation distributed by ``torch_harmonics``. All trainable layers use
random initialization, so a checkpoint can be created from scratch.
"""
from __future__ import annotations
from dataclasses import dataclass
import torch
from torch import nn
try:
from torch_harmonics.examples.models.sfno import SphericalFourierNeuralOperator
except ImportError as exc:
raise ImportError(
"ACE requires torch_harmonics. Activate the configured training "
"environment or install torch_harmonics before running ACE."
) from exc
@dataclass
class SFNOConfig:
nlat: int = 180
nlon: int = 360
in_channels: int = 40
out_channels: int = 44
embed_dim: int = 256
num_layers: int = 8
filter_type: str = "linear"
operator_type: str = "dhconv"
scale_factor: int = 1
spectral_layers: int = 3
grid: str = "legendre-gauss"
grid_internal: str = "legendre-gauss"
mlp_ratio: float = 2.0
fallback: bool = False
class SFNOAdapter(nn.Module):
"""Construct the paper-compatible spherical SFNO for one grid size."""
def __init__(self, config: SFNOConfig) -> None:
super().__init__()
if config.fallback:
raise ValueError("The planar fallback was removed; use the spherical SFNO implementation")
if config.nlat < 5 or config.nlon < 8:
raise ValueError("SFNO grid must be at least 5 x 8")
self.config = config
self.model = SphericalFourierNeuralOperator(
img_size=(config.nlat, config.nlon),
grid=config.grid,
grid_internal=config.grid_internal,
scale_factor=config.scale_factor,
in_chans=config.in_channels,
out_chans=config.out_channels,
embed_dim=config.embed_dim,
num_layers=config.num_layers,
use_mlp=True,
mlp_ratio=config.mlp_ratio,
normalization_layer="none",
residual_prediction=False,
pos_embed="none",
bias=True,
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
if x.ndim != 4:
raise ValueError(f"SFNO input must be [B,C,H,W], got {tuple(x.shape)}")
expected = (self.config.in_channels, self.config.nlat, self.config.nlon)
if tuple(x.shape[1:]) != expected:
raise ValueError(f"SFNO expects trailing shape {expected}, got {tuple(x.shape[1:])}")
return self.model(x.float())