-
Notifications
You must be signed in to change notification settings - Fork 657
Support for weighted gates in graph decomposition algorithm #7389
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
base: symbolic-rules-01
Are you sure you want to change the base?
Support for weighted gates in graph decomposition algorithm #7389
Conversation
- TODO: tests, debug
- Switch to use TypeError instead of custom error - always guarantee we have weights - stringify keys of weights dict - correct logic for edge weight calculation - closing this PR and switching to new one (#7389) which is off of symbolic-rules-01
Co-authored-by: Astral Cai <[email protected]>
Co-authored-by: Astral Cai <[email protected]>
Note that I did not introduce this attribute. :) Co-authored-by: Astral Cai <[email protected]>
Single line params instead of 1 param per line here. Co-authored-by: Astral Cai <[email protected]>
…c-rls' into feature/wghtd-gts-based-on-symblc-rls
- reduce number of attrs - reorder gate_set param - rename _gate_set attr
Co-authored-by: Astral Cai <[email protected]>
- refactor tests - update docstring
…c-rls' into feature/wghtd-gts-based-on-symblc-rls
- remove test of str weights - revert (now) redundant weights provided to Resources constructors in tests
…c-rls' into feature/wghtd-gts-based-on-symblc-rls
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@comp-phys-marc nice work 🎉! Just made some suggestions to redirect documentation more towards what the user will want to use.
pennylane/transforms/decompose.py
Outdated
raise ValueError("Gate weights provided in a `dict` type `gate_set` parameter to `decompose`" | ||
"must not be negative, as negative gate weights are not supported.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
raise ValueError("Gate weights provided in a `dict` type `gate_set` parameter to `decompose`" | |
"must not be negative, as negative gate weights are not supported.") | |
raise ValueError("Negative gate weights provided to gate_set in decompose" | |
"are not supported.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
match="Gate weights provided in a `dict` type `gate_set` parameter to `decompose`" | ||
"must not be negative, as negative gate weights are not supported."): | ||
qml.transforms.decompose(tape, gate_set={"CNOT": -10.0, "RZ": 1.0}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made a suggestion to the error message above, so just a note to change the tests too!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Roger!
@@ -368,6 +412,7 @@ This release contains contributions from (in alphabetical order): | |||
Guillermo Alonso-Linaje, | |||
Astral Cai, | |||
Yushao Chen, | |||
Marcus Edwards, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🍾 🥳
doc/releases/changelog-dev.md
Outdated
```python | ||
op = qml.CRX(2.5, wires=[0, 1]) | ||
|
||
# i.e. a RZ CZ RX CZ decomp (that does not involve RZ, CNOT) is chosen when the RZ and CNOT weights are large. | ||
gate_weights = {"RX": 1.0, "RY": 3.0, "RZ": 10.0, "GlobalPhase": 1.0, "CNOT": 20.0, "CZ": 1.0} | ||
|
||
graph = DecompositionGraph( | ||
operations=[op], | ||
gate_set=gate_weights, | ||
) | ||
graph.solve() | ||
``` | ||
|
||
```pycon | ||
>>> print(graph.resource_estimate(op).__repr__()) | ||
'<num_gates=4, gate_counts={RX: 2, CZ: 2}, weighted_cost=4.0>' | ||
``` | ||
|
||
```python | ||
op = qml.CRX(2.5, wires=[0, 1]) | ||
|
||
# alternatively, the RZ CZ RX CZ decomp is *avoided* when the CZ weight is large. | ||
gate_weights = {"RX": 1.0, "RY": 1.0, "RZ": 1.0, "GlobalPhase": 1.0, "CNOT": 1.0, "CZ": 100.0} | ||
|
||
graph = DecompositionGraph( | ||
operations=[op], | ||
gate_set=gate_weights, | ||
) | ||
graph.solve() | ||
``` | ||
|
||
```pycon | ||
>>> print(graph.resource_estimate(op).__repr__()) | ||
'<num_gates=16, gate_counts={RZ: 4, RY: 4, RX: 2, GlobalPhase: 4, CNOT: 2}, weighted_cost=16.0>' | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a nice example for power users and developers, but the majority users won't interact with the DecompositionGraph
object 🙂. With code examples in the changelog, they should always be showing something that the user will want to do (or what we want to direct the user to do).
Here, we should have an example like this:
gate_set = {"RX": 1.0, "RY": 3.0, "RZ": 10.0, "GlobalPhase": 1.0, "CNOT": 20.0, "CZ": 1.0}
@partial(decompose, gate_set=gate_set)
@qnode(...)
def circuit(...):
...
print(qml.draw(circuit, level="device"))
Really simple toy example that reflects a real-world scenario 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great feedback! I will create some examples in this style. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've addressed this in my most recent commit, let me know if you would like any further changes like more or different examples.
Co-authored-by: Isaac De Vlugt <[email protected]>
- use examples in docstring and changelog that are end user focussed (example from https://app.shortcut.com/xanaduai/epic/86583 used) - adopt change to wording of error message
…c-rls' into feature/wghtd-gts-based-on-symblc-rls
Co-authored-by: Isaac De Vlugt <[email protected]>
Co-authored-by: Isaac De Vlugt <[email protected]>
Co-authored-by: Isaac De Vlugt <[email protected]>
Co-authored-by: Isaac De Vlugt <[email protected]>
…c-rls' into feature/wghtd-gts-based-on-symblc-rls
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yay! Nice! 🚀
Co-authored-by: Isaac De Vlugt <[email protected]>
Co-authored-by: Isaac De Vlugt <[email protected]>
Co-authored-by: Isaac De Vlugt <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work! Congratulations on your first contribution! 🚀 Let's wait for a second technical review from @PietropaoloFrisoni but I'm happy to approve.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Congratulations @comp-phys-marc , this is really an impressive work 🎉
I only have a couple of minor comments/questions, but pretty much ready to approve : )
qml.Toffoli(wires=[0,1,2]) | ||
return qml.expval(qml.Z(0)) | ||
|
||
>>> print(qml.draw(circuit)()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I run this example, I get a different output (the Hadamard gate is not decomposed):
import pennylane as qml
from functools import partial
qml.decomposition.enable_graph()
@partial(
qml.transforms.decompose, gate_set={qml.Toffoli: 1.23, qml.RX: 4.56, qml.RZ: 0.01, qml.H: 420}
)
@qml.qnode(qml.device("default.qubit"))
def circuit():
qml.H(wires=[0])
qml.Toffoli(wires=[0, 1, 2])
return qml.expval(qml.Z(0))
>>> print(qml.draw(circuit)())
0: ──H─╭●─┤ <Z>
1: ────├●─┤
2: ────╰X─┤
Am I missing something? Do I need to test this using a different branch?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A better example might be the following. It shows how we avoid decompositions involving H
when the cost of H
is high just as well, and it works properly. Let's please discuss though whether the functionality around decomposing (or the lack of decomposing) gates in the target gateset is as desired.
@partial(
qml.transforms.decompose, gate_set={qml.Toffoli: 1.23, qml.RX: 4.56, qml.CZ: 0.01, qml.H: 420, qml.CRZ: 100}
)
@qml.qnode(qml.device("default.qubit"))
def circuit():
qml.CRX(0.1, wires=[0, 1])
qml.Toffoli(wires=[0, 1, 2])
return qml.expval(qml.Z(0))
>>> print(qml.draw(circuit)())
0: ───────────╭●────────────╭●─╭●─┤ <Z>
1: ──RX(0.05)─╰Z──RX(-0.05)─╰Z─├●─┤
2: ────────────────────────────╰X─┤
@partial(
qml.transforms.decompose, gate_set={qml.Toffoli: 1.23, qml.RX: 4.56, qml.CZ: 0.01, qml.H: 0.1, qml.CRZ: 0.1}
)
@qml.qnode(qml.device("default.qubit"))
def circuit():
qml.CRX(0.1, wires=[0, 1])
qml.Toffoli(wires=[0, 1, 2])
return qml.expval(qml.Z(0))
>>> print(qml.draw(circuit)())
0: ────╭●───────────╭●─┤ <Z>
1: ──H─╰RZ(0.10)──H─├●─┤
2: ─────────────────╰X─┤
Co-authored-by: Pietropaolo Frisoni <[email protected]>
Co-authored-by: Pietropaolo Frisoni <[email protected]>
- pending disussion of desired behaviour: should gates in the target gateset get decomposed?
…c-rls' into feature/wghtd-gts-based-on-symblc-rls
Context: In short, a decomposition system based on a graph traversal using Dijkstra's algorithm is applied to find the most optimal gate decompositions for non-native gates. The nodes in the graph represent either decompositions, operations in the target gateset, or higher order operations. The edges show either membership of a native op in a decomposition, or equivalence of a decomposition to a higher order operation. The cost of an edge between a native operation and a decomposition is the number of additional gates found in the decomposition. The cost of an edge between a decomposition and an equivalent higher order op is zero. For more context, see [sc-84664].
Description of the Change: Work in progress on implementing support for relatively weighted gates within a target gateset. The weights are provided by the user, and influence what gate decompositions are chosen by the graph decomposition algorithm. The implementation is such that the weights of the gates are represented by weights on the edges leading to and from native gates.
Benefits: Allows a user to more closely model the behaviour of their backends, and capture the relative cost of executing certain gates such as non-Clifford gates which are native, but more expensive.
Possible Drawbacks: The representation of weights in this way is possibly not intuitive.
Related GitHub Issues: