1use crate::{
29 cfg_chunks_mut,
30 cfg_into_iter,
31 cfg_iter,
32 cfg_iter_mut,
33 fft::{DomainCoeff, SparsePolynomial},
34};
35use snarkvm_fields::{FftField, FftParameters, Field, batch_inversion};
36#[cfg(not(feature = "serial"))]
37use snarkvm_utilities::max_available_threads;
38use snarkvm_utilities::{execute_with_max_available_threads, serialize::*};
39
40use rand::RngExt;
41use std::{borrow::Cow, fmt};
42
43use anyhow::{Result, ensure};
44
45#[cfg(feature = "serial")]
46use itertools::Itertools;
47#[cfg(not(feature = "serial"))]
48use rayon::prelude::*;
49
50pub fn log2(x: usize) -> u32 {
64 if x == 0 {
65 0
66 } else if x.is_power_of_two() {
67 1usize.leading_zeros() - x.leading_zeros()
68 } else {
69 0usize.leading_zeros() - x.leading_zeros()
70 }
71}
72
73#[allow(unused)]
75#[cfg(not(feature = "serial"))]
76const MIN_PARALLEL_CHUNK_SIZE: usize = 1 << 7;
77
78#[derive(Copy, Clone, Hash, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
82pub struct EvaluationDomain<F: FftField> {
83 pub size: u64,
85 pub log_size_of_group: u32,
87 pub size_as_field_element: F,
89 pub size_inv: F,
91 pub group_gen: F,
93 pub group_gen_inv: F,
95 pub generator_inv: F,
97}
98
99impl<F: FftField> fmt::Debug for EvaluationDomain<F> {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 write!(f, "Multiplicative subgroup of size {}", self.size)
102 }
103}
104
105impl<F: FftField> EvaluationDomain<F> {
106 pub fn sample_element_outside_domain<R: RngExt>(&self, rng: &mut R) -> F {
108 let mut t = F::rand(rng);
109 while self.evaluate_vanishing_polynomial(t).is_zero() {
110 t = F::rand(rng);
111 }
112 t
113 }
114
115 pub fn new(num_coeffs: usize) -> Option<Self> {
118 let size = num_coeffs.checked_next_power_of_two()? as u64;
120 let log_size_of_group = size.trailing_zeros();
121
122 if log_size_of_group > F::FftParameters::TWO_ADICITY {
124 return None;
125 }
126
127 let group_gen = F::get_root_of_unity(size as usize)?;
130
131 debug_assert_eq!(group_gen.pow([size]), F::one());
133
134 let size_as_field_element = F::from(size);
135 let size_inv = size_as_field_element.inverse()?;
136
137 Some(EvaluationDomain {
138 size,
139 log_size_of_group,
140 size_as_field_element,
141 size_inv,
142 group_gen,
143 group_gen_inv: group_gen.inverse()?,
144 generator_inv: F::multiplicative_generator().inverse()?,
145 })
146 }
147
148 pub fn compute_size_of_domain(num_coeffs: usize) -> Option<usize> {
151 let size = num_coeffs.checked_next_power_of_two()?;
152 if size.trailing_zeros() <= F::FftParameters::TWO_ADICITY { Some(size) } else { None }
153 }
154
155 pub fn size(&self) -> usize {
157 self.size as usize
158 }
159
160 pub fn fft<T: DomainCoeff<F>>(&self, coeffs: &[T]) -> Vec<T> {
162 let mut coeffs = coeffs.to_vec();
163 self.fft_in_place(&mut coeffs);
164 coeffs
165 }
166
167 pub fn fft_in_place<T: DomainCoeff<F>>(&self, coeffs: &mut Vec<T>) {
169 execute_with_max_available_threads(|| {
170 coeffs.resize(self.size(), T::zero());
171 self.in_order_fft_in_place(&mut *coeffs);
172 });
173 }
174
175 pub fn ifft<T: DomainCoeff<F>>(&self, evals: &[T]) -> Vec<T> {
177 let mut evals = evals.to_vec();
178 self.ifft_in_place(&mut evals);
179 evals
180 }
181
182 #[inline]
184 pub fn ifft_in_place<T: DomainCoeff<F>>(&self, evals: &mut Vec<T>) {
185 execute_with_max_available_threads(|| {
186 evals.resize(self.size(), T::zero());
187 self.in_order_ifft_in_place(&mut *evals);
188 });
189 }
190
191 pub fn coset_fft<T: DomainCoeff<F>>(&self, coeffs: &[T]) -> Vec<T> {
193 let mut coeffs = coeffs.to_vec();
194 self.coset_fft_in_place(&mut coeffs);
195 coeffs
196 }
197
198 pub fn coset_fft_in_place<T: DomainCoeff<F>>(&self, coeffs: &mut Vec<T>) {
201 execute_with_max_available_threads(|| {
202 Self::distribute_powers(coeffs, F::multiplicative_generator());
203 self.fft_in_place(coeffs);
204 });
205 }
206
207 pub fn coset_ifft<T: DomainCoeff<F>>(&self, evals: &[T]) -> Vec<T> {
209 let mut evals = evals.to_vec();
210 self.coset_ifft_in_place(&mut evals);
211 evals
212 }
213
214 pub fn coset_ifft_in_place<T: DomainCoeff<F>>(&self, evals: &mut Vec<T>) {
217 execute_with_max_available_threads(|| {
218 evals.resize(self.size(), T::zero());
219 self.in_order_coset_ifft_in_place(&mut *evals);
220 });
221 }
222
223 fn distribute_powers<T: DomainCoeff<F>>(coeffs: &mut [T], g: F) {
225 Self::distribute_powers_and_mul_by_const(coeffs, g, F::one());
226 }
227
228 #[cfg(feature = "serial")]
230 fn distribute_powers_and_mul_by_const<T: DomainCoeff<F>>(coeffs: &mut [T], g: F, c: F) {
231 let mut pow = c;
233 coeffs.iter_mut().for_each(|coeff| {
234 *coeff *= pow;
235 pow *= &g
236 })
237 }
238
239 #[cfg(not(feature = "serial"))]
241 fn distribute_powers_and_mul_by_const<T: DomainCoeff<F>>(coeffs: &mut [T], g: F, c: F) {
242 let min_parallel_chunk_size = 1024;
243 let num_cpus_available = max_available_threads();
244 let num_elem_per_thread = core::cmp::max(coeffs.len() / num_cpus_available, min_parallel_chunk_size);
245
246 cfg_chunks_mut!(coeffs, num_elem_per_thread).enumerate().for_each(|(i, chunk)| {
247 let offset = c * g.pow([(i * num_elem_per_thread) as u64]);
248 let mut pow = offset;
249 chunk.iter_mut().for_each(|coeff| {
250 *coeff *= pow;
251 pow *= &g
252 })
253 });
254 }
255
256 pub fn evaluate_all_lagrange_coefficients(&self, tau: F) -> Vec<F> {
259 let size = self.size as usize;
261 let t_size = tau.pow([self.size]);
262 let one = F::one();
263 if t_size.is_one() {
264 let mut u = vec![F::zero(); size];
265 let mut omega_i = one;
266 for x in u.iter_mut().take(size) {
267 if omega_i == tau {
268 *x = one;
269 break;
270 }
271 omega_i *= &self.group_gen;
272 }
273 u
274 } else {
275 let l0 = (t_size - one) * self.size_inv;
276 let mut u = vec![F::zero(); size];
277 let mut ls = vec![F::zero(); size];
278
279 #[cfg(not(feature = "serial"))]
286 let chunk = core::cmp::max(size / max_available_threads(), 1024);
287 #[cfg(feature = "serial")]
288 let chunk = core::cmp::max(size, 1);
289
290 let chunk_gen = self.group_gen.pow([chunk as u64]);
294 cfg_chunks_mut!(u, chunk).zip(cfg_chunks_mut!(ls, chunk)).enumerate().for_each(
295 |(i, (u_chunk, ls_chunk))| {
296 let mut r = chunk_gen.pow([i as u64]);
297 let mut l = l0 * r;
298 u_chunk.iter_mut().zip(ls_chunk.iter_mut()).for_each(|(tau_minus_r, l_i)| {
299 *tau_minus_r = tau - r;
300 *l_i = l;
301 r *= &self.group_gen;
302 l *= &self.group_gen;
303 });
304 },
305 );
306
307 batch_inversion(u.as_mut_slice());
308 cfg_iter_mut!(u, 1_000).zip_eq(ls).for_each(|(tau_minus_r, l)| {
309 *tau_minus_r = l * *tau_minus_r;
310 });
311 u
312 }
313 }
314
315 pub fn vanishing_polynomial(&self) -> SparsePolynomial<F> {
317 let coeffs = [(0, -F::one()), (self.size(), F::one())];
318 SparsePolynomial::from_coefficients(coeffs)
319 }
320
321 pub fn evaluate_vanishing_polynomial(&self, tau: F) -> F {
325 tau.pow([self.size]) - F::one()
326 }
327
328 pub fn elements(&self) -> Elements<F> {
330 Elements { cur_elem: F::one(), cur_pow: 0, domain: *self }
331 }
332
333 pub fn divide_by_vanishing_poly_on_coset_in_place(&self, evals: &mut [F]) {
337 let i = self.evaluate_vanishing_polynomial(F::multiplicative_generator()).inverse().unwrap();
338
339 cfg_iter_mut!(evals).for_each(|eval| *eval *= &i);
340 }
341
342 pub fn reindex_by_subdomain(&self, other: &Self, index: usize) -> Result<usize> {
346 ensure!(self.size() > other.size(), "other.size() must be smaller than self.size()");
347
348 let period = self.size() / other.size();
353 if index < other.size() {
354 Ok(index * period)
355 } else {
356 let i = index - other.size();
365 let x = period - 1;
366 Ok(i + (i / x) + 1)
367 }
368 }
369
370 pub fn mul_polynomials_in_evaluation_domain(&self, self_evals: Vec<F>, other_evals: &[F]) -> Result<Vec<F>> {
374 let mut result = self_evals;
375
376 ensure!(result.len() == other_evals.len());
377 cfg_iter_mut!(result).zip_eq(other_evals).for_each(|(a, b)| *a *= b);
378
379 Ok(result)
380 }
381}
382
383impl<F: FftField> EvaluationDomain<F> {
384 pub fn precompute_fft(&self) -> FFTPrecomputation<F> {
385 execute_with_max_available_threads(|| FFTPrecomputation {
386 roots: self.roots_of_unity(self.group_gen),
387 domain: *self,
388 })
389 }
390
391 pub fn precompute_ifft(&self) -> IFFTPrecomputation<F> {
392 execute_with_max_available_threads(|| IFFTPrecomputation {
393 inverse_roots: self.roots_of_unity(self.group_gen_inv),
394 domain: *self,
395 })
396 }
397
398 pub(crate) fn in_order_fft_in_place<T: DomainCoeff<F>>(&self, x_s: &mut [T]) {
399 #[cfg(all(feature = "cuda", target_arch = "x86_64"))]
400 if self.size >= 32 && std::mem::size_of::<T>() == 32 {
402 let result = snarkvm_algorithms_cuda::NTT(
403 self.size as usize,
404 x_s,
405 snarkvm_algorithms_cuda::NTTInputOutputOrder::NN,
406 snarkvm_algorithms_cuda::NTTDirection::Forward,
407 snarkvm_algorithms_cuda::NTTType::Standard,
408 );
409 if result.is_ok() {
410 return;
411 }
412 }
413
414 let pc = self.precompute_fft();
415 self.fft_helper_in_place_with_pc(x_s, FFTOrder::II, &pc)
416 }
417
418 pub fn in_order_fft_with_pc<T: DomainCoeff<F>>(&self, x_s: &[T], pc: &FFTPrecomputation<F>) -> Vec<T> {
419 let mut x_s = x_s.to_vec();
420 if self.size() != x_s.len() {
421 x_s.extend(core::iter::repeat_n(T::zero(), self.size() - x_s.len()));
422 }
423 self.fft_helper_in_place_with_pc(&mut x_s, FFTOrder::II, pc);
424 x_s
425 }
426
427 pub(crate) fn in_order_ifft_in_place<T: DomainCoeff<F>>(&self, x_s: &mut [T]) {
428 #[cfg(all(feature = "cuda", target_arch = "x86_64"))]
429 if self.size >= 32 && std::mem::size_of::<T>() == 32 {
431 let result = snarkvm_algorithms_cuda::NTT(
432 self.size as usize,
433 x_s,
434 snarkvm_algorithms_cuda::NTTInputOutputOrder::NN,
435 snarkvm_algorithms_cuda::NTTDirection::Inverse,
436 snarkvm_algorithms_cuda::NTTType::Standard,
437 );
438 if result.is_ok() {
439 return;
440 }
441 }
442
443 let pc = self.precompute_ifft();
444 self.ifft_helper_in_place_with_pc(x_s, FFTOrder::II, &pc);
445 cfg_iter_mut!(x_s, 1_000).for_each(|val| *val *= self.size_inv);
446 }
447
448 pub(crate) fn in_order_coset_ifft_in_place<T: DomainCoeff<F>>(&self, x_s: &mut [T]) {
449 #[cfg(all(feature = "cuda", target_arch = "x86_64"))]
450 if self.size >= 32 && std::mem::size_of::<T>() == 32 {
452 let result = snarkvm_algorithms_cuda::NTT(
453 self.size as usize,
454 x_s,
455 snarkvm_algorithms_cuda::NTTInputOutputOrder::NN,
456 snarkvm_algorithms_cuda::NTTDirection::Inverse,
457 snarkvm_algorithms_cuda::NTTType::Coset,
458 );
459 if result.is_ok() {
460 return;
461 }
462 }
463
464 let pc = self.precompute_ifft();
465 self.ifft_helper_in_place_with_pc(x_s, FFTOrder::II, &pc);
466 let coset_shift = self.generator_inv;
467 Self::distribute_powers_and_mul_by_const(x_s, coset_shift, self.size_inv);
468 }
469
470 #[allow(unused)]
471 pub(crate) fn in_order_fft_in_place_with_pc<T: DomainCoeff<F>>(
472 &self,
473 x_s: &mut [T],
474 pre_comp: &FFTPrecomputation<F>,
475 ) {
476 #[cfg(all(feature = "cuda", target_arch = "x86_64"))]
477 if self.size >= 32 && std::mem::size_of::<T>() == 32 {
479 let result = snarkvm_algorithms_cuda::NTT(
480 self.size as usize,
481 x_s,
482 snarkvm_algorithms_cuda::NTTInputOutputOrder::NN,
483 snarkvm_algorithms_cuda::NTTDirection::Forward,
484 snarkvm_algorithms_cuda::NTTType::Standard,
485 );
486 if result.is_ok() {
487 return;
488 }
489 }
490
491 self.fft_helper_in_place_with_pc(x_s, FFTOrder::II, pre_comp)
492 }
493
494 pub(crate) fn out_order_fft_in_place_with_pc<T: DomainCoeff<F>>(
495 &self,
496 x_s: &mut [T],
497 pre_comp: &FFTPrecomputation<F>,
498 ) {
499 self.fft_helper_in_place_with_pc(x_s, FFTOrder::IO, pre_comp)
500 }
501
502 pub(crate) fn in_order_ifft_in_place_with_pc<T: DomainCoeff<F>>(
503 &self,
504 x_s: &mut [T],
505 pre_comp: &IFFTPrecomputation<F>,
506 ) {
507 #[cfg(all(feature = "cuda", target_arch = "x86_64"))]
508 if self.size >= 32 && std::mem::size_of::<T>() == 32 {
510 let result = snarkvm_algorithms_cuda::NTT(
511 self.size as usize,
512 x_s,
513 snarkvm_algorithms_cuda::NTTInputOutputOrder::NN,
514 snarkvm_algorithms_cuda::NTTDirection::Inverse,
515 snarkvm_algorithms_cuda::NTTType::Standard,
516 );
517 if result.is_ok() {
518 return;
519 }
520 }
521
522 self.ifft_helper_in_place_with_pc(x_s, FFTOrder::II, pre_comp);
523 cfg_iter_mut!(x_s, 1_000).for_each(|val| *val *= self.size_inv);
524 }
525
526 pub(crate) fn out_order_ifft_in_place_with_pc<T: DomainCoeff<F>>(
527 &self,
528 x_s: &mut [T],
529 pre_comp: &IFFTPrecomputation<F>,
530 ) {
531 self.ifft_helper_in_place_with_pc(x_s, FFTOrder::OI, pre_comp);
532 cfg_iter_mut!(x_s, 1_000).for_each(|val| *val *= self.size_inv);
533 }
534
535 #[allow(unused)]
536 pub(crate) fn in_order_coset_ifft_in_place_with_pc<T: DomainCoeff<F>>(
537 &self,
538 x_s: &mut [T],
539 pre_comp: &IFFTPrecomputation<F>,
540 ) {
541 #[cfg(all(feature = "cuda", target_arch = "x86_64"))]
542 if self.size >= 32 && std::mem::size_of::<T>() == 32 {
544 let result = snarkvm_algorithms_cuda::NTT(
545 self.size as usize,
546 x_s,
547 snarkvm_algorithms_cuda::NTTInputOutputOrder::NN,
548 snarkvm_algorithms_cuda::NTTDirection::Inverse,
549 snarkvm_algorithms_cuda::NTTType::Coset,
550 );
551 if result.is_ok() {
552 return;
553 }
554 }
555
556 self.ifft_helper_in_place_with_pc(x_s, FFTOrder::II, pre_comp);
557 let coset_shift = self.generator_inv;
558 Self::distribute_powers_and_mul_by_const(x_s, coset_shift, self.size_inv);
559 }
560
561 fn fft_helper_in_place_with_pc<T: DomainCoeff<F>>(
562 &self,
563 x_s: &mut [T],
564 ord: FFTOrder,
565 pre_comp: &FFTPrecomputation<F>,
566 ) {
567 use FFTOrder::*;
568 let pc = pre_comp.precomputation_for_subdomain(self).unwrap();
569
570 let log_len = log2(x_s.len());
571
572 if ord == OI {
573 self.oi_helper_with_roots(x_s, &pc.roots);
574 } else {
575 self.io_helper_with_roots(x_s, &pc.roots);
576 }
577
578 if ord == II {
579 derange_helper(x_s, log_len);
580 }
581 }
582
583 fn ifft_helper_in_place_with_pc<T: DomainCoeff<F>>(
587 &self,
588 x_s: &mut [T],
589 ord: FFTOrder,
590 pre_comp: &IFFTPrecomputation<F>,
591 ) {
592 use FFTOrder::*;
593 let pc = pre_comp.precomputation_for_subdomain(self).unwrap();
594
595 let log_len = log2(x_s.len());
596
597 if ord == II {
598 derange_helper(x_s, log_len);
599 }
600
601 if ord == IO {
602 self.io_helper_with_roots(x_s, &pc.inverse_roots);
603 } else {
604 self.oi_helper_with_roots(x_s, &pc.inverse_roots);
605 }
606 }
607
608 #[cfg(feature = "serial")]
612 pub fn roots_of_unity(&self, root: F) -> Vec<F> {
613 compute_powers_serial((self.size as usize) / 2, root)
614 }
615
616 #[cfg(not(feature = "serial"))]
618 pub fn roots_of_unity(&self, root: F) -> Vec<F> {
619 let log_size = log2(self.size as usize);
621 if log_size <= LOG_ROOTS_OF_UNITY_PARALLEL_SIZE {
623 compute_powers_serial((self.size as usize) / 2, root)
624 } else {
625 let mut temp = root;
626 let log_powers: Vec<F> = (0..(log_size - 1))
628 .map(|_| {
629 let old_value = temp;
630 temp.square_in_place();
631 old_value
632 })
633 .collect();
634
635 let mut powers = vec![F::zero(); 1 << (log_size - 1)];
637 Self::roots_of_unity_recursive(&mut powers, &log_powers);
638 powers
639 }
640 }
641
642 #[cfg(not(feature = "serial"))]
643 fn roots_of_unity_recursive(out: &mut [F], log_powers: &[F]) {
644 assert_eq!(out.len(), 1 << log_powers.len());
645 if log_powers.len() <= LOG_ROOTS_OF_UNITY_PARALLEL_SIZE as usize {
648 out[0] = F::one();
649 for idx in 1..out.len() {
650 out[idx] = out[idx - 1] * log_powers[0];
651 }
652 return;
653 }
654
655 let (lr_lo, lr_hi) = log_powers.split_at(log_powers.len().div_ceil(2));
658 let mut scr_lo = vec![F::default(); 1 << lr_lo.len()];
659 let mut scr_hi = vec![F::default(); 1 << lr_hi.len()];
660 rayon::join(
662 || Self::roots_of_unity_recursive(&mut scr_lo, lr_lo),
663 || Self::roots_of_unity_recursive(&mut scr_hi, lr_hi),
664 );
665 out.par_chunks_mut(scr_lo.len()).zip(&scr_hi).for_each(|(out_chunk, scr_hi)| {
668 for (out_elem, scr_lo) in out_chunk.iter_mut().zip(&scr_lo) {
669 *out_elem = *scr_hi * scr_lo;
670 }
671 });
672 }
673
674 #[inline(always)]
675 fn butterfly_fn_io<T: DomainCoeff<F>>(((lo, hi), root): ((&mut T, &mut T), &F)) {
676 let neg = *lo - *hi;
677 *lo += *hi;
678 *hi = neg;
679 *hi *= *root;
680 }
681
682 #[inline(always)]
683 fn butterfly_fn_oi<T: DomainCoeff<F>>(((lo, hi), root): ((&mut T, &mut T), &F)) {
684 *hi *= *root;
685 let neg = *lo - *hi;
686 *lo += *hi;
687 *hi = neg;
688 }
689
690 #[allow(clippy::too_many_arguments)]
691 fn apply_butterfly<T: DomainCoeff<F>, G: Fn(((&mut T, &mut T), &F)) + Copy + Sync + Send>(
692 g: G,
693 xi: &mut [T],
694 roots: &[F],
695 step: usize,
696 chunk_size: usize,
697 num_chunks: usize,
698 max_threads: usize,
699 gap: usize,
700 ) {
701 cfg_chunks_mut!(xi, chunk_size).for_each(|cxi| {
702 let (lo, hi) = cxi.split_at_mut(gap);
703 if gap > MIN_GAP_SIZE_FOR_PARALLELISATION && num_chunks < max_threads {
707 cfg_iter_mut!(lo, 1000).zip(hi).zip(cfg_iter!(roots, 1000).step_by(step)).for_each(g);
708 } else {
709 lo.iter_mut().zip(hi).zip(roots.iter().step_by(step)).for_each(g);
710 }
711 });
712 }
713
714 #[allow(clippy::unnecessary_to_owned)]
715 fn io_helper_with_roots<T: DomainCoeff<F>>(&self, xi: &mut [T], roots: &[F]) {
716 let mut roots = std::borrow::Cow::Borrowed(roots);
717
718 let mut step = 1;
719 let mut first = true;
720
721 #[cfg(not(feature = "serial"))]
722 let max_threads = snarkvm_utilities::parallel::max_available_threads();
723 #[cfg(feature = "serial")]
724 let max_threads = 1;
725
726 let mut gap = xi.len() / 2;
727 while gap > 0 {
728 let chunk_size = 2 * gap;
730 let num_chunks = xi.len() / chunk_size;
731
732 if num_chunks >= MIN_NUM_CHUNKS_FOR_COMPACTION {
736 if !first {
737 roots = Cow::Owned(cfg_into_iter!(roots.into_owned()).step_by(step * 2).collect());
738 }
739 step = 1;
740 roots.to_mut().shrink_to_fit();
741 } else {
742 step = num_chunks;
743 }
744 first = false;
745
746 Self::apply_butterfly(
747 Self::butterfly_fn_io,
748 xi,
749 &roots[..],
750 step,
751 chunk_size,
752 num_chunks,
753 max_threads,
754 gap,
755 );
756
757 gap /= 2;
758 }
759 }
760
761 fn oi_helper_with_roots<T: DomainCoeff<F>>(&self, xi: &mut [T], roots_cache: &[F]) {
762 let compaction_max_size =
767 core::cmp::min(roots_cache.len() / 2, roots_cache.len() / MIN_NUM_CHUNKS_FOR_COMPACTION);
768 let mut compacted_roots = vec![F::default(); compaction_max_size];
769
770 #[cfg(not(feature = "serial"))]
771 let max_threads = snarkvm_utilities::parallel::max_available_threads();
772 #[cfg(feature = "serial")]
773 let max_threads = 1;
774
775 let mut gap = 1;
776 while gap < xi.len() {
777 let chunk_size = 2 * gap;
779 let num_chunks = xi.len() / chunk_size;
780
781 let (roots, step) = if num_chunks >= MIN_NUM_CHUNKS_FOR_COMPACTION && gap < xi.len() / 2 {
785 cfg_iter_mut!(compacted_roots[..gap], 1_000)
786 .zip(cfg_iter!(roots_cache[..(gap * num_chunks)], 1_000).step_by(num_chunks))
787 .for_each(|(a, b)| *a = *b);
788 (&compacted_roots[..gap], 1)
789 } else {
790 (roots_cache, num_chunks)
791 };
792
793 Self::apply_butterfly(Self::butterfly_fn_oi, xi, roots, step, chunk_size, num_chunks, max_threads, gap);
794
795 gap *= 2;
796 }
797 }
798}
799
800const MIN_NUM_CHUNKS_FOR_COMPACTION: usize = 1 << 7;
803
804const MIN_GAP_SIZE_FOR_PARALLELISATION: usize = 1 << 10;
807
808#[cfg(not(feature = "serial"))]
810const LOG_ROOTS_OF_UNITY_PARALLEL_SIZE: u32 = 7;
811
812#[inline]
813pub(super) fn bitrev(a: u64, log_len: u32) -> u64 {
814 a.reverse_bits() >> (64 - log_len)
815}
816
817pub(crate) fn derange<T>(xi: &mut [T]) {
818 derange_helper(xi, log2(xi.len()))
819}
820
821fn derange_helper<T>(xi: &mut [T], log_len: u32) {
822 for idx in 1..(xi.len() as u64 - 1) {
823 let ridx = bitrev(idx, log_len);
824 if idx < ridx {
825 xi.swap(idx as usize, ridx as usize);
826 }
827 }
828}
829
830#[derive(PartialEq, Eq, Debug)]
831enum FFTOrder {
832 II,
834 IO,
837 OI,
840}
841
842pub(crate) fn compute_powers_serial<F: Field>(size: usize, root: F) -> Vec<F> {
843 compute_powers_and_mul_by_const_serial(size, root, F::one())
844}
845
846pub(crate) fn compute_powers_and_mul_by_const_serial<F: Field>(size: usize, root: F, c: F) -> Vec<F> {
847 let mut value = c;
848 (0..size)
849 .map(|_| {
850 let old_value = value;
851 value *= root;
852 old_value
853 })
854 .collect()
855}
856
857#[allow(unused)]
858#[cfg(not(feature = "serial"))]
859pub(crate) fn compute_powers<F: Field>(size: usize, g: F) -> Vec<F> {
860 if size < MIN_PARALLEL_CHUNK_SIZE {
861 return compute_powers_serial(size, g);
862 }
863 let num_cpus_available = max_available_threads();
865 let num_elem_per_thread = core::cmp::max(size / num_cpus_available, MIN_PARALLEL_CHUNK_SIZE);
866 let num_cpus_used = size / num_elem_per_thread;
867
868 let res: Vec<F> = (0..num_cpus_used)
870 .into_par_iter()
871 .flat_map(|i| {
872 let offset = g.pow([(i * num_elem_per_thread) as u64]);
873 let num_elements_to_compute = core::cmp::min(size - i * num_elem_per_thread, num_elem_per_thread);
877 compute_powers_and_mul_by_const_serial(num_elements_to_compute, g, offset)
878 })
879 .collect();
880 res
881}
882
883#[derive(Clone)]
885pub struct Elements<F: FftField> {
886 cur_elem: F,
887 cur_pow: u64,
888 domain: EvaluationDomain<F>,
889}
890
891impl<F: FftField> Iterator for Elements<F> {
892 type Item = F;
893
894 fn next(&mut self) -> Option<F> {
895 if self.cur_pow == self.domain.size {
896 None
897 } else {
898 let cur_elem = self.cur_elem;
899 self.cur_elem *= &self.domain.group_gen;
900 self.cur_pow += 1;
901 Some(cur_elem)
902 }
903 }
904}
905
906#[derive(Clone, Eq, PartialEq, Debug, CanonicalDeserialize, CanonicalSerialize)]
908pub struct FFTPrecomputation<F: FftField> {
909 roots: Vec<F>,
910 domain: EvaluationDomain<F>,
911}
912
913impl<F: FftField> FFTPrecomputation<F> {
914 pub fn to_ifft_precomputation(&self) -> IFFTPrecomputation<F> {
915 let mut inverse_roots = self.roots.clone();
916 snarkvm_fields::batch_inversion(&mut inverse_roots);
917 IFFTPrecomputation { inverse_roots, domain: self.domain }
918 }
919
920 pub fn precomputation_for_subdomain<'a>(&'a self, domain: &EvaluationDomain<F>) -> Option<Cow<'a, Self>> {
921 if domain.size() == 1 {
922 return Some(Cow::Owned(Self { roots: vec![], domain: *domain }));
923 }
924 if &self.domain == domain {
925 Some(Cow::Borrowed(self))
926 } else if domain.size() < self.domain.size() {
927 let size_ratio = self.domain.size() / domain.size();
928 let roots = self.roots.iter().step_by(size_ratio).copied().collect();
929 Some(Cow::Owned(Self { roots, domain: *domain }))
930 } else {
931 None
932 }
933 }
934}
935
936#[derive(Clone, Eq, PartialEq, Debug, CanonicalSerialize, CanonicalDeserialize)]
938pub struct IFFTPrecomputation<F: FftField> {
939 inverse_roots: Vec<F>,
940 domain: EvaluationDomain<F>,
941}
942
943impl<F: FftField> IFFTPrecomputation<F> {
944 pub fn precomputation_for_subdomain<'a>(&'a self, domain: &EvaluationDomain<F>) -> Option<Cow<'a, Self>> {
945 if domain.size() == 1 {
946 return Some(Cow::Owned(Self { inverse_roots: vec![], domain: *domain }));
947 }
948 if &self.domain == domain {
949 Some(Cow::Borrowed(self))
950 } else if domain.size() < self.domain.size() {
951 let size_ratio = self.domain.size() / domain.size();
952 let inverse_roots = self.inverse_roots.iter().step_by(size_ratio).copied().collect();
953 Some(Cow::Owned(Self { inverse_roots, domain: *domain }))
954 } else {
955 None
956 }
957 }
958}
959
960#[cfg(test)]
961mod tests {
962 #[cfg(all(feature = "cuda", target_arch = "x86_64"))]
963 use crate::fft::domain::FFTOrder;
964 use crate::fft::{DensePolynomial, EvaluationDomain};
965 use rand::RngExt;
966 use snarkvm_curves::bls12_377::Fr;
967 use snarkvm_fields::{FftField, Field, One, Zero};
968 use snarkvm_utilities::{TestRng, Uniform};
969
970 #[test]
971 fn vanishing_polynomial_evaluation() {
972 let rng = &mut TestRng::default();
973 for coeffs in 0..10 {
974 let domain = EvaluationDomain::<Fr>::new(coeffs).unwrap();
975 let z = domain.vanishing_polynomial();
976 for _ in 0..100 {
977 let point = rng.random();
978 assert_eq!(z.evaluate(point), domain.evaluate_vanishing_polynomial(point))
979 }
980 }
981 }
982
983 #[test]
984 fn vanishing_polynomial_vanishes_on_domain() {
985 for coeffs in 0..1000 {
986 let domain = EvaluationDomain::<Fr>::new(coeffs).unwrap();
987 let z = domain.vanishing_polynomial();
988 for point in domain.elements() {
989 assert!(z.evaluate(point).is_zero())
990 }
991 }
992 }
993
994 #[test]
995 fn size_of_elements() {
996 for coeffs in 1..10 {
997 let size = 1 << coeffs;
998 let domain = EvaluationDomain::<Fr>::new(size).unwrap();
999 let domain_size = domain.size();
1000 assert_eq!(domain_size, domain.elements().count());
1001 }
1002 }
1003
1004 #[test]
1005 fn elements_contents() {
1006 for coeffs in 1..10 {
1007 let size = 1 << coeffs;
1008 let domain = EvaluationDomain::<Fr>::new(size).unwrap();
1009 for (i, element) in domain.elements().enumerate() {
1010 assert_eq!(element, domain.group_gen.pow([i as u64]));
1011 }
1012 }
1013 }
1014
1015 #[test]
1018 fn non_systematic_lagrange_coefficients_test() {
1019 let mut rng = TestRng::default();
1020 for domain_dimension in 1..10 {
1021 let domain_size = 1 << domain_dimension;
1022 let domain = EvaluationDomain::<Fr>::new(domain_size).unwrap();
1023 let random_point = Fr::rand(&mut rng);
1025 let lagrange_coefficients = domain.evaluate_all_lagrange_coefficients(random_point);
1026
1027 let random_polynomial = DensePolynomial::<Fr>::rand(domain_size - 1, &mut rng);
1030 let polynomial_evaluations = domain.fft(random_polynomial.coeffs());
1031 let actual_evaluations = random_polynomial.evaluate(random_point);
1032
1033 let mut interpolated_evaluation = Fr::zero();
1035 for i in 0..domain_size {
1036 interpolated_evaluation += lagrange_coefficients[i] * polynomial_evaluations[i];
1037 }
1038 assert_eq!(actual_evaluations, interpolated_evaluation);
1039 }
1040 }
1041
1042 #[test]
1044 fn systematic_lagrange_coefficients_test() {
1045 for domain_dimension in 1..5 {
1049 let domain_size = 1 << domain_dimension;
1050 let domain = EvaluationDomain::<Fr>::new(domain_size).unwrap();
1051 let all_domain_elements: Vec<Fr> = domain.elements().collect();
1052 for (i, domain_element) in all_domain_elements.iter().enumerate().take(domain_size) {
1053 let lagrange_coefficients = domain.evaluate_all_lagrange_coefficients(*domain_element);
1054 for (j, lagrange_coefficient) in lagrange_coefficients.iter().enumerate().take(domain_size) {
1055 if i == j {
1057 assert_eq!(*lagrange_coefficient, Fr::one());
1058 } else {
1059 assert_eq!(*lagrange_coefficient, Fr::zero());
1060 }
1061 }
1062 }
1063 }
1064 }
1065
1066 #[test]
1068 fn test_roots_of_unity() {
1069 let max_degree = 10;
1070 for log_domain_size in 0..max_degree {
1071 let domain_size = 1 << log_domain_size;
1072 let domain = EvaluationDomain::<Fr>::new(domain_size).unwrap();
1073 let actual_roots = domain.roots_of_unity(domain.group_gen);
1074 for &value in &actual_roots {
1075 assert!(domain.evaluate_vanishing_polynomial(value).is_zero());
1076 }
1077 let expected_roots_elements = domain.elements();
1078 for (expected, &actual) in expected_roots_elements.zip(&actual_roots) {
1079 assert_eq!(expected, actual);
1080 }
1081 assert_eq!(actual_roots.len(), domain_size / 2);
1082 }
1083 }
1084
1085 #[test]
1087 fn test_fft_correctness() {
1088 let mut rng = TestRng::default();
1093
1094 let log_degree = 5;
1096 let degree = 1 << log_degree;
1097 let random_polynomial = DensePolynomial::<Fr>::rand(degree - 1, &mut rng);
1098
1099 for log_domain_size in log_degree..(log_degree + 2) {
1100 let domain_size = 1 << log_domain_size;
1101 let domain = EvaluationDomain::<Fr>::new(domain_size).unwrap();
1102 let polynomial_evaluations = domain.fft(&random_polynomial.coeffs);
1103 let polynomial_coset_evaluations = domain.coset_fft(&random_polynomial.coeffs);
1104 for (i, x) in domain.elements().enumerate() {
1105 let coset_x = Fr::multiplicative_generator() * x;
1106
1107 assert_eq!(polynomial_evaluations[i], random_polynomial.evaluate(x));
1108 assert_eq!(polynomial_coset_evaluations[i], random_polynomial.evaluate(coset_x));
1109 }
1110
1111 let randon_polynomial_from_subgroup =
1112 DensePolynomial::from_coefficients_vec(domain.ifft(&polynomial_evaluations));
1113 let random_polynomial_from_coset =
1114 DensePolynomial::from_coefficients_vec(domain.coset_ifft(&polynomial_coset_evaluations));
1115
1116 assert_eq!(
1117 random_polynomial, randon_polynomial_from_subgroup,
1118 "degree = {degree}, domain size = {domain_size}"
1119 );
1120 assert_eq!(
1121 random_polynomial, random_polynomial_from_coset,
1122 "degree = {degree}, domain size = {domain_size}"
1123 );
1124 }
1125 }
1126
1127 #[test]
1129 fn test_fft_precomputation() {
1130 for i in 1..10 {
1131 let big_domain = EvaluationDomain::<Fr>::new(i).unwrap();
1132 let pc = big_domain.precompute_fft();
1133 for j in 1..i {
1134 let small_domain = EvaluationDomain::<Fr>::new(j).unwrap();
1135 let small_pc = small_domain.precompute_fft();
1136 assert_eq!(pc.precomputation_for_subdomain(&small_domain).unwrap().as_ref(), &small_pc);
1137 }
1138 }
1139 }
1140
1141 #[test]
1143 fn test_ifft_precomputation() {
1144 for i in 1..10 {
1145 let big_domain = EvaluationDomain::<Fr>::new(i).unwrap();
1146 let pc = big_domain.precompute_ifft();
1147 for j in 1..i {
1148 let small_domain = EvaluationDomain::<Fr>::new(j).unwrap();
1149 let small_pc = small_domain.precompute_ifft();
1150 assert_eq!(pc.precomputation_for_subdomain(&small_domain).unwrap().as_ref(), &small_pc);
1151 }
1152 }
1153 }
1154
1155 #[test]
1158 fn test_ifft_precomputation_from_fft() {
1159 for i in 1..10 {
1160 let domain = EvaluationDomain::<Fr>::new(i).unwrap();
1161 let pc = domain.precompute_ifft();
1162 let fft_pc = domain.precompute_fft();
1163 assert_eq!(pc, fft_pc.to_ifft_precomputation())
1164 }
1165 }
1166
1167 #[cfg(all(feature = "cuda", target_arch = "x86_64"))]
1169 #[test]
1170 fn test_fft_correctness_cuda() {
1171 let mut rng = TestRng::default();
1172 for log_domain in 2..20 {
1173 println!("Testing domain size {log_domain}");
1174 let domain_size = 1 << log_domain;
1175 let random_polynomial = DensePolynomial::<Fr>::rand(domain_size - 1, &mut rng);
1176 let mut polynomial_evaluations = random_polynomial.coeffs.clone();
1177 let mut polynomial_evaluations_cuda = random_polynomial.coeffs.clone();
1178
1179 let domain = EvaluationDomain::<Fr>::new(domain_size).unwrap();
1180 let pc = domain.precompute_fft();
1181 domain.fft_helper_in_place_with_pc(&mut polynomial_evaluations, FFTOrder::II, &pc);
1182
1183 if snarkvm_algorithms_cuda::NTT::<Fr>(
1184 domain_size,
1185 &mut polynomial_evaluations_cuda,
1186 snarkvm_algorithms_cuda::NTTInputOutputOrder::NN,
1187 snarkvm_algorithms_cuda::NTTDirection::Forward,
1188 snarkvm_algorithms_cuda::NTTType::Standard,
1189 )
1190 .is_err()
1191 {
1192 println!("cuda error!");
1193 }
1194
1195 assert_eq!(polynomial_evaluations, polynomial_evaluations_cuda, "domain size = {domain_size}");
1196
1197 if snarkvm_algorithms_cuda::NTT::<Fr>(
1199 domain_size,
1200 &mut polynomial_evaluations_cuda,
1201 snarkvm_algorithms_cuda::NTTInputOutputOrder::NN,
1202 snarkvm_algorithms_cuda::NTTDirection::Inverse,
1203 snarkvm_algorithms_cuda::NTTType::Standard,
1204 )
1205 .is_err()
1206 {
1207 println!("cuda error!");
1208 }
1209 assert_eq!(random_polynomial.coeffs, polynomial_evaluations_cuda, "domain size = {domain_size}");
1210
1211 polynomial_evaluations = random_polynomial.coeffs.clone();
1213 let domain = EvaluationDomain::<Fr>::new(domain_size).unwrap();
1214 let pc = domain.precompute_fft();
1215 EvaluationDomain::<Fr>::distribute_powers(&mut polynomial_evaluations, Fr::multiplicative_generator());
1216 domain.fft_helper_in_place_with_pc(&mut polynomial_evaluations, FFTOrder::II, &pc);
1217
1218 if snarkvm_algorithms_cuda::NTT::<Fr>(
1219 domain_size,
1220 &mut polynomial_evaluations_cuda,
1221 snarkvm_algorithms_cuda::NTTInputOutputOrder::NN,
1222 snarkvm_algorithms_cuda::NTTDirection::Forward,
1223 snarkvm_algorithms_cuda::NTTType::Coset,
1224 )
1225 .is_err()
1226 {
1227 println!("cuda error!");
1228 }
1229
1230 assert_eq!(polynomial_evaluations, polynomial_evaluations_cuda, "domain size = {domain_size}");
1231
1232 if snarkvm_algorithms_cuda::NTT::<Fr>(
1234 domain_size,
1235 &mut polynomial_evaluations_cuda,
1236 snarkvm_algorithms_cuda::NTTInputOutputOrder::NN,
1237 snarkvm_algorithms_cuda::NTTDirection::Inverse,
1238 snarkvm_algorithms_cuda::NTTType::Coset,
1239 )
1240 .is_err()
1241 {
1242 println!("cuda error!");
1243 }
1244 assert_eq!(random_polynomial.coeffs, polynomial_evaluations_cuda, "domain size = {domain_size}");
1245 }
1246 }
1247
1248 #[test]
1249 fn test_reindex_by_subdomain_is_a_permutation() {
1250 for (big, small) in [(8usize, 2usize), (16, 4), (32, 8), (64, 2), (128, 32), (4, 2), (8, 1)] {
1252 let g = EvaluationDomain::<Fr>::new(big).unwrap();
1253 let s = EvaluationDomain::<Fr>::new(small).unwrap();
1254 let mut image = (0..g.size()).map(|i| g.reindex_by_subdomain(&s, i).unwrap()).collect::<Vec<_>>();
1255 for (i, j) in image.iter().enumerate().take(s.size()) {
1257 assert_eq!(*j, i * (big / small), "big={big} small={small} i={i}");
1258 }
1259 image.sort_unstable();
1260 assert_eq!(image, (0..g.size()).collect::<Vec<_>>(), "big={big} small={small} not a permutation");
1261 }
1262 }
1263}