1use alloc::slice;
2use alloc::sync::Arc;
3use alloc::vec::Vec;
4use core::mem::{MaybeUninit, transmute};
5
6use itertools::{Itertools, izip};
7use p3_field::integers::QuotientMap;
8use p3_field::{Field, Powers, PrimeCharacteristicRing, TwoAdicField};
9use p3_matrix::Matrix;
10use p3_matrix::bitrev::{BitReversalPerm, BitReversedMatrixView, BitReversibleMatrix};
11use p3_matrix::dense::{RowMajorMatrix, RowMajorMatrixView, RowMajorMatrixViewMut};
12use p3_matrix::util::reverse_matrix_index_bits;
13use p3_maybe_rayon::prelude::*;
14use p3_util::{log2_strict_usize, reverse_bits_len, reverse_slice_index_bits};
15use tracing::{debug_span, instrument};
16
17use crate::butterflies::{Butterfly, DitButterfly, ScaledDitButterfly, TwiddleFreeButterfly};
18use crate::twiddle_cache::TwiddleCache;
19use crate::{Layout, TwoAdicSubgroupDft};
20
21#[derive(Default, Clone, Debug)]
29pub struct Radix2DitParallel<F> {
30 twiddles: Arc<TwiddleCache<usize, VectorPair<F>>>,
32
33 #[allow(clippy::type_complexity)]
35 coset_twiddles: Arc<TwiddleCache<(usize, F), [Vec<F>]>>,
36
37 inverse_twiddles: Arc<TwiddleCache<usize, VectorPair<F>>>,
39}
40
41#[derive(Default, Clone, Debug)]
43struct VectorPair<F> {
44 twiddles: Vec<F>,
45 bitrev_twiddles: Vec<F>,
46}
47
48impl<F> Radix2DitParallel<F>
49where
50 F: TwoAdicField + Ord,
51{
52 fn get_or_compute_twiddles(&self, log_h: usize) -> Arc<VectorPair<F>> {
53 self.twiddles.get_or_compute(log_h, || {
54 let half_h = (1 << log_h) >> 1;
55 let root = F::two_adic_generator(log_h);
56 let twiddles = root.powers().collect_n(half_h);
57 let mut bitrev_twiddles = twiddles.clone();
58 reverse_slice_index_bits(&mut bitrev_twiddles);
59
60 Arc::new(VectorPair {
61 twiddles,
62 bitrev_twiddles,
63 })
64 })
65 }
66
67 fn get_or_compute_coset_twiddles(&self, (log_h, shift): (usize, F)) -> Arc<[Vec<F>]> {
68 self.coset_twiddles.get_or_compute((log_h, shift), || {
69 let mid = log_h.div_ceil(2);
70 let h = 1 << log_h;
71 let root = F::two_adic_generator(log_h);
72 (0..log_h)
73 .map(|layer| {
74 let shift_power = shift.exp_power_of_2(layer);
75 let powers = Powers {
76 base: root.exp_power_of_2(layer),
77 current: shift_power,
78 };
79 let mut twiddles = powers.collect_n(h >> (layer + 1));
80 let layer_rev = log_h - 1 - layer;
81 if layer_rev >= mid {
82 reverse_slice_index_bits(&mut twiddles);
83 }
84 twiddles
85 })
86 .collect::<Vec<_>>()
87 .into()
88 })
89 }
90
91 fn get_or_compute_inverse_twiddles(&self, log_h: usize) -> Arc<VectorPair<F>> {
92 self.inverse_twiddles.get_or_compute(log_h, || {
93 let half_h = (1 << log_h) >> 1;
94 let root_inv = F::two_adic_generator(log_h).inverse();
95 let twiddles = root_inv.powers().collect_n(half_h);
96 let mut bitrev_twiddles = twiddles.clone();
97 reverse_slice_index_bits(&mut bitrev_twiddles);
98
99 Arc::new(VectorPair {
100 twiddles,
101 bitrev_twiddles,
102 })
103 })
104 }
105}
106
107impl<F: TwoAdicField + Ord> TwoAdicSubgroupDft<F> for Radix2DitParallel<F> {
108 type Evaluations = BitReversedMatrixView<RowMajorMatrix<F>>;
109
110 fn dft_batch(&self, mut mat: RowMajorMatrix<F>) -> Self::Evaluations {
111 let h = mat.height();
112 let log_h = log2_strict_usize(h);
113
114 let twiddles = self.get_or_compute_twiddles(log_h);
116
117 let mid = log_h.div_ceil(2);
118
119 reverse_matrix_index_bits(&mut mat);
121 first_half(&mut mat, mid, &twiddles.twiddles);
122
123 reverse_matrix_index_bits(&mut mat);
125 second_half(&mut mat, mid, &twiddles.bitrev_twiddles, None);
126
127 mat.bit_reverse_rows()
128 }
129
130 fn coset_dft_batch(&self, mut mat: RowMajorMatrix<F>, shift: F) -> Self::Evaluations {
131 reverse_matrix_index_bits(&mut mat);
132 coset_dft(self, &mut mat.as_view_mut(), shift, 0, &|_, _| {});
133 BitReversalPerm::new_view(mat)
134 }
135
136 fn idft_batch(&self, mut mat: RowMajorMatrix<F>) -> RowMajorMatrix<F> {
137 let h = mat.height();
138 if h == 1 {
139 return mat;
140 }
141 let log_h = log2_strict_usize(h);
142 let mid = log_h.div_ceil(2);
143 let inverse_twiddles = self.get_or_compute_inverse_twiddles(log_h);
144
145 reverse_matrix_index_bits(&mut mat);
146 first_half(&mut mat, mid, &inverse_twiddles.twiddles);
147 reverse_matrix_index_bits(&mut mat);
148 let h_inv_subfield = F::PrimeSubfield::ONE.div_2exp_u64(log_h as u64);
149 let scale = Some(F::from_prime_subfield(h_inv_subfield));
150 second_half(&mut mat, mid, &inverse_twiddles.bitrev_twiddles, scale);
151 reverse_matrix_index_bits(&mut mat);
152 mat
153 }
154
155 fn coset_idft_batch(&self, mat: RowMajorMatrix<F>, shift: F) -> RowMajorMatrix<F> {
156 let mut coeffs = self.idft_batch(mat);
157 crate::util::coset_shift_cols(&mut coeffs, shift.inverse());
158 coeffs
159 }
160
161 #[instrument(skip_all, level = "debug", fields(dims = %mat.dimensions(), added_bits = added_bits))]
162 fn coset_lde_batch_with_transform<T>(
163 &self,
164 mat: RowMajorMatrix<F>,
165 added_bits: usize,
166 shift: F,
167 transform: T,
168 ) -> Self::Evaluations
169 where
170 T: FnOnce(&mut RowMajorMatrixViewMut<'_, F>, Layout),
171 {
172 self.lde_with_consumer(mat, added_bits, shift, transform, &|_, _| {})
173 }
174
175 #[instrument(skip_all, level = "debug", fields(dims = %mat.dimensions(), added_bits = added_bits))]
176 fn coset_lde_batch_with_blocks<T, K, C>(
177 &self,
178 mat: RowMajorMatrix<F>,
179 added_bits: usize,
180 shift: F,
181 transform: T,
182 make_consumer: K,
183 ) -> Self::Evaluations
184 where
185 T: FnOnce(&mut RowMajorMatrixViewMut<'_, F>, Layout),
186 K: FnOnce(usize) -> C,
187 C: Fn(usize, RowMajorMatrixView<'_, F>) + Sync,
188 {
189 let log_h = log2_strict_usize(mat.height());
191 let mid = log_h.div_ceil(2);
192 let consume = make_consumer(1 << (log_h - mid));
193 self.lde_with_consumer(mat, added_bits, shift, transform, &consume)
194 }
195}
196
197impl<F: TwoAdicField + Ord> Radix2DitParallel<F> {
198 fn lde_with_consumer<T>(
199 &self,
200 mut mat: RowMajorMatrix<F>,
201 added_bits: usize,
202 shift: F,
203 transform: T,
204 consume: &(impl Fn(usize, RowMajorMatrixView<'_, F>) + Sync),
205 ) -> BitReversedMatrixView<RowMajorMatrix<F>>
206 where
207 T: FnOnce(&mut RowMajorMatrixViewMut<'_, F>, Layout),
208 {
209 let w = mat.width;
210 let h = mat.height();
211 let log_h = log2_strict_usize(h);
212 let mid = log_h.div_ceil(2);
213
214 let inverse_twiddles = self.get_or_compute_inverse_twiddles(log_h);
215
216 reverse_matrix_index_bits(&mut mat);
218 first_half(&mut mat, mid, &inverse_twiddles.twiddles);
219
220 reverse_matrix_index_bits(&mut mat);
222 let h_inv_subfield = F::PrimeSubfield::from_int(h).try_inverse();
226 let scale = h_inv_subfield.map(F::from_prime_subfield);
227 second_half(&mut mat, mid, &inverse_twiddles.bitrev_twiddles, scale);
228 transform(&mut mat.as_view_mut(), Layout::BitReversed);
231
232 let lde_elems = w * (h << added_bits);
233 let elems_to_add = lde_elems - w * h;
234 debug_span!("reserve_exact").in_scope(|| mat.values.reserve_exact(elems_to_add));
235
236 let g_big = F::two_adic_generator(log_h + added_bits);
237
238 let coset_twiddles: Vec<_> = (0..(1usize << added_bits))
240 .into_par_iter()
241 .map(|coset_idx| {
242 let total_shift = g_big.exp_u64(coset_idx as u64) * shift;
243 self.get_or_compute_coset_twiddles((log_h, total_shift))
244 })
245 .collect();
246
247 let mat_ptr = mat.values.as_mut_ptr();
248 let rest_ptr = unsafe { (mat_ptr as *mut MaybeUninit<F>).add(w * h) };
249 let first_slice: &mut [F] = unsafe { slice::from_raw_parts_mut(mat_ptr, w * h) };
250 let rest_slice: &mut [MaybeUninit<F>] =
251 unsafe { slice::from_raw_parts_mut(rest_ptr, lde_elems - w * h) };
252 let mut first_coset_mat = RowMajorMatrixViewMut::new(first_slice, w);
253 let mut rest_cosets_mat = rest_slice
254 .chunks_exact_mut(w * h)
255 .map(|slice| RowMajorMatrixViewMut::new(slice, w))
256 .collect_vec();
257
258 let src = first_coset_mat.as_view();
261 rest_cosets_mat
262 .par_iter_mut()
263 .enumerate()
264 .for_each(|(k, dest)| {
265 let coset_idx = reverse_bits_len(k + 1, added_bits);
266 coset_dft_oop(&src, dest, &coset_twiddles[coset_idx], (k + 1) * h, consume);
267 });
268
269 coset_dft(self, &mut first_coset_mat.as_view_mut(), shift, 0, consume);
271
272 unsafe {
274 mat.values.set_len(lde_elems);
275 }
276 BitReversalPerm::new_view(mat)
277 }
278}
279
280#[instrument(level = "debug", skip_all)]
281fn coset_dft<F: TwoAdicField + Ord>(
282 dft: &Radix2DitParallel<F>,
283 mat: &mut RowMajorMatrixViewMut<'_, F>,
284 shift: F,
285 row_base: usize,
286 consume: &(impl Fn(usize, RowMajorMatrixView<'_, F>) + Sync),
287) {
288 let log_h = log2_strict_usize(mat.height());
289 let mid = log_h.div_ceil(2);
290
291 let twiddles = dft.get_or_compute_coset_twiddles((log_h, shift));
292
293 first_half_general(mat, mid, &twiddles);
295
296 reverse_matrix_index_bits(mat);
298
299 second_half_general(mat, mid, &twiddles, row_base, consume);
300}
301
302#[instrument(level = "debug", skip_all)]
311fn coset_dft_oop<F: Field>(
312 src: &RowMajorMatrixView<'_, F>,
313 dst_maybe: &mut RowMajorMatrixViewMut<'_, MaybeUninit<F>>,
314 twiddles: &[Vec<F>],
315 row_base: usize,
316 consume: &(impl Fn(usize, RowMajorMatrixView<'_, F>) + Sync),
317) {
318 assert_eq!(src.dimensions(), dst_maybe.dimensions());
319
320 let log_h = log2_strict_usize(dst_maybe.height());
321 assert_eq!(twiddles.len(), log_h, "incorrect number of twiddle layers");
323 for (layer, table) in twiddles.iter().enumerate() {
324 assert_eq!(
325 table.len(),
326 src.height() >> (layer + 1),
327 "incorrect twiddle count for layer {layer}"
328 );
329 }
330
331 let mid = log_h.div_ceil(2);
332 if log_h == 0 {
333 let src_maybe = unsafe {
336 transmute::<&RowMajorMatrixView<'_, F>, &RowMajorMatrixView<'_, MaybeUninit<F>>>(src)
337 };
338 dst_maybe.copy_from(src_maybe);
339 } else {
340 first_half_general_oop(src, dst_maybe, mid, twiddles);
342 }
343
344 let dst = unsafe {
346 transmute::<&mut RowMajorMatrixViewMut<'_, MaybeUninit<F>>, &mut RowMajorMatrixViewMut<'_, F>>(
347 dst_maybe,
348 )
349 };
350
351 if log_h == 0 {
352 consume(row_base, dst.as_view());
353 return;
354 }
355
356 reverse_matrix_index_bits(dst);
358
359 second_half_general(dst, mid, twiddles, row_base, consume);
360}
361
362#[instrument(level = "debug", skip_all)]
370fn first_half<F: Field>(mat: &mut RowMajorMatrix<F>, mid: usize, twiddles: &[F]) {
371 let log_h = log2_strict_usize(mat.height());
372
373 mat.par_row_chunks_exact_mut(1 << mid)
375 .for_each(|mut submat| {
376 let mut backwards = false;
377 for layer in 0..mid {
378 if layer == 0 {
379 dit_layer_twiddle_free(&mut submat, backwards);
383 } else {
384 let layer_rev = log_h - 1 - layer;
385 let layer_pow = 1 << layer_rev;
386 dit_layer_first_one(
390 &mut submat,
391 layer,
392 twiddles.iter().step_by(layer_pow),
393 backwards,
394 );
395 }
396 backwards = !backwards;
397 }
398 });
399}
400
401#[instrument(level = "debug", skip_all)]
404fn first_half_general<F: Field>(
405 mat: &mut RowMajorMatrixViewMut<'_, F>,
406 mid: usize,
407 twiddles: &[Vec<F>],
408) {
409 let log_h = log2_strict_usize(mat.height());
410 mat.par_row_chunks_exact_mut(1 << mid)
411 .for_each(|mut submat| {
412 let mut backwards = false;
413 for layer in 0..mid {
414 let layer_rev = log_h - 1 - layer;
415 dit_layer(&mut submat, layer, twiddles[layer_rev].iter(), backwards);
416 backwards = !backwards;
417 }
418 });
419}
420
421#[instrument(level = "debug", skip_all)]
428fn first_half_general_oop<F: Field>(
429 src: &RowMajorMatrixView<'_, F>,
430 dst_maybe: &mut RowMajorMatrixViewMut<'_, MaybeUninit<F>>,
431 mid: usize,
432 twiddles: &[Vec<F>],
433) {
434 let log_h = log2_strict_usize(src.height());
435 src.par_row_chunks_exact(1 << mid)
436 .zip(dst_maybe.par_row_chunks_exact_mut(1 << mid))
437 .for_each(|(src_submat, mut dst_submat_maybe)| {
438 debug_assert_eq!(src_submat.dimensions(), dst_submat_maybe.dimensions());
439
440 let layer_rev = log_h - 1;
443 dit_layer_oop(
444 &src_submat,
445 &mut dst_submat_maybe,
446 0,
447 twiddles[layer_rev].iter(),
448 );
449
450 let mut dst_submat = unsafe {
452 transmute::<RowMajorMatrixViewMut<'_, MaybeUninit<F>>, RowMajorMatrixViewMut<'_, F>>(
453 dst_submat_maybe,
454 )
455 };
456
457 let mut backwards = true;
459 for layer in 1..mid {
460 let layer_rev = log_h - 1 - layer;
461 dit_layer(
462 &mut dst_submat,
463 layer,
464 twiddles[layer_rev].iter(),
465 backwards,
466 );
467 backwards = !backwards;
468 }
469 });
470}
471
472#[instrument(level = "debug", skip_all)]
478#[inline(always)] fn second_half<F: Field>(
480 mat: &mut RowMajorMatrix<F>,
481 mid: usize,
482 twiddles_rev: &[F],
483 scale: Option<F>,
484) {
485 let log_h = log2_strict_usize(mat.height());
486
487 mat.par_row_chunks_exact_mut(1 << (log_h - mid))
489 .enumerate()
490 .for_each(|(thread, mut submat)| {
491 let mut backwards = false;
492 if let Some(scale) = scale {
493 let mut scale_applied = false;
497 for layer in mid..log_h {
498 let first_block = thread << (layer - mid);
499 if !scale_applied {
500 scale_applied = true;
501 dit_layer_rev_scaled(
502 &mut submat,
503 log_h,
504 layer,
505 twiddles_rev[first_block..].iter().copied(),
506 backwards,
507 Some(scale),
508 );
509 } else {
510 dit_layer_rev(
511 &mut submat,
512 log_h,
513 layer,
514 twiddles_rev[first_block..].iter().copied(),
515 backwards,
516 );
517 }
518 backwards = !backwards;
519 }
520 if !scale_applied {
522 submat.scale(scale);
523 }
524 } else {
525 for layer in mid..log_h {
526 let first_block = thread << (layer - mid);
527 dit_layer_rev(
528 &mut submat,
529 log_h,
530 layer,
531 twiddles_rev[first_block..].iter().copied(),
532 backwards,
533 );
534 backwards = !backwards;
535 }
536 }
537 });
538}
539
540#[instrument(level = "debug", skip_all)]
543fn second_half_general<F: Field>(
544 mat: &mut RowMajorMatrixViewMut<'_, F>,
545 mid: usize,
546 twiddles_rev: &[Vec<F>],
547 row_base: usize,
548 consume: &(impl Fn(usize, RowMajorMatrixView<'_, F>) + Sync),
549) {
550 let log_h = log2_strict_usize(mat.height());
551 mat.par_row_chunks_exact_mut(1 << (log_h - mid))
552 .enumerate()
553 .for_each(|(thread, mut submat)| {
554 let mut backwards = false;
555 for layer in mid..log_h {
556 let layer_rev = log_h - 1 - layer;
557 let first_block = thread << (layer - mid);
558 dit_layer_rev(
559 &mut submat,
560 log_h,
561 layer,
562 twiddles_rev[layer_rev][first_block..].iter().copied(),
563 backwards,
564 );
565 backwards = !backwards;
566 }
567 consume(row_base + thread * submat.height(), submat.as_view());
568 });
569}
570
571fn dit_layer_twiddle_free<F: Field>(submat: &mut RowMajorMatrixViewMut<'_, F>, backwards: bool) {
580 let width = submat.width();
582 debug_assert!(submat.height() >= 2);
583
584 let process_block = move |block: &mut [F]| {
585 let (lo, hi) = block.split_at_mut(width);
587 TwiddleFreeButterfly.apply_to_rows(lo, hi);
588 };
589
590 let blocks = submat.values.chunks_mut(2 * width);
591 if backwards {
592 for block in blocks.rev() {
593 process_block(block);
594 }
595 } else {
596 for block in blocks {
597 process_block(block);
598 }
599 }
600}
601
602fn dit_layer_first_one<'a, F: Field>(
611 submat: &mut RowMajorMatrixViewMut<'_, F>,
612 layer: usize,
613 twiddles: impl Iterator<Item = &'a F> + Clone,
614 backwards: bool,
615) {
616 let half_block_size = 1 << layer;
617 let block_size = half_block_size * 2;
618 let width = submat.width();
619 debug_assert!(submat.height() >= block_size);
620 debug_assert!(
621 half_block_size >= 2,
622 "layer must be >= 1 for dit_layer_first_one"
623 );
624
625 let process_block = move |block: &mut [F]| {
626 let (lows, highs) = block.split_at_mut(half_block_size * width);
627 let mut tw_iter = twiddles.clone();
628 let _ = tw_iter.next(); let (lo0, lo_rest) = lows.split_at_mut(width);
631 let (hi0, hi_rest) = highs.split_at_mut(width);
632 TwiddleFreeButterfly.apply_to_rows(lo0, hi0);
633 for (lo, hi, twiddle) in izip!(
635 lo_rest.chunks_mut(width),
636 hi_rest.chunks_mut(width),
637 tw_iter
638 ) {
639 DitButterfly(*twiddle).apply_to_rows(lo, hi);
640 }
641 };
642
643 let blocks = submat.values.chunks_mut(block_size * width);
644 if backwards {
645 for block in blocks.rev() {
646 process_block(block);
647 }
648 } else {
649 for block in blocks {
650 process_block(block);
651 }
652 }
653}
654
655fn dit_layer<'a, F: Field>(
657 submat: &mut RowMajorMatrixViewMut<'_, F>,
658 layer: usize,
659 twiddles: impl Iterator<Item = &'a F> + Clone,
660 backwards: bool,
661) {
662 let half_block_size = 1 << layer;
663 let block_size = half_block_size * 2;
664 let width = submat.width();
665 debug_assert!(submat.height() >= block_size);
666
667 let process_block = move |block: &mut [F]| {
668 let (lows, highs) = block.split_at_mut(half_block_size * width);
669 for (lo, hi, twiddle) in izip!(
670 lows.chunks_mut(width),
671 highs.chunks_mut(width),
672 twiddles.clone()
673 ) {
674 DitButterfly(*twiddle).apply_to_rows(lo, hi);
675 }
676 };
677
678 let blocks = submat.values.chunks_mut(block_size * width);
679 if backwards {
680 for block in blocks.rev() {
681 process_block(block);
682 }
683 } else {
684 for block in blocks {
685 process_block(block);
686 }
687 }
688}
689
690fn dit_layer_oop<'a, F: Field>(
692 src: &RowMajorMatrixView<'_, F>,
693 dst: &mut RowMajorMatrixViewMut<'_, MaybeUninit<F>>,
694 layer: usize,
695 twiddles: impl Iterator<Item = &'a F> + Clone,
696) {
697 debug_assert_eq!(src.dimensions(), dst.dimensions());
698 let half_block_size = 1 << layer;
699 let block_size = half_block_size * 2;
700 let width = dst.width();
701 debug_assert!(dst.height() >= block_size);
702
703 let process_blocks = move |src_block: &[F], dst_block: &mut [MaybeUninit<F>]| {
704 let (src_lows, src_highs) = src_block.split_at(half_block_size * width);
705 let (dst_lows, dst_highs) = dst_block.split_at_mut(half_block_size * width);
706
707 for (src_lo, dst_lo, src_hi, dst_hi, twiddle) in izip!(
708 src_lows.chunks(width),
709 dst_lows.chunks_mut(width),
710 src_highs.chunks(width),
711 dst_highs.chunks_mut(width),
712 twiddles.clone()
713 ) {
714 DitButterfly(*twiddle).apply_to_rows_oop(src_lo, dst_lo, src_hi, dst_hi);
715 }
716 };
717
718 let src_chunks = src.values.chunks(block_size * width);
719 let dst_chunks = dst.values.chunks_mut(block_size * width);
720
721 for (src_block, dst_block) in src_chunks.zip(dst_chunks) {
722 process_blocks(src_block, dst_block);
723 }
724}
725
726fn dit_layer_rev_scaled<F: Field>(
734 submat: &mut RowMajorMatrixViewMut<'_, F>,
735 log_h: usize,
736 layer: usize,
737 twiddles_rev: impl DoubleEndedIterator<Item = F> + ExactSizeIterator,
738 backwards: bool,
739 scale: Option<F>,
740) {
741 let layer_rev = log_h - 1 - layer;
742
743 let half_block_size = 1 << layer_rev;
744 let block_size = half_block_size * 2;
745 let width = submat.width();
746 debug_assert!(submat.height() >= block_size);
747
748 match scale {
749 None => {
750 let blocks_and_twiddles = submat
752 .values
753 .chunks_mut(block_size * width)
754 .zip(twiddles_rev);
755 if backwards {
756 for (block, twiddle) in blocks_and_twiddles.rev() {
757 let (lo, hi) = block.split_at_mut(half_block_size * width);
758 DitButterfly(twiddle).apply_to_rows(lo, hi);
759 }
760 } else {
761 for (block, twiddle) in blocks_and_twiddles {
762 let (lo, hi) = block.split_at_mut(half_block_size * width);
763 DitButterfly(twiddle).apply_to_rows(lo, hi);
764 }
765 }
766 }
767 Some(s) => {
768 let blocks_and_twiddles = submat
772 .values
773 .chunks_mut(block_size * width)
774 .zip(twiddles_rev);
775 if backwards {
776 for (block, twiddle) in blocks_and_twiddles.rev() {
777 let (lo, hi) = block.split_at_mut(half_block_size * width);
778 ScaledDitButterfly::new(twiddle, s).apply_to_rows(lo, hi);
779 }
780 } else {
781 for (block, twiddle) in blocks_and_twiddles {
782 let (lo, hi) = block.split_at_mut(half_block_size * width);
783 ScaledDitButterfly::new(twiddle, s).apply_to_rows(lo, hi);
784 }
785 }
786 }
787 }
788}
789
790fn dit_layer_rev<F: Field>(
793 submat: &mut RowMajorMatrixViewMut<'_, F>,
794 log_h: usize,
795 layer: usize,
796 twiddles_rev: impl DoubleEndedIterator<Item = F> + ExactSizeIterator,
797 backwards: bool,
798) {
799 let layer_rev = log_h - 1 - layer;
800
801 let half_block_size = 1 << layer_rev;
802 let block_size = half_block_size * 2;
803 let width = submat.width();
804 debug_assert!(submat.height() >= block_size);
805
806 let blocks_and_twiddles = submat
807 .values
808 .chunks_mut(block_size * width)
809 .zip(twiddles_rev);
810 if backwards {
811 for (block, twiddle) in blocks_and_twiddles.rev() {
812 let (lo, hi) = block.split_at_mut(half_block_size * width);
813 DitButterfly(twiddle).apply_to_rows(lo, hi);
814 }
815 } else {
816 for (block, twiddle) in blocks_and_twiddles {
817 let (lo, hi) = block.split_at_mut(half_block_size * width);
818 DitButterfly(twiddle).apply_to_rows(lo, hi);
819 }
820 }
821}
822
823#[cfg(test)]
824mod tests {
825 use p3_baby_bear::BabyBear;
826 use p3_field::TwoAdicField;
827 use p3_matrix::Matrix;
828 use p3_matrix::dense::RowMajorMatrix;
829 use rand::SeedableRng;
830 use rand::rngs::SmallRng;
831
832 use super::*;
833
834 type F = BabyBear;
835
836 #[test]
837 #[should_panic(expected = "incorrect number of twiddle layers")]
838 fn coset_dft_oop_rejects_missing_twiddle_layer() {
839 let dft = Radix2DitParallel::<F>::default();
840 let mut twiddles = dft
841 .get_or_compute_coset_twiddles((3, F::GENERATOR))
842 .to_vec();
843 twiddles.pop();
844 let src = RowMajorMatrix::new(alloc::vec![F::ONE; 8], 1);
845 let mut dst = RowMajorMatrix::new(alloc::vec![MaybeUninit::uninit(); 8], 1);
846
847 coset_dft_oop(
848 &src.as_view(),
849 &mut dst.as_view_mut(),
850 &twiddles,
851 0,
852 &|_, _| {},
853 );
854 }
855
856 #[test]
857 #[should_panic(expected = "incorrect twiddle count for layer 2")]
858 fn coset_dft_oop_rejects_short_twiddle_layer() {
859 let dft = Radix2DitParallel::<F>::default();
860 let mut twiddles = dft
861 .get_or_compute_coset_twiddles((3, F::GENERATOR))
862 .to_vec();
863 twiddles[2].clear();
864 let src = RowMajorMatrix::new(alloc::vec![F::ONE; 8], 1);
865 let mut dst = RowMajorMatrix::new(alloc::vec![MaybeUninit::uninit(); 8], 1);
866
867 coset_dft_oop(
868 &src.as_view(),
869 &mut dst.as_view_mut(),
870 &twiddles,
871 0,
872 &|_, _| {},
873 );
874 }
875
876 #[test]
877 fn coset_dft_idft_roundtrip() {
878 let dft = Radix2DitParallel::<F>::default();
879 let shift = F::GENERATOR;
880 let mut rng = SmallRng::seed_from_u64(42);
881 let original = RowMajorMatrix::<F>::rand(&mut rng, 16, 3);
882
883 let evals = dft.coset_dft_batch(original.clone(), shift);
884 let recovered = dft.coset_idft_batch(evals.to_row_major_matrix(), shift);
885
886 assert_eq!(original, recovered);
887 }
888
889 #[test]
890 fn coset_dft_matches_default_trait() {
891 let dft = Radix2DitParallel::<F>::default();
892 let shift = F::two_adic_generator(4) * F::GENERATOR;
893 let mut rng = SmallRng::seed_from_u64(7);
894 let mat = RowMajorMatrix::<F>::rand(&mut rng, 16, 4);
895
896 let override_result = dft
897 .coset_dft_batch(mat.clone(), shift)
898 .to_row_major_matrix();
899
900 let mut shifted = mat;
901 crate::util::coset_shift_cols(&mut shifted, shift);
902 let default_result = dft.dft_batch(shifted).to_row_major_matrix();
903
904 assert_eq!(override_result, default_result);
905 }
906}