gonum.org/v1/gonum@v0.14.0/dsp/fourier/fourier_test.go (about) 1 // Copyright ©2018 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 fourier 6 7 import ( 8 "fmt" 9 "reflect" 10 "testing" 11 12 "golang.org/x/exp/rand" 13 14 "gonum.org/v1/gonum/floats" 15 ) 16 17 func TestFFT(t *testing.T) { 18 t.Parallel() 19 const tol = 1e-10 20 rnd := rand.New(rand.NewSource(1)) 21 t.Run("NewFFT", func(t *testing.T) { 22 for n := 1; n <= 200; n++ { 23 fft := NewFFT(n) 24 25 want := make([]float64, n) 26 for i := range want { 27 want[i] = rnd.Float64() 28 } 29 30 coeff := fft.Coefficients(nil, want) 31 got := fft.Sequence(nil, coeff) 32 floats.Scale(1/float64(n), got) 33 34 if !floats.EqualApprox(got, want, tol) { 35 t.Errorf("unexpected result for sequence(coefficients(x)) for length %d", n) 36 } 37 } 38 }) 39 t.Run("Reset FFT", func(t *testing.T) { 40 fft := NewFFT(1000) 41 for n := 1; n <= 2000; n++ { 42 fft.Reset(n) 43 44 want := make([]float64, n) 45 for i := range want { 46 want[i] = rnd.Float64() 47 } 48 49 coeff := fft.Coefficients(nil, want) 50 got := fft.Sequence(nil, coeff) 51 floats.Scale(1/float64(n), got) 52 53 if !floats.EqualApprox(got, want, tol) { 54 t.Errorf("unexpected result for sequence(coefficients(x)) for length %d", n) 55 } 56 } 57 }) 58 t.Run("known FFT", func(t *testing.T) { 59 // Values confirmed with reference to numpy rfft. 60 fft := NewFFT(1000) 61 cases := []struct { 62 in []float64 63 want []complex128 64 }{ 65 { 66 in: []float64{1, 0, 1, 0, 1, 0, 1, 0}, 67 want: []complex128{4, 0, 0, 0, 4}, 68 }, 69 { 70 in: []float64{1, 0, 1, 0, 1, 0, 1}, 71 want: []complex128{ 72 4, 73 0.5 + 0.24078730940376442i, 74 0.5 + 0.6269801688313512i, 75 0.5 + 2.190643133767413i, 76 }, 77 }, 78 { 79 in: []float64{1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0}, 80 want: []complex128{ 81 12, 82 -2.301937735804838 - 1.108554787638881i, 83 0.7469796037174659 + 0.9366827961047095i, 84 -0.9450418679126271 - 4.140498958131061i, 85 -0.9450418679126271 + 4.140498958131061i, 86 0.7469796037174659 - 0.9366827961047095i, 87 -2.301937735804838 + 1.108554787638881i, 88 12, 89 }, 90 }, 91 } 92 for _, test := range cases { 93 fft.Reset(len(test.in)) 94 got := fft.Coefficients(nil, test.in) 95 if !equalApprox(got, test.want, tol) { 96 t.Errorf("unexpected result for coefficients(%g):\ngot: %g\nwant:%g", 97 test.in, got, test.want) 98 } 99 } 100 }) 101 t.Run("Freq", func(t *testing.T) { 102 var fft FFT 103 cases := []struct { 104 n int 105 want []float64 106 }{ 107 {n: 1, want: []float64{0}}, 108 {n: 2, want: []float64{0, 0.5}}, 109 {n: 3, want: []float64{0, 1.0 / 3.0}}, 110 {n: 4, want: []float64{0, 0.25, 0.5}}, 111 } 112 for _, test := range cases { 113 fft.Reset(test.n) 114 for i, want := range test.want { 115 if got := fft.Freq(i); got != want { 116 t.Errorf("unexpected result for freq(%d) for length %d: got:%v want:%v", 117 i, test.n, got, want) 118 } 119 } 120 } 121 }) 122 } 123 124 func TestCmplxFFT(t *testing.T) { 125 const tol = 1e-12 126 rnd := rand.New(rand.NewSource(1)) 127 t.Run("NewFFT", func(t *testing.T) { 128 for n := 1; n <= 200; n++ { 129 fft := NewCmplxFFT(n) 130 131 want := make([]complex128, n) 132 for i := range want { 133 want[i] = complex(rnd.Float64(), rnd.Float64()) 134 } 135 136 coeff := fft.Coefficients(nil, want) 137 got := fft.Sequence(nil, coeff) 138 sf := complex(1/float64(n), 0) 139 for i := range got { 140 got[i] *= sf 141 } 142 143 if !equalApprox(got, want, tol) { 144 t.Errorf("unexpected result for complex sequence(coefficients(x)) for length %d", n) 145 } 146 } 147 }) 148 t.Run("Reset FFT", func(t *testing.T) { 149 fft := NewCmplxFFT(1000) 150 for n := 1; n <= 2000; n++ { 151 fft.Reset(n) 152 153 want := make([]complex128, n) 154 for i := range want { 155 want[i] = complex(rnd.Float64(), rnd.Float64()) 156 } 157 158 coeff := fft.Coefficients(nil, want) 159 got := fft.Sequence(nil, coeff) 160 sf := complex(1/float64(n), 0) 161 for i := range got { 162 got[i] *= sf 163 } 164 165 if !equalApprox(got, want, tol) { 166 t.Errorf("unexpected result for complex sequence(coefficients(x)) for length %d", n) 167 } 168 } 169 }) 170 t.Run("Freq", func(t *testing.T) { 171 var fft CmplxFFT 172 cases := []struct { 173 want []float64 174 }{ 175 {want: []float64{0}}, 176 {want: []float64{0, -0.5}}, 177 {want: []float64{0, 1.0 / 3.0, -1.0 / 3.0}}, 178 {want: []float64{0, 0.25, -0.5, -0.25}}, 179 } 180 for _, test := range cases { 181 fft.Reset(len(test.want)) 182 for i, want := range test.want { 183 if got := fft.Freq(i); got != want { 184 t.Errorf("unexpected result for freq(%d) for length %d: got:%v want:%v", 185 i, len(test.want), got, want) 186 } 187 } 188 } 189 }) 190 t.Run("Shift", func(t *testing.T) { 191 var fft CmplxFFT 192 cases := []struct { 193 index []int 194 want []int 195 }{ 196 {index: []int{0}, want: []int{0}}, 197 {index: []int{0, -1}, want: []int{-1, 0}}, 198 {index: []int{0, 1, -1}, want: []int{-1, 0, 1}}, 199 {index: []int{0, 1, -2, -1}, want: []int{-2, -1, 0, 1}}, 200 {index: []int{0, 1, 2, -2, -1}, want: []int{-2, -1, 0, 1, 2}}, 201 } 202 for _, test := range cases { 203 fft.Reset(len(test.index)) 204 got := make([]int, len(test.index)) 205 for i := range test.index { 206 got[i] = test.index[fft.ShiftIdx(i)] 207 su := fft.UnshiftIdx(fft.ShiftIdx(i)) 208 if su != i { 209 t.Errorf("unexpected result for unshift(shift(%d)) with length %d:\ngot: %d\nwant:%d", 210 i, len(test.index), su, i) 211 } 212 } 213 if !reflect.DeepEqual(got, test.want) { 214 t.Errorf("unexpected result for shift(%d):\ngot: %d\nwant:%d", 215 test.index, got, test.want) 216 } 217 } 218 }) 219 } 220 221 func TestDCT(t *testing.T) { 222 t.Parallel() 223 const tol = 1e-10 224 rnd := rand.New(rand.NewSource(1)) 225 t.Run("NewDCT", func(t *testing.T) { 226 for n := 2; n <= 200; n++ { 227 dct := NewDCT(n) 228 229 want := make([]float64, n) 230 for i := range want { 231 want[i] = rnd.Float64() 232 } 233 234 coeff := dct.Transform(nil, want) 235 got := dct.Transform(nil, coeff) 236 floats.Scale(1/float64(2*(n-1)), got) 237 238 if !floats.EqualApprox(got, want, tol) { 239 t.Errorf("unexpected result for transform(transform(x)) for length %d", n) 240 } 241 } 242 }) 243 t.Run("Reset DCT", func(t *testing.T) { 244 dct := NewDCT(1000) 245 for n := 2; n <= 2000; n++ { 246 dct.Reset(n) 247 248 want := make([]float64, n) 249 for i := range want { 250 want[i] = rnd.Float64() 251 } 252 253 coeff := dct.Transform(nil, want) 254 got := dct.Transform(nil, coeff) 255 floats.Scale(1/float64(2*(n-1)), got) 256 257 if !floats.EqualApprox(got, want, tol) { 258 t.Errorf("unexpected result for transform(transform(x)) for length %d", n) 259 } 260 } 261 }) 262 } 263 264 func TestDST(t *testing.T) { 265 t.Parallel() 266 const tol = 1e-10 267 rnd := rand.New(rand.NewSource(1)) 268 t.Run("NewDST", func(t *testing.T) { 269 for n := 1; n <= 200; n++ { 270 dst := NewDST(n) 271 272 want := make([]float64, n) 273 for i := range want { 274 want[i] = rnd.Float64() 275 } 276 277 coeff := dst.Transform(nil, want) 278 got := dst.Transform(nil, coeff) 279 floats.Scale(1/float64(2*(n+1)), got) 280 281 if !floats.EqualApprox(got, want, tol) { 282 t.Errorf("unexpected result for transform(transform(x)) for length %d", n) 283 } 284 } 285 }) 286 t.Run("Reset DST", func(t *testing.T) { 287 dst := NewDST(1000) 288 for n := 1; n <= 2000; n++ { 289 dst.Reset(n) 290 291 want := make([]float64, n) 292 for i := range want { 293 want[i] = rnd.Float64() 294 } 295 296 coeff := dst.Transform(nil, want) 297 got := dst.Transform(nil, coeff) 298 floats.Scale(1/float64(2*(n+1)), got) 299 300 if !floats.EqualApprox(got, want, tol) { 301 t.Errorf("unexpected result for transform(transform(x)) for length %d", n) 302 } 303 } 304 }) 305 } 306 307 func TestQuarterWaveFFT(t *testing.T) { 308 t.Parallel() 309 const tol = 1e-10 310 rnd := rand.New(rand.NewSource(1)) 311 t.Run("NewQuarterWaveFFT", func(t *testing.T) { 312 for n := 1; n <= 200; n++ { 313 qw := NewQuarterWaveFFT(n) 314 315 want := make([]float64, n) 316 for i := range want { 317 want[i] = rnd.Float64() 318 } 319 320 { 321 coeff := qw.CosCoefficients(nil, want) 322 got := qw.CosSequence(nil, coeff) 323 floats.Scale(1/float64(4*n), got) 324 325 if !floats.EqualApprox(got, want, tol) { 326 t.Errorf("unexpected result for cossequence(coscoefficient(x)) for length %d", n) 327 } 328 } 329 330 { 331 coeff := qw.SinCoefficients(nil, want) 332 got := qw.SinSequence(nil, coeff) 333 floats.Scale(1/float64(4*n), got) 334 335 if !floats.EqualApprox(got, want, tol) { 336 t.Errorf("unexpected result for sinsequence(sincoefficient(x)) for length %d", n) 337 } 338 } 339 } 340 }) 341 t.Run("Reset QuarterWaveFFT", func(t *testing.T) { 342 qw := NewQuarterWaveFFT(1000) 343 for n := 1; n <= 2000; n++ { 344 qw.Reset(n) 345 346 want := make([]float64, n) 347 for i := range want { 348 want[i] = rnd.Float64() 349 } 350 351 { 352 coeff := qw.CosCoefficients(nil, want) 353 got := qw.CosSequence(nil, coeff) 354 floats.Scale(1/float64(4*n), got) 355 356 if !floats.EqualApprox(got, want, tol) { 357 t.Errorf("unexpected result for cossequence(coscoefficient(x)) for length %d", n) 358 } 359 } 360 361 { 362 coeff := qw.SinCoefficients(nil, want) 363 got := qw.SinSequence(nil, coeff) 364 floats.Scale(1/float64(4*n), got) 365 366 if !floats.EqualApprox(got, want, tol) { 367 t.Errorf("unexpected result for sinsequence(sincoefficient(x)) for length %d", n) 368 } 369 } 370 } 371 }) 372 } 373 374 func equalApprox(a, b []complex128, tol float64) bool { 375 if len(a) != len(b) { 376 return false 377 } 378 ar := make([]float64, len(a)) 379 br := make([]float64, len(a)) 380 ai := make([]float64, len(a)) 381 bi := make([]float64, len(a)) 382 for i, cv := range a { 383 ar[i] = real(cv) 384 ai[i] = imag(cv) 385 } 386 for i, cv := range b { 387 br[i] = real(cv) 388 bi[i] = imag(cv) 389 } 390 return floats.EqualApprox(ar, br, tol) && floats.EqualApprox(ai, bi, tol) 391 } 392 393 func BenchmarkRealFFTCoefficients(b *testing.B) { 394 var sizes []int 395 for n := 16; n < 1<<24; n <<= 3 { 396 sizes = append(sizes, n) 397 } 398 sizes = append(sizes, 100, 4000, 1e6) 399 for _, n := range sizes { 400 fft := NewFFT(n) 401 seq := randFloats(n, rand.NewSource(1)) 402 dst := make([]complex128, n/2+1) 403 404 b.Run(fmt.Sprint(n), func(b *testing.B) { 405 for i := 0; i < b.N; i++ { 406 fft.Coefficients(dst, seq) 407 } 408 }) 409 } 410 } 411 412 func BenchmarkRealFFTSequence(b *testing.B) { 413 var sizes []int 414 for n := 16; n < 1<<24; n <<= 3 { 415 sizes = append(sizes, n) 416 } 417 sizes = append(sizes, 100, 4000, 1e6) 418 for _, n := range sizes { 419 fft := NewFFT(n) 420 coeff := randComplexes(n/2+1, rand.NewSource(1)) 421 dst := make([]float64, n) 422 423 b.Run(fmt.Sprint(n), func(b *testing.B) { 424 for i := 0; i < b.N; i++ { 425 fft.Sequence(dst, coeff) 426 } 427 }) 428 } 429 } 430 431 func BenchmarkCmplxFFTCoefficients(b *testing.B) { 432 var sizes []int 433 for n := 16; n < 1<<24; n <<= 3 { 434 sizes = append(sizes, n) 435 } 436 sizes = append(sizes, 100, 4000, 1e6) 437 for _, n := range sizes { 438 fft := NewCmplxFFT(n) 439 d := randComplexes(n, rand.NewSource(1)) 440 441 b.Run(fmt.Sprint(n), func(b *testing.B) { 442 for i := 0; i < b.N; i++ { 443 fft.Coefficients(d, d) 444 } 445 }) 446 } 447 } 448 449 func BenchmarkCmplxFFTSequence(b *testing.B) { 450 var sizes []int 451 for n := 16; n < 1<<24; n <<= 3 { 452 sizes = append(sizes, n) 453 } 454 sizes = append(sizes, 100, 4000, 1e6) 455 for _, n := range sizes { 456 fft := NewCmplxFFT(n) 457 d := randComplexes(n, rand.NewSource(1)) 458 459 b.Run(fmt.Sprint(n), func(b *testing.B) { 460 for i := 0; i < b.N; i++ { 461 fft.Sequence(d, d) 462 } 463 }) 464 } 465 } 466 467 func randFloats(n int, src rand.Source) []float64 { 468 rnd := rand.New(src) 469 f := make([]float64, n) 470 for i := range f { 471 f[i] = rnd.Float64() 472 } 473 return f 474 } 475 476 func randComplexes(n int, src rand.Source) []complex128 { 477 rnd := rand.New(src) 478 c := make([]complex128, n) 479 for i := range c { 480 c[i] = complex(rnd.Float64(), rnd.Float64()) 481 } 482 return c 483 }