├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── convolve.go ├── convolve_test.go ├── errors.go ├── errors_test.go ├── examples └── example.go ├── fft.go ├── fft_test.go ├── go.mod ├── go.sum ├── utils.go └── utils_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.11.x 5 | - 1.12.x 6 | 7 | env: 8 | - GO111MODULE=on 9 | 10 | script: 11 | - go build ./... 12 | - go test -v ./... 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Mark Canning 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gofft [![GoDoc][godoc-badge]][godoc] [![Build Status][travis-ci-badge]][travis-ci] [![Report Card][report-card-badge]][report-card] 2 | 3 | A better radix-2 fast Fourier transform in Go. 4 | 5 | Package gofft provides an efficient radix-2 fast discrete Fourier transformation algorithm in pure Go. 6 | 7 | This code is much faster than existing FFT implementations and uses no additional memory. 8 | 9 | The algorithm is non-recursive, works in-place overwriting the input array, and requires O(1) additional space. 10 | 11 | ## What 12 | 13 | I took [an existing](https://github.com/ktye/fft) FFT implementation in Go, cleaned and improved the code API and performance, and replaced the permutation step with an algorithm that works with no temp array. 14 | 15 | Performance was more than doubled over the original code, and is consistently the fastest Go FFT library (see benchmarks below) while remaining in pure Go. 16 | 17 | Added convolution functions `Convolve(x, y)`, `FastConvolve(x, y)`, `MultiConvolve(x...)`, `FastMultiConvolve(X)`, which implement the discrete convolution and a new hierarchical convolution algorithm that has utility in a number of CS problems. This computes the convolution of many arrays in $O(n log^2 n)$ run time, and in the case of FastMultiConvolve O(1) additional space. 18 | 19 | Also included new utility functions: `IsPow2`, `NextPow2`, `ZeroPad`, `ZeroPadToNextPow2`, `Float64ToComplex128Array`, `Complex128ToFloat64Array`, and `RoundFloat64Array` 20 | 21 | ## Why 22 | 23 | Most existing FFT libraries in Go allocate temporary arrays with O(N) additional space. This is less-than-ideal when you have arrays of length of $2^{25}$ or more, where you quickly end up allocating gigabytes of data and dragging down the FFT calculation to a halt. 24 | 25 | Additionally, the new convolution functions have significant utility for projects I've written or am planning. 26 | 27 | One downside is that the FFT is not multithreaded (like go-dsp is), so for large vector size FFTs on a multi-core machine it will be slower than it could be. FFTs can be run in parallel, however, so in the case of many FFT calls it will be faster. 28 | 29 | ## How 30 | 31 | ```go 32 | package main 33 | 34 | import ( 35 | "fmt" 36 | "github.com/argusdusty/gofft" 37 | ) 38 | 39 | func main() { 40 | // Do an FFT and IFFT and get the same result 41 | testArray := gofft.Float64ToComplex128Array([]float64{1, 2, 3, 4, 5, 6, 7, 8}) 42 | err := gofft.FFT(testArray) 43 | if err != nil { 44 | panic(err) 45 | } 46 | err = gofft.IFFT(testArray) 47 | if err != nil { 48 | panic(err) 49 | } 50 | result := gofft.Complex128ToFloat64Array(testArray) 51 | gofft.RoundFloat64Array(result) 52 | fmt.Println(result) 53 | 54 | // Do a discrete convolution of the testArray with itself 55 | testArray, err = gofft.Convolve(testArray, testArray) 56 | if err != nil { 57 | panic(err) 58 | } 59 | result = gofft.Complex128ToFloat64Array(testArray) 60 | gofft.RoundFloat64Array(result) 61 | fmt.Println(result) 62 | } 63 | ``` 64 | 65 | Outputs: 66 | 67 | ```text 68 | [1 2 3 4 5 6 7 8] 69 | [1 4 10 20 35 56 84 120 147 164 170 164 145 112 64] 70 | ``` 71 | 72 | ### Benchmarks 73 | 74 | ```text 75 | gofft>go test -bench=FFT$ -benchmem -cpu=1 -benchtime=5s 76 | goos: windows 77 | goarch: amd64 78 | pkg: github.com/argusdusty/gofft 79 | cpu: AMD Ryzen 9 5900X 12-Core Processor 80 | ``` 81 | 82 | | Algorithm | Size | Iterations | Time | Throughput | Memory | Allocs | 83 | |:---------------|:-------------------|:-----------|:-----------------|:--------------|:---------------|:-------------| 84 | | Naive | Tiny (4) | 43044590 | 149.1 ns/op | 429.29 MB/s | 64 B/op | 1 allocs/op | 85 | | Naive | Small (128) | 31022 | 199949 ns/op | 10.24 MB/s | 2048 B/op | 1 allocs/op | 86 | | Naive | Medium (4096) | 31 | 190195290 ns/op | 0.34 MB/s | 65536 B/op | 1 allocs/op | 87 | | ktye | Tiny (4) | 334951483 | 16.13 ns/op | 3968.29 MB/s | 0 B/op | 0 allocs/op | 88 | | ktye | Small (128) | 5627564 | 1064 ns/op | 1923.91 MB/s | 0 B/op | 0 allocs/op | 89 | | ktye | Medium (4096) | 98692 | 62663 ns/op | 1045.86 MB/s | 0 B/op | 0 allocs/op | 90 | | ktye | Large (131072) | 1436 | 4210853 ns/op | 498.03 MB/s | 0 B/op | 0 allocs/op | 91 | | mjibson/go-dsp | Tiny (4) | 1864420 | 2882 ns/op | 22.21 MB/s | 499 B/op | 13 allocs/op | 92 | | mjibson/go-dsp | Small (128) | 701971 | 8934 ns/op | 229.24 MB/s | 5572 B/op | 18 allocs/op | 93 | | mjibson/go-dsp | Medium (4096) | 33637 | 149844 ns/op | 437.36 MB/s | 164358 B/op | 23 allocs/op | 94 | | mjibson/go-dsp | Large (131072) | 993 | 6568056 ns/op | 319.30 MB/s | 5243432 B/op | 28 allocs/op | 95 | | mjibson/go-dsp | Huge (4194304) | 13 | 463206769 ns/op | 144.88 MB/s | 167772795 B/op | 33 allocs/op | 96 | | mjibson/go-dsp | Massive (16777216) | 3 | 2478991133 ns/op | 108.28 MB/s | 671089306 B/op | 35 allocs/op | 97 | | gonum | Tiny (4) | 123918710 | 50.12 ns/op | 1276.84 MB/s | 0 B/op | 0 allocs/op | 98 | | gonum | Small (128) | 2041134 | 2904 ns/op | 705.33 MB/s | 0 B/op | 0 allocs/op | 99 | | gonum | Medium (4096) | 42642 | 138597 ns/op | 472.85 MB/s | 0 B/op | 0 allocs/op | 100 | | gonum | Large (131072) | 870 | 6913295 ns/op | 303.35 MB/s | 0 B/op | 0 allocs/op | 101 | | gonum | Huge (4194304) | 15 | 359428827 ns/op | 186.71 MB/s | 0 B/op | 0 allocs/op | 102 | | gonum | Massive (16777216) | 4 | 1407001550 ns/op | 190.79 MB/s | 0 B/op | 0 allocs/op | 103 | | scientificgo | Tiny (4) | 66733696 | 77.52 ns/op | 825.58 MB/s | 128 B/op | 2 allocs/op | 104 | | scientificgo | Small (128) | 3675384 | 1653 ns/op | 1238.64 MB/s | 4096 B/op | 2 allocs/op | 105 | | scientificgo | Medium (4096) | 88891 | 60442 ns/op | 1084.28 MB/s | 131072 B/op | 2 allocs/op | 106 | | scientificgo | Large (131072) | 2526 | 2886025 ns/op | 726.66 MB/s | 4194304 B/op | 2 allocs/op | 107 | | scientificgo | Huge (4194304) | 26 | 228963535 ns/op | 293.10 MB/s | 134217728 B/op | 2 allocs/op | 108 | | scientificgo | Massive (16777216) | 4 | 1312125600 ns/op | 204.58 MB/s | 536870914 B/op | 2 allocs/op | 109 | | gofft | Tiny (4) | 1000000000 | 5.687 ns/op | 11253.23 MB/s | 0 B/op | 0 allocs/op | 110 | | gofft | Small (128) | 6283482 | 1037 ns/op | 1974.64 MB/s | 0 B/op | 0 allocs/op | 111 | | gofft | Medium (4096) | 120750 | 51886 ns/op | 1263.07 MB/s | 0 B/op | 0 allocs/op | 112 | | gofft | Large (131072) | 2596 | 2387875 ns/op | 878.25 MB/s | 0 B/op | 0 allocs/op | 113 | | gofft | Huge (4194304) | 27 | 265444115 ns/op | 252.82 MB/s | 0 B/op | 0 allocs/op | 114 | | gofft | Massive (16777216) | 5 | 1069123500 ns/op | 251.08 MB/s | 0 B/op | 0 allocs/op | 115 | 116 | [travis-ci-badge]: https://api.travis-ci.org/argusdusty/gofft.svg?branch=master 117 | [travis-ci]: https://api.travis-ci.org/argusdusty/gofft 118 | [godoc-badge]: https://godoc.org/github.com/argusdusty/gofft?status.svg 119 | [godoc]: https://godoc.org/github.com/argusdusty/gofft 120 | [report-card-badge]: https://goreportcard.com/badge/github.com/argusdusty/gofft 121 | [report-card]: https://goreportcard.com/report/github.com/argusdusty/gofft -------------------------------------------------------------------------------- /convolve.go: -------------------------------------------------------------------------------- 1 | package gofft 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | ) 7 | 8 | // Convolve computes the discrete convolution of x and y using FFT. 9 | // Pads x and y to the next power of 2 from len(x)+len(y)-1 10 | func Convolve(x, y []complex128) ([]complex128, error) { 11 | if len(x) == 0 && len(y) == 0 { 12 | return nil, nil 13 | } 14 | n := len(x) + len(y) - 1 15 | N := NextPow2(n) 16 | x = ZeroPad(x, N) 17 | y = ZeroPad(y, N) 18 | err := FastConvolve(x, y) 19 | return x[:n], err 20 | } 21 | 22 | // FastConvolve computes the discrete convolution of x and y using FFT 23 | // and stores the result in x, while erasing y (setting it to 0s). 24 | // Since this does no allocations, x and y are assumed to already be 0-padded 25 | // for at least half their length. 26 | func FastConvolve(x, y []complex128) error { 27 | if len(x) == 0 && len(y) == 0 { 28 | return nil 29 | } 30 | if err := checkZero("difference in FastConvolve input vectors length", len(x)-len(y)); err != nil { 31 | return err 32 | } 33 | if err := checkLength("FastConvolve input vector length", len(x)); err != nil { 34 | return err 35 | } 36 | convolve(x, y) 37 | return nil 38 | } 39 | 40 | // MultiConvolve computes the discrete convolution of many arrays using a 41 | // hierarchical FFT algorithm that successfully builds up larger convolutions. 42 | // This requires allocating up to 4*N extra memory for appropriate 0-padding 43 | // where N=sum(len(x) for x in X). 44 | // Takes O(N*log(N)^2) run time and O(N) additional space. 45 | // 46 | // This is much slower and takes many more allocations than FastMultiConvolve 47 | // below, but has a smart planner that handles disproportionate array sizes 48 | // very well. If all your arrays are the same length, FastMultiConvolve 49 | // will be much faster. 50 | func MultiConvolve(X ...[]complex128) ([]complex128, error) { 51 | if len(X) == 0 { 52 | return nil, nil 53 | } 54 | arraysByLength := map[int][][]complex128{} 55 | mx := 1 56 | returnLength := 1 57 | for _, x := range X { 58 | // Pad out each array to the next power of two after twice the length 59 | // Doubling the length gives a buffer-zone for convolve to write to 60 | n := NextPow2(2 * len(x)) 61 | arraysByLength[n] = append(arraysByLength[n], ZeroPad(x, n)) 62 | if n > mx { 63 | mx = n 64 | } 65 | returnLength += len(x) - 1 66 | } 67 | if returnLength <= 0 { 68 | return nil, nil 69 | } 70 | // For each successive power of 2, convolve the entries in pairs up to the 71 | // next power of 2 72 | for i := 1; ; i <<= 1 { 73 | arrays := arraysByLength[i] 74 | if len(arrays) > 0 { 75 | if len(arraysByLength) == 1 { 76 | return multiConvolveSingleLevel(arrays, returnLength) 77 | } 78 | for j := 0; j < len(arrays); j += 2 { 79 | if j+1 < len(arrays) { 80 | // For every pair, convolve to a single array 81 | convolve(arrays[j], arrays[j+1]) 82 | } 83 | // Pad out to the next power of 2 84 | arraysByLength[2*i] = append(arraysByLength[2*i], ZeroPad(arrays[j], 2*i)) 85 | } 86 | } 87 | // Trigger the garbage collector 88 | arraysByLength[i] = nil 89 | delete(arraysByLength, i) 90 | } 91 | } 92 | 93 | func multiConvolveSingleLevel(arrays [][]complex128, returnLength int) ([]complex128, error) { 94 | // If this is the final level, no need for further allocations, 95 | // just convolve together and return 96 | if len(arrays) == 2 { 97 | convolve(arrays[0], arrays[1]) 98 | return arrays[0][:returnLength], nil 99 | } 100 | if len(arrays) == 1 { 101 | return arrays[0][:returnLength], nil 102 | } 103 | // If everything has ended up on the same length, 104 | // just use FastMultiConvolve and return 105 | N := len(arrays[0]) 106 | n2 := NextPow2(len(arrays)) 107 | data := make([]complex128, n2*N) 108 | for j, array := range arrays { 109 | copy(data[N*j:], array) 110 | } 111 | for j := len(arrays); j < n2; j++ { 112 | data[N*j] = 1.0 113 | } 114 | err := FastMultiConvolve(data, N, false) 115 | return data[:returnLength], err 116 | } 117 | 118 | // FastMultiConvolve computes the discrete convolution of many arrays using a 119 | // hierarchical FFT algorithm, and stores the result in the first section of 120 | // the input, writing 0s to the remainder of the input 121 | // This does no allocations, so the arrays must first be 0-padded out to the 122 | // next power of 2 from sum of the lengths of the longest two arrays. 123 | // Additionally, the number of arrays must be a power of 2 124 | // X is the concatenated array of arrays, of length N (n*m) 125 | // n is the length of the 0-padded arrays. 126 | // multithread tells the algorithm to use goroutines, 127 | // which can slow things down for small N. 128 | // Takes O(N*log(N)^2) run time and O(1) additional space. 129 | func FastMultiConvolve(X []complex128, n int, multithread bool) error { 130 | if err := checkLength("Convolve single input array", n); err != nil { 131 | return err 132 | } 133 | N := len(X) 134 | if err := checkZero("FastMultiConvolve remainder", N%n); err != nil { 135 | return err 136 | } 137 | if err := checkLength("FastMultiConvolve number of input arrays", N/n); err != nil { 138 | return err 139 | } 140 | for ; n != N; n <<= 1 { 141 | n2 := n << 1 142 | if multithread { 143 | var wg sync.WaitGroup 144 | NumCPU := runtime.NumCPU() 145 | for j := 0; j < NumCPU; j++ { 146 | wg.Add(1) 147 | go func(s, e int) { 148 | defer wg.Done() 149 | for i := s; i < e; i += n2 { 150 | convolve(X[i:i+n], X[i+n:i+n2]) 151 | } 152 | }(n2*((j*N/n2)/NumCPU), n2*(((j+1)*N/n2)/NumCPU)) 153 | } 154 | wg.Wait() 155 | } else { 156 | for i := 0; i < N; i += n2 { 157 | convolve(X[i:i+n], X[i+n:i+n2]) 158 | } 159 | } 160 | } 161 | return nil 162 | } 163 | 164 | // convolve does the actual work of convolutions. 165 | func convolve(x, y []complex128) { 166 | fft(x) 167 | fft(y) 168 | for i := 0; i < len(x); i++ { 169 | x[i] *= y[i] 170 | y[i] = 0 171 | } 172 | ifft(x) 173 | } 174 | -------------------------------------------------------------------------------- /convolve_test.go: -------------------------------------------------------------------------------- 1 | package gofft 2 | 3 | import ( 4 | "math" 5 | "math/cmplx" 6 | "math/rand" 7 | "testing" 8 | ) 9 | 10 | func slowConvolve(x, y []complex128) []complex128 { 11 | if len(x) == 0 && len(y) == 0 { 12 | return nil 13 | } 14 | r := make([]complex128, len(x)+len(y)-1) 15 | for i := 0; i < len(x); i++ { 16 | for j := 0; j < len(y); j++ { 17 | r[i+j] += x[i] * y[j] 18 | } 19 | } 20 | return r 21 | } 22 | 23 | func TestConvolve(t *testing.T) { 24 | for i := 0; i < 64; i++ { 25 | x := complexRand(i) 26 | for j := 0; j < 64; j++ { 27 | y := complexRand(j) 28 | r1 := slowConvolve(x, y) 29 | r2, err := Convolve(x, y) 30 | if err != nil { 31 | t.Error(err) 32 | } 33 | if len(r1) != len(r2) { 34 | t.Errorf("slowConvolve and Convolve differ in length: len(r1)=%d, len(r2)=%d", len(r1), len(r2)) 35 | } 36 | for k := 0; k < i+j-1; k++ { 37 | if e := cmplx.Abs(r1[k] - r2[k]); e > 1e-9 { 38 | t.Errorf("slowConvolve and Convolve differ: r1[%d]=%v, r2[%d]=%v, diff=%v", k, r1[k], k, r2[k], e) 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | func TestFastConvolve(t *testing.T) { 46 | // Test FastConvolve of zero inputs returns nil 47 | x := complexRand(0) 48 | y := complexRand(0) 49 | err := FastConvolve(x, y) 50 | if err != nil { 51 | t.Errorf("FastConvolve on empty inputs returned error: %v", err) 52 | } 53 | // Test FastConvolve of non-powers of 2 returns InputSizeError 54 | err = FastConvolve(complexRand(17), complexRand(17)) 55 | checkIsInputSizeError(t, "FastConvolve(complexRand(17), complexRand(17))", err) 56 | err = FastConvolve(complexRand(4), complexRand(8)) 57 | checkIsInputSizeError(t, "FastConvolve(complexRand(4), complexRand(8))", err) 58 | // Test FastConvolve(x, y) == slowConvolve(x, y) 59 | for i := 1; i < 128; i++ { 60 | N := NextPow2(2 * i) 61 | x := complexRand(i) 62 | x = ZeroPad(x, N) 63 | y := complexRand(i) 64 | y = ZeroPad(y, N) 65 | r1 := slowConvolve(x, y) 66 | err := FastConvolve(x, y) 67 | if err != nil { 68 | t.Error(err) 69 | } 70 | for j := 0; j < 2*i-1; j++ { 71 | if e := cmplx.Abs(r1[j] - x[j]); e > 1e-9 { 72 | t.Errorf("slowConvolve and FastConvolve differ: r1[%d]=%v, x[%d]=%v, diff=%v", j, r1[j], j, x[j], e) 73 | } 74 | } 75 | for j := 2*i - 1; j < N; j++ { 76 | if e := cmplx.Abs(x[j]); e > 1e-9 { 77 | t.Errorf("FastConvolve failed to zero-pad x: got x[%d]=%v, expected x[%d]=%v, diff=%v", j, x[j], j, 0, e) 78 | } 79 | } 80 | for j := 0; j < N; j++ { 81 | if y[j] != 0 { 82 | t.Errorf("FastConvolve failed to erase y: got y[%d]=%v, expected y[%d]=%v", j, y[j], j, 0) 83 | } 84 | } 85 | } 86 | } 87 | 88 | func slowMultiConvolve(X [][]complex128) []complex128 { 89 | m := []complex128{1.0} 90 | for _, x := range X { 91 | m = slowConvolve(m, x) 92 | } 93 | return m 94 | } 95 | 96 | func TestMultiConvolve(t *testing.T) { 97 | // Test MultiConvolve() == nil 98 | x, err := MultiConvolve() 99 | if err != nil { 100 | t.Errorf("MultiConvolve() returned error: %v", err) 101 | } 102 | if len(x) != 0 { 103 | t.Errorf("MultiConvolve() returned non-empty result: %v", x) 104 | } 105 | // Test MultiConvolve(nil) == nil 106 | x, err = MultiConvolve(nil) 107 | if err != nil { 108 | t.Errorf("MultiConvolve(nil) returned error: %v", err) 109 | } 110 | if len(x) != 0 { 111 | t.Errorf("MultiConvolve(nil) returned non-empty result: %v", x) 112 | } 113 | // Test MultiConvolve(X...) == slowMultiConvolve(X) 114 | for i := 1; i < 25; i++ { 115 | X := make([][]complex128, i) 116 | for j := 1; j < 25; j++ { 117 | // Error propagates on the order of j^i 118 | errorThreshold := math.Pow(float64(j), float64(i)-1) * 1e-10 119 | // i arrays of length random(1, j) 120 | for k := 0; k < i; k++ { 121 | X[k] = complexRand(rand.Intn(j) + 1) 122 | } 123 | r1 := slowMultiConvolve(X) 124 | r2, err := MultiConvolve(X...) 125 | if err != nil { 126 | t.Error(err) 127 | } 128 | if len(r1) != len(r2) { 129 | t.Errorf("slowMultiConvolve and MultiConvolve differ in length: len(r1)=%d, len(r2)=%d", len(r1), len(r2)) 130 | } 131 | for k := 0; k < len(r1); k++ { 132 | if e := cmplx.Abs(r1[k] - r2[k]); e > errorThreshold { 133 | t.Errorf("slowMultiConvolve and MultiConvolve differ: r1[%d]=%v, r2[%d]=%v, diff=%v, i=%d, j=%d", k, r1[k], k, r2[k], e, i, j) 134 | } 135 | } 136 | } 137 | } 138 | } 139 | 140 | func TestFastMultiConvolve(t *testing.T) { 141 | // Test non-power of 2 number of arrays 142 | err := FastMultiConvolve(make([]complex128, 5), 4, false) 143 | checkIsInputSizeError(t, "FastMultiConvolve(make([]complex128, 5), 4, false)", err) 144 | err = FastMultiConvolve(make([]complex128, 4), 3, false) 145 | checkIsInputSizeError(t, "FastMultiConvolve(make([]complex128, 4), 3, false)", err) 146 | err = FastMultiConvolve(make([]complex128, 4), 8, false) 147 | checkIsInputSizeError(t, "FastMultiConvolve(make([]complex128, 4), 8, false)", err) 148 | err = FastMultiConvolve(make([]complex128, 12), 4, false) 149 | checkIsInputSizeError(t, "FastMultiConvolve(make([]complex128, 12), 4, false)", err) 150 | // Test FastMultiConvolve(X) == slowMultiConvolve(X) 151 | for i := 1; i < 25; i++ { 152 | X1 := make([][]complex128, i) 153 | n := NextPow2(i) 154 | for j := 1; j < 25; j++ { 155 | // Error propagates on the order of j^i 156 | errorThreshold := math.Pow(float64(j), float64(i)-1) * 1e-10 157 | // i arrays of length j 158 | m := NextPow2(2 * j) 159 | X2 := make([]complex128, n*m) 160 | for k := 0; k < i; k++ { 161 | X1[k] = complexRand(j) 162 | copy(X2[m*k:m*(k+1)], X1[k]) 163 | } 164 | for k := i; k < n; k++ { 165 | X2[m*k] = 1.0 166 | } 167 | X3 := make([]complex128, n*m) 168 | copy(X3, X2) 169 | r1 := slowMultiConvolve(X1) 170 | err := FastMultiConvolve(X2, m, false) 171 | if err != nil { 172 | t.Error(err) 173 | } 174 | err = FastMultiConvolve(X3, m, true) 175 | if err != nil { 176 | t.Error(err) 177 | } 178 | r2 := X2[:i*(j-1)+1] 179 | if len(r1) != len(r2) { 180 | t.Errorf("slowMultiConvolve and FastMultiConvolve differ in length: len(r1)=%d, len(r2)=%d", len(r1), len(r2)) 181 | } 182 | r3 := X3[:i*(j-1)+1] 183 | if len(r2) != len(r3) { 184 | t.Errorf("FastMultiConvolve multithreaded difference in length: len(r2)=%d, len(r3)=%d", len(r2), len(r3)) 185 | } 186 | for k := 0; k < len(r1); k++ { 187 | if e := cmplx.Abs(r1[k] - r2[k]); e > errorThreshold { 188 | t.Errorf("slowMultiConvolve and FastMultiConvolve differ: r1[%d]=%v, r2[%d]=%v, diff=%v, i=%d, j=%d", k, r1[k], k, r2[k], e, i, j) 189 | } 190 | if e := cmplx.Abs(r2[k] - r3[k]); e > errorThreshold { 191 | t.Errorf("FastMultiConvolve multithreaded difference: r2[%d]=%v, r3[%d]=%v, diff=%v, i=%d, j=%d", k, r2[k], k, r3[k], e, i, j) 192 | } 193 | } 194 | } 195 | } 196 | } 197 | 198 | func BenchmarkConvolve(b *testing.B) { 199 | for _, bm := range benchmarks { 200 | x := complexRand(bm.size) 201 | y := complexRand(bm.size) 202 | 203 | b.Run(bm.name, func(b *testing.B) { 204 | b.SetBytes(int64(bm.size * 32)) 205 | b.ResetTimer() 206 | for i := 0; i < b.N; i++ { 207 | Convolve(x, y) 208 | } 209 | }) 210 | } 211 | } 212 | 213 | func BenchmarkFastConvolve(b *testing.B) { 214 | for _, bm := range benchmarks { 215 | x := complexRand(bm.size) 216 | y := complexRand(bm.size) 217 | 218 | b.Run(bm.name, func(b *testing.B) { 219 | b.SetBytes(int64(bm.size * 32)) 220 | b.ResetTimer() 221 | for i := 0; i < b.N; i++ { 222 | FastConvolve(x, y) 223 | } 224 | }) 225 | } 226 | } 227 | 228 | var ( 229 | multiConvolveBenchmarks = []struct { 230 | size int 231 | number int 232 | name string 233 | }{ 234 | {4, 4, "Tiny (4, 4)"}, 235 | {4096, 4, "Small (4096, 4)"}, 236 | {131072, 4, "Medium-Tiny (131072, 4)"}, 237 | {4096, 128, "Medium-Small (4096, 4096)"}, 238 | {128, 4096, "Medium-Medium (128, 4096)"}, 239 | {4, 131072, "Medium-Large (4, 131072)"}, 240 | {100000, 5, "Large-Tiny (100000, 5)"}, 241 | {4000, 125, "Large-Small (4000, 125)"}, 242 | {125, 4000, "Large-Medium (125, 4000)"}, 243 | {5, 100000, "Large-Large (5, 100000)"}, 244 | } 245 | ) 246 | 247 | func BenchmarkMultiConvolve(b *testing.B) { 248 | for _, bm := range multiConvolveBenchmarks { 249 | x := make([][]complex128, bm.number) 250 | for i := 0; i < bm.number; i++ { 251 | x[i] = complexRand(bm.size) 252 | } 253 | 254 | b.Run(bm.name, func(b *testing.B) { 255 | b.SetBytes(int64(bm.size * bm.number * 16)) 256 | b.ResetTimer() 257 | for i := 0; i < b.N; i++ { 258 | MultiConvolve(x...) 259 | } 260 | }) 261 | } 262 | } 263 | 264 | func BenchmarkFastMultiConvolve(b *testing.B) { 265 | for _, bm := range multiConvolveBenchmarks { 266 | x := make([]complex128, 2*NextPow2(bm.size)*NextPow2(bm.number)) 267 | for i := 0; i < len(x); i += 2 * NextPow2(bm.size) { 268 | copy(x[i:], complexRand(NextPow2(bm.size))) 269 | } 270 | 271 | b.Run(bm.name, func(b *testing.B) { 272 | b.SetBytes(int64(bm.size * bm.number * 16)) 273 | b.ResetTimer() 274 | for i := 0; i < b.N; i++ { 275 | FastMultiConvolve(x, 2*NextPow2(bm.size), false) 276 | } 277 | }) 278 | } 279 | } 280 | 281 | func BenchmarkFastMultiConvolveParallel(b *testing.B) { 282 | for _, bm := range multiConvolveBenchmarks { 283 | x := make([]complex128, 2*NextPow2(bm.size)*NextPow2(bm.number)) 284 | for i := 0; i < len(x); i += 2 * NextPow2(bm.size) { 285 | copy(x[i:], complexRand(NextPow2(bm.size))) 286 | } 287 | 288 | b.Run(bm.name, func(b *testing.B) { 289 | b.SetBytes(int64(bm.size * bm.number * 16)) 290 | b.ResetTimer() 291 | for i := 0; i < b.N; i++ { 292 | err := FastMultiConvolve(x, 2*NextPow2(bm.size), true) 293 | if err != nil { 294 | b.Error(err) 295 | } 296 | } 297 | }) 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package gofft 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // InputSizeError represents an error when an input vector's size is not a power of 2. 8 | type InputSizeError struct { 9 | Context string 10 | Requirement string 11 | Size int 12 | } 13 | 14 | func (e *InputSizeError) Error() string { 15 | return fmt.Sprintf("Size of %s must be %s, is: %d", e.Context, e.Requirement, e.Size) 16 | } 17 | 18 | // checkLength checks that the length of x is a valid power of 2 19 | func checkLength(Context string, N int) error { 20 | if !IsPow2(N) { 21 | return &InputSizeError{Context: Context, Requirement: "power of 2", Size: N} 22 | } 23 | return nil 24 | } 25 | 26 | // checkLength checks that the length of x is a valid power of 2 27 | func checkZero(Context string, N int) error { 28 | if N != 0 { 29 | return &InputSizeError{Context: Context, Requirement: "zero", Size: N} 30 | } 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /errors_test.go: -------------------------------------------------------------------------------- 1 | package gofft 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestInputSizeError(t *testing.T) { 8 | e := &InputSizeError{"asdf", "qwer", 5} 9 | expect := "Size of asdf must be qwer, is: 5" 10 | got := e.Error() 11 | if expect != got { 12 | t.Errorf("InputSizeError.Error(), expected %s, got %s", expect, got) 13 | } 14 | } 15 | 16 | func checkIsInputSizeError(t *testing.T, context string, err error) { 17 | if err == nil { 18 | t.Errorf("%s didn't return error", context) 19 | } 20 | switch e := err.(type) { 21 | case *InputSizeError: 22 | default: 23 | t.Errorf("%s returned incorrect error type: %v", context, e) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/argusdusty/gofft" 6 | ) 7 | 8 | func main() { 9 | // Do an FFT and IFFT and get the same result 10 | testArray := gofft.Float64ToComplex128Array([]float64{1, 2, 3, 4, 5, 6, 7, 8}) 11 | err := gofft.FFT(testArray) 12 | if err != nil { 13 | panic(err) 14 | } 15 | err = gofft.IFFT(testArray) 16 | if err != nil { 17 | panic(err) 18 | } 19 | result := gofft.Complex128ToFloat64Array(testArray) 20 | gofft.RoundFloat64Array(result) 21 | fmt.Println(result) 22 | 23 | // Do a discrete convolution of the testArray with itself 24 | testArray, err = gofft.Convolve(testArray, testArray) 25 | if err != nil { 26 | panic(err) 27 | } 28 | result = gofft.Complex128ToFloat64Array(testArray) 29 | gofft.RoundFloat64Array(result) 30 | fmt.Println(result) 31 | } 32 | -------------------------------------------------------------------------------- /fft.go: -------------------------------------------------------------------------------- 1 | // Package gofft provides a fast discrete Fourier transformation algorithm. 2 | // 3 | // Implemented is the 1-dimensional DFT of complex input data 4 | // for with input lengths which are powers of 2. 5 | // 6 | // The algorithm is non-recursive, works in-place overwriting 7 | // the input array, and requires O(1) additional space. 8 | package gofft 9 | 10 | import ( 11 | "math/bits" 12 | "math/cmplx" 13 | ) 14 | 15 | // Prepare precomputes values used for FFT on a vector of length N. 16 | // N must be a perfect power of 2, otherwise this will return an error. 17 | // 18 | // Deprecated: This no longer has any functionality 19 | func Prepare(N int) error { 20 | return checkLength("FFT Input", N) 21 | } 22 | 23 | // FFT implements the fast Fourier transform. 24 | // This is done in-place (modifying the input array). 25 | // Requires O(1) additional memory. 26 | // len(x) must be a perfect power of 2, otherwise this will return an error. 27 | func FFT(x []complex128) error { 28 | if err := checkLength("FFT Input", len(x)); err != nil { 29 | return err 30 | } 31 | fft(x) 32 | return nil 33 | } 34 | 35 | // IFFT implements the inverse fast Fourier transform. 36 | // This is done in-place (modifying the input array). 37 | // Requires O(1) additional memory. 38 | // len(x) must be a perfect power of 2, otherwise this will return an error. 39 | func IFFT(x []complex128) error { 40 | if err := checkLength("IFFT Input", len(x)); err != nil { 41 | return err 42 | } 43 | ifft(x) 44 | return nil 45 | } 46 | 47 | // fft does the actual work for FFT 48 | func fft(x []complex128) { 49 | N := len(x) 50 | // Handle small N quickly 51 | switch N { 52 | case 1: 53 | return 54 | case 2: 55 | x[0], x[1] = x[0]+x[1], x[0]-x[1] 56 | return 57 | case 4: 58 | f := complex(imag(x[1])-imag(x[3]), real(x[3])-real(x[1])) 59 | x[0], x[1], x[2], x[3] = x[0]+x[1]+x[2]+x[3], x[0]-x[2]+f, x[0]-x[1]+x[2]-x[3], x[0]-x[2]-f 60 | return 61 | } 62 | // Reorder the input array. 63 | permute(x) 64 | // Butterfly 65 | // First 2 steps 66 | for i := 0; i < N; i += 4 { 67 | f := complex(imag(x[i+2])-imag(x[i+3]), real(x[i+3])-real(x[i+2])) 68 | x[i], x[i+1], x[i+2], x[i+3] = x[i]+x[i+1]+x[i+2]+x[i+3], x[i]-x[i+1]+f, x[i]-x[i+2]+x[i+1]-x[i+3], x[i]-x[i+1]-f 69 | } 70 | // Remaining steps 71 | w := complex(0, -1) 72 | for n := 4; n < N; n <<= 1 { 73 | w = cmplx.Sqrt(w) 74 | for o := 0; o < N; o += (n << 1) { 75 | wj := complex(1, 0) 76 | for k := 0; k < n; k++ { 77 | i := k + o 78 | f := wj * x[i+n] 79 | x[i], x[i+n] = x[i]+f, x[i]-f 80 | wj *= w 81 | } 82 | } 83 | } 84 | } 85 | 86 | // ifft does the actual work for IFFT 87 | func ifft(x []complex128) { 88 | N := len(x) 89 | // Reverse the input vector 90 | for i := 1; i < N/2; i++ { 91 | j := N - i 92 | x[i], x[j] = x[j], x[i] 93 | } 94 | 95 | // Do the transform. 96 | fft(x) 97 | 98 | // Scale the output by 1/N 99 | invN := complex(1.0/float64(N), 0) 100 | for i := 0; i < N; i++ { 101 | x[i] *= invN 102 | } 103 | } 104 | 105 | // permutate permutes the input vector using bit reversal. 106 | // Uses an in-place algorithm that runs in O(N) time and O(1) additional space. 107 | func permute(x []complex128) { 108 | N := len(x) 109 | // Handle small N quickly 110 | switch N { 111 | case 1, 2: 112 | return 113 | case 4: 114 | x[1], x[2] = x[2], x[1] 115 | return 116 | case 8: 117 | x[1], x[4] = x[4], x[1] 118 | x[3], x[6] = x[6], x[3] 119 | return 120 | } 121 | shift := 64 - uint64(bits.Len64(uint64(N-1))) 122 | N2 := N >> 1 123 | for i := 0; i < N; i += 2 { 124 | ind := int(bits.Reverse64(uint64(i)) >> shift) 125 | // Skip cases where low bit isn't set while high bit is 126 | // This eliminates 25% of iterations 127 | if i < N2 { 128 | if ind > i { 129 | x[i], x[ind] = x[ind], x[i] 130 | } 131 | } 132 | ind |= N2 // Fast way to get int(bits.Reverse64(uint64(i+1)) >> shift) here 133 | if ind > i+1 { 134 | x[i+1], x[ind] = x[ind], x[i+1] 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /fft_test.go: -------------------------------------------------------------------------------- 1 | package gofft 2 | 3 | import ( 4 | "math" 5 | "math/bits" 6 | "math/cmplx" 7 | "math/rand" 8 | "runtime" 9 | "sync/atomic" 10 | "testing" 11 | 12 | ktyefft "github.com/ktye/fft" 13 | dspfft "github.com/mjibson/go-dsp/fft" 14 | scientificfft "github.com/scientificgo/fft" 15 | gonumfft "gonum.org/v1/gonum/dsp/fourier" 16 | ) 17 | 18 | // Slow is the simplest and slowest FFT transform, for testing purposes 19 | func slowFFT(x []complex128) []complex128 { 20 | N := len(x) 21 | y := make([]complex128, N) 22 | for k := 0; k < N; k++ { 23 | for n := 0; n < N; n++ { 24 | phi := -2.0 * math.Pi * float64(k*n) / float64(N) 25 | s, c := math.Sincos(phi) 26 | y[k] += x[n] * complex(c, s) 27 | } 28 | } 29 | return y 30 | } 31 | 32 | func floatRand(N int) []float64 { 33 | x := make([]float64, N) 34 | for i := 0; i < N; i++ { 35 | x[i] = rand.NormFloat64() 36 | } 37 | return x 38 | } 39 | 40 | func complexRand(N int) []complex128 { 41 | x := make([]complex128, N) 42 | for i := 0; i < N; i++ { 43 | x[i] = complex(rand.NormFloat64(), rand.NormFloat64()) 44 | } 45 | return x 46 | } 47 | 48 | func copyVector(v []complex128) []complex128 { 49 | y := make([]complex128, len(v)) 50 | copy(y, v) 51 | return y 52 | } 53 | 54 | func TestPrepare(t *testing.T) { 55 | // Test Prepare of non-powers of 2 returns InputSizeError 56 | checkIsInputSizeError(t, "Prepare(17)", Prepare(17)) 57 | // Test Prepare for power of 2 up to 2^30 58 | for N := 2; N < (1 << 31); N <<= 1 { 59 | if err := Prepare(N); err != nil { 60 | t.Errorf("Prepare error on power of 2: %v", err) 61 | } 62 | } 63 | } 64 | 65 | func TestFFT(t *testing.T) { 66 | // Test FFT of non-powers of 2 returns InputSizeError 67 | checkIsInputSizeError(t, "FFT(complexRand(17))", FFT(complexRand(17))) 68 | // Test FFT(x) == slowFFT(x) for power of 2 up to 2^10 69 | for N := 2; N < (1 << 11); N <<= 1 { 70 | x := complexRand(N) 71 | 72 | y1 := slowFFT(copyVector(x)) 73 | y2 := copyVector(x) 74 | err := FFT(y2) 75 | if err != nil { 76 | t.Errorf("FFT error: %v", err) 77 | } 78 | for i := 0; i < N; i++ { 79 | if e := cmplx.Abs(y1[i] - y2[i]); e > 1e-9 { 80 | t.Errorf("slowFFT and FFT differ: i=%d N=%d y1[%d]=%v y2[%d]=%v diff=%v\n", i, N, i, y1[i], i, y2[i], e) 81 | } 82 | } 83 | } 84 | } 85 | 86 | func TestIFFT(t *testing.T) { 87 | // Test IFFT of non-powers of 2 returns InputSizeError 88 | checkIsInputSizeError(t, "IFFT(complexRand(17))", IFFT(complexRand(17))) 89 | // Test FFT(IFFT(x)) == x for power of 2 up to 2^10 90 | for N := 2; N < (1 << 11); N <<= 1 { 91 | x := complexRand(N) 92 | y := copyVector(x) 93 | err := FFT(y) 94 | if err != nil { 95 | t.Errorf("FFT error: %v", err) 96 | } 97 | err = IFFT(y) 98 | if err != nil { 99 | t.Errorf("IFFT error: %v", err) 100 | } 101 | for i := range x { 102 | if e := cmplx.Abs(x[i] - y[i]); e > 1e-9 { 103 | t.Errorf("inverse differs %d: %v %v\n", i, x[i], y[i]) 104 | } 105 | } 106 | } 107 | } 108 | 109 | func TestPermute(t *testing.T) { 110 | shift := uint64(64) 111 | for n := 1; n < (1 << 11); n <<= 1 { 112 | x := complexRand(n) 113 | y := make([]complex128, n) 114 | copy(y, x) 115 | permute(x) 116 | for i := 0; i < n; i++ { 117 | ind := int(bits.Reverse64(uint64(i)) >> shift) 118 | if x[i] != y[ind] { 119 | t.Errorf("%d expected: x[%d] = %v, got: %v\n", n, i, y[ind], x[i]) 120 | } 121 | } 122 | shift-- 123 | } 124 | } 125 | 126 | var ( 127 | benchmarks = []struct { 128 | size int 129 | name string 130 | }{ 131 | {4, "Tiny (4)"}, 132 | {128, "Small (128)"}, 133 | {4096, "Medium (4096)"}, 134 | {131072, "Large (131072)"}, 135 | {4194304, "Huge (4194304)"}, 136 | {16777216, "Massive (16777216)"}, 137 | } 138 | ) 139 | 140 | func BenchmarkSlowFFT(b *testing.B) { 141 | for _, bm := range benchmarks { 142 | if bm.size > 10000 { 143 | // Don't run sizes too big for slow 144 | continue 145 | } 146 | x := complexRand(bm.size) 147 | 148 | b.Run(bm.name, func(b *testing.B) { 149 | b.SetBytes(int64(bm.size * 16)) 150 | b.ResetTimer() 151 | for i := 0; i < b.N; i++ { 152 | slowFFT(x) 153 | } 154 | }) 155 | } 156 | } 157 | 158 | func BenchmarkKtyeFFT(b *testing.B) { 159 | for _, bm := range benchmarks { 160 | if bm.size > 1048576 { 161 | // Max size for ktye's fft 162 | continue 163 | } 164 | f, err := ktyefft.New(bm.size) 165 | if err != nil { 166 | b.Errorf("fft.New error: %v", err) 167 | } 168 | x := complexRand(bm.size) 169 | 170 | b.Run(bm.name, func(b *testing.B) { 171 | b.SetBytes(int64(bm.size * 16)) 172 | b.ResetTimer() 173 | for i := 0; i < b.N; i++ { 174 | f.Transform(x) 175 | } 176 | }) 177 | } 178 | } 179 | 180 | func BenchmarkGoDSPFFT(b *testing.B) { 181 | for _, bm := range benchmarks { 182 | dspfft.EnsureRadix2Factors(bm.size) 183 | x := complexRand(bm.size) 184 | 185 | b.Run(bm.name, func(b *testing.B) { 186 | b.SetBytes(int64(bm.size * 16)) 187 | b.ResetTimer() 188 | for i := 0; i < b.N; i++ { 189 | dspfft.FFT(x) 190 | } 191 | }) 192 | } 193 | } 194 | 195 | func BenchmarkGonumFFT(b *testing.B) { 196 | for _, bm := range benchmarks { 197 | fft := gonumfft.NewCmplxFFT(bm.size) 198 | x := complexRand(bm.size) 199 | 200 | b.Run(bm.name, func(b *testing.B) { 201 | b.SetBytes(int64(bm.size * 16)) 202 | b.ResetTimer() 203 | for i := 0; i < b.N; i++ { 204 | fft.Coefficients(x, x) 205 | } 206 | }) 207 | } 208 | } 209 | 210 | func BenchmarkScientificFFT(b *testing.B) { 211 | for _, bm := range benchmarks { 212 | x := complexRand(bm.size) 213 | 214 | b.Run(bm.name, func(b *testing.B) { 215 | b.SetBytes(int64(bm.size * 16)) 216 | b.ResetTimer() 217 | for i := 0; i < b.N; i++ { 218 | scientificfft.Fft(x, false) 219 | } 220 | }) 221 | } 222 | } 223 | 224 | func BenchmarkFFT(b *testing.B) { 225 | for _, bm := range benchmarks { 226 | x := complexRand(bm.size) 227 | 228 | b.Run(bm.name, func(b *testing.B) { 229 | b.SetBytes(int64(bm.size * 16)) 230 | b.ResetTimer() 231 | for i := 0; i < b.N; i++ { 232 | FFT(x) 233 | } 234 | }) 235 | } 236 | } 237 | 238 | func BenchmarkFFTParallel(b *testing.B) { 239 | for _, bm := range benchmarks { 240 | procs := runtime.GOMAXPROCS(0) 241 | x := complexRand(bm.size * procs) 242 | 243 | b.Run(bm.name, func(b *testing.B) { 244 | var idx uint64 245 | b.SetBytes(int64(bm.size * 16)) 246 | b.ResetTimer() 247 | b.RunParallel(func(pb *testing.PB) { 248 | i := int(atomic.AddUint64(&idx, 1) - 1) 249 | y := x[i*bm.size : (i+1)*bm.size] 250 | for pb.Next() { 251 | FFT(y) 252 | } 253 | }) 254 | }) 255 | } 256 | } 257 | 258 | func BenchmarkIFFT(b *testing.B) { 259 | for _, bm := range benchmarks { 260 | x := complexRand(bm.size) 261 | 262 | b.Run(bm.name, func(b *testing.B) { 263 | b.SetBytes(int64(bm.size * 16)) 264 | b.ResetTimer() 265 | for i := 0; i < b.N; i++ { 266 | IFFT(x) 267 | } 268 | }) 269 | } 270 | } 271 | 272 | func BenchmarkIFFTParallel(b *testing.B) { 273 | for _, bm := range benchmarks { 274 | procs := runtime.GOMAXPROCS(0) 275 | x := complexRand(bm.size * procs) 276 | 277 | b.Run(bm.name, func(b *testing.B) { 278 | var idx uint64 279 | b.SetBytes(int64(bm.size * 16)) 280 | b.ResetTimer() 281 | b.RunParallel(func(pb *testing.PB) { 282 | i := int(atomic.AddUint64(&idx, 1) - 1) 283 | y := x[i*bm.size : (i+1)*bm.size] 284 | for pb.Next() { 285 | IFFT(y) 286 | } 287 | }) 288 | }) 289 | } 290 | } 291 | 292 | func BenchmarkPermute(b *testing.B) { 293 | for _, bm := range benchmarks { 294 | x := complexRand(bm.size) 295 | b.Run(bm.name, func(b *testing.B) { 296 | b.SetBytes(int64(bm.size * 16)) 297 | b.ResetTimer() 298 | for i := 0; i < b.N; i++ { 299 | permute(x) 300 | } 301 | }) 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/argusdusty/gofft 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/ktye/fft v0.0.0-20230429065932-51351a1ec012 7 | github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12 8 | github.com/scientificgo/fft v0.0.2 9 | gonum.org/v1/gonum v0.13.0 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 2 | gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= 3 | git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= 4 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 5 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 6 | github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= 7 | github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= 8 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 9 | github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= 10 | github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= 11 | github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= 12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 14 | github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 15 | github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= 16 | github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= 17 | github.com/go-fonts/latin-modern v0.3.0/go.mod h1:ysEQXnuT/sCDOAONxC7ImeEDVINbltClhasMAqEtRK0= 18 | github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= 19 | github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= 20 | github.com/go-fonts/liberation v0.3.0/go.mod h1:jdJ+cqF+F4SUL2V+qxBth8fvBpBDS7yloUL5Fi8GTGY= 21 | github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= 22 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 23 | github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= 24 | github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= 25 | github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9/go.mod h1:gWuR/CrFDDeVRFQwHPvsv9soJVB/iqymhuZQuJ3a9OM= 26 | github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= 27 | github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= 28 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 29 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 30 | github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 31 | github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 32 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 33 | github.com/ktye/fft v0.0.0-20230429065932-51351a1ec012 h1:T0po9AjYF0jWmPBCI8zEKn2Vs5A00fa5eSblg2XIPb4= 34 | github.com/ktye/fft v0.0.0-20230429065932-51351a1ec012/go.mod h1:83kOI0RXxRVYkwvOLmYD1+RLELwVLUaqVbPPUmR92GQ= 35 | github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12 h1:dd7vnTDfjtwCETZDrRe+GPYNLA1jBtbZeyfyE8eZCyk= 36 | github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12/go.mod h1:i/KKcxEWEO8Yyl11DYafRPKOPVYTrhxiTRigjtEEXZU= 37 | github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= 38 | github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= 39 | github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= 40 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 41 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 42 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 43 | github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= 44 | github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= 45 | github.com/scientificgo/fft v0.0.2 h1:K6Wv+FhMnEYVNJ2MCsNm/UAyRNt3QayNOwZWAILee84= 46 | github.com/scientificgo/fft v0.0.2/go.mod h1:SlHsUoEbd3eNM1tk8QUgaSt+HJCn1Cxi6mBUwNfcgHc= 47 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 48 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 49 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 50 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 51 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 52 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 53 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 54 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 55 | golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= 56 | golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 57 | golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 58 | golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 59 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 60 | golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= 61 | golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= 62 | golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= 63 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 64 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 65 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 66 | golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 67 | golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 68 | golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 69 | golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 70 | golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 71 | golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 72 | golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= 73 | golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= 74 | golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= 75 | golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4= 76 | golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0= 77 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 78 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 79 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 80 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 81 | golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= 82 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 83 | golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 84 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 85 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 86 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 87 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 88 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 89 | golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= 90 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 91 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 92 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 93 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 94 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 95 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 96 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 97 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 98 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 99 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 100 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 101 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 102 | golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 103 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 104 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 105 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 106 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 107 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 108 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 109 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 110 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 111 | golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 112 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 113 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 114 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 115 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 116 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 117 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 118 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 119 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 120 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 121 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 122 | golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 123 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 124 | golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 125 | golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 126 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 127 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 128 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 129 | golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= 130 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 131 | golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= 132 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 133 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 134 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 135 | gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= 136 | gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= 137 | gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= 138 | gonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM= 139 | gonum.org/v1/gonum v0.13.0/go.mod h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU= 140 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= 141 | gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= 142 | gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= 143 | gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= 144 | honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= 145 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 146 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package gofft 2 | 3 | import ( 4 | "math" 5 | "math/bits" 6 | ) 7 | 8 | // IsPow2 returns true if N is a perfect power of 2 (1, 2, 4, 8, ...) and false otherwise. 9 | // Algorithm from: https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 10 | func IsPow2(N int) bool { 11 | if N == 0 { 12 | return false 13 | } 14 | return (uint64(N) & uint64(N-1)) == 0 15 | } 16 | 17 | // NextPow2 returns the smallest power of 2 >= N. 18 | func NextPow2(N int) int { 19 | if N == 0 { 20 | return 1 21 | } 22 | return 1 << uint64(bits.Len64(uint64(N-1))) 23 | } 24 | 25 | // ZeroPad pads x with 0s at the end into a new array of length N. 26 | // This does not alter x, and creates an entirely new array. 27 | // This should only be used as a convience function, and isn't meant for performance. 28 | // You should call this as few times as possible since it does potentially large allocations. 29 | func ZeroPad(x []complex128, N int) []complex128 { 30 | y := make([]complex128, N) 31 | copy(y, x) 32 | return y 33 | } 34 | 35 | // ZeroPadToNextPow2 pads x with 0s at the end into a new array of length 2^N >= len(x) 36 | // This does not alter x, and creates an entirely new array. 37 | // This should only be used as a convience function, and isn't meant for performance. 38 | // You should call this as few times as possible since it does potentially large allocations. 39 | func ZeroPadToNextPow2(x []complex128) []complex128 { 40 | N := NextPow2(len(x)) 41 | y := make([]complex128, N) 42 | copy(y, x) 43 | return y 44 | } 45 | 46 | // Float64ToComplex128Array converts a float64 array to the equivalent complex128 array 47 | // using an imaginary part of 0. 48 | func Float64ToComplex128Array(x []float64) []complex128 { 49 | y := make([]complex128, len(x)) 50 | for i, v := range x { 51 | y[i] = complex(v, 0) 52 | } 53 | return y 54 | } 55 | 56 | // Complex128ToFloat64Array converts a complex128 array to the equivalent float64 array 57 | // taking only the real part. 58 | func Complex128ToFloat64Array(x []complex128) []float64 { 59 | y := make([]float64, len(x)) 60 | for i, v := range x { 61 | y[i] = real(v) 62 | } 63 | return y 64 | } 65 | 66 | // RoundFloat64Array calls math.Round on each entry in x, changing the array in-place 67 | func RoundFloat64Array(x []float64) { 68 | for i, v := range x { 69 | x[i] = math.Round(v) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package gofft 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | "testing" 7 | ) 8 | 9 | func TestIsPow2(t *testing.T) { 10 | // 1. Test all powers of 2 up to 2^63 11 | for i := 0; i < 64; i++ { 12 | x := 1 << uint64(i) 13 | r := IsPow2(x) 14 | if r != true { 15 | t.Errorf("IsPow2(%d), got: %t, expected: %t", x, r, true) 16 | } 17 | } 18 | 19 | // 2. Test all non-powers of 2 up to 2^15 20 | n := 1 21 | for x := 0; x < (1 << 16); x++ { 22 | if x == n { 23 | n <<= 1 24 | continue 25 | } 26 | r := IsPow2(x) 27 | if r != false { 28 | t.Errorf("IsPow2(%d), got: %t, expected: %t", x, r, false) 29 | } 30 | } 31 | } 32 | 33 | func TestNextPow2(t *testing.T) { 34 | // 0. Test n=0 returns 1 35 | r := NextPow2(0) 36 | if r != 1 { 37 | t.Errorf("NextPow2(0), got: %d, expected: 1", r) 38 | } 39 | for i := 0; i < 63; i++ { 40 | // 1. Test all powers of 2 up to 2^62 41 | x := 1 << uint32(i) 42 | r := NextPow2(x) 43 | if r != x { 44 | t.Errorf("NextPow2(%d), got: %d, expected: %d", x, r, x) 45 | } 46 | // 2. Test powers of 2 plus one 47 | r = NextPow2(x + 1) 48 | if r != 2*x { 49 | t.Errorf("NextPow2(%d+1), got: %d, expected: %d", x, r, 2*x) 50 | } 51 | // 3. Test random number between here and next power of 2 52 | if x > 1 { 53 | n := rand.Intn(x-1) + 1 54 | r = NextPow2(x + n) 55 | if r != 2*x { 56 | t.Errorf("NextPow2(%d+%d), got: %d, expected: %d", x, n, r, 2*x) 57 | } 58 | } 59 | } 60 | } 61 | 62 | func checkZeroPadding(t *testing.T, x1, x2 []complex128, N1, N2 int) { 63 | if len(x1) != N1 { 64 | t.Errorf("ZeroPad old array length, got: %d, expected: %d", len(x1), N1) 65 | } 66 | if len(x2) != N2 { 67 | t.Errorf("ZeroPad new array length, got: %d, expected: %d", len(x2), N2) 68 | } 69 | for j := 0; j < N1; j++ { 70 | if x1[j] != x2[j] { 71 | t.Errorf("ZeroPad copied section, got: x2[j] = %v, expected: x2[j] = %v", x2[j], x1[j]) 72 | } 73 | } 74 | for j := N1; j < N2; j++ { 75 | if x2[j] != 0 { 76 | t.Errorf("ZeroPad padded section, got: x2[j] = %v, expected: x2[j] = %v", x2[j], 0) 77 | } 78 | } 79 | } 80 | 81 | func TestZeroPad(t *testing.T) { 82 | for i := 0; i < 100; i++ { 83 | // Test random lengths between 0 and 10000, and random paddings between 0 and 1000 84 | N1 := rand.Intn(10000) 85 | N2 := N1 + rand.Intn(1000) 86 | x1 := complexRand(N1) 87 | x2 := ZeroPad(x1, N2) 88 | checkZeroPadding(t, x1, x2, N1, N2) 89 | } 90 | } 91 | 92 | func TestZeroPadToNextPow2(t *testing.T) { 93 | // 0. Test n=0 returns [0] 94 | r := ZeroPadToNextPow2(nil) 95 | if len(r) != 1 { 96 | t.Errorf("len(ZeroPadToNextPow2(nil)), got: %d, expected: 1", len(r)) 97 | } 98 | for i := 0; i < 17; i++ { 99 | // 1. Test powers of 2 up to 2^16 100 | N1 := 1 << uint32(i) 101 | x1 := complexRand(N1) 102 | x2 := ZeroPadToNextPow2(x1) 103 | checkZeroPadding(t, x1, x2, N1, N1) 104 | // 2. Test powers of 2 plus one 105 | x1 = complexRand(N1 + 1) 106 | x2 = ZeroPadToNextPow2(x1) 107 | checkZeroPadding(t, x1, x2, N1+1, 2*N1) 108 | // 3. Test random number between here and next power of 2 109 | if N1 > 1 { 110 | n := rand.Intn(N1-1) + 1 111 | x1 = complexRand(N1 + n) 112 | x2 = ZeroPadToNextPow2(x1) 113 | checkZeroPadding(t, x1, x2, N1+n, 2*N1) 114 | } 115 | } 116 | } 117 | 118 | func TestFloat64ToComplex128Array(t *testing.T) { 119 | // Test random arrays of length 0 to 1000 120 | for i := 0; i < 1000; i++ { 121 | a := floatRand(i) 122 | b := Float64ToComplex128Array(a) 123 | if len(a) != len(b) { 124 | t.Errorf("Float64ToComplex128Array, got: len(b) = %v, expected: len(b) = %v", len(b), len(a)) 125 | } 126 | for j := 0; j < i; j++ { 127 | if a[j] != real(b[j]) { 128 | t.Errorf("Float64ToComplex128Array, got: real(b[j]) = %v, expected: real(b[j]) = %v", real(b[j]), a[j]) 129 | } 130 | if imag(b[j]) != 0 { 131 | t.Errorf("Float64ToComplex128Array, got: imag(b[j]) = %v, expected: imag(b[j]) = 0", imag(b[j])) 132 | } 133 | } 134 | } 135 | } 136 | 137 | func TestComplex128ToFloat64Array(t *testing.T) { 138 | // Test random arrays of length 0 to 1000 139 | for i := 0; i < 1000; i++ { 140 | a := complexRand(i) 141 | b := Complex128ToFloat64Array(a) 142 | if len(a) != len(b) { 143 | t.Errorf("Complex128ToFloat64Array, got: len(b) = %v, expected: len(b) = %v", len(b), len(a)) 144 | } 145 | for j := 0; j < i; j++ { 146 | if real(a[j]) != b[j] { 147 | t.Errorf("Complex128ToFloat64Array, got: b[j] = %v, expected: b[j] = %v", b[j], real(a[j])) 148 | } 149 | } 150 | } 151 | } 152 | 153 | func TestRoundFloat64Array(t *testing.T) { 154 | // Test random arrays of length 0 to 1000 155 | for i := 0; i < 1000; i++ { 156 | a := floatRand(i) 157 | b := make([]float64, i) 158 | copy(b, a) 159 | RoundFloat64Array(b) 160 | for j := 0; j < i; j++ { 161 | if math.Round(a[j]) != b[j] { 162 | t.Errorf("RoundFloat64Array, got: math.Round(a[j]) = %v, expected: math.Round(a[j]) = %v", math.Round(a[j]), b[j]) 163 | } 164 | } 165 | } 166 | } 167 | --------------------------------------------------------------------------------