Skip to content

Commit

Permalink
Add circle evaluatio with fold_circle_into_line
Browse files Browse the repository at this point in the history
  • Loading branch information
atgrosso committed Oct 1, 2024
1 parent fe6f3ad commit 5b054d0
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 29 deletions.
1 change: 1 addition & 0 deletions stwo_cairo_verifier/src/fri.cairo
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod folding;
pub mod verifier;
60 changes: 57 additions & 3 deletions stwo_cairo_verifier/src/fri/folding.cairo
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
use stwo_cairo_verifier::fields::m31::M31Trait;
use stwo_cairo_verifier::circle::{Coset, CosetImpl};
use stwo_cairo_verifier::poly::circle::{CircleDomain, CircleDomainImpl};
use stwo_cairo_verifier::poly::line::{LineDomain, LineDomainImpl};
use stwo_cairo_verifier::fields::qm31::{QM31, qm31};
use stwo_cairo_verifier::fields::m31::M31;
use stwo_cairo_verifier::utils::{bit_reverse_index, pow};
use stwo_cairo_verifier::fri::verifier::{FOLD_STEP, CIRCLE_TO_LINE_FOLD_STEP};
use stwo_cairo_verifier::poly::circle::{
CircleEvaluation, SparseCircleEvaluation, SparseCircleEvaluationImpl
};
use stwo_cairo_verifier::poly::line::{LineEvaluation, LineEvaluationImpl};
pub const CIRCLE_TO_LINE_FOLD_STEP: u32 = 1;
pub const FOLD_STEP: u32 = 1;


/// Folds a degree `d` polynomial into a degree `d/2` polynomial.
pub fn fold_line(eval: @LineEvaluation, alpha: QM31) -> LineEvaluation {
Expand All @@ -21,22 +25,57 @@ pub fn fold_line(eval: @LineEvaluation, alpha: QM31) -> LineEvaluation {
values.append(f0 + alpha * f1);
i += 1;
};

LineEvaluationImpl::new(domain.double(), values)
}


/// Folds and accumulates a degree `d` circle polynomial into a degree `d/2` univariate
/// polynomial.
///
/// Let `src` be the evaluation of a circle polynomial `f` on a
/// [`CircleDomain`] `E`. This function computes evaluations of `f' = f0
/// + alpha * f1` on the x-coordinates of `E` such that `2f(p) = f0(px) + py * f1(px)`. The
/// evaluations of `f'` are accumulated into `dst` by the formula `dst = dst * alpha^2 +
/// f'`.
pub fn fold_circle_into_line(eval: @CircleEvaluation, alpha: QM31) -> LineEvaluation {
let domain = eval.domain;
let mut values = array![];
let mut i = 0;
while i < eval.values.len() / 2 {
let p = domain
.at(bit_reverse_index(i * pow(2, CIRCLE_TO_LINE_FOLD_STEP), domain.log_size()));
let f_p = eval.values[2 * i];
let f_neg_p = eval.values[2 * i + 1];
let (f0, f1) = ibutterfly(*f_p, *f_neg_p, p.y.inverse());
values.append(f0 + alpha * f1);
i += 1;
};
LineEvaluation { values, domain: LineDomainImpl::new(*domain.half_coset) }
}

pub fn ibutterfly(v0: QM31, v1: QM31, itwid: M31) -> (QM31, QM31) {
(v0 + v1, (v0 - v1) * itwid.into())
}


