Skip to content

✨ Add MLIR pass for merging rotation gates #1019

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

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open

Conversation

denialhaag
Copy link
Collaborator

@denialhaag denialhaag commented Jun 23, 2025

Description

This PR adds a pass for merging simple rotation gates.

Fixes #898

Checklist:

  • The pull request only contains commits that are focused and relevant to this change.
  • I have added appropriate tests that cover the new/changed functionality.
  • I have updated the documentation to reflect these changes.
  • I have added entries to the changelog for any noteworthy additions, changes, fixes or removals.
  • I have added migration instructions to the upgrade guide (if needed).
  • The changes follow the project's style guidelines and introduce no new warnings.
  • The changes are fully tested and pass the CI checks.
  • I have reviewed my own code changes.

@denialhaag denialhaag self-assigned this Jun 23, 2025
@denialhaag
Copy link
Collaborator Author

So far, this PR adds a pass that can merge the following gates:

  • gphase
  • rx, ry, rz
  • rxx, ryy, rzz, rzx

I'm not sure about how to merge the remaining gates mentioned in #898. I have the feeling that they are fundamentally different and cannot be merged as easily because the respective gates cannot be decomposed into something that commutes.

I also still have to figure out why the tests are failing on Windows.

I already texted you privately, but if you have any input, I would be very interested, @burgholzer, @DRovara, and @ystade. :)

@DRovara
Copy link
Collaborator

DRovara commented Jun 25, 2025

I'm not sure about how to merge the remaining gates mentioned in #898. I have the feeling that they are fundamentally different and cannot be merged as easily because the respective gates cannot be decomposed into something that commutes.

I did some more thinking on this topic and I might have an idea: Rotations by Euler Angles are known to be difficult to compose from multiple rotations. That's why quaternions (https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation) are often used by game engines to represent rotations.

I think we might be able to do the same thing here: first translate the three Euler angles to quaternions, then we combine the multiple rotations into one single rotation and then we translate back into Euler angles. If you want we can also chat about this in person tomorrow, @denialhaag.

@burgholzer
Copy link
Member

I'm not sure about how to merge the remaining gates mentioned in #898. I have the feeling that they are fundamentally different and cannot be merged as easily because the respective gates cannot be decomposed into something that commutes.

I did some more thinking on this topic and I might have an idea: Rotations by Euler Angles are known to be difficult to compose from multiple rotations. That's why quaternions (https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation) are often used by game engines to represent rotations.

I think we might be able to do the same thing here: first translate the three Euler angles to quaternions, then we combine the multiple rotations into one single rotation and then we translate back into Euler angles. If you want we can also chat about this in person tomorrow, @denialhaag.

Have a look at the current Qiskit code in that regard. I remember that they are doing something very similar in the code since recently. Might serve as some good inspiration.

The rules for u gates should be fairly straight forward, I believe. u2 is only a special case of u so that should work similarly.

The two qubit gates are a bit more special.
I think that two of these with the same beta value just add up the angles of the first parameter. I am not aware of any more general rules for that at the moment.

In general, merging might not be particularly easy here. But cancellation is fairly easy to decide as also indicated by the code linked in the issue description. Maybe those gates should be included as special rules there.

Just my two cents here though.

@denialhaag
Copy link
Collaborator Author

denialhaag commented Jun 26, 2025

I did some more thinking on this topic and I might have an idea: Rotations by Euler Angles are known to be difficult to compose from multiple rotations. That's why quaternions (https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation) are often used by game engines to represent rotations.

I think we might be able to do the same thing here: first translate the three Euler angles to quaternions, then we combine the multiple rotations into one single rotation and then we translate back into Euler angles. If you want we can also chat about this in person tomorrow, @denialhaag.

@DRovara, yes, an in-person chat would be nice! I'll be in the office in a bit! :)

@denialhaag
Copy link
Collaborator Author

denialhaag commented Jun 26, 2025

Have a look at the current Qiskit code in that regard. I remember that they are doing something very similar in the code since recently. Might serve as some good inspiration.

The rules for u gates should be fairly straight forward, I believe. u2 is only a special case of u so that should work similarly.

The two qubit gates are a bit more special. I think that two of these with the same beta value just add up the angles of the first parameter. I am not aware of any more general rules for that at the moment.

In general, merging might not be particularly easy here. But cancellation is fairly easy to decide as also indicated by the code linked in the issue description. Maybe those gates should be included as special rules there.

Just my two cents here though.

@burgholzer, thanks for the ideas! I'll have a look at the Qiskit implementation!

@denialhaag
Copy link
Collaborator Author

For reference, the Qiskit pass is implemented here.

Copy link
Collaborator

@DRovara DRovara left a comment

Choose a reason for hiding this comment

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

Thanks a lot for the effort! I made some comments with suggestions, feel free to look into them.

One more thing though: As far as I see, this will only work if the parameters are passed as operands and won't work if the parameters are static attributes. This is okay for now, but we should probably keep this in mind and at the very least open a follow-up issue for that.

