The latest release of PennyLane is now out and available for everyone to use. It comes with many new additions, including executing large circuits with fewer qubits, differentiable mid-circuit measurements, a new high-performance GPU simulator, tools for quantum debugging, better batching support, and more.

Check out the table of contents below, or keep reading to find out more.

- Cut your circuits into fragments for execution with fewer qubits β
- Quantum teleport with mid-circuit measurements π
- Accelerate your simulations with cuQuantum GPU support β‘
- Debug with mid-circuit quantum snapshots π·
- Better batching π¦
- Even more mighty quantum transforms πβ‘π¦
- Improvements
- Deprecations and breaking changes
- Contributors

## Cut your circuits into fragments for execution with fewer qubits β

When building new quantum algorithms, we often want to push the limits of what is possible, and test or deploy our algorithms on more and more wires. Unfortunately, all too often we end up constrained in our quest for more qubits by the underlying hardware or simulator device we are using.

With PennyLane v0.22, you can now execute a quantum algorithm that requires `N`

wires on **fewer**
than `N`

wires, by taking advantage of *circuit cutting*.

Simply ‘cut’ wires within your QNode, and PennyLane will partition your algorithm into smaller fragments for execution, before combining and post-processing the results.

Circuit cutting is enabled by decorating a QNode with the
`@qml.cut_circuit`

transform. For example, to execute a three-wire circuit on a two-wire device:

```
dev = qml.device("default.qubit", wires=2)
@qml.cut_circuit
@qml.qnode(dev)
def circuit(x):
qml.RX(x, wires=0)
qml.RY(0.9, wires=1)
qml.RX(0.3, wires=2)
qml.CZ(wires=[0, 1])
qml.RY(-0.4, wires=0)
# cut the circuit into two fragments at wire 1
qml.WireCut(wires=1)
qml.CZ(wires=[1, 2])
return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2))
```

Instead of executing the circuit directly, it will be partitioned into smaller fragments according
to the `qml.WireCut`

locations, and each fragment executed multiple times. Combining the results of
the fragment executions will recover the expected output of the original uncut circuit:

```
>>> x = np.array(0.531, requires_grad=True)
>>> circuit(0.531)
0.47165198882111165
```

As always, circuit cutting support is also differentiable:

```
>>> qml.grad(circuit)(x)
-0.276982865449393
```

Note that while circuit cutting allows for executing a circuit on a device with fewer qubits,
cutting a circuit can be expensive in terms of the number of executions required as well as
additional classical postprocessing. For more details on circuit cutting, check out the
`qml.cut_circuit`

documentation page.

## Quantum teleport with mid-circuit measurements π

One of the most highly requested features over the years has been the ability to perform quantum teleportation with PennyLane, and this is now unlocked π in version 0.22, with the introduction of mid-circuit measurements and conditional operations.

### Conditional operations

`qml.measure()`

allows circuit measurements to be placed*in the middle*of a quantum function.

- The new
`qml.cond()`

transform allows operations to be conditioned on the result of a previous measurement.

Use mid-circuit measurements and conditional operations to build and run algorithms such as quantum teleportation, quantum error correction, and quantum error mitigation.

For example, the code below shows how to teleport a qubit from wire 0 to wire 2:

```
dev = qml.device("default.qubit", wires=3)
input_state = np.array([1, -1], requires_grad=False) / np.sqrt(2)
@qml.qnode(dev)
def teleport(state):
# Prepare input state
qml.QubitStateVector(state, wires=0)
# Prepare Bell state
qml.Hadamard(wires=1)
qml.CNOT(wires=[1, 2])
# Apply gates
qml.CNOT(wires=[0, 1])
qml.Hadamard(wires=0)
# Measure first two wires
m1 = qml.measure(0)
m2 = qml.measure(1)
# Condition final wire on results
qml.cond(m2 == 1, qml.PauliX)(wires=2)
qml.cond(m1 == 1, qml.PauliZ)(wires=2)
# Return state on final wire
return qml.density_matrix(wires=2)
```

```
>>> output_state = teleport(input_state)
>>> output_state
tensor([[ 0.5+0.j, -0.5+0.j],
[-0.5+0.j, 0.5+0.j]], requires_grad=True)
```

We can double-check that the qubit has been teleported by computing the overlap between the input state and the resulting state on wire 2:

```
>>> input_state.conj() @ output_state @ input_state
tensor(1.+0.j, requires_grad=True)
```

### Train mid-circuit measurements by deferring them

If a device doesn’t natively support mid-circuit measurements, the
`@qml.defer_measurements`

transform can be applied to the QNode to transform the QNode into one with *terminal* measurements
and *controlled* operations:

