Skip to content

Commit

Permalink
Set real limits on calling vf2 and add quality heuristic
Browse files Browse the repository at this point in the history
This commit adds options to set limits on the vf2 pass, both the
internal call limit for the vf2 execution in retworkx, a total time
spent in the pass trying multiple layouts, and the number of trials to
attempt. These are then set in the preset pass manager to ensure we
don't sit spinning on vf2 forever in the real world. While the pass is
generally fast there are edge cases where it can get stuck. At the same
time this adds a rough quality heuristic (based on readout error falling
back to connectivity) to select between multiple mappings found by
retworkx. This addresses the poor quality results we were getting with
vf2++ in earlier revisions as we can find the best from multiple
mappings.
  • Loading branch information
mtreinish committed Nov 11, 2021
1 parent e95b8e2 commit f7b3902
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 25 deletions.
82 changes: 75 additions & 7 deletions qiskit/transpiler/passes/layout/vf2_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@
"""VF2Layout pass to find a layout using subgraph isomorphism"""
from enum import Enum
import random
import time

from retworkx import PyGraph, PyDiGraph, vf2_mapping

from qiskit.transpiler.layout import Layout
from qiskit.transpiler.basepasses import AnalysisPass
from qiskit.providers.exceptions import BackendPropertyError


class VF2LayoutStopReason(Enum):
Expand Down Expand Up @@ -46,19 +48,38 @@ class VF2Layout(AnalysisPass):
"""

def __init__(self, coupling_map, strict_direction=False, seed=None):
def __init__(
self,
coupling_map,
strict_direction=False,
seed=None,
call_limit=None,
time_limit=None,
properties=None,
max_trials=-1,
):
"""Initialize a ``VF2Layout`` pass instance
Args:
coupling_map (CouplingMap): Directed graph representing a coupling map.
strict_direction (bool): If True, considers the direction of the coupling map.
Default is False.
seed (int): Sets the seed of the PRNG. -1 Means no node shuffling.
call_limit (int): The number of state visits to attempt in each execution of
vf2.
time_limit (float): The total time limit in seconds to run vf2layout
properties (BackendProperties): The backend properties for the backend
max_trials (int): The maximum number of trials to run vf2 to find
a layout.
"""
super().__init__()
self.coupling_map = coupling_map
self.strict_direction = strict_direction
self.seed = seed
self.call_limit = call_limit
self.time_limit = time_limit
self.properties = properties
self.max_trials = max_trials

def run(self, dag):
"""run the layout method"""
Expand Down Expand Up @@ -93,15 +114,62 @@ def run(self, dag):
im_graph.add_nodes_from(range(len(qubits)))
im_graph.add_edges_from_no_data(interactions)

mappings = vf2_mapping(cm_graph, im_graph, subgraph=True, id_order=False, induced=False)
try:
mapping = next(mappings)
mappings = vf2_mapping(
cm_graph,
im_graph,
subgraph=True,
id_order=False,
induced=False,
call_limit=self.call_limit,
)
chosen_layout = None
chosen_layout_score = None
start_time = time.time()
trials = 0
for mapping in mappings:
trials += 1
stop_reason = VF2LayoutStopReason.SOLUTION_FOUND
layout = Layout({qubits[im_i]: cm_nodes[cm_i] for cm_i, im_i in mapping.items()})
self.property_set["layout"] = layout
layout_score = self._score_layout(layout)
if chosen_layout is None:
chosen_layout = layout
chosen_layout_score = layout_score
elif layout_score < chosen_layout_score:
chosen_layout = layout
chosen_layout_score = layout_score
if trials >= self.max_trials or (
self.time_limit and time.time() - start_time >= self.time_limit
):
break
if chosen_layout is None:
stop_reason = VF2LayoutStopReason.NO_SOLUTION_FOUND
else:
self.property_set["layout"] = chosen_layout
for reg in dag.qregs.values():
self.property_set["layout"].add_register(reg)
except StopIteration:
stop_reason = VF2LayoutStopReason.NO_SOLUTION_FOUND

self.property_set["VF2Layout_stop_reason"] = stop_reason

def _score_layout(self, layout):
"""Score heurstic to determine the quality of the layout by looking at the readout fidelity
on the chosen qubits. If BackendProperties are not available it uses the coupling map degree
to weight against higher connectivity qubits."""
bits = layout.get_physical_bits()
score = 0
if self.properties is None:
# Sum qubit degree for each qubit in chosen layout as really rough estimate of error
for bit in bits:
score += self.coupling_map.graph.out_degree(
bit
) + self.coupling_map.graph.in_degree(bit)
return score
for bit in bits:
try:
score += self.properties.readout_error(bit)
# If readout error can't be found in properties fallback to degree
# divided by number of qubits as a terrible approximation
except BackendPropertyError:
score += (
self.coupling_map.graph.out_degree(bit) + self.coupling_map.graph.in_degree(bit)
) / len(self.coupling_map.graph)
return score
10 changes: 9 additions & 1 deletion qiskit/transpiler/preset_passmanagers/level1.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,15 @@ def _vf2_match_not_found(property_set):
)

_choose_layout_1 = (
[] if pass_manager_config.layout_method else VF2Layout(coupling_map, seed=vf2_seed)
[]
if pass_manager_config.layout_method
else VF2Layout(
coupling_map,
seed=vf2_seed,
call_limit=int(5e5),
time_limit=1e-2,
properties=backend_properties,
)
)

# 2. Use a better layout on densely connected qubits, if circuit needs swaps
Expand Down
10 changes: 9 additions & 1 deletion qiskit/transpiler/preset_passmanagers/level2.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,15 @@ def _vf2_match_not_found(property_set):
if vf2_seed is None:
vf2_seed = -1
_choose_layout_0 = (
[] if pass_manager_config.layout_method else VF2Layout(coupling_map, seed=vf2_seed)
[]
if pass_manager_config.layout_method
else VF2Layout(
coupling_map,
seed=vf2_seed,
call_limit=int(5e6),
time_limit=10.0,
properties=backend_properties,
)
)

# 1b. if VF2 layout doesn't converge on a solution use layout_method (dense) to get a layout
Expand Down
10 changes: 9 additions & 1 deletion qiskit/transpiler/preset_passmanagers/level3.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,15 @@ def _vf2_match_not_found(property_set):
vf2_seed = -1

_choose_layout_0 = (
[] if pass_manager_config.layout_method else VF2Layout(coupling_map, seed=vf2_seed)
[]
if pass_manager_config.layout_method
else VF2Layout(
coupling_map,
seed=vf2_seed,
call_limit=int(3e7),
time_limit=60,
properties=backend_properties,
)
)
# 2b. if VF2 didn't converge on a solution use layout_method (dense).
if layout_method == "trivial":
Expand Down
30 changes: 15 additions & 15 deletions test/python/transpiler/test_vf2_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def test_2q_circuit_2q_coupling(self):
circuit.cx(qr[1], qr[0]) # qr1 -> qr0

dag = circuit_to_dag(circuit)
pass_ = VF2Layout(cmap, strict_direction=False, seed=self.seed)
pass_ = VF2Layout(cmap, strict_direction=False, seed=self.seed, max_trials=1)
pass_.run(dag)
self.assertLayout(dag, cmap, pass_.property_set)

Expand All @@ -81,7 +81,7 @@ def test_2q_circuit_2q_coupling_sd(self):
circuit.cx(qr[1], qr[0]) # qr1 -> qr0

dag = circuit_to_dag(circuit)
pass_ = VF2Layout(cmap, strict_direction=True, seed=self.seed)
pass_ = VF2Layout(cmap, strict_direction=True, seed=self.seed, max_trials=1)
pass_.run(dag)
self.assertLayout(dag, cmap, pass_.property_set, strict_direction=True)

Expand All @@ -99,7 +99,7 @@ def test_3q_circuit_3q_coupling_non_induced(self):
circuit.cx(qr[1], qr[2]) # qr1-> qr2

dag = circuit_to_dag(circuit)
pass_ = VF2Layout(cmap, seed=-1)
pass_ = VF2Layout(cmap, seed=-1, max_trials=1)
pass_.run(dag)
self.assertLayout(dag, cmap, pass_.property_set)

Expand All @@ -120,7 +120,7 @@ def test_hexagonal_lattice_graph_20_in_25(self):
circuit = self.graph_state_from_pygraph(graph_20_20)

dag = circuit_to_dag(circuit)
pass_ = VF2Layout(self.cmap25, seed=self.seed)
pass_ = VF2Layout(self.cmap25, seed=self.seed, max_trials=1)
pass_.run(dag)
self.assertLayout(dag, self.cmap25, pass_.property_set)

Expand All @@ -130,7 +130,7 @@ def test_hexagonal_lattice_graph_9_in_25(self):
circuit = self.graph_state_from_pygraph(graph_9_9)

dag = circuit_to_dag(circuit)
pass_ = VF2Layout(self.cmap25, seed=self.seed)
pass_ = VF2Layout(self.cmap25, seed=self.seed, max_trials=1)
pass_.run(dag)
self.assertLayout(dag, self.cmap25, pass_.property_set)

Expand All @@ -154,7 +154,7 @@ def test_5q_circuit_Rueschlikon_no_solution(self):
circuit.cx(qr[0], qr[3])
circuit.cx(qr[0], qr[4])
dag = circuit_to_dag(circuit)
pass_ = VF2Layout(CouplingMap(cmap16), seed=self.seed)
pass_ = VF2Layout(CouplingMap(cmap16), seed=self.seed, max_trials=1)
pass_.run(dag)
layout = pass_.property_set["layout"]
self.assertIsNone(layout)
Expand All @@ -179,7 +179,7 @@ def test_9q_circuit_Rueschlikon_sd(self):
circuit.cx(qr1[4], qr0[2]) # q1[4] -> q0[2]

dag = circuit_to_dag(circuit)
pass_ = VF2Layout(cmap16, strict_direction=True, seed=self.seed)
pass_ = VF2Layout(cmap16, strict_direction=True, seed=self.seed, max_trials=1)
pass_.run(dag)
self.assertLayout(dag, cmap16, pass_.property_set)

Expand All @@ -200,7 +200,7 @@ def test_4q_circuit_Tenerife_loose_nodes(self):
circuit.cx(qr[0], qr[2]) # qr0 -> qr2

dag = circuit_to_dag(circuit)
pass_ = VF2Layout(cmap5, seed=self.seed)
pass_ = VF2Layout(cmap5, seed=self.seed, max_trials=1)
pass_.run(dag)
self.assertLayout(dag, cmap5, pass_.property_set)

Expand All @@ -221,7 +221,7 @@ def test_3q_circuit_Tenerife_sd(self):
circuit.cx(qr[1], qr[2]) # qr1 -> qr2

dag = circuit_to_dag(circuit)
pass_ = VF2Layout(cmap5, strict_direction=True, seed=self.seed)
pass_ = VF2Layout(cmap5, strict_direction=True, seed=self.seed, max_trials=1)
pass_.run(dag)
self.assertLayout(dag, cmap5, pass_.property_set, strict_direction=True)

Expand All @@ -246,7 +246,7 @@ def test_9q_circuit_Rueschlikon(self):
circuit.cx(qr1[4], qr0[2]) # q1[4] -> q0[2]

dag = circuit_to_dag(circuit)
pass_ = VF2Layout(cmap16, strict_direction=False, seed=self.seed)
pass_ = VF2Layout(cmap16, strict_direction=False, seed=self.seed, max_trials=1)
pass_.run(dag)
self.assertLayout(dag, cmap16, pass_.property_set)

Expand All @@ -268,7 +268,7 @@ def test_3q_circuit_Tenerife(self):
circuit.cx(qr[1], qr[2]) # qr1 -> qr2

dag = circuit_to_dag(circuit)
pass_ = VF2Layout(cmap5, strict_direction=False, seed=self.seed)
pass_ = VF2Layout(cmap5, strict_direction=False, seed=self.seed, max_trials=1)
pass_.run(dag)
self.assertLayout(dag, cmap5, pass_.property_set)

Expand All @@ -288,7 +288,7 @@ def test_perfect_fit_Manhattan(self):
circuit.measure_all()

dag = circuit_to_dag(circuit)
pass_ = VF2Layout(cmap65, seed=self.seed)
pass_ = VF2Layout(cmap65, seed=self.seed, max_trials=1)
pass_.run(dag)
self.assertLayout(dag, cmap65, pass_.property_set)

Expand All @@ -310,14 +310,14 @@ def test_seed(self):
circuit.cx(qr[1], qr[2]) # qr1 -> qr2
dag = circuit_to_dag(circuit)

pass_1 = VF2Layout(CouplingMap(cmap5), seed=seed_1)
pass_1 = VF2Layout(CouplingMap(cmap5), seed=seed_1, max_trials=1)
pass_1.run(dag)
layout_1 = pass_1.property_set["layout"]
self.assertEqual(
pass_1.property_set["VF2Layout_stop_reason"], VF2LayoutStopReason.SOLUTION_FOUND
)

pass_2 = VF2Layout(CouplingMap(cmap5), seed=seed_2)
pass_2 = VF2Layout(CouplingMap(cmap5), seed=seed_2, max_trials=1)
pass_2.run(dag)
layout_2 = pass_2.property_set["layout"]
self.assertEqual(
Expand All @@ -337,7 +337,7 @@ def test_3_q_gate(self):
circuit.ccx(qr[1], qr[0], qr[2])
dag = circuit_to_dag(circuit)

pass_1 = VF2Layout(CouplingMap(cmap5), seed=seed_1)
pass_1 = VF2Layout(CouplingMap(cmap5), seed=seed_1, max_trials=1)
pass_1.run(dag)
self.assertEqual(
pass_1.property_set["VF2Layout_stop_reason"], VF2LayoutStopReason.MORE_THAN_2Q
Expand Down

0 comments on commit f7b3902

Please sign in to comment.