1use crate::{
4 FancyArithmetic, FancyBinary, FancyProj,
5 circuit::CircuitExecutor,
6 fancy::{Fancy, HasModulus},
7};
8use std::cmp::max;
9use swanky_channel::Channel;
10use swanky_error::{ErrorKind, Result};
11
12#[derive(Clone, Debug)]
18pub struct AnalyzerItem {
19 modulus: u16,
20 depth: usize,
21}
22
23impl AnalyzerItem {
24 pub fn new(modulus: u16) -> Self {
27 Self { modulus, depth: 0 }
28 }
29}
30
31impl HasModulus for AnalyzerItem {
32 fn modulus(&self) -> u16 {
33 self.modulus
34 }
35}
36
37#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
45pub struct CircuitAnalyzer {
46 ninputs: usize,
47 nconstants: usize,
48 nands: usize,
49 nxors: usize,
50 nnegs: usize,
51 nadds: usize,
52 nsubs: usize,
53 ncmuls: usize,
54 nmuls: usize,
55 mul_depth: usize,
56}
57
58impl std::fmt::Display for CircuitAnalyzer {
59 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
60 writeln!(f, "computation info:")?;
61 writeln!(f, " number of inputs: {:16}", self.ninputs)?;
62 writeln!(f, " number of constants: {:16}", self.nconstants)?;
63 writeln!(f, " number of additions: {:16}", self.nadds)?;
64 writeln!(f, " number of subtractions: {:16}", self.nsubs)?;
65 writeln!(f, " number of cmuls: {:16}", self.ncmuls)?;
66 writeln!(f, " number of muls: {:16}", self.nmuls)?;
67 writeln!(f, " number of ands: {:16}", self.nands)?;
68 writeln!(f, " number of xors: {:16}", self.nxors)?;
69 writeln!(f, " number of negations: {:16}", self.nnegs)?;
70 writeln!(
71 f,
72 " total number of arithmetic gates(ADD, SUB, MUL, CMUL): {:16}",
73 self.nadds + self.nsubs + self.ncmuls + self.nmuls
74 )?;
75 writeln!(
76 f,
77 " total number of boolean gates (AND, XOR): {:16}",
78 self.nands + self.nxors
79 )?;
80 writeln!(f, " multiplicative depth: {:16}", self.mul_depth)?;
81 Ok(())
82 }
83}
84
85impl CircuitAnalyzer {
86 pub fn new() -> CircuitAnalyzer {
89 Default::default()
90 }
91 pub fn nands(&self) -> usize {
93 self.nands
94 }
95 pub fn ninputs(&self) -> usize {
97 self.ninputs
98 }
99 pub fn nconstants(&self) -> usize {
101 self.nconstants
102 }
103 pub fn nxors(&self) -> usize {
105 self.nxors
106 }
107
108 pub fn eval<C: CircuitExecutor<CircuitAnalyzer>>(&mut self, circuit: &C) -> Result<()> {
110 Channel::with(std::io::empty(), |channel| {
111 let inputs = (0..circuit.ninputs())
112 .map(|i| self.receive(circuit.modulus(i), channel))
113 .collect::<Result<Vec<_>>>()?;
114 circuit.execute(self, &inputs, channel)?;
115 Ok(())
116 })
117 }
118}
119
120impl FancyBinary for CircuitAnalyzer {
121 fn xor(&mut self, x: &Self::Item, y: &Self::Item) -> Self::Item {
122 self.nxors += 1;
123 self.nadds += 1;
125 AnalyzerItem {
126 modulus: x.modulus,
127 depth: max(x.depth, y.depth),
129 }
130 }
131
132 fn and(&mut self, x: &Self::Item, y: &Self::Item, _: &mut Channel) -> Result<Self::Item> {
133 self.nands += 1;
134 self.nmuls += 1;
136 Ok(AnalyzerItem {
137 modulus: x.modulus,
138 depth: max(x.depth, y.depth) + 1,
140 })
141 }
142
143 fn negate(&mut self, x: &Self::Item) -> Self::Item {
144 self.nnegs += 1;
145
146 self.nconstants += 1;
148 self.nxors += 1;
149
150 self.nadds += 1;
152 AnalyzerItem {
153 modulus: x.modulus,
154 depth: x.depth,
156 }
157 }
158}
159
160impl FancyArithmetic for CircuitAnalyzer {
161 fn add(&mut self, x: &Self::Item, y: &Self::Item) -> Self::Item {
162 self.nadds += 1;
163 AnalyzerItem {
164 modulus: x.modulus,
165 depth: max(x.depth, y.depth),
166 }
167 }
168
169 fn sub(&mut self, x: &Self::Item, y: &Self::Item) -> Self::Item {
170 self.nsubs += 1;
171 AnalyzerItem {
172 modulus: x.modulus,
173 depth: max(x.depth, y.depth),
174 }
175 }
176
177 fn cmul(&mut self, x: &Self::Item, _y: u16) -> Self::Item {
178 self.ncmuls += 1;
179 AnalyzerItem {
180 modulus: x.modulus,
181 depth: x.depth,
182 }
183 }
184
185 fn mul(&mut self, x: &Self::Item, y: &Self::Item, _: &mut Channel) -> Result<Self::Item> {
186 self.nmuls += 1;
187 Ok(AnalyzerItem {
188 modulus: x.modulus,
189 depth: max(x.depth, y.depth) + 1,
190 })
191 }
192}
193
194impl FancyProj for CircuitAnalyzer {
195 fn proj(
196 &mut self,
197 _x: &Self::Item,
198 _q: u16,
199 _tt: Option<Vec<u16>>,
200 _: &mut Channel,
201 ) -> Result<Self::Item> {
202 swanky_error::bail!(
203 ErrorKind::UnsupportedError,
204 "Projection gates are unsupported"
205 )
206 }
207}
208
209impl Fancy for CircuitAnalyzer {
210 type Item = AnalyzerItem;
211
212 fn receive_many(&mut self, moduli: &[u16], _: &mut Channel) -> Result<Vec<Self::Item>> {
213 self.ninputs += moduli.len();
214 Ok(moduli
215 .iter()
216 .map(|q| AnalyzerItem {
217 modulus: *q,
218 depth: 0,
219 })
220 .collect())
221 }
222
223 fn encode_many(
224 &mut self,
225 _values: &[u16],
226 moduli: &[u16],
227 channel: &mut Channel,
228 ) -> Result<Vec<Self::Item>> {
229 self.receive_many(moduli, channel)
230 }
231
232 fn constant(&mut self, _val: u16, q: u16, _: &mut Channel) -> Result<Self::Item> {
233 self.nconstants += 1;
234 Ok(AnalyzerItem {
235 modulus: q,
236 depth: 0,
237 })
238 }
239
240 fn output(&mut self, x: &Self::Item, _: &mut Channel) -> Result<Option<u16>> {
241 self.mul_depth = max(self.mul_depth, x.depth);
242 Ok(None)
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249 use crate::circuit::circuits;
250
251 #[test]
252 fn single_and_gate_count_is_correct() {
253 let nbits = 64;
254 let test = circuits::binary_gadgets::TestBinaryAnd(nbits);
255 let mut analyzer = CircuitAnalyzer::new();
256 analyzer.eval(&test).unwrap();
257
258 assert_eq!(analyzer.ninputs, 128);
259 assert_eq!(analyzer.nands, 64);
260 assert_eq!(analyzer.nxors, 0);
261 assert_eq!(analyzer.nconstants, 0);
262 assert_eq!(analyzer.nnegs, 0);
263 }
264
265 #[test]
266 fn binary_addition_counts_are_correct() {
267 let nbits = 64;
268 let test = circuits::binary_gadgets::TestBinaryAddition(nbits);
269 let mut analyzer = CircuitAnalyzer::new();
270 analyzer.eval(&test).unwrap();
271
272 assert_eq!(analyzer.ninputs, 128);
273 assert_eq!(analyzer.nands, 64);
274 assert_eq!(analyzer.nxors, 64 * 4 - 3);
279 assert_eq!(analyzer.nconstants, 0);
280 assert_eq!(analyzer.nnegs, 0);
281 }
282
283 #[test]
284 fn binary_multiplication_counts_are_correct() {
285 let nbits = 64;
286 let test = circuits::binary_gadgets::TestBinaryMultiplication(nbits);
287 let mut analyzer = CircuitAnalyzer::new();
288 analyzer.eval(&test).unwrap();
289
290 assert_eq!(analyzer.ninputs, 128);
296 assert_eq!(analyzer.nands, (65..128).sum::<usize>() + 64 * 64);
297 assert_eq!(
298 analyzer.nxors,
299 (65..128).sum::<usize>() * 4 - (3 * (128 - 65))
300 );
301 assert_eq!(analyzer.nconstants, 64);
302 assert_eq!(analyzer.nnegs, 0);
303 }
304
305 #[test]
306 fn binary_twos_complement_counts_are_correct() {
307 let nbits = 64;
308 let test = circuits::binary_gadgets::TestBinaryTwosComplement(nbits);
309 let mut analyzer = CircuitAnalyzer::new();
310 analyzer.eval(&test).unwrap();
311
312 assert_eq!(analyzer.ninputs, 64);
313 assert_eq!(analyzer.nands, 63);
314 assert_eq!(analyzer.nxors, 63 * 4 - 3 + 64 + 2);
315 assert_eq!(analyzer.nconstants, 64 + 64);
316 assert_eq!(analyzer.nnegs, 64);
317 }
318}