diff --git a/crates/prover/src/core/backend/cpu/quotients.rs b/crates/prover/src/core/backend/cpu/quotients.rs index 9cdb8efec..b9c62da85 100644 --- a/crates/prover/src/core/backend/cpu/quotients.rs +++ b/crates/prover/src/core/backend/cpu/quotients.rs @@ -1,17 +1,18 @@ -use itertools::izip; -use num_traits::Zero; +use itertools::{izip, zip_eq}; +use num_traits::{One, Zero}; use super::CpuBackend; use crate::core::backend::{Backend, Col}; -use crate::core::constraints::point_vanishing_fraction; +use crate::core::circle::CirclePoint; +use crate::core::constraints::{complex_conjugate_line_coeffs, pair_vanishing}; use crate::core::fields::m31::BaseField; use crate::core::fields::qm31::SecureField; use crate::core::fields::secure_column::SecureColumn; -use crate::core::fields::FieldExpOps; -use crate::core::pcs::quotients::{ColumnSampleBatch, QuotientOps}; +use crate::core::fields::{ComplexConjugate, FieldExpOps}; +use crate::core::pcs::quotients::{ColumnSampleBatch, PointSample, QuotientOps}; use crate::core::poly::circle::{CircleDomain, CircleEvaluation, SecureEvaluation}; use crate::core::poly::BitReversedOrder; -use crate::core::utils::bit_reverse; +use crate::core::utils::{bit_reverse, bit_reverse_index}; impl QuotientOps for CpuBackend { fn accumulate_quotients( @@ -26,33 +27,41 @@ impl QuotientOps for CpuBackend { // TODO(spapini): bit reverse iterator. for row in 0..domain.size() { // TODO(alonh): Make an efficient bit reverse domain iterator, possibly for AVX backend. - let row_value = - accumulate_row_quotients(sample_batches, columns, "ient_constants, row); + let domain_point = domain.at(bit_reverse_index(row, domain.log_size())); + let row_value = accumulate_row_quotients( + sample_batches, + columns, + "ient_constants, + row, + domain_point, + ); values.set(row, row_value); } SecureEvaluation { domain, values } } } -// TODO(Ohad): no longer using pair_vanishing, remove domain_point_vec and line_coeffs, or write a -// function that deals with quotients over pair_vanishing polynomials. pub fn accumulate_row_quotients( sample_batches: &[ColumnSampleBatch], columns: &[&CircleEvaluation], quotient_constants: &QuotientConstants, row: usize, + domain_point: CirclePoint, ) -> SecureField { let mut row_accumulator = SecureField::zero(); - for (sample_batch, batch_coeff, denominator_inverses) in izip!( + for (sample_batch, line_coeffs, batch_coeff, denominator_inverses) in izip!( sample_batches, + "ient_constants.line_coeffs, "ient_constants.batch_random_coeffs, "ient_constants.denominator_inverses ) { let mut numerator = SecureField::zero(); - for (column_index, sampled_value) in sample_batch.columns_and_values.iter() { + for ((column_index, _), (a, b, c)) in zip_eq(&sample_batch.columns_and_values, line_coeffs) + { let column = &columns[*column_index]; - let value = column[row]; - numerator += value - *sampled_value; + let value = column[row] * *c; + let linear_term = *a * domain_point.y + *b; + numerator += value - linear_term; } row_accumulator = row_accumulator * *batch_coeff + numerator * denominator_inverses[row]; @@ -60,6 +69,34 @@ pub fn accumulate_row_quotients( row_accumulator } +/// Precompute the complex conjugate line coefficients for each column in each sample batch. +/// Specifically, for the i-th (in a sample batch) column's numerator term +/// `alpha^i * (c * F(p) - (a * p.y + b))`, we precompute and return the constants: +/// (`alpha^i * a`, `alpha^i * b`, `alpha^i * c`). +pub fn column_line_coeffs( + sample_batches: &[ColumnSampleBatch], + random_coeff: SecureField, +) -> Vec> { + sample_batches + .iter() + .map(|sample_batch| { + let mut alpha = SecureField::one(); + sample_batch + .columns_and_values + .iter() + .map(|(_, sampled_value)| { + alpha *= random_coeff; + let sample = PointSample { + point: sample_batch.point, + value: *sampled_value, + }; + complex_conjugate_line_coeffs(&sample, alpha) + }) + .collect() + }) + .collect() +} + /// Precompute the random coefficients used to linearly combine the batched quotients. /// Specifically, for each sample batch we compute random_coeff^(number of columns in the batch), /// which is used to linearly combine the batch with the next one. @@ -77,24 +114,21 @@ fn denominator_inverses( sample_batches: &[ColumnSampleBatch], domain: CircleDomain, ) -> Vec> { - let n_fracions = sample_batches.len() * domain.size(); - let mut flat_denominators = Vec::with_capacity(n_fracions); - let mut numerator_terms = Vec::with_capacity(n_fracions); + let mut flat_denominators = Vec::with_capacity(sample_batches.len() * domain.size()); for sample_batch in sample_batches { for row in 0..domain.size() { let domain_point = domain.at(row); - let (num, denom) = point_vanishing_fraction(sample_batch.point, domain_point); - flat_denominators.push(num); - numerator_terms.push(denom); + let denominator = pair_vanishing( + sample_batch.point, + sample_batch.point.complex_conjugate(), + domain_point.into_ef(), + ); + flat_denominators.push(denominator); } } let mut flat_denominator_inverses = vec![SecureField::zero(); flat_denominators.len()]; SecureField::batch_inverse(&flat_denominators, &mut flat_denominator_inverses); - flat_denominator_inverses - .iter_mut() - .zip(&numerator_terms) - .for_each(|(inv, num_term)| *inv *= *num_term); flat_denominator_inverses .chunks_mut(domain.size()) @@ -110,9 +144,11 @@ pub fn quotient_constants( random_coeff: SecureField, domain: CircleDomain, ) -> QuotientConstants { + let line_coeffs = column_line_coeffs(sample_batches, random_coeff); let batch_random_coeffs = batch_random_coeffs(sample_batches, random_coeff); let denominator_inverses = denominator_inverses(sample_batches, domain); QuotientConstants { + line_coeffs, batch_random_coeffs, denominator_inverses, } @@ -120,6 +156,9 @@ pub fn quotient_constants( /// Holds the precomputed constant values used in each quotient evaluation. pub struct QuotientConstants { + /// The line coefficients for each quotient numerator term. For more details see + /// [self::column_line_coeffs]. + pub line_coeffs: Vec>, /// The random coefficients used to linearly combine the batched quotients For more details see /// [self::batch_random_coeffs]. pub batch_random_coeffs: Vec, diff --git a/crates/prover/src/core/backend/simd/quotients.rs b/crates/prover/src/core/backend/simd/quotients.rs index b011bd098..ddbd387d9 100644 --- a/crates/prover/src/core/backend/simd/quotients.rs +++ b/crates/prover/src/core/backend/simd/quotients.rs @@ -1,19 +1,19 @@ -use std::iter::zip; - -use itertools::izip; -use num_traits::{One, Zero}; +use itertools::{izip, zip_eq, Itertools}; +use num_traits::Zero; use super::column::SecureFieldVec; use super::m31::{PackedBaseField, LOG_N_LANES, N_LANES}; use super::qm31::PackedSecureField; use super::SimdBackend; -use crate::core::backend::cpu::quotients::{batch_random_coeffs, QuotientConstants}; +use crate::core::backend::cpu::quotients::{ + batch_random_coeffs, column_line_coeffs, QuotientConstants, +}; use crate::core::backend::{Col, Column}; use crate::core::circle::CirclePoint; use crate::core::fields::m31::BaseField; use crate::core::fields::qm31::SecureField; use crate::core::fields::secure_column::SecureColumn; -use crate::core::fields::FieldOps; +use crate::core::fields::{ComplexConjugate, FieldOps}; use crate::core::pcs::quotients::{ColumnSampleBatch, QuotientOps}; use crate::core::poly::circle::{CircleDomain, CircleEvaluation, SecureEvaluation}; use crate::core::poly::BitReversedOrder; @@ -59,19 +59,30 @@ pub fn accumulate_row_quotients( columns: &[&CircleEvaluation], quotient_constants: &QuotientConstants, vec_row: usize, - _domain_point_vec: (PackedBaseField, PackedBaseField), + domain_point_vec: (PackedBaseField, PackedBaseField), ) -> PackedSecureField { let mut row_accumulator = PackedSecureField::zero(); - for (sample_batch, batch_coeff, denominator_inverses) in izip!( + for (sample_batch, line_coeffs, batch_coeff, denominator_inverses) in izip!( sample_batches, + "ient_constants.line_coeffs, "ient_constants.batch_random_coeffs, "ient_constants.denominator_inverses ) { let mut numerator = PackedSecureField::zero(); - for (column_index, sampled_value) in sample_batch.columns_and_values.iter() { + for ((column_index, _), (a, b, c)) in zip_eq(&sample_batch.columns_and_values, line_coeffs) + { let column = &columns[*column_index]; - let value = column.data[vec_row]; - numerator += PackedSecureField::broadcast(-*sampled_value) + value; + let value = PackedSecureField::broadcast(*c) * column.data[vec_row]; + // The numerator is a line equation passing through + // (sample_point.y, sample_value), (conj(sample_point), conj(sample_value)) + // evaluated at (domain_point.y, value). + // When substituting a polynomial in this line equation, we get a polynomial with a root + // at sample_point and conj(sample_point) if the original polynomial had the values + // sample_value and conj(sample_value) at these points. + // TODO(AlonH): Use single point vanishing to save a multiplication. + let linear_term = PackedSecureField::broadcast(*a) * domain_point_vec.1 + + PackedSecureField::broadcast(*b); + numerator += value - linear_term; } row_accumulator = row_accumulator * PackedSecureField::broadcast(*batch_coeff) @@ -80,65 +91,53 @@ pub fn accumulate_row_quotients( row_accumulator } -/// Point vanishing for the packed representation of the points. skips the division. -/// See [crate::core::constraints::point_vanishing_fraction] for more details. -fn packed_point_vanishing_fraction( - excluded: CirclePoint, - p: (PackedBaseField, PackedBaseField), -) -> (PackedSecureField, PackedSecureField) { - let e_conjugate = excluded.conjugate(); - let h_x = PackedSecureField::broadcast(e_conjugate.x) * p.0 - - PackedSecureField::broadcast(e_conjugate.y) * p.1; - let h_y = PackedSecureField::broadcast(e_conjugate.y) * p.0 - + PackedSecureField::broadcast(e_conjugate.x) * p.1; - (h_y, PackedSecureField::one() + h_x) +/// Pair vanishing for the packed representation of the points. See +/// [crate::core::constraints::pair_vanishing] for more details. +fn packed_pair_vanishing( + excluded0: CirclePoint, + excluded1: CirclePoint, + packed_p: (PackedBaseField, PackedBaseField), +) -> PackedSecureField { + PackedSecureField::broadcast(excluded0.y - excluded1.y) * packed_p.0 + + PackedSecureField::broadcast(excluded1.x - excluded0.x) * packed_p.1 + + PackedSecureField::broadcast(excluded0.x * excluded1.y - excluded0.y * excluded1.x) } fn denominator_inverses( sample_batches: &[ColumnSampleBatch], domain: CircleDomain, ) -> Vec> { - let mut numerators = Vec::new(); - let mut denominators = Vec::new(); - - for sample_batch in sample_batches { - for vec_row in 0..1 << (domain.log_size() - LOG_N_LANES) { - // TODO(spapini): Optimize this, for the small number of columns case. - let points = std::array::from_fn(|i| { - domain.at(bit_reverse_index( - (vec_row << LOG_N_LANES) + i, - domain.log_size(), - )) - }); - let domain_points_x = PackedBaseField::from_array(points.map(|p| p.x)); - let domain_points_y = PackedBaseField::from_array(points.map(|p| p.y)); - let domain_point_vec = (domain_points_x, domain_points_y); - let (denominator, numerator) = - packed_point_vanishing_fraction(sample_batch.point, domain_point_vec); - denominators.push(denominator); - numerators.push(numerator); - } - } - - let denominators = SecureFieldVec { - length: denominators.len() * N_LANES, - data: denominators, - }; - - let numerators = SecureFieldVec { - length: numerators.len() * N_LANES, - data: numerators, - }; - - let mut flat_denominator_inverses = SecureFieldVec::zeros(denominators.len()); + let flat_denominators: SecureFieldVec = sample_batches + .iter() + .flat_map(|sample_batch| { + (0..(1 << (domain.log_size() - LOG_N_LANES))) + .map(|vec_row| { + // TODO(spapini): Optimize this, for the small number of columns case. + let points = std::array::from_fn(|i| { + domain.at(bit_reverse_index( + (vec_row << LOG_N_LANES) + i, + domain.log_size(), + )) + }); + let domain_points_x = PackedBaseField::from_array(points.map(|p| p.x)); + let domain_points_y = PackedBaseField::from_array(points.map(|p| p.y)); + let domain_point_vec = (domain_points_x, domain_points_y); + packed_pair_vanishing( + sample_batch.point, + sample_batch.point.complex_conjugate(), + domain_point_vec, + ) + }) + .collect_vec() + }) + .collect(); + + let mut flat_denominator_inverses = SecureFieldVec::zeros(flat_denominators.len()); >::batch_inverse( - &denominators, + &flat_denominators, &mut flat_denominator_inverses, ); - zip(&mut flat_denominator_inverses.data, &numerators.data) - .for_each(|(inv, denom_denom)| *inv *= *denom_denom); - flat_denominator_inverses .data .chunks(domain.size() / N_LANES) @@ -151,9 +150,11 @@ fn quotient_constants( random_coeff: SecureField, domain: CircleDomain, ) -> QuotientConstants { + let line_coeffs = column_line_coeffs(sample_batches, random_coeff); let batch_random_coeffs = batch_random_coeffs(sample_batches, random_coeff); let denominator_inverses = denominator_inverses(sample_batches, domain); QuotientConstants { + line_coeffs, batch_random_coeffs, denominator_inverses, } diff --git a/crates/prover/src/core/constraints.rs b/crates/prover/src/core/constraints.rs index cc7523e2a..31711d98e 100644 --- a/crates/prover/src/core/constraints.rs +++ b/crates/prover/src/core/constraints.rs @@ -2,7 +2,10 @@ use num_traits::One; use super::circle::{CirclePoint, Coset}; use super::fields::m31::BaseField; +use super::fields::qm31::SecureField; use super::fields::ExtensionOf; +use super::pcs::quotients::PointSample; +use crate::core::fields::ComplexConjugate; /// Evaluates a vanishing polynomial of the coset at a point. pub fn coset_vanishing>(coset: Coset, mut p: CirclePoint) -> F { @@ -68,16 +71,45 @@ pub fn point_vanishing, EF: ExtensionOf>( h.y / (EF::one() + h.x) } -/// Evaluates a vanishing polynomial of the vanish_point at a point. -/// Note that this function has a pole on the antipode of the vanish_point. -/// Returns the result in a fraction form: (numerator, denominator). -// TODO(Ohad): reorganize these functions. -pub fn point_vanishing_fraction, EF: ExtensionOf>( - vanish_point: CirclePoint, - p: CirclePoint, -) -> (EF, EF) { - let h = p.into_ef() - vanish_point; - (h.y, (EF::one() + h.x)) +/// Evaluates a point on a line between a point and its complex conjugate. +/// Relies on the fact that every polynomial F over the base field holds: +/// F(p*) == F(p)* (* being the complex conjugate). +pub fn complex_conjugate_line( + point: CirclePoint, + value: SecureField, + p: CirclePoint, +) -> SecureField { + // TODO(AlonH): This assertion will fail at a probability of 1 to 2^62. Use a better solution. + assert_ne!( + point.y, + point.y.complex_conjugate(), + "Cannot evaluate a line with a single point ({point:?})." + ); + value + + (value.complex_conjugate() - value) * (-point.y + p.y) + / (point.complex_conjugate().y - point.y) +} + +/// Evaluates the coefficients of a line between a point and its complex conjugate. Specifically, +/// `a, b, and c, s.t. a*x + b -c*y = 0` for (x,y) being (sample.y, sample.value) and +/// (conj(sample.y), conj(sample.value)). +/// Relies on the fact that every polynomial F over the base +/// field holds: F(p*) == F(p)* (* being the complex conjugate). +pub fn complex_conjugate_line_coeffs( + sample: &PointSample, + alpha: SecureField, +) -> (SecureField, SecureField, SecureField) { + // TODO(AlonH): This assertion will fail at a probability of 1 to 2^62. Use a better solution. + assert_ne!( + sample.point.y, + sample.point.y.complex_conjugate(), + "Cannot evaluate a line with a single point ({:?}).", + sample.point + ); + let a = sample.value.complex_conjugate() - sample.value; + let c = sample.point.complex_conjugate().y - sample.point.y; + let b = sample.value * c - a * sample.point.y; + (alpha * a, alpha * b, alpha * c) } #[cfg(test)] @@ -85,11 +117,15 @@ mod tests { use num_traits::Zero; use super::{coset_vanishing, point_excluder, point_vanishing}; - use crate::core::backend::cpu::CpuCirclePoly; + use crate::core::backend::cpu::{CpuCircleEvaluation, CpuCirclePoly}; use crate::core::circle::{CirclePoint, CirclePointIndex, Coset}; - use crate::core::constraints::pair_vanishing; + use crate::core::constraints::{complex_conjugate_line, pair_vanishing}; use crate::core::fields::m31::{BaseField, M31}; + use crate::core::fields::qm31::SecureField; use crate::core::fields::{ComplexConjugate, FieldExpOps}; + use crate::core::poly::circle::CanonicCoset; + use crate::core::poly::NaturalOrder; + use crate::core::test_utils::secure_eval_to_base_eval; use crate::m31; #[test] @@ -171,4 +207,44 @@ mod tests { polynomial.eval_at_point(oods_point).complex_conjugate() ); } + + #[test] + fn test_point_vanishing_degree() { + // Create a polynomial over a circle domain. + let log_domain_size = 7; + let domain_size = 1 << log_domain_size; + let polynomial = CpuCirclePoly::new((0..domain_size).map(|i| m31!(i)).collect()); + + // Create a larger domain. + let log_large_domain_size = log_domain_size + 1; + let large_domain_size = 1 << log_large_domain_size; + let large_domain = CanonicCoset::new(log_large_domain_size).circle_domain(); + + // Create a vanish point that is not in the large domain. + let vanish_point = CirclePoint::get_point(97); + let vanish_point_value = polynomial.eval_at_point(vanish_point); + + // Compute the quotient polynomial. + let mut quotient_polynomial_values = Vec::with_capacity(large_domain_size as usize); + for point in large_domain.iter() { + let line = complex_conjugate_line(vanish_point, vanish_point_value, point); + let mut value = polynomial.eval_at_point(point.into_ef()) - line; + value /= pair_vanishing( + vanish_point, + vanish_point.complex_conjugate(), + point.into_ef(), + ); + quotient_polynomial_values.push(value); + } + let quotient_evaluation = CpuCircleEvaluation::::new( + large_domain, + quotient_polynomial_values, + ); + let quotient_polynomial = secure_eval_to_base_eval("ient_evaluation) + .bit_reverse() + .interpolate(); + + // Check that the quotient polynomial is indeed in the wanted fft space. + assert!(quotient_polynomial.is_in_fft_space(log_domain_size)); + } } diff --git a/crates/prover/src/core/pcs/quotients.rs b/crates/prover/src/core/pcs/quotients.rs index 285d754cf..03f5e989e 100644 --- a/crates/prover/src/core/pcs/quotients.rs +++ b/crates/prover/src/core/pcs/quotients.rs @@ -16,6 +16,7 @@ use crate::core::poly::circle::{ use crate::core::poly::BitReversedOrder; use crate::core::prover::VerificationError; use crate::core::queries::SparseSubCircleDomain; +use crate::core::utils::bit_reverse_index; pub trait QuotientOps: PolyOps { /// Accumulates the quotients of the columns at the given domain. @@ -152,11 +153,13 @@ pub fn fri_answers_for_log_size( // TODO(spapini): bit reverse iterator. let mut values = Vec::new(); for row in 0..domain.size() { + let domain_point = domain.at(bit_reverse_index(row, log_size)); let value = accumulate_row_quotients( &sample_batches, &column_evals.iter().collect_vec(), "ient_constants, row, + domain_point, ); values.push(value); }