Repository: Qiskit/qiskit

Issue:
MPL circuit drawer returning qubit[register] names for input
### Environment

- **Qiskit Terra version**: main
- **Python version**: 
- **Operating system**: 

Circuit drawings seem to include the full Qubit[register] label rather than just eg`q0`, which makes the drawings a bit messy looking

```python
qc = EfficientSU2(6, entanglement='circular')
x0 = np.random.random(size=qc.num_parameters)
qc.assign_parameters(x0, inplace=True)
trans_circ = transpile(qc, backend,
                       basis_gates=['rz', 'sx', 'cx'],
                       optimization_level=3)
trans_circ.draw('mpl', idle_wires=False, fold=-1)
```
<img width="764" alt="Screenshot 2023-10-18 at 11 35 21" src="https://github.com/Qiskit/qiskit/assets/1249193/2271bf5d-c2cc-4cf8-aa86-f4661bd9d063">


### What is happening?

see above

### How can we reproduce the issue?

see above

### What should happen?

The drawer should return simple labels for the input qubits

### Any suggestions?

_No response_


Discussion:
This isn't in the 0.25.x branch so must have been introduced during the 0.45.x development.  This is a bug, so we'll target a fix for it each during the 0.45.0rc1 lifecycle (before 0.45.0 proper) or into the first patch release of the 0.45.x series, depending on when we can get it fixed.
A couple of questions.
- Does this occur when "mpl" is replaced by "text"?
- What backend are you using here? I picked an arbitrary backend and was unable to reproduce this with current main.

Thanks.
Edwin: I made a full repro script like this

```python
from qiskit import transpile
from qiskit.circuit.library import EfficientSU2
from qiskit.providers.fake_provider import FakePerth

backend = FakePerth()

qc = EfficientSU2(6, entanglement='circular')
x0 = np.random.random(size=qc.num_parameters)
qc.assign_parameters(x0, inplace=True)
trans_circ = transpile(qc, backend,
                       basis_gates=['rz', 'sx', 'cx'],
                       optimization_level=3)
trans_circ.draw('mpl', idle_wires=False, fold=-1)
```

and I see the behaviour with both mpl and text drawers on current `main` at 457aaa2bcd9899935154dd3f655ecf9e4ece09ae.
Yeah, it looks like those names are being loaded into `QuantumCircuit._layout`, so it appears to happen prior to the call to the drawers.
I also did some tests on this and it seems to me at least that it happens only if the optimization_level is higher than 0 when running the transpile function. Moreover, I observed a pattern that I think can point us to the pass that does this

- following the example above, when the entanglement is set out as 'linear' or 'reverse_linear' this bug doesn't happen, it seems to be displayed correctly

So I tried out with a smaller example in order to get a feeling of where this might be coming from.

The following example showcases the same bug (again, only for _optimization_level > 0_)
 ```
qc = QuantumCircuit(3)
qc.cx(0, 1)
qc.cx(1, 2)
qc.cx(2, 0)

trans_circ = transpile(qc, backend,
                       basis_gates=['rz', 'sx', 'cx'],
                       optimization_level=1)
trans_circ.draw('mpl')
```

But the following doesn't showcase the same bug (for all the optimization_levels)

```
qc = QuantumCircuit(3)
qc.cx(0, 1)
qc.cx(1, 2)
trans_circ = transpile(qc, backend,
                       basis_gates=['rz', 'sx', 'cx'],
                       optimization_level=1)
trans_circ.draw('mpl')
```

