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

Unroll3qOrMore before layout allocation stage in the levels 0, 1, and 2 #7251

Merged
merged 12 commits into from
Nov 30, 2021
36 changes: 18 additions & 18 deletions qiskit/transpiler/preset_passmanagers/level0.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,22 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
unitary_synthesis_method = pass_manager_config.unitary_synthesis_method
unitary_synthesis_plugin_config = pass_manager_config.unitary_synthesis_plugin_config

# 1. Choose an initial layout if not set by user (default: trivial layout)
# 1. Decompose so only 1-qubit and 2-qubit gates remain
_unroll3q = [
# Use unitary synthesis for basis aware decomposition of UnitaryGates
UnitarySynthesis(
basis_gates,
approximation_degree=approximation_degree,
coupling_map=coupling_map,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we haven't laid out the qubits yet, what does the coupling map mean for the plugin? We could omit it, but then it starts to feel like we'll not following the calling conventions we laid out in the definition of the synthesis plugin.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is a good point. If the circuit doesn't have a layout set this will be doing the wrong thing, because the synthesis pass assumes it's going to have the bits in physical order in the dag. I think we should set this to None if run prior to layout. None is still a valid value here to indicate there is no target coupling map set.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, I forgot we still passed through explicit None on that one. Yeah, that seems like the best solution for now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the circuit doesn't have a layout set this will be doing the wrong thing,

Any suggested test to add about this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit tricky to test for because it doesn't come up with the default/built-in unitary synthesis mechanism since for >=3q it uses isometry which isn't coupling map aware. For the default plugin this only comes up with <=2q unitaries. It's only for potential plugins like AQC or BQSKit where this would come up, since the synthesis pass sends bit indices to the plugin assuming their in physical order: https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/transpiler/passes/synthesis/unitary_synthesis.py#L272-L276 (ie post routing)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could set up a unittest.mock.MagicMock wrapper around the default synthesis plugin to catch all calls, then run the transpiler pass and assert that the first call has min_qubits=3 and coupling_map=None, perhaps? There's some examples of this ~~sort of test in test_unitary_synthesis_plugin.py, like here: https://github.com/Qiskit/qiskit-terra/blob/7e118e81344cb05aada5082e3cc71c1c239baaa5/test/python/transpiler/test_unitary_synthesis_plugin.py#L220-L235

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The testing is a bit artificial. Is it worthy?

backend_props=backend_properties,
method=unitary_synthesis_method,
min_qubits=3,
plugin_config=unitary_synthesis_plugin_config,
),
Unroll3qOrMore(),
]

# 2. Choose an initial layout if not set by user (default: trivial layout)
_given_layout = SetLayout(initial_layout)

def _choose_layout_condition(property_set):
Expand All @@ -111,24 +126,9 @@ def _choose_layout_condition(property_set):
else:
raise TranspilerError("Invalid layout method %s." % layout_method)

# 2. Extend dag/layout with ancillas using the full coupling map
# 3. Extend dag/layout with ancillas using the full coupling map
_embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()]

# 3. Decompose so only 1-qubit and 2-qubit gates remain
_unroll3q = [
# Use unitary synthesis for basis aware decomposition of UnitaryGates
UnitarySynthesis(
basis_gates,
approximation_degree=approximation_degree,
coupling_map=coupling_map,
backend_props=backend_properties,
method=unitary_synthesis_method,
min_qubits=3,
plugin_config=unitary_synthesis_plugin_config,
),
Unroll3qOrMore(),
]

# 4. Swap to fit the coupling map
_swap_check = CheckMap(coupling_map)

Expand Down Expand Up @@ -245,9 +245,9 @@ def _contains_delay(property_set):
pm0 = PassManager()
if coupling_map or initial_layout:
pm0.append(_given_layout)
pm0.append(_unroll3q)
pm0.append(_choose_layout, condition=_choose_layout_condition)
pm0.append(_embed)
pm0.append(_unroll3q)
pm0.append(_swap_check)
pm0.append(_swap, condition=_swap_condition)
pm0.append(_unroll)
Expand Down
36 changes: 18 additions & 18 deletions qiskit/transpiler/preset_passmanagers/level1.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,22 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
def _choose_layout_condition(property_set):
return not property_set["layout"]

# 2. Use a better layout on densely connected qubits, if circuit needs swaps
# 2. Decompose so only 1-qubit and 2-qubit gates remain
_unroll3q = [
# Use unitary synthesis for basis aware decomposition of UnitaryGates
UnitarySynthesis(
basis_gates,
approximation_degree=approximation_degree,
coupling_map=coupling_map,
method=unitary_synthesis_method,
backend_props=backend_properties,
min_qubits=3,
plugin_config=unitary_synthesis_plugin_config,
),
Unroll3qOrMore(),
]

# 3. Use a better layout on densely connected qubits, if circuit needs swaps
if layout_method == "trivial":
_improve_layout = TrivialLayout(coupling_map)
elif layout_method == "dense":
Expand All @@ -130,24 +145,9 @@ def _not_perfect_yet(property_set):
and property_set["trivial_layout_score"] != 0
)

# 3. Extend dag/layout with ancillas using the full coupling map
# 4. Extend dag/layout with ancillas using the full coupling map
_embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()]

