fixed dependencies
This commit is contained in:
150
vendor/gonum.org/v1/gonum/stat/distmv/dirichlet.go
generated
vendored
Normal file
150
vendor/gonum.org/v1/gonum/stat/distmv/dirichlet.go
generated
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright ©2016 The Gonum Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package distmv
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"golang.org/x/exp/rand"
|
||||
|
||||
"gonum.org/v1/gonum/floats"
|
||||
"gonum.org/v1/gonum/mat"
|
||||
"gonum.org/v1/gonum/stat/distuv"
|
||||
)
|
||||
|
||||
// Dirichlet implements the Dirichlet probability distribution.
|
||||
//
|
||||
// The Dirichlet distribution is a continuous probability distribution that
|
||||
// generates elements over the probability simplex, i.e. ||x||_1 = 1. The Dirichlet
|
||||
// distribution is the conjugate prior to the categorical distribution and the
|
||||
// multivariate version of the beta distribution. The probability of a point x is
|
||||
//
|
||||
// 1/Beta(α) \prod_i x_i^(α_i - 1)
|
||||
//
|
||||
// where Beta(α) is the multivariate Beta function (see the mathext package).
|
||||
//
|
||||
// For more information see https://en.wikipedia.org/wiki/Dirichlet_distribution
|
||||
type Dirichlet struct {
|
||||
alpha []float64
|
||||
dim int
|
||||
src rand.Source
|
||||
|
||||
lbeta float64
|
||||
sumAlpha float64
|
||||
}
|
||||
|
||||
// NewDirichlet creates a new dirichlet distribution with the given parameters alpha.
|
||||
// NewDirichlet will panic if len(alpha) == 0, or if any alpha is <= 0.
|
||||
func NewDirichlet(alpha []float64, src rand.Source) *Dirichlet {
|
||||
dim := len(alpha)
|
||||
if dim == 0 {
|
||||
panic(badZeroDimension)
|
||||
}
|
||||
for _, v := range alpha {
|
||||
if v <= 0 {
|
||||
panic("dirichlet: non-positive alpha")
|
||||
}
|
||||
}
|
||||
a := make([]float64, len(alpha))
|
||||
copy(a, alpha)
|
||||
d := &Dirichlet{
|
||||
alpha: a,
|
||||
dim: dim,
|
||||
src: src,
|
||||
}
|
||||
d.lbeta, d.sumAlpha = d.genLBeta(a)
|
||||
return d
|
||||
}
|
||||
|
||||
// CovarianceMatrix calculates the covariance matrix of the distribution,
|
||||
// storing the result in dst. Upon return, the value at element {i, j} of the
|
||||
// covariance matrix is equal to the covariance of the i^th and j^th variables.
|
||||
//
|
||||
// covariance(i, j) = E[(x_i - E[x_i])(x_j - E[x_j])]
|
||||
//
|
||||
// If the dst matrix is empty it will be resized to the correct dimensions,
|
||||
// otherwise dst must match the dimension of the receiver or CovarianceMatrix
|
||||
// will panic.
|
||||
func (d *Dirichlet) CovarianceMatrix(dst *mat.SymDense) {
|
||||
if dst.IsEmpty() {
|
||||
*dst = *(dst.GrowSym(d.dim).(*mat.SymDense))
|
||||
} else if dst.SymmetricDim() != d.dim {
|
||||
panic("dirichelet: input matrix size mismatch")
|
||||
}
|
||||
scale := 1 / (d.sumAlpha * d.sumAlpha * (d.sumAlpha + 1))
|
||||
for i := 0; i < d.dim; i++ {
|
||||
ai := d.alpha[i]
|
||||
v := ai * (d.sumAlpha - ai) * scale
|
||||
dst.SetSym(i, i, v)
|
||||
for j := i + 1; j < d.dim; j++ {
|
||||
aj := d.alpha[j]
|
||||
v := -ai * aj * scale
|
||||
dst.SetSym(i, j, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// genLBeta computes the generalized LBeta function.
|
||||
func (d *Dirichlet) genLBeta(alpha []float64) (lbeta, sumAlpha float64) {
|
||||
for _, alpha := range d.alpha {
|
||||
lg, _ := math.Lgamma(alpha)
|
||||
lbeta += lg
|
||||
sumAlpha += alpha
|
||||
}
|
||||
lg, _ := math.Lgamma(sumAlpha)
|
||||
return lbeta - lg, sumAlpha
|
||||
}
|
||||
|
||||
// Dim returns the dimension of the distribution.
|
||||
func (d *Dirichlet) Dim() int {
|
||||
return d.dim
|
||||
}
|
||||
|
||||
// LogProb computes the log of the pdf of the point x.
|
||||
//
|
||||
// It does not check that ||x||_1 = 1.
|
||||
func (d *Dirichlet) LogProb(x []float64) float64 {
|
||||
dim := d.dim
|
||||
if len(x) != dim {
|
||||
panic(badSizeMismatch)
|
||||
}
|
||||
var lprob float64
|
||||
for i, x := range x {
|
||||
lprob += (d.alpha[i] - 1) * math.Log(x)
|
||||
}
|
||||
lprob -= d.lbeta
|
||||
return lprob
|
||||
}
|
||||
|
||||
// Mean returns the mean of the probability distribution.
|
||||
//
|
||||
// If dst is not nil, the mean will be stored in-place into dst and returned,
|
||||
// otherwise a new slice will be allocated first. If dst is not nil, it must
|
||||
// have length equal to the dimension of the distribution.
|
||||
func (d *Dirichlet) Mean(dst []float64) []float64 {
|
||||
dst = reuseAs(dst, d.dim)
|
||||
floats.ScaleTo(dst, 1/d.sumAlpha, d.alpha)
|
||||
return dst
|
||||
}
|
||||
|
||||
// Prob computes the value of the probability density function at x.
|
||||
func (d *Dirichlet) Prob(x []float64) float64 {
|
||||
return math.Exp(d.LogProb(x))
|
||||
}
|
||||
|
||||
// Rand generates a random number according to the distributon.
|
||||
//
|
||||
// If dst is not nil, the sample will be stored in-place into dst and returned,
|
||||
// otherwise a new slice will be allocated first. If dst is not nil, it must
|
||||
// have length equal to the dimension of the distribution.
|
||||
func (d *Dirichlet) Rand(dst []float64) []float64 {
|
||||
dst = reuseAs(dst, d.dim)
|
||||
for i, alpha := range d.alpha {
|
||||
dst[i] = distuv.Gamma{Alpha: alpha, Beta: 1, Src: d.src}.Rand()
|
||||
}
|
||||
sum := floats.Sum(dst)
|
||||
floats.Scale(1/sum, dst)
|
||||
return dst
|
||||
}
|
||||
28
vendor/gonum.org/v1/gonum/stat/distmv/distmv.go
generated
vendored
Normal file
28
vendor/gonum.org/v1/gonum/stat/distmv/distmv.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright ©2015 The Gonum Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package distmv
|
||||
|
||||
const (
|
||||
badQuantile = "distmv: quantile not between 0 and 1"
|
||||
badOutputLen = "distmv: output slice is not nil or the correct length"
|
||||
badInputLength = "distmv: input slice length mismatch"
|
||||
badSizeMismatch = "distmv: size mismatch"
|
||||
badZeroDimension = "distmv: zero dimensional input"
|
||||
nonPosDimension = "distmv: non-positive dimension input"
|
||||
)
|
||||
|
||||
const logTwoPi = 1.8378770664093454835606594728112352797227949472755668
|
||||
|
||||
// reuseAs returns a slice of length n. If len(dst) is n, dst is returned,
|
||||
// otherwise dst must be nil or reuseAs will panic.
|
||||
func reuseAs(dst []float64, n int) []float64 {
|
||||
if dst == nil {
|
||||
dst = make([]float64, n)
|
||||
}
|
||||
if len(dst) != n {
|
||||
panic(badOutputLen)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
6
vendor/gonum.org/v1/gonum/stat/distmv/doc.go
generated
vendored
Normal file
6
vendor/gonum.org/v1/gonum/stat/distmv/doc.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright ©2017 The Gonum Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package distmv provides multivariate random distribution types.
|
||||
package distmv // import "gonum.org/v1/gonum/stat/distmv"
|
||||
33
vendor/gonum.org/v1/gonum/stat/distmv/interfaces.go
generated
vendored
Normal file
33
vendor/gonum.org/v1/gonum/stat/distmv/interfaces.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright ©2016 The Gonum Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package distmv
|
||||
|
||||
// Quantiler returns the multi-dimensional inverse cumulative distribution function.
|
||||
// len(x) must equal len(p), and if x is non-nil, len(x) must also equal len(p).
|
||||
// If x is nil, a new slice will be allocated and returned, otherwise the quantile
|
||||
// will be stored in-place into x. All of the values of p must be between 0 and 1,
|
||||
// or Quantile will panic.
|
||||
type Quantiler interface {
|
||||
Quantile(x, p []float64) []float64
|
||||
}
|
||||
|
||||
// LogProber computes the log of the probability of the point x.
|
||||
type LogProber interface {
|
||||
LogProb(x []float64) float64
|
||||
}
|
||||
|
||||
// Rander generates a random number according to the distributon.
|
||||
// If the input is non-nil, len(x) must equal len(p) and the dimension of the distribution,
|
||||
// otherwise Quantile will panic.
|
||||
// If the input is nil, a new slice will be allocated and returned.
|
||||
type Rander interface {
|
||||
Rand(x []float64) []float64
|
||||
}
|
||||
|
||||
// RandLogProber is both a Rander and a LogProber.
|
||||
type RandLogProber interface {
|
||||
Rander
|
||||
LogProber
|
||||
}
|
||||
525
vendor/gonum.org/v1/gonum/stat/distmv/normal.go
generated
vendored
Normal file
525
vendor/gonum.org/v1/gonum/stat/distmv/normal.go
generated
vendored
Normal file
@@ -0,0 +1,525 @@
|
||||
// Copyright ©2015 The Gonum Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package distmv
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"golang.org/x/exp/rand"
|
||||
|
||||
"gonum.org/v1/gonum/floats"
|
||||
"gonum.org/v1/gonum/mat"
|
||||
"gonum.org/v1/gonum/stat"
|
||||
"gonum.org/v1/gonum/stat/distuv"
|
||||
)
|
||||
|
||||
// Normal is a multivariate normal distribution (also known as the multivariate
|
||||
// Gaussian distribution). Its pdf in k dimensions is given by
|
||||
//
|
||||
// (2 π)^(-k/2) |Σ|^(-1/2) exp(-1/2 (x-μ)'Σ^-1(x-μ))
|
||||
//
|
||||
// where μ is the mean vector and Σ the covariance matrix. Σ must be symmetric
|
||||
// and positive definite. Use NewNormal to construct.
|
||||
type Normal struct {
|
||||
mu []float64
|
||||
|
||||
sigma mat.SymDense
|
||||
|
||||
chol mat.Cholesky
|
||||
logSqrtDet float64
|
||||
dim int
|
||||
|
||||
// If src is altered, rnd must be updated.
|
||||
src rand.Source
|
||||
rnd *rand.Rand
|
||||
}
|
||||
|
||||
// NewNormal creates a new Normal with the given mean and covariance matrix.
|
||||
// NewNormal panics if len(mu) == 0, or if len(mu) != sigma.N. If the covariance
|
||||
// matrix is not positive-definite, the returned boolean is false.
|
||||
func NewNormal(mu []float64, sigma mat.Symmetric, src rand.Source) (*Normal, bool) {
|
||||
if len(mu) == 0 {
|
||||
panic(badZeroDimension)
|
||||
}
|
||||
dim := sigma.SymmetricDim()
|
||||
if dim != len(mu) {
|
||||
panic(badSizeMismatch)
|
||||
}
|
||||
n := &Normal{
|
||||
src: src,
|
||||
rnd: rand.New(src),
|
||||
dim: dim,
|
||||
mu: make([]float64, dim),
|
||||
}
|
||||
copy(n.mu, mu)
|
||||
ok := n.chol.Factorize(sigma)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
n.sigma = *mat.NewSymDense(dim, nil)
|
||||
n.sigma.CopySym(sigma)
|
||||
n.logSqrtDet = 0.5 * n.chol.LogDet()
|
||||
return n, true
|
||||
}
|
||||
|
||||
// NewNormalChol creates a new Normal distribution with the given mean and
|
||||
// covariance matrix represented by its Cholesky decomposition. NewNormalChol
|
||||
// panics if len(mu) is not equal to chol.Size().
|
||||
func NewNormalChol(mu []float64, chol *mat.Cholesky, src rand.Source) *Normal {
|
||||
dim := len(mu)
|
||||
if dim != chol.SymmetricDim() {
|
||||
panic(badSizeMismatch)
|
||||
}
|
||||
n := &Normal{
|
||||
src: src,
|
||||
rnd: rand.New(src),
|
||||
dim: dim,
|
||||
mu: make([]float64, dim),
|
||||
}
|
||||
n.chol.Clone(chol)
|
||||
copy(n.mu, mu)
|
||||
n.logSqrtDet = 0.5 * n.chol.LogDet()
|
||||
return n
|
||||
}
|
||||
|
||||
// NewNormalPrecision creates a new Normal distribution with the given mean and
|
||||
// precision matrix (inverse of the covariance matrix). NewNormalPrecision
|
||||
// panics if len(mu) is not equal to prec.SymmetricDim(). If the precision matrix
|
||||
// is not positive-definite, NewNormalPrecision returns nil for norm and false
|
||||
// for ok.
|
||||
func NewNormalPrecision(mu []float64, prec *mat.SymDense, src rand.Source) (norm *Normal, ok bool) {
|
||||
if len(mu) == 0 {
|
||||
panic(badZeroDimension)
|
||||
}
|
||||
dim := prec.SymmetricDim()
|
||||
if dim != len(mu) {
|
||||
panic(badSizeMismatch)
|
||||
}
|
||||
// TODO(btracey): Computing a matrix inverse is generally numerically unstable.
|
||||
// This only has to compute the inverse of a positive definite matrix, which
|
||||
// is much better, but this still loses precision. It is worth considering if
|
||||
// instead the precision matrix should be stored explicitly and used instead
|
||||
// of the Cholesky decomposition of the covariance matrix where appropriate.
|
||||
var chol mat.Cholesky
|
||||
ok = chol.Factorize(prec)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
var sigma mat.SymDense
|
||||
err := chol.InverseTo(&sigma)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return NewNormal(mu, &sigma, src)
|
||||
}
|
||||
|
||||
// ConditionNormal returns the Normal distribution that is the receiver conditioned
|
||||
// on the input evidence. The returned multivariate normal has dimension
|
||||
// n - len(observed), where n is the dimension of the original receiver. The updated
|
||||
// mean and covariance are
|
||||
//
|
||||
// mu = mu_un + sigma_{ob,un}ᵀ * sigma_{ob,ob}^-1 (v - mu_ob)
|
||||
// sigma = sigma_{un,un} - sigma_{ob,un}ᵀ * sigma_{ob,ob}^-1 * sigma_{ob,un}
|
||||
//
|
||||
// where mu_un and mu_ob are the original means of the unobserved and observed
|
||||
// variables respectively, sigma_{un,un} is the unobserved subset of the covariance
|
||||
// matrix, sigma_{ob,ob} is the observed subset of the covariance matrix, and
|
||||
// sigma_{un,ob} are the cross terms. The elements of x_2 have been observed with
|
||||
// values v. The dimension order is preserved during conditioning, so if the value
|
||||
// of dimension 1 is observed, the returned normal represents dimensions {0, 2, ...}
|
||||
// of the original Normal distribution.
|
||||
//
|
||||
// ConditionNormal returns {nil, false} if there is a failure during the update.
|
||||
// Mathematically this is impossible, but can occur with finite precision arithmetic.
|
||||
func (n *Normal) ConditionNormal(observed []int, values []float64, src rand.Source) (*Normal, bool) {
|
||||
if len(observed) == 0 {
|
||||
panic("normal: no observed value")
|
||||
}
|
||||
if len(observed) != len(values) {
|
||||
panic(badInputLength)
|
||||
}
|
||||
for _, v := range observed {
|
||||
if v < 0 || v >= n.Dim() {
|
||||
panic("normal: observed value out of bounds")
|
||||
}
|
||||
}
|
||||
|
||||
_, mu1, sigma11 := studentsTConditional(observed, values, math.Inf(1), n.mu, &n.sigma)
|
||||
if mu1 == nil {
|
||||
return nil, false
|
||||
}
|
||||
return NewNormal(mu1, sigma11, src)
|
||||
}
|
||||
|
||||
// CovarianceMatrix stores the covariance matrix of the distribution in dst.
|
||||
// Upon return, the value at element {i, j} of the covariance matrix is equal
|
||||
// to the covariance of the i^th and j^th variables.
|
||||
//
|
||||
// covariance(i, j) = E[(x_i - E[x_i])(x_j - E[x_j])]
|
||||
//
|
||||
// If the dst matrix is empty it will be resized to the correct dimensions,
|
||||
// otherwise dst must match the dimension of the receiver or CovarianceMatrix
|
||||
// will panic.
|
||||
func (n *Normal) CovarianceMatrix(dst *mat.SymDense) {
|
||||
if dst.IsEmpty() {
|
||||
*dst = *(dst.GrowSym(n.dim).(*mat.SymDense))
|
||||
} else if dst.SymmetricDim() != n.dim {
|
||||
panic("normal: input matrix size mismatch")
|
||||
}
|
||||
dst.CopySym(&n.sigma)
|
||||
}
|
||||
|
||||
// Dim returns the dimension of the distribution.
|
||||
func (n *Normal) Dim() int {
|
||||
return n.dim
|
||||
}
|
||||
|
||||
// Entropy returns the differential entropy of the distribution.
|
||||
func (n *Normal) Entropy() float64 {
|
||||
return float64(n.dim)/2*(1+logTwoPi) + n.logSqrtDet
|
||||
}
|
||||
|
||||
// LogProb computes the log of the pdf of the point x.
|
||||
func (n *Normal) LogProb(x []float64) float64 {
|
||||
dim := n.dim
|
||||
if len(x) != dim {
|
||||
panic(badSizeMismatch)
|
||||
}
|
||||
return normalLogProb(x, n.mu, &n.chol, n.logSqrtDet)
|
||||
}
|
||||
|
||||
// NormalLogProb computes the log probability of the location x for a Normal
|
||||
// distribution the given mean and Cholesky decomposition of the covariance matrix.
|
||||
// NormalLogProb panics if len(x) is not equal to len(mu), or if len(mu) != chol.Size().
|
||||
//
|
||||
// This function saves time and memory if the Cholesky decomposition is already
|
||||
// available. Otherwise, the NewNormal function should be used.
|
||||
func NormalLogProb(x, mu []float64, chol *mat.Cholesky) float64 {
|
||||
dim := len(mu)
|
||||
if len(x) != dim {
|
||||
panic(badSizeMismatch)
|
||||
}
|
||||
if chol.SymmetricDim() != dim {
|
||||
panic(badSizeMismatch)
|
||||
}
|
||||
logSqrtDet := 0.5 * chol.LogDet()
|
||||
return normalLogProb(x, mu, chol, logSqrtDet)
|
||||
}
|
||||
|
||||
// normalLogProb is the same as NormalLogProb, but does not make size checks and
|
||||
// additionally requires log(|Σ|^-0.5)
|
||||
func normalLogProb(x, mu []float64, chol *mat.Cholesky, logSqrtDet float64) float64 {
|
||||
dim := len(mu)
|
||||
c := -0.5*float64(dim)*logTwoPi - logSqrtDet
|
||||
dst := stat.Mahalanobis(mat.NewVecDense(dim, x), mat.NewVecDense(dim, mu), chol)
|
||||
return c - 0.5*dst*dst
|
||||
}
|
||||
|
||||
// MarginalNormal returns the marginal distribution of the given input variables.
|
||||
// That is, MarginalNormal returns
|
||||
//
|
||||
// p(x_i) = \int_{x_o} p(x_i | x_o) p(x_o) dx_o
|
||||
//
|
||||
// where x_i are the dimensions in the input, and x_o are the remaining dimensions.
|
||||
// See https://en.wikipedia.org/wiki/Marginal_distribution for more information.
|
||||
//
|
||||
// The input src is passed to the call to NewNormal.
|
||||
func (n *Normal) MarginalNormal(vars []int, src rand.Source) (*Normal, bool) {
|
||||
newMean := make([]float64, len(vars))
|
||||
for i, v := range vars {
|
||||
newMean[i] = n.mu[v]
|
||||
}
|
||||
var s mat.SymDense
|
||||
s.SubsetSym(&n.sigma, vars)
|
||||
return NewNormal(newMean, &s, src)
|
||||
}
|
||||
|
||||
// MarginalNormalSingle returns the marginal of the given input variable.
|
||||
// That is, MarginalNormal returns
|
||||
//
|
||||
// p(x_i) = \int_{x_¬i} p(x_i | x_¬i) p(x_¬i) dx_¬i
|
||||
//
|
||||
// where i is the input index.
|
||||
// See https://en.wikipedia.org/wiki/Marginal_distribution for more information.
|
||||
//
|
||||
// The input src is passed to the constructed distuv.Normal.
|
||||
func (n *Normal) MarginalNormalSingle(i int, src rand.Source) distuv.Normal {
|
||||
return distuv.Normal{
|
||||
Mu: n.mu[i],
|
||||
Sigma: math.Sqrt(n.sigma.At(i, i)),
|
||||
Src: src,
|
||||
}
|
||||
}
|
||||
|
||||
// Mean returns the mean of the probability distribution.
|
||||
//
|
||||
// If dst is not nil, the mean will be stored in-place into dst and returned,
|
||||
// otherwise a new slice will be allocated first. If dst is not nil, it must
|
||||
// have length equal to the dimension of the distribution.
|
||||
func (n *Normal) Mean(dst []float64) []float64 {
|
||||
dst = reuseAs(dst, n.dim)
|
||||
copy(dst, n.mu)
|
||||
return dst
|
||||
}
|
||||
|
||||
// Prob computes the value of the probability density function at x.
|
||||
func (n *Normal) Prob(x []float64) float64 {
|
||||
return math.Exp(n.LogProb(x))
|
||||
}
|
||||
|
||||
// Quantile returns the value of the multi-dimensional inverse cumulative
|
||||
// distribution function at p.
|
||||
//
|
||||
// If dst is not nil, the quantile will be stored in-place into dst and
|
||||
// returned, otherwise a new slice will be allocated first. If dst is not nil,
|
||||
// it must have length equal to the dimension of the distribution. Quantile will
|
||||
// also panic if the length of p is not equal to the dimension of the
|
||||
// distribution.
|
||||
//
|
||||
// All of the values of p must be between 0 and 1, inclusive, or Quantile will
|
||||
// panic.
|
||||
func (n *Normal) Quantile(dst, p []float64) []float64 {
|
||||
if len(p) != n.dim {
|
||||
panic(badInputLength)
|
||||
}
|
||||
dst = reuseAs(dst, n.dim)
|
||||
|
||||
// Transform to a standard normal and then transform to a multivariate Gaussian.
|
||||
for i, v := range p {
|
||||
dst[i] = distuv.UnitNormal.Quantile(v)
|
||||
}
|
||||
n.TransformNormal(dst, dst)
|
||||
return dst
|
||||
}
|
||||
|
||||
// Rand generates a random sample according to the distributon.
|
||||
//
|
||||
// If dst is not nil, the sample will be stored in-place into dst and returned,
|
||||
// otherwise a new slice will be allocated first. If dst is not nil, it must
|
||||
// have length equal to the dimension of the distribution.
|
||||
func (n *Normal) Rand(dst []float64) []float64 {
|
||||
return NormalRand(dst, n.mu, &n.chol, n.src)
|
||||
}
|
||||
|
||||
// NormalRand generates a random sample from a multivariate normal distributon
|
||||
// given by the mean and the Cholesky factorization of the covariance matrix.
|
||||
//
|
||||
// If dst is not nil, the sample will be stored in-place into dst and returned,
|
||||
// otherwise a new slice will be allocated first. If dst is not nil, it must
|
||||
// have length equal to the dimension of the distribution.
|
||||
//
|
||||
// This function saves time and memory if the Cholesky factorization is already
|
||||
// available. Otherwise, the NewNormal function should be used.
|
||||
func NormalRand(dst, mean []float64, chol *mat.Cholesky, src rand.Source) []float64 {
|
||||
if len(mean) != chol.SymmetricDim() {
|
||||
panic(badInputLength)
|
||||
}
|
||||
dst = reuseAs(dst, len(mean))
|
||||
|
||||
if src == nil {
|
||||
for i := range dst {
|
||||
dst[i] = rand.NormFloat64()
|
||||
}
|
||||
} else {
|
||||
rnd := rand.New(src)
|
||||
for i := range dst {
|
||||
dst[i] = rnd.NormFloat64()
|
||||
}
|
||||
}
|
||||
transformNormal(dst, dst, mean, chol)
|
||||
return dst
|
||||
}
|
||||
|
||||
// EigenSym is an eigendecomposition of a symmetric matrix.
|
||||
type EigenSym interface {
|
||||
mat.Symmetric
|
||||
// RawValues returns all eigenvalues in ascending order. The returned slice
|
||||
// must not be modified.
|
||||
RawValues() []float64
|
||||
// RawQ returns an orthogonal matrix whose columns contain the eigenvectors.
|
||||
// The returned matrix must not be modified.
|
||||
RawQ() mat.Matrix
|
||||
}
|
||||
|
||||
// PositivePartEigenSym is an EigenSym that sets any negative eigenvalues from
|
||||
// the given eigendecomposition to zero but otherwise returns the values
|
||||
// unchanged.
|
||||
//
|
||||
// This is useful for filtering eigenvalues of positive semi-definite matrices
|
||||
// that are almost zero but negative due to rounding errors.
|
||||
type PositivePartEigenSym struct {
|
||||
ed *mat.EigenSym
|
||||
vals []float64
|
||||
}
|
||||
|
||||
var _ EigenSym = (*PositivePartEigenSym)(nil)
|
||||
var _ EigenSym = (*mat.EigenSym)(nil)
|
||||
|
||||
// NewPositivePartEigenSym returns a new PositivePartEigenSym, wrapping the
|
||||
// given eigendecomposition.
|
||||
func NewPositivePartEigenSym(ed *mat.EigenSym) *PositivePartEigenSym {
|
||||
n := ed.SymmetricDim()
|
||||
vals := make([]float64, n)
|
||||
for i, lamda := range ed.RawValues() {
|
||||
if lamda > 0 {
|
||||
vals[i] = lamda
|
||||
}
|
||||
}
|
||||
return &PositivePartEigenSym{
|
||||
ed: ed,
|
||||
vals: vals,
|
||||
}
|
||||
}
|
||||
|
||||
// SymmetricDim returns the value from the wrapped eigendecomposition.
|
||||
func (ed *PositivePartEigenSym) SymmetricDim() int { return ed.ed.SymmetricDim() }
|
||||
|
||||
// Dims returns the dimensions from the wrapped eigendecomposition.
|
||||
func (ed *PositivePartEigenSym) Dims() (r, c int) { return ed.ed.Dims() }
|
||||
|
||||
// At returns the value from the wrapped eigendecomposition.
|
||||
func (ed *PositivePartEigenSym) At(i, j int) float64 { return ed.ed.At(i, j) }
|
||||
|
||||
// T returns the transpose from the wrapped eigendecomposition.
|
||||
func (ed *PositivePartEigenSym) T() mat.Matrix { return ed.ed.T() }
|
||||
|
||||
// RawQ returns the orthogonal matrix Q from the wrapped eigendecomposition. The
|
||||
// returned matrix must not be modified.
|
||||
func (ed *PositivePartEigenSym) RawQ() mat.Matrix { return ed.ed.RawQ() }
|
||||
|
||||
// RawValues returns the eigenvalues from the wrapped eigendecomposition in
|
||||
// ascending order with any negative value replaced by zero. The returned slice
|
||||
// must not be modified.
|
||||
func (ed *PositivePartEigenSym) RawValues() []float64 { return ed.vals }
|
||||
|
||||
// NormalRandCov generates a random sample from a multivariate normal
|
||||
// distribution given by the mean and the covariance matrix.
|
||||
//
|
||||
// If dst is not nil, the sample will be stored in-place into dst and returned,
|
||||
// otherwise a new slice will be allocated first. If dst is not nil, it must
|
||||
// have length equal to the dimension of the distribution.
|
||||
//
|
||||
// cov should be *mat.Cholesky, *mat.PivotedCholesky or EigenSym, otherwise
|
||||
// NormalRandCov will be very inefficient because a pivoted Cholesky
|
||||
// factorization of cov will be computed for every sample.
|
||||
//
|
||||
// If cov is an EigenSym, all eigenvalues returned by RawValues must be
|
||||
// non-negative, otherwise NormalRandCov will panic.
|
||||
func NormalRandCov(dst, mean []float64, cov mat.Symmetric, src rand.Source) []float64 {
|
||||
n := len(mean)
|
||||
if cov.SymmetricDim() != n {
|
||||
panic(badInputLength)
|
||||
}
|
||||
dst = reuseAs(dst, n)
|
||||
if src == nil {
|
||||
for i := range dst {
|
||||
dst[i] = rand.NormFloat64()
|
||||
}
|
||||
} else {
|
||||
rnd := rand.New(src)
|
||||
for i := range dst {
|
||||
dst[i] = rnd.NormFloat64()
|
||||
}
|
||||
}
|
||||
|
||||
switch cov := cov.(type) {
|
||||
case *mat.Cholesky:
|
||||
dstVec := mat.NewVecDense(n, dst)
|
||||
dstVec.MulVec(cov.RawU().T(), dstVec)
|
||||
case *mat.PivotedCholesky:
|
||||
dstVec := mat.NewVecDense(n, dst)
|
||||
dstVec.MulVec(cov.RawU().T(), dstVec)
|
||||
dstVec.Permute(cov.ColumnPivots(nil), true)
|
||||
case EigenSym:
|
||||
vals := cov.RawValues()
|
||||
if vals[0] < 0 {
|
||||
panic("distmv: covariance matrix is not positive semi-definite")
|
||||
}
|
||||
for i, val := range vals {
|
||||
dst[i] *= math.Sqrt(val)
|
||||
}
|
||||
dstVec := mat.NewVecDense(n, dst)
|
||||
dstVec.MulVec(cov.RawQ(), dstVec)
|
||||
default:
|
||||
var chol mat.PivotedCholesky
|
||||
chol.Factorize(cov, -1)
|
||||
dstVec := mat.NewVecDense(n, dst)
|
||||
dstVec.MulVec(chol.RawU().T(), dstVec)
|
||||
dstVec.Permute(chol.ColumnPivots(nil), true)
|
||||
}
|
||||
floats.Add(dst, mean)
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// ScoreInput returns the gradient of the log-probability with respect to the
|
||||
// input x. That is, ScoreInput computes
|
||||
//
|
||||
// ∇_x log(p(x))
|
||||
//
|
||||
// If dst is not nil, the score will be stored in-place into dst and returned,
|
||||
// otherwise a new slice will be allocated first. If dst is not nil, it must
|
||||
// have length equal to the dimension of the distribution.
|
||||
func (n *Normal) ScoreInput(dst, x []float64) []float64 {
|
||||
// Normal log probability is
|
||||
// c - 0.5*(x-μ)' Σ^-1 (x-μ).
|
||||
// So the derivative is just
|
||||
// -Σ^-1 (x-μ).
|
||||
if len(x) != n.Dim() {
|
||||
panic(badInputLength)
|
||||
}
|
||||
dst = reuseAs(dst, n.dim)
|
||||
|
||||
floats.SubTo(dst, x, n.mu)
|
||||
dstVec := mat.NewVecDense(len(dst), dst)
|
||||
err := n.chol.SolveVecTo(dstVec, dstVec)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
floats.Scale(-1, dst)
|
||||
return dst
|
||||
}
|
||||
|
||||
// SetMean changes the mean of the normal distribution. SetMean panics if len(mu)
|
||||
// does not equal the dimension of the normal distribution.
|
||||
func (n *Normal) SetMean(mu []float64) {
|
||||
if len(mu) != n.Dim() {
|
||||
panic(badSizeMismatch)
|
||||
}
|
||||
copy(n.mu, mu)
|
||||
}
|
||||
|
||||
// TransformNormal transforms x generated from a standard multivariate normal
|
||||
// into a vector that has been generated under the normal distribution of the
|
||||
// receiver.
|
||||
//
|
||||
// If dst is not nil, the result will be stored in-place into dst and returned,
|
||||
// otherwise a new slice will be allocated first. If dst is not nil, it must
|
||||
// have length equal to the dimension of the distribution. TransformNormal will
|
||||
// also panic if the length of x is not equal to the dimension of the receiver.
|
||||
func (n *Normal) TransformNormal(dst, x []float64) []float64 {
|
||||
if len(x) != n.dim {
|
||||
panic(badInputLength)
|
||||
}
|
||||
dst = reuseAs(dst, n.dim)
|
||||
transformNormal(dst, x, n.mu, &n.chol)
|
||||
return dst
|
||||
}
|
||||
|
||||
// transformNormal performs the same operation as Normal.TransformNormal except
|
||||
// no safety checks are performed and all memory must be provided.
|
||||
func transformNormal(dst, normal, mu []float64, chol *mat.Cholesky) []float64 {
|
||||
dim := len(mu)
|
||||
dstVec := mat.NewVecDense(dim, dst)
|
||||
srcVec := mat.NewVecDense(dim, normal)
|
||||
// If dst and normal are the same slice, make them the same Vector otherwise
|
||||
// mat complains about being tricky.
|
||||
if &normal[0] == &dst[0] {
|
||||
srcVec = dstVec
|
||||
}
|
||||
dstVec.MulVec(chol.RawU().T(), srcVec)
|
||||
floats.Add(dst, mu)
|
||||
return dst
|
||||
}
|
||||
390
vendor/gonum.org/v1/gonum/stat/distmv/statdist.go
generated
vendored
Normal file
390
vendor/gonum.org/v1/gonum/stat/distmv/statdist.go
generated
vendored
Normal file
@@ -0,0 +1,390 @@
|
||||
// Copyright ©2016 The Gonum Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package distmv
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"gonum.org/v1/gonum/floats"
|
||||
"gonum.org/v1/gonum/mat"
|
||||
"gonum.org/v1/gonum/mathext"
|
||||
"gonum.org/v1/gonum/spatial/r1"
|
||||
"gonum.org/v1/gonum/stat"
|
||||
)
|
||||
|
||||
// Bhattacharyya is a type for computing the Bhattacharyya distance between
|
||||
// probability distributions.
|
||||
//
|
||||
// The Bhattacharyya distance is defined as
|
||||
//
|
||||
// D_B = -ln(BC(l,r))
|
||||
// BC = \int_-∞^∞ (p(x)q(x))^(1/2) dx
|
||||
//
|
||||
// Where BC is known as the Bhattacharyya coefficient.
|
||||
// The Bhattacharyya distance is related to the Hellinger distance by
|
||||
//
|
||||
// H(l,r) = sqrt(1-BC(l,r))
|
||||
//
|
||||
// For more information, see
|
||||
//
|
||||
// https://en.wikipedia.org/wiki/Bhattacharyya_distance
|
||||
type Bhattacharyya struct{}
|
||||
|
||||
// DistNormal computes the Bhattacharyya distance between normal distributions l and r.
|
||||
// The dimensions of the input distributions must match or DistNormal will panic.
|
||||
//
|
||||
// For Normal distributions, the Bhattacharyya distance is
|
||||
//
|
||||
// Σ = (Σ_l + Σ_r)/2
|
||||
// D_B = (1/8)*(μ_l - μ_r)ᵀ*Σ^-1*(μ_l - μ_r) + (1/2)*ln(det(Σ)/(det(Σ_l)*det(Σ_r))^(1/2))
|
||||
func (Bhattacharyya) DistNormal(l, r *Normal) float64 {
|
||||
dim := l.Dim()
|
||||
if dim != r.Dim() {
|
||||
panic(badSizeMismatch)
|
||||
}
|
||||
|
||||
var sigma mat.SymDense
|
||||
sigma.AddSym(&l.sigma, &r.sigma)
|
||||
sigma.ScaleSym(0.5, &sigma)
|
||||
|
||||
var chol mat.Cholesky
|
||||
chol.Factorize(&sigma)
|
||||
|
||||
mahalanobis := stat.Mahalanobis(mat.NewVecDense(dim, l.mu), mat.NewVecDense(dim, r.mu), &chol)
|
||||
mahalanobisSq := mahalanobis * mahalanobis
|
||||
|
||||
dl := l.chol.LogDet()
|
||||
dr := r.chol.LogDet()
|
||||
ds := chol.LogDet()
|
||||
|
||||
return 0.125*mahalanobisSq + 0.5*ds - 0.25*dl - 0.25*dr
|
||||
}
|
||||
|
||||
// DistUniform computes the Bhattacharyya distance between uniform distributions l and r.
|
||||
// The dimensions of the input distributions must match or DistUniform will panic.
|
||||
func (Bhattacharyya) DistUniform(l, r *Uniform) float64 {
|
||||
if len(l.bounds) != len(r.bounds) {
|
||||
panic(badSizeMismatch)
|
||||
}
|
||||
// BC = \int \sqrt(p(x)q(x)), which for uniform distributions is a constant
|
||||
// over the volume where both distributions have positive probability.
|
||||
// Compute the overlap and the value of sqrt(p(x)q(x)). The entropy is the
|
||||
// negative log probability of the distribution (use instead of LogProb so
|
||||
// it is not necessary to construct an x value).
|
||||
//
|
||||
// BC = volume * sqrt(p(x)q(x))
|
||||
// logBC = log(volume) + 0.5*(logP + logQ)
|
||||
// D_B = -logBC
|
||||
return -unifLogVolOverlap(l.bounds, r.bounds) + 0.5*(l.Entropy()+r.Entropy())
|
||||
}
|
||||
|
||||
// unifLogVolOverlap computes the log of the volume of the hyper-rectangle where
|
||||
// both uniform distributions have positive probability.
|
||||
func unifLogVolOverlap(b1, b2 []r1.Interval) float64 {
|
||||
var logVolOverlap float64
|
||||
for dim, v1 := range b1 {
|
||||
v2 := b2[dim]
|
||||
// If the surfaces don't overlap, then the volume is 0
|
||||
if v1.Max <= v2.Min || v2.Max <= v1.Min {
|
||||
return math.Inf(-1)
|
||||
}
|
||||
vol := math.Min(v1.Max, v2.Max) - math.Max(v1.Min, v2.Min)
|
||||
logVolOverlap += math.Log(vol)
|
||||
}
|
||||
return logVolOverlap
|
||||
}
|
||||
|
||||
// CrossEntropy is a type for computing the cross-entropy between probability
|
||||
// distributions.
|
||||
//
|
||||
// The cross-entropy is defined as
|
||||
// - \int_x l(x) log(r(x)) dx = KL(l || r) + H(l)
|
||||
//
|
||||
// where KL is the Kullback-Leibler divergence and H is the entropy.
|
||||
// For more information, see
|
||||
//
|
||||
// https://en.wikipedia.org/wiki/Cross_entropy
|
||||
type CrossEntropy struct{}
|
||||
|
||||
// DistNormal returns the cross-entropy between normal distributions l and r.
|
||||
// The dimensions of the input distributions must match or DistNormal will panic.
|
||||
func (CrossEntropy) DistNormal(l, r *Normal) float64 {
|
||||
if l.Dim() != r.Dim() {
|
||||
panic(badSizeMismatch)
|
||||
}
|
||||
kl := KullbackLeibler{}.DistNormal(l, r)
|
||||
return kl + l.Entropy()
|
||||
}
|
||||
|
||||
// Hellinger is a type for computing the Hellinger distance between probability
|
||||
// distributions.
|
||||
//
|
||||
// The Hellinger distance is defined as
|
||||
//
|
||||
// H^2(l,r) = 1/2 * int_x (\sqrt(l(x)) - \sqrt(r(x)))^2 dx
|
||||
//
|
||||
// and is bounded between 0 and 1. Note the above formula defines the squared
|
||||
// Hellinger distance, while this returns the Hellinger distance itself.
|
||||
// The Hellinger distance is related to the Bhattacharyya distance by
|
||||
//
|
||||
// H^2 = 1 - exp(-D_B)
|
||||
//
|
||||
// For more information, see
|
||||
//
|
||||
// https://en.wikipedia.org/wiki/Hellinger_distance
|
||||
type Hellinger struct{}
|
||||
|
||||
// DistNormal returns the Hellinger distance between normal distributions l and r.
|
||||
// The dimensions of the input distributions must match or DistNormal will panic.
|
||||
//
|
||||
// See the documentation of Bhattacharyya.DistNormal for the formula for Normal
|
||||
// distributions.
|
||||
func (Hellinger) DistNormal(l, r *Normal) float64 {
|
||||
if l.Dim() != r.Dim() {
|
||||
panic(badSizeMismatch)
|
||||
}
|
||||
db := Bhattacharyya{}.DistNormal(l, r)
|
||||
bc := math.Exp(-db)
|
||||
return math.Sqrt(1 - bc)
|
||||
}
|
||||
|
||||
// KullbackLeibler is a type for computing the Kullback-Leibler divergence from l to r.
|
||||
//
|
||||
// The Kullback-Leibler divergence is defined as
|
||||
//
|
||||
// D_KL(l || r ) = \int_x p(x) log(p(x)/q(x)) dx
|
||||
//
|
||||
// Note that the Kullback-Leibler divergence is not symmetric with respect to
|
||||
// the order of the input arguments.
|
||||
type KullbackLeibler struct{}
|
||||
|
||||
// DistDirichlet returns the Kullback-Leibler divergence between Dirichlet
|
||||
// distributions l and r. The dimensions of the input distributions must match
|
||||
// or DistDirichlet will panic.
|
||||
//
|
||||
// For two Dirichlet distributions, the KL divergence is computed as
|
||||
//
|
||||
// D_KL(l || r) = log Γ(α_0_l) - \sum_i log Γ(α_i_l) - log Γ(α_0_r) + \sum_i log Γ(α_i_r)
|
||||
// + \sum_i (α_i_l - α_i_r)(ψ(α_i_l)- ψ(α_0_l))
|
||||
//
|
||||
// Where Γ is the gamma function, ψ is the digamma function, and α_0 is the
|
||||
// sum of the Dirichlet parameters.
|
||||
func (KullbackLeibler) DistDirichlet(l, r *Dirichlet) float64 {
|
||||
// http://bariskurt.com/kullback-leibler-divergence-between-two-dirichlet-and-beta-distributions/
|
||||
if l.Dim() != r.Dim() {
|
||||
panic(badSizeMismatch)
|
||||
}
|
||||
l0, _ := math.Lgamma(l.sumAlpha)
|
||||
r0, _ := math.Lgamma(r.sumAlpha)
|
||||
dl := mathext.Digamma(l.sumAlpha)
|
||||
|
||||
var l1, r1, c float64
|
||||
for i, al := range l.alpha {
|
||||
ar := r.alpha[i]
|
||||
vl, _ := math.Lgamma(al)
|
||||
l1 += vl
|
||||
vr, _ := math.Lgamma(ar)
|
||||
r1 += vr
|
||||
c += (al - ar) * (mathext.Digamma(al) - dl)
|
||||
}
|
||||
return l0 - l1 - r0 + r1 + c
|
||||
}
|
||||
|
||||
// DistNormal returns the KullbackLeibler divergence between normal distributions l and r.
|
||||
// The dimensions of the input distributions must match or DistNormal will panic.
|
||||
//
|
||||
// For two normal distributions, the KL divergence is computed as
|
||||
//
|
||||
// D_KL(l || r) = 0.5*[ln(|Σ_r|) - ln(|Σ_l|) + (μ_l - μ_r)ᵀ*Σ_r^-1*(μ_l - μ_r) + tr(Σ_r^-1*Σ_l)-d]
|
||||
func (KullbackLeibler) DistNormal(l, r *Normal) float64 {
|
||||
dim := l.Dim()
|
||||
if dim != r.Dim() {
|
||||
panic(badSizeMismatch)
|
||||
}
|
||||
|
||||
mahalanobis := stat.Mahalanobis(mat.NewVecDense(dim, l.mu), mat.NewVecDense(dim, r.mu), &r.chol)
|
||||
mahalanobisSq := mahalanobis * mahalanobis
|
||||
|
||||
// TODO(btracey): Optimize where there is a SolveCholeskySym
|
||||
// TODO(btracey): There may be a more efficient way to just compute the trace
|
||||
// Compute tr(Σ_r^-1*Σ_l) using the fact that Σ_l = Uᵀ * U
|
||||
var u mat.TriDense
|
||||
l.chol.UTo(&u)
|
||||
var m mat.Dense
|
||||
err := r.chol.SolveTo(&m, u.T())
|
||||
if err != nil {
|
||||
return math.NaN()
|
||||
}
|
||||
m.Mul(&m, &u)
|
||||
tr := mat.Trace(&m)
|
||||
|
||||
return r.logSqrtDet - l.logSqrtDet + 0.5*(mahalanobisSq+tr-float64(l.dim))
|
||||
}
|
||||
|
||||
// DistUniform returns the KullbackLeibler divergence between uniform distributions
|
||||
// l and r. The dimensions of the input distributions must match or DistUniform
|
||||
// will panic.
|
||||
func (KullbackLeibler) DistUniform(l, r *Uniform) float64 {
|
||||
bl := l.Bounds(nil)
|
||||
br := r.Bounds(nil)
|
||||
if len(bl) != len(br) {
|
||||
panic(badSizeMismatch)
|
||||
}
|
||||
|
||||
// The KL is ∞ if l is not completely contained within r, because then
|
||||
// r(x) is zero when l(x) is non-zero for some x.
|
||||
contained := true
|
||||
for i, v := range bl {
|
||||
if v.Min < br[i].Min || br[i].Max < v.Max {
|
||||
contained = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !contained {
|
||||
return math.Inf(1)
|
||||
}
|
||||
|
||||
// The KL divergence is finite.
|
||||
//
|
||||
// KL defines 0*ln(0) = 0, so there is no contribution to KL where l(x) = 0.
|
||||
// Inside the region, l(x) and r(x) are constant (uniform distribution), and
|
||||
// this constant is integrated over l(x), which integrates out to one.
|
||||
// The entropy is -log(p(x)).
|
||||
logPx := -l.Entropy()
|
||||
logQx := -r.Entropy()
|
||||
return logPx - logQx
|
||||
}
|
||||
|
||||
// Renyi is a type for computing the Rényi divergence of order α from l to r.
|
||||
//
|
||||
// The Rényi divergence with α > 0, α ≠ 1 is defined as
|
||||
//
|
||||
// D_α(l || r) = 1/(α-1) log(\int_-∞^∞ l(x)^α r(x)^(1-α)dx)
|
||||
//
|
||||
// The Rényi divergence has special forms for α = 0 and α = 1. This type does
|
||||
// not implement α = ∞. For α = 0,
|
||||
//
|
||||
// D_0(l || r) = -log \int_-∞^∞ r(x)1{p(x)>0} dx
|
||||
//
|
||||
// that is, the negative log probability under r(x) that l(x) > 0.
|
||||
// When α = 1, the Rényi divergence is equal to the Kullback-Leibler divergence.
|
||||
// The Rényi divergence is also equal to half the Bhattacharyya distance when α = 0.5.
|
||||
//
|
||||
// The parameter α must be in 0 ≤ α < ∞ or the distance functions will panic.
|
||||
type Renyi struct {
|
||||
Alpha float64
|
||||
}
|
||||
|
||||
// DistNormal returns the Rényi divergence between normal distributions l and r.
|
||||
// The dimensions of the input distributions must match or DistNormal will panic.
|
||||
//
|
||||
// For two normal distributions, the Rényi divergence is computed as
|
||||
//
|
||||
// Σ_α = (1-α) Σ_l + αΣ_r
|
||||
// D_α(l||r) = α/2 * (μ_l - μ_r)'*Σ_α^-1*(μ_l - μ_r) + 1/(2(α-1))*ln(|Σ_λ|/(|Σ_l|^(1-α)*|Σ_r|^α))
|
||||
//
|
||||
// For a more nicely formatted version of the formula, see Eq. 15 of
|
||||
//
|
||||
// Kolchinsky, Artemy, and Brendan D. Tracey. "Estimating Mixture Entropy
|
||||
// with Pairwise Distances." arXiv preprint arXiv:1706.02419 (2017).
|
||||
//
|
||||
// Note that the this formula is for Chernoff divergence, which differs from
|
||||
// Rényi divergence by a factor of 1-α. Also be aware that most sources in
|
||||
// the literature report this formula incorrectly.
|
||||
func (renyi Renyi) DistNormal(l, r *Normal) float64 {
|
||||
if renyi.Alpha < 0 {
|
||||
panic("renyi: alpha < 0")
|
||||
}
|
||||
dim := l.Dim()
|
||||
if dim != r.Dim() {
|
||||
panic(badSizeMismatch)
|
||||
}
|
||||
if renyi.Alpha == 0 {
|
||||
return 0
|
||||
}
|
||||
if renyi.Alpha == 1 {
|
||||
return KullbackLeibler{}.DistNormal(l, r)
|
||||
}
|
||||
|
||||
logDetL := l.chol.LogDet()
|
||||
logDetR := r.chol.LogDet()
|
||||
|
||||
// Σ_α = (1-α)Σ_l + αΣ_r.
|
||||
sigA := mat.NewSymDense(dim, nil)
|
||||
for i := 0; i < dim; i++ {
|
||||
for j := i; j < dim; j++ {
|
||||
v := (1-renyi.Alpha)*l.sigma.At(i, j) + renyi.Alpha*r.sigma.At(i, j)
|
||||
sigA.SetSym(i, j, v)
|
||||
}
|
||||
}
|
||||
|
||||
var chol mat.Cholesky
|
||||
ok := chol.Factorize(sigA)
|
||||
if !ok {
|
||||
return math.NaN()
|
||||
}
|
||||
logDetA := chol.LogDet()
|
||||
|
||||
mahalanobis := stat.Mahalanobis(mat.NewVecDense(dim, l.mu), mat.NewVecDense(dim, r.mu), &chol)
|
||||
mahalanobisSq := mahalanobis * mahalanobis
|
||||
|
||||
return (renyi.Alpha/2)*mahalanobisSq + 1/(2*(1-renyi.Alpha))*(logDetA-(1-renyi.Alpha)*logDetL-renyi.Alpha*logDetR)
|
||||
}
|
||||
|
||||
// Wasserstein is a type for computing the Wasserstein distance between two
|
||||
// probability distributions.
|
||||
//
|
||||
// The Wasserstein distance is defined as
|
||||
//
|
||||
// W(l,r) := inf 𝔼(||X-Y||_2^2)^1/2
|
||||
//
|
||||
// For more information, see
|
||||
//
|
||||
// https://en.wikipedia.org/wiki/Wasserstein_metric
|
||||
type Wasserstein struct{}
|
||||
|
||||
// DistNormal returns the Wasserstein distance between normal distributions l and r.
|
||||
// The dimensions of the input distributions must match or DistNormal will panic.
|
||||
//
|
||||
// The Wasserstein distance for Normal distributions is
|
||||
//
|
||||
// d^2 = ||m_l - m_r||_2^2 + Tr(Σ_l + Σ_r - 2(Σ_l^(1/2)*Σ_r*Σ_l^(1/2))^(1/2))
|
||||
//
|
||||
// For more information, see
|
||||
//
|
||||
// http://djalil.chafai.net/blog/2010/04/30/wasserstein-distance-between-two-gaussians/
|
||||
func (Wasserstein) DistNormal(l, r *Normal) float64 {
|
||||
dim := l.Dim()
|
||||
if dim != r.Dim() {
|
||||
panic(badSizeMismatch)
|
||||
}
|
||||
|
||||
d := floats.Distance(l.mu, r.mu, 2)
|
||||
d = d * d
|
||||
|
||||
// Compute Σ_l^(1/2)
|
||||
var ssl mat.SymDense
|
||||
err := ssl.PowPSD(&l.sigma, 0.5)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Compute Σ_l^(1/2)*Σ_r*Σ_l^(1/2)
|
||||
var mean mat.Dense
|
||||
mean.Mul(&ssl, &r.sigma)
|
||||
mean.Mul(&mean, &ssl)
|
||||
|
||||
// Reinterpret as symdense, and take Σ^(1/2)
|
||||
meanSym := mat.NewSymDense(dim, mean.RawMatrix().Data)
|
||||
err = ssl.PowPSD(meanSym, 0.5)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
tr := mat.Trace(&r.sigma)
|
||||
tl := mat.Trace(&l.sigma)
|
||||
tm := mat.Trace(&ssl)
|
||||
|
||||
return d + tl + tr - 2*tm
|
||||
}
|
||||
362
vendor/gonum.org/v1/gonum/stat/distmv/studentst.go
generated
vendored
Normal file
362
vendor/gonum.org/v1/gonum/stat/distmv/studentst.go
generated
vendored
Normal file
@@ -0,0 +1,362 @@
|
||||
// Copyright ©2016 The Gonum Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package distmv
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/exp/rand"
|
||||
"golang.org/x/tools/container/intsets"
|
||||
|
||||
"gonum.org/v1/gonum/floats"
|
||||
"gonum.org/v1/gonum/mat"
|
||||
"gonum.org/v1/gonum/stat"
|
||||
"gonum.org/v1/gonum/stat/distuv"
|
||||
)
|
||||
|
||||
// StudentsT is a multivariate Student's T distribution. It is a distribution over
|
||||
// ℝ^n with the probability density
|
||||
//
|
||||
// p(y) = (Γ((ν+n)/2) / Γ(ν/2)) * (νπ)^(-n/2) * |Ʃ|^(-1/2) *
|
||||
// (1 + 1/ν * (y-μ)ᵀ * Ʃ^-1 * (y-μ))^(-(ν+n)/2)
|
||||
//
|
||||
// where ν is a scalar greater than 2, μ is a vector in ℝ^n, and Ʃ is an n×n
|
||||
// symmetric positive definite matrix.
|
||||
//
|
||||
// In this distribution, ν sets the spread of the distribution, similar to
|
||||
// the degrees of freedom in a univariate Student's T distribution. As ν → ∞,
|
||||
// the distribution approaches a multi-variate normal distribution.
|
||||
// μ is the mean of the distribution, and the covariance is ν/(ν-2)*Ʃ.
|
||||
//
|
||||
// See https://en.wikipedia.org/wiki/Student%27s_t-distribution and
|
||||
// http://users.isy.liu.se/en/rt/roth/student.pdf for more information.
|
||||
type StudentsT struct {
|
||||
nu float64
|
||||
mu []float64
|
||||
// If src is altered, rnd must be updated.
|
||||
src rand.Source
|
||||
rnd *rand.Rand
|
||||
|
||||
sigma mat.SymDense // only stored if needed
|
||||
|
||||
chol mat.Cholesky
|
||||
lower mat.TriDense
|
||||
logSqrtDet float64
|
||||
dim int
|
||||
}
|
||||
|
||||
// NewStudentsT creates a new StudentsT with the given nu, mu, and sigma
|
||||
// parameters.
|
||||
//
|
||||
// NewStudentsT panics if len(mu) == 0, or if len(mu) != sigma.SymmetricDim(). If
|
||||
// the covariance matrix is not positive-definite, nil is returned and ok is false.
|
||||
func NewStudentsT(mu []float64, sigma mat.Symmetric, nu float64, src rand.Source) (dist *StudentsT, ok bool) {
|
||||
if len(mu) == 0 {
|
||||
panic(badZeroDimension)
|
||||
}
|
||||
dim := sigma.SymmetricDim()
|
||||
if dim != len(mu) {
|
||||
panic(badSizeMismatch)
|
||||
}
|
||||
|
||||
s := &StudentsT{
|
||||
nu: nu,
|
||||
mu: make([]float64, dim),
|
||||
dim: dim,
|
||||
src: src,
|
||||
}
|
||||
if src != nil {
|
||||
s.rnd = rand.New(src)
|
||||
}
|
||||
copy(s.mu, mu)
|
||||
|
||||
ok = s.chol.Factorize(sigma)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
s.sigma = *mat.NewSymDense(dim, nil)
|
||||
s.sigma.CopySym(sigma)
|
||||
s.chol.LTo(&s.lower)
|
||||
s.logSqrtDet = 0.5 * s.chol.LogDet()
|
||||
return s, true
|
||||
}
|
||||
|
||||
// ConditionStudentsT returns the Student's T distribution that is the receiver
|
||||
// conditioned on the input evidence, and the success of the operation.
|
||||
// The returned Student's T has dimension
|
||||
// n - len(observed), where n is the dimension of the original receiver.
|
||||
// The dimension order is preserved during conditioning, so if the value
|
||||
// of dimension 1 is observed, the returned normal represents dimensions {0, 2, ...}
|
||||
// of the original Student's T distribution.
|
||||
//
|
||||
// ok indicates whether there was a failure during the update. If ok is false
|
||||
// the operation failed and dist is not usable.
|
||||
// Mathematically this is impossible, but can occur with finite precision arithmetic.
|
||||
func (s *StudentsT) ConditionStudentsT(observed []int, values []float64, src rand.Source) (dist *StudentsT, ok bool) {
|
||||
if len(observed) == 0 {
|
||||
panic("studentst: no observed value")
|
||||
}
|
||||
if len(observed) != len(values) {
|
||||
panic(badInputLength)
|
||||
}
|
||||
|
||||
for _, v := range observed {
|
||||
if v < 0 || v >= s.dim {
|
||||
panic("studentst: observed value out of bounds")
|
||||
}
|
||||
}
|
||||
|
||||
newNu, newMean, newSigma := studentsTConditional(observed, values, s.nu, s.mu, &s.sigma)
|
||||
if newMean == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return NewStudentsT(newMean, newSigma, newNu, src)
|
||||
|
||||
}
|
||||
|
||||
// studentsTConditional updates a Student's T distribution based on the observed samples
|
||||
// (see documentation for the public function). The Gaussian conditional update
|
||||
// is treated as a special case when nu == math.Inf(1).
|
||||
func studentsTConditional(observed []int, values []float64, nu float64, mu []float64, sigma mat.Symmetric) (newNu float64, newMean []float64, newSigma *mat.SymDense) {
|
||||
dim := len(mu)
|
||||
ob := len(observed)
|
||||
|
||||
unobserved := findUnob(observed, dim)
|
||||
|
||||
unob := len(unobserved)
|
||||
if unob == 0 {
|
||||
panic("stat: all dimensions observed")
|
||||
}
|
||||
|
||||
mu1 := make([]float64, unob)
|
||||
for i, v := range unobserved {
|
||||
mu1[i] = mu[v]
|
||||
}
|
||||
mu2 := make([]float64, ob) // really v - mu2
|
||||
for i, v := range observed {
|
||||
mu2[i] = values[i] - mu[v]
|
||||
}
|
||||
|
||||
var sigma11, sigma22 mat.SymDense
|
||||
sigma11.SubsetSym(sigma, unobserved)
|
||||
sigma22.SubsetSym(sigma, observed)
|
||||
|
||||
sigma21 := mat.NewDense(ob, unob, nil)
|
||||
for i, r := range observed {
|
||||
for j, c := range unobserved {
|
||||
v := sigma.At(r, c)
|
||||
sigma21.Set(i, j, v)
|
||||
}
|
||||
}
|
||||
|
||||
var chol mat.Cholesky
|
||||
ok := chol.Factorize(&sigma22)
|
||||
if !ok {
|
||||
return math.NaN(), nil, nil
|
||||
}
|
||||
|
||||
// Compute mu_1 + sigma_{2,1}ᵀ * sigma_{2,2}^-1 (v - mu_2).
|
||||
v := mat.NewVecDense(ob, mu2)
|
||||
var tmp, tmp2 mat.VecDense
|
||||
err := chol.SolveVecTo(&tmp, v)
|
||||
if err != nil {
|
||||
return math.NaN(), nil, nil
|
||||
}
|
||||
tmp2.MulVec(sigma21.T(), &tmp)
|
||||
|
||||
for i := range mu1 {
|
||||
mu1[i] += tmp2.At(i, 0)
|
||||
}
|
||||
|
||||
// Compute tmp4 = sigma_{2,1}ᵀ * sigma_{2,2}^-1 * sigma_{2,1}.
|
||||
// TODO(btracey): Should this be a method of SymDense?
|
||||
var tmp3, tmp4 mat.Dense
|
||||
err = chol.SolveTo(&tmp3, sigma21)
|
||||
if err != nil {
|
||||
return math.NaN(), nil, nil
|
||||
}
|
||||
tmp4.Mul(sigma21.T(), &tmp3)
|
||||
|
||||
// Compute sigma_{1,1} - tmp4
|
||||
// TODO(btracey): If tmp4 can constructed with a method, then this can be
|
||||
// replaced with SubSym.
|
||||
for i := 0; i < len(unobserved); i++ {
|
||||
for j := i; j < len(unobserved); j++ {
|
||||
v := sigma11.At(i, j)
|
||||
sigma11.SetSym(i, j, v-tmp4.At(i, j))
|
||||
}
|
||||
}
|
||||
|
||||
// The computed variables are accurate for a Normal.
|
||||
if math.IsInf(nu, 1) {
|
||||
return nu, mu1, &sigma11
|
||||
}
|
||||
|
||||
// Compute beta = (v - mu_2)ᵀ * sigma_{2,2}^-1 * (v - mu_2)ᵀ
|
||||
beta := mat.Dot(v, &tmp)
|
||||
|
||||
// Scale the covariance matrix
|
||||
sigma11.ScaleSym((nu+beta)/(nu+float64(ob)), &sigma11)
|
||||
|
||||
return nu + float64(ob), mu1, &sigma11
|
||||
}
|
||||
|
||||
// findUnob returns the unobserved variables (the complementary set to observed).
|
||||
// findUnob panics if any value repeated in observed.
|
||||
func findUnob(observed []int, dim int) (unobserved []int) {
|
||||
var setOb intsets.Sparse
|
||||
for _, v := range observed {
|
||||
setOb.Insert(v)
|
||||
}
|
||||
var setAll intsets.Sparse
|
||||
for i := 0; i < dim; i++ {
|
||||
setAll.Insert(i)
|
||||
}
|
||||
var setUnob intsets.Sparse
|
||||
setUnob.Difference(&setAll, &setOb)
|
||||
unobserved = setUnob.AppendTo(nil)
|
||||
sort.Ints(unobserved)
|
||||
return unobserved
|
||||
}
|
||||
|
||||
// CovarianceMatrix calculates the covariance matrix of the distribution,
|
||||
// storing the result in dst. Upon return, the value at element {i, j} of the
|
||||
// covariance matrix is equal to the covariance of the i^th and j^th variables.
|
||||
//
|
||||
// covariance(i, j) = E[(x_i - E[x_i])(x_j - E[x_j])]
|
||||
//
|
||||
// If the dst matrix is empty it will be resized to the correct dimensions,
|
||||
// otherwise dst must match the dimension of the receiver or CovarianceMatrix
|
||||
// will panic.
|
||||
func (st *StudentsT) CovarianceMatrix(dst *mat.SymDense) {
|
||||
if dst.IsEmpty() {
|
||||
*dst = *(dst.GrowSym(st.dim).(*mat.SymDense))
|
||||
} else if dst.SymmetricDim() != st.dim {
|
||||
panic("studentst: input matrix size mismatch")
|
||||
}
|
||||
dst.CopySym(&st.sigma)
|
||||
dst.ScaleSym(st.nu/(st.nu-2), dst)
|
||||
}
|
||||
|
||||
// Dim returns the dimension of the distribution.
|
||||
func (s *StudentsT) Dim() int {
|
||||
return s.dim
|
||||
}
|
||||
|
||||
// LogProb computes the log of the pdf of the point x.
|
||||
func (s *StudentsT) LogProb(y []float64) float64 {
|
||||
if len(y) != s.dim {
|
||||
panic(badInputLength)
|
||||
}
|
||||
|
||||
nu := s.nu
|
||||
n := float64(s.dim)
|
||||
lg1, _ := math.Lgamma((nu + n) / 2)
|
||||
lg2, _ := math.Lgamma(nu / 2)
|
||||
|
||||
t1 := lg1 - lg2 - n/2*math.Log(nu*math.Pi) - s.logSqrtDet
|
||||
|
||||
mahal := stat.Mahalanobis(mat.NewVecDense(len(y), y), mat.NewVecDense(len(s.mu), s.mu), &s.chol)
|
||||
mahal *= mahal
|
||||
return t1 - ((nu+n)/2)*math.Log(1+mahal/nu)
|
||||
}
|
||||
|
||||
// MarginalStudentsT returns the marginal distribution of the given input variables,
|
||||
// and the success of the operation.
|
||||
// That is, MarginalStudentsT returns
|
||||
//
|
||||
// p(x_i) = \int_{x_o} p(x_i | x_o) p(x_o) dx_o
|
||||
//
|
||||
// where x_i are the dimensions in the input, and x_o are the remaining dimensions.
|
||||
// See https://en.wikipedia.org/wiki/Marginal_distribution for more information.
|
||||
//
|
||||
// The input src is passed to the created StudentsT.
|
||||
//
|
||||
// ok indicates whether there was a failure during the marginalization. If ok is false
|
||||
// the operation failed and dist is not usable.
|
||||
// Mathematically this is impossible, but can occur with finite precision arithmetic.
|
||||
func (s *StudentsT) MarginalStudentsT(vars []int, src rand.Source) (dist *StudentsT, ok bool) {
|
||||
newMean := make([]float64, len(vars))
|
||||
for i, v := range vars {
|
||||
newMean[i] = s.mu[v]
|
||||
}
|
||||
var newSigma mat.SymDense
|
||||
newSigma.SubsetSym(&s.sigma, vars)
|
||||
return NewStudentsT(newMean, &newSigma, s.nu, src)
|
||||
}
|
||||
|
||||
// MarginalStudentsTSingle returns the marginal distribution of the given input variable.
|
||||
// That is, MarginalStudentsTSingle returns
|
||||
//
|
||||
// p(x_i) = \int_{x_o} p(x_i | x_o) p(x_o) dx_o
|
||||
//
|
||||
// where i is the input index, and x_o are the remaining dimensions.
|
||||
// See https://en.wikipedia.org/wiki/Marginal_distribution for more information.
|
||||
//
|
||||
// The input src is passed to the call to NewStudentsT.
|
||||
func (s *StudentsT) MarginalStudentsTSingle(i int, src rand.Source) distuv.StudentsT {
|
||||
return distuv.StudentsT{
|
||||
Mu: s.mu[i],
|
||||
Sigma: math.Sqrt(s.sigma.At(i, i)),
|
||||
Nu: s.nu,
|
||||
Src: src,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(btracey): Implement marginal single. Need to modify univariate StudentsT
|
||||
// to be three-parameter.
|
||||
|
||||
// Mean returns the mean of the probability distribution.
|
||||
//
|
||||
// If dst is not nil, the mean will be stored in-place into dst and returned,
|
||||
// otherwise a new slice will be allocated first. If dst is not nil, it must
|
||||
// have length equal to the dimension of the distribution.
|
||||
func (s *StudentsT) Mean(dst []float64) []float64 {
|
||||
dst = reuseAs(dst, s.dim)
|
||||
copy(dst, s.mu)
|
||||
return dst
|
||||
}
|
||||
|
||||
// Nu returns the degrees of freedom parameter of the distribution.
|
||||
func (s *StudentsT) Nu() float64 {
|
||||
return s.nu
|
||||
}
|
||||
|
||||
// Prob computes the value of the probability density function at x.
|
||||
func (s *StudentsT) Prob(y []float64) float64 {
|
||||
return math.Exp(s.LogProb(y))
|
||||
}
|
||||
|
||||
// Rand generates a random sample according to the distributon.
|
||||
//
|
||||
// If dst is not nil, the sample will be stored in-place into dst and returned,
|
||||
// otherwise a new slice will be allocated first. If dst is not nil, it must
|
||||
// have length equal to the dimension of the distribution.
|
||||
func (s *StudentsT) Rand(dst []float64) []float64 {
|
||||
// If Y is distributed according to N(0,Sigma), and U is chi^2 with
|
||||
// parameter ν, then
|
||||
// X = mu + Y * sqrt(nu / U)
|
||||
// X is distributed according to this distribution.
|
||||
|
||||
// Generate Y.
|
||||
dst = reuseAs(dst, s.dim)
|
||||
if s.rnd == nil {
|
||||
for i := range dst {
|
||||
dst[i] = rand.NormFloat64()
|
||||
}
|
||||
} else {
|
||||
for i := range dst {
|
||||
dst[i] = s.rnd.NormFloat64()
|
||||
}
|
||||
}
|
||||
y := mat.NewVecDense(s.dim, dst)
|
||||
y.MulVec(&s.lower, y)
|
||||
// Compute mu + Y*sqrt(nu/U)
|
||||
u := distuv.ChiSquared{K: s.nu, Src: s.src}.Rand()
|
||||
floats.AddScaledTo(dst, s.mu, math.Sqrt(s.nu/u), dst)
|
||||
return dst
|
||||
}
|
||||
200
vendor/gonum.org/v1/gonum/stat/distmv/uniform.go
generated
vendored
Normal file
200
vendor/gonum.org/v1/gonum/stat/distmv/uniform.go
generated
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
// Copyright ©2015 The Gonum Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package distmv
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"golang.org/x/exp/rand"
|
||||
"gonum.org/v1/gonum/spatial/r1"
|
||||
)
|
||||
|
||||
// Uniform represents a multivariate uniform distribution.
|
||||
type Uniform struct {
|
||||
bounds []r1.Interval
|
||||
dim int
|
||||
rnd *rand.Rand
|
||||
}
|
||||
|
||||
// NewUniform creates a new uniform distribution with the given bounds.
|
||||
func NewUniform(bnds []r1.Interval, src rand.Source) *Uniform {
|
||||
dim := len(bnds)
|
||||
if dim == 0 {
|
||||
panic(badZeroDimension)
|
||||
}
|
||||
for _, b := range bnds {
|
||||
if b.Max < b.Min {
|
||||
panic("uniform: maximum less than minimum")
|
||||
}
|
||||
}
|
||||
u := &Uniform{
|
||||
bounds: make([]r1.Interval, dim),
|
||||
dim: dim,
|
||||
}
|
||||
if src != nil {
|
||||
u.rnd = rand.New(src)
|
||||
}
|
||||
for i, b := range bnds {
|
||||
u.bounds[i].Min = b.Min
|
||||
u.bounds[i].Max = b.Max
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// NewUnitUniform creates a new Uniform distribution over the dim-dimensional
|
||||
// unit hypercube. That is, a uniform distribution where each dimension has
|
||||
// Min = 0 and Max = 1.
|
||||
func NewUnitUniform(dim int, src rand.Source) *Uniform {
|
||||
if dim <= 0 {
|
||||
panic(nonPosDimension)
|
||||
}
|
||||
bounds := make([]r1.Interval, dim)
|
||||
for i := range bounds {
|
||||
bounds[i].Min = 0
|
||||
bounds[i].Max = 1
|
||||
}
|
||||
u := Uniform{
|
||||
bounds: bounds,
|
||||
dim: dim,
|
||||
}
|
||||
if src != nil {
|
||||
u.rnd = rand.New(src)
|
||||
}
|
||||
return &u
|
||||
}
|
||||
|
||||
// Bounds returns the bounds on the variables of the distribution.
|
||||
//
|
||||
// If dst is not nil, the bounds will be stored in-place into dst and returned,
|
||||
// otherwise a new slice will be allocated first. If dst is not nil, it must
|
||||
// have length equal to the dimension of the distribution.
|
||||
func (u *Uniform) Bounds(bounds []r1.Interval) []r1.Interval {
|
||||
if bounds == nil {
|
||||
bounds = make([]r1.Interval, u.Dim())
|
||||
}
|
||||
if len(bounds) != u.Dim() {
|
||||
panic(badInputLength)
|
||||
}
|
||||
copy(bounds, u.bounds)
|
||||
return bounds
|
||||
}
|
||||
|
||||
// CDF returns the value of the multidimensional cumulative distribution
|
||||
// function of the probability distribution at the point x.
|
||||
//
|
||||
// If dst is not nil, the value will be stored in-place into dst and returned,
|
||||
// otherwise a new slice will be allocated first. If dst is not nil, it must
|
||||
// have length equal to the dimension of the distribution. CDF will also panic
|
||||
// if the length of x is not equal to the dimension of the distribution.
|
||||
func (u *Uniform) CDF(dst, x []float64) []float64 {
|
||||
if len(x) != u.dim {
|
||||
panic(badSizeMismatch)
|
||||
}
|
||||
dst = reuseAs(dst, u.dim)
|
||||
|
||||
for i, v := range x {
|
||||
if v < u.bounds[i].Min {
|
||||
dst[i] = 0
|
||||
} else if v > u.bounds[i].Max {
|
||||
dst[i] = 1
|
||||
} else {
|
||||
dst[i] = (v - u.bounds[i].Min) / (u.bounds[i].Max - u.bounds[i].Min)
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Dim returns the dimension of the distribution.
|
||||
func (u *Uniform) Dim() int {
|
||||
return u.dim
|
||||
}
|
||||
|
||||
// Entropy returns the differential entropy of the distribution.
|
||||
func (u *Uniform) Entropy() float64 {
|
||||
// Entropy is log of the volume.
|
||||
var logVol float64
|
||||
for _, b := range u.bounds {
|
||||
logVol += math.Log(b.Max - b.Min)
|
||||
}
|
||||
return logVol
|
||||
}
|
||||
|
||||
// LogProb computes the log of the pdf of the point x.
|
||||
func (u *Uniform) LogProb(x []float64) float64 {
|
||||
dim := u.dim
|
||||
if len(x) != dim {
|
||||
panic(badSizeMismatch)
|
||||
}
|
||||
var logprob float64
|
||||
for i, b := range u.bounds {
|
||||
if x[i] < b.Min || x[i] > b.Max {
|
||||
return math.Inf(-1)
|
||||
}
|
||||
logprob -= math.Log(b.Max - b.Min)
|
||||
}
|
||||
return logprob
|
||||
}
|
||||
|
||||
// Mean returns the mean of the probability distribution.
|
||||
//
|
||||
// If dst is not nil, the mean will be stored in-place into dst and returned,
|
||||
// otherwise a new slice will be allocated first. If dst is not nil, it must
|
||||
// have length equal to the dimension of the distribution.
|
||||
func (u *Uniform) Mean(dst []float64) []float64 {
|
||||
dst = reuseAs(dst, u.dim)
|
||||
for i, b := range u.bounds {
|
||||
dst[i] = (b.Max + b.Min) / 2
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Prob computes the value of the probability density function at x.
|
||||
func (u *Uniform) Prob(x []float64) float64 {
|
||||
return math.Exp(u.LogProb(x))
|
||||
}
|
||||
|
||||
// Rand generates a random sample according to the distributon.
|
||||
//
|
||||
// If dst is not nil, the sample will be stored in-place into dst and returned,
|
||||
// otherwise a new slice will be allocated first. If dst is not nil, it must
|
||||
// have length equal to the dimension of the distribution.
|
||||
func (u *Uniform) Rand(dst []float64) []float64 {
|
||||
dst = reuseAs(dst, u.dim)
|
||||
if u.rnd == nil {
|
||||
for i, b := range u.bounds {
|
||||
dst[i] = rand.Float64()*(b.Max-b.Min) + b.Min
|
||||
}
|
||||
return dst
|
||||
}
|
||||
for i, b := range u.bounds {
|
||||
dst[i] = u.rnd.Float64()*(b.Max-b.Min) + b.Min
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Quantile returns the value of the multi-dimensional inverse cumulative
|
||||
// distribution function at p.
|
||||
//
|
||||
// If dst is not nil, the quantile will be stored in-place into dst and
|
||||
// returned, otherwise a new slice will be allocated first. If dst is not nil,
|
||||
// it must have length equal to the dimension of the distribution. Quantile will
|
||||
// also panic if the length of p is not equal to the dimension of the
|
||||
// distribution.
|
||||
//
|
||||
// All of the values of p must be between 0 and 1, inclusive, or Quantile will
|
||||
// panic.
|
||||
func (u *Uniform) Quantile(dst, p []float64) []float64 {
|
||||
if len(p) != u.dim {
|
||||
panic(badSizeMismatch)
|
||||
}
|
||||
dst = reuseAs(dst, u.dim)
|
||||
for i, v := range p {
|
||||
if v < 0 || v > 1 {
|
||||
panic(badQuantile)
|
||||
}
|
||||
dst[i] = v*(u.bounds[i].Max-u.bounds[i].Min) + u.bounds[i].Min
|
||||
}
|
||||
return dst
|
||||
}
|
||||
Reference in New Issue
Block a user