Repository: Qiskit/qiskit

Issue:
SabreLayout Fails when Quantum Register < Coupling Map size
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Information

- **Qiskit Terra version**: Master, installed from source, v15.0
- **Python version**: Anaconda python, v3.7.6
- **Operating system**: Windows 10 LTSC 1809

### What is the current behavior?
When specifying a QuantumRegister that is smaller in magnitude than the size of the coupling_map you're using, SabreLayout will throw an error.


### Steps to reproduce the problem
```python
qasm_circuit = """OPENQASM 2.0;
include "qelib1.inc";
qreg q[6];
cx q[3],q[0];
cx q[1],q[0];
cx q[5],q[0];
x q[2];
cx q[2],q[0];
x q[4];
cx q[4],q[0];"""

qasm_hardware = """OPENQASM 2.0;
include "qelib1.inc";
qreg q[15];
cx q[3],q[0];
cx q[1],q[0];
cx q[5],q[0];
x q[2];
cx q[2],q[0];
x q[4];
cx q[4],q[0];"""

from qiskit import QuantumCircuit
from qiskit.transpiler import CouplingMap, passes, PassManager

# Load qreg = # logical qubits and qreg = # physical qubits circuits
logical = QuantumCircuit.from_qasm_str(qasm_circuit)
physical = QuantumCircuit.from_qasm_str(qasm_hardware)

# Define hardware graph (15 qubit)
melbourne = [[1, 0], [1, 2], [2, 3], [4, 3], [4, 10], [5, 4], [5, 6], [5, 9], [6, 8], [7, 8],
            [9, 8], [9, 10], [11, 10], [11, 3], [11, 12], [12, 2], [13, 1], [13, 12], [14, 0],
            [14, 13], [0, 1], [2, 1], [3, 2], [3, 4], [10, 4], [4, 5], [6, 5], [9, 5], [8, 6],
            [8, 7], [8, 9], [10, 9], [10, 11], [3, 11], [12, 11], [2, 12], [1, 13], [12, 13],
            [0, 14], [13, 14]]

# Define coupling map object
coupling_map = CouplingMap(couplinglist=melbourne)

# Routing passes
basic_r = passes.BasicSwap(coupling_map)

# Layout passes
sabre_l = passes.SabreLayout(coupling_map)

# Make pass lists
pass_sabre = [sabre_l, basic_r]

# Define passmanager object
pm = PassManager(pass_sabre)

# Run passes
logical_qreg = pm.run(logical)
physical_qreg = pm.run(physical)
```

