Files
beta_shape_pnpi/tests/test_exchange.py
T

120 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# tests/test_exchange.py — ExchangeCorrection unit tests
"""
Why test this?
--------------
The atomic exchange correction X(Z,W) can be very large for heavy nuclei at low energy
— exceeding 20% (i.e., factor of 1.2+) near threshold. This is because the emitted
electron's wave function overlaps with bound atomic electrons, modifying the phase space.
Unlike other corrections that are small perturbations (~1%), exchange is a genuine
O(α) effect that grows dramatically as p → 0 (because η = αZW/p diverges).
We test:
1. **Large correction at low energy for heavy nuclei**: At Z=80, W→1, X >> 1.
This is the hallmark of exchange — if it's small here, the fit coefficients
are wrong or the formula is broken.
2. **Approaches unity at high energy**: Exchange vanishes as W increases because
the emitted electron becomes distinguishable from bound electrons (higher momentum).
3. **No NaN/inf values**: The empirical fit involves sin() and division by Wⁱ —
we must ensure numerical stability across the full grid.
4. **Coefficients loaded correctly**: Verify that Z=2..100 are all in the CSV.
Common practice: For components with dramatic energy dependence, test both extremes
(low-energy blowup and high-energy suppression) to verify the shape is correct.
"""
import numpy as np
import pytest
from beta_spectrum.components.exchange import ExchangeCorrection
class TestExchangeLowEnergy:
"""Test exchange correction at low energies where it's most significant."""
def test_large_correction_for_heavy_nucleus_at_threshold(self):
"""For Z=80 (mercury), X >> 1 near threshold — can be > 20%.
This is the defining characteristic of the exchange correction. If this
fails, the empirical fit coefficients are wrong or the formula has a sign error.
"""
ex = ExchangeCorrection(Z=80)
low_W = np.array([1.1]) # T ≈ 50 keV — still "low" but above cutoff
result = ex(low_W)
assert result[0] > 1.0, (
f"Exchange must enhance spectrum at low energy for heavy Z: got {result[0]}"
)
def test_correction_grows_as_energy_decreases(self):
"""X(W) should increase as W → 1 (lower momentum → stronger exchange)."""
ex = ExchangeCorrection(Z=50) # Tin — moderate-heavy
low_W = np.array([1.2]) # Higher energy
lower_W = np.array([1.05]) # Lower energy (above cutoff W_cut=1.005)
s_low = ex(low_W)[0]
s_lower = ex(lower_W)[0]
assert s_lower > s_low, (
f"Exchange must increase at lower energy: {s_low} → {s_lower}"
)
class TestExchangeHighEnergy:
"""Test that exchange correction vanishes at high energy."""
def test_near_unity_at_high_energy(self):
"""At W >> 1, the emitted electron is far from atomic electrons → X → 1."""
ex = ExchangeCorrection(Z=40)
high_W = np.array([3.0, 5.0])
result = ex(high_W)
assert abs(result[0] - 1.0) < 0.5, (
f"Exchange must approach unity at high energy: got {result}"
)
class TestExchangeNumericalStability:
"""Test that the empirical fit doesn't produce NaN or inf."""
def test_no_nan_across_full_range(self):
ex = ExchangeCorrection(Z=92) # Uranium — extreme case
W_test = np.linspace(1.01, 6.0, 50)
result = ex(W_test)
assert not np.any(np.isnan(result)), "No NaN values allowed"
assert not np.any(np.isinf(result)), "No infinite values allowed"
class TestExchangeCoefficientLoading:
"""Test that coefficients are loaded correctly from CSV."""
def test_all_Z_values_available(self):
"""The CSV must contain coefficients for Z=2 through Z=100+ (per Hayen Table X)."""
# Test a few representative values across the periodic table
for Z in [6, 20, 50, 80]:
ex = ExchangeCorrection(Z=Z)
assert ex.coeffs is not None, f"Missing coefficients for Z={Z}"
def test_missing_Z_raises_error(self):
"""Requesting a Z without coefficients should raise ValueError."""
# Z=1 has no electrons to exchange with — should fail gracefully
with pytest.raises(ValueError, match="No exchange coefficients"):
ExchangeCorrection(Z=1)
class TestExchangeOutputShape:
"""Basic shape checks."""
def test_output_shape(self):
ex = ExchangeCorrection(Z=30)
for n in [1, 5, 100]:
W_test = np.linspace(1.1, 3.0, n)
result = ex(W_test)
assert result.shape == W_test.shape