halo2_proofs/dev/
gates.rs

1use std::{
2    collections::BTreeSet,
3    fmt::{self, Write},
4};
5
6use ff::PrimeField;
7
8use crate::{
9    dev::util,
10    plonk::{Circuit, ConstraintSystem},
11};
12
13#[derive(Debug)]
14struct Constraint {
15    name: &'static str,
16    expression: String,
17    queries: BTreeSet<String>,
18}
19
20#[derive(Debug)]
21struct Gate {
22    name: &'static str,
23    constraints: Vec<Constraint>,
24}
25
26/// A struct for collecting and displaying the gates within a circuit.
27///
28/// # Examples
29///
30/// ```
31/// use ff::Field;
32/// use halo2_proofs::{
33///     circuit::{Layouter, SimpleFloorPlanner},
34///     dev::CircuitGates,
35///     plonk::{Circuit, ConstraintSystem, Error},
36///     poly::Rotation,
37/// };
38/// use pasta_curves::pallas;
39///
40/// #[derive(Copy, Clone)]
41/// struct MyConfig {}
42///
43/// #[derive(Clone, Default)]
44/// struct MyCircuit {}
45///
46/// impl<F: Field> Circuit<F> for MyCircuit {
47///     type Config = MyConfig;
48///     type FloorPlanner = SimpleFloorPlanner;
49///
50///     fn without_witnesses(&self) -> Self {
51///         Self::default()
52///     }
53///
54///     fn configure(meta: &mut ConstraintSystem<F>) -> MyConfig {
55///         let a = meta.advice_column();
56///         let b = meta.advice_column();
57///         let c = meta.advice_column();
58///         let s = meta.selector();
59///
60///         meta.create_gate("R1CS constraint", |meta| {
61///             let a = meta.query_advice(a, Rotation::cur());
62///             let b = meta.query_advice(b, Rotation::cur());
63///             let c = meta.query_advice(c, Rotation::cur());
64///             let s = meta.query_selector(s);
65///
66///             Some(("R1CS", s * (a * b - c)))
67///         });
68///
69///         // We aren't using this circuit for anything in this example.
70///         MyConfig {}
71///     }
72///
73///     fn synthesize(&self, _: MyConfig, _: impl Layouter<F>) -> Result<(), Error> {
74///         // Gates are known at configure time; it doesn't matter how we use them.
75///         Ok(())
76///     }
77/// }
78///
79/// let gates = CircuitGates::collect::<pallas::Base, MyCircuit>();
80/// assert_eq!(
81///     format!("{}", gates),
82///     r#####"R1CS constraint:
83/// - R1CS:
84///   S0 * (A0@0 * A1@0 - A2@0)
85/// Total gates: 1
86/// Total custom constraint polynomials: 1
87/// Total negations: 1
88/// Total additions: 1
89/// Total multiplications: 2
90/// "#####,
91/// );
92/// ```
93#[derive(Debug)]
94pub struct CircuitGates {
95    gates: Vec<Gate>,
96    total_negations: usize,
97    total_additions: usize,
98    total_multiplications: usize,
99}
100
101impl CircuitGates {
102    /// Collects the gates from within the circuit.
103    pub fn collect<F: PrimeField, C: Circuit<F>>() -> Self {
104        // Collect the graph details.
105        let mut cs = ConstraintSystem::default();
106        let _ = C::configure(&mut cs);
107
108        let gates = cs
109            .gates
110            .iter()
111            .map(|gate| Gate {
112                name: gate.name(),
113                constraints: gate
114                    .polynomials()
115                    .iter()
116                    .enumerate()
117                    .map(|(i, constraint)| Constraint {
118                        name: gate.constraint_name(i),
119                        expression: constraint.evaluate(
120                            &util::format_value,
121                            &|selector| format!("S{}", selector.0),
122                            &|_, column, rotation| format!("F{}@{}", column, rotation.0),
123                            &|_, column, rotation| format!("A{}@{}", column, rotation.0),
124                            &|_, column, rotation| format!("I{}@{}", column, rotation.0),
125                            &|a| {
126                                if a.contains(' ') {
127                                    format!("-({})", a)
128                                } else {
129                                    format!("-{}", a)
130                                }
131                            },
132                            &|a, b| {
133                                if let Some(b) = b.strip_prefix('-') {
134                                    format!("{} - {}", a, b)
135                                } else {
136                                    format!("{} + {}", a, b)
137                                }
138                            },
139                            &|a, b| match (a.contains(' '), b.contains(' ')) {
140                                (false, false) => format!("{} * {}", a, b),
141                                (false, true) => format!("{} * ({})", a, b),
142                                (true, false) => format!("({}) * {}", a, b),
143                                (true, true) => format!("({}) * ({})", a, b),
144                            },
145                            &|a, s| {
146                                if a.contains(' ') {
147                                    format!("({}) * {}", a, util::format_value(s))
148                                } else {
149                                    format!("{} * {}", a, util::format_value(s))
150                                }
151                            },
152                        ),
153                        queries: constraint.evaluate(
154                            &|_| BTreeSet::default(),
155                            &|selector| vec![format!("S{}", selector.0)].into_iter().collect(),
156                            &|_, column, rotation| {
157                                vec![format!("F{}@{}", column, rotation.0)]
158                                    .into_iter()
159                                    .collect()
160                            },
161                            &|_, column, rotation| {
162                                vec![format!("A{}@{}", column, rotation.0)]
163                                    .into_iter()
164                                    .collect()
165                            },
166                            &|_, column, rotation| {
167                                vec![format!("I{}@{}", column, rotation.0)]
168                                    .into_iter()
169                                    .collect()
170                            },
171                            &|a| a,
172                            &|mut a, mut b| {
173                                a.append(&mut b);
174                                a
175                            },
176                            &|mut a, mut b| {
177                                a.append(&mut b);
178                                a
179                            },
180                            &|a, _| a,
181                        ),
182                    })
183                    .collect(),
184            })
185            .collect();
186
187        let (total_negations, total_additions, total_multiplications) = cs
188            .gates
189            .iter()
190            .flat_map(|gate| {
191                gate.polynomials().iter().map(|poly| {
192                    poly.evaluate(
193                        &|_| (0, 0, 0),
194                        &|_| (0, 0, 0),
195                        &|_, _, _| (0, 0, 0),
196                        &|_, _, _| (0, 0, 0),
197                        &|_, _, _| (0, 0, 0),
198                        &|(a_n, a_a, a_m)| (a_n + 1, a_a, a_m),
199                        &|(a_n, a_a, a_m), (b_n, b_a, b_m)| (a_n + b_n, a_a + b_a + 1, a_m + b_m),
200                        &|(a_n, a_a, a_m), (b_n, b_a, b_m)| (a_n + b_n, a_a + b_a, a_m + b_m + 1),
201                        &|(a_n, a_a, a_m), _| (a_n, a_a, a_m + 1),
202                    )
203                })
204            })
205            .fold((0, 0, 0), |(acc_n, acc_a, acc_m), (n, a, m)| {
206                (acc_n + n, acc_a + a, acc_m + m)
207            });
208
209        CircuitGates {
210            gates,
211            total_negations,
212            total_additions,
213            total_multiplications,
214        }
215    }
216
217    /// Prints the queries in this circuit to a CSV grid.
218    pub fn queries_to_csv(&self) -> String {
219        let mut queries = BTreeSet::new();
220        for gate in &self.gates {
221            for constraint in &gate.constraints {
222                for query in &constraint.queries {
223                    queries.insert(query);
224                }
225            }
226        }
227
228        let mut ret = String::new();
229        let w = &mut ret;
230        for query in &queries {
231            write!(w, "{},", query).unwrap();
232        }
233        writeln!(w, "Name").unwrap();
234
235        for gate in &self.gates {
236            for constraint in &gate.constraints {
237                for query in &queries {
238                    if constraint.queries.contains(*query) {
239                        write!(w, "1").unwrap();
240                    } else {
241                        write!(w, "0").unwrap();
242                    }
243                    write!(w, ",").unwrap();
244                }
245                writeln!(w, "{}/{}", gate.name, constraint.name).unwrap();
246            }
247        }
248        ret
249    }
250}
251
252impl fmt::Display for CircuitGates {
253    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
254        for gate in &self.gates {
255            writeln!(f, "{}:", gate.name)?;
256            for constraint in &gate.constraints {
257                if constraint.name.is_empty() {
258                    writeln!(f, "- {}", constraint.expression)?;
259                } else {
260                    writeln!(f, "- {}:", constraint.name)?;
261                    writeln!(f, "  {}", constraint.expression)?;
262                }
263            }
264        }
265        writeln!(f, "Total gates: {}", self.gates.len())?;
266        writeln!(
267            f,
268            "Total custom constraint polynomials: {}",
269            self.gates
270                .iter()
271                .map(|gate| gate.constraints.len())
272                .sum::<usize>()
273        )?;
274        writeln!(f, "Total negations: {}", self.total_negations)?;
275        writeln!(f, "Total additions: {}", self.total_additions)?;
276        writeln!(f, "Total multiplications: {}", self.total_multiplications)
277    }
278}