Skip to content

Commit 7ff9c67

Browse files
committed
Fix spherical harmonic angle ordering
SciPy's sph_harm_y expects the polar angle before the azimuthal angle. Add degree-one value checks so future convention changes cannot pass with only shape and Y_0^0 coverage.
1 parent a3fa357 commit 7ff9c67

3 files changed

Lines changed: 29 additions & 1 deletion

File tree

CLAUDE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,11 @@ configs/ # Hydra YAML configs (datasets, tasks, backbone, optimi
244244
245245
## Testing Gotchas
246246
247+
- SciPy's `sph_harm_y` reverses both the degree/order arguments and angle
248+
semantics relative to deprecated `sph_harm`: migrate
249+
`sph_harm(m, n, azimuth, polar)` as
250+
`sph_harm_y(n, m, polar, azimuth)`. Convention tests must cover
251+
angle-dependent harmonics rather than only angle-independent `Y_0^0`.
247252
- Tests that download registered checkpoints must declare their models with a
248253
`pretrained` marker. This lets base CI deselect them with `--exclude-models`
249254
and routes them to the matching model-sweep job.

src/fairchem/core/models/utils/basis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ def forward(self, xyz: torch.Tensor) -> torch.Tensor:
290290
theta_tile = np.tile(theta.reshape(len(xyz), 1), (1, len(self.m)))
291291
phi_tile = np.tile(phi.reshape(len(xyz), 1), (1, len(self.m)))
292292

293-
harm = sph_harm_y(n_tile, m_tile, theta_tile, phi_tile)
293+
harm = sph_harm_y(n_tile, m_tile, phi_tile, theta_tile)
294294

295295
harm_mzero = harm[:, self.m == 0]
296296
harm_mnonzero = harm[:, self.m != 0]

tests/core/models/utils/test_spherical_smearing.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,26 @@ def test_spherical_smearing_edge_cases():
100100

101101
assert output_random.shape == (5, smearing.out_dim)
102102
assert torch.isfinite(output_random).all()
103+
104+
105+
def test_spherical_smearing_degree_one_values():
106+
"""
107+
Test angle-dependent degree-one spherical harmonics.
108+
"""
109+
smearing = SphericalSmearing(max_n=2, option="all")
110+
xyz = torch.tensor(
111+
[
112+
[1.0, 2.0, 3.0],
113+
[-2.0, 1.0, -1.0],
114+
],
115+
dtype=torch.float32,
116+
)
117+
normalized_xyz = xyz / xyz.norm(dim=-1, keepdim=True)
118+
119+
y00 = torch.full_like(normalized_xyz[:, 0], 1.0 / np.sqrt(4.0 * np.pi))
120+
y10 = np.sqrt(3.0 / (4.0 * np.pi)) * normalized_xyz[:, 2]
121+
y11_real = -np.sqrt(3.0 / (8.0 * np.pi)) * normalized_xyz[:, 0]
122+
y11_imag = -np.sqrt(3.0 / (8.0 * np.pi)) * normalized_xyz[:, 1]
123+
expected = torch.stack([y00, y10, y11_real, y11_imag], dim=1)
124+
125+
assert torch.allclose(smearing(xyz), expected, atol=1e-6)

0 commit comments

Comments
 (0)