""" Integration tests for the full BetaSpectrum pipeline. These are integration tests that verify end-to-end workflows. They are NOT component-level tests — they test that components work together correctly. """ import numpy as np from beta_spectrum import BetaSpectrum, SpectrumConfig class TestBetaSpectrumIntegration: """Full pipeline integration tests.""" def test_from_config_basic(self): """The most basic use case: create a spectrum from config and evaluate it. This is the entry point shown in README.md — if this fails, nothing works. We test with a medium-Z nucleus (Z=20 calcium) which exercises all components. """ config = SpectrumConfig( Z_parent=19, Z_daughter=20, A_number=40, endpoint_MeV=2.5 ) spectrum = BetaSpectrum.from_config(config) W, E_MeV = spectrum.get_energy_grid(config) assert len(W) == len(E_MeV), "Energy grids must have same length" assert len(W) > 0, "Grid must not be empty" def test_spectrum_evaluates_without_error(self): """Calling the spectrum object should return a valid numpy array.""" config = SpectrumConfig( Z_parent=19, Z_daughter=20, A_number=40, endpoint_MeV=2.5 ) spectrum = BetaSpectrum.from_config(config) W, _ = spectrum.get_energy_grid(config) values = spectrum(W) assert isinstance(values, np.ndarray), "Output must be a numpy array" assert values.shape == W.shape, "Output shape must match input shape" def test_all_components_positive(self): """Every component and the total product must be positive.""" config = SpectrumConfig( Z_parent=19, Z_daughter=20, A_number=40, endpoint_MeV=2.5 ) spectrum = BetaSpectrum.from_config(config) W, _ = spectrum.get_energy_grid(config) # Filter out values exactly at the endpoint (where phase space is zero) mask = np.isfinite(spectrum(W)) & (spectrum(W) > 0) assert np.any(mask), "At least some finite positive values expected" class TestBetaSpectrumToggles: """Test that component toggling works correctly.""" def test_all_disabled(self): """With all corrections disabled, the core remains — still valid output.""" config = SpectrumConfig( Z_parent=19, Z_daughter=20, A_number=40, endpoint_MeV=2.5, use_screening=False, use_finite_size=False, use_charge_dist=False, use_exchange=False, use_radiative=False, ) spectrum = BetaSpectrum.from_config(config) W, _ = spectrum.get_energy_grid(config) values = spectrum(W) assert len(values) > 0, "Should return non-empty array" assert np.all(np.isfinite(values)), "All values must be finite" def test_core_equals_phase_space_times_fermi(self): """Core components are unconditional: with corrections disabled the spectrum equals phase space × Fermi function exactly.""" config_minimal = SpectrumConfig( Z_parent=19, Z_daughter=20, A_number=40, endpoint_MeV=2.5, use_screening=False, use_finite_size=False, use_charge_dist=False, use_exchange=False, use_radiative=False, ) spectrum = BetaSpectrum.from_config(config_minimal) W, _ = spectrum.get_energy_grid(config_minimal) values = spectrum(W) components = spectrum.calculate_components(W) assert set(components.keys()) == {"PhaseSpace", "Fermi"} expected = components["PhaseSpace"] * components["Fermi"] assert np.allclose(values, expected, rtol=1e-10) assert np.all(np.isfinite(values)), "Core-only output must be finite" class TestBetaSpectrumComponentExtraction: """Test calculate_components() returns correct per-component data.""" def test_component_names_match(self): """Component names should correspond to class names (minus suffixes).""" config = SpectrumConfig( Z_parent=19, Z_daughter=20, A_number=40, endpoint_MeV=2.5 ) spectrum = BetaSpectrum.from_config(config) W, _ = spectrum.get_energy_grid(config) components = spectrum.calculate_components(W) assert isinstance(components, dict), "Must return a dictionary" assert len(components) > 0, "Should have at least one component" class TestBetaSpectrumRealisticConfig: """Test with realistic physics parameters.""" def test_uranium_spectrum(self): """Heavy nucleus (Z=92) — tests numerical stability at extreme Z. Uranium-238 decays via alpha, but we can still evaluate the beta spectrum shape for testing purposes. High Z stresses the Fermi function's loggamma. """ config = SpectrumConfig( Z_parent=91, Z_daughter=92, A_number=238, endpoint_MeV=0.5 ) spectrum = BetaSpectrum.from_config(config) W, E = spectrum.get_energy_grid(config) values = spectrum(W) assert np.all(np.isfinite(values)), "Uranium must not produce NaN/inf" assert len(values) > 10, f"Grid should have many points: got {len(values)}" def test_low_Z_spectrum(self): """Light nucleus (Z=6 carbon-14 beta decay).""" config = SpectrumConfig( Z_parent=5, Z_daughter=6, A_number=14, endpoint_MeV=0.156, # ~156 keV for C-14 ) spectrum = BetaSpectrum.from_config(config) W, E = spectrum.get_energy_grid(config) values = spectrum(W) assert np.all(np.isfinite(values)), "C-14 must not produce NaN/inf" # Phase space should peak in the bulk for low-Q decay assert np.any(values > 0), "Should have non-zero spectrum values"