Distributions
Four probability distributions with inverse-CDF sampling, plus combinators for reshaping them.
BucketedDistribution
All distributions are instances of BucketedDistribution — a discrete approximation of
a continuous PDF using a grid of x-values and probability densities. Sampling uses inverse-CDF
interpolation via the trapezoid rule.
import io.peekandpoke.ultra.maths.stochastic.BucketedDistribution
import io.peekandpoke.ultra.maths.stochastic.sample
import kotlin.random.Random
val dist = BucketedDistribution.createTruncatedNormal()
// Raw sample in [0, 1]
val raw = dist.sample(Random)
// Mapped to a range
val mapped = dist.sample(Random, from = 10.0, to = 50.0) Distribution types
Uniform
Equal probability across the entire range. Every value in [0, 1] is equally likely.
val uniform = BucketedDistribution.createUniform()
val value = uniform.sample(Random) // any value in [0, 1] with equal probability Truncated normal
A Gaussian bell curve truncated to [0, 1]. Values cluster around the mean,
with spread controlled by std.
// Default: centered at 0.5, std = 0.15
val normal = BucketedDistribution.createTruncatedNormal()
// Custom: skewed toward the low end
val skewed = BucketedDistribution.createTruncatedNormal(mean = 0.2, std = 0.1) | Parameter | Default | Meaning |
|---|---|---|
mean | 0.5 | Center of the bell curve (must be in [0, 1]) |
std | 0.15 | Standard deviation — smaller = tighter clustering |
count | 100 | Number of buckets (higher = more precision) |
Exponential
High probability near zero, decaying exponentially. The tail is truncated at a configurable cutoff.
// Default: lambda = 1.0
val exp = BucketedDistribution.createExponential()
// Steeper decay
val steep = BucketedDistribution.createExponential(lambda = 3.0) | Parameter | Default | Meaning |
|---|---|---|
lambda | 1.0 | Rate parameter — higher = steeper decay |
tailCut | 1e-3 | Tail probability cutoff for truncation |
Triangular
Probability rises linearly to the mode, then falls linearly. Useful when you want a
peak at a specific value with bounded tails.
val tri = BucketedDistribution.createTriangular(
min = 0.0,
mode = 0.7, // peak probability here
max = 1.0,
) Combinators
Reshape existing distributions without creating new ones from scratch:
inverse()
Flips the PDF so high-probability regions become low-probability and vice versa.
val normal = BucketedDistribution.createTruncatedNormal()
val inverted = normal.inverse() // rare values near 0.5, common at edges reversed()
Mirrors the PDF shape. An exponential that decays left-to-right becomes one that decays right-to-left.
val exp = BucketedDistribution.createExponential(lambda = 2.0)
val rev = exp.reversed() // high probability at the right end instead of the left blend()
Linearly interpolates between two distributions. At ratio = 0.0 you get this distribution;
at ratio = 1.0 you get the other.
val normal = BucketedDistribution.createTruncatedNormal()
val uniform = BucketedDistribution.createUniform()
// 70% normal, 30% uniform
val blended = normal.blend(uniform, ratio = 0.3) RandomRange
RandomRange wraps a distribution with explicit min/max bounds, or represents a constant value.
Useful when you need a serializable "random or fixed" configuration:
import io.peekandpoke.ultra.maths.stochastic.RandomRange
// Distribution-backed range
val range = RandomRange.OfDistribution(
distribution = BucketedDistribution.createTruncatedNormal(),
min = 10.0,
max = 50.0,
)
// Constant (always returns the same value)
val fixed = RandomRange.OfConstant(constant = 42.0)
println(fixed.min) // 42.0
println(fixed.max) // 42.0