gonum.org/v1/gonum@v0.14.0/stat/distuv/triangle_test.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 "sort" 10 "testing" 11 12 "golang.org/x/exp/rand" 13 ) 14 15 func TestTriangleConstraint(t *testing.T) { 16 t.Parallel() 17 for _, test := range []struct{ a, b, c float64 }{ 18 {a: 1, b: 1, c: 1}, 19 {a: 1, b: 1, c: 0}, 20 {a: 1, b: 2, c: 3}, 21 {a: 1, b: 2, c: 0}, 22 } { 23 if !panics(func() { NewTriangle(test.a, test.b, test.c, nil) }) { 24 t.Errorf("expected panic for NewTriangle(%f, %f, %f, nil)", test.a, test.b, test.c) 25 } 26 } 27 } 28 29 func TestTriangle(t *testing.T) { 30 t.Parallel() 31 src := rand.New(rand.NewSource(1)) 32 for i, test := range []struct { 33 a, b, c float64 34 }{ 35 { 36 a: 0.0, 37 b: 1.0, 38 c: 0.5, 39 }, 40 { 41 a: 0.1, 42 b: 0.3, 43 c: 0.2, 44 }, 45 { 46 a: 1.0, 47 b: 2.0, 48 c: 1.5, 49 }, 50 { 51 a: 0.0, 52 b: 1.0, 53 c: 0.0, 54 }, 55 { 56 a: 0.0, 57 b: 1.2, 58 c: 1.2, 59 }, 60 } { 61 f := NewTriangle(test.a, test.b, test.c, src) 62 const ( 63 tol = 1e-2 64 n = 1e6 65 ) 66 x := make([]float64, n) 67 generateSamples(x, f) 68 sort.Float64s(x) 69 70 checkMean(t, i, x, f, tol) 71 checkVarAndStd(t, i, x, f, tol) 72 checkEntropy(t, i, x, f, tol) 73 checkExKurtosis(t, i, x, f, tol) 74 checkSkewness(t, i, x, f, 5e-2) 75 checkMedian(t, i, x, f, tol) 76 checkQuantileCDFSurvival(t, i, x, f, tol) 77 checkProbContinuous(t, i, x, f.a, f.b, f, 1e-10) 78 checkProbQuantContinuous(t, i, x, f, tol) 79 80 if f.c != f.Mode() { 81 t.Errorf("Mismatch in mode value: got %v, want %g", f.Mode(), f.c) 82 } 83 } 84 } 85 86 func TestTriangleProb(t *testing.T) { 87 t.Parallel() 88 pts := []univariateProbPoint{ 89 { 90 loc: 0.5, 91 prob: 0, 92 cumProb: 0, 93 logProb: math.Inf(-1), 94 }, 95 { 96 loc: 1, 97 prob: 0, 98 cumProb: 0, 99 logProb: math.Inf(-1), 100 }, 101 { 102 loc: 2, 103 prob: 1.0, 104 cumProb: 0.5, 105 logProb: 0, 106 }, 107 { 108 loc: 3, 109 prob: 0, 110 cumProb: 1, 111 logProb: math.Inf(-1), 112 }, 113 { 114 loc: 3.5, 115 prob: 0, 116 cumProb: 1, 117 logProb: math.Inf(-1), 118 }, 119 } 120 testDistributionProbs(t, NewTriangle(1, 3, 2, nil), "Standard 1,2,3 Triangle", pts) 121 } 122 123 func TestTriangleScore(t *testing.T) { 124 const ( 125 h = 1e-6 126 tol = 1e-6 127 ) 128 t.Parallel() 129 130 f := Triangle{a: -0.5, b: 0.7, c: 0.1} 131 testDerivParam(t, &f) 132 133 f = Triangle{a: 0, b: 1, c: 0} 134 x := 0.5 135 score := f.Score(nil, x) 136 if !math.IsNaN(score[0]) { 137 t.Errorf("Expected score over A to be NaN for A == C, got %v", score[0]) 138 } 139 if !math.IsNaN(score[2]) { 140 t.Errorf("Expected score over C to be NaN for A == C, got %v", score[2]) 141 } 142 expectedScore := logProbDerivative(f, x, 1, h) 143 if math.Abs(expectedScore-score[1]) > tol { 144 t.Errorf("Mismatch in score over B for A == C: want %g, got %v", expectedScore, score[1]) 145 } 146 147 f = Triangle{a: 0, b: 1, c: 1} 148 score = f.Score(nil, x) 149 if !math.IsNaN(score[1]) { 150 t.Errorf("Expected score over B to be NaN for B == C, got %v", score[1]) 151 } 152 if !math.IsNaN(score[2]) { 153 t.Errorf("Expected score over C to be NaN for B == C, got %v", score[2]) 154 } 155 expectedScore = logProbDerivative(f, x, 0, h) 156 if math.Abs(expectedScore-score[0]) > tol { 157 t.Errorf("Mismatch in score over A for B == C: want %g, got %v", expectedScore, score[0]) 158 } 159 160 f = Triangle{a: 0, b: 1, c: 0.5} 161 score = f.Score(nil, f.a-0.01) 162 if !math.IsNaN(score[0]) { 163 t.Errorf("Expected score over B to be NaN for x < A, got %v", score[0]) 164 } 165 if !math.IsNaN(score[1]) { 166 t.Errorf("Expected score over B to be NaN for x < A, got %v", score[1]) 167 } 168 if !math.IsNaN(score[2]) { 169 t.Errorf("Expected score over C to be NaN for x < A, got %v", score[2]) 170 } 171 172 score = f.Score(nil, f.b+0.01) 173 if !math.IsNaN(score[0]) { 174 t.Errorf("Expected score over B to be NaN for x > B, got %v", score[0]) 175 } 176 if !math.IsNaN(score[1]) { 177 t.Errorf("Expected score over B to be NaN for x > B, got %v", score[1]) 178 } 179 if !math.IsNaN(score[2]) { 180 t.Errorf("Expected score over C to be NaN for x > B, got %v", score[2]) 181 } 182 183 score = f.Score(nil, f.a) 184 if !math.IsNaN(score[0]) { 185 t.Errorf("Expected score over C to be NaN for x == A, got %v", score[0]) 186 } 187 score = f.Score(nil, f.b) 188 if !math.IsNaN(score[1]) { 189 t.Errorf("Expected score over C to be NaN for x == B, got %v", score[1]) 190 } 191 score = f.Score(nil, f.c) 192 if !math.IsNaN(score[2]) { 193 t.Errorf("Expected score over C to be NaN for x == C, got %v", score[2]) 194 } 195 } 196 197 func logProbDerivative(t Triangle, x float64, i int, h float64) float64 { 198 origParams := t.parameters(nil) 199 params := make([]Parameter, len(origParams)) 200 copy(params, origParams) 201 params[i].Value = origParams[i].Value + h 202 t.setParameters(params) 203 lpUp := t.LogProb(x) 204 params[i].Value = origParams[i].Value - h 205 t.setParameters(params) 206 lpDown := t.LogProb(x) 207 t.setParameters(origParams) 208 return (lpUp - lpDown) / (2 * h) 209 } 210 211 func TestTriangleScoreInput(t *testing.T) { 212 t.Parallel() 213 f := Triangle{a: -0.5, b: 0.7, c: 0.1} 214 xs := []float64{f.a, f.b, f.c, f.a - 0.0001, f.b + 0.0001} 215 for _, x := range xs { 216 scoreInput := f.ScoreInput(x) 217 if !math.IsNaN(scoreInput) { 218 t.Errorf("Expected NaN input score for x == %g, got %v", x, scoreInput) 219 } 220 } 221 }