use crate::field::Goldilocks4;
pub(crate) struct MonomialLift {
x_powers: Vec<Goldilocks4>,
nu: u32,
}
impl MonomialLift {
pub(crate) fn new(x: Goldilocks4, nu: u32) -> Self {
let mut x_powers = Vec::with_capacity(nu as usize);
if nu > 0 {
x_powers.push(x);
for _ in 1..nu {
let last = *x_powers.last().unwrap();
x_powers.push(last * last);
}
}
Self { x_powers, nu }
}
#[cfg(test)]
pub(crate) fn materialize(&self) -> Vec<Goldilocks4> {
let mut v = vec![Goldilocks4::ONE];
for j in 1..=self.nu as usize {
let a_j = self.x_powers[j - 1];
let half = v.len();
v.reserve(half);
for k in 0..half {
let prod = v[k] * a_j;
v.push(prod);
}
}
v
}
pub(crate) fn folded(&self, rand: &[Goldilocks4]) -> Vec<Goldilocks4> {
let r = rand.len() as u32;
assert!(r <= self.nu, "rand.len()={r} > nu={}", self.nu);
let mut scalar = Goldilocks4::ONE;
for (i, &r_i) in rand.iter().enumerate() {
let j = self.nu - i as u32;
let a_j = self.x_powers[j as usize - 1];
let f = Goldilocks4::ONE + r_i * (a_j - Goldilocks4::ONE);
scalar *= f;
}
let l = self.nu - r;
let mut v = vec![scalar];
for j in 1..=l as usize {
let a_j = self.x_powers[j - 1];
let half = v.len();
v.reserve(half);
for k in 0..half {
let prod = v[k] * a_j;
v.push(prod);
}
}
v
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::field::Goldilocks;
fn g(n: u64) -> Goldilocks4 {
Goldilocks4::new([
Goldilocks::new(n),
Goldilocks::new(0),
Goldilocks::new(0),
Goldilocks::new(0),
])
}
fn manual_fold(mut v: Vec<Goldilocks4>, rand: &[Goldilocks4]) -> Vec<Goldilocks4> {
for &w in rand {
let half = v.len() / 2;
let mut new = Vec::with_capacity(half);
for k in 0..half {
new.push(v[k] + (v[k + half] - v[k]) * w);
}
v = new;
}
v
}
#[test]
fn materialize_has_length_m() {
let lift = MonomialLift::new(g(7), 3);
let v = lift.materialize();
assert_eq!(v.len(), 8);
}
#[test]
fn materialize_matches_horner() {
let x = g(7);
let lift = MonomialLift::new(x, 4);
let v = lift.materialize();
assert_eq!(v.len(), 16);
let mut x_pow = Goldilocks4::ONE;
for (k, v_k) in v.iter().enumerate() {
assert_eq!(*v_k, x_pow, "mismatch at k={k}");
x_pow *= x;
}
}
#[test]
fn folded_matches_materialize_plus_explicit_fold() {
for nu in [2u32, 3, 4, 5] {
let lift = MonomialLift::new(g(11), nu);
let materialised = lift.materialize();
for r in 0..=nu {
let rand: Vec<Goldilocks4> = (0..r).map(|i| g(13 + i as u64)).collect();
let symbolic = lift.folded(&rand);
let explicit = manual_fold(materialised.clone(), &rand);
assert_eq!(symbolic, explicit, "nu={nu} R={r}");
}
}
}
#[test]
fn folded_full_returns_single_value() {
let lift = MonomialLift::new(g(5), 3);
let rand: Vec<Goldilocks4> = (0..3).map(|i| g(20 + i)).collect();
let v = lift.folded(&rand);
assert_eq!(v.len(), 1);
}
}