mirror of https://github.com/Qiskit/qiskit.git
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:
parent
93f7659327
commit
e42e90bbe1
|
@ -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
|
||||
|
|
|
@ -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__":
|
||||
|
|
Loading…
Reference in New Issue