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

Fix wrong relative phase of MCRZ (backport #9836) #10141

Merged
merged 2 commits into from
May 23, 2023
Merged
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
13 changes: 4 additions & 9 deletions qiskit/circuit/add_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ def control(
q_target[bit_indices[qargs[0]]],
use_basis_gates=True,
)
continue
elif gate.name == "p":
from qiskit.circuit.library import MCPhaseGate

Expand Down Expand Up @@ -184,23 +185,17 @@ 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,
q_target[bit_indices[qargs[0]]],
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,45 +84,52 @@ 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],
ctrl_state: str = None,
):
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.

The algorithm this method implements is described in: https://arxiv.org/abs/2302.06377
Return a multi-controlled SU(2) gate [1]_ with a real main diagonal or secondary diagonal.

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
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) <https://arxiv.org/abs/2302.06377>`__

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

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(
unitary[1, 0].imag, 0.0
)

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]
Expand All @@ -131,27 +138,34 @@ 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)]])

# 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))
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)

Expand Down Expand Up @@ -179,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,
Expand Down Expand Up @@ -233,7 +252,12 @@ def mcrx(
use_basis_gates=use_basis_gates,
)
else:
_mcsu2_real_diagonal(self, RXGate(theta).to_matrix(), control_qubits, target_qubit)
cgate = _mcsu2_real_diagonal(
RXGate(theta).to_matrix(),
num_controls=len(control_qubits),
use_basis_gates=use_basis_gates,
)
self.compose(cgate, control_qubits + [target_qubit], inplace=True)


def mcry(
Expand Down Expand Up @@ -303,7 +327,12 @@ def mcry(
use_basis_gates=use_basis_gates,
)
else:
_mcsu2_real_diagonal(self, RYGate(theta).to_matrix(), control_qubits, target_qubit)
cgate = _mcsu2_real_diagonal(
RYGate(theta).to_matrix(),
num_controls=len(control_qubits),
use_basis_gates=use_basis_gates,
)
self.compose(cgate, control_qubits + [target_qubit], inplace=True)
else:
raise QiskitError(f"Unrecognized mode for building MCRY circuit: {mode}.")

Expand All @@ -328,6 +357,8 @@ def mcrz(
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:
Expand All @@ -337,13 +368,21 @@ 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:
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:
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
cgate = _mcsu2_real_diagonal(
RZGate(lam).to_matrix(),
num_controls=len(control_qubits),
use_basis_gates=use_basis_gates,
)
self.compose(cgate, control_qubits + [target_qubit], inplace=True)


QuantumCircuit.mcrx = mcrx
Expand Down
Original file line number Diff line number Diff line change
@@ -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 :meth:`.QuantumCircuit.mcrz` behaviour, use :meth:`.QuantumCircuit.mcp`.
10 changes: 7 additions & 3 deletions test/python/circuit/test_controlled_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -612,9 +612,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()))
Expand Down Expand Up @@ -659,6 +658,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)

Expand All @@ -667,7 +671,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}"):
Expand Down
7 changes: 3 additions & 4 deletions test/qpy_compat/test_qpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, 24, 1):
output_circuits["open_controlled_gates.qpy"] = generate_open_controlled_gates()
output_circuits["controlled_gates.qpy"] = generate_controlled_gates()
return output_circuits


Expand Down