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 transform to push commuting operations through controlled gates #1464

Merged

Conversation

glassnotes
Copy link
Contributor

@glassnotes glassnotes commented Jul 20, 2021

Context: Addition of compilation transforms to PennyLane.

Description of the Change: Adds a new transform, commute_controlled that pushes commuting gates through controls/targets of controlled operations. All diagonal gates are pushed ahead (or behind, if specified) of control qubits, and X/Y/Z-type gates are pushed through targets when appropriate. Additional compilation attributes and properties are also added to support the transform.

Benefits: Moving as many gates as possible through controlled operations will enable more single-qubit gate simplifications to be performed, as well as fusion and cancellation of two-qubit controlled rotations once those in-between gates are out of the way.

Possible Drawbacks: Rather than adding attributes, it would be nice if we could instead resolve #1447 and have things inherit from ControlledOperation. However after a preliminary look, it seems that would be a pretty extensive refactor.

Related GitHub Issues: Uses some of the code and ideas from #1446 🎉

@github-actions
Copy link
Contributor

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.

@glassnotes glassnotes changed the title Implement transform to push commuting operations behind controlled gates Implement transform to push commuting operations through controlled gates Jul 20, 2021
"""

@property
def comp_control_wires(self):
Copy link
Contributor Author

@glassnotes glassnotes Jul 20, 2021

Choose a reason for hiding this comment

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

I originally named this property control_wires, however this caused issues with variables of the same name used in operations like MultiControlledX. I prefixed with comp_ to indicate compilation, but we should figure out a better way of incorporating this information for all gates (e.g., inheritance from a class similar to ControlledOperation)

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, I would suggest renaming MultiControlledX.control_wires -> MultiControlledX._control_wires.

Then, you could have this property do two things:

  • By default, Operation.control_wires could simply return self._control_wires if it exists, otherwise raising NotImplementedError

  • Inheriting devices can defined this class property directly.

@codecov
Copy link

codecov bot commented Jul 20, 2021

Codecov Report

Merging #1464 (455560c) into master (c9b78a5) will increase coverage by 0.00%.
The diff coverage is 99.20%.

Impacted file tree graph

@@           Coverage Diff            @@
##           master    #1464    +/-   ##
========================================
  Coverage   98.29%   98.30%            
========================================
  Files         169      170     +1     
  Lines       12360    12483   +123     
========================================
+ Hits        12149    12271   +122     
- Misses        211      212     +1     
Impacted Files Coverage Δ
...nnylane/transforms/optimization/cancel_inverses.py 100.00% <ø> (ø)
...nnylane/transforms/optimization/merge_rotations.py 100.00% <ø> (ø)
...ane/transforms/optimization/single_qubit_fusion.py 100.00% <ø> (ø)
pennylane/ops/qubit.py 98.67% <97.82%> (-0.04%) ⬇️
pennylane/operation.py 95.78% <100.00%> (+0.01%) ⬆️
pennylane/transforms/__init__.py 100.00% <100.00%> (ø)
pennylane/transforms/control.py 100.00% <100.00%> (ø)
pennylane/transforms/optimization/__init__.py 100.00% <100.00%> (ø)
...lane/transforms/optimization/commute_controlled.py 100.00% <100.00%> (ø)

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 c9b78a5...455560c. Read the comment docs.

pennylane/operation.py Outdated Show resolved Hide resolved
@property
def comp_control_wires(self):
return Wires(self.wires[0])

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure how to handle testing for this one, it seems redundant to do it for every gate.

Copy link
Member

Choose a reason for hiding this comment

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

Agree 🤔 What you could do is add # pragma: no cover to this property, to indicate it is intentionally not covered?

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 the # pragma (L595 of operation.py) but the warning still occurs. Was it added incorrectly?

Copy link
Member

Choose a reason for hiding this comment

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

Looks okay to me 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Huh :( Well... since it is not causing any issues with the CI and doesn't require an override, I am going to just merge 👍

if op.wires == qml.wires.Wires(wire_order):
mat = qml.math.dot(op.matrix, mat)
else:
mat = qml.math.dot(qml.SWAP(wires=[0, 1]).matrix, qml.math.dot(op.matrix, mat))
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 should have caught this when I wrote it originally. Emphasizes the need for that get_unitary_matrix transform.

Copy link
Member

Choose a reason for hiding this comment

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

👍

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.

Nice @glassnotes! I left some suggestions and thoughts regarding the structure. Looking forward to having all these transforms in!

"""

@property
def comp_control_wires(self):
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, I would suggest renaming MultiControlledX.control_wires -> MultiControlledX._control_wires.

Then, you could have this property do two things:

  • By default, Operation.control_wires could simply return self._control_wires if it exists, otherwise raising NotImplementedError

  • Inheriting devices can defined this class property directly.

@property
def comp_control_wires(self):
return Wires(self.wires[0])

Copy link
Member

Choose a reason for hiding this comment

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

Agree 🤔 What you could do is add # pragma: no cover to this property, to indicate it is intentionally not covered?

