diff --git a/README.md b/README.md index bfab241..dc55985 100644 --- a/README.md +++ b/README.md @@ -11,22 +11,25 @@ Transform (FFT) library written in pure Rust. ## Features -- Simple implementation using a single, general-purpose FFT algorithm +- Simple implementation using the Cooley-Tukey FFT algorithm - Performance on par with other Rust FFT implementations - Zero `unsafe` code -- Takes advantage of latest CPU features up to and including AVX-512, but performs well even without them +- Takes advantage of latest CPU features up to and including `AVX-512`, but performs well even without them - Optional parallelization of some steps to 2 threads (with even more planned) - 2x lower memory usage than [RustFFT](https://crates.io/crates/rustfft/) - Python bindings (via [PyO3](https://github.com/PyO3/pyo3)) ## Limitations +- Only supports input with a length of `2^n` (i.e., a power of 2) -- input should be padded with zeros to the next power + of 2 - No runtime CPU feature detection (yet). Right now achieving the highest performance requires compiling - with `-C target-cpu=native` or [`cargo multivers`](https://github.com/ronnychevalier/cargo-multivers). + with `-C target-cpu=native` or [`cargo multivers`](https://github.com/ronnychevalier/cargo-multivers) - Requires nightly Rust compiler due to use of portable SIMD ## Planned features +- Bluestein's algorithm (to handle arbitrary sized FFTs) - Runtime CPU feature detection - More multi-threading - More work on cache-optimal FFT @@ -39,7 +42,7 @@ years or so). The two major bottlenecks in FFT are the **CPU cycles** and **memory accesses**. We picked an efficient, general-purpose FFT algorithm. Our implementation can make use of latest CPU features such as -AVX-512, but performs well even without them. +`AVX-512`, but performs well even without them. Our key insight for speeding up memory accesses is that FFT is equivalent to applying gates to all qubits in `[0, n)`. This creates the opportunity to leverage the same memory access patterns as @@ -130,7 +133,7 @@ submit a pull request. Follow the contribution guidelines outlined in the CONTRI PhastFT is licensed under MIT or Apache 2.0 license, at your option. -## PhastFT vs RustFFT +## PhastFT vs. RustFFT [RustFFT](https://crates.io/crates/rustfft/) is another excellent FFT implementation in pure Rust. RustFFT and PhastFT make different trade-offs. diff --git a/pyphastft/32_bins_demo.mov b/pyphastft/32_bins_demo.mov deleted file mode 100644 index d2fda1e..0000000 Binary files a/pyphastft/32_bins_demo.mov and /dev/null differ diff --git a/pyphastft/example.py b/pyphastft/example.py index 82ebd88..31e2e5b 100644 --- a/pyphastft/example.py +++ b/pyphastft/example.py @@ -1,7 +1,8 @@ import matplotlib.pyplot as plt import numpy as np +from numpy.fft import fft -from pyphastft import fft +# from pyphastft import fft def main(): @@ -16,7 +17,7 @@ def main(): ) # Adjusted time vector # Generate the signal - s_re = 2 * np.sin(2 * np.pi * t) + np.sin(2 * np.pi * 10 * t) + s_re = 2 * np.sin(2 * np.pi * t + 1) + np.sin(2 * np.pi * 10 * t + 1) s_im = np.ascontiguousarray([0.0] * len(s_re), dtype=np.float64) # Plot the original signal @@ -30,7 +31,7 @@ def main(): plt.legend() # Perform FFT - fft(s_re, s_im, direction="f") + s_re = fft(s_re) # Plot the magnitude spectrum of the FFT result plt.subplot(2, 1, 2) diff --git a/pyphastft/src/lib.rs b/pyphastft/src/lib.rs index 7dee962..f69b64f 100644 --- a/pyphastft/src/lib.rs +++ b/pyphastft/src/lib.rs @@ -1,5 +1,5 @@ use numpy::PyReadwriteArray1; -use phastft::{fft as fft_rs, planner::Direction}; +use phastft::{fft_64 as fft_64_rs, planner::Direction}; use pyo3::prelude::*; #[pyfunction] @@ -11,7 +11,7 @@ fn fft(mut reals: PyReadwriteArray1, mut imags: PyReadwriteArray1, dir Direction::Reverse }; - fft_rs( + fft_64_rs( reals.as_slice_mut().unwrap(), imags.as_slice_mut().unwrap(), dir, diff --git a/pyphastft/vis_qt.py b/pyphastft/vis_qt.py index 4d8dba2..2a5c5e7 100644 --- a/pyphastft/vis_qt.py +++ b/pyphastft/vis_qt.py @@ -1,9 +1,10 @@ -from pyphastft import fft +import sys + import numpy as np import pyaudio import pyqtgraph as pg +from pyphastft import fft from pyqtgraph.Qt import QtWidgets, QtCore -import sys class RealTimeAudioSpectrum(QtWidgets.QWidget): @@ -28,7 +29,7 @@ def init_ui(self): self.plot_widget.setBackground("k") self.plot_item = self.plot_widget.getPlotItem() self.plot_item.setTitle( - "Real-Time Audio Spectrum Visualizer\npowered by PhastFT", + "Real-Time Audio Spectrum Visualizer powered by PhastFT", color="w", size="16pt", ) @@ -79,10 +80,10 @@ def init_audio_stream(self): def audio_callback(self, in_data, frame_count, time_info, status): audio_data = np.frombuffer(in_data, dtype=np.float32) reals = np.zeros(self.n_fft_bins) - imaginaries = np.zeros(self.n_fft_bins) + imags = np.zeros(self.n_fft_bins) reals[: len(audio_data)] = audio_data # Fill the reals array with audio data - fft(reals, imaginaries, direction="f") - fft_magnitude = np.sqrt(reals**2 + imaginaries**2)[: self.n_fft_bins // 2] + fft(reals, imags, direction="f") + fft_magnitude = np.sqrt(reals**2 + imags**2)[: self.n_fft_bins // 2] # Aggregate or interpolate FFT data to fit into display bins new_fft_data = np.interp( @@ -93,13 +94,12 @@ def audio_callback(self, in_data, frame_count, time_info, status): # Apply exponential moving average filter self.ema_fft_data = self.ema_fft_data * self.smoothing_factor + new_fft_data * ( - 1 - self.smoothing_factor + 1.0 - self.smoothing_factor ) - return (in_data, pyaudio.paContinue) + return in_data, pyaudio.paContinue def update(self): - if hasattr(self, "ema_fft_data"): - self.bar_graph.setOpts(height=self.ema_fft_data, width=self.bar_width) + self.bar_graph.setOpts(height=self.ema_fft_data, width=self.bar_width) def closeEvent(self, event): self.stream.stop_stream() diff --git a/src/lib.rs b/src/lib.rs index 7c7e4b4..257a9f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,8 +30,7 @@ macro_rules! impl_fft_for { /// /// # Panics /// - /// Panics if `reals.len() != imags.len()` or if `reals.len()` and `imags.len()` are not a power of - /// 2 + /// Panics if `reals.len() != imags.len()`, or if the input length is _not_ a power of 2. /// /// ## References /// @@ -77,7 +76,7 @@ macro_rules! impl_fft_with_opts_and_plan_for { /// /// # Panics /// - /// Panics if `reals.len() != imags.len()`, or if the input length is *not* a power of 2. + /// Panics if `reals.len() != imags.len()`, or if the input length is _not_ a power of 2. pub fn $func_name( reals: &mut [$precision], imags: &mut [$precision],