use alloc::vec::Vec;
use p3_field::{Field, batch_multiplicative_inverse};
use p3_matrix::dense::RowMajorMatrix;
use p3_matrix::interpolation::{InterpolateArbitrary, barycentric_weights};
pub struct RoundPolyInterpolator<EF> {
x_coords: Vec<EF>,
weights: Vec<EF>,
}
impl<EF: Field> RoundPolyInterpolator<EF> {
#[must_use]
pub fn transmitted_node(index: usize) -> EF {
EF::interpolation_node(if index == 0 { 0 } else { index + 1 })
}
pub fn new(degree: usize) -> Self {
let x_coords: Vec<EF> = (0..=degree).map(EF::interpolation_node).collect();
let weights =
barycentric_weights(&x_coords).expect("interpolation nodes are pairwise distinct");
Self { x_coords, weights }
}
pub fn eval(&self, evals: &[EF], sum_constraint: EF, r: EF) -> EF {
debug_assert_eq!(evals.len() + 1, self.x_coords.len());
let mut full: Vec<EF> = Vec::with_capacity(self.x_coords.len());
full.push(evals[0]);
full.push(sum_constraint - evals[0]);
full.extend_from_slice(&evals[1..]);
let diffs: Vec<EF> = self.x_coords.iter().map(|&x| r - x).collect();
if let Some(i) = diffs.iter().position(|d| d.is_zero()) {
return full[i];
}
let diff_invs = batch_multiplicative_inverse(&diffs);
RowMajorMatrix::new_col(full)
.interpolate_arbitrary_with_precomputation(&self.weights, &diff_invs)[0]
}
pub fn extend_evals(&self, evals: &[EF], sum_constraint: EF, target_len: usize) -> Vec<EF> {
let mut extended = Vec::with_capacity(target_len);
extended.extend_from_slice(evals);
extended.extend(
(evals.len()..target_len)
.map(|i| self.eval(evals, sum_constraint, EF::interpolation_node(i + 1))),
);
extended
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use alloc::vec::Vec;
use p3_baby_bear::BabyBear;
use p3_field::PrimeCharacteristicRing;
use p3_field::extension::BinomialExtensionField;
use rand::rngs::SmallRng;
use rand::{RngExt, SeedableRng};
use super::*;
type F = BabyBear;
type EF = BinomialExtensionField<F, 4>;
fn poly_eval(coeffs: &[EF], x: EF) -> EF {
coeffs.iter().rev().fold(EF::ZERO, |acc, &c| acc * x + c)
}
#[test]
fn round_poly_eval_at_known_points_matches_definition() {
let mut rng = SmallRng::seed_from_u64(11);
let coeffs: Vec<EF> = (0..4)
.map(|_| EF::from_u64(rng.random_range(0..(1 << 30))))
.collect();
let h_at = |x: EF| poly_eval(&coeffs, x);
let h0 = h_at(EF::ZERO);
let h1 = h_at(EF::ONE);
let h2 = h_at(EF::from_u64(2));
let h3 = h_at(EF::from_u64(3));
let evals = vec![h0, h2, h3];
let sum = h0 + h1;
let r = EF::from_u64(rng.random_range(0..(1 << 30)));
let interp = RoundPolyInterpolator::new(3);
assert_eq!(interp.eval(&evals, sum, r), h_at(r));
}
#[test]
fn round_poly_eval_on_domain_node_is_exact() {
let mut rng = SmallRng::seed_from_u64(7);
let coeffs: Vec<EF> = (0..4)
.map(|_| EF::from_u64(rng.random_range(0..(1 << 30))))
.collect();
let h_at = |x: EF| poly_eval(&coeffs, x);
let evals = vec![h_at(EF::ZERO), h_at(EF::from_u64(2)), h_at(EF::from_u64(3))];
let sum = h_at(EF::ZERO) + h_at(EF::ONE);
let interp = RoundPolyInterpolator::new(3);
assert_eq!(
interp.eval(&evals, sum, EF::from_u64(2)),
h_at(EF::from_u64(2))
);
assert_eq!(interp.eval(&evals, sum, EF::ONE), h_at(EF::ONE));
}
}