Skip to content

Conversation

@OpheliaMiralles
Copy link
Contributor

Description

On top of #678, this PR introduces SpectralCRPS, adapt Octahedral losses by @theissenhelen and @PortillaS-Predictia (PRs #709 and #729) to match skeleton introduced in #678 by @frazane. The PR also adds tests for all of the losses. We can decide later which Octahedral loss we want or leave both options.

What problem does this change solve?

New feature/Consolidation

What issue or task does this change relate to?

#678 #709 #729

Additional notes

To be merged after #678 and if/when we decide to harmonise something about #709 and #729.

As a contributor to the Anemoi framework, please ensure that your changes include unit tests, updates to any affected dependencies and documentation, and have been tested in a parallel setting (i.e., with multiple GPUs). As a reviewer, you are also responsible for verifying these aspects and requesting changes if they are not adequately addressed. For guidelines about those please refer to https://anemoi.readthedocs.io/en/latest/

By opening this pull request, I affirm that all authors agree to the Contributor License Agreement.

frazane and others added 27 commits November 24, 2025 21:01
…, torch-harmonics) (HEALPix transform by M. Price et al., S2FFT).
… dim (as well as predictions with ensemble dim)

Signed-off-by: evenmn <[email protected]>
…nt that is already passed from KernelCRPS and AlmostFairKernalCRPS

Signed-off-by: evenmn <[email protected]>
…moi-core into feat/harmonize-spectral-losses
def __init__(
self,
nlat: int,
lmax: int | None = None,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to document these options better in a docstring here. Also, a section in the documentation for the spectral losses would be necessary.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we harmonize it with the FFT2D class? The apply_filter argument serves the same (or similar) purpose (compared to the lmax/mmax arguments here) - "apply low-pass filter to ignore frequencies beyond the Nyquist limit".

Please notice that the folding argument isn't even implemented, it will raise NotImplementedError (supposedly, it should account for the frequency extension and folding, but I don't fully understand it).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have decided to finalise this harmonisation when we have decided which method should go into main.

class EcTransOctahedralSHT(SpectralTransform):
def __init__(
self,
truncation: int,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to document these options better in a docstring here. Also, a section in the documentation for the spectral losses would be necessary.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sahahner, @PortillaS-Predictia and I had a chat earlier, and we wondered if we should just delete all HEALPix-related stuff from this file. We don't think there's a use case for it yet, and we have our hands full with the other spherical harmonic transform implementations. On top of that, it's currently a "stub" with no wrapper class in spectral_transforms.py, and also only the inverse is implemented so it can't even be used for spectral loss calculations. Shall we get rid of it and potentially resurrect it later when we have a use case?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I think that sounds sensible!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also remove the "Cartesian" (Regular) transforms and just add torch-harmonics as a dependency? These classes are mostly the original code from said library. I only ever added type hints and some minor changes (I also solved a minor bug with some dtypes, that's why there's a type change in the forward method of the inverse transform). However, in the meantime, they also added some other features, like properly guessing the truncation frequency for these Grids (@samhatfield).

Copy link
Member

@sahahner sahahner Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could possibly remove the cartesian/regular transform, as I am not aware that anyone is actively using these grids. The code would still be available on the closed PR #729. We could also point to torch-harmonics in the documentation for implementations for other grids and add the dependency when someone starts working with these grids.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So in fact this PR would only implement octahedral and reduced SHTs?

Copy link
Contributor Author

@OpheliaMiralles OpheliaMiralles Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And spectral CRPS used in bris models, it’s already a decent expansion

return legpoly(mmax, lmax, np.cos(t), norm=norm, inverse=inverse, csphase=csphase)


class CartesianRealSHT(Module):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the meaning of Real in this context, and below for OctahedralRealSHT? If it's referring to the fact that the inputs must be real valued, I would say this is not necessary to mention as that will always be the case.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, following discussion with @PortillaS-Predictia, I would recommend to rename this and the inverse, along with the wrapper CartesianSHT to RegularSHT to denote the fact that it takes a regular grid (i.e. a latitude-longitude grid with no reduction towards the poles) as input.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, Real only refers to the reality of the input fields, we can remove it. Renaming the class is also fine by me, but I left another comment related to this (here). Let me know what you think.

@samhatfield
Copy link
Collaborator

Two more ideas that would simplify this PR:

  1. I have looked at the two implementations of octahedral SHT (OctahedralRealSHT and EcTransOctahedralSHTModule) and they are essentially the same, algorithmically. The major difference is in how the Legendre polynomials are generated: the former uses a native pytorch approach, whereas the latter retrieves them from the ecTrans library. Therefore I suggest we choose just one of these two rather than implementing both.
    @sahahner and I have compared the computational performance of the two and found that OctahedralRealSHT has a slight advantage, so I propose that we delete EcTransOctahedralSHTModule. I would still like to have the option to use ecTrans to generate Legendre polynomials, as I believe the algorithm there to be more numerically stable at high resolutions (e.g. O2000 and above) (not confirmed, just a hunch). But that can be done with a later PR.
  2. Before merging we also need a generic reduced grid transform in addition to the existing regular grid (CartesianRealSHT) and the octahedral grid. This should be a trivial extension of OctahedralRealSHT. Then we will be able to handle e.g. the N320 grid of ERA5. I can do this work, but only after 1. is resolved.

@sahahner
Copy link
Member

sahahner commented Feb 2, 2026

I am very happy to see that we are already at a place where we can train global models with spectral losses. I have played around with the loss and uploaded some results to mlflow.
Feel free to add more experiments there.

Either way, I feel there are still a few open points:

  • Test spectral loss in a combined loss: here we should have a close eye on the loss function scalers and their correct application
  • As the octahedral losses are being refactored into one SHT class, it would be important to test the losses again after the refactor.
  • We need to enable and test the SHT classes on n320 grids
  • Documentation: all newly introduced spectral losses and until now undocumented 2D versions need to be documented.
  • Scientific evaluation of SHT transforms (runtime, performance, default values for truncation, ...)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: To be triaged

Development

Successfully merging this pull request may close these issues.

10 participants