mirror of
https://github.com/gonum/gonum.git
synced 2025-10-08 00:20:11 +08:00
244 lines
6.9 KiB
Go
244 lines
6.9 KiB
Go
// Copyright ©2014 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 distuv
|
|
|
|
import (
|
|
"math"
|
|
"math/cmplx"
|
|
|
|
"golang.org/x/exp/rand"
|
|
)
|
|
|
|
// Weibull distribution. Valid range for x is [0,+∞).
|
|
type Weibull struct {
|
|
// Shape parameter of the distribution. A value of 1 represents
|
|
// the exponential distribution. A value of 2 represents the
|
|
// Rayleigh distribution. Valid range is (0,+∞).
|
|
K float64
|
|
// Scale parameter of the distribution. Valid range is (0,+∞).
|
|
Lambda float64
|
|
// Source of random numbers
|
|
Src rand.Source
|
|
}
|
|
|
|
// CDF computes the value of the cumulative density function at x.
|
|
func (w Weibull) CDF(x float64) float64 {
|
|
if x < 0 {
|
|
return 0
|
|
}
|
|
return 1 - cmplx.Abs(cmplx.Exp(w.LogCDF(x)))
|
|
}
|
|
|
|
// Entropy returns the entropy of the distribution.
|
|
func (w Weibull) Entropy() float64 {
|
|
return eulerGamma*(1-1/w.K) + math.Log(w.Lambda/w.K) + 1
|
|
}
|
|
|
|
// ExKurtosis returns the excess kurtosis of the distribution.
|
|
func (w Weibull) ExKurtosis() float64 {
|
|
return (-6*w.gammaIPow(1, 4) + 12*w.gammaIPow(1, 2)*math.Gamma(1+2/w.K) - 3*w.gammaIPow(2, 2) - 4*math.Gamma(1+1/w.K)*math.Gamma(1+3/w.K) + math.Gamma(1+4/w.K)) / math.Pow(math.Gamma(1+2/w.K)-w.gammaIPow(1, 2), 2)
|
|
}
|
|
|
|
// gammIPow is a shortcut for computing the gamma function to a power.
|
|
func (w Weibull) gammaIPow(i, pow float64) float64 {
|
|
return math.Pow(math.Gamma(1+i/w.K), pow)
|
|
}
|
|
|
|
// LogCDF computes the value of the log of the cumulative density function at x.
|
|
func (w Weibull) LogCDF(x float64) complex128 {
|
|
if x < 0 {
|
|
return 0
|
|
}
|
|
return cmplx.Log(-1) + complex(-math.Pow(x/w.Lambda, w.K), 0)
|
|
}
|
|
|
|
// LogProb computes the natural logarithm of the value of the probability
|
|
// density function at x. Zero is returned if x is less than zero.
|
|
//
|
|
// Special cases occur when x == 0, and the result depends on the shape
|
|
// parameter as follows:
|
|
// If 0 < K < 1, LogProb returns +Inf.
|
|
// If K == 1, LogProb returns 0.
|
|
// If K > 1, LogProb returns -Inf.
|
|
func (w Weibull) LogProb(x float64) float64 {
|
|
if x < 0 {
|
|
return 0
|
|
}
|
|
return math.Log(w.K) - math.Log(w.Lambda) + (w.K-1)*(math.Log(x)-math.Log(w.Lambda)) - math.Pow(x/w.Lambda, w.K)
|
|
}
|
|
|
|
// LogSurvival returns the log of the survival function (complementary CDF) at x.
|
|
func (w Weibull) LogSurvival(x float64) float64 {
|
|
if x < 0 {
|
|
return 0
|
|
}
|
|
return -math.Pow(x/w.Lambda, w.K)
|
|
}
|
|
|
|
// Mean returns the mean of the probability distribution.
|
|
func (w Weibull) Mean() float64 {
|
|
return w.Lambda * math.Gamma(1+1/w.K)
|
|
}
|
|
|
|
// Median returns the median of the normal distribution.
|
|
func (w Weibull) Median() float64 {
|
|
return w.Lambda * math.Pow(ln2, 1/w.K)
|
|
}
|
|
|
|
// Mode returns the mode of the normal distribution.
|
|
//
|
|
// The mode is NaN in the special case where the K (shape) parameter
|
|
// is less than 1.
|
|
func (w Weibull) Mode() float64 {
|
|
if w.K > 1 {
|
|
return w.Lambda * math.Pow((w.K-1)/w.K, 1/w.K)
|
|
} else if w.K == 1 {
|
|
return 0
|
|
} else {
|
|
return math.NaN()
|
|
}
|
|
}
|
|
|
|
// NumParameters returns the number of parameters in the distribution.
|
|
func (Weibull) NumParameters() int {
|
|
return 2
|
|
}
|
|
|
|
// Prob computes the value of the probability density function at x.
|
|
func (w Weibull) Prob(x float64) float64 {
|
|
if x < 0 {
|
|
return 0
|
|
}
|
|
return math.Exp(w.LogProb(x))
|
|
}
|
|
|
|
// Quantile returns the inverse of the cumulative probability distribution.
|
|
func (w Weibull) Quantile(p float64) float64 {
|
|
if p < 0 || p > 1 {
|
|
panic(badPercentile)
|
|
}
|
|
return w.Lambda * math.Pow(-math.Log(1-p), 1/w.K)
|
|
}
|
|
|
|
// Rand returns a random sample drawn from the distribution.
|
|
func (w Weibull) Rand() float64 {
|
|
var rnd float64
|
|
if w.Src == nil {
|
|
rnd = rand.Float64()
|
|
} else {
|
|
rnd = rand.New(w.Src).Float64()
|
|
}
|
|
return w.Quantile(rnd)
|
|
}
|
|
|
|
// Score returns the score function with respect to the parameters of the
|
|
// distribution at the input location x. The score function is the derivative
|
|
// of the log-likelihood at x with respect to the parameters
|
|
// (∂/∂θ) log(p(x;θ))
|
|
// If deriv is non-nil, len(deriv) must equal the number of parameters otherwise
|
|
// Score will panic, and the derivative is stored in-place into deriv. If deriv
|
|
// is nil a new slice will be allocated and returned.
|
|
//
|
|
// The order is [∂LogProb / ∂K, ∂LogProb / ∂λ].
|
|
//
|
|
// For more information, see https://en.wikipedia.org/wiki/Score_%28statistics%29.
|
|
//
|
|
// Special cases:
|
|
// Score(0) = [NaN, NaN]
|
|
func (w Weibull) Score(deriv []float64, x float64) []float64 {
|
|
if deriv == nil {
|
|
deriv = make([]float64, w.NumParameters())
|
|
}
|
|
if len(deriv) != w.NumParameters() {
|
|
panic(badLength)
|
|
}
|
|
if x > 0 {
|
|
deriv[0] = 1/w.K + math.Log(x) - math.Log(w.Lambda) - (math.Log(x)-math.Log(w.Lambda))*math.Pow(x/w.Lambda, w.K)
|
|
deriv[1] = (w.K * (math.Pow(x/w.Lambda, w.K) - 1)) / w.Lambda
|
|
return deriv
|
|
}
|
|
if x < 0 {
|
|
deriv[0] = 0
|
|
deriv[1] = 0
|
|
return deriv
|
|
}
|
|
deriv[0] = math.NaN()
|
|
deriv[0] = math.NaN()
|
|
return deriv
|
|
}
|
|
|
|
// ScoreInput returns the score function with respect to the input of the
|
|
// distribution at the input location specified by x. The score function is the
|
|
// derivative of the log-likelihood
|
|
// (d/dx) log(p(x)) .
|
|
//
|
|
// Special cases:
|
|
// ScoreInput(0) = NaN
|
|
func (w Weibull) ScoreInput(x float64) float64 {
|
|
if x > 0 {
|
|
return (-w.K*math.Pow(x/w.Lambda, w.K) + w.K - 1) / x
|
|
}
|
|
if x < 0 {
|
|
return 0
|
|
}
|
|
return math.NaN()
|
|
}
|
|
|
|
// Skewness returns the skewness of the distribution.
|
|
func (w Weibull) Skewness() float64 {
|
|
stdDev := w.StdDev()
|
|
firstGamma, firstGammaSign := math.Lgamma(1 + 3/w.K)
|
|
logFirst := firstGamma + 3*(math.Log(w.Lambda)-math.Log(stdDev))
|
|
logSecond := math.Log(3) + math.Log(w.Mean()) + 2*math.Log(stdDev) - 3*math.Log(stdDev)
|
|
logThird := 3 * (math.Log(w.Mean()) - math.Log(stdDev))
|
|
return float64(firstGammaSign)*math.Exp(logFirst) - math.Exp(logSecond) - math.Exp(logThird)
|
|
}
|
|
|
|
// StdDev returns the standard deviation of the probability distribution.
|
|
func (w Weibull) StdDev() float64 {
|
|
return math.Sqrt(w.Variance())
|
|
}
|
|
|
|
// Survival returns the survival function (complementary CDF) at x.
|
|
func (w Weibull) Survival(x float64) float64 {
|
|
return math.Exp(w.LogSurvival(x))
|
|
}
|
|
|
|
// setParameters modifies the parameters of the distribution.
|
|
func (w *Weibull) setParameters(p []Parameter) {
|
|
if len(p) != w.NumParameters() {
|
|
panic("weibull: incorrect number of parameters to set")
|
|
}
|
|
if p[0].Name != "K" {
|
|
panic("weibull: " + panicNameMismatch)
|
|
}
|
|
if p[1].Name != "λ" {
|
|
panic("weibull: " + panicNameMismatch)
|
|
}
|
|
w.K = p[0].Value
|
|
w.Lambda = p[1].Value
|
|
}
|
|
|
|
// Variance returns the variance of the probability distribution.
|
|
func (w Weibull) Variance() float64 {
|
|
return math.Pow(w.Lambda, 2) * (math.Gamma(1+2/w.K) - w.gammaIPow(1, 2))
|
|
}
|
|
|
|
// parameters returns the parameters of the distribution.
|
|
func (w Weibull) parameters(p []Parameter) []Parameter {
|
|
nParam := w.NumParameters()
|
|
if p == nil {
|
|
p = make([]Parameter, nParam)
|
|
} else if len(p) != nParam {
|
|
panic("weibull: improper parameter length")
|
|
}
|
|
p[0].Name = "K"
|
|
p[0].Value = w.K
|
|
p[1].Name = "λ"
|
|
p[1].Value = w.Lambda
|
|
return p
|
|
|
|
}
|