openvm_stark_backend/air_builders/symbolic/
mod.rs
1use itertools::Itertools;
4use p3_air::{
5 AirBuilder, AirBuilderWithPublicValues, ExtensionBuilder, PairBuilder, PermutationAirBuilder,
6};
7use p3_field::Field;
8use p3_matrix::{dense::RowMajorMatrix, Matrix};
9use p3_util::log2_ceil_usize;
10use tracing::instrument;
11
12use self::{
13 symbolic_expression::SymbolicExpression,
14 symbolic_variable::{Entry, SymbolicVariable},
15};
16use super::PartitionedAirBuilder;
17use crate::{
18 interaction::{
19 fri_log_up::find_interaction_chunks, rap::InteractionPhaseAirBuilder, Interaction,
20 InteractionBuilder, RapPhaseSeqKind, SymbolicInteraction,
21 },
22 keygen::types::{StarkVerifyingParams, TraceWidth},
23 rap::{BaseAirWithPublicValues, PermutationAirBuilderWithExposedValues, Rap},
24};
25
26mod dag;
27pub mod symbolic_expression;
28pub mod symbolic_variable;
29
30pub use dag::*;
31
32use crate::interaction::BusIndex;
33
34#[derive(Clone, Debug)]
37pub struct SymbolicConstraints<F> {
38 pub constraints: Vec<SymbolicExpression<F>>,
40 pub interactions: Vec<SymbolicInteraction<F>>,
48}
49
50impl<F: Field> SymbolicConstraints<F> {
51 pub fn max_constraint_degree(&self) -> usize {
52 Iterator::max(self.constraints.iter().map(|c| c.degree_multiple())).unwrap_or(0)
53 }
54
55 pub fn get_log_quotient_degree(&self) -> usize {
56 let constraint_degree = self.max_constraint_degree().max(2);
58
59 log2_ceil_usize(constraint_degree - 1)
63 }
64
65 pub fn max_interaction_degrees(&self) -> (usize, usize) {
67 let max_field_degree = self
68 .interactions
69 .iter()
70 .map(|interaction| {
71 interaction
72 .message
73 .iter()
74 .map(|field| field.degree_multiple())
75 .max()
76 .unwrap_or(0)
77 })
78 .max()
79 .unwrap_or(0);
80
81 let max_count_degree = self
82 .interactions
83 .iter()
84 .map(|interaction| interaction.count.degree_multiple())
85 .max()
86 .unwrap_or(0);
87
88 (max_field_degree, max_count_degree)
89 }
90}
91
92#[instrument(name = "evaluate constraints symbolically", skip_all, level = "debug")]
93pub fn get_symbolic_builder<F, R>(
94 rap: &R,
95 width: &TraceWidth,
96 num_challenges_to_sample: &[usize],
97 num_exposed_values_after_challenge: &[usize],
98 rap_phase_seq_kind: RapPhaseSeqKind,
99 max_constraint_degree: usize,
100) -> SymbolicRapBuilder<F>
101where
102 F: Field,
103 R: Rap<SymbolicRapBuilder<F>> + BaseAirWithPublicValues<F> + ?Sized,
104{
105 let mut builder = SymbolicRapBuilder::new(
106 width,
107 rap.num_public_values(),
108 num_challenges_to_sample,
109 num_exposed_values_after_challenge,
110 rap_phase_seq_kind,
111 max_constraint_degree,
112 );
113 Rap::eval(rap, &mut builder);
114 builder
115}
116
117#[derive(Debug)]
119pub struct SymbolicRapBuilder<F> {
120 preprocessed: RowMajorMatrix<SymbolicVariable<F>>,
121 partitioned_main: Vec<RowMajorMatrix<SymbolicVariable<F>>>,
122 after_challenge: Vec<RowMajorMatrix<SymbolicVariable<F>>>,
123 public_values: Vec<SymbolicVariable<F>>,
124 challenges: Vec<Vec<SymbolicVariable<F>>>,
125 exposed_values_after_challenge: Vec<Vec<SymbolicVariable<F>>>,
126 constraints: Vec<SymbolicExpression<F>>,
127 interactions: Vec<SymbolicInteraction<F>>,
128 max_constraint_degree: usize,
129 rap_phase_seq_kind: RapPhaseSeqKind,
130 trace_width: TraceWidth,
131
132 interaction_partitions: Option<Vec<Vec<usize>>>,
134}
135
136impl<F: Field> SymbolicRapBuilder<F> {
137 pub(crate) fn new(
140 width: &TraceWidth,
141 num_public_values: usize,
142 num_challenges_to_sample: &[usize],
143 num_exposed_values_after_challenge: &[usize],
144 rap_phase_seq_kind: RapPhaseSeqKind,
145 max_constraint_degree: usize,
146 ) -> Self {
147 let preprocessed_width = width.preprocessed.unwrap_or(0);
148 let prep_values = [0, 1]
149 .into_iter()
150 .flat_map(|offset| {
151 (0..width.preprocessed.unwrap_or(0))
152 .map(move |index| SymbolicVariable::new(Entry::Preprocessed { offset }, index))
153 })
154 .collect();
155 let preprocessed = RowMajorMatrix::new(prep_values, preprocessed_width);
156
157 let mut partitioned_main: Vec<_> = width
158 .cached_mains
159 .iter()
160 .enumerate()
161 .map(|(part_index, &width)| gen_main_trace(part_index, width))
162 .collect();
163 if width.common_main != 0 {
164 partitioned_main.push(gen_main_trace(width.cached_mains.len(), width.common_main));
165 }
166 let after_challenge = Self::new_after_challenge(&width.after_challenge);
167
168 let public_values = (0..num_public_values)
169 .map(move |index| SymbolicVariable::new(Entry::Public, index))
170 .collect();
171
172 let challenges = Self::new_challenges(num_challenges_to_sample);
173
174 let exposed_values_after_challenge =
175 Self::new_exposed_values_after_challenge(num_exposed_values_after_challenge);
176
177 Self {
178 preprocessed,
179 partitioned_main,
180 after_challenge,
181 public_values,
182 challenges,
183 exposed_values_after_challenge,
184 constraints: vec![],
185 interactions: vec![],
186 max_constraint_degree,
187 rap_phase_seq_kind,
188 trace_width: width.clone(),
189 interaction_partitions: None,
190 }
191 }
192
193 pub fn constraints(self) -> SymbolicConstraints<F> {
194 SymbolicConstraints {
195 constraints: self.constraints,
196 interactions: self.interactions,
197 }
198 }
199
200 pub fn params(&self) -> StarkVerifyingParams {
201 let width = self.width();
202 let num_exposed_values_after_challenge = self.num_exposed_values_after_challenge();
203 let num_challenges_to_sample = self.num_challenges_to_sample();
204 StarkVerifyingParams {
205 width,
206 num_public_values: self.public_values.len(),
207 num_exposed_values_after_challenge,
208 num_challenges_to_sample,
209 }
210 }
211
212 pub fn width(&self) -> TraceWidth {
213 let mut ret = self.trace_width.clone();
214 ret.after_challenge = self.after_challenge.iter().map(|m| m.width()).collect();
215 ret
216 }
217
218 pub fn num_exposed_values_after_challenge(&self) -> Vec<usize> {
219 self.exposed_values_after_challenge
220 .iter()
221 .map(|c| c.len())
222 .collect()
223 }
224
225 pub fn num_challenges_to_sample(&self) -> Vec<usize> {
226 self.challenges.iter().map(|c| c.len()).collect()
227 }
228
229 fn new_after_challenge(
230 width_after_phase: &[usize],
231 ) -> Vec<RowMajorMatrix<SymbolicVariable<F>>> {
232 width_after_phase
233 .iter()
234 .map(|&width| {
235 let mat_values = [0, 1]
236 .into_iter()
237 .flat_map(|offset| {
238 (0..width).map(move |index| {
239 SymbolicVariable::new(Entry::Permutation { offset }, index)
240 })
241 })
242 .collect_vec();
243 RowMajorMatrix::new(mat_values, width)
244 })
245 .collect_vec()
246 }
247
248 fn new_challenges(num_challenges_to_sample: &[usize]) -> Vec<Vec<SymbolicVariable<F>>> {
249 num_challenges_to_sample
250 .iter()
251 .map(|&num_challenges| {
252 (0..num_challenges)
253 .map(|index| SymbolicVariable::new(Entry::Challenge, index))
254 .collect_vec()
255 })
256 .collect_vec()
257 }
258
259 fn new_exposed_values_after_challenge(
260 num_exposed_values_after_challenge: &[usize],
261 ) -> Vec<Vec<SymbolicVariable<F>>> {
262 num_exposed_values_after_challenge
263 .iter()
264 .map(|&num| {
265 (0..num)
266 .map(|index| SymbolicVariable::new(Entry::Exposed, index))
267 .collect_vec()
268 })
269 .collect_vec()
270 }
271}
272
273impl<F: Field> AirBuilder for SymbolicRapBuilder<F> {
274 type F = F;
275 type Expr = SymbolicExpression<Self::F>;
276 type Var = SymbolicVariable<Self::F>;
277 type M = RowMajorMatrix<Self::Var>;
278
279 fn main(&self) -> Self::M {
281 if self.partitioned_main.len() == 1 {
282 self.partitioned_main[0].clone()
283 } else {
284 panic!("Main trace is either empty or partitioned. This function should not be used.")
285 }
286 }
287
288 fn is_first_row(&self) -> Self::Expr {
289 SymbolicExpression::IsFirstRow
290 }
291
292 fn is_last_row(&self) -> Self::Expr {
293 SymbolicExpression::IsLastRow
294 }
295
296 fn is_transition_window(&self, size: usize) -> Self::Expr {
297 if size == 2 {
298 SymbolicExpression::IsTransition
299 } else {
300 panic!("uni-stark only supports a window size of 2")
301 }
302 }
303
304 fn assert_zero<I: Into<Self::Expr>>(&mut self, x: I) {
305 self.constraints.push(x.into());
306 }
307}
308
309impl<F: Field> PairBuilder for SymbolicRapBuilder<F> {
310 fn preprocessed(&self) -> Self::M {
311 self.preprocessed.clone()
312 }
313}
314
315impl<F: Field> ExtensionBuilder for SymbolicRapBuilder<F> {
316 type EF = F;
317 type ExprEF = SymbolicExpression<F>;
318 type VarEF = SymbolicVariable<F>;
319
320 fn assert_zero_ext<I>(&mut self, x: I)
321 where
322 I: Into<Self::ExprEF>,
323 {
324 self.constraints.push(x.into());
325 }
326}
327
328impl<F: Field> AirBuilderWithPublicValues for SymbolicRapBuilder<F> {
329 type PublicVar = SymbolicVariable<F>;
330
331 fn public_values(&self) -> &[Self::PublicVar] {
332 &self.public_values
333 }
334}
335
336impl<F: Field> PermutationAirBuilder for SymbolicRapBuilder<F> {
337 type MP = RowMajorMatrix<Self::VarEF>;
338 type RandomVar = SymbolicVariable<F>;
339
340 fn permutation(&self) -> Self::MP {
341 self.after_challenge
342 .first()
343 .expect("Challenge phase not supported")
344 .clone()
345 }
346
347 fn permutation_randomness(&self) -> &[Self::RandomVar] {
348 self.challenges
349 .first()
350 .map(|c| c.as_slice())
351 .expect("Challenge phase not supported")
352 }
353}
354
355impl<F: Field> PermutationAirBuilderWithExposedValues for SymbolicRapBuilder<F> {
356 fn permutation_exposed_values(&self) -> &[Self::VarEF] {
357 self.exposed_values_after_challenge
358 .first()
359 .map(|c| c.as_slice())
360 .expect("Challenge phase not supported")
361 }
362}
363
364impl<F: Field> InteractionBuilder for SymbolicRapBuilder<F> {
365 fn push_interaction<E: Into<Self::Expr>>(
366 &mut self,
367 bus_index: BusIndex,
368 fields: impl IntoIterator<Item = E>,
369 count: impl Into<Self::Expr>,
370 count_weight: u32,
371 ) {
372 let fields = fields.into_iter().map(|f| f.into()).collect();
373 let count = count.into();
374 self.interactions.push(Interaction {
375 bus_index,
376 message: fields,
377 count,
378 count_weight,
379 });
380 }
381
382 fn num_interactions(&self) -> usize {
383 self.interactions.len()
384 }
385
386 fn all_interactions(&self) -> &[Interaction<Self::Expr>] {
387 &self.interactions
388 }
389}
390
391impl<F: Field> InteractionPhaseAirBuilder for SymbolicRapBuilder<F> {
392 fn finalize_interactions(&mut self) {
393 let num_interactions = self.num_interactions();
394 if num_interactions != 0 {
395 assert!(
396 self.after_challenge.is_empty(),
397 "after_challenge width should be auto-populated by the InteractionBuilder"
398 );
399 assert!(self.challenges.is_empty());
400 assert!(self.exposed_values_after_challenge.is_empty());
401
402 if self.rap_phase_seq_kind == RapPhaseSeqKind::FriLogUp {
403 let interaction_partitions =
404 find_interaction_chunks(&self.interactions, self.max_constraint_degree)
405 .interaction_partitions();
406 let num_chunks = interaction_partitions.len();
407 self.interaction_partitions.replace(interaction_partitions);
408 let perm_width = num_chunks + 1;
409 self.after_challenge = Self::new_after_challenge(&[perm_width]);
410 }
411
412 let phases_shapes = self.rap_phase_seq_kind.shape();
413 let phase_shape = phases_shapes.first().unwrap();
414
415 self.challenges = Self::new_challenges(&[phase_shape.num_challenges]);
416 self.exposed_values_after_challenge =
417 Self::new_exposed_values_after_challenge(&[phase_shape.num_exposed_values]);
418 }
419 }
420
421 fn max_constraint_degree(&self) -> usize {
422 self.max_constraint_degree
423 }
424
425 fn rap_phase_seq_kind(&self) -> RapPhaseSeqKind {
426 self.rap_phase_seq_kind
427 }
428
429 fn symbolic_interactions(&self) -> Vec<SymbolicInteraction<F>> {
430 self.interactions.clone()
431 }
432}
433
434impl<F: Field> PartitionedAirBuilder for SymbolicRapBuilder<F> {
435 fn cached_mains(&self) -> &[Self::M] {
436 &self.partitioned_main[..self.trace_width.cached_mains.len()]
437 }
438 fn common_main(&self) -> &Self::M {
439 assert_ne!(
440 self.trace_width.common_main, 0,
441 "AIR doesn't have a common main trace"
442 );
443 &self.partitioned_main[self.trace_width.cached_mains.len()]
444 }
445}
446
447#[allow(dead_code)]
448struct LocalOnlyChecker;
449
450#[allow(dead_code)]
451impl LocalOnlyChecker {
452 fn check_var<F: Field>(var: SymbolicVariable<F>) -> bool {
453 match var.entry {
454 Entry::Preprocessed { offset } => offset == 0,
455 Entry::Main { offset, .. } => offset == 0,
456 Entry::Permutation { offset } => offset == 0,
457 Entry::Public => true,
458 Entry::Challenge => true,
459 Entry::Exposed => true,
460 }
461 }
462
463 fn check_expr<F: Field>(expr: &SymbolicExpression<F>) -> bool {
464 match expr {
465 SymbolicExpression::Variable(var) => Self::check_var(*var),
466 SymbolicExpression::IsFirstRow => false,
467 SymbolicExpression::IsLastRow => false,
468 SymbolicExpression::IsTransition => false,
469 SymbolicExpression::Constant(_) => true,
470 SymbolicExpression::Add { x, y, .. } => Self::check_expr(x) && Self::check_expr(y),
471 SymbolicExpression::Sub { x, y, .. } => Self::check_expr(x) && Self::check_expr(y),
472 SymbolicExpression::Neg { x, .. } => Self::check_expr(x),
473 SymbolicExpression::Mul { x, y, .. } => Self::check_expr(x) && Self::check_expr(y),
474 }
475 }
476}
477
478fn gen_main_trace<F: Field>(
479 part_index: usize,
480 width: usize,
481) -> RowMajorMatrix<SymbolicVariable<F>> {
482 let mat_values = [0, 1]
483 .into_iter()
484 .flat_map(|offset| {
485 (0..width)
486 .map(move |index| SymbolicVariable::new(Entry::Main { part_index, offset }, index))
487 })
488 .collect_vec();
489 RowMajorMatrix::new(mat_values, width)
490}