Installation

NuOscProbExact is written entirely in Python, so there is nothing to compile or link.

Requirements

The dependencies are deliberately minimal, and split by what you actually want to do:

To do this

You need

Compute probabilities (oscprob2nu, oscprob3nu, oscprob4nu)

numpy

Use the bundled sample Hamiltonians

numpy

Run the notebooks (notebooks/)

numpy, matplotlib, Jupyter

Run the regression suite (tests/)

numpy, pytest, scipy, coverage

Speed up large scans (optional)

numba

Build this documentation

the contents of docs/requirements.txt

scipy is used only by the test suite, to check the evolution operator against an independent matrix exponential. The library itself never imports it.

NuOscProbExact requires Python 3.9 or newer, and every release is tested on 3.9, 3.10, 3.11, 3.12, and 3.13. The floor comes from numpy.broadcast_shapes(), which the batched paths use and which arrived in NumPy 1.20; 3.9 is also the oldest version for which the optional numba backend still has a wheel.

Installing

From GitHub

Install from a clone if you want the notebooks, the worked examples from the paper, the regression suite, or a version that is not yet released:

git clone https://github.com/mbustama/NuOscProbExact.git
cd NuOscProbExact
pip install -e .

-e installs in editable mode, so edits to src/ take effect without reinstalling. The extras work the same way, for example pip install -e ".[fast,test]".

Without installing anything

The three core modules are self-contained — they need only numpy and the standard library — so copying src/oscprob2nu.py, src/oscprob3nu.py or src/oscprob4nu.py into your own project is a supported way to use NuOscProbExact. Each imports fastkernels if it is there and does without it if it is not, so a lone copy works and simply runs the NumPy path; a test copies each of the three out and exercises it that way. Adding src/ to the path works too, and is what the bundled examples do:

import sys
sys.path.append('/path/to/NuOscProbExact/src')

import oscprob3nu

Checking the installation

Run the regression suite:

pytest

Every test should either pass or be skipped, and skips are expected rather than a sign of trouble: the only tests that skip are those for the optional Numba backend, which stand down when numba is not installed — the default. Install the fast extra and the whole suite runs. It is several hundred tests and takes a few seconds.

The suite checks the SU(2), SU(3) and SU(4) machinery against independent computations: unitarity of the evolution operator, agreement with scipy.linalg.expm, agreement with the standard oscillation formulas, the d tensor and star product of the SU(3) algebra, and the sign conventions of the sample Hamiltonians. Beyond that it exercises the batched evaluation paths against the scalar ones, the two backends against each other, degenerate and near-degenerate Hamiltonians, the energies and baselines the library is actually used at, and the agreement between the type annotations and the docstrings — and it runs every example embedded in the docstrings.

Then run one of the worked examples:

cd examples
python example_3nu_vacuum.py

Note

This directory was called test/ in version 1.0.0 of the code, and is named that way in version 2 of the paper. It was renamed to examples/ to stop it being confused with tests/, which holds the regression suite.

Building the documentation

pip install -r docs/requirements.txt
cd docs
make html

The result lands in docs/build/html; open index.html.

File tree

