quantr/
simulated_circuit.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 crate::{
12    complex_re,
13    states::{ProductState, SuperPosition},
14    Measurement,
15};
16use crate::{Circuit, Gate};
17use std::collections::HashMap;
18
19/// Contains the resulting state vector produced from the simulation of a circuit.
20pub struct SimulatedCircuit {
21    // Copy of Circuit struct but removed the wrapper around register.
22    pub(crate) circuit_gates: Vec<Gate>,
23    pub(crate) num_qubits: usize,
24    pub(crate) register: SuperPosition,
25    pub(crate) config_progress: bool,
26    pub(super) disable_warnings: bool,
27}
28
29impl SimulatedCircuit {
30    /// Returns a `HashMap` that contains the number of times the corresponding state was observed over
31    /// `n` measurements of the superpositions (shots).
32    ///
33    /// Explicitly, this performs repeated measurements where a register is attached to the circuit,
34    /// the resulting superposition measured in the computational basis, and then the reduced state
35    /// recorded. If the HashMap does not include a product state, then it was not observed over the
36    /// `n` measurements.
37    ///
38    /// For efficiency, this will use the cached register from the simulated circuit. If your
39    /// circuit contains mixed states, then most likely the circuit will have to be simulated again
40    /// for each shot. To achieve this, use [SimulatedCircuit::measure_all_without_cache].
41    ///
42    /// # Example
43    /// ```
44    /// use quantr::{states::SuperPosition, Circuit, Measurement::Observable, Gate};
45    ///
46    /// let mut circuit = Circuit::new(3).unwrap();
47    ///
48    /// circuit.add_gate(Gate::H, 2).unwrap();
49    /// let simulated_circuit = circuit.simulate();
50    ///
51    /// // Measures 500 superpositions.
52    /// println!("State | Number of Times Observed");
53    /// if let Observable(bin_count) = simulated_circuit.measure_all(500) {
54    ///     for (state, observed_count) in bin_count {
55    ///         println!("|{}>   : {}", state, observed_count);
56    ///     }
57    /// }
58    ///
59    /// // State | Number of Times Observed
60    /// // |000> : 247
61    /// // |001> : 253
62    /// ```
63    pub fn measure_all(&self, shots: usize) -> Measurement<HashMap<ProductState, usize>> {
64        let mut bin_count: HashMap<ProductState, usize> = Default::default();
65        if self.circuit_gates.iter().any(|x| x.is_custom_gate()) && !self.disable_warnings {
66            eprintln!("\x1b[93m[Quantr Warning] Custom gates were detected in the circuit. Measurements will be taken from a cached register in memory, and so if the Custom gate does NOT implement a unitary mapping, the measure_all method will most likely lead to wrong results. To simulate a circuit without cache, see SimulatedCircuit::measure_all_without_cache.\x1b[0m")
67        }
68
69        for _ in 0..shots {
70            self.add_to_bin(&mut bin_count);
71        }
72        Measurement::Observable(bin_count)
73    }
74
75    /// Similar to [SimulatedCircuit::measure_all], however for every shot it will simulate the
76    /// circuit, where the input register is reset to the zero state.
77    ///
78    /// This _potentially_ allows for mixed states to be simulated, through the implementation of
79    /// [Gate::Custom]. In doing so will dramatically increase the simulation time, as a new
80    /// circuit will be simulated for each shot.
81    pub fn measure_all_without_cache(
82        self,
83        shots: usize,
84    ) -> Measurement<HashMap<ProductState, usize>> {
85        let mut bin_count: HashMap<ProductState, usize> = Default::default();
86        let mut simulated_circ = self;
87        simulated_circ.add_to_bin(&mut bin_count);
88        if simulated_circ.config_progress {
89            println!("Measured state # 1/{}", shots);
90        }
91        for i in 0..shots - 1 {
92            // reset to |0> register
93            simulated_circ
94                .register
95                .amplitudes
96                .fill(num_complex::Complex64::ZERO);
97            simulated_circ.register.amplitudes[0] = complex_re!(1f64);
98            if simulated_circ.config_progress {
99                println!("Register reset to zero state")
100            }
101            let circuit = Circuit {
102                circuit_gates: simulated_circ.circuit_gates,
103                num_qubits: simulated_circ.num_qubits,
104                register: Some(simulated_circ.register),
105                config_progress: simulated_circ.config_progress,
106            };
107            simulated_circ = circuit.simulate();
108            simulated_circ.add_to_bin(&mut bin_count);
109            if simulated_circ.config_progress {
110                println!("Measured state # {}/{}", i + 2, shots);
111            }
112        }
113        Measurement::Observable(bin_count)
114    }
115
116    fn add_to_bin(&self, bin: &mut HashMap<ProductState, usize>) {
117        match self.register.measure() {
118            Some(state) => {
119                bin.entry(state)
120                    .and_modify(|count| {
121                        *count = *count + 1;
122                    })
123                    .or_insert(1);
124            }
125            None if !self.disable_warnings => {
126                eprintln!("\x1b[93m[Quantr Warning] The superposition failed to collapse to a state during repeat measurements. This is likely due to the use of Gate::Custom where the mapping is not unitary.\x1b[0m")
127            }
128            None => {}
129        }
130    }
131
132    /// Returns the resulting superposition after the circuit has been simulated using
133    /// [super::Circuit::simulate].
134    ///
135    /// This is a non-physical observable, as the superposition would reduce to a single state upon measurement.
136    ///
137    /// # Example
138    /// ```
139    /// use quantr::{states::SuperPosition, Circuit, Measurement::NonObservable, Gate};
140    ///
141    /// let mut circuit = Circuit::new(3).unwrap();
142    ///
143    /// circuit.add_gate(Gate::H, 2).unwrap();
144    /// circuit.add_gate(Gate::Y, 2).unwrap();
145    /// let simulated_circuit = circuit.simulate();
146    ///
147    /// println!("State | Amplitude of State");
148    /// if let NonObservable(super_pos) = simulated_circuit.get_state() {
149    ///     for (state, amplitude) in super_pos.into_iter() {
150    ///         println!("|{}>   : {}", state.to_string(), amplitude);
151    ///     }
152    /// }
153    ///
154    /// // State | Amplitude of State
155    /// // |000> : 0 - 0.71...i
156    /// // |001> : 0 + 0.71...i
157    /// ```
158    pub fn get_state(&self) -> Measurement<&SuperPosition> {
159        Measurement::NonObservable(&self.register)
160    }
161
162    /// Sets if the printer should display warnings.
163    pub fn print_warnings(&mut self, printing: bool) {
164        self.disable_warnings = printing;
165    }
166
167    /// The slice of gates that composed the circuit, equivalent to [Circuit::get_gates].
168    pub fn get_circuit_gates(&self) -> &Vec<Gate> {
169        &self.circuit_gates
170    }
171
172    /// The number of qubits that composed the circuit, equivalent to [Circuit::get_num_qubits].
173    pub fn get_num_qubits(&self) -> usize {
174        self.num_qubits
175    }
176
177    /// Sets whether the simulation progress of the circuit will be printed to the terminal. This
178    /// value is inherited from the circuit this struct was derived from.
179    pub fn set_print_progress(&mut self, printing: bool) {
180        self.config_progress = printing;
181    }
182
183    /// Takes ownership of the state that the `SimulatedCircuit` wraps around, that is the state
184    /// that resulted from a circuit simulation.
185    pub fn take_state(self) -> Measurement<SuperPosition> {
186        Measurement::NonObservable(self.register)
187    }
188}