github.com/gopherd/gonum@v0.0.4/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 "math/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 // (∂/∂θ) log(p(x;θ)) 141 // If deriv is non-nil, len(deriv) must equal the number of parameters otherwise 142 // Score will panic, and the derivative is stored in-place into deriv. If deriv 143 // is nil a new slice will be allocated and returned. 144 // 145 // The order is [∂LogProb / ∂Mu, ∂LogProb / ∂Sigma]. 146 // 147 // For more information, see https://en.wikipedia.org/wiki/Score_%28statistics%29. 148 func (t Triangle) Score(deriv []float64, x float64) []float64 { 149 if deriv == nil { 150 deriv = make([]float64, t.NumParameters()) 151 } 152 if len(deriv) != t.NumParameters() { 153 panic(badLength) 154 } 155 if (x < t.a) || (x > t.b) { 156 deriv[0] = math.NaN() 157 deriv[1] = math.NaN() 158 deriv[2] = math.NaN() 159 } else { 160 invBA := 1 / (t.b - t.a) 161 invCA := 1 / (t.c - t.a) 162 invBC := 1 / (t.b - t.c) 163 switch { 164 case x < t.c: 165 deriv[0] = -1/(x-t.a) + invBA + invCA 166 deriv[1] = -invBA 167 deriv[2] = -invCA 168 case x > t.c: 169 deriv[0] = invBA 170 deriv[1] = 1/(t.b-x) - invBA - invBC 171 deriv[2] = invBC 172 default: 173 deriv[0] = invBA 174 deriv[1] = -invBA 175 deriv[2] = 0 176 } 177 switch { 178 case x == t.a: 179 deriv[0] = math.NaN() 180 case x == t.b: 181 deriv[1] = math.NaN() 182 case x == t.c: 183 deriv[2] = math.NaN() 184 } 185 switch { 186 case t.a == t.c: 187 deriv[0] = math.NaN() 188 deriv[2] = math.NaN() 189 case t.b == t.c: 190 deriv[1] = math.NaN() 191 deriv[2] = math.NaN() 192 } 193 } 194 return deriv 195 } 196 197 // ScoreInput returns the score function with respect to the input of the 198 // distribution at the input location specified by x. The score function is the 199 // derivative of the log-likelihood 200 // (d/dx) log(p(x)) . 201 // Special cases (c is the mode of the distribution): 202 // ScoreInput(c) = NaN 203 // ScoreInput(x) = NaN for x not in (a, b) 204 func (t Triangle) ScoreInput(x float64) float64 { 205 if (x <= t.a) || (x >= t.b) || (x == t.c) { 206 return math.NaN() 207 } 208 if x < t.c { 209 return 1 / (x - t.a) 210 } 211 return 1 / (x - t.b) 212 } 213 214 // Skewness returns the skewness of the distribution. 215 func (t Triangle) Skewness() float64 { 216 n := math.Sqrt2 * (t.a + t.b - 2*t.c) * (2*t.a - t.b - t.c) * (t.a - 2*t.b + t.c) 217 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) 218 219 return n / d 220 } 221 222 // StdDev returns the standard deviation of the probability distribution. 223 func (t Triangle) StdDev() float64 { 224 return math.Sqrt(t.Variance()) 225 } 226 227 // Survival returns the survival function (complementary CDF) at x. 228 func (t Triangle) Survival(x float64) float64 { 229 return 1 - t.CDF(x) 230 } 231 232 // parameters returns the parameters of the distribution. 233 func (t Triangle) parameters(p []Parameter) []Parameter { 234 nParam := t.NumParameters() 235 if p == nil { 236 p = make([]Parameter, nParam) 237 } else if len(p) != nParam { 238 panic("triangle: improper parameter length") 239 } 240 p[0].Name = "A" 241 p[0].Value = t.a 242 p[1].Name = "B" 243 p[1].Value = t.b 244 p[2].Name = "C" 245 p[2].Value = t.c 246 return p 247 } 248 249 // setParameters modifies the parameters of the distribution. 250 func (t *Triangle) setParameters(p []Parameter) { 251 if len(p) != t.NumParameters() { 252 panic("triangle: incorrect number of parameters to set") 253 } 254 if p[0].Name != "A" { 255 panic("triangle: " + panicNameMismatch) 256 } 257 if p[1].Name != "B" { 258 panic("triangle: " + panicNameMismatch) 259 } 260 if p[2].Name != "C" { 261 panic("triangle: " + panicNameMismatch) 262 } 263 264 checkTriangleParameters(p[0].Value, p[1].Value, p[2].Value) 265 266 t.a = p[0].Value 267 t.b = p[1].Value 268 t.c = p[2].Value 269 } 270 271 // Variance returns the variance of the probability distribution. 272 func (t Triangle) Variance() float64 { 273 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 274 }