GaussianRandomFieldS2 samples are not isotropic: orders m > 0 get twice the power of m = 0
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 62/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- python, pytorch
- Domain
- machine-learning
Research direction
Start by reading GaussianRandomFieldS2.forward and the inverse transform behavior at order m = 0; the issue identifies both as the source of the variance difference. Apply the proposed scaling to orders m > 0 for generated and supplied xi, then update test_spectral_amplitudes_are_computed_in_requested_dtype and add the stated variance check across the five grids. Done means the tested variance ratio is 1 within the reported numerical spread.
Written by the indexing model from the issue text.
Description
Problem
GaussianRandomFieldS2 defines its covariance through a power spectrum C_l. Such a covariance is rotation invariant, so the variance of the field should be the same at every point. Instead it depends on the latitude:
| grid | variance, ring nearest the pole | variance, equator | ratio |
|---|---|---|---|
legendre-gauss, 24x48 |
0.004320 | 0.006850 | 0.631 |
equiangular, 33x66 |
0.003334 | 0.006434 | 0.518 |
These use alpha=1.5, tau=3.0, sigma=1.0 and float64. They are exact rather than sampled: the map from noise to field is linear, so pushing one unit per noise component through it and summing the squares gives the variance. Sampling agrees: 4000 fields on legendre-gauss 48x96 give a ratio of 0.589.
import torch
import torch_harmonics as th
from torch_harmonics.random_fields import GaussianRandomFieldS2
field = GaussianRandomFieldS2(th.as_grid("legendre-gauss", nlat=24, nlon=48), alpha=1.5, tau=3.0, sigma=1.0, dtype=torch.float64)
lmax, mmax = field.isht.lmax, field.isht.mmax
count = 2 * lmax * mmax
noise = torch.eye(count, dtype=torch.float64).reshape(count, lmax, mmax, 2)
variance = field(count, xi=torch.view_as_complex(noise)).pow(2).sum(dim=0).mean(dim=-1)
print(variance[0] / variance[12]) # 0.631, expected 1
Cause
forward draws xi with unit-variance real and imaginary parts at every order and calls self.isht(xi * self.sqrt_eig). The inverse transform drops the imaginary part at m = 0, while a coefficient of order m > 0 also stands for its conjugate at -m. So E|a_lm|^2 is C_l at m = 0 and 2 C_l at m > 0. For comparison, healpy.synalm with C_l = 1 gives E|a_16,0|^2 = 0.997 and E|a_16,8|^2 = 0.991 over 4000 draws.
Proposed fix
Scale orders m > 0 by 1 / sqrt(2) in forward, which also covers a user-supplied xi. With that change the ratio is 1.0000 on the two grids above, with a spread of about 2e-15, and I have a test for the spread on five grids (legendre-gauss with two longitude counts, equiangular, lobatto, fejer1).
This changes behavior: samples for a given seed or xi change, and the variance at the equator about halves. One existing assertion, in test_spectral_amplitudes_are_computed_in_requested_dtype, encodes the current formula and would need to change.
A question on the overall scale
This is not part of the proposed fix. After it, the variance equals sum_l C_l (2l + 1) / (4 pi)^2, not / (4 pi). The transform uses norm="backward", which legendre.py treats as four-pi, so a factor of 4 pi separates the two: 0.00354 against 0.0445 for the parameters above. The docstring says the default sigma makes the variance O(1), while the defaults alpha=2.0, tau=3.0 give 0.0058. Is that scale intended?
Would you take a PR for the fix above? This was found and reproduced with AI assistance (Claude Opus 5.5). I can follow up on review.
- Dominant language
- Jupyter Notebook
- Stars
- 726
- Forks
- 78
- Avg merge
- 3d 13h
- Merged PRs (30d)
- 24
Getting set up
- Ships a Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from NVIDIA/torch-harmonics
-
Difficulty 3/5 1-2 days Newbie friendliness 69/100
NVIDIA/torch-harmonics#350 ·
Maintainers usually reply within 1 day
-
PyTorch 2.15 / CUDA 13.4 / Python 3.15Possibly taken @mwyau claimed this 4 days ago. Open
Difficulty 5/5 Over a week Newbie friendliness 20/100
NVIDIA/torch-harmonics#345 · 2 comments ·
Maintainers usually reply within 1 day
-
enhancement
Difficulty 5/5 Over a week Newbie friendliness 12/100
NVIDIA/torch-harmonics#321 ·
Maintainers usually reply within 1 day
-
Double backward through distributed layers silently drops the communication termsMay be free again @dalyathan claimed this 7 days ago, and no pull request is open. Openbug
Difficulty 4/5 3-5 days Newbie friendliness 35/100
NVIDIA/torch-harmonics#318 · 2 comments ·
Maintainers usually reply within 1 day
-
Support for Shell modelsMay be free again @azrael417 claimed this 35 days ago, and no pull request is open. Open
NVIDIA/torch-harmonics#298 · 1 assignee ·
Maintainers usually reply within 1 day
All issues in NVIDIA/torch-harmonics
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
PyTomography/PyTomography#431 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 78/100
RVC-Boss/GPT-SoVITS#2869 ·
Maintainers usually reply within 1 day
-
bug testing
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
code-health
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
tinaudio/synth-setter#3750 ·
Maintainers usually reply within 1 day