github.com/jingcheng-WU/gonum@v0.9.1-0.20210323123734-f1a2a11a8f7b/stat/distuv/gamma.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 distuv
     6  
     7  import (
     8  	"math"
     9  
    10  	"golang.org/x/exp/rand"
    11  
    12  	"github.com/jingcheng-WU/gonum/mathext"
    13  )
    14  
    15  // Gamma implements the Gamma distribution, a two-parameter continuous distribution
    16  // with support over the positive real numbers.
    17  //
    18  // The gamma distribution has density function
    19  //  β^α / Γ(α) x^(α-1)e^(-βx)
    20  //
    21  // For more information, see https://en.wikipedia.org/wiki/Gamma_distribution
    22  type Gamma struct {
    23  	// Alpha is the shape parameter of the distribution. Alpha must be greater
    24  	// than 0. If Alpha == 1, this is equivalent to an exponential distribution.
    25  	Alpha float64
    26  	// Beta is the rate parameter of the distribution. Beta must be greater than 0.
    27  	// If Beta == 2, this is equivalent to a Chi-Squared distribution.
    28  	Beta float64
    29  
    30  	Src rand.Source
    31  }
    32  
    33  // CDF computes the value of the cumulative distribution function at x.
    34  func (g Gamma) CDF(x float64) float64 {
    35  	if x < 0 {
    36  		return 0
    37  	}
    38  	return mathext.GammaIncReg(g.Alpha, g.Beta*x)
    39  }
    40  
    41  // ExKurtosis returns the excess kurtosis of the distribution.
    42  func (g Gamma) ExKurtosis() float64 {
    43  	return 6 / g.Alpha
    44  }
    45  
    46  // LogProb computes the natural logarithm of the value of the probability
    47  // density function at x.
    48  func (g Gamma) LogProb(x float64) float64 {
    49  	if x <= 0 {
    50  		return math.Inf(-1)
    51  	}
    52  	a := g.Alpha
    53  	b := g.Beta
    54  	lg, _ := math.Lgamma(a)
    55  	return a*math.Log(b) - lg + (a-1)*math.Log(x) - b*x
    56  }
    57  
    58  // Mean returns the mean of the probability distribution.
    59  func (g Gamma) Mean() float64 {
    60  	return g.Alpha / g.Beta
    61  }
    62  
    63  // Mode returns the mode of the normal distribution.
    64  //
    65  // The mode is NaN in the special case where the Alpha (shape) parameter
    66  // is less than 1.
    67  func (g Gamma) Mode() float64 {
    68  	if g.Alpha < 1 {
    69  		return math.NaN()
    70  	}
    71  	return (g.Alpha - 1) / g.Beta
    72  }
    73  
    74  // NumParameters returns the number of parameters in the distribution.
    75  func (Gamma) NumParameters() int {
    76  	return 2
    77  }
    78  
    79  // Prob computes the value of the probability density function at x.
    80  func (g Gamma) Prob(x float64) float64 {
    81  	return math.Exp(g.LogProb(x))
    82  }
    83  
    84  // Quantile returns the inverse of the cumulative distribution function.
    85  func (g Gamma) Quantile(p float64) float64 {
    86  	if p < 0 || p > 1 {
    87  		panic(badPercentile)
    88  	}
    89  	return mathext.GammaIncRegInv(g.Alpha, p) / g.Beta
    90  }
    91  
    92  // Rand returns a random sample drawn from the distribution.
    93  //
    94  // Rand panics if either alpha or beta is <= 0.
    95  func (g Gamma) Rand() float64 {
    96  	const (
    97  		// The 0.2 threshold is from https://www4.stat.ncsu.edu/~rmartin/Codes/rgamss.R
    98  		// described in detail in https://arxiv.org/abs/1302.1884.
    99  		smallAlphaThresh = 0.2
   100  	)
   101  	if g.Beta <= 0 {
   102  		panic("gamma: beta <= 0")
   103  	}
   104  
   105  	unifrnd := rand.Float64
   106  	exprnd := rand.ExpFloat64
   107  	normrnd := rand.NormFloat64
   108  	if g.Src != nil {
   109  		rnd := rand.New(g.Src)
   110  		unifrnd = rnd.Float64
   111  		exprnd = rnd.ExpFloat64
   112  		normrnd = rnd.NormFloat64
   113  	}
   114  
   115  	a := g.Alpha
   116  	b := g.Beta
   117  	switch {
   118  	case a <= 0:
   119  		panic("gamma: alpha <= 0")
   120  	case a == 1:
   121  		// Generate from exponential
   122  		return exprnd() / b
   123  	case a < smallAlphaThresh:
   124  		// Generate using
   125  		//  Liu, Chuanhai, Martin, Ryan and Syring, Nick. "Simulating from a
   126  		//  gamma distribution with small shape parameter"
   127  		//  https://arxiv.org/abs/1302.1884
   128  		//   use this reference: http://link.springer.com/article/10.1007/s00180-016-0692-0
   129  
   130  		// Algorithm adjusted to work in log space as much as possible.
   131  		lambda := 1/a - 1
   132  		lr := -math.Log1p(1 / lambda / math.E)
   133  		for {
   134  			e := exprnd()
   135  			var z float64
   136  			if e >= -lr {
   137  				z = e + lr
   138  			} else {
   139  				z = -exprnd() / lambda
   140  			}
   141  			eza := math.Exp(-z / a)
   142  			lh := -z - eza
   143  			var lEta float64
   144  			if z >= 0 {
   145  				lEta = -z
   146  			} else {
   147  				lEta = -1 + lambda*z
   148  			}
   149  			if lh-lEta > -exprnd() {
   150  				return eza / b
   151  			}
   152  		}
   153  	case a >= smallAlphaThresh:
   154  		// Generate using:
   155  		//  Marsaglia, George, and Wai Wan Tsang. "A simple method for generating
   156  		//  gamma variables." ACM Transactions on Mathematical Software (TOMS)
   157  		//  26.3 (2000): 363-372.
   158  		d := a - 1.0/3
   159  		m := 1.0
   160  		if a < 1 {
   161  			d += 1.0
   162  			m = math.Pow(unifrnd(), 1/a)
   163  		}
   164  		c := 1 / (3 * math.Sqrt(d))
   165  		for {
   166  			x := normrnd()
   167  			v := 1 + x*c
   168  			if v <= 0.0 {
   169  				continue
   170  			}
   171  			v = v * v * v
   172  			u := unifrnd()
   173  			if u < 1.0-0.0331*(x*x)*(x*x) {
   174  				return m * d * v / b
   175  			}
   176  			if math.Log(u) < 0.5*x*x+d*(1-v+math.Log(v)) {
   177  				return m * d * v / b
   178  			}
   179  		}
   180  	}
   181  	panic("unreachable")
   182  }
   183  
   184  // Survival returns the survival function (complementary CDF) at x.
   185  func (g Gamma) Survival(x float64) float64 {
   186  	if x < 0 {
   187  		return 1
   188  	}
   189  	return mathext.GammaIncRegComp(g.Alpha, g.Beta*x)
   190  }
   191  
   192  // StdDev returns the standard deviation of the probability distribution.
   193  func (g Gamma) StdDev() float64 {
   194  	return math.Sqrt(g.Alpha) / g.Beta
   195  }
   196  
   197  // Variance returns the variance of the probability distribution.
   198  func (g Gamma) Variance() float64 {
   199  	return g.Alpha / g.Beta / g.Beta
   200  }