Repository: Qiskit/qiskit-terra (python)

Issue:
QuantumCircuit.reverse_bits does not work with some circuits with registerless bits
### Environment

- **Qiskit Terra version**: Current main
- **Python version**: 3.8
- **Operating system**: Ubuntu


### What is happening?

The method `QuantumCircuit.reverse_bits` fails when using a circuit with registerless bits. Relates to discussion in #7303.

### How can we reproduce the issue?

```
bits = [Qubit(), Qubit(), Clbit(), Clbit()]
cr = ClassicalRegister(2, "cr")
crx = ClassicalRegister(3, "cs")
circuit = QuantumCircuit(bits, cr, [Clbit()], crx)
circuit.x(0).c_if(crx[1], True)
circuit.measure(0, cr[1])
circuit = circuit.reverse_bits()
```
produces
```
IndexError                                Traceback (most recent call last)
<ipython-input-17-7cc0a3ece212> in <module>
      5 circuit.x(0).c_if(crx[1], True)
      6 circuit.measure(0, cr[1])
----> 7 circuit = circuit.reverse_bits()
      8 circuit.draw()

~/qiskit/qiskit-terra/qiskit/circuit/quantumcircuit.py in reverse_bits(self)
    493 
    494         for inst, qargs, cargs in self.data:
--> 495             new_qargs = [new_qubits[num_qubits - old_qubits.index(q) - 1] for q in qargs]
    496             new_cargs = [new_clbits[num_clbits - old_clbits.index(c) - 1] for c in cargs]
    497             circ._append(inst, new_qargs, new_cargs)

~/qiskit/qiskit-terra/qiskit/circuit/quantumcircuit.py in <listcomp>(.0)
    493 
    494         for inst, qargs, cargs in self.data:
--> 495             new_qargs = [new_qubits[num_qubits - old_qubits.index(q) - 1] for q in qargs]
    496             new_cargs = [new_clbits[num_clbits - old_clbits.index(c) - 1] for c in cargs]
    497             circ._append(inst, new_qargs, new_cargs)

IndexError: list index out of range
```
Also for a simple circuit, the circuit drawers do not reverse the bit labels after a successful `QuantumCircuit.reverse_bits`.
```
qc = QuantumCircuit(3,3)
qc.x(0).c_if(0, 1)
qc = qc.reverse_bits()
qc.draw('mpl', cregbundle=False)
```
produces
![image](https://user-images.githubusercontent.com/16268251/146215769-38ac253e-7725-431d-83e8-71fb99250cd5.png)


### What should happen?

`QuantumCircuit.reverse_bits` shouldn't fail and the method and `reverse_bits=True` in the drawers should produce the same result.

### Any suggestions?

_No response_

Pull request:
Improve reverse_bits to support registerless bits
<!--
⚠️ If you do not respect this template, your pull request will be closed.
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Also, please add a release note file using reno if the change needs to be
  documented in the release notes.
⚠️ If your pull request fixes an open issue, please link to the issue.

- [x] I have added the tests to cover my changes.
- [x] I have updated the documentation accordingly.
- [x] I have read the CONTRIBUTING document.
-->

### Summary
This PR fixes #7415. It modifies `QuantumCircuit.reverse_bits` to support circuits that contain registerless qubit and classical bits.

### Details and comments
I assume the semantics of `reverse_bits` method is to reverse the order of registers but keep the bit order within a register unchanged. This assumption is made based on a test case of the function:

https://github.com/Qiskit/qiskit-terra/blob/6742941a62bf1a12c854f028dab15489118b6c15/test/python/circuit/test_circuit_operations.py#L878-L896

To emphasize these semantics, I modified the example circuit diagram in the docstring.

For registerless bits, I regard them as single-bit anonymous registers. Therefore, they are reversed. A test case is added to demonstrate this case. It would fail without the modification.

close #7415

Patch:
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
index 7951277be..fcc24beed 100644
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -475,37 +475,51 @@ class QuantumCircuit:
             .. parsed-literal::
 
                      ┌───┐
-                q_0: ┤ H ├─────■──────
-                     └───┘┌────┴─────┐
-                q_1: ─────┤ RX(1.57) ├
-                          └──────────┘
+                a_0: ┤ H ├──■─────────────────
+                     └───┘┌─┴─┐
+                a_1: ─────┤ X ├──■────────────
+                          └───┘┌─┴─┐
+                a_2: ──────────┤ X ├──■───────
+                               └───┘┌─┴─┐
+                b_0: ───────────────┤ X ├──■──
+                                    └───┘┌─┴─┐
+                b_1: ────────────────────┤ X ├
+                                         └───┘
 
             output:
 
             .. parsed-literal::
 
-                          ┌──────────┐
-                q_0: ─────┤ RX(1.57) ├
-                     ┌───┐└────┬─────┘
-                q_1: ┤ H ├─────■──────
+                                         ┌───┐
+                b_0: ────────────────────┤ X ├
+                                    ┌───┐└─┬─┘
+                b_1: ───────────────┤ X ├──■──
+                               ┌───┐└─┬─┘
+                a_0: ──────────┤ X ├──■───────
+                          ┌───┐└─┬─┘
+                a_1: ─────┤ X ├──■────────────
+                     ┌───┐└─┬─┘
+                a_2: ┤ H ├──■─────────────────
                      └───┘
         """
         circ = QuantumCircuit(
-            *reversed(self.qregs),
-            *reversed(self.cregs),
+            list(reversed(self.qubits)),
+            list(reversed(self.clbits)),
             name=self.name,
             global_phase=self.global_phase,
         )
-        num_qubits = self.num_qubits
-        num_clbits = self.num_clbits
-        old_qubits = self.qubits
-        old_clbits = self.clbits
-        new_qubits = circ.qubits
-        new_clbits = circ.clbits
+        new_qubit_map = circ.qubits[::-1]
+        new_clbit_map = circ.clbits[::-1]
+        for reg in reversed(self.qregs):
+            bits = [new_qubit_map[self.find_bit(qubit).index] for qubit in reversed(reg)]
+            circ.add_register(QuantumRegister(bits=bits, name=reg.name))
+        for reg in reversed(self.cregs):
+            bits = [new_clbit_map[self.find_bit(clbit).index] for clbit in reversed(reg)]
+            circ.add_register(ClassicalRegister(bits=bits, name=reg.name))
 
         for inst, qargs, cargs in self.data:
-            new_qargs = [new_qubits[num_qubits - old_qubits.index(q) - 1] for q in qargs]
-            new_cargs = [new_clbits[num_clbits - old_clbits.index(c) - 1] for c in cargs]
+            new_qargs = [new_qubit_map[self.find_bit(qubit).index] for qubit in qargs]
+            new_cargs = [new_clbit_map[self.find_bit(clbit).index] for clbit in cargs]
             circ._append(inst, new_qargs, new_cargs)
         return circ
 
diff --git a/releasenotes/notes/fix-reverse_bits-with-registerless-bits-6d17597b99640fb0.yaml b/releasenotes/notes/fix-reverse_bits-with-registerless-bits-6d17597b99640fb0.yaml
new file mode 100644
index 000000000..6fa9789cf
--- /dev/null
+++ b/releasenotes/notes/fix-reverse_bits-with-registerless-bits-6d17597b99640fb0.yaml
@@ -0,0 +1,11 @@
+---
+fixes:
+  - |
+    Fixed :meth:`.QuantumCircuit.reverse_bits` with circuits containing registerless
+    :class:`.Qubit` and :class:`.Clbit`. For example, the following will now work::
+
+        from qiskit.circuit import QuantumCircuit, Qubit, Clbit
+
+        qc = QuantumCircuit([Qubit(), Clbit()])
+        qc.h(0).c_if(qc.clbits[0], 0)
+        qc.reverse_bits()


Test patch:
diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py
index 148ae91ad..4fe619402 100644
--- a/test/python/circuit/test_circuit_operations.py
+++ b/test/python/circuit/test_circuit_operations.py
@@ -1008,6 +1008,80 @@ class TestCircuitOperations(QiskitTestCase):
 
         self.assertEqual(qc.reverse_bits(), expected)
 
+    def test_reverse_bits_with_overlapped_registers(self):
+        """Test reversing order of bits when registers are overlapped."""
+        qr1 = QuantumRegister(2, "a")
+        qr2 = QuantumRegister(bits=[qr1[0], qr1[1], Qubit()], name="b")
+        qc = QuantumCircuit(qr1, qr2)
+        qc.h(qr1[0])
+        qc.cx(qr1[0], qr1[1])
+        qc.cx(qr1[1], qr2[2])
+
+        qr2 = QuantumRegister(bits=[Qubit(), qr1[0], qr1[1]], name="b")
+        expected = QuantumCircuit(qr2, qr1)
+        expected.h(qr1[1])
+        expected.cx(qr1[1], qr1[0])
+        expected.cx(qr1[0], qr2[0])
+
+        self.assertEqual(qc.reverse_bits(), expected)
+
+    def test_reverse_bits_with_registerless_bits(self):
+        """Test reversing order of registerless bits."""
+        q0 = Qubit()
+        q1 = Qubit()
+        c0 = Clbit()
+        c1 = Clbit()
+        qc = QuantumCircuit([q0, q1], [c0, c1])
+        qc.h(0)
+        qc.cx(0, 1)
+        qc.x(0).c_if(1, True)
+        qc.measure(0, 0)
+
+        expected = QuantumCircuit([c1, c0], [q1, q0])
+        expected.h(1)
+        expected.cx(1, 0)
+        expected.x(1).c_if(0, True)
+        expected.measure(1, 1)
+
+        self.assertEqual(qc.reverse_bits(), expected)
+
+    def test_reverse_bits_with_registers_and_bits(self):
+        """Test reversing order of bits with registers and registerless bits."""
+        qr = QuantumRegister(2, "a")
+        q = Qubit()
+        qc = QuantumCircuit(qr, [q])
+        qc.h(qr[0])
+        qc.cx(qr[0], qr[1])
+        qc.cx(qr[1], q)
+
+        expected = QuantumCircuit([q], qr)
+        expected.h(qr[1])
+        expected.cx(qr[1], qr[0])
+        expected.cx(qr[0], q)
+
+        self.assertEqual(qc.reverse_bits(), expected)
+
+    def test_reverse_bits_with_mixed_overlapped_registers(self):
+        """Test reversing order of bits with overlapped registers and registerless bits."""
+        q = Qubit()
+        qr1 = QuantumRegister(bits=[q, Qubit()], name="qr1")
+        qr2 = QuantumRegister(bits=[qr1[1], Qubit()], name="qr2")
+        qc = QuantumCircuit(qr1, qr2, [Qubit()])
+        qc.h(q)
+        qc.cx(qr1[0], qr1[1])
+        qc.cx(qr1[1], qr2[1])
+        qc.cx(2, 3)
+
+        qr2 = QuantumRegister(2, "qr2")
+        qr1 = QuantumRegister(bits=[qr2[1], q], name="qr1")
+        expected = QuantumCircuit([Qubit()], qr2, qr1)
+        expected.h(qr1[1])
+        expected.cx(qr1[1], qr1[0])
+        expected.cx(qr1[0], qr2[0])
+        expected.cx(1, 0)
+
+        self.assertEqual(qc.reverse_bits(), expected)
+
     def test_cnot_alias(self):
         """Test that the cnot method alias adds a cx gate."""
         qc = QuantumCircuit(2)