1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use super::{
super::EvaluationTableFragment, BoundaryConstraints, CompositionPolyTrace,
ConstraintEvaluationTable, ConstraintEvaluator, PeriodicValueTable, StarkDomain, TraceLde,
};
use air::{
Air, AuxTraceRandElements, ConstraintCompositionCoefficients, EvaluationFrame,
TransitionConstraints,
};
use math::FieldElement;
use utils::iter_mut;
#[cfg(feature = "concurrent")]
use utils::{iterators::*, rayon};
// CONSTANTS
// ================================================================================================
#[cfg(feature = "concurrent")]
const MIN_CONCURRENT_DOMAIN_SIZE: usize = 8192;
// DEFAULT CONSTRAINT EVALUATOR
// ================================================================================================
/// Default implementation of the [ConstraintEvaluator] trait.
///
/// This implementation iterates over all evaluation frames of an extended execution trace and
/// evaluates constraints over these frames one-by-one. Constraint evaluations are merged together
/// using random linear combinations and in the end, only a single column is returned.
///
/// When `concurrent` feature is enabled, the extended execution trace is split into sets of
/// sequential evaluation frames (called fragments), and frames in each fragment are evaluated
/// in separate threads.
pub struct DefaultConstraintEvaluator<'a, A: Air, E: FieldElement<BaseField = A::BaseField>> {
air: &'a A,
boundary_constraints: BoundaryConstraints<E>,
transition_constraints: TransitionConstraints<E>,
aux_rand_elements: AuxTraceRandElements<E>,
periodic_values: PeriodicValueTable<E::BaseField>,
}
impl<'a, A, E> ConstraintEvaluator<E> for DefaultConstraintEvaluator<'a, A, E>
where
A: Air,
E: FieldElement<BaseField = A::BaseField>,
{
type Air = A;
fn evaluate<T: TraceLde<E>>(
self,
trace: &T,
domain: &StarkDomain<<E as FieldElement>::BaseField>,
) -> CompositionPolyTrace<E> {
assert_eq!(
trace.trace_len(),
domain.lde_domain_size(),
"extended trace length is not consistent with evaluation domain"
);
// build a list of constraint divisors; currently, all transition constraints have the same
// divisor which we put at the front of the list; boundary constraint divisors are appended
// after that
let mut divisors = vec![self.transition_constraints.divisor().clone()];
divisors.append(&mut self.boundary_constraints.get_divisors());
// allocate space for constraint evaluations; when we are in debug mode, we also allocate
// memory to hold all transition constraint evaluations (before they are merged into a
// single value) so that we can check their degrees later
#[cfg(not(debug_assertions))]
let mut evaluation_table = ConstraintEvaluationTable::<E>::new(domain, divisors);
#[cfg(debug_assertions)]
let mut evaluation_table =
ConstraintEvaluationTable::<E>::new(domain, divisors, &self.transition_constraints);
// when `concurrent` feature is enabled, break the evaluation table into multiple fragments
// to evaluate them into multiple threads; unless the constraint evaluation domain is small,
// then don't bother with concurrent evaluation
#[cfg(not(feature = "concurrent"))]
let num_fragments = 1;
#[cfg(feature = "concurrent")]
let num_fragments = if domain.ce_domain_size() >= MIN_CONCURRENT_DOMAIN_SIZE {
rayon::current_num_threads().next_power_of_two()
} else {
1
};
// evaluate constraints for each fragment; if the trace consist of multiple segments
// we evaluate constraints for all segments. otherwise, we evaluate constraints only
// for the main segment.
let mut fragments = evaluation_table.fragments(num_fragments);
iter_mut!(fragments).for_each(|fragment| {
if self.air.trace_info().is_multi_segment() {
self.evaluate_fragment_full(trace, domain, fragment);
} else {
self.evaluate_fragment_main(trace, domain, fragment);
}
});
// when in debug mode, make sure expected transition constraint degrees align with
// actual degrees we got during constraint evaluation
#[cfg(debug_assertions)]
evaluation_table.validate_transition_degrees();
// combine all evaluations into a single column and return
evaluation_table.combine()
}
}
impl<'a, A, E> DefaultConstraintEvaluator<'a, A, E>
where
A: Air,
E: FieldElement<BaseField = A::BaseField>,
{
// CONSTRUCTOR
// --------------------------------------------------------------------------------------------
/// Returns a new evaluator which can be used to evaluate transition and boundary constraints
/// over extended execution trace.
pub fn new(
air: &'a A,
aux_rand_elements: AuxTraceRandElements<E>,
composition_coefficients: ConstraintCompositionCoefficients<E>,
) -> Self {
// build transition constraint groups; these will be used to compose transition constraint
// evaluations
let transition_constraints =
air.get_transition_constraints(&composition_coefficients.transition);
// build periodic value table
let periodic_values = PeriodicValueTable::new(air);
// build boundary constraint groups; these will be used to evaluate and compose boundary
// constraint evaluations.
let boundary_constraints =
BoundaryConstraints::new(air, &aux_rand_elements, &composition_coefficients.boundary);
DefaultConstraintEvaluator {
air,
boundary_constraints,
transition_constraints,
aux_rand_elements,
periodic_values,
}
}
// EVALUATION HELPERS
// --------------------------------------------------------------------------------------------
/// Evaluates constraints for a single fragment of the evaluation table.
///
/// This evaluates constraints only over the main segment of the execution trace.
fn evaluate_fragment_main<T: TraceLde<E>>(
&self,
trace: &T,
domain: &StarkDomain<A::BaseField>,
fragment: &mut EvaluationTableFragment<E>,
) {
// initialize buffers to hold trace values and evaluation results at each step;
let mut main_frame = EvaluationFrame::new(trace.trace_layout().main_trace_width());
let mut evaluations = vec![E::ZERO; fragment.num_columns()];
let mut t_evaluations = vec![E::BaseField::ZERO; self.num_main_transition_constraints()];
// this will be used to convert steps in constraint evaluation domain to steps in
// LDE domain
let lde_shift = domain.ce_to_lde_blowup().trailing_zeros();
for i in 0..fragment.num_rows() {
let step = i + fragment.offset();
// update evaluation frame buffer with data from the execution trace; this will
// read current and next rows from the trace into the buffer; data in the trace
// table is extended over the LDE domain, so, we need to convert step in constraint
// evaluation domain, into a step in LDE domain, in case these domains are different
trace.read_main_trace_frame_into(step << lde_shift, &mut main_frame);
// evaluate transition constraints and save the merged result the first slot of the
// evaluations buffer
evaluations[0] = self.evaluate_main_transition(&main_frame, step, &mut t_evaluations);
// when in debug mode, save transition constraint evaluations
#[cfg(debug_assertions)]
fragment.update_transition_evaluations(i, &t_evaluations, &[]);
// evaluate boundary constraints; the results go into remaining slots of the
// evaluations buffer
let main_state = main_frame.current();
self.boundary_constraints.evaluate_main(
main_state,
domain,
step,
&mut evaluations[1..],
);
// record the result in the evaluation table
fragment.update_row(i, &evaluations);
}
}
/// Evaluates constraints for a single fragment of the evaluation table.
///
/// This evaluates constraints only over all segments of the execution trace (i.e. main segment
/// and all auxiliary segments).
fn evaluate_fragment_full<T: TraceLde<E>>(
&self,
trace: &T,
domain: &StarkDomain<A::BaseField>,
fragment: &mut EvaluationTableFragment<E>,
) {
// initialize buffers to hold trace values and evaluation results at each step
let mut main_frame = EvaluationFrame::new(trace.trace_layout().main_trace_width());
let mut aux_frame = EvaluationFrame::new(trace.trace_layout().aux_trace_width());
let mut tm_evaluations = vec![E::BaseField::ZERO; self.num_main_transition_constraints()];
let mut ta_evaluations = vec![E::ZERO; self.num_aux_transition_constraints()];
let mut evaluations = vec![E::ZERO; fragment.num_columns()];
// this will be used to convert steps in constraint evaluation domain to steps in
// LDE domain
let lde_shift = domain.ce_to_lde_blowup().trailing_zeros();
for i in 0..fragment.num_rows() {
let step = i + fragment.offset();
// read both the main and the auxiliary evaluation frames from the trace
trace.read_main_trace_frame_into(step << lde_shift, &mut main_frame);
trace.read_aux_trace_frame_into(step << lde_shift, &mut aux_frame);
// evaluate transition constraints and save the merged result the first slot of the
// 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, step, &mut tm_evaluations);
evaluations[0] +=
self.evaluate_aux_transition(&main_frame, &aux_frame, step, &mut ta_evaluations);
// when in debug mode, save transition constraint evaluations
#[cfg(debug_assertions)]
fragment.update_transition_evaluations(i, &tm_evaluations, &ta_evaluations);
// evaluate boundary constraints; the results go into remaining slots of the
// evaluations buffer
let main_state = main_frame.current();
let aux_state = aux_frame.current();
self.boundary_constraints.evaluate_all(
main_state,
aux_state,
domain,
step,
&mut evaluations[1..],
);
// record the result in the evaluation table
fragment.update_row(i, &evaluations);
}
}
// TRANSITION CONSTRAINT EVALUATORS
// --------------------------------------------------------------------------------------------
/// Evaluates transition constraints of the main execution trace at the specified step of the
/// constraint evaluation domain.
///
/// `x` is the corresponding domain value at the specified step. That is, x = s * g^step,
/// where g is the generator of the constraint evaluation domain, and s is the domain offset.
fn evaluate_main_transition(
&self,
main_frame: &EvaluationFrame<E::BaseField>,
step: usize,
evaluations: &mut [E::BaseField],
) -> E {
// TODO: use a more efficient way to zero out memory
evaluations.fill(E::BaseField::ZERO);
// get periodic values at the evaluation step
let periodic_values = self.periodic_values.get_row(step);
// evaluate transition constraints over the main segment of the execution trace and save
// 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.
evaluations
.iter()
.zip(self.transition_constraints.main_constraint_coef().iter())
.fold(E::ZERO, |acc, (&const_eval, &coef)| acc + coef.mul_base(const_eval))
}
/// Evaluates all transition constraints (i.e., for main and auxiliary trace segments) at the
/// specified step of the constraint evaluation domain.
///
/// `x` is the corresponding domain value at the specified step. That is, x = s * g^step,
/// where g is the generator of the constraint evaluation domain, and s is the domain offset.
fn evaluate_aux_transition(
&self,
main_frame: &EvaluationFrame<E::BaseField>,
aux_frame: &EvaluationFrame<E>,
step: usize,
evaluations: &mut [E],
) -> E {
// TODO: use a more efficient way to zero out memory
evaluations.fill(E::ZERO);
// get periodic values at the evaluation step
let periodic_values = self.periodic_values.get_row(step);
// evaluate transition constraints over auxiliary trace segments and save the results into
// evaluations buffer
self.air.evaluate_aux_transition(
main_frame,
aux_frame,
periodic_values,
&self.aux_rand_elements,
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.
evaluations
.iter()
.zip(self.transition_constraints.aux_constraint_coef().iter())
.fold(E::ZERO, |acc, (&const_eval, &coef)| acc + coef * const_eval)
}
// ACCESSORS
// --------------------------------------------------------------------------------------------
/// Returns the number of transition constraints applied against the main segment of the
/// execution trace.
fn num_main_transition_constraints(&self) -> usize {
self.transition_constraints.num_main_constraints()
}
/// Returns the number of transition constraints applied against all auxiliary trace segments.
fn num_aux_transition_constraints(&self) -> usize {
self.transition_constraints.num_aux_constraints()
}
}