```
@qml.qnode(dev)
@qml.defer_measurements
def circuit(x):
qml.Hadamard(wires=0)
m = qml.measure(0)
qml.cond(
m == 1, # measurement condition
lambda: qml.RX(x**2, wires=1), # qfunc to apply if condition is True (m==1)
lambda: qml.RY(x, wires=1) # qfunc to apply if condition is False (m!=1)
)()
return qml.expval(qml.PauliZ(1))
```

```
>>> x = np.array(0.7, requires_grad=True)
>>> print(qml.draw(circuit, expansion_strategy="device")(x))
0: ββHββCβββββββββXββCβββββββββXββ€
1: βββββ°RX(0.49)βββββ°RY(0.70)βββββ€ <Z>
>>> circuit(x)
tensor(0.82358752, requires_grad=True)
```

Deferring mid-circuit measurements also enables differentiation: don’t just evaluate your mid-circuit algorithms, but train π them as well!

```
>>> qml.grad(circuit)(x)
-0.651546965338656
```

For a full description of new capabilities, refer to the Mid-circuit measurements and conditional operations section in the documentation.

## Accelerate your simulations with cuQuantum GPU support β‘

We are excited to announce the release of GPU support for our high-performance `lightning.qubit`

simulator: `lightning.gpu`

. This new device utilizes the NVIDIA cuQuantum
library under-the-hood, and includes efficient
computation of quantum gradients via adjoint differentiation.

Use `lightning.gpu`

for a significant speed-up for large quantum circuit evaluations, even when
using multiple CPU-threads:

The `lightning.gpu`

device can be installed via pip:

```
pip install pennylane-lightning[gpu]
```

Once installed, it can be loaded and used with any PennyLane QNodes:

```
dev = qml.device("lightning.gpu", wires=22)
@qml.qnode(dev, diff_method="adjoint")
def circuit(weights):
qml.StronglyEntanglingLayers(weights, wires=list(range(n_wires)))
return [qml.expval(qml.PauliZ(i)) for i in range(n_wires)]
param_shape = qml.StronglyEntanglingLayers.shape(n_layers=2, n_wires=22)
params = np.random.random(param_shape)
jac = qml.jacobian(circuit)(params)
```

For more details on installing and using `lightning.gpu`

, check out the device
documentation.

## Debug with mid-circuit quantum snapshots π·

Designing new quantum algorithms is tough, and *debugging* quantum algorithms is sometimes
even harder!

To help alleviate the frustration of (quantum) debugging, v0.22 of PennyLane introduces the
`qml.Snapshot`

operation, which saves the internal state of simulator devices at arbitrary points within the
quantum execution.

```
dev = qml.device("default.qubit", wires=2)
@qml.qnode(dev, interface=None)
def circuit():
qml.Snapshot()
qml.Hadamard(wires=0)
qml.Snapshot("very_important_state")
qml.CNOT(wires=[0, 1])
qml.Snapshot()
return qml.expval(qml.PauliX(0))
```

During normal execution, the snapshots are ignored:

```
>>> circuit()
array(0.)
```

However, when using the
`qml.snapshots`

transform, intermediate values of the statevector will be stored and returned alongside the results:

```
>>> qml.snapshots(circuit)()
{0: array([1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]),
'very_important_state': array([0.70710678+0.j, 0. +0.j, 0.70710678+0.j, 0. +0.j]),
2: array([0.70710678+0.j, 0. +0.j, 0. +0.j, 0.70710678+0.j]),
'execution_results': array(0.)}
```

Currently, snapshots are supported on `default.qubit`

, `default.mixed`

, and
`default.gaussian`

, returning representations of the quantum state. Over time, we plan
to add more options for snapshots, across both simulator and hardware devices.

## Better batching π¦

We previously added the `@qml.batch_params`

transform to enable batching over trainable parameters
in your quantum algorithms. In this release, we extend this support to *all* gate parameters,
including embeddings and state preparation, via
`@qml.batch_input`

.

```
dev = qml.device("default.qubit", wires=2)
@qml.batch_input(argnum=0)
@qml.qnode(dev, diff_method="parameter-shift", interface="tf")
def circuit(inputs, weights):
# add a batch dimension to the embedding data
qml.AngleEmbedding(inputs, wires=range(2), rotation="Y")
qml.RY(weights[0], wires=0)
qml.RY(weights[1], wires=1)
return qml.expval(qml.PauliZ(1))
```

Batched input parameters can then be passed during QNode evaluation:

```
>>> import tensorflow as tf
>>> x = tf.random.uniform((10, 2), 0, 1)
>>> w = tf.random.uniform((2,), 0, 1)
>>> circuit(x, w)
<tf.Tensor: shape=(10,), dtype=float64, numpy=
array([0.46230079, 0.73971315, 0.95666004, 0.5355225 , 0.66180948,
0.44519553, 0.93874261, 0.9483197 , 0.78737918, 0.90866411])>
```

## Even more mighty quantum transforms πβ‘π¦

