Repository: Qiskit/qiskit

Issue:
Performance regressions in NoiseAdaptiveLayout
<!-- ⚠️ 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 with 7972d6c95de41dbffb06594a27b88ddd2c108ec8
- **Python version**: 3.6
- **Operating system**: Linux

Since https://github.com/Qiskit/qiskit-terra/commit/7972d6c9 merged a few regressions have been tagged by the benchmark suite. These are both runtime such as:

 https://qiskit.github.io/qiskit/#transpiler_qualitative.TranspilerQualitativeBench.time_transpile_time_qft_16?machine=dedicated-benchmarking-softlayer-baremetal&os=Linux%204.15.0-46-generic&ram=16GB&p-optimization%20level=3&p-routing%20method='sabre'&p-layout%20method='noise_adaptive'&commits=7972d6c9
 
 as well as depth of the output:
 
 https://qiskit.github.io/qiskit/#transpiler_qualitative.TranspilerQualitativeBench.track_depth_transpile_4gt10_v1_81?machine=qiskit-benchmarking&os=Ubuntu%2018.04&ram=16%20GB&p-optimization%20level=2&p-routing%20method='stochastic'&p-layout%20method='noise_adaptive'&commits=7972d6c9
 
This is unexpected both because `retworkx.PyDiGraph.neighbors()` should be faster than `retworkx.PyDiGraph.adj` so a runtime regression is the opposite of what's expected. But, what's more concerning is the depth regression because the output should be identical between the two methods since they're using the same internal methods in retworkx, it's just a difference in return format. 

Before we release 0.17.0 we should get to the bottom of these or revert https://github.com/Qiskit/qiskit-terra/commit/7972d6c9


Discussion:
Is this solved? The times seem to come back to normal after 070f9e2

However, depths are still above those before the introduction of 7972d6c. I've noticed that in the removed [TODO it says](https://github.com/Qiskit/qiskit-terra/commit/7972d6c9#diff-e81847b77bca720a95c7ee67f99cccaf2b26c613b2cdcfc1377f6ba940c39274L124):

> Replace with `neighbors_directed()` after...

 I'm unsure if this could be the cause of the problem but, in the commit, it is replaced with `neighbors()`.
Ok. The problem comes from the fact that `neighbors()` returns an iterator with duplicated entries (expected for multigraphs) while the keys in `adj()` are unique. Here you can see a comparison between the data returned while using `adj()` and `neighbors()`:

```
# Running track_depth_transpile_4gt10_v1_81()
adj() call: {3: 1, 4: 1, 0: 1, 1: 1}
adj() call: {0: 1, 4: 1, 1: 1, 2: 1}
adj() call: {0: 1, 1: 1, 2: 1, 3: 1}
Depth: 196

# Running track_depth_transpile_4gt10_v1_81()
neighbors() call: [4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]
neighbors() call: [4, 4, 4, 4, 0, 2, 0, 2, 0, 0, 2, 0, 2, 0, 0, 0, 2, 1, 2, 1, 2, 0, 0, 2, 1, 2, 1, 2]
neighbors() call: [2, 0, 2, 0, 2, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 3, 0, 3, 0, 0, 3, 0, 3, 0]
Depth: 203
```

From the change in the data structure, you can expect longer searches too so it could also explain the regression in time. In [the case of line 124](https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/transpiler/passes/layout/noise_adaptive_layout.py#L124), where the result is used to check membership, the semantics are preserved and it does not matter using one or another although the search time is expected to be longer.

Considering [the case for the loop in line 198](https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/transpiler/passes/layout/noise_adaptive_layout.py#L198), it is driven by the data in the iterator and so, the input data differ depending on the method used.

I'm preparing a PR to address the change in semantics in Terra since I believe this repetition is desired in `retworkx`. If not, I'm happy to provide a patch for the graph library too. Let me know.

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
@@ -121,7 +121,7 @@ def _initialize_backend_prop(self):
                     self.swap_reliabs[i][j] = self.cx_reliability[(j, i)]
                 else:
                     best_reliab = 0.0
-                    for n in self.swap_graph.neighbors(j):
+                    for n in set(self.swap_graph.neighbors(j)):
                         if (n, j) in self.cx_reliability:
                             reliab = math.exp(-swap_reliabs_ro[i][n])*self.cx_reliability[(n, j)]
                         else:
@@ -192,7 +192,7 @@ def _select_best_remaining_qubit(self, prog_qubit):
         """Select the best remaining hardware qubit for the next program qubit."""
         reliab_store = {}
         if prog_qubit not in self.prog_neighbors:
-            self.prog_neighbors[prog_qubit] = self.prog_graph.neighbors(prog_qubit)
+            self.prog_neighbors[prog_qubit] = set(self.prog_graph.neighbors(prog_qubit))
         for hw_qubit in self.available_hw_qubits:
             reliab = 1
             for n in self.prog_neighbors[prog_qubit]:


Test patch: