Repository: Qiskit/qiskit

Issue:
QuantumCircuit.mcrz not correct
### Environment

- **Qiskit Terra version**:  0.22.2
- **Python version**: 3.9.7
- **Operating system**: Darwin


### What is happening?

multicontrol Rz rotation not correctly being built - see example below. 

(Also multicontrol Rz, Ry, Rx gates can all be implemented using fewer nonlocal (two) qubit gates... See solution below)

### How can we reproduce the issue?

```
from qiskit.quantum_info import Operator
from qiskit import QuantumCircuit, transpile
import numpy as np
from qiskit.circuit.library import MCMT


###### two qubit case ####
angle = np.pi/3
### correct
crz_circuit = QuantumCircuit(2)
crz_circuit.crz(angle, 0,1)
crz_circuit_unitary = Operator(crz_circuit).data

### incorrect
multi_crz = QuantumCircuit(2)
multi_crz.mcrz(angle, [0],1)
multi_crz_unitary = Operator(multi_crz).data

## FALSE
print('single control correct:', np.allclose(crz_circuit_unitary,
                             multi_crz_unitary))

###### multi control case ####
ctrl_list = [0,2,5]
target= 4

# ## Correct
gate = QuantumCircuit(1)
gate.rz(angle, 0)
  
mc_gate = MCMT(gate,
              num_ctrl_qubits=len(ctrl_list),
              num_target_qubits=1).to_gate()


mc_circuit = QuantumCircuit(max([target, max(ctrl_list)])+1)
mc_circuit.append(mc_gate, [*ctrl_list, target])


# ## Incorrect
mcrz_multi = QuantumCircuit(max([target, max(ctrl_list)])+1)
mcrz_multi.mcrz(angle,ctrl_list,target)


print('multicontrol correct:  ', np.allclose(Operator(mc_circuit).data,
                                   Operator(mcrz_multi).data))
```

### What should happen?

mcrz method in `QuantumCircuit ` should implement the correct quantum  circuit. Solution provided below.

### Any suggestions?

This code fixes the problem and acutally reduces the depth of all multicontrol rotation Rz,Ry,Rz gates:

```
from qiskit import QuantumCircuit

def primitive_block(angle, n_ctrl, axis):
    """
    get primative gates 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

def get_pattern(n_ctrl, pattern=[0]):
    """
    find control sequence to implement an n_control gate 
    """
    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 custom_mcrtl_rot(angle, ctrl_list, target, axis='y'):
    """
    return circuit that performs an n-control: Rz, Ry, Rx rotation
    
    """
    assert axis in ['x','y','z'], f'can only rotated around x,y,z axis, not {axis}'
    
    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 = p_plus.compose(p_minus)
    
    return circ

```

We can check this is working via:


