stat/distuv: improve Exponential test coverage and factor out some tests
Also fix Normal.ConjugateUpdate behavior to match documentation and pass new testing.
MODIFY
stat/distuv/exponential.go
stat/distuv/exponential.go
@@ -38,12 +38,13 @@ func (e Exponential) CDF(x float64) float64 {
// As a result of this function, Exponential.Rate is updated based on the weighted
// samples, and priorStrength is modified to include the new number of samples observed.
//
-// This function panics if len(suffStat) != 1 or len(priorStrength) != 1.
+// This function panics if len(suffStat) != e.NumSuffStat() or
+// len(priorStrength) != e.NumSuffStat().
func (e *Exponential) ConjugateUpdate(suffStat []float64, nSamples float64, priorStrength []float64) {
- if len(suffStat) != 1 {
+ if len(suffStat) != e.NumSuffStat() {
panic("exponential: incorrect suffStat length")
}
- if len(priorStrength) != 1 {
+ if len(priorStrength) != e.NumSuffStat() {
panic("exponential: incorrect priorStrength length")
}

MODIFY
stat/distuv/exponential_test.go
stat/distuv/exponential_test.go
@@ -79,6 +79,48 @@ func testExponential(t *testing.T, dist Exponential, i int) {
checkQuantileCDFSurvival(t, i, x, dist, tol)
checkProbContinuous(t, i, x, dist, 1e-10)
checkProbQuantContinuous(t, i, x, dist, tol)
+
+ if dist.Mode() != 0 {
+ t.Errorf("Mode is not 0. Got %v", dist.Mode())
+ }
+
+ if dist.NumParameters() != 1 {
+ t.Errorf("NumParameters is not 1. Got %v", dist.NumParameters())
+ }
+
+ if dist.NumSuffStat() != 1 {
+ t.Errorf("NumSuffStat is not 1. Got %v", dist.NumSuffStat())
+ }
+
+ scoreInput := dist.ScoreInput(-0.0001)
+ if scoreInput != 0 {
+ t.Errorf("ScoreInput is not 0 for a negative argument. Got %v", scoreInput)
+ }
+ scoreInput = dist.ScoreInput(0)
+ if !math.IsNaN(scoreInput) {
+ t.Errorf("ScoreInput is not NaN at 0. Got %v", scoreInput)
+ }
+ scoreInput = dist.ScoreInput(1)
+ if scoreInput != -dist.Rate {
+ t.Errorf("ScoreInput mismatch for a positive argument. Got %v, want %g", scoreInput, dist.Rate)
+ }
+
+ deriv := make([]float64, 1)
+ dist.Score(deriv, -0.0001)
+ if deriv[0] != 0 {
+ t.Errorf("Score is not 0 for a negative argument. Got %v", deriv[0])
+ }
+ dist.Score(deriv, 0)
+ if !math.IsNaN(deriv[0]) {
+ t.Errorf("Score is not NaN at 0. Got %v", deriv[0])
+ }
+
+ if !panics(func() { dist.Quantile(-0.0001) }) {
+ t.Errorf("Expected panic with negative argument to Quantile")
+ }
+ if !panics(func() { dist.Quantile(1.0001) }) {
+ t.Errorf("Expected panic with argument to Quantile above 1")
+ }
}
func TestExponentialScore(t *testing.T) {

MODIFY
stat/distuv/general_test.go
stat/distuv/general_test.go
@@ -124,6 +124,33 @@ func testConjugateUpdate(t *testing.T, newFittable func() ConjugateUpdater) {
}
}
}
+ testSuffStatPanics(t, newFittable)
+ testConjugateUpdatePanics(t, newFittable)
+}
+
+func testSuffStatPanics(t *testing.T, newFittable func() ConjugateUpdater) {
+ dist := newFittable()
+ sample := randn(dist, 10)
+ if !panics(func() { dist.SuffStat(make([]float64, dist.NumSuffStat()), sample, make([]float64, len(sample)+1)) }) {
+ t.Errorf("Expected panic for mismatch between samples and weights lengths")
+ }
+ if !panics(func() { dist.SuffStat(make([]float64, dist.NumSuffStat()+1), sample, nil) }) {
+ t.Errorf("Expected panic for wrong sufficient statistic length")
+ }
+}
+
+func testConjugateUpdatePanics(t *testing.T, newFittable func() ConjugateUpdater) {
+ dist := newFittable()
+ if !panics(func() {
+ dist.ConjugateUpdate(make([]float64, dist.NumSuffStat()+1), 100, make([]float64, dist.NumParameters()))
+ }) {
+ t.Errorf("Expected panic for wrong sufficient statistic length")
+ }
+ if !panics(func() {
+ dist.ConjugateUpdate(make([]float64, dist.NumSuffStat()), 100, make([]float64, dist.NumParameters()+1))
+ }) {
+ t.Errorf("Expected panic for wrong prior strength length")
+ }
}
// randn generates a specified number of random samples
@@ -174,6 +201,10 @@ func testDerivParam(t *testing.T, d derivParamTester) {
deriv := make([]float64, d.NumParameters())
fdDeriv := make([]float64, d.NumParameters())
+ if !panics(func() { d.Score(make([]float64, d.NumParameters()+1), 0) }) {
+ t.Errorf("Expected panic for wrong derivative slice length")
+ }
+
initParams := d.parameters(nil)
init := make([]float64, d.NumParameters())
for i, v := range initParams {
@@ -182,7 +213,10 @@ func testDerivParam(t *testing.T, d derivParamTester) {
for _, v := range quantiles {
d.setParameters(initParams)
x := d.Quantile(v)
- d.Score(deriv, x)
+ gotDeriv := d.Score(deriv, x)
+ if &gotDeriv[0] != &deriv[0] {
+ t.Errorf("Returned a different derivative slice than passed in. Got %v, want %v", gotDeriv, deriv)
+ }
f := func(p []float64) float64 {
params := d.parameters(nil)
for i, v := range p {

MODIFY
stat/distuv/norm.go
stat/distuv/norm.go
@@ -44,10 +44,17 @@ func (n Normal) CDF(x float64) float64 {
// this function, Normal.Mu and Normal.Sigma are updated based on the weighted
// samples, and strength is modified to include the new number of samples observed.
//
-// This function panics if len(suffStat) != 2 or len(priorStrength) != 2.
+// This function panics if len(suffStat) != n.NumSuffStat() or
+// len(priorStrength) != n.NumSuffStat().
func (n *Normal) ConjugateUpdate(suffStat []float64, nSamples float64, priorStrength []float64) {
// TODO: Support prior strength with math.Inf(1) to allow updating with
// a known mean/standard deviation
+ if len(suffStat) != n.NumSuffStat() {
+ panic("norm: incorrect suffStat length")
+ }
+ if len(priorStrength) != n.NumSuffStat() {
+ panic("norm: incorrect priorStrength length")
+ }
totalMeanSamples := nSamples + priorStrength[0]
totalSum := suffStat[0]*nSamples + n.Mu*priorStrength[0]

stat/distuv: improve Exponential test coverage and factor out some tests

Also fix Normal.ConjugateUpdate behavior to match documentation and pass new testing.