|
14 | 14 | [(#2002)](https://github.com/PennyLaneAI/catalyst/pull/2002) |
15 | 15 |
|
16 | 16 | Two new functions, ``qml.allocate()`` and ``qml.deallocate()``, [have been added to |
17 | | - PennyLane](https://docs.pennylane.ai/en/stable/development/release_notes.html#release-0-43-0) to support |
18 | | - dynamic wire allocation. With Catalyst, these features can be accessed on |
| 17 | + PennyLane](https://docs.pennylane.ai/en/stable/development/release_notes.html#release-0-43-0) |
| 18 | + to support dynamic wire allocation. With Catalyst, these features can be accessed on |
19 | 19 | ``lightning.qubit``, ``lightning.kokkos``, and ``lightning.gpu``. |
20 | 20 |
|
21 | 21 | Dynamic wire allocation refers to the allocation of wires in the middle of a circuit, as opposed to the static allocation during device initialization. For example: |
|
26 | 26 | @qjit |
27 | 27 | @qml.qnode(qml.device("lightning.qubit", wires=3)) # 3 initial qubits |
28 | 28 | def circuit(): |
29 | | - qml.X(1) # |010> |
| 29 | + qml.X(0) # |10> |
30 | 30 |
|
31 | | - with qml.allocate(1) as q: # |010> and |0>, 1 dynamically allocted qubit |
32 | | - qml.X(q[0]) # |010> and |1> |
33 | | - qml.CNOT(wires=[q[0], 2]) # |011> and |1> |
| 31 | + with qml.allocate(1) as q: # |10> and |0>, 1 dynamically allocated qubit |
| 32 | + qml.X(q[0]) # |10> and |1> |
| 33 | + qml.CNOT(wires=[q[0], 1]) # |11> and |1> |
34 | 34 |
|
35 | 35 | return qml.probs(wires=[0, 1, 2]) |
36 | | - |
37 | | - qml.capture.disable() |
38 | 36 | ``` |
39 | 37 |
|
40 | 38 | ```pycon |
41 | 39 | >>> print(circuit()) |
42 | | - [0. 0. 0. 1. 0. 0. 0. 0.] |
43 | | - ``` |
44 | | - |
45 | | - In the above program, 3 qubits are allocated during device initialization, and 1 |
46 | | - additional qubit is allocated inside the circuit with ``qml.allocate(1)``. This is clear |
47 | | - when we inspect the compiled MLIR: |
48 | | - |
49 | | - ``` |
50 | | - >>> print(circuit.mlir) |
51 | | - func.func public @circuit() -> tensor<8xf64> attributes {qnode} { |
52 | | - %c0_i64 = arith.constant 0 : i64 |
53 | | - quantum.device shots(%c0_i64) ["/path/to/liblightning_qubit_catalyst.so", "LightningSimulator", "{'mcmc': False, 'num_burnin': 0, 'kernel_name': None}"] |
54 | | - %0 = quantum.alloc( 3) : !quantum.reg |
55 | | - %1 = quantum.extract %0[ 1] : !quantum.reg -> !quantum.bit |
56 | | - %out_qubits = quantum.custom "PauliX"() %1 : !quantum.bit |
57 | | - %2 = quantum.alloc( 1) : !quantum.reg |
58 | | - %3 = quantum.extract %2[ 0] : !quantum.reg -> !quantum.bit |
59 | | - %out_qubits_0 = quantum.custom "PauliX"() %3 : !quantum.bit |
60 | | - %4 = quantum.extract %0[ 2] : !quantum.reg -> !quantum.bit |
61 | | - %out_qubits_1:2 = quantum.custom "CNOT"() %out_qubits_0, %4 : !quantum.bit, !quantum.bit |
62 | | - %5 = quantum.insert %2[ 0], %out_qubits_1#0 : !quantum.reg, !quantum.bit |
63 | | - quantum.dealloc %5 : !quantum.reg |
64 | | - %6 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit |
65 | | - %7 = quantum.compbasis qubits %6, %out_qubits, %out_qubits_1#1 : !quantum.obs |
66 | | - %8 = quantum.probs %7 : tensor<8xf64> |
67 | | - %9 = quantum.insert %0[ 1], %out_qubits : !quantum.reg, !quantum.bit |
68 | | - %10 = quantum.insert %9[ 2], %out_qubits_1#1 : !quantum.reg, !quantum.bit |
69 | | - %11 = quantum.insert %10[ 0], %6 : !quantum.reg, !quantum.bit |
70 | | - quantum.dealloc %11 : !quantum.reg |
71 | | - quantum.device_release |
72 | | - return %8 : tensor<8xf64> |
73 | | - } |
| 40 | + [0. 0. 0. 1.] |
74 | 41 | ``` |
75 | 42 |
|
76 | | - We can see that there are now 2 pairs of ``quantum.alloc`` and ``quantum.dealloc`` |
77 | | - operations. The quantum register value ``%0`` corresponds to the initial wires on the |
78 | | - device, and the quantum register value ``%2`` corresponds to the dynamically allocated |
79 | | - wire. |
| 43 | + In the above program, 2 qubits are allocated during device initialization, and 1 |
| 44 | + additional qubit is allocated inside the circuit with ``qml.allocate(1)``. |
80 | 45 |
|
81 | 46 | For more information on what ``qml.allocate`` and ``qml.deallocate`` do, please consult the |
82 | 47 | [PennyLane v0.43 release notes](https://docs.pennylane.ai/en/stable/development/release_notes.html#release-0-43-0). |
83 | 48 |
|
84 | 49 | However, there are some notable differences between the behaviour of these features |
85 | | - with ``qjit`` versus without. For details, please see |
86 | | - [the relevant sections on the Catalyst sharp bits page](https://docs.pennylane.ai/projects/catalyst/en/stable/dev/sharp_bits.html#functionality-differences-from-pennylane). |
| 50 | + with ``qjit`` versus without. For details, please see the relevant sections in the |
| 51 | + [Catalyst sharp bits page](https://docs.pennylane.ai/projects/catalyst/en/stable/dev/sharp_bits.html#functionality-differences-from-pennylane). |
87 | 52 |
|
88 | 53 | * A new quantum compilation pass that reduces the depth and count of non-Clifford Pauli product |
89 | 54 | rotations (PPRs) in circuits is now available. This compilation pass works by commuting |
90 | | - non-Clifford PPRs (often referred to as ``T`` gates) in adjacent |
| 55 | + non-Clifford PPRs (often just referred to as ``T`` gates) in adjacent |
91 | 56 | layers and merging compatible ones. More details can be found in Figure 6 of |
92 | 57 | [A Game of Surface Codes](https://arXiv:1808.02892v3). |
93 | 58 | [(#1975)](https://github.com/PennyLaneAI/catalyst/pull/1975) |
94 | 59 | [(#2048)](https://github.com/PennyLaneAI/catalyst/pull/2048) |
95 | 60 |
|
96 | | - Consider the following circuit. |
| 61 | + Consider the following circuit: |
97 | 62 |
|
98 | 63 | ```python |
99 | 64 | import pennylane as qml |
100 | 65 | from catalyst import qjit, measure |
101 | | - from catalyst.passes import to_ppr, commute_ppr, t_layer_reduction, merge_ppr_ppm |
| 66 | + from catalyst.passes import to_ppr, commute_ppr, t_layer_reduction, merge_ppr_ppm, ppm_specs |
102 | 67 |
|
103 | 68 | pips = [("pipe", ["enforce-runtime-invariants-pipeline"])] |
104 | 69 |
|
| 70 | + no_reduce_T = { |
| 71 | + "to_ppr": {}, |
| 72 | + "commute_ppr": {}, |
| 73 | + "merge_ppr_ppm": {}, |
| 74 | + } |
105 | 75 |
|
106 | | - @qjit(pipelines=pips, target="mlir") |
107 | | - @t_layer_reduction |
108 | | - @merge_ppr_ppm |
109 | | - @commute_ppr |
110 | | - @to_ppr |
111 | | - @qml.qnode(qml.device("null.qubit", wires=3)) |
112 | | - def circuit(): |
113 | | - for i in range(3): |
114 | | - qml.H(wires=i) |
115 | | - qml.S(wires=i) |
116 | | - qml.CNOT(wires=[i, (i + 1) % n]) |
117 | | - qml.T(wires=i) |
118 | | - qml.H(wires=i) |
119 | | - qml.T(wires=i) |
120 | | - |
121 | | - return [measure(wires=i) for i in range(n)] |
122 | | - ``` |
| 76 | + reduce_T = { |
| 77 | + "to_ppr": {}, |
| 78 | + "commute_ppr": {}, |
| 79 | + "merge_ppr_ppm": {}, |
| 80 | + "t_layer_reduction": {} |
| 81 | + } |
123 | 82 |
|
124 | | - After performing the ``catalyst.passes.to_ppr`` and ``catalyst.passes.merge_ppr_ppm`` |
125 | | - passes, the circuit contains a depth of four of non-Clifford PPRs. Subsequently applying the |
126 | | - ``t_layer_reduction`` pass will move PPRs around via commutation, resulting in a circuit with a |
127 | | - smaller PPR depth of three. |
| 83 | + for pipeline in [reduce_T, no_reduce_T]: |
| 84 | + |
| 85 | + @qjit(pipelines=pips, target="mlir", circuit_transform_pipeline=pipeline) |
| 86 | + @qml.qnode(qml.device("null.qubit", wires=3)) |
| 87 | + def circuit(): |
| 88 | + n = 3 |
| 89 | + for i in range(n): |
| 90 | + qml.H(wires=i) |
| 91 | + qml.S(wires=i) |
| 92 | + qml.CNOT(wires=[i, (i + 1) % n]) |
| 93 | + qml.T(wires=i) |
| 94 | + qml.H(wires=i) |
| 95 | + qml.T(wires=i) |
| 96 | + |
| 97 | + return [measure(wires=i) for i in range(n)] |
| 98 | + |
| 99 | + print(ppm_specs(circuit)) |
| 100 | + ``` |
128 | 101 |
|
129 | 102 | ```pycon |
130 | | - >>> print(circuit.mlir_opt) |
131 | | - ... |
132 | | - %1 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit |
133 | | - %2 = quantum.extract %0[ 1] : !quantum.reg -> !quantum.bit |
134 | | - // layer 1 |
135 | | - %3 = qec.ppr ["X"](8) %1 : !quantum.bit |
136 | | - %4 = qec.ppr ["X"](8) %2 : !quantum.bit |
137 | | - |
138 | | - // layer 2 |
139 | | - %5 = quantum.extract %0[ 2] : !quantum.reg -> !quantum.bit |
140 | | - %6:2 = qec.ppr ["Y", "X"](8) %3, %4 : !quantum.bit, !quantum.bit |
141 | | - %7 = qec.ppr ["X"](8) %5 : !quantum.bit |
142 | | - %8:3 = qec.ppr ["X", "Y", "X"](8) %6#0, %6#1, %7:!quantum.bit, !quantum.bit, !quantum.bit |
143 | | - |
144 | | - // layer 3 |
145 | | - %9:3 = qec.ppr ["X", "X", "Y"](8) %8#0, %8#1, %8#2:!quantum.bit, !quantum.bit, !quantum.bit |
146 | | - ... |
| 103 | + {'circuit_0': {'depth_pi8_ppr': 3, 'depth_ppm': 1, 'logical_qubits': 3, 'max_weight_pi8': 3, 'num_of_ppm': 3, 'pi8_ppr': 6}} |
| 104 | + {'circuit_0': {'depth_pi8_ppr': 4, 'depth_ppm': 1, 'logical_qubits': 3, 'max_weight_pi8': 3, 'num_of_ppm': 3, 'pi8_ppr': 6}} |
147 | 105 | ``` |
148 | 106 |
|
| 107 | + After performing the :func:`~.passes.to_ppr`, :func:`~.passes.commute_ppr`, and :func:`~.passes.merge_ppr_ppm`, |
| 108 | + passes, the circuit contains a depth of four of non-Clifford PPRs (`depth_pi8_ppr`). Subsequently applying the |
| 109 | + :func:`~.passes.t_layer_reduction` pass will move PPRs around via commutation, resulting in a circuit with a |
| 110 | + smaller PPR depth of three. |
| 111 | + |
149 | 112 | * Catalyst now provides native support for `SingleExcitation`, `DoubleExcitation`, |
150 | 113 | and `PCPhase` on compatible devices like Lightning simulators. |
151 | 114 | This enhancement avoids unnecessary gate decomposition, |
|
165 | 128 | return qml.probs() |
166 | 129 | ``` |
167 | 130 |
|
168 | | -<h3>Improvements 🛠</h3> |
169 | | - |
170 | | -* Significantly improved resource tracking with `null.qubit`. |
171 | | - The new tracking has better integration with PennyLane (e.g. for passing the filename to write out), cleaner documentation, and its own wrapper class. |
172 | | - It also now tracks circuit depth, as well as gate counts by number of wires. |
173 | | - [(#2033)](https://github.com/PennyLaneAI/catalyst/pull/2033) |
174 | | - [(#2055)](https://github.com/PennyLaneAI/catalyst/pull/2055) |
175 | | - |
176 | 131 | * Catalyst now supports returning classical and MCM values with the dynamic one-shot MCM method. |
177 | 132 | [(#2004)](https://github.com/PennyLaneAI/catalyst/pull/2004) |
178 | 133 |
|
|
202 | 157 | True], dtype=bool)) |
203 | 158 | ``` |
204 | 159 |
|
205 | | -* Improve the pass `--ppm-specs` to count the depth of PPRs and PPMs in the circuit. |
206 | | - [(#2014)](https://github.com/PennyLaneAI/catalyst/pull/2014) |
207 | | - |
208 | 160 | * The default mid-circuit measurement method in catalyst has been changed from `"single-branch-statistics"` to `"one-shot"`. |
209 | 161 | [[#2017]](https://github.com/PennyLaneAI/catalyst/pull/2017) |
210 | 162 | [[#2019]](https://github.com/PennyLaneAI/catalyst/pull/2019) |
211 | 163 |
|
| 164 | +<h3>Improvements 🛠</h3> |
| 165 | + |
| 166 | +* Significantly improved resource tracking with `null.qubit`. |
| 167 | + The new tracking has better integration with PennyLane (e.g. for passing the filename to write out), cleaner documentation, and its own wrapper class. |
| 168 | + It also now tracks circuit depth, as well as gate counts by number of wires. |
| 169 | + [(#2033)](https://github.com/PennyLaneAI/catalyst/pull/2033) |
| 170 | + [(#2055)](https://github.com/PennyLaneAI/catalyst/pull/2055) |
| 171 | + |
| 172 | +* Improve the pass `--ppm-specs` to count the depth of PPRs and PPMs in the circuit. |
| 173 | + [(#2014)](https://github.com/PennyLaneAI/catalyst/pull/2014) |
| 174 | + |
212 | 175 | * A new pass `--partition-layers` has been added to group PPR/PPM operations into `qec.layer` |
213 | 176 | operations based on qubit interactive and commutativity, enabling circuit analysis and |
214 | 177 | potentially to support parallel execution. |
|
324 | 287 |
|
325 | 288 | <h3>Deprecations 👋</h3> |
326 | 289 |
|
327 | | -* Deprecated usages of `Device.shots` along with setting `device(..., shots=...)`. |
| 290 | +* Deprecated usages of ``Device.shots`` along with setting ``device(..., shots=...)``. |
328 | 291 | Heavily adjusted frontend pipelines within qfunc, tracer, verification and QJITDevice to account for this change. |
| 292 | + Please use ``qml.set_shots(shots=...)`` or set shots at the QNode level (i.e., ``qml.QNode(..., shots=...)``). |
329 | 293 | [(#1952)](https://github.com/PennyLaneAI/catalyst/pull/1952) |
330 | 294 |
|
331 | 295 | <h3>Bug fixes 🐛</h3> |
@@ -485,7 +449,7 @@ for example the one-shot mid circuit measurement transform. |
485 | 449 | [(#2057)](https://github.com/PennyLaneAI/catalyst/pull/2057) |
486 | 450 |
|
487 | 451 | This pass is part of a bottom-of-stack MBQC execution pathway, with a thin shim between the |
488 | | - PPR/PPM layer and MBQC to enable end-to-end compilation on a mocked backend. Also, in MBQC gate |
| 452 | + PPR/PPM layer and MBQC to enable end-to-end compilation on a mocked backend. Also, in an MBQC gate |
489 | 453 | set, one of the gate `RotXZX` cannot yet be executed on available backends. |
490 | 454 |
|
491 | 455 | ```python |
|
0 commit comments