use itertools::Itertools;
use crate::core::fft::ibutterfly;
use crate::core::fields::m31::BaseField;
use crate::core::fields::ExtensionOf;
use crate::core::poly::line::{LineDomain, LinePoly};
use crate::prover::backend::{ColumnOps, CpuBackend};
use crate::prover::secure_column::SecureColumnByCoords;
#[derive(Clone, Debug)]
pub struct LineEvaluation<B: ColumnOps<BaseField>> {
pub values: SecureColumnByCoords<B>,
domain: LineDomain,
}
impl<B: ColumnOps<BaseField>> LineEvaluation<B> {
pub fn new(domain: LineDomain, values: SecureColumnByCoords<B>) -> Self {
assert_eq!(values.len(), domain.size());
Self { values, domain }
}
pub fn new_zero(domain: LineDomain) -> Self {
Self::new(domain, SecureColumnByCoords::zeros(domain.size()))
}
#[allow(clippy::len_without_is_empty)]
pub const fn len(&self) -> usize {
1 << self.domain.log_size()
}
pub const fn domain(&self) -> LineDomain {
self.domain
}
pub fn to_cpu(&self) -> LineEvaluation<CpuBackend> {
LineEvaluation::new(self.domain, self.values.to_cpu())
}
}
impl LineEvaluation<CpuBackend> {
pub fn interpolate(self) -> LinePoly {
let mut values = self.values.into_iter().collect_vec();
CpuBackend::bit_reverse_column(&mut values);
line_ifft(&mut values, self.domain);
let len_inv = BaseField::from(values.len()).inverse();
values.iter_mut().for_each(|v| *v *= len_inv);
LinePoly::new(values)
}
}
fn line_ifft<F: ExtensionOf<BaseField> + Copy>(values: &mut [F], mut domain: LineDomain) {
assert_eq!(values.len(), domain.size());
while domain.size() > 1 {
for chunk in values.chunks_exact_mut(domain.size()) {
let (l, r) = chunk.split_at_mut(domain.size() / 2);
for (i, x) in domain.iter().take(domain.size() / 2).enumerate() {
ibutterfly(&mut l[i], &mut r[i], x.inverse());
}
}
domain = domain.double();
}
}
#[cfg(test)]
mod tests {
use itertools::Itertools;
use crate::core::circle::{CirclePoint, Coset};
use crate::core::fields::m31::BaseField;
use crate::core::poly::line::LineDomain;
use crate::core::utils::bit_reverse_index;
use crate::prover::backend::{ColumnOps, CpuBackend};
use crate::prover::line::{LineEvaluation, LinePoly};
#[test]
fn line_evaluation_interpolation() {
let coeffs = vec![
BaseField::from(7).into(), BaseField::from(9).into(), BaseField::from(5).into(), BaseField::from(3).into(), ];
let poly = LinePoly::new(coeffs.clone());
let coset = Coset::half_odds(poly.len().ilog2());
let domain = LineDomain::new(coset);
let mut values = domain
.iter()
.map(|x| {
let pi_x = CirclePoint::double_x(x);
coeffs[0] + coeffs[1] * pi_x + coeffs[2] * x + coeffs[3] * pi_x * x
})
.collect_vec();
CpuBackend::bit_reverse_column(&mut values);
let evals = LineEvaluation::<CpuBackend>::new(domain, values.into_iter().collect());
let interpolated_poly = evals.interpolate();
let mut coeffs = interpolated_poly.into_ordered_coefficients();
CpuBackend::bit_reverse_column(&mut coeffs);
assert_eq!(coeffs, coeffs);
}
#[test]
fn line_polynomial_eval_at_point() {
const LOG_SIZE: u32 = 2;
let coset = Coset::half_odds(LOG_SIZE);
let domain = LineDomain::new(coset);
let evals = LineEvaluation::<CpuBackend>::new(
domain,
(0..1 << LOG_SIZE)
.map(BaseField::from)
.map(|x| x.into())
.collect(),
);
let poly = evals.clone().interpolate();
for (i, x) in domain.iter().enumerate() {
assert_eq!(
poly.eval_at_point(x.into()),
evals.values.at(bit_reverse_index(i, domain.log_size())),
"mismatch at {i}"
);
}
}
}