```
from qiskit.quantum_info import Operator
from qiskit import QuantumCircuit, transpile
import numpy as np
from qiskit.circuit.library import MCMT

### CAN CHANGE HERE
ctrl_list = [0,2,5]
target= 4
angle = np.pi/3
####


axis = 'z'
new_circuit = custom_mcrtl_rot(angle,ctrl_list,target, axis=axis)

gate = QuantumCircuit(1)
gate.rz(angle, 0)
mc_gate = MCMT(gate,
              num_ctrl_qubits=len(ctrl_list),
              num_target_qubits=1).to_gate()


mc_circuit = QuantumCircuit(max([target, max(ctrl_list)])+1)
mc_circuit.append(mc_gate, [*ctrl_list, target])

print('does new method match MCMT:', np.allclose(Operator(new_circuit).data,
            Operator(mc_circuit).data))


###  compare to mcrz
mcrz_multi = QuantumCircuit(max([target, max(ctrl_list)])+1)
mcrz_multi.mcrz(angle,ctrl_list,target)

print('does mcrz match MCMT:      ', np.allclose(Operator(mcrz_multi).data,
            Operator(new_circuit).data))

print('does mcrz match new:       ', np.allclose(Operator(mcrz_multi).data,
            Operator(mc_circuit).data))

### compare number of nonlocal gates in each approach depth
new_trans = transpile(new_circuit, basis_gates=['cx', 'rz','ry','rx'])
mctc_trans = transpile(mc_circuit, basis_gates=['cx', 'rz','ry','rx'])
mcrz_multi_trans = transpile(mcrz_multi, basis_gates=['cx', 'rz','ry','rx'])

print('\ndepth comparison')
print('num nonlocal gates new :', new_trans.num_nonlocal_gates())
print('num nonlocal gates mctc:', mctc_trans.num_nonlocal_gates())
print('num nonlocal gates mcrz:', mcrz_multi_trans.num_nonlocal_gates())

### allow old implementations to be optmized (NEW method not)
opt_mctc_trans = transpile(mc_circuit, optimization_level=3)
opt_mcrz_multi_trans = transpile(mcrz_multi, optimization_level=3)

print('\ndepth comparison (allowing old methods to be optimized)')
print('num nonlocal gates new :', new_trans.num_nonlocal_gates(), '(no optimization)')
print('num nonlocal gates mctc:', opt_mctc_trans.num_nonlocal_gates())
print('num nonlocal gates mcrz:', opt_mcrz_multi_trans.num_nonlocal_gates())


### prints:

# does new method match MCMT: True
# does mcrz match MCMT:       False
# does mcrz match new:        False

# depth comparison
# num nonlocal gates new : 8
# num nonlocal gates mctc: 28
# num nonlocal gates mcrz: 20

# depth comparison (allowing old methods to be optimized)
# num nonlocal gates new : 8 (no optimization)
# num nonlocal gates mctc: 25
# num nonlocal gates mcrz: 13


```

The new approach gives an improvement to the correctly implemented mutlicontrol rotation gates currently coded in qiskit:

```
### CAN CHANGE HERE
ctrl_list = [0,2,5]
target= 4
angle = np.pi/3
####


for ax in ['x','y']:
    
    new_circuit = custom_mcrtl_rot(angle,ctrl_list,target, axis=ax)

    if ax =='x':
        gate = QuantumCircuit(1)
        gate.rx(angle, 0) #< --- rx
    elif ax == 'y':
        gate = QuantumCircuit(1)
        gate.ry(angle, 0) #< --- ry
        
    mc_gate = MCMT(gate,
                  num_ctrl_qubits=len(ctrl_list),
                  num_target_qubits=1).to_gate()

    mc_circuit = QuantumCircuit(max([target, max(ctrl_list)])+1)
    mc_circuit.append(mc_gate, [*ctrl_list, target])

    print('does new method match MCMT:', np.allclose(Operator(new_circuit).data,
                                         Operator(mc_circuit).data))
    print()


    ###  compare to mcr
    mcr_multi = QuantumCircuit(max([target, max(ctrl_list)])+1)
    if ax =='x':
        mcr_multi.mcrx(angle,ctrl_list,target)
    elif ax == 'y':
        mcr_multi.mcry(angle,ctrl_list,target)
    

    print(f'does mcr{ax} match MCMT:      ', np.allclose(Operator(mcr_multi).data,
                                             Operator(new_circuit).data))

    print(f'does mcr{ax} match new:       ', np.allclose(Operator(mcr_multi).data,
                                            Operator(mc_circuit).data))

    ### compare number of nonlocal gates in each approach depth
    new_trans = transpile(new_circuit, basis_gates=['cx', 'rz','ry','rx'])
    mctc_trans = transpile(mc_circuit, basis_gates=['cx', 'rz','ry','rx'])
    mcr_multi_trans = transpile(mcr_multi, basis_gates=['cx', 'rz','ry','rx'])

    print(f'\ndepth comparison for multi control R{ax} gate')
    print('num nonlocal gates new :', new_trans.num_nonlocal_gates())
    print('num nonlocal gates mctc:', mctc_trans.num_nonlocal_gates())
    print(f'num nonlocal gates mcr{ax}:', mcr_multi_trans.num_nonlocal_gates())

    ### allow old implementations to be optmized (NEW method not)
    opt_mctc_trans = transpile(mc_circuit, optimization_level=3)
    opt_mcrX_multi_trans = transpile(mcr_multi, optimization_level=3)
    
    print(f'\ndepth comparison for multi control R{ax} gate (allowing old methods to be optimized)')
    print('num nonlocal gates new :', new_trans.num_nonlocal_gates(), '(no optimization)')
    print('num nonlocal gates mctc:', opt_mctc_trans.num_nonlocal_gates())
    print(f'num nonlocal gates mcr{ax}:', opt_mcrX_multi_trans.num_nonlocal_gates())

    
    print('*'*12)
    
# does new method match MCMT: True

# does mcrx match MCMT:       True
# does mcrx match new:        True

# depth comparison for multi control Rx gate
# num nonlocal gates new : 8
# num nonlocal gates mctc: 20
# num nonlocal gates mcrx: 20

# depth comparison for multi control Rx gate (allowing old methods to be optimized)
# num nonlocal gates new : 8 (no optimization)
# num nonlocal gates mctc: 20
# num nonlocal gates mcrx: 13
# ************
# does new method match MCMT: True

# does mcry match MCMT:       True
# does mcry match new:        True

# depth comparison for multi control Ry gate
# num nonlocal gates new : 8
# num nonlocal gates mctc: 20
# num nonlocal gates mcry: 20

# depth comparison for multi control Ry gate (allowing old methods to be optimized)
# num nonlocal gates new : 8 (no optimization)
# num nonlocal gates mctc: 20
# num nonlocal gates mcry: 13
# ************

```





