Skip to content

Commit

Permalink
Performance improvements for collect_2q_blocks (#6433)
Browse files Browse the repository at this point in the history
* Use find_successors_by_edge in quantum_successors

* Add is_successors to DAGCircuit and collect_2q

* Replace qargs with _qargs in collect_2q_blocks

* Replace op with _op in collect_2q_blocks

* Add tests for is_sucessor/is_predecessor

* Modify requirements.txt temporarily to fetch latest retworkx code

* Correct syntax in requirements.txt to temporarily fetch latest retworkx

* Use Qiskit/retworkx@main

* Update requirements.txt

* Add release note about retworkx upgrade

* Update releasenotes/notes/bump-retworkx-59c7ab59ad02c3c0.yaml

Co-authored-by: Matthew Treinish <[email protected]>

* Fix indentation in release note

Co-authored-by: Matthew Treinish <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Jun 8, 2021
1 parent 33e7448 commit 0240377
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 65 deletions.
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

0 comments on commit 0240377

Please sign in to comment.