Skip to content

Commit ab34cdd

Browse files
mtreinishking-p3nguin
authored andcommitted
Don't run routing in SabreLayout with split components (Qiskit#10000)
* Don't run routing in SabreLayout with split components This commit fixes an issue in the `SabreLayout` pass when run on backends with a disjoint connectivity graph. The `SabreLayout` pass internally runs routing as part of its operation and as a performance efficiency we use that routing output by default. However, in the case of a disjoint coupling map we are splitting the circuit up into different subcircuits to map that to the connected components on the backend. Doing final routing as part of that split context is no sound because depsite the quantum operations being isolated to each component, other operations could have a dependency between the components. This was previously discovered during the development of Qiskit#9802 to be the case with classical data/bits, but since merging that it also has come up with barriers. To prevent any issues in the future this commit changes the SabreLayout pass to never use the routing output during the layout search when there is >1 connected component because we *need* to rerun routing on the full circuit after applying the layout. Fixes Qiskit#9995 * Remove unused shared_clbits logic * Remove swap and routing assertions from disjoint sabre layout tests * Fix lint
1 parent 883b21d commit ab34cdd

File tree

3 files changed

+65
-54
lines changed

3 files changed

+65
-54
lines changed

qiskit/transpiler/passes/layout/sabre_layout.py

+5-13
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,6 @@ def run(self, dag):
232232
)
233233
initial_layout_dict = {}
234234
final_layout_dict = {}
235-
shared_clbits = False
236-
seen_clbits = set()
237235
for (
238236
layout_dict,
239237
final_dict,
@@ -244,20 +242,14 @@ def run(self, dag):
244242
) in layout_components:
245243
initial_layout_dict.update({k: component_map[v] for k, v in layout_dict.items()})
246244
final_layout_dict.update({component_map[k]: component_map[v] for k, v in final_dict})
247-
if not shared_clbits:
248-
for clbit in local_dag.clbits:
249-
if clbit in seen_clbits:
250-
shared_clbits = True
251-
break
252-
seen_clbits.add(clbit)
253245
self.property_set["layout"] = Layout(initial_layout_dict)
254246
# If skip_routing is set then return the layout in the property set
255247
# and throwaway the extra work we did to compute the swap map.
256-
# We also skip routing here if the input circuit is split over multiple
257-
# connected components and there is a shared clbit between any
258-
# components. We can only reliably route the full dag if there is any
259-
# shared classical data.
260-
if self.skip_routing or shared_clbits:
248+
# We also skip routing here if there is more than one connected
249+
# component we ran layout on. We can only reliably route the full dag
250+
# in this case if there is any dependency between the components
251+
# (typically shared classical data or barriers).
252+
if self.skip_routing or len(layout_components) > 1:
261253
return dag
262254
# After this point the pass is no longer an analysis pass and the
263255
# output circuit returned is transformed with the layout applied

test/python/compiler/test_transpiler.py

+56-1
Original file line numberDiff line numberDiff line change
@@ -2274,7 +2274,7 @@ def _visit_block(circuit, qubit_mapping=None):
22742274
)
22752275

22762276
@slow_test
2277-
@data(1, 2, 3)
2277+
@data(2, 3)
22782278
def test_six_component_circuit(self, opt_level):
22792279
"""Test input circuit with more than 1 component per backend component."""
22802280
qc = QuantumCircuit(42)
@@ -2329,6 +2329,61 @@ def test_six_component_circuit(self, opt_level):
23292329
continue
23302330
self.assertIn(qubits, self.backend.target[op_name])
23312331

2332+
def test_six_component_circuit_level_1(self):
2333+
"""Test input circuit with more than 1 component per backend component."""
2334+
opt_level = 1
2335+
qc = QuantumCircuit(42)
2336+
qc.h(0)
2337+
qc.h(10)
2338+
qc.h(20)
2339+
qc.cx(0, 1)
2340+
qc.cx(0, 2)
2341+
qc.cx(0, 3)
2342+
qc.cx(0, 4)
2343+
qc.cx(0, 5)
2344+
qc.cx(0, 6)
2345+
qc.cx(0, 7)
2346+
qc.cx(0, 8)
2347+
qc.cx(0, 9)
2348+
qc.ecr(10, 11)
2349+
qc.ecr(10, 12)
2350+
qc.ecr(10, 13)
2351+
qc.ecr(10, 14)
2352+
qc.ecr(10, 15)
2353+
qc.ecr(10, 16)
2354+
qc.ecr(10, 17)
2355+
qc.ecr(10, 18)
2356+
qc.ecr(10, 19)
2357+
qc.cy(20, 21)
2358+
qc.cy(20, 22)
2359+
qc.cy(20, 23)
2360+
qc.cy(20, 24)
2361+
qc.cy(20, 25)
2362+
qc.cy(20, 26)
2363+
qc.cy(20, 27)
2364+
qc.cy(20, 28)
2365+
qc.cy(20, 29)
2366+
qc.h(30)
2367+
qc.cx(30, 31)
2368+
qc.cx(30, 32)
2369+
qc.cx(30, 33)
2370+
qc.h(34)
2371+
qc.cx(34, 35)
2372+
qc.cx(34, 36)
2373+
qc.cx(34, 37)
2374+
qc.h(38)
2375+
qc.cx(38, 39)
2376+
qc.cx(39, 40)
2377+
qc.cx(39, 41)
2378+
qc.measure_all()
2379+
tqc = transpile(qc, self.backend, optimization_level=opt_level, seed_transpiler=42)
2380+
for inst in tqc.data:
2381+
qubits = tuple(tqc.find_bit(x).index for x in inst.qubits)
2382+
op_name = inst.operation.name
2383+
if op_name == "barrier":
2384+
continue
2385+
self.assertIn(qubits, self.backend.target[op_name])
2386+
23322387
@data(0, 1, 2, 3)
23332388
def test_shared_classical_between_components_condition(self, opt_level):
23342389
"""Test a condition sharing classical bits between components."""

