use super::{AirContext, BTreeMap, ConstraintDivisor, ExtensionOf, FieldElement, Vec};
mod frame;
pub use frame::EvaluationFrame;
mod degree;
pub use degree::TransitionConstraintDegree;
const MIN_CYCLE_LENGTH: usize = 2;
pub struct TransitionConstraints<E: FieldElement> {
main_constraints: Vec<TransitionConstraintGroup<E>>,
main_constraint_degrees: Vec<TransitionConstraintDegree>,
aux_constraints: Vec<TransitionConstraintGroup<E>>,
aux_constraint_degrees: Vec<TransitionConstraintDegree>,
divisor: ConstraintDivisor<E::BaseField>,
}
impl<E: FieldElement> TransitionConstraints<E> {
pub fn new(context: &AirContext<E::BaseField>, composition_coefficients: &[(E, E)]) -> Self {
assert_eq!(
context.num_transition_constraints(),
composition_coefficients.len(),
"number of transition constraints must match the number of composition coefficient tuples"
);
let divisor = ConstraintDivisor::from_transition(
context.trace_len(),
context.num_transition_exemptions(),
);
let (main_constraint_coefficients, aux_constraint_coefficients) =
composition_coefficients.split_at(context.main_transition_constraint_degrees.len());
let main_constraint_degrees = context.main_transition_constraint_degrees.clone();
let main_constraints = group_constraints(
&main_constraint_degrees,
context,
main_constraint_coefficients,
divisor.degree(),
);
let aux_constraint_degrees = context.aux_transition_constraint_degrees.clone();
let aux_constraints = group_constraints(
&aux_constraint_degrees,
context,
aux_constraint_coefficients,
divisor.degree(),
);
Self {
main_constraints,
main_constraint_degrees,
aux_constraints,
aux_constraint_degrees,
divisor,
}
}
pub fn main_constraints(&self) -> &[TransitionConstraintGroup<E>] {
&self.main_constraints
}
pub fn main_constraint_degrees(&self) -> &[TransitionConstraintDegree] {
&self.main_constraint_degrees
}
pub fn num_main_constraints(&self) -> usize {
self.main_constraint_degrees.len()
}
pub fn aux_constraints(&self) -> &[TransitionConstraintGroup<E>] {
&self.aux_constraints
}
pub fn aux_constraint_degrees(&self) -> &[TransitionConstraintDegree] {
&self.aux_constraint_degrees
}
pub fn num_aux_constraints(&self) -> usize {
self.aux_constraint_degrees.len()
}
pub fn divisor(&self) -> &ConstraintDivisor<E::BaseField> {
&self.divisor
}
pub fn combine_evaluations<F>(&self, main_evaluations: &[F], aux_evaluations: &[E], x: F) -> E
where
F: FieldElement<BaseField = E::BaseField>,
E: ExtensionOf<F>,
{
let mut result = self.main_constraints().iter().fold(E::ZERO, |acc, group| {
let xp = x.exp(group.degree_adjustment.into());
acc + group.merge_evaluations::<F, F>(main_evaluations, xp)
});
if self.num_aux_constraints() > 0 {
result += self.aux_constraints().iter().fold(E::ZERO, |acc, group| {
let xp = x.exp(group.degree_adjustment.into());
acc + group.merge_evaluations::<F, E>(aux_evaluations, xp)
});
}
let z = E::from(self.divisor.evaluate_at(x));
result / z
}
}
#[derive(Clone, Debug)]
pub struct TransitionConstraintGroup<E: FieldElement> {
degree: TransitionConstraintDegree,
degree_adjustment: u64,
domain_offset_exp: E::BaseField,
indexes: Vec<usize>,
coefficients: Vec<(E, E)>,
}
impl<E: FieldElement> TransitionConstraintGroup<E> {
pub(super) fn new(
degree: TransitionConstraintDegree,
trace_length: usize,
composition_degree: usize,
divisor_degree: usize,
domain_offset: E::BaseField,
) -> Self {
let target_degree = composition_degree + divisor_degree;
let evaluation_degree = degree.get_evaluation_degree(trace_length);
let degree_adjustment = (target_degree - evaluation_degree) as u64;
let domain_offset_exp = domain_offset.exp(degree_adjustment.into());
TransitionConstraintGroup {
degree,
degree_adjustment,
domain_offset_exp,
indexes: vec![],
coefficients: vec![],
}
}
pub fn indexes(&self) -> &[usize] {
&self.indexes
}
pub fn degree(&self) -> &TransitionConstraintDegree {
&self.degree
}
pub fn degree_adjustment(&self) -> u64 {
self.degree_adjustment
}
pub fn domain_offset_exp(&self) -> E::BaseField {
self.domain_offset_exp
}
pub fn add(&mut self, constraint_idx: usize, coefficients: (E, E)) {
self.indexes.push(constraint_idx);
self.coefficients.push(coefficients);
}
pub fn merge_evaluations<B, F>(&self, evaluations: &[F], xp: B) -> E
where
B: FieldElement,
F: FieldElement<BaseField = B::BaseField> + ExtensionOf<B>,
E: FieldElement<BaseField = B::BaseField> + ExtensionOf<B> + ExtensionOf<F>,
{
let mut result = E::ZERO;
for (&constraint_idx, coefficients) in self.indexes.iter().zip(self.coefficients.iter()) {
let evaluation = evaluations[constraint_idx];
result += (coefficients.0 + coefficients.1.mul_base(xp)).mul_base(evaluation);
}
result
}
}
fn group_constraints<E: FieldElement>(
degrees: &[TransitionConstraintDegree],
context: &AirContext<E::BaseField>,
coefficients: &[(E, E)],
divisor_degree: usize,
) -> Vec<TransitionConstraintGroup<E>> {
let mut groups = BTreeMap::new();
for (i, degree) in degrees.iter().enumerate() {
let evaluation_degree = degree.get_evaluation_degree(context.trace_len());
let group = groups.entry(evaluation_degree).or_insert_with(|| {
TransitionConstraintGroup::new(
degree.clone(),
context.trace_len(),
context.composition_degree(),
divisor_degree,
context.options.domain_offset(),
)
});
group.add(i, coefficients[i]);
}
groups.into_iter().map(|e| e.1).collect()
}