gonum.org/v1/gonum@v0.14.0/stat/distuv/triangle.go (about) 1 // Copyright ©2017 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 13 // Triangle represents a triangle distribution (https://en.wikipedia.org/wiki/Triangular_distribution). 14 type Triangle struct { 15 a, b, c float64 16 src rand.Source 17 } 18 19 // NewTriangle constructs a new triangle distribution with lower limit a, upper limit b, and mode c. 20 // Constraints are a < b and a ≤ c ≤ b. 21 // This distribution is uncommon in nature, but may be useful for simulation. 22 func NewTriangle(a, b, c float64, src rand.Source) Triangle { 23 checkTriangleParameters(a, b, c) 24 return Triangle{a: a, b: b, c: c, src: src} 25 } 26 27 func checkTriangleParameters(a, b, c float64) { 28 if a >= b { 29 panic("triangle: constraint of a < b violated") 30 } 31 if a > c { 32 panic("triangle: constraint of a <= c violated") 33 } 34 if c > b { 35 panic("triangle: constraint of c <= b violated") 36 } 37 } 38 39 // CDF computes the value of the cumulative density function at x. 40 func (t Triangle) CDF(x float64) float64 { 41 switch { 42 case x <= t.a: 43 return 0 44 case x <= t.c: 45 d := x - t.a 46 return (d * d) / ((t.b - t.a) * (t.c - t.a)) 47 case x < t.b: 48 d := t.b - x 49 return 1 - (d*d)/((t.b-t.a)*(t.b-t.c)) 50 default: 51 return 1 52 } 53 } 54 55 // Entropy returns the entropy of the distribution. 56 func (t Triangle) Entropy() float64 { 57 return 0.5 + math.Log(t.b-t.a) - math.Ln2 58 } 59 60 // ExKurtosis returns the excess kurtosis of the distribution. 61 func (Triangle) ExKurtosis() float64 { 62 return -3.0 / 5.0 63 } 64 65 // Fit is not appropriate for Triangle, because the distribution is generally used when there is little data. 66 67 // LogProb computes the natural logarithm of the value of the probability density function at x. 68 func (t Triangle) LogProb(x float64) float64 { 69 return math.Log(t.Prob(x)) 70 } 71 72 // Mean returns the mean of the probability distribution. 73 func (t Triangle) Mean() float64 { 74 return (t.a + t.b + t.c) / 3 75 } 76 77 // Median returns the median of the probability distribution. 78 func (t Triangle) Median() float64 { 79 if t.c >= (t.a+t.b)/2 { 80 return t.a + math.Sqrt((t.b-t.a)*(t.c-t.a)/2) 81 } 82 return t.b - math.Sqrt((t.b-t.a)*(t.b-t.c)/2) 83 } 84 85 // Mode returns the mode of the probability distribution. 86 func (t Triangle) Mode() float64 { 87 return t.c 88 } 89 90 // NumParameters returns the number of parameters in the distribution. 91 func (Triangle) NumParameters() int { 92 return 3 93 } 94 95 // Prob computes the value of the probability density function at x. 96 func (t Triangle) Prob(x float64) float64 { 97 switch { 98 case x < t.a: 99 return 0 100 case x < t.c: 101 return 2 * (x - t.a) / ((t.b - t.a) * (t.c - t.a)) 102 case x == t.c: 103 return 2 / (t.b - t.a) 104 case x <= t.b: 105 return 2 * (t.b - x) / ((t.b - t.a) * (t.b - t.c)) 106 default: 107 return 0 108 } 109 } 110 111 // Quantile returns the inverse of the cumulative probability distribution. 112 func (t Triangle) Quantile(p float64) float64 { 113 if p < 0 || p > 1 { 114 panic(badPercentile) 115 } 116 117 f := (t.c - t.a) / (t.b - t.a) 118 119 if p < f { 120 return t.a + math.Sqrt(p*(t.b-t.a)*(t.c-t.a)) 121 } 122 return t.b - math.Sqrt((1-p)*(t.b-t.a)*(t.b-t.c)) 123 } 124 125 // Rand returns a random sample drawn from the distribution. 126 func (t Triangle) Rand() float64 { 127 var rnd float64 128 if t.src == nil { 129 rnd = rand.Float64() 130 } else { 131 rnd = rand.New(t.src).Float64() 132 } 133 134 return t.Quantile(rnd) 135 } 136 137 // Score returns the score function with respect to the parameters of the 138 // distribution at the input location x. The score function is the derivative 139 // of the log-likelihood at x with respect to the parameters 140 // 141 // (∂/∂θ) log(p(x;θ)) 142 // 143 // If deriv is non-nil, len(deriv) must equal the number of parameters otherwise 144 // Score will panic, and the derivative is stored in-place into deriv. If deriv 145 // is nil a new slice will be allocated and returned. 146 // 147 // The order is [∂LogProb / ∂Mu, ∂LogProb / ∂Sigma]. 148 // 149 // For more information, see https://en.wikipedia.org/wiki/Score_%28statistics%29. 150 func (t Triangle) Score(deriv []float64, x float64) []float64 { 151 if deriv == nil { 152 deriv = make([]float64, t.NumParameters()) 153 } 154 if len(deriv) != t.NumParameters() { 155 panic(badLength) 156 } 157 if (x < t.a) || (x > t.b) { 158 deriv[0] = math.NaN() 159 deriv[1] = math.NaN() 160 deriv[2] = math.NaN() 161 } else { 162 invBA := 1 / (t.b - t.a) 163 invCA := 1 / (t.c - t.a) 164 invBC := 1 / (t.b - t.c) 165 switch { 166 case x < t.c: 167 deriv[0] = -1/(x-t.a) + invBA + invCA 168 deriv[1] = -invBA 169 deriv[2] = -invCA 170 case x > t.c: 171 deriv[0] = invBA 172 deriv[1] = 1/(t.b-x) - invBA - invBC 173 deriv[2] = invBC 174 default: 175 deriv[0] = invBA 176 deriv[1] = -invBA 177 deriv[2] = 0 178 } 179 switch { 180 case x == t.a: 181 deriv[0] = math.NaN() 182 case x == t.b: 183 deriv[1] = math.NaN() 184 case x == t.c: 185 deriv[2] = math.NaN() 186 } 187 switch { 188 case t.a == t.c: 189 deriv[0] = math.NaN() 190 deriv[2] = math.NaN() 191 case t.b == t.c: 192 deriv[1] = math.NaN() 193 deriv[2] = math.NaN() 194 } 195 } 196 return deriv 197 } 198 199 // ScoreInput returns the score function with respect to the input of the 200 // distribution at the input location specified by x. The score function is the 201 // derivative of the log-likelihood 202 // 203 // (d/dx) log(p(x)) . 204 // 205 // Special cases (c is the mode of the distribution): 206 // 207 // ScoreInput(c) = NaN 208 // ScoreInput(x) = NaN for x not in (a, b) 209 func (t Triangle) ScoreInput(x float64) float64 { 210 if (x <= t.a) || (x >= t.b) || (x == t.c) { 211 return math.NaN() 212 } 213 if x < t.c { 214 return 1 / (x - t.a) 215 } 216 return 1 / (x - t.b) 217 } 218 219 // Skewness returns the skewness of the distribution. 220 func (t Triangle) Skewness() float64 { 221 n := math.Sqrt2 * (t.a + t.b - 2*t.c) * (2*t.a - t.b - t.c) * (t.a - 2*t.b + t.c) 222 d := 5 * math.Pow(t.a*t.a+t.b*t.b+t.c*t.c-t.a*t.b-t.a*t.c-t.b*t.c, 3.0/2.0) 223 224 return n / d 225 } 226 227 // StdDev returns the standard deviation of the probability distribution. 228 func (t Triangle) StdDev() float64 { 229 return math.Sqrt(t.Variance()) 230 } 231 232 // Survival returns the survival function (complementary CDF) at x. 233 func (t Triangle) Survival(x float64) float64 { 234 return 1 - t.CDF(x) 235 } 236 237 // parameters returns the parameters of the distribution. 238 func (t Triangle) parameters(p []Parameter) []Parameter { 239 nParam := t.NumParameters() 240 if p == nil { 241 p = make([]Parameter, nParam) 242 } else if len(p) != nParam { 243 panic("triangle: improper parameter length") 244 } 245 p[0].Name = "A" 246 p[0].Value = t.a 247 p[1].Name = "B" 248 p[1].Value = t.b 249 p[2].Name = "C" 250 p[2].Value = t.c 251 return p 252 } 253 254 // setParameters modifies the parameters of the distribution. 255 func (t *Triangle) setParameters(p []Parameter) { 256 if len(p) != t.NumParameters() { 257 panic("triangle: incorrect number of parameters to set") 258 } 259 if p[0].Name != "A" { 260 panic("triangle: " + panicNameMismatch) 261 } 262 if p[1].Name != "B" { 263 panic("triangle: " + panicNameMismatch) 264 } 265 if p[2].Name != "C" { 266 panic("triangle: " + panicNameMismatch) 267 } 268 269 checkTriangleParameters(p[0].Value, p[1].Value, p[2].Value) 270 271 t.a = p[0].Value 272 t.b = p[1].Value 273 t.c = p[2].Value 274 } 275 276 // Variance returns the variance of the probability distribution. 277 func (t Triangle) Variance() float64 { 278 return (t.a*t.a + t.b*t.b + t.c*t.c - t.a*t.b - t.a*t.c - t.b*t.c) / 18 279 }