So I think this comes from a pass starting at optimization_level 1. However, I'll try to see If I can find out more
Update: the following code reproduces the bug:
```
qc = QuantumCircuit(3)

qc.cx(0, 1)
qc.cx(1, 2)
qc.cx(0, 2)

qc_t = PassManager([SabreLayout(backend.coupling_map)]).run(qc)

qc_t.draw('mpl')
```
~~Maybe it has to do with SabreLayout.?~~
`SabreLayout` isn't the problem, what you're actually seeing is that the problem happens when we need to run layout and routing, because that sets a full `TranspileLayout` on the output circuit.
```python
from qiskit import QuantumCircuit
from qiskit.transpiler import Layout, TranspileLayout
qc = QuantumCircuit(3)
qc.cx(0, 1)

# This isn't something users should do, but the point is that it's just the fault of a layout being set:
phys_map = {q: i for i, q in enumerate(qc.qubits)}
qc._layout = TranspileLayout(Layout(phys_map), phys_map, Layout(phys_map))

# Drawing it:
qc.draw("mpl")
```
Oh, I see. My mistake, sorry for that
Oh no sorry, don't be sorry - thank you for looking into it!
Not quite sure I have a full understanding, but here's what I've got so far. 

In `visualization/circuit/utils.py` in the function `get_wire_label`, there is a call to `Layout.get_registers`. If FakeMelbourne is used, both the `q` register and the `ancilla` register are returned.

If FakePerth is used, only the `ancilla` register is returned and since this does not match the register in the bit to be displayed, the function assumes this is a bit without a register and displays the whole bit.

So the question is 'When TranspileLayout is used, why is only one register returned?'
Hmm, nothing in `TranspileLayout` should be causing this. It's just a dataclass container and the `layout` property set field should get set as `TranspileLayout.initial_layout` which is what the visualizers access IIRC: https://github.com/Qiskit/qiskit/blob/main/qiskit/transpiler/runningpassmanager.py#L131 What this points to for me is that we're missing adding a register when we create the `Layout` object that goes into the property set.

I believe that @SoranaAurelia is correct and I think the bug is actually in `SabreLayout`. If you look at: https://github.com/Qiskit/qiskit/blob/e49ff9096f9299352487656eb1d8f7022eebfd1a/qiskit/transpiler/passes/layout/sabre_layout.py#L270-L280
we're setting the layout on the property set but neglecting to add the register information about the registers any of the bits in the layout belong to. It's easy to fix we just need to ensure we add the registers which is already done in the non-rust path above it: 

https://github.com/Qiskit/qiskit/blob/e49ff9096f9299352487656eb1d8f7022eebfd1a/qiskit/transpiler/passes/layout/sabre_layout.py#L250-L252

so we can just add that to after the `Layout` object creation in the rust sabre code path and it should correctly find the labels. Although, if this is the root cause, it's been this way for a long time since at least #9116 which moved to creating the layout by just using bit indices, after we moved `SabreLayout` to rust so something else changing is probably triggering it. But at least annotating the `Layout` object with registers in `SabreLayout` will fix the reported issue I think.
Yeah, `TranspileLayout` isn't the problem itself, though `SabreLayout` being part of the problem does make sense with why it would have appeared only recently.

I don't think the drawers should be reading any information about registers from a `Layout`, and really, a `Layout` shouldn't contain any information about registers.  Quantum registers are a circuit-level construction only, and the drawers should be determining "what do I call this `Qubit`" by asking the circuit, not the layout.  The root problem is here: https://github.com/Qiskit/qiskit/blob/80018cc14ce682a8d1b9fab4a4e48cc1d7a90bcc/qiskit/visualization/circuit/_utils.py#L260-L285

On line 268, the `layout` should not be referenced; it should be the circuit.  I can provide a fix.
Ah, no, sorry, you're right - after a transpilation, we don't have the initial virtual bit objects any more in the circuit.  I guess in that case we _do_ need to get the information from somewhere else, and in practice that's going to have to be some portion of the layout.

The `Layout` object itself does not seem like the correct place to me, because that's just meant to be a bidict.  It feels like it ought to be a generalisation of the `TranspileLayout.input_qubit_mapping` field that includes all the information about containing registers in the initial circuit, if we're committing to transferring that information over.

Maybe in practice, though, since `Layout` _has_ been doing this for so long, it's ok to let it keep doing it.  The code in the drawers to retrieve bit labels is horribly inefficient because of its use of `Layout`, but the information is (in theory) there at least.
This was interesting! But yeah, as @mtreinish suggested, adding 
```py
for qreg in dag.qregs.values():
    self.property_set["layout"].add_register(qreg)
```
to the sabre_layout code fixed the problem locally. I was not able to reproduce the bug anymore
@SoranaAurelia Yep, that seems to work. Would you like to do a PR for this? 

