Skip to content

Commit 5a0a622

Browse files
andrijapaumudit2812
authored andcommitted
Adding a user-friendly qml.transform.decompose function (#6334)
**Context:** Users often want to inspect and transform quantum circuits at different stages of decomposition. This feature will allow them to gain a better understanding for the decomposition and to think about algorithms at different layers of abstraction. Prior to this feature, users only had access to `qml.devices.preprocess.decompose`- a function focused on device preprocessing. Over time, it became apparent that this function is developer focused, making use of hard-coded error messages / exceptions as well as non-intuitive stopping conditions. This led to some frustrations where users were trying to explore abstract decompositions (such as counting the number of CNOTs in a multi-controlled gate). For now, this function will serve to call the `qml.devices.preprocess.decompose` function and prune any developer-focused features. This feature should accomplish the following, 1. Decomposition of circuits into a desired gate set. 2. Decomposition of circuits into gate sets defined by a `Callable[Operator, bool]` (e.g. `lambda op: len(op.wires) <= 2`). 3. Decomposition of circuits in stages to understand the process. This can be done by adjusting the `max_expansion` kwarg. **Description of the Change:** The goal of this feature is to create a user-facing function that enables intuitive decomposition in a less restrictive manner. Examples: ```python >>> @partial(decompose, gate_set={qml.Toffoli, "RX", "RZ"}) >>> @qml.qnode(dev) >>> def circuit(): >>> qml.Hadamard(wires=[0]) >>> qml.Toffoli(wires=[0,1,2]) >>> return qml.expval(qml.Z(0)) >>> >>> print(qml.draw(circuit)()) 0: ──RZ(1.57)──RX(1.57)──RZ(1.57)─╭●─┤ <Z> 1: ───────────────────────────────├●─┤ 2: ───────────────────────────────╰X─┤ >>> @partial(decompose, gate_set=lambda op: len(op.wires) <= 2) >>> @qml.qnode(dev) >>> def circuit(): >>> qml.Hadamard(wires=[0]) >>> qml.Toffoli(wires=[0,1,2]) >>> return qml.expval(qml.Z(0)) >>> >>> print(qml.draw(circuit)()) 0: ──H────────╭●───────────╭●────╭●──T──╭●─┤ <Z> 1: ────╭●─────│─────╭●─────│───T─╰X──T†─╰X─┤ 2: ──H─╰X──T†─╰X──T─╰X──T†─╰X──T──H────────┤ >>> tape = qml.tape.QuantumScript([qml.IsingXX(1.2, wires=(0,1))], [qml.expval(qml.Z(0))]) >>> batch, fn = qml.transforms.decompose(tape, gate_set={"CNOT", "RX", "RZ"}) >>> batch[0].circuit [CNOT(wires=[0, 1]), RX(1.2, wires=[0]), CNOT(wires=[0, 1]), expval(Z(0))] ``` **Benefits:** Users can explore quantum circuit decomposition in a more abstract sense without having to rely on specific device architectures. **Possible Drawbacks:** None that I am aware of (yet 😄). **Related Shortcut Story:** [sc-51282]
1 parent c293405 commit 5a0a622

File tree

5 files changed

+596
-3
lines changed

5 files changed

+596
-3
lines changed

‎doc/introduction/compiling_circuits.rst‎

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ quantum functions of optimized circuits:
121121
~pennylane.transforms.remove_barrier
122122
~pennylane.transforms.single_qubit_fusion
123123
~pennylane.transforms.undo_swaps
124+
~pennylane.transforms.decompose
124125

125126
:html:`</div>`
126127

@@ -173,6 +174,8 @@ controlled gates and cancel adjacent inverses, we could do:
173174
.. code-block:: python
174175
175176
from pennylane.transforms import commute_controlled, cancel_inverses
177+
from functools import partial
178+
176179
pipeline = [commute_controlled, cancel_inverses]
177180
178181
@partial(qml.compile, pipeline=pipeline)
@@ -208,8 +211,8 @@ For more details on :func:`~.pennylane.compile` and the available compilation tr
208211
`the compilation documentation
209212
<../code/qml_transforms.html#transforms-for-circuit-compilation>`_.
210213

211-
Custom decompositions
212-
---------------------
214+
Custom Operator Decomposition
215+
-----------------------------
213216

214217
PennyLane decomposes gates unknown to the device into other, "lower-level" gates. As a user, you may want to fine-tune this mechanism. For example, you may wish your circuit to use different fundamental gates.
215218

@@ -262,6 +265,115 @@ according to our specifications:
262265
If the custom decomposition is only supposed to be used in a specific code context,
263266
a separate context manager :func:`~.pennylane.transforms.set_decomposition` can be used.
264267

268+
Circuit Decomposition
269+
---------------------
270+
271+
When compiling a circuit it is often beneficial to decompose the circuit into a set of basis gates.
272+
To do this, we can use the :func:`~.pennylane.transforms.decompose` function, which allows the decomposition of
273+
circuits into a set of gates defined either by their name, type, or by a set of rules they must follow.
274+
275+
Using a gate set
276+
****************
277+
278+
The example below demonstrates how a three-wire circuit can be decomposed using a pre-defined set of gates:
279+
280+
.. code-block:: python
281+
282+
from pennylane.transforms import decompose
283+
from functools import partial
284+
285+
dev = qml.device('default.qubit')
286+
allowed_gates = {qml.Toffoli, qml.RX, qml.RZ}
287+
288+
@partial(decompose, gate_set=allowed_gates)
289+
@qml.qnode(dev)
290+
def circuit():
291+
qml.Hadamard(wires=[0])
292+
qml.Toffoli(wires=[0,1,2])
293+
return qml.expval(qml.Z(0))
294+
295+
With the Hadamard gate not in our gate set, it will be decomposed into the respective rotation gate operators.
296+
297+
>>> print(qml.draw(circuit)())
298+
0: ──RZ(1.57)──RX(1.57)──RZ(1.57)─╭●─┤ <Z>
299+
1: ───────────────────────────────├●─┤
300+
2: ───────────────────────────────╰X─┤
301+
302+
Using a gate rule
303+
*****************
304+
305+
The example below demonstrates how a three-wire circuit can be decomposed using a rule that decomposes the circuit down into single or two-qubit gates:
306+
307+
.. code-block:: python
308+
309+
@partial(decompose, gate_set=lambda op: len(op.wires)<=2)
310+
@qml.qnode(dev)
311+
312+
def circuit():
313+
qml.Toffoli(wires=[0,1,2])
314+
return qml.expval(qml.Z(0))
315+
316+
>>> print(qml.draw(circuit)())
317+
0: ───────────╭●───────────╭●────╭●──T──╭●─┤ <Z>
318+
1: ────╭●─────│─────╭●─────│───T─╰X──T†─╰X─┤
319+
2: ──H─╰X──T†─╰X──T─╰X──T†─╰X──T──H────────┤
320+
321+
Decomposition in stages
322+
***********************
323+
324+
You can use the ``max_expansion`` kwarg to have control over the number
325+
of decomposition stages applied to the circuit. By default, the function will decompose
326+
the circuit until the desired gate set is reached.
327+
328+
The example below shows how the user can visualize the decomposition.
329+
We begin with creating a :class:`~.pennylane.QuantumPhaseEstimation` circuit:
330+
331+
.. code-block:: python
332+
333+
phase = 1
334+
target_wires = [0]
335+
unitary = qml.RX(phase, wires=0).matrix()
336+
n_estimation_wires = 3
337+
estimation_wires = range(1, n_estimation_wires + 1)
338+
339+
@qml.qnode(qml.device('default.qubit'))
340+
def circuit():
341+
# Start in the |+> eigenstate of the unitary
342+
qml.Hadamard(wires=target_wires)
343+
qml.QuantumPhaseEstimation(
344+
unitary,
345+
target_wires=target_wires,
346+
estimation_wires=estimation_wires,
347+
)
348+
349+
From here, we can iterate through the stages of decomposition:
350+
351+
>>> print(qml.draw(decompose(circuit, max_expansion=0))())
352+
0: ──H─╭QuantumPhaseEstimation─┤
353+
1: ────├QuantumPhaseEstimation─┤
354+
2: ────├QuantumPhaseEstimation─┤
355+
3: ────╰QuantumPhaseEstimation─┤
356+
357+
>>> print(qml.draw(decompose(circuit, max_expansion=1))())
358+
0: ──H─╭U(M0)⁴─╭U(M0)²─╭U(M0)¹───────┤
359+
1: ──H─╰●──────│───────│───────╭QFT†─┤
360+
2: ──H─────────╰●──────│───────├QFT†─┤
361+
3: ──H─────────────────╰●──────╰QFT†─┤
362+
363+
>>> print(qml.draw(decompose(circuit, max_expansion=2))())
364+
0: ──H──RZ(11.00)──RY(1.14)─╭X──RY(-1.14)──RZ(-9.42)─╭X──RZ(-1.57)──RZ(1.57)──RY(1.00)─╭X──RY(-1.00)
365+
1: ──H──────────────────────╰●───────────────────────╰●────────────────────────────────│────────────
366+
2: ──H─────────────────────────────────────────────────────────────────────────────────╰●───────────
367+
3: ──H──────────────────────────────────────────────────────────────────────────────────────────────
368+
───RZ(-6.28)─╭X──RZ(4.71)──RZ(1.57)──RY(0.50)─╭X──RY(-0.50)──RZ(-6.28)─╭X──RZ(4.71)─────────────────
369+
─────────────│────────────────────────────────│────────────────────────│──╭SWAP†────────────────────
370+
─────────────╰●───────────────────────────────│────────────────────────│──│─────────────╭(Rϕ(1.57))†
371+
──────────────────────────────────────────────╰●───────────────────────╰●─╰SWAP†─────H†─╰●──────────
372+
────────────────────────────────────┤
373+
──────╭(Rϕ(0.79))†─╭(Rϕ(1.57))†──H†─┤
374+
───H†─│────────────╰●───────────────┤
375+
──────╰●────────────────────────────┤
376+
265377
Circuit cutting
266378
---------------
267379

‎doc/releases/changelog-dev.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
<h3>New features since last release</h3>
66

7+
* `qml.transforms.decompose` is added for stepping through decompositions to a target gate set.
8+
[(#6334)](https://github.com/PennyLaneAI/pennylane/pull/6334)
9+
710
* Added `process_density_matrix` implementations to 5 `StateMeasurement` subclasses:
811
`ExpVal`, `Var`, `Purity`, `MutualInformation`, and `VnEntropy`.
912
This enables `process_density_matrix` to be an abstract method in `StateMeasurement`,
@@ -329,4 +332,4 @@ Erick Ochoa Lopez,
329332
Lee J. O'Riordan,
330333
Mudit Pandey,
331334
Andrija Paurevic,
332-
David Wierichs,
335+
David Wierichs,

‎pennylane/transforms/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
~transforms.undo_swaps
5252
~transforms.pattern_matching_optimization
5353
~transforms.transpile
54+
~transforms.decompose
5455
5556
There are also utility functions and decompositions available that assist with
5657
both transforms, and decompositions within the larger PennyLane codebase.
@@ -361,3 +362,4 @@ def circuit(params):
361362
from .transpile import transpile
362363
from .zx import to_zx, from_zx
363364
from .broadcast_expand import broadcast_expand
365+
from .decompose import decompose

0 commit comments

Comments
 (0)