1use std::fmt;
6
7use binius_core::{ConstraintSystem, Operand, ShiftedValueIndex};
8
9use crate::compiler::circuit::Circuit;
10
11pub struct CircuitStat {
13 pub n_gates: usize,
15 pub n_eval_insn: usize,
19 pub n_and_constraints: usize,
23 pub n_mul_constraints: usize,
27 pub distinct_shifted_value_indices: usize,
33 pub distinct_unshifted_value_indices: usize,
37 pub value_vec_len: usize,
41 pub n_const: usize,
43 pub n_inout: usize,
45 pub n_witness: usize,
47 pub n_internal: usize,
51 pub n_scratch: usize,
55 pub and_allocated: usize,
57 pub mul_allocated: usize,
59 pub public_allocated: usize,
61 pub private_allocated: usize,
69}
70
71impl CircuitStat {
72 pub fn collect(circuit: &Circuit) -> Self {
74 let mut cs = circuit.constraint_system().clone();
76
77 let n_and_constraints = cs.n_and_constraints();
79 let n_mul_constraints = cs.n_mul_constraints();
80 let (distinct_shifted_value_indices, distinct_unshifted_value_indices) =
81 traverse_constraint_system(&cs);
82
83 cs.validate_and_prepare()
85 .expect("constraint system should be valid");
86
87 let and_allocated = cs.n_and_constraints();
89 let mul_allocated = cs.n_mul_constraints();
90
91 let n_const = cs.value_vec_layout.n_const;
93 let n_inout = cs.value_vec_layout.n_inout;
94 let public_allocated = cs.value_vec_layout.offset_witness;
95 let total_allocated = cs.value_vec_layout.committed_total_len;
96 let private_allocated = total_allocated - public_allocated;
97
98 Self {
99 n_gates: circuit.n_gates(),
100 n_eval_insn: circuit.n_eval_insn(),
101 n_and_constraints,
102 n_mul_constraints,
103 value_vec_len: total_allocated,
104 distinct_shifted_value_indices,
105 distinct_unshifted_value_indices,
106 n_const,
107 n_inout,
108 n_witness: cs.value_vec_layout.n_witness,
109 n_internal: cs.value_vec_layout.n_internal,
110 n_scratch: cs.value_vec_layout.n_scratch,
111 and_allocated,
112 mul_allocated,
113 public_allocated,
114 private_allocated,
115 }
116 }
117}
118
119impl fmt::Display for CircuitStat {
120 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
121 fn fmt_num(n: usize) -> String {
123 let s = n.to_string();
124 let mut result = String::new();
125 for (i, c) in s.chars().rev().enumerate() {
126 if i > 0 && i % 3 == 0 {
127 result.push(',');
128 }
129 result.push(c);
130 }
131 result.chars().rev().collect()
132 }
133
134 fn progress_bar(used: usize, total: usize) -> String {
136 let percent = (used as f64 / total as f64 * 100.0) as usize;
137 let filled = percent / 10;
138 let mut bar = String::from("[");
139 for i in 0..10 {
140 if i < filled {
141 bar.push('▓');
142 } else {
143 bar.push('░');
144 }
145 }
146 bar.push(']');
147 bar
148 }
149
150 fn log2(n: usize) -> u32 {
152 n.trailing_zeros()
153 }
154
155 let public_used = self.n_const + self.n_inout;
157 let private_used = self.n_witness + self.n_internal;
158 let total_used = public_used + private_used;
159
160 writeln!(f, "Gates & Instructions")?;
162 writeln!(f, "├─ Number of gates: {}", fmt_num(self.n_gates))?;
163 writeln!(f, "└─ Number of evaluation instructions: {}", fmt_num(self.n_eval_insn))?;
164 writeln!(f)?;
165
166 writeln!(f, "Constraints")?;
168 let and_percent = self.n_and_constraints as f64 / self.and_allocated as f64 * 100.0;
169 let and_spare = self.and_allocated - self.n_and_constraints;
170 writeln!(
171 f,
172 "├─ AND constraints: {} used ({:.1}% of 2^{})",
173 fmt_num(self.n_and_constraints),
174 and_percent,
175 log2(self.and_allocated)
176 )?;
177 writeln!(
178 f,
179 "│ {} spare: {}",
180 progress_bar(self.n_and_constraints, self.and_allocated),
181 fmt_num(and_spare)
182 )?;
183
184 let mul_percent = if self.mul_allocated > 0 {
185 self.n_mul_constraints as f64 / self.mul_allocated as f64 * 100.0
186 } else {
187 0.0
188 };
189 let mul_spare = self.mul_allocated - self.n_mul_constraints;
190 writeln!(
191 f,
192 "├─ MUL constraints: {} used ({:.1}% of 2^{})",
193 fmt_num(self.n_mul_constraints),
194 mul_percent,
195 log2(self.mul_allocated)
196 )?;
197 writeln!(
198 f,
199 "│ {} spare: {}",
200 progress_bar(self.n_mul_constraints, self.mul_allocated),
201 fmt_num(mul_spare)
202 )?;
203 writeln!(
204 f,
205 "└─ Distinct value indices: {}",
206 fmt_num(self.distinct_shifted_value_indices + self.distinct_unshifted_value_indices)
207 )?;
208 writeln!(
209 f,
210 " ├─ Distinct shifted value indices: {}",
211 fmt_num(self.distinct_shifted_value_indices)
212 )?;
213 writeln!(
214 f,
215 " └─ Distinct unshifted value indices: {}",
216 fmt_num(self.distinct_unshifted_value_indices)
217 )?;
218 writeln!(f)?;
219
220 writeln!(f, "Value Vector")?;
222
223 let public_percent = public_used as f64 / self.public_allocated as f64 * 100.0;
225 let public_spare = self.public_allocated - public_used;
226 writeln!(
227 f,
228 "├─ Public Section: {} used ({:.1}% of 2^{})",
229 fmt_num(public_used),
230 public_percent,
231 log2(self.public_allocated)
232 )?;
233 writeln!(
234 f,
235 "│ {} spare: {}",
236 progress_bar(public_used, self.public_allocated),
237 fmt_num(public_spare)
238 )?;
239 writeln!(f, "│ ├─ Constants: {}", fmt_num(self.n_const))?;
240 writeln!(f, "│ └─ Inout: {}", fmt_num(self.n_inout))?;
241
242 let private_percent = private_used as f64 / self.private_allocated as f64 * 100.0;
244 let private_spare = self.private_allocated - private_used;
245 writeln!(
246 f,
247 "├─ Private Section: {} used ({:.1}%)",
248 fmt_num(private_used),
249 private_percent
250 )?;
251 writeln!(
252 f,
253 "│ {} spare: {}",
254 progress_bar(private_used, self.private_allocated),
255 fmt_num(private_spare)
256 )?;
257 writeln!(f, "│ ├─ Witness: {}", fmt_num(self.n_witness))?;
258 writeln!(f, "│ └─ Internal: {}", fmt_num(self.n_internal))?;
259
260 let total_percent = total_used as f64 / self.value_vec_len as f64 * 100.0;
262 let total_spare = self.value_vec_len - total_used;
263 writeln!(
264 f,
265 "├─ Total Committed: {} used ({:.1}% of 2^{})",
266 fmt_num(total_used),
267 total_percent,
268 log2(self.value_vec_len)
269 )?;
270 writeln!(
271 f,
272 "│ {} spare: {}",
273 progress_bar(total_used, self.value_vec_len),
274 fmt_num(total_spare)
275 )?;
276
277 writeln!(f, "└─ Scratch (uncommitted): {}", fmt_num(self.n_scratch))?;
279 writeln!(f)?;
280
281 Ok(())
282 }
283}
284
285fn traverse_constraint_system(cs: &ConstraintSystem) -> (usize, usize) {
288 use std::collections::HashSet;
289 let mut cx = Cx {
290 shifted_terms: HashSet::new(),
291 unshifted_terms: HashSet::new(),
292 };
293 for and in &cs.and_constraints {
294 visit_operand(&and.a, &mut cx);
295 visit_operand(&and.b, &mut cx);
296 visit_operand(&and.c, &mut cx);
297 }
298 for mul in &cs.mul_constraints {
299 visit_operand(&mul.a, &mut cx);
300 visit_operand(&mul.b, &mut cx);
301 visit_operand(&mul.lo, &mut cx);
302 visit_operand(&mul.hi, &mut cx);
303 }
304 return (cx.shifted_terms.len(), cx.unshifted_terms.len());
305
306 struct Cx {
307 shifted_terms: HashSet<ShiftedValueIndex>,
308 unshifted_terms: HashSet<ShiftedValueIndex>,
309 }
310
311 fn visit_operand(operand: &Operand, cx: &mut Cx) {
312 for term in operand {
313 if term.amount == 0 {
314 cx.unshifted_terms.insert(*term);
315 } else {
316 cx.shifted_terms.insert(*term);
317 }
318 }
319 }
320}