use nebu::Goldilocks;
pub fn fri_fold(evals: &[Goldilocks], r: Goldilocks) -> Goldilocks {
let n = evals.len();
assert!(n >= 1 && n.is_power_of_two(), "fri_fold: evals length must be a non-empty power of two");
if n == 1 {
return evals[0];
}
let mut buf: Vec<Goldilocks> = evals.to_vec();
let mut width = n;
while width > 1 {
let half = width / 2;
for i in 0..half {
let left = buf[2 * i];
let right = buf[2 * i + 1];
buf[i] = left + r * (right - left);
}
width = half;
}
buf[0]
}
#[cfg(test)]
mod tests {
use super::*;
fn g(v: u64) -> Goldilocks { Goldilocks::new(v) }
const P: u64 = 0xFFFF_FFFF_0000_0001;
#[test]
fn single_element_returns_itself() {
assert_eq!(fri_fold(&[g(7)], g(3)), g(7));
assert_eq!(fri_fold(&[g(0)], g(0)), g(0));
}
#[test]
fn two_elements_r_zero_returns_left() {
assert_eq!(fri_fold(&[g(3), g(7)], g(0)), g(3));
}
#[test]
fn two_elements_r_one_returns_right() {
assert_eq!(fri_fold(&[g(3), g(7)], g(1)), g(7));
}
#[test]
fn two_elements_linear_interpolation() {
assert_eq!(fri_fold(&[g(3), g(7)], g(2)), g(11));
}
#[test]
fn four_elements_two_levels_at_r_half() {
let half = Goldilocks::new((P + 1) / 2);
assert_eq!(fri_fold(&[g(0), g(4), g(8), g(12)], half), g(6));
}
#[test]
fn four_elements_r_zero_returns_first() {
assert_eq!(fri_fold(&[g(5), g(9), g(13), g(17)], g(0)), g(5));
}
#[test]
fn four_elements_r_one_returns_last() {
assert_eq!(fri_fold(&[g(5), g(9), g(13), g(17)], g(1)), g(17));
}
#[test]
fn caller_slice_unchanged() {
let evals = vec![g(1), g(2), g(3), g(4)];
let snapshot = evals.clone();
let _ = fri_fold(&evals, g(5));
assert_eq!(evals, snapshot);
}
#[test]
#[should_panic(expected = "fri_fold: evals length must be a non-empty power of two")]
fn empty_panics() {
fri_fold(&[], g(1));
}
#[test]
#[should_panic(expected = "fri_fold: evals length must be a non-empty power of two")]
fn non_power_of_two_panics() {
fri_fold(&[g(1), g(2), g(3)], g(1));
}
}