From c7ef19b9765bc75edf3cf937b5a3d5e61761d859 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Wed, 25 Jan 2023 14:50:47 -0600 Subject: [PATCH 01/24] Add decompose_dag method to DAGCircuit class --- qiskit/dagcircuit/dagcircuit.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 0368bb8df7c2..485f8079b543 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1393,6 +1393,38 @@ def substitute_node(self, node, op, inplace=False): self._decrement_op(node.op) return new_node + def decompose_dag(self) -> List["DAGCircuit"]: + """Decompose the dag circuit into its disconnected components.""" + disconnected_components = rx.weakly_connected_components(self._multi_graph) + + # Collect each disconnected subgraph + disconnected_subgraphs = [] + for components in disconnected_components: + disconnected_subgraphs.append(self._multi_graph.subgraph(list(components))) + + # Create new DAGCircuit objects from each of the rustworkx subgraph objects + decomposed_dags = [] + for subgraph in disconnected_subgraphs: + new_dag = DAGCircuit() + new_dag.add_qubits(self.qubits) + for node in subgraph.nodes(): + if not isinstance(node, DAGOpNode): + continue + new_dag.apply_operation_back(node.op, node.qargs) + decomposed_dags.append(new_dag) + + # Clean up idle wires from each DAG + for dag in decomposed_dags: + idle_qubits = [] + # These loops are done consecutively since the second loop changes size + # of data idle_wires() generator is acting on + for qubit in dag.idle_wires(): + idle_qubits.append(qubit) + for qubit in idle_qubits: + dag.remove_qubits(qubit) + + return decomposed_dags + def node(self, node_id): """Get the node in the dag. From e1533639287adf831d3b7c75cb194fd67136cd1f Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Thu, 26 Jan 2023 16:25:58 -0600 Subject: [PATCH 02/24] Don't clean up idle wires within function --- qiskit/dagcircuit/dagcircuit.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 485f8079b543..3297b77117c7 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1393,13 +1393,13 @@ def substitute_node(self, node, op, inplace=False): self._decrement_op(node.op) return new_node - def decompose_dag(self) -> List["DAGCircuit"]: - """Decompose the dag circuit into its disconnected components.""" - disconnected_components = rx.weakly_connected_components(self._multi_graph) + def weakly_connected_components(self) -> List["DAGCircuit"]: + """Decompose the dag circuit into its weakly connected components.""" + connected_components = rx.weakly_connected_components(self._multi_graph) # Collect each disconnected subgraph disconnected_subgraphs = [] - for components in disconnected_components: + for components in connected_components: disconnected_subgraphs.append(self._multi_graph.subgraph(list(components))) # Create new DAGCircuit objects from each of the rustworkx subgraph objects @@ -1413,16 +1413,6 @@ def decompose_dag(self) -> List["DAGCircuit"]: new_dag.apply_operation_back(node.op, node.qargs) decomposed_dags.append(new_dag) - # Clean up idle wires from each DAG - for dag in decomposed_dags: - idle_qubits = [] - # These loops are done consecutively since the second loop changes size - # of data idle_wires() generator is acting on - for qubit in dag.idle_wires(): - idle_qubits.append(qubit) - for qubit in idle_qubits: - dag.remove_qubits(qubit) - return decomposed_dags def node(self, node_id): From 89633c75b2d5d7ee3cece1cce7f93c214799abe3 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Tue, 31 Jan 2023 11:34:10 -0600 Subject: [PATCH 03/24] Add tests for DAGCircuit.separable_circuits --- qiskit/dagcircuit/dagcircuit.py | 16 ++++++++++----- test/python/dagcircuit/test_dagcircuit.py | 24 +++++++++++++++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 3297b77117c7..74ed141172fd 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1393,8 +1393,12 @@ def substitute_node(self, node, op, inplace=False): self._decrement_op(node.op) return new_node - def weakly_connected_components(self) -> List["DAGCircuit"]: - """Decompose the dag circuit into its weakly connected components.""" + def separable_circuits(self) -> List["DAGCircuit"]: + """Separate the dag circuit into its weakly connected components. + + The global phase information in `self` will not be maintained in the + subcircuits returned by this method. + """ connected_components = rx.weakly_connected_components(self._multi_graph) # Collect each disconnected subgraph @@ -1405,13 +1409,15 @@ def weakly_connected_components(self) -> List["DAGCircuit"]: # Create new DAGCircuit objects from each of the rustworkx subgraph objects decomposed_dags = [] for subgraph in disconnected_subgraphs: - new_dag = DAGCircuit() - new_dag.add_qubits(self.qubits) + new_dag = self.copy_empty_like() for node in subgraph.nodes(): if not isinstance(node, DAGOpNode): continue new_dag.apply_operation_back(node.op, node.qargs) - decomposed_dags.append(new_dag) + + # Ignore DAGs created for empty qubits and classical bits + if new_dag.op_nodes(): + decomposed_dags.append(new_dag) return decomposed_dags diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 9db67f55f7ec..d88603b82afc 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -1223,6 +1223,30 @@ def test_circuit_factors(self): """Test number of separable factors in circuit.""" self.assertEqual(self.dag.num_tensor_factors(), 2) + def test_separable_circuits(self): + """Test separating disconnected sets of qubits in a circuit.""" + # Empty case + dag = DAGCircuit() + self.assertEqual([], dag.separable_circuits()) + + # 3 disconnected qubits + qreg = QuantumRegister(3, "qr") + creg = ClassicalRegister(2, "cr") + dag.add_qreg(qreg) + dag.add_creg(creg) + dag.apply_operation_back(HGate(), [qreg[0]], []) + dag.apply_operation_back(HGate(), [qreg[1]], []) + dag.apply_operation_back(HGate(), [qreg[2]], []) + self.assertEqual(3, len(dag.separable_circuits())) + + # 2 sets of disconnected qubits + dag.apply_operation_back(CXGate(), [qreg[1], qreg[2]], []) + self.assertEqual(2, len(dag.separable_circuits())) + + # One connected component + dag.apply_operation_back(CXGate(), [qreg[0], qreg[1]], []) + self.assertEqual(1, len(dag.separable_circuits())) + class TestCircuitControlFlowProperties(QiskitTestCase): """Properties tests of DAGCircuit with control-flow instructions.""" From 898844819a6b952a8577c9dbe061b4c244a4f430 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Wed, 1 Feb 2023 14:30:20 -0600 Subject: [PATCH 04/24] Add ordering and QuantumCircuit equivalency tests. Add release note. --- qiskit/dagcircuit/dagcircuit.py | 10 ++++-- ...t-separable-circuits-142853e69f530a16.yaml | 15 ++++++++ test/python/dagcircuit/test_dagcircuit.py | 34 ++++++++++++++++++- 3 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 74ed141172fd..74ea235ad37c 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1406,14 +1406,20 @@ def separable_circuits(self) -> List["DAGCircuit"]: for components in connected_components: disconnected_subgraphs.append(self._multi_graph.subgraph(list(components))) + + # Helper function for ensuring rustworkx nodes are returned in lexicographical, + # topological order + def _key(x): + return x.sort_key + # Create new DAGCircuit objects from each of the rustworkx subgraph objects decomposed_dags = [] for subgraph in disconnected_subgraphs: new_dag = self.copy_empty_like() - for node in subgraph.nodes(): + for node in rx.lexicographical_topological_sort(subgraph, key=_key): if not isinstance(node, DAGOpNode): continue - new_dag.apply_operation_back(node.op, node.qargs) + new_dag.apply_operation_back(node.op, node.qargs, node.cargs) # Ignore DAGs created for empty qubits and classical bits if new_dag.op_nodes(): diff --git a/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml b/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml new file mode 100644 index 000000000000..ddeb94d1a63e --- /dev/null +++ b/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml @@ -0,0 +1,15 @@ +--- +features: + - | + There is currently no easy way to retrieve all of the disconnected subsets + of qubits in a quantum circuit with Qiskit. This feature introduces a new + `DAGCircuit` class method, `separable_circuits`, which returns a list of + `DAGCircuit` objects -- one for each mutually disconnected set of qubits + in the `DAGCircuit` object. + + This method will not return a DAG for idle wires. + +issues: + - | + The subgraphs returned from `DAGCircuit.separable_circuits` will not + contain the global phase information of the parent `DAGCircuit`. diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index d88603b82afc..06aad2ab4f19 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -38,7 +38,7 @@ from qiskit.circuit.library.standard_gates.u1 import U1Gate from qiskit.circuit.barrier import Barrier from qiskit.dagcircuit.exceptions import DAGCircuitError -from qiskit.converters import circuit_to_dag +from qiskit.converters import circuit_to_dag, dag_to_circuit from qiskit.test import QiskitTestCase @@ -1237,6 +1237,7 @@ def test_separable_circuits(self): dag.apply_operation_back(HGate(), [qreg[0]], []) dag.apply_operation_back(HGate(), [qreg[1]], []) dag.apply_operation_back(HGate(), [qreg[2]], []) + dag.apply_operation_back(HGate(), [qreg[2]], []) self.assertEqual(3, len(dag.separable_circuits())) # 2 sets of disconnected qubits @@ -1247,6 +1248,37 @@ def test_separable_circuits(self): dag.apply_operation_back(CXGate(), [qreg[0], qreg[1]], []) self.assertEqual(1, len(dag.separable_circuits())) + # Test circuit ordering with measurements + dag = DAGCircuit() + qreg = QuantumRegister(3, "q") + creg = ClassicalRegister(1, "c") + dag.add_qreg(qreg) + dag.add_creg(creg) + dag.apply_operation_back(HGate(), [qreg[0]], []) + dag.apply_operation_back(XGate(), [qreg[0]], []) + dag.apply_operation_back(XGate(), [qreg[1]], []) + dag.apply_operation_back(HGate(), [qreg[1]], []) + dag.apply_operation_back(YGate(), [qreg[2]], []) + dag.apply_operation_back(HGate(), [qreg[2]], []) + dag.apply_operation_back(Measure(), [qreg[0]], [creg[0]]) + + compare1 = QuantumCircuit(3, 1) + compare1.h(0) + compare1.x(0) + compare1.measure(0, 0) + compare2 = QuantumCircuit(3, 1) + compare2.x(1) + compare2.h(1) + compare3 = QuantumCircuit(3, 1) + compare3.y(2) + compare3.h(2) + + dags = dag.separable_circuits() + + self.assertEqual(dag_to_circuit(dags[0]), compare1) + self.assertEqual(dag_to_circuit(dags[1]), compare2) + self.assertEqual(dag_to_circuit(dags[2]), compare3) + class TestCircuitControlFlowProperties(QiskitTestCase): """Properties tests of DAGCircuit with control-flow instructions.""" From 761a9e99ce2b8d7a93f9f3fe6f241a8ebdb22a72 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Wed, 1 Feb 2023 14:48:21 -0600 Subject: [PATCH 05/24] Code style changes --- qiskit/dagcircuit/dagcircuit.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 74ea235ad37c..bbe32158316e 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1396,8 +1396,8 @@ def substitute_node(self, node, op, inplace=False): def separable_circuits(self) -> List["DAGCircuit"]: """Separate the dag circuit into its weakly connected components. - The global phase information in `self` will not be maintained in the - subcircuits returned by this method. + The global phase information in `self` will not be maintained in the + subcircuits returned by this method. """ connected_components = rx.weakly_connected_components(self._multi_graph) @@ -1406,7 +1406,6 @@ def separable_circuits(self) -> List["DAGCircuit"]: for components in connected_components: disconnected_subgraphs.append(self._multi_graph.subgraph(list(components))) - # Helper function for ensuring rustworkx nodes are returned in lexicographical, # topological order def _key(x): From 1e91faac3ce55104c08cf73a8dc8455ee0670674 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Wed, 1 Feb 2023 14:52:53 -0600 Subject: [PATCH 06/24] Update release note --- .../dagcircuit-separable-circuits-142853e69f530a16.yaml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml b/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml index ddeb94d1a63e..4aff637bcf5a 100644 --- a/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml +++ b/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml @@ -1,11 +1,9 @@ --- features: - | - There is currently no easy way to retrieve all of the disconnected subsets - of qubits in a quantum circuit with Qiskit. This feature introduces a new - `DAGCircuit` class method, `separable_circuits`, which returns a list of - `DAGCircuit` objects -- one for each mutually disconnected set of qubits - in the `DAGCircuit` object. + This feature introduces a new `DAGCircuit` class method, + `separable_circuits`, which returns a list of `DAGCircuit` objects, + one for each mutually disconnected set of qubits in the `DAGCircuit` object. This method will not return a DAG for idle wires. From 235d6186aa170122e041529aa5308bad38a09efd Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Wed, 15 Feb 2023 12:13:36 -0600 Subject: [PATCH 07/24] Clean up documentation --- qiskit/dagcircuit/dagcircuit.py | 3 ++- ...gcircuit-separable-circuits-142853e69f530a16.yaml | 12 +++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index bbe32158316e..0bb146700556 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1394,7 +1394,7 @@ def substitute_node(self, node, op, inplace=False): return new_node def separable_circuits(self) -> List["DAGCircuit"]: - """Separate the dag circuit into its weakly connected components. + """Decompose the circuit into sets of qubits with no gates connecting them. The global phase information in `self` will not be maintained in the subcircuits returned by this method. @@ -1415,6 +1415,7 @@ def _key(x): decomposed_dags = [] for subgraph in disconnected_subgraphs: new_dag = self.copy_empty_like() + new_dag.global_phase = 0 for node in rx.lexicographical_topological_sort(subgraph, key=_key): if not isinstance(node, DAGOpNode): continue diff --git a/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml b/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml index 4aff637bcf5a..6cf837eb6a49 100644 --- a/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml +++ b/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml @@ -1,13 +1,15 @@ --- features: - | - This feature introduces a new `DAGCircuit` class method, - `separable_circuits`, which returns a list of `DAGCircuit` objects, - one for each mutually disconnected set of qubits in the `DAGCircuit` object. + Introduced a new :class:`.DAGCircuit` class method, + :meth:`~.separable_circuits`, which returns a list of :class:`.DAGCircuit` objects, + one for each set of connected qubits which have no gates connecting them to another + set. This method will not return a DAG for idle wires. issues: - | - The subgraphs returned from `DAGCircuit.separable_circuits` will not - contain the global phase information of the parent `DAGCircuit`. + The subcircuits returned from `DAGCircuit.separable_circuits` will not + retain the global phase information of the parent :class:`.DAGCircuit`. + All subcircuit global phases will be zero. From 925481fe9244fdd6fa99b5db779ca5704f785807 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Wed, 22 Feb 2023 18:23:41 -0600 Subject: [PATCH 08/24] Return a DAG for any partition with at least one qubit, even if empty --- qiskit/dagcircuit/dagcircuit.py | 8 ++++++-- .../dagcircuit-separable-circuits-142853e69f530a16.yaml | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 0bb146700556..ced462ffdc02 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1416,13 +1416,17 @@ def _key(x): for subgraph in disconnected_subgraphs: new_dag = self.copy_empty_like() new_dag.global_phase = 0 + subgraph_is_classical = True for node in rx.lexicographical_topological_sort(subgraph, key=_key): + if isinstance(node, DAGInNode): + if isinstance(node.wire, Qubit): + subgraph_is_classical = False if not isinstance(node, DAGOpNode): continue new_dag.apply_operation_back(node.op, node.qargs, node.cargs) - # Ignore DAGs created for empty qubits and classical bits - if new_dag.op_nodes(): + # Ignore DAGs created for empty clbits + if not subgraph_is_classical: decomposed_dags.append(new_dag) return decomposed_dags diff --git a/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml b/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml index 6cf837eb6a49..a05164c7dd7c 100644 --- a/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml +++ b/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml @@ -6,7 +6,7 @@ features: one for each set of connected qubits which have no gates connecting them to another set. - This method will not return a DAG for idle wires. + This method will not return a DAG solely of classical bits. issues: - | From a890b11da5f6c013495c7a385ad5c4510f468d7f Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Fri, 9 Jun 2023 07:44:30 -0500 Subject: [PATCH 09/24] Adjust tests to new flag --- qiskit/dagcircuit/dagcircuit.py | 8 +++++++- test/python/dagcircuit/test_dagcircuit.py | 6 +++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index c4e4f17b7bd9..cb96ddc9ab4d 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1407,7 +1407,7 @@ def substitute_node(self, node, op, inplace=False): self._decrement_op(node.op) return new_node - def separable_circuits(self) -> List["DAGCircuit"]: + def separable_circuits(self, remove_idle_wires=False) -> List["DAGCircuit"]: """Decompose the circuit into sets of qubits with no gates connecting them. The global phase information in `self` will not be maintained in the @@ -1443,6 +1443,12 @@ def _key(x): if not subgraph_is_classical: decomposed_dags.append(new_dag) + if remove_idle_wires: + for dag in decomposed_dags: + idle_qubits = [qubit for qubit in dag.idle_wires() if isinstance(qubit, Qubit)] + for qubit in idle_qubits: + dag.remove_qubits(qubit) + return decomposed_dags def swap_nodes(self, node1, node2): diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index c93e0ca855a9..4e622496fe4f 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -1241,15 +1241,15 @@ def test_separable_circuits(self): dag.apply_operation_back(HGate(), [qreg[1]], []) dag.apply_operation_back(HGate(), [qreg[2]], []) dag.apply_operation_back(HGate(), [qreg[2]], []) - self.assertEqual(3, len(dag.separable_circuits())) + self.assertEqual(3, len(dag.separable_circuits(remove_idle_wires=True))) # 2 sets of disconnected qubits dag.apply_operation_back(CXGate(), [qreg[1], qreg[2]], []) - self.assertEqual(2, len(dag.separable_circuits())) + self.assertEqual(2, len(dag.separable_circuits(remove_idle_wires=True))) # One connected component dag.apply_operation_back(CXGate(), [qreg[0], qreg[1]], []) - self.assertEqual(1, len(dag.separable_circuits())) + self.assertEqual(1, len(dag.separable_circuits(remove_idle_wires=True))) # Test circuit ordering with measurements dag = DAGCircuit() From 0270c8da044086339436de33bc56a77affef12ee Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Mon, 17 Jul 2023 16:36:56 -0500 Subject: [PATCH 10/24] peer review --- qiskit/dagcircuit/dagcircuit.py | 19 +++++++++++++------ ...t-separable-circuits-142853e69f530a16.yaml | 10 ++-------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 93ea96104226..e5525bed677f 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1402,11 +1402,20 @@ def substitute_node(self, node, op, inplace=False): self._decrement_op(node.op) return new_node - def separable_circuits(self, remove_idle_wires=False) -> List["DAGCircuit"]: + def separable_circuits(self, remove_idle_qubits=False) -> List["DAGCircuit"]: """Decompose the circuit into sets of qubits with no gates connecting them. - The global phase information in `self` will not be maintained in the - subcircuits returned by this method. + Args: + remove_idle_qubits (bool): Flag denoting whether to remove idle qubits from + the separated circuits. If ``True``, each output circuit will contain the + same number of qubits as ``self``. + + Returns: + List[DAGCircuit]: The circuits resulting from separating ``self`` into sets + of disconnected qubits + + The global phase information in `self` will not be maintained in the subcircuits + returned by this method. """ connected_components = rx.weakly_connected_components(self._multi_graph) @@ -1440,9 +1449,7 @@ def _key(x): if remove_idle_wires: for dag in decomposed_dags: - idle_qubits = [qubit for qubit in dag.idle_wires() if isinstance(qubit, Qubit)] - for qubit in idle_qubits: - dag.remove_qubits(qubit) + dag.remove_qubits(*(bit for bit in dag.idle_wires() if isinstance(qubit, Qubit))) return decomposed_dags diff --git a/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml b/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml index a05164c7dd7c..c7b61c18ac88 100644 --- a/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml +++ b/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml @@ -1,15 +1,9 @@ --- features: - | - Introduced a new :class:`.DAGCircuit` class method, - :meth:`~.separable_circuits`, which returns a list of :class:`.DAGCircuit` objects, + Introduced a new :class:`.DAGCircuit` method, + :meth:`~.DAGCircuit.separable_circuits`, which returns a list of :class:`.DAGCircuit` objects, one for each set of connected qubits which have no gates connecting them to another set. This method will not return a DAG solely of classical bits. - -issues: - - | - The subcircuits returned from `DAGCircuit.separable_circuits` will not - retain the global phase information of the parent :class:`.DAGCircuit`. - All subcircuit global phases will be zero. From 71d33f731dca167e10bccaccf0cea3ad3f98c56c Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Mon, 17 Jul 2023 16:43:25 -0500 Subject: [PATCH 11/24] peer review --- qiskit/dagcircuit/dagcircuit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index e5525bed677f..142445a6340a 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1407,7 +1407,7 @@ def separable_circuits(self, remove_idle_qubits=False) -> List["DAGCircuit"]: Args: remove_idle_qubits (bool): Flag denoting whether to remove idle qubits from - the separated circuits. If ``True``, each output circuit will contain the + the separated circuits. If ``False``, each output circuit will contain the same number of qubits as ``self``. Returns: From 0c60880a93412dbb65892ca3839d3e8200dea15c Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Mon, 17 Jul 2023 16:44:43 -0500 Subject: [PATCH 12/24] fix var name --- qiskit/dagcircuit/dagcircuit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 142445a6340a..4a47a618d0da 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1447,7 +1447,7 @@ def _key(x): if not subgraph_is_classical: decomposed_dags.append(new_dag) - if remove_idle_wires: + if remove_idle_qubits: for dag in decomposed_dags: dag.remove_qubits(*(bit for bit in dag.idle_wires() if isinstance(qubit, Qubit))) From b331cff2c980dbc1a46ef819d475c826675cf508 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Mon, 17 Jul 2023 18:22:05 -0500 Subject: [PATCH 13/24] test against dagcircuits --- qiskit/dagcircuit/dagcircuit.py | 4 +- test/python/dagcircuit/test_dagcircuit.py | 70 +++++++++++++++++------ 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 4a47a618d0da..449f718caed5 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1414,7 +1414,7 @@ def separable_circuits(self, remove_idle_qubits=False) -> List["DAGCircuit"]: List[DAGCircuit]: The circuits resulting from separating ``self`` into sets of disconnected qubits - The global phase information in `self` will not be maintained in the subcircuits + The global phase information in ``self`` will not be maintained in the subcircuits returned by this method. """ connected_components = rx.weakly_connected_components(self._multi_graph) @@ -1449,7 +1449,7 @@ def _key(x): if remove_idle_qubits: for dag in decomposed_dags: - dag.remove_qubits(*(bit for bit in dag.idle_wires() if isinstance(qubit, Qubit))) + dag.remove_qubits(*(bit for bit in dag.idle_wires() if isinstance(bit, Qubit))) return decomposed_dags diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index bbf23bce88fc..8304ba06f150 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -1360,27 +1360,59 @@ def test_separable_circuits(self): """Test separating disconnected sets of qubits in a circuit.""" # Empty case dag = DAGCircuit() - self.assertEqual([], dag.separable_circuits()) + self.assertEqual(dag.separable_circuits(), []) # 3 disconnected qubits - qreg = QuantumRegister(3, "qr") - creg = ClassicalRegister(2, "cr") + qreg = QuantumRegister(3, "q") + creg = ClassicalRegister(2, "c") dag.add_qreg(qreg) dag.add_creg(creg) dag.apply_operation_back(HGate(), [qreg[0]], []) dag.apply_operation_back(HGate(), [qreg[1]], []) dag.apply_operation_back(HGate(), [qreg[2]], []) dag.apply_operation_back(HGate(), [qreg[2]], []) - self.assertEqual(3, len(dag.separable_circuits(remove_idle_wires=True))) + + qc1 = QuantumCircuit(3, 2) + qc2 = QuantumCircuit(3, 2) + qc3 = QuantumCircuit(3, 2) + qc1.h(0) + qc2.h(1) + qc3.h(2) + qc3.h(2) + qcs = [qc1, qc2, qc3] + compare_dags = [circuit_to_dag(qc) for qc in qcs] + + dags = dag.separable_circuits(remove_idle_qubits=False) + + self.assertEqual(dags, compare_dags) # 2 sets of disconnected qubits + qc1 = QuantumCircuit(3, 2) + qc2 = QuantumCircuit(3, 2) + qc1.h(0) + qc2.h([1, 2, 2]) + qc2.cx(1, 2) + qcs = [qc1, qc2] + compare_dags = [circuit_to_dag(qc) for qc in qcs] + dag.apply_operation_back(CXGate(), [qreg[1], qreg[2]], []) - self.assertEqual(2, len(dag.separable_circuits(remove_idle_wires=True))) + + dags = dag.separable_circuits(remove_idle_qubits=False) + + self.assertEqual(dags, compare_dags) # One connected component + qc = QuantumCircuit(3, 2) + qc.h([0, 1, 2, 2]) + qc.cx(1, 2) + qc.cx(0, 1) + compare_dags = [circuit_to_dag(qc)] dag.apply_operation_back(CXGate(), [qreg[0], qreg[1]], []) - self.assertEqual(1, len(dag.separable_circuits(remove_idle_wires=True))) + dags = dag.separable_circuits(remove_idle_qubits=False) + self.assertEqual(dags, compare_dags) + + def test_separable_circuits_w_measurements(self): # Test circuit ordering with measurements dag = DAGCircuit() qreg = QuantumRegister(3, "q") @@ -1395,22 +1427,22 @@ def test_separable_circuits(self): dag.apply_operation_back(HGate(), [qreg[2]], []) dag.apply_operation_back(Measure(), [qreg[0]], [creg[0]]) - compare1 = QuantumCircuit(3, 1) - compare1.h(0) - compare1.x(0) - compare1.measure(0, 0) - compare2 = QuantumCircuit(3, 1) - compare2.x(1) - compare2.h(1) - compare3 = QuantumCircuit(3, 1) - compare3.y(2) - compare3.h(2) + qc1 = QuantumCircuit(3, 1) + qc1.h(0) + qc1.x(0) + qc1.measure(0, 0) + qc2 = QuantumCircuit(3, 1) + qc2.x(1) + qc2.h(1) + qc3 = QuantumCircuit(3, 1) + qc3.y(2) + qc3.h(2) + qcs = [qc1, qc2, qc3] + compare_dags = [circuit_to_dag(qc) for qc in qcs] dags = dag.separable_circuits() - self.assertEqual(dag_to_circuit(dags[0]), compare1) - self.assertEqual(dag_to_circuit(dags[1]), compare2) - self.assertEqual(dag_to_circuit(dags[2]), compare3) + self.assertEqual(dags, compare_dags) def test_default_metadata_value(self): """Test that the default DAGCircuit metadata is valid QuantumCircuit metadata.""" From 8330b3b5251ca89fdc563c3675c5929e0cc14341 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Mon, 17 Jul 2023 19:05:20 -0500 Subject: [PATCH 14/24] lint --- test/python/dagcircuit/test_dagcircuit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 8304ba06f150..be1b36e83eb6 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -1413,6 +1413,7 @@ def test_separable_circuits(self): self.assertEqual(dags, compare_dags) def test_separable_circuits_w_measurements(self): + """Test separating disconnected sets of qubits in a circuit with measurements.""" # Test circuit ordering with measurements dag = DAGCircuit() qreg = QuantumRegister(3, "q") From 6aa0a7def9afb3a37ea7398d390d0279a193eefc Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Mon, 17 Jul 2023 19:36:46 -0500 Subject: [PATCH 15/24] test remove_idle_wires --- test/python/dagcircuit/test_dagcircuit.py | 72 +++++++++++++++-------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index be1b36e83eb6..e8ba263fb1d9 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -1372,44 +1372,64 @@ def test_separable_circuits(self): dag.apply_operation_back(HGate(), [qreg[2]], []) dag.apply_operation_back(HGate(), [qreg[2]], []) - qc1 = QuantumCircuit(3, 2) - qc2 = QuantumCircuit(3, 2) - qc3 = QuantumCircuit(3, 2) - qc1.h(0) - qc2.h(1) - qc3.h(2) - qc3.h(2) - qcs = [qc1, qc2, qc3] - compare_dags = [circuit_to_dag(qc) for qc in qcs] - - dags = dag.separable_circuits(remove_idle_qubits=False) + comp_dag1 = DAGCircuit() + comp_dag1.add_qubits([qreg[0]]) + comp_dag1.add_creg(creg) + comp_dag1.apply_operation_back(HGate(), [qreg[0]], []) + comp_dag2 = DAGCircuit() + comp_dag2.add_qubits([qreg[1]]) + comp_dag2.add_creg(creg) + comp_dag2.apply_operation_back(HGate(), [qreg[1]], []) + comp_dag3 = DAGCircuit() + comp_dag3.add_qubits([qreg[2]]) + comp_dag3.add_creg(creg) + comp_dag3.apply_operation_back(HGate(), [qreg[2]], []) + comp_dag3.apply_operation_back(HGate(), [qreg[2]], []) + + compare_dags = [comp_dag1, comp_dag2, comp_dag3] + + dags = dag.separable_circuits(remove_idle_qubits=True) self.assertEqual(dags, compare_dags) # 2 sets of disconnected qubits - qc1 = QuantumCircuit(3, 2) - qc2 = QuantumCircuit(3, 2) - qc1.h(0) - qc2.h([1, 2, 2]) - qc2.cx(1, 2) - qcs = [qc1, qc2] - compare_dags = [circuit_to_dag(qc) for qc in qcs] - dag.apply_operation_back(CXGate(), [qreg[1], qreg[2]], []) - dags = dag.separable_circuits(remove_idle_qubits=False) + comp_dag1 = DAGCircuit() + comp_dag1.add_qubits([qreg[0]]) + comp_dag1.add_creg(creg) + comp_dag1.apply_operation_back(HGate(), [qreg[0]], []) + comp_dag2 = DAGCircuit() + comp_dag2.add_qubits([qreg[1], qreg[2]]) + comp_dag2.add_creg(creg) + comp_dag2.apply_operation_back(HGate(), [qreg[1]], []) + comp_dag2.apply_operation_back(HGate(), [qreg[2]], []) + comp_dag2.apply_operation_back(HGate(), [qreg[2]], []) + comp_dag2.apply_operation_back(CXGate(), [qreg[1], qreg[2]], []) + + compare_dags = [comp_dag1, comp_dag2] + + dags = dag.separable_circuits(remove_idle_qubits=True) self.assertEqual(dags, compare_dags) # One connected component - qc = QuantumCircuit(3, 2) - qc.h([0, 1, 2, 2]) - qc.cx(1, 2) - qc.cx(0, 1) - compare_dags = [circuit_to_dag(qc)] dag.apply_operation_back(CXGate(), [qreg[0], qreg[1]], []) - dags = dag.separable_circuits(remove_idle_qubits=False) + comp_dag1 = DAGCircuit() + comp_dag1.add_qreg(qreg) + comp_dag1.add_creg(creg) + comp_dag1.apply_operation_back(HGate(), [qreg[0]], []) + comp_dag1.apply_operation_back(HGate(), [qreg[1]], []) + comp_dag1.apply_operation_back(HGate(), [qreg[2]], []) + comp_dag1.apply_operation_back(HGate(), [qreg[2]], []) + comp_dag1.apply_operation_back(CXGate(), [qreg[1], qreg[2]], []) + comp_dag1.apply_operation_back(CXGate(), [qreg[0], qreg[1]], []) + + compare_dags = [comp_dag1] + + dags = dag.separable_circuits(remove_idle_qubits=True) + self.assertEqual(dags, compare_dags) def test_separable_circuits_w_measurements(self): From eeacdde750f9067f0420a79e4f84e31fc79ca193 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Mon, 17 Jul 2023 19:46:17 -0500 Subject: [PATCH 16/24] improve release note --- ...agcircuit-separable-circuits-142853e69f530a16.yaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml b/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml index c7b61c18ac88..2ddd5fbb2894 100644 --- a/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml +++ b/releasenotes/notes/dagcircuit-separable-circuits-142853e69f530a16.yaml @@ -1,9 +1,10 @@ --- features: - | - Introduced a new :class:`.DAGCircuit` method, - :meth:`~.DAGCircuit.separable_circuits`, which returns a list of :class:`.DAGCircuit` objects, - one for each set of connected qubits which have no gates connecting them to another - set. + Introduced a new method, :meth:`~.DAGCircuit.separable_circuits`, which returns a + list of :class:`.DAGCircuit` objects, one for each set of connected qubits + which have no gates connecting them to another set. - This method will not return a DAG solely of classical bits. + Each :class:`~.DAGCircuit` instance returned by this method will contain the same + number of clbits as ``self``. This method will not return :class:`~.DAGCircuit` + instances consisting solely of clbits. From 89817bc8c798526668e5e43a991aebc042b30a7e Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Tue, 18 Jul 2023 07:51:21 -0500 Subject: [PATCH 17/24] Update separable_circuits docstring --- qiskit/dagcircuit/dagcircuit.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 449f718caed5..3431cf5c244a 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1414,8 +1414,9 @@ def separable_circuits(self, remove_idle_qubits=False) -> List["DAGCircuit"]: List[DAGCircuit]: The circuits resulting from separating ``self`` into sets of disconnected qubits - The global phase information in ``self`` will not be maintained in the subcircuits - returned by this method. + Each :class:`~.DAGCircuit` instance returned by this method will contain the same number of + clbits as ``self``. The global phase information in ``self`` will not be maintained + in the subcircuits returned by this method. """ connected_components = rx.weakly_connected_components(self._multi_graph) From 3696d2af16072b66955e2becdbadf3e4ea778158 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Tue, 18 Jul 2023 09:31:06 -0500 Subject: [PATCH 18/24] Sort the dag circuits in tests to prevent them breaking in future --- test/python/dagcircuit/test_dagcircuit.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index e8ba263fb1d9..9ec3bca4719b 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -40,7 +40,7 @@ ) from qiskit.circuit.classical import expr from qiskit.circuit.library import IGate, HGate, CXGate, CZGate, XGate, YGate, U1Gate, RXGate -from qiskit.converters import circuit_to_dag +from qiskit.converters import circuit_to_dag, dag_to_circuit from qiskit.test import QiskitTestCase @@ -1356,6 +1356,11 @@ def test_circuit_factors(self): """Test number of separable factors in circuit.""" self.assertEqual(self.dag.num_tensor_factors(), 2) + def _min_active_qubit(self, d): + """Return the minimum index of all active qubits.""" + circ = dag_to_circuit(d) + return min({circ.find_bit(inst.qubits[i]).index for inst in circ for i in range(len(inst.qubits))}) + def test_separable_circuits(self): """Test separating disconnected sets of qubits in a circuit.""" # Empty case @@ -1388,7 +1393,7 @@ def test_separable_circuits(self): compare_dags = [comp_dag1, comp_dag2, comp_dag3] - dags = dag.separable_circuits(remove_idle_qubits=True) + dags = sorted(dag.separable_circuits(remove_idle_qubits=True), key=self._min_active_qubit) self.assertEqual(dags, compare_dags) @@ -1409,7 +1414,7 @@ def test_separable_circuits(self): compare_dags = [comp_dag1, comp_dag2] - dags = dag.separable_circuits(remove_idle_qubits=True) + dags = sorted(dag.separable_circuits(remove_idle_qubits=True), key=self._min_active_qubit) self.assertEqual(dags, compare_dags) @@ -1428,7 +1433,7 @@ def test_separable_circuits(self): compare_dags = [comp_dag1] - dags = dag.separable_circuits(remove_idle_qubits=True) + dags = sorted(dag.separable_circuits(remove_idle_qubits=True), key=self._min_active_qubit) self.assertEqual(dags, compare_dags) @@ -1461,7 +1466,7 @@ def test_separable_circuits_w_measurements(self): qcs = [qc1, qc2, qc3] compare_dags = [circuit_to_dag(qc) for qc in qcs] - dags = dag.separable_circuits() + dags = sorted(dag.separable_circuits(), key=self._min_active_qubit) self.assertEqual(dags, compare_dags) From 6a8611d9dce315889ce5892783437c32cee0eb79 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Tue, 18 Jul 2023 09:42:16 -0500 Subject: [PATCH 19/24] minor cleanup --- test/python/dagcircuit/test_dagcircuit.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 9ec3bca4719b..5f09f3155a23 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -1358,8 +1358,7 @@ def test_circuit_factors(self): def _min_active_qubit(self, d): """Return the minimum index of all active qubits.""" - circ = dag_to_circuit(d) - return min({circ.find_bit(inst.qubits[i]).index for inst in circ for i in range(len(inst.qubits))}) + return min({dag_to_circuit(d).find_bit(inst.qubits[i]).index for inst in circ for i in range(len(inst.qubits))}) def test_separable_circuits(self): """Test separating disconnected sets of qubits in a circuit.""" From 7cb417fcf339ce9dc95f4a818fa25c40711c68e9 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Tue, 18 Jul 2023 09:44:33 -0500 Subject: [PATCH 20/24] minor cleanup --- test/python/dagcircuit/test_dagcircuit.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 5f09f3155a23..8a492980b311 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -1356,9 +1356,10 @@ def test_circuit_factors(self): """Test number of separable factors in circuit.""" self.assertEqual(self.dag.num_tensor_factors(), 2) - def _min_active_qubit(self, d): + def _min_active_qubit(self, dag): """Return the minimum index of all active qubits.""" - return min({dag_to_circuit(d).find_bit(inst.qubits[i]).index for inst in circ for i in range(len(inst.qubits))}) + circ = dag_to_circuit(dag) + return min({circ.find_bit(inst.qubits[i]).index for inst in circ for i in range(len(inst.qubits))}) def test_separable_circuits(self): """Test separating disconnected sets of qubits in a circuit.""" From 919a42e266977409a26115d9bc8a9d7c7db27066 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Tue, 18 Jul 2023 15:39:34 -0500 Subject: [PATCH 21/24] Fix sorting --- test/python/dagcircuit/test_dagcircuit.py | 35 +++++++++++++++++------ 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 8a492980b311..11316a3e73a4 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -1300,6 +1300,18 @@ def test_layers_maintains_order(self): self.assertEqual(comp, truth) +def _sort_key(indices: dict[Qubit, int]): + """Return key function for sorting DAGCircuits, given a global qubit mapping.""" + def _min_active_qubit_id(dag): + """Transform a DAGCircuit into its minimum active qubit index.""" + try: + first_op = next(dag.topological_op_nodes()) + except StopIteration: + return -1 + return min(indices[q] for q in first_op.qargs) + return _min_active_qubit_id + + class TestCircuitProperties(QiskitTestCase): """DAGCircuit properties test.""" @@ -1356,11 +1368,6 @@ def test_circuit_factors(self): """Test number of separable factors in circuit.""" self.assertEqual(self.dag.num_tensor_factors(), 2) - def _min_active_qubit(self, dag): - """Return the minimum index of all active qubits.""" - circ = dag_to_circuit(dag) - return min({circ.find_bit(inst.qubits[i]).index for inst in circ for i in range(len(inst.qubits))}) - def test_separable_circuits(self): """Test separating disconnected sets of qubits in a circuit.""" # Empty case @@ -1393,7 +1400,11 @@ def test_separable_circuits(self): compare_dags = [comp_dag1, comp_dag2, comp_dag3] - dags = sorted(dag.separable_circuits(remove_idle_qubits=True), key=self._min_active_qubit) + # Get a mapping from qubit to qubit id in original circuit + indices = {bit: i for i, bit in enumerate(dag.qubits)} + + # Don't rely on separable_circuits outputs to be in any order. We sort by min active qubit id + dags = sorted(dag.separable_circuits(remove_idle_qubits=True), key=_sort_key(indices)) self.assertEqual(dags, compare_dags) @@ -1414,7 +1425,8 @@ def test_separable_circuits(self): compare_dags = [comp_dag1, comp_dag2] - dags = sorted(dag.separable_circuits(remove_idle_qubits=True), key=self._min_active_qubit) + # Don't rely on separable_circuits outputs to be in any order. We sort by min active qubit id + dags = sorted(dag.separable_circuits(remove_idle_qubits=True), key=_sort_key(indices)) self.assertEqual(dags, compare_dags) @@ -1433,7 +1445,8 @@ def test_separable_circuits(self): compare_dags = [comp_dag1] - dags = sorted(dag.separable_circuits(remove_idle_qubits=True), key=self._min_active_qubit) + # Don't rely on separable_circuits outputs to be in any order. We sort by min active qubit id + dags = sorted(dag.separable_circuits(remove_idle_qubits=True), key=_sort_key(indices)) self.assertEqual(dags, compare_dags) @@ -1466,7 +1479,11 @@ def test_separable_circuits_w_measurements(self): qcs = [qc1, qc2, qc3] compare_dags = [circuit_to_dag(qc) for qc in qcs] - dags = sorted(dag.separable_circuits(), key=self._min_active_qubit) + # Get a mapping from qubit to qubit id in original circuit + indices = {bit: i for i, bit in enumerate(dag.qubits)} + + # Don't rely on separable_circuits outputs to be in any order. We sort by min active qubit id + dags = sorted(dag.separable_circuits(), key=_sort_key(indices)) self.assertEqual(dags, compare_dags) From 55db18045dfd2cb6b2d5eff99c0a50194c0af790 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 18 Jul 2023 21:48:14 +0100 Subject: [PATCH 22/24] Fix type-hint resolution --- test/python/dagcircuit/test_dagcircuit.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 11316a3e73a4..943889a1ed76 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -12,6 +12,8 @@ """Test for the DAGCircuit object""" +from __future__ import annotations + from collections import Counter import unittest From e04fc8b1652b3b86bf574028ff4aebc59d12cf1c Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Tue, 18 Jul 2023 16:42:19 -0500 Subject: [PATCH 23/24] black --- test/python/dagcircuit/test_dagcircuit.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 11316a3e73a4..7406d4caff83 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -1302,6 +1302,7 @@ def test_layers_maintains_order(self): def _sort_key(indices: dict[Qubit, int]): """Return key function for sorting DAGCircuits, given a global qubit mapping.""" + def _min_active_qubit_id(dag): """Transform a DAGCircuit into its minimum active qubit index.""" try: @@ -1309,6 +1310,7 @@ def _min_active_qubit_id(dag): except StopIteration: return -1 return min(indices[q] for q in first_op.qargs) + return _min_active_qubit_id From c3dd9f107cc2637841314b43525cc551c4146cbb Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Tue, 18 Jul 2023 19:44:40 -0500 Subject: [PATCH 24/24] unused import --- test/python/dagcircuit/test_dagcircuit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index b629c26906bf..945055493cc7 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -42,7 +42,7 @@ ) from qiskit.circuit.classical import expr from qiskit.circuit.library import IGate, HGate, CXGate, CZGate, XGate, YGate, U1Gate, RXGate -from qiskit.converters import circuit_to_dag, dag_to_circuit +from qiskit.converters import circuit_to_dag from qiskit.test import QiskitTestCase