• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TritonVM / triton-vm / 10825170249

12 Sep 2024 06:08AM UTC coverage: 98.369%. Remained the same
10825170249

push

github

jan-ferdinand
chore: Use “main” & “aux over “base” & “ext”

This completes the transition started in 07fdf159.

changelog: ignore

546 of 558 new or added lines in 17 files covered. (97.85%)

30 existing lines in 4 files now uncovered.

23038 of 23420 relevant lines covered (98.37%)

7954849.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.06
/triton-constraint-circuit/src/lib.rs
1
//! Constraint circuits are a way to represent constraint polynomials in a way
2
//! that is amenable to optimizations. The constraint circuit is a directed
3
//! acyclic graph (DAG) of [`CircuitExpression`]s, where each
4
//! `CircuitExpression` is a node in the graph. The edges of the graph are
5
//! labeled with [`BinOp`]s. The leafs of the graph are the inputs to the
6
//! constraint polynomial, and the (multiple) roots of the graph are the outputs
7
//! of all the constraint polynomials, with each root corresponding to a
8
//! different constraint polynomial. Because the graph has multiple roots, it is
9
//! called a “multitree.”
10

11
use std::cell::RefCell;
12
use std::cmp;
13
use std::collections::HashMap;
14
use std::fmt::Debug;
15
use std::fmt::Display;
16
use std::fmt::Formatter;
17
use std::fmt::Result as FmtResult;
18
use std::hash::Hash;
19
use std::hash::Hasher;
20
use std::iter::Sum;
21
use std::ops::Add;
22
use std::ops::AddAssign;
23
use std::ops::Mul;
24
use std::ops::Neg;
25
use std::ops::Sub;
26
use std::rc::Rc;
27

28
use arbitrary::Arbitrary;
29
use arbitrary::Unstructured;
30
use itertools::Itertools;
31
use ndarray::ArrayView2;
32
use num_traits::One;
33
use num_traits::Zero;
34
use quote::quote;
35
use quote::ToTokens;
36
use twenty_first::prelude::*;
37

38
mod private {
39
    // A public but un-nameable type for sealing traits.
40
    pub trait Seal {}
41
}
42

43
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
44
pub struct DegreeLoweringInfo {
45
    /// The degree after degree lowering. Must be greater than 1.
46
    pub target_degree: isize,
47

48
    /// The total number of main columns _before_ degree lowering has happened.
49
    pub num_main_cols: usize,
50

51
    /// The total number of auxiliary columns _before_ degree lowering has happened.
52
    pub num_aux_cols: usize,
53
}
54

55
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
56
pub enum BinOp {
57
    Add,
58
    Mul,
59
}
60

61
impl Display for BinOp {
62
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
40,829✔
63
        match self {
40,829✔
64
            BinOp::Add => write!(f, "+"),
18,105✔
65
            BinOp::Mul => write!(f, "*"),
22,724✔
66
        }
67
    }
40,829✔
68
}
69

70
impl ToTokens for BinOp {
71
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
1,308✔
72
        match self {
1,308✔
73
            BinOp::Add => tokens.extend(quote!(+)),
524✔
74
            BinOp::Mul => tokens.extend(quote!(*)),
784✔
75
        }
76
    }
1,308✔
77
}
78

79
impl BinOp {
80
    pub fn operation<L, R, O>(&self, lhs: L, rhs: R) -> O
2,147,483,647✔
81
    where
2,147,483,647✔
82
        L: Add<R, Output = O> + Mul<R, Output = O>,
2,147,483,647✔
83
    {
2,147,483,647✔
84
        match self {
2,147,483,647✔
85
            BinOp::Add => lhs + rhs,
1,020,458,141✔
86
            BinOp::Mul => lhs * rhs,
1,370,690,033✔
87
        }
88
    }
2,147,483,647✔
89
}
90

91
/// Describes the position of a variable in a constraint polynomial in the row layout applicable
92
/// for a certain kind of constraint polynomial.
93
///
94
/// The position of variable in a constraint polynomial is, in principle, a `usize`. However,
95
/// depending on the type of the constraint polynomial, this index may be an index into a single
96
/// row (for initial, consistency and terminal constraints), or a pair of adjacent rows (for
97
/// transition constraints). Additionally, the index may refer to a column in the main table, or
98
/// a column in the auxiliary table. This trait abstracts over these possibilities, and provides
99
/// a uniform interface for accessing the index.
100
///
101
/// Having `Copy + Hash + Eq` helps to put `InputIndicator`s into containers.
102
///
103
/// This is a _sealed_ trait. It is not intended (or possible) to implement this trait outside the
104
/// crate defining it.
105
pub trait InputIndicator: Debug + Display + Copy + Hash + Eq + ToTokens + private::Seal {
106
    /// `true` iff `self` refers to a column in the main table.
107
    fn is_main_table_column(&self) -> bool;
108

109
    /// `true` iff `self` refers to the current row.
110
    fn is_current_row(&self) -> bool;
111

112
    /// The index of the indicated (main or auxiliary) column.
113
    fn column(&self) -> usize;
114

115
    fn main_table_input(index: usize) -> Self;
116
    fn aux_table_input(index: usize) -> Self;
117

118
    fn evaluate(
119
        &self,
120
        main_table: ArrayView2<BFieldElement>,
121
        aux_table: ArrayView2<XFieldElement>,
122
    ) -> XFieldElement;
123
}
124

125
/// The position of a variable in a constraint polynomial that operates on a single row of the
126
/// execution trace.
127
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Arbitrary)]
103,530✔
128
pub enum SingleRowIndicator {
129
    Main(usize),
130
    Aux(usize),
131
}
132

133
impl private::Seal for SingleRowIndicator {}
134

135
impl Display for SingleRowIndicator {
136
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
1,762✔
137
        let input_indicator: String = match self {
1,762✔
138
            Self::Main(i) => format!("main_row[{i}]"),
1,642✔
139
            Self::Aux(i) => format!("aux_row[{i}]"),
120✔
140
        };
141

142
        write!(f, "{input_indicator}")
1,762✔
143
    }
1,762✔
144
}
145

