Skip to content

Commit

Permalink
Fix VF2Layout and VF2PostLayout handling of instructions without error (
Browse files Browse the repository at this point in the history
#8978) (#8997)

* 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>
(cherry picked from commit 6580d96)

Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
  • Loading branch information
mergify[bot] and mtreinish authored Oct 25, 2022
1 parent 04fd2f2 commit 471e8b5
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 9 deletions.
11 changes: 11 additions & 0 deletions qiskit/transpiler/passes/layout/vf2_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@ def run(self, dag):
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,
Expand All @@ -162,6 +167,12 @@ def run(self, dag):
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
Expand Down
46 changes: 40 additions & 6 deletions qiskit/transpiler/passes/layout/vf2_post_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -131,28 +132,61 @@ def run(self, dag):
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
return
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(
Expand Down
7 changes: 5 additions & 2 deletions qiskit/transpiler/passes/layout/vf2_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
15 changes: 15 additions & 0 deletions releasenotes/notes/fix-vf2-layout-no-noise-22261601684710c3.yaml
Original file line number Diff line number Diff line change
@@ -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.
31 changes: 30 additions & 1 deletion test/python/transpiler/test_vf2_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@
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


Expand Down Expand Up @@ -217,6 +218,34 @@ def test_neither_coupling_map_or_target(self):
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"""
Expand Down
31 changes: 31 additions & 0 deletions test/python/transpiler/test_vf2_post_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

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
Expand All @@ -24,6 +25,7 @@
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):
Expand Down Expand Up @@ -326,6 +328,35 @@ def test_target_invalid_2q_gate_control_flow(self):
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."""
Expand Down

0 comments on commit 471e8b5

Please sign in to comment.