Skip to content

Commit

Permalink
Fix wrong relative phase of MCRZ (#9836)
Browse files Browse the repository at this point in the history
* efficient multicontrolled su2 gate decomposition

Co-authored-by: thiagom123 <thiagomdazevedo@hotmail.com>
Co-authored-by: IsmaelCesar <leamscesar@gmail.com>
Co-authored-by: Israel F. Araujo <israelferrazaraujo@hotmail.com>
Co-authored-by: Adenilton Silva <7927558+adjs@users.noreply.github.com>

* removed optimization flag

Co-authored-by: thiagom123 <thiagomdazevedo@hotmail.com>
Co-authored-by: IsmaelCesar <leamscesar@gmail.com>
Co-authored-by: Israel F. Araujo <israelferrazaraujo@hotmail.com>
Co-authored-by: Adenilton Silva <7927558+adjs@users.noreply.github.com>

* tox -eblack

* updated docstrings

* Adds `MCSU2Gate` to `__init__`

* fixed circular import

* defined control and inverse methods

* changed MCSU2Gate from Gate to ControlledGate

* adjusted some tests for controlled gates

* reformatting

* Fix regarding the integer `ctrl_state` parameter

* Tests to check the CX count upper bound

* Gate's `label` in the `control` function

* Upd. Qiskit tests to include cases for MCSU2Gate

* Upd. Qiskit tests to include cases for MCSU2Gate

* Revert "Upd. Qiskit tests to include cases for MCSU2Gate"

This reverts commit c1ceaf6.

* Revert "Upd. Qiskit tests to include cases for MCSU2Gate"

This reverts commit 7c75611.

* Revert "Tests to check the CX count upper bound"

This reverts commit 100a690.

* Update test_controlled_gate.py

* Update test_circuit_operations.py

* remove mcsu2gate class

* remove mcsu2gate class

* fix mcry

* lint

* fix mcrx

* add reference

* Create `s_gate` directly

* Revert "Create `s_gate` directly"

This reverts commit b762b39.

* review

* release notes

* review 2

* backwards compat

* function signature and number of controls

* fix mcrz

* Update multi_control_rotation_gates.py

* review

* Update test_qpy.py

* Revert "Update test_qpy.py"

This reverts commit fab1c80.

* Update test_qpy.py

* Update multi_control_rotation_gates.py

* Fix `use_basis_gates=True` case

* lint

---------

Co-authored-by: rafaella-vale <26910380+rafaella-vale@users.noreply.github.com>
Co-authored-by: thiagom123 <thiagomdazevedo@hotmail.com>
Co-authored-by: IsmaelCesar <leamscesar@gmail.com>
Co-authored-by: Israel F. Araujo <israelferrazaraujo@hotmail.com>
Co-authored-by: Rafaella Vale <rfv@cin.ufpe.br>
Co-authored-by: Julien Gacon <gaconju@gmail.com>
(cherry picked from commit 7b677ed)

# Conflicts:
#	qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py
  • Loading branch information
adjs authored and Cryoris committed May 23, 2023
1 parent ab40959 commit a502480
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 50 deletions.
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
113 changes: 79 additions & 34 deletions qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,45 +84,58 @@ 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:
"""
<<<<<<< HEAD
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.
>>>>>>> 7b677edf1 (Fix wrong relative phase of MCRZ (#9836))
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 +144,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 +199,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 +258,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 +333,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 +363,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 +374,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 `.QuantumCircuit.mcrz` behaviour, use `.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, 25, 0):
output_circuits["open_controlled_gates.qpy"] = generate_open_controlled_gates()
output_circuits["controlled_gates.qpy"] = generate_controlled_gates()
return output_circuits


Expand Down

0 comments on commit a502480

Please sign in to comment.