// Copyright ©2018 The Gonum Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fourier import "gonum.org/v1/gonum/dsp/fourier/internal/fftpack" // FFT implements Fast Fourier Transform and its inverse for real sequences. type FFT struct { work []float64 ifac [15]int // real temporarily store complex data as // pairs of real values to allow passing to // the backing code. The length of real // must always be half the length of work. real []float64 } // NewFFT returns an FFT initialized for work on sequences of length n. func NewFFT(n int) *FFT { var t FFT t.Reset(n) return &t } // Len returns the length of the acceptable input. func (t *FFT) Len() int { return len(t.real) } // Reset reinitializes the FFT for work on sequences of length n. func (t *FFT) Reset(n int) { if 2*n <= cap(t.work) { t.work = t.work[:2*n] t.real = t.real[:n] } else { t.work = make([]float64, 2*n) t.real = make([]float64, n) } fftpack.Rffti(n, t.work, t.ifac[:]) } // Coefficients computes the Fourier coefficients of the input sequence, // converting the time series in seq into the frequency spectrum, placing // the result in dst and returning it. This transform is unnormalized; a // call to Coefficients followed by a call of Sequence will multiply the // input sequence by the length of the sequence. // // If the length of seq is not t.Len(), Coefficients will panic. // If dst is nil, a new slice is allocated and returned. If dst is not nil and // the length of dst does not equal t.Len()/2+1, Coefficients will panic. func (t *FFT) Coefficients(dst []complex128, seq []float64) []complex128 { if len(seq) != t.Len() { panic("fourier: sequence length mismatch") } if dst == nil { dst = make([]complex128, t.Len()/2+1) } else if len(dst) != t.Len()/2+1 { panic("fourier: destination length mismatch") } copy(t.real, seq) fftpack.Rfftf(len(t.real), t.real, t.work, t.ifac[:]) dst[0] = complex(t.real[0], 0) if len(seq) < 2 { return dst } if len(seq)%2 == 1 { dst[len(dst)-1] = complex(t.real[len(t.real)-2], t.real[len(t.real)-1]) } else { dst[len(dst)-1] = complex(t.real[len(t.real)-1], 0) } for i := 1; i < len(dst)-1; i++ { dst[i] = complex(t.real[2*i-1], t.real[2*i]) } return dst } // Sequence computes the real periodic sequence from the Fourier coefficients, // converting the frequency spectrum in coeff into a time series, placing the // result in dst and returning it. This transform is unnormalized; a call to // Coefficients followed by a call of Sequence will multiply the input sequence // by the length of the sequence. // // If the length of coeff is not t.Len()/2+1, Sequence will panic. // If dst is nil, a new slice is allocated and returned. If dst is not nil and // the length of dst does not equal the length of coeff, Sequence will panic. func (t *FFT) Sequence(dst []float64, coeff []complex128) []float64 { if len(coeff) != t.Len()/2+1 { panic("fourier: coefficients length mismatch") } if dst == nil { dst = make([]float64, t.Len()) } else if len(dst) != t.Len() { panic("fourier: destination length mismatch") } dst[0] = real(coeff[0]) if len(dst) < 2 { return dst } nf := coeff[len(coeff)-1] if len(dst)%2 == 1 { dst[len(dst)-2] = real(nf) dst[len(dst)-1] = imag(nf) } else { dst[len(dst)-1] = real(nf) } for i, cv := range coeff[1 : len(coeff)-1] { dst[2*i+1] = real(cv) dst[2*i+2] = imag(cv) } fftpack.Rfftb(len(dst), dst, t.work, t.ifac[:]) return dst } // Freq returns the relative frequency center for coefficient i. // Freq will panic if i is negative or greater than or equal to t.Len(). func (t *FFT) Freq(i int) float64 { if i < 0 || t.Len() <= i { panic("fourier: index out of range") } step := 1 / float64(t.Len()) return step * float64(i) } // CmplxFFT implements Fast Fourier Transform and its inverse for complex sequences. type CmplxFFT struct { work []float64 ifac [15]int // real temporarily store complex data as // pairs of real values to allow passing to // the backing code. The length of real // must always be half the length of work. real []float64 } // NewCmplxFFT returns an CmplxFFT initialized for work on sequences of length n. func NewCmplxFFT(n int) *CmplxFFT { var t CmplxFFT t.Reset(n) return &t } // Len returns the length of the acceptable input. func (t *CmplxFFT) Len() int { return len(t.work) / 4 } // Reset reinitializes the FFT for work on sequences of length n. func (t *CmplxFFT) Reset(n int) { if 4*n <= cap(t.work) { t.work = t.work[:4*n] t.real = t.real[:2*n] } else { t.work = make([]float64, 4*n) t.real = make([]float64, 2*n) } fftpack.Cffti(n, t.work, t.ifac[:]) } // Coefficients computes the Fourier coefficients of a complex input sequence, // converting the time series in seq into the frequency spectrum, placing // the result in dst and returning it. This transform is unnormalized; a call // to Coefficients followed by a call of Sequence will multiply the input // sequence by the length of the sequence. // // If the length of seq is not t.Len(), Coefficients will panic. // If dst is nil, a new slice is allocated and returned. If dst is not nil and // the length of dst does not equal the length of seq, Coefficients will panic. // It is safe to use the same slice for dst and seq. func (t *CmplxFFT) Coefficients(dst, seq []complex128) []complex128 { if len(seq) != t.Len() { panic("fourier: sequence length mismatch") } if dst == nil { dst = make([]complex128, len(seq)) } else if len(dst) != len(seq) { panic("fourier: destination length mismatch") } for i, cv := range seq { t.real[2*i] = real(cv) t.real[2*i+1] = imag(cv) } fftpack.Cfftf(len(dst), t.real, t.work, t.ifac[:]) for i := range dst { dst[i] = complex(t.real[2*i], t.real[2*i+1]) } return dst } // Sequence computes the complex periodic sequence from the Fourier coefficients, // converting the frequency spectrum in coeff into a time series, placing the // result in dst and returning it. This transform is unnormalized; a call to // Coefficients followed by a call of Sequence will multiply the input sequence // by the length of the sequence. // // If the length of coeff is not t.Len(), Sequence will panic. // If dst is nil, a new slice is allocated and returned. If dst is not nil and // the length of dst does not equal the length of coeff, Sequence will panic. // It is safe to use the same slice for dst and coeff. func (t *CmplxFFT) Sequence(dst, coeff []complex128) []complex128 { if len(coeff) != t.Len() { panic("fourier: coefficients length mismatch") } if dst == nil { dst = make([]complex128, len(coeff)) } else if len(dst) != len(coeff) { panic("fourier: destination length mismatch") } for i, cv := range coeff { t.real[2*i] = real(cv) t.real[2*i+1] = imag(cv) } fftpack.Cfftb(len(dst), t.real, t.work, t.ifac[:]) for i := range dst { dst[i] = complex(t.real[2*i], t.real[2*i+1]) } return dst } // Freq returns the relative frequency center for coefficient i. // Freq will panic if i is negative or greater than or equal to t.Len(). func (t *CmplxFFT) Freq(i int) float64 { if i < 0 || t.Len() <= i { panic("fourier: index out of range") } step := 1 / float64(t.Len()) if i < (t.Len()-1)/2+1 { return step * float64(i) } return step * float64(i-t.Len()) } // ShiftIdx returns a shifted index into a slice of coefficients // returned by the CmplxFFT so that indexing into the coefficients // places the zero frequency component at the center of the spectrum. // ShiftIdx will panic if i is negative or greater than or equal to // t.Len(). func (t *CmplxFFT) ShiftIdx(i int) int { if i < 0 || t.Len() <= i { panic("fourier: index out of range") } h := t.Len() / 2 if i < h { return i + (t.Len()+1)/2 } return i - h } // UnshiftIdx returns inverse of ShiftIdx. UnshiftIdx will panic if i is // negative or greater than or equal to t.Len(). func (t *CmplxFFT) UnshiftIdx(i int) int { if i < 0 || t.Len() <= i { panic("fourier: index out of range") } h := (t.Len() + 1) / 2 if i < h { return i + t.Len()/2 } return i - h }