Help us shape the future of Go! Take the 2026 Go Developer Survey

fft

package module
v0.28.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 27, 2026 License: BSD-3-Clause Imports: 8 Imported by: 0

README

go-fft/fft

fft — go-fft

Docs Playground License Go Status

A pure-Go (no cgo) FFT library — the numpy.fft / scipy.fft equivalent for Go. It computes the discrete Fourier transform of complex and real signals of any length, with no dependency on the native FFTW3 C library.

Try it without installing anything: the playground runs go-fft compiled to WebAssembly in your browser.

Ruby has no cgo-free FFT (every option wraps FFTW3); gonum/dsp/fourier is pure Go but its optimized assembly is amd64-only. This module is a portable Go core that runs on the six 64-bit Go targets (amd64, arm64, riscv64, loong64, ppc64le, s390x), with AVX2 and AVX-512 kernels on amd64 and NEON kernels on arm64, generated by go-asmgen and bit-identical to the Go code.

Use v0.26.3 or later. v0.25.0 to v0.26.2 return wrong N-D results, silently, on Intel CPUs with AVX-512 when an axis other than the last has a length 9·p (v0.26.3 release notes).

Status: released as v0.28.x (Go 1.27.1 or later). Complex and real transforms of any length (FFT/IFFT, RFFT/IRFFT, HFFT/IHFFT), the multi-dimensional transforms (FFT2/IFFT2, FFTN/IFFTN, RFFT2/IRFFT2, RFFTN/IRFFTN), numpy's n, norm and axes arguments (...With(Options)), opt-in fused multiply-add plans on amd64 (Options.FMA), FFTShift, NextFastLen, the DCT and DST of types I–IV, all of these in single precision too (FFT32, FFTN32, RFFT2_32, FFT32With, DCT32, Plan32, …), reusable and allocation-free plans (Plan, RealPlan, PlanN, RealPlan2, DCTPlan, DSTPlan), and the windowing and spectral helpers (windows, FFTFreq/RFFTFreq, PSD, Spectrogram). The transform is also exposed to Ruby through the go-embedded-ruby FFT module. See docs/plan-fft.md for the phased roadmap.

API

import "github.com/go-fft/fft"

X := fft.FFT(x)          // forward DFT of []complex128, any length
y := fft.IFFT(X)         // inverse DFT, normalized by N (IFFT(FFT(x)) ≈ x)
S := fft.FFTReal(r)      // forward DFT of a []float64 signal (full spectrum)

R := fft.RFFT(r)         // real-input DFT, non-redundant N/2+1 bins (numpy.fft.rfft)
z := fft.IRFFT(R, len(r)) // back to []float64 (numpy.fft.irfft)

// Multi-dimensional, on flat row-major (C-order) data plus an explicit shape:
F2 := fft.FFT2(data, [2]int{rows, cols})   // 2-D DFT (numpy.fft.fft2)
d2 := fft.IFFT2(F2, [2]int{rows, cols})    // inverse 2-D DFT (numpy.fft.ifft2)
FN := fft.FFTN(data, shape)                // N-D DFT (numpy.fft.fftn)
dN := fft.IFFTN(FN, shape)                 // inverse N-D DFT (numpy.fft.ifftn)

// Real image-style 2-D transforms (last axis keeps cols/2+1 bins per row):
RG := fft.RFFT2(img, [2]int{rows, cols})   // numpy.fft.rfft2
zG := fft.IRFFT2(RG, [2]int{rows, cols})   // numpy.fft.irfft2

// Windowing and spectral helpers:
w  := fft.Hann(n)             // also Hamming, Blackman, BlackmanHarris, Bartlett
f  := fft.FFTFreq(n, d)       // bin frequencies (numpy.fft.fftfreq)
rf := fft.RFFTFreq(n, d)      // real-FFT bin frequencies (numpy.fft.rfftfreq)
p  := fft.PSD(sig, d)         // one-sided power spectral density (periodogram)
S  := fft.Spectrogram(sig, segment, overlap, fft.Hann(segment), d) // PSD frames

// Reusable plans — precompute the twiddle tables once, amortize across calls
// (no per-call sin/cos). The convenience FFT/RFFT functions above use an
// internal per-length plan cache, so they get this for free too.
p   := fft.NewPlan(n)          // complex transform plan of length n
p.FFT(dst, src)               // dst and src are []complex128 of length n (may alias)
p.IFFT(dst, src)              // normalized inverse
rp  := fft.NewRealPlan(n)      // real-input transform plan
rp.RFFT(dst, src)             // src []float64 (len n), dst []complex128 (len n/2+1)
rp.IRFFT(out, spec)           // out []float64 (len n), spec the half spectrum

// N-D and real 2-D plans: write into your slice, allocate nothing in steady
// state. FFTN/FFT2/RFFT2 return a new slice each call; on a many-core host
// that allocation, not the FFT, dominates a small repeated transform.
pn  := fft.NewPlanN(rows, cols)      // any rank: NewPlanN(d0, d1, d2, ...)
pn.FFT(dst, src)                     // row-major, len = product of the shape (may alias)
pn.IFFT(dst, src)                    // normalized inverse (matches IFFTN)
r2  := fft.NewRealPlan2(rows, cols)  // real 2-D plan (matches RFFT2/IRFFT2)
r2.RFFT(spec, img)                   // spec len r2.SpectrumLen() = rows*(cols/2+1)
r2.IRFFT(img, spec)                  // normalized inverse

The multi-dimensional transforms are separable: the 1-D FFT is applied along each axis in turn. The forward transforms are unnormalized; the inverses divide by the product of the transformed axis lengths. The shape must be positive and its product must equal len(data), else the call panics (numpy semantics).

Empty input returns an empty slice; length 1 returns a copy. RFFT keeps only the lower N/2+1 bins because a real signal's spectrum is conjugate-symmetric (X[N-k] = conj(X[k])); IRFFT takes the target length n explicitly.

numpy's optional arguments: n, norm, axes

Every transform has a ...With variant taking an fft.Options, whose fields are numpy's keyword arguments and whose zero value is numpy's defaults — so FFTWith(x, fft.Options{}) is FFT(x) and a call names only what it changes. The plain functions keep their signatures. A field that does not apply (N on an N-D transform, Axes on a 1-D one) panics rather than being ignored.

o := fft.Options{
	N:    1024,           // numpy n: truncate or zero-pad the input (1-D only)
	Norm: fft.NormOrtho,  // numpy norm: NormBackward (zero value), NormOrtho, NormForward
	Axes: nil,            // numpy axes: nil = all; negative counts from the end (N-D only)
}

X := fft.FFTWith(x, fft.Options{N: 1024, Norm: fft.NormOrtho}) // numpy.fft.fft(x, n, norm=)
y := fft.IFFTWith(X, fft.Options{Norm: fft.NormOrtho})        // numpy.fft.ifft
R := fft.RFFTWith(r, fft.Options{N: 1024})                     // numpy.fft.rfft(r, n)
z := fft.IRFFTWith(R, fft.Options{})                           // numpy.fft.irfft: N=0 → 2*(len(R)-1)

// Hermitian-symmetric input (numpy.fft.hfft / ihfft), any Norm:
h := fft.HFFT(half, n)        // real spectrum of the signal whose first half is `half`
g := fft.IHFFT(h)             // back to the half signal; HFFTWith/IHFFTWith take Options

// N-D, with axes and norm (numpy.fft.fftn/ifftn/fft2/ifft2(axes=, norm=)):
F := fft.FFTNWith(data, shape, fft.Options{Axes: []int{0, 2}, Norm: fft.NormForward})
d := fft.IFFTNWith(F, shape, fft.Options{Axes: []int{0, 2}, Norm: fft.NormForward})
F2 := fft.FFT2With(img, [2]int{rows, cols}, fft.Options{Norm: fft.NormOrtho})

// Batched 1-D transforms: many signals of one length, one plan — numpy's
// fft(x, axis=-1) on a rows×n matrix. Rows run across goroutines when large.
B := fft.FFTNWith(signals, []int{rows, n}, fft.Options{Axes: []int{-1}})
RB := fft.RFFTNWith(realSignals, []int{rows, n}, fft.Options{Axes: []int{-1}})

// Real N-D (numpy.fft.rfftn/irfftn): the real transform runs along the LAST
// axis listed (the last axis by default), which keeps n/2+1 bins.
S := fft.RFFTN(vol, []int{d0, d1, d2})          // shape d0×d1×(d2/2+1)
v := fft.IRFFTN(S, []int{d0, d1, d2})           // the REAL output's shape
S2 := fft.RFFTNWith(vol, shape, fft.Options{Axes: []int{2, 0}, Norm: fft.NormOrtho})
v2 := fft.IRFFTNWith(S2, shape, fft.Options{Axes: []int{2, 0}, Norm: fft.NormOrtho})
// RFFT2With / IRFFT2With are the 2-D cases.

// Plans take the Norm per call (their length is fixed, so n and axes do not apply):
p.FFTNorm(dst, src, fft.NormOrtho)    // also Plan.IFFTNorm, RealPlan.RFFTNorm/IRFFTNorm,
pn.IFFTNorm(dst, src, fft.NormOrtho)  // PlanN.FFTNorm/IFFTNorm, RealPlan2.RFFTNorm/IRFFTNorm

// Zero frequency to the centre (numpy.fft.fftshift / ifftshift), generic over
// the element type:
c := fft.FFTShift(X)                          // also fft.FFTShift(fft.FFTFreq(n, d))
X = fft.IFFTShift(c)                          // inverse; differs from FFTShift for odd n
C := fft.FFTShiftN(F, shape, nil)             // every axis, or the listed ones
F = fft.IFFTShiftN(C, shape, nil)

// The length to zero-pad to (scipy.fft.next_fast_len):
m := fft.NextFastLen(len(x), false)  // smallest 7-smooth length >= len(x)
mr := fft.NextFastLen(len(r), true)  // smallest EVEN 7-smooth length (real transforms)

NextFastLen follows this library's engines, not scipy's: lengths 2^a·3^b·5^c·7^d run entirely on specialized radix-2/3/4/5/7/8 passes, while a factor 11 or 13 takes a general O(p²) pass and a larger prime goes to Rader/Bluestein — several times slower per point. A real transform of odd length cannot use the half-length packing, so the real rule also requires an even length.

Where this differs from numpy: an N of zero means "numpy's default" rather than an error, and a transform of zero points returns an empty slice (numpy raises); an axis listed twice is refused (numpy's fftn transforms it twice); IRFFTN/IRFFTNWith take the real output's shape instead of numpy's s, and the N-D transforms do not pad or crop axes (numpy's s argument).

Opt-in fused multiply-add: Options.FMA (v0.28.0)

By default every amd64 kernel is bit-identical to the package's Go code, so a result never depends on which CPU ran it. Options{FMA: true} trades that for speed on amd64 CPUs with FMA3: twiddle products and butterfly constants are fused, and on Cascade Lake class CPUs (AVX-512) 1,024 and 2,048 points take radix-32 passes that only the FMA plans have. Results differ from the default in the last bits and are as accurate: against long-double scipy on the same inputs, the error was 0.76–1.08× the default's over 90 transforms.

X := fft.FFTWith(x, fft.Options{FMA: true})  // every ...With function accepts it
p := fft.NewPlanWith(1024, fft.Options{FMA: true})
rp := fft.NewRealPlanWith(4096, fft.Options{FMA: true}) // also NewPlanNWith, NewRealPlan2With

When to use it: when the last bits may differ between machines and you want the speed. It is a request: elsewhere than amd64 with FMA3 (arm64, other architectures, a CPU or hypervisor without FMA3) you get the default transform, bit for bit; the float32 transforms ignore it.

What it gains (in-process against the default plan, one core; BENCHMARKS.md, Rounds 41 and 42):

Cascade Lake (AVX-512) Zen 3 (AVX2)
complex 1,024 1.22×, 1.19 → 0.94 × FFTW 1.00–1.04×, 1.13 → 1.10 × FFTW
complex 2,048 / RFFT 4,096 1.11× / 1.09× 1.03× / 1.03×
complex 256, 512 1.07–1.08× 1.06×
composites (1,000 … 1,920), 2-D 1.01–1.05× 1.01–1.05×
rows it does not speed up 4,096–16,384 (FMA off there: the default kernels) most rows ±2%

On those two CPUs the plan fuses only the passes measured faster fused, so no row measured slower than the default beyond noise (lowest 0.99). Other amd64 CPUs (Haswell, Zen 2/4) were not measured: there every pass fuses.

DCT and DST (scipy.fft.dct / dst)

The discrete cosine and sine transforms of types I–IV, forward and inverse, with scipy's definitions and its norm argument (fft.NormBackward, the default, fft.NormOrtho and fft.NormForward):

y := fft.DCT(x, 2, fft.NormOrtho)      // scipy.fft.dct(x, type=2, norm="ortho")
x2 := fft.IDCT(y, 2, fft.NormOrtho)    // scipy.fft.idct: IDCT(DCT(x)) == x for every norm
s := fft.DST(x, 1, fft.NormBackward)   // scipy.fft.dst(x, type=1)
x3 := fft.IDST(s, 1, fft.NormBackward) // scipy.fft.idst

// N-D over a row-major shape, every axis (scipy.fft.dctn / idctn / dstn / idstn):
Y := fft.DCTN(img, []int{rows, cols}, 2, fft.NormOrtho)

// Reusable plans, writing into your slice (dst may alias src):
dp := fft.NewDCTPlan(n, 2)             // also NewDSTPlan(n, typ)
dp.DCT(dst, src, fft.NormOrtho)
dp.IDCT(dst, src, fft.NormOrtho)

NormOrtho applies scipy's default orthogonalisation (orthogonalize=True), which makes every type an orthonormal matrix; the inverse of type 2 is type 3 and vice versa, types 1 and 4 are their own inverses. Every length is supported except those scipy rejects: DCT-I needs at least 2 points, the others 1. Each transform is O(N log N) on the package's real and complex FFTs: Makhoul's algorithm (one N-point real FFT) for DCT-II/III, a half-length complex FFT for even-length DCT-IV, a symmetric extension for DCT-I and DST-I, and index identities for the DSTs. The results are checked against scipy's own values (testdata/scipy_r2r.json).

Single precision (complex64 / float32)

As scipy.fft does for complex64 input, the 1-D transforms also run in single precision, complex64 in and complex64 out, for any length:

X := fft.FFT32(x)             // []complex64 → []complex64, unnormalized
y := fft.IFFT32(X)            // normalized by N
R := fft.RFFT32(r)            // []float32 → N/2+1 bins of []complex64
z := fft.IRFFT32(R, len(r))   // back to []float32

p := fft.NewPlan32(n)                  // reusable, safe for concurrent use
p.FFT(dst, src)                        // also p.IFFT
p.FFTNorm(dst, src, fft.NormOrtho)     // numpy/scipy norm=: NormBackward (default),
p.IFFTNorm(dst, src, fft.NormOrtho)    // NormOrtho, NormForward
rp := fft.NewRealPlan32(n)
rp.RFFT(dst, src)                      // also rp.IRFFT, rp.RFFTNorm, rp.IRFFTNorm

