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

Implement transforms to cancel redundant single-qubit operations #1455

Merged
merged 37 commits into from
Jul 19, 2021

Conversation

glassnotes
Copy link
Contributor

@glassnotes glassnotes commented Jul 8, 2021

Context: Addition of circuit optimization / compilation tools to PennyLane.

Description of the Change: Creates a subdirectory transforms/optimization with new transforms, cancel_inverses, and merge_rotations. The Operator class, and relevant single-qubit operations, are augmented with additional attributes to enable this.

Benefits: Enables reduction of single-qubit gates in a circuit.

Possible Drawbacks: None

Related GitHub Issues: None

@github-actions
Copy link
Contributor

github-actions bot commented Jul 8, 2021

Hello. You may have forgotten to update the changelog!
Please edit .github/CHANGELOG.md with:

  • A one-to-two sentence description of the change. You may include a small working example for new features.
  • A link back to this PR.
  • Your name (or GitHub username) in the contributors section.

@codecov
Copy link

codecov bot commented Jul 8, 2021

Codecov Report

Merging #1455 (9720a71) into master (a5b9b23) will increase coverage by 0.03%.
The diff coverage is 99.35%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1455      +/-   ##
==========================================
+ Coverage   98.23%   98.27%   +0.03%     
==========================================
  Files         163      167       +4     
  Lines       12086    12242     +156     
==========================================
+ Hits        11873    12031     +158     
+ Misses        213      211       -2     
Impacted Files Coverage Δ
...lane/transforms/optimization/optimization_utils.py 97.82% <97.82%> (ø)
pennylane/math/single_dispatch.py 99.42% <100.00%> (+0.01%) ⬆️
pennylane/operation.py 95.95% <100.00%> (+0.03%) ⬆️
pennylane/ops/qubit.py 98.68% <100.00%> (+0.02%) ⬆️
pennylane/transforms/__init__.py 100.00% <100.00%> (ø)
pennylane/transforms/optimization/__init__.py 100.00% <100.00%> (ø)
...nnylane/transforms/optimization/cancel_inverses.py 100.00% <100.00%> (ø)
...nnylane/transforms/optimization/merge_rotations.py 100.00% <100.00%> (ø)
... and 3 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a5b9b23...9720a71. Read the comment docs.

@@ -44,7 +44,9 @@
:toctree: api

~adjoint
~transforms.cancel_inverses
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Once we have more compilation transforms available, these should go in their own section together.

are_angles_1_zero = allclose(angles_1, zeros(3))
are_angles_2_zero = allclose(angles_2, zeros(3))

if are_angles_1_zero and are_angles_2_zero:
Copy link
Contributor Author

@glassnotes glassnotes Jul 12, 2021

Choose a reason for hiding this comment

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

This was a tricky function to write; I've separated things out as much as possible so that the quaternion math only has to be done when it's needed, but in principle we could make this a lot more streamlined by removing some of the cases. Not sure yet what is the best option; probably it'd be a good idea to do some more benchmarking/testing to determine whether having multiple cases like this is actually worth it.

@glassnotes glassnotes requested a review from josh146 July 12, 2021 15:00
Copy link
Member

@josh146 josh146 left a comment

Choose a reason for hiding this comment

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

Halfway through my review, will complete the rest tomorrow 🙂

.github/CHANGELOG.md Outdated Show resolved Hide resolved
Comment on lines +31 to +33
>>> print(qml.draw(optimized_qnode)())
0: ──T──┤ ⟨X⟩
1: ──Z──┤
Copy link
Member

Choose a reason for hiding this comment

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

🤯 This is almost magical

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🧙‍♀️

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed, amazing! 🤩

.github/CHANGELOG.md Outdated Show resolved Hide resolved
Comment on lines 543 to 550
is_self_inverse = None
"""bool: True if the operation is its own inverse."""

is_symmetric_over_wires = None
"""bool: True if the operation is the same if you exchange the order of wires."""

is_composable_rotation = None
"""bool: True if the composing the operations results in an addition of parameters."""
Copy link
Member

Choose a reason for hiding this comment

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

It might be good to have bool or None, and to specify what None means?

Copy link
Member

Choose a reason for hiding this comment

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

Oh, and is_symmetric_over_wires and is_composable_rotation might benefit from a more detailed description/small example?

Copy link
Member

Choose a reason for hiding this comment

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

After seeing that CZ is symmetric-over-wires, I get it now 😆

pennylane/transforms/optimization/cancel_inverses.py Outdated Show resolved Hide resolved


@qfunc_transform
def merge_rotations(tape):
Copy link
Member

Choose a reason for hiding this comment

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

is there any utility in allowing only specific rotation gates be selected?

e.g.,

@qml.qnode(dev)
@merge_rotations(tape, ops={"RX", "RY", "CRZ"})
def qfunc(params):

