mirror of https://github.com/Qiskit/qiskit.git
Performance improvements for collect_2q_blocks (#6433)
* 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 <mtreinish@kortar.org> * Fix indentation in release note Co-authored-by: Matthew Treinish <mtreinish@kortar.org> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
parent
33e7448a22
commit
0240377262
|
@ -1302,17 +1302,22 @@ class DAGCircuit:
|
|||
"""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."""
|
||||
|
@ -1332,14 +1337,11 @@ class DAGCircuit:
|
|||
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.
|
||||
|
|
|
@ -58,13 +58,13 @@ class Collect2qBlocks(AnalysisPass):
|
|||
# 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
|
||||
|
@ -75,13 +75,13 @@ class Collect2qBlocks(AnalysisPass):
|
|||
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
|
||||
|
@ -89,52 +89,52 @@ class Collect2qBlocks(AnalysisPass):
|
|||
# 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]:
|
||||
|
@ -154,7 +154,7 @@ class Collect2qBlocks(AnalysisPass):
|
|||
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
|
||||
|
@ -165,13 +165,13 @@ class Collect2qBlocks(AnalysisPass):
|
|||
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
|
||||
|
@ -179,55 +179,55 @@ class Collect2qBlocks(AnalysisPass):
|
|||
# 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]:
|
||||
|
|
|
@ -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.
|
|
@ -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
|
||||
|
|
|
@ -515,6 +515,20 @@ class TestDagNodeSelection(QiskitTestCase):
|
|||
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"""
|
||||
|
||||
|
@ -550,6 +564,21 @@ class TestDagNodeSelection(QiskitTestCase):
|
|||
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], [])
|
||||
|
|
Loading…
Reference in New Issue