From 09373e56f8c535a24f11d2670a37833e7fc3f603 Mon Sep 17 00:00:00 2001 From: Andrew Barlow Date: Sat, 30 Dec 2023 22:21:14 +0000 Subject: [PATCH 01/10] Add deprecated flags to fields of circuit Signed-off-by: Andrew Barlow --- CHANGELOG.md | 7 +++++++ src/circuit.rs | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 597291e..f63a1f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ This file logs the versions of quantr. +## 0.4.1 - More optimisations + +Deprecated features: + +- The public fields of `Circuit` are to be made private (specifically + updated to `pub(crate)` status in the next breaking update). + ## 0.4.0 - Optimisations of speed and memory allocation The optimisations and breaking changes that this update induces greatly diff --git a/src/circuit.rs b/src/circuit.rs index ddf9987..9e9dff8 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -8,6 +8,9 @@ * Author: Andrew Rowan Barlow */ +// Added only for silencing deprecated warnings for using public fields of `Circuit`. +#![allow(deprecated)] + use super::circuit::gate::{GateInfo, GateSize}; use crate::states::{ProductState, SuperPosition}; use crate::{Gate, QuantrError}; @@ -36,7 +39,9 @@ pub enum Measurement { /// A quantum circuit where gates can be appended and then simulated to measure resulting /// superpositions. pub struct Circuit<'a> { + #[deprecated(note="This field will be made private to the user, where it will be given pub(crate) status in the next major update.")] pub circuit_gates: Vec>, + #[deprecated(note="This field will be made private to the user, where it will be given pub(crate) status in the next major update.")] pub num_qubits: usize, output_state: Option, register: Option, From ba67aca3eb71e1db8bfe2fb976f9e566839236a7 Mon Sep 17 00:00:00 2001 From: Andrew Barlow Date: Mon, 8 Jan 2024 15:15:03 +0000 Subject: [PATCH 02/10] Edit inversion of qubit states In the main algorithm, when the product states are modified (not the amplitudes), they are inverted if they are in fact changed. Rather than setting all states, even when they're the same. Signed-off-by: Andrew Barlow --- CHANGELOG.md | 6 ++++++ src/circuit/simulation.rs | 8 ++++---- src/circuit/states/product_states.rs | 26 +++++++++++++------------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f63a1f2..9b9c36d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ This file logs the versions of quantr. ## 0.4.1 - More optimisations +Optimisations: + +- The main simulating algorithm has been updated to increase it's speed, + mostly bypassing computations that are uneeded: + - Product state qubits are flipped only if they are indeed different. + Deprecated features: - The public fields of `Circuit` are to be made private (specifically diff --git a/src/circuit/simulation.rs b/src/circuit/simulation.rs index 2923fdd..e427980 100644 --- a/src/circuit/simulation.rs +++ b/src/circuit/simulation.rs @@ -267,10 +267,10 @@ impl<'a> Circuit<'a> { continue; } // Insert these image states back into a product space - let swapped_state: ProductState = - prod_state.insert_qubits(state.qubits.as_slice(), gate_positions.as_slice()); - if mapped_states.contains_key(&swapped_state) { - let existing_amp: Complex = *mapped_states.get(&swapped_state).unwrap(); + let mut swapped_state: ProductState = prod_state.clone(); + swapped_state.insert_qubits(state.qubits.as_slice(), gate_positions.as_slice()); + + if let Some(existing_amp) = mapped_states.get(&swapped_state) { mapped_states.insert(swapped_state, existing_amp.add(state_amp.mul(amp))); } else { mapped_states.insert(swapped_state, state_amp.mul(amp)); diff --git a/src/circuit/states/product_states.rs b/src/circuit/states/product_states.rs index 9b09876..47cff95 100644 --- a/src/circuit/states/product_states.rs +++ b/src/circuit/states/product_states.rs @@ -106,18 +106,18 @@ impl ProductState { // Changes the qubits at specified positions within the product state with a slice of other // qubits. - pub(crate) fn insert_qubits(&self, qubits: &[Qubit], pos: &[usize]) -> ProductState { - let mut edited_qubits: Vec = self.qubits.clone(); - let num_qubits: usize = qubits.len(); + pub(crate) fn insert_qubits(&mut self, qubits: &[Qubit], pos: &[usize]) { + //let mut edited_qubits: Vec = self.qubits.clone(); - if num_qubits != pos.len() { - panic!("Size of qubits and positions must be equal.") + for (enum_i, &i) in pos.iter().enumerate() { + if self.qubits[i] != qubits[enum_i] { + self.qubits[i] = match self.qubits[i] { + Qubit::Zero => Qubit::One, + Qubit::One => Qubit::Zero, + }; + } } - for (index, position) in pos.iter().enumerate() { - edited_qubits[*position] = qubits[index]; - } - ProductState::new_unchecked(&edited_qubits) } /// Returns the number of qubits that form the product state. @@ -274,11 +274,11 @@ mod tests { #[test] fn insert_qubits_in_state() { + let mut prod = ProductState::new_unchecked(&[Qubit::One, Qubit::One, Qubit::One]); + prod.insert_qubits(&[Qubit::Zero, Qubit::Zero], &[0, 2]); assert_eq!( - ProductState::new_unchecked(&[Qubit::Zero, Qubit::Zero, Qubit::One]).qubits, - ProductState::new_unchecked(&[Qubit::One, Qubit::One, Qubit::One]) - .insert_qubits(&[Qubit::Zero, Qubit::Zero], &[0, 1]) - .qubits + ProductState::new_unchecked(&[Qubit::Zero, Qubit::One, Qubit::Zero]).qubits, + prod.qubits ); } From 267f40971348f3248250fff3f000440d368611f2 Mon Sep 17 00:00:00 2001 From: Andrew Barlow Date: Mon, 8 Jan 2024 21:08:05 +0000 Subject: [PATCH 03/10] Add type and change random crate dependence A type declared in `circuit` has been introduced for the developers ease, essentially shortens the repetitive Result. The random crate has been switched to fastrand, as it's lighter weight and security is not needed. This means that download time and compilation time is reduced. Signed-off-by: Andrew Barlow --- CHANGELOG.md | 11 ++++- Cargo.toml | 4 +- README.md | 5 +- src/circuit.rs | 46 ++++++++----------- src/circuit/states/product_states.rs | 10 ++-- src/circuit/states/super_positions.rs | 22 ++++----- .../states/super_positions_unchecked.rs | 4 +- 7 files changed, 49 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b9c36d..2116437 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,18 @@ This file logs the versions of quantr. ## 0.4.1 - More optimisations +Edited the README to include "No parallelisation" to limitations, and +reduced the tractable number of qubit simulations to 18. + +Change of dependency: + +- For the random + Optimisations: - The main simulating algorithm has been updated to increase it's speed, - mostly bypassing computations that are uneeded: - - Product state qubits are flipped only if they are indeed different. + mostly bypassing computations that are uneeded, for instance product + state qubits are flipped only if they are indeed different. Deprecated features: diff --git a/Cargo.toml b/Cargo.toml index 026a3e3..1dc4ce6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quantr" -version = "0.4.0" +version = "0.4.1" edition = "2021" license = "EUPL-1.2" readme = "README.md" @@ -13,4 +13,4 @@ description = "Readily create, simulate and print quantum circuits." publish = false [dependencies] -rand = "0.8.5" +fastrand = "^2.0.1" diff --git a/README.md b/README.md index acb409f..5689c31 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # quantr [![Crates.io](https://img.shields.io/crates/v/quantr?style=flat-square&color=%23B94700)](https://crates.io/crates/quantr) -[![Static Badge](https://img.shields.io/badge/version%20-%201.74.1%20-%20white?style=flat-square&logo=rust&color=%23B94700)](https://releases.rs/) +[![Static Badge](https://img.shields.io/badge/version%20-%201.75.0%20-%20white?style=flat-square&logo=rust&color=%23B94700)](https://releases.rs/) [![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/a-barlow/quantr/rust.yml?style=flat-square&label=tests&color=%2349881B)](https://github.com/a-barlow/quantr/actions/workflows/rust.yml) [![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/a-barlow/quantr/rust_dev.yml?style=flat-square&label=tests%20(dev)&color=%2349881B)](https://github.com/a-barlow/quantr/actions/workflows/rust_dev.yml) [![docs.rs](https://img.shields.io/docsrs/quantr?style=flat-square&color=%2349881B)](https://crates.io/crates/quantr) @@ -46,7 +46,7 @@ implementation of Grover's algorithm. when `n >= 16` is approximately the size of the state vector itself in Rust, `2**(n-6) KiB`. For `n < 16`, the memory required is less than `1 MiB`. -- Can simulate circuits up to ~20 qubits within a reasonable time. +- Can simulate circuits up to ~18 qubits within a reasonable time. - Only safe Rust code is used, and the only dependency is the [rand](https://docs.rs/rand/latest/rand/) crate and its sub-dependencies. @@ -98,6 +98,7 @@ guide](QUICK_START.md). ### Limitations (currently) - There is **no noise** consideration, or ability to introduce noise. +- **No parallelisation** available. - There is **no ability to add classical wires** nor gates that measure a single wire of a quantum circuit. diff --git a/src/circuit.rs b/src/circuit.rs index 9e9dff8..3feda14 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -8,13 +8,12 @@ * Author: Andrew Rowan Barlow */ -// Added only for silencing deprecated warnings for using public fields of `Circuit`. +// Added only for silencing deprecated warnings for using public fields of `Circuit`. #![allow(deprecated)] use super::circuit::gate::{GateInfo, GateSize}; use crate::states::{ProductState, SuperPosition}; use crate::{Gate, QuantrError}; -use rand::Rng; use std::collections::HashMap; pub mod gate; @@ -23,6 +22,8 @@ mod simulation; mod standard_gate_ops; pub mod states; +pub(crate) type QResult = Result; + // The tolerance for declaring non-zero amplitudes. const ZERO_MARGIN: f64 = 1e-7; @@ -39,9 +40,13 @@ pub enum Measurement { /// A quantum circuit where gates can be appended and then simulated to measure resulting /// superpositions. pub struct Circuit<'a> { - #[deprecated(note="This field will be made private to the user, where it will be given pub(crate) status in the next major update.")] + #[deprecated( + note = "This field will be made private to the user, where it will be given pub(crate) status in the next major update." + )] pub circuit_gates: Vec>, - #[deprecated(note="This field will be made private to the user, where it will be given pub(crate) status in the next major update.")] + #[deprecated( + note = "This field will be made private to the user, where it will be given pub(crate) status in the next major update." + )] pub num_qubits: usize, output_state: Option, register: Option, @@ -61,7 +66,7 @@ impl<'a> Circuit<'a> { /// // Initialises a 3 qubit circuit. /// let quantum_circuit: Circuit = Circuit::new(3).unwrap(); /// ``` - pub fn new(num_qubits: usize) -> Result, QuantrError> { + pub fn new(num_qubits: usize) -> QResult> { if num_qubits == 0 { return Err(QuantrError { message: String::from("The initiliased circuit must have at least one wire."), @@ -95,11 +100,7 @@ impl<'a> Circuit<'a> { /// // ------- /// // ------- /// ``` - pub fn add_gate( - &mut self, - gate: Gate<'a>, - position: usize, - ) -> Result<&mut Circuit<'a>, QuantrError> { + pub fn add_gate(&mut self, gate: Gate<'a>, position: usize) -> QResult<&mut Circuit<'a>> { Self::add_gates_with_positions(self, HashMap::from([(position, gate)])) } @@ -130,7 +131,7 @@ impl<'a> Circuit<'a> { pub fn add_gates_with_positions( &mut self, gates_with_positions: HashMap>, - ) -> Result<&mut Circuit<'a>, QuantrError> { + ) -> QResult<&mut Circuit<'a>> { // If any keys are out of bounds, return an error. if let Some(out_of_bounds_key) = gates_with_positions.keys().find(|k| *k >= &self.num_qubits) @@ -185,7 +186,7 @@ impl<'a> Circuit<'a> { /// // -- X -- /// // -- Y -- /// ``` - pub fn add_gates(&mut self, gates: &[Gate<'a>]) -> Result<&mut Circuit<'a>, QuantrError> { + pub fn add_gates(&mut self, gates: &[Gate<'a>]) -> QResult<&mut Circuit<'a>> { // Ensured we have a gate for every wire. if gates.len() != self.num_qubits { return Err(QuantrError { @@ -205,7 +206,7 @@ impl<'a> Circuit<'a> { // Pushes multi-controlled gates into their own column. Potentially expensive operation to // insert new elements at smaller positions into a long vector. - fn push_multi_gates(gates: &mut Vec>) -> Result<(), QuantrError> { + fn push_multi_gates(gates: &mut Vec>) -> QResult<()> { let mut extended_vec: Vec = Default::default(); let mut multi_gate_positions: Vec = Default::default(); @@ -251,10 +252,7 @@ impl<'a> Circuit<'a> { } // need to implement all other gates, in addition to checking that it's within circuit size! - fn has_overlapping_controls_and_target( - gates: &[Gate], - circuit_size: usize, - ) -> Result<(), QuantrError> { + fn has_overlapping_controls_and_target(gates: &[Gate], circuit_size: usize) -> QResult<()> { for (pos, gate) in gates.iter().enumerate() { match gate.get_nodes() { Some(nodes) => { @@ -317,7 +315,7 @@ impl<'a> Circuit<'a> { &mut self, gate: Gate<'a>, positions: &[usize], - ) -> Result<&mut Circuit<'a>, QuantrError> { + ) -> QResult<&mut Circuit<'a>> { // Incase the user has attempted to place the gate twice on the same wire. if Self::contains_repeating_values(self.num_qubits, positions) { return Err(QuantrError { @@ -430,7 +428,7 @@ impl<'a> Circuit<'a> { /// // |000> : 0 - 0.71...i /// // |001> : 0 + 0.71...i /// ``` - pub fn get_superposition(&self) -> Result, QuantrError> { + pub fn get_superposition(&self) -> QResult> { match &self.output_state { Some(super_position) => Ok(Measurement::NonObservable(super_position)), None => { @@ -473,7 +471,7 @@ impl<'a> Circuit<'a> { pub fn repeat_measurement( &self, number_iterations: usize, - ) -> Result>, QuantrError> { + ) -> QResult>> { match &self.output_state { Some(super_position) => { // Peform bin count of states @@ -484,10 +482,9 @@ impl<'a> Circuit<'a> { let mut bin_count: HashMap = Default::default(); - let mut rng = rand::thread_rng(); for _ in 0..number_iterations { let mut cummalitive: f64 = 0f64; - let dice_roll: f64 = rng.gen(); + let dice_roll: f64 = fastrand::f64(); for (state_label, probability) in &probabilities { cummalitive = cummalitive + probability; if dice_roll < cummalitive { @@ -535,10 +532,7 @@ impl<'a> Circuit<'a> { /// // |1> ------- /// // |0> -- X -- /// ```` - pub fn change_register( - &mut self, - super_pos: SuperPosition, - ) -> Result<&mut Circuit<'a>, QuantrError> { + pub fn change_register(&mut self, super_pos: SuperPosition) -> QResult<&mut Circuit<'a>> { if super_pos.product_dim != self.num_qubits { return Err(QuantrError { message: format!("The custom register has a product state dimension of {}, while the number of qubits is {}. These must equal each other.", super_pos.product_dim, self.num_qubits) diff --git a/src/circuit/states/product_states.rs b/src/circuit/states/product_states.rs index 47cff95..7617188 100644 --- a/src/circuit/states/product_states.rs +++ b/src/circuit/states/product_states.rs @@ -8,6 +8,7 @@ * Author: Andrew Rowan Barlow */ +use crate::circuit::QResult; use crate::states::Qubit; use crate::QuantrError; @@ -31,7 +32,7 @@ impl ProductState { /// /// let prod: ProductState = ProductState::new(&[Qubit::One, Qubit::Zero]).unwrap(); // |10> /// ``` - pub fn new(product_state: &[Qubit]) -> Result { + pub fn new(product_state: &[Qubit]) -> QResult { if product_state.is_empty() { return Err(QuantrError { message: String::from( @@ -117,7 +118,6 @@ impl ProductState { }; } } - } /// Returns the number of qubits that form the product state. @@ -149,7 +149,7 @@ impl ProductState { /// /// assert_eq!(&[Qubit::One, Qubit::One, Qubit::One], prod.get_qubits()); /// ``` - pub fn invert_digit(&mut self, place_num: usize) -> Result<&mut ProductState, QuantrError> { + pub fn invert_digit(&mut self, place_num: usize) -> QResult<&mut ProductState> { if place_num >= self.num_qubits() { return Err(QuantrError { message: format!("The position of the binary digit, {}, is out of bounds. The product dimension is {}, and so the position must be strictly less.", place_num, self.num_qubits()) }); } @@ -274,11 +274,11 @@ mod tests { #[test] fn insert_qubits_in_state() { - let mut prod = ProductState::new_unchecked(&[Qubit::One, Qubit::One, Qubit::One]); + let mut prod = ProductState::new_unchecked(&[Qubit::One, Qubit::One, Qubit::One]); prod.insert_qubits(&[Qubit::Zero, Qubit::Zero], &[0, 2]); assert_eq!( ProductState::new_unchecked(&[Qubit::Zero, Qubit::One, Qubit::Zero]).qubits, - prod.qubits + prod.qubits ); } diff --git a/src/circuit/states/super_positions.rs b/src/circuit/states/super_positions.rs index a0c1943..7c5a95f 100644 --- a/src/circuit/states/super_positions.rs +++ b/src/circuit/states/super_positions.rs @@ -8,7 +8,7 @@ * Author: Andrew Rowan Barlow */ -use crate::circuit::{HashMap, ZERO_MARGIN}; +use crate::circuit::{HashMap, QResult, ZERO_MARGIN}; use crate::complex_re; use crate::{states::ProductState, Complex, QuantrError, COMPLEX_ZERO}; @@ -33,7 +33,7 @@ impl SuperPosition { /// /// assert_eq!(&complex_re_array![1f64, 0f64, 0f64, 0f64], superpos.get_amplitudes()); /// ``` - pub fn new(prod_dimension: usize) -> Result { + pub fn new(prod_dimension: usize) -> QResult { if prod_dimension == 0 { return Err(QuantrError { message: String::from("The number of qubits must be non-zero."), @@ -60,7 +60,7 @@ impl SuperPosition { /// /// assert_eq!(&complex_re_array![1f64, 0f64, 0f64, 0f64], superpos.get_amplitudes()); /// ``` - pub fn new_with_amplitudes(amplitudes: &[Complex]) -> Result { + pub fn new_with_amplitudes(amplitudes: &[Complex]) -> QResult { if !Self::equal_within_error(amplitudes.iter().map(|x| x.abs_square()).sum::(), 1f64) { return Err(QuantrError { message: String::from("Slice given to set amplitudes in super position does not conserve probability, the absolute square sum of the coefficents must be one."), @@ -100,7 +100,7 @@ impl SuperPosition { /// ``` pub fn new_with_hash_amplitudes( hash_amplitudes: HashMap>, - ) -> Result { + ) -> QResult { if hash_amplitudes.is_empty() { return Err(QuantrError { message: String::from("An empty HashMap was given. A superposition must have at least one non-zero state.") }); } @@ -137,7 +137,7 @@ impl SuperPosition { /// /// assert_eq!(complex_re!(1f64), superpos.get_amplitude(1).unwrap()); /// ``` - pub fn get_amplitude(&self, pos: usize) -> Result, QuantrError> { + pub fn get_amplitude(&self, pos: usize) -> QResult> { if pos >= self.amplitudes.len() { let length = self.amplitudes.len(); Err(QuantrError { message: format!("Failed to retrieve amplitude from list. Index given was, {pos}, which is greater than length of list, {length}."), @@ -205,10 +205,7 @@ impl SuperPosition { /// /// assert_eq!(complex_re!(1f64), superpos.get_amplitude_from_state(prod_state).unwrap()); /// ``` - pub fn get_amplitude_from_state( - &self, - prod_state: ProductState, - ) -> Result, QuantrError> { + pub fn get_amplitude_from_state(&self, prod_state: ProductState) -> QResult> { if 2usize << prod_state.qubits.len() - 1 != self.amplitudes.len() { return Err(QuantrError { message: format!("Unable to retreive product state, |{:?}> with dimension {}. The superposition is a linear combination of states with different dimension. These dimensions should be equal.", prod_state.to_string(), prod_state.num_qubits()),}); } @@ -230,10 +227,7 @@ impl SuperPosition { /// /// assert_eq!(&complex_re_array![0f64, 1f64, 0f64, 0f64], superpos.get_amplitudes()); /// ``` - pub fn set_amplitudes( - &mut self, - amplitudes: &[Complex], - ) -> Result<&mut SuperPosition, QuantrError> { + pub fn set_amplitudes(&mut self, amplitudes: &[Complex]) -> QResult<&mut SuperPosition> { if amplitudes.len() != self.amplitudes.len() { return Err(QuantrError { message: format!("The slice given to set the amplitudes in the computational basis has length {}, when it should have length {}.", amplitudes.len(), self.amplitudes.len()), @@ -278,7 +272,7 @@ impl SuperPosition { pub fn set_amplitudes_from_states( &mut self, amplitudes: HashMap>, - ) -> Result<&mut SuperPosition, QuantrError> { + ) -> QResult<&mut SuperPosition> { // Check if amplitudes and product states are correct. if amplitudes.is_empty() { return Err(QuantrError { message: String::from("An empty HashMap was given. A superposition must have at least one non-zero state.") }); diff --git a/src/circuit/states/super_positions_unchecked.rs b/src/circuit/states/super_positions_unchecked.rs index 73bae1a..5c3d2a1 100644 --- a/src/circuit/states/super_positions_unchecked.rs +++ b/src/circuit/states/super_positions_unchecked.rs @@ -41,9 +41,9 @@ impl SuperPosition { /// States that are missing from the HashMap will be assumed to have 0 amplitude. pub(crate) fn set_amplitudes_from_states_unchecked( &mut self, - amplitudes: HashMap>, + hash_amplitudes: HashMap>, ) -> &mut SuperPosition { - for (key, val) in amplitudes { + for (key, val) in hash_amplitudes { self.amplitudes[key.comp_basis()] = val; } self From c880e858304ba14c093459d9c22236eb8e0f2c6b Mon Sep 17 00:00:00 2001 From: Andrew Barlow Date: Wed, 10 Jan 2024 11:02:58 +0000 Subject: [PATCH 04/10] Add methods to Circuit Following the deprecated public fields, methods have been added to get the list of gates added to the circuit, and the number of qubits within the circuit. Signed-off-by: Andrew Barlow --- README.md | 8 ++++---- src/circuit.rs | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5689c31..e27f289 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ implementation of Grover's algorithm. `1 MiB`. - Can simulate circuits up to ~18 qubits within a reasonable time. - Only safe Rust code is used, and the only dependency is the - [rand](https://docs.rs/rand/latest/rand/) crate and its + [fastrand](https://crates.io/crates/fastrand) crate and its sub-dependencies. ### Usage @@ -97,9 +97,9 @@ guide](QUICK_START.md). ### Limitations (currently) -- There is **no noise** consideration, or ability to introduce noise. -- **No parallelisation** available. -- There is **no ability to add classical wires** nor gates that measure a +- **No noise** consideration, or ability to introduce noise. +- **No parallelisation** option. +- **No ability to add classical wires** nor gates that measure a single wire of a quantum circuit. ### Conventions diff --git a/src/circuit.rs b/src/circuit.rs index 3feda14..5e4eae2 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -41,11 +41,11 @@ pub enum Measurement { /// superpositions. pub struct Circuit<'a> { #[deprecated( - note = "This field will be made private to the user, where it will be given pub(crate) status in the next major update." + note = "This field will be made private to the user, where it will be given pub(crate) status in the next major update. Use Circuit::get_gates instead." )] pub circuit_gates: Vec>, #[deprecated( - note = "This field will be made private to the user, where it will be given pub(crate) status in the next major update." + note = "This field will be made private to the user, where it will be given pub(crate) status in the next major update. Use Circuit::get_num_qubits instead." )] pub num_qubits: usize, output_state: Option, @@ -83,6 +83,36 @@ impl<'a> Circuit<'a> { }) } + /// Returns the number of qubits in the circuit. + /// + /// # Example + /// ``` + /// use quantr::{Circuit, Gate}; + /// + /// let quantum_circuit: Circuit = Circuit::new(3).unwrap(); + /// assert_eq!(quantum_circuit.get_num_qubits(), 3usize); + /// ``` + pub fn get_num_qubits(self) -> usize { + self.num_qubits + } + + /// Returns the vector of gates that have been added to the circuit. + /// + /// It is a flattened vector which is buffered with identity gates. + /// + /// # Example + /// ``` + /// use quantr::{Circuit, Gate}; + /// + /// let mut quantum_circuit: Circuit = Circuit::new(3).unwrap(); + /// quantum_circuit.add_gate(Gate::X, 2).unwrap(); + /// + /// assert_eq!(quantum_circuit.get_gates(), &[Gate::Id, Gate::Id, Gate::X]); + /// ``` + pub fn get_gates(&self) -> &[Gate<'a>] { + self.circuit_gates.as_slice() + } + /// Adds a single gate to the circuit. /// /// If wanting to add multiple gates, or a single gate repeatedly across multiple wires, see From b5e80619cd1bb84ec87f182d02d1bff29ec05ad9 Mon Sep 17 00:00:00 2001 From: Andrew Barlow Date: Wed, 10 Jan 2024 11:40:40 +0000 Subject: [PATCH 05/10] Cargo clippy Ran cargo clippy and corrected code that it suggested to fix. Signed-off-by: Andrew Barlow --- src/circuit.rs | 45 +++++++++++------------ src/circuit/printer.rs | 36 +++++++++---------- src/circuit/simulation.rs | 2 +- src/circuit/states/product_states.rs | 5 +-- src/circuit/states/super_positions.rs | 8 ++--- src/lib.rs | 52 +++++++++++++-------------- 6 files changed, 71 insertions(+), 77 deletions(-) diff --git a/src/circuit.rs b/src/circuit.rs index 5e4eae2..0c079c6 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -187,7 +187,7 @@ impl<'a> Circuit<'a> { } // No overlapping gates - Self::has_overlapping_controls_and_target(&gates_to_add, self.num_qubits.clone())?; + Self::has_overlapping_controls_and_target(&gates_to_add, self.num_qubits)?; // Push any multi-controlled gates to isolated columns Self::push_multi_gates(&mut gates_to_add)?; @@ -225,7 +225,7 @@ impl<'a> Circuit<'a> { } // Make sure there are no control nodes that overlap with it's other nodes. - Self::has_overlapping_controls_and_target(&gates, self.num_qubits.clone())?; + Self::has_overlapping_controls_and_target(gates, self.num_qubits)?; // Push n-gates to another line (double, triple, etc.) let mut gates_vec: Vec> = gates.to_vec(); @@ -284,27 +284,24 @@ impl<'a> Circuit<'a> { // need to implement all other gates, in addition to checking that it's within circuit size! fn has_overlapping_controls_and_target(gates: &[Gate], circuit_size: usize) -> QResult<()> { for (pos, gate) in gates.iter().enumerate() { - match gate.get_nodes() { - Some(nodes) => { - // check for overlapping control nodes. - if Self::contains_repeating_values(circuit_size, &nodes) { - return Err(QuantrError { - message: format!( - "The gate, {:?}, has overlapping control nodes.", - gate - ), - }); - } - if nodes.contains(&pos) { - return Err(QuantrError { message: format!("The gate, {:?}, has a control node that equals the gate's position {}.", gate, pos) }); - } - for &node in nodes.iter() { - if node >= circuit_size { - return Err(QuantrError { message: format!("The control node at position {:?}, is greater than the umnber of qubits {}.", node, circuit_size) }); - } + if let Some(nodes) = gate.get_nodes() { + // check for overlapping control nodes. + if Self::contains_repeating_values(circuit_size, &nodes) { + return Err(QuantrError { + message: format!( + "The gate, {:?}, has overlapping control nodes.", + gate + ), + }); + } + if nodes.contains(&pos) { + return Err(QuantrError { message: format!("The gate, {:?}, has a control node that equals the gate's position {}.", gate, pos) }); + } + for &node in nodes.iter() { + if node >= circuit_size { + return Err(QuantrError { message: format!("The control node at position {:?}, is greater than the umnber of qubits {}.", node, circuit_size) }); } } - None => {} } } @@ -463,7 +460,7 @@ impl<'a> Circuit<'a> { Some(super_position) => Ok(Measurement::NonObservable(super_position)), None => { Err(QuantrError { - message: format!("The circuit has not been simulated. Call Circuit::simulate before calling this method, Circuit::get_superposition.") + message: "The circuit has not been simulated. Call Circuit::simulate before calling this method, Circuit::get_superposition.".to_string(), }) } } @@ -516,7 +513,7 @@ impl<'a> Circuit<'a> { let mut cummalitive: f64 = 0f64; let dice_roll: f64 = fastrand::f64(); for (state_label, probability) in &probabilities { - cummalitive = cummalitive + probability; + cummalitive += probability; if dice_roll < cummalitive { match bin_count.get(state_label) { Some(previous_count) => bin_count.insert(state_label.clone(), previous_count+1), @@ -530,7 +527,7 @@ impl<'a> Circuit<'a> { }, None => { Err(QuantrError { - message: format!("The circuit has not been simulated. Call Circuit::simulate before calling this method, Circuit::repeat_measurement.") + message: "The circuit has not been simulated. Call Circuit::simulate before calling this method, Circuit::repeat_measurement.".to_string(), }) }, } diff --git a/src/circuit/printer.rs b/src/circuit/printer.rs index 36252e1..39e0b58 100644 --- a/src/circuit/printer.rs +++ b/src/circuit/printer.rs @@ -106,7 +106,7 @@ impl Printer<'_> { /// ``` pub fn save_diagram(&mut self, file_path: &str) -> std::io::Result<()> { let path: &Path = Path::new(file_path); - let mut file = File::create(&path)?; + let mut file = File::create(path)?; file.write_all(self.get_or_make_diagram().as_bytes()) } @@ -132,7 +132,7 @@ impl Printer<'_> { println!("{}", diagram); let path = Path::new(file_path); - let mut file = File::create(&path)?; + let mut file = File::create(path)?; file.write_all(diagram.as_bytes()) } @@ -188,14 +188,14 @@ impl Printer<'_> { ); } else { // Deals with single gates - Self::draw_single_gates(&mut printed_diagram, diagram_schematic); + Self::draw_single_gates(printed_diagram.as_mut_slice(), diagram_schematic); } } // Collect all the strings to return a single string giving the diagram let final_diagram = printed_diagram .into_iter() - .fold(String::from(""), |acc, line| acc + &line + &"\n"); + .fold(String::from(""), |acc, line| acc + &line + "\n"); self.diagram = Some(final_diagram.clone()); @@ -212,12 +212,12 @@ impl Printer<'_> { ) -> (Vec>, usize) { let mut gates_infos: Vec = Default::default(); let mut longest_name_length: usize = 1usize; - for gate in gates_column.into_iter() { + for gate in gates_column.iter() { let gate_size: GateSize = super::Circuit::classify_gate_size(gate); let gate_name: String = Self::get_gate_name(gate); let gate_name_length: usize = gate_name.len(); if gate_name_length > longest_name_length { - longest_name_length = gate_name_length.clone() + longest_name_length = gate_name_length; } gates_infos.push(GatePrinterInfo { gate_size, @@ -261,7 +261,7 @@ impl Printer<'_> { // Finds if there is a gate with one/multiple control nodes fn get_multi_gate<'a>( - gates: &Vec>, + gates: &[GatePrinterInfo<'a>], ) -> Option<(usize, GatePrinterInfo<'a>)> { for (pos, gate_info) in gates.iter().enumerate() { match gate_info.gate_size { @@ -273,7 +273,7 @@ impl Printer<'_> { } // Draw a column of single gates - fn draw_single_gates(row_schematics: &mut Vec, diagram_scheme: DiagramSchema) { + fn draw_single_gates(row_schematics: &mut [String], diagram_scheme: DiagramSchema) { for (pos, gate_info) in diagram_scheme.gate_info_column.iter().enumerate() { let padding: usize = diagram_scheme.longest_name_length - gate_info.gate_name_length; let cache: RowSchematic = match gate_info.gate { @@ -286,12 +286,12 @@ impl Printer<'_> { _ => RowSchematic { top: "┏━".to_string() + &"━".repeat(gate_info.gate_name_length) - + &"━┓" + + "━┓" + &" ".repeat(padding), - name: "┨ ".to_string() + &gate_info.gate_name + &" ┠" + &"─".repeat(padding), + name: "┨ ".to_string() + &gate_info.gate_name + " ┠" + &"─".repeat(padding), bottom: "┗━".to_string() + &"━".repeat(gate_info.gate_name_length) - + &"━┛" + + "━┛" + &" ".repeat(padding), connection: " ".repeat(diagram_scheme.longest_name_length + 4), }, @@ -301,9 +301,9 @@ impl Printer<'_> { } // Draw a single column containing a multigate function. - fn draw_multi_gates<'a>( - row_schematics: &mut Vec, - multi_gate_info: GatePrinterInfo<'a>, + fn draw_multi_gates( + row_schematics: &mut [String], + multi_gate_info: GatePrinterInfo<'_>, column_size: &usize, position: usize, ) { @@ -330,8 +330,8 @@ impl Printer<'_> { "━" } + &"━".repeat(multi_gate_info.gate_name_length - 1) - + &"━┓", - name: "┨ ".to_string() + &multi_gate_info.gate_name + &" ┠", + + "━┓", + name: "┨ ".to_string() + &multi_gate_info.gate_name + " ┠", bottom: "┗━".to_string() + if position < extreme_nodes.max { "┯" @@ -339,7 +339,7 @@ impl Printer<'_> { "━" } + &"━".repeat(multi_gate_info.gate_name_length - 1) - + &"━┛", + + "━┛", connection: " ".to_string() + if position < extreme_nodes.max { "│" @@ -393,7 +393,7 @@ impl Printer<'_> { // Adds a gate to the vector of strings. fn add_string_to_schematic( - schematic: &mut Vec, + schematic: &mut [String], row_schem_num: usize, cache: RowSchematic, ) { diff --git a/src/circuit/simulation.rs b/src/circuit/simulation.rs index e427980..9592682 100644 --- a/src/circuit/simulation.rs +++ b/src/circuit/simulation.rs @@ -80,7 +80,7 @@ impl<'a> Circuit<'a> { // the sum of states that are required to be added to the register let mut mapped_states: HashMap> = Default::default(); - for (prod_state, amp) in (®ister).into_iter() { + for (prod_state, amp) in register.into_iter() { //Looping through super position of register // Obtain superposition from applying gate from a specified wire onto the product state, and add control nodes if necersary diff --git a/src/circuit/states/product_states.rs b/src/circuit/states/product_states.rs index 7617188..915d7ae 100644 --- a/src/circuit/states/product_states.rs +++ b/src/circuit/states/product_states.rs @@ -154,7 +154,7 @@ impl ProductState { return Err(QuantrError { message: format!("The position of the binary digit, {}, is out of bounds. The product dimension is {}, and so the position must be strictly less.", place_num, self.num_qubits()) }); } - let old_qubit: Qubit = self.qubits[place_num].clone(); + let old_qubit: Qubit = self.qubits[place_num]; self.qubits[place_num] = if old_qubit == Qubit::Zero { Qubit::One } else { @@ -195,6 +195,7 @@ impl ProductState { /// /// assert_eq!(String::from("01"), prod.to_string()); /// ``` + #[allow(clippy::inherent_to_string)] pub fn to_string(&self) -> String { self.qubits .iter() @@ -215,7 +216,7 @@ impl ProductState { Qubit::Zero => 0u32, Qubit::One => 1 << pos, }) - .fold(0, |sum, i| sum + i) as usize + .sum::() as usize } // Produces a product states based on converting a base 10 number to binary, where the product diff --git a/src/circuit/states/super_positions.rs b/src/circuit/states/super_positions.rs index 7c5a95f..a230b87 100644 --- a/src/circuit/states/super_positions.rs +++ b/src/circuit/states/super_positions.rs @@ -206,7 +206,7 @@ impl SuperPosition { /// assert_eq!(complex_re!(1f64), superpos.get_amplitude_from_state(prod_state).unwrap()); /// ``` pub fn get_amplitude_from_state(&self, prod_state: ProductState) -> QResult> { - if 2usize << prod_state.qubits.len() - 1 != self.amplitudes.len() { + if 2usize << (prod_state.qubits.len() - 1) != self.amplitudes.len() { return Err(QuantrError { message: format!("Unable to retreive product state, |{:?}> with dimension {}. The superposition is a linear combination of states with different dimension. These dimensions should be equal.", prod_state.to_string(), prod_state.num_qubits()),}); } Ok(*self.amplitudes.get(prod_state.comp_basis()).unwrap()) @@ -329,11 +329,11 @@ impl SuperPosition { ) { let length: usize = vec_amplitudes.len(); let trailing_length: usize = length.trailing_zeros() as usize; - for i in 0..length { + for (i, amp) in vec_amplitudes.iter_mut().enumerate() { let key: ProductState = ProductState::binary_basis(i, trailing_length); match hash_amplitudes.get(&key) { - Some(val) => vec_amplitudes[i] = *val, - None => vec_amplitudes[i] = COMPLEX_ZERO, + Some(val) => *amp = *val, + None => *amp = COMPLEX_ZERO, } } } diff --git a/src/lib.rs b/src/lib.rs index 0687393..79386e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,39 +34,35 @@ //! ``` //! use quantr::{Circuit, Gate, Printer, Measurement::Observable}; //! -//! fn main() { +//! let mut quantum_circuit: Circuit = Circuit::new(2).unwrap(); +//! +//! quantum_circuit +//! .add_gates(&[Gate::H, Gate::Y]).unwrap() +//! .add_gate(Gate::CNot(0), 1).unwrap(); +//! +//! let mut printer = Printer::new(&quantum_circuit); +//! printer.print_diagram(); +//! // The above prints the following: +//! // ┏━━━┓ +//! // ┨ H ┠──█── +//! // ┗━━━┛ │ +//! // │ +//! // ┏━━━┓┏━┷━┓ +//! // ┨ Y ┠┨ X ┠ +//! // ┗━━━┛┗━━━┛ //! -//! let mut quantum_circuit: Circuit = Circuit::new(2).unwrap(); -//! -//! quantum_circuit -//! .add_gates(&[Gate::H, Gate::Y]).unwrap() -//! .add_gate(Gate::CNot(0), 1).unwrap(); -//! -//! let mut printer = Printer::new(&quantum_circuit); -//! printer.print_diagram(); -//! // The above prints the following: -//! // ┏━━━┓ -//! // ┨ H ┠──█── -//! // ┗━━━┛ │ -//! // │ -//! // ┏━━━┓┏━┷━┓ -//! // ┨ Y ┠┨ X ┠ -//! // ┗━━━┛┗━━━┛ +//! quantum_circuit.simulate(); //! -//! quantum_circuit.simulate(); +//! // Below prints the number of times that each state was observered +//! // over 500 measurements of superpositions. //! -//! // Below prints the number of times that each state was observered -//! // over 500 measurements of superpositions. -//! -//! if let Ok(Observable(bin_count)) = quantum_circuit.repeat_measurement(500) { -//! println!("[Observable] Bin count of observed states."); -//! for (state, count) in bin_count { -//! println!("|{}> observed {} times", state.to_string(), count); -//! } +//! if let Ok(Observable(bin_count)) = quantum_circuit.repeat_measurement(500) { +//! println!("[Observable] Bin count of observed states."); +//! for (state, count) in bin_count { +//! println!("|{}> observed {} times", state.to_string(), count); //! } -//! //! } -//! ``` +//! mod circuit; mod complex; From b7b95d0bb06e105ff449224e452aabb78b9cdf72 Mon Sep 17 00:00:00 2001 From: Andrew Barlow Date: Wed, 10 Jan 2024 11:56:07 +0000 Subject: [PATCH 06/10] Change arguments of standard gate operations The arguments have been changed for the internal gate operations so that the kronecker product is no longer needed. This should help computational speed. Signed-off-by: Andrew Barlow --- src/circuit.rs | 9 +++----- src/circuit/printer.rs | 4 +--- src/circuit/simulation.rs | 22 ++++++++----------- src/circuit/standard_gate_ops.rs | 37 +++++++++++++------------------- src/lib.rs | 4 ++-- 5 files changed, 30 insertions(+), 46 deletions(-) diff --git a/src/circuit.rs b/src/circuit.rs index 0c079c6..f147454 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -96,7 +96,7 @@ impl<'a> Circuit<'a> { self.num_qubits } - /// Returns the vector of gates that have been added to the circuit. + /// Returns the vector of gates that have been added to the circuit. /// /// It is a flattened vector which is buffered with identity gates. /// @@ -111,7 +111,7 @@ impl<'a> Circuit<'a> { /// ``` pub fn get_gates(&self) -> &[Gate<'a>] { self.circuit_gates.as_slice() - } + } /// Adds a single gate to the circuit. /// @@ -288,10 +288,7 @@ impl<'a> Circuit<'a> { // check for overlapping control nodes. if Self::contains_repeating_values(circuit_size, &nodes) { return Err(QuantrError { - message: format!( - "The gate, {:?}, has overlapping control nodes.", - gate - ), + message: format!("The gate, {:?}, has overlapping control nodes.", gate), }); } if nodes.contains(&pos) { diff --git a/src/circuit/printer.rs b/src/circuit/printer.rs index 39e0b58..50b889e 100644 --- a/src/circuit/printer.rs +++ b/src/circuit/printer.rs @@ -260,9 +260,7 @@ impl Printer<'_> { } // Finds if there is a gate with one/multiple control nodes - fn get_multi_gate<'a>( - gates: &[GatePrinterInfo<'a>], - ) -> Option<(usize, GatePrinterInfo<'a>)> { + fn get_multi_gate<'a>(gates: &[GatePrinterInfo<'a>]) -> Option<(usize, GatePrinterInfo<'a>)> { for (pos, gate_info) in gates.iter().enumerate() { match gate_info.gate_size { GateSize::Single => (), diff --git a/src/circuit/simulation.rs b/src/circuit/simulation.rs index 9592682..1e0d144 100644 --- a/src/circuit/simulation.rs +++ b/src/circuit/simulation.rs @@ -160,17 +160,15 @@ impl<'a> Circuit<'a> { if let Gate::CR(angle, control) = double_gate.name { positions.push(control); standard_gate_ops::cr( - prod_state - .get_unchecked(control) - .kronecker_prod(prod_state.get_unchecked(double_gate.position)), + prod_state.get_unchecked(control), + prod_state.get_unchecked(double_gate.position), angle, ) } else if let Gate::CRk(k, control) = double_gate.name { positions.push(control); standard_gate_ops::crk( - prod_state - .get_unchecked(control) - .kronecker_prod(prod_state.get_unchecked(double_gate.position)), + prod_state.get_unchecked(control), + prod_state.get_unchecked(double_gate.position), k, ) } else { @@ -197,9 +195,8 @@ impl<'a> Circuit<'a> { positions.push(control_node); operator( - prod_state - .get_unchecked(control_node) - .kronecker_prod(prod_state.get_unchecked(double_gate.position)), + prod_state.get_unchecked(control_node), + prod_state.get_unchecked(double_gate.position), ) } } @@ -218,10 +215,9 @@ impl<'a> Circuit<'a> { positions.push(control_node_two); positions.push(control_node_one); operator( - prod_state - .get_unchecked(control_node_one) - .kronecker_prod(prod_state.get_unchecked(control_node_two)) - .kronecker_prod(prod_state.get_unchecked(triple_gate.position)), + prod_state.get_unchecked(control_node_one), + prod_state.get_unchecked(control_node_two), + prod_state.get_unchecked(triple_gate.position), ) } diff --git a/src/circuit/standard_gate_ops.rs b/src/circuit/standard_gate_ops.rs index b71111c..af8e63c 100644 --- a/src/circuit/standard_gate_ops.rs +++ b/src/circuit/standard_gate_ops.rs @@ -13,7 +13,7 @@ //! These linear functions are defined by how they act on product states of qubits. Defining the //! mappings on a basis defines how the gates act on larger product spaces. -use crate::states::{ProductState, Qubit, SuperPosition}; +use crate::states::{Qubit, SuperPosition}; use crate::Complex; use crate::{complex, complex_im, complex_im_array, complex_re, complex_re_array, COMPLEX_ZERO}; use std::f64::consts::FRAC_1_SQRT_2; @@ -194,9 +194,8 @@ pub fn pauli_z(register: Qubit) -> SuperPosition { // #[rustfmt::skip] -pub fn cnot(register: ProductState) -> SuperPosition { - let input_register: [Qubit; 2] = [register.qubits[0], register.qubits[1]]; - SuperPosition::new_with_register_unchecked::<4>(match input_register { +pub fn cnot(qubit_one: Qubit, qubit_two: Qubit) -> SuperPosition { + SuperPosition::new_with_register_unchecked::<4>(match [qubit_one, qubit_two] { [Qubit::Zero, Qubit::Zero] => complex_re_array!(1f64, 0f64, 0f64, 0f64), [Qubit::Zero, Qubit::One] => complex_re_array!(0f64, 1f64, 0f64, 0f64), [Qubit::One, Qubit::Zero] => complex_re_array!(0f64, 0f64, 0f64, 1f64), @@ -205,9 +204,8 @@ pub fn cnot(register: ProductState) -> SuperPosition { } #[rustfmt::skip] -pub fn cy(register: ProductState) -> SuperPosition { - let input_register: [Qubit; 2] = [register.qubits[0], register.qubits[1]]; - SuperPosition::new_with_register_unchecked::<4>(match input_register { +pub fn cy(qubit_one: Qubit, qubit_two: Qubit) -> SuperPosition { + SuperPosition::new_with_register_unchecked::<4>(match [qubit_one, qubit_two] { [Qubit::Zero, Qubit::Zero] => complex_re_array!(1f64, 0f64, 0f64, 0f64), [Qubit::Zero, Qubit::One] => complex_re_array!(0f64, 1f64, 0f64, 0f64), [Qubit::One, Qubit::Zero] => complex_im_array!(0f64, 0f64, 0f64, 1f64), @@ -216,9 +214,8 @@ pub fn cy(register: ProductState) -> SuperPosition { } #[rustfmt::skip] -pub fn cz(register: ProductState) -> SuperPosition { - let input_register: [Qubit; 2] = [register.qubits[0], register.qubits[1]]; - SuperPosition::new_with_register_unchecked::<4>(match input_register { +pub fn cz(qubit_one: Qubit, qubit_two: Qubit) -> SuperPosition { + SuperPosition::new_with_register_unchecked::<4>(match [qubit_one, qubit_two] { [Qubit::Zero, Qubit::Zero] => complex_re_array!(1f64, 0f64, 0f64, 0f64), [Qubit::Zero, Qubit::One] => complex_re_array!(0f64, 1f64, 0f64, 0f64), [Qubit::One, Qubit::Zero] => complex_re_array!(0f64, 0f64, 1f64, 0f64), @@ -227,9 +224,8 @@ pub fn cz(register: ProductState) -> SuperPosition { } #[rustfmt::skip] -pub fn swap(register: ProductState) -> SuperPosition { - let input_register: [Qubit; 2] = [register.qubits[0], register.qubits[1]]; - SuperPosition::new_with_register_unchecked::<4>(match input_register { +pub fn swap(qubit_one: Qubit, qubit_two: Qubit) -> SuperPosition { + SuperPosition::new_with_register_unchecked::<4>(match [qubit_one, qubit_two] { [Qubit::Zero, Qubit::Zero] => complex_re_array!(1f64, 0f64, 0f64, 0f64), [Qubit::Zero, Qubit::One] => complex_re_array!(0f64, 0f64, 1f64, 0f64), [Qubit::One, Qubit::Zero] => complex_re_array!(0f64, 1f64, 0f64, 0f64), @@ -238,10 +234,9 @@ pub fn swap(register: ProductState) -> SuperPosition { } #[rustfmt::skip] -pub fn cr(register: ProductState, angle: f64) -> SuperPosition { - let input_register: [Qubit; 2] = [register.qubits[0], register.qubits[1]]; +pub fn cr(qubit_one: Qubit, qubit_two: Qubit, angle: f64) -> SuperPosition { let exp_array: [Complex; 4] = [COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, Complex::::exp_im(angle)]; - SuperPosition::new_with_register_unchecked::<4>(match input_register { + SuperPosition::new_with_register_unchecked::<4>(match [qubit_one, qubit_two] { [Qubit::Zero, Qubit::Zero] => complex_re_array!(1f64, 0f64, 0f64, 0f64), [Qubit::Zero, Qubit::One] => complex_re_array!(0f64, 1f64, 0f64, 0f64), [Qubit::One, Qubit::Zero] => complex_re_array!(0f64, 0f64, 1f64, 0f64), @@ -250,11 +245,10 @@ pub fn cr(register: ProductState, angle: f64) -> SuperPosition { } #[rustfmt::skip] -pub fn crk(register: ProductState, k: i32) -> SuperPosition { - let input_register: [Qubit; 2] = [register.qubits[0], register.qubits[1]]; +pub fn crk(qubit_one: Qubit, qubit_two: Qubit, k: i32) -> SuperPosition { let exp_array: [Complex; 4] = [COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, Complex::::exp_im((2f64*std::f64::consts::PI).div(2f64.powi(k)))]; - SuperPosition::new_with_register_unchecked::<4>(match input_register { + SuperPosition::new_with_register_unchecked::<4>(match [qubit_one, qubit_two] { [Qubit::Zero, Qubit::Zero] => complex_re_array!(1f64, 0f64, 0f64, 0f64), [Qubit::Zero, Qubit::One] => complex_re_array!(0f64, 1f64, 0f64, 0f64), [Qubit::One, Qubit::Zero] => complex_re_array!(0f64, 0f64, 1f64, 0f64), @@ -267,9 +261,8 @@ pub fn crk(register: ProductState, k: i32) -> SuperPosition { // #[rustfmt::skip] -pub fn toffoli(register: ProductState) -> SuperPosition { - let input_register: [Qubit; 3] = [register.qubits[0], register.qubits[1], register.qubits[2]]; - SuperPosition::new_with_register_unchecked::<8>(match input_register { +pub fn toffoli(qubit_one: Qubit, qubit_two: Qubit, qubit_three: Qubit) -> SuperPosition { + SuperPosition::new_with_register_unchecked::<8>(match [qubit_one, qubit_two, qubit_three] { [Qubit::Zero, Qubit::Zero, Qubit::Zero] => { complex_re_array!(1f64, 0f64, 0f64, 0f64, 0f64, 0f64, 0f64, 0f64) } [Qubit::Zero, Qubit::Zero, Qubit::One] => { complex_re_array!(0f64, 1f64, 0f64, 0f64, 0f64, 0f64, 0f64, 0f64) } [Qubit::Zero, Qubit::One, Qubit::Zero] => { complex_re_array!(0f64, 0f64, 1f64, 0f64, 0f64, 0f64, 0f64, 0f64) } diff --git a/src/lib.rs b/src/lib.rs index 79386e3..1cd6b11 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,11 +35,11 @@ //! use quantr::{Circuit, Gate, Printer, Measurement::Observable}; //! //! let mut quantum_circuit: Circuit = Circuit::new(2).unwrap(); -//! +//! //! quantum_circuit //! .add_gates(&[Gate::H, Gate::Y]).unwrap() //! .add_gate(Gate::CNot(0), 1).unwrap(); -//! +//! //! let mut printer = Printer::new(&quantum_circuit); //! printer.print_diagram(); //! // The above prints the following: From 95906fd05222349b713dcc7c706451323b9aaa5e Mon Sep 17 00:00:00 2001 From: Andrew Barlow Date: Wed, 10 Jan 2024 12:05:58 +0000 Subject: [PATCH 07/10] Update CHANGELOG Signed-off-by: Andrew Barlow --- CHANGELOG.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2116437..a71ecf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,14 +5,19 @@ This file logs the versions of quantr. ## 0.4.1 - More optimisations Edited the README to include "No parallelisation" to limitations, and -reduced the tractable number of qubit simulations to 18. +reduced the tractable number of qubit simulations to 18. There has also +been a general clean up of the code, with the help of `cargo clippy`. Change of dependency: -- For the random +- The `rand` crate has been swapped with `fastrand` which decreases + compilation time. Optimisations: +- The definition of the gates in `standard_gate_ops.rs` have had there + arguments changed so that the `kronecker_prod` is not used; increasing + speed for double gate processing. - The main simulating algorithm has been updated to increase it's speed, mostly bypassing computations that are uneeded, for instance product state qubits are flipped only if they are indeed different. From 3272216500269c56128c9f59d3e73a62f98efc51 Mon Sep 17 00:00:00 2001 From: Andrew Barlow Date: Thu, 11 Jan 2024 15:54:55 +0000 Subject: [PATCH 08/10] Restructre code for better maintainability The `GateSize` enum has been replaced with `GateCategories` that makes better use of match statements, by allowing a completely exhaustive pattern. This has reduced code massively, in addition to reducing the number of panic statements and unnecessary branches in the match statements. The GateSize was used in the printer struct, however that's been removed too. More code cleaning needs to be done for this file, for maintainability reasons, but this will be left for a future update. Lastly, most functions that linked the gates to functions, names or returning control nodes have been moved to `gates.rs`. Signed-off-by: Andrew Barlow --- src/circuit.rs | 42 ++-- src/circuit/gate.rs | 117 ++++++++- src/circuit/printer.rs | 42 +--- src/circuit/simulation.rs | 228 ++++-------------- src/circuit/standard_gate_ops.rs | 8 - .../states/super_positions_unchecked.rs | 1 + tests/grovers.rs | 2 + tests/qft.rs | 2 + 8 files changed, 189 insertions(+), 253 deletions(-) diff --git a/src/circuit.rs b/src/circuit.rs index f147454..344764a 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -11,10 +11,11 @@ // Added only for silencing deprecated warnings for using public fields of `Circuit`. #![allow(deprecated)] -use super::circuit::gate::{GateInfo, GateSize}; +use super::circuit::gate::{GateCategory, GateInfo}; use crate::states::{ProductState, SuperPosition}; use crate::{Gate, QuantrError}; use std::collections::HashMap; +use std::iter::zip; pub mod gate; pub mod printer; @@ -43,6 +44,7 @@ pub struct Circuit<'a> { #[deprecated( note = "This field will be made private to the user, where it will be given pub(crate) status in the next major update. Use Circuit::get_gates instead." )] + // Change this to Vec in next major update. pub circuit_gates: Vec>, #[deprecated( note = "This field will be made private to the user, where it will be given pub(crate) status in the next major update. Use Circuit::get_num_qubits instead." @@ -261,14 +263,11 @@ impl<'a> Circuit<'a> { gates.extend(extended_vec) } else { for (pos, gate) in gates.iter().enumerate() { - match Self::classify_gate_size(gate) { - GateSize::Double | GateSize::Triple | GateSize::Custom => { - let mut temp_vec = vec![Gate::Id; gates.len()]; - temp_vec[pos] = gate.clone(); - extended_vec.extend(temp_vec); - multi_gate_positions.push(pos); - } - _ => {} + if !gate.is_single_gate() { + let mut temp_vec = vec![Gate::Id; gates.len()]; + temp_vec[pos] = gate.clone(); + extended_vec.extend(temp_vec); + multi_gate_positions.push(pos); } } @@ -395,28 +394,33 @@ impl<'a> Circuit<'a> { let mut qubit_counter: usize = 0; let number_gates: usize = self.circuit_gates.len(); + // This will removed in next major update, as the circuit will directly store this. Instead + // of what's happening now, in which the gates are being copied into another wapper. + let mut categorised_gates: Vec = Vec::with_capacity(number_gates); + for gate in &self.circuit_gates { + categorised_gates.push(Gate::linker(gate)); + } + if self.config_progress { - println!("Starting circuit simulation...") + println!("Starting circuit simulation..."); } // Loop through each gate of circuit from starting at top row to bottom, then moving onto the next. - for gate in &self.circuit_gates { + for (cat_gate, gate) in zip(categorised_gates, &self.circuit_gates) { + if cat_gate == GateCategory::Identity { + qubit_counter += 1; + continue; + } + let gate_pos: usize = qubit_counter % self.num_qubits; if self.config_progress { Self::print_circuit_log(gate, &gate_pos, &qubit_counter, &number_gates); } - if *gate == Gate::Id { - qubit_counter += 1; - continue; - } - - let gate_class: GateSize = Self::classify_gate_size(gate); let gate_to_apply: GateInfo = GateInfo { - name: gate.clone(), + cat_gate, position: gate_pos, - size: gate_class, }; Circuit::apply_gate(gate_to_apply, &mut register); diff --git a/src/circuit/gate.rs b/src/circuit/gate.rs index 9a55b1f..66f4ac9 100644 --- a/src/circuit/gate.rs +++ b/src/circuit/gate.rs @@ -8,7 +8,8 @@ * Author: Andrew Rowan Barlow */ -use crate::states::{ProductState, SuperPosition}; +use crate::circuit::standard_gate_ops; +use crate::states::{ProductState, Qubit, SuperPosition}; /// Gates that can be added to a [crate::Circuit] struct. /// @@ -135,21 +136,117 @@ impl<'a> Gate<'a> { Gate::Custom(_, nodes, _) => Some(nodes.to_vec()), } } + + pub(crate) fn linker(&self) -> GateCategory { + match self { + Gate::Id => GateCategory::Identity, + Gate::H => GateCategory::Single(standard_gate_ops::hadamard), + Gate::S => GateCategory::Single(standard_gate_ops::phase), + Gate::Sdag => GateCategory::Single(standard_gate_ops::phasedag), + Gate::T => GateCategory::Single(standard_gate_ops::tgate), + Gate::Tdag => GateCategory::Single(standard_gate_ops::tgatedag), + Gate::X => GateCategory::Single(standard_gate_ops::pauli_x), + Gate::Y => GateCategory::Single(standard_gate_ops::pauli_y), + Gate::Z => GateCategory::Single(standard_gate_ops::pauli_z), + Gate::X90 => GateCategory::Single(standard_gate_ops::x90), + Gate::Y90 => GateCategory::Single(standard_gate_ops::y90), + Gate::MX90 => GateCategory::Single(standard_gate_ops::mx90), + Gate::MY90 => GateCategory::Single(standard_gate_ops::my90), + Gate::Rx(arg) => GateCategory::SingleArg(*arg, standard_gate_ops::rx), + Gate::Ry(arg) => GateCategory::SingleArg(*arg, standard_gate_ops::ry), + Gate::Rz(arg) => GateCategory::SingleArg(*arg, standard_gate_ops::rz), + Gate::Phase(arg) => GateCategory::SingleArg(*arg, standard_gate_ops::global_phase), + Gate::CNot(c) => GateCategory::Double(*c, standard_gate_ops::cnot), + Gate::Swap(c) => GateCategory::Double(*c, standard_gate_ops::swap), + Gate::CZ(c) => GateCategory::Double(*c, standard_gate_ops::cz), + Gate::CY(c) => GateCategory::Double(*c, standard_gate_ops::cy), + Gate::CR(arg, c) => GateCategory::DoubleArg(*arg, *c, standard_gate_ops::cr), + Gate::CRk(arg, c) => GateCategory::DoubleArgInt(*arg, *c, standard_gate_ops::crk), + Gate::Toffoli(c1, c2) => GateCategory::Triple(*c1, *c2, standard_gate_ops::toffoli), + Gate::Custom(func, controls, _) => GateCategory::Custom(*func, controls), + } + } + + // Helps in constructing a bundle. This ultimately makes the match statements more concise. + // Maybe best to see if this can be hardcoded in before hand; that is the bundles are added to + // the circuit instead? + pub(crate) fn is_single_gate(&self) -> bool { + match self { + Gate::Id + | Gate::H + | Gate::S + | Gate::Sdag + | Gate::T + | Gate::Tdag + | Gate::X + | Gate::Y + | Gate::Z + | Gate::Rx(_) + | Gate::Ry(_) + | Gate::Rz(_) + | Gate::Phase(_) + | Gate::X90 + | Gate::Y90 + | Gate::MX90 + | Gate::MY90 => true, + Gate::CNot(_) + | Gate::Swap(_) + | Gate::CZ(_) + | Gate::CY(_) + | Gate::CR(_, _) + | Gate::CRk(_, _) + | Gate::Toffoli(_, _) + | Gate::Custom(_, _, _) => false, + } + } + + pub(crate) fn get_name(&self) -> String { + match self { + Gate::Id => "".to_string(), + Gate::X => "X".to_string(), + Gate::H => "H".to_string(), + Gate::S => "S".to_string(), + Gate::Sdag => "S*".to_string(), + Gate::T => "T".to_string(), + Gate::Tdag => "T*".to_string(), + Gate::Y => "Y".to_string(), + Gate::Z => "Z".to_string(), + Gate::Rx(_) => "Rx".to_string(), + Gate::Ry(_) => "Ry".to_string(), + Gate::Rz(_) => "Rz".to_string(), + Gate::Phase(_) => "P".to_string(), + Gate::X90 => "X90".to_string(), + Gate::Y90 => "Y90".to_string(), + Gate::MX90 => "X90*".to_string(), + Gate::MY90 => "Y90*".to_string(), + Gate::CR(_, _) => "CR".to_string(), + Gate::CRk(_, _) => "CRk".to_string(), + Gate::Swap(_) => "Sw".to_string(), + Gate::CZ(_) => "Z".to_string(), + Gate::CY(_) => "Y".to_string(), + Gate::CNot(_) => "X".to_string(), + Gate::Toffoli(_, _) => "X".to_string(), + Gate::Custom(_, _, name) => name.to_string(), + } + } } -/// For identifying which gates are single, double etc. -#[derive(Debug, Clone)] -pub(crate) enum GateSize { - Single, - Double, - Triple, - Custom, +// Contain second variant that references the function in standard_gate_ops.rs +#[derive(PartialEq, Debug)] +pub(crate) enum GateCategory<'a> { + Identity, + Single(fn(Qubit) -> SuperPosition), + SingleArg(f64, fn(Qubit, f64) -> SuperPosition), + Double(usize, fn(Qubit, Qubit) -> SuperPosition), + DoubleArg(f64, usize, fn(Qubit, Qubit, f64) -> SuperPosition), + DoubleArgInt(i32, usize, fn(Qubit, Qubit, i32) -> SuperPosition), + Triple(usize, usize, fn(Qubit, Qubit, Qubit) -> SuperPosition), + Custom(fn(ProductState) -> Option, &'a [usize]), } /// Bundles the gate and position together. #[derive(Debug)] pub(crate) struct GateInfo<'a> { - pub name: Gate<'a>, + pub cat_gate: GateCategory<'a>, pub position: usize, - pub size: GateSize, } diff --git a/src/circuit/printer.rs b/src/circuit/printer.rs index 50b889e..5f3ab4a 100644 --- a/src/circuit/printer.rs +++ b/src/circuit/printer.rs @@ -8,7 +8,7 @@ * Author: Andrew Rowan Barlow */ -use super::{Circuit, Gate, GateSize}; +use super::{Circuit, Gate}; use std::fs::File; use std::io::Write; use std::path::Path; @@ -38,7 +38,6 @@ struct RowSchematic { #[derive(Clone)] struct GatePrinterInfo<'a> { - gate_size: GateSize, gate_name: String, gate_name_length: usize, gate: &'a Gate<'a>, @@ -213,14 +212,12 @@ impl Printer<'_> { let mut gates_infos: Vec = Default::default(); let mut longest_name_length: usize = 1usize; for gate in gates_column.iter() { - let gate_size: GateSize = super::Circuit::classify_gate_size(gate); - let gate_name: String = Self::get_gate_name(gate); + let gate_name: String = gate.get_name(); let gate_name_length: usize = gate_name.len(); if gate_name_length > longest_name_length { longest_name_length = gate_name_length; } gates_infos.push(GatePrinterInfo { - gate_size, gate_name, gate_name_length, gate, @@ -229,42 +226,11 @@ impl Printer<'_> { (gates_infos, longest_name_length) } - fn get_gate_name(gate: &Gate) -> String { - match gate { - Gate::Id => "".to_string(), - Gate::X => "X".to_string(), - Gate::H => "H".to_string(), - Gate::S => "S".to_string(), - Gate::Sdag => "S*".to_string(), - Gate::T => "T".to_string(), - Gate::Tdag => "T*".to_string(), - Gate::Y => "Y".to_string(), - Gate::Z => "Z".to_string(), - Gate::Rx(_) => "Rx".to_string(), - Gate::Ry(_) => "Ry".to_string(), - Gate::Rz(_) => "Rz".to_string(), - Gate::Phase(_) => "P".to_string(), - Gate::X90 => "X90".to_string(), - Gate::Y90 => "Y90".to_string(), - Gate::MX90 => "X90*".to_string(), - Gate::MY90 => "Y90*".to_string(), - Gate::CR(_, _) => "CR".to_string(), - Gate::CRk(_, _) => "CRk".to_string(), - Gate::Swap(_) => "Sw".to_string(), - Gate::CZ(_) => "Z".to_string(), - Gate::CY(_) => "Y".to_string(), - Gate::CNot(_) => "X".to_string(), - Gate::Toffoli(_, _) => "X".to_string(), - Gate::Custom(_, _, name) => name.to_string(), - } - } - // Finds if there is a gate with one/multiple control nodes fn get_multi_gate<'a>(gates: &[GatePrinterInfo<'a>]) -> Option<(usize, GatePrinterInfo<'a>)> { for (pos, gate_info) in gates.iter().enumerate() { - match gate_info.gate_size { - GateSize::Single => (), - _ => return Some((pos, gate_info.clone())), + if !gate_info.gate.is_single_gate() { + return Some((pos, gate_info.clone())); } } None diff --git a/src/circuit/simulation.rs b/src/circuit/simulation.rs index 1e0d144..473b173 100644 --- a/src/circuit/simulation.rs +++ b/src/circuit/simulation.rs @@ -8,10 +8,10 @@ * Author: Andrew Rowan Barlow */ -use super::{standard_gate_ops, GateInfo, GateSize, ZERO_MARGIN}; -use crate::states::{ProductState, Qubit, SuperPosition}; +use super::gate::GateCategory; +use super::{GateInfo, ZERO_MARGIN}; +use crate::states::{ProductState, SuperPosition}; use crate::{Circuit, Complex, Gate}; -use core::panic; use std::collections::HashMap; use std::ops::{Add, Mul}; @@ -23,54 +23,19 @@ impl<'a> Circuit<'a> { qubit_counter: &usize, number_gates: &usize, ) { - if gate != &Gate::Id { - println!( - "Applying {:?} on wire {} # {}/{} ", - gate, - gate_pos, - qubit_counter + 1, - number_gates - ); - } + println!( + "Applying {:?} on wire {} # {}/{} ", + gate, + gate_pos, + qubit_counter + 1, + number_gates + ); if *qubit_counter + 1 == *number_gates { println!("Finished circuit simulation.") } } - // Helps in constructing a bundle. This ultimately makes the match statements more concise. - // Maybe best to see if this can be hardcoded in before hand; that is the bundles are added to - // the circuit instead? - pub(crate) fn classify_gate_size(gate: &Gate) -> GateSize { - match gate { - Gate::Id - | Gate::H - | Gate::S - | Gate::Sdag - | Gate::T - | Gate::Tdag - | Gate::X - | Gate::Y - | Gate::Z - | Gate::Rx(_) - | Gate::Ry(_) - | Gate::Rz(_) - | Gate::Phase(_) - | Gate::X90 - | Gate::Y90 - | Gate::MX90 - | Gate::MY90 => GateSize::Single, - Gate::CNot(_) - | Gate::Swap(_) - | Gate::CZ(_) - | Gate::CY(_) - | Gate::CR(_, _) - | Gate::CRk(_, _) => GateSize::Double, - Gate::Toffoli(_, _) => GateSize::Triple, - Gate::Custom(_, _, _) => GateSize::Custom, - } - } - // The main algorithm and impetus for this project. // // This takes linear mappings defined on how they act on the basis of their product space, to @@ -86,25 +51,43 @@ impl<'a> Circuit<'a> { // Obtain superposition from applying gate from a specified wire onto the product state, and add control nodes if necersary let mut acting_positions: Vec = Vec::::with_capacity(3); // change to array for increased speed? - let wrapped_super_pos: Option = match gate.size { - GateSize::Single => Some(Self::single_gate_on_wire(&gate, &prod_state)), - GateSize::Double => Some(Self::double_gate_on_wires( - &gate, - &prod_state, - &mut acting_positions, - )), - GateSize::Triple => Some(Self::triple_gate_on_wires( - &gate, - &prod_state, - &mut acting_positions, - )), - GateSize::Custom => { - Self::custom_gate_on_wires(&gate, &prod_state, &mut acting_positions) + let wrapped_super_pos: Option = match gate.cat_gate { + GateCategory::Identity => None, + GateCategory::Single(func) => Some(func(prod_state.get_qubits()[gate.position])), + GateCategory::SingleArg(arg, func) => { + Some(func(prod_state.get_qubits()[gate.position], arg)) + } + GateCategory::Double(c, func) => { + acting_positions.push(c); + let qubits = prod_state.get_qubits(); + Some(func(qubits[c], qubits[gate.position])) + } + GateCategory::DoubleArg(arg, c, func) => { + acting_positions.push(c); + let qubits = prod_state.get_qubits(); + Some(func(qubits[c], qubits[gate.position], arg)) + } + GateCategory::DoubleArgInt(arg_int, c, func) => { + acting_positions.push(c); + let qubits = prod_state.get_qubits(); + Some(func(qubits[c], qubits[gate.position], arg_int)) + } + GateCategory::Triple(c1, c2, func) => { + acting_positions.push(c2); + acting_positions.push(c1); + let qubits = prod_state.get_qubits(); + Some(func(qubits[c1], qubits[c2], qubits[gate.position])) + } + GateCategory::Custom(func, controls) => { + acting_positions.extend(controls.iter().rev()); + Self::custom_gate_on_wires(func, controls, gate.position, &prod_state) } }; if let Some(super_pos) = wrapped_super_pos { - acting_positions.reverse(); + if !acting_positions.is_empty() { + acting_positions.reverse() + }; acting_positions.push(gate.position); Self::insert_gate_image_into_product_state( super_pos, @@ -119,136 +102,25 @@ impl<'a> Circuit<'a> { register.set_amplitudes_from_states_unchecked(mapped_states); } - // The following functions compartmentalise the algorithms for applying a gate to the - // register. - fn single_gate_on_wire(single_gate: &GateInfo, prod_state: &ProductState) -> SuperPosition { - if let Gate::Rx(angle) = single_gate.name { - standard_gate_ops::rx(prod_state.qubits[single_gate.position], angle) - } else if let Gate::Ry(angle) = single_gate.name { - standard_gate_ops::ry(prod_state.qubits[single_gate.position], angle) - } else if let Gate::Rz(angle) = single_gate.name { - standard_gate_ops::rz(prod_state.qubits[single_gate.position], angle) - } else if let Gate::Phase(angle) = single_gate.name { - standard_gate_ops::global_phase(prod_state.qubits[single_gate.position], angle) - } else { - let operator: fn(Qubit) -> SuperPosition = match single_gate.name { - Gate::Id => standard_gate_ops::identity, - Gate::H => standard_gate_ops::hadamard, - Gate::S => standard_gate_ops::phase, - Gate::Sdag => standard_gate_ops::phasedag, - Gate::T => standard_gate_ops::tgate, - Gate::Tdag => standard_gate_ops::tgatedag, - Gate::X => standard_gate_ops::pauli_x, - Gate::Y => standard_gate_ops::pauli_y, - Gate::Z => standard_gate_ops::pauli_z, - Gate::X90 => standard_gate_ops::x90, - Gate::Y90 => standard_gate_ops::y90, - Gate::MX90 => standard_gate_ops::mx90, - Gate::MY90 => standard_gate_ops::my90, - _ => panic!("Non single gate was passed to single gate operation function."), - }; - operator(prod_state.qubits[single_gate.position]) - } - } - - fn double_gate_on_wires( - double_gate: &GateInfo, - prod_state: &ProductState, - positions: &mut Vec, - ) -> SuperPosition { - // operator: fn(ProductState) -> SuperPosition - if let Gate::CR(angle, control) = double_gate.name { - positions.push(control); - standard_gate_ops::cr( - prod_state.get_unchecked(control), - prod_state.get_unchecked(double_gate.position), - angle, - ) - } else if let Gate::CRk(k, control) = double_gate.name { - positions.push(control); - standard_gate_ops::crk( - prod_state.get_unchecked(control), - prod_state.get_unchecked(double_gate.position), - k, - ) - } else { - let control_node: usize; - let operator = match double_gate.name { - Gate::CNot(control) => { - control_node = control; - standard_gate_ops::cnot - } - Gate::CZ(control) => { - control_node = control; - standard_gate_ops::cz - } - Gate::CY(control) => { - control_node = control; - standard_gate_ops::cy - } - Gate::Swap(control) => { - control_node = control; - standard_gate_ops::swap - } - _ => panic!("Non double gate was passed to double gate operation function."), - }; - - positions.push(control_node); - operator( - prod_state.get_unchecked(control_node), - prod_state.get_unchecked(double_gate.position), - ) - } - } - - fn triple_gate_on_wires( - triple_gate: &GateInfo, - prod_state: &ProductState, - positions: &mut Vec, - ) -> SuperPosition { - // operator: fn(ProductState) -> SuperPosition - let (operator, control_node_one, control_node_two) = match triple_gate.name { - Gate::Toffoli(control1, control2) => (standard_gate_ops::toffoli, control1, control2), - _ => panic!("Non triple gate was passed to triple gate operation function"), - }; - - positions.push(control_node_two); - positions.push(control_node_one); - operator( - prod_state.get_unchecked(control_node_one), - prod_state.get_unchecked(control_node_two), - prod_state.get_unchecked(triple_gate.position), - ) - } - fn custom_gate_on_wires( - custom_gate: &GateInfo, + operator: fn(ProductState) -> Option, + controls: &[usize], + position: usize, prod_state: &ProductState, - positions: &mut Vec, ) -> Option { - let (operator, controls) = match custom_gate.name { - Gate::Custom(func, control_map, _) => (func, control_map), - _ => panic!("Non custom gate was passed to custom gate operation function."), - }; - - let result_super: Option = if !controls.is_empty() { + if !controls.is_empty() { let mut concat_prodstate: ProductState = prod_state.get_unchecked(controls[0]).into(); for c in &controls[1..] { //converts product to larger product concat_prodstate = concat_prodstate.kronecker_prod(prod_state.get_unchecked(*c)); } - concat_prodstate = - concat_prodstate.kronecker_prod(prod_state.get_unchecked(custom_gate.position)); + concat_prodstate = concat_prodstate.kronecker_prod(prod_state.get_unchecked(position)); operator(concat_prodstate) } else { - operator(ProductState::from(prod_state.qubits[custom_gate.position])) - }; - - positions.extend(controls.iter().rev()); - - result_super + operator(ProductState::from(prod_state.qubits[position])) + } } fn insert_gate_image_into_product_state( diff --git a/src/circuit/standard_gate_ops.rs b/src/circuit/standard_gate_ops.rs index af8e63c..cdccf47 100644 --- a/src/circuit/standard_gate_ops.rs +++ b/src/circuit/standard_gate_ops.rs @@ -33,14 +33,6 @@ use std::ops::{Div, Mul}; // Single gates // -#[rustfmt::skip] -pub fn identity(register: Qubit) -> SuperPosition { - SuperPosition::new_with_register_unchecked::<2>(match register { - Qubit::Zero => [complex_re!(1f64), COMPLEX_ZERO], - Qubit::One => [COMPLEX_ZERO, complex_re!(1f64)], - }) -} - #[rustfmt::skip] pub fn hadamard(register: Qubit) -> SuperPosition { SuperPosition::new_with_register_unchecked::<2>(match register { diff --git a/src/circuit/states/super_positions_unchecked.rs b/src/circuit/states/super_positions_unchecked.rs index 5c3d2a1..35cee3b 100644 --- a/src/circuit/states/super_positions_unchecked.rs +++ b/src/circuit/states/super_positions_unchecked.rs @@ -24,6 +24,7 @@ impl SuperPosition { } } + // As only used in `standard_gate_ops`, could specify product_dim manually, saves computation. /// Used in standard_gate_ops.rs for defining the "standard gates".1 pub(crate) fn new_with_register_unchecked( amplitudes: [Complex; N], diff --git a/tests/grovers.rs b/tests/grovers.rs index bb6c3cd..39dbb17 100644 --- a/tests/grovers.rs +++ b/tests/grovers.rs @@ -19,6 +19,7 @@ const ERROR_MARGIN: f64 = 0.00000001f64; #[test] fn grovers_3qubit() -> Result<(), QuantrError> { + fastrand::seed(0); let mut circuit = Circuit::new(3)?; // Kick state into superposition of equal weights @@ -70,6 +71,7 @@ fn grovers_3qubit() -> Result<(), QuantrError> { #[test] fn x3sudoko() -> Result<(), QuantrError> { + fastrand::seed(0); let mut qc: Circuit = Circuit::new(10)?; qc.add_repeating_gate(Gate::H, &[0, 1, 2, 3, 4, 5])? diff --git a/tests/qft.rs b/tests/qft.rs index 676a8f8..f75ee8a 100644 --- a/tests/qft.rs +++ b/tests/qft.rs @@ -19,6 +19,7 @@ const ERROR_MARGIN: f64 = 0.00000001f64; #[test] fn simple_qft() -> Result<(), QuantrError> { + fastrand::seed(0); let mut qc: Circuit = Circuit::new(3)?; // Apply qft @@ -39,6 +40,7 @@ fn simple_qft() -> Result<(), QuantrError> { ]; if let Measurement::NonObservable(super_pos) = qc.get_superposition().unwrap() { + println!("{:?}", super_pos); compare_complex_lists_and_register(&correct_super, &super_pos); } From fb545a3355f7e3e098e708427d44139deca138db Mon Sep 17 00:00:00 2001 From: Andrew Barlow Date: Thu, 11 Jan 2024 15:56:49 +0000 Subject: [PATCH 09/10] Update README Signed-off-by: Andrew Barlow --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a71ecf1..a855329 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ This file logs the versions of quantr. Edited the README to include "No parallelisation" to limitations, and reduced the tractable number of qubit simulations to 18. There has also -been a general clean up of the code, with the help of `cargo clippy`. +been a large overhaul of the code increasing maintainability. Some +common mistakes were also fixed with the help of `cargo clippy`. Change of dependency: From 70ffb29913c0e4df919e55a3a2a10a7c85f6544e Mon Sep 17 00:00:00 2001 From: Andrew Barlow Date: Thu, 11 Jan 2024 16:14:36 +0000 Subject: [PATCH 10/10] Edit CHANGELOG Signed-off-by: Andrew Barlow --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a855329..bf90fa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ This file logs the versions of quantr. Edited the README to include "No parallelisation" to limitations, and reduced the tractable number of qubit simulations to 18. There has also -been a large overhaul of the code increasing maintainability. Some +been a large overhaul of the code to increase maintainability. Some common mistakes were also fixed with the help of `cargo clippy`. Change of dependency: @@ -18,7 +18,7 @@ Optimisations: - The definition of the gates in `standard_gate_ops.rs` have had there arguments changed so that the `kronecker_prod` is not used; increasing - speed for double gate processing. + speed for multi gate processing. - The main simulating algorithm has been updated to increase it's speed, mostly bypassing computations that are uneeded, for instance product state qubits are flipped only if they are indeed different.