1#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
13
14use std::cell::RefCell;
15use std::cmp;
16use std::collections::HashMap;
17use std::fmt::Debug;
18use std::fmt::Display;
19use std::fmt::Formatter;
20use std::fmt::Result as FmtResult;
21use std::hash::Hash;
22use std::hash::Hasher;
23use std::iter::Sum;
24use std::ops::Add;
25use std::ops::Mul;
26use std::ops::Neg;
27use std::ops::Sub;
28use std::rc::Rc;
29
30use arbitrary::Arbitrary;
31use arbitrary::Unstructured;
32use itertools::Itertools;
33use ndarray::ArrayView2;
34use num_traits::One;
35use num_traits::Zero;
36use quote::ToTokens;
37use quote::quote;
38use twenty_first::prelude::*;
39
40type RcCircuit<II> = Rc<RefCell<ConstraintCircuit<II>>>;
42
43mod private {
44 pub trait Seal {}
46}
47
48#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
49pub struct DegreeLoweringInfo {
50 pub target_degree: isize,
52
53 pub num_main_cols: usize,
55
56 pub num_aux_cols: usize,
59}
60
61#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
62pub enum BinOp {
63 Add,
64 Mul,
65}
66
67impl Display for BinOp {
68 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
69 match self {
70 BinOp::Add => write!(f, "+"),
71 BinOp::Mul => write!(f, "*"),
72 }
73 }
74}
75
76impl ToTokens for BinOp {
77 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
78 match self {
79 BinOp::Add => tokens.extend(quote!(+)),
80 BinOp::Mul => tokens.extend(quote!(*)),
81 }
82 }
83}
84
85impl BinOp {
86 pub fn operation<L, R, O>(&self, lhs: L, rhs: R) -> O
87 where
88 L: Add<R, Output = O> + Mul<R, Output = O>,
89 {
90 match self {
91 BinOp::Add => lhs + rhs,
92 BinOp::Mul => lhs * rhs,
93 }
94 }
95}
96
97pub trait InputIndicator: Debug + Display + Copy + Hash + Eq + ToTokens + private::Seal {
113 fn is_main_table_column(&self) -> bool;
115
116 fn is_current_row(&self) -> bool;
118
119 fn column(&self) -> usize;
121
122 fn main_table_input(index: usize) -> Self;
123 fn aux_table_input(index: usize) -> Self;
124
125 fn evaluate(
126 &self,
127 main_table: ArrayView2<BFieldElement>,
128 aux_table: ArrayView2<XFieldElement>,
129 ) -> XFieldElement;
130}
131
132#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Arbitrary)]
135pub enum SingleRowIndicator {
136 Main(usize),
137 Aux(usize),
138}
139
140impl private::Seal for SingleRowIndicator {}
141
142impl Display for SingleRowIndicator {
143 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
144 let input_indicator: String = match self {
145 Self::Main(i) => format!("main_row[{i}]"),
146 Self::Aux(i) => format!("aux_row[{i}]"),
147 };
148
149 write!(f, "{input_indicator}")
150 }
151}
152
153impl ToTokens for SingleRowIndicator {
154 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
155 match self {
156 Self::Main(i) => tokens.extend(quote!(main_row[#i])),
157 Self::Aux(i) => tokens.extend(quote!(aux_row[#i])),
158 }
159 }
160}
161
162impl InputIndicator for SingleRowIndicator {
163 fn is_main_table_column(&self) -> bool {
164 matches!(self, Self::Main(_))
165 }
166
167 fn is_current_row(&self) -> bool {
168 true
169 }
170
171 fn column(&self) -> usize {
172 match self {
173 Self::Main(i) | Self::Aux(i) => *i,
174 }
175 }
176
177 fn main_table_input(index: usize) -> Self {
178 Self::Main(index)
179 }
180
181 fn aux_table_input(index: usize) -> Self {
182 Self::Aux(index)
183 }
184
185 fn evaluate(
186 &self,
187 main_table: ArrayView2<BFieldElement>,
188 aux_table: ArrayView2<XFieldElement>,
189 ) -> XFieldElement {
190 match self {
191 Self::Main(i) => main_table[[0, *i]].lift(),
192 Self::Aux(i) => aux_table[[0, *i]],
193 }
194 }
195}
196
197#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Arbitrary)]
200pub enum DualRowIndicator {
201 CurrentMain(usize),
202 CurrentAux(usize),
203 NextMain(usize),
204 NextAux(usize),
205}
206
207impl private::Seal for DualRowIndicator {}
208
209impl Display for DualRowIndicator {
210 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
211 let input_indicator: String = match self {
212 Self::CurrentMain(i) => format!("current_main_row[{i}]"),
213 Self::CurrentAux(i) => format!("current_aux_row[{i}]"),
214 Self::NextMain(i) => format!("next_main_row[{i}]"),
215 Self::NextAux(i) => format!("next_aux_row[{i}]"),
216 };
217
218 write!(f, "{input_indicator}")
219 }
220}
221
222impl ToTokens for DualRowIndicator {
223 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
224 match self {
225 Self::CurrentMain(i) => tokens.extend(quote!(current_main_row[#i])),
226 Self::CurrentAux(i) => tokens.extend(quote!(current_aux_row[#i])),
227 Self::NextMain(i) => tokens.extend(quote!(next_main_row[#i])),
228 Self::NextAux(i) => tokens.extend(quote!(next_aux_row[#i])),
229 }
230 }
231}
232
233impl InputIndicator for DualRowIndicator {
234 fn is_main_table_column(&self) -> bool {
235 matches!(self, Self::CurrentMain(_) | Self::NextMain(_))
236 }
237
238 fn is_current_row(&self) -> bool {
239 matches!(self, Self::CurrentMain(_) | Self::CurrentAux(_))
240 }
241
242 fn column(&self) -> usize {
243 match self {
244 Self::CurrentMain(i) | Self::NextMain(i) | Self::CurrentAux(i) | Self::NextAux(i) => *i,
245 }
246 }
247
248 fn main_table_input(index: usize) -> Self {
249 Self::CurrentMain(index)
253 }
254
255 fn aux_table_input(index: usize) -> Self {
256 Self::CurrentAux(index)
257 }
258
259 fn evaluate(
260 &self,
261 main_table: ArrayView2<BFieldElement>,
262 aux_table: ArrayView2<XFieldElement>,
263 ) -> XFieldElement {
264 match self {
265 Self::CurrentMain(i) => main_table[[0, *i]].lift(),
266 Self::CurrentAux(i) => aux_table[[0, *i]],
267 Self::NextMain(i) => main_table[[1, *i]].lift(),
268 Self::NextAux(i) => aux_table[[1, *i]],
269 }
270 }
271}
272
273#[derive(Debug, Clone)]
293pub enum CircuitExpression<II: InputIndicator> {
294 BConst(BFieldElement),
295 XConst(XFieldElement),
296 Input(II),
297 Challenge(usize),
298 BinOp(BinOp, RcCircuit<II>, RcCircuit<II>),
299}
300
301impl<II: InputIndicator> Hash for CircuitExpression<II> {
302 fn hash<H: Hasher>(&self, state: &mut H) {
303 match self {
305 Self::BConst(bfe) => {
306 0_usize.hash(state);
307 bfe.hash(state);
308 }
309 Self::XConst(xfe) => {
310 1_usize.hash(state);
311 xfe.hash(state);
312 }
313 Self::Input(index) => {
314 2_usize.hash(state);
315 index.hash(state);
316 }
317 Self::Challenge(id) => {
318 3_usize.hash(state);
319 id.hash(state);
320 }
321 Self::BinOp(binop, lhs, rhs) => {
322 4_usize.hash(state);
323 binop.hash(state);
324 lhs.borrow().hash(state);
325 rhs.borrow().hash(state);
326 }
327 }
328 }
329}
330
331impl<II: InputIndicator> PartialEq for CircuitExpression<II> {
332 fn eq(&self, other: &Self) -> bool {
333 match (self, other) {
334 (Self::BConst(b), Self::BConst(b_o)) => b == b_o,
335 (Self::XConst(x), Self::XConst(x_o)) => x == x_o,
336 (Self::Input(i), Self::Input(i_o)) => i == i_o,
337 (Self::Challenge(c), Self::Challenge(c_o)) => c == c_o,
338 (Self::BinOp(op, l, r), Self::BinOp(op_o, l_o, r_o)) => {
339 op == op_o && l == l_o && r == r_o
340 }
341 _ => false,
342 }
343 }
344}
345
346impl<II: InputIndicator> Hash for ConstraintCircuit<II> {
347 fn hash<H: Hasher>(&self, state: &mut H) {
348 self.expression.hash(state)
349 }
350}
351
352impl<II: InputIndicator> Hash for ConstraintCircuitMonad<II> {
353 fn hash<H: Hasher>(&self, state: &mut H) {
354 self.circuit.borrow().hash(state)
355 }
356}
357
358#[derive(Debug, Clone)]
364pub struct ConstraintCircuit<II: InputIndicator> {
365 pub id: u64,
366 pub ref_count: usize,
367 pub expression: CircuitExpression<II>,
368}
369
370impl<II: InputIndicator> Eq for ConstraintCircuit<II> {}
371
372impl<II: InputIndicator> PartialEq for ConstraintCircuit<II> {
373 fn eq(&self, other: &Self) -> bool {
378 self.expression == other.expression
379 }
380}
381
382impl<II: InputIndicator> Display for ConstraintCircuit<II> {
383 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
384 match &self.expression {
385 CircuitExpression::XConst(xfe) => write!(f, "{xfe}"),
386 CircuitExpression::BConst(bfe) => write!(f, "{bfe}"),
387 CircuitExpression::Input(input) => write!(f, "{input} "),
388 CircuitExpression::Challenge(self_challenge_idx) => write!(f, "{self_challenge_idx}"),
389 CircuitExpression::BinOp(operation, lhs, rhs) => {
390 write!(f, "({}) {operation} ({})", lhs.borrow(), rhs.borrow())
391 }
392 }
393 }
394}
395
396#[derive(Debug, Clone)]
401struct ConstraintCircuitIter<II: InputIndicator> {
402 stack: Vec<RcCircuit<II>>,
403}
404
405impl<II: InputIndicator> Iterator for ConstraintCircuitIter<II> {
406 type Item = RcCircuit<II>;
407
408 fn next(&mut self) -> Option<Self::Item> {
409 let to_yield = self.stack.pop()?;
410 if let CircuitExpression::BinOp(_, _, ref rhs) = to_yield.borrow().expression {
411 self.push_left_lineage_to_stack(Rc::clone(rhs));
412 }
413
414 Some(to_yield)
415 }
416}
417
418impl<II: InputIndicator> ConstraintCircuitIter<II> {
419 fn new(root: ConstraintCircuit<II>) -> Self {
420 let mut iterator = Self { stack: Vec::new() };
421 iterator.push_left_lineage_to_stack(Rc::new(RefCell::new(root)));
422
423 iterator
424 }
425
426 fn push_left_lineage_to_stack(&mut self, current: RcCircuit<II>) {
429 let mut current = Some(current);
430 while let Some(new_node) = current.take() {
431 if let CircuitExpression::BinOp(_, ref lhs, _) = new_node.borrow().expression {
432 current = Some(Rc::clone(lhs));
433 }
434 self.stack.push(new_node);
435 }
436 }
437}
438
439impl<II: InputIndicator> ConstraintCircuit<II> {
440 fn new(id: u64, expression: CircuitExpression<II>) -> Self {
441 Self {
442 id,
443 ref_count: 0,
444 expression,
445 }
446 }
447
448 fn into_iter(self) -> ConstraintCircuitIter<II> {
450 ConstraintCircuitIter::new(self)
451 }
452
453 fn reset_ref_count_for_tree(&mut self) {
455 self.ref_count = 0;
456
457 if let CircuitExpression::BinOp(_, lhs, rhs) = &self.expression {
458 lhs.borrow_mut().reset_ref_count_for_tree();
459 rhs.borrow_mut().reset_ref_count_for_tree();
460 }
461 }
462
463 fn assert_unique_ids_inner(&mut self, ids: &mut HashMap<u64, ConstraintCircuit<II>>) {
469 if self.ref_count == 0 {
471 let id = self.id;
472 if let Some(other) = ids.insert(id, self.clone()) {
473 panic!("Repeated ID: {id}\nSelf:\n{self}\n{self:?}\nOther:\n{other}\n{other:?}");
474 }
475 }
476
477 self.ref_count += 1;
479 if let CircuitExpression::BinOp(_, lhs, rhs) = &self.expression {
480 lhs.borrow_mut().assert_unique_ids_inner(ids);
481 rhs.borrow_mut().assert_unique_ids_inner(ids);
482 }
483 }
484
485 pub fn assert_unique_ids(constraints: &mut [ConstraintCircuit<II>]) {
493 for circuit in constraints.iter_mut() {
496 circuit.reset_ref_count_for_tree();
497 }
498 let mut ids = HashMap::new();
499 for circuit in constraints.iter_mut() {
500 circuit.assert_unique_ids_inner(&mut ids);
501 }
502 }
503
504 pub fn degree(&self) -> isize {
506 if self.is_zero() {
507 return -1;
508 }
509
510 match &self.expression {
511 CircuitExpression::BinOp(binop, lhs, rhs) => {
512 let degree_lhs = lhs.borrow().degree();
513 let degree_rhs = rhs.borrow().degree();
514 let degree_additive = cmp::max(degree_lhs, degree_rhs);
515 let degree_multiplicative = if cmp::min(degree_lhs, degree_rhs) <= -1 {
516 -1
517 } else {
518 degree_lhs + degree_rhs
519 };
520 match binop {
521 BinOp::Add => degree_additive,
522 BinOp::Mul => degree_multiplicative,
523 }
524 }
525 CircuitExpression::Input(_) => 1,
526 CircuitExpression::BConst(_)
527 | CircuitExpression::XConst(_)
528 | CircuitExpression::Challenge(_) => 0,
529 }
530 }
531
532 pub fn all_ref_counters(&self) -> Vec<usize> {
534 let mut ref_counters = Vec::new();
535 self.all_ref_counters_inner(&mut ref_counters);
536 ref_counters.sort_unstable();
537 ref_counters.dedup();
538
539 ref_counters
540 }
541
542 fn all_ref_counters_inner(&self, ref_counters: &mut Vec<usize>) {
543 ref_counters.push(self.ref_count);
544 if let CircuitExpression::BinOp(_, lhs, rhs) = &self.expression {
545 lhs.borrow().all_ref_counters_inner(ref_counters);
546 rhs.borrow().all_ref_counters_inner(ref_counters);
547 };
548 }
549
550 pub fn is_zero(&self) -> bool {
554 match self.expression {
555 CircuitExpression::BConst(bfe) => bfe.is_zero(),
556 CircuitExpression::XConst(xfe) => xfe.is_zero(),
557 _ => false,
558 }
559 }
560
561 pub fn is_one(&self) -> bool {
565 match self.expression {
566 CircuitExpression::BConst(bfe) => bfe.is_one(),
567 CircuitExpression::XConst(xfe) => xfe.is_one(),
568 _ => false,
569 }
570 }
571
572 pub fn is_neg_one(&self) -> bool {
573 match self.expression {
574 CircuitExpression::BConst(bfe) => (-bfe).is_one(),
575 CircuitExpression::XConst(xfe) => (-xfe).is_one(),
576 _ => false,
577 }
578 }
579
580 pub fn evaluates_to_base_element(&self) -> bool {
586 match &self.expression {
587 CircuitExpression::BConst(_) => true,
588 CircuitExpression::XConst(_) => false,
589 CircuitExpression::Input(indicator) => indicator.is_main_table_column(),
590 CircuitExpression::Challenge(_) => false,
591 CircuitExpression::BinOp(_, lhs, rhs) => {
592 lhs.borrow().evaluates_to_base_element() && rhs.borrow().evaluates_to_base_element()
593 }
594 }
595 }
596
597 pub fn evaluate(
598 &self,
599 main_table: ArrayView2<BFieldElement>,
600 aux_table: ArrayView2<XFieldElement>,
601 challenges: &[XFieldElement],
602 ) -> XFieldElement {
603 match &self.expression {
604 CircuitExpression::BConst(bfe) => bfe.lift(),
605 CircuitExpression::XConst(xfe) => *xfe,
606 CircuitExpression::Input(input) => input.evaluate(main_table, aux_table),
607 CircuitExpression::Challenge(challenge_id) => challenges[*challenge_id],
608 CircuitExpression::BinOp(binop, lhs, rhs) => {
609 let lhs_value = lhs.borrow().evaluate(main_table, aux_table, challenges);
610 let rhs_value = rhs.borrow().evaluate(main_table, aux_table, challenges);
611 binop.operation(lhs_value, rhs_value)
612 }
613 }
614 }
615}
616
617#[derive(Clone)]
628pub struct ConstraintCircuitMonad<II: InputIndicator> {
629 pub circuit: RcCircuit<II>,
630 pub builder: ConstraintCircuitBuilder<II>,
631}
632
633impl<II: InputIndicator> Debug for ConstraintCircuitMonad<II> {
634 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
637 f.debug_struct("ConstraintCircuitMonad")
638 .field("id", &self.circuit)
639 .field("all_nodes length: ", &self.builder.all_nodes.borrow().len())
640 .field("id_counter_ref value: ", &self.builder.id_counter.borrow())
641 .finish()
642 }
643}
644
645impl<II: InputIndicator> Display for ConstraintCircuitMonad<II> {
646 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
647 write!(f, "{}", self.circuit.borrow())
648 }
649}
650
651impl<II: InputIndicator> PartialEq for ConstraintCircuitMonad<II> {
652 fn eq(&self, other: &Self) -> bool {
655 self.circuit == other.circuit
656 }
657}
658
659impl<II: InputIndicator> Eq for ConstraintCircuitMonad<II> {}
660
661fn binop<II: InputIndicator>(
667 binop: BinOp,
668 lhs: ConstraintCircuitMonad<II>,
669 rhs: ConstraintCircuitMonad<II>,
670) -> ConstraintCircuitMonad<II> {
671 assert!(lhs.builder.is_same_as(&rhs.builder));
672
673 match (binop, &lhs, &rhs) {
674 (BinOp::Add, _, zero) if zero.circuit.borrow().is_zero() => return lhs,
675 (BinOp::Add, zero, _) if zero.circuit.borrow().is_zero() => return rhs,
676 (BinOp::Mul, _, one) if one.circuit.borrow().is_one() => return lhs,
677 (BinOp::Mul, one, _) if one.circuit.borrow().is_one() => return rhs,
678 (BinOp::Mul, _, zero) if zero.circuit.borrow().is_zero() => return rhs,
679 (BinOp::Mul, zero, _) if zero.circuit.borrow().is_zero() => return lhs,
680 _ => (),
681 };
682
683 match (
684 &lhs.circuit.borrow().expression,
685 &rhs.circuit.borrow().expression,
686 ) {
687 (&CircuitExpression::BConst(l), &CircuitExpression::BConst(r)) => {
688 return lhs.builder.b_constant(binop.operation(l, r));
689 }
690 (&CircuitExpression::BConst(l), &CircuitExpression::XConst(r)) => {
691 return lhs.builder.x_constant(binop.operation(l, r));
692 }
693 (&CircuitExpression::XConst(l), &CircuitExpression::BConst(r)) => {
694 return lhs.builder.x_constant(binop.operation(l, r));
695 }
696 (&CircuitExpression::XConst(l), &CircuitExpression::XConst(r)) => {
697 return lhs.builder.x_constant(binop.operation(l, r));
698 }
699 _ => (),
700 };
701
702 let all_nodes = &mut lhs.builder.all_nodes.borrow_mut();
704 let new_node = binop_new_node(binop, &rhs, &lhs);
705 if let Some(node) = all_nodes.values().find(|&n| n == &new_node) {
706 return node.to_owned();
707 }
708
709 let new_node = binop_new_node(binop, &lhs, &rhs);
710 if let Some(node) = all_nodes.values().find(|&n| n == &new_node) {
711 return node.to_owned();
712 }
713
714 let new_id = new_node.circuit.borrow().id;
715 let old = all_nodes.insert(new_id, new_node.clone());
716 assert!(old.is_none(), "new node must not overwrite existing node");
717 *lhs.builder.id_counter.borrow_mut() += 1;
718
719 new_node
720}
721
722fn binop_new_node<II: InputIndicator>(
723 binop: BinOp,
724 lhs: &ConstraintCircuitMonad<II>,
725 rhs: &ConstraintCircuitMonad<II>,
726) -> ConstraintCircuitMonad<II> {
727 let id = lhs.builder.id_counter.borrow().to_owned();
728 let expression = CircuitExpression::BinOp(binop, lhs.circuit.clone(), rhs.circuit.clone());
729 let circuit = ConstraintCircuit::new(id, expression);
730
731 lhs.builder.new_monad(circuit)
732}
733
734impl<II: InputIndicator> Add for ConstraintCircuitMonad<II> {
735 type Output = ConstraintCircuitMonad<II>;
736
737 fn add(self, rhs: Self) -> Self::Output {
738 binop(BinOp::Add, self, rhs)
739 }
740}
741
742impl<II: InputIndicator> Sub for ConstraintCircuitMonad<II> {
743 type Output = ConstraintCircuitMonad<II>;
744
745 fn sub(self, rhs: Self) -> Self::Output {
746 binop(BinOp::Add, self, -rhs)
747 }
748}
749
750impl<II: InputIndicator> Mul for ConstraintCircuitMonad<II> {
751 type Output = ConstraintCircuitMonad<II>;
752
753 fn mul(self, rhs: Self) -> Self::Output {
754 binop(BinOp::Mul, self, rhs)
755 }
756}
757
758impl<II: InputIndicator> Neg for ConstraintCircuitMonad<II> {
759 type Output = ConstraintCircuitMonad<II>;
760
761 fn neg(self) -> Self::Output {
762 binop(BinOp::Mul, self.builder.minus_one(), self)
763 }
764}
765
766impl<II: InputIndicator> Sum for ConstraintCircuitMonad<II> {
769 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
770 iter.reduce(|accum, item| accum + item)
771 .expect("ConstraintCircuitMonad Iterator was empty")
772 }
773}
774
775struct EvolvingMainConstraintsNumber(usize);
776impl From<EvolvingMainConstraintsNumber> for usize {
777 fn from(value: EvolvingMainConstraintsNumber) -> Self {
778 value.0
779 }
780}
781
782struct EvolvingAuxConstraintsNumber(usize);
783impl From<EvolvingAuxConstraintsNumber> for usize {
784 fn from(value: EvolvingAuxConstraintsNumber) -> Self {
785 value.0
786 }
787}
788
789impl<II: InputIndicator> ConstraintCircuitMonad<II> {
790 pub fn consume(&self) -> ConstraintCircuit<II> {
792 self.circuit.borrow().to_owned()
793 }
794
795 pub fn lower_to_degree(
821 multicircuit: &mut [Self],
822 info: DegreeLoweringInfo,
823 ) -> (Vec<Self>, Vec<Self>) {
824 let target_degree = info.target_degree;
825 assert!(
826 target_degree > 1,
827 "Target degree must be greater than 1. Got {target_degree}."
828 );
829
830 let mut main_constraints = vec![];
831 let mut aux_constraints = vec![];
832
833 if multicircuit.is_empty() {
834 return (main_constraints, aux_constraints);
835 }
836
837 while Self::multicircuit_degree(multicircuit) > target_degree {
838 let chosen_node_id = Self::pick_node_to_substitute(multicircuit, target_degree);
839
840 let new_constraint = Self::apply_substitution(
841 multicircuit,
842 info,
843 chosen_node_id,
844 EvolvingMainConstraintsNumber(main_constraints.len()),
845 EvolvingAuxConstraintsNumber(aux_constraints.len()),
846 );
847
848 if new_constraint.circuit.borrow().evaluates_to_base_element() {
849 main_constraints.push(new_constraint)
850 } else {
851 aux_constraints.push(new_constraint)
852 }
853 }
854
855 (main_constraints, aux_constraints)
856 }
857
858 fn apply_substitution(
865 multicircuit: &mut [Self],
866 info: DegreeLoweringInfo,
867 chosen_node_id: u64,
868 new_main_constraints_count: EvolvingMainConstraintsNumber,
869 new_aux_constraints_count: EvolvingAuxConstraintsNumber,
870 ) -> ConstraintCircuitMonad<II> {
871 let builder = multicircuit[0].builder.clone();
872
873 let chosen_node = builder.all_nodes.borrow()[&chosen_node_id].clone();
875 let chosen_node_is_main_col = chosen_node.circuit.borrow().evaluates_to_base_element();
876 let new_input_indicator = if chosen_node_is_main_col {
877 let new_main_col_idx = info.num_main_cols + usize::from(new_main_constraints_count);
878 II::main_table_input(new_main_col_idx)
879 } else {
880 let new_aux_col_idx = info.num_aux_cols + usize::from(new_aux_constraints_count);
881 II::aux_table_input(new_aux_col_idx)
882 };
883 let new_variable = builder.input(new_input_indicator);
884
885 builder.redirect_all_references_to_node(chosen_node_id, new_variable.clone());
887
888 for circuit in multicircuit.iter_mut() {
890 if circuit.circuit.borrow().id == chosen_node_id {
891 circuit.circuit = new_variable.circuit.clone();
892 }
893 }
894
895 new_variable - chosen_node
897 }
898
899 fn pick_node_to_substitute(
903 multicircuit: &[ConstraintCircuitMonad<II>],
904 target_degree: isize,
905 ) -> u64 {
906 assert!(!multicircuit.is_empty());
907 let multicircuit = multicircuit
908 .iter()
909 .map(|c| c.clone().consume())
910 .collect_vec();
911
912 let node_degrees = Self::all_nodes_in_multicircuit(&multicircuit)
914 .map(|node| (node.borrow().id, node.borrow().degree()))
915 .collect::<HashMap<_, _>>();
916
917 let high_degree_nodes = Self::all_nodes_in_multicircuit(&multicircuit)
919 .filter(|node| node_degrees[&node.borrow().id] > target_degree)
920 .map(|node| node.borrow().clone())
921 .unique()
922 .collect_vec();
923
924 let low_degree_nodes = Self::all_nodes_in_multicircuit(&high_degree_nodes)
928 .map(|node| node.borrow().id)
929 .filter(|id| 1 < node_degrees[id] && node_degrees[id] <= target_degree)
930 .collect_vec();
931
932 assert!(!low_degree_nodes.is_empty(), "Cannot lower degree.");
934
935 let mut nodes_and_occurrences = HashMap::new();
937 for node in low_degree_nodes {
938 *nodes_and_occurrences.entry(node).or_insert(0) += 1;
939 }
940 let max_occurrences = nodes_and_occurrences.iter().map(|(_, &c)| c).max().unwrap();
941 nodes_and_occurrences.retain(|_, &mut count| count == max_occurrences);
942 let mut candidate_node_ids = nodes_and_occurrences.keys().copied().collect_vec();
943
944 let max_degree = candidate_node_ids
947 .iter()
948 .map(|node_id| node_degrees[node_id])
949 .max()
950 .unwrap();
951 candidate_node_ids.retain(|node_id| node_degrees[node_id] == max_degree);
952
953 candidate_node_ids.sort_unstable();
954
955 candidate_node_ids.into_iter().min().unwrap()
958 }
959
960 pub fn all_nodes_in_multicircuit(
969 multicircuit: &[ConstraintCircuit<II>],
970 ) -> impl Iterator<Item = RcCircuit<II>> + Clone {
971 multicircuit.iter().flat_map(|cc| cc.clone().into_iter())
972 }
973
974 pub fn num_visible_nodes(constraints: &[Self]) -> usize {
977 constraints
978 .iter()
979 .flat_map(|ccm| ccm.circuit.borrow().clone().into_iter())
980 .map(|circuit| circuit.borrow().to_owned())
981 .unique()
982 .count()
983 }
984
985 pub fn multicircuit_degree(multicircuit: &[ConstraintCircuitMonad<II>]) -> isize {
987 multicircuit
988 .iter()
989 .map(|circuit| circuit.circuit.borrow().degree())
990 .max()
991 .unwrap_or(-1)
992 }
993}
994
995#[derive(Debug, Clone, Eq, PartialEq)]
1000pub struct ConstraintCircuitBuilder<II: InputIndicator> {
1001 id_counter: Rc<RefCell<u64>>,
1002 all_nodes: Rc<RefCell<HashMap<u64, ConstraintCircuitMonad<II>>>>,
1003}
1004
1005impl<II: InputIndicator> Default for ConstraintCircuitBuilder<II> {
1006 fn default() -> Self {
1007 Self::new()
1008 }
1009}
1010
1011impl<II: InputIndicator> ConstraintCircuitBuilder<II> {
1012 pub fn new() -> Self {
1013 Self {
1014 id_counter: Rc::new(RefCell::new(0)),
1015 all_nodes: Rc::new(RefCell::new(HashMap::new())),
1016 }
1017 }
1018
1019 pub fn is_same_as(&self, other: &Self) -> bool {
1025 Rc::ptr_eq(&self.id_counter, &other.id_counter)
1026 && Rc::ptr_eq(&self.all_nodes, &other.all_nodes)
1027 }
1028
1029 fn new_monad(&self, circuit: ConstraintCircuit<II>) -> ConstraintCircuitMonad<II> {
1030 let circuit = Rc::new(RefCell::new(circuit));
1031 ConstraintCircuitMonad {
1032 circuit,
1033 builder: self.clone(),
1034 }
1035 }
1036
1037 pub fn zero(&self) -> ConstraintCircuitMonad<II> {
1039 self.b_constant(0)
1040 }
1041
1042 pub fn one(&self) -> ConstraintCircuitMonad<II> {
1044 self.b_constant(1)
1045 }
1046
1047 pub fn minus_one(&self) -> ConstraintCircuitMonad<II> {
1049 self.b_constant(-1)
1050 }
1051
1052 pub fn b_constant<B>(&self, bfe: B) -> ConstraintCircuitMonad<II>
1054 where
1055 B: Into<BFieldElement>,
1056 {
1057 self.make_leaf(CircuitExpression::BConst(bfe.into()))
1058 }
1059
1060 pub fn x_constant<X>(&self, xfe: X) -> ConstraintCircuitMonad<II>
1062 where
1063 X: Into<XFieldElement>,
1064 {
1065 self.make_leaf(CircuitExpression::XConst(xfe.into()))
1066 }
1067
1068 pub fn input(&self, input: II) -> ConstraintCircuitMonad<II> {
1070 self.make_leaf(CircuitExpression::Input(input))
1071 }
1072
1073 pub fn challenge<C>(&self, challenge: C) -> ConstraintCircuitMonad<II>
1075 where
1076 C: Into<usize>,
1077 {
1078 self.make_leaf(CircuitExpression::Challenge(challenge.into()))
1079 }
1080
1081 fn make_leaf(&self, mut expression: CircuitExpression<II>) -> ConstraintCircuitMonad<II> {
1082 assert!(
1083 !matches!(expression, CircuitExpression::BinOp(_, _, _)),
1084 "`make_leaf` is intended for anything but `BinOp`s"
1085 );
1086
1087 if let CircuitExpression::XConst(xfe) = expression
1089 && let Some(bfe) = xfe.unlift()
1090 {
1091 expression = CircuitExpression::BConst(bfe);
1092 }
1093
1094 let id = self.id_counter.borrow().to_owned();
1095 let circuit = ConstraintCircuit::new(id, expression);
1096 let new_node = self.new_monad(circuit);
1097
1098 if let Some(same_node) = self.all_nodes.borrow().values().find(|&n| n == &new_node) {
1099 return same_node.to_owned();
1100 }
1101
1102 let old = self.all_nodes.borrow_mut().insert(id, new_node.clone());
1103 assert!(old.is_none(), "Leaf-created node must be new… {new_node}");
1104 *self.id_counter.borrow_mut() += 1;
1105
1106 new_node
1107 }
1108
1109 fn redirect_all_references_to_node(&self, old_id: u64, new: ConstraintCircuitMonad<II>) {
1115 self.all_nodes.borrow_mut().remove(&old_id);
1116 for node in self.all_nodes.borrow_mut().values_mut() {
1117 let CircuitExpression::BinOp(_, ref mut left, ref mut right) =
1118 node.circuit.borrow_mut().expression
1119 else {
1120 continue;
1121 };
1122 if left.borrow().id == old_id {
1123 *left = new.circuit.clone();
1124 }
1125 if right.borrow().id == old_id {
1126 *right = new.circuit.clone();
1127 }
1128 }
1129 }
1130}
1131
1132impl<'a, II: InputIndicator + Arbitrary<'a>> Arbitrary<'a> for ConstraintCircuitMonad<II> {
1133 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
1134 let builder = ConstraintCircuitBuilder::new();
1135 let mut random_circuit = random_circuit_leaf(&builder, u)?;
1136
1137 let num_nodes_in_circuit = u.arbitrary_len::<Self>()?;
1138 for _ in 0..num_nodes_in_circuit {
1139 let leaf = random_circuit_leaf(&builder, u)?;
1140 match u.int_in_range(0..=5)? {
1141 0 => random_circuit = random_circuit * leaf,
1142 1 => random_circuit = random_circuit + leaf,
1143 2 => random_circuit = random_circuit - leaf,
1144 3 => random_circuit = leaf * random_circuit,
1145 4 => random_circuit = leaf + random_circuit,
1146 5 => random_circuit = leaf - random_circuit,
1147 _ => unreachable!(),
1148 }
1149 }
1150
1151 Ok(random_circuit)
1152 }
1153}
1154
1155fn random_circuit_leaf<'a, II: InputIndicator + Arbitrary<'a>>(
1156 builder: &ConstraintCircuitBuilder<II>,
1157 u: &mut Unstructured<'a>,
1158) -> arbitrary::Result<ConstraintCircuitMonad<II>> {
1159 let leaf = match u.int_in_range(0..=5)? {
1160 0 => builder.input(u.arbitrary()?),
1161 1 => builder.challenge(u.arbitrary::<usize>()?),
1162 2 => builder.b_constant(u.arbitrary::<BFieldElement>()?),
1163 3 => builder.x_constant(u.arbitrary::<XFieldElement>()?),
1164 4 => builder.one(),
1165 5 => builder.zero(),
1166 _ => unreachable!(),
1167 };
1168 Ok(leaf)
1169}
1170
1171#[cfg(test)]
1172#[cfg_attr(coverage_nightly, coverage(off))]
1173mod tests {
1174 use std::collections::hash_map::DefaultHasher;
1175 use std::hash::Hasher;
1176 use std::ops::Not;
1177
1178 use itertools::Itertools;
1179 use ndarray::Array2;
1180 use ndarray::Axis;
1181 use proptest::arbitrary::Arbitrary;
1182 use proptest::collection::vec;
1183 use proptest::prelude::*;
1184 use proptest_arbitrary_adapter::arb;
1185 use test_strategy::proptest;
1186
1187 use super::*;
1188
1189 impl<II: InputIndicator> ConstraintCircuitMonad<II> {
1190 fn contains(&self, other: &Self) -> bool {
1192 let self_expression = &self.circuit.borrow().expression;
1193 let other_expression = &other.circuit.borrow().expression;
1194 self_expression.contains(other_expression)
1195 }
1196
1197 fn iter_nodes(
1202 constraints: &[Self],
1203 ) -> std::vec::IntoIter<(u64, ConstraintCircuitMonad<II>)> {
1204 let Some(first) = constraints.first() else {
1205 return vec![].into_iter();
1206 };
1207
1208 first
1209 .builder
1210 .all_nodes
1211 .borrow()
1212 .iter()
1213 .map(|(n, m)| (*n, m.clone()))
1214 .collect_vec()
1215 .into_iter()
1216 }
1217
1218 fn num_nodes(constraints: &[Self]) -> usize {
1220 Self::iter_nodes(constraints).count()
1221 }
1222
1223 fn is_main_table_column(&self) -> bool {
1226 let CircuitExpression::Input(ii) = self.circuit.borrow().expression else {
1227 return false;
1228 };
1229
1230 ii.is_main_table_column()
1231 }
1232
1233 fn num_main_inputs(constraints: &[Self]) -> usize {
1235 Self::iter_nodes(constraints)
1236 .filter(|(_, cc)| cc.is_main_table_column())
1237 .filter(|(_, cc)| cc.circuit.borrow().evaluates_to_base_element())
1238 .count()
1239 }
1240
1241 fn num_aux_inputs(constraints: &[Self]) -> usize {
1243 Self::iter_nodes(constraints)
1244 .filter(|(_, cc)| !cc.is_main_table_column())
1245 .filter(|(_, cc)| {
1246 matches!(cc.circuit.borrow().expression, CircuitExpression::Input(_))
1247 })
1248 .count()
1249 }
1250
1251 fn num_inputs(constraints: &[Self]) -> usize {
1253 Self::num_main_inputs(constraints) + Self::num_aux_inputs(constraints)
1254 }
1255
1256 fn num_challenges(constraints: &[Self]) -> usize {
1258 Self::iter_nodes(constraints)
1259 .filter(|(_, cc)| {
1260 matches!(
1261 cc.circuit.borrow().expression,
1262 CircuitExpression::Challenge(_)
1263 )
1264 })
1265 .count()
1266 }
1267
1268 fn num_binops(constraints: &[Self]) -> usize {
1270 Self::iter_nodes(constraints)
1271 .filter(|(_, cc)| {
1272 matches!(
1273 cc.circuit.borrow().expression,
1274 CircuitExpression::BinOp(_, _, _)
1275 )
1276 })
1277 .count()
1278 }
1279
1280 fn num_bfield_constants(constraints: &[Self]) -> usize {
1282 Self::iter_nodes(constraints)
1283 .filter(|(_, cc)| {
1284 matches!(cc.circuit.borrow().expression, CircuitExpression::BConst(_))
1285 })
1286 .count()
1287 }
1288
1289 fn num_xfield_constants(constraints: &[Self]) -> usize {
1291 Self::iter_nodes(constraints)
1292 .filter(|(_, cc)| {
1293 matches!(
1294 cc.circuit.as_ref().borrow().expression,
1295 CircuitExpression::XConst(_)
1296 )
1297 })
1298 .count()
1299 }
1300 }
1301
1302 impl<II: InputIndicator> CircuitExpression<II> {
1303 fn contains(&self, other: &Self) -> bool {
1305 if self == other {
1306 return true;
1307 }
1308 let CircuitExpression::BinOp(_, lhs, rhs) = self else {
1309 return false;
1310 };
1311
1312 lhs.borrow().expression.contains(other) || rhs.borrow().expression.contains(other)
1313 }
1314 }
1315
1316 #[proptest]
1322 fn unequal_hash_implies_unequal_constraint_circuit_monad(
1323 #[strategy(arb())] circuit_0: ConstraintCircuitMonad<SingleRowIndicator>,
1324 #[strategy(arb())] circuit_1: ConstraintCircuitMonad<SingleRowIndicator>,
1325 ) {
1326 if hash_circuit(&circuit_0) != hash_circuit(&circuit_1) {
1327 prop_assert_ne!(circuit_0, circuit_1);
1328 }
1329 }
1330
1331 #[proptest]
1336 fn multi_circuit_hash_is_unchanged_by_meta_data(
1337 #[strategy(arb())] circuit: ConstraintCircuitMonad<DualRowIndicator>,
1338 new_ref_count: usize,
1339 new_id_counter: u64,
1340 ) {
1341 let original_digest = hash_circuit(&circuit);
1342
1343 circuit.circuit.borrow_mut().ref_count = new_ref_count;
1344 prop_assert_eq!(original_digest, hash_circuit(&circuit));
1345
1346 circuit.builder.id_counter.replace(new_id_counter);
1347 prop_assert_eq!(original_digest, hash_circuit(&circuit));
1348 }
1349
1350 fn hash_circuit<II: InputIndicator>(circuit: &ConstraintCircuitMonad<II>) -> u64 {
1351 let mut hasher = DefaultHasher::new();
1352 circuit.hash(&mut hasher);
1353 hasher.finish()
1354 }
1355
1356 #[test]
1357 fn printing_constraint_circuit_gives_expected_strings() {
1358 let builder = ConstraintCircuitBuilder::new();
1359 assert_eq!("1", builder.b_constant(1).to_string());
1360 assert_eq!(
1361 "main_row[5] ",
1362 builder.input(SingleRowIndicator::Main(5)).to_string()
1363 );
1364 assert_eq!("6", builder.challenge(6_usize).to_string());
1365
1366 let xfe_str = builder.x_constant([2, 3, 4]).to_string();
1367 assert_eq!("(4·x² + 3·x + 2)", xfe_str);
1368 }
1369
1370 #[proptest]
1371 fn constant_folding_can_deal_with_multiplication_by_one(
1372 #[strategy(arb())] c: ConstraintCircuitMonad<DualRowIndicator>,
1373 ) {
1374 let one = || c.builder.one();
1375 prop_assert_eq!(c.clone(), c.clone() * one());
1376 prop_assert_eq!(c.clone(), one() * c.clone());
1377 prop_assert_eq!(c.clone(), one() * c.clone() * one());
1378 }
1379
1380 #[proptest]
1381 fn constant_folding_can_deal_with_adding_zero(
1382 #[strategy(arb())] c: ConstraintCircuitMonad<DualRowIndicator>,
1383 ) {
1384 let zero = || c.builder.zero();
1385 prop_assert_eq!(c.clone(), c.clone() + zero());
1386 prop_assert_eq!(c.clone(), zero() + c.clone());
1387 prop_assert_eq!(c.clone(), zero() + c.clone() + zero());
1388 }
1389
1390 #[proptest]
1391 fn constant_folding_can_deal_with_subtracting_zero(
1392 #[strategy(arb())] c: ConstraintCircuitMonad<DualRowIndicator>,
1393 ) {
1394 prop_assert_eq!(c.clone(), c.clone() - c.builder.zero());
1395 }
1396
1397 #[proptest]
1398 fn constant_folding_can_deal_with_adding_effectively_zero_term(
1399 #[strategy(arb())] c: ConstraintCircuitMonad<DualRowIndicator>,
1400 modification_selectors: [bool; 4],
1401 ) {
1402 let zero = || c.builder.zero();
1403 let mut redundant_circuit = c.clone();
1404 if modification_selectors[0] {
1405 redundant_circuit = redundant_circuit + (c.clone() * zero());
1406 }
1407 if modification_selectors[1] {
1408 redundant_circuit = redundant_circuit + (zero() * c.clone());
1409 }
1410 if modification_selectors[2] {
1411 redundant_circuit = (c.clone() * zero()) + redundant_circuit;
1412 }
1413 if modification_selectors[3] {
1414 redundant_circuit = (zero() * c.clone()) + redundant_circuit;
1415 }
1416 prop_assert_eq!(c, redundant_circuit);
1417 }
1418
1419 #[proptest]
1420 fn constant_folding_does_not_replace_0_minus_circuit_with_the_circuit(
1421 #[strategy(arb())] circuit: ConstraintCircuitMonad<DualRowIndicator>,
1422 ) {
1423 if circuit.circuit.borrow().is_zero() {
1424 return Err(TestCaseError::Reject("0 - 0 actually is 0".into()));
1425 }
1426 let zero_minus_circuit = circuit.builder.zero() - circuit.clone();
1427 prop_assert_ne!(&circuit, &zero_minus_circuit);
1428 }
1429
1430 #[test]
1431 fn pointer_redirection_obliviates_a_node_in_a_circuit() {
1432 let builder = ConstraintCircuitBuilder::new();
1433 let x = |i| builder.input(SingleRowIndicator::Main(i));
1434 let constant = |c: u32| builder.b_constant(c);
1435 let challenge = |i: usize| builder.challenge(i);
1436
1437 let part = x(0) + x(1);
1438 let substitute_me = x(0) * part.clone();
1439 let root_0 = part.clone() + challenge(1) - constant(84);
1440 let root_1 = substitute_me.clone() + challenge(0) - constant(42);
1441 let root_2 = x(2) * substitute_me.clone() - challenge(1);
1442
1443 assert!(!root_0.contains(&substitute_me));
1444 assert!(root_1.contains(&substitute_me));
1445 assert!(root_2.contains(&substitute_me));
1446
1447 let new_variable = x(3);
1448 builder.redirect_all_references_to_node(
1449 substitute_me.circuit.borrow().id,
1450 new_variable.clone(),
1451 );
1452
1453 assert!(!root_0.contains(&substitute_me));
1454 assert!(!root_1.contains(&substitute_me));
1455 assert!(!root_2.contains(&substitute_me));
1456
1457 assert!(root_0.contains(&part));
1458 assert!(root_1.contains(&new_variable));
1459 assert!(root_2.contains(&new_variable));
1460 }
1461
1462 #[test]
1463 fn simple_degree_lowering() {
1464 let builder = ConstraintCircuitBuilder::new();
1465 let x = || builder.input(SingleRowIndicator::Main(0));
1466 let x_pow_3 = x() * x() * x();
1467 let x_pow_5 = x() * x() * x() * x() * x();
1468 let mut multicircuit = [x_pow_5, x_pow_3];
1469
1470 let degree_lowering_info = DegreeLoweringInfo {
1471 target_degree: 3,
1472 num_main_cols: 1,
1473 num_aux_cols: 0,
1474 };
1475 let (new_main_constraints, new_aux_constraints) =
1476 ConstraintCircuitMonad::lower_to_degree(&mut multicircuit, degree_lowering_info);
1477
1478 assert_eq!(1, new_main_constraints.len());
1479 assert!(new_aux_constraints.is_empty());
1480 }
1481
1482 #[test]
1483 fn somewhat_simple_degree_lowering() {
1484 let builder = ConstraintCircuitBuilder::new();
1485 let x = |i| builder.input(SingleRowIndicator::Main(i));
1486 let y = |i| builder.input(SingleRowIndicator::Aux(i));
1487 let b_con = |i: u64| builder.b_constant(i);
1488
1489 let constraint_0 = x(0) * x(0) * (x(1) - x(2)) - x(0) * x(2) - b_con(42);
1490 let constraint_1 = x(1) * (x(1) - b_con(5)) * x(2) * (x(2) - b_con(1));
1491 let constraint_2 = y(0)
1492 * (b_con(2) * x(0) + b_con(3) * x(1) + b_con(4) * x(2))
1493 * (b_con(4) * x(0) + b_con(8) * x(1) + b_con(16) * x(2))
1494 - y(1);
1495
1496 let mut multicircuit = [constraint_0, constraint_1, constraint_2];
1497
1498 let degree_lowering_info = DegreeLoweringInfo {
1499 target_degree: 2,
1500 num_main_cols: 3,
1501 num_aux_cols: 2,
1502 };
1503 let (new_main_constraints, new_aux_constraints) =
1504 ConstraintCircuitMonad::lower_to_degree(&mut multicircuit, degree_lowering_info);
1505
1506 assert!(new_main_constraints.len() <= 3);
1507 assert!(new_aux_constraints.len() <= 1);
1508 }
1509
1510 #[test]
1511 fn less_simple_degree_lowering() {
1512 let builder = ConstraintCircuitBuilder::new();
1513 let x = |i| builder.input(SingleRowIndicator::Main(i));
1514
1515 let constraint_0 = (x(0) * x(1) * x(2)) * (x(3) * x(4)) * x(5);
1516 let constraint_1 = (x(6) * x(7)) * (x(3) * x(4)) * x(8);
1517
1518 let mut multicircuit = [constraint_0, constraint_1];
1519
1520 let degree_lowering_info = DegreeLoweringInfo {
1521 target_degree: 3,
1522 num_main_cols: 9,
1523 num_aux_cols: 0,
1524 };
1525 let (new_main_constraints, new_aux_constraints) =
1526 ConstraintCircuitMonad::lower_to_degree(&mut multicircuit, degree_lowering_info);
1527
1528 assert!(new_main_constraints.len() <= 3);
1529 assert!(new_aux_constraints.is_empty());
1530 }
1531
1532 fn circuit_with_multiple_options_for_degree_lowering_to_degree_2()
1542 -> [ConstraintCircuitMonad<SingleRowIndicator>; 2] {
1543 let builder = ConstraintCircuitBuilder::new();
1544 let x = |i| builder.input(SingleRowIndicator::Main(i));
1545
1546 let constraint_0 = x(0) * x(0) * x(0);
1547 let constraint_1 = x(1) * x(1) * x(1);
1548
1549 [constraint_0, constraint_1]
1550 }
1551
1552 #[test]
1553 fn pick_node_to_substitute_is_deterministic() {
1554 let multicircuit = circuit_with_multiple_options_for_degree_lowering_to_degree_2();
1555 let first_node_id = ConstraintCircuitMonad::pick_node_to_substitute(&multicircuit, 2);
1556
1557 for _ in 0..20 {
1558 let node_id_again = ConstraintCircuitMonad::pick_node_to_substitute(&multicircuit, 2);
1559 assert_eq!(first_node_id, node_id_again);
1560 }
1561 }
1562
1563 #[test]
1564 fn degree_lowering_specific_simple_circuit_is_deterministic() {
1565 let degree_lowering_info = DegreeLoweringInfo {
1566 target_degree: 2,
1567 num_main_cols: 2,
1568 num_aux_cols: 0,
1569 };
1570
1571 let mut original_multicircuit =
1572 circuit_with_multiple_options_for_degree_lowering_to_degree_2();
1573 let (new_main_constraints, _) = ConstraintCircuitMonad::lower_to_degree(
1574 &mut original_multicircuit,
1575 degree_lowering_info,
1576 );
1577
1578 for _ in 0..20 {
1579 let mut new_multicircuit =
1580 circuit_with_multiple_options_for_degree_lowering_to_degree_2();
1581 let (new_main_constraints_again, _) = ConstraintCircuitMonad::lower_to_degree(
1582 &mut new_multicircuit,
1583 degree_lowering_info,
1584 );
1585 assert_eq!(new_main_constraints, new_main_constraints_again);
1586 assert_eq!(original_multicircuit, new_multicircuit);
1587 }
1588 }
1589
1590 #[test]
1591 fn all_nodes_in_multicircuit_are_identified_correctly() {
1592 let builder = ConstraintCircuitBuilder::new();
1593
1594 let x = |i| builder.input(SingleRowIndicator::Main(i));
1595 let b_con = |i: u64| builder.b_constant(i);
1596
1597 let sub_tree_0 = x(0) * x(1) * (x(2) - b_con(1)) * x(3) * x(4);
1598 let sub_tree_1 = x(0) * x(1) * (x(2) - b_con(1)) * x(3) * x(5);
1599 let sub_tree_2 = x(10) * x(10) * x(2) * x(13);
1600 let sub_tree_3 = x(10) * x(10) * x(2) * x(14);
1601
1602 let circuit_0 = sub_tree_0.clone() + sub_tree_1.clone();
1603 let circuit_1 = sub_tree_2.clone() + sub_tree_3.clone();
1604 let circuit_2 = sub_tree_0 + sub_tree_2;
1605 let circuit_3 = sub_tree_1 + sub_tree_3;
1606
1607 let multicircuit = [circuit_0, circuit_1, circuit_2, circuit_3].map(|c| c.consume());
1608
1609 let all_nodes = ConstraintCircuitMonad::all_nodes_in_multicircuit(&multicircuit);
1610 let count_node = |node| all_nodes.clone().filter(|n| n.borrow().eq(&node)).count();
1611
1612 let x0 = x(0).consume();
1613 assert_eq!(4, count_node(x0));
1614
1615 let x2 = x(2).consume();
1616 assert_eq!(8, count_node(x2));
1617
1618 let x10 = x(10).consume();
1619 assert_eq!(8, count_node(x10));
1620
1621 let x4 = x(4).consume();
1622 assert_eq!(2, count_node(x4));
1623
1624 let x6 = x(6).consume();
1625 assert_eq!(0, count_node(x6));
1626
1627 let x0_x1 = (x(0) * x(1)).consume();
1628 assert_eq!(4, count_node(x0_x1));
1629
1630 let tree = (x(0) * x(1) * (x(2) - b_con(1))).consume();
1631 assert_eq!(4, count_node(tree));
1632
1633 let max_occurrences = all_nodes
1634 .clone()
1635 .map(|node| all_nodes.clone().filter(|n| n == &node).count())
1636 .max()
1637 .unwrap();
1638 assert_eq!(8, max_occurrences);
1639
1640 let most_frequent_nodes = all_nodes
1641 .clone()
1642 .filter(|node| all_nodes.clone().filter(|n| n == node).count() == max_occurrences)
1643 .map(|node| node.borrow().clone())
1644 .unique()
1645 .collect_vec();
1646 assert_eq!(2, most_frequent_nodes.len());
1647 assert!(most_frequent_nodes.contains(&x(2).consume()));
1648 assert!(most_frequent_nodes.contains(&x(10).consume()));
1649 }
1650
1651 #[test]
1652 fn visible_nodes_are_counted_correctly() {
1653 let builder = ConstraintCircuitBuilder::new();
1654
1655 let x = |i| builder.input(SingleRowIndicator::Main(i));
1656 let b_con = |i: u64| builder.b_constant(i);
1657
1658 let tree_of_constants = b_con(42) * b_con(7) + b_con(13) - b_con(12);
1660
1661 let circuit_1 = (x(0) - x(1)) * (x(1) + x(2));
1668
1669 let _circuit_2 = x(1) * x(3) + (x(4) - x(5)) * x(6);
1671
1672 let circuit_3 = (x(2) + x(1)) * tree_of_constants.clone();
1677
1678 let multicircuit = [tree_of_constants, circuit_1, circuit_3];
1679 let num_visible = ConstraintCircuitMonad::num_visible_nodes(&multicircuit);
1680 assert_eq!(10, num_visible);
1681 }
1682
1683 #[derive(Debug, Copy, Clone, Eq, PartialEq, test_strategy::Arbitrary)]
1684 enum CircuitOperationChoice {
1685 Add(usize, usize),
1686 Mul(usize, usize),
1687 }
1688
1689 #[derive(Debug, Copy, Clone, Eq, PartialEq, test_strategy::Arbitrary)]
1690 enum CircuitInputType {
1691 Main,
1692 Aux,
1693 }
1694
1695 #[derive(Debug, Copy, Clone, Eq, PartialEq, test_strategy::Arbitrary)]
1696 enum CircuitConstantType {
1697 Base(#[strategy(arb())] BFieldElement),
1698 Extension(#[strategy(arb())] XFieldElement),
1699 }
1700
1701 fn arbitrary_circuit_monad<II: InputIndicator>(
1702 num_inputs: usize,
1703 num_challenges: usize,
1704 num_constants: usize,
1705 num_operations: usize,
1706 num_outputs: usize,
1707 ) -> BoxedStrategy<Vec<ConstraintCircuitMonad<II>>> {
1708 (
1709 vec(CircuitInputType::arbitrary(), num_inputs),
1710 vec(CircuitConstantType::arbitrary(), num_constants),
1711 vec(CircuitOperationChoice::arbitrary(), num_operations),
1712 vec(arb::<usize>(), num_outputs),
1713 )
1714 .prop_map(move |(inputs, constants, operations, outputs)| {
1715 let builder = ConstraintCircuitBuilder::<II>::new();
1716
1717 assert_eq!(0, *builder.id_counter.borrow());
1718 assert!(
1719 builder.all_nodes.borrow().is_empty(),
1720 "fresh hashmap should be empty"
1721 );
1722
1723 let mut num_main_inputs = 0;
1724 let mut num_aux_inputs = 0;
1725 let mut all_nodes = vec![];
1726 let mut output_nodes = vec![];
1727
1728 for input in inputs {
1729 match input {
1730 CircuitInputType::Main => {
1731 let node = builder.input(II::main_table_input(num_main_inputs));
1732 num_main_inputs += 1;
1733 all_nodes.push(node);
1734 }
1735 CircuitInputType::Aux => {
1736 let node = builder.input(II::aux_table_input(num_aux_inputs));
1737 num_aux_inputs += 1;
1738 all_nodes.push(node);
1739 }
1740 }
1741 }
1742
1743 for i in 0..num_challenges {
1744 let node = builder.challenge(i);
1745 all_nodes.push(node);
1746 }
1747
1748 for constant in constants {
1749 let node = match constant {
1750 CircuitConstantType::Base(bfe) => builder.b_constant(bfe),
1751 CircuitConstantType::Extension(xfe) => builder.x_constant(xfe),
1752 };
1753 all_nodes.push(node);
1754 }
1755
1756 if all_nodes.is_empty() {
1757 return vec![];
1758 }
1759
1760 for operation in operations {
1761 let (lhs, rhs) = match operation {
1762 CircuitOperationChoice::Add(lhs, rhs) => (lhs, rhs),
1763 CircuitOperationChoice::Mul(lhs, rhs) => (lhs, rhs),
1764 };
1765
1766 let lhs_index = lhs % all_nodes.len();
1767 let rhs_index = rhs % all_nodes.len();
1768
1769 let lhs_node = all_nodes[lhs_index].clone();
1770 let rhs_node = all_nodes[rhs_index].clone();
1771
1772 let node = match operation {
1773 CircuitOperationChoice::Add(_, _) => lhs_node + rhs_node,
1774 CircuitOperationChoice::Mul(_, _) => lhs_node * rhs_node,
1775 };
1776 all_nodes.push(node);
1777 }
1778
1779 for output in outputs {
1780 let index = output % all_nodes.len();
1781 output_nodes.push(all_nodes[index].clone());
1782 }
1783
1784 output_nodes
1785 })
1786 .boxed()
1787 }
1788
1789 #[proptest]
1790 fn node_type_counts_add_up(
1791 #[strategy(arbitrary_circuit_monad(10, 10, 10, 60, 10))] multicircuit_monad: Vec<
1792 ConstraintCircuitMonad<SingleRowIndicator>,
1793 >,
1794 ) {
1795 prop_assert_eq!(
1796 ConstraintCircuitMonad::num_nodes(&multicircuit_monad),
1797 ConstraintCircuitMonad::num_main_inputs(&multicircuit_monad)
1798 + ConstraintCircuitMonad::num_aux_inputs(&multicircuit_monad)
1799 + ConstraintCircuitMonad::num_challenges(&multicircuit_monad)
1800 + ConstraintCircuitMonad::num_bfield_constants(&multicircuit_monad)
1801 + ConstraintCircuitMonad::num_xfield_constants(&multicircuit_monad)
1802 + ConstraintCircuitMonad::num_binops(&multicircuit_monad)
1803 );
1804
1805 prop_assert_eq!(10, ConstraintCircuitMonad::num_inputs(&multicircuit_monad));
1806 }
1807
1808 #[proptest(cases = 1000, max_shrink_iters = 0)]
1871 fn node_substitution_is_complete_and_sound(
1872 #[strategy(arbitrary_circuit_monad(10, 10, 10, 160, 10))] mut multicircuit_monad: Vec<
1873 ConstraintCircuitMonad<SingleRowIndicator>,
1874 >,
1875 #[strategy(vec(arb(), ConstraintCircuitMonad::num_main_inputs(&#multicircuit_monad)))]
1876 #[filter(!#main_input.is_empty())]
1877 main_input: Vec<BFieldElement>,
1878 #[strategy(vec(arb(), ConstraintCircuitMonad::num_aux_inputs(&#multicircuit_monad)))]
1879 #[filter(!#aux_input.is_empty())]
1880 aux_input: Vec<XFieldElement>,
1881 #[strategy(vec(arb(), ConstraintCircuitMonad::num_challenges(&#multicircuit_monad)))]
1882 challenges: Vec<XFieldElement>,
1883 #[strategy(arb())] substitution_node_index: usize,
1884 ) {
1885 let mut main_input = Array2::from_shape_vec((1, main_input.len()), main_input).unwrap();
1886 let mut aux_input = Array2::from_shape_vec((1, aux_input.len()), aux_input).unwrap();
1887
1888 let output_before_lowering = multicircuit_monad
1889 .iter()
1890 .map(|m| m.circuit.borrow())
1891 .map(|c| c.evaluate(main_input.view(), aux_input.view(), &challenges))
1892 .collect_vec();
1893
1894 let num_nodes = ConstraintCircuitMonad::num_nodes(&multicircuit_monad);
1896 let &substitution_node_id = multicircuit_monad[0]
1897 .builder
1898 .all_nodes
1899 .borrow()
1900 .iter()
1901 .cycle()
1902 .skip(substitution_node_index % num_nodes)
1903 .take(num_nodes)
1904 .find_map(|(id, monad)| monad.circuit.borrow().is_zero().not().then_some(id))
1905 .expect("no suitable nodes to substitute");
1906
1907 let degree_lowering_info = DegreeLoweringInfo {
1908 target_degree: 2,
1909 num_main_cols: main_input.len(),
1910 num_aux_cols: aux_input.len(),
1911 };
1912 let substitution_constraint = ConstraintCircuitMonad::apply_substitution(
1913 &mut multicircuit_monad,
1914 degree_lowering_info,
1915 substitution_node_id,
1916 EvolvingMainConstraintsNumber(0),
1917 EvolvingAuxConstraintsNumber(0),
1918 );
1919
1920 let CircuitExpression::BinOp(BinOp::Add, variable, neg_expression) =
1922 &substitution_constraint.circuit.borrow().expression
1923 else {
1924 panic!();
1925 };
1926 let extra_input =
1927 match &neg_expression.borrow().expression {
1928 CircuitExpression::BinOp(BinOp::Mul, _neg_one, circuit) => circuit
1929 .borrow()
1930 .evaluate(main_input.view(), aux_input.view(), &challenges),
1931 CircuitExpression::BConst(c) => -c.lift(),
1932 CircuitExpression::XConst(c) => -*c,
1933 _ => panic!(),
1934 };
1935 if variable.borrow().evaluates_to_base_element() {
1936 let extra_input = extra_input.unlift().unwrap();
1937 let extra_input = Array2::from_shape_vec([1, 1], vec![extra_input]).unwrap();
1938 main_input.append(Axis(1), extra_input.view()).unwrap();
1939 } else {
1940 let extra_input = Array2::from_shape_vec([1, 1], vec![extra_input]).unwrap();
1941 aux_input.append(Axis(1), extra_input.view()).unwrap();
1942 }
1943
1944 let output_after_lowering = multicircuit_monad
1946 .iter()
1947 .map(|m| m.circuit.borrow())
1948 .map(|c| c.evaluate(main_input.view(), aux_input.view(), &challenges))
1949 .collect_vec();
1950 prop_assert_eq!(output_before_lowering, output_after_lowering);
1951
1952 let evaluated_constraint = substitution_constraint.circuit.borrow().evaluate(
1953 main_input.view(),
1954 aux_input.view(),
1955 &challenges,
1956 );
1957 prop_assert!(evaluated_constraint.is_zero());
1958 }
1959}