winter_math/fft/mod.rs
1// Copyright (c) Facebook, Inc. and its affiliates.
2//
3// This source code is licensed under the MIT license found in the
4// LICENSE file in the root directory of this source tree.
5
6//! FFT-based polynomial evaluation and interpolation.
7//!
8//! Functions in this module can be used to evaluate and interpolate polynomials over domains
9//! which are multiplicative subgroups of finite fields and have lengths equal to powers of two.
10//! As compared to evaluation and interpolation functions available in the `polynom` module,
11//! these functions are much more efficient: their runtime complexity is O(`n` log `n`), where
12//! `n` is the domain size.
13
14use alloc::vec::Vec;
15
16use crate::{
17 fft::fft_inputs::FftInputs,
18 field::{FieldElement, StarkField},
19 utils::get_power_series,
20};
21
22pub mod fft_inputs;
23pub mod real_u64;
24mod serial;
25
26#[cfg(feature = "concurrent")]
27mod concurrent;
28
29#[cfg(test)]
30mod tests;
31
32// CONSTANTS
33// ================================================================================================
34const MIN_CONCURRENT_SIZE: usize = 1024;
35
36// POLYNOMIAL EVALUATION
37// ================================================================================================
38
39/// Evaluates a polynomial on all points of the specified domain using the FFT algorithm.
40///
41/// Uses the [FFT](https://en.wikipedia.org/wiki/Discrete_Fourier_transform_(general)) algorithm
42/// to evaluate polynomial `p` on all points of a domain defined by the length of `p` in the field
43/// specified by the `B` type parameter. The evaluation is done in-place, meaning no additional
44/// memory is allocated and `p` is updated with results of the evaluation. The polynomial `p`
45/// is expected to be in coefficient form.
46///
47/// The complexity of evaluation is O(`n` log(`n`)), where `n` is the size of the domain.
48///
49/// The size of the domain is assumed to be equal to `p.len()` which must be a power of two. The
50/// base field specified by `B` must have a multiplicative subgroup of size equal to `p.len()`.
51///
52/// The `twiddles` needed for evaluation can be obtained via `fft::get_twiddles()` function using
53/// `p.len()` as the domain size parameter. This implies that `twiddles.len()` must be equal to
54/// `p.len()` / 2.
55///
56/// When `concurrent` feature is enabled, the evaluation is done in multiple threads.
57///
58/// # Panics
59/// Panics if:
60/// * Length of `p` is not a power of two.
61/// * Length of `twiddles` is not `p.len()` / 2.
62/// * Field specified by `B` does not contain a multiplicative subgroup of size `p.len()`.
63///
64/// # Examples
65/// ```
66/// # use winter_math::{polynom, fft::*, get_power_series};
67/// # use winter_math::{fields::{f128::BaseElement}, FieldElement, StarkField};
68/// # use rand_utils::rand_vector;
69/// let n = 2048;
70///
71/// // build a random polynomial
72/// let mut p: Vec<BaseElement> = rand_vector(n);
73///
74/// // evaluate the polynomial over the domain using regular polynomial evaluation
75/// let g = BaseElement::get_root_of_unity(n.ilog2());
76/// let domain = get_power_series(g, n);
77/// let expected = polynom::eval_many(&p, &domain);
78///
79/// // evaluate the polynomial over the domain using FFT-based evaluation
80/// let twiddles = get_twiddles::<BaseElement>(p.len());
81/// evaluate_poly(&mut p, &twiddles);
82///
83/// assert_eq!(expected, p);
84/// ```
85pub fn evaluate_poly<B, E>(p: &mut [E], twiddles: &[B])
86where
87 B: StarkField,
88 E: FieldElement<BaseField = B>,
89{
90 assert!(p.len().is_power_of_two(), "number of coefficients must be a power of 2");
91 assert_eq!(
92 p.len(),
93 twiddles.len() * 2,
94 "invalid number of twiddles: expected {} but received {}",
95 p.len() / 2,
96 twiddles.len()
97 );
98 assert!(
99 p.len().ilog2() <= B::TWO_ADICITY,
100 "multiplicative subgroup of size {} does not exist in the specified base field",
101 p.len()
102 );
103
104 // when `concurrent` feature is enabled, run the concurrent version of the function; unless
105 // the polynomial is small, then don't bother with the concurrent version
106 if cfg!(feature = "concurrent") && p.len() >= MIN_CONCURRENT_SIZE {
107 #[cfg(feature = "concurrent")]
108 concurrent::evaluate_poly(p, twiddles);
109 } else {
110 serial::evaluate_poly(p, twiddles);
111 }
112}
113
114/// Evaluates a polynomial on all points of the specified (shifted) domain using the FFT algorithm.
115///
116/// Uses the [FFT](https://en.wikipedia.org/wiki/Discrete_Fourier_transform_(general)) algorithm
117/// to evaluate polynomial `p` on all points of a domain defined by the length of `p`, expanded
118/// by the `blowup_factor`, and shifted by the `domain_offset` in the field specified by the `B`
119/// type parameter. The polynomial `p` is expected to be in coefficient form.
120///
121/// The complexity of evaluation is O(`n` log(`n`)), where `n` is the size of the domain.
122///
123/// The size of the domain is assumed to be equal to `p.len()` * `blowup_factor` both of which must
124/// be powers of two. The base field specified by `B` must have a multiplicative subgroup of size
125/// equal to `p.len()` * `blowup_factor`.
126///
127/// The shifted domain is defined as the original domain with every element multiplied by the
128/// `domain_offset`.
129///
130/// The `twiddles` needed for evaluation can be obtained via `fft::get_twiddles()` function using
131/// `p.len()` as the domain size parameter. This implies that `twiddles.len()` must be equal to
132/// `p.len()` / 2.
133///
134/// When `concurrent` feature is enabled, the evaluation is done in multiple threads.
135///
136/// # Panics
137/// Panics if:
138/// * Length of `p` is not a power of two.
139/// * `blowup_factor` is not a power of two.
140/// * Length of `twiddles` is not `p.len()` / 2.
141/// * Field specified by `B` does not contain a multiplicative subgroup of size `p.len()`.
142/// * `domain_offset` is ZERO.
143///
144/// # Examples
145/// ```
146/// # use winter_math::{polynom, fft::*, get_power_series};
147/// # use winter_math::{fields::{f128::BaseElement}, FieldElement, StarkField};
148/// # use rand_utils::rand_vector;
149/// let n = 2048;
150/// let offset = BaseElement::GENERATOR;
151/// let blowup_factor = 2;
152///
153/// // build a random polynomial
154/// let mut p: Vec<BaseElement> = rand_vector(n / blowup_factor);
155///
156/// // evaluate the polynomial over the domain using regular polynomial evaluation
157/// let g = BaseElement::get_root_of_unity(n.ilog2());
158/// let domain = get_power_series(g, n);
159/// let shifted_domain = domain.iter().map(|&x| x * offset).collect::<Vec<_>>();
160/// let expected = polynom::eval_many(&p, &shifted_domain);
161///
162/// // evaluate the polynomial over the domain using FFT-based evaluation
163/// let twiddles = get_twiddles::<BaseElement>(p.len());
164/// let actual = evaluate_poly_with_offset(&mut p, &twiddles, offset, blowup_factor);
165///
166/// assert_eq!(expected, actual);
167/// ```
168pub fn evaluate_poly_with_offset<B, E>(
169 p: &[E],
170 twiddles: &[B],
171 domain_offset: B,
172 blowup_factor: usize,
173) -> Vec<E>
174where
175 B: StarkField,
176 E: FieldElement<BaseField = B>,
177{
178 assert!(p.len().is_power_of_two(), "number of coefficients must be a power of 2");
179 assert!(blowup_factor.is_power_of_two(), "blowup factor must be a power of 2");
180 assert_eq!(
181 p.len(),
182 twiddles.len() * 2,
183 "invalid number of twiddles: expected {} but received {}",
184 p.len() / 2,
185 twiddles.len()
186 );
187 assert!(
188 (p.len() * blowup_factor).ilog2() <= B::TWO_ADICITY,
189 "multiplicative subgroup of size {} does not exist in the specified base field",
190 p.len() * blowup_factor
191 );
192 assert_ne!(domain_offset, B::ZERO, "domain offset cannot be zero");
193
194 // assign a dummy value here to make the compiler happy
195 #[allow(unused_assignments)]
196 let mut result = Vec::new();
197
198 // when `concurrent` feature is enabled, run the concurrent version of the function; unless
199 // the polynomial is small, then don't bother with the concurrent version
200 if cfg!(feature = "concurrent") && p.len() >= MIN_CONCURRENT_SIZE {
201 #[cfg(feature = "concurrent")]
202 {
203 result =
204 concurrent::evaluate_poly_with_offset(p, twiddles, domain_offset, blowup_factor);
205 }
206 } else {
207 result = serial::evaluate_poly_with_offset(p, twiddles, domain_offset, blowup_factor);
208 }
209
210 result
211}
212
213// POLYNOMIAL INTERPOLATION
214// ================================================================================================
215
216/// Interpolates evaluations of a polynomial over the specified domain into a polynomial in
217/// coefficient from using the FFT algorithm.
218///
219/// Uses the inverse [FFT](https://en.wikipedia.org/wiki/Discrete_Fourier_transform_(general))
220/// algorithm to interpolate a polynomial from its evaluations over a domain defined by the
221/// length of `evaluations` in the field specified by the `B` type parameter. The interpolation
222/// is done in-place, meaning no additional memory is allocated and the evaluations contained in
223/// `evaluations` are replaced with polynomial coefficients.
224///
225/// The complexity of interpolation is O(`n` log(`n`)), where `n` is the size of the domain.
226///
227/// The size of the domain is assumed to be equal to `evaluations.len()` which must be a power
228/// of two. The base field specified by `B` must have a multiplicative subgroup of size equal
229/// to `evaluations.len()`.
230///
231/// The `inv_twiddles` needed for interpolation can be obtained via `fft::get_inv_twiddles()`
232/// function using `evaluations.len()` as the domain size parameter. This implies that
233/// `twiddles.len()` must be equal to `evaluations.len()` / 2.
234///
235/// When `concurrent` feature is enabled, the interpolation is done in multiple threads.
236///
237/// # Panics
238/// Panics if:
239/// * Length of `evaluations` is not a power of two.
240/// * Length of `inv_twiddles` is not `evaluations.len()` / 2.
241/// * Field specified by `B` does not contain a multiplicative subgroup of size `evaluations.len()`.
242///
243/// # Examples
244/// ```
245/// # use winter_math::{polynom, fft::*, get_power_series};
246/// # use winter_math::{fields::{f128::BaseElement}, FieldElement, StarkField};
247/// # use rand_utils::rand_vector;
248/// let n = 2048;
249///
250/// // build a random polynomial
251/// let p: Vec<BaseElement> = rand_vector(n);
252///
253/// // evaluate the polynomial over the domain using regular polynomial evaluation
254/// let g = BaseElement::get_root_of_unity(n.ilog2());
255/// let domain = get_power_series(g, n);
256/// let mut ys = polynom::eval_many(&p, &domain);
257///
258/// // interpolate the evaluations into a polynomial
259/// let inv_twiddles = get_inv_twiddles::<BaseElement>(ys.len());
260/// interpolate_poly(&mut ys, &inv_twiddles);
261///
262/// assert_eq!(p, ys);
263/// ```
264pub fn interpolate_poly<B, E>(evaluations: &mut [E], inv_twiddles: &[B])
265where
266 B: StarkField,
267 E: FieldElement<BaseField = B>,
268{
269 assert!(
270 evaluations.len().is_power_of_two(),
271 "number of evaluations must be a power of 2, but was {}",
272 evaluations.len()
273 );
274 assert_eq!(
275 evaluations.len(),
276 inv_twiddles.len() * 2,
277 "invalid number of twiddles: expected {} but received {}",
278 evaluations.len() / 2,
279 inv_twiddles.len()
280 );
281 assert!(
282 evaluations.len().ilog2() <= B::TWO_ADICITY,
283 "multiplicative subgroup of size {} does not exist in the specified base field",
284 evaluations.len()
285 );
286
287 // when `concurrent` feature is enabled, run the concurrent version of interpolate_poly;
288 // unless the number of evaluations is small, then don't bother with the concurrent version
289 if cfg!(feature = "concurrent") && evaluations.len() >= MIN_CONCURRENT_SIZE {
290 #[cfg(feature = "concurrent")]
291 concurrent::interpolate_poly(evaluations, inv_twiddles);
292 } else {
293 serial::interpolate_poly(evaluations, inv_twiddles);
294 }
295}
296
297/// Interpolates evaluations of a polynomial over the specified (shifted) domain into a polynomial
298/// in coefficient from using the FFT algorithm.
299///
300/// Uses the inverse [FFT](https://en.wikipedia.org/wiki/Discrete_Fourier_transform_(general))
301/// algorithm to interpolate a polynomial from its evaluations over a domain defined by the
302/// length of `evaluations` and shifted by the `domain_offset` in the field specified by the `B`
303/// type parameter. The interpolation is done in-place, meaning no additional memory is allocated
304/// and the evaluations contained in `evaluations` are replaced with polynomial coefficients.
305///
306/// The complexity of interpolation is O(`n` log(`n`)), where `n` is the size of the domain.
307///
308/// The size of the domain is assumed to be equal to `evaluations.len()` which must be a power
309/// of two. The base field specified by `B` must have a multiplicative subgroup of size equal
310/// to `evaluations.len()`.
311///
312/// The shifted domain is defined as the original domain with every element multiplied by the
313/// `domain_offset`.
314///
315/// The `inv_twiddles` needed for interpolation can be obtained via `fft::get_inv_twiddles()`
316/// function using `evaluations.len()` as the domain size parameter. This implies that
317/// `twiddles.len()` must be equal to `evaluations.len()` / 2.
318///
319/// When `concurrent` feature is enabled, the interpolation is done in multiple threads.
320///
321/// # Panics
322/// Panics if:
323/// * Length of `evaluations` is not a power of two.
324/// * Length of `inv_twiddles` is not `evaluations.len()` / 2.
325/// * Field specified by `B` does not contain a multiplicative subgroup of size `evaluations.len()`.
326/// * `domain_offset` is ZERO.
327///
328/// # Examples
329/// ```
330/// # use winter_math::{polynom, fft::*, get_power_series};
331/// # use winter_math::{fields::{f128::BaseElement}, FieldElement, StarkField};
332/// # use rand_utils::rand_vector;
333/// let n = 2048;
334/// let offset = BaseElement::GENERATOR;
335///
336/// // build a random polynomial
337/// let p: Vec<BaseElement> = rand_vector(n);
338///
339/// // evaluate the polynomial over the domain using regular polynomial evaluation
340/// let g = BaseElement::get_root_of_unity(n.ilog2());
341/// let domain = get_power_series(g, n);
342/// let shifted_domain = domain.iter().map(|&x| x * offset).collect::<Vec<_>>();
343/// let mut ys = polynom::eval_many(&p, &shifted_domain);
344///
345/// // interpolate the evaluations into a polynomial
346/// let inv_twiddles = get_inv_twiddles::<BaseElement>(ys.len());
347/// interpolate_poly_with_offset(&mut ys, &inv_twiddles, offset);
348///
349/// assert_eq!(p, ys);
350/// ```
351pub fn interpolate_poly_with_offset<B, E>(
352 evaluations: &mut [E],
353 inv_twiddles: &[B],
354 domain_offset: B,
355) where
356 B: StarkField,
357 E: FieldElement<BaseField = B>,
358{
359 assert!(
360 evaluations.len().is_power_of_two(),
361 "number of evaluations must be a power of 2, but was {}",
362 evaluations.len()
363 );
364 assert_eq!(
365 evaluations.len(),
366 inv_twiddles.len() * 2,
367 "invalid number of twiddles: expected {} but received {}",
368 evaluations.len() / 2,
369 inv_twiddles.len()
370 );
371 assert!(
372 evaluations.len().ilog2() <= B::TWO_ADICITY,
373 "multiplicative subgroup of size {} does not exist in the specified base field",
374 evaluations.len()
375 );
376 assert_ne!(domain_offset, B::ZERO, "domain offset cannot be zero");
377
378 // when `concurrent` feature is enabled, run the concurrent version of the function; unless
379 // the polynomial is small, then don't bother with the concurrent version
380 if cfg!(feature = "concurrent") && evaluations.len() >= MIN_CONCURRENT_SIZE {
381 #[cfg(feature = "concurrent")]
382 concurrent::interpolate_poly_with_offset(evaluations, inv_twiddles, domain_offset);
383 } else {
384 serial::interpolate_poly_with_offset(evaluations, inv_twiddles, domain_offset);
385 }
386}
387
388// RAW FFT ALGORITHM
389// ================================================================================================
390
391/// Executes a single-threaded version of the FFT algorithm on the provided values.
392///
393/// The evaluation is done in-place, meaning the function does not allocate any additional memory,
394/// and the results are written back into `values`.
395///
396/// The `twiddles` needed for evaluation can be obtained via `fft::get_twiddles()` function using
397/// `values.len()` as the domain size parameter. This implies that `twiddles.len()` must be equal
398/// to `values.len()` / 2.
399///
400/// # Panics
401/// Panics if:
402/// * Length of `values` is not a power of two.
403/// * Length of `twiddles` is not `values.len()` / 2.
404/// * Field specified by `B` does not contain a multiplicative subgroup of size `values.len()`.
405pub fn serial_fft<B, E>(values: &mut [E], twiddles: &[B])
406where
407 B: StarkField,
408 E: FieldElement<BaseField = B>,
409{
410 assert!(
411 values.len().is_power_of_two(),
412 "number of values must be a power of 2, but was {}",
413 values.len()
414 );
415 assert_eq!(
416 values.len(),
417 twiddles.len() * 2,
418 "invalid number of twiddles: expected {} but received {}",
419 values.len() / 2,
420 twiddles.len()
421 );
422 assert!(
423 values.len().ilog2() <= B::TWO_ADICITY,
424 "multiplicative subgroup of size {} does not exist in the specified base field",
425 values.len()
426 );
427 values.fft_in_place(twiddles);
428 values.permute();
429}
430
431// TWIDDLES
432// ================================================================================================
433
434/// Returns a set of twiddles for the specified domain size.
435///
436/// These twiddles can then be used for FFT-based polynomial evaluation. The length of the returned
437/// vector will be equal to `domain_size` / 2.
438///
439/// When `concurrent` feature is enabled, the twiddles are generated in multiple threads.
440///
441/// # Panics
442/// Panics if:
443/// * `domain_size` is not a power of two.
444/// * Field specified by `B` does not contain a multiplicative subgroup of size `domain_size`.
445///
446/// # Examples
447/// ```
448/// # use winter_math::fft::*;
449/// # use winter_math::{fields::{f128::BaseElement}, FieldElement, StarkField};
450/// let n = 2048;
451/// let twiddles = get_twiddles::<BaseElement>(n);
452///
453/// assert_eq!(n / 2, twiddles.len());
454/// ```
455pub fn get_twiddles<B>(domain_size: usize) -> Vec<B>
456where
457 B: StarkField,
458{
459 assert!(domain_size.is_power_of_two(), "domain size must be a power of 2");
460 assert!(
461 domain_size.ilog2() <= B::TWO_ADICITY,
462 "multiplicative subgroup of size {domain_size} does not exist in the specified base field"
463 );
464 let root = B::get_root_of_unity(domain_size.ilog2());
465 let mut twiddles = get_power_series(root, domain_size / 2);
466 permute(&mut twiddles);
467 twiddles
468}
469
470/// Returns a set of inverse twiddles for the specified domain size.
471///
472/// These twiddles can then be used for FFT-based polynomial interpolation. The length of the
473/// returned vector will be equal to `domain_size` / 2.
474///
475/// When `concurrent` feature is enabled, the twiddles are generated in multiple threads.
476///
477/// # Panics
478/// Panics if:
479/// * `domain_size` is not a power of two.
480/// * Field specified by `B` does not contain a multiplicative subgroup of size `domain_size`.
481///
482/// # Examples
483/// ```
484/// # use winter_math::fft::*;
485/// # use winter_math::{fields::{f128::BaseElement}, FieldElement, StarkField};
486/// let n = 2048;
487/// let inv_twiddles = get_inv_twiddles::<BaseElement>(n);
488///
489/// assert_eq!(n / 2, inv_twiddles.len());
490/// ```
491pub fn get_inv_twiddles<B>(domain_size: usize) -> Vec<B>
492where
493 B: StarkField,
494{
495 assert!(domain_size.is_power_of_two(), "domain size must be a power of 2");
496 assert!(
497 domain_size.ilog2() <= B::TWO_ADICITY,
498 "multiplicative subgroup of size {domain_size} does not exist in the specified base field"
499 );
500 let root = B::get_root_of_unity(domain_size.ilog2());
501 let inv_root = root.exp((domain_size as u32 - 1).into());
502 let mut inv_twiddles = get_power_series(inv_root, domain_size / 2);
503 permute(&mut inv_twiddles);
504 inv_twiddles
505}
506
507// DEGREE INFERENCE
508// ================================================================================================
509
510/// Returns the degree of a polynomial implied by the provided evaluations.
511///
512/// The polynomial is assumed to be evaluated over a multiplicative subgroup of length equal to
513/// the length of `evaluations` in the field defined by `B` type parameter and shifted by the
514/// `domain_offset`.
515///
516/// The shifted domain is defined as the original domain with every element multiplied by the
517/// `domain_offset`.
518///
519/// The degree is determined by first interpolating the polynomial into a coefficient form using
520/// FFT-based polynomial interpolation, and then finding the first non-zero coefficient.
521///
522/// # Panics
523/// Panics if
524/// * Length of `evaluations` is not a power of two.
525/// * Field specified by `B` does not contain a multiplicative subgroup of size `domain_size`.
526/// * `domain_offset` is ZERO.
527///
528/// # Examples
529/// ```
530/// # use winter_math::{polynom, fft::*, get_power_series};
531/// # use winter_math::{fields::{f128::BaseElement}, FieldElement, StarkField};
532/// let offset = BaseElement::GENERATOR;
533/// // p(x) = x^2 + 1
534/// let p = [BaseElement::new(1), BaseElement::ZERO, BaseElement::new(1), BaseElement::ZERO];
535///
536/// let g = BaseElement::get_root_of_unity(p.len().ilog2());
537/// let domain = get_power_series(g, p.len());
538/// let shifted_domain = domain.iter().map(|&x| x * offset).collect::<Vec<_>>();
539/// let evaluations = polynom::eval_many(&p, &shifted_domain);
540///
541/// assert_eq!(2, infer_degree(&evaluations, offset));
542/// ```
543pub fn infer_degree<B, E>(evaluations: &[E], domain_offset: B) -> usize
544where
545 B: StarkField,
546 E: FieldElement<BaseField = B>,
547{
548 assert!(
549 evaluations.len().is_power_of_two(),
550 "number of evaluations must be a power of 2"
551 );
552 assert!(
553 evaluations.len().ilog2() <= B::TWO_ADICITY,
554 "multiplicative subgroup of size {} does not exist in the specified base field",
555 evaluations.len()
556 );
557 assert_ne!(domain_offset, B::ZERO, "domain offset cannot be zero");
558 let mut poly = evaluations.to_vec();
559 let inv_twiddles = get_inv_twiddles::<B>(evaluations.len());
560 interpolate_poly_with_offset(&mut poly, &inv_twiddles, domain_offset);
561 super::polynom::degree_of(&poly)
562}
563
564// PERMUTATIONS
565// ================================================================================================
566
567/// Computes bit reverse of the specified index in the domain of the specified size.
568///
569/// Domain size is assumed to be a power of two and index must be smaller than domain size.
570pub fn permute_index(size: usize, index: usize) -> usize {
571 const USIZE_BITS: u32 = 0_usize.count_zeros();
572
573 debug_assert!(index < size);
574 debug_assert!(size.is_power_of_two());
575
576 let bits = size.trailing_zeros();
577 index.reverse_bits().wrapping_shr(USIZE_BITS - bits)
578}
579
580fn permute<E: FieldElement>(v: &mut [E]) {
581 if cfg!(feature = "concurrent") && v.len() >= MIN_CONCURRENT_SIZE {
582 #[cfg(feature = "concurrent")]
583 concurrent::permute(v);
584 } else {
585 FftInputs::permute(v);
586 }
587}