Fix VF2Layout and VF2PostLayout handling of instructions without error (#8978)

* Fix VF2Layout and VF2PostLayout handling of instructions without error

This commit fixes an issue in the VF2Layout and VF2PostLayout pass where
the passes would potentially error in cases when a target was specified
and there were instructions present without any error rates defined. In
such cases the instructions should be treated as ideal (having no error)
and the passes shouldn't fail. In cases where there are no error rates
in the target for VF2Layout the first perfect match should be used, and
for VF2PostLayout it should effectively be a no-op and not select a new
layout.

Fixes #8970

* Add missing comma to comment

* Exit early if score is 0

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
Matthew Treinish 2022-10-25 17:51:24 -04:00 committed by GitHub
parent de8e4dd836
commit 6580d96879
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 132 additions and 9 deletions

View File

@ -154,6 +154,11 @@ class VF2Layout(AnalysisPass):
if len(cm_graph) == len(im_graph):
chosen_layout = layout
break
# If there is no error map avilable we can just skip the scoring stage as there
# is nothing to score with, so any match is the best we can find.
if not self.avg_error_map:
chosen_layout = layout
break
layout_score = vf2_utils.score_layout(
self.avg_error_map,
layout,
@ -162,6 +167,12 @@ class VF2Layout(AnalysisPass):
im_graph,
self.strict_direction,
)
# If the layout score is 0 we can't do any better and we'll just
# waste time finding additional mappings that will at best match
# the performance, so exit early in this case
if layout_score == 0.0:
chosen_layout = layout
break
logger.debug("Trial %s has score %s", trials, layout_score)
if chosen_layout is None:
chosen_layout = layout

View File

@ -13,6 +13,7 @@
"""VF2PostLayout pass to find a layout after transpile using subgraph isomorphism"""
from enum import Enum
import logging
import inspect
import time
from retworkx import PyDiGraph, vf2_mapping, PyGraph
@ -131,6 +132,7 @@ class VF2PostLayout(AnalysisPass):
self.avg_error_map = vf2_utils.build_average_error_map(
self.target, self.properties, self.coupling_map
)
result = vf2_utils.build_interaction_graph(dag, self.strict_direction)
if result is None:
self.property_set["VF2PostLayout_stop_reason"] = VF2PostLayoutStopReason.MORE_THAN_2Q
@ -138,21 +140,53 @@ class VF2PostLayout(AnalysisPass):
im_graph, im_graph_node_map, reverse_im_graph_node_map = result
if self.target is not None:
# If qargs is None then target is global and ideal so no
# scoring is needed
if self.target.qargs is None:
return
if self.strict_direction:
cm_graph = PyDiGraph(multigraph=False)
else:
cm_graph = PyGraph(multigraph=False)
cm_graph.add_nodes_from(
[self.target.operation_names_for_qargs((i,)) for i in range(self.target.num_qubits)]
)
# If None is present in qargs there are globally defined ideal operations
# we should add these to all entries based on the number of qubits so we
# treat that as a valid operation even if there is no scoring for the
# strict direction case
global_ops = None
if None in self.target.qargs:
global_ops = {1: [], 2: []}
for op in self.target.operation_names_for_qargs(None):
operation = self.target.operation_for_name(op)
# If operation is a class this is a variable width ideal instruction
# so we treat it as available on both 1 and 2 qubits
if inspect.isclass(operation):
global_ops[1].append(op)
global_ops[2].append(op)
else:
num_qubits = operation.num_qubits
if num_qubits in global_ops:
global_ops[num_qubits].append(op)
op_names = []
for i in range(self.target.num_qubits):
entry = set()
try:
entry = set(self.target.operation_names_for_qargs((i,)))
except KeyError:
pass
if global_ops is not None:
entry.update(global_ops[1])
op_names.append(entry)
cm_graph.add_nodes_from(op_names)
for qargs in self.target.qargs:
len_args = len(qargs)
# If qargs == 1 we already populated it and if qargs > 2 there are no instructions
# using those in the circuit because we'd have already returned by this point
if len_args == 2:
cm_graph.add_edge(
qargs[0], qargs[1], self.target.operation_names_for_qargs(qargs)
)
ops = set(self.target.operation_names_for_qargs(qargs))
if global_ops is not None:
ops.update(global_ops[2])
cm_graph.add_edge(qargs[0], qargs[1], ops)
cm_nodes = list(cm_graph.node_indexes())
else:
cm_graph, cm_nodes = vf2_utils.shuffle_coupling_graph(

View File

@ -84,13 +84,16 @@ def score_layout(avg_error_map, layout, bit_map, reverse_bit_map, im_graph, stri
fidelity = 1
for bit, node_index in bit_map.items():
gate_count = sum(im_graph[node_index].values())
fidelity *= (1 - avg_error_map[(bits[bit],)]) ** gate_count
error_rate = avg_error_map.get((bits[bit],))
if error_rate is not None:
fidelity *= (1 - avg_error_map[(bits[bit],)]) ** gate_count
for edge in im_graph.edge_index_map().values():
gate_count = sum(edge[2].values())
qargs = (bits[reverse_bit_map[edge[0]]], bits[reverse_bit_map[edge[1]]])
if not strict_direction and qargs not in avg_error_map:
qargs = (qargs[1], qargs[0])
fidelity *= (1 - avg_error_map[qargs]) ** gate_count
if qargs in avg_error_map:
fidelity *= (1 - avg_error_map[qargs]) ** gate_count
return 1 - fidelity

View File

@ -0,0 +1,15 @@
---
fixes:
- |
Fixed an issue with the :class:`~.VF2Layout` pass where it would error
when running with a :class:`~.Target` that had instructions that were
missing error rates. This has been corrected so in such cases the
lack of an error rate will be treated as an ideal implementation and
if no error rates are present it will just select the first matching
layout.
Fixed `#8970 <https://github.com/Qiskit/qiskit-terra/issues/8970>`__
- |
Fixed an issue with the :class:`~.VF2PostLayout` pass where it would
error when running with a :class:~.Target` that had instructions that
were missing. In such cases the lack of an error rate will be treated as
an ideal implementation of the operation.

View File

@ -32,8 +32,9 @@ from qiskit.providers.fake_provider import (
FakeYorktown,
FakeGuadalupeV2,
)
from qiskit.circuit.library import GraphState, CXGate
from qiskit.circuit.library import GraphState, CXGate, XGate
from qiskit.transpiler import PassManager
from qiskit.transpiler.target import InstructionProperties
from qiskit.transpiler.preset_passmanagers.common import generate_embed_passmanager
@ -217,6 +218,34 @@ class TestVF2LayoutSimple(LayoutTestCase):
with self.assertRaises(TranspilerError):
vf2_pass.run(dag)
def test_target_no_error(self):
"""Test that running vf2layout on a pass against a target with no error rates works."""
n_qubits = 15
target = Target()
target.add_instruction(CXGate(), {(i, i + 1): None for i in range(n_qubits - 1)})
vf2_pass = VF2Layout(target=target)
circuit = QuantumCircuit(2)
circuit.cx(0, 1)
dag = circuit_to_dag(circuit)
vf2_pass.run(dag)
self.assertLayout(dag, target.build_coupling_map(), vf2_pass.property_set)
def test_target_some_error(self):
"""Test that running vf2layout on a pass against a target with some error rates works."""
n_qubits = 15
target = Target()
target.add_instruction(
XGate(), {(i,): InstructionProperties(error=0.00123) for i in range(n_qubits)}
)
target.add_instruction(CXGate(), {(i, i + 1): None for i in range(n_qubits - 1)})
vf2_pass = VF2Layout(target=target)
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
dag = circuit_to_dag(circuit)
vf2_pass.run(dag)
self.assertLayout(dag, target.build_coupling_map(), vf2_pass.property_set)
class TestVF2LayoutLattice(LayoutTestCase):
"""Fit in 25x25 hexagonal lattice coupling map"""

View File

@ -16,6 +16,7 @@ import retworkx
from qiskit import QuantumRegister, QuantumCircuit
from qiskit.circuit import ControlFlowOp
from qiskit.circuit.library import CXGate, XGate
from qiskit.transpiler import CouplingMap, Layout, TranspilerError
from qiskit.transpiler.passes.layout import vf2_utils
from qiskit.transpiler.passes.layout.vf2_post_layout import VF2PostLayout, VF2PostLayoutStopReason
@ -24,6 +25,7 @@ from qiskit.test import QiskitTestCase
from qiskit.providers.fake_provider import FakeLima, FakeYorktown, FakeLimaV2, FakeYorktownV2
from qiskit.circuit import Qubit
from qiskit.compiler.transpiler import transpile
from qiskit.transpiler.target import Target, InstructionProperties
class TestVF2PostLayout(QiskitTestCase):
@ -326,6 +328,35 @@ class TestVF2PostLayout(QiskitTestCase):
VF2PostLayoutStopReason.NO_SOLUTION_FOUND,
)
def test_target_no_error(self):
"""Test that running vf2layout on a pass against a target with no error rates works."""
n_qubits = 15
target = Target()
target.add_instruction(CXGate(), {(i, i + 1): None for i in range(n_qubits - 1)})
vf2_pass = VF2PostLayout(target=target)
circuit = QuantumCircuit(2)
circuit.cx(0, 1)
dag = circuit_to_dag(circuit)
vf2_pass.run(dag)
self.assertNotIn("post_layout", vf2_pass.property_set)
def test_target_some_error(self):
"""Test that running vf2layout on a pass against a target with some error rates works."""
n_qubits = 15
target = Target()
target.add_instruction(
XGate(), {(i,): InstructionProperties(error=0.00123) for i in range(n_qubits)}
)
target.add_instruction(CXGate(), {(i, i + 1): None for i in range(n_qubits - 1)})
vf2_pass = VF2PostLayout(target=target, seed=1234, strict_direction=False)
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
dag = circuit_to_dag(circuit)
vf2_pass.run(dag)
# No layout selected because nothing will beat initial layout
self.assertNotIn("post_layout", vf2_pass.property_set)
class TestVF2PostLayoutScoring(QiskitTestCase):
"""Test scoring heuristic function for VF2PostLayout."""