output
```python
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-21-ce5f1efe766e> in <module>
     51 
     52 # Run passes
---> 53 logical_qreg = pm.run(logical)
     54 physical_qreg = pm.run(physical)

c:\anaconda3\envs\qubit\lib\site-packages\qiskit\transpiler\passmanager.py in run(self, circuits, output_name, callback)
    212         """
    213         if isinstance(circuits, QuantumCircuit):
--> 214             return self._run_single_circuit(circuits, output_name, callback)
    215         elif len(circuits) == 1:
    216             return self._run_single_circuit(circuits[0], output_name, callback)

c:\anaconda3\envs\qubit\lib\site-packages\qiskit\transpiler\passmanager.py in _run_single_circuit(self, circuit, output_name, callback)
    275         if callback is None and self.callback:  # TODO to remove with __init__(callback)
    276             callback = self.callback
--> 277         result = running_passmanager.run(circuit, output_name=output_name, callback=callback)
    278         self.property_set = running_passmanager.property_set
    279         return result

c:\anaconda3\envs\qubit\lib\site-packages\qiskit\transpiler\runningpassmanager.py in run(***failed resolving arguments***)
    113         for passset in self.working_list:
    114             for pass_ in passset:
--> 115                 dag = self._do_pass(pass_, dag, passset.options)
    116 
    117         circuit = dag_to_circuit(dag)

c:\anaconda3\envs\qubit\lib\site-packages\qiskit\transpiler\runningpassmanager.py in _do_pass(self, pass_, dag, options)
    143         # Run the pass itself, if not already run
    144         if pass_ not in self.valid_passes:
--> 145             dag = self._run_this_pass(pass_, dag)
    146 
    147             # update the valid_passes property

c:\anaconda3\envs\qubit\lib\site-packages\qiskit\transpiler\runningpassmanager.py in _run_this_pass(self, pass_, dag)
    174             # Measure time if we have a callback or logging set
    175             start_time = time()
--> 176             pass_.run(FencedDAGCircuit(dag))
    177             end_time = time()
    178             run_time = end_time - start_time

c:\anaconda3\envs\qubit\lib\site-packages\qiskit\transpiler\passes\layout\sabre_layout.py in run(self, dag)
    105                 final_layout = self._compose_layouts(initial_layout,
    106                                                      pass_final_layout,
--> 107                                                      circ.qregs)
    108                 initial_layout = final_layout
    109                 circ = circ.reverse_ops()

c:\anaconda3\envs\qubit\lib\site-packages\qiskit\transpiler\passes\layout\sabre_layout.py in _compose_layouts(self, initial_layout, pass_final_layout, qregs)
    141         trivial_layout = Layout.generate_trivial_layout(*qregs)
    142         pass_final_layout = Layout({trivial_layout[v.index]: p
--> 143                                     for v, p in pass_final_layout.get_virtual_bits().items()})
    144         qubit_map = Layout.combine_into_edge_map(initial_layout, trivial_layout)
    145         final_layout = {v: pass_final_layout[qubit_map[v]]

c:\anaconda3\envs\qubit\lib\site-packages\qiskit\transpiler\passes\layout\sabre_layout.py in <dictcomp>(.0)
    141         trivial_layout = Layout.generate_trivial_layout(*qregs)
    142         pass_final_layout = Layout({trivial_layout[v.index]: p
--> 143                                     for v, p in pass_final_layout.get_virtual_bits().items()})
    144         qubit_map = Layout.combine_into_edge_map(initial_layout, trivial_layout)
    145         final_layout = {v: pass_final_layout[qubit_map[v]]

c:\anaconda3\envs\qubit\lib\site-packages\qiskit\transpiler\layout.py in __getitem__(self, item)
    102         if item in self._v2p:
    103             return self._v2p[item]
--> 104         raise KeyError('The item %s does not exist in the Layout' % (item,))
    105 
    106     def __setitem__(self, key, value):

KeyError: 'The item 6 does not exist in the Layout'
```

The item it specifies seems to be random as well (i.e. it can throw item 6 - item 14 as KeyError). Note that the line:
```python
 physical_qreg = pm.run(physical)
```
will not fail, just the one that is using the circuit with the Quantum Register < Coupling Map size.

### What is the expected behavior?
If you specify the initial layout yourself or use the built in Trivial or Dense options, they will not fail when the Quantum Register is less than the hardware graph size and I imagine it should be the same with Sabre. As far as I can tell, if you don't specify the Quantum Register size as the # logical qubits in the circuit, if you use the:
```python
[FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()]
```
passes then it won't properly label the 'extra' qubits as ancilla, so you probably want to always specify the # logical qubits as the Quantum Register size. Granted, this analysis could be wrong, but it seems that way from what I could tell.


### Suggested solutions
Unsure what the source of the problem is (new-ish to qiskit), so no suggestions from me.




Discussion:
Hi, I also encountered this bug. I think replacing 'circ.qregs' with new-circ.qregs at line 104 in sabre_layout.py would solve the problem.

before
```python
for i in range(self.max_iterations):
            for _ in ('forward', 'backward'):
                pm = self._layout_and_route_passmanager(initial_layout)
                new_circ = pm.run(circ)

                # Update initial layout and reverse the unmapped circuit.
                pass_final_layout = pm.property_set['final_layout']
                final_layout = self._compose_layouts(initial_layout,
                                                     pass_final_layout,
                                                     circ.qregs)
                initial_layout = final_layout
                circ = circ.reverse_ops()
```

