From 4737e87ca5e7dd9f18d7b6b37257a4ea20db6cf2 Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Fri, 21 Jul 2023 08:02:17 -0400 Subject: [PATCH] Allow circuits w/ control flow in O2 and O3 (#10372) * Allow control flow in opt levels 2 and 3. * Add release note. * Enable testing for opt 2 and 3. * Remove guard on translation_method='synthesis'. * Fix test for synthesis guard. * 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. --------- Co-authored-by: Matthew Treinish --- .../transpiler/preset_passmanagers/common.py | 6 ++--- .../transpiler/preset_passmanagers/level2.py | 10 +++++++-- .../transpiler/preset_passmanagers/level3.py | 10 +++++++-- .../ctrl-flow-o2-o3-83f660d704226848.yaml | 7 ++++++ test/python/compiler/test_transpiler.py | 15 ++++++++----- .../transpiler/test_preset_passmanagers.py | 22 +++++-------------- 6 files changed, 39 insertions(+), 31 deletions(-) create mode 100644 releasenotes/notes/ctrl-flow-o2-o3-83f660d704226848.yaml diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index edf81efd8e..56ffb74f54 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -66,11 +66,9 @@ _CONTROL_FLOW_STATES = { "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"}), diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 743018881d..62880e3eee 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -254,8 +254,14 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa 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 7fe64eed52..81ca9af75e 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -189,8 +189,14 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa ] # 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( 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 0000000000..19e4bbe1d8 --- /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``). diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index bb2c07e5eb..92187ec749 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -1525,9 +1525,7 @@ class TestTranspile(QiskitTestCase): 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,10 +1757,15 @@ class TestPostTranspileIntegration(QiskitTestCase): 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.""" + 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( @@ -1781,7 +1784,7 @@ class TestPostTranspileIntegration(QiskitTestCase): 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 +1821,7 @@ class TestPostTranspileIntegration(QiskitTestCase): # 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 51685256a9..b7c496fabb 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -1427,7 +1427,7 @@ class TestGeenratePresetPassManagers(QiskitTestCase): 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 @@ class TestIntegrationControlFlow(QiskitTestCase): # 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 @@ class TestIntegrationControlFlow(QiskitTestCase): 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) @@ -1550,22 +1550,10 @@ class TestIntegrationControlFlow(QiskitTestCase): 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) - @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 +1579,7 @@ class TestIntegrationControlFlow(QiskitTestCase): 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."""