Distributions
26 of 36 SIMD distribution structs with full Python + closed-form parity. Bulk samplers; ziggurat, rejection, inversion.
The stochastic-rs-distributions crate ships 36 Simd* distribution
structs (grep -rc "^pub struct Simd" stochastic-rs-distributions/src --include='*.rs'),
plus ComplexDistribution (37 total). This page's table covers the
26 that carry the full package — every one of them has:
- A
Simd<Name><T>struct that implementsrand_distr::Distribution<T> - A bulk filler
fill_slice(dst)that draws from the distribution's own internal RNG (no separaterng-explicit /_fastpair) - A
DistributionExtimpl with closed-form pdf / cdf / characteristic function / moments - A
py_distribution!- orpy_distribution_int!-generated#[pyclass]for Python use - A KS-test against the analytic CDF
Not shown here — 10 more real, public Simd* types that don't carry
the full package (26 + 10 = 36), plus ComplexDistribution outside that
count entirely:
Ged,Skellam, and the fourTruncated{Normal,Exp,Beta,Gamma}wrappers (6) implementDistributionExtbut have no Python binding.SimdExpZigis the ziggurat-tableExp(1)primitive thatSimdExpand other samplers build on internally — the same distribution as theExponentialrow below, exposed as a lower-level type, not a distinct continuous distribution of its own.SimdNonCentralChiSquaredis structurally different from every type in this table: its noncentrality parameter is supplied per draw (sample_ncp(ncp)), not fixed at construction, so it implements neitherrand_distr::DistributionnorDistributionExtnorfill_slice— see its own module docs. It backs the exact CIR transition density (stochastic-rs-stats/src/cir.rs) via the free functionnon_central_chi_squared::sample(df, lambda, seed).DirichletandWishartare multivariate (aVec<T>/Array2<f64>per draw), so the scalarrand_distr::Distribution<T>shape does not apply; each exposes its ownsample_fast/log_pdfinstead.
That accounts for all 36. ComplexDistribution is a 37th, separate type
(not Simd-prefixed) that samples Complex<T> directly and is the one
type with no DistributionExt impl at all (see CLAUDE.md's note on the
trait). The API docs (cargo doc -p stochastic-rs-distributions) are the
exhaustive reference for all of them.
The list
Continuous
| Distribution | Strategy |
|---|---|
| Normal | Ziggurat |
| Lognormal | Transformation (exp(N)) |
| Cauchy | Inversion |
| Student-t | Composition () |
| Exponential | Ziggurat |
| Gamma | Marsaglia-Tsang squeeze |
| Beta | Gamma-ratio composition |
| Chi-squared | Composition (Gamma) |
| Inverse Gaussian | Michael-Schucany |
| Pareto | Inversion |
| Generalized Pareto | Inversion |
| Generalized Extreme Value | Inversion |
| Weibull | Ziggurat (Exp) + power transform |
| Uniform | Inversion (trivial) |
| Alpha-stable | Chambers-Mallows-Stuck |
| Normal Inverse Gaussian | Inverse-Gaussian mixture |
| Variance Gamma | Gamma-time normal mixture |
| Johnson SU | Transformation (sinh of N) |
| Skewed Student-t (Hansen) | Two-piece scaled Student-t |
| Generalized Inverse Gaussian | Hörmann-Leydold rejection / ratio-of-uniforms |
| Generalized Hyperbolic | GIG-clock normal mixture |
| Tempered Stable (positive) | Devroye double rejection (cumulants and transforms; no closed-form density) |
Discrete
| Distribution | Strategy |
|---|---|
| Binomial | BTRS rejection / geometric waiting-time |
| Geometric | Inversion |
| Poisson | Inverse transform (precomputed CDF table) |
| Hypergeometric | Inverse transform (precomputed CDF table) |
Choosing a distribution
The tables above group by sampling strategy; this groups by modeling role
instead. The full version — with the honest "these are actually the same
distribution" call-outs and every citation — lives in the crate's own doc
comment (stochastic-rs-distributions/src/lib.rs). Short version:
| Need | Reach for |
|---|---|
| Symmetric, light-tailed, unbounded | Normal (default), or Ged to tune peakedness — Ged is never power-law heavy-tailed, for any shape parameter |
| Heavy-tailed, unbounded (financial returns) | StudentT (ν=1 is Cauchy, exactly) or AlphaStable (generalizes Normal at α=2 and Cauchy at α=1, β=0, but has no closed-form pdf/cdf at general α); NormalInverseGauss or VarianceGamma for a skewed heavy tail with finite variance and closed-form moments (neither has a closed-form cdf); SkewT for Hansen's standardised skewed t with closed-form cdf and quantile — the GARCH innovation density; JohnsonSu when you want skew with light tails and every moment closed-form; GeneralizedHyperbolic is the parent family of NormalInverseGauss (λ = −1/2) and VarianceGamma over a Gig clock |
| Extreme-value tails | Gev for block maxima, Gpd for excesses over a threshold — a different job from bulk return modeling; both carry closed-form moments with the usual existence thresholds, and stats::evt fits them |
| Positive, duration/volatility-shaped | Exponential (constant hazard), Weibull (rising or falling hazard), Gamma (waiting time; backs Beta/ChiSquared/Ged/Dirichlet internally), LogNormal (multiplicative growth), InverseGauss (first-passage time) |
| Genuine power-law heavy tail, positive support | Pareto — the only positive-support type here with real threshold-dependent moment existence |
Bounded [0, 1] or [a, b] | Beta, Uniform, or a Truncated* wrapper — the four Truncated* types aren't a family to pick among, just "which base distribution, restricted to an interval" |
| Discrete counts, with replacement | Poisson, Binomial |
| Discrete counts, without replacement (finite population) | Hypergeometric — that assumption, not a tuning knob, separates it from Binomial |
| Signed count data | Skellam — the only discrete type here that can go negative |
| Covariance matrices, simplex proportions | Wishart, Dirichlet — both skip DistributionExt entirely for their own inherent density methods |
DistributionExt coverage is uneven: Ged, Skellam and the four
Truncated* wrappers implement only pdf/cdf, not the moments;
Dirichlet, Wishart, SimdNonCentralChiSquared and
ComplexDistribution implement none of it. Confirm the override exists
(cargo doc -p stochastic-rs-distributions) before assuming .mean() /
.variance() are there.
Examples
Normal — bulk sampling and closed-form moments
// docs: distributions#normal-bulk-sampling-and-closed-form-moments
//! Backs the Normal example on the distributions catalog page.
use rand_distr::Distribution;
use stochastic_rs::distributions::normal::SimdNormal;
use stochastic_rs::simd_rng::Deterministic;
use stochastic_rs::simd_rng::SeedExt;
use stochastic_rs::traits::DistributionExt;
#[test]
fn normal_bulk_sample_and_closed_form() {
let seed = Deterministic::new(42);
let d = SimdNormal::<f64>::new(/* mean */ 0.0, /* std */ 1.0, &seed);
// Single sample, drawn from the project's own RNG (not `rand::thread_rng`).
let mut rng = seed.rng();
let _x: f64 = d.sample(&mut rng);
// Bulk fill (uses internal RNG)
let mut buf = vec![0.0_f64; 10_000];
d.fill_slice(&mut buf);
// Closed-form analytics
assert!((d.mean() - 0.0).abs() < 1e-12);
assert!((d.variance() - 1.0).abs() < 1e-12);
let pdf = d.pdf(0.0); // 1/sqrt(2*pi) ~= 0.3989
let cdf = d.cdf(1.96); // ~= 0.975
assert!((pdf - 0.398_942_280_4).abs() < 1e-6);
assert!((cdf - 0.975).abs() < 1e-3);
}import stochastic_rs as srs
import numpy as np
d = srs.PyNormal(mean=0.0, std_dev=1.0, seed=42)
samples = d.sample(10_000) # numpy.ndarray
matrix = d.sample_par(100, 10_000) # shape (100, 10_000)
# Closed-form analytics
print(d.mean(), d.variance()) # 0.0 1.0
print(d.pdf(0.0), d.cdf(1.96)) # 0.3989, 0.975Beta — bounded support distribution
// docs: distributions#beta-bounded-support-distribution
//! Backs the Beta example on the distributions catalog page.
use stochastic_rs::distributions::beta::SimdBeta;
use stochastic_rs::simd_rng::Deterministic;
use stochastic_rs::traits::DistributionExt;
#[test]
fn beta_bounded_support_and_moments() {
let d = SimdBeta::<f64>::new(
/* alpha */ 2.0,
/* beta */ 5.0,
&Deterministic::new(42),
);
let mut buf = vec![0.0; 1_000];
d.fill_slice(&mut buf);
assert!(buf.iter().all(|&x| (0.0..=1.0).contains(&x)));
// E[X] = alpha / (alpha + beta) = 2/7
// Var = alpha*beta / ((alpha+beta)^2 * (alpha+beta+1)) = 10/(49*8)
assert!((d.mean() - 2.0 / 7.0).abs() < 1e-12);
assert!((d.variance() - 10.0 / (49.0 * 8.0)).abs() < 1e-12);
}import stochastic_rs as srs
d = srs.PyBeta(alpha=2.0, beta=5.0, seed=42)
samples = d.sample(1000)
print(samples.min(), samples.max()) # in [0, 1]
print(d.mean(), d.variance()) # 0.2857, 0.0255Poisson — discrete count distribution
// docs: distributions#poisson-discrete-count-distribution
//! Backs the Poisson example on the distributions catalog page.
use stochastic_rs::distributions::poisson::SimdPoisson;
use stochastic_rs::simd_rng::Deterministic;
#[test]
fn poisson_bulk_sample_mean() {
let d = SimdPoisson::<u32>::new(/* lambda */ 4.0, &Deterministic::new(42));
let mut buf = vec![0_u32; 10_000];
d.fill_slice(&mut buf);
let mean = buf.iter().sum::<u32>() as f64 / 10_000.0;
assert!(
(mean - 4.0).abs() < 0.1,
"sample mean = {mean:.3} (expect ~4.0)"
);
}import stochastic_rs as srs
d = srs.PyPoissonD(lambda_=4.0, seed=42)
counts = d.sample(10_000) # int dtype
print(counts.mean(), counts.var()) # both ≈ 4.0Common patterns
Construction
Every distribution follows the same new(args, &seed) constructor pattern — pass
Unseeded for an auto-seeded RNG or
Deterministic::new(u64) for a reproducible
stream. See the seeding concept page for the
full design (including SeedExt::reseed, the generic R: SimdRngExt
backing RNG, and the experimental dual-stream-rng feature). Note the
seed is taken by reference and comes last. SimdNormal stands in for
any distribution below; substitute the parameters of the one you want:
use stochastic_rs::distributions::normal::SimdNormal;
use stochastic_rs::simd_rng::Deterministic;
use stochastic_rs::simd_rng::Unseeded;
// auto-seeded
let d = SimdNormal::<f64>::new(/* mean */ 0.0, /* std_dev */ 1.0, &Unseeded);
// reproducible
let d = SimdNormal::<f64>::new(0.0, 1.0, &Deterministic::new(42));
// share one seed source across sub-components
let shared = Deterministic::new(42);
let d = SimdNormal::<f64>::new(0.0, 1.0, &shared);Sampling
use rand_distr::Distribution;
use stochastic_rs::simd_rng::SeedExt;
// Single sample, via the project's own RNG (see the seeding concept page).
// `SeedExt` is what brings `.rng()` into scope.
let mut rng = Deterministic::new(0).rng();
let x: f64 = d.sample(&mut rng);
// Bulk fill (uses the distribution's own internal RNG, applies SIMD)
let mut buf = vec![0.0_f64; 10_000];
d.fill_slice(&mut buf);Closed-form analytics
DistributionExt is f64-only (not generic over T) and every method
defaults to a type_name-annotated unimplemented!(), overridden per
distribution where a closed form exists:
use stochastic_rs::traits::DistributionExt;
let pdf = d.pdf(0.5);
let cdf = d.cdf(0.5);
let cf = d.characteristic_function(2.0); // Complex64
let m1 = d.mean();
let m2 = d.variance();Adding a new distribution
See the
adding-distribution
SKILL — file-by-file recipe including the py_distribution! macro at
the bottom.
Last updated on
Stochastic processes
132 stochastic processes — diffusion, jump, volatility, interest-rate, fractional / rough, and noise. Catalog organised by mathematical family.
Copulas
All 15 bivariate and 8 multivariate copulas with the BivariateExt / MultivariateExt traits — Archimedean, extreme-value, elliptical, and vine families.