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

Performance improvements for collect_2q_blocks #6433

Merged
merged 22 commits into from
Jun 8, 2021
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2a086ab
Use find_successors_by_edge in quantum_successors
IvanIsCoding May 16, 2021
96bafa9
Add is_successors to DAGCircuit and collect_2q
IvanIsCoding May 16, 2021
145ecc1
Replace qargs with _qargs in collect_2q_blocks
IvanIsCoding May 16, 2021
b7030dc
Replace op with _op in collect_2q_blocks
IvanIsCoding May 16, 2021
83a29fc
Add tests for is_sucessor/is_predecessor
IvanIsCoding May 18, 2021
102674f
Modify requirements.txt temporarily to fetch latest retworkx code
IvanIsCoding May 18, 2021
cc64b94
Correct syntax in requirements.txt to temporarily fetch latest retworkx
IvanIsCoding May 18, 2021
44c9216
Merge remote-tracking branch 'origin/main' into improve_collect2q
IvanIsCoding May 21, 2021
7948add
Use Qiskit/retworkx@main
IvanIsCoding May 21, 2021
b0f9f14
Update requirements.txt
mtreinish Jun 1, 2021
adeab0e
Merge branch 'main' into improve_collect2q
mtreinish Jun 1, 2021
11159af
Merge remote-tracking branch 'origin/main' into improve_collect2q
IvanIsCoding Jun 1, 2021
008b056
Merge branch 'improve_collect2q' of github.com:IvanIsCoding/qiskit-te…
IvanIsCoding Jun 1, 2021
0ade23f
Add release note about retworkx upgrade
IvanIsCoding Jun 1, 2021
e2935d7
Merge branch 'main' into improve_collect2q
mtreinish Jun 2, 2021
86af1a1
Update releasenotes/notes/bump-retworkx-59c7ab59ad02c3c0.yaml
IvanIsCoding Jun 2, 2021
485a308
Merge branch 'main' into improve_collect2q
IvanIsCoding Jun 3, 2021
517723a
Fix indentation in release note
IvanIsCoding Jun 5, 2021
86be667
Merge branch 'main' into improve_collect2q
mergify[bot] Jun 7, 2021
913de5c
Merge branch 'main' into improve_collect2q
mergify[bot] Jun 7, 2021
09e4e85
Merge branch 'main' into improve_collect2q
IvanIsCoding Jun 8, 2021
49ede15
Merge branch 'main' into improve_collect2q
mergify[bot] Jun 8, 2021
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
34 changes: 18 additions & 16 deletions qiskit/dagcircuit/dagcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1302,17 +1302,22 @@ def predecessors(self, node):
"""Returns iterator of the predecessors of a node as DAGNodes."""
return iter(self._multi_graph.predecessors(node._node_id))

def is_successor(self, node, node_succ):
"""Checks if a second node is in the successors of node."""
return self._multi_graph.has_edge(node._node_id, node_succ._node_id)

def is_predecessor(self, node, node_pred):
"""Checks if a second node is in the predecessors of node."""
return self._multi_graph.has_edge(node_pred._node_id, node._node_id)

def quantum_predecessors(self, node):
"""Returns iterator of the predecessors of a node that are
connected by a quantum edge as DAGNodes."""
for predecessor in self.predecessors(node):
if any(
isinstance(edge_data, Qubit)
for edge_data in self._multi_graph.get_all_edge_data(
predecessor._node_id, node._node_id
)
):
yield predecessor
return iter(
self._multi_graph.find_predecessors_by_edge(
node._node_id, lambda edge_data: isinstance(edge_data, Qubit)
)
)

