# Distributions

URL: https://stochastic.rust-dd.com/docs/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 implements `rand_distr::Distribution<T>`
* A bulk filler `fill_slice(dst)` that draws from the distribution's own
  internal RNG (no separate `rng`-explicit / `_fast` pair)
* A
  [`DistributionExt`](/docs/concepts/distribution-ext)
  impl with closed-form pdf / cdf / characteristic function / moments
* A `py_distribution!`- or `py_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 four `Truncated{Normal,Exp,Beta,Gamma}`
  wrappers (6) implement `DistributionExt` but have no Python binding.
* `SimdExpZig` is the ziggurat-table `Exp(1)` primitive that
  `SimdExp` and other samplers build on internally — the same
  distribution as the `Exponential` row below, exposed as a lower-level
  type, not a distinct continuous distribution of its own.
* `SimdNonCentralChiSquared` is 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 neither
  `rand_distr::Distribution` nor `DistributionExt` nor `fill_slice` — see
  its own module docs. It backs the exact CIR transition density
  (`stochastic-rs-stats/src/cir.rs`) via the free function
  `non_central_chi_squared::sample(df, lambda, seed)`.
* `Dirichlet` and `Wishart` are multivariate (a `Vec<T>` / `Array2<f64>`
  per draw), so the scalar `rand_distr::Distribution<T>` shape does not
  apply; each exposes its own `sample_fast`/`log_pdf` instead.

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 ($Z / \sqrt{\chi^2_\nu/\nu}$)                                   |
| 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 $\xi$ 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

```rust title="tests/doctest_distributions_normal.rs"
// 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);
}
```

```python
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.975
```

### Beta — bounded support distribution

```rust title="tests/doctest_distributions_beta.rs"
// 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);
}
```

```python
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.0255
```

### Poisson — discrete count distribution

```rust title="tests/doctest_distributions_poisson.rs"
// 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)"
  );
}
```

```python
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.0
```

## Common patterns

### Construction

Every distribution follows the same `new(args, &seed)` constructor pattern — pass
[`Unseeded`](/docs/concepts/seeding) for an auto-seeded RNG or
[`Deterministic::new(u64)`](/docs/concepts/seeding) for a reproducible
stream. See the [seeding concept page](/docs/concepts/seeding) 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:

```rust
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

```rust
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:

```rust
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`](https://github.com/dancixx/stochastic-rs/blob/main/.claude/skills/adding-distribution/SKILL.md)
SKILL — file-by-file recipe including the `py_distribution!` macro at
the bottom.

