Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Multicontrol Rz fix #9574

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions qiskit/circuit/library/generalized_gates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
from .pauli import PauliGate
from .rv import RVGate
from .linear_function import LinearFunction
from .multicontrol_arb_gate import MCU2Gate
265 changes: 265 additions & 0 deletions qiskit/circuit/library/generalized_gates/multicontrol_arb_gate.py
Original file line number Diff line number Diff line change
@@ -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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My suggestion is to create a different PR with the MCU2Gate because this class is for "any multicontrol version of any single qubit unitary matrix (gate)" and this PR is about multicontrolled rotation gates.

"""
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
Loading