def ancestors(self, node):
"""Returns set of the ancestors of a node as DAGNodes."""
Expand All @@ -1332,14 +1337,11 @@ def bfs_successors(self, node):
def quantum_successors(self, node):
"""Returns iterator of the successors of a node that are
connected by a quantum edge as DAGNodes."""
for successor in self.successors(node):
if any(
isinstance(edge_data, Qubit)
for edge_data in self._multi_graph.get_all_edge_data(
node._node_id, successor._node_id
)
):
yield successor
return iter(
self._multi_graph.find_successors_by_edge(
node._node_id, lambda edge_data: isinstance(edge_data, Qubit)
)
)

def remove_op_node(self, node):
"""Remove an operation node n.
Expand Down
96 changes: 48 additions & 48 deletions qiskit/transpiler/passes/optimization/collect_2q_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ def run(self, dag):
# Explore predecessors and successors of 2q gates
if ( # pylint: disable=too-many-boolean-expressions
nd.type == "op"
and isinstance(nd.op, Gate)
and len(nd.qargs) == 2
and isinstance(nd._op, Gate)
and len(nd._qargs) == 2
and not nodes_seen[nd]
and nd.op.condition is None
and not nd.op.is_parameterized()
and nd._op.condition is None
and not nd._op.is_parameterized()
):
these_qubits = set(nd.qargs)
these_qubits = set(nd._qargs)
# Explore predecessors of the 2q node
pred = list(dag.quantum_predecessors(nd))
explore = True
Expand All @@ -75,66 +75,66 @@ def run(self, dag):
pnd = pred[0]
if (
pnd.type == "op"
and isinstance(pnd.op, Gate)
and len(pnd.qargs) <= 2
and pnd.op.condition is None
and not pnd.op.is_parameterized()
and isinstance(pnd._op, Gate)
and len(pnd._qargs) <= 2
and pnd._op.condition is None
and not pnd._op.is_parameterized()
):
if (len(pnd.qargs) == 2 and set(pnd.qargs) == these_qubits) or len(
pnd.qargs
if (len(pnd._qargs) == 2 and set(pnd._qargs) == these_qubits) or len(
pnd._qargs
) == 1:
group.append(pnd)
nodes_seen[pnd] = True
pred_next.extend(dag.quantum_predecessors(pnd))
# If there are two, then we consider cases
elif len(pred) == 2:
# First, check if there is a relationship
if pred[0] in dag.predecessors(pred[1]):
if dag.is_predecessor(pred[1], pred[0]):
sorted_pred = [pred[1]] # was [pred[1], pred[0]]
elif pred[1] in dag.predecessors(pred[0]):
elif dag.is_predecessor(pred[0], pred[1]):
sorted_pred = [pred[0]] # was [pred[0], pred[1]]
else:
# We need to avoid accidentally adding a 2q gate on these_qubits
# since these must have a dependency through the other predecessor
# in this case
if len(pred[0].qargs) == 2 and set(pred[0].qargs) == these_qubits:
if len(pred[0]._qargs) == 2 and set(pred[0]._qargs) == these_qubits:
sorted_pred = [pred[1]]
elif len(pred[1].qargs) == 1 and set(pred[1].qargs) == these_qubits:
elif len(pred[1]._qargs) == 1 and set(pred[1]._qargs) == these_qubits:
sorted_pred = [pred[0]]
else:
sorted_pred = pred
if (
len(sorted_pred) == 2
and len(sorted_pred[0].qargs) == 2
and len(sorted_pred[1].qargs) == 2
and len(sorted_pred[0]._qargs) == 2
and len(sorted_pred[1]._qargs) == 2
):
break # stop immediately if we hit a pair of 2q gates
# Examine each predecessor
for pnd in sorted_pred:
if (
pnd.type != "op"
or not isinstance(pnd.op, Gate)
or len(pnd.qargs) > 2
or pnd.op.condition is not None
or pnd.op.is_parameterized()
or not isinstance(pnd._op, Gate)
or len(pnd._qargs) > 2
or pnd._op.condition is not None
or pnd._op.is_parameterized()
):
# remove any qubits that are interrupted by a gate
# e.g. a measure in the middle of the circuit
these_qubits = list(set(these_qubits) - set(pnd.qargs))
these_qubits = list(set(these_qubits) - set(pnd._qargs))
continue
# If a predecessor is a single qubit gate, add it
if len(pnd.qargs) == 1 and not pnd.op.is_parameterized():
if len(pnd._qargs) == 1 and not pnd._op.is_parameterized():
if not nodes_seen[pnd]:
group.append(pnd)
nodes_seen[pnd] = True
pred_next.extend(dag.quantum_predecessors(pnd))
# If 2q, check qubits
else:
pred_qubits = set(pnd.qargs)
pred_qubits = set(pnd._qargs)
if (
pred_qubits == these_qubits
and pnd.op.condition is None
and not pnd.op.is_parameterized()
and pnd._op.condition is None
and not pnd._op.is_parameterized()
):
# add if on same qubits
if not nodes_seen[pnd]:
Expand All @@ -154,7 +154,7 @@ def run(self, dag):
group.append(nd)
nodes_seen[nd] = True
# Reset these_qubits
these_qubits = set(nd.qargs)
these_qubits = set(nd._qargs)
# Explore successors of the 2q node
succ = list(dag.quantum_successors(nd))
explore = True
Expand All @@ -165,69 +165,69 @@ def run(self, dag):
snd = succ[0]
if (
snd.type == "op"
and isinstance(snd.op, Gate)
and len(snd.qargs) <= 2
and snd.op.condition is None
and not snd.op.is_parameterized()
and isinstance(snd._op, Gate)
and len(snd._qargs) <= 2
and snd._op.condition is None
and not snd._op.is_parameterized()
):
if (len(snd.qargs) == 2 and set(snd.qargs) == these_qubits) or len(
snd.qargs
if (len(snd._qargs) == 2 and set(snd._qargs) == these_qubits) or len(
snd._qargs
) == 1:
group.append(snd)
nodes_seen[snd] = True
succ_next.extend(dag.quantum_successors(snd))
# If there are two, then we consider cases
elif len(succ) == 2:
# First, check if there is a relationship
if succ[0] in dag.successors(succ[1]):
if dag.is_successor(succ[1], succ[0]):
sorted_succ = [succ[1]] # was [succ[1], succ[0]]
elif succ[1] in dag.successors(succ[0]):
elif dag.is_successor(succ[0], succ[1]):
sorted_succ = [succ[0]] # was [succ[0], succ[1]]
else:
# We need to avoid accidentally adding a 2q gate on these_qubits
# since these must have a dependency through the other successor
# in this case
if len(succ[0].qargs) == 2 and set(succ[0].qargs) == these_qubits:
if len(succ[0]._qargs) == 2 and set(succ[0]._qargs) == these_qubits:
sorted_succ = [succ[1]]
elif len(succ[1].qargs) == 2 and set(succ[1].qargs) == these_qubits:
elif len(succ[1]._qargs) == 2 and set(succ[1]._qargs) == these_qubits:
sorted_succ = [succ[0]]
else:
sorted_succ = succ
if (
len(sorted_succ) == 2
and len(sorted_succ[0].qargs) == 2
and len(sorted_succ[1].qargs) == 2
and len(sorted_succ[0]._qargs) == 2
and len(sorted_succ[1]._qargs) == 2
):
break # stop immediately if we hit a pair of 2q gates
# Examine each successor
for snd in sorted_succ:
if (
snd.type != "op"
or not isinstance(snd.op, Gate)
or len(snd.qargs) > 2
or snd.op.condition is not None
or snd.op.is_parameterized()
or not isinstance(snd._op, Gate)
or len(snd._qargs) > 2
or snd._op.condition is not None
or snd._op.is_parameterized()
):
# remove qubits from consideration if interrupted
# by a gate e.g. a measure in the middle of the circuit
these_qubits = list(set(these_qubits) - set(snd.qargs))
these_qubits = list(set(these_qubits) - set(snd._qargs))
continue

# If a successor is a single qubit gate, add it
# NB as we have eliminated all gates with names not in
# good_names, this check guarantees they are single qubit
if len(snd.qargs) == 1 and not snd.op.is_parameterized():
if len(snd._qargs) == 1 and not snd._op.is_parameterized():
if not nodes_seen[snd]:
group.append(snd)
nodes_seen[snd] = True
succ_next.extend(dag.quantum_successors(snd))
else:
# If 2q, check qubits
succ_qubits = set(snd.qargs)
succ_qubits = set(snd._qargs)
if (
succ_qubits == these_qubits
and snd.op.condition is None
and not snd.op.is_parameterized()
and snd._op.condition is None
and not snd._op.is_parameterized()
):
# add if on same qubits
if not nodes_seen[snd]:
Expand Down
11 changes: 11 additions & 0 deletions releasenotes/notes/bump-retworkx-59c7ab59ad02c3c0.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
features:
- |
Added two new methods, :meth:`~qiskit.dagcircuit.DAGCircuit.is_successor` and
:meth:`~qiskit.dagcircuit.DAGCircuit.is_predecessor`, to the
:class:`~qiskit.dagcircuit.DAGCircuit` class. These functions are used to check if a node
is either a successor or predecessor of another node on the :class:`~qiskit.dagcircuit.DAGCircuit`.
upgrade:
- |
The minimum version of the `retworkx <https://pypi.org/project/retworkx/>`_ dependency
was increased to version `0.9.0`. This was done to use new APIs introduced in that release
which improved the performance of some transpiler passes.
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
contextvars>=2.4;python_version<'3.7'
jsonschema>=2.6
retworkx>=0.8.0
retworkx>=0.9.0
numpy>=1.17
ply>=3.10
psutil>=5
Expand Down
29 changes: 29 additions & 0 deletions test/python/dagcircuit/test_dagcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,20 @@ def test_quantum_successors(self):
or (successor2.type == "out" and isinstance(successor1.op, Reset))
)

def test_is_successor(self):
"""The method dag.is_successor(A, B) checks if node B is a successor of A"""
self.dag.apply_operation_back(Measure(), [self.qubit1, self.clbit1], [])
self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], [])
self.dag.apply_operation_back(Reset(), [self.qubit0], [])

measure_node = self.dag.named_nodes("measure")[0]
cx_node = self.dag.named_nodes("cx")[0]
reset_node = self.dag.named_nodes("reset")[0]

self.assertTrue(self.dag.is_successor(measure_node, cx_node))
self.assertFalse(self.dag.is_successor(measure_node, reset_node))
self.assertTrue(self.dag.is_successor(cx_node, reset_node))

def test_quantum_predecessors(self):
"""The method dag.quantum_predecessors() returns predecessors connected by quantum edges"""

Expand Down Expand Up @@ -550,6 +564,21 @@ def test_quantum_predecessors(self):
or (predecessor2.type == "in" and isinstance(predecessor1.op, Reset))
)

def test_is_predecessor(self):
"""The method dag.is_predecessor(A, B) checks if node B is a predecessor of A"""

self.dag.apply_operation_back(Measure(), [self.qubit1, self.clbit1], [])
self.dag.apply_operation_back(CXGate(), [self.qubit0, self.qubit1], [])
self.dag.apply_operation_back(Reset(), [self.qubit0], [])

measure_node = self.dag.named_nodes("measure")[0]
cx_node = self.dag.named_nodes("cx")[0]
reset_node = self.dag.named_nodes("reset")[0]

self.assertTrue(self.dag.is_predecessor(cx_node, measure_node))
self.assertFalse(self.dag.is_predecessor(reset_node, measure_node))
self.assertTrue(self.dag.is_predecessor(reset_node, cx_node))

def test_get_gates_nodes(self):
"""The method dag.gate_nodes() returns all gate nodes"""
self.dag.apply_operation_back(HGate(), [self.qubit0], [])
Expand Down