test/python/transpiler/test_sabre_layout.py

+4-40
Original file line numberDiff line numberDiff line change
@@ -241,18 +241,9 @@ def test_dual_ghz(self):
241241
layout_routing_pass = SabreLayout(
242242
self.dual_grid_cmap, seed=123456, swap_trials=1, layout_trials=1
243243
)
244-
out = layout_routing_pass(qc)
244+
layout_routing_pass(qc)
245245
layout = layout_routing_pass.property_set["layout"]
246246
self.assertEqual([layout[q] for q in qc.qubits], [3, 1, 2, 5, 4, 6, 7, 8])
247-
self.assertEqual(1, out.count_ops()["swap"])
248-
edge_set = set(self.dual_grid_cmap.graph.edge_list())
249-
for gate in out.data:
250-
if len(gate.qubits) == 2:
251-
qubits = tuple(out.find_bit(x).index for x in gate.qubits)
252-
# Handle reverse edges which will be fixed by gate direction
253-
# later
254-
if qubits not in edge_set:
255-
self.assertIn((qubits[1], qubits[0]), edge_set)
256247

257248
def test_dual_ghz_with_wide_barrier(self):
258249
"""Test a basic example with 2 circuit components and 2 cmap components."""
@@ -269,18 +260,9 @@ def test_dual_ghz_with_wide_barrier(self):
269260
layout_routing_pass = SabreLayout(
270261
self.dual_grid_cmap, seed=123456, swap_trials=1, layout_trials=1
271262
)
272-
out = layout_routing_pass(qc)
263+
layout_routing_pass(qc)
273264
layout = layout_routing_pass.property_set["layout"]
274265
self.assertEqual([layout[q] for q in qc.qubits], [3, 1, 2, 5, 4, 6, 7, 8])
275-
self.assertEqual(1, out.count_ops()["swap"])
276-
edge_set = set(self.dual_grid_cmap.graph.edge_list())
277-
for gate in out.data:
278-
if len(gate.qubits) == 2:
279-
qubits = tuple(out.find_bit(x).index for x in gate.qubits)
280-
# Handle reverse edges which will be fixed by gate direction
281-
# later
282-
if qubits not in edge_set:
283-
self.assertIn((qubits[1], qubits[0]), edge_set)
284266

285267
def test_dual_ghz_with_intermediate_barriers(self):
286268
"""Test dual ghz circuit with intermediate barriers local to each componennt."""
@@ -299,18 +281,9 @@ def test_dual_ghz_with_intermediate_barriers(self):
299281
layout_routing_pass = SabreLayout(
300282
self.dual_grid_cmap, seed=123456, swap_trials=1, layout_trials=1
301283
)
302-
out = layout_routing_pass(qc)
284+
layout_routing_pass(qc)
303285
layout = layout_routing_pass.property_set["layout"]
304286
self.assertEqual([layout[q] for q in qc.qubits], [3, 1, 2, 5, 4, 6, 7, 8])
305-
self.assertEqual(1, out.count_ops()["swap"])
306-
edge_set = set(self.dual_grid_cmap.graph.edge_list())
307-
for gate in out.data:
308-
if len(gate.qubits) == 2:
309-
qubits = tuple(out.find_bit(x).index for x in gate.qubits)
310-
# Handle reverse edges which will be fixed by gate direction
311-
# later
312-
if qubits not in edge_set:
313-
self.assertIn((qubits[1], qubits[0]), edge_set)
314287

315288
def test_dual_ghz_with_intermediate_spanning_barriers(self):
316289
"""Test dual ghz circuit with barrier in the middle across components."""
@@ -328,18 +301,9 @@ def test_dual_ghz_with_intermediate_spanning_barriers(self):
328301
layout_routing_pass = SabreLayout(
329302
self.dual_grid_cmap, seed=123456, swap_trials=1, layout_trials=1
330303
)
331-
out = layout_routing_pass(qc)
304+
layout_routing_pass(qc)
332305
layout = layout_routing_pass.property_set["layout"]
333306
self.assertEqual([layout[q] for q in qc.qubits], [3, 1, 2, 5, 4, 6, 7, 8])
334-
self.assertEqual(1, out.count_ops()["swap"])
335-
edge_set = set(self.dual_grid_cmap.graph.edge_list())
336-
for gate in out.data:
337-
if len(gate.qubits) == 2:
338-
qubits = tuple(out.find_bit(x).index for x in gate.qubits)
339-
# Handle reverse edges which will be fixed by gate direction
340-
# later
341-
if qubits not in edge_set:
342-
self.assertIn((qubits[1], qubits[0]), edge_set)
343307

344308
def test_too_large_components(self):
345309
"""Assert trying to run a circuit with too large a connected component raises."""

0 commit comments

Comments
 (0)