NextStat is a high-performance statistical fitting toolkit for High Energy Physics (HEP), implemented in Rust with Python bindings.
- Model: S+B HistFactory (synthetic), 50 channels × 4 bins, 201 parameters (mu + 200 nuisances)
- Task: CLs via toy-based q~_mu
- Load: 10,000 toys (b-only) + 10,000 toys (s+b)
- Machine: Apple M5 (arm64), macOS-26.2-arm64-arm-64bit-Mach-O
- Versions: nextstat 0.9.0, pyhf 0.7.6, Python 3.13.11
- Recorded: 2026-02-07 (UTC)
- Commit: 88d57856 (benchmark snapshot)
| Tool | Wall time | Speedup |
|---|---|---|
| NextStat (Rayon) | 3.47 s | 1.0× |
| pyhf (multiprocessing, 10 procs) | 50m 11.7s | 868.0× |
Reproduce:
PYTHONPATH=bindings/ns-py/python ./.venv/bin/python scripts/god_run_benchmark.py --n-toys 10000- pyhf JSON compatibility (HistFactory-style workspaces)
- Native HS3 (HEP Statistics Serialization Standard) v0.2 support — load ROOT 6.37+ HS3 JSON directly, auto-detected alongside pyhf
- Native ROOT TTree reader with mmap I/O, rayon-parallel basket decompression, and columnar extraction — no ROOT C++ dependency
- Ntuple-to-workspace pipeline: ROOT ntuples → histograms → HistFactory workspace (TRExFitter replacement)
- Expression engine for string-based selections and weights (
"njet >= 4 && pt > 25.0") - Negative log-likelihood (Poisson + constraints), including Barlow-Beeston auxiliary terms
- Maximum Likelihood Estimation (L-BFGS-B) with uncertainties via (damped) Hessian-based covariance + diagonal fallback
- NUTS sampling surface (generic
PosteriorAPI) + optional ArviZ integration - SIMD kernels, Rayon parallelism, Apple Accelerate (vDSP/vForce), and optional GPU acceleration (CUDA for NVIDIA, Metal for Apple Silicon)
- Rust library, Python package (PyO3/maturin), and a CLI
- Implemented packs: regression/GLM, hierarchical models, time series (Kalman/EM/forecast), econometrics/causal helpers, and PK/NLME baselines
- Docs index:
docs/README.md - Tutorials (end-to-end):
docs/tutorials/README.md - References (CLI/Python/Rust/Server/Tools):
docs/references/ - Demo: Physics Assistant (ROOT -> anomaly scan -> p-values + plots):
docs/demos/physics-assistant.md
cargo add ns-core ns-inference ns-computepip install nextstatgit clone https://github.com/NextStat/nextstat.io.git
cd nextstat.io
# Rust workspace
cargo build --release
# Python bindings (editable dev install)
cd bindings/ns-py
maturin develop --releaseRun asymptotic CLs upper limits (Brazil bands) in the browser (no Python, no server) using a pyhf-style workspace.json.
From the repo root:
rustup target add wasm32-unknown-unknown
cargo install wasm-bindgen-cli --version 0.2.108
make playground-build-wasm
make playground-serveOpen http://localhost:8000/ and drag & drop a workspace.json (example: playground/examples/simple_workspace.json).
use ns_inference::mle::MaximumLikelihoodEstimator;
use ns_translate::pyhf::{HistFactoryModel, HistoSysInterpCode, NormSysInterpCode, Workspace};
let json = std::fs::read_to_string("workspace.json")?;
let workspace: Workspace = serde_json::from_str(&json)?;
// Default interpolation (NextStat "smooth" defaults): NormSys=Code4, HistoSys=Code4p.
// For strict HistFactory/pyhf defaults, use Code1/Code0:
let model = HistFactoryModel::from_workspace_with_settings(
&workspace,
NormSysInterpCode::Code1,
HistoSysInterpCode::Code0,
)?;
let mle = MaximumLikelihoodEstimator::new();
let result = mle.fit(&model)?;
println!("Best-fit params: {:?}", result.parameters);
println!("NLL at minimum: {}", result.nll);import json
import nextstat
workspace = json.loads(open("workspace.json").read())
model = nextstat.from_pyhf(json.dumps(workspace))
result = nextstat.fit(model)
poi_idx = model.poi_index()
print("POI index:", poi_idx)
print("Best-fit POI:", result.bestfit[poi_idx])
print("Uncertainty:", result.uncertainties[poi_idx])Install optional deps:
pip install "nextstat[bayes]"Run sampling and get an ArviZ InferenceData:
import json
from pathlib import Path
import nextstat
workspace = json.loads(Path("workspace.json").read_text())
model = nextstat.from_pyhf(json.dumps(workspace))
idata = nextstat.bayes.sample(
model,
n_chains=2,
n_warmup=500,
n_samples=1000,
seed=42,
target_accept=0.8,
)
print(idata)Install optional deps:
pip install "nextstat[viz]"Compute artifacts and plot (matplotlib):
import json
import numpy as np
from pathlib import Path
import nextstat
workspace = json.loads(Path("workspace.json").read_text())
model = nextstat.from_pyhf(json.dumps(workspace))
scan = np.linspace(0.0, 5.0, 101)
cls_art = nextstat.viz.cls_curve(model, scan, alpha=0.05)
nextstat.viz.plot_cls_curve(cls_art, title="CLs Brazil band")
mu = [0.0, 0.5, 1.0, 2.0]
prof_art = nextstat.viz.profile_curve(model, mu)
nextstat.viz.plot_profile_curve(prof_art, title="Profile likelihood scan")use ns_translate::NtupleWorkspaceBuilder;
let ws = NtupleWorkspaceBuilder::new()
.ntuple_path("ntuples/")
.tree_name("events")
.measurement("meas", "mu")
.add_channel("SR", |ch| {
ch.variable("mbb")
.binning(&[0., 50., 100., 150., 200., 300.])
.selection("njet >= 4 && pt > 25.0")
.data_file("data.root")
.add_sample("signal", |s| {
s.file("ttH.root")
.weight("weight_mc * weight_sf")
.normfactor("mu")
})
.add_sample("background", |s| {
s.file("ttbar.root")
.weight("weight_mc * weight_sf")
.normsys("bkg_norm", 0.9, 1.1)
.weight_sys("jes", "weight_jes_up", "weight_jes_down")
.tree_sys("jer", "jer_up.root", "jer_down.root")
.staterror()
})
})
.build()?; // → Workspace (same type as pyhf JSON path)No ROOT C++ dependency. ~8.5x faster than uproot+numpy on the full pipeline.
use ns_root::RootFile;
let file = RootFile::open("data.root")?;
let tree = file.get_tree("events")?;
// Columnar access
let pt: Vec<f64> = file.branch_data(&tree, "pt")?;
let eta: Vec<f64> = file.branch_data(&tree, "eta")?;
// Expression engine
let expr = ns_root::CompiledExpr::compile("pt > 25.0 && abs(eta) < 2.5")?;nextstat fit --input workspace.json
nextstat --interp-defaults pyhf fit --input workspace.json # NormSys=Code1, HistoSys=Code0
nextstat hypotest --input workspace.json --mu 1.0 --expected-set
nextstat hypotest-toys --input workspace.json --mu 1.0 --n-toys 10000 --seed 42 --threads 0
nextstat hypotest-toys --input workspace.json --mu 1.0 --n-toys 10000 --gpu cuda # NVIDIA GPU (f64)
nextstat hypotest-toys --input workspace.json --mu 1.0 --n-toys 10000 --gpu metal # Apple Silicon GPU (f32)
nextstat upper-limit --input workspace.json --expected --scan-start 0 --scan-stop 5 --scan-points 201
nextstat version- Tutorial index:
docs/tutorials/README.md - Python API reference:
docs/references/python-api.md - Rust API reference:
docs/references/rust-api.md - R Bindings reference:
docs/references/r-bindings.md - CLI reference:
docs/references/cli.md - Playground (browser/WASM):
docs/references/playground.md(andplayground/README.md)
NextStat follows a "clean architecture" style: inference depends on stable abstractions, not on specific execution backends.
┌─────────────────────────────────────────────────────────────────┐
│ HIGH-LEVEL LOGIC │
│ ns-inference (MLE, Profile Likelihood, Hypothesis Tests, ...) │
│ - depends on core types and model interfaces │
└─────────────────────────┬───────────────────────────────────────┘
│ depends on abstractions
┌─────────────────────────┴───────────────────────────────────────┐
│ ns-core (interfaces) │
│ - error types, FitResult, traits │
└─────────────────────────┬───────────────────────────────────────┘
│ implemented by
┌─────────────────────────┴───────────────────────────────────────┐
│ LOW-LEVEL IMPLEMENTATIONS │
│ ns-translate (pyhf + ntuple → Workspace) ns-compute (SIMD/CUDA/Metal) │
│ ns-ad (dual/tape AD) ns-root (ROOT I/O, TTree, expressions) │
└─────────────────────────────────────────────────────────────────┘
nextstat/
├── crates/
│ ├── ns-core/ # Core types, traits, error handling
│ ├── ns-compute/ # SIMD kernels, Apple Accelerate, CUDA/Metal batch NLL+grad
│ ├── ns-ad/ # Automatic differentiation (dual/tape)
│ ├── ns-prob/ # Probability distributions and math (logpdf, cdf, transforms)
│ ├── ns-root/ # Native ROOT file reader (TH1, TTree, expressions, filler)
│ ├── ns-translate/ # Format translators (pyhf, HS3, HistFactory XML, ntuple builder)
│ ├── ns-unbinned/ # Unbinned (event-level) likelihood models and PDFs
│ ├── ns-inference/ # MLE, NUTS, CLs, GLM, time series, PK/NLME
│ ├── ns-viz/ # Visualization artifacts
│ ├── ns-server/ # Self-hosted GPU inference API (axum, REST)
│ ├── ns-zstd/ # High-performance pure-Rust Zstd decoder (fork of ruzstd)
│ └── ns-cli/ # CLI binary
├── bindings/
│ ├── ns-py/ # Python bindings (PyO3/maturin)
│ ├── ns-wasm/ # WASM bindings (browser playground)
│ └── ns-r/ # R bindings (experimental)
├── docs/
│ ├── legal/
│ ├── plans/
│ └── references/
└── tests/
- Rust 1.93+ (edition 2024)
- Python 3.11+ (for bindings)
- maturin (for Python bindings)
- R 4.1+ (optional, for
bindings/ns-r)
# Build
cargo build --workspace
# Build with CUDA support (requires nvcc)
cargo build --workspace --features cuda
# Build with Metal support (Apple Silicon, macOS)
cargo build --workspace --features metal
# Tests (default features)
cargo test --workspace
# Tests including optional backends (CUDA requires nvcc)
cargo test --workspace --all-features
# Opt-in slow Rust tests (toys, SBC, NUTS quality gates)
make rust-slow-tests
# Very slow (release) regression check
make rust-very-slow-tests
# Format and lint
cargo fmt --check
cargo clippy --workspace -- -D warningsLocal test runs should use the repo venv (it pins a Python version compatible with the built extension).
# Run fast Python tests (parity + API contracts)
PYTHONPATH=bindings/ns-py/python ./.venv/bin/python -m pytest -q -m "not slow" tests/python
# Run slow toy regression tests (opt-in)
PYTHONPATH=bindings/ns-py/python NS_RUN_SLOW=1 NS_TOYS=200 NS_SEED=0 ./.venv/bin/python -m pytest -q -m slow tests/python# Compile and run all benches
cargo bench --workspace
# Common entry points
cargo bench -p ns-translate --bench model_benchmark
cargo bench -p ns-translate --bench nll_benchmark
cargo bench -p ns-compute --bench simd_benchmark
cargo bench -p ns-inference --bench mle_benchmark
cargo bench -p ns-inference --bench hypotest_benchmark
cargo bench -p ns-ad --bench ad_benchmark
cargo bench -p ns-core --bench core_benchmarkDetails (quick mode, baselines, CI workflows): docs/benchmarks.md.
Record a reference baseline (writes JSON under tmp/baselines/ with a full environment fingerprint):
make apex2-baseline-recordCompare current HEAD vs the latest recorded baseline (writes tmp/baseline_compare_report.json):
make apex2-baseline-comparePre-release gate runbook: docs/tutorials/release-gates.md.
- White paper (Markdown):
docs/WHITEPAPER.md - White paper (PDF): built by
python3 scripts/build_whitepaper.pyand attached to GitHub Releases on tags (v*) - Internal plans/design notes are maintained outside this public repository.
See CONTRIBUTING.md. All commits must include DCO sign-off (git commit -s).
NextStat uses a dual-licensing model:
- Open Source:
LICENSE(AGPL-3.0-or-later) - Commercial:
LICENSE-COMMERCIAL
- Website: https://nextstat.io
- GitHub: https://github.com/NextStat/nextstat.io