116 lines
4.2 KiB
Python
116 lines
4.2 KiB
Python
# tests/test_screening.py — ScreeningCorrection unit tests
|
|
|
|
"""
|
|
Why test this?
|
|
--------------
|
|
Atomic screening reduces the effective nuclear charge felt by the emitted electron,
|
|
modifying the Fermi function at low energies. The correction is typically 10⁻³–10⁻²
|
|
but grows significant near threshold where p is small.
|
|
|
|
The implementation uses a ratio method (screened vs unscreened F(Z,W)) with a
|
|
logistic switching function to smoothly turn off the correction at high energy.
|
|
|
|
We test:
|
|
|
|
1. **Near-unity at high energy**: At W >> V₀, screening should vanish → S(W) → 1.
|
|
The logistic switch ensures this. This is critical — we don't want corrections
|
|
where they shouldn't be applied.
|
|
2. **Positive values**: Screening correction must stay positive (it's a multiplicative factor).
|
|
3. **V₀ estimate合理性**: The default V0 scaling law should give reasonable values for
|
|
light and heavy nuclei.
|
|
|
|
Common practice: For components with "switching" logic, always test both the on-region
|
|
(low energy) and off-region (high energy) to verify the transition is smooth and correct.
|
|
"""
|
|
|
|
import numpy as np
|
|
|
|
from beta_spectrum.components.fermi import FermiFunction
|
|
from beta_spectrum.components.screening import ScreeningCorrection
|
|
|
|
|
|
class TestScreeningBasicProperties:
|
|
"""Test fundamental properties of the screening correction."""
|
|
|
|
def test_near_unity_at_high_energy(self, W_full):
|
|
"""At high energies (W >> V₀), S(W) → 1. The logistic switch should be ~0 here."""
|
|
|
|
# For a typical nucleus (Z=20), V₀ ≈ α·Z^(4/3)·C ≈ 0.007
|
|
# At W > 2, the switch is essentially zero
|
|
ff = FermiFunction(Z=19, A=40)
|
|
sc = ScreeningCorrection(ff)
|
|
|
|
high_W = np.array([3.0, 5.0])
|
|
result = sc(high_W)
|
|
|
|
assert (
|
|
abs(result[0] - 1.0) < 0.1
|
|
), f"Screening must approach unity at high energy: got {result}"
|
|
|
|
def test_positive_values(self):
|
|
"""S(W) must be positive everywhere — it multiplies the spectrum."""
|
|
ff = FermiFunction(Z=40, A=95) # Zirconium
|
|
sc = ScreeningCorrection(ff)
|
|
|
|
W_test = np.linspace(1.02, 3.0, 20)
|
|
result = sc(W_test)
|
|
|
|
assert np.all(result > 0), "Screening correction must be positive"
|
|
|
|
|
|
class TestScreeningEnergyDependence:
|
|
"""Test how screening varies with energy."""
|
|
|
|
def test_low_energy_correction_is_smaller(self):
|
|
"""At low energy, screening reduces the effective Coulomb field → S(W) < 1.
|
|
|
|
Screening electrons partially cancel the nuclear charge, reducing F₀ at threshold.
|
|
The correction factor should dip below 1 near W=1 and return to 1 at high energy.
|
|
"""
|
|
ff = FermiFunction(Z=30, A=70) # Zinc
|
|
sc = ScreeningCorrection(ff)
|
|
|
|
low_W = np.array([1.05])
|
|
mid_W = np.array([2.0])
|
|
|
|
s_low = sc(low_W)[0]
|
|
s_mid = sc(mid_W)[0]
|
|
|
|
# At low energy, correction is active and reduces the spectrum
|
|
assert s_low < 1.1, f"Low-energy screening should suppress: {s_low}"
|
|
# At mid energy, it's closer to unity
|
|
assert abs(s_mid - 1.0) < abs(
|
|
s_low - 1.0
|
|
), "Correction must decrease toward unity as W increases"
|
|
|
|
|
|
class TestScreeningParameters:
|
|
"""Test that parameter choices affect behavior correctly."""
|
|
|
|
def test_custom_V0_used(self):
|
|
"""If V₀ is explicitly provided, it should be used instead of the estimate."""
|
|
ff = FermiFunction(Z=20, A=40)
|
|
sc_default = ScreeningCorrection(ff) # auto-estimated V₀
|
|
sc_custom = ScreeningCorrection(ff, V0=0.1)
|
|
|
|
low_W = np.array([1.05])
|
|
result_default = sc_default(low_W)[0]
|
|
result_custom = sc_custom(low_W)[0]
|
|
|
|
# Different V₀ should give different results at low energy
|
|
assert (
|
|
abs(result_default - result_custom) > 0
|
|
), "Custom V₀ must change the screening behavior"
|
|
|
|
|
|
class TestScreeningOutputShape:
|
|
"""Basic shape/type checks."""
|
|
|
|
def test_output_shape(self):
|
|
ff = FermiFunction(Z=20, A=40)
|
|
sc = ScreeningCorrection(ff)
|
|
for n in [1, 5, 100]:
|
|
W_test = np.linspace(1.1, 3.0, n)
|
|
result = sc(W_test)
|
|
assert result.shape == W_test.shape
|