Repository: Qiskit/qiskit (python)

Issue:
plot_circuit_layout colors edges incorrectly
### Environment

- **Qiskit version**: 
- **Python version**: 
- **Operating system**: 


### What is happening?

I believe the intent is for the black edges to connect the black qubits.

### How can we reproduce the issue?

```python
from qiskit.circuit.library import EfficientSU2
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.transpiler import CouplingMap
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.visualization import plot_circuit_layout

# Construct circuit
n_qubits = 10
circuit = EfficientSU2(n_qubits, entanglement="pairwise")

# Initialize simulator backend
coupling_map = CouplingMap.from_heavy_hex(3)
backend = GenericBackendV2(
    coupling_map.size(),
    basis_gates=["ecr", "id", "rz", "sx", "x"],
    coupling_map=coupling_map,
    seed=1234
)

# Transpile circuit
pass_manager = generate_preset_pass_manager(
    optimization_level=3, backend=backend, seed_transpiler=1234
)
isa_circuit = pass_manager.run(circuit)

plot_circuit_layout(isa_circuit, backend, view="physical")
```
![image](https://github.com/Qiskit/qiskit/assets/4296166/c4158a16-30c4-4129-bac7-0c0688526a66)


### What should happen?

Black edges should connect black qubits.

### Any suggestions?

_No response_

Pull request description:
fix edge coloring bug in plot_coupling_map (backport #12369)


### Summary

Fixes #12354 .

### Details and comments

The conversion to an undirected graph changes the edge ordering, causing it not to match the color ordering. This change uses a dictionary to recover the correct ordering.
<hr>This is an automatic backport of pull request #12369 done by [Mergify](https://mergify.com).

Patch:
diff --git a/docs/apidoc/index.rst b/docs/apidoc/index.rst
index 3a6c1b04c..30bb20998 100644
--- a/docs/apidoc/index.rst
+++ b/docs/apidoc/index.rst
@@ -1,42 +1,89 @@
 .. module:: qiskit
+..
+   Within each section, the modules should be ordered alphabetically by
+   module name (not RST filename).
 
 =============
 API Reference
 =============
 
+Circuit construction:
+
 .. toctree::
    :maxdepth: 1
 
    circuit
-   circuit_library
    circuit_classical
-   circuit_singleton
-   compiler
-   visualization
    classicalfunction
+   circuit_library
+   circuit_singleton
+
+Quantum information:
+
+.. toctree::
+   :maxdepth: 1
+
+   quantum_info
+
+Transpilation:
+
+.. toctree::
+   :maxdepth: 1
+
    converters
-   assembler
    dagcircuit
    passmanager
+   synthesis
+   qiskit.synthesis.unitary.aqc
+   transpiler
+   transpiler_passes
+   transpiler_synthesis_plugins
+   transpiler_preset
+   transpiler_plugins
+
+Primitives and providers:
+
+.. toctree::
+   :maxdepth: 1
+
+   primitives
    providers
    providers_basic_provider
    providers_fake_provider
    providers_models
-   pulse
-   scheduler
-   synthesis
-   qiskit.synthesis.unitary.aqc
-   primitives
+
+Results and visualizations:
+
+.. toctree::
+   :maxdepth: 1
+
+   result
+   visualization
+
+Serialization:
+
+.. toctree::
+   :maxdepth: 1
+
    qasm2
    qasm3
-   qobj
    qpy
-   quantum_info
-   result
-   transpiler
-   transpiler_passes
-   transpiler_preset
-   transpiler_plugins
-   transpiler_synthesis_plugins
-   utils
+
+Pulse-level programming:
+
+.. toctree::
+   :maxdepth: 1
+
+   pulse
+   scheduler
+
+Other:
+
+.. toctree::
+   :maxdepth: 1
+
+   assembler
+   compiler
    exceptions
+   qobj
+   utils
diff --git a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py
index 402aa9146..501400f70 100644
--- a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py
+++ b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py
@@ -160,8 +160,13 @@ class Commuting2qGateRouter(TransformationPass):
         if len(dag.qubits) != next(iter(dag.qregs.values())).size:
             raise TranspilerError("Circuit has qubits not contained in the qubit register.")
 
-        new_dag = dag.copy_empty_like()
+        # Fix output permutation -- copied from ElidePermutations
+        input_qubit_mapping = {qubit: index for index, qubit in enumerate(dag.qubits)}
+        self.property_set["original_layout"] = Layout(input_qubit_mapping)
+        if self.property_set["original_qubit_indices"] is None:
+            self.property_set["original_qubit_indices"] = input_qubit_mapping
 
+        new_dag = dag.copy_empty_like()
         current_layout = Layout.generate_trivial_layout(*dag.qregs.values())
 
         # Used to keep track of nodes that do not decompose using swap strategies.
@@ -183,6 +188,8 @@ class Commuting2qGateRouter(TransformationPass):
 
         self._compose_non_swap_nodes(accumulator, current_layout, new_dag)
 
+        self.property_set["virtual_permutation_layout"] = current_layout
+
         return new_dag
 
     def _compose_non_swap_nodes(
diff --git a/qiskit/transpiler/passes/routing/star_prerouting.py b/qiskit/transpiler/passes/routing/star_prerouting.py
index 4b27749c6..b79a298ad 100644
--- a/qiskit/transpiler/passes/routing/star_prerouting.py
+++ b/qiskit/transpiler/passes/routing/star_prerouting.py
@@ -329,13 +329,13 @@ class StarPreRouting(TransformationPass):
             last_2q_gate = None
 
         int_digits = floor(log10(len(processing_order))) + 1
-        processing_order_s = set(processing_order)
+        processing_order_index_map = {
+            node: f"a{str(index).zfill(int(int_digits))}"
+            for index, node in enumerate(processing_order)
+        }
 
         def tie_breaker_key(node):
-            if node in processing_order_s:
-                return "a" + str(processing_order.index(node)).zfill(int(int_digits))
-            else:
-                return node.sort_key
+            return processing_order_index_map.get(node, node.sort_key)
 
         for node in dag.topological_op_nodes(key=tie_breaker_key):
             block_id = node_to_block_id.get(node, None)
diff --git a/qiskit/visualization/gate_map.py b/qiskit/visualization/gate_map.py
index d8ffb6e10..b950c84c9 100644
--- a/qiskit/visualization/gate_map.py
+++ b/qiskit/visualization/gate_map.py
@@ -1039,7 +1039,9 @@ def plot_coupling_map(
         graph = CouplingMap(coupling_map).graph
 
     if not plot_directed:
+        line_color_map = dict(zip(graph.edge_list(), line_color))
         graph = graph.to_undirected(multigraph=False)
+        line_color = [line_color_map[edge] for edge in graph.edge_list()]
 
     for node in graph.node_indices():
         graph[node] = node
@@ -1122,7 +1124,13 @@ def plot_circuit_layout(circuit, backend, view="virtual", qubit_coordinates=None
     Args:
         circuit (QuantumCircuit): Input quantum circuit.
         backend (Backend): Target backend.
-        view (str): Layout view: either 'virtual' or 'physical'.
+        view (str): How to label qubits in the layout. Options:
+
+          - ``"virtual"``: Label each qubit with the index of the virtual qubit that
+            mapped to it.
+          - ``"physical"``: Label each qubit with the index of the physical qubit that it
+            corresponds to on the device.
+
         qubit_coordinates (Sequence): An optional sequence input (list or array being the
             most common) of 2d coordinates for each qubit. The length of the
             sequence must match the number of qubits on the backend. The sequence
diff --git a/releasenotes/notes/fix-swap-router-layout-f28cf0a2de7976a8.yaml b/releasenotes/notes/fix-swap-router-layout-f28cf0a2de7976a8.yaml
new file mode 100644
index 000000000..834d7986a
--- /dev/null
+++ b/releasenotes/notes/fix-swap-router-layout-f28cf0a2de7976a8.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+  - |
+    Fixed an oversight in the :class:`.Commuting2qGateRouter` transpiler pass where the qreg permutations
+    were not added to the pass property set, so they would have to be tracked manually by the user. Now it's 
+    possible to access the permutation through the output circuit's ``layout`` property and plug the pass
+    into any transpilation pipeline without loss of information.
diff --git a/releasenotes/notes/plot-circuit-layout-5935646107893c12.yaml b/releasenotes/notes/plot-circuit-layout-5935646107893c12.yaml
new file mode 100644
index 000000000..72f2c9596
--- /dev/null
+++ b/releasenotes/notes/plot-circuit-layout-5935646107893c12.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+  - |
+    Fixed a bug in :func:`plot_coupling_map` that caused the edges of the coupling map to be colored incorrectly.
+    See https://github.com/Qiskit/qiskit/pull/12369 for details.


Test patch:
diff --git a/test/python/transpiler/test_swap_strategy_router.py b/test/python/transpiler/test_swap_strategy_router.py
index 4a46efd57..d6ca1bde5 100644
--- a/test/python/transpiler/test_swap_strategy_router.py
+++ b/test/python/transpiler/test_swap_strategy_router.py
@@ -1,6 +1,6 @@
 # This code is part of Qiskit.
 #
-# (C) Copyright IBM 2022, 2023.
+# (C) Copyright IBM 2022, 2024.
 #
 # 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
@@ -15,12 +15,14 @@
 from ddt import ddt, data
 
 from qiskit.circuit import QuantumCircuit, Qubit, QuantumRegister
+from qiskit.providers.fake_provider import GenericBackendV2
 from qiskit.transpiler import PassManager, CouplingMap, Layout, TranspilerError
 from qiskit.circuit.library import PauliEvolutionGate, CXGate
 from qiskit.circuit.library.n_local import QAOAAnsatz
 from qiskit.converters import circuit_to_dag
 from qiskit.exceptions import QiskitError
 from qiskit.quantum_info import Pauli, SparsePauliOp
+from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
 from qiskit.transpiler.passes import FullAncillaAllocation
 from qiskit.transpiler.passes import EnlargeWithAncilla
 from qiskit.transpiler.passes import ApplyLayout
@@ -562,9 +564,47 @@ class TestPauliEvolutionSwapStrategies(QiskitTestCase):
 
         self.assertEqual(pm_.run(circ), expected)
 
+    def test_permutation_tracking(self):
+        """Test that circuit layout permutations are properly tracked in the pass property
+        set and returned with the output circuit."""
+
+        # We use the same scenario as the QAOA test above
+        mixer = QuantumCircuit(4)
+        for idx in range(4):
+            mixer.ry(-idx, idx)
+
+        op = SparsePauliOp.from_list([("IZZI", 1), ("ZIIZ", 2), ("ZIZI", 3)])
+        circ = QAOAAnsatz(op, reps=2, mixer_operator=mixer)
+
+        expected_swap_permutation = [3, 1, 2, 0]
+        expected_full_permutation = [1, 3, 2, 0]
+
+        cmap = CouplingMap(couplinglist=[(0, 1), (1, 2), (2, 3)])
+        swap_strat = SwapStrategy(cmap, swap_layers=[[(0, 1), (2, 3)], [(1, 2)]])
+
+        # test standalone
+        swap_pm = PassManager(
+            [
+                FindCommutingPauliEvolutions(),
+                Commuting2qGateRouter(swap_strat),
+            ]
+        )
+        swapped = swap_pm.run(circ.decompose())
+
+        # test as pre-routing step
+        backend = GenericBackendV2(num_qubits=4, coupling_map=[[0, 1], [0, 2], [0, 3]], seed=42)
+        pm = generate_preset_pass_manager(
+            optimization_level=3, target=backend.target, seed_transpiler=40
+        )
+        pm.pre_routing = swap_pm
+        full = pm.run(circ.decompose())
+
+        self.assertEqual(swapped.layout.routing_permutation(), expected_swap_permutation)
+        self.assertEqual(full.layout.routing_permutation(), expected_full_permutation)
+
 
 class TestSwapRouterExceptions(QiskitTestCase):
-    """Test that exceptions are properly raises."""
+    """Test that exceptions are properly raised."""
 
     def setUp(self):
         """Setup useful variables."""