The multi-dimensional transforms, the numpy optional arguments and the DCT/DST have single-precision forms too, so the float32 surface mirrors the float64 one:

// N-D and 2-D (scipy.fft.fftn/ifftn/fft2/ifft2/rfftn/irfftn/rfft2/irfft2):
F := fft.FFTN32(data, shape)              // []complex64, row-major
d := fft.IFFTN32(F, shape)                // normalized by the product of the shape
F2 := fft.FFT2_32(img, [2]int{rows, cols}) // also IFFT2_32
S := fft.RFFTN32(vol, shape)              // []float32 → last axis keeps n/2+1 bins
v := fft.IRFFTN32(S, shape)               // the REAL output's shape
R2 := fft.RFFT2_32(img, [2]int{rows, cols}) // also IRFFT2_32

pn := fft.NewPlanN32(rows, cols)          // FFT, IFFT, FFTNorm, IFFTNorm
r2 := fft.NewRealPlan2_32(rows, cols)     // RFFT, IRFFT, RFFTNorm, IRFFTNorm

// numpy's n, norm and axes, with the same fft.Options:
X := fft.FFT32With(x, fft.Options{N: 1024, Norm: fft.NormOrtho}) // also IFFT32With
R := fft.RFFT32With(r, fft.Options{N: 1024})                     // also IRFFT32With
h := fft.HFFT32(half, n)                                         // HFFT32With, IHFFT32, IHFFT32With
B := fft.RFFTN32With(rows, []int{m, n}, fft.Options{Axes: []int{-1}}) // batched rows
// FFTN32With, IFFTN32With, FFT2_32With, IFFT2_32With, IRFFTN32With,
// RFFT2_32With, IRFFT2_32With likewise.

// DCT/DST types I–IV (scipy.fft.dct/idct/dst/idst/dctn/idctn/dstn/idstn):
y := fft.DCT32(x32, 2, fft.NormOrtho)     // also IDCT32, DST32, IDST32
Y := fft.DCTN32(img32, shape, 2, fft.NormOrtho) // also IDCTN32, DSTN32, IDSTN32
dp := fft.NewDCTPlan32(n, 2)              // dp.DCT/dp.IDCT; NewDSTPlan32 likewise

Each name is the float64 one with 32 appended; where that name already ends in a digit an underscore keeps the two numbers apart (FFT2_32, not FFT232), as the standard library's crypto/sha512.Sum512_256 does.

The precision follows scipy.fft, which keeps float32 and complex64 (as numpy.fft does since NumPy 2.0). The arithmetic is float32; twiddles, the Rader/Bluestein kernels and the DCT/DST twiddles are computed in float64 and rounded once, and a Norm factor is rounded once and applied in one pass. The error is within eps32·log2(N) of the exact transform, relative to the vector's 2-norm (eps32 = 2⁻²³; N is the number of points combined: the product of the transformed axes, or the DCT/DST's logical size), on every length and shape tested, and the results agree with scipy's own float32 ones (testdata/scipy_f32.json). On amd64 with AVX2 and on arm64, the float32 Stockham passes of radix 2, 3, 4, 5 and 8 run on SIMD kernels, four complex64 per register, bit-identical to the Go passes (BENCHMARKS.md, Round 22).

Performance

How a length is transformed.

  • Lengths whose prime factors are all ≤ 13 go through an iterative mixed-radix Stockham engine: radix-8/4/2/3/5/7 straight-line passes plus a general radix-p pass for 11 and 13.
  • A prime whose N−1 is 7-smooth uses Rader's algorithm; every other length uses Bluestein's chirp-z. Both convolve on the Stockham engine.
  • Powers of two on loong64, s390x and amd64 without AVX2, and above 65536 on riscv64, take an iterative, cache-blocked radix-4 kernel instead (SSE2 butterflies on amd64).
  • Real transforms run a half-length complex transform and one untangle pass.
  • N-D transforms run the 1-D transform along each axis, spread across cores above a work threshold. On amd64 with AVX2, an axis other than the last runs as batched passes over strips of 16 or 32 neighbouring lines, read and written where they lie in the array, instead of gathering each line.

Twiddle factors are computed once per length and cached.

SIMD. On amd64 the Stockham passes run as generated AVX2 kernels, and powers of two of 256 points or more as AVX-512 kernels where the CPU and the OS allow it; the real-FFT untangle has an AVX2 kernel too. They keep the Go code's arithmetic order (separately rounded multiplies and adds, no FMA), so their results are bit-identical to the Go passes, and the tests compare them bit for bit on generic, ±0 and ∞ inputs. The opt-in FMA plans (Options.FMA, above) run fused twins of these kernels, each bit-identical to a Go reference written with math.FMA. On arm64 the radix-2/3/4/5/8 passes, and the radix-2 to 5 final passes over an even number of blocks, run as generated NEON kernels. gc fuses multiply-adds on arm64, so each kernel reproduces, pass by pass, which product the Go code rounds and which it fuses; the results equal the Go code's bit for bit, except the bits of a NaN produced from infinite inputs, which IEEE 754 leaves unspecified (on amd64 too, where a kernel may add two NaNs in the other order and propagate the other one). On riscv64, loong64, ppc64le and s390x the passes are Go code, compiled to scalar instructions (gc does not vectorize; it fuses multiply-adds on all of them). Complex-multiply kernels for riscv64 (RVV) and s390x exist and are validated bit-identical, but are not used: the scalar loop measured as fast.

Against FFTW, numpy and scipy, on three GCC Compile Farm hosts, each with the newest parity runs of the code its paths run in v0.27.0 (BENCHMARKS.md, "Current numbers", all 2026-10-11): Neoverse-N1 Round 40 (the v0.26.1 tag; arm64 has not changed since), Cascade Lake Round 39 (v0.27.0), Zen 3 Round 36 (v0.26.0). v0.24.0 changed only Haswell's and arm64's paths, v0.25.0, v0.26.3 and v0.27.0 Intel AVX-512's, v0.26.0 only paths taken off Intel; the exceptions are listed in BENCHMARKS.md. Every row on one pinned core. FFTW's own times move by up to 47% between runs on the same host, so go-fft's time is the mean of two runs (each the median of its repetitions) and FFTW's the median over the round's runs (four; two on Neoverse-N1). go-fft time ÷ FFTW time (below 1 means go-fft is faster):

transform AMD EPYC 7773X (Zen 3, AVX2) Neoverse-N1 (arm64, NEON) Xeon Cascade Lake (AVX-512)
complex 256 1.10 0.88 1.052
complex 1,024 1.13 0.77 1.19
complex 4,096 0.89 0.59 0.94
complex 65,536 0.87 0.58 1.08
complex 1,048,576 0.51 0.47 0.70
complex 1,000 (2³·5³) 0.986 0.85 1.10
complex 1,296 (2⁴·3⁴) 0.91 0.77 1.10
complex 1,009 (prime, Rader) 0.61 0.45 0.59
complex 10,007 (prime, Bluestein) 0.82 0.69 0.94
RFFT 256 1.051 0.998 0.94
RFFT 4,096 1.09 0.72 1.049
RFFT 1,048,576 0.78 0.56 1.09
2-D 64×64 0.89 0.79 0.94
2-D 128×128 0.83 0.66 0.95
2-D 512×512 0.991 0.48 0.88
2-D 1024×1024 0.96 0.42 0.83

Bold: at or above FFTW, which here includes rows up to 1.05, slower than FFTW by at most 5%.

  • At or above FFTW on 24 of the 24 rows of the full sweep on Neoverse-N1 (faster on all 24), 18 on Zen 3 (faster on 17) and 15 on Cascade Lake (faster on 14, among them all five 2-D rows). The rows counted but still slower than FFTW: RFFT 1,024 (1.047) on Zen 3; RFFT 4,096 (1.049) on Cascade Lake. None on Neoverse-N1, whose closest rows are RFFT 256 (0.998) and RFFT 1,080 (0.994).
  • At or above numpy.fft and scipy.fft on all 24 rows, in both runs, on every host.
  • Where FFTW still leads: on Zen 3 complex 256 and 1,024 (1.10×, 1.13×), complex 1,080 (1.08×) and RFFT 256, 4,096 and 1,080 (1.05–1.09×); on Cascade Lake complex 256 (1.052×), 1,024 (1.19×), 65,536 (1.08×), the composites 1,000, 1,080, 1,920 and 1,296 (1.10–1.13×), RFFT 65,536 (1.09×) and RFFT 2^20 (1.09×). That last row is noise: go-fft read 7.97 and 10.49 ms in its two runs, and Round 39 does not touch its path.
  • Haswell (no FFTW on the host): v0.21.0 runs complex 512–2^18 1.03–1.21× and the composites 1000–6000 1.07–1.21× faster than v0.19.1 (Round 29); v0.24.0 runs 2-D 64×1024 … 1024² 1.04–1.06× and complex 6,144 and 10,240 1.20× faster than v0.23.1 (Round 37), which also measured that v0.22.x cost RFFT 256 about 1.5%. v0.25.0 removes the call responsible; that has not been re-measured on Haswell. Not measured: Apple M4 with the current engines, and loong64 (no host reachable).
  • Against gonum/dsp/fourier, the pure-Go peer (both CGO_ENABLED=0), go-fft is faster on every row measured (19/19 on each host), by the widest margin on primes: gonum's arbitrary-length path is a naive Bluestein with no Rader.

Every size, GFLOP/s, the methodology and the dated optimization rounds (what was tried, kept or dropped, and why) are in BENCHMARKS.md. The raw runs are under benchmarks/results/. Reproduce them with benchmarks/run.sh, which gates on correctness first: every transform must match numpy.fft within rtol=1e-9. gonum lives in the separate benchmarks/ module, so the library itself has no dependencies.

Why not cgo / FFTW?

FFTW3 is a C library: binding it reintroduces a C toolchain, cross-compilation pain, and a non-Go build. A pure-Go implementation cross-compiles to every Go target for free and is CGO_ENABLED=0 clean — which is what makes it usable as the FFT backend for an embedded Ruby (go-embedded-ruby) and for the wider go-* ecosystem.

Security

Report a vulnerability privately through GitHub's Report a vulnerability form. SECURITY.md lists what deserves a report, where the code steps outside Go's memory safety, and the findings of the 2026-10-04 audit.

License

BSD-3-Clause. See LICENSE.

Documentation

Overview

Package fft is a pure-Go (cgo-free) fast Fourier transform library — the numpy.fft / scipy.fft equivalent for Go.

It computes the discrete Fourier transform (DFT) of complex and real signals of any length, with no dependency on the native FFTW3 C library. A length whose prime factors are all small uses an iterative mixed-radix (Stockham) engine (radix-8/4/2/3/5/7 straight-line passes plus a general radix-p pass for 11/13), whose radix-2/3/4/5/8 passes run as AVX2 (or, for powers of two, AVX-512) kernels on amd64, bit-identical to the Go passes; a power of two on loong64/s390x, above 65536 on riscv64, or on amd64 without AVX2, uses an iterative radix-4 kernel instead (with SSE2 butterflies on amd64). A prime whose N-1 is 7-smooth uses Rader's algorithm and any other length Bluestein's chirp-z algorithm, so any length transforms correctly and fast. Twiddle factors are precomputed and cached per length (see Plan / NewPlan), so repeated transforms of one length recompute no sin/cos.

The multi-dimensional transforms (FFT2/FFTN and their real and inverse forms) are separable: they apply a 1-D transform along each axis, and the independent lines of a large axis run in parallel across goroutines — a multicore path single-threaded references such as pocketfft cannot take. NewPlanN and NewRealPlan2 build reusable N-D plans that write into the caller's slice and allocate nothing in steady state. See BENCHMARKS.md for the head-to-head benchmarks against FFTW, numpy.fft and scipy.fft.

The forward transform follows the unnormalized convention

X[k] = sum_{n=0}^{N-1} x[n] * exp(-2πi·k·n/N)

and the inverse divides by N, so IFFT(FFT(x)) ≈ x to floating-point tolerance.

RFFT returns only the non-redundant N/2+1 bins of a real signal's spectrum (numpy.fft.rfft) and IRFFT inverts it back to a real signal of a caller-specified length (numpy.fft.irfft). See docs/plan-fft.md for the roadmap.

Fused multiply-add (Options.FMA)

By default every amd64 kernel is bit-identical to the package's Go passes compiled without fused multiply-add, so a result does not depend on which kernel ran. Options{FMA: true} (FFTWith and the other ...With functions, NewPlanWith, NewRealPlanWith, NewPlanNWith, NewRealPlan2With) gives that up for speed on amd64 CPUs with FMA3: every twiddle product between passes, the √2/2 products of the radix-8 butterfly and the internal twiddles of the radix-9 and radix-16 butterflies are fused, as FFTW's FMA codelets fuse them. The results then differ from the default's in the last bits, and are as accurate: against scipy.fft in long double on the same inputs, the relative RMS error of 90 transforms (complex 16 to 2^20, composites, Rader and Bluestein primes, real, inverse and 2-D) was 0.90 to 1.04 times the default's, 1.1e-15 at most (BENCHMARKS.md, Round 41). The float32 transforms ignore the option; elsewhere than amd64 with FMA3 it builds the default plans. Each FMA kernel is bit-identical to a Go reference written with math.FMA, and FMA plans are cached apart from the default ones.

An FMA plan fuses only where fusing was measured to pay (Round 42): on Intel CPUs with the AVX-512 split layout (Cascade Lake) and on AMD CPUs with the AVX2 one (Zen 3), a table gives, per length, the passes that fuse (the others run the default kernels) and, on the former, radix-32 passes for 1,024 and 2,048 points, which only FMA plans have. Elsewhere every pass fuses. A transform of a length the table leaves unfused is the default transform, bit for bit.

Example (RealPlan2_32)

A RealPlan2_32 is the plan behind RFFT2_32 and IRFFT2_32.

p := fft.NewRealPlan2_32(2, 2)
S := p.RFFT(make([]complex64, p.SpectrumLen()), []float32{1, 2, 3, 4})
fmt.Println(tidy(S))
fmt.Println(p.IRFFT(make([]float32, 4), S))
Output:
[(10+0i) (-2+0i) (-4+0i) (0+0i)]
[1 2 3 4]
Example (Rfft2_32)

RFFT2_32 keeps cols/2+1 bins per row of a float32 image; IRFFT2_32 takes the image's shape back. (A package-level example: go vet reads a name like ExampleRFFT2_32 as an example of a method "32" of RFFT2.)

img := []float32{
	1, 2, 3, 4,
	5, 6, 7, 8,
}
S := fft.RFFT2_32(img, [2]int{2, 4}) // 2×3 bins
fmt.Println(tidy(S))
fmt.Println(fft.IRFFT2_32(S, [2]int{2, 4}))
Output:
[(36+0i) (-4+4i) (-4+0i) (-16+0i) (0+0i) (0+0i)]
[1 2 3 4 5 6 7 8]

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Bartlett

