-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Starting layout analysis pass for sabre (#10829)
* adding SabreStartingLayoutUsingVF2 analysis pass * fixing imports * bug fix * fixing test after changing some of the options * renaming * adding target; more renaming; tests * release notes * applying suggestions from code review * removing debug print * Update qiskit/transpiler/passes/layout/sabre_pre_layout.py I didn't know this existed :) Co-authored-by: Matthew Treinish <mtreinish@kortar.org> * adding missing : * collecting edges into a set * adjusting error_rate with respect to distance * letting coupling_map be either coupling map or target, for consistency with other passes * apply suggestions from code review * Update qiskit/transpiler/passes/layout/sabre_pre_layout.py Co-authored-by: Matthew Treinish <mtreinish@kortar.org> --------- Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
- Loading branch information
1 parent
3b97b37
commit df9eae4
Showing
6 changed files
with
399 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
# This code is part of Qiskit. | ||
# | ||
# (C) Copyright IBM 2023. | ||
# | ||
# This code is licensed under the Apache License, Version 2.0. You may | ||
# obtain a copy of this license in the LICENSE.txt file in the root directory | ||
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. | ||
# | ||
# Any modifications or derivative works of this code must retain this | ||
# copyright notice, and modified files need to carry a notice indicating | ||
# that they have been altered from the originals. | ||
|
||
"""Creating Sabre starting layouts.""" | ||
|
||
import itertools | ||
|
||
from qiskit.transpiler import CouplingMap, Target, AnalysisPass, TranspilerError | ||
from qiskit.transpiler.passes.layout.vf2_layout import VF2Layout | ||
from qiskit._accelerate.error_map import ErrorMap | ||
|
||
|
||
class SabrePreLayout(AnalysisPass): | ||
"""Choose a starting layout to use for additional Sabre layout trials. | ||
Property Set Values Written | ||
--------------------------- | ||
``sabre_starting_layouts`` (``list[Layout]``) | ||
An optional list of :class:`~.Layout` objects to use for additional Sabre layout trials. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
coupling_map, | ||
max_distance=2, | ||
error_rate=0.1, | ||
max_trials_vf2=100, | ||
call_limit_vf2=None, | ||
improve_layout=True, | ||
): | ||
"""SabrePreLayout initializer. | ||
The pass works by augmenting the coupling map with more and more "extra" edges | ||
until VF2 succeeds to find a perfect graph isomorphism. More precisely, the | ||
augmented coupling map contains edges between nodes that are within a given | ||
distance ``d`` in the original coupling map, and the value of ``d`` is increased | ||
until an isomorphism is found. | ||
Intuitively, a better layout involves fewer extra edges. The pass also optionally | ||
minimizes the number of extra edges involved in the layout until a local minimum | ||
is found. This involves removing extra edges and running VF2 to see if an | ||
isomorphism still exists. | ||
Args: | ||
coupling_map (Union[CouplingMap, Target]): directed graph representing the | ||
original coupling map or a target modelling the backend (including its | ||
connectivity). | ||
max_distance (int): the maximum distance to consider for augmented coupling maps. | ||
error_rate (float): the error rate to assign to the "extra" edges. A non-zero | ||
error rate prioritizes VF2 to choose original edges over extra edges. | ||
max_trials_vf2 (int): specifies the maximum number of VF2 trials. A larger number | ||
allows VF2 to explore more layouts, eventually choosing the one with the smallest | ||
error rate. | ||
call_limit_vf2 (int): limits each call to VF2 by bounding the number of VF2 state visits. | ||
improve_layout (bool): whether to improve the layout by minimizing the number of | ||
extra edges involved. This might be time-consuming as this requires additional | ||
VF2 calls. | ||
Raises: | ||
TranspilerError: At runtime, if neither ``coupling_map`` or ``target`` are provided. | ||
""" | ||
|
||
self.max_distance = max_distance | ||
self.error_rate = error_rate | ||
self.max_trials_vf2 = max_trials_vf2 | ||
self.call_limit_vf2 = call_limit_vf2 | ||
self.improve_layout = improve_layout | ||
|
||
if isinstance(coupling_map, Target): | ||
self.target = coupling_map | ||
self.coupling_map = self.target.build_coupling_map() | ||
else: | ||
self.target = None | ||
self.coupling_map = coupling_map | ||
|
||
super().__init__() | ||
|
||
def run(self, dag): | ||
"""Run the SabrePreLayout pass on `dag`. | ||
The discovered starting layout is written to the property set | ||
value ``sabre_starting_layouts``. | ||
Args: | ||
dag (DAGCircuit): DAG to create starting layout for. | ||
""" | ||
|
||
if self.coupling_map is None: | ||
raise TranspilerError( | ||
"SabrePreLayout requires coupling_map to be used with either" | ||
"CouplingMap or a Target." | ||
) | ||
|
||
starting_layout = None | ||
cur_distance = 1 | ||
while cur_distance <= self.max_distance: | ||
augmented_map, augmented_error_map = self._add_extra_edges(cur_distance) | ||
pass_ = VF2Layout( | ||
augmented_map, | ||
seed=0, | ||
max_trials=self.max_trials_vf2, | ||
call_limit=self.call_limit_vf2, | ||
) | ||
pass_.property_set["vf2_avg_error_map"] = augmented_error_map | ||
pass_.run(dag) | ||
|
||
if "layout" in pass_.property_set: | ||
starting_layout = pass_.property_set["layout"] | ||
break | ||
|
||
cur_distance += 1 | ||
|
||
if cur_distance > 1 and starting_layout is not None: | ||
# optionally improve starting layout | ||
if self.improve_layout: | ||
starting_layout = self._minimize_extra_edges(dag, starting_layout) | ||
# write discovered layout into the property set | ||
if "sabre_starting_layouts" not in self.property_set: | ||
self.property_set["sabre_starting_layouts"] = [starting_layout] | ||
else: | ||
self.property_set["sabre_starting_layouts"].append(starting_layout) | ||
|
||
def _add_extra_edges(self, distance): | ||
"""Augments the coupling map with extra edges that connect nodes ``distance`` | ||
apart in the original graph. The extra edges are assigned errors allowing VF2 | ||
to prioritize real edges over extra edges. | ||
""" | ||
nq = len(self.coupling_map.graph) | ||
augmented_coupling_map = CouplingMap() | ||
augmented_coupling_map.graph = self.coupling_map.graph.copy() | ||
augmented_error_map = ErrorMap(nq) | ||
|
||
for (x, y) in itertools.combinations(self.coupling_map.graph.node_indices(), 2): | ||
d = self.coupling_map.distance(x, y) | ||
if 1 < d <= distance: | ||
error_rate = 1 - ((1 - self.error_rate) ** d) | ||
augmented_coupling_map.add_edge(x, y) | ||
augmented_error_map.add_error((x, y), error_rate) | ||
augmented_coupling_map.add_edge(y, x) | ||
augmented_error_map.add_error((y, x), error_rate) | ||
|
||
return augmented_coupling_map, augmented_error_map | ||
|
||
def _get_extra_edges_used(self, dag, layout): | ||
"""Returns the set of extra edges involved in the layout.""" | ||
extra_edges_used = set() | ||
virtual_bits = layout.get_virtual_bits() | ||
for node in dag.two_qubit_ops(): | ||
p0 = virtual_bits[node.qargs[0]] | ||
p1 = virtual_bits[node.qargs[1]] | ||
if self.coupling_map.distance(p0, p1) > 1: | ||
extra_edge = (p0, p1) if p0 < p1 else (p1, p0) | ||
extra_edges_used.add(extra_edge) | ||
return extra_edges_used | ||
|
||
def _find_layout(self, dag, edges): | ||
"""Checks if there is a layout for a given set of edges.""" | ||
cm = CouplingMap(edges) | ||
pass_ = VF2Layout(cm, seed=0, max_trials=1, call_limit=self.call_limit_vf2) | ||
pass_.run(dag) | ||
return pass_.property_set.get("layout", None) | ||
|
||
def _minimize_extra_edges(self, dag, starting_layout): | ||
"""Minimizes the set of extra edges involved in the layout. This iteratively | ||
removes extra edges from the coupling map and uses VF2 to check if a layout | ||
still exists. This is reasonably efficiently as it only looks for a local | ||
minimum. | ||
""" | ||
# compute the set of edges in the original coupling map | ||
real_edges = [] | ||
for (x, y) in itertools.combinations(self.coupling_map.graph.node_indices(), 2): | ||
d = self.coupling_map.distance(x, y) | ||
if d == 1: | ||
real_edges.append((x, y)) | ||
|
||
best_layout = starting_layout | ||
|
||
# keeps the set of "necessary" extra edges: without a necessary edge | ||
# a layout no longer exists | ||
extra_edges_necessary = [] | ||
|
||
extra_edges_unprocessed_set = self._get_extra_edges_used(dag, starting_layout) | ||
|
||
while extra_edges_unprocessed_set: | ||
# choose some unprocessed edge | ||
edge_chosen = next(iter(extra_edges_unprocessed_set)) | ||
extra_edges_unprocessed_set.remove(edge_chosen) | ||
|
||
# check if a layout still exists without this edge | ||
layout = self._find_layout( | ||
dag, real_edges + extra_edges_necessary + list(extra_edges_unprocessed_set) | ||
) | ||
|
||
if layout is None: | ||
# without this edge the layout either does not exist or is too hard to find | ||
extra_edges_necessary.append(edge_chosen) | ||
|
||
else: | ||
# this edge is not necessary, furthermore we can trim the set of edges to examine based | ||
# in the edges involved in the layout. | ||
extra_edges_unprocessed_set = self._get_extra_edges_used(dag, layout).difference( | ||
set(extra_edges_necessary) | ||
) | ||
best_layout = layout | ||
|
||
return best_layout |
37 changes: 37 additions & 0 deletions
37
releasenotes/notes/add-sabre-starting-layout-7e151b7abb8a6c13.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
--- | ||
features: | ||
- | | ||
Added a new analysis :class:`.SabrePreLayout` pass that creates a starting | ||
layout for :class:`.SabreLayout`, writing the layout into the property set | ||
value ``sabre_starting_layouts``. | ||
The pass works by augmenting the coupling map with more and more "extra" edges | ||
until :class:`.VF2Layout` succeeds to find a perfect graph isomorphism. | ||
More precisely, the augmented coupling map contains edges between nodes that are | ||
within a given distance ``d`` in the original coupling map, and the value of ``d`` | ||
is increased until an isomorphism is found. The pass also optionally minimizes | ||
the number of extra edges involved in the layout until a local minimum is found. | ||
This involves removing extra edges and calling :class:`.VF2Layout` to check if | ||
an isomorphism still exists. | ||
Here is an example of calling the :class:`.SabrePreLayout` before :class:`.SabreLayout`:: | ||
import math | ||
from qiskit.transpiler import CouplingMap, PassManager | ||
from qiskit.circuit.library import EfficientSU2 | ||
from qiskit.transpiler.passes import SabrePreLayout, SabreLayout | ||
qc = EfficientSU2(16, entanglement='circular', reps=6, flatten=True) | ||
qc.assign_parameters([math.pi / 2] * len(qc.parameters), inplace=True) | ||
qc.measure_all() | ||
coupling_map = CouplingMap.from_heavy_hex(7) | ||
pm = PassManager( | ||
[ | ||
SabrePreLayout(coupling_map=coupling_map), | ||
SabreLayout(coupling_map), | ||
] | ||
) | ||
pm.run(qc) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.