Improve VF2Layout pass (#7276)

* Improve VF2Layout pass

This commit fixes some issues with the initial implementation of the
VF2Layout pass and improves the pass to make it suitable for use in the
preset pass managers. This includes several changes:

1. The vf2 pass was raising an exception if it was
   run with >2 qubit gates. This caused issues if we run with
   calibrations (or backends that support >2q gates) as vf2 layout is
   used as an opportunistic thing and if there is for example a 5q gate
   being used we shouldn't fail the entire transpile just because vf2
   can't deal with it. It's only an issue if the later passes can't
   either, it just means vf2 won't be able to find a perfect layout.

2. Add multiple trials to the pass and a mapping quality heusristic with
   basic noise awareness to the pass. For performance we should be using
   the vf2++ heursitic as it improves the performance and makes matches
   much easier to find, however this has the tradeoff as the vf2++
   heurstic orders the nodes by degree for the search which means when
   there are multiple mappings found (as in the case of a 1q gate only
   circuit) it would favor the qubits with highest degree which isn't
   desireable as these tend to be the noisest qubits. Now the pass will
   iterate over all the mappings found by retworkx.vf2_mapping() and
   score each of them, picking the best mapping. If a BackendProperties
   object is available it will factor in readout error for each qubit,
   if it's not it scores nodes with lower degree more favorably. A
   future optimization would be to look at 1q and 2q error rates on the
   device and pick based on that, but this gets tricky as to do this
   well we should weight this based on the number of operations on the
   qubits or edges which isn't as simple, so this is left for a future
   optimization

3. Add configurable limits to the pass. While VF2 is normally quite
   quick both to find mappings and to also determine if a mapping isn't
   feasible it isn't always. The edge cases can take quite a long time.
   If we're going to use this pass in the preset pass managers we need
   to be able to set limits on the execution. This commit enables the
   pass to be configured with limits on the number of trials to
   consider, how many internal state visits to use in vf2 before we give
   up (which can be used as a proxy for per trial timeout 1e7 is roughly
   20 seconds), and a total timeout which will be checked after each
   trial to ensure the total time spent in the pass doesn't exceed a
   certain amount of time. This gives us the necessary knobs to ensure
   that the pass doesn't spend too much time trying to find an ideal
   solution.

4. Using an Enum class for the stop conditions. Since there are a finite
   number of stop conditions for the pass, instead of using magic
   strings to determine this using an enum makes it easier for users to
   programmtically interact with the pass and it's return status. The
   value for the enums are the same strings as used before but this
   gives a slightly cleaner interface for interacting with the
   VF2LayoutStopReason value in the property set.

5. Add debug log messages to the pass. After changes 1-4 the internal
   logic and operation of the pass is sufficiently complicated that some
   logging is necessary to figure out how it's behaving. This commit
   adds a bunch of log messages to describe the operation as the pass
   operates all at the debug level because it's all only useful for
   debugging how the pass could have gone wrong and not generally useful
   for information for users.

This was originally split out from #7213 where these changes were
originally made incrementally as we experimented with making this pass
run by default for optimization levels >= 1.

* Set a default number of max trials

As was found by @nonhermitian in testing locally for trivial problems
with a large search space (like idle qubits in the interaction graph) we
were spending most of our time trying to run through the entire search
space to find all mappings and picking the best one. To address this
issue, this commit sets the default value of max_trials based on the
size of the graphs being used to set limits for these simple cases.

* Documentation improvements
This commit is contained in:
Matthew Treinish 2021-11-20 03:51:56 -05:00 committed by GitHub
parent 93f7659327
commit e42e90bbe1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 365 additions and 38 deletions

View File

@ -11,11 +11,27 @@
# that they have been altered from the originals.
"""VF2Layout pass to find a layout using subgraph isomorphism"""
from enum import Enum
import logging
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.transpiler.exceptions import TranspilerError
from qiskit.providers.exceptions import BackendPropertyError
logger = logging.getLogger(__name__)
class VF2LayoutStopReason(Enum):
"""Stop reasons for VF2Layout pass."""
SOLUTION_FOUND = "solution found"
NO_SOLUTION_FOUND = "nonexistent solution"
MORE_THAN_2Q = ">2q gates in basis"
class VF2Layout(AnalysisPass):
@ -27,14 +43,25 @@ class VF2Layout(AnalysisPass):
will be set in the property set as ``property_set['layout']``. However, if no
solution is found, no ``property_set['layout']`` is set. The stopping reason is
set in ``property_set['VF2Layout_stop_reason']`` in all the cases and will be
one of the following values:
one of the values enumerated in ``VF2LayoutStopReason`` which has the
following values:
* ``"solution found"``: If a perfect layout was found.
* ``"nonexistent solution"``: If no perfect layout was found.
* ``">2q gates in basis"``: If VF2Layout can't work with basis
"""
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=None,
):
"""Initialize a ``VF2Layout`` pass instance
Args:
@ -42,11 +69,26 @@ class VF2Layout(AnalysisPass):
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. If
:meth:`~qiskit.providers.models.BackendProperties.readout_error` is available
it is used to score the layout.
max_trials (int): The maximum number of trials to run VF2 to find
a layout. If this is not specified the number of trials will be limited
based on the number of edges in the interaction graph or the coupling graph
(whichever is larger). If set to a value <= 0 no limit on the number of trials
will be set.
"""
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"""
@ -59,10 +101,8 @@ class VF2Layout(AnalysisPass):
if len_args == 2:
interactions.append((qubit_indices[node.qargs[0]], qubit_indices[node.qargs[1]]))
if len_args >= 3:
raise TranspilerError(
"VF2Layout only can handle 2-qubit gates or less. Node "
f"{node.name} ({node}) is {len_args}-qubit"
)
self.property_set["VF2Layout_stop_reason"] = VF2LayoutStopReason.MORE_THAN_2Q
return
if self.strict_direction:
cm_graph = self.coupling_map.graph
@ -80,18 +120,97 @@ class VF2Layout(AnalysisPass):
shuffled_cm_graph.add_edges_from_no_data(new_edges)
cm_nodes = [k for k, v in sorted(enumerate(cm_nodes), key=lambda item: item[1])]
cm_graph = shuffled_cm_graph
im_graph.add_nodes_from(range(len(qubits)))
im_graph.add_edges_from_no_data(interactions)
# To avoid trying to over optimize the result by default limit the number
# of trials based on the size of the graphs. For circuits with simple layouts
# like an all 1q circuit we don't want to sit forever trying every possible
# mapping in the search space
if self.max_trials is None:
im_graph_edge_count = len(im_graph.edge_list())
cm_graph_edge_count = len(cm_graph.edge_list())
self.max_trials = max(im_graph_edge_count, cm_graph_edge_count) + 15
mappings = vf2_mapping(cm_graph, im_graph, subgraph=True, id_order=False, induced=False)
try:
mapping = next(mappings)
stop_reason = "solution found"
logger.debug("Running VF2 to find 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
logger.debug("Running trial: %s", trials)
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
# If the graphs have the same number of nodes we don't need to score or do multiple
# trials as the score heuristic currently doesn't weigh nodes based on gates on a
# qubit so the scores will always all be the same
if len(cm_graph) == len(im_graph):
chosen_layout = layout
break
layout_score = self._score_layout(layout)
logger.debug("Trial %s has score %s", trials, layout_score)
if chosen_layout is None:
chosen_layout = layout
chosen_layout_score = layout_score
elif layout_score < chosen_layout_score:
logger.debug(
"Found layout %s has a lower score (%s) than previous best %s (%s)",
layout,
layout_score,
chosen_layout,
chosen_layout_score,
)
chosen_layout = layout
chosen_layout_score = layout_score
if self.max_trials > 0 and trials >= self.max_trials:
logger.debug("Trial %s is >= configured max trials %s", trials, self.max_trials)
break
elapsed_time = time.time() - start_time
if self.time_limit is not None and elapsed_time >= self.time_limit:
logger.debug(
"VF2Layout has taken %s which exceeds configured max time: %s",
elapsed_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 = "nonexistent solution"
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

View File

@ -17,11 +17,11 @@ import numpy
import retworkx
from qiskit import QuantumRegister, QuantumCircuit
from qiskit.transpiler import CouplingMap, TranspilerError
from qiskit.transpiler.passes import VF2Layout
from qiskit.transpiler import CouplingMap, Layout
from qiskit.transpiler.passes.layout.vf2_layout import VF2Layout, VF2LayoutStopReason
from qiskit.converters import circuit_to_dag
from qiskit.test import QiskitTestCase
from qiskit.test.mock import FakeTenerife, FakeRueschlikon, FakeManhattan
from qiskit.test.mock import FakeTenerife, FakeRueschlikon, FakeManhattan, FakeYorktown
from qiskit.circuit.library import GraphState
@ -33,7 +33,7 @@ class LayoutTestCase(QiskitTestCase):
def assertLayout(self, dag, coupling_map, property_set, strict_direction=False):
"""Checks if the circuit in dag was a perfect layout in property_set for the given
coupling_map"""
self.assertEqual(property_set["VF2Layout_stop_reason"], "solution found")
self.assertEqual(property_set["VF2Layout_stop_reason"], VF2LayoutStopReason.SOLUTION_FOUND)
layout = property_set["layout"]
edges = coupling_map.graph.edge_list()
@ -65,7 +65,7 @@ class TestVF2LayoutSimple(LayoutTestCase):
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)
@ -81,7 +81,7 @@ class TestVF2LayoutSimple(LayoutTestCase):
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)
@ -99,10 +99,26 @@ class TestVF2LayoutSimple(LayoutTestCase):
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)
def test_call_limit(self):
"""Test that call limit is enforce."""
cmap = CouplingMap([[0, 1], [1, 2], [2, 0]])
qr = QuantumRegister(3, "qr")
circuit = QuantumCircuit(qr)
circuit.cx(qr[0], qr[1]) # qr0-> qr1
circuit.cx(qr[1], qr[2]) # qr1-> qr2
dag = circuit_to_dag(circuit)
pass_ = VF2Layout(cmap, seed=-1, call_limit=1)
pass_.run(dag)
self.assertEqual(
pass_.property_set["VF2Layout_stop_reason"], VF2LayoutStopReason.NO_SOLUTION_FOUND
)
class TestVF2LayoutLattice(LayoutTestCase):
"""Fit in 25x25 hexagonal lattice coupling map"""
@ -120,7 +136,7 @@ class TestVF2LayoutLattice(LayoutTestCase):
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)
@ -130,7 +146,7 @@ class TestVF2LayoutLattice(LayoutTestCase):
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)
@ -154,11 +170,13 @@ class TestVF2LayoutBackend(LayoutTestCase):
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)
self.assertEqual(pass_.property_set["VF2Layout_stop_reason"], "nonexistent solution")
self.assertEqual(
pass_.property_set["VF2Layout_stop_reason"], VF2LayoutStopReason.NO_SOLUTION_FOUND
)
def test_9q_circuit_Rueschlikon_sd(self):
"""9 qubits in Rueschlikon, considering the direction
@ -177,7 +195,7 @@ class TestVF2LayoutBackend(LayoutTestCase):
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)
@ -198,7 +216,7 @@ class TestVF2LayoutBackend(LayoutTestCase):
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)
@ -219,7 +237,7 @@ class TestVF2LayoutBackend(LayoutTestCase):
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)
@ -244,7 +262,7 @@ class TestVF2LayoutBackend(LayoutTestCase):
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)
@ -266,7 +284,7 @@ class TestVF2LayoutBackend(LayoutTestCase):
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)
@ -286,7 +304,7 @@ class TestVF2LayoutBackend(LayoutTestCase):
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)
@ -308,15 +326,19 @@ class TestVF2LayoutOther(LayoutTestCase):
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"], "solution found")
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(pass_2.property_set["VF2Layout_stop_reason"], "solution found")
self.assertEqual(
pass_2.property_set["VF2Layout_stop_reason"], VF2LayoutStopReason.SOLUTION_FOUND
)
self.assertNotEqual(layout_1, layout_2)
@ -331,9 +353,195 @@ class TestVF2LayoutOther(LayoutTestCase):
circuit.ccx(qr[1], qr[0], qr[2])
dag = circuit_to_dag(circuit)
pass_1 = VF2Layout(CouplingMap(cmap5), seed=seed_1)
with self.assertRaises(TranspilerError):
pass_1.run(dag)
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
)
class TestScoreHeuristic(QiskitTestCase):
"""Test the internal score heuristic of the pass."""
def test_no_properties(self):
"""Test scores with no properties."""
vf2_pass = VF2Layout(
CouplingMap(
[
(0, 1),
(0, 2),
(0, 3),
(1, 0),
(1, 2),
(1, 3),
(2, 0),
(2, 1),
(2, 2),
(2, 3),
(3, 0),
(3, 1),
(3, 2),
(4, 0),
(0, 4),
(5, 1),
(1, 5),
]
)
)
qr = QuantumRegister(2)
layout = Layout({qr[0]: 0, qr[1]: 1})
score = vf2_pass._score_layout(layout)
self.assertEqual(score, 16)
better_layout = Layout({qr[0]: 4, qr[1]: 5})
better_score = vf2_pass._score_layout(better_layout)
self.assertEqual(4, better_score)
def test_with_properties(self):
"""Test scores with properties."""
backend = FakeYorktown()
cmap = CouplingMap(backend.configuration().coupling_map)
properties = backend.properties()
vf2_pass = VF2Layout(cmap, properties=properties)
qr = QuantumRegister(2)
layout = Layout({qr[0]: 4, qr[1]: 2})
bad_score = vf2_pass._score_layout(layout)
self.assertAlmostEqual(0.4075, bad_score)
better_layout = Layout({qr[0]: 1, qr[1]: 3})
better_score = vf2_pass._score_layout(better_layout)
self.assertAlmostEqual(0.0588, better_score)
class TestMultipleTrials(QiskitTestCase):
"""Test the passes behavior with >1 trial."""
def test_no_properties(self):
"""Test it finds the lowest degree perfect layout with no properties."""
vf2_pass = VF2Layout(
CouplingMap(
[
(0, 1),
(0, 2),
(0, 3),
(1, 0),
(1, 2),
(1, 3),
(2, 0),
(2, 1),
(2, 2),
(2, 3),
(3, 0),
(3, 1),
(3, 2),
(4, 0),
(0, 4),
(5, 1),
(1, 5),
]
)
)
qr = QuantumRegister(2)
qc = QuantumCircuit(qr)
qc.x(qr)
qc.measure_all()
property_set = {}
vf2_pass(qc, property_set)
self.assertEqual(set(property_set["layout"].get_physical_bits()), {5, 4})
def test_with_properties(self):
"""Test it finds the least noise perfect layout with no properties."""
backend = FakeYorktown()
qr = QuantumRegister(2)
qc = QuantumCircuit(qr)
qc.x(qr)
qc.measure_all()
cmap = CouplingMap(backend.configuration().coupling_map)
properties = backend.properties()
vf2_pass = VF2Layout(cmap, properties=properties)
property_set = {}
vf2_pass(qc, property_set)
self.assertEqual(set(property_set["layout"].get_physical_bits()), {1, 3})
def test_max_trials_exceeded(self):
"""Test it exits when max_trials is reached."""
backend = FakeYorktown()
qr = QuantumRegister(2)
qc = QuantumCircuit(qr)
qc.x(qr)
qc.measure_all()
cmap = CouplingMap(backend.configuration().coupling_map)
properties = backend.properties()
vf2_pass = VF2Layout(cmap, properties=properties, seed=-1, max_trials=1)
property_set = {}
with self.assertLogs("qiskit.transpiler.passes.layout.vf2_layout", level="DEBUG") as cm:
vf2_pass(qc, property_set)
self.assertIn(
"DEBUG:qiskit.transpiler.passes.layout.vf2_layout:Trial 1 is >= configured max trials 1",
cm.output,
)
self.assertEqual(set(property_set["layout"].get_physical_bits()), {2, 0})
def test_time_limit_exceeded(self):
"""Test the pass stops after time_limit is reached."""
backend = FakeYorktown()
qr = QuantumRegister(2)
qc = QuantumCircuit(qr)
qc.x(qr)
qc.measure_all()
cmap = CouplingMap(backend.configuration().coupling_map)
properties = backend.properties()
vf2_pass = VF2Layout(cmap, properties=properties, seed=-1, time_limit=0.0)
property_set = {}
with self.assertLogs("qiskit.transpiler.passes.layout.vf2_layout", level="DEBUG") as cm:
vf2_pass(qc, property_set)
for output in cm.output:
if output.startswith(
"DEBUG:qiskit.transpiler.passes.layout.vf2_layout:VF2Layout has taken"
) and output.endswith("which exceeds configured max time: 0.0"):
break
else:
self.fail("No failure debug log message found")
self.assertEqual(set(property_set["layout"].get_physical_bits()), {2, 0})
def test_reasonable_limits_for_simple_layouts(self):
"""Test that the default trials is set to a reasonable number."""
backend = FakeManhattan()
qc = QuantumCircuit(5)
qc.cx(0, 1)
cmap = CouplingMap(backend.configuration().coupling_map)
properties = backend.properties()
# Run without any limits set
vf2_pass = VF2Layout(cmap, properties=properties, seed=42)
property_set = {}
with self.assertLogs("qiskit.transpiler.passes.layout.vf2_layout", level="DEBUG") as cm:
vf2_pass(qc, property_set)
self.assertIn(
"DEBUG:qiskit.transpiler.passes.layout.vf2_layout:Trial 159 is >= configured max trials 159",
cm.output,
)
self.assertEqual(set(property_set["layout"].get_physical_bits()), {48, 49, 40, 47, 58})
def test_no_limits_with_negative(self):
"""Test that we're not enforcing a trial limit if set to negative."""
backend = FakeYorktown()
qc = QuantumCircuit(3)
qc.h(0)
cmap = CouplingMap(backend.configuration().coupling_map)
implicit_max = len(cmap.graph.edge_list()) + 15
properties = backend.properties()
# Run without any limits set
vf2_pass = VF2Layout(cmap, properties=properties, seed=42, max_trials=0)
property_set = {}
with self.assertLogs("qiskit.transpiler.passes.layout.vf2_layout", level="DEBUG") as cm:
vf2_pass(qc, property_set)
for output in cm.output:
self.assertNotIn("is >= configured max trials", output)
last_line = cm.output[-1]
# The last line should be
# DEBUG:qiskit.transpiler.passes.layout.vf2_layout: Trial n has score 0.122
trials = int(last_line.split(" ")[1])
self.assertGreater(trials, implicit_max)
self.assertEqual(set(property_set["layout"].get_physical_bits()), {3, 1, 0})
if __name__ == "__main__":