blob: 6cc95b164218a1f9fd054ca7aec96b01bb13b7d5 [file] [log] [blame]
// Copyright ©2017 The Gonum 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 distuv
import (
"math"
"sort"
"testing"
"golang.org/x/exp/rand"
)
func TestTriangleConstraint(t *testing.T) {
t.Parallel()
for _, test := range []struct{ a, b, c float64 }{
{a: 1, b: 1, c: 1},
{a: 1, b: 1, c: 0},
{a: 1, b: 2, c: 3},
{a: 1, b: 2, c: 0},
} {
if !panics(func() { NewTriangle(test.a, test.b, test.c, nil) }) {
t.Errorf("expected panic for NewTriangle(%f, %f, %f, nil)", test.a, test.b, test.c)
}
}
}
func TestTriangle(t *testing.T) {
t.Parallel()
src := rand.New(rand.NewSource(1))
for i, test := range []struct {
a, b, c float64
}{
{
a: 0.0,
b: 1.0,
c: 0.5,
},
{
a: 0.1,
b: 0.3,
c: 0.2,
},
{
a: 1.0,
b: 2.0,
c: 1.5,
},
{
a: 0.0,
b: 1.0,
c: 0.0,
},
{
a: 0.0,
b: 1.2,
c: 1.2,
},
} {
f := NewTriangle(test.a, test.b, test.c, src)
const (
tol = 1e-2
n = 1e6
)
x := make([]float64, n)
generateSamples(x, f)
sort.Float64s(x)
checkMean(t, i, x, f, tol)
checkVarAndStd(t, i, x, f, tol)
checkEntropy(t, i, x, f, tol)
checkExKurtosis(t, i, x, f, tol)
checkSkewness(t, i, x, f, 5e-2)
checkMedian(t, i, x, f, tol)
checkQuantileCDFSurvival(t, i, x, f, tol)
checkProbContinuous(t, i, x, f.a, f.b, f, 1e-10)
checkProbQuantContinuous(t, i, x, f, tol)
if f.c != f.Mode() {
t.Errorf("Mismatch in mode value: got %v, want %g", f.Mode(), f.c)
}
}
}
func TestTriangleProb(t *testing.T) {
t.Parallel()
pts := []univariateProbPoint{
{
loc: 0.5,
prob: 0,
cumProb: 0,
logProb: math.Inf(-1),
},
{
loc: 1,
prob: 0,
cumProb: 0,
logProb: math.Inf(-1),
},
{
loc: 2,
prob: 1.0,
cumProb: 0.5,
logProb: 0,
},
{
loc: 3,
prob: 0,
cumProb: 1,
logProb: math.Inf(-1),
},
{
loc: 3.5,
prob: 0,
cumProb: 1,
logProb: math.Inf(-1),
},
}
testDistributionProbs(t, NewTriangle(1, 3, 2, nil), "Standard 1,2,3 Triangle", pts)
}
func TestTriangleScore(t *testing.T) {
const (
h = 1e-6
tol = 1e-6
)
t.Parallel()
f := Triangle{a: -0.5, b: 0.7, c: 0.1}
testDerivParam(t, &f)
f = Triangle{a: 0, b: 1, c: 0}
x := 0.5
score := f.Score(nil, x)
if !math.IsNaN(score[0]) {
t.Errorf("Expected score over A to be NaN for A == C, got %v", score[0])
}
if !math.IsNaN(score[2]) {
t.Errorf("Expected score over C to be NaN for A == C, got %v", score[2])
}
expectedScore := logProbDerivative(f, x, 1, h)
if math.Abs(expectedScore-score[1]) > tol {
t.Errorf("Mismatch in score over B for A == C: want %g, got %v", expectedScore, score[1])
}
f = Triangle{a: 0, b: 1, c: 1}
score = f.Score(nil, x)
if !math.IsNaN(score[1]) {
t.Errorf("Expected score over B to be NaN for B == C, got %v", score[1])
}
if !math.IsNaN(score[2]) {
t.Errorf("Expected score over C to be NaN for B == C, got %v", score[2])
}
expectedScore = logProbDerivative(f, x, 0, h)
if math.Abs(expectedScore-score[0]) > tol {
t.Errorf("Mismatch in score over A for B == C: want %g, got %v", expectedScore, score[0])
}
f = Triangle{a: 0, b: 1, c: 0.5}
score = f.Score(nil, f.a-0.01)
if !math.IsNaN(score[0]) {
t.Errorf("Expected score over B to be NaN for x < A, got %v", score[0])
}
if !math.IsNaN(score[1]) {
t.Errorf("Expected score over B to be NaN for x < A, got %v", score[1])
}
if !math.IsNaN(score[2]) {
t.Errorf("Expected score over C to be NaN for x < A, got %v", score[2])
}
score = f.Score(nil, f.b+0.01)
if !math.IsNaN(score[0]) {
t.Errorf("Expected score over B to be NaN for x > B, got %v", score[0])
}
if !math.IsNaN(score[1]) {
t.Errorf("Expected score over B to be NaN for x > B, got %v", score[1])
}
if !math.IsNaN(score[2]) {
t.Errorf("Expected score over C to be NaN for x > B, got %v", score[2])
}
score = f.Score(nil, f.a)
if !math.IsNaN(score[0]) {
t.Errorf("Expected score over C to be NaN for x == A, got %v", score[0])
}
score = f.Score(nil, f.b)
if !math.IsNaN(score[1]) {
t.Errorf("Expected score over C to be NaN for x == B, got %v", score[1])
}
score = f.Score(nil, f.c)
if !math.IsNaN(score[2]) {
t.Errorf("Expected score over C to be NaN for x == C, got %v", score[2])
}
}
func logProbDerivative(t Triangle, x float64, i int, h float64) float64 {
origParams := t.parameters(nil)
params := make([]Parameter, len(origParams))
copy(params, origParams)
params[i].Value = origParams[i].Value + h
t.setParameters(params)
lpUp := t.LogProb(x)
params[i].Value = origParams[i].Value - h
t.setParameters(params)
lpDown := t.LogProb(x)
t.setParameters(origParams)
return (lpUp - lpDown) / (2 * h)
}
func TestTriangleScoreInput(t *testing.T) {
t.Parallel()
f := Triangle{a: -0.5, b: 0.7, c: 0.1}
xs := []float64{f.a, f.b, f.c, f.a - 0.0001, f.b + 0.0001}
for _, x := range xs {
scoreInput := f.ScoreInput(x)
if !math.IsNaN(scoreInput) {
t.Errorf("Expected NaN input score for x == %g, got %v", x, scoreInput)
}
}
}