From circuit cutting, to conditional operations, snapshots, deferred measurements, and input
batching, you must be thinking ‘Wow! This release couldn’t possibly fit even *more* transforms!’

And yet! We have barely scratched the surface. Here is a quick rundown of more quantum transforms that are debuting in version 0.22:

`qml.matrix()`

for computing the matrix representation of one or more unitary operators.

`qml.eigvals()`

for computing the eigenvalues of one or more operators.

`qml.generator()`

for computing the generator of a single-parameter unitary operation.

`qml.commutation_dag()`

to construct the pairwise-commutation directed acyclic graph (DAG) representation of a quantum circuit.

As always, all transforms support a functional user interface with differentiation support (where possible):

```
>>> def circuit(theta):
... qml.RX(theta, wires=1)
... qml.PauliZ(wires=0)
>>> qml.matrix(circuit)(np.pi / 4)
array([[ 0.92387953+0.j, 0.+0.j , 0.-0.38268343j, 0.+0.j],
[ 0.+0.j, -0.92387953+0.j, 0.+0.j, 0. +0.38268343j],
[ 0. -0.38268343j, 0.+0.j, 0.92387953+0.j, 0.+0.j],
[ 0.+0.j, 0.+0.38268343j, 0.+0.j, -0.92387953+0.j]])
```

## Improvements

In addition to the new features listed above, the release contains a wide array of improvements and optimizations:

- Most compilation transforms, and relevant subroutines, have been updated to support just-in-time
compilation with
`@jax.jit`

.

- The frequencies of gate parameters are now accessible as an operation property, and are used for
circuit analysis, optimization via the
`RotosolveOptimizer`

, and differentiation with the parameter-shift rule (including the general shift rule).

- When computing gradients on quantum hardware, the parameter-shift rule now uses
parameter frequencies to compute
*general*shift rules when operation gradient recipes are not defined. This enables hardware gradient support on a wider set of operations.

- The text-based drawer accessed via
`qml.draw()`

has been improved, with new options for displaying parameters and matrices, an improved algorithm for determining gate positions, and cosmetic improvements.

For the full list of improvements, please refer to the release notes.

## Deprecations and breaking changes

As new things are added, outdated features are removed. Here’s what will be changing in this release.

### Deprecations

`qml.transforms.get_unitary_matrix()`

has been deprecated and will be removed in a future release. For extracting matrices of operations and quantum functions, please use`qml.matrix()`

.

- The
`qml.finite_diff()`

function has been deprecated and will be removed in an upcoming release. Instead,`qml.gradients.finite_diff()`

can be used to compute purely quantum gradients (that is, gradients of tapes or QNodes).

- The
`MultiControlledX`

operation now accepts a single`wires`

keyword argument for both`control_wires`

and`wires`

. The single`wires`

keyword should contain all control wires followed by a single target wire.

- Executing tapes using
`tape.execute(dev)`

is deprecated. Please use the`qml.execute([tape], dev)`

function instead.

- The subclasses of the quantum tape, including
`JacobianTape`

, are deprecated. Instead of calling`JacobianTape.jacobian()`

please use a standard`QuantumTape`

, and apply gradient transforms using the`qml.gradients`

module.

In addition, there are several important changes when creating custom operations:

- The
`Operator.matrix`

method has been deprecated and`Operator.compute_matrix`

should be defined instead. Operator matrices can be accessed using`qml.matrix(op)`

.

- The
`Operator.decomposition`

method has been deprecated and`Operator.compute_decomposition`

should be defined instead. Operator decompositions can be accessed using`Operator.decomposition()`

.

- The
`Operator.eigvals`

method has been deprecated and`Operator.compute_eigvals`

should be defined instead. Operator eigenvalues can be accessed using`qml.eigvals(op)`

.

- The
`Operator.generator`

property is now a method, and should return an*operator instance*representing the generator. Operator generators can be accessed using`qml.generator(op)`

.

For more details on adding custom operations in version 0.22, please see the Adding new operators page in the documentation.

### Breaking changes

- The
`pennylane.measure`

module has been renamed to`pennylane.measurements`

.

These highlights are just scratching the surface — check out the full release notes for more details.

## Contributors

As always, this release would not have been possible without the hard work of our development team and contributors:

Catalina Albornoz, Jack Y. Araz, Juan Miguel Arrazola, Ali Asadi, Utkarsh Azad, Sam Banning, Thomas Bromley, Olivia Di Matteo, Christian Gogolin, Diego Guala, Anthony Hayes, David Ittah, Josh Izaac, Soran Jahangiri, Nathan Killoran, Christina Lee, Angus Lowe, Maria Fernanda Morris, Romain Moyard, Zeyue Niu, Lee James OβRiordan, Chae-Yeun Park, Maria Schuld, Jay Soni, Antal SzΓ‘va, Trevor Vincent, and David Wierichs.