1use super::{CantorBasis, CantorError};
21use crate::{BinaryFieldExtras, Flat, HardwareField, PackedFlat};
22use alloc::boxed::Box;
23use alloc::vec::Vec;
24use core::ops::{Add, AddAssign, Mul};
25#[cfg(feature = "parallel")]
26use rayon::prelude::*;
27
28const MAX_LEVELS: usize = 64;
29
30#[cfg(feature = "parallel")]
31const TILE_LOG: usize = 10;
32
33#[cfg(feature = "parallel")]
34const TILE: usize = 1 << TILE_LOG;
35
36#[cfg(feature = "parallel")]
37const PARALLEL_THRESHOLD_BYTES: usize = 1 << 20;
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41#[non_exhaustive]
42pub enum FftError {
43 BadLength {
45 expected: usize,
47
48 got: usize,
50 },
51
52 BadLogN {
54 log_n: u32,
56
57 max: u32,
59 },
60
61 TwiddleAlloc {
63 log_n: u32,
65 },
66}
67
68impl core::fmt::Display for FftError {
69 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
70 match self {
71 FftError::BadLength { expected, got } => {
72 write!(f, "AdditiveFft data length {got}, expected {expected}")
73 }
74 FftError::BadLogN { log_n, max } => {
75 write!(f, "AdditiveFft log_n {log_n}, expected 1..={max}")
76 }
77 FftError::TwiddleAlloc { log_n } => {
78 write!(
79 f,
80 "AdditiveFft log_n {log_n}: twiddle table allocation failed"
81 )
82 }
83 }
84 }
85}
86
87impl core::error::Error for FftError {}
88
89pub struct AdditiveFft<F> {
112 log_n: u32,
113
114 twiddles: Box<[Flat<F>]>,
117}
118
119impl<F: BinaryFieldExtras + HardwareField> AdditiveFft<F> {
120 pub fn new(log_n: u32) -> Result<Self, FftError> {
129 let max = F::BITS.min(usize::BITS as usize - 1) as u32;
130
131 if log_n == 0 || log_n > max {
132 return Err(FftError::BadLogN { log_n, max });
133 }
134
135 let basis = CantorBasis::<F>::new(log_n as usize).map_err(|e| match e {
136 CantorError::ChainEnds { at } => FftError::BadLogN {
137 log_n,
138 max: at as u32,
139 },
140 _ => FftError::BadLogN { log_n, max },
141 })?;
142
143 let half = 1usize << (log_n - 1);
144
145 let mut twiddles = Vec::new();
146 twiddles
147 .try_reserve_exact(half)
148 .map_err(|_| FftError::TwiddleAlloc { log_n })?;
149
150 twiddles.push(Flat::from_raw(F::ZERO));
151
152 for &beta in &basis.betas()[1..] {
153 for t in 0..twiddles.len() {
154 let tw = twiddles[t] + beta;
155 twiddles.push(tw);
156 }
157 }
158
159 Ok(Self {
160 log_n,
161 twiddles: twiddles.into_boxed_slice(),
162 })
163 }
164
165 pub fn forward_scalar(&self, data: &mut [Flat<F>]) -> Result<(), FftError> {
171 self.forward_coset_scalar(data, Flat::from_raw(F::ZERO))
172 }
173
174 pub fn inverse_scalar(&self, data: &mut [Flat<F>]) -> Result<(), FftError> {
179 self.inverse_coset_scalar(data, Flat::from_raw(F::ZERO))
180 }
181
182 pub fn forward_coset_scalar(
188 &self,
189 data: &mut [Flat<F>],
190 offset: Flat<F>,
191 ) -> Result<(), FftError> {
192 self.check_len(data.len())?;
193 self.fwd_levels(data, offset, fwd_butterflies);
194
195 Ok(())
196 }
197
198 pub fn inverse_coset_scalar(
204 &self,
205 data: &mut [Flat<F>],
206 offset: Flat<F>,
207 ) -> Result<(), FftError> {
208 self.check_len(data.len())?;
209 self.inv_levels(data, offset, inv_butterflies);
210
211 Ok(())
212 }
213
214 pub fn forward(&self, data: &mut [PackedFlat<F>]) -> Result<(), FftError> {
220 self.forward_coset(data, Flat::from_raw(F::ZERO))
221 }
222
223 pub fn inverse(&self, data: &mut [PackedFlat<F>]) -> Result<(), FftError> {
228 self.inverse_coset(data, Flat::from_raw(F::ZERO))
229 }
230
231 pub fn forward_coset(
236 &self,
237 data: &mut [PackedFlat<F>],
238 offset: Flat<F>,
239 ) -> Result<(), FftError> {
240 self.check_len(data.len())?;
241 self.fwd_levels(data, offset, fwd_butterflies);
242
243 Ok(())
244 }
245
246 pub fn inverse_coset(
251 &self,
252 data: &mut [PackedFlat<F>],
253 offset: Flat<F>,
254 ) -> Result<(), FftError> {
255 self.check_len(data.len())?;
256 self.inv_levels(data, offset, inv_butterflies);
257
258 Ok(())
259 }
260
261 fn check_len(&self, got: usize) -> Result<(), FftError> {
262 let expected = 1usize << self.log_n;
263 if got != expected {
264 return Err(FftError::BadLength { expected, got });
265 }
266
267 Ok(())
268 }
269
270 fn fwd_levels<T, K>(&self, data: &mut [T], offset: Flat<F>, kernel: K)
275 where
276 T: Send,
277 K: Fn(&mut [T], &mut [T], Flat<F>) + Sync,
278 {
279 let levels = self.log_n as usize;
280 let chain = coset_chain(offset, levels);
281
282 #[cfg(feature = "parallel")]
283 if parallel_eligible(data) {
284 rayon::scope(|_| fwd_parallel(data, &self.twiddles, &chain[..levels], &kernel));
287 return;
288 }
289
290 for l in (0..levels).rev() {
291 pass(data, &self.twiddles, chain[l], 1usize << l, &kernel);
292 }
293 }
294
295 fn inv_levels<T, K>(&self, data: &mut [T], offset: Flat<F>, kernel: K)
298 where
299 T: Send,
300 K: Fn(&mut [T], &mut [T], Flat<F>) + Sync,
301 {
302 #[cfg(feature = "parallel")]
303 if parallel_eligible(data) {
304 let levels = self.log_n as usize;
305 let chain = coset_chain(offset, levels);
306
307 rayon::scope(|_| inv_parallel(data, &self.twiddles, &chain[..levels], &kernel));
310
311 return;
312 }
313
314 let mut c = offset;
315 for l in 0..self.log_n as usize {
316 pass(data, &self.twiddles, c, 1usize << l, &kernel);
317 c = c * c + c;
318 }
319 }
320}
321
322fn coset_chain<F: HardwareField>(offset: Flat<F>, levels: usize) -> [Flat<F>; MAX_LEVELS] {
323 let mut chain = [Flat::from_raw(F::ZERO); MAX_LEVELS];
324 let mut c = offset;
325
326 for slot in chain.iter_mut().take(levels) {
327 *slot = c;
328 c = c * c + c;
329 }
330
331 chain
332}
333
334fn pass<F, T, K>(data: &mut [T], twiddles: &[Flat<F>], coset: Flat<F>, s: usize, kernel: &K)
337where
338 F: HardwareField,
339 T: Send,
340 K: Fn(&mut [T], &mut [T], Flat<F>) + Sync,
341{
342 let tws = &twiddles[..data.len() / (2 * s)];
343
344 blocks_serial(data, tws, coset, s, kernel);
345}
346
347#[cfg(feature = "parallel")]
348fn parallel_eligible<T>(data: &[T]) -> bool {
349 size_of_val(data) >= PARALLEL_THRESHOLD_BYTES && data.len() >= TILE
350}
351
352#[cfg(feature = "parallel")]
353fn fwd_parallel<F, T, K>(data: &mut [T], twiddles: &[Flat<F>], chain: &[Flat<F>], kernel: &K)
354where
355 F: HardwareField,
356 T: Send,
357 K: Fn(&mut [T], &mut [T], Flat<F>) + Sync,
358{
359 for l in (TILE_LOG..chain.len()).rev() {
360 wide_pass(data, twiddles, chain[l], 1usize << l, kernel);
361 }
362
363 data.par_chunks_exact_mut(TILE)
364 .enumerate()
365 .for_each(|(i, tile)| {
366 for l in (0..TILE_LOG).rev() {
367 tile_pass(tile, i, twiddles, chain[l], l, kernel);
368 }
369 });
370}
371
372#[cfg(feature = "parallel")]
373fn inv_parallel<F, T, K>(data: &mut [T], twiddles: &[Flat<F>], chain: &[Flat<F>], kernel: &K)
374where
375 F: HardwareField,
376 T: Send,
377 K: Fn(&mut [T], &mut [T], Flat<F>) + Sync,
378{
379 data.par_chunks_exact_mut(TILE)
380 .enumerate()
381 .for_each(|(i, tile)| {
382 for (l, &coset) in chain[..TILE_LOG].iter().enumerate() {
383 tile_pass(tile, i, twiddles, coset, l, kernel);
384 }
385 });
386
387 for (l, &coset) in chain.iter().enumerate().skip(TILE_LOG) {
388 wide_pass(data, twiddles, coset, 1usize << l, kernel);
389 }
390}
391
392#[cfg(feature = "parallel")]
393fn tile_pass<F, T, K>(
394 tile: &mut [T],
395 i: usize,
396 twiddles: &[Flat<F>],
397 coset: Flat<F>,
398 l: usize,
399 kernel: &K,
400) where
401 F: HardwareField,
402 K: Fn(&mut [T], &mut [T], Flat<F>) + Sync,
403{
404 let per = TILE >> (l + 1);
405
406 blocks_serial(
407 tile,
408 &twiddles[i * per..(i + 1) * per],
409 coset,
410 1usize << l,
411 kernel,
412 );
413}
414
415#[cfg(feature = "parallel")]
416fn wide_pass<F, T, K>(data: &mut [T], twiddles: &[Flat<F>], coset: Flat<F>, s: usize, kernel: &K)
417where
418 F: HardwareField,
419 T: Send,
420 K: Fn(&mut [T], &mut [T], Flat<F>) + Sync,
421{
422 let tws = &twiddles[..data.len() / (2 * s)];
423
424 data.par_chunks_exact_mut(2 * s)
425 .zip(tws.par_iter())
426 .for_each(|(blk, &t)| {
427 let tw = coset + t;
428 let (lo, hi) = blk.split_at_mut(s);
429
430 lo.par_chunks_mut(TILE)
431 .zip(hi.par_chunks_mut(TILE))
432 .for_each(|(l, h)| kernel(l, h, tw));
433 });
434}
435
436fn blocks_serial<F, T, K>(data: &mut [T], tws: &[Flat<F>], coset: Flat<F>, s: usize, kernel: &K)
437where
438 F: HardwareField,
439 K: Fn(&mut [T], &mut [T], Flat<F>) + Sync,
440{
441 for (blk, &t) in data.chunks_exact_mut(2 * s).zip(tws) {
442 let (lo, hi) = blk.split_at_mut(s);
443 kernel(lo, hi, coset + t);
444 }
445}
446
447fn fwd_butterflies<F, T>(lo: &mut [T], hi: &mut [T], tw: Flat<F>)
448where
449 F: HardwareField,
450 T: Copy + Add<Output = T> + Mul<Flat<F>, Output = T>,
451{
452 for (p, q) in lo.iter_mut().zip(hi.iter_mut()) {
453 let qv = *q;
454 let v = *p + qv * tw;
455
456 *p = v;
457 *q = v + qv;
458 }
459}
460
461fn inv_butterflies<F, T>(lo: &mut [T], hi: &mut [T], tw: Flat<F>)
462where
463 F: HardwareField,
464 T: Copy + AddAssign + Add<Output = T> + Mul<Flat<F>, Output = T>,
465{
466 for (p, q) in lo.iter_mut().zip(hi.iter_mut()) {
467 let qv = *p + *q;
468 *p += qv * tw;
469 *q = qv;
470 }
471}