# Beta Spectrum Calculation Toolkit A modular Python package for calculating high-precision **beta decay energy spectra**. It models the spectral shape of nuclear transitions by combining well-established physical corrections into a coherent, configurable pipeline. Since v0.5.0 the project scope is the spectrum-shape **calculation** alone: detector response modeling, data calibration and fitting/extraction live in separate, dedicated projects. **Features:** - Numerical evaluation from near-zero kinetic energies up to the endpoint - Selective inclusion of higher-order corrections via `SpectrumConfig` toggles - Clean, composable component architecture - Built-in visualization and CSV export via `BetaSpectrumAnalyzer` - Normal mode: single spectrum plot with nuclear data header - Debug mode (`-vv`): 4-panel view with all correction factors - All plots include commit hash and UTC timestamp for traceability - Multi-branch decays with intensity weighting and per-branch endpoints - Nuclear data retrieval from ENSDF via paceENSDF (`bs_pnpi --nuclide Tc99`) - Comprehensive scientific reference in [`docs/`](docs/) - Debug verification workflow: `bs_pnpi -vv` validates all parameters end-to-end ______________________________________________________________________ ## Core Concept The beta spectrum is constructed as a product of independent correction factors: $$N(W) \\propto p W (W_0 - W)^2 \\times \\prod_i C_i(W)$$ Each $C_i(W)$ is an independently implemented and toggleable component. The phase space and the Fermi function are the unconditional core: every spectrum carries their product, and the higher-order corrections multiply on top of it. ______________________________________________________________________ ## Project Structure ``` beta_shape_pnpi/ ├── pyproject.toml ├── docs/ ← Obsidian knowledge base (physics theory) │ ├── README.md ← Vault overview + navigation index │ ├── 00-beta-spectrum-overview.md │ ├── 02-phase-space.md │ └── corrections/ ← One note per correction component ├── beta_spectrum/ ← theory-only core package │ ├── __init__.py ← Public API │ ├── base.py ← Abstract SpectrumComponent base class │ ├── constants.py ← Physical constants (natural units, m_e = 1) │ ├── utils.py ← T<->W conversion, momentum, nuclear radius │ ├── spectrum.py ← BetaSpectrum + SpectrumConfig + BranchConfig │ ├── nuclear_data.py ← paceENSDF integration + JSON input support │ ├── cli.py ← Command-line interface (bs_pnpi) │ ├── logging_utils.py ← Structured logging helpers │ ├── components/ │ │ ├── phase_space.py ← Phase space shape (p·W·(W₀−W)²) │ │ ├── fermi.py ← Coulomb correction (loggamma for stability) │ │ ├── finite_size.py ← L0 expansion + charge distribution U term │ │ ├── screening.py ← Atomic electron screening (ratio method) │ │ ├── exchange.py ← Hayen-2018 empirical fit coefficients │ │ └── radiative.py ← Outer radiative corrections, soft-photon resummation │ ├── data/ │ │ └── exchange_coeff.csv ← Tabulated coefficients for X(Z,W), Z=2..120 │ └── output/ ← visualization, CSV export │ ├── core.py ← BetaSpectrumAnalyzer │ ├── csv_export.py ← CSV export │ └── visualization.py ← Plotting built from exported CSV ├── data/ │ └── custom_input_example.json ← Sample JSON input file ├── notebooks/ ← Calculator notebook + nbmake conftest ├── output/ ← Test run outputs (gitignored except .gitkeep) ├── tests/ │ ├── physics/ ← Physics component tests │ ├── quality/ ← Code quality tests (logging, API, nuclear data) │ ├── integration/ ← CLI and pipeline integration tests │ └── common/ ← Shared hypothesis + property tests ├── scripts/ ← docs_check.py, notebook runners ├── AGENTS.md ← Agent instructions: stack, git workflow, TDD, quality gates ├── CONVENTIONS.md ← Physics conventions: natural units, energy representation, component design ├── TODO.md ← Development roadmap ``` ______________________________________________________________________ ## Architecture ### `SpectrumComponent` (base class) Defined in `beta_spectrum/base.py`. All physics corrections inherit from this abstract base and implement: ```python def __call__(self, W: np.ndarray) -> np.ndarray ``` This makes every component stateless (or minimally stateful), vectorized, and composable. ### `BetaSpectrum` Defined in `beta_spectrum/spectrum.py`. Orchestrates the calculation: - Combines components multiplicatively via `__call__(W)` - Factory method `from_config(config)` for declarative setup - Generates energy grids with `get_energy_grid(config)` - Returns individual component values via `calculate_components(W)` ### `BetaSpectrumAnalyzer` In `beta_spectrum.output`. Provides debugging and visualization tools: - `total_spectrum(normalize=True/False)` — computed spectrum array; raises `ValueError` when every branch is degenerate (non-positive integral) - `physical_branch_spectrum(i)` — physical spectrum of branch *i*: the core (phase space × Fermi function) times every enabled higher-order correction, clamped beyond the branch endpoint (unnormalized) - `universal_factor()` — product of the components shared across all branches (Fermi function and the universal higher-order corrections) - `plot_analysis(save_path=None, show_components=True)` — plot with two modes: - `show_components=True` (debug mode): 4-panel figure (spectrum, components, cumulative effect, deviations) - `show_components=False` (normal mode): single spectrum plot with nuclear data header - All plots include commit hash and UTC timestamp for traceability - Plots are built *from the exported CSV file* (temporary full-verbosity export), so a plot always shows exactly the bytes the CSV export produces - `export_to_csv(filename)` — export via pandas DataFrame with YAML-style metadata header: - the basic spectrum is always the core `phase_space × fermi` (the header carries a `# core:` line); finite size, charge distribution, screening, exchange and radiative are higher-order corrections - `branch_{i}_spectrum` columns contain the *physical* branch spectra weighted by intensity and normalized so that the sum of branch columns equals `total_spectrum` - with `separate_branches=True` (CLI `--separate-branches`) each branch column integrates to 1 (intensity ignored, no total_spectrum column) - `parse_spectrum_csv(path)` — read an exported CSV and its metadata header (module function in `beta_spectrum.output.visualization`) - `get_data()` — return all numerical data for downstream analysis ______________________________________________________________________ ## Quick Start ```python from beta_spectrum import SpectrumConfig, BetaSpectrum from beta_spectrum.output import BetaSpectrumAnalyzer config = SpectrumConfig( Z_parent=90, # Thorium-232 (example) Z_daughter=91, A_number=232, endpoint_MeV=4.8, # Q-value transition_type="A", # Allowed transition (default) ) spectrum = BetaSpectrum.from_config(config) W, E_MeV = spectrum.get_energy_grid(config) values = spectrum(W) analyzer = BetaSpectrumAnalyzer(spectrum, config) analyzer.plot_analysis("analysis.png") # Normal mode (single spectrum plot) analyzer.plot_analysis("debug.png", show_components=True) # Debug mode (4-panel) analyzer.export_to_csv("spectrum.csv") # Export data to CSV ``` ### From the command line ```bash bs_pnpi --nuclide Tc99 -vv --output spectrum.csv # ENSDF lookup + debug export bs_pnpi --input data/custom_input_example.json # JSON-driven configuration ``` CLI flags (`--e-step`, `--neutrino-mass`, `--intensity-cutoff`, `--csv-separator`, `--separate-branches`) override JSON input values only when explicitly given; every long option has a single-letter alias. ______________________________________________________________________ ## Nuclear Data (paceENSDF) `beta_spectrum.nuclear_data` retrieves decay data automatically: `get_decay_info_from_paceENSDF()`, `decay_info_to_config()`, and `create_config_from_source("paceENSDF", nuclide="Tc99")`. Z, A, endpoint energies, half-life, spin/parity, branch intensities, log*ft and forbiddenness are populated from ENSDF; per-branch endpoints are the measured transition endpoint energies (max endpoint across branches defines the spectrum endpoint). paceENSDF is an optional extra: `pip install ".[ensdf]"`. ______________________________________________________________________ ## Implementation Status For a complete list of implemented and planned features, see [TODO.md](TODO.md). Key highlights: - **Physics corrections**: phase space, Fermi function, finite size, screening, exchange, radiative (with delta_cut resummation) - **Multi-branch decay**: intensity-weighted sum of branch spectra, per-branch calculators with branch-specific endpoint, transition type (from ENSDF), and `--intensity-cutoff` CLI filter - **CLI**: `bs_pnpi` with full paceENSDF integration, structured logging (`-v`/`-vv`/`-q`), `--dry-run`, `--version`, `--log-file` - **CSV metadata headers** with nuclide, decay and calculation parameters plus software version and git commit hash