openvm_stark_backend/air_builders/symbolic/
mod.rs

1// Originally copied from uni-stark/src/symbolic_builder.rs to allow A: ?Sized
2
3use 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/// Symbolic constraints for a single AIR with interactions.
35/// The constraints contain the constraints on the logup partial sums.
36#[derive(Clone, Debug)]
37pub struct SymbolicConstraints<F> {
38    /// All constraints of the RAP, including the constraints on the logup partial sums.
39    pub constraints: Vec<SymbolicExpression<F>>,
40    /// Symbolic representation of chip interactions. This is used by
41    /// the prover for after challenge trace generation, and some partial
42    /// information may be used by the verifier.
43    ///
44    /// **However**, any contributions to the quotient polynomial from
45    /// logup are already included in `constraints` and do not need to
46    /// be separately calculated from `interactions`.
47    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        // We pad to at least degree 2, since a quotient argument doesn't make sense with smaller
57        // degrees.
58        let constraint_degree = self.max_constraint_degree().max(2);
59
60        // The quotient's actual degree is approximately (max_constraint_degree - 1) * (trace
61        // height), where subtracting 1 comes from division by the zerofier.
62        // But we pad it to a power of two so that we can efficiently decompose the quotient.
63        log2_ceil_usize(constraint_degree - 1)
64    }
65
66    /// Returns the maximum field degree and count degree across all interactions
67    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/// An `AirBuilder` for evaluating constraints symbolically, and recording them for later use.
119#[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    /// Caching for FRI logup to avoid recomputation during keygen
134    interaction_partitions: Option<Vec<Vec<usize>>>,
135}
136
137impl<F: Field> SymbolicRapBuilder<F> {
138    /// - `num_challenges_to_sample`: for each challenge phase, how many challenges to sample
139    /// - `num_exposed_values_after_challenge`: in each challenge phase, how many values to expose
140    ///   to verifier
141    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    /// It is difficult to horizontally concatenate matrices when the main trace is partitioned, so
282    /// we disable this method in that case.
283    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}