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 <mtreinish@kortar.org>
This commit is contained in:
Kevin Hartman 2023-07-21 08:02:17 -04:00 committed by GitHub
parent 2b225d3713
commit 4737e87ca5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 39 additions and 31 deletions

View File

@ -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"}),

View File

@ -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(

View File

@ -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(

View File

@ -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``).

View File

@ -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."""

View File

@ -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."""