From daaf49b326f3116a574e236b831edfd9ea27384a Mon Sep 17 00:00:00 2001 From: Daniel Hauck Date: Mon, 8 Nov 2021 00:24:22 +0100 Subject: [PATCH 1/3] Add MultiCNOT gate. --- qoqo/src/operations/mod.rs | 1 + .../operations/multi_qubit_gate_operations.rs | 8 +++ .../operations/multi_qubit_gate_operations.rs | 71 ++++++++++++++++++- 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/qoqo/src/operations/mod.rs b/qoqo/src/operations/mod.rs index a0e023b0..6b9d8eaa 100644 --- a/qoqo/src/operations/mod.rs +++ b/qoqo/src/operations/mod.rs @@ -179,5 +179,6 @@ pub fn operations(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; Ok(()) } diff --git a/qoqo/src/operations/multi_qubit_gate_operations.rs b/qoqo/src/operations/multi_qubit_gate_operations.rs index 03ec96cb..9a4d2d3f 100644 --- a/qoqo/src/operations/multi_qubit_gate_operations.rs +++ b/qoqo/src/operations/multi_qubit_gate_operations.rs @@ -34,3 +34,11 @@ pub struct MultiQubitMS { /// The angle of the multi qubit Molmer-Sorensen gate. theta: CalculatorFloat, } + +#[allow(clippy::upper_case_acronyms)] +#[wrap(Operate, OperateMultiQubit, OperateGate, OperateMultiQubitGate)] +/// The CNOT gate with multiple controls +pub struct MultiCNOT { + /// The qubits involved in the MultiCNOT gate. + qubits: Vec, +} diff --git a/roqoqo/src/operations/multi_qubit_gate_operations.rs b/roqoqo/src/operations/multi_qubit_gate_operations.rs index 674e1254..8b0f1b7c 100644 --- a/roqoqo/src/operations/multi_qubit_gate_operations.rs +++ b/roqoqo/src/operations/multi_qubit_gate_operations.rs @@ -14,7 +14,7 @@ use crate::operations; use crate::prelude::*; use crate::Circuit; use crate::RoqoqoError; -use ndarray::Array2; +use ndarray::prelude::*; use num_complex::Complex64; use qoqo_calculator::CalculatorFloat; #[cfg(feature = "overrotate")] @@ -86,3 +86,72 @@ impl OperateMultiQubitGate for MultiQubitMS { circuit } } + +#[allow(clippy::upper_case_acronyms)] +#[derive( + Debug, + Clone, + PartialEq, + roqoqo_derive::InvolveQubits, + roqoqo_derive::Operate, + roqoqo_derive::Substitute, + roqoqo_derive::OperateMultiQubit, +)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] +/// The CNOT gate with multiple controls +pub struct MultiCNOT { + qubits: Vec, +} + +#[allow(non_upper_case_globals)] +const TAGS_MultiCNOT: &[&str; 4] = &[ + "Operation", + "GateOperation", + "MultiQubitGateOperation", + "MultiCNOT", +]; + +impl OperateGate for MultiCNOT { + fn unitary_matrix(&self) -> Result, RoqoqoError> { + let dim = 2_usize.pow(self.qubits.len() as u32); + let mut array = Array2::eye(dim); + array + .slice_mut(s![dim - 2.., dim - 2..]) + .assign(&array![[0., 1.], [1., 0.]]); + Ok(array.map(|x| x.into())) + } +} + +impl OperateMultiQubitGate for MultiCNOT { + // https://en.wikipedia.org/wiki/Toffoli_gate#/media/File:Qcircuit_ToffolifromCNOT.svg + fn circuit(&self) -> Circuit { + let mut circuit = Circuit::new(); + match self.qubits().len() { + 2 => { + circuit += operations::CNOT::new(self.qubits[0], self.qubits[1]); + } + 3 => { + circuit += operations::Hadamard::new(self.qubits[2]); + circuit += operations::CNOT::new(self.qubits[1], self.qubits[2]); + circuit += + operations::PhaseShiftState1::new(self.qubits[2], -CalculatorFloat::FRAC_PI_4); + circuit += operations::CNOT::new(self.qubits[0], self.qubits[2]); + circuit += operations::TGate::new(self.qubits[2]); + circuit += operations::CNOT::new(self.qubits[1], self.qubits[2]); + circuit += + operations::PhaseShiftState1::new(self.qubits[2], -CalculatorFloat::FRAC_PI_4); + circuit += operations::CNOT::new(self.qubits[0], self.qubits[2]); + circuit += operations::TGate::new(self.qubits[1]); + circuit += operations::TGate::new(self.qubits[2]); + circuit += operations::Hadamard::new(self.qubits[2]); + circuit += operations::CNOT::new(self.qubits[0], self.qubits[1]); + circuit += operations::TGate::new(self.qubits[0]); + circuit += + operations::PhaseShiftState1::new(self.qubits[1], -CalculatorFloat::FRAC_PI_4); + circuit += operations::CNOT::new(self.qubits[0], self.qubits[1]); + } + _ => panic!("Only MultiCNOT gates with 2 or 3 controls can be turned into a circuit."), + } + circuit + } +} From f7ef1029256eccc2a2e3371f818cd29033684aca Mon Sep 17 00:00:00 2001 From: Daniel Hauck Date: Thu, 18 Nov 2021 23:04:55 +0100 Subject: [PATCH 2/3] Add tests for MultiCNOT operation in roqoqo. --- .../operations/multi_qubit_gate_operations.rs | 121 +++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/roqoqo/tests/integration/operations/multi_qubit_gate_operations.rs b/roqoqo/tests/integration/operations/multi_qubit_gate_operations.rs index c9c428fd..d49bcfc4 100644 --- a/roqoqo/tests/integration/operations/multi_qubit_gate_operations.rs +++ b/roqoqo/tests/integration/operations/multi_qubit_gate_operations.rs @@ -14,7 +14,7 @@ use std::collections::{HashMap, HashSet}; -use ndarray::array; +use ndarray::prelude::*; use num_complex::Complex64; use qoqo_calculator::Calculator; use qoqo_calculator::CalculatorFloat; @@ -296,3 +296,122 @@ fn test_rotatex_powercf(theta: CalculatorFloat, power: CalculatorFloat) { assert_eq!(power_gate, test_gate); assert_eq!(power_gate.theta(), test_gate.theta()); } + +/// Test circuit function of MultiCNOT +#[test_case(vec![0,1]; "two_qubit")] +#[test_case(vec![0,1,2]; "three_qubit")] +fn test_circuit_multi_cnot(qubits: Vec) { + let gate = MultiCNOT::new(qubits.clone()); + let c = gate.circuit(); + if qubits.len() == 2 { + let mut comparison_circuit = Circuit::new(); + comparison_circuit += CNOT::new(0, 1); + assert!(c == comparison_circuit); + } + if qubits.len() == 3 { + let mut comparison_circuit = Circuit::new(); + comparison_circuit += Hadamard::new(2); + comparison_circuit += CNOT::new(1, 2); + comparison_circuit += PhaseShiftState1::new(2, -CalculatorFloat::FRAC_PI_4); + comparison_circuit += CNOT::new(0, 2); + comparison_circuit += TGate::new(2); + comparison_circuit += CNOT::new(1, 2); + comparison_circuit += PhaseShiftState1::new(2, -CalculatorFloat::FRAC_PI_4); + comparison_circuit += CNOT::new(0, 2); + comparison_circuit += TGate::new(1); + comparison_circuit += TGate::new(2); + comparison_circuit += Hadamard::new(2); + comparison_circuit += CNOT::new(0, 1); + comparison_circuit += TGate::new(0); + comparison_circuit += PhaseShiftState1::new(1, -CalculatorFloat::FRAC_PI_4); + comparison_circuit += CNOT::new(0, 1); + assert!(c == comparison_circuit); + } +} + +#[test_case(2; "two_qubit")] +#[test_case(3; "three_qubit")] +#[test_case(4; "four_qubit")] +fn test_matrix_output_multi_cnot(num_qubits: usize) { + let gate = MultiCNOT::new((0..num_qubits).collect()); + let unit = gate.unitary_matrix().unwrap(); + let n = 2_usize.pow(num_qubits as u32); + for i in 0..n - 2 { + let mut v = Array1::zeros(n); + v[i] = Complex64::new(1., 0.); + let u = (&unit).dot(&v); + assert_eq!(v, u); + } + let mut v0 = Array1::zeros(n); + let mut v1 = Array1::zeros(n); + v0[n - 2] = Complex64::new(1., 0.); + v1[n - 1] = Complex64::new(1., 0.); + let u0 = (&unit).dot(&v0); + let u1 = (&unit).dot(&v1); + assert_eq!(u0, v1); + assert_eq!(u1, v0); +} + +#[test] +fn test_clone_partial_eq_multi_cnot() { + let qubits = vec![0, 1, 2]; + + let gate = MultiCNOT::new(qubits); + assert_eq!(gate.hqslang(), "MultiCNOT"); + assert_eq!( + gate.tags(), + &[ + "Operation", + "GateOperation", + "MultiQubitGateOperation", + "MultiCNOT", + ] + ); + assert!(!gate.is_parametrized()); + let gate2 = gate.clone(); + assert_eq!(gate2, gate); +} + +#[test] +fn test_substitute_multi_cnot() { + let qubits = vec![0, 1, 2]; + let gate = MultiCNOT::new(qubits); + let mut mapping: HashMap = std::collections::HashMap::new(); + let _ = mapping.insert(0, 1); + let _ = mapping.insert(1, 2); + let _ = mapping.insert(2, 0); + let remapped = gate.remap_qubits(&mapping).unwrap(); + let qubits = remapped.qubits(); + assert_eq!(qubits, &vec![1, 2, 0]); +} + +#[test] +fn test_substitute_error_multi_cnot() { + let qubits = vec![0, 1, 2]; + let gate = MultiCNOT::new(qubits); + let mut mapping: HashMap = std::collections::HashMap::new(); + let _ = mapping.insert(1, 2); + let _ = mapping.insert(2, 0); + let remapped = gate.remap_qubits(&mapping); + assert!(remapped.is_err()); +} + +#[test] +fn test_format_multi_cnot() { + let qubits = vec![0, 1, 2]; + let gate = MultiCNOT::new(qubits); + let string = format!("{:?}", gate); + assert!(string.contains("MultiCNOT")); +} + +#[test] +fn test_involved_qubits_multi_cnot() { + let qubits = vec![0, 1, 2]; + let gate = MultiCNOT::new(qubits); + let involved_qubits = gate.involved_qubits(); + let mut comp_set: HashSet = HashSet::new(); + let _ = comp_set.insert(0); + let _ = comp_set.insert(1); + let _ = comp_set.insert(2); + assert_eq!(involved_qubits, InvolvedQubits::Set(comp_set)); +} From bb01e4d4fbad3e32175b6f844e2d86ae1f18d755 Mon Sep 17 00:00:00 2001 From: Daniel Hauck Date: Wed, 23 Mar 2022 16:17:03 +0100 Subject: [PATCH 3/3] Add MultiCNOT tests to qoqo. --- .../operations/multi_qubit_gate_operations.rs | 107 +++++++++++++++++- 1 file changed, 106 insertions(+), 1 deletion(-) diff --git a/qoqo/tests/integration/operations/multi_qubit_gate_operations.rs b/qoqo/tests/integration/operations/multi_qubit_gate_operations.rs index 2543be75..918edad5 100644 --- a/qoqo/tests/integration/operations/multi_qubit_gate_operations.rs +++ b/qoqo/tests/integration/operations/multi_qubit_gate_operations.rs @@ -16,7 +16,7 @@ use numpy::PyArray2; use pyo3::prelude::*; use pyo3::Python; use qoqo::operations::convert_operation_to_pyobject; -use qoqo::operations::MultiQubitMSWrapper; +use qoqo::operations::{MultiCNOTWrapper, MultiQubitMSWrapper}; use qoqo::CircuitWrapper; use qoqo_calculator::Calculator; use qoqo_calculator::CalculatorFloat; @@ -95,6 +95,53 @@ fn test_new_multi_qubit_ms(input_operation: Operation, arguments: (Vec, f64 }) } +#[test_case(Operation::from(MultiCNOT::new(vec![0, 1])), vec![0, 1], "__eq__"; "MultiCNOT_eq")] +#[test_case(Operation::from(MultiCNOT::new(vec![2, 3])), vec![0, 1], "__ne__"; "MultiCNOT_ne")] +fn test_new_multi_cnot(input_operation: Operation, arguments: Vec, method: &str) { + let operation = convert_operation_to_pyobject(input_operation).unwrap(); + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + // Basic initialisation, no errors + let operation_type = py.get_type::(); + let operation_py = operation_type + .call1((arguments,)) + .unwrap() + .cast_as::>() + .unwrap(); + let comparison = bool::extract( + operation + .as_ref(py) + .call_method1(method, (operation_py,)) + .unwrap(), + ) + .unwrap(); + assert!(comparison); + + // Error initialisation + let result = operation_type.call1((vec!["fails"],)); + let result_ref = result.as_ref(); + assert!(result_ref.is_err()); + + // Testing PartialEq, Clone and Debug + let def_wrapper = operation_py.extract::().unwrap(); + let new_op_diff = operation_type + .call1((vec![1, 2],)) + .unwrap() + .cast_as::>() + .unwrap(); + let def_wrapper_diff = new_op_diff.extract::().unwrap(); + let helper_ne: bool = def_wrapper_diff != def_wrapper; + assert!(helper_ne); + let helper_eq: bool = def_wrapper == def_wrapper.clone(); + assert!(helper_eq); + + assert_eq!( + format!("{:?}", def_wrapper_diff), + "MultiCNOTWrapper { internal: MultiCNOT { qubits: [1, 2] } }" + ); + }) +} + /// Test is_parametrized() function for MultiQubitGate Operations #[test_case(Operation::from(MultiQubitMS::new(vec![0, 1], CalculatorFloat::from("theta"))); "MultiQubitMS")] fn test_pyo3_is_parametrized(input_operation: Operation) { @@ -113,6 +160,9 @@ fn test_pyo3_is_parametrized(input_operation: Operation) { /// Test is_parametrized = false for MultiQubitGate Operations #[test_case(Operation::from(MultiQubitMS::new(vec![0, 1], CalculatorFloat::PI)); "MultiQubitMS")] +#[test_case(Operation::from(MultiCNOT::new(vec![0])); "MultiCNOT one")] +#[test_case(Operation::from(MultiCNOT::new(vec![0, 1])); "MultiCNOT two")] +#[test_case(Operation::from(MultiCNOT::new(vec![0, 1, 2])); "MultiCNOT three")] fn test_pyo3_is_not_parametrized(input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -147,6 +197,9 @@ fn test_pyo3_theta(theta: CalculatorFloat, input_operation: Operation) { /// Test qubits() function for MultiQubitGate Operations #[test_case(vec![0, 1], Operation::from(MultiQubitMS::new(vec![0, 1], CalculatorFloat::from(0))); "MultiQubitMS two")] #[test_case(vec![0, 1, 2], Operation::from(MultiQubitMS::new(vec![0, 1, 2], CalculatorFloat::from(0))); "MultiQubitMS three")] +#[test_case(vec![0], Operation::from(MultiCNOT::new(vec![0])); "MultiCNOT one")] +#[test_case(vec![0, 1], Operation::from(MultiCNOT::new(vec![0, 1])); "MultiCNOT two")] +#[test_case(vec![0, 1, 2], Operation::from(MultiCNOT::new(vec![0, 1, 2])); "MultiCNOT three")] fn test_pyo3_qubits(qubit: Vec, input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -163,6 +216,7 @@ fn test_pyo3_qubits(qubit: Vec, input_operation: Operation) { /// Test hqslang() function for MultiQubitGate Operations #[test_case("MultiQubitMS", Operation::from(MultiQubitMS::new(vec![0, 1], CalculatorFloat::from(0))); "MultiQubitMS")] +#[test_case("MultiCNOT", Operation::from(MultiCNOT::new(vec![0, 1])); "MultiCNOT")] fn test_pyo3_hqslang(name: &'static str, input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -184,6 +238,15 @@ fn test_pyo3_hqslang(name: &'static str, input_operation: Operation) { "MultiQubitMS", ]; "MultiQubitMS")] +#[test_case( + Operation::from(MultiCNOT::new(vec![0, 1, 2])), + vec![ + "Operation", + "GateOperation", + "MultiQubitGateOperation", + "MultiCNOT", + ]; + "MultiCNOT")] fn test_pyo3_tags(input_operation: Operation, tags: Vec<&str>) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -199,6 +262,7 @@ fn test_pyo3_tags(input_operation: Operation, tags: Vec<&str>) { /// Test remap_qubits() function for MultiQubitGate Operations #[test_case(Operation::from(MultiQubitMS::new(vec![0, 1, 2], CalculatorFloat::from(1.3))); "MultiQubitMS")] +#[test_case(Operation::from(MultiCNOT::new(vec![0, 1, 2])); "MultiCNOT")] fn test_pyo3_remapqubits(input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -234,6 +298,7 @@ fn test_pyo3_remapqubits(input_operation: Operation) { // test remap_qubits() function returning an error. #[test_case(Operation::from(MultiQubitMS::new(vec![0, 1, 2], CalculatorFloat::from(1.3))); "MultiQubitMS")] +#[test_case(Operation::from(MultiCNOT::new(vec![0, 1, 2])); "MultiCNOT")] fn test_pyo3_remapqubits_error(input_operation: Operation) { // preparation pyo3::prepare_freethreaded_python(); @@ -249,6 +314,7 @@ fn test_pyo3_remapqubits_error(input_operation: Operation) { /// Test unitary_matrix() function for MultiQubitGate Operations #[test_case(Operation::from(MultiQubitMS::new(vec![0, 1, 2], CalculatorFloat::from(1.3))); "MultiQubitMS")] +#[test_case(Operation::from(MultiCNOT::new(vec![0, 1, 2])); "MultiCNOT")] fn test_pyo3_unitarymatrix(input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -308,8 +374,40 @@ fn test_pyo3_circuit_ms() { }) } +/// Test circuit() function for MultiCNOT +#[test] +fn test_pyo3_circuit_multi_cnot() { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let input_operation = Operation::from(MultiCNOT::new(vec![0, 1, 2])); + let operation = convert_operation_to_pyobject(input_operation).unwrap(); + let py_result = operation.call_method0(py, "circuit").unwrap(); + let result_circuit: CircuitWrapper = py_result.extract(py).unwrap(); + + let mut circuit = Circuit::new(); + circuit += Hadamard::new(2); + circuit += CNOT::new(1, 2); + circuit += PhaseShiftState1::new(2, -CalculatorFloat::FRAC_PI_4); + circuit += CNOT::new(0, 2); + circuit += TGate::new(2); + circuit += CNOT::new(1, 2); + circuit += PhaseShiftState1::new(2, -CalculatorFloat::FRAC_PI_4); + circuit += CNOT::new(0, 2); + circuit += TGate::new(1); + circuit += TGate::new(2); + circuit += Hadamard::new(2); + circuit += CNOT::new(0, 1); + circuit += TGate::new(0); + circuit += PhaseShiftState1::new(1, -CalculatorFloat::FRAC_PI_4); + circuit += CNOT::new(0, 1); + + assert_eq!(result_circuit.internal, circuit); + }) +} + /// Test copy and deepcopy functions #[test_case(Operation::from(MultiQubitMS::new(vec![0, 1, 2], CalculatorFloat::from(1.3))); "MultiQubitMS")] +#[test_case(Operation::from(MultiCNOT::new(vec![0, 1, 2])); "MultiCNOT")] fn test_pyo3_copy_deepcopy(input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -342,6 +440,10 @@ fn test_pyo3_copy_deepcopy(input_operation: Operation) { "MultiQubitMS { qubits: [0, 1, 2], theta: Float(0.0) }", Operation::from(MultiQubitMS::new(vec![0, 1, 2], CalculatorFloat::ZERO)); "MultiQubitMS")] +#[test_case( + "MultiCNOT { qubits: [0, 1, 2] }", + Operation::from(MultiCNOT::new(vec![0, 1, 2])); + "MultiCNOT")] fn test_pyo3_format_repr(format_repr: &str, input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -425,6 +527,9 @@ fn test_pyo3_rotate_powercf(first_op: Operation, second_op: Operation) { #[test_case( Operation::from(MultiQubitMS::new(vec![0, 1, 2], CalculatorFloat::from(0))), Operation::from(MultiQubitMS::new(vec![1, 2], CalculatorFloat::from(0))); "MultiQubitMS")] +#[test_case( + Operation::from(MultiCNOT::new(vec![0, 1, 2])), + Operation::from(MultiCNOT::new(vec![1, 2])); "MultiCNOT")] fn test_pyo3_richcmp(definition_1: Operation, definition_2: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| {