Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow circuits w/ control flow in O2 and O3 #10372

Merged
merged 8 commits into from
Jul 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
kevinhartman marked this conversation as resolved.
Show resolved Hide resolved
)
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