mod test {
use stwo_cairo_verifier::poly::line::{
LineEvaluation, SparseLineEvaluation, SparseLineEvaluationImpl
};
use stwo_cairo_verifier::fields::m31::M31Trait;
use stwo_cairo_verifier::circle::{Coset, CosetImpl};
use stwo_cairo_verifier::poly::circle::{CircleDomain, CircleDomainImpl};
use stwo_cairo_verifier::poly::line::{LineDomain, LineDomainImpl};
use stwo_cairo_verifier::fields::qm31::{QM31, qm31};
use stwo_cairo_verifier::fields::m31::M31;
use stwo_cairo_verifier::utils::{bit_reverse_index, pow};
use stwo_cairo_verifier::fri::folding::{FOLD_STEP, CIRCLE_TO_LINE_FOLD_STEP};
use stwo_cairo_verifier::fri::verifier::{FOLD_STEP, CIRCLE_TO_LINE_FOLD_STEP};
use stwo_cairo_verifier::poly::circle::{
CircleEvaluation, SparseCircleEvaluation, SparseCircleEvaluationImpl
};


#[test]
fn test_fold_line_1() {
let domain = LineDomainImpl::new(CosetImpl::new(67108864, 1));
Expand All @@ -52,6 +91,7 @@ mod test {
let expected_result = array![qm31(515899232, 1030391528, 1006544539, 11142505)];
assert_eq!(expected_result, result);
}

#[test]
fn test_fold_line_2() {
let domain = LineDomainImpl::new(CosetImpl::new(553648128, 1));
Expand All @@ -67,4 +107,18 @@ mod test {
let expected_result = array![qm31(1379727866, 1083096056, 1409020369, 1977903500)];
assert_eq!(expected_result, result);
}


#[test]
fn test_fold_circle_into_line_1() {
let domain = CircleDomain { half_coset: CosetImpl::new(553648128, 0) };
let values = array![qm31(807167738, 0, 0, 0), qm31(1359821401, 0, 0, 0)];
let sparse_circle_evaluation: SparseCircleEvaluation = SparseCircleEvaluation {
subcircle_evals: array![CircleEvaluation { values, domain }]
};
let alpha = qm31(260773061, 362745443, 1347591543, 1084609991);
let result = sparse_circle_evaluation.fold(alpha);
let expected_result = array![qm31(730692421, 1363821003, 2146256633, 106012305)];
assert_eq!(expected_result, result);
}
}
2 changes: 2 additions & 0 deletions stwo_cairo_verifier/src/fri/verifier.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub const CIRCLE_TO_LINE_FOLD_STEP: u32 = 1;
pub const FOLD_STEP: u32 = 1;
176 changes: 150 additions & 26 deletions stwo_cairo_verifier/src/poly/circle.cairo
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
use stwo_cairo_verifier::circle::CirclePointTrait;
use core::option::OptionTrait;
use core::clone::Clone;
use core::result::ResultTrait;
use stwo_cairo_verifier::fields::qm31::{QM31, qm31};
use stwo_cairo_verifier::fields::m31::{M31, m31};
use stwo_cairo_verifier::utils::pow;
use stwo_cairo_verifier::circle::{
Coset, CosetImpl, CirclePoint, CirclePointM31Impl, M31_CIRCLE_GEN, CIRCLE_ORDER
};
use stwo_cairo_verifier::fri::folding::fold_circle_into_line;

/// A valid domain for circle polynomial interpolation and evaluation.
///
/// Valid domains are a disjoint union of two conjugate cosets: `+-C + <G_n>`.
/// The ordering defined on this domain is `C + iG_n`, and then `-C - iG_n`.
#[derive(Debug, Copy, Drop, PartialEq, Eq)]
Expand All @@ -18,10 +20,23 @@ pub struct CircleDomain {

#[generate_trait]
pub impl CircleDomainImpl of CircleDomainTrait {
/// Given a coset C + <G_n>, constructs the circle domain +-C + <G_n> (i.e.,
/// this coset and its conjugate).
fn new(half_coset: Coset) -> CircleDomain {
CircleDomain { half_coset }
}

/// Returns the size of the domain.
fn size(self: @CircleDomain) -> usize {
pow(2, self.log_size())
}

/// Returns the log size of the domain.
fn log_size(self: @CircleDomain) -> usize {
*self.half_coset.log_size + 1
}

/// Returns the circle point index of the `i`th domain element.
fn index_at(self: @CircleDomain, index: usize) -> usize {
if index < self.half_coset.size() {
self.half_coset.index_at(index)
Expand All @@ -30,37 +45,146 @@ pub impl CircleDomainImpl of CircleDomainTrait {
}
}

fn at(self: @CircleDomain, index: usize) -> CirclePoint::<M31> {
/// Returns the `i` th domain element.
fn at(self: @CircleDomain, index: usize) -> CirclePoint<M31> {
M31_CIRCLE_GEN.mul(self.index_at(index).into())
}

fn new_with_log_size(log_size: u32) -> CircleDomain {
CircleDomain { half_coset: CosetImpl::half_odds(log_size - 1) }
}
}

/// An evaluation defined on a [CircleDomain].
/// The values are ordered according to the [CircleDomain] ordering.
#[derive(Debug, Drop, Clone, PartialEq, Eq)]
pub struct CircleEvaluation {
pub values: Array<QM31>,
pub domain: CircleDomain,
}

#[cfg(test)]
mod tests {
use super::{CircleDomain, CircleDomainTrait};
use stwo_cairo_verifier::circle::{
Coset, CosetImpl, CirclePoint, CirclePointM31Impl, M31_CIRCLE_GEN, CIRCLE_ORDER
};
use stwo_cairo_verifier::fields::m31::{M31, m31};

#[test]
fn test_circle_domain_at_1() {
let half_coset = Coset { initial_index: 16777216, step_size: 67108864, log_size: 5 };
let domain = CircleDomain { half_coset };
let index = 17;
let result = domain.at(index);
let expected_result = CirclePoint::<M31> { x: m31(7144319), y: m31(1742797653) };
assert_eq!(expected_result, result);
#[generate_trait]
pub impl CircleEvaluationImpl of CircleEvaluationTrait {
fn new(domain: CircleDomain, values: Array<QM31>) -> CircleEvaluation {
CircleEvaluation { values: values, domain: domain }
}
}

/// Holds a foldable subset of circle polynomial evaluations.
#[derive(Drop, Clone, Debug, PartialEq)]
pub struct SparseCircleEvaluation {
pub subcircle_evals: Array<CircleEvaluation>
}

#[test]
fn test_circle_domain_at_2() {
let half_coset = Coset { initial_index: 16777216, step_size: 67108864, log_size: 5 };
let domain = CircleDomain { half_coset };
let index = 37;
let result = domain.at(index);
let expected_result = CirclePoint::<M31> { x: m31(9803698), y: m31(2079025011) };
assert_eq!(expected_result, result);
#[generate_trait]
pub impl SparseCircleEvaluationImpl of SparseCircleEvaluationImplTrait {
fn new(subcircle_evals: Array<CircleEvaluation>) -> SparseCircleEvaluation {
SparseCircleEvaluation { subcircle_evals: subcircle_evals }
}

fn accumulate(
self: @SparseCircleEvaluation, rhs: @SparseCircleEvaluation, alpha: QM31
) -> SparseCircleEvaluation {
assert_eq!(self.subcircle_evals.len(), rhs.subcircle_evals.len());
let mut subcircle_evals = array![];
let mut i = 0;
while i < self.subcircle_evals.len() {
let lhs = self.subcircle_evals[i];
let rhs = rhs.subcircle_evals[i];
let mut values = array![];
assert_eq!(lhs.values.len(), rhs.values.len());
let mut j = 0;
while j < lhs.values.len() {
values.append(*lhs.values[j] * alpha + *rhs.values[j]);
j += 1;
};
subcircle_evals.append(CircleEvaluation { domain: *lhs.domain, values });
i += 1;
};

SparseCircleEvaluation { subcircle_evals }
}

fn fold(self: @SparseCircleEvaluation, alpha: QM31) -> Array<QM31> {
let mut i = 0;
let mut res: Array<QM31> = array![];
while i < self.subcircle_evals.len() {
let circle_evaluation = fold_circle_into_line(self.subcircle_evals[i], alpha);
res.append(*circle_evaluation.values.at(0));
i += 1;
};
return res;
}
}

#[test]
fn test_circle_domain_at_1() {
let half_coset = Coset { initial_index: 16777216, step_size: 67108864, log_size: 5 };
let domain = CircleDomain { half_coset };
let index = 17;
let result = domain.at(index);
let expected_result = CirclePoint::<M31> { x: m31(7144319), y: m31(1742797653) };
assert_eq!(expected_result, result);
}

#[test]
fn test_circle_domain_at_2() {
let half_coset = Coset { initial_index: 16777216, step_size: 67108864, log_size: 5 };
let domain = CircleDomain { half_coset };
let index = 37;
let result = domain.at(index);
let expected_result = CirclePoint::<M31> { x: m31(9803698), y: m31(2079025011) };
assert_eq!(expected_result, result);
}

#[test]
fn test_accumulate_1() {
let alpha = qm31(984186742, 463356387, 533637878, 1417871498);
let lhs = SparseCircleEvaluation {
subcircle_evals: array![
CircleEvaluation {
values: array![qm31(28672, 0, 0, 0), qm31(36864, 0, 0, 0)],
domain: CircleDomain { half_coset: CosetImpl::new(16777216, 0) }
},
CircleEvaluation {
values: array![qm31(28672, 0, 0, 0), qm31(36864, 0, 0, 0)],
domain: CircleDomain { half_coset: CosetImpl::new(2030043136, 0) }
},
CircleEvaluation {
values: array![qm31(2147454975, 0, 0, 0), qm31(2147446783, 0, 0, 0)],
domain: CircleDomain { half_coset: CosetImpl::new(2097152000, 0) }
},
]
};
let rhs = lhs.clone();
let result = lhs.accumulate(@rhs, alpha);

let expected_result = SparseCircleEvaluation {
subcircle_evals: array![
CircleEvaluation {
values: array![
qm31(667173716, 1020487722, 1791736788, 1346152946),
qm31(1471361534, 84922130, 1076528072, 810417939)
],
domain: CircleDomain { half_coset: CosetImpl::new(16777216, 0) }
},
CircleEvaluation {
values: array![
qm31(667173716, 1020487722, 1791736788, 1346152946),
qm31(1471361534, 84922130, 1076528072, 810417939)
],
domain: CircleDomain { half_coset: CosetImpl::new(2030043136, 0) }
},
CircleEvaluation {
values: array![
qm31(1480309931, 1126995925, 355746859, 801330701),
qm31(676122113, 2062561517, 1070955575, 1337065708)
],
domain: CircleDomain { half_coset: CosetImpl::new(2097152000, 0) }
},
]
};

assert_eq!(expected_result, result);
}

0 comments on commit 5b054d0

Please sign in to comment.