Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add flexible-layer mapper (FlexlayerSwap) pass #1803

Closed
wants to merge 46 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
42db44d
add Register
itoko Feb 12, 2019
cd13703
add undirected_neighbors
itoko Feb 12, 2019
566cb30
WIP: add flexlayer swapper pass
itoko Feb 12, 2019
2d04837
fix #1791
itoko Feb 12, 2019
3f65ed6
Merge remote-tracking branch 'upstream/master' into pr-flexlayer
itoko Feb 13, 2019
b702400
add comments and lint
itoko Feb 13, 2019
7c03055
lint
itoko Feb 13, 2019
4450eb0
lint
itoko Feb 13, 2019
f432021
fix #1791
itoko Feb 13, 2019
6560757
add comments and refactor variable names
itoko Feb 13, 2019
18f08d2
linting
itoko Feb 13, 2019
7c7274b
Merge remote-tracking branch 'upstream/master' into pr-flexlayer
itoko Feb 14, 2019
c04d7d0
pylint for test_dependency_graph.py and test_flexlayer_heuristics.py
a-matsuo Feb 14, 2019
eb4252a
modified test_dependenchy_graph.py and test_flexlayer_heuristics.py
a-matsuo Feb 14, 2019
16a3c32
Merge pull request #1 from itoko/pylint-matsuo
itoko Feb 14, 2019
87e9c1f
merge master
itoko Jul 5, 2019
5b0a716
update aligning changes in basic classes
itoko Jul 5, 2019
e119d1d
remove initial_layout from constructor's argument
itoko Jul 9, 2019
37c1817
lint and replace crlf to lf
itoko Jul 9, 2019
4470ebe
increase human readability of print
itoko Jul 10, 2019
cf85e93
remove unused lines
itoko Jul 10, 2019
ccb0a3d
add more tests
itoko Jul 10, 2019
7c4e089
change to return iterator
itoko Jul 12, 2019
f02cc96
remove layer-type dependency graph
itoko Jul 12, 2019
6e783cb
increase readability
itoko Jul 12, 2019
46081ea
add type hint
itoko Jul 12, 2019
2d1a577
update comments
itoko Jul 12, 2019
ac4ec1d
expose dependency_graph_type parameter
itoko Jul 12, 2019
001d725
lint
itoko Jul 12, 2019
504c8a6
simplify cache algorithm
itoko Jul 12, 2019
8e79ae5
add FlexlayerSwap to swapper common test
itoko Jul 16, 2019
6d8d04a
move ancestors to dependency_graph
itoko Jul 17, 2019
31a9890
improve performance
itoko Jul 17, 2019
32cef83
fix RecursionError in ancestors for large circuits
itoko Jul 17, 2019
c42a381
lint
itoko Jul 17, 2019
ccc65ea
improve performance without ancestors
itoko Jul 17, 2019
4fd0103
add comments and do some renames
itoko Jul 24, 2019
44cf705
rename private classes and remove unused private method
itoko Jul 24, 2019
e341920
remove useless lines
itoko Jul 24, 2019
2ebb56b
tweak default parameter
itoko Aug 5, 2019
6867a21
Merge remote-tracking branch 'upstream/master' into pr-flexlayer
itoko Aug 8, 2019
7effabd
update pickles for test
itoko Aug 8, 2019
a762392
consider reset in dependency graph
itoko Aug 8, 2019
a8e0162
Merge remote-tracking branch 'upstream/master' into pr-flexlayer
itoko Aug 27, 2019
b04dd0c
consider id gate in dependency graph
itoko Aug 28, 2019
9ec90d1
add release note
itoko Aug 28, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions qiskit/transpiler/coupling.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,17 @@ 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):
itoko marked this conversation as resolved.
Show resolved Hide resolved
"""List up neighbors of the `physical_qubit` in the undirected coupling graph.

Args:
physical_qubit (int): A physical qubit whose neighors should be listed up

Returns:
iterator: The neighbors of the physical qubit
"""
return self.graph.to_undirected().neighbors(physical_qubit)

