use std::borrow::Cow;
use itertools::Itertools;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
use stwo::core::air::Component;
use stwo::core::constraints::coset_vanishing;
use stwo::core::fields::m31::BaseField;
use stwo::core::fields::qm31::SecureField;
use stwo::core::pcs::TreeVec;
use stwo::core::poly::circle::CanonicCoset;
use stwo::core::utils::bit_reverse;
use stwo::prover::backend::simd::column::VeryPackedSecureColumnByCoords;
use stwo::prover::backend::simd::m31::LOG_N_LANES;
use stwo::prover::backend::simd::very_packed_m31::{VeryPackedBaseField, LOG_N_VERY_PACKED_ELEMS};
use stwo::prover::backend::simd::SimdBackend;
use stwo::prover::backend::CpuBackend;
use stwo::prover::poly::circle::{CircleEvaluation, PolyOps};
use stwo::prover::poly::BitReversedOrder;
use stwo::prover::secure_column::SecureColumnByCoords;
use stwo::prover::{ComponentProver, DomainEvaluationAccumulator, Trace};
use tracing::{span, Level};
use super::{CpuDomainEvaluator, SimdDomainEvaluator};
use crate::{FrameworkComponent, FrameworkEval, PREPROCESSED_TRACE_IDX};
const CHUNK_SIZE: usize = 1;
impl<E: FrameworkEval + Sync> ComponentProver<SimdBackend> for FrameworkComponent<E> {
fn evaluate_constraint_quotients_on_domain(
&self,
trace: &Trace<'_, SimdBackend>,
evaluation_accumulator: &mut DomainEvaluationAccumulator<SimdBackend>,
) {
if self.n_constraints() == 0 {
return;
}
if self.is_disabled() {
evaluation_accumulator.skip_coeffs(self.n_constraints());
return;
}
let eval_domain = CanonicCoset::new(self.max_constraint_log_degree_bound()).circle_domain();
let trace_domain = CanonicCoset::new(self.eval.log_size());
let mut component_polys = trace.polys.sub_tree(&self.trace_locations);
component_polys[PREPROCESSED_TRACE_IDX] = self
.preprocessed_column_indices
.iter()
.map(|idx| &trace.polys[PREPROCESSED_TRACE_IDX][*idx])
.collect();
let need_to_extend = component_polys
.iter()
.flatten()
.any(|c| c.evals.domain.log_size() != eval_domain.log_size());
let trace: TreeVec<
Vec<Cow<'_, CircleEvaluation<SimdBackend, BaseField, BitReversedOrder>>>,
> = if need_to_extend {
let _span = span!(Level::INFO, "Constraint Extension").entered();
let twiddles = SimdBackend::precompute_twiddles(eval_domain.half_coset);
component_polys
.as_cols_ref()
.map_cols(|col| Cow::Owned(col.get_evaluation_on_domain(eval_domain, &twiddles)))
} else {
component_polys.map_cols(|c| Cow::Borrowed(&c.evals))
};
let log_expand = eval_domain.log_size() - trace_domain.log_size();
let mut denom_inv = (0..1 << log_expand)
.map(|i| coset_vanishing(trace_domain.coset(), eval_domain.at(i)).inverse())
.collect_vec();
bit_reverse(&mut denom_inv);
let [mut accum] =
evaluation_accumulator.columns([(eval_domain.log_size(), self.n_constraints())]);
accum.random_coeff_powers.reverse();
let _span = span!(
Level::INFO,
"Constraint point-wise eval",
class = "ConstraintEval"
)
.entered();
if trace_domain.log_size() < LOG_N_LANES + LOG_N_VERY_PACKED_ELEMS {
let trace_cols = trace.as_cols_ref().map_cols(|c| c.to_cpu());
let trace_cols = trace_cols.as_cols_ref();
*accum.col = SecureColumnByCoords::from_cpu(accumulate_pointwise_cpu(
self,
trace_cols,
eval_domain.log_size(),
trace_domain.log_size(),
denom_inv,
&accum.random_coeff_powers,
&accum.col.to_cpu(),
));
return;
}
let col = unsafe { VeryPackedSecureColumnByCoords::transform_under_mut(accum.col) };
let range = 0..(1 << (eval_domain.log_size() - LOG_N_LANES - LOG_N_VERY_PACKED_ELEMS));
#[cfg(not(feature = "parallel"))]
let iter = range.step_by(CHUNK_SIZE).zip(col.chunks_mut(CHUNK_SIZE));
#[cfg(feature = "parallel")]
let iter = range
.into_par_iter()
.step_by(CHUNK_SIZE)
.zip(col.chunks_mut(CHUNK_SIZE));
let self_eval = &self.eval;
let self_claimed_sum = self.claimed_sum;
iter.for_each(|(chunk_idx, mut chunk)| {
let trace_cols = trace.as_cols_ref().map_cols(|c| c.as_ref());
for idx_in_chunk in 0..CHUNK_SIZE {
let vec_row = chunk_idx * CHUNK_SIZE + idx_in_chunk;
let eval = SimdDomainEvaluator::new(
&trace_cols,
vec_row,
&accum.random_coeff_powers,
trace_domain.log_size(),
eval_domain.log_size(),
self_eval.log_size(),
self_claimed_sum,
);
let row_res = self_eval.evaluate(eval).row_res;
unsafe {
let row_denom_inv = VeryPackedBaseField::broadcast(
denom_inv[vec_row
>> (trace_domain.log_size() - LOG_N_LANES - LOG_N_VERY_PACKED_ELEMS)],
);
chunk.set_packed(
idx_in_chunk,
chunk.packed_at(idx_in_chunk) + row_res * row_denom_inv,
)
}
}
});
}
}
impl<E: FrameworkEval + Sync> ComponentProver<CpuBackend> for FrameworkComponent<E> {
fn evaluate_constraint_quotients_on_domain(
&self,
trace: &Trace<'_, CpuBackend>,
evaluation_accumulator: &mut DomainEvaluationAccumulator<CpuBackend>,
) {
let n_constraints = self.n_constraints();
if n_constraints == 0 {
return;
}
if self.is_disabled() {
evaluation_accumulator.skip_coeffs(n_constraints);
return;
}
let eval_domain = CanonicCoset::new(self.max_constraint_log_degree_bound()).circle_domain();
let trace_domain = CanonicCoset::new(self.eval.log_size());
let mut component_polys = trace.polys.sub_tree(&self.trace_locations);
component_polys[PREPROCESSED_TRACE_IDX] = self
.preprocessed_column_indices
.iter()
.map(|idx| &trace.polys[PREPROCESSED_TRACE_IDX][*idx])
.collect();
let need_to_extend = component_polys
.iter()
.flatten()
.any(|c| c.evals.domain.log_size() != eval_domain.log_size());
let trace: TreeVec<
Vec<Cow<'_, CircleEvaluation<CpuBackend, BaseField, BitReversedOrder>>>,
> = if need_to_extend {
let _span = span!(Level::INFO, "Constraint Extension").entered();
let twiddles = CpuBackend::precompute_twiddles(eval_domain.half_coset);
component_polys
.as_cols_ref()
.map_cols(|col| Cow::Owned(col.get_evaluation_on_domain(eval_domain, &twiddles)))
} else {
component_polys.map_cols(|c| Cow::Borrowed(&c.evals))
};
let log_expand = eval_domain.log_size() - trace_domain.log_size();
let mut denom_inv = (0..1 << log_expand)
.map(|i| coset_vanishing(trace_domain.coset(), eval_domain.at(i)).inverse())
.collect_vec();
bit_reverse(&mut denom_inv);
let [mut accum] =
evaluation_accumulator.columns([(eval_domain.log_size(), self.n_constraints())]);
accum.random_coeff_powers.reverse();
let _span = span!(
Level::INFO,
"Constraint point-wise eval",
class = "ConstraintEval"
)
.entered();
let trace_cols = trace.as_cols_ref().map_cols(|c| c.as_ref());
*accum.col = accumulate_pointwise_cpu(
self,
trace_cols,
eval_domain.log_size(),
trace_domain.log_size(),
denom_inv,
&accum.random_coeff_powers,
accum.col,
);
}
}
fn accumulate_pointwise_cpu<E: FrameworkEval>(
component: &FrameworkComponent<E>,
trace_cols: TreeVec<Vec<&CircleEvaluation<CpuBackend, BaseField, BitReversedOrder>>>,
eval_log_size: u32,
trace_log_size: u32,
denom_inv: Vec<BaseField>,
random_coeff_powers: &[SecureField],
accum: &SecureColumnByCoords<CpuBackend>,
) -> SecureColumnByCoords<CpuBackend> {
let mut res = SecureColumnByCoords::zeros(1 << eval_log_size);
for row in 0..(1 << eval_log_size) {
let eval = CpuDomainEvaluator::new(
&trace_cols,
row,
random_coeff_powers,
trace_log_size,
eval_log_size,
component.eval.log_size(),
component.claimed_sum,
);
let row_res = component.eval.evaluate(eval).row_res;
let row_denom_inv = denom_inv[row >> trace_log_size];
res.set(row, accum.at(row) + row_res * row_denom_inv)
}
res
}