github.com/jgbaldwinbrown/perf@v0.1.1/benchmath/assumption_test.go (about) 1 // Copyright 2022 The Go 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 benchmath 6 7 import ( 8 "fmt" 9 "math" 10 "testing" 11 12 "github.com/aclements/go-moremath/stats" 13 ) 14 15 func TestMedianSamples(t *testing.T) { 16 if false { 17 for n := 2; n <= 50; n++ { 18 d := stats.BinomialDist{N: n, P: 0.5} 19 t.Log(n, 1-(d.PMF(0)+d.PMF(float64(d.N))), d.PMF(0)) 20 } 21 } 22 23 check := func(confidence float64, wantOp string, wantN int) { 24 t.Helper() 25 gotOp, gotN := medianSamples(confidence) 26 if gotOp != wantOp || gotN != wantN { 27 t.Errorf("for confidence %v, want %s %d, got %s %d", confidence, wantOp, wantN, gotOp, gotN) 28 } 29 } 30 31 // At n=6, the tails are 0.015625 * 2 => 0.03125 32 check(0.95, ">=", 6) 33 // At n=8, the tails are 0.00390625 * 2 => 0.0078125 34 check(0.99, ">=", 8) 35 // The hard-coded threshold is 50. 36 check(1, ">", 50) 37 // Check the other extreme. We always need at least two 38 // samples to have an interval. 39 check(0, ">=", 2) 40 } 41 42 func TestUTestSamples(t *testing.T) { 43 check := func(alpha float64, wantOp string, wantN int) { 44 t.Helper() 45 gotOp, gotN := uTestSamples(alpha) 46 if gotOp != wantOp || gotN != wantN { 47 t.Errorf("for alpha %v, want %s %d, got %s %d", alpha, wantOp, wantN, gotOp, gotN) 48 } 49 } 50 check(1, ">=", 1) 51 check(0.05, ">=", 4) 52 check(0.01, ">=", 5) 53 check(1e-50, ">", 10) 54 check(0, ">", 10) 55 } 56 57 func TestSummaryNone(t *testing.T) { 58 // The following tests correspond to the tests in 59 // TestMedianSamples. 60 a := AssumeNothing 61 var sample *Sample 62 inf := math.Inf(1) 63 sample = NewSample([]float64{-10, 2, 3, 4, 5, 6}, &DefaultThresholds) 64 checkSummary(t, a.Summary(sample, 0.95), 65 Summary{Center: 3.5, Lo: -10, Hi: 6, Confidence: 1 - 0.03125}) 66 checkSummary(t, a.Summary(sample, 0.99), 67 Summary{Center: 3.5, Lo: -inf, Hi: inf, Confidence: 1}, 68 "need >= 8 samples for confidence interval at level 0.99") 69 checkSummary(t, a.Summary(sample, 1), 70 Summary{Center: 3.5, Lo: -inf, Hi: inf, Confidence: 1}, 71 "need > 50 samples for confidence interval at level 1") 72 sample = NewSample([]float64{1, 2}, &DefaultThresholds) 73 checkSummary(t, a.Summary(sample, 0), 74 Summary{Center: 1.5, Lo: 1, Hi: 2, Confidence: 0.5}) 75 76 // And test very small samples. 77 sample = NewSample([]float64{1}, &DefaultThresholds) 78 checkSummary(t, a.Summary(sample, 0.95), 79 Summary{Center: 1, Lo: -inf, Hi: inf, Confidence: 1}, 80 "need >= 6 samples for confidence interval at level 0.95") 81 } 82 83 func TestCompareNone(t *testing.T) { 84 // Most of the complexity is in the sample size warning. 85 a := AssumeNothing 86 thr := DefaultThresholds 87 thr.CompareAlpha = 0.05 88 // Too-small samples. 89 s1 := NewSample([]float64{-1, -1, -1}, &thr) 90 s2 := NewSample([]float64{1, 1, 1}, &thr) 91 checkComparison(t, a.Compare(s1, s2), 92 Comparison{P: 0.1, N1: 3, N2: 3, Alpha: 0.05}, 93 "need >= 4 samples to detect a difference at alpha level 0.05") 94 // Big enough samples with a difference. 95 s1 = NewSample([]float64{-1, -1, -1, -1}, &thr) 96 s2 = NewSample([]float64{1, 1, 1, 1}, &thr) 97 checkComparison(t, a.Compare(s1, s2), 98 Comparison{P: 0.02857142857142857, N1: 4, N2: 4, Alpha: 0.05}) 99 // Big enough samples, but not enough difference. 100 s1 = NewSample([]float64{1, -1, -1, -1}, &thr) 101 s2 = NewSample([]float64{-1, 1, 1, 1}, &thr) 102 checkComparison(t, a.Compare(s1, s2), 103 Comparison{P: 0.4857142857142857, N1: 4, N2: 4, Alpha: 0.05}) 104 105 // All samples equal, so the U-test is meaningless. 106 s1 = NewSample([]float64{1, 1, 1, 1}, &thr) 107 s2 = NewSample([]float64{1, 1, 1, 1}, &thr) 108 checkComparison(t, a.Compare(s1, s2), 109 Comparison{P: 1, N1: 4, N2: 4, Alpha: 0.05}, 110 "all samples are equal") 111 112 } 113 114 func TestSummaryNormal(t *testing.T) { 115 // This is a thin wrapper around sample.MeanCI, so just do a 116 // smoke test. 117 a := AssumeNormal 118 sample := NewSample([]float64{-8, 2, 3, 4, 5, 6}, &DefaultThresholds) 119 checkSummary(t, a.Summary(sample, 0.95), 120 Summary{Center: 2, Lo: -3.351092806089359, Hi: 7.351092806089359, Confidence: 0.95}) 121 } 122 123 func TestSummaryExact(t *testing.T) { 124 a := AssumeExact 125 sample := NewSample([]float64{1, 1, 1, 1}, &DefaultThresholds) 126 checkSummary(t, a.Summary(sample, 0.95), 127 Summary{Center: 1, Lo: 1, Hi: 1, Confidence: 1}) 128 129 sample = NewSample([]float64{1}, &DefaultThresholds) 130 checkSummary(t, a.Summary(sample, 0.95), 131 Summary{Center: 1, Lo: 1, Hi: 1, Confidence: 1}) 132 133 sample = NewSample([]float64{1, 2, 2, 3}, &DefaultThresholds) 134 checkSummary(t, a.Summary(sample, 0.95), 135 Summary{Center: 2, Lo: 1, Hi: 3, Confidence: 1}, 136 "exact distribution expected, but values range from 1 to 3") 137 } 138 139 func aeq(x, y float64) bool { 140 if x < 0 && y < 0 { 141 x, y = -x, -y 142 } 143 // Check that x and y are equal to 8 digits. 144 const factor = 1 - 1e-7 145 return x*factor <= y && y*factor <= x 146 } 147 148 func checkSummary(t *testing.T, got, want Summary, warnings ...string) { 149 t.Helper() 150 for _, w := range warnings { 151 want.Warnings = append(want.Warnings, fmt.Errorf("%s", w)) 152 } 153 if !aeq(got.Center, want.Center) || !aeq(got.Lo, want.Lo) || !aeq(got.Hi, got.Hi) || got.Confidence != want.Confidence || !errorsEq(got.Warnings, want.Warnings) { 154 t.Errorf("got %v, want %v", got, want) 155 } 156 } 157 158 func checkComparison(t *testing.T, got, want Comparison, warnings ...string) { 159 t.Helper() 160 for _, w := range warnings { 161 want.Warnings = append(want.Warnings, fmt.Errorf("%s", w)) 162 } 163 if !aeq(got.P, want.P) || got.N1 != want.N1 || got.N2 != want.N2 || got.Alpha != want.Alpha || !errorsEq(got.Warnings, want.Warnings) { 164 t.Errorf("got %#v, want %#v", got, want) 165 } 166 } 167 168 func errorsEq(a, b []error) bool { 169 if len(a) != len(b) { 170 return false 171 } 172 for i := range a { 173 if a[i].Error() != b[i].Error() { 174 return false 175 } 176 } 177 return true 178 }