Skip to content

Commit

Permalink
Allow circuits w/ control flow in O2 and O3 (#10372)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
kevinhartman and mtreinish authored Jul 21, 2023
1 parent 2b225d3 commit 4737e87
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 31 deletions.
6 changes: 2 additions & 4 deletions qiskit/transpiler/preset_passmanagers/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,9 @@
"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"}),
Expand Down
10 changes: 8 additions & 2 deletions qiskit/transpiler/preset_passmanagers/level2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
10 changes: 8 additions & 2 deletions qiskit/transpiler/preset_passmanagers/level3.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
7 changes: 7 additions & 0 deletions releasenotes/notes/ctrl-flow-o2-o3-83f660d704226848.yaml
Original file line number Diff line number Diff line change
@@ -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``).
15 changes: 9 additions & 6 deletions test/python/compiler/test_transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1759,10 +1757,15 @@ 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."""
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(
Expand All @@ -1781,7 +1784,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."""
Expand Down Expand Up @@ -1818,7 +1821,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."""
Expand Down
22 changes: 5 additions & 17 deletions test/python/transpiler/test_preset_passmanagers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -1550,22 +1550,10 @@ 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)

@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."""
Expand All @@ -1591,7 +1579,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."""
Expand Down

0 comments on commit 4737e87

Please sign in to comment.