@jakelishman 
> The Layout object itself does not seem like the correct place to me, because that's just meant to be a bidict. It feels like it ought to be a generalisation of the TranspileLayout.input_qubit_mapping field that includes all the information about containing registers in the initial circuit, if we're committing to transferring that information over.

Agreed having a `{bit: reg}` mapping would be more efficient. I tried creating that once at the beginning of the drawers and using it in `get_wire_label`. I didn't notice any performance difference however up to 300 qubits. The text version did a transpilation and display on a simple circuit with 300 qubits in .25 sec. and the mpl did 2.5 sec on my local. So the performance aspect is probably not that significant.
@enavarro51 Yes, I would like to do a PR on this :) 

Resolving patch:
diff --git a/qiskit/transpiler/passes/layout/sabre_layout.py b/qiskit/transpiler/passes/layout/sabre_layout.py
--- a/qiskit/transpiler/passes/layout/sabre_layout.py
+++ b/qiskit/transpiler/passes/layout/sabre_layout.py
@@ -279,6 +279,10 @@ def run(self, dag):
             }
         )
 
+        # Add the existing registers to the layout
+        for qreg in dag.qregs.values():
+            self.property_set["layout"].add_register(qreg)
+
         # If skip_routing is set then return the layout in the property set
         # and throwaway the extra work we did to compute the swap map.
         # We also skip routing here if there is more than one connected


Test patch:
diff --git a/test/visual/mpl/circuit/references/qreg_names_after_layout.png b/test/visual/mpl/circuit/references/qreg_names_after_layout.png
new file mode 100644
Binary files /dev/null and b/test/visual/mpl/circuit/references/qreg_names_after_layout.png differ
diff --git a/test/visual/mpl/circuit/test_circuit_matplotlib_drawer.py b/test/visual/mpl/circuit/test_circuit_matplotlib_drawer.py
--- a/test/visual/mpl/circuit/test_circuit_matplotlib_drawer.py
+++ b/test/visual/mpl/circuit/test_circuit_matplotlib_drawer.py
@@ -1,6 +1,6 @@
 # This code is part of Qiskit.
 #
-# (C) Copyright IBM 2020.
+# (C) Copyright IBM 2020, 2023.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory
@@ -2177,6 +2177,7 @@ def test_control_flow_nested_layout(self):
         backend.target.add_instruction(SwitchCaseOp, name="switch_case")
         backend.target.add_instruction(IfElseOp, name="if_else")
         tqc = transpile(qc, backend, optimization_level=2, seed_transpiler=671_42)
+
         fname = "nested_layout_control_flow.png"
         self.circuit_drawer(tqc, output="mpl", filename=fname)
 
@@ -2208,6 +2209,31 @@ def test_iqx_pendingdeprecation(self):
             ):
                 qc.draw("mpl", style=style)
 
+    def test_no_qreg_names_after_layout(self):
+        """Test that full register names are not shown after transpilation.
+        See https://github.com/Qiskit/qiskit-terra/issues/11038"""
+        backend = FakeBelemV2()
+
+        qc = QuantumCircuit(3)
+        qc.cx(0, 1)
+        qc.cx(1, 2)
+        qc.cx(2, 0)
+        circuit = transpile(
+            qc, backend, basis_gates=["rz", "sx", "cx"], layout_method="sabre", seed_transpiler=42
+        )
+
+        fname = "qreg_names_after_layout.png"
+        self.circuit_drawer(circuit, output="mpl", filename=fname)
+
+        ratio = VisualTestUtilities._save_diff(
+            self._image_path(fname),
+            self._reference_path(fname),
+            fname,
+            FAILURE_DIFF_DIR,
+            FAILURE_PREFIX,
+        )
+        self.assertGreaterEqual(ratio, 0.9999)
+
 
 if __name__ == "__main__":
     unittest.main(verbosity=1)