|  | 
| 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) [(#2090)](https://github.com/PennyLaneAI/catalyst/pull/2090) | 
| 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