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 ( |
|
Use the bundled sample Hamiltonians |
|
Run the notebooks ( |
|
Run the regression suite ( |
|
Speed up large scans (installed by default) |
|
Build this documentation |
the contents of |
scipy is used only by the test suite, to check the evolution operator
against an independent matrix exponential. The library itself never imports
it.
Important
numba is a base dependency as of 1.13.0, not an optional extra, so the
compiled path is what a plain install gets. It brings a ceiling with it:
numba declares an upper bound on numpy (0.66.0 requires
numpy<2.5), and installing into an environment holding a newer numpy
than that will downgrade it. The bound is numba’s own and cannot be
avoided from this side. If the newest numpy matters more than the speed,
install with --no-deps and add numpy yourself, or leave numba
installed and set fastkernels.USE_NUMBA to False.
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 numba still has a
wheel, which is why it is not excluded by a version marker — every Python
from 3.9 to 3.14 resolves a working numba.
Installing
From PyPI (recommended)
pip install nuoscprobexact
That is the whole installation, and it includes the compiled backend:
numpy and numba both arrive, and large scans run on compiled kernels
without anything further being asked for.
The optional extras add what each task needs, and can be combined:
pip install "nuoscprobexact[fast]" # numba; a base dependency, so a no-op
pip install "nuoscprobexact[notebooks]" # Jupyter, matplotlib and scipy
pip install "nuoscprobexact[test]" # pytest, scipy and coverage
pip install "nuoscprobexact[docs]" # Sphinx and friends
This puts oscprob2nu, oscprob3nu, oscprob4nu, hamiltonians2nu,
hamiltonians3nu, hamiltonians4nu, globaldefs, fastkernels,
slabs and earth on your Python path under exactly those names — the
same names the paper and the worked examples use.
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 Numba
backend, which stand down when numba is not installed. Since numba
is a base dependency, that now happens only if it has been uninstalled
deliberately, so the usual result is the whole suite running. 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
├── RELEASE_NOTES.md # What changed since the published version
├── LICENSE # MIT license
├── README.md # Project overview and worked examples
├── pyproject.toml # Packaging metadata and pytest configuration
├── MANIFEST.in # Keeps the test suite out of the source distribution
├── 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
│ ├── notebooks.rst # The twenty tutorial notebooks, in reading order
│ ├── 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
│ ├── anim_cp.gif # The demonstration reel, one scene per file
│ ├── anim_earth.gif
│ ├── anim_slabs.gif
│ ├── anim_sterile.gif
│ ├── 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_anim_cp.png
│ ├── gallery_anim_earth.png
│ ├── gallery_anim_slabs.png
│ ├── gallery_anim_sterile.png
│ ├── gallery_biprobability.png
│ ├── gallery_earth.png
│ ├── gallery_long_range.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
│ ├── 19_animations.ipynb # Four animated scenes, as stills; the reel they came from
│ ├── 20_arbitrary_hamiltonian.ipynb # A long-range force, carried through three profiles
│ ├── make_notebooks.py # Generates and executes all of the above
│ └── coastlines_crude.json # Coastlines for the Earth cutaway, from GSHHS
├── 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 # Numba kernels, with a NumPy fallback
│ ├── slabs.py # Propagation across adjacent slabs
│ └── earth.py # PREM, chord geometry, and Earth crossings
├── tools/ # Scripts that are not part of the package
│ └── make_demo_video.py # Joins and shrinks the clips notebook 19 renders
├── resources/ # Things the repository carries but does not install
│ ├── paper/ # Source of the paper that documents this library
│ │ ├── README.md # How to build it, and what each file is
│ │ ├── HANDOVER-magnus-fig11.md # What another paper needs to redraw Figure 11 with its own code on it
│ │ ├── main.tex # The paper; BibTeX, no inlined bibliography
│ │ ├── make_versions.py # Derives the clean and the diffed versions
│ │ ├── baseline_cpc_v1.tex # The published version, for the diff
│ │ ├── refs.bib # The bibliography, read by BibTeX at build
│ │ ├── elsarticle.cls
│ │ ├── elsarticle-num.bst
│ │ └── figs/ # Every figure, fourteen of them from notebook 10
│ │ ├── validation.pdf
│ │ ├── prob_2nu_vs_energy_compare.pdf
│ │ ├── prob_3nu_vs_energy_compare.pdf
│ │ ├── slabs_composition.pdf
│ │ ├── earth_oscillogram.pdf
│ │ ├── density_arrangement.pdf
│ │ ├── sterile_earth_oscillogram.pdf
│ │ ├── architecture.pdf
│ │ ├── performance.pdf
│ │ ├── exact_vs_approximations.pdf
│ │ ├── speed_accuracy.pdf
│ │ ├── scan_sensitivity.pdf
│ │ ├── prem_speed_accuracy.pdf
│ │ ├── prem_speed_accuracy_3plus1.pdf
│ │ ├── speed_accuracy_combined.pdf
│ │ ├── lri_earth.pdf
│ │ └── Notes_on_SU_n__probability_relations.pdf
│ └── tools/ # Tooling for the paper, not for the library
│ └── manuscript/ # Checks the prose of main.tex against the criteria beside them
│ ├── CHECKLIST.md # The audit criteria the paper is held to
│ ├── audit_section.py # The mechanical criteria, one section at a time
│ ├── check_apparatus_prose.py # Prose about the manuscript rather than about neutrinos
│ ├── check_convolution.py # Sentence shapes a reader has to unpick: abstract predicates and echoed heads
│ ├── check_dangles.py # Dangling last lines, over the typeset page
│ ├── check_informality.py # Colloquial register in a technical text
│ ├── check_llm_tells.py # Vocabulary and rhythms that read as machine-written
│ ├── check_numerals.py # Every quoted number, ranked by how badly it could be wrong
│ ├── check_oxford.py # Three-item lists missing the serial comma
│ ├── check_paragraphs.py # Paragraph length and sentence count, from the source
│ ├── check_scale_words.py # Adjectives of scale standing where a number should
│ ├── check_voice.py # Passive rate and first-person density, by section
│ └── check_weak_prose.py # Hedges, nominalizations, filler and vague reference
└── tests/ # Regression suite, run with pytest
├── conftest.py # Shared fixtures and path setup
├── bench/ # Fair-comparison benchmark pipeline
│ ├── artifacts/ # One JSON per measured cell, written by the run and replaced by the next one
│ ├── manifest.json # Pinned versions, build profiles, thread policy, capabilities
│ ├── README.md # Entry point: state of play and where each decision is recorded
│ ├── ADVERSARIAL.md # What the adversarial check must try to break
│ ├── FINDINGS.md # What that check broke, and how each claim was verified
│ ├── OBJECTIONS.md # Each objection, the measurement answering it, and its test
│ ├── requirements.lock # Exact Python versions the benchmark venv installs
│ ├── build.sh # Clones, hash-verifies and builds all seven at their pins
│ ├── bench.hpp # The C++ harness: owns main(), every clock, the statistics
│ ├── machine.py # Environment capture, the canary, and the rejection rule
│ ├── conversions.py # The one place a physical conversion factor is computed
│ ├── gen_conversions.py # Emits conversions.h so no adapter carries a physical literal
│ ├── adapters/ # One adapter per code, physics only
│ │ ├── nufast_earth.cpp # NuFast-Earth, batched over energy and zenith, invariants hoisted
│ │ ├── nufast_lbl.cpp # NuFast-LBL, one batched call over the whole energy vector
│ │ ├── prob3.cpp # Prob3++, looped in C++ because that is its interface
│ │ ├── globes.cpp # GLoBES, looped, chord decomposition hoisted
│ │ ├── nuoscprobexact.py # This library, batched
│ │ ├── second_order.py # The second-order expansion in alpha and s13, as a measured point
│ │ ├── nusquids.py # nuSQuIDS through its multiple-energy constructor
│ │ └── nucraft.py # nuCraft, whose batching is interface-only
│ ├── runner.py # The Python harness; the only place Python timing happens
│ ├── reference.py # One 50-digit reference per code, in that code's own convention
│ ├── audit_reference.py # Measures whether each reference matches the code it judges
│ ├── sweep_accuracy.py # Every code against its own reference, over every precision knob
│ ├── run_all.py # The run matrix: which code, which grid, which protocol, which knob
│ ├── check_neutrality.py # Checks that a harness change moved the spread and not the speed
│ ├── emit_figures.py # Turns the measured artifacts into the files the paper figures draw
│ ├── audit_objections.py # Checks the measured output against each objection, one at a time
│ ├── accuracy_const.json # Accuracy of every code on the constant-density grid, per knob
│ ├── accuracy_chord.json # Accuracy of every code on the Earth chord, per knob
│ ├── accuracy_discretisation.json # Accuracy against the layer count, for the code whose precision knob is something else
│ ├── earth_plane.json # The Earth speed-accuracy plane, three flavors, for the figure
│ ├── scan_sensitivity.json # What each code costs when a fit moves Dmsq31 rather than delta_CP
│ ├── speed_accuracy_plane.json # Every timed point paired with the accuracy its own setting reached
│ └── reference_audit.json # What that audit measured; the notebook renders it, never types it
├── test_bench_pipeline.py # The benchmark pipeline's fairness invariants
├── test_bench_behaviour.py # What an adapter does, not where its constants came from
├── gen_stiff_reference.py # Regenerates the fifty-digit four-flavor oracle
├── stiff_reference.json # That oracle, frozen: nine Hamiltonians in hexadecimal floats
├── 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_batching_and_tolerance.py # Energy batching, and the rtol/atol refinement
├── bit_capture.py # Exact-bit capture, for refactors meant to be invisible
├── bit_compare.py # Compares two captures, in ulps
├── test_documented_figures.py # Keeps the quoted performance figures agreeing
├── test_paper.py # Keeps the paper agreeing with the repository
├── 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
├── nusquids_scan.py # Regenerates the frozen nuSQuIDS energy scan
├── nusquids_scan.json # That scan, for the paper's figures
├── nufast_scan.json # NuFast-LBL at two Newton settings
├── speed_accuracy.json # The six-code constant-density speed-accuracy plane
├── timing_other_codes.json # Timings behind the performance figure
├── measure_performance_scaling.py # Freezes the three call routes against stack size, on a quiet machine
├── performance_scaling.json # That measurement, which the performance figure reads
├── prem_scan.py # Regenerates the two Earth speed-accuracy planes
├── prem_speed_accuracy.json # Those planes, at three flavors and at 3+1
├── const_density_scan.json # The exact reference for the comparison figure
├── external_drivers/ # Drivers for the codes that cannot be called from Python
│ ├── nufast_scan.cpp # Regenerates the NuFast-LBL curve of the comparison figure
│ ├── README.md # Every convention each one had to be told, and why
│ ├── gen_prem_header.py # Emits this library's PREM as a C header
│ ├── nufast_drv.cpp # NuFast-Earth on the PREM chord
│ ├── nufast_earth_prem.txt
│ ├── globes_drv.c # GLoBES on the PREM chord
│ ├── globes_prem.txt
│ ├── prob3_drv.cpp # Prob3++ on the PREM chord
│ ├── prob3_prem.txt
│ ├── perf_nusquids.py # nuSQuIDS on the constant-density scan
│ ├── globes_perf.c # GLoBES on the same scan
│ ├── prob3_perf.cpp # Prob3++ on the same scan
│ ├── nufast_lbl_perf.cpp # NuFast-LBL at two Newton settings
│ ├── const_scan.py # The 50-digit reference for the comparison figure
│ ├── globes_scan.c # GLoBES on that comparison
│ └── prob3_scan.cpp # Prob3++ on that comparison
└── test_file_tree.py # Keeps this tree in step with the repository