binius_frontend/artifact/
stat.rs1use std::fmt;
7
8use binius_core::{ConstraintSystem, InoutSegment, Operand, ShiftedValueIndex};
9use itertools::chain;
10use rustc_hash::FxHashSet;
11
12use crate::artifact::circuit::Circuit;
13
14pub struct CircuitStat {
16 pub n_gates: usize,
18 pub n_eval_insn: usize,
22 pub n_zero_constraints: usize,
27 pub n_and_constraints: usize,
31 pub n_imul_constraints: usize,
35 pub n_bmul_constraints: usize,
39 pub distinct_shifted_value_indices: usize,
45 pub distinct_unshifted_value_indices: usize,
49 pub committed_allocated: usize,
53 pub n_const: usize,
55 pub n_inout: usize,
57 pub n_witness: usize,
59 pub n_internal: usize,
63 pub n_scratch: usize,
67 pub scratch_peak_live: usize,
72 pub zero_allocated: usize,
74 pub and_allocated: usize,
76 pub imul_allocated: usize,
78 pub bmul_allocated: usize,
80}
81
82impl CircuitStat {
83 pub fn collect(circuit: &Circuit) -> Self {
85 let cs = circuit.constraint_system();
86
87 let n_zero_constraints = cs.n_zero_constraints();
89 let n_and_constraints = cs.n_and_constraints();
90 let n_imul_constraints = cs.n_imul_constraints();
91 let n_bmul_constraints = cs.n_bmul_constraints();
92 let (distinct_shifted_value_indices, distinct_unshifted_value_indices) =
93 traverse_constraint_system(cs);
94
95 let pad = |n: usize| n.next_power_of_two();
102 let pad_or_skip = |n: usize| if n == 0 { 0 } else { pad(n) };
103 let zero_allocated = pad_or_skip(n_zero_constraints);
104 let and_allocated = pad(n_and_constraints);
105 let imul_allocated = pad_or_skip(n_imul_constraints);
106 let bmul_allocated = pad_or_skip(n_bmul_constraints);
107
108 let layout = circuit.value_vec_layout();
111 let n_const = layout.n_const;
112 let n_inout = layout.n_inout;
113 let committed_allocated = 1 << cs.log_segment_words(InoutSegment::Public);
116
117 Self {
118 n_gates: circuit.n_gates(),
119 n_eval_insn: circuit.n_eval_insn(),
120 n_zero_constraints,
121 n_and_constraints,
122 n_imul_constraints,
123 n_bmul_constraints,
124 committed_allocated,
125 distinct_shifted_value_indices,
126 distinct_unshifted_value_indices,
127 n_const,
128 n_inout,
129 n_witness: layout.n_witness,
130 n_internal: layout.n_internal,
131 n_scratch: layout.n_scratch,
132 scratch_peak_live: circuit.scratch_peak_live(),
133 zero_allocated,
134 and_allocated,
135 imul_allocated,
136 bmul_allocated,
137 }
138 }
139}
140
141impl fmt::Display for CircuitStat {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 fn fmt_num(n: usize) -> String {
145 let s = n.to_string();
146 let mut result = String::new();
147 for (i, c) in s.chars().rev().enumerate() {
148 if i > 0 && i % 3 == 0 {
149 result.push(',');
150 }
151 result.push(c);
152 }
153 result.chars().rev().collect()
154 }
155
156 fn progress_bar(used: usize, total: usize) -> String {
158 let percent = (used as f64 / total as f64 * 100.0) as usize;
159 let filled = percent / 10;
160 let mut bar = String::from("[");
161 for i in 0..10 {
162 if i < filled {
163 bar.push('▓');
164 } else {
165 bar.push('░');
166 }
167 }
168 bar.push(']');
169 bar
170 }
171
172 const fn log2(n: usize) -> u32 {
174 n.trailing_zeros()
175 }
176
177 let public_used = self.n_const + self.n_inout;
179 let private_used = self.n_witness + self.n_internal;
180
181 writeln!(f, "Gates & Instructions")?;
183 writeln!(f, "├─ Number of gates: {}", fmt_num(self.n_gates))?;
184 writeln!(f, "└─ Number of evaluation instructions: {}", fmt_num(self.n_eval_insn))?;
185 writeln!(f)?;
186
187 fn constraint_line(
192 f: &mut fmt::Formatter<'_>,
193 name: &str,
194 used: usize,
195 allocated: usize,
196 ) -> fmt::Result {
197 let percent = if allocated > 0 {
198 used as f64 / allocated as f64 * 100.0
199 } else {
200 0.0
201 };
202 let allocation = if allocated == 0 {
203 "0".to_string()
204 } else {
205 format!("2^{}", log2(allocated))
206 };
207 writeln!(
208 f,
209 "├─ {name} constraints: {} used ({percent:.1}% of {allocation})",
210 fmt_num(used)
211 )?;
212 writeln!(f, "│ {} spare: {}", progress_bar(used, allocated), fmt_num(allocated - used))
213 }
214
215 writeln!(f, "Constraints")?;
217 constraint_line(f, "ZERO", self.n_zero_constraints, self.zero_allocated)?;
218 constraint_line(f, "AND", self.n_and_constraints, self.and_allocated)?;
219 constraint_line(f, "IMUL", self.n_imul_constraints, self.imul_allocated)?;
220 constraint_line(f, "BMUL", self.n_bmul_constraints, self.bmul_allocated)?;
221 writeln!(
222 f,
223 "└─ Distinct value indices: {}",
224 fmt_num(self.distinct_shifted_value_indices + self.distinct_unshifted_value_indices)
225 )?;
226 writeln!(
227 f,
228 " ├─ Distinct shifted value indices: {}",
229 fmt_num(self.distinct_shifted_value_indices)
230 )?;
231 writeln!(
232 f,
233 " └─ Distinct unshifted value indices: {}",
234 fmt_num(self.distinct_unshifted_value_indices)
235 )?;
236 writeln!(f)?;
237
238 writeln!(f, "Value Vector")?;
240
241 writeln!(f, "├─ Public Section: {} (not committed)", fmt_num(public_used))?;
244 writeln!(f, "│ ├─ Constants: {}", fmt_num(self.n_const))?;
245 writeln!(f, "│ └─ Inout: {}", fmt_num(self.n_inout))?;
246
247 writeln!(f, "├─ Private Section: {}", fmt_num(private_used))?;
249 writeln!(f, "│ ├─ Witness: {}", fmt_num(self.n_witness))?;
250 writeln!(f, "│ └─ Internal: {}", fmt_num(self.n_internal))?;
251
252 let committed_percent = private_used as f64 / self.committed_allocated as f64 * 100.0;
256 let committed_spare = self.committed_allocated - private_used;
257 writeln!(
258 f,
259 "├─ Committed Trace: {} used ({:.1}% of 2^{})",
260 fmt_num(private_used),
261 committed_percent,
262 log2(self.committed_allocated)
263 )?;
264 writeln!(
265 f,
266 "│ {} spare: {}",
267 progress_bar(private_used, self.committed_allocated),
268 fmt_num(committed_spare)
269 )?;
270
271 writeln!(
274 f,
275 "└─ Scratch (uncommitted): {} (peak live: {})",
276 fmt_num(self.n_scratch),
277 fmt_num(self.scratch_peak_live)
278 )?;
279 writeln!(f)?;
280
281 Ok(())
282 }
283}
284
285fn traverse_constraint_system(cs: &ConstraintSystem) -> (usize, usize) {
288 let mut cx = Cx::default();
289 let operands = chain!(
290 cs.zero_constraints.iter().flat_map(|c| &c.0),
291 cs.and_constraints.iter().flat_map(|c| &c.0),
292 cs.imul_constraints.iter().flat_map(|c| &c.0),
293 cs.bmul_constraints.iter().flat_map(|c| &c.0),
294 );
295 for operand in operands {
296 cx.visit_operand(operand);
297 }
298 (cx.shifted_terms.len(), cx.unshifted_terms.len())
299}
300
301#[derive(Default)]
303struct Cx {
304 shifted_terms: FxHashSet<ShiftedValueIndex>,
305 unshifted_terms: FxHashSet<ShiftedValueIndex>,
306}
307
308impl Cx {
309 fn visit_operand(&mut self, operand: &Operand) {
311 for term in operand {
312 if term.is_unshifted() {
313 self.unshifted_terms.insert(*term);
314 } else {
315 self.shifted_terms.insert(*term);
316 }
317 }
318 }
319}