Skip to content

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

Open
wants to merge 74 commits into
base: symbolic-rules-01
Choose a base branch
from

Conversation

comp-phys-marc
Copy link


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:

ringo-but-quantum and others added 9 commits May 6, 2025 09:51
	- 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
	- 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
Copy link
Contributor

@isaacdevlugt isaacdevlugt left a 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.

Comment on lines 63 to 64
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.")
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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.")

Copy link
Author

Choose a reason for hiding this comment

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

Done!

Comment on lines 33 to 35
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})
Copy link
Contributor

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!

Copy link
Author

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,
Copy link
Contributor

Choose a reason for hiding this comment

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

🍾 🥳

Comment on lines 54 to 88
```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>'
```
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 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 😄

Copy link
Author

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!

Copy link
Author

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.

comp-phys-marc and others added 3 commits May 8, 2025 13:19
	- 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
Copy link
Contributor

@isaacdevlugt isaacdevlugt left a comment

Choose a reason for hiding this comment

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

Yay! Nice! 🚀

Copy link
Contributor

@astralcai astralcai left a 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.

Copy link
Member

@PietropaoloFrisoni PietropaoloFrisoni left a 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)())
Copy link
Member

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?

Copy link
Author

Choose a reason for hiding this comment

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

Aha I found what is happening here. The way the stopping condition is implemented, it just checks if the gate is in the target gateset, and if it is then it stops. It never gets to _construct_and_solve_decomp_graph in this case, so no graph based decomposition occurs.

Screenshot 2025-05-08 at 6 44 59 PM

Copy link
Author

@comp-phys-marc comp-phys-marc May 8, 2025

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─┤   

comp-phys-marc and others added 4 commits May 8, 2025 18:53
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
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.

6 participants