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

Add identity removal pass to presets #13363

Merged
merged 4 commits into from
Nov 6, 2024
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
16 changes: 16 additions & 0 deletions qiskit/transpiler/preset_passmanagers/builtin_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
CommutativeCancellation,
ConsolidateBlocks,
InverseCancellation,
RemoveIdentityEquivalent,
)
from qiskit.transpiler.passes import Depth, Size, FixedPoint, MinimumPoint
from qiskit.transpiler.passes.utils.gates_basis import GatesInBasis
Expand Down Expand Up @@ -156,6 +157,13 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
if pass_manager_config.routing_method != "none":
init.append(ElidePermutations())
init.append(RemoveDiagonalGatesBeforeMeasure())
# Target not set on RemoveIdentityEquivalent because we haven't applied a Layout
Copy link
Contributor

Choose a reason for hiding this comment

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

I think I would expect the transpiler to remove Ry(0) & co even as a very basic optimization, should we also include this target-free run in optimization level 1?

Copy link
Member Author

Choose a reason for hiding this comment

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

My concern with doing it for level 1 was more about small angles like Ry(1e-8) will be removed and I was worried that might be surprising to people especially at level 1. But I don't feel super strongly about it, it's easy enough to add it to level 1 if you think that's better.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm you're right, I didn't think that these angles are already removed already... as rule we should maybe not increase the level of approximation that is already happening (e.g. cutoffs that unitary re-synthesis does). On level 1 we don't do re-synthesis, right? So then I agree we shouldn't add it one 1 yet 🙂

Copy link
Member Author

Choose a reason for hiding this comment

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

Level 1 will run 1q re-synthesis, but via Optikize1qGatesDecomposition not UnitarySynthesis. I can't remember how that pass handles tolerances, I'll have to check.

# yet so doing anything relative to an error rate in the target is not valid.
init.append(
RemoveIdentityEquivalent(
approximation_degree=pass_manager_config.approximation_degree
)
)
init.append(
InverseCancellation(
[
Expand Down Expand Up @@ -580,6 +588,10 @@ def _opt_control(property_set):

elif optimization_level == 2:
_opt = [
RemoveIdentityEquivalent(
approximation_degree=pass_manager_config.approximation_degree,
target=pass_manager_config.target,
),
Optimize1qGatesDecomposition(
basis=pass_manager_config.basis_gates, target=pass_manager_config.target
),
Expand All @@ -602,6 +614,10 @@ def _opt_control(property_set):
plugin_config=pass_manager_config.unitary_synthesis_plugin_config,
target=pass_manager_config.target,
),
RemoveIdentityEquivalent(
approximation_degree=pass_manager_config.approximation_degree,
target=pass_manager_config.target,
),
Optimize1qGatesDecomposition(
basis=pass_manager_config.basis_gates, target=pass_manager_config.target
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
features_transpiler:
- |
The :class:`.RemoveIdentityEquivalent` transpiler pass is now run as part
of the preset pass managers at optimization levels 2 and 3. The pass is
run in the ``init`` stage and the ``optimization`` stage, because the
optimizations it applies are valid in both stages and the pass is
fast to execute.
28 changes: 18 additions & 10 deletions test/python/compiler/test_transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1796,21 +1796,29 @@ def test_no_infinite_loop(self, optimization_level):
seed_transpiler=42,
)

# Expect a -pi/2 global phase for the U3 to RZ/SX conversion, and
# a -0.5 * theta phase for RZ to P twice, once at theta, and once at 3 pi
# for the second and third RZ gates in the U3 decomposition.
expected = QuantumCircuit(
1, global_phase=-np.pi / 2 - 0.5 * (-0.2 + np.pi) - 0.5 * 3 * np.pi
)
expected.p(-np.pi, 0)
expected.sx(0)
expected.p(np.pi - 0.2, 0)
expected.sx(0)
if optimization_level == 1:
# Expect a -pi/2 global phase for the U3 to RZ/SX conversion, and
# a -0.5 * theta phase for RZ to P twice, once at theta, and once at 3 pi
# for the second and third RZ gates in the U3 decomposition.
expected = QuantumCircuit(
1, global_phase=-np.pi / 2 - 0.5 * (-0.2 + np.pi) - 0.5 * 3 * np.pi
)
expected.p(-np.pi, 0)
expected.sx(0)
expected.p(np.pi - 0.2, 0)
expected.sx(0)
else:
expected = QuantumCircuit(1, global_phase=(15 * np.pi - 1) / 10)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is slightly unexpected, I guess at some point a gate is now removed and Optimize1qGatesDecomposition optimizes the sequence differently? Or why do you think this happens?

Copy link
Member Author

Choose a reason for hiding this comment

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

That was my assumption, I don't remember the exact details though. Let me run it with a callback and see what's going on exactly.

Copy link
Member Author

@mtreinish mtreinish Nov 6, 2024

Choose a reason for hiding this comment

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

Looking through each pass it looks like RemoveIdentityEquivalent changes:

global phase: 4.6124
   ┌──────┐┌────┐┌───────────┐┌────┐┌───────┐
q: ┤ P(0) ├┤ √X ├┤ P(3.3416) ├┤ √X ├┤ P(3π) ├
   └──────┘└────┘└───────────┘└────┘└───────┘

to:

global phase: 4.6124
   ┌────┐┌───────────┐┌────┐┌───────┐
q: ┤ √X ├┤ P(3.3416) ├┤ √X ├┤ P(3π) ├
   └────┘└───────────┘└────┘└───────┘

This changes the output of Optimize1qGatesDecomposition which runs immediately after from:

global phase: 4.8124
   ┌───────┐┌────┐┌───────────┐┌────┐
q: ┤ P(-π) ├┤ √X ├┤ P(2.9416) ├┤ √X ├
   └───────┘└────┘└───────────┘└────┘

which is the output on calling the pass on the circuit with P(0)

to:

global phase: 4.6124
   ┌────┐┌───────────┐┌────┐┌───────┐
q: ┤ √X ├┤ P(3.3416) ├┤ √X ├┤ P(3π) ├
   └────┘└───────────┘└────┘└───────┘

which is the pass's output with P(0) removed. What this is coming down to is the preference for Optimize1qGatesDecomposition to defer to the input circuit if it doesn't think the synthesis output is any better. Since the gate counts of either output are the same, the pass views them as equivalent (in the absence of error rates in the target) and prefer to not modify the circuit instead of use the synthesis output. While in the case of without the removal pass being run there is an extra 1q gate in the sequence the pass views the synthesis output as being more efficient because it has 1 fewer gate.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ahh that makes sense -- thanks for checking, better to be sure! 🙂

expected.sx(0)
expected.p(1.0 / 5.0 + np.pi, 0)
expected.sx(0)
expected.p(3 * np.pi, 0)

error_message = (
f"\nOutput circuit:\n{out!s}\n{Operator(out).data}\n"
f"Expected circuit:\n{expected!s}\n{Operator(expected).data}"
)
self.assertEqual(Operator(qc), Operator(out))
self.assertEqual(out, expected, error_message)

@data(0, 1, 2, 3)
Expand Down