Source code for tests.test_diffpes.test_maths.test_gaunt

"""Validate Gaunt coefficient table.

Extended Summary
----------------
The tests validate the precomputed Gaunt coefficient table for angular
coupling in dipole matrix elements. Gaunt coefficients encode the integral
of three spherical harmonics over the unit sphere. They enforce the dipole
selection rules ``Delta l = +/-1`` and ``Delta m = q``. The tests verify the
table shape and both selection rules. They also verify allowed transitions,
reproducible construction, real values, and the positive fundamental s-p
coupling.

"""

import math

import chex
import jax.numpy as jnp
import pytest
from jaxtyping import Array

from diffpes.maths import GAUNT_TABLE, build_gaunt_table, gaunt_lookup
from diffpes.types import L_MAX


[docs] class TestBuildGauntTable: """Validate the precomputed Gaunt coefficient table. Validates the module-level ``GAUNT_TABLE`` array and the ``gaunt_lookup`` accessor function. ``build_gaunt_table(l_max=4)`` builds the table at import time. The table stores real-valued Gaunt coefficients indexed by (l, m, q, l', m'). Tests systematically check selection rules, allowed transitions, table shape, dtype, reproducibility, and a known analytical value. :see: :func:`~diffpes.maths.build_gaunt_table` """
[docs] def test_table_shape(self) -> None: """Verify the precomputed table has the expected 5-D shape. The five axes represent ``l``, ``m``, ``q``, ``l'``, and ``m'``. Their respective sizes are 5, 9, 3, 6, and 11 for ``l_max=4``. The test asserts the exact shape ``(5, 9, 3, 6, 11)``. Notes ----- The test builds the inputs in the test body and checks the stated property with the documented numerical or structural assertions.""" assert GAUNT_TABLE.shape == (5, 9, 3, 6, 11)
[docs] def test_selection_rule_delta_l(self) -> None: """Verify the dipole selection rule Delta l = +/-1. The test iterates over every valid table index combination. It asserts that whenever ``|l' - l| != 1`` the Gaunt coefficient is zero (< 1e-12). This is the fundamental angular momentum selection rule for electric-dipole transitions. Notes ----- The test builds the inputs in the test body and checks the stated property with the documented numerical or structural assertions.""" l: int m: int q: int lp: int val: Array for l in range(L_MAX + 1): for m in range(-l, l + 1): for q in (-1, 0, 1): for lp in range(L_MAX + 2): val = gaunt_lookup(l, m, q, lp, m + q) if abs(lp - l) != 1: assert abs(val) < 1e-12, ( f"Expected zero for l={l}, lp={lp} (Delta_l != ±1), " f"got {val}" )
[docs] def test_s_to_p_nonzero(self) -> None: """Verify the s -> p allowed transition has a nonzero Gaunt coefficient. Looks up G(l=0, m=0, q=0, l'=1, m'=0), the prototypical electric-dipole transition from an s-orbital to pz. Asserts ``|G| > 1e-6``, confirming the table correctly encodes the coupling. Notes ----- The test builds the inputs in the test body and checks the stated property with the documented numerical or structural assertions.""" val: Array val = gaunt_lookup(0, 0, 0, 1, 0) assert abs(val) > 1e-6
[docs] def test_p_to_s_nonzero(self) -> None: """Verify the p -> s allowed transition has a nonzero Gaunt coefficient. Looks up G(l=1, m=0, q=0, l'=0, m'=0), the reverse of the s->p transition. Asserts ``|G| > 1e-6``, confirming reciprocity of the Gaunt integral. Notes ----- The test builds the inputs in the test body and checks the stated property with the documented numerical or structural assertions.""" val: Array val = gaunt_lookup(1, 0, 0, 0, 0) assert abs(val) > 1e-6
[docs] def test_p_to_d_nonzero(self) -> None: """Verify the p -> d allowed transition has a nonzero Gaunt coefficient. Looks up G(l=1, m=0, q=0, l'=2, m'=0), a higher-l allowed dipole transition. Asserts ``|G| > 1e-6``, confirming that the table covers transitions beyond the lowest-order s<->p coupling. Notes ----- The test builds the inputs in the test body and checks the stated property with the documented numerical or structural assertions.""" val: Array val = gaunt_lookup(1, 0, 0, 2, 0) assert abs(val) > 1e-6
[docs] def test_forbidden_delta_m(self) -> None: """Verify the magnetic selection rule forbids ``|Delta m| > 1``. Looks up G(l=2, m=0, q=0, l'=1, m'=2) where m' - m = 2 != q = 0. The selection rule requires ``m' = m + q`` and forbids ``m'=2`` here. The test asserts the coefficient is zero (< 1e-12). Notes ----- The test builds the inputs in the test body and checks the stated property with the documented numerical or structural assertions.""" val: Array val = gaunt_lookup(2, 0, 0, 1, 2) assert abs(val) < 1e-12
[docs] def test_rebuild_matches_precomputed(self) -> None: """Verify that rebuilding the table reproduces the precomputed values. The test calls ``build_gaunt_table(l_max=4)`` at test time and compares to the module-level ``GAUNT_TABLE`` with ``jnp.allclose``. This guards against silent corruption of the cached table and confirms deterministic construction. Notes ----- The test builds the inputs in the test body and checks the stated property with the documented numerical or structural assertions.""" table2: Array table2 = build_gaunt_table(l_max=4) assert jnp.allclose(GAUNT_TABLE, table2)
[docs] def test_real_valued(self) -> None: """Verify the table dtype is float64 (no imaginary residuals). The Gaunt coefficients for real spherical harmonics are purely real. Asserts the table dtype is ``jnp.float64``, confirming the construction did not accidentally introduce complex values. Notes ----- The test builds the inputs in the test body and checks the stated property with the documented numerical or structural assertions.""" assert GAUNT_TABLE.dtype == jnp.float64
[docs] def test_known_value_y00_dipole(self) -> None: """Verify the s-to-p Gaunt coefficient is a known positive value. The integral G(0, 0, 0, 1, 0) = integral Y_0^0 * Y_1^0 * Y_1^0 dOmega is analytically positive. While the exact numerical value depends on normalization conventions, the sign is unambiguous. Asserts val > 0.0 as a consistency check against sign errors in the Condon-Shortley phase convention. Notes ----- The test builds the inputs in the test body and checks the stated property with the documented numerical or structural assertions.""" val: Array val = gaunt_lookup(0, 0, 0, 1, 0) assert val > 0.0
[docs] class TestGauntLookup: """Validate :func:`~diffpes.maths.gaunt_lookup`. Covers indexed retrieval from the canonical dipole Gaunt table for one allowed transition and one forbidden magnetic transition. :see: :func:`~diffpes.maths.gaunt_lookup` """
[docs] def test_matches_canonical_table_entries(self) -> None: """Match lookup results to the canonical table at allowed and zero entries. The accessor must preserve both the positive s-to-p coefficient and an exactly forbidden magnetic channel under the package indexing convention. Notes ----- The test evaluates two scalar lookups and compares them with their directly indexed ``GAUNT_TABLE`` entries at zero absolute and relative tolerance. """ allowed: Array forbidden: Array expected_allowed: Array expected_forbidden: Array allowed = gaunt_lookup(0, 0, 0, 1, 0) forbidden = gaunt_lookup(0, 0, 1, 1, 0) expected_allowed = GAUNT_TABLE[0, L_MAX, 1, 1, L_MAX] expected_forbidden = GAUNT_TABLE[0, L_MAX, 2, 1, L_MAX] chex.assert_trees_all_close( allowed, expected_allowed, rtol=0.0, atol=0.0, ) chex.assert_trees_all_close( forbidden, expected_forbidden, rtol=0.0, atol=0.0, )
[docs] class TestWigner3jSelectionRules: """Validate internal Wigner 3-j and complex Gaunt zero paths. The tests exercise early returns in ``_wigner3j`` and ``_complex_gaunt``. These branches return 0.0 for violations of selection rules. :see: :func:`~diffpes.maths.build_gaunt_table` """
[docs] def test_abs_m_exceeds_j_returns_zero(self) -> None: """Verify ``|m1| > j1`` causes _wigner3j to return 0.0. The test constructs a call where ``|m1| = 2 > j1 = 1``, violating the ``|mi| <= ji`` constraint, and asserts the result is 0.0 (line 111). Notes ----- The test builds the inputs in the test body and checks the stated property with the documented numerical or structural assertions.""" val: Array from diffpes.maths.gaunt import _wigner3j val = _wigner3j(1, 1, 0, 2, -1, -1) assert val == 0.0
[docs] def test_triangle_inequality_violated_returns_zero(self) -> None: """Verify triangle inequality violation causes _wigner3j to return 0.0. The test uses j1=2, j2=1, j3=0 where ``j3 < |j1 - j2| = 1``, violating the triangle inequality, and asserts the result is 0.0 (line 113). Notes ----- The test builds the inputs in the test body and checks the stated property with the documented numerical or structural assertions.""" val: Array from diffpes.maths.gaunt import _wigner3j val = _wigner3j(2, 1, 0, 0, 0, 0) assert val == 0.0
[docs] def test_complex_gaunt_zero_w3j_000_returns_zero(self) -> None: """Verify that zero w3j_000 (parity violation) causes _complex_gaunt to return 0.0. The three-j symbol ``(2,1,0 | 0,0,0)`` is zero for this input. The parity rule gives zero because ``l1+l2+l3 = 3`` is odd. The test asserts that the complex Gaunt integral returns 0.0. Notes ----- The test builds the inputs in the test body and checks the stated property with the documented numerical or structural assertions.""" val: Array from diffpes.maths.gaunt import _complex_gaunt val = _complex_gaunt(2, 0, 1, 0, 0, 0) assert val == 0.0