Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Opt const eval #17

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
9 changes: 9 additions & 0 deletions air/src/air/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,15 @@ impl<B: StarkField> AirContext<B> {
self.num_transition_exemptions
}

/// Returns the a tuple containing the main and auxiliary transition constraint degrees.
pub fn transition_constraint_degrees(
&self,
) -> Vec<TransitionConstraintDegree>{

[self.main_transition_constraint_degrees.clone(),self.aux_transition_constraint_degrees.clone()].concat()

}

// DATA MUTATORS
// --------------------------------------------------------------------------------------------

Expand Down
28 changes: 16 additions & 12 deletions air/src/air/transition/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,15 @@ impl<E: FieldElement> TransitionConstraints<E> {
{
// merge constraint evaluations for the main trace segment
let mut result = self.main_constraints().iter().fold(E::ZERO, |acc, group| {
acc + group.merge_evaluations::<F, F>(main_evaluations, x)
let xp = x.exp(group.degree_adjustment.into());
acc + group.merge_evaluations::<F, F>(main_evaluations, xp)
});

// merge constraint evaluations for auxiliary trace segments (if any)
if self.num_aux_constraints() > 0 {
result += self.aux_constraints().iter().fold(E::ZERO, |acc, group| {
acc + group.merge_evaluations::<F, E>(aux_evaluations, x)
let xp = x.exp(group.degree_adjustment.into());
acc + group.merge_evaluations::<F, E>(aux_evaluations, xp)
});
}

Expand Down Expand Up @@ -247,21 +249,26 @@ impl<E: FieldElement> TransitionConstraintGroup<E> {
self.coefficients.push(coefficients);
}

/// Returns adjustment degree for this group.
pub fn degree_adjustment(&self) -> u32 {
self.degree_adjustment
}

// EVALUATOR
// --------------------------------------------------------------------------------------------
/// Computes a linear combination of evaluations relevant to this constraint group.
///
/// The linear combination is computed as follows:
/// $$
/// \sum_{i=0}^{k-1}{C_i(x) \cdot (\alpha_i + \beta_i \cdot x^d)}
/// \sum_{i=0}^{k-1}{C_i(x) \cdot (\alpha_i + \beta_i \cdot xp)}
/// $$
/// where:
/// * $C_i(x)$ is the evaluation of the $i$th constraint at `x` (same as `evaluations[i]`).
/// * $xp = x^d$ where $d$ is the degree adjustment factor computed as $D + (n - 1) - deg(C_i(x))$,
/// where $D$ is the degree of the composition polynomial, $n$ is the length of the execution
/// trace, and $deg(C_i(x))$ is the evaluation degree of the $i$th constraint.
/// * $\alpha$ and $\beta$ are random field elements. In the interactive version of the
/// protocol, these are provided by the verifier.
/// * $d$ is the degree adjustment factor computed as $D + (n - 1) - deg(C_i(x))$, where
/// $D$ is the degree of the composition polynomial, $n$ is the length of the execution
/// trace, and $deg(C_i(x))$ is the evaluation degree of the $i$th constraint.
///
/// There are two things to note here. First, the degree adjustment factor $d$ is the same
/// for all constraints in the group (since all constraints have the same degree). Second,
Expand All @@ -271,17 +278,14 @@ impl<E: FieldElement> TransitionConstraintGroup<E> {
/// them by the divisor later on. The degree of the divisor for transition constraints is
/// always $n - 1$. Thus, once we divide out the divisor, the evaluations will represent a
/// polynomial of degree $D$.
pub fn merge_evaluations<B, F>(&self, evaluations: &[F], x: B) -> E
pub fn merge_evaluations<B, F>(&self, evaluations: &[F], xp: B) -> E
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment above probably should be updated.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, let me know what you think.

where
B: FieldElement,
F: FieldElement<BaseField = B::BaseField> + ExtensionOf<B>,
E: FieldElement<BaseField = B::BaseField> + ExtensionOf<B> + ExtensionOf<F>,
{
// compute degree adjustment factor for this group
let xp = x.exp(self.degree_adjustment.into());

// compute linear combination of evaluations as D(x) * (cc_0 + cc_1 * x^p), where D(x)
// is an evaluation of a particular constraint, and x^p is the degree adjustment factor
// compute linear combination of evaluations as D(x) * (cc_0 + cc_1 * xp), where D(x)
// is an evaluation of a particular constraint, and xp is the degree adjustment factor
let mut result = E::ZERO;
for (&constraint_idx, coefficients) in self.indexes.iter().zip(self.coefficients.iter()) {
let evaluation = evaluations[constraint_idx];
Expand Down
25 changes: 17 additions & 8 deletions prover/src/constraints/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ impl<'a, A: Air, E: FieldElement<BaseField = A::BaseField>> ConstraintEvaluator<
// evaluate transition constraints and save the merged result the first slot of the
// evaluations buffer
evaluations[0] =
self.evaluate_main_transition(&main_frame, x, step, &mut t_evaluations);
self.evaluate_main_transition(&main_frame, &domain, step, &mut t_evaluations);

// when in debug mode, save transition constraint evaluations
#[cfg(debug_assertions)]
Expand Down Expand Up @@ -224,9 +224,14 @@ impl<'a, A: Air, E: FieldElement<BaseField = A::BaseField>> ConstraintEvaluator<
// evaluations buffer; we evaluate and compose constraints in the same function, we
// can just add up the results of evaluating main and auxiliary constraints.
evaluations[0] =
self.evaluate_main_transition(&main_frame, x, step, &mut tm_evaluations);
evaluations[0] +=
self.evaluate_aux_transition(&main_frame, &aux_frame, x, step, &mut ta_evaluations);
self.evaluate_main_transition(&main_frame, &domain, step, &mut tm_evaluations);
evaluations[0] += self.evaluate_aux_transition(
&main_frame,
&aux_frame,
step,
&mut ta_evaluations,
&domain,
);

// when in debug mode, save transition constraint evaluations
#[cfg(debug_assertions)]
Expand Down Expand Up @@ -264,7 +269,7 @@ impl<'a, A: Air, E: FieldElement<BaseField = A::BaseField>> ConstraintEvaluator<
fn evaluate_main_transition(
&self,
main_frame: &EvaluationFrame<E::BaseField>,
x: E::BaseField,
domain: &StarkDomain<A::BaseField>,
step: usize,
evaluations: &mut [E::BaseField],
) -> E {
Expand All @@ -278,10 +283,13 @@ impl<'a, A: Air, E: FieldElement<BaseField = A::BaseField>> ConstraintEvaluator<
// the results into evaluations buffer
self.air.evaluate_transition(main_frame, periodic_values, evaluations);


// merge transition constraint evaluations into a single value and return it;
// we can do this here because all transition constraints have the same divisor.
self.transition_constraints.main_constraints().iter().fold(E::ZERO, |result, group| {
result + group.merge_evaluations(evaluations, x)
let xp = domain.get_ce_x_power_at::<A>(step, group.degree_adjustment());
//let xp = domain.offset().exp((group.degree_adjustment()).into())*domain.ce_domain_generator().exp(((step as u32) * group.degree_adjustment()).into());
result + group.merge_evaluations(evaluations, xp)
})
}

Expand All @@ -295,9 +303,9 @@ impl<'a, A: Air, E: FieldElement<BaseField = A::BaseField>> ConstraintEvaluator<
&self,
main_frame: &EvaluationFrame<E::BaseField>,
aux_frame: &EvaluationFrame<E>,
x: E::BaseField,
step: usize,
evaluations: &mut [E],
domain: &StarkDomain<A::BaseField>,
) -> E {
// TODO: use a more efficient way to zero out memory
evaluations.fill(E::ZERO);
Expand All @@ -318,7 +326,8 @@ impl<'a, A: Air, E: FieldElement<BaseField = A::BaseField>> ConstraintEvaluator<
// merge transition constraint evaluations into a single value and return it;
// we can do this here because all transition constraints have the same divisor.
self.transition_constraints.aux_constraints().iter().fold(E::ZERO, |result, group| {
result + group.merge_evaluations::<E::BaseField, E>(evaluations, x)
let xp = domain.get_ce_x_power_at::<A>(step, group.degree_adjustment());
result + group.merge_evaluations::<E::BaseField, E>(evaluations, xp)
})
}

Expand Down
81 changes: 74 additions & 7 deletions prover/src/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

use air::Air;
use math::{fft, log2, StarkField};
use std::collections::HashMap;

use air::{Air, ConstraintDivisor};
use math::{fft, get_power_series, log2, StarkField};
use utils::collections::Vec;

// TYPES AND INTERFACES
Expand All @@ -15,14 +17,17 @@ pub struct StarkDomain<B: StarkField> {
/// vector is half the length of the trace domain size.
trace_twiddles: Vec<B>,

/// Size of the constraint evaluation domain.
ce_domain_size: usize,

/// LDE domain size / constraint evaluation domain size
ce_to_lde_blowup: usize,

/// Offset of the low-degree extension domain.
domain_offset: B,

/// [g^i for i in (0..ce_domain_size)] where g is the constraint evaluation domain generator.
ce_domain: Vec<B>,

/// A mapping from adjustment degrees to domain_offset^adjustment_degree.
degree_adj_map: HashMap<u32, B>,
}

// STARK DOMAIN IMPLEMENTATION
Expand All @@ -32,11 +37,17 @@ impl<B: StarkField> StarkDomain<B> {
/// Returns a new STARK domain initialized with the provided `context`.
pub fn new<A: Air<BaseField = B>>(air: &A) -> Self {
let trace_twiddles = fft::get_twiddles(air.trace_length());

let domain_gen = B::get_root_of_unity(log2(air.ce_domain_size()));
let ce_domain = get_power_series(domain_gen, air.ce_domain_size());
let degree_adj_map = generate_degree_adj_map(air);

StarkDomain {
trace_twiddles,
ce_domain_size: air.ce_domain_size(),
ce_to_lde_blowup: air.lde_domain_size() / air.ce_domain_size(),
domain_offset: air.domain_offset(),
ce_domain,
degree_adj_map,
}
}

Expand Down Expand Up @@ -68,7 +79,17 @@ impl<B: StarkField> StarkDomain<B> {

/// Returns the size of the constraint evaluation domain for this computation.
pub fn ce_domain_size(&self) -> usize {
self.ce_domain_size
self.ce_domain.len()
}

/// Returns the constraint evaluation domain for this computation.
pub fn ce_domain(&self) -> Vec<B> {
self.ce_domain.clone()
}

/// Returns the degree adjustment map for this computation.
pub fn degree_adj_map(&self) -> HashMap<u32, B> {
self.degree_adj_map.clone()
}

/// Returns the generator of constraint evaluation domain.
Expand All @@ -81,6 +102,24 @@ impl<B: StarkField> StarkDomain<B> {
self.ce_to_lde_blowup
}

/// Returns (offset * g^(step))^degree_adjustment.
#[inline(always)]
pub fn get_ce_x_power_at<A: Air<BaseField = B>>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could try adding #[inline(always)] attribute to this function (and maybe some other functions). Sometimes, this improves performance.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I did here and in the helper function on the same file.

&self,
step: usize,
degree_adjustment: u32,
) -> A::BaseField {
let index: usize = step * (degree_adjustment as usize);
let index = index % (self.ce_domain_size());
let xp = self.ce_domain()[index]
* *self
.degree_adj_map()
.get(&degree_adjustment)
.expect("Degree adjustment map malformed");

xp
}

// LOW-DEGREE EXTENSION DOMAIN
// --------------------------------------------------------------------------------------------

Expand All @@ -94,3 +133,31 @@ impl<B: StarkField> StarkDomain<B> {
self.domain_offset
}
}

// HELPERS
// --------------------------------------------------------------------------------------------

#[inline(always)]
fn generate_degree_adj_map<A: Air<BaseField = B>, B: StarkField>(air: &A) -> HashMap<u32, B> {
let mut degree_adj_map = HashMap::new();
let domain_offset = air.domain_offset();
let context = air.context();
let divisor: ConstraintDivisor<B> = ConstraintDivisor::from_transition(
context.trace_len(),
context.num_transition_exemptions(),
);
let div_deg = divisor.degree();
let constraint_degrees = context.transition_constraint_degrees();
let trace_len = context.trace_len();
let comp_deg = context.composition_degree();
let target_deg = comp_deg + div_deg;

for degree in constraint_degrees {
let evaluation_degree = degree.get_evaluation_degree(trace_len);
let degree_adjustment = (target_deg - evaluation_degree) as u32;
let _ = degree_adj_map
.entry(degree_adjustment)
.or_insert_with(|| domain_offset.exp(degree_adjustment.into()));
}
degree_adj_map
}