From 413aa0227805b08b2cc64af5e328c642c20d4a6f Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Tue, 27 Jun 2023 23:08:54 -0400 Subject: [PATCH 01/26] Support control flow in ConsolidateBlocks --- .../passes/optimization/consolidate_blocks.py | 41 +++++++++++++++++++ .../transpiler/test_consolidate_blocks.py | 30 ++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index 9f573728572c..ff6106abc25e 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -23,6 +23,8 @@ from qiskit.extensions import UnitaryGate from qiskit.circuit.library.standard_gates import CXGate from qiskit.transpiler.basepasses import TransformationPass +from qiskit.circuit import ControlFlowOp + from qiskit.transpiler.passes.synthesis import unitary_synthesis @@ -71,6 +73,9 @@ def __init__( ) else: self.decomposer = TwoQubitBasisDecomposer(CXGate()) + self._basis_gates = basis_gates + self._kak_basis_gate = kak_basis_gate + self._approximation_degree = approximation_degree def run(self, dag): """Run the ConsolidateBlocks pass on `dag`. @@ -159,11 +164,47 @@ def run(self, dag): dag.remove_op_node(node) else: dag.replace_block_with_op(run, unitary, {qubit: 0}, cycle_check=False) + + dag = self._handle_control_flow_ops(dag) + # Clear collected blocks and runs as they are no longer valid after consolidation if "run_list" in self.property_set: del self.property_set["run_list"] if "block_list" in self.property_set: del self.property_set["block_list"] + + return dag + + def _handle_control_flow_ops(self, dag): + """ + This is similar to transpiler/passes/utils/control_flow.py except that the + collect blocks is redone for the control flow blocks. + """ + from qiskit.transpiler import PassManager + from qiskit.transpiler.passes import Collect2qBlocks + from qiskit.transpiler.passes import Collect1qRuns + + pass_manager = PassManager() + if "run_list" in self.property_set: + pass_manager.append(Collect1qRuns()) + if "block_list" in self.property_set: + pass_manager.append(Collect2qBlocks()) + + new_consolidate_blocks = self.__class__( + self._kak_basis_gate, + self.force_consolidate, + self._basis_gates, + self._approximation_degree, + self.target, + ) + + pass_manager.append(new_consolidate_blocks) + for node in dag.op_nodes(ControlFlowOp): + mapped_blocks = [] + for block in node.op.blocks: + new_circ = pass_manager.run(block) + mapped_blocks.append(new_circ) + node.op = node.op.replace_blocks(mapped_blocks) return dag def _check_not_in_basis(self, gate_name, qargs, global_index_map): diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index 3332026e436b..64d81ab624ae 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -428,6 +428,36 @@ def test_identity_1q_unitary_is_removed(self): pm = PassManager([Collect2qBlocks(), Collect1qRuns(), ConsolidateBlocks()]) self.assertEqual(QuantumCircuit(5), pm.run(qc)) + def test_descent_into_control_flow(self): + """Test consolidation in blocks of control flow op is the same as at top level.""" + qc = QuantumCircuit(2) + u2gate1 = U2Gate(-1.2, np.pi) + u2gate2 = U2Gate(-3.4, np.pi) + qc.append(u2gate1, [0]) + qc.append(u2gate2, [1]) + qc.cx(0, 1) + qc.cx(1, 0) + + pass_manager = PassManager() + pass_manager.append(Collect2qBlocks()) + pass_manager.append(ConsolidateBlocks(force_consolidate=True)) + result_top = pass_manager.run(qc) + + qc_control_flow = QuantumCircuit(2, 1) + with qc_control_flow.if_test((0, False)): + qc_control_flow.append(u2gate1, [0]) + qc_control_flow.append(u2gate2, [1]) + qc_control_flow.cx(0, 1) + qc_control_flow.cx(1, 0) + + pass_manager = PassManager() + pass_manager.append(Collect2qBlocks()) + pass_manager.append(ConsolidateBlocks(force_consolidate=True)) + result_block = pass_manager.run(qc_control_flow) + gate_top = result_top[0].operation + gate_block = result_block[0].operation.blocks[0][0].operation + self.assertEqual(gate_top, gate_block) + if __name__ == "__main__": unittest.main() From c882c6a719fc107065566ce6a16a44efa7d624a4 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 28 Jun 2023 14:52:16 -0400 Subject: [PATCH 02/26] Add release note for support control flow in ConsolidateBlocks --- ...trol-flow-to-consolidate-blocks-e013e28007170377.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 releasenotes/notes/add-control-flow-to-consolidate-blocks-e013e28007170377.yaml diff --git a/releasenotes/notes/add-control-flow-to-consolidate-blocks-e013e28007170377.yaml b/releasenotes/notes/add-control-flow-to-consolidate-blocks-e013e28007170377.yaml new file mode 100644 index 000000000000..3876c8d8db85 --- /dev/null +++ b/releasenotes/notes/add-control-flow-to-consolidate-blocks-e013e28007170377.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Enabled performing the :class:`qiskit.transpiler.passes.ConsolidateBlocks` pass inside the + blocks of :class:`qiskit.circuit.ControlFlowOp`. This pass collects several sequences of gates + and replaces each sequence with the equivalent numeric unitary gate. This new feature enables + applying this pass recursively to the blocks in control flow operations. Note that the meaning + of "block" in `:class:`qiskit.transpiler.passes.ConsolidateBlocks` is unrelated to that in + :class:`qiskit.circuit.ControlFlowOp`. From d269db5de42f6496f4b935a366c8e7282209b601 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 28 Jun 2023 22:15:53 -0400 Subject: [PATCH 03/26] Move imports from inside function to top level --- qiskit/transpiler/passes/optimization/consolidate_blocks.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index ff6106abc25e..e84b285bff9c 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -23,6 +23,9 @@ from qiskit.extensions import UnitaryGate from qiskit.circuit.library.standard_gates import CXGate from qiskit.transpiler.basepasses import TransformationPass +from qiskit.transpiler.passes.optimization import Collect2qBlocks +from qiskit.transpiler.passes.optimization.collect_1q_runs import Collect1qRuns + from qiskit.circuit import ControlFlowOp from qiskit.transpiler.passes.synthesis import unitary_synthesis @@ -181,8 +184,6 @@ def _handle_control_flow_ops(self, dag): collect blocks is redone for the control flow blocks. """ from qiskit.transpiler import PassManager - from qiskit.transpiler.passes import Collect2qBlocks - from qiskit.transpiler.passes import Collect1qRuns pass_manager = PassManager() if "run_list" in self.property_set: From c32d69cab99d460f42ff7f4917274bb6946bf11d Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 28 Jun 2023 23:47:01 -0400 Subject: [PATCH 04/26] Make construction of ConsolidateBlocks for control flow ops more efficient --- .../passes/optimization/consolidate_blocks.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index e84b285bff9c..578d63c5189e 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -51,15 +51,20 @@ def __init__( basis_gates=None, approximation_degree=1.0, target=None, + decomposer=None, ): """ConsolidateBlocks initializer. + The decomoposer used is determined by the first of the following arguments + with a non-None value: decomposer, kak_basis_gate, basis_gates. If all are None, + then a default decomposer is used. Args: kak_basis_gate (Gate): Basis gate for KAK decomposition. - force_consolidate (bool): Force block consolidation + force_consolidate (bool): Force block consolidation. basis_gates (List(str)): Basis gates from which to choose a KAK gate. approximation_degree (float): a float between [0.0, 1.0]. Lower approximates more. - target (Target): The target object for the compilation target backend + target (Target): The target object for the compilation target backend. + decomposer: A 2q gate decomposer. """ super().__init__() self.basis_gates = None @@ -68,6 +73,8 @@ def __init__( self.basis_gates = set(basis_gates) self.force_consolidate = force_consolidate + if decomposer is not None: + self.decomposer = decomposer if kak_basis_gate is not None: self.decomposer = TwoQubitBasisDecomposer(kak_basis_gate) elif basis_gates is not None: @@ -76,8 +83,6 @@ def __init__( ) else: self.decomposer = TwoQubitBasisDecomposer(CXGate()) - self._basis_gates = basis_gates - self._kak_basis_gate = kak_basis_gate self._approximation_degree = approximation_degree def run(self, dag): @@ -192,11 +197,10 @@ def _handle_control_flow_ops(self, dag): pass_manager.append(Collect2qBlocks()) new_consolidate_blocks = self.__class__( - self._kak_basis_gate, - self.force_consolidate, - self._basis_gates, - self._approximation_degree, - self.target, + force_consolidate=self.force_consolidate, + approximation_degree=self._approximation_degree, + target=self.target, + decomposer=self.decomposer, ) pass_manager.append(new_consolidate_blocks) From d4bc8bdead1616fe6288069f7ee421b6aae3a324 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Mon, 10 Jul 2023 15:39:49 -0400 Subject: [PATCH 05/26] Do IfElseOp test without builder interface Before, we used the builder interface for an ifelse op. Some details of the circuit built, in particular the mapping of wires is not deterministic. We could have used canonicalize_control_flow. But instead we construct the IfElseOp manually. This removes the complexity of the builder interface from this test. --- .../transpiler/test_consolidate_blocks.py | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index 64d81ab624ae..81172cc53fc4 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -16,8 +16,9 @@ import unittest import numpy as np +import copy -from qiskit.circuit import QuantumCircuit, QuantumRegister +from qiskit.circuit import QuantumCircuit, QuantumRegister, IfElseOp from qiskit.circuit.library import U2Gate, SwapGate, CXGate from qiskit.extensions import UnitaryGate from qiskit.converters import circuit_to_dag @@ -430,7 +431,7 @@ def test_identity_1q_unitary_is_removed(self): def test_descent_into_control_flow(self): """Test consolidation in blocks of control flow op is the same as at top level.""" - qc = QuantumCircuit(2) + qc = QuantumCircuit(2, 1) u2gate1 = U2Gate(-1.2, np.pi) u2gate2 = U2Gate(-3.4, np.pi) qc.append(u2gate1, [0]) @@ -444,11 +445,16 @@ def test_descent_into_control_flow(self): result_top = pass_manager.run(qc) qc_control_flow = QuantumCircuit(2, 1) - with qc_control_flow.if_test((0, False)): - qc_control_flow.append(u2gate1, [0]) - qc_control_flow.append(u2gate2, [1]) - qc_control_flow.cx(0, 1) - qc_control_flow.cx(1, 0) + qc_block = copy.deepcopy(qc) + + qc_block = QuantumCircuit(qc.qubits, qc.clbits) + qc_block.append(u2gate1, [0]) + qc_block.append(u2gate2, [1]) + qc_block.cx(0, 1) + qc_block.cx(1, 0) + + ifop = IfElseOp((qc.clbits[0], False), qc_block, None) + qc_control_flow.append(ifop, qc.qubits, qc.clbits) pass_manager = PassManager() pass_manager.append(Collect2qBlocks()) From d210512a45dd2fd8463b631424c44335ab806957 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Mon, 10 Jul 2023 15:55:53 -0400 Subject: [PATCH 06/26] Linting --- test/python/transpiler/test_consolidate_blocks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index 81172cc53fc4..786f1651afd5 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -15,8 +15,8 @@ """ import unittest -import numpy as np import copy +import numpy as np from qiskit.circuit import QuantumCircuit, QuantumRegister, IfElseOp from qiskit.circuit.library import U2Gate, SwapGate, CXGate From eabaee5b9e31e300c389350dc8dfd4dcbae624fc Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Mon, 10 Jul 2023 17:01:27 -0400 Subject: [PATCH 07/26] Avoid cyclic import --- qiskit/transpiler/passes/optimization/consolidate_blocks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index 578d63c5189e..368f06a77272 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -26,7 +26,7 @@ from qiskit.transpiler.passes.optimization import Collect2qBlocks from qiskit.transpiler.passes.optimization.collect_1q_runs import Collect1qRuns -from qiskit.circuit import ControlFlowOp +from qiskit.circuit.controlflow import ControlFlowOp from qiskit.transpiler.passes.synthesis import unitary_synthesis From c567d0d8d1d502a8b8491e9dbce22aba3d0b85bc Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Mon, 10 Jul 2023 17:59:52 -0400 Subject: [PATCH 08/26] Try to fix cyclic import (in lint tool only) --- qiskit/transpiler/passes/optimization/consolidate_blocks.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index 368f06a77272..64e5efe35029 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -23,12 +23,10 @@ from qiskit.extensions import UnitaryGate from qiskit.circuit.library.standard_gates import CXGate from qiskit.transpiler.basepasses import TransformationPass -from qiskit.transpiler.passes.optimization import Collect2qBlocks -from qiskit.transpiler.passes.optimization.collect_1q_runs import Collect1qRuns - from qiskit.circuit.controlflow import ControlFlowOp - from qiskit.transpiler.passes.synthesis import unitary_synthesis +from .collect_1q_runs import Collect1qRuns +from .collect_2q_blocks import Collect2qBlocks class ConsolidateBlocks(TransformationPass): From 7a34941a9e1bf252231f760af6df8da1bad08c69 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Tue, 11 Jul 2023 00:46:30 -0400 Subject: [PATCH 09/26] Reuse top-level consolidation pass and add tests * Before, we created an new ConsolidationPass when descending into control flow blocks. With this commit, we use the existing pass. * Add some tests for cases where consolidation should not happen. --- .../passes/optimization/consolidate_blocks.py | 11 +--- .../transpiler/test_consolidate_blocks.py | 54 ++++++++++++++++++- 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index 64e5efe35029..eecddc613788 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -53,7 +53,7 @@ def __init__( ): """ConsolidateBlocks initializer. - The decomoposer used is determined by the first of the following arguments + The decomposer used is determined by the first of the following arguments with a non-None value: decomposer, kak_basis_gate, basis_gates. If all are None, then a default decomposer is used. Args: @@ -194,14 +194,7 @@ def _handle_control_flow_ops(self, dag): if "block_list" in self.property_set: pass_manager.append(Collect2qBlocks()) - new_consolidate_blocks = self.__class__( - force_consolidate=self.force_consolidate, - approximation_degree=self._approximation_degree, - target=self.target, - decomposer=self.decomposer, - ) - - pass_manager.append(new_consolidate_blocks) + pass_manager.append(self) for node in dag.op_nodes(ControlFlowOp): mapped_blocks = [] for block in node.op.blocks: diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index 786f1651afd5..57e32e736c0b 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -19,7 +19,7 @@ import numpy as np from qiskit.circuit import QuantumCircuit, QuantumRegister, IfElseOp -from qiskit.circuit.library import U2Gate, SwapGate, CXGate +from qiskit.circuit.library import U2Gate, SwapGate, CXGate, CZGate from qiskit.extensions import UnitaryGate from qiskit.converters import circuit_to_dag from qiskit.transpiler.passes import ConsolidateBlocks @@ -430,7 +430,7 @@ def test_identity_1q_unitary_is_removed(self): self.assertEqual(QuantumCircuit(5), pm.run(qc)) def test_descent_into_control_flow(self): - """Test consolidation in blocks of control flow op is the same as at top level.""" + """Test consolidation in blocks when control flow op is the same as at top level.""" qc = QuantumCircuit(2, 1) u2gate1 = U2Gate(-1.2, np.pi) u2gate2 = U2Gate(-3.4, np.pi) @@ -464,6 +464,56 @@ def test_descent_into_control_flow(self): gate_block = result_block[0].operation.blocks[0][0].operation self.assertEqual(gate_top, gate_block) + def test_not_crossing_between_control_flow_block_and_parent(self): + """Test that consolidation does not occur across the boundary between control flow + blocks and the parent circuit.""" + qc = QuantumCircuit(2, 1) + qc.cx(0, 1) + qc_true = QuantumCircuit(qc.qubits, qc.clbits) + qc_false = QuantumCircuit(qc.qubits, qc.clbits) + qc_true.cx(0, 1) + qc_false.cz(0, 1) + ifop = IfElseOp((qc.clbits[0], True), qc_true, qc_false) + qc.append(ifop, qc.qubits, qc.clbits) + + pass_manager = PassManager() + pass_manager.append(Collect2qBlocks()) + pass_manager.append(ConsolidateBlocks(force_consolidate=True)) + qc_out = pass_manager.run(qc) + + self.assertIsInstance(qc_out[0].operation, UnitaryGate) + self.assertTrue(np.alltrue(CXGate().to_matrix() == qc_out[0].operation.to_matrix())) + op_true = qc_out[1].operation.blocks[0][0].operation + op_false = qc_out[1].operation.blocks[1][0].operation + self.assertTrue(np.alltrue(CXGate().to_matrix() == op_true.to_matrix())) + self.assertTrue(np.alltrue(CZGate().to_matrix() == op_false.to_matrix())) + + def test_not_crossing_between_control_flow_ops(self): + """Test that consolidation does not occur between control flow ops.""" + qc = QuantumCircuit(2, 1) + qc_true = QuantumCircuit(qc.qubits, qc.clbits) + qc_false = QuantumCircuit(qc.qubits, qc.clbits) + qc_true.cx(0, 1) + qc_false.cz(0, 1) + ifop1 = IfElseOp((qc.clbits[0], True), qc_true, qc_false) + qc.append(ifop1, qc.qubits, qc.clbits) + ifop2 = IfElseOp((qc.clbits[0], True), qc_true, qc_false) + qc.append(ifop2, qc.qubits, qc.clbits) + + pass_manager = PassManager() + pass_manager.append(Collect2qBlocks()) + pass_manager.append(ConsolidateBlocks(force_consolidate=True)) + qc_out = pass_manager.run(qc) + + op_true1 = qc_out[0].operation.blocks[0][0].operation + op_false1 = qc_out[0].operation.blocks[1][0].operation + op_true2 = qc_out[1].operation.blocks[0][0].operation + op_false2 = qc_out[1].operation.blocks[1][0].operation + self.assertTrue(np.alltrue(CXGate().to_matrix() == op_true1.to_matrix())) + self.assertTrue(np.alltrue(CZGate().to_matrix() == op_false1.to_matrix())) + self.assertTrue(np.alltrue(CXGate().to_matrix() == op_true2.to_matrix())) + self.assertTrue(np.alltrue(CZGate().to_matrix() == op_false2.to_matrix())) + if __name__ == "__main__": unittest.main() From 3da49906528a08a0bdf380e2b82e409af7fc32bc Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Tue, 11 Jul 2023 00:54:53 -0400 Subject: [PATCH 10/26] Remove argument `decomposer` from constructor of ConsolidateBlocks This was added in a previous commit in the series of commits for this PR. The code has been redesigned so that this argument is no longer necessary. --- .../transpiler/passes/optimization/consolidate_blocks.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index eecddc613788..19114e037bd7 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -49,20 +49,19 @@ def __init__( basis_gates=None, approximation_degree=1.0, target=None, - decomposer=None, ): """ConsolidateBlocks initializer. - The decomposer used is determined by the first of the following arguments - with a non-None value: decomposer, kak_basis_gate, basis_gates. If all are None, + The decomposer used is kak_basis_gate if it has non-None value. Otherwise + the decomposer is basis_gates if it has non-None value. If both are None, then a default decomposer is used. + Args: kak_basis_gate (Gate): Basis gate for KAK decomposition. force_consolidate (bool): Force block consolidation. basis_gates (List(str)): Basis gates from which to choose a KAK gate. approximation_degree (float): a float between [0.0, 1.0]. Lower approximates more. target (Target): The target object for the compilation target backend. - decomposer: A 2q gate decomposer. """ super().__init__() self.basis_gates = None @@ -71,8 +70,6 @@ def __init__( self.basis_gates = set(basis_gates) self.force_consolidate = force_consolidate - if decomposer is not None: - self.decomposer = decomposer if kak_basis_gate is not None: self.decomposer = TwoQubitBasisDecomposer(kak_basis_gate) elif basis_gates is not None: From 248479d6d6c6d250631c102659969f9e12a71a3d Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Tue, 11 Jul 2023 15:55:54 -0400 Subject: [PATCH 11/26] Remove cruft accidentally left In the first version of the associated PR, we created a new pass when descending into control flow blocks. These lines were included to support that construction and are no longer needed. --- qiskit/transpiler/passes/optimization/consolidate_blocks.py | 1 - test/python/transpiler/test_consolidate_blocks.py | 1 - 2 files changed, 2 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index 19114e037bd7..7d3afdd7535b 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -78,7 +78,6 @@ def __init__( ) else: self.decomposer = TwoQubitBasisDecomposer(CXGate()) - self._approximation_degree = approximation_degree def run(self, dag): """Run the ConsolidateBlocks pass on `dag`. diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index 57e32e736c0b..787e8ad58e78 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -445,7 +445,6 @@ def test_descent_into_control_flow(self): result_top = pass_manager.run(qc) qc_control_flow = QuantumCircuit(2, 1) - qc_block = copy.deepcopy(qc) qc_block = QuantumCircuit(qc.qubits, qc.clbits) qc_block.append(u2gate1, [0]) From dc696542f67f59315c6e9b96a400440b291c1cdb Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Tue, 11 Jul 2023 16:00:47 -0400 Subject: [PATCH 12/26] Move function-level import to module level Remove unused import in tests --- qiskit/transpiler/passes/optimization/consolidate_blocks.py | 2 +- test/python/transpiler/test_consolidate_blocks.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index 7d3afdd7535b..00c8785792dc 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -24,6 +24,7 @@ from qiskit.circuit.library.standard_gates import CXGate from qiskit.transpiler.basepasses import TransformationPass from qiskit.circuit.controlflow import ControlFlowOp +from qiskit.transpiler.passmanager import PassManager from qiskit.transpiler.passes.synthesis import unitary_synthesis from .collect_1q_runs import Collect1qRuns from .collect_2q_blocks import Collect2qBlocks @@ -182,7 +183,6 @@ def _handle_control_flow_ops(self, dag): This is similar to transpiler/passes/utils/control_flow.py except that the collect blocks is redone for the control flow blocks. """ - from qiskit.transpiler import PassManager pass_manager = PassManager() if "run_list" in self.property_set: diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index 787e8ad58e78..3d6aa1fcdb3f 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -15,7 +15,6 @@ """ import unittest -import copy import numpy as np from qiskit.circuit import QuantumCircuit, QuantumRegister, IfElseOp From 60e27b40dbf79cf787b7267ac131ace16600aada Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Tue, 11 Jul 2023 23:08:16 -0400 Subject: [PATCH 13/26] Write loop more concisely --- qiskit/transpiler/passes/optimization/consolidate_blocks.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index 00c8785792dc..8d65956a89c7 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -192,11 +192,7 @@ def _handle_control_flow_ops(self, dag): pass_manager.append(self) for node in dag.op_nodes(ControlFlowOp): - mapped_blocks = [] - for block in node.op.blocks: - new_circ = pass_manager.run(block) - mapped_blocks.append(new_circ) - node.op = node.op.replace_blocks(mapped_blocks) + node.op = node.op.replace_blocks(pass_manager.run(block) for block in node.op.blocks) return dag def _check_not_in_basis(self, gate_name, qargs, global_index_map): From 8bea1e71848c9fb02416583f344c7c4a1e5630b3 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Tue, 11 Jul 2023 23:13:59 -0400 Subject: [PATCH 14/26] Update releasenotes/notes/add-control-flow-to-consolidate-blocks-e013e28007170377.yaml Co-authored-by: Jake Lishman --- ...ntrol-flow-to-consolidate-blocks-e013e28007170377.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/releasenotes/notes/add-control-flow-to-consolidate-blocks-e013e28007170377.yaml b/releasenotes/notes/add-control-flow-to-consolidate-blocks-e013e28007170377.yaml index 3876c8d8db85..cdfc14ca3810 100644 --- a/releasenotes/notes/add-control-flow-to-consolidate-blocks-e013e28007170377.yaml +++ b/releasenotes/notes/add-control-flow-to-consolidate-blocks-e013e28007170377.yaml @@ -1,9 +1,9 @@ --- features: - | - Enabled performing the :class:`qiskit.transpiler.passes.ConsolidateBlocks` pass inside the - blocks of :class:`qiskit.circuit.ControlFlowOp`. This pass collects several sequences of gates + Enabled performing the :class:`.ConsolidateBlocks` pass inside the + blocks of :class:`.ControlFlowOp`. This pass collects several sequences of gates and replaces each sequence with the equivalent numeric unitary gate. This new feature enables applying this pass recursively to the blocks in control flow operations. Note that the meaning - of "block" in `:class:`qiskit.transpiler.passes.ConsolidateBlocks` is unrelated to that in - :class:`qiskit.circuit.ControlFlowOp`. + of "block" in :class:`.ConsolidateBlocks` is unrelated to that in + :class:`.ControlFlowOp`. From 87b979e0e513c2384161e5a9220a08333749ced1 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Tue, 11 Jul 2023 23:47:10 -0400 Subject: [PATCH 15/26] Use assertion in tests with better diagnostics --- test/python/transpiler/test_consolidate_blocks.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index 3d6aa1fcdb3f..d759b57a2d56 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -507,10 +507,10 @@ def test_not_crossing_between_control_flow_ops(self): op_false1 = qc_out[0].operation.blocks[1][0].operation op_true2 = qc_out[1].operation.blocks[0][0].operation op_false2 = qc_out[1].operation.blocks[1][0].operation - self.assertTrue(np.alltrue(CXGate().to_matrix() == op_true1.to_matrix())) - self.assertTrue(np.alltrue(CZGate().to_matrix() == op_false1.to_matrix())) - self.assertTrue(np.alltrue(CXGate().to_matrix() == op_true2.to_matrix())) - self.assertTrue(np.alltrue(CZGate().to_matrix() == op_false2.to_matrix())) + np.testing.assert_allclose(CXGate().to_matrix(), op_true1.to_matrix()) + np.testing.assert_allclose(CZGate().to_matrix(), op_false1.to_matrix()) + np.testing.assert_allclose(CXGate().to_matrix(), op_true2.to_matrix()) + np.testing.assert_allclose(CZGate().to_matrix(), op_false2.to_matrix()) if __name__ == "__main__": From bf0a1f46d6ee2a593aebbd18e6436c6a21724efd Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 12 Jul 2023 15:58:51 -0400 Subject: [PATCH 16/26] Remove reference to decomposer from docstring to ConsolidateBlocks The previous doc string was a bit imprecise. It also referred to a decomposer which although implied, is not meant to be accessible by the user. --- qiskit/transpiler/passes/optimization/consolidate_blocks.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index 8d65956a89c7..fdc18e3cc155 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -53,9 +53,9 @@ def __init__( ): """ConsolidateBlocks initializer. - The decomposer used is kak_basis_gate if it has non-None value. Otherwise - the decomposer is basis_gates if it has non-None value. If both are None, - then a default decomposer is used. + If `kak_basis_gate` is not `None` it will be used as the basis gate for KAK decomposition. + Otherwise, if `basis_gates` is not `None` a basis gate will be chosen from this list. + Otherwise the basis gate will be `CXGate`. Args: kak_basis_gate (Gate): Basis gate for KAK decomposition. From d3d2925124352ec2bd8eb5aff769c50c016a5625 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 19 Jul 2023 15:42:43 -0400 Subject: [PATCH 17/26] Use more informative tests for ConsolidateBlocks with control flow --- test/python/transpiler/test_consolidate_blocks.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index d759b57a2d56..cd81d2c73ed6 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -460,7 +460,7 @@ def test_descent_into_control_flow(self): result_block = pass_manager.run(qc_control_flow) gate_top = result_top[0].operation gate_block = result_block[0].operation.blocks[0][0].operation - self.assertEqual(gate_top, gate_block) + np.testing.assert_allclose(gate_top, gate_block) def test_not_crossing_between_control_flow_block_and_parent(self): """Test that consolidation does not occur across the boundary between control flow @@ -480,11 +480,11 @@ def test_not_crossing_between_control_flow_block_and_parent(self): qc_out = pass_manager.run(qc) self.assertIsInstance(qc_out[0].operation, UnitaryGate) - self.assertTrue(np.alltrue(CXGate().to_matrix() == qc_out[0].operation.to_matrix())) + np.testing.assert_allclose(CXGate().to_matrix(), qc_out[0].operation.to_matrix()) op_true = qc_out[1].operation.blocks[0][0].operation op_false = qc_out[1].operation.blocks[1][0].operation - self.assertTrue(np.alltrue(CXGate().to_matrix() == op_true.to_matrix())) - self.assertTrue(np.alltrue(CZGate().to_matrix() == op_false.to_matrix())) + np.testing.assert_allclose(CXGate().to_matrix(), op_true.to_matrix()) + np.testing.assert_allclose(CZGate().to_matrix(), op_false.to_matrix()) def test_not_crossing_between_control_flow_ops(self): """Test that consolidation does not occur between control flow ops.""" From c24f63e3d12a49b6856ff1b135bad118640e01fe Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 19 Jul 2023 17:09:56 -0400 Subject: [PATCH 18/26] Simplify test in test_consolidate_blocks and factor * Gates used in testing ConsolidateBlocks with control flow ops were copied from another test in the file. They complicate the test, and removing them does not weaken the test at all. * Factor some code within a test into a function --- .../transpiler/test_consolidate_blocks.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index cd81d2c73ed6..8a049caaab56 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -430,14 +430,13 @@ def test_identity_1q_unitary_is_removed(self): def test_descent_into_control_flow(self): """Test consolidation in blocks when control flow op is the same as at top level.""" - qc = QuantumCircuit(2, 1) - u2gate1 = U2Gate(-1.2, np.pi) - u2gate2 = U2Gate(-3.4, np.pi) - qc.append(u2gate1, [0]) - qc.append(u2gate2, [1]) - qc.cx(0, 1) - qc.cx(1, 0) + def append_test_gates(qc): + qc.cx(0, 1) + qc.cx(1, 0) + + qc = QuantumCircuit(2, 1) + append_test_gates(qc) pass_manager = PassManager() pass_manager.append(Collect2qBlocks()) pass_manager.append(ConsolidateBlocks(force_consolidate=True)) @@ -446,10 +445,7 @@ def test_descent_into_control_flow(self): qc_control_flow = QuantumCircuit(2, 1) qc_block = QuantumCircuit(qc.qubits, qc.clbits) - qc_block.append(u2gate1, [0]) - qc_block.append(u2gate2, [1]) - qc_block.cx(0, 1) - qc_block.cx(1, 0) + append_test_gates(qc_block) ifop = IfElseOp((qc.clbits[0], False), qc_block, None) qc_control_flow.append(ifop, qc.qubits, qc.clbits) From 2ae8558b43df1988340d2e9021c29084970cddde Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 19 Jul 2023 18:10:59 -0400 Subject: [PATCH 19/26] Factor more code in test --- .../transpiler/test_consolidate_blocks.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index 8a049caaab56..60a0e5ab68fe 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -435,25 +435,24 @@ def append_test_gates(qc): qc.cx(0, 1) qc.cx(1, 0) + def do_consolidation(qc): + pass_manager = PassManager() + pass_manager.append(Collect2qBlocks()) + pass_manager.append(ConsolidateBlocks(force_consolidate=True)) + return pass_manager.run(qc) + qc = QuantumCircuit(2, 1) append_test_gates(qc) - pass_manager = PassManager() - pass_manager.append(Collect2qBlocks()) - pass_manager.append(ConsolidateBlocks(force_consolidate=True)) - result_top = pass_manager.run(qc) + result_top = do_consolidation(qc) qc_control_flow = QuantumCircuit(2, 1) - qc_block = QuantumCircuit(qc.qubits, qc.clbits) append_test_gates(qc_block) ifop = IfElseOp((qc.clbits[0], False), qc_block, None) qc_control_flow.append(ifop, qc.qubits, qc.clbits) - pass_manager = PassManager() - pass_manager.append(Collect2qBlocks()) - pass_manager.append(ConsolidateBlocks(force_consolidate=True)) - result_block = pass_manager.run(qc_control_flow) + result_block = do_consolidation(qc_control_flow) gate_top = result_top[0].operation gate_block = result_block[0].operation.blocks[0][0].operation np.testing.assert_allclose(gate_top, gate_block) From c077e49e58379d7b28b0315e0d205dfc83ee8899 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 19 Jul 2023 18:11:29 -0400 Subject: [PATCH 20/26] Use clbit in circuit as test bit when appending IfElse This has no effect on the test. But, previously, we used a clbit taken from an unrelated circuit. This might give the impression that there is some significance to this unexpected choice. --- test/python/transpiler/test_consolidate_blocks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index 60a0e5ab68fe..799fb4afdb64 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -449,7 +449,7 @@ def do_consolidation(qc): qc_block = QuantumCircuit(qc.qubits, qc.clbits) append_test_gates(qc_block) - ifop = IfElseOp((qc.clbits[0], False), qc_block, None) + ifop = IfElseOp((qc_control_flow.clbits[0], False), qc_block, None) qc_control_flow.append(ifop, qc.qubits, qc.clbits) result_block = do_consolidation(qc_control_flow) From 7cef53b04417a2b64f73a6d3dd849fbc661d528d Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 19 Jul 2023 18:15:50 -0400 Subject: [PATCH 21/26] In test, use bits in circuit as specifiers when appending gate to that circuit Previously we used qubits and clbits from an unrelated circuit to specify which bits to apply an IfElse to when appending. Here, we use bits from the same circuit that we are appending the gate to. This does not change the test. But again, one might look for significance in the previous unusual choice. Qiskit considers two registers of the same length and the same name to be the same register. The previous behavior depended on this choice. --- test/python/transpiler/test_consolidate_blocks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index 799fb4afdb64..38222c607ae7 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -450,7 +450,7 @@ def do_consolidation(qc): append_test_gates(qc_block) ifop = IfElseOp((qc_control_flow.clbits[0], False), qc_block, None) - qc_control_flow.append(ifop, qc.qubits, qc.clbits) + qc_control_flow.append(ifop, qc_control_flow.qubits, qc_control_flow.clbits) result_block = do_consolidation(qc_control_flow) gate_top = result_top[0].operation From d4460a58ed4a89c19bae8e3d9860bc7949072e0b Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 19 Jul 2023 18:24:41 -0400 Subject: [PATCH 22/26] In a test, don't use qubits from unrelated circuit when constructing circuit This has no effect on the test. As in the previous commits, the choice was unusual and might imply some significance. The more natural choice is to create new qubits by specifying the number. --- test/python/transpiler/test_consolidate_blocks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index 38222c607ae7..e8a1531fc0a5 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -446,7 +446,7 @@ def do_consolidation(qc): result_top = do_consolidation(qc) qc_control_flow = QuantumCircuit(2, 1) - qc_block = QuantumCircuit(qc.qubits, qc.clbits) + qc_block = QuantumCircuit(2, 1) append_test_gates(qc_block) ifop = IfElseOp((qc_control_flow.clbits[0], False), qc_block, None) From 54bd958c1db33a03e37b3ae8071043136d934540 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 19 Jul 2023 18:39:20 -0400 Subject: [PATCH 23/26] Factor code in test to simplify test --- test/python/transpiler/test_consolidate_blocks.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index e8a1531fc0a5..8f5a48da751f 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -431,9 +431,11 @@ def test_identity_1q_unitary_is_removed(self): def test_descent_into_control_flow(self): """Test consolidation in blocks when control flow op is the same as at top level.""" - def append_test_gates(qc): + def circuit_of_test_gates(): + qc = QuantumCircuit(2, 1) qc.cx(0, 1) qc.cx(1, 0) + return qc def do_consolidation(qc): pass_manager = PassManager() @@ -441,15 +443,10 @@ def do_consolidation(qc): pass_manager.append(ConsolidateBlocks(force_consolidate=True)) return pass_manager.run(qc) - qc = QuantumCircuit(2, 1) - append_test_gates(qc) - result_top = do_consolidation(qc) + result_top = do_consolidation(circuit_of_test_gates()) qc_control_flow = QuantumCircuit(2, 1) - qc_block = QuantumCircuit(2, 1) - append_test_gates(qc_block) - - ifop = IfElseOp((qc_control_flow.clbits[0], False), qc_block, None) + ifop = IfElseOp((qc_control_flow.clbits[0], False), circuit_of_test_gates(), None) qc_control_flow.append(ifop, qc_control_flow.qubits, qc_control_flow.clbits) result_block = do_consolidation(qc_control_flow) From 8eae8c3cc652d3594fb3b2dcc499afcaed4053cc Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 19 Jul 2023 19:13:26 -0400 Subject: [PATCH 24/26] Simplify remaining control flow op tests for ConsolidateBlocks These simplifications are similar to those made for the first test. --- .../transpiler/test_consolidate_blocks.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index 8f5a48da751f..defa7ea25b0c 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -459,8 +459,8 @@ def test_not_crossing_between_control_flow_block_and_parent(self): blocks and the parent circuit.""" qc = QuantumCircuit(2, 1) qc.cx(0, 1) - qc_true = QuantumCircuit(qc.qubits, qc.clbits) - qc_false = QuantumCircuit(qc.qubits, qc.clbits) + qc_true = QuantumCircuit(2, 1) + qc_false = QuantumCircuit(2, 1) qc_true.cx(0, 1) qc_false.cz(0, 1) ifop = IfElseOp((qc.clbits[0], True), qc_true, qc_false) @@ -472,17 +472,18 @@ def test_not_crossing_between_control_flow_block_and_parent(self): qc_out = pass_manager.run(qc) self.assertIsInstance(qc_out[0].operation, UnitaryGate) - np.testing.assert_allclose(CXGate().to_matrix(), qc_out[0].operation.to_matrix()) + np.testing.assert_allclose(CXGate(), qc_out[0].operation) op_true = qc_out[1].operation.blocks[0][0].operation op_false = qc_out[1].operation.blocks[1][0].operation - np.testing.assert_allclose(CXGate().to_matrix(), op_true.to_matrix()) - np.testing.assert_allclose(CZGate().to_matrix(), op_false.to_matrix()) + np.testing.assert_allclose(CXGate(), op_true) + np.testing.assert_allclose(CZGate(), op_false) + def test_not_crossing_between_control_flow_ops(self): """Test that consolidation does not occur between control flow ops.""" qc = QuantumCircuit(2, 1) - qc_true = QuantumCircuit(qc.qubits, qc.clbits) - qc_false = QuantumCircuit(qc.qubits, qc.clbits) + qc_true = QuantumCircuit(2, 1) + qc_false = QuantumCircuit(2, 1) qc_true.cx(0, 1) qc_false.cz(0, 1) ifop1 = IfElseOp((qc.clbits[0], True), qc_true, qc_false) @@ -499,10 +500,10 @@ def test_not_crossing_between_control_flow_ops(self): op_false1 = qc_out[0].operation.blocks[1][0].operation op_true2 = qc_out[1].operation.blocks[0][0].operation op_false2 = qc_out[1].operation.blocks[1][0].operation - np.testing.assert_allclose(CXGate().to_matrix(), op_true1.to_matrix()) - np.testing.assert_allclose(CZGate().to_matrix(), op_false1.to_matrix()) - np.testing.assert_allclose(CXGate().to_matrix(), op_true2.to_matrix()) - np.testing.assert_allclose(CZGate().to_matrix(), op_false2.to_matrix()) + np.testing.assert_allclose(CXGate(), op_true1) + np.testing.assert_allclose(CZGate(), op_false1) + np.testing.assert_allclose(CXGate(), op_true2) + np.testing.assert_allclose(CZGate(), op_false2) if __name__ == "__main__": From b36a9492f360910ec39e06b0e197e6807d9cecc7 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Wed, 19 Jul 2023 20:31:38 -0400 Subject: [PATCH 25/26] Run black --- test/python/transpiler/test_consolidate_blocks.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index defa7ea25b0c..dc58b0b8c6fb 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -478,7 +478,6 @@ def test_not_crossing_between_control_flow_block_and_parent(self): np.testing.assert_allclose(CXGate(), op_true) np.testing.assert_allclose(CZGate(), op_false) - def test_not_crossing_between_control_flow_ops(self): """Test that consolidation does not occur between control flow ops.""" qc = QuantumCircuit(2, 1) From 5dc5f85ff86a8ec406588f6a27eec49d1a8076f7 Mon Sep 17 00:00:00 2001 From: John Lapeyre Date: Thu, 20 Jul 2023 12:21:34 -0400 Subject: [PATCH 26/26] Update test/python/transpiler/test_consolidate_blocks.py Co-authored-by: Jake Lishman --- .../transpiler/test_consolidate_blocks.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index dc58b0b8c6fb..6eb78086dd9b 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -504,6 +504,56 @@ def test_not_crossing_between_control_flow_ops(self): np.testing.assert_allclose(CXGate(), op_true2) np.testing.assert_allclose(CZGate(), op_false2) + def test_inverted_order(self): + """Test that the `ConsolidateBlocks` pass creates matrices that are correct under the + application of qubit binding from the outer circuit to the inner block.""" + body = QuantumCircuit(2, 1) + body.h(0) + body.cx(0, 1) + + id_op = Operator(np.eye(4)) + bell = Operator(body) + + qc = QuantumCircuit(2, 1) + # The first two 'if' blocks here represent exactly the same operation as each other on the + # outer bits, because in the second, the bit-order of the block is reversed, but so is the + # order of the bits in the outer circuit that they're bound to, which makes them the same. + # The second two 'if' blocks also represnt the same operation as each other, but the 'first + # two' and 'second two' pairs represent qubit-flipped operations. + qc.if_test((0, False), body.copy(), qc.qubits, qc.clbits) + qc.if_test((0, False), body.reverse_bits(), reversed(qc.qubits), qc.clbits) + qc.if_test((0, False), body.copy(), reversed(qc.qubits), qc.clbits) + qc.if_test((0, False), body.reverse_bits(), qc.qubits, qc.clbits) + + # The first two operations represent Bell-state creation on _outer_ qubits (0, 1), the + # second two represent the same creation, but on outer qubits (1, 0). + expected = [ + id_op.compose(bell, qargs=(0, 1)), + id_op.compose(bell, qargs=(0, 1)), + id_op.compose(bell, qargs=(1, 0)), + id_op.compose(bell, qargs=(1, 0)), + ] + + actual = [] + pm = PassManager([Collect2qBlocks(), ConsolidateBlocks(force_consolidate=True)]) + for instruction in pm.run(qc).data: + # For each instruction, the `UnitaryGate` that's been created will always have been made + # (as an implementation detail of `DAGCircuit.collect_2q_runs` as of commit e5950661) to + # apply to _inner_ qubits (0, 1). We need to map that back to the _outer_ qubits that + # it applies to compare. + body = instruction.operation.blocks[0] + wire_map = { + inner: qc.find_bit(outer).index + for inner, outer in zip(body.qubits, instruction.qubits) + } + actual.append( + id_op.compose( + Operator(body.data[0].operation), + qargs=[wire_map[q] for q in body.data[0].qubits], + ) + ) + self.assertEqual(expected, actual) + if __name__ == "__main__": unittest.main()