Version 0.26 of PennyLane is upon us! Brace yourselves for amazing new features, including classical shadows, and qutrits, the QNSPSA optimizer, and more.

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

## Classical shadows 👤

The popular classical shadows measurement protocol is now directly built-in to PennyLane. Return classical shadows from your quantum functions, use them to estimate expectation values, and even perform state tomography on hardware.

The classical-shadow measurement protocol is described in detail in the paper Predicting Many Properties of a Quantum System from Very Few Measurements. As part of the support for classical shadows in this release, two new finite-shot (and of course, fully-differentiable) measurements are available.

#### Measuring classical shadows

The new `qml.classical_shadow()`

measurement will return both
`bits`

(0 or 1 if the 1 or -1 eigenvalue is sampled, respectively) and `recipes`

(the randomized Pauli measurements that are performed for each qubit, labelled by integer):

```
dev = qml.device("default.qubit", wires=2, shots=3)
@qml.qnode(dev)
def circuit():
qml.Hadamard(wires=0)
qml.CNOT(wires=[0, 1])
return qml.classical_shadow(wires=[0, 1])
```

```
>>> bits, recipes = circuit()
>>> bits
tensor([[0, 0],
[1, 0],
[0, 1]], dtype=uint8, requires_grad=True)
>>> recipes
tensor([[2, 2],
[0, 2],
[0, 2]], dtype=uint8, requires_grad=True)
```

#### Estimating expectation values with shadows

The new measurement `qml.shadow_expval()`

returns an estimate of the expectation value estimation using classical shadows:

```
dev = qml.device("default.qubit", wires=range(2), shots=10000)
@qml.qnode(dev)
def circuit(x, H):
qml.Hadamard(0)
qml.CNOT((0,1))
qml.RX(x, wires=0)
return qml.shadow_expval(H)
x = np.array(0.5, requires_grad=True)
H = qml.Hamiltonian(
[1., 1.],
[qml.PauliZ(0) @ qml.PauliZ(1), qml.PauliX(0) @ qml.PauliX(1)]
)
```

```
>>> circuit(x, H),
tensor(1.8486, requires_grad=True)
>>> qml.grad(circuit)(x, H))
-0.4797000000000001
```

Fully-differentiable QNode transforms for both new classical-shadows measurements are also available via
`qml.shadows.shadow_state`

and `qml.shadows.shadow_expval`

, respectively.

For convenient post-processing, we’ve also added the ability to calculate general Renyi entropies
by way of the `ClassicalShadow`

class’ `entropy`

method, which requires the wires of the subsystem of interest
and the Renyi entropy order:

```
>>> shadow = qml.ClassicalShadow(bits, recipes)
>>> vN_entropy = shadow.entropy(wires=[0, 3], alpha=1)
```

## Qutrits: quantum circuits for tertiary degrees of freedom ☘

Good things come in threes!

An entirely new framework for quantum computing is now simulatable with the addition of qutrit functionalities.

Qutrits are like qubits, but they aren’t binary degrees of freedom, they are *tertiary*.
The advent of qutrits allows for all sorts of interesting theoretical, practical, and algorithmic capabilities that have yet to be discovered.

To facilitate qutrit circuits requires a new device: `default.qutrit`

.
The `default.qutrit`

device is a Python-based simulator, akin to `default.qubit`

, and is defined as per usual:

```
dev = qml.device("default.qutrit", wires=2)
@qml.qnode(dev)
def circuit(U, obs):
qml.TShift(0)
qml.TClock(0)
qml.QutritUnitary(U, wires=0)
return qml.expval(qml.THermitian(obs, wires=0))
```

```
>>> U = np.ones([3, 3]) / np.sqrt(3)
>>> obs = np.array([[1, 1, 0], [1, -1, 0], [0, 0, np.sqrt(2)]]) / np.sqrt(2)
>>> circuit(U, obs)
tensor(0.80473785, requires_grad=True)
```

The following operations are supported on `default.qutrit`

devices:

- The qutrit shift operator,
`qml.TShift`

, and the ternary clock operator,`qml.TClock`

, as defined in this paper by Yeh et al. (2022), which are the qutrit analogs of the Pauli X and Pauli Z operations, respectively.

- The
`qml.TAdd`

and`qml.TSWAP`

operations, which are the qutrit analogs of the CNOT and SWAP operations, respectively.

- Custom unitary operations via
`qml.QutritUnitary`

.

`qml.state`

and`qml.probs`

measurements.

- Measuring user-specified Hermitian matrix observables via
`qml.THermitian`

.

We will continue to add more and more support for qutrits in future releases.

## Simplifying just got… simpler 😌

To put it simply, we’ve simplified how `qml.simplify`

simplifies your circuits. Simple as that.

`qml.simplify`

can now perform the following:

