183 lines
5.3 KiB
Go
183 lines
5.3 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
|
|
// SPDX-License-Identifier: MIT
|
|||
|
|
|
|||
|
|
package stats
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"sourcedock.dev/petrbalvin/tensor/internal/base"
|
|||
|
|
"sourcedock.dev/petrbalvin/tensor/internal/core"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"math"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// ExponentialDraws returns n draws from the exponential distribution
|
|||
|
|
// with the given rate (mean 1/rate), by inverse CDF.
|
|||
|
|
func ExponentialDraws(g *core.Generator, n int, rate float64) (*core.Array, error) {
|
|||
|
|
if n < 1 {
|
|||
|
|
return nil, base.Errf("ExponentialDraws: n must be ≥ 1")
|
|||
|
|
}
|
|||
|
|
if !(rate > 0) {
|
|||
|
|
return nil, base.Errf("ExponentialDraws: rate must be positive, got %v", rate)
|
|||
|
|
}
|
|||
|
|
out := core.New(core.Float, []int{n}...)
|
|||
|
|
for i := range n {
|
|||
|
|
u := 1 - g.Unit()
|
|||
|
|
out.RawFloats()[i] = -math.Log(u) / rate
|
|||
|
|
}
|
|||
|
|
return out, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GammaDraws returns n draws from the gamma distribution with shape
|
|||
|
|
// α > 0 and rate β > 0, by Marsaglia-Tsang for α ≥ 1 and by boosting
|
|||
|
|
// with the exponential for α < 1.
|
|||
|
|
func GammaDraws(g *core.Generator, n int, alpha, beta float64) (*core.Array, error) {
|
|||
|
|
if n < 1 {
|
|||
|
|
return nil, base.Errf("GammaDraws: n must be ≥ 1")
|
|||
|
|
}
|
|||
|
|
if !(alpha > 0 && beta > 0) {
|
|||
|
|
return nil, base.Errf("GammaDraws: shape and rate must be positive")
|
|||
|
|
}
|
|||
|
|
out := core.New(core.Float, []int{n}...)
|
|||
|
|
d := alpha - 1.0/3
|
|||
|
|
c := 1 / math.Sqrt(9*d)
|
|||
|
|
for i := range n {
|
|||
|
|
if alpha >= 1 {
|
|||
|
|
out.RawFloats()[i] = gammaMarsagliaTsang(g, alpha, d, c) / beta
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
// α < 1: boost to α + 1 and scale by a uniform^(1/α).
|
|||
|
|
boost := alpha + 1
|
|||
|
|
dd := boost - 1.0/3
|
|||
|
|
cc := 1 / math.Sqrt(9*dd)
|
|||
|
|
v := gammaMarsagliaTsang(g, boost, dd, cc)
|
|||
|
|
out.RawFloats()[i] = v * math.Pow(g.Unit(), 1/alpha) / beta
|
|||
|
|
}
|
|||
|
|
return out, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// gammaMarsagliaTsang draws one gamma(α, 1) for α ≥ 1 by the
|
|||
|
|
// Marsaglia-Tsang squeeze: a normal draw shapes the cube root of the
|
|||
|
|
// scale, an exponential-tilted accept/reject polishes the tail.
|
|||
|
|
func gammaMarsagliaTsang(g *core.Generator, alpha, d, c float64) float64 {
|
|||
|
|
for {
|
|||
|
|
x := g.NormalUnit()
|
|||
|
|
v := 1 + c*x
|
|||
|
|
if v <= 0 {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
vvv := v * v * v
|
|||
|
|
u := g.Unit()
|
|||
|
|
if u < 1-0.0331*x*x*x*x {
|
|||
|
|
return d * vvv
|
|||
|
|
}
|
|||
|
|
if math.Log(u) < 0.5*x*x+d*(1-vvv+math.Log(vvv)) {
|
|||
|
|
return d * vvv
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ChiSquareDraws returns n draws from the chi-squared distribution
|
|||
|
|
// with df degrees of freedom, the gamma(df/2, 2) distribution.
|
|||
|
|
func ChiSquareDraws(g *core.Generator, n int, df int) (*core.Array, error) {
|
|||
|
|
if df < 1 {
|
|||
|
|
return nil, base.Errf("ChiSquareDraws: df must be ≥ 1")
|
|||
|
|
}
|
|||
|
|
return GammaDraws(g, n, float64(df)/2, 0.5)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// StudentTDraws returns n draws from Student's t with df degrees of
|
|||
|
|
// freedom, as N(0,1)/√(χ²_df/df).
|
|||
|
|
func StudentTDraws(g *core.Generator, n int, df int) (*core.Array, error) {
|
|||
|
|
if df < 1 {
|
|||
|
|
return nil, base.Errf("StudentTDraws: df must be ≥ 1")
|
|||
|
|
}
|
|||
|
|
chi, err := ChiSquareDraws(g, n, df)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
out := core.New(core.Float, []int{n}...)
|
|||
|
|
for i := range n {
|
|||
|
|
z := g.NormalUnit()
|
|||
|
|
den := math.Sqrt(chi.FloatAt(i) / float64(df))
|
|||
|
|
if den == 0 {
|
|||
|
|
// The χ² draw underflowed to exactly zero: the ratio is an
|
|||
|
|
// infinity carrying the numerator's sign, not an unsigned
|
|||
|
|
// one (and not the NaN a 0/0 numerator of zero would make).
|
|||
|
|
out.RawFloats()[i] = math.Copysign(math.Inf(1), z)
|
|||
|
|
} else {
|
|||
|
|
out.RawFloats()[i] = z / den
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return out, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// PoissonDraws returns n draws from the Poisson distribution with the
|
|||
|
|
// given λ, by the Knuth multiplication method for λ < 30 and the
|
|||
|
|
// normal approximation above.
|
|||
|
|
func PoissonDraws(g *core.Generator, n int, lambda float64) (*core.Array, error) {
|
|||
|
|
if n < 1 {
|
|||
|
|
return nil, base.Errf("PoissonDraws: n must be ≥ 1")
|
|||
|
|
}
|
|||
|
|
if !(lambda >= 0) {
|
|||
|
|
return nil, base.Errf("PoissonDraws: λ must be ≥ 0")
|
|||
|
|
}
|
|||
|
|
out := core.New(core.Float, []int{n}...)
|
|||
|
|
L := math.Exp(-lambda)
|
|||
|
|
for i := range n {
|
|||
|
|
if lambda < 30 {
|
|||
|
|
// λ = 0 (or tiny enough that exp(−λ) rounds to 1) is the
|
|||
|
|
// degenerate distribution at 0: the multiplication loop
|
|||
|
|
// would never run and hand back k−1 = −1.
|
|||
|
|
if L >= 1 {
|
|||
|
|
out.RawFloats()[i] = 0
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
k := 0.0
|
|||
|
|
p := 1.0
|
|||
|
|
for p > L {
|
|||
|
|
k++
|
|||
|
|
p *= g.Unit()
|
|||
|
|
}
|
|||
|
|
out.RawFloats()[i] = k - 1
|
|||
|
|
} else {
|
|||
|
|
// Normal approximation for large λ.
|
|||
|
|
z := g.NormalUnit()
|
|||
|
|
out.RawFloats()[i] = max(0, math.Floor(lambda+math.Sqrt(lambda)*z+0.5))
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return out, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// BinomialDraws returns n draws from the binomial distribution with
|
|||
|
|
// the given number of trials and success probability: each draw runs
|
|||
|
|
// trials uniform comparisons against p and counts the successes, the
|
|||
|
|
// exact per-trial loop. It is exact but costs O(trials) uniforms per
|
|||
|
|
// draw, so keep trials modest.
|
|||
|
|
func BinomialDraws(g *core.Generator, n int, trials int, p float64) (*core.Array, error) {
|
|||
|
|
if n < 1 {
|
|||
|
|
// The sibling draws all refuse n < 1; without the guard this one
|
|||
|
|
// returned a nil array with a nil error, which a caller that only
|
|||
|
|
// checks the error then dereferenced.
|
|||
|
|
return nil, base.Errf("BinomialDraws: n must be ≥ 1")
|
|||
|
|
}
|
|||
|
|
if trials < 1 {
|
|||
|
|
return nil, base.Errf("BinomialDraws: trials must be ≥ 1")
|
|||
|
|
}
|
|||
|
|
if !(p >= 0 && p <= 1) {
|
|||
|
|
return nil, base.Errf("BinomialDraws: p must be in [0, 1]")
|
|||
|
|
}
|
|||
|
|
out := core.New(core.Float, []int{n}...)
|
|||
|
|
for i := range n {
|
|||
|
|
count := 0.0
|
|||
|
|
for range trials {
|
|||
|
|
if g.Unit() < p {
|
|||
|
|
count++
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
out.RawFloats()[i] = count
|
|||
|
|
}
|
|||
|
|
return out, nil
|
|||
|
|
}
|