github.com/gopherd/gonum@v0.0.4/stat/distmv/dirichlet.go (about) 1 // Copyright ©2016 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 "math/rand" 11 12 "github.com/gopherd/gonum/floats" 13 "github.com/gopherd/gonum/mat" 14 "github.com/gopherd/gonum/stat/distuv" 15 ) 16 17 // Dirichlet implements the Dirichlet probability distribution. 18 // 19 // The Dirichlet distribution is a continuous probability distribution that 20 // generates elements over the probability simplex, i.e. ||x||_1 = 1. The Dirichlet 21 // distribution is the conjugate prior to the categorical distribution and the 22 // multivariate version of the beta distribution. The probability of a point x is 23 // 1/Beta(α) \prod_i x_i^(α_i - 1) 24 // where Beta(α) is the multivariate Beta function (see the mathext package). 25 // 26 // For more information see https://en.wikipedia.org/wiki/Dirichlet_distribution 27 type Dirichlet struct { 28 alpha []float64 29 dim int 30 src rand.Source 31 32 lbeta float64 33 sumAlpha float64 34 } 35 36 // NewDirichlet creates a new dirichlet distribution with the given parameters alpha. 37 // NewDirichlet will panic if len(alpha) == 0, or if any alpha is <= 0. 38 func NewDirichlet(alpha []float64, src rand.Source) *Dirichlet { 39 dim := len(alpha) 40 if dim == 0 { 41 panic(badZeroDimension) 42 } 43 for _, v := range alpha { 44 if v <= 0 { 45 panic("dirichlet: non-positive alpha") 46 } 47 } 48 a := make([]float64, len(alpha)) 49 copy(a, alpha) 50 d := &Dirichlet{ 51 alpha: a, 52 dim: dim, 53 src: src, 54 } 55 d.lbeta, d.sumAlpha = d.genLBeta(a) 56 return d 57 } 58 59 // CovarianceMatrix calculates the covariance matrix of the distribution, 60 // storing the result in dst. Upon return, the value at element {i, j} of the 61 // covariance matrix is equal to the covariance of the i^th and j^th variables. 62 // covariance(i, j) = E[(x_i - E[x_i])(x_j - E[x_j])] 63 // If the dst matrix is empty it will be resized to the correct dimensions, 64 // otherwise dst must match the dimension of the receiver or CovarianceMatrix 65 // will panic. 66 func (d *Dirichlet) CovarianceMatrix(dst *mat.SymDense) { 67 if dst.IsEmpty() { 68 *dst = *(dst.GrowSym(d.dim).(*mat.SymDense)) 69 } else if dst.SymmetricDim() != d.dim { 70 panic("dirichelet: input matrix size mismatch") 71 } 72 scale := 1 / (d.sumAlpha * d.sumAlpha * (d.sumAlpha + 1)) 73 for i := 0; i < d.dim; i++ { 74 ai := d.alpha[i] 75 v := ai * (d.sumAlpha - ai) * scale 76 dst.SetSym(i, i, v) 77 for j := i + 1; j < d.dim; j++ { 78 aj := d.alpha[j] 79 v := -ai * aj * scale 80 dst.SetSym(i, j, v) 81 } 82 } 83 } 84 85 // genLBeta computes the generalized LBeta function. 86 func (d *Dirichlet) genLBeta(alpha []float64) (lbeta, sumAlpha float64) { 87 for _, alpha := range d.alpha { 88 lg, _ := math.Lgamma(alpha) 89 lbeta += lg 90 sumAlpha += alpha 91 } 92 lg, _ := math.Lgamma(sumAlpha) 93 return lbeta - lg, sumAlpha 94 } 95 96 // Dim returns the dimension of the distribution. 97 func (d *Dirichlet) Dim() int { 98 return d.dim 99 } 100 101 // LogProb computes the log of the pdf of the point x. 102 // 103 // It does not check that ||x||_1 = 1. 104 func (d *Dirichlet) LogProb(x []float64) float64 { 105 dim := d.dim 106 if len(x) != dim { 107 panic(badSizeMismatch) 108 } 109 var lprob float64 110 for i, x := range x { 111 lprob += (d.alpha[i] - 1) * math.Log(x) 112 } 113 lprob -= d.lbeta 114 return lprob 115 } 116 117 // Mean returns the mean of the probability distribution at x. If the 118 // input argument is nil, a new slice will be allocated, otherwise the result 119 // will be put in-place into the receiver. 120 func (d *Dirichlet) Mean(x []float64) []float64 { 121 x = reuseAs(x, d.dim) 122 copy(x, d.alpha) 123 floats.Scale(1/d.sumAlpha, x) 124 return x 125 } 126 127 // Prob computes the value of the probability density function at x. 128 func (d *Dirichlet) Prob(x []float64) float64 { 129 return math.Exp(d.LogProb(x)) 130 } 131 132 // Rand generates a random number according to the distributon. 133 // If the input slice is nil, new memory is allocated, otherwise the result is stored 134 // in place. 135 func (d *Dirichlet) Rand(x []float64) []float64 { 136 x = reuseAs(x, d.dim) 137 for i := range x { 138 x[i] = distuv.Gamma{Alpha: d.alpha[i], Beta: 1, Src: d.src}.Rand() 139 } 140 sum := floats.Sum(x) 141 floats.Scale(1/sum, x) 142 return x 143 }