146
impl ToTokens for SingleRowIndicator {
147
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
68✔
148
        match self {
68✔
149
            Self::Main(i) => tokens.extend(quote!(main_row[#i])),
67✔
150
            Self::Aux(i) => tokens.extend(quote!(aux_row[#i])),
1✔
151
        }
152
    }
68✔
153
}
154

155
impl InputIndicator for SingleRowIndicator {
156
    fn is_main_table_column(&self) -> bool {
439✔
157
        matches!(self, Self::Main(_))
439✔
158
    }
439✔
159

160
    fn is_current_row(&self) -> bool {
12✔
161
        true
12✔
162
    }
12✔
163

164
    fn column(&self) -> usize {
136✔
165
        match self {
136✔
166
            Self::Main(i) | Self::Aux(i) => *i,
136✔
167
        }
136✔
168
    }
136✔
169

170
    fn main_table_input(index: usize) -> Self {
97✔
171
        Self::Main(index)
97✔
172
    }
97✔
173

174
    fn aux_table_input(index: usize) -> Self {
1✔
175
        Self::Aux(index)
1✔
176
    }
1✔
177

178
    fn evaluate(
68,716,723✔
179
        &self,
68,716,723✔
180
        main_table: ArrayView2<BFieldElement>,
68,716,723✔
181
        aux_table: ArrayView2<XFieldElement>,
68,716,723✔
182
    ) -> XFieldElement {
68,716,723✔
183
        match self {
68,716,723✔
184
            Self::Main(i) => main_table[[0, *i]].lift(),
68,706,157✔
185
            Self::Aux(i) => aux_table[[0, *i]],
10,566✔
186
        }
187
    }
68,716,723✔
188
}
189

190
/// The position of a variable in a constraint polynomial that operates on two rows (current and
191
/// next) of the execution trace.
192
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Arbitrary)]
295,929✔
193
pub enum DualRowIndicator {
194
    CurrentMain(usize),
195
    CurrentAux(usize),
196
    NextMain(usize),
197
    NextAux(usize),
198
}
199

200
impl private::Seal for DualRowIndicator {}
201

202
impl Display for DualRowIndicator {
203
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
21,750✔
204
        let input_indicator: String = match self {
21,750✔
205
            Self::CurrentMain(i) => format!("current_main_row[{i}]"),
15,533✔
206
            Self::CurrentAux(i) => format!("current_aux_row[{i}]"),
778✔
207
            Self::NextMain(i) => format!("next_main_row[{i}]"),
4,769✔
208
            Self::NextAux(i) => format!("next_aux_row[{i}]"),
670✔
209
        };
210

211
        write!(f, "{input_indicator}")
21,750✔
212
    }
21,750✔
213
}
214

215
impl ToTokens for DualRowIndicator {
216
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
854✔
217
        match self {
854✔
218
            Self::CurrentMain(i) => tokens.extend(quote!(current_main_row[#i])),
617✔
219
            Self::CurrentAux(i) => tokens.extend(quote!(current_aux_row[#i])),
59✔
220
            Self::NextMain(i) => tokens.extend(quote!(next_main_row[#i])),
157✔
221
            Self::NextAux(i) => tokens.extend(quote!(next_aux_row[#i])),
21✔
222
        }
223
    }
854✔
224
}
225

226
impl InputIndicator for DualRowIndicator {
227
    fn is_main_table_column(&self) -> bool {
4,784✔
228
        matches!(self, Self::CurrentMain(_) | Self::NextMain(_))
4,784✔
229
    }
4,784✔
230

UNCOV
231
    fn is_current_row(&self) -> bool {
×
UNCOV
232
        matches!(self, Self::CurrentMain(_) | Self::CurrentAux(_))
×
UNCOV
233
    }
×
234

235
    fn column(&self) -> usize {
1,316✔
236
        match self {
1,316✔
237
            Self::CurrentMain(i) | Self::NextMain(i) | Self::CurrentAux(i) | Self::NextAux(i) => *i,
1,316✔
238
        }
1,316✔
239
    }
1,316✔
240

241
    fn main_table_input(index: usize) -> Self {
1,042✔
242
        // It seems that the choice between `CurrentMain` and `NextMain` is arbitrary:
1,042✔
243
        // any transition constraint polynomial is evaluated on both the current and the next row.
1,042✔
244
        // Hence, both rows are in scope.
1,042✔
245
        Self::CurrentMain(index)
1,042✔
246
    }
1,042✔
247

248
    fn aux_table_input(index: usize) -> Self {
180✔
249
        Self::CurrentAux(index)
180✔
250
    }
180✔
251

252
    fn evaluate(
1,275,269,832✔
253
        &self,
1,275,269,832✔
254
        main_table: ArrayView2<BFieldElement>,
1,275,269,832✔
255
        aux_table: ArrayView2<XFieldElement>,
1,275,269,832✔
256
    ) -> XFieldElement {
1,275,269,832✔
257
        match self {
1,275,269,832✔
258
            Self::CurrentMain(i) => main_table[[0, *i]].lift(),
980,800,869✔
259
            Self::CurrentAux(i) => aux_table[[0, *i]],
30,963,023✔
260
            Self::NextMain(i) => main_table[[1, *i]].lift(),
232,452,688✔
261
            Self::NextAux(i) => aux_table[[1, *i]],
31,053,252✔
262
        }
263
    }
1,275,269,832✔
264
}
265

266
/// A circuit expression is the recursive data structure that represents the constraint polynomials.
267
/// It is a directed, acyclic graph of binary operations on the variables of the constraint
268
/// polynomials, constants, and challenges. It has multiple roots, making it a “multitree.” Each
269
/// root corresponds to one constraint polynomial.
270
///
271
/// The leafs of the tree are
272
/// - constants in the base field, _i.e._, [`BFieldElement`]s,
273
/// - constants in the extension field, _i.e._, [`XFieldElement`]s,
274
/// - input variables, _i.e._, entries from the Algebraic Execution Trace, and
275
/// - challenges, _i.e._, (pseudo-)random values sampled through the Fiat-Shamir heuristic.
276
///
277
/// An inner node, representing some binary operation, is either addition, multiplication, or
278
/// subtraction. The left and right children of the node are the operands of the binary operation.
279
/// The left and right children are not themselves `CircuitExpression`s, but rather
280
/// [`ConstraintCircuit`]s, which is a wrapper around `CircuitExpression` that manages additional
281
/// bookkeeping information.
282
#[derive(Debug, Clone)]
283
pub enum CircuitExpression<II: InputIndicator> {
284
    BConst(BFieldElement),
285
    XConst(XFieldElement),
286
    Input(II),
287
    Challenge(usize),
288
    BinOp(
289
        BinOp,
290
        Rc<RefCell<ConstraintCircuit<II>>>,
291
        Rc<RefCell<ConstraintCircuit<II>>>,
292
    ),
293
}
294

295
impl<II: InputIndicator> Hash for CircuitExpression<II> {
296
    fn hash<H: Hasher>(&self, state: &mut H) {
1,854,734,817✔
297
        match self {
1,854,734,817✔
298
            Self::BConst(bfe) => {
157,983,963✔
299
                "bfe".hash(state);
157,983,963✔
300
                bfe.hash(state);
157,983,963✔
301
            }
157,983,963✔
302
            Self::XConst(xfe) => {
2,632✔
303
                "xfe".hash(state);
2,632✔
304
                xfe.hash(state);
2,632✔
305
            }
2,632✔
306
            Self::Input(index) => {
532,253,288✔
307
                "input".hash(state);
532,253,288✔
308
                index.hash(state);
532,253,288✔
309
            }
532,253,288✔
310
            Self::Challenge(table_challenge_id) => {
256,219,610✔
311
                "challenge".hash(state);
256,219,610✔
312
                table_challenge_id.hash(state);
256,219,610✔
313
            }
256,219,610✔
314
            Self::BinOp(binop, lhs, rhs) => {
908,275,324✔
315
                "binop".hash(state);
908,275,324✔
316
                binop.hash(state);
908,275,324✔
317
                lhs.borrow().hash(state);
908,275,324✔
318
                rhs.borrow().hash(state);
908,275,324✔
319
            }
908,275,324✔
320
        }
321
    }
1,854,734,817✔
322
}
323

324
impl<II: InputIndicator> PartialEq for CircuitExpression<II> {
325
    fn eq(&self, other: &Self) -> bool {
2,147,483,647✔
326
        match (self, other) {
2,147,483,647✔
327
            (Self::BConst(b), Self::BConst(b_o)) => b == b_o,
31,439,499✔
328
            (Self::XConst(x), Self::XConst(x_o)) => x == x_o,
34,656✔
329
            (Self::Input(i), Self::Input(i_o)) => i == i_o,
94,090,411✔
330
            (Self::Challenge(c), Self::Challenge(c_o)) => c == c_o,
47,625,023✔
331
            (Self::BinOp(op, l, r), Self::BinOp(op_o, l_o, r_o)) => {
2,147,483,647✔
332
                op == op_o && l == l_o && r == r_o
2,147,483,647✔
333
            }
334
            _ => false,
2,147,483,647✔
335
        }
336
    }
2,147,483,647✔
337
}
338

339
impl<II: InputIndicator> Hash for ConstraintCircuit<II> {
340
    fn hash<H: Hasher>(&self, state: &mut H) {
1,854,734,817✔
341
        self.expression.hash(state)
1,854,734,817✔
342
    }
1,854,734,817✔
343
}
344

345
impl<II: InputIndicator> Hash for ConstraintCircuitMonad<II> {
346
    fn hash<H: Hasher>(&self, state: &mut H) {
1,280✔
347
        self.circuit.borrow().hash(state)
1,280✔
348
    }
1,280✔
349
}
350

351
/// A wrapper around a [`CircuitExpression`] that manages additional bookkeeping information.
352
#[derive(Debug, Clone)]
353
pub struct ConstraintCircuit<II: InputIndicator> {
354
    pub id: usize,
355
    pub ref_count: usize,
356
    pub expression: CircuitExpression<II>,
357
}
358

359
impl<II: InputIndicator> Eq for ConstraintCircuit<II> {}
360

361
impl<II: InputIndicator> PartialEq for ConstraintCircuit<II> {
362
    /// Calculate equality of circuits. In particular, this function does *not* attempt to
363
    /// simplify or reduce neutral terms or products. So this comparison will return false for
364
    /// `a == a + 0`. It will also return false for `XFieldElement(7) == BFieldElement(7)`
365
    fn eq(&self, other: &Self) -> bool {
2,147,483,647✔
366
        self.expression == other.expression
2,147,483,647✔
367
    }
2,147,483,647✔
368
}
369

370
impl<II: InputIndicator> Display for ConstraintCircuit<II> {
371
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
82,598✔
372
        match &self.expression {
82,598✔
373
            CircuitExpression::XConst(xfe) => write!(f, "{xfe}"),
1✔
374
            CircuitExpression::BConst(bfe) => write!(f, "{bfe}"),
12,902✔
375
            CircuitExpression::Input(input) => write!(f, "{input} "),
23,512✔
376
            CircuitExpression::Challenge(self_challenge_idx) => write!(f, "{self_challenge_idx}"),
5,354✔
377
            CircuitExpression::BinOp(operation, lhs, rhs) => {
40,829✔
378
                write!(f, "({}) {operation} ({})", lhs.borrow(), rhs.borrow())
40,829✔
379
            }
380
        }
381
    }
82,598✔
382
}
383

384
impl<II: InputIndicator> ConstraintCircuit<II> {
385
    fn new(id: usize, expression: CircuitExpression<II>) -> Self {
6,833,268✔
386
        Self {
6,833,268✔
387
            id,
6,833,268✔
388
            ref_count: 0,
6,833,268✔
389
            expression,
6,833,268✔
390
        }
6,833,268✔
391
    }
6,833,268✔
392

393
    /// Reset the reference counters for the entire subtree
394
    fn reset_ref_count_for_tree(&mut self) {
54,113✔
395
        self.ref_count = 0;
54,113✔
396

397
        if let CircuitExpression::BinOp(_, lhs, rhs) = &self.expression {
54,113✔
398
            lhs.borrow_mut().reset_ref_count_for_tree();
26,898✔
399
            rhs.borrow_mut().reset_ref_count_for_tree();
26,898✔
400
        }
27,215✔
401
    }
54,113✔
402

403
    /// Assert that all IDs in the subtree are unique.
404
    ///
405
    /// # Panics
406
    ///
407
    /// Panics if a duplicate ID is found.
408
    fn assert_unique_ids_inner(&mut self, ids: &mut HashMap<usize, ConstraintCircuit<II>>) {
13,425✔
409
        self.ref_count += 1;
13,425✔
410

13,425✔
411
        // Try to detect duplicate IDs only once for this node.
13,425✔
412
        if self.ref_count > 1 {
13,425✔
413
            return;
5,842✔
414
        }
7,583✔
415

7,583✔
416
        let self_id = self.id;
7,583✔
417
        if let Some(other) = ids.insert(self_id, self.clone()) {
7,583✔
UNCOV
418
            panic!("Repeated ID: {self_id}\nSelf:\n{self}\n{self:?}\nOther:\n{other}\n{other:?}");
×
419
        }
7,583✔
420

421
        if let CircuitExpression::BinOp(_, lhs, rhs) = &self.expression {
7,583✔
422
            lhs.borrow_mut().assert_unique_ids_inner(ids);
6,554✔
423
            rhs.borrow_mut().assert_unique_ids_inner(ids);
6,554✔
424
        }
6,554✔
425
    }
13,425✔
426

427
    /// Assert that a multicircuit has unique IDs.
428
    /// Also determines how often each node is referenced, updating the respective `ref_count`s.
429
    ///
430
    /// # Panics
431
    ///
432
    /// Panics if a duplicate ID is found.
433
    pub fn assert_unique_ids(constraints: &mut [ConstraintCircuit<II>]) {
48✔
434
        // inner uniqueness checks relies on reference counters being 0 for unseen nodes
435
        for circuit in constraints.iter_mut() {
317✔
436
            circuit.reset_ref_count_for_tree();
317✔
437
        }
317✔
438
        let mut ids: HashMap<usize, ConstraintCircuit<II>> = HashMap::new();
48✔
439
        for circuit in constraints.iter_mut() {
317✔
440
            circuit.assert_unique_ids_inner(&mut ids);
317✔
441
        }
317✔
442
    }
48✔
443

444
    /// Return degree of the multivariate polynomial represented by this circuit
445
    pub fn degree(&self) -> isize {
2,147,483,647✔
446
        if self.is_zero() {
2,147,483,647✔
UNCOV
447
            return -1;
×
448
        }
2,147,483,647✔
449

2,147,483,647✔
450
        match &self.expression {
2,147,483,647✔
451
            CircuitExpression::BinOp(binop, lhs, rhs) => {
2,147,483,647✔
452
                let degree_lhs = lhs.borrow().degree();
2,147,483,647✔
453
                let degree_rhs = rhs.borrow().degree();
2,147,483,647✔
454
                let degree_additive = cmp::max(degree_lhs, degree_rhs);
2,147,483,647✔
455
                let degree_multiplicative = if cmp::min(degree_lhs, degree_rhs) <= -1 {
2,147,483,647✔
UNCOV
456
                    -1
×
457
                } else {
458
                    degree_lhs + degree_rhs
2,147,483,647✔
459
                };
460
                match binop {
2,147,483,647✔
461
                    BinOp::Add => degree_additive,
2,147,483,647✔
462
                    BinOp::Mul => degree_multiplicative,
2,147,483,647✔
463
                }
464
            }
465
            CircuitExpression::Input(_) => 1,
2,147,483,647✔
466
            CircuitExpression::BConst(_)
467
            | CircuitExpression::XConst(_)
468
            | CircuitExpression::Challenge(_) => 0,
2,147,483,647✔
469
        }
470
    }
2,147,483,647✔
471

472
    /// All unique reference counters in the subtree, sorted.
473
    pub fn all_ref_counters(&self) -> Vec<usize> {
27✔
474
        let mut ref_counters = vec![self.ref_count];
27✔
475
        if let CircuitExpression::BinOp(_, lhs, rhs) = &self.expression {
27✔
476
            ref_counters.extend(lhs.borrow().all_ref_counters());
12✔
477
            ref_counters.extend(rhs.borrow().all_ref_counters());
12✔
478
        };
15✔
479
        ref_counters.sort_unstable();
27✔
480
        ref_counters.dedup();
27✔
481
        ref_counters
27✔
482
    }
27✔
483

484
    /// Is the node the constant 0?
485
    /// Does not catch composite expressions that will always evaluate to zero, like `0·a`.
486
    pub fn is_zero(&self) -> bool {
2,147,483,647✔
487
        match self.expression {
2,147,483,647✔
488
            CircuitExpression::BConst(bfe) => bfe.is_zero(),
1,377,522,518✔
489
            CircuitExpression::XConst(xfe) => xfe.is_zero(),
11,048✔
490
            _ => false,
2,147,483,647✔
491
        }
492
    }
2,147,483,647✔
493

494
    /// Is the node the constant 1?
495
    /// Does not catch composite expressions that will always evaluate to one, like `1·1`.
496
    pub fn is_one(&self) -> bool {
3,208,139✔
497
        match self.expression {
3,208,139✔
498
            CircuitExpression::BConst(bfe) => bfe.is_one(),
840,216✔
499
            CircuitExpression::XConst(xfe) => xfe.is_one(),
5,295✔
500
            _ => false,
2,362,628✔
501
        }
502
    }
3,208,139✔
503

504
    pub fn is_neg_one(&self) -> bool {
265✔
505
        match self.expression {
265✔
506
            CircuitExpression::BConst(bfe) => (-bfe).is_one(),
265✔
507
            CircuitExpression::XConst(xfe) => (-xfe).is_one(),
×
UNCOV
508
            _ => false,
×
509
        }
510
    }
265✔
511

512
    /// Recursively check whether this node is composed of only BFieldElements, i.e., only uses
513
    /// 1. inputs from main rows,
514
    /// 2. constants from the B-field, and
515
    /// 3. binary operations on BFieldElements.
516
    pub fn evaluates_to_base_element(&self) -> bool {
8,773✔
517
        match &self.expression {
8,773✔
518
            CircuitExpression::BConst(_) => true,
1,467✔
UNCOV
519
            CircuitExpression::XConst(_) => false,
×
520
            CircuitExpression::Input(indicator) => indicator.is_main_table_column(),
3,387✔
521
            CircuitExpression::Challenge(_) => false,
39✔
522
            CircuitExpression::BinOp(_, lhs, rhs) => {
3,880✔
523
                lhs.borrow().evaluates_to_base_element() && rhs.borrow().evaluates_to_base_element()
3,880✔
524
            }
525
        }
526
    }
8,773✔
527

528
    pub fn evaluate(
2,147,483,647✔
529
        &self,
2,147,483,647✔
530
        main_table: ArrayView2<BFieldElement>,
2,147,483,647✔
531
        aux_table: ArrayView2<XFieldElement>,
2,147,483,647✔
532
        challenges: &[XFieldElement],
2,147,483,647✔
533
    ) -> XFieldElement {
2,147,483,647✔
534
        match &self.expression {
2,147,483,647✔
535
            CircuitExpression::BConst(bfe) => bfe.lift(),
809,098,842✔
UNCOV
536
            CircuitExpression::XConst(xfe) => *xfe,
×
537
            CircuitExpression::Input(input) => input.evaluate(main_table, aux_table),
1,343,986,555✔
538
            CircuitExpression::Challenge(challenge_id) => challenges[*challenge_id],
258,384,682✔
539
            CircuitExpression::BinOp(binop, lhs, rhs) => {
2,147,483,647✔
540
                let lhs_value = lhs.borrow().evaluate(main_table, aux_table, challenges);
2,147,483,647✔
541
                let rhs_value = rhs.borrow().evaluate(main_table, aux_table, challenges);
2,147,483,647✔
542
                binop.operation(lhs_value, rhs_value)
2,147,483,647✔
543
            }
544
        }
545
    }
2,147,483,647✔
546
}
547

548
/// Constraint expressions, with context needed to ensure that two equal nodes are not added to
549
/// the multicircuit.
550
#[derive(Clone)]
551
pub struct ConstraintCircuitMonad<II: InputIndicator> {
552
    pub circuit: Rc<RefCell<ConstraintCircuit<II>>>,
553
    pub builder: ConstraintCircuitBuilder<II>,
554
}
555

556
impl<II: InputIndicator> Debug for ConstraintCircuitMonad<II> {
557
    // `all_nodes` contains itself, leading to infinite recursion during `Debug` printing.
558
    // Hence, this manual implementation.
UNCOV
559
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
×
UNCOV
560
        f.debug_struct("ConstraintCircuitMonad")
×
UNCOV
561
            .field("id", &self.circuit)
×
UNCOV
562
            .field("all_nodes length: ", &self.builder.all_nodes.borrow().len())
×
UNCOV
563
            .field("id_counter_ref value: ", &self.builder.id_counter.borrow())
×
UNCOV
564
            .finish()
×
UNCOV
565
    }
×
566
}
567

568
impl<II: InputIndicator> Display for ConstraintCircuitMonad<II> {
569
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
940✔
570
        write!(f, "{}", self.circuit.borrow())
940✔
571
    }
940✔
572
}
573

574
impl<II: InputIndicator> PartialEq for ConstraintCircuitMonad<II> {
575
    // Equality for the ConstraintCircuitMonad is defined by the circuit, not the
576
    // other metadata (e.g. ID) that it carries around.
577
    fn eq(&self, other: &Self) -> bool {
2,147,483,647✔
578
        self.circuit == other.circuit
2,147,483,647✔
579
    }
2,147,483,647✔
580
}
581

582
impl<II: InputIndicator> Eq for ConstraintCircuitMonad<II> {}
583

584
/// Helper function for binary operations that are used to generate new parent
585
/// nodes in the multitree that represents the algebraic circuit. Ensures that
586
/// each newly created node has a unique ID.
587
///
588
/// This function does not (currently) catch expressions of the form ((x+1)+1).
589
fn binop<II: InputIndicator>(
2,740,577✔
590
    binop: BinOp,
2,740,577✔
591
    lhs: ConstraintCircuitMonad<II>,
2,740,577✔
592
    rhs: ConstraintCircuitMonad<II>,
2,740,577✔
593
) -> ConstraintCircuitMonad<II> {
2,740,577✔
594
    assert!(lhs.builder.is_same_as(&rhs.builder));
2,740,577✔
595

596
    match (binop, &lhs, &rhs) {
2,740,577✔
597
        (BinOp::Add, _, zero) if zero.circuit.borrow().is_zero() => return lhs,
1,090,722✔
598
        (BinOp::Add, zero, _) if zero.circuit.borrow().is_zero() => return rhs,
946,829✔
599
        (BinOp::Mul, _, one) if one.circuit.borrow().is_one() => return lhs,
1,649,855✔
600
        (BinOp::Mul, one, _) if one.circuit.borrow().is_one() => return rhs,
1,558,284✔
601
        (BinOp::Mul, _, zero) if zero.circuit.borrow().is_zero() => return rhs,
1,529,788✔
602
        (BinOp::Mul, zero, _) if zero.circuit.borrow().is_zero() => return lhs,
1,395,281✔
603
        _ => (),
2,303,148✔
604
    };
2,303,148✔
605

2,303,148✔
606
    match (
2,303,148✔
607
        &lhs.circuit.borrow().expression,
2,303,148✔
608
        &rhs.circuit.borrow().expression,
2,303,148✔
609
    ) {
610
        (&CircuitExpression::BConst(l), &CircuitExpression::BConst(r)) => {
101,196✔
611
            return lhs.builder.b_constant(binop.operation(l, r))
101,196✔
612
        }
613
        (&CircuitExpression::BConst(l), &CircuitExpression::XConst(r)) => {
2,221✔
614
            return lhs.builder.x_constant(binop.operation(l, r))
2,221✔
615
        }
616
        (&CircuitExpression::XConst(l), &CircuitExpression::BConst(r)) => {
610✔
617
            return lhs.builder.x_constant(binop.operation(l, r))
610✔
618
        }
619
        (&CircuitExpression::XConst(l), &CircuitExpression::XConst(r)) => {
444✔
620
            return lhs.builder.x_constant(binop.operation(l, r))
444✔
621
        }
622
        _ => (),
2,198,677✔
623
    };
2,198,677✔
624

2,198,677✔
625
    // all `BinOp`s are commutative – try both orders of the operands
2,198,677✔
626
    let all_nodes = &mut lhs.builder.all_nodes.borrow_mut();
2,198,677✔
627
    let new_node = binop_new_node(binop, &rhs, &lhs);
2,198,677✔
628
    if let Some(node) = all_nodes.values().find(|&n| n == &new_node) {
2,147,483,647✔
629
        return node.to_owned();
902✔
630
    }
2,197,775✔
631

2,197,775✔
632
    let new_node = binop_new_node(binop, &lhs, &rhs);
2,197,775✔
633
    if let Some(node) = all_nodes.values().find(|&n| n == &new_node) {
1,995,228,393✔
634
        return node.to_owned();
1,204,793✔
635
    }
992,982✔
636

992,982✔
637
    let new_id = new_node.circuit.borrow().id;
992,982✔
638
    let maybe_existing_node = all_nodes.insert(new_id, new_node.clone());
992,982✔
639
    let new_node_is_new = maybe_existing_node.is_none();
992,982✔
640
    assert!(new_node_is_new, "new node must not overwrite existing node");
992,982✔
641
    lhs.builder.id_counter.borrow_mut().add_assign(1);
992,982✔
642
    new_node
992,982✔
643
}
2,740,577✔
644

645
fn binop_new_node<II: InputIndicator>(
4,396,452✔
646
    binop: BinOp,
4,396,452✔
647
    lhs: &ConstraintCircuitMonad<II>,
4,396,452✔
648
    rhs: &ConstraintCircuitMonad<II>,
4,396,452✔
649
) -> ConstraintCircuitMonad<II> {
4,396,452✔
650
    let id = lhs.builder.id_counter.borrow().to_owned();
4,396,452✔
651
    let expression = CircuitExpression::BinOp(binop, lhs.circuit.clone(), rhs.circuit.clone());
4,396,452✔
652
    let circuit = ConstraintCircuit::new(id, expression);
4,396,452✔
653
    lhs.builder.new_monad(circuit)
4,396,452✔
654
}
4,396,452✔
655

656
impl<II: InputIndicator> Add for ConstraintCircuitMonad<II> {
657
    type Output = ConstraintCircuitMonad<II>;
658

659
    fn add(self, rhs: Self) -> Self::Output {
570,230✔
660
        binop(BinOp::Add, self, rhs)
570,230✔
661
    }
570,230✔
662
}
663

664
impl<II: InputIndicator> Sub for ConstraintCircuitMonad<II> {
665
    type Output = ConstraintCircuitMonad<II>;
666

667
    fn sub(self, rhs: Self) -> Self::Output {
520,492✔
668
        binop(BinOp::Add, self, -rhs)
520,492✔
669
    }
520,492✔
670
}
671

672
impl<II: InputIndicator> Mul for ConstraintCircuitMonad<II> {
673
    type Output = ConstraintCircuitMonad<II>;
674

675
    fn mul(self, rhs: Self) -> Self::Output {
1,129,363✔
676
        binop(BinOp::Mul, self, rhs)
1,129,363✔
677
    }
1,129,363✔
678
}
679

680
impl<II: InputIndicator> Neg for ConstraintCircuitMonad<II> {
681
    type Output = ConstraintCircuitMonad<II>;
682

683
    fn neg(self) -> Self::Output {
520,492✔
684
        binop(BinOp::Mul, self.builder.minus_one(), self)
520,492✔
685
    }
520,492✔
686
}
687

688
/// This will panic if the iterator is empty because the neutral element needs a unique ID, and
689
/// we have no way of getting that here.
690
impl<II: InputIndicator> Sum for ConstraintCircuitMonad<II> {
691
    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
19,605✔
692
        iter.reduce(|accum, item| accum + item)
351,485✔
693
            .expect("ConstraintCircuitMonad Iterator was empty")
19,605✔
694
    }
19,605✔
695
}
696

697
impl<II: InputIndicator> ConstraintCircuitMonad<II> {
698
    /// Unwrap a ConstraintCircuitMonad to reveal its inner ConstraintCircuit
699
    pub fn consume(&self) -> ConstraintCircuit<II> {
163,185✔
700
        self.circuit.borrow().to_owned()
163,185✔
701
    }
163,185✔
702

703
    /// Lowers the degree of a given multicircuit to the target degree.
704
    /// This is achieved by introducing additional variables and constraints.
705
    /// The appropriate substitutions are applied to the given multicircuit.
706
    /// The target degree must be greater than 1.
707
    ///
708
    /// The new constraints are returned as two vector of ConstraintCircuitMonads:
709
    /// the first corresponds to main columns and constraints,
710
    /// the second to auxiliary columns and constraints.
711
    ///
712
    /// Each returned constraint is guaranteed to correspond to some
713
    /// `CircuitExpression::BinaryOperation(BinOp::Sub, lhs, rhs)` where
714
    /// - `lhs` is the new variable, and
715
    /// - `rhs` is the (sub)circuit replaced by `lhs`.
716
    ///   These can then be used to construct new columns,
717
    ///   as well as derivation rules for filling those new columns.
718
    ///
719
    /// The highest index of main and auxiliary columns used by the multicircuit have to be
720
    /// provided. The uniqueness of the new columns' indices depends on these provided values.
721
    /// Note that these indices are generally not equal to the number of used columns, especially
722
    /// when a tables' constraints are built using the master table's column indices.
723
    pub fn lower_to_degree(
139✔
724
        multicircuit: &mut [Self],
139✔
725
        info: DegreeLoweringInfo,
139✔
726
    ) -> (Vec<Self>, Vec<Self>) {
139✔
727
        let target_degree = info.target_degree;
139✔
728
        assert!(
139✔
729
            target_degree > 1,
139✔
UNCOV
730
            "Target degree must be greater than 1. Got {target_degree}."
×
731
        );
732

733
        let mut main_constraints = vec![];
139✔
734
        let mut aux_constraints = vec![];
139✔
735

139✔
736
        if multicircuit.is_empty() {
139✔
737
            return (main_constraints, aux_constraints);
28✔
738
        }
111✔
739

111✔
740
        let builder = multicircuit[0].builder.clone();
111✔
741

742
        while Self::multicircuit_degree(multicircuit) > target_degree {
1,431✔
743
            let chosen_node_id = Self::pick_node_to_substitute(multicircuit, target_degree);
1,320✔
744

1,320✔
745
            // Create a new variable.
1,320✔
746
            let chosen_node = builder.all_nodes.borrow()[&chosen_node_id].clone();
1,320✔
747
            let chosen_node_is_main_col = chosen_node.circuit.borrow().evaluates_to_base_element();
1,320✔
748
            let new_input_indicator = if chosen_node_is_main_col {
1,320✔
749
                let new_main_col_idx = info.num_main_cols + main_constraints.len();
1,139✔
750
                II::main_table_input(new_main_col_idx)
1,139✔
751
            } else {
752
                let new_aux_col_idx = info.num_aux_cols + aux_constraints.len();
181✔
753
                II::aux_table_input(new_aux_col_idx)
181✔
754
            };
755
            let new_variable = builder.input(new_input_indicator);
1,320✔
756

1,320✔
757
            // Substitute the chosen circuit with the new variable.
1,320✔
758
            builder.substitute(chosen_node_id, new_variable.clone());
1,320✔
759

760
            // Treat roots of the multicircuit explicitly.
761
            for circuit in multicircuit.iter_mut() {
121,083✔
762
                if circuit.circuit.borrow().id == chosen_node_id {
121,083✔
763
                    circuit.circuit = new_variable.circuit.clone();
1✔
764
                }
121,082✔
765
            }
766

767
            // Create new constraint and put it into the appropriate return vector.
768
            let new_constraint = new_variable - chosen_node;
1,320✔
769
            match chosen_node_is_main_col {
1,320✔
770
                true => main_constraints.push(new_constraint),
1,139✔
771
                false => aux_constraints.push(new_constraint),
181✔
772
            }
773
        }
774

775
        (main_constraints, aux_constraints)
111✔
776
    }
139✔
777

778
    /// Heuristically pick a node from the given multicircuit that is to be substituted with a new
779
    /// variable. The ID of the chosen node is returned.
780
    fn pick_node_to_substitute(
1,320✔
781
        multicircuit: &[ConstraintCircuitMonad<II>],
1,320✔
782
        target_degree: isize,
1,320✔
783
    ) -> usize {
1,320✔
784
        // The relevant fields of a ConstraintCircuit.
785
        // Avoids interior mutability in a HashSet, which is a foot gun.
786
        #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
787
        struct PartialNodeInfo {
788
            id: usize,
789
            degree: isize,
790
        }
791

792
        assert!(!multicircuit.is_empty());
1,320✔
793
        let multicircuit = multicircuit
1,320✔
794
            .iter()
1,320✔
795
            .map(|c| c.clone().consume())
121,083✔
796
            .collect_vec();
1,320✔
797

1,320✔
798
        // Only nodes with degree > target_degree need changing.
1,320✔
799
        let high_degree_nodes = Self::all_nodes_in_multicircuit(&multicircuit)
1,320✔
800
            .into_iter()
1,320✔
801
            .unique()
1,320✔
802
            .filter(|node| node.degree() > target_degree)
5,805,920✔
803
            .collect_vec();
1,320✔
804

1,320✔
805
        // Collect all candidates for substitution, i.e., descendents of high_degree_nodes
1,320✔
806
        // with degree <= target_degree.
1,320✔
807
        // Substituting a node of degree 1 is both pointless and can lead to infinite iteration.
1,320✔
808
        let low_degree_nodes = Self::all_nodes_in_multicircuit(&high_degree_nodes)
1,320✔
809
            .into_iter()
1,320✔
810
            .filter(|node| 1 < node.degree() && node.degree() <= target_degree)
289,835,116✔
811
            .map(|node| PartialNodeInfo {
32,878,150✔
812
                id: node.id,
32,878,150✔
813
                degree: node.degree(),
32,878,150✔
814
            })
32,878,150✔
815
            .collect_vec();
1,320✔
816

1,320✔
817
        // If the resulting list is empty, there is no way forward. Stop – panic time!
1,320✔
818
        assert!(!low_degree_nodes.is_empty(), "Cannot lower degree.");
1,320✔
819

820
        // Of the remaining nodes, keep the ones occurring the most often.
821
        let mut nodes_and_occurrences = HashMap::new();
1,320✔
822
        for node in &low_degree_nodes {
32,879,470✔
823
            *nodes_and_occurrences.entry(node).or_insert(0) += 1;
32,878,150✔
824
        }
32,878,150✔
825
        let max_occurrences = nodes_and_occurrences.iter().map(|(_, &c)| c).max().unwrap();
1,304,754✔
826
        nodes_and_occurrences.retain(|_, &mut count| count == max_occurrences);
1,304,754✔
827
        let mut candidate_nodes = nodes_and_occurrences.keys().copied().collect_vec();
1,320✔
828

1,320✔
829
        // If there are still multiple nodes, pick the one with the highest degree.
1,320✔
830
        let max_degree = candidate_nodes.iter().map(|n| n.degree).max().unwrap();
18,128✔
831
        candidate_nodes.retain(|node| node.degree == max_degree);
18,128✔
832

1,320✔
833
        // If there are still multiple nodes, pick any one – but deterministically so.
1,320✔
834
        candidate_nodes.sort_unstable_by_key(|node| node.id);
74,026✔
835
        candidate_nodes[0].id
1,320✔
836
    }
1,320✔
837

838
    /// Returns all nodes used in the multicircuit.
839
    /// This is distinct from `ConstraintCircuitBuilder::all_nodes` because it
840
    /// 1. only considers nodes used in the given multicircuit, not all nodes in the builder,
841
    /// 2. returns the nodes as [`ConstraintCircuit`]s, not as [`ConstraintCircuitMonad`]s, and
842
    /// 3. keeps duplicates, allowing to count how often a node occurs.
843
    pub fn all_nodes_in_multicircuit(
2,641✔
844
        multicircuit: &[ConstraintCircuit<II>],
2,641✔
845
    ) -> Vec<ConstraintCircuit<II>> {
2,641✔
846
        multicircuit
2,641✔
847
            .iter()
2,641✔
848
            .flat_map(Self::all_nodes_in_circuit)
2,641✔
849
            .collect()
2,641✔
850
    }
2,641✔
851

852
    /// Internal helper function to recursively find all nodes in a circuit.
853
    fn all_nodes_in_circuit(circuit: &ConstraintCircuit<II>) -> Vec<ConstraintCircuit<II>> {
319,797,196✔
854
        let mut all_nodes = vec![];
319,797,196✔
855
        if let CircuitExpression::BinOp(_, lhs, rhs) = circuit.expression.clone() {
319,797,196✔
856
            let lhs_nodes = Self::all_nodes_in_circuit(&lhs.borrow());
159,487,553✔
857
            let rhs_nodes = Self::all_nodes_in_circuit(&rhs.borrow());
159,487,553✔
858
            all_nodes.extend(lhs_nodes);
159,487,553✔
859
            all_nodes.extend(rhs_nodes);
159,487,553✔
860
        };
160,309,643✔
861
        all_nodes.push(circuit.to_owned());
319,797,196✔
862
        all_nodes
319,797,196✔
863
    }
319,797,196✔
864

865
    /// Counts the number of nodes in this multicircuit. Only counts nodes that
866
    /// are used; not nodes that have been forgotten.
867
    pub fn num_nodes(constraints: &[Self]) -> usize {
12✔
868
        constraints
12✔
869
            .iter()
12✔
870
            .flat_map(|ccm| Self::all_nodes_in_circuit(&ccm.circuit.borrow()))
1,375✔
871
            .unique()
12✔
872
            .count()
12✔
873
    }
12✔
874

875
    /// Returns the maximum degree of all circuits in the multicircuit.
876
    pub fn multicircuit_degree(multicircuit: &[ConstraintCircuitMonad<II>]) -> isize {
1,659✔
877
        multicircuit
1,659✔
878
            .iter()
1,659✔
879
            .map(|circuit| circuit.circuit.borrow().degree())
125,011✔
880
            .max()
1,659✔
881
            .unwrap_or(-1)
1,659✔
882
    }
1,659✔
883
}
884

885
#[derive(Debug, Clone, Eq, PartialEq)]
886
/// Helper struct to construct new leaf nodes in the circuit multitree. Ensures that each newly
887
/// created node gets a unique ID.
888
pub struct ConstraintCircuitBuilder<II: InputIndicator> {
889
    id_counter: Rc<RefCell<usize>>,
890
    all_nodes: Rc<RefCell<HashMap<usize, ConstraintCircuitMonad<II>>>>,
891
}
892

893
impl<II: InputIndicator> Default for ConstraintCircuitBuilder<II> {
UNCOV
894
    fn default() -> Self {
×
UNCOV
895
        Self::new()
×
UNCOV
896
    }
×
897
}
898

899
impl<II: InputIndicator> ConstraintCircuitBuilder<II> {
900
    pub fn new() -> Self {
7,387✔
901
        Self {
7,387✔
902
            id_counter: Rc::new(RefCell::new(0)),
7,387✔
903
            all_nodes: Rc::new(RefCell::new(HashMap::new())),
7,387✔
904
        }
7,387✔
905
    }
7,387✔
906

907
    /// Check whether two builders are the same.
908
    ///
909
    /// Notably, this is distinct from checking equality: two builders are equal if they are in the
910
    /// same state. Two builders are the same if they are the same instance.
911
    pub fn is_same_as(&self, other: &Self) -> bool {
2,740,577✔
912
        Rc::ptr_eq(&self.id_counter, &other.id_counter)
2,740,577✔
913
            && Rc::ptr_eq(&self.all_nodes, &other.all_nodes)
2,740,577✔
914
    }
2,740,577✔
915

916
    fn new_monad(&self, circuit: ConstraintCircuit<II>) -> ConstraintCircuitMonad<II> {
6,833,268✔
917
        let circuit = Rc::new(RefCell::new(circuit));
6,833,268✔
918
        ConstraintCircuitMonad {
6,833,268✔
919
            circuit,
6,833,268✔
920
            builder: self.clone(),
6,833,268✔
921
        }
6,833,268✔
922
    }
6,833,268✔
923

924
    /// The unique monad representing the constant value 0.
925
    pub fn zero(&self) -> ConstraintCircuitMonad<II> {
9,733✔
926
        self.b_constant(0)
9,733✔
927
    }
9,733✔
928

929
    /// The unique monad representing the constant value 1.
930
    pub fn one(&self) -> ConstraintCircuitMonad<II> {
8,731✔
931
        self.b_constant(1)
8,731✔
932
    }
8,731✔
933

934
    /// The unique monad representing the constant value -1.
935
    pub fn minus_one(&self) -> ConstraintCircuitMonad<II> {
520,492✔
936
        self.b_constant(-1)
520,492✔
937
    }
520,492✔
938

939
    /// Leaf node with constant over the [base field][BFieldElement].
940
    pub fn b_constant<B>(&self, bfe: B) -> ConstraintCircuitMonad<II>
1,080,644✔
941
    where
1,080,644✔
942
        B: Into<BFieldElement>,
1,080,644✔
943
    {
1,080,644✔
944
        self.make_leaf(CircuitExpression::BConst(bfe.into()))
1,080,644✔
945
    }
1,080,644✔
946

947
    /// Leaf node with constant over the [extension field][XFieldElement].
948
    pub fn x_constant<X>(&self, xfe: X) -> ConstraintCircuitMonad<II>
14,112✔
949
    where
14,112✔
950
        X: Into<XFieldElement>,
14,112✔
951
    {
14,112✔
952
        self.make_leaf(CircuitExpression::XConst(xfe.into()))
14,112✔
953
    }
14,112✔
954

955
    /// Create deterministic input leaf node.
956
    pub fn input(&self, input: II) -> ConstraintCircuitMonad<II> {
999,949✔
957
        self.make_leaf(CircuitExpression::Input(input))
999,949✔
958
    }
999,949✔
959

960
    /// Create challenge leaf node.
961
    pub fn challenge<C>(&self, challenge: C) -> ConstraintCircuitMonad<II>
342,111✔
962
    where
342,111✔
963
        C: Into<usize>,
342,111✔
964
    {
342,111✔
965
        self.make_leaf(CircuitExpression::Challenge(challenge.into()))
342,111✔
966
    }
342,111✔
967

968
    fn make_leaf(&self, mut expression: CircuitExpression<II>) -> ConstraintCircuitMonad<II> {
2,436,816✔
969
        // Don't generate an X field leaf if it can be expressed as a B field leaf
970
        if let CircuitExpression::XConst(xfe) = expression {
2,436,816✔
971
            if let Some(bfe) = xfe.unlift() {
14,112✔
972
                expression = CircuitExpression::BConst(bfe);
3,239✔
973
            }
10,873✔
974
        }
2,422,704✔
975

976
        let id = self.id_counter.borrow().to_owned();
2,436,816✔
977
        let circuit = ConstraintCircuit::new(id, expression);
2,436,816✔
978
        let new_node = self.new_monad(circuit);
2,436,816✔
979

980
        if let Some(same_node) = self.all_nodes.borrow().values().find(|&n| n == &new_node) {
1,565,589,037✔
981
            return same_node.to_owned();
2,242,573✔
982
        }
194,243✔
983

194,243✔
984
        let maybe_previous_node = self.all_nodes.borrow_mut().insert(id, new_node.clone());
194,243✔
985
        let new_node_is_new = maybe_previous_node.is_none();
194,243✔
986
        assert!(new_node_is_new, "Leaf-created node must be new… {new_node}");
194,243✔
987
        self.id_counter.borrow_mut().add_assign(1);
194,243✔
988
        new_node
194,243✔
989
    }
2,436,816✔
990

991
    /// Replace all pointers to a given node (identified by `old_id`) by one
992
    /// to the new node.
993
    ///
994
    /// A circuit's root node cannot be substituted with this method. Manual care
995
    /// must be taken to update the root node if necessary.
996
    fn substitute(&self, old_id: usize, new: ConstraintCircuitMonad<II>) {
1,321✔
997
        self.all_nodes.borrow_mut().remove(&old_id);
1,321✔
998
        for node in self.all_nodes.borrow_mut().values_mut() {
6,244,837✔
999
            let node_expression = &mut node.circuit.borrow_mut().expression;
6,244,837✔
1000
            let CircuitExpression::BinOp(_, ref mut node_lhs, ref mut node_rhs) = node_expression
6,244,837✔
1001
            else {
1002
                continue;
486,495✔
1003
            };
1004

1005
            if node_lhs.borrow().id == old_id {
5,758,342✔
1006
                *node_lhs = new.circuit.clone();
7,206✔
1007
            }
5,751,136✔
1008
            if node_rhs.borrow().id == old_id {
5,758,342✔
1009
                *node_rhs = new.circuit.clone();
1,273✔
1010
            }
5,757,069✔
1011
        }
1012
    }
1,321✔
1013
}
1014

1015
impl<'a, II: InputIndicator + Arbitrary<'a>> Arbitrary<'a> for ConstraintCircuitMonad<II> {
1016
    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
2,073✔
1017
        let builder = ConstraintCircuitBuilder::new();
2,073✔
1018
        let mut random_circuit = random_circuit_leaf(&builder, u)?;
2,073✔
1019

1020
        let num_nodes_in_circuit = u.arbitrary_len::<Self>()?;
2,073✔
1021
        for _ in 0..num_nodes_in_circuit {
2,073✔
1022
            let leaf = random_circuit_leaf(&builder, u)?;
240,551✔
1023
            match u.int_in_range(0..=5)? {
240,551✔
1024
                0 => random_circuit = random_circuit * leaf,
204,539✔
1025
                1 => random_circuit = random_circuit + leaf,
7,233✔
1026
                2 => random_circuit = random_circuit - leaf,
7,274✔
1027
                3 => random_circuit = leaf * random_circuit,
7,285✔
1028
                4 => random_circuit = leaf + random_circuit,
7,196✔
1029
                5 => random_circuit = leaf - random_circuit,
7,024✔
UNCOV
1030
                _ => unreachable!(),
×
1031
            }
1032
        }
1033

1034
        Ok(random_circuit)
2,073✔
1035
    }
2,073✔
1036
}
1037

1038
fn random_circuit_leaf<'a, II: InputIndicator + Arbitrary<'a>>(
242,624✔
1039
    builder: &ConstraintCircuitBuilder<II>,
242,624✔
1040
    u: &mut Unstructured<'a>,
242,624✔
1041
) -> arbitrary::Result<ConstraintCircuitMonad<II>> {
242,624✔
1042
    let leaf = match u.int_in_range(0..=5)? {
242,624✔
1043
        0 => builder.input(u.arbitrary()?),
203,622✔
1044
        1 => builder.challenge(u.arbitrary::<usize>()?),
7,996✔
1045
        2 => builder.b_constant(u.arbitrary::<BFieldElement>()?),
7,796✔
1046
        3 => builder.x_constant(u.arbitrary::<XFieldElement>()?),
7,833✔
1047
        4 => builder.one(),
7,707✔
1048
        5 => builder.zero(),
7,670✔
UNCOV
1049
        _ => unreachable!(),
×
1050
    };
1051
    Ok(leaf)
242,624✔
1052
}
242,624✔
1053

1054
#[cfg(test)]
1055
mod tests {
1056
    use std::collections::hash_map::DefaultHasher;
1057
    use std::hash::Hasher;
1058

1059
    use itertools::Itertools;
1060
    use proptest::prelude::*;
1061
    use proptest_arbitrary_interop::arb;
1062
    use rand::random;
1063
    use test_strategy::proptest;
1064

1065
    use super::*;
1066

1067
    impl<II: InputIndicator> ConstraintCircuitMonad<II> {
1068
        /// Traverse the circuit and find all nodes that are equivalent. Note that
1069
        /// two nodes are equivalent if they compute the same value on all identical
1070
        /// inputs. Equivalence is different from identity, which is when two nodes
1071
        /// connect the same set of neighbors in the same way. (There may be two
1072
        /// different ways to compute the same result; they are equivalent but
1073
        /// unequal.)
1074
        ///
1075
        /// This function returns a list of lists of equivalent nodes such that
1076
        /// every inner list can be reduced to a single node without changing the
1077
        /// circuit's function.
1078
        ///
1079
        /// Equivalent nodes are detected probabilistically using the multivariate
1080
        /// Schwartz-Zippel lemma. The false positive probability is zero (we can be
1081
        /// certain that equivalent nodes will be found). The false negative
1082
        /// probability is bounded by max_degree / (2^64 - 2^32 + 1)^3.
1083
        pub fn find_equivalent_nodes(&self) -> Vec<Vec<Rc<RefCell<ConstraintCircuit<II>>>>> {
2✔
1084
            let mut id_to_eval = HashMap::new();
2✔
1085
            let mut eval_to_ids = HashMap::new();
2✔
1086
            let mut id_to_node = HashMap::new();
2✔
1087
            Self::probe_random(
2✔
1088
                &self.circuit,
2✔
1089
                &mut id_to_eval,
2✔
1090
                &mut eval_to_ids,
2✔
1091
                &mut id_to_node,
2✔
1092
                random(),
2✔
1093
            );
2✔
1094

2✔
1095
            eval_to_ids
2✔
1096
                .values()
2✔
1097
                .filter(|ids| ids.len() >= 2)
36✔
1098
                .map(|ids| ids.iter().map(|i| id_to_node[i].clone()).collect_vec())
2✔
1099
                .collect_vec()
2✔
1100
        }
2✔
1101

1102
        /// Populate the dictionaries such that they associate with every node in
1103
        /// the circuit its evaluation in a random point. The inputs are assigned
1104
        /// random values. Equivalent nodes are detected based on evaluating to the
1105
        /// same value using the Schwartz-Zippel lemma.
1106
        fn probe_random(
52✔
1107
            circuit: &Rc<RefCell<ConstraintCircuit<II>>>,
52✔
1108
            id_to_eval: &mut HashMap<usize, XFieldElement>,
52✔
1109
            eval_to_ids: &mut HashMap<XFieldElement, Vec<usize>>,
52✔
1110
            id_to_node: &mut HashMap<usize, Rc<RefCell<ConstraintCircuit<II>>>>,
52✔
1111
            master_seed: XFieldElement,
52✔
1112
        ) -> XFieldElement {
52✔
1113
            const DOMAIN_SEPARATOR_CURR_ROW: BFieldElement = BFieldElement::new(0);
1114
            const DOMAIN_SEPARATOR_NEXT_ROW: BFieldElement = BFieldElement::new(1);
1115
            const DOMAIN_SEPARATOR_CHALLENGE: BFieldElement = BFieldElement::new(2);
1116

1117
            let circuit_id = circuit.borrow().id;
52✔
1118
            if let Some(&xfe) = id_to_eval.get(&circuit_id) {
52✔
1119
                return xfe;
15✔
1120
            }
37✔
1121

1122
            let evaluation = match &circuit.borrow().expression {
37✔
1123
                CircuitExpression::BConst(bfe) => bfe.lift(),
2✔
UNCOV
1124
                CircuitExpression::XConst(xfe) => *xfe,
×
1125
                CircuitExpression::Input(input) => {
8✔
1126
                    let [s0, s1, s2] = master_seed.coefficients;
8✔
1127
                    let dom_sep = if input.is_current_row() {
8✔
1128
                        DOMAIN_SEPARATOR_CURR_ROW
8✔
1129
                    } else {
UNCOV
1130
                        DOMAIN_SEPARATOR_NEXT_ROW
×
1131
                    };
1132
                    let i = bfe!(u64::try_from(input.column()).unwrap());
8✔
1133
                    let Digest([d0, d1, d2, _, _]) = Tip5::hash_varlen(&[s0, s1, s2, dom_sep, i]);
8✔
1134
                    xfe!([d0, d1, d2])
8✔
1135
                }
1136
                CircuitExpression::Challenge(challenge) => {
2✔
1137
                    let [s0, s1, s2] = master_seed.coefficients;
2✔
1138
                    let dom_sep = DOMAIN_SEPARATOR_CHALLENGE;
2✔
1139
                    let ch = bfe!(u64::try_from(*challenge).unwrap());
2✔
1140
                    let Digest([d0, d1, d2, _, _]) = Tip5::hash_varlen(&[s0, s1, s2, dom_sep, ch]);
2✔
1141
                    xfe!([d0, d1, d2])
2✔
1142
                }
1143
                CircuitExpression::BinOp(bin_op, lhs, rhs) => {
25✔
1144
                    let l =
25✔
1145
                        Self::probe_random(lhs, id_to_eval, eval_to_ids, id_to_node, master_seed);
25✔
1146
                    let r =
25✔
1147
                        Self::probe_random(rhs, id_to_eval, eval_to_ids, id_to_node, master_seed);
25✔
1148
                    bin_op.operation(l, r)
25✔
1149
                }
1150
            };
1151

1152
            id_to_eval.insert(circuit_id, evaluation);
37✔
1153
            eval_to_ids.entry(evaluation).or_default().push(circuit_id);
37✔
1154
            id_to_node.insert(circuit_id, circuit.clone());
37✔
1155

37✔
1156
            evaluation
37✔
1157
        }
52✔
1158

1159
        /// Check whether the given node is contained in this circuit.
1160
        fn contains(&self, other: &Self) -> bool {
9✔
1161
            let self_expression = &self.circuit.borrow().expression;
9✔
1162
            let other_expression = &other.circuit.borrow().expression;
9✔
1163
            self_expression.contains(other_expression)
9✔
1164
        }
9✔
1165
    }
1166

1167
    impl<II: InputIndicator> CircuitExpression<II> {
1168
        /// Check whether the given node is contained in this circuit.
1169
        fn contains(&self, other: &Self) -> bool {
43✔
1170
            if self == other {
43✔
1171
                return true;
5✔
1172
            }
38✔
1173
            let CircuitExpression::BinOp(_, lhs, rhs) = self else {
38✔
1174
                return false;
17✔
1175
            };
1176

1177
            lhs.borrow().expression.contains(other) || rhs.borrow().expression.contains(other)
21✔
1178
        }
43✔
1179
    }
1180

1181
    /// The [`Hash`] trait requires:
1182
    /// circuit_0 == circuit_1 => hash(circuit_0) == hash(circuit_1)
1183
    ///
1184
    /// By De-Morgan's law, this is equivalent to the more meaningful test:
1185
    /// hash(circuit_0) != hash(circuit_1) => circuit_0 != circuit_1
1186
    #[proptest]
512✔
1187
    fn unequal_hash_implies_unequal_constraint_circuit_monad(
1188
        #[strategy(arb())] circuit_0: ConstraintCircuitMonad<SingleRowIndicator>,
1✔
1189
        #[strategy(arb())] circuit_1: ConstraintCircuitMonad<SingleRowIndicator>,
1✔
1190
    ) {
1191
        if hash_circuit(&circuit_0) != hash_circuit(&circuit_1) {
1192
            prop_assert_ne!(circuit_0, circuit_1);
1193
        }
1194
    }
1195

1196
    /// The hash of a node may not depend on `ref_count`, `counter`, `id_counter_ref`, or
1197
    /// `all_nodes`, since `all_nodes` contains the digest of all nodes in the multi tree.
1198
    /// For more details, see [`HashSet`].
1199
    #[proptest]
512✔
1200
    fn multi_circuit_hash_is_unchanged_by_meta_data(
1201
        #[strategy(arb())] circuit: ConstraintCircuitMonad<DualRowIndicator>,
1✔
1202
        new_ref_count: usize,
1203
        new_id_counter: usize,
1204
    ) {
1205
        let original_digest = hash_circuit(&circuit);
1206

1207
        circuit.circuit.borrow_mut().ref_count = new_ref_count;
1208
        prop_assert_eq!(original_digest, hash_circuit(&circuit));
1209

1210
        circuit.builder.id_counter.replace(new_id_counter);
1211
        prop_assert_eq!(original_digest, hash_circuit(&circuit));
1212
    }
1213

1214
    fn hash_circuit<II: InputIndicator>(circuit: &ConstraintCircuitMonad<II>) -> u64 {
1,280✔
1215
        let mut hasher = DefaultHasher::new();
1,280✔
1216
        circuit.hash(&mut hasher);
1,280✔
1217
        hasher.finish()
1,280✔
1218
    }
1,280✔
1219

1220
    #[test]
1221
    fn printing_constraint_circuit_gives_expected_strings() {
1✔
1222
        let builder = ConstraintCircuitBuilder::new();
1✔
1223
        assert_eq!("1", builder.b_constant(1).to_string());
1✔
1224
        assert_eq!(
1✔
1225
            "main_row[5] ",
1✔
1226
            builder.input(SingleRowIndicator::Main(5)).to_string()
1✔
1227
        );
1✔
1228
        assert_eq!("6", builder.challenge(6_usize).to_string());
1✔
1229

1230
        let xfe_str = builder.x_constant([2, 3, 4]).to_string();
1✔
1231
        assert_eq!("(4·x² + 3·x + 2)", xfe_str);
1✔
1232
    }
1✔
1233

1234
    #[proptest]
256✔
1235
    fn constant_folding_can_deal_with_multiplication_by_one(
1236
        #[strategy(arb())] c: ConstraintCircuitMonad<DualRowIndicator>,
1✔
1237
    ) {
1238
        let one = || c.builder.one();
1,024✔
1239
        prop_assert_eq!(c.clone(), c.clone() * one());
1240
        prop_assert_eq!(c.clone(), one() * c.clone());
1241
        prop_assert_eq!(c.clone(), one() * c.clone() * one());
1242
    }
1243

1244
    #[proptest]
256✔
1245
    fn constant_folding_can_deal_with_adding_zero(
1246
        #[strategy(arb())] c: ConstraintCircuitMonad<DualRowIndicator>,
1✔
1247
    ) {
1248
        let zero = || c.builder.zero();
1,024✔
1249
        prop_assert_eq!(c.clone(), c.clone() + zero());
1250
        prop_assert_eq!(c.clone(), zero() + c.clone());
1251
        prop_assert_eq!(c.clone(), zero() + c.clone() + zero());
1252
    }
1253

1254
    #[proptest]
256✔
1255
    fn constant_folding_can_deal_with_subtracting_zero(
1256
        #[strategy(arb())] c: ConstraintCircuitMonad<DualRowIndicator>,
1✔
1257
    ) {
1258
        prop_assert_eq!(c.clone(), c.clone() - c.builder.zero());
1259
    }
1260

1261
    #[proptest]
512✔
1262
    fn constant_folding_can_deal_with_adding_effectively_zero_term(
1263
        #[strategy(arb())] c: ConstraintCircuitMonad<DualRowIndicator>,
1✔
1264
        modification_selectors: [bool; 4],
1265
    ) {
1266
        let zero = || c.builder.zero();
527✔
1267
        let mut redundant_circuit = c.clone();
1268
        if modification_selectors[0] {
1269
            redundant_circuit = redundant_circuit + (c.clone() * zero());
1270
        }
1271
        if modification_selectors[1] {
1272
            redundant_circuit = redundant_circuit + (zero() * c.clone());
1273
        }
1274
        if modification_selectors[2] {
1275
            redundant_circuit = (c.clone() * zero()) + redundant_circuit;
1276
        }
1277
        if modification_selectors[3] {
1278
            redundant_circuit = (zero() * c.clone()) + redundant_circuit;
1279
        }
1280
        prop_assert_eq!(c, redundant_circuit);
1281
    }
1282

1283
    #[proptest]
281✔
1284
    fn constant_folding_does_not_replace_0_minus_circuit_with_the_circuit(
1285
        #[strategy(arb())] circuit: ConstraintCircuitMonad<DualRowIndicator>,
1✔
1286
    ) {
1287
        if circuit.circuit.borrow().is_zero() {
1288
            return Err(TestCaseError::Reject("0 - 0 actually is 0".into()));
1289
        }
1290
        let zero_minus_circuit = circuit.builder.zero() - circuit.clone();
1291
        prop_assert_ne!(&circuit, &zero_minus_circuit);
1292
    }
1293

1294
    #[test]
1295
    fn substitution_replaces_a_node_in_a_circuit() {
1✔
1296
        let builder = ConstraintCircuitBuilder::new();
1✔
1297
        let x = |i| builder.input(SingleRowIndicator::Main(i));
5✔
1298
        let constant = |c: u32| builder.b_constant(c);
2✔
1299
        let challenge = |i: usize| builder.challenge(i);
3✔
1300

1301
        let part = x(0) + x(1);
1✔
1302
        let substitute_me = x(0) * part.clone();
1✔
1303
        let root_0 = part.clone() + challenge(1) - constant(84);
1✔
1304
        let root_1 = substitute_me.clone() + challenge(0) - constant(42);
1✔
1305
        let root_2 = x(2) * substitute_me.clone() - challenge(1);
1✔
1306

1✔
1307
        assert!(!root_0.contains(&substitute_me));
1✔
1308
        assert!(root_1.contains(&substitute_me));
1✔
1309
        assert!(root_2.contains(&substitute_me));
1✔
1310

1311
        let new_variable = x(3);
1✔
1312
        builder.substitute(substitute_me.circuit.borrow().id, new_variable.clone());
1✔
1313

1✔
1314
        assert!(!root_0.contains(&substitute_me));
1✔
1315
        assert!(!root_1.contains(&substitute_me));
1✔
1316
        assert!(!root_2.contains(&substitute_me));
1✔
1317

1318
        assert!(root_0.contains(&part));
1✔
1319
        assert!(root_1.contains(&new_variable));
1✔
1320
        assert!(root_2.contains(&new_variable));
1✔
1321
    }
1✔
1322

1323
    #[test]
1324
    fn simple_degree_lowering() {
1✔
1325
        let builder = ConstraintCircuitBuilder::new();
1✔
1326
        let x = || builder.input(SingleRowIndicator::Main(0));
8✔
1327
        let x_pow_3 = x() * x() * x();
1✔
1328
        let x_pow_5 = x() * x() * x() * x() * x();
1✔
1329
        let mut multicircuit = [x_pow_5, x_pow_3];
1✔
1330

1✔
1331
        let degree_lowering_info = DegreeLoweringInfo {
1✔
1332
            target_degree: 3,
1✔
1333
            num_main_cols: 1,
1✔
1334
            num_aux_cols: 0,
1✔
1335
        };
1✔
1336
        let (new_main_constraints, new_aux_constraints) =
1✔
1337
            ConstraintCircuitMonad::lower_to_degree(&mut multicircuit, degree_lowering_info);
1✔
1338

1✔
1339
        assert_eq!(1, new_main_constraints.len());
1✔
1340
        assert!(new_aux_constraints.is_empty());
1✔
1341
    }
1✔
1342

1343
    #[test]
1344
    fn somewhat_simple_degree_lowering() {
1✔
1345
        let builder = ConstraintCircuitBuilder::new();
1✔
1346
        let x = |i| builder.input(SingleRowIndicator::Main(i));
16✔
1347
        let y = |i| builder.input(SingleRowIndicator::Aux(i));
2✔
1348
        let b_con = |i: u64| builder.b_constant(i);
9✔
1349

1350
        let constraint_0 = x(0) * x(0) * (x(1) - x(2)) - x(0) * x(2) - b_con(42);
1✔
1351
        let constraint_1 = x(1) * (x(1) - b_con(5)) * x(2) * (x(2) - b_con(1));
1✔
1352
        let constraint_2 = y(0)
1✔
1353
            * (b_con(2) * x(0) + b_con(3) * x(1) + b_con(4) * x(2))
1✔
1354
            * (b_con(4) * x(0) + b_con(8) * x(1) + b_con(16) * x(2))
1✔
1355
            - y(1);
1✔
1356

1✔
1357
        let mut multicircuit = [constraint_0, constraint_1, constraint_2];
1✔
1358

1✔
1359
        let degree_lowering_info = DegreeLoweringInfo {
1✔
1360
            target_degree: 2,
1✔
1361
            num_main_cols: 3,
1✔
1362
            num_aux_cols: 2,
1✔
1363
        };
1✔
1364
        let (new_main_constraints, new_aux_constraints) =
1✔
1365
            ConstraintCircuitMonad::lower_to_degree(&mut multicircuit, degree_lowering_info);
1✔
1366

1✔
1367
        assert!(new_main_constraints.len() <= 3);
1✔
1368
        assert!(new_aux_constraints.len() <= 1);
1✔
1369
    }
1✔
1370

1371
    #[test]
1372
    fn less_simple_degree_lowering() {
1✔
1373
        let builder = ConstraintCircuitBuilder::new();
1✔
1374
        let x = |i| builder.input(SingleRowIndicator::Main(i));
11✔
1375

1376
        let constraint_0 = (x(0) * x(1) * x(2)) * (x(3) * x(4)) * x(5);
1✔
1377
        let constraint_1 = (x(6) * x(7)) * (x(3) * x(4)) * x(8);
1✔
1378

1✔
1379
        let mut multicircuit = [constraint_0, constraint_1];
1✔
1380

1✔
1381
        let degree_lowering_info = DegreeLoweringInfo {
1✔
1382
            target_degree: 3,
1✔
1383
            num_main_cols: 9,
1✔
1384
            num_aux_cols: 0,
1✔
1385
        };
1✔
1386
        let (new_main_constraints, new_aux_constraints) =
1✔
1387
            ConstraintCircuitMonad::lower_to_degree(&mut multicircuit, degree_lowering_info);
1✔
1388

1✔
1389
        assert!(new_main_constraints.len() <= 3);
1✔
1390
        assert!(new_aux_constraints.is_empty());
1✔
1391
    }
1✔
1392

1393
    #[test]
1394
    fn all_nodes_in_multicircuit_are_identified_correctly() {
1✔
1395
        let builder = ConstraintCircuitBuilder::new();
1✔
1396

1✔
1397
        let x = |i| builder.input(SingleRowIndicator::Main(i));
30✔
1398
        let b_con = |i: u64| builder.b_constant(i);
3✔
1399

1400
        let sub_tree_0 = x(0) * x(1) * (x(2) - b_con(1)) * x(3) * x(4);
1✔
1401
        let sub_tree_1 = x(0) * x(1) * (x(2) - b_con(1)) * x(3) * x(5);
1✔
1402
        let sub_tree_2 = x(10) * x(10) * x(2) * x(13);
1✔
1403
        let sub_tree_3 = x(10) * x(10) * x(2) * x(14);
1✔
1404

1✔
1405
        let circuit_0 = sub_tree_0.clone() + sub_tree_1.clone();
1✔
1406
        let circuit_1 = sub_tree_2.clone() + sub_tree_3.clone();
1✔
1407
        let circuit_2 = sub_tree_0 + sub_tree_2;
1✔
1408
        let circuit_3 = sub_tree_1 + sub_tree_3;
1✔
1409

1✔
1410
        let multicircuit = [circuit_0, circuit_1, circuit_2, circuit_3].map(|c| c.consume());
4✔
1411

1✔
1412
        let all_nodes = ConstraintCircuitMonad::all_nodes_in_multicircuit(&multicircuit);
1✔
1413
        let count_node = |node| all_nodes.iter().filter(|&n| n == &node).count();
532✔
1414

1415
        let x0 = x(0).consume();
1✔
1416
        assert_eq!(4, count_node(x0));
1✔
1417

1418
        let x2 = x(2).consume();
1✔
1419
        assert_eq!(8, count_node(x2));
1✔
1420

1421
        let x10 = x(10).consume();
1✔
1422
        assert_eq!(8, count_node(x10));
1✔
1423

1424
        let x4 = x(4).consume();
1✔
1425
        assert_eq!(2, count_node(x4));
1✔
1426

1427
        let x6 = x(6).consume();
1✔
1428
        assert_eq!(0, count_node(x6));
1✔
1429

1430
        let x0_x1 = (x(0) * x(1)).consume();
1✔
1431
        assert_eq!(4, count_node(x0_x1));
1✔
1432

1433
        let tree = (x(0) * x(1) * (x(2) - b_con(1))).consume();
1✔
1434
        assert_eq!(4, count_node(tree));
1✔
1435

1436
        let max_occurences = all_nodes
1✔
1437
            .iter()
1✔
1438
            .map(|node| all_nodes.iter().filter(|&n| n == node).count())
5,776✔
1439
            .max()
1✔
1440
            .unwrap();
1✔
1441
        assert_eq!(8, max_occurences);
1✔
1442

1443
        let most_frequent_nodes = all_nodes
1✔
1444
            .iter()
1✔
1445
            .filter(|&node| all_nodes.iter().filter(|&n| n == node).count() == max_occurences)
5,776✔
1446
            .unique()
1✔
1447
            .collect_vec();
1✔
1448
        assert_eq!(2, most_frequent_nodes.len());
1✔
1449
        assert!(most_frequent_nodes.contains(&&x(2).consume()));
1✔
1450
        assert!(most_frequent_nodes.contains(&&x(10).consume()));
1✔
1451
    }
1✔
1452

1453
    #[test]
1454
    fn equivalent_nodes_are_detected_when_present() {
1✔
1455
        let builder = ConstraintCircuitBuilder::new();
1✔
1456

1✔
1457
        let x = |i| builder.input(SingleRowIndicator::Main(i));
12✔
1458
        let ch = |i: usize| builder.challenge(i);
2✔
1459

1460
        let u0 = x(0) + x(1);
1✔
1461
        let u1 = x(2) + x(3);
1✔
1462
        let v = u0 * u1;
1✔
1463

1✔
1464
        let z0 = x(0) * x(2);
1✔
1465
        let z2 = x(1) * x(3);
1✔
1466

1✔
1467
        let z1 = x(1) * x(2) + x(0) * x(3);
1✔
1468
        let w = v - z0 - z2;
1✔
1469
        assert!(w.find_equivalent_nodes().is_empty());
1✔
1470

1471
        let o = ch(0) * z1 - ch(1) * w;
1✔
1472
        assert!(!o.find_equivalent_nodes().is_empty());
1✔
1473
    }
1✔
1474
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc