Repository: Qiskit/qiskit — pull request #9836 (python)
Title: Fix wrong relative phase of MCRZ

<!--\r\n⚠️ If you do not respect this template, your pull request will be closed.\r\n⚠️ Your pull request title should be short detailed and understandable for all.\r\n⚠️ Also, please add a release note file using reno if the change needs to be\r\n  documented in the release notes.\r\n⚠️ If your pull request fixes an open issue, please link to the issue.\r\n\r\n- [X] I have added the tests to cover my changes.\r\n- [ ] I have updated the documentation accordingly.\r\n- [X] I have read the CONTRIBUTING document.\r\n-->\r\n\r\n### Summary\r\n\r\nThis PR is on top of PR #9688 and fixes #9202.\r\n\r\n\r\n

Resolved issues:
title: QuantumCircuit.mcrz not correct
body: ### Environment\r\n\r\n- **Qiskit Terra version**:  0.22.2\r\n- **Python version**: 3.9.7\r\n- **Operating system**: Darwin\r\n\r\n\r\n### What is happening?\r\n\r\nmulticontrol Rz rotation not correctly being built - see example below. \r\n\r\n(Also multicontrol Rz, Ry, Rx gates can all be implemented using fewer nonlocal (two) qubit gates... See solution below)\r\n\r\n### How can we reproduce the issue?\r\n\r\n```\r\nfrom qiskit.quantum_info import Operator\r\nfrom qiskit import QuantumCircuit, transpile\r\nimport numpy as np\r\nfrom qiskit.circuit.library import MCMT\r\n\r\n\r\n###### two qubit case ####\r\nangle = np.pi/3\r\n### correct\r\ncrz_circuit = QuantumCircuit(2)\r\ncrz_circuit.crz(angle, 0,1)\r\ncrz_circuit_unitary = Operator(crz_circuit).data\r\n\r\n### incorrect\r\nmulti_crz = QuantumCircuit(2)\r\nmulti_crz.mcrz(angle, [0],1)\r\nmulti_crz_unitary = Operator(multi_crz).data\r\n\r\n## FALSE\r\nprint('single control correct:', np.allclose(crz_circuit_unitary,\r\n                             multi_crz_unitary))\r\n\r\n###### multi control case ####\r\nctrl_list = [0,2,5]\r\ntarget= 4\r\n\r\n# ## Correct\r\ngate = QuantumCircuit(1)\r\ngate.rz(angle, 0)\r\n  \r\nmc_gate = MCMT(gate,\r\n              num_ctrl_qubits=len(ctrl_list),\r\n              num_target_qubits=1).to_gate()\r\n\r\n\r\nmc_circuit = QuantumCircuit(max([target, max(ctrl_list)])+1)\r\nmc_circuit.append(mc_gate, [*ctrl_list, target])\r\n\r\n\r\n# ## Incorrect\r\nmcrz_multi = QuantumCircuit(max([target, max(ctrl_list)])+1)\r\nmcrz_multi.mcrz(angle,ctrl_list,target)\r\n\r\n\r\nprint('multicontrol correct:  ', np.allclose(Operator(mc_circuit).data,\r\n                                   Operator(mcrz_multi).data))\r\n```\r\n\r\n### What should happen?\r\n\r\nmcrz method in `QuantumCircuit ` should implement the correct quantum  circuit. Solution provided below.\r\n\r\n### Any suggestions?\r\n\r\nThis code fixes the problem and acutally reduces the depth of all multicontrol rotation Rz,Ry,Rz gates:\r\n\r\n```\r\nfrom qiskit import QuantumCircuit\r\n\r\ndef primitive_block(angle, n_ctrl, axis):\r\n    """\r\n    get primative gates to perform multicontrol rotation\r\n    """\r\n    primitive=QuantumCircuit(2)\r\n    if axis=='x':\r\n        primitive.rx(angle/(2**n_ctrl), 1); primitive.cz(0,1)\r\n    elif axis=='y':\r\n        primitive.ry(angle/(2**n_ctrl), 1); primitive.cx(0,1)\r\n    elif axis=='z':\r\n        primitive.rz(angle/(2**n_ctrl), 1); primitive.cx(0,1)\r\n    else:\r\n        raise ValueError('Unrecognised axis, must be one of x,y or z.')\r\n    \r\n    return primitive\r\n\r\ndef get_pattern(n_ctrl, pattern=[0]):\r\n    """\r\n    find control sequence to implement an n_control gate \r\n    """\r\n    if n_ctrl == pattern[-1]+1:\r\n        return pattern\r\n    else:\r\n        new_pattern = pattern*2\r\n        new_pattern[-1]+=1\r\n        return get_pattern(n_ctrl, new_pattern)\r\n    \r\ndef custom_mcrtl_rot(angle, ctrl_list, target, axis='y'):\r\n    """\r\n    return circuit that performs an n-control: Rz, Ry, Rx rotation\r\n    \r\n    """\r\n    assert axis in ['x','y','z'], f'can only rotated around x,y,z axis, not {axis}'\r\n    \r\n    ctrl_list=sorted(ctrl_list)\r\n    n_ctrl=len(ctrl_list)\r\n    circ=QuantumCircuit(max([target, max(ctrl_list)])+1)    \r\n    pattern = get_pattern(n_ctrl)\r\n    \r\n    if n_ctrl >1: \r\n        for s,i in enumerate(pattern):\r\n            j = ctrl_list[-i-1]\r\n            p = primitive_block((-1)**s*angle, n_ctrl, axis)\r\n            circ = circ.compose(p, [j,target])\r\n\r\n        circ = circ.compose(circ)\r\n    else:\r\n        p_plus = primitive_block(+angle, n_ctrl, axis)\r\n        p_minus = primitive_block(-angle, n_ctrl, axis)\r\n        circ = p_plus.compose(p_minus)\r\n    \r\n    return circ\r\n\r\n```\r\n\r\nWe can check this is working via:\r\n\r\n\r\n```\r\nfrom qiskit.quantum_info import Operator\r\nfrom qiskit import QuantumCircuit, transpile\r\nimport numpy as np\r\nfrom qiskit.circuit.library import MCMT\r\n\r\n### CAN CHANGE HERE\r\nctrl_list = [0,2,5]\r\ntarget= 4\r\nangle = np.pi/3\r\n####\r\n\r\n\r\naxis = 'z'\r\nnew_circuit = custom_mcrtl_rot(angle,ctrl_list,target, axis=axis)\r\n\r\ngate = QuantumCircuit(1)\r\ngate.rz(angle, 0)\r\nmc_gate = MCMT(gate,\r\n              num_ctrl_qubits=len(ctrl_list),\r\n              num_target_qubits=1).to_gate()\r\n\r\n\r\nmc_circuit = QuantumCircuit(max([target, max(ctrl_list)])+1)\r\nmc_circuit.append(mc_gate, [*ctrl_list, target])\r\n\r\nprint('does new method match MCMT:', np.allclose(Operator(new_circuit).data,\r\n            Operator(mc_circuit).data))\r\n\r\n\r\n###  compare to mcrz\r\nmcrz_multi = QuantumCircuit(max([target, max(ctrl_list)])+1)\r\nmcrz_multi.mcrz(angle,ctrl_list,target)\r\n\r\nprint('does mcrz match MCMT:      ', np.allclose(Operator(mcrz_multi).data,\r\n            Operator(new_circuit).data))\r\n\r\nprint('does mcrz match new:       ', np.allclose(Operator(mcrz_multi).data,\r\n            Operator(mc_circuit).data))\r\n\r\n### compare number of nonlocal gates in each approach depth\r\nnew_trans = transpile(new_circuit, basis_gates=['cx', 'rz','ry','rx'])\r\nmctc_trans = transpile(mc_circuit, basis_gates=['cx', 'rz','ry','rx'])\r\nmcrz_multi_trans = transpile(mcrz_multi, basis_gates=['cx', 'rz','ry','rx'])\r\n\r\nprint('\\ndepth comparison')\r\nprint('num nonlocal gates new :', new_trans.num_nonlocal_gates())\r\nprint('num nonlocal gates mctc:', mctc_trans.num_nonlocal_gates())\r\nprint('num nonlocal gates mcrz:', mcrz_multi_trans.num_nonlocal_gates())\r\n\r\n### allow old implementations to be optmized (NEW method not)\r\nopt_mctc_trans = transpile(mc_circuit, optimization_level=3)\r\nopt_mcrz_multi_trans = transpile(mcrz_multi, optimization_level=3)\r\n\r\nprint('\\ndepth comparison (allowing old methods to be optimized)')\r\nprint('num nonlocal gates new :', new_trans.num_nonlocal_gates(), '(no optimization)')\r\nprint('num nonlocal gates mctc:', opt_mctc_trans.num_nonlocal_gates())\r\nprint('num nonlocal gates mcrz:', opt_mcrz_multi_trans.num_nonlocal_gates())\r\n\r\n\r\n### prints:\r\n\r\n# does new method match MCMT: True\r\n# does mcrz match MCMT:       False\r\n# does mcrz match new:        False\r\n\r\n# depth comparison\r\n# num nonlocal gates new : 8\r\n# num nonlocal gates mctc: 28\r\n# num nonlocal gates mcrz: 20\r\n\r\n# depth comparison (allowing old methods to be optimized)\r\n# num nonlocal gates new : 8 (no optimization)\r\n# num nonlocal gates mctc: 25\r\n# num nonlocal gates mcrz: 13\r\n\r\n\r\n```\r\n\r\nThe new approach gives an improvement to the correctly implemented mutlicontrol rotation gates currently coded in qiskit:\r\n\r\n```\r\n### CAN CHANGE HERE\r\nctrl_list = [0,2,5]\r\ntarget= 4\r\nangle = np.pi/3\r\n####\r\n\r\n\r\nfor ax in ['x','y']:\r\n    \r\n    new_circuit = custom_mcrtl_rot(angle,ctrl_list,target, axis=ax)\r\n\r\n    if ax =='x':\r\n        gate = QuantumCircuit(1)\r\n        gate.rx(angle, 0) #< --- rx\r\n    elif ax == 'y':\r\n        gate = QuantumCircuit(1)\r\n        gate.ry(angle, 0) #< --- ry\r\n        \r\n    mc_gate = MCMT(gate,\r\n                  num_ctrl_qubits=len(ctrl_list),\r\n                  num_target_qubits=1).to_gate()\r\n\r\n    mc_circuit = QuantumCircuit(max([target, max(ctrl_list)])+1)\r\n    mc_circuit.append(mc_gate, [*ctrl_list, target])\r\n\r\n    print('does new method match MCMT:', np.allclose(Operator(new_circuit).data,\r\n                                         Operator(mc_circuit).data))\r\n    print()\r\n\r\n\r\n    ###  compare to mcr\r\n    mcr_multi = QuantumCircuit(max([target, max(ctrl_list)])+1)\r\n    if ax =='x':\r\n        mcr_multi.mcrx(angle,ctrl_list,target)\r\n    elif ax == 'y':\r\n        mcr_multi.mcry(angle,ctrl_list,target)\r\n    \r\n\r\n    print(f'does mcr{ax} match MCMT:      ', np.allclose(Operator(mcr_multi).data,\r\n                                             Operator(new_circuit).data))\r\n\r\n    print(f'does mcr{ax} match new:       ', np.allclose(Operator(mcr_multi).data,\r\n                                            Operator(mc_circuit).data))\r\n\r\n    ### compare number of nonlocal gates in each approach depth\r\n    new_trans = transpile(new_circuit, basis_gates=['cx', 'rz','ry','rx'])\r\n    mctc_trans = transpile(mc_circuit, basis_gates=['cx', 'rz','ry','rx'])\r\n    mcr_multi_trans = transpile(mcr_multi, basis_gates=['cx', 'rz','ry','rx'])\r\n\r\n    print(f'\\ndepth comparison for multi control R{ax} gate')\r\n    print('num nonlocal gates new :', new_trans.num_nonlocal_gates())\r\n    print('num nonlocal gates mctc:', mctc_trans.num_nonlocal_gates())\r\n    print(f'num nonlocal gates mcr{ax}:', mcr_multi_trans.num_nonlocal_gates())\r\n\r\n    ### allow old implementations to be optmized (NEW method not)\r\n    opt_mctc_trans = transpile(mc_circuit, optimization_level=3)\r\n    opt_mcrX_multi_trans = transpile(mcr_multi, optimization_level=3)\r\n    \r\n    print(f'\\ndepth comparison for multi control R{ax} gate (allowing old methods to be optimized)')\r\n    print('num nonlocal gates new :', new_trans.num_nonlocal_gates(), '(no optimization)')\r\n    print('num nonlocal gates mctc:', opt_mctc_trans.num_nonlocal_gates())\r\n    print(f'num nonlocal gates mcr{ax}:', opt_mcrX_multi_trans.num_nonlocal_gates())\r\n\r\n    \r\n    print('*'*12)\r\n    \r\n# does new method match MCMT: True\r\n\r\n# does mcrx match MCMT:       True\r\n# does mcrx match new:        True\r\n\r\n# depth comparison for multi control Rx gate\r\n# num nonlocal gates new : 8\r\n# num nonlocal gates mctc: 20\r\n# num nonlocal gates mcrx: 20\r\n\r\n# depth comparison for multi control Rx gate (allowing old methods to be optimized)\r\n# num nonlocal gates new : 8 (no optimization)\r\n# num nonlocal gates mctc: 20\r\n# num nonlocal gates mcrx: 13\r\n# ************\r\n# does new method match MCMT: True\r\n\r\n# does mcry match MCMT:       True\r\n# does mcry match new:        True\r\n\r\n# depth comparison for multi control Ry gate\r\n# num nonlocal gates new : 8\r\n# num nonlocal gates mctc: 20\r\n# num nonlocal gates mcry: 20\r\n\r\n# depth comparison for multi control Ry gate (allowing old methods to be optimized)\r\n# num nonlocal gates new : 8 (no optimization)\r\n# num nonlocal gates mctc: 20\r\n# num nonlocal gates mcry: 13\r\n# ************\r\n\r\n```\r\n\r\n\r\n

Hints:

Fix patch:
diff --git a/qiskit/circuit/add_control.py b/qiskit/circuit/add_control.py\nindex 39a91eba36ef..9a9837673d1a 100644\n--- a/qiskit/circuit/add_control.py\n+++ b/qiskit/circuit/add_control.py\n@@ -148,6 +148,7 @@ def control(\n                     q_target[bit_indices[qargs[0]]],\n                     use_basis_gates=True,\n                 )\n+                continue\n             elif gate.name == "p":\n                 from qiskit.circuit.library import MCPhaseGate\n \n@@ -184,13 +185,9 @@ def control(\n                             use_basis_gates=True,\n                         )\n                     elif theta == 0 and phi == 0:\n-                        controlled_circ.mcrz(\n-                            lamb, q_control, q_target[bit_indices[qargs[0]]], use_basis_gates=True\n-                        )\n+                        controlled_circ.mcp(lamb, q_control, q_target[bit_indices[qargs[0]]])\n                     else:\n-                        controlled_circ.mcrz(\n-                            lamb, q_control, q_target[bit_indices[qargs[0]]], use_basis_gates=True\n-                        )\n+                        controlled_circ.mcp(lamb, q_control, q_target[bit_indices[qargs[0]]])\n                         controlled_circ.mcry(\n                             theta,\n                             q_control,\n@@ -198,9 +195,7 @@ def control(\n                             q_ancillae,\n                             use_basis_gates=True,\n                         )\n-                        controlled_circ.mcrz(\n-                            phi, q_control, q_target[bit_indices[qargs[0]]], use_basis_gates=True\n-                        )\n+                        controlled_circ.mcp(phi, q_control, q_target[bit_indices[qargs[0]]])\n             elif gate.name == "z":\n                 controlled_circ.h(q_target[bit_indices[qargs[0]]])\n                 controlled_circ.mcx(q_control, q_target[bit_indices[qargs[0]]], q_ancillae)\ndiff --git a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py\nindex 547883a6f7a9..ce7f7861440b 100644\n--- a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py\n+++ b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py\n@@ -84,36 +84,44 @@ def _apply_mcu_graycode(circuit, theta, phi, lam, ctls, tgt, use_basis_gates):\n \n \n def _mcsu2_real_diagonal(\n-    circuit,\n     unitary: np.ndarray,\n-    controls: Union[QuantumRegister, List[Qubit]],\n-    target: Union[Qubit, int],\n-    ctrl_state: str = None,\n-):\n+    num_controls: int,\n+    ctrl_state: Optional[str] = None,\n+    use_basis_gates: bool = False,\n+) -> QuantumCircuit:\n     """\n-    Apply multi-controlled SU(2) gate with a real main diagonal or secondary diagonal.\n-    https://arxiv.org/abs/2302.06377\n+    Return a multi-controlled SU(2) gate [1]_ with a real main diagonal or secondary diagonal.\n \n     Args:\n-        circuit (QuantumCircuit): The QuantumCircuit object to apply the diagonal operator on.\n-        unitary (ndarray): SU(2) unitary matrix with one real diagonal\n-        controls (QuantumRegister or list(Qubit)): The list of control qubits\n-        target (Qubit or int): The target qubit\n-        ctrl_state (str): control state of the operator SU(2) operator\n+        unitary: SU(2) unitary matrix with one real diagonal.\n+        num_controls: The number of control qubits.\n+        ctrl_state: The state on which the SU(2) operation is controlled. Defaults to all\n+            control qubits being in state 1.\n+        use_basis_gates: If ``True``, use ``[p, u, cx]`` gates to implement the decomposition.\n+\n+    Returns:\n+        A :class:`.QuantumCircuit` implementing the multi-controlled SU(2) gate.\n \n     Raises:\n-        QiskitError: parameter errors\n+        QiskitError: If the input matrix is invalid.\n+\n+    References:\n+\n+        .. [1]: R. Vale et al. Decomposition of Multi-controlled Special Unitary Single-Qubit Gates\n+            `arXiv:2302.06377 (2023) <https://arxiv.org/abs/2302.06377>`__\n+\n     """\n     # pylint: disable=cyclic-import\n-    from qiskit.circuit.library import MCXVChain\n+    from .x import MCXVChain\n     from qiskit.extensions import UnitaryGate\n     from qiskit.quantum_info.operators.predicates import is_unitary_matrix\n-\n-    if not is_unitary_matrix(unitary):\n-        raise QiskitError("parameter unitary in mcsu2_real_diagonal must be an unitary matrix")\n+    from qiskit.compiler import transpile\n \n     if unitary.shape != (2, 2):\n-        raise QiskitError("parameter unitary in mcsu2_real_diagonal must be a 2x2 matrix")\n+        raise QiskitError(f"The unitary must be a 2x2 matrix, but has shape {unitary.shape}.")\n+\n+    if not is_unitary_matrix(unitary):\n+        raise QiskitError(f"The unitary in must be an unitary matrix, but is {unitary}.")\n \n     is_main_diag_real = np.isclose(unitary[0, 0].imag, 0.0) and np.isclose(unitary[1, 1].imag, 0.0)\n     is_secondary_diag_real = np.isclose(unitary[0, 1].imag, 0.0) and np.isclose(\n@@ -121,7 +129,7 @@ def _mcsu2_real_diagonal(\n     )\n \n     if not is_main_diag_real and not is_secondary_diag_real:\n-        raise QiskitError("parameter unitary in mcsu2_real_diagonal must have one real diagonal")\n+        raise QiskitError("The unitary must have one real diagonal.")\n \n     if is_secondary_diag_real:\n         x = unitary[0, 1]\n@@ -130,27 +138,34 @@ def _mcsu2_real_diagonal(\n         x = -unitary[0, 1].real\n         z = unitary[1, 1] - unitary[0, 1].imag * 1.0j\n \n-    alpha_r = np.sqrt((np.sqrt((z.real + 1.0) / 2.0) + 1.0) / 2.0)\n-    alpha_i = z.imag / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0)))\n-    alpha = alpha_r + 1.0j * alpha_i\n-    beta = x / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0)))\n+    if np.isclose(z, -1):\n+        s_op = [[1.0, 0.0], [0.0, 1.0j]]\n+    else:\n+        alpha_r = np.sqrt((np.sqrt((z.real + 1.0) / 2.0) + 1.0) / 2.0)\n+        alpha_i = z.imag / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0)))\n+        alpha = alpha_r + 1.0j * alpha_i\n+        beta = x / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0)))\n+\n+        # S gate definition\n+        s_op = np.array([[alpha, -np.conj(beta)], [beta, np.conj(alpha)]])\n \n-    # S gate definition\n-    s_op = np.array([[alpha, -np.conj(beta)], [beta, np.conj(alpha)]])\n     s_gate = UnitaryGate(s_op)\n \n-    num_ctrl = len(controls)\n-    k_1 = int(np.ceil(num_ctrl / 2.0))\n-    k_2 = int(np.floor(num_ctrl / 2.0))\n+    k_1 = int(np.ceil(num_controls / 2.0))\n+    k_2 = int(np.floor(num_controls / 2.0))\n \n     ctrl_state_k_1 = None\n     ctrl_state_k_2 = None\n \n     if ctrl_state is not None:\n-        str_ctrl_state = f"{ctrl_state:0{num_ctrl}b}"\n+        str_ctrl_state = f"{ctrl_state:0{num_controls}b}"\n         ctrl_state_k_1 = str_ctrl_state[::-1][:k_1][::-1]\n         ctrl_state_k_2 = str_ctrl_state[::-1][k_1:][::-1]\n \n+    circuit = QuantumCircuit(num_controls + 1, name="MCSU2")\n+    controls = list(range(num_controls))  # control indices, defined for code legibility\n+    target = num_controls  # target index, defined for code legibility\n+\n     if not is_secondary_diag_real:\n         circuit.h(target)\n \n@@ -178,6 +193,11 @@ def _mcsu2_real_diagonal(\n     if not is_secondary_diag_real:\n         circuit.h(target)\n \n+    if use_basis_gates:\n+        circuit = transpile(circuit, basis_gates=["p", "u", "cx"])\n+\n+    return circuit\n+\n \n def mcrx(\n     self,\n@@ -232,7 +252,12 @@ def mcrx(\n             use_basis_gates=use_basis_gates,\n         )\n     else:\n-        _mcsu2_real_diagonal(self, RXGate(theta).to_matrix(), control_qubits, target_qubit)\n+        cgate = _mcsu2_real_diagonal(\n+            RXGate(theta).to_matrix(),\n+            num_controls=len(control_qubits),\n+            use_basis_gates=use_basis_gates,\n+        )\n+        self.compose(cgate, control_qubits + [target_qubit], inplace=True)\n \n \n def mcry(\n@@ -302,7 +327,12 @@ def mcry(\n                 use_basis_gates=use_basis_gates,\n             )\n         else:\n-            _mcsu2_real_diagonal(self, RYGate(theta).to_matrix(), control_qubits, target_qubit)\n+            cgate = _mcsu2_real_diagonal(\n+                RYGate(theta).to_matrix(),\n+                num_controls=len(control_qubits),\n+                use_basis_gates=use_basis_gates,\n+            )\n+            self.compose(cgate, control_qubits + [target_qubit], inplace=True)\n     else:\n         raise QiskitError(f"Unrecognized mode for building MCRY circuit: {mode}.")\n \n@@ -327,6 +357,8 @@ def mcrz(\n     Raises:\n         QiskitError: parameter errors\n     """\n+    from .rz import CRZGate, RZGate\n+\n     control_qubits = self.qbit_argument_conversion(q_controls)\n     target_qubit = self.qbit_argument_conversion(q_target)\n     if len(target_qubit) != 1:\n@@ -336,13 +368,21 @@ def mcrz(\n     self._check_dups(all_qubits)\n \n     n_c = len(control_qubits)\n-    if n_c == 1:  # cu\n-        _apply_cu(self, 0, 0, lam, control_qubits[0], target_qubit, use_basis_gates=use_basis_gates)\n+    if n_c == 1:\n+        if use_basis_gates:\n+            self.u(0, 0, lam / 2, target_qubit)\n+            self.cx(control_qubits[0], target_qubit)\n+            self.u(0, 0, -lam / 2, target_qubit)\n+            self.cx(control_qubits[0], target_qubit)\n+        else:\n+            self.append(CRZGate(lam), control_qubits + [target_qubit])\n     else:\n-        lam_step = lam * (1 / (2 ** (n_c - 1)))\n-        _apply_mcu_graycode(\n-            self, 0, 0, lam_step, control_qubits, target_qubit, use_basis_gates=use_basis_gates\n+        cgate = _mcsu2_real_diagonal(\n+            RZGate(lam).to_matrix(),\n+            num_controls=len(control_qubits),\n+            use_basis_gates=use_basis_gates,\n         )\n+        self.compose(cgate, control_qubits + [target_qubit], inplace=True)\n \n \n QuantumCircuit.mcrx = mcrx\ndiff --git a/releasenotes/notes/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml b/releasenotes/notes/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml\nnew file mode 100644\nindex 000000000000..aa89abeb662d\n--- /dev/null\n+++ b/releasenotes/notes/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml\n@@ -0,0 +1,7 @@\n+---\n+fixes:\n+  - |\n+    Fixed the gate decomposition of multi-controlled Z rotation gates added via\n+    :meth:`.QuantumCircuit.mcrz`. Previously, this method implemented a multi-controlled\n+    phase gate, which has a relative phase difference  to the Z rotation. To obtain the\n+    previous `.QuantumCircuit.mcrz` behaviour, use `.QuantumCircuit.mcp`.\n

Test patch:
diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py\nindex a40f7747e2e7..248aabab7839 100644\n--- a/test/python/circuit/test_controlled_gate.py\n+++ b/test/python/circuit/test_controlled_gate.py\n@@ -610,9 +610,8 @@ def test_mcsu2_real_diagonal(self):\n         """Test mcsu2_real_diagonal"""\n         num_ctrls = 6\n         theta = 0.3\n-        qc = QuantumCircuit(num_ctrls + 1)\n         ry_matrix = RYGate(theta).to_matrix()\n-        _mcsu2_real_diagonal(qc, ry_matrix, list(range(num_ctrls)), num_ctrls)\n+        qc = _mcsu2_real_diagonal(ry_matrix, num_ctrls)\n \n         mcry_matrix = _compute_control_matrix(ry_matrix, 6)\n         self.assertTrue(np.allclose(mcry_matrix, Operator(qc).to_matrix()))\n@@ -657,6 +656,11 @@ def test_multi_controlled_rotation_gate_matrices(\n                 if bit == "0":\n                     qc.x(q_controls[idx])\n \n+            if use_basis_gates:\n+                with self.subTest(msg="check only basis gates used"):\n+                    gates_used = set(qc.count_ops().keys())\n+                    self.assertTrue(gates_used.issubset({"x", "u", "p", "cx"}))\n+\n             backend = BasicAer.get_backend("unitary_simulator")\n             simulated = execute(qc, backend).result().get_unitary(qc)\n \n@@ -665,7 +669,7 @@ def test_multi_controlled_rotation_gate_matrices(\n             elif base_gate_name == "y":\n                 rot_mat = RYGate(theta).to_matrix()\n             else:  # case 'z'\n-                rot_mat = U1Gate(theta).to_matrix()\n+                rot_mat = RZGate(theta).to_matrix()\n \n             expected = _compute_control_matrix(rot_mat, num_controls, ctrl_state=ctrl_state)\n             with self.subTest(msg=f"control state = {ctrl_state}"):\ndiff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py\nindex ef01d3873e39..2a4b112e1508 100755\n--- a/test/qpy_compat/test_qpy.py\n+++ b/test/qpy_compat/test_qpy.py\n@@ -603,15 +603,14 @@ def generate_circuits(version_parts):\n     if version_parts >= (0, 19, 2):\n         output_circuits["control_flow.qpy"] = generate_control_flow_circuits()\n     if version_parts >= (0, 21, 0):\n-        output_circuits["controlled_gates.qpy"] = generate_controlled_gates()\n         output_circuits["schedule_blocks.qpy"] = generate_schedule_blocks()\n         output_circuits["pulse_gates.qpy"] = generate_calibrated_circuits()\n-    if version_parts >= (0, 21, 2):\n-        output_circuits["open_controlled_gates.qpy"] = generate_open_controlled_gates()\n     if version_parts >= (0, 24, 0):\n         output_circuits["referenced_schedule_blocks.qpy"] = generate_referenced_schedule()\n         output_circuits["control_flow_switch.qpy"] = generate_control_flow_switch_circuits()\n-\n+    if version_parts >= (0, 25, 0):\n+        output_circuits["open_controlled_gates.qpy"] = generate_open_controlled_gates()\n+        output_circuits["controlled_gates.qpy"] = generate_controlled_gates()\n     return output_circuits\n \n \n