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  }