Algostream_stochastic.VariateRandom variate samplers over Rng.
All samplers are stateless with respect to the caller: the only mutable state is the Rng.t itself. In particular normal does not cache the second Box-Muller branch. Caching would be ~2× faster, but it puts a spare value somewhere outside the Rng.t — which Rng.copy does not copy and Rng.substream does not derive — so a cached generator's output would depend on call history in a way that quietly breaks the substream reproducibility contract. Callers who want both branches ask for normal_pair and get them explicitly.
Every sampler draws from Rng.uniform_pos rather than Rng.uniform wherever a log or a division is involved, so nan cannot arise from a boundary draw.
module Rng = Algostream_rng.Rngval normal : Rng.t -> floatStandard normal, N(0, 1). Box-Muller in trigonometric form; the sine branch is discarded (see the header). Consumes two uniforms.
val normal_pair : Rng.t -> float * floatBoth Box-Muller branches. Consumes two uniforms and returns two independent standard normals — use this in a loop filling an even-length buffer.
val gaussian : Rng.t -> mu:float -> sigma:float -> floatN(mu, sigma²). sigma is a standard deviation, not a variance.
val normal_array : Rng.t -> n:int -> float arraynormal_array rng ~n fills an array using normal_pair, so it costs n uniforms rather than 2n.
val exponential : Rng.t -> lambda:float -> floatExponential with rate lambda (mean 1 / lambda). Inverse-CDF.
val gamma : Rng.t -> shape:float -> scale:float -> floatGamma(shape, scale) by Marsaglia & Tsang (2000), "A Simple Method for Generating Gamma Variables", with the Johnk boost for shape < 1. Raises Invalid_argument for non-positive shape or scale.
val chi_squared : Rng.t -> df:float -> floatChi-squared with df degrees of freedom — Gamma(df/2, 2).
val student_t : Rng.t -> df:float -> floatStudent-t with df degrees of freedom, as Z / sqrt(X / df) with Z standard normal and X chi-squared. Heavier tails than normal — the right innovation distribution when a scenario generator should produce realistic tail events.
val lognormal : Rng.t -> mu:float -> sigma:float -> floatLog-normal: exp(mu + sigma · Z). Note mu and sigma parameterize the underlying normal, not the log-normal's own mean and standard deviation.
val bernoulli : Rng.t -> p:float -> boolBernoulli. Returns true with probability p; clamps p into [0, 1].
val poisson : Rng.t -> lambda:float -> intPoisson with mean lambda. Knuth's product method below lambda = 30, normal approximation with continuity correction above it — the approximation is documented rather than hidden because the exact method's cost grows linearly in lambda.
val multivariate_normal :
Rng.t ->
mean:float array ->
chol_lower:float array array ->
float arraymultivariate_normal rng ~mean ~chol_lower returns mean + L · z with z a vector of iid standard normals. chol_lower comes from Cholesky.factor (or Cholesky.factor_jittered) of the target covariance matrix — factorize once, sample many times. Raises Invalid_argument on a dimension mismatch.
val choose_weighted : Rng.t -> weights:float array -> intIndex sampled proportionally to weights by linear-scan inverse CDF. Negative weights are treated as zero. Returns 0 if every weight is zero. O(n) per draw — fine for the small categorical draws this library needs (regime transitions); a caller doing millions of draws from one fixed distribution should build an alias table.