mirror of https://github.com/Qiskit/qiskit.git
Sabre layout and routing transpiler passes (#4537)
* add SABRE swap pass * add SABRE layout bidirectional search pass * expose sabre via preset passmanagers * undo deprecation for Layout.combine_into_edge_map * add Approx2qDecompose and SimplifyU3 passes * allow synthesis_fidelity in global transpile options * stopgap fix for circuits with regs in sabre_layout * add test * add tests * clean up sabre swap * restore lost qasm test files * fix tests * leave SimplifyU3 for later * leave Approx2qDecompose for later * Release notes Co-authored-by: Gushu Li <Skywalker2012@users.noreply.github.com> * lint * update level 3 * lint * lint relax * regenerate mapper tests * make set to list conversion deterministic * cleaning the diff a bit * test.python.transpiler.test_coupling.CouplingTest.test_make_symmetric * make randomization of SabreSwap controllable via seed * control randomization of SabreSwap via seed * move imports * test.python.transpiler.test_coupling.CouplingTest.test_neighbors * test.python.dagcircuit.test_dagcircuit.TestDagNodeSelection.test_front_layer * fix doc * Update test/python/transpiler/test_sabre_swap.py Co-authored-by: Luciano Bello <luciano.bello@ibm.com> * Update qiskit/transpiler/passes/routing/sabre_swap.py Co-authored-by: Luciano Bello <luciano.bello@ibm.com> * add note and test for neighbors * lint * release note Co-authored-by: Gushu Li <Skywalker2012@users.noreply.github.com> Co-authored-by: Luciano Bello <luciano.bello@ibm.com>
This commit is contained in:
parent
6aca34a9e9
commit
fab61d21d1
|
@ -116,7 +116,7 @@ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / stateme
|
|||
# pi = the PI constant
|
||||
# op = operation iterator
|
||||
# b = basis iterator
|
||||
good-names=i,j,k,d,n,m,ex,v,w,x,y,z,Run,_,logger,q,c,r,qr,cr,qc,nd,pi,op,b,ar,br,
|
||||
good-names=a,b,i,j,k,d,n,m,ex,v,w,x,y,z,Run,_,logger,q,c,r,qr,cr,qc,nd,pi,op,b,ar,br,
|
||||
__unittest,iSwapGate
|
||||
|
||||
# Bad variable names which should always be refused, separated by a comma
|
||||
|
@ -176,10 +176,10 @@ argument-rgx=[a-z_][a-z0-9_]{2,30}|ax|dt$
|
|||
argument-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression matching correct variable names
|
||||
variable-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
variable-rgx=[a-z_][a-z0-9_]{1,30}$
|
||||
|
||||
# Naming hint for variable names
|
||||
variable-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
variable-name-hint=[a-z_][a-z0-9_]{1,30}$
|
||||
|
||||
# Regular expression matching correct class attribute names
|
||||
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
|
||||
|
|
|
@ -115,10 +115,10 @@ def transpile(circuits: Union[QuantumCircuit, List[QuantumCircuit]],
|
|||
|
||||
[qr[0], None, None, qr[1], None, qr[2]]
|
||||
|
||||
layout_method: Name of layout selection pass ('trivial', 'dense', 'noise_adaptive')
|
||||
layout_method: Name of layout selection pass ('trivial', 'dense', 'noise_adaptive', 'sabre')
|
||||
Sometimes a perfect layout can be available in which case the layout_method
|
||||
may not run.
|
||||
routing_method: Name of routing pass ('basic', 'lookahead', 'stochastic')
|
||||
routing_method: Name of routing pass ('basic', 'lookahead', 'stochastic', 'sabre')
|
||||
seed_transpiler: Sets random seed for the stochastic parts of the transpiler
|
||||
optimization_level: How much optimization to perform on the circuits.
|
||||
Higher levels generate more optimized circuits,
|
||||
|
|
|
@ -1124,7 +1124,7 @@ class DAGCircuit:
|
|||
|
||||
def quantum_successors(self, node):
|
||||
"""Returns iterator of the successors of a node that are
|
||||
connected by a quantum edge as DAGNodes."""
|
||||
connected by a qubit edge."""
|
||||
for successor in self.successors(node):
|
||||
if any(isinstance(x['wire'], Qubit)
|
||||
for x in
|
||||
|
@ -1182,6 +1182,19 @@ class DAGCircuit:
|
|||
if n.type == "op":
|
||||
self.remove_op_node(n)
|
||||
|
||||
def front_layer(self):
|
||||
"""Return a list of op nodes in the first layer of this dag.
|
||||
"""
|
||||
graph_layers = self.multigraph_layers()
|
||||
try:
|
||||
next(graph_layers) # Remove input nodes
|
||||
except StopIteration:
|
||||
return []
|
||||
|
||||
op_nodes = [node for node in next(graph_layers) if node.type == "op"]
|
||||
|
||||
return op_nodes
|
||||
|
||||
def layers(self):
|
||||
"""Yield a shallow view on a layer of this DAGCircuit for all d layers of this circuit.
|
||||
|
||||
|
@ -1192,9 +1205,9 @@ class DAGCircuit:
|
|||
greedy algorithm. Each returned layer is a dict containing
|
||||
{"graph": circuit graph, "partition": list of qubit lists}.
|
||||
|
||||
New but semantically equivalent DAGNodes will be included in the returned layers,
|
||||
NOT the DAGNodes from the original DAG. The original vs. new nodes can be compared using
|
||||
DAGNode.semantic_eq(node1, node2).
|
||||
The returned layer contains new (but semantically equivalent) DAGNodes.
|
||||
These are not the same as nodes of the original dag, but are equivalent
|
||||
via DAGNode.semantic_eq(node1, node2).
|
||||
|
||||
TODO: Gates that use the same cbits will end up in different
|
||||
layers as this is currently implemented. This may not be
|
||||
|
@ -1214,7 +1227,7 @@ class DAGCircuit:
|
|||
# Sort to make sure they are in the order they were added to the original DAG
|
||||
# It has to be done by node_id as graph_layer is just a list of nodes
|
||||
# with no implied topology
|
||||
# Drawing tools that rely on _node_id to infer order of node creation
|
||||
# Drawing tools rely on _node_id to infer order of node creation
|
||||
# so we need this to be preserved by layers()
|
||||
op_nodes.sort(key=lambda nd: nd._node_id)
|
||||
|
||||
|
|
|
@ -134,6 +134,14 @@ class CouplingMap:
|
|||
except nx.exception.NetworkXException:
|
||||
return False
|
||||
|
||||
def neighbors(self, physical_qubit):
|
||||
"""Return the nearest neighbors of a physical qubit.
|
||||
|
||||
Directionality matters, i.e. a neighbor must be reachable
|
||||
by going one hop in the direction of an edge.
|
||||
"""
|
||||
return self.graph.neighbors(physical_qubit)
|
||||
|
||||
def _compute_distance_matrix(self):
|
||||
"""Compute the full distance matrix on pairs of nodes.
|
||||
|
||||
|
@ -201,6 +209,17 @@ class CouplingMap:
|
|||
self._is_symmetric = self._check_symmetry()
|
||||
return self._is_symmetric
|
||||
|
||||
def make_symmetric(self):
|
||||
"""
|
||||
Convert uni-directional edges into bi-directional.
|
||||
"""
|
||||
edges = self.get_edges()
|
||||
for src, dest in edges:
|
||||
if (dest, src) not in edges:
|
||||
self.add_edge(dest, src)
|
||||
self._dist_matrix = None # invalidate
|
||||
self._is_symmetric = None # invalidate
|
||||
|
||||
def _check_symmetry(self):
|
||||
"""
|
||||
Calculates symmetry
|
||||
|
|
|
@ -19,7 +19,6 @@ Layout is the relation between virtual (qu)bits and physical (qu)bits.
|
|||
Virtual (qu)bits are tuples, e.g. `(QuantumRegister(3, 'qr'), 2)` or simply `qr[2]`.
|
||||
Physical (qu)bits are integers.
|
||||
"""
|
||||
import warnings
|
||||
|
||||
from qiskit.circuit.quantumregister import Qubit
|
||||
from qiskit.transpiler.exceptions import LayoutError
|
||||
|
@ -224,10 +223,6 @@ class Layout():
|
|||
LayoutError: another_layout can be bigger than self, but not smaller.
|
||||
Otherwise, raises.
|
||||
"""
|
||||
warnings.warn('combine_into_edge_map is deprecated as of 0.14.0 and '
|
||||
'will be removed in a future release. Instead '
|
||||
'reorder_bits() should be used', DeprecationWarning,
|
||||
stacklevel=2)
|
||||
edge_map = dict()
|
||||
|
||||
for virtual, physical in self.get_virtual_bits().items():
|
||||
|
|
|
@ -29,6 +29,7 @@ Layout Selection (Placement)
|
|||
TrivialLayout
|
||||
DenseLayout
|
||||
NoiseAdaptiveLayout
|
||||
SabreLayout
|
||||
CSPLayout
|
||||
ApplyLayout
|
||||
Layout2qDistance
|
||||
|
@ -44,6 +45,7 @@ Routing
|
|||
BasicSwap
|
||||
LookaheadSwap
|
||||
StochasticSwap
|
||||
SabreSwap
|
||||
|
||||
Basis Change
|
||||
============
|
||||
|
@ -108,6 +110,7 @@ from .layout import SetLayout
|
|||
from .layout import TrivialLayout
|
||||
from .layout import DenseLayout
|
||||
from .layout import NoiseAdaptiveLayout
|
||||
from .layout import SabreLayout
|
||||
from .layout import CSPLayout
|
||||
from .layout import ApplyLayout
|
||||
from .layout import Layout2qDistance
|
||||
|
@ -119,6 +122,7 @@ from .routing import BasicSwap
|
|||
from .routing import LayoutTransformation
|
||||
from .routing import LookaheadSwap
|
||||
from .routing import StochasticSwap
|
||||
from .routing import SabreSwap
|
||||
|
||||
# basis change
|
||||
from .basis import Decompose
|
||||
|
|
|
@ -18,6 +18,7 @@ from .set_layout import SetLayout
|
|||
from .trivial_layout import TrivialLayout
|
||||
from .dense_layout import DenseLayout
|
||||
from .noise_adaptive_layout import NoiseAdaptiveLayout
|
||||
from .sabre_layout import SabreLayout
|
||||
from .csp_layout import CSPLayout
|
||||
from .apply_layout import ApplyLayout
|
||||
from .layout_2q_distance import Layout2qDistance
|
||||
|
|
|
@ -74,8 +74,8 @@ class CSPLayout(AnalysisPass):
|
|||
time_limit=10):
|
||||
"""If possible, chooses a Layout as a CSP, using backtracking.
|
||||
|
||||
If not possible, does not set the layout property. In all the cases, the property
|
||||
:meth:`qiskit.transpiler.passes.CSPLayout_stop_reason` will be added with one of the
|
||||
If not possible, does not set the layout property. In all the cases,
|
||||
the property `CSPLayout_stop_reason` will be added with one of the
|
||||
following values:
|
||||
|
||||
* solution found: If a perfect layout was found.
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This code is part of Qiskit.
|
||||
#
|
||||
# (C) Copyright IBM 2017, 2020.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Layout selection using the SABRE bidirectional search approach from Li et al.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import numpy as np
|
||||
|
||||
from qiskit.converters import dag_to_circuit
|
||||
from qiskit.transpiler.passes.layout.set_layout import SetLayout
|
||||
from qiskit.transpiler.passes.layout.full_ancilla_allocation import FullAncillaAllocation
|
||||
from qiskit.transpiler.passes.layout.enlarge_with_ancilla import EnlargeWithAncilla
|
||||
from qiskit.transpiler.passes.layout.apply_layout import ApplyLayout
|
||||
from qiskit.transpiler.passes.routing import SabreSwap
|
||||
from qiskit.transpiler.passmanager import PassManager
|
||||
from qiskit.transpiler.layout import Layout
|
||||
from qiskit.transpiler.basepasses import AnalysisPass
|
||||
from qiskit.transpiler.exceptions import TranspilerError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SabreLayout(AnalysisPass):
|
||||
"""Choose a Layout via iterative bidirectional routing of the input circuit.
|
||||
|
||||
Starting with a random initial `Layout`, the algorithm does a full routing
|
||||
of the circuit (via the `routing_pass` method) to end up with a
|
||||
`final_layout`. This final_layout is then used as the initial_layout for
|
||||
routing the reverse circuit. The algorithm iterates a number of times until
|
||||
it finds an initial_layout that reduces full routing cost.
|
||||
|
||||
This method exploits the reversibility of quantum circuits, and tries to
|
||||
include global circuit information in the choice of initial_layout.
|
||||
|
||||
**References:**
|
||||
|
||||
[1] Li, Gushu, Yufei Ding, and Yuan Xie. "Tackling the qubit mapping problem
|
||||
for NISQ-era quantum devices." ASPLOS 2019.
|
||||
`arXiv:1809.02573 <https://arxiv.org/pdf/1809.02573.pdf>`_
|
||||
"""
|
||||
|
||||
def __init__(self, coupling_map, routing_pass=None, seed=None,
|
||||
max_iterations=3):
|
||||
"""SabreLayout initializer.
|
||||
|
||||
Args:
|
||||
coupling_map (Coupling): directed graph representing a coupling map.
|
||||
routing_pass (BasePass): the routing pass to use while iterating.
|
||||
seed (int): seed for setting a random first trial layout.
|
||||
max_iterations (int): number of forward-backward iterations.
|
||||
"""
|
||||
super().__init__()
|
||||
self.coupling_map = coupling_map
|
||||
self.routing_pass = routing_pass
|
||||
self.seed = seed
|
||||
self.max_iterations = max_iterations
|
||||
|
||||
def run(self, dag):
|
||||
"""Run the SabreLayout pass on `dag`.
|
||||
|
||||
Args:
|
||||
dag (DAGCircuit): DAG to find layout for.
|
||||
|
||||
Raises:
|
||||
TranspilerError: if dag wider than self.coupling_map
|
||||
"""
|
||||
if len(dag.qubits) > self.coupling_map.size():
|
||||
raise TranspilerError('More virtual qubits exist than physical.')
|
||||
|
||||
# Choose a random initial_layout.
|
||||
if self.seed is None:
|
||||
self.seed = np.random.randint(0, np.iinfo(np.int32).max)
|
||||
rng = np.random.default_rng(self.seed)
|
||||
|
||||
physical_qubits = rng.choice(self.coupling_map.size(),
|
||||
len(dag.qubits), replace=False)
|
||||
physical_qubits = rng.permutation(physical_qubits)
|
||||
initial_layout = Layout({q: dag.qubits[i]
|
||||
for i, q in enumerate(physical_qubits)})
|
||||
|
||||
if self.routing_pass is None:
|
||||
self.routing_pass = SabreSwap(self.coupling_map, 'decay')
|
||||
|
||||
# Do forward-backward iterations.
|
||||
circ = dag_to_circuit(dag)
|
||||
for i in range(self.max_iterations):
|
||||
for _ in ('forward', 'backward'):
|
||||
pm = self._layout_and_route_passmanager(initial_layout)
|
||||
new_circ = pm.run(circ)
|
||||
|
||||
# Update initial layout and reverse the unmapped circuit.
|
||||
pass_final_layout = pm.property_set['final_layout']
|
||||
final_layout = self._compose_layouts(initial_layout,
|
||||
pass_final_layout,
|
||||
circ.qregs)
|
||||
initial_layout = final_layout
|
||||
circ = circ.reverse_ops()
|
||||
|
||||
# Diagnostics
|
||||
logger.info('After round %d, num_swaps: %d',
|
||||
i+1, new_circ.count_ops().get('swap', 0))
|
||||
logger.info('new initial layout')
|
||||
logger.info(initial_layout)
|
||||
|
||||
self.property_set['layout'] = initial_layout
|
||||
|
||||
def _layout_and_route_passmanager(self, initial_layout):
|
||||
"""Return a passmanager for a full layout and routing.
|
||||
|
||||
We use a factory to remove potential statefulness of passes.
|
||||
"""
|
||||
layout_and_route = [SetLayout(initial_layout),
|
||||
FullAncillaAllocation(self.coupling_map),
|
||||
EnlargeWithAncilla(),
|
||||
ApplyLayout(),
|
||||
self.routing_pass]
|
||||
pm = PassManager(layout_and_route)
|
||||
return pm
|
||||
|
||||
def _compose_layouts(self, initial_layout, pass_final_layout, qregs):
|
||||
"""Return the real final_layout resulting from the composition
|
||||
of an initial_layout with the final_layout reported by a pass.
|
||||
|
||||
The routing passes internally start with a trivial layout, as the
|
||||
layout gets applied to the circuit prior to running them. So the
|
||||
"final_layout" they report must be amended to account for the actual
|
||||
initial_layout that was selected.
|
||||
"""
|
||||
trivial_layout = Layout.generate_trivial_layout(*qregs)
|
||||
pass_final_layout = Layout({trivial_layout[v.index]: p
|
||||
for v, p in pass_final_layout.get_virtual_bits().items()})
|
||||
qubit_map = Layout.combine_into_edge_map(initial_layout, trivial_layout)
|
||||
final_layout = {v: pass_final_layout[qubit_map[v]]
|
||||
for v, _ in initial_layout.get_virtual_bits().items()}
|
||||
return Layout(final_layout)
|
|
@ -18,3 +18,4 @@ from .basic_swap import BasicSwap
|
|||
from .layout_transformation import LayoutTransformation
|
||||
from .lookahead_swap import LookaheadSwap
|
||||
from .stochastic_swap import StochasticSwap
|
||||
from .sabre_swap import SabreSwap
|
||||
|
|
|
@ -25,7 +25,7 @@ from qiskit.transpiler.exceptions import TranspilerError
|
|||
from qiskit.transpiler.layout import Layout
|
||||
from qiskit.dagcircuit import DAGNode
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LookaheadSwap(TransformationPass):
|
||||
|
|
|
@ -0,0 +1,363 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This code is part of Qiskit.
|
||||
#
|
||||
# (C) Copyright IBM 2017, 2020.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Routing via SWAP insertion using the SABRE method from Li et al."""
|
||||
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
from itertools import cycle
|
||||
import numpy as np
|
||||
|
||||
from qiskit.dagcircuit import DAGCircuit
|
||||
from qiskit.circuit.library.standard_gates import SwapGate
|
||||
from qiskit.transpiler.basepasses import TransformationPass
|
||||
from qiskit.transpiler.exceptions import TranspilerError
|
||||
from qiskit.transpiler.layout import Layout
|
||||
from qiskit.dagcircuit import DAGNode
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
EXTENDED_SET_SIZE = 20 # Size of lookahead window. TODO: set dynamically to len(current_layout)
|
||||
EXTENDED_SET_WEIGHT = 0.5 # Weight of lookahead window compared to front_layer.
|
||||
|
||||
DECAY_RATE = 0.001 # Decay cooefficient for penalizing serial swaps.
|
||||
DECAY_RESET_INTERVAL = 5 # How often to reset all decay rates to 1.
|
||||
|
||||
|
||||
class SabreSwap(TransformationPass):
|
||||
r"""Map input circuit onto a backend topology via insertion of SWAPs.
|
||||
|
||||
Implementation of the SWAP-based heuristic search from the SABRE qubit
|
||||
mapping paper [1] (Algorithm 1). The hueristic aims to minimize the number
|
||||
of lossy SWAPs inserted and the depth of the circuit.
|
||||
|
||||
This algorithm starts from an initial layout of virtual qubits onto physical
|
||||
qubits, and iterates over the circuit DAG until all gates are exhausted,
|
||||
inserting SWAPs along the way. It only considers 2-qubit gates as only those
|
||||
are germane for the mapping problem (it is assumed that 3+ qubit gates are
|
||||
already decomposed).
|
||||
|
||||
In each iteration, it will first check if there are any gates in the
|
||||
``front_layer`` that can be directly applied. If so, it will apply them and
|
||||
remove them from ``front_layer``, and replenish that layer with new gates
|
||||
if possible. Otherwise, it will try to search for SWAPs, insert the SWAPs,
|
||||
and update the mapping.
|
||||
|
||||
The search for SWAPs is restricted, in the sense that we only consider
|
||||
physical qubits in the neighoborhood of those qubits involved in
|
||||
``front_layer``. These give rise to a ``swap_candidate_list`` which is
|
||||
scored according to some heuristic cost function. The best SWAP is
|
||||
implemented and ``current_layout`` updated.
|
||||
|
||||
**References:**
|
||||
|
||||
[1] Li, Gushu, Yufei Ding, and Yuan Xie. "Tackling the qubit mapping problem
|
||||
for NISQ-era quantum devices." ASPLOS 2019.
|
||||
`arXiv:1809.02573 <https://arxiv.org/pdf/1809.02573.pdf>`_
|
||||
"""
|
||||
|
||||
def __init__(self, coupling_map, heuristic='basic', seed=None):
|
||||
r"""SabreSwap initializer.
|
||||
|
||||
Args:
|
||||
coupling_map (CouplingMap): CouplingMap of the target backend.
|
||||
heuristic (str): The type of heuristic to use when deciding best
|
||||
swap strategy ('basic' or 'lookahead' or 'decay').
|
||||
seed (int): random seed used to tie-break among candidate swaps.
|
||||
|
||||
Additional Information:
|
||||
|
||||
The search space of possible SWAPs on physical qubits is explored
|
||||
by assigning a score to the layout that would result from each SWAP.
|
||||
The goodness of a layout is evaluated based on how viable it makes
|
||||
the remaining virtual gates that must be applied. A few heuristic
|
||||
cost functions are supported
|
||||
|
||||
- 'basic':
|
||||
|
||||
The sum of distances for corresponding physical qubits of
|
||||
interacting virtual qubits in the front_layer.
|
||||
|
||||
.. math::
|
||||
|
||||
H_{basic} = \sum_{gate \in F} D[\pi(gate.q_1)][\pi(gate.q2)]
|
||||
|
||||
- 'lookahead':
|
||||
|
||||
This is the sum of two costs: first is the same as the basic cost.
|
||||
Second is the basic cost but now evaluated for the
|
||||
extended set as well (i.e. :math:`|E|` number of upcoming successors to gates in
|
||||
front_layer F). This is weighted by some amount EXTENDED_SET_WEIGHT (W) to
|
||||
signify that upcoming gates are less important that the front_layer.
|
||||
|
||||
.. math::
|
||||
|
||||
H_{decay} = \frac{1}{\abs{F}} \sum_{gate \in F} D[\pi(gate.q_1)][\pi(gate.q2)]
|
||||
+ W * \frac{1}{\abs{E}} \sum_{gate \in E} D[\pi(gate.q_1)][\pi(gate.q2)]
|
||||
|
||||
- 'decay':
|
||||
|
||||
This is the same as 'lookahead', but the whole cost is multiplied by a
|
||||
decay factor. This increases the cost if the SWAP that generated the
|
||||
trial layout was recently used (i.e. it penalizes increase in depth).
|
||||
|
||||
.. math::
|
||||
|
||||
H_{decay} = max(decay(SWAP.q_1), decay(SWAP.q_2)) {
|
||||
\frac{1}{\abs{F}} \sum_{gate \in F} D[\pi(gate.q_1)][\pi(gate.q2)]
|
||||
+ W * \frac{1}{\abs{E}} \sum_{gate \in E} D[\pi(gate.q_1)][\pi(gate.q2)]
|
||||
}
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
self.coupling_map = coupling_map
|
||||
self.heuristic = heuristic
|
||||
self.seed = seed
|
||||
self.applied_gates = None
|
||||
self.qubits_decay = None
|
||||
|
||||
def run(self, dag):
|
||||
"""Run the SabreSwap pass on `dag`.
|
||||
|
||||
Args:
|
||||
dag (DAGCircuit): the directed acyclic graph to be mapped.
|
||||
Returns:
|
||||
DAGCircuit: A dag mapped to be compatible with the coupling_map.
|
||||
Raises:
|
||||
TranspilerError: if the coupling map or the layout are not
|
||||
compatible with the DAG
|
||||
"""
|
||||
if len(dag.qregs) != 1 or dag.qregs.get('q', None) is None:
|
||||
raise TranspilerError('Sabre swap runs on physical circuits only.')
|
||||
|
||||
if len(dag.qubits) > self.coupling_map.size():
|
||||
raise TranspilerError('More virtual qubits exist than physical.')
|
||||
|
||||
rng = np.random.default_rng(self.seed)
|
||||
|
||||
# Preserve input DAG's name, regs, wire_map, etc. but replace the graph.
|
||||
mapped_dag = _copy_circuit_metadata(dag)
|
||||
|
||||
# Assume bidirectional couplings, fixing gate direction is easy later.
|
||||
self.coupling_map.make_symmetric()
|
||||
|
||||
canonical_register = dag.qregs['q']
|
||||
current_layout = Layout.generate_trivial_layout(canonical_register)
|
||||
|
||||
# A decay factor for each qubit used to heuristically penalize recently
|
||||
# used qubits (to encourage parallelism).
|
||||
self.qubits_decay = {qubit: 1 for qubit in dag.qubits}
|
||||
|
||||
# Start algorithm from the front layer and iterate until all gates done.
|
||||
num_search_steps = 0
|
||||
front_layer = dag.front_layer()
|
||||
self.applied_gates = set()
|
||||
while front_layer:
|
||||
execute_gate_list = []
|
||||
|
||||
# Remove as many immediately applicable gates as possible
|
||||
for node in front_layer:
|
||||
if len(node.qargs) == 2:
|
||||
v0, v1 = node.qargs
|
||||
physical_qubits = (current_layout[v0], current_layout[v1])
|
||||
if physical_qubits in self.coupling_map.get_edges():
|
||||
execute_gate_list.append(node)
|
||||
else: # Single-qubit gates as well as barriers are free
|
||||
execute_gate_list.append(node)
|
||||
|
||||
if execute_gate_list:
|
||||
for node in execute_gate_list:
|
||||
new_node = _transform_gate_for_layout(node, current_layout)
|
||||
mapped_dag.apply_operation_back(new_node.op,
|
||||
new_node.qargs,
|
||||
new_node.cargs,
|
||||
new_node.condition)
|
||||
front_layer.remove(node)
|
||||
self.applied_gates.add(node)
|
||||
for successor in dag.quantum_successors(node):
|
||||
if successor.type != 'op':
|
||||
continue
|
||||
if self._is_resolved(successor, dag):
|
||||
front_layer.append(successor)
|
||||
|
||||
if node.qargs:
|
||||
self._reset_qubits_decay()
|
||||
|
||||
# Diagnostics
|
||||
logger.debug('free! %s',
|
||||
[(n.name, n.qargs) for n in execute_gate_list])
|
||||
logger.debug('front_layer: %s',
|
||||
[(n.name, n.qargs) for n in front_layer])
|
||||
|
||||
continue
|
||||
|
||||
# After all free gates are exhausted, heuristically find
|
||||
# the best swap and insert it. When two or more swaps tie
|
||||
# for best score, pick one randomly.
|
||||
extended_set = self._obtain_extended_set(dag, front_layer)
|
||||
swap_candidates = self._obtain_swaps(front_layer, current_layout)
|
||||
swap_scores = dict.fromkeys(swap_candidates, 0)
|
||||
for swap_qubits in swap_scores:
|
||||
trial_layout = current_layout.copy()
|
||||
trial_layout.swap(*swap_qubits)
|
||||
score = self._score_heuristic(self.heuristic,
|
||||
front_layer,
|
||||
extended_set,
|
||||
trial_layout,
|
||||
swap_qubits)
|
||||
swap_scores[swap_qubits] = score
|
||||
min_score = min(swap_scores.values())
|
||||
best_swaps = [k for k, v in swap_scores.items() if v == min_score]
|
||||
best_swaps.sort(key=lambda x: (x[0].index, x[1].index))
|
||||
best_swap = rng.choice(best_swaps)
|
||||
swap_node = DAGNode(op=SwapGate(), qargs=best_swap, type='op')
|
||||
swap_node = _transform_gate_for_layout(swap_node, current_layout)
|
||||
mapped_dag.apply_operation_back(swap_node.op, swap_node.qargs)
|
||||
current_layout.swap(*best_swap)
|
||||
|
||||
num_search_steps += 1
|
||||
if num_search_steps % DECAY_RESET_INTERVAL == 0:
|
||||
self._reset_qubits_decay()
|
||||
else:
|
||||
self.qubits_decay[best_swap[0]] += DECAY_RATE
|
||||
self.qubits_decay[best_swap[1]] += DECAY_RATE
|
||||
|
||||
# Diagnostics
|
||||
logger.debug('SWAP Selection...')
|
||||
logger.debug('extended_set: %s',
|
||||
[(n.name, n.qargs) for n in extended_set])
|
||||
logger.debug('swap scores: %s', swap_scores)
|
||||
logger.debug('best swap: %s', best_swap)
|
||||
logger.debug('qubits decay: %s', self.qubits_decay)
|
||||
|
||||
self.property_set['final_layout'] = current_layout
|
||||
|
||||
return mapped_dag
|
||||
|
||||
def _reset_qubits_decay(self):
|
||||
"""Reset all qubit decay factors to 1 upon request (to forget about
|
||||
past penalizations).
|
||||
"""
|
||||
self.qubits_decay = {k: 1 for k in self.qubits_decay.keys()}
|
||||
|
||||
def _is_resolved(self, node, dag):
|
||||
"""Return True if all of a node's predecessors in dag are applied.
|
||||
"""
|
||||
predecessors = dag.quantum_predecessors(node)
|
||||
predecessors = filter(lambda x: x.type == 'op', predecessors)
|
||||
return all([n in self.applied_gates for n in predecessors])
|
||||
|
||||
def _obtain_extended_set(self, dag, front_layer):
|
||||
"""Populate extended_set by looking ahead a fixed number of gates.
|
||||
For each existing element add a successor until reaching limit.
|
||||
"""
|
||||
# TODO: use layers instead of bfs_successors so long range successors aren't included.
|
||||
extended_set = set()
|
||||
bfs_successors_pernode = [dag.bfs_successors(n) for n in front_layer]
|
||||
node_lookahead_exhausted = [False] * len(front_layer)
|
||||
for i, node_successor_generator in cycle(enumerate(bfs_successors_pernode)):
|
||||
if all(node_lookahead_exhausted) or len(extended_set) >= EXTENDED_SET_SIZE:
|
||||
break
|
||||
|
||||
try:
|
||||
_, successors = next(node_successor_generator)
|
||||
successors = list(filter(lambda x: x.type == 'op' and len(x.qargs) == 2,
|
||||
successors))
|
||||
except StopIteration:
|
||||
node_lookahead_exhausted[i] = True
|
||||
continue
|
||||
|
||||
successors = iter(successors)
|
||||
while len(extended_set) < EXTENDED_SET_SIZE:
|
||||
try:
|
||||
extended_set.add(next(successors))
|
||||
except StopIteration:
|
||||
break
|
||||
|
||||
return extended_set
|
||||
|
||||
def _obtain_swaps(self, front_layer, current_layout):
|
||||
"""Return a set of candidate swaps that affect qubits in front_layer.
|
||||
|
||||
For each virtual qubit in front_layer, find its current location
|
||||
on hardware and the physical qubits in that neighborhood. Every SWAP
|
||||
on virtual qubits that corresponds to one of those physical couplings
|
||||
is a candidate SWAP.
|
||||
|
||||
Candidate swaps are sorted so SWAP(i,j) and SWAP(j,i) are not duplicated.
|
||||
"""
|
||||
candidate_swaps = set()
|
||||
for node in front_layer:
|
||||
for virtual in node.qargs:
|
||||
physical = current_layout[virtual]
|
||||
for neighbor in self.coupling_map.neighbors(physical):
|
||||
virtual_neighbor = current_layout[neighbor]
|
||||
swap = sorted([virtual, virtual_neighbor],
|
||||
key=lambda q: (q.register.name, q.index))
|
||||
candidate_swaps.add(tuple(swap))
|
||||
|
||||
return candidate_swaps
|
||||
|
||||
def _score_heuristic(self, heuristic, front_layer, extended_set, layout, swap_qubits=None):
|
||||
"""Return a heuristic score for a trial layout.
|
||||
|
||||
Assuming a trial layout has resulted from a SWAP, we now assign a cost
|
||||
to it. The goodness of a layout is evaluated based on how viable it makes
|
||||
the remaining virtual gates that must be applied.
|
||||
"""
|
||||
if heuristic == 'basic':
|
||||
return sum(self.coupling_map.distance(*[layout[q] for q in node.qargs])
|
||||
for node in front_layer)
|
||||
|
||||
elif heuristic == 'lookahead':
|
||||
first_cost = self._score_heuristic('basic', front_layer, [], layout)
|
||||
first_cost /= len(front_layer)
|
||||
|
||||
second_cost = self._score_heuristic('basic', extended_set, [], layout)
|
||||
second_cost = 0.0 if not extended_set else second_cost / len(extended_set)
|
||||
|
||||
return first_cost + EXTENDED_SET_WEIGHT * second_cost
|
||||
|
||||
elif heuristic == 'decay':
|
||||
return max(self.qubits_decay[swap_qubits[0]], self.qubits_decay[swap_qubits[1]]) * \
|
||||
self._score_heuristic('lookahead', front_layer, extended_set, layout)
|
||||
|
||||
else:
|
||||
raise TranspilerError('Heuristic %s not recognized.' % heuristic)
|
||||
|
||||
|
||||
def _copy_circuit_metadata(source_dag):
|
||||
"""Return a copy of source_dag with metadata but empty.
|
||||
"""
|
||||
target_dag = DAGCircuit()
|
||||
target_dag.name = source_dag.name
|
||||
|
||||
for qreg in source_dag.qregs.values():
|
||||
target_dag.add_qreg(qreg)
|
||||
for creg in source_dag.cregs.values():
|
||||
target_dag.add_creg(creg)
|
||||
|
||||
return target_dag
|
||||
|
||||
|
||||
def _transform_gate_for_layout(op_node, layout):
|
||||
"""Return node implementing a virtual op on given layout."""
|
||||
mapped_op_node = deepcopy(op_node)
|
||||
|
||||
device_qreg = op_node.qargs[0].register
|
||||
premap_qargs = op_node.qargs
|
||||
mapped_qargs = map(lambda x: device_qreg[layout[x]], premap_qargs)
|
||||
mapped_op_node.qargs = mapped_op_node.op.qargs = list(mapped_qargs)
|
||||
|
||||
return mapped_op_node
|
|
@ -28,10 +28,12 @@ from qiskit.transpiler.passes import SetLayout
|
|||
from qiskit.transpiler.passes import TrivialLayout
|
||||
from qiskit.transpiler.passes import DenseLayout
|
||||
from qiskit.transpiler.passes import NoiseAdaptiveLayout
|
||||
from qiskit.transpiler.passes import SabreLayout
|
||||
from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements
|
||||
from qiskit.transpiler.passes import BasicSwap
|
||||
from qiskit.transpiler.passes import LookaheadSwap
|
||||
from qiskit.transpiler.passes import StochasticSwap
|
||||
from qiskit.transpiler.passes import SabreSwap
|
||||
from qiskit.transpiler.passes import FullAncillaAllocation
|
||||
from qiskit.transpiler.passes import EnlargeWithAncilla
|
||||
from qiskit.transpiler.passes import ApplyLayout
|
||||
|
@ -83,6 +85,8 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
|
|||
_choose_layout = DenseLayout(coupling_map, backend_properties)
|
||||
elif layout_method == 'noise_adaptive':
|
||||
_choose_layout = NoiseAdaptiveLayout(backend_properties)
|
||||
elif layout_method == 'sabre':
|
||||
_choose_layout = SabreLayout(coupling_map, max_iterations=1, seed=seed_transpiler)
|
||||
else:
|
||||
raise TranspilerError("Invalid layout method %s." % layout_method)
|
||||
|
||||
|
@ -105,6 +109,8 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
|
|||
_swap += [StochasticSwap(coupling_map, trials=20, seed=seed_transpiler)]
|
||||
elif routing_method == 'lookahead':
|
||||
_swap += [LookaheadSwap(coupling_map, search_depth=2, search_width=2)]
|
||||
elif routing_method == 'sabre':
|
||||
_swap += [SabreSwap(coupling_map, heuristic='basic', seed=seed_transpiler)]
|
||||
else:
|
||||
raise TranspilerError("Invalid routing method %s." % routing_method)
|
||||
|
||||
|
|
|
@ -29,10 +29,12 @@ from qiskit.transpiler.passes import SetLayout
|
|||
from qiskit.transpiler.passes import TrivialLayout
|
||||
from qiskit.transpiler.passes import DenseLayout
|
||||
from qiskit.transpiler.passes import NoiseAdaptiveLayout
|
||||
from qiskit.transpiler.passes import SabreLayout
|
||||
from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements
|
||||
from qiskit.transpiler.passes import BasicSwap
|
||||
from qiskit.transpiler.passes import LookaheadSwap
|
||||
from qiskit.transpiler.passes import StochasticSwap
|
||||
from qiskit.transpiler.passes import SabreSwap
|
||||
from qiskit.transpiler.passes import FullAncillaAllocation
|
||||
from qiskit.transpiler.passes import EnlargeWithAncilla
|
||||
from qiskit.transpiler.passes import FixedPoint
|
||||
|
@ -96,6 +98,8 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
|
|||
_improve_layout = DenseLayout(coupling_map, backend_properties)
|
||||
elif layout_method == 'noise_adaptive':
|
||||
_improve_layout = NoiseAdaptiveLayout(backend_properties)
|
||||
elif layout_method == 'sabre':
|
||||
_improve_layout = SabreLayout(coupling_map, max_iterations=2, seed=seed_transpiler)
|
||||
else:
|
||||
raise TranspilerError("Invalid layout method %s." % layout_method)
|
||||
|
||||
|
@ -122,6 +126,8 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
|
|||
_swap += [StochasticSwap(coupling_map, trials=20, seed=seed_transpiler)]
|
||||
elif routing_method == 'lookahead':
|
||||
_swap += [LookaheadSwap(coupling_map, search_depth=4, search_width=4)]
|
||||
elif routing_method == 'sabre':
|
||||
_swap += [SabreSwap(coupling_map, heuristic='lookahead', seed=seed_transpiler)]
|
||||
else:
|
||||
raise TranspilerError("Invalid routing method %s." % routing_method)
|
||||
|
||||
|
|
|
@ -30,10 +30,12 @@ from qiskit.transpiler.passes import CSPLayout
|
|||
from qiskit.transpiler.passes import TrivialLayout
|
||||
from qiskit.transpiler.passes import DenseLayout
|
||||
from qiskit.transpiler.passes import NoiseAdaptiveLayout
|
||||
from qiskit.transpiler.passes import SabreLayout
|
||||
from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements
|
||||
from qiskit.transpiler.passes import BasicSwap
|
||||
from qiskit.transpiler.passes import LookaheadSwap
|
||||
from qiskit.transpiler.passes import StochasticSwap
|
||||
from qiskit.transpiler.passes import SabreSwap
|
||||
from qiskit.transpiler.passes import FullAncillaAllocation
|
||||
from qiskit.transpiler.passes import EnlargeWithAncilla
|
||||
from qiskit.transpiler.passes import FixedPoint
|
||||
|
@ -95,6 +97,8 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
|
|||
_choose_layout_2 = DenseLayout(coupling_map, backend_properties)
|
||||
elif layout_method == 'noise_adaptive':
|
||||
_choose_layout_2 = NoiseAdaptiveLayout(backend_properties)
|
||||
elif layout_method == 'sabre':
|
||||
_choose_layout_2 = SabreLayout(coupling_map, max_iterations=2, seed=seed_transpiler)
|
||||
else:
|
||||
raise TranspilerError("Invalid layout method %s." % layout_method)
|
||||
|
||||
|
@ -117,6 +121,8 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
|
|||
_swap += [StochasticSwap(coupling_map, trials=20, seed=seed_transpiler)]
|
||||
elif routing_method == 'lookahead':
|
||||
_swap += [LookaheadSwap(coupling_map, search_depth=5, search_width=5)]
|
||||
elif routing_method == 'sabre':
|
||||
_swap += [SabreSwap(coupling_map, heuristic='decay', seed=seed_transpiler)]
|
||||
else:
|
||||
raise TranspilerError("Invalid routing method %s." % routing_method)
|
||||
|
||||
|
|
|
@ -30,10 +30,12 @@ from qiskit.transpiler.passes import CSPLayout
|
|||
from qiskit.transpiler.passes import TrivialLayout
|
||||
from qiskit.transpiler.passes import DenseLayout
|
||||
from qiskit.transpiler.passes import NoiseAdaptiveLayout
|
||||
from qiskit.transpiler.passes import SabreLayout
|
||||
from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements
|
||||
from qiskit.transpiler.passes import BasicSwap
|
||||
from qiskit.transpiler.passes import LookaheadSwap
|
||||
from qiskit.transpiler.passes import StochasticSwap
|
||||
from qiskit.transpiler.passes import SabreSwap
|
||||
from qiskit.transpiler.passes import FullAncillaAllocation
|
||||
from qiskit.transpiler.passes import EnlargeWithAncilla
|
||||
from qiskit.transpiler.passes import FixedPoint
|
||||
|
@ -86,8 +88,8 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
|
|||
seed_transpiler = pass_manager_config.seed_transpiler
|
||||
backend_properties = pass_manager_config.backend_properties
|
||||
|
||||
# 1. Unroll to the basis first, to prepare for noise-adaptive layout
|
||||
_unroll = Unroller(basis_gates)
|
||||
# 1. Unroll to 1q or 2q gates
|
||||
_unroll3q = Unroll3qOrMore()
|
||||
|
||||
# 2. Layout on good qubits if calibration info available, otherwise on dense links
|
||||
_given_layout = SetLayout(initial_layout)
|
||||
|
@ -102,39 +104,34 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
|
|||
_choose_layout_2 = DenseLayout(coupling_map, backend_properties)
|
||||
elif layout_method == 'noise_adaptive':
|
||||
_choose_layout_2 = NoiseAdaptiveLayout(backend_properties)
|
||||
elif layout_method == 'sabre':
|
||||
_choose_layout_2 = SabreLayout(coupling_map, max_iterations=4, seed=seed_transpiler)
|
||||
else:
|
||||
raise TranspilerError("Invalid layout method %s." % layout_method)
|
||||
|
||||
# 3. Extend dag/layout with ancillas using the full coupling map
|
||||
_embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()]
|
||||
|
||||
# 4. Unroll to 1q or 2q gates, swap to fit the coupling map
|
||||
# 4. Swap to fit the coupling map
|
||||
_swap_check = CheckMap(coupling_map)
|
||||
|
||||
def _swap_condition(property_set):
|
||||
return not property_set['is_swap_mapped']
|
||||
|
||||
_swap = [BarrierBeforeFinalMeasurements(), Unroll3qOrMore()]
|
||||
_swap = [BarrierBeforeFinalMeasurements()]
|
||||
if routing_method == 'basic':
|
||||
_swap += [BasicSwap(coupling_map)]
|
||||
elif routing_method == 'stochastic':
|
||||
_swap += [StochasticSwap(coupling_map, trials=200, seed=seed_transpiler)]
|
||||
elif routing_method == 'lookahead':
|
||||
_swap += [LookaheadSwap(coupling_map, search_depth=5, search_width=6)]
|
||||
elif routing_method == 'sabre':
|
||||
_swap += [SabreSwap(coupling_map, heuristic='decay', seed=seed_transpiler)]
|
||||
else:
|
||||
raise TranspilerError("Invalid routing method %s." % routing_method)
|
||||
|
||||
# 5. 1q rotation merge and commutative cancellation iteratively until no more change in depth
|
||||
_depth_check = [Depth(), FixedPoint('depth')]
|
||||
|
||||
def _opt_control(property_set):
|
||||
return not property_set['depth_fixed_point']
|
||||
|
||||
_opt = [RemoveResetInZeroState(),
|
||||
Collect2qBlocks(), ConsolidateBlocks(),
|
||||
Unroller(basis_gates), # unroll unitaries
|
||||
Optimize1qGates(basis_gates), CommutativeCancellation(),
|
||||
OptimizeSwapBeforeMeasure(), RemoveDiagonalGatesBeforeMeasure()]
|
||||
# 5. Unroll to the basis
|
||||
_unroll = [Unroller(basis_gates)]
|
||||
|
||||
# 6. Fix any CX direction mismatch
|
||||
_direction_check = [CheckCXDirection(coupling_map)]
|
||||
|
@ -144,19 +141,35 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
|
|||
|
||||
_direction = [CXDirection(coupling_map)]
|
||||
|
||||
# 8. Optimize iteratively until no more change in depth. Removes useless gates
|
||||
# after reset and before measure, commutes gates and optimizes continguous blocks.
|
||||
_depth_check = [Depth(), FixedPoint('depth')]
|
||||
|
||||
def _opt_control(property_set):
|
||||
return not property_set['depth_fixed_point']
|
||||
|
||||
_reset = [RemoveResetInZeroState()]
|
||||
|
||||
_meas = [OptimizeSwapBeforeMeasure(), RemoveDiagonalGatesBeforeMeasure()]
|
||||
|
||||
_opt = [Collect2qBlocks(), ConsolidateBlocks(),
|
||||
Optimize1qGates(basis_gates), CommutativeCancellation()]
|
||||
|
||||
# Build pass manager
|
||||
pm3 = PassManager()
|
||||
pm3.append(_unroll)
|
||||
pm3.append(_unroll3q)
|
||||
if coupling_map:
|
||||
pm3.append(_given_layout)
|
||||
pm3.append(_choose_layout_1, condition=_choose_layout_condition)
|
||||
pm3.append(_choose_layout_2, condition=_choose_layout_condition)
|
||||
pm3.append(_embed)
|
||||
pm3.append(_reset + _meas)
|
||||
pm3.append(_swap_check)
|
||||
pm3.append(_swap, condition=_swap_condition)
|
||||
pm3.append(_depth_check + _opt, do_while=_opt_control)
|
||||
pm3.append(_depth_check + _opt + _unroll, do_while=_opt_control)
|
||||
if coupling_map and not coupling_map.is_symmetric:
|
||||
pm3.append(_direction_check)
|
||||
pm3.append(_direction, condition=_direction_condition)
|
||||
pm3.append(_reset)
|
||||
|
||||
return pm3
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
A :meth:`~qiskit.transpiler.Layout.combine_into_edge_map()` method is added
|
||||
for converting two Layouts into a qubit map composing two circuits.
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Two new methods for layout and routing have been added
|
||||
to the transpiler. They can be selected by
|
||||
passing the `layout_method='sabre'` and `routing_method='sabre'`
|
||||
to the :meth:`~qiskit.transpile()` function. The methods are based on
|
||||
Li et al. Tackling the Qubit Mapping Problem for NISQ-Era Quantum Devices,
|
||||
ASPLOS 2019.
|
|
@ -382,6 +382,16 @@ class TestDagNodeSelection(QiskitTestCase):
|
|||
self.clbit1 = creg[1]
|
||||
self.condition = (creg, 3)
|
||||
|
||||
def test_front_layer(self):
|
||||
"""The method dag.front_layer() returns first layer"""
|
||||
self.dag.apply_operation_back(HGate(), [self.qubit0], [])
|
||||
self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], [])
|
||||
self.dag.apply_operation_back(Reset(), [self.qubit0], [])
|
||||
|
||||
op_nodes = self.dag.front_layer()
|
||||
self.assertEqual(len(op_nodes), 1)
|
||||
self.assertIsInstance(op_nodes[0].op, HGate)
|
||||
|
||||
def test_get_op_nodes_all(self):
|
||||
"""The method dag.op_nodes() returns all op nodes"""
|
||||
self.dag.apply_operation_back(HGate(), [self.qubit0], [])
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
OPENQASM 2.0;
|
||||
include "qelib1.inc";
|
||||
qreg q[3];
|
||||
creg c[3];
|
||||
h q[1];
|
||||
measure q[0] -> c[0];
|
||||
swap q[0],q[2];
|
||||
cx q[1],q[0];
|
||||
measure q[1] -> c[1];
|
||||
measure q[0] -> c[2];
|
|
@ -0,0 +1,14 @@
|
|||
OPENQASM 2.0;
|
||||
include "qelib1.inc";
|
||||
qreg q[4];
|
||||
creg c[4];
|
||||
cx q[0],q[1];
|
||||
h q[3];
|
||||
measure q[2] -> c[2];
|
||||
swap q[2],q[3];
|
||||
cx q[2],q[1];
|
||||
measure q[1] -> c[1];
|
||||
swap q[1],q[2];
|
||||
cx q[1],q[0];
|
||||
measure q[0] -> c[0];
|
||||
measure q[1] -> c[3];
|
|
@ -0,0 +1,11 @@
|
|||
OPENQASM 2.0;
|
||||
include "qelib1.inc";
|
||||
qreg q[4];
|
||||
creg c[4];
|
||||
h q[1];
|
||||
measure q[0] -> c[0];
|
||||
swap q[0],q[2];
|
||||
cx q[1],q[0];
|
||||
measure q[1] -> c[1];
|
||||
measure q[0] -> c[2];
|
||||
measure q[3] -> c[3];
|
|
@ -63,6 +63,15 @@ class CouplingTest(QiskitTestCase):
|
|||
expected = ("[[0, 1]]")
|
||||
self.assertEqual(expected, str(coupling))
|
||||
|
||||
def test_neighbors(self):
|
||||
"""Test neighboring qubits are found correctly."""
|
||||
coupling = CouplingMap([[0, 1], [0, 2], [1, 0]])
|
||||
|
||||
physical_qubits = coupling.physical_qubits
|
||||
self.assertEqual(set(coupling.neighbors(physical_qubits[0])), set([1, 2]))
|
||||
self.assertEqual(set(coupling.neighbors(physical_qubits[1])), set([0]))
|
||||
self.assertEqual(set(coupling.neighbors(physical_qubits[2])), set([]))
|
||||
|
||||
def test_distance_error(self):
|
||||
"""Test distance between unconnected physical_qubits."""
|
||||
graph = CouplingMap()
|
||||
|
@ -114,6 +123,15 @@ class CouplingTest(QiskitTestCase):
|
|||
|
||||
self.assertFalse(coupling.is_symmetric)
|
||||
|
||||
def test_make_symmetric(self):
|
||||
coupling_list = [[0, 1], [0, 2]]
|
||||
coupling = CouplingMap(coupling_list)
|
||||
|
||||
coupling.make_symmetric()
|
||||
edges = coupling.get_edges()
|
||||
|
||||
self.assertEqual(set(edges), set([(0, 1), (0, 2), (2, 0), (1, 0)]))
|
||||
|
||||
def test_full_factory(self):
|
||||
coupling = CouplingMap.from_full(4)
|
||||
edges = coupling.get_edges()
|
||||
|
|
|
@ -77,7 +77,8 @@ import os
|
|||
from qiskit import execute
|
||||
from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit, BasicAer
|
||||
from qiskit.transpiler import PassManager
|
||||
from qiskit.transpiler.passes import BasicSwap, LookaheadSwap, StochasticSwap, SetLayout
|
||||
from qiskit.transpiler.passes import BasicSwap, LookaheadSwap, StochasticSwap, SabreSwap
|
||||
from qiskit.transpiler.passes import SetLayout
|
||||
from qiskit.transpiler import CouplingMap, Layout
|
||||
|
||||
from qiskit.test import QiskitTestCase
|
||||
|
@ -284,6 +285,12 @@ class TestsStochasticSwap(SwapperCommonTestCases, QiskitTestCase):
|
|||
additional_args = {'seed': 0}
|
||||
|
||||
|
||||
class TestsSabreSwap(SwapperCommonTestCases, QiskitTestCase):
|
||||
"""Test SwapperCommonTestCases using SabreSwap."""
|
||||
pass_class = SabreSwap
|
||||
additional_args = {'seed': 0}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) >= 2 and sys.argv[1] == 'regenerate':
|
||||
CommonUtilitiesMixin.regenerate_expected = True
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This code is part of Qiskit.
|
||||
#
|
||||
# (C) Copyright IBM 2017, 2020.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Test the Sabre Swap pass"""
|
||||
|
||||
import unittest
|
||||
from qiskit.transpiler.passes import SabreSwap
|
||||
from qiskit.transpiler import CouplingMap, PassManager
|
||||
from qiskit import QuantumRegister, QuantumCircuit
|
||||
from qiskit.test import QiskitTestCase
|
||||
|
||||
|
||||
class TestSabreSwap(QiskitTestCase):
|
||||
"""Tests the SabreSwap pass."""
|
||||
|
||||
def test_trivial_case(self):
|
||||
"""Test that an already mapped circuit is unchanged.
|
||||
┌───┐┌───┐
|
||||
q_0: ──■──┤ H ├┤ X ├──■──
|
||||
┌─┴─┐└───┘└─┬─┘ │
|
||||
q_1: ┤ X ├──■────■────┼──
|
||||
└───┘┌─┴─┐ │
|
||||
q_2: ──■──┤ X ├───────┼──
|
||||
┌─┴─┐├───┤ │
|
||||
q_3: ┤ X ├┤ X ├───────┼──
|
||||
└───┘└─┬─┘ ┌─┴─┐
|
||||
q_4: ───────■───────┤ X ├
|
||||
└───┘
|
||||
"""
|
||||
coupling = CouplingMap.from_ring(5)
|
||||
|
||||
qr = QuantumRegister(5, 'q')
|
||||
qc = QuantumCircuit(qr)
|
||||
qc.cx(0, 1) # free
|
||||
qc.cx(2, 3) # free
|
||||
qc.h(0) # free
|
||||
qc.cx(1, 2) # F
|
||||
qc.cx(1, 0)
|
||||
qc.cx(4, 3) # F
|
||||
qc.cx(0, 4)
|
||||
|
||||
passmanager = PassManager(SabreSwap(coupling, 'basic'))
|
||||
new_qc = passmanager.run(qc)
|
||||
|
||||
self.assertEqual(new_qc, qc)
|
||||
|
||||
def test_lookahead_mode(self):
|
||||
"""Test lookahead mode's lookahead finds single SWAP gate.
|
||||
┌───┐
|
||||
q_0: ──■──┤ H ├───────────────
|
||||
┌─┴─┐└───┘
|
||||
q_1: ┤ X ├──■────■─────────■──
|
||||
└───┘┌─┴─┐ │ │
|
||||
q_2: ──■──┤ X ├──┼────■────┼──
|
||||
┌─┴─┐└───┘┌─┴─┐┌─┴─┐┌─┴─┐
|
||||
q_3: ┤ X ├─────┤ X ├┤ X ├┤ X ├
|
||||
└───┘ └───┘└───┘└───┘
|
||||
q_4: ─────────────────────────
|
||||
|
||||
"""
|
||||
coupling = CouplingMap.from_line(5)
|
||||
|
||||
qr = QuantumRegister(5, 'q')
|
||||
qc = QuantumCircuit(qr)
|
||||
qc.cx(0, 1) # free
|
||||
qc.cx(2, 3) # free
|
||||
qc.h(0) # free
|
||||
qc.cx(1, 2) # free
|
||||
qc.cx(1, 3) # F
|
||||
qc.cx(2, 3) # E
|
||||
qc.cx(1, 3) # E
|
||||
|
||||
pm = PassManager(SabreSwap(coupling, 'lookahead'))
|
||||
new_qc = pm.run(qc)
|
||||
|
||||
self.assertEqual(new_qc.num_nonlocal_gates(), 7)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Loading…
Reference in New Issue