Skip to content

Commit

Permalink
Make VF2Layout pass Target aware (#7735)
Browse files Browse the repository at this point in the history
* Make VF2Layout pass Target aware

This commit updates the VF2Layout pass to be target aware. It adds a new
kwarg for specifying a target and if it's specified that target object
is used to define the parameters for finding the isomorphic mapping used
for the layout. However, with this commit the extra information contained in
the target isn't really leveraged yet and just the global coupling graph
and measurement error rates are pulled from the target just as in the
BackendV1 case. This commit is mostly to facilitate future expansion where
we will improve the layout scoring heuristic used and having the full set
of data available in the target will be useful.

Part of #7113

* Don't raise in __init__ with no coupling map

* Add release note

* Apply suggestions from code review

Co-authored-by: Jake Lishman <jake@binhbar.com>

* Fix test to make it clear it's using target graph and not the coupling map

* Handle edge case where target doesn't have measurement defined

This commit fixes an edge case in the heurstic scoring where if a target
is present but doesn't have measurement defined we always return a score
of 0. In the case measure ment doesn't have measurement defined this
will fall back to looking at the degree of the qubit instead of trying
to use the readout error rate.

* Update qiskit/transpiler/passes/layout/vf2_layout.py

Co-authored-by: Luciano Bello <bel@zurich.ibm.com>

* Simplify heuristic scoring logic

Co-authored-by: Luciano Bello <bel@zurich.ibm.com>

* Fix lint

Co-authored-by: Jake Lishman <jake@binhbar.com>
Co-authored-by: Luciano Bello <bel@zurich.ibm.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
4 people authored Mar 31, 2022
1 parent 5ed5db9 commit eb333de
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 20 deletions.
63 changes: 45 additions & 18 deletions qiskit/transpiler/passes/layout/vf2_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

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


Expand Down Expand Up @@ -54,13 +55,14 @@ class VF2Layout(AnalysisPass):

def __init__(
self,
coupling_map,
coupling_map=None,
strict_direction=False,
seed=None,
call_limit=None,
time_limit=None,
properties=None,
max_trials=None,
target=None,
):
"""Initialize a ``VF2Layout`` pass instance
Expand All @@ -80,18 +82,31 @@ def __init__(
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.
target (Target): A target representing the backend device to run ``VF2Layout`` on.
If specified it will supersede a set value for ``properties`` and
``coupling_map``.
Raises:
TypeError: At runtime, if neither ``coupling_map`` or ``target`` are provided.
"""
super().__init__()
self.coupling_map = coupling_map
self.target = target
if target is not None:
self.coupling_map = self.target.build_coupling_map()
else:
self.coupling_map = coupling_map
self.properties = properties
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"""
if self.coupling_map is None:
raise TranspilerError("coupling_map or target must be specified.")

qubits = dag.qubits
qubit_indices = {qubit: index for index, qubit in enumerate(qubits)}

Expand Down Expand Up @@ -197,20 +212,32 @@ def _score_layout(self, layout):
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
if self.target is not None and "measure" in self.target:
for bit in bits:
props = self.target["measure"].get((bit,))
if props is None or props.error is None:
score += (
self.coupling_map.graph.out_degree(bit)
+ self.coupling_map.graph.in_degree(bit)
) / len(self.coupling_map.graph)
else:
score += props.error
else:
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:
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)
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
1 change: 1 addition & 0 deletions qiskit/transpiler/preset_passmanagers/level1.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ def _vf2_match_not_found(property_set):
call_limit=int(5e4), # Set call limit to ~100ms with retworkx 0.10.2
time_limit=0.1,
properties=backend_properties,
target=target,
)
)

Expand Down
1 change: 1 addition & 0 deletions qiskit/transpiler/preset_passmanagers/level2.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ def _vf2_match_not_found(property_set):
call_limit=int(5e6), # Set call limit to ~10 sec with retworkx 0.10.2
time_limit=10.0,
properties=backend_properties,
target=target,
)
)

Expand Down
1 change: 1 addition & 0 deletions qiskit/transpiler/preset_passmanagers/level3.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ def _vf2_match_not_found(property_set):
call_limit=int(3e7), # Set call limit to ~60 sec with retworkx 0.10.2
time_limit=60,
properties=backend_properties,
target=target,
)
)
# 2b. if VF2 didn't converge on a solution use layout_method (dense).
Expand Down
9 changes: 9 additions & 0 deletions releasenotes/notes/vf2layout-target-51cc8f77fdfcde67.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
features:
- |
The :class:`~.VF2Layout` transpiler pass has a new keyword argument,
``target`` which is used to provide a :class:`~.Target` object for
the pass. When specified, the :class:`~.Target` will be used by the
pass for all information about the target device. If it is specified,
the ``target`` option will take priority over the ``coupling_map`` and
``properties`` arguments.
27 changes: 25 additions & 2 deletions test/python/transpiler/test_vf2_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
import retworkx

from qiskit import QuantumRegister, QuantumCircuit
from qiskit.transpiler import CouplingMap, Layout
from qiskit.transpiler import CouplingMap, Layout, Target, TranspilerError
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, FakeYorktown
from qiskit.circuit.library import GraphState
from qiskit.circuit.library import GraphState, CXGate


class LayoutTestCase(QiskitTestCase):
Expand Down Expand Up @@ -119,6 +119,29 @@ def test_call_limit(self):
pass_.property_set["VF2Layout_stop_reason"], VF2LayoutStopReason.NO_SOLUTION_FOUND
)

def test_coupling_map_and_target(self):
"""Test that a Target is used instead of a CouplingMap if both are specified."""
cmap = CouplingMap([[0, 1], [1, 2]])
target = Target()
target.add_instruction(CXGate(), {(0, 1): None, (1, 2): None, (1, 0): None})
qr = QuantumRegister(3, "qr")
circuit = QuantumCircuit(qr)
circuit.cx(qr[0], qr[1]) # qr0-> qr1
circuit.cx(qr[1], qr[2]) # qr1-> qr2
circuit.cx(qr[1], qr[0]) # qr1-> qr0
dag = circuit_to_dag(circuit)
pass_ = VF2Layout(cmap, seed=-1, max_trials=1, target=target)
pass_.run(dag)
self.assertLayout(dag, target.build_coupling_map(), pass_.property_set)

def test_neither_coupling_map_or_target(self):
"""Test that we raise if neither a target or coupling map is specified."""
vf2_pass = VF2Layout(seed=123, call_limit=1000, time_limit=20, max_trials=7)
circuit = QuantumCircuit(2)
dag = circuit_to_dag(circuit)
with self.assertRaises(TranspilerError):
vf2_pass.run(dag)


class TestVF2LayoutLattice(LayoutTestCase):
"""Fit in 25x25 hexagonal lattice coupling map"""
Expand Down

0 comments on commit eb333de

Please sign in to comment.