Discussion:
Hi @AlexisRalli, thanks for reporting this! Indeed the MCRZ looks wrong, it's not equivalent to the multicontrolled phase gates, as which it is implemented. Do you have a reference to the multicontrolled Pauli rotations you're using? We could definitely add those as default decomposition to Qiskit if they use less CNOTS (or have shallower depth) 🙂 
Hi @Cryoris , I was using Theorem 8 of https://arxiv.org/pdf/quant-ph/0406176.pdf to build these circuits.
Thanks for the reference! Would you be interested in fixing this in Qiskit? 🙂 We can of course have a discussion on how to tackle this, if that would be helpful.
@Cryoris Sorry for the late reply. I'd be happy to fix this.  I guess I should just fork the repo and make a pull request
@AlexisRalli I have assigned you the issue. You can find instructions on how to setup your local development environment in [the contributing guide](https://qiskit.org/documentation/contributing_to_qiskit.html). If you need any help, please let me know!

Resolving patch:
diff --git a/qiskit/circuit/add_control.py b/qiskit/circuit/add_control.py
--- a/qiskit/circuit/add_control.py
+++ b/qiskit/circuit/add_control.py
@@ -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
 
@@ -184,13 +185,9 @@ 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,
@@ -198,9 +195,7 @@ def control(
                             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)
diff --git a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py
--- a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py
+++ b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py
@@ -84,36 +84,44 @@ 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.
-    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(
@@ -121,7 +129,7 @@ def _mcsu2_real_diagonal(
     )
 
     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]
@@ -130,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)
 
@@ -178,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,
@@ -232,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(
@@ -302,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}.")
 
@@ -327,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:
@@ -336,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


Test patch:
diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py
--- a/test/python/circuit/test_controlled_gate.py
+++ b/test/python/circuit/test_controlled_gate.py
@@ -610,9 +610,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()))
@@ -657,6 +656,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)
 
@@ -665,7 +669,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}"):
diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py
--- a/test/qpy_compat/test_qpy.py
+++ b/test/qpy_compat/test_qpy.py
@@ -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