(can't think of a better argument name 🤔 )

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes this is a good idea; another suggestion from Nathan was to add an accuracy threshold for the angle cancellation.

I'd been wondering whether having a mix of parametrized and non-parametrized functions would make it more difficult to write the compile function though? Or could the pipeline be specified and applied like this:

pipeline = [cancel_inverses, merge_rotations(ops=["RX", "CRZ"])]

for transform in pipeline:
    qfunc = transform(qfunc)

Copy link
Member

@josh146 josh146 Jul 14, 2021

Choose a reason for hiding this comment

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

another suggestion from Nathan was to add an accuracy threshold for the angle cancellation.

Good idea!

Or could the pipeline be specified and applied like this:

Omg I think this would work! I've often despaired at transform(params)(func)(args) being less-than-intuitive, but this might be an advantage in this case!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay amazing, I will update this then!


# Queue the measurements normally
for m in tape.measurements:
apply(m)
Copy link
Member

Choose a reason for hiding this comment

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

It strikes me that so much of this for loop is similar to the previous transform... I wonder if there is a way of 'combining' the for loops if both are applied consecutively 🤔

Copy link
Member

Choose a reason for hiding this comment

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

Oh another question - I suppose this transform would have to be run multiple times to merge all Rot gates?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It should actually glob together arbitrary-length sequences, so if there 3 Rot gates in a row, it will end up as a single Rot in just one pass. Probably a good thing to add to a test!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It strikes me that so much of this for loop is similar to the previous transform... I wonder if there is a way of 'combining' the for loops if both are applied consecutively

We could just combine them into a single transform, but I kind of like the flexibility of them being separate. Also, the way the loops are set up, you might run into problems with circuits like the following:

qml.RZ(0.3, wires=0)
qml.Hadamard(wires=0)
qml.Hadamard(wires=0)
qml.RZ(0.2, wires=0)

If we do only one pass-through of a combined version, the two Hadamards would cancel, but the two RZ wouldn't merge, because we've moved ahead to the second RZ and it won't see the one behind. To catch them both, we'd need a second pass, at which point we may as well have a separate transform.

Copy link
Member

Choose a reason for hiding this comment

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

got it 👍

pennylane/transforms/optimization/optimization_utils.py Outdated Show resolved Hide resolved
pennylane/transforms/optimization/optimization_utils.py Outdated Show resolved Hide resolved
from pennylane.wires import Wires


def _find_next_gate(wires, op_list):
Copy link
Member

Choose a reason for hiding this comment

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

since these functions are being imported elsewhere, it kinda feels like they shouldn't be private 🤔 To me, private functions are generally used within the same module they are defined (not sure if there is a strong Python convention for this or not)

return stack([z1, y, z2])


def _fuse_rot_angles(angles_1, angles_2):
Copy link
Member

Choose a reason for hiding this comment

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

Feel free to turn this off using a pylint comment (or, alternatively, it might be a sign it could be factored out into smaller unit functions)

Copy link
Member

@josh146 josh146 left a comment

Choose a reason for hiding this comment

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

Tests are 💯 @glassnotes! I don't have any major suggestions, just minor ones I left in the first half of the review :)


# Then we can combine to create
# RZ(a + u) RY(v) RZ(w + f)
return stack([left_z + u, v, w + right_z])
Copy link
Member

Choose a reason for hiding this comment

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

code looks good, but I must admit I did not check the mathematics!


new_tape = qml.transforms.make_tape(transformed_qfunc)()

assert len(new_tape.operations) == 0
Copy link
Member

Choose a reason for hiding this comment

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

🙌

Co-authored-by: Josh Izaac <josh146@gmail.com>
.github/CHANGELOG.md Outdated Show resolved Hide resolved
pennylane/operation.py Outdated Show resolved Hide resolved
pennylane/operation.py Outdated Show resolved Hide resolved
else:
# If the wires are in a different order, gates that are "symmetric"
# over all wires (e.g., CZ), can be cancelled.
if current_gate.is_symmetric_over_all_wires:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added a bit of extra logic here to handle the additional case where adjacent controlled operations share all controls and a target, but the controls may be in a different order. However this had led to extra branching, and this can probably be streamlined.

@glassnotes glassnotes requested a review from josh146 July 14, 2021 16:00
Comment on lines +31 to +33
>>> print(qml.draw(optimized_qnode)())
0: ──T──┤ ⟨X⟩
1: ──Z──┤
Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed, amazing! 🤩

# There are then three possibilities that may lead to inverse cancellation. For a gate U,
# 1. U is self-inverse, and the next gate is also U
# 2. The current gate is U.inv and the next gate is U
# 3. The current gate is U and the next gate is U.inv
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this take into account inverse operators that are not next to each other in the queue but can commute through the intermediate gates that seperate them?

Copy link
Contributor

Choose a reason for hiding this comment

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

A similar question could apply to merging rotators too.

Copy link
Contributor Author

@glassnotes glassnotes Jul 16, 2021

Choose a reason for hiding this comment

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

Yes, it does handle cases where there are gates in between on different qubits (via find_next_gate). However it doesn't handle actual commutation relations (e.g., T Z T.inv will not cancel the T and T.inv... yet 😄 ).


Args:
qfunc (function): A quantum function.
atol (float): After fusion of gates, if the fused angle :math:`\theta` is such that
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice catch!

pennylane/transforms/optimization/merge_rotations.py Outdated Show resolved Hide resolved
wires_expected = [Wires(0), Wires(1)]
compare_operation_lists(ops, names_expected, wires_expected)

def test_three_qubits_inverse_after_cnot(self):
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 this answers my above question about commuting intermediate operations

@anthayes92
Copy link
Contributor

This looks fantastic @glassnotes ! Some really useful utils functions for compilation too 😁

Co-authored-by: anthayes92 <34694788+anthayes92@users.noreply.github.com>
@josh146 josh146 merged commit b8dad5d into master Jul 19, 2021
@josh146 josh146 deleted the ch7104-implement-transforms-to-cancel-redundant branch July 19, 2021 04:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants