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