@property
def is_symmetric(self):
"""
Expand Down
1 change: 1 addition & 0 deletions qiskit/transpiler/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -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})"

Expand Down
1 change: 1 addition & 0 deletions qiskit/transpiler/passes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,4 @@
from .mapping.lookahead_swap import LookaheadSwap
from .remove_diagonal_gates_before_measure import RemoveDiagonalGatesBeforeMeasure
from .mapping.stochastic_swap import StochasticSwap
from .mapping.flexlayer_swap import FlexlayerSwap
15 changes: 15 additions & 0 deletions qiskit/transpiler/passes/mapping/algorithm/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# -*- 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.

"""Module containing algorithms used in transpiler pass."""
302 changes: 302 additions & 0 deletions qiskit/transpiler/passes/mapping/algorithm/dependency_graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
# -*- 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. 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
from typing import List, FrozenSet

import networkx as nx
from qiskit.circuit import QuantumCircuit, QuantumRegister
from qiskit.circuit import Qubit
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.layout import Layout

InstructionContext = namedtuple("InstructionContext", "op qargs cargs")


class ArgumentedGate(InstructionContext):
"""
A wrapper class of `InstructionContext` (operation with args context).
"""

@property
def name(self) -> str:
"""Name of this instruction."""
return self.op.name


class DependencyGraph:
"""
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,
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:
Rz-CX(control), Rx-CX(target), CX-CX(controls), CX-CX(targets).
- "basic": consider only the commutation between gates without sharing qubits.

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))

# for speed up of _prior_gates_on_wire
self._create_gates_by_qubit()

if graph_type == "xz_commute":
self._create_xz_graph()
elif graph_type == "basic":
self._create_basic_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", "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:
b = self._gates[n].qargs[0] # acting qubit of gate n
# pylint: disable=unbalanced-tuple-unpacking
[pgow] = self._prior_gates_on_sharing_qubits_of(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[0] # acting qubit of gate n
# pylint: disable=unbalanced-tuple-unpacking
[pgow] = self._prior_gates_on_sharing_qubits_of(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_sharing_qubits_of(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:
all_args_of_n = self._gates[n].qargs + self._gates[n].cargs
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:
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_sharing_qubits_of(n):
m = next(pgow, -1)
if m != -1:
self._graph.add_edge(m, n)

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`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain the API to me? If I am using this data structure, and I want the qubit args from a gate in my graph, how do I know what index to use? As a user, I probably shouldn't be relying on _gates since it is marked private.

Copy link
Contributor Author

@itoko itoko Jul 17, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this API, DependencyGraph depends on QuantumCircuit (say qc) and all of the gates are considered as integers, which are the indices of qc.data. Since DependencyGraph is constructed from a QuantumCircuit, user knows all _gates as qc.data. All public functions (except for gates) accept and return integers. I might set gate objects directly as nodes of graph instead of integers, but I didn't do so, just because object id can be changed even though it would be a very very rare case (Am I misunderstanding Python spec?).
BTW. Excuse me for my confusing variable names gate/gates, in this case I should use instruction instead of gate, or more precisely instruction_context, because they include non-gate instructions like measure and their acting (qu)bits. I had followed naming convention in old-version qiskit, but what gate means was changed at some point. Should I refactor all names around gate?

Copy link
Contributor

@lcapelluto lcapelluto Jul 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! Could you put just a little of the essence of that description into the docstrings, so that other people with the same question might understand when they come across this? At least enough so they could know how to use it.

If you think there is a better name, I am all for updating it! :D Don't be afraid to use a better name when you think of it! And it's easier to change the sooner you do so :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update docstrings at 4fd0103

Returns:
List of qubit arguments.
"""
return self._gates[i].qargs

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) -> FrozenSet[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 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

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)
"""
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])

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_qubit[qarg] if i < toidx]))
for carg in self._gates[toidx].cargs:
res.append(reversed([i for i in self._gates_by_qubit[carg] if i < toidx]))
return res

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_qubit[qarg].append(i)
for carg in gate.cargs:
self._gates_by_qubit[carg].append(i)
Loading