splotrs
splotrs fits the yields and shared shape parameters of normalized component probability-density
functions, then calculates per-event sWeights. The numerical core is written in Rust and uses
Ganesh; the Python API accepts vectorized NumPy callbacks.
The library is intended for unbinned mixture fits where a discriminating variable separates components and the resulting sWeights are used to study another, component-independent variable. It supports signed input event weights and exposes both the sPlot yield covariance and the full joint fit covariance.
Features
- Joint extended maximum-likelihood fits of component yields and shared PDF shape parameters.
- Vectorized Python PDF callbacks with runtime validation of their output type and shape.
- A native Rust API for allocation-conscious fitting without Python.
- Per-event sWeights in the same row order as the input data.
- Signed input event weights.
- Fit diagnostics, uncertainties, and covariance matrices.
Installation
Install the Python package from PyPI:
python -m pip install splotrs
Or add the Rust crate from crates.io:
cargo add splotrs
Python 3.10 or newer is required.
Python quick start
Each component PDF must be callable as
pdf(events: NDArray[np.float64], parameters: dict[str, float]) -> NDArray[np.float64]. It receives
the complete two-dimensional event array and must return one finite, nonnegative float64 density
per event.
=
=
= -
return /
= / 0.7
return /
=
: =
=
# (n_events, n_components)
shape_parameters are fitted jointly with the yields. Every PDF receives the complete parameter
dictionary, so multiple components can share the same parameter.
Inputs
data: finite values with shape(n_events, n_features).pdfs: one callable per mixture component, in output-column order.shape_parameters: optionalShapeParameterobjects with initial values and bounds.initial_yields: optional nonnegative starting yields, one per PDF.weights: optional signed event weights with shape(n_events,).max_stepsandtolerance: optimizer controls.
Result
splot returns an immutable SPlotResult containing:
yieldsandyield_errors;sweightswith shape(n_events, n_components);covariance, the event-summed sPlot yield covariance;shape_parametersandshape_errorskeyed by parameter name;fit_covariance, the full joint yield-and-shape covariance;- convergence status, message, minimum negative log-likelihood, and evaluation counts.
Invalid inputs and callback contracts raise ValueError. Numerical PDF, optimizer, and covariance
failures raise SPlotError. Exceptions raised inside a PDF callback are preserved.
Complete example
The included example fits a Gaussian bump over a quadratic background and uses sWeights to recover the component distributions of a control variable:
just examples
It writes the resulting plot to target/examples/bump.png.
Rust usage
Implement ParametricPdf or use compatible closures, then call splot with event rows, PDFs,
shape parameters, and an SPlotConfig:
use ;
let data = vec!;
let signal = ;
let background = ;
let pdfs: = ;
let shapes = ;
let result = splot?;
println!;
# Ok::
Statistical assumptions
- Component PDFs must be normalized over the fitted discriminating variables.
- Components must be identifiable; linearly dependent PDFs produce a singular covariance error.
- A variable studied with sWeights should be independent of the discriminating variables within each component.
- Signed input weights are supported, but some weighted datasets can produce an indefinite or singular information matrix.
Development
Enter the reproducible Nix development shell:
nix develop
The shell supplies Rust, Cargo, Clippy, rustfmt, Python, uv, and Just. It synchronizes the locked
Python development environment on entry. Run just to list all recipes; the main workflows are:
just build # Build the Rust crate and Python distributions
just test # Run Rust and Python tests
just lint # Run Clippy, rustfmt, Ruff, ty, and Yamloom checks
just fmt # Format Rust and Python sources
just examples # Run the complete plotting example
just clean # Remove generated build and test artifacts
Without Nix, install uv, Rust, and Just, then run uv sync before using the same recipes.
Before publishing, inspect both artifacts locally:
cargo publish --dry-run
uv build
License
Licensed under either of the following, at your option:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT License (LICENSE-MIT)