diff --git a/qiskit/circuit/library/generalized_gates/__init__.py b/qiskit/circuit/library/generalized_gates/__init__.py index 43c0b4c36e5a..0ed047050fb0 100644 --- a/qiskit/circuit/library/generalized_gates/__init__.py +++ b/qiskit/circuit/library/generalized_gates/__init__.py @@ -20,3 +20,4 @@ from .pauli import PauliGate from .rv import RVGate from .linear_function import LinearFunction +from .multicontrol_arb_gate import MCU2Gate diff --git a/qiskit/circuit/library/generalized_gates/multicontrol_arb_gate.py b/qiskit/circuit/library/generalized_gates/multicontrol_arb_gate.py new file mode 100644 index 000000000000..cdef3f0212d2 --- /dev/null +++ b/qiskit/circuit/library/generalized_gates/multicontrol_arb_gate.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. + +"""multicontrol single qubit unitary gate.""" + +from typing import Union, List, Optional +import numpy +from scipy.linalg import fractional_matrix_power +from qiskit.circuit import QuantumCircuit, QuantumRegister +from qiskit.circuit.controlledgate import ControlledGate +from qiskit.exceptions import QiskitError +from qiskit.circuit._utils import _ctrl_state_to_int, _compute_control_matrix +from qiskit.extensions.quantum_initializer.squ import SingleQubitUnitary + + +class MCU2Gate(ControlledGate): + """ + Gate to implement a multicontrol version of any single qubit unitary matrix (gate) + via approach proposed in https://doi.org/10.1103/PhysRevA.106.042602. The decomposition has + a quadratic CNOT gate count in the number of control qubits + + Args: + u2_matrix (numpy.array): two by two unitary matrix to implement as a control operation + num_ctrl_qubits (int): number of control qubits + ctrl_state (optional): control state as string or integer + label (optional): string label for single qubit gate + + """ + + def __init__( + self, + u2_matrix: Union[numpy.array, List[List[int]]], + num_ctrl_qubits: int, + ctrl_state: Optional[Union[str, int]] = None, + label: Optional[str] = "U(2)", + ): + u2_matrix = numpy.asarray(u2_matrix) + if u2_matrix.shape != (2, 2): + raise QiskitError( + "The dimension of the input matrix is not equal to (2,2). \n " + str(u2_matrix) + ) + if not self._check_unitary(u2_matrix): + raise QiskitError("Single qubit input matrix is not unitary.") + + self.u2_matrix = u2_matrix + self.base_gate = SingleQubitUnitary(u2_matrix) + self.base_gate.label = label + self._num_qubits = num_ctrl_qubits + 1 + self.num_ctrl_qubits = num_ctrl_qubits + + cntrl_int = _ctrl_state_to_int(ctrl_state, self.num_ctrl_qubits) + cntrl_str = numpy.binary_repr(cntrl_int, width=self.num_ctrl_qubits) + self.ctrl_state = cntrl_str[::-1] + + super().__init__( + name=self.base_gate.label, + num_qubits=self._num_qubits, + params=[self.u2_matrix], + label=None, + 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) + + cntrl_int = _ctrl_state_to_int(self.ctrl_state, self.num_ctrl_qubits) + cntrl_str = numpy.binary_repr(cntrl_int, width=self.num_ctrl_qubits) + + control_circ = QuantumCircuit(controls, target) + for q_ind, cntrol_bit in enumerate(cntrl_str): + if cntrol_bit == "0": + control_circ.x(q_ind) + + cntrl_qbits = list(range(self.num_ctrl_qubits)) + target_qbit = self.num_ctrl_qubits + + self.definition = control_circ + + self.definition = self.definition.compose( + multiconrol_single_qubit_gate(self.u2_matrix, cntrl_qbits, target_qbit).decompose() + ).compose(control_circ) + + def inverse(self): + """ + Returns inverted MCU2Gate. + """ + return MCU2Gate( + numpy.linalg.inv(self.u2_matrix), + self.num_ctrl_qubits, + ctrl_state=self.ctrl_state, + label=self.label, + ) + + @staticmethod + def _check_unitary(matrix): + return numpy.allclose(matrix @ matrix.conj().T, numpy.eye(2)) + + def __array__(self, dtype=None): + """ + Return numpy array for gate + """ + mat = _compute_control_matrix( + self.u2_matrix, self.num_ctrl_qubits, ctrl_state=self.ctrl_state + ) + if dtype: + mat = numpy.asarray(mat, dtype=dtype) + return mat + + +def multiconrol_single_qubit_gate( + single_q_unitary: numpy.array, control_list: List[int], target_q: int +) -> QuantumCircuit: + """ + Generate a quantum circuit to implement a defined multicontrol single qubit unitary + via approach proposed in https://doi.org/10.1103/PhysRevA.106.042602 + + Args: + single_q_unitary (numpy.array): two by two unitary matrix to implement as a control operation + control_list (list): list of control qubit indices + target_q (int): target qubit index + Returns: + circuit (QuantumCircuit): quantum circuit implementing multicontrol single_q_unitary + + """ + assert target_q not in control_list, f"target qubit: {target_q} in control list" + + n_qubits = max(*control_list, target_q) + 1 + circuit = QuantumCircuit(n_qubits) + cnu_gate = cn_u(len(control_list), single_q_unitary).to_gate() + circuit.append(cnu_gate, [*control_list, target_q]) + return circuit + + +def cn_u(n_controls: int, single_q_unitary: numpy.array) -> QuantumCircuit: + """ + Implement a multicontrol U gate according to https://doi.org/10.1103/PhysRevA.106.042602 + + Args: + n_controls(int): number of control qubits + single_q_unitary (array): two by two unitary matrix to implement as a control operation + Returns: + circuit (QuantumCircuit): Quantum circuit implementing multicontrol unitary + """ + assert single_q_unitary.shape == (2, 2), "input unitary is not a single qubit gate" + assert numpy.allclose( + single_q_unitary @ single_q_unitary.conj().T, numpy.eye(2) + ), "input unitary is not unitary" + + targ = n_controls + 1 + circuit = QuantumCircuit(targ) + if n_controls == 1: + ucirc = QuantumCircuit(1) + ucirc.unitary(single_q_unitary, 0) + ucirc.name = "U" + ucircgate = ucirc.to_gate().control(1) + circuit.append(ucircgate, [0, n_controls]) + else: + pnu = pnu_gate(n_controls, single_q_unitary) + circuit = circuit.compose(pnu) + + power = n_controls - 1 + rootu = fractional_matrix_power(single_q_unitary, 1 / 2 ** (n_controls - 1)) + rootucirc = QuantumCircuit(1) + rootucirc.unitary(rootu, 0) + rootucirc.name = f"U^1/{2 ** power}" + controlrootugate = rootucirc.to_gate().control(1) + circuit.append(controlrootugate, [0, n_controls]) + + qn = qn_gate(n_controls) + circuit = circuit.compose(qn) + circuit = circuit.compose(pnu.inverse()) + circuit = circuit.compose(qn.inverse()) + + return circuit + + +def pn_gate(n_controls: int) -> QuantumCircuit: + """ + Pn gate defined in equation 1 of https://doi.org/10.1103/PhysRevA.106.042602 + + Args: + n_controls (int): number of controls + Returns: + circuit (QuantumCircuit): quantum circuit of Pn gate + """ + assert n_controls > 0, "number of controls must be 1 or more!" + + # target = n_controls[-1] +1 for now! + circuit = QuantumCircuit(n_controls + 1) + + for k in reversed(range(2, n_controls + 1)): + circuit.crx(numpy.pi / 2 ** (n_controls - k + 1), k - 1, n_controls) + + return circuit + + +def pnu_gate(n_controls: int, single_q_unitary: numpy.array) -> QuantumCircuit: + """ + Pn(U) gate defined in equation 2 of https://doi.org/10.1103/PhysRevA.106.042602 + + Args: + n_controls (int): number of controls + single_q_unitary (numpy.array): two by two unitary matrix to implement as a control operation + Returns: + circuit (QuantumCircuit): quantum circuit of Pn(U) gate + """ + assert n_controls > 0, "number of controls must be 1 or more!" + + # target = n_controls[-1] +1 for now! + circuit = QuantumCircuit(n_controls + 1) + + for k in reversed(range(2, n_controls + 1)): + power = n_controls - k + 1 + root_u = fractional_matrix_power(single_q_unitary, 1 / 2 ** (power)) + + root_u_circ = QuantumCircuit(1) + root_u_circ.unitary(root_u, 0) + root_u_circ.name = f"U^1/{2 ** (power)}" + root_u_circ_gate = root_u_circ.to_gate().control(1) + + circuit.append(root_u_circ_gate, [k - 1, n_controls]) + + return circuit + + +def qn_gate(n_controls: int) -> QuantumCircuit: + """ + Qn gate defined in equation 5 of https://doi.org/10.1103/PhysRevA.106.042602 + + Args: + n_controls (int): number of controls + Returns: + circuit (QuantumCircuit): quantum circuit of Qn gate + """ + assert n_controls > 0, "number of controls must be 1 or more!" + + if n_controls == 2: + circuit = QuantumCircuit(n_controls + 1) + circuit.crx(numpy.pi, 0, 1) + return circuit + + circuit = QuantumCircuit(n_controls + 1) + + for j in reversed(range(2, n_controls)): + circuit = circuit.compose(pn_gate(j)) + circuit.crx(numpy.pi / (2 ** (j - 1)), 0, j) + + # Q2 gate in paper + circuit.crx(numpy.pi, 0, 1) + for k in range(2, n_controls): + circuit = circuit.compose(pn_gate(k).inverse()) + + return circuit 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..fa6ba5e0c7ee 100644 --- a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py +++ b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2019. +# (C) Copyright IBM 2017, 2019 # # 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 @@ -9,76 +9,18 @@ # 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. -""" -Multiple-Controlled U3 gate. Not using ancillary qubits. -""" -from math import pi +"""multicontrol rotation gates around an axis in x,y and z planes.""" + from typing import Optional, Union, Tuple, List +import numpy 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 - - -def _apply_cu(circuit, theta, phi, lam, control, target, use_basis_gates=True): - if use_basis_gates: - # pylint: disable=cyclic-import - # ┌──────────────┐ - # control: ┤ P(λ/2 + φ/2) ├──■──────────────────────────────────■──────────────── - # ├──────────────┤┌─┴─┐┌────────────────────────────┐┌─┴─┐┌────────────┐ - # target: ┤ P(λ/2 - φ/2) ├┤ X ├┤ U(-0.5*0,0,-0.5*λ - 0.5*φ) ├┤ X ├┤ U(0/2,φ,0) ├ - # └──────────────┘└───┘└────────────────────────────┘└───┘└────────────┘ - circuit.p((lam + phi) / 2, [control]) - circuit.p((lam - phi) / 2, [target]) - circuit.cx(control, target) - circuit.u(-theta / 2, 0, -(phi + lam) / 2, [target]) - circuit.cx(control, target) - circuit.u(theta / 2, phi, 0, [target]) - else: - circuit.cu(theta, phi, lam, 0, control, target) - - -def _apply_mcu_graycode(circuit, theta, phi, lam, ctls, tgt, use_basis_gates): - """Apply multi-controlled u gate from ctls to tgt using graycode - pattern with single-step angles theta, phi, lam.""" - - n = len(ctls) - - gray_code = _generate_gray_code(n) - last_pattern = None - - for pattern in gray_code: - if "1" not in pattern: - continue - if last_pattern is None: - last_pattern = pattern - # find left most set bit - lm_pos = list(pattern).index("1") - - # find changed bit - comp = [i != j for i, j in zip(pattern, last_pattern)] - if True in comp: - pos = comp.index(True) - else: - pos = None - if pos is not None: - if pos != lm_pos: - circuit.cx(ctls[pos], ctls[lm_pos]) - else: - indices = [i for i, x in enumerate(pattern) if x == "1"] - for idx in indices[1:]: - circuit.cx(ctls[idx], ctls[lm_pos]) - # check parity and undo rotation - if pattern.count("1") % 2 == 0: - # inverse CU: u(theta, phi, lamb)^dagger = u(-theta, -lam, -phi) - _apply_cu( - circuit, -theta, -lam, -phi, ctls[lm_pos], tgt, use_basis_gates=use_basis_gates - ) - else: - _apply_cu(circuit, theta, phi, lam, ctls[lm_pos], tgt, use_basis_gates=use_basis_gates) - last_pattern = pattern +from qiskit.circuit.library.generalized_gates import MCU2Gate +from qiskit.circuit.library.standard_gates import XGate, YGate, ZGate, RXGate, RYGate, RZGate +from qiskit.circuit.controlledgate import ControlledGate +from qiskit.circuit._utils import _ctrl_state_to_int, _compute_control_matrix def mcrx( @@ -106,31 +48,21 @@ def mcrx( if len(target_qubit) != 1: raise QiskitError("The mcrz gate needs a single qubit as target.") all_qubits = control_qubits + target_qubit - target_qubit = target_qubit[0] self._check_dups(all_qubits) n_c = len(control_qubits) - if n_c == 1: # cu - _apply_cu( - self, - theta, - -pi / 2, - pi / 2, - control_qubits[0], - target_qubit, - use_basis_gates=use_basis_gates, - ) + if n_c <= 6: + ncrx = ControlRotationGate(theta, n_c, axis="x") 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, + rxgate = ( + numpy.cos(theta / 2) * numpy.eye(2) - 1j * numpy.sin(theta / 2) * XGate().__array__() ) + ncrx = MCU2Gate(rxgate, n_c, label=f"Rx({theta:0.3f})") + + # if use_basis_gates: + # ncrx = transpile(ncrx, basis_gates=['cx','u', 'p']) + + self.append(ncrx, [*control_qubits, q_target]) def mcry( @@ -163,42 +95,21 @@ def mcry( raise QiskitError("The mcrz gate needs a single qubit as target.") ancillary_qubits = [] if q_ancillae is None else self.qbit_argument_conversion(q_ancillae) all_qubits = control_qubits + target_qubit + ancillary_qubits - target_qubit = target_qubit[0] self._check_dups(all_qubits) - # auto-select the best mode - if mode is None: - # if enough ancillary qubits are provided, use the 'v-chain' method - additional_vchain = MCXGate.get_num_ancilla_qubits(len(control_qubits), "v-chain") - if len(ancillary_qubits) >= additional_vchain: - mode = "basic" - else: - mode = "noancilla" - - if mode == "basic": - self.ry(theta / 2, q_target) - self.mcx(q_controls, q_target, q_ancillae, mode="v-chain") - self.ry(-theta / 2, q_target) - self.mcx(q_controls, q_target, q_ancillae, mode="v-chain") - elif mode == "noancilla": - n_c = len(control_qubits) - if n_c == 1: # cu - _apply_cu( - 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, - ) + n_c = len(control_qubits) + if n_c <= 6: + ncry = ControlRotationGate(theta, n_c, axis="y") else: - raise QiskitError(f"Unrecognized mode for building MCRY circuit: {mode}.") + rygate = ( + numpy.cos(theta / 2) * numpy.eye(2) - 1j * numpy.sin(theta / 2) * YGate().__array__() + ) + ncry = MCU2Gate(rygate, n_c, label=f"Ry({theta:0.3f})") + + # if use_basis_gates: + # ncry = transpile(ncry, basis_gates=['cx','u', 'p']) + + self.append(ncry, [*control_qubits, q_target]) def mcrz( @@ -223,22 +134,205 @@ def mcrz( """ control_qubits = self.qbit_argument_conversion(q_controls) target_qubit = self.qbit_argument_conversion(q_target) + if len(target_qubit) != 1: raise QiskitError("The mcrz gate needs a single qubit as target.") all_qubits = control_qubits + target_qubit - target_qubit = target_qubit[0] 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 <= 6: + ncrz = ControlRotationGate(lam, n_c, axis="z") 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 - ) + rzgate = numpy.cos(lam / 2) * numpy.eye(2) - 1j * numpy.sin(lam / 2) * ZGate().__array__() + ncrz = MCU2Gate(rzgate, n_c, label=f"Rz({lam:0.3f})") + # if use_basis_gates: + # ncrz = transpile(ncrz, basis_gates=['cx','u', 'p']) + + self.append(ncrz, [*control_qubits, q_target]) QuantumCircuit.mcrx = mcrx QuantumCircuit.mcry = mcry QuantumCircuit.mcrz = mcrz + + +class ControlRotationGate(ControlledGate): + """ + Control rotation gate. See Theorem 8 of https://arxiv.org/pdf/quant-ph/0406176.pdf + + Generate quantum circuit implementing a rotation generated by a single qubit Pauli operator. + + Args: + angle (float): angle of rotation + ctrl_list (list): list of control qubit indices + target (int): index of target qubit + axis (str): x,y,z axis + Returns: + circ (QuantumCircuit): quantum circuit implementing multicontrol rotation + + """ + + def __init__( + self, + angle: ParameterValueType, + num_ctrl_qubits: int, + axis: str, + ctrl_state: Optional[Union[str, int]] = None, + ): + + assert axis in ["x", "y", "z"], f"can only rotated around x,y,z axis, not {axis}" + + self.axis = axis + self.angle = angle + + if self.axis == "x": + self.base_gate = RXGate(angle) + elif self.axis == "y": + self.base_gate = RYGate(angle) + elif self.axis == "z": + self.base_gate = RZGate(angle) + + self._num_qubits = num_ctrl_qubits + 1 + self.num_ctrl_qubits = num_ctrl_qubits + + cntrl_int = _ctrl_state_to_int(ctrl_state, self.num_ctrl_qubits) + cntrl_str = numpy.binary_repr(cntrl_int, width=self.num_ctrl_qubits) + self.ctrl_state = cntrl_str[::-1] + + self.label = self.base_gate.label + super().__init__( + name=self.label, + num_qubits=self._num_qubits, + params=[angle], + num_ctrl_qubits=self.num_ctrl_qubits, + ctrl_state=self.ctrl_state, + base_gate=self.base_gate, + ) + + def _define(self): + + target_qbit = self.num_ctrl_qubits + rot_circuit = custom_mcrtl_rot( + self.angle, list(range(self.num_ctrl_qubits)), target_qbit, self.axis + ) + + controls = QuantumRegister(self.num_ctrl_qubits) + target = QuantumRegister(1) + + cntrl_int = _ctrl_state_to_int(self.ctrl_state, self.num_ctrl_qubits) + cntrl_str = numpy.binary_repr(cntrl_int, width=self.num_ctrl_qubits) + + control_circ = QuantumCircuit(controls, target) + for q_ind, cntrol_bit in enumerate(cntrl_str): + if cntrol_bit == "0": + control_circ.x(q_ind) + + self.definition = control_circ + self.definition = self.definition.compose(rot_circuit).compose(control_circ) + + def inverse(self): + """ + Returns inverse rotation gate + """ + return ControlRotationGate( + -1 * self.angle, self.num_ctrl_qubits, self.axis, ctrl_state=self.ctrl_state + ) + + def __array__(self, dtype=None): + """ + Return numpy array for gate + """ + mat = _compute_control_matrix( + self.base_gate.to_matrix(), self.num_ctrl_qubits, ctrl_state=self.ctrl_state + ) + if dtype: + mat = numpy.asarray(mat, dtype=dtype) + return mat + + +def custom_mcrtl_rot( + angle: float, ctrl_list: List[int], target: int, axis: str = "y" +) -> QuantumCircuit: + """ + See Theorem 8 of https://arxiv.org/pdf/quant-ph/0406176.pdf + + Generate quantum circuit implementing a rotation generated by a single qubit Pauli operator. + + Args: + angle (float): angle of rotation + ctrl_list (list): list of control qubit indices + target (int): index of target qubit + axis (str): x,y,z axis + Returns: + circ (QuantumCircuit): quantum circuit implementing multicontrol rotation + """ + assert axis in ["x", "y", "z"], f"can only rotated around x,y,z axis, not {axis}" + assert target not in ctrl_list, f"target qubit: {target} in control list" + + ctrl_list = sorted(ctrl_list) + n_ctrl = len(ctrl_list) + circ = QuantumCircuit(max([target, max(ctrl_list)]) + 1) + pattern = _get_pattern(n_ctrl) + + if n_ctrl > 1: + for s, i in enumerate(pattern): + j = ctrl_list[-i - 1] + p = _primitive_block((-1) ** s * angle, n_ctrl, axis) + circ = circ.compose(p, [j, target]) + + circ = circ.compose(circ) + else: + p_plus = _primitive_block(+angle, n_ctrl, axis) + p_minus = _primitive_block(-angle, n_ctrl, axis) + + circ = circ.compose(p_plus, [ctrl_list[0], target]) + circ = circ.compose(p_minus, [ctrl_list[0], target]) + + return circ + + +def _get_pattern(n_ctrl: int, _pattern=[0]) -> List[int]: + """ + Recursively get list of control indices for multicontrol rotation gate + + Args: + n_ctrl (int): number of controls + _pattern (list): stores list of control indices in each recursive call. This should not be changed by user + Returns + list of control indices + + """ + if n_ctrl == _pattern[-1] + 1: + return _pattern + else: + new_pattern = _pattern * 2 + new_pattern[-1] += 1 + return _get_pattern(n_ctrl, new_pattern) + + +def _primitive_block(angle: float, n_ctrl: int, axis: str) -> QuantumCircuit: + """ + Get primative gates to perform multicontrol rotation + + Args: + angle (float): angle of rotation + n_ctrl (int): number of control qubits + axis (str): axis of rotation (x,y or z) + Returns: + primitive (QuantumCircuit): quantum circuit of primative needed to perform multicontrol rotation + """ + primitive = QuantumCircuit(2) + if axis == "x": + primitive.rx(angle / (2**n_ctrl), 1) + primitive.cz(0, 1) + elif axis == "y": + primitive.ry(angle / (2**n_ctrl), 1) + primitive.cx(0, 1) + elif axis == "z": + primitive.rz(angle / (2**n_ctrl), 1) + primitive.cx(0, 1) + else: + raise ValueError("Unrecognised axis, must be one of x,y or z.") + + return primitive