- simplify parametrized operations
- simplify the adjoint and power of specific operators
- group like terms in a sum
- resolve products of Pauli operators
- combine rotation angles of identical rotation gates

Here is an example of `qml.simplify`

in action with parameterized rotation gates.
In this case, the angles of rotation are simplified to be modulo \(4\pi\).

```
>>> op1 = qml.RX(30.0, wires=0)
>>> qml.simplify(op1)
RX(4.867258771281655, wires=[0])
>>> op2 = qml.RX(4 * np.pi, wires=0)
>>> qml.simplify(op2)
Identity(wires=[0])
```

All of these simplification features can be applied directly to quantum functions, QNodes, and tapes via decorating with `@qml.simplify`

, as well:

```
dev = qml.device("default.qubit", wires=2)
@qml.simplify
@qml.qnode(dev)
def circuit():
qml.adjoint(qml.prod(qml.RX(1, 0) ** 1, qml.RY(1, 0), qml.RZ(1, 0)))
return qml.probs(wires=0)
```

```
>>> print(qml.draw(circuit)())
0: ──RZ(11.57)@RY(11.57)@RX(11.57)─┤ Probs
```

Enjoy the simple things!

## QNSPSA optimizer 💪

Good golly! Another optimizer!

A new optimizer called `qml.QNSPSAOptimizer`

that implements the quantum natural simultaneous
perturbation stochastic approximation (QNSPSA) method based on
Simultaneous Perturbation Stochastic Approximation of the Quantum Fisher Information is available.

`qml.QNSPSAOptimizer`

is a second-order SPSA algorithm,
which combines the convergence power of the quantum-aware Quantum Natural Gradient
(QNG) optimization method with the reduced quantum evaluations of SPSA methods.

While the QNSPSA optimizer requires additional circuit executions (10 executions per step) compared to standard SPSA optimization (3 executions per step), these additional evaluations are used to provide a stochastic estimation of a second-order metric tensor, which often helps the optimizer to achieve faster convergence.

Use `qml.QNSPSAOptimizer`

like you would any other optimizer:

```
max_iterations = 50
opt = qml.QNSPSAOptimizer()
for _ in range(max_iterations):
params, cost = opt.step_and_cost(cost, params)
```

Check out our demo on the QNSPSA optimizer for more information.

## Improvements 🛠

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

- By default,
`qml.counts`

only returns the outcomes observed in sampling. Optionally, specifying`qml.counts(all_outcomes=True)`

will return a dictionary containing all possible outcomes.

`qml.matrix`

can now compute the matrix of tapes and QNodes that contain multiple broadcasted operations or non-broadcasted operations after broadcasted ones. A common scenario in which this becomes relevant is the decomposition of broadcasted operations.

- Products and sums of operators created using
`qml.prod`

and`qml.op_sum`

now support the`sparse_matrix()`

method, and their matrix computation, sparse or not, is now more efficient.

- You can now iterate over products and sums of operators created from
`qml.op_sum`

and`qml.prod`

.

- Both
`lightning.qubit`

and`lightning.gpu`

now natively support`qml.Hamiltonian`

expectation values in the forward pass. In addition,`lightning.gpu`

also supports`qml.Hamiltonian`

expectation values with the adjoint Jacobian gradient method.

- The
`qml.math.expand_matrix()`

method now allows the sparse matrix representation of an operator to be extended to a larger Hilbert space.

## Deprecations and breaking changes 💔

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

- In-place inversion is now deprecated. This includes
`op.inv()`

and`op.inverse=value`

. Please use`qml.adjoint(op)`

,`qml.pow(op, -1)`

, or`op ** -1`

instead. Note that for unitary operations,`qml.adjoint(op)`

is the most efficient approach.

- Measuring an operator that might not be hermitian now raises a warning instead of an
error. To definitively determine whether or not an operator is hermitian, use
`qml.is_hermitian`

.

`qml.grouping.utils.is_commuting`

has been removed, and its Pauli word logic is now part of`qml.is_commuting`

.

`qml.is_commuting`

has been moved from`pennylane.transforms.commutation_dag`

to`pennylane.ops.functions`

.

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:

Ali Asadi, Juan Miguel Arrazola, Utkarsh Azad, Tom Bromley, Isaac De Vlugt, Olivia Di Matteo, Amintor Dusko, Yiheng Duan, Lillian Marie Austin Frederiksen, Josh Izaac, Soran Jahangiri, Edward Jiang, Ankit Khandelwal, Korbinian Kottmann, Meenu Kumari, Christina Lee, Albert Mitjans Coma, Romain Moyard, Rashid N. H. M., Zeyue Niu, Lee James O’Riordan, Mudit Pandey, Chae-Yeun Park, Matthew Silverman, Jay Soni, Antal Száva, Trevor Vincent, Cody Wang, David Wierichs