| // Copyright 2015 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package stats |
| |
| import ( |
| "fmt" |
| "math" |
| "sort" |
| "strings" |
| "testing" |
| ) |
| |
| func testDiscreteCDF(t *testing.T, name string, dist DiscreteDist) { |
| // Build the expected CDF out of the PMF. |
| l, h := dist.Bounds() |
| s := dist.Step() |
| want := map[float64]float64{l - 0.1: 0, h: 1} |
| sum := 0.0 |
| for x := l; x < h; x += s { |
| sum += dist.PMF(x) |
| want[x] = sum |
| want[x+s/2] = sum |
| } |
| |
| testFunc(t, name, dist.CDF, want) |
| } |
| |
| func testInvCDF(t *testing.T, dist Dist, bounded bool) { |
| inv := InvCDF(dist) |
| name := fmt.Sprintf("InvCDF(%+v)", dist) |
| cdfName := fmt.Sprintf("CDF(%+v)", dist) |
| |
| // Test bounds. |
| vals := map[float64]float64{-0.01: nan, 1.01: nan} |
| if !bounded { |
| vals[0] = -inf |
| vals[1] = inf |
| } |
| testFunc(t, name, inv, vals) |
| |
| if bounded { |
| lo, hi := inv(0), inv(1) |
| vals := map[float64]float64{ |
| lo - 0.01: 0, lo: 0, |
| hi: 1, hi + 0.01: 1, |
| } |
| testFunc(t, cdfName, dist.CDF, vals) |
| if got := dist.CDF(lo + 0.01); !(got > 0) { |
| t.Errorf("%s(0)=%v, but %s(%v)=0", name, lo, cdfName, lo+0.01) |
| } |
| if got := dist.CDF(hi - 0.01); !(got < 1) { |
| t.Errorf("%s(1)=%v, but %s(%v)=1", name, hi, cdfName, hi-0.01) |
| } |
| } |
| |
| // Test points between. |
| vals = map[float64]float64{} |
| for _, p := range vecLinspace(0, 1, 11) { |
| if p == 0 || p == 1 { |
| continue |
| } |
| x := inv(p) |
| vals[x] = x |
| } |
| testFunc(t, fmt.Sprintf("InvCDF(CDF(%+v))", dist), |
| func(x float64) float64 { |
| return inv(dist.CDF(x)) |
| }, |
| vals) |
| } |
| |
| // aeq returns true if expect and got are equal to 8 significant |
| // figures (1 part in 100 million). |
| func aeq(expect, got float64) bool { |
| if expect < 0 && got < 0 { |
| expect, got = -expect, -got |
| } |
| return expect*0.99999999 <= got && got*0.99999999 <= expect |
| } |
| |
| func testFunc(t *testing.T, name string, f func(float64) float64, vals map[float64]float64) { |
| xs := make([]float64, 0, len(vals)) |
| for x := range vals { |
| xs = append(xs, x) |
| } |
| sort.Float64s(xs) |
| |
| for _, x := range xs { |
| want, got := vals[x], f(x) |
| if math.IsNaN(want) && math.IsNaN(got) || aeq(want, got) { |
| continue |
| } |
| var label string |
| if strings.Contains(name, "%v") { |
| label = fmt.Sprintf(name, x) |
| } else { |
| label = fmt.Sprintf("%s(%v)", name, x) |
| } |
| t.Errorf("want %s=%v, got %v", label, want, got) |
| } |
| } |
| |
| // vecLinspace returns num values spaced evenly between lo and hi, |
| // inclusive. If num is 1, this returns an array consisting of lo. |
| func vecLinspace(lo, hi float64, num int) []float64 { |
| res := make([]float64, num) |
| if num == 1 { |
| res[0] = lo |
| return res |
| } |
| for i := 0; i < num; i++ { |
| res[i] = lo + float64(i)*(hi-lo)/float64(num-1) |
| } |
| return res |
| } |