arnaucube
arnaucube/shockwave-plusPublic Self-hosted repository
shockwave-plus/tensor_pcs/src/fft.rs
tensor_pcs/src/fft.rsRaw
  1use crate::FieldExt;
  2use halo2curves::ff::Field;
  3use std::vec;
  4
  5pub fn fft<F>(coeffs: &[F], domain: &[F]) -> Vec<F>
  6where
  7    F: FieldExt,
  8{
  9    debug_assert_eq!(coeffs.len(), domain.len());
 10    if coeffs.len() == 1 {
 11        return coeffs.to_vec();
 12    }
 13
 14    // TODO: Just borrow the values
 15    // Split into evens and odds
 16    let L = coeffs
 17        .iter()
 18        .enumerate()
 19        .filter(|(i, _)| i % 2 == 0)
 20        .map(|(_, x)| *x)
 21        .collect::<Vec<F>>();
 22
 23    let R = coeffs
 24        .iter()
 25        .enumerate()
 26        .filter(|(i, _)| i % 2 == 1)
 27        .map(|(_, x)| *x)
 28        .collect::<Vec<F>>();
 29
 30    // Square the domain values
 31    let domain_squared: Vec<F> = (0..(domain.len() / 2)).map(|i| domain[i * 2]).collect();
 32
 33    let fft_e = fft(&L, &domain_squared);
 34    let fft_o = fft(&R, &domain_squared);
 35
 36    let mut evals_L = vec![];
 37    let mut evals_R = vec![];
 38    for i in 0..(coeffs.len() / 2) {
 39        // We can use the previous evaluations to create a list of evaluations
 40        // of the domain
 41        evals_L.push(fft_e[i] + fft_o[i] * domain[i]);
 42        evals_R.push(fft_e[i] - fft_o[i] * domain[i]);
 43    }
 44
 45    evals_L.extend(evals_R);
 46    return evals_L;
 47}
 48
 49pub fn ifft<F: FieldExt + Field>(domain: &[F], evals: &[F]) -> Vec<F> {
 50    let mut coeffs = vec![];
 51    let len_mod_inv = F::from(domain.len() as u64).invert().unwrap();
 52    let vals = fft(&evals, &domain);
 53
 54    coeffs.push(vals[0] * len_mod_inv);
 55    for val in vals[1..].iter().rev() {
 56        coeffs.push(*val * len_mod_inv);
 57    }
 58
 59    coeffs
 60}
 61
 62#[cfg(test)]
 63mod tests {
 64    use halo2curves::ff::PrimeField;
 65    use halo2curves::pasta::Fp;
 66
 67    use super::*;
 68    #[test]
 69    fn test_fft_ifft() {
 70        // f(x) = 1 + 2x + 3x^2 + 4x^3
 71        let mut coeffs = vec![
 72            Fp::from(1),
 73            Fp::from(2),
 74            Fp::from(3),
 75            Fp::from(4),
 76            Fp::from(5),
 77            Fp::from(6),
 78            Fp::from(7),
 79            Fp::from(81),
 80        ];
 81
 82        let mut domain = vec![];
 83
 84        let root_of_unity = Fp::ROOT_OF_UNITY;
 85
 86        let subgroup_order = (coeffs.len() * 2).next_power_of_two();
 87
 88        coeffs.resize(subgroup_order, Fp::ZERO);
 89
 90        // Generator for the subgroup with order _subgroup_order_ in the field
 91        let generator = root_of_unity.pow(&[
 92            2u32.pow(32 - ((subgroup_order as f64).log2() as u32)) as u64,
 93            0,
 94            0,
 95            0,
 96        ]);
 97
 98        for i in 0..(subgroup_order) {
 99            domain.push(generator.pow(&[i as u64, 0, 0, 0]));
100        }
101
102        let mut expected_evals = vec![];
103
104        for w in &domain {
105            let mut eval = Fp::ZERO;
106            for (i, coeff) in (&coeffs).iter().enumerate() {
107                eval += *coeff * w.pow(&[i as u64, 0, 0, 0]);
108            }
109            expected_evals.push(eval);
110        }
111
112        let evals = fft(&coeffs, &domain);
113        debug_assert!(evals == expected_evals);
114
115        let recovered_coeffs = ifft(&domain, &evals);
116        debug_assert!(recovered_coeffs == coeffs);
117    }
118}