From 9f49257148263530c1385483ceeb5b9a86fd4e20 Mon Sep 17 00:00:00 2001 From: rafaella-vale <26910380+rafaella-vale@users.noreply.github.com> Date: Wed, 1 Mar 2023 01:03:42 -0300 Subject: [PATCH 01/42] efficient multicontrolled su2 gate decomposition Co-authored-by: thiagom123 Co-authored-by: IsmaelCesar Co-authored-by: Israel F. Araujo Co-authored-by: Adenilton Silva <7927558+adjs@users.noreply.github.com> --- .../standard_gates/multi_control_su2.py | 265 ++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 qiskit/circuit/library/standard_gates/multi_control_su2.py diff --git a/qiskit/circuit/library/standard_gates/multi_control_su2.py b/qiskit/circuit/library/standard_gates/multi_control_su2.py new file mode 100644 index 000000000000..1b440a5d1450 --- /dev/null +++ b/qiskit/circuit/library/standard_gates/multi_control_su2.py @@ -0,0 +1,265 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Multi-controlled SU(2) gate. +""" + +from typing import Union, List +from cmath import isclose +import numpy as np +from qiskit import QuantumCircuit, QuantumRegister +from qiskit.circuit.library.standard_gates.x import MCXVChain +from qiskit.circuit import Gate, Qubit + + +def _check_su2(matrix): + return isclose(np.linalg.det(matrix), 1.0) + + +class MCSU2Gate(Gate): + r""" + Linear-depth multi-controlled gate for special unitary single-qubit gates. + + This decomposition for SU(2) gates with multiple controls does not use auxiliary qubits. + An n-qubit gate implemented with this method will have at most 20n - 38 CNOTs if + the number of qubits is odd, or 20n - 42 CNOTs if the number of qubits is even. + """ + + def __init__( + self, + su2_matrix, + num_ctrl_qubits, + ctrl_state: str=None + ): + _check_su2(su2_matrix) + + self.su2_matrix = su2_matrix + self.num_ctrl_qubits = num_ctrl_qubits + self.ctrl_state = ctrl_state + + super().__init__("mcsu2", self.num_ctrl_qubits + 1, [], "mcsu2") + + + def _define(self): + controls = QuantumRegister(self.num_ctrl_qubits) + target = QuantumRegister(1) + self.definition = QuantumCircuit(controls, target) + + is_main_diag_real = isclose(self.su2_matrix[0, 0].imag, 0.0) and \ + isclose(self.su2_matrix[1, 1].imag, 0.0) + is_secondary_diag_real = isclose(self.su2_matrix[0,1].imag, 0.0) and \ + isclose(self.su2_matrix[1,0].imag, 0.0) + + if not is_main_diag_real and not is_secondary_diag_real: + # U = V D V^-1, where the entries of the diagonal D are the eigenvalues + # `eig_vals` of U and the column vectors of V are the eigenvectors + # `eig_vecs` of U. These columns are orthonormal and the main diagonal + # of V is real-valued. + eig_vals, eig_vecs = np.linalg.eig(self.su2_matrix) + + x_vecs, z_vecs = self._get_x_z(eig_vecs) + x_vals, z_vals = self._get_x_z(np.diag(eig_vals)) + + self.half_linear_depth_mcv( + x_vecs, z_vecs, controls, target, self.ctrl_state, inverse=True + ) + self.linear_depth_mcv( + x_vals, + z_vals, + controls, + target, + self.ctrl_state, + general_su2_optimization=True + ) + self.half_linear_depth_mcv(x_vecs, z_vecs, controls, target, self.ctrl_state) + + else: + x, z = self._get_x_z(self.su2_matrix) + + if not is_secondary_diag_real: + self.definition.h(target) + + self.linear_depth_mcv(x, z, controls, target, self.ctrl_state) + + if not is_secondary_diag_real: + self.definition.h(target) + + + @staticmethod + def _get_x_z(su2): + is_secondary_diag_real = isclose(su2[0,1].imag, 0.0) and isclose(su2[1,0].imag, 0.0) + + if is_secondary_diag_real: + x = su2[0,1] + z = su2[1,1] + else: + x = -su2[0,1].real + z = su2[1,1] - su2[0,1].imag * 1.0j + + return x, z + + + def linear_depth_mcv( + self, + x, + z, + controls: Union[QuantumRegister, List[Qubit]], + target: Qubit, + ctrl_state: str=None, + general_su2_optimization=False + ): + alpha_r = np.sqrt( + (np.sqrt((z.real + 1.) / 2.) + 1.) / 2. + ) + alpha_i = z.imag / (2. * np.sqrt( + (z.real + 1.) * (np.sqrt((z.real + 1.) / 2.) + 1.) + ) + ) + alpha = alpha_r + 1.j * alpha_i + beta = x / (2. * np.sqrt( + (z.real + 1.) * (np.sqrt((z.real + 1.) / 2.) + 1.) + ) + ) + + s_op = np.array([ + [alpha, -np.conj(beta)], + [beta, np.conj(alpha)] + ]) + + # S gate definition + s_gate = QuantumCircuit(1) + s_gate.unitary(s_op, 0) + + num_ctrl = len(controls) + k_1 = int(np.ceil(num_ctrl / 2.)) + k_2 = int(np.floor(num_ctrl / 2.)) + + ctrl_state_k_1 = None + ctrl_state_k_2 = None + + if ctrl_state is not None: + ctrl_state_k_1 = ctrl_state[::-1][:k_1][::-1] + ctrl_state_k_2 = ctrl_state[::-1][k_1:][::-1] + + if not general_su2_optimization: + mcx_1 = MCXVChain( + num_ctrl_qubits=k_1, + dirty_ancillas=True, + ctrl_state=ctrl_state_k_1 + ).definition + self.definition.append(mcx_1, controls[:k_1] + [target] + controls[k_1:2*k_1 - 2]) + self.definition.append(s_gate, [target]) + + mcx_2 = MCXVChain( + num_ctrl_qubits=k_2, + dirty_ancillas=True, + ctrl_state=ctrl_state_k_2, + action_only=general_su2_optimization + ).definition + self.definition.append( + mcx_2.inverse(), + controls[k_1:] + [target] + controls[k_1 - k_2 + 2:k_1] + ) + self.definition.append(s_gate.inverse(), [target]) + + mcx_3 = MCXVChain( + num_ctrl_qubits=k_1, + dirty_ancillas=True, + ctrl_state=ctrl_state_k_1 + ).definition + self.definition.append(mcx_3, controls[:k_1] + [target] + controls[k_1:2*k_1 - 2]) + self.definition.append(s_gate, [target]) + + mcx_4 = MCXVChain( + num_ctrl_qubits=k_2, + dirty_ancillas=True, + ctrl_state=ctrl_state_k_2 + ).definition + self.definition.append(mcx_4, controls[k_1:] + [target] + controls[k_1 - k_2 + 2:k_1]) + self.definition.append(s_gate.inverse(), [target]) + + + def half_linear_depth_mcv( + self, + x, + z, + controls: Union[QuantumRegister, List[Qubit]], + target: Qubit, + ctrl_state: str=None, + inverse: bool=False + ): + alpha_r = np.sqrt((z.real + 1.) / 2.) + alpha_i = z.imag / np.sqrt(2*(z.real + 1.)) + alpha = alpha_r + 1.j * alpha_i + + beta = x / np.sqrt(2*(z.real + 1.)) + + s_op = np.array([ + [alpha, -np.conj(beta)], + [beta, np.conj(alpha)] + ]) + + # S gate definition + s_gate = QuantumCircuit(1) + s_gate.unitary(s_op, 0) + + # Hadamard equivalent definition + h_gate = QuantumCircuit(1) + h_gate.unitary(np.array([[-1, 1], [1, 1]]) * 1/np.sqrt(2), 0) + + num_ctrl = len(controls) + k_1 = int(np.ceil(num_ctrl / 2.)) + k_2 = int(np.floor(num_ctrl / 2.)) + + ctrl_state_k_1 = None + ctrl_state_k_2 = None + + if ctrl_state is not None: + ctrl_state_k_1 = ctrl_state[::-1][:k_1][::-1] + ctrl_state_k_2 = ctrl_state[::-1][k_1:][::-1] + + if inverse: + self.definition.h(target) + + self.definition.append(s_gate, [target]) + mcx_2 = MCXVChain( + num_ctrl_qubits=k_2, + dirty_ancillas=True, + ctrl_state=ctrl_state_k_2, + action_only=True + ).definition + self.definition.append(mcx_2, controls[k_1:] + [target] + controls[k_1 - k_2 + 2:k_1]) + + self.definition.append(s_gate.inverse(), [target]) + + self.definition.append(h_gate, [target]) + + else: + mcx_1 = MCXVChain( + num_ctrl_qubits=k_1, + dirty_ancillas=True, + ctrl_state=ctrl_state_k_1 + ).definition + self.definition.append(mcx_1, controls[:k_1] + [target] + controls[k_1:2*k_1 - 2]) + self.definition.append(h_gate, [target]) + + self.definition.append(s_gate, [target]) + + mcx_2 = MCXVChain( + num_ctrl_qubits=k_2, + dirty_ancillas=True, + ctrl_state=ctrl_state_k_2 + ).definition + self.definition.append(mcx_2, controls[k_1:] + [target] + controls[k_1 - k_2 + 2:k_1]) + self.definition.append(s_gate.inverse(), [target]) + + self.definition.h(target) From 2c81328b0b5c6342bf30f9b7f5a3d52c3cfd781d Mon Sep 17 00:00:00 2001 From: rafaella-vale <26910380+rafaella-vale@users.noreply.github.com> Date: Wed, 1 Mar 2023 01:09:39 -0300 Subject: [PATCH 02/42] removed optimization flag Co-authored-by: thiagom123 Co-authored-by: IsmaelCesar Co-authored-by: Israel F. Araujo Co-authored-by: Adenilton Silva <7927558+adjs@users.noreply.github.com> --- qiskit/circuit/library/standard_gates/multi_control_su2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/multi_control_su2.py b/qiskit/circuit/library/standard_gates/multi_control_su2.py index 1b440a5d1450..25831f0260c8 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_su2.py +++ b/qiskit/circuit/library/standard_gates/multi_control_su2.py @@ -163,7 +163,7 @@ def linear_depth_mcv( num_ctrl_qubits=k_2, dirty_ancillas=True, ctrl_state=ctrl_state_k_2, - action_only=general_su2_optimization + # action_only=general_su2_optimization ).definition self.definition.append( mcx_2.inverse(), @@ -235,7 +235,7 @@ def half_linear_depth_mcv( num_ctrl_qubits=k_2, dirty_ancillas=True, ctrl_state=ctrl_state_k_2, - action_only=True + # action_only=True ).definition self.definition.append(mcx_2, controls[k_1:] + [target] + controls[k_1 - k_2 + 2:k_1]) From a8fbb81ffd5b40a73acd8e8609c6c6fc1314b2e6 Mon Sep 17 00:00:00 2001 From: Adenilton Silva Date: Thu, 2 Mar 2023 14:04:20 -0300 Subject: [PATCH 03/42] tox -eblack --- .../standard_gates/multi_control_su2.py | 125 ++++++------------ 1 file changed, 44 insertions(+), 81 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/multi_control_su2.py b/qiskit/circuit/library/standard_gates/multi_control_su2.py index 25831f0260c8..47206e5a359f 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_su2.py +++ b/qiskit/circuit/library/standard_gates/multi_control_su2.py @@ -34,12 +34,7 @@ class MCSU2Gate(Gate): the number of qubits is odd, or 20n - 42 CNOTs if the number of qubits is even. """ - def __init__( - self, - su2_matrix, - num_ctrl_qubits, - ctrl_state: str=None - ): + def __init__(self, su2_matrix, num_ctrl_qubits, ctrl_state: str = None): _check_su2(su2_matrix) self.su2_matrix = su2_matrix @@ -48,16 +43,17 @@ def __init__( super().__init__("mcsu2", self.num_ctrl_qubits + 1, [], "mcsu2") - def _define(self): controls = QuantumRegister(self.num_ctrl_qubits) target = QuantumRegister(1) self.definition = QuantumCircuit(controls, target) - is_main_diag_real = isclose(self.su2_matrix[0, 0].imag, 0.0) and \ - isclose(self.su2_matrix[1, 1].imag, 0.0) - is_secondary_diag_real = isclose(self.su2_matrix[0,1].imag, 0.0) and \ - isclose(self.su2_matrix[1,0].imag, 0.0) + is_main_diag_real = isclose(self.su2_matrix[0, 0].imag, 0.0) and isclose( + self.su2_matrix[1, 1].imag, 0.0 + ) + is_secondary_diag_real = isclose(self.su2_matrix[0, 1].imag, 0.0) and isclose( + self.su2_matrix[1, 0].imag, 0.0 + ) if not is_main_diag_real and not is_secondary_diag_real: # U = V D V^-1, where the entries of the diagonal D are the eigenvalues @@ -73,12 +69,7 @@ def _define(self): x_vecs, z_vecs, controls, target, self.ctrl_state, inverse=True ) self.linear_depth_mcv( - x_vals, - z_vals, - controls, - target, - self.ctrl_state, - general_su2_optimization=True + x_vals, z_vals, controls, target, self.ctrl_state, general_su2_optimization=True ) self.half_linear_depth_mcv(x_vecs, z_vecs, controls, target, self.ctrl_state) @@ -93,55 +84,42 @@ def _define(self): if not is_secondary_diag_real: self.definition.h(target) - @staticmethod def _get_x_z(su2): - is_secondary_diag_real = isclose(su2[0,1].imag, 0.0) and isclose(su2[1,0].imag, 0.0) + is_secondary_diag_real = isclose(su2[0, 1].imag, 0.0) and isclose(su2[1, 0].imag, 0.0) if is_secondary_diag_real: - x = su2[0,1] - z = su2[1,1] + x = su2[0, 1] + z = su2[1, 1] else: - x = -su2[0,1].real - z = su2[1,1] - su2[0,1].imag * 1.0j + x = -su2[0, 1].real + z = su2[1, 1] - su2[0, 1].imag * 1.0j return x, z - def linear_depth_mcv( self, x, z, controls: Union[QuantumRegister, List[Qubit]], target: Qubit, - ctrl_state: str=None, - general_su2_optimization=False + ctrl_state: str = None, + general_su2_optimization=False, ): - alpha_r = np.sqrt( - (np.sqrt((z.real + 1.) / 2.) + 1.) / 2. - ) - alpha_i = z.imag / (2. * np.sqrt( - (z.real + 1.) * (np.sqrt((z.real + 1.) / 2.) + 1.) - ) - ) - alpha = alpha_r + 1.j * alpha_i - beta = x / (2. * np.sqrt( - (z.real + 1.) * (np.sqrt((z.real + 1.) / 2.) + 1.) - ) - ) + alpha_r = np.sqrt((np.sqrt((z.real + 1.0) / 2.0) + 1.0) / 2.0) + alpha_i = z.imag / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0))) + alpha = alpha_r + 1.0j * alpha_i + beta = x / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0))) - s_op = np.array([ - [alpha, -np.conj(beta)], - [beta, np.conj(alpha)] - ]) + s_op = np.array([[alpha, -np.conj(beta)], [beta, np.conj(alpha)]]) # S gate definition s_gate = QuantumCircuit(1) s_gate.unitary(s_op, 0) num_ctrl = len(controls) - k_1 = int(np.ceil(num_ctrl / 2.)) - k_2 = int(np.floor(num_ctrl / 2.)) + k_1 = int(np.ceil(num_ctrl / 2.0)) + k_2 = int(np.floor(num_ctrl / 2.0)) ctrl_state_k_1 = None ctrl_state_k_2 = None @@ -152,11 +130,9 @@ def linear_depth_mcv( if not general_su2_optimization: mcx_1 = MCXVChain( - num_ctrl_qubits=k_1, - dirty_ancillas=True, - ctrl_state=ctrl_state_k_1 + num_ctrl_qubits=k_1, dirty_ancillas=True, ctrl_state=ctrl_state_k_1 ).definition - self.definition.append(mcx_1, controls[:k_1] + [target] + controls[k_1:2*k_1 - 2]) + self.definition.append(mcx_1, controls[:k_1] + [target] + controls[k_1 : 2 * k_1 - 2]) self.definition.append(s_gate, [target]) mcx_2 = MCXVChain( @@ -166,47 +142,38 @@ def linear_depth_mcv( # action_only=general_su2_optimization ).definition self.definition.append( - mcx_2.inverse(), - controls[k_1:] + [target] + controls[k_1 - k_2 + 2:k_1] + mcx_2.inverse(), controls[k_1:] + [target] + controls[k_1 - k_2 + 2 : k_1] ) self.definition.append(s_gate.inverse(), [target]) mcx_3 = MCXVChain( - num_ctrl_qubits=k_1, - dirty_ancillas=True, - ctrl_state=ctrl_state_k_1 + num_ctrl_qubits=k_1, dirty_ancillas=True, ctrl_state=ctrl_state_k_1 ).definition - self.definition.append(mcx_3, controls[:k_1] + [target] + controls[k_1:2*k_1 - 2]) + self.definition.append(mcx_3, controls[:k_1] + [target] + controls[k_1 : 2 * k_1 - 2]) self.definition.append(s_gate, [target]) mcx_4 = MCXVChain( - num_ctrl_qubits=k_2, - dirty_ancillas=True, - ctrl_state=ctrl_state_k_2 + num_ctrl_qubits=k_2, dirty_ancillas=True, ctrl_state=ctrl_state_k_2 ).definition - self.definition.append(mcx_4, controls[k_1:] + [target] + controls[k_1 - k_2 + 2:k_1]) + self.definition.append(mcx_4, controls[k_1:] + [target] + controls[k_1 - k_2 + 2 : k_1]) self.definition.append(s_gate.inverse(), [target]) - def half_linear_depth_mcv( self, x, z, controls: Union[QuantumRegister, List[Qubit]], target: Qubit, - ctrl_state: str=None, - inverse: bool=False + ctrl_state: str = None, + inverse: bool = False, ): - alpha_r = np.sqrt((z.real + 1.) / 2.) - alpha_i = z.imag / np.sqrt(2*(z.real + 1.)) - alpha = alpha_r + 1.j * alpha_i + alpha_r = np.sqrt((z.real + 1.0) / 2.0) + alpha_i = z.imag / np.sqrt(2 * (z.real + 1.0)) + alpha = alpha_r + 1.0j * alpha_i - beta = x / np.sqrt(2*(z.real + 1.)) + beta = x / np.sqrt(2 * (z.real + 1.0)) - s_op = np.array([ - [alpha, -np.conj(beta)], - [beta, np.conj(alpha)] - ]) + s_op = np.array([[alpha, -np.conj(beta)], [beta, np.conj(alpha)]]) # S gate definition s_gate = QuantumCircuit(1) @@ -214,11 +181,11 @@ def half_linear_depth_mcv( # Hadamard equivalent definition h_gate = QuantumCircuit(1) - h_gate.unitary(np.array([[-1, 1], [1, 1]]) * 1/np.sqrt(2), 0) + h_gate.unitary(np.array([[-1, 1], [1, 1]]) * 1 / np.sqrt(2), 0) num_ctrl = len(controls) - k_1 = int(np.ceil(num_ctrl / 2.)) - k_2 = int(np.floor(num_ctrl / 2.)) + k_1 = int(np.ceil(num_ctrl / 2.0)) + k_2 = int(np.floor(num_ctrl / 2.0)) ctrl_state_k_1 = None ctrl_state_k_2 = None @@ -237,7 +204,7 @@ def half_linear_depth_mcv( ctrl_state=ctrl_state_k_2, # action_only=True ).definition - self.definition.append(mcx_2, controls[k_1:] + [target] + controls[k_1 - k_2 + 2:k_1]) + self.definition.append(mcx_2, controls[k_1:] + [target] + controls[k_1 - k_2 + 2 : k_1]) self.definition.append(s_gate.inverse(), [target]) @@ -245,21 +212,17 @@ def half_linear_depth_mcv( else: mcx_1 = MCXVChain( - num_ctrl_qubits=k_1, - dirty_ancillas=True, - ctrl_state=ctrl_state_k_1 + num_ctrl_qubits=k_1, dirty_ancillas=True, ctrl_state=ctrl_state_k_1 ).definition - self.definition.append(mcx_1, controls[:k_1] + [target] + controls[k_1:2*k_1 - 2]) + self.definition.append(mcx_1, controls[:k_1] + [target] + controls[k_1 : 2 * k_1 - 2]) self.definition.append(h_gate, [target]) self.definition.append(s_gate, [target]) mcx_2 = MCXVChain( - num_ctrl_qubits=k_2, - dirty_ancillas=True, - ctrl_state=ctrl_state_k_2 + num_ctrl_qubits=k_2, dirty_ancillas=True, ctrl_state=ctrl_state_k_2 ).definition - self.definition.append(mcx_2, controls[k_1:] + [target] + controls[k_1 - k_2 + 2:k_1]) + self.definition.append(mcx_2, controls[k_1:] + [target] + controls[k_1 - k_2 + 2 : k_1]) self.definition.append(s_gate.inverse(), [target]) self.definition.h(target) From 5151a42ba756808a181b474315ddb4aed5f0d43b Mon Sep 17 00:00:00 2001 From: rafaella-vale <26910380+rafaella-vale@users.noreply.github.com> Date: Fri, 3 Mar 2023 10:12:50 -0300 Subject: [PATCH 04/42] updated docstrings --- .../standard_gates/multi_control_su2.py | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/multi_control_su2.py b/qiskit/circuit/library/standard_gates/multi_control_su2.py index 47206e5a359f..54f1b850da29 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_su2.py +++ b/qiskit/circuit/library/standard_gates/multi_control_su2.py @@ -26,12 +26,13 @@ def _check_su2(matrix): class MCSU2Gate(Gate): - r""" + """ Linear-depth multi-controlled gate for special unitary single-qubit gates. This decomposition for SU(2) gates with multiple controls does not use auxiliary qubits. An n-qubit gate implemented with this method will have at most 20n - 38 CNOTs if the number of qubits is odd, or 20n - 42 CNOTs if the number of qubits is even. + This scheme is described in https://arxiv.org/abs/2302.06377. """ def __init__(self, su2_matrix, num_ctrl_qubits, ctrl_state: str = None): @@ -104,8 +105,22 @@ def linear_depth_mcv( controls: Union[QuantumRegister, List[Qubit]], target: Qubit, ctrl_state: str = None, - general_su2_optimization=False, + general_su2_optimization: bool = False, ): + """ + Apply circuit for the diagonal matrix D of the eigendecomposition U = V D V^-1, + where U is in SU(2). + + Args: + self (QuantumCircuit): The QuantumCircuit object to apply the diagonal operator on. + x (float): real parameter for the single-qubit operators + z (complex): complex parameter for the single-qubit operators + controls (QuantumRegister or list(Qubit)): The list of control qubits + target (Qubit): The target qubit + ctrl_state (str): control state of the operator SU(2) operator U + general_su2_optimization (bool): gate canceling in SU(2) gates with no real diagonal + """ + alpha_r = np.sqrt((np.sqrt((z.real + 1.0) / 2.0) + 1.0) / 2.0) alpha_i = z.imag / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0))) alpha = alpha_r + 1.0j * alpha_i @@ -167,6 +182,20 @@ def half_linear_depth_mcv( ctrl_state: str = None, inverse: bool = False, ): + """ + Apply circuit for the eigenvector matrix V and its inverse from the eigendecomposition + U = V D V^-1, where U is in SU(2). + + Args: + self (QuantumCircuit): The QuantumCircuit object to apply the diagonal operator on. + x (float): real parameter for the single-qubit operators + z (complex): complex parameter for the single-qubit operators + controls (QuantumRegister or list(Qubit)): The list of control qubits + target (Qubit): The target qubit + ctrl_state (str): control state of the operator SU(2) operator U + inverse (bool): apply the inverse operator V^-1 + """ + alpha_r = np.sqrt((z.real + 1.0) / 2.0) alpha_i = z.imag / np.sqrt(2 * (z.real + 1.0)) alpha = alpha_r + 1.0j * alpha_i From 0aec4ec0fd7e693788b697af5a278ff0acec5f5a Mon Sep 17 00:00:00 2001 From: "Israel F. Araujo" Date: Sat, 4 Mar 2023 00:22:58 +0900 Subject: [PATCH 05/42] Adds `MCSU2Gate` to `__init__` --- qiskit/circuit/library/standard_gates/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qiskit/circuit/library/standard_gates/__init__.py b/qiskit/circuit/library/standard_gates/__init__.py index 3dca1abb6201..071f27d1054e 100644 --- a/qiskit/circuit/library/standard_gates/__init__.py +++ b/qiskit/circuit/library/standard_gates/__init__.py @@ -70,6 +70,7 @@ XGate YGate ZGate + MCSU2Gate """ @@ -101,6 +102,7 @@ from .x import MCXGate, MCXGrayCode, MCXRecursive, MCXVChain from .y import YGate, CYGate from .z import ZGate, CZGate, CCZGate +from .multi_control_su2 import MCSU2Gate from .multi_control_rotation_gates import mcrx, mcry, mcrz From e39e09d128207be88836e104b7e4475b3235cfb6 Mon Sep 17 00:00:00 2001 From: rafaella-vale <26910380+rafaella-vale@users.noreply.github.com> Date: Fri, 3 Mar 2023 15:04:20 -0300 Subject: [PATCH 06/42] fixed circular import --- qiskit/circuit/library/standard_gates/multi_control_su2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/circuit/library/standard_gates/multi_control_su2.py b/qiskit/circuit/library/standard_gates/multi_control_su2.py index 54f1b850da29..29a80c2ef6f0 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_su2.py +++ b/qiskit/circuit/library/standard_gates/multi_control_su2.py @@ -16,7 +16,7 @@ from typing import Union, List from cmath import isclose import numpy as np -from qiskit import QuantumCircuit, QuantumRegister +from qiskit.circuit import QuantumCircuit, QuantumRegister from qiskit.circuit.library.standard_gates.x import MCXVChain from qiskit.circuit import Gate, Qubit From d6328195b9e727d708ea505f0a4bf3f0dafd55ac Mon Sep 17 00:00:00 2001 From: rafaella-vale <26910380+rafaella-vale@users.noreply.github.com> Date: Fri, 3 Mar 2023 15:46:46 -0300 Subject: [PATCH 07/42] defined control and inverse methods --- .../standard_gates/multi_control_su2.py | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/qiskit/circuit/library/standard_gates/multi_control_su2.py b/qiskit/circuit/library/standard_gates/multi_control_su2.py index 29a80c2ef6f0..410b5ae1b18d 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_su2.py +++ b/qiskit/circuit/library/standard_gates/multi_control_su2.py @@ -13,7 +13,7 @@ Multi-controlled SU(2) gate. """ -from typing import Union, List +from typing import Union, List, Optional from cmath import isclose import numpy as np from qiskit.circuit import QuantumCircuit, QuantumRegister @@ -85,6 +85,40 @@ def _define(self): if not is_secondary_diag_real: self.definition.h(target) + def control( + self, + num_ctrl_qubits: int = 1, + label: Optional[str] = None, + ctrl_state: Optional[Union[str, int]] = None, + ): + """ + Controlled version of this gate. + + Args: + num_ctrl_qubits (int): number of control qubits. + label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. + + Returns: + ControlledGate: controlled version of this gate. + """ + ctrl_state = _ctrl_state_to_int(ctrl_state, num_ctrl_qubits) + new_ctrl_state = (self.ctrl_state << num_ctrl_qubits) | ctrl_state + gate = MCSU2Gate( + self.su2_matrix, + num_ctrl_qubits=num_ctrl_qubits + self.num_ctrl_qubits, + label=label, + ctrl_state=new_ctrl_state, + ) + return gate + + def inverse(self): + """ + Returns inverted MCSU2 gate. + """ + return MCSU2Gate(np.linalg.inv(su2_matrix), self.num_ctrl_qubits) + @staticmethod def _get_x_z(su2): is_secondary_diag_real = isclose(su2[0, 1].imag, 0.0) and isclose(su2[1, 0].imag, 0.0) From 32acaccae7c1e29c0077e8b34d12bf018f182c7b Mon Sep 17 00:00:00 2001 From: rafaella-vale <26910380+rafaella-vale@users.noreply.github.com> Date: Fri, 3 Mar 2023 22:30:28 -0300 Subject: [PATCH 08/42] changed MCSU2Gate from Gate to ControlledGate --- .../standard_gates/multi_control_su2.py | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/multi_control_su2.py b/qiskit/circuit/library/standard_gates/multi_control_su2.py index 410b5ae1b18d..4d330df8057f 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_su2.py +++ b/qiskit/circuit/library/standard_gates/multi_control_su2.py @@ -16,16 +16,18 @@ from typing import Union, List, Optional from cmath import isclose import numpy as np -from qiskit.circuit import QuantumCircuit, QuantumRegister +from qiskit.circuit import QuantumCircuit, QuantumRegister, Qubit +from qiskit.circuit.controlledgate import ControlledGate from qiskit.circuit.library.standard_gates.x import MCXVChain -from qiskit.circuit import Gate, Qubit +from qiskit.exceptions import QiskitError +from qiskit.circuit._utils import _ctrl_state_to_int def _check_su2(matrix): return isclose(np.linalg.det(matrix), 1.0) -class MCSU2Gate(Gate): +class MCSU2Gate(ControlledGate): """ Linear-depth multi-controlled gate for special unitary single-qubit gates. @@ -36,13 +38,29 @@ class MCSU2Gate(Gate): """ def __init__(self, su2_matrix, num_ctrl_qubits, ctrl_state: str = None): - _check_su2(su2_matrix) + if su2_matrix.shape != (2, 2): + raise QiskitError("The dimension of the input matrix is not equal to (2,2)." + str(su2_matrix)) + if not _check_su2(su2_matrix): + raise QiskitError("The 2*2 matrix is not special unitary.") + + from qiskit.extensions.quantum_initializer.squ import SingleQubitUnitary self.su2_matrix = su2_matrix + self.base_gate = SingleQubitUnitary(self.su2_matrix) + self._num_qubits = num_ctrl_qubits + 1 self.num_ctrl_qubits = num_ctrl_qubits self.ctrl_state = ctrl_state - super().__init__("mcsu2", self.num_ctrl_qubits + 1, [], "mcsu2") + super().__init__( + name="mcsu2", + num_qubits=self._num_qubits, + params=[self.su2_matrix], + label="mcsu2", + num_ctrl_qubits=self.num_ctrl_qubits, + # definition=self.definition, + ctrl_state=self.ctrl_state, + base_gate=self.base_gate, + ) def _define(self): controls = QuantumRegister(self.num_ctrl_qubits) @@ -106,18 +124,22 @@ def control( ctrl_state = _ctrl_state_to_int(ctrl_state, num_ctrl_qubits) new_ctrl_state = (self.ctrl_state << num_ctrl_qubits) | ctrl_state gate = MCSU2Gate( - self.su2_matrix, + su2_matrix=self.su2_matrix, num_ctrl_qubits=num_ctrl_qubits + self.num_ctrl_qubits, - label=label, ctrl_state=new_ctrl_state, ) + return gate def inverse(self): """ Returns inverted MCSU2 gate. """ - return MCSU2Gate(np.linalg.inv(su2_matrix), self.num_ctrl_qubits) + return MCSU2Gate( + su2_matrix=np.linalg.inv(su2_matrix), + num_ctrl_qubits=self.num_ctrl_qubits, + ctrl_state=self.ctrl_state, + ) @staticmethod def _get_x_z(su2): From 3c8bdc4bdf7f9a533eaad37ba0694c8ade3a9013 Mon Sep 17 00:00:00 2001 From: rafaella-vale <26910380+rafaella-vale@users.noreply.github.com> Date: Fri, 3 Mar 2023 22:35:41 -0300 Subject: [PATCH 09/42] adjusted some tests for controlled gates --- test/python/circuit/test_controlled_gate.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index 9c2a32006daf..9a9158ae8d2a 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -64,6 +64,7 @@ RCCXGate, RC3XGate, MCU1Gate, + MCSU2Gate, MCXGate, MCXGrayCode, MCXRecursive, @@ -976,6 +977,12 @@ def test_standard_base_gate_setting(self, gate_class): free_params[1] = 3 elif gate_class in [MCXGate]: free_params[0] = 3 + elif gate_class in [MCSU2Gate]: + free_params[0] = np.array([ + [np.exp(-1.j * (np.pi / 2.)), 0.], + [0., np.exp(1.j * (np.pi / 2.))] + ]) + free_params[1] = 3 base_gate = gate_class(*free_params) cgate = base_gate.control() @@ -983,6 +990,8 @@ def test_standard_base_gate_setting(self, gate_class): # the base gate of CU is U (3 params), the base gate of CCU is CU (4 params) if gate_class == CUGate: self.assertListEqual(cgate.base_gate.params[:3], base_gate.base_gate.params[:3]) + elif gate_class == MCSU2Gate: + self.assertListEqual(cgate.base_gate.params, base_gate.base_gate.params) else: self.assertEqual(base_gate.base_gate, cgate.base_gate) @@ -1104,6 +1113,12 @@ def test_base_gate_params_reference(self): free_params[1] = 3 elif gate_class in [MCXGate]: free_params[0] = 3 + elif gate_class in [MCSU2Gate]: + free_params[0] = np.array([ + [np.exp(-1.j * (np.pi / 2.)), 0.], + [0., np.exp(1.j * (np.pi / 2.))] + ]) + free_params[1] = 3 base_gate = gate_class(*free_params) if base_gate.params: cgate = base_gate.control(num_ctrl_qubits) From 3a92dc93d485f6c156508ade65fad8c2e6ace2d7 Mon Sep 17 00:00:00 2001 From: rafaella-vale <26910380+rafaella-vale@users.noreply.github.com> Date: Fri, 3 Mar 2023 22:38:52 -0300 Subject: [PATCH 10/42] reformatting --- .../library/standard_gates/multi_control_su2.py | 6 ++++-- test/python/circuit/test_controlled_gate.py | 14 ++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/multi_control_su2.py b/qiskit/circuit/library/standard_gates/multi_control_su2.py index 4d330df8057f..aa59b3f9395d 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_su2.py +++ b/qiskit/circuit/library/standard_gates/multi_control_su2.py @@ -39,7 +39,9 @@ class MCSU2Gate(ControlledGate): def __init__(self, su2_matrix, num_ctrl_qubits, ctrl_state: str = None): if su2_matrix.shape != (2, 2): - raise QiskitError("The dimension of the input matrix is not equal to (2,2)." + str(su2_matrix)) + raise QiskitError( + "The dimension of the input matrix is not equal to (2,2)." + str(su2_matrix) + ) if not _check_su2(su2_matrix): raise QiskitError("The 2*2 matrix is not special unitary.") @@ -136,7 +138,7 @@ def inverse(self): Returns inverted MCSU2 gate. """ return MCSU2Gate( - su2_matrix=np.linalg.inv(su2_matrix), + su2_matrix=np.linalg.inv(self.su2_matrix), num_ctrl_qubits=self.num_ctrl_qubits, ctrl_state=self.ctrl_state, ) diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index 9a9158ae8d2a..4ac7954b5f6d 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -978,10 +978,9 @@ def test_standard_base_gate_setting(self, gate_class): elif gate_class in [MCXGate]: free_params[0] = 3 elif gate_class in [MCSU2Gate]: - free_params[0] = np.array([ - [np.exp(-1.j * (np.pi / 2.)), 0.], - [0., np.exp(1.j * (np.pi / 2.))] - ]) + free_params[0] = np.array( + [[np.exp(-1.0j * (np.pi / 2.0)), 0.0], [0.0, np.exp(1.0j * (np.pi / 2.0))]] + ) free_params[1] = 3 base_gate = gate_class(*free_params) @@ -1114,10 +1113,9 @@ def test_base_gate_params_reference(self): elif gate_class in [MCXGate]: free_params[0] = 3 elif gate_class in [MCSU2Gate]: - free_params[0] = np.array([ - [np.exp(-1.j * (np.pi / 2.)), 0.], - [0., np.exp(1.j * (np.pi / 2.))] - ]) + free_params[0] = np.array( + [[np.exp(-1.0j * (np.pi / 2.0)), 0.0], [0.0, np.exp(1.0j * (np.pi / 2.0))]] + ) free_params[1] = 3 base_gate = gate_class(*free_params) if base_gate.params: From ed6953b7395efaad6e08fd0ee924321b21931919 Mon Sep 17 00:00:00 2001 From: "Israel F. Araujo" Date: Sun, 5 Mar 2023 11:53:03 +0900 Subject: [PATCH 11/42] Fix regarding the integer `ctrl_state` parameter --- .../standard_gates/multi_control_su2.py | 52 +++++-------------- 1 file changed, 12 insertions(+), 40 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/multi_control_su2.py b/qiskit/circuit/library/standard_gates/multi_control_su2.py index aa59b3f9395d..8253bc6875a5 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_su2.py +++ b/qiskit/circuit/library/standard_gates/multi_control_su2.py @@ -20,11 +20,6 @@ from qiskit.circuit.controlledgate import ControlledGate from qiskit.circuit.library.standard_gates.x import MCXVChain from qiskit.exceptions import QiskitError -from qiskit.circuit._utils import _ctrl_state_to_int - - -def _check_su2(matrix): - return isclose(np.linalg.det(matrix), 1.0) class MCSU2Gate(ControlledGate): @@ -37,12 +32,12 @@ class MCSU2Gate(ControlledGate): This scheme is described in https://arxiv.org/abs/2302.06377. """ - def __init__(self, su2_matrix, num_ctrl_qubits, ctrl_state: str = None): + def __init__(self, su2_matrix, num_ctrl_qubits, ctrl_state: Optional[Union[str, int]] = None): if su2_matrix.shape != (2, 2): raise QiskitError( "The dimension of the input matrix is not equal to (2,2)." + str(su2_matrix) ) - if not _check_su2(su2_matrix): + if not self._check_su2(su2_matrix): raise QiskitError("The 2*2 matrix is not special unitary.") from qiskit.extensions.quantum_initializer.squ import SingleQubitUnitary @@ -59,7 +54,6 @@ def __init__(self, su2_matrix, num_ctrl_qubits, ctrl_state: str = None): params=[self.su2_matrix], label="mcsu2", num_ctrl_qubits=self.num_ctrl_qubits, - # definition=self.definition, ctrl_state=self.ctrl_state, base_gate=self.base_gate, ) @@ -105,34 +99,6 @@ def _define(self): if not is_secondary_diag_real: self.definition.h(target) - def control( - self, - num_ctrl_qubits: int = 1, - label: Optional[str] = None, - ctrl_state: Optional[Union[str, int]] = None, - ): - """ - Controlled version of this gate. - - Args: - num_ctrl_qubits (int): number of control qubits. - label (str or None): An optional label for the gate [Default: None] - ctrl_state (int or str or None): control state expressed as integer, - string (e.g. '110'), or None. If None, use all 1s. - - Returns: - ControlledGate: controlled version of this gate. - """ - ctrl_state = _ctrl_state_to_int(ctrl_state, num_ctrl_qubits) - new_ctrl_state = (self.ctrl_state << num_ctrl_qubits) | ctrl_state - gate = MCSU2Gate( - su2_matrix=self.su2_matrix, - num_ctrl_qubits=num_ctrl_qubits + self.num_ctrl_qubits, - ctrl_state=new_ctrl_state, - ) - - return gate - def inverse(self): """ Returns inverted MCSU2 gate. @@ -143,6 +109,10 @@ def inverse(self): ctrl_state=self.ctrl_state, ) + @staticmethod + def _check_su2(matrix): + return isclose(np.linalg.det(matrix), 1.0) + @staticmethod def _get_x_z(su2): is_secondary_diag_real = isclose(su2[0, 1].imag, 0.0) and isclose(su2[1, 0].imag, 0.0) @@ -198,8 +168,9 @@ def linear_depth_mcv( ctrl_state_k_2 = None if ctrl_state is not None: - ctrl_state_k_1 = ctrl_state[::-1][:k_1][::-1] - ctrl_state_k_2 = ctrl_state[::-1][k_1:][::-1] + str_ctrl_state = f"{ctrl_state:0{num_ctrl}b}" + ctrl_state_k_1 = str_ctrl_state[::-1][:k_1][::-1] + ctrl_state_k_2 = str_ctrl_state[::-1][k_1:][::-1] if not general_su2_optimization: mcx_1 = MCXVChain( @@ -278,8 +249,9 @@ def half_linear_depth_mcv( ctrl_state_k_2 = None if ctrl_state is not None: - ctrl_state_k_1 = ctrl_state[::-1][:k_1][::-1] - ctrl_state_k_2 = ctrl_state[::-1][k_1:][::-1] + str_ctrl_state = f"{ctrl_state:0{num_ctrl}b}" + ctrl_state_k_1 = str_ctrl_state[::-1][:k_1][::-1] + ctrl_state_k_2 = str_ctrl_state[::-1][k_1:][::-1] if inverse: self.definition.h(target) From 100a690c3a83556fd280cc4ede9e2c0f5d838455 Mon Sep 17 00:00:00 2001 From: "Israel F. Araujo" Date: Sun, 5 Mar 2023 11:55:56 +0900 Subject: [PATCH 12/42] Tests to check the CX count upper bound --- test/python/circuit/test_controlled_gate.py | 82 ++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index 4ac7954b5f6d..a200c442dc5e 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -19,7 +19,7 @@ from numpy import pi from ddt import ddt, data, unpack -from qiskit import QuantumRegister, QuantumCircuit, execute, BasicAer, QiskitError +from qiskit import QuantumRegister, QuantumCircuit, execute, BasicAer, QiskitError, transpile from qiskit.test import QiskitTestCase from qiskit.circuit import ControlledGate, Parameter, Gate from qiskit.circuit.exceptions import CircuitError @@ -73,6 +73,7 @@ C3SXGate, C4XGate, MCPhaseGate, + MCSU2Gate, ) from qiskit.circuit._utils import _compute_control_matrix import qiskit.circuit.library.standard_gates as allGates @@ -1269,6 +1270,85 @@ def test_control_zero_operand_gate(self, num_ctrl_qubits): target.flat[-1] = -1 self.assertEqual(Operator(controlled), Operator(target)) + @data(5, 10, 15) + def test_mcsu2_cx_count(self, num_ctrl_qubits): + """Test if the CX count of the multicontrolled SU(2) gate, + `MCSU2Gate`, is less than or equal to the upper bound.""" + + alpha = np.random.rand() + 1.j * np.random.rand() + beta = np.random.rand() + 1.j * np.random.rand() + + length = np.linalg.norm([alpha, beta]) + su2 = np.array([ + [alpha, -np.conj(beta)], + [beta, np.conj(alpha)] + ]) / length + + mcsu2 = MCSU2Gate(su2, num_ctrl_qubits) + qc = QuantumCircuit(mcsu2.num_qubits) + + qc.append(mcsu2, list(range(mcsu2.num_qubits))) + + tr_mcsu2 = transpile(qc, basis_gates=["u", "cx"]) + cx_count = tr_mcsu2.count_ops()["cx"] + + if mcsu2.num_qubits % 2: + # odd + self.assertLessEqual(cx_count, 20 * mcsu2.num_qubits - 38) + else: + # even + self.assertLessEqual(cx_count, 20 * mcsu2.num_qubits - 42) + + @data(5, 10, 15) + def test_mcsu2_cx_count_main_diag_real(self, num_ctrl_qubits): + """Test if the CX count of the multicontrolled SU(2) gate + with real-valued primary diagonal elements is less than or equal + to the upper bound.""" + + alpha = np.random.rand() + beta = np.random.rand() + 1.j * np.random.rand() + + length = np.linalg.norm([alpha, beta]) + su2 = np.array([ + [alpha, -np.conj(beta)], + [beta, np.conj(alpha)] + ]) / length + + mcsu2 = MCSU2Gate(su2, num_ctrl_qubits) + qc = QuantumCircuit(mcsu2.num_qubits) + + qc.append(mcsu2, list(range(mcsu2.num_qubits))) + + tr_mcsu2 = transpile(qc, basis_gates=["u", "cx"]) + cx_count = tr_mcsu2.count_ops()["cx"] + + self.assertLessEqual(cx_count, 16 * mcsu2.num_qubits - 40) + + @data(5, 10, 15) + def test_mcsu2_cx_count_off_diag_real(self, num_ctrl_qubits): + """Test if the CX count of the multicontrolled SU(2) gate + with real-valued off-diagonal elements is less than or equal + to the upper bound.""" + + alpha = np.random.rand() + 1.j * np.random.rand() + beta = np.random.rand() + + length = np.linalg.norm([alpha, beta]) + su2 = np.array([ + [alpha, -np.conj(beta)], + [beta, np.conj(alpha)] + ]) / length + + mcsu2 = MCSU2Gate(su2, num_ctrl_qubits) + qc = QuantumCircuit(mcsu2.num_qubits) + + qc.append(mcsu2, list(range(mcsu2.num_qubits))) + + tr_mcsu2 = transpile(qc, basis_gates=["u", "cx"]) + cx_count = tr_mcsu2.count_ops()["cx"] + + self.assertLessEqual(cx_count, 16 * mcsu2.num_qubits - 40) + @ddt class TestOpenControlledToMatrix(QiskitTestCase): From 7d3ea149b5c2bc431abb479f9b6572cdabcc732b Mon Sep 17 00:00:00 2001 From: "Israel F. Araujo" Date: Sun, 5 Mar 2023 12:52:54 +0900 Subject: [PATCH 13/42] Gate's `label` in the `control` function --- .../standard_gates/multi_control_su2.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/qiskit/circuit/library/standard_gates/multi_control_su2.py b/qiskit/circuit/library/standard_gates/multi_control_su2.py index 8253bc6875a5..53739c3b20c2 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_su2.py +++ b/qiskit/circuit/library/standard_gates/multi_control_su2.py @@ -20,6 +20,7 @@ from qiskit.circuit.controlledgate import ControlledGate from qiskit.circuit.library.standard_gates.x import MCXVChain from qiskit.exceptions import QiskitError +from qiskit.circuit._utils import _ctrl_state_to_int class MCSU2Gate(ControlledGate): @@ -109,6 +110,34 @@ def inverse(self): ctrl_state=self.ctrl_state, ) + def control( + self, + num_ctrl_qubits: int = 1, + label: Optional[str] = None, + ctrl_state: Optional[Union[str, int]] = None, + ): + """ + Controlled version of this gate. + Args: + num_ctrl_qubits (int): number of control qubits. + label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. + Returns: + ControlledGate: controlled version of this gate. + """ + ctrl_state = _ctrl_state_to_int(ctrl_state, num_ctrl_qubits) + new_ctrl_state = (self.ctrl_state << num_ctrl_qubits) | ctrl_state + gate = MCSU2Gate( + su2_matrix=self.su2_matrix, + num_ctrl_qubits=num_ctrl_qubits + self.num_ctrl_qubits, + ctrl_state=new_ctrl_state, + ) + + gate.label = label + + return gate + @staticmethod def _check_su2(matrix): return isclose(np.linalg.det(matrix), 1.0) From 7c756111a31e22e6c7d7aba0a3df2584fd00817f Mon Sep 17 00:00:00 2001 From: "Israel F. Araujo" Date: Sun, 5 Mar 2023 12:55:56 +0900 Subject: [PATCH 14/42] Upd. Qiskit tests to include cases for MCSU2Gate --- test/python/circuit/test_controlled_gate.py | 24 +++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index a200c442dc5e..76ffab806e94 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -1363,6 +1363,18 @@ def test_open_controlled_to_matrix(self, gate_class, ctrl_state): free_params[1] = 3 elif gate_class in [MCXGate]: free_params[0] = 3 + elif gate_class in [MCSU2Gate]: + alpha = np.random.rand() + 1.j * np.random.rand() + beta = np.random.rand() + 1.j * np.random.rand() + + length = np.linalg.norm([alpha, beta]) + su2 = np.array([ + [alpha, -np.conj(beta)], + [beta, np.conj(alpha)] + ]) / length + free_params[0] = su2 + free_params[1] = 7 + cgate = gate_class(*free_params) cgate.ctrl_state = ctrl_state @@ -1471,6 +1483,18 @@ def test_controlled_standard_gates(self, num_ctrl_qubits, gate_class): args[1] = 2 elif issubclass(gate_class, MCXGate): args = [5] + elif gate_class in [MCSU2Gate]: + alpha = np.random.rand() + 1.j * np.random.rand() + beta = np.random.rand() + 1.j * np.random.rand() + + length = np.linalg.norm([alpha, beta]) + su2 = np.array([ + [alpha, -np.conj(beta)], + [beta, np.conj(alpha)] + ]) / length + args[0] = su2 + args[1] = 7 + gate = gate_class(*args) for ctrl_state in (ctrl_state_ones, ctrl_state_zeros, ctrl_state_mixed): From c1ceaf6b28830caa5709cdb6e8d4db209acc8c84 Mon Sep 17 00:00:00 2001 From: "Israel F. Araujo" Date: Sun, 5 Mar 2023 13:36:06 +0900 Subject: [PATCH 15/42] Upd. Qiskit tests to include cases for MCSU2Gate --- test/python/circuit/test_gate_definitions.py | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py index b7d5f4f06834..32f1baa88fa0 100644 --- a/test/python/circuit/test_gate_definitions.py +++ b/test/python/circuit/test_gate_definitions.py @@ -244,6 +244,18 @@ def test_inverse(self, class_name, gate_class): elif class_name == "PauliGate": pauli_string = "IXYZ" gate = gate_class(pauli_string) + elif class_name == "MCSU2Gate": + num_ctrl_qubits = 7 + alpha = np.random.rand() + 1.j * np.random.rand() + beta = np.random.rand() + 1.j * np.random.rand() + + length = np.linalg.norm([alpha, beta]) + su2 = np.array([ + [alpha, -np.conj(beta)], + [beta, np.conj(alpha)] + ]) / length + + gate = gate_class(su2, num_ctrl_qubits) else: gate = gate_class(*float_vector) @@ -300,6 +312,17 @@ def test_equivalence_phase(self, gate_class): params = ["IXYZ"] if gate_class.__name__ in ["BooleanExpression"]: params = ["x | y"] + if gate_class.__name__ in ["MCSU2Gate"]: + alpha = np.random.rand() + 1.j * np.random.rand() + beta = np.random.rand() + 1.j * np.random.rand() + + length = np.linalg.norm([alpha, beta]) + su2 = np.array([ + [alpha, -np.conj(beta)], + [beta, np.conj(alpha)] + ]) / length + params[0] = su2 + params[1] = 7 gate = gate_class(*params) equiv_lib_list = std_eqlib.get_entry(gate) From 39d35b4f460cb3d7137e2a6b6f46112b3f8b7dde Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Sun, 5 Mar 2023 12:12:54 -0300 Subject: [PATCH 16/42] Revert "Upd. Qiskit tests to include cases for MCSU2Gate" This reverts commit c1ceaf6b28830caa5709cdb6e8d4db209acc8c84. --- test/python/circuit/test_gate_definitions.py | 23 -------------------- 1 file changed, 23 deletions(-) diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py index 32f1baa88fa0..b7d5f4f06834 100644 --- a/test/python/circuit/test_gate_definitions.py +++ b/test/python/circuit/test_gate_definitions.py @@ -244,18 +244,6 @@ def test_inverse(self, class_name, gate_class): elif class_name == "PauliGate": pauli_string = "IXYZ" gate = gate_class(pauli_string) - elif class_name == "MCSU2Gate": - num_ctrl_qubits = 7 - alpha = np.random.rand() + 1.j * np.random.rand() - beta = np.random.rand() + 1.j * np.random.rand() - - length = np.linalg.norm([alpha, beta]) - su2 = np.array([ - [alpha, -np.conj(beta)], - [beta, np.conj(alpha)] - ]) / length - - gate = gate_class(su2, num_ctrl_qubits) else: gate = gate_class(*float_vector) @@ -312,17 +300,6 @@ def test_equivalence_phase(self, gate_class): params = ["IXYZ"] if gate_class.__name__ in ["BooleanExpression"]: params = ["x | y"] - if gate_class.__name__ in ["MCSU2Gate"]: - alpha = np.random.rand() + 1.j * np.random.rand() - beta = np.random.rand() + 1.j * np.random.rand() - - length = np.linalg.norm([alpha, beta]) - su2 = np.array([ - [alpha, -np.conj(beta)], - [beta, np.conj(alpha)] - ]) / length - params[0] = su2 - params[1] = 7 gate = gate_class(*params) equiv_lib_list = std_eqlib.get_entry(gate) From c00d17a2af87a685032c40b663a241dad4fcce8d Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Sun, 5 Mar 2023 12:13:03 -0300 Subject: [PATCH 17/42] Revert "Upd. Qiskit tests to include cases for MCSU2Gate" This reverts commit 7c756111a31e22e6c7d7aba0a3df2584fd00817f. --- test/python/circuit/test_controlled_gate.py | 24 --------------------- 1 file changed, 24 deletions(-) diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index 76ffab806e94..a200c442dc5e 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -1363,18 +1363,6 @@ def test_open_controlled_to_matrix(self, gate_class, ctrl_state): free_params[1] = 3 elif gate_class in [MCXGate]: free_params[0] = 3 - elif gate_class in [MCSU2Gate]: - alpha = np.random.rand() + 1.j * np.random.rand() - beta = np.random.rand() + 1.j * np.random.rand() - - length = np.linalg.norm([alpha, beta]) - su2 = np.array([ - [alpha, -np.conj(beta)], - [beta, np.conj(alpha)] - ]) / length - free_params[0] = su2 - free_params[1] = 7 - cgate = gate_class(*free_params) cgate.ctrl_state = ctrl_state @@ -1483,18 +1471,6 @@ def test_controlled_standard_gates(self, num_ctrl_qubits, gate_class): args[1] = 2 elif issubclass(gate_class, MCXGate): args = [5] - elif gate_class in [MCSU2Gate]: - alpha = np.random.rand() + 1.j * np.random.rand() - beta = np.random.rand() + 1.j * np.random.rand() - - length = np.linalg.norm([alpha, beta]) - su2 = np.array([ - [alpha, -np.conj(beta)], - [beta, np.conj(alpha)] - ]) / length - args[0] = su2 - args[1] = 7 - gate = gate_class(*args) for ctrl_state in (ctrl_state_ones, ctrl_state_zeros, ctrl_state_mixed): From 0a6148d7c0bc59d44ad0115fbc7c127014f7c5d6 Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Sun, 5 Mar 2023 12:13:30 -0300 Subject: [PATCH 18/42] Revert "Tests to check the CX count upper bound" This reverts commit 100a690c3a83556fd280cc4ede9e2c0f5d838455. --- test/python/circuit/test_controlled_gate.py | 82 +-------------------- 1 file changed, 1 insertion(+), 81 deletions(-) diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index a200c442dc5e..4ac7954b5f6d 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -19,7 +19,7 @@ from numpy import pi from ddt import ddt, data, unpack -from qiskit import QuantumRegister, QuantumCircuit, execute, BasicAer, QiskitError, transpile +from qiskit import QuantumRegister, QuantumCircuit, execute, BasicAer, QiskitError from qiskit.test import QiskitTestCase from qiskit.circuit import ControlledGate, Parameter, Gate from qiskit.circuit.exceptions import CircuitError @@ -73,7 +73,6 @@ C3SXGate, C4XGate, MCPhaseGate, - MCSU2Gate, ) from qiskit.circuit._utils import _compute_control_matrix import qiskit.circuit.library.standard_gates as allGates @@ -1270,85 +1269,6 @@ def test_control_zero_operand_gate(self, num_ctrl_qubits): target.flat[-1] = -1 self.assertEqual(Operator(controlled), Operator(target)) - @data(5, 10, 15) - def test_mcsu2_cx_count(self, num_ctrl_qubits): - """Test if the CX count of the multicontrolled SU(2) gate, - `MCSU2Gate`, is less than or equal to the upper bound.""" - - alpha = np.random.rand() + 1.j * np.random.rand() - beta = np.random.rand() + 1.j * np.random.rand() - - length = np.linalg.norm([alpha, beta]) - su2 = np.array([ - [alpha, -np.conj(beta)], - [beta, np.conj(alpha)] - ]) / length - - mcsu2 = MCSU2Gate(su2, num_ctrl_qubits) - qc = QuantumCircuit(mcsu2.num_qubits) - - qc.append(mcsu2, list(range(mcsu2.num_qubits))) - - tr_mcsu2 = transpile(qc, basis_gates=["u", "cx"]) - cx_count = tr_mcsu2.count_ops()["cx"] - - if mcsu2.num_qubits % 2: - # odd - self.assertLessEqual(cx_count, 20 * mcsu2.num_qubits - 38) - else: - # even - self.assertLessEqual(cx_count, 20 * mcsu2.num_qubits - 42) - - @data(5, 10, 15) - def test_mcsu2_cx_count_main_diag_real(self, num_ctrl_qubits): - """Test if the CX count of the multicontrolled SU(2) gate - with real-valued primary diagonal elements is less than or equal - to the upper bound.""" - - alpha = np.random.rand() - beta = np.random.rand() + 1.j * np.random.rand() - - length = np.linalg.norm([alpha, beta]) - su2 = np.array([ - [alpha, -np.conj(beta)], - [beta, np.conj(alpha)] - ]) / length - - mcsu2 = MCSU2Gate(su2, num_ctrl_qubits) - qc = QuantumCircuit(mcsu2.num_qubits) - - qc.append(mcsu2, list(range(mcsu2.num_qubits))) - - tr_mcsu2 = transpile(qc, basis_gates=["u", "cx"]) - cx_count = tr_mcsu2.count_ops()["cx"] - - self.assertLessEqual(cx_count, 16 * mcsu2.num_qubits - 40) - - @data(5, 10, 15) - def test_mcsu2_cx_count_off_diag_real(self, num_ctrl_qubits): - """Test if the CX count of the multicontrolled SU(2) gate - with real-valued off-diagonal elements is less than or equal - to the upper bound.""" - - alpha = np.random.rand() + 1.j * np.random.rand() - beta = np.random.rand() - - length = np.linalg.norm([alpha, beta]) - su2 = np.array([ - [alpha, -np.conj(beta)], - [beta, np.conj(alpha)] - ]) / length - - mcsu2 = MCSU2Gate(su2, num_ctrl_qubits) - qc = QuantumCircuit(mcsu2.num_qubits) - - qc.append(mcsu2, list(range(mcsu2.num_qubits))) - - tr_mcsu2 = transpile(qc, basis_gates=["u", "cx"]) - cx_count = tr_mcsu2.count_ops()["cx"] - - self.assertLessEqual(cx_count, 16 * mcsu2.num_qubits - 40) - @ddt class TestOpenControlledToMatrix(QiskitTestCase): From f4042c1b0ae163c4b8f99bc212b6b91c463e1cf3 Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Mon, 6 Mar 2023 09:22:18 -0300 Subject: [PATCH 19/42] Update test_controlled_gate.py --- test/python/circuit/test_controlled_gate.py | 28 +++++++++------------ 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index 4ac7954b5f6d..9c77fa57d66d 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -64,7 +64,6 @@ RCCXGate, RC3XGate, MCU1Gate, - MCSU2Gate, MCXGate, MCXGrayCode, MCXRecursive, @@ -977,11 +976,6 @@ def test_standard_base_gate_setting(self, gate_class): free_params[1] = 3 elif gate_class in [MCXGate]: free_params[0] = 3 - elif gate_class in [MCSU2Gate]: - free_params[0] = np.array( - [[np.exp(-1.0j * (np.pi / 2.0)), 0.0], [0.0, np.exp(1.0j * (np.pi / 2.0))]] - ) - free_params[1] = 3 base_gate = gate_class(*free_params) cgate = base_gate.control() @@ -989,8 +983,6 @@ def test_standard_base_gate_setting(self, gate_class): # the base gate of CU is U (3 params), the base gate of CCU is CU (4 params) if gate_class == CUGate: self.assertListEqual(cgate.base_gate.params[:3], base_gate.base_gate.params[:3]) - elif gate_class == MCSU2Gate: - self.assertListEqual(cgate.base_gate.params, base_gate.base_gate.params) else: self.assertEqual(base_gate.base_gate, cgate.base_gate) @@ -1010,10 +1002,18 @@ def test_all_inverses(self, gate, num_ctrl_qubits, ctrl_state): numargs = len(_get_free_params(gate)) args = [2] * numargs gate = gate(*args) - self.assertEqual( - gate.inverse().control(num_ctrl_qubits, ctrl_state=ctrl_state), - gate.control(num_ctrl_qubits, ctrl_state=ctrl_state).inverse(), + + gate_inverse_control = gate.inverse().control(num_ctrl_qubits, ctrl_state=ctrl_state) + gate_control_inverse = gate.control(num_ctrl_qubits, ctrl_state=ctrl_state).inverse() + + + self.assertTrue( + np.allclose( + Operator(gate_inverse_control).to_matrix(), + Operator(gate_control_inverse).to_matrix() + ) ) + except AttributeError: # skip gates that do not have a control attribute (e.g. barrier) pass @@ -1112,11 +1112,7 @@ def test_base_gate_params_reference(self): free_params[1] = 3 elif gate_class in [MCXGate]: free_params[0] = 3 - elif gate_class in [MCSU2Gate]: - free_params[0] = np.array( - [[np.exp(-1.0j * (np.pi / 2.0)), 0.0], [0.0, np.exp(1.0j * (np.pi / 2.0))]] - ) - free_params[1] = 3 + base_gate = gate_class(*free_params) if base_gate.params: cgate = base_gate.control(num_ctrl_qubits) From b999c202a7b6cc3cc9fdd56452a0efc7bd6649cd Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Mon, 6 Mar 2023 09:22:35 -0300 Subject: [PATCH 20/42] Update test_circuit_operations.py --- test/python/circuit/test_circuit_operations.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py index 2612f2cfb8c9..19730f1ad557 100644 --- a/test/python/circuit/test_circuit_operations.py +++ b/test/python/circuit/test_circuit_operations.py @@ -828,7 +828,13 @@ def test_control_implementation(self): ref = QuantumCircuit(*c_qc.qregs) ref.append(cgate, ref.qubits) - self.assertEqual(ref, c_qc) + + self.assertTrue( + np.allclose( + Operator(ref).to_matrix(), + Operator(c_qc).to_matrix() + ) + ) @data("gate", "instruction") def test_repeat_appended_type(self, subtype): From 0c3eeedd638141553ca595d5ad9a5fea8b8d1a97 Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Mon, 6 Mar 2023 09:23:01 -0300 Subject: [PATCH 21/42] remove mcsu2gate class --- qiskit/circuit/library/standard_gates/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/circuit/library/standard_gates/__init__.py b/qiskit/circuit/library/standard_gates/__init__.py index 3922601014a9..f085c1f3a5b8 100644 --- a/qiskit/circuit/library/standard_gates/__init__.py +++ b/qiskit/circuit/library/standard_gates/__init__.py @@ -42,7 +42,6 @@ from .x import MCXGate, MCXGrayCode, MCXRecursive, MCXVChain from .y import YGate, CYGate from .z import ZGate, CZGate, CCZGate -from .multi_control_su2 import MCSU2Gate from .multi_control_rotation_gates import mcrx, mcry, mcrz From 6bdb533b5b8550f4e16aa88542ce89e40622948d Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Mon, 6 Mar 2023 09:23:25 -0300 Subject: [PATCH 22/42] remove mcsu2gate class --- .../standard_gates/multi_control_su2.py | 366 ++++-------------- 1 file changed, 73 insertions(+), 293 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/multi_control_su2.py b/qiskit/circuit/library/standard_gates/multi_control_su2.py index 53739c3b20c2..d2211ee405db 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_su2.py +++ b/qiskit/circuit/library/standard_gates/multi_control_su2.py @@ -13,304 +13,84 @@ Multi-controlled SU(2) gate. """ -from typing import Union, List, Optional +from typing import Union, List from cmath import isclose import numpy as np from qiskit.circuit import QuantumCircuit, QuantumRegister, Qubit -from qiskit.circuit.controlledgate import ControlledGate from qiskit.circuit.library.standard_gates.x import MCXVChain from qiskit.exceptions import QiskitError -from qiskit.circuit._utils import _ctrl_state_to_int - - -class MCSU2Gate(ControlledGate): +from qiskit.circuit.library.standard_gates import HGate + +def linear_depth_mcv( + circuit, + x, + z, + controls: Union[QuantumRegister, List[Qubit]], + target: Qubit, + ctrl_state: str = None, + general_su2_optimization: bool = False, +): """ - Linear-depth multi-controlled gate for special unitary single-qubit gates. - - This decomposition for SU(2) gates with multiple controls does not use auxiliary qubits. - An n-qubit gate implemented with this method will have at most 20n - 38 CNOTs if - the number of qubits is odd, or 20n - 42 CNOTs if the number of qubits is even. - This scheme is described in https://arxiv.org/abs/2302.06377. + Apply multi-controlled SU(2) gate [[z*, x],[-x, z]] with x real and z complex. + + Args: + circuit (QuantumCircuit): The QuantumCircuit object to apply the diagonal operator on. + x (float): real parameter for the single-qubit operators + z (complex): complex parameter for the single-qubit operators + controls (QuantumRegister or list(Qubit)): The list of control qubits + target (Qubit): The target qubit + ctrl_state (str): control state of the operator SU(2) operator U """ - def __init__(self, su2_matrix, num_ctrl_qubits, ctrl_state: Optional[Union[str, int]] = None): - if su2_matrix.shape != (2, 2): - raise QiskitError( - "The dimension of the input matrix is not equal to (2,2)." + str(su2_matrix) - ) - if not self._check_su2(su2_matrix): - raise QiskitError("The 2*2 matrix is not special unitary.") - - from qiskit.extensions.quantum_initializer.squ import SingleQubitUnitary - - self.su2_matrix = su2_matrix - self.base_gate = SingleQubitUnitary(self.su2_matrix) - self._num_qubits = num_ctrl_qubits + 1 - self.num_ctrl_qubits = num_ctrl_qubits - self.ctrl_state = ctrl_state - - super().__init__( - name="mcsu2", - num_qubits=self._num_qubits, - params=[self.su2_matrix], - label="mcsu2", - num_ctrl_qubits=self.num_ctrl_qubits, - ctrl_state=self.ctrl_state, - base_gate=self.base_gate, - ) - - def _define(self): - controls = QuantumRegister(self.num_ctrl_qubits) - target = QuantumRegister(1) - self.definition = QuantumCircuit(controls, target) - - is_main_diag_real = isclose(self.su2_matrix[0, 0].imag, 0.0) and isclose( - self.su2_matrix[1, 1].imag, 0.0 - ) - is_secondary_diag_real = isclose(self.su2_matrix[0, 1].imag, 0.0) and isclose( - self.su2_matrix[1, 0].imag, 0.0 - ) - - if not is_main_diag_real and not is_secondary_diag_real: - # U = V D V^-1, where the entries of the diagonal D are the eigenvalues - # `eig_vals` of U and the column vectors of V are the eigenvectors - # `eig_vecs` of U. These columns are orthonormal and the main diagonal - # of V is real-valued. - eig_vals, eig_vecs = np.linalg.eig(self.su2_matrix) - - x_vecs, z_vecs = self._get_x_z(eig_vecs) - x_vals, z_vals = self._get_x_z(np.diag(eig_vals)) - - self.half_linear_depth_mcv( - x_vecs, z_vecs, controls, target, self.ctrl_state, inverse=True - ) - self.linear_depth_mcv( - x_vals, z_vals, controls, target, self.ctrl_state, general_su2_optimization=True - ) - self.half_linear_depth_mcv(x_vecs, z_vecs, controls, target, self.ctrl_state) - - else: - x, z = self._get_x_z(self.su2_matrix) - - if not is_secondary_diag_real: - self.definition.h(target) - - self.linear_depth_mcv(x, z, controls, target, self.ctrl_state) - - if not is_secondary_diag_real: - self.definition.h(target) - - def inverse(self): - """ - Returns inverted MCSU2 gate. - """ - return MCSU2Gate( - su2_matrix=np.linalg.inv(self.su2_matrix), - num_ctrl_qubits=self.num_ctrl_qubits, - ctrl_state=self.ctrl_state, - ) - - def control( - self, - num_ctrl_qubits: int = 1, - label: Optional[str] = None, - ctrl_state: Optional[Union[str, int]] = None, - ): - """ - Controlled version of this gate. - Args: - num_ctrl_qubits (int): number of control qubits. - label (str or None): An optional label for the gate [Default: None] - ctrl_state (int or str or None): control state expressed as integer, - string (e.g. '110'), or None. If None, use all 1s. - Returns: - ControlledGate: controlled version of this gate. - """ - ctrl_state = _ctrl_state_to_int(ctrl_state, num_ctrl_qubits) - new_ctrl_state = (self.ctrl_state << num_ctrl_qubits) | ctrl_state - gate = MCSU2Gate( - su2_matrix=self.su2_matrix, - num_ctrl_qubits=num_ctrl_qubits + self.num_ctrl_qubits, - ctrl_state=new_ctrl_state, - ) - - gate.label = label - - return gate - - @staticmethod - def _check_su2(matrix): - return isclose(np.linalg.det(matrix), 1.0) - - @staticmethod - def _get_x_z(su2): - is_secondary_diag_real = isclose(su2[0, 1].imag, 0.0) and isclose(su2[1, 0].imag, 0.0) - - if is_secondary_diag_real: - x = su2[0, 1] - z = su2[1, 1] - else: - x = -su2[0, 1].real - z = su2[1, 1] - su2[0, 1].imag * 1.0j - - return x, z - - def linear_depth_mcv( - self, - x, - z, - controls: Union[QuantumRegister, List[Qubit]], - target: Qubit, - ctrl_state: str = None, - general_su2_optimization: bool = False, - ): - """ - Apply circuit for the diagonal matrix D of the eigendecomposition U = V D V^-1, - where U is in SU(2). - - Args: - self (QuantumCircuit): The QuantumCircuit object to apply the diagonal operator on. - x (float): real parameter for the single-qubit operators - z (complex): complex parameter for the single-qubit operators - controls (QuantumRegister or list(Qubit)): The list of control qubits - target (Qubit): The target qubit - ctrl_state (str): control state of the operator SU(2) operator U - general_su2_optimization (bool): gate canceling in SU(2) gates with no real diagonal - """ - - alpha_r = np.sqrt((np.sqrt((z.real + 1.0) / 2.0) + 1.0) / 2.0) - alpha_i = z.imag / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0))) - alpha = alpha_r + 1.0j * alpha_i - beta = x / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0))) - - s_op = np.array([[alpha, -np.conj(beta)], [beta, np.conj(alpha)]]) - - # S gate definition - s_gate = QuantumCircuit(1) - s_gate.unitary(s_op, 0) - - num_ctrl = len(controls) - k_1 = int(np.ceil(num_ctrl / 2.0)) - k_2 = int(np.floor(num_ctrl / 2.0)) - - ctrl_state_k_1 = None - ctrl_state_k_2 = None - - if ctrl_state is not None: - str_ctrl_state = f"{ctrl_state:0{num_ctrl}b}" - ctrl_state_k_1 = str_ctrl_state[::-1][:k_1][::-1] - ctrl_state_k_2 = str_ctrl_state[::-1][k_1:][::-1] - - if not general_su2_optimization: - mcx_1 = MCXVChain( - num_ctrl_qubits=k_1, dirty_ancillas=True, ctrl_state=ctrl_state_k_1 - ).definition - self.definition.append(mcx_1, controls[:k_1] + [target] + controls[k_1 : 2 * k_1 - 2]) - self.definition.append(s_gate, [target]) - - mcx_2 = MCXVChain( - num_ctrl_qubits=k_2, - dirty_ancillas=True, - ctrl_state=ctrl_state_k_2, - # action_only=general_su2_optimization - ).definition - self.definition.append( - mcx_2.inverse(), controls[k_1:] + [target] + controls[k_1 - k_2 + 2 : k_1] - ) - self.definition.append(s_gate.inverse(), [target]) - - mcx_3 = MCXVChain( - num_ctrl_qubits=k_1, dirty_ancillas=True, ctrl_state=ctrl_state_k_1 - ).definition - self.definition.append(mcx_3, controls[:k_1] + [target] + controls[k_1 : 2 * k_1 - 2]) - self.definition.append(s_gate, [target]) - - mcx_4 = MCXVChain( - num_ctrl_qubits=k_2, dirty_ancillas=True, ctrl_state=ctrl_state_k_2 - ).definition - self.definition.append(mcx_4, controls[k_1:] + [target] + controls[k_1 - k_2 + 2 : k_1]) - self.definition.append(s_gate.inverse(), [target]) - - def half_linear_depth_mcv( - self, - x, - z, - controls: Union[QuantumRegister, List[Qubit]], - target: Qubit, - ctrl_state: str = None, - inverse: bool = False, - ): - """ - Apply circuit for the eigenvector matrix V and its inverse from the eigendecomposition - U = V D V^-1, where U is in SU(2). - - Args: - self (QuantumCircuit): The QuantumCircuit object to apply the diagonal operator on. - x (float): real parameter for the single-qubit operators - z (complex): complex parameter for the single-qubit operators - controls (QuantumRegister or list(Qubit)): The list of control qubits - target (Qubit): The target qubit - ctrl_state (str): control state of the operator SU(2) operator U - inverse (bool): apply the inverse operator V^-1 - """ - - alpha_r = np.sqrt((z.real + 1.0) / 2.0) - alpha_i = z.imag / np.sqrt(2 * (z.real + 1.0)) - alpha = alpha_r + 1.0j * alpha_i - - beta = x / np.sqrt(2 * (z.real + 1.0)) - - s_op = np.array([[alpha, -np.conj(beta)], [beta, np.conj(alpha)]]) - - # S gate definition - s_gate = QuantumCircuit(1) - s_gate.unitary(s_op, 0) - - # Hadamard equivalent definition - h_gate = QuantumCircuit(1) - h_gate.unitary(np.array([[-1, 1], [1, 1]]) * 1 / np.sqrt(2), 0) - - num_ctrl = len(controls) - k_1 = int(np.ceil(num_ctrl / 2.0)) - k_2 = int(np.floor(num_ctrl / 2.0)) - - ctrl_state_k_1 = None - ctrl_state_k_2 = None - - if ctrl_state is not None: - str_ctrl_state = f"{ctrl_state:0{num_ctrl}b}" - ctrl_state_k_1 = str_ctrl_state[::-1][:k_1][::-1] - ctrl_state_k_2 = str_ctrl_state[::-1][k_1:][::-1] - - if inverse: - self.definition.h(target) - - self.definition.append(s_gate, [target]) - mcx_2 = MCXVChain( - num_ctrl_qubits=k_2, - dirty_ancillas=True, - ctrl_state=ctrl_state_k_2, - # action_only=True - ).definition - self.definition.append(mcx_2, controls[k_1:] + [target] + controls[k_1 - k_2 + 2 : k_1]) - - self.definition.append(s_gate.inverse(), [target]) - - self.definition.append(h_gate, [target]) - - else: - mcx_1 = MCXVChain( - num_ctrl_qubits=k_1, dirty_ancillas=True, ctrl_state=ctrl_state_k_1 - ).definition - self.definition.append(mcx_1, controls[:k_1] + [target] + controls[k_1 : 2 * k_1 - 2]) - self.definition.append(h_gate, [target]) - - self.definition.append(s_gate, [target]) - - mcx_2 = MCXVChain( - num_ctrl_qubits=k_2, dirty_ancillas=True, ctrl_state=ctrl_state_k_2 - ).definition - self.definition.append(mcx_2, controls[k_1:] + [target] + controls[k_1 - k_2 + 2 : k_1]) - self.definition.append(s_gate.inverse(), [target]) - - self.definition.h(target) + alpha_r = np.sqrt((np.sqrt((z.real + 1.0) / 2.0) + 1.0) / 2.0) + alpha_i = z.imag / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0))) + alpha = alpha_r + 1.0j * alpha_i + beta = x / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0))) + + s_op = np.array([[alpha, -np.conj(beta)], [beta, np.conj(alpha)]]) + + # S gate definition + s_gate = QuantumCircuit(1) + s_gate.unitary(s_op, 0) + s_gate = s_gate.to_gate() + + num_ctrl = len(controls) + k_1 = int(np.ceil(num_ctrl / 2.0)) + k_2 = int(np.floor(num_ctrl / 2.0)) + + ctrl_state_k_1 = None + ctrl_state_k_2 = None + + if ctrl_state is not None: + str_ctrl_state = f"{ctrl_state:0{num_ctrl}b}" + ctrl_state_k_1 = str_ctrl_state[::-1][:k_1][::-1] + ctrl_state_k_2 = str_ctrl_state[::-1][k_1:][::-1] + + mcx_1 = MCXVChain( + num_ctrl_qubits=k_1, dirty_ancillas=True, ctrl_state=ctrl_state_k_1 + ) + circuit.append(mcx_1, controls[:k_1] + [target] + controls[k_1: 2 * k_1 - 2]) + circuit.append(s_gate, [target]) + + mcx_2 = MCXVChain( + num_ctrl_qubits=k_2, + dirty_ancillas=True, + ctrl_state=ctrl_state_k_2, + # action_only=general_su2_optimization # Requires PR #9687 + ) + circuit.append( + mcx_2.inverse(), controls[k_1:] + [target] + controls[k_1 - k_2 + 2 : k_1] + ) + circuit.append(s_gate.inverse(), [target]) + + mcx_3 = MCXVChain( + num_ctrl_qubits=k_1, dirty_ancillas=True, ctrl_state=ctrl_state_k_1 + ) + circuit.append(mcx_3, controls[:k_1] + [target] + controls[k_1: 2 * k_1 - 2]) + circuit.append(s_gate, [target]) + + mcx_4 = MCXVChain( + num_ctrl_qubits=k_2, dirty_ancillas=True, ctrl_state=ctrl_state_k_2 + ) + circuit.append(mcx_4, controls[k_1:] + [target] + controls[k_1 - k_2 + 2: k_1]) + circuit.append(s_gate.inverse(), [target]) \ No newline at end of file From 9b0245139d1793449c3237c9624056e885b9709a Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Mon, 6 Mar 2023 09:23:38 -0300 Subject: [PATCH 23/42] fix mcry --- .../multi_control_rotation_gates.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py index 1e420185176e..c0e82aa4ee01 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py +++ b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py @@ -20,6 +20,7 @@ from qiskit.circuit.library.standard_gates.u3 import _generate_gray_code from qiskit.circuit.parameterexpression import ParameterValueType from qiskit.exceptions import QiskitError +from qiskit.circuit.library.standard_gates.multi_control_su2 import linear_depth_mcv def _apply_cu(circuit, theta, phi, lam, control, target, use_basis_gates=True): @@ -187,16 +188,10 @@ def mcry( self, theta, 0, 0, control_qubits[0], target_qubit, use_basis_gates=use_basis_gates ) else: - theta_step = theta * (1 / (2 ** (n_c - 1))) - _apply_mcu_graycode( - self, - theta_step, - 0, - 0, - control_qubits, - target_qubit, - use_basis_gates=use_basis_gates, - ) + import numpy as np + x = -np.sin(theta / 2) + z = np.cos(theta / 2) + linear_depth_mcv(self, x, z, control_qubits, target_qubit) else: raise QiskitError(f"Unrecognized mode for building MCRY circuit: {mode}.") From 2568a397ae0e0b161946e7de16198dec5017828d Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Mon, 6 Mar 2023 15:06:29 -0300 Subject: [PATCH 24/42] lint --- .../multi_control_rotation_gates.py | 8 ++--- .../standard_gates/multi_control_su2.py | 29 ++++++------------- .../python/circuit/test_circuit_operations.py | 8 +---- test/python/circuit/test_controlled_gate.py | 11 ++++--- 4 files changed, 21 insertions(+), 35 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py index c0e82aa4ee01..fe9a0cd018c8 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py +++ b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py @@ -13,7 +13,7 @@ Multiple-Controlled U3 gate. Not using ancillary qubits. """ -from math import pi +from math import pi, sin, cos from typing import Optional, Union, Tuple, List from qiskit.circuit import QuantumCircuit, QuantumRegister, Qubit from qiskit.circuit.library.standard_gates.x import MCXGate @@ -188,9 +188,9 @@ def mcry( self, theta, 0, 0, control_qubits[0], target_qubit, use_basis_gates=use_basis_gates ) else: - import numpy as np - x = -np.sin(theta / 2) - z = np.cos(theta / 2) + + x = -sin(theta / 2) + z = cos(theta / 2) linear_depth_mcv(self, x, z, control_qubits, target_qubit) else: raise QiskitError(f"Unrecognized mode for building MCRY circuit: {mode}.") diff --git a/qiskit/circuit/library/standard_gates/multi_control_su2.py b/qiskit/circuit/library/standard_gates/multi_control_su2.py index d2211ee405db..f9c18c62caf3 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_su2.py +++ b/qiskit/circuit/library/standard_gates/multi_control_su2.py @@ -14,12 +14,10 @@ """ from typing import Union, List -from cmath import isclose import numpy as np from qiskit.circuit import QuantumCircuit, QuantumRegister, Qubit from qiskit.circuit.library.standard_gates.x import MCXVChain -from qiskit.exceptions import QiskitError -from qiskit.circuit.library.standard_gates import HGate + def linear_depth_mcv( circuit, @@ -28,7 +26,6 @@ def linear_depth_mcv( controls: Union[QuantumRegister, List[Qubit]], target: Qubit, ctrl_state: str = None, - general_su2_optimization: bool = False, ): """ Apply multi-controlled SU(2) gate [[z*, x],[-x, z]] with x real and z complex. @@ -66,10 +63,8 @@ def linear_depth_mcv( ctrl_state_k_1 = str_ctrl_state[::-1][:k_1][::-1] ctrl_state_k_2 = str_ctrl_state[::-1][k_1:][::-1] - mcx_1 = MCXVChain( - num_ctrl_qubits=k_1, dirty_ancillas=True, ctrl_state=ctrl_state_k_1 - ) - circuit.append(mcx_1, controls[:k_1] + [target] + controls[k_1: 2 * k_1 - 2]) + mcx_1 = MCXVChain(num_ctrl_qubits=k_1, dirty_ancillas=True, ctrl_state=ctrl_state_k_1) + circuit.append(mcx_1, controls[:k_1] + [target] + controls[k_1 : 2 * k_1 - 2]) circuit.append(s_gate, [target]) mcx_2 = MCXVChain( @@ -78,19 +73,13 @@ def linear_depth_mcv( ctrl_state=ctrl_state_k_2, # action_only=general_su2_optimization # Requires PR #9687 ) - circuit.append( - mcx_2.inverse(), controls[k_1:] + [target] + controls[k_1 - k_2 + 2 : k_1] - ) + circuit.append(mcx_2.inverse(), controls[k_1:] + [target] + controls[k_1 - k_2 + 2 : k_1]) circuit.append(s_gate.inverse(), [target]) - mcx_3 = MCXVChain( - num_ctrl_qubits=k_1, dirty_ancillas=True, ctrl_state=ctrl_state_k_1 - ) - circuit.append(mcx_3, controls[:k_1] + [target] + controls[k_1: 2 * k_1 - 2]) + mcx_3 = MCXVChain(num_ctrl_qubits=k_1, dirty_ancillas=True, ctrl_state=ctrl_state_k_1) + circuit.append(mcx_3, controls[:k_1] + [target] + controls[k_1 : 2 * k_1 - 2]) circuit.append(s_gate, [target]) - mcx_4 = MCXVChain( - num_ctrl_qubits=k_2, dirty_ancillas=True, ctrl_state=ctrl_state_k_2 - ) - circuit.append(mcx_4, controls[k_1:] + [target] + controls[k_1 - k_2 + 2: k_1]) - circuit.append(s_gate.inverse(), [target]) \ No newline at end of file + mcx_4 = MCXVChain(num_ctrl_qubits=k_2, dirty_ancillas=True, ctrl_state=ctrl_state_k_2) + circuit.append(mcx_4, controls[k_1:] + [target] + controls[k_1 - k_2 + 2 : k_1]) + circuit.append(s_gate.inverse(), [target]) diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py index 19730f1ad557..9ed04a9dc98c 100644 --- a/test/python/circuit/test_circuit_operations.py +++ b/test/python/circuit/test_circuit_operations.py @@ -828,13 +828,7 @@ def test_control_implementation(self): ref = QuantumCircuit(*c_qc.qregs) ref.append(cgate, ref.qubits) - - self.assertTrue( - np.allclose( - Operator(ref).to_matrix(), - Operator(c_qc).to_matrix() - ) - ) + self.assertTrue(np.allclose(Operator(ref).to_matrix(), Operator(c_qc).to_matrix())) @data("gate", "instruction") def test_repeat_appended_type(self, subtype): diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index 9c77fa57d66d..c671a9b5117a 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -1003,14 +1003,17 @@ def test_all_inverses(self, gate, num_ctrl_qubits, ctrl_state): args = [2] * numargs gate = gate(*args) - gate_inverse_control = gate.inverse().control(num_ctrl_qubits, ctrl_state=ctrl_state) - gate_control_inverse = gate.control(num_ctrl_qubits, ctrl_state=ctrl_state).inverse() - + gate_inverse_control = gate.inverse().control( + num_ctrl_qubits, ctrl_state=ctrl_state + ) + gate_control_inverse = gate.control( + num_ctrl_qubits, ctrl_state=ctrl_state + ).inverse() self.assertTrue( np.allclose( Operator(gate_inverse_control).to_matrix(), - Operator(gate_control_inverse).to_matrix() + Operator(gate_control_inverse).to_matrix(), ) ) From 15034fd1b3362bc3afc9255193168f1ba821ab11 Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Tue, 7 Mar 2023 18:44:35 -0300 Subject: [PATCH 25/42] fix mcrx --- .../multi_control_rotation_gates.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py index fe9a0cd018c8..9315b4c6fcff 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py +++ b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py @@ -122,16 +122,11 @@ def mcrx( use_basis_gates=use_basis_gates, ) else: - theta_step = theta * (1 / (2 ** (n_c - 1))) - _apply_mcu_graycode( - self, - theta_step, - -pi / 2, - pi / 2, - control_qubits, - target_qubit, - use_basis_gates=use_basis_gates, - ) + x = 0 + z = cos(theta / 2) + sin(theta / 2) * 1.0j + self.h(target_qubit) + linear_depth_mcv(self, x, z, control_qubits, target_qubit) + self.h(target_qubit) def mcry( From fe874ef61f72c39a7f4017db050558a8b2f86613 Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Wed, 8 Mar 2023 09:48:45 -0300 Subject: [PATCH 26/42] add reference --- qiskit/circuit/library/standard_gates/multi_control_su2.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit/circuit/library/standard_gates/multi_control_su2.py b/qiskit/circuit/library/standard_gates/multi_control_su2.py index f9c18c62caf3..44d6135764e3 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_su2.py +++ b/qiskit/circuit/library/standard_gates/multi_control_su2.py @@ -29,6 +29,7 @@ def linear_depth_mcv( ): """ Apply multi-controlled SU(2) gate [[z*, x],[-x, z]] with x real and z complex. + https://arxiv.org/abs/2302.06377 Args: circuit (QuantumCircuit): The QuantumCircuit object to apply the diagonal operator on. From b762b393428f8c6889a055a3217779d84e8754bf Mon Sep 17 00:00:00 2001 From: "Israel F. Araujo" Date: Wed, 15 Mar 2023 04:12:10 +0900 Subject: [PATCH 27/42] Create `s_gate` directly --- .../library/standard_gates/multi_control_su2.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/multi_control_su2.py b/qiskit/circuit/library/standard_gates/multi_control_su2.py index 44d6135764e3..f438eb538661 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_su2.py +++ b/qiskit/circuit/library/standard_gates/multi_control_su2.py @@ -15,9 +15,9 @@ from typing import Union, List import numpy as np -from qiskit.circuit import QuantumCircuit, QuantumRegister, Qubit +from qiskit.circuit import QuantumRegister, Qubit from qiskit.circuit.library.standard_gates.x import MCXVChain - +from qiskit.extensions import UnitaryGate def linear_depth_mcv( circuit, @@ -48,10 +48,8 @@ def linear_depth_mcv( s_op = np.array([[alpha, -np.conj(beta)], [beta, np.conj(alpha)]]) # S gate definition - s_gate = QuantumCircuit(1) - s_gate.unitary(s_op, 0) - s_gate = s_gate.to_gate() - + s_gate = UnitaryGate(s_op) + num_ctrl = len(controls) k_1 = int(np.ceil(num_ctrl / 2.0)) k_2 = int(np.floor(num_ctrl / 2.0)) From d75b9c9f04b28210a046495862c558421375b61d Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Tue, 14 Mar 2023 18:42:45 -0300 Subject: [PATCH 28/42] Revert "Create `s_gate` directly" This reverts commit b762b393428f8c6889a055a3217779d84e8754bf. --- .../library/standard_gates/multi_control_su2.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/multi_control_su2.py b/qiskit/circuit/library/standard_gates/multi_control_su2.py index f438eb538661..44d6135764e3 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_su2.py +++ b/qiskit/circuit/library/standard_gates/multi_control_su2.py @@ -15,9 +15,9 @@ from typing import Union, List import numpy as np -from qiskit.circuit import QuantumRegister, Qubit +from qiskit.circuit import QuantumCircuit, QuantumRegister, Qubit from qiskit.circuit.library.standard_gates.x import MCXVChain -from qiskit.extensions import UnitaryGate + def linear_depth_mcv( circuit, @@ -48,8 +48,10 @@ def linear_depth_mcv( s_op = np.array([[alpha, -np.conj(beta)], [beta, np.conj(alpha)]]) # S gate definition - s_gate = UnitaryGate(s_op) - + s_gate = QuantumCircuit(1) + s_gate.unitary(s_op, 0) + s_gate = s_gate.to_gate() + num_ctrl = len(controls) k_1 = int(np.ceil(num_ctrl / 2.0)) k_2 = int(np.floor(num_ctrl / 2.0)) From d5843409333e97414096772ef1bd55e1e5f1cbb5 Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Tue, 14 Mar 2023 20:56:19 -0300 Subject: [PATCH 29/42] review --- .../multi_control_rotation_gates.py | 69 ++++++++++++++- .../standard_gates/multi_control_su2.py | 86 ------------------- test/python/circuit/test_controlled_gate.py | 15 +++- 3 files changed, 82 insertions(+), 88 deletions(-) delete mode 100644 qiskit/circuit/library/standard_gates/multi_control_su2.py diff --git a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py index 9315b4c6fcff..97e687567de2 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py +++ b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py @@ -15,12 +15,12 @@ from math import pi, sin, cos from typing import Optional, Union, Tuple, List +import numpy as np from qiskit.circuit import QuantumCircuit, QuantumRegister, Qubit from qiskit.circuit.library.standard_gates.x import MCXGate from qiskit.circuit.library.standard_gates.u3 import _generate_gray_code from qiskit.circuit.parameterexpression import ParameterValueType from qiskit.exceptions import QiskitError -from qiskit.circuit.library.standard_gates.multi_control_su2 import linear_depth_mcv def _apply_cu(circuit, theta, phi, lam, control, target, use_basis_gates=True): @@ -82,6 +82,73 @@ def _apply_mcu_graycode(circuit, theta, phi, lam, ctls, tgt, use_basis_gates): last_pattern = pattern +def linear_depth_mcv( + circuit, + x, + z, + controls: Union[QuantumRegister, List[Qubit]], + target: Qubit, + ctrl_state: str = None, +): + """ + Apply multi-controlled SU(2) gate [[z*, x],[-x, z]] with x real and z complex. + https://arxiv.org/abs/2302.06377 + + Args: + circuit (QuantumCircuit): The QuantumCircuit object to apply the diagonal operator on. + x (float): real parameter for the single-qubit operators + z (complex): complex parameter for the single-qubit operators + controls (QuantumRegister or list(Qubit)): The list of control qubits + target (Qubit): The target qubit + ctrl_state (str): control state of the operator SU(2) operator U + """ + # pylint: disable=cyclic-import + from qiskit.circuit.library import MCXVChain + from qiskit.extensions import UnitaryGate + + alpha_r = np.sqrt((np.sqrt((z.real + 1.0) / 2.0) + 1.0) / 2.0) + alpha_i = z.imag / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0))) + alpha = alpha_r + 1.0j * alpha_i + beta = x / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0))) + + # S gate definition + s_op = np.array([[alpha, -np.conj(beta)], [beta, np.conj(alpha)]]) + s_gate = UnitaryGate(s_op) + + num_ctrl = len(controls) + k_1 = int(np.ceil(num_ctrl / 2.0)) + k_2 = int(np.floor(num_ctrl / 2.0)) + + ctrl_state_k_1 = None + ctrl_state_k_2 = None + + if ctrl_state is not None: + str_ctrl_state = f"{ctrl_state:0{num_ctrl}b}" + ctrl_state_k_1 = str_ctrl_state[::-1][:k_1][::-1] + ctrl_state_k_2 = str_ctrl_state[::-1][k_1:][::-1] + + mcx_1 = MCXVChain(num_ctrl_qubits=k_1, dirty_ancillas=True, ctrl_state=ctrl_state_k_1) + circuit.append(mcx_1, controls[:k_1] + [target] + controls[k_1 : 2 * k_1 - 2]) + circuit.append(s_gate, [target]) + + mcx_2 = MCXVChain( + num_ctrl_qubits=k_2, + dirty_ancillas=True, + ctrl_state=ctrl_state_k_2, + # action_only=general_su2_optimization # Requires PR #9687 + ) + circuit.append(mcx_2.inverse(), controls[k_1:] + [target] + controls[k_1 - k_2 + 2 : k_1]) + circuit.append(s_gate.inverse(), [target]) + + mcx_3 = MCXVChain(num_ctrl_qubits=k_1, dirty_ancillas=True, ctrl_state=ctrl_state_k_1) + circuit.append(mcx_3, controls[:k_1] + [target] + controls[k_1 : 2 * k_1 - 2]) + circuit.append(s_gate, [target]) + + mcx_4 = MCXVChain(num_ctrl_qubits=k_2, dirty_ancillas=True, ctrl_state=ctrl_state_k_2) + circuit.append(mcx_4, controls[k_1:] + [target] + controls[k_1 - k_2 + 2 : k_1]) + circuit.append(s_gate.inverse(), [target]) + + def mcrx( self, theta: ParameterValueType, diff --git a/qiskit/circuit/library/standard_gates/multi_control_su2.py b/qiskit/circuit/library/standard_gates/multi_control_su2.py deleted file mode 100644 index 44d6135764e3..000000000000 --- a/qiskit/circuit/library/standard_gates/multi_control_su2.py +++ /dev/null @@ -1,86 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -""" -Multi-controlled SU(2) gate. -""" - -from typing import Union, List -import numpy as np -from qiskit.circuit import QuantumCircuit, QuantumRegister, Qubit -from qiskit.circuit.library.standard_gates.x import MCXVChain - - -def linear_depth_mcv( - circuit, - x, - z, - controls: Union[QuantumRegister, List[Qubit]], - target: Qubit, - ctrl_state: str = None, -): - """ - Apply multi-controlled SU(2) gate [[z*, x],[-x, z]] with x real and z complex. - https://arxiv.org/abs/2302.06377 - - Args: - circuit (QuantumCircuit): The QuantumCircuit object to apply the diagonal operator on. - x (float): real parameter for the single-qubit operators - z (complex): complex parameter for the single-qubit operators - controls (QuantumRegister or list(Qubit)): The list of control qubits - target (Qubit): The target qubit - ctrl_state (str): control state of the operator SU(2) operator U - """ - - alpha_r = np.sqrt((np.sqrt((z.real + 1.0) / 2.0) + 1.0) / 2.0) - alpha_i = z.imag / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0))) - alpha = alpha_r + 1.0j * alpha_i - beta = x / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0))) - - s_op = np.array([[alpha, -np.conj(beta)], [beta, np.conj(alpha)]]) - - # S gate definition - s_gate = QuantumCircuit(1) - s_gate.unitary(s_op, 0) - s_gate = s_gate.to_gate() - - num_ctrl = len(controls) - k_1 = int(np.ceil(num_ctrl / 2.0)) - k_2 = int(np.floor(num_ctrl / 2.0)) - - ctrl_state_k_1 = None - ctrl_state_k_2 = None - - if ctrl_state is not None: - str_ctrl_state = f"{ctrl_state:0{num_ctrl}b}" - ctrl_state_k_1 = str_ctrl_state[::-1][:k_1][::-1] - ctrl_state_k_2 = str_ctrl_state[::-1][k_1:][::-1] - - mcx_1 = MCXVChain(num_ctrl_qubits=k_1, dirty_ancillas=True, ctrl_state=ctrl_state_k_1) - circuit.append(mcx_1, controls[:k_1] + [target] + controls[k_1 : 2 * k_1 - 2]) - circuit.append(s_gate, [target]) - - mcx_2 = MCXVChain( - num_ctrl_qubits=k_2, - dirty_ancillas=True, - ctrl_state=ctrl_state_k_2, - # action_only=general_su2_optimization # Requires PR #9687 - ) - circuit.append(mcx_2.inverse(), controls[k_1:] + [target] + controls[k_1 - k_2 + 2 : k_1]) - circuit.append(s_gate.inverse(), [target]) - - mcx_3 = MCXVChain(num_ctrl_qubits=k_1, dirty_ancillas=True, ctrl_state=ctrl_state_k_1) - circuit.append(mcx_3, controls[:k_1] + [target] + controls[k_1 : 2 * k_1 - 2]) - circuit.append(s_gate, [target]) - - mcx_4 = MCXVChain(num_ctrl_qubits=k_2, dirty_ancillas=True, ctrl_state=ctrl_state_k_2) - circuit.append(mcx_4, controls[k_1:] + [target] + controls[k_1 - k_2 + 2 : k_1]) - circuit.append(s_gate.inverse(), [target]) diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index c671a9b5117a..826f2421ebd3 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -16,7 +16,7 @@ import unittest from test import combine import numpy as np -from numpy import pi +from numpy import pi, sin, cos from ddt import ddt, data, unpack from qiskit import QuantumRegister, QuantumCircuit, execute, BasicAer, QiskitError @@ -76,6 +76,7 @@ from qiskit.circuit._utils import _compute_control_matrix import qiskit.circuit.library.standard_gates as allGates from qiskit.extensions import UnitaryGate +from qiskit.circuit.library.standard_gates.multi_control_rotation_gates import linear_depth_mcv from .gate_utils import _get_free_params @@ -577,6 +578,18 @@ def test_multi_control_toffoli_matrix_noancilla_dirty_ancillas(self, num_control expected = _compute_control_matrix(base, num_controls) self.assertTrue(matrix_equal(simulated, expected, atol=1e-8)) + def test_linear_depth_mcv(self): + """Test linear_depth_mcv""" + num_ctrls = 6 + theta = 0.3 + qc = QuantumCircuit(num_ctrls + 1) + x = -sin(theta / 2) + z = cos(theta / 2) + linear_depth_mcv(qc, x, z, list(range(num_ctrls)), num_ctrls) + ry_matrix = RYGate(theta).to_matrix() + mcry_matrix = _compute_control_matrix(ry_matrix, 6) + self.assertTrue(np.allclose(mcry_matrix, Operator(qc).to_matrix())) + @combine(num_controls=[1, 2, 4], base_gate_name=["x", "y", "z"], use_basis_gates=[True, False]) def test_multi_controlled_rotation_gate_matrices( self, num_controls, base_gate_name, use_basis_gates From 1fe9ce5f5725d9ea8a2281d54d6579dbe484752b Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Wed, 15 Mar 2023 07:52:56 -0300 Subject: [PATCH 30/42] release notes --- releasenotes/notes/su2-synthesis-295ebf03d9033263.yaml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 releasenotes/notes/su2-synthesis-295ebf03d9033263.yaml diff --git a/releasenotes/notes/su2-synthesis-295ebf03d9033263.yaml b/releasenotes/notes/su2-synthesis-295ebf03d9033263.yaml new file mode 100644 index 000000000000..f7c40024feb4 --- /dev/null +++ b/releasenotes/notes/su2-synthesis-295ebf03d9033263.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + The new function `linear_depth_mcv(circuit, x, z, controls, target, ctrl_state)` + applies a multi-controlled SU(2) gate [[z*, x],[-x, z]] with x real and z + complex. With n-1 controls, the decomposition requires approximately 16n cx gates. + Synthesis of mcrx and mcry without auxiliary now uses the `linear_depth_mcv` decomposition. From de3d840e262ddb316626aa43cdbb4c0a4025ce24 Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Wed, 15 Mar 2023 19:39:37 -0300 Subject: [PATCH 31/42] review 2 --- .../multi_control_rotation_gates.py | 11 ++++++++++- test/python/circuit/test_circuit_operations.py | 2 +- test/python/circuit/test_controlled_gate.py | 17 ++++------------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py index 97e687567de2..efd605617c3c 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py +++ b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py @@ -101,11 +101,20 @@ def linear_depth_mcv( controls (QuantumRegister or list(Qubit)): The list of control qubits target (Qubit): The target qubit ctrl_state (str): control state of the operator SU(2) operator U + + Raises: + QiskitError: parameter errors """ # pylint: disable=cyclic-import from qiskit.circuit.library import MCXVChain from qiskit.extensions import UnitaryGate + if np.iscomplex(x): + raise QiskitError("Parameter x in function linear_depth_mcv must be real.") + + if not np.allclose(np.abs(z) ** 2 + x**2, 1): + raise QiskitError("Matrix [[z*, x],[-x, z]] in function linear_depth_mcv must be SU(2)") + alpha_r = np.sqrt((np.sqrt((z.real + 1.0) / 2.0) + 1.0) / 2.0) alpha_i = z.imag / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0))) alpha = alpha_r + 1.0j * alpha_i @@ -202,7 +211,7 @@ def mcry( q_controls: Union[QuantumRegister, List[Qubit]], q_target: Qubit, q_ancillae: Optional[Union[QuantumRegister, Tuple[QuantumRegister, int]]] = None, - mode: str = None, + mode: str = "noancilla", use_basis_gates=False, ): """ diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py index 9ed04a9dc98c..2612f2cfb8c9 100644 --- a/test/python/circuit/test_circuit_operations.py +++ b/test/python/circuit/test_circuit_operations.py @@ -828,7 +828,7 @@ def test_control_implementation(self): ref = QuantumCircuit(*c_qc.qregs) ref.append(cgate, ref.qubits) - self.assertTrue(np.allclose(Operator(ref).to_matrix(), Operator(c_qc).to_matrix())) + self.assertEqual(ref, c_qc) @data("gate", "instruction") def test_repeat_appended_type(self, subtype): diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index 826f2421ebd3..6d917d5d2a99 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -706,7 +706,7 @@ def test_mcry_defaults_to_vchain(self): control_qubits = circuit.qubits[:3] target_qubit = circuit.qubits[3] additional_qubits = circuit.qubits[4:] - circuit.mcry(0.2, control_qubits, target_qubit, additional_qubits) + circuit.mcry(0.2, control_qubits, target_qubit, additional_qubits, mode=None) # If the v-chain mode is selected, all qubits are used. If the noancilla mode would be # selected, the bottom qubit would remain unused. @@ -1016,18 +1016,9 @@ def test_all_inverses(self, gate, num_ctrl_qubits, ctrl_state): args = [2] * numargs gate = gate(*args) - gate_inverse_control = gate.inverse().control( - num_ctrl_qubits, ctrl_state=ctrl_state - ) - gate_control_inverse = gate.control( - num_ctrl_qubits, ctrl_state=ctrl_state - ).inverse() - - self.assertTrue( - np.allclose( - Operator(gate_inverse_control).to_matrix(), - Operator(gate_control_inverse).to_matrix(), - ) + self.assertEqual( + gate.inverse().control(num_ctrl_qubits, ctrl_state=ctrl_state), + gate.control(num_ctrl_qubits, ctrl_state=ctrl_state).inverse(), ) except AttributeError: From e39de45c103a8be5af969a74281f9be9bf8cedef Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Wed, 15 Mar 2023 20:18:19 -0300 Subject: [PATCH 32/42] backwards compat --- .../library/standard_gates/multi_control_rotation_gates.py | 2 +- test/python/circuit/test_controlled_gate.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py index efd605617c3c..a73dd6db2cc8 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py +++ b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py @@ -211,7 +211,7 @@ def mcry( q_controls: Union[QuantumRegister, List[Qubit]], q_target: Qubit, q_ancillae: Optional[Union[QuantumRegister, Tuple[QuantumRegister, int]]] = None, - mode: str = "noancilla", + mode: str = None, use_basis_gates=False, ): """ diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index 6d917d5d2a99..f7c9b225f68d 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -706,7 +706,7 @@ def test_mcry_defaults_to_vchain(self): control_qubits = circuit.qubits[:3] target_qubit = circuit.qubits[3] additional_qubits = circuit.qubits[4:] - circuit.mcry(0.2, control_qubits, target_qubit, additional_qubits, mode=None) + circuit.mcry(0.2, control_qubits, target_qubit, additional_qubits) # If the v-chain mode is selected, all qubits are used. If the noancilla mode would be # selected, the bottom qubit would remain unused. From 4e4f14d93414bb670194f36564e9b9a6cf8f4c5b Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Fri, 17 Mar 2023 20:20:11 -0300 Subject: [PATCH 33/42] function signature and number of controls --- .../multi_control_rotation_gates.py | 86 ++++++++++++++----- .../notes/su2-synthesis-295ebf03d9033263.yaml | 8 +- test/python/circuit/test_controlled_gate.py | 11 ++- 3 files changed, 72 insertions(+), 33 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py index a73dd6db2cc8..e0bcf587e427 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py +++ b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py @@ -13,9 +13,10 @@ Multiple-Controlled U3 gate. Not using ancillary qubits. """ -from math import pi, sin, cos +from math import pi from typing import Optional, Union, Tuple, List import numpy as np + from qiskit.circuit import QuantumCircuit, QuantumRegister, Qubit from qiskit.circuit.library.standard_gates.x import MCXGate from qiskit.circuit.library.standard_gates.u3 import _generate_gray_code @@ -82,25 +83,23 @@ def _apply_mcu_graycode(circuit, theta, phi, lam, ctls, tgt, use_basis_gates): last_pattern = pattern -def linear_depth_mcv( +def mcsu2_real_diagonal( circuit, - x, - z, + unitary: np.ndarray, controls: Union[QuantumRegister, List[Qubit]], - target: Qubit, + target: Union[Qubit, int], ctrl_state: str = None, ): """ - Apply multi-controlled SU(2) gate [[z*, x],[-x, z]] with x real and z complex. + Apply multi-controlled SU(2) gate with one real diagonal. https://arxiv.org/abs/2302.06377 Args: circuit (QuantumCircuit): The QuantumCircuit object to apply the diagonal operator on. - x (float): real parameter for the single-qubit operators - z (complex): complex parameter for the single-qubit operators + unitary (ndarray): SU(2) unitary matrix with one real diagonal controls (QuantumRegister or list(Qubit)): The list of control qubits - target (Qubit): The target qubit - ctrl_state (str): control state of the operator SU(2) operator U + target (Qubit or int): The target qubit + ctrl_state (str): control state of the operator SU(2) operator Raises: QiskitError: parameter errors @@ -108,12 +107,28 @@ def linear_depth_mcv( # pylint: disable=cyclic-import from qiskit.circuit.library import MCXVChain from qiskit.extensions import UnitaryGate + from qiskit.quantum_info.operators.predicates import is_unitary_matrix + + if not is_unitary_matrix(unitary): + raise QiskitError("parameter unitary in linear_depth_mcv must be an unitary matrix") + + if unitary.shape != (2, 2): + raise QiskitError("parameter unitary in linear_depth_mcv must be a 2x2 matrix") + + is_main_diag_real = np.isclose(unitary[0, 0].imag, 0.0) and np.isclose(unitary[1, 1].imag, 0.0) + is_secondary_diag_real = np.isclose(unitary[0, 1].imag, 0.0) and np.isclose( + unitary[1, 0].imag, 0.0 + ) - if np.iscomplex(x): - raise QiskitError("Parameter x in function linear_depth_mcv must be real.") + if not is_main_diag_real and not is_secondary_diag_real: + raise QiskitError("parameter unitary in linear_depth_mcv must have one real diagonal") - if not np.allclose(np.abs(z) ** 2 + x**2, 1): - raise QiskitError("Matrix [[z*, x],[-x, z]] in function linear_depth_mcv must be SU(2)") + if is_secondary_diag_real: + x = unitary[0, 1] + z = unitary[1, 1] + else: + x = -unitary[0, 1].real + z = unitary[1, 1] - unitary[0, 1].imag * 1.0j alpha_r = np.sqrt((np.sqrt((z.real + 1.0) / 2.0) + 1.0) / 2.0) alpha_i = z.imag / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0))) @@ -136,6 +151,9 @@ def linear_depth_mcv( ctrl_state_k_1 = str_ctrl_state[::-1][:k_1][::-1] ctrl_state_k_2 = str_ctrl_state[::-1][k_1:][::-1] + if not is_secondary_diag_real: + circuit.h(target) + mcx_1 = MCXVChain(num_ctrl_qubits=k_1, dirty_ancillas=True, ctrl_state=ctrl_state_k_1) circuit.append(mcx_1, controls[:k_1] + [target] + controls[k_1 : 2 * k_1 - 2]) circuit.append(s_gate, [target]) @@ -157,6 +175,9 @@ def linear_depth_mcv( circuit.append(mcx_4, controls[k_1:] + [target] + controls[k_1 - k_2 + 2 : k_1]) circuit.append(s_gate.inverse(), [target]) + if not is_secondary_diag_real: + circuit.h(target) + def mcrx( self, @@ -178,6 +199,8 @@ def mcrx( Raises: QiskitError: parameter errors """ + from .rx import RXGate + control_qubits = self.qbit_argument_conversion(q_controls) target_qubit = self.qbit_argument_conversion(q_target) if len(target_qubit) != 1: @@ -197,12 +220,19 @@ def mcrx( target_qubit, use_basis_gates=use_basis_gates, ) + elif n_c < 4: + theta_step = theta * (1 / (2 ** (n_c - 1))) + _apply_mcu_graycode( + self, + theta_step, + -pi / 2, + pi / 2, + control_qubits, + target_qubit, + use_basis_gates=use_basis_gates, + ) else: - x = 0 - z = cos(theta / 2) + sin(theta / 2) * 1.0j - self.h(target_qubit) - linear_depth_mcv(self, x, z, control_qubits, target_qubit) - self.h(target_qubit) + mcsu2_real_diagonal(self, RXGate(theta).to_matrix(), control_qubits, target_qubit) def mcry( @@ -229,6 +259,8 @@ def mcry( Raises: QiskitError: parameter errors """ + from .ry import RYGate + control_qubits = self.qbit_argument_conversion(q_controls) target_qubit = self.qbit_argument_conversion(q_target) if len(target_qubit) != 1: @@ -258,11 +290,19 @@ def mcry( _apply_cu( self, theta, 0, 0, control_qubits[0], target_qubit, use_basis_gates=use_basis_gates ) + elif n_c < 4: + theta_step = theta * (1 / (2 ** (n_c - 1))) + _apply_mcu_graycode( + self, + theta_step, + 0, + 0, + control_qubits, + target_qubit, + use_basis_gates=use_basis_gates, + ) else: - - x = -sin(theta / 2) - z = cos(theta / 2) - linear_depth_mcv(self, x, z, control_qubits, target_qubit) + mcsu2_real_diagonal(self, RYGate(theta).to_matrix(), control_qubits, target_qubit) else: raise QiskitError(f"Unrecognized mode for building MCRY circuit: {mode}.") diff --git a/releasenotes/notes/su2-synthesis-295ebf03d9033263.yaml b/releasenotes/notes/su2-synthesis-295ebf03d9033263.yaml index f7c40024feb4..e8a2bb138d00 100644 --- a/releasenotes/notes/su2-synthesis-295ebf03d9033263.yaml +++ b/releasenotes/notes/su2-synthesis-295ebf03d9033263.yaml @@ -1,7 +1,7 @@ --- features: - | - The new function `linear_depth_mcv(circuit, x, z, controls, target, ctrl_state)` - applies a multi-controlled SU(2) gate [[z*, x],[-x, z]] with x real and z - complex. With n-1 controls, the decomposition requires approximately 16n cx gates. - Synthesis of mcrx and mcry without auxiliary now uses the `linear_depth_mcv` decomposition. + The new function `mcsu2_real_diagonal(circuit, unitary, controls, target, ctrl_state)` + applies a multi-controlled SU(2) gate unitary with one real diagonal. With n-1 controls, + the decomposition requires approximately 16n cx gates. Synthesis of mcrx and mcry without + auxiliary qubits now uses the `mcsu2_real_diagonal` decomposition. diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index f7c9b225f68d..d341a29403d8 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -16,7 +16,7 @@ import unittest from test import combine import numpy as np -from numpy import pi, sin, cos +from numpy import pi from ddt import ddt, data, unpack from qiskit import QuantumRegister, QuantumCircuit, execute, BasicAer, QiskitError @@ -76,7 +76,7 @@ from qiskit.circuit._utils import _compute_control_matrix import qiskit.circuit.library.standard_gates as allGates from qiskit.extensions import UnitaryGate -from qiskit.circuit.library.standard_gates.multi_control_rotation_gates import linear_depth_mcv +from qiskit.circuit.library.standard_gates.multi_control_rotation_gates import mcsu2_real_diagonal from .gate_utils import _get_free_params @@ -579,14 +579,13 @@ def test_multi_control_toffoli_matrix_noancilla_dirty_ancillas(self, num_control self.assertTrue(matrix_equal(simulated, expected, atol=1e-8)) def test_linear_depth_mcv(self): - """Test linear_depth_mcv""" + """Test mcsu2_real_diagonal""" num_ctrls = 6 theta = 0.3 qc = QuantumCircuit(num_ctrls + 1) - x = -sin(theta / 2) - z = cos(theta / 2) - linear_depth_mcv(qc, x, z, list(range(num_ctrls)), num_ctrls) ry_matrix = RYGate(theta).to_matrix() + mcsu2_real_diagonal(qc, ry_matrix, list(range(num_ctrls)), num_ctrls) + mcry_matrix = _compute_control_matrix(ry_matrix, 6) self.assertTrue(np.allclose(mcry_matrix, Operator(qc).to_matrix())) From ac258059b897fe3a3ba94417e973119af94badfd Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Tue, 21 Mar 2023 23:04:21 -0300 Subject: [PATCH 34/42] fix mcrz --- qiskit/circuit/add_control.py | 14 +++----- .../multi_control_rotation_gates.py | 36 +++++++++---------- test/python/circuit/test_controlled_gate.py | 6 ++-- 3 files changed, 24 insertions(+), 32 deletions(-) diff --git a/qiskit/circuit/add_control.py b/qiskit/circuit/add_control.py index 03a176956840..0ea126cf08ca 100644 --- a/qiskit/circuit/add_control.py +++ b/qiskit/circuit/add_control.py @@ -146,8 +146,8 @@ def control( gate.definition.data[0].operation.params[0], q_control, q_target[bit_indices[qargs[0]]], - use_basis_gates=True, ) + continue elif gate.name == "p": from qiskit.circuit.library import MCPhaseGate @@ -184,13 +184,9 @@ def control( use_basis_gates=True, ) elif theta == 0 and phi == 0: - controlled_circ.mcrz( - lamb, q_control, q_target[bit_indices[qargs[0]]], use_basis_gates=True - ) + controlled_circ.mcp(lamb, q_control, q_target[bit_indices[qargs[0]]]) else: - controlled_circ.mcrz( - lamb, q_control, q_target[bit_indices[qargs[0]]], use_basis_gates=True - ) + controlled_circ.mcp(lamb, q_control, q_target[bit_indices[qargs[0]]]) controlled_circ.mcry( theta, q_control, @@ -198,9 +194,7 @@ def control( q_ancillae, use_basis_gates=True, ) - controlled_circ.mcrz( - phi, q_control, q_target[bit_indices[qargs[0]]], use_basis_gates=True - ) + controlled_circ.mcp(phi, q_control, q_target[bit_indices[qargs[0]]]) elif gate.name == "z": controlled_circ.h(q_target[bit_indices[qargs[0]]]) controlled_circ.mcx(q_control, q_target[bit_indices[qargs[0]]], q_ancillae) diff --git a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py index e0bcf587e427..379afb2b2851 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py +++ b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py @@ -130,13 +130,18 @@ def mcsu2_real_diagonal( x = -unitary[0, 1].real z = unitary[1, 1] - unitary[0, 1].imag * 1.0j - alpha_r = np.sqrt((np.sqrt((z.real + 1.0) / 2.0) + 1.0) / 2.0) - alpha_i = z.imag / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0))) - alpha = alpha_r + 1.0j * alpha_i - beta = x / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0))) + if np.isclose(z, -1): + s_op = [[1.0, 0.0], [0.0, 1.0j]] + else: + alpha_r = np.sqrt((np.sqrt((z.real + 1.0) / 2.0) + 1.0) / 2.0) + alpha_i = z.imag / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0))) + alpha = alpha_r + 1.0j * alpha_i + beta = x / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0))) + + # S gate definition + s_op = np.array([[alpha, -np.conj(beta)], [beta, np.conj(alpha)]]) + print(s_op) - # S gate definition - s_op = np.array([[alpha, -np.conj(beta)], [beta, np.conj(alpha)]]) s_gate = UnitaryGate(s_op) num_ctrl = len(controls) @@ -308,11 +313,7 @@ def mcry( def mcrz( - self, - lam: ParameterValueType, - q_controls: Union[QuantumRegister, List[Qubit]], - q_target: Qubit, - use_basis_gates: bool = False, + self, lam: ParameterValueType, q_controls: Union[QuantumRegister, List[Qubit]], q_target: Qubit ): """ Apply Multiple-Controlled Z rotation gate @@ -322,11 +323,12 @@ def mcrz( lam (float): angle lambda q_controls (list(Qubit)): The list of control qubits q_target (Qubit): The target qubit - use_basis_gates (bool): use p, u, cx Raises: QiskitError: parameter errors """ + from .rz import CRZGate, RZGate + control_qubits = self.qbit_argument_conversion(q_controls) target_qubit = self.qbit_argument_conversion(q_target) if len(target_qubit) != 1: @@ -336,13 +338,11 @@ def mcrz( self._check_dups(all_qubits) n_c = len(control_qubits) - if n_c == 1: # cu - _apply_cu(self, 0, 0, lam, control_qubits[0], target_qubit, use_basis_gates=use_basis_gates) + if n_c == 1: + self.compose(CRZGate(lam), control_qubits + [target_qubit], inplace=True) else: - lam_step = lam * (1 / (2 ** (n_c - 1))) - _apply_mcu_graycode( - self, 0, 0, lam_step, control_qubits, target_qubit, use_basis_gates=use_basis_gates - ) + # self.append(u1_gate.control(n_c), control_qubits + [target_qubit]) + mcsu2_real_diagonal(self, RZGate(lam).to_matrix(), control_qubits, target_qubit) QuantumCircuit.mcrx = mcrx diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index d341a29403d8..99ad673b9d01 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -621,9 +621,7 @@ def test_multi_controlled_rotation_gate_matrices( use_basis_gates=use_basis_gates, ) else: # case 'x' or 'z' only support the noancilla mode and do not have this keyword - getattr(qc, "mcr" + base_gate_name)( - theta, q_controls, q_target[0], use_basis_gates=use_basis_gates - ) + getattr(qc, "mcr" + base_gate_name)(theta, q_controls, q_target[0]) for idx, bit in enumerate(bitstr): if bit == "0": @@ -637,7 +635,7 @@ def test_multi_controlled_rotation_gate_matrices( elif base_gate_name == "y": rot_mat = RYGate(theta).to_matrix() else: # case 'z' - rot_mat = U1Gate(theta).to_matrix() + rot_mat = RZGate(theta).to_matrix() expected = _compute_control_matrix(rot_mat, num_controls, ctrl_state=ctrl_state) with self.subTest(msg=f"control state = {ctrl_state}"): From ab9920fe6b82e16fab881f58fdc7000a7244e8c0 Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Tue, 21 Mar 2023 23:56:18 -0300 Subject: [PATCH 35/42] Update multi_control_rotation_gates.py --- .../library/standard_gates/multi_control_rotation_gates.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py index 379afb2b2851..0f5ef3f49d61 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py +++ b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py @@ -140,7 +140,6 @@ def mcsu2_real_diagonal( # S gate definition s_op = np.array([[alpha, -np.conj(beta)], [beta, np.conj(alpha)]]) - print(s_op) s_gate = UnitaryGate(s_op) From 17ce419d613512deb45f3088ef9ecc4dd7f2c87d Mon Sep 17 00:00:00 2001 From: Adenilton Silva Date: Fri, 21 Apr 2023 10:13:41 -0300 Subject: [PATCH 36/42] review --- qiskit/circuit/add_control.py | 1 + .../multi_control_rotation_gates.py | 32 ++++++++++++------- ...-mcrz-relative-phase-6ea81a369f8bda38.yaml | 7 ++++ test/python/circuit/test_controlled_gate.py | 4 ++- 4 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 releasenotes/notes/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml diff --git a/qiskit/circuit/add_control.py b/qiskit/circuit/add_control.py index a4326633237e..9a9837673d1a 100644 --- a/qiskit/circuit/add_control.py +++ b/qiskit/circuit/add_control.py @@ -146,6 +146,7 @@ def control( gate.definition.data[0].operation.params[0], q_control, q_target[bit_indices[qargs[0]]], + use_basis_gates=True, ) continue elif gate.name == "p": diff --git a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py index 063673222dd3..23cfe200497b 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py +++ b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py @@ -84,28 +84,28 @@ def _apply_mcu_graycode(circuit, theta, phi, lam, ctls, tgt, use_basis_gates): def mcsu2_real_diagonal( - circuit, + circuit: QuantumCircuit, unitary: np.ndarray, controls: Union[QuantumRegister, List[Qubit]], target: Union[Qubit, int], - ctrl_state: str = None, + ctrl_state: Optional[str] = None, ): """ Apply multi-controlled SU(2) gate with a real main diagonal or secondary diagonal. https://arxiv.org/abs/2302.06377 Args: - circuit (QuantumCircuit): The QuantumCircuit object to apply the diagonal operator on. - unitary (ndarray): SU(2) unitary matrix with one real diagonal - controls (QuantumRegister or list(Qubit)): The list of control qubits - target (Qubit or int): The target qubit - ctrl_state (str): control state of the operator SU(2) operator + circuit: The QuantumCircuit object to apply the diagonal operator on. + unitary: SU(2) unitary matrix with one real diagonal + controls: The list of control qubits + target: The target qubit + ctrl_state: control state of the operator SU(2) operator Raises: QiskitError: parameter errors """ # pylint: disable=cyclic-import - from qiskit.circuit.library import MCXVChain + from .x import MCXVChain from qiskit.extensions import UnitaryGate from qiskit.quantum_info.operators.predicates import is_unitary_matrix @@ -312,7 +312,11 @@ def mcry( def mcrz( - self, lam: ParameterValueType, q_controls: Union[QuantumRegister, List[Qubit]], q_target: Qubit + self, + lam: ParameterValueType, + q_controls: Union[QuantumRegister, List[Qubit]], + q_target: Qubit, + use_basis_gates: bool = False, ): """ Apply Multiple-Controlled Z rotation gate @@ -322,6 +326,7 @@ def mcrz( lam (float): angle lambda q_controls (list(Qubit)): The list of control qubits q_target (Qubit): The target qubit + use_basis_gates (bool): use p, u, cx Raises: QiskitError: parameter errors @@ -338,11 +343,16 @@ def mcrz( n_c = len(control_qubits) if n_c == 1: - self.compose(CRZGate(lam), control_qubits + [target_qubit], inplace=True) + self.append(CRZGate(lam), control_qubits + [target_qubit]) else: - # self.append(u1_gate.control(n_c), control_qubits + [target_qubit]) mcsu2_real_diagonal(self, RZGate(lam).to_matrix(), control_qubits, target_qubit) + if use_basis_gates: + # pylint: disable=cyclic-import + from qiskit import transpile + + self = transpile(self, basis_gates=["p", "u", "cx"], optimization_level=0) + QuantumCircuit.mcrx = mcrx QuantumCircuit.mcry = mcry diff --git a/releasenotes/notes/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml b/releasenotes/notes/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml new file mode 100644 index 000000000000..aa89abeb662d --- /dev/null +++ b/releasenotes/notes/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixed the gate decomposition of multi-controlled Z rotation gates added via + :meth:`.QuantumCircuit.mcrz`. Previously, this method implemented a multi-controlled + phase gate, which has a relative phase difference to the Z rotation. To obtain the + previous `.QuantumCircuit.mcrz` behaviour, use `.QuantumCircuit.mcp`. diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index 476e584ef267..5921d72643b6 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -651,7 +651,9 @@ def test_multi_controlled_rotation_gate_matrices( use_basis_gates=use_basis_gates, ) else: # case 'x' or 'z' only support the noancilla mode and do not have this keyword - getattr(qc, "mcr" + base_gate_name)(theta, q_controls, q_target[0]) + getattr(qc, "mcr" + base_gate_name)( + theta, q_controls, q_target[0], use_basis_gates=use_basis_gates + ) for idx, bit in enumerate(bitstr): if bit == "0": From fab1c80ab44be86812fb2b95b538918400966f7e Mon Sep 17 00:00:00 2001 From: Adenilton Silva Date: Fri, 21 Apr 2023 10:13:45 -0300 Subject: [PATCH 37/42] Update test_qpy.py --- test/qpy_compat/test_qpy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py index ef01d3873e39..2270dc11f94f 100755 --- a/test/qpy_compat/test_qpy.py +++ b/test/qpy_compat/test_qpy.py @@ -535,7 +535,7 @@ def generate_controlled_gates(): custom_gate = Gate("black_box", 1, []) custom_definition = QuantumCircuit(1) custom_definition.h(0) - custom_definition.rz(1.5, 0) + custom_definition.p(1.5, 0) custom_definition.sdg(0) custom_gate.definition = custom_definition nested_qc = QuantumCircuit(3, name="nested_qc") @@ -561,7 +561,7 @@ def generate_open_controlled_gates(): custom_gate = Gate("black_box", 1, []) custom_definition = QuantumCircuit(1) custom_definition.h(0) - custom_definition.rz(1.5, 0) + custom_definition.p(1.5, 0) custom_definition.sdg(0) custom_gate.definition = custom_definition nested_qc = QuantumCircuit(3, name="open_controls_nested") From 3aedea6e17fd6fa7cf35e005ca63ddc966eb8c4a Mon Sep 17 00:00:00 2001 From: Adenilton Silva Date: Fri, 21 Apr 2023 14:44:22 -0300 Subject: [PATCH 38/42] Revert "Update test_qpy.py" This reverts commit fab1c80ab44be86812fb2b95b538918400966f7e. --- test/qpy_compat/test_qpy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py index 2270dc11f94f..ef01d3873e39 100755 --- a/test/qpy_compat/test_qpy.py +++ b/test/qpy_compat/test_qpy.py @@ -535,7 +535,7 @@ def generate_controlled_gates(): custom_gate = Gate("black_box", 1, []) custom_definition = QuantumCircuit(1) custom_definition.h(0) - custom_definition.p(1.5, 0) + custom_definition.rz(1.5, 0) custom_definition.sdg(0) custom_gate.definition = custom_definition nested_qc = QuantumCircuit(3, name="nested_qc") @@ -561,7 +561,7 @@ def generate_open_controlled_gates(): custom_gate = Gate("black_box", 1, []) custom_definition = QuantumCircuit(1) custom_definition.h(0) - custom_definition.p(1.5, 0) + custom_definition.rz(1.5, 0) custom_definition.sdg(0) custom_gate.definition = custom_definition nested_qc = QuantumCircuit(3, name="open_controls_nested") From fd14c8717465a1e0ec433057a5b8db2da307a110 Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Wed, 3 May 2023 15:11:10 -0300 Subject: [PATCH 39/42] Update test_qpy.py --- test/qpy_compat/test_qpy.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py index ef01d3873e39..2a4b112e1508 100755 --- a/test/qpy_compat/test_qpy.py +++ b/test/qpy_compat/test_qpy.py @@ -603,15 +603,14 @@ def generate_circuits(version_parts): if version_parts >= (0, 19, 2): output_circuits["control_flow.qpy"] = generate_control_flow_circuits() if version_parts >= (0, 21, 0): - output_circuits["controlled_gates.qpy"] = generate_controlled_gates() output_circuits["schedule_blocks.qpy"] = generate_schedule_blocks() output_circuits["pulse_gates.qpy"] = generate_calibrated_circuits() - if version_parts >= (0, 21, 2): - output_circuits["open_controlled_gates.qpy"] = generate_open_controlled_gates() if version_parts >= (0, 24, 0): output_circuits["referenced_schedule_blocks.qpy"] = generate_referenced_schedule() output_circuits["control_flow_switch.qpy"] = generate_control_flow_switch_circuits() - + if version_parts >= (0, 25, 0): + output_circuits["open_controlled_gates.qpy"] = generate_open_controlled_gates() + output_circuits["controlled_gates.qpy"] = generate_controlled_gates() return output_circuits From 7a9df501ad8fd3d47370b6334c159929442c29c2 Mon Sep 17 00:00:00 2001 From: Adenilton Silva <7927558+adjs@users.noreply.github.com> Date: Thu, 4 May 2023 17:00:18 -0300 Subject: [PATCH 40/42] Update multi_control_rotation_gates.py --- .../library/standard_gates/multi_control_rotation_gates.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py index 926d77a6025f..ee2327b7be1f 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py +++ b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py @@ -348,10 +348,7 @@ def mcrz( _mcsu2_real_diagonal(self, RZGate(lam).to_matrix(), control_qubits, target_qubit) if use_basis_gates: - # pylint: disable=cyclic-import - from qiskit import transpile - - self = transpile(self, basis_gates=["p", "u", "cx"], optimization_level=0) + pass QuantumCircuit.mcrx = mcrx From 9269ecf975ab0ac0ce9bfe76d0b28fe8b66f464c Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Tue, 16 May 2023 15:53:11 +0200 Subject: [PATCH 41/42] Fix `use_basis_gates=True` case --- .../multi_control_rotation_gates.py | 90 +++++++++++++------ test/python/circuit/test_controlled_gate.py | 8 +- 2 files changed, 68 insertions(+), 30 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py index ee2327b7be1f..dc96d7543a33 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py +++ b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py @@ -84,36 +84,44 @@ def _apply_mcu_graycode(circuit, theta, phi, lam, ctls, tgt, use_basis_gates): def _mcsu2_real_diagonal( - circuit, unitary: np.ndarray, - controls: Union[QuantumRegister, List[Qubit]], - target: Union[Qubit, int], + num_controls: int, ctrl_state: Optional[str] = None, -): + use_basis_gates: bool = False, +) -> QuantumCircuit: """ - Apply multi-controlled SU(2) gate with a real main diagonal or secondary diagonal. - https://arxiv.org/abs/2302.06377 + Return a multi-controlled SU(2) gate [1]_ with a real main diagonal or secondary diagonal. Args: - circuit: The QuantumCircuit object to apply the diagonal operator on. - unitary: SU(2) unitary matrix with one real diagonal - controls: The list of control qubits - target: The target qubit - ctrl_state: control state of the operator SU(2) operator + unitary: SU(2) unitary matrix with one real diagonal. + num_controls: The number of control qubits. + ctrl_state: The state on which the SU(2) operation is controlled. Defaults to all + control qubits being in state 1. + use_basis_gates: If ``True``, use ``[p, u, cx]`` gates to implement the decomposition. + + Returns: + A :class:`.QuantumCircuit` implementing the multi-controlled SU(2) gate. Raises: - QiskitError: parameter errors + QiskitError: If the input matrix is invalid. + + References: + + .. [1]: R. Vale et al. Decomposition of Multi-controlled Special Unitary Single-Qubit Gates + `arXiv:2302.06377 (2023) `__ + """ # pylint: disable=cyclic-import from .x import MCXVChain from qiskit.extensions import UnitaryGate from qiskit.quantum_info.operators.predicates import is_unitary_matrix - - if not is_unitary_matrix(unitary): - raise QiskitError("parameter unitary in mcsu2_real_diagonal must be an unitary matrix") + from qiskit.compiler import transpile if unitary.shape != (2, 2): - raise QiskitError("parameter unitary in mcsu2_real_diagonal must be a 2x2 matrix") + raise QiskitError(f"The unitary must be a 2x2 matrix, but has shape {unitary.shape}.") + + if not is_unitary_matrix(unitary): + raise QiskitError(f"The unitary in must be an unitary matrix, but is {unitary}.") is_main_diag_real = np.isclose(unitary[0, 0].imag, 0.0) and np.isclose(unitary[1, 1].imag, 0.0) is_secondary_diag_real = np.isclose(unitary[0, 1].imag, 0.0) and np.isclose( @@ -121,7 +129,7 @@ def _mcsu2_real_diagonal( ) if not is_main_diag_real and not is_secondary_diag_real: - raise QiskitError("parameter unitary in mcsu2_real_diagonal must have one real diagonal") + raise QiskitError("The unitary must have one real diagonal.") if is_secondary_diag_real: x = unitary[0, 1] @@ -143,18 +151,21 @@ def _mcsu2_real_diagonal( s_gate = UnitaryGate(s_op) - num_ctrl = len(controls) - k_1 = int(np.ceil(num_ctrl / 2.0)) - k_2 = int(np.floor(num_ctrl / 2.0)) + k_1 = int(np.ceil(num_controls / 2.0)) + k_2 = int(np.floor(num_controls / 2.0)) ctrl_state_k_1 = None ctrl_state_k_2 = None if ctrl_state is not None: - str_ctrl_state = f"{ctrl_state:0{num_ctrl}b}" + str_ctrl_state = f"{ctrl_state:0{num_controls}b}" ctrl_state_k_1 = str_ctrl_state[::-1][:k_1][::-1] ctrl_state_k_2 = str_ctrl_state[::-1][k_1:][::-1] + circuit = QuantumCircuit(num_controls + 1, name="MCSU2") + controls = list(range(num_controls)) # control indices, defined for code legibility + target = num_controls # target index, defined for code legibility + if not is_secondary_diag_real: circuit.h(target) @@ -182,6 +193,11 @@ def _mcsu2_real_diagonal( if not is_secondary_diag_real: circuit.h(target) + if use_basis_gates: + circuit = transpile(circuit, basis_gates=["p", "u", "cx"]) + + return circuit + def mcrx( self, @@ -236,7 +252,12 @@ def mcrx( use_basis_gates=use_basis_gates, ) else: - _mcsu2_real_diagonal(self, RXGate(theta).to_matrix(), control_qubits, target_qubit) + mcrx = _mcsu2_real_diagonal( + RXGate(theta).to_matrix(), + num_controls=len(control_qubits), + use_basis_gates=use_basis_gates, + ) + self.compose(mcrx, control_qubits + [target_qubit], inplace=True) def mcry( @@ -306,7 +327,12 @@ def mcry( use_basis_gates=use_basis_gates, ) else: - _mcsu2_real_diagonal(self, RYGate(theta).to_matrix(), control_qubits, target_qubit) + mcry = _mcsu2_real_diagonal( + RYGate(theta).to_matrix(), + num_controls=len(control_qubits), + use_basis_gates=use_basis_gates, + ) + self.compose(mcry, control_qubits + [target_qubit], inplace=True) else: raise QiskitError(f"Unrecognized mode for building MCRY circuit: {mode}.") @@ -343,12 +369,20 @@ def mcrz( n_c = len(control_qubits) if n_c == 1: - self.append(CRZGate(lam), control_qubits + [target_qubit]) + if use_basis_gates: + self.u(0, 0, lam / 2, target_qubit) + self.cx(control_qubits[0], target_qubit) + self.u(0, 0, -lam / 2, target_qubit) + self.cx(control_qubits[0], target_qubit) + else: + self.append(CRZGate(lam), control_qubits + [target_qubit]) else: - _mcsu2_real_diagonal(self, RZGate(lam).to_matrix(), control_qubits, target_qubit) - - if use_basis_gates: - pass + mcrz = _mcsu2_real_diagonal( + RZGate(lam).to_matrix(), + num_controls=len(control_qubits), + use_basis_gates=use_basis_gates, + ) + self.compose(mcrz, control_qubits + [target_qubit], inplace=True) QuantumCircuit.mcrx = mcrx diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index 899d035c6557..248aabab7839 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -610,9 +610,8 @@ def test_mcsu2_real_diagonal(self): """Test mcsu2_real_diagonal""" num_ctrls = 6 theta = 0.3 - qc = QuantumCircuit(num_ctrls + 1) ry_matrix = RYGate(theta).to_matrix() - _mcsu2_real_diagonal(qc, ry_matrix, list(range(num_ctrls)), num_ctrls) + qc = _mcsu2_real_diagonal(ry_matrix, num_ctrls) mcry_matrix = _compute_control_matrix(ry_matrix, 6) self.assertTrue(np.allclose(mcry_matrix, Operator(qc).to_matrix())) @@ -657,6 +656,11 @@ def test_multi_controlled_rotation_gate_matrices( if bit == "0": qc.x(q_controls[idx]) + if use_basis_gates: + with self.subTest(msg="check only basis gates used"): + gates_used = set(qc.count_ops().keys()) + self.assertTrue(gates_used.issubset({"x", "u", "p", "cx"})) + backend = BasicAer.get_backend("unitary_simulator") simulated = execute(qc, backend).result().get_unitary(qc) From fb180f44295431dc176e905c25eb881f52d3158c Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Tue, 16 May 2023 16:29:11 +0200 Subject: [PATCH 42/42] lint --- .../standard_gates/multi_control_rotation_gates.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py index dc96d7543a33..ce7f7861440b 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py +++ b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py @@ -252,12 +252,12 @@ def mcrx( use_basis_gates=use_basis_gates, ) else: - mcrx = _mcsu2_real_diagonal( + cgate = _mcsu2_real_diagonal( RXGate(theta).to_matrix(), num_controls=len(control_qubits), use_basis_gates=use_basis_gates, ) - self.compose(mcrx, control_qubits + [target_qubit], inplace=True) + self.compose(cgate, control_qubits + [target_qubit], inplace=True) def mcry( @@ -327,12 +327,12 @@ def mcry( use_basis_gates=use_basis_gates, ) else: - mcry = _mcsu2_real_diagonal( + cgate = _mcsu2_real_diagonal( RYGate(theta).to_matrix(), num_controls=len(control_qubits), use_basis_gates=use_basis_gates, ) - self.compose(mcry, control_qubits + [target_qubit], inplace=True) + self.compose(cgate, control_qubits + [target_qubit], inplace=True) else: raise QiskitError(f"Unrecognized mode for building MCRY circuit: {mode}.") @@ -377,12 +377,12 @@ def mcrz( else: self.append(CRZGate(lam), control_qubits + [target_qubit]) else: - mcrz = _mcsu2_real_diagonal( + cgate = _mcsu2_real_diagonal( RZGate(lam).to_matrix(), num_controls=len(control_qubits), use_basis_gates=use_basis_gates, ) - self.compose(mcrz, control_qubits + [target_qubit], inplace=True) + self.compose(cgate, control_qubits + [target_qubit], inplace=True) QuantumCircuit.mcrx = mcrx