func Bartlett(n int) []float64

Bartlett returns the length-n Bartlett (triangular) window:

w[k] = (2/(n-1)) · ((n-1)/2 - |k - (n-1)/2|),  k = 0 .. n-1,

which rises linearly from 0 to 1 at the center and back to 0. This matches numpy.bartlett(n).

func Blackman

func Blackman(n int) []float64

Blackman returns the length-n Blackman window:

w[k] = 0.42 - 0.5·cos(2π·k/(n-1)) + 0.08·cos(4π·k/(n-1)).

This matches numpy.blackman(n).

func BlackmanHarris

func BlackmanHarris(n int) []float64

BlackmanHarris returns the length-n (4-term) Blackman–Harris window with the standard coefficients a0..a3 = 0.35875, 0.48829, 0.14128, 0.01168:

w[k] = a0 - a1·cos(2π·k/(n-1)) + a2·cos(4π·k/(n-1)) - a3·cos(6π·k/(n-1)).

This matches scipy.signal.windows.blackmanharris(n, sym=True).

func DCT added in v0.5.0

func DCT(x []float64, typ int, norm Norm) []float64

DCT returns the discrete cosine transform of type typ (1, 2, 3 or 4) of x, scaled by norm, as scipy.fft.dct(x, type=typ, norm=norm). The definitions are those of scipy (see the DCTPlan methods); NormOrtho also applies scipy's default orthogonalisation. x is not modified.

It panics if typ is not 1..4, if len(x) is below the type's minimum (2 for DCT-I, 1 otherwise) or if norm is unknown.

Example

The DCT-II with norm="ortho" is MATLAB's dct and the transform JPEG uses; a smooth signal packs its energy into the first coefficients.

package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round4 rounds to 4 decimals and turns the rounding residue of an exact zero
// (which may print as -0.0000 on one architecture and 0.0000 on another) into 0.
func round4(x []float64) []float64 {
	out := make([]float64, len(x))
	for i, v := range x {
		out[i] = math.Round(v*1e4) / 1e4
		if out[i] == 0 {
			out[i] = 0
		}
	}
	return out
}

func main() {
	x := []float64{1, 2, 3, 4}
	y := fft.DCT(x, 2, fft.NormOrtho)
	fmt.Println(round4(y))
	fmt.Println(round4(fft.IDCT(y, 2, fft.NormOrtho)))
}
Output:
[5 -2.2304 0 -0.1585]
[1 2 3 4]

func DCT32 added in v0.9.0

func DCT32(x []float32, typ int, norm Norm) []float32

DCT32 returns the DCT of type typ (1, 2, 3 or 4) of the float32 signal x, scaled by norm, in single precision: scipy.fft.dct(x, type=typ, norm=norm) for a float32 x, which scipy returns as float32. x is not modified. The same panics as DCT apply.

Example

DCT32 is scipy.fft.dct on float32 input; with NormOrtho, IDCT32 undoes it.

package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round4f32 rounds v to four decimals for printing, with positive zeros: a value
// that cancels exactly, or rounds to zero, may carry either sign depending on
// the CPU's fused multiply-add, and the sign carries no information here.
func round4f32(v []float32) []float64 {
	out := make([]float64, len(v))
	for i, x := range v {
		out[i] = math.Round(float64(x)*1e4)/1e4 + 0
	}
	return out
}

func main() {
	x := []float32{1, 2, 3, 4}
	y := fft.DCT32(x, 2, fft.NormOrtho)
	fmt.Println(round4f32(y))
	fmt.Println(round4f32(fft.IDCT32(y, 2, fft.NormOrtho)))
}
Output:
[5 -2.2304 0 -0.1585]
[1 2 3 4]

func DCTN added in v0.5.0

func DCTN(x []float64, shape []int, typ int, norm Norm) []float64

DCTN returns the N-dimensional DCT of type typ of x, a row-major array of the given shape, as scipy.fft.dctn(x, type=typ, norm=norm). x is not modified. It panics if the shape does not match len(x), if an axis is below the type's minimum length, or on an unknown type or norm.

Example

DCTN transforms every axis of a row-major array.

package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round4 rounds to 4 decimals and turns the rounding residue of an exact zero
// (which may print as -0.0000 on one architecture and 0.0000 on another) into 0.
func round4(x []float64) []float64 {
	out := make([]float64, len(x))
	for i, v := range x {
		out[i] = math.Round(v*1e4) / 1e4
		if out[i] == 0 {
			out[i] = 0
		}
	}
	return out
}

func main() {
	img := []float64{
		1, 1, 1,
		1, 1, 1,
	}
	fmt.Println(round4(fft.DCTN(img, []int{2, 3}, 2, fft.NormOrtho)))
}
Output:
[2.4495 0 0 0 0 0]

func DCTN32 added in v0.9.0

func DCTN32(x []float32, shape []int, typ int, norm Norm) []float32

DCTN32 is DCTN in single precision: the DCT of type typ along every axis of the row-major float32 array x, as scipy.fft.dctn(x, type=typ, norm=norm). The same rules as DCTN apply.

func DST added in v0.5.0

func DST(x []float64, typ int, norm Norm) []float64

DST returns the discrete sine transform of type typ (1, 2, 3 or 4) of x, scaled by norm, as scipy.fft.dst(x, type=typ, norm=norm); NormOrtho also applies scipy's default orthogonalisation. x is not modified.

It panics if typ is not 1..4, if x is empty or if norm is unknown.

Example

The DST-I diagonalises the second-difference operator with zero boundary values: a sine mode is one coefficient.

package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round4 rounds to 4 decimals and turns the rounding residue of an exact zero
// (which may print as -0.0000 on one architecture and 0.0000 on another) into 0.
func round4(x []float64) []float64 {
	out := make([]float64, len(x))
	for i, v := range x {
		out[i] = math.Round(v*1e4) / 1e4
		if out[i] == 0 {
			out[i] = 0
		}
	}
	return out
}

func main() {
	x := []float64{0.5, 0.8660254037844386, 1, 0.8660254037844386, 0.5} // sin(π(n+1)/6)
	fmt.Println(round4(fft.DST(x, 1, fft.NormBackward)))
}
Output:
[6 0 0 0 0]

func DST32 added in v0.9.0

func DST32(x []float32, typ int, norm Norm) []float32

DST32 returns the DST of type typ (1, 2, 3 or 4) of x in single precision, as scipy.fft.dst(x, type=typ, norm=norm) for a float32 x. The same panics as DST apply.

func DSTN added in v0.5.0

func DSTN(x []float64, shape []int, typ int, norm Norm) []float64

DSTN returns the N-dimensional DST of type typ of x, a row-major array of the given shape, as scipy.fft.dstn(x, type=typ, norm=norm). The same rules as DCTN apply.

func DSTN32 added in v0.9.0

func DSTN32(x []float32, shape []int, typ int, norm Norm) []float32

DSTN32 is DSTN in single precision, as scipy.fft.dstn(x, type=typ, norm=norm). The same rules as DCTN apply.

func FFT

func FFT(x []complex128) []complex128

FFT returns the forward discrete Fourier transform of x, using the unnormalized convention

X[k] = sum_{n=0}^{N-1} x[n] * exp(-2πi·k·n/N).

Highly-composite lengths (whose prime factors are all small) use mixed-radix Cooley–Tukey; a length with a large prime factor falls back to Bluestein's chirp-z algorithm. Twiddle factors are cached per length in an internal plan cache, so repeated transforms of the same length recompute no sin/cos. The input is not modified. Empty input returns an empty (non-nil) slice; length 1 returns a copy.

func FFT2

func FFT2(data []complex128, shape [2]int) []complex128

FFT2 is the two-dimensional FFTN: it transforms data as a row-major shape[0]×shape[1] matrix (rows then columns), matching numpy.fft.fft2.

func FFT2With added in v0.4.0

func FFT2With(data []complex128, shape [2]int, o Options) []complex128

FFT2With is FFT2 with numpy.fft.fft2's axes and norm arguments: FFTNWith on a two-axis shape.

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundC(x []complex128) []complex128 {
	out := make([]complex128, len(x))
	for i, v := range x {
		out[i] = complex(round(real(v)), round(imag(v)))
	}
	return out
}

func main() {
	x := []complex128{1, 1, 1, 1}
	fmt.Println(roundC(fft.FFT2With(x, [2]int{2, 2}, fft.Options{Norm: fft.NormOrtho})))
}
Output:
[(2+0i) (0+0i) (0+0i) (0+0i)]

func FFT2_32 added in v0.9.0

func FFT2_32(data []complex64, shape [2]int) []complex64

FFT2_32 is the two-dimensional FFTN32: FFT2 in single precision (scipy.fft.fft2 on complex64 input).

func FFT2_32With added in v0.9.0

func FFT2_32With(data []complex64, shape [2]int, o Options) []complex64

FFT2_32With is FFT2_32 with numpy.fft.fft2's axes and norm arguments: FFTN32With on a two-axis shape.

func FFT32 added in v0.6.0

func FFT32(x []complex64) []complex64

FFT32 returns the forward discrete Fourier transform of x in single precision, unnormalized like FFT. The input is not modified; empty input returns an empty (non-nil) slice. Plans are cached per length.

Example

FFT32 and IFFT32 are FFT and IFFT for complex64 data.

package main

import (
	"fmt"

	"github.com/go-fft/fft"
)

// tidy turns the zeros of v positive for printing: whether an exactly
// cancelling sum comes out +0 or -0 depends on the CPU's fused multiply-add
// (loong64 and amd64 differ), and the sign of zero carries no information here.
func tidy(v []complex64) []complex64 {
	out := make([]complex64, len(v))
	for i, z := range v {
		out[i] = complex(real(z)+0, imag(z)+0)
	}
	return out
}

func main() {
	x := []complex64{1, 2, 3, 4}
	X := fft.FFT32(x)
	fmt.Println(tidy(X))
	fmt.Println(tidy(fft.IFFT32(X)))
}
Output:
[(10+0i) (-2+2i) (-2+0i) (-2-2i)]
[(1+0i) (2+0i) (3+0i) (4+0i)]

func FFT32With added in v0.9.0

func FFT32With(x []complex64, o Options) []complex64

FFT32With is FFT32 with numpy.fft.fft's n and norm arguments, as FFTWith: the input is truncated or zero-padded to o.N points (len(x) when o.N is zero) and the transform is scaled as o.Norm says. The input is not modified. o.Axes must be nil.

Example

The Options of the float64 transforms apply unchanged: here numpy's n (zero-padding to 4 points) and norm="forward".

x := []complex64{4, 4}
X := fft.FFT32With(x, fft.Options{N: 4, Norm: fft.NormForward})
fmt.Println(tidy(X))
Output:
[(2+0i) (1-1i) (0+0i) (1+1i)]

func FFTFreq

func FFTFreq(n int, d float64) []float64

FFTFreq returns the n sample frequencies of an FFT of length n, given the sample spacing d (the inverse of the sampling rate). The result matches numpy.fft.fftfreq(n, d) exactly:

