github.com/jingcheng-WU/gonum@v0.9.1-0.20210323123734-f1a2a11a8f7b/stat/sampleuv/weighted_test.go (about)

     1  // Copyright ©2015 The Gonum Authors. All rights reserved.
     2  // Use of this code is governed by a BSD-style
     3  // license that can be found in the LICENSE file
     4  
     5  package sampleuv
     6  
     7  import (
     8  	"flag"
     9  	"reflect"
    10  	"testing"
    11  	"time"
    12  
    13  	"golang.org/x/exp/rand"
    14  
    15  	"github.com/jingcheng-WU/gonum/floats"
    16  )
    17  
    18  var prob = flag.Bool("prob", false, "enables probabilistic testing of the random weighted sampler")
    19  
    20  const sigChi2 = 16.92 // p = 0.05 df = 9
    21  
    22  var (
    23  	newExp = func() []float64 {
    24  		return []float64{1 << 0, 1 << 1, 1 << 2, 1 << 3, 1 << 4, 1 << 5, 1 << 6, 1 << 7, 1 << 8, 1 << 9}
    25  	}
    26  	exp = newExp()
    27  
    28  	obt = []float64{1020, 1909, 3937, 7881, 15687, 31486, 62310, 124632, 250453, 500685}
    29  )
    30  
    31  func newTestWeighted() Weighted {
    32  	weights := make([]float64, len(obt))
    33  	for i := range weights {
    34  		weights[i] = float64(int(1) << uint(i))
    35  	}
    36  	return NewWeighted(weights, nil)
    37  }
    38  
    39  func TestWeightedUnseeded(t *testing.T) {
    40  	rand.Seed(0)
    41  
    42  	want := Weighted{
    43  		weights: []float64{1 << 0, 1 << 1, 1 << 2, 1 << 3, 1 << 4, 1 << 5, 1 << 6, 1 << 7, 1 << 8, 1 << 9},
    44  		heap: []float64{
    45  			exp[0] + exp[1] + exp[3] + exp[4] + exp[7] + exp[8] + exp[9] + exp[2] + exp[5] + exp[6],
    46  			exp[1] + exp[3] + exp[4] + exp[7] + exp[8] + exp[9],
    47  			exp[2] + exp[5] + exp[6],
    48  			exp[3] + exp[7] + exp[8],
    49  			exp[4] + exp[9],
    50  			exp[5],
    51  			exp[6],
    52  			exp[7],
    53  			exp[8],
    54  			exp[9],
    55  		},
    56  	}
    57  
    58  	ts := newTestWeighted()
    59  	if !reflect.DeepEqual(ts, want) {
    60  		t.Fatalf("unexpected new Weighted value:\ngot: %#v\nwant:%#v", ts, want)
    61  	}
    62  
    63  	f := make([]float64, len(obt))
    64  	for i := 0; i < 1e6; i++ {
    65  		item, ok := newTestWeighted().Take()
    66  		if !ok {
    67  			t.Fatal("Weighted unexpectedly empty")
    68  		}
    69  		f[item]++
    70  	}
    71  
    72  	exp := newExp()
    73  	fac := floats.Sum(f) / floats.Sum(exp)
    74  	for i := range f {
    75  		exp[i] *= fac
    76  	}
    77  
    78  	if !reflect.DeepEqual(f, obt) {
    79  		t.Fatalf("unexpected selection:\ngot: %#v\nwant:%#v", f, obt)
    80  	}
    81  
    82  	// Check that this is within statistical expectations - we know this is true for this set.
    83  	X := chi2(f, exp)
    84  	if X >= sigChi2 {
    85  		t.Errorf("H₀: d(Sample) = d(Expect), H₁: d(S) ≠ d(Expect). df = %d, p = 0.05, X² threshold = %.2f, X² = %f", len(f)-1, sigChi2, X)
    86  	}
    87  }
    88  
    89  func TestWeightedTimeSeeded(t *testing.T) {
    90  	if !*prob {
    91  		t.Skip("probabilistic testing not requested")
    92  	}
    93  	t.Log("Note: This test is stochastic and is expected to fail with probability ≈ 0.05.")
    94  
    95  	rand.Seed(uint64(time.Now().Unix()))
    96  
    97  	f := make([]float64, len(obt))
    98  	for i := 0; i < 1e6; i++ {
    99  		item, ok := newTestWeighted().Take()
   100  		if !ok {
   101  			t.Fatal("Weighted unexpectedly empty")
   102  		}
   103  		f[item]++
   104  	}
   105  
   106  	exp := newExp()
   107  	fac := floats.Sum(f) / floats.Sum(exp)
   108  	for i := range f {
   109  		exp[i] *= fac
   110  	}
   111  
   112  	// Check that our obtained values are within statistical expectations for p = 0.05.
   113  	// This will not be true approximately 1 in 20 tests.
   114  	X := chi2(f, exp)
   115  	if X >= sigChi2 {
   116  		t.Errorf("H₀: d(Sample) = d(Expect), H₁: d(S) ≠ d(Expect). df = %d, p = 0.05, X² threshold = %.2f, X² = %f", len(f)-1, sigChi2, X)
   117  	}
   118  }
   119  
   120  func TestWeightZero(t *testing.T) {
   121  	rand.Seed(0)
   122  
   123  	want := Weighted{
   124  		weights: []float64{1 << 0, 1 << 1, 1 << 2, 1 << 3, 1 << 4, 1 << 5, 0, 1 << 7, 1 << 8, 1 << 9},
   125  		heap: []float64{
   126  			exp[0] + exp[1] + exp[3] + exp[4] + exp[7] + exp[8] + exp[9] + exp[2] + exp[5],
   127  			exp[1] + exp[3] + exp[4] + exp[7] + exp[8] + exp[9],
   128  			exp[2] + exp[5],
   129  			exp[3] + exp[7] + exp[8],
   130  			exp[4] + exp[9],
   131  			exp[5],
   132  			0,
   133  			exp[7],
   134  			exp[8],
   135  			exp[9],
   136  		},
   137  	}
   138  
   139  	ts := newTestWeighted()
   140  	ts.Reweight(6, 0)
   141  	if !reflect.DeepEqual(ts, want) {
   142  		t.Fatalf("unexpected new Weighted value:\ngot: %#v\nwant:%#v", ts, want)
   143  	}
   144  
   145  	f := make([]float64, len(obt))
   146  	for i := 0; i < 1e6; i++ {
   147  		ts := newTestWeighted()
   148  		ts.Reweight(6, 0)
   149  		item, ok := ts.Take()
   150  		if !ok {
   151  			t.Fatal("Weighted unexpectedly empty")
   152  		}
   153  		f[item]++
   154  	}
   155  
   156  	exp := newExp()
   157  	fac := floats.Sum(f) / floats.Sum(exp)
   158  	for i := range f {
   159  		exp[i] *= fac
   160  	}
   161  
   162  	if f[6] != 0 {
   163  		t.Errorf("unexpected selection rate for zero-weighted item: got: %v want:%v", f[6], 0)
   164  	}
   165  	if reflect.DeepEqual(f[:6], obt[:6]) {
   166  		t.Fatalf("unexpected selection: too few elements chosen in range:\ngot: %v\nwant:%v",
   167  			f[:6], obt[:6])
   168  	}
   169  	if reflect.DeepEqual(f[7:], obt[7:]) {
   170  		t.Fatalf("unexpected selection: too few elements chosen in range:\ngot: %v\nwant:%v",
   171  			f[7:], obt[7:])
   172  	}
   173  }
   174  
   175  func TestWeightIncrease(t *testing.T) {
   176  	rand.Seed(0)
   177  
   178  	want := Weighted{
   179  		weights: []float64{1 << 0, 1 << 1, 1 << 2, 1 << 3, 1 << 4, 1 << 5, 1 << 9 * 2, 1 << 7, 1 << 8, 1 << 9},
   180  		heap: []float64{
   181  			exp[0] + exp[1] + exp[3] + exp[4] + exp[7] + exp[8] + exp[9] + exp[2] + exp[5] + exp[9]*2,
   182  			exp[1] + exp[3] + exp[4] + exp[7] + exp[8] + exp[9],
   183  			exp[2] + exp[5] + exp[9]*2,
   184  			exp[3] + exp[7] + exp[8],
   185  			exp[4] + exp[9],
   186  			exp[5],
   187  			exp[9] * 2,
   188  			exp[7],
   189  			exp[8],
   190  			exp[9],
   191  		},
   192  	}
   193  
   194  	ts := newTestWeighted()
   195  	ts.Reweight(6, ts.weights[len(ts.weights)-1]*2)
   196  	if !reflect.DeepEqual(ts, want) {
   197  		t.Fatalf("unexpected new Weighted value:\ngot: %#v\nwant:%#v", ts, want)
   198  	}
   199  
   200  	f := make([]float64, len(obt))
   201  	for i := 0; i < 1e6; i++ {
   202  		ts := newTestWeighted()
   203  		ts.Reweight(6, ts.weights[len(ts.weights)-1]*2)
   204  		item, ok := ts.Take()
   205  		if !ok {
   206  			t.Fatal("Weighted unexpectedly empty")
   207  		}
   208  		f[item]++
   209  	}
   210  
   211  	exp := newExp()
   212  	fac := floats.Sum(f) / floats.Sum(exp)
   213  	for i := range f {
   214  		exp[i] *= fac
   215  	}
   216  
   217  	if f[6] < f[9] {
   218  		t.Errorf("unexpected selection rate for re-weighted item: got: %v want:%v", f[6], f[9])
   219  	}
   220  	if reflect.DeepEqual(f[:6], obt[:6]) {
   221  		t.Fatalf("unexpected selection: too many elements chosen in range:\ngot: %v\nwant:%v",
   222  			f[:6], obt[:6])
   223  	}
   224  	if reflect.DeepEqual(f[7:], obt[7:]) {
   225  		t.Fatalf("unexpected selection: too many elements chosen in range:\ngot: %v\nwant:%v",
   226  			f[7:], obt[7:])
   227  	}
   228  }
   229  
   230  func chi2(ob, ex []float64) (sum float64) {
   231  	for i := range ob {
   232  		x := ob[i] - ex[i]
   233  		sum += (x * x) / ex[i]
   234  	}
   235  
   236  	return sum
   237  }
   238  
   239  func TestWeightedNoResample(t *testing.T) {
   240  	const (
   241  		tries = 10
   242  		n     = 10e4
   243  	)
   244  	ts := NewWeighted(make([]float64, n), nil)
   245  	w := make([]float64, n)
   246  	for i := 0; i < tries; i++ {
   247  		for j := range w {
   248  			w[j] = rand.Float64() * n
   249  		}
   250  		ts.ReweightAll(w)
   251  		taken := make(map[int]struct{})
   252  		var c int
   253  		for {
   254  			item, ok := ts.Take()
   255  			if !ok {
   256  				if c != n {
   257  					t.Errorf("unexpected number of items: got: %d want: %d", c, int(n))
   258  				}
   259  				break
   260  			}
   261  			c++
   262  			if _, exists := taken[item]; exists {
   263  				t.Errorf("unexpected duplicate sample for item: %d", item)
   264  			}
   265  			taken[item] = struct{}{}
   266  		}
   267  	}
   268  }