use alloc::vec::Vec;
use p3_field::coset::TwoAdicMultiplicativeCoset;
use p3_field::{
ExtensionField, Field, TwoAdicField, batch_multiplicative_inverse,
scale_slice_in_place_single_core,
};
use p3_maybe_rayon::prelude::*;
use p3_util::log2_strict_usize;
use crate::Matrix;
use crate::dense::RowMajorMatrix;
pub fn compute_adjusted_weights<EF: Field>(point: EF, diff_invs: &[EF]) -> Vec<EF> {
let point_inv = point.inverse();
diff_invs.par_iter().map(|&d| d - point_inv).collect()
}
pub trait Interpolate<F: TwoAdicField>: Matrix<F> {
fn interpolate_subgroup<EF: ExtensionField<F>>(&self, point: EF) -> Vec<EF> {
self.interpolate_coset(F::ONE, point)
}
fn interpolate_coset<EF: ExtensionField<F>>(&self, shift: F, point: EF) -> Vec<EF> {
let log_height = log2_strict_usize(self.height());
let coset: Vec<F> = TwoAdicMultiplicativeCoset::new(shift, log_height)
.unwrap()
.iter()
.collect();
let diffs: Vec<EF> = coset.par_iter().map(|&g| point - g).collect();
if let Some(i) = diffs.iter().position(|d| d.is_zero()) {
return self.row(i).unwrap().into_iter().map(EF::from).collect();
}
let diff_invs = batch_multiplicative_inverse(&diffs);
let adjusted = compute_adjusted_weights(point, &diff_invs);
self.interpolate_coset_with_precomputation(shift, point, &adjusted)
}
fn interpolate_coset_with_precomputation<EF: ExtensionField<F>>(
&self,
shift: F,
point: EF,
adjusted_weights: &[EF],
) -> Vec<EF> {
debug_assert_eq!(adjusted_weights.len(), self.height());
let log_height = log2_strict_usize(self.height());
let z_pow_n = point.exp_power_of_2(log_height);
let g_pow_n = shift.exp_power_of_2(log_height);
let denom_inv = g_pow_n.mul_2exp_u64(log_height as u64).inverse();
let scaling_factor = point * (z_pow_n - g_pow_n) * denom_inv;
let mut evals = self.columnwise_dot_product(adjusted_weights);
scale_slice_in_place_single_core(&mut evals, scaling_factor);
evals
}
}
impl<F: TwoAdicField, M: Matrix<F>> Interpolate<F> for M {}
pub fn barycentric_weights<F: Field>(x_coords: &[F]) -> Option<Vec<F>> {
let n = x_coords.len();
if n == 0 {
return Some(Vec::new());
}
let mut denoms = alloc::vec![F::ONE; n];
for i in 0..n {
for j in 0..i {
let diff = x_coords[i] - x_coords[j];
if diff.is_zero() {
return None;
}
denoms[i] *= diff;
denoms[j] *= -diff;
}
}
Some(batch_multiplicative_inverse(&denoms))
}
pub trait InterpolateArbitrary<F: Field>: Matrix<F> {
fn interpolate_arbitrary_point<EF: ExtensionField<F>>(
&self,
x_coords: &[F],
point: EF,
) -> Option<Vec<EF>> {
debug_assert_eq!(x_coords.len(), self.height());
let weights = barycentric_weights(x_coords)?;
for (i, &x) in x_coords.iter().enumerate() {
if point == EF::from(x) {
return Some(self.row(i).unwrap().into_iter().map(EF::from).collect());
}
}
let diffs: Vec<EF> = x_coords.iter().map(|&x| point - x).collect();
let diff_invs = batch_multiplicative_inverse(&diffs);
Some(self.interpolate_arbitrary_with_precomputation(&weights, &diff_invs))
}
fn interpolate_arbitrary_with_precomputation<EF: ExtensionField<F>>(
&self,
weights: &[F],
diff_invs: &[EF],
) -> Vec<EF> {
debug_assert_eq!(weights.len(), self.height());
debug_assert_eq!(diff_invs.len(), self.height());
if self.height() == 0 {
return EF::zero_vec(self.width());
}
let col_scale: Vec<EF> = weights
.iter()
.zip(diff_invs)
.map(|(&w, &d)| d * w)
.collect();
let denominator = col_scale.iter().copied().fold(EF::ZERO, |a, b| a + b);
let denom_inv = denominator.inverse();
let mut evals = self.columnwise_dot_product(&col_scale);
scale_slice_in_place_single_core(&mut evals, denom_inv);
evals
}
fn recover_coefficients(&self, x_coords: &[F]) -> Option<RowMajorMatrix<F>> {
let n = self.height();
let w = self.width();
debug_assert_eq!(x_coords.len(), n);
if n == 0 {
return Some(RowMajorMatrix::new(Vec::new(), w.max(1)));
}
let mut result = RowMajorMatrix::new(F::zero_vec(n * w), w);
let mut basis = F::zero_vec(n);
basis[0] = F::ONE;
let mut scratch = F::zero_vec(w);
for k in 0..n {
let x_k = x_coords[k];
let mut b_xk = F::ONE;
for &x_i in &x_coords[..k] {
b_xk *= x_k - x_i;
}
let b_xk_inv = b_xk.try_inverse()?;
scratch.fill(F::ZERO);
for i in (0..k).rev() {
let row = result.row_slice(i).unwrap();
for j in 0..w {
scratch[j] = scratch[j] * x_k + row[j];
}
}
for (j, y_kj) in self.row(k).unwrap().into_iter().enumerate() {
scratch[j] = (y_kj - scratch[j]) * b_xk_inv;
}
for (i, &b_i) in basis.iter().enumerate().take(k + 1) {
let row = result.row_mut(i);
for j in 0..w {
row[j] += scratch[j] * b_i;
}
}
if k + 1 < n {
basis[k + 1] = basis[k];
}
for i in (1..=k).rev() {
basis[i] = basis[i - 1] - x_k * basis[i];
}
basis[0] = -x_k * basis[0];
}
Some(result)
}
}
impl<F: Field, M: Matrix<F>> InterpolateArbitrary<F> for M {}
pub fn interpolate_lagrange<F: Field>(points: &[(F, F)]) -> Option<Vec<F>> {
if points.is_empty() {
return Some(Vec::new());
}
let (xs, ys): (Vec<F>, Vec<F>) = points.iter().copied().unzip();
let evals = RowMajorMatrix::new_col(ys);
Some(evals.recover_coefficients(&xs)?.values)
}
#[cfg(test)]
mod tests {
use alloc::vec;
use alloc::vec::Vec;
use p3_baby_bear::BabyBear;
use p3_field::extension::BinomialExtensionField;
use p3_field::{
BasedVectorSpace, ExtensionField, Field, HornerIter, PrimeCharacteristicRing, TwoAdicField,
batch_multiplicative_inverse,
};
use p3_util::log2_strict_usize;
use proptest::prelude::*;
use super::*;
use crate::dense::RowMajorMatrix;
type F = BabyBear;
type EF4 = BinomialExtensionField<BabyBear, 4>;
fn eval_poly<EF: ExtensionField<F>>(coeffs: &[F], point: EF) -> EF {
coeffs.iter().copied().horner(point)
}
fn eval_poly_on_coset<EF: ExtensionField<F>>(coeffs: &[F], shift: F, log_n: usize) -> Vec<EF> {
let n = 1 << log_n;
let subgroup_gen = F::two_adic_generator(log_n);
(0..n)
.map(|i| {
let coset_elem = shift * subgroup_gen.exp_u64(i as u64);
eval_poly(coeffs, EF::from(coset_elem))
})
.collect()
}
#[test]
fn test_interpolate_subgroup() {
let evals = [
6, 886605102, 1443543107, 708307799, 2, 556938009, 569722818, 1874680944,
]
.map(F::from_u32);
let evals_mat = RowMajorMatrix::new(evals.to_vec(), 1);
let point = F::from_u16(100);
let result = evals_mat.interpolate_subgroup(point);
assert_eq!(result, vec![F::from_u16(10203)]);
}
#[test]
fn test_interpolate_coset() {
let shift = F::GENERATOR;
let evals = [
1026, 129027310, 457985035, 994890337, 902, 1988942953, 1555278970, 913671254,
]
.map(F::from_u32);
let evals_mat = RowMajorMatrix::new(evals.to_vec(), 1);
let point = F::from_u16(100);
let result = evals_mat.interpolate_coset(shift, point);
assert_eq!(result, vec![F::from_u16(10203)]);
let n = evals.len();
let k = log2_strict_usize(n);
let coset = F::two_adic_generator(k).shifted_powers(shift).collect_n(n);
let denom: Vec<_> = coset.iter().map(|&w| point - w).collect();
let denom = batch_multiplicative_inverse(&denom);
let adjusted = compute_adjusted_weights(point, &denom);
let result = evals_mat.interpolate_coset_with_precomputation(shift, point, &adjusted);
assert_eq!(result, vec![F::from_u16(10203)]);
}
#[test]
fn test_interpolate_coset_single_point_identity() {
let c = F::from_u32(42);
let evals = vec![c; 8];
let evals_mat = RowMajorMatrix::new(evals, 1);
let shift = F::GENERATOR;
let point = F::from_u16(1337);
let result = evals_mat.interpolate_coset(shift, point);
assert_eq!(result, vec![c]);
}
#[test]
fn test_interpolate_coset_point_on_coset() {
let log_n = 3;
let n = 1usize << log_n;
let shift = F::GENERATOR;
let h = F::two_adic_generator(log_n);
let coset: Vec<F> = (0..n).map(|i| shift * h.exp_u64(i as u64)).collect();
let evals: Vec<F> = (0..n as u32).map(|i| F::from_u32(100 + i)).collect();
let m = RowMajorMatrix::new(evals.clone(), 1);
for (i, &x) in coset.iter().enumerate() {
let result = m.interpolate_coset(shift, x);
assert_eq!(result, vec![evals[i]]);
}
}
#[test]
fn test_interpolate_coset_point_on_coset_extension() {
let log_n = 3;
let n = 1usize << log_n;
let shift = F::GENERATOR;
let h = F::two_adic_generator(log_n);
let coset: Vec<F> = (0..n).map(|i| shift * h.exp_u64(i as u64)).collect();
let mut evals: Vec<F> = Vec::with_capacity(n * 2);
for i in 0..n {
evals.push(F::from_u32(200 + i as u32));
evals.push(F::from_u32(300 + i as u32));
}
let m = RowMajorMatrix::new(evals, 2);
let i = 3;
let result = m.interpolate_coset(shift, EF4::from(coset[i]));
assert_eq!(
result,
vec![
EF4::from(F::from_u32(200 + i as u32)),
EF4::from(F::from_u32(300 + i as u32)),
]
);
}
#[test]
fn test_interpolate_subgroup_point_on_subgroup() {
let log_n = 2;
let n = 1usize << log_n;
let h = F::two_adic_generator(log_n);
let evals: Vec<F> = (0..n as u32).map(|i| F::from_u32(10 + i)).collect();
let m = RowMajorMatrix::new(evals.clone(), 1);
let result = m.interpolate_subgroup(h.exp_u64(2));
assert_eq!(result, vec![evals[2]]);
}
#[test]
fn test_interpolate_subgroup_degree_3_correctness() {
let poly = |x: EF4| x * x * x + x * x * F::TWO + x * F::from_u32(3) + F::from_u32(4);
let subgroup = EF4::two_adic_generator(2).powers().collect_n(4);
let evals: Vec<_> = subgroup.iter().map(|&x| poly(x)).collect();
let evals_mat = RowMajorMatrix::new(evals, 1);
let point = EF4::from_u16(5);
let result = evals_mat.interpolate_subgroup(point);
let expected = poly(point);
assert_eq!(result[0], expected);
}
#[test]
fn test_interpolate_coset_multiple_polynomials() {
let shift = EF4::GENERATOR;
let coset = EF4::two_adic_generator(3)
.shifted_powers(shift)
.collect_n(8);
let f1 = |x: EF4| x * x + x * F::TWO + F::from_u32(3);
let f2 = |x: EF4| x * x * F::from_u32(4) + x * F::from_u32(5) + F::from_u32(6);
let evals: Vec<_> = coset.iter().flat_map(|&x| vec![f1(x), f2(x)]).collect();
let evals_mat = RowMajorMatrix::new(evals, 2);
let point = EF4::from_u32(77);
let result = evals_mat.interpolate_coset(shift, point);
let expected_f1 = f1(point);
let expected_f2 = f2(point);
assert_eq!(result[0], expected_f1);
assert_eq!(result[1], expected_f2);
}
#[test]
fn test_interpolate_subgroup_multiple_columns() {
let f1 = |x: EF4| x * x + x * F::TWO + F::from_u32(3);
let f2 = |x: EF4| x * x * F::from_u32(4) + x * F::from_u32(5) + F::from_u32(6);
let subgroup_iter = EF4::two_adic_generator(3).powers().take(8);
let evals: Vec<_> = subgroup_iter.flat_map(|x| vec![f1(x), f2(x)]).collect();
let evals_mat = RowMajorMatrix::new(evals, 2);
let point = EF4::from_u32(77);
let result = evals_mat.interpolate_subgroup(point);
let expected_f1 = f1(point);
let expected_f2 = f2(point);
assert_eq!(result, vec![expected_f1, expected_f2]);
}
proptest! {
#[test]
fn prop_roundtrip_subgroup(
log_n in 1usize..=4,
coeffs_raw in prop::collection::vec(0u32..2013265921, 1..=16),
point_raw in 1u32..2013265921u32,
) {
let n = 1usize << log_n;
let coeffs: Vec<F> = coeffs_raw.iter().take(n).map(|&v| F::from_u32(v)).collect();
let evals: Vec<F> = eval_poly_on_coset(&coeffs, F::ONE, log_n);
let evals_mat = RowMajorMatrix::new(evals, 1);
let point = EF4::from_u32(point_raw);
let result = evals_mat.interpolate_subgroup(point);
let expected = eval_poly(&coeffs, point);
prop_assert_eq!(result[0], expected);
}
#[test]
fn prop_roundtrip_coset(
log_n in 1usize..=4,
coeffs_raw in prop::collection::vec(0u32..2013265921, 1..=16),
point_raw in 1u32..2013265921u32,
) {
let n = 1usize << log_n;
let coeffs: Vec<F> = coeffs_raw.iter().take(n).map(|&v| F::from_u32(v)).collect();
let shift = F::GENERATOR;
let evals: Vec<F> = eval_poly_on_coset(&coeffs, shift, log_n);
let evals_mat = RowMajorMatrix::new(evals, 1);
let point = EF4::from_u32(point_raw);
let result = evals_mat.interpolate_coset(shift, point);
let expected = eval_poly(&coeffs, point);
prop_assert_eq!(result[0], expected);
}
#[test]
fn prop_precomputation_equivalence(
log_n in 1usize..=4,
coeffs_raw in prop::collection::vec(0u32..2013265921, 1..=16),
point_raw in 1u32..2013265921u32,
) {
let n = 1usize << log_n;
let coeffs: Vec<F> = coeffs_raw.iter().take(n).map(|&v| F::from_u32(v)).collect();
let shift = F::GENERATOR;
let evals: Vec<F> = eval_poly_on_coset(&coeffs, shift, log_n);
let evals_mat = RowMajorMatrix::new(evals, 1);
let point = EF4::from_u32(point_raw);
let result_standard = evals_mat.interpolate_coset(shift, point);
let subgroup_gen = F::two_adic_generator(log_n);
let coset: Vec<F> =
(0..n).map(|i| shift * subgroup_gen.exp_u64(i as u64)).collect();
let diffs: Vec<EF4> = coset.iter().map(|&c| point - c).collect();
let diff_invs = batch_multiplicative_inverse(&diffs);
let adjusted = compute_adjusted_weights(point, &diff_invs);
let result_precomp = evals_mat
.interpolate_coset_with_precomputation(shift, point, &adjusted);
prop_assert_eq!(result_standard, result_precomp);
}
#[test]
fn prop_constant_polynomial(
log_n in 1usize..=4,
c_raw in 0u32..2013265921u32,
point_raw in 1u32..2013265921u32,
) {
let n = 1usize << log_n;
let c = F::from_u32(c_raw);
let evals = vec![c; n];
let evals_mat = RowMajorMatrix::new(evals, 1);
let point = EF4::from_u32(point_raw);
let result = evals_mat.interpolate_subgroup(point);
prop_assert_eq!(result[0], EF4::from(c));
}
#[test]
fn prop_linearity(
log_n in 1usize..=3,
f_raw in prop::collection::vec(0u32..2013265921, 1..=8),
g_raw in prop::collection::vec(0u32..2013265921, 1..=8),
a_raw in 0u32..2013265921u32,
b_raw in 0u32..2013265921u32,
point_raw in 1u32..2013265921u32,
) {
let n = 1usize << log_n;
let f_coeffs: Vec<F> = f_raw.iter().take(n).map(|&v| F::from_u32(v)).collect();
let g_coeffs: Vec<F> = g_raw.iter().take(n).map(|&v| F::from_u32(v)).collect();
let a = F::from_u32(a_raw);
let b = F::from_u32(b_raw);
let f_evals: Vec<F> = eval_poly_on_coset(&f_coeffs, F::ONE, log_n);
let g_evals: Vec<F> = eval_poly_on_coset(&g_coeffs, F::ONE, log_n);
let combined_evals: Vec<F> = f_evals
.iter()
.zip(&g_evals)
.map(|(&fe, &ge)| a * fe + b * ge)
.collect();
let f_mat = RowMajorMatrix::new(f_evals, 1);
let g_mat = RowMajorMatrix::new(g_evals, 1);
let combined_mat = RowMajorMatrix::new(combined_evals, 1);
let point = EF4::from_u32(point_raw);
let interp_f = f_mat.interpolate_subgroup(point)[0];
let interp_g = g_mat.interpolate_subgroup(point)[0];
let interp_combined = combined_mat.interpolate_subgroup(point)[0];
let expected = EF4::from(a) * interp_f + EF4::from(b) * interp_g;
prop_assert_eq!(interp_combined, expected);
}
#[test]
fn prop_batch_equals_individual(
log_n in 1usize..=3,
f_raw in prop::collection::vec(0u32..2013265921, 1..=8),
g_raw in prop::collection::vec(0u32..2013265921, 1..=8),
point_raw in 1u32..2013265921u32,
) {
let n = 1usize << log_n;
let f_coeffs: Vec<F> = f_raw.iter().take(n).map(|&v| F::from_u32(v)).collect();
let g_coeffs: Vec<F> = g_raw.iter().take(n).map(|&v| F::from_u32(v)).collect();
let shift = F::GENERATOR;
let f_evals: Vec<F> = eval_poly_on_coset(&f_coeffs, shift, log_n);
let g_evals: Vec<F> = eval_poly_on_coset(&g_coeffs, shift, log_n);
let batch_evals: Vec<F> = f_evals
.iter()
.zip(&g_evals)
.flat_map(|(&fe, &ge)| vec![fe, ge])
.collect();
let batch_mat = RowMajorMatrix::new(batch_evals, 2);
let f_mat = RowMajorMatrix::new(f_evals, 1);
let g_mat = RowMajorMatrix::new(g_evals, 1);
let point = EF4::from_u32(point_raw);
let batch_result = batch_mat.interpolate_coset(shift, point);
let f_result = f_mat.interpolate_coset(shift, point)[0];
let g_result = g_mat.interpolate_coset(shift, point)[0];
prop_assert_eq!(batch_result[0], f_result);
prop_assert_eq!(batch_result[1], g_result);
}
}
#[test]
fn test_barycentric_weights_empty() {
assert_eq!(barycentric_weights::<F>(&[]), Some(vec![]));
}
#[test]
fn test_barycentric_weights_duplicates() {
let xs = [F::from_u32(1), F::from_u32(2), F::from_u32(1)];
assert_eq!(barycentric_weights(&xs), None);
}
#[test]
fn test_barycentric_weights_known() {
let xs = [F::from_u32(0), F::from_u32(1), F::from_u32(2)];
let ws = barycentric_weights(&xs).unwrap();
let half = F::TWO.inverse();
assert_eq!(ws, vec![half, -F::ONE, half]);
}
#[test]
fn test_interpolate_arbitrary_known_quadratic() {
let xs = [F::from_u32(0), F::from_u32(1), F::from_u32(2)];
let evals = RowMajorMatrix::new(vec![F::from_u32(3), F::from_u32(6), F::from_u32(11)], 1);
let result = evals.interpolate_arbitrary_point(&xs, F::from_u32(100));
assert_eq!(result, Some(vec![F::from_u32(10203)]));
}
#[test]
fn test_interpolate_arbitrary_point_on_domain() {
let xs = [F::from_u32(0), F::from_u32(1), F::from_u32(2)];
let evals = RowMajorMatrix::new(vec![F::from_u32(3), F::from_u32(6), F::from_u32(11)], 1);
let result = evals.interpolate_arbitrary_point(&xs, F::from_u32(1));
assert_eq!(result, Some(vec![F::from_u32(6)]));
}
#[test]
fn test_interpolate_arbitrary_duplicates() {
let xs = [F::from_u32(1), F::from_u32(1)];
let evals = RowMajorMatrix::new(vec![F::from_u32(5), F::from_u32(7)], 1);
assert_eq!(
evals.interpolate_arbitrary_point(&xs, F::from_u32(42)),
None
);
}
#[test]
fn test_interpolate_arbitrary_duplicates_target_on_duplicate() {
let xs = [F::ONE, F::TWO, F::ONE];
let evals = RowMajorMatrix::new(vec![F::from_u32(10), F::from_u32(20), F::from_u32(30)], 1);
assert_eq!(evals.interpolate_arbitrary_point(&xs, F::ONE), None);
}
#[test]
fn test_interpolate_arbitrary_duplicates_target_on_unique() {
let xs = [F::ONE, F::TWO, F::ONE];
let evals = RowMajorMatrix::new(vec![F::from_u32(10), F::from_u32(20), F::from_u32(30)], 1);
assert_eq!(evals.interpolate_arbitrary_point(&xs, F::TWO), None);
}
#[test]
fn test_interpolate_arbitrary_multi_column() {
let xs = [F::from_u32(0), F::from_u32(1), F::from_u32(2)];
let evals = RowMajorMatrix::new(
vec![
F::from_u32(3),
F::from_u32(6), F::from_u32(6),
F::from_u32(15), F::from_u32(11),
F::from_u32(32), ],
2,
);
let result = evals
.interpolate_arbitrary_point(&xs, F::from_u32(100))
.unwrap();
assert_eq!(result, vec![F::from_u32(10203), F::from_u32(40506)]);
}
#[test]
fn test_interpolate_arbitrary_with_precomputation_equivalence() {
let xs = [F::from_u32(0), F::from_u32(1), F::from_u32(2)];
let evals = RowMajorMatrix::new(vec![F::from_u32(3), F::from_u32(6), F::from_u32(11)], 1);
let point = F::from_u32(100);
let standard = evals.interpolate_arbitrary_point(&xs, point).unwrap();
let weights = barycentric_weights(&xs).unwrap();
let diffs: Vec<F> = xs.iter().map(|&x| point - x).collect();
let diff_invs = batch_multiplicative_inverse(&diffs);
let precomp = evals.interpolate_arbitrary_with_precomputation(&weights, &diff_invs);
assert_eq!(standard, precomp);
}
#[test]
fn test_interpolate_arbitrary_extension_point() {
let xs = [F::from_u32(0), F::from_u32(1), F::from_u32(2)];
let evals = RowMajorMatrix::new(vec![F::from_u32(3), F::from_u32(6), F::from_u32(11)], 1);
let point = EF4::GENERATOR;
let result = evals.interpolate_arbitrary_point(&xs, point).unwrap();
let expected = point * point + point * F::TWO + EF4::from(F::from_u32(3));
assert_eq!(result, vec![expected]);
}
#[test]
fn test_interpolate_arbitrary_extension_point_on_domain() {
let xs = [F::from_u32(0), F::from_u32(1), F::from_u32(2)];
let evals = RowMajorMatrix::new(vec![F::from_u32(3), F::from_u32(6), F::from_u32(11)], 1);
let point = EF4::from(F::from_u32(1));
let result = evals.interpolate_arbitrary_point(&xs, point).unwrap();
assert_eq!(result, vec![EF4::from(F::from_u32(6))]);
}
#[test]
fn test_recover_coefficients_known_quadratic() {
let xs = [F::from_u32(0), F::from_u32(1), F::from_u32(2)];
let evals = RowMajorMatrix::new(vec![F::from_u32(3), F::from_u32(6), F::from_u32(11)], 1);
let coeffs = evals.recover_coefficients(&xs).unwrap();
assert_eq!(
coeffs.values,
vec![F::from_u32(3), F::from_u32(2), F::from_u32(1)]
);
}
#[test]
fn test_recover_coefficients_multi_column() {
let xs = [F::from_u32(0), F::from_u32(1), F::from_u32(2)];
let evals = RowMajorMatrix::new(
vec![
F::from_u32(3),
F::from_u32(6), F::from_u32(6),
F::from_u32(15), F::from_u32(11),
F::from_u32(32), ],
2,
);
let coeffs = evals.recover_coefficients(&xs).unwrap();
assert_eq!(
coeffs.values,
vec![
F::from_u32(3),
F::from_u32(6),
F::from_u32(2),
F::from_u32(5),
F::from_u32(1),
F::from_u32(4),
]
);
}
#[test]
fn test_interpolate_arbitrary_empty_matrix() {
let xs: Vec<F> = vec![];
let evals = RowMajorMatrix::<F>::new(vec![], 3);
let result = evals.interpolate_arbitrary_point(&xs, F::from_u32(42));
assert_eq!(result, Some(vec![F::ZERO, F::ZERO, F::ZERO]));
}
#[test]
fn test_interpolate_arbitrary_with_precomputation_empty_direct() {
let weights: Vec<F> = vec![];
let diff_invs: Vec<F> = vec![];
let evals = RowMajorMatrix::<F>::new(vec![], 5);
let result = evals.interpolate_arbitrary_with_precomputation(&weights, &diff_invs);
assert_eq!(result, vec![F::ZERO; 5]);
}
#[test]
fn test_lagrange_empty() {
assert_eq!(interpolate_lagrange::<F>(&[]), Some(vec![]));
}
#[test]
fn test_lagrange_single_point() {
let points = [(F::from_u32(7), F::from_u32(42))];
assert_eq!(interpolate_lagrange(&points), Some(vec![F::from_u32(42)]));
}
#[test]
fn test_lagrange_known_quadratic() {
let points = [
(F::from_u32(0), F::from_u32(3)),
(F::from_u32(1), F::from_u32(6)),
(F::from_u32(2), F::from_u32(11)),
];
let coeffs = interpolate_lagrange(&points).unwrap();
assert_eq!(coeffs, vec![F::from_u32(3), F::from_u32(2), F::from_u32(1)]);
}
#[test]
fn test_lagrange_duplicate_x_returns_none() {
let points = [
(F::from_u32(1), F::from_u32(5)),
(F::from_u32(1), F::from_u32(7)),
];
assert_eq!(interpolate_lagrange(&points), None);
}
proptest! {
#[test]
fn prop_lagrange_roundtrip(
n in 1usize..=8,
coeffs_raw in prop::collection::vec(0u32..2013265921, 1..=8),
) {
let mut coeffs: Vec<F> = coeffs_raw.iter().take(n).map(|&v| F::from_u32(v)).collect();
coeffs.resize(n, F::ZERO);
let points: Vec<(F, F)> = (0..n)
.map(|i| {
let x = F::from_u32(i as u32);
let y = eval_poly(&coeffs, x);
(x, y)
})
.collect();
let recovered = interpolate_lagrange(&points).unwrap();
prop_assert_eq!(recovered, coeffs);
}
#[test]
fn prop_arbitrary_roundtrip(
n in 1usize..=8,
coeffs_raw in prop::collection::vec(0u32..2013265921, 1..=8),
point_raw in 1u32..2013265921u32,
) {
let mut coeffs: Vec<F> = coeffs_raw.iter().take(n).map(|&v| F::from_u32(v)).collect();
coeffs.resize(n, F::ZERO);
let xs: Vec<F> = (0..n).map(|i| F::from_u32(i as u32)).collect();
let ys: Vec<F> = xs.iter().map(|&x| eval_poly(&coeffs, x)).collect();
let evals = RowMajorMatrix::new(ys, 1);
let point = F::from_u32(point_raw);
let result = evals.interpolate_arbitrary_point(&xs, point).unwrap();
let expected = eval_poly(&coeffs, point);
prop_assert_eq!(result[0], expected);
}
#[test]
fn prop_recover_coefficients_roundtrip(
n in 1usize..=8,
coeffs_raw in prop::collection::vec(0u32..2013265921, 1..=8),
) {
let mut coeffs: Vec<F> = coeffs_raw.iter().take(n).map(|&v| F::from_u32(v)).collect();
coeffs.resize(n, F::ZERO);
let xs: Vec<F> = (0..n).map(|i| F::from_u32(i as u32)).collect();
let ys: Vec<F> = xs.iter().map(|&x| eval_poly(&coeffs, x)).collect();
let evals = RowMajorMatrix::new(ys, 1);
let recovered = evals.recover_coefficients(&xs).unwrap();
prop_assert_eq!(recovered.values, coeffs);
}
#[test]
fn prop_arbitrary_batch_equals_individual(
n in 1usize..=6,
f_raw in prop::collection::vec(0u32..2013265921, 1..=6),
g_raw in prop::collection::vec(0u32..2013265921, 1..=6),
point_raw in 1u32..2013265921u32,
) {
let mut f_coeffs: Vec<F> = f_raw.iter().take(n).map(|&v| F::from_u32(v)).collect();
let mut g_coeffs: Vec<F> = g_raw.iter().take(n).map(|&v| F::from_u32(v)).collect();
f_coeffs.resize(n, F::ZERO);
g_coeffs.resize(n, F::ZERO);
let xs: Vec<F> = (0..n).map(|i| F::from_u32(i as u32)).collect();
let f_ys: Vec<F> = xs.iter().map(|&x| eval_poly(&f_coeffs, x)).collect();
let g_ys: Vec<F> = xs.iter().map(|&x| eval_poly(&g_coeffs, x)).collect();
let batch_vals: Vec<F> = f_ys.iter().zip(&g_ys)
.flat_map(|(&f, &g)| vec![f, g])
.collect();
let batch_mat = RowMajorMatrix::new(batch_vals, 2);
let f_mat = RowMajorMatrix::new(f_ys, 1);
let g_mat = RowMajorMatrix::new(g_ys, 1);
let point = F::from_u32(point_raw);
let batch_result = batch_mat.interpolate_arbitrary_point(&xs, point).unwrap();
let f_result = f_mat.interpolate_arbitrary_point(&xs, point).unwrap()[0];
let g_result = g_mat.interpolate_arbitrary_point(&xs, point).unwrap()[0];
prop_assert_eq!(batch_result[0], f_result);
prop_assert_eq!(batch_result[1], g_result);
}
#[test]
fn prop_precomputation_equivalence_arbitrary(
n in 1usize..=8,
coeffs_raw in prop::collection::vec(0u32..2013265921, 1..=8),
point_raw in 1u32..2013265921u32,
) {
let mut coeffs: Vec<F> = coeffs_raw.iter().take(n).map(|&v| F::from_u32(v)).collect();
coeffs.resize(n, F::ZERO);
let xs: Vec<F> = (0..n).map(|i| F::from_u32(i as u32)).collect();
let ys: Vec<F> = xs.iter().map(|&x| eval_poly(&coeffs, x)).collect();
let evals = RowMajorMatrix::new(ys, 1);
let point = F::from_u32(point_raw);
let standard = evals.interpolate_arbitrary_point(&xs, point).unwrap();
let weights = barycentric_weights(&xs).unwrap();
let diffs: Vec<F> = xs.iter().map(|&x| point - x).collect();
if diffs.iter().any(|d| d.is_zero()) {
return Ok(());
}
let diff_invs = batch_multiplicative_inverse(&diffs);
let precomp = evals.interpolate_arbitrary_with_precomputation(&weights, &diff_invs);
prop_assert_eq!(standard, precomp);
}
#[test]
fn prop_arbitrary_roundtrip_extension_point(
n in 1usize..=8,
coeffs_raw in prop::collection::vec(0u32..2013265921, 1..=8),
point_raw in prop::collection::vec(0u32..2013265921, 4..=4),
) {
let mut coeffs: Vec<F> = coeffs_raw.iter().take(n).map(|&v| F::from_u32(v)).collect();
coeffs.resize(n, F::ZERO);
let xs: Vec<F> = (0..n).map(|i| F::from_u32(i as u32)).collect();
let ys: Vec<F> = xs.iter().map(|&x| eval_poly(&coeffs, x)).collect();
let evals = RowMajorMatrix::new(ys, 1);
let point = EF4::from_basis_coefficients_iter(
point_raw.iter().map(|&v| F::from_u32(v)),
).unwrap();
let result = evals.interpolate_arbitrary_point(&xs, point).unwrap();
let expected: EF4 = eval_poly(&coeffs, point);
prop_assert_eq!(result[0], expected);
}
}
}