f = [0, 1, ..., (n-1)//2, -(n//2), ..., -1] / (d·n).

The DC bin is first, then the positive frequencies in increasing order, then the negative frequencies. For even n the Nyquist bin appears as -n/2; for odd n the largest positive bin is (n-1)/2. n <= 0 returns an empty (non-nil) slice.

func FFTN

func FFTN(data []complex128, shape []int) []complex128

FFTN returns the forward N-dimensional discrete Fourier transform of data, interpreted as a row-major (C-order) array of the given shape. The result is a new slice of the same length; the input is not modified.

The transform is separable and unnormalized: it applies the 1-D FFT along each axis in turn, matching numpy.fft.fftn. shape must contain only positive lengths whose product equals len(data); FFTN panics otherwise (consistent with numpy raising on a mismatched shape). An empty shape transforms the single scalar unchanged.

func FFTN32 added in v0.9.0

func FFTN32(data []complex64, shape []int) []complex64

FFTN32 returns the forward N-dimensional DFT of the complex64 row-major array data of the given shape, in single precision and unnormalized, like FFTN (scipy.fft.fftn on complex64 input). The input is not modified; the same shape rules as FFTN apply.

Example

FFTN32 and IFFTN32 are FFTN and IFFTN for complex64 data; FFT2_32 is the two-axis case (the underscore keeps "2" and "32" apart).

x := []complex64{1, 2, 3, 4} // a 2×2 matrix, row-major
X := fft.FFTN32(x, []int{2, 2})
fmt.Println(tidy(X))
fmt.Println(tidy(fft.FFT2_32(x, [2]int{2, 2})))
fmt.Println(tidy(fft.IFFTN32(X, []int{2, 2})))
Output:
[(10+0i) (-2+0i) (-4+0i) (0+0i)]
[(10+0i) (-2+0i) (-4+0i) (0+0i)]
[(1+0i) (2+0i) (3+0i) (4+0i)]

func FFTN32With added in v0.9.0

func FFTN32With(data []complex64, shape []int, o Options) []complex64

FFTN32With is FFTN32 with numpy.fft.fftn's axes and norm arguments, as FFTNWith: only the axes in o.Axes are transformed (every axis when nil) and the result is scaled as o.Norm says. o.N must be zero.

func FFTNWith added in v0.4.0

func FFTNWith(data []complex128, shape []int, o Options) []complex128

FFTNWith is FFTN with numpy.fft.fftn's axes and norm arguments: data is a row-major array of the given shape, only the axes in o.Axes are transformed (every axis when o.Axes is nil), and the result is scaled as o.Norm says. FFTNWith(data, shape, Options{}) equals FFTN(data, shape). The output has the input's shape; the input is not modified. o.N must be zero.

With o.Axes = []int{-1} it is the batched 1-D transform: every row of a matrix of signals is transformed with one plan.

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundC(x []complex128) []complex128 {
	out := make([]complex128, len(x))
	for i, v := range x {
		out[i] = complex(round(real(v)), round(imag(v)))
	}
	return out
}

func main() {
	// Three signals of four points, one per row: transforming the last axis
	// alone is numpy's fft(x, axis=-1), the batched 1-D transform.
	rows := []complex128{
		1, 0, 0, 0,
		1, 1, 1, 1,
		0, 1, 0, 0,
	}
	out := fft.FFTNWith(rows, []int{3, 4}, fft.Options{Axes: []int{-1}})
	for r := 0; r < 3; r++ {
		fmt.Println(roundC(out[r*4 : (r+1)*4]))
	}
}
Output:
[(1+0i) (1+0i) (1+0i) (1+0i)]
[(4+0i) (0+0i) (0+0i) (0+0i)]
[(1+0i) (0-1i) (-1+0i) (0+1i)]

func FFTReal

func FFTReal(x []float64) []complex128

FFTReal returns the forward discrete Fourier transform of a real-valued signal as the full complex spectrum (length len(x)). It is a convenience wrapper that promotes x to complex128 and calls FFT.

func FFTShift added in v0.4.0

func FFTShift[T any](x []T) []T

FFTShift returns x with its zero-frequency element moved to the centre: x rotated right by len(x)/2, numpy.fft.fftshift on a 1-D array. For FFTFreq(n, d) it returns the frequencies in increasing order. The input is not modified; the result is a new slice (empty, not nil, for empty input).

Example
package main

import (
	"fmt"

	"github.com/go-fft/fft"
)

func main() {
	// Bin frequencies in increasing order, the zero frequency in the middle.
	fmt.Println(fft.FFTShift(fft.FFTFreq(5, 1)))
	fmt.Println(fft.FFTShift([]int{0, 1, 2, 3, 4, 5}))
}
Output:
[-0.4 -0.2 0 0.2 0.4]
[3 4 5 0 1 2]

func FFTShiftN added in v0.4.0

func FFTShiftN[T any](x []T, shape []int, axes []int) []T

FFTShiftN is numpy.fft.fftshift on a row-major array of the given shape: every axis in axes (every axis when axes is nil; negative axes count from the end) is rotated by its length/2, which moves the zero-frequency bin of an N-D spectrum to the centre. shape must hold positive lengths whose product is len(x), and an axis may not be listed twice; FFTShiftN panics otherwise. The input is not modified.

Example
package main

import (
	"fmt"

	"github.com/go-fft/fft"
)

func main() {
	x := []int{
		0, 1, 2,
		3, 4, 5,
		6, 7, 8,
	}
	fmt.Println(fft.FFTShiftN(x, []int{3, 3}, nil))
	fmt.Println(fft.FFTShiftN(x, []int{3, 3}, []int{-1})) // the rows only
}
Output:
[8 6 7 2 0 1 5 3 4]
[2 0 1 5 3 4 8 6 7]

func FFTWith added in v0.4.0

func FFTWith(x []complex128, o Options) []complex128

FFTWith is FFT with numpy.fft.fft's n and norm arguments: the input is truncated or zero-padded to o.N points (len(x) when o.N is zero) and the forward transform is scaled as o.Norm says. FFTWith(x, Options{}) equals FFT(x). The input is not modified. o.Axes must be nil.

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundC(x []complex128) []complex128 {
	out := make([]complex128, len(x))
	for i, v := range x {
		out[i] = complex(round(real(v)), round(imag(v)))
	}
	return out
}

func main() {
	x := []complex128{1, 1, 1, 1, 1, 1}
	// Truncate to 4 points and scale by 1/sqrt(4) (numpy norm="ortho").
	fmt.Println(roundC(fft.FFTWith(x, fft.Options{N: 4, Norm: fft.NormOrtho})))
}
Output:
[(2+0i) (0+0i) (0+0i) (0+0i)]

func HFFT added in v0.4.0

func HFFT(x []complex128, n int) []float64

HFFT returns the DFT of a signal with Hermitian symmetry, given its first half x (x[0] .. x[n/2]; the rest is conj(x[n-k])), as a real slice of n values: numpy.fft.hfft(x, n). It is the forward transform, unscaled:

X[k] = sum_{j=0}^{n-1} y[j] * exp(-2πi·k·j/n),  y[j] = x[j], y[n-j] = conj(x[j]).

numpy defines it as irfft(conj(x), n) multiplied by n; HFFT computes the same. n is the length of the output, as for IRFFT (n and n-1 share the same half); n <= 0 returns an empty slice. x is read up to n/2+1 values and missing ones are zero. The input is not modified.

See https://numpy.org/doc/stable/reference/generated/numpy.fft.hfft.html.

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundR(x []float64) []float64 {
	out := make([]float64, len(x))
	for i, v := range x {
		out[i] = round(v)
	}
	return out
}

func main() {
	// The first half of a Hermitian-symmetric signal y = [1, 2, 3, 2]; its DFT
	// is real.
	fmt.Println(roundR(fft.HFFT([]complex128{1, 2, 3}, 4)))
}
Output:
[8 -2 0 -2]

func HFFT32 added in v0.9.0

func HFFT32(x []complex64, n int) []float32

HFFT32 is HFFT in single precision: the real DFT, of n points, of the Hermitian-symmetric signal whose first half is x (numpy.fft.hfft(x, n)). n <= 0 returns an empty slice. The input is not modified.

func HFFT32With added in v0.9.0

func HFFT32With(x []complex64, o Options) []float32

HFFT32With is HFFTWith in single precision: o.N is the output length (zero selects 2*(len(x)-1)) and o.Norm scales it as a forward transform. o.Axes must be nil.

func HFFTWith added in v0.4.0

func HFFTWith(x []complex128, o Options) []float64

HFFTWith is HFFT with numpy.fft.hfft's n and norm arguments: o.N is the length of the real output (zero selects numpy's default 2*(len(x)-1)) and o.Norm scales it as a FORWARD transform — unscaled under the default NormBackward, 1/sqrt(n) under NormOrtho, 1/n under NormForward. numpy implements this as irfft(conj(x), n, norm=<the opposite direction>), which is the same thing. o.Axes must be nil.

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundR(x []float64) []float64 {
	out := make([]float64, len(x))
	for i, v := range x {
		out[i] = round(v)
	}
	return out
}

func main() {
	// The same transform scaled as a forward transform under NormForward: 1/n.
	fmt.Println(roundR(fft.HFFTWith([]complex128{1, 2, 3}, fft.Options{N: 4, Norm: fft.NormForward})))
}
Output:
[2 -0.5 0 -0.5]

func Hamming

func Hamming(n int) []float64

Hamming returns the length-n Hamming window:

w[k] = 0.54 - 0.46·cos(2π·k/(n-1)),  k = 0 .. n-1.

This matches numpy.hamming(n).

func Hann

func Hann(n int) []float64

Hann returns the length-n Hann (Hanning) window:

w[k] = 0.5 - 0.5·cos(2π·k/(n-1)),  k = 0 .. n-1.

This matches numpy.hanning(n).

func IDCT added in v0.5.0

func IDCT(x []float64, typ int, norm Norm) []float64

IDCT returns the inverse discrete cosine transform of type typ of x, as scipy.fft.idct(x, type=typ, norm=norm), so that IDCT(DCT(x, t, m), t, m) reproduces x. The same panics as DCT apply.

func IDCT32 added in v0.9.0

func IDCT32(x []float32, typ int, norm Norm) []float32

IDCT32 returns the inverse DCT of type typ of x in single precision, as scipy.fft.idct(x, type=typ, norm=norm). The same panics as DCT apply.

func IDCTN added in v0.5.0

func IDCTN(x []float64, shape []int, typ int, norm Norm) []float64

IDCTN inverts DCTN, as scipy.fft.idctn(x, type=typ, norm=norm).

func IDCTN32 added in v0.9.0

func IDCTN32(x []float32, shape []int, typ int, norm Norm) []float32

IDCTN32 inverts DCTN32, as scipy.fft.idctn(x, type=typ, norm=norm).

func IDST added in v0.5.0

func IDST(x []float64, typ int, norm Norm) []float64

IDST returns the inverse discrete sine transform of type typ of x, as scipy.fft.idst(x, type=typ, norm=norm), so that IDST(DST(x, t, m), t, m) reproduces x. The same panics as DST apply.

func IDST32 added in v0.9.0

func IDST32(x []float32, typ int, norm Norm) []float32

IDST32 returns the inverse DST of type typ of x in single precision, as scipy.fft.idst(x, type=typ, norm=norm). The same panics as DST apply.

func IDSTN added in v0.5.0

func IDSTN(x []float64, shape []int, typ int, norm Norm) []float64

IDSTN inverts DSTN, as scipy.fft.idstn(x, type=typ, norm=norm).

func IDSTN32 added in v0.9.0

func IDSTN32(x []float32, shape []int, typ int, norm Norm) []float32

IDSTN32 inverts DSTN32, as scipy.fft.idstn(x, type=typ, norm=norm).

func IFFT

func IFFT(x []complex128) []complex128

IFFT returns the inverse discrete Fourier transform of x, normalized by N so that IFFT(FFT(x)) ≈ x to floating-point tolerance:

x[n] = (1/N) * sum_{k=0}^{N-1} X[k] * exp(+2πi·k·n/N).

The input is not modified.

func IFFT2

func IFFT2(data []complex128, shape [2]int) []complex128

IFFT2 is the two-dimensional IFFTN, the inverse of FFT2, normalized by shape[0]*shape[1]. It matches numpy.fft.ifft2.

func IFFT2With added in v0.4.0

func IFFT2With(data []complex128, shape [2]int, o Options) []complex128

IFFT2With is IFFT2 with numpy.fft.ifft2's axes and norm arguments: IFFTNWith on a two-axis shape.

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundC(x []complex128) []complex128 {
	out := make([]complex128, len(x))
	for i, v := range x {
		out[i] = complex(round(real(v)), round(imag(v)))
	}
	return out
}

func main() {
	X := []complex128{2, 0, 0, 0}
	fmt.Println(roundC(fft.IFFT2With(X, [2]int{2, 2}, fft.Options{Norm: fft.NormOrtho})))
}
Output:
[(1+0i) (1+0i) (1+0i) (1+0i)]

func IFFT2_32 added in v0.9.0

func IFFT2_32(data []complex64, shape [2]int) []complex64

IFFT2_32 is the two-dimensional IFFTN32, the inverse of FFT2_32, normalized by shape[0]*shape[1].

func IFFT2_32With added in v0.9.0

func IFFT2_32With(data []complex64, shape [2]int, o Options) []complex64

IFFT2_32With is IFFT2_32 with numpy.fft.ifft2's axes and norm arguments: IFFTN32With on a two-axis shape.

func IFFT32 added in v0.6.0

func IFFT32(x []complex64) []complex64

IFFT32 returns the inverse discrete Fourier transform of x in single precision, normalized by N like IFFT. The input is not modified.

func IFFT32With added in v0.9.0

func IFFT32With(x []complex64, o Options) []complex64

IFFT32With is IFFT32 with numpy.fft.ifft's n and norm arguments, as IFFTWith (1/n under the default NormBackward). o.Axes must be nil.

func IFFTN

func IFFTN(data []complex128, shape []int) []complex128

IFFTN returns the inverse N-dimensional discrete Fourier transform of data, the inverse of FFTN. It applies the 1-D IFFT along each axis, so the result is normalized by the product of the transformed axis lengths and IFFTN(FFTN(x), shape) ≈ x. The input is not modified; the same shape rules as FFTN apply.

func IFFTN32 added in v0.9.0

func IFFTN32(data []complex64, shape []int) []complex64

IFFTN32 returns the inverse N-dimensional DFT of data in single precision, normalized by the product of the shape, like IFFTN. The input is not modified.

func IFFTN32With added in v0.9.0

func IFFTN32With(data []complex64, shape []int, o Options) []complex64

IFFTN32With is IFFTN32 with numpy.fft.ifftn's axes and norm arguments; see FFTN32With.

func IFFTNWith added in v0.4.0

func IFFTNWith(data []complex128, shape []int, o Options) []complex128

IFFTNWith is IFFTN with numpy.fft.ifftn's axes and norm arguments; see FFTNWith. Under the default NormBackward the result is divided by the product of the transformed axis lengths.

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundC(x []complex128) []complex128 {
	out := make([]complex128, len(x))
	for i, v := range x {
		out[i] = complex(round(real(v)), round(imag(v)))
	}
	return out
}

func main() {
	X := []complex128{4, 0, 0, 0}
	// Inverse along axis 0 only, of a 2×2 array.
	fmt.Println(roundC(fft.IFFTNWith(X, []int{2, 2}, fft.Options{Axes: []int{0}})))
}
Output:
[(2+0i) (0+0i) (2+0i) (0+0i)]

func IFFTShift added in v0.4.0

func IFFTShift[T any](x []T) []T

IFFTShift is the inverse of FFTShift: x rotated left by len(x)/2, numpy.fft.ifftshift on a 1-D array. IFFTShift(FFTShift(x)) == x for every length; for an even length the two functions coincide. The input is not modified.

Example
package main

import (
	"fmt"

	"github.com/go-fft/fft"
)

func main() {
	shifted := fft.FFTShift([]int{0, 1, 2, 3, 4})
	fmt.Println(shifted, fft.IFFTShift(shifted))
}
Output:
[3 4 0 1 2] [0 1 2 3 4]

func IFFTShiftN added in v0.4.0

func IFFTShiftN[T any](x []T, shape []int, axes []int) []T

IFFTShiftN is numpy.fft.ifftshift on a row-major array of the given shape, the inverse of FFTShiftN: every axis in axes (every axis when nil) is rotated back by its length/2. The same rules as FFTShiftN apply.

Example
package main

import (
	"fmt"

	"github.com/go-fft/fft"
)

func main() {
	x := []int{0, 1, 2, 3, 4, 5}
	s := fft.FFTShiftN(x, []int{2, 3}, nil)
	fmt.Println(s, fft.IFFTShiftN(s, []int{2, 3}, nil))
}
Output:
[5 3 4 2 0 1] [0 1 2 3 4 5]

func IFFTWith added in v0.4.0

func IFFTWith(x []complex128, o Options) []complex128

IFFTWith is IFFT with numpy.fft.ifft's n and norm arguments: the input is truncated or zero-padded to o.N points (len(x) when o.N is zero) and the inverse transform is scaled as o.Norm says (1/n under the default NormBackward). IFFTWith(x, Options{}) equals IFFT(x). The input is not modified. o.Axes must be nil.

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundC(x []complex128) []complex128 {
	out := make([]complex128, len(x))
	for i, v := range x {
		out[i] = complex(round(real(v)), round(imag(v)))
	}
	return out
}

func main() {
	X := []complex128{4, 0, 0, 0}
	// Under NormForward the inverse is unscaled.
	fmt.Println(roundC(fft.IFFTWith(X, fft.Options{Norm: fft.NormForward})))
}
Output:
[(4+0i) (4+0i) (4+0i) (4+0i)]

func IHFFT added in v0.4.0

func IHFFT(x []float64) []complex128

IHFFT is the inverse of HFFT: it returns the first n/2+1 values of the Hermitian-symmetric signal whose real DFT is x (n = len(x)), normalized by 1/n — numpy.fft.ihfft(x), which numpy defines as conj(rfft(x))/n. Empty input returns an empty slice. The input is not modified.

See https://numpy.org/doc/stable/reference/generated/numpy.fft.ihfft.html.

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundC(x []complex128) []complex128 {
	out := make([]complex128, len(x))
	for i, v := range x {
		out[i] = complex(round(real(v)), round(imag(v)))
	}
	return out
}

func main() {
	// IHFFT inverts HFFT: back to the half signal.
	fmt.Println(roundC(fft.IHFFT([]float64{8, -2, 0, -2})))
}
Output:
[(1+0i) (2+0i) (3+0i)]

func IHFFT32 added in v0.9.0

func IHFFT32(x []float32) []complex64

IHFFT32 is IHFFT in single precision: the first n/2+1 values of the Hermitian signal whose real DFT is x, normalized by 1/n (numpy.fft.ihfft(x)). The input is not modified.

func IHFFT32With added in v0.9.0

func IHFFT32With(x []float32, o Options) []complex64

