../../.cargo/katex-header.html

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}