1use alloc::vec;
2use alloc::vec::Vec;
3
4use p3_air::{Air, AirBuilder, AirBuilderWithPublicValues, PairBuilder};
5use p3_field::Field;
6use p3_matrix::dense::RowMajorMatrix;
7use p3_util::log2_ceil_usize;
8use tracing::instrument;
9
10use crate::Entry;
11use crate::symbolic_expression::SymbolicExpression;
12use crate::symbolic_variable::SymbolicVariable;
13
14#[instrument(name = "infer log of constraint degree", skip_all)]
15pub fn get_log_quotient_degree<F, A>(
16 air: &A,
17 preprocessed_width: usize,
18 num_public_values: usize,
19 is_zk: usize,
20) -> usize
21where
22 F: Field,
23 A: Air<SymbolicAirBuilder<F>>,
24{
25 assert!(is_zk <= 1, "is_zk must be either 0 or 1");
26 let constraint_degree =
28 (get_max_constraint_degree(air, preprocessed_width, num_public_values) + is_zk).max(2);
29
30 log2_ceil_usize(constraint_degree - 1)
34}
35
36#[instrument(name = "infer constraint degree", skip_all, level = "debug")]
37pub fn get_max_constraint_degree<F, A>(
38 air: &A,
39 preprocessed_width: usize,
40 num_public_values: usize,
41) -> usize
42where
43 F: Field,
44 A: Air<SymbolicAirBuilder<F>>,
45{
46 get_symbolic_constraints(air, preprocessed_width, num_public_values)
47 .iter()
48 .map(|c| c.degree_multiple())
49 .max()
50 .unwrap_or(0)
51}
52
53#[instrument(name = "evaluate constraints symbolically", skip_all, level = "debug")]
54pub fn get_symbolic_constraints<F, A>(
55 air: &A,
56 preprocessed_width: usize,
57 num_public_values: usize,
58) -> Vec<SymbolicExpression<F>>
59where
60 F: Field,
61 A: Air<SymbolicAirBuilder<F>>,
62{
63 let mut builder = SymbolicAirBuilder::new(preprocessed_width, air.width(), num_public_values);
64 air.eval(&mut builder);
65 builder.constraints()
66}
67
68#[derive(Debug)]
70pub struct SymbolicAirBuilder<F: Field> {
71 preprocessed: RowMajorMatrix<SymbolicVariable<F>>,
72 main: RowMajorMatrix<SymbolicVariable<F>>,
73 public_values: Vec<SymbolicVariable<F>>,
74 constraints: Vec<SymbolicExpression<F>>,
75}
76
77impl<F: Field> SymbolicAirBuilder<F> {
78 pub(crate) fn new(preprocessed_width: usize, width: usize, num_public_values: usize) -> Self {
79 let prep_values = [0, 1]
80 .into_iter()
81 .flat_map(|offset| {
82 (0..preprocessed_width)
83 .map(move |index| SymbolicVariable::new(Entry::Preprocessed { offset }, index))
84 })
85 .collect();
86 let main_values = [0, 1]
87 .into_iter()
88 .flat_map(|offset| {
89 (0..width).map(move |index| SymbolicVariable::new(Entry::Main { offset }, index))
90 })
91 .collect();
92 let public_values = (0..num_public_values)
93 .map(move |index| SymbolicVariable::new(Entry::Public, index))
94 .collect();
95 Self {
96 preprocessed: RowMajorMatrix::new(prep_values, preprocessed_width),
97 main: RowMajorMatrix::new(main_values, width),
98 public_values,
99 constraints: vec![],
100 }
101 }
102
103 pub(crate) fn constraints(self) -> Vec<SymbolicExpression<F>> {
104 self.constraints
105 }
106}
107
108impl<F: Field> AirBuilder for SymbolicAirBuilder<F> {
109 type F = F;
110 type Expr = SymbolicExpression<F>;
111 type Var = SymbolicVariable<F>;
112 type M = RowMajorMatrix<Self::Var>;
113
114 fn main(&self) -> Self::M {
115 self.main.clone()
116 }
117
118 fn is_first_row(&self) -> Self::Expr {
119 SymbolicExpression::IsFirstRow
120 }
121
122 fn is_last_row(&self) -> Self::Expr {
123 SymbolicExpression::IsLastRow
124 }
125
126 fn is_transition_window(&self, size: usize) -> Self::Expr {
129 if size == 2 {
130 SymbolicExpression::IsTransition
131 } else {
132 panic!("uni-stark only supports a window size of 2")
133 }
134 }
135
136 fn assert_zero<I: Into<Self::Expr>>(&mut self, x: I) {
137 self.constraints.push(x.into());
138 }
139}
140
141impl<F: Field> AirBuilderWithPublicValues for SymbolicAirBuilder<F> {
142 type PublicVar = SymbolicVariable<F>;
143 fn public_values(&self) -> &[Self::PublicVar] {
144 &self.public_values
145 }
146}
147
148impl<F: Field> PairBuilder for SymbolicAirBuilder<F> {
149 fn preprocessed(&self) -> Self::M {
150 self.preprocessed.clone()
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use alloc::vec;
157 use alloc::vec::Vec;
158
159 use p3_air::BaseAir;
160 use p3_baby_bear::BabyBear;
161
162 use super::*;
163
164 #[derive(Debug)]
165 struct MockAir {
166 constraints: Vec<SymbolicVariable<BabyBear>>,
167 width: usize,
168 }
169
170 impl BaseAir<BabyBear> for MockAir {
171 fn width(&self) -> usize {
172 self.width
173 }
174 }
175
176 impl Air<SymbolicAirBuilder<BabyBear>> for MockAir {
177 fn eval(&self, builder: &mut SymbolicAirBuilder<BabyBear>) {
178 for constraint in &self.constraints {
179 builder.assert_zero(*constraint);
180 }
181 }
182 }
183
184 #[test]
185 fn test_get_log_quotient_degree_no_constraints() {
186 let air = MockAir {
187 constraints: vec![],
188 width: 4,
189 };
190 let log_degree = get_log_quotient_degree(&air, 3, 2, 0);
191 assert_eq!(log_degree, 0);
192 }
193
194 #[test]
195 fn test_get_log_quotient_degree_single_constraint() {
196 let air = MockAir {
197 constraints: vec![SymbolicVariable::new(Entry::Main { offset: 0 }, 0)],
198 width: 4,
199 };
200 let log_degree = get_log_quotient_degree(&air, 3, 2, 0);
201 assert_eq!(log_degree, log2_ceil_usize(1));
202 }
203
204 #[test]
205 fn test_get_log_quotient_degree_multiple_constraints() {
206 let air = MockAir {
207 constraints: vec![
208 SymbolicVariable::new(Entry::Main { offset: 0 }, 0),
209 SymbolicVariable::new(Entry::Main { offset: 1 }, 1),
210 SymbolicVariable::new(Entry::Main { offset: 2 }, 2),
211 ],
212 width: 4,
213 };
214 let log_degree = get_log_quotient_degree(&air, 3, 2, 0);
215 assert_eq!(log_degree, log2_ceil_usize(1));
216 }
217
218 #[test]
219 fn test_get_max_constraint_degree_no_constraints() {
220 let air = MockAir {
221 constraints: vec![],
222 width: 4,
223 };
224 let max_degree = get_max_constraint_degree(&air, 3, 2);
225 assert_eq!(
226 max_degree, 0,
227 "No constraints should result in a degree of 0"
228 );
229 }
230
231 #[test]
232 fn test_get_max_constraint_degree_multiple_constraints() {
233 let air = MockAir {
234 constraints: vec![
235 SymbolicVariable::new(Entry::Main { offset: 0 }, 0),
236 SymbolicVariable::new(Entry::Main { offset: 1 }, 1),
237 SymbolicVariable::new(Entry::Main { offset: 2 }, 2),
238 ],
239 width: 4,
240 };
241 let max_degree = get_max_constraint_degree(&air, 3, 2);
242 assert_eq!(max_degree, 1, "Max constraint degree should be 1");
243 }
244
245 #[test]
246 fn test_get_symbolic_constraints() {
247 let c1 = SymbolicVariable::new(Entry::Main { offset: 0 }, 0);
248 let c2 = SymbolicVariable::new(Entry::Main { offset: 1 }, 1);
249
250 let air = MockAir {
251 constraints: vec![c1, c2],
252 width: 4,
253 };
254
255 let constraints = get_symbolic_constraints(&air, 3, 2);
256
257 assert_eq!(constraints.len(), 2, "Should return exactly 2 constraints");
258
259 assert!(
260 constraints.iter().any(|x| matches!(x, SymbolicExpression::Variable(v) if v.index == c1.index && v.entry == c1.entry)),
261 "Expected constraint {:?} was not found",
262 c1
263 );
264
265 assert!(
266 constraints.iter().any(|x| matches!(x, SymbolicExpression::Variable(v) if v.index == c2.index && v.entry == c2.entry)),
267 "Expected constraint {:?} was not found",
268 c2
269 );
270 }
271
272 #[test]
273 fn test_symbolic_air_builder_initialization() {
274 let builder = SymbolicAirBuilder::<BabyBear>::new(2, 4, 3);
275
276 let expected_main = [
277 SymbolicVariable::<BabyBear>::new(Entry::Main { offset: 0 }, 0),
278 SymbolicVariable::<BabyBear>::new(Entry::Main { offset: 0 }, 1),
279 SymbolicVariable::<BabyBear>::new(Entry::Main { offset: 0 }, 2),
280 SymbolicVariable::<BabyBear>::new(Entry::Main { offset: 0 }, 3),
281 SymbolicVariable::<BabyBear>::new(Entry::Main { offset: 1 }, 0),
282 SymbolicVariable::<BabyBear>::new(Entry::Main { offset: 1 }, 1),
283 SymbolicVariable::<BabyBear>::new(Entry::Main { offset: 1 }, 2),
284 SymbolicVariable::<BabyBear>::new(Entry::Main { offset: 1 }, 3),
285 ];
286
287 let builder_main = builder.main.values;
288
289 assert_eq!(
290 builder_main.len(),
291 expected_main.len(),
292 "Main matrix should have the expected length"
293 );
294
295 for (expected, actual) in expected_main.iter().zip(builder_main.iter()) {
296 assert_eq!(expected.index, actual.index, "Index mismatch");
297 assert_eq!(expected.entry, actual.entry, "Entry mismatch");
298 }
299 }
300
301 #[test]
302 fn test_symbolic_air_builder_is_first_last_row() {
303 let builder = SymbolicAirBuilder::<BabyBear>::new(2, 4, 3);
304
305 assert!(
306 matches!(builder.is_first_row(), SymbolicExpression::IsFirstRow),
307 "First row condition did not match"
308 );
309
310 assert!(
311 matches!(builder.is_last_row(), SymbolicExpression::IsLastRow),
312 "Last row condition did not match"
313 );
314 }
315
316 #[test]
317 fn test_symbolic_air_builder_assert_zero() {
318 let mut builder = SymbolicAirBuilder::<BabyBear>::new(2, 4, 3);
319 let expr = SymbolicExpression::Constant(BabyBear::new(5));
320 builder.assert_zero(expr.clone());
321
322 let constraints = builder.constraints();
323 assert_eq!(constraints.len(), 1, "One constraint should be recorded");
324
325 assert!(
326 constraints.iter().any(
327 |x| matches!(x, SymbolicExpression::Constant(val) if *val == BabyBear::new(5))
328 ),
329 "Constraint should match the asserted one"
330 );
331 }
332}