@denialhaag denialhaag added feature New feature or request c++ Anything related to C++ code MLIR Anything related to MLIR labels Jun 27, 2025
@denialhaag
Copy link
Collaborator Author

Thanks a lot for the effort! I made some comments with suggestions, feel free to look into them.

One more thing though: As far as I see, this will only work if the parameters are passed as operands and won't work if the parameters are static attributes. This is okay for now, but we should probably keep this in mind and at the very least open a follow-up issue for that.

Thank you for the review!

Regarding your second point, I think I am still missing some context (and knowledge) to understand. 😬

@denialhaag
Copy link
Collaborator Author

denialhaag commented Jun 27, 2025

My commits from today add support for canceling all gates mentioned in the issue. 👍

xxminusyy, xxminusyy, u, and u2 still cannot be merged, though. I don't know if it maybe makes sense to move this to a different issue. 🤔

@DRovara
Copy link
Collaborator

DRovara commented Jun 30, 2025

xxminusyy, xxminusyy, u, and u2 still cannot be merged, though. I don't know if it maybe makes sense to move this to a different issue. 🤔

I'm fine with making that a separate step. In that case, let's not forget to create the corresponding issue if we don't plan to tackle that situation immediately.

@burgholzer
Copy link
Member

xxminusyy, xxminusyy, u, and u2 still cannot be merged, though. I don't know if it maybe makes sense to move this to a different issue. 🤔

I'm fine with making that a separate step. In that case, let's not forget to create the corresponding issue if we don't plan to tackle that situation immediately.

Yeah. In the interest of closing out issues and merging features fast, I'd also argue that we should create a follow-up issue for that, which summarizes the discussions we had in this PR, and move this PR along so that it can be merged rather soon.

@denialhaag
Copy link
Collaborator Author

For reference, the Qiskit pass is implemented here.

I created #1029. 👍

@denialhaag
Copy link
Collaborator Author

After an offline discussion with @DRovara, I removed the feature of erasing gates whose parameters meet the necessary conditions. This should only be supported for gates with static parameters. To this end, I created #1030.

@DRovara
Copy link
Collaborator

DRovara commented Jun 30, 2025

After an offline discussion with @DRovara, I removed the feature of erasing gates whose parameters meet the necessary conditions. This should only be supported for gates with static parameters. To this end, I created #1030.

Just for context, the idea was

rx(a), rx(b) ==> rx(a + b), then if a + b === 0, the gate can be removed.

However, since this PR works with dynamic operands, we can't really know if a + b === 0.
The previous solution was to check if a and b were arith.constant expressions and getting their numeric values from there, but in my opinion, it makes sense to have this idea covered by a different constant folding path that converts the arguments from dynamic operands to static attributes.

Once we have that, a + b === 0 can be checked in the merge process for rotation gates that use static attributes (see #1030).

@denialhaag denialhaag changed the title ✨ MLIR - Add pass for merging rotation gates ✨ Add MLIR pass for merging rotation gates Jun 30, 2025
@denialhaag denialhaag marked this pull request as ready for review June 30, 2025 17:10
Copy link
Collaborator

@DRovara DRovara left a comment

Choose a reason for hiding this comment

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

I gave the PR another look and I just have some small comments left. I hope they should be addressable without too much effort.

Especially regarding the test cases, I think it is good to set up a good standard early.

Thanks a lot!

Comment on lines +203 to +204
if (type == "gphase" || type == "rx" || type == "ry" || type == "rz" ||
type == "rxx" || type == "ryy" || type == "rzz" || type == "rzx") {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we use the MERGEABLE_GATES constant for this check instead?

// -----
// This test checks that consecutive rx gates are merged correctly.

module {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it would be best if we still did some smaller clean-up with the CHECK strings here.

While writing the MQTOpt dialect features tests, we decided for the standard of collecting all CHECK strings at the start of the test function body. Maybe we should do that here, too, rather than interleaving the checks.

Also, we had some discussions on what parts of the code actually warrant CHECK strings and decided that only those functions relevant to the tested feature do. That means that something like the register allocations/deallocations or qubit extractions/insertions don't really require CHECK strings.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done. 👍

}

// -----
// This test checks that incompatible multi-qubit gates are not merged.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe there might still be some scenarios that are worthy of test cases. Here are some ideas:

  • rx(a) q1; rx(b) q2 should not be merged as they are different qubits
  • similarly, two rxx gates that only share one qubit should not be merged
  • maybe we can even test something like rzz q1, q2; rzz q2, q1 which should not be merged based on the code (but could in theory also be merged, just so that we have a documentation of that behaviour in the tests)
  • it would also be interesting to test how merging works if the angle parameters are not constants, but e.g. arguments to the test function. In that situation, there would probably be an actual "add" operation to add up the angles rather than just the single, already added up constant that is generated by MLIR's internal optimizations.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done. Let me know what you think! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ Anything related to C++ code feature New feature or request MLIR Anything related to MLIR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

✨ MLIR - Rotation Gate Merge Pass/Pattern
3 participants