# 4. Decompose so only 1-qubit and 2-qubit gates remain
_unroll3q = [
# Use unitary synthesis for basis aware decomposition of UnitaryGates
UnitarySynthesis(
basis_gates,
approximation_degree=approximation_degree,
coupling_map=coupling_map,
method=unitary_synthesis_method,
backend_props=backend_properties,
min_qubits=3,
plugin_config=unitary_synthesis_plugin_config,
),
Unroll3qOrMore(),
]

# 5. Swap to fit the coupling map
_swap_check = CheckMap(coupling_map)

Expand Down Expand Up @@ -277,10 +277,10 @@ def _contains_delay(property_set):
pm1 = PassManager()
if coupling_map or initial_layout:
pm1.append(_given_layout)
pm1.append(_unroll3q)
pm1.append(_choose_layout_and_score, condition=_choose_layout_condition)
pm1.append(_improve_layout, condition=_not_perfect_yet)
pm1.append(_embed)
pm1.append(_unroll3q)
pm1.append(_swap_check)
pm1.append(_swap, condition=_swap_condition)
pm1.append(_unroll)
Expand Down
42 changes: 21 additions & 21 deletions qiskit/transpiler/preset_passmanagers/level2.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,22 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
timing_constraints = pass_manager_config.timing_constraints or TimingConstraints()
unitary_synthesis_plugin_config = pass_manager_config.unitary_synthesis_plugin_config

# 1. Search for a perfect layout, or choose a dense layout, if no layout given
# 1. Unroll to 1q or 2q gates
_unroll3q = [
# Use unitary synthesis for basis aware decomposition of UnitaryGates
UnitarySynthesis(
basis_gates,
approximation_degree=approximation_degree,
coupling_map=coupling_map,
backend_props=backend_properties,
method=unitary_synthesis_method,
min_qubits=3,
plugin_config=unitary_synthesis_plugin_config,
),
Unroll3qOrMore(),
]

# 2. Search for a perfect layout, or choose a dense layout, if no layout given
_given_layout = SetLayout(initial_layout)

def _choose_layout_condition(property_set):
Expand All @@ -121,7 +136,7 @@ def _choose_layout_condition(property_set):
Layout2qDistance(coupling_map, property_name="trivial_layout_score"),
]
)
# 1b. If a trivial layout wasn't perfect (ie no swaps are needed) then try using
# 2b. If a trivial layout wasn't perfect (ie no swaps are needed) then try using
# CSP layout to find a perfect layout
_choose_layout_1 = (
[]
Expand All @@ -132,7 +147,7 @@ def _choose_layout_condition(property_set):
def _trivial_not_perfect(property_set):
# Verify that a trivial layout is perfect. If trivial_layout_score > 0
# the layout is not perfect. The layout is unconditionally set by trivial
# layout so we need to clear it before contuing.
# layout so we need to clear it before continuing.
if property_set["trivial_layout_score"] is not None:
if property_set["trivial_layout_score"] != 0:
property_set["layout"]._wrapped = None
Expand All @@ -152,7 +167,7 @@ def _csp_not_found_match(property_set):
return True
return False

# 1c. if CSP layout doesn't converge on a solution use layout_method (dense) to get a layout
# 2c. if CSP layout doesn't converge on a solution use layout_method (dense) to get a layout
if layout_method == "trivial":
_choose_layout_2 = TrivialLayout(coupling_map)
elif layout_method == "dense":
Expand All @@ -164,24 +179,9 @@ def _csp_not_found_match(property_set):
else:
raise TranspilerError("Invalid layout method %s." % layout_method)

# 2. Extend dag/layout with ancillas using the full coupling map
# 3. Extend dag/layout with ancillas using the full coupling map
_embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()]

# 3. Unroll to 1q or 2q gates
_unroll3q = [
# Use unitary synthesis for basis aware decomposition of UnitaryGates
UnitarySynthesis(
basis_gates,
approximation_degree=approximation_degree,
coupling_map=coupling_map,
backend_props=backend_properties,
method=unitary_synthesis_method,
min_qubits=3,
plugin_config=unitary_synthesis_plugin_config,
),
Unroll3qOrMore(),
]

# 4. Swap to fit the coupling map
_swap_check = CheckMap(coupling_map)

Expand Down Expand Up @@ -315,11 +315,11 @@ def _contains_delay(property_set):
pm2 = PassManager()
if coupling_map or initial_layout:
pm2.append(_given_layout)
pm2.append(_unroll3q)
pm2.append(_choose_layout_0, condition=_choose_layout_condition)
pm2.append(_choose_layout_1, condition=_trivial_not_perfect)
pm2.append(_choose_layout_2, condition=_csp_not_found_match)
pm2.append(_embed)
pm2.append(_unroll3q)
pm2.append(_swap_check)
pm2.append(_swap, condition=_swap_condition)
pm2.append(_unroll)
Expand Down
6 changes: 6 additions & 0 deletions releasenotes/notes/7156-df1a60c608b93184.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
fixes:
- |
Fixed `#7156 <https://github.com/Qiskit/qiskit-terra/issues/7156>`__ .
Many layout methods ignore 3-or-more qubit gates resulting in expected layout allocation decisions.
The pass :class:`qiskit.transpiler.passes.Unroll3qOrMore` is now being executed before the layout pass.