openvm_stark_backend/air_builders/symbolic/
mod.rs1use std::iter;
4
5use itertools::Itertools;
6use p3_air::{
7 Air, AirBuilder, AirBuilderWithPublicValues, BaseAirWithPublicValues, ExtensionBuilder,
8 PairBuilder,
9};
10use p3_field::Field;
11use p3_matrix::dense::RowMajorMatrix;
12use tracing::instrument;
13
14use self::{
15 symbolic_expression::SymbolicExpression,
16 symbolic_variable::{Entry, SymbolicVariable},
17};
18use super::PartitionedAirBuilder;
19use crate::{
20 interaction::{Interaction, InteractionBuilder, SymbolicInteraction},
21 keygen::types::TraceWidth,
22};
23
24mod dag;
25pub mod statistics;
26pub mod symbolic_expression;
27pub mod symbolic_variable;
28
29pub use dag::*;
30
31use crate::interaction::BusIndex;
32
33#[derive(Clone, Debug)]
35pub struct SymbolicConstraints<F> {
36 pub constraints: Vec<SymbolicExpression<F>>,
39 pub interactions: Vec<SymbolicInteraction<F>>,
42}
43
44impl<F: Field> SymbolicConstraints<F> {
45 pub fn max_constraint_degree(&self) -> usize {
46 iter::empty()
47 .chain(&self.constraints)
48 .chain(
49 self.interactions
50 .iter()
51 .flat_map(|i| iter::once(&i.count).chain(&i.message)),
52 )
53 .map(|expr| expr.degree_multiple())
54 .max()
55 .unwrap_or(0)
56 }
57
58 pub fn max_interaction_degrees(&self) -> (usize, usize) {
60 let max_field_degree = self
61 .interactions
62 .iter()
63 .map(|interaction| {
64 interaction
65 .message
66 .iter()
67 .map(|field| field.degree_multiple())
68 .max()
69 .unwrap_or(0)
70 })
71 .max()
72 .unwrap_or(0);
73
74 let max_count_degree = self
75 .interactions
76 .iter()
77 .map(|interaction| interaction.count.degree_multiple())
78 .max()
79 .unwrap_or(0);
80
81 (max_field_degree, max_count_degree)
82 }
83}
84
85#[instrument(name = "evaluate constraints symbolically", skip_all, level = "debug")]
86pub fn get_symbolic_builder<F, R>(rap: &R, width: &TraceWidth) -> SymbolicRapBuilder<F>
87where
88 F: Field,
89 R: Air<SymbolicRapBuilder<F>> + BaseAirWithPublicValues<F> + ?Sized,
90{
91 let mut builder = SymbolicRapBuilder::new(width, rap.num_public_values());
92 Air::eval(rap, &mut builder);
93 builder
94}
95
96#[derive(Debug)]
98pub struct SymbolicRapBuilder<F> {
99 preprocessed: RowMajorMatrix<SymbolicVariable<F>>,
100 partitioned_main: Vec<RowMajorMatrix<SymbolicVariable<F>>>,
101 public_values: Vec<SymbolicVariable<F>>,
102 constraints: Vec<SymbolicExpression<F>>,
103 interactions: Vec<SymbolicInteraction<F>>,
104 trace_width: TraceWidth,
105}
106
107impl<F: Field> SymbolicRapBuilder<F> {
108 pub(crate) fn new(width: &TraceWidth, num_public_values: usize) -> Self {
109 let preprocessed_width = width.preprocessed.unwrap_or(0);
110 let prep_values = [0, 1]
111 .into_iter()
112 .flat_map(|offset| {
113 (0..width.preprocessed.unwrap_or(0))
114 .map(move |index| SymbolicVariable::new(Entry::Preprocessed { offset }, index))
115 })
116 .collect();
117 let preprocessed = RowMajorMatrix::new(prep_values, preprocessed_width);
118
119 let mut partitioned_main: Vec<_> = width
120 .cached_mains
121 .iter()
122 .enumerate()
123 .map(|(part_index, &width)| gen_main_trace(part_index, width))
124 .collect();
125 if width.common_main != 0 {
126 partitioned_main.push(gen_main_trace(width.cached_mains.len(), width.common_main));
127 }
128
129 let public_values = (0..num_public_values)
130 .map(move |index| SymbolicVariable::new(Entry::Public, index))
131 .collect();
132
133 Self {
134 preprocessed,
135 partitioned_main,
136 public_values,
137 constraints: vec![],
138 interactions: vec![],
139 trace_width: width.clone(),
140 }
141 }
142
143 pub fn constraints(self) -> SymbolicConstraints<F> {
144 SymbolicConstraints {
145 constraints: self.constraints,
146 interactions: self.interactions,
147 }
148 }
149
150 pub fn num_public_values(&self) -> usize {
151 self.public_values.len()
152 }
153
154 pub fn width(&self) -> TraceWidth {
155 self.trace_width.clone()
156 }
157}
158
159impl<F: Field> AirBuilder for SymbolicRapBuilder<F> {
160 type F = F;
161 type Expr = SymbolicExpression<Self::F>;
162 type Var = SymbolicVariable<Self::F>;
163 type M = RowMajorMatrix<Self::Var>;
164
165 fn main(&self) -> Self::M {
168 if self.partitioned_main.len() == 1 {
169 self.partitioned_main[0].clone()
170 } else {
171 panic!("Main trace is either empty or partitioned. This function should not be used.")
172 }
173 }
174
175 fn is_first_row(&self) -> Self::Expr {
176 SymbolicExpression::IsFirstRow
177 }
178
179 fn is_last_row(&self) -> Self::Expr {
180 SymbolicExpression::IsLastRow
181 }
182
183 fn is_transition_window(&self, size: usize) -> Self::Expr {
184 if size == 2 {
185 SymbolicExpression::IsTransition
186 } else {
187 panic!("uni-stark only supports a window size of 2")
188 }
189 }
190
191 fn assert_zero<I: Into<Self::Expr>>(&mut self, x: I) {
192 self.constraints.push(x.into());
193 }
194}
195
196impl<F: Field> PairBuilder for SymbolicRapBuilder<F> {
197 fn preprocessed(&self) -> Self::M {
198 self.preprocessed.clone()
199 }
200}
201
202impl<F: Field> ExtensionBuilder for SymbolicRapBuilder<F> {
203 type EF = F;
204 type ExprEF = SymbolicExpression<F>;
205 type VarEF = SymbolicVariable<F>;
206
207 fn assert_zero_ext<I>(&mut self, x: I)
208 where
209 I: Into<Self::ExprEF>,
210 {
211 self.constraints.push(x.into());
212 }
213}
214
215impl<F: Field> AirBuilderWithPublicValues for SymbolicRapBuilder<F> {
216 type PublicVar = SymbolicVariable<F>;
217
218 fn public_values(&self) -> &[Self::PublicVar] {
219 &self.public_values
220 }
221}
222
223impl<F: Field> InteractionBuilder for SymbolicRapBuilder<F> {
224 fn push_interaction<E: Into<Self::Expr>>(
225 &mut self,
226 bus_index: BusIndex,
227 fields: impl IntoIterator<Item = E>,
228 count: impl Into<Self::Expr>,
229 count_weight: u32,
230 ) {
231 let fields = fields.into_iter().map(|f| f.into()).collect();
232 let count = count.into();
233 self.interactions.push(Interaction {
234 bus_index,
235 message: fields,
236 count,
237 count_weight,
238 });
239 }
240
241 fn num_interactions(&self) -> usize {
242 self.interactions.len()
243 }
244
245 fn all_interactions(&self) -> &[Interaction<Self::Expr>] {
246 &self.interactions
247 }
248}
249
250impl<F: Field> PartitionedAirBuilder for SymbolicRapBuilder<F> {
251 fn cached_mains(&self) -> &[Self::M] {
252 &self.partitioned_main[..self.trace_width.cached_mains.len()]
253 }
254 fn common_main(&self) -> &Self::M {
255 assert_ne!(
256 self.trace_width.common_main, 0,
257 "AIR doesn't have a common main trace"
258 );
259 &self.partitioned_main[self.trace_width.cached_mains.len()]
260 }
261}
262
263#[allow(dead_code)]
264struct LocalOnlyChecker;
265
266#[allow(dead_code)]
267impl LocalOnlyChecker {
268 fn check_var<F: Field>(var: SymbolicVariable<F>) -> bool {
269 match var.entry {
270 Entry::Preprocessed { offset } => offset == 0,
271 Entry::Main { offset, .. } => offset == 0,
272 Entry::Public => true,
273 Entry::Challenge => true,
274 }
275 }
276
277 fn check_expr<F: Field>(expr: &SymbolicExpression<F>) -> bool {
278 match expr {
279 SymbolicExpression::Variable(var) => Self::check_var(*var),
280 SymbolicExpression::IsFirstRow => false,
281 SymbolicExpression::IsLastRow => false,
282 SymbolicExpression::IsTransition => false,
283 SymbolicExpression::Constant(_) => true,
284 SymbolicExpression::Add { x, y, .. } => Self::check_expr(x) && Self::check_expr(y),
285 SymbolicExpression::Sub { x, y, .. } => Self::check_expr(x) && Self::check_expr(y),
286 SymbolicExpression::Neg { x, .. } => Self::check_expr(x),
287 SymbolicExpression::Mul { x, y, .. } => Self::check_expr(x) && Self::check_expr(y),
288 }
289 }
290}
291
292fn gen_main_trace<F: Field>(
293 part_index: usize,
294 width: usize,
295) -> RowMajorMatrix<SymbolicVariable<F>> {
296 let mat_values = [0, 1]
297 .into_iter()
298 .flat_map(|offset| {
299 (0..width)
300 .map(move |index| SymbolicVariable::new(Entry::Main { part_index, offset }, index))
301 })
302 .collect_vec();
303 RowMajorMatrix::new(mat_values, width)
304}