gonum.org/v1/gonum@v0.15.1-0.20240517103525-f853624cb1bb/stat/distmv/uniform.go (about) 1 // Copyright ©2015 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package distmv 6 7 import ( 8 "math" 9 10 "golang.org/x/exp/rand" 11 "gonum.org/v1/gonum/spatial/r1" 12 ) 13 14 // Uniform represents a multivariate uniform distribution. 15 type Uniform struct { 16 bounds []r1.Interval 17 dim int 18 rnd *rand.Rand 19 } 20 21 // NewUniform creates a new uniform distribution with the given bounds. 22 func NewUniform(bnds []r1.Interval, src rand.Source) *Uniform { 23 dim := len(bnds) 24 if dim == 0 { 25 panic(badZeroDimension) 26 } 27 for _, b := range bnds { 28 if b.Max < b.Min { 29 panic("uniform: maximum less than minimum") 30 } 31 } 32 u := &Uniform{ 33 bounds: make([]r1.Interval, dim), 34 dim: dim, 35 } 36 if src != nil { 37 u.rnd = rand.New(src) 38 } 39 for i, b := range bnds { 40 u.bounds[i].Min = b.Min 41 u.bounds[i].Max = b.Max 42 } 43 return u 44 } 45 46 // NewUnitUniform creates a new Uniform distribution over the dim-dimensional 47 // unit hypercube. That is, a uniform distribution where each dimension has 48 // Min = 0 and Max = 1. 49 func NewUnitUniform(dim int, src rand.Source) *Uniform { 50 if dim <= 0 { 51 panic(nonPosDimension) 52 } 53 bounds := make([]r1.Interval, dim) 54 for i := range bounds { 55 bounds[i].Min = 0 56 bounds[i].Max = 1 57 } 58 u := Uniform{ 59 bounds: bounds, 60 dim: dim, 61 } 62 if src != nil { 63 u.rnd = rand.New(src) 64 } 65 return &u 66 } 67 68 // Bounds returns the bounds on the variables of the distribution. 69 // 70 // If dst is not nil, the bounds will be stored in-place into dst and returned, 71 // otherwise a new slice will be allocated first. If dst is not nil, it must 72 // have length equal to the dimension of the distribution. 73 func (u *Uniform) Bounds(bounds []r1.Interval) []r1.Interval { 74 if bounds == nil { 75 bounds = make([]r1.Interval, u.Dim()) 76 } 77 if len(bounds) != u.Dim() { 78 panic(badInputLength) 79 } 80 copy(bounds, u.bounds) 81 return bounds 82 } 83 84 // CDF returns the value of the multidimensional cumulative distribution 85 // function of the probability distribution at the point x. 86 // 87 // If dst is not nil, the value will be stored in-place into dst and returned, 88 // otherwise a new slice will be allocated first. If dst is not nil, it must 89 // have length equal to the dimension of the distribution. CDF will also panic 90 // if the length of x is not equal to the dimension of the distribution. 91 func (u *Uniform) CDF(dst, x []float64) []float64 { 92 if len(x) != u.dim { 93 panic(badSizeMismatch) 94 } 95 dst = reuseAs(dst, u.dim) 96 97 for i, v := range x { 98 if v < u.bounds[i].Min { 99 dst[i] = 0 100 } else if v > u.bounds[i].Max { 101 dst[i] = 1 102 } else { 103 dst[i] = (v - u.bounds[i].Min) / (u.bounds[i].Max - u.bounds[i].Min) 104 } 105 } 106 return dst 107 } 108 109 // Dim returns the dimension of the distribution. 110 func (u *Uniform) Dim() int { 111 return u.dim 112 } 113 114 // Entropy returns the differential entropy of the distribution. 115 func (u *Uniform) Entropy() float64 { 116 // Entropy is log of the volume. 117 var logVol float64 118 for _, b := range u.bounds { 119 logVol += math.Log(b.Max - b.Min) 120 } 121 return logVol 122 } 123 124 // LogProb computes the log of the pdf of the point x. 125 func (u *Uniform) LogProb(x []float64) float64 { 126 dim := u.dim 127 if len(x) != dim { 128 panic(badSizeMismatch) 129 } 130 var logprob float64 131 for i, b := range u.bounds { 132 if x[i] < b.Min || x[i] > b.Max { 133 return math.Inf(-1) 134 } 135 logprob -= math.Log(b.Max - b.Min) 136 } 137 return logprob 138 } 139 140 // Mean returns the mean of the probability distribution. 141 // 142 // If dst is not nil, the mean will be stored in-place into dst and returned, 143 // otherwise a new slice will be allocated first. If dst is not nil, it must 144 // have length equal to the dimension of the distribution. 145 func (u *Uniform) Mean(dst []float64) []float64 { 146 dst = reuseAs(dst, u.dim) 147 for i, b := range u.bounds { 148 dst[i] = (b.Max + b.Min) / 2 149 } 150 return dst 151 } 152 153 // Prob computes the value of the probability density function at x. 154 func (u *Uniform) Prob(x []float64) float64 { 155 return math.Exp(u.LogProb(x)) 156 } 157 158 // Rand generates a random sample according to the distributon. 159 // 160 // If dst is not nil, the sample will be stored in-place into dst and returned, 161 // otherwise a new slice will be allocated first. If dst is not nil, it must 162 // have length equal to the dimension of the distribution. 163 func (u *Uniform) Rand(dst []float64) []float64 { 164 dst = reuseAs(dst, u.dim) 165 if u.rnd == nil { 166 for i, b := range u.bounds { 167 dst[i] = rand.Float64()*(b.Max-b.Min) + b.Min 168 } 169 return dst 170 } 171 for i, b := range u.bounds { 172 dst[i] = u.rnd.Float64()*(b.Max-b.Min) + b.Min 173 } 174 return dst 175 } 176 177 // Quantile returns the value of the multi-dimensional inverse cumulative 178 // distribution function at p. 179 // 180 // If dst is not nil, the quantile will be stored in-place into dst and 181 // returned, otherwise a new slice will be allocated first. If dst is not nil, 182 // it must have length equal to the dimension of the distribution. Quantile will 183 // also panic if the length of p is not equal to the dimension of the 184 // distribution. 185 // 186 // All of the values of p must be between 0 and 1, inclusive, or Quantile will 187 // panic. 188 func (u *Uniform) Quantile(dst, p []float64) []float64 { 189 if len(p) != u.dim { 190 panic(badSizeMismatch) 191 } 192 dst = reuseAs(dst, u.dim) 193 for i, v := range p { 194 if v < 0 || v > 1 { 195 panic(badQuantile) 196 } 197 dst[i] = v*(u.bounds[i].Max-u.bounds[i].Min) + u.bounds[i].Min 198 } 199 return dst 200 }