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}