NuOscProbExact/
├── .github/                         # Continuous integration (GitHub Actions)
│   └── workflows/
│       ├── tests.yml                # The suite: five Pythons, all three backends
│       ├── lint.yml                 # ruff, and the docs build under -W
│       ├── pages.yml                # Builds and deploys the docs to GitHub Pages
│       └── publish.yml              # Publishes to PyPI on a GitHub Release
├── .gitignore                       # Build, cache, and generated-output artefacts
├── CHANGELOG.md                     # Notable changes, rendered as a docs page
├── LICENSE                          # MIT license
├── README.md                        # Project overview and worked examples
├── pyproject.toml                   # Packaging metadata and pytest configuration
├── examples/                        # Runnable scripts, one per scenario, linked from README.md
│   ├── example_2nu_trivial.py       # Two-flavor, arbitrary Hamiltonian
│   ├── example_2nu_vacuum.py        # Two-flavor, oscillations in vacuum
│   ├── example_2nu_vacuum_coeffs.py # Two-flavor, expansion coefficients
│   ├── example_3nu_trivial.py       # Three-flavor, arbitrary Hamiltonian
│   ├── example_3nu_vacuum.py        # Three-flavor, oscillations in vacuum
│   ├── example_3nu_vacuum_coeffs.py # Three-flavor, expansion coefficients
│   ├── example_3nu_matter.py        # Three-flavor, oscillations in matter
│   ├── example_3nu_nsi.py           # Three-flavor, matter with NSI
│   └── example_3nu_liv.py           # Three-flavor, LIV background
├── docs/                            # Sphinx documentation
│   ├── Makefile                     # `make html` on Linux and macOS
│   ├── make.bat                     # `make html` on Windows
│   ├── requirements.txt             # Documentation-only dependencies
│   └── source/
│       ├── conf.py                  # Sphinx configuration
│       ├── index.rst                # Landing page
│       ├── installation.rst         # Requirements, installation, file tree
│       ├── quickstart.rst           # Shortest path to a probability
│       ├── recipes.rst              # Numerical recipes, with pre-generated figures
│       ├── methodology.rst          # The SU(2), SU(3) and SU(4) expansions
│       ├── functions.rst            # API reference, from the docstrings
│       ├── references.rst           # Bibliography
│       ├── refs.bib                 # BibTeX entries for the bibliography
│       ├── changelog.rst            # Includes the root CHANGELOG.md
│       └── _static/
│           ├── nuoscprobexact_logo.png
│           └── slabs_composition.svg  # How slabs compose, drawn for quickstart.rst
├── img/                             # Figures from earlier versions of README.md
│   ├── prob_3nu_vacuum_vs_baseline_ee_em_et.png
│   ├── prob_3nu_vacuum_vs_energy_ee_em_et.png
│   └── gallery/                     # Figures lifted from the notebooks, shown in README.md
│       ├── gallery_biprobability.png
│       ├── gallery_earth.png
│       ├── gallery_matter.png
│       ├── gallery_ordering.png
│       ├── gallery_oscillogram.png
│       ├── gallery_prem.png
│       ├── gallery_profiles.png
│       ├── gallery_sterile.png
│       ├── gallery_sterile_earth.png
│       └── gallery_vacuum.png
├── notebooks/                       # Worked examples, with their figures stored inline
│   ├── 01_basics.ipynb              # Units, one probability, and broadcasting
│   ├── 02_vacuum_oscillations.ipynb # Against baseline and against energy
│   ├── 03_matter_nsi_liv.ipynb      # Constant-density matter, NSI, and LIV
│   ├── 04_oscillogram.ipynb         # Energy-baseline maps in one call
│   ├── 05_biprobability.ipynb       # CP ellipses, in vacuum and in matter
│   ├── 06_earth_and_prem.ipynb      # PREM, chord geometry, and slabs
│   ├── 07_earth_probabilities.ipynb # Through the Earth, and between sites
│   ├── 08_unusual_density_profiles.ipynb  # Castle-wall and other hand-built profiles
│   ├── 09_performance.ipynb         # Looping vs broadcasting, and the backend
│   ├── 10_paper_figures.ipynb       # The two figures from arXiv:1904.12391
│   ├── 11_exact_vs_approximations.ipynb  # Where the textbook formulas break down
│   ├── 12_ordering_and_octant.ipynb # Normal vs inverted, and the 23 octant
│   ├── 13_antineutrinos.ipynb       # Conjugate and flip, and two ways to slip
│   ├── 14_solar_and_adiabatic_msw.ipynb  # The MSW resonance, and the cost wall
│   ├── 15_numerical_edge_cases.ipynb  # Degeneracies, and what does not go NaN
│   ├── 16_four_neutrinos.ipynb      # A 3+1 sterile state, through the SU(4) expansion
│   ├── 17_cross_checks.ipynb        # Corroboration from nuSQuIDS and Zaglauer-Schwarzer
│   ├── 18_evolution_operator.ipynb  # The operator, and the SU(n) coefficients
│   └── make_notebooks.py            # Generates and executes all of the above
├── src/                             # The library
│   ├── oscprob2nu.py                # Two-flavor probabilities, SU(2) expansion
│   ├── oscprob3nu.py                # Three-flavor probabilities, SU(3) expansion
│   ├── oscprob4nu.py                # Four-flavor probabilities, SU(4) expansion
│   ├── hamiltonians2nu.py           # Example two-flavor Hamiltonians
│   ├── hamiltonians3nu.py           # Example three-flavor Hamiltonians
│   ├── hamiltonians4nu.py           # Example four-flavor (3+1) Hamiltonians
│   ├── globaldefs.py                # Physical constants and unit conversions
│   ├── fastkernels.py               # Optional Numba kernels, with a NumPy fallback
│   ├── slabs.py                     # Propagation across adjacent slabs
│   └── earth.py                     # PREM, chord geometry, and Earth crossings
└── tests/                           # Regression suite, run with pytest
    ├── conftest.py                  # Shared fixtures and path setup
    ├── test_su3_algebra.py          # d tensor, star product, SU(3) invariants
    ├── test_oscprob4nu.py           # SU(4) algebra, quartic roots, 3+1 physics
    ├── test_evolution_operator.py   # U against an independent matrix exponential
    ├── test_probabilities.py        # Normalization, positivity, P = |U|^2
    ├── test_hamiltonians.py         # Sample Hamiltonians and sign conventions
    ├── test_reference_formulas.py   # Exact result against the standard formulas
    ├── test_matter_eigenvalues.py   # Matter spectrum, against Zaglauer-Schwarzer
    ├── test_edge_cases.py           # Degenerate and near-degenerate Hamiltonians
    ├── test_docstrings.py           # Runs the examples embedded in the docstrings
    ├── test_vectorized.py           # The batched path, against the scalar one
    ├── test_vectorized_hamiltonians.py  # Hamiltonians built for an array of energies
    ├── test_annotations.py          # Annotations, and their agreement with the docs
    ├── test_fastkernels.py          # Both backends, against each other
    ├── test_physical_scales.py      # Both backends at the scales actually used
    ├── test_slabs.py                # Slab composition, against expm
    ├── test_earth.py                # PREM, geometry, and Earth probabilities
    ├── test_documented_figures.py   # Keeps the quoted performance figures agreeing
    ├── test_version_consistency.py  # Keeps the version agreeing wherever it is implied
    ├── test_nusquids_comparison.py  # Against nuSQuIDS, an independent external code
    ├── nusquids_reference.py        # Regenerates the frozen nuSQuIDS reference data
    ├── nusquids_reference.json      # Those reference values, with their provenance
    └── test_file_tree.py            # Keeps this tree in step with the repository