From d33b3c45fdb623818dbe5d380db621395c200b80 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Sun, 2 Jul 2023 22:45:34 -0400 Subject: [PATCH 1/6] Allow control flow in opt levels 2 and 3. --- qiskit/transpiler/preset_passmanagers/level2.py | 10 ++++++++-- qiskit/transpiler/preset_passmanagers/level3.py | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 743018881da3..62880e3eeec9 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -254,8 +254,14 @@ def _unroll_condition(property_set): sched = plugin_manager.get_passmanager_stage( "scheduling", scheduling_method, pass_manager_config, optimization_level=2 ) - init = common.generate_error_on_control_flow( - "The optimizations in optimization_level=2 do not yet support control flow." + init = common.generate_control_flow_options_check( + layout_method=layout_method, + routing_method=routing_method, + translation_method=translation_method, + optimization_method=optimization_method, + scheduling_method=scheduling_method, + basis_gates=basis_gates, + target=target, ) if init_method is not None: init += plugin_manager.get_passmanager_stage( diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 7fe64eed521d..81ca9af75eee 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -189,8 +189,14 @@ def _opt_control(property_set): ] # Build pass manager - init = common.generate_error_on_control_flow( - "The optimizations in optimization_level=3 do not yet support control flow." + init = common.generate_control_flow_options_check( + layout_method=layout_method, + routing_method=routing_method, + translation_method=translation_method, + optimization_method=optimization_method, + scheduling_method=scheduling_method, + basis_gates=basis_gates, + target=target, ) if init_method is not None: init += plugin_manager.get_passmanager_stage( From 7e18682c354f59bae40fd4eb97e7353f96f3f669 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Sun, 2 Jul 2023 22:51:55 -0400 Subject: [PATCH 2/6] Add release note. --- releasenotes/notes/ctrl-flow-o2-o3-83f660d704226848.yaml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 releasenotes/notes/ctrl-flow-o2-o3-83f660d704226848.yaml diff --git a/releasenotes/notes/ctrl-flow-o2-o3-83f660d704226848.yaml b/releasenotes/notes/ctrl-flow-o2-o3-83f660d704226848.yaml new file mode 100644 index 000000000000..19e4bbe1d818 --- /dev/null +++ b/releasenotes/notes/ctrl-flow-o2-o3-83f660d704226848.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Control-flow operations are now supported through the transpiler at + all optimization levels, including levels 2 and 3 (e.g. calling + :func:`.transpile` or :func:`.generate_preset_pass_manager` with + keyword argument ``optimization_level=3``). From b1808ce6b8434ee7b3e8d40e814b70928e080684 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Wed, 5 Jul 2023 15:27:08 -0400 Subject: [PATCH 3/6] Enable testing for opt 2 and 3. --- test/python/compiler/test_transpiler.py | 10 ++++------ .../transpiler/test_preset_passmanagers.py | 20 +++++-------------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index ebfc79af9b04..902b5370ad99 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -1525,9 +1525,7 @@ def test_target_ideal_gates(self, opt_level): self.assertEqual(Operator.from_circuit(result), Operator.from_circuit(qc)) - # TODO: Add optimization level 2 and 3 after they support control flow - # compilation - @data(0, 1) + @data(0, 1, 2, 3) def test_transpile_with_custom_control_flow_target(self, opt_level): """Test transpile() with a target and constrol flow ops.""" target = FakeMumbaiV2().target @@ -1759,7 +1757,7 @@ def test_qpy_roundtrip_backendv2(self, optimization_level): self.assertEqual(round_tripped, transpiled) - @data(0, 1) + @data(0, 1, 2, 3) def test_qpy_roundtrip_control_flow(self, optimization_level): """Test that the output of a transpiled circuit with control flow can be round-tripped through QPY.""" @@ -1781,7 +1779,7 @@ def test_qpy_roundtrip_control_flow(self, optimization_level): round_tripped = qpy.load(buffer)[0] self.assertEqual(round_tripped, transpiled) - @data(0, 1) + @data(0, 1, 2, 3) def test_qpy_roundtrip_control_flow_backendv2(self, optimization_level): """Test that the output of a transpiled circuit with control flow can be round-tripped through QPY.""" @@ -1818,7 +1816,7 @@ def test_qasm3_output(self, optimization_level): # itself doesn't throw an error, though. self.assertIsInstance(qasm3.dumps(transpiled).strip(), str) - @data(0, 1) + @data(0, 1, 2, 3) def test_qasm3_output_control_flow(self, optimization_level): """Test that the output of a transpiled circuit with control flow can be dumped into OpenQASM 3.""" diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index 51685256a92f..9fbdbc3e7770 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -1427,7 +1427,7 @@ def get_translation_stage_plugin(self): class TestIntegrationControlFlow(QiskitTestCase): """Integration tests for control-flow circuits through the preset pass managers.""" - @data(0, 1) + @data(0, 1, 2, 3) def test_default_compilation(self, optimization_level): """Test that a simple circuit with each type of control-flow passes a full transpilation pipeline with the defaults.""" @@ -1503,7 +1503,7 @@ def _visit_block(circuit, stack=None): # Assert routing ran. _visit_block(transpiled) - @data(0, 1) + @data(0, 1, 2, 3) def test_allow_overriding_defaults(self, optimization_level): """Test that the method options can be overridden.""" circuit = QuantumCircuit(3, 1) @@ -1541,7 +1541,7 @@ def callback(pass_, **_): self.assertNotIn("SabreLayout", calls) self.assertNotIn("BasisTranslator", calls) - @data(0, 1) + @data(0, 1, 2, 3) def test_invalid_methods_raise_on_control_flow(self, optimization_level): """Test that trying to use an invalid method with control flow fails.""" qc = QuantumCircuit(1) @@ -1555,17 +1555,7 @@ def test_invalid_methods_raise_on_control_flow(self, optimization_level): with self.assertRaisesRegex(TranspilerError, "Got scheduling_method="): transpile(qc, scheduling_method="alap", optimization_level=optimization_level) - @data(2, 3) - def test_unsupported_levels_raise(self, optimization_level): - """Test that trying to use an invalid method with control flow fails.""" - qc = QuantumCircuit(1) - with qc.for_loop((1,)): - qc.x(0) - - with self.assertRaisesRegex(TranspilerError, "The optimizations in optimization_level="): - transpile(qc, optimization_level=optimization_level) - - @data(0, 1) + @data(0, 1, 2, 3) def test_unsupported_basis_gates_raise(self, optimization_level): """Test that trying to transpile a control-flow circuit for a backend that doesn't support the necessary operations in its `basis_gates` will raise a sensible error.""" @@ -1591,7 +1581,7 @@ def test_unsupported_basis_gates_raise(self, optimization_level): with self.assertRaisesRegex(TranspilerError, "The control-flow construct.*not supported"): transpile(qc, backend, optimization_level=optimization_level) - @data(0, 1) + @data(0, 1, 2, 3) def test_unsupported_targets_raise(self, optimization_level): """Test that trying to transpile a control-flow circuit for a backend that doesn't support the necessary operations in its `Target` will raise a more sensible error.""" From 2a2ae223e6485e179f47894c58addad6b6eb20ef Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Wed, 19 Jul 2023 11:40:54 -0400 Subject: [PATCH 4/6] Remove guard on translation_method='synthesis'. --- qiskit/transpiler/preset_passmanagers/common.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index edf81efd8ed0..13c372786653 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -66,11 +66,8 @@ "routing_method": _ControlFlowState( working={"none", "stochastic", "sabre"}, not_working={"lookahead", "basic"} ), - # 'synthesis' is not a supported translation method because of the block-collection passes - # involved; we currently don't have a neat way to pass the information about nested blocks - the - # `UnitarySynthesis` pass itself is control-flow aware. "translation_method": _ControlFlowState( - working={"translator", "unroller"}, not_working={"synthesis"} + working={"translator", "synthesis", "unroller"}, not_working=set(), ), "optimization_method": _ControlFlowState(working=set(), not_working=set()), "scheduling_method": _ControlFlowState(working=set(), not_working={"alap", "asap"}), From 8784260639a87990f84a54a0005b0fa5bf52e7b7 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Thu, 20 Jul 2023 17:45:29 -0400 Subject: [PATCH 5/6] Fix test for synthesis guard. --- qiskit/transpiler/preset_passmanagers/common.py | 3 ++- test/python/transpiler/test_preset_passmanagers.py | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 13c372786653..56ffb74f5415 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -67,7 +67,8 @@ working={"none", "stochastic", "sabre"}, not_working={"lookahead", "basic"} ), "translation_method": _ControlFlowState( - working={"translator", "synthesis", "unroller"}, not_working=set(), + working={"translator", "synthesis", "unroller"}, + not_working=set(), ), "optimization_method": _ControlFlowState(working=set(), not_working=set()), "scheduling_method": _ControlFlowState(working=set(), not_working={"alap", "asap"}), diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index 9fbdbc3e7770..b7c496fabb62 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -1550,8 +1550,6 @@ def test_invalid_methods_raise_on_control_flow(self, optimization_level): with self.assertRaisesRegex(TranspilerError, "Got routing_method="): transpile(qc, routing_method="lookahead", optimization_level=optimization_level) - with self.assertRaisesRegex(TranspilerError, "Got translation_method="): - transpile(qc, translation_method="synthesis", optimization_level=optimization_level) with self.assertRaisesRegex(TranspilerError, "Got scheduling_method="): transpile(qc, scheduling_method="alap", optimization_level=optimization_level) From b1a3d11b5ae8bb10d411601d743e3aa5e65a2245 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 21 Jul 2023 07:05:15 -0400 Subject: [PATCH 6/6] Skip o3 qpy full path transpile test on windows In CI we're seeing a reliable failure on windows when scipy is calling LAPACK. This appears to be unrelated to the change in this PR branch and is isolated to specific windows environments. This commit adds a skip condition to skip that one test to workaround the failure in CI. The wider issue with scipy compatibility on windows is being tracked in issue #10345, when we have a conclusion to that and can reliably run this test we should remove this skip condition. --- test/python/compiler/test_transpiler.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index b847799ecf41..92187ec74999 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -1761,6 +1761,11 @@ def test_qpy_roundtrip_backendv2(self, optimization_level): def test_qpy_roundtrip_control_flow(self, optimization_level): """Test that the output of a transpiled circuit with control flow can be round-tripped through QPY.""" + if optimization_level == 3 and sys.platform == "win32": + self.skipTest( + "This test case triggers a bug in the eigensolver routine on windows. " + "See #10345 for more details." + ) backend = FakeMelbourne() transpiled = transpile(