quantr/circuit/
printer.rs

1/*
2* Copyright (c) 2024 Andrew Rowan Barlow. Licensed under the EUPL-1.2
3* or later. You may obtain a copy of the licence at
4* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12. A copy
5* of the EUPL-1.2 licence in English is given in LICENCE.txt which is
6* found in the root directory of this repository.
7*
8* Author: Andrew Rowan Barlow <a.barlow.dev@gmail.com>
9*/
10
11use super::{Circuit, Gate};
12use std::fs::File;
13use std::io::Write;
14use std::path::Path;
15
16/// Constructs, displays and saves the circuit diagram as a UTF-8 string.
17///
18/// The user has the option to print the string to the terminal or a text file, where the text file
19/// has the advantage of not wrapping the circuit within the terminal. The [Printer] will also
20/// cache a copy of the diagram so subsequent prints will require no building of the diagram.
21pub 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    /// Handle the printing of the given circuit.
55    pub fn new<'circ>(circuit: &'circ Circuit) -> Printer<'circ> {
56        Printer {
57            circuit,
58            diagram: None,
59            disable_warnings: false,
60        }
61    }
62
63    /// Prints the circuit to the console in UTF-8.
64    ///
65    /// A warning is printed to the console if the circuit diagram is expected to exceed 72 chars.
66    ///
67    /// # Example
68    /// ```
69    /// use quantr::{Circuit, Gate, Printer};
70    ///
71    /// let mut qc: Circuit = Circuit::new(2).unwrap();
72    /// qc.add_gate(Gate::CNot(0), 1).unwrap();
73    ///
74    /// let mut printer: Printer = Printer::new(&qc);
75    /// printer.print_diagram();
76    ///
77    /// // The above prints:
78    /// // ──█──
79    /// //   │  
80    /// //   │  
81    /// // ┏━┷━┓
82    /// // ┨ X ┠
83    /// // ┗━━━┛
84    /// ```
85    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    /// Saves the circuit diagram in UTF-8 chars to a text file.
94    ///
95    /// If the file already exists, it will overwrite it.
96    ///
97    /// # Example
98    /// ```
99    /// use quantr::{Circuit, Gate, Printer};
100    ///
101    /// let mut qc: Circuit = Circuit::new(2).unwrap();
102    /// qc.add_gate(Gate::CNot(0), 1).unwrap();
103    ///
104    /// let mut printer: Printer = Printer::new(&qc);
105    /// // printer.save_diagram("diagram.txt").unwrap();
106    /// // Saves in directory of Cargo package.
107    /// // (Commented so it doesn't create file during `cargo test`.)
108    /// ```
109    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    /// Prints the circuit diagram to the terminal and saves it to a text file in UTF-8.
116    ///
117    /// Essentially, this is a combination of [Printer::save_diagram] and [Printer::print_diagram].
118    ///
119    /// # Example
120    /// ```
121    /// use quantr::{Circuit, Gate, Printer};
122    ///
123    /// let mut qc: Circuit = Circuit::new(2).unwrap();
124    /// qc.add_gate(Gate::CNot(0), 1).unwrap();
125    ///
126    /// let mut printer: Printer = Printer::new(&qc);
127    /// // printer.print_and_save_diagram("diagram.txt").unwrap();
128    /// // Saves in directory of cargo project, and prints to console.
129    /// // (Commented so it doesn't create file during `cargo test`.)
130    /// ```
131    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    /// Returns the circuit diagram that is made from UTF-8 chars.
142    ///
143    /// # Example
144    /// ```
145    /// use quantr::{Circuit, Gate, Printer};
146    ///
147    /// let mut qc: Circuit = Circuit::new(2).unwrap();
148    /// qc.add_gate(Gate::CNot(0), 1).unwrap();
149    ///
150    /// let mut printer: Printer = Printer::new(&qc);
151    /// println!("{}", printer.get_diagram()); // equivalent to Printer::print_diagram
152    /// ```
153    pub fn get_diagram(&mut self) -> String {
154        self.get_or_make_diagram()
155    }
156
157    /// Sets if the printer should display warnings.
158    pub fn set_warnings(&mut self, printing: bool) {
159        self.disable_warnings = printing;
160    }
161
162    // Constructs the diagram, or returns the diagram previously built.
163    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        // num qubits cannot be zero due to initialisation
172        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            // Get a column of gates with all names and length of names
178            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                // Deals with column of single multi-gate
190                Self::draw_multi_gates(
191                    &mut printed_diagram,
192                    multi_gate_info,
193                    &self.circuit.num_qubits,
194                    position,
195                );
196            } else {
197                // Deals with single gates
198                Self::draw_single_gates(printed_diagram.as_mut_slice(), diagram_schematic);
199            }
200        }
201
202        // Collect all the strings to return a single string giving the diagram
203        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    // Finds if there is a gate with one/multiple control nodes
236    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    // Draw a column of single gates
248    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    // Draw a single column containing a multigate function.
276    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    // Adds a gate to the vector of strings.
367    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    // These are primarily tested by making sure they print correctly to
388    // the terminal, and then copy the output for the assert_eq! macro.
389
390    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}