IHFFT32With is IHFFTWith in single precision: the input is truncated or zero-padded to o.N points (len(x) when zero) and o.Norm scales the result as an inverse transform. o.Axes must be nil.

func IHFFTWith added in v0.4.0

func IHFFTWith(x []float64, o Options) []complex128

IHFFTWith is IHFFT with numpy.fft.ihfft's n and norm arguments: the input is truncated or zero-padded to o.N points (len(x) when o.N is zero) and o.Norm scales the result as an INVERSE transform — 1/n under the default NormBackward, 1/sqrt(n) under NormOrtho, unscaled under NormForward — so that HFFTWith(IHFFTWith(x, o), Options{N: len(x), Norm: o.Norm}) ≈ x for every o.Norm. o.Axes must be nil.

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundR(x []float64) []float64 {
	out := make([]float64, len(x))
	for i, v := range x {
		out[i] = round(v)
	}
	return out
}

func main() {
	x := []float64{8, -2, 0, -2}
	h := fft.IHFFTWith(x, fft.Options{Norm: fft.NormOrtho})
	// The pair round-trips under any Norm, given the same one both ways.
	fmt.Println(roundR(fft.HFFTWith(h, fft.Options{N: len(x), Norm: fft.NormOrtho})))
}
Output:
[8 -2 0 -2]

func IRFFT

func IRFFT(spectrum []complex128, n int) []float64

IRFFT inverts RFFT, reconstructing a real-valued signal of length n from the n/2+1 non-redundant spectral bins produced by RFFT. The caller passes the desired output length n explicitly, because n and n-1 yield the same number of kept bins so the half spectrum alone is ambiguous; this mirrors numpy.fft.irfft(spectrum, n).

spectrum is read up to min(len(spectrum), n/2+1) bins; any bins beyond that (or beyond what the Hermitian mirror needs) are treated as zero. The result is normalized by n so that IRFFT(RFFT(x), len(x)) ≈ x.

The input is not modified. n <= 0 returns an empty (non-nil) slice.

The output has n values whatever the spectrum's length, so n sets the allocation, as it would for make: bound it when it comes from untrusted input (see SECURITY.md).

func IRFFT2

func IRFFT2(data []complex128, shape [2]int) []float64

IRFFT2 inverts RFFT2, reconstructing a real row-major matrix of shape shape[0]×shape[1] from a spectrum laid out as shape[0]×(shape[1]/2+1) complex bins (the layout RFFT2 produces). The complex inverse is applied down the columns first, then the real inverse (irfft) along each row, with the target row length shape[1] supplied explicitly (since shape[1] and shape[1]-1 share a bin count). The result is normalized by shape[0]*shape[1] so that IRFFT2(RFFT2(x, shape), shape) ≈ x. This matches numpy.fft.irfft2.

shape lengths must be positive; IRFFT2 panics otherwise. data is read up to shape[0]*(shape[1]/2+1) bins; any beyond that are treated as zero. The input is not modified. The output has shape[0]*shape[1] values whatever the spectrum's length: bound the shape when it comes from untrusted input (see SECURITY.md).

func IRFFT2With added in v0.4.0

func IRFFT2With(data []complex128, shape [2]int, o Options) []float64

IRFFT2With is IRFFT2 with numpy.fft.irfft2's axes and norm arguments. With o.Axes nil it runs IRFFT2's plan and rescales the result; with axes it is IRFFTNWith on a two-axis shape.

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundR(x []float64) []float64 {
	out := make([]float64, len(x))
	for i, v := range x {
		out[i] = round(v)
	}
	return out
}

func main() {
	spec := []complex128{1, 0, 0, 0}
	fmt.Println(roundR(fft.IRFFT2With(spec, [2]int{2, 2}, fft.Options{Norm: fft.NormForward})))
}
Output:
[1 1 1 1]

func IRFFT2_32 added in v0.9.0

func IRFFT2_32(data []complex64, shape [2]int) []float32

IRFFT2_32 inverts RFFT2_32, as IRFFT2 inverts RFFT2: shape is the real output's, data is read up to shape[0]*(shape[1]/2+1) bins (missing ones are zero) and the result is normalized by shape[0]*shape[1]. The output has shape[0]*shape[1] values whatever the spectrum's length: bound the shape when it comes from untrusted input (see SECURITY.md).

func IRFFT2_32With added in v0.9.0

func IRFFT2_32With(data []complex64, shape [2]int, o Options) []float32

IRFFT2_32With is IRFFT2_32 with numpy.fft.irfft2's axes and norm arguments. With o.Axes nil it runs IRFFT2_32's plan; with axes it is IRFFTN32With on a two-axis shape.

func IRFFT32 added in v0.6.0

func IRFFT32(spectrum []complex64, n int) []float32

IRFFT32 inverts RFFT32, returning n real samples normalized by n like IRFFT. n <= 0 returns an empty (non-nil) slice. n sets the allocation: bound it when it comes from untrusted input (see SECURITY.md).

func IRFFT32With added in v0.9.0

func IRFFT32With(spectrum []complex64, o Options) []float32

IRFFT32With is IRFFT32 with numpy.fft.irfft's n and norm arguments, as IRFFTWith: o.N is the length of the real output, zero selecting numpy's default 2*(len(spectrum)-1). o.Axes must be nil. The output length is the caller's o.N whatever the spectrum's length: bound it when it comes from untrusted input (see SECURITY.md).

func IRFFTN added in v0.4.0

func IRFFTN(data []complex128, shape []int) []float64

IRFFTN inverts RFFTN: shape is the shape of the REAL output (its last length n cannot be recovered from the n/2+1 bins it keeps, as for IRFFT), and data is the spectrum laid out as RFFTN produces it, that shape with the last length replaced by n/2+1. The complex inverse runs along every axis but the last, then the real inverse along the last; the result is normalized by the product of the shape. This is numpy.fft.irfftn(data, s=shape).

data is read up to the spectrum's size and missing bins are zero, as for IRFFT2. shape must hold at least one axis and every length must be positive; IRFFTN panics otherwise. The input is not modified. The output has the product of shape values whatever the spectrum's length: bound the shape when it comes from untrusted input (see SECURITY.md).

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundR(x []float64) []float64 {
	out := make([]float64, len(x))
	for i, v := range x {
		out[i] = round(v)
	}
	return out
}

func main() {
	x := []float64{1, 2, 3, 4, 5, 6}
	// The shape is the real output's: its last length cannot be recovered
	// from the bins alone.
	back := fft.IRFFTN(fft.RFFTN(x, []int{2, 3}), []int{2, 3})
	fmt.Println(roundR(back))
}
Output:
[1 2 3 4 5 6]

func IRFFTN32 added in v0.9.0

func IRFFTN32(data []complex64, shape []int) []float32

IRFFTN32 inverts RFFTN32, as IRFFTN inverts RFFTN: shape is the REAL output's, and the result is normalized by its product. The output has the product of shape values whatever the spectrum's length: bound the shape when it comes from untrusted input (see SECURITY.md).

func IRFFTN32With added in v0.9.0

func IRFFTN32With(data []complex64, shape []int, o Options) []float32

IRFFTN32With is IRFFTN32 with numpy.fft.irfftn's axes and norm arguments, as IRFFTNWith: the real inverse runs along the LAST axis in o.Axes, the complex inverse along the others. o.Axes must name at least one axis; o.N must be zero.

func IRFFTNWith added in v0.4.0

func IRFFTNWith(data []complex128, shape []int, o Options) []float64

IRFFTNWith is IRFFTN with numpy.fft.irfftn's axes and norm arguments: the real inverse runs along the LAST axis in o.Axes (every axis when nil), whose spectrum holds shape[axis]/2+1 bins, the complex inverse along the others, and the result is scaled as o.Norm says. o.Axes must name at least one axis; o.N must be zero.

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundR(x []float64) []float64 {
	out := make([]float64, len(x))
	for i, v := range x {
		out[i] = round(v)
	}
	return out
}

func main() {
	x := []float64{1, 2, 3, 4, 5, 6}
	o := fft.Options{Axes: []int{0}, Norm: fft.NormOrtho}
	fmt.Println(roundR(fft.IRFFTNWith(fft.RFFTNWith(x, []int{3, 2}, o), []int{3, 2}, o)))
}
Output:
[1 2 3 4 5 6]

func IRFFTWith added in v0.4.0

func IRFFTWith(spectrum []complex128, o Options) []float64

IRFFTWith is IRFFT with numpy.fft.irfft's n and norm arguments. o.N is the length of the real output; zero selects numpy's default 2*(len(spectrum)-1), which reconstructs an even-length signal (pass the length explicitly for an odd one: n and n-1 keep the same number of bins). The spectrum is read up to o.N/2+1 bins and missing bins are zero, as for IRFFT; the imaginary parts of the zero-frequency bin and, for an even length, of the Nyquist bin are ignored, as numpy does. The result is scaled as o.Norm says (1/n under the default NormBackward). A length of zero (a spectrum of fewer than two bins and o.N zero) gives an empty result, where numpy raises. The input is not modified. o.Axes must be nil.

The output length is the caller's o.N whatever the spectrum's length: bound it when it comes from untrusted input (see SECURITY.md).

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundR(x []float64) []float64 {
	out := make([]float64, len(x))
	for i, v := range x {
		out[i] = round(v)
	}
	return out
}

func main() {
	spectrum := []complex128{6, -2 - 2i, 2}
	// N = 0 takes numpy's default length 2*(3-1) = 4.
	fmt.Println(roundR(fft.IRFFTWith(spectrum, fft.Options{})))
}
Output:
[1 2 3 0]

func NextFastLen added in v0.4.0

func NextFastLen(target int, real bool) int

