From 2aef56c0e5ef93dbce2f1c18bfd485c358c40fdf Mon Sep 17 00:00:00 2001 From: Matteo Lodi <108724576+mlodi-hqs@users.noreply.github.com> Date: Thu, 17 Oct 2024 15:24:08 +0200 Subject: [PATCH] New 3-qubit and 4-qubit Gates (#592) * temp commit * moved to new file * first tests * mqg test fixes * prelude OperateFourQubit * four_qubit_gate_op file mod * tests mod * involv qubits * lib OperateFourQubit * substitute control_2 * finished testing cccx * circuits * again circuits * tests * operate enum four + three fix * missed build.rs stuff (enum) * OperateFourQubitGate * JsonSchema test * doctests fix * newline * feature gate * missed qoqo build.rs * mod qoqo * lib qoqo-macro * interface * interface tests * fmt * CHANGELOG * json_schema fix * compat tests * lock * qoqo * macros * derive * test * roqoqo * changelog * build.rs roq * ops mod roq * copyright * lock * DEP * fixed changelog * again fix * cswap definitions * cswap tests * qoqo-macros new targets-controls * prelude * OperateThreeQubit new sol * involvequbits new targets-controls * again * operate n qubits new targets-controls * substitute * test fix * lock * doc fix * changelog * triplecontrolledphaseshift definitions * changelog * triplecontrolledphaseshift tests * phase-shifted changelog * roq derive sol (to be changed) * phase-shifted double-controlled definitions * phse-shifted double-controlled tests * roq derive incompatibility fix (wip qoqo) * better docs * qoqo compatiiblity issue cswap fix * fmt * qoqo mod * redundant changlog * clippy * dereference * pr corrections * misisng tests * small derive fixes * no target_0 target_1 reserved fields --- CHANGELOG.md | 5 +- qoqo-macros/src/lib.rs | 40 +- qoqo/build.rs | 1 + .../operations/four_qubit_gate_operations.rs | 81 ++ qoqo/src/operations/mod.rs | 8 + .../operations/three_qubit_gate_operations.rs | 241 ++++ .../operations/four_qubit_gate_operations.rs | 605 ++++++++ qoqo/tests/integration/operations/mod.rs | 2 + .../operations/operation_conversions.rs | 12 + .../operations/three_qubit_gate_operations.rs | 447 +++++- roqoqo-derive/src/involve_qubits.rs | 30 +- roqoqo-derive/src/lib.rs | 17 +- roqoqo-derive/src/operate_n_qubit.rs | 154 ++ roqoqo-derive/src/operate_unitary.rs | 40 + roqoqo-derive/src/substitute.rs | 1 + roqoqo/build.rs | 48 + .../operations/four_qubit_gate_operations.rs | 246 ++++ roqoqo/src/operations/mod.rs | 66 + .../operations/three_qubit_gate_operations.rs | 546 ++++++- roqoqo/src/prelude.rs | 12 +- .../operations/four_qubit_gate_operations.rs | 1282 +++++++++++++++++ roqoqo/tests/integration/operations/mod.rs | 2 + .../operations/multi_qubit_gate_operations.rs | 18 +- .../operations/supported_version.rs | 21 +- .../operations/three_qubit_gate_operations.rs | 222 ++- 25 files changed, 4120 insertions(+), 27 deletions(-) create mode 100644 qoqo/src/operations/four_qubit_gate_operations.rs create mode 100644 qoqo/tests/integration/operations/four_qubit_gate_operations.rs create mode 100644 roqoqo/src/operations/four_qubit_gate_operations.rs create mode 100644 roqoqo/tests/integration/operations/four_qubit_gate_operations.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c049e1bd..f4ccc6398 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ This changelog track changes to the qoqo project starting at version v0.5.0 - ## 1.16.0 ### Fixed in 1.16.0 @@ -11,7 +10,7 @@ This changelog track changes to the qoqo project starting at version v0.5.0 ### Added in 1.16.0 -* Added `InvSGate`, `InvTGate`, `SXGate`, `InvSXGate` gates. +* Added `InvSGate`, `InvTGate`, `SXGate`, `InvSXGate`, `TripleControlledPauliX`, `TripleControlledPauliZ`, `TripleControlledPhaseShift`, `ControlledSWAP`, `PhaseShiftedControlledControlledZ`, `PhaseShiftedControlledControlledPhase` gates. ## 1.15.2 @@ -39,7 +38,7 @@ This changelog track changes to the qoqo project starting at version v0.5.0 ### Added in 1.15.0 -* Added `SqrtPaulY` and `InvSqrtPauliY` gates. +* Added `SqrtPauliY` and `InvSqrtPauliY` gates. ## 1.14.0 diff --git a/qoqo-macros/src/lib.rs b/qoqo-macros/src/lib.rs index 8e9f2c617..64cdf6b90 100644 --- a/qoqo-macros/src/lib.rs +++ b/qoqo-macros/src/lib.rs @@ -53,11 +53,12 @@ pub fn devicechainenvironmentwrapper( } /// Array of field names that are reserved for use with specific traits -const RESERVED_FIELDS: &[&str; 15] = &[ +const RESERVED_FIELDS: &[&str; 16] = &[ "qubit", "control", "control_0", "control_1", + "control_2", "target", "qubits", "global_phase", @@ -368,6 +369,41 @@ pub fn wrap( } else { TokenStream::new() }; + let operate_four_qubit_quote = if attribute_arguments.contains("OperateFourQubit") { + quote! { + /// Returns control_0 qubit of the four-qubit operation + pub fn control_0(&self) -> usize { + self.internal.control_0().clone() + } + /// Returns control_1 qubit of the four-qubit operation + pub fn control_1(&self) -> usize { + self.internal.control_1().clone() + } + /// Returns control_2 qubit of the four-qubit operation + pub fn control_2(&self) -> usize { + self.internal.control_2().clone() + } + /// Returns target qubit of the four-qubit operation + pub fn target(&self) -> usize { + self.internal.target().clone() + } + } + } else { + TokenStream::new() + }; + let operate_four_qubit_gate_quote = if attribute_arguments.contains("OperateFourQubitGate") { + quote! { + /// Returns circuit implementing the FourQubitGateOperation + /// + /// Returns: + /// Circuit + pub fn circuit(&self) -> CircuitWrapper { + CircuitWrapper { internal: self.internal.circuit().clone() } + } + } + } else { + TokenStream::new() + }; let operate_gate_quote = if attribute_arguments.contains("OperateGate") { quote! { /// Return unitary matrix of gate. @@ -623,6 +659,8 @@ pub fn wrap( // #operate_two_qubit_gate_quote #operate_three_qubit_quote #operate_three_qubit_gate_quote + #operate_four_qubit_quote + #operate_four_qubit_gate_quote #operate_multi_qubit_quote #operate_multi_qubit_gate_quote #operate_gate_quote diff --git a/qoqo/build.rs b/qoqo/build.rs index c5f259532..868cf04da 100644 --- a/qoqo/build.rs +++ b/qoqo/build.rs @@ -159,6 +159,7 @@ const SOURCE_FILES: &[&str] = &[ "src/operations/pragma_operations.rs", "src/operations/two_qubit_gate_operations.rs", "src/operations/three_qubit_gate_operations.rs", + "src/operations/four_qubit_gate_operations.rs", "src/operations/multi_qubit_gate_operations.rs", "src/operations/measurement_operations.rs", "src/operations/define_operations.rs", diff --git a/qoqo/src/operations/four_qubit_gate_operations.rs b/qoqo/src/operations/four_qubit_gate_operations.rs new file mode 100644 index 000000000..7744f6e8f --- /dev/null +++ b/qoqo/src/operations/four_qubit_gate_operations.rs @@ -0,0 +1,81 @@ +// Copyright © 2021-2024 HQS Quantum Simulations GmbH. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under the +// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing permissions and +// limitations under the License. + +use num_complex::Complex64; +use numpy::{PyArray2, ToPyArray}; + +use qoqo_calculator::CalculatorFloat; +use qoqo_calculator_pyo3::{convert_into_calculator_float, CalculatorFloatWrapper}; + +use crate::CircuitWrapper; + +use pyo3::exceptions::{PyRuntimeError, PyValueError}; +use pyo3::prelude::*; +use pyo3::types::PySet; + +use std::collections::HashMap; + +#[cfg(feature = "json_schema")] +use roqoqo::ROQOQO_VERSION; + +use roqoqo::operations::*; + +use qoqo_macros::*; + +#[wrap( + Operate, + OperateFourQubit, + OperateGate, + OperateFourQubitGate, + JsonSchema +)] +/// The triple-controlled PauliX gate. +/// +pub struct TripleControlledPauliX { + control_0: usize, + control_1: usize, + control_2: usize, + target: usize, +} + +#[wrap( + Operate, + OperateFourQubit, + OperateGate, + OperateFourQubitGate, + JsonSchema +)] +/// The triple-controlled PauliZ gate. +/// +pub struct TripleControlledPauliZ { + control_0: usize, + control_1: usize, + control_2: usize, + target: usize, +} + +#[wrap( + Operate, + OperateFourQubit, + OperateGate, + OperateFourQubitGate, + JsonSchema +)] +/// The triple-controlled PhaseShift gate. +/// +pub struct TripleControlledPhaseShift { + control_0: usize, + control_1: usize, + control_2: usize, + target: usize, + theta: CalculatorFloat, +} diff --git a/qoqo/src/operations/mod.rs b/qoqo/src/operations/mod.rs index 22a381d6a..389e7ce6b 100644 --- a/qoqo/src/operations/mod.rs +++ b/qoqo/src/operations/mod.rs @@ -26,6 +26,8 @@ mod two_qubit_gate_operations; pub use two_qubit_gate_operations::*; mod three_qubit_gate_operations; pub use three_qubit_gate_operations::*; +mod four_qubit_gate_operations; +pub use four_qubit_gate_operations::*; mod multi_qubit_gate_operations; pub use multi_qubit_gate_operations::*; mod bosonic_operations; @@ -190,6 +192,12 @@ pub fn operations(_py: Python, m: &Bound) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; Ok(()) } diff --git a/qoqo/src/operations/three_qubit_gate_operations.rs b/qoqo/src/operations/three_qubit_gate_operations.rs index caf9f2420..dc90c11af 100644 --- a/qoqo/src/operations/three_qubit_gate_operations.rs +++ b/qoqo/src/operations/three_qubit_gate_operations.rs @@ -129,3 +129,244 @@ pub struct Toffoli { control_1: usize, target: usize, } + +#[allow(clippy::upper_case_acronyms)] +#[wrap(OperateGate, OperateThreeQubitGate, JsonSchema)] +/// Implements ControlledSWAP gate. +/// +/// .. math:: +/// U = \begin{pmatrix} +/// 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\\\ +/// 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\\\ +/// 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\\\ +/// 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\\\ +/// 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\\\ +/// 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\\\ +/// 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\\\ +/// 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 +/// \end{pmatrix} +/// +/// Args: +/// control (int): The index of the most significant qubit in the unitary representation. Here, the controlling qubit of the operation. +/// target_0 (int): The index of the second most significant qubit in the unitary representation. Here, the first targeting qubit of the operation. +/// target_1 (int): The index of the least significant qubit in the unitary representation. Here, the second targeting qubit of the operation. +pub struct ControlledSWAP { + control: usize, + target_0: usize, + target_1: usize, +} + +#[pymethods] +impl ControlledSWAPWrapper { + #[new] + /// Creates new instance of Operation ControlledSWAP + fn new(control: usize, target_0: usize, target_1: usize) -> PyResult { + Ok(Self { + internal: ControlledSWAP::new(control, target_0, target_1), + }) + } + /// Returns true if operation contains symbolic parameters + /// + /// Returns: + /// bool: Whether or not the operation contains symbolic parameters. + fn is_parametrized(&self) -> bool { + self.internal.is_parametrized() + } + /// Returns tags identifying the Operation + /// + /// Returns: + /// List[str]: The tags identifying the operation + fn tags(&self) -> Vec { + self.internal.tags().iter().map(|s| s.to_string()).collect() + } + /// Returns hqslang name of Operation + /// + /// Returns: + /// str: The name + fn hqslang(&self) -> &'static str { + self.internal.hqslang() + } + /// Substitutes internal symbolic parameters with float values + /// + /// Only available when all symbolic expressions can be evaluated to float with the + /// provided parameters. + /// + /// Args: + /// substitution_parameters (Dict[str, float]): The substituted free parameters + /// + /// Returns: + /// Operation: The operation with the parameters substituted + /// + /// Raises: + /// RuntimeError: Parameter Substitution failed + fn substitute_parameters( + &self, + substitution_parameters: std::collections::HashMap, + ) -> PyResult { + let mut calculator = qoqo_calculator::Calculator::new(); + for (key, val) in substitution_parameters.iter() { + calculator.set_variable(key, *val); + } + Ok(Self { + internal: self + .internal + .substitute_parameters(&calculator) + .map_err(|x| { + pyo3::exceptions::PyRuntimeError::new_err(format!( + "Parameter Substitution failed: {:?}", + x + )) + })?, + }) + } + /// Remap qubits + /// + /// Args: + /// mapping (Dict[int, int]): The mapping + /// + /// Returns: + /// Operation: The operation with the remapped qubits + /// + /// Raises: + /// RuntimeError: Qubit remapping failed + fn remap_qubits(&self, mapping: HashMap) -> PyResult { + let new_internal = self + .internal + .remap_qubits(&mapping) + .map_err(|x| PyRuntimeError::new_err(format!("Qubit remapping failed: {:?}", x)))?; + Ok(Self { + internal: new_internal, + }) + } + /// List all involved Qubits + /// + /// Returns: + /// Union[Set[int], str]: The involved qubits as a set or 'ALL' if all qubits are involved + fn involved_qubits(&self) -> PyObject { + Python::with_gil(|py| -> PyObject { + let involved = self.internal.involved_qubits(); + match involved { + InvolvedQubits::All => { + let pyref: &Bound = &PySet::new_bound(py, &["All"]).unwrap(); + let pyobject: PyObject = pyref.to_object(py); + pyobject + } + InvolvedQubits::None => { + let pyref: &Bound = &PySet::empty_bound(py).unwrap(); + let pyobject: PyObject = pyref.to_object(py); + pyobject + } + InvolvedQubits::Set(x) => { + let mut vector: Vec = Vec::new(); + for qubit in x { + vector.push(qubit) + } + let pyref: &Bound = &PySet::new_bound(py, &vector[..]).unwrap(); + let pyobject: PyObject = pyref.to_object(py); + pyobject + } + } + }) + } + /// Copies Operation + /// + /// For qoqo operations copy is always a deep copy + fn __copy__(&self) -> Self { + self.clone() + } + /// Creates deep copy of Operation + fn __deepcopy__(&self, _memodict: &Bound) -> Self { + self.clone() + } + /// Returns control qubit of the three-qubit operation + pub fn control(&self) -> usize { + *self.internal.control_0() + } + /// Returns target_0 qubit of the three-qubit operation + pub fn target_0(&self) -> usize { + *self.internal.control_1() + } + /// Returns target_1 qubit of the three-qubit operation + pub fn target_1(&self) -> usize { + *self.internal.target() + } +} + +#[allow(clippy::upper_case_acronyms)] +#[wrap( + Operate, + OperateThreeQubit, + OperateGate, + OperateThreeQubitGate, + JsonSchema +)] +/// The phased-shifted double-controlled-Z gate. +/// +/// +/// The unitary matrix representation is: +/// +/// .. math:: +/// U = \begin{pmatrix} +/// 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\\\ +/// 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\\\ +/// 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\\\ +/// 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\\\ +/// 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\\\ +/// 0 & 0 & 0 & 0 & 0 & e^{i \phi} & 0 & 0 \\\\ +/// 0 & 0 & 0 & 0 & 0 & 0 & e^{i \phi} & 0 \\\\ +/// 0 & 0 & 0 & 0 & 0 & 0 & 0 & e^{i (2\cdot\phi + \pi)} +/// \end{pmatrix} +/// +/// Args: +/// control_0 (int): The index of the most significant qubit in the unitary representation. Here, the first qubit that controls the application of the phase-shift on the target qubit. +/// control_1 (int): The index of the second most significant qubit in the unitary representation. Here, the second qubit that controls the application of the phase-shift on the target qubit. +/// target (int):: The index of the least significant qubit in the unitary representation. Here, the qubit phase-shift is applied to. +/// phi (CalculatorFloat): The single qubit phase $\phi$. +/// +pub struct PhaseShiftedControlledControlledZ { + control_0: usize, + control_1: usize, + target: usize, + phi: CalculatorFloat, +} + +#[allow(clippy::upper_case_acronyms)] +#[wrap( + Operate, + OperateThreeQubit, + OperateGate, + Rotate, + OperateThreeQubitGate, + JsonSchema +)] +/// The phased-shifted double-controlled-Z gate. +/// +/// +/// The unitary matrix representation is: +/// +/// .. math:: +/// U = \begin{pmatrix} +/// 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\\\ +/// 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\\\ +/// 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\\\ +/// 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\\\ +/// 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\\\ +/// 0 & 0 & 0 & 0 & 0 & e^{i \phi} & 0 & 0 \\\\ +/// 0 & 0 & 0 & 0 & 0 & 0 & e^{i \phi} & 0 \\\\ +/// 0 & 0 & 0 & 0 & 0 & 0 & 0 & e^{i (2\cdot\phi + \theta)} +/// \end{pmatrix} +/// +/// Args: +/// control_0 (int): The index of the most significant qubit in the unitary representation. Here, the first qubit that controls the application of the phase-shift on the target qubit. +/// control_1 (int): The index of the second most significant qubit in the unitary representation. Here, the second qubit that controls the application of the phase-shift on the target qubit. +/// target (int):: The index of the least significant qubit in the unitary representation. Here, the qubit phase-shift is applied to. +/// phi (CalculatorFloat): The single qubit phase $\phi$. +/// theta (CalculatorFloat): The phase rotation $\theta$. +/// +pub struct PhaseShiftedControlledControlledPhase { + control_0: usize, + control_1: usize, + target: usize, + theta: CalculatorFloat, + phi: CalculatorFloat, +} diff --git a/qoqo/tests/integration/operations/four_qubit_gate_operations.rs b/qoqo/tests/integration/operations/four_qubit_gate_operations.rs new file mode 100644 index 000000000..9cb8f9142 --- /dev/null +++ b/qoqo/tests/integration/operations/four_qubit_gate_operations.rs @@ -0,0 +1,605 @@ +// Copyright © 2021-2024 HQS Quantum Simulations GmbH. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under the +// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing permissions and +// limitations under the License. + +use ndarray::Array2; +use num_complex::Complex64; +use numpy::PyArray2; + +use qoqo_calculator::CalculatorFloat; + +use pyo3::prelude::*; +use std::collections::HashMap; + +use qoqo::operations::{ + convert_operation_to_pyobject, TripleControlledPauliXWrapper, TripleControlledPauliZWrapper, + TripleControlledPhaseShiftWrapper, +}; +use qoqo::CircuitWrapper; +use roqoqo::operations::*; +#[cfg(feature = "json_schema")] +use roqoqo::ROQOQO_VERSION; +use roqoqo::{Circuit, RoqoqoError}; + +use test_case::test_case; + +#[test_case(Operation::from(TripleControlledPauliX::new(0, 1, 2, 3)); "TripleControlledPauliX")] +#[test_case(Operation::from(TripleControlledPauliZ::new(0, 1, 2, 3)); "TripleControlledPauliZ")] +#[test_case(Operation::from(TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::from(1.0))); "TripleControlledPhaseShift")] +fn test_pyo3_is_not_parametrized(input_operation: Operation) { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let operation = convert_operation_to_pyobject(input_operation).unwrap(); + assert!(!operation + .call_method0(py, "is_parametrized") + .unwrap() + .bind(py) + .extract::() + .unwrap()); + }) +} + +#[test_case(Operation::from(TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::from("theta"))); "TripleControlledPhaseShift")] +fn test_pyo3_is_parametrized(input_operation: Operation) { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let operation = convert_operation_to_pyobject(input_operation).unwrap(); + assert!(operation + .call_method0(py, "is_parametrized") + .unwrap() + .bind(py) + .extract::() + .unwrap()); + }) +} + +#[test_case( + vec![ + "Operation", + "GateOperation", + "FourQubitGateOperation", + "TripleControlledPauliX", + ], + Operation::from(TripleControlledPauliX::new(0, 1, 2, 3)); "TripleControlledPauliX")] +#[test_case( + vec![ + "Operation", + "GateOperation", + "FourQubitGateOperation", + "TripleControlledPauliZ", + ], + Operation::from(TripleControlledPauliZ::new(0, 1, 2, 3)); "TripleControlledPauliZ")] +#[test_case( + vec![ + "Operation", + "GateOperation", + "FourQubitGateOperation", + "TripleControlledPhaseShift", + ], + Operation::from(TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::from(1.0))); "TripleControlledPhaseShift")] +fn test_pyo3_tags(tags: Vec<&str>, input_operation: Operation) { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let operation = convert_operation_to_pyobject(input_operation).unwrap(); + let tags_op: Vec = operation + .call_method0(py, "tags") + .unwrap() + .bind(py) + .extract() + .unwrap(); + assert_eq!(tags_op.len(), tags.len()); + for i in 0..tags.len() { + assert_eq!(tags_op[i], tags[i]); + } + }) +} + +#[test_case("TripleControlledPauliX", Operation::from(TripleControlledPauliX::new(0, 1, 2, 3)); "TripleControlledPauliX")] +#[test_case("TripleControlledPauliZ", Operation::from(TripleControlledPauliZ::new(0, 1, 2, 3)); "TripleControlledPauliZ")] +#[test_case("TripleControlledPhaseShift", Operation::from(TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::from(1.0))); "TripleControlledPhaseShift")] +fn test_pyo3_hqslang(name: &'static str, input_operation: Operation) { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let operation = convert_operation_to_pyobject(input_operation).unwrap(); + let name_op: String = operation + .call_method0(py, "hqslang") + .unwrap() + .bind(py) + .extract() + .unwrap(); + assert_eq!(name_op, name.to_string()); + }) +} + +#[test_case(Operation::from(TripleControlledPauliX::new(0, 1, 2, 3)); "TripleControlledPauliX")] +#[test_case(Operation::from(TripleControlledPauliZ::new(0, 1, 2, 3)); "TripleControlledPauliZ")] +#[test_case(Operation::from(TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::from(1.0))); "TripleControlledPhaseShift")] +fn test_pyo3_remapqubits(input_operation: Operation) { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let operation = convert_operation_to_pyobject(input_operation).unwrap(); + + // test initial qubits + let control_0: usize = operation + .call_method0(py, "control_0") + .unwrap() + .bind(py) + .extract() + .unwrap(); + assert_eq!(control_0.clone(), 0); + let control_1: usize = operation + .call_method0(py, "control_1") + .unwrap() + .bind(py) + .extract() + .unwrap(); + assert_eq!(control_1.clone(), 1); + let control_2: usize = operation + .call_method0(py, "control_2") + .unwrap() + .bind(py) + .extract() + .unwrap(); + assert_eq!(control_2.clone(), 2); + let target: usize = operation + .call_method0(py, "target") + .unwrap() + .bind(py) + .extract() + .unwrap(); + assert_eq!(target.clone(), 3); + + // remap qubits + let mut qubit_mapping: HashMap = HashMap::new(); + qubit_mapping.insert(0, 2); + qubit_mapping.insert(2, 0); + qubit_mapping.insert(1, 3); + qubit_mapping.insert(3, 1); + let result = operation + .call_method1(py, "remap_qubits", (qubit_mapping,)) + .unwrap(); + + // test re-mapped qubit + let control_0_new: usize = result + .call_method0(py, "control_0") + .unwrap() + .bind(py) + .extract() + .unwrap(); + assert_eq!(control_0_new.clone(), 2); + let control_1_new: usize = result + .call_method0(py, "control_1") + .unwrap() + .bind(py) + .extract() + .unwrap(); + assert_eq!(control_1_new.clone(), 3); + let control_2_new: usize = result + .call_method0(py, "control_2") + .unwrap() + .bind(py) + .extract() + .unwrap(); + assert_eq!(control_2_new.clone(), 0); + let target_new: usize = result + .call_method0(py, "target") + .unwrap() + .bind(py) + .extract() + .unwrap(); + assert_eq!(target_new.clone(), 1); + + // test that initial and rempapped qubits are different + assert_ne!(control_0, control_0_new); + assert_ne!(control_1, control_1_new); + assert_ne!(control_2, control_2_new); + assert_ne!(target, target_new); + }) +} + +#[test_case(Operation::from(TripleControlledPauliX::new(0, 1, 2, 3)); "TripleControlledPauliX")] +#[test_case(Operation::from(TripleControlledPauliZ::new(0, 1, 2, 3)); "TripleControlledPauliZ")] +#[test_case(Operation::from(TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::from(1.0))); "TripleControlledPhaseShift")] +fn test_pyo3_remapqubits_error(input_operation: Operation) { + // preparation + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let operation = convert_operation_to_pyobject(input_operation).unwrap(); + // remap qubits + let mut qubit_mapping: HashMap = HashMap::new(); + qubit_mapping.insert(2, 0); + let result = operation.call_method1(py, "remap_qubits", (qubit_mapping,)); + assert!(result.is_err()); + }) +} + +#[test_case(Operation::from(TripleControlledPauliX::new(0, 1, 2, 3)); "TripleControlledPauliX")] +#[test_case(Operation::from(TripleControlledPauliZ::new(0, 1, 2, 3)); "TripleControlledPauliZ")] +#[test_case(Operation::from(TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::from(1.0))); "TripleControlledPhaseShift")] +fn test_pyo3_unitarymatrix(input_operation: Operation) { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let operation = convert_operation_to_pyobject(input_operation.clone()).unwrap(); + let py_result = operation.call_method0(py, "unitary_matrix").unwrap(); + let result_matrix = py_result + .downcast_bound::>(py) + .unwrap() + .as_gil_ref() + .readonly() + .as_array() + .to_owned(); + + // compare to reference matrix obtained in Rust directly (without passing to Python) + let gate: GateOperation = input_operation.try_into().unwrap(); + let rust_matrix: Result, RoqoqoError> = gate.unitary_matrix(); + let test_matrix: Array2 = rust_matrix.unwrap(); + + assert_eq!(result_matrix, test_matrix); + }) +} + +#[test_case("TripleControlledPauliX { control_0: 0, control_1: 1, control_2: 2, target: 3 }", Operation::from(TripleControlledPauliX::new(0, 1, 2, 3)); "TripleControlledPauliX")] +#[test_case("TripleControlledPauliZ { control_0: 0, control_1: 1, control_2: 2, target: 3 }", Operation::from(TripleControlledPauliZ::new(0, 1, 2, 3)); "TripleControlledPauliZ")] +#[test_case("TripleControlledPhaseShift { control_0: 0, control_1: 1, control_2: 2, target: 3, theta: Float(1.0) }", Operation::from(TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::from(1.0))); "TripleControlledPhaseShift")] +fn test_pyo3_format_repr(format_repr: &str, input_operation: Operation) { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let operation = convert_operation_to_pyobject(input_operation).unwrap(); + let to_format = operation.call_method1(py, "__format__", ("",)).unwrap(); + let format_op: String = to_format.bind(py).extract().unwrap(); + let to_repr = operation.call_method0(py, "__repr__").unwrap(); + let repr_op: String = to_repr.bind(py).extract().unwrap(); + assert_eq!(format_op, format_repr); + assert_eq!(repr_op, format_repr); + }) +} + +#[test_case(Operation::from(TripleControlledPauliX::new(0, 1, 2, 3)); "TripleControlledPauliX")] +#[test_case(Operation::from(TripleControlledPauliZ::new(0, 1, 2, 3)); "TripleControlledPauliZ")] +#[test_case(Operation::from(TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::from(1.0))); "TripleControlledPhaseShift")] +fn test_pyo3_copy_deepcopy(input_operation: Operation) { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let operation = convert_operation_to_pyobject(input_operation).unwrap(); + let copy_op = operation.call_method0(py, "__copy__").unwrap(); + let deepcopy_op = operation.call_method1(py, "__deepcopy__", ("",)).unwrap(); + let copy_deepcopy_param = operation; + + let comparison_copy = copy_op + .bind(py) + .call_method1("__eq__", (copy_deepcopy_param.clone(),)) + .unwrap() + .extract::() + .unwrap(); + assert!(comparison_copy); + let comparison_deepcopy = bool::extract_bound( + &deepcopy_op + .bind(py) + .call_method1("__eq__", (copy_deepcopy_param,)) + .unwrap(), + ) + .unwrap(); + assert!(comparison_deepcopy); + }) +} + +#[test_case(Operation::from(TripleControlledPauliX::new(0, 1, 2, 3)), Operation::from(TripleControlledPauliX::new(0, 1, 2, 3)); "TripleControlledPauliX")] +#[test_case(Operation::from(TripleControlledPauliZ::new(0, 1, 2, 3)), Operation::from(TripleControlledPauliZ::new(0, 1, 2, 3)); "TripleControlledPauliZ")] +#[test_case(Operation::from(TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::from("test"))), Operation::from(TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::from(1.0))); "TripleControlledPhaseShift")] +fn test_pyo3_substitute_parameters(first_op: Operation, second_op: Operation) { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let operation = convert_operation_to_pyobject(first_op).unwrap(); + let mut substitution_dict: HashMap = HashMap::new(); + substitution_dict.insert("test".to_owned(), 1.0); + let substitute_op = operation + .call_method1(py, "substitute_parameters", (substitution_dict,)) + .unwrap(); + let substitute_param = convert_operation_to_pyobject(second_op).unwrap(); + + let comparison = bool::extract_bound( + &substitute_op + .bind(py) + .call_method1("__eq__", (substitute_param,)) + .unwrap(), + ) + .unwrap(); + assert!(comparison); + }) +} + +#[test_case(Operation::from(TripleControlledPauliX::new(0, 1, 2, 3)), (0 ,1, 2, 3), "__eq__"; "TripleControlledPauliX_eq")] +#[test_case(Operation::from(TripleControlledPauliX::new(3, 2, 1, 0)), (0 ,1, 2, 3), "__ne__"; "TripleControlledPauliX_ne")] +fn test_new_triplecontrolledpaulix( + input_operation: Operation, + arguments: (u32, u32, u32, 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_bound::(); + let binding = operation_type.call1(arguments).unwrap(); + let operation_py = binding.downcast::().unwrap(); + let comparison = bool::extract_bound( + &operation + .bind(py) + .call_method1(method, (operation_py,)) + .unwrap(), + ) + .unwrap(); + assert!(comparison); + + // Error initialisation + let result = operation_type.call1((0, 1, vec!["fails"])); + assert!(result.is_err()); + + let result = operation_type.call1((0, vec!["fails"], 2)); + assert!(result.is_err()); + + // Testing PartialEq, Clone and Debug + let def_wrapper = operation_py + .extract::() + .unwrap(); + let binding = operation_type.call1((1, 2, 3, 4)).unwrap(); + let new_op_diff = binding.downcast::().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), + "TripleControlledPauliXWrapper { internal: TripleControlledPauliX { control_0: 1, control_1: 2, control_2: 3, target: 4 } }" + ); + }) +} + +#[test_case(Operation::from(TripleControlledPauliZ::new(0, 1, 2, 3)), (0 ,1, 2, 3), "__eq__"; "TripleControlledPauliZ_eq")] +#[test_case(Operation::from(TripleControlledPauliZ::new(3, 2, 1, 0)), (0 ,1, 2, 3), "__ne__"; "TripleControlledPauliZ_ne")] +fn test_new_triplecontrolledpauliz( + input_operation: Operation, + arguments: (u32, u32, u32, 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_bound::(); + let binding = operation_type.call1(arguments).unwrap(); + let operation_py = binding.downcast::().unwrap(); + let comparison = bool::extract_bound( + &operation + .bind(py) + .call_method1(method, (operation_py,)) + .unwrap(), + ) + .unwrap(); + assert!(comparison); + + // Error initialisation + let result = operation_type.call1((0, 1, vec!["fails"])); + assert!(result.is_err()); + + let result = operation_type.call1((0, vec!["fails"], 2)); + assert!(result.is_err()); + + // Testing PartialEq, Clone and Debug + let def_wrapper = operation_py + .extract::() + .unwrap(); + let binding = operation_type.call1((1, 2, 3, 4)).unwrap(); + let new_op_diff = binding.downcast::().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), + "TripleControlledPauliZWrapper { internal: TripleControlledPauliZ { control_0: 1, control_1: 2, control_2: 3, target: 4 } }" + ); + }) +} + +#[test_case(Operation::from(TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::from(1.0))), (0 ,1, 2, 3, 1.0), "__eq__"; "TripleControlledPhaseShift_eq")] +#[test_case(Operation::from(TripleControlledPhaseShift::new(3, 2, 1, 0, CalculatorFloat::from(1.0))), (0 ,1, 2, 3, 1.0), "__ne__"; "TripleControlledPhaseShift_ne")] +fn test_new_triplecontrolledphaseshift( + input_operation: Operation, + arguments: (u32, u32, u32, u32, f64), + 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_bound::(); + let binding = operation_type.call1(arguments).unwrap(); + let operation_py = binding + .downcast::() + .unwrap(); + let comparison = bool::extract_bound( + &operation + .bind(py) + .call_method1(method, (operation_py,)) + .unwrap(), + ) + .unwrap(); + assert!(comparison); + + // Error initialisation + let result = operation_type.call1((0, 1, vec!["fails"])); + assert!(result.is_err()); + + let result = operation_type.call1((0, vec!["fails"], 2)); + assert!(result.is_err()); + + // Testing PartialEq, Clone and Debug + let def_wrapper = operation_py + .extract::() + .unwrap(); + let binding = operation_type.call1((1, 2, 3, 4, 1.0)).unwrap(); + let new_op_diff = binding + .downcast::() + .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), + "TripleControlledPhaseShiftWrapper { internal: TripleControlledPhaseShift { control_0: 1, control_1: 2, control_2: 3, target: 4, theta: Float(1.0) } }" + ); + }) +} + +#[test] +fn test_circuit_pyo3_triplecontrolledpaulix() { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let input_operation = Operation::from(TripleControlledPauliX::new(0, 1, 2, 3)); + 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 += CNOT::new(0, 3); + circuit += CNOT::new(0, 1); + circuit += CNOT::new(1, 3); + circuit += CNOT::new(0, 1); + circuit += CNOT::new(1, 3); + circuit += CNOT::new(1, 2); + circuit += CNOT::new(2, 3); + circuit += CNOT::new(0, 2); + circuit += CNOT::new(2, 3); + circuit += CNOT::new(1, 2); + circuit += CNOT::new(2, 3); + circuit += CNOT::new(0, 2); + circuit += CNOT::new(2, 3); + + assert_eq!(result_circuit.internal, circuit); + }); +} + +#[test] +fn test_circuit_pyo3_triplecontrolledpauliz() { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let input_operation = Operation::from(TripleControlledPauliZ::new(0, 1, 2, 3)); + 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 += ControlledPauliZ::new(0, 3); + circuit += CNOT::new(0, 1); + circuit += ControlledPauliZ::new(1, 3); + circuit += CNOT::new(0, 1); + circuit += ControlledPauliZ::new(1, 3); + circuit += CNOT::new(1, 2); + circuit += ControlledPauliZ::new(2, 3); + circuit += CNOT::new(0, 2); + circuit += ControlledPauliZ::new(2, 3); + circuit += CNOT::new(1, 2); + circuit += ControlledPauliZ::new(2, 3); + circuit += CNOT::new(0, 2); + circuit += ControlledPauliZ::new(2, 3); + + assert_eq!(result_circuit.internal, circuit); + }); +} + +#[test] +fn test_circuit_pyo3_triplecontrolledphaseshift() { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let input_operation = Operation::from(TripleControlledPhaseShift::new( + 0, + 1, + 2, + 3, + CalculatorFloat::FRAC_PI_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 += ControlledPhaseShift::new(0, 3, CalculatorFloat::FRAC_PI_4); + circuit += CNOT::new(0, 1); + circuit += ControlledPhaseShift::new(1, 3, -CalculatorFloat::FRAC_PI_4); + circuit += CNOT::new(0, 1); + circuit += ControlledPhaseShift::new(1, 3, CalculatorFloat::FRAC_PI_4); + circuit += CNOT::new(1, 2); + circuit += ControlledPhaseShift::new(2, 3, -CalculatorFloat::FRAC_PI_4); + circuit += CNOT::new(0, 2); + circuit += ControlledPhaseShift::new(2, 3, CalculatorFloat::FRAC_PI_4); + circuit += CNOT::new(1, 2); + circuit += ControlledPhaseShift::new(2, 3, -CalculatorFloat::FRAC_PI_4); + circuit += CNOT::new(0, 2); + circuit += ControlledPhaseShift::new(2, 3, CalculatorFloat::FRAC_PI_4); + + assert_eq!(result_circuit.internal, circuit); + }); +} + +/// Test json_schema function for all three qubit gate operations +#[cfg(feature = "json_schema")] +#[test_case(FourQubitGateOperation::from(TripleControlledPauliX::new(0, 1, 2, 3)); "TripleControlledPauliX")] +#[test_case(FourQubitGateOperation::from(TripleControlledPauliZ::new(0, 1, 2, 3)); "TripleControlledPauliZ")] +#[test_case(FourQubitGateOperation::from(TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::from(1.0))); "TripleControlledPhaseShift")] +fn test_pyo3_json_schema(operation: FourQubitGateOperation) { + let rust_schema = match operation { + FourQubitGateOperation::TripleControlledPauliX(_) => { + serde_json::to_string_pretty(&schemars::schema_for!(TripleControlledPauliX)).unwrap() + } + FourQubitGateOperation::TripleControlledPauliZ(_) => { + serde_json::to_string_pretty(&schemars::schema_for!(TripleControlledPauliZ)).unwrap() + } + FourQubitGateOperation::TripleControlledPhaseShift(_) => { + serde_json::to_string_pretty(&schemars::schema_for!(TripleControlledPhaseShift)) + .unwrap() + } + _ => unreachable!(), + }; + pyo3::prepare_freethreaded_python(); + pyo3::Python::with_gil(|py| { + let converted_op = Operation::from(operation); + let pyobject = convert_operation_to_pyobject(converted_op).unwrap(); + let operation = pyobject.bind(py); + + let schema: String = + String::extract_bound(&operation.call_method0("json_schema").unwrap()).unwrap(); + + assert_eq!(schema, rust_schema); + + let current_version_string = + String::extract_bound(&operation.call_method0("current_version").unwrap()).unwrap(); + let minimum_supported_version_string = + String::extract_bound(&operation.call_method0("min_supported_version").unwrap()) + .unwrap(); + + assert_eq!(current_version_string, ROQOQO_VERSION); + assert_eq!(minimum_supported_version_string, "1.16.0"); + }); +} diff --git a/qoqo/tests/integration/operations/mod.rs b/qoqo/tests/integration/operations/mod.rs index 8ad0edd74..c5db0a597 100644 --- a/qoqo/tests/integration/operations/mod.rs +++ b/qoqo/tests/integration/operations/mod.rs @@ -26,6 +26,8 @@ mod two_qubit_gate_operations; mod three_qubit_gate_operations; +mod four_qubit_gate_operations; + mod bosonic_operations; mod spin_boson_operations; diff --git a/qoqo/tests/integration/operations/operation_conversions.rs b/qoqo/tests/integration/operations/operation_conversions.rs index 2d7d12ffb..5d35a954f 100644 --- a/qoqo/tests/integration/operations/operation_conversions.rs +++ b/qoqo/tests/integration/operations/operation_conversions.rs @@ -143,6 +143,18 @@ use test_case::test_case; #[test_case(Operation::from(InvSqrtPauliY::new(100)); "InvSqrtPauliY")] #[test_case(Operation::from(SXGate::new(1)); "SXGate")] #[test_case(Operation::from(InvSXGate::new(1)); "InvSXGate")] +#[test_case(Operation::from(ControlledSWAP::new(0, 1, 2)); "ControlledSWAP")] +#[test_case(Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::PI)); "PhaseShiftedControlledControlledZ")] +#[test_case(Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::PI, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] +#[test_case( + Operation::from(TripleControlledPauliX::new(0, 1, 2, 3)); "TripleControlledPauliX" +)] +#[test_case( + Operation::from(TripleControlledPauliZ::new(0, 1, 2, 3)); "TripleControlledPauliZ" +)] +#[test_case( + Operation::from(TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::PI)); "TripleControlledPhaseShift" +)] fn test_conversion(input: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { diff --git a/qoqo/tests/integration/operations/three_qubit_gate_operations.rs b/qoqo/tests/integration/operations/three_qubit_gate_operations.rs index cdaf745d9..eddd3d626 100644 --- a/qoqo/tests/integration/operations/three_qubit_gate_operations.rs +++ b/qoqo/tests/integration/operations/three_qubit_gate_operations.rs @@ -23,7 +23,9 @@ use pyo3::prelude::*; use qoqo::{ operations::{ convert_operation_to_pyobject, ControlledControlledPauliZWrapper, - ControlledControlledPhaseShiftWrapper, ToffoliWrapper, + ControlledControlledPhaseShiftWrapper, ControlledSWAPWrapper, + PhaseShiftedControlledControlledPhaseWrapper, PhaseShiftedControlledControlledZWrapper, + ToffoliWrapper, }, CircuitWrapper, }; @@ -37,6 +39,9 @@ use test_case::test_case; #[test_case(Operation::from(ControlledControlledPauliZ::new(0, 1, 2)); "ControlledControlledPauliZ")] #[test_case(Operation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from(0.2))); "ControlledControlledPhaseShift")] #[test_case(Operation::from(Toffoli::new(0, 1, 2)); "Toffoli")] +#[test_case(Operation::from(ControlledSWAP::new(0, 1, 2)); "ControlledSWAP")] +#[test_case(Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::FRAC_PI_2)); "PhaseShiftedControlledControlledZ")] +#[test_case(Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::FRAC_PI_2, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] fn test_pyo3_is_not_parametrized(input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -50,6 +55,21 @@ fn test_pyo3_is_not_parametrized(input_operation: Operation) { }) } +#[test_case(Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::from("theta"))); "PhaseShiftedControlledControlledZ")] +#[test_case(Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::from("phi"), CalculatorFloat::from("theta"))); "PhaseShiftedControlledControlledPhase")] +fn test_pyo3_is_parametrized(input_operation: Operation) { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let operation = convert_operation_to_pyobject(input_operation).unwrap(); + assert!(operation + .call_method0(py, "is_parametrized") + .unwrap() + .bind(py) + .extract::() + .unwrap()); + }) +} + #[test_case( vec![ "Operation", @@ -75,6 +95,30 @@ fn test_pyo3_is_not_parametrized(input_operation: Operation) { "Toffoli", ], Operation::from(Toffoli::new(0, 1, 2)); "Toffoli")] +#[test_case( + vec![ + "Operation", + "GateOperation", + "ThreeQubitGateOperation", + "ControlledSWAP", + ], + Operation::from(ControlledSWAP::new(0, 1, 2)); "ControlledSWAP")] +#[test_case( + vec![ + "Operation", + "GateOperation", + "ThreeQubitGateOperation", + "PhaseShiftedControlledControlledZ", + ], + Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::PI)); "PhaseShiftedControlledControlledZ")] +#[test_case( + vec![ + "Operation", + "GateOperation", + "ThreeQubitGateOperation", + "PhaseShiftedControlledControlledPhase", + ], + Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::PI, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] fn test_pyo3_tags(tags: Vec<&str>, input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -95,6 +139,9 @@ fn test_pyo3_tags(tags: Vec<&str>, input_operation: Operation) { #[test_case("ControlledControlledPauliZ", Operation::from(ControlledControlledPauliZ::new(0, 1, 2)); "ControlledControlledPauliZ")] #[test_case("ControlledControlledPhaseShift", Operation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from(0.2))); "ControlledControlledPhaseShift")] #[test_case("Toffoli", Operation::from(Toffoli::new(0, 1, 2)); "Toffoli")] +#[test_case("ControlledSWAP", Operation::from(ControlledSWAP::new(0, 1, 2)); "ControlledSWAP")] +#[test_case("PhaseShiftedControlledControlledZ", Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::from("theta"))); "PhaseShiftedControlledControlledZ")] +#[test_case("PhaseShiftedControlledControlledPhase", Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::from("phi"), CalculatorFloat::from("theta"))); "PhaseShiftedControlledControlledPhase")] fn test_pyo3_hqslang(name: &'static str, input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -112,6 +159,8 @@ fn test_pyo3_hqslang(name: &'static str, input_operation: Operation) { #[test_case(Operation::from(ControlledControlledPauliZ::new(0, 1, 2)); "ControlledControlledPauliZ")] #[test_case(Operation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from(0.2))); "ControlledControlledPhaseShift")] #[test_case(Operation::from(Toffoli::new(0, 1, 2)); "Toffoli")] +#[test_case(Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::FRAC_PI_2)); "PhaseShiftedControlledControlledZ")] +#[test_case(Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::FRAC_PI_2, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] fn test_pyo3_remapqubits(input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -180,9 +229,81 @@ fn test_pyo3_remapqubits(input_operation: Operation) { }) } +#[test_case(Operation::from(ControlledSWAP::new(0, 1, 2)); "ControlledSWAP")] +fn test_pyo3_remapqubits_cswap(input_operation: Operation) { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let operation = convert_operation_to_pyobject(input_operation).unwrap(); + + // test initial qubits + let control: usize = operation + .call_method0(py, "control") + .unwrap() + .bind(py) + .extract() + .unwrap(); + assert_eq!(control.clone(), 0); + let target_0: usize = operation + .call_method0(py, "target_0") + .unwrap() + .bind(py) + .extract() + .unwrap(); + assert_eq!(target_0.clone(), 1); + let target_1: usize = operation + .call_method0(py, "target_1") + .unwrap() + .bind(py) + .extract() + .unwrap(); + assert_eq!(target_1.clone(), 2); + + // remap qubits + let mut qubit_mapping: HashMap = HashMap::new(); + qubit_mapping.insert(0, 2); + qubit_mapping.insert(2, 0); + qubit_mapping.insert(1, 3); + qubit_mapping.insert(3, 1); + let result = operation + .call_method1(py, "remap_qubits", (qubit_mapping,)) + .unwrap(); + + // test re-mapped qubit + let control_new: usize = result + .call_method0(py, "control") + .unwrap() + .bind(py) + .extract() + .unwrap(); + assert_eq!(control_new.clone(), 2); + let target_0_new: usize = result + .call_method0(py, "target_0") + .unwrap() + .bind(py) + .extract() + .unwrap(); + assert_eq!(target_0_new.clone(), 3); + let target_1_new: usize = result + .call_method0(py, "target_1") + .unwrap() + .bind(py) + .extract() + .unwrap(); + assert_eq!(target_1_new.clone(), 0); + + // test that initial and rempapped qubits are different + assert_ne!(control, control_new); + assert_ne!(target_0, target_0_new); + assert_ne!(target_1, target_1_new); + }) +} + #[test_case(Operation::from(ControlledControlledPauliZ::new(0, 1, 2)); "ControlledControlledPauliZ")] #[test_case(Operation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from(0.2))); "ControlledControlledPhaseShift")] #[test_case(Operation::from(Toffoli::new(0, 1, 2)); "Toffoli")] +#[test_case(Operation::from(ControlledSWAP::new(0, 1, 2)); "ControlledSWAP")] +#[test_case(Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::FRAC_PI_2)); "PhaseShiftedControlledControlledZ")] +#[test_case(Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::FRAC_PI_2, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] fn test_pyo3_remapqubits_error(input_operation: Operation) { // preparation pyo3::prepare_freethreaded_python(); @@ -197,6 +318,8 @@ fn test_pyo3_remapqubits_error(input_operation: Operation) { } #[test_case(Operation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from("theta"))); "ControlledControlledPhaseShift")] +#[test_case(Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::from("theta"))); "PhaseShiftedControlledControlledZ")] +#[test_case(Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::FRAC_PI_2, CalculatorFloat::from("theta"))); "PhaseShiftedControlledControlledPhase")] fn test_pyo3_unitarymatrix_error(input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -209,6 +332,9 @@ fn test_pyo3_unitarymatrix_error(input_operation: Operation) { #[test_case(Operation::from(ControlledControlledPauliZ::new(0, 1, 2)); "ControlledControlledPauliZ")] #[test_case(Operation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from(0.2))); "ControlledControlledPhaseShift")] #[test_case(Operation::from(Toffoli::new(0, 1, 2)); "Toffoli")] +#[test_case(Operation::from(ControlledSWAP::new(0, 1, 2)); "ControlledSWAP")] +#[test_case(Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::FRAC_PI_2)); "PhaseShiftedControlledControlledZ")] +#[test_case(Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::FRAC_PI_2, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] fn test_pyo3_unitarymatrix(input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -240,6 +366,15 @@ fn test_pyo3_unitarymatrix(input_operation: Operation) { #[test_case( "Toffoli { control_0: 1, control_1: 0, target: 2 }", Operation::from(Toffoli::new(1, 0, 2)); "Toffoli")] +#[test_case( + "ControlledSWAP { control: 1, target_0: 0, target_1: 2 }", + Operation::from(ControlledSWAP::new(1, 0, 2)); "ControlledSWAP")] +#[test_case( + "PhaseShiftedControlledControlledZ { control_0: 0, control_1: 1, target: 2, phi: Float(1.5707963267948966) }", + Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::FRAC_PI_2)); "PhaseShiftedControlledControlledZ")] +#[test_case( + "PhaseShiftedControlledControlledPhase { control_0: 0, control_1: 1, target: 2, theta: Float(1.5707963267948966), phi: Float(3.141592653589793) }", + Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::FRAC_PI_2, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] fn test_pyo3_format_repr(format_repr: &str, input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -256,6 +391,9 @@ fn test_pyo3_format_repr(format_repr: &str, input_operation: Operation) { #[test_case(Operation::from(ControlledControlledPauliZ::new(0, 1, 2)); "ControlledControlledPauliZ")] #[test_case(Operation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from(0.2))); "ControlledControlledPhaseShift")] #[test_case(Operation::from(Toffoli::new(0, 1, 2)); "Toffoli")] +#[test_case(Operation::from(ControlledSWAP::new(0, 1, 2)); "ControlledSWAP")] +#[test_case(Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::FRAC_PI_2)); "PhaseShiftedControlledControlledZ")] +#[test_case(Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::FRAC_PI_2, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] fn test_pyo3_copy_deepcopy(input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -288,6 +426,12 @@ fn test_pyo3_copy_deepcopy(input_operation: Operation) { Operation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from(1.0))); "ControlledControlledPhaseShift")] #[test_case(Operation::from(Toffoli::new(0, 1, 2)), Operation::from(Toffoli::new(0, 1, 2)); "Toffoli")] +#[test_case(Operation::from(ControlledSWAP::new(0, 1, 2)), + Operation::from(ControlledSWAP::new(0, 1, 2)); "ControlledSWAP")] +#[test_case(Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::from("test"))), + Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::from(1.0))); "PhaseShiftedControlledControlledZ")] +#[test_case(Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::from("test"), CalculatorFloat::from("test"))), + Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::from(1.0), CalculatorFloat::from(1.0))); "PhaseShiftedControlledControlledPhase")] fn test_pyo3_substitute_parameters(first_op: Operation, second_op: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -311,6 +455,8 @@ fn test_pyo3_substitute_parameters(first_op: Operation, second_op: Operation) { } #[test_case(Operation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from("test"))); "ControlledControlledPhaseShift")] +#[test_case(Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::from("test"))); "PhaseShiftedControlledControlledZ")] +#[test_case(Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::from("test"), CalculatorFloat::from("test"))); "PhaseShiftedControlledControlledPhase")] fn test_pyo3_substitute_params_error(input_operation: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -323,6 +469,8 @@ fn test_pyo3_substitute_params_error(input_operation: Operation) { #[test_case(Operation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from(1.0))), Operation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from(1.0 * 1.5))); "ControlledControlledPhaseShift")] +#[test_case(Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::from(1.0), CalculatorFloat::from(1.0))), + Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::from(1.0 * 1.5), CalculatorFloat::from(1.0))); "PhaseShiftedControlledControlledPhase")] fn test_pyo3_powercf(first_op: Operation, second_op: Operation) { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -495,6 +643,159 @@ fn test_new_toffoli(input_operation: Operation, arguments: (u32, u32, u32), meth }) } +#[test_case(Operation::from(ControlledSWAP::new(0, 1, 2)), (0, 1, 2), "__eq__"; "ControlledSWAP_eq")] +#[test_case(Operation::from(ControlledSWAP::new(2, 1, 0)), (0, 1, 2), "__ne__"; "ControlledSWAP_ne")] +fn test_new_controlledswap(input_operation: Operation, arguments: (u32, u32, 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_bound::(); + let binding = operation_type.call1(arguments).unwrap(); + let operation_py = binding.downcast::().unwrap(); + let comparison = bool::extract_bound( + &operation + .bind(py) + .call_method1(method, (operation_py,)) + .unwrap(), + ) + .unwrap(); + assert!(comparison); + + // Error initialisation + let result = operation_type.call1((0, 1, vec!["fails"])); + assert!(result.is_err()); + + let result = operation_type.call1((0, vec!["fails"], 2)); + assert!(result.is_err()); + + // Testing PartialEq, Clone and Debug + let def_wrapper = operation_py.extract::().unwrap(); + let binding = operation_type.call1((1, 2, 3)).unwrap(); + let new_op_diff = binding.downcast::().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), + "ControlledSWAPWrapper { internal: ControlledSWAP { control: 1, target_0: 2, target_1: 3 } }" + ); + }) +} + +#[test_case(Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::from(1.0))), (0, 1, 2, 1.0), "__eq__"; "PhaseShiftedControlledControlledZ_eq")] +#[test_case(Operation::from(PhaseShiftedControlledControlledZ::new(2, 1, 0, CalculatorFloat::from(1.0))), (0, 1, 2, 1.0), "__ne__"; "PhaseShiftedControlledControlledZ_ne")] +fn test_new_phaseshiftedccz( + input_operation: Operation, + arguments: (u32, u32, u32, f64), + 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_bound::(); + let binding = operation_type.call1(arguments).unwrap(); + let operation_py = binding + .downcast::() + .unwrap(); + let comparison = bool::extract_bound( + &operation + .bind(py) + .call_method1(method, (operation_py,)) + .unwrap(), + ) + .unwrap(); + assert!(comparison); + + // Error initialisation + let result = operation_type.call1((0, 1, vec!["fails"])); + assert!(result.is_err()); + + let result = operation_type.call1((0, vec!["fails"], 2)); + assert!(result.is_err()); + + // Testing PartialEq, Clone and Debug + let def_wrapper = operation_py + .extract::() + .unwrap(); + let binding = operation_type.call1((1, 2, 3, 1.0)).unwrap(); + let new_op_diff = binding + .downcast::() + .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), + "PhaseShiftedControlledControlledZWrapper { internal: PhaseShiftedControlledControlledZ { control_0: 1, control_1: 2, target: 3, phi: Float(1.0) } }" + ); + }) +} + +#[test_case(Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::from(1.0), CalculatorFloat::from(1.0))), (0, 1, 2, 1.0, 1.0), "__eq__"; "PhaseShiftedControlledControlledPhase_eq")] +#[test_case(Operation::from(PhaseShiftedControlledControlledPhase::new(2, 1, 0, CalculatorFloat::from(1.0), CalculatorFloat::from(1.0))), (0, 1, 2, 1.0, 1.0), "__ne__"; "PhaseShiftedControlledControlledPhase_ne")] +fn test_new_phaseshiftedccphase( + input_operation: Operation, + arguments: (u32, u32, u32, f64, f64), + 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_bound::(); + let binding = operation_type.call1(arguments).unwrap(); + let operation_py = binding + .downcast::() + .unwrap(); + let comparison = bool::extract_bound( + &operation + .bind(py) + .call_method1(method, (operation_py,)) + .unwrap(), + ) + .unwrap(); + assert!(comparison); + + // Error initialisation + let result = operation_type.call1((0, 1, vec!["fails"])); + assert!(result.is_err()); + + let result = operation_type.call1((0, vec!["fails"], 2)); + assert!(result.is_err()); + + // Testing PartialEq, Clone and Debug + let def_wrapper = operation_py + .extract::() + .unwrap(); + let binding = operation_type.call1((1, 2, 3, 1.0, 1.0)).unwrap(); + let new_op_diff = binding + .downcast::() + .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), + "PhaseShiftedControlledControlledPhaseWrapper { internal: PhaseShiftedControlledControlledPhase { control_0: 1, control_1: 2, target: 3, theta: Float(1.0), phi: Float(1.0) } }" + ); + }) +} + #[test] fn test_circuit_pyo3_controlledcontrolledpauliz() { pyo3::prepare_freethreaded_python(); @@ -570,11 +871,127 @@ fn test_circuit_pyo3_toffoli() { }); } +#[test] +fn test_circuit_pyo3_controlledswap() { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let input_operation = Operation::from(ControlledSWAP::new(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 += CNOT::new(2, 1); + circuit += Hadamard::new(2); + circuit += CNOT::new(1, 2); + circuit += RotateZ::new(2, -CalculatorFloat::FRAC_PI_4); + circuit += CNOT::new(0, 2); + circuit += TGate::new(2); + circuit += CNOT::new(1, 2); + circuit += RotateZ::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 += RotateZ::new(1, -CalculatorFloat::FRAC_PI_4); + circuit += CNOT::new(0, 1); + circuit += CNOT::new(2, 1); + + assert_eq!(result_circuit.internal, circuit); + }); +} + +#[test] +fn test_circuit_pyo3_phaseshiftedccz() { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let input_operation = Operation::from(PhaseShiftedControlledControlledZ::new( + 0, + 1, + 2, + CalculatorFloat::from(1.0), + )); + 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 += PhaseShiftedControlledPhase::new( + 1, + 2, + CalculatorFloat::FRAC_PI_2, + CalculatorFloat::from(1.0), + ); + circuit += CNOT::new(0, 1); + circuit += PhaseShiftedControlledPhase::new( + 1, + 2, + -CalculatorFloat::FRAC_PI_2, + CalculatorFloat::from(1.0), + ); + circuit += CNOT::new(0, 1); + circuit += PhaseShiftedControlledPhase::new( + 0, + 2, + CalculatorFloat::FRAC_PI_2, + CalculatorFloat::from(1.0), + ); + + assert_eq!(result_circuit.internal, circuit); + }); +} + +#[test] +fn test_circuit_pyo3_phaseshiftedccphase() { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let input_operation = Operation::from(PhaseShiftedControlledControlledPhase::new( + 0, + 1, + 2, + CalculatorFloat::from(1.0), + CalculatorFloat::from(1.0), + )); + 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 += PhaseShiftedControlledPhase::new( + 1, + 2, + CalculatorFloat::from(0.5), + CalculatorFloat::from(1.0), + ); + circuit += CNOT::new(0, 1); + circuit += PhaseShiftedControlledPhase::new( + 1, + 2, + -CalculatorFloat::from(0.5), + CalculatorFloat::from(1.0), + ); + circuit += CNOT::new(0, 1); + circuit += PhaseShiftedControlledPhase::new( + 0, + 2, + CalculatorFloat::from(0.5), + CalculatorFloat::from(1.0), + ); + + assert_eq!(result_circuit.internal, circuit); + }); +} + /// Test json_schema function for all three qubit gate operations #[cfg(feature = "json_schema")] #[test_case(ThreeQubitGateOperation::from(ControlledControlledPauliZ::new(0, 1, 2)); "ControlleControlledPauliZ")] #[test_case(ThreeQubitGateOperation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from("test"))); "ControlledControlledPhaseShift")] #[test_case(ThreeQubitGateOperation::from(Toffoli::new(0, 1, 2)); "Toffoli")] +#[test_case(ThreeQubitGateOperation::from(ControlledSWAP::new(0, 1, 2)); "ControlledSWAP")] +#[test_case(ThreeQubitGateOperation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::FRAC_PI_2)); "PhaseShiftedControlledControlledZ")] +#[test_case(ThreeQubitGateOperation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::FRAC_PI_2, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] fn test_pyo3_json_schema(operation: ThreeQubitGateOperation) { let rust_schema = match operation { ThreeQubitGateOperation::ControlledControlledPauliZ(_) => { @@ -588,8 +1005,22 @@ fn test_pyo3_json_schema(operation: ThreeQubitGateOperation) { ThreeQubitGateOperation::Toffoli(_) => { serde_json::to_string_pretty(&schemars::schema_for!(Toffoli)).unwrap() } + ThreeQubitGateOperation::ControlledSWAP(_) => { + serde_json::to_string_pretty(&schemars::schema_for!(ControlledSWAP)).unwrap() + } + ThreeQubitGateOperation::PhaseShiftedControlledControlledZ(_) => { + serde_json::to_string_pretty(&schemars::schema_for!(PhaseShiftedControlledControlledZ)) + .unwrap() + } + ThreeQubitGateOperation::PhaseShiftedControlledControlledPhase(_) => { + serde_json::to_string_pretty(&schemars::schema_for!( + PhaseShiftedControlledControlledPhase + )) + .unwrap() + } _ => unreachable!(), }; + let og_op = operation.clone(); pyo3::prepare_freethreaded_python(); pyo3::Python::with_gil(|py| { let converted_op = Operation::from(operation); @@ -608,6 +1039,18 @@ fn test_pyo3_json_schema(operation: ThreeQubitGateOperation) { .unwrap(); assert_eq!(current_version_string, ROQOQO_VERSION); - assert_eq!(minimum_supported_version_string, "1.3.0"); + match og_op { + ThreeQubitGateOperation::ControlledControlledPauliZ(_) + | ThreeQubitGateOperation::ControlledControlledPhaseShift(_) + | ThreeQubitGateOperation::Toffoli(_) => { + assert_eq!(minimum_supported_version_string, "1.3.0") + } + ThreeQubitGateOperation::ControlledSWAP(_) + | ThreeQubitGateOperation::PhaseShiftedControlledControlledZ(_) + | ThreeQubitGateOperation::PhaseShiftedControlledControlledPhase(_) => { + assert_eq!(minimum_supported_version_string, "1.16.0") + } + _ => unreachable!(), + }; }); } diff --git a/roqoqo-derive/src/involve_qubits.rs b/roqoqo-derive/src/involve_qubits.rs index 3f88cfbb0..dc253ec75 100644 --- a/roqoqo-derive/src/involve_qubits.rs +++ b/roqoqo-derive/src/involve_qubits.rs @@ -72,6 +72,7 @@ fn involve_qubits_struct(ds: DataStruct, ident: Ident) -> TokenStream { let mut control: bool = false; let mut control_0: bool = false; let mut control_1: bool = false; + let mut control_2: bool = false; let mut target: bool = false; let mut qubits: bool = false; @@ -115,6 +116,13 @@ fn involve_qubits_struct(ds: DataStruct, ident: Ident) -> TokenStream { panic!("Field control_1 must have type usize") } } + "control_2" => { + if type_string == Some("usize".to_string()) { + control_2 = true; + } else { + panic!("Field control_2 must have type usize") + } + } "qubits" => { qubits = true; } @@ -138,9 +146,29 @@ fn involve_qubits_struct(ds: DataStruct, ident: Ident) -> TokenStream { } } } + } else if control_2 { + if !(control_0 && control_1 && target) { + panic!("When deriving InvolveQubits for a four-qubit operation control_0, control_1, control_2 and target have to be present"); + }; + // Creating a function that puts qubits `control_0`, `control_1` `control_2` and `target` into the InvolvedQubits HashSet + quote! { + /// Implements [InvolveQubits] trait for the qubits involved in this Operation. + #[automatically_derived] + impl InvolveQubits for #ident{ + /// Returns a list of all involed qubits. + fn involved_qubits(&self ) -> InvolvedQubits { + let mut new_hash_set: std::collections::HashSet = std::collections::HashSet::new(); + new_hash_set.insert(self.control_0); + new_hash_set.insert(self.control_1); + new_hash_set.insert(self.control_2); + new_hash_set.insert(self.target); + InvolvedQubits::Set(new_hash_set) + } + } + } } else if control_0 || control_1 { if control { - panic!("When deriving InvolveQubits for a three-qubit operation, control field must not be present") + panic!("When deriving InvolveQubits for a three-qubit operation, control field is not compatible with control_0 and control_1 fields"); } if !(control_0 && control_1 && target) { panic!("When deriving InvolveQubits for a three-qubit operation control_0, control_1 and target have to be present"); diff --git a/roqoqo-derive/src/lib.rs b/roqoqo-derive/src/lib.rs index 159d1af80..d86ab870b 100644 --- a/roqoqo-derive/src/lib.rs +++ b/roqoqo-derive/src/lib.rs @@ -30,11 +30,12 @@ mod substitute_modes; mod supported_version; /// Array of field names that are reserved for use with specific traits -const RESERVED_FIELDS: &[&str; 15] = &[ +const RESERVED_FIELDS: &[&str; 16] = &[ "qubit", "control", "control_0", "control_1", + "control_2", "target", "qubits", "global_phase", @@ -104,6 +105,13 @@ pub fn derive_operate_three_qubit(input: proc_macro::TokenStream) -> proc_macro: operate_n_qubit::dispatch_struct_enum_three_qubit(parsed_input).into() } +/// Derive macro for the [roqoqo::OperateFourQubit] trait +#[proc_macro_derive(OperateFourQubit)] +pub fn derive_operate_four_qubit(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let parsed_input = parse_macro_input!(input as DeriveInput); + operate_n_qubit::dispatch_struct_enum_four_qubit(parsed_input).into() +} + /// Derive macro for the [roqoqo::OperateMultiQubit] trait #[proc_macro_derive(OperateMultiQubit)] pub fn derive_operate_multi_qubit(input: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -183,6 +191,13 @@ pub fn derive_operate_three_qubit_gate(input: proc_macro::TokenStream) -> proc_m operate_unitary::dispatch_struct_enum_three_qubit_gate(parsed_input).into() } +/// Derive macro for the [roqoqo::OperateFourQubitGate] trait +#[proc_macro_derive(OperateFourQubitGate)] +pub fn derive_operate_four_qubit_gate(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let parsed_input = parse_macro_input!(input as DeriveInput); + operate_unitary::dispatch_struct_enum_four_qubit_gate(parsed_input).into() +} + /// Derive macro for the [roqoqo::OperateMultiQubitGate] trait #[proc_macro_derive(OperateMultiQubitGate)] pub fn derive_operate_multi_qubit_gate(input: proc_macro::TokenStream) -> proc_macro::TokenStream { diff --git a/roqoqo-derive/src/operate_n_qubit.rs b/roqoqo-derive/src/operate_n_qubit.rs index 75d5fc137..ba9f77f79 100644 --- a/roqoqo-derive/src/operate_n_qubit.rs +++ b/roqoqo-derive/src/operate_n_qubit.rs @@ -300,6 +300,160 @@ fn operate_three_qubit_struct(ident: Ident) -> TokenStream { } } +/// Dispatch to derive OperateFourQubit for enums and structs +pub fn dispatch_struct_enum_four_qubit(input: DeriveInput) -> TokenStream { + let ident = input.ident; + match input.data { + Data::Struct(_ds) => operate_four_qubit_struct(ident), + Data::Enum(de) => operate_four_qubit_enum(de, ident), + // There can be other objects that are valid Derive inputs, but we define our macro only for structs and enums + _ => panic!("OperateFourQubit can only be derived on structs and enums"), + } +} + +fn operate_four_qubit_enum(de: DataEnum, ident: Ident) -> TokenStream { + let DataEnum { variants, .. } = de; + let control_0_quotes = variants.clone().into_iter().map(|v| { + let vident = v.ident.clone(); + + // We match the fields in the variant, + let fields = match v.fields { + Fields::Unnamed(fields) => fields, + // and panic when the fields are not unnamed, + _ => panic!( + "OperateFourQubit can only be derived for enums with newtype structs as variants" + ), + }; + // and panic when there is more than one field, ensuring newtype structs. + if fields.unnamed.iter().len() != 1 { + panic!( + "OperateFourQubit can only be derived for enums with newtype structs as variants" + ) + } + quote! { + &#ident::#vident(ref inner) => {OperateFourQubit::control_0(&(*inner))}, + } + }); + let control_1_quotes = variants.clone().into_iter().map(|v| { + let vident = v.ident.clone(); + + // We match the fields in the variant, + let fields = match v.fields { + Fields::Unnamed(fields) => fields, + // and panic when the fields are not unnamed, + _ => panic!( + "OperateFourQubit can only be derived for enums with newtype structs as variants" + ), + }; + // and panic when there is more than one field, ensuring newtype structs. + if fields.unnamed.iter().len() != 1 { + panic!( + "OperateFourQubit can only be derived for enums with newtype structs as variants" + ) + } + quote! { + &#ident::#vident(ref inner) => {OperateFourQubit::control_1(&(*inner))}, + } + }); + let control_2_quotes = variants.clone().into_iter().map(|v| { + let vident = v.ident.clone(); + + // We match the fields in the variant, + let fields = match v.fields { + Fields::Unnamed(fields) => fields, + // and panic when the fields are not unnamed, + _ => panic!( + "OperateFourQubit can only be derived for enums with newtype structs as variants" + ), + }; + // and panic when there is more than one field, ensuring newtype structs. + if fields.unnamed.iter().len() != 1 { + panic!( + "OperateFourQubit can only be derived for enums with newtype structs as variants" + ) + } + quote! { + &#ident::#vident(ref inner) => {OperateFourQubit::control_2(&(*inner))}, + } + }); + let target_quotes = variants.into_iter().map(|v| { + let vident = v.ident.clone(); + let fields = match v.fields { + Fields::Unnamed(fields) => fields, + _ => panic!( + "OperateFourQubit can only be derived for enums with newtype structs as variants" + ), + }; + if fields.unnamed.iter().len() != 1 { + panic!( + "OperateFourQubit can only be derived for enums with newtype structs as variants" + ) + } + quote! { + &#ident::#vident(ref inner) => {OperateFourQubit::target(&(*inner))}, + } + }); + quote! { + #[automatically_derived] + /// Trait for Operations acting on exactly four qubits. + impl OperateFourQubit for #ident{ + /// Returns `control_0` qubit of the four qubit Operation. + fn control_0(&self) -> &usize { + match self{ + #(#control_0_quotes)* + _ => panic!("Unexpectedly cannot match variant") + } + } + /// Returns `control_1` qubit of the four qubit Operation. + fn control_1(&self) -> &usize { + match self{ + #(#control_1_quotes)* + _ => panic!("Unexpectedly cannot match variant") + } + } + /// Returns `control_2` qubit of the four qubit Operation. + fn control_2(&self) -> &usize { + match self{ + #(#control_2_quotes)* + _ => panic!("Unexpectedly cannot match variant") + } + } + /// Returns `target` qubit of the four qubit Operation. + fn target(&self) -> &usize { + match self{ + #(#target_quotes)* + _ => panic!("Unexpectedly cannot match variant") + } + } + } + } +} + +fn operate_four_qubit_struct(ident: Ident) -> TokenStream { + quote! { + #[automatically_derived] + /// Trait for Operations acting on exactly four qubits. + impl OperateFourQubit for #ident{ + /// Returns `control_0` qubit of the four qubit Operation. + fn control_0(&self ) -> &usize { + &self.control_0 + } + /// Returns `control_1` qubit of the four qubit Operation. + fn control_1(&self ) -> &usize { + &self.control_1 + } + /// Returns `control_2` qubit of the four qubit Operation. + fn control_2(&self ) -> &usize { + &self.control_2 + } + /// Returns `target` qubit of the four qubit Operation. + fn target(&self ) -> &usize { + &self.target + } + } + } +} + pub fn dispatch_struct_enum_multi_qubit(input: DeriveInput) -> TokenStream { let ident = input.ident; match input.data { diff --git a/roqoqo-derive/src/operate_unitary.rs b/roqoqo-derive/src/operate_unitary.rs index c67be753f..7078a32fa 100644 --- a/roqoqo-derive/src/operate_unitary.rs +++ b/roqoqo-derive/src/operate_unitary.rs @@ -267,6 +267,46 @@ fn three_qubit_gate_struct(ident: Ident) -> TokenStream { } } +pub fn dispatch_struct_enum_four_qubit_gate(input: DeriveInput) -> TokenStream { + let ident = input.ident; + match input.data { + Data::Struct(_ds) => four_qubit_gate_struct(ident), + Data::Enum(de) => four_qubit_gate_enum(de, ident), + _ => panic!("OperateFourQubitGate can only be derived on structs and enums"), + } +} + +fn four_qubit_gate_enum(de: DataEnum, ident: Ident) -> TokenStream { + let variants_with_type = extract_variants_with_types(de).into_iter(); + let match_quotes = variants_with_type.map(|(vident, _, _)| { + quote! { + &#ident::#vident(ref inner) => {OperateFourQubitGate::circuit(&(*inner))}, + } + }); + quote! { + #[automatically_derived] + impl OperateFourQubitGate for #ident{ + fn circuit(&self) -> crate::Circuit { + match self{ + #(#match_quotes)* + _ => panic!("Unexpectedly cannot match variant") + } + } + } + } +} + +fn four_qubit_gate_struct(ident: Ident) -> TokenStream { + quote! { + #[automatically_derived] + impl OperateFourQubitGate for #ident{ + fn circuit(&self ) -> crate::Circuit { + self.circuit + } + } + } +} + pub fn dispatch_struct_enum_multi_qubit_gate(input: DeriveInput) -> TokenStream { let ident = input.ident; match input.data { diff --git a/roqoqo-derive/src/substitute.rs b/roqoqo-derive/src/substitute.rs index 745fc13ff..5eddc74ff 100644 --- a/roqoqo-derive/src/substitute.rs +++ b/roqoqo-derive/src/substitute.rs @@ -92,6 +92,7 @@ fn substitute_struct(ds: DataStruct, ident: Ident) -> TokenStream { "control" => quote! {*mapping.get(&self.control).unwrap_or(&self.control)}, "control_0" => quote! {*mapping.get(&self.control_0).unwrap_or(&self.control_0)}, "control_1" => quote! {*mapping.get(&self.control_1).unwrap_or(&self.control_1)}, + "control_2" => quote! {*mapping.get(&self.control_2).unwrap_or(&self.control_2)}, "target" => quote! {*mapping.get(&self.target).unwrap_or(&self.target)}, "qubits" => quote! { new_qubits }, _ => quote! {(self).#fid.clone()}, diff --git a/roqoqo/build.rs b/roqoqo/build.rs index 20b6e681d..78d777e50 100644 --- a/roqoqo/build.rs +++ b/roqoqo/build.rs @@ -44,6 +44,8 @@ struct Visitor { two_qubit_operations: Vec, // Identifiers of structs belonging to ThreeQubitOperation enum three_qubit_operations: Vec, + // Identifiers of structs belonging to FourQubitOperation enum + four_qubit_operations: Vec, // Identifiers of structs belonging to MultiQubitOperation enum multi_qubit_operations: Vec, // Identifiers of structs belonging to PragmaOperation enum @@ -66,6 +68,8 @@ struct Visitor { two_qubit_gate_operations: Vec, // Identifiers of structs belonging to ThreeQubitGateOperation enum three_qubit_gate_operations: Vec, + // Identifiers of structs belonging to FourQubitGateOperation enum + four_qubit_gate_operations: Vec, // Identifiers of structs belonging to MultiQubitGateOperation enum multi_qubit_gate_operations: Vec, // Register of minor point version Operation was introduced in. @@ -91,6 +95,7 @@ impl Visitor { single_qubit_operations: Vec::new(), two_qubit_operations: Vec::new(), three_qubit_operations: Vec::new(), + four_qubit_operations: Vec::new(), multi_qubit_operations: Vec::new(), pragma_operations: Vec::new(), pragma_noise_operations: Vec::new(), @@ -102,6 +107,7 @@ impl Visitor { single_qubit_gate_operations: Vec::new(), two_qubit_gate_operations: Vec::new(), three_qubit_gate_operations: Vec::new(), + four_qubit_gate_operations: Vec::new(), multi_qubit_gate_operations: Vec::new(), roqoqo_version_register: HashMap::new(), mode_gate_operations: Vec::new(), @@ -213,6 +219,11 @@ impl<'ast> Visit<'ast> for Visitor { { self.three_qubit_operations.push(i.ident.clone()); } + if parsed_arguments.contains("Operate") + && parsed_arguments.contains("OperateFourQubit") + { + self.four_qubit_operations.push(i.ident.clone()); + } if parsed_arguments.contains("Operate") && parsed_arguments.contains("OperateMultiQubit") { @@ -260,6 +271,9 @@ impl<'ast> Visit<'ast> for Visitor { if parsed_arguments.contains("OperateThreeQubitGate") { self.three_qubit_gate_operations.push(i.ident.clone()); } + if parsed_arguments.contains("OperateFourQubitGate") { + self.four_qubit_gate_operations.push(i.ident.clone()); + } if parsed_arguments.contains("OperateMultiQubitGate") { self.multi_qubit_gate_operations.push(i.ident.clone()); } @@ -334,6 +348,9 @@ impl<'ast> Visit<'ast> for Visitor { if trait_name.as_str() == "OperateThreeQubit" { self.three_qubit_operations.push(id.clone()); } + if trait_name.as_str() == "OperateFourQubit" { + self.four_qubit_operations.push(id.clone()); + } if trait_name.as_str() == "OperateMultiQubit" { self.multi_qubit_operations.push(id.clone()); } @@ -398,6 +415,9 @@ impl<'ast> Visit<'ast> for Visitor { if trait_name.as_str() == "OperateThreeQubitGate" { self.three_qubit_gate_operations.push(id.clone()); } + if trait_name.as_str() == "OperateFourQubitGate" { + self.four_qubit_gate_operations.push(id.clone()); + } if trait_name.as_str() == "OperatePragmaNoise" { self.pragma_noise_operations.push(id.clone()); } @@ -436,6 +456,7 @@ const SOURCE_FILES: &[&str] = &[ "src/operations/pragma_operations.rs", "src/operations/two_qubit_gate_operations.rs", "src/operations/three_qubit_gate_operations.rs", + "src/operations/four_qubit_gate_operations.rs", "src/operations/multi_qubit_gate_operations.rs", "src/operations/measurement_operations.rs", "src/operations/define_operations.rs", @@ -482,6 +503,7 @@ fn main() { two_qubit_operations_quotes.extend(res); } + // Construct TokenStreams for variants of operation enum let mut three_qubit_operations_quotes: Vec = Vec::new(); for i in 0..NUMBER_OF_MINOR_VERSIONS { @@ -491,6 +513,15 @@ fn main() { three_qubit_operations_quotes.extend(res); } + // Construct TokenStreams for variants of operation enum + let mut four_qubit_operations_quotes: Vec = Vec::new(); + for i in 0..NUMBER_OF_MINOR_VERSIONS { + let res: Vec = + build_quotes(&vis, i, vis.four_qubit_operations.clone()); + + four_qubit_operations_quotes.extend(res); + } + // Construct TokenStreams for variants of operation enum let mut multi_qubit_operations_quotes: Vec = Vec::new(); for i in 0..NUMBER_OF_MINOR_VERSIONS { @@ -574,6 +605,14 @@ fn main() { three_qubit_gate_operations_quote.extend(res); } + // Construct TokenStreams for variants of operation enum + let mut four_qubit_gate_operations_quote: Vec = Vec::new(); + for i in 0..NUMBER_OF_MINOR_VERSIONS { + let res: Vec = + build_quotes(&vis, i, vis.four_qubit_gate_operations.clone()); + four_qubit_gate_operations_quote.extend(res); + } + // Construct TokenStreams for variants of operation enum let mut multi_qubit_gate_operations_quote: Vec = Vec::new(); for i in 0..NUMBER_OF_MINOR_VERSIONS { @@ -780,6 +819,15 @@ fn main() { #(#three_qubit_gate_operations_quote),* } + /// Enum of all Operations implementing [OperateFourQubitGate] + #[derive(Debug, Clone, PartialEq, InvolveQubits, Operate, OperateTryFromEnum, Substitute, OperateGate, OperateFourQubit, OperateFourQubitGate, SupportedVersion)] + #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] + #[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))] + #[non_exhaustive] + pub enum FourQubitGateOperation { + #(#four_qubit_gate_operations_quote),* + } + /// Enum of all Operations implementing [OperateMultiQubitGate] #[derive(Debug, Clone, PartialEq, InvolveQubits, Operate, OperateTryFromEnum, Substitute, OperateGate, OperateMultiQubit, OperateMultiQubitGate, SupportedVersion)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] diff --git a/roqoqo/src/operations/four_qubit_gate_operations.rs b/roqoqo/src/operations/four_qubit_gate_operations.rs new file mode 100644 index 000000000..55ad89056 --- /dev/null +++ b/roqoqo/src/operations/four_qubit_gate_operations.rs @@ -0,0 +1,246 @@ +// Copyright © 2021-2024 HQS Quantum Simulations GmbH. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under the +// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing permissions and +// limitations under the License. + +use crate::operations::*; +use crate::Circuit; +use ndarray::Array2; +use num_complex::Complex64; + +/// The triple-controlled PauliX gate. +/// +/// +#[allow(clippy::upper_case_acronyms)] +#[derive( + Debug, + Clone, + PartialEq, + roqoqo_derive::Operate, + roqoqo_derive::OperateFourQubit, + roqoqo_derive::InvolveQubits, + roqoqo_derive::Substitute, +)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))] +pub struct TripleControlledPauliX { + /// The first control qubit involved in the triple-controlled PauliX gate. + control_0: usize, + /// The second control qubit involved in the triple-controlled PauliX gate. + control_1: usize, + /// The third control qubit involved in the triple-controlled PauliX gate. + control_2: usize, + /// The target qubit to apply the PauliX gate to. + target: usize, +} + +impl super::ImplementedIn1point16 for TripleControlledPauliX {} + +impl SupportedVersion for TripleControlledPauliX { + fn minimum_supported_roqoqo_version(&self) -> (u32, u32, u32) { + (1, 16, 0) + } +} + +#[allow(non_upper_case_globals)] +const TAGS_TripleControlledPauliX: &[&str; 4] = &[ + "Operation", + "GateOperation", + "FourQubitGateOperation", + "TripleControlledPauliX", +]; + +impl OperateGate for TripleControlledPauliX { + fn unitary_matrix(&self) -> Result, RoqoqoError> { + let dim = 16; + let mut array: Array2 = Array2::zeros((dim, dim)); + for i in 0..dim - 2 { + array[(i, i)] = Complex64::new(1.0, 0.0); + } + array[(dim - 2, dim - 1)] = Complex64::new(1.0, 0.0); + array[(dim - 1, dim - 2)] = Complex64::new(1.0, 0.0); + Ok(array) + } +} + +impl OperateFourQubitGate for TripleControlledPauliX { + fn circuit(&self) -> Circuit { + let mut circuit = Circuit::new(); + circuit += CNOT::new(self.control_0, self.target); + circuit += CNOT::new(self.control_0, self.control_1); + circuit += CNOT::new(self.control_1, self.target); + circuit += CNOT::new(self.control_0, self.control_1); + circuit += CNOT::new(self.control_1, self.target); + circuit += CNOT::new(self.control_1, self.control_2); + circuit += CNOT::new(self.control_2, self.target); + circuit += CNOT::new(self.control_0, self.control_2); + circuit += CNOT::new(self.control_2, self.target); + circuit += CNOT::new(self.control_1, self.control_2); + circuit += CNOT::new(self.control_2, self.target); + circuit += CNOT::new(self.control_0, self.control_2); + circuit += CNOT::new(self.control_2, self.target); + circuit + } +} + +/// The triple-controlled PauliZ gate. +/// +/// +#[allow(clippy::upper_case_acronyms)] +#[derive( + Debug, + Clone, + PartialEq, + roqoqo_derive::Operate, + roqoqo_derive::OperateFourQubit, + roqoqo_derive::InvolveQubits, + roqoqo_derive::Substitute, +)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))] +pub struct TripleControlledPauliZ { + /// The first control qubit involved in the triple-controlled PauliZ gate. + control_0: usize, + /// The second control qubit involved in the triple-controlled PauliZ gate. + control_1: usize, + /// The third control qubit involved in the triple-controlled PauliZ gate. + control_2: usize, + /// The target qubit to apply the PauliZ gate to. + target: usize, +} + +impl super::ImplementedIn1point16 for TripleControlledPauliZ {} + +impl SupportedVersion for TripleControlledPauliZ { + fn minimum_supported_roqoqo_version(&self) -> (u32, u32, u32) { + (1, 16, 0) + } +} + +#[allow(non_upper_case_globals)] +const TAGS_TripleControlledPauliZ: &[&str; 4] = &[ + "Operation", + "GateOperation", + "FourQubitGateOperation", + "TripleControlledPauliZ", +]; + +impl OperateGate for TripleControlledPauliZ { + fn unitary_matrix(&self) -> Result, RoqoqoError> { + let dim = 16; + let mut array: Array2 = Array2::zeros((dim, dim)); + for i in 0..dim - 1 { + array[(i, i)] = Complex64::new(1.0, 0.0); + } + array[(dim - 1, dim - 1)] = Complex64::new(-1.0, 0.0); + Ok(array) + } +} + +impl OperateFourQubitGate for TripleControlledPauliZ { + fn circuit(&self) -> Circuit { + let mut circuit = Circuit::new(); + circuit += ControlledPauliZ::new(self.control_0, self.target); + circuit += CNOT::new(self.control_0, self.control_1); + circuit += ControlledPauliZ::new(self.control_1, self.target); + circuit += CNOT::new(self.control_0, self.control_1); + circuit += ControlledPauliZ::new(self.control_1, self.target); + circuit += CNOT::new(self.control_1, self.control_2); + circuit += ControlledPauliZ::new(self.control_2, self.target); + circuit += CNOT::new(self.control_0, self.control_2); + circuit += ControlledPauliZ::new(self.control_2, self.target); + circuit += CNOT::new(self.control_1, self.control_2); + circuit += ControlledPauliZ::new(self.control_2, self.target); + circuit += CNOT::new(self.control_0, self.control_2); + circuit += ControlledPauliZ::new(self.control_2, self.target); + circuit + } +} + +/// The triple-controlled PhaseShift gate. +/// +/// +#[allow(clippy::upper_case_acronyms)] +#[derive( + Debug, + Clone, + PartialEq, + roqoqo_derive::Operate, + roqoqo_derive::OperateFourQubit, + roqoqo_derive::InvolveQubits, + roqoqo_derive::Substitute, +)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))] +pub struct TripleControlledPhaseShift { + /// The first control qubit involved in the triple-controlled PhaseShift gate. + control_0: usize, + /// The second control qubit involved in the triple-controlled PhaseShift gate. + control_1: usize, + /// The third control qubit involved in the triple-controlled PhaseShift gate. + control_2: usize, + /// The target qubit to apply the PhaseShift gate to. + target: usize, + /// The rotation angle θ. + theta: CalculatorFloat, +} + +impl super::ImplementedIn1point16 for TripleControlledPhaseShift {} + +impl SupportedVersion for TripleControlledPhaseShift { + fn minimum_supported_roqoqo_version(&self) -> (u32, u32, u32) { + (1, 16, 0) + } +} + +#[allow(non_upper_case_globals)] +const TAGS_TripleControlledPhaseShift: &[&str; 4] = &[ + "Operation", + "GateOperation", + "FourQubitGateOperation", + "TripleControlledPhaseShift", +]; + +impl OperateGate for TripleControlledPhaseShift { + fn unitary_matrix(&self) -> Result, RoqoqoError> { + let c: f64 = (f64::try_from(self.theta.clone())?).cos(); + let s: f64 = (f64::try_from(self.theta.clone())?).sin(); + let dim = 16; + let mut array: Array2 = Array2::zeros((dim, dim)); + for i in 0..dim - 1 { + array[(i, i)] = Complex64::new(1.0, 0.0); + } + array[(dim - 1, dim - 1)] = Complex64::new(c, s); + Ok(array) + } +} + +impl OperateFourQubitGate for TripleControlledPhaseShift { + fn circuit(&self) -> Circuit { + let mut circuit = Circuit::new(); + circuit += ControlledPhaseShift::new(self.control_0, self.target, self.theta().clone() / 2); + circuit += CNOT::new(self.control_0, self.control_1); + circuit += + ControlledPhaseShift::new(self.control_1, self.target, -self.theta().clone() / 2); + circuit += CNOT::new(self.control_0, self.control_1); + circuit += ControlledPhaseShift::new(self.control_1, self.target, self.theta().clone() / 2); + circuit += CNOT::new(self.control_1, self.control_2); + circuit += + ControlledPhaseShift::new(self.control_2, self.target, -self.theta().clone() / 2); + circuit += CNOT::new(self.control_0, self.control_2); + circuit += ControlledPhaseShift::new(self.control_2, self.target, self.theta().clone() / 2); + circuit += CNOT::new(self.control_1, self.control_2); + circuit += + ControlledPhaseShift::new(self.control_2, self.target, -self.theta().clone() / 2); + circuit += CNOT::new(self.control_0, self.control_2); + circuit += ControlledPhaseShift::new(self.control_2, self.target, self.theta().clone() / 2); + circuit + } +} diff --git a/roqoqo/src/operations/mod.rs b/roqoqo/src/operations/mod.rs index aaa0e8358..5efbae06d 100644 --- a/roqoqo/src/operations/mod.rs +++ b/roqoqo/src/operations/mod.rs @@ -56,6 +56,10 @@ pub use two_qubit_gate_operations::*; #[doc(hidden)] mod three_qubit_gate_operations; pub use three_qubit_gate_operations::*; +/// Collection of roqoqo four qubit gate operations. +#[doc(hidden)] +mod four_qubit_gate_operations; +pub use four_qubit_gate_operations::*; /// Collection of roqoqo bosonic operations. #[doc(hidden)] mod bosonic_operations; @@ -337,6 +341,29 @@ pub trait OperateThreeQubit: Operate + InvolveQubits + Substitute + Clone + Part fn control_1(&self) -> &usize; } +/// Trait for Operations acting on exactly four qubits. +/// +/// # Example +/// ``` +/// use roqoqo::operations::{TripleControlledPauliX, OperateFourQubit}; +/// let cccx = TripleControlledPauliX::new(0, 1, 2, 3); +/// assert_eq!(cccx.control_0(), &0_usize); +/// assert_eq!(cccx.control_1(), &1_usize); +/// assert_eq!(cccx.control_2(), &2_usize); +/// assert_eq!(cccx.target(), &3_usize); +/// ``` +/// +pub trait OperateFourQubit: Operate + InvolveQubits + Substitute + Clone + PartialEq { + /// Returns `target` qubit of four qubit Operation. + fn target(&self) -> &usize; + /// Returns `control_0` qubit of four qubit Operation. + fn control_0(&self) -> &usize; + /// Returns `control_1` qubit of four qubit Operation. + fn control_1(&self) -> &usize; + /// Returns `control_2` qubit of four qubit Operation. + fn control_2(&self) -> &usize; +} + /// Trait for operations acting on multiple (more than two) qubits. /// /// # Example @@ -773,6 +800,45 @@ pub trait OperateThreeQubitGate: fn circuit(&self) -> crate::Circuit; } +/// Trait for all Operations operating on or affecting exactly three qubits. +/// +/// # Example +/// ``` +/// use roqoqo::operations::{CNOT, TripleControlledPauliX, OperateFourQubitGate}; +/// use roqoqo::Circuit; +/// +/// let cccx = TripleControlledPauliX::new(0, 1, 2, 3); +/// let mut circuit = Circuit::new(); +/// circuit += CNOT::new(0, 3); +/// circuit += CNOT::new(0, 1); +/// circuit += CNOT::new(1, 3); +/// circuit += CNOT::new(0, 1); +/// circuit += CNOT::new(1, 3); +/// circuit += CNOT::new(1, 2); +/// circuit += CNOT::new(2, 3); +/// circuit += CNOT::new(0, 2); +/// circuit += CNOT::new(2, 3); +/// circuit += CNOT::new(1, 2); +/// circuit += CNOT::new(2, 3); +/// circuit += CNOT::new(0, 2); +/// circuit += CNOT::new(2, 3); +/// +/// assert_eq!(cccx.circuit(), circuit); +/// ``` +pub trait OperateFourQubitGate: + Operate + + OperateGate + + OperateFourQubit + + InvolveQubits + + Substitute + + Clone + + PartialEq + + SupportedVersion +{ + /// Returns a decomposition of the three-qubit operation using a circuit with two-qubit-operations. + fn circuit(&self) -> crate::Circuit; +} + /// Trait for all Operations operating on or affecting more than two qubits. /// /// # Example diff --git a/roqoqo/src/operations/three_qubit_gate_operations.rs b/roqoqo/src/operations/three_qubit_gate_operations.rs index d030faa53..33310169a 100644 --- a/roqoqo/src/operations/three_qubit_gate_operations.rs +++ b/roqoqo/src/operations/three_qubit_gate_operations.rs @@ -10,7 +10,7 @@ // express or implied. See the License for the specific language governing permissions and // limitations under the License. -use super::{ControlledPhaseShift, Hadamard, CNOT}; +use super::{ControlledPhaseShift, Hadamard, PhaseShiftedControlledPhase, CNOT}; use super::{RotateZ, TGate}; use crate::prelude::*; use crate::Circuit; @@ -18,9 +18,9 @@ use ndarray::{array, Array2}; use num_complex::Complex64; use qoqo_calculator::CalculatorFloat; - #[cfg(feature = "overrotate")] use rand_distr::{Distribution, Normal}; +use std::f64::consts::PI; /// Implements the double-controlled PauliZ gate. /// @@ -484,3 +484,545 @@ impl OperateThreeQubitGate for Toffoli { circuit } } + +/// Implements the controlled SWAP gate. +/// +/// NOTE: for compatibility reasons, the OperateThreeQubit trait is implemented, but +/// the "control" qubit of the operation can be accessed via the "control_0()" method, +/// the "target_0" qubit of the operation can be accessed via the "control_1()" method and +/// the "target_1" qubit of the operation can be accessed via the "target()" method. +#[derive(Debug, Clone, PartialEq, Eq, roqoqo_derive::Operate)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))] +pub struct ControlledSWAP { + /// The index of the most significant qubit in the unitary representation. Here, the controlling qubit of the operation. + control: usize, + /// The index of the second most significant qubit in the unitary representation. Here, the first targeting qubit of the operation. + target_0: usize, + /// The index of the least significant qubit in the unitary representation. Here, the second targeting qubit of the operation. + target_1: usize, +} + +impl super::ImplementedIn1point16 for ControlledSWAP {} + +impl SupportedVersion for ControlledSWAP { + fn minimum_supported_roqoqo_version(&self) -> (u32, u32, u32) { + (1, 16, 0) + } +} + +#[allow(non_upper_case_globals)] +const TAGS_ControlledSWAP: &[&str; 4] = &[ + "Operation", + "GateOperation", + "ThreeQubitGateOperation", + "ControlledSWAP", +]; + +/// Trait for all Operations acting with a unitary gate on a set of qubits. +impl OperateGate for ControlledSWAP { + /// Returns unitary matrix of the gate. + /// + /// # Returns + /// + /// * `Ok(Array2)` - The unitary matrix representation of the gate. + /// * `Err(RoqoqoError)` - The conversion of parameters to f64 failed (here, not possible). + fn unitary_matrix(&self) -> Result, RoqoqoError> { + Ok(array![ + [ + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0) + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0) + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0) + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0) + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0) + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0) + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0) + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0) + ], + ]) + } +} + +/// Trait for all gate operations acting on exactly three qubits. +impl OperateThreeQubitGate for ControlledSWAP { + fn circuit(&self) -> Circuit { + // Based on CNOT(2, 1) -> Toffoli(0, 1, 2) -> CNOT(2, 1) + let mut circuit = Circuit::new(); + circuit += CNOT::new(self.target_1, self.target_0); + circuit += Hadamard::new(self.target_1); + circuit += CNOT::new(self.target_0, self.target_1); + circuit += RotateZ::new(self.target_1, -CalculatorFloat::FRAC_PI_4); + circuit += CNOT::new(self.control, self.target_1); + circuit += TGate::new(self.target_1); + circuit += CNOT::new(self.target_0, self.target_1); + circuit += RotateZ::new(self.target_1, -CalculatorFloat::FRAC_PI_4); + circuit += CNOT::new(self.control, self.target_1); + circuit += TGate::new(self.target_0); + circuit += TGate::new(self.target_1); + circuit += Hadamard::new(self.target_1); + circuit += CNOT::new(self.control, self.target_0); + circuit += TGate::new(self.control); + circuit += RotateZ::new(self.target_0, -CalculatorFloat::FRAC_PI_4); + circuit += CNOT::new(self.control, self.target_0); + circuit += CNOT::new(self.target_1, self.target_0); + circuit + } +} + +impl OperateThreeQubit for ControlledSWAP { + /// Returns `target_1` qubit of the three qubit Operation. + fn target(&self) -> &usize { + &self.target_1 + } + + /// Returns `control` qubit of the three qubit Operation. + fn control_0(&self) -> &usize { + &self.control + } + + /// Returns `target_0` qubit of the three qubit Operation. + fn control_1(&self) -> &usize { + &self.target_0 + } +} + +impl Substitute for ControlledSWAP { + fn substitute_parameters( + &self, + _calculator: &qoqo_calculator::Calculator, + ) -> Result { + Ok(Self::new(self.control, self.target_0, self.target_1)) + } + + fn remap_qubits( + &self, + mapping: &std::collections::HashMap, + ) -> Result { + crate::operations::check_valid_mapping(mapping)?; + Ok(Self::new( + *mapping.get(&self.control).unwrap_or(&self.control), + *mapping.get(&self.target_0).unwrap_or(&self.target_0), + *mapping.get(&self.target_1).unwrap_or(&self.target_1), + )) + } +} + +impl InvolveQubits for ControlledSWAP { + fn involved_qubits(&self) -> InvolvedQubits { + let mut new_hash_set: std::collections::HashSet = std::collections::HashSet::new(); + new_hash_set.insert(self.control); + new_hash_set.insert(self.target_0); + new_hash_set.insert(self.target_1); + InvolvedQubits::Set(new_hash_set) + } +} + +/// Implements the double-controlled phase-shifted PauliZ gate. +/// +#[derive( + Debug, + Clone, + PartialEq, + roqoqo_derive::InvolveQubits, + roqoqo_derive::Operate, + roqoqo_derive::Substitute, + roqoqo_derive::OperateThreeQubit, +)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))] +pub struct PhaseShiftedControlledControlledZ { + /// The index of the most significant qubit in the unitary representation. Here, the first controlling qubit of the operation. + control_0: usize, + /// The index of the second most significant qubit in the unitary representation. Here, the second controlling qubit of the operation. + control_1: usize, + /// The index of the least significant qubit in the unitary representation. Here, the targeting qubit of the operation. + target: usize, + /// The single qubit phase φ. + phi: CalculatorFloat, +} + +impl super::ImplementedIn1point16 for PhaseShiftedControlledControlledZ {} + +impl SupportedVersion for PhaseShiftedControlledControlledZ { + fn minimum_supported_roqoqo_version(&self) -> (u32, u32, u32) { + (1, 16, 0) + } +} + +#[allow(non_upper_case_globals)] +const TAGS_PhaseShiftedControlledControlledZ: &[&str; 4] = &[ + "Operation", + "GateOperation", + "ThreeQubitGateOperation", + "PhaseShiftedControlledControlledZ", +]; + +/// Trait for all Operations acting with a unitary gate on a set of qubits. +impl OperateGate for PhaseShiftedControlledControlledZ { + /// Returns unitary matrix of the gate. + /// + /// # Returns + /// + /// * `Ok(Array2)` - The unitary matrix representation of the gate. + /// * `Err(RoqoqoError)` - The conversion of parameters to f64 failed (here, not possible). + fn unitary_matrix(&self) -> Result, RoqoqoError> { + // exp(i*x) = cos(x)+i*sin(x) + let phi: f64 = f64::try_from(self.phi.clone())?; + let cos: f64 = phi.cos(); + let sin: f64 = phi.sin(); + let cos2: f64 = (2.0 * phi + PI).cos(); + let sin2: f64 = (2.0 * phi + PI).sin(); + Ok(array![ + [ + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0) + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0) + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0) + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0) + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0) + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(cos, sin), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0) + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(cos, sin), + Complex64::new(0.0, 0.0) + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(cos2, sin2) + ], + ]) + } +} + +/// Trait for all gate operations acting on exactly three qubits. +impl OperateThreeQubitGate for PhaseShiftedControlledControlledZ { + fn circuit(&self) -> Circuit { + let mut circuit = Circuit::new(); + circuit += PhaseShiftedControlledPhase::new( + self.control_1, + self.target, + CalculatorFloat::FRAC_PI_2, + self.phi.clone(), + ); + circuit += CNOT::new(self.control_0, self.control_1); + circuit += PhaseShiftedControlledPhase::new( + self.control_1, + self.target, + -CalculatorFloat::FRAC_PI_2, + self.phi.clone(), + ); + circuit += CNOT::new(self.control_0, self.control_1); + circuit += PhaseShiftedControlledPhase::new( + self.control_0, + self.target, + CalculatorFloat::FRAC_PI_2, + self.phi.clone(), + ); + circuit + } +} + +/// Implements the double-controlled phase-shifted PhaseShift gate. +/// +#[derive( + Debug, + Clone, + PartialEq, + roqoqo_derive::InvolveQubits, + roqoqo_derive::Operate, + roqoqo_derive::Substitute, + roqoqo_derive::OperateThreeQubit, + roqoqo_derive::Rotate, +)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))] +pub struct PhaseShiftedControlledControlledPhase { + /// The index of the most significant qubit in the unitary representation. Here, the first controlling qubit of the operation. + control_0: usize, + /// The index of the second most significant qubit in the unitary representation. Here, the second controlling qubit of the operation. + control_1: usize, + /// The index of the least significant qubit in the unitary representation. Here, the targeting qubit of the operation. + target: usize, + /// The rotation angle θ. + theta: CalculatorFloat, + /// The single qubit phase φ. + phi: CalculatorFloat, +} + +impl super::ImplementedIn1point16 for PhaseShiftedControlledControlledPhase {} + +impl SupportedVersion for PhaseShiftedControlledControlledPhase { + fn minimum_supported_roqoqo_version(&self) -> (u32, u32, u32) { + (1, 16, 0) + } +} + +#[allow(non_upper_case_globals)] +const TAGS_PhaseShiftedControlledControlledPhase: &[&str; 4] = &[ + "Operation", + "GateOperation", + "ThreeQubitGateOperation", + "PhaseShiftedControlledControlledPhase", +]; + +/// Trait for all Operations acting with a unitary gate on a set of qubits. +impl OperateGate for PhaseShiftedControlledControlledPhase { + /// Returns unitary matrix of the gate. + /// + /// # Returns + /// + /// * `Ok(Array2)` - The unitary matrix representation of the gate. + /// * `Err(RoqoqoError)` - The conversion of parameters to f64 failed (here, not possible). + fn unitary_matrix(&self) -> Result, RoqoqoError> { + // exp(i*x) = cos(x)+i*sin(x) + let phi: f64 = f64::try_from(self.phi.clone())?; + let theta: f64 = f64::try_from(self.theta.clone())?; + let cos: f64 = phi.cos(); + let sin: f64 = phi.sin(); + let cos2: f64 = (2.0 * phi + theta).cos(); + let sin2: f64 = (2.0 * phi + theta).sin(); + Ok(array![ + [ + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0) + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0) + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0) + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0) + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0) + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(cos, sin), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0) + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(cos, sin), + Complex64::new(0.0, 0.0) + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(cos2, sin2) + ], + ]) + } +} + +/// Trait for all gate operations acting on exactly three qubits. +impl OperateThreeQubitGate for PhaseShiftedControlledControlledPhase { + fn circuit(&self) -> Circuit { + let mut circuit = Circuit::new(); + circuit += PhaseShiftedControlledPhase::new( + self.control_1, + self.target, + self.theta.clone() / 2, + self.phi.clone(), + ); + circuit += CNOT::new(self.control_0, self.control_1); + circuit += PhaseShiftedControlledPhase::new( + self.control_1, + self.target, + -self.theta.clone() / 2, + self.phi.clone(), + ); + circuit += CNOT::new(self.control_0, self.control_1); + circuit += PhaseShiftedControlledPhase::new( + self.control_0, + self.target, + self.theta.clone() / 2, + self.phi.clone(), + ); + circuit + } +} diff --git a/roqoqo/src/prelude.rs b/roqoqo/src/prelude.rs index 4a55294c9..a16e3b141 100644 --- a/roqoqo/src/prelude.rs +++ b/roqoqo/src/prelude.rs @@ -26,11 +26,11 @@ pub use crate::measurements::{Measure, MeasureExpectationValues}; pub use crate::operations::OperateSpinsAnalog; pub use crate::operations::{ Define, InvolveModes, InvolveQubits, InvolvedModes, InvolvedQubits, Operate, - OperateConstantGate, OperateGate, OperateModeGate, OperateMultiQubit, OperateMultiQubitGate, - OperatePragma, OperatePragmaNoise, OperatePragmaNoiseProba, OperateSingleMode, - OperateSingleModeGate, OperateSingleQubit, OperateSingleQubitGate, OperateThreeQubit, - OperateThreeQubitGate, OperateTwoMode, OperateTwoModeGate, OperateTwoQubit, - OperateTwoQubitGate, Rotate, Substitute, SubstituteModes, SupportedVersion, - AVAILABLE_GATES_HQSLANG, + OperateConstantGate, OperateFourQubit, OperateFourQubitGate, OperateGate, OperateModeGate, + OperateMultiQubit, OperateMultiQubitGate, OperatePragma, OperatePragmaNoise, + OperatePragmaNoiseProba, OperateSingleMode, OperateSingleModeGate, OperateSingleQubit, + OperateSingleQubitGate, OperateThreeQubit, OperateThreeQubitGate, OperateTwoMode, + OperateTwoModeGate, OperateTwoQubit, OperateTwoQubitGate, Rotate, Substitute, SubstituteModes, + SupportedVersion, AVAILABLE_GATES_HQSLANG, }; pub use crate::{RoqoqoBackendError, RoqoqoError}; diff --git a/roqoqo/tests/integration/operations/four_qubit_gate_operations.rs b/roqoqo/tests/integration/operations/four_qubit_gate_operations.rs new file mode 100644 index 000000000..ac24e1818 --- /dev/null +++ b/roqoqo/tests/integration/operations/four_qubit_gate_operations.rs @@ -0,0 +1,1282 @@ +// Copyright © 2021-2024 HQS Quantum Simulations GmbH. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under the +// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing permissions and +// limitations under the License. + +//! Integration test for public API of four qubit gate operations + +#[cfg(feature = "json_schema")] +use jsonschema::{Draft, Validator}; +use ndarray::array; +use num_complex::Complex64; +use qoqo_calculator::{Calculator, CalculatorFloat}; +use roqoqo::operations::*; +use roqoqo::Circuit; +#[cfg(feature = "json_schema")] +use schemars::schema_for; +use std::collections::{HashMap, HashSet}; + +#[cfg(feature = "json_schema")] +use test_case::test_case; + +#[test] +fn test_circuit_triple_controlled_x() { + let gate = TripleControlledPauliX::new(0, 1, 2, 3); + let c = gate.circuit(); + + let mut comparison_circuit = Circuit::new(); + comparison_circuit += CNOT::new(0, 3); + comparison_circuit += CNOT::new(0, 1); + comparison_circuit += CNOT::new(1, 3); + comparison_circuit += CNOT::new(0, 1); + comparison_circuit += CNOT::new(1, 3); + comparison_circuit += CNOT::new(1, 2); + comparison_circuit += CNOT::new(2, 3); + comparison_circuit += CNOT::new(0, 2); + comparison_circuit += CNOT::new(2, 3); + comparison_circuit += CNOT::new(1, 2); + comparison_circuit += CNOT::new(2, 3); + comparison_circuit += CNOT::new(0, 2); + comparison_circuit += CNOT::new(2, 3); + + assert!(c == comparison_circuit); +} + +#[test] +fn test_matrix_output_triple_controlled_x() { + let gate = TripleControlledPauliX::new(0, 1, 2, 3); + let test_array = array![ + [ + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + ], + ]; + let unit = gate.unitary_matrix().unwrap(); + let should_be_zero = unit - test_array; + assert!(should_be_zero.iter().all(|x| x.norm() < f64::EPSILON)); +} + +#[test] +fn test_clone_partial_eq_triple_controlled_x() { + let gate = TripleControlledPauliX::new(0, 1, 2, 3); + let gate1 = TripleControlledPauliX::new(1, 2, 3, 4); + let helper = gate != gate1; + assert!(helper); + #[allow(clippy::redundant_clone)] + let gate2 = gate1.clone(); + assert_eq!(gate2, gate1); +} + +#[test] +fn test_operate_triple_controlled_x() { + let gate = TripleControlledPauliX::new(0, 1, 2, 3); + assert_eq!(gate.hqslang(), "TripleControlledPauliX"); + assert_eq!( + gate.tags(), + &[ + "Operation", + "GateOperation", + "FourQubitGateOperation", + "TripleControlledPauliX", + ] + ); + assert!(!gate.is_parametrized()); +} + +#[test] +fn test_substitute_triple_controlled_x() { + let gate = TripleControlledPauliX::new(0, 1, 2, 3); + let gate1 = TripleControlledPauliX::new(1, 2, 3, 0); + let mut substitution_dict: Calculator = Calculator::new(); + substitution_dict.set_variable("theta", 0.0); + let result = gate.substitute_parameters(&substitution_dict).unwrap(); + assert_eq!(result, gate); + + let mut mapping: HashMap = std::collections::HashMap::new(); + let _ = mapping.insert(0, 1); + let _ = mapping.insert(1, 2); + let _ = mapping.insert(2, 3); + let _ = mapping.insert(3, 0); + let remapped = gate1.remap_qubits(&mapping).unwrap(); + let qubits = remapped.involved_qubits(); + assert_eq!(qubits, InvolvedQubits::Set(HashSet::from([1, 2, 3, 0]))); +} + +#[test] +fn test_substitute_error_triple_controlled_x() { + let gate = TripleControlledPauliX::new(0, 1, 2, 3); + let mut mapping: HashMap = std::collections::HashMap::new(); + let _ = mapping.insert(1, 2); + let _ = mapping.insert(2, 3); + let _ = mapping.insert(3, 4); + let _ = mapping.insert(4, 0); + let remapped = gate.remap_qubits(&mapping); + assert!(remapped.is_err()); +} + +#[test] +fn test_format_triple_controlled_x() { + let gate = TripleControlledPauliX::new(0, 1, 2, 3); + let string = format!("{:?}", gate); + assert!(string.contains("TripleControlledPauliX")); + assert!(string.contains("control_0")); + assert!(string.contains("control_1")); + assert!(string.contains("control_2")); + assert!(string.contains("target")); + println!("{:?}", string); +} + +#[test] +fn test_involved_qubits_triple_controlled_x() { + let gate = TripleControlledPauliX::new(0, 1, 2, 3); + 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); + let _ = comp_set.insert(3); + assert_eq!(involved_qubits, InvolvedQubits::Set(comp_set)); +} + +#[test] +fn test_circuit_triple_controlled_z() { + let gate = TripleControlledPauliZ::new(0, 1, 2, 3); + let c = gate.circuit(); + + let mut comparison_circuit = Circuit::new(); + comparison_circuit += ControlledPauliZ::new(0, 3); + comparison_circuit += CNOT::new(0, 1); + comparison_circuit += ControlledPauliZ::new(1, 3); + comparison_circuit += CNOT::new(0, 1); + comparison_circuit += ControlledPauliZ::new(1, 3); + comparison_circuit += CNOT::new(1, 2); + comparison_circuit += ControlledPauliZ::new(2, 3); + comparison_circuit += CNOT::new(0, 2); + comparison_circuit += ControlledPauliZ::new(2, 3); + comparison_circuit += CNOT::new(1, 2); + comparison_circuit += ControlledPauliZ::new(2, 3); + comparison_circuit += CNOT::new(0, 2); + comparison_circuit += ControlledPauliZ::new(2, 3); + + assert!(c == comparison_circuit); +} + +#[test] +fn test_matrix_output_triple_controlled_z() { + let gate = TripleControlledPauliZ::new(0, 1, 2, 3); + let test_array = array![ + [ + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(-1.0, 0.0), + ], + ]; + let unit = gate.unitary_matrix().unwrap(); + let should_be_zero = unit - test_array; + assert!(should_be_zero.iter().all(|x| x.norm() < f64::EPSILON)); +} + +#[test] +fn test_clone_partial_eq_triple_controlled_z() { + let gate = TripleControlledPauliZ::new(0, 1, 2, 3); + let gate1 = TripleControlledPauliZ::new(1, 2, 3, 4); + let helper = gate != gate1; + assert!(helper); + #[allow(clippy::redundant_clone)] + let gate2 = gate1.clone(); + assert_eq!(gate2, gate1); +} + +#[test] +fn test_operate_triple_controlled_z() { + let gate = TripleControlledPauliZ::new(0, 1, 2, 3); + assert_eq!(gate.hqslang(), "TripleControlledPauliZ"); + assert_eq!( + gate.tags(), + &[ + "Operation", + "GateOperation", + "FourQubitGateOperation", + "TripleControlledPauliZ", + ] + ); + assert!(!gate.is_parametrized()); +} + +#[test] +fn test_substitute_triple_controlled_z() { + let gate = TripleControlledPauliZ::new(0, 1, 2, 3); + let gate1 = TripleControlledPauliZ::new(1, 2, 3, 0); + let mut substitution_dict: Calculator = Calculator::new(); + substitution_dict.set_variable("theta", 0.0); + let result = gate.substitute_parameters(&substitution_dict).unwrap(); + assert_eq!(result, gate); + + let mut mapping: HashMap = std::collections::HashMap::new(); + let _ = mapping.insert(0, 1); + let _ = mapping.insert(1, 2); + let _ = mapping.insert(2, 3); + let _ = mapping.insert(3, 0); + let remapped = gate1.remap_qubits(&mapping).unwrap(); + let qubits = remapped.involved_qubits(); + assert_eq!(qubits, InvolvedQubits::Set(HashSet::from([1, 2, 3, 0]))); +} + +#[test] +fn test_substitute_error_triple_controlled_z() { + let gate = TripleControlledPauliX::new(0, 1, 2, 3); + let mut mapping: HashMap = std::collections::HashMap::new(); + let _ = mapping.insert(1, 2); + let _ = mapping.insert(2, 3); + let _ = mapping.insert(3, 4); + let _ = mapping.insert(4, 0); + let remapped = gate.remap_qubits(&mapping); + assert!(remapped.is_err()); +} + +#[test] +fn test_format_triple_controlled_z() { + let gate = TripleControlledPauliZ::new(0, 1, 2, 3); + let string = format!("{:?}", gate); + assert!(string.contains("TripleControlledPauliZ")); + assert!(string.contains("control_0")); + assert!(string.contains("control_1")); + assert!(string.contains("control_2")); + assert!(string.contains("target")); + println!("{:?}", string); +} + +#[test] +fn test_involved_qubits_triple_controlled_z() { + let gate = TripleControlledPauliZ::new(0, 1, 2, 3); + 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); + let _ = comp_set.insert(3); + assert_eq!(involved_qubits, InvolvedQubits::Set(comp_set)); +} + +#[test] +fn test_circuit_triple_controlled_phaseshift() { + let gate = TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::FRAC_PI_2); + let c = gate.circuit(); + + let mut comparison_circuit = Circuit::new(); + comparison_circuit += ControlledPhaseShift::new(0, 3, CalculatorFloat::FRAC_PI_4); + comparison_circuit += CNOT::new(0, 1); + comparison_circuit += ControlledPhaseShift::new(1, 3, -CalculatorFloat::FRAC_PI_4); + comparison_circuit += CNOT::new(0, 1); + comparison_circuit += ControlledPhaseShift::new(1, 3, CalculatorFloat::FRAC_PI_4); + comparison_circuit += CNOT::new(1, 2); + comparison_circuit += ControlledPhaseShift::new(2, 3, -CalculatorFloat::FRAC_PI_4); + comparison_circuit += CNOT::new(0, 2); + comparison_circuit += ControlledPhaseShift::new(2, 3, CalculatorFloat::FRAC_PI_4); + comparison_circuit += CNOT::new(1, 2); + comparison_circuit += ControlledPhaseShift::new(2, 3, -CalculatorFloat::FRAC_PI_4); + comparison_circuit += CNOT::new(0, 2); + comparison_circuit += ControlledPhaseShift::new(2, 3, CalculatorFloat::FRAC_PI_4); + + assert!(c == comparison_circuit); +} + +#[test] +fn test_matrix_output_triple_controlled_phaseshift() { + let gate = TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::FRAC_PI_2); + let test_array = array![ + [ + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(1.0, 0.0), + Complex64::new(0.0, 0.0), + ], + [ + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 0.0), + Complex64::new(0.0, 1.0), + ], + ]; + let unit = gate.unitary_matrix().unwrap(); + let should_be_zero = unit - test_array; + assert!(should_be_zero.iter().all(|x| x.norm() < f64::EPSILON)); +} + +#[test] +fn test_clone_partial_eq_triple_controlled_phaseshift() { + let gate = TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::from(1.0)); + let gate1 = TripleControlledPhaseShift::new(1, 2, 3, 4, CalculatorFloat::from(1.0)); + let helper = gate != gate1; + assert!(helper); + #[allow(clippy::redundant_clone)] + let gate2 = gate1.clone(); + assert_eq!(gate2, gate1); +} + +#[test] +fn test_operate_triple_controlled_phaseshift() { + let gate = TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::from(1.0)); + let gate_p = TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::from("theta")); + assert_eq!(gate.hqslang(), "TripleControlledPhaseShift"); + assert_eq!( + gate.tags(), + &[ + "Operation", + "GateOperation", + "FourQubitGateOperation", + "TripleControlledPhaseShift", + ] + ); + assert!(!gate.is_parametrized()); + assert!(gate_p.is_parametrized()); +} + +#[test] +fn test_substitute_triple_controlled_phaseshift() { + let gate = TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::from(1.0)); + let gate1 = TripleControlledPhaseShift::new(1, 2, 3, 0, CalculatorFloat::from(1.0)); + let mut substitution_dict: Calculator = Calculator::new(); + substitution_dict.set_variable("theta", 0.0); + let result = gate.substitute_parameters(&substitution_dict).unwrap(); + assert_eq!(result, gate); + + let mut mapping: HashMap = std::collections::HashMap::new(); + let _ = mapping.insert(0, 1); + let _ = mapping.insert(1, 2); + let _ = mapping.insert(2, 3); + let _ = mapping.insert(3, 0); + let remapped = gate1.remap_qubits(&mapping).unwrap(); + let qubits = remapped.involved_qubits(); + assert_eq!(qubits, InvolvedQubits::Set(HashSet::from([1, 2, 3, 0]))); +} + +#[test] +fn test_substitute_error_triple_controlled_phaseshift() { + let gate = TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::from(1.0)); + let mut mapping: HashMap = std::collections::HashMap::new(); + let _ = mapping.insert(1, 2); + let _ = mapping.insert(2, 3); + let _ = mapping.insert(3, 4); + let _ = mapping.insert(4, 0); + let remapped = gate.remap_qubits(&mapping); + assert!(remapped.is_err()); +} + +#[test] +fn test_format_triple_controlled_phaseshift() { + let gate = TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::from(1.0)); + let string = format!("{:?}", gate); + assert!(string.contains("TripleControlledPhaseShift")); + assert!(string.contains("control_0")); + assert!(string.contains("control_1")); + assert!(string.contains("control_2")); + assert!(string.contains("target")); + println!("{:?}", string); +} + +#[test] +fn test_involved_qubits_triple_controlled_phaseshift() { + let gate = TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::from(1.0)); + 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); + let _ = comp_set.insert(3); + assert_eq!(involved_qubits, InvolvedQubits::Set(comp_set)); +} + +/// Test JsonSchema trait +#[cfg(feature = "json_schema")] +#[test_case(FourQubitGateOperation::from(TripleControlledPauliX::new(0, 1, 2, 3)); "TripleControlledPauliX")] +#[test_case(FourQubitGateOperation::from(TripleControlledPauliZ::new(0, 1, 2, 3)); "TripleControlledPauliZ")] +#[test_case( + FourQubitGateOperation::from(TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::from(0.0))); + "TripleControlledPhaseShift" +)] +pub fn test_json_schema_three_qubit_gate_operations(gate: FourQubitGateOperation) { + // Serialize + let test_json = match gate.clone() { + FourQubitGateOperation::TripleControlledPauliX(op) => serde_json::to_string(&op).unwrap(), + FourQubitGateOperation::TripleControlledPauliZ(op) => serde_json::to_string(&op).unwrap(), + FourQubitGateOperation::TripleControlledPhaseShift(op) => { + serde_json::to_string(&op).unwrap() + } + _ => unreachable!(), + }; + let test_value: serde_json::Value = serde_json::from_str(&test_json).unwrap(); + + // Create JSONSchema + let test_schema = match gate { + FourQubitGateOperation::TripleControlledPauliX(_) => { + schema_for!(TripleControlledPauliX) + } + FourQubitGateOperation::TripleControlledPauliZ(_) => { + schema_for!(TripleControlledPauliZ) + } + FourQubitGateOperation::TripleControlledPhaseShift(_) => { + schema_for!(TripleControlledPhaseShift) + } + _ => unreachable!(), + }; + let schema = serde_json::to_string(&test_schema).unwrap(); + let schema_value: serde_json::Value = serde_json::from_str(&schema).unwrap(); + let compiled_schema = Validator::options() + .with_draft(Draft::Draft7) + .build(&schema_value) + .unwrap(); + + let validation_result = compiled_schema.validate(&test_value); + assert!(validation_result.is_ok()); +} diff --git a/roqoqo/tests/integration/operations/mod.rs b/roqoqo/tests/integration/operations/mod.rs index 84c49942c..9a0fefd2b 100644 --- a/roqoqo/tests/integration/operations/mod.rs +++ b/roqoqo/tests/integration/operations/mod.rs @@ -22,6 +22,8 @@ mod two_qubit_gate_operations; mod three_qubit_gate_operations; +mod four_qubit_gate_operations; + mod multi_qubit_gate_operations; mod involved_classical; diff --git a/roqoqo/tests/integration/operations/multi_qubit_gate_operations.rs b/roqoqo/tests/integration/operations/multi_qubit_gate_operations.rs index d6c088289..094f05d6e 100644 --- a/roqoqo/tests/integration/operations/multi_qubit_gate_operations.rs +++ b/roqoqo/tests/integration/operations/multi_qubit_gate_operations.rs @@ -60,7 +60,7 @@ fn test_circuit_multi_ms(qubits: Vec) { } #[test_case(vec![0,1]; "two_qubit")] -fn test_matrix_output(qubits: Vec) { +fn test_matrix_output_multi_ms(qubits: Vec) { let gate = MultiQubitMS::new(qubits, CalculatorFloat::FRAC_PI_2); let f: f64 = 1.0 / ((2.0_f64).sqrt()); let test_array = array![ @@ -95,7 +95,7 @@ fn test_matrix_output(qubits: Vec) { } #[test_case(vec![0,1,2]; "three_qubit")] -fn test_matrix_output_three(qubits: Vec) { +fn test_matrix_output_three_multi_ms(qubits: Vec) { let gate = MultiQubitMS::new(qubits, CalculatorFloat::FRAC_PI_2); let f: f64 = 1.0 / ((2.0_f64).sqrt()); let test_array = array![ @@ -186,7 +186,7 @@ fn test_matrix_output_three(qubits: Vec) { } #[test] -fn test_clone_partial_eq() { +fn test_clone_partial_eq_multi_ms() { let qubits = vec![0, 1, 2]; let gate = MultiQubitMS::new(qubits.clone(), CalculatorFloat::FRAC_PI_2); @@ -211,7 +211,7 @@ fn test_clone_partial_eq() { } #[test] -fn test_operate() { +fn test_operate_multi_ms() { let qubits = vec![0, 1, 2]; let gate = MultiQubitMS::new(qubits.clone(), CalculatorFloat::FRAC_PI_2); assert_eq!(gate.hqslang(), "MultiQubitMS"); @@ -231,7 +231,7 @@ fn test_operate() { } #[test] -fn test_substitute() { +fn test_substitute_multi_ms() { let qubits = vec![0, 1, 2]; let gate1 = MultiQubitMS::new(qubits.clone(), "theta".into()); let gate = MultiQubitMS::new(qubits, CalculatorFloat::FRAC_PI_2); @@ -250,7 +250,7 @@ fn test_substitute() { } #[test] -fn test_substitute_error() { +fn test_substitute_error_multi_ms() { let qubits = vec![0, 1, 2]; let gate1 = MultiQubitMS::new(qubits, "theta".into()); let calc = Calculator::new(); @@ -264,7 +264,7 @@ fn test_substitute_error() { } #[test] -fn test_format() { +fn test_format_multi_ms() { let qubits = vec![0, 1, 2]; let gate = MultiQubitMS::new(qubits, "theta".into()); let string = format!("{:?}", gate); @@ -273,7 +273,7 @@ fn test_format() { } #[test] -fn test_involved_qubits() { +fn test_involved_qubits_multi_ms() { let qubits = vec![0, 1, 2]; let gate = MultiQubitMS::new(qubits, "theta".into()); let involved_qubits = gate.involved_qubits(); @@ -291,7 +291,7 @@ fn test_involved_qubits() { #[test_case(CalculatorFloat::from("theta"), CalculatorFloat::from(0.0); "power_0")] #[test_case(CalculatorFloat::from("theta"), CalculatorFloat::from(-2.0); "power_-2.0")] #[test_case(CalculatorFloat::from("theta"), CalculatorFloat::from("power"); "power_symbolic")] -fn test_rotatex_powercf(theta: CalculatorFloat, power: CalculatorFloat) { +fn test_rotatex_powercf_multi_ms(theta: CalculatorFloat, power: CalculatorFloat) { let qubits = vec![0, 1, 2]; let gate = MultiQubitMS::new(qubits.clone(), theta); diff --git a/roqoqo/tests/integration/operations/supported_version.rs b/roqoqo/tests/integration/operations/supported_version.rs index ae48a1de5..215634417 100644 --- a/roqoqo/tests/integration/operations/supported_version.rs +++ b/roqoqo/tests/integration/operations/supported_version.rs @@ -13,7 +13,6 @@ //! Integration test for supported version trait use ndarray::array; -#[cfg(feature = "unstable_operation_definition")] use qoqo_calculator::CalculatorFloat; #[cfg(feature = "circuitdag")] use roqoqo::measurements::Cheated; @@ -372,3 +371,23 @@ fn test_version_1_15_0_single_qubit_gate(operation: operations::SingleQubitGateO fn test_version_1_16_0_single_qubit_gate(operation: operations::SingleQubitGateOperation) { assert_eq!(operation.minimum_supported_roqoqo_version(), (1, 16, 0)); } + +#[test_case(operations::ThreeQubitGateOperation::from(operations::ControlledSWAP::new(0, 1, 2)); "ControlledSWAP")] +#[test_case(operations::ThreeQubitGateOperation::from(operations::PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::PI)); "PhaseShiftedControlledControlledZ")] +#[test_case(operations::ThreeQubitGateOperation::from(operations::PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::PI, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] +fn test_version_1_16_0_three_qubit_gate(operation: operations::ThreeQubitGateOperation) { + assert_eq!(operation.minimum_supported_roqoqo_version(), (1, 16, 0)); +} + +#[test_case( + operations::FourQubitGateOperation::from(operations::TripleControlledPauliX::new(0, 1, 2, 3)); "TripleControlledPauliX" +)] +#[test_case( + operations::FourQubitGateOperation::from(operations::TripleControlledPauliZ::new(0, 1, 2, 3)); "TripleControlledPauliZ" +)] +#[test_case( + operations::FourQubitGateOperation::from(operations::TripleControlledPhaseShift::new(0, 1, 2, 3, CalculatorFloat::PI)); "TripleControlledPhaseShift" +)] +fn test_version_1_16_0_four_qubit_gate(operation: operations::FourQubitGateOperation) { + assert_eq!(operation.minimum_supported_roqoqo_version(), (1, 16, 0)); +} diff --git a/roqoqo/tests/integration/operations/three_qubit_gate_operations.rs b/roqoqo/tests/integration/operations/three_qubit_gate_operations.rs index ed4ba607a..45911b284 100644 --- a/roqoqo/tests/integration/operations/three_qubit_gate_operations.rs +++ b/roqoqo/tests/integration/operations/three_qubit_gate_operations.rs @@ -95,6 +95,87 @@ fn test_circuit_toffoli() { assert_eq!(c, circuit); } +#[test] +fn test_circuit_controlledswap() { + let op = ControlledSWAP::new(0, 1, 2); + let c = op.circuit(); + + let mut circuit = Circuit::new(); + circuit += CNOT::new(2, 1); + circuit += Hadamard::new(2); + circuit += CNOT::new(1, 2); + circuit += RotateZ::new(2, -CalculatorFloat::FRAC_PI_4); + circuit += CNOT::new(0, 2); + circuit += TGate::new(2); + circuit += CNOT::new(1, 2); + circuit += RotateZ::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 += RotateZ::new(1, -CalculatorFloat::FRAC_PI_4); + circuit += CNOT::new(0, 1); + circuit += CNOT::new(2, 1); + + assert_eq!(c, circuit); +} + +#[test] +fn test_circuit_phaseshiftedcontrolledcontrolledz() { + let op = PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::FRAC_PI_2); + let c = op.circuit(); + + let mut circuit = Circuit::new(); + circuit += PhaseShiftedControlledPhase::new( + 1, + 2, + CalculatorFloat::FRAC_PI_2, + CalculatorFloat::FRAC_PI_2, + ); + circuit += CNOT::new(0, 1); + circuit += PhaseShiftedControlledPhase::new( + 1, + 2, + -CalculatorFloat::FRAC_PI_2, + CalculatorFloat::FRAC_PI_2, + ); + circuit += CNOT::new(0, 1); + circuit += PhaseShiftedControlledPhase::new( + 0, + 2, + CalculatorFloat::FRAC_PI_2, + CalculatorFloat::FRAC_PI_2, + ); + + assert_eq!(c, circuit); +} + +#[test] +fn test_circuit_phaseshiftedcontrolledcontrolledphase() { + let op = PhaseShiftedControlledControlledPhase::new( + 0, + 1, + 2, + CalculatorFloat::FRAC_PI_2, + CalculatorFloat::PI, + ); + let c = op.circuit(); + + let mut circuit = Circuit::new(); + circuit += + PhaseShiftedControlledPhase::new(1, 2, CalculatorFloat::FRAC_PI_4, CalculatorFloat::PI); + circuit += CNOT::new(0, 1); + circuit += + PhaseShiftedControlledPhase::new(1, 2, -CalculatorFloat::FRAC_PI_4, CalculatorFloat::PI); + circuit += CNOT::new(0, 1); + circuit += + PhaseShiftedControlledPhase::new(0, 2, CalculatorFloat::FRAC_PI_4, CalculatorFloat::PI); + + assert_eq!(c, circuit); +} + // // Test Unitary Matrix for ThreeQubit Gates // @@ -103,6 +184,9 @@ fn test_circuit_toffoli() { #[test_case(GateOperation::from(ControlledControlledPauliZ::new(0, 1, 2)); "ControlledControlledPauliZ")] #[test_case(GateOperation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from(0.2))); "ControlledControlledPhaseShift")] #[test_case(GateOperation::from(Toffoli::new(0, 1, 2)); "Toffoli")] +#[test_case(GateOperation::from(ControlledSWAP::new(0, 1, 2)); "ControlledSwap")] +#[test_case(GateOperation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::FRAC_PI_2)); "PhaseShiftedControlledControlledZ")] +#[test_case(GateOperation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::FRAC_PI_2, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] fn test_three_qubit_gate_unitarity(gate: GateOperation) { let result: Result, RoqoqoError> = gate.unitary_matrix(); let result_array: Array2 = result.unwrap(); @@ -126,7 +210,10 @@ fn test_three_qubit_gate_unitarity(gate: GateOperation) { #[test_case(Operation::from(ControlledControlledPauliZ::new(0, 1, 2)); "ControlledControlledPauliZ")] #[test_case(Operation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from(0.2))); "ControlledControlledPhaseShift")] #[test_case(Operation::from(Toffoli::new(0, 1, 2)); "Toffoli")] -fn test_twoqubitgates_clone(gate1: Operation) { +#[test_case(Operation::from(ControlledSWAP::new(0, 1, 2)); "ControlledSwap")] +#[test_case(Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::FRAC_PI_2)); "PhaseShiftedControlledControlledZ")] +#[test_case(Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::FRAC_PI_2, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] +fn test_threequbitgates_clone(gate1: Operation) { #[allow(clippy::redundant_clone)] let gate2 = gate1.clone(); assert_eq!(gate2, gate1); @@ -135,6 +222,9 @@ fn test_twoqubitgates_clone(gate1: Operation) { #[test_case(ThreeQubitGateOperation::from(ControlledControlledPauliZ::new(0, 1, 2)); "ControlledControlledPauliZ")] #[test_case(ThreeQubitGateOperation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from(0.2))); "ControlledControlledPhaseShift")] #[test_case(ThreeQubitGateOperation::from(Toffoli::new(0, 1, 2)); "Toffoli")] +#[test_case(ThreeQubitGateOperation::from(ControlledSWAP::new(0, 1, 2)); "ControlledSwap")] +#[test_case(ThreeQubitGateOperation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::FRAC_PI_2)); "PhaseShiftedControlledControlledZ")] +#[test_case(ThreeQubitGateOperation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::FRAC_PI_2, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] fn test_qubits_threequbitgates(gate: ThreeQubitGateOperation) { let control_0: &usize = gate.control_0(); assert_eq!(control_0, &0); @@ -153,12 +243,17 @@ fn test_qubits_threequbitgates(gate: ThreeQubitGateOperation) { #[test_case(Operation::from(ControlledControlledPauliZ::new(0, 1, 2)); "ControlledControlledPauliZ")] #[test_case(Operation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from(0.2))); "ControlledControlledPhaseShift")] #[test_case(Operation::from(Toffoli::new(0, 1, 2)); "Toffoli")] +#[test_case(Operation::from(ControlledSWAP::new(0, 1, 2)); "ControlledSwap")] +#[test_case(Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::FRAC_PI_2)); "PhaseShiftedControlledControlledZ")] +#[test_case(Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::FRAC_PI_2, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] fn test_is_parametrized_false(gate: Operation) { let bool_parameter = gate.is_parametrized(); assert!(!bool_parameter); } #[test_case(Operation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from("x"))); "ControlledControlledPhaseShift")] +#[test_case(Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::from("x"))); "PhaseShiftedControlledControlledZ")] +#[test_case(Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::from("y"), CalculatorFloat::from("z"))); "PhaseShiftedControlledControlledPhase")] fn test_is_parametrized_true(gate: Operation) { let bool_parameter = gate.is_parametrized(); assert!(bool_parameter); @@ -167,6 +262,9 @@ fn test_is_parametrized_true(gate: Operation) { #[test_case("ControlledControlledPauliZ", Operation::from(ControlledControlledPauliZ::new(0, 1, 2)); "ControlledControlledPauliZ")] #[test_case("ControlledControlledPhaseShift", Operation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from(0.2))); "ControlledControlledPhaseShift")] #[test_case("Toffoli", Operation::from(Toffoli::new(0, 1, 2)); "Toffoli")] +#[test_case("ControlledSWAP", Operation::from(ControlledSWAP::new(0, 1, 2)); "ControlledSwap")] +#[test_case("PhaseShiftedControlledControlledZ", Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::FRAC_PI_2)); "PhaseShiftedControlledControlledZ")] +#[test_case("PhaseShiftedControlledControlledPhase", Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::FRAC_PI_2, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] fn test_threequbitgateoperations_hqslang(name: &'static str, gate: Operation) { assert!(!gate.hqslang().is_empty()); assert_eq!(gate.hqslang(), name); @@ -181,6 +279,15 @@ fn test_threequbitgateoperations_hqslang(name: &'static str, gate: Operation) { #[test_case( GateOperation::from(Toffoli::new(0, 1, 2)), GateOperation::from(Toffoli::new(1, 2, 0)); "Toffoli")] +#[test_case( + GateOperation::from(ControlledSWAP::new(0, 1, 2)), + GateOperation::from(ControlledSWAP::new(1, 2, 0)); "ControlledSWAP")] +#[test_case( + GateOperation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::FRAC_PI_2)), + GateOperation::from(PhaseShiftedControlledControlledZ::new(1, 2, 0, CalculatorFloat::FRAC_PI_2)); "PhaseShiftedControlledControlledZ")] +#[test_case( + GateOperation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::FRAC_PI_2, CalculatorFloat::PI)), + GateOperation::from(PhaseShiftedControlledControlledPhase::new(1, 2, 0, CalculatorFloat::FRAC_PI_2, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] fn remap_qubits_result(gate: GateOperation, test_gate: GateOperation) { let mut qubit_mapping: HashMap = HashMap::new(); qubit_mapping.insert(0, 1); @@ -193,6 +300,9 @@ fn remap_qubits_result(gate: GateOperation, test_gate: GateOperation) { #[test_case(GateOperation::from(ControlledControlledPauliZ::new(0, 1, 2)); "ControlledControlledPauliZ")] #[test_case(GateOperation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from(0.2))); "ControlledControlledPhaseShift")] #[test_case(GateOperation::from(Toffoli::new(0, 1, 2)); "Toffoli")] +#[test_case(GateOperation::from(ControlledSWAP::new(0, 1, 2)); "ControlledSwap")] +#[test_case(GateOperation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::FRAC_PI_2)); "PhaseShiftedControlledControlledZ")] +#[test_case(GateOperation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::FRAC_PI_2, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] fn remap_qubits_error0(gate: GateOperation) { let mut qubit_mapping: HashMap = HashMap::new(); qubit_mapping.insert(1, 0); @@ -203,6 +313,9 @@ fn remap_qubits_error0(gate: GateOperation) { #[test_case(GateOperation::from(ControlledControlledPauliZ::new(0, 1, 2)); "ControlledControlledPauliZ")] #[test_case(GateOperation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from(0.2))); "ControlledControlledPhaseShift")] #[test_case(GateOperation::from(Toffoli::new(0, 1, 2)); "Toffoli")] +#[test_case(GateOperation::from(ControlledSWAP::new(0, 1, 2)); "ControlledSwap")] +#[test_case(GateOperation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::FRAC_PI_2)); "PhaseShiftedControlledControlledZ")] +#[test_case(GateOperation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::FRAC_PI_2, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] fn remap_qubits_error1(gate: GateOperation) { let mut qubit_mapping: HashMap = HashMap::new(); qubit_mapping.insert(0, 2); @@ -235,6 +348,30 @@ fn remap_qubits_error1(gate: GateOperation) { "Toffoli", ], Operation::from(Toffoli::new(0, 1, 2)); "Toffoli")] +#[test_case( + vec![ + "Operation", + "GateOperation", + "ThreeQubitGateOperation", + "ControlledSWAP", + ], + Operation::from(ControlledSWAP::new(0, 1, 2)); "ControlledSWAP")] +#[test_case( + vec![ + "Operation", + "GateOperation", + "ThreeQubitGateOperation", + "PhaseShiftedControlledControlledZ", + ], + Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::FRAC_PI_2)); "PhaseShiftedControlledControlledZ")] +#[test_case( + vec![ + "Operation", + "GateOperation", + "ThreeQubitGateOperation", + "PhaseShiftedControlledControlledPhase", + ], + Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::FRAC_PI_2, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] pub fn test_tags(tags: Vec<&str>, gate: Operation) { let range = 0..tags.len(); for i in range { @@ -251,6 +388,15 @@ pub fn test_tags(tags: Vec<&str>, gate: Operation) { #[test_case( "Toffoli(Toffoli { control_0: 1, control_1: 0, target: 2 })", Operation::from(Toffoli::new(1, 0, 2)); "Toffoli")] +#[test_case( + "ControlledSWAP(ControlledSWAP { control: 1, target_0: 0, target_1: 2 })", + Operation::from(ControlledSWAP::new(1, 0, 2)); "ControlledSWAP")] +#[test_case( + "PhaseShiftedControlledControlledZ(PhaseShiftedControlledControlledZ { control_0: 1, control_1: 0, target: 2, phi: Float(3.141592653589793) })", + Operation::from(PhaseShiftedControlledControlledZ::new(1, 0, 2, CalculatorFloat::PI)); "PhaseShiftedControlledControlledZ")] +#[test_case( + "PhaseShiftedControlledControlledPhase(PhaseShiftedControlledControlledPhase { control_0: 1, control_1: 0, target: 2, theta: Float(3.141592653589793), phi: Float(1.5707963267948966) })", + Operation::from(PhaseShiftedControlledControlledPhase::new(1, 0, 2, CalculatorFloat::PI, CalculatorFloat::FRAC_PI_2)); "PhaseShiftedControlledControlledPhase")] fn test_three_qubitgates_debug(message: &'static str, gate: Operation) { assert_eq!(format!("{:?}", gate), message); } @@ -264,6 +410,15 @@ fn test_three_qubitgates_debug(message: &'static str, gate: Operation) { #[test_case( Operation::from(Toffoli::new(0, 1, 2)), Operation::from(Toffoli::new(1, 0, 2)); "Toffoli")] +#[test_case( + Operation::from(ControlledSWAP::new(0, 1, 2)), + Operation::from(ControlledSWAP::new(1, 2, 0)); "ControlledSWAP")] +#[test_case( + Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::PI)), + Operation::from(PhaseShiftedControlledControlledZ::new(1, 2, 0, CalculatorFloat::PI)); "PhaseShiftedControlledControlledZ")] +#[test_case( + Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::PI, CalculatorFloat::PI)), + Operation::from(PhaseShiftedControlledControlledPhase::new(1, 2, 0, CalculatorFloat::PI, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] fn test_threequbitgates_partialeq(gate1: Operation, gate2: Operation) { assert!(gate1 == gate1.clone()); assert_eq!(gate1, gate1.clone()); @@ -274,6 +429,9 @@ fn test_threequbitgates_partialeq(gate1: Operation, gate2: Operation) { #[test_case( Rotation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::PI)), Rotation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::PI * 1.5)); "ControlledControlledPhaseShift")] +#[test_case( + Rotation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::FRAC_PI_2, CalculatorFloat::PI)), + Rotation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::FRAC_PI_2 * 1.5, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] fn test_rotate_powercf(gate: Rotation, gate2: Rotation) { let power_gate = gate.powercf(CalculatorFloat::from(1.5)); assert_eq!(power_gate, gate2); @@ -283,6 +441,9 @@ fn test_rotate_powercf(gate: Rotation, gate2: Rotation) { #[test_case(Operation::from(ControlledControlledPauliZ::new(0, 1, 2)); "ControlledControlledPauliZ")] #[test_case(Operation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from(0.2))); "ControlledControlledPhaseShift")] #[test_case(Operation::from(Toffoli::new(0, 1, 2)); "Toffoli")] +#[test_case(Operation::from(ControlledSWAP::new(0, 1, 2)); "ControlledSwap")] +#[test_case(Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::FRAC_PI_2)); "PhaseShiftedControlledControlledZ")] +#[test_case(Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::FRAC_PI_2, CalculatorFloat::PI)); "PhaseShiftedControlledControlledPhase")] fn test_ineffective_substitute_parameters(gate: Operation) { let mut substitution_dict: Calculator = Calculator::new(); substitution_dict.set_variable("theta", 0.0); @@ -293,6 +454,12 @@ fn test_ineffective_substitute_parameters(gate: Operation) { #[test_case( Operation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from("theta"))), Operation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::ZERO)); "ControlledControlledPhaseShift")] +#[test_case( + Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::from("theta"))), + Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::ZERO)); "PhaseShiftedControlledControlledZ")] +#[test_case( + Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::from("theta"), CalculatorFloat::from("theta"))), + Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::ZERO, CalculatorFloat::ZERO)); "PhaseShiftedControlledControlledPhase")] fn test_substitute_parameters(gate: Operation, gate2: Operation) { let mut substitution_dict: Calculator = Calculator::new(); substitution_dict.set_variable("theta", 0.0); @@ -302,6 +469,10 @@ fn test_substitute_parameters(gate: Operation, gate2: Operation) { #[test_case( Operation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from("theta"))); "ControlledControlledPhaseShift")] +#[test_case( + Operation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::from("theta"))); "PhaseShiftedControlledControlledZ")] +#[test_case( + Operation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::from("theta"), CalculatorFloat::from("theta"))); "PhaseShiftedControlledControlledPhase")] fn test_substitute_parameters_error(gate: Operation) { let mut substitution_dict: Calculator = Calculator::new(); substitution_dict.set_variable("error", 0.0); @@ -334,11 +505,46 @@ fn test_inputs_toffoli() { assert_eq!(gate.target(), &2); } +#[test] +fn test_inputs_cswap() { + let gate = ControlledSWAP::new(0, 1, 2); + assert_eq!(gate.control_0(), &0); + assert_eq!(gate.control_1(), &1); + assert_eq!(gate.target(), &2); +} + +#[test] +fn test_inputs_phaseshiftedccz() { + let gate = PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::PI); + assert_eq!(gate.control_0(), &0); + assert_eq!(gate.control_1(), &1); + assert_eq!(gate.target(), &2); +} + +#[test] +fn test_inputs_phaseshiftedccps() { + let gate = PhaseShiftedControlledControlledPhase::new( + 0, + 1, + 2, + CalculatorFloat::PI, + CalculatorFloat::PI, + ); + assert_eq!(gate.control_0(), &0); + assert_eq!(gate.control_1(), &1); + assert_eq!(gate.target(), &2); +} + /// Test JsonSchema trait #[cfg(feature = "json_schema")] #[test_case(ThreeQubitGateOperation::from(ControlledControlledPauliZ::new(0, 1, 2)); "ControlledControlledPauliZ")] #[test_case(ThreeQubitGateOperation::from(ControlledControlledPhaseShift::new(0, 1, 2, CalculatorFloat::from(0.2))); "ControlledControlledPhaseShift")] #[test_case(ThreeQubitGateOperation::from(Toffoli::new(0, 1, 2)); "Toffoli")] +#[test_case(ThreeQubitGateOperation::from(ControlledSWAP::new(0, 1, 2)); "ControlledSWAP")] +#[test_case( + ThreeQubitGateOperation::from(PhaseShiftedControlledControlledZ::new(0, 1, 2, CalculatorFloat::from("theta"))); "PhaseShiftedControlledControlledZ")] +#[test_case( + ThreeQubitGateOperation::from(PhaseShiftedControlledControlledPhase::new(0, 1, 2, CalculatorFloat::from("theta"), CalculatorFloat::from("theta"))); "PhaseShiftedControlledControlledPhase")] pub fn test_json_schema_three_qubit_gate_operations(gate: ThreeQubitGateOperation) { // Serialize let test_json = match gate.clone() { @@ -349,6 +555,13 @@ pub fn test_json_schema_three_qubit_gate_operations(gate: ThreeQubitGateOperatio serde_json::to_string(&op).unwrap() } ThreeQubitGateOperation::Toffoli(op) => serde_json::to_string(&op).unwrap(), + ThreeQubitGateOperation::ControlledSWAP(op) => serde_json::to_string(&op).unwrap(), + ThreeQubitGateOperation::PhaseShiftedControlledControlledZ(op) => { + serde_json::to_string(&op).unwrap() + } + ThreeQubitGateOperation::PhaseShiftedControlledControlledPhase(op) => { + serde_json::to_string(&op).unwrap() + } _ => unreachable!(), }; let test_value: serde_json::Value = serde_json::from_str(&test_json).unwrap(); @@ -362,6 +575,13 @@ pub fn test_json_schema_three_qubit_gate_operations(gate: ThreeQubitGateOperatio schema_for!(ControlledControlledPhaseShift) } ThreeQubitGateOperation::Toffoli(_) => schema_for!(Toffoli), + ThreeQubitGateOperation::ControlledSWAP(_) => schema_for!(ControlledSWAP), + ThreeQubitGateOperation::PhaseShiftedControlledControlledZ(_) => { + schema_for!(PhaseShiftedControlledControlledZ) + } + ThreeQubitGateOperation::PhaseShiftedControlledControlledPhase(_) => { + schema_for!(PhaseShiftedControlledControlledPhase) + } _ => unreachable!(), }; let schema = serde_json::to_string(&test_schema).unwrap();