From 350d8482ec19a698267279043cd164bb2818b9c4 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Thu, 30 Apr 2020 04:59:32 -0400 Subject: [PATCH 01/34] add SABRE swap pass --- qiskit/circuit/bit.py | 2 +- qiskit/dagcircuit/dagcircuit.py | 42 ++- qiskit/transpiler/coupling.py | 16 + qiskit/transpiler/passes/__init__.py | 2 + qiskit/transpiler/passes/routing/__init__.py | 1 + .../transpiler/passes/routing/sabre_swap.py | 320 ++++++++++++++++++ 6 files changed, 368 insertions(+), 15 deletions(-) create mode 100644 qiskit/transpiler/passes/routing/sabre_swap.py diff --git a/qiskit/circuit/bit.py b/qiskit/circuit/bit.py index be6cec0305b9..618f8878defb 100644 --- a/qiskit/circuit/bit.py +++ b/qiskit/circuit/bit.py @@ -70,7 +70,7 @@ def index(self, value): def __repr__(self): """Return the official string representing the bit.""" - return "%s(%s, %s)" % (self.__class__.__name__, self._register, self._index) + return "%s[%s]" % (self._register.name, self._index) def __hash__(self): return self._hash diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index bac79bacef9a..29631bce23fb 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1097,6 +1097,16 @@ def predecessors(self, node): """Returns iterator of the predecessors of a node as DAGNodes.""" raise NotImplementedError() + def quantum_successors(self, node): + """Returns iterator of the successors of a node that are + connected by a qubit edge.""" + for successor in self.successors(node): + if any(isinstance(x['wire'], Qubit) + for x in + self._get_all_multi_graph_edges( + node._node_id, successor._node_id)): + yield successor + def quantum_predecessors(self, node): """Returns iterator of the predecessors of a node that are connected by a quantum edge as DAGNodes.""" @@ -1122,16 +1132,6 @@ def bfs_successors(self, node): """ raise NotImplementedError() - def quantum_successors(self, node): - """Returns iterator of the successors of a node that are - connected by a quantum edge as DAGNodes.""" - for successor in self.successors(node): - if any(isinstance(x['wire'], Qubit) - for x in - self._get_all_multi_graph_edges( - node._node_id, successor._node_id)): - yield successor - def remove_op_node(self, node): """Remove an operation node n. @@ -1182,6 +1182,20 @@ def remove_nondescendants_of(self, node): 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 +1206,9 @@ def layers(self): 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 +1228,7 @@ def layers(self): # 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) diff --git a/qiskit/transpiler/coupling.py b/qiskit/transpiler/coupling.py index 519c7c7f7f5b..6df11359604e 100644 --- a/qiskit/transpiler/coupling.py +++ b/qiskit/transpiler/coupling.py @@ -134,6 +134,11 @@ def is_connected(self): except nx.exception.NetworkXException: return False + def neighbors(self, physical_qubit): + """Return the nearest neighbors of a physical qubit. + """ + return self.graph.neighbors(physical_qubit) + def _compute_distance_matrix(self): """Compute the full distance matrix on pairs of nodes. @@ -201,6 +206,17 @@ def is_symmetric(self): 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 diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index bdf213e6539a..e723cf5c1f24 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -44,6 +44,7 @@ BasicSwap LookaheadSwap StochasticSwap + SabreSwap Basis Change ============ @@ -119,6 +120,7 @@ from .routing import LayoutTransformation from .routing import LookaheadSwap from .routing import StochasticSwap +from .routing import SabreSwap # basis change from .basis import Decompose diff --git a/qiskit/transpiler/passes/routing/__init__.py b/qiskit/transpiler/passes/routing/__init__.py index c32baa354706..c4085d72da32 100644 --- a/qiskit/transpiler/passes/routing/__init__.py +++ b/qiskit/transpiler/passes/routing/__init__.py @@ -18,3 +18,4 @@ from .layout_transformation import LayoutTransformation from .lookahead_swap import LookaheadSwap from .stochastic_swap import StochasticSwap +from .sabre_swap import SabreSwap diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py new file mode 100644 index 000000000000..c7f1ae894772 --- /dev/null +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -0,0 +1,320 @@ +# -*- 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 + +from qiskit.circuit.quantumregister import QuantumRegister +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() + +W = 0.5 # Weight of extened_set (lookahead window) compared to front_layer. +DELTA = 0.001 # Decay cooefficient for penalizing serial swaps. + +class SabreSwap(TransformationPass): + """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 `_ + """ + + def __init__(self, coupling_map, heuristic): + """SabreSwap initializer. + + Args: + coupling_map (CouplingMap): CouplingMap of the target backend. + """ + + super().__init__() + self.coupling_map = coupling_map + self.heuristic = heuristic + + 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()) > len(self.coupling_map.physical_qubits): + raise TranspilerError('More virtual qubits exist than physical qubits.') + + # 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) + + # Set the max number of gates in the lookahead window to dag width. + EXTENDED_SET_SIZE = 4 # len(current_layout) + + # Start algorithm from the front layer and iterate until all gates done. + front_layer = dag.front_layer() + applied_gates = set() + while front_layer: + logger.debug('Top-level routing step: %d gates remaining.', + 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) + applied_gates.add(node) + for successor in dag.quantum_successors(node): + if successor.type != 'op': + continue + elif _is_resolved(successor, dag, applied_gates): + front_layer.append(successor) + + # diagnostics + for node in execute_gate_list: + print('free! ', node.name, node.qargs) + print('front_layer: ', [(n.name, n.qargs) for n in front_layer]) + + continue + + # After all free gates are exhausted, heuristically find + # the best swap and insert it. + else: + extended_set = _obtain_extended_set(dag, front_layer, + EXTENDED_SET_SIZE) + + swap_candidate_list = _obtain_swaps(front_layer, current_layout, + self.coupling_map) + + swap_scores = [] + for i, swap in enumerate(swap_candidate_list): + trial_layout = current_layout.copy() + trial_layout.swap(*swap) + score = _score_heuristic(front_layer, + extended_set, + trial_layout, + self.coupling_map, + self.heuristic) + swap_scores.append(score) + min_score = min(swap_scores) + best_swap = swap_candidate_list[swap_scores.index(min_score)] + 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) + + # diagnostics + print() + print('<===== SWAP Selection =====>') + print('extended_set: ', [(n.name, n.qargs) for n in extended_set]) + print('swap scores:') + for i in range(len(swap_scores)): + print('\t%s: %s' % (swap_candidate_list[i], swap_scores[i])) + print('\tbest swap: ', best_swap) + print('updated layout:') + print(current_layout) + print() + + return mapped_dag + + +def _is_resolved(node, dag, applied_gates): + """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) + if all([n in applied_gates for n in predecessors]): + return True + else: + return False + + +def _obtain_extended_set(dag, front_layer, window_size): + """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) >= window_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) < window_size: + try: + extended_set.add(next(successors)) + except StopIteration: + break + + return extended_set + + +def _obtain_swaps(front_layer, current_layout, coupling_map): + """Return a list 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 = set() + for node in front_layer: + for virtual in node.qargs: + physical = current_layout[virtual] + for neighbor in coupling_map.neighbors(physical): + virtual_neighbor = current_layout[neighbor] + candidate_swaps.add((virtual, virtual_neighbor)) # TODO: sort so i,j is seen once. + + return list(candidate_swaps) + + +def _score_heuristic(front_layer, extended_set, layout, coupling_map, heuristic): + """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. + + Basic cost function: + 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)] + + Advanced cost function: + 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. |E| number of upcoming successors to gates in + front_layer F). This is weighted by some amount W to signify that + upcoming gates are less important that the front_layer. + The whole cost is multiplied by a decay factor. This increases the cost + if the SWAP that generated this trial layout was recently used (i.e. + it penalizes increase in depth). + + .. math:: + + H_{advanced} = 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)] + } + """ + # TODO: add decay + if heuristic == 'basic': + return sum(coupling_map.distance(*[layout[q] for q in node.qargs]) + for node in front_layer) + elif heuristic == 'advanced': + W = 0.5 + + first_cost = _score_heuristic(front_layer, [], layout, coupling_map, 'basic') + first_cost /= len(front_layer) + + second_cost = _score_heuristic(extended_set, [], layout, coupling_map, 'basic') + second_cost = 0.0 if not extended_set else second_cost / len(extended_set) + + return first_cost + W * second_cost + 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 From a08323d70d055791be38cf7f98dcbf25d2ea5fcc Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Sun, 3 May 2020 06:45:45 -0400 Subject: [PATCH 02/34] add SABRE layout bidirectional search pass --- qiskit/transpiler/passes/__init__.py | 2 + qiskit/transpiler/passes/layout/__init__.py | 1 + .../transpiler/passes/layout/sabre_layout.py | 145 ++++++++++++++++++ .../passes/routing/lookahead_swap.py | 2 +- .../transpiler/passes/routing/sabre_swap.py | 39 +++-- 5 files changed, 168 insertions(+), 21 deletions(-) create mode 100644 qiskit/transpiler/passes/layout/sabre_layout.py diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index e723cf5c1f24..2179bac17a83 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -29,6 +29,7 @@ TrivialLayout DenseLayout NoiseAdaptiveLayout + SabreLayout CSPLayout ApplyLayout Layout2qDistance @@ -109,6 +110,7 @@ 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 diff --git a/qiskit/transpiler/passes/layout/__init__.py b/qiskit/transpiler/passes/layout/__init__.py index 1461b3cc0eca..dcbf9c77cba3 100644 --- a/qiskit/transpiler/passes/layout/__init__.py +++ b/qiskit/transpiler/passes/layout/__init__.py @@ -18,6 +18,7 @@ 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 diff --git a/qiskit/transpiler/passes/layout/sabre_layout.py b/qiskit/transpiler/passes/layout/sabre_layout.py new file mode 100644 index 000000000000..8c9916868f2b --- /dev/null +++ b/qiskit/transpiler/passes/layout/sabre_layout.py @@ -0,0 +1,145 @@ +# -*- 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.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 `_ + """ + + 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. + 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 + """ + from qiskit.converters import dag_to_circuit + from qiskit.transpiler.passes.layout import SetLayout + from qiskit.transpiler.passes.layout import FullAncillaAllocation + from qiskit.transpiler.passes.layout import EnlargeWithAncilla + from qiskit.transpiler.passes.layout import ApplyLayout + from qiskit.transpiler.passes.routing import SabreSwap + from qiskit.transpiler.passmanager import PassManager + + def _layout_and_route_passmanager(): + """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(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) + 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) + + 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, 'advanced') + + # Do forward-backward iterations. + circ = dag_to_circuit(dag) + for i in range(self.max_iterations): + for _ in ('forward', 'backward'): + pm = _layout_and_route_passmanager() + new_circ = pm.run(circ) + + # Update initial layout and mirror the unmapped circuit. + pass_final_layout = pm.property_set['final_layout'] + final_layout = _compose_layouts(initial_layout, + pass_final_layout, + circ.qregs) + initial_layout = final_layout + circ = circ.mirror() + + # Diagnostics + logger.info('After round %d, num_swaps: %d' % + (i+1, new_circ.count_ops().get('swap'))) + logger.info('new initial layout') + logger.info(initial_layout) + + self.property_set['layout'] = initial_layout diff --git a/qiskit/transpiler/passes/routing/lookahead_swap.py b/qiskit/transpiler/passes/routing/lookahead_swap.py index 95ce69a17716..4fcadc1c254e 100644 --- a/qiskit/transpiler/passes/routing/lookahead_swap.py +++ b/qiskit/transpiler/passes/routing/lookahead_swap.py @@ -25,7 +25,7 @@ from qiskit.transpiler.layout import Layout from qiskit.dagcircuit import DAGNode -logger = logging.getLogger() +logger = logging.getLogger(__name__) class LookaheadSwap(TransformationPass): diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index c7f1ae894772..d4e039ac8ea3 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -26,11 +26,12 @@ from qiskit.transpiler.layout import Layout from qiskit.dagcircuit import DAGNode -logger = logging.getLogger() +logger = logging.getLogger(__name__) W = 0.5 # Weight of extened_set (lookahead window) compared to front_layer. DELTA = 0.001 # Decay cooefficient for penalizing serial swaps. + class SabreSwap(TransformationPass): """Map input circuit onto a backend topology via insertion of SWAPs. @@ -88,8 +89,8 @@ def run(self, 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()) > len(self.coupling_map.physical_qubits): - raise TranspilerError('More virtual qubits exist than physical qubits.') + if len(dag.qubits()) > self.coupling_map.size(): + raise TranspilerError('More virtual qubits exist than physical.') # Preserve input DAG's name, regs, wire_map, etc. but replace the graph. mapped_dag = _copy_circuit_metadata(dag) @@ -107,8 +108,6 @@ def run(self, dag): front_layer = dag.front_layer() applied_gates = set() while front_layer: - logger.debug('Top-level routing step: %d gates remaining.', - front_layer) execute_gate_list = [] # Remove as many immediately applicable gates as possible @@ -136,10 +135,11 @@ def run(self, dag): elif _is_resolved(successor, dag, applied_gates): front_layer.append(successor) - # diagnostics - for node in execute_gate_list: - print('free! ', node.name, node.qargs) - print('front_layer: ', [(n.name, n.qargs) for n in front_layer]) + # 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 @@ -169,17 +169,16 @@ def run(self, dag): mapped_dag.apply_operation_back(swap_node.op, swap_node.qargs) current_layout.swap(*best_swap) - # diagnostics - print() - print('<===== SWAP Selection =====>') - print('extended_set: ', [(n.name, n.qargs) for n in extended_set]) - print('swap scores:') - for i in range(len(swap_scores)): - print('\t%s: %s' % (swap_candidate_list[i], swap_scores[i])) - print('\tbest swap: ', best_swap) - print('updated layout:') - print(current_layout) - print() + # 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_candidate_list[i], swap_scores[i]) + for i in range(len(swap_scores))]) + logger.debug('best swap: %s', best_swap) + + self.property_set['final_layout'] = current_layout return mapped_dag From 5fb7a47c9e18ff9eb9418363bfc6055de2639074 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Mon, 4 May 2020 22:21:35 -0400 Subject: [PATCH 03/34] expose sabre via preset passmanagers --- qiskit/compiler/transpile.py | 4 ++-- qiskit/transpiler/preset_passmanagers/level0.py | 6 ++++++ qiskit/transpiler/preset_passmanagers/level1.py | 6 ++++++ qiskit/transpiler/preset_passmanagers/level2.py | 6 ++++++ qiskit/transpiler/preset_passmanagers/level3.py | 6 ++++++ 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/qiskit/compiler/transpile.py b/qiskit/compiler/transpile.py index 95b8f047670f..bd2d1fa91fc9 100644 --- a/qiskit/compiler/transpile.py +++ b/qiskit/compiler/transpile.py @@ -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, diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 5c2655bf44f8..03011af6b863 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -28,10 +28,12 @@ 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 _choose_layout_condition(property_set): _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 _swap_condition(property_set): _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')] else: raise TranspilerError("Invalid routing method %s." % routing_method) diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index bd72419479d9..662f4176a908 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -29,10 +29,12 @@ 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 _choose_layout_condition(property_set): _improve_layout = DenseLayout(coupling_map, backend_properties) elif layout_method == 'noise_adaptive': _improve_layout = NoiseAdaptiveLayout(backend_properties) + elif layout_method == 'sabre': + _choose_layout = SabreLayout(coupling_map, max_iterations=2, seed=seed_transpiler) else: raise TranspilerError("Invalid layout method %s." % layout_method) @@ -122,6 +126,8 @@ def _swap_condition(property_set): _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='advanced')] else: raise TranspilerError("Invalid routing method %s." % routing_method) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index f04d842cfdac..35131cfb938c 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -30,10 +30,12 @@ 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 _choose_layout_condition(property_set): _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 = SabreLayout(coupling_map, max_iterations=2, seed=seed_transpiler) else: raise TranspilerError("Invalid layout method %s." % layout_method) @@ -117,6 +121,8 @@ def _swap_condition(property_set): _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='advanced')] else: raise TranspilerError("Invalid routing method %s." % routing_method) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 9ca72586b716..35b79a7dc6bd 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -30,10 +30,12 @@ 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 @@ -102,6 +104,8 @@ def _choose_layout_condition(property_set): _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) @@ -121,6 +125,8 @@ def _swap_condition(property_set): _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='advanced')] else: raise TranspilerError("Invalid routing method %s." % routing_method) From 6591ba121d8a80edb5434b132c73f3c9d2709672 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Mon, 4 May 2020 22:53:28 -0400 Subject: [PATCH 04/34] undo deprecation for Layout.combine_into_edge_map --- qiskit/transpiler/layout.py | 4 ---- qiskit/transpiler/passes/layout/sabre_layout.py | 2 +- qiskit/transpiler/preset_passmanagers/level1.py | 2 +- qiskit/transpiler/preset_passmanagers/level2.py | 2 +- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/qiskit/transpiler/layout.py b/qiskit/transpiler/layout.py index ba53a917b33c..3c765f300ecf 100644 --- a/qiskit/transpiler/layout.py +++ b/qiskit/transpiler/layout.py @@ -224,10 +224,6 @@ def combine_into_edge_map(self, another_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(): diff --git a/qiskit/transpiler/passes/layout/sabre_layout.py b/qiskit/transpiler/passes/layout/sabre_layout.py index 8c9916868f2b..323f18f58e1e 100644 --- a/qiskit/transpiler/passes/layout/sabre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_layout.py @@ -138,7 +138,7 @@ def _compose_layouts(initial_layout, pass_final_layout, qregs): # Diagnostics logger.info('After round %d, num_swaps: %d' % - (i+1, new_circ.count_ops().get('swap'))) + (i+1, new_circ.count_ops().get('swap', 0))) logger.info('new initial layout') logger.info(initial_layout) diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 662f4176a908..0a66961926ed 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -99,7 +99,7 @@ def _choose_layout_condition(property_set): elif layout_method == 'noise_adaptive': _improve_layout = NoiseAdaptiveLayout(backend_properties) elif layout_method == 'sabre': - _choose_layout = SabreLayout(coupling_map, max_iterations=2, seed=seed_transpiler) + _improve_layout = SabreLayout(coupling_map, max_iterations=2, seed=seed_transpiler) else: raise TranspilerError("Invalid layout method %s." % layout_method) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 35131cfb938c..0206e57b74c0 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -98,7 +98,7 @@ def _choose_layout_condition(property_set): elif layout_method == 'noise_adaptive': _choose_layout_2 = NoiseAdaptiveLayout(backend_properties) elif layout_method == 'sabre': - _choose_layout = SabreLayout(coupling_map, max_iterations=2, seed=seed_transpiler) + _choose_layout_2 = SabreLayout(coupling_map, max_iterations=2, seed=seed_transpiler) else: raise TranspilerError("Invalid layout method %s." % layout_method) From c7fffb299631ab8674fa2b8933ae1b4b57e77beb Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Tue, 5 May 2020 18:35:32 -0400 Subject: [PATCH 05/34] add Approx2qDecompose and SimplifyU3 passes --- qiskit/transpiler/passes/__init__.py | 3 + qiskit/transpiler/passes/basis/__init__.py | 1 + .../passes/basis/approx_2q_decompose.py | 42 ++++++++ .../passes/optimization/__init__.py | 1 + .../passes/optimization/simplify_u3.py | 100 ++++++++++++++++++ .../transpiler/preset_passmanagers/level3.py | 53 ++++++---- 6 files changed, 180 insertions(+), 20 deletions(-) create mode 100644 qiskit/transpiler/passes/basis/approx_2q_decompose.py create mode 100644 qiskit/transpiler/passes/optimization/simplify_u3.py diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 2179bac17a83..5291482f8749 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -74,6 +74,7 @@ RemoveDiagonalGatesBeforeMeasure RemoveResetInZeroState CrosstalkAdaptiveSchedule + SimplifyU3 Circuit Analysis ================ @@ -130,6 +131,7 @@ from .basis import UnrollCustomDefinitions from .basis import Unroll3qOrMore from .basis import BasisTranslator +from .basis import Approx2qDecompose # optimization from .optimization import Optimize1qGates @@ -143,6 +145,7 @@ from .optimization import RemoveDiagonalGatesBeforeMeasure from .optimization import CrosstalkAdaptiveSchedule from .optimization import HoareOptimizer +from .optimization import SimplifyU3 # circuit analysis from .analysis import ResourceEstimation diff --git a/qiskit/transpiler/passes/basis/__init__.py b/qiskit/transpiler/passes/basis/__init__.py index 692e50d56188..e725dd207c32 100644 --- a/qiskit/transpiler/passes/basis/__init__.py +++ b/qiskit/transpiler/passes/basis/__init__.py @@ -19,3 +19,4 @@ from .unroll_custom_definitions import UnrollCustomDefinitions from .unroll_3q_or_more import Unroll3qOrMore from .basis_translator import BasisTranslator +from .approx_2q_decompose import Approx2qDecompose diff --git a/qiskit/transpiler/passes/basis/approx_2q_decompose.py b/qiskit/transpiler/passes/basis/approx_2q_decompose.py new file mode 100644 index 000000000000..69a563d58e35 --- /dev/null +++ b/qiskit/transpiler/passes/basis/approx_2q_decompose.py @@ -0,0 +1,42 @@ +# -*- 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. + +"""Expand 2-qubit unitaries into a 2-qubit basis, optionally approximating.""" + +from qiskit.quantum_info.synthesis.two_qubit_decompose import two_qubit_cnot_decompose +from qiskit.circuit import QuantumRegister +from qiskit.transpiler.basepasses import TransformationPass +from qiskit.dagcircuit import DAGCircuit + + +class Approx2qDecompose(TransformationPass): + """Expand 2-qubit unitaries into a 2-qubit basis, optionally approximating.""" + + def __init__(self, fidelity=1): + super().__init__() + self.fidelity=fidelity + + def run(self, dag): + qr = QuantumRegister(2) + for node in dag.named_nodes("unitary"): + if len(node.qargs) != 2: + continue + rule = two_qubit_cnot_decompose(node.op, basis_fidelity=self.fidelity) + decomposition = DAGCircuit() + decomposition.add_qreg(rule[0][1][0].register) + for inst in rule: + decomposition.apply_operation_back(*inst) + + dag.substitute_node_with_dag(node, decomposition) + return dag diff --git a/qiskit/transpiler/passes/optimization/__init__.py b/qiskit/transpiler/passes/optimization/__init__.py index 52d815534c38..da02c3b6cc53 100644 --- a/qiskit/transpiler/passes/optimization/__init__.py +++ b/qiskit/transpiler/passes/optimization/__init__.py @@ -25,3 +25,4 @@ from .remove_diagonal_gates_before_measure import RemoveDiagonalGatesBeforeMeasure from .crosstalk_adaptive_schedule import CrosstalkAdaptiveSchedule from .hoare_opt import HoareOptimizer +from .simplify_u3 import SimplifyU3 diff --git a/qiskit/transpiler/passes/optimization/simplify_u3.py b/qiskit/transpiler/passes/optimization/simplify_u3.py new file mode 100644 index 000000000000..195b6caa6fc8 --- /dev/null +++ b/qiskit/transpiler/passes/optimization/simplify_u3.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2018. +# +# 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. + +"""A strength reduction pass to simplify single qubit U3 gates, if possible. +""" + +import numpy as np + +from qiskit.extensions.standard.u1 import U1Gate +from qiskit.extensions.standard.u2 import U2Gate +from qiskit.extensions.standard.u3 import U3Gate +from qiskit.circuit.controlledgate import ControlledGate +from qiskit.circuit.add_control import add_control +from qiskit.transpiler.basepasses import TransformationPass +from qiskit.quantum_info import Operator + + +DEFAULT_ATOL = 1e-12 + + +class SimplifyU3(TransformationPass): + """A strength reduction pass to simplify single qubit U3 gates, if possible. + The cost metric is the number of X90 pulses required to implement the gate. + Can convert U3 -> U2 OR U1 OR None. + Also makes all Euler angles modulo 2*pi. + Additional Information + ---------------------- + U3(θ,φ,λ) is a generic single-qubit gate (generic Bloch sphere rotation). + It can be realized with TWO pre-calibrated X90 pulses. + Example: X gate. + U2(φ,λ) is a rotation around the Z+X axis (with co-efficient). It can + be implemented using ONE pre-calibrated X90 pulse. + Example: H gate. + U1(λ) is a rotation about the Z axis. It requires ZERO pulses (i.e. + done virtually in software). + Example: T gate. + """ + + def run(self, dag): + """Run the SimplifyU3 pass on `dag`. + Args: + dag (DAGCircuit): the DAG to be optimized. + Returns: + DAGCircuit: the optimized DAG. + """ + for node in dag.gate_nodes(): + op = node.op + num_ctrl_qubits = None + + if op.is_parameterized(): + continue + + if isinstance(node.op, ControlledGate): + num_ctrl_qubits = op.num_ctrl_qubits + op = node.op.base_gate + + if isinstance(op, U3Gate): + theta, phi, lam = op.params + + new_op = U3Gate(_mod2pi(theta), _mod2pi(phi), _mod2pi(lam)) + + + if np.isclose(_mod2pi(theta), [0., 2*np.pi], atol=DEFAULT_ATOL).any(): + if np.isclose(_mod2pi(phi+lam), [0., 2*np.pi], atol=DEFAULT_ATOL).any(): + new_op = None + else: + new_op = U1Gate(_mod2pi(phi+lam)) + + elif np.isclose(theta, [np.pi/2, 3*np.pi/2], atol=DEFAULT_ATOL).any(): + new_op = U2Gate(_mod2pi(phi+theta-np.pi/2), _mod2pi(lam+theta-np.pi/2)) + + else: + new_op = U3Gate(_mod2pi(theta), _mod2pi(phi), _mod2pi(lam)) + + if new_op is None: + dag.remove_op_node(node) + else: + if num_ctrl_qubits is not None: + new_op = add_control(new_op, num_ctrl_qubits) + dag.substitute_node(node, new_op) + + return dag + + +def _mod2pi(angle): + if angle >= 0: + return np.mod(angle, 2*np.pi) + else: + return np.mod(angle, -2*np.pi) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 35b79a7dc6bd..28b8dd57b69f 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -49,6 +49,8 @@ from qiskit.transpiler.passes import ConsolidateBlocks from qiskit.transpiler.passes import ApplyLayout from qiskit.transpiler.passes import CheckCXDirection +from qiskit.transpiler.passes import SimplifyU3 +from qiskit.transpiler.passes import Approx2qDecompose from qiskit.transpiler import TranspilerError @@ -88,10 +90,7 @@ 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) - - # 2. Layout on good qubits if calibration info available, otherwise on dense links + # 1. Layout on good qubits if calibration info available, otherwise on dense links _given_layout = SetLayout(initial_layout) def _choose_layout_condition(property_set): @@ -109,16 +108,19 @@ def _choose_layout_condition(property_set): else: raise TranspilerError("Invalid layout method %s." % layout_method) - # 3. Extend dag/layout with ancillas using the full coupling map + # 2. 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 + # 3. Unroll to 1q or 2q gates + _unroll3q = Unroll3qOrMore() + + # 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': @@ -130,17 +132,8 @@ def _swap_condition(property_set): 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)] @@ -150,19 +143,39 @@ def _direction_condition(property_set): _direction = [CXDirection(coupling_map)] + # 7. Remove zero-state reset + _reset = RemoveResetInZeroState() + + # 8. 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 = [Collect2qBlocks(), ConsolidateBlocks(), + Approx2qDecompose(fidelity=1.0), + Optimize1qGates(basis_gates), CommutativeCancellation(), + SimplifyU3()] + + # 9. Remove useless gates before measure. + _meas = [OptimizeSwapBeforeMeasure(), RemoveDiagonalGatesBeforeMeasure()] + # Build pass manager pm3 = PassManager() - pm3.append(_unroll) 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(_unroll3q) pm3.append(_swap_check) pm3.append(_swap, condition=_swap_condition) - pm3.append(_depth_check + _opt, do_while=_opt_control) + pm3.append(_unroll) if coupling_map and not coupling_map.is_symmetric: pm3.append(_direction_check) pm3.append(_direction, condition=_direction_condition) + pm3.append(_reset) + pm3.append(_depth_check + _opt, do_while=_opt_control) + pm3.append(_meas) return pm3 From c93ee320fd6c613d1cb4548359552074a885f615 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Wed, 6 May 2020 10:15:58 -0400 Subject: [PATCH 06/34] allow synthesis_fidelity in global transpile options --- qiskit/compiler/transpile.py | 32 +++++++++++++------ qiskit/transpiler/passes/layout/csp_layout.py | 4 +-- .../passes/optimization/simplify_u3.py | 7 ++-- qiskit/transpiler/passmanager_config.py | 3 ++ .../transpiler/preset_passmanagers/level3.py | 18 ++++++----- 5 files changed, 41 insertions(+), 23 deletions(-) diff --git a/qiskit/compiler/transpile.py b/qiskit/compiler/transpile.py index bd2d1fa91fc9..3b535ed5a61b 100644 --- a/qiskit/compiler/transpile.py +++ b/qiskit/compiler/transpile.py @@ -46,6 +46,7 @@ def transpile(circuits: Union[QuantumCircuit, List[QuantumCircuit]], backend_properties: Optional[BackendProperties] = None, initial_layout: Optional[Union[Layout, Dict, List]] = None, layout_method: Optional[str] = None, + synthesis_fidelity: Optional[float] = None, routing_method: Optional[str] = None, seed_transpiler: Optional[int] = None, optimization_level: Optional[int] = None, @@ -119,6 +120,7 @@ def transpile(circuits: Union[QuantumCircuit, List[QuantumCircuit]], 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', 'sabre') + synthesis_fidelity (float): tolerable fidelity for approximate synthesis. 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, @@ -185,7 +187,9 @@ def callback_func(**kwargs): coupling_map=coupling_map, seed_transpiler=seed_transpiler, backend_properties=backend_properties, initial_layout=initial_layout, layout_method=layout_method, - routing_method=routing_method, backend=backend) + routing_method=routing_method, + synthesis_fidelity=synthesis_fidelity, + backend=backend) warnings.warn("The parameter pass_manager in transpile is being deprecated. " "The preferred way to tranpile a circuit using a custom pass manager is" @@ -200,7 +204,7 @@ def callback_func(**kwargs): # Get transpile_args to configure the circuit transpilation job(s) transpile_args = _parse_transpile_args(circuits, backend, basis_gates, coupling_map, backend_properties, initial_layout, - layout_method, routing_method, + layout_method, routing_method, synthesis_fidelity, seed_transpiler, optimization_level, callback, output_name) @@ -310,8 +314,8 @@ def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict]) -> Qua def _parse_transpile_args(circuits, backend, basis_gates, coupling_map, backend_properties, initial_layout, layout_method, routing_method, - seed_transpiler, optimization_level, - callback, output_name) -> List[Dict]: + synthesis_fidelity, seed_transpiler, + optimization_level, callback, output_name) -> List[Dict]: """Resolve the various types of args allowed to the transpile() function through duck typing, overriding args, etc. Refer to the transpile() docstring for details on what types of inputs are allowed. @@ -336,6 +340,7 @@ def _parse_transpile_args(circuits, backend, initial_layout = _parse_initial_layout(initial_layout, circuits) layout_method = _parse_layout_method(layout_method, num_circuits) routing_method = _parse_routing_method(routing_method, num_circuits) + synthesis_fidelity = _parse_synthesis_fidelity(synthesis_fidelity, num_circuits) seed_transpiler = _parse_seed_transpiler(seed_transpiler, num_circuits) optimization_level = _parse_optimization_level(optimization_level, num_circuits) output_name = _parse_output_name(output_name, circuits) @@ -344,18 +349,19 @@ def _parse_transpile_args(circuits, backend, list_transpile_args = [] for args in zip(basis_gates, coupling_map, backend_properties, initial_layout, layout_method, routing_method, - seed_transpiler, optimization_level, - output_name, callback): + synthesis_fidelity, seed_transpiler, + optimization_level, output_name, callback): transpile_args = {'pass_manager_config': PassManagerConfig(basis_gates=args[0], coupling_map=args[1], backend_properties=args[2], initial_layout=args[3], layout_method=args[4], routing_method=args[5], - seed_transpiler=args[6]), - 'optimization_level': args[7], - 'output_name': args[8], - 'callback': args[9]} + synthesis_fidelity=args[6], + seed_transpiler=args[7]), + 'optimization_level': args[8], + 'output_name': args[9], + 'callback': args[10]} list_transpile_args.append(transpile_args) return list_transpile_args @@ -446,6 +452,12 @@ def _parse_routing_method(routing_method, num_circuits): return routing_method +def _parse_synthesis_fidelity(synthesis_fidelity, num_circuits): + if not isinstance(synthesis_fidelity, list): + synthesis_fidelity = [synthesis_fidelity] * num_circuits + return synthesis_fidelity + + def _parse_seed_transpiler(seed_transpiler, num_circuits): if not isinstance(seed_transpiler, list): seed_transpiler = [seed_transpiler] * num_circuits diff --git a/qiskit/transpiler/passes/layout/csp_layout.py b/qiskit/transpiler/passes/layout/csp_layout.py index 7765b1da544f..078f82d951fd 100644 --- a/qiskit/transpiler/passes/layout/csp_layout.py +++ b/qiskit/transpiler/passes/layout/csp_layout.py @@ -74,8 +74,8 @@ def __init__(self, coupling_map, strict_direction=False, seed=None, call_limit=1 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. diff --git a/qiskit/transpiler/passes/optimization/simplify_u3.py b/qiskit/transpiler/passes/optimization/simplify_u3.py index 195b6caa6fc8..8dffa9dbce02 100644 --- a/qiskit/transpiler/passes/optimization/simplify_u3.py +++ b/qiskit/transpiler/passes/optimization/simplify_u3.py @@ -17,9 +17,9 @@ import numpy as np -from qiskit.extensions.standard.u1 import U1Gate -from qiskit.extensions.standard.u2 import U2Gate -from qiskit.extensions.standard.u3 import U3Gate +from qiskit.circuit.library.standard_gates import U1Gate +from qiskit.circuit.library.standard_gates import U2Gate +from qiskit.circuit.library.standard_gates import U3Gate from qiskit.circuit.controlledgate import ControlledGate from qiskit.circuit.add_control import add_control from qiskit.transpiler.basepasses import TransformationPass @@ -34,6 +34,7 @@ class SimplifyU3(TransformationPass): The cost metric is the number of X90 pulses required to implement the gate. Can convert U3 -> U2 OR U1 OR None. Also makes all Euler angles modulo 2*pi. + Additional Information ---------------------- U3(θ,φ,λ) is a generic single-qubit gate (generic Bloch sphere rotation). diff --git a/qiskit/transpiler/passmanager_config.py b/qiskit/transpiler/passmanager_config.py index 392d5ac09b6f..3853e58ab279 100644 --- a/qiskit/transpiler/passmanager_config.py +++ b/qiskit/transpiler/passmanager_config.py @@ -26,6 +26,7 @@ def __init__(self, layout_method=None, routing_method=None, backend_properties=None, + synthesis_fidelity=None, seed_transpiler=None): """Initialize a PassManagerConfig object @@ -42,6 +43,7 @@ def __init__(self, backend_properties (BackendProperties): Properties returned by a backend, including information on gate errors, readout errors, qubit coherence times, etc. + synthesis_fidelity (float): tolerable fidelity for approximate synthesis. seed_transpiler (int): Sets random seed for the stochastic parts of the transpiler. """ @@ -51,4 +53,5 @@ def __init__(self, self.layout_method = layout_method self.routing_method = routing_method self.backend_properties = backend_properties + self.synthesis_fidelity = synthesis_fidelity self.seed_transpiler = seed_transpiler diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 28b8dd57b69f..bfe686c74469 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -89,8 +89,12 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: routing_method = pass_manager_config.routing_method or 'stochastic' seed_transpiler = pass_manager_config.seed_transpiler backend_properties = pass_manager_config.backend_properties + synthesis_fidelity = pass_manager_config.synthesis_fidelity - # 1. Layout on good qubits if calibration info available, otherwise on dense links + # 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) def _choose_layout_condition(property_set): @@ -108,12 +112,9 @@ def _choose_layout_condition(property_set): else: raise TranspilerError("Invalid layout method %s." % layout_method) - # 2. Extend dag/layout with ancillas using the full coupling map + # 3. Extend dag/layout with ancillas using the full coupling map _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] - # 3. Unroll to 1q or 2q gates - _unroll3q = Unroll3qOrMore() - # 4. Swap to fit the coupling map _swap_check = CheckMap(coupling_map) @@ -153,7 +154,7 @@ def _opt_control(property_set): return not property_set['depth_fixed_point'] _opt = [Collect2qBlocks(), ConsolidateBlocks(), - Approx2qDecompose(fidelity=1.0), + Approx2qDecompose(fidelity=synthesis_fidelity), Optimize1qGates(basis_gates), CommutativeCancellation(), SimplifyU3()] @@ -162,20 +163,21 @@ def _opt_control(property_set): # Build pass manager pm3 = PassManager() + 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(_unroll3q) pm3.append(_swap_check) pm3.append(_swap, condition=_swap_condition) + pm3.append(_depth_check + _opt, do_while=_opt_control) pm3.append(_unroll) + pm3.append(_depth_check + _opt, 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) - pm3.append(_depth_check + _opt, do_while=_opt_control) pm3.append(_meas) return pm3 From 7597258eafbd87b14459f580ffd47e6f6a61880c Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Thu, 7 May 2020 00:49:03 -0400 Subject: [PATCH 07/34] stopgap fix for circuits with regs in sabre_layout --- qiskit/transpiler/passes/layout/sabre_layout.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qiskit/transpiler/passes/layout/sabre_layout.py b/qiskit/transpiler/passes/layout/sabre_layout.py index 323f18f58e1e..38c16d8dc57f 100644 --- a/qiskit/transpiler/passes/layout/sabre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_layout.py @@ -99,6 +99,8 @@ def _compose_layouts(initial_layout, pass_final_layout, qregs): 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()} From 3f63290108517161f511afd90748391e7b8bde3d Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Sun, 31 May 2020 02:00:35 -0500 Subject: [PATCH 08/34] add test --- test/python/transpiler/test_sabre_swap.py | 59 +++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 test/python/transpiler/test_sabre_swap.py diff --git a/test/python/transpiler/test_sabre_swap.py b/test/python/transpiler/test_sabre_swap.py new file mode 100644 index 000000000000..14b4e8a39058 --- /dev/null +++ b/test/python/transpiler/test_sabre_swap.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2018. +# +# 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 Stochastic Swap pass""" + +import unittest +from qiskit.transpiler.passes import StochasticSwap +from qiskit.transpiler import CouplingMap, PassManager +from qiskit.transpiler.exceptions import TranspilerError +from qiskit.converters import circuit_to_dag +from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit +from qiskit.test import QiskitTestCase + + +class TestSabreSwap(QiskitTestCase): + """Tests the SabreSwap pass.""" + + def test_trivial_case(self): + """ + q0:--(+)-[H]-(+)- + | | + q1:---.-------|-- + | + q2:-----------.-- + Coupling map: [1]--[0]--[2] + """ + 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(0, 4) # F + qc.cx(1, 2) # free + qc.cx(1, 3) # F + qc.cx(2, 3) # E + qc.cx(1, 3) # E + + dag = circuit_to_dag(circuit) + pass_ = StochasticSwap(coupling, 20, 13) + after = pass_.run(dag) + + self.assertEqual(dag, after) + + +if __name__ == '__main__': + unittest.main() From 61d076ff85c0ad22b99d9470fbfdbf805c9da024 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Sun, 31 May 2020 23:50:06 -0500 Subject: [PATCH 09/34] add tests --- .../transpiler/passes/routing/sabre_swap.py | 4 +- .../qasm/TestsBasicSwap_a_cx_to_map.qasm | 2 +- .../TestsBasicSwap_handle_measurement.qasm | 4 +- .../qasm/TestsBasicSwap_initial_layout.qasm | 2 +- .../qasm/TestsStochasticSwap_a_cx_to_map.qasm | 4 +- ...estsStochasticSwap_handle_measurement.qasm | 9 +- .../TestsStochasticSwap_initial_layout.qasm | 4 +- test/python/qasm/all_gates.qasm | 55 ----- test/python/qasm/example.qasm | 11 - test/python/qasm/example_fail.qasm | 1 - test/python/qasm/example_if.qasm | 26 --- test/python/qasm/move_measurements.qasm | 212 ------------------ test/python/qasm/random_n5_d5.qasm | 110 --------- test/python/transpiler/test_mappers.py | 7 +- test/python/transpiler/test_sabre_swap.py | 60 +++-- 15 files changed, 68 insertions(+), 443 deletions(-) delete mode 100644 test/python/qasm/all_gates.qasm delete mode 100644 test/python/qasm/example.qasm delete mode 100644 test/python/qasm/example_fail.qasm delete mode 100644 test/python/qasm/example_if.qasm delete mode 100644 test/python/qasm/move_measurements.qasm delete mode 100644 test/python/qasm/random_n5_d5.qasm diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index d4e039ac8ea3..c0d9a5713d75 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -64,11 +64,13 @@ class SabreSwap(TransformationPass): `arXiv:1809.02573 `_ """ - def __init__(self, coupling_map, heuristic): + def __init__(self, coupling_map, heuristic='basic'): """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 'advanced'). """ super().__init__() diff --git a/test/python/qasm/TestsBasicSwap_a_cx_to_map.qasm b/test/python/qasm/TestsBasicSwap_a_cx_to_map.qasm index a616ac1144a2..aa4582c452d0 100644 --- a/test/python/qasm/TestsBasicSwap_a_cx_to_map.qasm +++ b/test/python/qasm/TestsBasicSwap_a_cx_to_map.qasm @@ -2,9 +2,9 @@ OPENQASM 2.0; include "qelib1.inc"; qreg q[3]; creg c[3]; +measure q[0] -> c[0]; h q[1]; swap q[1],q[0]; cx q[0],q[2]; -measure q[1] -> c[0]; measure q[0] -> c[1]; measure q[2] -> c[2]; diff --git a/test/python/qasm/TestsBasicSwap_handle_measurement.qasm b/test/python/qasm/TestsBasicSwap_handle_measurement.qasm index 360d1f057192..314e581cc181 100644 --- a/test/python/qasm/TestsBasicSwap_handle_measurement.qasm +++ b/test/python/qasm/TestsBasicSwap_handle_measurement.qasm @@ -3,12 +3,12 @@ include "qelib1.inc"; qreg q[4]; creg c[4]; cx q[0],q[1]; +measure q[2] -> c[2]; h q[3]; swap q[3],q[2]; cx q[2],q[1]; +measure q[1] -> c[1]; swap q[2],q[1]; cx q[1],q[0]; measure q[0] -> c[0]; -measure q[2] -> c[1]; -measure q[3] -> c[2]; measure q[1] -> c[3]; diff --git a/test/python/qasm/TestsBasicSwap_initial_layout.qasm b/test/python/qasm/TestsBasicSwap_initial_layout.qasm index 21de2ee9be0a..eac63472d3b4 100644 --- a/test/python/qasm/TestsBasicSwap_initial_layout.qasm +++ b/test/python/qasm/TestsBasicSwap_initial_layout.qasm @@ -2,10 +2,10 @@ OPENQASM 2.0; include "qelib1.inc"; qreg q[4]; creg c[4]; +measure q[0] -> c[0]; h q[1]; swap q[1],q[0]; cx q[0],q[2]; -measure q[1] -> c[0]; measure q[0] -> c[1]; measure q[2] -> c[2]; measure q[3] -> c[3]; diff --git a/test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm b/test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm index 15c0c43005f0..aa4582c452d0 100644 --- a/test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm +++ b/test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm @@ -2,9 +2,9 @@ OPENQASM 2.0; include "qelib1.inc"; qreg q[3]; creg c[3]; -h q[1]; measure q[0] -> c[0]; -swap q[0],q[1]; +h q[1]; +swap q[1],q[0]; cx q[0],q[2]; measure q[0] -> c[1]; measure q[2] -> c[2]; diff --git a/test/python/qasm/TestsStochasticSwap_handle_measurement.qasm b/test/python/qasm/TestsStochasticSwap_handle_measurement.qasm index bc161f609758..a685bfeed415 100644 --- a/test/python/qasm/TestsStochasticSwap_handle_measurement.qasm +++ b/test/python/qasm/TestsStochasticSwap_handle_measurement.qasm @@ -3,13 +3,12 @@ include "qelib1.inc"; qreg q[4]; creg c[4]; cx q[0],q[1]; -h q[3]; measure q[2] -> c[2]; -swap q[1],q[2]; +h q[3]; +swap q[3],q[2]; +cx q[2],q[1]; +measure q[1] -> c[1]; swap q[0],q[1]; -cx q[3],q[2]; -swap q[2],q[3]; cx q[2],q[1]; measure q[1] -> c[0]; -measure q[3] -> c[1]; measure q[2] -> c[3]; diff --git a/test/python/qasm/TestsStochasticSwap_initial_layout.qasm b/test/python/qasm/TestsStochasticSwap_initial_layout.qasm index 76adf18d61fc..eac63472d3b4 100644 --- a/test/python/qasm/TestsStochasticSwap_initial_layout.qasm +++ b/test/python/qasm/TestsStochasticSwap_initial_layout.qasm @@ -2,9 +2,9 @@ OPENQASM 2.0; include "qelib1.inc"; qreg q[4]; creg c[4]; -h q[1]; measure q[0] -> c[0]; -swap q[0],q[1]; +h q[1]; +swap q[1],q[0]; cx q[0],q[2]; measure q[0] -> c[1]; measure q[2] -> c[2]; diff --git a/test/python/qasm/all_gates.qasm b/test/python/qasm/all_gates.qasm deleted file mode 100644 index 2763e3704ac0..000000000000 --- a/test/python/qasm/all_gates.qasm +++ /dev/null @@ -1,55 +0,0 @@ -OPENQASM 2.0; -include "qelib1.inc"; -qreg q[3]; -creg c[3]; - -// note that the order and where the gates are applied to is important! - -// the hardware primitives -u3(0.2,0.1,0.6) q[0]; -u2(0.1,0.6) q[0]; -u1(0.6) q[0]; -id q[0]; -cx q[0], q[1]; - -// the standard single qubit gates -x q[0]; -y q[0]; -z q[0]; -h q[0]; -s q[0]; -t q[0]; -sdg q[0]; -tdg q[0]; - -// the standard rotations -rx(0.1) q[0]; -ry(0.1) q[0]; -rz(0.1) q[0]; - -// the barrier -barrier q; - -// the standard user-defined gates -swap q[0], q[1]; -cswap q[0], q[1], q[2]; - -cy q[0], q[1]; -cz q[0], q[1]; -ch q[0], q[1]; -cu1(0.6) q[0], q[1]; -cu3(0.2,0.1,0.6) q[0], q[1]; -ccx q[0], q[1], q[2]; - -crx(0.6) q[0], q[1]; -cry(0.6) q[0], q[1]; -crz(0.6) q[0], q[1]; - -rxx(0.2) q[0], q[1]; -rzz(0.2) q[0], q[1]; - -// measure -measure q->c; - - - diff --git a/test/python/qasm/example.qasm b/test/python/qasm/example.qasm deleted file mode 100644 index 72195851f2ec..000000000000 --- a/test/python/qasm/example.qasm +++ /dev/null @@ -1,11 +0,0 @@ -OPENQASM 2.0; -include "qelib1.inc"; -qreg q[3]; -qreg r[3]; -h q; -cx q, r; -creg c[3]; -creg d[3]; -barrier q; -measure q->c; -measure r->d; diff --git a/test/python/qasm/example_fail.qasm b/test/python/qasm/example_fail.qasm deleted file mode 100644 index c06113705ede..000000000000 --- a/test/python/qasm/example_fail.qasm +++ /dev/null @@ -1 +0,0 @@ -OPENQASM 2.0 diff --git a/test/python/qasm/example_if.qasm b/test/python/qasm/example_if.qasm deleted file mode 100644 index 68fb5f170fd3..000000000000 --- a/test/python/qasm/example_if.qasm +++ /dev/null @@ -1,26 +0,0 @@ -// Example file containing "if" instructions. -OPENQASM 2.0; -include "qelib1.inc"; -qreg qr0[1]; -creg cr0[1]; -h qr0[0]; -measure qr0[0] -> cr0[0]; -if(cr0==0) x qr0[0]; -h qr0[0]; -reset qr0[0]; -measure qr0[0] -> cr0[0]; -h qr0[0]; -reset qr0[0]; -u1(0.562367489586523) qr0[0]; -measure qr0[0] -> cr0[0]; -reset qr0[0]; -reset qr0[0]; -reset qr0[0]; -h qr0[0]; -measure qr0[0] -> cr0[0]; -if(cr0==1) x qr0[0]; -h qr0[0]; -u1(0.083178154631269) qr0[0]; -h qr0[0]; -reset qr0[0]; -measure qr0[0] -> cr0[0]; diff --git a/test/python/qasm/move_measurements.qasm b/test/python/qasm/move_measurements.qasm deleted file mode 100644 index 846c61e864f6..000000000000 --- a/test/python/qasm/move_measurements.qasm +++ /dev/null @@ -1,212 +0,0 @@ -OPENQASM 2.0; -include "qelib1.inc"; -qreg qa[2]; -qreg qb[3]; -qreg qN[3]; -qreg qc[1]; -qreg qNt[2]; -qreg qt[1]; -creg c[3]; -x qa[1]; -x qb[0]; -x qb[1]; -x qN[0]; -x qN[1]; -x qNt[0]; -x qNt[1]; -ccx qa[0],qb[0],qc[0]; -cx qa[0],qb[0]; -ccx qa[1],qb[1],qb[2]; -cx qa[1],qb[1]; -ccx qc[0],qb[1],qb[2]; -cx qa[1],qb[1]; -cx qa[1],qb[1]; -cx qc[0],qb[1]; -cx qa[0],qb[0]; -ccx qa[0],qb[0],qc[0]; -cx qa[0],qb[0]; -cx qN[0],qb[0]; -ccx qN[0],qb[0],qc[0]; -cx qN[0],qb[0]; -cx qc[0],qb[1]; -cx qN[1],qb[1]; -cx qN[1],qb[1]; -ccx qc[0],qb[1],qb[2]; -cx qN[1],qb[1]; -ccx qN[1],qb[1],qb[2]; -cx qN[0],qb[0]; -ccx qN[0],qb[0],qc[0]; -x qb[2]; -cx qb[2],qt[0]; -x qb[2]; -ccx qt[0],qNt[0],qN[0]; -h qc[0]; -ccx qt[0],qN[0],qc[0]; -tdg qc[0]; -ccx qt[0],qNt[0],qc[0]; -t qc[0]; -ccx qt[0],qN[0],qc[0]; -tdg qc[0]; -ccx qt[0],qNt[0],qc[0]; -t qc[0]; -h qc[0]; -t qN[0]; -ccx qt[0],qNt[0],qN[0]; -t qNt[0]; -tdg qN[0]; -ccx qt[0],qNt[0],qN[0]; -ccx qt[0],qNt[0],qN[0]; -ccx qt[0],qc[0],qN[1]; -ccx qt[0],qNt[1],qN[1]; -ccx qt[0],qNt[1],qN[1]; -h qN[2]; -ccx qt[0],qN[1],qN[2]; -tdg qN[2]; -ccx qt[0],qc[0],qN[2]; -t qN[2]; -ccx qt[0],qN[1],qN[2]; -tdg qN[2]; -ccx qt[0],qc[0],qN[2]; -t qN[2]; -h qN[2]; -t qN[1]; -ccx qt[0],qc[0],qN[1]; -t qc[0]; -tdg qN[1]; -ccx qt[0],qc[0],qN[1]; -ccx qt[0],qNt[1],qN[1]; -h qN[2]; -ccx qt[0],qN[1],qN[2]; -tdg qN[2]; -ccx qt[0],qNt[1],qN[2]; -t qN[2]; -ccx qt[0],qN[1],qN[2]; -tdg qN[2]; -ccx qt[0],qNt[1],qN[2]; -t qN[2]; -h qN[2]; -t qN[1]; -ccx qt[0],qNt[1],qN[1]; -t qNt[1]; -tdg qN[1]; -ccx qt[0],qNt[1],qN[1]; -ccx qt[0],qNt[0],qN[0]; -h qc[0]; -ccx qt[0],qN[0],qc[0]; -tdg qc[0]; -ccx qt[0],qNt[0],qc[0]; -t qc[0]; -ccx qt[0],qN[0],qc[0]; -tdg qc[0]; -ccx qt[0],qNt[0],qc[0]; -t qc[0]; -h qc[0]; -t qN[0]; -ccx qt[0],qNt[0],qN[0]; -t qNt[0]; -tdg qN[0]; -ccx qt[0],qNt[0],qN[0]; -ccx qN[0],qb[0],qc[0]; -cx qN[0],qb[0]; -ccx qN[1],qb[1],qb[2]; -cx qN[1],qb[1]; -ccx qc[0],qb[1],qb[2]; -cx qN[1],qb[1]; -cx qN[1],qb[1]; -cx qc[0],qb[1]; -cx qN[0],qb[0]; -ccx qN[0],qb[0],qc[0]; -cx qN[0],qb[0]; -h qc[0]; -ccx qt[0],qN[0],qc[0]; -tdg qc[0]; -ccx qt[0],qNt[0],qc[0]; -t qc[0]; -ccx qt[0],qN[0],qc[0]; -tdg qc[0]; -ccx qt[0],qNt[0],qc[0]; -t qc[0]; -h qc[0]; -t qN[0]; -ccx qt[0],qNt[0],qN[0]; -t qNt[0]; -tdg qN[0]; -ccx qt[0],qNt[0],qN[0]; -ccx qt[0],qNt[0],qN[0]; -h qN[2]; -ccx qt[0],qN[1],qN[2]; -tdg qN[2]; -ccx qt[0],qNt[1],qN[2]; -t qN[2]; -ccx qt[0],qN[1],qN[2]; -tdg qN[2]; -ccx qt[0],qNt[1],qN[2]; -t qN[2]; -h qN[2]; -t qN[1]; -ccx qt[0],qNt[1],qN[1]; -t qNt[1]; -tdg qN[1]; -ccx qt[0],qNt[1],qN[1]; -ccx qt[0],qNt[1],qN[1]; -h qN[2]; -ccx qt[0],qN[1],qN[2]; -tdg qN[2]; -ccx qt[0],qc[0],qN[2]; -t qN[2]; -ccx qt[0],qN[1],qN[2]; -tdg qN[2]; -ccx qt[0],qc[0],qN[2]; -t qN[2]; -h qN[2]; -t qN[1]; -ccx qt[0],qc[0],qN[1]; -t qc[0]; -tdg qN[1]; -ccx qt[0],qc[0],qN[1]; -ccx qt[0],qNt[1],qN[1]; -ccx qt[0],qNt[1],qN[1]; -ccx qt[0],qc[0],qN[1]; -ccx qt[0],qNt[0],qN[0]; -h qc[0]; -ccx qt[0],qN[0],qc[0]; -tdg qc[0]; -ccx qt[0],qNt[0],qc[0]; -t qc[0]; -ccx qt[0],qN[0],qc[0]; -tdg qc[0]; -ccx qt[0],qNt[0],qc[0]; -t qc[0]; -h qc[0]; -t qN[0]; -ccx qt[0],qNt[0],qN[0]; -t qNt[0]; -tdg qN[0]; -ccx qt[0],qNt[0],qN[0]; -ccx qt[0],qNt[0],qN[0]; -cx qa[0],qb[0]; -ccx qa[0],qb[0],qc[0]; -cx qa[0],qb[0]; -cx qc[0],qb[1]; -cx qa[1],qb[1]; -cx qa[1],qb[1]; -ccx qc[0],qb[1],qb[2]; -cx qa[1],qb[1]; -ccx qa[1],qb[1],qb[2]; -cx qa[0],qb[0]; -ccx qa[0],qb[0],qc[0]; -cx qb[2],qt[0]; -ccx qa[0],qb[0],qc[0]; -cx qa[0],qb[0]; -ccx qa[1],qb[1],qb[2]; -cx qa[1],qb[1]; -ccx qc[0],qb[1],qb[2]; -cx qa[1],qb[1]; -cx qa[1],qb[1]; -cx qc[0],qb[1]; -cx qa[0],qb[0]; -ccx qa[0],qb[0],qc[0]; -cx qa[0],qb[0]; -measure qb[0] -> c[0]; -measure qb[1] -> c[1]; -measure qb[2] -> c[2]; \ No newline at end of file diff --git a/test/python/qasm/random_n5_d5.qasm b/test/python/qasm/random_n5_d5.qasm deleted file mode 100644 index 5c2f6021ee82..000000000000 --- a/test/python/qasm/random_n5_d5.qasm +++ /dev/null @@ -1,110 +0,0 @@ -OPENQASM 2.0; -include "qelib1.inc"; -qreg q[5]; -creg c[5]; -u3(0.634346715653683,0.880910487587674,0.827499019150487) q[1]; -u3(1.484323068020462,-0.218212999454475,-3.592363844052204) q[0]; -cx q[0],q[1]; -u1(3.308485833921549) q[1]; -u3(-1.810211319750651,0.000000000000000,0.000000000000000) q[0]; -cx q[1],q[0]; -u3(1.099272036188136,0.000000000000000,0.000000000000000) q[0]; -cx q[0],q[1]; -u3(2.421859755364273,2.353435858968482,-2.973056963234994) q[1]; -u3(2.455896940634234,0.124779338899211,0.037434518192186) q[0]; -u3(1.129855614911814,1.049626985018942,-3.212204913542003) q[3]; -u3(0.875794477896647,-2.759645955801507,3.190456341307370) q[2]; -cx q[2],q[3]; -u1(1.511890404671010) q[3]; -u3(0.293141728119888,0.000000000000000,0.000000000000000) q[2]; -cx q[3],q[2]; -u3(1.016455330162957,0.000000000000000,0.000000000000000) q[2]; -cx q[2],q[3]; -u3(0.334309092748757,0.521417129139045,1.088703327654617) q[3]; -u3(2.078918628857339,-5.853779889473016,0.117993422312274) q[2]; -u3(0.066133062177417,-1.138507778410453,0.171231283320043) q[2]; -u3(0.533649199725493,-3.175942607101125,1.605744443352545) q[1]; -cx q[1],q[2]; -u1(3.757413769360157) q[2]; -u3(-3.605282263355746,0.000000000000000,0.000000000000000) q[1]; -cx q[2],q[1]; -u3(-0.958110312777987,0.000000000000000,0.000000000000000) q[1]; -cx q[1],q[2]; -u3(0.827682817458717,-1.720505332888779,2.154562394941609) q[2]; -u3(1.817140010299614,-1.220359595860445,4.688766804035865) q[1]; -u3(1.341045551718784,-0.608686333717184,-1.756587055231964) q[4]; -u3(1.561872017694270,1.387145535856301,-4.379023525190993) q[0]; -cx q[0],q[4]; -u1(0.484884767345497) q[4]; -u3(-3.265271371905420,0.000000000000000,0.000000000000000) q[0]; -cx q[4],q[0]; -u3(1.860045495435328,0.000000000000000,0.000000000000000) q[0]; -cx q[0],q[4]; -u3(1.695221563500504,-2.888422194660264,2.978375458262739) q[4]; -u3(0.604973257101321,5.096821375484736,0.151580711829459) q[0]; -u3(2.264838682685293,2.192061313298150,-0.415843311326973) q[0]; -u3(2.008823010418951,-0.909941656301332,-4.417998093169953) q[3]; -cx q[3],q[0]; -u1(0.510524156727350) q[0]; -u3(-1.207202180779869,0.000000000000000,0.000000000000000) q[3]; -cx q[0],q[3]; -u3(2.960945140232618,0.000000000000000,0.000000000000000) q[3]; -cx q[3],q[0]; -u3(3.061662326494465,-0.624418514911125,2.466421200808710) q[0]; -u3(1.325006076330307,-3.192834170078656,-1.726581820210002) q[3]; -u3(1.256450084601955,-0.556520865508894,1.699053270512565) q[2]; -u3(1.300439999199225,-1.817379150670086,-1.939257837183832) q[1]; -cx q[1],q[2]; -u1(2.042054298937185) q[2]; -u3(0.712518803857714,0.000000000000000,0.000000000000000) q[1]; -cx q[2],q[1]; -u3(1.701399622828943,0.000000000000000,0.000000000000000) q[1]; -cx q[1],q[2]; -u3(2.363510068172205,3.344182218420100,-0.352403693269645) q[2]; -u3(2.736552984228416,1.857400895757674,-3.458744088625263) q[1]; -u3(1.885221313272570,-4.111246600359667,1.263896163963675) q[4]; -u3(2.245883757569570,0.415789947336587,3.592639595348905) q[2]; -cx q[2],q[4]; -u1(1.471229415145194) q[4]; -u3(-1.146228063273526,0.000000000000000,0.000000000000000) q[2]; -cx q[4],q[2]; -u3(2.706742224447916,0.000000000000000,0.000000000000000) q[2]; -cx q[2],q[4]; -u3(2.208226126480162,1.577197878815174,-2.830849787565416) q[4]; -u3(0.896122652728372,-0.226711120411633,5.392354155993746) q[2]; -u3(0.416020400069302,3.810392878194613,-1.283379742380341) q[1]; -u3(1.960920589455032,2.309523850490192,-1.514823210880829) q[0]; -cx q[0],q[1]; -u1(2.024620895333101) q[1]; -u3(0.226083454073590,0.000000000000000,0.000000000000000) q[0]; -cx q[1],q[0]; -u3(0.884590377979485,0.000000000000000,0.000000000000000) q[0]; -cx q[0],q[1]; -u3(2.545573385995641,-2.012145518385366,0.264220290428662) q[1]; -u3(2.005619037013396,-1.905747310870419,-2.347933504260405) q[0]; -u3(2.243678899063514,-0.428716035636104,1.818381814503391) q[2]; -u3(2.749359171104891,-2.011059232776085,-0.228840744181900) q[1]; -cx q[1],q[2]; -u1(1.378046823197370) q[2]; -u3(-0.723820608259067,0.000000000000000,0.000000000000000) q[1]; -cx q[2],q[1]; -u3(-0.255642156738184,0.000000000000000,0.000000000000000) q[1]; -cx q[1],q[2]; -u3(2.983520707154838,-1.112747818281526,3.123895568815588) q[2]; -u3(1.740467732269037,0.515041546225282,5.397279301019271) q[1]; -u3(2.593150404530149,1.013497671346222,-1.386610389037105) q[0]; -u3(1.772531076135327,0.810011859752890,-4.799091878810430) q[3]; -cx q[3],q[0]; -u1(4.063446914963040) q[0]; -u3(-3.243270441698034,0.000000000000000,0.000000000000000) q[3]; -cx q[0],q[3]; -u3(-0.473197807476301,0.000000000000000,0.000000000000000) q[3]; -cx q[3],q[0]; -u3(2.078571935001867,0.638938443408619,-2.128941975613032) q[0]; -u3(0.540247300850280,-5.624086444202922,-0.051515330897875) q[3]; -barrier q[0],q[1],q[2],q[3],q[4]; -measure q[0] -> c[0]; -measure q[1] -> c[1]; -measure q[2] -> c[2]; -measure q[3] -> c[3]; -measure q[4] -> c[4]; diff --git a/test/python/transpiler/test_mappers.py b/test/python/transpiler/test_mappers.py index 5095c1e60c99..097c8e4fb28d 100644 --- a/test/python/transpiler/test_mappers.py +++ b/test/python/transpiler/test_mappers.py @@ -77,7 +77,8 @@ def test_a_common_test(self): 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 @@ -283,6 +284,10 @@ class TestsStochasticSwap(SwapperCommonTestCases, QiskitTestCase): pass_class = StochasticSwap additional_args = {'seed': 0} +class TestsStochasticSwap(SwapperCommonTestCases, QiskitTestCase): + """Test SwapperCommonTestCases using SabreSwap.""" + pass_class = SabreSwap + if __name__ == '__main__': if len(sys.argv) >= 2 and sys.argv[1] == 'regenerate': diff --git a/test/python/transpiler/test_sabre_swap.py b/test/python/transpiler/test_sabre_swap.py index 14b4e8a39058..118a0d1ad764 100644 --- a/test/python/transpiler/test_sabre_swap.py +++ b/test/python/transpiler/test_sabre_swap.py @@ -12,10 +12,10 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Test the Stochastic Swap pass""" +"""Test the Sabre Swap pass""" import unittest -from qiskit.transpiler.passes import StochasticSwap +from qiskit.transpiler.passes import SabreSwap from qiskit.transpiler import CouplingMap, PassManager from qiskit.transpiler.exceptions import TranspilerError from qiskit.converters import circuit_to_dag @@ -27,13 +27,49 @@ 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 ├ + └───┘ """ - q0:--(+)-[H]-(+)- - | | - q1:---.-------|-- - | - q2:-----------.-- - Coupling map: [1]--[0]--[2] + 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) + + pm = PassManager(SabreSwap(coupling, 'basic')) + new_qc = pm.run(qc) + + self.assertEqual(new_qc, qc) + + def test_advanced_mode(self): + """Test advanced 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) @@ -42,17 +78,15 @@ def test_trivial_case(self): qc.cx(0, 1) # free qc.cx(2, 3) # free qc.h(0) # free - qc.cx(0, 4) # F qc.cx(1, 2) # free qc.cx(1, 3) # F qc.cx(2, 3) # E qc.cx(1, 3) # E - dag = circuit_to_dag(circuit) - pass_ = StochasticSwap(coupling, 20, 13) - after = pass_.run(dag) + pm = PassManager(SabreSwap(coupling, 'advanced')) + new_qc = pm.run(qc) - self.assertEqual(dag, after) + self.assertEqual(new_qc.num_nonlocal_gates(), 7) if __name__ == '__main__': From 5a82572dd3fa9e5277a047c68413afa5dc29bc1d Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Tue, 2 Jun 2020 01:10:46 -0500 Subject: [PATCH 10/34] clean up sabre swap --- qiskit/dagcircuit/dagcircuit.py | 1 - qiskit/transpiler/coupling.py | 2 +- .../passes/basis/approx_2q_decompose.py | 2 +- .../passes/optimization/simplify_u3.py | 5 +- .../transpiler/passes/routing/sabre_swap.py | 285 ++++++++++-------- .../transpiler/preset_passmanagers/level3.py | 2 +- test/python/transpiler/test_mappers.py | 1 + 7 files changed, 168 insertions(+), 130 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 29631bce23fb..7fc45d9f7215 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1195,7 +1195,6 @@ def front_layer(self): return op_nodes - def layers(self): """Yield a shallow view on a layer of this DAGCircuit for all d layers of this circuit. diff --git a/qiskit/transpiler/coupling.py b/qiskit/transpiler/coupling.py index 6df11359604e..6ca24b297309 100644 --- a/qiskit/transpiler/coupling.py +++ b/qiskit/transpiler/coupling.py @@ -215,7 +215,7 @@ def make_symmetric(self): if (dest, src) not in edges: self.add_edge(dest, src) self._dist_matrix = None # invalidate - self._is_symmetric = None # invalidate + self._is_symmetric = None # invalidate def _check_symmetry(self): """ diff --git a/qiskit/transpiler/passes/basis/approx_2q_decompose.py b/qiskit/transpiler/passes/basis/approx_2q_decompose.py index 69a563d58e35..82f3c4b80b07 100644 --- a/qiskit/transpiler/passes/basis/approx_2q_decompose.py +++ b/qiskit/transpiler/passes/basis/approx_2q_decompose.py @@ -25,7 +25,7 @@ class Approx2qDecompose(TransformationPass): def __init__(self, fidelity=1): super().__init__() - self.fidelity=fidelity + self.fidelity = fidelity def run(self, dag): qr = QuantumRegister(2) diff --git a/qiskit/transpiler/passes/optimization/simplify_u3.py b/qiskit/transpiler/passes/optimization/simplify_u3.py index 8dffa9dbce02..62ea27c3b18f 100644 --- a/qiskit/transpiler/passes/optimization/simplify_u3.py +++ b/qiskit/transpiler/passes/optimization/simplify_u3.py @@ -32,7 +32,7 @@ class SimplifyU3(TransformationPass): """A strength reduction pass to simplify single qubit U3 gates, if possible. The cost metric is the number of X90 pulses required to implement the gate. - Can convert U3 -> U2 OR U1 OR None. + Can convert U3 -> U2 OR U1 OR None. Also makes all Euler angles modulo 2*pi. Additional Information @@ -71,7 +71,6 @@ def run(self, dag): new_op = U3Gate(_mod2pi(theta), _mod2pi(phi), _mod2pi(lam)) - if np.isclose(_mod2pi(theta), [0., 2*np.pi], atol=DEFAULT_ATOL).any(): if np.isclose(_mod2pi(phi+lam), [0., 2*np.pi], atol=DEFAULT_ATOL).any(): new_op = None @@ -88,7 +87,7 @@ def run(self, dag): dag.remove_op_node(node) else: if num_ctrl_qubits is not None: - new_op = add_control(new_op, num_ctrl_qubits) + new_op = add_control(new_op, num_ctrl_qubits) dag.substitute_node(node, new_op) return dag diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index c0d9a5713d75..78a7ec98a003 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -28,8 +28,11 @@ logger = logging.getLogger(__name__) -W = 0.5 # Weight of extened_set (lookahead window) compared to front_layer. -DELTA = 0.001 # Decay cooefficient for penalizing serial swaps. +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): @@ -65,12 +68,55 @@ class SabreSwap(TransformationPass): """ def __init__(self, coupling_map, heuristic='basic'): - """SabreSwap initializer. + 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 'advanced'). + swap strategy ('basic' or 'lookahead' or 'decay'). + + 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. |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__() @@ -103,12 +149,14 @@ def run(self, dag): canonical_register = dag.qregs['q'] current_layout = Layout.generate_trivial_layout(canonical_register) - # Set the max number of gates in the lookahead window to dag width. - EXTENDED_SET_SIZE = 4 # len(current_layout) + # A decay factor for each qubit used to heuristically penalize recently + # used qubits (to encourage parallelism). + self.qubits_decay = dict(zip(dag.qubits(), len(dag.qubits()) * [1])) # Start algorithm from the front layer and iterate until all gates done. + num_search_steps = 0 front_layer = dag.front_layer() - applied_gates = set() + self.applied_gates = set() while front_layer: execute_gate_list = [] @@ -130,13 +178,16 @@ def run(self, dag): new_node.cargs, new_node.condition) front_layer.remove(node) - applied_gates.add(node) + self.applied_gates.add(node) for successor in dag.quantum_successors(node): if successor.type != 'op': continue - elif _is_resolved(successor, dag, applied_gates): + elif self._is_resolved(successor, dag): front_layer.append(successor) + if len(node.qargs): + self._reset_qubits_decay() + # Diagnostics logger.debug('free! %s', [(n.name, n.qargs) for n in execute_gate_list]) @@ -148,21 +199,17 @@ def run(self, dag): # After all free gates are exhausted, heuristically find # the best swap and insert it. else: - extended_set = _obtain_extended_set(dag, front_layer, - EXTENDED_SET_SIZE) - - swap_candidate_list = _obtain_swaps(front_layer, current_layout, - self.coupling_map) - + extended_set = self._obtain_extended_set(dag, front_layer) + swap_candidate_list = self._obtain_swaps(front_layer, current_layout) swap_scores = [] - for i, swap in enumerate(swap_candidate_list): + for i, swap_qubits in enumerate(swap_candidate_list): trial_layout = current_layout.copy() - trial_layout.swap(*swap) - score = _score_heuristic(front_layer, - extended_set, - trial_layout, - self.coupling_map, - self.heuristic) + trial_layout.swap(*swap_qubits) + score = self._score_heuristic(self.heuristic, + front_layer, + extended_set, + trial_layout, + swap_qubits) swap_scores.append(score) min_score = min(swap_scores) best_swap = swap_candidate_list[swap_scores.index(min_score)] @@ -171,6 +218,13 @@ def run(self, dag): 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', @@ -179,120 +233,105 @@ def run(self, dag): [(swap_candidate_list[i], swap_scores[i]) for i in range(len(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(node, dag, applied_gates): - """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) - if all([n in applied_gates for n in predecessors]): - return True - else: - return False - + 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) + if all([n in self.applied_gates for n in predecessors]): + return True + else: + return False + + 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 -def _obtain_extended_set(dag, front_layer, window_size): - """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) >= window_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) < window_size: try: - extended_set.add(next(successors)) + _, successors = next(node_successor_generator) + successors = list(filter(lambda x: x.type == 'op' and len(x.qargs) == 2, + successors)) except StopIteration: - break + node_lookahead_exhausted[i] = True + continue - return extended_set + 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(front_layer, current_layout, coupling_map): - """Return a list of candidate swaps that affect qubits in front_layer. + def _obtain_swaps(self, front_layer, current_layout): + """Return a list 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 = set() - for node in front_layer: - for virtual in node.qargs: - physical = current_layout[virtual] - for neighbor in coupling_map.neighbors(physical): - virtual_neighbor = current_layout[neighbor] - candidate_swaps.add((virtual, virtual_neighbor)) # TODO: sort so i,j is seen once. - - return list(candidate_swaps) - - -def _score_heuristic(front_layer, extended_set, layout, coupling_map, heuristic): - """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. - - Basic cost function: - 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)] - - Advanced cost function: - 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. |E| number of upcoming successors to gates in - front_layer F). This is weighted by some amount W to signify that - upcoming gates are less important that the front_layer. - The whole cost is multiplied by a decay factor. This increases the cost - if the SWAP that generated this trial layout was recently used (i.e. - it penalizes increase in depth). - - .. math:: - - H_{advanced} = 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)] - } - """ - # TODO: add decay - if heuristic == 'basic': - return sum(coupling_map.distance(*[layout[q] for q in node.qargs]) - for node in front_layer) - elif heuristic == 'advanced': - W = 0.5 - - first_cost = _score_heuristic(front_layer, [], layout, coupling_map, 'basic') - first_cost /= len(front_layer) - - second_cost = _score_heuristic(extended_set, [], layout, coupling_map, 'basic') - second_cost = 0.0 if not extended_set else second_cost / len(extended_set) - - return first_cost + W * second_cost - else: - raise TranspilerError('Heuristic %s not recognized.' % heuristic) + 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 list(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): diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index bfe686c74469..bf0408de6176 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -129,7 +129,7 @@ def _swap_condition(property_set): elif routing_method == 'lookahead': _swap += [LookaheadSwap(coupling_map, search_depth=5, search_width=6)] elif routing_method == 'sabre': - _swap += [SabreSwap(coupling_map, heuristic='advanced')] + _swap += [SabreSwap(coupling_map, heuristic='lookahead')] else: raise TranspilerError("Invalid routing method %s." % routing_method) diff --git a/test/python/transpiler/test_mappers.py b/test/python/transpiler/test_mappers.py index 097c8e4fb28d..7d00aba10366 100644 --- a/test/python/transpiler/test_mappers.py +++ b/test/python/transpiler/test_mappers.py @@ -284,6 +284,7 @@ class TestsStochasticSwap(SwapperCommonTestCases, QiskitTestCase): pass_class = StochasticSwap additional_args = {'seed': 0} + class TestsStochasticSwap(SwapperCommonTestCases, QiskitTestCase): """Test SwapperCommonTestCases using SabreSwap.""" pass_class = SabreSwap From 2fcbac1cbea798719f311b554c19f84d691adfcf Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Tue, 2 Jun 2020 01:19:25 -0500 Subject: [PATCH 11/34] restore lost qasm test files --- test/python/qasm/all_gates.qasm | 55 ++++++ test/python/qasm/example.qasm | 11 ++ test/python/qasm/example_fail.qasm | 1 + test/python/qasm/example_if.qasm | 26 +++ test/python/qasm/move_measurements.qasm | 212 ++++++++++++++++++++++++ test/python/qasm/random_n5_d5.qasm | 110 ++++++++++++ 6 files changed, 415 insertions(+) create mode 100644 test/python/qasm/all_gates.qasm create mode 100644 test/python/qasm/example.qasm create mode 100644 test/python/qasm/example_fail.qasm create mode 100644 test/python/qasm/example_if.qasm create mode 100644 test/python/qasm/move_measurements.qasm create mode 100644 test/python/qasm/random_n5_d5.qasm diff --git a/test/python/qasm/all_gates.qasm b/test/python/qasm/all_gates.qasm new file mode 100644 index 000000000000..2763e3704ac0 --- /dev/null +++ b/test/python/qasm/all_gates.qasm @@ -0,0 +1,55 @@ +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[3]; +creg c[3]; + +// note that the order and where the gates are applied to is important! + +// the hardware primitives +u3(0.2,0.1,0.6) q[0]; +u2(0.1,0.6) q[0]; +u1(0.6) q[0]; +id q[0]; +cx q[0], q[1]; + +// the standard single qubit gates +x q[0]; +y q[0]; +z q[0]; +h q[0]; +s q[0]; +t q[0]; +sdg q[0]; +tdg q[0]; + +// the standard rotations +rx(0.1) q[0]; +ry(0.1) q[0]; +rz(0.1) q[0]; + +// the barrier +barrier q; + +// the standard user-defined gates +swap q[0], q[1]; +cswap q[0], q[1], q[2]; + +cy q[0], q[1]; +cz q[0], q[1]; +ch q[0], q[1]; +cu1(0.6) q[0], q[1]; +cu3(0.2,0.1,0.6) q[0], q[1]; +ccx q[0], q[1], q[2]; + +crx(0.6) q[0], q[1]; +cry(0.6) q[0], q[1]; +crz(0.6) q[0], q[1]; + +rxx(0.2) q[0], q[1]; +rzz(0.2) q[0], q[1]; + +// measure +measure q->c; + + + diff --git a/test/python/qasm/example.qasm b/test/python/qasm/example.qasm new file mode 100644 index 000000000000..72195851f2ec --- /dev/null +++ b/test/python/qasm/example.qasm @@ -0,0 +1,11 @@ +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[3]; +qreg r[3]; +h q; +cx q, r; +creg c[3]; +creg d[3]; +barrier q; +measure q->c; +measure r->d; diff --git a/test/python/qasm/example_fail.qasm b/test/python/qasm/example_fail.qasm new file mode 100644 index 000000000000..c06113705ede --- /dev/null +++ b/test/python/qasm/example_fail.qasm @@ -0,0 +1 @@ +OPENQASM 2.0 diff --git a/test/python/qasm/example_if.qasm b/test/python/qasm/example_if.qasm new file mode 100644 index 000000000000..68fb5f170fd3 --- /dev/null +++ b/test/python/qasm/example_if.qasm @@ -0,0 +1,26 @@ +// Example file containing "if" instructions. +OPENQASM 2.0; +include "qelib1.inc"; +qreg qr0[1]; +creg cr0[1]; +h qr0[0]; +measure qr0[0] -> cr0[0]; +if(cr0==0) x qr0[0]; +h qr0[0]; +reset qr0[0]; +measure qr0[0] -> cr0[0]; +h qr0[0]; +reset qr0[0]; +u1(0.562367489586523) qr0[0]; +measure qr0[0] -> cr0[0]; +reset qr0[0]; +reset qr0[0]; +reset qr0[0]; +h qr0[0]; +measure qr0[0] -> cr0[0]; +if(cr0==1) x qr0[0]; +h qr0[0]; +u1(0.083178154631269) qr0[0]; +h qr0[0]; +reset qr0[0]; +measure qr0[0] -> cr0[0]; diff --git a/test/python/qasm/move_measurements.qasm b/test/python/qasm/move_measurements.qasm new file mode 100644 index 000000000000..846c61e864f6 --- /dev/null +++ b/test/python/qasm/move_measurements.qasm @@ -0,0 +1,212 @@ +OPENQASM 2.0; +include "qelib1.inc"; +qreg qa[2]; +qreg qb[3]; +qreg qN[3]; +qreg qc[1]; +qreg qNt[2]; +qreg qt[1]; +creg c[3]; +x qa[1]; +x qb[0]; +x qb[1]; +x qN[0]; +x qN[1]; +x qNt[0]; +x qNt[1]; +ccx qa[0],qb[0],qc[0]; +cx qa[0],qb[0]; +ccx qa[1],qb[1],qb[2]; +cx qa[1],qb[1]; +ccx qc[0],qb[1],qb[2]; +cx qa[1],qb[1]; +cx qa[1],qb[1]; +cx qc[0],qb[1]; +cx qa[0],qb[0]; +ccx qa[0],qb[0],qc[0]; +cx qa[0],qb[0]; +cx qN[0],qb[0]; +ccx qN[0],qb[0],qc[0]; +cx qN[0],qb[0]; +cx qc[0],qb[1]; +cx qN[1],qb[1]; +cx qN[1],qb[1]; +ccx qc[0],qb[1],qb[2]; +cx qN[1],qb[1]; +ccx qN[1],qb[1],qb[2]; +cx qN[0],qb[0]; +ccx qN[0],qb[0],qc[0]; +x qb[2]; +cx qb[2],qt[0]; +x qb[2]; +ccx qt[0],qNt[0],qN[0]; +h qc[0]; +ccx qt[0],qN[0],qc[0]; +tdg qc[0]; +ccx qt[0],qNt[0],qc[0]; +t qc[0]; +ccx qt[0],qN[0],qc[0]; +tdg qc[0]; +ccx qt[0],qNt[0],qc[0]; +t qc[0]; +h qc[0]; +t qN[0]; +ccx qt[0],qNt[0],qN[0]; +t qNt[0]; +tdg qN[0]; +ccx qt[0],qNt[0],qN[0]; +ccx qt[0],qNt[0],qN[0]; +ccx qt[0],qc[0],qN[1]; +ccx qt[0],qNt[1],qN[1]; +ccx qt[0],qNt[1],qN[1]; +h qN[2]; +ccx qt[0],qN[1],qN[2]; +tdg qN[2]; +ccx qt[0],qc[0],qN[2]; +t qN[2]; +ccx qt[0],qN[1],qN[2]; +tdg qN[2]; +ccx qt[0],qc[0],qN[2]; +t qN[2]; +h qN[2]; +t qN[1]; +ccx qt[0],qc[0],qN[1]; +t qc[0]; +tdg qN[1]; +ccx qt[0],qc[0],qN[1]; +ccx qt[0],qNt[1],qN[1]; +h qN[2]; +ccx qt[0],qN[1],qN[2]; +tdg qN[2]; +ccx qt[0],qNt[1],qN[2]; +t qN[2]; +ccx qt[0],qN[1],qN[2]; +tdg qN[2]; +ccx qt[0],qNt[1],qN[2]; +t qN[2]; +h qN[2]; +t qN[1]; +ccx qt[0],qNt[1],qN[1]; +t qNt[1]; +tdg qN[1]; +ccx qt[0],qNt[1],qN[1]; +ccx qt[0],qNt[0],qN[0]; +h qc[0]; +ccx qt[0],qN[0],qc[0]; +tdg qc[0]; +ccx qt[0],qNt[0],qc[0]; +t qc[0]; +ccx qt[0],qN[0],qc[0]; +tdg qc[0]; +ccx qt[0],qNt[0],qc[0]; +t qc[0]; +h qc[0]; +t qN[0]; +ccx qt[0],qNt[0],qN[0]; +t qNt[0]; +tdg qN[0]; +ccx qt[0],qNt[0],qN[0]; +ccx qN[0],qb[0],qc[0]; +cx qN[0],qb[0]; +ccx qN[1],qb[1],qb[2]; +cx qN[1],qb[1]; +ccx qc[0],qb[1],qb[2]; +cx qN[1],qb[1]; +cx qN[1],qb[1]; +cx qc[0],qb[1]; +cx qN[0],qb[0]; +ccx qN[0],qb[0],qc[0]; +cx qN[0],qb[0]; +h qc[0]; +ccx qt[0],qN[0],qc[0]; +tdg qc[0]; +ccx qt[0],qNt[0],qc[0]; +t qc[0]; +ccx qt[0],qN[0],qc[0]; +tdg qc[0]; +ccx qt[0],qNt[0],qc[0]; +t qc[0]; +h qc[0]; +t qN[0]; +ccx qt[0],qNt[0],qN[0]; +t qNt[0]; +tdg qN[0]; +ccx qt[0],qNt[0],qN[0]; +ccx qt[0],qNt[0],qN[0]; +h qN[2]; +ccx qt[0],qN[1],qN[2]; +tdg qN[2]; +ccx qt[0],qNt[1],qN[2]; +t qN[2]; +ccx qt[0],qN[1],qN[2]; +tdg qN[2]; +ccx qt[0],qNt[1],qN[2]; +t qN[2]; +h qN[2]; +t qN[1]; +ccx qt[0],qNt[1],qN[1]; +t qNt[1]; +tdg qN[1]; +ccx qt[0],qNt[1],qN[1]; +ccx qt[0],qNt[1],qN[1]; +h qN[2]; +ccx qt[0],qN[1],qN[2]; +tdg qN[2]; +ccx qt[0],qc[0],qN[2]; +t qN[2]; +ccx qt[0],qN[1],qN[2]; +tdg qN[2]; +ccx qt[0],qc[0],qN[2]; +t qN[2]; +h qN[2]; +t qN[1]; +ccx qt[0],qc[0],qN[1]; +t qc[0]; +tdg qN[1]; +ccx qt[0],qc[0],qN[1]; +ccx qt[0],qNt[1],qN[1]; +ccx qt[0],qNt[1],qN[1]; +ccx qt[0],qc[0],qN[1]; +ccx qt[0],qNt[0],qN[0]; +h qc[0]; +ccx qt[0],qN[0],qc[0]; +tdg qc[0]; +ccx qt[0],qNt[0],qc[0]; +t qc[0]; +ccx qt[0],qN[0],qc[0]; +tdg qc[0]; +ccx qt[0],qNt[0],qc[0]; +t qc[0]; +h qc[0]; +t qN[0]; +ccx qt[0],qNt[0],qN[0]; +t qNt[0]; +tdg qN[0]; +ccx qt[0],qNt[0],qN[0]; +ccx qt[0],qNt[0],qN[0]; +cx qa[0],qb[0]; +ccx qa[0],qb[0],qc[0]; +cx qa[0],qb[0]; +cx qc[0],qb[1]; +cx qa[1],qb[1]; +cx qa[1],qb[1]; +ccx qc[0],qb[1],qb[2]; +cx qa[1],qb[1]; +ccx qa[1],qb[1],qb[2]; +cx qa[0],qb[0]; +ccx qa[0],qb[0],qc[0]; +cx qb[2],qt[0]; +ccx qa[0],qb[0],qc[0]; +cx qa[0],qb[0]; +ccx qa[1],qb[1],qb[2]; +cx qa[1],qb[1]; +ccx qc[0],qb[1],qb[2]; +cx qa[1],qb[1]; +cx qa[1],qb[1]; +cx qc[0],qb[1]; +cx qa[0],qb[0]; +ccx qa[0],qb[0],qc[0]; +cx qa[0],qb[0]; +measure qb[0] -> c[0]; +measure qb[1] -> c[1]; +measure qb[2] -> c[2]; \ No newline at end of file diff --git a/test/python/qasm/random_n5_d5.qasm b/test/python/qasm/random_n5_d5.qasm new file mode 100644 index 000000000000..5c2f6021ee82 --- /dev/null +++ b/test/python/qasm/random_n5_d5.qasm @@ -0,0 +1,110 @@ +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[5]; +creg c[5]; +u3(0.634346715653683,0.880910487587674,0.827499019150487) q[1]; +u3(1.484323068020462,-0.218212999454475,-3.592363844052204) q[0]; +cx q[0],q[1]; +u1(3.308485833921549) q[1]; +u3(-1.810211319750651,0.000000000000000,0.000000000000000) q[0]; +cx q[1],q[0]; +u3(1.099272036188136,0.000000000000000,0.000000000000000) q[0]; +cx q[0],q[1]; +u3(2.421859755364273,2.353435858968482,-2.973056963234994) q[1]; +u3(2.455896940634234,0.124779338899211,0.037434518192186) q[0]; +u3(1.129855614911814,1.049626985018942,-3.212204913542003) q[3]; +u3(0.875794477896647,-2.759645955801507,3.190456341307370) q[2]; +cx q[2],q[3]; +u1(1.511890404671010) q[3]; +u3(0.293141728119888,0.000000000000000,0.000000000000000) q[2]; +cx q[3],q[2]; +u3(1.016455330162957,0.000000000000000,0.000000000000000) q[2]; +cx q[2],q[3]; +u3(0.334309092748757,0.521417129139045,1.088703327654617) q[3]; +u3(2.078918628857339,-5.853779889473016,0.117993422312274) q[2]; +u3(0.066133062177417,-1.138507778410453,0.171231283320043) q[2]; +u3(0.533649199725493,-3.175942607101125,1.605744443352545) q[1]; +cx q[1],q[2]; +u1(3.757413769360157) q[2]; +u3(-3.605282263355746,0.000000000000000,0.000000000000000) q[1]; +cx q[2],q[1]; +u3(-0.958110312777987,0.000000000000000,0.000000000000000) q[1]; +cx q[1],q[2]; +u3(0.827682817458717,-1.720505332888779,2.154562394941609) q[2]; +u3(1.817140010299614,-1.220359595860445,4.688766804035865) q[1]; +u3(1.341045551718784,-0.608686333717184,-1.756587055231964) q[4]; +u3(1.561872017694270,1.387145535856301,-4.379023525190993) q[0]; +cx q[0],q[4]; +u1(0.484884767345497) q[4]; +u3(-3.265271371905420,0.000000000000000,0.000000000000000) q[0]; +cx q[4],q[0]; +u3(1.860045495435328,0.000000000000000,0.000000000000000) q[0]; +cx q[0],q[4]; +u3(1.695221563500504,-2.888422194660264,2.978375458262739) q[4]; +u3(0.604973257101321,5.096821375484736,0.151580711829459) q[0]; +u3(2.264838682685293,2.192061313298150,-0.415843311326973) q[0]; +u3(2.008823010418951,-0.909941656301332,-4.417998093169953) q[3]; +cx q[3],q[0]; +u1(0.510524156727350) q[0]; +u3(-1.207202180779869,0.000000000000000,0.000000000000000) q[3]; +cx q[0],q[3]; +u3(2.960945140232618,0.000000000000000,0.000000000000000) q[3]; +cx q[3],q[0]; +u3(3.061662326494465,-0.624418514911125,2.466421200808710) q[0]; +u3(1.325006076330307,-3.192834170078656,-1.726581820210002) q[3]; +u3(1.256450084601955,-0.556520865508894,1.699053270512565) q[2]; +u3(1.300439999199225,-1.817379150670086,-1.939257837183832) q[1]; +cx q[1],q[2]; +u1(2.042054298937185) q[2]; +u3(0.712518803857714,0.000000000000000,0.000000000000000) q[1]; +cx q[2],q[1]; +u3(1.701399622828943,0.000000000000000,0.000000000000000) q[1]; +cx q[1],q[2]; +u3(2.363510068172205,3.344182218420100,-0.352403693269645) q[2]; +u3(2.736552984228416,1.857400895757674,-3.458744088625263) q[1]; +u3(1.885221313272570,-4.111246600359667,1.263896163963675) q[4]; +u3(2.245883757569570,0.415789947336587,3.592639595348905) q[2]; +cx q[2],q[4]; +u1(1.471229415145194) q[4]; +u3(-1.146228063273526,0.000000000000000,0.000000000000000) q[2]; +cx q[4],q[2]; +u3(2.706742224447916,0.000000000000000,0.000000000000000) q[2]; +cx q[2],q[4]; +u3(2.208226126480162,1.577197878815174,-2.830849787565416) q[4]; +u3(0.896122652728372,-0.226711120411633,5.392354155993746) q[2]; +u3(0.416020400069302,3.810392878194613,-1.283379742380341) q[1]; +u3(1.960920589455032,2.309523850490192,-1.514823210880829) q[0]; +cx q[0],q[1]; +u1(2.024620895333101) q[1]; +u3(0.226083454073590,0.000000000000000,0.000000000000000) q[0]; +cx q[1],q[0]; +u3(0.884590377979485,0.000000000000000,0.000000000000000) q[0]; +cx q[0],q[1]; +u3(2.545573385995641,-2.012145518385366,0.264220290428662) q[1]; +u3(2.005619037013396,-1.905747310870419,-2.347933504260405) q[0]; +u3(2.243678899063514,-0.428716035636104,1.818381814503391) q[2]; +u3(2.749359171104891,-2.011059232776085,-0.228840744181900) q[1]; +cx q[1],q[2]; +u1(1.378046823197370) q[2]; +u3(-0.723820608259067,0.000000000000000,0.000000000000000) q[1]; +cx q[2],q[1]; +u3(-0.255642156738184,0.000000000000000,0.000000000000000) q[1]; +cx q[1],q[2]; +u3(2.983520707154838,-1.112747818281526,3.123895568815588) q[2]; +u3(1.740467732269037,0.515041546225282,5.397279301019271) q[1]; +u3(2.593150404530149,1.013497671346222,-1.386610389037105) q[0]; +u3(1.772531076135327,0.810011859752890,-4.799091878810430) q[3]; +cx q[3],q[0]; +u1(4.063446914963040) q[0]; +u3(-3.243270441698034,0.000000000000000,0.000000000000000) q[3]; +cx q[0],q[3]; +u3(-0.473197807476301,0.000000000000000,0.000000000000000) q[3]; +cx q[3],q[0]; +u3(2.078571935001867,0.638938443408619,-2.128941975613032) q[0]; +u3(0.540247300850280,-5.624086444202922,-0.051515330897875) q[3]; +barrier q[0],q[1],q[2],q[3],q[4]; +measure q[0] -> c[0]; +measure q[1] -> c[1]; +measure q[2] -> c[2]; +measure q[3] -> c[3]; +measure q[4] -> c[4]; From dc57e5940af7c0b8563568d73197e16af7d9e7cf Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Tue, 2 Jun 2020 08:26:31 -0500 Subject: [PATCH 12/34] fix tests --- qiskit/circuit/bit.py | 2 +- qiskit/transpiler/preset_passmanagers/level1.py | 2 +- qiskit/transpiler/preset_passmanagers/level2.py | 2 +- qiskit/transpiler/preset_passmanagers/level3.py | 2 +- test/python/qasm/TestsBasicSwap_a_cx_to_map.qasm | 2 +- .../python/qasm/TestsBasicSwap_handle_measurement.qasm | 4 ++-- test/python/qasm/TestsBasicSwap_initial_layout.qasm | 2 +- test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm | 10 +++++----- .../qasm/TestsStochasticSwap_handle_measurement.qasm | 4 ++-- .../qasm/TestsStochasticSwap_initial_layout.qasm | 4 ++-- test/python/transpiler/test_sabre_swap.py | 6 +++--- 11 files changed, 20 insertions(+), 20 deletions(-) diff --git a/qiskit/circuit/bit.py b/qiskit/circuit/bit.py index 618f8878defb..be6cec0305b9 100644 --- a/qiskit/circuit/bit.py +++ b/qiskit/circuit/bit.py @@ -70,7 +70,7 @@ def index(self, value): def __repr__(self): """Return the official string representing the bit.""" - return "%s[%s]" % (self._register.name, self._index) + return "%s(%s, %s)" % (self.__class__.__name__, self._register, self._index) def __hash__(self): return self._hash diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 0a66961926ed..32e329934c8f 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -127,7 +127,7 @@ def _swap_condition(property_set): elif routing_method == 'lookahead': _swap += [LookaheadSwap(coupling_map, search_depth=4, search_width=4)] elif routing_method == 'sabre': - _swap += [SabreSwap(coupling_map, heuristic='advanced')] + _swap += [SabreSwap(coupling_map, heuristic='lookahead')] else: raise TranspilerError("Invalid routing method %s." % routing_method) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 0206e57b74c0..12f13773c00e 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -122,7 +122,7 @@ def _swap_condition(property_set): elif routing_method == 'lookahead': _swap += [LookaheadSwap(coupling_map, search_depth=5, search_width=5)] elif routing_method == 'sabre': - _swap += [SabreSwap(coupling_map, heuristic='advanced')] + _swap += [SabreSwap(coupling_map, heuristic='decay')] else: raise TranspilerError("Invalid routing method %s." % routing_method) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index bf0408de6176..6264ab979a6d 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -129,7 +129,7 @@ def _swap_condition(property_set): elif routing_method == 'lookahead': _swap += [LookaheadSwap(coupling_map, search_depth=5, search_width=6)] elif routing_method == 'sabre': - _swap += [SabreSwap(coupling_map, heuristic='lookahead')] + _swap += [SabreSwap(coupling_map, heuristic='decay')] else: raise TranspilerError("Invalid routing method %s." % routing_method) diff --git a/test/python/qasm/TestsBasicSwap_a_cx_to_map.qasm b/test/python/qasm/TestsBasicSwap_a_cx_to_map.qasm index aa4582c452d0..a616ac1144a2 100644 --- a/test/python/qasm/TestsBasicSwap_a_cx_to_map.qasm +++ b/test/python/qasm/TestsBasicSwap_a_cx_to_map.qasm @@ -2,9 +2,9 @@ OPENQASM 2.0; include "qelib1.inc"; qreg q[3]; creg c[3]; -measure q[0] -> c[0]; h q[1]; swap q[1],q[0]; cx q[0],q[2]; +measure q[1] -> c[0]; measure q[0] -> c[1]; measure q[2] -> c[2]; diff --git a/test/python/qasm/TestsBasicSwap_handle_measurement.qasm b/test/python/qasm/TestsBasicSwap_handle_measurement.qasm index 314e581cc181..360d1f057192 100644 --- a/test/python/qasm/TestsBasicSwap_handle_measurement.qasm +++ b/test/python/qasm/TestsBasicSwap_handle_measurement.qasm @@ -3,12 +3,12 @@ include "qelib1.inc"; qreg q[4]; creg c[4]; cx q[0],q[1]; -measure q[2] -> c[2]; h q[3]; swap q[3],q[2]; cx q[2],q[1]; -measure q[1] -> c[1]; swap q[2],q[1]; cx q[1],q[0]; measure q[0] -> c[0]; +measure q[2] -> c[1]; +measure q[3] -> c[2]; measure q[1] -> c[3]; diff --git a/test/python/qasm/TestsBasicSwap_initial_layout.qasm b/test/python/qasm/TestsBasicSwap_initial_layout.qasm index eac63472d3b4..21de2ee9be0a 100644 --- a/test/python/qasm/TestsBasicSwap_initial_layout.qasm +++ b/test/python/qasm/TestsBasicSwap_initial_layout.qasm @@ -2,10 +2,10 @@ OPENQASM 2.0; include "qelib1.inc"; qreg q[4]; creg c[4]; -measure q[0] -> c[0]; h q[1]; swap q[1],q[0]; cx q[0],q[2]; +measure q[1] -> c[0]; measure q[0] -> c[1]; measure q[2] -> c[2]; measure q[3] -> c[3]; diff --git a/test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm b/test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm index aa4582c452d0..bceabc6db0ab 100644 --- a/test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm +++ b/test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm @@ -2,9 +2,9 @@ OPENQASM 2.0; include "qelib1.inc"; qreg q[3]; creg c[3]; -measure q[0] -> c[0]; h q[1]; -swap q[1],q[0]; -cx q[0],q[2]; -measure q[0] -> c[1]; -measure q[2] -> c[2]; +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]; diff --git a/test/python/qasm/TestsStochasticSwap_handle_measurement.qasm b/test/python/qasm/TestsStochasticSwap_handle_measurement.qasm index a685bfeed415..80754de18a52 100644 --- a/test/python/qasm/TestsStochasticSwap_handle_measurement.qasm +++ b/test/python/qasm/TestsStochasticSwap_handle_measurement.qasm @@ -3,9 +3,9 @@ include "qelib1.inc"; qreg q[4]; creg c[4]; cx q[0],q[1]; -measure q[2] -> c[2]; h q[3]; -swap q[3],q[2]; +measure q[2] -> c[2]; +swap q[2],q[3]; cx q[2],q[1]; measure q[1] -> c[1]; swap q[0],q[1]; diff --git a/test/python/qasm/TestsStochasticSwap_initial_layout.qasm b/test/python/qasm/TestsStochasticSwap_initial_layout.qasm index eac63472d3b4..76adf18d61fc 100644 --- a/test/python/qasm/TestsStochasticSwap_initial_layout.qasm +++ b/test/python/qasm/TestsStochasticSwap_initial_layout.qasm @@ -2,9 +2,9 @@ OPENQASM 2.0; include "qelib1.inc"; qreg q[4]; creg c[4]; -measure q[0] -> c[0]; h q[1]; -swap q[1],q[0]; +measure q[0] -> c[0]; +swap q[0],q[1]; cx q[0],q[2]; measure q[0] -> c[1]; measure q[2] -> c[2]; diff --git a/test/python/transpiler/test_sabre_swap.py b/test/python/transpiler/test_sabre_swap.py index 118a0d1ad764..7787fc1030cd 100644 --- a/test/python/transpiler/test_sabre_swap.py +++ b/test/python/transpiler/test_sabre_swap.py @@ -57,8 +57,8 @@ def test_trivial_case(self): self.assertEqual(new_qc, qc) - def test_advanced_mode(self): - """Test advanced mode's lookahead finds single SWAP gate. + def test_lookahead_mode(self): + """Test lookahead mode's lookahead finds single SWAP gate. ┌───┐ q_0: ──■──┤ H ├─────────────── ┌─┴─┐└───┘ @@ -83,7 +83,7 @@ def test_advanced_mode(self): qc.cx(2, 3) # E qc.cx(1, 3) # E - pm = PassManager(SabreSwap(coupling, 'advanced')) + pm = PassManager(SabreSwap(coupling, 'lookahead')) new_qc = pm.run(qc) self.assertEqual(new_qc.num_nonlocal_gates(), 7) From b35c025dca487a7b48ba7062a52e13b7a8fd8779 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Tue, 2 Jun 2020 12:54:39 -0500 Subject: [PATCH 13/34] leave SimplifyU3 for later --- qiskit/transpiler/passes/__init__.py | 2 - .../passes/optimization/__init__.py | 1 - .../passes/optimization/simplify_u3.py | 100 ------------------ .../transpiler/preset_passmanagers/level3.py | 4 +- 4 files changed, 1 insertion(+), 106 deletions(-) delete mode 100644 qiskit/transpiler/passes/optimization/simplify_u3.py diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 5291482f8749..6d1202bce5ec 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -74,7 +74,6 @@ RemoveDiagonalGatesBeforeMeasure RemoveResetInZeroState CrosstalkAdaptiveSchedule - SimplifyU3 Circuit Analysis ================ @@ -145,7 +144,6 @@ from .optimization import RemoveDiagonalGatesBeforeMeasure from .optimization import CrosstalkAdaptiveSchedule from .optimization import HoareOptimizer -from .optimization import SimplifyU3 # circuit analysis from .analysis import ResourceEstimation diff --git a/qiskit/transpiler/passes/optimization/__init__.py b/qiskit/transpiler/passes/optimization/__init__.py index da02c3b6cc53..52d815534c38 100644 --- a/qiskit/transpiler/passes/optimization/__init__.py +++ b/qiskit/transpiler/passes/optimization/__init__.py @@ -25,4 +25,3 @@ from .remove_diagonal_gates_before_measure import RemoveDiagonalGatesBeforeMeasure from .crosstalk_adaptive_schedule import CrosstalkAdaptiveSchedule from .hoare_opt import HoareOptimizer -from .simplify_u3 import SimplifyU3 diff --git a/qiskit/transpiler/passes/optimization/simplify_u3.py b/qiskit/transpiler/passes/optimization/simplify_u3.py deleted file mode 100644 index 62ea27c3b18f..000000000000 --- a/qiskit/transpiler/passes/optimization/simplify_u3.py +++ /dev/null @@ -1,100 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2018. -# -# 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. - -"""A strength reduction pass to simplify single qubit U3 gates, if possible. -""" - -import numpy as np - -from qiskit.circuit.library.standard_gates import U1Gate -from qiskit.circuit.library.standard_gates import U2Gate -from qiskit.circuit.library.standard_gates import U3Gate -from qiskit.circuit.controlledgate import ControlledGate -from qiskit.circuit.add_control import add_control -from qiskit.transpiler.basepasses import TransformationPass -from qiskit.quantum_info import Operator - - -DEFAULT_ATOL = 1e-12 - - -class SimplifyU3(TransformationPass): - """A strength reduction pass to simplify single qubit U3 gates, if possible. - The cost metric is the number of X90 pulses required to implement the gate. - Can convert U3 -> U2 OR U1 OR None. - Also makes all Euler angles modulo 2*pi. - - Additional Information - ---------------------- - U3(θ,φ,λ) is a generic single-qubit gate (generic Bloch sphere rotation). - It can be realized with TWO pre-calibrated X90 pulses. - Example: X gate. - U2(φ,λ) is a rotation around the Z+X axis (with co-efficient). It can - be implemented using ONE pre-calibrated X90 pulse. - Example: H gate. - U1(λ) is a rotation about the Z axis. It requires ZERO pulses (i.e. - done virtually in software). - Example: T gate. - """ - - def run(self, dag): - """Run the SimplifyU3 pass on `dag`. - Args: - dag (DAGCircuit): the DAG to be optimized. - Returns: - DAGCircuit: the optimized DAG. - """ - for node in dag.gate_nodes(): - op = node.op - num_ctrl_qubits = None - - if op.is_parameterized(): - continue - - if isinstance(node.op, ControlledGate): - num_ctrl_qubits = op.num_ctrl_qubits - op = node.op.base_gate - - if isinstance(op, U3Gate): - theta, phi, lam = op.params - - new_op = U3Gate(_mod2pi(theta), _mod2pi(phi), _mod2pi(lam)) - - if np.isclose(_mod2pi(theta), [0., 2*np.pi], atol=DEFAULT_ATOL).any(): - if np.isclose(_mod2pi(phi+lam), [0., 2*np.pi], atol=DEFAULT_ATOL).any(): - new_op = None - else: - new_op = U1Gate(_mod2pi(phi+lam)) - - elif np.isclose(theta, [np.pi/2, 3*np.pi/2], atol=DEFAULT_ATOL).any(): - new_op = U2Gate(_mod2pi(phi+theta-np.pi/2), _mod2pi(lam+theta-np.pi/2)) - - else: - new_op = U3Gate(_mod2pi(theta), _mod2pi(phi), _mod2pi(lam)) - - if new_op is None: - dag.remove_op_node(node) - else: - if num_ctrl_qubits is not None: - new_op = add_control(new_op, num_ctrl_qubits) - dag.substitute_node(node, new_op) - - return dag - - -def _mod2pi(angle): - if angle >= 0: - return np.mod(angle, 2*np.pi) - else: - return np.mod(angle, -2*np.pi) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 6264ab979a6d..a4c46e86d031 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -49,7 +49,6 @@ from qiskit.transpiler.passes import ConsolidateBlocks from qiskit.transpiler.passes import ApplyLayout from qiskit.transpiler.passes import CheckCXDirection -from qiskit.transpiler.passes import SimplifyU3 from qiskit.transpiler.passes import Approx2qDecompose from qiskit.transpiler import TranspilerError @@ -155,8 +154,7 @@ def _opt_control(property_set): _opt = [Collect2qBlocks(), ConsolidateBlocks(), Approx2qDecompose(fidelity=synthesis_fidelity), - Optimize1qGates(basis_gates), CommutativeCancellation(), - SimplifyU3()] + Optimize1qGates(basis_gates), CommutativeCancellation()] # 9. Remove useless gates before measure. _meas = [OptimizeSwapBeforeMeasure(), RemoveDiagonalGatesBeforeMeasure()] From 9257639ed52ff2e37aeb5219a39278c4fbbf07d7 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Tue, 2 Jun 2020 12:58:21 -0500 Subject: [PATCH 14/34] leave Approx2qDecompose for later --- qiskit/compiler/transpile.py | 19 ++------- qiskit/transpiler/passes/__init__.py | 1 - qiskit/transpiler/passes/basis/__init__.py | 1 - .../passes/basis/approx_2q_decompose.py | 42 ------------------- qiskit/transpiler/passmanager_config.py | 3 -- .../transpiler/preset_passmanagers/level3.py | 3 -- 6 files changed, 4 insertions(+), 65 deletions(-) delete mode 100644 qiskit/transpiler/passes/basis/approx_2q_decompose.py diff --git a/qiskit/compiler/transpile.py b/qiskit/compiler/transpile.py index 3b535ed5a61b..7b9ad730dc63 100644 --- a/qiskit/compiler/transpile.py +++ b/qiskit/compiler/transpile.py @@ -46,7 +46,6 @@ def transpile(circuits: Union[QuantumCircuit, List[QuantumCircuit]], backend_properties: Optional[BackendProperties] = None, initial_layout: Optional[Union[Layout, Dict, List]] = None, layout_method: Optional[str] = None, - synthesis_fidelity: Optional[float] = None, routing_method: Optional[str] = None, seed_transpiler: Optional[int] = None, optimization_level: Optional[int] = None, @@ -120,7 +119,6 @@ def transpile(circuits: Union[QuantumCircuit, List[QuantumCircuit]], 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', 'sabre') - synthesis_fidelity (float): tolerable fidelity for approximate synthesis. 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, @@ -188,7 +186,6 @@ def callback_func(**kwargs): backend_properties=backend_properties, initial_layout=initial_layout, layout_method=layout_method, routing_method=routing_method, - synthesis_fidelity=synthesis_fidelity, backend=backend) warnings.warn("The parameter pass_manager in transpile is being deprecated. " @@ -204,7 +201,7 @@ def callback_func(**kwargs): # Get transpile_args to configure the circuit transpilation job(s) transpile_args = _parse_transpile_args(circuits, backend, basis_gates, coupling_map, backend_properties, initial_layout, - layout_method, routing_method, synthesis_fidelity, + layout_method, routing_method, seed_transpiler, optimization_level, callback, output_name) @@ -314,7 +311,7 @@ def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict]) -> Qua def _parse_transpile_args(circuits, backend, basis_gates, coupling_map, backend_properties, initial_layout, layout_method, routing_method, - synthesis_fidelity, seed_transpiler, + seed_transpiler, optimization_level, callback, output_name) -> List[Dict]: """Resolve the various types of args allowed to the transpile() function through duck typing, overriding args, etc. Refer to the transpile() docstring for details on @@ -340,7 +337,6 @@ def _parse_transpile_args(circuits, backend, initial_layout = _parse_initial_layout(initial_layout, circuits) layout_method = _parse_layout_method(layout_method, num_circuits) routing_method = _parse_routing_method(routing_method, num_circuits) - synthesis_fidelity = _parse_synthesis_fidelity(synthesis_fidelity, num_circuits) seed_transpiler = _parse_seed_transpiler(seed_transpiler, num_circuits) optimization_level = _parse_optimization_level(optimization_level, num_circuits) output_name = _parse_output_name(output_name, circuits) @@ -349,7 +345,7 @@ def _parse_transpile_args(circuits, backend, list_transpile_args = [] for args in zip(basis_gates, coupling_map, backend_properties, initial_layout, layout_method, routing_method, - synthesis_fidelity, seed_transpiler, + seed_transpiler, optimization_level, output_name, callback): transpile_args = {'pass_manager_config': PassManagerConfig(basis_gates=args[0], coupling_map=args[1], @@ -357,8 +353,7 @@ def _parse_transpile_args(circuits, backend, initial_layout=args[3], layout_method=args[4], routing_method=args[5], - synthesis_fidelity=args[6], - seed_transpiler=args[7]), + seed_transpiler=args[6]), 'optimization_level': args[8], 'output_name': args[9], 'callback': args[10]} @@ -452,12 +447,6 @@ def _parse_routing_method(routing_method, num_circuits): return routing_method -def _parse_synthesis_fidelity(synthesis_fidelity, num_circuits): - if not isinstance(synthesis_fidelity, list): - synthesis_fidelity = [synthesis_fidelity] * num_circuits - return synthesis_fidelity - - def _parse_seed_transpiler(seed_transpiler, num_circuits): if not isinstance(seed_transpiler, list): seed_transpiler = [seed_transpiler] * num_circuits diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 6d1202bce5ec..2179bac17a83 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -130,7 +130,6 @@ from .basis import UnrollCustomDefinitions from .basis import Unroll3qOrMore from .basis import BasisTranslator -from .basis import Approx2qDecompose # optimization from .optimization import Optimize1qGates diff --git a/qiskit/transpiler/passes/basis/__init__.py b/qiskit/transpiler/passes/basis/__init__.py index e725dd207c32..692e50d56188 100644 --- a/qiskit/transpiler/passes/basis/__init__.py +++ b/qiskit/transpiler/passes/basis/__init__.py @@ -19,4 +19,3 @@ from .unroll_custom_definitions import UnrollCustomDefinitions from .unroll_3q_or_more import Unroll3qOrMore from .basis_translator import BasisTranslator -from .approx_2q_decompose import Approx2qDecompose diff --git a/qiskit/transpiler/passes/basis/approx_2q_decompose.py b/qiskit/transpiler/passes/basis/approx_2q_decompose.py deleted file mode 100644 index 82f3c4b80b07..000000000000 --- a/qiskit/transpiler/passes/basis/approx_2q_decompose.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- 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. - -"""Expand 2-qubit unitaries into a 2-qubit basis, optionally approximating.""" - -from qiskit.quantum_info.synthesis.two_qubit_decompose import two_qubit_cnot_decompose -from qiskit.circuit import QuantumRegister -from qiskit.transpiler.basepasses import TransformationPass -from qiskit.dagcircuit import DAGCircuit - - -class Approx2qDecompose(TransformationPass): - """Expand 2-qubit unitaries into a 2-qubit basis, optionally approximating.""" - - def __init__(self, fidelity=1): - super().__init__() - self.fidelity = fidelity - - def run(self, dag): - qr = QuantumRegister(2) - for node in dag.named_nodes("unitary"): - if len(node.qargs) != 2: - continue - rule = two_qubit_cnot_decompose(node.op, basis_fidelity=self.fidelity) - decomposition = DAGCircuit() - decomposition.add_qreg(rule[0][1][0].register) - for inst in rule: - decomposition.apply_operation_back(*inst) - - dag.substitute_node_with_dag(node, decomposition) - return dag diff --git a/qiskit/transpiler/passmanager_config.py b/qiskit/transpiler/passmanager_config.py index 3853e58ab279..392d5ac09b6f 100644 --- a/qiskit/transpiler/passmanager_config.py +++ b/qiskit/transpiler/passmanager_config.py @@ -26,7 +26,6 @@ def __init__(self, layout_method=None, routing_method=None, backend_properties=None, - synthesis_fidelity=None, seed_transpiler=None): """Initialize a PassManagerConfig object @@ -43,7 +42,6 @@ def __init__(self, backend_properties (BackendProperties): Properties returned by a backend, including information on gate errors, readout errors, qubit coherence times, etc. - synthesis_fidelity (float): tolerable fidelity for approximate synthesis. seed_transpiler (int): Sets random seed for the stochastic parts of the transpiler. """ @@ -53,5 +51,4 @@ def __init__(self, self.layout_method = layout_method self.routing_method = routing_method self.backend_properties = backend_properties - self.synthesis_fidelity = synthesis_fidelity self.seed_transpiler = seed_transpiler diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index a4c46e86d031..e0023fe85400 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -49,7 +49,6 @@ from qiskit.transpiler.passes import ConsolidateBlocks from qiskit.transpiler.passes import ApplyLayout from qiskit.transpiler.passes import CheckCXDirection -from qiskit.transpiler.passes import Approx2qDecompose from qiskit.transpiler import TranspilerError @@ -88,7 +87,6 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: routing_method = pass_manager_config.routing_method or 'stochastic' seed_transpiler = pass_manager_config.seed_transpiler backend_properties = pass_manager_config.backend_properties - synthesis_fidelity = pass_manager_config.synthesis_fidelity # 1. Unroll to 1q or 2q gates _unroll3q = Unroll3qOrMore() @@ -153,7 +151,6 @@ def _opt_control(property_set): return not property_set['depth_fixed_point'] _opt = [Collect2qBlocks(), ConsolidateBlocks(), - Approx2qDecompose(fidelity=synthesis_fidelity), Optimize1qGates(basis_gates), CommutativeCancellation()] # 9. Remove useless gates before measure. From 7c0dcd3b5783985543753d8e654dd1f55ad26e4d Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Tue, 2 Jun 2020 16:12:52 -0500 Subject: [PATCH 15/34] Release notes Co-authored-by: Gushu Li --- qiskit/compiler/transpile.py | 6 +++--- releasenotes/notes/sabre-2a3bba505e48ee82.yaml | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/sabre-2a3bba505e48ee82.yaml diff --git a/qiskit/compiler/transpile.py b/qiskit/compiler/transpile.py index 7b9ad730dc63..db7c4e5f509b 100644 --- a/qiskit/compiler/transpile.py +++ b/qiskit/compiler/transpile.py @@ -354,9 +354,9 @@ def _parse_transpile_args(circuits, backend, layout_method=args[4], routing_method=args[5], seed_transpiler=args[6]), - 'optimization_level': args[8], - 'output_name': args[9], - 'callback': args[10]} + 'optimization_level': args[7], + 'output_name': args[8], + 'callback': args[9]} list_transpile_args.append(transpile_args) return list_transpile_args diff --git a/releasenotes/notes/sabre-2a3bba505e48ee82.yaml b/releasenotes/notes/sabre-2a3bba505e48ee82.yaml new file mode 100644 index 000000000000..dee12e47a076 --- /dev/null +++ b/releasenotes/notes/sabre-2a3bba505e48ee82.yaml @@ -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. From adeccf4ca421697e0fc397c6d66292043f1bff71 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Thu, 4 Jun 2020 00:08:26 -0500 Subject: [PATCH 16/34] lint --- test/python/transpiler/test_sabre_swap.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/python/transpiler/test_sabre_swap.py b/test/python/transpiler/test_sabre_swap.py index 7787fc1030cd..57a605f35c6f 100644 --- a/test/python/transpiler/test_sabre_swap.py +++ b/test/python/transpiler/test_sabre_swap.py @@ -17,9 +17,7 @@ import unittest from qiskit.transpiler.passes import SabreSwap from qiskit.transpiler import CouplingMap, PassManager -from qiskit.transpiler.exceptions import TranspilerError -from qiskit.converters import circuit_to_dag -from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit +from qiskit import QuantumRegister, QuantumCircuit from qiskit.test import QiskitTestCase @@ -52,8 +50,8 @@ def test_trivial_case(self): qc.cx(4, 3) # F qc.cx(0, 4) - pm = PassManager(SabreSwap(coupling, 'basic')) - new_qc = pm.run(qc) + passmanager = PassManager(SabreSwap(coupling, 'basic')) + new_qc = passmanager.run(qc) self.assertEqual(new_qc, qc) From 2afd8d5860a71a8570586aa6a8897af191451d66 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Fri, 5 Jun 2020 17:13:20 -0500 Subject: [PATCH 17/34] update level 3 --- .../transpiler/passes/layout/sabre_layout.py | 2 +- .../transpiler/preset_passmanagers/level3.py | 21 ++++++++----------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/qiskit/transpiler/passes/layout/sabre_layout.py b/qiskit/transpiler/passes/layout/sabre_layout.py index 38c16d8dc57f..ee1aed5a67a5 100644 --- a/qiskit/transpiler/passes/layout/sabre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_layout.py @@ -121,7 +121,7 @@ def _compose_layouts(initial_layout, pass_final_layout, qregs): for i, q in enumerate(physical_qubits)}) if self.routing_pass is None: - self.routing_pass = SabreSwap(self.coupling_map, 'advanced') + self.routing_pass = SabreSwap(self.coupling_map, 'decay') # Do forward-backward iterations. circ = dag_to_circuit(dag) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index e0023fe85400..45d6bc0469c3 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -131,7 +131,7 @@ def _swap_condition(property_set): raise TranspilerError("Invalid routing method %s." % routing_method) # 5. Unroll to the basis - _unroll = Unroller(basis_gates) + _unroll = [Unroller(basis_gates)] # 6. Fix any CX direction mismatch _direction_check = [CheckCXDirection(coupling_map)] @@ -141,21 +141,20 @@ def _direction_condition(property_set): _direction = [CXDirection(coupling_map)] - # 7. Remove zero-state reset - _reset = RemoveResetInZeroState() - - # 8. 1q rotation merge and commutative cancellation iteratively until no more change in depth + # 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'] - _opt = [Collect2qBlocks(), ConsolidateBlocks(), - Optimize1qGates(basis_gates), CommutativeCancellation()] + _reset = [RemoveResetInZeroState()] - # 9. Remove useless gates before measure. _meas = [OptimizeSwapBeforeMeasure(), RemoveDiagonalGatesBeforeMeasure()] + _opt = [Collect2qBlocks(), ConsolidateBlocks(), + Optimize1qGates(basis_gates), CommutativeCancellation()] + # Build pass manager pm3 = PassManager() pm3.append(_unroll3q) @@ -164,15 +163,13 @@ def _opt_control(property_set): 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(_unroll) - 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) - pm3.append(_meas) return pm3 From a4e26375ef00e99ac1062370f957cf642a54deac Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Fri, 19 Jun 2020 09:53:25 -0400 Subject: [PATCH 18/34] lint --- .pylintrc | 4 +- .../transpiler/passes/layout/sabre_layout.py | 1 + .../transpiler/passes/routing/sabre_swap.py | 81 +++++++++---------- test/python/transpiler/test_mappers.py | 2 +- 4 files changed, 43 insertions(+), 45 deletions(-) diff --git a/.pylintrc b/.pylintrc index 78d9e029e364..67e176fc26fa 100644 --- a/.pylintrc +++ b/.pylintrc @@ -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 @@ -179,7 +179,7 @@ argument-name-hint=[a-z_][a-z0-9_]{2,30}$ variable-rgx=[a-z_][a-z0-9_]{2,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}|(__.*__))$ diff --git a/qiskit/transpiler/passes/layout/sabre_layout.py b/qiskit/transpiler/passes/layout/sabre_layout.py index ee1aed5a67a5..ae3dc8fd4df5 100644 --- a/qiskit/transpiler/passes/layout/sabre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_layout.py @@ -50,6 +50,7 @@ def __init__(self, coupling_map, routing_pass=None, seed=None, 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. """ diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index 78a7ec98a003..7147bc67907e 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -18,7 +18,6 @@ from copy import deepcopy from itertools import cycle -from qiskit.circuit.quantumregister import QuantumRegister from qiskit.dagcircuit import DAGCircuit from qiskit.circuit.library.standard_gates import SwapGate from qiskit.transpiler.basepasses import TransformationPass @@ -122,6 +121,8 @@ def __init__(self, coupling_map, heuristic='basic'): super().__init__() self.coupling_map = coupling_map self.heuristic = heuristic + self.applied_gates = None + self.qubits_decay = None def run(self, dag): """Run the SabreSwap pass on `dag`. @@ -182,10 +183,10 @@ def run(self, dag): for successor in dag.quantum_successors(node): if successor.type != 'op': continue - elif self._is_resolved(successor, dag): + if self._is_resolved(successor, dag): front_layer.append(successor) - if len(node.qargs): + if node.qargs: self._reset_qubits_decay() # Diagnostics @@ -198,42 +199,41 @@ def run(self, dag): # After all free gates are exhausted, heuristically find # the best swap and insert it. + extended_set = self._obtain_extended_set(dag, front_layer) + swap_candidate_list = self._obtain_swaps(front_layer, current_layout) + swap_scores = [] + for i, swap_qubits in enumerate(swap_candidate_list): + 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.append(score) + min_score = min(swap_scores) + best_swap = swap_candidate_list[swap_scores.index(min_score)] + 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: - extended_set = self._obtain_extended_set(dag, front_layer) - swap_candidate_list = self._obtain_swaps(front_layer, current_layout) - swap_scores = [] - for i, swap_qubits in enumerate(swap_candidate_list): - 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.append(score) - min_score = min(swap_scores) - best_swap = swap_candidate_list[swap_scores.index(min_score)] - 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_candidate_list[i], swap_scores[i]) - for i in range(len(swap_scores))]) - logger.debug('best swap: %s', best_swap) - logger.debug('qubits decay: %s', self.qubits_decay) + 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_candidate_list[i], swap_scores[i]) + for i in range(len(swap_scores))]) + logger.debug('best swap: %s', best_swap) + logger.debug('qubits decay: %s', self.qubits_decay) self.property_set['final_layout'] = current_layout @@ -250,10 +250,7 @@ def _is_resolved(self, node, dag): """ predecessors = dag.quantum_predecessors(node) predecessors = filter(lambda x: x.type == 'op', predecessors) - if all([n in self.applied_gates for n in predecessors]): - return True - else: - return False + 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. diff --git a/test/python/transpiler/test_mappers.py b/test/python/transpiler/test_mappers.py index 7d00aba10366..2b209db92e1e 100644 --- a/test/python/transpiler/test_mappers.py +++ b/test/python/transpiler/test_mappers.py @@ -285,7 +285,7 @@ class TestsStochasticSwap(SwapperCommonTestCases, QiskitTestCase): additional_args = {'seed': 0} -class TestsStochasticSwap(SwapperCommonTestCases, QiskitTestCase): +class TestsSabreSwap(SwapperCommonTestCases, QiskitTestCase): """Test SwapperCommonTestCases using SabreSwap.""" pass_class = SabreSwap From 8b82fe7b248940cb2842d6188b7c21c05f387992 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Fri, 19 Jun 2020 11:47:06 -0400 Subject: [PATCH 19/34] lint relax --- .pylintrc | 2 +- qiskit/transpiler/layout.py | 1 - qiskit/transpiler/passes/layout/sabre_layout.py | 12 ++++++------ qiskit/transpiler/passes/routing/sabre_swap.py | 4 ++-- test/python/qasm/TestsSabreSwap_a_cx_to_map.qasm | 10 ++++++++++ .../qasm/TestsSabreSwap_handle_measurement.qasm | 14 ++++++++++++++ .../python/qasm/TestsSabreSwap_initial_layout.qasm | 11 +++++++++++ .../qasm/TestsStochasticSwap_a_cx_to_map.qasm | 8 ++++---- .../TestsStochasticSwap_handle_measurement.qasm | 7 ++++--- 9 files changed, 52 insertions(+), 17 deletions(-) create mode 100644 test/python/qasm/TestsSabreSwap_a_cx_to_map.qasm create mode 100644 test/python/qasm/TestsSabreSwap_handle_measurement.qasm create mode 100644 test/python/qasm/TestsSabreSwap_initial_layout.qasm diff --git a/.pylintrc b/.pylintrc index 67e176fc26fa..e4ff30a0c24e 100644 --- a/.pylintrc +++ b/.pylintrc @@ -176,7 +176,7 @@ 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_]{1,30}$ diff --git a/qiskit/transpiler/layout.py b/qiskit/transpiler/layout.py index 3c765f300ecf..68d9575595fb 100644 --- a/qiskit/transpiler/layout.py +++ b/qiskit/transpiler/layout.py @@ -19,7 +19,6 @@ 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 diff --git a/qiskit/transpiler/passes/layout/sabre_layout.py b/qiskit/transpiler/passes/layout/sabre_layout.py index ae3dc8fd4df5..ca57dd08fdcd 100644 --- a/qiskit/transpiler/passes/layout/sabre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_layout.py @@ -70,10 +70,10 @@ def run(self, dag): TranspilerError: if dag wider than self.coupling_map """ from qiskit.converters import dag_to_circuit - from qiskit.transpiler.passes.layout import SetLayout - from qiskit.transpiler.passes.layout import FullAncillaAllocation - from qiskit.transpiler.passes.layout import EnlargeWithAncilla - from qiskit.transpiler.passes.layout import ApplyLayout + 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 @@ -140,8 +140,8 @@ def _compose_layouts(initial_layout, pass_final_layout, qregs): circ = circ.mirror() # Diagnostics - logger.info('After round %d, num_swaps: %d' % - (i+1, new_circ.count_ops().get('swap', 0))) + 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) diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index 7147bc67907e..5cbea8bf8eac 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -138,7 +138,7 @@ def run(self, 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(): + if len(dag.qubits) > self.coupling_map.size(): raise TranspilerError('More virtual qubits exist than physical.') # Preserve input DAG's name, regs, wire_map, etc. but replace the graph. @@ -152,7 +152,7 @@ def run(self, dag): # A decay factor for each qubit used to heuristically penalize recently # used qubits (to encourage parallelism). - self.qubits_decay = dict(zip(dag.qubits(), len(dag.qubits()) * [1])) + self.qubits_decay = dict(zip(dag.qubits, len(dag.qubits) * [1])) # Start algorithm from the front layer and iterate until all gates done. num_search_steps = 0 diff --git a/test/python/qasm/TestsSabreSwap_a_cx_to_map.qasm b/test/python/qasm/TestsSabreSwap_a_cx_to_map.qasm new file mode 100644 index 000000000000..15c0c43005f0 --- /dev/null +++ b/test/python/qasm/TestsSabreSwap_a_cx_to_map.qasm @@ -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[1]; +cx q[0],q[2]; +measure q[0] -> c[1]; +measure q[2] -> c[2]; diff --git a/test/python/qasm/TestsSabreSwap_handle_measurement.qasm b/test/python/qasm/TestsSabreSwap_handle_measurement.qasm new file mode 100644 index 000000000000..80754de18a52 --- /dev/null +++ b/test/python/qasm/TestsSabreSwap_handle_measurement.qasm @@ -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[0],q[1]; +cx q[2],q[1]; +measure q[1] -> c[0]; +measure q[2] -> c[3]; diff --git a/test/python/qasm/TestsSabreSwap_initial_layout.qasm b/test/python/qasm/TestsSabreSwap_initial_layout.qasm new file mode 100644 index 000000000000..ea822fc97a5e --- /dev/null +++ b/test/python/qasm/TestsSabreSwap_initial_layout.qasm @@ -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]; diff --git a/test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm b/test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm index bceabc6db0ab..15c0c43005f0 100644 --- a/test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm +++ b/test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm @@ -4,7 +4,7 @@ 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]; +swap q[0],q[1]; +cx q[0],q[2]; +measure q[0] -> c[1]; +measure q[2] -> c[2]; diff --git a/test/python/qasm/TestsStochasticSwap_handle_measurement.qasm b/test/python/qasm/TestsStochasticSwap_handle_measurement.qasm index 80754de18a52..bc161f609758 100644 --- a/test/python/qasm/TestsStochasticSwap_handle_measurement.qasm +++ b/test/python/qasm/TestsStochasticSwap_handle_measurement.qasm @@ -5,10 +5,11 @@ 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]; swap q[0],q[1]; +cx q[3],q[2]; +swap q[2],q[3]; cx q[2],q[1]; measure q[1] -> c[0]; +measure q[3] -> c[1]; measure q[2] -> c[3]; From 394eeda2d44f4984853d1be667b0204ab484c623 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Fri, 19 Jun 2020 14:38:05 -0400 Subject: [PATCH 20/34] regenerate mapper tests --- test/python/qasm/TestsSabreSwap_a_cx_to_map.qasm | 8 ++++---- .../qasm/TestsSabreSwap_handle_measurement.qasm | 13 +++++++------ test/python/qasm/TestsSabreSwap_initial_layout.qasm | 8 ++++---- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/test/python/qasm/TestsSabreSwap_a_cx_to_map.qasm b/test/python/qasm/TestsSabreSwap_a_cx_to_map.qasm index 15c0c43005f0..bceabc6db0ab 100644 --- a/test/python/qasm/TestsSabreSwap_a_cx_to_map.qasm +++ b/test/python/qasm/TestsSabreSwap_a_cx_to_map.qasm @@ -4,7 +4,7 @@ qreg q[3]; creg c[3]; h q[1]; measure q[0] -> c[0]; -swap q[0],q[1]; -cx q[0],q[2]; -measure q[0] -> c[1]; -measure q[2] -> c[2]; +swap q[0],q[2]; +cx q[1],q[0]; +measure q[1] -> c[1]; +measure q[0] -> c[2]; diff --git a/test/python/qasm/TestsSabreSwap_handle_measurement.qasm b/test/python/qasm/TestsSabreSwap_handle_measurement.qasm index 80754de18a52..a963ad9c3cea 100644 --- a/test/python/qasm/TestsSabreSwap_handle_measurement.qasm +++ b/test/python/qasm/TestsSabreSwap_handle_measurement.qasm @@ -5,10 +5,11 @@ creg c[4]; cx q[0],q[1]; h q[3]; measure q[2] -> c[2]; +swap q[1],q[2]; +cx q[3],q[2]; +measure q[2] -> c[1]; swap q[2],q[3]; -cx q[2],q[1]; -measure q[1] -> c[1]; -swap q[0],q[1]; -cx q[2],q[1]; -measure q[1] -> c[0]; -measure q[2] -> c[3]; +swap q[1],q[2]; +cx q[1],q[0]; +measure q[0] -> c[0]; +measure q[1] -> c[3]; diff --git a/test/python/qasm/TestsSabreSwap_initial_layout.qasm b/test/python/qasm/TestsSabreSwap_initial_layout.qasm index ea822fc97a5e..76adf18d61fc 100644 --- a/test/python/qasm/TestsSabreSwap_initial_layout.qasm +++ b/test/python/qasm/TestsSabreSwap_initial_layout.qasm @@ -4,8 +4,8 @@ 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]; +swap q[0],q[1]; +cx q[0],q[2]; +measure q[0] -> c[1]; +measure q[2] -> c[2]; measure q[3] -> c[3]; From feaa4ec869b8e0ecd16a18fa9829ecc9c88afe9c Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Fri, 19 Jun 2020 15:28:17 -0400 Subject: [PATCH 21/34] make set to list conversion deterministic --- qiskit/circuit/bit.py | 2 +- qiskit/transpiler/passes/layout/sabre_layout.py | 6 +++--- qiskit/transpiler/passes/routing/sabre_swap.py | 2 +- test/python/qasm/TestsBasicSwap_a_cx_to_map.qasm | 2 +- .../python/qasm/TestsBasicSwap_handle_measurement.qasm | 4 ++-- test/python/qasm/TestsBasicSwap_initial_layout.qasm | 2 +- test/python/qasm/TestsSabreSwap_a_cx_to_map.qasm | 10 +++++----- .../python/qasm/TestsSabreSwap_handle_measurement.qasm | 10 +++++----- test/python/qasm/TestsSabreSwap_initial_layout.qasm | 2 +- test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm | 2 +- .../qasm/TestsStochasticSwap_handle_measurement.qasm | 4 ++-- .../qasm/TestsStochasticSwap_initial_layout.qasm | 2 +- 12 files changed, 24 insertions(+), 24 deletions(-) diff --git a/qiskit/circuit/bit.py b/qiskit/circuit/bit.py index be6cec0305b9..618f8878defb 100644 --- a/qiskit/circuit/bit.py +++ b/qiskit/circuit/bit.py @@ -70,7 +70,7 @@ def index(self, value): def __repr__(self): """Return the official string representing the bit.""" - return "%s(%s, %s)" % (self.__class__.__name__, self._register, self._index) + return "%s[%s]" % (self._register.name, self._index) def __hash__(self): return self._hash diff --git a/qiskit/transpiler/passes/layout/sabre_layout.py b/qiskit/transpiler/passes/layout/sabre_layout.py index ca57dd08fdcd..93001be4b1a2 100644 --- a/qiskit/transpiler/passes/layout/sabre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_layout.py @@ -107,7 +107,7 @@ def _compose_layouts(initial_layout, pass_final_layout, qregs): for v, _ in initial_layout.get_virtual_bits().items()} return Layout(final_layout) - if len(dag.qubits()) > self.coupling_map.size(): + if len(dag.qubits) > self.coupling_map.size(): raise TranspilerError('More virtual qubits exist than physical.') # Choose a random initial_layout. @@ -116,9 +116,9 @@ def _compose_layouts(initial_layout, pass_final_layout, qregs): rng = np.random.default_rng(self.seed) physical_qubits = rng.choice(self.coupling_map.size(), - len(dag.qubits()), replace=False) + len(dag.qubits), replace=False) physical_qubits = rng.permutation(physical_qubits) - initial_layout = Layout({q: dag.qubits()[i] + initial_layout = Layout({q: dag.qubits[i] for i, q in enumerate(physical_qubits)}) if self.routing_pass is None: diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index 5cbea8bf8eac..490d4d54efa3 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -301,7 +301,7 @@ def _obtain_swaps(self, front_layer, current_layout): key=lambda q: (q.register.name, q.index)) candidate_swaps.add(tuple(swap)) - return list(candidate_swaps) + return list(sorted(candidate_swaps, key=lambda x: (x[0].index, x[1].index))) def _score_heuristic(self, heuristic, front_layer, extended_set, layout, swap_qubits=None): """Return a heuristic score for a trial layout. diff --git a/test/python/qasm/TestsBasicSwap_a_cx_to_map.qasm b/test/python/qasm/TestsBasicSwap_a_cx_to_map.qasm index a616ac1144a2..aa4582c452d0 100644 --- a/test/python/qasm/TestsBasicSwap_a_cx_to_map.qasm +++ b/test/python/qasm/TestsBasicSwap_a_cx_to_map.qasm @@ -2,9 +2,9 @@ OPENQASM 2.0; include "qelib1.inc"; qreg q[3]; creg c[3]; +measure q[0] -> c[0]; h q[1]; swap q[1],q[0]; cx q[0],q[2]; -measure q[1] -> c[0]; measure q[0] -> c[1]; measure q[2] -> c[2]; diff --git a/test/python/qasm/TestsBasicSwap_handle_measurement.qasm b/test/python/qasm/TestsBasicSwap_handle_measurement.qasm index 360d1f057192..314e581cc181 100644 --- a/test/python/qasm/TestsBasicSwap_handle_measurement.qasm +++ b/test/python/qasm/TestsBasicSwap_handle_measurement.qasm @@ -3,12 +3,12 @@ include "qelib1.inc"; qreg q[4]; creg c[4]; cx q[0],q[1]; +measure q[2] -> c[2]; h q[3]; swap q[3],q[2]; cx q[2],q[1]; +measure q[1] -> c[1]; swap q[2],q[1]; cx q[1],q[0]; measure q[0] -> c[0]; -measure q[2] -> c[1]; -measure q[3] -> c[2]; measure q[1] -> c[3]; diff --git a/test/python/qasm/TestsBasicSwap_initial_layout.qasm b/test/python/qasm/TestsBasicSwap_initial_layout.qasm index 21de2ee9be0a..eac63472d3b4 100644 --- a/test/python/qasm/TestsBasicSwap_initial_layout.qasm +++ b/test/python/qasm/TestsBasicSwap_initial_layout.qasm @@ -2,10 +2,10 @@ OPENQASM 2.0; include "qelib1.inc"; qreg q[4]; creg c[4]; +measure q[0] -> c[0]; h q[1]; swap q[1],q[0]; cx q[0],q[2]; -measure q[1] -> c[0]; measure q[0] -> c[1]; measure q[2] -> c[2]; measure q[3] -> c[3]; diff --git a/test/python/qasm/TestsSabreSwap_a_cx_to_map.qasm b/test/python/qasm/TestsSabreSwap_a_cx_to_map.qasm index bceabc6db0ab..f19e5de75382 100644 --- a/test/python/qasm/TestsSabreSwap_a_cx_to_map.qasm +++ b/test/python/qasm/TestsSabreSwap_a_cx_to_map.qasm @@ -2,9 +2,9 @@ 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]; +h q[1]; +swap q[0],q[1]; +cx q[0],q[2]; +measure q[0] -> c[1]; +measure q[2] -> c[2]; diff --git a/test/python/qasm/TestsSabreSwap_handle_measurement.qasm b/test/python/qasm/TestsSabreSwap_handle_measurement.qasm index a963ad9c3cea..ec1075ef230d 100644 --- a/test/python/qasm/TestsSabreSwap_handle_measurement.qasm +++ b/test/python/qasm/TestsSabreSwap_handle_measurement.qasm @@ -3,13 +3,13 @@ include "qelib1.inc"; qreg q[4]; creg c[4]; cx q[0],q[1]; -h q[3]; measure q[2] -> c[2]; swap q[1],q[2]; +swap q[0],q[1]; +h q[3]; cx q[3],q[2]; measure q[2] -> c[1]; -swap q[2],q[3]; swap q[1],q[2]; -cx q[1],q[0]; -measure q[0] -> c[0]; -measure q[1] -> c[3]; +cx q[3],q[2]; +measure q[2] -> c[0]; +measure q[3] -> c[3]; diff --git a/test/python/qasm/TestsSabreSwap_initial_layout.qasm b/test/python/qasm/TestsSabreSwap_initial_layout.qasm index 76adf18d61fc..0957484a6edd 100644 --- a/test/python/qasm/TestsSabreSwap_initial_layout.qasm +++ b/test/python/qasm/TestsSabreSwap_initial_layout.qasm @@ -2,8 +2,8 @@ OPENQASM 2.0; include "qelib1.inc"; qreg q[4]; creg c[4]; -h q[1]; measure q[0] -> c[0]; +h q[1]; swap q[0],q[1]; cx q[0],q[2]; measure q[0] -> c[1]; diff --git a/test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm b/test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm index 15c0c43005f0..f19e5de75382 100644 --- a/test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm +++ b/test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm @@ -2,8 +2,8 @@ OPENQASM 2.0; include "qelib1.inc"; qreg q[3]; creg c[3]; -h q[1]; measure q[0] -> c[0]; +h q[1]; swap q[0],q[1]; cx q[0],q[2]; measure q[0] -> c[1]; diff --git a/test/python/qasm/TestsStochasticSwap_handle_measurement.qasm b/test/python/qasm/TestsStochasticSwap_handle_measurement.qasm index bc161f609758..7a01800b5344 100644 --- a/test/python/qasm/TestsStochasticSwap_handle_measurement.qasm +++ b/test/python/qasm/TestsStochasticSwap_handle_measurement.qasm @@ -3,13 +3,13 @@ include "qelib1.inc"; qreg q[4]; creg c[4]; cx q[0],q[1]; -h q[3]; measure q[2] -> c[2]; swap q[1],q[2]; swap q[0],q[1]; +h q[3]; cx q[3],q[2]; swap q[2],q[3]; cx q[2],q[1]; measure q[1] -> c[0]; -measure q[3] -> c[1]; measure q[2] -> c[3]; +measure q[3] -> c[1]; diff --git a/test/python/qasm/TestsStochasticSwap_initial_layout.qasm b/test/python/qasm/TestsStochasticSwap_initial_layout.qasm index 76adf18d61fc..0957484a6edd 100644 --- a/test/python/qasm/TestsStochasticSwap_initial_layout.qasm +++ b/test/python/qasm/TestsStochasticSwap_initial_layout.qasm @@ -2,8 +2,8 @@ OPENQASM 2.0; include "qelib1.inc"; qreg q[4]; creg c[4]; -h q[1]; measure q[0] -> c[0]; +h q[1]; swap q[0],q[1]; cx q[0],q[2]; measure q[0] -> c[1]; From 7cb2861163df18438462e0df7d7f009812d9a92b Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Fri, 19 Jun 2020 17:19:26 -0400 Subject: [PATCH 22/34] cleaning the diff a bit --- qiskit/compiler/transpile.py | 11 +++++------ qiskit/dagcircuit/dagcircuit.py | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/qiskit/compiler/transpile.py b/qiskit/compiler/transpile.py index db7c4e5f509b..bd2d1fa91fc9 100644 --- a/qiskit/compiler/transpile.py +++ b/qiskit/compiler/transpile.py @@ -185,8 +185,7 @@ def callback_func(**kwargs): coupling_map=coupling_map, seed_transpiler=seed_transpiler, backend_properties=backend_properties, initial_layout=initial_layout, layout_method=layout_method, - routing_method=routing_method, - backend=backend) + routing_method=routing_method, backend=backend) warnings.warn("The parameter pass_manager in transpile is being deprecated. " "The preferred way to tranpile a circuit using a custom pass manager is" @@ -311,8 +310,8 @@ def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict]) -> Qua def _parse_transpile_args(circuits, backend, basis_gates, coupling_map, backend_properties, initial_layout, layout_method, routing_method, - seed_transpiler, - optimization_level, callback, output_name) -> List[Dict]: + seed_transpiler, optimization_level, + callback, output_name) -> List[Dict]: """Resolve the various types of args allowed to the transpile() function through duck typing, overriding args, etc. Refer to the transpile() docstring for details on what types of inputs are allowed. @@ -345,8 +344,8 @@ def _parse_transpile_args(circuits, backend, list_transpile_args = [] for args in zip(basis_gates, coupling_map, backend_properties, initial_layout, layout_method, routing_method, - seed_transpiler, - optimization_level, output_name, callback): + seed_transpiler, optimization_level, + output_name, callback): transpile_args = {'pass_manager_config': PassManagerConfig(basis_gates=args[0], coupling_map=args[1], backend_properties=args[2], diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 7fc45d9f7215..d0efbd18911b 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1097,16 +1097,6 @@ def predecessors(self, node): """Returns iterator of the predecessors of a node as DAGNodes.""" raise NotImplementedError() - def quantum_successors(self, node): - """Returns iterator of the successors of a node that are - connected by a qubit edge.""" - for successor in self.successors(node): - if any(isinstance(x['wire'], Qubit) - for x in - self._get_all_multi_graph_edges( - node._node_id, successor._node_id)): - yield successor - def quantum_predecessors(self, node): """Returns iterator of the predecessors of a node that are connected by a quantum edge as DAGNodes.""" @@ -1132,6 +1122,16 @@ def bfs_successors(self, node): """ raise NotImplementedError() + def quantum_successors(self, node): + """Returns iterator of the successors of a node that are + connected by a qubit edge.""" + for successor in self.successors(node): + if any(isinstance(x['wire'], Qubit) + for x in + self._get_all_multi_graph_edges( + node._node_id, successor._node_id)): + yield successor + def remove_op_node(self, node): """Remove an operation node n. From a9decf29957f1c606793d1c3d3025074fe36d41f Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Fri, 19 Jun 2020 17:27:00 -0400 Subject: [PATCH 23/34] test.python.transpiler.test_coupling.CouplingTest.test_make_symmetric --- test/python/transpiler/test_coupling.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/python/transpiler/test_coupling.py b/test/python/transpiler/test_coupling.py index df35af310661..5aa8be3a6130 100644 --- a/test/python/transpiler/test_coupling.py +++ b/test/python/transpiler/test_coupling.py @@ -114,6 +114,15 @@ def test_symmetric_big_false(self): 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() From 33f6e400603c5507977adf40dfde080b2e33fdcb Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Fri, 19 Jun 2020 18:53:22 -0400 Subject: [PATCH 24/34] make randomization of SabreSwap controllable via seed --- qiskit/transpiler/passes/routing/sabre_swap.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index 490d4d54efa3..26810b197486 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -200,9 +200,9 @@ def run(self, dag): # After all free gates are exhausted, heuristically find # the best swap and insert it. extended_set = self._obtain_extended_set(dag, front_layer) - swap_candidate_list = self._obtain_swaps(front_layer, current_layout) - swap_scores = [] - for i, swap_qubits in enumerate(swap_candidate_list): + 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, @@ -210,7 +210,8 @@ def run(self, dag): extended_set, trial_layout, swap_qubits) - swap_scores.append(score) + swap_scores[swap_qubits] = score + print(swap_scores) min_score = min(swap_scores) best_swap = swap_candidate_list[swap_scores.index(min_score)] swap_node = DAGNode(op=SwapGate(), qargs=best_swap, type='op') @@ -282,7 +283,7 @@ def _obtain_extended_set(self, dag, front_layer): return extended_set def _obtain_swaps(self, front_layer, current_layout): - """Return a list of candidate swaps that affect qubits in front_layer. + """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 @@ -301,7 +302,7 @@ def _obtain_swaps(self, front_layer, current_layout): key=lambda q: (q.register.name, q.index)) candidate_swaps.add(tuple(swap)) - return list(sorted(candidate_swaps, key=lambda x: (x[0].index, x[1].index))) + return candidate_swaps def _score_heuristic(self, heuristic, front_layer, extended_set, layout, swap_qubits=None): """Return a heuristic score for a trial layout. From 574b86b81189272985699c6f5153a404d64740b7 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Fri, 19 Jun 2020 22:17:34 -0400 Subject: [PATCH 25/34] control randomization of SabreSwap via seed --- qiskit/circuit/bit.py | 2 +- .../transpiler/passes/routing/sabre_swap.py | 22 ++++++++++++------- .../transpiler/preset_passmanagers/level0.py | 2 +- .../transpiler/preset_passmanagers/level1.py | 2 +- .../transpiler/preset_passmanagers/level2.py | 2 +- .../transpiler/preset_passmanagers/level3.py | 2 +- 6 files changed, 19 insertions(+), 13 deletions(-) diff --git a/qiskit/circuit/bit.py b/qiskit/circuit/bit.py index 618f8878defb..be6cec0305b9 100644 --- a/qiskit/circuit/bit.py +++ b/qiskit/circuit/bit.py @@ -70,7 +70,7 @@ def index(self, value): def __repr__(self): """Return the official string representing the bit.""" - return "%s[%s]" % (self._register.name, self._index) + return "%s(%s, %s)" % (self.__class__.__name__, self._register, self._index) def __hash__(self): return self._hash diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index 26810b197486..7684169e039c 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -15,8 +15,10 @@ """Routing via SWAP insertion using the SABRE method from Li et al.""" import logging +import random 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 @@ -66,13 +68,14 @@ class SabreSwap(TransformationPass): `arXiv:1809.02573 `_ """ - def __init__(self, coupling_map, heuristic='basic'): + 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: @@ -121,6 +124,7 @@ def __init__(self, coupling_map, heuristic='basic'): super().__init__() self.coupling_map = coupling_map self.heuristic = heuristic + self.seed = seed self.applied_gates = None self.qubits_decay = None @@ -141,6 +145,8 @@ def run(self, dag): 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) @@ -198,7 +204,8 @@ def run(self, dag): continue # After all free gates are exhausted, heuristically find - # the best swap and insert it. + # 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) @@ -211,9 +218,10 @@ def run(self, dag): trial_layout, swap_qubits) swap_scores[swap_qubits] = score - print(swap_scores) - min_score = min(swap_scores) - best_swap = swap_candidate_list[swap_scores.index(min_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) @@ -230,9 +238,7 @@ def run(self, dag): logger.debug('SWAP Selection...') logger.debug('extended_set: %s', [(n.name, n.qargs) for n in extended_set]) - logger.debug('swap scores: %s', - [(swap_candidate_list[i], swap_scores[i]) - for i in range(len(swap_scores))]) + logger.debug('swap scores: %s', swap_scores) logger.debug('best swap: %s', best_swap) logger.debug('qubits decay: %s', self.qubits_decay) diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 03011af6b863..2cc76f72c3d5 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -110,7 +110,7 @@ def _swap_condition(property_set): elif routing_method == 'lookahead': _swap += [LookaheadSwap(coupling_map, search_depth=2, search_width=2)] elif routing_method == 'sabre': - _swap += [SabreSwap(coupling_map, heuristic='basic')] + _swap += [SabreSwap(coupling_map, heuristic='basic', seed=seed_transpiler)] else: raise TranspilerError("Invalid routing method %s." % routing_method) diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 32e329934c8f..c44d7a80af89 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -127,7 +127,7 @@ def _swap_condition(property_set): elif routing_method == 'lookahead': _swap += [LookaheadSwap(coupling_map, search_depth=4, search_width=4)] elif routing_method == 'sabre': - _swap += [SabreSwap(coupling_map, heuristic='lookahead')] + _swap += [SabreSwap(coupling_map, heuristic='lookahead', seed=seed_transpiler)] else: raise TranspilerError("Invalid routing method %s." % routing_method) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 12f13773c00e..7b6352ce83a2 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -122,7 +122,7 @@ def _swap_condition(property_set): elif routing_method == 'lookahead': _swap += [LookaheadSwap(coupling_map, search_depth=5, search_width=5)] elif routing_method == 'sabre': - _swap += [SabreSwap(coupling_map, heuristic='decay')] + _swap += [SabreSwap(coupling_map, heuristic='decay', seed=seed_transpiler)] else: raise TranspilerError("Invalid routing method %s." % routing_method) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 45d6bc0469c3..4e8d4dec0ead 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -126,7 +126,7 @@ def _swap_condition(property_set): elif routing_method == 'lookahead': _swap += [LookaheadSwap(coupling_map, search_depth=5, search_width=6)] elif routing_method == 'sabre': - _swap += [SabreSwap(coupling_map, heuristic='decay')] + _swap += [SabreSwap(coupling_map, heuristic='decay', seed=seed_transpiler)] else: raise TranspilerError("Invalid routing method %s." % routing_method) From cb7ef39ea659da9d78754119544f36a09abb22bf Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Fri, 19 Jun 2020 23:19:34 -0400 Subject: [PATCH 26/34] move imports --- .../transpiler/passes/layout/sabre_layout.py | 23 +++++++++---------- .../transpiler/passes/routing/sabre_swap.py | 1 - .../qasm/TestsBasicSwap_a_cx_to_map.qasm | 2 +- .../TestsBasicSwap_handle_measurement.qasm | 4 ++-- .../qasm/TestsBasicSwap_initial_layout.qasm | 2 +- .../qasm/TestsSabreSwap_a_cx_to_map.qasm | 10 ++++---- .../TestsSabreSwap_handle_measurement.qasm | 15 ++++++------ .../qasm/TestsSabreSwap_initial_layout.qasm | 10 ++++---- .../qasm/TestsStochasticSwap_a_cx_to_map.qasm | 2 +- ...estsStochasticSwap_handle_measurement.qasm | 4 ++-- .../TestsStochasticSwap_initial_layout.qasm | 2 +- test/python/transpiler/test_mappers.py | 1 + 12 files changed, 37 insertions(+), 39 deletions(-) diff --git a/qiskit/transpiler/passes/layout/sabre_layout.py b/qiskit/transpiler/passes/layout/sabre_layout.py index 93001be4b1a2..8c2e273e02b7 100644 --- a/qiskit/transpiler/passes/layout/sabre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_layout.py @@ -18,6 +18,13 @@ 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 @@ -69,15 +76,7 @@ def run(self, dag): Raises: TranspilerError: if dag wider than self.coupling_map """ - 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 - - def _layout_and_route_passmanager(): + def _layout_and_route_passmanager(initial_layout): """Return a passmanager for a full layout and routing. We use a factory to remove potential statefulness of passes. @@ -128,16 +127,16 @@ def _compose_layouts(initial_layout, pass_final_layout, qregs): circ = dag_to_circuit(dag) for i in range(self.max_iterations): for _ in ('forward', 'backward'): - pm = _layout_and_route_passmanager() + pm = _layout_and_route_passmanager(initial_layout) new_circ = pm.run(circ) - # Update initial layout and mirror the unmapped circuit. + # Update initial layout and reverse the unmapped circuit. pass_final_layout = pm.property_set['final_layout'] final_layout = _compose_layouts(initial_layout, pass_final_layout, circ.qregs) initial_layout = final_layout - circ = circ.mirror() + circ = circ.reverse_ops() # Diagnostics logger.info('After round %d, num_swaps: %d', diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index 7684169e039c..36d1574ddf94 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -15,7 +15,6 @@ """Routing via SWAP insertion using the SABRE method from Li et al.""" import logging -import random from copy import deepcopy from itertools import cycle import numpy as np diff --git a/test/python/qasm/TestsBasicSwap_a_cx_to_map.qasm b/test/python/qasm/TestsBasicSwap_a_cx_to_map.qasm index aa4582c452d0..a616ac1144a2 100644 --- a/test/python/qasm/TestsBasicSwap_a_cx_to_map.qasm +++ b/test/python/qasm/TestsBasicSwap_a_cx_to_map.qasm @@ -2,9 +2,9 @@ OPENQASM 2.0; include "qelib1.inc"; qreg q[3]; creg c[3]; -measure q[0] -> c[0]; h q[1]; swap q[1],q[0]; cx q[0],q[2]; +measure q[1] -> c[0]; measure q[0] -> c[1]; measure q[2] -> c[2]; diff --git a/test/python/qasm/TestsBasicSwap_handle_measurement.qasm b/test/python/qasm/TestsBasicSwap_handle_measurement.qasm index 314e581cc181..360d1f057192 100644 --- a/test/python/qasm/TestsBasicSwap_handle_measurement.qasm +++ b/test/python/qasm/TestsBasicSwap_handle_measurement.qasm @@ -3,12 +3,12 @@ include "qelib1.inc"; qreg q[4]; creg c[4]; cx q[0],q[1]; -measure q[2] -> c[2]; h q[3]; swap q[3],q[2]; cx q[2],q[1]; -measure q[1] -> c[1]; swap q[2],q[1]; cx q[1],q[0]; measure q[0] -> c[0]; +measure q[2] -> c[1]; +measure q[3] -> c[2]; measure q[1] -> c[3]; diff --git a/test/python/qasm/TestsBasicSwap_initial_layout.qasm b/test/python/qasm/TestsBasicSwap_initial_layout.qasm index eac63472d3b4..21de2ee9be0a 100644 --- a/test/python/qasm/TestsBasicSwap_initial_layout.qasm +++ b/test/python/qasm/TestsBasicSwap_initial_layout.qasm @@ -2,10 +2,10 @@ OPENQASM 2.0; include "qelib1.inc"; qreg q[4]; creg c[4]; -measure q[0] -> c[0]; h q[1]; swap q[1],q[0]; cx q[0],q[2]; +measure q[1] -> c[0]; measure q[0] -> c[1]; measure q[2] -> c[2]; measure q[3] -> c[3]; diff --git a/test/python/qasm/TestsSabreSwap_a_cx_to_map.qasm b/test/python/qasm/TestsSabreSwap_a_cx_to_map.qasm index f19e5de75382..bceabc6db0ab 100644 --- a/test/python/qasm/TestsSabreSwap_a_cx_to_map.qasm +++ b/test/python/qasm/TestsSabreSwap_a_cx_to_map.qasm @@ -2,9 +2,9 @@ OPENQASM 2.0; include "qelib1.inc"; qreg q[3]; creg c[3]; -measure q[0] -> c[0]; h q[1]; -swap q[0],q[1]; -cx q[0],q[2]; -measure q[0] -> c[1]; -measure q[2] -> c[2]; +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]; diff --git a/test/python/qasm/TestsSabreSwap_handle_measurement.qasm b/test/python/qasm/TestsSabreSwap_handle_measurement.qasm index ec1075ef230d..11370fef5993 100644 --- a/test/python/qasm/TestsSabreSwap_handle_measurement.qasm +++ b/test/python/qasm/TestsSabreSwap_handle_measurement.qasm @@ -3,13 +3,12 @@ include "qelib1.inc"; qreg q[4]; creg c[4]; cx q[0],q[1]; -measure q[2] -> c[2]; -swap q[1],q[2]; -swap q[0],q[1]; h q[3]; -cx q[3],q[2]; -measure q[2] -> c[1]; +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[3],q[2]; -measure q[2] -> c[0]; -measure q[3] -> c[3]; +cx q[1],q[0]; +measure q[0] -> c[0]; +measure q[1] -> c[3]; diff --git a/test/python/qasm/TestsSabreSwap_initial_layout.qasm b/test/python/qasm/TestsSabreSwap_initial_layout.qasm index 0957484a6edd..ea822fc97a5e 100644 --- a/test/python/qasm/TestsSabreSwap_initial_layout.qasm +++ b/test/python/qasm/TestsSabreSwap_initial_layout.qasm @@ -2,10 +2,10 @@ OPENQASM 2.0; include "qelib1.inc"; qreg q[4]; creg c[4]; -measure q[0] -> c[0]; h q[1]; -swap q[0],q[1]; -cx q[0],q[2]; -measure q[0] -> c[1]; -measure q[2] -> c[2]; +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]; diff --git a/test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm b/test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm index f19e5de75382..15c0c43005f0 100644 --- a/test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm +++ b/test/python/qasm/TestsStochasticSwap_a_cx_to_map.qasm @@ -2,8 +2,8 @@ OPENQASM 2.0; include "qelib1.inc"; qreg q[3]; creg c[3]; -measure q[0] -> c[0]; h q[1]; +measure q[0] -> c[0]; swap q[0],q[1]; cx q[0],q[2]; measure q[0] -> c[1]; diff --git a/test/python/qasm/TestsStochasticSwap_handle_measurement.qasm b/test/python/qasm/TestsStochasticSwap_handle_measurement.qasm index 7a01800b5344..bc161f609758 100644 --- a/test/python/qasm/TestsStochasticSwap_handle_measurement.qasm +++ b/test/python/qasm/TestsStochasticSwap_handle_measurement.qasm @@ -3,13 +3,13 @@ include "qelib1.inc"; qreg q[4]; creg c[4]; cx q[0],q[1]; +h q[3]; measure q[2] -> c[2]; swap q[1],q[2]; swap q[0],q[1]; -h q[3]; cx q[3],q[2]; swap q[2],q[3]; cx q[2],q[1]; measure q[1] -> c[0]; -measure q[2] -> c[3]; measure q[3] -> c[1]; +measure q[2] -> c[3]; diff --git a/test/python/qasm/TestsStochasticSwap_initial_layout.qasm b/test/python/qasm/TestsStochasticSwap_initial_layout.qasm index 0957484a6edd..76adf18d61fc 100644 --- a/test/python/qasm/TestsStochasticSwap_initial_layout.qasm +++ b/test/python/qasm/TestsStochasticSwap_initial_layout.qasm @@ -2,8 +2,8 @@ OPENQASM 2.0; include "qelib1.inc"; qreg q[4]; creg c[4]; -measure q[0] -> c[0]; h q[1]; +measure q[0] -> c[0]; swap q[0],q[1]; cx q[0],q[2]; measure q[0] -> c[1]; diff --git a/test/python/transpiler/test_mappers.py b/test/python/transpiler/test_mappers.py index 2b209db92e1e..7910499181a3 100644 --- a/test/python/transpiler/test_mappers.py +++ b/test/python/transpiler/test_mappers.py @@ -288,6 +288,7 @@ class TestsStochasticSwap(SwapperCommonTestCases, QiskitTestCase): class TestsSabreSwap(SwapperCommonTestCases, QiskitTestCase): """Test SwapperCommonTestCases using SabreSwap.""" pass_class = SabreSwap + additional_args = {'seed': 0} if __name__ == '__main__': From 010c09aeaccb8c6c94e1592ccbb86b64a53dafd3 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Sat, 20 Jun 2020 11:36:10 -0400 Subject: [PATCH 27/34] test.python.transpiler.test_coupling.CouplingTest.test_neighbors --- test/python/transpiler/test_coupling.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/python/transpiler/test_coupling.py b/test/python/transpiler/test_coupling.py index 5aa8be3a6130..4e157ab0c6a3 100644 --- a/test/python/transpiler/test_coupling.py +++ b/test/python/transpiler/test_coupling.py @@ -123,6 +123,15 @@ def test_make_symmetric(self): self.assertEqual(set(edges), set([(0, 1), (0, 2), (2, 0), (1, 0)])) + def test_neighbors(self): + coupling_list = [[0, 1], [0, 2], [2, 0]] + coupling = CouplingMap(coupling_list) + + 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([])) + self.assertEqual(set(coupling.neighbors(physical_qubits[2])), set([0])) + def test_full_factory(self): coupling = CouplingMap.from_full(4) edges = coupling.get_edges() From 5527c0512b6258b1b0120e7cb418126f563b58e1 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Sat, 20 Jun 2020 12:03:31 -0400 Subject: [PATCH 28/34] test.python.dagcircuit.test_dagcircuit.TestDagNodeSelection.test_front_layer --- test/python/dagcircuit/test_dagcircuit.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 54e4bc802b87..c2b5db4875e2 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -382,6 +382,16 @@ def setUp(self): 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], []) From c41221e7621b181c74a4fe6f3f4b40823bb41d28 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Sat, 20 Jun 2020 15:06:46 -0400 Subject: [PATCH 29/34] fix doc --- qiskit/transpiler/passes/routing/sabre_swap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index 36d1574ddf94..4905109003ec 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -97,7 +97,7 @@ def __init__(self, coupling_map, heuristic='basic', seed=None): 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. |E| number of upcoming successors to gates in + 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. From 444cc70cbbb1041d23c99ce31ea20c801b688931 Mon Sep 17 00:00:00 2001 From: Ali Javadi-Abhari Date: Sat, 20 Jun 2020 16:26:18 -0400 Subject: [PATCH 30/34] Update test/python/transpiler/test_sabre_swap.py Co-authored-by: Luciano Bello --- test/python/transpiler/test_sabre_swap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/transpiler/test_sabre_swap.py b/test/python/transpiler/test_sabre_swap.py index 57a605f35c6f..4e45a853fb66 100644 --- a/test/python/transpiler/test_sabre_swap.py +++ b/test/python/transpiler/test_sabre_swap.py @@ -2,7 +2,7 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2018. +# (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 From 0dc1e45f47d82fa8d35863d11ac27a9d0db562b5 Mon Sep 17 00:00:00 2001 From: Ali Javadi-Abhari Date: Sat, 20 Jun 2020 16:27:11 -0400 Subject: [PATCH 31/34] Update qiskit/transpiler/passes/routing/sabre_swap.py Co-authored-by: Luciano Bello --- qiskit/transpiler/passes/routing/sabre_swap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index 4905109003ec..f06c6f6e02c6 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -157,7 +157,7 @@ def run(self, dag): # A decay factor for each qubit used to heuristically penalize recently # used qubits (to encourage parallelism). - self.qubits_decay = dict(zip(dag.qubits, len(dag.qubits) * [1])) + 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 From e65fff2303bfbd57902265ccd7e8e94776dfbe4d Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Sat, 20 Jun 2020 09:53:12 -0400 Subject: [PATCH 32/34] add note and test for neighbors --- qiskit/transpiler/coupling.py | 3 +++ qiskit/transpiler/passes/routing/sabre_swap.py | 2 +- test/python/transpiler/test_coupling.py | 9 +++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/coupling.py b/qiskit/transpiler/coupling.py index 6ca24b297309..6272b8127d73 100644 --- a/qiskit/transpiler/coupling.py +++ b/qiskit/transpiler/coupling.py @@ -136,6 +136,9 @@ def is_connected(self): 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) diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index f06c6f6e02c6..024f55d2f0bd 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -36,7 +36,7 @@ class SabreSwap(TransformationPass): - """Map input circuit onto a backend topology via insertion of SWAPs. + 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 diff --git a/test/python/transpiler/test_coupling.py b/test/python/transpiler/test_coupling.py index 4e157ab0c6a3..cebe322e8c14 100644 --- a/test/python/transpiler/test_coupling.py +++ b/test/python/transpiler/test_coupling.py @@ -63,6 +63,15 @@ def test_add_edge(self): 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() From 9333b9d7be0159a5f8d46899be52f2cae2dc5468 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Mon, 22 Jun 2020 15:08:39 -0400 Subject: [PATCH 33/34] lint --- .../transpiler/passes/layout/sabre_layout.py | 68 +++++++++---------- test/python/transpiler/test_coupling.py | 9 --- 2 files changed, 34 insertions(+), 43 deletions(-) diff --git a/qiskit/transpiler/passes/layout/sabre_layout.py b/qiskit/transpiler/passes/layout/sabre_layout.py index 8c2e273e02b7..750e17c1520e 100644 --- a/qiskit/transpiler/passes/layout/sabre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_layout.py @@ -76,36 +76,6 @@ def run(self, dag): Raises: TranspilerError: if dag wider than self.coupling_map """ - def _layout_and_route_passmanager(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(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) - if len(dag.qubits) > self.coupling_map.size(): raise TranspilerError('More virtual qubits exist than physical.') @@ -127,14 +97,14 @@ def _compose_layouts(initial_layout, pass_final_layout, qregs): circ = dag_to_circuit(dag) for i in range(self.max_iterations): for _ in ('forward', 'backward'): - pm = _layout_and_route_passmanager(initial_layout) + 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 = _compose_layouts(initial_layout, - pass_final_layout, - circ.qregs) + final_layout = self._compose_layouts(initial_layout, + pass_final_layout, + circ.qregs) initial_layout = final_layout circ = circ.reverse_ops() @@ -145,3 +115,33 @@ def _compose_layouts(initial_layout, pass_final_layout, qregs): 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) diff --git a/test/python/transpiler/test_coupling.py b/test/python/transpiler/test_coupling.py index cebe322e8c14..178a9b1fb425 100644 --- a/test/python/transpiler/test_coupling.py +++ b/test/python/transpiler/test_coupling.py @@ -132,15 +132,6 @@ def test_make_symmetric(self): self.assertEqual(set(edges), set([(0, 1), (0, 2), (2, 0), (1, 0)])) - def test_neighbors(self): - coupling_list = [[0, 1], [0, 2], [2, 0]] - coupling = CouplingMap(coupling_list) - - 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([])) - self.assertEqual(set(coupling.neighbors(physical_qubits[2])), set([0])) - def test_full_factory(self): coupling = CouplingMap.from_full(4) edges = coupling.get_edges() From 20518a2c56021e0037431838a6b6f8a249282619 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Mon, 22 Jun 2020 16:03:55 -0400 Subject: [PATCH 34/34] release note --- .../notes/combine-into-edge-map-279441b53ed4e790.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 releasenotes/notes/combine-into-edge-map-279441b53ed4e790.yaml diff --git a/releasenotes/notes/combine-into-edge-map-279441b53ed4e790.yaml b/releasenotes/notes/combine-into-edge-map-279441b53ed4e790.yaml new file mode 100644 index 000000000000..bab3998e4273 --- /dev/null +++ b/releasenotes/notes/combine-into-edge-map-279441b53ed4e790.yaml @@ -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.