Task: Support ECR gates in `Pauli.evolve(QuantumCircuit)` (Qiskit/qiskit#12095)

org: Qiskit
repo: qiskit
pr_number: 12095
issue_title: Support ECR gates in `Pauli.evolve(QuantumCircuit)`
issue_body: <!--\r\n⚠️ If you do not respect this template, your pull request will be closed.\r\n⚠️ Your pull request title should be short detailed and understandable for all.\r\n⚠️ Also, please add a release note file using reno if the change needs to be\r\n  documented in the release notes.\r\n⚠️ If your pull request fixes an open issue, please link to the issue.\r\n\r\n- [X] I have added the tests to cover my changes.\r\n- [ ] I have updated the documentation accordingly.\r\n- [X] I have read the CONTRIBUTING document.\r\n-->\r\n\r\n### Summary\r\nShould fix #12086 and also #12093.\r\n\r\n\r\n### Details and comments\r\n\r\n\r\n
resolved_issues: number: 12086
title: `Pauli.evolve()` should recognize ECR gates as Clifford gates
body: ### Environment\n\n- **Qiskit version**: 1.0.2\r\n- **Python version**: 3.11\r\n- **Operating system**: macos\r\n\n\n### What is happening?\n\n```\r\nqc = QuantumCircuit(2)\r\nqc.ecr(0,1)\r\nqi.Pauli('XX').evolve(qc)\r\n```\r\n\r\nThe above call to evolve() raises an error even though qc contains only Clifford gates:\r\n\r\n```\r\nQiskitError: 'Cannot apply Instruction: u'\r\n```\n\n### How can we reproduce the issue?\n\n```\r\nqc = QuantumCircuit(2)\r\nqc.ecr(0,1)\r\nqi.Pauli('XX').evolve(qc)\r\n```\n\n### What should happen?\n\nECR gates should be recognized as Clifford gates, and the Pauli should evolve successfully.\r\n\r\nRelated: #12085 \n\n### Any suggestions?\n\n_No response_

Reference solution:
fix_patch: diff --git a/qiskit/quantum_info/operators/symplectic/base_pauli.py b/qiskit/quantum_info/operators/symplectic/base_pauli.py\nindex 28bb647d0364..e43eca4aff21 100644\n--- a/qiskit/quantum_info/operators/symplectic/base_pauli.py\n+++ b/qiskit/quantum_info/operators/symplectic/base_pauli.py\n@@ -541,75 +541,50 @@ def _append_circuit(self, circuit, qargs=None):\n         if qargs is None:\n             qargs = list(range(self.num_qubits))\n \n-        if isinstance(circuit, QuantumCircuit):\n-            gate = circuit.to_instruction()\n-        else:\n+        if not isinstance(circuit, QuantumCircuit):\n             gate = circuit\n \n-        # Basis Clifford Gates\n-        basis_1q = {\n-            "i": _evolve_i,\n-            "id": _evolve_i,\n-            "iden": _evolve_i,\n-            "x": _evolve_x,\n-            "y": _evolve_y,\n-            "z": _evolve_z,\n-            "h": _evolve_h,\n-            "s": _evolve_s,\n-            "sdg": _evolve_sdg,\n-            "sinv": _evolve_sdg,\n-        }\n-        basis_2q = {"cx": _evolve_cx, "cz": _evolve_cz, "cy": _evolve_cy, "swap": _evolve_swap}\n-\n-        # Non-Clifford gates\n-        non_clifford = ["t", "tdg", "ccx", "ccz"]\n-\n-        if isinstance(gate, str):\n-            # Check if gate is a valid Clifford basis gate string\n-            if gate not in basis_1q and gate not in basis_2q:\n-                raise QiskitError(f"Invalid Clifford gate name string {gate}")\n-            name = gate\n-        else:\n-            # Assume gate is an Instruction\n-            name = gate.name\n-\n-        # Apply gate if it is a Clifford basis gate\n-        if name in non_clifford:\n-            raise QiskitError(f"Cannot update Pauli with non-Clifford gate {name}")\n-        if name in basis_1q:\n-            if len(qargs) != 1:\n-                raise QiskitError("Invalid qubits for 1-qubit gate.")\n-            return basis_1q[name](self, qargs[0])\n-        if name in basis_2q:\n-            if len(qargs) != 2:\n-                raise QiskitError("Invalid qubits for 2-qubit gate.")\n-            return basis_2q[name](self, qargs[0], qargs[1])\n-\n-        # If not a Clifford basis gate we try to unroll the gate and\n-        # raise an exception if unrolling reaches a non-Clifford gate.\n-        if gate.definition is None:\n-            raise QiskitError(f"Cannot apply Instruction: {gate.name}")\n-        if not isinstance(gate.definition, QuantumCircuit):\n-            raise QiskitError(\n-                "{} instruction definition is {}; expected QuantumCircuit".format(\n-                    gate.name, type(gate.definition)\n+            if isinstance(gate, str):\n+                # Check if gate is a valid Clifford basis gate string\n+                if gate not in _basis_1q and gate not in _basis_2q:\n+                    raise QiskitError(f"Invalid Clifford gate name string {gate}")\n+                name = gate\n+            else:\n+                # Assume gate is an Instruction\n+                name = gate.name\n+\n+            # Apply gate if it is a Clifford basis gate\n+            if name in _non_clifford:\n+                raise QiskitError(f"Cannot update Pauli with non-Clifford gate {name}")\n+            if name in _basis_1q:\n+                if len(qargs) != 1:\n+                    raise QiskitError("Invalid qubits for 1-qubit gate.")\n+                return _basis_1q[name](self, qargs[0])\n+            if name in _basis_2q:\n+                if len(qargs) != 2:\n+                    raise QiskitError("Invalid qubits for 2-qubit gate.")\n+                return _basis_2q[name](self, qargs[0], qargs[1])\n+\n+            # If not a Clifford basis gate we try to unroll the gate and\n+            # raise an exception if unrolling reaches a non-Clifford gate.\n+            if gate.definition is None:\n+                raise QiskitError(f"Cannot apply Instruction: {gate.name}")\n+            if not isinstance(gate.definition, QuantumCircuit):\n+                raise QiskitError(\n+                    "{} instruction definition is {}; expected QuantumCircuit".format(\n+                        gate.name, type(gate.definition)\n+                    )\n                 )\n-            )\n \n-        flat_instr = gate.definition\n-        bit_indices = {\n-            bit: index\n-            for bits in [flat_instr.qubits, flat_instr.clbits]\n-            for index, bit in enumerate(bits)\n-        }\n+            circuit = gate.definition\n \n-        for instruction in flat_instr:\n+        for instruction in circuit:\n             if instruction.clbits:\n                 raise QiskitError(\n                     f"Cannot apply Instruction with classical bits: {instruction.operation.name}"\n                 )\n             # Get the integer position of the flat register\n-            new_qubits = [qargs[bit_indices[tup]] for tup in instruction.qubits]\n+            new_qubits = [qargs[circuit.find_bit(qb)[0]] for qb in instruction.qubits]\n             self._append_circuit(instruction.operation, new_qubits)\n \n         # Since the individual gate evolution functions don't take mod\n@@ -715,6 +690,42 @@ def _evolve_swap(base_pauli, q1, q2):\n     return base_pauli\n \n \n+def _evolve_ecr(base_pauli, q1, q2):\n+    """Update P -> ECR.P.ECR"""\n+    base_pauli = _evolve_s(base_pauli, q1)\n+    base_pauli = _evolve_h(base_pauli, q2)\n+    base_pauli = _evolve_s(base_pauli, q2)\n+    base_pauli = _evolve_h(base_pauli, q2)\n+    base_pauli = _evolve_cx(base_pauli, q1, q2)\n+    base_pauli = _evolve_x(base_pauli, q1)\n+    return base_pauli\n+\n+\n def _count_y(x, z, dtype=None):\n     """Count the number of I Paulis"""\n     return (x & z).sum(axis=1, dtype=dtype)\n+\n+\n+# Basis Clifford Gates\n+_basis_1q = {\n+    "i": _evolve_i,\n+    "id": _evolve_i,\n+    "iden": _evolve_i,\n+    "x": _evolve_x,\n+    "y": _evolve_y,\n+    "z": _evolve_z,\n+    "h": _evolve_h,\n+    "s": _evolve_s,\n+    "sdg": _evolve_sdg,\n+    "sinv": _evolve_sdg,\n+}\n+_basis_2q = {\n+    "cx": _evolve_cx,\n+    "cz": _evolve_cz,\n+    "cy": _evolve_cy,\n+    "swap": _evolve_swap,\n+    "ecr": _evolve_ecr,\n+}\n+\n+# Non-Clifford gates\n+_non_clifford = ["t", "tdg", "ccx", "ccz"]\ndiff --git a/releasenotes/notes/fix-pauli-evolve-ecr-and-name-bugs.yaml b/releasenotes/notes/fix-pauli-evolve-ecr-and-name-bugs.yaml\nnew file mode 100644\nindex 000000000000..143dfadf31e2\n--- /dev/null\n+++ b/releasenotes/notes/fix-pauli-evolve-ecr-and-name-bugs.yaml\n@@ -0,0 +1,6 @@\n+---\n+fixes:\n+  - |\n+    :meth:`.Pauli.evolve` now correctly handles quantum circuits containing ECR gates. Formerly they were not recognized as Clifford gates, and an error was raised.\n+  - |\n+    Fixed a bug in :meth:`.Pauli.evolve` where evolving by a circuit with a name matching certain Clifford gates ('cx', 'cz', etc) would evolve the Pauli according to the name of the circuit, not by the contents of the circuit. This bug occurred only with the non-default option ``frame='s'``.\n