nufast
Fast and accurate three-flavor neutrino oscillation probabilities in vacuum and matter.
Implementations: Rust + Zig — both first-class, production-ready.
Based on NuFast by Peter Denton (arXiv:2405.02400). The original algorithm is used in production by major neutrino experiments including T2K (via MaCH3) and JUNO.
Features
| Feature | Rust | Zig |
|---|---|---|
| Vacuum oscillations | ✅ | ✅ |
| Matter effects (MSW) | ✅ | ✅ |
| Anti-neutrino mode | ✅ | ✅ |
| Batch API (pre-computed mixing) | ✅ | ✅ |
| SIMD vectorization | — | ✅ (f64 & f32) |
| f32 mode (2× SIMD lanes) | — | ✅ |
| Zero dependencies | ✅ | ✅ |
Performance
Benchmarks on AMD Ryzen (WSL2), 10M iterations:
Scalar Performance
| Language | Vacuum | Matter N=0 |
|---|---|---|
| Zig | 42 ns | 108 ns |
| Rust | 61 ns | 95 ns |
| C++ | 49 ns | 130 ns |
| Fortran | 51 ns | 107 ns |
| Python | 14,700 ns | 21,900 ns |
Zig SIMD Performance
| Mode | Scalar | SIMD f64 (4×) | SIMD f32 (8×) |
|---|---|---|---|
| Vacuum | 42 ns | 44 ns | 21 ns (48 M/s) |
| Matter | 108 ns | 56 ns | 37 ns (27 M/s) |
Key findings:
- Zig SIMD f32 achieves 48 M/s vacuum throughput
- Zig SIMD f32 is 3× faster than scalar for matter
- Rust is 27% faster than C++ for matter calculations
Rust
Installation
[]
= "0.3"
Quick Start
use ;
use PI;
// Vacuum oscillation (T2K-like)
let vacuum = VacuumParameters ;
let probs = probability_vacuum_lbl;
println!;
// Matter oscillation (DUNE-like)
let matter = MatterParameters ;
let probs = probability_matter_lbl;
println!;
Batch API
Pre-compute mixing matrix for repeated calculations:
use VacuumBatch;
let batch = new;
// ~30% faster for energy spectra
for e in
Anti-Neutrino Mode
let mut params = nufit52_no;
params.antineutrino = true;
let probs = probability_matter_lbl;
Zig
Installation
Add to build.zig.zon:
.dependencies = .{
.nufast = .{
.url = "https://github.com/planckeon/nufast/archive/refs/heads/main.tar.gz",
.hash = "...",
},
},
Then in build.zig:
const nufast = b.dependency("nufast", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("nufast", nufast.module("nufast"));
Quick Start
const nufast = @import("nufast");
// Vacuum oscillation
const params = nufast.VacuumParams.default;
const probs = nufast.vacuumProbability(params, 1300.0, 2.5);
// probs[1][0] = P(νμ → νe)
// Matter oscillation
const matter = nufast.MatterParams{
.vacuum = params,
.rho = 2.848,
.Ye = 0.5,
.n_newton = 0,
};
const matter_probs = nufast.matterProbability(matter, 1300.0, 2.5);
Batch API
// Pre-computed mixing (30-40% faster for spectra)
const batch = nufast.VacuumBatch.init(params);
for (energies) |E| {
const p = batch.probabilityAt(1300.0, E);
}
// Matter batch
const matter_batch = nufast.MatterBatch.init(matter_params);
SIMD
// f64 SIMD: 4 energies at once
var E_vec: nufast.F64Vec = .{ 1.0, 2.0, 3.0, 4.0 };
const p_vec = nufast.vacuumProbabilitySimd(batch, 1300.0, E_vec);
// f32 SIMD: 8 energies at once (2× throughput)
const batch_f32 = nufast.VacuumBatchF32.fromF64(batch);
var E_f32: nufast.F32Vec = .{ 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5 };
const p_f32 = nufast.vacuumProbabilitySimdF32(batch_f32, 1300.0, E_f32);
// SIMD matter
const matter_probs = nufast.matterProbabilitySimd(matter_batch, 1300.0, E_vec);
Anti-Neutrino Mode
var params = nufast.MatterParams.default;
params.antineutrino = true;
const probs = nufast.matterProbability(params, 1300.0, 2.5);
Probability Matrix
The returned [[f64; 3]; 3] matrix is indexed as probs[α][β] = P(ν_α → ν_β):
| e (0) | μ (1) | τ (2) | |
|---|---|---|---|
| e | P_ee | P_eμ | P_eτ |
| μ | P_μe | P_μμ | P_μτ |
| τ | P_τe | P_τμ | P_ττ |
Physics Notes
NuFit 5.2 Best-Fit Values (2022)
| Parameter | Normal Ordering | Inverted Ordering |
|---|---|---|
| sin²θ₁₂ | 0.307 | 0.307 |
| sin²θ₁₃ | 0.02203 | 0.02219 |
| sin²θ₂₃ | 0.546 | 0.539 |
| δ_CP | 1.36π | 1.56π |
| Δm²₂₁ | 7.42×10⁻⁵ eV² | 7.42×10⁻⁵ eV² |
| Δm²₃₁ | 2.517×10⁻³ eV² | −2.498×10⁻³ eV² |
Matter Effect (MSW)
The N_Newton parameter controls accuracy:
N_Newton = 0: DMP approximation (fast, ~0.1% accuracy)N_Newton = 1: One Newton iteration (~0.01% accuracy)N_Newton ≥ 2: Machine precision
Anti-Neutrino Physics
For anti-neutrinos, two sign flips are applied:
- CP phase: δ → −δ
- Matter potential: A → −A
This enables direct CP asymmetry calculation: A_CP = P(νμ→νe) − P(ν̄μ→ν̄e)
Project Structure
nufast/
├── src/ # Rust implementation
│ ├── lib.rs
│ └── main.rs
├── benchmarks/
│ ├── zig/ # Zig implementation (SIMD, f32)
│ │ ├── src/nufast.zig
│ │ └── README.md
│ ├── cpp/ # C++ (original NuFast)
│ ├── fortran/ # Fortran (original NuFast)
│ └── python/ # Python (original NuFast)
├── paper/
│ └── nufast-benchmark.pdf
└── README.md
Running Benchmarks
# Rust
# Zig
# C++
&& &&
See benchmarks/README.md for full details.
References
- P.B. Denton, "Neutrino Oscillation Probabilities: A Compact Multi-algorithm Approach," arXiv:2405.02400 (2024)
- NuFast — Original C++/Fortran/Python implementations
- NuFit 5.2 (2022)
Acknowledgments
This implementation was inspired by correspondence with Dr. Peter B. Denton, who recommended NuFast as the optimal algorithm for efficient neutrino oscillation probability calculations.
License
MIT