1use super::{Circuit, Gate};
12use std::fs::File;
13use std::io::Write;
14use std::path::Path;
15
16pub struct Printer<'a> {
22 circuit: &'a Circuit,
23 diagram: Option<String>,
24 disable_warnings: bool,
25}
26
27struct DiagramSchema<'a> {
28 longest_name_length: usize,
29 gate_info_column: Vec<GatePrinterInfo<'a>>,
30}
31
32#[derive(Clone)]
33struct RowSchematic {
34 top: String,
35 name: String,
36 bottom: String,
37 connection: String,
38}
39
40#[derive(Clone)]
41struct GatePrinterInfo<'a> {
42 gate_name: String,
43 gate_name_length: usize,
44 gate: &'a Gate,
45}
46
47#[derive(Debug)]
48struct Extrema {
49 pub max: usize,
50 pub min: usize,
51}
52
53impl Printer<'_> {
54 pub fn new<'circ>(circuit: &'circ Circuit) -> Printer<'circ> {
56 Printer {
57 circuit,
58 diagram: None,
59 disable_warnings: false,
60 }
61 }
62
63 pub fn print_diagram(&mut self) {
86 if self.circuit.circuit_gates.len() / self.circuit.num_qubits > 14 && !self.disable_warnings
87 {
88 eprintln!("\x1b[93m[Quantr Warning] The string displaying the circuit diagram exceeds 72 chars, which could cause the circuit to render incorrectly in terminals (due to the wrapping). Instead, consider saving the string to a .txt file by using Printer::save_diagram.\x1b[0m");
89 }
90 println!("{}", self.get_or_make_diagram());
91 }
92
93 pub fn save_diagram(&mut self, file_path: &str) -> std::io::Result<()> {
110 let path: &Path = Path::new(file_path);
111 let mut file = File::create(path)?;
112 file.write_all(self.get_or_make_diagram().as_bytes())
113 }
114
115 pub fn print_and_save_diagram(&mut self, file_path: &str) -> std::io::Result<()> {
132 let diagram: String = self.get_or_make_diagram();
133
134 println!("{}", diagram);
135
136 let path = Path::new(file_path);
137 let mut file = File::create(path)?;
138 file.write_all(diagram.as_bytes())
139 }
140
141 pub fn get_diagram(&mut self) -> String {
154 self.get_or_make_diagram()
155 }
156
157 pub fn set_warnings(&mut self, printing: bool) {
159 self.disable_warnings = printing;
160 }
161
162 fn get_or_make_diagram(&mut self) -> String {
164 match &self.diagram {
165 Some(diagram) => diagram.to_string(),
166 None => self.make_diagram(),
167 }
168 }
169
170 fn make_diagram(&mut self) -> String {
171 let number_of_columns: usize = self.circuit.circuit_gates.len() / self.circuit.num_qubits;
173 let mut printed_diagram: Vec<String> =
174 vec!["".to_string(); 4 * self.circuit.num_qubits + 1];
175
176 for column_num in 0..number_of_columns {
177 let (gate_info_column, longest_name_length): (Vec<GatePrinterInfo>, usize) =
179 Self::into_printer_gate_info(self.get_column_of_gates(column_num));
180
181 let diagram_schematic = DiagramSchema {
182 longest_name_length,
183 gate_info_column,
184 };
185
186 if let Some((position, multi_gate_info)) =
187 Self::get_multi_gate(&diagram_schematic.gate_info_column)
188 {
189 Self::draw_multi_gates(
191 &mut printed_diagram,
192 multi_gate_info,
193 &self.circuit.num_qubits,
194 position,
195 );
196 } else {
197 Self::draw_single_gates(printed_diagram.as_mut_slice(), diagram_schematic);
199 }
200 }
201
202 let final_diagram = printed_diagram
204 .into_iter()
205 .fold(String::from(""), |acc, line| acc + &line + "\n");
206
207 self.diagram = Some(final_diagram.clone());
208
209 final_diagram
210 }
211
212 fn get_column_of_gates(&self, column_num: usize) -> &[Gate] {
213 &self.circuit.circuit_gates
214 [column_num * self.circuit.num_qubits..(column_num + 1) * self.circuit.num_qubits]
215 }
216
217 fn into_printer_gate_info(gates_column: &[Gate]) -> (Vec<GatePrinterInfo>, usize) {
218 let mut gates_infos: Vec<GatePrinterInfo> = Default::default();
219 let mut longest_name_length: usize = 1usize;
220 for gate in gates_column.iter() {
221 let gate_name: String = gate.get_name();
222 let gate_name_length: usize = gate_name.len();
223 if gate_name_length > longest_name_length {
224 longest_name_length = gate_name_length;
225 }
226 gates_infos.push(GatePrinterInfo {
227 gate_name,
228 gate_name_length,
229 gate,
230 })
231 }
232 (gates_infos, longest_name_length)
233 }
234
235 fn get_multi_gate<'gate>(
237 gates: &[GatePrinterInfo<'gate>],
238 ) -> Option<(usize, GatePrinterInfo<'gate>)> {
239 for (pos, gate_info) in gates.iter().enumerate() {
240 if !gate_info.gate.is_single_gate() {
241 return Some((pos, gate_info.clone()));
242 }
243 }
244 None
245 }
246
247 fn draw_single_gates(row_schematics: &mut [String], diagram_scheme: DiagramSchema) {
249 for (pos, gate_info) in diagram_scheme.gate_info_column.iter().enumerate() {
250 let padding: usize = diagram_scheme.longest_name_length - gate_info.gate_name_length;
251 let cache: RowSchematic = match gate_info.gate {
252 Gate::Id => RowSchematic {
253 top: " ".repeat(diagram_scheme.longest_name_length + 4),
254 name: "─".repeat(diagram_scheme.longest_name_length + 4),
255 bottom: " ".repeat(diagram_scheme.longest_name_length + 4),
256 connection: " ".repeat(diagram_scheme.longest_name_length + 4),
257 },
258 _ => RowSchematic {
259 top: "┏━".to_string()
260 + &"━".repeat(gate_info.gate_name_length)
261 + "━┓"
262 + &" ".repeat(padding),
263 name: "┨ ".to_string() + &gate_info.gate_name + " ┠" + &"─".repeat(padding),
264 bottom: "┗━".to_string()
265 + &"━".repeat(gate_info.gate_name_length)
266 + "━┛"
267 + &" ".repeat(padding),
268 connection: " ".repeat(diagram_scheme.longest_name_length + 4),
269 },
270 };
271 Self::add_string_to_schematic(row_schematics, pos, cache)
272 }
273 }
274
275 fn draw_multi_gates(
277 row_schematics: &mut [String],
278 multi_gate_info: GatePrinterInfo<'_>,
279 column_size: &usize,
280 position: usize,
281 ) {
282 let mut control_nodes: Vec<usize> = multi_gate_info
283 .gate
284 .get_nodes()
285 .expect("Single gate in drawing multi gate.");
286 control_nodes.push(position);
287
288 let (min, max): (usize, usize) = (
289 *control_nodes.iter().min().unwrap(),
290 *control_nodes.iter().max().unwrap(),
291 );
292
293 let extreme_nodes: Extrema = Extrema { max, min };
294
295 for row in 0..*column_size {
296 let cache: RowSchematic = if row == position {
297 RowSchematic {
298 top: "┏━".to_string()
299 + if position > extreme_nodes.min {
300 "┷"
301 } else {
302 "━"
303 }
304 + &"━".repeat(multi_gate_info.gate_name_length - 1)
305 + "━┓",
306 name: "┨ ".to_string() + &multi_gate_info.gate_name + " ┠",
307 bottom: "┗━".to_string()
308 + if position < extreme_nodes.max {
309 "┯"
310 } else {
311 "━"
312 }
313 + &"━".repeat(multi_gate_info.gate_name_length - 1)
314 + "━┛",
315 connection: " ".to_string()
316 + if position < extreme_nodes.max {
317 "│"
318 } else {
319 " "
320 }
321 + &" ".repeat(multi_gate_info.gate_name_length + 1),
322 }
323 } else if row == extreme_nodes.min {
324 RowSchematic {
325 top: " ".repeat(multi_gate_info.gate_name_length + 4),
326 name: "──█──".to_string() + &"─".repeat(multi_gate_info.gate_name_length - 1),
327 bottom: " │ ".to_string() + &" ".repeat(multi_gate_info.gate_name_length - 1),
328 connection: " │ ".to_string()
329 + &" ".repeat(multi_gate_info.gate_name_length - 1),
330 }
331 } else if row == extreme_nodes.max {
332 RowSchematic {
333 top: " │ ".to_string() + &" ".repeat(multi_gate_info.gate_name_length - 1),
334 name: "──█──".to_string() + &"─".repeat(multi_gate_info.gate_name_length - 1),
335 bottom: " ".repeat(multi_gate_info.gate_name_length + 4),
336 connection: " ".repeat(multi_gate_info.gate_name_length + 4),
337 }
338 } else if control_nodes.contains(&row) {
339 RowSchematic {
340 top: " │ ".to_string() + &" ".repeat(multi_gate_info.gate_name_length - 1),
341 name: "──█──".to_string() + &"─".repeat(multi_gate_info.gate_name_length - 1),
342 bottom: " │ ".to_string() + &" ".repeat(multi_gate_info.gate_name_length - 1),
343 connection: " │ ".to_string()
344 + &" ".repeat(multi_gate_info.gate_name_length - 1),
345 }
346 } else if (extreme_nodes.min..=extreme_nodes.max).contains(&row) {
347 RowSchematic {
348 top: " │ ".to_string() + &" ".repeat(multi_gate_info.gate_name_length - 1),
349 name: "──┼──".to_string() + &"─".repeat(multi_gate_info.gate_name_length - 1),
350 bottom: " │ ".to_string() + &" ".repeat(multi_gate_info.gate_name_length - 1),
351 connection: " │ ".to_string()
352 + &" ".repeat(multi_gate_info.gate_name_length - 1),
353 }
354 } else {
355 RowSchematic {
356 top: " ".repeat(multi_gate_info.gate_name_length + 4),
357 name: "─────".to_string() + &"─".repeat(multi_gate_info.gate_name_length - 1),
358 bottom: " ".to_string() + &" ".repeat(multi_gate_info.gate_name_length + 3),
359 connection: " ".to_string() + &" ".repeat(multi_gate_info.gate_name_length + 3),
360 }
361 };
362 Self::add_string_to_schematic(row_schematics, row, cache)
363 }
364 }
365
366 fn add_string_to_schematic(
368 schematic: &mut [String],
369 row_schem_num: usize,
370 cache: RowSchematic,
371 ) {
372 schematic[row_schem_num * 4].push_str(&cache.top);
373 schematic[row_schem_num * 4 + 1].push_str(&cache.name);
374 schematic[row_schem_num * 4 + 2].push_str(&cache.bottom);
375 schematic[row_schem_num * 4 + 3].push_str(&cache.connection);
376 }
377}
378
379#[rustfmt::skip]
380#[cfg(test)]
381mod tests {
382
383 use crate::{
384 Printer, Circuit, Gate, states::{Qubit, ProductState, SuperPosition},
385 };
386 use crate::complex_re_array;
387 fn example_cnot(prod: ProductState) -> Option<SuperPosition> {
391 let input_register: [Qubit; 2] = [prod.qubits[0], prod.qubits[1]];
392 Some(SuperPosition::new_with_amplitudes(match input_register {
393 [Qubit::Zero, Qubit::Zero] => return None,
394 [Qubit::Zero, Qubit::One] => return None,
395 [Qubit::One, Qubit::Zero] => &complex_re_array!(0f64, 0f64, 0f64, 1f64),
396 [Qubit::One, Qubit::One] => &complex_re_array!(0f64, 0f64, 1f64, 0f64),
397 })
398 .unwrap())
399 }
400
401 #[test]
402 fn producing_string_circuit() {
403 let mut quantum_circuit = Circuit::new(4).unwrap();
404 quantum_circuit.add_gate(Gate::H, 3).unwrap()
405 .add_repeating_gate(Gate::Y, &[0, 1]).unwrap()
406 .add_gate(Gate::Toffoli(0, 3), 1).unwrap()
407 .add_gate(Gate::CNot(1), 3).unwrap()
408 .add_gate(Gate::CNot(2), 0).unwrap()
409 .add_gate(Gate::CNot(2), 1).unwrap();
410
411 let mut circuit_printer: Printer = Printer::new(&quantum_circuit);
412
413 circuit_printer.print_diagram();
414
415 assert_eq!(circuit_printer.get_diagram(), " ┏━━━┓ ┏━━━┓ \n─────┨ Y ┠──█───────┨ X ┠─────\n ┗━━━┛ │ ┗━┯━┛ \n │ │ \n ┏━━━┓┏━┷━┓ │ ┏━━━┓\n─────┨ Y ┠┨ X ┠──█────┼──┨ X ┠\n ┗━━━┛┗━┯━┛ │ │ ┗━┯━┛\n │ │ │ │ \n │ │ │ │ \n────────────┼────┼────█────█──\n │ │ \n │ │ \n┏━━━┓ │ ┏━┷━┓ \n┨ H ┠───────█──┨ X ┠──────────\n┗━━━┛ ┗━━━┛ \n \n\n".to_string());
416 }
417
418 #[test]
419 fn producing_string_circuit_custom() {
420 let mut quantum_circuit = Circuit::new(4).unwrap();
421 quantum_circuit.add_gate(Gate::H, 3).unwrap();
422 quantum_circuit
423 .add_gates(&[
424 Gate::H,
425 Gate::Custom(example_cnot, vec!(3), "Custom CNot".to_string()),
426 Gate::Id,
427 Gate::X,
428 ]).unwrap()
429 .add_repeating_gate(Gate::Y, &[0, 1]).unwrap()
430 .add_gate(Gate::Toffoli(0, 3), 1).unwrap()
431 .add_gate(Gate::CNot(1), 3).unwrap()
432 .add_gate(Gate::CNot(2), 0).unwrap()
433 .add_gate(Gate::CNot(2), 1).unwrap();
434
435 let mut circuit_printer: Printer = Printer::new(&quantum_circuit);
436
437 circuit_printer.print_diagram();
438
439 assert_eq!(circuit_printer.get_diagram(), " ┏━━━┓ ┏━━━┓ ┏━━━┓ \n─────┨ H ┠───────────────┨ Y ┠──█───────┨ X ┠─────\n ┗━━━┛ ┗━━━┛ │ ┗━┯━┛ \n │ │ \n ┏━━━━━━━━━━━━━┓┏━━━┓┏━┷━┓ │ ┏━━━┓\n──────────┨ Custom CNot ┠┨ Y ┠┨ X ┠──█────┼──┨ X ┠\n ┗━┯━━━━━━━━━━━┛┗━━━┛┗━┯━┛ │ │ ┗━┯━┛\n │ │ │ │ │ \n │ │ │ │ │ \n────────────┼───────────────────┼────┼────█────█──\n │ │ │ \n │ │ │ \n┏━━━┓┏━━━┓ │ │ ┏━┷━┓ \n┨ H ┠┨ X ┠──█───────────────────█──┨ X ┠──────────\n┗━━━┛┗━━━┛ ┗━━━┛ \n \n\n".to_string());
440 }
441}