From 42db44de10dc3eb8ce2ce2992ca54e0aa84284fa Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Tue, 12 Feb 2019 18:46:32 +0900 Subject: [PATCH 01/40] add Register --- qiskit/circuit/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py index 9f72821e5d7e..33b56a26c809 100644 --- a/qiskit/circuit/__init__.py +++ b/qiskit/circuit/__init__.py @@ -9,6 +9,7 @@ from .quantumcircuit import QuantumCircuit from .classicalregister import ClassicalRegister from .quantumregister import QuantumRegister +from .register import Register from .gate import Gate from .instruction import Instruction from .instructionset import InstructionSet From cd137038e254a50d914fec476302636c8968946e Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Tue, 12 Feb 2019 18:47:01 +0900 Subject: [PATCH 02/40] add undirected_neighbors --- qiskit/mapper/_coupling.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/qiskit/mapper/_coupling.py b/qiskit/mapper/_coupling.py index b61fe7b9494d..c8edcc84632a 100644 --- a/qiskit/mapper/_coupling.py +++ b/qiskit/mapper/_coupling.py @@ -177,6 +177,19 @@ def shortest_undirected_path(self, physical_qubit1, physical_qubit2): raise CouplingError( "Nodes %s and %s are not connected" % (str(physical_qubit1), str(physical_qubit2))) + def undirected_neighbors(self, physical_qubit): + """List up neighbors of the physical_qubit in the undirected coupling graph. + + Args: + physical_qubit: + + Returns: + List: The neighbors of the physical_qubit + """ + neighbors = self.graph.to_undirected().neighbors(physical_qubit) + return [r for r in neighbors] + + def __str__(self): """Return a string representation of the coupling graph.""" string = "" From 566cb3064f01b1449b8d173fdc33793453ebcd6d Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Tue, 12 Feb 2019 18:53:05 +0900 Subject: [PATCH 03/40] WIP: add flexlayer swapper pass --- .../passes/mapping/algorithm/__init__.py | 12 + .../passes/mapping/algorithm/ancestors.py | 44 ++ .../mapping/algorithm/dependency_graph.py | 277 +++++++++++ .../mapping/algorithm/flexlayer_heuristics.py | 457 ++++++++++++++++++ .../passes/mapping/flexlayer_swap.py | 115 +++++ .../transpiler/test_dependency_graph.py | 117 +++++ .../transpiler/test_flexlayer_heuristics.py | 156 ++++++ test/python/transpiler/test_flexlayer_swap.py | 433 +++++++++++++++++ 8 files changed, 1611 insertions(+) create mode 100644 qiskit/transpiler/passes/mapping/algorithm/__init__.py create mode 100644 qiskit/transpiler/passes/mapping/algorithm/ancestors.py create mode 100644 qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py create mode 100644 qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py create mode 100644 qiskit/transpiler/passes/mapping/flexlayer_swap.py create mode 100644 test/python/transpiler/test_dependency_graph.py create mode 100644 test/python/transpiler/test_flexlayer_heuristics.py create mode 100644 test/python/transpiler/test_flexlayer_swap.py diff --git a/qiskit/transpiler/passes/mapping/algorithm/__init__.py b/qiskit/transpiler/passes/mapping/algorithm/__init__.py new file mode 100644 index 000000000000..1d130fdca886 --- /dev/null +++ b/qiskit/transpiler/passes/mapping/algorithm/__init__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +"""Module containing algorithms used in transpiler pass.""" + +from .flexlayer_heuristics import FlexlayerHeuristics, remove_head_swaps +from .ancestors import Ancestors +from .dependency_graph import DependencyGraph diff --git a/qiskit/transpiler/passes/mapping/algorithm/ancestors.py b/qiskit/transpiler/passes/mapping/algorithm/ancestors.py new file mode 100644 index 000000000000..58ed39cd1f70 --- /dev/null +++ b/qiskit/transpiler/passes/mapping/algorithm/ancestors.py @@ -0,0 +1,44 @@ +# coding: utf-8 + +from functools import lru_cache + +import networkx as nx + +CACHE_SIZE = 2 ** 10 + + +class Ancestors: + def __init__(self, G: nx.DiGraph, max_depth=5): + self._G = G.reverse() + self._max_depth = max_depth + self._depth = 0 + + def ancestors(self, n): + self._depth = 0 + return self._ancestors_rec(n) + + @lru_cache(maxsize=CACHE_SIZE) + def _ancestors_rec(self, n) -> set: + if self._depth >= self._max_depth: + return self._ancestors_loop(n) + self._depth += 1 + ret = set() + for n2 in self._G.successors(n): + ret.add(n2) + ret.update(self._ancestors_rec(n2)) + self._depth -= 1 + return ret + + @lru_cache(maxsize=CACHE_SIZE) + def _ancestors_loop(self, n) -> set: + ret = set() + done = set() + cand = [n] + while cand: + u = cand.pop() + for v in self._G.successors(u): + if v not in done: + ret.add(v) + cand.append(v) + done.add(u) + return ret diff --git a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py new file mode 100644 index 000000000000..bb11b232c4a4 --- /dev/null +++ b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py @@ -0,0 +1,277 @@ +# -*- coding: utf-8 -*- + +# Copyright 2018, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +""" +Dependency graph +""" +import copy +from collections import defaultdict +from typing import List, Set, Tuple + +import networkx as nx + +from qiskit import QuantumCircuit, QuantumRegister +from qiskit.mapper import MapperError + + +class DependencyGraph: + """ + A Dependency graph expresses precedence relations of gates in a quantum circuit considering commutation rules. + + """ + + def __init__(self, + quantum_circuit: QuantumCircuit, + graph_type: str = "basic"): + + self.L = quantum_circuit.data + + self.G = nx.DiGraph() # dependency graph (including 1-qubit gates) + + for i, _ in enumerate(self.L): + self.G.add_node(i) + + self.qubits = set() + for i, _ in enumerate(self.L): + self.qubits.update(self.qargs(i)) + + if graph_type == "xz_commute": + self._create_xz_graph() + elif graph_type == "basic": + self._create_basic_graph() + elif graph_type == "layer": + self._create_layer_graph() + else: + raise MapperError("Unknown graph_type:" + graph_type) + + # remove redundant edges in dependency graph + self.remove_redundancy(self.G) + + def _create_xz_graph(self): + Z_GATES = ["u1", "rz", "s", "t", "z", "sdg", "tdg"] + X_GATES = ["rx", "x"] + B_GATES = ["u3", "h", "u2", "ry", "barrier", "measure", "swap", "y"] + # construct commutation-rules-aware dependency graph + for n in self.G.nodes(): + if self.L[n].name in X_GATES: + [b] = self.L[n].qargs + [pgow] = self._prior_gates_on_wire(self.L, n) + Z_FLAG = False + for m in pgow: + g = self.L[m] + if g.name in B_GATES: + self.G.add_edge(m, n) + break + elif g.name in X_GATES or (g.name == "cx" and g.qargs[1] == b): + if Z_FLAG: + break + else: + continue + elif g.name in Z_GATES or (g.name == "cx" and g.qargs[0] == b): + self.G.add_edge(m, n) + Z_FLAG = True + else: + raise MapperError("Unknown gate: " + g.name) + elif self.L[n].name in Z_GATES: + [b] = self.L[n].qargs + [pgow] = self._prior_gates_on_wire(self.L, n) + X_FLAG = False + for m in pgow: + g = self.L[m] + if g.name in B_GATES: + self.G.add_edge(m, n) + break + elif g.name in X_GATES or (g.name == "cx" and g.qargs[1] == b): + self.G.add_edge(m, n) + X_FLAG = True + elif g.name in Z_GATES or (g.name == "cx" and g.qargs[0] == b): + if X_FLAG: + break + else: + continue + else: + raise MapperError("Unknown gate: " + g.name) + elif self.L[n].name == "cx": + bc, bt = self.L[n].qargs + [cpgow, tpgow] = self._prior_gates_on_wire(self.L, n) + + Z_FLAG = False + for m in tpgow: # target bit: bt + g = self.L[m] + if g.name in B_GATES: + self.G.add_edge(m, n) + break + elif g.name in X_GATES or (g.name == "cx" and g.qargs[1] == bt): + if Z_FLAG: + break + else: + continue + elif g.name in Z_GATES or (g.name == "cx" and g.qargs[0] == bt): + self.G.add_edge(m, n) + Z_FLAG = True + else: + raise MapperError("Unknown gate: " + g.name) + + X_FLAG = False + for m in cpgow: # control bit: bc + g = self.L[m] + if g.name in B_GATES: + self.G.add_edge(m, n) + break + elif g.name in X_GATES or (g.name == "cx" and g.qargs[1] == bc): + self.G.add_edge(m, n) + X_FLAG = True + elif g.name in Z_GATES or (g.name == "cx" and g.qargs[0] == bc): + if X_FLAG: + break + else: + continue + else: + raise MapperError("Unknown gate: " + g.name) + elif self.L[n].name in B_GATES: + for i, pgow in enumerate(self._prior_gates_on_wire(self.L, n)): + b = self._all_args(n)[i] + X_FLAG, Z_FLAG = False, False + for m in pgow: + g = self.L[m] + if g.name in B_GATES: + self.G.add_edge(m, n) + break + elif g.name in X_GATES or (g.name == "cx" and g.qargs[1] == b): + if Z_FLAG: + break + else: + self.G.add_edge(m, n) + X_FLAG = True + elif g.name in Z_GATES or (g.name == "cx" and g.qargs[0] == b): + if X_FLAG: + break + else: + self.G.add_edge(m, n) + Z_FLAG = True + else: + raise MapperError("Unknown gate: " + g.name) + else: + raise MapperError("Unknown gate: " + self.L[n].name) + + def _create_basic_graph(self): + for n in self.G.nodes(): + for pgow in self._prior_gates_on_wire(self.L, n): + m = next(pgow, -1) + if m != -1: + self.G.add_edge(m, n) + + def _create_layer_graph(self): + self._create_basic_graph() + + # construct CNOT layers + layers = [] + wire = defaultdict(int) + for n in self.G.nodes(): + qargs = self.qargs(n) + if self.gate_name(n) == "cx": # consider only CNOTs + i = 1 + max(wire[qargs[0]], wire[qargs[1]]) + wire[qargs[0]] = i + wire[qargs[1]] = i + if len(layers) > i: + layers[i].append(n) + else: + layers.append([n]) + + # Add more edges to basic graph for fixing layers + for i in range(len(layers) - 1): + j = i + 1 + for icx in layers[i]: + for jcx in layers[j]: + self.G.add_edge(icx, jcx) + + def n_nodes(self): + return self.G.__len__() + + def qargs(self, i: int) -> List[Tuple[QuantumRegister, int]]: + """Qubit arguments of the gate + Args: + i: Index of the gate in the quantum gate list + Returns: List of qubit arguments + """ + return self.L[i].qargs + + def _all_args(self, i: int) -> List[Tuple[QuantumRegister, int]]: + """Qubit and classical-bit arguments of the gate + Args: + i: Index of the gate in the quantum gate list + Returns: List of all arguments + """ + return self.L[i].qargs + self.L[i].cargs + + def gate_name(self, i: int) -> str: + """Name of the gate + Args: + i: Index of the gate in the quantum gate list + Returns: Name of the gate + """ + return self.L[i].name + + def head_gates(self) -> Set[int]: + """Gates which can be applicable prior to the other gates + Returns: Set of indices of the gates + """ + return frozenset([n for n in self.G.nodes() if len(self.G.in_edges(n)) == 0]) + + def gr_successors(self, i: int) -> List[int]: + """Successor gates in Gr (transitive reduction) of the gate + Args: + i: Index of the gate in the quantum gate list + Returns: Set of indices of the successor gates + """ + return self.G.successors(i) + + def descendants(self, i: int) -> Set[int]: + return nx.descendants(self.G, i) + + def ancestors(self, i: int) -> Set[int]: + return nx.ancestors(self.G, i) + + def gate(self, g, layout, physical_qreg): + gate = copy.deepcopy(self.L[g]) + for i, logical_qubit in enumerate(gate.qargs): + if logical_qubit in layout.get_virtual_bits().keys(): + gate.qargs[i] = (physical_qreg, layout[logical_qubit]) + else: + raise MapperError("logical_qubit must be in layout") + return gate + + @staticmethod + def remove_redundancy(G): + """remove redundant edges in DAG (= change G to its transitive reduction) + """ + edges = list(G.edges()) + for e in edges: + G.remove_edge(e[0], e[1]) + if not nx.has_path(G, e[0], e[1]): + G.add_edge(e[0], e[1]) + + @staticmethod + def _prior_gates_on_wire(L, toidx): + res = [] + for qarg in L[toidx].qargs: + gates = [] + for i, g in enumerate(L[:toidx]): + if qarg in g.qargs: + gates.append(i) + res.append(reversed(gates)) + for carg in L[toidx].cargs: + gates = [] + for i, g in enumerate(L[:toidx]): + if carg in g.cargs: + gates.append(i) + res.append(reversed(gates)) + return res + + +if __name__ == '__main__': + pass diff --git a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py new file mode 100644 index 000000000000..6f148e297815 --- /dev/null +++ b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py @@ -0,0 +1,457 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +""" +A . + +""" +import collections +import copy +import logging +import pprint +from typing import Dict, Tuple + +import networkx as nx + +from .ancestors import Ancestors +from .dependency_graph import DependencyGraph +from qiskit import QuantumCircuit, QuantumRegister +from qiskit.circuit import Gate +from qiskit.dagcircuit import DAGCircuit +from qiskit.extensions.standard import SwapGate +from qiskit.mapper import MapperError, CouplingMap, Layout + +logger = logging.getLogger(__name__) + + +class FlexlayerHeuristics: + + def __init__(self, + qc: QuantumCircuit, + dependency_graph: DependencyGraph, + coupling: CouplingMap, + initial_layout: Layout, + lookahead_depth: int = 5, + decay_rate: float = 0.5): + + self._qc = qc + + if initial_layout is None: + raise MapperError("FlexlayerHeuristics requires initial_layout") + + self._dg = dependency_graph + self._coupling = coupling + self._initial_layout = copy.deepcopy(initial_layout) + + self._qubit_count_validity_check() + if len(initial_layout.get_virtual_bits()) != len(initial_layout.get_physical_bits()): + raise MapperError("FlexlayerHeuristics assumes #virtual-qubits == #physical-qubits") + # self._add_ancilla_qubits() + + self._lookahead_depth = lookahead_depth + self._decay_rate = decay_rate + + self._ancestors = Ancestors(self._dg.G) # for speed up + + def search(self) -> (DAGCircuit, Layout): + """ + + Returns: + Mapped physical circuit (DAGCircuit) and initial qubit layout (Layout) + """ + qreg = QuantumRegister(self._coupling.size(), name='q') + new_dag = self._create_empty_dagcircuit(qreg) + + # initialize blocking gates + blocking_gates = self._dg.head_gates() + # initialize layout + layout = copy.deepcopy(self._initial_layout) + logger.debug("initial_layout = %s", pprint.pformat(layout)) + + MAX_N_ITERATION = self._coupling.size() * (self._dg.n_nodes() ** 2) + for k in range(1, MAX_N_ITERATION + 1): # escape infinite loop + logger.debug("iteration %d", k) + logger.debug("layout=%s", pprint.pformat(layout)) + + # update blocking gates + blocking_gates, dones = self._next_blocking_gates_from(blocking_gates, layout=layout) + logger.debug("#blocking_gates = %s", pprint.pformat(blocking_gates)) + logger.debug("#done_gates = %d", len(dones)) + + if len(dones) > 0: + for g in dones: + new_dag.apply_operation_back(self._dg.gate(g, layout, qreg)) + + if len(blocking_gates) == 0: + break + + ece = EdgeCostEstimator(gates=blocking_gates, + layout=layout, + coupling=self._coupling, + dg=self._dg, + max_depth=self._lookahead_depth) + + costs = [ece.cost(e, alpha=self._decay_rate) for e in ece.cand_edges] + + min_cost, e = min(zip(costs, ece.cand_edges)) + # logger.debug("top3 edges = %s", pprint.pformat(sorted(zip(costs, ece.cand_edges))[:3])) + + if min_cost.immediate_cost < 0: + # usual case + logger.debug("swap min-cost edge = %s", pprint.pformat(e)) + e = self._fix_swap_direction(e) + new_dag.apply_operation_back(SwapGate(qreg[e[0]], qreg[e[1]])) + # update layout + layout.swap(e[0], e[1]) + else: + # special case necessary to avoid cyclic swaps + logger.debug("cannot reduce total path length -> resolve a single path") + focus_gates = self._find_focus_gates(gates=blocking_gates, + layout=layout) + + MAX_N_INNER_LOOPS = len(focus_gates) * self._coupling.size() + for kk in range(MAX_N_INNER_LOOPS): # escape infinite loop + ece = EdgeCostEstimator(gates=focus_gates, + layout=layout, + coupling=self._coupling, + dg=self._dg, + max_depth=self._lookahead_depth) + + costs = [ece.cost(e, priority=['immediate_cost', + 'lookahead_cost'], alpha=self._decay_rate) for e in ece.cand_edges] + + min_cost, e = min(zip(costs, ece.cand_edges)) + + e = self._fix_swap_direction(e) + new_dag.apply_operation_back(SwapGate(qreg[e[0]], qreg[e[1]])) + layout.swap(e[0], e[1]) + + logger.debug("%d-th inner iter. add a swap (%d, %d)", kk, e[0], e[1]) + logger.debug("resolved layout = %s", pprint.pformat(sorted(layout.items()))) + + dones = self._find_done_gates(blocking_gates, layout=layout) + if len(dones) > 0: + break + + if kk == MAX_N_INNER_LOOPS: + raise MapperError("Unknown error (maybe infinite inner loop)") # bug + + if k == MAX_N_ITERATION: + raise MapperError("UnknowError: #iteration reached MAX_N_ITERATION") + + reslayout = self._initial_layout + # resqc = dag_to_circuit(new_dag) + # resqc, reslayout = remove_head_swaps(resqc, self._initial_layout) + + return new_dag, reslayout + + def _next_blocking_gates_from(self, blocking_gates, layout): + """next blocking gates from blocking_gates for layout with finding applicable gates (dones) + """ + leadings = set(blocking_gates) # new leading gates + dones = [] + new_dones = self._find_done_gates(leadings, layout) + while len(new_dones) > 0: + dones.extend(new_dones) + leadings = self._update_leading_gates(leadings, new_dones) + new_dones = self._find_done_gates(leadings, layout) + + assert (len(dones) == len(set(dones))) + # dones must be list (order is essential!) + + return frozenset(leadings), dones + + def _find_done_gates(self, blocking_gates, layout): + dones = [] + for n in blocking_gates: + qargs = self._dg.qargs(n) + if self._dg.gate_name(n) == "barrier": + dones.append(n) + elif len(qargs) == 1: # 1-qubit gate (including measure) + dones.append(n) + elif len(qargs) == 2: # CNOT(2-qubit gate) + dist = self._coupling.distance(layout[qargs[0]], + layout[qargs[1]]) + if dist == 1: + dones.append(n) + else: + raise MapperError("DG contains unknown >2 qubit gates") + + return dones + + def ancestors(self, n) -> set: + return self._ancestors.ancestors(n) + + def _update_leading_gates(self, leading_gates, dones): + news = set(leading_gates) + for n in dones: + news |= set(self._dg.gr_successors(n)) + + news -= set(dones) + + rmlist = [] + for n in news: + if len(news & self.ancestors(n)) > 0: + rmlist.append(n) + + news -= set(rmlist) + + return news + + def _fix_swap_direction(self, e): + if e in self._coupling.get_edges(): + return e + else: + return e[1], e[0] + + def _find_focus_gates(self, gates, layout): + if len(gates) <= 1: + logger.debug("_find_focus_gates: %d <= 1 gates" % len(gates)) + return gates + + paths = {g: self._path_of(g, layout) for g in gates} + gce = GateCostEstimator(gates, paths) + + costs = [gce.cost(g) for g in gce.gates] + + _, best_gates = min(zip(costs, gce.gates)) + + return [best_gates] + + def _path_of(self, gate, layout): + """ + gate: two-qubit gate + """ + qargs = self._dg.qargs(gate) + if len(qargs) != 2: + raise MapperError("gate must be a two-qubit gate") + s, t = layout[qargs[0]], layout[qargs[1]] + path = self._coupling.shortest_undirected_path(s, t) + return path + + def _qubit_count_validity_check(self): + virtual_qubits = list(self._dg.qubits) + physical_qubits = list(self._coupling.physical_qubits) + + if len(virtual_qubits) > len(physical_qubits): + raise MapperError("Not enough qubits in CouplingMap") + + for q in self._initial_layout.get_physical_bits().keys(): + if q not in physical_qubits: + raise MapperError("%s is not in CouplingMap but in initial_layout" % pprint.pformat(q)) + + def _add_ancilla_qubits(self): + virtual_qubits = sorted(self._dg.qubits) + physical_qubits = sorted(self._coupling.physical_qubits) + for b in self._initial_layout.get_virtual_bits(): + if b not in virtual_qubits: + del self._initial_layout[b] + logger.info("remove unused qubit in initial_layout %s" % pprint.pformat(b)) + + ancilla_qubits = set(physical_qubits) - set(self._initial_layout.get_physical_bits().keys()) + + # add ancilla qubits + if len(ancilla_qubits) > 0: + anc_qreg = QuantumRegister(len(ancilla_qubits), name="ancilla") + for i, aq in enumerate(ancilla_qubits): + virtual_qubits.append((anc_qreg, i)) + self._initial_layout[(anc_qreg, i)] = aq + + assert len(physical_qubits) == len(virtual_qubits), "physical_qubits=%d, virtual_qubits=%d" % ( + len(physical_qubits), len(virtual_qubits)) + + def _create_empty_dagcircuit(self, physical_qreg): + new_dag = DAGCircuit() + new_dag.add_qreg(physical_qreg) + for creg in self._qc.cregs: + new_dag.add_creg(creg) + # new_dag.add_basis_element('swap', 2, 0, 0) + for name, data in self._qc.definitions.items(): + # new_dag.add_basis_element(name, data["n_bits"], 0, data["n_args"]) + new_dag.add_gate_data(name, data) + return new_dag + + +class GateCostEstimator: + + def __init__(self, gates, paths): + self.gates = gates + self.paths = paths + + def cost(self, gate, priority=None): + """estimate cost if the gate pair (specified by gate_idx_pair) are resolved first + """ + if priority is None: + priority = ['dependent_cost'] + + Cost = collections.namedtuple('Cost', priority) + + pi = self.paths[gate] + + dc = 0 # dependent_cost + for j in self.gates: + pj = self.paths[j] + if pj[0] in pi or pj[-1] in pi: + dc += 1 + + return Cost(dependent_cost=dc) + + +class EdgeCostEstimator: + + def __init__(self, gates, layout, coupling, dg, max_depth=10): + self.cand_edges = [] + for gate in gates: + qargs = dg.qargs(gate) + if len(qargs) != 2: + raise MapperError("EdgeCostEstimator is only for two-qubit gates") + s, t = layout[qargs[0]], layout[qargs[1]] + for v in coupling.undirected_neighbors(s): + if coupling.distance(s, t) > coupling.distance(v, t): + self.cand_edges.append((s, v)) + for u in coupling.undirected_neighbors(t): + if coupling.distance(s, t) > coupling.distance(s, u): + self.cand_edges.append((u, t)) + + self.care_ends_list = self._construct_care_ends_list(gates, layout, dg, max_depth) + + self.coupling = coupling + + def cost(self, e, priority=None, alpha=0.5): + """ + estimate cost if the edge e is swapped + Args: + e: edge whose cost is estimated as pair of physical qubits, ex. (('q', 1), ('q', 2)) + priority: costs are sorted by this lexicographic order + alpha: discount rate for cost of post layer gates + """ + if priority is None: + priority = ['lookahead_cost', 'immediate_cost'] + + Cost = collections.namedtuple('Cost', priority) + lc = 0 # lookahead_cost + ic = 0 # immediate_cost + for (s, t), d in self.care_ends_list: + inc = 0 + if (s in e) ^ (t in e): # xor: either node of e is s or t of p (path from s to t) + if s == e[0]: + inc = self._after_swap_cost(e[0], e[1], t) + elif s == e[1]: + inc = self._after_swap_cost(e[1], e[0], t) + elif t == e[0]: + inc = self._after_swap_cost(e[0], e[1], s) + elif t == e[1]: + inc = self._after_swap_cost(e[1], e[0], s) + else: + raise MapperError("Unknown error") + + lc += inc * (alpha ** d) + if d == 0: # current layer + ic += inc + + return Cost(lookahead_cost=lc, immediate_cost=ic) + + def _construct_care_ends_list(self, gates, layout, dg, max_depth): + nodes = set(gates) + dic = collections.defaultdict(int) # approximate max path length from gates to the key gate + for d in range(max_depth): + nexts = set([s for n in nodes for s in dg.gr_successors(n)]) + for n in nexts: + if len(dg.qargs(n)) == 2: + dic[n] = d + 1 + nodes = nexts + + care_ends_list = [] + for g in gates: + qargs = dg.qargs(g) + s, t = layout[qargs[0]], layout[qargs[1]] + care_ends_list.append(((s, t), 0)) + + for g, d in dic.items(): + if d <= max_depth: + qargs = dg.qargs(g) + s, t = layout[qargs[0]], layout[qargs[1]] + care_ends_list.append(((s, t), d)) + + return care_ends_list + + def _after_swap_cost(self, s1, s2, t): + """return distance difference in switching path s1->t to path s2->t + for the coupling as undirected graph + """ + d1 = self.coupling.distance(s1, t) + d2 = self.coupling.distance(s2, t) + if abs(d1 - d2) > 1: + raise MapperError("Invalid shortest paths on coupling graph") + return d2 - d1 + + +def _qargs(gate): + return [ridx for (reg, ridx) in gate.qargs] + + +def remove_head_swaps(qc: QuantumCircuit, + initial_layout: dict) -> (QuantumCircuit, Dict[Tuple[str, int], Tuple[str, int]]): + """remove unnecessary swap gates from qc by changing initial_layout + assume all of the gates in qc are expanded to one-qubit gate, cx, swap + """ + + rev_new_layout = {v: k for k, v in initial_layout.items()} + cx_seen = collections.defaultdict(bool) # phisical qubit -> seen first cx or not + to_be_removed = [] + for i, gate in enumerate(qc.data): + if len(gate.qargs) > 1: + qargs = _qargs(gate) + if gate.name == "swap": + if (not cx_seen[qargs[0]]) and (not cx_seen[qargs[1]]): + # swap before the first cx -> update rev_layout and do not output swap to qasm + rev_new_layout[qargs[0]], rev_new_layout[qargs[1]] = rev_new_layout[qargs[1]], rev_new_layout[ + qargs[0]] + to_be_removed.append(i) + else: + # must change flag! because left swap = cx + cx_seen[qargs[0]] = True + cx_seen[qargs[1]] = True + else: + for qarg in qargs: + cx_seen[qarg] = True + + logger.debug("remove_head_swaps: n_removed = %d" % len(to_be_removed)) + + resqc = copy.deepcopy(qc) + new_initial_layout = {v: k for k, v in rev_new_layout.items()} + new_layout = new_initial_layout + rev_org_layout = {v: k for k, v in initial_layout.items()} + + if len(to_be_removed) > 0: + qregs = resqc.qregs + for i, gate in enumerate(resqc.data[:1 + to_be_removed[-1]]): + qargs = _qargs(gate) + if gate.name == "swap": + rev_org_layout[qargs[0]], rev_org_layout[qargs[1]] = rev_org_layout[qargs[1]], rev_org_layout[qargs[0]] + if i not in to_be_removed: + rev_new_layout[qargs[0]], rev_new_layout[qargs[1]] = rev_new_layout[qargs[1]], rev_new_layout[ + qargs[0]] + new_layout = {v: k for k, v in rev_new_layout.items()} + else: + _change_qargs(gate, rev_org_layout, new_layout, qregs) + + resqc.data = [g for i, g in enumerate(resqc.data) if i not in to_be_removed] + + return resqc, new_initial_layout + + +def _change_qargs(gate: Gate, rev_org_layout: dict, new_layout: dict, qregs: dict): + if gate.name == "measure": + raise MapperError("_change_qargs() is not applicable to measure gate") + + new_qarg = [] + for qubit in _qargs(gate): + new_qubit = new_layout[rev_org_layout[qubit]] + new_qarg.append((qregs[new_qubit[0]], new_qubit[1])) + + gate.arg = new_qarg diff --git a/qiskit/transpiler/passes/mapping/flexlayer_swap.py b/qiskit/transpiler/passes/mapping/flexlayer_swap.py new file mode 100644 index 000000000000..415527246680 --- /dev/null +++ b/qiskit/transpiler/passes/mapping/flexlayer_swap.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +""" +A pass implementing the flexible-layer mapper. + +That is the swap mapper proposed in the paper [Itoko et. al. 2019]. +For the role of the swap mapper pass, see `lookahed_swap.py`. + +This algorithm considers the *dependency graph* of a given circuit +with less dependencies by considering commutativity of consecutive gates, +and updates `blocking gates` in the dependency graph by changing qubit layout +(= adding SWAPs). The blocking gates are the leading unresolved gates for +a current layout, and they can be seen as a kind of *flexible layer* +in contrast to many other swap passes assumes fixed layers as their input. +That's why this pass is named FlexlayerSwap pass. + +The outline of the algorithm is as follows. +0. Assume an initial_layout is given and set it to `layout`. +1. Initialize `blocking_gates` as gates without in-edge in dependency graph. +2. Update `blocking_gates` by processing applicable gates for the `layout`. +3. If it comes to no blocking gates, it terminates. Otherwise, it selects a qubit + pair (= an edge in the coupling graph) to be swapped based on its `cost`. +4. Add the swap gate at the min-cost edge (= update `layout`). +5. Go back to the step 2. +Note: In the actual flow, there is an additional path for avoiding handling cyclic swaps in step 3. + +For more details on the algorithm, see [Itoko et. al. 2019]: +T. Itoko, R. Raymond, T. Imamichi, A. Matsuo, and A. W. Cross. +Quantum circuit compilers using gate commutation rules. +In Proceedings of ASP-DAC, pp. 191--196. ACM, 2019. +""" +from qiskit.converters import dag_to_circuit, circuit_to_dag +from qiskit.dagcircuit import DAGCircuit +from qiskit.mapper import CouplingMap, Layout +from qiskit.transpiler import TransformationPass +from .algorithm.dependency_graph import DependencyGraph +from .algorithm.flexlayer_heuristics import FlexlayerHeuristics +from .barrier_before_final_measurements import BarrierBeforeFinalMeasurements + + +class FlexlayerSwap(TransformationPass): + """ + Maps a DAGCircuit onto a `coupling_map` inserting swap gates. + """ + + def __init__(self, + coupling_map: CouplingMap, + initial_layout: Layout = None, + lookahead_depth: int = 10, + decay_rate: float = 0.5): + """ + Maps a DAGCircuit onto a `coupling_map` using swap gates for a given `initial_layout`. + Args: + coupling_map: Directed graph represented a coupling map. + initial_layout: initial layout of qubits in mapping + lookahead_depth: how far gates from blocking gates should be looked ahead + decay_rate: decay rate of look-ahead weight (0 < decay_rate < 1) + """ + super().__init__() + self.requires.append(BarrierBeforeFinalMeasurements()) + self._coupling_map = coupling_map + self._initial_layout = initial_layout + self._lookahead_depth = lookahead_depth + self._decay_rate = decay_rate + + def run(self, dag: DAGCircuit) -> DAGCircuit: + """ + Runs the FlexlayerSwap pass on `dag`. + Args: + dag: DAG to map. + Returns: + A mapped DAG (with virtual qubits). + """ + if not self._initial_layout: + self._initial_layout = self.property_set["layout"] + if not self._initial_layout: + self._initial_layout = Layout.generate_trivial_layout(*dag.qregs.values()) + + qc = dag_to_circuit(dag) + dg = DependencyGraph(qc, graph_type="xz_commute") + lh = FlexlayerHeuristics(qc=qc, + dependency_graph=dg, + coupling=self._coupling_map, + initial_layout=self._initial_layout, + lookahead_depth=self._lookahead_depth, + decay_rate=self._decay_rate) + res_dag, layout = lh.search() + res_dag = physical_to_virtual(res_dag, layout) + return res_dag + + +def physical_to_virtual(dag: DAGCircuit, initial_layout: Layout) -> DAGCircuit: + """ + Convert a physical circuit `dag` into the virtual circuit under a given `initial_layout`. + Args: + dag: a physical circuit, assuming 'q' is the name of its physical qubit. + initial_layout: given initial layout. + """ + layout = {} + qubits = dag.qubits() + for k, v in initial_layout.get_physical_bits().items(): + layout[qubits[k]] = v + + circuit = dag_to_circuit(dag) + circuit.qregs = initial_layout.get_registers() + for gate in circuit.data: + for i, q in enumerate(gate.qargs): + gate.qargs[i] = layout[q] + + return circuit_to_dag(circuit) diff --git a/test/python/transpiler/test_dependency_graph.py b/test/python/transpiler/test_dependency_graph.py new file mode 100644 index 000000000000..3e0f0391035e --- /dev/null +++ b/test/python/transpiler/test_dependency_graph.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +import unittest + +# from qiskit.transpiler.passes.algorithm import DependencyGraph +from new_swappers.algorithm import DependencyGraph +from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit +from qiskit.test import QiskitTestCase + + +class TestDependencyGraph(QiskitTestCase): + """Tests the DependencyGraph.""" + + def test_qargs(self): + q = QuantumRegister(2, 'q') + c = ClassicalRegister(2, 'c') + circuit = QuantumCircuit(q, c) + circuit.cx(q[1], q[0]) + circuit.measure(q[0], c[1]) + dg = DependencyGraph(circuit) + self.assertEqual(dg.qargs(0), [(q, 1), (q, 0)]) + self.assertEqual(dg.qargs(1), [(q, 0)]) + + def test_head_gates(self): + q = QuantumRegister(4, 'q') + c = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(q, c) + circuit.cx(q[0], q[1]) + circuit.cx(q[2], q[3]) + circuit.cx(q[1], q[2]) + circuit.h(q[0]) + circuit.cx(q[0], q[1]) + circuit.cx(q[2], q[3]) + dg = DependencyGraph(circuit) + self.assertEqual(dg.head_gates(), set([0, 1])) + + def test_gr_successors(self): + q = QuantumRegister(4, 'q') + c = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(q, c) + circuit.cx(q[0], q[1]) + circuit.cx(q[2], q[3]) + circuit.cx(q[1], q[2]) + circuit.h(q[0]) + circuit.cx(q[0], q[1]) + circuit.cx(q[2], q[3]) + dg = DependencyGraph(circuit) + self.assertEqual(list(dg.gr_successors(0)), [2, 3]) + self.assertEqual(list(dg.gr_successors(2)), [4, 5]) + self.assertEqual(list(dg.gr_successors(4)), []) + + def test_barrier(self): + b = QuantumRegister(4, 'b') + c = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(b, c) + circuit.cx(b[0], b[1]) + circuit.barrier(b) + circuit.cx(b[1], b[0]) + circuit.cx(b[2], b[3]) + dg = DependencyGraph(circuit) + expected = sorted([(0, 1), (1, 2), (1, 3)]) + self.assertEqual(list(dg.G.edges()), expected) + + def test_measure(self): + b = QuantumRegister(3, 'b') + c = ClassicalRegister(3, 'c') + circuit = QuantumCircuit(b, c) + circuit.h(b[0]) + circuit.cx(b[0], b[1]) + circuit.measure(b[0], c[1]) + circuit.cx(b[1], b[2]) + circuit.measure(b[2], c[1]) # overwrite -> introduce dependency (2, 4) + dg = DependencyGraph(circuit, graph_type="xz_commute") + expected_edges = sorted([(0, 1), (1, 2), (1, 3), (2, 4), (3, 4)]) + self.assertEqual(list(dg.G.edges()), expected_edges) + + def test_h_gate_in_the_middle(self): + q = QuantumRegister(4, 'q') + c = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(q, c) + circuit.cx(q[0], q[1]) + circuit.cx(q[2], q[3]) + circuit.cx(q[1], q[2]) + circuit.h(q[1]) + circuit.cx(q[1], q[0]) + dg = DependencyGraph(circuit, graph_type="xz_commute") + expected = sorted([(0, 2), (1, 2), (2, 3), (3, 4)]) + self.assertEqual(list(dg.G.edges()), expected) + + def test_rz_gate_in_the_middle(self): + q = QuantumRegister(4, 'q') + c = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(q, c) + circuit.cx(q[0], q[1]) + circuit.cx(q[2], q[3]) + circuit.cx(q[1], q[2]) + circuit.rz(1.0, q[1]) + circuit.cx(q[1], q[0]) + dg = DependencyGraph(circuit, graph_type="xz_commute") + expected = sorted([(0, 2), (1, 2), (0, 3), (0, 4)]) + self.assertEqual(list(dg.G.edges()), expected) + + def test_rz_gate_in_the_middle_blg(self): + q = QuantumRegister(4, 'q') + c = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(q, c) + circuit.cx(q[0], q[1]) + circuit.cx(q[2], q[3]) + circuit.cx(q[1], q[2]) + circuit.rz(1.0, q[1]) + circuit.cx(q[1], q[0]) + dg = DependencyGraph(circuit, graph_type="basic") + expected = sorted([(0, 2), (1, 2), (2, 3), (3, 4)]) + self.assertEqual(list(dg.G.edges()), expected) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/python/transpiler/test_flexlayer_heuristics.py b/test/python/transpiler/test_flexlayer_heuristics.py new file mode 100644 index 000000000000..aaab8a40b579 --- /dev/null +++ b/test/python/transpiler/test_flexlayer_heuristics.py @@ -0,0 +1,156 @@ +# -*- coding: utf-8 -*- +import unittest +from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister +from qiskit.mapper import CouplingMap, Layout +from qiskit.converters import circuit_to_dag, dag_to_circuit + +# from qiskit.transpiler.passes.algorithm import DependencyGraph +from new_swappers.algorithm import FlexlayerHeuristics, remove_head_swaps, DependencyGraph + + +class TestLookaheadHeuristics(unittest.TestCase): + """Tests for dependency_graph.py""" + + @unittest.skip("due to a bug in DAGCircuit.__eq__()") + def test_search_n4cx4h1(self): + q = QuantumRegister(4, 'q') + c = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(q, c) + circuit.cx(q[0], q[1]) + circuit.cx(q[2], q[3]) + circuit.cx(q[1], q[2]) + circuit.h(q[1]) + circuit.cx(q[1], q[0]) + circuit.barrier() + for i in range(4): + circuit.measure(q[i], c[i]) + dg = DependencyGraph(circuit, graph_type="basic") + coupling = CouplingMap([[0, 1], [1, 2], [1, 3]]) # {0: [1], 1: [2, 3]} + initial_layout = Layout.generate_trivial_layout(q) + algo = FlexlayerHeuristics(circuit, dg, coupling, initial_layout) + actual_dag, layout = algo.search() + + expected = QuantumCircuit(q, c) + expected.cx(q[0], q[1]) + expected.swap(q[1], q[2]) + expected.cx(q[1], q[3]) + expected.cx(q[2], q[1]) + expected.h(q[2]) + expected.swap(q[0], q[1]) + expected.cx(q[2], q[1]) + expected.barrier() + expected.measure(q[1], c[0]) + expected.measure(q[2], c[1]) + expected.measure(q[0], c[2]) + expected.measure(q[3], c[3]) + expected_layout = initial_layout + from qiskit.tools.visualization import dag_drawer + dag_drawer(actual_dag) + dag_drawer(circuit_to_dag(expected)) + print(dag_to_circuit(actual_dag).draw()) + print(expected.draw()) + self.assertEqual(actual_dag, circuit_to_dag(expected)) + self.assertEqual(layout, expected_layout) + + @unittest.skip("TODO: Change to use DAGCircuit and Layout") + def test_search_multi_creg(self): + b = QuantumRegister(4, 'b') + c = ClassicalRegister(2, 'c') + d = ClassicalRegister(2, 'd') + circuit = QuantumCircuit(b, c, d) + circuit.cx(b[0], b[1]) + circuit.cx(b[2], b[3]) + circuit.cx(b[1], b[2]) + circuit.h(b[1]) + circuit.cx(b[1], b[0]) + circuit.barrier(b) + circuit.measure(b[0], c[0]) + circuit.measure(b[1], c[1]) + circuit.measure(b[2], d[0]) + circuit.measure(b[3], d[1]) + dg = DependencyGraph(circuit, graph_type="basic") + coupling = CouplingMap([[0, 1], [1, 2], [1, 3]]) # {0: [1], 1: [2, 3]} + initial_layout = Layout.generate_trivial_layout(b) + algo = FlexlayerHeuristics(circuit, dg, coupling, initial_layout) + qc, layout = algo.search() + actual_measures = [s for s in qc.qasm().split('\n') if s.startswith("measure")] + expected_measures = [] + expected_measures.append("measure q[0] -> d[0];") + expected_measures.append("measure q[1] -> c[0];") + expected_measures.append("measure q[2] -> c[1];") + expected_measures.append("measure q[3] -> d[1];") + expected_layout = initial_layout + self.assertEqual(sorted(actual_measures), expected_measures) + self.assertEqual(layout, expected_layout) + + @unittest.skip("TODO: Change to use DAGCircuit and Layout") + def test_remove_head_swaps(self): + q = QuantumRegister(4, 'q') + c = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(q, c) + circuit.cx(q[0], q[1]) + circuit.u1(1, q[2]) + circuit.swap(q[2], q[3]) + circuit.cx(q[1], q[2]) + for i in range(4): + circuit.measure(q[i], c[i]) + initial_layout = {('q', i): ('q', i) for i in range(4)} + resqc, layout = remove_head_swaps(circuit, initial_layout) + actual_qasm = ''.join([s for s in resqc.qasm().split('\n') if not s.startswith("measure")]) + actual_measure = sorted([s for s in resqc.qasm().split('\n') if s.startswith("measure")]) + header = 'OPENQASM 2.0;include "qelib1.inc";qreg q[4];creg c[4];' + expected_qasm = header + "cx q[0],q[1];u1(1) q[3];cx q[1],q[2];" + expected_measure = ["measure q[%d] -> c[%d];" % (i, i) for i in range(4)] + expected_layout = {('q', 0): ('q', 0), ('q', 1): ('q', 1), ('q', 3): ('q', 2), ('q', 2): ('q', 3)} + self.assertEqual(actual_qasm, expected_qasm) + self.assertEqual(actual_measure, expected_measure) + self.assertEqual(layout, expected_layout) + + # @unittest.skip("WIP") + # def test_search_rd32_v0_67(self): + # qp = QuantumProgram() + # qasm_text = 'OPENQASM 2.0;\ + # include "qelib1.inc";\ + # gate peres a,b,c {ccx a, b, c; cx a, b;}\ + # qreg q[4];\ + # creg c[4];\ + # peres q[0], q[1], q[3];\ + # peres q[1], q[2], q[3];\ + # barrier q;\ + # measure q->c;' + # qc = load_qasm_string(qasm_text, basis_gates="u1,u2,u3,cx,id") + # dg = DependencyGraph(qc, graph_type="basic") + # coupling = Coupling({1: [0], 2: [0, 1, 4], 3: [2, 4]}) + # initial_layout = {('q', 0): ('q', 1), ('q', 1): ('q', 0), ('q', 2): ('q', 2), ('q', 3): ('q', 4)} + # algo = FlexlayerHeuristics(qc, dg, coupling, initial_layout) + # dag, layout = algo.search() + + @unittest.skip("TODO: Change to use DAGCircuit and Layout") + def test_remove_head_swaps2(self): + q = QuantumRegister(6, 'q') + c = ClassicalRegister(6, 'c') + circuit = QuantumCircuit(q, c) + circuit.u1(1, q[4]) + circuit.h(q[3]) + circuit.swap(q[4], q[5]) + circuit.cx(q[3], q[4]) + circuit.swap(q[3], q[4]) + circuit.u1(1, q[1]) + circuit.h(q[0]) + circuit.swap(q[0], q[1]) + circuit.swap(q[1], q[2]) + circuit.swap(q[2], q[3]) + circuit.cx(q[3], q[4]) + initial_layout = {('b', i): ('q', i) for i in range(6)} + resqc, layout = remove_head_swaps(circuit, initial_layout) + actual_qasm = ''.join([s for s in resqc.qasm().split('\n')]) + header = 'OPENQASM 2.0;include "qelib1.inc";qreg q[6];creg c[6];' + expected_qasm = header + "u1(1) q[5];h q[3];cx q[3],q[4];swap q[3],q[4];u1(1) q[0];h q[2];swap q[2],q[3];cx q[3],q[4];" + expected_layout = {('b', 0): ('q', 2), ('b', 1): ('q', 0), ('b', 2): ('q', 1), ('b', 3): ('q', 3), + ('b', 4): ('q', 5), ('b', 5): ('q', 4)} + self.assertEqual(actual_qasm, expected_qasm) + self.assertEqual(layout, expected_layout) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/python/transpiler/test_flexlayer_swap.py b/test/python/transpiler/test_flexlayer_swap.py new file mode 100644 index 000000000000..7be7ab327b20 --- /dev/null +++ b/test/python/transpiler/test_flexlayer_swap.py @@ -0,0 +1,433 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +"""Test the FlexlayerSwap pass""" + +import unittest +# from qiskit.transpiler.passes import FlexlayerSwap +from new_swappers import FlexlayerSwap +from qiskit.transpiler import PassManager, transpile_dag +from qiskit.mapper import CouplingMap, Layout +from qiskit.converters import circuit_to_dag, dag_to_circuit +from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit +from qiskit.test import QiskitTestCase + + +class TestFlexlayerSwap(QiskitTestCase): + """ Tests the FlexlayerSwap pass.""" + + def test_trivial_case(self): + """No need to have any swap, the CX are distance 1 to each other + q0:--(+)-[U]-(+)- + | | + q1:---.-------|-- + | + q2:-----------.-- + + CouplingMap map: [1]--[0]--[2] + """ + coupling = CouplingMap([[0, 1], [0, 2]]) + + qr = QuantumRegister(3, 'qr') + circuit = QuantumCircuit(qr) + circuit.cx(qr[0], qr[1]) + circuit.h(qr[0]) + circuit.cx(qr[0], qr[2]) + + dag = circuit_to_dag(circuit) + pass_ = FlexlayerSwap(coupling) + after = pass_.run(dag) + + self.assertEqual(dag, after) + + def test_trivial_in_same_layer(self): + """ No need to have any swap, two CXs distance 1 to each other, in the same layer + q0:--(+)-- + | + q1:---.--- + + q2:--(+)-- + | + q3:---.--- + + CouplingMap map: [0]--[1]--[2]--[3] + """ + coupling = CouplingMap([[0, 1], [1, 2], [2, 3]]) + + qr = QuantumRegister(4, 'qr') + circuit = QuantumCircuit(qr) + circuit.cx(qr[2], qr[3]) + circuit.cx(qr[0], qr[1]) + + dag = circuit_to_dag(circuit) + pass_ = FlexlayerSwap(coupling) + after = pass_.run(dag) + + self.assertEqual(dag, after) + + def test_a_single_swap(self): + """ Adding a swap + q0:------- + + q1:--(+)-- + | + q2:---.--- + + CouplingMap map: [1]--[0]--[2] + + q0:--X---.--- + | | + q1:--X---|--- + | + q2:-----(+)-- + + """ + coupling = CouplingMap([[0, 1], [0, 2]]) + + qr = QuantumRegister(3, 'qr') + circuit = QuantumCircuit(qr) + circuit.cx(qr[1], qr[2]) + dag = circuit_to_dag(circuit) + + expected = QuantumCircuit(qr) + expected.swap(qr[0], qr[2]) + expected.cx(qr[1], qr[0]) + + pass_ = FlexlayerSwap(coupling) + after = pass_.run(dag) + + self.assertEqual(circuit_to_dag(expected), after) + + def test_keep_layout(self): + """After a swap, the following gates also change the wires. + qr0:---.---[H]-- + | + qr1:---|-------- + | + qr2:--(+)------- + + CouplingMap map: [0]--[1]--[2] + + qr0:--X----------- + | + qr1:--X---.--[H]-- + | + qr2:-----(+)------ + """ + coupling = CouplingMap([[1, 0], [1, 2]]) + + qr = QuantumRegister(3, 'qr') + circuit = QuantumCircuit(qr) + circuit.cx(qr[0], qr[2]) + circuit.h(qr[0]) + dag = circuit_to_dag(circuit) + + expected = QuantumCircuit(qr) + expected.swap(qr[1], qr[0]) + expected.cx(qr[1], qr[2]) + expected.h(qr[1]) + + pass_ = FlexlayerSwap(coupling) + after = pass_.run(dag) + + self.assertEqual(circuit_to_dag(expected), after) + + def test_far_swap(self): + """ A far swap that affects coming CXs. + qr0:--(+)---.-- + | | + qr1:---|----|-- + | | + qr2:---|----|-- + | | + qr3:---.---(+)- + + CouplingMap map: [0]--[1]--[2]--[3] + + qr0:--X-------------- + | + qr1:--X--X----------- + | + qr2:-----X--(+)---.-- + | | + qr3:---------.---(+)- + + """ + coupling = CouplingMap([[0, 1], [1, 2], [2, 3]]) + + qr = QuantumRegister(4, 'qr') + circuit = QuantumCircuit(qr) + circuit.cx(qr[0], qr[3]) + circuit.cx(qr[3], qr[0]) + dag = circuit_to_dag(circuit) + + expected = QuantumCircuit(qr) + expected.swap(qr[0], qr[1]) + expected.swap(qr[1], qr[2]) + expected.cx(qr[2], qr[3]) + expected.cx(qr[3], qr[2]) + + pass_ = FlexlayerSwap(coupling) + after = pass_.run(dag) + + self.assertEqual(circuit_to_dag(expected), after) + + def test_far_swap_with_gate_the_front(self): + """ A far swap with a gate in the front. + qr0:------(+)-- + | + qr1:-------|--- + | + qr2:-------|--- + | + qr3:--[H]--.--- + + CouplingMap map: [0]--[1]--[2]--[3] + + q0:------X---------- + | + q1:------X--X------- + | + q2:---------X--(+)-- + | + q3:-[H]---------.--- + + """ + coupling = CouplingMap([[0, 1], [1, 2], [2, 3]]) + + qr = QuantumRegister(4, 'qr') # virtual qubit + circuit = QuantumCircuit(qr) + circuit.h(qr[3]) + circuit.cx(qr[3], qr[0]) + dag = circuit_to_dag(circuit) + + expected = QuantumCircuit(qr) + expected.h(qr[3]) + expected.swap(qr[0], qr[1]) + expected.swap(qr[1], qr[2]) + expected.cx(qr[3], qr[2]) + + pass_ = FlexlayerSwap(coupling) + after = pass_.run(dag) + + self.assertEqual(circuit_to_dag(expected), after) + + def test_initial_layout(self): + """ Using an initial_layout + 0:q1:--(+)-- + | + 1:q0:---|--- + | + 2:q2:---.--- + + CouplingMap map: [0]--[1]--[2] + + 0:q1:--X------- + | + 1:q0:--X---.--- + | + 2:q2:-----(+)-- + + """ + coupling = CouplingMap([[0, 1], [1, 2]]) + + qr = QuantumRegister(3, 'q') + circuit = QuantumCircuit(qr) + circuit.cx(qr[1], qr[2]) + dag = circuit_to_dag(circuit) + layout = Layout([(qr, 1), (qr, 0), (qr, 2)]) + + expected = QuantumCircuit(qr) + expected.swap(qr[1], qr[0]) + expected.cx(qr[0], qr[2]) + + pass_ = FlexlayerSwap(coupling, initial_layout=layout) + after = pass_.run(dag) + + self.assertEqual(circuit_to_dag(expected), after) + + def test_initial_layout_in_different_qregs(self): + """ Using an initial_layout, and with several qregs + 0:q1_0:--(+)-- + | + 1:q0_0:---|--- + | + 2:q2_0:---.--- + + CouplingMap map: [0]--[1]--[2] + + 0:q1_0:--X------- + | + 1:q0_0:--X---.--- + | + 2:q2_0:-----(+)-- + """ + coupling = CouplingMap([[0, 1], [1, 2]]) + + qr0 = QuantumRegister(1, 'q0') + qr1 = QuantumRegister(1, 'q1') + qr2 = QuantumRegister(1, 'q2') + circuit = QuantumCircuit(qr0, qr1, qr2) + circuit.cx(qr1[0], qr2[0]) + dag = circuit_to_dag(circuit) + layout = Layout([(qr1, 0), (qr0, 0), (qr2, 0)]) + + expected = QuantumCircuit(qr0, qr1, qr2) + expected.swap(qr1[0], qr0[0]) + expected.cx(qr0[0], qr2[0]) + + pass_ = FlexlayerSwap(coupling, initial_layout=layout) + after = pass_.run(dag) + + self.assertEqual(circuit_to_dag(expected), after) + + + # def test_flexlayer_swap_doesnt_modify_mapped_circuit(self): + # """Test that lookahead mapper is idempotent. + # + # It should not modify a circuit which is already compatible with the + # coupling map, and can be applied repeatedly without modifying the circuit. + # """ + # + # qr = QuantumRegister(3, name='q') + # circuit = QuantumCircuit(qr) + # circuit.cx(qr[0], qr[2]) + # circuit.cx(qr[0], qr[1]) + # original_dag = circuit_to_dag(circuit) + # + # # Create coupling map which contains all two-qubit gates in the circuit. + # coupling_map = CouplingMap([[0, 1], [0, 2]]) + # + # pass_manager = PassManager() + # pass_manager.append(FlexlayerSwap(coupling_map)) + # mapped_dag = transpile_dag(original_dag, pass_manager=pass_manager) + # + # self.assertEqual(original_dag, mapped_dag) + # + # second_pass_manager = PassManager() + # second_pass_manager.append(FlexlayerSwap(coupling_map)) + # remapped_dag = transpile_dag(mapped_dag, pass_manager=second_pass_manager) + # + # self.assertEqual(mapped_dag, remapped_dag) + # + # def test_flexlayer_swap_should_add_a_single_swap(self): + # """Test that LookaheadSwap will insert a SWAP to match layout. + # + # For a single cx gate which is not available in the current layout, test + # that the mapper inserts a single swap to enable the gate. + # """ + # + # qr = QuantumRegister(3) + # circuit = QuantumCircuit(qr) + # circuit.cx(qr[0], qr[2]) + # dag_circuit = circuit_to_dag(circuit) + # + # coupling_map = CouplingMap([[0, 1], [1, 2]]) + # + # pass_manager = PassManager() + # pass_manager.append([FlexlayerSwap(coupling_map)]) + # mapped_dag = transpile_dag(dag_circuit, pass_manager=pass_manager) + # + # self.assertEqual(mapped_dag.count_ops().get('swap', 0), + # dag_circuit.count_ops().get('swap', 0) + 1) + # + # def test_flexlayer_swap_finds_minimal_swap_solution(self): + # """Of many valid SWAPs, test that LookaheadSwap finds the cheapest path. + # + # For a two CNOT circuit: cx q[0],q[2]; cx q[0],q[1] + # on the initial layout: qN -> qN + # (At least) two solutions exist: + # - SWAP q[0],[1], cx q[0],q[2], cx q[0],q[1] + # - SWAP q[1],[2], cx q[0],q[2], SWAP q[1],q[2], cx q[0],q[1] + # + # Verify that we find the first solution, as it requires fewer SWAPs. + # """ + # + # qr = QuantumRegister(3) + # circuit = QuantumCircuit(qr) + # circuit.cx(qr[0], qr[2]) + # circuit.cx(qr[0], qr[1]) + # + # dag_circuit = circuit_to_dag(circuit) + # + # coupling_map = CouplingMap([[0, 1], [1, 2]]) + # + # pass_manager = PassManager() + # pass_manager.append([FlexlayerSwap(coupling_map)]) + # mapped_dag = transpile_dag(dag_circuit, pass_manager=pass_manager) + # + # self.assertEqual(mapped_dag.count_ops().get('swap', 0), + # dag_circuit.count_ops().get('swap', 0) + 1) + # + # def test_flexlayer_swap_maps_measurements(self): + # """Verify measurement nodes are updated to map correct cregs to re-mapped qregs. + # + # Create a circuit with measures on q0 and q2, following a swap between q0 and q2. + # Since that swap is not in the coupling, one of the two will be required to move. + # Verify that the mapped measure corresponds to one of the two possible layouts following + # the swap. + # + # """ + # + # qr = QuantumRegister(3) + # cr = ClassicalRegister(2) + # circuit = QuantumCircuit(qr, cr) + # + # circuit.cx(qr[0], qr[2]) + # circuit.measure(qr[0], cr[0]) + # circuit.measure(qr[2], cr[1]) + # + # dag_circuit = circuit_to_dag(circuit) + # + # coupling_map = CouplingMap([[0, 1], [1, 2]]) + # + # pass_manager = PassManager() + # pass_manager.append([FlexlayerSwap(coupling_map)]) + # mapped_dag = transpile_dag(dag_circuit, pass_manager=pass_manager) + # + # mapped_measure_qargs = set(mapped_dag.multi_graph.nodes(data=True)[op]['qargs'][0] + # for op in mapped_dag.get_named_nodes('measure')) + # + # self.assertIn(mapped_measure_qargs, + # [set(((QuantumRegister(3, 'q'), 0), (QuantumRegister(3, 'q'), 1))), + # set(((QuantumRegister(3, 'q'), 1), (QuantumRegister(3, 'q'), 2)))]) + # + # def test_flexlayer_swap_maps_barriers(self): + # """Verify barrier nodes are updated to re-mapped qregs. + # + # Create a circuit with a barrier on q0 and q2, following a swap between q0 and q2. + # Since that swap is not in the coupling, one of the two will be required to move. + # Verify that the mapped barrier corresponds to one of the two possible layouts following + # the swap. + # + # """ + # + # qr = QuantumRegister(3) + # cr = ClassicalRegister(2) + # circuit = QuantumCircuit(qr, cr) + # + # circuit.cx(qr[0], qr[2]) + # circuit.barrier(qr[0], qr[2]) + # + # dag_circuit = circuit_to_dag(circuit) + # + # coupling_map = CouplingMap([[0, 1], [1, 2]]) + # + # pass_manager = PassManager() + # pass_manager.append([FlexlayerSwap(coupling_map)]) + # mapped_dag = transpile_dag(dag_circuit, pass_manager=pass_manager) + # + # mapped_barrier_qargs = [set(mapped_dag.multi_graph.nodes(data=True)[op]['qargs']) + # for op in mapped_dag.get_named_nodes('barrier')][0] + # + # self.assertIn(mapped_barrier_qargs, + # [set(((QuantumRegister(3, 'q'), 0), (QuantumRegister(3, 'q'), 1))), + # set(((QuantumRegister(3, 'q'), 1), (QuantumRegister(3, 'q'), 2)))]) + + +if __name__ == '__main__': + unittest.main() From 2d0483733611c67b504222b37a07a1367ee7f54d Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Tue, 12 Feb 2019 18:53:28 +0900 Subject: [PATCH 04/40] fix #1791 --- qiskit/mapper/_layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/mapper/_layout.py b/qiskit/mapper/_layout.py index 325cc2ade256..984f3cefb751 100644 --- a/qiskit/mapper/_layout.py +++ b/qiskit/mapper/_layout.py @@ -189,7 +189,7 @@ def get_registers(self): Returns: List: A list of Register in the layout """ - return list(self.get_virtual_bits().keys()) + return set([reg for reg, idx in self.get_virtual_bits().keys()]) def idle_physical_bits(self): """ From b702400fb32816405b2d74635c3cf84e7569c853 Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Wed, 13 Feb 2019 11:37:00 +0900 Subject: [PATCH 05/40] add comments and lint --- .../mapping/algorithm/dependency_graph.py | 19 +++++++-- .../mapping/algorithm/flexlayer_heuristics.py | 40 +++++++++---------- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py index bb11b232c4a4..943ba540b63e 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py +++ b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py @@ -6,7 +6,10 @@ # the LICENSE.txt file in the root directory of this source tree. """ -Dependency graph +A dependency graph represents precedence relations of the gates in a quantum circuit considering +commutation rules. Each node represents gates in the circuit. Each directed edge represents +dependency of two gates. For example, gate g1 must be applied before gate g2 if and only if +there exists a path from g1 to g2. """ import copy from collections import defaultdict @@ -20,13 +23,23 @@ class DependencyGraph: """ - A Dependency graph expresses precedence relations of gates in a quantum circuit considering commutation rules. - + Create a dependency graph of a quantum circuit with a chosen commutation rule. """ def __init__(self, quantum_circuit: QuantumCircuit, graph_type: str = "basic"): + """ + Construct the dependency graph of `quantum_circuit` considering commutations/dependencies + specified by `graph_type`. + + Args: + quantum_circuit: A quantum circuit whose dependency graph to be constructed. + graph_type: Which type of dependency is considered. + - "xz_commute": consider four commutation rules proposed in [Itoko et. al. 2019]. + - "basic": consider only the commutation between gates without sharing qubits. + - "layer": fix layers and add dependencies between layers to `basic`. + """ self.L = quantum_circuit.data diff --git a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py index 6f148e297815..c7b79d7e78cc 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py +++ b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py @@ -6,24 +6,20 @@ # the LICENSE.txt file in the root directory of this source tree. """ -A . - +A core algorithm for 'FlexlayerSwap'. """ import collections import copy import logging import pprint -from typing import Dict, Tuple - -import networkx as nx -from .ancestors import Ancestors -from .dependency_graph import DependencyGraph from qiskit import QuantumCircuit, QuantumRegister from qiskit.circuit import Gate from qiskit.dagcircuit import DAGCircuit from qiskit.extensions.standard import SwapGate from qiskit.mapper import MapperError, CouplingMap, Layout +from .ancestors import Ancestors +from .dependency_graph import DependencyGraph logger = logging.getLogger(__name__) @@ -98,7 +94,6 @@ def search(self) -> (DAGCircuit, Layout): costs = [ece.cost(e, alpha=self._decay_rate) for e in ece.cand_edges] min_cost, e = min(zip(costs, ece.cand_edges)) - # logger.debug("top3 edges = %s", pprint.pformat(sorted(zip(costs, ece.cand_edges))[:3])) if min_cost.immediate_cost < 0: # usual case @@ -121,8 +116,10 @@ def search(self) -> (DAGCircuit, Layout): dg=self._dg, max_depth=self._lookahead_depth) - costs = [ece.cost(e, priority=['immediate_cost', - 'lookahead_cost'], alpha=self._decay_rate) for e in ece.cand_edges] + costs = [ece.cost(e, + priority=['immediate_cost', 'lookahead_cost'], + alpha=self._decay_rate) + for e in ece.cand_edges] min_cost, e = min(zip(costs, ece.cand_edges)) @@ -238,11 +235,11 @@ def _qubit_count_validity_check(self): physical_qubits = list(self._coupling.physical_qubits) if len(virtual_qubits) > len(physical_qubits): - raise MapperError("Not enough qubits in CouplingMap") + raise MapperError("Not enough qubits in _coupling") - for q in self._initial_layout.get_physical_bits().keys(): + for q in self._initial_layout.get_physical_bits(): if q not in physical_qubits: - raise MapperError("%s is not in CouplingMap but in initial_layout" % pprint.pformat(q)) + raise MapperError("%s isn't in _coupling but in initial_layout" % pprint.pformat(q)) def _add_ancilla_qubits(self): virtual_qubits = sorted(self._dg.qubits) @@ -261,8 +258,8 @@ def _add_ancilla_qubits(self): virtual_qubits.append((anc_qreg, i)) self._initial_layout[(anc_qreg, i)] = aq - assert len(physical_qubits) == len(virtual_qubits), "physical_qubits=%d, virtual_qubits=%d" % ( - len(physical_qubits), len(virtual_qubits)) + assert len(physical_qubits) == len(virtual_qubits), \ + "physical_qubits=%d, virtual_qubits=%d" % (len(physical_qubits), len(virtual_qubits)) def _create_empty_dagcircuit(self, physical_qreg): new_dag = DAGCircuit() @@ -395,7 +392,7 @@ def _qargs(gate): def remove_head_swaps(qc: QuantumCircuit, - initial_layout: dict) -> (QuantumCircuit, Dict[Tuple[str, int], Tuple[str, int]]): + initial_layout: Layout) -> (QuantumCircuit, Layout): """remove unnecessary swap gates from qc by changing initial_layout assume all of the gates in qc are expanded to one-qubit gate, cx, swap """ @@ -409,8 +406,8 @@ def remove_head_swaps(qc: QuantumCircuit, if gate.name == "swap": if (not cx_seen[qargs[0]]) and (not cx_seen[qargs[1]]): # swap before the first cx -> update rev_layout and do not output swap to qasm - rev_new_layout[qargs[0]], rev_new_layout[qargs[1]] = rev_new_layout[qargs[1]], rev_new_layout[ - qargs[0]] + rev_new_layout[qargs[0]], rev_new_layout[qargs[1]] = rev_new_layout[qargs[1]], \ + rev_new_layout[qargs[0]] to_be_removed.append(i) else: # must change flag! because left swap = cx @@ -432,10 +429,11 @@ def remove_head_swaps(qc: QuantumCircuit, for i, gate in enumerate(resqc.data[:1 + to_be_removed[-1]]): qargs = _qargs(gate) if gate.name == "swap": - rev_org_layout[qargs[0]], rev_org_layout[qargs[1]] = rev_org_layout[qargs[1]], rev_org_layout[qargs[0]] + rev_org_layout[qargs[0]], rev_org_layout[qargs[1]] = rev_org_layout[qargs[1]], \ + rev_org_layout[qargs[0]] if i not in to_be_removed: - rev_new_layout[qargs[0]], rev_new_layout[qargs[1]] = rev_new_layout[qargs[1]], rev_new_layout[ - qargs[0]] + rev_new_layout[qargs[0]], rev_new_layout[qargs[1]] = rev_new_layout[qargs[1]], \ + rev_new_layout[qargs[0]] new_layout = {v: k for k, v in rev_new_layout.items()} else: _change_qargs(gate, rev_org_layout, new_layout, qregs) From 7c030552cb5146524bdb3dcc2c3e50a3dca670ef Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Wed, 13 Feb 2019 13:53:20 +0900 Subject: [PATCH 06/40] lint --- qiskit/mapper/_layout.py | 2 +- qiskit/transpiler/passes/__init__.py | 1 + .../passes/mapping/algorithm/ancestors.py | 15 +- .../mapping/algorithm/dependency_graph.py | 251 ++++++++++-------- .../mapping/algorithm/flexlayer_heuristics.py | 10 +- .../passes/mapping/flexlayer_swap.py | 20 +- .../transpiler/test_dependency_graph.py | 13 +- .../transpiler/test_flexlayer_heuristics.py | 21 +- test/python/transpiler/test_flexlayer_swap.py | 10 +- 9 files changed, 192 insertions(+), 151 deletions(-) diff --git a/qiskit/mapper/_layout.py b/qiskit/mapper/_layout.py index 984f3cefb751..e0f39814349f 100644 --- a/qiskit/mapper/_layout.py +++ b/qiskit/mapper/_layout.py @@ -189,7 +189,7 @@ def get_registers(self): Returns: List: A list of Register in the layout """ - return set([reg for reg, idx in self.get_virtual_bits().keys()]) + return {reg for reg, _ in self.get_virtual_bits()} def idle_physical_bits(self): """ diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 43b2ee52343a..cfd469b4f155 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -23,4 +23,5 @@ from .mapping.basic_swap import BasicSwap from .mapping.lookahead_swap import LookaheadSwap from .mapping.stochastic_swap import StochasticSwap +from .mapping.flexlayer_swap import FlexlayerSwap from .mapping.enlarge_with_ancilla import EnlargeWithAncilla diff --git a/qiskit/transpiler/passes/mapping/algorithm/ancestors.py b/qiskit/transpiler/passes/mapping/algorithm/ancestors.py index 58ed39cd1f70..a1ffc6a938a0 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/ancestors.py +++ b/qiskit/transpiler/passes/mapping/algorithm/ancestors.py @@ -1,4 +1,13 @@ -# coding: utf-8 +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +""" +Utility for speeding up a function to find ancestors in DAG. +""" from functools import lru_cache @@ -8,6 +17,10 @@ class Ancestors: + """ + Utility class for speeding up a function to find ancestors in DAG. + """ + def __init__(self, G: nx.DiGraph, max_depth=5): self._G = G.reverse() self._max_depth = max_depth diff --git a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py index 943ba540b63e..dc6f6ee8b029 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py +++ b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright 2018, IBM. +# Copyright 2019, IBM. # # This source code is licensed under the Apache License, Version 2.0 found in # the LICENSE.txt file in the root directory of this source tree. @@ -18,7 +18,8 @@ import networkx as nx from qiskit import QuantumCircuit, QuantumRegister -from qiskit.mapper import MapperError +from qiskit.mapper import Layout +from qiskit.transpiler import TranspilerError class DependencyGraph: @@ -39,17 +40,21 @@ def __init__(self, - "xz_commute": consider four commutation rules proposed in [Itoko et. al. 2019]. - "basic": consider only the commutation between gates without sharing qubits. - "layer": fix layers and add dependencies between layers to `basic`. + + Raises: + TranspilerError: if the coupling map or the layout are not + compatible with the DAG """ - self.L = quantum_circuit.data + self._gates = quantum_circuit.data - self.G = nx.DiGraph() # dependency graph (including 1-qubit gates) + self._graph = nx.DiGraph() # dependency graph (including 1-qubit gates) - for i, _ in enumerate(self.L): - self.G.add_node(i) + for i, _ in enumerate(self._gates): + self._graph.add_node(i) self.qubits = set() - for i, _ in enumerate(self.L): + for i, _ in enumerate(self._gates): self.qubits.update(self.qargs(i)) if graph_type == "xz_commute": @@ -59,124 +64,124 @@ def __init__(self, elif graph_type == "layer": self._create_layer_graph() else: - raise MapperError("Unknown graph_type:" + graph_type) + raise TranspilerError("Unknown graph_type:" + graph_type) # remove redundant edges in dependency graph - self.remove_redundancy(self.G) + self.remove_redundancy(self._graph) def _create_xz_graph(self): - Z_GATES = ["u1", "rz", "s", "t", "z", "sdg", "tdg"] - X_GATES = ["rx", "x"] - B_GATES = ["u3", "h", "u2", "ry", "barrier", "measure", "swap", "y"] + z_gates = ["u1", "rz", "s", "t", "z", "sdg", "tdg"] + x_gates = ["rx", "x"] + b_gates = ["u3", "h", "u2", "ry", "barrier", "measure", "swap", "y"] # construct commutation-rules-aware dependency graph - for n in self.G.nodes(): - if self.L[n].name in X_GATES: - [b] = self.L[n].qargs - [pgow] = self._prior_gates_on_wire(self.L, n) - Z_FLAG = False + for n in self._graph.nodes(): + if self._gates[n].name in x_gates: + [b] = self._gates[n].qargs + [pgow] = self._prior_gates_on_wire(self._gates, n) + z_flag = False for m in pgow: - g = self.L[m] - if g.name in B_GATES: - self.G.add_edge(m, n) + gate = self._gates[m] + if gate.name in b_gates: + self._graph.add_edge(m, n) break - elif g.name in X_GATES or (g.name == "cx" and g.qargs[1] == b): - if Z_FLAG: + elif gate.name in x_gates or (gate.name == "cx" and gate.qargs[1] == b): + if z_flag: break else: continue - elif g.name in Z_GATES or (g.name == "cx" and g.qargs[0] == b): - self.G.add_edge(m, n) - Z_FLAG = True + elif gate.name in z_gates or (gate.name == "cx" and gate.qargs[0] == b): + self._graph.add_edge(m, n) + z_flag = True else: - raise MapperError("Unknown gate: " + g.name) - elif self.L[n].name in Z_GATES: - [b] = self.L[n].qargs - [pgow] = self._prior_gates_on_wire(self.L, n) - X_FLAG = False + raise TranspilerError("Unknown gate: " + gate.name) + elif self._gates[n].name in z_gates: + [b] = self._gates[n].qargs + [pgow] = self._prior_gates_on_wire(self._gates, n) + x_flag = False for m in pgow: - g = self.L[m] - if g.name in B_GATES: - self.G.add_edge(m, n) + gate = self._gates[m] + if gate.name in b_gates: + self._graph.add_edge(m, n) break - elif g.name in X_GATES or (g.name == "cx" and g.qargs[1] == b): - self.G.add_edge(m, n) - X_FLAG = True - elif g.name in Z_GATES or (g.name == "cx" and g.qargs[0] == b): - if X_FLAG: + elif gate.name in x_gates or (gate.name == "cx" and gate.qargs[1] == b): + self._graph.add_edge(m, n) + x_flag = True + elif gate.name in z_gates or (gate.name == "cx" and gate.qargs[0] == b): + if x_flag: break else: continue else: - raise MapperError("Unknown gate: " + g.name) - elif self.L[n].name == "cx": - bc, bt = self.L[n].qargs - [cpgow, tpgow] = self._prior_gates_on_wire(self.L, n) + raise TranspilerError("Unknown gate: " + gate.name) + elif self._gates[n].name == "cx": + cbit, tbit = self._gates[n].qargs + [cpgow, tpgow] = self._prior_gates_on_wire(self._gates, n) - Z_FLAG = False + z_flag = False for m in tpgow: # target bit: bt - g = self.L[m] - if g.name in B_GATES: - self.G.add_edge(m, n) + gate = self._gates[m] + if gate.name in b_gates: + self._graph.add_edge(m, n) break - elif g.name in X_GATES or (g.name == "cx" and g.qargs[1] == bt): - if Z_FLAG: + elif gate.name in x_gates or (gate.name == "cx" and gate.qargs[1] == tbit): + if z_flag: break else: continue - elif g.name in Z_GATES or (g.name == "cx" and g.qargs[0] == bt): - self.G.add_edge(m, n) - Z_FLAG = True + elif gate.name in z_gates or (gate.name == "cx" and gate.qargs[0] == tbit): + self._graph.add_edge(m, n) + z_flag = True else: - raise MapperError("Unknown gate: " + g.name) + raise TranspilerError("Unknown gate: " + gate.name) - X_FLAG = False + x_flag = False for m in cpgow: # control bit: bc - g = self.L[m] - if g.name in B_GATES: - self.G.add_edge(m, n) + gate = self._gates[m] + if gate.name in b_gates: + self._graph.add_edge(m, n) break - elif g.name in X_GATES or (g.name == "cx" and g.qargs[1] == bc): - self.G.add_edge(m, n) - X_FLAG = True - elif g.name in Z_GATES or (g.name == "cx" and g.qargs[0] == bc): - if X_FLAG: + elif gate.name in x_gates or (gate.name == "cx" and gate.qargs[1] == cbit): + self._graph.add_edge(m, n) + x_flag = True + elif gate.name in z_gates or (gate.name == "cx" and gate.qargs[0] == cbit): + if x_flag: break else: continue else: - raise MapperError("Unknown gate: " + g.name) - elif self.L[n].name in B_GATES: - for i, pgow in enumerate(self._prior_gates_on_wire(self.L, n)): + raise TranspilerError("Unknown gate: " + gate.name) + elif self._gates[n].name in b_gates: + for i, pgow in enumerate(self._prior_gates_on_wire(self._gates, n)): b = self._all_args(n)[i] - X_FLAG, Z_FLAG = False, False + x_flag, z_flag = False, False for m in pgow: - g = self.L[m] - if g.name in B_GATES: - self.G.add_edge(m, n) + gate = self._gates[m] + if gate.name in b_gates: + self._graph.add_edge(m, n) break - elif g.name in X_GATES or (g.name == "cx" and g.qargs[1] == b): - if Z_FLAG: + elif gate.name in x_gates or (gate.name == "cx" and gate.qargs[1] == b): + if z_flag: break else: - self.G.add_edge(m, n) - X_FLAG = True - elif g.name in Z_GATES or (g.name == "cx" and g.qargs[0] == b): - if X_FLAG: + self._graph.add_edge(m, n) + x_flag = True + elif gate.name in z_gates or (gate.name == "cx" and gate.qargs[0] == b): + if x_flag: break else: - self.G.add_edge(m, n) - Z_FLAG = True + self._graph.add_edge(m, n) + z_flag = True else: - raise MapperError("Unknown gate: " + g.name) + raise TranspilerError("Unknown gate: " + gate.name) else: - raise MapperError("Unknown gate: " + self.L[n].name) + raise TranspilerError("Unknown gate: " + self._gates[n].name) def _create_basic_graph(self): - for n in self.G.nodes(): - for pgow in self._prior_gates_on_wire(self.L, n): + for n in self._graph.nodes(): + for pgow in self._prior_gates_on_wire(self._gates, n): m = next(pgow, -1) if m != -1: - self.G.add_edge(m, n) + self._graph.add_edge(m, n) def _create_layer_graph(self): self._create_basic_graph() @@ -184,7 +189,7 @@ def _create_layer_graph(self): # construct CNOT layers layers = [] wire = defaultdict(int) - for n in self.G.nodes(): + for n in self._graph.nodes(): qargs = self.qargs(n) if self.gate_name(n) == "cx": # consider only CNOTs i = 1 + max(wire[qargs[0]], wire[qargs[1]]) @@ -200,87 +205,107 @@ def _create_layer_graph(self): j = i + 1 for icx in layers[i]: for jcx in layers[j]: - self.G.add_edge(icx, jcx) + self._graph.add_edge(icx, jcx) - def n_nodes(self): - return self.G.__len__() + def n_nodes(self) -> int: + """Number of the nodes + Returns: Number of the nodes in this graph + """ + return self._graph.__len__() def qargs(self, i: int) -> List[Tuple[QuantumRegister, int]]: """Qubit arguments of the gate Args: - i: Index of the gate in the quantum gate list + i: Index of the gate in the `self._gates` Returns: List of qubit arguments """ - return self.L[i].qargs + return self._gates[i].qargs def _all_args(self, i: int) -> List[Tuple[QuantumRegister, int]]: """Qubit and classical-bit arguments of the gate Args: - i: Index of the gate in the quantum gate list + i: Index of the gate in the `self._gates` Returns: List of all arguments """ - return self.L[i].qargs + self.L[i].cargs + return self._gates[i].qargs + self._gates[i].cargs def gate_name(self, i: int) -> str: """Name of the gate Args: - i: Index of the gate in the quantum gate list + i: Index of the gate in the `self._gates` Returns: Name of the gate """ - return self.L[i].name + return self._gates[i].name def head_gates(self) -> Set[int]: """Gates which can be applicable prior to the other gates Returns: Set of indices of the gates """ - return frozenset([n for n in self.G.nodes() if len(self.G.in_edges(n)) == 0]) + return frozenset([n for n in self._graph.nodes() if len(self._graph.in_edges(n)) == 0]) def gr_successors(self, i: int) -> List[int]: """Successor gates in Gr (transitive reduction) of the gate Args: - i: Index of the gate in the quantum gate list + i: Index of the gate in the `self._gates` Returns: Set of indices of the successor gates """ - return self.G.successors(i) + return self._graph.successors(i) def descendants(self, i: int) -> Set[int]: - return nx.descendants(self.G, i) + """Descendant gates of gate `i` + Args: + i: Index of the gate in the `self._gates` + Returns: Set of indices of the descendant gates + """ + return nx.descendants(self._graph, i) def ancestors(self, i: int) -> Set[int]: - return nx.ancestors(self.G, i) + """Ancestor gates of gate `i` + Args: + i: Index of the gate in the `self._gates` + Returns: Set of indices of the ancestor gates + """ + return nx.ancestors(self._graph, i) - def gate(self, g, layout, physical_qreg): - gate = copy.deepcopy(self.L[g]) + def gate(self, gidx: int, layout: Layout, physical_qreg: QuantumRegister): + """Convert acting qubits of gate `g` from virtual qubits to physical ones. + Args: + gidx: Index of the gate in the `self._gates` + layout: Layout used in conversion + physical_qreg: Register of physical qubit + Returns: Converted gate with physical qubit + """ + gate = copy.deepcopy(self._gates[gidx]) for i, logical_qubit in enumerate(gate.qargs): if logical_qubit in layout.get_virtual_bits().keys(): gate.qargs[i] = (physical_qreg, layout[logical_qubit]) else: - raise MapperError("logical_qubit must be in layout") + raise TranspilerError("logical_qubit must be in layout") return gate @staticmethod - def remove_redundancy(G): - """remove redundant edges in DAG (= change G to its transitive reduction) + def remove_redundancy(graph): + """remove redundant edges in DAG (= change `graph` to its transitive reduction) """ - edges = list(G.edges()) - for e in edges: - G.remove_edge(e[0], e[1]) - if not nx.has_path(G, e[0], e[1]): - G.add_edge(e[0], e[1]) + edges = list(graph.edges()) + for edge in edges: + graph.remove_edge(edge[0], edge[1]) + if not nx.has_path(graph, edge[0], edge[1]): + graph.add_edge(edge[0], edge[1]) @staticmethod - def _prior_gates_on_wire(L, toidx): + def _prior_gates_on_wire(gate_list, toidx): res = [] - for qarg in L[toidx].qargs: + for qarg in gate_list[toidx].qargs: gates = [] - for i, g in enumerate(L[:toidx]): - if qarg in g.qargs: + for i, gate in enumerate(gate_list[:toidx]): + if qarg in gate.qargs: gates.append(i) res.append(reversed(gates)) - for carg in L[toidx].cargs: + for carg in gate_list[toidx].cargs: gates = [] - for i, g in enumerate(L[:toidx]): - if carg in g.cargs: + for i, gate in enumerate(gate_list[:toidx]): + if carg in gate.cargs: gates.append(i) res.append(reversed(gates)) return res diff --git a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py index c7b79d7e78cc..8ea5cd4f1fed 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py +++ b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py @@ -51,7 +51,7 @@ def __init__(self, self._lookahead_depth = lookahead_depth self._decay_rate = decay_rate - self._ancestors = Ancestors(self._dg.G) # for speed up + self._ancestors = Ancestors(self._dg._graph) # for speed up def search(self) -> (DAGCircuit, Layout): """ @@ -157,7 +157,7 @@ def _next_blocking_gates_from(self, blocking_gates, layout): leadings = self._update_leading_gates(leadings, new_dones) new_dones = self._find_done_gates(leadings, layout) - assert (len(dones) == len(set(dones))) + assert len(dones) == len(set(dones)) # dones must be list (order is essential!) return frozenset(leadings), dones @@ -267,9 +267,9 @@ def _create_empty_dagcircuit(self, physical_qreg): for creg in self._qc.cregs: new_dag.add_creg(creg) # new_dag.add_basis_element('swap', 2, 0, 0) - for name, data in self._qc.definitions.items(): - # new_dag.add_basis_element(name, data["n_bits"], 0, data["n_args"]) - new_dag.add_gate_data(name, data) + # for name, data in self._qc.definitions.items(): + # new_dag.add_basis_element(name, data["n_bits"], 0, data["n_args"]) + # new_dag.add_gate_data(name, data) return new_dag diff --git a/qiskit/transpiler/passes/mapping/flexlayer_swap.py b/qiskit/transpiler/passes/mapping/flexlayer_swap.py index 415527246680..878e4b5ace85 100644 --- a/qiskit/transpiler/passes/mapping/flexlayer_swap.py +++ b/qiskit/transpiler/passes/mapping/flexlayer_swap.py @@ -82,14 +82,14 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: self._initial_layout = Layout.generate_trivial_layout(*dag.qregs.values()) qc = dag_to_circuit(dag) - dg = DependencyGraph(qc, graph_type="xz_commute") - lh = FlexlayerHeuristics(qc=qc, - dependency_graph=dg, - coupling=self._coupling_map, - initial_layout=self._initial_layout, - lookahead_depth=self._lookahead_depth, - decay_rate=self._decay_rate) - res_dag, layout = lh.search() + dependency_graph = DependencyGraph(qc, graph_type="xz_commute") + algo = FlexlayerHeuristics(qc=qc, + dependency_graph=dependency_graph, + coupling=self._coupling_map, + initial_layout=self._initial_layout, + lookahead_depth=self._lookahead_depth, + decay_rate=self._decay_rate) + res_dag, layout = algo.search() res_dag = physical_to_virtual(res_dag, layout) return res_dag @@ -98,8 +98,10 @@ def physical_to_virtual(dag: DAGCircuit, initial_layout: Layout) -> DAGCircuit: """ Convert a physical circuit `dag` into the virtual circuit under a given `initial_layout`. Args: - dag: a physical circuit, assuming 'q' is the name of its physical qubit. + dag: a physical circuit, assuming 'q' is the register name of its physical qubits. initial_layout: given initial layout. + Returns: + A converted circuit with virtual qubits """ layout = {} qubits = dag.qubits() diff --git a/test/python/transpiler/test_dependency_graph.py b/test/python/transpiler/test_dependency_graph.py index 3e0f0391035e..79a89c4d8b30 100644 --- a/test/python/transpiler/test_dependency_graph.py +++ b/test/python/transpiler/test_dependency_graph.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- import unittest -# from qiskit.transpiler.passes.algorithm import DependencyGraph -from new_swappers.algorithm import DependencyGraph from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit from qiskit.test import QiskitTestCase +from qiskit.transpiler.passes.mapping.algorithm import DependencyGraph class TestDependencyGraph(QiskitTestCase): @@ -58,7 +57,7 @@ def test_barrier(self): circuit.cx(b[2], b[3]) dg = DependencyGraph(circuit) expected = sorted([(0, 1), (1, 2), (1, 3)]) - self.assertEqual(list(dg.G.edges()), expected) + self.assertEqual(list(dg._graph.edges()), expected) def test_measure(self): b = QuantumRegister(3, 'b') @@ -71,7 +70,7 @@ def test_measure(self): circuit.measure(b[2], c[1]) # overwrite -> introduce dependency (2, 4) dg = DependencyGraph(circuit, graph_type="xz_commute") expected_edges = sorted([(0, 1), (1, 2), (1, 3), (2, 4), (3, 4)]) - self.assertEqual(list(dg.G.edges()), expected_edges) + self.assertEqual(list(dg._graph.edges()), expected_edges) def test_h_gate_in_the_middle(self): q = QuantumRegister(4, 'q') @@ -84,7 +83,7 @@ def test_h_gate_in_the_middle(self): circuit.cx(q[1], q[0]) dg = DependencyGraph(circuit, graph_type="xz_commute") expected = sorted([(0, 2), (1, 2), (2, 3), (3, 4)]) - self.assertEqual(list(dg.G.edges()), expected) + self.assertEqual(list(dg._graph.edges()), expected) def test_rz_gate_in_the_middle(self): q = QuantumRegister(4, 'q') @@ -97,7 +96,7 @@ def test_rz_gate_in_the_middle(self): circuit.cx(q[1], q[0]) dg = DependencyGraph(circuit, graph_type="xz_commute") expected = sorted([(0, 2), (1, 2), (0, 3), (0, 4)]) - self.assertEqual(list(dg.G.edges()), expected) + self.assertEqual(list(dg._graph.edges()), expected) def test_rz_gate_in_the_middle_blg(self): q = QuantumRegister(4, 'q') @@ -110,7 +109,7 @@ def test_rz_gate_in_the_middle_blg(self): circuit.cx(q[1], q[0]) dg = DependencyGraph(circuit, graph_type="basic") expected = sorted([(0, 2), (1, 2), (2, 3), (3, 4)]) - self.assertEqual(list(dg.G.edges()), expected) + self.assertEqual(list(dg._graph.edges()), expected) if __name__ == "__main__": diff --git a/test/python/transpiler/test_flexlayer_heuristics.py b/test/python/transpiler/test_flexlayer_heuristics.py index aaab8a40b579..b5ee3f290fe3 100644 --- a/test/python/transpiler/test_flexlayer_heuristics.py +++ b/test/python/transpiler/test_flexlayer_heuristics.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- import unittest + from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister -from qiskit.mapper import CouplingMap, Layout from qiskit.converters import circuit_to_dag, dag_to_circuit - -# from qiskit.transpiler.passes.algorithm import DependencyGraph -from new_swappers.algorithm import FlexlayerHeuristics, remove_head_swaps, DependencyGraph +from qiskit.mapper import CouplingMap, Layout +from qiskit.transpiler.passes.mapping.algorithm import DependencyGraph +from qiskit.transpiler.passes.mapping.algorithm import FlexlayerHeuristics, remove_head_swaps class TestLookaheadHeuristics(unittest.TestCase): @@ -101,7 +101,8 @@ def test_remove_head_swaps(self): header = 'OPENQASM 2.0;include "qelib1.inc";qreg q[4];creg c[4];' expected_qasm = header + "cx q[0],q[1];u1(1) q[3];cx q[1],q[2];" expected_measure = ["measure q[%d] -> c[%d];" % (i, i) for i in range(4)] - expected_layout = {('q', 0): ('q', 0), ('q', 1): ('q', 1), ('q', 3): ('q', 2), ('q', 2): ('q', 3)} + expected_layout = {('q', 0): ('q', 0), ('q', 1): ('q', 1), ('q', 3): ('q', 2), + ('q', 2): ('q', 3)} self.assertEqual(actual_qasm, expected_qasm) self.assertEqual(actual_measure, expected_measure) self.assertEqual(layout, expected_layout) @@ -121,7 +122,8 @@ def test_remove_head_swaps(self): # qc = load_qasm_string(qasm_text, basis_gates="u1,u2,u3,cx,id") # dg = DependencyGraph(qc, graph_type="basic") # coupling = Coupling({1: [0], 2: [0, 1, 4], 3: [2, 4]}) - # initial_layout = {('q', 0): ('q', 1), ('q', 1): ('q', 0), ('q', 2): ('q', 2), ('q', 3): ('q', 4)} + # initial_layout = {('q', 0): ('q', 1), ('q', 1): ('q', 0), ('q', 2): ('q', 2), + # ('q', 3): ('q', 4)} # algo = FlexlayerHeuristics(qc, dg, coupling, initial_layout) # dag, layout = algo.search() @@ -145,9 +147,10 @@ def test_remove_head_swaps2(self): resqc, layout = remove_head_swaps(circuit, initial_layout) actual_qasm = ''.join([s for s in resqc.qasm().split('\n')]) header = 'OPENQASM 2.0;include "qelib1.inc";qreg q[6];creg c[6];' - expected_qasm = header + "u1(1) q[5];h q[3];cx q[3],q[4];swap q[3],q[4];u1(1) q[0];h q[2];swap q[2],q[3];cx q[3],q[4];" - expected_layout = {('b', 0): ('q', 2), ('b', 1): ('q', 0), ('b', 2): ('q', 1), ('b', 3): ('q', 3), - ('b', 4): ('q', 5), ('b', 5): ('q', 4)} + expected_qasm = header + "u1(1) q[5];h q[3];cx q[3],q[4];swap q[3],q[4];u1(1) q[0];" \ + "h q[2];swap q[2],q[3];cx q[3],q[4];" + expected_layout = {('b', 0): ('q', 2), ('b', 1): ('q', 0), ('b', 2): ('q', 1), + ('b', 3): ('q', 3), ('b', 4): ('q', 5), ('b', 5): ('q', 4)} self.assertEqual(actual_qasm, expected_qasm) self.assertEqual(layout, expected_layout) diff --git a/test/python/transpiler/test_flexlayer_swap.py b/test/python/transpiler/test_flexlayer_swap.py index 7be7ab327b20..da3c5a411b47 100644 --- a/test/python/transpiler/test_flexlayer_swap.py +++ b/test/python/transpiler/test_flexlayer_swap.py @@ -8,12 +8,11 @@ """Test the FlexlayerSwap pass""" import unittest -# from qiskit.transpiler.passes import FlexlayerSwap -from new_swappers import FlexlayerSwap -from qiskit.transpiler import PassManager, transpile_dag + +from qiskit.transpiler.passes import FlexlayerSwap +from qiskit import QuantumRegister, QuantumCircuit +from qiskit.converters import circuit_to_dag from qiskit.mapper import CouplingMap, Layout -from qiskit.converters import circuit_to_dag, dag_to_circuit -from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit from qiskit.test import QiskitTestCase @@ -285,7 +284,6 @@ def test_initial_layout_in_different_qregs(self): self.assertEqual(circuit_to_dag(expected), after) - # def test_flexlayer_swap_doesnt_modify_mapped_circuit(self): # """Test that lookahead mapper is idempotent. # From 4450eb08b809649072c6262eb5ff205dbb7f7e13 Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Wed, 13 Feb 2019 14:21:30 +0900 Subject: [PATCH 07/40] lint --- qiskit/mapper/_coupling.py | 1 - .../transpiler/passes/mapping/algorithm/flexlayer_heuristics.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/qiskit/mapper/_coupling.py b/qiskit/mapper/_coupling.py index c8edcc84632a..183bf7101ed3 100644 --- a/qiskit/mapper/_coupling.py +++ b/qiskit/mapper/_coupling.py @@ -189,7 +189,6 @@ def undirected_neighbors(self, physical_qubit): neighbors = self.graph.to_undirected().neighbors(physical_qubit) return [r for r in neighbors] - def __str__(self): """Return a string representation of the coupling graph.""" string = "" diff --git a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py index 8ea5cd4f1fed..6453826813f6 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py +++ b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py @@ -356,7 +356,7 @@ def _construct_care_ends_list(self, gates, layout, dg, max_depth): nodes = set(gates) dic = collections.defaultdict(int) # approximate max path length from gates to the key gate for d in range(max_depth): - nexts = set([s for n in nodes for s in dg.gr_successors(n)]) + nexts = {s for n in nodes for s in dg.gr_successors(n)} for n in nexts: if len(dg.qargs(n)) == 2: dic[n] = d + 1 From f432021a365535cf1411cd5f6a536139aaf527b7 Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Wed, 13 Feb 2019 14:22:13 +0900 Subject: [PATCH 08/40] fix #1791 --- qiskit/transpiler/passes/mapping/basic_swap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/basic_swap.py b/qiskit/transpiler/passes/mapping/basic_swap.py index 204cd88026ce..c122629c7072 100644 --- a/qiskit/transpiler/passes/mapping/basic_swap.py +++ b/qiskit/transpiler/passes/mapping/basic_swap.py @@ -91,8 +91,8 @@ def run(self, dag): # create qregs for qreg in current_layout.get_registers(): - if qreg[0] not in swap_layer.qregs.values(): - swap_layer.add_qreg(qreg[0]) + if qreg not in swap_layer.qregs.values(): + swap_layer.add_qreg(qreg) # create the swap operation swap_layer.apply_operation_back(self.swap_gate(qubit_1, qubit_2), From 6560757e00e9f524b6333276324c2ace83e654fb Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Wed, 13 Feb 2019 16:58:11 +0900 Subject: [PATCH 09/40] add comments and refactor variable names --- .../passes/mapping/algorithm/ancestors.py | 29 +- .../mapping/algorithm/dependency_graph.py | 45 +-- .../mapping/algorithm/flexlayer_heuristics.py | 257 ++++++++++-------- .../passes/mapping/flexlayer_swap.py | 21 +- 4 files changed, 202 insertions(+), 150 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/algorithm/ancestors.py b/qiskit/transpiler/passes/mapping/algorithm/ancestors.py index a1ffc6a938a0..1c7bc8a44e58 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/ancestors.py +++ b/qiskit/transpiler/passes/mapping/algorithm/ancestors.py @@ -22,11 +22,18 @@ class Ancestors: """ def __init__(self, G: nx.DiGraph, max_depth=5): - self._G = G.reverse() + self._graph = G.reverse() self._max_depth = max_depth self._depth = 0 - def ancestors(self, n): + def ancestors(self, n: int) -> set: + """ + Ancestor nodes of the node `n` + Args: + n: Index of the node in the `self._graph` + Returns: + Set of indices of the ancestor nodes. + """ self._depth = 0 return self._ancestors_rec(n) @@ -36,9 +43,9 @@ def _ancestors_rec(self, n) -> set: return self._ancestors_loop(n) self._depth += 1 ret = set() - for n2 in self._G.successors(n): - ret.add(n2) - ret.update(self._ancestors_rec(n2)) + for n_succ in self._graph.successors(n): + ret.add(n_succ) + ret.update(self._ancestors_rec(n_succ)) self._depth -= 1 return ret @@ -46,12 +53,12 @@ def _ancestors_rec(self, n) -> set: def _ancestors_loop(self, n) -> set: ret = set() done = set() - cand = [n] - while cand: - u = cand.pop() - for v in self._G.successors(u): + cands = [n] + while cands: + node = cands.pop() + for v in self._graph.successors(node): if v not in done: ret.add(v) - cand.append(v) - done.add(u) + cands.append(v) + done.add(node) return ret diff --git a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py index dc6f6ee8b029..4d5c1f70736e 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py +++ b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py @@ -18,6 +18,7 @@ import networkx as nx from qiskit import QuantumCircuit, QuantumRegister +from qiskit.circuit import Instruction from qiskit.mapper import Layout from qiskit.transpiler import TranspilerError @@ -42,8 +43,7 @@ def __init__(self, - "layer": fix layers and add dependencies between layers to `basic`. Raises: - TranspilerError: if the coupling map or the layout are not - compatible with the DAG + TranspilerError: if `graph_type` is not one of the types listed above. """ self._gates = quantum_circuit.data @@ -209,7 +209,8 @@ def _create_layer_graph(self): def n_nodes(self) -> int: """Number of the nodes - Returns: Number of the nodes in this graph + Returns: + Number of the nodes in this graph. """ return self._graph.__len__() @@ -217,7 +218,8 @@ def qargs(self, i: int) -> List[Tuple[QuantumRegister, int]]: """Qubit arguments of the gate Args: i: Index of the gate in the `self._gates` - Returns: List of qubit arguments + Returns: + List of qubit arguments. """ return self._gates[i].qargs @@ -225,7 +227,8 @@ def _all_args(self, i: int) -> List[Tuple[QuantumRegister, int]]: """Qubit and classical-bit arguments of the gate Args: i: Index of the gate in the `self._gates` - Returns: List of all arguments + Returns: + List of all arguments. """ return self._gates[i].qargs + self._gates[i].cargs @@ -233,13 +236,15 @@ def gate_name(self, i: int) -> str: """Name of the gate Args: i: Index of the gate in the `self._gates` - Returns: Name of the gate + Returns: + Name of the gate. """ return self._gates[i].name def head_gates(self) -> Set[int]: """Gates which can be applicable prior to the other gates - Returns: Set of indices of the gates + Returns: + Set of indices of the gates. """ return frozenset([n for n in self._graph.nodes() if len(self._graph.in_edges(n)) == 0]) @@ -247,7 +252,8 @@ def gr_successors(self, i: int) -> List[int]: """Successor gates in Gr (transitive reduction) of the gate Args: i: Index of the gate in the `self._gates` - Returns: Set of indices of the successor gates + Returns: + Set of indices of the successor gates. """ return self._graph.successors(i) @@ -255,7 +261,8 @@ def descendants(self, i: int) -> Set[int]: """Descendant gates of gate `i` Args: i: Index of the gate in the `self._gates` - Returns: Set of indices of the descendant gates + Returns: + Set of indices of the descendant gates. """ return nx.descendants(self._graph, i) @@ -263,24 +270,28 @@ def ancestors(self, i: int) -> Set[int]: """Ancestor gates of gate `i` Args: i: Index of the gate in the `self._gates` - Returns: Set of indices of the ancestor gates + Returns: + Set of indices of the ancestor gates. """ return nx.ancestors(self._graph, i) - def gate(self, gidx: int, layout: Layout, physical_qreg: QuantumRegister): - """Convert acting qubits of gate `g` from virtual qubits to physical ones. + def gate(self, gidx: int, layout: Layout, physical_qreg: QuantumRegister) -> Instruction: + """Convert acting qubits of gate `gidx` from virtual qubits to physical ones. Args: gidx: Index of the gate in the `self._gates` layout: Layout used in conversion physical_qreg: Register of physical qubit - Returns: Converted gate with physical qubit + Returns: + Converted gate with physical qubit. + Raises: + TranspilerError: if virtual qubit of the gate `gidx` is not found in the layout. """ gate = copy.deepcopy(self._gates[gidx]) - for i, logical_qubit in enumerate(gate.qargs): - if logical_qubit in layout.get_virtual_bits().keys(): - gate.qargs[i] = (physical_qreg, layout[logical_qubit]) + for i, virtual_qubit in enumerate(gate.qargs): + if virtual_qubit in layout.get_virtual_bits().keys(): + gate.qargs[i] = (physical_qreg, layout[virtual_qubit]) else: - raise TranspilerError("logical_qubit must be in layout") + raise TranspilerError("virtual_qubit must be in layout") return gate @staticmethod diff --git a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py index 6453826813f6..743b52d8366d 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py +++ b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py @@ -6,7 +6,22 @@ # the LICENSE.txt file in the root directory of this source tree. """ -A core algorithm for 'FlexlayerSwap'. +A core algorithm of the flexible-layer swap mapping heuristics proposed in [Itoko et. al. 2019]. + +The outline of the algorithm is as follows. +0. Assume an initial_layout is given and set it to `layout`. +1. Initialize `blocking_gates` as gates without in-edge in dependency graph. +2. Update `blocking_gates` by processing applicable gates for the `layout`. +3. If it comes to no blocking gates, it terminates. Otherwise, it selects a qubit + pair (= an edge in the coupling graph) to be swapped based on its `cost`. +4. Add the swap gate at the min-cost edge (= update `layout`). +5. Go back to the step 2. +Note: In the actual flow, there is an additional path for avoiding handling cyclic swaps in step 3. + +For more details on the algorithm, see [Itoko et. al. 2019]: +T. Itoko, R. Raymond, T. Imamichi, A. Matsuo, and A. W. Cross. +Quantum circuit compilers using gate commutation rules. +In Proceedings of ASP-DAC, pp. 191--196. ACM, 2019. """ import collections import copy @@ -17,7 +32,8 @@ from qiskit.circuit import Gate from qiskit.dagcircuit import DAGCircuit from qiskit.extensions.standard import SwapGate -from qiskit.mapper import MapperError, CouplingMap, Layout +from qiskit.mapper import CouplingMap, Layout +from qiskit.transpiler import TranspilerError from .ancestors import Ancestors from .dependency_graph import DependencyGraph @@ -25,6 +41,9 @@ class FlexlayerHeuristics: + """ + A core algorithm class implementing the flexible-layer swap mapping heuristics. + """ def __init__(self, qc: QuantumCircuit, @@ -37,7 +56,7 @@ def __init__(self, self._qc = qc if initial_layout is None: - raise MapperError("FlexlayerHeuristics requires initial_layout") + raise TranspilerError("FlexlayerHeuristics requires initial_layout") self._dg = dependency_graph self._coupling = coupling @@ -45,7 +64,7 @@ def __init__(self, self._qubit_count_validity_check() if len(initial_layout.get_virtual_bits()) != len(initial_layout.get_physical_bits()): - raise MapperError("FlexlayerHeuristics assumes #virtual-qubits == #physical-qubits") + raise TranspilerError("FlexlayerHeuristics assumes #virtual-qubits == #physical-qubits") # self._add_ancilla_qubits() self._lookahead_depth = lookahead_depth @@ -58,6 +77,8 @@ def search(self) -> (DAGCircuit, Layout): Returns: Mapped physical circuit (DAGCircuit) and initial qubit layout (Layout) + Raises: + TranspilerError: if found too many loops (maybe unexpected infinite loop). """ qreg = QuantumRegister(self._coupling.size(), name='q') new_dag = self._create_empty_dagcircuit(qreg) @@ -68,8 +89,8 @@ def search(self) -> (DAGCircuit, Layout): layout = copy.deepcopy(self._initial_layout) logger.debug("initial_layout = %s", pprint.pformat(layout)) - MAX_N_ITERATION = self._coupling.size() * (self._dg.n_nodes() ** 2) - for k in range(1, MAX_N_ITERATION + 1): # escape infinite loop + max_n_iteration = self._coupling.size() * (self._dg.n_nodes() ** 2) + for k in range(1, max_n_iteration + 1): # escape infinite loop logger.debug("iteration %d", k) logger.debug("layout=%s", pprint.pformat(layout)) @@ -78,11 +99,11 @@ def search(self) -> (DAGCircuit, Layout): logger.debug("#blocking_gates = %s", pprint.pformat(blocking_gates)) logger.debug("#done_gates = %d", len(dones)) - if len(dones) > 0: - for g in dones: - new_dag.apply_operation_back(self._dg.gate(g, layout, qreg)) + if dones: + for gidx in dones: + new_dag.apply_operation_back(self._dg.gate(gidx, layout, qreg)) - if len(blocking_gates) == 0: + if not blocking_gates: break ece = EdgeCostEstimator(gates=blocking_gates, @@ -93,23 +114,23 @@ def search(self) -> (DAGCircuit, Layout): costs = [ece.cost(e, alpha=self._decay_rate) for e in ece.cand_edges] - min_cost, e = min(zip(costs, ece.cand_edges)) + min_cost, edge = min(zip(costs, ece.cand_edges)) if min_cost.immediate_cost < 0: # usual case - logger.debug("swap min-cost edge = %s", pprint.pformat(e)) - e = self._fix_swap_direction(e) - new_dag.apply_operation_back(SwapGate(qreg[e[0]], qreg[e[1]])) + logger.debug("swap min-cost edge = %s", pprint.pformat(edge)) + edge = self._fix_swap_direction(edge) + new_dag.apply_operation_back(SwapGate(qreg[edge[0]], qreg[edge[1]])) # update layout - layout.swap(e[0], e[1]) + layout.swap(edge[0], edge[1]) else: # special case necessary to avoid cyclic swaps logger.debug("cannot reduce total path length -> resolve a single path") focus_gates = self._find_focus_gates(gates=blocking_gates, layout=layout) - MAX_N_INNER_LOOPS = len(focus_gates) * self._coupling.size() - for kk in range(MAX_N_INNER_LOOPS): # escape infinite loop + max_n_inner_loops = len(focus_gates) * self._coupling.size() + for kin in range(max_n_inner_loops): # escape infinite loop ece = EdgeCostEstimator(gates=focus_gates, layout=layout, coupling=self._coupling, @@ -121,24 +142,24 @@ def search(self) -> (DAGCircuit, Layout): alpha=self._decay_rate) for e in ece.cand_edges] - min_cost, e = min(zip(costs, ece.cand_edges)) + min_cost, edge = min(zip(costs, ece.cand_edges)) - e = self._fix_swap_direction(e) - new_dag.apply_operation_back(SwapGate(qreg[e[0]], qreg[e[1]])) - layout.swap(e[0], e[1]) + edge = self._fix_swap_direction(edge) + new_dag.apply_operation_back(SwapGate(qreg[edge[0]], qreg[edge[1]])) + layout.swap(edge[0], edge[1]) - logger.debug("%d-th inner iter. add a swap (%d, %d)", kk, e[0], e[1]) + logger.debug("%d-th inner iter. add a swap (%d, %d)", kin, edge[0], edge[1]) logger.debug("resolved layout = %s", pprint.pformat(sorted(layout.items()))) dones = self._find_done_gates(blocking_gates, layout=layout) - if len(dones) > 0: + if dones: break - if kk == MAX_N_INNER_LOOPS: - raise MapperError("Unknown error (maybe infinite inner loop)") # bug + if kin == max_n_inner_loops: + raise TranspilerError("Unknown error (maybe infinite inner loop)") # bug - if k == MAX_N_ITERATION: - raise MapperError("UnknowError: #iteration reached MAX_N_ITERATION") + if k == max_n_iteration: + raise TranspilerError("Unknown error: #iteration reached max_n_iteration") reslayout = self._initial_layout # resqc = dag_to_circuit(new_dag) @@ -152,7 +173,7 @@ def _next_blocking_gates_from(self, blocking_gates, layout): leadings = set(blocking_gates) # new leading gates dones = [] new_dones = self._find_done_gates(leadings, layout) - while len(new_dones) > 0: + while new_dones: dones.extend(new_dones) leadings = self._update_leading_gates(leadings, new_dones) new_dones = self._find_done_gates(leadings, layout) @@ -176,11 +197,18 @@ def _find_done_gates(self, blocking_gates, layout): if dist == 1: dones.append(n) else: - raise MapperError("DG contains unknown >2 qubit gates") + raise TranspilerError("DG contains unknown >2 qubit gates") return dones - def ancestors(self, n) -> set: + def ancestors(self, n: int) -> set: + """ + Ancestor gates of the gate `n` + Args: + n: Index of the gate in the `self._graph` + Returns: + Set of indices of the ancestor gates. + """ return self._ancestors.ancestors(n) def _update_leading_gates(self, leading_gates, dones): @@ -192,22 +220,22 @@ def _update_leading_gates(self, leading_gates, dones): rmlist = [] for n in news: - if len(news & self.ancestors(n)) > 0: + if news & self.ancestors(n): rmlist.append(n) news -= set(rmlist) return news - def _fix_swap_direction(self, e): - if e in self._coupling.get_edges(): - return e + def _fix_swap_direction(self, edge): + if edge in self._coupling.get_edges(): + return edge else: - return e[1], e[0] + return edge[1], edge[0] def _find_focus_gates(self, gates, layout): if len(gates) <= 1: - logger.debug("_find_focus_gates: %d <= 1 gates" % len(gates)) + logger.debug("_find_focus_gates: %d <= 1 gates", len(gates)) return gates paths = {g: self._path_of(g, layout) for g in gates} @@ -220,14 +248,11 @@ def _find_focus_gates(self, gates, layout): return [best_gates] def _path_of(self, gate, layout): - """ - gate: two-qubit gate - """ qargs = self._dg.qargs(gate) if len(qargs) != 2: - raise MapperError("gate must be a two-qubit gate") - s, t = layout[qargs[0]], layout[qargs[1]] - path = self._coupling.shortest_undirected_path(s, t) + raise TranspilerError("gate must be a two-qubit gate") + source, target = layout[qargs[0]], layout[qargs[1]] + path = self._coupling.shortest_undirected_path(source, target) return path def _qubit_count_validity_check(self): @@ -235,11 +260,12 @@ def _qubit_count_validity_check(self): physical_qubits = list(self._coupling.physical_qubits) if len(virtual_qubits) > len(physical_qubits): - raise MapperError("Not enough qubits in _coupling") + raise TranspilerError("Not enough qubits in _coupling") for q in self._initial_layout.get_physical_bits(): if q not in physical_qubits: - raise MapperError("%s isn't in _coupling but in initial_layout" % pprint.pformat(q)) + raise TranspilerError("%s is not in _coupling but in initial_layout" % + pprint.pformat(q)) def _add_ancilla_qubits(self): virtual_qubits = sorted(self._dg.qubits) @@ -247,16 +273,16 @@ def _add_ancilla_qubits(self): for b in self._initial_layout.get_virtual_bits(): if b not in virtual_qubits: del self._initial_layout[b] - logger.info("remove unused qubit in initial_layout %s" % pprint.pformat(b)) + logger.info("remove unused qubit in initial_layout %s", pprint.pformat(b)) ancilla_qubits = set(physical_qubits) - set(self._initial_layout.get_physical_bits().keys()) # add ancilla qubits - if len(ancilla_qubits) > 0: + if ancilla_qubits: anc_qreg = QuantumRegister(len(ancilla_qubits), name="ancilla") - for i, aq in enumerate(ancilla_qubits): + for i, anq in enumerate(ancilla_qubits): virtual_qubits.append((anc_qreg, i)) - self._initial_layout[(anc_qreg, i)] = aq + self._initial_layout[(anc_qreg, i)] = anq assert len(physical_qubits) == len(virtual_qubits), \ "physical_qubits=%d, virtual_qubits=%d" % (len(physical_qubits), len(virtual_qubits)) @@ -274,117 +300,136 @@ def _create_empty_dagcircuit(self, physical_qreg): class GateCostEstimator: + """ + Define cost of gate used for selecting the gate to be resolved first in the special loops + to avoid cyclic swaps. + """ def __init__(self, gates, paths): self.gates = gates self.paths = paths - def cost(self, gate, priority=None): - """estimate cost if the gate pair (specified by gate_idx_pair) are resolved first + def cost(self, gate: int, priority: str = None) -> collections.namedtuple: + """ + estimate cost if the `gate` is resolved first + Args: + gate: gate whose cost is estimated as index + priority: costs are sorted by this lexicographic order + Returns: + estimated cost if the `gate` is resolved first """ if priority is None: priority = ['dependent_cost'] Cost = collections.namedtuple('Cost', priority) - pi = self.paths[gate] + path_i = self.paths[gate] - dc = 0 # dependent_cost + dependent_cost = 0 for j in self.gates: - pj = self.paths[j] - if pj[0] in pi or pj[-1] in pi: - dc += 1 + path_j = self.paths[j] + if path_j[0] in path_i or path_j[-1] in path_i: + dependent_cost += 1 - return Cost(dependent_cost=dc) + return Cost(dependent_cost=dependent_cost) class EdgeCostEstimator: + """ + Define cost of edge (in coupling graph) used for selecting the edge to be swapped. + """ def __init__(self, gates, layout, coupling, dg, max_depth=10): self.cand_edges = [] for gate in gates: qargs = dg.qargs(gate) if len(qargs) != 2: - raise MapperError("EdgeCostEstimator is only for two-qubit gates") - s, t = layout[qargs[0]], layout[qargs[1]] - for v in coupling.undirected_neighbors(s): - if coupling.distance(s, t) > coupling.distance(v, t): - self.cand_edges.append((s, v)) - for u in coupling.undirected_neighbors(t): - if coupling.distance(s, t) > coupling.distance(s, u): - self.cand_edges.append((u, t)) + raise TranspilerError("EdgeCostEstimator is only for two-qubit gates") + source, target = layout[qargs[0]], layout[qargs[1]] + for v in coupling.undirected_neighbors(source): + if coupling.distance(source, target) > coupling.distance(v, target): + self.cand_edges.append((source, v)) + for v in coupling.undirected_neighbors(target): + if coupling.distance(source, target) > coupling.distance(source, v): + self.cand_edges.append((v, target)) self.care_ends_list = self._construct_care_ends_list(gates, layout, dg, max_depth) self.coupling = coupling - def cost(self, e, priority=None, alpha=0.5): + def cost(self, edge: (int, int), + priority: str = None, + alpha: float = 0.5) -> collections.namedtuple: """ estimate cost if the edge e is swapped Args: - e: edge whose cost is estimated as pair of physical qubits, ex. (('q', 1), ('q', 2)) + edge: edge whose cost is estimated as pair of physical qubits priority: costs are sorted by this lexicographic order alpha: discount rate for cost of post layer gates + Returns: + estimated cost if the edge e is swapped + Raises: + TranspilerError: if found the case to be impossible. """ if priority is None: priority = ['lookahead_cost', 'immediate_cost'] Cost = collections.namedtuple('Cost', priority) - lc = 0 # lookahead_cost - ic = 0 # immediate_cost - for (s, t), d in self.care_ends_list: + lookahead_cost = 0 + immediate_cost = 0 + for (source, target), dist in self.care_ends_list: inc = 0 - if (s in e) ^ (t in e): # xor: either node of e is s or t of p (path from s to t) - if s == e[0]: - inc = self._after_swap_cost(e[0], e[1], t) - elif s == e[1]: - inc = self._after_swap_cost(e[1], e[0], t) - elif t == e[0]: - inc = self._after_swap_cost(e[0], e[1], s) - elif t == e[1]: - inc = self._after_swap_cost(e[1], e[0], s) + if (source in edge) ^ (target in edge): # xor + if source == edge[0]: + inc = self._after_swap_cost(edge[0], edge[1], target) + elif source == edge[1]: + inc = self._after_swap_cost(edge[1], edge[0], target) + elif target == edge[0]: + inc = self._after_swap_cost(edge[0], edge[1], source) + elif target == edge[1]: + inc = self._after_swap_cost(edge[1], edge[0], source) else: - raise MapperError("Unknown error") + raise TranspilerError("Unknown error") - lc += inc * (alpha ** d) - if d == 0: # current layer - ic += inc + lookahead_cost += inc * (alpha ** dist) + if dist == 0: # current layer + immediate_cost += inc - return Cost(lookahead_cost=lc, immediate_cost=ic) + return Cost(lookahead_cost=lookahead_cost, immediate_cost=immediate_cost) - def _construct_care_ends_list(self, gates, layout, dg, max_depth): + def _construct_care_ends_list(self, gates, layout, dependency_graph, max_depth): nodes = set(gates) dic = collections.defaultdict(int) # approximate max path length from gates to the key gate - for d in range(max_depth): - nexts = {s for n in nodes for s in dg.gr_successors(n)} + for dist in range(max_depth): + nexts = {s for n in nodes for s in dependency_graph.gr_successors(n)} for n in nexts: - if len(dg.qargs(n)) == 2: - dic[n] = d + 1 + if len(dependency_graph.qargs(n)) == 2: + dic[n] = dist + 1 nodes = nexts care_ends_list = [] - for g in gates: - qargs = dg.qargs(g) - s, t = layout[qargs[0]], layout[qargs[1]] - care_ends_list.append(((s, t), 0)) + for gidx in gates: + qargs = dependency_graph.qargs(gidx) + source, target = layout[qargs[0]], layout[qargs[1]] + care_ends_list.append(((source, target), 0)) - for g, d in dic.items(): - if d <= max_depth: - qargs = dg.qargs(g) - s, t = layout[qargs[0]], layout[qargs[1]] - care_ends_list.append(((s, t), d)) + for gidx, dist in dic.items(): + if dist <= max_depth: + qargs = dependency_graph.qargs(gidx) + source, target = layout[qargs[0]], layout[qargs[1]] + care_ends_list.append(((source, target), dist)) return care_ends_list - def _after_swap_cost(self, s1, s2, t): + def _after_swap_cost(self, source1, source2, target): """return distance difference in switching path s1->t to path s2->t for the coupling as undirected graph """ - d1 = self.coupling.distance(s1, t) - d2 = self.coupling.distance(s2, t) - if abs(d1 - d2) > 1: - raise MapperError("Invalid shortest paths on coupling graph") - return d2 - d1 + dist1 = self.coupling.distance(source1, target) + dits2 = self.coupling.distance(source2, target) + if abs(dist1 - dits2) > 1: + raise TranspilerError("Invalid shortest paths on coupling graph") + return dits2 - dist1 def _qargs(gate): @@ -417,14 +462,14 @@ def remove_head_swaps(qc: QuantumCircuit, for qarg in qargs: cx_seen[qarg] = True - logger.debug("remove_head_swaps: n_removed = %d" % len(to_be_removed)) + logger.debug("remove_head_swaps: n_removed = %d", len(to_be_removed)) resqc = copy.deepcopy(qc) new_initial_layout = {v: k for k, v in rev_new_layout.items()} new_layout = new_initial_layout rev_org_layout = {v: k for k, v in initial_layout.items()} - if len(to_be_removed) > 0: + if to_be_removed: qregs = resqc.qregs for i, gate in enumerate(resqc.data[:1 + to_be_removed[-1]]): qargs = _qargs(gate) @@ -445,7 +490,7 @@ def remove_head_swaps(qc: QuantumCircuit, def _change_qargs(gate: Gate, rev_org_layout: dict, new_layout: dict, qregs: dict): if gate.name == "measure": - raise MapperError("_change_qargs() is not applicable to measure gate") + raise TranspilerError("_change_qargs() is not applicable to measure gate") new_qarg = [] for qubit in _qargs(gate): diff --git a/qiskit/transpiler/passes/mapping/flexlayer_swap.py b/qiskit/transpiler/passes/mapping/flexlayer_swap.py index 878e4b5ace85..0316f9461aea 100644 --- a/qiskit/transpiler/passes/mapping/flexlayer_swap.py +++ b/qiskit/transpiler/passes/mapping/flexlayer_swap.py @@ -8,8 +8,10 @@ """ A pass implementing the flexible-layer mapper. -That is the swap mapper proposed in the paper [Itoko et. al. 2019]. -For the role of the swap mapper pass, see `lookahed_swap.py`. +That is the swap mapper proposed in the paper: +T. Itoko, R. Raymond, T. Imamichi, A. Matsuo, and A. W. Cross. +Quantum circuit compilers using gate commutation rules. +In Proceedings of ASP-DAC, pp. 191--196. ACM, 2019. This algorithm considers the *dependency graph* of a given circuit with less dependencies by considering commutativity of consecutive gates, @@ -19,20 +21,7 @@ in contrast to many other swap passes assumes fixed layers as their input. That's why this pass is named FlexlayerSwap pass. -The outline of the algorithm is as follows. -0. Assume an initial_layout is given and set it to `layout`. -1. Initialize `blocking_gates` as gates without in-edge in dependency graph. -2. Update `blocking_gates` by processing applicable gates for the `layout`. -3. If it comes to no blocking gates, it terminates. Otherwise, it selects a qubit - pair (= an edge in the coupling graph) to be swapped based on its `cost`. -4. Add the swap gate at the min-cost edge (= update `layout`). -5. Go back to the step 2. -Note: In the actual flow, there is an additional path for avoiding handling cyclic swaps in step 3. - -For more details on the algorithm, see [Itoko et. al. 2019]: -T. Itoko, R. Raymond, T. Imamichi, A. Matsuo, and A. W. Cross. -Quantum circuit compilers using gate commutation rules. -In Proceedings of ASP-DAC, pp. 191--196. ACM, 2019. +(For the general role of the swap mapper pass, see `lookahed_swap.py`.) """ from qiskit.converters import dag_to_circuit, circuit_to_dag from qiskit.dagcircuit import DAGCircuit From 18f08d20787621d07497903f85f08914e7ff4f79 Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Wed, 13 Feb 2019 17:23:59 +0900 Subject: [PATCH 10/40] linting --- .../transpiler/passes/mapping/algorithm/dependency_graph.py | 3 +++ .../passes/mapping/algorithm/flexlayer_heuristics.py | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py index 4d5c1f70736e..635d0f307101 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py +++ b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py @@ -77,6 +77,7 @@ def _create_xz_graph(self): for n in self._graph.nodes(): if self._gates[n].name in x_gates: [b] = self._gates[n].qargs + # pylint: disable=unbalanced-tuple-unpacking [pgow] = self._prior_gates_on_wire(self._gates, n) z_flag = False for m in pgow: @@ -96,6 +97,7 @@ def _create_xz_graph(self): raise TranspilerError("Unknown gate: " + gate.name) elif self._gates[n].name in z_gates: [b] = self._gates[n].qargs + # pylint: disable=unbalanced-tuple-unpacking [pgow] = self._prior_gates_on_wire(self._gates, n) x_flag = False for m in pgow: @@ -115,6 +117,7 @@ def _create_xz_graph(self): raise TranspilerError("Unknown gate: " + gate.name) elif self._gates[n].name == "cx": cbit, tbit = self._gates[n].qargs + # pylint: disable=unbalanced-tuple-unpacking [cpgow, tpgow] = self._prior_gates_on_wire(self._gates, n) z_flag = False diff --git a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py index 743b52d8366d..abda63f01489 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py +++ b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py @@ -321,7 +321,7 @@ def cost(self, gate: int, priority: str = None) -> collections.namedtuple: if priority is None: priority = ['dependent_cost'] - Cost = collections.namedtuple('Cost', priority) + Cost = collections.namedtuple('Cost', priority) # pylint: disable=invalid-name path_i = self.paths[gate] @@ -374,7 +374,8 @@ def cost(self, edge: (int, int), if priority is None: priority = ['lookahead_cost', 'immediate_cost'] - Cost = collections.namedtuple('Cost', priority) + Cost = collections.namedtuple('Cost', priority) # pylint: disable=invalid-name + lookahead_cost = 0 immediate_cost = 0 for (source, target), dist in self.care_ends_list: From c04d7d040521e14587431cb283479dc8714676d5 Mon Sep 17 00:00:00 2001 From: Atsushi Matsuo Date: Thu, 14 Feb 2019 11:15:12 +0900 Subject: [PATCH 11/40] pylint for test_dependency_graph.py and test_flexlayer_heuristics.py --- .../transpiler/test_dependency_graph.py | 187 ++++++++++-------- .../transpiler/test_flexlayer_heuristics.py | 150 +++++++------- 2 files changed, 188 insertions(+), 149 deletions(-) diff --git a/test/python/transpiler/test_dependency_graph.py b/test/python/transpiler/test_dependency_graph.py index 79a89c4d8b30..40a5f0066bcf 100644 --- a/test/python/transpiler/test_dependency_graph.py +++ b/test/python/transpiler/test_dependency_graph.py @@ -1,4 +1,11 @@ # -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +"""Tests the DependencyGraph.""" import unittest from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit @@ -10,106 +17,122 @@ class TestDependencyGraph(QiskitTestCase): """Tests the DependencyGraph.""" def test_qargs(self): - q = QuantumRegister(2, 'q') - c = ClassicalRegister(2, 'c') - circuit = QuantumCircuit(q, c) - circuit.cx(q[1], q[0]) - circuit.measure(q[0], c[1]) - dg = DependencyGraph(circuit) - self.assertEqual(dg.qargs(0), [(q, 1), (q, 0)]) - self.assertEqual(dg.qargs(1), [(q, 0)]) + """ Test for qargs. + """ + qr = QuantumRegister(2, 'q') + cr = ClassicalRegister(2, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.cx(qr[1], qr[0]) + circuit.measure(qr[0], cr[1]) + dep_graph = DependencyGraph(circuit) + self.assertEqual(dep_graph.qargs(0), [(qr, 1), (qr, 0)]) + self.assertEqual(dep_graph.qargs(1), [(qr, 0)]) def test_head_gates(self): - q = QuantumRegister(4, 'q') - c = ClassicalRegister(4, 'c') - circuit = QuantumCircuit(q, c) - circuit.cx(q[0], q[1]) - circuit.cx(q[2], q[3]) - circuit.cx(q[1], q[2]) - circuit.h(q[0]) - circuit.cx(q[0], q[1]) - circuit.cx(q[2], q[3]) - dg = DependencyGraph(circuit) - self.assertEqual(dg.head_gates(), set([0, 1])) + """ Test for head gates. + """ + qr = QuantumRegister(4, 'q') + cr = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[2], qr[3]) + circuit.cx(qr[1], qr[2]) + circuit.h(qr[0]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[2], qr[3]) + dep_graph = DependencyGraph(circuit) + self.assertEqual(dep_graph.head_gates(), set([0, 1])) def test_gr_successors(self): - q = QuantumRegister(4, 'q') - c = ClassicalRegister(4, 'c') - circuit = QuantumCircuit(q, c) - circuit.cx(q[0], q[1]) - circuit.cx(q[2], q[3]) - circuit.cx(q[1], q[2]) - circuit.h(q[0]) - circuit.cx(q[0], q[1]) - circuit.cx(q[2], q[3]) - dg = DependencyGraph(circuit) - self.assertEqual(list(dg.gr_successors(0)), [2, 3]) - self.assertEqual(list(dg.gr_successors(2)), [4, 5]) - self.assertEqual(list(dg.gr_successors(4)), []) + """ Test for successors of qrs. + """ + qr = QuantumRegister(4, 'q') + cr = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[2], qr[3]) + circuit.cx(qr[1], qr[2]) + circuit.h(qr[0]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[2], qr[3]) + dep_graph = DependencyGraph(circuit) + self.assertEqual(list(dep_graph.gr_successors(0)), [2, 3]) + self.assertEqual(list(dep_graph.gr_successors(2)), [4, 5]) + self.assertEqual(list(dep_graph.gr_successors(4)), []) def test_barrier(self): - b = QuantumRegister(4, 'b') - c = ClassicalRegister(4, 'c') - circuit = QuantumCircuit(b, c) - circuit.cx(b[0], b[1]) - circuit.barrier(b) - circuit.cx(b[1], b[0]) - circuit.cx(b[2], b[3]) - dg = DependencyGraph(circuit) + """ Test for barriers. + """ + qr = QuantumRegister(4, 'b') + cr = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.cx(qr[0], qr[1]) + circuit.barrier(qr) + circuit.cx(qr[1], qr[0]) + circuit.cx(qr[2], qr[3]) + dep_graph = DependencyGraph(circuit) expected = sorted([(0, 1), (1, 2), (1, 3)]) - self.assertEqual(list(dg._graph.edges()), expected) + self.assertEqual(list(dep_graph._graph.edges()), expected) def test_measure(self): - b = QuantumRegister(3, 'b') - c = ClassicalRegister(3, 'c') - circuit = QuantumCircuit(b, c) - circuit.h(b[0]) - circuit.cx(b[0], b[1]) - circuit.measure(b[0], c[1]) - circuit.cx(b[1], b[2]) - circuit.measure(b[2], c[1]) # overwrite -> introduce dependency (2, 4) - dg = DependencyGraph(circuit, graph_type="xz_commute") + """ Test for measurements. + """ + qr = QuantumRegister(3, 'b') + cr = ClassicalRegister(3, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.h(qr[0]) + circuit.cx(qr[0], qr[1]) + circuit.measure(qr[0], cr[1]) + circuit.cx(qr[1], qr[2]) + circuit.measure(qr[2], cr[1]) # overwrite -> introduce dependency (2, 4) + dep_graph = DependencyGraph(circuit, graph_type="xz_commute") expected_edges = sorted([(0, 1), (1, 2), (1, 3), (2, 4), (3, 4)]) - self.assertEqual(list(dg._graph.edges()), expected_edges) + self.assertEqual(list(dep_graph._graph.edges()), expected_edges) def test_h_gate_in_the_middle(self): - q = QuantumRegister(4, 'q') - c = ClassicalRegister(4, 'c') - circuit = QuantumCircuit(q, c) - circuit.cx(q[0], q[1]) - circuit.cx(q[2], q[3]) - circuit.cx(q[1], q[2]) - circuit.h(q[1]) - circuit.cx(q[1], q[0]) - dg = DependencyGraph(circuit, graph_type="xz_commute") + """ Test for a h gate in the middle of a quantum circuit. + """ + qr = QuantumRegister(4, 'q') + cr = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[2], qr[3]) + circuit.cx(qr[1], qr[2]) + circuit.h(qr[1]) + circuit.cx(qr[1], qr[0]) + dep_graph = DependencyGraph(circuit, graph_type="xz_commute") expected = sorted([(0, 2), (1, 2), (2, 3), (3, 4)]) - self.assertEqual(list(dg._graph.edges()), expected) + self.assertEqual(list(dep_graph._graph.edges()), expected) def test_rz_gate_in_the_middle(self): - q = QuantumRegister(4, 'q') - c = ClassicalRegister(4, 'c') - circuit = QuantumCircuit(q, c) - circuit.cx(q[0], q[1]) - circuit.cx(q[2], q[3]) - circuit.cx(q[1], q[2]) - circuit.rz(1.0, q[1]) - circuit.cx(q[1], q[0]) - dg = DependencyGraph(circuit, graph_type="xz_commute") + """ Test for a rz gate in the middle of a quantum circuit. + """ + qr = QuantumRegister(4, 'q') + cr = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[2], qr[3]) + circuit.cx(qr[1], qr[2]) + circuit.rz(1.0, qr[1]) + circuit.cx(qr[1], qr[0]) + dep_graph = DependencyGraph(circuit, graph_type="xz_commute") expected = sorted([(0, 2), (1, 2), (0, 3), (0, 4)]) - self.assertEqual(list(dg._graph.edges()), expected) + self.assertEqual(list(dep_graph._graph.edges()), expected) - def test_rz_gate_in_the_middle_blg(self): - q = QuantumRegister(4, 'q') - c = ClassicalRegister(4, 'c') - circuit = QuantumCircuit(q, c) - circuit.cx(q[0], q[1]) - circuit.cx(q[2], q[3]) - circuit.cx(q[1], q[2]) - circuit.rz(1.0, q[1]) - circuit.cx(q[1], q[0]) - dg = DependencyGraph(circuit, graph_type="basic") + def test_rz_gate_in_the_middle_basic_graph_type(self): + """ Test for a h gate in the middle of a quantum circuit with "basic" graph_type. + """ + qr = QuantumRegister(4, 'q') + cr = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[2], qr[3]) + circuit.cx(qr[1], qr[2]) + circuit.rz(1.0, qr[1]) + circuit.cx(qr[1], qr[0]) + dep_graph = DependencyGraph(circuit, graph_type="basic") expected = sorted([(0, 2), (1, 2), (2, 3), (3, 4)]) - self.assertEqual(list(dg._graph.edges()), expected) + self.assertEqual(list(dep_graph._graph.edges()), expected) if __name__ == "__main__": diff --git a/test/python/transpiler/test_flexlayer_heuristics.py b/test/python/transpiler/test_flexlayer_heuristics.py index b5ee3f290fe3..a642d2041dfc 100644 --- a/test/python/transpiler/test_flexlayer_heuristics.py +++ b/test/python/transpiler/test_flexlayer_heuristics.py @@ -1,4 +1,12 @@ # -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +"""Tests for FlexlayerHeuristics.""" + import unittest from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister @@ -7,42 +15,43 @@ from qiskit.transpiler.passes.mapping.algorithm import DependencyGraph from qiskit.transpiler.passes.mapping.algorithm import FlexlayerHeuristics, remove_head_swaps - class TestLookaheadHeuristics(unittest.TestCase): - """Tests for dependency_graph.py""" + """Tests for FlexlayerHeuristics.""" @unittest.skip("due to a bug in DAGCircuit.__eq__()") - def test_search_n4cx4h1(self): - q = QuantumRegister(4, 'q') - c = ClassicalRegister(4, 'c') - circuit = QuantumCircuit(q, c) - circuit.cx(q[0], q[1]) - circuit.cx(q[2], q[3]) - circuit.cx(q[1], q[2]) - circuit.h(q[1]) - circuit.cx(q[1], q[0]) + def test_search_4qcx4h1(self): + """Test for 4 cx gates and 1 h gate in a 4q circuit. + """ + qr = QuantumRegister(4, 'q') + cr = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[2], qr[3]) + circuit.cx(qr[1], qr[2]) + circuit.h(qr[1]) + circuit.cx(qr[1], qr[0]) circuit.barrier() for i in range(4): - circuit.measure(q[i], c[i]) - dg = DependencyGraph(circuit, graph_type="basic") + circuit.measure(qr[i], cr[i]) + dep_graph = DependencyGraph(circuit, graph_type="basic") coupling = CouplingMap([[0, 1], [1, 2], [1, 3]]) # {0: [1], 1: [2, 3]} - initial_layout = Layout.generate_trivial_layout(q) - algo = FlexlayerHeuristics(circuit, dg, coupling, initial_layout) + initial_layout = Layout.generate_trivial_layout(qr) + algo = FlexlayerHeuristics(circuit, dep_graph, coupling, initial_layout) actual_dag, layout = algo.search() - expected = QuantumCircuit(q, c) - expected.cx(q[0], q[1]) - expected.swap(q[1], q[2]) - expected.cx(q[1], q[3]) - expected.cx(q[2], q[1]) - expected.h(q[2]) - expected.swap(q[0], q[1]) - expected.cx(q[2], q[1]) + expected = QuantumCircuit(qr, cr) + expected.cx(qr[0], qr[1]) + expected.swap(qr[1], qr[2]) + expected.cx(qr[1], qr[3]) + expected.cx(qr[2], qr[1]) + expected.h(qr[2]) + expected.swap(qr[0], qr[1]) + expected.cx(qr[2], qr[1]) expected.barrier() - expected.measure(q[1], c[0]) - expected.measure(q[2], c[1]) - expected.measure(q[0], c[2]) - expected.measure(q[3], c[3]) + expected.measure(qr[1], cr[0]) + expected.measure(qr[2], cr[1]) + expected.measure(qr[0], cr[2]) + expected.measure(qr[3], cr[3]) expected_layout = initial_layout from qiskit.tools.visualization import dag_drawer dag_drawer(actual_dag) @@ -54,24 +63,26 @@ def test_search_n4cx4h1(self): @unittest.skip("TODO: Change to use DAGCircuit and Layout") def test_search_multi_creg(self): - b = QuantumRegister(4, 'b') - c = ClassicalRegister(2, 'c') - d = ClassicalRegister(2, 'd') - circuit = QuantumCircuit(b, c, d) - circuit.cx(b[0], b[1]) - circuit.cx(b[2], b[3]) - circuit.cx(b[1], b[2]) - circuit.h(b[1]) - circuit.cx(b[1], b[0]) - circuit.barrier(b) - circuit.measure(b[0], c[0]) - circuit.measure(b[1], c[1]) - circuit.measure(b[2], d[0]) - circuit.measure(b[3], d[1]) - dg = DependencyGraph(circuit, graph_type="basic") + """Test for multiple ClassicalRegisters. + """ + qr = QuantumRegister(4, 'b') + cr1 = ClassicalRegister(2, 'c') + cr2 = ClassicalRegister(2, 'd') + circuit = QuantumCircuit(qr, cr1, cr2) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[2], qr[3]) + circuit.cx(qr[1], qr[2]) + circuit.h(qr[1]) + circuit.cx(qr[1], qr[0]) + circuit.barrier(qr) + circuit.measure(qr[0], cr1[0]) + circuit.measure(qr[1], cr1[1]) + circuit.measure(qr[2], cr2[0]) + circuit.measure(qr[3], cr2[1]) + dep_graph = DependencyGraph(circuit, graph_type="basic") coupling = CouplingMap([[0, 1], [1, 2], [1, 3]]) # {0: [1], 1: [2, 3]} - initial_layout = Layout.generate_trivial_layout(b) - algo = FlexlayerHeuristics(circuit, dg, coupling, initial_layout) + initial_layout = Layout.generate_trivial_layout(qr) + algo = FlexlayerHeuristics(circuit, dep_graph, coupling, initial_layout) qc, layout = algo.search() actual_measures = [s for s in qc.qasm().split('\n') if s.startswith("measure")] expected_measures = [] @@ -85,15 +96,17 @@ def test_search_multi_creg(self): @unittest.skip("TODO: Change to use DAGCircuit and Layout") def test_remove_head_swaps(self): - q = QuantumRegister(4, 'q') - c = ClassicalRegister(4, 'c') - circuit = QuantumCircuit(q, c) - circuit.cx(q[0], q[1]) - circuit.u1(1, q[2]) - circuit.swap(q[2], q[3]) - circuit.cx(q[1], q[2]) + """Test for removing unnecessary swap gates from qc by changing initial_layout. + """ + qr = QuantumRegister(4, 'q') + cr = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.cx(qr[0], qr[1]) + circuit.u1(1, qr[2]) + circuit.swap(qr[2], qr[3]) + circuit.cx(qr[1], qr[2]) for i in range(4): - circuit.measure(q[i], c[i]) + circuit.measure(qr[i], cr[i]) initial_layout = {('q', i): ('q', i) for i in range(4)} resqc, layout = remove_head_swaps(circuit, initial_layout) actual_qasm = ''.join([s for s in resqc.qasm().split('\n') if not s.startswith("measure")]) @@ -128,21 +141,24 @@ def test_remove_head_swaps(self): # dag, layout = algo.search() @unittest.skip("TODO: Change to use DAGCircuit and Layout") - def test_remove_head_swaps2(self): - q = QuantumRegister(6, 'q') - c = ClassicalRegister(6, 'c') - circuit = QuantumCircuit(q, c) - circuit.u1(1, q[4]) - circuit.h(q[3]) - circuit.swap(q[4], q[5]) - circuit.cx(q[3], q[4]) - circuit.swap(q[3], q[4]) - circuit.u1(1, q[1]) - circuit.h(q[0]) - circuit.swap(q[0], q[1]) - circuit.swap(q[1], q[2]) - circuit.swap(q[2], q[3]) - circuit.cx(q[3], q[4]) + def test_remove_head_swaps_and_leave_middle_swaps(self): + """Test for removing unnecessary swap gates from qc by changing initial_layout, + and test for middle swaps are left. + """ + qr = QuantumRegister(6, 'q') + cr = ClassicalRegister(6, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.u1(1, qr[4]) + circuit.h(qr[3]) + circuit.swap(qr[4], qr[5]) + circuit.cx(qr[3], qr[4]) + circuit.swap(qr[3], qr[4]) + circuit.u1(1, qr[1]) + circuit.h(qr[0]) + circuit.swap(qr[0], qr[1]) + circuit.swap(qr[1], qr[2]) + circuit.swap(qr[2], qr[3]) + circuit.cx(qr[3], qr[4]) initial_layout = {('b', i): ('q', i) for i in range(6)} resqc, layout = remove_head_swaps(circuit, initial_layout) actual_qasm = ''.join([s for s in resqc.qasm().split('\n')]) From eb4252a8194152dc5eab78c57e45797418129ef1 Mon Sep 17 00:00:00 2001 From: Atsushi Matsuo Date: Thu, 14 Feb 2019 12:45:43 +0900 Subject: [PATCH 12/40] modified test_dependenchy_graph.py and test_flexlayer_heuristics.py --- test/python/transpiler/test_dependency_graph.py | 2 +- test/python/transpiler/test_flexlayer_heuristics.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/python/transpiler/test_dependency_graph.py b/test/python/transpiler/test_dependency_graph.py index 40a5f0066bcf..9c6a6b79b4e9 100644 --- a/test/python/transpiler/test_dependency_graph.py +++ b/test/python/transpiler/test_dependency_graph.py @@ -44,7 +44,7 @@ def test_head_gates(self): self.assertEqual(dep_graph.head_gates(), set([0, 1])) def test_gr_successors(self): - """ Test for successors of qrs. + """ Test for successor gates of Gr (transitive reduction). """ qr = QuantumRegister(4, 'q') cr = ClassicalRegister(4, 'c') diff --git a/test/python/transpiler/test_flexlayer_heuristics.py b/test/python/transpiler/test_flexlayer_heuristics.py index a642d2041dfc..9c0e1eccdf05 100644 --- a/test/python/transpiler/test_flexlayer_heuristics.py +++ b/test/python/transpiler/test_flexlayer_heuristics.py @@ -15,7 +15,8 @@ from qiskit.transpiler.passes.mapping.algorithm import DependencyGraph from qiskit.transpiler.passes.mapping.algorithm import FlexlayerHeuristics, remove_head_swaps -class TestLookaheadHeuristics(unittest.TestCase): + +class TestFlexlayerHeuristics(unittest.TestCase): """Tests for FlexlayerHeuristics.""" @unittest.skip("due to a bug in DAGCircuit.__eq__()") @@ -141,9 +142,8 @@ def test_remove_head_swaps(self): # dag, layout = algo.search() @unittest.skip("TODO: Change to use DAGCircuit and Layout") - def test_remove_head_swaps_and_leave_middle_swaps(self): - """Test for removing unnecessary swap gates from qc by changing initial_layout, - and test for middle swaps are left. + def test_remove_head_swaps_and_remain_middle_swaps(self): + """Test for only head swaps are removed, and middle swaps are remained properly. """ qr = QuantumRegister(6, 'q') cr = ClassicalRegister(6, 'c') From 5b0a71636f34e59cbd4bbbf379bc3c9898dc0bfa Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Fri, 5 Jul 2019 11:36:23 +0900 Subject: [PATCH 13/40] update aligning changes in basic classes --- .../mapping/algorithm/dependency_graph.py | 28 ++++++++++++------- .../mapping/algorithm/flexlayer_heuristics.py | 9 +++--- .../passes/mapping/flexlayer_swap.py | 26 ++--------------- 3 files changed, 25 insertions(+), 38 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py index 635d0f307101..f3437583bb16 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py +++ b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py @@ -12,16 +12,23 @@ there exists a path from g1 to g2. """ import copy -from collections import defaultdict -from typing import List, Set, Tuple +from collections import defaultdict, namedtuple +from typing import List, Set, Union import networkx as nx - from qiskit import QuantumCircuit, QuantumRegister -from qiskit.circuit import Instruction -from qiskit.mapper import Layout +from qiskit.circuit import Qubit, Clbit +from qiskit.transpiler import Layout from qiskit.transpiler import TranspilerError +InstructionContext = namedtuple("InstructionContext", "instruction qargs cargs") + + +class ArgumentedGate(InstructionContext): + @property + def name(self) -> str: + return self.instruction.name + class DependencyGraph: """ @@ -46,7 +53,8 @@ def __init__(self, TranspilerError: if `graph_type` is not one of the types listed above. """ - self._gates = quantum_circuit.data + self._gates = [ArgumentedGate(instr, qrags, cargs) + for instr, qrags, cargs in quantum_circuit.data] self._graph = nx.DiGraph() # dependency graph (including 1-qubit gates) @@ -217,7 +225,7 @@ def n_nodes(self) -> int: """ return self._graph.__len__() - def qargs(self, i: int) -> List[Tuple[QuantumRegister, int]]: + def qargs(self, i: int) -> List[Qubit]: """Qubit arguments of the gate Args: i: Index of the gate in the `self._gates` @@ -226,7 +234,7 @@ def qargs(self, i: int) -> List[Tuple[QuantumRegister, int]]: """ return self._gates[i].qargs - def _all_args(self, i: int) -> List[Tuple[QuantumRegister, int]]: + def _all_args(self, i: int) -> List[Union[Qubit, Clbit]]: """Qubit and classical-bit arguments of the gate Args: i: Index of the gate in the `self._gates` @@ -278,7 +286,7 @@ def ancestors(self, i: int) -> Set[int]: """ return nx.ancestors(self._graph, i) - def gate(self, gidx: int, layout: Layout, physical_qreg: QuantumRegister) -> Instruction: + def gate(self, gidx: int, layout: Layout, physical_qreg: QuantumRegister) -> InstructionContext: """Convert acting qubits of gate `gidx` from virtual qubits to physical ones. Args: gidx: Index of the gate in the `self._gates` @@ -292,7 +300,7 @@ def gate(self, gidx: int, layout: Layout, physical_qreg: QuantumRegister) -> Ins gate = copy.deepcopy(self._gates[gidx]) for i, virtual_qubit in enumerate(gate.qargs): if virtual_qubit in layout.get_virtual_bits().keys(): - gate.qargs[i] = (physical_qreg, layout[virtual_qubit]) + gate.qargs[i] = Qubit(physical_qreg, layout[virtual_qubit]) else: raise TranspilerError("virtual_qubit must be in layout") return gate diff --git a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py index abda63f01489..4e0416d6afc9 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py +++ b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py @@ -32,8 +32,9 @@ from qiskit.circuit import Gate from qiskit.dagcircuit import DAGCircuit from qiskit.extensions.standard import SwapGate -from qiskit.mapper import CouplingMap, Layout +from qiskit.transpiler import CouplingMap, Layout from qiskit.transpiler import TranspilerError + from .ancestors import Ancestors from .dependency_graph import DependencyGraph @@ -101,7 +102,7 @@ def search(self) -> (DAGCircuit, Layout): if dones: for gidx in dones: - new_dag.apply_operation_back(self._dg.gate(gidx, layout, qreg)) + new_dag.apply_operation_back(*self._dg.gate(gidx, layout, qreg)) if not blocking_gates: break @@ -120,7 +121,7 @@ def search(self) -> (DAGCircuit, Layout): # usual case logger.debug("swap min-cost edge = %s", pprint.pformat(edge)) edge = self._fix_swap_direction(edge) - new_dag.apply_operation_back(SwapGate(qreg[edge[0]], qreg[edge[1]])) + new_dag.apply_operation_back(SwapGate(), [qreg[edge[0]], qreg[edge[1]]]) # update layout layout.swap(edge[0], edge[1]) else: @@ -145,7 +146,7 @@ def search(self) -> (DAGCircuit, Layout): min_cost, edge = min(zip(costs, ece.cand_edges)) edge = self._fix_swap_direction(edge) - new_dag.apply_operation_back(SwapGate(qreg[edge[0]], qreg[edge[1]])) + new_dag.apply_operation_back(SwapGate(), [qreg[edge[0]], qreg[edge[1]]]) layout.swap(edge[0], edge[1]) logger.debug("%d-th inner iter. add a swap (%d, %d)", kin, edge[0], edge[1]) diff --git a/qiskit/transpiler/passes/mapping/flexlayer_swap.py b/qiskit/transpiler/passes/mapping/flexlayer_swap.py index 0316f9461aea..dd29ebfcd826 100644 --- a/qiskit/transpiler/passes/mapping/flexlayer_swap.py +++ b/qiskit/transpiler/passes/mapping/flexlayer_swap.py @@ -25,8 +25,9 @@ """ from qiskit.converters import dag_to_circuit, circuit_to_dag from qiskit.dagcircuit import DAGCircuit -from qiskit.mapper import CouplingMap, Layout +from qiskit.transpiler import CouplingMap, Layout from qiskit.transpiler import TransformationPass + from .algorithm.dependency_graph import DependencyGraph from .algorithm.flexlayer_heuristics import FlexlayerHeuristics from .barrier_before_final_measurements import BarrierBeforeFinalMeasurements @@ -79,28 +80,5 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: lookahead_depth=self._lookahead_depth, decay_rate=self._decay_rate) res_dag, layout = algo.search() - res_dag = physical_to_virtual(res_dag, layout) return res_dag - -def physical_to_virtual(dag: DAGCircuit, initial_layout: Layout) -> DAGCircuit: - """ - Convert a physical circuit `dag` into the virtual circuit under a given `initial_layout`. - Args: - dag: a physical circuit, assuming 'q' is the register name of its physical qubits. - initial_layout: given initial layout. - Returns: - A converted circuit with virtual qubits - """ - layout = {} - qubits = dag.qubits() - for k, v in initial_layout.get_physical_bits().items(): - layout[qubits[k]] = v - - circuit = dag_to_circuit(dag) - circuit.qregs = initial_layout.get_registers() - for gate in circuit.data: - for i, q in enumerate(gate.qargs): - gate.qargs[i] = layout[q] - - return circuit_to_dag(circuit) From e119d1da4a4c5796573c40074544f4791ef905ac Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Tue, 9 Jul 2019 17:31:52 +0900 Subject: [PATCH 14/40] remove initial_layout from constructor's argument --- .../passes/mapping/flexlayer_swap.py | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/flexlayer_swap.py b/qiskit/transpiler/passes/mapping/flexlayer_swap.py index dd29ebfcd826..deb64c85fbb5 100644 --- a/qiskit/transpiler/passes/mapping/flexlayer_swap.py +++ b/qiskit/transpiler/passes/mapping/flexlayer_swap.py @@ -1,9 +1,16 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2019. +# +# 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 pass implementing the flexible-layer mapper. @@ -23,10 +30,11 @@ (For the general role of the swap mapper pass, see `lookahed_swap.py`.) """ -from qiskit.converters import dag_to_circuit, circuit_to_dag +from qiskit.converters import dag_to_circuit from qiskit.dagcircuit import DAGCircuit -from qiskit.transpiler import CouplingMap, Layout -from qiskit.transpiler import TransformationPass +from qiskit.transpiler.basepasses import TransformationPass +from qiskit.transpiler.coupling import CouplingMap +from qiskit.transpiler.layout import Layout from .algorithm.dependency_graph import DependencyGraph from .algorithm.flexlayer_heuristics import FlexlayerHeuristics @@ -40,21 +48,18 @@ class FlexlayerSwap(TransformationPass): def __init__(self, coupling_map: CouplingMap, - initial_layout: Layout = None, lookahead_depth: int = 10, decay_rate: float = 0.5): """ - Maps a DAGCircuit onto a `coupling_map` using swap gates for a given `initial_layout`. + Maps a DAGCircuit onto a `coupling_map` using swap gates. Args: coupling_map: Directed graph represented a coupling map. - initial_layout: initial layout of qubits in mapping lookahead_depth: how far gates from blocking gates should be looked ahead decay_rate: decay rate of look-ahead weight (0 < decay_rate < 1) """ super().__init__() self.requires.append(BarrierBeforeFinalMeasurements()) self._coupling_map = coupling_map - self._initial_layout = initial_layout self._lookahead_depth = lookahead_depth self._decay_rate = decay_rate @@ -66,19 +71,15 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: Returns: A mapped DAG (with virtual qubits). """ - if not self._initial_layout: - self._initial_layout = self.property_set["layout"] - if not self._initial_layout: - self._initial_layout = Layout.generate_trivial_layout(*dag.qregs.values()) + initial_layout = Layout.generate_trivial_layout(*dag.qregs.values()) qc = dag_to_circuit(dag) dependency_graph = DependencyGraph(qc, graph_type="xz_commute") algo = FlexlayerHeuristics(qc=qc, dependency_graph=dependency_graph, coupling=self._coupling_map, - initial_layout=self._initial_layout, + initial_layout=initial_layout, lookahead_depth=self._lookahead_depth, decay_rate=self._decay_rate) - res_dag, layout = algo.search() + res_dag, _ = algo.search() return res_dag - From 37c181762ce5f68a0c54e2a692e193d8bec28aec Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Tue, 9 Jul 2019 18:01:07 +0900 Subject: [PATCH 15/40] lint and replace crlf to lf --- .../passes/mapping/algorithm/__init__.py | 17 +- .../passes/mapping/algorithm/ancestors.py | 13 +- .../mapping/algorithm/dependency_graph.py | 686 +++++++++--------- .../mapping/algorithm/flexlayer_heuristics.py | 21 +- .../transpiler/test_dependency_graph.py | 285 ++++---- .../transpiler/test_flexlayer_heuristics.py | 358 ++++----- test/python/transpiler/test_flexlayer_swap.py | 349 +-------- 7 files changed, 735 insertions(+), 994 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/algorithm/__init__.py b/qiskit/transpiler/passes/mapping/algorithm/__init__.py index 1d130fdca886..1e9e5e7aa8fb 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/__init__.py +++ b/qiskit/transpiler/passes/mapping/algorithm/__init__.py @@ -1,12 +1,15 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2019. +# +# 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. """Module containing algorithms used in transpiler pass.""" - -from .flexlayer_heuristics import FlexlayerHeuristics, remove_head_swaps -from .ancestors import Ancestors -from .dependency_graph import DependencyGraph diff --git a/qiskit/transpiler/passes/mapping/algorithm/ancestors.py b/qiskit/transpiler/passes/mapping/algorithm/ancestors.py index 1c7bc8a44e58..24da1cf1bd41 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/ancestors.py +++ b/qiskit/transpiler/passes/mapping/algorithm/ancestors.py @@ -1,9 +1,16 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2019. +# +# 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. """ Utility for speeding up a function to find ancestors in DAG. diff --git a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py index f3437583bb16..358b4eef0431 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py +++ b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py @@ -1,337 +1,349 @@ -# -*- coding: utf-8 -*- - -# Copyright 2019, IBM. -# -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. - -""" -A dependency graph represents precedence relations of the gates in a quantum circuit considering -commutation rules. Each node represents gates in the circuit. Each directed edge represents -dependency of two gates. For example, gate g1 must be applied before gate g2 if and only if -there exists a path from g1 to g2. -""" -import copy -from collections import defaultdict, namedtuple -from typing import List, Set, Union - -import networkx as nx -from qiskit import QuantumCircuit, QuantumRegister -from qiskit.circuit import Qubit, Clbit -from qiskit.transpiler import Layout -from qiskit.transpiler import TranspilerError - -InstructionContext = namedtuple("InstructionContext", "instruction qargs cargs") - - -class ArgumentedGate(InstructionContext): - @property - def name(self) -> str: - return self.instruction.name - - -class DependencyGraph: - """ - Create a dependency graph of a quantum circuit with a chosen commutation rule. - """ - - def __init__(self, - quantum_circuit: QuantumCircuit, - graph_type: str = "basic"): - """ - Construct the dependency graph of `quantum_circuit` considering commutations/dependencies - specified by `graph_type`. - - Args: - quantum_circuit: A quantum circuit whose dependency graph to be constructed. - graph_type: Which type of dependency is considered. - - "xz_commute": consider four commutation rules proposed in [Itoko et. al. 2019]. - - "basic": consider only the commutation between gates without sharing qubits. - - "layer": fix layers and add dependencies between layers to `basic`. - - Raises: - TranspilerError: if `graph_type` is not one of the types listed above. - """ - - self._gates = [ArgumentedGate(instr, qrags, cargs) - for instr, qrags, cargs in quantum_circuit.data] - - self._graph = nx.DiGraph() # dependency graph (including 1-qubit gates) - - for i, _ in enumerate(self._gates): - self._graph.add_node(i) - - self.qubits = set() - for i, _ in enumerate(self._gates): - self.qubits.update(self.qargs(i)) - - if graph_type == "xz_commute": - self._create_xz_graph() - elif graph_type == "basic": - self._create_basic_graph() - elif graph_type == "layer": - self._create_layer_graph() - else: - raise TranspilerError("Unknown graph_type:" + graph_type) - - # remove redundant edges in dependency graph - self.remove_redundancy(self._graph) - - def _create_xz_graph(self): - z_gates = ["u1", "rz", "s", "t", "z", "sdg", "tdg"] - x_gates = ["rx", "x"] - b_gates = ["u3", "h", "u2", "ry", "barrier", "measure", "swap", "y"] - # construct commutation-rules-aware dependency graph - for n in self._graph.nodes(): - if self._gates[n].name in x_gates: - [b] = self._gates[n].qargs - # pylint: disable=unbalanced-tuple-unpacking - [pgow] = self._prior_gates_on_wire(self._gates, n) - z_flag = False - for m in pgow: - gate = self._gates[m] - if gate.name in b_gates: - self._graph.add_edge(m, n) - break - elif gate.name in x_gates or (gate.name == "cx" and gate.qargs[1] == b): - if z_flag: - break - else: - continue - elif gate.name in z_gates or (gate.name == "cx" and gate.qargs[0] == b): - self._graph.add_edge(m, n) - z_flag = True - else: - raise TranspilerError("Unknown gate: " + gate.name) - elif self._gates[n].name in z_gates: - [b] = self._gates[n].qargs - # pylint: disable=unbalanced-tuple-unpacking - [pgow] = self._prior_gates_on_wire(self._gates, n) - x_flag = False - for m in pgow: - gate = self._gates[m] - if gate.name in b_gates: - self._graph.add_edge(m, n) - break - elif gate.name in x_gates or (gate.name == "cx" and gate.qargs[1] == b): - self._graph.add_edge(m, n) - x_flag = True - elif gate.name in z_gates or (gate.name == "cx" and gate.qargs[0] == b): - if x_flag: - break - else: - continue - else: - raise TranspilerError("Unknown gate: " + gate.name) - elif self._gates[n].name == "cx": - cbit, tbit = self._gates[n].qargs - # pylint: disable=unbalanced-tuple-unpacking - [cpgow, tpgow] = self._prior_gates_on_wire(self._gates, n) - - z_flag = False - for m in tpgow: # target bit: bt - gate = self._gates[m] - if gate.name in b_gates: - self._graph.add_edge(m, n) - break - elif gate.name in x_gates or (gate.name == "cx" and gate.qargs[1] == tbit): - if z_flag: - break - else: - continue - elif gate.name in z_gates or (gate.name == "cx" and gate.qargs[0] == tbit): - self._graph.add_edge(m, n) - z_flag = True - else: - raise TranspilerError("Unknown gate: " + gate.name) - - x_flag = False - for m in cpgow: # control bit: bc - gate = self._gates[m] - if gate.name in b_gates: - self._graph.add_edge(m, n) - break - elif gate.name in x_gates or (gate.name == "cx" and gate.qargs[1] == cbit): - self._graph.add_edge(m, n) - x_flag = True - elif gate.name in z_gates or (gate.name == "cx" and gate.qargs[0] == cbit): - if x_flag: - break - else: - continue - else: - raise TranspilerError("Unknown gate: " + gate.name) - elif self._gates[n].name in b_gates: - for i, pgow in enumerate(self._prior_gates_on_wire(self._gates, n)): - b = self._all_args(n)[i] - x_flag, z_flag = False, False - for m in pgow: - gate = self._gates[m] - if gate.name in b_gates: - self._graph.add_edge(m, n) - break - elif gate.name in x_gates or (gate.name == "cx" and gate.qargs[1] == b): - if z_flag: - break - else: - self._graph.add_edge(m, n) - x_flag = True - elif gate.name in z_gates or (gate.name == "cx" and gate.qargs[0] == b): - if x_flag: - break - else: - self._graph.add_edge(m, n) - z_flag = True - else: - raise TranspilerError("Unknown gate: " + gate.name) - else: - raise TranspilerError("Unknown gate: " + self._gates[n].name) - - def _create_basic_graph(self): - for n in self._graph.nodes(): - for pgow in self._prior_gates_on_wire(self._gates, n): - m = next(pgow, -1) - if m != -1: - self._graph.add_edge(m, n) - - def _create_layer_graph(self): - self._create_basic_graph() - - # construct CNOT layers - layers = [] - wire = defaultdict(int) - for n in self._graph.nodes(): - qargs = self.qargs(n) - if self.gate_name(n) == "cx": # consider only CNOTs - i = 1 + max(wire[qargs[0]], wire[qargs[1]]) - wire[qargs[0]] = i - wire[qargs[1]] = i - if len(layers) > i: - layers[i].append(n) - else: - layers.append([n]) - - # Add more edges to basic graph for fixing layers - for i in range(len(layers) - 1): - j = i + 1 - for icx in layers[i]: - for jcx in layers[j]: - self._graph.add_edge(icx, jcx) - - def n_nodes(self) -> int: - """Number of the nodes - Returns: - Number of the nodes in this graph. - """ - return self._graph.__len__() - - def qargs(self, i: int) -> List[Qubit]: - """Qubit arguments of the gate - Args: - i: Index of the gate in the `self._gates` - Returns: - List of qubit arguments. - """ - return self._gates[i].qargs - - def _all_args(self, i: int) -> List[Union[Qubit, Clbit]]: - """Qubit and classical-bit arguments of the gate - Args: - i: Index of the gate in the `self._gates` - Returns: - List of all arguments. - """ - return self._gates[i].qargs + self._gates[i].cargs - - def gate_name(self, i: int) -> str: - """Name of the gate - Args: - i: Index of the gate in the `self._gates` - Returns: - Name of the gate. - """ - return self._gates[i].name - - def head_gates(self) -> Set[int]: - """Gates which can be applicable prior to the other gates - Returns: - Set of indices of the gates. - """ - return frozenset([n for n in self._graph.nodes() if len(self._graph.in_edges(n)) == 0]) - - def gr_successors(self, i: int) -> List[int]: - """Successor gates in Gr (transitive reduction) of the gate - Args: - i: Index of the gate in the `self._gates` - Returns: - Set of indices of the successor gates. - """ - return self._graph.successors(i) - - def descendants(self, i: int) -> Set[int]: - """Descendant gates of gate `i` - Args: - i: Index of the gate in the `self._gates` - Returns: - Set of indices of the descendant gates. - """ - return nx.descendants(self._graph, i) - - def ancestors(self, i: int) -> Set[int]: - """Ancestor gates of gate `i` - Args: - i: Index of the gate in the `self._gates` - Returns: - Set of indices of the ancestor gates. - """ - return nx.ancestors(self._graph, i) - - def gate(self, gidx: int, layout: Layout, physical_qreg: QuantumRegister) -> InstructionContext: - """Convert acting qubits of gate `gidx` from virtual qubits to physical ones. - Args: - gidx: Index of the gate in the `self._gates` - layout: Layout used in conversion - physical_qreg: Register of physical qubit - Returns: - Converted gate with physical qubit. - Raises: - TranspilerError: if virtual qubit of the gate `gidx` is not found in the layout. - """ - gate = copy.deepcopy(self._gates[gidx]) - for i, virtual_qubit in enumerate(gate.qargs): - if virtual_qubit in layout.get_virtual_bits().keys(): - gate.qargs[i] = Qubit(physical_qreg, layout[virtual_qubit]) - else: - raise TranspilerError("virtual_qubit must be in layout") - return gate - - @staticmethod - def remove_redundancy(graph): - """remove redundant edges in DAG (= change `graph` to its transitive reduction) - """ - edges = list(graph.edges()) - for edge in edges: - graph.remove_edge(edge[0], edge[1]) - if not nx.has_path(graph, edge[0], edge[1]): - graph.add_edge(edge[0], edge[1]) - - @staticmethod - def _prior_gates_on_wire(gate_list, toidx): - res = [] - for qarg in gate_list[toidx].qargs: - gates = [] - for i, gate in enumerate(gate_list[:toidx]): - if qarg in gate.qargs: - gates.append(i) - res.append(reversed(gates)) - for carg in gate_list[toidx].cargs: - gates = [] - for i, gate in enumerate(gate_list[:toidx]): - if carg in gate.cargs: - gates.append(i) - res.append(reversed(gates)) - return res - - -if __name__ == '__main__': - pass +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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 dependency graph represents precedence relations of the gates in a quantum circuit considering +commutation rules. Each node represents gates in the circuit. Each directed edge represents +dependency of two gates. For example, gate g1 must be applied before gate g2 if and only if +there exists a path from g1 to g2. +""" +import copy +from collections import defaultdict, namedtuple +from typing import List, Set, Union + +import networkx as nx +from qiskit.circuit import QuantumCircuit, QuantumRegister +from qiskit.circuit import Qubit, Clbit +from qiskit.transpiler.exceptions import TranspilerError +from qiskit.transpiler.layout import Layout + +InstructionContext = namedtuple("InstructionContext", "instruction qargs cargs") + + +class ArgumentedGate(InstructionContext): + """ + A wrapper class of `InstructionContext` (instruction with args context). + """ + + @property + def name(self) -> str: + """Name of this instruction.""" + return self.instruction.name + + +class DependencyGraph: + """ + Create a dependency graph of a quantum circuit with a chosen commutation rule. + """ + + def __init__(self, + quantum_circuit: QuantumCircuit, + graph_type: str = "basic"): + """ + Construct the dependency graph of `quantum_circuit` considering commutations/dependencies + specified by `graph_type`. + + Args: + quantum_circuit: A quantum circuit whose dependency graph to be constructed. + graph_type: Which type of dependency is considered. + - "xz_commute": consider four commutation rules proposed in [Itoko et. al. 2019]. + - "basic": consider only the commutation between gates without sharing qubits. + - "layer": fix layers and add dependencies between layers to `basic`. + + Raises: + TranspilerError: if `graph_type` is not one of the types listed above. + """ + + self._gates = [ArgumentedGate(instr, qrags, cargs) + for instr, qrags, cargs in quantum_circuit.data] + + self._graph = nx.DiGraph() # dependency graph (including 1-qubit gates) + + for i, _ in enumerate(self._gates): + self._graph.add_node(i) + + self.qubits = set() + for i, _ in enumerate(self._gates): + self.qubits.update(self.qargs(i)) + + if graph_type == "xz_commute": + self._create_xz_graph() + elif graph_type == "basic": + self._create_basic_graph() + elif graph_type == "layer": + self._create_layer_graph() + else: + raise TranspilerError("Unknown graph_type:" + graph_type) + + # remove redundant edges in dependency graph + self.remove_redundancy(self._graph) + + def _create_xz_graph(self): + z_gates = ["u1", "rz", "s", "t", "z", "sdg", "tdg"] + x_gates = ["rx", "x"] + b_gates = ["u3", "h", "u2", "ry", "barrier", "measure", "swap", "y"] + # construct commutation-rules-aware dependency graph + for n in self._graph.nodes(): + if self._gates[n].name in x_gates: + [b] = self._gates[n].qargs + # pylint: disable=unbalanced-tuple-unpacking + [pgow] = self._prior_gates_on_wire(self._gates, n) + z_flag = False + for m in pgow: + gate = self._gates[m] + if gate.name in b_gates: + self._graph.add_edge(m, n) + break + elif gate.name in x_gates or (gate.name == "cx" and gate.qargs[1] == b): + if z_flag: + break + else: + continue + elif gate.name in z_gates or (gate.name == "cx" and gate.qargs[0] == b): + self._graph.add_edge(m, n) + z_flag = True + else: + raise TranspilerError("Unknown gate: " + gate.name) + elif self._gates[n].name in z_gates: + [b] = self._gates[n].qargs + # pylint: disable=unbalanced-tuple-unpacking + [pgow] = self._prior_gates_on_wire(self._gates, n) + x_flag = False + for m in pgow: + gate = self._gates[m] + if gate.name in b_gates: + self._graph.add_edge(m, n) + break + elif gate.name in x_gates or (gate.name == "cx" and gate.qargs[1] == b): + self._graph.add_edge(m, n) + x_flag = True + elif gate.name in z_gates or (gate.name == "cx" and gate.qargs[0] == b): + if x_flag: + break + else: + continue + else: + raise TranspilerError("Unknown gate: " + gate.name) + elif self._gates[n].name == "cx": + cbit, tbit = self._gates[n].qargs + # pylint: disable=unbalanced-tuple-unpacking + [cpgow, tpgow] = self._prior_gates_on_wire(self._gates, n) + + z_flag = False + for m in tpgow: # target bit: bt + gate = self._gates[m] + if gate.name in b_gates: + self._graph.add_edge(m, n) + break + elif gate.name in x_gates or (gate.name == "cx" and gate.qargs[1] == tbit): + if z_flag: + break + else: + continue + elif gate.name in z_gates or (gate.name == "cx" and gate.qargs[0] == tbit): + self._graph.add_edge(m, n) + z_flag = True + else: + raise TranspilerError("Unknown gate: " + gate.name) + + x_flag = False + for m in cpgow: # control bit: bc + gate = self._gates[m] + if gate.name in b_gates: + self._graph.add_edge(m, n) + break + elif gate.name in x_gates or (gate.name == "cx" and gate.qargs[1] == cbit): + self._graph.add_edge(m, n) + x_flag = True + elif gate.name in z_gates or (gate.name == "cx" and gate.qargs[0] == cbit): + if x_flag: + break + else: + continue + else: + raise TranspilerError("Unknown gate: " + gate.name) + elif self._gates[n].name in b_gates: + for i, pgow in enumerate(self._prior_gates_on_wire(self._gates, n)): + b = self._all_args(n)[i] + x_flag, z_flag = False, False + for m in pgow: + gate = self._gates[m] + if gate.name in b_gates: + self._graph.add_edge(m, n) + break + elif gate.name in x_gates or (gate.name == "cx" and gate.qargs[1] == b): + if z_flag: + break + else: + self._graph.add_edge(m, n) + x_flag = True + elif gate.name in z_gates or (gate.name == "cx" and gate.qargs[0] == b): + if x_flag: + break + else: + self._graph.add_edge(m, n) + z_flag = True + else: + raise TranspilerError("Unknown gate: " + gate.name) + else: + raise TranspilerError("Unknown gate: " + self._gates[n].name) + + def _create_basic_graph(self): + for n in self._graph.nodes(): + for pgow in self._prior_gates_on_wire(self._gates, n): + m = next(pgow, -1) + if m != -1: + self._graph.add_edge(m, n) + + def _create_layer_graph(self): + self._create_basic_graph() + + # construct CNOT layers + layers = [] + wire = defaultdict(int) + for n in self._graph.nodes(): + qargs = self.qargs(n) + if self.gate_name(n) == "cx": # consider only CNOTs + i = 1 + max(wire[qargs[0]], wire[qargs[1]]) + wire[qargs[0]] = i + wire[qargs[1]] = i + if len(layers) > i: + layers[i].append(n) + else: + layers.append([n]) + + # Add more edges to basic graph for fixing layers + for i in range(len(layers) - 1): + j = i + 1 + for icx in layers[i]: + for jcx in layers[j]: + self._graph.add_edge(icx, jcx) + + def n_nodes(self) -> int: + """Number of the nodes + Returns: + Number of the nodes in this graph. + """ + return self._graph.__len__() + + def qargs(self, i: int) -> List[Qubit]: + """Qubit arguments of the gate + Args: + i: Index of the gate in the `self._gates` + Returns: + List of qubit arguments. + """ + return self._gates[i].qargs + + def _all_args(self, i: int) -> List[Union[Qubit, Clbit]]: + """Qubit and classical-bit arguments of the gate + Args: + i: Index of the gate in the `self._gates` + Returns: + List of all arguments. + """ + return self._gates[i].qargs + self._gates[i].cargs + + def gate_name(self, i: int) -> str: + """Name of the gate + Args: + i: Index of the gate in the `self._gates` + Returns: + Name of the gate. + """ + return self._gates[i].name + + def head_gates(self) -> Set[int]: + """Gates which can be applicable prior to the other gates + Returns: + Set of indices of the gates. + """ + return frozenset([n for n in self._graph.nodes() if len(self._graph.in_edges(n)) == 0]) + + def gr_successors(self, i: int) -> List[int]: + """Successor gates in Gr (transitive reduction) of the gate + Args: + i: Index of the gate in the `self._gates` + Returns: + Set of indices of the successor gates. + """ + return self._graph.successors(i) + + def descendants(self, i: int) -> Set[int]: + """Descendant gates of gate `i` + Args: + i: Index of the gate in the `self._gates` + Returns: + Set of indices of the descendant gates. + """ + return nx.descendants(self._graph, i) + + def ancestors(self, i: int) -> Set[int]: + """Ancestor gates of gate `i` + Args: + i: Index of the gate in the `self._gates` + Returns: + Set of indices of the ancestor gates. + """ + return nx.ancestors(self._graph, i) + + def gate(self, gidx: int, layout: Layout, physical_qreg: QuantumRegister) -> InstructionContext: + """Convert acting qubits of gate `gidx` from virtual qubits to physical ones. + Args: + gidx: Index of the gate in the `self._gates` + layout: Layout used in conversion + physical_qreg: Register of physical qubit + Returns: + Converted gate with physical qubit. + Raises: + TranspilerError: if virtual qubit of the gate `gidx` is not found in the layout. + """ + gate = copy.deepcopy(self._gates[gidx]) + for i, virtual_qubit in enumerate(gate.qargs): + if virtual_qubit in layout.get_virtual_bits().keys(): + gate.qargs[i] = Qubit(physical_qreg, layout[virtual_qubit]) + else: + raise TranspilerError("virtual_qubit must be in layout") + return gate + + @staticmethod + def remove_redundancy(graph): + """remove redundant edges in DAG (= change `graph` to its transitive reduction) + """ + edges = list(graph.edges()) + for edge in edges: + graph.remove_edge(edge[0], edge[1]) + if not nx.has_path(graph, edge[0], edge[1]): + graph.add_edge(edge[0], edge[1]) + + @staticmethod + def _prior_gates_on_wire(gate_list, toidx): + res = [] + for qarg in gate_list[toidx].qargs: + gates = [] + for i, gate in enumerate(gate_list[:toidx]): + if qarg in gate.qargs: + gates.append(i) + res.append(reversed(gates)) + for carg in gate_list[toidx].cargs: + gates = [] + for i, gate in enumerate(gate_list[:toidx]): + if carg in gate.cargs: + gates.append(i) + res.append(reversed(gates)) + return res + + +if __name__ == '__main__': + pass diff --git a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py index 4e0416d6afc9..7b930287d1f8 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py +++ b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py @@ -1,9 +1,16 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2019. +# +# 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 core algorithm of the flexible-layer swap mapping heuristics proposed in [Itoko et. al. 2019]. @@ -28,12 +35,12 @@ import logging import pprint -from qiskit import QuantumCircuit, QuantumRegister -from qiskit.circuit import Gate +from qiskit.circuit import QuantumCircuit, QuantumRegister, Gate from qiskit.dagcircuit import DAGCircuit from qiskit.extensions.standard import SwapGate -from qiskit.transpiler import CouplingMap, Layout -from qiskit.transpiler import TranspilerError +from qiskit.transpiler.coupling import CouplingMap +from qiskit.transpiler.exceptions import TranspilerError +from qiskit.transpiler.layout import Layout from .ancestors import Ancestors from .dependency_graph import DependencyGraph diff --git a/test/python/transpiler/test_dependency_graph.py b/test/python/transpiler/test_dependency_graph.py index 9c6a6b79b4e9..215ed9bbb3ef 100644 --- a/test/python/transpiler/test_dependency_graph.py +++ b/test/python/transpiler/test_dependency_graph.py @@ -1,139 +1,146 @@ -# -*- coding: utf-8 -*- - -# Copyright 2019, IBM. -# -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. - -"""Tests the DependencyGraph.""" -import unittest - -from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit -from qiskit.test import QiskitTestCase -from qiskit.transpiler.passes.mapping.algorithm import DependencyGraph - - -class TestDependencyGraph(QiskitTestCase): - """Tests the DependencyGraph.""" - - def test_qargs(self): - """ Test for qargs. - """ - qr = QuantumRegister(2, 'q') - cr = ClassicalRegister(2, 'c') - circuit = QuantumCircuit(qr, cr) - circuit.cx(qr[1], qr[0]) - circuit.measure(qr[0], cr[1]) - dep_graph = DependencyGraph(circuit) - self.assertEqual(dep_graph.qargs(0), [(qr, 1), (qr, 0)]) - self.assertEqual(dep_graph.qargs(1), [(qr, 0)]) - - def test_head_gates(self): - """ Test for head gates. - """ - qr = QuantumRegister(4, 'q') - cr = ClassicalRegister(4, 'c') - circuit = QuantumCircuit(qr, cr) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[2], qr[3]) - circuit.cx(qr[1], qr[2]) - circuit.h(qr[0]) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[2], qr[3]) - dep_graph = DependencyGraph(circuit) - self.assertEqual(dep_graph.head_gates(), set([0, 1])) - - def test_gr_successors(self): - """ Test for successor gates of Gr (transitive reduction). - """ - qr = QuantumRegister(4, 'q') - cr = ClassicalRegister(4, 'c') - circuit = QuantumCircuit(qr, cr) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[2], qr[3]) - circuit.cx(qr[1], qr[2]) - circuit.h(qr[0]) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[2], qr[3]) - dep_graph = DependencyGraph(circuit) - self.assertEqual(list(dep_graph.gr_successors(0)), [2, 3]) - self.assertEqual(list(dep_graph.gr_successors(2)), [4, 5]) - self.assertEqual(list(dep_graph.gr_successors(4)), []) - - def test_barrier(self): - """ Test for barriers. - """ - qr = QuantumRegister(4, 'b') - cr = ClassicalRegister(4, 'c') - circuit = QuantumCircuit(qr, cr) - circuit.cx(qr[0], qr[1]) - circuit.barrier(qr) - circuit.cx(qr[1], qr[0]) - circuit.cx(qr[2], qr[3]) - dep_graph = DependencyGraph(circuit) - expected = sorted([(0, 1), (1, 2), (1, 3)]) - self.assertEqual(list(dep_graph._graph.edges()), expected) - - def test_measure(self): - """ Test for measurements. - """ - qr = QuantumRegister(3, 'b') - cr = ClassicalRegister(3, 'c') - circuit = QuantumCircuit(qr, cr) - circuit.h(qr[0]) - circuit.cx(qr[0], qr[1]) - circuit.measure(qr[0], cr[1]) - circuit.cx(qr[1], qr[2]) - circuit.measure(qr[2], cr[1]) # overwrite -> introduce dependency (2, 4) - dep_graph = DependencyGraph(circuit, graph_type="xz_commute") - expected_edges = sorted([(0, 1), (1, 2), (1, 3), (2, 4), (3, 4)]) - self.assertEqual(list(dep_graph._graph.edges()), expected_edges) - - def test_h_gate_in_the_middle(self): - """ Test for a h gate in the middle of a quantum circuit. - """ - qr = QuantumRegister(4, 'q') - cr = ClassicalRegister(4, 'c') - circuit = QuantumCircuit(qr, cr) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[2], qr[3]) - circuit.cx(qr[1], qr[2]) - circuit.h(qr[1]) - circuit.cx(qr[1], qr[0]) - dep_graph = DependencyGraph(circuit, graph_type="xz_commute") - expected = sorted([(0, 2), (1, 2), (2, 3), (3, 4)]) - self.assertEqual(list(dep_graph._graph.edges()), expected) - - def test_rz_gate_in_the_middle(self): - """ Test for a rz gate in the middle of a quantum circuit. - """ - qr = QuantumRegister(4, 'q') - cr = ClassicalRegister(4, 'c') - circuit = QuantumCircuit(qr, cr) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[2], qr[3]) - circuit.cx(qr[1], qr[2]) - circuit.rz(1.0, qr[1]) - circuit.cx(qr[1], qr[0]) - dep_graph = DependencyGraph(circuit, graph_type="xz_commute") - expected = sorted([(0, 2), (1, 2), (0, 3), (0, 4)]) - self.assertEqual(list(dep_graph._graph.edges()), expected) - - def test_rz_gate_in_the_middle_basic_graph_type(self): - """ Test for a h gate in the middle of a quantum circuit with "basic" graph_type. - """ - qr = QuantumRegister(4, 'q') - cr = ClassicalRegister(4, 'c') - circuit = QuantumCircuit(qr, cr) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[2], qr[3]) - circuit.cx(qr[1], qr[2]) - circuit.rz(1.0, qr[1]) - circuit.cx(qr[1], qr[0]) - dep_graph = DependencyGraph(circuit, graph_type="basic") - expected = sorted([(0, 2), (1, 2), (2, 3), (3, 4)]) - self.assertEqual(list(dep_graph._graph.edges()), expected) - - -if __name__ == "__main__": - unittest.main() +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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. + +"""Tests the DependencyGraph.""" +import unittest + +from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit +from qiskit.test import QiskitTestCase +from qiskit.transpiler.passes.mapping.algorithm.dependency_graph import DependencyGraph + + +class TestDependencyGraph(QiskitTestCase): + """Tests the DependencyGraph.""" + + def test_qargs(self): + """ Test for qargs. + """ + qr = QuantumRegister(2, 'q') + cr = ClassicalRegister(2, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.cx(qr[1], qr[0]) + circuit.measure(qr[0], cr[1]) + dep_graph = DependencyGraph(circuit) + self.assertEqual(dep_graph.qargs(0), [(qr, 1), (qr, 0)]) + self.assertEqual(dep_graph.qargs(1), [(qr, 0)]) + + def test_head_gates(self): + """ Test for head gates. + """ + qr = QuantumRegister(4, 'q') + cr = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[2], qr[3]) + circuit.cx(qr[1], qr[2]) + circuit.h(qr[0]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[2], qr[3]) + dep_graph = DependencyGraph(circuit) + self.assertEqual(dep_graph.head_gates(), set([0, 1])) + + def test_gr_successors(self): + """ Test for successor gates of Gr (transitive reduction). + """ + qr = QuantumRegister(4, 'q') + cr = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[2], qr[3]) + circuit.cx(qr[1], qr[2]) + circuit.h(qr[0]) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[2], qr[3]) + dep_graph = DependencyGraph(circuit) + self.assertEqual(list(dep_graph.gr_successors(0)), [2, 3]) + self.assertEqual(list(dep_graph.gr_successors(2)), [4, 5]) + self.assertEqual(list(dep_graph.gr_successors(4)), []) + + def test_barrier(self): + """ Test for barriers. + """ + qr = QuantumRegister(4, 'b') + cr = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.cx(qr[0], qr[1]) + circuit.barrier(qr) + circuit.cx(qr[1], qr[0]) + circuit.cx(qr[2], qr[3]) + dep_graph = DependencyGraph(circuit) + expected = sorted([(0, 1), (1, 2), (1, 3)]) + self.assertEqual(list(dep_graph._graph.edges()), expected) + + def test_measure(self): + """ Test for measurements. + """ + qr = QuantumRegister(3, 'b') + cr = ClassicalRegister(3, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.h(qr[0]) + circuit.cx(qr[0], qr[1]) + circuit.measure(qr[0], cr[1]) + circuit.cx(qr[1], qr[2]) + circuit.measure(qr[2], cr[1]) # overwrite -> introduce dependency (2, 4) + dep_graph = DependencyGraph(circuit, graph_type="xz_commute") + expected_edges = sorted([(0, 1), (1, 2), (1, 3), (2, 4), (3, 4)]) + self.assertEqual(list(dep_graph._graph.edges()), expected_edges) + + def test_h_gate_in_the_middle(self): + """ Test for a h gate in the middle of a quantum circuit. + """ + qr = QuantumRegister(4, 'q') + cr = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[2], qr[3]) + circuit.cx(qr[1], qr[2]) + circuit.h(qr[1]) + circuit.cx(qr[1], qr[0]) + dep_graph = DependencyGraph(circuit, graph_type="xz_commute") + expected = sorted([(0, 2), (1, 2), (2, 3), (3, 4)]) + self.assertEqual(list(dep_graph._graph.edges()), expected) + + def test_rz_gate_in_the_middle(self): + """ Test for a rz gate in the middle of a quantum circuit. + """ + qr = QuantumRegister(4, 'q') + cr = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[2], qr[3]) + circuit.cx(qr[1], qr[2]) + circuit.rz(1.0, qr[1]) + circuit.cx(qr[1], qr[0]) + dep_graph = DependencyGraph(circuit, graph_type="xz_commute") + expected = sorted([(0, 2), (1, 2), (0, 3), (0, 4)]) + self.assertEqual(list(dep_graph._graph.edges()), expected) + + def test_rz_gate_in_the_middle_basic_graph_type(self): + """ Test for a h gate in the middle of a quantum circuit with "basic" graph_type. + """ + qr = QuantumRegister(4, 'q') + cr = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[2], qr[3]) + circuit.cx(qr[1], qr[2]) + circuit.rz(1.0, qr[1]) + circuit.cx(qr[1], qr[0]) + dep_graph = DependencyGraph(circuit, graph_type="basic") + expected = sorted([(0, 2), (1, 2), (2, 3), (3, 4)]) + self.assertEqual(list(dep_graph._graph.edges()), expected) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/python/transpiler/test_flexlayer_heuristics.py b/test/python/transpiler/test_flexlayer_heuristics.py index 9c0e1eccdf05..dc87816ef22f 100644 --- a/test/python/transpiler/test_flexlayer_heuristics.py +++ b/test/python/transpiler/test_flexlayer_heuristics.py @@ -1,175 +1,183 @@ -# -*- coding: utf-8 -*- - -# Copyright 2019, IBM. -# -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. - -"""Tests for FlexlayerHeuristics.""" - -import unittest - -from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister -from qiskit.converters import circuit_to_dag, dag_to_circuit -from qiskit.mapper import CouplingMap, Layout -from qiskit.transpiler.passes.mapping.algorithm import DependencyGraph -from qiskit.transpiler.passes.mapping.algorithm import FlexlayerHeuristics, remove_head_swaps - - -class TestFlexlayerHeuristics(unittest.TestCase): - """Tests for FlexlayerHeuristics.""" - - @unittest.skip("due to a bug in DAGCircuit.__eq__()") - def test_search_4qcx4h1(self): - """Test for 4 cx gates and 1 h gate in a 4q circuit. - """ - qr = QuantumRegister(4, 'q') - cr = ClassicalRegister(4, 'c') - circuit = QuantumCircuit(qr, cr) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[2], qr[3]) - circuit.cx(qr[1], qr[2]) - circuit.h(qr[1]) - circuit.cx(qr[1], qr[0]) - circuit.barrier() - for i in range(4): - circuit.measure(qr[i], cr[i]) - dep_graph = DependencyGraph(circuit, graph_type="basic") - coupling = CouplingMap([[0, 1], [1, 2], [1, 3]]) # {0: [1], 1: [2, 3]} - initial_layout = Layout.generate_trivial_layout(qr) - algo = FlexlayerHeuristics(circuit, dep_graph, coupling, initial_layout) - actual_dag, layout = algo.search() - - expected = QuantumCircuit(qr, cr) - expected.cx(qr[0], qr[1]) - expected.swap(qr[1], qr[2]) - expected.cx(qr[1], qr[3]) - expected.cx(qr[2], qr[1]) - expected.h(qr[2]) - expected.swap(qr[0], qr[1]) - expected.cx(qr[2], qr[1]) - expected.barrier() - expected.measure(qr[1], cr[0]) - expected.measure(qr[2], cr[1]) - expected.measure(qr[0], cr[2]) - expected.measure(qr[3], cr[3]) - expected_layout = initial_layout - from qiskit.tools.visualization import dag_drawer - dag_drawer(actual_dag) - dag_drawer(circuit_to_dag(expected)) - print(dag_to_circuit(actual_dag).draw()) - print(expected.draw()) - self.assertEqual(actual_dag, circuit_to_dag(expected)) - self.assertEqual(layout, expected_layout) - - @unittest.skip("TODO: Change to use DAGCircuit and Layout") - def test_search_multi_creg(self): - """Test for multiple ClassicalRegisters. - """ - qr = QuantumRegister(4, 'b') - cr1 = ClassicalRegister(2, 'c') - cr2 = ClassicalRegister(2, 'd') - circuit = QuantumCircuit(qr, cr1, cr2) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[2], qr[3]) - circuit.cx(qr[1], qr[2]) - circuit.h(qr[1]) - circuit.cx(qr[1], qr[0]) - circuit.barrier(qr) - circuit.measure(qr[0], cr1[0]) - circuit.measure(qr[1], cr1[1]) - circuit.measure(qr[2], cr2[0]) - circuit.measure(qr[3], cr2[1]) - dep_graph = DependencyGraph(circuit, graph_type="basic") - coupling = CouplingMap([[0, 1], [1, 2], [1, 3]]) # {0: [1], 1: [2, 3]} - initial_layout = Layout.generate_trivial_layout(qr) - algo = FlexlayerHeuristics(circuit, dep_graph, coupling, initial_layout) - qc, layout = algo.search() - actual_measures = [s for s in qc.qasm().split('\n') if s.startswith("measure")] - expected_measures = [] - expected_measures.append("measure q[0] -> d[0];") - expected_measures.append("measure q[1] -> c[0];") - expected_measures.append("measure q[2] -> c[1];") - expected_measures.append("measure q[3] -> d[1];") - expected_layout = initial_layout - self.assertEqual(sorted(actual_measures), expected_measures) - self.assertEqual(layout, expected_layout) - - @unittest.skip("TODO: Change to use DAGCircuit and Layout") - def test_remove_head_swaps(self): - """Test for removing unnecessary swap gates from qc by changing initial_layout. - """ - qr = QuantumRegister(4, 'q') - cr = ClassicalRegister(4, 'c') - circuit = QuantumCircuit(qr, cr) - circuit.cx(qr[0], qr[1]) - circuit.u1(1, qr[2]) - circuit.swap(qr[2], qr[3]) - circuit.cx(qr[1], qr[2]) - for i in range(4): - circuit.measure(qr[i], cr[i]) - initial_layout = {('q', i): ('q', i) for i in range(4)} - resqc, layout = remove_head_swaps(circuit, initial_layout) - actual_qasm = ''.join([s for s in resqc.qasm().split('\n') if not s.startswith("measure")]) - actual_measure = sorted([s for s in resqc.qasm().split('\n') if s.startswith("measure")]) - header = 'OPENQASM 2.0;include "qelib1.inc";qreg q[4];creg c[4];' - expected_qasm = header + "cx q[0],q[1];u1(1) q[3];cx q[1],q[2];" - expected_measure = ["measure q[%d] -> c[%d];" % (i, i) for i in range(4)] - expected_layout = {('q', 0): ('q', 0), ('q', 1): ('q', 1), ('q', 3): ('q', 2), - ('q', 2): ('q', 3)} - self.assertEqual(actual_qasm, expected_qasm) - self.assertEqual(actual_measure, expected_measure) - self.assertEqual(layout, expected_layout) - - # @unittest.skip("WIP") - # def test_search_rd32_v0_67(self): - # qp = QuantumProgram() - # qasm_text = 'OPENQASM 2.0;\ - # include "qelib1.inc";\ - # gate peres a,b,c {ccx a, b, c; cx a, b;}\ - # qreg q[4];\ - # creg c[4];\ - # peres q[0], q[1], q[3];\ - # peres q[1], q[2], q[3];\ - # barrier q;\ - # measure q->c;' - # qc = load_qasm_string(qasm_text, basis_gates="u1,u2,u3,cx,id") - # dg = DependencyGraph(qc, graph_type="basic") - # coupling = Coupling({1: [0], 2: [0, 1, 4], 3: [2, 4]}) - # initial_layout = {('q', 0): ('q', 1), ('q', 1): ('q', 0), ('q', 2): ('q', 2), - # ('q', 3): ('q', 4)} - # algo = FlexlayerHeuristics(qc, dg, coupling, initial_layout) - # dag, layout = algo.search() - - @unittest.skip("TODO: Change to use DAGCircuit and Layout") - def test_remove_head_swaps_and_remain_middle_swaps(self): - """Test for only head swaps are removed, and middle swaps are remained properly. - """ - qr = QuantumRegister(6, 'q') - cr = ClassicalRegister(6, 'c') - circuit = QuantumCircuit(qr, cr) - circuit.u1(1, qr[4]) - circuit.h(qr[3]) - circuit.swap(qr[4], qr[5]) - circuit.cx(qr[3], qr[4]) - circuit.swap(qr[3], qr[4]) - circuit.u1(1, qr[1]) - circuit.h(qr[0]) - circuit.swap(qr[0], qr[1]) - circuit.swap(qr[1], qr[2]) - circuit.swap(qr[2], qr[3]) - circuit.cx(qr[3], qr[4]) - initial_layout = {('b', i): ('q', i) for i in range(6)} - resqc, layout = remove_head_swaps(circuit, initial_layout) - actual_qasm = ''.join([s for s in resqc.qasm().split('\n')]) - header = 'OPENQASM 2.0;include "qelib1.inc";qreg q[6];creg c[6];' - expected_qasm = header + "u1(1) q[5];h q[3];cx q[3],q[4];swap q[3],q[4];u1(1) q[0];" \ - "h q[2];swap q[2],q[3];cx q[3],q[4];" - expected_layout = {('b', 0): ('q', 2), ('b', 1): ('q', 0), ('b', 2): ('q', 1), - ('b', 3): ('q', 3), ('b', 4): ('q', 5), ('b', 5): ('q', 4)} - self.assertEqual(actual_qasm, expected_qasm) - self.assertEqual(layout, expected_layout) - - -if __name__ == "__main__": - unittest.main() +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017. +# +# 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. + +"""Tests for FlexlayerHeuristics.""" + +import unittest + +from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister +from qiskit.converters import circuit_to_dag, dag_to_circuit +from qiskit.transpiler import CouplingMap, Layout +from qiskit.transpiler.passes.mapping.algorithm.dependency_graph import DependencyGraph +from qiskit.transpiler.passes.mapping.algorithm.flexlayer_heuristics import FlexlayerHeuristics +from qiskit.transpiler.passes.mapping.algorithm.flexlayer_heuristics import remove_head_swaps + + +class TestFlexlayerHeuristics(unittest.TestCase): + """Tests for FlexlayerHeuristics.""" + + @unittest.skip("due to a bug in DAGCircuit.__eq__()") + def test_search_4qcx4h1(self): + """Test for 4 cx gates and 1 h gate in a 4q circuit. + """ + qr = QuantumRegister(4, 'q') + cr = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[2], qr[3]) + circuit.cx(qr[1], qr[2]) + circuit.h(qr[1]) + circuit.cx(qr[1], qr[0]) + circuit.barrier() + for i in range(4): + circuit.measure(qr[i], cr[i]) + dep_graph = DependencyGraph(circuit, graph_type="basic") + coupling = CouplingMap([[0, 1], [1, 2], [1, 3]]) # {0: [1], 1: [2, 3]} + initial_layout = Layout.generate_trivial_layout(qr) + algo = FlexlayerHeuristics(circuit, dep_graph, coupling, initial_layout) + actual_dag, layout = algo.search() + + expected = QuantumCircuit(qr, cr) + expected.cx(qr[0], qr[1]) + expected.swap(qr[1], qr[2]) + expected.cx(qr[1], qr[3]) + expected.cx(qr[2], qr[1]) + expected.h(qr[2]) + expected.swap(qr[0], qr[1]) + expected.cx(qr[2], qr[1]) + expected.barrier() + expected.measure(qr[1], cr[0]) + expected.measure(qr[2], cr[1]) + expected.measure(qr[0], cr[2]) + expected.measure(qr[3], cr[3]) + expected_layout = initial_layout + from qiskit.tools.visualization import dag_drawer + dag_drawer(actual_dag) + dag_drawer(circuit_to_dag(expected)) + print(dag_to_circuit(actual_dag).draw()) + print(expected.draw()) + self.assertEqual(actual_dag, circuit_to_dag(expected)) + self.assertEqual(layout, expected_layout) + + @unittest.skip("TODO: Change to use DAGCircuit and Layout") + def test_search_multi_creg(self): + """Test for multiple ClassicalRegisters. + """ + qr = QuantumRegister(4, 'q') + cr1 = ClassicalRegister(2, 'c') + cr2 = ClassicalRegister(2, 'd') + circuit = QuantumCircuit(qr, cr1, cr2) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[2], qr[3]) + circuit.cx(qr[1], qr[2]) + circuit.h(qr[1]) + circuit.cx(qr[1], qr[0]) + circuit.barrier(qr) + circuit.measure(qr[0], cr1[0]) + circuit.measure(qr[1], cr1[1]) + circuit.measure(qr[2], cr2[0]) + circuit.measure(qr[3], cr2[1]) + dep_graph = DependencyGraph(circuit, graph_type="basic") + coupling = CouplingMap([[0, 1], [1, 2], [1, 3]]) # {0: [1], 1: [2, 3]} + initial_layout = Layout.generate_trivial_layout(qr) + algo = FlexlayerHeuristics(circuit, dep_graph, coupling, initial_layout) + qc, layout = algo.search() + actual_measures = [s for s in qc.qasm().split('\n') if s.startswith("measure")] + expected_measures = [] + expected_measures.append("measure q[0] -> d[0];") + expected_measures.append("measure q[1] -> c[0];") + expected_measures.append("measure q[2] -> c[1];") + expected_measures.append("measure q[3] -> d[1];") + expected_layout = initial_layout + self.assertEqual(sorted(actual_measures), expected_measures) + self.assertEqual(layout, expected_layout) + + @unittest.skip("TODO: Change to use DAGCircuit and Layout") + def test_remove_head_swaps(self): + """Test for removing unnecessary swap gates from qc by changing initial_layout. + """ + qr = QuantumRegister(4, 'q') + cr = ClassicalRegister(4, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.cx(qr[0], qr[1]) + circuit.u1(1, qr[2]) + circuit.swap(qr[2], qr[3]) + circuit.cx(qr[1], qr[2]) + for i in range(4): + circuit.measure(qr[i], cr[i]) + initial_layout = {('q', i): ('q', i) for i in range(4)} + resqc, layout = remove_head_swaps(circuit, initial_layout) + actual_qasm = ''.join([s for s in resqc.qasm().split('\n') if not s.startswith("measure")]) + actual_measure = sorted([s for s in resqc.qasm().split('\n') if s.startswith("measure")]) + header = 'OPENQASM 2.0;include "qelib1.inc";qreg q[4];creg c[4];' + expected_qasm = header + "cx q[0],q[1];u1(1) q[3];cx q[1],q[2];" + expected_measure = ["measure q[%d] -> c[%d];" % (i, i) for i in range(4)] + expected_layout = {('q', 0): ('q', 0), ('q', 1): ('q', 1), ('q', 3): ('q', 2), + ('q', 2): ('q', 3)} + self.assertEqual(actual_qasm, expected_qasm) + self.assertEqual(actual_measure, expected_measure) + self.assertEqual(layout, expected_layout) + + # @unittest.skip("WIP") + # def test_search_rd32_v0_67(self): + # qp = QuantumProgram() + # qasm_text = 'OPENQASM 2.0;\ + # include "qelib1.inc";\ + # gate peres a,b,c {ccx a, b, c; cx a, b;}\ + # qreg q[4];\ + # creg c[4];\ + # peres q[0], q[1], q[3];\ + # peres q[1], q[2], q[3];\ + # barrier q;\ + # measure q->c;' + # qc = load_qasm_string(qasm_text, basis_gates="u1,u2,u3,cx,id") + # dg = DependencyGraph(qc, graph_type="basic") + # coupling = Coupling({1: [0], 2: [0, 1, 4], 3: [2, 4]}) + # initial_layout = {('q', 0): ('q', 1), ('q', 1): ('q', 0), ('q', 2): ('q', 2), + # ('q', 3): ('q', 4)} + # algo = FlexlayerHeuristics(qc, dg, coupling, initial_layout) + # dag, layout = algo.search() + + @unittest.skip("TODO: Change to use DAGCircuit and Layout") + def test_remove_head_swaps_and_remain_middle_swaps(self): + """Test for only head swaps are removed, and middle swaps are remained properly. + """ + qr = QuantumRegister(6, 'q') + cr = ClassicalRegister(6, 'c') + circuit = QuantumCircuit(qr, cr) + circuit.u1(1, qr[4]) + circuit.h(qr[3]) + circuit.swap(qr[4], qr[5]) + circuit.cx(qr[3], qr[4]) + circuit.swap(qr[3], qr[4]) + circuit.u1(1, qr[1]) + circuit.h(qr[0]) + circuit.swap(qr[0], qr[1]) + circuit.swap(qr[1], qr[2]) + circuit.swap(qr[2], qr[3]) + circuit.cx(qr[3], qr[4]) + initial_layout = {('b', i): ('q', i) for i in range(6)} + resqc, layout = remove_head_swaps(circuit, initial_layout) + actual_qasm = ''.join([s for s in resqc.qasm().split('\n')]) + header = 'OPENQASM 2.0;include "qelib1.inc";qreg q[6];creg c[6];' + expected_qasm = header + "u1(1) q[5];h q[3];cx q[3],q[4];swap q[3],q[4];u1(1) q[0];" \ + "h q[2];swap q[2],q[3];cx q[3],q[4];" + expected_layout = {('b', 0): ('q', 2), ('b', 1): ('q', 0), ('b', 2): ('q', 1), + ('b', 3): ('q', 3), ('b', 4): ('q', 5), ('b', 5): ('q', 4)} + self.assertEqual(actual_qasm, expected_qasm) + self.assertEqual(layout, expected_layout) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/python/transpiler/test_flexlayer_swap.py b/test/python/transpiler/test_flexlayer_swap.py index da3c5a411b47..1686ef22d183 100644 --- a/test/python/transpiler/test_flexlayer_swap.py +++ b/test/python/transpiler/test_flexlayer_swap.py @@ -1,19 +1,26 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2019. +# +# 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 FlexlayerSwap pass""" import unittest -from qiskit.transpiler.passes import FlexlayerSwap from qiskit import QuantumRegister, QuantumCircuit from qiskit.converters import circuit_to_dag -from qiskit.mapper import CouplingMap, Layout from qiskit.test import QiskitTestCase +from qiskit.transpiler import CouplingMap +from qiskit.transpiler.passes import FlexlayerSwap class TestFlexlayerSwap(QiskitTestCase): @@ -31,36 +38,11 @@ def test_trivial_case(self): """ coupling = CouplingMap([[0, 1], [0, 2]]) - qr = QuantumRegister(3, 'qr') + qr = QuantumRegister(3, name='q') circuit = QuantumCircuit(qr) - circuit.cx(qr[0], qr[1]) + circuit.cx(qr[1], qr[0]) circuit.h(qr[0]) - circuit.cx(qr[0], qr[2]) - - dag = circuit_to_dag(circuit) - pass_ = FlexlayerSwap(coupling) - after = pass_.run(dag) - - self.assertEqual(dag, after) - - def test_trivial_in_same_layer(self): - """ No need to have any swap, two CXs distance 1 to each other, in the same layer - q0:--(+)-- - | - q1:---.--- - - q2:--(+)-- - | - q3:---.--- - - CouplingMap map: [0]--[1]--[2]--[3] - """ - coupling = CouplingMap([[0, 1], [1, 2], [2, 3]]) - - qr = QuantumRegister(4, 'qr') - circuit = QuantumCircuit(qr) - circuit.cx(qr[2], qr[3]) - circuit.cx(qr[0], qr[1]) + circuit.cx(qr[2], qr[0]) dag = circuit_to_dag(circuit) pass_ = FlexlayerSwap(coupling) @@ -72,22 +54,22 @@ def test_a_single_swap(self): """ Adding a swap q0:------- - q1:--(+)-- + q1:---.--- | - q2:---.--- + q2:--(+)-- CouplingMap map: [1]--[0]--[2] - q0:--X---.--- + q0:--X--(+)-- | | - q1:--X---|--- - | - q2:-----(+)-- + q1:--|---.--- + | + q2:--x------- """ coupling = CouplingMap([[0, 1], [0, 2]]) - qr = QuantumRegister(3, 'qr') + qr = QuantumRegister(3, name='q') circuit = QuantumCircuit(qr) circuit.cx(qr[1], qr[2]) dag = circuit_to_dag(circuit) @@ -101,40 +83,6 @@ def test_a_single_swap(self): self.assertEqual(circuit_to_dag(expected), after) - def test_keep_layout(self): - """After a swap, the following gates also change the wires. - qr0:---.---[H]-- - | - qr1:---|-------- - | - qr2:--(+)------- - - CouplingMap map: [0]--[1]--[2] - - qr0:--X----------- - | - qr1:--X---.--[H]-- - | - qr2:-----(+)------ - """ - coupling = CouplingMap([[1, 0], [1, 2]]) - - qr = QuantumRegister(3, 'qr') - circuit = QuantumCircuit(qr) - circuit.cx(qr[0], qr[2]) - circuit.h(qr[0]) - dag = circuit_to_dag(circuit) - - expected = QuantumCircuit(qr) - expected.swap(qr[1], qr[0]) - expected.cx(qr[1], qr[2]) - expected.h(qr[1]) - - pass_ = FlexlayerSwap(coupling) - after = pass_.run(dag) - - self.assertEqual(circuit_to_dag(expected), after) - def test_far_swap(self): """ A far swap that affects coming CXs. qr0:--(+)---.-- @@ -158,7 +106,7 @@ def test_far_swap(self): """ coupling = CouplingMap([[0, 1], [1, 2], [2, 3]]) - qr = QuantumRegister(4, 'qr') + qr = QuantumRegister(4, name='q') circuit = QuantumCircuit(qr) circuit.cx(qr[0], qr[3]) circuit.cx(qr[3], qr[0]) @@ -175,257 +123,6 @@ def test_far_swap(self): self.assertEqual(circuit_to_dag(expected), after) - def test_far_swap_with_gate_the_front(self): - """ A far swap with a gate in the front. - qr0:------(+)-- - | - qr1:-------|--- - | - qr2:-------|--- - | - qr3:--[H]--.--- - - CouplingMap map: [0]--[1]--[2]--[3] - - q0:------X---------- - | - q1:------X--X------- - | - q2:---------X--(+)-- - | - q3:-[H]---------.--- - - """ - coupling = CouplingMap([[0, 1], [1, 2], [2, 3]]) - - qr = QuantumRegister(4, 'qr') # virtual qubit - circuit = QuantumCircuit(qr) - circuit.h(qr[3]) - circuit.cx(qr[3], qr[0]) - dag = circuit_to_dag(circuit) - - expected = QuantumCircuit(qr) - expected.h(qr[3]) - expected.swap(qr[0], qr[1]) - expected.swap(qr[1], qr[2]) - expected.cx(qr[3], qr[2]) - - pass_ = FlexlayerSwap(coupling) - after = pass_.run(dag) - - self.assertEqual(circuit_to_dag(expected), after) - - def test_initial_layout(self): - """ Using an initial_layout - 0:q1:--(+)-- - | - 1:q0:---|--- - | - 2:q2:---.--- - - CouplingMap map: [0]--[1]--[2] - - 0:q1:--X------- - | - 1:q0:--X---.--- - | - 2:q2:-----(+)-- - - """ - coupling = CouplingMap([[0, 1], [1, 2]]) - - qr = QuantumRegister(3, 'q') - circuit = QuantumCircuit(qr) - circuit.cx(qr[1], qr[2]) - dag = circuit_to_dag(circuit) - layout = Layout([(qr, 1), (qr, 0), (qr, 2)]) - - expected = QuantumCircuit(qr) - expected.swap(qr[1], qr[0]) - expected.cx(qr[0], qr[2]) - - pass_ = FlexlayerSwap(coupling, initial_layout=layout) - after = pass_.run(dag) - - self.assertEqual(circuit_to_dag(expected), after) - - def test_initial_layout_in_different_qregs(self): - """ Using an initial_layout, and with several qregs - 0:q1_0:--(+)-- - | - 1:q0_0:---|--- - | - 2:q2_0:---.--- - - CouplingMap map: [0]--[1]--[2] - - 0:q1_0:--X------- - | - 1:q0_0:--X---.--- - | - 2:q2_0:-----(+)-- - """ - coupling = CouplingMap([[0, 1], [1, 2]]) - - qr0 = QuantumRegister(1, 'q0') - qr1 = QuantumRegister(1, 'q1') - qr2 = QuantumRegister(1, 'q2') - circuit = QuantumCircuit(qr0, qr1, qr2) - circuit.cx(qr1[0], qr2[0]) - dag = circuit_to_dag(circuit) - layout = Layout([(qr1, 0), (qr0, 0), (qr2, 0)]) - - expected = QuantumCircuit(qr0, qr1, qr2) - expected.swap(qr1[0], qr0[0]) - expected.cx(qr0[0], qr2[0]) - - pass_ = FlexlayerSwap(coupling, initial_layout=layout) - after = pass_.run(dag) - - self.assertEqual(circuit_to_dag(expected), after) - - # def test_flexlayer_swap_doesnt_modify_mapped_circuit(self): - # """Test that lookahead mapper is idempotent. - # - # It should not modify a circuit which is already compatible with the - # coupling map, and can be applied repeatedly without modifying the circuit. - # """ - # - # qr = QuantumRegister(3, name='q') - # circuit = QuantumCircuit(qr) - # circuit.cx(qr[0], qr[2]) - # circuit.cx(qr[0], qr[1]) - # original_dag = circuit_to_dag(circuit) - # - # # Create coupling map which contains all two-qubit gates in the circuit. - # coupling_map = CouplingMap([[0, 1], [0, 2]]) - # - # pass_manager = PassManager() - # pass_manager.append(FlexlayerSwap(coupling_map)) - # mapped_dag = transpile_dag(original_dag, pass_manager=pass_manager) - # - # self.assertEqual(original_dag, mapped_dag) - # - # second_pass_manager = PassManager() - # second_pass_manager.append(FlexlayerSwap(coupling_map)) - # remapped_dag = transpile_dag(mapped_dag, pass_manager=second_pass_manager) - # - # self.assertEqual(mapped_dag, remapped_dag) - # - # def test_flexlayer_swap_should_add_a_single_swap(self): - # """Test that LookaheadSwap will insert a SWAP to match layout. - # - # For a single cx gate which is not available in the current layout, test - # that the mapper inserts a single swap to enable the gate. - # """ - # - # qr = QuantumRegister(3) - # circuit = QuantumCircuit(qr) - # circuit.cx(qr[0], qr[2]) - # dag_circuit = circuit_to_dag(circuit) - # - # coupling_map = CouplingMap([[0, 1], [1, 2]]) - # - # pass_manager = PassManager() - # pass_manager.append([FlexlayerSwap(coupling_map)]) - # mapped_dag = transpile_dag(dag_circuit, pass_manager=pass_manager) - # - # self.assertEqual(mapped_dag.count_ops().get('swap', 0), - # dag_circuit.count_ops().get('swap', 0) + 1) - # - # def test_flexlayer_swap_finds_minimal_swap_solution(self): - # """Of many valid SWAPs, test that LookaheadSwap finds the cheapest path. - # - # For a two CNOT circuit: cx q[0],q[2]; cx q[0],q[1] - # on the initial layout: qN -> qN - # (At least) two solutions exist: - # - SWAP q[0],[1], cx q[0],q[2], cx q[0],q[1] - # - SWAP q[1],[2], cx q[0],q[2], SWAP q[1],q[2], cx q[0],q[1] - # - # Verify that we find the first solution, as it requires fewer SWAPs. - # """ - # - # qr = QuantumRegister(3) - # circuit = QuantumCircuit(qr) - # circuit.cx(qr[0], qr[2]) - # circuit.cx(qr[0], qr[1]) - # - # dag_circuit = circuit_to_dag(circuit) - # - # coupling_map = CouplingMap([[0, 1], [1, 2]]) - # - # pass_manager = PassManager() - # pass_manager.append([FlexlayerSwap(coupling_map)]) - # mapped_dag = transpile_dag(dag_circuit, pass_manager=pass_manager) - # - # self.assertEqual(mapped_dag.count_ops().get('swap', 0), - # dag_circuit.count_ops().get('swap', 0) + 1) - # - # def test_flexlayer_swap_maps_measurements(self): - # """Verify measurement nodes are updated to map correct cregs to re-mapped qregs. - # - # Create a circuit with measures on q0 and q2, following a swap between q0 and q2. - # Since that swap is not in the coupling, one of the two will be required to move. - # Verify that the mapped measure corresponds to one of the two possible layouts following - # the swap. - # - # """ - # - # qr = QuantumRegister(3) - # cr = ClassicalRegister(2) - # circuit = QuantumCircuit(qr, cr) - # - # circuit.cx(qr[0], qr[2]) - # circuit.measure(qr[0], cr[0]) - # circuit.measure(qr[2], cr[1]) - # - # dag_circuit = circuit_to_dag(circuit) - # - # coupling_map = CouplingMap([[0, 1], [1, 2]]) - # - # pass_manager = PassManager() - # pass_manager.append([FlexlayerSwap(coupling_map)]) - # mapped_dag = transpile_dag(dag_circuit, pass_manager=pass_manager) - # - # mapped_measure_qargs = set(mapped_dag.multi_graph.nodes(data=True)[op]['qargs'][0] - # for op in mapped_dag.get_named_nodes('measure')) - # - # self.assertIn(mapped_measure_qargs, - # [set(((QuantumRegister(3, 'q'), 0), (QuantumRegister(3, 'q'), 1))), - # set(((QuantumRegister(3, 'q'), 1), (QuantumRegister(3, 'q'), 2)))]) - # - # def test_flexlayer_swap_maps_barriers(self): - # """Verify barrier nodes are updated to re-mapped qregs. - # - # Create a circuit with a barrier on q0 and q2, following a swap between q0 and q2. - # Since that swap is not in the coupling, one of the two will be required to move. - # Verify that the mapped barrier corresponds to one of the two possible layouts following - # the swap. - # - # """ - # - # qr = QuantumRegister(3) - # cr = ClassicalRegister(2) - # circuit = QuantumCircuit(qr, cr) - # - # circuit.cx(qr[0], qr[2]) - # circuit.barrier(qr[0], qr[2]) - # - # dag_circuit = circuit_to_dag(circuit) - # - # coupling_map = CouplingMap([[0, 1], [1, 2]]) - # - # pass_manager = PassManager() - # pass_manager.append([FlexlayerSwap(coupling_map)]) - # mapped_dag = transpile_dag(dag_circuit, pass_manager=pass_manager) - # - # mapped_barrier_qargs = [set(mapped_dag.multi_graph.nodes(data=True)[op]['qargs']) - # for op in mapped_dag.get_named_nodes('barrier')][0] - # - # self.assertIn(mapped_barrier_qargs, - # [set(((QuantumRegister(3, 'q'), 0), (QuantumRegister(3, 'q'), 1))), - # set(((QuantumRegister(3, 'q'), 1), (QuantumRegister(3, 'q'), 2)))]) - if __name__ == '__main__': unittest.main() From 4470ebeb446f5b27362b2dc0ac249693b018cdf8 Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Wed, 10 Jul 2019 11:46:01 +0900 Subject: [PATCH 16/40] increase human readability of print --- qiskit/transpiler/layout.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit/transpiler/layout.py b/qiskit/transpiler/layout.py index 49a1625004fa..ba2983fd2759 100644 --- a/qiskit/transpiler/layout.py +++ b/qiskit/transpiler/layout.py @@ -44,6 +44,7 @@ def __repr__(self): for key, val in self._p2v.items(): str_list.append("{k}: {v},".format(k=key, v=val)) if str_list: + str_list.sort() str_list[-1] = str_list[-1][:-1] return "Layout({\n" + "\n".join(str_list) + "\n})" From cf85e93a28d54313e8bc1b1dac6553394df764a9 Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Wed, 10 Jul 2019 11:46:20 +0900 Subject: [PATCH 17/40] remove unused lines --- .../mapping/algorithm/flexlayer_heuristics.py | 83 +------------------ 1 file changed, 3 insertions(+), 80 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py index 7b930287d1f8..af53cb1a8dfd 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py +++ b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py @@ -35,7 +35,7 @@ import logging import pprint -from qiskit.circuit import QuantumCircuit, QuantumRegister, Gate +from qiskit.circuit import QuantumCircuit, QuantumRegister from qiskit.dagcircuit import DAGCircuit from qiskit.extensions.standard import SwapGate from qiskit.transpiler.coupling import CouplingMap @@ -73,7 +73,6 @@ def __init__(self, self._qubit_count_validity_check() if len(initial_layout.get_virtual_bits()) != len(initial_layout.get_physical_bits()): raise TranspilerError("FlexlayerHeuristics assumes #virtual-qubits == #physical-qubits") - # self._add_ancilla_qubits() self._lookahead_depth = lookahead_depth self._decay_rate = decay_rate @@ -84,7 +83,7 @@ def search(self) -> (DAGCircuit, Layout): """ Returns: - Mapped physical circuit (DAGCircuit) and initial qubit layout (Layout) + Mapped physical circuit (DAGCircuit) and last qubit layout (Layout) Raises: TranspilerError: if found too many loops (maybe unexpected infinite loop). """ @@ -169,11 +168,7 @@ def search(self) -> (DAGCircuit, Layout): if k == max_n_iteration: raise TranspilerError("Unknown error: #iteration reached max_n_iteration") - reslayout = self._initial_layout - # resqc = dag_to_circuit(new_dag) - # resqc, reslayout = remove_head_swaps(resqc, self._initial_layout) - - return new_dag, reslayout + return new_dag, layout def _next_blocking_gates_from(self, blocking_gates, layout): """next blocking gates from blocking_gates for layout with finding applicable gates (dones) @@ -300,10 +295,6 @@ def _create_empty_dagcircuit(self, physical_qreg): new_dag.add_qreg(physical_qreg) for creg in self._qc.cregs: new_dag.add_creg(creg) - # new_dag.add_basis_element('swap', 2, 0, 0) - # for name, data in self._qc.definitions.items(): - # new_dag.add_basis_element(name, data["n_bits"], 0, data["n_args"]) - # new_dag.add_gate_data(name, data) return new_dag @@ -439,71 +430,3 @@ def _after_swap_cost(self, source1, source2, target): if abs(dist1 - dits2) > 1: raise TranspilerError("Invalid shortest paths on coupling graph") return dits2 - dist1 - - -def _qargs(gate): - return [ridx for (reg, ridx) in gate.qargs] - - -def remove_head_swaps(qc: QuantumCircuit, - initial_layout: Layout) -> (QuantumCircuit, Layout): - """remove unnecessary swap gates from qc by changing initial_layout - assume all of the gates in qc are expanded to one-qubit gate, cx, swap - """ - - rev_new_layout = {v: k for k, v in initial_layout.items()} - cx_seen = collections.defaultdict(bool) # phisical qubit -> seen first cx or not - to_be_removed = [] - for i, gate in enumerate(qc.data): - if len(gate.qargs) > 1: - qargs = _qargs(gate) - if gate.name == "swap": - if (not cx_seen[qargs[0]]) and (not cx_seen[qargs[1]]): - # swap before the first cx -> update rev_layout and do not output swap to qasm - rev_new_layout[qargs[0]], rev_new_layout[qargs[1]] = rev_new_layout[qargs[1]], \ - rev_new_layout[qargs[0]] - to_be_removed.append(i) - else: - # must change flag! because left swap = cx - cx_seen[qargs[0]] = True - cx_seen[qargs[1]] = True - else: - for qarg in qargs: - cx_seen[qarg] = True - - logger.debug("remove_head_swaps: n_removed = %d", len(to_be_removed)) - - resqc = copy.deepcopy(qc) - new_initial_layout = {v: k for k, v in rev_new_layout.items()} - new_layout = new_initial_layout - rev_org_layout = {v: k for k, v in initial_layout.items()} - - if to_be_removed: - qregs = resqc.qregs - for i, gate in enumerate(resqc.data[:1 + to_be_removed[-1]]): - qargs = _qargs(gate) - if gate.name == "swap": - rev_org_layout[qargs[0]], rev_org_layout[qargs[1]] = rev_org_layout[qargs[1]], \ - rev_org_layout[qargs[0]] - if i not in to_be_removed: - rev_new_layout[qargs[0]], rev_new_layout[qargs[1]] = rev_new_layout[qargs[1]], \ - rev_new_layout[qargs[0]] - new_layout = {v: k for k, v in rev_new_layout.items()} - else: - _change_qargs(gate, rev_org_layout, new_layout, qregs) - - resqc.data = [g for i, g in enumerate(resqc.data) if i not in to_be_removed] - - return resqc, new_initial_layout - - -def _change_qargs(gate: Gate, rev_org_layout: dict, new_layout: dict, qregs: dict): - if gate.name == "measure": - raise TranspilerError("_change_qargs() is not applicable to measure gate") - - new_qarg = [] - for qubit in _qargs(gate): - new_qubit = new_layout[rev_org_layout[qubit]] - new_qarg.append((qregs[new_qubit[0]], new_qubit[1])) - - gate.arg = new_qarg From ccb0a3dbbe853296ea03070a3db25ea8e8c24958 Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Wed, 10 Jul 2019 11:47:07 +0900 Subject: [PATCH 18/40] add more tests --- .../transpiler/test_flexlayer_heuristics.py | 122 ++++-------------- test/python/transpiler/test_flexlayer_swap.py | 82 +++++++++--- 2 files changed, 90 insertions(+), 114 deletions(-) diff --git a/test/python/transpiler/test_flexlayer_heuristics.py b/test/python/transpiler/test_flexlayer_heuristics.py index dc87816ef22f..5f26c1879924 100644 --- a/test/python/transpiler/test_flexlayer_heuristics.py +++ b/test/python/transpiler/test_flexlayer_heuristics.py @@ -17,17 +17,15 @@ import unittest from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister -from qiskit.converters import circuit_to_dag, dag_to_circuit +from qiskit.converters import circuit_to_dag from qiskit.transpiler import CouplingMap, Layout from qiskit.transpiler.passes.mapping.algorithm.dependency_graph import DependencyGraph from qiskit.transpiler.passes.mapping.algorithm.flexlayer_heuristics import FlexlayerHeuristics -from qiskit.transpiler.passes.mapping.algorithm.flexlayer_heuristics import remove_head_swaps class TestFlexlayerHeuristics(unittest.TestCase): """Tests for FlexlayerHeuristics.""" - @unittest.skip("due to a bug in DAGCircuit.__eq__()") def test_search_4qcx4h1(self): """Test for 4 cx gates and 1 h gate in a 4q circuit. """ @@ -40,13 +38,13 @@ def test_search_4qcx4h1(self): circuit.h(qr[1]) circuit.cx(qr[1], qr[0]) circuit.barrier() - for i in range(4): - circuit.measure(qr[i], cr[i]) + circuit.measure(qr, cr) + dep_graph = DependencyGraph(circuit, graph_type="basic") coupling = CouplingMap([[0, 1], [1, 2], [1, 3]]) # {0: [1], 1: [2, 3]} initial_layout = Layout.generate_trivial_layout(qr) algo = FlexlayerHeuristics(circuit, dep_graph, coupling, initial_layout) - actual_dag, layout = algo.search() + actual_dag, actual_last_layout = algo.search() expected = QuantumCircuit(qr, cr) expected.cx(qr[0], qr[1]) @@ -61,16 +59,10 @@ def test_search_4qcx4h1(self): expected.measure(qr[2], cr[1]) expected.measure(qr[0], cr[2]) expected.measure(qr[3], cr[3]) - expected_layout = initial_layout - from qiskit.tools.visualization import dag_drawer - dag_drawer(actual_dag) - dag_drawer(circuit_to_dag(expected)) - print(dag_to_circuit(actual_dag).draw()) - print(expected.draw()) + expected_last_layout = Layout.from_qubit_list([qr[2], qr[0], qr[1], qr[3]]) self.assertEqual(actual_dag, circuit_to_dag(expected)) - self.assertEqual(layout, expected_layout) + self.assertEqual(str(actual_last_layout), str(expected_last_layout)) - @unittest.skip("TODO: Change to use DAGCircuit and Layout") def test_search_multi_creg(self): """Test for multiple ClassicalRegisters. """ @@ -88,95 +80,29 @@ def test_search_multi_creg(self): circuit.measure(qr[1], cr1[1]) circuit.measure(qr[2], cr2[0]) circuit.measure(qr[3], cr2[1]) + dep_graph = DependencyGraph(circuit, graph_type="basic") coupling = CouplingMap([[0, 1], [1, 2], [1, 3]]) # {0: [1], 1: [2, 3]} initial_layout = Layout.generate_trivial_layout(qr) algo = FlexlayerHeuristics(circuit, dep_graph, coupling, initial_layout) - qc, layout = algo.search() - actual_measures = [s for s in qc.qasm().split('\n') if s.startswith("measure")] - expected_measures = [] - expected_measures.append("measure q[0] -> d[0];") - expected_measures.append("measure q[1] -> c[0];") - expected_measures.append("measure q[2] -> c[1];") - expected_measures.append("measure q[3] -> d[1];") - expected_layout = initial_layout - self.assertEqual(sorted(actual_measures), expected_measures) - self.assertEqual(layout, expected_layout) + actual_dag, actual_last_layout = algo.search() - @unittest.skip("TODO: Change to use DAGCircuit and Layout") - def test_remove_head_swaps(self): - """Test for removing unnecessary swap gates from qc by changing initial_layout. - """ - qr = QuantumRegister(4, 'q') - cr = ClassicalRegister(4, 'c') - circuit = QuantumCircuit(qr, cr) - circuit.cx(qr[0], qr[1]) - circuit.u1(1, qr[2]) - circuit.swap(qr[2], qr[3]) - circuit.cx(qr[1], qr[2]) - for i in range(4): - circuit.measure(qr[i], cr[i]) - initial_layout = {('q', i): ('q', i) for i in range(4)} - resqc, layout = remove_head_swaps(circuit, initial_layout) - actual_qasm = ''.join([s for s in resqc.qasm().split('\n') if not s.startswith("measure")]) - actual_measure = sorted([s for s in resqc.qasm().split('\n') if s.startswith("measure")]) - header = 'OPENQASM 2.0;include "qelib1.inc";qreg q[4];creg c[4];' - expected_qasm = header + "cx q[0],q[1];u1(1) q[3];cx q[1],q[2];" - expected_measure = ["measure q[%d] -> c[%d];" % (i, i) for i in range(4)] - expected_layout = {('q', 0): ('q', 0), ('q', 1): ('q', 1), ('q', 3): ('q', 2), - ('q', 2): ('q', 3)} - self.assertEqual(actual_qasm, expected_qasm) - self.assertEqual(actual_measure, expected_measure) - self.assertEqual(layout, expected_layout) - - # @unittest.skip("WIP") - # def test_search_rd32_v0_67(self): - # qp = QuantumProgram() - # qasm_text = 'OPENQASM 2.0;\ - # include "qelib1.inc";\ - # gate peres a,b,c {ccx a, b, c; cx a, b;}\ - # qreg q[4];\ - # creg c[4];\ - # peres q[0], q[1], q[3];\ - # peres q[1], q[2], q[3];\ - # barrier q;\ - # measure q->c;' - # qc = load_qasm_string(qasm_text, basis_gates="u1,u2,u3,cx,id") - # dg = DependencyGraph(qc, graph_type="basic") - # coupling = Coupling({1: [0], 2: [0, 1, 4], 3: [2, 4]}) - # initial_layout = {('q', 0): ('q', 1), ('q', 1): ('q', 0), ('q', 2): ('q', 2), - # ('q', 3): ('q', 4)} - # algo = FlexlayerHeuristics(qc, dg, coupling, initial_layout) - # dag, layout = algo.search() - - @unittest.skip("TODO: Change to use DAGCircuit and Layout") - def test_remove_head_swaps_and_remain_middle_swaps(self): - """Test for only head swaps are removed, and middle swaps are remained properly. - """ - qr = QuantumRegister(6, 'q') - cr = ClassicalRegister(6, 'c') - circuit = QuantumCircuit(qr, cr) - circuit.u1(1, qr[4]) - circuit.h(qr[3]) - circuit.swap(qr[4], qr[5]) - circuit.cx(qr[3], qr[4]) - circuit.swap(qr[3], qr[4]) - circuit.u1(1, qr[1]) - circuit.h(qr[0]) - circuit.swap(qr[0], qr[1]) - circuit.swap(qr[1], qr[2]) - circuit.swap(qr[2], qr[3]) - circuit.cx(qr[3], qr[4]) - initial_layout = {('b', i): ('q', i) for i in range(6)} - resqc, layout = remove_head_swaps(circuit, initial_layout) - actual_qasm = ''.join([s for s in resqc.qasm().split('\n')]) - header = 'OPENQASM 2.0;include "qelib1.inc";qreg q[6];creg c[6];' - expected_qasm = header + "u1(1) q[5];h q[3];cx q[3],q[4];swap q[3],q[4];u1(1) q[0];" \ - "h q[2];swap q[2],q[3];cx q[3],q[4];" - expected_layout = {('b', 0): ('q', 2), ('b', 1): ('q', 0), ('b', 2): ('q', 1), - ('b', 3): ('q', 3), ('b', 4): ('q', 5), ('b', 5): ('q', 4)} - self.assertEqual(actual_qasm, expected_qasm) - self.assertEqual(layout, expected_layout) + expected = QuantumCircuit(qr, cr1, cr2) + expected.cx(qr[0], qr[1]) + expected.swap(qr[1], qr[2]) + expected.cx(qr[1], qr[3]) + expected.cx(qr[2], qr[1]) + expected.h(qr[2]) + expected.swap(qr[0], qr[1]) + expected.cx(qr[2], qr[1]) + expected.barrier() + expected.measure(qr[0], cr2[0]) + expected.measure(qr[1], cr1[0]) + expected.measure(qr[2], cr1[1]) + expected.measure(qr[3], cr2[1]) + expected_last_layout = Layout.from_qubit_list([qr[2], qr[0], qr[1], qr[3]]) + self.assertEqual(actual_dag, circuit_to_dag(expected)) + self.assertEqual(str(actual_last_layout), str(expected_last_layout)) if __name__ == "__main__": diff --git a/test/python/transpiler/test_flexlayer_swap.py b/test/python/transpiler/test_flexlayer_swap.py index 1686ef22d183..38b00e164e57 100644 --- a/test/python/transpiler/test_flexlayer_swap.py +++ b/test/python/transpiler/test_flexlayer_swap.py @@ -16,7 +16,7 @@ import unittest -from qiskit import QuantumRegister, QuantumCircuit +from qiskit import QuantumRegister, QuantumCircuit, ClassicalRegister from qiskit.converters import circuit_to_dag from qiskit.test import QiskitTestCase from qiskit.transpiler import CouplingMap @@ -28,7 +28,7 @@ class TestFlexlayerSwap(QiskitTestCase): def test_trivial_case(self): """No need to have any swap, the CX are distance 1 to each other - q0:--(+)-[U]-(+)- + q0:--(+)-[H]-(+)- | | q1:---.-------|-- | @@ -83,40 +83,90 @@ def test_a_single_swap(self): self.assertEqual(circuit_to_dag(expected), after) + def test_flexlayer_swap_maps_measurements(self): + """Verify measurement nodes are updated to map correct cregs to re-mapped qregs. + + Create a circuit with measures on q1 and q2, following a cx between q1 and q2. + Since (1, 2) is not in the coupling, one of the two will be required to move. + Verify that the mapped measure corresponds to one of the two possible layouts following + the swap. + """ + coupling = CouplingMap([[0, 1], [0, 2]]) + + qr = QuantumRegister(3, 'q') + cr = ClassicalRegister(2) + circuit = QuantumCircuit(qr, cr) + circuit.cx(qr[1], qr[2]) + circuit.measure(qr[1], cr[0]) + circuit.measure(qr[2], cr[1]) + dag = circuit_to_dag(circuit) + + expected = QuantumCircuit(qr, cr) + expected.swap(qr[0], qr[2]) + expected.cx(qr[1], qr[0]) + expected.measure(qr[1], cr[0]) + expected.measure(qr[0], cr[1]) # <- changed due to swap insertion + + pass_ = FlexlayerSwap(coupling) + after = pass_.run(dag) + + self.assertEqual(circuit_to_dag(expected), after) + + def test_flexlayer_swap_doesnt_modify_mapped_circuit(self): + """Test that flexlayer swap is idempotent. + + It should not modify a circuit which is already compatible with the + coupling map, and can be applied repeatedly without modifying the circuit. + """ + coupling = CouplingMap([[0, 1], [0, 2]]) + + qr = QuantumRegister(3, 'q') + cr = ClassicalRegister(2) + circuit = QuantumCircuit(qr, cr) + circuit.cx(qr[1], qr[2]) + circuit.measure(qr[1], cr[0]) + circuit.measure(qr[2], cr[1]) + dag = circuit_to_dag(circuit) + + mapped_dag = FlexlayerSwap(coupling).run(dag) + remapped_dag = FlexlayerSwap(coupling).run(mapped_dag) + + self.assertEqual(mapped_dag, remapped_dag) + def test_far_swap(self): """ A far swap that affects coming CXs. qr0:--(+)---.-- | | - qr1:---|----|-- - | | - qr2:---|----|-- - | | - qr3:---.---(+)- + qr1:---.----|-- + | + qr2:--------|-- + | + qr3:-------(+)- CouplingMap map: [0]--[1]--[2]--[3] - qr0:--X-------------- - | - qr1:--X--X----------- - | - qr2:-----X--(+)---.-- - | | - qr3:---------.---(+)- + qr0:-(+)-X---------- + | | + qr1:--.--X--X------- + | + qr2:--------X--.---- + | + qr3:----------(+)--- """ coupling = CouplingMap([[0, 1], [1, 2], [2, 3]]) qr = QuantumRegister(4, name='q') circuit = QuantumCircuit(qr) + circuit.cx(qr[1], qr[0]) circuit.cx(qr[0], qr[3]) - circuit.cx(qr[3], qr[0]) dag = circuit_to_dag(circuit) expected = QuantumCircuit(qr) + expected.cx(qr[1], qr[0]) expected.swap(qr[0], qr[1]) expected.swap(qr[1], qr[2]) expected.cx(qr[2], qr[3]) - expected.cx(qr[3], qr[2]) pass_ = FlexlayerSwap(coupling) after = pass_.run(dag) From 7c4e0899ffeae500924a5015562901095b70eee2 Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Fri, 12 Jul 2019 13:51:56 +0900 Subject: [PATCH 19/40] change to return iterator --- qiskit/transpiler/coupling.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/qiskit/transpiler/coupling.py b/qiskit/transpiler/coupling.py index 095f993f2a89..c7e29ecf33c1 100644 --- a/qiskit/transpiler/coupling.py +++ b/qiskit/transpiler/coupling.py @@ -188,16 +188,15 @@ def shortest_undirected_path(self, physical_qubit1, physical_qubit2): "Nodes %s and %s are not connected" % (str(physical_qubit1), str(physical_qubit2))) def undirected_neighbors(self, physical_qubit): - """List up neighbors of the physical_qubit in the undirected coupling graph. + """List up neighbors of the `physical_qubit` in the undirected coupling graph. Args: - physical_qubit: + physical_qubit (int): A physical qubit whose neighors should be listed up Returns: - List: The neighbors of the physical_qubit + iterator: The neighbors of the physical qubit """ - neighbors = self.graph.to_undirected().neighbors(physical_qubit) - return [r for r in neighbors] + return self.graph.to_undirected().neighbors(physical_qubit) @property def is_symmetric(self): From f02cc96ffdc82c5feedc29dea9a151d3b0c7e24a Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Fri, 12 Jul 2019 13:58:35 +0900 Subject: [PATCH 20/40] remove layer-type dependency graph --- .../mapping/algorithm/dependency_graph.py | 32 ++----------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py index 358b4eef0431..d89be05bf241 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py +++ b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py @@ -19,7 +19,7 @@ there exists a path from g1 to g2. """ import copy -from collections import defaultdict, namedtuple +from collections import namedtuple from typing import List, Set, Union import networkx as nx @@ -57,9 +57,9 @@ def __init__(self, Args: quantum_circuit: A quantum circuit whose dependency graph to be constructed. graph_type: Which type of dependency is considered. - - "xz_commute": consider four commutation rules proposed in [Itoko et. al. 2019]. + - "xz_commute": consider four commutation rules: + Rz-CX(control), Rx-CX(target), CX-CX(controls), CX-CX(targets). - "basic": consider only the commutation between gates without sharing qubits. - - "layer": fix layers and add dependencies between layers to `basic`. Raises: TranspilerError: if `graph_type` is not one of the types listed above. @@ -81,8 +81,6 @@ def __init__(self, self._create_xz_graph() elif graph_type == "basic": self._create_basic_graph() - elif graph_type == "layer": - self._create_layer_graph() else: raise TranspilerError("Unknown graph_type:" + graph_type) @@ -206,30 +204,6 @@ def _create_basic_graph(self): if m != -1: self._graph.add_edge(m, n) - def _create_layer_graph(self): - self._create_basic_graph() - - # construct CNOT layers - layers = [] - wire = defaultdict(int) - for n in self._graph.nodes(): - qargs = self.qargs(n) - if self.gate_name(n) == "cx": # consider only CNOTs - i = 1 + max(wire[qargs[0]], wire[qargs[1]]) - wire[qargs[0]] = i - wire[qargs[1]] = i - if len(layers) > i: - layers[i].append(n) - else: - layers.append([n]) - - # Add more edges to basic graph for fixing layers - for i in range(len(layers) - 1): - j = i + 1 - for icx in layers[i]: - for jcx in layers[j]: - self._graph.add_edge(icx, jcx) - def n_nodes(self) -> int: """Number of the nodes Returns: From 6e783cb9e60ca0232fb4e55cfd3ccd86a35d285e Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Fri, 12 Jul 2019 14:03:36 +0900 Subject: [PATCH 21/40] increase readability --- .../transpiler/passes/mapping/algorithm/dependency_graph.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py index d89be05bf241..ea0a1e39c39c 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py +++ b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py @@ -94,7 +94,7 @@ def _create_xz_graph(self): # construct commutation-rules-aware dependency graph for n in self._graph.nodes(): if self._gates[n].name in x_gates: - [b] = self._gates[n].qargs + b = self._gates[n].qargs[0] # acting qubit of gate n # pylint: disable=unbalanced-tuple-unpacking [pgow] = self._prior_gates_on_wire(self._gates, n) z_flag = False @@ -114,7 +114,7 @@ def _create_xz_graph(self): else: raise TranspilerError("Unknown gate: " + gate.name) elif self._gates[n].name in z_gates: - [b] = self._gates[n].qargs + b = self._gates[n].qargs[0] # acting qubit of gate n # pylint: disable=unbalanced-tuple-unpacking [pgow] = self._prior_gates_on_wire(self._gates, n) x_flag = False From 46081ea0027628813731ec63c72079ffcdd7e765 Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Fri, 12 Jul 2019 14:06:42 +0900 Subject: [PATCH 22/40] add type hint --- qiskit/transpiler/passes/mapping/algorithm/ancestors.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/algorithm/ancestors.py b/qiskit/transpiler/passes/mapping/algorithm/ancestors.py index 24da1cf1bd41..773b5f7b7cab 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/ancestors.py +++ b/qiskit/transpiler/passes/mapping/algorithm/ancestors.py @@ -28,7 +28,7 @@ class Ancestors: Utility class for speeding up a function to find ancestors in DAG. """ - def __init__(self, G: nx.DiGraph, max_depth=5): + def __init__(self, G: nx.DiGraph, max_depth: int = 5): self._graph = G.reverse() self._max_depth = max_depth self._depth = 0 @@ -45,7 +45,7 @@ def ancestors(self, n: int) -> set: return self._ancestors_rec(n) @lru_cache(maxsize=CACHE_SIZE) - def _ancestors_rec(self, n) -> set: + def _ancestors_rec(self, n: int) -> set: if self._depth >= self._max_depth: return self._ancestors_loop(n) self._depth += 1 @@ -57,7 +57,7 @@ def _ancestors_rec(self, n) -> set: return ret @lru_cache(maxsize=CACHE_SIZE) - def _ancestors_loop(self, n) -> set: + def _ancestors_loop(self, n: int) -> set: ret = set() done = set() cands = [n] From 2d1a577d7765497e6e25ca5af0230de01df662f4 Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Fri, 12 Jul 2019 15:01:52 +0900 Subject: [PATCH 23/40] update comments --- .../passes/mapping/algorithm/flexlayer_heuristics.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py index af53cb1a8dfd..48c8c1637712 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py +++ b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py @@ -13,22 +13,23 @@ # that they have been altered from the originals. """ -A core algorithm of the flexible-layer swap mapping heuristics proposed in [Itoko et. al. 2019]. +A core algorithm of the flexible-layer swap mapping heuristics first proposed in [1]. The outline of the algorithm is as follows. 0. Assume an initial_layout is given and set it to `layout`. 1. Initialize `blocking_gates` as gates without in-edge in dependency graph. -2. Update `blocking_gates` by processing applicable gates for the `layout`. +2. Update `blocking_gates` by processing applicable gates for a current `layout`. 3. If it comes to no blocking gates, it terminates. Otherwise, it selects a qubit pair (= an edge in the coupling graph) to be swapped based on its `cost`. 4. Add the swap gate at the min-cost edge (= update `layout`). 5. Go back to the step 2. Note: In the actual flow, there is an additional path for avoiding handling cyclic swaps in step 3. -For more details on the algorithm, see [Itoko et. al. 2019]: -T. Itoko, R. Raymond, T. Imamichi, A. Matsuo, and A. W. Cross. +[1] T. Itoko, R. Raymond, T. Imamichi, A. Matsuo, and A. W. Cross. Quantum circuit compilers using gate commutation rules. In Proceedings of ASP-DAC, pp. 191--196. ACM, 2019. + +See https://arxiv.org/abs/1907.02686 (extended version of [1]) for the details of the algorithm. """ import collections import copy From ac4ec1d25ebf9e4a4213302e21b6e4eec23536ab Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Fri, 12 Jul 2019 15:02:52 +0900 Subject: [PATCH 24/40] expose dependency_graph_type parameter --- .../passes/mapping/flexlayer_swap.py | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/flexlayer_swap.py b/qiskit/transpiler/passes/mapping/flexlayer_swap.py index deb64c85fbb5..6b882ebe8381 100644 --- a/qiskit/transpiler/passes/mapping/flexlayer_swap.py +++ b/qiskit/transpiler/passes/mapping/flexlayer_swap.py @@ -13,12 +13,16 @@ # that they have been altered from the originals. """ -A pass implementing the flexible-layer mapper. +A pass implementing the flexible-layer mapping algorithm. -That is the swap mapper proposed in the paper: +That is a swap mapping algorithm proposed in the paper: T. Itoko, R. Raymond, T. Imamichi, A. Matsuo, and A. W. Cross. Quantum circuit compilers using gate commutation rules. In Proceedings of ASP-DAC, pp. 191--196. ACM, 2019. + (Its extended version is available at https://arxiv.org/abs/1907.02686 ) +Note: This implementation does not include a post process for removing meaningless head swaps, +i.e. changing initial layout, which was applied in the experiments in the paper. +This is due to the limitation of swap mapper passes which are not allowed to change initial layout. This algorithm considers the *dependency graph* of a given circuit with less dependencies by considering commutativity of consecutive gates, @@ -28,7 +32,7 @@ in contrast to many other swap passes assumes fixed layers as their input. That's why this pass is named FlexlayerSwap pass. -(For the general role of the swap mapper pass, see `lookahed_swap.py`.) +(For the general role of the swap mapping pass, see :doc:`lookahed_swap`.) """ from qiskit.converters import dag_to_circuit from qiskit.dagcircuit import DAGCircuit @@ -43,38 +47,44 @@ class FlexlayerSwap(TransformationPass): """ - Maps a DAGCircuit onto a `coupling_map` inserting swap gates. + Map input circuit onto a backend topology via insertion of SWAPs + using flexible-layer mapping algorithm. """ def __init__(self, - coupling_map: CouplingMap, - lookahead_depth: int = 10, - decay_rate: float = 0.5): + coupling_map, + dependency_graph_type="xz_commute", + lookahead_depth=10, + decay_rate=0.5): """ - Maps a DAGCircuit onto a `coupling_map` using swap gates. + Initialize a FlexlayerSwap instance. Args: - coupling_map: Directed graph represented a coupling map. - lookahead_depth: how far gates from blocking gates should be looked ahead - decay_rate: decay rate of look-ahead weight (0 < decay_rate < 1) + coupling_map (CouplingMap): CouplingMap of the target backend. + dependency_graph_type (str): Type of dependency graph: + - "basic": consider only the commutation between gates without sharing qubits. + - "xz_commute": consider four more commutation rules. + lookahead_depth (int): How far gates from blocking gates should be looked ahead + decay_rate (float): Decay rate of look-ahead weight (0 < decay_rate < 1) """ super().__init__() self.requires.append(BarrierBeforeFinalMeasurements()) self._coupling_map = coupling_map + self._graph_type = dependency_graph_type self._lookahead_depth = lookahead_depth self._decay_rate = decay_rate - def run(self, dag: DAGCircuit) -> DAGCircuit: + def run(self, dag): """ Runs the FlexlayerSwap pass on `dag`. Args: - dag: DAG to map. + dag (DAGCircuit): A circuit to map. Returns: - A mapped DAG (with virtual qubits). + DAGCircuit: A mapped circuit compatible with the coupling_map. """ initial_layout = Layout.generate_trivial_layout(*dag.qregs.values()) qc = dag_to_circuit(dag) - dependency_graph = DependencyGraph(qc, graph_type="xz_commute") + dependency_graph = DependencyGraph(qc, graph_type=self._graph_type) algo = FlexlayerHeuristics(qc=qc, dependency_graph=dependency_graph, coupling=self._coupling_map, From 001d725cc5871cab4b707d5f21ca1f5dd0de740e Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Fri, 12 Jul 2019 15:10:31 +0900 Subject: [PATCH 25/40] lint --- qiskit/transpiler/passes/mapping/flexlayer_swap.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/flexlayer_swap.py b/qiskit/transpiler/passes/mapping/flexlayer_swap.py index 6b882ebe8381..5baa5dd7729d 100644 --- a/qiskit/transpiler/passes/mapping/flexlayer_swap.py +++ b/qiskit/transpiler/passes/mapping/flexlayer_swap.py @@ -35,9 +35,7 @@ (For the general role of the swap mapping pass, see :doc:`lookahed_swap`.) """ from qiskit.converters import dag_to_circuit -from qiskit.dagcircuit import DAGCircuit from qiskit.transpiler.basepasses import TransformationPass -from qiskit.transpiler.coupling import CouplingMap from qiskit.transpiler.layout import Layout from .algorithm.dependency_graph import DependencyGraph From 504c8a6f30508ab8603ae2862a3203aca09b4414 Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Fri, 12 Jul 2019 19:41:03 +0900 Subject: [PATCH 26/40] simplify cache algorithm --- .../passes/mapping/algorithm/ancestors.py | 32 +++---------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/algorithm/ancestors.py b/qiskit/transpiler/passes/mapping/algorithm/ancestors.py index 773b5f7b7cab..3a3eb7cad3c3 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/ancestors.py +++ b/qiskit/transpiler/passes/mapping/algorithm/ancestors.py @@ -28,11 +28,10 @@ class Ancestors: Utility class for speeding up a function to find ancestors in DAG. """ - def __init__(self, G: nx.DiGraph, max_depth: int = 5): + def __init__(self, G: nx.DiGraph): self._graph = G.reverse() - self._max_depth = max_depth - self._depth = 0 + @lru_cache(maxsize=CACHE_SIZE) def ancestors(self, n: int) -> set: """ Ancestor nodes of the node `n` @@ -41,31 +40,10 @@ def ancestors(self, n: int) -> set: Returns: Set of indices of the ancestor nodes. """ - self._depth = 0 - return self._ancestors_rec(n) - - @lru_cache(maxsize=CACHE_SIZE) - def _ancestors_rec(self, n: int) -> set: - if self._depth >= self._max_depth: - return self._ancestors_loop(n) - self._depth += 1 ret = set() for n_succ in self._graph.successors(n): + if n_succ in ret: + continue ret.add(n_succ) - ret.update(self._ancestors_rec(n_succ)) - self._depth -= 1 - return ret - - @lru_cache(maxsize=CACHE_SIZE) - def _ancestors_loop(self, n: int) -> set: - ret = set() - done = set() - cands = [n] - while cands: - node = cands.pop() - for v in self._graph.successors(node): - if v not in done: - ret.add(v) - cands.append(v) - done.add(node) + ret.update(self.ancestors(n_succ)) return ret From 8e79ae5c3bfcd62893f0f5202f8e225c1fe3869a Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Tue, 16 Jul 2019 09:32:33 +0900 Subject: [PATCH 27/40] add FlexlayerSwap to swapper common test --- .../TestsFlexlayerSwap_a_cx_to_map.pickle | Bin 0 -> 1441 bytes .../TestsFlexlayerSwap_handle_measurement.pickle | Bin 0 -> 1821 bytes .../TestsFlexlayerSwap_initial_layout.pickle | Bin 0 -> 1748 bytes test/python/transpiler/test_mappers.py | 7 +++++++ 4 files changed, 7 insertions(+) create mode 100644 test/python/pickles/TestsFlexlayerSwap_a_cx_to_map.pickle create mode 100644 test/python/pickles/TestsFlexlayerSwap_handle_measurement.pickle create mode 100644 test/python/pickles/TestsFlexlayerSwap_initial_layout.pickle diff --git a/test/python/pickles/TestsFlexlayerSwap_a_cx_to_map.pickle b/test/python/pickles/TestsFlexlayerSwap_a_cx_to_map.pickle new file mode 100644 index 0000000000000000000000000000000000000000..a9aa09440fa45c16a9bf9652cb2f2244734d54aa GIT binary patch literal 1441 zcma)+=~ojm6vev$;(&@9qT;>{E_L5`P(dpd3hKDjnwbVNGM)C$a;%<{bM#aHZC^5t zRrH`=n&u_B@Aq!rADg-xV9 zEV0>khDfd+Fqa3gDx&D*X)ZA0SSn-MtRr>(B*tht|1;zTI<%#nb2h2dZ5FX+jIA|n z(*ds)CQ&QESVhCu8+^qN{e)1ky^^{$>@bY$e2Dm}A16E_BPfG~okY1yVz=!KQy#O5 z@qZuJwpNWC_K?to#9q5sAh1un7W-d0_LqyP>kLu`KWy+0Cd>KMWi=e=;-Ja&H#6hU ztZgmPPiE;M%5hlYh%K~_>c-R#92KMGf*`X58FWmHRYYdOaa*YRPDq@z#kiOdljU6M z>ewj>+r$c~%WIhG;`Bx=UdNgVTS-Q8hGfr5oa>Pkh{Wrl^ee~tKF5WO97U$JH*bZN%ZSDDvq7r+Xu=6Uz;Wo633BEWHi5MKnd@%I!YQos8zL#66Rl z9MClPB_5cVA$%w?ZDJYWOc##~p=qxsXK@;FYrZ$)F;P8{m^D=rQG)%U z>h@JVt=f>_MwKpu{8`bes}jPfD&M;Bx&GcjF2(p%!O_$Yn3oWXvErIN%%QPa1ICga;U|(8kf`lHX(hv19 B&tw1q literal 0 HcmV?d00001 diff --git a/test/python/pickles/TestsFlexlayerSwap_handle_measurement.pickle b/test/python/pickles/TestsFlexlayerSwap_handle_measurement.pickle new file mode 100644 index 0000000000000000000000000000000000000000..3ef77ce48848c414f260aa697eb82b78182e7fd6 GIT binary patch literal 1821 zcma)-`JdB77{=S~a+pP3QE^qgui#Pd`(_o;O65}H5$hx!m`IxLlS2yn3V204Q1L$h zR^OS)5Ll3pzqHMpdB4x|zBBXo*j$XHIGB|EILYIRzfp;-tR~H)x7$80+qWJ%TaKek z==s5Xr_;&AL}D)3$Ul|H#;F`lq$sLfPGnYM-mDpmQeaJpwZ4;*vr=Y7GR=y9QF5Bd z$NhNbEoakmyC|iHbu6-;aFHLZW%4K$kxbzY)m+fy=Xi#Nz|-p0WP~b8$^sh(z0;vM zHKB_KonW0#UlF;O6u2bBr6v$hvofEiSR4im`u$k$PqL(BKe%jIMIknxcDkx5xjc{* zrOdf2EaBmD_IU;2N zD%`3z47!RNr?^?Ssl}nP4Y}Q@-9hl3+FTWh5L;^8c_FppkXZ5d@`u&Di#6{i+|y|4 z1Eg1=&$2xydv7aypKo;UCp_SEdrjpJ5*~6f_QI7vT;ma?56+{6$E<={I1~JM3x2|a zpCmlxAP)RA;TadREq%7eb4pu(p0Kq6pXGSD1;1dyFA}ynh})~}gdHws!7tT#**G2$ zUOAuRoh^81!LJf_If!$-o3O{lEO>8?eHYr6NaT6Kr+91gjn}xk*9mXvtehyCN?6x=VGoMf)KeFSHc?@*BCq3?&37l#M^17 ziU-L~_Y)2{r_BPW=Hzzu$xT|P?;8UL38@p~7V*iQtX7IsQ4~omQg@5JWj#hbWUM$P ztZ@GS$!RF<)duJ1gu-3z7^N~dn(r((L)560uIdBAAzq`-s$|wya@bUIgz%xOL}!Qi zNNI;e_*lntI|-P2)KHHRj-NyQq@{jpsGku&cT{_`zcAFA@TH^jCNcGdp?*d9`W)&v zE%jSNJxTb^QFV5R?+x__!YN19YBhe8X1sxWU*M+@KdU8SBC(1565>~ti(rwDrk&=J zhw!KrQ7ZA<>RmD8UN123y&Jb1xrg7mTYnJ#w0|v@@fYFmA~KOw9G+Gs{^37@ F{=bm?O;i8? literal 0 HcmV?d00001 diff --git a/test/python/pickles/TestsFlexlayerSwap_initial_layout.pickle b/test/python/pickles/TestsFlexlayerSwap_initial_layout.pickle new file mode 100644 index 0000000000000000000000000000000000000000..065f2e2cec823718ac704082d4d647ac1790618a GIT binary patch literal 1748 zcma)6>7Ud@5S`gsu7J1-!pfl{9sza4^#a6;6*&UKwO|y+v6E@&NG3Bc$19 z+ar;RL58(mJZpe(nB?g&#!SawtBj`s+2HYe9_bi7aaz+$Y8b$nRjqbKPIM4Cq2!2R_TAG4O-ggx4MJuGzq_OhC3 z>&hn+HTx}VpXU1ugfn)kL!4D+n5$6|CB9L*DxdHz$Ebf*^4+A8bEcB>gs!cm z`T)LHT49gygT@z_RpTXuVGRi3W32rNt2C?^2p26YH;w&KhBYGmXj!#yB`z7(m@s&Z zHJPvu4eN*i%gQZg*3__Ogxs=fyhI_*mkstl!(tbg)vPc`XpnIiSJZ;=XLvNLJtleo z^l}lz5?7aY#Vk4o;byh^d!xBU+#>wu@($4}<2IqxRqCyFhw!`hYty67bKN|vI_@sv z4;^=>ng`xO8ofujZ&B{2*D~k>!k-r9eiUXs_7>+x_>1tjt8R1dyUcr!Ssr+;f= 2 and sys.argv[1] == 'regenerate': CommonUtilitiesMixin.regenerate_expected = True From 6d8d04af24c5a192ccb5a01e70fddb3f8a51f876 Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Wed, 17 Jul 2019 13:52:46 +0900 Subject: [PATCH 28/40] move ancestors to dependency_graph --- .../passes/mapping/algorithm/ancestors.py | 49 ------------------- .../mapping/algorithm/dependency_graph.py | 39 +++++++++++++-- .../mapping/algorithm/flexlayer_heuristics.py | 31 ++++++------ 3 files changed, 50 insertions(+), 69 deletions(-) delete mode 100644 qiskit/transpiler/passes/mapping/algorithm/ancestors.py diff --git a/qiskit/transpiler/passes/mapping/algorithm/ancestors.py b/qiskit/transpiler/passes/mapping/algorithm/ancestors.py deleted file mode 100644 index 3a3eb7cad3c3..000000000000 --- a/qiskit/transpiler/passes/mapping/algorithm/ancestors.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019. -# -# 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. - -""" -Utility for speeding up a function to find ancestors in DAG. -""" - -from functools import lru_cache - -import networkx as nx - -CACHE_SIZE = 2 ** 10 - - -class Ancestors: - """ - Utility class for speeding up a function to find ancestors in DAG. - """ - - def __init__(self, G: nx.DiGraph): - self._graph = G.reverse() - - @lru_cache(maxsize=CACHE_SIZE) - def ancestors(self, n: int) -> set: - """ - Ancestor nodes of the node `n` - Args: - n: Index of the node in the `self._graph` - Returns: - Set of indices of the ancestor nodes. - """ - ret = set() - for n_succ in self._graph.successors(n): - if n_succ in ret: - continue - ret.add(n_succ) - ret.update(self.ancestors(n_succ)) - return ret diff --git a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py index ea0a1e39c39c..87556cc3128a 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py +++ b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py @@ -20,7 +20,8 @@ """ import copy from collections import namedtuple -from typing import List, Set, Union +from functools import lru_cache +from typing import List, FrozenSet, Set, Union import networkx as nx from qiskit.circuit import QuantumCircuit, QuantumRegister @@ -42,6 +43,35 @@ def name(self) -> str: return self.instruction.name +CACHE_SIZE = 2 ** 10 + + +class Ancestors: + """ + Utility class for speeding up a function to find ancestors in DAG. + """ + + def __init__(self, G: nx.DiGraph): + self._graph = G.reverse() + + @lru_cache(maxsize=CACHE_SIZE) + def ancestors(self, n: int) -> set: + """ + Ancestor nodes of the node `n` + Args: + n: Index of the node in the `self._graph` + Returns: + Set of indices of the ancestor nodes. + """ + ret = set() + for n_succ in self._graph.successors(n): + if n_succ in ret: + continue + ret.add(n_succ) + ret.update(self.ancestors(n_succ)) + return ret + + class DependencyGraph: """ Create a dependency graph of a quantum circuit with a chosen commutation rule. @@ -87,6 +117,9 @@ def __init__(self, # remove redundant edges in dependency graph self.remove_redundancy(self._graph) + # for speed up, this works only for a graphs remaining unchanged in future + self._ancestors = Ancestors(self._graph) + def _create_xz_graph(self): z_gates = ["u1", "rz", "s", "t", "z", "sdg", "tdg"] x_gates = ["rx", "x"] @@ -238,7 +271,7 @@ def gate_name(self, i: int) -> str: """ return self._gates[i].name - def head_gates(self) -> Set[int]: + def head_gates(self) -> FrozenSet[int]: """Gates which can be applicable prior to the other gates Returns: Set of indices of the gates. @@ -270,7 +303,7 @@ def ancestors(self, i: int) -> Set[int]: Returns: Set of indices of the ancestor gates. """ - return nx.ancestors(self._graph, i) + return self._ancestors.ancestors(i) def gate(self, gidx: int, layout: Layout, physical_qreg: QuantumRegister) -> InstructionContext: """Convert acting qubits of gate `gidx` from virtual qubits to physical ones. diff --git a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py index 48c8c1637712..430b99e89415 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py +++ b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py @@ -35,6 +35,7 @@ import copy import logging import pprint +from typing import List from qiskit.circuit import QuantumCircuit, QuantumRegister from qiskit.dagcircuit import DAGCircuit @@ -43,7 +44,6 @@ from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.layout import Layout -from .ancestors import Ancestors from .dependency_graph import DependencyGraph logger = logging.getLogger(__name__) @@ -61,7 +61,16 @@ def __init__(self, initial_layout: Layout, lookahead_depth: int = 5, decay_rate: float = 0.5): - + """ + Initialize a FlexlayerHeuristics instance. + Args: + qc: A circuit to be mapped. + dependency_graph: Dependency graph of the circuit. + coupling: CouplingMap of the target backend. + initial_layout: Initial layout (layout at the beginning of circuit). + lookahead_depth: How far gates from blocking gates should be looked ahead. + decay_rate: Decay rate of look-ahead weight (0 < decay_rate < 1). + """ self._qc = qc if initial_layout is None: @@ -78,8 +87,6 @@ def __init__(self, self._lookahead_depth = lookahead_depth self._decay_rate = decay_rate - self._ancestors = Ancestors(self._dg._graph) # for speed up - def search(self) -> (DAGCircuit, Layout): """ @@ -157,7 +164,7 @@ def search(self) -> (DAGCircuit, Layout): layout.swap(edge[0], edge[1]) logger.debug("%d-th inner iter. add a swap (%d, %d)", kin, edge[0], edge[1]) - logger.debug("resolved layout = %s", pprint.pformat(sorted(layout.items()))) + logger.debug("resolved layout = %s", str(layout)) dones = self._find_done_gates(blocking_gates, layout=layout) if dones: @@ -205,16 +212,6 @@ def _find_done_gates(self, blocking_gates, layout): return dones - def ancestors(self, n: int) -> set: - """ - Ancestor gates of the gate `n` - Args: - n: Index of the gate in the `self._graph` - Returns: - Set of indices of the ancestor gates. - """ - return self._ancestors.ancestors(n) - def _update_leading_gates(self, leading_gates, dones): news = set(leading_gates) for n in dones: @@ -224,7 +221,7 @@ def _update_leading_gates(self, leading_gates, dones): rmlist = [] for n in news: - if news & self.ancestors(n): + if news & self._dg.ancestors(n): rmlist.append(n) news -= set(rmlist) @@ -358,7 +355,7 @@ def __init__(self, gates, layout, coupling, dg, max_depth=10): self.coupling = coupling def cost(self, edge: (int, int), - priority: str = None, + priority: List[str] = None, alpha: float = 0.5) -> collections.namedtuple: """ estimate cost if the edge e is swapped From 31a98904e0c18390918c294581d88f74e6369388 Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Wed, 17 Jul 2019 18:52:08 +0900 Subject: [PATCH 29/40] improve performance --- .../mapping/algorithm/dependency_graph.py | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py index 87556cc3128a..53eb11494783 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py +++ b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py @@ -19,7 +19,7 @@ there exists a path from g1 to g2. """ import copy -from collections import namedtuple +from collections import namedtuple, defaultdict from functools import lru_cache from typing import List, FrozenSet, Set, Union @@ -107,6 +107,9 @@ def __init__(self, for i, _ in enumerate(self._gates): self.qubits.update(self.qargs(i)) + # for speed up of _prior_gates_on_wire + self._create_gates_by_wire() + if graph_type == "xz_commute": self._create_xz_graph() elif graph_type == "basic": @@ -129,7 +132,7 @@ def _create_xz_graph(self): if self._gates[n].name in x_gates: b = self._gates[n].qargs[0] # acting qubit of gate n # pylint: disable=unbalanced-tuple-unpacking - [pgow] = self._prior_gates_on_wire(self._gates, n) + [pgow] = self._prior_gates_on_wire(n) z_flag = False for m in pgow: gate = self._gates[m] @@ -149,7 +152,7 @@ def _create_xz_graph(self): elif self._gates[n].name in z_gates: b = self._gates[n].qargs[0] # acting qubit of gate n # pylint: disable=unbalanced-tuple-unpacking - [pgow] = self._prior_gates_on_wire(self._gates, n) + [pgow] = self._prior_gates_on_wire(n) x_flag = False for m in pgow: gate = self._gates[m] @@ -169,7 +172,7 @@ def _create_xz_graph(self): elif self._gates[n].name == "cx": cbit, tbit = self._gates[n].qargs # pylint: disable=unbalanced-tuple-unpacking - [cpgow, tpgow] = self._prior_gates_on_wire(self._gates, n) + [cpgow, tpgow] = self._prior_gates_on_wire(n) z_flag = False for m in tpgow: # target bit: bt @@ -205,7 +208,7 @@ def _create_xz_graph(self): else: raise TranspilerError("Unknown gate: " + gate.name) elif self._gates[n].name in b_gates: - for i, pgow in enumerate(self._prior_gates_on_wire(self._gates, n)): + for i, pgow in enumerate(self._prior_gates_on_wire(n)): b = self._all_args(n)[i] x_flag, z_flag = False, False for m in pgow: @@ -232,7 +235,7 @@ def _create_xz_graph(self): def _create_basic_graph(self): for n in self._graph.nodes(): - for pgow in self._prior_gates_on_wire(self._gates, n): + for pgow in self._prior_gates_on_wire(n): m = next(pgow, -1) if m != -1: self._graph.add_edge(m, n) @@ -303,6 +306,7 @@ def ancestors(self, i: int) -> Set[int]: Returns: Set of indices of the ancestor gates. """ + # return nx.ancestors(self._graph, i) return self._ancestors.ancestors(i) def gate(self, gidx: int, layout: Layout, physical_qreg: QuantumRegister) -> InstructionContext: @@ -334,23 +338,22 @@ def remove_redundancy(graph): if not nx.has_path(graph, edge[0], edge[1]): graph.add_edge(edge[0], edge[1]) - @staticmethod - def _prior_gates_on_wire(gate_list, toidx): + def _prior_gates_on_wire(self, toidx): res = [] - for qarg in gate_list[toidx].qargs: - gates = [] - for i, gate in enumerate(gate_list[:toidx]): - if qarg in gate.qargs: - gates.append(i) - res.append(reversed(gates)) - for carg in gate_list[toidx].cargs: - gates = [] - for i, gate in enumerate(gate_list[:toidx]): - if carg in gate.cargs: - gates.append(i) - res.append(reversed(gates)) + for qarg in self._gates[toidx].qargs: + res.append(reversed([i for i in self._gates_by_wire[qarg] if i < toidx])) + for carg in self._gates[toidx].cargs: + res.append(reversed([i for i in self._gates_by_wire[carg] if i < toidx])) return res + def _create_gates_by_wire(self): + self._gates_by_wire = defaultdict(list) + for i, gate in enumerate(self._gates): + for qarg in gate.qargs: + self._gates_by_wire[qarg].append(i) + for carg in gate.cargs: + self._gates_by_wire[carg].append(i) + if __name__ == '__main__': pass From 32cef835908490170a069aa479cccbf667bfa5eb Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Wed, 17 Jul 2019 18:55:33 +0900 Subject: [PATCH 30/40] fix RecursionError in ancestors for large circuits --- .../passes/mapping/algorithm/dependency_graph.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py index 53eb11494783..7222ac857b2d 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py +++ b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py @@ -43,7 +43,7 @@ def name(self) -> str: return self.instruction.name -CACHE_SIZE = 2 ** 10 +CACHE_SIZE = 2 ** 14 class Ancestors: @@ -64,11 +64,13 @@ def ancestors(self, n: int) -> set: Set of indices of the ancestor nodes. """ ret = set() - for n_succ in self._graph.successors(n): - if n_succ in ret: - continue - ret.add(n_succ) - ret.update(self.ancestors(n_succ)) + frontier = [n] + while frontier: + node = frontier.pop() + for v in self._graph.successors(node): + if v not in ret: + ret.add(v) + frontier.append(v) return ret From c42a381670a299dcc8dc04c3119ccd52ec896500 Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Wed, 17 Jul 2019 19:05:57 +0900 Subject: [PATCH 31/40] lint --- .../passes/mapping/algorithm/flexlayer_heuristics.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py index 430b99e89415..e3ce91819aeb 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py +++ b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py @@ -70,6 +70,8 @@ def __init__(self, initial_layout: Initial layout (layout at the beginning of circuit). lookahead_depth: How far gates from blocking gates should be looked ahead. decay_rate: Decay rate of look-ahead weight (0 < decay_rate < 1). + Raises: + TranspilerError: if no (or invalid) initial_layout is given. """ self._qc = qc @@ -89,7 +91,7 @@ def __init__(self, def search(self) -> (DAGCircuit, Layout): """ - + Search mapping solution. Returns: Mapped physical circuit (DAGCircuit) and last qubit layout (Layout) Raises: From ccc65eab783bb07d6a9fc4f780117229168ec84b Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Wed, 17 Jul 2019 22:15:21 +0900 Subject: [PATCH 32/40] improve performance without ancestors --- .../mapping/algorithm/dependency_graph.py | 77 +++---------------- .../mapping/algorithm/flexlayer_heuristics.py | 13 ++-- 2 files changed, 15 insertions(+), 75 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py index 7222ac857b2d..330bc08efc5b 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py +++ b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py @@ -20,12 +20,11 @@ """ import copy from collections import namedtuple, defaultdict -from functools import lru_cache -from typing import List, FrozenSet, Set, Union +from typing import List, FrozenSet import networkx as nx from qiskit.circuit import QuantumCircuit, QuantumRegister -from qiskit.circuit import Qubit, Clbit +from qiskit.circuit import Qubit from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.layout import Layout @@ -43,37 +42,6 @@ def name(self) -> str: return self.instruction.name -CACHE_SIZE = 2 ** 14 - - -class Ancestors: - """ - Utility class for speeding up a function to find ancestors in DAG. - """ - - def __init__(self, G: nx.DiGraph): - self._graph = G.reverse() - - @lru_cache(maxsize=CACHE_SIZE) - def ancestors(self, n: int) -> set: - """ - Ancestor nodes of the node `n` - Args: - n: Index of the node in the `self._graph` - Returns: - Set of indices of the ancestor nodes. - """ - ret = set() - frontier = [n] - while frontier: - node = frontier.pop() - for v in self._graph.successors(node): - if v not in ret: - ret.add(v) - frontier.append(v) - return ret - - class DependencyGraph: """ Create a dependency graph of a quantum circuit with a chosen commutation rule. @@ -122,9 +90,6 @@ def __init__(self, # remove redundant edges in dependency graph self.remove_redundancy(self._graph) - # for speed up, this works only for a graphs remaining unchanged in future - self._ancestors = Ancestors(self._graph) - def _create_xz_graph(self): z_gates = ["u1", "rz", "s", "t", "z", "sdg", "tdg"] x_gates = ["rx", "x"] @@ -210,8 +175,9 @@ def _create_xz_graph(self): else: raise TranspilerError("Unknown gate: " + gate.name) elif self._gates[n].name in b_gates: + all_args_of_n = self._gates[n].qargs + self._gates[n].cargs for i, pgow in enumerate(self._prior_gates_on_wire(n)): - b = self._all_args(n)[i] + b = all_args_of_n[i] x_flag, z_flag = False, False for m in pgow: gate = self._gates[m] @@ -258,15 +224,6 @@ def qargs(self, i: int) -> List[Qubit]: """ return self._gates[i].qargs - def _all_args(self, i: int) -> List[Union[Qubit, Clbit]]: - """Qubit and classical-bit arguments of the gate - Args: - i: Index of the gate in the `self._gates` - Returns: - List of all arguments. - """ - return self._gates[i].qargs + self._gates[i].cargs - def gate_name(self, i: int) -> str: """Name of the gate Args: @@ -292,25 +249,6 @@ def gr_successors(self, i: int) -> List[int]: """ return self._graph.successors(i) - def descendants(self, i: int) -> Set[int]: - """Descendant gates of gate `i` - Args: - i: Index of the gate in the `self._gates` - Returns: - Set of indices of the descendant gates. - """ - return nx.descendants(self._graph, i) - - def ancestors(self, i: int) -> Set[int]: - """Ancestor gates of gate `i` - Args: - i: Index of the gate in the `self._gates` - Returns: - Set of indices of the ancestor gates. - """ - # return nx.ancestors(self._graph, i) - return self._ancestors.ancestors(i) - def gate(self, gidx: int, layout: Layout, physical_qreg: QuantumRegister) -> InstructionContext: """Convert acting qubits of gate `gidx` from virtual qubits to physical ones. Args: @@ -330,9 +268,14 @@ def gate(self, gidx: int, layout: Layout, physical_qreg: QuantumRegister) -> Ins raise TranspilerError("virtual_qubit must be in layout") return gate + def nx_graph(self) -> nx.DiGraph: + """Return deep copied networkx graph of this dependency graph. + """ + return copy.deepcopy(self._graph) + @staticmethod def remove_redundancy(graph): - """remove redundant edges in DAG (= change `graph` to its transitive reduction) + """Remove redundant edges in DAG (= change `graph` to its transitive reduction) """ edges = list(graph.edges()) for edge in edges: diff --git a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py index e3ce91819aeb..fb32849f8d12 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py +++ b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py @@ -89,6 +89,8 @@ def __init__(self, self._lookahead_depth = lookahead_depth self._decay_rate = decay_rate + self._residual_graph = dependency_graph.nx_graph() + def search(self) -> (DAGCircuit, Layout): """ Search mapping solution. @@ -218,17 +220,12 @@ def _update_leading_gates(self, leading_gates, dones): news = set(leading_gates) for n in dones: news |= set(self._dg.gr_successors(n)) - news -= set(dones) - rmlist = [] - for n in news: - if news & self._dg.ancestors(n): - rmlist.append(n) - - news -= set(rmlist) + for n in dones: + self._residual_graph.remove_node(n) - return news + return [n for n in news if len(self._residual_graph.in_edges(n)) == 0] def _fix_swap_direction(self, edge): if edge in self._coupling.get_edges(): From 4fd0103c030750f1a18e97a4419161cd4f9b4472 Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Wed, 24 Jul 2019 17:55:45 +0900 Subject: [PATCH 33/40] add comments and do some renames --- .../mapping/algorithm/dependency_graph.py | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py index 330bc08efc5b..198e896b7a2f 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py +++ b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py @@ -16,7 +16,8 @@ A dependency graph represents precedence relations of the gates in a quantum circuit considering commutation rules. Each node represents gates in the circuit. Each directed edge represents dependency of two gates. For example, gate g1 must be applied before gate g2 if and only if -there exists a path from g1 to g2. +there exists a path from g1 to g2. In this file, we use the term `gate` instead of `operation` +(or `instruction`) with or without its qu/cl-bit arguments. """ import copy from collections import namedtuple, defaultdict @@ -28,23 +29,24 @@ from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.layout import Layout -InstructionContext = namedtuple("InstructionContext", "instruction qargs cargs") +InstructionContext = namedtuple("InstructionContext", "op qargs cargs") class ArgumentedGate(InstructionContext): """ - A wrapper class of `InstructionContext` (instruction with args context). + A wrapper class of `InstructionContext` (operation with args context). """ @property def name(self) -> str: """Name of this instruction.""" - return self.instruction.name + return self.op.name class DependencyGraph: """ - Create a dependency graph of a quantum circuit with a chosen commutation rule. + Create a dependency graph of a quantum circuit (say `qc`) with a chosen commutation rule. + All of its nodes (i.e. gates) are considered as integers, which are the indices of `qc.data`. """ def __init__(self, @@ -78,7 +80,7 @@ def __init__(self, self.qubits.update(self.qargs(i)) # for speed up of _prior_gates_on_wire - self._create_gates_by_wire() + self._create_gates_by_qubit() if graph_type == "xz_commute": self._create_xz_graph() @@ -99,7 +101,7 @@ def _create_xz_graph(self): if self._gates[n].name in x_gates: b = self._gates[n].qargs[0] # acting qubit of gate n # pylint: disable=unbalanced-tuple-unpacking - [pgow] = self._prior_gates_on_wire(n) + [pgow] = self._prior_gates_on_sharing_qubits_of(n) z_flag = False for m in pgow: gate = self._gates[m] @@ -119,7 +121,7 @@ def _create_xz_graph(self): elif self._gates[n].name in z_gates: b = self._gates[n].qargs[0] # acting qubit of gate n # pylint: disable=unbalanced-tuple-unpacking - [pgow] = self._prior_gates_on_wire(n) + [pgow] = self._prior_gates_on_sharing_qubits_of(n) x_flag = False for m in pgow: gate = self._gates[m] @@ -139,7 +141,7 @@ def _create_xz_graph(self): elif self._gates[n].name == "cx": cbit, tbit = self._gates[n].qargs # pylint: disable=unbalanced-tuple-unpacking - [cpgow, tpgow] = self._prior_gates_on_wire(n) + [cpgow, tpgow] = self._prior_gates_on_sharing_qubits_of(n) z_flag = False for m in tpgow: # target bit: bt @@ -176,7 +178,7 @@ def _create_xz_graph(self): raise TranspilerError("Unknown gate: " + gate.name) elif self._gates[n].name in b_gates: all_args_of_n = self._gates[n].qargs + self._gates[n].cargs - for i, pgow in enumerate(self._prior_gates_on_wire(n)): + for i, pgow in enumerate(self._prior_gates_on_sharing_qubits_of(n)): b = all_args_of_n[i] x_flag, z_flag = False, False for m in pgow: @@ -203,7 +205,7 @@ def _create_xz_graph(self): def _create_basic_graph(self): for n in self._graph.nodes(): - for pgow in self._prior_gates_on_wire(n): + for pgow in self._prior_gates_on_sharing_qubits_of(n): m = next(pgow, -1) if m != -1: self._graph.add_edge(m, n) @@ -283,21 +285,21 @@ def remove_redundancy(graph): if not nx.has_path(graph, edge[0], edge[1]): graph.add_edge(edge[0], edge[1]) - def _prior_gates_on_wire(self, toidx): + def _prior_gates_on_sharing_qubits_of(self, toidx): res = [] for qarg in self._gates[toidx].qargs: - res.append(reversed([i for i in self._gates_by_wire[qarg] if i < toidx])) + res.append(reversed([i for i in self._gates_by_qubit[qarg] if i < toidx])) for carg in self._gates[toidx].cargs: - res.append(reversed([i for i in self._gates_by_wire[carg] if i < toidx])) + res.append(reversed([i for i in self._gates_by_qubit[carg] if i < toidx])) return res - def _create_gates_by_wire(self): - self._gates_by_wire = defaultdict(list) + def _create_gates_by_qubit(self): + self._gates_by_qubit = defaultdict(list) for i, gate in enumerate(self._gates): for qarg in gate.qargs: - self._gates_by_wire[qarg].append(i) + self._gates_by_qubit[qarg].append(i) for carg in gate.cargs: - self._gates_by_wire[carg].append(i) + self._gates_by_qubit[carg].append(i) if __name__ == '__main__': From 44cf705597c948bc4dadfae9933d4daad575d767 Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Wed, 24 Jul 2019 17:57:02 +0900 Subject: [PATCH 34/40] rename private classes and remove unused private method --- .../mapping/algorithm/flexlayer_heuristics.py | 46 ++++++------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py index fb32849f8d12..119517609986 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py +++ b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py @@ -125,11 +125,11 @@ def search(self) -> (DAGCircuit, Layout): if not blocking_gates: break - ece = EdgeCostEstimator(gates=blocking_gates, - layout=layout, - coupling=self._coupling, - dg=self._dg, - max_depth=self._lookahead_depth) + ece = _EdgeCostEstimator(gates=blocking_gates, + layout=layout, + coupling=self._coupling, + dg=self._dg, + max_depth=self._lookahead_depth) costs = [ece.cost(e, alpha=self._decay_rate) for e in ece.cand_edges] @@ -150,11 +150,11 @@ def search(self) -> (DAGCircuit, Layout): max_n_inner_loops = len(focus_gates) * self._coupling.size() for kin in range(max_n_inner_loops): # escape infinite loop - ece = EdgeCostEstimator(gates=focus_gates, - layout=layout, - coupling=self._coupling, - dg=self._dg, - max_depth=self._lookahead_depth) + ece = _EdgeCostEstimator(gates=focus_gates, + layout=layout, + coupling=self._coupling, + dg=self._dg, + max_depth=self._lookahead_depth) costs = [ece.cost(e, priority=['immediate_cost', 'lookahead_cost'], @@ -239,7 +239,7 @@ def _find_focus_gates(self, gates, layout): return gates paths = {g: self._path_of(g, layout) for g in gates} - gce = GateCostEstimator(gates, paths) + gce = _GateCostEstimator(gates, paths) costs = [gce.cost(g) for g in gce.gates] @@ -267,26 +267,6 @@ def _qubit_count_validity_check(self): raise TranspilerError("%s is not in _coupling but in initial_layout" % pprint.pformat(q)) - def _add_ancilla_qubits(self): - virtual_qubits = sorted(self._dg.qubits) - physical_qubits = sorted(self._coupling.physical_qubits) - for b in self._initial_layout.get_virtual_bits(): - if b not in virtual_qubits: - del self._initial_layout[b] - logger.info("remove unused qubit in initial_layout %s", pprint.pformat(b)) - - ancilla_qubits = set(physical_qubits) - set(self._initial_layout.get_physical_bits().keys()) - - # add ancilla qubits - if ancilla_qubits: - anc_qreg = QuantumRegister(len(ancilla_qubits), name="ancilla") - for i, anq in enumerate(ancilla_qubits): - virtual_qubits.append((anc_qreg, i)) - self._initial_layout[(anc_qreg, i)] = anq - - assert len(physical_qubits) == len(virtual_qubits), \ - "physical_qubits=%d, virtual_qubits=%d" % (len(physical_qubits), len(virtual_qubits)) - def _create_empty_dagcircuit(self, physical_qreg): new_dag = DAGCircuit() new_dag.add_qreg(physical_qreg) @@ -295,7 +275,7 @@ def _create_empty_dagcircuit(self, physical_qreg): return new_dag -class GateCostEstimator: +class _GateCostEstimator: """ Define cost of gate used for selecting the gate to be resolved first in the special loops to avoid cyclic swaps. @@ -330,7 +310,7 @@ def cost(self, gate: int, priority: str = None) -> collections.namedtuple: return Cost(dependent_cost=dependent_cost) -class EdgeCostEstimator: +class _EdgeCostEstimator: """ Define cost of edge (in coupling graph) used for selecting the edge to be swapped. """ From e3419208d2c41aa52e926d2e5756e50ca31382e9 Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Wed, 24 Jul 2019 18:00:59 +0900 Subject: [PATCH 35/40] remove useless lines --- .../transpiler/passes/mapping/algorithm/dependency_graph.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py index 198e896b7a2f..2e855ea9bacf 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py +++ b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py @@ -300,7 +300,3 @@ def _create_gates_by_qubit(self): self._gates_by_qubit[qarg].append(i) for carg in gate.cargs: self._gates_by_qubit[carg].append(i) - - -if __name__ == '__main__': - pass From 2ebb56b6d464a7b5a77d2b40ba5c681b57f1f661 Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Mon, 5 Aug 2019 14:03:18 +0900 Subject: [PATCH 36/40] tweak default parameter --- .../passes/mapping/algorithm/flexlayer_heuristics.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py index 119517609986..f40c9d49ddc2 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py +++ b/qiskit/transpiler/passes/mapping/algorithm/flexlayer_heuristics.py @@ -59,7 +59,7 @@ def __init__(self, dependency_graph: DependencyGraph, coupling: CouplingMap, initial_layout: Layout, - lookahead_depth: int = 5, + lookahead_depth: int = 10, decay_rate: float = 0.5): """ Initialize a FlexlayerHeuristics instance. @@ -118,9 +118,8 @@ def search(self) -> (DAGCircuit, Layout): logger.debug("#blocking_gates = %s", pprint.pformat(blocking_gates)) logger.debug("#done_gates = %d", len(dones)) - if dones: - for gidx in dones: - new_dag.apply_operation_back(*self._dg.gate(gidx, layout, qreg)) + for gidx in dones: + new_dag.apply_operation_back(*self._dg.gate(gidx, layout, qreg)) if not blocking_gates: break From 7effabddce06cb2db67d6937a0a9bdea93b63a07 Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Thu, 8 Aug 2019 10:08:48 +0900 Subject: [PATCH 37/40] update pickles for test --- .../TestsFlexlayerSwap_a_cx_to_map.pickle | Bin 1441 -> 1334 bytes ...stsFlexlayerSwap_handle_measurement.pickle | Bin 1821 -> 1592 bytes .../TestsFlexlayerSwap_initial_layout.pickle | Bin 1748 -> 1642 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/test/python/pickles/TestsFlexlayerSwap_a_cx_to_map.pickle b/test/python/pickles/TestsFlexlayerSwap_a_cx_to_map.pickle index a9aa09440fa45c16a9bf9652cb2f2244734d54aa..766a4504668fced2e2ceb0449d99cfb45a9f5df6 100644 GIT binary patch delta 657 zcmZXS%Wl(95Qc3hp;DkgMFf>7A%+4cP#W$nloD>G0T**C5H5#cr-x3Ya{298l*JKZ zt_18Tp)euFMaCNUg_^5-(^nFqOWk^dw$V uCu>#q*g>{jY0ZSJ)Yn++Zs8$&!yw#HP#Uk|&>W(-5qx-?46)9~pZy7FPQVoa literal 1441 zcma)+=~ojm6vev$;(&@9qT;>{E_L5`P(dpd3hKDjnwbVNGM)C$a;%<{bM#aHZC^5t zRrH`=n&u_B@Aq!rADg-xV9 zEV0>khDfd+Fqa3gDx&D*X)ZA0SSn-MtRr>(B*tht|1;zTI<%#nb2h2dZ5FX+jIA|n z(*ds)CQ&QESVhCu8+^qN{e)1ky^^{$>@bY$e2Dm}A16E_BPfG~okY1yVz=!KQy#O5 z@qZuJwpNWC_K?to#9q5sAh1un7W-d0_LqyP>kLu`KWy+0Cd>KMWi=e=;-Ja&H#6hU ztZgmPPiE;M%5hlYh%K~_>c-R#92KMGf*`X58FWmHRYYdOaa*YRPDq@z#kiOdljU6M z>ewj>+r$c~%WIhG;`Bx=UdNgVTS-Q8hGfr5oa>Pkh{Wrl^ee~tKF5WO97U$JH*bZN%ZSDDvq7r+Xu=6Uz;Wo633BEWHi5MKnd@%I!YQos8zL#66Rl z9MClPB_5cVA$%w?ZDJYWOc##~p=qxsXK@;FYrZ$)F;P8{m^D=rQG)%U z>h@JVt=f>_MwKpu{8`bes}jPfD&M;Bx&GcjF2(p%!O_$Yn3oWXvErIN%%QPa1ICga;U|(8kf`lHX(hv19 B&tw1q diff --git a/test/python/pickles/TestsFlexlayerSwap_handle_measurement.pickle b/test/python/pickles/TestsFlexlayerSwap_handle_measurement.pickle index 3ef77ce48848c414f260aa697eb82b78182e7fd6..dc6698007a4f155c1913d6b99fd913f58ded0801 100644 GIT binary patch literal 1592 zcma)6>3`Ek5Vf6}UK&CZaU!6~ETi{C6q$TBsd*iMm3= zc1CNpS|oZBBhGIABoTE(+3874z=UII_N>=I<^^ej$yQ^{8qtCbYjR?*~zkaz6O5op~;HuVuPHlvq9pl2pd5 z3R76v!+B0Cd}KS@IZ)}v-m=hl!;Qw*%5d1nfo2r;+gxIuLjfNf?ULY$#3!v$XPZvc zl=Z2L&up3|%{{B|xlMa%#`uG%D+ics)icj{=M@eZFU_?Yg>6CMi_$idd6La@tr|aP zrHe0z_^M3(ZFfZ0rTl&lGPkX8$R=|sI%C3NnrzjHRVP;Z2u-yqSMzIH5H7wMqPbP< zWL2zK*ZGk%e9H_+6^<1Qy4Umx9A}b7N%CDqa>Ay0THqSKAL0kn{qv*3PdS<<-UOYj zKtF4cF8tIGzmTRpt?+9BTE}#z0-e?3YtU~)EE=XIg>!FXI$wbW~e@{W^wqE9L*6puto`PT6bo*;tS3#DxH``wfai6ph z4-|-rXkGC^Rs6p+&BZ^YdyEJTqNfmTLi8)difMn4$B5V<5`}aVBC8OOXr4J%smq9g zG@}g`HNj&SPiRIMi*GDXT|A>@;Y{iEVD_Bk_0&m)7fPs&a<3gQe(Q?fi~9TxecZx- eT>o>07x~i7W4u&&m3a9c4aMVrmf|(<$mah&O#s>e literal 1821 zcma)-`JdB77{=S~a+pP3QE^qgui#Pd`(_o;O65}H5$hx!m`IxLlS2yn3V204Q1L$h zR^OS)5Ll3pzqHMpdB4x|zBBXo*j$XHIGB|EILYIRzfp;-tR~H)x7$80+qWJ%TaKek z==s5Xr_;&AL}D)3$Ul|H#;F`lq$sLfPGnYM-mDpmQeaJpwZ4;*vr=Y7GR=y9QF5Bd z$NhNbEoakmyC|iHbu6-;aFHLZW%4K$kxbzY)m+fy=Xi#Nz|-p0WP~b8$^sh(z0;vM zHKB_KonW0#UlF;O6u2bBr6v$hvofEiSR4im`u$k$PqL(BKe%jIMIknxcDkx5xjc{* zrOdf2EaBmD_IU;2N zD%`3z47!RNr?^?Ssl}nP4Y}Q@-9hl3+FTWh5L;^8c_FppkXZ5d@`u&Di#6{i+|y|4 z1Eg1=&$2xydv7aypKo;UCp_SEdrjpJ5*~6f_QI7vT;ma?56+{6$E<={I1~JM3x2|a zpCmlxAP)RA;TadREq%7eb4pu(p0Kq6pXGSD1;1dyFA}ynh})~}gdHws!7tT#**G2$ zUOAuRoh^81!LJf_If!$-o3O{lEO>8?eHYr6NaT6Kr+91gjn}xk*9mXvtehyCN?6x=VGoMf)KeFSHc?@*BCq3?&37l#M^17 ziU-L~_Y)2{r_BPW=Hzzu$xT|P?;8UL38@p~7V*iQtX7IsQ4~omQg@5JWj#hbWUM$P ztZ@GS$!RF<)duJ1gu-3z7^N~dn(r((L)560uIdBAAzq`-s$|wya@bUIgz%xOL}!Qi zNNI;e_*lntI|-P2)KHHRj-NyQq@{jpsGku&cT{_`zcAFA@TH^jCNcGdp?*d9`W)&v zE%jSNJxTb^QFV5R?+x__!YN19YBhe8X1sxWU*M+@KdU8SBC(1565>~ti(rwDrk&=J zhw!KrQ7ZA<>RmD8UN123y&Jb1xrg7mTYnJ#w0|v@@fYFmA~KOw9G+Gs{^37@ F{=bm?O;i8? diff --git a/test/python/pickles/TestsFlexlayerSwap_initial_layout.pickle b/test/python/pickles/TestsFlexlayerSwap_initial_layout.pickle index 065f2e2cec823718ac704082d4d647ac1790618a..07c47dc84a074de31f5dcaf1026619cec75f6f0f 100644 GIT binary patch literal 1642 zcma)6`+pNf5Ko%&IuNTCtf;lMDu<7#_5DPtSnNS41?3cvuyy zmsUj~9US50M-`5_{$b9@sY02+=}^mW@|kelcQk3)@1hLjBF71@xo?URV|vPK_(wE) zC^FH{aWcTu#t;qCA{!)_>G;RBzbEgSI}cJk(<#FM&)Pd4XhkOP#d#q!?gcYAc#dy5 zt8mKok8&RC#p(Zso6c_7baDw;N)1nOU&KT_{@kAqM zB^?wM%e&0-p2GVz%M`OjBP>74@xg@SLzhdRXO54?`1na2p~$kB=Xjz1;uGfiRN*r{ z0Iiui0G~6{w5{fg3DcJ@Ez(lW_SG0)TQ;khz7%NO4VCpOr3I4$NH3iQ~Fv4YG z`Z^JcF2x`(lOgSQN1}dZdc(CYva{mYSn3D$B48+kXRUc@s1M literal 1748 zcma)6>7Ud@5S`gsu7J1-!pfl{9sza4^#a6;6*&UKwO|y+v6E@&NG3Bc$19 z+ar;RL58(mJZpe(nB?g&#!SawtBj`s+2HYe9_bi7aaz+$Y8b$nRjqbKPIM4Cq2!2R_TAG4O-ggx4MJuGzq_OhC3 z>&hn+HTx}VpXU1ugfn)kL!4D+n5$6|CB9L*DxdHz$Ebf*^4+A8bEcB>gs!cm z`T)LHT49gygT@z_RpTXuVGRi3W32rNt2C?^2p26YH;w&KhBYGmXj!#yB`z7(m@s&Z zHJPvu4eN*i%gQZg*3__Ogxs=fyhI_*mkstl!(tbg)vPc`XpnIiSJZ;=XLvNLJtleo z^l}lz5?7aY#Vk4o;byh^d!xBU+#>wu@($4}<2IqxRqCyFhw!`hYty67bKN|vI_@sv z4;^=>ng`xO8ofujZ&B{2*D~k>!k-r9eiUXs_7>+x_>1tjt8R1dyUcr!Ssr+;f Date: Thu, 8 Aug 2019 11:17:57 +0900 Subject: [PATCH 38/40] consider reset in dependency graph --- qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py index 2e855ea9bacf..5abc88a98561 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py +++ b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py @@ -95,7 +95,7 @@ def __init__(self, def _create_xz_graph(self): z_gates = ["u1", "rz", "s", "t", "z", "sdg", "tdg"] x_gates = ["rx", "x"] - b_gates = ["u3", "h", "u2", "ry", "barrier", "measure", "swap", "y"] + b_gates = ["u3", "h", "u2", "ry", "swap", "y", "barrier", "measure", "reset"] # construct commutation-rules-aware dependency graph for n in self._graph.nodes(): if self._gates[n].name in x_gates: From b04dd0c3841023fad8759f7e83fbd8bdc7e1d024 Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Wed, 28 Aug 2019 16:32:50 +0900 Subject: [PATCH 39/40] consider id gate in dependency graph --- qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py index 5abc88a98561..f810b7fb7543 100644 --- a/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py +++ b/qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py @@ -95,7 +95,7 @@ def __init__(self, def _create_xz_graph(self): z_gates = ["u1", "rz", "s", "t", "z", "sdg", "tdg"] x_gates = ["rx", "x"] - b_gates = ["u3", "h", "u2", "ry", "swap", "y", "barrier", "measure", "reset"] + b_gates = ["u3", "h", "u2", "ry", "swap", "y", "barrier", "measure", "reset", "id"] # construct commutation-rules-aware dependency graph for n in self._graph.nodes(): if self._gates[n].name in x_gates: From 9ec90d18447d3932e0ed564e285554cadc5ee1e0 Mon Sep 17 00:00:00 2001 From: Toshinari Itoko Date: Wed, 28 Aug 2019 16:51:01 +0900 Subject: [PATCH 40/40] add release note --- .../add-flexlayer-swap-pass-da827619e619faa1.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 releasenotes/notes/add-flexlayer-swap-pass-da827619e619faa1.yaml diff --git a/releasenotes/notes/add-flexlayer-swap-pass-da827619e619faa1.yaml b/releasenotes/notes/add-flexlayer-swap-pass-da827619e619faa1.yaml new file mode 100644 index 000000000000..cdc3e9c62db8 --- /dev/null +++ b/releasenotes/notes/add-flexlayer-swap-pass-da827619e619faa1.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + Added a `FlexlayerSwap` pass, that implements a look-ahead heuristic + mapping algorithm. It can be used by replacing the `StochasticSwap`, + for example:: + + # in transpile/preset_passmanagers/level1.py + from qiskit.transpiler.passes import FlexlayerSwap + + FlexlayerSwap(coupling_map), # instead of StochasticSwap(...),