after
```python
for i in range(self.max_iterations):
            for _ in ('forward', 'backward'):
                pm = self._layout_and_route_passmanager(initial_layout)
                new_circ = pm.run(circ)

                # Update initial layout and reverse the unmapped circuit.
                pass_final_layout = pm.property_set['final_layout']
                final_layout = self._compose_layouts(initial_layout,
                                                     pass_final_layout,
                                                     new_circ.qregs)
                initial_layout = final_layout
                circ = circ.reverse_ops()
```

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
@@ -102,7 +102,7 @@ def run(self, dag):
                 pass_final_layout = pm.property_set['final_layout']
                 final_layout = self._compose_layouts(initial_layout,
                                                      pass_final_layout,
-                                                     circ.qregs)
+                                                     new_circ.qregs)
                 initial_layout = final_layout
                 circ = circ.reverse_ops()
 


Test patch:
diff --git a/test/python/transpiler/test_sabre_layout.py b/test/python/transpiler/test_sabre_layout.py
new file mode 100644
--- /dev/null
+++ b/test/python/transpiler/test_sabre_layout.py
@@ -0,0 +1,83 @@
+# This code is part of Qiskit.
+#
+# (C) Copyright IBM 2017, 2020.
+#
+# 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
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+"""Test the SabreLayout pass"""
+
+import unittest
+
+from qiskit import QuantumRegister, QuantumCircuit
+from qiskit.transpiler import CouplingMap
+from qiskit.transpiler.passes import SabreLayout
+from qiskit.converters import circuit_to_dag
+from qiskit.test import QiskitTestCase
+from qiskit.test.mock import FakeAlmaden
+
+
+class TestSabreLayout(QiskitTestCase):
+    """Tests the SabreLayout pass"""
+
+    def setUp(self):
+        self.cmap20 = FakeAlmaden().configuration().coupling_map
+
+    def test_5q_circuit_20q_coupling(self):
+        """Test finds layout for 5q circuit on 20q device.
+        """
+        qr = QuantumRegister(5, 'q')
+        circuit = QuantumCircuit(qr)
+        circuit.cx(qr[0], qr[2])
+        circuit.cx(qr[1], qr[3])
+        circuit.cx(qr[3], qr[0])
+        circuit.x(qr[2])
+        circuit.cx(qr[4], qr[2])
+        circuit.x(qr[1])
+        circuit.cx(qr[1], qr[2])
+
+        dag = circuit_to_dag(circuit)
+        pass_ = SabreLayout(CouplingMap(self.cmap20), seed=0)
+        pass_.run(dag)
+
+        layout = pass_.property_set['layout']
+        self.assertEqual(layout[qr[0]], 10)
+        self.assertEqual(layout[qr[1]], 12)
+        self.assertEqual(layout[qr[2]], 7)
+        self.assertEqual(layout[qr[3]], 11)
+        self.assertEqual(layout[qr[4]], 13)
+
+    def test_6q_circuit_20q_coupling(self):
+        """Test finds layout for 6q circuit on 20q device.
+        """
+        qr0 = QuantumRegister(3, 'q0')
+        qr1 = QuantumRegister(3, 'q1')
+        circuit = QuantumCircuit(qr0, qr1)
+        circuit.cx(qr1[0], qr0[0])
+        circuit.cx(qr0[1], qr0[0])
+        circuit.cx(qr1[2], qr0[0])
+        circuit.x(qr0[2])
+        circuit.cx(qr0[2], qr0[0])
+        circuit.x(qr1[1])
+        circuit.cx(qr1[1], qr0[0])
+
+        dag = circuit_to_dag(circuit)
+        pass_ = SabreLayout(CouplingMap(self.cmap20), seed=0)
+        pass_.run(dag)
+
+        layout = pass_.property_set['layout']
+        self.assertEqual(layout[qr0[0]], 2)
+        self.assertEqual(layout[qr0[1]], 3)
+        self.assertEqual(layout[qr0[2]], 10)
+        self.assertEqual(layout[qr1[0]], 1)
+        self.assertEqual(layout[qr1[1]], 7)
+        self.assertEqual(layout[qr1[2]], 5)
+
+
+if __name__ == '__main__':
+    unittest.main()