use itertools::{izip, zip_eq, Itertools};
use num_traits::Zero;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
use tracing::{span, Level};
use super::cm31::PackedCM31;
use super::column::CM31Column;
use super::domain::CircleDomainBitRevIterator;
use super::m31::{PackedBaseField, LOG_N_LANES, N_LANES};
use super::qm31::PackedSecureField;
use super::SimdBackend;
use crate::core::circle::CirclePoint;
use crate::core::fields::m31::BaseField;
use crate::core::fields::qm31::{SecureField, SECURE_EXTENSION_DEGREE};
use crate::core::fields::FieldExpOps;
use crate::core::pcs::quotients::{batch_random_coeffs, column_line_coeffs, ColumnSampleBatch};
use crate::core::poly::circle::CircleDomain;
use crate::core::utils::bit_reverse;
use crate::prover::backend::simd::column::SecureColumnByCoordsMutSlice;
use crate::prover::backend::CpuBackend;
use crate::prover::poly::circle::{CircleEvaluation, PolyOps, SecureEvaluation};
use crate::prover::poly::BitReversedOrder;
use crate::prover::secure_column::SecureColumnByCoords;
use crate::prover::QuotientOps;
pub struct QuotientConstants {
pub line_coeffs: Vec<Vec<(SecureField, SecureField, SecureField)>>,
pub batch_random_coeffs: Vec<SecureField>,
pub denominator_inverses: Vec<CM31Column>,
}
impl QuotientOps for SimdBackend {
fn accumulate_quotients(
domain: CircleDomain,
columns: &[&CircleEvaluation<Self, BaseField, BitReversedOrder>],
random_coeff: SecureField,
sample_batches: &[ColumnSampleBatch],
log_blowup_factor: u32,
) -> SecureEvaluation<Self, BitReversedOrder> {
let (subdomain, mut subdomain_shifts) = domain.split(log_blowup_factor);
if subdomain.log_size() < LOG_N_LANES + 2 {
let columns = columns
.iter()
.map(|circle_eval| circle_eval.to_cpu())
.collect_vec();
let eval = CpuBackend::accumulate_quotients(
domain,
&columns.iter().collect_vec(),
random_coeff,
sample_batches,
log_blowup_factor,
);
return SecureEvaluation::new(
domain,
SecureColumnByCoords::from_iter(eval.values.to_vec()),
);
}
bit_reverse(&mut subdomain_shifts);
let (span, mut extended_eval, subeval_polys) = accumulate_quotients_on_subdomain(
subdomain,
sample_batches,
random_coeff,
columns,
domain,
);
for (ci, &c) in subdomain_shifts.iter().enumerate() {
let subdomain = subdomain.shift(c);
let twiddles = SimdBackend::precompute_twiddles(subdomain.half_coset);
#[allow(clippy::needless_range_loop)]
for i in 0..SECURE_EXTENSION_DEGREE {
let eval = subeval_polys[i].evaluate_with_twiddles(subdomain, &twiddles);
extended_eval.columns[i].data[(ci * eval.data.len())..((ci + 1) * eval.data.len())]
.copy_from_slice(&eval.data);
}
}
span.exit();
SecureEvaluation::new(domain, extended_eval)
}
}
fn accumulate_quotients_on_subdomain(
subdomain: CircleDomain,
sample_batches: &[ColumnSampleBatch],
random_coeff: SecureField,
columns: &[&CircleEvaluation<SimdBackend, BaseField, BitReversedOrder>],
domain: CircleDomain,
) -> (
span::EnteredSpan,
SecureColumnByCoords<SimdBackend>,
[crate::prover::poly::circle::CirclePoly<SimdBackend>; 4],
) {
assert!(subdomain.log_size() >= LOG_N_LANES + 2);
let mut values =
unsafe { SecureColumnByCoords::<SimdBackend>::uninitialized(subdomain.size()) };
let quotient_constants = quotient_constants(sample_batches, random_coeff, subdomain);
let span = span!(
Level::INFO,
"Quotient accumulation",
class = "FRIQuotientAccumulation"
)
.entered();
let quad_rows = CircleDomainBitRevIterator::new(subdomain);
let accumulate = |(quad_row, (points, mut values_dst)): (
usize,
(
[CirclePoint<PackedBaseField>; 4],
SecureColumnByCoordsMutSlice<'_>,
),
)| {
let (y01, _) = points[0].y.deinterleave(points[1].y);
let (y23, _) = points[2].y.deinterleave(points[3].y);
let (spaced_ys, _) = y01.deinterleave(y23);
let row_accumulator = accumulate_row_quotients(
sample_batches,
columns,
"ient_constants,
quad_row,
spaced_ys,
);
unsafe {
values_dst.set_packed(0, row_accumulator[0]);
values_dst.set_packed(1, row_accumulator[1]);
values_dst.set_packed(2, row_accumulator[2]);
values_dst.set_packed(3, row_accumulator[3]);
}
};
#[cfg(not(feature = "parallel"))]
let iter = quad_rows
.array_chunks::<4>()
.zip(values.chunks_mut(4))
.enumerate();
#[cfg(feature = "parallel")]
let iter = {
const CHUNK_SIZE: usize = 1 << 12;
values
.par_chunks_mut(CHUNK_SIZE)
.enumerate()
.flat_map_iter(|(chunk_idx, values_dst)| {
let vec_offset = chunk_idx * CHUNK_SIZE;
let quad_rows = quad_rows.start_at(vec_offset).array_chunks::<4>();
let values_dst = {
let [a, b, c, d] = values_dst.0.map(|x| x.0);
izip!(
a.chunks_mut(4),
b.chunks_mut(4),
c.chunks_mut(4),
d.chunks_mut(4)
)
.map(|(a, b, c, d)| unsafe {
SecureColumnByCoordsMutSlice::from_coordinates_unchecked([a, b, c, d])
})
};
(vec_offset / 4..).zip(quad_rows.zip(values_dst))
})
};
iter.for_each(accumulate);
span.exit();
let span = span!(
Level::INFO,
"Quotient extension",
class = "FRIQuotientExtension"
)
.entered();
let extended_eval =
unsafe { SecureColumnByCoords::<SimdBackend>::uninitialized(domain.size()) };
let mut i = 0;
let values = values.columns;
let twiddles = SimdBackend::precompute_twiddles(subdomain.half_coset);
let subeval_polys = values.map(|c| {
i += 1;
CircleEvaluation::<SimdBackend, BaseField, BitReversedOrder>::new(subdomain, c)
.interpolate_with_twiddles(&twiddles)
});
(span, extended_eval, subeval_polys)
}
pub fn accumulate_row_quotients(
sample_batches: &[ColumnSampleBatch],
columns: &[&CircleEvaluation<SimdBackend, BaseField, BitReversedOrder>],
quotient_constants: &QuotientConstants,
quad_row: usize,
spaced_ys: PackedBaseField,
) -> [PackedSecureField; 4] {
let mut row_accumulator = [PackedSecureField::zero(); 4];
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(); 4];
for ((column_index, _), (a, b, c)) in zip_eq(&sample_batch.columns_and_values, line_coeffs)
{
let column = &columns[*column_index];
let cvalues: [_; 4] = std::array::from_fn(|i| {
PackedSecureField::broadcast(*c) * column.data[(quad_row << 2) + i]
});
let spaced_ay = PackedSecureField::broadcast(*a) * spaced_ys;
let (t0, t1) = spaced_ay.interleave(-spaced_ay);
let (t2, t3) = t0.interleave(-t0);
let (t4, t5) = t1.interleave(-t1);
let ay = [t2, t3, t4, t5];
for i in 0..4 {
numerator[i] += cvalues[i] - ay[i] - PackedSecureField::broadcast(*b);
}
}
for i in 0..4 {
row_accumulator[i] = row_accumulator[i] * PackedSecureField::broadcast(*batch_coeff)
+ numerator[i] * denominator_inverses.data[(quad_row << 2) + i];
}
}
row_accumulator
}
fn denominator_inverses(
sample_batches: &[ColumnSampleBatch],
domain: CircleDomain,
) -> Vec<CM31Column> {
let domain_points = CircleDomainBitRevIterator::new(domain);
#[cfg(not(feature = "parallel"))]
let iter = domain_points;
#[cfg(feature = "parallel")]
let iter = domain_points.par_iter();
let flat_denominators: CM31Column = sample_batches
.iter()
.flat_map(|sample_batch| {
let prx = PackedCM31::broadcast(sample_batch.point.x.0);
let pry = PackedCM31::broadcast(sample_batch.point.y.0);
let pix = PackedCM31::broadcast(sample_batch.point.x.1);
let piy = PackedCM31::broadcast(sample_batch.point.y.1);
iter.clone()
.map(|points| (prx - points.x) * piy - (pry - points.y) * pix)
.collect::<Vec<_>>()
})
.collect();
flat_denominators
.data
.chunks(domain.size() / N_LANES)
.map(PackedCM31::batch_inverse)
.map(|data| CM31Column {
data,
length: domain.size(),
})
.collect()
}
fn quotient_constants(
sample_batches: &[ColumnSampleBatch],
random_coeff: SecureField,
domain: CircleDomain,
) -> QuotientConstants {
let _span = span!(
Level::INFO,
"Quotient constants",
class = "FRIQuotientConstants"
)
.entered();
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,
}
}
#[cfg(test)]
mod tests {
use itertools::Itertools;
use crate::core::circle::SECURE_FIELD_CIRCLE_GEN;
use crate::core::fields::m31::BaseField;
use crate::core::pcs::quotients::ColumnSampleBatch;
use crate::core::poly::circle::CanonicCoset;
use crate::prover::backend::simd::column::BaseColumn;
use crate::prover::backend::simd::SimdBackend;
use crate::prover::backend::{Column, CpuBackend};
use crate::prover::poly::circle::CircleEvaluation;
use crate::prover::poly::BitReversedOrder;
use crate::prover::QuotientOps;
use crate::qm31;
#[test]
fn test_accumulate_quotients() {
const LOG_SIZE: u32 = 8;
const LOG_BLOWUP_FACTOR: u32 = 1;
let small_domain = CanonicCoset::new(LOG_SIZE).circle_domain();
let domain = CanonicCoset::new(LOG_SIZE + LOG_BLOWUP_FACTOR).circle_domain();
let e0: BaseColumn = (0..small_domain.size()).map(BaseField::from).collect();
let e1: BaseColumn = (0..small_domain.size())
.map(|i| BaseField::from(2 * i))
.collect();
let polys = [
CircleEvaluation::<SimdBackend, BaseField, BitReversedOrder>::new(small_domain, e0)
.interpolate(),
CircleEvaluation::<SimdBackend, BaseField, BitReversedOrder>::new(small_domain, e1)
.interpolate(),
];
let columns = [polys[0].evaluate(domain), polys[1].evaluate(domain)];
let random_coeff = qm31!(1, 2, 3, 4);
let a = polys[0].eval_at_point(SECURE_FIELD_CIRCLE_GEN);
let b = polys[1].eval_at_point(SECURE_FIELD_CIRCLE_GEN);
let samples = vec![ColumnSampleBatch {
point: SECURE_FIELD_CIRCLE_GEN,
columns_and_values: vec![(0, a), (1, b)],
}];
let cpu_columns = columns
.iter()
.map(|c| CircleEvaluation::new(c.domain, c.values.to_cpu()))
.collect_vec();
let cpu_result = CpuBackend::accumulate_quotients(
domain,
&cpu_columns.iter().collect_vec(),
random_coeff,
&samples,
LOG_BLOWUP_FACTOR,
)
.values
.to_vec();
let res = SimdBackend::accumulate_quotients(
domain,
&columns.iter().collect_vec(),
random_coeff,
&samples,
LOG_BLOWUP_FACTOR,
)
.values
.to_vec();
assert_eq!(res, cpu_result);
}
}