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}