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 }