gonum.org/v1/gonum@v0.14.0/stat/distuv/triangle_test.go (about)

     1  // Copyright ©2017 The Gonum 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 distuv
     6  
     7  import (
     8  	"math"
     9  	"sort"
    10  	"testing"
    11  
    12  	"golang.org/x/exp/rand"
    13  )
    14  
    15  func TestTriangleConstraint(t *testing.T) {
    16  	t.Parallel()
    17  	for _, test := range []struct{ a, b, c float64 }{
    18  		{a: 1, b: 1, c: 1},
    19  		{a: 1, b: 1, c: 0},
    20  		{a: 1, b: 2, c: 3},
    21  		{a: 1, b: 2, c: 0},
    22  	} {
    23  		if !panics(func() { NewTriangle(test.a, test.b, test.c, nil) }) {
    24  			t.Errorf("expected panic for NewTriangle(%f, %f, %f, nil)", test.a, test.b, test.c)
    25  		}
    26  	}
    27  }
    28  
    29  func TestTriangle(t *testing.T) {
    30  	t.Parallel()
    31  	src := rand.New(rand.NewSource(1))
    32  	for i, test := range []struct {
    33  		a, b, c float64
    34  	}{
    35  		{
    36  			a: 0.0,
    37  			b: 1.0,
    38  			c: 0.5,
    39  		},
    40  		{
    41  			a: 0.1,
    42  			b: 0.3,
    43  			c: 0.2,
    44  		},
    45  		{
    46  			a: 1.0,
    47  			b: 2.0,
    48  			c: 1.5,
    49  		},
    50  		{
    51  			a: 0.0,
    52  			b: 1.0,
    53  			c: 0.0,
    54  		},
    55  		{
    56  			a: 0.0,
    57  			b: 1.2,
    58  			c: 1.2,
    59  		},
    60  	} {
    61  		f := NewTriangle(test.a, test.b, test.c, src)
    62  		const (
    63  			tol = 1e-2
    64  			n   = 1e6
    65  		)
    66  		x := make([]float64, n)
    67  		generateSamples(x, f)
    68  		sort.Float64s(x)
    69  
    70  		checkMean(t, i, x, f, tol)
    71  		checkVarAndStd(t, i, x, f, tol)
    72  		checkEntropy(t, i, x, f, tol)
    73  		checkExKurtosis(t, i, x, f, tol)
    74  		checkSkewness(t, i, x, f, 5e-2)
    75  		checkMedian(t, i, x, f, tol)
    76  		checkQuantileCDFSurvival(t, i, x, f, tol)
    77  		checkProbContinuous(t, i, x, f.a, f.b, f, 1e-10)
    78  		checkProbQuantContinuous(t, i, x, f, tol)
    79  
    80  		if f.c != f.Mode() {
    81  			t.Errorf("Mismatch in mode value: got %v, want %g", f.Mode(), f.c)
    82  		}
    83  	}
    84  }
    85  
    86  func TestTriangleProb(t *testing.T) {
    87  	t.Parallel()
    88  	pts := []univariateProbPoint{
    89  		{
    90  			loc:     0.5,
    91  			prob:    0,
    92  			cumProb: 0,
    93  			logProb: math.Inf(-1),
    94  		},
    95  		{
    96  			loc:     1,
    97  			prob:    0,
    98  			cumProb: 0,
    99  			logProb: math.Inf(-1),
   100  		},
   101  		{
   102  			loc:     2,
   103  			prob:    1.0,
   104  			cumProb: 0.5,
   105  			logProb: 0,
   106  		},
   107  		{
   108  			loc:     3,
   109  			prob:    0,
   110  			cumProb: 1,
   111  			logProb: math.Inf(-1),
   112  		},
   113  		{
   114  			loc:     3.5,
   115  			prob:    0,
   116  			cumProb: 1,
   117  			logProb: math.Inf(-1),
   118  		},
   119  	}
   120  	testDistributionProbs(t, NewTriangle(1, 3, 2, nil), "Standard 1,2,3 Triangle", pts)
   121  }
   122  
   123  func TestTriangleScore(t *testing.T) {
   124  	const (
   125  		h   = 1e-6
   126  		tol = 1e-6
   127  	)
   128  	t.Parallel()
   129  
   130  	f := Triangle{a: -0.5, b: 0.7, c: 0.1}
   131  	testDerivParam(t, &f)
   132  
   133  	f = Triangle{a: 0, b: 1, c: 0}
   134  	x := 0.5
   135  	score := f.Score(nil, x)
   136  	if !math.IsNaN(score[0]) {
   137  		t.Errorf("Expected score over A to be NaN for A == C, got %v", score[0])
   138  	}
   139  	if !math.IsNaN(score[2]) {
   140  		t.Errorf("Expected score over C to be NaN for A == C, got %v", score[2])
   141  	}
   142  	expectedScore := logProbDerivative(f, x, 1, h)
   143  	if math.Abs(expectedScore-score[1]) > tol {
   144  		t.Errorf("Mismatch in score over B for A == C: want %g, got %v", expectedScore, score[1])
   145  	}
   146  
   147  	f = Triangle{a: 0, b: 1, c: 1}
   148  	score = f.Score(nil, x)
   149  	if !math.IsNaN(score[1]) {
   150  		t.Errorf("Expected score over B to be NaN for B == C, got %v", score[1])
   151  	}
   152  	if !math.IsNaN(score[2]) {
   153  		t.Errorf("Expected score over C to be NaN for B == C, got %v", score[2])
   154  	}
   155  	expectedScore = logProbDerivative(f, x, 0, h)
   156  	if math.Abs(expectedScore-score[0]) > tol {
   157  		t.Errorf("Mismatch in score over A for B == C: want %g, got %v", expectedScore, score[0])
   158  	}
   159  
   160  	f = Triangle{a: 0, b: 1, c: 0.5}
   161  	score = f.Score(nil, f.a-0.01)
   162  	if !math.IsNaN(score[0]) {
   163  		t.Errorf("Expected score over B to be NaN for x < A, got %v", score[0])
   164  	}
   165  	if !math.IsNaN(score[1]) {
   166  		t.Errorf("Expected score over B to be NaN for x < A, got %v", score[1])
   167  	}
   168  	if !math.IsNaN(score[2]) {
   169  		t.Errorf("Expected score over C to be NaN for x < A, got %v", score[2])
   170  	}
   171  
   172  	score = f.Score(nil, f.b+0.01)
   173  	if !math.IsNaN(score[0]) {
   174  		t.Errorf("Expected score over B to be NaN for x > B, got %v", score[0])
   175  	}
   176  	if !math.IsNaN(score[1]) {
   177  		t.Errorf("Expected score over B to be NaN for x > B, got %v", score[1])
   178  	}
   179  	if !math.IsNaN(score[2]) {
   180  		t.Errorf("Expected score over C to be NaN for x > B, got %v", score[2])
   181  	}
   182  
   183  	score = f.Score(nil, f.a)
   184  	if !math.IsNaN(score[0]) {
   185  		t.Errorf("Expected score over C to be NaN for x == A, got %v", score[0])
   186  	}
   187  	score = f.Score(nil, f.b)
   188  	if !math.IsNaN(score[1]) {
   189  		t.Errorf("Expected score over C to be NaN for x == B, got %v", score[1])
   190  	}
   191  	score = f.Score(nil, f.c)
   192  	if !math.IsNaN(score[2]) {
   193  		t.Errorf("Expected score over C to be NaN for x == C, got %v", score[2])
   194  	}
   195  }
   196  
   197  func logProbDerivative(t Triangle, x float64, i int, h float64) float64 {
   198  	origParams := t.parameters(nil)
   199  	params := make([]Parameter, len(origParams))
   200  	copy(params, origParams)
   201  	params[i].Value = origParams[i].Value + h
   202  	t.setParameters(params)
   203  	lpUp := t.LogProb(x)
   204  	params[i].Value = origParams[i].Value - h
   205  	t.setParameters(params)
   206  	lpDown := t.LogProb(x)
   207  	t.setParameters(origParams)
   208  	return (lpUp - lpDown) / (2 * h)
   209  }
   210  
   211  func TestTriangleScoreInput(t *testing.T) {
   212  	t.Parallel()
   213  	f := Triangle{a: -0.5, b: 0.7, c: 0.1}
   214  	xs := []float64{f.a, f.b, f.c, f.a - 0.0001, f.b + 0.0001}
   215  	for _, x := range xs {
   216  		scoreInput := f.ScoreInput(x)
   217  		if !math.IsNaN(scoreInput) {
   218  			t.Errorf("Expected NaN input score for x == %g, got %v", x, scoreInput)
   219  		}
   220  	}
   221  }