├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches └── rustfft.rs ├── examples └── concurrency.rs ├── src ├── algorithm │ ├── butterflies.rs │ ├── dft.rs │ ├── good_thomas_algorithm.rs │ ├── mixed_radix.rs │ ├── mod.rs │ ├── raders_algorithm.rs │ └── radix4.rs ├── array_utils.rs ├── common.rs ├── lib.rs ├── math_utils.rs ├── plan.rs ├── test_utils.rs └── twiddles.rs └── tests └── accuracy.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | *.swp 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - 1.26.2 5 | - beta 6 | - nightly 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [3.0.1] 2 | ### Fixed 3 | - Fixed warnings regarding "dyn trait", and warnings regarding inclusive ranges 4 | - Several documentation improvements 5 | 6 | ## [3.0.0] 7 | ### Changed 8 | - Reduced the setup time and memory usage of GoodThomasAlgorithm 9 | - Reduced the setup time and memory usage of RadersAlgorithm 10 | 11 | ### Breaking Changes 12 | - Documented the minimum rustsc version. Before, none was specified. now, it's 1.26. Further increases to minimum version will be a breaking change. 13 | - Increased the version of the num-complex dependency to 0.2. This is a breaking change because we have a public dependency on num-complex. 14 | See the [num-complex changelog](https://github.com/rust-num/num-complex/blob/master/RELEASES.md) for a list of breaking changes in num-complex 0.2 15 | 16 | ## [2.1.0] 17 | ### Added 18 | - Added a specialized implementation of Good Thomas Algorithm for when both inner FFTs are butterflies. (#33) 19 | 20 | ### Changed 21 | - Documentation typo fixes (#27, #35) 22 | - Increased minimum version of num_traits and num_complex. Notably, Complex is now guaranteed to be repr(C) 23 | - Significantly improved the performance of the Radix4 algorithm (#26) 24 | - Reduced memory usage of prime-sized FFTs (#34) 25 | - Incorporated the Good-Thomas Double Butterfly algorithm into the planner, improving performance for most composite and prime FFTs 26 | 27 | ## [2.0.0] 28 | ### Added 29 | - Added implementation of Good Thomas algorithm. 30 | - Added implementation of Raders algorithm. 31 | - Added implementation of Radix algorithm for power-of-two lengths. 32 | - Added `FFTPlanner` to choose the fastest algorithm for a given size. 33 | 34 | ### Changed 35 | - Changed API to take the "signal" as mutable and use it for scratch space. 36 | 37 | ## [1.0.1] 38 | ### Changed 39 | - Relicensed to dual MIT/Apache-2.0. 40 | 41 | ## [1.0.0] 42 | ### Added 43 | - Added initial implementation of Cooley-Tukey. 44 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "rustfft" 4 | version = "3.0.1" 5 | authors = ["Allen Welkie ", "Elliott Mahler "] 6 | 7 | description = "Compute FFTs of any size in O(nlogn) time, in pure Rust." 8 | documentation = "https://docs.rs/rustfft/" 9 | repository = "https://github.com/awelkie/RustFFT" 10 | keywords = ["fft", "dft", "discrete", "fourier", "transform"] 11 | categories = ["algorithms", "compression", "multimedia::encoding", "science"] 12 | license = "MIT OR Apache-2.0" 13 | 14 | [dependencies] 15 | num-complex = "0.2" 16 | num-traits = "0.2" 17 | num-integer = "0.1" 18 | strength_reduce = "^0.2.1" 19 | transpose = "0.1" 20 | 21 | [dev-dependencies] 22 | rand = "0.5" 23 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 The RustFFT Developers 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RustFFT 2 | 3 | [![Build Status](https://travis-ci.org/awelkie/RustFFT.svg?branch=master)](https://travis-ci.org/awelkie/RustFFT) 4 | [![](https://img.shields.io/crates/v/rustfft.svg)](https://crates.io/crates/rustfft) 5 | [![](https://img.shields.io/crates/l/rustfft.svg)](https://crates.io/crates/rustfft) 6 | [![](https://docs.rs/rustfft/badge.svg)](https://docs.rs/rustfft/) 7 | ![minimum rustc 1.26](https://img.shields.io/badge/rustc-1.26+-red.svg) 8 | 9 | RustFFT is a mixed-radix FFT implementation written in Rust. See the [documentation](https://docs.rs/rustfft/) for more details. 10 | 11 | ## Compatibility 12 | 13 | The `rustfft` crate requires rustc 1.26 or greater. 14 | 15 | ## License 16 | 17 | Licensed under either of 18 | 19 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 20 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 21 | 22 | at your option. 23 | 24 | ### Contribution 25 | 26 | Unless you explicitly state otherwise, any contribution intentionally 27 | submitted for inclusion in the work by you, as defined in the Apache-2.0 28 | license, shall be dual licensed as above, without any additional terms or 29 | conditions. 30 | -------------------------------------------------------------------------------- /benches/rustfft.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | extern crate test; 3 | extern crate rustfft; 4 | 5 | use std::sync::Arc; 6 | use test::Bencher; 7 | use rustfft::FFT; 8 | use rustfft::num_complex::Complex; 9 | use rustfft::algorithm::*; 10 | use rustfft::algorithm::butterflies::*; 11 | 12 | /// Times just the FFT execution (not allocation and pre-calculation) 13 | /// for a given length 14 | fn bench_fft(b: &mut Bencher, len: usize) { 15 | 16 | let mut planner = rustfft::FFTplanner::new(false); 17 | let fft = planner.plan_fft(len); 18 | 19 | let mut signal = vec![Complex{re: 0_f32, im: 0_f32}; len]; 20 | let mut spectrum = signal.clone(); 21 | b.iter(|| {fft.process(&mut signal, &mut spectrum);} ); 22 | } 23 | 24 | 25 | // Powers of 4 26 | #[bench] fn complex_p2_00000064(b: &mut Bencher) { bench_fft(b, 64); } 27 | #[bench] fn complex_p2_00000256(b: &mut Bencher) { bench_fft(b, 256); } 28 | #[bench] fn complex_p2_00001024(b: &mut Bencher) { bench_fft(b, 1024); } 29 | #[bench] fn complex_p2_00004096(b: &mut Bencher) { bench_fft(b, 4096); } 30 | #[bench] fn complex_p2_00016384(b: &mut Bencher) { bench_fft(b, 16384); } 31 | #[bench] fn complex_p2_00065536(b: &mut Bencher) { bench_fft(b, 65536); } 32 | #[bench] fn complex_p2_01048576(b: &mut Bencher) { bench_fft(b, 1048576); } 33 | #[bench] fn complex_p2_16777216(b: &mut Bencher) { bench_fft(b, 16777216); } 34 | 35 | 36 | // Powers of 7 37 | #[bench] fn complex_p7_00343(b: &mut Bencher) { bench_fft(b, 343); } 38 | #[bench] fn complex_p7_02401(b: &mut Bencher) { bench_fft(b, 2401); } 39 | #[bench] fn complex_p7_16807(b: &mut Bencher) { bench_fft(b, 16807); } 40 | 41 | // Prime lengths 42 | #[bench] fn complex_prime_00005(b: &mut Bencher) { bench_fft(b, 5); } 43 | #[bench] fn complex_prime_00017(b: &mut Bencher) { bench_fft(b, 17); } 44 | #[bench] fn complex_prime_00151(b: &mut Bencher) { bench_fft(b, 151); } 45 | #[bench] fn complex_prime_00257(b: &mut Bencher) { bench_fft(b, 257); } 46 | #[bench] fn complex_prime_01009(b: &mut Bencher) { bench_fft(b, 1009); } 47 | #[bench] fn complex_prime_02017(b: &mut Bencher) { bench_fft(b, 2017); } 48 | #[bench] fn complex_prime_65537(b: &mut Bencher) { bench_fft(b, 65537); } 49 | #[bench] fn complex_prime_746497(b: &mut Bencher) { bench_fft(b, 746497); } 50 | 51 | //primes raised to a power 52 | #[bench] fn complex_primepower_44521(b: &mut Bencher) { bench_fft(b, 44521); } // 211^2 53 | #[bench] fn complex_primepower_160801(b: &mut Bencher) { bench_fft(b, 160801); } // 401^2 54 | 55 | // numbers times powers of two 56 | #[bench] fn complex_composite_24576(b: &mut Bencher) { bench_fft(b, 24576); } 57 | #[bench] fn complex_composite_20736(b: &mut Bencher) { bench_fft(b, 20736); } 58 | 59 | // power of 2 times large prime 60 | #[bench] fn complex_composite_32192(b: &mut Bencher) { bench_fft(b, 32192); } 61 | #[bench] fn complex_composite_24028(b: &mut Bencher) { bench_fft(b, 24028); } 62 | 63 | // small mixed composites times a large prime 64 | #[bench] fn complex_composite_30270(b: &mut Bencher) { bench_fft(b, 30270); } 65 | 66 | 67 | /// Times just the FFT execution (not allocation and pre-calculation) 68 | /// for a given length, specific to the Good-Thomas algorithm 69 | fn bench_good_thomas(b: &mut Bencher, width: usize, height: usize) { 70 | 71 | let mut planner = rustfft::FFTplanner::new(false); 72 | let width_fft = planner.plan_fft(width); 73 | let height_fft = planner.plan_fft(height); 74 | 75 | let fft : Arc> = Arc::new(GoodThomasAlgorithm::new(width_fft, height_fft)); 76 | 77 | let mut signal = vec![Complex{re: 0_f32, im: 0_f32}; width * height]; 78 | let mut spectrum = signal.clone(); 79 | b.iter(|| {fft.process(&mut signal, &mut spectrum);} ); 80 | } 81 | 82 | #[bench] fn good_thomas_0002_3(b: &mut Bencher) { bench_good_thomas(b, 2, 3); } 83 | #[bench] fn good_thomas_0003_4(b: &mut Bencher) { bench_good_thomas(b, 3, 4); } 84 | #[bench] fn good_thomas_0004_5(b: &mut Bencher) { bench_good_thomas(b, 4, 5); } 85 | #[bench] fn good_thomas_0007_32(b: &mut Bencher) { bench_good_thomas(b, 7, 32); } 86 | #[bench] fn good_thomas_0032_27(b: &mut Bencher) { bench_good_thomas(b, 32, 27); } 87 | #[bench] fn good_thomas_0256_243(b: &mut Bencher) { bench_good_thomas(b, 256, 243); } 88 | #[bench] fn good_thomas_2048_3(b: &mut Bencher) { bench_good_thomas(b, 2048, 3); } 89 | #[bench] fn good_thomas_2048_2187(b: &mut Bencher) { bench_good_thomas(b, 2048, 2187); } 90 | 91 | /// Times just the FFT setup (not execution) 92 | /// for a given length, specific to the Good-Thomas algorithm 93 | fn bench_good_thomas_setup(b: &mut Bencher, width: usize, height: usize) { 94 | 95 | let mut planner = rustfft::FFTplanner::new(false); 96 | let width_fft = planner.plan_fft(width); 97 | let height_fft = planner.plan_fft(height); 98 | 99 | b.iter(|| { 100 | let fft : Arc> = Arc::new(GoodThomasAlgorithm::new(Arc::clone(&width_fft), Arc::clone(&height_fft))); 101 | test::black_box(fft); 102 | }); 103 | } 104 | 105 | #[bench] fn good_thomas_setup_0002_3(b: &mut Bencher) { bench_good_thomas_setup(b, 2, 3); } 106 | #[bench] fn good_thomas_setup_0003_4(b: &mut Bencher) { bench_good_thomas_setup(b, 3, 4); } 107 | #[bench] fn good_thomas_setup_0004_5(b: &mut Bencher) { bench_good_thomas_setup(b, 4, 5); } 108 | #[bench] fn good_thomas_setup_0007_32(b: &mut Bencher) { bench_good_thomas_setup(b, 7, 32); } 109 | #[bench] fn good_thomas_setup_0032_27(b: &mut Bencher) { bench_good_thomas_setup(b, 32, 27); } 110 | #[bench] fn good_thomas_setup_0256_243(b: &mut Bencher) { bench_good_thomas_setup(b, 256, 243); } 111 | #[bench] fn good_thomas_setup_2048_3(b: &mut Bencher) { bench_good_thomas_setup(b, 2048, 3); } 112 | #[bench] fn good_thomas_setup_2048_2187(b: &mut Bencher) { bench_good_thomas_setup(b, 2048, 2187); } 113 | 114 | /// Times just the FFT execution (not allocation and pre-calculation) 115 | /// for a given length, specific to the Mixed-Radix algorithm 116 | fn bench_mixed_radix(b: &mut Bencher, width: usize, height: usize) { 117 | 118 | let mut planner = rustfft::FFTplanner::new(false); 119 | let width_fft = planner.plan_fft(width); 120 | let height_fft = planner.plan_fft(height); 121 | 122 | let fft : Arc> = Arc::new(MixedRadix::new(width_fft, height_fft)); 123 | 124 | let mut signal = vec![Complex{re: 0_f32, im: 0_f32}; width * height]; 125 | let mut spectrum = signal.clone(); 126 | b.iter(|| {fft.process(&mut signal, &mut spectrum);} ); 127 | } 128 | 129 | #[bench] fn mixed_radix_0002_3(b: &mut Bencher) { bench_mixed_radix(b, 2, 3); } 130 | #[bench] fn mixed_radix_0003_4(b: &mut Bencher) { bench_mixed_radix(b, 3, 4); } 131 | #[bench] fn mixed_radix_0004_5(b: &mut Bencher) { bench_mixed_radix(b, 4, 5); } 132 | #[bench] fn mixed_radix_0007_32(b: &mut Bencher) { bench_mixed_radix(b, 7, 32); } 133 | #[bench] fn mixed_radix_0032_27(b: &mut Bencher) { bench_mixed_radix(b, 32, 27); } 134 | #[bench] fn mixed_radix_0256_243(b: &mut Bencher) { bench_mixed_radix(b, 256, 243); } 135 | #[bench] fn mixed_radix_2048_3(b: &mut Bencher) { bench_mixed_radix(b, 2048, 3); } 136 | #[bench] fn mixed_radix_2048_2187(b: &mut Bencher) { bench_mixed_radix(b, 2048, 2187); } 137 | 138 | 139 | 140 | fn plan_butterfly(len: usize) -> Arc> { 141 | match len { 142 | 2 => Arc::new(Butterfly2::new(false)), 143 | 3 => Arc::new(Butterfly3::new(false)), 144 | 4 => Arc::new(Butterfly4::new(false)), 145 | 5 => Arc::new(Butterfly5::new(false)), 146 | 6 => Arc::new(Butterfly6::new(false)), 147 | 7 => Arc::new(Butterfly7::new(false)), 148 | 8 => Arc::new(Butterfly8::new(false)), 149 | 16 => Arc::new(Butterfly16::new(false)), 150 | 32 => Arc::new(Butterfly32::new(false)), 151 | _ => panic!("Invalid butterfly size: {}", len), 152 | } 153 | } 154 | 155 | /// Times just the FFT execution (not allocation and pre-calculation) 156 | /// for a given length, specific to the Mixed-Radix Double Butterfly algorithm 157 | fn bench_mixed_radix_butterfly(b: &mut Bencher, width: usize, height: usize) { 158 | 159 | let width_fft = plan_butterfly(width); 160 | let height_fft = plan_butterfly(height); 161 | 162 | let fft : Arc> = Arc::new(MixedRadixDoubleButterfly::new(width_fft, height_fft)); 163 | 164 | let mut signal = vec![Complex{re: 0_f32, im: 0_f32}; width * height]; 165 | let mut spectrum = signal.clone(); 166 | b.iter(|| {fft.process(&mut signal, &mut spectrum);} ); 167 | } 168 | 169 | #[bench] fn mixed_radix_butterfly_0002_3(b: &mut Bencher) { bench_mixed_radix_butterfly(b, 2, 3); } 170 | #[bench] fn mixed_radix_butterfly_0003_4(b: &mut Bencher) { bench_mixed_radix_butterfly(b, 3, 4); } 171 | #[bench] fn mixed_radix_butterfly_0004_5(b: &mut Bencher) { bench_mixed_radix_butterfly(b, 4, 5); } 172 | #[bench] fn mixed_radix_butterfly_0007_32(b: &mut Bencher) { bench_mixed_radix_butterfly(b, 7, 32); } 173 | 174 | /// Times just the FFT execution (not allocation and pre-calculation) 175 | /// for a given length, specific to the Mixed-Radix Double Butterfly algorithm 176 | fn bench_good_thomas_butterfly(b: &mut Bencher, width: usize, height: usize) { 177 | 178 | let width_fft = plan_butterfly(width); 179 | let height_fft = plan_butterfly(height); 180 | 181 | let fft : Arc> = Arc::new(GoodThomasAlgorithmDoubleButterfly::new(width_fft, height_fft)); 182 | 183 | let mut signal = vec![Complex{re: 0_f32, im: 0_f32}; width * height]; 184 | let mut spectrum = signal.clone(); 185 | b.iter(|| {fft.process(&mut signal, &mut spectrum);} ); 186 | } 187 | 188 | #[bench] fn good_thomas_butterfly_0002_3(b: &mut Bencher) { bench_good_thomas_butterfly(b, 2, 3); } 189 | #[bench] fn good_thomas_butterfly_0003_4(b: &mut Bencher) { bench_good_thomas_butterfly(b, 3, 4); } 190 | #[bench] fn good_thomas_butterfly_0004_5(b: &mut Bencher) { bench_good_thomas_butterfly(b, 4, 5); } 191 | #[bench] fn good_thomas_butterfly_0007_32(b: &mut Bencher) { bench_good_thomas_butterfly(b, 7, 32); } 192 | 193 | 194 | /// Times just the FFT execution (not allocation and pre-calculation) 195 | /// for a given length, specific to Rader's algorithm 196 | fn bench_raders(b: &mut Bencher, len: usize) { 197 | 198 | let mut planner = rustfft::FFTplanner::new(false); 199 | let inner_fft = planner.plan_fft(len - 1); 200 | 201 | let fft : Arc> = Arc::new(RadersAlgorithm::new(len, inner_fft)); 202 | 203 | let mut signal = vec![Complex{re: 0_f32, im: 0_f32}; len]; 204 | let mut spectrum = signal.clone(); 205 | b.iter(|| {fft.process(&mut signal, &mut spectrum);} ); 206 | } 207 | 208 | #[bench] fn raders_0005(b: &mut Bencher) { bench_raders(b, 5); } 209 | #[bench] fn raders_0017(b: &mut Bencher) { bench_raders(b, 17); } 210 | #[bench] fn raders_0151(b: &mut Bencher) { bench_raders(b, 151); } 211 | #[bench] fn raders_0257(b: &mut Bencher) { bench_raders(b, 257); } 212 | #[bench] fn raders_1009(b: &mut Bencher) { bench_raders(b, 1009); } 213 | #[bench] fn raders_2017(b: &mut Bencher) { bench_raders(b, 2017); } 214 | #[bench] fn raders_65537(b: &mut Bencher) { bench_raders(b, 65537); } 215 | #[bench] fn raders_746497(b: &mut Bencher) { bench_raders(b,746497); } 216 | 217 | /// Times just the FFT setup (not execution) 218 | /// for a given length, specific to Rader's algorithm 219 | fn bench_raders_setup(b: &mut Bencher, len: usize) { 220 | 221 | let mut planner = rustfft::FFTplanner::new(false); 222 | let inner_fft = planner.plan_fft(len - 1); 223 | 224 | b.iter(|| { 225 | let fft : Arc> = Arc::new(RadersAlgorithm::new(len, Arc::clone(&inner_fft))); 226 | test::black_box(fft); 227 | }); 228 | } 229 | 230 | #[bench] fn raders_setup_0005(b: &mut Bencher) { bench_raders_setup(b, 5); } 231 | #[bench] fn raders_setup_0017(b: &mut Bencher) { bench_raders_setup(b, 17); } 232 | #[bench] fn raders_setup_0151(b: &mut Bencher) { bench_raders_setup(b, 151); } 233 | #[bench] fn raders_setup_0257(b: &mut Bencher) { bench_raders_setup(b, 257); } 234 | #[bench] fn raders_setup_1009(b: &mut Bencher) { bench_raders_setup(b, 1009); } 235 | #[bench] fn raders_setup_2017(b: &mut Bencher) { bench_raders_setup(b, 2017); } 236 | #[bench] fn raders_setup_65537(b: &mut Bencher) { bench_raders_setup(b, 65537); } 237 | #[bench] fn raders_setup_746497(b: &mut Bencher) { bench_raders_setup(b,746497); } -------------------------------------------------------------------------------- /examples/concurrency.rs: -------------------------------------------------------------------------------- 1 | //! Show how to use an `FFT` object from multiple threads 2 | 3 | extern crate rustfft; 4 | 5 | use std::thread; 6 | use std::sync::Arc; 7 | 8 | use rustfft::FFTplanner; 9 | use rustfft::num_complex::Complex32; 10 | 11 | fn main() { 12 | let inverse = false; 13 | let mut planner = FFTplanner::new(inverse); 14 | let fft = planner.plan_fft(100); 15 | 16 | let threads: Vec> = (0..2).map(|_| { 17 | let fft_copy = Arc::clone(&fft); 18 | thread::spawn(move || { 19 | let mut signal = vec![Complex32::new(0.0, 0.0); 100]; 20 | let mut spectrum = vec![Complex32::new(0.0, 0.0); 100]; 21 | fft_copy.process(&mut signal, &mut spectrum); 22 | }) 23 | }).collect(); 24 | 25 | for thread in threads { 26 | thread.join().unwrap(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/algorithm/butterflies.rs: -------------------------------------------------------------------------------- 1 | use num_complex::Complex; 2 | use num_traits::{FromPrimitive, Zero}; 3 | 4 | use common::{FFTnum, verify_length, verify_length_divisible}; 5 | 6 | use twiddles; 7 | use ::{Length, IsInverse, FFT}; 8 | 9 | 10 | pub trait FFTButterfly: Length + IsInverse + Sync + Send { 11 | /// Computes the FFT in-place in the given buffer 12 | /// 13 | /// # Safety 14 | /// This method performs unsafe reads/writes on `buffer`. Make sure `buffer.len()` is equal to `self.len()` 15 | unsafe fn process_inplace(&self, buffer: &mut [Complex]); 16 | 17 | /// Divides the given buffer into chunks of length `self.len()` and computes an in-place FFT on each chunk 18 | /// 19 | /// # Safety 20 | /// This method performs unsafe reads/writes on `buffer`. Make sure `buffer.len()` is a multiple of `self.len()` 21 | unsafe fn process_multi_inplace(&self, buffer: &mut [Complex]); 22 | } 23 | 24 | 25 | #[inline(always)] 26 | unsafe fn swap_unchecked(buffer: &mut [T], a: usize, b: usize) { 27 | let temp = *buffer.get_unchecked(a); 28 | *buffer.get_unchecked_mut(a) = *buffer.get_unchecked(b); 29 | *buffer.get_unchecked_mut(b) = temp; 30 | } 31 | 32 | 33 | 34 | 35 | 36 | pub struct Butterfly2 { 37 | inverse: bool, 38 | } 39 | impl Butterfly2 { 40 | #[inline(always)] 41 | pub fn new(inverse: bool) -> Self { 42 | Butterfly2 { 43 | inverse: inverse, 44 | } 45 | } 46 | 47 | #[inline(always)] 48 | unsafe fn perform_fft_direct(left: &mut Complex, right: &mut Complex) { 49 | let temp = *left + *right; 50 | 51 | *right = *left - *right; 52 | *left = temp; 53 | } 54 | } 55 | impl FFTButterfly for Butterfly2 { 56 | #[inline(always)] 57 | unsafe fn process_inplace(&self, buffer: &mut [Complex]) { 58 | let temp = *buffer.get_unchecked(0) + *buffer.get_unchecked(1); 59 | 60 | *buffer.get_unchecked_mut(1) = *buffer.get_unchecked(0) - *buffer.get_unchecked(1); 61 | *buffer.get_unchecked_mut(0) = temp; 62 | } 63 | #[inline(always)] 64 | unsafe fn process_multi_inplace(&self, buffer: &mut [Complex]) { 65 | for chunk in buffer.chunks_mut(self.len()) { 66 | self.process_inplace(chunk); 67 | } 68 | } 69 | } 70 | impl FFT for Butterfly2 { 71 | fn process(&self, input: &mut [Complex], output: &mut [Complex]) { 72 | verify_length(input, output, self.len()); 73 | output.copy_from_slice(input); 74 | 75 | unsafe { self.process_inplace(output) }; 76 | } 77 | fn process_multi(&self, input: &mut [Complex], output: &mut [Complex]) { 78 | verify_length_divisible(input, output, self.len()); 79 | output.copy_from_slice(input); 80 | 81 | unsafe { self.process_multi_inplace(output) }; 82 | } 83 | } 84 | impl Length for Butterfly2 { 85 | #[inline(always)] 86 | fn len(&self) -> usize { 87 | 2 88 | } 89 | } 90 | impl IsInverse for Butterfly2 { 91 | #[inline(always)] 92 | fn is_inverse(&self) -> bool { 93 | self.inverse 94 | } 95 | } 96 | 97 | 98 | 99 | pub struct Butterfly3 { 100 | pub twiddle: Complex, 101 | inverse: bool, 102 | } 103 | impl Butterfly3 { 104 | #[inline(always)] 105 | pub fn new(inverse: bool) -> Self { 106 | Butterfly3 { 107 | twiddle: twiddles::single_twiddle(1, 3, inverse), 108 | inverse: inverse, 109 | } 110 | } 111 | 112 | #[inline(always)] 113 | pub fn inverse_of(fft: &Butterfly3) -> Self { 114 | Butterfly3 { 115 | twiddle: fft.twiddle.conj(), 116 | inverse: !fft.inverse, 117 | } 118 | } 119 | } 120 | impl FFTButterfly for Butterfly3 { 121 | #[inline(always)] 122 | unsafe fn process_inplace(&self, buffer: &mut [Complex]) { 123 | let butterfly2 = Butterfly2::new(self.inverse); 124 | 125 | butterfly2.process_inplace(&mut buffer[1..]); 126 | let temp = *buffer.get_unchecked(0); 127 | 128 | *buffer.get_unchecked_mut(0) = temp + *buffer.get_unchecked(1); 129 | 130 | *buffer.get_unchecked_mut(1) = *buffer.get_unchecked(1) * self.twiddle.re + temp; 131 | *buffer.get_unchecked_mut(2) = *buffer.get_unchecked(2) * Complex{re: Zero::zero(), im: self.twiddle.im}; 132 | 133 | butterfly2.process_inplace(&mut buffer[1..]); 134 | } 135 | #[inline(always)] 136 | unsafe fn process_multi_inplace(&self, buffer: &mut [Complex]) { 137 | for chunk in buffer.chunks_mut(self.len()) { 138 | self.process_inplace(chunk); 139 | } 140 | } 141 | } 142 | impl FFT for Butterfly3 { 143 | fn process(&self, input: &mut [Complex], output: &mut [Complex]) { 144 | verify_length(input, output, self.len()); 145 | output.copy_from_slice(input); 146 | 147 | unsafe { self.process_inplace(output) }; 148 | } 149 | fn process_multi(&self, input: &mut [Complex], output: &mut [Complex]) { 150 | verify_length_divisible(input, output, self.len()); 151 | output.copy_from_slice(input); 152 | 153 | unsafe { self.process_multi_inplace(output) }; 154 | } 155 | } 156 | impl Length for Butterfly3 { 157 | #[inline(always)] 158 | fn len(&self) -> usize { 159 | 3 160 | } 161 | } 162 | impl IsInverse for Butterfly3 { 163 | #[inline(always)] 164 | fn is_inverse(&self) -> bool { 165 | self.inverse 166 | } 167 | } 168 | 169 | 170 | 171 | 172 | 173 | pub struct Butterfly4 { 174 | inverse: bool, 175 | } 176 | impl Butterfly4 177 | { 178 | #[inline(always)] 179 | pub fn new(inverse: bool) -> Self { 180 | Butterfly4 { inverse:inverse } 181 | } 182 | } 183 | impl FFTButterfly for Butterfly4 { 184 | #[inline(always)] 185 | unsafe fn process_inplace(&self, buffer: &mut [Complex]) { 186 | let butterfly2 = Butterfly2::new(self.inverse); 187 | 188 | //we're going to hardcode a step of mixed radix 189 | //aka we're going to do the six step algorithm 190 | 191 | // step 1: transpose, which we're skipping because we're just going to perform non-contiguous FFTs 192 | 193 | // step 2: column FFTs 194 | { 195 | let (a, b) = buffer.split_at_mut(2); 196 | Butterfly2::perform_fft_direct(a.get_unchecked_mut(0), b.get_unchecked_mut(0)); 197 | Butterfly2::perform_fft_direct(a.get_unchecked_mut(1), b.get_unchecked_mut(1)); 198 | 199 | // step 3: apply twiddle factors (only one in this case, and it's either 0 + i or 0 - i) 200 | *b.get_unchecked_mut(1) = twiddles::rotate_90(*b.get_unchecked(1), self.inverse); 201 | 202 | // step 4: transpose, which we're skipping because we're the previous FFTs were non-contiguous 203 | 204 | // step 5: row FFTs 205 | butterfly2.process_inplace(a); 206 | butterfly2.process_inplace(b); 207 | } 208 | 209 | // step 6: transpose 210 | swap_unchecked(buffer, 1, 2); 211 | } 212 | #[inline(always)] 213 | unsafe fn process_multi_inplace(&self, buffer: &mut [Complex]) { 214 | for chunk in buffer.chunks_mut(self.len()) { 215 | self.process_inplace(chunk); 216 | } 217 | } 218 | } 219 | impl FFT for Butterfly4 { 220 | fn process(&self, input: &mut [Complex], output: &mut [Complex]) { 221 | verify_length(input, output, self.len()); 222 | output.copy_from_slice(input); 223 | 224 | unsafe { self.process_inplace(output) }; 225 | } 226 | fn process_multi(&self, input: &mut [Complex], output: &mut [Complex]) { 227 | verify_length_divisible(input, output, self.len()); 228 | output.copy_from_slice(input); 229 | 230 | unsafe { self.process_multi_inplace(output) }; 231 | } 232 | } 233 | impl Length for Butterfly4 { 234 | #[inline(always)] 235 | fn len(&self) -> usize { 236 | 4 237 | } 238 | } 239 | impl IsInverse for Butterfly4 { 240 | #[inline(always)] 241 | fn is_inverse(&self) -> bool { 242 | self.inverse 243 | } 244 | } 245 | 246 | 247 | 248 | 249 | 250 | pub struct Butterfly5 { 251 | inner_fft_multiply: [Complex; 4], 252 | inverse: bool, 253 | } 254 | impl Butterfly5 { 255 | pub fn new(inverse: bool) -> Self { 256 | 257 | //we're going to hardcode a raders algorithm of size 5 and an inner FFT of size 4 258 | let quarter: T = FromPrimitive::from_f32(0.25f32).unwrap(); 259 | let twiddle1: Complex = twiddles::single_twiddle(1, 5, inverse) * quarter; 260 | let twiddle2: Complex = twiddles::single_twiddle(2, 5, inverse) * quarter; 261 | 262 | //our primitive root will be 2, and our inverse will be 3. the powers of 3 mod 5 are 1.3.4.2, so we hardcode to use the twiddles in that order 263 | let mut fft_data = [twiddle1, twiddle2.conj(), twiddle1.conj(), twiddle2]; 264 | 265 | let butterfly = Butterfly4::new(inverse); 266 | unsafe { butterfly.process_inplace(&mut fft_data) }; 267 | 268 | Butterfly5 { 269 | inner_fft_multiply: fft_data, 270 | inverse: inverse, 271 | } 272 | } 273 | } 274 | impl FFTButterfly for Butterfly5 { 275 | #[inline(always)] 276 | unsafe fn process_inplace(&self, buffer: &mut [Complex]) { 277 | //we're going to reorder the buffer directly into our scratch vec 278 | //our primitive root is 2. the powers of 2 mod 5 are 1, 2,4,3 so use that ordering 279 | let mut scratch = [*buffer.get_unchecked(1), *buffer.get_unchecked(2), *buffer.get_unchecked(4), *buffer.get_unchecked(3)]; 280 | 281 | //perform the first inner FFT 282 | Butterfly4::new(self.inverse).process_inplace(&mut scratch); 283 | 284 | //multiply the fft result with our precomputed data 285 | for i in 0..4 { 286 | scratch[i] = scratch[i] * self.inner_fft_multiply[i]; 287 | } 288 | 289 | //perform the second inner FFT 290 | Butterfly4::new(!self.inverse).process_inplace(&mut scratch); 291 | 292 | //the first element of the output is the sum of the rest 293 | let first_input = *buffer.get_unchecked_mut(0); 294 | let mut sum = first_input; 295 | for i in 1..5 { 296 | sum = sum + *buffer.get_unchecked_mut(i); 297 | } 298 | *buffer.get_unchecked_mut(0) = sum; 299 | 300 | //use the inverse root ordering to copy data back out 301 | *buffer.get_unchecked_mut(1) = scratch[0] + first_input; 302 | *buffer.get_unchecked_mut(3) = scratch[1] + first_input; 303 | *buffer.get_unchecked_mut(4) = scratch[2] + first_input; 304 | *buffer.get_unchecked_mut(2) = scratch[3] + first_input; 305 | } 306 | #[inline(always)] 307 | unsafe fn process_multi_inplace(&self, buffer: &mut [Complex]) { 308 | for chunk in buffer.chunks_mut(self.len()) { 309 | self.process_inplace(chunk); 310 | } 311 | } 312 | } 313 | impl FFT for Butterfly5 { 314 | fn process(&self, input: &mut [Complex], output: &mut [Complex]) { 315 | verify_length(input, output, self.len()); 316 | output.copy_from_slice(input); 317 | 318 | unsafe { self.process_inplace(output) }; 319 | } 320 | fn process_multi(&self, input: &mut [Complex], output: &mut [Complex]) { 321 | verify_length_divisible(input, output, self.len()); 322 | output.copy_from_slice(input); 323 | 324 | unsafe { self.process_multi_inplace(output) }; 325 | } 326 | } 327 | impl Length for Butterfly5 { 328 | #[inline(always)] 329 | fn len(&self) -> usize { 330 | 5 331 | } 332 | } 333 | impl IsInverse for Butterfly5 { 334 | #[inline(always)] 335 | fn is_inverse(&self) -> bool { 336 | self.inverse 337 | } 338 | } 339 | 340 | 341 | 342 | 343 | pub struct Butterfly6 { 344 | butterfly3: Butterfly3, 345 | } 346 | impl Butterfly6 { 347 | 348 | pub fn new(inverse: bool) -> Self { 349 | Butterfly6 { butterfly3: Butterfly3::new(inverse) } 350 | } 351 | pub fn inverse_of(fft: &Butterfly6) -> Self { 352 | Butterfly6 { butterfly3: Butterfly3::inverse_of(&fft.butterfly3) } 353 | } 354 | } 355 | impl FFTButterfly for Butterfly6 { 356 | #[inline(always)] 357 | unsafe fn process_inplace(&self, buffer: &mut [Complex]) { 358 | //since GCD(2,3) == 1 we're going to hardcode a step of the Good-Thomas algorithm to avoid twiddle factors 359 | 360 | // step 1: reorder the input directly into the scratch. normally there's a whole thing to compute this ordering 361 | //but thankfully we can just precompute it and hardcode it 362 | let mut scratch_a = [ 363 | *buffer.get_unchecked(0), 364 | *buffer.get_unchecked(2), 365 | *buffer.get_unchecked(4), 366 | ]; 367 | 368 | let mut scratch_b = [ 369 | *buffer.get_unchecked(3), 370 | *buffer.get_unchecked(5), 371 | *buffer.get_unchecked(1), 372 | ]; 373 | 374 | // step 2: column FFTs 375 | self.butterfly3.process_inplace(&mut scratch_a); 376 | self.butterfly3.process_inplace(&mut scratch_b); 377 | 378 | // step 3: apply twiddle factors -- SKIPPED because good-thomas doesn't have twiddle factors :) 379 | 380 | // step 4: SKIPPED because the next FFTs will be non-contiguous 381 | 382 | // step 5: row FFTs 383 | Butterfly2::perform_fft_direct(&mut scratch_a[0], &mut scratch_b[0]); 384 | Butterfly2::perform_fft_direct(&mut scratch_a[1], &mut scratch_b[1]); 385 | Butterfly2::perform_fft_direct(&mut scratch_a[2], &mut scratch_b[2]); 386 | 387 | // step 6: reorder the result back into the buffer. again we would normally have to do an expensive computation 388 | // but instead we can precompute and hardcode the ordering 389 | // note that we're also rolling a transpose step into this reorder 390 | *buffer.get_unchecked_mut(0) = scratch_a[0]; 391 | *buffer.get_unchecked_mut(3) = scratch_b[0]; 392 | *buffer.get_unchecked_mut(4) = scratch_a[1]; 393 | *buffer.get_unchecked_mut(1) = scratch_b[1]; 394 | *buffer.get_unchecked_mut(2) = scratch_a[2]; 395 | *buffer.get_unchecked_mut(5) = scratch_b[2]; 396 | } 397 | #[inline(always)] 398 | unsafe fn process_multi_inplace(&self, buffer: &mut [Complex]) { 399 | for chunk in buffer.chunks_mut(self.len()) { 400 | self.process_inplace(chunk); 401 | } 402 | } 403 | } 404 | impl FFT for Butterfly6 { 405 | fn process(&self, input: &mut [Complex], output: &mut [Complex]) { 406 | verify_length(input, output, self.len()); 407 | output.copy_from_slice(input); 408 | 409 | unsafe { self.process_inplace(output) }; 410 | } 411 | fn process_multi(&self, input: &mut [Complex], output: &mut [Complex]) { 412 | verify_length_divisible(input, output, self.len()); 413 | output.copy_from_slice(input); 414 | 415 | unsafe { self.process_multi_inplace(output) }; 416 | } 417 | } 418 | impl Length for Butterfly6 { 419 | #[inline(always)] 420 | fn len(&self) -> usize { 421 | 6 422 | } 423 | } 424 | impl IsInverse for Butterfly6 { 425 | #[inline(always)] 426 | fn is_inverse(&self) -> bool { 427 | self.butterfly3.is_inverse() 428 | } 429 | } 430 | 431 | 432 | pub struct Butterfly7 { 433 | inner_fft: Butterfly6, 434 | inner_fft_multiply: [Complex; 6] 435 | } 436 | impl Butterfly7 { 437 | pub fn new(inverse: bool) -> Self { 438 | 439 | //we're going to hardcode a raders algorithm of size 5 and an inner FFT of size 4 440 | let sixth: T = FromPrimitive::from_f64(1f64/6f64).unwrap(); 441 | let twiddle1: Complex = twiddles::single_twiddle(1, 7, inverse) * sixth; 442 | let twiddle2: Complex = twiddles::single_twiddle(2, 7, inverse) * sixth; 443 | let twiddle3: Complex = twiddles::single_twiddle(3, 7, inverse) * sixth; 444 | 445 | //our primitive root will be 3, and our inverse will be 5. the powers of 5 mod 7 are 1,5,4,6,2,3, so we hardcode to use the twiddles in that order 446 | let mut fft_data = [twiddle1, twiddle2.conj(), twiddle3.conj(), twiddle1.conj(), twiddle2, twiddle3]; 447 | 448 | let butterfly = Butterfly6::new(inverse); 449 | unsafe { butterfly.process_inplace(&mut fft_data) }; 450 | 451 | Butterfly7 { 452 | inner_fft: butterfly, 453 | inner_fft_multiply: fft_data, 454 | } 455 | } 456 | } 457 | impl FFTButterfly for Butterfly7 { 458 | #[inline(always)] 459 | unsafe fn process_inplace(&self, buffer: &mut [Complex]) { 460 | //we're going to reorder the buffer directly into our scratch vec 461 | //our primitive root is 3. use 3^n mod 7 to determine which index to copy from 462 | let mut scratch = [ 463 | *buffer.get_unchecked(3), 464 | *buffer.get_unchecked(2), 465 | *buffer.get_unchecked(6), 466 | *buffer.get_unchecked(4), 467 | *buffer.get_unchecked(5), 468 | *buffer.get_unchecked(1), 469 | ]; 470 | 471 | //perform the first inner FFT 472 | self.inner_fft.process_inplace(&mut scratch); 473 | 474 | //multiply the fft result with our precomputed data 475 | for i in 0..6 { 476 | scratch[i] = scratch[i] * self.inner_fft_multiply[i]; 477 | } 478 | 479 | //perform the second inner FFT 480 | let inverse6 = Butterfly6::inverse_of(&self.inner_fft); 481 | inverse6.process_inplace(&mut scratch); 482 | 483 | //the first element of the output is the sum of the rest 484 | let first_input = *buffer.get_unchecked(0); 485 | let mut sum = first_input; 486 | for i in 1..7 { 487 | sum = sum + *buffer.get_unchecked_mut(i); 488 | } 489 | *buffer.get_unchecked_mut(0) = sum; 490 | 491 | //use the inverse root ordering to copy data back out 492 | *buffer.get_unchecked_mut(5) = scratch[0] + first_input; 493 | *buffer.get_unchecked_mut(4) = scratch[1] + first_input; 494 | *buffer.get_unchecked_mut(6) = scratch[2] + first_input; 495 | *buffer.get_unchecked_mut(2) = scratch[3] + first_input; 496 | *buffer.get_unchecked_mut(3) = scratch[4] + first_input; 497 | *buffer.get_unchecked_mut(1) = scratch[5] + first_input; 498 | } 499 | #[inline(always)] 500 | unsafe fn process_multi_inplace(&self, buffer: &mut [Complex]) { 501 | for chunk in buffer.chunks_mut(self.len()) { 502 | self.process_inplace(chunk); 503 | } 504 | } 505 | } 506 | impl FFT for Butterfly7 { 507 | fn process(&self, input: &mut [Complex], output: &mut [Complex]) { 508 | verify_length(input, output, self.len()); 509 | output.copy_from_slice(input); 510 | 511 | unsafe { self.process_inplace(output) }; 512 | } 513 | fn process_multi(&self, input: &mut [Complex], output: &mut [Complex]) { 514 | verify_length_divisible(input, output, self.len()); 515 | output.copy_from_slice(input); 516 | 517 | unsafe { self.process_multi_inplace(output) }; 518 | } 519 | } 520 | impl Length for Butterfly7 { 521 | #[inline(always)] 522 | fn len(&self) -> usize { 523 | 7 524 | } 525 | } 526 | impl IsInverse for Butterfly7 { 527 | #[inline(always)] 528 | fn is_inverse(&self) -> bool { 529 | self.inner_fft.is_inverse() 530 | } 531 | } 532 | 533 | 534 | 535 | pub struct Butterfly8 { 536 | twiddle: Complex, 537 | inverse: bool, 538 | } 539 | impl Butterfly8 540 | { 541 | #[inline(always)] 542 | pub fn new(inverse: bool) -> Self { 543 | Butterfly8 { 544 | inverse: inverse, 545 | twiddle: twiddles::single_twiddle(1, 8, inverse) 546 | } 547 | } 548 | 549 | #[inline(always)] 550 | unsafe fn transpose_4x2_to_2x4(buffer: &mut [Complex; 8]) { 551 | let temp1 = buffer[1]; 552 | buffer[1] = buffer[4]; 553 | buffer[4] = buffer[2]; 554 | buffer[2] = temp1; 555 | 556 | let temp6 = buffer[6]; 557 | buffer[6] = buffer[3]; 558 | buffer[3] = buffer[5]; 559 | buffer[5] = temp6; 560 | } 561 | } 562 | impl FFTButterfly for Butterfly8 { 563 | #[inline(always)] 564 | unsafe fn process_inplace(&self, buffer: &mut [Complex]) { 565 | let butterfly2 = Butterfly2::new(self.inverse); 566 | let butterfly4 = Butterfly4::new(self.inverse); 567 | 568 | //we're going to hardcode a step of mixed radix 569 | //aka we're going to do the six step algorithm 570 | 571 | // step 1: transpose the input into the scratch 572 | let mut scratch = [ 573 | *buffer.get_unchecked(0), 574 | *buffer.get_unchecked(2), 575 | *buffer.get_unchecked(4), 576 | *buffer.get_unchecked(6), 577 | *buffer.get_unchecked(1), 578 | *buffer.get_unchecked(3), 579 | *buffer.get_unchecked(5), 580 | *buffer.get_unchecked(7), 581 | ]; 582 | 583 | // step 2: column FFTs 584 | butterfly4.process_inplace(&mut scratch[..4]); 585 | butterfly4.process_inplace(&mut scratch[4..]); 586 | 587 | // step 3: apply twiddle factors 588 | let twiddle1 = self.twiddle; 589 | let twiddle3 = Complex{ re: -twiddle1.re, im: twiddle1.im }; 590 | 591 | *scratch.get_unchecked_mut(5) = scratch.get_unchecked(5) * twiddle1; 592 | *scratch.get_unchecked_mut(6) = twiddles::rotate_90(*scratch.get_unchecked(6), self.inverse); 593 | *scratch.get_unchecked_mut(7) = scratch.get_unchecked(7) * twiddle3; 594 | 595 | // step 4: transpose 596 | Self::transpose_4x2_to_2x4(&mut scratch); 597 | 598 | // step 5: row FFTs 599 | butterfly2.process_inplace(&mut scratch[..2]); 600 | butterfly2.process_inplace(&mut scratch[2..4]); 601 | butterfly2.process_inplace(&mut scratch[4..6]); 602 | butterfly2.process_inplace(&mut scratch[6..]); 603 | 604 | // step 6: transpose the scratch into the buffer 605 | *buffer.get_unchecked_mut(0) = scratch[0]; 606 | *buffer.get_unchecked_mut(1) = scratch[2]; 607 | *buffer.get_unchecked_mut(2) = scratch[4]; 608 | *buffer.get_unchecked_mut(3) = scratch[6]; 609 | *buffer.get_unchecked_mut(4) = scratch[1]; 610 | *buffer.get_unchecked_mut(5) = scratch[3]; 611 | *buffer.get_unchecked_mut(6) = scratch[5]; 612 | *buffer.get_unchecked_mut(7) = scratch[7]; 613 | } 614 | #[inline(always)] 615 | unsafe fn process_multi_inplace(&self, buffer: &mut [Complex]) { 616 | for chunk in buffer.chunks_mut(self.len()) { 617 | self.process_inplace(chunk); 618 | } 619 | } 620 | } 621 | impl FFT for Butterfly8 { 622 | fn process(&self, input: &mut [Complex], output: &mut [Complex]) { 623 | verify_length(input, output, self.len()); 624 | output.copy_from_slice(input); 625 | 626 | unsafe { self.process_inplace(output) }; 627 | } 628 | fn process_multi(&self, input: &mut [Complex], output: &mut [Complex]) { 629 | verify_length_divisible(input, output, self.len()); 630 | output.copy_from_slice(input); 631 | 632 | unsafe { self.process_multi_inplace(output) }; 633 | } 634 | } 635 | impl Length for Butterfly8 { 636 | #[inline(always)] 637 | fn len(&self) -> usize { 638 | 8 639 | } 640 | } 641 | impl IsInverse for Butterfly8 { 642 | #[inline(always)] 643 | fn is_inverse(&self) -> bool { 644 | self.inverse 645 | } 646 | } 647 | 648 | 649 | 650 | pub struct Butterfly16 { 651 | butterfly8: Butterfly8, 652 | twiddle1: Complex, 653 | twiddle2: Complex, 654 | twiddle3: Complex, 655 | inverse: bool, 656 | } 657 | impl Butterfly16 658 | { 659 | #[inline(always)] 660 | pub fn new(inverse: bool) -> Self { 661 | Butterfly16 { 662 | butterfly8: Butterfly8::new(inverse), 663 | twiddle1: twiddles::single_twiddle(1, 16, inverse), 664 | twiddle2: twiddles::single_twiddle(2, 16, inverse), 665 | twiddle3: twiddles::single_twiddle(3, 16, inverse), 666 | inverse: inverse, 667 | } 668 | } 669 | } 670 | impl FFTButterfly for Butterfly16 { 671 | #[inline(always)] 672 | unsafe fn process_inplace(&self, buffer: &mut [Complex]) { 673 | let butterfly4 = Butterfly4::new(self.inverse); 674 | 675 | // we're going to hardcode a step of split radix 676 | // step 1: copy and reorder the input into the scratch 677 | let mut scratch_evens = [ 678 | *buffer.get_unchecked(0), 679 | *buffer.get_unchecked(2), 680 | *buffer.get_unchecked(4), 681 | *buffer.get_unchecked(6), 682 | *buffer.get_unchecked(8), 683 | *buffer.get_unchecked(10), 684 | *buffer.get_unchecked(12), 685 | *buffer.get_unchecked(14), 686 | ]; 687 | 688 | let mut scratch_odds_n1 = [ 689 | *buffer.get_unchecked(1), 690 | *buffer.get_unchecked(5), 691 | *buffer.get_unchecked(9), 692 | *buffer.get_unchecked(13), 693 | ]; 694 | let mut scratch_odds_n3 = [ 695 | *buffer.get_unchecked(15), 696 | *buffer.get_unchecked(3), 697 | *buffer.get_unchecked(7), 698 | *buffer.get_unchecked(11), 699 | ]; 700 | 701 | // step 2: column FFTs 702 | self.butterfly8.process_inplace(&mut scratch_evens); 703 | butterfly4.process_inplace(&mut scratch_odds_n1); 704 | butterfly4.process_inplace(&mut scratch_odds_n3); 705 | 706 | // step 3: apply twiddle factors 707 | scratch_odds_n1[1] = scratch_odds_n1[1] * self.twiddle1; 708 | scratch_odds_n3[1] = scratch_odds_n3[1] * self.twiddle1.conj(); 709 | 710 | scratch_odds_n1[2] = scratch_odds_n1[2] * self.twiddle2; 711 | scratch_odds_n3[2] = scratch_odds_n3[2] * self.twiddle2.conj(); 712 | 713 | scratch_odds_n1[3] = scratch_odds_n1[3] * self.twiddle3; 714 | scratch_odds_n3[3] = scratch_odds_n3[3] * self.twiddle3.conj(); 715 | 716 | // step 4: cross FFTs 717 | Butterfly2::perform_fft_direct(&mut scratch_odds_n1[0], &mut scratch_odds_n3[0]); 718 | Butterfly2::perform_fft_direct(&mut scratch_odds_n1[1], &mut scratch_odds_n3[1]); 719 | Butterfly2::perform_fft_direct(&mut scratch_odds_n1[2], &mut scratch_odds_n3[2]); 720 | Butterfly2::perform_fft_direct(&mut scratch_odds_n1[3], &mut scratch_odds_n3[3]); 721 | 722 | // apply the butterfly 4 twiddle factor, which is just a rotation 723 | scratch_odds_n3[0] = twiddles::rotate_90(scratch_odds_n3[0], self.inverse); 724 | scratch_odds_n3[1] = twiddles::rotate_90(scratch_odds_n3[1], self.inverse); 725 | scratch_odds_n3[2] = twiddles::rotate_90(scratch_odds_n3[2], self.inverse); 726 | scratch_odds_n3[3] = twiddles::rotate_90(scratch_odds_n3[3], self.inverse); 727 | 728 | //step 5: copy/add/subtract data back to buffer 729 | *buffer.get_unchecked_mut(0) = scratch_evens[0] + scratch_odds_n1[0]; 730 | *buffer.get_unchecked_mut(1) = scratch_evens[1] + scratch_odds_n1[1]; 731 | *buffer.get_unchecked_mut(2) = scratch_evens[2] + scratch_odds_n1[2]; 732 | *buffer.get_unchecked_mut(3) = scratch_evens[3] + scratch_odds_n1[3]; 733 | *buffer.get_unchecked_mut(4) = scratch_evens[4] + scratch_odds_n3[0]; 734 | *buffer.get_unchecked_mut(5) = scratch_evens[5] + scratch_odds_n3[1]; 735 | *buffer.get_unchecked_mut(6) = scratch_evens[6] + scratch_odds_n3[2]; 736 | *buffer.get_unchecked_mut(7) = scratch_evens[7] + scratch_odds_n3[3]; 737 | *buffer.get_unchecked_mut(8) = scratch_evens[0] - scratch_odds_n1[0]; 738 | *buffer.get_unchecked_mut(9) = scratch_evens[1] - scratch_odds_n1[1]; 739 | *buffer.get_unchecked_mut(10) = scratch_evens[2] - scratch_odds_n1[2]; 740 | *buffer.get_unchecked_mut(11) = scratch_evens[3] - scratch_odds_n1[3]; 741 | *buffer.get_unchecked_mut(12) = scratch_evens[4] - scratch_odds_n3[0]; 742 | *buffer.get_unchecked_mut(13) = scratch_evens[5] - scratch_odds_n3[1]; 743 | *buffer.get_unchecked_mut(14) = scratch_evens[6] - scratch_odds_n3[2]; 744 | *buffer.get_unchecked_mut(15) = scratch_evens[7] - scratch_odds_n3[3]; 745 | 746 | } 747 | #[inline(always)] 748 | unsafe fn process_multi_inplace(&self, buffer: &mut [Complex]) { 749 | for chunk in buffer.chunks_mut(self.len()) { 750 | self.process_inplace(chunk); 751 | } 752 | } 753 | } 754 | impl FFT for Butterfly16 { 755 | fn process(&self, input: &mut [Complex], output: &mut [Complex]) { 756 | verify_length(input, output, self.len()); 757 | output.copy_from_slice(input); 758 | 759 | unsafe { self.process_inplace(output) }; 760 | } 761 | fn process_multi(&self, input: &mut [Complex], output: &mut [Complex]) { 762 | verify_length_divisible(input, output, self.len()); 763 | output.copy_from_slice(input); 764 | 765 | unsafe { self.process_multi_inplace(output) }; 766 | } 767 | } 768 | impl Length for Butterfly16 { 769 | #[inline(always)] 770 | fn len(&self) -> usize { 771 | 16 772 | } 773 | } 774 | impl IsInverse for Butterfly16 { 775 | #[inline(always)] 776 | fn is_inverse(&self) -> bool { 777 | self.inverse 778 | } 779 | } 780 | 781 | 782 | 783 | pub struct Butterfly32 { 784 | butterfly16: Butterfly16, 785 | butterfly8: Butterfly8, 786 | twiddles: [Complex; 7], 787 | inverse: bool, 788 | } 789 | impl Butterfly32 790 | { 791 | #[inline(always)] 792 | pub fn new(inverse: bool) -> Self { 793 | Butterfly32 { 794 | butterfly16: Butterfly16::new(inverse), 795 | butterfly8: Butterfly8::new(inverse), 796 | twiddles: [ 797 | twiddles::single_twiddle(1, 32, inverse), 798 | twiddles::single_twiddle(2, 32, inverse), 799 | twiddles::single_twiddle(3, 32, inverse), 800 | twiddles::single_twiddle(4, 32, inverse), 801 | twiddles::single_twiddle(5, 32, inverse), 802 | twiddles::single_twiddle(6, 32, inverse), 803 | twiddles::single_twiddle(7, 32, inverse), 804 | ], 805 | inverse: inverse, 806 | } 807 | } 808 | } 809 | impl FFTButterfly for Butterfly32 { 810 | #[inline(always)] 811 | unsafe fn process_inplace(&self, buffer: &mut [Complex]) { 812 | // we're going to hardcode a step of split radix 813 | // step 1: copy and reorder the input into the scratch 814 | let mut scratch_evens = [ 815 | *buffer.get_unchecked(0), 816 | *buffer.get_unchecked(2), 817 | *buffer.get_unchecked(4), 818 | *buffer.get_unchecked(6), 819 | *buffer.get_unchecked(8), 820 | *buffer.get_unchecked(10), 821 | *buffer.get_unchecked(12), 822 | *buffer.get_unchecked(14), 823 | *buffer.get_unchecked(16), 824 | *buffer.get_unchecked(18), 825 | *buffer.get_unchecked(20), 826 | *buffer.get_unchecked(22), 827 | *buffer.get_unchecked(24), 828 | *buffer.get_unchecked(26), 829 | *buffer.get_unchecked(28), 830 | *buffer.get_unchecked(30), 831 | ]; 832 | 833 | let mut scratch_odds_n1 = [ 834 | *buffer.get_unchecked(1), 835 | *buffer.get_unchecked(5), 836 | *buffer.get_unchecked(9), 837 | *buffer.get_unchecked(13), 838 | *buffer.get_unchecked(17), 839 | *buffer.get_unchecked(21), 840 | *buffer.get_unchecked(25), 841 | *buffer.get_unchecked(29), 842 | ]; 843 | let mut scratch_odds_n3 = [ 844 | *buffer.get_unchecked(31), 845 | *buffer.get_unchecked(3), 846 | *buffer.get_unchecked(7), 847 | *buffer.get_unchecked(11), 848 | *buffer.get_unchecked(15), 849 | *buffer.get_unchecked(19), 850 | *buffer.get_unchecked(23), 851 | *buffer.get_unchecked(27), 852 | ]; 853 | 854 | // step 2: column FFTs 855 | self.butterfly16.process_inplace(&mut scratch_evens); 856 | self.butterfly8.process_inplace(&mut scratch_odds_n1); 857 | self.butterfly8.process_inplace(&mut scratch_odds_n3); 858 | 859 | // step 3: apply twiddle factors 860 | scratch_odds_n1[1] = scratch_odds_n1[1] * self.twiddles[0]; 861 | scratch_odds_n3[1] = scratch_odds_n3[1] * self.twiddles[0].conj(); 862 | 863 | scratch_odds_n1[2] = scratch_odds_n1[2] * self.twiddles[1]; 864 | scratch_odds_n3[2] = scratch_odds_n3[2] * self.twiddles[1].conj(); 865 | 866 | scratch_odds_n1[3] = scratch_odds_n1[3] * self.twiddles[2]; 867 | scratch_odds_n3[3] = scratch_odds_n3[3] * self.twiddles[2].conj(); 868 | 869 | scratch_odds_n1[4] = scratch_odds_n1[4] * self.twiddles[3]; 870 | scratch_odds_n3[4] = scratch_odds_n3[4] * self.twiddles[3].conj(); 871 | 872 | scratch_odds_n1[5] = scratch_odds_n1[5] * self.twiddles[4]; 873 | scratch_odds_n3[5] = scratch_odds_n3[5] * self.twiddles[4].conj(); 874 | 875 | scratch_odds_n1[6] = scratch_odds_n1[6] * self.twiddles[5]; 876 | scratch_odds_n3[6] = scratch_odds_n3[6] * self.twiddles[5].conj(); 877 | 878 | scratch_odds_n1[7] = scratch_odds_n1[7] * self.twiddles[6]; 879 | scratch_odds_n3[7] = scratch_odds_n3[7] * self.twiddles[6].conj(); 880 | 881 | // step 4: cross FFTs 882 | Butterfly2::perform_fft_direct(&mut scratch_odds_n1[0], &mut scratch_odds_n3[0]); 883 | Butterfly2::perform_fft_direct(&mut scratch_odds_n1[1], &mut scratch_odds_n3[1]); 884 | Butterfly2::perform_fft_direct(&mut scratch_odds_n1[2], &mut scratch_odds_n3[2]); 885 | Butterfly2::perform_fft_direct(&mut scratch_odds_n1[3], &mut scratch_odds_n3[3]); 886 | Butterfly2::perform_fft_direct(&mut scratch_odds_n1[4], &mut scratch_odds_n3[4]); 887 | Butterfly2::perform_fft_direct(&mut scratch_odds_n1[5], &mut scratch_odds_n3[5]); 888 | Butterfly2::perform_fft_direct(&mut scratch_odds_n1[6], &mut scratch_odds_n3[6]); 889 | Butterfly2::perform_fft_direct(&mut scratch_odds_n1[7], &mut scratch_odds_n3[7]); 890 | 891 | // apply the butterfly 4 twiddle factor, which is just a rotation 892 | scratch_odds_n3[0] = twiddles::rotate_90(scratch_odds_n3[0], self.inverse); 893 | scratch_odds_n3[1] = twiddles::rotate_90(scratch_odds_n3[1], self.inverse); 894 | scratch_odds_n3[2] = twiddles::rotate_90(scratch_odds_n3[2], self.inverse); 895 | scratch_odds_n3[3] = twiddles::rotate_90(scratch_odds_n3[3], self.inverse); 896 | scratch_odds_n3[4] = twiddles::rotate_90(scratch_odds_n3[4], self.inverse); 897 | scratch_odds_n3[5] = twiddles::rotate_90(scratch_odds_n3[5], self.inverse); 898 | scratch_odds_n3[6] = twiddles::rotate_90(scratch_odds_n3[6], self.inverse); 899 | scratch_odds_n3[7] = twiddles::rotate_90(scratch_odds_n3[7], self.inverse); 900 | 901 | //step 5: copy/add/subtract data back to buffer 902 | *buffer.get_unchecked_mut(0) = scratch_evens[0] + scratch_odds_n1[0]; 903 | *buffer.get_unchecked_mut(1) = scratch_evens[1] + scratch_odds_n1[1]; 904 | *buffer.get_unchecked_mut(2) = scratch_evens[2] + scratch_odds_n1[2]; 905 | *buffer.get_unchecked_mut(3) = scratch_evens[3] + scratch_odds_n1[3]; 906 | *buffer.get_unchecked_mut(4) = scratch_evens[4] + scratch_odds_n1[4]; 907 | *buffer.get_unchecked_mut(5) = scratch_evens[5] + scratch_odds_n1[5]; 908 | *buffer.get_unchecked_mut(6) = scratch_evens[6] + scratch_odds_n1[6]; 909 | *buffer.get_unchecked_mut(7) = scratch_evens[7] + scratch_odds_n1[7]; 910 | *buffer.get_unchecked_mut(8) = scratch_evens[8] + scratch_odds_n3[0]; 911 | *buffer.get_unchecked_mut(9) = scratch_evens[9] + scratch_odds_n3[1]; 912 | *buffer.get_unchecked_mut(10) = scratch_evens[10] + scratch_odds_n3[2]; 913 | *buffer.get_unchecked_mut(11) = scratch_evens[11] + scratch_odds_n3[3]; 914 | *buffer.get_unchecked_mut(12) = scratch_evens[12] + scratch_odds_n3[4]; 915 | *buffer.get_unchecked_mut(13) = scratch_evens[13] + scratch_odds_n3[5]; 916 | *buffer.get_unchecked_mut(14) = scratch_evens[14] + scratch_odds_n3[6]; 917 | *buffer.get_unchecked_mut(15) = scratch_evens[15] + scratch_odds_n3[7]; 918 | *buffer.get_unchecked_mut(16) = scratch_evens[0] - scratch_odds_n1[0]; 919 | *buffer.get_unchecked_mut(17) = scratch_evens[1] - scratch_odds_n1[1]; 920 | *buffer.get_unchecked_mut(18) = scratch_evens[2] - scratch_odds_n1[2]; 921 | *buffer.get_unchecked_mut(19) = scratch_evens[3] - scratch_odds_n1[3]; 922 | *buffer.get_unchecked_mut(20) = scratch_evens[4] - scratch_odds_n1[4]; 923 | *buffer.get_unchecked_mut(21) = scratch_evens[5] - scratch_odds_n1[5]; 924 | *buffer.get_unchecked_mut(22) = scratch_evens[6] - scratch_odds_n1[6]; 925 | *buffer.get_unchecked_mut(23) = scratch_evens[7] - scratch_odds_n1[7]; 926 | *buffer.get_unchecked_mut(24) = scratch_evens[8] - scratch_odds_n3[0]; 927 | *buffer.get_unchecked_mut(25) = scratch_evens[9] - scratch_odds_n3[1]; 928 | *buffer.get_unchecked_mut(26) = scratch_evens[10] - scratch_odds_n3[2]; 929 | *buffer.get_unchecked_mut(27) = scratch_evens[11] - scratch_odds_n3[3]; 930 | *buffer.get_unchecked_mut(28) = scratch_evens[12] - scratch_odds_n3[4]; 931 | *buffer.get_unchecked_mut(29) = scratch_evens[13] - scratch_odds_n3[5]; 932 | *buffer.get_unchecked_mut(30) = scratch_evens[14] - scratch_odds_n3[6]; 933 | *buffer.get_unchecked_mut(31) = scratch_evens[15] - scratch_odds_n3[7]; 934 | 935 | } 936 | #[inline(always)] 937 | unsafe fn process_multi_inplace(&self, buffer: &mut [Complex]) { 938 | for chunk in buffer.chunks_mut(self.len()) { 939 | self.process_inplace(chunk); 940 | } 941 | } 942 | } 943 | impl FFT for Butterfly32 { 944 | fn process(&self, input: &mut [Complex], output: &mut [Complex]) { 945 | verify_length(input, output, self.len()); 946 | output.copy_from_slice(input); 947 | 948 | unsafe { self.process_inplace(output) }; 949 | } 950 | fn process_multi(&self, input: &mut [Complex], output: &mut [Complex]) { 951 | verify_length_divisible(input, output, self.len()); 952 | output.copy_from_slice(input); 953 | 954 | unsafe { self.process_multi_inplace(output) }; 955 | } 956 | } 957 | impl Length for Butterfly32 { 958 | #[inline(always)] 959 | fn len(&self) -> usize { 960 | 32 961 | } 962 | } 963 | impl IsInverse for Butterfly32 { 964 | #[inline(always)] 965 | fn is_inverse(&self) -> bool { 966 | self.inverse 967 | } 968 | } 969 | 970 | 971 | 972 | #[cfg(test)] 973 | mod unit_tests { 974 | use super::*; 975 | use test_utils::{random_signal, compare_vectors, check_fft_algorithm}; 976 | use algorithm::DFT; 977 | use num_traits::Zero; 978 | 979 | //the tests for all butterflies will be identical except for the identifiers used and size 980 | //so it's ideal for a macro 981 | macro_rules! test_butterfly_func { 982 | ($test_name:ident, $struct_name:ident, $size:expr) => ( 983 | #[test] 984 | fn $test_name() { 985 | let butterfly = $struct_name::new(false); 986 | 987 | check_fft_algorithm(&butterfly, $size, false); 988 | check_butterfly(&butterfly, $size, false); 989 | 990 | let butterfly_inverse = $struct_name::new(true); 991 | 992 | check_fft_algorithm(&butterfly_inverse, $size, true); 993 | check_butterfly(&butterfly_inverse, $size, true); 994 | } 995 | ) 996 | } 997 | test_butterfly_func!(test_butterfly2, Butterfly2, 2); 998 | test_butterfly_func!(test_butterfly3, Butterfly3, 3); 999 | test_butterfly_func!(test_butterfly4, Butterfly4, 4); 1000 | test_butterfly_func!(test_butterfly5, Butterfly5, 5); 1001 | test_butterfly_func!(test_butterfly6, Butterfly6, 6); 1002 | test_butterfly_func!(test_butterfly7, Butterfly7, 7); 1003 | test_butterfly_func!(test_butterfly8, Butterfly8, 8); 1004 | test_butterfly_func!(test_butterfly16, Butterfly16, 16); 1005 | test_butterfly_func!(test_butterfly32, Butterfly32, 32); 1006 | 1007 | 1008 | fn check_butterfly(butterfly: &FFTButterfly, size: usize, inverse: bool) { 1009 | assert_eq!(butterfly.len(), size, "Butterfly algorithm reported wrong size"); 1010 | assert_eq!(butterfly.is_inverse(), inverse, "Butterfly algorithm reported wrong inverse value"); 1011 | 1012 | let n = 5; 1013 | 1014 | //test the forward direction 1015 | let dft = DFT::new(size, inverse); 1016 | 1017 | // set up buffers 1018 | let mut expected_input = random_signal(size * n); 1019 | let mut expected_output = vec![Zero::zero(); size * n]; 1020 | 1021 | let mut inplace_buffer = expected_input.clone(); 1022 | let mut inplace_multi_buffer = expected_input.clone(); 1023 | 1024 | // perform the test 1025 | dft.process_multi(&mut expected_input, &mut expected_output); 1026 | 1027 | unsafe { butterfly.process_multi_inplace(&mut inplace_multi_buffer); } 1028 | 1029 | for chunk in inplace_buffer.chunks_mut(size) { 1030 | unsafe { butterfly.process_inplace(chunk) }; 1031 | } 1032 | 1033 | assert!(compare_vectors(&expected_output, &inplace_buffer), "process_inplace() failed, length = {}, inverse = {}", size, inverse); 1034 | assert!(compare_vectors(&expected_output, &inplace_multi_buffer), "process_multi_inplace() failed, length = {}, inverse = {}", size, inverse); 1035 | } 1036 | } 1037 | -------------------------------------------------------------------------------- /src/algorithm/dft.rs: -------------------------------------------------------------------------------- 1 | use num_complex::Complex; 2 | use num_traits::Zero; 3 | 4 | use common::{FFTnum, verify_length, verify_length_divisible}; 5 | 6 | use ::{Length, IsInverse, FFT}; 7 | use twiddles; 8 | 9 | /// Naive O(n^2 ) Discrete Fourier Transform implementation 10 | /// 11 | /// This implementation is primarily used to test other FFT algorithms. In a few rare cases, such as small 12 | /// [Cunningham Chain](https://en.wikipedia.org/wiki/Cunningham_chain) primes, this can be faster than the O(nlogn) 13 | /// algorithms 14 | /// 15 | /// ~~~ 16 | /// // Computes a naive DFT of size 1234 17 | /// use rustfft::algorithm::DFT; 18 | /// use rustfft::FFT; 19 | /// use rustfft::num_complex::Complex; 20 | /// use rustfft::num_traits::Zero; 21 | /// 22 | /// let mut input: Vec> = vec![Zero::zero(); 1234]; 23 | /// let mut output: Vec> = vec![Zero::zero(); 1234]; 24 | /// 25 | /// let dft = DFT::new(1234, false); 26 | /// dft.process(&mut input, &mut output); 27 | /// ~~~ 28 | pub struct DFT { 29 | twiddles: Vec>, 30 | inverse: bool, 31 | } 32 | 33 | impl DFT { 34 | /// Preallocates necessary arrays and precomputes necessary data to efficiently compute DFT 35 | pub fn new(len: usize, inverse: bool) -> Self { 36 | DFT { 37 | twiddles: twiddles::generate_twiddle_factors(len, inverse), 38 | inverse: inverse 39 | } 40 | } 41 | 42 | #[inline(always)] 43 | fn perform_fft(&self, signal: &[Complex], spectrum: &mut [Complex]) { 44 | for k in 0..spectrum.len() { 45 | let output_cell = spectrum.get_mut(k).unwrap(); 46 | 47 | *output_cell = Zero::zero(); 48 | let mut twiddle_index = 0; 49 | 50 | for input_cell in signal { 51 | let twiddle = self.twiddles[twiddle_index]; 52 | *output_cell = *output_cell + twiddle * input_cell; 53 | 54 | twiddle_index += k; 55 | if twiddle_index >= self.twiddles.len() { 56 | twiddle_index -= self.twiddles.len(); 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | impl FFT for DFT { 64 | fn process(&self, input: &mut [Complex], output: &mut [Complex]) { 65 | verify_length(input, output, self.len()); 66 | 67 | self.perform_fft(input, output); 68 | } 69 | fn process_multi(&self, input: &mut [Complex], output: &mut [Complex]) { 70 | verify_length_divisible(input, output, self.len()); 71 | 72 | for (in_chunk, out_chunk) in input.chunks_mut(self.len()).zip(output.chunks_mut(self.len())) { 73 | self.perform_fft(in_chunk, out_chunk); 74 | } 75 | } 76 | } 77 | impl Length for DFT { 78 | #[inline(always)] 79 | fn len(&self) -> usize { 80 | self.twiddles.len() 81 | } 82 | } 83 | impl IsInverse for DFT { 84 | #[inline(always)] 85 | fn is_inverse(&self) -> bool { 86 | self.inverse 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod unit_tests { 92 | use super::*; 93 | use std::f32; 94 | use test_utils::{random_signal, compare_vectors}; 95 | use num_complex::Complex; 96 | use num_traits::Zero; 97 | 98 | fn dft(signal: &[Complex], spectrum: &mut [Complex]) { 99 | for (k, spec_bin) in spectrum.iter_mut().enumerate() { 100 | let mut sum = Zero::zero(); 101 | for (i, &x) in signal.iter().enumerate() { 102 | let angle = -1f32 * (i * k) as f32 * 2f32 * f32::consts::PI / signal.len() as f32; 103 | let twiddle = Complex::from_polar(&1f32, &angle); 104 | 105 | sum = sum + twiddle * x; 106 | } 107 | *spec_bin = sum; 108 | } 109 | } 110 | 111 | #[test] 112 | fn test_matches_dft() { 113 | let n = 4; 114 | 115 | for len in 1..20 { 116 | let dft_instance = DFT::new(len, false); 117 | assert_eq!(dft_instance.len(), len, "DFT instance reported incorrect length"); 118 | 119 | let mut expected_input = random_signal(len * n); 120 | let mut actual_input = expected_input.clone(); 121 | let mut multi_input = expected_input.clone(); 122 | 123 | let mut expected_output = vec![Zero::zero(); len * n]; 124 | let mut actual_output = expected_output.clone(); 125 | let mut multi_output = expected_output.clone(); 126 | 127 | // perform the test 128 | dft_instance.process_multi(&mut multi_input, &mut multi_output); 129 | 130 | for (input_chunk, output_chunk) in actual_input.chunks_mut(len).zip(actual_output.chunks_mut(len)) { 131 | dft_instance.process(input_chunk, output_chunk); 132 | } 133 | 134 | for (input_chunk, output_chunk) in expected_input.chunks_mut(len).zip(expected_output.chunks_mut(len)) { 135 | dft(input_chunk, output_chunk); 136 | } 137 | 138 | assert!(compare_vectors(&expected_output, &actual_output), "process() failed, length = {}", len); 139 | assert!(compare_vectors(&expected_output, &multi_output), "process_multi() failed, length = {}", len); 140 | } 141 | 142 | //verify that it doesn't crash if we have a length of 0 143 | let zero_dft = DFT::new(0, false); 144 | let mut zero_input: Vec> = Vec::new(); 145 | let mut zero_output: Vec> = Vec::new(); 146 | 147 | zero_dft.process(&mut zero_input, &mut zero_output); 148 | } 149 | 150 | /// Returns true if our `dft` function calculates the given spectrum from the 151 | /// given signal, and if rustfft's DFT struct does the same 152 | fn test_dft_correct(signal: &[Complex], spectrum: &[Complex]) -> bool { 153 | assert_eq!(signal.len(), spectrum.len()); 154 | 155 | let expected_signal = signal.to_vec(); 156 | let mut expected_spectrum = vec![Zero::zero(); spectrum.len()]; 157 | 158 | let mut actual_signal = signal.to_vec(); 159 | let mut actual_spectrum = vec![Zero::zero(); spectrum.len()]; 160 | 161 | dft(&expected_signal, &mut expected_spectrum); 162 | 163 | let dft_instance = DFT::new(signal.len(), false); 164 | dft_instance.process(&mut actual_signal, &mut actual_spectrum); 165 | 166 | return compare_vectors(spectrum, &expected_spectrum) && compare_vectors(spectrum, &actual_spectrum); 167 | } 168 | 169 | #[test] 170 | fn test_dft_known_len_2() { 171 | let signal = [Complex{re: 1f32, im: 0f32}, 172 | Complex{re:-1f32, im: 0f32}]; 173 | let spectrum = [Complex{re: 0f32, im: 0f32}, 174 | Complex{re: 2f32, im: 0f32}]; 175 | assert!(test_dft_correct(&signal[..], &spectrum[..])); 176 | } 177 | 178 | #[test] 179 | fn test_dft_known_len_3() { 180 | let signal = [Complex{re: 1f32, im: 1f32}, 181 | Complex{re: 2f32, im:-3f32}, 182 | Complex{re:-1f32, im: 4f32}]; 183 | let spectrum = [Complex{re: 2f32, im: 2f32}, 184 | Complex{re: -5.562177f32, im: -2.098076f32}, 185 | Complex{re: 6.562178f32, im: 3.09807f32}]; 186 | assert!(test_dft_correct(&signal[..], &spectrum[..])); 187 | } 188 | 189 | #[test] 190 | fn test_dft_known_len_4() { 191 | let signal = [Complex{re: 0f32, im: 1f32}, 192 | Complex{re: 2.5f32, im:-3f32}, 193 | Complex{re:-1f32, im: -1f32}, 194 | Complex{re: 4f32, im: 0f32}]; 195 | let spectrum = [Complex{re: 5.5f32, im: -3f32}, 196 | Complex{re: -2f32, im: 3.5f32}, 197 | Complex{re: -7.5f32, im: 3f32}, 198 | Complex{re: 4f32, im: 0.5f32}]; 199 | assert!(test_dft_correct(&signal[..], &spectrum[..])); 200 | } 201 | 202 | #[test] 203 | fn test_dft_known_len_6() { 204 | let signal = [Complex{re: 1f32, im: 1f32}, 205 | Complex{re: 2f32, im: 2f32}, 206 | Complex{re: 3f32, im: 3f32}, 207 | Complex{re: 4f32, im: 4f32}, 208 | Complex{re: 5f32, im: 5f32}, 209 | Complex{re: 6f32, im: 6f32}]; 210 | let spectrum = [Complex{re: 21f32, im: 21f32}, 211 | Complex{re: -8.16f32, im: 2.16f32}, 212 | Complex{re: -4.76f32, im: -1.24f32}, 213 | Complex{re: -3f32, im: -3f32}, 214 | Complex{re: -1.24f32, im: -4.76f32}, 215 | Complex{re: 2.16f32, im: -8.16f32}]; 216 | assert!(test_dft_correct(&signal[..], &spectrum[..])); 217 | } 218 | } -------------------------------------------------------------------------------- /src/algorithm/good_thomas_algorithm.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use num_complex::Complex; 4 | use strength_reduce::StrengthReducedUsize; 5 | use transpose; 6 | 7 | use common::{FFTnum, verify_length, verify_length_divisible}; 8 | 9 | use math_utils; 10 | use array_utils; 11 | 12 | use ::{Length, IsInverse, FFT}; 13 | use algorithm::butterflies::FFTButterfly; 14 | 15 | /// Implementation of the [Good-Thomas Algorithm (AKA Prime Factor Algorithm)](https://en.wikipedia.org/wiki/Prime-factor_FFT_algorithm) 16 | /// 17 | /// This algorithm factors a size n FFT into n1 * n2, where GCD(n1, n2) == 1 18 | /// 19 | /// Conceptually, this algorithm is very similar to the Mixed-Radix FFT, except because GCD(n1, n2) == 1 we can do some 20 | /// number theory trickery to reduce the number of floating-point multiplications and additions. Additionally, It can 21 | /// be faster than Mixed-Radix at sizes below 10,000 or so. 22 | /// 23 | /// ~~~ 24 | /// // Computes a forward FFT of size 1200, using the Good-Thomas Algorithm 25 | /// use rustfft::algorithm::GoodThomasAlgorithm; 26 | /// use rustfft::{FFT, FFTplanner}; 27 | /// use rustfft::num_complex::Complex; 28 | /// use rustfft::num_traits::Zero; 29 | /// 30 | /// let mut input: Vec> = vec![Zero::zero(); 1200]; 31 | /// let mut output: Vec> = vec![Zero::zero(); 1200]; 32 | /// 33 | /// // we need to find an n1 and n2 such that n1 * n2 == 1200 and GCD(n1, n2) == 1 34 | /// // n1 = 48 and n2 = 25 satisfies this 35 | /// let mut planner = FFTplanner::new(false); 36 | /// let inner_fft_n1 = planner.plan_fft(48); 37 | /// let inner_fft_n2 = planner.plan_fft(25); 38 | /// 39 | /// // the good-thomas FFT length will be inner_fft_n1.len() * inner_fft_n2.len() = 1200 40 | /// let fft = GoodThomasAlgorithm::new(inner_fft_n1, inner_fft_n2); 41 | /// fft.process(&mut input, &mut output); 42 | /// ~~~ 43 | pub struct GoodThomasAlgorithm { 44 | width: usize, 45 | width_size_fft: Arc>, 46 | 47 | height: usize, 48 | height_size_fft: Arc>, 49 | 50 | input_x_stride: usize, 51 | input_y_stride: usize, 52 | 53 | len: StrengthReducedUsize, 54 | inverse: bool, 55 | } 56 | 57 | impl GoodThomasAlgorithm { 58 | /// Creates a FFT instance which will process inputs/outputs of size `width_fft.len() * height_fft.len()` 59 | /// 60 | /// GCD(width_fft.len(), height_fft.len()) must be equal to 1 61 | pub fn new(width_fft: Arc>, height_fft: Arc>) -> Self { 62 | assert_eq!( 63 | width_fft.is_inverse(), height_fft.is_inverse(), 64 | "width_fft and height_fft must both be inverse, or neither. got width inverse={}, height inverse={}", 65 | width_fft.is_inverse(), height_fft.is_inverse()); 66 | 67 | let width = width_fft.len(); 68 | let height = height_fft.len(); 69 | let is_inverse = width_fft.is_inverse(); 70 | 71 | // compute the nultiplicative inverse of width mod height and vice versa 72 | let (gcd, mut width_inverse, mut height_inverse) = 73 | math_utils::extended_euclidean_algorithm(width as i64, height as i64); 74 | assert!(gcd == 1, 75 | "Invalid input width and height to Good-Thomas Algorithm: ({},{}): Inputs must be coprime", 76 | width, 77 | height); 78 | 79 | // width_inverse or height_inverse might be negative, make it positive 80 | if width_inverse < 0 { 81 | width_inverse += height as i64; 82 | } 83 | if height_inverse < 0 { 84 | height_inverse += width as i64; 85 | } 86 | 87 | Self { 88 | width: width, 89 | width_size_fft: width_fft, 90 | 91 | height: height, 92 | height_size_fft: height_fft, 93 | 94 | input_x_stride: height_inverse as usize * height, 95 | input_y_stride: width_inverse as usize * width, 96 | 97 | len: StrengthReducedUsize::new(width * height), 98 | inverse: is_inverse, 99 | } 100 | } 101 | 102 | fn perform_fft(&self, input: &mut [Complex], output: &mut [Complex]) { 103 | // copy the input into the output buffer 104 | for (y, row) in output.chunks_mut(self.width).enumerate() { 105 | let input_base = y * self.input_y_stride; 106 | for (x, output_cell) in row.iter_mut().enumerate() { 107 | let input_index = (input_base + x * self.input_x_stride) % self.len; 108 | *output_cell = input[input_index]; 109 | } 110 | } 111 | 112 | // run FFTs of size `width` 113 | self.width_size_fft.process_multi(output, input); 114 | 115 | // transpose 116 | transpose::transpose(input, output, self.width, self.height); 117 | 118 | // run FFTs of size 'height' 119 | self.height_size_fft.process_multi(output, input); 120 | 121 | // copy to the output, using our output redordering mapping 122 | for (x, row) in input.chunks(self.height).enumerate() { 123 | let output_base = x * self.height; 124 | for (y, input_cell) in row.iter().enumerate() { 125 | let output_index = (output_base + y * self.width) % self.len; 126 | output[output_index] = *input_cell; 127 | } 128 | } 129 | } 130 | } 131 | 132 | impl FFT for GoodThomasAlgorithm { 133 | fn process(&self, input: &mut [Complex], output: &mut [Complex]) { 134 | verify_length(input, output, self.len()); 135 | 136 | self.perform_fft(input, output); 137 | } 138 | fn process_multi(&self, input: &mut [Complex], output: &mut [Complex]) { 139 | verify_length_divisible(input, output, self.len()); 140 | 141 | for (in_chunk, out_chunk) in input.chunks_mut(self.len()).zip(output.chunks_mut(self.len())) { 142 | self.perform_fft(in_chunk, out_chunk); 143 | } 144 | } 145 | } 146 | impl Length for GoodThomasAlgorithm { 147 | #[inline(always)] 148 | fn len(&self) -> usize { 149 | self.width * self.height 150 | } 151 | } 152 | impl IsInverse for GoodThomasAlgorithm { 153 | #[inline(always)] 154 | fn is_inverse(&self) -> bool { 155 | self.inverse 156 | } 157 | } 158 | 159 | 160 | 161 | 162 | /// Implementation of the Good-Thomas Algorithm, specialized for the case where both inner FFTs are butterflies 163 | /// 164 | /// This algorithm factors a size n FFT into n1 * n2, where GCD(n1, n2) == 1 165 | /// 166 | /// Conceptually, this algorithm is very similar to the Mixed-Radix FFT, except because GCD(n1, n2) == 1 we can do some 167 | /// number theory trickery to reduce the number of floating-point multiplications and additions. It typically performs 168 | /// better than Mixed-Radix Double Butterfly Algorithm, especially at small sizes. 169 | /// 170 | /// ~~~ 171 | /// // Computes a forward FFT of size 56, using the Good-Thoma Butterfly Algorithm 172 | /// use std::sync::Arc; 173 | /// use rustfft::algorithm::GoodThomasAlgorithmDoubleButterfly; 174 | /// use rustfft::algorithm::butterflies::{Butterfly7, Butterfly8}; 175 | /// use rustfft::FFT; 176 | /// use rustfft::num_complex::Complex; 177 | /// use rustfft::num_traits::Zero; 178 | /// 179 | /// let mut input: Vec> = vec![Zero::zero(); 56]; 180 | /// let mut output: Vec> = vec![Zero::zero(); 56]; 181 | /// 182 | /// // we need to find an n1 and n2 such that n1 * n2 == 56 and GCD(n1, n2) == 1 183 | /// // n1 = 7 and n2 = 8 satisfies this 184 | /// let inner_fft_n1 = Arc::new(Butterfly7::new(false)); 185 | /// let inner_fft_n2 = Arc::new(Butterfly8::new(false)); 186 | /// 187 | /// // the good-thomas FFT length will be inner_fft_n1.len() * inner_fft_n2.len() = 56 188 | /// let fft = GoodThomasAlgorithmDoubleButterfly::new(inner_fft_n1, inner_fft_n2); 189 | /// fft.process(&mut input, &mut output); 190 | /// ~~~ 191 | pub struct GoodThomasAlgorithmDoubleButterfly { 192 | width: usize, 193 | width_size_fft: Arc>, 194 | 195 | height: usize, 196 | height_size_fft: Arc>, 197 | 198 | input_output_map: Box<[usize]>, 199 | 200 | inverse: bool, 201 | } 202 | 203 | impl GoodThomasAlgorithmDoubleButterfly { 204 | /// Creates a FFT instance which will process inputs/outputs of size `width_fft.len() * height_fft.len()` 205 | /// 206 | /// GCD(n1.len(), n2.len()) must be equal to 1 207 | pub fn new(width_fft: Arc>, height_fft: Arc>) -> Self { 208 | assert_eq!( 209 | width_fft.is_inverse(), height_fft.is_inverse(), 210 | "n1_fft and height_fft must both be inverse, or neither. got width inverse={}, height inverse={}", 211 | width_fft.is_inverse(), height_fft.is_inverse()); 212 | 213 | let width = width_fft.len(); 214 | let height = height_fft.len(); 215 | let len = width * height; 216 | 217 | // compute the nultiplicative inverse of n1 mod height and vice versa 218 | let (gcd, mut width_inverse, mut height_inverse) = 219 | math_utils::extended_euclidean_algorithm(width as i64, height as i64); 220 | assert!(gcd == 1, 221 | "Invalid input n1 and height to Good-Thomas Algorithm: ({},{}): Inputs must be coprime", 222 | width, 223 | height); 224 | 225 | // width_inverse or height_inverse might be negative, make it positive 226 | if width_inverse < 0 { 227 | width_inverse += height as i64; 228 | } 229 | if height_inverse < 0 { 230 | height_inverse += width as i64; 231 | } 232 | 233 | // NOTE: we are precomputing the input and output reordering indexes, because benchmarking shows that it's 10-20% faster 234 | // If we wanted to optimize for memory use or setup time instead of multiple-FFT speed, we could compute these on the fly in the perform_fft() method 235 | let input_iter = (0..len) 236 | .map(|i| (i % width, i / width)) 237 | .map(|(x, y)| (x * height + y * width) % len); 238 | let output_iter = (0..len) 239 | .map(|i| (i % height, i / height)) 240 | .map(|(y, x)| (x * height * height_inverse as usize + y * width * width_inverse as usize) % len); 241 | 242 | let input_output_map: Vec = input_iter.chain(output_iter).collect(); 243 | 244 | GoodThomasAlgorithmDoubleButterfly { 245 | inverse: width_fft.is_inverse(), 246 | 247 | width: width, 248 | width_size_fft: width_fft, 249 | 250 | height: height, 251 | height_size_fft: height_fft, 252 | 253 | input_output_map: input_output_map.into_boxed_slice(), 254 | } 255 | } 256 | 257 | unsafe fn perform_fft(&self, input: &mut [Complex], output: &mut [Complex]) { 258 | 259 | let (input_map, output_map) = self.input_output_map.split_at(self.len()); 260 | 261 | // copy the input using our reordering mapping 262 | for (output_element, &input_index) in output.iter_mut().zip(input_map.iter()) { 263 | *output_element = input[input_index]; 264 | } 265 | 266 | // run FFTs of size `width` 267 | self.width_size_fft.process_multi_inplace(output); 268 | 269 | // transpose 270 | array_utils::transpose_small(self.width, self.height, output, input); 271 | 272 | // run FFTs of size 'height' 273 | self.height_size_fft.process_multi_inplace(input); 274 | 275 | // copy to the output, using our output redordeing mapping 276 | for (input_element, &output_index) in input.iter().zip(output_map.iter()) { 277 | output[output_index] = *input_element; 278 | } 279 | } 280 | } 281 | 282 | impl FFT for GoodThomasAlgorithmDoubleButterfly { 283 | fn process(&self, input: &mut [Complex], output: &mut [Complex]) { 284 | verify_length(input, output, self.len()); 285 | 286 | unsafe { self.perform_fft(input, output) }; 287 | } 288 | fn process_multi(&self, input: &mut [Complex], output: &mut [Complex]) { 289 | verify_length_divisible(input, output, self.len()); 290 | 291 | for (in_chunk, out_chunk) in input.chunks_mut(self.len()).zip(output.chunks_mut(self.len())) { 292 | unsafe { self.perform_fft(in_chunk, out_chunk) }; 293 | } 294 | } 295 | } 296 | impl Length for GoodThomasAlgorithmDoubleButterfly { 297 | #[inline(always)] 298 | fn len(&self) -> usize { 299 | self.width * self.height 300 | } 301 | } 302 | impl IsInverse for GoodThomasAlgorithmDoubleButterfly { 303 | #[inline(always)] 304 | fn is_inverse(&self) -> bool { 305 | self.inverse 306 | } 307 | } 308 | 309 | 310 | #[cfg(test)] 311 | mod unit_tests { 312 | use super::*; 313 | use std::sync::Arc; 314 | use test_utils::{check_fft_algorithm, make_butterfly}; 315 | use algorithm::DFT; 316 | use num_integer::gcd; 317 | 318 | #[test] 319 | fn test_good_thomas() { 320 | for width in 1..12 { 321 | for height in 1..12 { 322 | if gcd(width, height) == 1 { 323 | test_good_thomas_with_lengths(width, height, false); 324 | test_good_thomas_with_lengths(width, height, true); 325 | } 326 | } 327 | } 328 | } 329 | 330 | #[test] 331 | fn test_good_thomas_double_butterfly() { 332 | let butterfly_sizes = [2,3,4,5,6,7,8,16]; 333 | for width in &butterfly_sizes { 334 | for height in &butterfly_sizes { 335 | if gcd(*width, *height) == 1 { 336 | test_good_thomas_butterfly_with_lengths(*width, *height, false); 337 | test_good_thomas_butterfly_with_lengths(*width, *height, true); 338 | } 339 | } 340 | } 341 | } 342 | 343 | fn test_good_thomas_with_lengths(width: usize, height: usize, inverse: bool) { 344 | let width_fft = Arc::new(DFT::new(width, inverse)) as Arc>; 345 | let height_fft = Arc::new(DFT::new(height, inverse)) as Arc>; 346 | 347 | let fft = GoodThomasAlgorithm::new(width_fft, height_fft); 348 | 349 | check_fft_algorithm(&fft, width * height, inverse); 350 | } 351 | 352 | fn test_good_thomas_butterfly_with_lengths(width: usize, height: usize, inverse: bool) { 353 | let width_fft = make_butterfly(width, inverse); 354 | let height_fft = make_butterfly(height, inverse); 355 | 356 | let fft = GoodThomasAlgorithmDoubleButterfly::new(width_fft, height_fft); 357 | 358 | check_fft_algorithm(&fft, width * height, inverse); 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /src/algorithm/mixed_radix.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use num_complex::Complex; 4 | use transpose; 5 | 6 | use common::{FFTnum, verify_length, verify_length_divisible}; 7 | 8 | use ::{Length, IsInverse, FFT}; 9 | use algorithm::butterflies::FFTButterfly; 10 | use array_utils; 11 | use twiddles; 12 | 13 | /// Implementation of the Mixed-Radix FFT algorithm 14 | /// 15 | /// This algorithm factors a size n FFT into n1 * n2, computes several inner FFTs of size n1 and n2, then combines the 16 | /// results to get the final answer 17 | /// 18 | /// ~~~ 19 | /// // Computes a forward FFT of size 1200, using the Mixed-Radix Algorithm 20 | /// use rustfft::algorithm::MixedRadix; 21 | /// use rustfft::{FFT, FFTplanner}; 22 | /// use rustfft::num_complex::Complex; 23 | /// use rustfft::num_traits::Zero; 24 | /// 25 | /// let mut input: Vec> = vec![Zero::zero(); 1200]; 26 | /// let mut output: Vec> = vec![Zero::zero(); 1200]; 27 | /// 28 | /// // we need to find an n1 and n2 such that n1 * n2 == 1200 29 | /// // n1 = 30 and n2 = 40 satisfies this 30 | /// let mut planner = FFTplanner::new(false); 31 | /// let inner_fft_n1 = planner.plan_fft(30); 32 | /// let inner_fft_n2 = planner.plan_fft(40); 33 | /// 34 | /// // the mixed radix FFT length will be inner_fft_n1.len() * inner_fft_n2.len() = 1200 35 | /// let fft = MixedRadix::new(inner_fft_n1, inner_fft_n2); 36 | /// fft.process(&mut input, &mut output); 37 | /// ~~~ 38 | 39 | pub struct MixedRadix { 40 | width: usize, 41 | width_size_fft: Arc>, 42 | 43 | height: usize, 44 | height_size_fft: Arc>, 45 | 46 | twiddles: Box<[Complex]>, 47 | inverse: bool, 48 | } 49 | 50 | impl MixedRadix { 51 | /// Creates a FFT instance which will process inputs/outputs of size `width_fft.len() * height_fft.len()` 52 | pub fn new(width_fft: Arc>, height_fft: Arc>) -> Self { 53 | assert_eq!( 54 | width_fft.is_inverse(), height_fft.is_inverse(), 55 | "width_fft and height_fft must both be inverse, or neither. got width inverse={}, height inverse={}", 56 | width_fft.is_inverse(), height_fft.is_inverse()); 57 | 58 | let inverse = width_fft.is_inverse(); 59 | 60 | let width = width_fft.len(); 61 | let height = height_fft.len(); 62 | 63 | let len = width * height; 64 | 65 | let mut twiddles = Vec::with_capacity(len); 66 | for x in 0..width { 67 | for y in 0..height { 68 | twiddles.push(twiddles::single_twiddle(x * y, len, inverse)); 69 | } 70 | } 71 | 72 | MixedRadix { 73 | width: width, 74 | width_size_fft: width_fft, 75 | 76 | height: height, 77 | height_size_fft: height_fft, 78 | 79 | twiddles: twiddles.into_boxed_slice(), 80 | inverse: inverse, 81 | } 82 | } 83 | 84 | 85 | fn perform_fft(&self, input: &mut [Complex], output: &mut [Complex]) { 86 | // SIX STEP FFT: 87 | 88 | // STEP 1: transpose 89 | transpose::transpose(input, output, self.width, self.height); 90 | 91 | // STEP 2: perform FFTs of size `height` 92 | self.height_size_fft.process_multi(output, input); 93 | 94 | // STEP 3: Apply twiddle factors 95 | for (element, &twiddle) in input.iter_mut().zip(self.twiddles.iter()) { 96 | *element = *element * twiddle; 97 | } 98 | 99 | // STEP 4: transpose again 100 | transpose::transpose(input, output, self.height, self.width); 101 | 102 | // STEP 5: perform FFTs of size `width` 103 | self.width_size_fft.process_multi(output, input); 104 | 105 | // STEP 6: transpose again 106 | transpose::transpose(input, output, self.width, self.height); 107 | } 108 | } 109 | impl FFT for MixedRadix { 110 | fn process(&self, input: &mut [Complex], output: &mut [Complex]) { 111 | verify_length(input, output, self.len()); 112 | 113 | self.perform_fft(input, output); 114 | } 115 | fn process_multi(&self, input: &mut [Complex], output: &mut [Complex]) { 116 | verify_length_divisible(input, output, self.len()); 117 | 118 | for (in_chunk, out_chunk) in input.chunks_mut(self.len()).zip(output.chunks_mut(self.len())) { 119 | self.perform_fft(in_chunk, out_chunk); 120 | } 121 | } 122 | } 123 | impl Length for MixedRadix { 124 | #[inline(always)] 125 | fn len(&self) -> usize { 126 | self.twiddles.len() 127 | } 128 | } 129 | impl IsInverse for MixedRadix { 130 | #[inline(always)] 131 | fn is_inverse(&self) -> bool { 132 | self.inverse 133 | } 134 | } 135 | 136 | 137 | 138 | 139 | 140 | /// Implementation of the Mixed-Radix FFT algorithm, specialized for the case where both inner FFTs are butterflies 141 | /// 142 | /// This algorithm factors a size n FFT into n1 * n2 143 | /// 144 | /// ~~~ 145 | /// // Computes a forward FFT of size 56, using the Mixed-Radix Butterfly Algorithm 146 | /// use std::sync::Arc; 147 | /// use rustfft::algorithm::MixedRadixDoubleButterfly; 148 | /// use rustfft::algorithm::butterflies::{Butterfly7, Butterfly8}; 149 | /// use rustfft::FFT; 150 | /// use rustfft::num_complex::Complex; 151 | /// use rustfft::num_traits::Zero; 152 | /// 153 | /// let mut input: Vec> = vec![Zero::zero(); 56]; 154 | /// let mut output: Vec> = vec![Zero::zero(); 56]; 155 | /// 156 | /// // we need to find an n1 and n2 such that n1 * n2 == 56 157 | /// // n1 = 7 and n2 = 8 satisfies this 158 | /// let inner_fft_n1 = Arc::new(Butterfly7::new(false)); 159 | /// let inner_fft_n2 = Arc::new(Butterfly8::new(false)); 160 | /// 161 | /// // the mixed radix FFT length will be inner_fft_n1.len() * inner_fft_n2.len() = 56 162 | /// let fft = MixedRadixDoubleButterfly::new(inner_fft_n1, inner_fft_n2); 163 | /// fft.process(&mut input, &mut output); 164 | /// ~~~ 165 | pub struct MixedRadixDoubleButterfly { 166 | width: usize, 167 | width_size_fft: Arc>, 168 | 169 | height: usize, 170 | height_size_fft: Arc>, 171 | 172 | twiddles: Box<[Complex]>, 173 | inverse: bool, 174 | } 175 | 176 | impl MixedRadixDoubleButterfly { 177 | /// Creates a FFT instance which will process inputs/outputs of size `width_fft.len() * height_fft.len()` 178 | pub fn new(width_fft: Arc>, height_fft: Arc>) -> Self { 179 | assert_eq!( 180 | width_fft.is_inverse(), height_fft.is_inverse(), 181 | "width_fft and height_fft must both be inverse, or neither. got width inverse={}, height inverse={}", 182 | width_fft.is_inverse(), height_fft.is_inverse()); 183 | 184 | let inverse = width_fft.is_inverse(); 185 | 186 | let width = width_fft.len(); 187 | let height = height_fft.len(); 188 | 189 | let len = width * height; 190 | 191 | let mut twiddles = Vec::with_capacity(len); 192 | for x in 0..width { 193 | for y in 0..height { 194 | twiddles.push(twiddles::single_twiddle(x * y, len, inverse)); 195 | } 196 | } 197 | 198 | MixedRadixDoubleButterfly { 199 | width: width, 200 | width_size_fft: width_fft, 201 | 202 | height: height, 203 | height_size_fft: height_fft, 204 | 205 | twiddles: twiddles.into_boxed_slice(), 206 | inverse: inverse 207 | } 208 | } 209 | 210 | 211 | unsafe fn perform_fft(&self, input: &mut [Complex], output: &mut [Complex]) { 212 | // SIX STEP FFT: 213 | 214 | // STEP 1: transpose 215 | array_utils::transpose_small(self.width, self.height, input, output); 216 | 217 | // STEP 2: perform FFTs of size 'height' 218 | self.height_size_fft.process_multi_inplace(output); 219 | 220 | // STEP 3: Apply twiddle factors 221 | for (element, &twiddle) in output.iter_mut().zip(self.twiddles.iter()) { 222 | *element = *element * twiddle; 223 | } 224 | 225 | // STEP 4: transpose again 226 | array_utils::transpose_small(self.height, self.width, output, input); 227 | 228 | // STEP 5: perform FFTs of size 'width' 229 | self.width_size_fft.process_multi_inplace(input); 230 | 231 | // STEP 6: transpose again 232 | array_utils::transpose_small(self.width, self.height, input, output); 233 | } 234 | } 235 | 236 | impl FFT for MixedRadixDoubleButterfly { 237 | fn process(&self, input: &mut [Complex], output: &mut [Complex]) { 238 | verify_length(input, output, self.len()); 239 | 240 | unsafe { self.perform_fft(input, output) }; 241 | } 242 | fn process_multi(&self, input: &mut [Complex], output: &mut [Complex]) { 243 | verify_length_divisible(input, output, self.len()); 244 | 245 | for (in_chunk, out_chunk) in input.chunks_mut(self.len()).zip(output.chunks_mut(self.len())) { 246 | unsafe { self.perform_fft(in_chunk, out_chunk) }; 247 | } 248 | } 249 | } 250 | impl Length for MixedRadixDoubleButterfly { 251 | #[inline(always)] 252 | fn len(&self) -> usize { 253 | self.twiddles.len() 254 | } 255 | } 256 | impl IsInverse for MixedRadixDoubleButterfly { 257 | #[inline(always)] 258 | fn is_inverse(&self) -> bool { 259 | self.inverse 260 | } 261 | } 262 | 263 | 264 | 265 | 266 | #[cfg(test)] 267 | mod unit_tests { 268 | use super::*; 269 | use std::sync::Arc; 270 | use test_utils::{check_fft_algorithm, make_butterfly}; 271 | use algorithm::DFT; 272 | 273 | #[test] 274 | fn test_mixed_radix() { 275 | for width in 1..7 { 276 | for height in 1..7 { 277 | test_mixed_radix_with_lengths(width, height, false); 278 | test_mixed_radix_with_lengths(width, height, true); 279 | } 280 | } 281 | } 282 | 283 | #[test] 284 | fn test_mixed_radix_double_butterfly() { 285 | for width in 2..7 { 286 | for height in 2..7 { 287 | test_mixed_radix_butterfly_with_lengths(width, height, false); 288 | test_mixed_radix_butterfly_with_lengths(width, height, true); 289 | } 290 | } 291 | } 292 | 293 | 294 | 295 | 296 | fn test_mixed_radix_with_lengths(width: usize, height: usize, inverse: bool) { 297 | let width_fft = Arc::new(DFT::new(width, inverse)) as Arc>; 298 | let height_fft = Arc::new(DFT::new(height, inverse)) as Arc>; 299 | 300 | let fft = MixedRadix::new(width_fft, height_fft); 301 | 302 | check_fft_algorithm(&fft, width * height, inverse); 303 | } 304 | 305 | fn test_mixed_radix_butterfly_with_lengths(width: usize, height: usize, inverse: bool) { 306 | let width_fft = make_butterfly(width, inverse); 307 | let height_fft = make_butterfly(height, inverse); 308 | 309 | let fft = MixedRadixDoubleButterfly::new(width_fft, height_fft); 310 | 311 | check_fft_algorithm(&fft, width * height, inverse); 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /src/algorithm/mod.rs: -------------------------------------------------------------------------------- 1 | mod good_thomas_algorithm; 2 | mod mixed_radix; 3 | mod raders_algorithm; 4 | mod radix4; 5 | mod dft; 6 | 7 | /// Hardcoded size-specfic FFT algorithms 8 | pub mod butterflies; 9 | 10 | pub use self::mixed_radix::{MixedRadix, MixedRadixDoubleButterfly}; 11 | pub use self::raders_algorithm::RadersAlgorithm; 12 | pub use self::radix4::Radix4; 13 | pub use self::good_thomas_algorithm::{GoodThomasAlgorithm, GoodThomasAlgorithmDoubleButterfly}; 14 | pub use self::dft::DFT; 15 | -------------------------------------------------------------------------------- /src/algorithm/raders_algorithm.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use num_complex::Complex; 4 | use num_traits::Zero; 5 | use strength_reduce::StrengthReducedUsize; 6 | 7 | use common::{FFTnum, verify_length, verify_length_divisible}; 8 | 9 | use math_utils; 10 | use twiddles; 11 | use ::{Length, IsInverse, FFT}; 12 | 13 | /// Implementation of Rader's Algorithm 14 | /// 15 | /// This algorithm computes a prime-sized FFT in O(nlogn) time. It does this by converting this size n FFT into a 16 | /// size (n - 1) FFT, which is guaranteed to be composite. 17 | /// 18 | /// The worst case for this algorithm is when (n - 1) is 2 * prime, resulting in a 19 | /// [Cunningham Chain](https://en.wikipedia.org/wiki/Cunningham_chain) 20 | /// 21 | /// ~~~ 22 | /// // Computes a forward FFT of size 1201 (prime number), using Rader's Algorithm 23 | /// use rustfft::algorithm::RadersAlgorithm; 24 | /// use rustfft::{FFT, FFTplanner}; 25 | /// use rustfft::num_complex::Complex; 26 | /// use rustfft::num_traits::Zero; 27 | /// 28 | /// let mut input: Vec> = vec![Zero::zero(); 1201]; 29 | /// let mut output: Vec> = vec![Zero::zero(); 1201]; 30 | /// 31 | /// // plan a FFT of size n - 1 = 1200 32 | /// let mut planner = FFTplanner::new(false); 33 | /// let inner_fft = planner.plan_fft(1200); 34 | /// 35 | /// let fft = RadersAlgorithm::new(1201, inner_fft); 36 | /// fft.process(&mut input, &mut output); 37 | /// ~~~ 38 | /// 39 | /// Rader's Algorithm is relatively expensive compared to other FFT algorithms. Benchmarking shows that it is up to 40 | /// an order of magnitude slower than similar composite sizes. In the example size above of 1201, benchmarking shows 41 | /// that it takes 2.5x more time to compute than a FFT of size 1200. 42 | 43 | pub struct RadersAlgorithm { 44 | inner_fft: Arc>, 45 | inner_fft_data: Box<[Complex]>, 46 | 47 | primitive_root: usize, 48 | primitive_root_inverse: usize, 49 | 50 | len: StrengthReducedUsize, 51 | } 52 | 53 | impl RadersAlgorithm { 54 | /// Creates a FFT instance which will process inputs/outputs of size `len`. `inner_fft.len()` must be `len - 1` 55 | /// 56 | /// Note that this constructor is quite expensive to run; This algorithm must run a FFT of size n - 1 within the 57 | /// constructor. This further underlines the fact that Rader's Algorithm is more expensive to run than other 58 | /// FFT algorithms 59 | /// 60 | /// Note also that if `len` is not prime, this algorithm may silently produce garbage output 61 | pub fn new(len: usize, inner_fft: Arc>) -> Self { 62 | assert_eq!(len - 1, inner_fft.len(), "For raders algorithm, inner_fft.len() must be self.len() - 1. Expected {}, got {}", len - 1, inner_fft.len()); 63 | 64 | let inner_fft_len = len - 1; 65 | let reduced_len = StrengthReducedUsize::new(len); 66 | 67 | // compute the primitive root and its inverse for this size 68 | let primitive_root = math_utils::primitive_root(len as u64).unwrap() as usize; 69 | let primitive_root_inverse = math_utils::multiplicative_inverse(primitive_root as usize, len); 70 | 71 | // precompute the coefficients to use inside the process method 72 | let unity_scale = T::from_f64(1f64 / inner_fft_len as f64).unwrap(); 73 | let mut inner_fft_input = vec![Complex::zero(); inner_fft_len]; 74 | let mut twiddle_input = 1; 75 | for input_cell in &mut inner_fft_input { 76 | let twiddle = twiddles::single_twiddle(twiddle_input, len, inner_fft.is_inverse()); 77 | *input_cell = twiddle * unity_scale; 78 | 79 | twiddle_input = (twiddle_input * primitive_root_inverse) % reduced_len; 80 | } 81 | 82 | //precompute a FFT of our reordered twiddle factors 83 | let mut inner_fft_output = vec![Zero::zero(); inner_fft_len]; 84 | inner_fft.process(&mut inner_fft_input, &mut inner_fft_output); 85 | 86 | Self { 87 | inner_fft: inner_fft, 88 | inner_fft_data: inner_fft_output.into_boxed_slice(), 89 | 90 | primitive_root, 91 | primitive_root_inverse, 92 | 93 | len: reduced_len, 94 | } 95 | } 96 | 97 | fn perform_fft(&self, input: &mut [Complex], output: &mut [Complex]) { 98 | 99 | // The first output element is just the sum of all the input elements 100 | output[0] = input.iter().sum(); 101 | let first_input_val = input[0]; 102 | 103 | // we're now done with the first input and output 104 | let (_, output) = output.split_first_mut().unwrap(); 105 | let (_, input) = input.split_first_mut().unwrap(); 106 | 107 | // copy the input into the output, reordering as we go 108 | let mut input_index = 1; 109 | for output_element in output.iter_mut() { 110 | input_index = (input_index * self.primitive_root) % self.len; 111 | *output_element = input[input_index - 1]; 112 | } 113 | 114 | // perform the first of two inner FFTs 115 | self.inner_fft.process(output, input); 116 | 117 | // multiply the inner result with our cached setup data 118 | // also conjugate every entry. this sets us up to do an inverse FFT 119 | // (because an inverse FFT is equivalent to a normal FFT where you conjugate both the inputs and outputs) 120 | for ((&input_cell, output_cell), &multiple) in input.iter().zip(output.iter_mut()).zip(self.inner_fft_data.iter()) { 121 | *output_cell = (input_cell * multiple).conj(); 122 | } 123 | 124 | // execute the second FFT 125 | self.inner_fft.process(output, input); 126 | 127 | // copy the final values into the output, reordering as we go 128 | let mut output_index = 1; 129 | for input_element in input { 130 | output_index = (output_index * self.primitive_root_inverse) % self.len; 131 | output[output_index - 1] = input_element.conj() + first_input_val; 132 | } 133 | } 134 | } 135 | 136 | impl FFT for RadersAlgorithm { 137 | fn process(&self, input: &mut [Complex], output: &mut [Complex]) { 138 | verify_length(input, output, self.len()); 139 | 140 | self.perform_fft(input, output); 141 | } 142 | fn process_multi(&self, input: &mut [Complex], output: &mut [Complex]) { 143 | verify_length_divisible(input, output, self.len()); 144 | 145 | for (in_chunk, out_chunk) in input.chunks_mut(self.len()).zip(output.chunks_mut(self.len())) { 146 | self.perform_fft(in_chunk, out_chunk); 147 | } 148 | } 149 | } 150 | impl Length for RadersAlgorithm { 151 | #[inline(always)] 152 | fn len(&self) -> usize { 153 | self.len.get() 154 | } 155 | } 156 | impl IsInverse for RadersAlgorithm { 157 | #[inline(always)] 158 | fn is_inverse(&self) -> bool { 159 | self.inner_fft.is_inverse() 160 | } 161 | } 162 | 163 | #[cfg(test)] 164 | mod unit_tests { 165 | use super::*; 166 | use std::sync::Arc; 167 | use test_utils::check_fft_algorithm; 168 | use algorithm::DFT; 169 | 170 | #[test] 171 | fn test_raders() { 172 | for &len in &[3,5,7,11,13] { 173 | test_raders_with_length(len, false); 174 | test_raders_with_length(len, true); 175 | } 176 | } 177 | 178 | fn test_raders_with_length(len: usize, inverse: bool) { 179 | let inner_fft = Arc::new(DFT::new(len - 1, inverse)); 180 | let fft = RadersAlgorithm::new(len, inner_fft); 181 | 182 | check_fft_algorithm(&fft, len, inverse); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/algorithm/radix4.rs: -------------------------------------------------------------------------------- 1 | use num_complex::Complex; 2 | use num_traits::Zero; 3 | 4 | use common::{FFTnum, verify_length, verify_length_divisible}; 5 | 6 | use algorithm::butterflies::{Butterfly2, Butterfly4, Butterfly8, Butterfly16, FFTButterfly}; 7 | use ::{Length, IsInverse, FFT}; 8 | use twiddles; 9 | 10 | /// FFT algorithm optimized for power-of-two sizes 11 | /// 12 | /// ~~~ 13 | /// // Computes a forward FFT of size 4096 14 | /// use rustfft::algorithm::Radix4; 15 | /// use rustfft::FFT; 16 | /// use rustfft::num_complex::Complex; 17 | /// use rustfft::num_traits::Zero; 18 | /// 19 | /// let mut input: Vec> = vec![Zero::zero(); 4096]; 20 | /// let mut output: Vec> = vec![Zero::zero(); 4096]; 21 | /// 22 | /// let fft = Radix4::new(4096, false); 23 | /// fft.process(&mut input, &mut output); 24 | /// ~~~ 25 | 26 | pub struct Radix4 { 27 | twiddles: Box<[Complex]>, 28 | butterfly8: Butterfly8, 29 | butterfly16: Butterfly16, 30 | len: usize, 31 | inverse: bool, 32 | } 33 | 34 | impl Radix4 { 35 | /// Preallocates necessary arrays and precomputes necessary data to efficiently compute the power-of-two FFT 36 | pub fn new(len: usize, inverse: bool) -> Self { 37 | assert!(len.is_power_of_two(), "Radix4 algorithm requires a power-of-two input size. Got {}", len); 38 | 39 | // precompute the twiddle factors this algorithm will use. 40 | // we're doing the same precomputation of twiddle factors as the mixed radix algorithm where width=4 and height=len/4 41 | // but mixed radix only does one step and then calls itself recusrively, and this algorithm does every layer all the way down 42 | // so we're going to pack all the "layers" of twiddle factors into a single array, starting with the bottom and going up 43 | let num_bits = len.trailing_zeros(); 44 | let mut twiddle_stride = if num_bits%2 == 0 { 45 | len / 64 46 | } else { 47 | len / 32 48 | }; 49 | 50 | let mut twiddle_factors = Vec::with_capacity(len * 2); 51 | while twiddle_stride > 0 { 52 | let num_rows = len / (twiddle_stride * 4); 53 | for i in 0..num_rows { 54 | for k in 1..4 { 55 | let twiddle = twiddles::single_twiddle(i * k * twiddle_stride, len, inverse); 56 | twiddle_factors.push(twiddle); 57 | } 58 | } 59 | twiddle_stride >>= 2; 60 | } 61 | 62 | Radix4 { 63 | twiddles: twiddle_factors.into_boxed_slice(), 64 | butterfly8: Butterfly8::new(inverse), 65 | butterfly16: Butterfly16::new(inverse), 66 | len: len, 67 | inverse: inverse, 68 | } 69 | } 70 | 71 | fn perform_fft(&self, signal: &[Complex], spectrum: &mut [Complex]) { 72 | match self.len() { 73 | 0|1 => spectrum.copy_from_slice(signal), 74 | 2 => { 75 | spectrum.copy_from_slice(signal); 76 | unsafe { Butterfly2::new(self.inverse).process_inplace(spectrum) } 77 | }, 78 | 4 => { 79 | spectrum.copy_from_slice(signal); 80 | unsafe { Butterfly4::new(self.inverse).process_inplace(spectrum) } 81 | }, 82 | _ => { 83 | // copy the data into the spectrum vector 84 | prepare_radix4(signal.len(), signal, spectrum, 1); 85 | 86 | // perform the butterflies. the butterfly size depends on the input size 87 | let num_bits = signal.len().trailing_zeros(); 88 | let mut current_size = if num_bits % 2 == 0 { 89 | unsafe { self.butterfly16.process_multi_inplace(spectrum) }; 90 | 91 | // for the cross-ffts we want to to start off with a size of 64 (16 * 4) 92 | 64 93 | } else { 94 | unsafe { self.butterfly8.process_multi_inplace(spectrum) }; 95 | 96 | // for the cross-ffts we want to to start off with a size of 32 (8 * 4) 97 | 32 98 | }; 99 | 100 | let mut layer_twiddles: &[Complex] = &self.twiddles; 101 | 102 | // now, perform all the cross-FFTs, one "layer" at a time 103 | while current_size <= signal.len() { 104 | let num_rows = signal.len() / current_size; 105 | 106 | for i in 0..num_rows { 107 | unsafe { 108 | butterfly_4(&mut spectrum[i * current_size..], 109 | layer_twiddles, 110 | current_size / 4, 111 | self.inverse) 112 | } 113 | } 114 | 115 | //skip past all the twiddle factors used in this layer 116 | let twiddle_offset = (current_size * 3) / 4; 117 | layer_twiddles = &layer_twiddles[twiddle_offset..]; 118 | 119 | current_size *= 4; 120 | } 121 | } 122 | } 123 | } 124 | } 125 | 126 | impl FFT for Radix4 { 127 | fn process(&self, input: &mut [Complex], output: &mut [Complex]) { 128 | verify_length(input, output, self.len()); 129 | 130 | self.perform_fft(input, output); 131 | } 132 | fn process_multi(&self, input: &mut [Complex], output: &mut [Complex]) { 133 | verify_length_divisible(input, output, self.len()); 134 | 135 | for (in_chunk, out_chunk) in input.chunks_mut(self.len()).zip(output.chunks_mut(self.len())) { 136 | self.perform_fft(in_chunk, out_chunk); 137 | } 138 | } 139 | } 140 | impl Length for Radix4 { 141 | #[inline(always)] 142 | fn len(&self) -> usize { 143 | self.len 144 | } 145 | } 146 | impl IsInverse for Radix4 { 147 | #[inline(always)] 148 | fn is_inverse(&self) -> bool { 149 | self.inverse 150 | } 151 | } 152 | 153 | 154 | 155 | // after testing an iterative bit reversal algorithm, this recursive algorithm 156 | // was almost an order of magnitude faster at setting up 157 | fn prepare_radix4(size: usize, 158 | signal: &[Complex], 159 | spectrum: &mut [Complex], 160 | stride: usize) { 161 | match size { 162 | 16 => unsafe { 163 | for i in 0..16 { 164 | *spectrum.get_unchecked_mut(i) = *signal.get_unchecked(i * stride); 165 | } 166 | }, 167 | 8 => unsafe { 168 | for i in 0..8 { 169 | *spectrum.get_unchecked_mut(i) = *signal.get_unchecked(i * stride); 170 | } 171 | }, 172 | 4 => unsafe { 173 | for i in 0..4 { 174 | *spectrum.get_unchecked_mut(i) = *signal.get_unchecked(i * stride); 175 | } 176 | }, 177 | 2 => unsafe { 178 | for i in 0..2 { 179 | *spectrum.get_unchecked_mut(i) = *signal.get_unchecked(i * stride); 180 | } 181 | }, 182 | _ => { 183 | for i in 0..4 { 184 | prepare_radix4(size / 4, 185 | &signal[i * stride..], 186 | &mut spectrum[i * (size / 4)..], 187 | stride * 4); 188 | } 189 | } 190 | } 191 | } 192 | 193 | unsafe fn butterfly_4(data: &mut [Complex], 194 | twiddles: &[Complex], 195 | num_ffts: usize, 196 | inverse: bool) 197 | { 198 | let mut idx = 0usize; 199 | let mut tw_idx = 0usize; 200 | let mut scratch: [Complex; 6] = [Zero::zero(); 6]; 201 | for _ in 0..num_ffts { 202 | scratch[0] = data.get_unchecked(idx + 1 * num_ffts) * twiddles[tw_idx]; 203 | scratch[1] = data.get_unchecked(idx + 2 * num_ffts) * twiddles[tw_idx + 1]; 204 | scratch[2] = data.get_unchecked(idx + 3 * num_ffts) * twiddles[tw_idx + 2]; 205 | scratch[5] = data.get_unchecked(idx) - scratch[1]; 206 | *data.get_unchecked_mut(idx) = data.get_unchecked(idx) + scratch[1]; 207 | scratch[3] = scratch[0] + scratch[2]; 208 | scratch[4] = scratch[0] - scratch[2]; 209 | *data.get_unchecked_mut(idx + 2 * num_ffts) = data.get_unchecked(idx) - scratch[3]; 210 | *data.get_unchecked_mut(idx) = data.get_unchecked(idx) + scratch[3]; 211 | if inverse { 212 | data.get_unchecked_mut(idx + num_ffts).re = scratch[5].re - scratch[4].im; 213 | data.get_unchecked_mut(idx + num_ffts).im = scratch[5].im + scratch[4].re; 214 | data.get_unchecked_mut(idx + 3 * num_ffts).re = scratch[5].re + scratch[4].im; 215 | data.get_unchecked_mut(idx + 3 * num_ffts).im = scratch[5].im - scratch[4].re; 216 | } else { 217 | data.get_unchecked_mut(idx + num_ffts).re = scratch[5].re + scratch[4].im; 218 | data.get_unchecked_mut(idx + num_ffts).im = scratch[5].im - scratch[4].re; 219 | data.get_unchecked_mut(idx + 3 * num_ffts).re = scratch[5].re - scratch[4].im; 220 | data.get_unchecked_mut(idx + 3 * num_ffts).im = scratch[5].im + scratch[4].re; 221 | } 222 | 223 | tw_idx += 3; 224 | idx += 1; 225 | } 226 | } 227 | 228 | #[cfg(test)] 229 | mod unit_tests { 230 | use super::*; 231 | use test_utils::check_fft_algorithm; 232 | 233 | #[test] 234 | fn test_radix4() { 235 | for pow in 0..8 { 236 | let len = 1 << pow; 237 | test_radix4_with_length(len, false); 238 | test_radix4_with_length(len, true); 239 | } 240 | } 241 | 242 | fn test_radix4_with_length(len: usize, inverse: bool) { 243 | let fft = Radix4::new(len, inverse); 244 | 245 | check_fft_algorithm(&fft, len, inverse); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/array_utils.rs: -------------------------------------------------------------------------------- 1 | 2 | /// Given an array of size width * height, representing a flattened 2D array, 3 | /// transpose the rows and columns of that 2D array into the output 4 | /// benchmarking shows that loop tiling isn't effective for small arrays (in the range of 50x50 or smaller) 5 | pub unsafe fn transpose_small(width: usize, height: usize, input: &[T], output: &mut [T]) { 6 | for x in 0..width { 7 | for y in 0..height { 8 | let input_index = x + y * width; 9 | let output_index = y + x * height; 10 | 11 | *output.get_unchecked_mut(output_index) = *input.get_unchecked(input_index); 12 | } 13 | } 14 | } 15 | 16 | 17 | 18 | #[cfg(test)] 19 | mod unit_tests { 20 | use super::*; 21 | use test_utils::random_signal; 22 | use num_complex::Complex; 23 | use num_traits::Zero; 24 | 25 | #[test] 26 | fn test_transpose() { 27 | let sizes: Vec = (1..16).collect(); 28 | 29 | for &width in &sizes { 30 | for &height in &sizes { 31 | let len = width * height; 32 | 33 | let input: Vec> = random_signal(len); 34 | let mut output = vec![Zero::zero(); len]; 35 | 36 | unsafe { transpose_small(width, height, &input, &mut output) }; 37 | 38 | for x in 0..width { 39 | for y in 0..height { 40 | assert_eq!(input[x + y * width], output[y + x * height], "x = {}, y = {}", x, y); 41 | } 42 | } 43 | 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | use num_traits::{FromPrimitive, Signed}; 2 | 3 | /// Generic floating point number, implemented for f32 and f64 4 | pub trait FFTnum: Copy + FromPrimitive + Signed + Sync + Send + 'static {} 5 | 6 | impl FFTnum for f32 {} 7 | impl FFTnum for f64 {} 8 | 9 | 10 | #[inline(always)] 11 | pub fn verify_length(input: &[T], output: &[T], expected: usize) { 12 | assert_eq!(input.len(), expected, "Input is the wrong length. Expected {}, got {}", expected, input.len()); 13 | assert_eq!(output.len(), expected, "Output is the wrong length. Expected {}, got {}", expected, output.len()); 14 | } 15 | 16 | 17 | #[inline(always)] 18 | pub fn verify_length_divisible(input: &[T], output: &[T], expected: usize) { 19 | assert_eq!(input.len() % expected, 0, "Input is the wrong length. Expected multiple of {}, got {}", expected, input.len()); 20 | assert_eq!(input.len(), output.len(), "Input and output must have the same length. Expected {}, got {}", input.len(), output.len()); 21 | } 22 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(all(feature = "bench", test), feature(test))] 2 | 3 | //! RustFFT allows users to compute arbitrary-sized FFTs in O(nlogn) time. 4 | //! 5 | //! The recommended way to use RustFFT is to create a [`FFTplanner`](struct.FFTplanner.html) instance and then call its 6 | //! `plan_fft` method. This method will automatically choose which FFT algorithms are best 7 | //! for a given size and initialize the required buffers and precomputed data. 8 | //! 9 | //! ``` 10 | //! // Perform a forward FFT of size 1234 11 | //! use std::sync::Arc; 12 | //! use rustfft::FFTplanner; 13 | //! use rustfft::num_complex::Complex; 14 | //! use rustfft::num_traits::Zero; 15 | //! 16 | //! let mut input: Vec> = vec![Complex::zero(); 1234]; 17 | //! let mut output: Vec> = vec![Complex::zero(); 1234]; 18 | //! 19 | //! let mut planner = FFTplanner::new(false); 20 | //! let fft = planner.plan_fft(1234); 21 | //! fft.process(&mut input, &mut output); 22 | //! 23 | //! // The fft instance returned by the planner is stored behind an `Arc`, so it's cheap to clone 24 | //! let fft_clone = Arc::clone(&fft); 25 | //! ``` 26 | //! The planner returns trait objects of the [`FFT`](trait.FFT.html) trait, allowing for FFT sizes that aren't known 27 | //! until runtime. 28 | //! 29 | //! RustFFT also exposes individual FFT algorithms. If you know beforehand that you need a power-of-two FFT, you can 30 | //! avoid the overhead of the planner and trait object by directly creating instances of the Radix4 algorithm: 31 | //! 32 | //! ``` 33 | //! // Computes a forward FFT of size 4096 34 | //! use rustfft::algorithm::Radix4; 35 | //! use rustfft::FFT; 36 | //! use rustfft::num_complex::Complex; 37 | //! use rustfft::num_traits::Zero; 38 | //! 39 | //! let mut input: Vec> = vec![Complex::zero(); 4096]; 40 | //! let mut output: Vec> = vec![Complex::zero(); 4096]; 41 | //! 42 | //! let fft = Radix4::new(4096, false); 43 | //! fft.process(&mut input, &mut output); 44 | //! ``` 45 | //! 46 | //! For the vast majority of situations, simply using the [`FFTplanner`](struct.FFTplanner.html) will be enough, but 47 | //! advanced users may have better insight than the planner into which algorithms are best for a specific size. See the 48 | //! [`algorithm`](algorithm/index.html) module for a complete list of algorithms implemented by RustFFT. 49 | //! 50 | //! ### Normalization 51 | //! 52 | //! RustFFT does not normalize outputs. Callers must manually normalize the results by scaling each element by 53 | //! `1/len().sqrt()`. Multiple normalization steps can be merged into one via pairwise multiplication, so when 54 | //! doing a forward FFT followed by an inverse FFT, callers can normalize once by scaling each element by `1/len()` 55 | //! 56 | //! ### Output Order 57 | //! 58 | //! Elements in the output are ordered by ascending frequency, with the first element corresponding to frequency 0. 59 | 60 | #![allow(unknown_lints)] // The "bare trait objects" lint is unknown on rustc 1.26 61 | #![allow(bare_trait_objects)] 62 | 63 | pub extern crate num_complex; 64 | pub extern crate num_traits; 65 | extern crate num_integer; 66 | extern crate strength_reduce; 67 | extern crate transpose; 68 | 69 | 70 | 71 | /// Individual FFT algorithms 72 | pub mod algorithm; 73 | mod math_utils; 74 | mod array_utils; 75 | mod plan; 76 | mod twiddles; 77 | mod common; 78 | 79 | use num_complex::Complex; 80 | 81 | pub use plan::FFTplanner; 82 | pub use common::FFTnum; 83 | 84 | 85 | 86 | /// A trait that allows FFT algorithms to report their expected input/output size 87 | pub trait Length { 88 | /// The FFT size that this algorithm can process 89 | fn len(&self) -> usize; 90 | } 91 | 92 | /// A trait that allows FFT algorithms to report whether they compute forward FFTs or inverse FFTs 93 | pub trait IsInverse { 94 | /// Returns false if this instance computes forward FFTs, true for inverse FFTs 95 | fn is_inverse(&self) -> bool; 96 | } 97 | 98 | /// An umbrella trait for all available FFT algorithms 99 | pub trait FFT: Length + IsInverse + Sync + Send { 100 | /// Computes an FFT on the `input` buffer and places the result in the `output` buffer. 101 | /// 102 | /// The output is not normalized. Callers must manually normalize the results by scaling each element by 103 | /// `1/len().sqrt()`. Multiple normalization steps can be merged into one via pairwise multiplication, so when 104 | /// doing a forward FFT followed by an inverse FFT, callers can normalize once by scaling each element by `1/len()` 105 | /// 106 | /// This method uses the `input` buffer as scratch space, so the contents of `input` should be considered garbage 107 | /// after calling 108 | fn process(&self, input: &mut [Complex], output: &mut [Complex]); 109 | 110 | /// Divides the `input` and `output` buffers into chunks of length self.len(), then computes an FFT on each chunk. 111 | /// 112 | /// The output is not normalized. Callers must manually normalize the results by scaling each element by 113 | /// `1/len().sqrt()`. Multiple normalization steps can be merged into one via pairwise multiplication, so when 114 | /// doing a forward FFT followed by an inverse FFT, callers can normalize once by scaling each element by `1/len()` 115 | /// 116 | /// This method uses the `input` buffer as scratch space, so the contents of `input` should be considered garbage 117 | /// after calling 118 | fn process_multi(&self, input: &mut [Complex], output: &mut [Complex]); 119 | } 120 | 121 | #[cfg(test)] 122 | extern crate rand; 123 | #[cfg(test)] 124 | mod test_utils; 125 | -------------------------------------------------------------------------------- /src/math_utils.rs: -------------------------------------------------------------------------------- 1 | 2 | use num_traits::{Zero, One, FromPrimitive, PrimInt, Signed}; 3 | use std::mem::swap; 4 | 5 | pub fn primitive_root(prime: u64) -> Option { 6 | let test_exponents: Vec = distinct_prime_factors(prime - 1) 7 | .iter() 8 | .map(|factor| (prime - 1) / factor) 9 | .collect(); 10 | 'next: for potential_root in 2..prime { 11 | // for each distinct factor, if potential_root^(p-1)/factor mod p is 1, reject it 12 | for exp in &test_exponents { 13 | if modular_exponent(potential_root, *exp, prime) == 1 { 14 | continue 'next; 15 | } 16 | } 17 | 18 | // if we reach this point, it means this root was not rejected, so return it 19 | return Some(potential_root); 20 | } 21 | None 22 | } 23 | 24 | /// computes base^exponent % modulo using the standard exponentiation by squaring algorithm 25 | pub fn modular_exponent(mut base: T, mut exponent: T, modulo: T) -> T { 26 | let one = T::one(); 27 | 28 | let mut result = one; 29 | 30 | while exponent > Zero::zero() { 31 | if exponent & one == one { 32 | result = result * base % modulo; 33 | } 34 | exponent = exponent >> One::one(); 35 | base = (base * base) % modulo; 36 | } 37 | 38 | result 39 | } 40 | 41 | pub fn multiplicative_inverse(a: T, n: T) -> T { 42 | // we're going to use a modified version extended euclidean algorithm 43 | // we only need half the output 44 | 45 | let mut t = Zero::zero(); 46 | let mut t_new = One::one(); 47 | 48 | let mut r = n; 49 | let mut r_new = a; 50 | 51 | while r_new > Zero::zero() { 52 | let quotient = r / r_new; 53 | 54 | r = r - quotient * r_new; 55 | swap(&mut r, &mut r_new); 56 | 57 | // t might go negative here, so we have to do a checked subtract 58 | // if it underflows, wrap it around to the other end of the modulo 59 | // IE, 3 - 4 mod 5 = -1 mod 5 = 4 60 | let t_subtract = quotient * t_new; 61 | t = if t_subtract < t { 62 | t - t_subtract 63 | } else { 64 | n - (t_subtract - t) % n 65 | }; 66 | swap(&mut t, &mut t_new); 67 | } 68 | 69 | t 70 | } 71 | 72 | pub fn extended_euclidean_algorithm(a: T, 73 | b: T) 74 | -> (T, T, T) { 75 | let mut s = Zero::zero(); 76 | let mut s_old = One::one(); 77 | 78 | let mut t = One::one(); 79 | let mut t_old = Zero::zero(); 80 | 81 | let mut r = b; 82 | let mut r_old = a; 83 | 84 | while r > Zero::zero() { 85 | let quotient = r_old / r; 86 | 87 | r_old = r_old - quotient * r; 88 | swap(&mut r_old, &mut r); 89 | 90 | s_old = s_old - quotient * s; 91 | swap(&mut s_old, &mut s); 92 | 93 | t_old = t_old - quotient * t; 94 | swap(&mut t_old, &mut t); 95 | } 96 | 97 | (r_old, s_old, t_old) 98 | } 99 | 100 | /// return all of the prime factors of n, but omit duplicate prime factors 101 | pub fn distinct_prime_factors(mut n: u64) -> Vec { 102 | let mut result = Vec::new(); 103 | 104 | // handle 2 separately so we dont have to worry about adding 2 vs 1 105 | if n % 2 == 0 { 106 | while n % 2 == 0 { 107 | n /= 2; 108 | } 109 | result.push(2); 110 | } 111 | if n > 1 { 112 | let mut divisor = 3; 113 | let mut limit = (n as f32).sqrt() as u64 + 1; 114 | while divisor < limit { 115 | if n % divisor == 0 { 116 | 117 | // remove as many factors as possible from n 118 | while n % divisor == 0 { 119 | n /= divisor; 120 | } 121 | result.push(divisor); 122 | 123 | // recalculate the limit to reduce the amount of work we need to do 124 | limit = (n as f32).sqrt() as u64 + 1; 125 | } 126 | 127 | divisor += 2; 128 | } 129 | 130 | if n > 1 { 131 | result.push(n); 132 | } 133 | } 134 | 135 | result 136 | } 137 | 138 | /// Factors an integer into its prime factors. 139 | pub fn prime_factors(mut n: usize) -> Vec { 140 | let mut result = Vec::new(); 141 | 142 | while n % 2 == 0 { 143 | n /= 2; 144 | result.push(2); 145 | } 146 | if n > 1 { 147 | let mut divisor = 3; 148 | let mut limit = (n as f32).sqrt() as usize + 1; 149 | while divisor < limit { 150 | while n % divisor == 0 { 151 | n /= divisor; 152 | result.push(divisor); 153 | } 154 | 155 | // recalculate the limit to reduce the amount of other factors we need to check 156 | limit = (n as f32).sqrt() as usize + 1; 157 | divisor += 2; 158 | } 159 | 160 | if n > 1 { 161 | result.push(n); 162 | } 163 | } 164 | 165 | result 166 | } 167 | 168 | #[cfg(test)] 169 | mod unit_tests { 170 | use super::*; 171 | 172 | #[test] 173 | fn test_modular_exponent() { 174 | // make sure to test something that would overflow under ordinary circumstances 175 | // ie 3 ^ 416788 mod 47 176 | let test_list = vec![ 177 | ((2,8,300), 256), 178 | ((2,9,300), 212), 179 | ((1,9,300), 1), 180 | ((3,416788,47), 8), 181 | ]; 182 | 183 | for (input, expected) in test_list { 184 | let (base, exponent, modulo) = input; 185 | 186 | let result = modular_exponent(base, exponent, modulo); 187 | 188 | assert_eq!(result, expected); 189 | } 190 | } 191 | 192 | #[test] 193 | fn test_multiplicative_inverse() { 194 | let prime_list = vec![3, 5, 7, 11, 13, 17, 19, 23, 29]; 195 | 196 | for modulo in prime_list { 197 | for i in 2..modulo { 198 | let inverse = multiplicative_inverse(i, modulo); 199 | 200 | assert_eq!(i * inverse % modulo, 1); 201 | } 202 | } 203 | } 204 | 205 | #[test] 206 | fn test_extended_euclidean() { 207 | let test_list = vec![ 208 | ((3,5), (1, 2, -1)), 209 | ((15,12), (3, 1, -1)), 210 | ((16,21), (1, 4, -3)), 211 | ]; 212 | 213 | for (input, expected) in test_list { 214 | let (a, b) = input; 215 | 216 | let result = extended_euclidean_algorithm(a, b); 217 | assert_eq!(expected, result); 218 | 219 | let (gcd, mut a_inverse, mut b_inverse) = result; 220 | 221 | // sanity check: if gcd=1, then a*a_inverse mod b should equal 1 and vice versa 222 | if gcd == 1 { 223 | if a_inverse < 0 { 224 | a_inverse += b; 225 | } 226 | if b_inverse < 0 { 227 | b_inverse += a; 228 | } 229 | 230 | assert_eq!(1, a * a_inverse % b); 231 | assert_eq!(1, b * b_inverse % a); 232 | } 233 | } 234 | } 235 | 236 | #[test] 237 | fn test_primitive_root() { 238 | let test_list = vec![(3, 2), (7, 3), (11, 2), (13, 2), (47, 5), (7919, 7)]; 239 | 240 | for (input, expected) in test_list { 241 | let root = primitive_root(input).unwrap(); 242 | 243 | assert_eq!(root, expected); 244 | } 245 | } 246 | 247 | #[test] 248 | fn test_prime_factors() { 249 | let test_list = vec![ 250 | (46, vec![2,23]), 251 | (2, vec![2]), 252 | (3, vec![3]), 253 | (162, vec![2, 3]), 254 | ]; 255 | 256 | for (input, expected) in test_list { 257 | let factors = distinct_prime_factors(input); 258 | 259 | assert_eq!(factors, expected); 260 | } 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/plan.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::Arc; 3 | use num_integer::gcd; 4 | 5 | use common::FFTnum; 6 | 7 | use FFT; 8 | use algorithm::*; 9 | use algorithm::butterflies::*; 10 | 11 | use math_utils; 12 | 13 | 14 | const MIN_RADIX4_BITS: u32 = 5; // smallest size to consider radix 4 an option is 2^5 = 32 15 | const MAX_RADIX4_BITS: u32 = 16; // largest size to consider radix 4 an option is 2^16 = 65536 16 | const BUTTERFLIES: [usize; 9] = [2, 3, 4, 5, 6, 7, 8, 16, 32]; 17 | const COMPOSITE_BUTTERFLIES: [usize; 5] = [4, 6, 8, 16, 32]; 18 | 19 | /// The FFT planner is used to make new FFT algorithm instances. 20 | /// 21 | /// RustFFT has several FFT algorithms available; For a given FFT size, the FFTplanner decides which of the 22 | /// available FFT algorithms to use and then initializes them. 23 | /// 24 | /// ~~~ 25 | /// // Perform a forward FFT of size 1234 26 | /// use std::sync::Arc; 27 | /// use rustfft::FFTplanner; 28 | /// use rustfft::num_complex::Complex; 29 | /// use rustfft::num_traits::Zero; 30 | /// 31 | /// let mut input: Vec> = vec![Zero::zero(); 1234]; 32 | /// let mut output: Vec> = vec![Zero::zero(); 1234]; 33 | /// 34 | /// let mut planner = FFTplanner::new(false); 35 | /// let fft = planner.plan_fft(1234); 36 | /// fft.process(&mut input, &mut output); 37 | /// 38 | /// // The fft instance returned by the planner is stored behind an `Arc`, so it's cheap to clone 39 | /// let fft_clone = Arc::clone(&fft); 40 | /// ~~~ 41 | /// 42 | /// If you plan on creating multiple FFT instances, it is recommnded to reuse the same planner for all of them. This 43 | /// is because the planner re-uses internal data across FFT instances wherever possible, saving memory and reducing 44 | /// setup time. (FFT instances created with one planner will never re-use data and buffers with FFT instances created 45 | /// by a different planner) 46 | /// 47 | /// Each FFT instance owns `Arc`s to its internal data, rather than borrowing it from the planner, so it's perfectly 48 | /// safe to drop the planner after creating FFT instances. 49 | pub struct FFTplanner { 50 | inverse: bool, 51 | algorithm_cache: HashMap>>, 52 | butterfly_cache: HashMap>>, 53 | } 54 | 55 | impl FFTplanner { 56 | /// Creates a new FFT planner. 57 | /// 58 | /// If `inverse` is false, this planner will plan forward FFTs. If `inverse` is true, it will plan inverse FFTs. 59 | pub fn new(inverse: bool) -> Self { 60 | FFTplanner { 61 | inverse: inverse, 62 | algorithm_cache: HashMap::new(), 63 | butterfly_cache: HashMap::new(), 64 | } 65 | } 66 | 67 | /// Returns a FFT instance which processes signals of size `len` 68 | /// If this is called multiple times, it will attempt to re-use internal data between instances 69 | pub fn plan_fft(&mut self, len: usize) -> Arc> { 70 | if len < 2 { 71 | Arc::new(DFT::new(len, self.inverse)) as Arc> 72 | } else { 73 | let factors = math_utils::prime_factors(len); 74 | self.plan_fft_with_factors(len, &factors) 75 | } 76 | } 77 | 78 | fn plan_butterfly(&mut self, len: usize) -> Arc> { 79 | let inverse = self.inverse; 80 | let instance = self.butterfly_cache.entry(len).or_insert_with(|| 81 | match len { 82 | 2 => Arc::new(Butterfly2::new(inverse)), 83 | 3 => Arc::new(Butterfly3::new(inverse)), 84 | 4 => Arc::new(Butterfly4::new(inverse)), 85 | 5 => Arc::new(Butterfly5::new(inverse)), 86 | 6 => Arc::new(Butterfly6::new(inverse)), 87 | 7 => Arc::new(Butterfly7::new(inverse)), 88 | 8 => Arc::new(Butterfly8::new(inverse)), 89 | 16 => Arc::new(Butterfly16::new(inverse)), 90 | 32 => Arc::new(Butterfly32::new(inverse)), 91 | _ => panic!("Invalid butterfly size: {}", len), 92 | } 93 | ); 94 | Arc::clone(instance) 95 | } 96 | 97 | fn plan_fft_with_factors(&mut self, len: usize, factors: &[usize]) -> Arc> { 98 | if self.algorithm_cache.contains_key(&len) { 99 | Arc::clone(self.algorithm_cache.get(&len).unwrap()) 100 | } else { 101 | let result = if factors.len() == 1 || COMPOSITE_BUTTERFLIES.contains(&len) { 102 | self.plan_fft_single_factor(len) 103 | 104 | } else if len.trailing_zeros() <= MAX_RADIX4_BITS && len.trailing_zeros() >= MIN_RADIX4_BITS { 105 | //the number of trailing zeroes in len is the number of `2` factors 106 | //ie if len = 2048 * n, len.trailing_zeros() will equal 11 because 2^11 == 2048 107 | 108 | if len.is_power_of_two() { 109 | Arc::new(Radix4::new(len, self.inverse)) 110 | } else { 111 | let left_len = 1 << len.trailing_zeros(); 112 | let right_len = len / left_len; 113 | 114 | let (left_factors, right_factors) = factors.split_at(len.trailing_zeros() as usize); 115 | 116 | self.plan_mixed_radix(left_len, left_factors, right_len, right_factors) 117 | } 118 | 119 | } else { 120 | let sqrt = (len as f32).sqrt() as usize; 121 | if sqrt * sqrt == len { 122 | // since len is a perfect square, each of its prime factors is duplicated. 123 | // since we know they're sorted, we can loop through them in chunks of 2 and keep one out of each chunk 124 | // if the stride iterator ever becomes stabilized, it'll be cleaner to use that instead of chunks 125 | let mut sqrt_factors = Vec::with_capacity(factors.len() / 2); 126 | for chunk in factors.chunks(2) { 127 | sqrt_factors.push(chunk[0]); 128 | } 129 | 130 | self.plan_mixed_radix(sqrt, &sqrt_factors, sqrt, &sqrt_factors) 131 | } else { 132 | //len isn't a perfect square. greedily take factors from the list until both sides are as close as possible to sqrt(len) 133 | //TODO: We can probably make this more optimal by using a more sophisticated non-greedy algorithm 134 | let mut product = 1; 135 | let mut second_half_index = 1; 136 | for (i, factor) in factors.iter().enumerate() { 137 | if product * *factor > sqrt { 138 | second_half_index = i; 139 | break; 140 | } else { 141 | product = product * *factor; 142 | } 143 | } 144 | 145 | //we now know that product is the largest it can be without being greater than len / product 146 | //there's one more thing we can try to make them closer together -- if product * factors[index] < len / product, 147 | if product * factors[second_half_index] < len / product { 148 | product = product * factors[second_half_index]; 149 | second_half_index = second_half_index + 1; 150 | } 151 | 152 | //we now have our two FFT sizes: product and product / len 153 | let (left_factors, right_factors) = factors.split_at(second_half_index); 154 | self.plan_mixed_radix(product, left_factors, len / product, right_factors) 155 | } 156 | }; 157 | self.algorithm_cache.insert(len, Arc::clone(&result)); 158 | result 159 | } 160 | } 161 | 162 | fn plan_mixed_radix(&mut self, 163 | left_len: usize, 164 | left_factors: &[usize], 165 | right_len: usize, 166 | right_factors: &[usize]) 167 | -> Arc> { 168 | 169 | let left_is_butterfly = BUTTERFLIES.contains(&left_len); 170 | let right_is_butterfly = BUTTERFLIES.contains(&right_len); 171 | 172 | //if both left_len and right_len are butterflies, use a mixed radix implementation specialized for butterfly sub-FFTs 173 | if left_is_butterfly && right_is_butterfly { 174 | let left_fft = self.plan_butterfly(left_len); 175 | let right_fft = self.plan_butterfly(right_len); 176 | 177 | // for butterflies, if gcd is 1, we always want to use good-thomas 178 | if gcd(left_len, right_len) == 1 { 179 | Arc::new(GoodThomasAlgorithmDoubleButterfly::new(left_fft, right_fft)) as Arc> 180 | } else { 181 | Arc::new(MixedRadixDoubleButterfly::new(left_fft, right_fft)) as Arc> 182 | } 183 | } else { 184 | //neither size is a butterfly, so go with the normal algorithm 185 | let left_fft = self.plan_fft_with_factors(left_len, left_factors); 186 | let right_fft = self.plan_fft_with_factors(right_len, right_factors); 187 | 188 | Arc::new(MixedRadix::new(left_fft, right_fft)) as Arc> 189 | } 190 | } 191 | 192 | 193 | fn plan_fft_single_factor(&mut self, len: usize) -> Arc> { 194 | match len { 195 | 0|1 => Arc::new(DFT::new(len, self.inverse)) as Arc>, 196 | 2 => Arc::new(butterflies::Butterfly2::new(self.inverse)) as Arc>, 197 | 3 => Arc::new(butterflies::Butterfly3::new(self.inverse)) as Arc>, 198 | 4 => Arc::new(butterflies::Butterfly4::new(self.inverse)) as Arc>, 199 | 5 => Arc::new(butterflies::Butterfly5::new(self.inverse)) as Arc>, 200 | 6 => Arc::new(butterflies::Butterfly6::new(self.inverse)) as Arc>, 201 | 7 => Arc::new(butterflies::Butterfly7::new(self.inverse)) as Arc>, 202 | 8 => Arc::new(butterflies::Butterfly8::new(self.inverse)) as Arc>, 203 | 16 => Arc::new(butterflies::Butterfly16::new(self.inverse)) as Arc>, 204 | 32 => Arc::new(butterflies::Butterfly32::new(self.inverse)) as Arc>, 205 | _ => self.plan_prime(len), 206 | } 207 | } 208 | 209 | fn plan_prime(&mut self, len: usize) -> Arc> { 210 | let inner_fft_len = len - 1; 211 | let factors = math_utils::prime_factors(inner_fft_len); 212 | 213 | let inner_fft = self.plan_fft_with_factors(inner_fft_len, &factors); 214 | 215 | Arc::new(RadersAlgorithm::new(len, inner_fft)) as Arc> 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/test_utils.rs: -------------------------------------------------------------------------------- 1 | use num_complex::Complex; 2 | use num_traits::Zero; 3 | 4 | use std::sync::Arc; 5 | 6 | use rand::{StdRng, SeedableRng}; 7 | use rand::distributions::{Normal, Distribution}; 8 | 9 | use algorithm::{DFT, butterflies}; 10 | use FFT; 11 | 12 | 13 | /// The seed for the random number generator used to generate 14 | /// random signals. It's defined here so that we have deterministic 15 | /// tests 16 | const RNG_SEED: [u8; 32] = [1, 9, 1, 0, 1, 1, 4, 3, 1, 4, 9, 8, 17 | 4, 1, 4, 8, 2, 8, 1, 2, 2, 2, 6, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 18 | 19 | pub fn random_signal(length: usize) -> Vec> { 20 | let mut sig = Vec::with_capacity(length); 21 | let normal_dist = Normal::new(0.0, 10.0); 22 | let mut rng: StdRng = SeedableRng::from_seed(RNG_SEED); 23 | for _ in 0..length { 24 | sig.push(Complex{re: (normal_dist.sample(&mut rng) as f32), 25 | im: (normal_dist.sample(&mut rng) as f32)}); 26 | } 27 | return sig; 28 | } 29 | 30 | pub fn compare_vectors(vec1: &[Complex], vec2: &[Complex]) -> bool { 31 | assert_eq!(vec1.len(), vec2.len()); 32 | let mut sse = 0f32; 33 | for (&a, &b) in vec1.iter().zip(vec2.iter()) { 34 | sse = sse + (a - b).norm(); 35 | } 36 | return (sse / vec1.len() as f32) < 0.1f32; 37 | } 38 | 39 | pub fn check_fft_algorithm(fft: &FFT, size: usize, inverse: bool) { 40 | assert_eq!(fft.len(), size, "Algorithm reported incorrect size"); 41 | assert_eq!(fft.is_inverse(), inverse, "Algorithm reported incorrect inverse value"); 42 | 43 | let n = 5; 44 | 45 | //test the forward direction 46 | let dft = DFT::new(size, inverse); 47 | 48 | // set up buffers 49 | let mut expected_input = random_signal(size * n); 50 | let mut actual_input = expected_input.clone(); 51 | let mut multi_input = expected_input.clone(); 52 | 53 | let mut expected_output = vec![Zero::zero(); size * n]; 54 | let mut actual_output = expected_output.clone(); 55 | let mut multi_output = expected_output.clone(); 56 | 57 | // perform the test 58 | dft.process_multi(&mut expected_input, &mut expected_output); 59 | fft.process_multi(&mut multi_input, &mut multi_output); 60 | 61 | for (input_chunk, output_chunk) in actual_input.chunks_mut(size).zip(actual_output.chunks_mut(size)) { 62 | fft.process(input_chunk, output_chunk); 63 | } 64 | 65 | //assert!(compare_vectors(&expected_output, &actual_output), "process() failed, length = {}, inverse = {}", size, inverse); 66 | assert!(compare_vectors(&expected_output, &multi_output), "process_multi() failed, length = {}, inverse = {}", size, inverse); 67 | } 68 | 69 | pub fn make_butterfly(len: usize, inverse: bool) -> Arc> { 70 | match len { 71 | 2 => Arc::new(butterflies::Butterfly2::new(inverse)), 72 | 3 => Arc::new(butterflies::Butterfly3::new(inverse)), 73 | 4 => Arc::new(butterflies::Butterfly4::new(inverse)), 74 | 5 => Arc::new(butterflies::Butterfly5::new(inverse)), 75 | 6 => Arc::new(butterflies::Butterfly6::new(inverse)), 76 | 7 => Arc::new(butterflies::Butterfly7::new(inverse)), 77 | 8 => Arc::new(butterflies::Butterfly8::new(inverse)), 78 | 16 => Arc::new(butterflies::Butterfly16::new(inverse)), 79 | _ => panic!("Invalid butterfly size: {}", len), 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/twiddles.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::f64; 3 | 4 | use num_complex::Complex; 5 | use num_traits::{FromPrimitive, One}; 6 | 7 | use common::FFTnum; 8 | 9 | pub fn generate_twiddle_factors(fft_len: usize, inverse: bool) -> Vec> { 10 | (0..fft_len).map(|i| single_twiddle(i, fft_len, inverse)).collect() 11 | } 12 | 13 | #[inline(always)] 14 | pub fn single_twiddle(i: usize, fft_len: usize, inverse: bool) -> Complex { 15 | let constant = if inverse { 16 | 2f64 * f64::consts::PI 17 | } else { 18 | -2f64 * f64::consts::PI 19 | }; 20 | 21 | let c = Complex::from_polar(&One::one(), &(constant * i as f64 / fft_len as f64)); 22 | 23 | Complex { 24 | re: FromPrimitive::from_f64(c.re).unwrap(), 25 | im: FromPrimitive::from_f64(c.im).unwrap(), 26 | } 27 | } 28 | 29 | pub fn rotate_90(value: Complex, inverse:bool) -> Complex 30 | { 31 | if inverse { 32 | Complex{re:-value.im, im: value.re} 33 | } else { 34 | Complex{re: value.im, im:-value.re} 35 | } 36 | } 37 | 38 | #[cfg(test)] 39 | mod unit_tests { 40 | use super::*; 41 | use std::f32; 42 | use test_utils::{compare_vectors}; 43 | 44 | #[test] 45 | fn test_generate() { 46 | //test the length-0 case 47 | let zero_twiddles: Vec> = generate_twiddle_factors(0, false); 48 | assert_eq!(0, zero_twiddles.len()); 49 | 50 | let constant = -2f32 * f32::consts::PI; 51 | 52 | for len in 1..10 { 53 | let actual: Vec> = generate_twiddle_factors(len, false); 54 | let expected: Vec> = (0..len).map(|i| Complex::from_polar(&1f32, &(constant * i as f32 / len as f32))).collect(); 55 | 56 | assert!(compare_vectors(&actual, &expected), "len = {}", len) 57 | } 58 | 59 | //for each len, verify that each element in the inverse is the conjugate of the non-inverse 60 | for len in 1..10 { 61 | let twiddles: Vec> = generate_twiddle_factors(len, false); 62 | let mut twiddles_inverse: Vec> = generate_twiddle_factors(len, true); 63 | 64 | for value in twiddles_inverse.iter_mut() 65 | { 66 | *value = value.conj(); 67 | } 68 | 69 | assert!(compare_vectors(&twiddles, &twiddles_inverse), "len = {}", len) 70 | } 71 | } 72 | 73 | #[test] 74 | fn test_single() { 75 | let len = 20; 76 | 77 | let twiddles: Vec> = generate_twiddle_factors(len, false); 78 | let twiddles_inverse: Vec> = generate_twiddle_factors(len, true); 79 | 80 | for i in 0..len { 81 | let single: Complex = single_twiddle(i, len, false); 82 | let single_inverse: Complex = single_twiddle(i, len, true); 83 | 84 | assert_eq!(single, twiddles[i], "forwards, i = {}", i); 85 | assert_eq!(single_inverse, twiddles_inverse[i], "inverse, i = {}", i); 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /tests/accuracy.rs: -------------------------------------------------------------------------------- 1 | //! To test the accuracy of our FFT algorithm, we first test that our 2 | //! naive DFT function is correct by comparing its output against several 3 | //! known signal/spectrum relationships. Then, we generate random signals 4 | //! for a variety of lengths, and test that our FFT algorithm matches our 5 | //! DFT calculation for those signals. 6 | 7 | 8 | extern crate rustfft; 9 | extern crate rand; 10 | 11 | use std::f32; 12 | 13 | use rustfft::num_complex::Complex; 14 | use rustfft::num_traits::Zero; 15 | 16 | use rand::{StdRng, SeedableRng}; 17 | use rand::distributions::{Normal, Distribution}; 18 | use rustfft::{FFT, FFTplanner}; 19 | use rustfft::algorithm::DFT; 20 | 21 | /// The seed for the random number generator used to generate 22 | /// random signals. It's defined here so that we have deterministic 23 | /// tests 24 | const RNG_SEED: [u8; 32] = [1, 9, 1, 0, 1, 1, 4, 3, 1, 4, 9, 8, 25 | 4, 1, 4, 8, 2, 8, 1, 2, 2, 2, 6, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 26 | 27 | /// Returns true if the mean difference in the elements of the two vectors 28 | /// is small 29 | fn compare_vectors(vec1: &[Complex], vec2: &[Complex]) -> bool { 30 | assert_eq!(vec1.len(), vec2.len()); 31 | let mut sse = 0f32; 32 | for (&a, &b) in vec1.iter().zip(vec2.iter()) { 33 | sse = sse + (a - b).norm(); 34 | } 35 | return (sse / vec1.len() as f32) < 0.1f32; 36 | } 37 | 38 | 39 | fn fft_matches_dft(signal: Vec>, inverse: bool) -> bool { 40 | let mut signal_dft = signal.clone(); 41 | let mut signal_fft = signal.clone(); 42 | 43 | let mut spectrum_dft = vec![Zero::zero(); signal.len()]; 44 | let mut spectrum_fft = vec![Zero::zero(); signal.len()]; 45 | 46 | let mut planner = FFTplanner::new(inverse); 47 | let fft = planner.plan_fft(signal.len()); 48 | assert_eq!(fft.len(), signal.len(), "FFTplanner created FFT of wrong length"); 49 | assert_eq!(fft.is_inverse(), inverse, "FFTplanner created FFT of wrong direction"); 50 | 51 | fft.process(&mut signal_fft, &mut spectrum_fft); 52 | 53 | let dft = DFT::new(signal.len(), inverse); 54 | dft.process(&mut signal_dft, &mut spectrum_dft); 55 | 56 | return compare_vectors(&spectrum_dft[..], &spectrum_fft[..]); 57 | } 58 | 59 | fn random_signal(length: usize) -> Vec> { 60 | let mut sig = Vec::with_capacity(length); 61 | let normal_dist = Normal::new(0.0, 10.0); 62 | let mut rng: StdRng = SeedableRng::from_seed(RNG_SEED); 63 | for _ in 0..length { 64 | sig.push(Complex{re: (normal_dist.sample(&mut rng) as f32), 65 | im: (normal_dist.sample(&mut rng) as f32)}); 66 | } 67 | return sig; 68 | } 69 | 70 | /// Integration tests that verify our FFT output matches the direct DFT calculation 71 | /// for random signals. 72 | #[test] 73 | fn test_fft() { 74 | for len in 1..100 { 75 | let signal = random_signal(len); 76 | assert!(fft_matches_dft(signal, false), "length = {}", len); 77 | } 78 | 79 | //test some specific lengths > 100 80 | for &len in &[256, 768] { 81 | let signal = random_signal(len); 82 | assert!(fft_matches_dft(signal, false), "length = {}", len); 83 | } 84 | } 85 | 86 | #[test] 87 | fn test_fft_inverse() { 88 | for len in 1..100 { 89 | let signal = random_signal(len); 90 | assert!(fft_matches_dft(signal, true), "length = {}", len); 91 | } 92 | 93 | //test some specific lengths > 100 94 | for &len in &[256, 768] { 95 | let signal = random_signal(len); 96 | assert!(fft_matches_dft(signal, true), "length = {}", len); 97 | } 98 | } 99 | --------------------------------------------------------------------------------