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