single-qubit gates ahead of controlled operations.
[(#1464)](https://github.com/PennyLaneAI/pennylane/pull/1464)

The `commute_through_controls_targets` transform works as follows:
Copy link
Member

Choose a reason for hiding this comment

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

Relatively minor, but is there more succinct name we could use for this transform? I tend to dislike the verbosity/typing of functions with more than 2 or so words!

Copy link
Member

Choose a reason for hiding this comment

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

commute_controlled?

Comment on lines 24 to 29
# Gates that commute among each particular basis
commuting_gates = {
"X": ["PauliX", "RX", "SX"],
"Y": ["PauliY", "RY"],
"Z": ["PauliZ", "RZ", "PhaseShift", "S", "T"],
}
Copy link
Member

Choose a reason for hiding this comment

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

Out of curiosity: could we not have a basis property on gates instead? e.g.:

class RX(Operation):
    basis = "X"
    ...

class CRX(Operation):
    basis = "X"
    ...

This could replace the dictionary here as well as the target_basis property?

Copy link
Member

Choose a reason for hiding this comment

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

Alternatively, you could instead define the controlled target basis as a dictionary of gates here?

Copy link
Member

Choose a reason for hiding this comment

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

Not really suggesting one over the other, I think both approaches have their place. Just gathering my thoughts: I guess I'm trying to determine why commuting_gates is transform-specific data, while target_basis is a Operation-specific data.

Is commuting_gates explicitly linked to the compilation transform?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Awesome, I like the idea of specifying a basis; then we can just check that they match, and there is no need to have any of this info here. Nothing is compilation-specific, the gates either commute or don't, it's just we aren't right now making use of that information anywhere else.

Comment on lines 39 to 40
Args:
qfunc (function): A quantum function.
Copy link
Member

Choose a reason for hiding this comment

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

I can't recall if other compilation transforms have this, but does it make sense to have

Returns:
    function: the transformed quantum function

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They don't, but I think that's a good idea. Will update!

Comment on lines 76 to 77
with ``CRY``). We can use the transform to push single-qubit gates as
far as possible through the controlled operations:
Copy link
Member

Choose a reason for hiding this comment

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

Question: could we have

@commute_through_controls_target(direction="left"|"right")

in case you want to move the single-qubit operations in a particular direction? :D

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 love this!! After I wrote the transform I was still debating whether I should not rewrite it the other way (in fact, the precursors to it worked by pushing the controlled operations to the right and thus the single-qubit gates to the left, but didn't work nearly as well).

Also, this implicitly solves the long name problem 😁

Comment on lines 89 to 91
# We will go through the list backwards; whenever we find a single-qubit
# gate, we will extract it and push it through 2-qubit gates as far as
# possible to the right.
Copy link
Member

Choose a reason for hiding this comment

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

Your comments are always super helpful to understand the logical flow!

Copy link
Contributor

Choose a reason for hiding this comment

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

💯

# Find the next gate that contains an overlapping wire
next_gate_idx = find_next_gate(current_gate.wires, list_copy[current_location + 1 :])

where_should_we_put_it = current_location
Copy link
Member

Choose a reason for hiding this comment

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

lol

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps new_location?

Comment on lines 136 to 137
except KeyError:
break
Copy link
Member

Choose a reason for hiding this comment

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

When does this happen? Shouldn't this be AttributeError? 🤔

if op.wires == qml.wires.Wires(wire_order):
mat = qml.math.dot(op.matrix, mat)
else:
mat = qml.math.dot(qml.SWAP(wires=[0, 1]).matrix, qml.math.dot(op.matrix, mat))
Copy link
Member

Choose a reason for hiding this comment

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

👍

Comment on lines 22 to 24
compare_operation_lists,
compute_matrix_from_ops_two_qubit,
check_matrix_equivalence,
Copy link
Contributor

Choose a reason for hiding this comment

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

Really useful to see these functions being used that I'd otherwise remain unaware of!

def test_commute_through_controls_targets_autograd(self):
"""Test QNode and gradient in autograd interface."""

original_qnode = qml.QNode(qfunc, dev)
Copy link
Contributor

Choose a reason for hiding this comment

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

might be worth explicitly passing interface="autograd" in case the default interface changes in future


Diagonal gates on either side of control qubits do not affect the outcome
of controlled gates; thus we can push all the single-qubit gates on the
first qubit together on the right (and fuse them if desired). Similarly, X
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there any plan to combine this and fusion in a single function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not at the moment - for now, trying to keep the transforms as modular as possible so they can be chained in different ways, but this might change in the future.

Comment on lines 89 to 91
# We will go through the list backwards; whenever we find a single-qubit
# gate, we will extract it and push it through 2-qubit gates as far as
# possible to the right.
Copy link
Contributor

Choose a reason for hiding this comment

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

💯

.github/CHANGELOG.md Outdated Show resolved Hide resolved
.github/CHANGELOG.md Outdated Show resolved Hide resolved
return op_list


def _commute_controlled_left(op_list):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since this is so close to the _right version, I have not added a full set of comments in order to keep things more streamlined. I can add them back if preferred though 🙂

@glassnotes
Copy link
Contributor Author

@josh146 @anthayes92 thank you both for the feedback, updated version is cleaner and more flexible now 😁

@glassnotes glassnotes requested a review from josh146 July 21, 2021 15:29
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.

💯 Happy to approve this!

Only blocker it looks like there are a few places where the left function is not being tested?

Comment on lines +58 to +60
# Only go ahead if information is available
if next_gate.basis is None:
break
Copy link
Member

Choose a reason for hiding this comment

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

I guess this never happens in the test suite?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Coverage has been added for this and the instances in _commute_controlled_left 👍

.github/CHANGELOG.md Outdated Show resolved Hide resolved
@glassnotes glassnotes merged commit 347d06e into master Jul 23, 2021
@glassnotes glassnotes deleted the ch7108-implement-transforms-for-re-ordering-of-commuting branch July 23, 2021 16:51
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.

All controlled operations should belong to the class Controlled
3 participants