diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 1da0ea71e0..36f88bd03f 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -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. diff --git a/qiskit/transpiler/passes/optimization/collect_2q_blocks.py b/qiskit/transpiler/passes/optimization/collect_2q_blocks.py index 000bfe5845..226ec0bf19 100644 --- a/qiskit/transpiler/passes/optimization/collect_2q_blocks.py +++ b/qiskit/transpiler/passes/optimization/collect_2q_blocks.py @@ -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]: diff --git a/releasenotes/notes/bump-retworkx-59c7ab59ad02c3c0.yaml b/releasenotes/notes/bump-retworkx-59c7ab59ad02c3c0.yaml new file mode 100644 index 0000000000..7d7ae40fbf --- /dev/null +++ b/releasenotes/notes/bump-retworkx-59c7ab59ad02c3c0.yaml @@ -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 `_ 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. diff --git a/requirements.txt b/requirements.txt index 964c637b77..9e7be907d3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 53d2f36be8..564fd133de 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -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], [])