Repository: Qiskit/qiskit

Issue:
NoiseAdaptiveLayout cannot handle inconsistent properties/configuration (like in FakeMelbourne)
### Environment

- **Qiskit Terra version**: 0.19.1
- **Python version**: 3.9
- **Operating system**: macOS Monterey


### What is happening?

Hi, I was playing around with different layout & routing options in the transpiler, and I noticed that the NoiseAdaptiveLayout pass sometime leads to errors in later passes because it adds invalid qubits to the layout.

### How can we reproduce the issue?

Here's a minimized version of the example that I saw failing:
```
from qiskit import QuantumCircuit
from qiskit.test.mock import FakeMelbourne
from qiskit.compiler import transpile

backend = FakeMelbourne()
circuit = QuantumCircuit(12)
circuit.cx(1, 8)
circuit.cx(1, 11)

optimized_0 = transpile(circuit, backend=backend, optimization_level=0, layout_method='noise_adaptive')
```

### What should happen?

On my machine, this code produces an error `IndexError: index 14 is out of bounds for axis 1 with size 14`.

After some prodding, it seems that NoiseAdaptiveLayout is producing a layout that uses 15 qubits even though the backend only provides 14. To see this, you can run:
```
from qiskit import QuantumCircuit
from qiskit.test.mock import FakeMelbourne
from qiskit.transpiler import PassManager, CouplingMap
from qiskit.transpiler.passes import NoiseAdaptiveLayout
from qiskit.transpiler.passes import FullAncillaAllocation, EnlargeWithAncilla, ApplyLayout

backend = FakeMelbourne() # backend provides 14 qubits
coupling_map = CouplingMap(couplinglist=backend.configuration().coupling_map)
circuit = QuantumCircuit(12) # circuit has 12 qubits (and only uses 3)
circuit.cx(1, 8)
circuit.cx(1, 11)

_choose_layout = NoiseAdaptiveLayout(backend.properties())
def _choose_layout_condition(property_set):
    return not property_set["layout"]
_embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()]
pm = PassManager()
pm.append(_choose_layout, condition=_choose_layout_condition)
pm.append(_embed)

opt_circuit = pm.run(circuit)
print(opt_circuit) # circuit after layout has 15 qubits
```


### Any suggestions?

Sorry I don't have a more concise example. I tried to reproduce the issue on a smaller backend (like Yorktown) but I couldn't find a circuit that caused this issue. Possibly there is something strange about the Melbourne backend?


Discussion:
This seems to be a bug in the fake Melbourne backend since its properties report an extra qubit:

```python
from qiskit.test.mock import FakeMelbourne

backend = FakeMelbourne()
props = backend.properties()
conf = backend.configuration()

conf.num_qubits, len(props.qubits)
----
Output: (14, 15)
```
Thanks for the response! Should I delete this issue / rename to something about the Melbourne backend?

Resolving patch:
diff --git a/qiskit/transpiler/passes/layout/noise_adaptive_layout.py b/qiskit/transpiler/passes/layout/noise_adaptive_layout.py
--- a/qiskit/transpiler/passes/layout/noise_adaptive_layout.py
+++ b/qiskit/transpiler/passes/layout/noise_adaptive_layout.py
@@ -13,7 +13,7 @@
 """Choose a noise-adaptive Layout based on current calibration data for the backend."""
 
 import math
-
+from copy import deepcopy
 import rustworkx as rx
 
 from qiskit.transpiler.layout import Layout
@@ -55,11 +55,14 @@ class NoiseAdaptiveLayout(AnalysisPass):
          by being set in `property_set`.
     """
 
-    def __init__(self, backend_prop):
+    def __init__(self, backend_prop, coupling_map=None):
         """NoiseAdaptiveLayout initializer.
 
         Args:
             backend_prop (Union[BackendProperties, Target]): backend properties object
+            coupling_map (CouplingMap): Optional. To filter the backend_prop qubits/gates.
+                This parameter is ignored if :class:`.Target` is provided in ``backend_prop``.
+                That method is preferred.
 
         Raises:
             TranspilerError: if invalid options
@@ -70,7 +73,21 @@ def __init__(self, backend_prop):
             self.backend_prop = target_to_backend_properties(self.target)
         else:
             self.target = None
+
+            if coupling_map:
+                # A backend might have more properties than qubits/gates in the configuration. This is a
+                # problem that the Target path should handle differently (by solving that possible
+                # inconsistency internally). For the non-target path, this is a possible solution.
+                # See https://github.com/Qiskit/qiskit/issues/7677
+                backend_prop = deepcopy(backend_prop)
+                edge_set = set(coupling_map.graph.edge_list())
+                backend_prop.gates = filter(
+                    lambda ginfo: tuple(ginfo.qubits) in edge_set,
+                    backend_prop.gates,
+                )
+                backend_prop.qubits = backend_prop.qubits[: 1 + max(coupling_map.physical_qubits)]
             self.backend_prop = backend_prop
+
         self.swap_graph = rx.PyDiGraph()
         self.cx_reliability = {}
         self.readout_reliability = {}
diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py
--- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py
+++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py
@@ -775,7 +775,9 @@ def _choose_layout_condition(property_set):
         layout.append(_given_layout)
         if pass_manager_config.target is None:
             layout.append(
-                NoiseAdaptiveLayout(pass_manager_config.backend_properties),
+                NoiseAdaptiveLayout(
+                    pass_manager_config.backend_properties, pass_manager_config.coupling_map
+                ),
                 condition=_choose_layout_condition,
             )
         else:


Test patch:
diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py
--- a/test/python/transpiler/test_preset_passmanagers.py
+++ b/test/python/transpiler/test_preset_passmanagers.py
@@ -121,6 +121,22 @@ def test_no_coupling_map(self, level):
         circuit.cz(q[0], q[1])
         result = transpile(circuit, basis_gates=["u1", "u2", "u3", "cx"], optimization_level=level)
         self.assertIsInstance(result, QuantumCircuit)
+        self.assertEqual(result.num_qubits, circuit.num_qubits)
+
+    @combine(level=[0, 1, 2, 3], name="level{level}")
+    def test_7677(self, level):
+        """Melbourne (with inconsistency configuration/properties) should not fail with noise_adaptive
+        See: https://github.com/Qiskit/qiskit/issues/7677
+        """
+        qc = QuantumCircuit(12)  # circuit has 12 qubits (and only uses 3)
+        qc.cx(1, 8)
+        qc.cx(1, 11)
+        backend = FakeMelbourne()
+
+        result = transpile(qc, backend, layout_method="noise_adaptive", optimization_level=level)
+
+        self.assertIsInstance(result, QuantumCircuit)
+        self.assertEqual(result.num_qubits, 14)
 
     def test_layout_3239(self, level=3):
         """Test final layout after preset level3 passmanager does not include diagonal gates