Add argument `assume_unitary` to ``Operator.power``. (Qiskit/qiskit#13319)

<!--\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- [x] 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\nFixes #13307 following @jakelishman's suggestion in the description.\r\n\r\n### Details and comments\r\n\r\nAfter a quick round of profiling on my laptop (Python 10, Windows 11), the old (Schur-based) way to raise an operator to a power is 3x-5x faster than calling `scipy.linalg.fractional_matrix_power`, so it makes sense to use it when the operator is known to be unitary.\r\n\r\nI have renamed the old method to be called `Operator.power_if_unitary`, this **assumes** that the given operator is unitary. The `Operator.is_power` does not make this assumption (and calls `fractional_matrix_power`). The gate class uses the `power_if_unitary` method. \r\n\r\nP.S. In addition, for the `power` method and an integer power, I don't see much runtime difference when calling `fractional_matrix_power` and `matrix_power`. For the `power_if_unitary` method calling `matrix_power` is faster than calling the Schur-based implementation.

`Operator.power` does not handle non-unitary inputs correctly

### Environment\n\n- **Qiskit version**: 1.2.4\n- **Python version**: 3.10\n- **Operating system**: macOS\n\n\n### What is happening?\n\nThe way `Operator.power` works, it's strongly assuming that the internal matrix is unitary, because it's assuming that it can calculate fractional matrix powers by taking the Schur decomposition taking the matrix power of the Schur form then applying the unitary transformation (ok so far), except it assumes that the matrix power of the Schur form is the power of its diagonal.  The Schur form is upper-triangular, but not necessarily diagonal (roughly, I think it's diagonal in the case that the input is a scaled unitary).  So our `Operator.power` code fails for non-unitary operators.\n\n### How can we reproduce the issue?\n\n```python\n>>> from qiskit.quantum_info import Operator\n>>> import scipy.linalg\n>>> # Not unitary.\n>>> data = [[1, 1], [0, -1]]\n>>> print(Operator(data).power(0.5).data)\n[[1.000000e+00+0.j 0.000000e+00+0.j]\n [0.000000e+00+0.j 6.123234e-17+1.j]]\n>>> print(scipy.linalg.fractional_matrix_power(data, 0.5)))\n[[1.00000000e+00+0.j  5.00000000e-01-0.5j]\n [0.00000000e+00+0.j  4.93096709e-17+1.j ]]\n```\n\n### What should happen?\n\nThe two should give (approximately) equal results (though see #13305 for branch-cut problems).\n\n### Any suggestions?\n\nThe ability to do fractional matrix powers of `Operator` is relatively recent (#11534), and it seems to have pulled the code from `Gate.power`, which _was_ empowered to make that assumption.\n\nWe probably should just use `scipy.linalg.fractional_matrix_power` for `Operator.power`, and specialise `Gate.power` to use a more explicit eigenvector/eigenvalue form, since it's allowed to assume that its input was unitary.

diff --git a/qiskit/circuit/gate.py b/qiskit/circuit/gate.py\nindex 2f1c09668252..122db0973124 100644\n--- a/qiskit/circuit/gate.py\n+++ b/qiskit/circuit/gate.py\n@@ -89,7 +89,9 @@ def power(self, exponent: float, annotated: bool = False):\n         from qiskit.circuit.library.generalized_gates.unitary import UnitaryGate\n \n         if not annotated:\n-            return UnitaryGate(Operator(self).power(exponent), label=f"{self.name}^{exponent}")\n+            return UnitaryGate(\n+                Operator(self).power(exponent, assume_unitary=True), label=f"{self.name}^{exponent}"\n+            )\n         else:\n             return AnnotatedOperation(self, PowerModifier(exponent))\n \ndiff --git a/qiskit/quantum_info/operators/operator.py b/qiskit/quantum_info/operators/operator.py\nindex 3b6ec8abff89..d549a130ae3a 100644\n--- a/qiskit/quantum_info/operators/operator.py\n+++ b/qiskit/quantum_info/operators/operator.py\n@@ -541,7 +541,9 @@ def compose(self, other: Operator, qargs: list | None = None, front: bool = Fals\n         ret._op_shape = new_shape\n         return ret\n \n-    def power(self, n: float, branch_cut_rotation=cmath.pi * 1e-12) -> Operator:\n+    def power(\n+        self, n: float, branch_cut_rotation=cmath.pi * 1e-12, assume_unitary=False\n+    ) -> Operator:\n         """Return the matrix power of the operator.\n \n         Non-integer powers of operators with an eigenvalue whose complex phase is :math:`\\\\pi` have\n@@ -572,6 +574,8 @@ def power(self, n: float, branch_cut_rotation=cmath.pi * 1e-12) -> Operator:\n                 complex plane.  This shifts the branch cut away from the common point of :math:`-1`,\n                 but can cause a different root to be selected as the principal root.  The rotation\n                 is anticlockwise, following the standard convention for complex phase.\n+            assume_unitary (bool): if ``True``, the operator is assumed to be unitary. In this case,\n+                for fractional powers we employ a faster implementation based on Schur's decomposition.\n \n         Returns:\n             Operator: the resulting operator ``O ** n``.\n@@ -579,6 +583,11 @@ def power(self, n: float, branch_cut_rotation=cmath.pi * 1e-12) -> Operator:\n         Raises:\n             QiskitError: if the input and output dimensions of the operator\n                          are not equal.\n+\n+        .. note::\n+            It is only safe to set the argument ``assume_unitary`` to ``True`` when the operator\n+            is unitary (or, more generally, normal). Otherwise, the function will return an\n+            incorrect output.\n         """\n         if self.input_dims() != self.output_dims():\n             raise QiskitError("Can only power with input_dims = output_dims.")\n@@ -588,11 +597,23 @@ def power(self, n: float, branch_cut_rotation=cmath.pi * 1e-12) -> Operator:\n         else:\n             import scipy.linalg\n \n-            ret._data = cmath.rect(\n-                1, branch_cut_rotation * n\n-            ) * scipy.linalg.fractional_matrix_power(\n-                cmath.rect(1, -branch_cut_rotation) * self.data, n\n-            )\n+            if assume_unitary:\n+                # Experimentally, for fractional powers this seems to be 3x faster than\n+                # calling scipy.linalg.fractional_matrix_power(self.data, exponent)\n+                decomposition, unitary = scipy.linalg.schur(\n+                    cmath.rect(1, -branch_cut_rotation) * self.data, output="complex"\n+                )\n+                decomposition_diagonal = decomposition.diagonal()\n+                decomposition_power = [pow(element, n) for element in decomposition_diagonal]\n+                unitary_power = unitary @ np.diag(decomposition_power) @ unitary.conj().T\n+                ret._data = cmath.rect(1, branch_cut_rotation * n) * unitary_power\n+            else:\n+                ret._data = cmath.rect(\n+                    1, branch_cut_rotation * n\n+                ) * scipy.linalg.fractional_matrix_power(\n+                    cmath.rect(1, -branch_cut_rotation) * self.data, n\n+                )\n+\n         return ret\n \n     def tensor(self, other: Operator) -> Operator:\ndiff --git a/releasenotes/notes/operator-power-assume-unitary-0a2f97ea9de91b49.yaml b/releasenotes/notes/operator-power-assume-unitary-0a2f97ea9de91b49.yaml\nnew file mode 100644\nindex 000000000000..780781c212a7\n--- /dev/null\n+++ b/releasenotes/notes/operator-power-assume-unitary-0a2f97ea9de91b49.yaml\n@@ -0,0 +1,6 @@\n+---\n+features_quantum_info:\n+  - |\n+    Added a new argument ``assume_unitary`` to :meth:`qiskit.quantum_info.Operator.power`.\n+    When ``True``, we use a faster method based on Schur's decomposition to raise an\n+    ``Operator`` to a fractional power.\n

diff --git a/test/python/quantum_info/operators/test_operator.py b/test/python/quantum_info/operators/test_operator.py\nindex d9423d0ec141..7bb0db110ac1 100644\n--- a/test/python/quantum_info/operators/test_operator.py\n+++ b/test/python/quantum_info/operators/test_operator.py\n@@ -522,8 +522,16 @@ def test_power_of_nonunitary(self):\n         expected = Operator([[1.0 + 0.0j, 0.5 - 0.5j], [0.0 + 0.0j, 0.0 + 1.0j]])\n         assert_allclose(powered.data, expected.data)\n \n-    @ddt.data(0.5, 1.0 / 3.0, 0.25)\n-    def test_root_stability(self, root):\n+    @ddt.data(\n+        (0.5, False),\n+        (1.0 / 3.0, False),\n+        (0.25, False),\n+        (0.5, True),\n+        (1.0 / 3.0, True),\n+        (0.25, True),\n+    )\n+    @ddt.unpack\n+    def test_root_stability(self, root, assume_unitary):\n         """Test that the root of operators that have eigenvalues that are -1 up to floating-point\n         imprecision stably choose the positive side of the principal-root branch cut."""\n         rng = np.random.default_rng(2024_10_22)\n@@ -540,17 +548,24 @@ def test_root_stability(self, root):\n         matrices = [basis.conj().T @ np.diag(eigenvalues) @ basis for basis in bases]\n         expected = [basis.conj().T @ np.diag(root_eigenvalues) @ basis for basis in bases]\n         self.assertEqual(\n-            [Operator(matrix).power(root) for matrix in matrices],\n+            [Operator(matrix).power(root, assume_unitary=assume_unitary) for matrix in matrices],\n             [Operator(single) for single in expected],\n         )\n \n-    def test_root_branch_cut(self):\n+    @ddt.data(True, False)\n+    def test_root_branch_cut(self, assume_unitary):\n         """Test that we can choose where the branch cut appears in the root."""\n         z_op = Operator(library.ZGate())\n         # Depending on the direction we move the branch cut, we should be able to select the root to\n         # be either of the two valid options.\n-        self.assertEqual(z_op.power(0.5, branch_cut_rotation=1e-3), Operator(library.SGate()))\n-        self.assertEqual(z_op.power(0.5, branch_cut_rotation=-1e-3), Operator(library.SdgGate()))\n+        self.assertEqual(\n+            z_op.power(0.5, branch_cut_rotation=1e-3, assume_unitary=assume_unitary),\n+            Operator(library.SGate()),\n+        )\n+        self.assertEqual(\n+            z_op.power(0.5, branch_cut_rotation=-1e-3, assume_unitary=assume_unitary),\n+            Operator(library.SdgGate()),\n+        )\n \n     def test_expand(self):\n         """Test expand method."""\n


Fix the calculation for identity operator's expectation value (Qiskit/qiskit#13345)

<!--\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- [ ] 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\nFix a bug in `qiskit.quantum_info` in the calculation of expectation value of the identity operator.\r\n\r\nFixes #13029 \r\n\r\n\r\n\r\n### Details and comments\r\nCurrently,  `qiskit.quantum_info` yields incorrect results for the expectation value of an identity operator for a given state vector. It returns the norm of the state vector; however, it ought to return the square of the norm.\r\n\r\nTherefore, I am changing the following line  \r\n\r\nhttps://github.com/Qiskit/qiskit/blob/1be2c5aa103f1991f2fdc75a77a09f82635b8431/qiskit/quantum_info/states/statevector.py#L478-L479\r\n\r\nto \r\n```Python\r\n   if x_mask + z_mask == 0: \r\n       return pauli_phase * np.linalg.norm(self.data) ** 2\r\n```\r\n

Statevector._expectation_value_pauli returns incorrect result in certain cases

The calculation for the expectation value of the __identity operator__ seems incorrect when the statevector is __not normalized__ to 1.\r\n\r\nI think the reason is that one should use the norm squared rather than the norm in the following lines.\r\nhttps://github.com/Qiskit/qiskit/blob/1be2c5aa103f1991f2fdc75a77a09f82635b8431/qiskit/quantum_info/states/statevector.py#L478-L479\r\n\r\n---\r\n\r\nThe whole function is attached below to provide context.\r\n\r\nhttps://github.com/Qiskit/qiskit/blob/1be2c5aa103f1991f2fdc75a77a09f82635b8431/qiskit/quantum_info/states/statevector.py#L458-L491

diff --git a/qiskit/quantum_info/states/statevector.py b/qiskit/quantum_info/states/statevector.py\nindex 901ce95af424..1265d677abe4 100644\n--- a/qiskit/quantum_info/states/statevector.py\n+++ b/qiskit/quantum_info/states/statevector.py\n@@ -476,7 +476,7 @@ def _expectation_value_pauli(self, pauli, qargs=None):\n         pauli_phase = (-1j) ** pauli.phase if pauli.phase else 1\n \n         if x_mask + z_mask == 0:\n-            return pauli_phase * np.linalg.norm(self.data)\n+            return pauli_phase * np.linalg.norm(self.data) ** 2\n \n         if x_mask == 0:\n             return pauli_phase * expval_pauli_no_x(self.data, self.num_qubits, z_mask)\n

diff --git a/releasenotes/notes/fix_identity_operator_9e2ec9770ac046a6.yaml b/releasenotes/notes/fix_identity_operator_9e2ec9770ac046a6.yaml\nnew file mode 100644\nindex 000000000000..69779a793cc3\n--- /dev/null\n+++ b/releasenotes/notes/fix_identity_operator_9e2ec9770ac046a6.yaml\n@@ -0,0 +1,5 @@\n+---\n+fixes:\n+  - |\n+    Fixed a bug that caused :meth:`.Statevector.expectation_value` to yield incorrect results\n+    for the identity operator when the statevector was not normalized.\ndiff --git a/test/python/quantum_info/states/test_statevector.py b/test/python/quantum_info/states/test_statevector.py\nindex 29d5d42f3783..d16b3b3453ec 100644\n--- a/test/python/quantum_info/states/test_statevector.py\n+++ b/test/python/quantum_info/states/test_statevector.py\n@@ -1152,6 +1152,31 @@ def test_expval_pauli_qargs(self, qubits):\n         expval = state.expectation_value(op, qubits)\n         self.assertAlmostEqual(expval, target)\n \n+    def test_expval_identity(self):\n+        """Test whether the calculation for identity operator has been fixed"""\n+\n+        # 1 qubit case test\n+        state_1 = Statevector.from_label("0")\n+        state_1_n1 = 2 * state_1  # test the same state with different norms\n+        state_1_n2 = (1 + 2j) * state_1\n+        identity_op_1 = SparsePauliOp.from_list([("I", 1)])\n+        expval_state_1 = state_1.expectation_value(identity_op_1)\n+        expval_state_1_n1 = state_1_n1.expectation_value(identity_op_1)\n+        expval_state_1_n2 = state_1_n2.expectation_value(identity_op_1)\n+        self.assertAlmostEqual(expval_state_1, 1.0 + 0j)\n+        self.assertAlmostEqual(expval_state_1_n1, 4 + 0j)\n+        self.assertAlmostEqual(expval_state_1_n2, 5 + 0j)\n+\n+        # Let's try a multi-qubit case\n+        n_qubits = 3\n+        state_coeff = 3 - 4j\n+        op_coeff = 2 - 2j\n+        state_test = state_coeff * Statevector.from_label("0" * n_qubits)\n+        op_test = SparsePauliOp.from_list([("I" * n_qubits, op_coeff)])\n+        expval = state_test.expectation_value(op_test)\n+        target = op_coeff * np.abs(state_coeff) ** 2\n+        self.assertAlmostEqual(expval, target)\n+\n     @data(*(qargs for i in range(4) for qargs in permutations(range(4), r=i + 1)))\n     def test_probabilities_qargs(self, qargs):\n         """Test probabilities method with qargs"""\n


Fix the calculation for identity operator's expectation value (backport #13345) (Qiskit/qiskit#13436)

\n\n### Summary\n\nFix a bug in `qiskit.quantum_info` in the calculation of expectation value of the identity operator.\n\nFixes #13029 \n\n\n\n### Details and comments\nCurrently,  `qiskit.quantum_info` yields incorrect results for the expectation value of an identity operator for a given state vector. It returns the norm of the state vector; however, it ought to return the square of the norm.\n\nTherefore, I am changing the following line  \n\nhttps://github.com/Qiskit/qiskit/blob/1be2c5aa103f1991f2fdc75a77a09f82635b8431/qiskit/quantum_info/states/statevector.py#L478-L479\n\nto \n```Python\n   if x_mask + z_mask == 0: \n       return pauli_phase * np.linalg.norm(self.data) ** 2\n```\n<hr>This is an automatic backport of pull request #13345 done by [Mergify](https://mergify.com).

Statevector._expectation_value_pauli returns incorrect result in certain cases

The calculation for the expectation value of the __identity operator__ seems incorrect when the statevector is __not normalized__ to 1.\r\n\r\nI think the reason is that one should use the norm squared rather than the norm in the following lines.\r\nhttps://github.com/Qiskit/qiskit/blob/1be2c5aa103f1991f2fdc75a77a09f82635b8431/qiskit/quantum_info/states/statevector.py#L478-L479\r\n\r\n---\r\n\r\nThe whole function is attached below to provide context.\r\n\r\nhttps://github.com/Qiskit/qiskit/blob/1be2c5aa103f1991f2fdc75a77a09f82635b8431/qiskit/quantum_info/states/statevector.py#L458-L491

diff --git a/qiskit/quantum_info/states/statevector.py b/qiskit/quantum_info/states/statevector.py\nindex 901ce95af424..1265d677abe4 100644\n--- a/qiskit/quantum_info/states/statevector.py\n+++ b/qiskit/quantum_info/states/statevector.py\n@@ -476,7 +476,7 @@ def _expectation_value_pauli(self, pauli, qargs=None):\n         pauli_phase = (-1j) ** pauli.phase if pauli.phase else 1\n \n         if x_mask + z_mask == 0:\n-            return pauli_phase * np.linalg.norm(self.data)\n+            return pauli_phase * np.linalg.norm(self.data) ** 2\n \n         if x_mask == 0:\n             return pauli_phase * expval_pauli_no_x(self.data, self.num_qubits, z_mask)\n


Correctly updating global phase when removing gates that are identity up to a global phase (backport #13785) (Qiskit/qiskit#13825)

\n\n### Summary\n\nThis fixes a bug in the ``RemoveIdentityEquivalent`` transpiler pass (the pass was introduced in #12384), where unitary gates close to identity _up to a global phase_ were removed from the circuit but the global phase of the circuit was not updated. This includes, for instance, single-qubit unitary gates with matrices close to [[ $e^{i\\theta}$, 0], [0, $e^{i\\theta}$]] for fix floating-point values of $\\theta$, standard gates $R_X(2\\pi)$, $R_Y(2\\pi)$, $R_Z(2\\pi)$ (whose matrices are close to $-I$), and some others. Now the pass removes these gates and correctly updates the global phase of the circuit. In particular, the pass now handles non-parameterized ``GlobalPhaseGates`` as well.  \n\nFixes #13778.\n\n### Details and comments:\n\nThe pass ignores parameterized gates. We could probably relatively easily extend it to handle parameterized global phase gates.\n\nThe thing to notice [in the current documentation of the pass](https://docs.quantum.ibm.com/api/qiskit/qiskit.transpiler.passes.RemoveIdentityEquivalent) is that the current condition for removing a gate is indeed satisfied for gates of the form $e^{i\\theta} I$. And in fact the opposite is also true (thanks to @ShellyGarion for helping me work out the math): if the removal condition is satisfied, then a gate is necessarily of the form $e^{i\\theta} I$.\n\nLol, yet another change after the #13759 was merged is to now also explicitly handle Unitary gates.\n\nThanks to @Cryoris and @ShellyGarion for joint debugging of the problem. \n<hr>This is an automatic backport of pull request #13785 done by [Mergify](https://mergify.com).

incorrect statevector simulation using qiskit & qiskit-aer

### Environment\n\n- **Qiskit version**: 1.3.0 (I believe the bug still exists in 1.3.2)\n- **Python version**: 3.12\n- **Operating system**: linux\n\nmirror ticket from https://github.com/Qiskit/qiskit-aer/issues/2271 for visibility\n\n### What is happening?\n\nFor a fixed QuantumCircuit with custom unitary gate matrix, QiskitAer is producing different statevector simulation results for qiskit v.1.2.4 and v.1.3.0\n\n### How can we reproduce the issue?\n\nUsing `qiskit-aer=0.15.1`, run the following script with `qiskit==1.2.4` and `qiskit==1.3.0` will reproduce different statevector results\n```python\nimport qiskit\n\nfrom qiskit_aer import AerSimulator\nfrom qiskit.circuit.library import UnitaryGate\nfrom qiskit.quantum_info import random_unitary\n\nimport numpy as np\n\ndef get_qiskit_unitary_gate(rng, control=None):\n    rand_unitary = random_unitary(4, seed=rng)\n    gate = UnitaryGate(rand_unitary)\n    print(f"unitary gate matrix:")\n    print(gate)\n    if control is None:\n        return gate\n    else:\n        return gate.control(control)\n    \ndef get_qiskit_multi_control_circuit():\n    qubits = qiskit.QuantumRegister(5)\n    circuit = qiskit.QuantumCircuit(qubits)\n    for q in qubits:\n        circuit.h(q)\n    qs = list(qubits)\n\n    rng = np.random.default_rng(1234)\n    for i in range(2):\n        rng.shuffle(qs)\n        ccu_gate = get_qiskit_unitary_gate(rng, control=2)\n        print(f"{i}th gate on {qs}")\n        circuit.append(ccu_gate, qs[:4])\n    circuit.p(0.1, qubits[0])\n    return circuit\n\nprint(f"{qiskit.__version__=}")\n\ncircuit = get_qiskit_multi_control_circuit()\nprint(circuit)\n\ncircuit.save_statevector()\n\n# Transpile for simulator\nsimulator = AerSimulator(method='statevector')\ncirc = qiskit.transpile(circuit, simulator)\n\n# Run and get statevector\nresult = simulator.run(circ).result()\nsv = np.asarray(result.get_statevector(circ))\nprint("Sum of state vector:", sv.sum())\n```\n\n### What should happen?\n\nThe gate matrices defining the custom unitary gates are identical between `qiskit==1.2.4` and `qiskit==1.3.0` and therefore the returned statevector should be the same. On my end I have the following results:\n\n`qiskit==1.2.4`\n```\nqiskit.__version__='1.2.4'\nunitary gate matrix:\nInstruction(name='unitary', num_qubits=2, num_clbits=0, params=[array([[ 0.26093746+0.18318812j,  0.12558674-0.38936694j,\n         0.56486648-0.53659333j, -0.03587912+0.35025885j],\n       [-0.5208327 +0.41955884j,  0.38293134-0.20373037j,\n        -0.26997299-0.01611016j,  0.47100825+0.26375647j],\n       [-0.18047959-0.0114164j ,  0.42182388+0.05310412j,\n         0.01892911-0.48459796j, -0.10334574-0.73530097j],\n       [-0.44557601+0.46884564j, -0.63238151+0.25262043j,\n         0.15882664-0.24378088j, -0.17446941-0.05317653j]])])\n0th gate on [Qubit(QuantumRegister(5, 'q1'), 0), Qubit(QuantumRegister(5, 'q1'), 4), Qubit(QuantumRegister(5, 'q1'), 1), Qubit(QuantumRegister(5, 'q1'), 3), Qubit(QuantumRegister(5, 'q1'), 2)]\nunitary gate matrix:\nInstruction(name='unitary', num_qubits=2, num_clbits=0, params=[array([[-0.41490662-0.1027918j , -0.45229939-0.45892536j,\n         0.1965442 +0.27907236j,  0.53283662+0.04090535j],\n       [-0.54960898-0.38886747j, -0.18060452+0.35532657j,\n        -0.42299907-0.41452735j,  0.00811512+0.19238008j],\n       [-0.48710529+0.16105765j,  0.30802545-0.50934349j,\n         0.16188328-0.37911161j, -0.36497996-0.28166686j],\n       [ 0.16591044+0.27066769j,  0.26600357-0.02948626j,\n         0.10303047-0.5934482j ,  0.6357136 +0.24628756j]])])\n1th gate on [Qubit(QuantumRegister(5, 'q1'), 1), Qubit(QuantumRegister(5, 'q1'), 4), Qubit(QuantumRegister(5, 'q1'), 2), Qubit(QuantumRegister(5, 'q1'), 0), Qubit(QuantumRegister(5, 'q1'), 3)]                      \nSum of state vector: (3.450103311811794-0.308774856832824j)\n```\n\n`qiskit==1.3.0`\n```\nqiskit.__version__='1.3.0'\nunitary gate matrix:\nInstruction(name='unitary', num_qubits=2, num_clbits=0, params=[array([[ 0.26093746+0.18318812j,  0.12558674-0.38936694j,\n         0.56486648-0.53659333j, -0.03587912+0.35025885j],\n       [-0.5208327 +0.41955884j,  0.38293134-0.20373037j,\n        -0.26997299-0.01611016j,  0.47100825+0.26375647j],\n       [-0.18047959-0.0114164j ,  0.42182388+0.05310412j,\n         0.01892911-0.48459796j, -0.10334574-0.73530097j],\n       [-0.44557601+0.46884564j, -0.63238151+0.25262043j,\n         0.15882664-0.24378088j, -0.17446941-0.05317653j]])])\n0th gate on [Qubit(QuantumRegister(5, 'q1'), 0), Qubit(QuantumRegister(5, 'q1'), 4), Qubit(QuantumRegister(5, 'q1'), 1), Qubit(QuantumRegister(5, 'q1'), 3), Qubit(QuantumRegister(5, 'q1'), 2)]\nunitary gate matrix:\nInstruction(name='unitary', num_qubits=2, num_clbits=0, params=[array([[-0.41490662-0.1027918j , -0.45229939-0.45892536j,\n         0.1965442 +0.27907236j,  0.53283662+0.04090535j],\n       [-0.54960898-0.38886747j, -0.18060452+0.35532657j,\n        -0.42299907-0.41452735j,  0.00811512+0.19238008j],\n       [-0.48710529+0.16105765j,  0.30802545-0.50934349j,\n         0.16188328-0.37911161j, -0.36497996-0.28166686j],\n       [ 0.16591044+0.27066769j,  0.26600357-0.02948626j,\n         0.10303047-0.5934482j ,  0.6357136 +0.24628756j]])])\n1th gate on [Qubit(QuantumRegister(5, 'q1'), 1), Qubit(QuantumRegister(5, 'q1'), 4), Qubit(QuantumRegister(5, 'q1'), 2), Qubit(QuantumRegister(5, 'q1'), 0), Qubit(QuantumRegister(5, 'q1'), 3)]                        \nSum of state vector: (2.6579282427026834+2.2212546524499133j)\n```\n\nFrom our experiment, I believe the results from `qiskit=1.2.4` are correct. There might be some parsing issue between `qiskit-aer` and `qiskit==1.3.0`\n\n### Any suggestions?\n\n_No response_

diff --git a/crates/accelerate/src/remove_identity_equiv.rs b/crates/accelerate/src/remove_identity_equiv.rs\nindex a3eb921628e2..d5d644919c8f 100644\n--- a/crates/accelerate/src/remove_identity_equiv.rs\n+++ b/crates/accelerate/src/remove_identity_equiv.rs\n@@ -9,7 +9,6 @@\n // Any modifications or derivative works of this code must retain this\n // copyright notice, and modified files need to carry a notice indicating\n // that they have been altered from the originals.\n-\n use num_complex::Complex64;\n use num_complex::ComplexFloat;\n use pyo3::prelude::*;\n@@ -27,11 +26,13 @@ use qiskit_circuit::packed_instruction::PackedInstruction;\n #[pyfunction]\n #[pyo3(signature=(dag, approx_degree=Some(1.0), target=None))]\n fn remove_identity_equiv(\n+    py: Python,\n     dag: &mut DAGCircuit,\n     approx_degree: Option<f64>,\n     target: Option<&Target>,\n ) {\n     let mut remove_list: Vec<NodeIndex> = Vec::new();\n+    let mut global_phase_update: f64 = 0.;\n \n     let get_error_cutoff = |inst: &PackedInstruction| -> f64 {\n         match approx_degree {\n@@ -76,12 +77,17 @@ fn remove_identity_equiv(\n \n     for op_node in dag.op_nodes(false) {\n         let inst = dag.dag()[op_node].unwrap_operation();\n-        match inst.op.view() {\n+        if inst.is_parameterized() {\n+            // Skip parameterized gates\n+            continue;\n+        }\n+        let view = inst.op.view();\n+        match view {\n             OperationRef::Standard(gate) => {\n                 let (dim, trace) = match gate {\n                     StandardGate::RXGate | StandardGate::RYGate | StandardGate::RZGate => {\n                         if let Param::Float(theta) = inst.params_view()[0] {\n-                            let trace = (theta / 2.).cos() * 2.;\n+                            let trace = Complex64::new((theta / 2.).cos() * 2., 0.);\n                             (2., trace)\n                         } else {\n                             continue;\n@@ -92,20 +98,16 @@ fn remove_identity_equiv(\n                     | StandardGate::RZZGate\n                     | StandardGate::RZXGate => {\n                         if let Param::Float(theta) = inst.params_view()[0] {\n-                            let trace = (theta / 2.).cos() * 4.;\n+                            let trace = Complex64::new((theta / 2.).cos() * 4., 0.);\n                             (4., trace)\n                         } else {\n                             continue;\n                         }\n                     }\n                     _ => {\n-                        // Skip global phase gate\n-                        if gate.num_qubits() < 1 {\n-                            continue;\n-                        }\n                         if let Some(matrix) = gate.matrix(inst.params_view()) {\n                             let dim = matrix.shape()[0] as f64;\n-                            let trace = matrix.diag().iter().sum::<Complex64>().abs();\n+                            let trace = matrix.diag().iter().sum::<Complex64>();\n                             (dim, trace)\n                         } else {\n                             continue;\n@@ -113,34 +115,38 @@ fn remove_identity_equiv(\n                     }\n                 };\n                 let error = get_error_cutoff(inst);\n-                let f_pro = (trace / dim).powi(2);\n+                let f_pro = (trace / dim).abs().powi(2);\n                 let gate_fidelity = (dim * f_pro + 1.) / (dim + 1.);\n                 if (1. - gate_fidelity).abs() < error {\n-                    remove_list.push(op_node)\n+                    remove_list.push(op_node);\n+                    global_phase_update += (trace / dim).arg();\n                 }\n             }\n-            OperationRef::Gate(gate) => {\n-                // Skip global phase like gate\n-                if gate.num_qubits() < 1 {\n-                    continue;\n-                }\n-                if let Some(matrix) = gate.matrix(inst.params_view()) {\n+            _ => {\n+                let matrix = view.matrix(inst.params_view());\n+                // If view.matrix() returns None, then there is no matrix and we skip the operation.\n+                if let Some(matrix) = matrix {\n                     let error = get_error_cutoff(inst);\n                     let dim = matrix.shape()[0] as f64;\n                     let trace: Complex64 = matrix.diag().iter().sum();\n                     let f_pro = (trace / dim).abs().powi(2);\n                     let gate_fidelity = (dim * f_pro + 1.) / (dim + 1.);\n                     if (1. - gate_fidelity).abs() < error {\n-                        remove_list.push(op_node)\n+                        remove_list.push(op_node);\n+                        global_phase_update += (trace / dim).arg();\n                     }\n                 }\n             }\n-            _ => continue,\n         }\n     }\n     for node in remove_list {\n         dag.remove_op_node(node);\n     }\n+\n+    if global_phase_update != 0. {\n+        dag.add_global_phase(py, &Param::Float(global_phase_update))\n+            .expect("The global phase is guaranteed to be a float");\n+    }\n }\n \n pub fn remove_identity_equiv_mod(m: &Bound<PyModule>) -> PyResult<()> {\ndiff --git a/qiskit/transpiler/passes/optimization/remove_identity_equiv.py b/qiskit/transpiler/passes/optimization/remove_identity_equiv.py\nindex fbf132d958a2..17445eb5ad2a 100644\n--- a/qiskit/transpiler/passes/optimization/remove_identity_equiv.py\n+++ b/qiskit/transpiler/passes/optimization/remove_identity_equiv.py\n@@ -23,9 +23,8 @@\n class RemoveIdentityEquivalent(TransformationPass):\n     r"""Remove gates with negligible effects.\n \n-    Removes gates whose effect is close to an identity operation, up to the specified\n-    tolerance. Zero qubit gates such as :class:`.GlobalPhaseGate` are not considered\n-    by this pass.\n+    Removes gates whose effect is close to an identity operation up to a global phase\n+    and up to the specified tolerance. Parameterized gates are not considered by this pass.\n \n     For a cutoff fidelity :math:`f`, this pass removes gates whose average\n     gate fidelity with respect to the identity is below :math:`f`. Concretely,\ndiff --git a/releasenotes/notes/fix-phase-in-remove-id-equivalent-6480da0c62f20df1.yaml b/releasenotes/notes/fix-phase-in-remove-id-equivalent-6480da0c62f20df1.yaml\nnew file mode 100644\nindex 000000000000..3ee4c4accc21\n--- /dev/null\n+++ b/releasenotes/notes/fix-phase-in-remove-id-equivalent-6480da0c62f20df1.yaml\n@@ -0,0 +1,8 @@\n+---\n+fixes:\n+  - |\n+    Fixed a bug in the :class:`.RemoveIdentityEquivalent` transpiler pass, where gates close\n+    to identity up to a global phase were removed from the circuit,\n+    but the global phase of the circuit was not updated. In particular,\n+    :class:`.RemoveIdentityEquivalent` now removes non-parameterized :class:`.GlobalPhaseGate`\n+    gates.\n

diff --git a/test/python/transpiler/test_remove_identity_equivalent.py b/test/python/transpiler/test_remove_identity_equivalent.py\nindex 1db392d3654b..736c02b765bd 100644\n--- a/test/python/transpiler/test_remove_identity_equivalent.py\n+++ b/test/python/transpiler/test_remove_identity_equivalent.py\n@@ -12,6 +12,7 @@\n \n """Tests for the DropNegligible transpiler pass."""\n \n+import ddt\n import numpy as np\n \n from qiskit.circuit import Parameter, QuantumCircuit, QuantumRegister, Gate\n@@ -26,6 +27,7 @@\n     XXMinusYYGate,\n     XXPlusYYGate,\n     GlobalPhaseGate,\n+    UnitaryGate,\n )\n from qiskit.quantum_info import Operator\n from qiskit.transpiler.passes import RemoveIdentityEquivalent\n@@ -34,6 +36,7 @@\n from test import QiskitTestCase  # pylint: disable=wrong-import-order\n \n \n+@ddt.ddt\n class TestDropNegligible(QiskitTestCase):\n     """Test the DropNegligible pass."""\n \n@@ -173,13 +176,33 @@ def to_matrix(self):\n         expected = QuantumCircuit(3)\n         self.assertEqual(expected, transpiled)\n \n-    def test_global_phase_ignored(self):\n-        """Test that global phase gate isn't considered."""\n+    @ddt.data(\n+        RXGate(0),\n+        RXGate(2 * np.pi),\n+        RYGate(0),\n+        RYGate(2 * np.pi),\n+        RZGate(0),\n+        RZGate(2 * np.pi),\n+        UnitaryGate(np.array([[1, 0], [0, 1]])),\n+        UnitaryGate(np.array([[-1, 0], [0, -1]])),\n+        UnitaryGate(np.array([[np.exp(1j * np.pi / 4), 0], [0, np.exp(1j * np.pi / 4)]])),\n+        GlobalPhaseGate(0),\n+        GlobalPhaseGate(np.pi / 4),\n+    )\n+    def test_remove_identity_up_to_global_phase(self, gate):\n+        """Test that gates equivalent to identity up to a global phase are removed from the circuit,\n+        and the global phase of the circuit is updated correctly.\n+        """\n+        qc = QuantumCircuit(gate.num_qubits)\n+        qc.append(gate, qc.qubits)\n+        transpiled = RemoveIdentityEquivalent()(qc)\n+        self.assertEqual(transpiled.size(), 0)\n+        self.assertEqual(Operator(qc), Operator(transpiled))\n \n+    def test_parameterized_global_phase_ignored(self):\n+        """Test that parameterized global phase gates are not removed by the pass."""\n+        theta = Parameter("theta")\n         qc = QuantumCircuit(1)\n-        qc.id(0)\n-        qc.append(GlobalPhaseGate(0))\n+        qc.append(GlobalPhaseGate(theta), [])\n         transpiled = RemoveIdentityEquivalent()(qc)\n-        expected = QuantumCircuit(1)\n-        expected.append(GlobalPhaseGate(0))\n-        self.assertEqual(transpiled, expected)\n+        self.assertEqual(qc, transpiled)\n


Fix tracking of routing permutation in Sabre with disjoint backends (backport #13833) (Qiskit/qiskit#13835)

### Summary\n\nIf the backing coupling graph is disjoint, and unused components of the coupling graph would not be considered when constructing the complete routing permutation.  In practice, Sabre aborts immediately after layout without attempting to route, if it needed to split the DAG across more than one disjoint component, because it can't guarantee correctness of the final routing in the presence of component-spanning barriers or classical communication, so the only way for a component to be forgotten is if the backend is disjoint, but the DAG fits into a single component.\n\n\n\n\n### Details and comments\n\nFix #13732, though there's a _whole_ cornucopia of bugs split between very old versions of Qiskit, this commit's parent, and the example in the issue itself that lead from that issue seeing a regression in Qiskit 1.3 to this one.\n\nIn actuality, this bug has been around since the disjoint-coupling handling was first added in #9802 (qiskit-terra 0.24), and the test case in this PR would fail that version too (the fact that `TranspileLayout.initial_index_layout` wasn't added til later notwithstanding - the first assertion fails).<hr>This is an automatic backport of pull request #13833 done by [Mergify](https://mergify.com).

Error when running `final_index_layout(filter_ancillas=False)`

### Environment\n\n- **Qiskit version**: 1.3.2\n- **Python version**:  3.11\n- **Operating system**: macos/linux\n\n\n### What is happening?\n\nOn certain circuits transpiled with a backend and a coupling map, this call fails\n```\ntranspiled_circuit.layout.final_index_layout(filter_ancillas=False)\n```\nraising `KeyError: "The item Qubit(QuantumRegister(5, 'q'), 3) does not exist in the Layout"`.\n\nThis worked fine until qiskit 1.3.0\n\n### How can we reproduce the issue?\n\nRunning the following code:\n\n```\nfrom qiskit import QuantumCircuit, transpile, qasm2\nfrom qiskit_ibm_runtime import QiskitRuntimeService\n\nservice = QiskitRuntimeService(channel="ibm_quantum")\nbackend = service.backend("ibmq_manila")\n\nqc = qasm2.loads('OPENQASM 2.0;include "qelib1.inc";qreg q[3];rccx q[2],q[1],q[0];t q[2];u(0.28905709212845454,5.626877140821112,5.481851443455051) q[1];h q[0];cx q[1],q[2];s q[0];', custom_instructions=qasm2.LEGACY_CUSTOM_INSTRUCTIONS)\n\ntranspiled_circuit = transpile(qc,backend=backend,coupling_map=[[0,1],[1,2],[2,3]],optimization_level=2,seed_transpiler=42)\ntranspiled_circuit.layout.final_index_layout(filter_ancillas=False)\n```\n\n\n\n### What should happen?\n\nThere should not be any error in the call. The output to that particular example in `qiskit 1.2.4` is `[3, 1, 2, 0]`\n\n\n### Any suggestions?\n\n_No response_

diff --git a/qiskit/transpiler/passes/layout/sabre_layout.py b/qiskit/transpiler/passes/layout/sabre_layout.py\nindex af17cc226cb7..360e8d003e79 100644\n--- a/qiskit/transpiler/passes/layout/sabre_layout.py\n+++ b/qiskit/transpiler/passes/layout/sabre_layout.py\n@@ -328,6 +328,19 @@ def run(self, dag):\n                 for initial, final in enumerate(component.final_permutation)\n             }\n         )\n+\n+        # The coupling map may have been split into more components than the DAG.  In this case,\n+        # there will be some physical qubits unaccounted for in our `final_layout`.  Strictly the\n+        # `if` check is unnecessary, but we can avoid the loop for most circuits and backends.\n+        if len(final_layout) != len(physical_qubits):\n+            used_qubits = {\n+                qubit for component in components for qubit in component.coupling_map.graph.nodes()\n+            }\n+            for index, qubit in enumerate(physical_qubits):\n+                if index in used_qubits:\n+                    continue\n+                final_layout[qubit] = index\n+\n         if self.property_set["final_layout"] is None:\n             self.property_set["final_layout"] = final_layout\n         else:\ndiff --git a/releasenotes/notes/sabre-disjoint-routing-85c6f6481c9ffca4.yaml b/releasenotes/notes/sabre-disjoint-routing-85c6f6481c9ffca4.yaml\nnew file mode 100644\nindex 000000000000..e0618dd601f7\n--- /dev/null\n+++ b/releasenotes/notes/sabre-disjoint-routing-85c6f6481c9ffca4.yaml\n@@ -0,0 +1,15 @@\n+---\n+fixes:\n+  - |\n+    When :class:`.SabreLayout` is used to do both layout and routing simultaneously (as is the case\n+    for the default options to :func:`.transpile` and :func:`.generate_preset_pass_manager`) on a\n+    :class:`.Target` or :class:`.CouplingMap` with disjoint connectivity, and the input circuit fits\n+    into a single component of the coupling map, the routing permutation will now be tracked\n+    correctly.\n+\n+    Previously, any qubits in the coupling map that were not connected, even indirectly, to a qubit\n+    used by the routed circuit would not be included in the final routing permutation.  This could\n+    cause surprising behaviour a long way from the point of failure, even if compilation appeared to\n+    succeed, such as calls to :meth:`.TranspileLayout.final_index_layout` raising :exc:`KeyError`.\n+\n+    This bug did not affect backends that were fully connected, as most are.\n

diff --git a/test/python/transpiler/test_sabre_layout.py b/test/python/transpiler/test_sabre_layout.py\nindex 2942cab30cdd..932822af012b 100644\n--- a/test/python/transpiler/test_sabre_layout.py\n+++ b/test/python/transpiler/test_sabre_layout.py\n@@ -470,6 +470,21 @@ def test_with_partial_layout(self):\n         layout = pm.property_set["layout"]\n         self.assertEqual([layout[q] for q in qc.qubits], [3, 1, 2, 5, 4, 6, 7, 8])\n \n+    def test_dag_fits_in_one_component(self):\n+        """Test that the output is valid if the DAG all fits in a single component of a disjoint\n+        coupling map.."""\n+        qc = QuantumCircuit(3)\n+        qc.cx(0, 1)\n+        qc.cx(1, 2)\n+        qc.cx(2, 0)\n+\n+        disjoint = CouplingMap([(0, 1), (1, 2), (3, 4), (4, 5)])\n+        layout_routing_pass = SabreLayout(disjoint, seed=2025_02_12, swap_trials=1, layout_trials=1)\n+        out = layout_routing_pass(qc)\n+        self.assertEqual(len(out.layout.initial_layout), len(out.layout.final_layout))\n+        self.assertEqual(out.layout.initial_index_layout(filter_ancillas=False), [1, 0, 2, 3, 4, 5])\n+        self.assertEqual(out.layout.final_index_layout(filter_ancillas=False), [2, 0, 1, 3, 4, 5])\n+\n \n class TestSabrePreLayout(QiskitTestCase):\n     """Tests the SabreLayout pass with starting layout created by SabrePreLayout."""\n


Fix global phase update in `BasisTranslator` Pass (backport #14078) (Qiskit/qiskit#14092)

\n\n### Summary\n\nFixes #14074. \n\nThe `basis_translator` pass was not always updating the DAG's global phase as a part of  `replace_node`: this was only done when the target dag's global phase matched ``Param::ParameterExpression``. Updating the global phase in the case of ``Param::Float`` was missing, and this is fixed now. \n\nI am somewhat unsure if we also need to handle the ``Param::Obj`` case. I don't understand in what kind of a circuit we would see this, and I don't believe it's even supported, as per ``dag_circuit.rs.add_global_phase``, which throws an error in the case of ``Param::Obj``. Update: @jakelishman confirmed that ``Param::Obj`` is never permitted in ``global_phase``.\n\nUpdate: added release notes.\n<hr>This is an automatic backport of pull request #14078 done by [Mergify](https://mergify.com).

Transplie Rx introduce an incorrect global phase.

### Environment\n\n- **Qiskit version**: 1.4.2 \n- **Python version**: 2.9.0\n- **Operating system**: linux\n\n\n### What is happening?\n\nRx(theta) = H Rz(theta) H \n\nThis conversion theoretically does not generate any global phase. However, the transpiler from qiskit will generate an extra global phase pi here.\n\n### How can we reproduce the issue?\n\n```\nfrom qiskit import QuantumCircuit, transpile\nimport numpy as np\ntheta = 0.5*np.pi\ncircuit1 = QuantumCircuit(1)\ncircuit1.rx(theta, 0)\ncircuit2 = transpile(circuit1, basis_gates=['h','rz' ])\nglobal_phase = circuit2.global_phase-circuit1.global_phase\nprint(global_phase)\ncircuit2.draw('mpl')\n```\noutput: \n\n3.141592653589793\n\n![Image](https://github.com/user-attachments/assets/7a2d6cea-c838-4560-811f-299c3c73ff9c)\n\n### What should happen?\n\nDuring the transformation of Rx gate, the global phase may be added incorrectly, resulting in an error in the final result.\n\n### Any suggestions?\n\n_No response_

diff --git a/crates/accelerate/src/basis/basis_translator/mod.rs b/crates/accelerate/src/basis/basis_translator/mod.rs\nindex d47fc5e04022..1bc066b988c6 100644\n--- a/crates/accelerate/src/basis/basis_translator/mod.rs\n+++ b/crates/accelerate/src/basis/basis_translator/mod.rs\n@@ -762,38 +762,46 @@ fn replace_node(\n             )?;\n         }\n \n-        if let Param::ParameterExpression(old_phase) = target_dag.global_phase() {\n-            let bound_old_phase = old_phase.bind(py);\n-            let bind_dict = PyDict::new(py);\n-            for key in target_dag.global_phase().iter_parameters(py)? {\n-                let key = key?;\n-                bind_dict.set_item(&key, parameter_map.get_item(&key)?)?;\n-            }\n-            let mut new_phase: Bound<PyAny>;\n-            if bind_dict.values().iter().any(|param| {\n-                param\n-                    .is_instance(PARAMETER_EXPRESSION.get_bound(py))\n-                    .is_ok_and(|x| x)\n-            }) {\n-                new_phase = bound_old_phase.clone();\n-                for key_val in bind_dict.items() {\n-                    new_phase =\n-                        new_phase.call_method1(intern!(py, "assign"), key_val.downcast()?)?;\n+        match target_dag.global_phase() {\n+            Param::ParameterExpression(old_phase) => {\n+                let bound_old_phase = old_phase.bind(py);\n+                let bind_dict = PyDict::new(py);\n+                for key in target_dag.global_phase().iter_parameters(py)? {\n+                    let key = key?;\n+                    bind_dict.set_item(&key, parameter_map.get_item(&key)?)?;\n                 }\n-            } else {\n-                new_phase = bound_old_phase.call_method1(intern!(py, "bind"), (bind_dict,))?;\n-            }\n-            if !new_phase.getattr(intern!(py, "parameters"))?.is_truthy()? {\n-                new_phase = new_phase.call_method0(intern!(py, "numeric"))?;\n-                if new_phase.is_instance(&PyComplex::type_object(py))? {\n-                    return Err(TranspilerError::new_err(format!(\n-                        "Global phase must be real, but got {}",\n-                        new_phase.repr()?\n-                    )));\n+                let mut new_phase: Bound<PyAny>;\n+                if bind_dict.values().iter().any(|param| {\n+                    param\n+                        .is_instance(PARAMETER_EXPRESSION.get_bound(py))\n+                        .is_ok_and(|x| x)\n+                }) {\n+                    new_phase = bound_old_phase.clone();\n+                    for key_val in bind_dict.items() {\n+                        new_phase =\n+                            new_phase.call_method1(intern!(py, "assign"), key_val.downcast()?)?;\n+                    }\n+                } else {\n+                    new_phase = bound_old_phase.call_method1(intern!(py, "bind"), (bind_dict,))?;\n                 }\n+                if !new_phase.getattr(intern!(py, "parameters"))?.is_truthy()? {\n+                    new_phase = new_phase.call_method0(intern!(py, "numeric"))?;\n+                    if new_phase.is_instance(&PyComplex::type_object(py))? {\n+                        return Err(TranspilerError::new_err(format!(\n+                            "Global phase must be real, but got {}",\n+                            new_phase.repr()?\n+                        )));\n+                    }\n+                }\n+                let new_phase: Param = new_phase.extract()?;\n+                dag.add_global_phase(py, &new_phase)?;\n             }\n-            let new_phase: Param = new_phase.extract()?;\n-            dag.add_global_phase(py, &new_phase)?;\n+\n+            Param::Float(_) => {\n+                dag.add_global_phase(py, target_dag.global_phase())?;\n+            }\n+\n+            _ => {}\n         }\n     }\n \ndiff --git a/releasenotes/notes/fix-global-phase-in-basis-translator-d08d665198589828.yaml b/releasenotes/notes/fix-global-phase-in-basis-translator-d08d665198589828.yaml\nnew file mode 100644\nindex 000000000000..f73afd91e79e\n--- /dev/null\n+++ b/releasenotes/notes/fix-global-phase-in-basis-translator-d08d665198589828.yaml\n@@ -0,0 +1,6 @@\n+---\n+fixes:\n+  - |\n+    Fixed a problem in :class:`.BasisTranslator` transpiler pass, where the global\n+    phase of the DAG was not updated correctly. \n+    Fixed `#14074 <https://github.com/Qiskit/qiskit/issues/14074>`__.\n

diff --git a/test/python/transpiler/test_basis_translator.py b/test/python/transpiler/test_basis_translator.py\nindex a03765778a3f..751073deb28c 100644\n--- a/test/python/transpiler/test_basis_translator.py\n+++ b/test/python/transpiler/test_basis_translator.py\n@@ -1008,6 +1008,15 @@ def test_global_phase(self):\n         )\n         self.assertEqual(Operator(dag_to_circuit(out_dag)), Operator(expected))\n \n+    def test_rx_to_rz(self):\n+        """Verify global phase is updated correctly in basis translation.\n+        See https://github.com/Qiskit/qiskit/issues/14074."""\n+        theta = 0.5 * pi\n+        circ = QuantumCircuit(1)\n+        circ.rx(theta, 0)\n+        out_circ = BasisTranslator(std_eqlib, ["h", "rz"])(circ)\n+        self.assertEqual(Operator(circ), Operator(out_circ))\n+\n     def test_skip_target_basis_equivalences_1(self):\n         """Test that BasisTranslator skips gates in the target_basis - #6085"""\n         circ = QuantumCircuit()\n


Fix qpy for `MCMTGate` (Qiskit/qiskit#14096)

<!--\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- [x] I have updated the documentation accordingly.\r\n- [x] I have read the CONTRIBUTING document.\r\n-->\r\n\r\n### Summary\r\nThis PR fixes #13965.\r\n\r\n\r\n### Details and comments\r\n`qpy` had to save `base_gate` for `MCMTGate`, which didn't. I only added `MCMTGate` to custom operations because `_read_custom_operations` and `_write_custom_operation` already handled `base_gate`.\r\n\r\n

QPY fails to serialize MCMTGate gate

### Environment\n\n- **Qiskit version**: 1.4.0\n- **Python version**: 3.12.9\n- **Operating system**: Mac M1\n\n\n### What is happening?\n\nI saved a quantum circuit containing `qiskit.circuit.library.MCMTGate` using `qpy`. The following error happened when I had attempted to load the saved quantum circuit.\n\n```\n---------------------------------------------------------------------------\nTypeError                                 Traceback (most recent call last)\nCell In[9], line 2\n      1 with open(circuit_path, "rb") as circuit_file:\n----> 2     qc = qpy.load(circuit_file)\n      3 qc\n\nFile /opt/homebrew/Caskroom/miniforge/base/envs/qsvm/lib/python3.12/site-packages/qiskit/qpy/interface.py:370, in load(file_obj, metadata_deserializer)\n    367 programs = []\n    368 for _ in range(data.num_programs):\n    369     programs.append(\n--> 370         loader(\n    371             file_obj,\n    372             data.qpy_version,\n    373             metadata_deserializer=metadata_deserializer,\n    374             use_symengine=use_symengine,\n    375         )\n    376     )\n    377 return programs\n\nFile /opt/homebrew/Caskroom/miniforge/base/envs/qsvm/lib/python3.12/site-packages/qiskit/qpy/binary_io/circuits.py:1443, in read_circuit(file_obj, version, metadata_deserializer, use_symengine)\n   1441 custom_operations = _read_custom_operations(file_obj, version, vectors)\n   1442 for _instruction in range(num_instructions):\n-> 1443     _read_instruction(\n   1444         file_obj,\n   1445         circ,\n   1446         out_registers,\n   1447         custom_operations,\n   1448         version,\n   1449         vectors,\n   1450         use_symengine,\n   1451         standalone_var_indices,\n   1452     )\n   1454 # Read calibrations\n   1455 if version >= 5:\n\nFile /opt/homebrew/Caskroom/miniforge/base/envs/qsvm/lib/python3.12/site-packages/qiskit/qpy/binary_io/circuits.py:379, in _read_instruction(file_obj, circuit, registers, custom_operations, version, vectors, use_symengine, standalone_vars)\n    377     gate = gate_class(*params, instruction.num_ctrl_qubits, label=label)\n    378 else:\n--> 379     gate = gate_class(*params, label=label)\n    380     if (\n    381         gate.num_ctrl_qubits != instruction.num_ctrl_qubits\n    382         or gate.ctrl_state != instruction.ctrl_state\n    383     ):\n    384         gate = gate.to_mutable()\n\nTypeError: MCMTGate.__init__() missing 3 required positional arguments: 'gate', 'num_ctrl_qubits', and 'num_target_qubits'\n\n```\n\n### How can we reproduce the issue?\n\n```python\nqc = qiskit.QuantumCircuit(3)\nqc.compose(qiskit.circuit.library.MCMTGate(qiskit.circuit.library.ZGate(), 2, 1), inplace=True)\n\ncircuit_path = "circuit.qpy"\nwith open(circuit_path, "wb") as circuit_file:\n    qpy.dump(qc, circuit_file)\n\nwith open(circuit_path, "rb") as circuit_file:\n    qc = qpy.load(circuit_file)\n```\n\n### What should happen?\n\nThe saved quantum circuit should be loaded without any error.\n\n### Any suggestions?\n\nIt seems like this issue relates to #9390 for me?

diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py\nindex 91a06ccfe92f..46837b9f2deb 100644\n--- a/qiskit/qpy/binary_io/circuits.py\n+++ b/qiskit/qpy/binary_io/circuits.py\n@@ -783,6 +783,11 @@ def _write_instruction(\n         custom_operations[gate_class_name] = instruction.operation\n         custom_operations_list.append(gate_class_name)\n \n+    elif isinstance(instruction.operation, library.MCMTGate):\n+        gate_class_name = instruction.operation.name + "_" + str(uuid.uuid4())\n+        custom_operations[gate_class_name] = instruction.operation\n+        custom_operations_list.append(gate_class_name)\n+\n     condition_type = type_keys.Condition.NONE\n     condition_register = b""\n     condition_value = 0\ndiff --git a/releasenotes/notes/fix-qpy-mcmtgate-ab735812998f4107.yaml b/releasenotes/notes/fix-qpy-mcmtgate-ab735812998f4107.yaml\nnew file mode 100644\nindex 000000000000..72de29c3c31e\n--- /dev/null\n+++ b/releasenotes/notes/fix-qpy-mcmtgate-ab735812998f4107.yaml\n@@ -0,0 +1,6 @@\n+---\n+fixes:\n+  - |\n+    Fixed a bug in QPY (:mod:`qiskit.qpy`) where circuits containing gates of class\n+    :class:`.MCMTGate` would fail to serialize.\n+    See `#13965 <https://github.com/Qiskit/qiskit/issues/13965>`__.\n\\ No newline at end of file\n

diff --git a/test/python/circuit/test_circuit_load_from_qpy.py b/test/python/circuit/test_circuit_load_from_qpy.py\nindex 32cea72a783c..f8d17a5fd100 100644\n--- a/test/python/circuit/test_circuit_load_from_qpy.py\n+++ b/test/python/circuit/test_circuit_load_from_qpy.py\n@@ -30,6 +30,7 @@\n from qiskit.circuit.gate import Gate\n from qiskit.circuit.library import (\n     XGate,\n+    ZGate,\n     CXGate,\n     RYGate,\n     QFT,\n@@ -41,6 +42,7 @@\n     MCXGrayCode,\n     MCXRecursive,\n     MCXVChain,\n+    MCMTGate,\n     UCRXGate,\n     UCRYGate,\n     UCRZGate,\n@@ -1261,11 +1263,13 @@ def test_standard_control_gates(self):\n         mcx_gray_gate = MCXGrayCode(5)\n         mcx_recursive_gate = MCXRecursive(4)\n         mcx_vchain_gate = MCXVChain(3)\n+        mcmt_gate = MCMTGate(ZGate(), 2, 1)\n         qc.append(mcu1_gate, [0, 2, 1])\n         qc.append(mcx_gate, list(range(0, 6)))\n         qc.append(mcx_gray_gate, list(range(0, 6)))\n         qc.append(mcx_recursive_gate, list(range(0, 5)))\n         qc.append(mcx_vchain_gate, list(range(0, 5)))\n+        qc.append(mcmt_gate, list(range(0, 3)))\n         qc.mcp(np.pi, [0, 2], 1)\n         qc.mcx([0, 2], 1)\n         qc.measure_all()\n


Bug fixes in Solovay Kitaev Decomposition (Qiskit/qiskit#14217)

<!--\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- [x] 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 fixes a number of bugs we have recently discovered in the Python code for the Solovay-Kitaev decomposition, together with @Cryoris and @anedumla.\r\n\r\nFixes include:\r\n* fixed a problem with the rotation axis computation for 180 degree rotations (thanks to @Cryoris for working out the math)\r\n* fixed a problem where the global phase of the returned circuit might have been off by $\\pi$, due to the underlying algorithm approximating not the original ``SU(2)`` matrix, but its projection onto ``SO(3)``\r\n* in particular fixed #9552.\r\n\r\nLonger-term, we will deprecate the Python code for Solovay-Kitaev decomposition altogether, and switch to the new improved pass written in Rust that also fixes these problems (see #14203), however for backward compatibility we need to temporarily support the Python code as well, and thus it makes sense to fix the problems on the Python side too.\r\n\r\n\r\n### Details and comments\r\n\r\nOther changes:\r\n\r\n* There was a very strange "back door" for returning a circuit with the original unitary matrix if the computed matrix is not close to the identity. This behavior is not consistent with the documented description of the pass and was removed. (Or rather the functions ``to_circuit`` and ``to_dag`` remain for backwards compatibility but are no longer used by the main pass).\r\n* The ``GateSequence`` class used to store both the SO(3) matrix as ``product`` and the SU(2) matrix as ``product_su2``, however the parameter ``product_su2`` was not updated correctly in various places (and it was not even clear what to do within ``GateSequence.from_matrix``). As ``product_su2`` was almost not used anywhere in the algorithm (with the exception of one place where its usage did not seem to be correct anyway), it was simply removed to avoid confusion and unnecessary computations. \r\n

Global phase on Solovay-Kitaev decomposition seems wrong

### Environment\r\n\r\n- **Qiskit Terra version**: 0.23.1\r\n- **Python version**: 3.10.6\r\n- **Operating system**: Ubuntu Linux\r\n\r\n\r\n### What is happening?\r\n\r\nThe global phase of some circuits generated by `SolovayKitaev` seems wrong.\r\n\r\n### How can we reproduce the issue?\r\n\r\n```python\r\n>>> from qiskit.transpiler.passes.synthesis import SolovayKitaev\r\n>>> from qiskit.quantum_info import Operator\r\n>>> from qiskit import QuantumCircuit, Aer, transpile, assemble\r\n>>> c = QuantumCircuit(1)\r\n>>> c.y(0)\r\n<qiskit.circuit.instructionset.InstructionSet object at 0x7f62280bc910>\r\n>>> skd = SolovayKitaev(recursion_degree=3)\r\n>>> dc = skd(c)\r\n>>> c.draw()\r\n   ┌───┐\r\nq: ┤ Y ├\r\n   └───┘\r\n>>> dc.draw()\r\nglobal phase: 3π/4\r\n   ┌───┐┌───┐┌───┐┌───┐┌─────┐┌─────┐┌───┐┌───┐┌───┐\r\nq: ┤ H ├┤ T ├┤ T ├┤ H ├┤ Tdg ├┤ Tdg ├┤ H ├┤ T ├┤ T ├\r\n   └───┘└───┘└───┘└───┘└─────┘└─────┘└───┘└───┘└───┘\r\n>>> Operator(c)\r\nOperator([[0.+0.j, 0.-1.j],\r\n          [0.+1.j, 0.+0.j]],\r\n         input_dims=(2,), output_dims=(2,))\r\n>>> Operator(dc)\r\nOperator([[-1.11022302e-16+7.85046229e-17j,\r\n            1.05981241e-15+1.00000000e+00j],\r\n          [-9.43689571e-16-1.00000000e+00j,\r\n            0.00000000e+00-3.92523115e-17j]],\r\n         input_dims=(2,), output_dims=(2,))\r\n```\r\n\r\n### What should happen?\r\n\r\nGiven `Operator(dc).data` aprroximately equals to `[[0, 1j], [-1j, 0]]` which equals to `-Operator(c).data`, `dc`'s global phase should be π rather than 3π/4, I believe.\r\n\r\n### Any suggestions?\r\n\r\n_No response_

diff --git a/qiskit/synthesis/discrete_basis/commutator_decompose.py b/qiskit/synthesis/discrete_basis/commutator_decompose.py\nindex 67094a7105a4..b9d274b38a3d 100644\n--- a/qiskit/synthesis/discrete_basis/commutator_decompose.py\n+++ b/qiskit/synthesis/discrete_basis/commutator_decompose.py\n@@ -53,16 +53,40 @@ def _compute_rotation_axis(matrix: np.ndarray) -> np.ndarray:\n     """\n     _check_is_so3(matrix)\n \n+    # If theta represents the rotation angle, then trace = 1 + 2cos(theta).\n     trace = _compute_trace_so3(matrix)\n-    theta = math.acos(0.5 * (trace - 1))\n-    if math.sin(theta) > 1e-10:\n-        x = 1 / (2 * math.sin(theta)) * (matrix[2][1] - matrix[1][2])\n-        y = 1 / (2 * math.sin(theta)) * (matrix[0][2] - matrix[2][0])\n-        z = 1 / (2 * math.sin(theta)) * (matrix[1][0] - matrix[0][1])\n-    else:\n+\n+    if trace >= 3 - 1e-10:\n+        # The matrix is the identity (rotation by 0)\n         x = 1.0\n         y = 0.0\n         z = 0.0\n+\n+    elif trace <= -1 + 1e-10:\n+        # The matrix is the 180-degree rotation\n+        squares = (1 + np.diagonal(matrix)) / 2\n+        index_of_max = np.argmax(squares)\n+\n+        if index_of_max == 0:\n+            x = math.sqrt(squares[0])\n+            y = matrix[0][1] / (2 * x)\n+            z = matrix[0][2] / (2 * x)\n+        elif index_of_max == 1:\n+            y = math.sqrt(squares[1])\n+            x = matrix[0][1] / (2 * y)\n+            z = matrix[1][2] / (2 * y)\n+        else:\n+            z = math.sqrt(squares[2])\n+            x = matrix[0][2] / (2 * z)\n+            y = matrix[1][2] / (2 * z)\n+\n+    else:\n+        # The matrix is the rotation by theta with sin(theta)!=0\n+        theta = math.acos(0.5 * (trace - 1))\n+        x = 1 / (2 * math.sin(theta)) * (matrix[2][1] - matrix[1][2])\n+        y = 1 / (2 * math.sin(theta)) * (matrix[0][2] - matrix[2][0])\n+        z = 1 / (2 * math.sin(theta)) * (matrix[1][0] - matrix[0][1])\n+\n     return np.array([x, y, z])\n \n \ndiff --git a/qiskit/synthesis/discrete_basis/gate_sequence.py b/qiskit/synthesis/discrete_basis/gate_sequence.py\nindex 39adff0ab5bc..73a0c631529b 100644\n--- a/qiskit/synthesis/discrete_basis/gate_sequence.py\n+++ b/qiskit/synthesis/discrete_basis/gate_sequence.py\n@@ -55,7 +55,6 @@ def __init__(self, gates: Sequence[Gate] = ()) -> None:\n         self.name = " ".join(self.labels)\n         self.global_phase = global_phase\n         self.product = so3_matrix\n-        self.product_su2 = su2_matrix\n \n     def remove_cancelling_pair(self, indices: Sequence[int]) -> None:\n         """Remove a pair of indices that cancel each other and *do not* change the matrices."""\n@@ -106,6 +105,16 @@ def to_circuit(self):\n \n         return circuit\n \n+    def _to_u2(self):\n+        """Creates the U2 matrix corresponding to the stored sequence of gates\n+        and the global phase.\n+        """\n+        u2 = np.eye(2, dtype=complex)\n+        for mat in self.gates:\n+            u2 = mat.to_matrix().dot(u2)\n+        u2 = np.exp(1j * self.global_phase) * u2\n+        return u2\n+\n     def to_dag(self):\n         """Convert to a :class:`.DAGCircuit`.\n \n@@ -149,7 +158,6 @@ def append(self, gate: Gate) -> "GateSequence":\n         so3 = _convert_su2_to_so3(su2)\n \n         self.product = so3.dot(self.product)\n-        self.product_su2 = su2.dot(self.product_su2)\n         self.global_phase = self.global_phase + phase\n \n         self.gates.append(gate)\n@@ -172,7 +180,6 @@ def adjoint(self) -> "GateSequence":\n         adjoint.labels = [inv.name for inv in adjoint.gates]\n         adjoint.name = " ".join(adjoint.labels)\n         adjoint.product = np.conj(self.product).T\n-        adjoint.product_su2 = np.conj(self.product_su2).T\n         adjoint.global_phase = -self.global_phase\n \n         return adjoint\n@@ -190,7 +197,6 @@ def copy(self) -> "GateSequence":\n         out.matrices = self.matrices.copy()\n         out.global_phase = self.global_phase\n         out.product = self.product.copy()\n-        out.product_su2 = self.product_su2.copy()\n         out.name = self.name\n         out._eulers = self._eulers\n         return out\ndiff --git a/qiskit/synthesis/discrete_basis/generate_basis_approximations.py b/qiskit/synthesis/discrete_basis/generate_basis_approximations.py\nindex 388b5cca061d..a603b89d94e4 100644\n--- a/qiskit/synthesis/discrete_basis/generate_basis_approximations.py\n+++ b/qiskit/synthesis/discrete_basis/generate_basis_approximations.py\n@@ -76,7 +76,7 @@ def _check_candidate_greedy(candidate, existing_sequences, tol=1e-10):\n         return False\n \n     for existing in existing_sequences:\n-        if matrix_equal(existing.product_su2, candidate.product_su2, ignore_phase=True, atol=tol):\n+        if matrix_equal(existing.product, candidate.product, ignore_phase=True, atol=tol):\n             # is the new sequence less or more efficient?\n             return len(candidate.gates) < len(existing.gates)\n     return True\ndiff --git a/qiskit/synthesis/discrete_basis/solovay_kitaev.py b/qiskit/synthesis/discrete_basis/solovay_kitaev.py\nindex f367f6c0f0b5..e87efe2884c3 100644\n--- a/qiskit/synthesis/discrete_basis/solovay_kitaev.py\n+++ b/qiskit/synthesis/discrete_basis/solovay_kitaev.py\n@@ -24,7 +24,7 @@\n class SolovayKitaevDecomposition:\n     """The Solovay Kitaev discrete decomposition algorithm.\n \n-    This class is called recursively by the transpiler pass, which is why it is separeted.\n+    This class is called recursively by the transpiler pass, which is why it is separated.\n     See :class:`qiskit.transpiler.passes.SolovayKitaev` for more information.\n     """\n \n@@ -33,7 +33,7 @@ def __init__(\n     ) -> None:\n         """\n         Args:\n-            basic_approximations: A specification of the basic SU(2) approximations in terms\n+            basic_approximations: A specification of the basic SO(3) approximations in terms\n                 of discrete gates. At each iteration this algorithm, the remaining error is\n                 approximated with the closest sequence of gates in this set.\n                 If a ``str``, this specifies a ``.npy`` filename from which to load the\n@@ -116,23 +116,33 @@ def run(\n         """\n         # make input matrix SU(2) and get the according global phase\n         z = 1 / np.sqrt(np.linalg.det(gate_matrix))\n-        gate_matrix_su2 = GateSequence.from_matrix(z * gate_matrix)\n+\n+        gate_matrix_su2 = z * gate_matrix\n+        gate_matrix_as_sequence = GateSequence.from_matrix(gate_matrix_su2)\n         global_phase = np.arctan2(np.imag(z), np.real(z))\n \n         # get the decomposition as GateSequence type\n-        decomposition = self._recurse(gate_matrix_su2, recursion_degree, check_input=check_input)\n+        decomposition = self._recurse(\n+            gate_matrix_as_sequence, recursion_degree, check_input=check_input\n+        )\n \n         # simplify\n         _remove_identities(decomposition)\n         _remove_inverse_follows_gate(decomposition)\n \n+        # adjust to the correct SU(2) phase\n+        adjust_phase = (\n+            np.pi if _should_adjust_phase(decomposition._to_u2(), gate_matrix_su2) else 0.0\n+        )\n+\n         # convert to a circuit and attach the right phases\n         if return_dag:\n             out = decomposition.to_dag()\n         else:\n             out = decomposition.to_circuit()\n \n-        out.global_phase = decomposition.global_phase - global_phase\n+        out.global_phase += adjust_phase\n+        out.global_phase -= global_phase\n \n         return out\n \n@@ -155,17 +165,20 @@ def _recurse(self, sequence: GateSequence, n: int, check_input: bool = True) ->\n             raise ValueError("Shape of U must be (3, 3) but is", sequence.shape)\n \n         if n == 0:\n-            return self.find_basic_approximation(sequence)\n+            res = self.find_basic_approximation(sequence)\n \n-        u_n1 = self._recurse(sequence, n - 1, check_input=check_input)\n+        else:\n+            u_n1 = self._recurse(sequence, n - 1, check_input=check_input)\n \n-        v_n, w_n = commutator_decompose(\n-            sequence.dot(u_n1.adjoint()).product, check_input=check_input\n-        )\n+            v_n, w_n = commutator_decompose(\n+                sequence.dot(u_n1.adjoint()).product, check_input=check_input\n+            )\n+\n+            v_n1 = self._recurse(v_n, n - 1, check_input=check_input)\n+            w_n1 = self._recurse(w_n, n - 1, check_input=check_input)\n+            res = v_n1.dot(w_n1).dot(v_n1.adjoint()).dot(w_n1.adjoint()).dot(u_n1)\n \n-        v_n1 = self._recurse(v_n, n - 1, check_input=check_input)\n-        w_n1 = self._recurse(w_n, n - 1, check_input=check_input)\n-        return v_n1.dot(w_n1).dot(v_n1.adjoint()).dot(w_n1.adjoint()).dot(u_n1)\n+        return res\n \n     def find_basic_approximation(self, sequence: GateSequence) -> GateSequence:\n         """Find ``GateSequence`` in ``self._basic_approximations`` that approximates ``sequence``.\n@@ -215,3 +228,13 @@ def _remove_identities(sequence):\n             sequence.gates.pop(index)\n         else:\n             index += 1\n+\n+\n+def _should_adjust_phase(computed: np.ndarray, target: np.ndarray) -> bool:\n+    """\n+    The implemented SolovayKitaevDecomposition has a global phase uncertainty of +-1,\n+    due to approximating not the original SU(2) matrix but its projection onto SO(3).\n+    This function returns ``True`` if the global phase of the computed approximation\n+    should be adjusted (by adding pi) to better much the target.\n+    """\n+    return np.linalg.norm(-computed - target) < np.linalg.norm(computed - target)\ndiff --git a/releasenotes/notes/fix-sk-decomposition-23da3ee4b6a10d62.yaml b/releasenotes/notes/fix-sk-decomposition-23da3ee4b6a10d62.yaml\nnew file mode 100644\nindex 000000000000..21a899a7df9e\n--- /dev/null\n+++ b/releasenotes/notes/fix-sk-decomposition-23da3ee4b6a10d62.yaml\n@@ -0,0 +1,12 @@\n+---\n+fixes:\n+  - |\n+    Fixed a problem in the :class:`.SolovayKitaev` transpiler pass where the pass could\n+    crash due to encountering a 180 degree rotation in the internal recursion,\n+    which was not handled correctly.\n+  - |\n+    Fixed a problem in the :class:`.SolovayKitaev` transpiler pass where the generated\n+    approximation could have a phase that differs by :math:`\\pi` from the correct value.\n+    This resulted due to the internal :math:`SO(3)` representation, which requires additional\n+    handling to obtain the correct sign of the qubit gate matrix.\n+    Fixed `#9552 <https://github.com/Qiskit/qiskit-terra/issues/9552>`__\n

diff --git a/test/python/transpiler/test_solovay_kitaev.py b/test/python/transpiler/test_solovay_kitaev.py\nindex bb39f8db8e81..da3a0840e2b8 100644\n--- a/test/python/transpiler/test_solovay_kitaev.py\n+++ b/test/python/transpiler/test_solovay_kitaev.py\n@@ -99,9 +99,7 @@ def test_unitary_synthesis(self):\n         passes = PassManager([_1q, _cons, _synth])\n         compiled = passes.run(circuit)\n \n-        diff = np.linalg.norm(Operator(compiled) - Operator(circuit))\n-        self.assertLess(diff, 1)\n-        self.assertEqual(set(compiled.count_ops().keys()), {"h", "s", "cx"})\n+        self.assertLessEqual(set(compiled.count_ops().keys()), {"h", "s", "sdg", "cx"})\n \n     def test_plugin(self):\n         """Test calling the plugin directly."""\n@@ -113,12 +111,7 @@ def test_plugin(self):\n         plugin = SolovayKitaevSynthesis()\n         out = plugin.run(unitary, basis_gates=["h", "s"])\n \n-        reference = QuantumCircuit(1, global_phase=3 * np.pi / 4)\n-        reference.h(0)\n-        reference.s(0)\n-        reference.h(0)\n-\n-        self.assertEqual(dag_to_circuit(out), reference)\n+        self.assertLessEqual(set(out.count_ops().keys()), {"h", "s", "sdg", "cx"})\n \n     def test_generating_default_approximation(self):\n         """Test the approximation set is generated by default."""\n@@ -164,11 +157,14 @@ def test_str_basis_gates(self):\n         dag = circuit_to_dag(circuit)\n         discretized = dag_to_circuit(synth.run(dag))\n \n-        reference = QuantumCircuit(1, global_phase=7 * np.pi / 8)\n+        reference = QuantumCircuit(1, global_phase=15 * np.pi / 8)\n         reference.h(0)\n         reference.t(0)\n         reference.h(0)\n \n+        # Make sure that the discretized circuit gives a valid approximation\n+        diff = _trace_distance(circuit, discretized)\n+        self.assertLess(diff, 0.01)\n         self.assertEqual(discretized, reference)\n \n     def test_approximation_on_qft(self):\n@@ -209,14 +205,14 @@ def test_u_gates_work(self):\n         circuit.u(np.pi / 2, 0, 15 * np.pi / 16, 0)\n \n         depth = 4\n-        basis_gates = ["h", "t", "tdg", "s", "z"]\n+        basis_gates = ["h", "t", "tdg", "s", "sdg", "z"]\n         gate_approx_library = generate_basic_approximations(basis_gates=basis_gates, depth=depth)\n \n         skd = SolovayKitaev(recursion_degree=2, basic_approximations=gate_approx_library)\n         discretized = skd(circuit)\n \n         included_gates = set(discretized.count_ops().keys())\n-        self.assertEqual(set(basis_gates), included_gates)\n+        self.assertLessEqual(included_gates, set(basis_gates))\n \n     def test_load_from_file(self):\n         """Test loading basic approximations from a file works.\n@@ -229,22 +225,21 @@ def test_load_from_file(self):\n             fullpath = os.path.join(tmp_dir, filename)\n \n             # dump approximations to file\n-            generate_basic_approximations(basis_gates=["h", "s", "sdg"], depth=3, filename=fullpath)\n+            gate_approx_library = generate_basic_approximations(\n+                basis_gates=["h", "s", "sdg"], depth=3, filename=fullpath\n+            )\n \n             # circuit to decompose and reference decomp\n             circuit = QuantumCircuit(1)\n             circuit.rx(0.8, 0)\n \n-            reference = QuantumCircuit(1, global_phase=3 * np.pi / 4)\n-            reference.h(0)\n-            reference.s(0)\n-            reference.h(0)\n+            # Run SK pass using gate_approx_library\n+            reference = SolovayKitaev(basic_approximations=gate_approx_library)(circuit)\n \n-            # load the decomp and compare to reference\n-            skd = SolovayKitaev(basic_approximations=fullpath)\n-            # skd = SolovayKitaev(basic_approximations=filename)\n-            discretized = skd(circuit)\n+            # Run SK pass using stored basis_approximations\n+            discretized = SolovayKitaev(basic_approximations=fullpath)(circuit)\n \n+        # Check that both flows produce the same result\n         self.assertEqual(discretized, reference)\n \n     def test_measure(self):\n@@ -297,6 +292,15 @@ def test_no_to_matrix(self):\n         transpiled = SolovayKitaev()(qc)\n         self.assertEqual(set(transpiled.count_ops()), {"initialize"})\n \n+    def test_y_gate(self):\n+        """Test the Solovay-Kitaev decomposition on the circuit with a Y-gate (see issue #9552)."""\n+        circuit = QuantumCircuit(1)\n+        circuit.y(0)\n+\n+        transpiled = SolovayKitaev()(circuit)\n+        diff = _trace_distance(circuit, transpiled)\n+        self.assertLess(diff, 1e-6)\n+\n \n @ddt\n class TestGateSequence(QiskitTestCase):\n


Bug fix in HighLevelSynthesis fast-return mechanism (Qiskit/qiskit#14349)

<!--\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- [x] 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\nThe `HighLevelSynthesis` transpiler pass checks whether all of the circuit instructions are already natively supported, in which case the pass terminates early. Previously this check called `dag.count_ops(py, recurse=false`), which however is not correct on circuits with control-flow operations when operations within the blocks that are not natively supported. The PR simply changes `recurse=false` to `recurse=true`.\r\n\r\n### Details and comments\r\n\r\nThis should fix #14338 and #14254.

`TranspilerError` occurs when transpiling a circuit with `for_loop`

### Environment\n\n- **Qiskit version**: 2.0.0\n- **Python version**: 3.13.2\n- **Operating system**: Windows 11, Version 24H2, Build 26100\n\n\n### What is happening?\n\nWhen transpiling a circuit that uses an `for_loop` conditional statement, a `TranspilerError` occurs.\n```\nqiskit.transpiler.exceptions.TranspilerError: 'Unable to translate the operations in the circuit: ["for_loop", "clifford"] to the backend\\'s (or manually specified) target basis: {"mcy", "sxdg", "rzx", "mcx", "unitary", "switch_case", "delay", "initialize", "h", "cswap", "mcx_gray", "x", "reset", "u2", "crx", "diagonal", "set_stabilizer", "mcp", "cu2", "save_density_matrix", "mcu3", "mcrz", "pauli", "mcsx", "cu3", "set_density_matrix", "set_statevector", "z", "save_expval_var", "save_stabilizer", "crz", "cp", "ry", "save_state", "measure", "save_matrix_product_state", "qerror_loc", "save_amplitudes", "save_superop", "save_expval", "cy", "save_statevector", "tdg", "mcz", "ccx", "cz", "cu", "u3", "continue_loop", "for_loop", "t", "mcu1", "quantum_channel", "save_statevector_dict", "roerror", "ecr", "cu1", "if_else", "s", "cry", "save_unitary", "cx", "mcphase", "ccz", "superop", "store", "mcrx", "u1", "set_unitary", "save_clifford", "rxx", "set_matrix_product_state", "rz", "mcu", "mcry", "u", "break_loop", "rx", "sx", "save_amplitudes_sq", "sdg", "csx", "ryy", "p", "id", "mcu2", "save_probabilities", "multiplexer", "save_probabilities_dict", "while_loop", "kraus", "r", "set_superop", "y", "rzz", "swap", "mcswap", "mcr", "barrier", "snapshot"}. This likely means the target basis is not universal or there are additional equivalence rules needed in the EquivalenceLibrary being used. For more details on this error see: https://docs.quantum.ibm.com/api/qiskit/qiskit.transpiler.passes. BasisTranslator#translation-errors'\n```\n\n### How can we reproduce the issue?\n\nMinimal Working Example: \n```\nfrom qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, transpile\nfrom qiskit_aer import Aer\nfrom qiskit.transpiler.passes import *\nfrom qiskit.transpiler import PassManager\n\nqreg = QuantumRegister(5)\ncreg = ClassicalRegister(5)\nqc = QuantumCircuit(qreg, creg)\n\nwith qc.for_loop(range(5)) as i:\n\tqc.cx(4, 3)\n\tqc.cz(0, 4)\n\nqc.measure(qreg[0], creg[0])\n\nsimulator = Aer.get_backend("aer_simulator")\n\np = PassManager(CollectCliffords())\nqc = p.run(qc)\n\ncompiled_circuit = transpile(qc, backend = simulator, optimization_level = 3, routing_method = "sabre", layout_method = "noise_adaptive", approximation_degree = 1 )\njob = simulator.run(compiled_circuit, shots=10000)\nresult = job.result().get_counts()\nprint(result)\n```\n\n### What should happen?\n\nThe circuit should be successfully transpiled and executed. \n\n### Any suggestions?\n\n_No response_

diff --git a/crates/transpiler/src/passes/high_level_synthesis.rs b/crates/transpiler/src/passes/high_level_synthesis.rs\nindex 162a23ce890d..a434efdec012 100644\n--- a/crates/transpiler/src/passes/high_level_synthesis.rs\n+++ b/crates/transpiler/src/passes/high_level_synthesis.rs\n@@ -333,7 +333,7 @@ fn all_instructions_supported(\n     data: &Bound<HighLevelSynthesisData>,\n     dag: &DAGCircuit,\n ) -> PyResult<bool> {\n-    let ops = dag.count_ops(py, false)?;\n+    let ops = dag.count_ops(py, true)?;\n     let mut op_keys = ops.keys();\n \n     let borrowed_data = data.borrow();\ndiff --git a/releasenotes/notes/fix-hls-fast-return-6daff39a5eba40ce.yaml b/releasenotes/notes/fix-hls-fast-return-6daff39a5eba40ce.yaml\nnew file mode 100644\nindex 000000000000..70abdd361183\n--- /dev/null\n+++ b/releasenotes/notes/fix-hls-fast-return-6daff39a5eba40ce.yaml\n@@ -0,0 +1,7 @@\n+---\n+fixes:\n+  - |\n+    Fixed a problem in :class:`.HighLevelSynthesis` transpiler pass that caused\n+    it to erroneously terminate early on certain circuits with control flow\n+    operations.\n+    Fixed `#14338 <https://github.com/Qiskit/qiskit-terra/issues/14338>`__\n

diff --git a/test/python/transpiler/test_high_level_synthesis.py b/test/python/transpiler/test_high_level_synthesis.py\nindex 25ee44270553..8999fb8408d7 100644\n--- a/test/python/transpiler/test_high_level_synthesis.py\n+++ b/test/python/transpiler/test_high_level_synthesis.py\n@@ -742,6 +742,21 @@ def test_track_global_phase(self):\n             expected_block.cx(0, 1)\n             self.assertEqual(transpiled_block, expected_block)\n \n+    def test_control_flow(self):\n+        """Test that the pass recurses into control-flow ops."""\n+        clifford_circuit = QuantumCircuit(3)\n+        clifford_circuit.cx(1, 0)\n+        clifford_circuit.cz(0, 2)\n+        cliff = Clifford(clifford_circuit)\n+\n+        qc = QuantumCircuit(5, 5)\n+        with qc.for_loop(range(3)):\n+            qc.append(cliff, [0, 1, 4])\n+\n+        transpiled = HighLevelSynthesis(basis_gates=["cx", "u", "for_loop"])(qc)\n+        transpiled_block = transpiled[0].operation.blocks[0]\n+        self.assertNotIn("clifford", transpiled_block.count_ops())\n+\n \n class TestHighLevelSynthesisQuality(QiskitTestCase):\n     """Test the "quality" of circuits produced by HighLevelSynthesis."""\n


Fix Sabre extended-set tracking (Qiskit/qiskit#14617)

<!--\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- [ ] I have added the tests to cover my changes.\r\n- [ ] I have updated the documentation accordingly.\r\n- [ ] I have read the CONTRIBUTING document.\r\n-->\r\n\r\n### Summary\r\n\r\nThis fixes the logic bug in extended-set tracking where the "other" qubit of a gate touched by a swap would not have its tracking data updated during an `apply_swap` operation.\r\n\r\nThis change has the effect of making the extended-set update operation now linear in the size of the extended set in all cases, whereas previously it was only worst-case linear and average-case constant. This can be offset in the future by modifications to how the "lookahead" gates are tracked at all, if it is needed.\r\n\r\n\r\n\r\n### Details and comments\r\n\r\nFix #14244.  I'm tagging for 2.2.0 because it's not a correctness bug, so imo it's not worth the risk of changing behaviour in the backport, and it _does_ have performance implications.\r\n\r\nThis does make it more costly to update a swap in the `ExtendedSet`, and we probably should revisit the struct to make it cheaper.  The ASV benchmarks below (with options `-b UtilityScale -b sabre` and `--only-changed`) show that it doesn't seem to have a critical impact on runtime performance, though, at the scale of fixed 20-gate size:\r\n\r\n```text\r\n| Change   | Before [3e19a7ad]    | After [801295b5]    |   Ratio | Benchmark (Parameter)                                                                        |\r\n|----------|----------------------|---------------------|---------|----------------------------------------------------------------------------------------------|\r\n| -        | 148497               | 122160              |    0.82 | qft.LargeQFTMappingTrackBench.track_depth_sabre_swap(1081, 'lookahead')                      |\r\n| -        | 3358                 | 2827                |    0.84 | qft.LargeQFTMappingTrackBench.track_depth_sabre_swap(115, 'lookahead')                       |\r\n| -        | 31891                | 27395               |    0.86 | qft.LargeQFTMappingTrackBench.track_depth_sabre_swap(409, 'lookahead')                       |\r\n| -        | 6360                 | 5337                |    0.84 | quantum_volume.LargeQuantumVolumeMappingTrackBench.track_depth_sabre_swap(1081, 10, 'decay') |\r\n| -        | 203±10ms             | 157±7ms             |    0.77 | queko.QUEKOTranspilerBench.time_transpile_bss(3, 'sabre')                                    |\r\n| -        | 666                  | 592                 |    0.89 | queko.QUEKOTranspilerBench.track_depth_bntf_optimal_depth_25(0, 'sabre')                     |\r\n| -        | 230                  | 198                 |    0.86 | queko.QUEKOTranspilerBench.track_depth_bntf_optimal_depth_25(2, 'sabre')                     |\r\n| -        | 197                  | 165                 |    0.84 | queko.QUEKOTranspilerBench.track_depth_bntf_optimal_depth_25(3, 'sabre')                     |\r\n| -        | 765                  | 671                 |    0.88 | queko.QUEKOTranspilerBench.track_depth_bss_optimal_depth_100(0, 'sabre')                     |\r\n| -        | 505                  | 392                 |    0.78 | queko.QUEKOTranspilerBench.track_depth_bss_optimal_depth_100(1, 'sabre')                     |\r\n| Change   | Before [3e19a7ad]    | After [801295b5]    |   Ratio | Benchmark (Parameter)                                                                       |\r\n|----------|----------------------|---------------------|---------|---------------------------------------------------------------------------------------------|\r\n| +        | 428±4ms              | 479±3ms             |    1.12 | mapping_passes.PassBenchmarks.time_sabre_layout(5, 1024)                                    |\r\n| +        | 3239                 | 3807                |    1.18 | qft.LargeQFTMappingTrackBench.track_depth_sabre_swap(115, 'decay')                          |\r\n| +        | 1.35±0.01s           | 1.61±0.01s          |    1.19 | quantum_volume.LargeQuantumVolumeMappingTimeBench.time_sabre_swap(1081, 10, 'decay')        |\r\n| +        | 1396                 | 1667                |    1.19 | quantum_volume.LargeQuantumVolumeMappingTrackBench.track_depth_sabre_swap(409, 10, 'decay') |\r\n| +        | 262                  | 330                 |    1.26 | queko.QUEKOTranspilerBench.track_depth_bss_optimal_depth_100(2, 'sabre')                    |\r\n| +        | 2053                 | 2321                |    1.13 | utility_scale.UtilityScaleBenchmarks.track_qft_depth('cx')                                  |\r\n| +        | 2053                 | 2321                |    1.13 | utility_scale.UtilityScaleBenchmarks.track_qft_depth('cz')                                  |\r\n| +        | 2053                 | 2320                |    1.13 | utility_scale.UtilityScaleBenchmarks.track_qft_depth('ecr')                                 |\r\n```

ExtendSet.apply_swap can break bidirectional consistency of qubit connections

### Environment\n\n- **Qiskit version**: Tested on 1.2.4, should still be the case in the lastest versions given that the concerned functions have not changed since.\n- **Python version**: 3.10.12 \n\n\n### What is happening?\n\nThe apply_swap method of the ExtendedSet structure (defined in crates/accelerate/src/sabre/layer.rs)  currently only updates the adjacency lists for the two qubits being swapped. It does not update references to these qubits elsewhere in the data structure, which can result in inconsistent or broken bidirectional connections.\n\n### How can we reproduce the issue?\n\nAny call the the apply_set method will have this error. Simply printing the ExtendedSet right before and right after the call to this method allow to see the bug. \nDown below is a copy of the ExtendedSet struct from crates/accelerate/src/sabre/layer.rs reduced to only the necessary methods and simplied using simple datatype (u32 rather than PhysicalQubit) for simplicity. Compiling and executing this code show the error as ExtendedSet after the apply_swap call doesn't respect the bidirectional connection rule.\n\n```\npub struct ExtendedSetDebug {\n    qubits: Vec<Vec<usize>>,\n}\n\nimpl ExtendedSetDebug {\n    pub fn new(num_qubits: u32) -> Self {\n        ExtendedSetDebug {\n            qubits: vec![Vec::new(); num_qubits as usize],\n        }\n    }\n\tpub fn qubits(&self) -> &[Vec<usize>] {\n\t&self.qubits\n    }\n\t\n\t/// Add a node and its active qubits to the extended set.\n    pub fn push(&mut self, qubits: [usize; 2]) {\n        let [a, b] = qubits;\n        self.qubits[a].push(b);\n        self.qubits[b].push(a);\n    }\n\n\tpub fn apply_swap(&mut self, swap: [usize; 2]) {\n\t\tlet [a, b] = swap;\n\t\t\n\t\tfor other in self.qubits[a].iter_mut() {\n\t\t\tif *other == b {\n\t\t\t\t*other = a;\n\t\t\t}\n\t\t}\n\t\t\n\t\tfor other in self.qubits[b].iter_mut() {\n\t\t\tif *other == a {\n\t\t\t\t*other = b;\n\t\t\t}\n\t\t}\n\t\t\n\t\tself.qubits.swap(a, b);\n\t}\n}\n\nfn main() {\n    println!("Hello, world!");\n\tlet mut L = ExtendedSetDebug::new(3);\n\tL.push([0,1]);\n\tL.push([0,2]);\n\t\n\tprintln!("{:?}",L.qubits());\t\n\tL.apply_swap([0,2]);\t\n\tprintln!("{:?}",L.qubits());\t\t\n}\n```\n\n### What should happen?\n\nLet's take the example of the previous code:\nApplying the swap [0,2] to qubits = [[1,2],[0],[0]] leads to qubits = [[2],[0],[1,0]]. Bidirectional connections is broken since this result say that qubit 0 interacts only with qubit 2 (qubits[0] = [2]) but it also says that qubit 1 interacts with qubit 0 (qubits[1] = [0]). \nInstead, the result should be [[2], [2], [1,0]] which preserves the bidirectional connections of qubit interactions.\n\nNote that this happens for pretty much any call to apply_swap and that this is only one example.\n\n### Any suggestions?\n\nUpdate the implementation to iterate through all qubit adjacency lists and update every reference to a or b, not just those of a and b.\n\n```\npub fn apply_swap(&mut self, swap: [PhysicalQubit; 2]) {\n        let [a, b] = swap;\n        let a_idx = a.index();\n        let b_idx = b.index();\n\n        // Iterate through *all* qubits and update references\n        for neighbors in self.qubits.iter_mut() {\n            for qubit in neighbors.iter_mut() {\n                if *qubit == a {\n                    *qubit = b;\n                } else if *qubit == b {\n                    *qubit = a;\n                }\n            }\n        }\n        self.qubits.swap(a_idx, b_idx);\n    }\n```

diff --git a/crates/transpiler/src/passes/sabre/layer.rs b/crates/transpiler/src/passes/sabre/layer.rs\nindex 328ca4341fd3..243f10958a6c 100644\n--- a/crates/transpiler/src/passes/sabre/layer.rs\n+++ b/crates/transpiler/src/passes/sabre/layer.rs\n@@ -236,14 +236,14 @@ impl ExtendedSet {\n     /// Apply a physical swap to the current layout data structure.\n     pub fn apply_swap(&mut self, swap: [PhysicalQubit; 2]) {\n         let [a, b] = swap;\n-        for other in self.qubits[a.index()].iter_mut() {\n-            if *other == b {\n-                *other = a\n-            }\n-        }\n-        for other in self.qubits[b.index()].iter_mut() {\n+        // This effectively iterates twice through each element of the extended set, which is a\n+        // complexity bottleneck if the extended set scales with the size of the circuit.  We should\n+        // find a new structure that makes these updates more efficient.\n+        for other in self.qubits.iter_mut().flat_map(|others| others.iter_mut()) {\n             if *other == a {\n-                *other = b\n+                *other = b;\n+            } else if *other == b {\n+                *other = a;\n             }\n         }\n         self.qubits.swap(a.index(), b.index());\ndiff --git a/releasenotes/notes/sabre-extended-set-fix-0b168b2e643a77af.yaml b/releasenotes/notes/sabre-extended-set-fix-0b168b2e643a77af.yaml\nnew file mode 100644\nindex 000000000000..88aefbc01711\n--- /dev/null\n+++ b/releasenotes/notes/sabre-extended-set-fix-0b168b2e643a77af.yaml\n@@ -0,0 +1,7 @@\n+---\n+fixes:\n+  - |\n+    A logic bug in the extended-set tracking for the ``lookahead`` heuristic in Sabre routing\n+    previously causing its tracking to become self-inconsistent has been fixed.  This did not affect\n+    the correctness of the compiler, but caused the heuristic to not measure what it was intended to\n+    measure.  See `#14244 <https://github.com/Qiskit/qiskit/issues/14244>`__ for more detail.\n

diff --git a/test/python/transpiler/test_sabre_layout.py b/test/python/transpiler/test_sabre_layout.py\nindex 61407d4c4c8d..a5aaa5687a85 100644\n--- a/test/python/transpiler/test_sabre_layout.py\n+++ b/test/python/transpiler/test_sabre_layout.py\n@@ -270,7 +270,7 @@ def test_layout_many_search_trials(self):\n         self.assertIsInstance(res, QuantumCircuit)\n         layout = res._layout.initial_layout\n         self.assertEqual(\n-            [layout[q] for q in qc.qubits], [0, 12, 7, 8, 6, 3, 1, 10, 4, 9, 2, 11, 13, 5]\n+            [layout[q] for q in qc.qubits], [26, 19, 16, 23, 14, 15, 25, 20, 22, 17, 24, 21, 13, 18]\n         )\n \n     def test_support_var_with_rust_fastpath(self):\n@@ -519,7 +519,7 @@ def test_integration_with_pass_manager(self):\n         qct_initial_layout = qct.layout.initial_layout\n         self.assertEqual(\n             [qct_initial_layout[q] for q in self.circuit.qubits],\n-            [8, 7, 12, 13, 18, 19, 17, 16, 11, 10, 5, 6, 1, 2, 3, 9],\n+            [2, 3, 8, 7, 12, 13, 14, 18, 17, 16, 15, 11, 10, 5, 6, 1],\n         )\n \n \n