From a92bea1747d5d5deaa5067d92757b22b580e04d9 Mon Sep 17 00:00:00 2001 From: Saveliy Yusufov Date: Mon, 12 Feb 2024 16:41:25 -0500 Subject: [PATCH] Update documentation and readme --- .gitignore | 181 +++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 3 +- README.md | 24 +++--- pybindings/Cargo.toml | 2 + src/cobra.rs | 21 ++--- src/lib.rs | 25 ++++-- src/options.rs | 8 +- src/planner.rs | 17 +++- 8 files changed, 246 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index 3a8cabc..bcc088a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,179 @@ -/target -.idea +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ + +# macOS +.DS_Store diff --git a/Cargo.toml b/Cargo.toml index 17b00e9..bc9575b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,13 +2,14 @@ name = "phastft" version = "0.1.0" edition = "2021" - authors = ["Saveliy Yusufov", "Shnatsel"] license = "MIT OR Apache-2.0" description = "A high-performance, quantum-inspired, implementation of FFT in pure Rust" repository = "https://github.com/QuState/PhastFT" keywords = ["quantum", "fft", "discrete", "fourier", "transform"] categories = ["algorithms", "compression", "science"] +exclude = ["assets", "scripts", "benches"] + [dev-dependencies] utilities = { path = "utilities" } diff --git a/README.md b/README.md index 2304d36..423a995 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![Build](https://github.com/QuState/PhastFT/actions/workflows/rust.yml/badge.svg)](https://github.com/QuState/PhastFT/actions/workflows/rust.yml) [![codecov](https://codecov.io/gh/QuState/PhastFT/graph/badge.svg?token=IM86XMURHN)](https://codecov.io/gh/QuState/PhastFT) +[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) # PhastFT @@ -59,13 +60,14 @@ less memory. ### Rust ```rust -fn main() { - let N = 1 << 10; - let mut reals = vec![0.0; N]; - let mut imags = vec![0.0; N]; - gen_random_signal(&mut reals, &mut imags); +use phastft::planner::Direction; +use phastft::fft; - fft_dif(&mut reals, &mut imags); +fn main() { + let big_n = 1 << 10; + let mut reals: Vec = (1..=big_n).map(|i| i as f64).collect(); + let mut imags: Vec = (1..=big_n).map(|i| i as f64).collect(); + fft(&mut reals, &mut imags, Direction::Forward); } ``` @@ -97,16 +99,16 @@ fft(a_re, a_im) ## Benchmarks PhastFT is benchmarked against several other FFT libraries. Scripts and instructions to reproduce benchmark results and -plots are available [here](benches). +plots are available [here](https://github.com/QuState/PhastFT/tree/main/benches#readme).

- PhastFT vs. RustFFT vs. FFTW3 - PhastFT vs. RustFFT vs. FFTW3 + PhastFT vs. RustFFT vs. FFTW3 + PhastFT vs. RustFFT vs. FFTW3

- PhastFT vs. NumPy FFT vs. pyFFTW - PhastFT vs. NumPy FFT vs. pyFFTW + PhastFT vs. NumPy FFT vs. pyFFTW + PhastFT vs. NumPy FFT vs. pyFFTW

## Contributing diff --git a/pybindings/Cargo.toml b/pybindings/Cargo.toml index d4fc15c..df327bc 100644 --- a/pybindings/Cargo.toml +++ b/pybindings/Cargo.toml @@ -2,6 +2,8 @@ name = "pybindings" version = "0.1.0" edition = "2021" +authors = ["Saveliy Yusufov", "Shnatsel"] +license = "MIT OR Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] diff --git a/src/cobra.rs b/src/cobra.rs index 6435590..ed7bef6 100644 --- a/src/cobra.rs +++ b/src/cobra.rs @@ -4,9 +4,10 @@ const BLOCK_WIDTH: usize = 128; // size of the cacheline const LOG_BLOCK_WIDTH: usize = 7; // log2 of cacheline -/// In-place bit reversal on a single buffer. Referred to as "Jennifer's method" -/// in the link below. -/// Source: https://www.katjaas.nl/bitreversal/bitreversal.html +/// In-place bit reversal on a single buffer. Also referred to as "Jennifer's method" [1]. +/// +/// ## References +/// [1] pub(crate) fn bit_rev(buf: &mut [T], log_n: usize) { let mut nodd: usize; let mut noddrev; // to hold bitwise negated or odd values @@ -54,7 +55,9 @@ pub(crate) fn bit_rev(buf: &mut [T], log_n: usize) { } /// Run in-place bit reversal on the entire state (i.e., the reals and imags buffers) -/// Source: https://www.katjaas.nl/bitreversal/bitreversal.html +/// +/// ## References +/// [1] #[allow(unused)] #[deprecated( since = "0.1.0", @@ -74,7 +77,7 @@ fn complex_bit_rev(reals: &mut [Float], imags: &mut [Float], log_n: usize) { let mut i = quartn; while i > 0 { - // start of bitreversed permutation loop, N/4 iterations + // start of bit-reversed permutation loop, N/4 iterations // Gray code generator for even values: @@ -154,16 +157,16 @@ pub(crate) fn bit_reverse_permutation(buf: &mut [T]) { } } -/// Pure Rust implementation of Cache Optimal BitReverse Algorithm (COBRA). -/// Rewritten from a C++ implementation, which is available here: -/// https://bitbucket.org/orserang/bit-reversal-methods/src/master/src_and_bin/src/algorithms/COBRAShuffle.hpp +/// Pure Rust implementation of Cache Optimal Bit-Reverse Algorithm (COBRA). +/// Rewritten from a C++ implementation [3]. /// -/// References +/// ## References /// [1] L. Carter and K. S. Gatlin, "Towards an optimal bit-reversal permutation program," Proceedings 39th Annual /// Symposium on Foundations of Computer Science (Cat. No.98CB36280), Palo Alto, CA, USA, 1998, pp. 544-553, doi: /// 10.1109/SFCS.1998.743505. /// [2] Christian Knauth, Boran Adas, Daniel Whitfield, Xuesong Wang, Lydia Ickler, Tim Conrad, Oliver Serang: /// Practically efficient methods for performing bit-reversed permutation in C++11 on the x86-64 architecture +/// [3] pub(crate) fn cobra_apply(v: &mut [T], log_n: usize) { if log_n <= 2 * LOG_BLOCK_WIDTH { bit_rev(v, log_n); diff --git a/src/lib.rs b/src/lib.rs index 737ff74..b9210c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,11 @@ +#![doc = include_str!("../README.md")] +#![warn(clippy::complexity)] +#![warn(missing_docs)] +#![warn(clippy::style)] +#![warn(clippy::correctness)] +#![warn(clippy::suspicious)] +#![warn(clippy::perf)] +#![forbid(unsafe_code)] #![feature(portable_simd)] use crate::cobra::cobra_apply; @@ -12,16 +20,16 @@ pub mod options; pub mod planner; mod twiddles; -/// FFT -- Decimation in Frequency -/// -/// This is just the decimation-in-time algorithm, reversed. -/// The inputs are in normal order, and the outputs are then bit reversed. +/// FFT -- Decimation in Frequency. This is just the decimation-in-time algorithm, reversed. +/// This call to FFT is run, in-place. +/// The input should be provided in normal order, and then the modified input is bit-reversed. /// /// # Panics /// /// Panics if `reals.len() != imags.len()` /// -/// [1] https://inst.eecs.berkeley.edu/~ee123/sp15/Notes/Lecture08_FFT_and_SpectAnalysis.key.pdf +/// ## References +/// pub fn fft(reals: &mut [Float], imags: &mut [Float], direction: Direction) { assert_eq!( reals.len(), @@ -100,14 +108,17 @@ pub fn fft_with_opts_and_plan( #[cfg(test)] mod tests { - use super::*; - use crate::planner::Direction; use std::ops::Range; + use utilities::{ assert_f64_closeness, rustfft::{num_complex::Complex64, FftPlanner}, }; + use crate::planner::Direction; + + use super::*; + #[should_panic] #[test] fn non_power_of_two_fft() { diff --git a/src/options.rs b/src/options.rs index 07d1086..9903946 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,14 +1,14 @@ -/// Options to tune to improve performance depending on the hardware and input size. -/// +//! Options to tune to improve performance depending on the hardware and input size. + /// Calling FFT routines without specifying options will automatically select reasonable defaults /// depending on the input size and other factors. /// /// You only need to tune these options if you are trying to squeeze maximum performance -/// out of a known hardware platform that you can bechmark at varying input sizes. +/// out of a known hardware platform that you can benchmark at varying input sizes. #[non_exhaustive] #[derive(Debug, Clone, Default)] pub struct Options { - /// Whether to run bit reversal step in 2 threads instead of one. + /// Whether to run the bit reversal step in 2 threads instead of one. /// This is beneficial only at large input sizes (i.e. gigabytes of data). /// The exact threshold where it starts being beneficial varies depending on the hardware. pub multithreaded_bit_reversal: bool, diff --git a/src/planner.rs b/src/planner.rs index 57a0a48..cec8fe4 100644 --- a/src/planner.rs +++ b/src/planner.rs @@ -1,12 +1,27 @@ +//! The planner module provides a convenient interface for planning and executing +//! a Fast Fourier Transform (FFT). Currently, the planner is responsible for +//! pre-computing twiddle factors based on the input signal length, as well as the +//! direction of the FFT. + use crate::twiddles::{generate_twiddles, generate_twiddles_simd}; +/// Reverse is for running the Inverse Fast Fourier Transform (IFFT) +/// Forward is for running the regular FFT pub enum Direction { + /// Leave the exponent term in the twiddle factor alone Forward = 1, + /// Multiply the exponent term in the twiddle factor by -1 Reverse = -1, } +/// The planner is responsible for pre-computing and storing twiddle factors for all the +/// `log_2(N)` stages of the FFT. +/// The amount of twiddle factors should always be a power of 2. In addition, +/// the amount of twiddle factors should always be `(1/2) * N` pub struct Planner { + /// The real components of the twiddle factors pub twiddles_re: Vec, + /// The imaginary components of the twiddle factors pub twiddles_im: Vec, } @@ -18,7 +33,7 @@ impl Planner { /// /// # Panics /// - /// Panics if `num_points` is less than 1 + /// Panics if `num_points < 1` pub fn new(num_points: usize, direction: Direction) -> Self { assert!(num_points > 0 && num_points.is_power_of_two()); if num_points <= 4 {