SparseFFT.jl

Sparse fast Fourier transforms in Julia

View the Project on GitHub klho/SparseFFT.jl

SparseFFT

Build Status Coverage Status codecov.io

This Julia package provides functions for computing sparse (or “pruned”) fast Fourier transforms (FFTs) in one (1D) and two (2D) dimensions. We say that an FFT is sparse if:

Sparse FFTs can be efficiently computed by, taking the first case for concreteness, essentially hand-unrolling a standard FFT decimation then selectively recombining to form only the required outputs. More specifically, write a 1D n-point transform as:

y[i] = sum([w(n)^((i-1)*(j-1))*x[j] for j in 1:n])

where w(p) = exp(-2im*pi/p) or exp(2im*pi/p) depending on the transform direction. Then to compute any set of k entries, assuming for simplicity that m = n/k is integral, use the identity:

y[k*(i1-1)+i2] =
    sum([w(m)^((i1-1)*(j2-1))
             * w(n)^((i2-1)*(j2-1))
             * sum([w(k)^((i2-1)*(j1-1))*x[m*(j1-1)+j2] for j1 = 1:k])
         for j2 = 1:m])

for i1 in 1:m and i2 in 1:k, i.e., do m FFTs of size k (over j1) then sum m terms (over j2) for each entry. If k does not divide n, then we replace it in the above with a divisor l < k. The dual sparse-to-full problem is similar, with both algorithms having O(n*log(l)) complexity. Sparse FFTs in 2D (and higher dimensions) can be handled via tensor 1D transforms.

For further details, see:

Note: An obvious optimization that we have not implemented here is to simply do the full FFT then subsample for transforms that are too small or not sufficiently sparse. This is especially important in higher dimensions where each individual dimension may not directly permit much savings.

Warning: This package is no longer actively maintained (nor is it really all that useful). Last updated for Julia 1.0.

Usage

SparseFFT follows the same organizational principle as FFTW, with separate planning and execution routines. The primary planning functions in 1D are:

which produce, respectively, plans for forward and backward transforms, with arguments:

Corresponding execution functions include:

which perform full-to-sparse (f2s) or sparse-to-full (s2f) transforms, as appropriate, using a precomputed plan. The arguments x and y are nominally complex but can also be real; real input x is simply upcasted to Complex type T for the underlying FFT, while real output y just takes the real part. The optional argument nb specifies a block size for handling a required transpose in a cache-friendly way.

Optimizations using real FFTs are available for the real-to-complex full-to-sparse and complex-to-full sparse-to-real cases. Planning routines:

where now T <: Real and idx for the backward transform must contain only nonredundant frequencies, i.e., up to index div(n,2) + 1. Execution routines:

In 2D, we have essentially the essentially the same interface, with the following exceptions:

Example

The following example computes a random 100 x 100 subset of the spectrum of a 1000 x 1000 field and compares it to naively computing the full FFT:

using SparseFFT
using FFTW, LinearAlgebra, Random

T = ComplexF64
n1 = 1000
n2 = 1000
k1 = 100
k2 = 100
idx1 = randperm(n1)[1:k1]
idx2 = randperm(n2)[1:k2]
x = rand(T, n1, n2)
y = rand(T, k1, k2)

@time f = fft(x)[idx1,idx2]
@time P = plan_spfft(T, n1, n2, idx1, idx2)
@time spfft_f2s!(y, P, x)

norm(f - y)/norm(f)

Sample output (with annotations):

  0.043323 seconds (72 allocations: 15.415 MB)     # fft
  0.000529 seconds (124 allocations: 3.252 MB)     # plan_spfft
  0.020511 seconds (6.61 k allocations: 4.012 MB)  # spfft_f2s!
2.568551894989442e-15                              # vecnorm