mirror of https://github.com/Qiskit/qiskit.git
DenseLayout and TrivialLayout pass (#1499)
* adding an embedder pass * improve error messages in dagcircuit * get_cnot_nodes -> get_2q_nodes and get_named_nodes return node not node_id * TranspilerError is fine grained enough * rebase * add test * transpiler only calls DenseLayout if TrivialLayout does not work * add tests
This commit is contained in:
parent
11e9a31f1c
commit
285128ecdc
|
@ -304,7 +304,7 @@ class DAGCircuit:
|
|||
# Check for each wire
|
||||
for wire in args:
|
||||
if wire not in amap:
|
||||
raise DAGCircuitError("(qu)bit %s not found" % (wire,))
|
||||
raise DAGCircuitError("(qu)bit %s[%d] not found" % (wire[0].name, wire[1]))
|
||||
|
||||
def _bits_in_condition(self, cond):
|
||||
"""Return a list of bits in the given condition.
|
||||
|
@ -624,10 +624,10 @@ class DAGCircuit:
|
|||
m_wire = edge_map.get(nd["wire"], nd["wire"])
|
||||
# the mapped wire should already exist
|
||||
if m_wire not in self.output_map:
|
||||
raise DAGCircuitError("wire (%s,%d) not in self" % (m_wire[0].name, m_wire[1]))
|
||||
raise DAGCircuitError("wire %s[%d] not in self" % (m_wire[0].name, m_wire[1]))
|
||||
|
||||
if nd["wire"] not in input_circuit.wires:
|
||||
raise DAGCircuitError("inconsistent wire type for (%s,%d) in input_circuit"
|
||||
raise DAGCircuitError("inconsistent wire type for %s[%d] in input_circuit"
|
||||
% (nd["wire"][0].name, nd["wire"][1]))
|
||||
|
||||
elif nd["type"] == "out":
|
||||
|
@ -692,11 +692,11 @@ class DAGCircuit:
|
|||
m_name = wire_map.get(nd["wire"], nd["wire"])
|
||||
# the mapped wire should already exist
|
||||
if m_name not in self.input_map:
|
||||
raise DAGCircuitError("wire (%s,%d) not in self" % (m_name[0].name, m_name[1]))
|
||||
raise DAGCircuitError("wire %s[%d] not in self" % (m_name[0].name, m_name[1]))
|
||||
|
||||
if nd["wire"] not in input_circuit.wires:
|
||||
raise DAGCircuitError(
|
||||
"inconsistent wire for (%s,%d) in input_circuit"
|
||||
"inconsistent wire for %s[%d] in input_circuit"
|
||||
% (nd["wire"][0].name, nd["wire"][1]))
|
||||
|
||||
elif nd["type"] == "in":
|
||||
|
@ -957,11 +957,8 @@ class DAGCircuit:
|
|||
full_pred_map[w] = self.multi_graph.predecessors(
|
||||
self.output_map[w])[0]
|
||||
if len(list(self.multi_graph.predecessors(self.output_map[w]))) != 1:
|
||||
raise DAGCircuitError(
|
||||
|
||||
"too many predecessors for (%s,%d) output node" % (w[0], w[1])
|
||||
)
|
||||
|
||||
raise DAGCircuitError("too many predecessors for %s[%d] "
|
||||
"output node" % (w[0], w[1]))
|
||||
return full_pred_map, full_succ_map
|
||||
|
||||
@staticmethod
|
||||
|
@ -1252,26 +1249,21 @@ class DAGCircuit:
|
|||
nodes = [n[0] for n in nodes]
|
||||
return nodes
|
||||
|
||||
def get_named_nodes(self, name):
|
||||
"""Get the set of "op" node ids with the given name."""
|
||||
if name not in self.basis:
|
||||
raise DAGCircuitError("%s is not in the list of basis operations"
|
||||
% name)
|
||||
def get_named_nodes(self, *names):
|
||||
"""Get the set of "op" nodes with the given name."""
|
||||
named_nodes = []
|
||||
for node_id, node_data in self.multi_graph.nodes(data=True):
|
||||
if node_data['type'] == 'op' and node_data['op'].name in names:
|
||||
named_nodes.append(node_id)
|
||||
return named_nodes
|
||||
|
||||
# We need to instantiate the full list now because the underlying multi_graph
|
||||
# may change when users iterate over the named nodes.
|
||||
return {node_id for node_id, data in self.multi_graph.nodes(data=True)
|
||||
if data["type"] == "op" and data["op"].name == name}
|
||||
|
||||
def get_cnot_nodes(self):
|
||||
"""Get the set of Cnot."""
|
||||
cx_names = ['cx', 'CX']
|
||||
cxs_nodes = []
|
||||
for cx_name in cx_names:
|
||||
if cx_name in self.basis:
|
||||
for cx_id in self.get_named_nodes(cx_name):
|
||||
cxs_nodes.append(self.multi_graph.node[cx_id])
|
||||
return cxs_nodes
|
||||
def get_2q_nodes(self):
|
||||
"""Get the set of 2-qubit nodes."""
|
||||
two_q_nodes = []
|
||||
for node_id, node_data in self.multi_graph.nodes(data=True):
|
||||
if node_data['type'] == 'op' and len(node_data['qargs']) == 2:
|
||||
two_q_nodes.append(self.multi_graph.node[node_id])
|
||||
return two_q_nodes
|
||||
|
||||
def successors(self, node):
|
||||
"""Returns the successors of a node."""
|
||||
|
|
|
@ -363,7 +363,7 @@ def swap_mapper(circuit_graph, coupling_graph,
|
|||
Args:
|
||||
circuit_graph (DAGCircuit): input DAG circuit
|
||||
coupling_graph (CouplingGraph): coupling graph to map onto
|
||||
initial_layout (dict): dict {(str, int): (str, int)}
|
||||
initial_layout (Layout): dict {(str, int): (str, int)}
|
||||
from qubits of circuit_graph to qubits of coupling_graph (optional)
|
||||
trials (int): number of trials.
|
||||
seed (int): initial seed.
|
||||
|
|
|
@ -110,13 +110,13 @@ The control argument `do_while` will run these passes until the callable returns
|
|||
The pass manager developer can avoid one or more passes by making them conditional (on a property in the property set):
|
||||
|
||||
```
|
||||
pm.append(LayoutMapper(coupling_map))
|
||||
pm.append(CheckIfMapped(coupling_map))
|
||||
pm.append(BasicLayout(coupling_map))
|
||||
pm.append(CheckMap(coupling_map))
|
||||
pm.append(BasicSwap(coupling_map),
|
||||
condition=lambda property_set: not property_set['is_mapped'])
|
||||
condition=lambda property_set: not property_set['is_swap_mapped'])
|
||||
```
|
||||
|
||||
The `CheckIfMapped` is an analysis pass that updates the property `is_mapped`. If `LayoutMapper` could map the circuit to the coupling map, the `SwapMapper` is unnecessary.
|
||||
The `CheckMap` is an analysis pass that updates the property `is_swap_mapped`. If `LayoutMapper` could map the circuit to the coupling map, the `SwapMapper` is unnecessary.
|
||||
|
||||
|
||||
### Idempotent passes
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import os
|
||||
from ._passmanager import PassManager, FlowController
|
||||
from ._propertyset import PropertySet
|
||||
from ._transpilererror import TranspilerError, TranspilerAccessError, MapperError
|
||||
from ._transpilererror import TranspilerError, TranspilerAccessError
|
||||
from ._fencedobjs import FencedDAGCircuit, FencedPropertySet
|
||||
from ._basepasses import AnalysisPass, TransformationPass
|
||||
from ._transpiler import transpile, transpile_dag
|
||||
|
|
|
@ -8,19 +8,15 @@
|
|||
"""Tools for compiling a batch of quantum circuits."""
|
||||
import logging
|
||||
import warnings
|
||||
import numpy as np
|
||||
import scipy.sparse as sp
|
||||
import scipy.sparse.csgraph as cs
|
||||
|
||||
from qiskit.circuit import QuantumCircuit
|
||||
from qiskit.circuit import QuantumRegister
|
||||
from qiskit.mapper import CouplingMap, swap_mapper
|
||||
from qiskit.tools.parallel import parallel_map
|
||||
from qiskit.converters import circuit_to_dag
|
||||
from qiskit.converters import dag_to_circuit
|
||||
from qiskit.extensions.standard import SwapGate
|
||||
from .passes import (Unroller, CXDirection, CXCancellation,
|
||||
Decompose, Optimize1qGates, BarrierBeforeFinalMeasurements)
|
||||
from .passes import (Unroller, CXDirection, CXCancellation, DenseLayout, TrivialLayout,
|
||||
CheckMap, Decompose, Optimize1qGates, BarrierBeforeFinalMeasurements)
|
||||
from ._transpilererror import TranspilerError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -59,8 +55,7 @@ def transpile(circuits, backend=None, basis_gates=None, coupling_map=None,
|
|||
raise TranspilerError('no basis_gates or backend to compile to')
|
||||
|
||||
circuits = parallel_map(_transpilation, circuits,
|
||||
task_kwargs={'backend': backend,
|
||||
'basis_gates': basis_gates,
|
||||
task_kwargs={'basis_gates': basis_gates,
|
||||
'coupling_map': coupling_map,
|
||||
'initial_layout': initial_layout,
|
||||
'seed_mapper': seed_mapper,
|
||||
|
@ -70,14 +65,13 @@ def transpile(circuits, backend=None, basis_gates=None, coupling_map=None,
|
|||
return circuits
|
||||
|
||||
|
||||
def _transpilation(circuit, backend=None, basis_gates=None, coupling_map=None,
|
||||
def _transpilation(circuit, basis_gates=None, coupling_map=None,
|
||||
initial_layout=None, seed_mapper=None,
|
||||
pass_manager=None):
|
||||
"""Perform transpilation of a single circuit.
|
||||
|
||||
Args:
|
||||
circuit (QuantumCircuit): A circuit to transpile.
|
||||
backend (BaseBackend): a backend to compile for
|
||||
basis_gates (str): comma-separated basis gate set to compile to
|
||||
coupling_map (list): coupling map (perhaps custom) to target in mapping
|
||||
initial_layout (list): initial layout of qubits in mapping
|
||||
|
@ -94,12 +88,26 @@ def _transpilation(circuit, backend=None, basis_gates=None, coupling_map=None,
|
|||
return circuit
|
||||
|
||||
dag = circuit_to_dag(circuit)
|
||||
if not backend and not initial_layout:
|
||||
raise TranspilerError('initial layout not supplied, and cannot '
|
||||
'be inferred from backend.')
|
||||
if (initial_layout is None and not backend.configuration().simulator
|
||||
and not _matches_coupling_map(dag, coupling_map)):
|
||||
initial_layout = _pick_best_layout(dag, backend)
|
||||
|
||||
# pick a trivial layout if the circuit already satisfies the coupling constraints
|
||||
# else layout on the most densely connected physical qubit subset
|
||||
# FIXME: this should be simplified once it is ported to a PassManager
|
||||
if coupling_map:
|
||||
check_map = CheckMap(CouplingMap(coupling_map))
|
||||
check_map.run(dag)
|
||||
if check_map.property_set['is_direction_mapped']:
|
||||
trivial_layout = TrivialLayout(CouplingMap(coupling_map))
|
||||
trivial_layout.run(dag)
|
||||
initial_layout = trivial_layout.property_set['layout']
|
||||
else:
|
||||
dense_layout = DenseLayout(CouplingMap(coupling_map))
|
||||
dense_layout.run(dag)
|
||||
initial_layout = dense_layout.property_set['layout']
|
||||
# temporarily build old-style layout dict
|
||||
# (FIXME: remove after transition to StochasticSwap pass)
|
||||
layout = initial_layout.copy()
|
||||
virtual_qubits = layout.get_virtual_bits()
|
||||
initial_layout = {(v[0].name, v[1]): ('q', layout[v]) for v in virtual_qubits}
|
||||
|
||||
final_dag = transpile_dag(dag, basis_gates=basis_gates,
|
||||
coupling_map=coupling_map,
|
||||
|
@ -154,7 +162,6 @@ def transpile_dag(dag, basis_gates='u1,u2,u3,cx,id', coupling_map=None,
|
|||
|
||||
Returns:
|
||||
DAGCircuit: transformed dag
|
||||
DAGCircuit, dict: transformed dag along with the final layout on backend qubits
|
||||
"""
|
||||
# TODO: `basis_gates` will be removed after we have the unroller pass.
|
||||
# TODO: `coupling_map`, `initial_layout`, `seed_mapper` removed after mapper pass.
|
||||
|
@ -181,7 +188,7 @@ def transpile_dag(dag, basis_gates='u1,u2,u3,cx,id', coupling_map=None,
|
|||
logger.info("pre-mapping properties: %s",
|
||||
dag.properties())
|
||||
# Insert swap gates
|
||||
coupling = CouplingMap(couplinglist=coupling_map)
|
||||
coupling = CouplingMap(coupling_map)
|
||||
logger.info("initial layout: %s", initial_layout)
|
||||
dag = BarrierBeforeFinalMeasurements().run(dag)
|
||||
dag, final_layout = swap_mapper(
|
||||
|
@ -207,102 +214,3 @@ def transpile_dag(dag, basis_gates='u1,u2,u3,cx,id', coupling_map=None,
|
|||
DeprecationWarning)
|
||||
|
||||
return dag
|
||||
|
||||
|
||||
def _best_subset(backend, n_qubits):
|
||||
"""Computes the qubit mapping with the best
|
||||
connectivity.
|
||||
|
||||
Parameters:
|
||||
backend (BaseBackend): A Qiskit backend instance.
|
||||
n_qubits (int): Number of subset qubits to consider.
|
||||
|
||||
Returns:
|
||||
ndarray: Array of qubits to use for best
|
||||
connectivity mapping.
|
||||
|
||||
Raises:
|
||||
TranspilerError: Wrong number of qubits given.
|
||||
"""
|
||||
if n_qubits == 1:
|
||||
return np.array([0])
|
||||
elif n_qubits <= 0:
|
||||
raise TranspilerError('Number of qubits <= 0.')
|
||||
|
||||
device_qubits = backend.configuration().n_qubits
|
||||
if n_qubits > device_qubits:
|
||||
raise TranspilerError('Number of qubits greater than device.')
|
||||
|
||||
cmap = np.asarray(getattr(backend.configuration(), 'coupling_map', None))
|
||||
data = np.ones_like(cmap[:, 0])
|
||||
sp_cmap = sp.coo_matrix((data, (cmap[:, 0], cmap[:, 1])),
|
||||
shape=(device_qubits, device_qubits)).tocsr()
|
||||
best = 0
|
||||
best_map = None
|
||||
# do bfs with each node as starting point
|
||||
for k in range(sp_cmap.shape[0]):
|
||||
bfs = cs.breadth_first_order(sp_cmap, i_start=k, directed=False,
|
||||
return_predecessors=False)
|
||||
|
||||
connection_count = 0
|
||||
for i in range(n_qubits):
|
||||
node_idx = bfs[i]
|
||||
for j in range(sp_cmap.indptr[node_idx],
|
||||
sp_cmap.indptr[node_idx + 1]):
|
||||
node = sp_cmap.indices[j]
|
||||
for counter in range(n_qubits):
|
||||
if node == bfs[counter]:
|
||||
connection_count += 1
|
||||
break
|
||||
|
||||
if connection_count > best:
|
||||
best = connection_count
|
||||
best_map = bfs[0:n_qubits]
|
||||
return best_map
|
||||
|
||||
|
||||
def _matches_coupling_map(dag, coupling_map):
|
||||
"""Iterate over circuit gates to check if all multi-qubit couplings
|
||||
match the qubit coupling graph in the backend.
|
||||
|
||||
Parameters:
|
||||
dag (DAGCircuit): DAG representation of circuit.
|
||||
coupling_map (list): Backend coupling map, represented as an adjacency list.
|
||||
|
||||
Returns:
|
||||
bool: True if all gates readily fit the backend coupling graph.
|
||||
False if there's at least one gate that uses multiple qubits
|
||||
which does not match the backend couplings.
|
||||
"""
|
||||
match = True
|
||||
for _, data in dag.multi_graph.nodes(data=True):
|
||||
if data['type'] == 'op':
|
||||
gate_map = [qr[1] for qr in data['qargs']]
|
||||
if len(gate_map) > 1:
|
||||
if gate_map not in coupling_map:
|
||||
match = False
|
||||
break
|
||||
return match
|
||||
|
||||
|
||||
def _pick_best_layout(dag, backend):
|
||||
"""Pick a convenient layout depending on the best matching qubit connectivity
|
||||
|
||||
Parameters:
|
||||
dag (DAGCircuit): DAG representation of circuit.
|
||||
backend (BaseBackend) : The backend with the coupling_map for searching
|
||||
|
||||
Returns:
|
||||
dict: A special ordered initial_layout
|
||||
"""
|
||||
num_qubits = sum([qreg.size for qreg in dag.qregs.values()])
|
||||
best_sub = _best_subset(backend, num_qubits)
|
||||
layout = {}
|
||||
map_iter = 0
|
||||
device_qubits = backend.configuration().n_qubits
|
||||
q = QuantumRegister(device_qubits, 'q')
|
||||
for qreg in dag.qregs.values():
|
||||
for i in range(qreg.size):
|
||||
layout[(qreg.name, i)] = (q, int(best_sub[map_iter]))
|
||||
map_iter += 1
|
||||
return layout
|
||||
|
|
|
@ -16,8 +16,4 @@ class TranspilerError(QiskitError):
|
|||
|
||||
|
||||
class TranspilerAccessError(QiskitError):
|
||||
""" Exception of access error in the transpiler passes. """
|
||||
|
||||
|
||||
class MapperError(QiskitError):
|
||||
""" Exception for cases where a mapper pass cannot map. """
|
||||
"""Exception of access error in the transpiler passes."""
|
||||
|
|
|
@ -17,6 +17,8 @@ from .mapping.barrier_before_final_measurements import BarrierBeforeFinalMeasure
|
|||
from .mapping.check_map import CheckMap
|
||||
from .mapping.cx_direction import CXDirection
|
||||
from .mapping.unroller import Unroller
|
||||
from .mapping.trivial_layout import TrivialLayout
|
||||
from .mapping.dense_layout import DenseLayout
|
||||
from .mapping.basic_swap import BasicSwap
|
||||
from .mapping.lookahead_swap import LookaheadSwap
|
||||
from .mapping.stochastic_swap import StochasticSwap
|
||||
|
|
|
@ -66,9 +66,9 @@ class BasicSwap(TransformationPass):
|
|||
for layer in dag.serial_layers():
|
||||
subdag = layer['graph']
|
||||
|
||||
for a_cx in subdag.get_cnot_nodes():
|
||||
physical_q0 = current_layout[a_cx['qargs'][0]]
|
||||
physical_q1 = current_layout[a_cx['qargs'][1]]
|
||||
for gate in subdag.get_2q_nodes():
|
||||
physical_q0 = current_layout[gate['qargs'][0]]
|
||||
physical_q1 = current_layout[gate['qargs'][1]]
|
||||
if self.coupling_map.distance(physical_q0, physical_q1) != 1:
|
||||
# Insert a new layer with the SWAP(s).
|
||||
swap_layer = DAGCircuit()
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
"""
|
||||
This pass checks if a DAG is mapped to a coupling map.
|
||||
|
||||
It checks that all 2-qubit interactions are laid out to be physically close.
|
||||
"""
|
||||
|
||||
from qiskit.transpiler._basepasses import AnalysisPass
|
||||
|
@ -16,6 +18,8 @@ from qiskit.mapper import Layout
|
|||
class CheckMap(AnalysisPass):
|
||||
"""
|
||||
Checks if a DAGCircuit is mapped to `coupling_map`.
|
||||
|
||||
It checks that all 2-qubit interactions are laid out to be physically close.
|
||||
"""
|
||||
|
||||
def __init__(self, coupling_map, initial_layout=None):
|
||||
|
@ -31,8 +35,8 @@ class CheckMap(AnalysisPass):
|
|||
|
||||
def run(self, dag):
|
||||
"""
|
||||
If `dag` is mapped to `coupling_map`, the property `is_mapped` is
|
||||
set to True (or to False otherwise).
|
||||
If `dag` is mapped to `coupling_map`, the property
|
||||
`is_swap_mapped` is set to True (or to False otherwise).
|
||||
If `dag` is mapped and the direction is correct the property
|
||||
`is_direction_mapped` is set to True (or to False otherwise).
|
||||
|
||||
|
@ -44,17 +48,17 @@ class CheckMap(AnalysisPass):
|
|||
for qreg in dag.qregs.values():
|
||||
self.layout.add_register(qreg)
|
||||
|
||||
self.property_set['is_mapped'] = True
|
||||
self.property_set['is_swap_mapped'] = True
|
||||
self.property_set['is_direction_mapped'] = True
|
||||
|
||||
for layer in dag.serial_layers():
|
||||
subdag = layer['graph']
|
||||
|
||||
for cnot in subdag.get_cnot_nodes():
|
||||
physical_q0 = self.layout[cnot['qargs'][0]]
|
||||
physical_q1 = self.layout[cnot['qargs'][1]]
|
||||
for gate in subdag.get_2q_nodes():
|
||||
physical_q0 = self.layout[gate['qargs'][0]]
|
||||
physical_q1 = self.layout[gate['qargs'][1]]
|
||||
if self.coupling_map.distance(physical_q0, physical_q1) != 1:
|
||||
self.property_set['is_mapped'] = False
|
||||
self.property_set['is_swap_mapped'] = False
|
||||
self.property_set['is_direction_mapped'] = False
|
||||
return
|
||||
else:
|
||||
|
|
|
@ -11,7 +11,7 @@ compatible with the coupling_map.
|
|||
"""
|
||||
|
||||
from qiskit.transpiler._basepasses import TransformationPass
|
||||
from qiskit.transpiler import MapperError
|
||||
from qiskit.transpiler import TranspilerError
|
||||
from qiskit.dagcircuit import DAGCircuit
|
||||
from qiskit.mapper import Layout
|
||||
from qiskit.extensions.standard import HGate
|
||||
|
@ -49,8 +49,8 @@ class CXDirection(TransformationPass):
|
|||
DAGCircuit: The rearranged dag for the coupling map
|
||||
|
||||
Raises:
|
||||
MapperError: If the circuit cannot be mapped just by flipping the
|
||||
cx nodes.
|
||||
TranspilerError: If the circuit cannot be mapped just by flipping the
|
||||
cx nodes.
|
||||
"""
|
||||
new_dag = DAGCircuit()
|
||||
|
||||
|
@ -66,16 +66,16 @@ class CXDirection(TransformationPass):
|
|||
for layer in dag.serial_layers():
|
||||
subdag = layer['graph']
|
||||
|
||||
for cnot in subdag.get_cnot_nodes():
|
||||
|
||||
control = cnot['op'].qargs[0]
|
||||
target = cnot['op'].qargs[1]
|
||||
for cnot_id in subdag.get_named_nodes('cx', 'CX'):
|
||||
cnot_node = subdag.multi_graph.nodes[cnot_id]
|
||||
control = cnot_node['op'].qargs[0]
|
||||
target = cnot_node['op'].qargs[1]
|
||||
|
||||
physical_q0 = self.layout[control]
|
||||
physical_q1 = self.layout[target]
|
||||
if self.coupling_map.distance(physical_q0, physical_q1) != 1:
|
||||
raise MapperError('The circuit requires a connectiontion between the phsycial '
|
||||
'qubits %s and %s' % (physical_q0, physical_q1))
|
||||
raise TranspilerError('The circuit requires a connection between physical '
|
||||
'qubits %s and %s' % (physical_q0, physical_q1))
|
||||
|
||||
if (physical_q0, physical_q1) not in self.coupling_map.get_edges():
|
||||
# A flip needs to be done
|
||||
|
@ -94,7 +94,7 @@ class CXDirection(TransformationPass):
|
|||
subdag.apply_operation_front(HGate(control))
|
||||
|
||||
# Flips the CX
|
||||
cnot['op'].qargs[0], cnot['op'].qargs[1] = target, control
|
||||
cnot_node['op'].qargs[0], cnot_node['op'].qargs[1] = target, control
|
||||
|
||||
new_dag.extend_back(subdag)
|
||||
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2018, IBM.
|
||||
#
|
||||
# This source code is licensed under the Apache License, Version 2.0 found in
|
||||
# the LICENSE.txt file in the root directory of this source tree.
|
||||
|
||||
"""A pass for choosing a Layout of a circuit onto a Coupling graph.
|
||||
|
||||
This pass associates a physical qubit (int) to each virtual qubit
|
||||
of the circuit (tuple(QuantumRegister, int)).
|
||||
|
||||
Note: even though a 'layout' is not strictly a property of the DAG,
|
||||
in the transpiler architecture it is best passed around between passes by
|
||||
being set in `property_set`.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import scipy.sparse as sp
|
||||
import scipy.sparse.csgraph as cs
|
||||
|
||||
from qiskit.mapper import Layout
|
||||
from qiskit.transpiler._basepasses import AnalysisPass
|
||||
from qiskit.transpiler import TranspilerError
|
||||
|
||||
|
||||
class DenseLayout(AnalysisPass):
|
||||
"""
|
||||
Chooses a Layout by finding the most connected subset of qubits.
|
||||
"""
|
||||
|
||||
def __init__(self, coupling_map):
|
||||
"""
|
||||
Chooses a DenseLayout
|
||||
|
||||
Args:
|
||||
coupling_map (Coupling): directed graph representing a coupling map.
|
||||
|
||||
Raises:
|
||||
TranspilerError: if invalid options
|
||||
"""
|
||||
super().__init__()
|
||||
self.coupling_map = coupling_map
|
||||
|
||||
def run(self, dag):
|
||||
"""
|
||||
Pick a convenient layout depending on the best matching
|
||||
qubit connectivity, and set the property `layout`.
|
||||
|
||||
Args:
|
||||
dag (DAGCircuit): DAG to find layout for.
|
||||
|
||||
Raises:
|
||||
TranspilerError: if dag wider than self.coupling_map
|
||||
"""
|
||||
num_dag_qubits = sum([qreg.size for qreg in dag.qregs.values()])
|
||||
if num_dag_qubits > self.coupling_map.size():
|
||||
raise TranspilerError('Number of qubits greater than device.')
|
||||
best_sub = self._best_subset(num_dag_qubits)
|
||||
layout = Layout()
|
||||
map_iter = 0
|
||||
for qreg in dag.qregs.values():
|
||||
for i in range(qreg.size):
|
||||
layout[(qreg, i)] = int(best_sub[map_iter])
|
||||
map_iter += 1
|
||||
self.property_set['layout'] = layout
|
||||
|
||||
def _best_subset(self, n_qubits):
|
||||
"""Computes the qubit mapping with the best connectivity.
|
||||
|
||||
Args:
|
||||
n_qubits (int): Number of subset qubits to consider.
|
||||
|
||||
Returns:
|
||||
ndarray: Array of qubits to use for best connectivity mapping.
|
||||
"""
|
||||
if n_qubits == 1:
|
||||
return np.array([0])
|
||||
|
||||
device_qubits = self.coupling_map.size()
|
||||
|
||||
cmap = np.asarray(self.coupling_map.get_edges())
|
||||
data = np.ones_like(cmap[:, 0])
|
||||
sp_cmap = sp.coo_matrix((data, (cmap[:, 0], cmap[:, 1])),
|
||||
shape=(device_qubits, device_qubits)).tocsr()
|
||||
best = 0
|
||||
best_map = None
|
||||
# do bfs with each node as starting point
|
||||
for k in range(sp_cmap.shape[0]):
|
||||
bfs = cs.breadth_first_order(sp_cmap, i_start=k, directed=False,
|
||||
return_predecessors=False)
|
||||
|
||||
connection_count = 0
|
||||
for i in range(n_qubits):
|
||||
node_idx = bfs[i]
|
||||
for j in range(sp_cmap.indptr[node_idx],
|
||||
sp_cmap.indptr[node_idx + 1]):
|
||||
node = sp_cmap.indices[j]
|
||||
for counter in range(n_qubits):
|
||||
if node == bfs[counter]:
|
||||
connection_count += 1
|
||||
break
|
||||
|
||||
if connection_count > best:
|
||||
best = connection_count
|
||||
best_map = bfs[0:n_qubits]
|
||||
return best_map
|
|
@ -46,8 +46,9 @@ from qiskit import QuantumRegister
|
|||
from qiskit.dagcircuit import DAGCircuit
|
||||
from qiskit.extensions.standard import SwapGate
|
||||
from qiskit.transpiler._basepasses import TransformationPass
|
||||
from qiskit.mapper import Layout, MapperError
|
||||
from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements
|
||||
from qiskit.transpiler import TranspilerError
|
||||
from qiskit.mapper import Layout
|
||||
|
||||
SEARCH_DEPTH = 4
|
||||
SEARCH_WIDTH = 4
|
||||
|
@ -74,18 +75,18 @@ class LookaheadSwap(TransformationPass):
|
|||
dag (DAGCircuit): the directed acyclic graph to be mapped
|
||||
Returns:
|
||||
DAGCircuit: A dag mapped to be compatible with the coupling_map in
|
||||
the property_set.
|
||||
the property_set.
|
||||
Raises:
|
||||
MapperError: If the provided DAG has more qubits than are available
|
||||
in the coupling map.
|
||||
|
||||
TranspilerError: If the provided DAG has more qubits than are
|
||||
available in the coupling map.
|
||||
"""
|
||||
|
||||
coupling_map = self._coupling_map
|
||||
ordered_virtual_gates = list(dag.serial_layers())
|
||||
|
||||
if len(dag.get_qubits()) > len(coupling_map.physical_qubits):
|
||||
raise MapperError('DAG contains more qubits than are present in the coupling map.')
|
||||
raise TranspilerError('DAG contains more qubits than are '
|
||||
'present in the coupling map.')
|
||||
|
||||
dag_qubits = dag.get_qubits()
|
||||
coupling_qubits = coupling_map.physical_qubits
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2018, IBM.
|
||||
#
|
||||
# This source code is licensed under the Apache License, Version 2.0 found in
|
||||
# the LICENSE.txt file in the root directory of this source tree.
|
||||
|
||||
"""A pass for choosing a Layout of a circuit onto a Coupling graph, using a simple
|
||||
round-robin order.
|
||||
|
||||
This pass associates a physical qubit (int) to each virtual qubit
|
||||
of the circuit (tuple(QuantumRegister, int)) in increasing order.
|
||||
"""
|
||||
|
||||
from qiskit.mapper import Layout
|
||||
from qiskit.transpiler._basepasses import AnalysisPass
|
||||
from qiskit.transpiler import TranspilerError
|
||||
|
||||
|
||||
class TrivialLayout(AnalysisPass):
|
||||
"""
|
||||
Chooses a Layout by assigning n circuit qubits to device qubits 0, .., n-1.
|
||||
|
||||
Does not assume any ancilla.
|
||||
"""
|
||||
|
||||
def __init__(self, coupling_map):
|
||||
"""
|
||||
Choose a TrivialLayout.
|
||||
|
||||
Args:
|
||||
coupling_map (Coupling): directed graph representing a coupling map.
|
||||
|
||||
Raises:
|
||||
TranspilerError: if invalid options
|
||||
"""
|
||||
super().__init__()
|
||||
self.coupling_map = coupling_map
|
||||
|
||||
def run(self, dag):
|
||||
"""
|
||||
Pick a convenient layout depending on the best matching
|
||||
qubit connectivity, and set the property `layout`.
|
||||
|
||||
Args:
|
||||
dag (DAGCircuit): DAG to find layout for.
|
||||
|
||||
Raises:
|
||||
TranspilerError: if dag wider than self.coupling_map
|
||||
"""
|
||||
num_dag_qubits = sum([qreg.size for qreg in dag.qregs.values()])
|
||||
if num_dag_qubits > self.coupling_map.size():
|
||||
raise TranspilerError('Number of qubits greater than device.')
|
||||
layout = Layout()
|
||||
for qreg in dag.qregs.values():
|
||||
layout.add_register(qreg)
|
||||
self.property_set['layout'] = layout
|
|
@ -16,7 +16,7 @@ import nbformat
|
|||
from nbconvert.preprocessors import ExecutePreprocessor
|
||||
from qiskit.tools.visualization._matplotlib import HAS_MATPLOTLIB
|
||||
from ...common import (Path, QiskitTestCase, requires_qe_access,
|
||||
requires_cpp_simulator)
|
||||
requires_cpp_simulator, slow_test)
|
||||
|
||||
|
||||
# Timeout (in seconds) for a single notebook.
|
||||
|
@ -62,6 +62,7 @@ class TestJupyter(QiskitTestCase):
|
|||
|
||||
@unittest.skipIf(not HAS_MATPLOTLIB, 'matplotlib not available.')
|
||||
@requires_qe_access
|
||||
@slow_test
|
||||
def test_backend_tools(self, qe_token, qe_url):
|
||||
"Test Jupyter backend tools."
|
||||
self._execute_notebook(self._get_resource_path(
|
||||
|
|
|
@ -36,7 +36,7 @@ class TestCheckMap(QiskitTestCase):
|
|||
dag = circuit_to_dag(circuit)
|
||||
pass_ = CheckMap(coupling)
|
||||
pass_.run(dag)
|
||||
self.assertTrue(pass_.property_set['is_mapped'])
|
||||
self.assertTrue(pass_.property_set['is_swap_mapped'])
|
||||
self.assertTrue(pass_.property_set['is_direction_mapped'])
|
||||
|
||||
def test_true_map(self):
|
||||
|
@ -60,7 +60,7 @@ class TestCheckMap(QiskitTestCase):
|
|||
pass_ = CheckMap(coupling)
|
||||
pass_.run(dag)
|
||||
|
||||
self.assertTrue(pass_.property_set['is_mapped'])
|
||||
self.assertTrue(pass_.property_set['is_swap_mapped'])
|
||||
self.assertTrue(pass_.property_set['is_direction_mapped'])
|
||||
|
||||
def test_true_map_in_same_layer(self):
|
||||
|
@ -85,7 +85,7 @@ class TestCheckMap(QiskitTestCase):
|
|||
pass_ = CheckMap(coupling)
|
||||
pass_.run(dag)
|
||||
|
||||
self.assertTrue(pass_.property_set['is_mapped'])
|
||||
self.assertTrue(pass_.property_set['is_swap_mapped'])
|
||||
self.assertTrue(pass_.property_set['is_direction_mapped'])
|
||||
|
||||
def test_false_map(self):
|
||||
|
@ -105,7 +105,7 @@ class TestCheckMap(QiskitTestCase):
|
|||
pass_ = CheckMap(coupling)
|
||||
pass_.run(dag)
|
||||
|
||||
self.assertFalse(pass_.property_set['is_mapped'])
|
||||
self.assertFalse(pass_.property_set['is_swap_mapped'])
|
||||
self.assertFalse(pass_.property_set['is_direction_mapped'])
|
||||
|
||||
def test_true_map_undirected(self):
|
||||
|
@ -129,7 +129,7 @@ class TestCheckMap(QiskitTestCase):
|
|||
pass_ = CheckMap(coupling)
|
||||
pass_.run(dag)
|
||||
|
||||
self.assertTrue(pass_.property_set['is_mapped'])
|
||||
self.assertTrue(pass_.property_set['is_swap_mapped'])
|
||||
self.assertFalse(pass_.property_set['is_direction_mapped'])
|
||||
|
||||
def test_true_map_in_same_layer_undirected(self):
|
||||
|
@ -154,7 +154,7 @@ class TestCheckMap(QiskitTestCase):
|
|||
pass_ = CheckMap(coupling)
|
||||
pass_.run(dag)
|
||||
|
||||
self.assertTrue(pass_.property_set['is_mapped'])
|
||||
self.assertTrue(pass_.property_set['is_swap_mapped'])
|
||||
self.assertFalse(pass_.property_set['is_direction_mapped'])
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2018, IBM.
|
||||
#
|
||||
# This source code is licensed under the Apache License, Version 2.0 found in
|
||||
# the LICENSE.txt file in the root directory of this source tree.
|
||||
|
||||
"""Test the DenseLayout pass"""
|
||||
|
||||
import unittest
|
||||
|
||||
from qiskit import QuantumRegister, QuantumCircuit
|
||||
from qiskit.mapper import CouplingMap
|
||||
from qiskit.transpiler.passes import DenseLayout
|
||||
from qiskit.converters import circuit_to_dag
|
||||
from ..common import QiskitTestCase
|
||||
|
||||
|
||||
class TestDenseLayout(QiskitTestCase):
|
||||
"""Tests the DenseLayout pass"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
0 = 1 = 2 = 3 4
|
||||
|
||||
|| || || || X ||
|
||||
|
||||
5 = 6 = 7 = 8 = 9
|
||||
|
||||
|| X || || X ||
|
||||
|
||||
10 = 11 = 12 = 13 = 14
|
||||
|
||||
|| || X || X ||
|
||||
|
||||
15 = 16 = 17 18 19
|
||||
"""
|
||||
self.cmap20 = [[0, 1], [0, 5], [1, 0], [1, 2], [1, 6], [2, 1],
|
||||
[2, 3], [2, 6], [3, 2], [3, 8], [3, 9], [4, 8], [4, 9],
|
||||
[5, 0], [5, 6], [5, 10], [5, 11], [6, 1], [6, 2], [6, 5],
|
||||
[6, 7], [6, 10], [6, 11], [7, 1], [7, 6], [7, 8], [7, 12],
|
||||
[7, 13], [8, 3], [8, 4], [8, 7], [8, 9], [8, 12], [8, 13],
|
||||
[9, 3], [9, 4], [9, 8], [10, 5], [10, 6], [10, 11], [10, 15],
|
||||
[11, 5], [11, 6], [11, 10], [11, 12], [11, 16], [11, 17],
|
||||
[12, 7], [12, 8], [12, 11], [12, 13], [12, 16], [13, 7],
|
||||
[13, 8], [13, 12], [13, 14], [13, 18], [13, 19], [14, 13],
|
||||
[14, 18], [14, 19], [15, 10], [15, 16], [16, 11], [16, 12],
|
||||
[16, 15], [16, 17], [17, 11], [17, 16], [18, 13], [18, 14],
|
||||
[19, 13], [19, 14]]
|
||||
|
||||
def test_5q_circuit_20q_coupling(self):
|
||||
"""Test finds dense 5q corner in 20q coupling map.
|
||||
"""
|
||||
qr = QuantumRegister(5, 'q')
|
||||
circuit = QuantumCircuit(qr)
|
||||
circuit.cx(qr[0], qr[3])
|
||||
circuit.cx(qr[3], qr[4])
|
||||
circuit.cx(qr[3], qr[1])
|
||||
circuit.cx(qr[0], qr[2])
|
||||
|
||||
dag = circuit_to_dag(circuit)
|
||||
pass_ = DenseLayout(CouplingMap(self.cmap20))
|
||||
pass_.run(dag)
|
||||
|
||||
layout = pass_.property_set['layout']
|
||||
self.assertEqual(layout[qr[0]], 5)
|
||||
self.assertEqual(layout[qr[1]], 0)
|
||||
self.assertEqual(layout[qr[2]], 6)
|
||||
self.assertEqual(layout[qr[3]], 10)
|
||||
self.assertEqual(layout[qr[4]], 11)
|
||||
|
||||
def test_6q_circuit_20q_coupling(self):
|
||||
"""Test finds dense 5q corner in 20q coupling map.
|
||||
"""
|
||||
qr0 = QuantumRegister(3, 'q0')
|
||||
qr1 = QuantumRegister(3, 'q1')
|
||||
circuit = QuantumCircuit(qr0, qr1)
|
||||
circuit.cx(qr0[0], qr1[2])
|
||||
circuit.cx(qr1[1], qr0[2])
|
||||
|
||||
dag = circuit_to_dag(circuit)
|
||||
pass_ = DenseLayout(CouplingMap(self.cmap20))
|
||||
pass_.run(dag)
|
||||
|
||||
layout = pass_.property_set['layout']
|
||||
self.assertEqual(layout[qr0[0]], 5)
|
||||
self.assertEqual(layout[qr0[1]], 0)
|
||||
self.assertEqual(layout[qr0[2]], 6)
|
||||
self.assertEqual(layout[qr1[0]], 10)
|
||||
self.assertEqual(layout[qr1[1]], 11)
|
||||
self.assertEqual(layout[qr1[2]], 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -10,7 +10,7 @@
|
|||
import unittest
|
||||
|
||||
from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit
|
||||
from qiskit.transpiler import MapperError
|
||||
from qiskit.transpiler import TranspilerError
|
||||
from qiskit.mapper import CouplingMap
|
||||
from qiskit.transpiler.passes import CXDirection
|
||||
from qiskit.converters import circuit_to_dag
|
||||
|
@ -59,7 +59,7 @@ class TestCXDirection(QiskitTestCase):
|
|||
|
||||
pass_ = CXDirection(coupling)
|
||||
|
||||
with self.assertRaises(MapperError):
|
||||
with self.assertRaises(TranspilerError):
|
||||
pass_.run(dag)
|
||||
|
||||
def test_direction_correct(self):
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2018, IBM.
|
||||
#
|
||||
# This source code is licensed under the Apache License, Version 2.0 found in
|
||||
# the LICENSE.txt file in the root directory of this source tree.
|
||||
|
||||
"""Test the TrivialLayout pass"""
|
||||
|
||||
import unittest
|
||||
|
||||
from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit
|
||||
from qiskit.mapper import CouplingMap
|
||||
from qiskit.transpiler.passes import TrivialLayout
|
||||
from qiskit.transpiler import TranspilerError
|
||||
from qiskit.converters import circuit_to_dag
|
||||
from ..common import QiskitTestCase
|
||||
|
||||
|
||||
class TestDenseLayout(QiskitTestCase):
|
||||
"""Tests the TrivialLayout pass"""
|
||||
|
||||
def setUp(self):
|
||||
self.cmap5 = CouplingMap([[1, 0], [2, 0], [2, 1], [3, 2], [3, 4], [4, 2]])
|
||||
|
||||
self.cmap16 = CouplingMap([[1, 0], [1, 2], [2, 3], [3, 4], [3, 14], [5, 4],
|
||||
[6, 5], [6, 7], [6, 11], [7, 10], [8, 7], [9, 8],
|
||||
[9, 10], [11, 10], [12, 5], [12, 11], [12, 13],
|
||||
[13, 4], [13, 14], [15, 0], [15, 2], [15, 14]])
|
||||
|
||||
def test_3q_circuit_5q_coupling(self):
|
||||
"""Test finds trivial layout for 3q circuit on 5q device.
|
||||
"""
|
||||
qr = QuantumRegister(3, 'q')
|
||||
circuit = QuantumCircuit(qr)
|
||||
circuit.cx(qr[1], qr[0])
|
||||
circuit.cx(qr[0], qr[2])
|
||||
circuit.cx(qr[1], qr[2])
|
||||
|
||||
dag = circuit_to_dag(circuit)
|
||||
pass_ = TrivialLayout(self.cmap5)
|
||||
pass_.run(dag)
|
||||
layout = pass_.property_set['layout']
|
||||
|
||||
for i in range(3):
|
||||
self.assertEqual(layout[qr[i]], i)
|
||||
|
||||
def test_9q_circuit_16q_coupling(self):
|
||||
"""Test finds trivial layout for 9q circuit with 2 registers on 16q device.
|
||||
"""
|
||||
qr0 = QuantumRegister(4, 'q0')
|
||||
qr1 = QuantumRegister(5, 'q1')
|
||||
cr = ClassicalRegister(2, 'c')
|
||||
circuit = QuantumCircuit(qr0, qr1, cr)
|
||||
circuit.cx(qr0[1], qr0[2])
|
||||
circuit.cx(qr0[0], qr1[3])
|
||||
circuit.cx(qr1[4], qr0[2])
|
||||
circuit.measure(qr1[1], cr[0])
|
||||
circuit.measure(qr0[2], cr[1])
|
||||
|
||||
dag = circuit_to_dag(circuit)
|
||||
pass_ = TrivialLayout(self.cmap16)
|
||||
pass_.run(dag)
|
||||
layout = pass_.property_set['layout']
|
||||
|
||||
for i in range(4):
|
||||
self.assertEqual(layout[qr0[i]], i)
|
||||
for i in range(5):
|
||||
self.assertEqual(layout[qr1[i]], i+4)
|
||||
|
||||
def test_raises_wider_circuit(self):
|
||||
"""Test error is raised if the circuit is wider than coupling map.
|
||||
"""
|
||||
qr0 = QuantumRegister(3, 'q0')
|
||||
qr1 = QuantumRegister(3, 'q1')
|
||||
circuit = QuantumCircuit(qr0, qr1)
|
||||
circuit.cx(qr0, qr1)
|
||||
|
||||
dag = circuit_to_dag(circuit)
|
||||
with self.assertRaises(TranspilerError):
|
||||
pass_ = TrivialLayout(self.cmap5)
|
||||
pass_.run(dag)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Loading…
Reference in New Issue