Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MultiCNOT gate. #120

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions qoqo/src/operations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ pub fn operations(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<PhaseShiftState0Wrapper>()?;
m.add_class::<PhaseShiftState1Wrapper>()?;
m.add_class::<MultiQubitMSWrapper>()?;
m.add_class::<MultiCNOTWrapper>()?;
m.add_class::<MultiQubitZZWrapper>()?;
Ok(())
}
7 changes: 7 additions & 0 deletions qoqo/src/operations/multi_qubit_gate_operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ pub struct MultiQubitMS {
}

#[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<usize>,
}

#[wrap(Operate, Rotate, OperateMultiQubit, OperateGate, OperateMultiQubitGate)]
/// The multi qubit Pauli-Z-Product gate.
///
Expand Down
107 changes: 106 additions & 1 deletion qoqo/tests/integration/operations/multi_qubit_gate_operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -95,6 +95,53 @@ fn test_new_multi_qubit_ms(input_operation: Operation, arguments: (Vec<u32>, 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<u32>, 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::<MultiCNOTWrapper>();
let operation_py = operation_type
.call1((arguments,))
.unwrap()
.cast_as::<PyCell<MultiCNOTWrapper>>()
.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::<MultiCNOTWrapper>().unwrap();
let new_op_diff = operation_type
.call1((vec![1, 2],))
.unwrap()
.cast_as::<PyCell<MultiCNOTWrapper>>()
.unwrap();
let def_wrapper_diff = new_op_diff.extract::<MultiCNOTWrapper>().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) {
Expand All @@ -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| {
Expand Down Expand Up @@ -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<usize>, input_operation: Operation) {
pyo3::prepare_freethreaded_python();
Python::with_gil(|py| {
Expand All @@ -163,6 +216,7 @@ fn test_pyo3_qubits(qubit: Vec<usize>, 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| {
Expand All @@ -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| {
Expand All @@ -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| {
Expand Down Expand Up @@ -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();
Expand All @@ -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| {
Expand Down Expand Up @@ -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| {
Expand Down Expand Up @@ -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| {
Expand Down Expand Up @@ -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| {
Expand Down
71 changes: 70 additions & 1 deletion roqoqo/src/operations/multi_qubit_gate_operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,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")]
Expand Down Expand Up @@ -92,6 +92,75 @@ impl OperateMultiQubitGate for MultiQubitMS {
}
}

#[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<usize>,
}

#[allow(non_upper_case_globals)]
const TAGS_MultiCNOT: &[&str; 4] = &[
"Operation",
"GateOperation",
"MultiQubitGateOperation",
"MultiCNOT",
];

impl OperateGate for MultiCNOT {
fn unitary_matrix(&self) -> Result<Array2<Complex64>, 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
}
}

/// The multi qubit Pauli-Z-Product gate.
///
/// The gate applies the rotation under the product of Pauli Z operators on multiple qubits.
Expand Down
Loading