NextFastLen returns the smallest length >= target that this package transforms fast, the counterpart of scipy.fft.next_fast_len(target, real) (https://docs.scipy.org/doc/scipy/reference/generated/scipy.fft.next_fast_len.html): zero-pad a signal to it before transforming (FFTWith/RFFTWith with Options{N: NextFastLen(len(x), ...)}).

What is fast depends on the library, so the rule differs from scipy's:

  • complex (real == false): the 7-smooth lengths, 2^a·3^b·5^c·7^d. Every such length runs entirely on the Stockham engine's specialized radix 2/3/4/5/7/8 passes. A factor 11 or 13 also stays on the mixed-radix path but through its general O(p²) radix pass, and a larger prime factor goes to Rader or Bluestein. Measured on Apple M4 Max (2026-10-05, ns per point): 1001 = 7·11·13 cost 20 and 1100 = 2²·5²·11 cost 11, against 2.5 for 1008 and 1120; 2197 = 13³ cost 33 and 4067 = 7²·83 (Bluestein) 15, against 2.7 for 4096. scipy, whose pocketfft has specialized radix-11 passes, accepts 11-smooth lengths here.
  • real (real == true): the EVEN 7-smooth lengths. An even length packs into a half-length complex transform; an odd one runs the full complex transform: as a real transform 1125 = 3²·5³ cost 4.7 ns per point against 1.6 for 1134 = 2·3⁴·7. scipy's real rule is 5-smooth, odd lengths included.

Targets 0 and 1 are returned unchanged (such transforms are trivial). A negative target panics, as does a target above the largest qualifying length that fits in an int.

Example
package main

import (
	"fmt"

	"github.com/go-fft/fft"
)

func main() {
	fmt.Println(fft.NextFastLen(1009, false)) // 1009 is prime; the next 7-smooth length is 1024
	fmt.Println(fft.NextFastLen(1001, true))  // 1001 = 7·11·13 is not; 1008 = 2⁴·3²·7 is even and 7-smooth
}
Output:
1024
1008

func PSD

func PSD(x []float64, d float64) []float64

PSD returns the one-sided power spectral density (periodogram) of the real-valued signal x sampled at spacing d, using the non-redundant RFFT bins. The result has length len(x)/2+1.

Each bin is scaled to a density: |X[k]|² / (fs·N) where fs = 1/d is the sampling rate and N = len(x). To conserve total power, every bin except DC (and the Nyquist bin for even N) is doubled to fold in the discarded mirror half. This matches scipy.signal.periodogram(x, fs, window="boxcar", scaling="density", return_onesided=True). The companion frequency axis is RFFTFreq(len(x), d). The input is not modified; an empty x returns an empty (non-nil) slice.

func RFFT

func RFFT(x []float64) []complex128

RFFT returns the forward discrete Fourier transform of a real-valued signal, keeping only the non-redundant spectrum. For input of length N the result has length N/2+1 (integer division): bins 0..N/2. The discarded upper half is the conjugate mirror of the lower half (X[N-k] = conj(X[k])), so it carries no new information. This matches numpy.fft.rfft.

The transform is unnormalized, consistent with FFT:

X[k] = sum_{n=0}^{N-1} x[n] * exp(-2πi·k·n/N),  k = 0 .. N/2.

For even N the real signal is packed into an N/2-point complex transform and untangled — about twice as fast as a full complex FFT — using twiddle tables cached per length. The input is not modified. Empty input returns an empty (non-nil) slice.

func RFFT2

func RFFT2(data []float64, shape [2]int) []complex128

RFFT2 returns the forward 2-D DFT of a real row-major matrix of the given shape (shape[0] rows × shape[1] columns). The real transform is applied along the last axis, so each output row keeps shape[1]/2+1 non-redundant bins; the result is a row-major matrix of shape shape[0]×(shape[1]/2+1). The full complex transform is then applied down the columns. This matches numpy.fft.rfft2.

shape lengths must be positive and shape[0]*shape[1] must equal len(data); RFFT2 panics otherwise. The input is not modified.

func RFFT2With added in v0.4.0

func RFFT2With(data []float64, shape [2]int, o Options) []complex128

RFFT2With is RFFT2 with numpy.fft.rfft2's axes and norm arguments. With o.Axes nil it runs RFFT2's plan and scales the result; with axes it is RFFTNWith on a two-axis shape.

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundC(x []complex128) []complex128 {
	out := make([]complex128, len(x))
	for i, v := range x {
		out[i] = complex(round(real(v)), round(imag(v)))
	}
	return out
}

func main() {
	img := []float64{1, 1, 1, 1}
	fmt.Println(roundC(fft.RFFT2With(img, [2]int{2, 2}, fft.Options{Norm: fft.NormForward})))
}
Output:
[(1+0i) (0+0i) (0+0i) (0+0i)]

func RFFT2_32 added in v0.9.0

func RFFT2_32(data []float32, shape [2]int) []complex64

RFFT2_32 is RFFT2 in single precision: the forward 2-D DFT of a real row-major float32 matrix of shape shape[0]×shape[1], keeping shape[1]/2+1 bins per row (scipy.fft.rfft2 on float32 input). The same shape rules as RFFT2 apply; the input is not modified.

func RFFT2_32With added in v0.9.0

func RFFT2_32With(data []float32, shape [2]int, o Options) []complex64

RFFT2_32With is RFFT2_32 with numpy.fft.rfft2's axes and norm arguments. With o.Axes nil it runs RFFT2_32's plan; with axes it is RFFTN32With on a two-axis shape.

func RFFT32 added in v0.6.0

func RFFT32(x []float32) []complex64

RFFT32 returns the N/2+1 non-redundant bins of the real signal x in single precision, unnormalized like RFFT. Empty input returns an empty (non-nil) slice. Plans are cached per length.

Example

RFFT32 keeps the N/2+1 non-redundant bins of a float32 signal; IRFFT32 needs the length back, since N and N+1 give as many bins.

package main

import (
	"fmt"

	"github.com/go-fft/fft"
)

// tidy turns the zeros of v positive for printing: whether an exactly
// cancelling sum comes out +0 or -0 depends on the CPU's fused multiply-add
// (loong64 and amd64 differ), and the sign of zero carries no information here.
func tidy(v []complex64) []complex64 {
	out := make([]complex64, len(v))
	for i, z := range v {
		out[i] = complex(real(z)+0, imag(z)+0)
	}
	return out
}

func main() {
	x := []float32{1, 2, 3, 4}
	X := fft.RFFT32(x)
	fmt.Println(tidy(X))
	fmt.Println(fft.IRFFT32(X, len(x)))
}
Output:
[(10+0i) (-2+2i) (-2+0i)]
[1 2 3 4]

func RFFT32With added in v0.9.0

func RFFT32With(x []float32, o Options) []complex64

RFFT32With is RFFT32 with numpy.fft.rfft's n and norm arguments, as RFFTWith: the result holds n/2+1 bins of the input truncated or zero-padded to n = o.N points (len(x) when o.N is zero). o.Axes must be nil.

func RFFTFreq

func RFFTFreq(n int, d float64) []float64

RFFTFreq returns the n/2+1 sample frequencies of a real FFT (RFFT) of input length n, given the sample spacing d. The result matches numpy.fft.rfftfreq(n, d) exactly:

f = [0, 1, ..., n//2] / (d·n),  length n//2+1.

All frequencies are non-negative because RFFT keeps only the non-redundant lower half of the spectrum. n <= 0 returns an empty (non-nil) slice.

func RFFTN added in v0.4.0

func RFFTN(data []float64, shape []int) []complex128

RFFTN returns the forward N-dimensional DFT of the real row-major array data of the given shape, keeping only the non-redundant half of the last axis: the result is a row-major array of the same shape except that the last length n becomes n/2+1. The real transform runs along the last axis and the complex one along the others, unnormalized. This is numpy.fft.rfftn; RFFT2 is its two-dimensional case.

shape must hold at least one axis, every length must be positive and their product must equal len(data); RFFTN panics otherwise. The input is not modified.

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundC(x []complex128) []complex128 {
	out := make([]complex128, len(x))
	for i, v := range x {
		out[i] = complex(round(real(v)), round(imag(v)))
	}
	return out
}

func main() {
	// A 2×2×4 real array: the last axis keeps 4/2+1 = 3 bins.
	x := make([]float64, 16)
	for i := range x {
		x[i] = 1
	}
	s := fft.RFFTN(x, []int{2, 2, 4})
	fmt.Println(len(s), roundC(s[:3]))
}
Output:
12 [(16+0i) (0+0i) (0+0i)]

func RFFTN32 added in v0.9.0

func RFFTN32(data []float32, shape []int) []complex64

RFFTN32 is RFFTN in single precision: the forward N-dimensional DFT of the real row-major float32 array data, the real transform along the last axis (which keeps n/2+1 bins) and the complex one along the others (scipy.fft.rfftn on float32 input). The same rules as RFFTN apply.

func RFFTN32With added in v0.9.0

func RFFTN32With(data []float32, shape []int, o Options) []complex64

RFFTN32With is RFFTN32 with numpy.fft.rfftn's axes and norm arguments, as RFFTNWith: the real transform runs along the LAST axis listed. o.Axes must name at least one axis; o.N must be zero.

Example

Batched real transforms: every row of a matrix of float32 signals, one plan, with Options.Axes as numpy's axis=-1.

rows := []float32{
	1, 1, 1, 1,
	1, 0, -1, 0,
}
S := fft.RFFTN32With(rows, []int{2, 4}, fft.Options{Axes: []int{-1}})
fmt.Println(tidy(S))
Output:
[(4+0i) (0+0i) (0+0i) (0+0i) (2+0i) (0+0i)]

func RFFTNWith added in v0.4.0

func RFFTNWith(data []float64, shape []int, o Options) []complex128

RFFTNWith is RFFTN with numpy.fft.rfftn's axes and norm arguments: only the axes in o.Axes are transformed (every axis when nil), the real transform runs along the LAST axis listed — that axis of the output keeps n/2+1 bins — and the result is scaled as o.Norm says. o.Axes must name at least one axis; o.N must be zero.

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundC(x []complex128) []complex128 {
	out := make([]complex128, len(x))
	for i, v := range x {
		out[i] = complex(round(real(v)), round(imag(v)))
	}
	return out
}

func main() {
	x := []float64{
		1, 2,
		3, 4,
		5, 6,
	}
	// The real transform along axis 0 (the last axis listed): 3 rows keep
	// 3/2+1 = 2 bins, and the output is 2×2.
	fmt.Println(roundC(fft.RFFTNWith(x, []int{3, 2}, fft.Options{Axes: []int{0}})))
}
Output:
[(9+0i) (12+0i) (-3+1.7321i) (-3+1.7321i)]

func RFFTWith added in v0.4.0

func RFFTWith(x []float64, o Options) []complex128

RFFTWith is RFFT with numpy.fft.rfft's n and norm arguments: the real input is truncated or zero-padded to o.N points (len(x) when o.N is zero), the result holds o.N/2+1 bins, and the transform is scaled as o.Norm says. RFFTWith(x, Options{}) equals RFFT(x). The input is not modified. o.Axes must be nil.

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundC(x []complex128) []complex128 {
	out := make([]complex128, len(x))
	for i, v := range x {
		out[i] = complex(round(real(v)), round(imag(v)))
	}
	return out
}

func main() {
	r := []float64{1, 2, 3}
	// Zero-pad to 4 points: 4/2+1 = 3 bins.
	fmt.Println(roundC(fft.RFFTWith(r, fft.Options{N: 4})))
}
Output:
[(6+0i) (-2-2i) (2+0i)]

func Spectrogram

func Spectrogram(x []float64, segment, overlap int, window []float64, d float64) [][]float64

Spectrogram computes a sequence of one-sided power spectra over successive, optionally overlapping segments of the real signal x, the building block of a time–frequency display. segment is the segment length (window size) and overlap is the number of samples shared between consecutive segments (0 <= overlap < segment). Each segment is multiplied by window (which must have length segment) before its PSD is taken.

The return value is a slice of frames, each a []float64 of length segment/2+1 (the RFFTFreq(segment, d) axis); frame t covers samples [t·step, t·step+segment) with step = segment-overlap. Segments are taken only while a full window fits, so trailing samples shorter than segment are dropped (the common STFT convention). The input is not modified.

Spectrogram panics if segment <= 0, len(window) != segment, or overlap is out of range, mirroring the contract of scipy.signal.spectrogram.

Types

type DCTPlan added in v0.5.0

type DCTPlan struct {
	// contains filtered or unexported fields
}

A DCTPlan is a reusable discrete cosine transform of a fixed length and type (1 to 4), matching scipy.fft.dct and scipy.fft.idct. Like Plan it precomputes every twiddle factor once; its methods write into a caller-supplied destination. A DCTPlan is immutable and safe for concurrent use.

func NewDCTPlan added in v0.5.0

func NewDCTPlan(n, typ int) *DCTPlan

NewDCTPlan returns a plan for the DCT of type typ (1, 2, 3 or 4) of length n. It panics if typ is not 1..4 or if n is below the type's minimum: 2 for DCT-I, 1 for the others.

Example

A plan computes repeated transforms of one length without allocating; dst may be src itself.

package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round4 rounds to 4 decimals and turns the rounding residue of an exact zero
// (which may print as -0.0000 on one architecture and 0.0000 on another) into 0.
func round4(x []float64) []float64 {
	out := make([]float64, len(x))
	for i, v := range x {
		out[i] = math.Round(v*1e4) / 1e4
		if out[i] == 0 {
			out[i] = 0
		}
	}
	return out
}

func main() {
	p := fft.NewDCTPlan(4, 4)
	buf := []float64{1, 0, 0, 0}
	p.DCT(buf, buf, fft.NormBackward)
	fmt.Println(round4(buf))
	p.IDCT(buf, buf, fft.NormBackward)
	fmt.Println(round4(buf))
}
Output:
[1.9616 1.6629 1.1111 0.3902]
[1 0 0 0]

func (*DCTPlan) DCT added in v0.5.0

func (p *DCTPlan) DCT(dst, src []float64, norm Norm) []float64

DCT writes the DCT of src into dst and returns dst[:Len()], as scipy.fft.dct(src, type=Type(), norm=norm). dst and src must have at least Len() elements; dst may alias src. src is not modified unless it aliases dst.

func (*DCTPlan) IDCT added in v0.5.0

func (p *DCTPlan) IDCT(dst, src []float64, norm Norm) []float64

IDCT writes the inverse DCT of src into dst and returns dst[:Len()], as scipy.fft.idct(src, type=Type(), norm=norm): IDCT(DCT(x, norm), norm) == x for every norm. The same slice rules as DCT apply.

func (*DCTPlan) Len added in v0.5.0

func (p *DCTPlan) Len() int

Len reports the transform length the plan was built for.

func (*DCTPlan) Type added in v0.5.0

func (p *DCTPlan) Type() int

Type reports the DCT type (1 to 4) the plan was built for.

type DCTPlan32 added in v0.9.0

type DCTPlan32 struct {
	// contains filtered or unexported fields
}

A DCTPlan32 is the single-precision counterpart of DCTPlan: a reusable DCT of a fixed length and type (1 to 4) over float32 data, matching scipy.fft.dct and scipy.fft.idct on float32 input. It is immutable and safe for concurrent use.

func NewDCTPlan32 added in v0.9.0

func NewDCTPlan32(n, typ int) *DCTPlan32

NewDCTPlan32 returns a single-precision plan for the DCT of type typ (1, 2, 3 or 4) of length n. It panics if typ is not 1..4 or if n is below the type's minimum: 2 for DCT-I, 1 for the others.

func (*DCTPlan32) DCT added in v0.9.0

func (p *DCTPlan32) DCT(dst, src []float32, norm Norm) []float32

DCT writes the DCT of src into dst and returns dst[:Len()], as scipy.fft.dct(src, type=Type(), norm=norm) on float32 input. dst and src must have at least Len() elements; dst may alias src.

func (*DCTPlan32) IDCT added in v0.9.0

func (p *DCTPlan32) IDCT(dst, src []float32, norm Norm) []float32

IDCT writes the inverse DCT of src into dst and returns dst[:Len()], as scipy.fft.idct(src, type=Type(), norm=norm). The same slice rules as DCT apply.

func (*DCTPlan32) Len added in v0.9.0

func (p *DCTPlan32) Len() int

Len reports the transform length the plan was built for.

func (*DCTPlan32) Type added in v0.9.0

func (p *DCTPlan32) Type() int

Type reports the DCT type (1 to 4) the plan was built for.

type DSTPlan added in v0.5.0

type DSTPlan struct {
	// contains filtered or unexported fields
}

A DSTPlan is a reusable discrete sine transform of a fixed length and type (1 to 4), matching scipy.fft.dst and scipy.fft.idst. It is immutable and safe for concurrent use.

func NewDSTPlan added in v0.5.0

func NewDSTPlan(n, typ int) *DSTPlan

NewDSTPlan returns a plan for the DST of type typ (1, 2, 3 or 4) of length n >= 1. It panics if typ is not 1..4 or n < 1.

func (*DSTPlan) DST added in v0.5.0

func (p *DSTPlan) DST(dst, src []float64, norm Norm) []float64

DST writes the DST of src into dst and returns dst[:Len()], as scipy.fft.dst(src, type=Type(), norm=norm). dst and src must have at least Len() elements; dst may alias src. src is not modified unless it aliases dst.

func (*DSTPlan) IDST added in v0.5.0

func (p *DSTPlan) IDST(dst, src []float64, norm Norm) []float64

IDST writes the inverse DST of src into dst and returns dst[:Len()], as scipy.fft.idst(src, type=Type(), norm=norm): IDST(DST(x, norm), norm) == x for every norm. The same slice rules as DST apply.

func (*DSTPlan) Len added in v0.5.0

func (p *DSTPlan) Len() int

Len reports the transform length the plan was built for.

func (*DSTPlan) Type added in v0.5.0

func (p *DSTPlan) Type() int

Type reports the DST type (1 to 4) the plan was built for.

type DSTPlan32 added in v0.9.0

type DSTPlan32 struct {
	// contains filtered or unexported fields
}

A DSTPlan32 is the single-precision counterpart of DSTPlan, matching scipy.fft.dst and scipy.fft.idst on float32 input. It is immutable and safe for concurrent use.

func NewDSTPlan32 added in v0.9.0

func NewDSTPlan32(n, typ int) *DSTPlan32

NewDSTPlan32 returns a single-precision plan for the DST of type typ (1, 2, 3 or 4) of length n >= 1. It panics if typ is not 1..4 or n < 1.

func (*DSTPlan32) DST added in v0.9.0

func (p *DSTPlan32) DST(dst, src []float32, norm Norm) []float32

DST writes the DST of src into dst and returns dst[:Len()], as scipy.fft.dst(src, type=Type(), norm=norm) on float32 input. dst and src must have at least Len() elements; dst may alias src.

func (*DSTPlan32) IDST added in v0.9.0

func (p *DSTPlan32) IDST(dst, src []float32, norm Norm) []float32

IDST writes the inverse DST of src into dst and returns dst[:Len()], as scipy.fft.idst(src, type=Type(), norm=norm). The same slice rules as DST apply.

func (*DSTPlan32) Len added in v0.9.0

func (p *DSTPlan32) Len() int

Len reports the transform length the plan was built for.

func (*DSTPlan32) Type added in v0.9.0

func (p *DSTPlan32) Type() int

Type reports the DST type (1 to 4) the plan was built for.

type Norm added in v0.3.0

type Norm int

Norm selects how a transform pair is scaled, as numpy.fft's and scipy.fft's norm argument does. The zero value is NormBackward, numpy's default and what FFT/IFFT do: the forward transform is unscaled and the inverse divides by n.

const (
	// NormBackward leaves the forward transform unscaled and scales the
	// inverse by 1/n (numpy norm="backward", the default).
	NormBackward Norm = iota
	// NormOrtho scales both directions by 1/sqrt(n), which makes the
	// transform unitary (numpy norm="ortho").
	NormOrtho
	// NormForward scales the forward transform by 1/n and leaves the inverse
	// unscaled (numpy norm="forward").
	NormForward
)

func (Norm) String added in v0.3.0

func (m Norm) String() string

String returns numpy's name for the mode: "backward", "ortho" or "forward".

type Options added in v0.4.0

type Options struct {
	// N is numpy's n: the number of input points a 1-D transform uses. The
	// input is truncated to N points, or zero-padded to N, before the
	// transform. For IRFFTWith and HFFTWith, N is the length of the real
	// output (numpy's default for both being 2*(len(input)-1)). Zero selects
	// numpy's default; a negative N panics. N must be zero for the
	// multi-dimensional transforms.
	N int

	// Norm is numpy's norm: how the forward/inverse pair is scaled. The zero
	// value, NormBackward, is numpy's default and what FFT/IFFT do.
	Norm Norm

	// Axes is numpy's axes: the axes of a row-major array the
	// multi-dimensional transforms run along. Negative values count from the
	// last axis, as in numpy (-1 is the last axis). nil transforms every axis;
	// an empty non-nil slice transforms none. An axis may not appear twice.
	// For RFFTNWith/IRFFTNWith the real transform runs along the LAST axis
	// listed, as numpy.fft.rfftn does. Axes must be nil for the 1-D
	// transforms.
	Axes []int

	// FMA lets the transform fuse its twiddle products with fused
	// multiply-add instructions, which is faster and, measured against
	// numpy.fft, as accurate (see "Fused multiply-add" in the package
	// documentation), but NOT bit-identical to the default: the results
	// differ from FFT's in the last bits. The default (false) keeps every
	// amd64 kernel bit-identical to the package's Go reference. FMA is a
	// request: it takes effect on amd64 CPUs with FMA3, and elsewhere the
	// transform is the default one, bit for bit. The float32 transforms
	// ignore it. NewPlanWith, NewRealPlanWith, NewPlanNWith and
	// NewRealPlan2With take it for plans.
	FMA bool
}

Options carries numpy.fft's optional keyword arguments — n, norm and axes — to the ...With variants of the transforms (FFTWith, RFFTNWith, ...). Its zero value is numpy's defaults, so Options{} makes every ...With function behave exactly like its plain counterpart, and a call names only what it changes:

fft.FFTWith(x, fft.Options{N: 1024, Norm: fft.NormOrtho})
fft.FFTNWith(img, []int{rows, cols}, fft.Options{Axes: []int{-1}})

A field that has no meaning for the transform it is passed to is refused with a panic rather than silently ignored: N belongs to the 1-D transforms, Axes to the multi-dimensional ones.

A struct was chosen over functional options or extra positional arguments because it keeps the existing signatures untouched (adding a variadic parameter to FFT would break every func-typed use of it), because its zero value already is numpy's default for each field (the zero Norm is NormBackward), and because it costs nothing at run time: no closures, no allocation, one type to learn for every transform.

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundC(x []complex128) []complex128 {
	out := make([]complex128, len(x))
	for i, v := range x {
		out[i] = complex(round(real(v)), round(imag(v)))
	}
	return out
}

func main() {
	x := []complex128{1, 2, 3, 4}
	// The zero Options is numpy's defaults: the same as FFT.
	fmt.Println(roundC(fft.FFTWith(x, fft.Options{})))
	// Name only what changes: pad to 8 points, unitary scaling.
	fmt.Println(len(fft.FFTWith(x, fft.Options{N: 8, Norm: fft.NormOrtho})))
}
Output:
[(10+0i) (-2+2i) (-2+0i) (-2-2i)]
8

type Plan

type Plan struct {
	// contains filtered or unexported fields
}

A Plan is a reusable, precomputed transform of a fixed length N. Building a plan factors N and precomputes every twiddle factor once; the resulting tables are then amortized across an unbounded number of FFT/IFFT calls, so repeated transforms of the same length pay no sin/cos cost. Plans are immutable after construction and safe for concurrent use by multiple goroutines (the transform methods write only into the caller-supplied destination and per-call scratch).

For one-off transforms the package-level FFT/IFFT functions are more convenient; they consult an internal plan cache so even they avoid recomputing twiddles for a length they have seen before.

func NewPlan

func NewPlan(n int) *Plan

NewPlan returns a transform plan for length n, precomputing all twiddle factors. n may be any non-negative integer; n == 0 and n == 1 produce a trivial plan. The same plan serves both the forward FFT and the inverse IFFT.

func NewPlanWith added in v0.28.0

func NewPlanWith(n int, o Options) *Plan

NewPlanWith is NewPlan with options: o.FMA asks for an FMA plan (see Options.FMA), which is built where the CPU has FMA3 and is otherwise the plan NewPlan builds. The other fields of Options have no meaning for a plan and must be zero: the length is n, and the plan's methods take a Norm each (FFTNorm, IFFTNorm). NewPlanWith(n, Options{}) is NewPlan(n).

func (*Plan) FFT

func (p *Plan) FFT(dst, src []complex128) []complex128

FFT writes the forward DFT of src into dst and returns dst. dst and src must each have length Len(); dst may alias src. src is not modified unless it aliases dst.

func (*Plan) FFTNorm added in v0.4.0

func (p *Plan) FFTNorm(dst, src []complex128, m Norm) []complex128

FFTNorm is FFT scaled as m says: unscaled under NormBackward, 1/sqrt(n) under NormOrtho, 1/n under NormForward. dst and src follow FFT's rules.

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundC(x []complex128) []complex128 {
	out := make([]complex128, len(x))
	for i, v := range x {
		out[i] = complex(round(real(v)), round(imag(v)))
	}
	return out
}

func main() {
	p := fft.NewPlan(4)
	dst := make([]complex128, 4)
	fmt.Println(roundC(p.FFTNorm(dst, []complex128{1, 1, 1, 1}, fft.NormForward)))
}
Output:
[(1+0i) (0+0i) (0+0i) (0+0i)]

func (*Plan) IFFT

func (p *Plan) IFFT(dst, src []complex128) []complex128

IFFT writes the inverse DFT of src into dst (normalized by N) and returns dst. dst and src must each have length Len(); dst may alias src.

func (*Plan) IFFTNorm added in v0.4.0

func (p *Plan) IFFTNorm(dst, src []complex128, m Norm) []complex128

IFFTNorm is IFFT scaled as m says: 1/n under NormBackward (as IFFT), 1/sqrt(n) under NormOrtho, unscaled under NormForward. dst and src follow IFFT's rules.

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundC(x []complex128) []complex128 {
	out := make([]complex128, len(x))
	for i, v := range x {
		out[i] = complex(round(real(v)), round(imag(v)))
	}
	return out
}

func main() {
	p := fft.NewPlan(4)
	dst := make([]complex128, 4)
	fmt.Println(roundC(p.IFFTNorm(dst, []complex128{1, 0, 0, 0}, fft.NormForward)))
}
Output:
[(1+0i) (1+0i) (1+0i) (1+0i)]

func (*Plan) Len

func (p *Plan) Len() int

Len reports the transform length the plan was built for.

type Plan32 added in v0.6.0

type Plan32 struct {
	// contains filtered or unexported fields
}

A Plan32 is the single-precision counterpart of Plan: a reusable transform of a fixed length N over complex64 data, as scipy.fft computes when it is given complex64 input (and numpy.fft since NumPy 2.0). Every length is accepted and routed as Plan routes it: smooth lengths (prime factors <= 13) to the Stockham passes, a prime whose N-1 is 7-smooth to Rader's algorithm, anything else to Bluestein's chirp-z.

Precision. The arithmetic is float32 throughout, as in FFTW's fftwf_ and pocketfft's float instantiation, but every precomputed table — twiddles, Rader's and Bluestein's kernel spectra, Bluestein's chirp — is computed in float64 by the double-precision engine and rounded once to float32. A table built in float32 (or by a float32 sin/cos recurrence) would carry its own O(eps32·log N) error into every transform; rounded once, each entry is within half an ulp. The error of a transform is then O(eps32·log2 N) relative to the vector's 2-norm; the tests hold it to eps32·log2 N against the DFT of the same input computed in float64, for every length up to 130 and for powers of two up to 2^20, composites and primes beyond. The worst error measured is 0.49 of that bound (N = 2, NormOrtho), 0.14 from 1024 points up.

The engines are separate float32 code, not a generic instantiation of the float64 ones, because a generic engine measured slower on the float64 path. Go does not let real, imag or complex take a type-parameter argument (go.dev/issue/50937), so a generic butterfly has to reach the parts through a conversion to complex128 — free for T = complex128 in itself, but it raises the inliner's cost of the butterflies (bfly4 from 56 to 88, bfly3 from 69 to 175, over the budget of 80), they stop being inlined into the pass loops, and the float64 transforms got 1.44× slower (geometric mean of the Complex/Real/CReal benchmarks, interleaved A/B, Apple M4 Max). The float64 path also dispatches to amd64 assembly through internal/kernels, which a complex64 instantiation would have to bypass anyway.

Plans are immutable after construction and safe for concurrent use.

func NewPlan32 added in v0.6.0

func NewPlan32(n int) *Plan32

NewPlan32 returns a single-precision transform plan for length n, precomputing all twiddle factors. n may be any non-negative integer; n == 0 and n == 1 produce a trivial plan.

func (*Plan32) FFT added in v0.6.0

func (p *Plan32) FFT(dst, src []complex64) []complex64

FFT writes the forward DFT of src into dst, unnormalized, and returns dst. dst and src must each have at least Len() elements; dst may alias src.

func (*Plan32) FFTNorm added in v0.6.0

func (p *Plan32) FFTNorm(dst, src []complex64, norm Norm) []complex64

FFTNorm writes the forward DFT of src into dst, scaled as norm says (1 for NormBackward, 1/sqrt(N) for NormOrtho, 1/N for NormForward), and returns dst. It panics on a Norm that is not one of the three constants.

Example

A Plan32 is reused across transforms of one length, here with the unitary scaling (numpy's norm="ortho"): both directions divide by sqrt(N).

package main

import (
	"fmt"

	"github.com/go-fft/fft"
)

// tidy turns the zeros of v positive for printing: whether an exactly
// cancelling sum comes out +0 or -0 depends on the CPU's fused multiply-add
// (loong64 and amd64 differ), and the sign of zero carries no information here.
func tidy(v []complex64) []complex64 {
	out := make([]complex64, len(v))
	for i, z := range v {
		out[i] = complex(real(z)+0, imag(z)+0)
	}
	return out
}

func main() {
	p := fft.NewPlan32(4)
	x := []complex64{1, 1, 1, 1}
	X := p.FFTNorm(make([]complex64, 4), x, fft.NormOrtho)
	fmt.Println(tidy(X))
	fmt.Println(tidy(p.IFFTNorm(make([]complex64, 4), X, fft.NormOrtho)))
}
Output:
[(2+0i) (0+0i) (0+0i) (0+0i)]
[(1+0i) (1+0i) (1+0i) (1+0i)]

func (*Plan32) IFFT added in v0.6.0

func (p *Plan32) IFFT(dst, src []complex64) []complex64

IFFT writes the inverse DFT of src into dst, normalized by N, and returns dst. dst and src must each have at least Len() elements; dst may alias src.

func (*Plan32) IFFTNorm added in v0.6.0

func (p *Plan32) IFFTNorm(dst, src []complex64, norm Norm) []complex64

IFFTNorm writes the inverse DFT of src into dst, scaled as norm says (1/N for NormBackward, 1/sqrt(N) for NormOrtho, 1 for NormForward), and returns dst. It panics on a Norm that is not one of the three constants.

func (*Plan32) Len added in v0.6.0

func (p *Plan32) Len() int

Len reports the transform length the plan was built for.

type PlanN

type PlanN struct {
	// contains filtered or unexported fields
}

A PlanN is a reusable N-dimensional complex transform of a fixed row-major (C-order) shape: the N-D counterpart of Plan, the way FFTW's fftw_plan_dft(rank, dims, …) is the counterpart of its 1-D plans. Building it resolves one 1-D plan per axis; FFT and IFFT then write into a caller-supplied slice and, in steady state, allocate nothing — unlike FFTN/FFT2, which return a new slice each call. On a many-core host that allocation is what dominates a small repeated transform: a 128×128 FFT2 spent about three quarters of its time in the garbage collector on a 128-thread machine (see BENCHMARKS.md).

PlanN is immutable after construction and safe for concurrent use; each call borrows private scratch from a pool.

func NewPlanN

func NewPlanN(shape ...int) *PlanN

NewPlanN returns a plan for arrays of the given row-major shape. Every length must be positive; NewPlanN panics otherwise. An empty shape is the single-element (scalar) array.

func NewPlanNWith added in v0.28.0

func NewPlanNWith(shape []int, o Options) *PlanN

NewPlanNWith is NewPlanN with options: o.FMA asks for an FMA plan (see Options.FMA), whose every axis, the ones transformed as strips of batched passes included, runs on FMA plans. The other fields must be zero, as for NewPlanWith. NewPlanNWith(shape, Options{}) is NewPlanN(shape...).

func (*PlanN) FFT

func (p *PlanN) FFT(dst, src []complex128) []complex128

FFT writes the forward N-dimensional DFT of src into dst and returns dst. dst and src must each have length Len(); dst may alias src. It matches FFTN.

func (*PlanN) FFTNorm added in v0.4.0

func (p *PlanN) FFTNorm(dst, src []complex128, m Norm) []complex128

FFTNorm is FFT scaled as m says, n being Len(), the product of the shape (see Plan.FFTNorm).

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundC(x []complex128) []complex128 {
	out := make([]complex128, len(x))
	for i, v := range x {
		out[i] = complex(round(real(v)), round(imag(v)))
	}
	return out
}

func main() {
	p := fft.NewPlanN(2, 2)
	dst := make([]complex128, 4)
	fmt.Println(roundC(p.FFTNorm(dst, []complex128{1, 1, 1, 1}, fft.NormOrtho)))
}
Output:
[(2+0i) (0+0i) (0+0i) (0+0i)]

func (*PlanN) IFFT

func (p *PlanN) IFFT(dst, src []complex128) []complex128

IFFT writes the inverse N-dimensional DFT of src into dst, normalized by Len(), and returns dst. dst may alias src. It matches IFFTN.

func (*PlanN) IFFTNorm added in v0.4.0

func (p *PlanN) IFFTNorm(dst, src []complex128, m Norm) []complex128

IFFTNorm is IFFT scaled as m says, n being Len(), the product of the shape (see Plan.IFFTNorm).

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundC(x []complex128) []complex128 {
	out := make([]complex128, len(x))
	for i, v := range x {
		out[i] = complex(round(real(v)), round(imag(v)))
	}
	return out
}

func main() {
	p := fft.NewPlanN(2, 2)
	dst := make([]complex128, 4)
	fmt.Println(roundC(p.IFFTNorm(dst, []complex128{2, 0, 0, 0}, fft.NormOrtho)))
}
Output:
[(1+0i) (1+0i) (1+0i) (1+0i)]

func (*PlanN) Len

func (p *PlanN) Len() int

Len reports the number of elements the plan transforms (the product of its shape).

func (*PlanN) Shape

func (p *PlanN) Shape() []int

Shape returns a copy of the plan's shape.

type PlanN32 added in v0.9.0

type PlanN32 struct {
	// contains filtered or unexported fields
}

A PlanN32 is the single-precision counterpart of PlanN: a reusable N-dimensional complex64 transform of a fixed row-major (C-order) shape, as scipy.fft.fftn computes for complex64 input. It runs a Plan32 along each axis; FFT and IFFT write into a caller-supplied slice. PlanN32 is immutable after construction and safe for concurrent use.

Example

A PlanN32 serves repeated transforms of one shape, writing into the caller's slice.

p := fft.NewPlanN32(2, 3)
x := []complex64{1, 1, 1, 1, 1, 1}
X := p.FFTNorm(make([]complex64, p.Len()), x, fft.NormOrtho)
fmt.Println(round4f32([]float32{real(X[0])}), tidy(X[1:]))
Output:
[2.4495] [(0+0i) (0+0i) (0+0i) (0+0i) (0+0i)]

func NewPlanN32 added in v0.9.0

func NewPlanN32(shape ...int) *PlanN32

NewPlanN32 returns a single-precision plan for arrays of the given row-major shape. Every length must be positive; NewPlanN32 panics otherwise. An empty shape is the single-element (scalar) array.

func (*PlanN32) FFT added in v0.9.0

func (p *PlanN32) FFT(dst, src []complex64) []complex64

FFT writes the forward N-dimensional DFT of src into dst, unnormalized, and returns dst. dst and src must each have length Len(); dst may alias src. It matches FFTN32.

func (*PlanN32) FFTNorm added in v0.9.0

func (p *PlanN32) FFTNorm(dst, src []complex64, m Norm) []complex64

FFTNorm is FFT scaled as m says, n being Len(): unscaled under NormBackward, 1/sqrt(n) under NormOrtho, 1/n under NormForward.

func (*PlanN32) IFFT added in v0.9.0

func (p *PlanN32) IFFT(dst, src []complex64) []complex64

IFFT writes the inverse N-dimensional DFT of src into dst, normalized by Len(), and returns dst. dst may alias src. It matches IFFTN32.

func (*PlanN32) IFFTNorm added in v0.9.0

func (p *PlanN32) IFFTNorm(dst, src []complex64, m Norm) []complex64

IFFTNorm is IFFT scaled as m says, n being Len(): 1/n under NormBackward (as IFFT), 1/sqrt(n) under NormOrtho, unscaled under NormForward.

func (*PlanN32) Len added in v0.9.0

func (p *PlanN32) Len() int

Len reports the number of elements the plan transforms (the product of its shape).

func (*PlanN32) Shape added in v0.9.0

func (p *PlanN32) Shape() []int

Shape returns a copy of the plan's shape.

type RealPlan

type RealPlan struct {
	// contains filtered or unexported fields
}

A RealPlan is a reusable, precomputed real-input transform of a fixed length N. Like Plan it amortizes all twiddle computation across calls; it is the real-signal counterpart used by RFFT/IRFFT. RealPlans are immutable after construction and safe for concurrent use.

For even N the plan packs the N real samples into an N/2-point complex transform and untangles the result with a precomputed table — about twice as fast as a full complex FFT. For odd N (which cannot be packed) it wraps a full complex plan of length N.

func NewRealPlan

func NewRealPlan(n int) *RealPlan

NewRealPlan returns a real-input transform plan for length n, precomputing all twiddle factors. n must be non-negative.

func NewRealPlanWith added in v0.28.0

func NewRealPlanWith(n int, o Options) *RealPlan

NewRealPlanWith is NewRealPlan with options: o.FMA asks for an FMA plan (see Options.FMA), whose complex half (or, for an odd n, full) transform is an FMA plan; the untangle is the default one. The other fields must be zero, as for NewPlanWith. NewRealPlanWith(n, Options{}) is NewRealPlan(n).

func (*RealPlan) IRFFT

func (p *RealPlan) IRFFT(dst []float64, src []complex128) []float64

IRFFT writes the real signal of length Len() reconstructed from the half spectrum src into dst and returns dst. dst must have length Len(); src is read up to min(len(src), Len()/2+1) bins. The result is normalized by N. src is not modified.

For even N the inverse mirrors the forward packing: the half spectrum is pre-processed into an N/2-point complex spectrum, run through one N/2-point inverse complex FFT, and unpacked — half the arithmetic and memory traffic of promoting to a full conjugate-symmetric length-N inverse transform. For odd N (and the trivial N<=1) it falls back to the full conjugate-mirror inverse.

func (*RealPlan) IRFFTNorm added in v0.4.0

func (p *RealPlan) IRFFTNorm(dst []float64, src []complex128, m Norm) []float64

IRFFTNorm is IRFFT scaled as m says (see Plan.IFFTNorm). dst and src follow IRFFT's rules.

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundR(x []float64) []float64 {
	out := make([]float64, len(x))
	for i, v := range x {
		out[i] = round(v)
	}
	return out
}

func main() {
	p := fft.NewRealPlan(4)
	dst := make([]float64, 4)
	fmt.Println(roundR(p.IRFFTNorm(dst, []complex128{2, 0, 0}, fft.NormOrtho)))
}
Output:
[1 1 1 1]

func (*RealPlan) Len

func (p *RealPlan) Len() int

Len reports the real-input length the plan was built for.

func (*RealPlan) RFFT

func (p *RealPlan) RFFT(dst []complex128, src []float64) []complex128

RFFT writes the non-redundant N/2+1 spectral bins of the real signal src into dst and returns dst. src must have length Len(); dst must have length Len()/2+1. src is not modified. dst may share memory with src (which only an unsafe conversion can arrange): the transform then goes through a private buffer instead of building the half-length spectrum in dst.

func (*RealPlan) RFFTNorm added in v0.4.0

func (p *RealPlan) RFFTNorm(dst []complex128, src []float64, m Norm) []complex128

RFFTNorm is RFFT scaled as m says (see Plan.FFTNorm). dst and src follow RFFT's rules.

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundC(x []complex128) []complex128 {
	out := make([]complex128, len(x))
	for i, v := range x {
		out[i] = complex(round(real(v)), round(imag(v)))
	}
	return out
}

func main() {
	p := fft.NewRealPlan(4)
	dst := make([]complex128, 3)
	fmt.Println(roundC(p.RFFTNorm(dst, []float64{1, 1, 1, 1}, fft.NormOrtho)))
}
Output:
[(2+0i) (0+0i) (0+0i)]

type RealPlan2

type RealPlan2 struct {
	// contains filtered or unexported fields
}

A RealPlan2 is a reusable real 2-D transform of a fixed rows×cols shape: the plan behind RFFT2/IRFFT2, as PlanN is behind FFTN. Its RFFT and IRFFT write into caller-supplied slices, so a repeated transform of an even-width matrix allocates nothing in steady state (an odd width goes through the full complex row transform, which allocates). It is immutable after construction and safe for concurrent use.

func NewRealPlan2

func NewRealPlan2(rows, cols int) *RealPlan2

NewRealPlan2 returns a plan for real rows×cols matrices. Both lengths must be positive; NewRealPlan2 panics otherwise.

func NewRealPlan2With added in v0.28.0

func NewRealPlan2With(rows, cols int, o Options) *RealPlan2

NewRealPlan2With is NewRealPlan2 with options: o.FMA asks for an FMA plan (see Options.FMA), whose row and column transforms are FMA plans. The other fields must be zero, as for NewPlanWith. NewRealPlan2With(rows, cols, Options{}) is NewRealPlan2(rows, cols).

func (*RealPlan2) IRFFT

func (p *RealPlan2) IRFFT(dst []float64, src []complex128) []float64

IRFFT writes the real matrix (rows×cols) whose spectrum is src (SpectrumLen bins, the RFFT layout) into dst, normalized by rows×cols, and returns dst. It matches IRFFT2. src is not modified.

func (*RealPlan2) IRFFTNorm added in v0.4.0

func (p *RealPlan2) IRFFTNorm(dst []float64, src []complex128, m Norm) []float64

IRFFTNorm is IRFFT scaled as m says, n being rows×cols (see Plan.IFFTNorm).

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundR(x []float64) []float64 {
	out := make([]float64, len(x))
	for i, v := range x {
		out[i] = round(v)
	}
	return out
}

func main() {
	p := fft.NewRealPlan2(2, 2)
	dst := make([]float64, 4)
	fmt.Println(roundR(p.IRFFTNorm(dst, []complex128{1, 0, 0, 0}, fft.NormForward)))
}
Output:
[1 1 1 1]

func (*RealPlan2) RFFT

func (p *RealPlan2) RFFT(dst []complex128, src []float64) []complex128

RFFT writes the forward 2-D DFT of the real row-major matrix src (rows×cols) into dst (rows×(cols/2+1), SpectrumLen bins) and returns dst. It matches RFFT2. src is not modified.

func (*RealPlan2) RFFTNorm added in v0.4.0

func (p *RealPlan2) RFFTNorm(dst []complex128, src []float64, m Norm) []complex128

RFFTNorm is RFFT scaled as m says, n being rows×cols (see Plan.FFTNorm).

Example
package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

// round keeps four decimals and turns -0 into 0, so the examples print stable
// output across architectures.
func round(v float64) float64 {
	r := math.Round(v*1e4) / 1e4
	if r == 0 {
		return 0
	}
	return r
}

func roundC(x []complex128) []complex128 {
	out := make([]complex128, len(x))
	for i, v := range x {
		out[i] = complex(round(real(v)), round(imag(v)))
	}
	return out
}

func main() {
	p := fft.NewRealPlan2(2, 2)
	dst := make([]complex128, p.SpectrumLen())
	fmt.Println(roundC(p.RFFTNorm(dst, []float64{1, 1, 1, 1}, fft.NormForward)))
}
Output:
[(1+0i) (0+0i) (0+0i) (0+0i)]

func (*RealPlan2) SpectrumLen

func (p *RealPlan2) SpectrumLen() int

SpectrumLen is the number of bins RFFT writes and IRFFT reads: rows×(cols/2+1).

type RealPlan2_32 added in v0.9.0

type RealPlan2_32 struct {
	// contains filtered or unexported fields
}

A RealPlan2_32 is the single-precision counterpart of RealPlan2: a reusable real 2-D transform of a fixed rows×cols shape over float32 samples and complex64 bins. It is immutable after construction and safe for concurrent use.

func NewRealPlan2_32 added in v0.9.0

func NewRealPlan2_32(rows, cols int) *RealPlan2_32

NewRealPlan2_32 returns a single-precision plan for real rows×cols matrices. Both lengths must be positive; NewRealPlan2_32 panics otherwise.

func (*RealPlan2_32) IRFFT added in v0.9.0

func (p *RealPlan2_32) IRFFT(dst []float32, src []complex64) []float32

IRFFT writes the real matrix (rows×cols) whose spectrum is src (SpectrumLen bins, the RFFT layout) into dst, normalized by rows×cols, and returns dst. It matches IRFFT2_32. src is not modified.

func (*RealPlan2_32) IRFFTNorm added in v0.9.0

func (p *RealPlan2_32) IRFFTNorm(dst []float32, src []complex64, m Norm) []float32

IRFFTNorm is IRFFT scaled as m says, n being rows×cols (see Plan32.IFFTNorm). The rows are inverted unscaled and the factor is applied once, rounded once to float32.

func (*RealPlan2_32) RFFT added in v0.9.0

func (p *RealPlan2_32) RFFT(dst []complex64, src []float32) []complex64

RFFT writes the forward 2-D DFT of the real row-major matrix src (rows×cols) into dst (rows×(cols/2+1), SpectrumLen bins), unnormalized, and returns dst. It matches RFFT2_32. src is not modified.

func (*RealPlan2_32) RFFTNorm added in v0.9.0

func (p *RealPlan2_32) RFFTNorm(dst []complex64, src []float32, m Norm) []complex64

RFFTNorm is RFFT scaled as m says, n being rows×cols (see Plan32.FFTNorm).

func (*RealPlan2_32) SpectrumLen added in v0.9.0

func (p *RealPlan2_32) SpectrumLen() int

SpectrumLen is the number of bins RFFT writes and IRFFT reads: rows×(cols/2+1).

type RealPlan32 added in v0.6.0

type RealPlan32 struct {
	// contains filtered or unexported fields
}

A RealPlan32 is the single-precision counterpart of RealPlan: a reusable real-input transform of a fixed length N over float32 samples and complex64 bins. For even N the samples are packed into an N/2-point Plan32 and untangled with a table computed in float64 and rounded once; for odd N a full-length Plan32 is used. RealPlan32s are immutable after construction and safe for concurrent use.

Example

A RealPlan32 serves repeated real transforms of one length.

package main

import (
	"fmt"
	"math"

	"github.com/go-fft/fft"
)

func main() {
	p := fft.NewRealPlan32(8)
	x := []float32{0, 1, 0, -1, 0, 1, 0, -1} // two cycles of a sine
	X := p.RFFT(make([]complex64, 5), x)
	for k, v := range X {
		fmt.Printf("bin %d: magnitude %.3f\n", k, math.Hypot(float64(real(v)), float64(imag(v))))
	}
}
Output:
bin 0: magnitude 0.000
bin 1: magnitude 0.000
bin 2: magnitude 4.000
bin 3: magnitude 0.000
bin 4: magnitude 0.000

func NewRealPlan32 added in v0.6.0

func NewRealPlan32(n int) *RealPlan32

NewRealPlan32 returns a single-precision real-input plan for length n (n >= 0).

func (*RealPlan32) IRFFT added in v0.6.0

func (p *RealPlan32) IRFFT(dst []float32, src []complex64) []float32

IRFFT writes the real signal of length Len() reconstructed from the half spectrum src into dst, normalized by N, and returns dst[:N]. src is read up to min(len(src), Len()/2+1) bins; missing bins count as zero, and the imaginary parts of the DC (and, for even N, Nyquist) bins are ignored. src is not modified.

func (*RealPlan32) IRFFTNorm added in v0.6.0

func (p *RealPlan32) IRFFTNorm(dst []float32, src []complex64, norm Norm) []float32

IRFFTNorm is IRFFT scaled as norm says (1/N, 1/sqrt(N) or 1). It panics on a Norm that is not one of the three constants.

func (*RealPlan32) Len added in v0.6.0

func (p *RealPlan32) Len() int

Len reports the real-input length the plan was built for.

func (*RealPlan32) RFFT added in v0.6.0

func (p *RealPlan32) RFFT(dst []complex64, src []float32) []complex64

RFFT writes the N/2+1 non-redundant bins of the real signal src into dst, unnormalized, and returns dst[:N/2+1]. src must have at least Len() elements and dst at least Len()/2+1. src is not modified.

func (*RealPlan32) RFFTNorm added in v0.6.0

func (p *RealPlan32) RFFTNorm(dst []complex64, src []float32, norm Norm) []complex64

RFFTNorm is RFFT scaled as norm says (1, 1/sqrt(N) or 1/N). It panics on a Norm that is not one of the three constants.

Directories

Path Synopsis
internal
kernels
Package kernels holds the inner loops of the FFT — the bit-reversal permutation and the radix-2 butterfly stages — isolated from the public API.
Package kernels holds the inner loops of the FFT — the bit-reversal permutation and the radix-2 butterfly stages — isolated from the public API.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL