1use air::AIR;
2use ndarray::ArrayView2;
3use ndarray::ArrayViewMut2;
4use strum::Display;
5use strum::EnumCount;
6use strum::EnumIter;
7use twenty_first::prelude::*;
8
9use crate::aet::AlgebraicExecutionTrace;
10use crate::challenges::Challenges;
11pub use crate::stark::NUM_QUOTIENT_SEGMENTS;
12use crate::table::master_table::MasterAuxTable;
13use crate::table::master_table::MasterMainTable;
14
15pub mod auxiliary_table;
16pub mod cascade;
17pub mod degree_lowering;
18pub mod hash;
19pub mod jump_stack;
20pub mod lookup;
21pub mod master_table;
22pub mod op_stack;
23pub mod processor;
24pub mod program;
25pub mod ram;
26pub mod u32;
27
28trait TraceTable: AIR {
29 type FillParam;
31 type FillReturnInfo;
32
33 fn fill(
34 main_table: ArrayViewMut2<BFieldElement>,
35 aet: &AlgebraicExecutionTrace,
36 _: Self::FillParam,
37 ) -> Self::FillReturnInfo;
38
39 fn pad(main_table: ArrayViewMut2<BFieldElement>, table_length: usize);
40
41 fn extend(
42 main_table: ArrayView2<BFieldElement>,
43 aux_table: ArrayViewMut2<XFieldElement>,
44 challenges: &Challenges,
45 );
46}
47
48#[derive(
49 Debug, Display, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, EnumCount, EnumIter,
50)]
51pub enum ConstraintType {
52 Initial,
54
55 Consistency,
57
58 Transition,
60
61 Terminal,
63}
64
65pub type MainRow<T> = [T; MasterMainTable::NUM_COLUMNS];
71
72pub type AuxiliaryRow = [XFieldElement; MasterAuxTable::NUM_COLUMNS];
74
75pub type QuotientSegments = [XFieldElement; NUM_QUOTIENT_SEGMENTS];
79
80#[cfg(test)]
81#[cfg_attr(coverage_nightly, coverage(off))]
82mod tests {
83 use std::collections::HashMap;
84
85 use air::table::AUX_CASCADE_TABLE_END;
86 use air::table::AUX_HASH_TABLE_END;
87 use air::table::AUX_JUMP_STACK_TABLE_END;
88 use air::table::AUX_LOOKUP_TABLE_END;
89 use air::table::AUX_OP_STACK_TABLE_END;
90 use air::table::AUX_PROCESSOR_TABLE_END;
91 use air::table::AUX_PROGRAM_TABLE_END;
92 use air::table::AUX_RAM_TABLE_END;
93 use air::table::AUX_U32_TABLE_END;
94 use air::table::CASCADE_TABLE_END;
95 use air::table::HASH_TABLE_END;
96 use air::table::JUMP_STACK_TABLE_END;
97 use air::table::LOOKUP_TABLE_END;
98 use air::table::OP_STACK_TABLE_END;
99 use air::table::PROCESSOR_TABLE_END;
100 use air::table::PROGRAM_TABLE_END;
101 use air::table::RAM_TABLE_END;
102 use air::table::U32_TABLE_END;
103 use air::table::cascade::CascadeTable;
104 use air::table::hash::HashTable;
105 use air::table::jump_stack::JumpStackTable;
106 use air::table::lookup::LookupTable;
107 use air::table::op_stack::OpStackTable;
108 use air::table::processor::ProcessorTable;
109 use air::table::program::ProgramTable;
110 use air::table::ram::RamTable;
111 use air::table::u32::U32Table;
112 use constraint_circuit::BinOp;
113 use constraint_circuit::CircuitExpression;
114 use constraint_circuit::ConstraintCircuit;
115 use constraint_circuit::ConstraintCircuitBuilder;
116 use constraint_circuit::ConstraintCircuitMonad;
117 use constraint_circuit::DegreeLoweringInfo;
118 use constraint_circuit::InputIndicator;
119 use itertools::Itertools;
120 use ndarray::Array2;
121 use ndarray::ArrayView2;
122 use rand::prelude::*;
123 use rand::random;
124
125 use crate::challenges::Challenges;
126 use crate::prelude::Claim;
127 use crate::table::degree_lowering::DegreeLoweringTable;
128 use crate::tests::test;
129
130 use super::*;
131
132 fn table_constraints_prop<II: InputIndicator>(
136 constraints: &[ConstraintCircuit<II>],
137 table_name: &str,
138 ) {
139 let seed = random();
140 let mut rng = StdRng::seed_from_u64(seed);
141 println!("seed: {seed}");
142
143 let dummy_claim = Claim::default();
144 let challenges: [XFieldElement; Challenges::SAMPLE_COUNT] = rng.random();
145 let challenges = challenges.to_vec();
146 let challenges = Challenges::new(challenges, &dummy_claim);
147 let challenges = &challenges.challenges;
148
149 let num_rows = 2;
150 let main_shape = [num_rows, MasterMainTable::NUM_COLUMNS];
151 let aux_shape = [num_rows, MasterAuxTable::NUM_COLUMNS];
152 let main_rows = Array2::from_shape_simple_fn(main_shape, || rng.random::<BFieldElement>());
153 let aux_rows = Array2::from_shape_simple_fn(aux_shape, || rng.random::<XFieldElement>());
154 let main_rows = main_rows.view();
155 let aux_rows = aux_rows.view();
156
157 let mut values = HashMap::new();
158 for c in constraints {
159 evaluate_assert_unique(c, challenges, main_rows, aux_rows, &mut values);
160 }
161
162 let circuit_degree = constraints.iter().map(|c| c.degree()).max().unwrap_or(-1);
163 println!("Max degree constraint for {table_name} table: {circuit_degree}");
164 }
165
166 fn evaluate_assert_unique<II: InputIndicator>(
175 constraint: &ConstraintCircuit<II>,
176 challenges: &[XFieldElement],
177 main_rows: ArrayView2<BFieldElement>,
178 aux_rows: ArrayView2<XFieldElement>,
179 values: &mut HashMap<XFieldElement, (u64, ConstraintCircuit<II>)>,
180 ) -> XFieldElement {
181 let value = match &constraint.expression {
182 CircuitExpression::BinOp(binop, lhs, rhs) => {
183 let lhs = lhs.borrow();
184 let rhs = rhs.borrow();
185 let lhs = evaluate_assert_unique(&lhs, challenges, main_rows, aux_rows, values);
186 let rhs = evaluate_assert_unique(&rhs, challenges, main_rows, aux_rows, values);
187 binop.operation(lhs, rhs)
188 }
189 _ => constraint.evaluate(main_rows, aux_rows, challenges),
190 };
191
192 let own_id = constraint.id.to_owned();
193 let maybe_entry = values.insert(value, (own_id, constraint.clone()));
194 if let Some((other_id, other_circuit)) = maybe_entry {
195 assert_eq!(
196 own_id, other_id,
197 "Circuit ID {other_id} and circuit ID {own_id} are not unique. \
198 Collision on:\n\
199 ID {other_id} – {other_circuit}\n\
200 ID {own_id} – {constraint}\n\
201 Both evaluate to {value}.",
202 );
203 }
204
205 value
206 }
207
208 #[macro_rules_attr::apply(test)]
209 fn nodes_are_unique_for_all_constraints() {
210 fn build_constraints<II: InputIndicator>(
211 multicircuit_builder: &dyn Fn(
212 &ConstraintCircuitBuilder<II>,
213 ) -> Vec<ConstraintCircuitMonad<II>>,
214 ) -> Vec<ConstraintCircuit<II>> {
215 let circuit_builder = ConstraintCircuitBuilder::new();
216 let multicircuit = multicircuit_builder(&circuit_builder);
217 let mut constraints = multicircuit.into_iter().map(|c| c.consume()).collect_vec();
218 ConstraintCircuit::assert_unique_ids(&mut constraints);
219 constraints
220 }
221
222 macro_rules! assert_constraint_properties {
223 ($table:ident) => {{
224 let init = build_constraints(&$table::initial_constraints);
225 let cons = build_constraints(&$table::consistency_constraints);
226 let tran = build_constraints(&$table::transition_constraints);
227 let term = build_constraints(&$table::terminal_constraints);
228 table_constraints_prop(&init, concat!(stringify!($table), " init"));
229 table_constraints_prop(&cons, concat!(stringify!($table), " cons"));
230 table_constraints_prop(&tran, concat!(stringify!($table), " tran"));
231 table_constraints_prop(&term, concat!(stringify!($table), " term"));
232 }};
233 }
234
235 assert_constraint_properties!(ProcessorTable);
236 assert_constraint_properties!(ProgramTable);
237 assert_constraint_properties!(JumpStackTable);
238 assert_constraint_properties!(OpStackTable);
239 assert_constraint_properties!(RamTable);
240 assert_constraint_properties!(HashTable);
241 assert_constraint_properties!(U32Table);
242 assert_constraint_properties!(CascadeTable);
243 assert_constraint_properties!(LookupTable);
244 }
245
246 fn lower_degree_and_assert_properties<II: InputIndicator>(
254 multicircuit: &mut [ConstraintCircuitMonad<II>],
255 info: DegreeLoweringInfo,
256 ) -> (
257 Vec<ConstraintCircuitMonad<II>>,
258 Vec<ConstraintCircuitMonad<II>>,
259 ) {
260 let seed = random();
261 let mut rng = StdRng::seed_from_u64(seed);
262 println!("seed: {seed}");
263
264 let num_constraints = multicircuit.len();
265 println!("original multicircuit:");
266 for circuit in multicircuit.iter() {
267 println!(" {circuit}");
268 }
269
270 let (new_main_constraints, new_aux_constraints) =
271 ConstraintCircuitMonad::lower_to_degree(multicircuit, info);
272
273 assert_eq!(num_constraints, multicircuit.len());
274
275 let target_deg = info.target_degree;
276 assert!(ConstraintCircuitMonad::multicircuit_degree(multicircuit) <= target_deg);
277 assert!(ConstraintCircuitMonad::multicircuit_degree(&new_main_constraints) <= target_deg);
278 assert!(ConstraintCircuitMonad::multicircuit_degree(&new_aux_constraints) <= target_deg);
279
280 let mut substitution_rules = vec![];
282 for (constraint_type, constraints) in [
283 ("main", &new_main_constraints),
284 ("aux", &new_aux_constraints),
285 ] {
286 for (i, constraint) in constraints.iter().enumerate() {
287 let expression = constraint.circuit.borrow().expression.clone();
288 let CircuitExpression::BinOp(BinOp::Add, lhs, rhs) = expression else {
289 panic!("New {constraint_type} constraint {i} must be a subtraction.");
290 };
291 let CircuitExpression::Input(input_indicator) = lhs.borrow().expression.clone()
292 else {
293 panic!("New {constraint_type} constraint {i} must be a simple substitution.");
294 };
295 let substitution_rule = rhs.borrow().clone();
296 assert_substitution_rule_uses_legal_variables(input_indicator, &substitution_rule);
297 substitution_rules.push(substitution_rule);
298 }
299 }
300
301 let dummy_claim = Claim::default();
304 let challenges: [XFieldElement; Challenges::SAMPLE_COUNT] = rng.random();
305 let challenges = challenges.to_vec();
306 let challenges = Challenges::new(challenges, &dummy_claim);
307 let challenges = &challenges.challenges;
308
309 let num_rows = 2;
310 let main_shape = [num_rows, MasterMainTable::NUM_COLUMNS];
311 let aux_shape = [num_rows, MasterAuxTable::NUM_COLUMNS];
312 let main_rows = Array2::from_shape_simple_fn(main_shape, || rng.random::<BFieldElement>());
313 let aux_rows = Array2::from_shape_simple_fn(aux_shape, || rng.random::<XFieldElement>());
314 let main_rows = main_rows.view();
315 let aux_rows = aux_rows.view();
316
317 let evaluated_substitution_rules = substitution_rules
318 .iter()
319 .map(|c| c.evaluate(main_rows, aux_rows, challenges));
320
321 let mut values_to_index = HashMap::new();
322 for (idx, value) in evaluated_substitution_rules.enumerate() {
323 if let Some(index) = values_to_index.get(&value) {
324 panic!("Substitution {idx} must be distinct from substitution {index}.");
325 } else {
326 values_to_index.insert(value, idx);
327 }
328 }
329
330 println!("new multicircuit:");
332 for circuit in multicircuit.iter() {
333 println!(" {circuit}");
334 }
335 println!("new main constraints:");
336 for constraint in &new_main_constraints {
337 println!(" {constraint}");
338 }
339 println!("new aux constraints:");
340 for constraint in &new_aux_constraints {
341 println!(" {constraint}");
342 }
343
344 let num_new_main_constraints = new_main_constraints.len();
345 let num_new_aux_constraints = new_aux_constraints.len();
346 println!(
347 "Started with {num_constraints} constraints. \
348 Derived {num_new_main_constraints} new main, \
349 {num_new_aux_constraints} new auxiliary constraints."
350 );
351
352 (new_main_constraints, new_aux_constraints)
353 }
354
355 fn assert_substitution_rule_uses_legal_variables<II: InputIndicator>(
359 new_var: II,
360 substitution_rule: &ConstraintCircuit<II>,
361 ) {
362 match substitution_rule.expression.clone() {
363 CircuitExpression::BinOp(_, lhs, rhs) => {
364 let lhs = lhs.borrow();
365 let rhs = rhs.borrow();
366 assert_substitution_rule_uses_legal_variables(new_var, &lhs);
367 assert_substitution_rule_uses_legal_variables(new_var, &rhs);
368 }
369 CircuitExpression::Input(old_var) => {
370 let new_var_is_main = new_var.is_main_table_column();
371 let old_var_is_main = old_var.is_main_table_column();
372 let legal_substitute = match (new_var_is_main, old_var_is_main) {
373 (true, false) => false,
374 (false, true) => true,
375 _ => old_var.column() < new_var.column(),
376 };
377 assert!(legal_substitute, "Cannot replace {old_var} with {new_var}.");
378 }
379 _ => (),
380 };
381 }
382
383 #[macro_rules_attr::apply(test)]
384 fn degree_lowering_works_correctly_for_all_tables() {
385 macro_rules! assert_degree_lowering {
386 ($table:ident ($main_end:ident, $aux_end:ident)) => {{
387 let degree_lowering_info = DegreeLoweringInfo {
388 target_degree: air::TARGET_DEGREE,
389 num_main_cols: $main_end,
390 num_aux_cols: $aux_end,
391 };
392 let circuit_builder = ConstraintCircuitBuilder::new();
393 let mut init = $table::initial_constraints(&circuit_builder);
394 lower_degree_and_assert_properties(&mut init, degree_lowering_info);
395
396 let circuit_builder = ConstraintCircuitBuilder::new();
397 let mut cons = $table::consistency_constraints(&circuit_builder);
398 lower_degree_and_assert_properties(&mut cons, degree_lowering_info);
399
400 let circuit_builder = ConstraintCircuitBuilder::new();
401 let mut tran = $table::transition_constraints(&circuit_builder);
402 lower_degree_and_assert_properties(&mut tran, degree_lowering_info);
403
404 let circuit_builder = ConstraintCircuitBuilder::new();
405 let mut term = $table::terminal_constraints(&circuit_builder);
406 lower_degree_and_assert_properties(&mut term, degree_lowering_info);
407 }};
408 }
409
410 assert_degree_lowering!(ProgramTable(PROGRAM_TABLE_END, AUX_PROGRAM_TABLE_END));
411 assert_degree_lowering!(ProcessorTable(PROCESSOR_TABLE_END, AUX_PROCESSOR_TABLE_END));
412 assert_degree_lowering!(OpStackTable(OP_STACK_TABLE_END, AUX_OP_STACK_TABLE_END));
413 assert_degree_lowering!(RamTable(RAM_TABLE_END, AUX_RAM_TABLE_END));
414 assert_degree_lowering!(JumpStackTable(
415 JUMP_STACK_TABLE_END,
416 AUX_JUMP_STACK_TABLE_END
417 ));
418 assert_degree_lowering!(HashTable(HASH_TABLE_END, AUX_HASH_TABLE_END));
419 assert_degree_lowering!(CascadeTable(CASCADE_TABLE_END, AUX_CASCADE_TABLE_END));
420 assert_degree_lowering!(LookupTable(LOOKUP_TABLE_END, AUX_LOOKUP_TABLE_END));
421 assert_degree_lowering!(U32Table(U32_TABLE_END, AUX_U32_TABLE_END));
422 }
423
424 #[macro_rules_attr::apply(test)]
430 #[ignore = "(probably) requires normalization of circuit expressions"]
431 fn substitution_rules_are_unique() {
432 let challenges = Challenges::default();
433 let mut main_table_rows =
434 Array2::from_shape_fn((2, MasterMainTable::NUM_COLUMNS), |_| random());
435 let mut aux_table_rows =
436 Array2::from_shape_fn((2, MasterAuxTable::NUM_COLUMNS), |_| random());
437
438 DegreeLoweringTable::fill_derived_main_columns(main_table_rows.view_mut());
439 DegreeLoweringTable::fill_derived_aux_columns(
440 main_table_rows.view(),
441 aux_table_rows.view_mut(),
442 &challenges,
443 );
444
445 let mut encountered_values = HashMap::new();
446 for col_idx in 0..MasterMainTable::NUM_COLUMNS {
447 let val = main_table_rows[(0, col_idx)].lift();
448 let other_entry = encountered_values.insert(val, col_idx);
449 if let Some(other_idx) = other_entry {
450 panic!("Duplicate value {val} in derived main column {other_idx} and {col_idx}.");
451 }
452 }
453 println!("Now comparing auxiliary columns…");
454 for col_idx in 0..MasterAuxTable::NUM_COLUMNS {
455 let val = aux_table_rows[(0, col_idx)];
456 let other_entry = encountered_values.insert(val, col_idx);
457 if let Some(other_idx) = other_entry {
458 panic!("Duplicate value {val} in derived aux column {other_idx} and {col_idx}.");
459 }
460 }
461 }
462}