gonum.org/v1/gonum@v0.14.0/interp/interp_test.go (about) 1 // Copyright ©2020 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 interp 6 7 import ( 8 "math" 9 "testing" 10 11 "gonum.org/v1/gonum/floats" 12 ) 13 14 func TestConstant(t *testing.T) { 15 t.Parallel() 16 const value = 42.0 17 c := Constant(value) 18 xs := []float64{math.Inf(-1), -11, 0.4, 1e9, math.Inf(1)} 19 for _, x := range xs { 20 y := c.Predict(x) 21 if y != value { 22 t.Errorf("unexpected Predict(%g) value: got: %g want: %g", x, y, value) 23 } 24 } 25 } 26 27 func TestFunction(t *testing.T) { 28 fn := func(x float64) float64 { return math.Exp(x) } 29 predictor := Function(fn) 30 xs := []float64{-100, -1, 0, 0.5, 15} 31 for _, x := range xs { 32 want := fn(x) 33 got := predictor.Predict(x) 34 if got != want { 35 t.Errorf("unexpected Predict(%g) value: got: %g want: %g", x, got, want) 36 } 37 } 38 } 39 40 func TestFindSegment(t *testing.T) { 41 t.Parallel() 42 xs := []float64{0, 1, 2} 43 testXs := []float64{-0.6, 0, 0.3, 1, 1.5, 2, 2.8} 44 expectedIs := []int{-1, 0, 0, 1, 1, 2, 2} 45 for k, x := range testXs { 46 i := findSegment(xs, x) 47 if i != expectedIs[k] { 48 t.Errorf("unexpected value of findSegment(xs, %g): got %d want: %d", x, i, expectedIs[k]) 49 } 50 } 51 } 52 53 func BenchmarkFindSegment(b *testing.B) { 54 xs := []float64{0, 1.5, 3, 4.5, 6, 7.5, 9, 12, 13.5, 16.5} 55 for i := 0; i < b.N; i++ { 56 findSegment(xs, 0) 57 findSegment(xs, 16.5) 58 findSegment(xs, -1) 59 findSegment(xs, 8.25) 60 findSegment(xs, 4.125) 61 findSegment(xs, 13.6) 62 findSegment(xs, 23.6) 63 findSegment(xs, 13.5) 64 findSegment(xs, 6) 65 findSegment(xs, 4.5) 66 } 67 } 68 69 // testPiecewiseInterpolatorCreation tests common functionality in creating piecewise interpolators. 70 func testPiecewiseInterpolatorCreation(t *testing.T, fp FittablePredictor) { 71 type errorParams struct { 72 xs []float64 73 ys []float64 74 } 75 errorParamSets := []errorParams{ 76 {[]float64{0, 1, 2}, []float64{-0.5, 1.5}}, 77 {[]float64{0.3}, []float64{0}}, 78 {[]float64{0.3, 0.3}, []float64{0, 0}}, 79 {[]float64{0.3, -0.3}, []float64{0, 0}}, 80 } 81 for _, params := range errorParamSets { 82 if !panics(func() { _ = fp.Fit(params.xs, params.ys) }) { 83 t.Errorf("expected panic for xs: %v and ys: %v", params.xs, params.ys) 84 } 85 } 86 } 87 88 func TestPiecewiseLinearFit(t *testing.T) { 89 t.Parallel() 90 testPiecewiseInterpolatorCreation(t, &PiecewiseLinear{}) 91 } 92 93 // testInterpolatorPredict tests evaluation of a interpolator. 94 func testInterpolatorPredict(t *testing.T, p Predictor, xs []float64, expectedYs []float64, tol float64) { 95 for i, x := range xs { 96 y := p.Predict(x) 97 yErr := math.Abs(y - expectedYs[i]) 98 if yErr > tol { 99 if tol == 0 { 100 t.Errorf("unexpected Predict(%g) value: got: %g want: %g", x, y, expectedYs[i]) 101 } else { 102 t.Errorf("unexpected Predict(%g) value: got: %g want: %g with tolerance: %g", x, y, expectedYs[i], tol) 103 } 104 } 105 } 106 } 107 108 func TestPiecewiseLinearPredict(t *testing.T) { 109 t.Parallel() 110 xs := []float64{0, 1, 2} 111 ys := []float64{-0.5, 1.5, 1} 112 var pl PiecewiseLinear 113 err := pl.Fit(xs, ys) 114 if err != nil { 115 t.Errorf("Fit error: %s", err.Error()) 116 } 117 testInterpolatorPredict(t, pl, xs, ys, 0) 118 testInterpolatorPredict(t, pl, []float64{-0.4, 2.6}, []float64{-0.5, 1}, 0) 119 testInterpolatorPredict(t, pl, []float64{0.1, 0.5, 0.8, 1.2}, []float64{-0.3, 0.5, 1.1, 1.4}, 1e-15) 120 } 121 122 func BenchmarkNewPiecewiseLinear(b *testing.B) { 123 xs := []float64{0, 1.5, 3, 4.5, 6, 7.5, 9, 12, 13.5, 16.5} 124 ys := []float64{0, 1, 2, 2.5, 2, 1.5, 4, 10, -2, 2} 125 var pl PiecewiseLinear 126 for i := 0; i < b.N; i++ { 127 _ = pl.Fit(xs, ys) 128 } 129 } 130 131 func BenchmarkPiecewiseLinearPredict(b *testing.B) { 132 xs := []float64{0, 1.5, 3, 4.5, 6, 7.5, 9, 12, 13.5, 16.5} 133 ys := []float64{0, 1, 2, 2.5, 2, 1.5, 4, 10, -2, 2} 134 var pl PiecewiseLinear 135 _ = pl.Fit(xs, ys) 136 for i := 0; i < b.N; i++ { 137 pl.Predict(0) 138 pl.Predict(16.5) 139 pl.Predict(-2) 140 pl.Predict(4) 141 pl.Predict(7.32) 142 pl.Predict(9.0001) 143 pl.Predict(1.4) 144 pl.Predict(1.6) 145 pl.Predict(30) 146 pl.Predict(13.5) 147 pl.Predict(4.5) 148 } 149 } 150 151 func TestNewPiecewiseConstant(t *testing.T) { 152 var pc PiecewiseConstant 153 testPiecewiseInterpolatorCreation(t, &pc) 154 } 155 156 func benchmarkPiecewiseConstantPredict(b *testing.B) { 157 xs := []float64{0, 1.5, 3, 4.5, 6, 7.5, 9, 12, 13.5, 16.5} 158 ys := []float64{0, 1, 2, 2.5, 2, 1.5, 4, 10, -2, 2} 159 var pc PiecewiseConstant 160 _ = pc.Fit(xs, ys) 161 for i := 0; i < b.N; i++ { 162 pc.Predict(0) 163 pc.Predict(16.5) 164 pc.Predict(4) 165 pc.Predict(7.32) 166 pc.Predict(9.0001) 167 pc.Predict(1.4) 168 pc.Predict(1.6) 169 pc.Predict(13.5) 170 pc.Predict(4.5) 171 } 172 } 173 174 func BenchmarkPiecewiseConstantPredict(b *testing.B) { 175 benchmarkPiecewiseConstantPredict(b) 176 } 177 178 func TestPiecewiseConstantPredict(t *testing.T) { 179 t.Parallel() 180 xs := []float64{0, 1, 2} 181 ys := []float64{-0.5, 1.5, 1} 182 var pc PiecewiseConstant 183 err := pc.Fit(xs, ys) 184 if err != nil { 185 t.Errorf("Fit error: %s", err.Error()) 186 } 187 testInterpolatorPredict(t, pc, xs, ys, 0) 188 testXs := []float64{-0.9, 0.1, 0.5, 0.8, 1.2, 3.1} 189 leftYs := []float64{-0.5, 1.5, 1.5, 1.5, 1, 1} 190 testInterpolatorPredict(t, pc, testXs, leftYs, 0) 191 } 192 193 func TestCalculateSlopesErrors(t *testing.T) { 194 t.Parallel() 195 for _, test := range []struct { 196 xs, ys []float64 197 }{ 198 { 199 xs: []float64{0}, 200 ys: []float64{0}, 201 }, 202 { 203 xs: []float64{0, 1, 2}, 204 ys: []float64{0, 1}}, 205 { 206 xs: []float64{0, 0, 1}, 207 ys: []float64{0, 0, 0}, 208 }, 209 { 210 xs: []float64{0, 1, 0}, 211 ys: []float64{0, 0, 0}, 212 }, 213 } { 214 if !panics(func() { calculateSlopes(test.xs, test.ys) }) { 215 t.Errorf("expected panic for xs: %v and ys: %v", test.xs, test.ys) 216 } 217 } 218 } 219 220 func TestCalculateSlopes(t *testing.T) { 221 t.Parallel() 222 for i, test := range []struct { 223 xs, ys, want []float64 224 }{ 225 { 226 xs: []float64{0, 2, 3, 5}, 227 ys: []float64{0, 1, 1, -1}, 228 want: []float64{0.5, 0, -1}, 229 }, 230 { 231 xs: []float64{10, 20}, 232 ys: []float64{50, 100}, 233 want: []float64{5}, 234 }, 235 } { 236 got := calculateSlopes(test.xs, test.ys) 237 if !floats.EqualApprox(got, test.want, 1e-14) { 238 t.Errorf("Mismatch in calculated slopes in case %d: got %v, want %v", i, got, test.want) 239 } 240 } 241 } 242 243 func applyFunc(xs []float64, f func(x float64) float64) []float64 { 244 ys := make([]float64, len(xs)) 245 for i, x := range xs { 246 ys[i] = f(x) 247 } 248 return ys 249 } 250 251 func panics(fun func()) (b bool) { 252 defer func() { 253 err := recover() 254 if err != nil { 255 b = true 256 } 257 }() 258 fun() 259 return 260 } 261 262 func discrDerivPredict(p Predictor, x0, x1, x, h float64) float64 { 263 if x <= x0+h { 264 return (p.Predict(x+h) - p.Predict(x)) / h 265 } else if x >= x1-h { 266 return (p.Predict(x) - p.Predict(x-h)) / h 267 } else { 268 return (p.Predict(x+h) - p.Predict(x-h)) / (2 * h) 269 } 270 }