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  }