PennyLane v0.28 — the release so good, not even ChatGPT can fathom it 🤖! Check out all of the awesome new functionality below.

## Custom measurement processes 📐

Spread your wings and get creative with the ability to create custom
measurements using the new
`qml.measurements`

module!

Previously, PennyLane only came with built-in measurements that you can return from QNodes: `qml.expval`

, `qml.probs`

, etc., to name a few. Over time, we have added more, but now you can define your own measurement process function to use in QNodes.

Within
`qml.measurements`

are new subclasses that allow for the possibility to create measurements:

`StateMeasurement`

: represents a state-based measurement`SampleMeasurement`

: represents a sample-based measurement

Creating a custom measurement involves making a class that inherits from one of
the classes above. Here is an example that inherits from `SampleMeasurement`

,
where the new measurement, `CountState`

, computes the number of obtained samples
for a given state:

```
from pennylane.measurements import SampleMeasurement
class CountState(SampleMeasurement):
def __init__(self, state: str):
self.state = state # string identifying the state, e.g. "0101"
wires = list(range(len(state)))
super().__init__(wires=wires)
def process_samples(self, samples, wire_order, shot_range, bin_size):
counts_mp = qml.counts(wires=self._wires)
counts = counts_mp.process_samples(samples, wire_order, shot_range, bin_size)
return counts.get(self.state, 0)
def __copy__(self):
return CountState(state=self.state)
```

The method `process_samples`

must be defined in order to instruct how
`CountState`

processes the samples. Within it, a call to the familiar
`qml.counts`

measurement is made, *its* `process_samples`

method is called to
obtain measurement counts, and only counts for the specified `state`

are returned.

We can now execute the new measurement in a QNode as follows.

```
dev = qml.device("default.qubit", wires=1, shots=10000)
@qml.qnode(dev)
def circuit(x):
qml.RX(x, wires=0)
return CountState(state="1")
```

```
>>> circuit(1.23)
tensor(3303., requires_grad=True)
```

Differentiability is also conserved for this new measurement process:

```
>>> x = qml.numpy.array(1.23, requires_grad=True)
>>> qml.grad(circuit)(x)
4715.000000000001
```

`MeasurementTransform`

: represents a measurement process that requires the application of a batch transform

For more information about these new features, see the documentation for
`qml.measurements`

.

## ZX-calculus 🧮

The magic of ZX-calculus is now at your fingertips! Envision quantum protocols like a boss. 😤

ZX-calculus is a graphical formalism designed to provide an intuitive way to look at quantum protocols as linear maps between qubits, which are called ZX-diagrams. A great example of how intuitive a ZX-diagram can be is the one representing a SWAP gate — consisting of three CNOT gates — as seen above.

With the help of the
PyZX framework, QNodes
decorated with
`@qml.transforms.to_zx`

will return a PyZX graph that represents
the computation at hand in the language of ZX-calculus.

```
dev = qml.device("default.qubit", wires=2)
@qml.transforms.to_zx
@qml.qnode(device=dev)
def circuit(p):
qml.RZ(p[0], wires=1),
qml.RZ(p[1], wires=1),
qml.RX(p[2], wires=0),
qml.PauliZ(wires=0),
qml.RZ(p[3], wires=1),
qml.PauliX(wires=1),
qml.CNOT(wires=[0, 1]),
qml.CNOT(wires=[1, 0]),
qml.SWAP(wires=[0, 1]),
return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
```

```
>>> params = [5 / 4 * np.pi, 3 / 4 * np.pi, 0.1, 0.3]
>>> circuit(params)
Graph(20 vertices, 23 edges)
```

Information about PyZX graphs can be found in the PyZX Graphs API.

## PubChem database access and new basis sets ⚛

### PubChem database access

PennyLane, meet PubChem. PubChem, meet PennyLane. 🤝

The symbols and geometry of compounds from the PubChem database can now be
accessed via `qchem.mol_data()`

.

```
>>> import pennylane as qml
>>> from pennylane.qchem import mol_data
>>> mol_data("BeH2")
(['Be', 'H', 'H'],
tensor([[ 4.79404621, 0.29290755, 0. ],
[ 3.77945225, -0.29290755, 0. ],
[ 5.80882913, -0.29290755, 0. ]], requires_grad=True))
>>> mol_data(223, "CID")
(['N', 'H', 'H', 'H', 'H'],
tensor([[ 0. , 0. , 0. ],
[ 1.82264085, 0.52836742, 0.40402345],
[ 0.01417295, -1.67429735, -0.98038991],
[-0.98927163, -0.22714508, 1.65369933],
[-0.84773114, 1.373075 , -1.07733286]], requires_grad=True))
```

### New basis sets

On the basis of basis sets, PennyLane basically has it covered. 🙌

Enjoy the ability to use the `6-311g`

and `CC-PVDZ`

basis sets in your
`qml.qchem`

code!

```
>>> symbols = ["H", "He"]
>>> geometry = np.array([[1.0, 0.0, 0.0], [0.0, 0.0, 0.0]], requires_grad=False)
>>> charge = 1
>>> basis_names = ["6-311G", "CC-PVDZ"]
>>> for basis_name in basis_names:
... mol = qml.qchem.Molecule(symbols, geometry, charge=charge, basis_name=basis_name)
... print(qml.qchem.hf_energy(mol)())
[-2.84429531]
[-2.84061284]
```

## New transforms, operators, and more 😯

Enjoy this roux of the finest functionalities in town! 🧑🍳🍲

### Purity

Calculating the purity of arbitrary quantum states is now supported. Purity can now be calculated in an analogous fashion to, say, the Von Neumann entropy.

`qml.math.purity`

can be used as an in-line function`qml.qinfo.transforms.purity`

can transform a QNode returning a state to a function that returns the purity

```
>>> theta = np.array(np.pi/3, requires_grad=True)
>>> x = np.array([np.cos(theta), 0, 0, np.sin(theta)])
>>> qml.math.purity(x, [0, 1])
1.0
>>> qml.math.purity(np.outer(x, np.conj(x)), [0])
0.625
>>> dev = qml.device("default.mixed", wires=2)
>>> @qml.qnode(dev)
... def circuit(theta):
... qml.QubitStateVector(np.array([np.cos(theta), 0, 0, np.sin(theta)]), wires=[0, 1])
... return qml.state()
...
>>> qml.qinfo.transforms.purity(circuit, wires=[0])(theta)
0.625
>>> qml.qinfo.transforms.purity(circuit, wires=[0, 1])(theta)
1.0
```

As with the other methods in
`qml.qinfo`

,
purity is fully differentiable:

```
>>> qml.grad(qml.qinfo.transforms.purity(circuit, wires=[0]))(theta)
(0.8660254037844384+0j)
```

### Combine mid-circuit measurements

- Multiple mid-circuit measurements can now be combined arithmetically to create new conditionals.

```
dev = qml.device("default.qubit", wires=3)
@qml.qnode(dev)
def circuit():
qml.Hadamard(wires=0)
qml.Hadamard(wires=1)
m0 = qml.measure(wires=0)
m1 = qml.measure(wires=1)
combined = 2 * m1 + m0
qml.cond(combined == 2, qml.RX)(1.3, wires=2)
return qml.probs(wires=2)
```

```
>>> circuit()
[0.90843735 0.09156265]
```

### New operators

#### Parameteric operators

Three new parametric operators,
`qml.CPhaseShift00`

,
`qml.CPhaseShift01`

,
and
`qml.CPhaseShift10`

,
are now available. Each of these operators performs a phase shift akin to
`qml.ControlledPhaseShift`

but on different positions of the state vector.

```
>>> dev = qml.device("default.qubit", wires=2)
>>> @qml.qnode(dev)
>>> def circuit():
... qml.PauliX(wires=1)
... qml.CPhaseShift01(phi=1.23, wires=[0,1])
... return qml.state()
...
>>> circuit()
tensor([0. +0.j , 0.33423773+0.9424888j,
1. +0.j , 0. +0.j ], requires_grad=True)
```

#### Create operators from a generator

Create operators defined from a generator via
`qml.ops.op_math.Evolution`

.
`qml.ops.op_math.Evolution`

defines the exponential of an operator \(\hat{O}\), of the form \(e^{ix\hat{O}}\), with a single trainable parameter, \(x\). Limiting to a single trainable parameter allows the
use of
`qml.gradients.param_shift`

to find the gradient with respect to the parameter \(x\).

```
dev = qml.device('default.qubit', wires=2)
@qml.qnode(dev, diff_method=qml.gradients.param_shift)
def circuit(phi):
qml.ops.op_math.Evolution(qml.PauliX(0), -.5 * phi)
return qml.expval(qml.PauliZ(0))
```

```
>>> phi = np.array(1.2)
>>> circuit(phi)
tensor(0.36235775, requires_grad=True)
>>> qml.grad(circuit)(phi)
-0.9320390495504149
```

#### Qutrit Hadamard

The qutrit Hadamard gate,
`qml.THadamard`

,
is now available. This operation accepts a `subspace`

keyword argument, which
determines which variant of the qutrit Hadamard to use.

```
>>> th = qml.THadamard(wires=0, subspace=[0, 1])
>>> qml.matrix(th)
array([[ 0.70710678+0.j, 0.70710678+0.j, 0. +0.j],
[ 0.70710678+0.j, -0.70710678+0.j, 0. +0.j],
[ 0. +0.j, 0. +0.j, 1. +0.j]])
```

## Improvements 🛠

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

- PennyLane now supports Python 3.11 and Torch v1.13. Support for Python 3.7 has been dropped.

`qml.pauli.is_pauli_word`

now supports instances of`qml.Hamiltonian`

.

- When
`qml.probs`

,`qml.counts`

, and`qml.sample`

are called with no arguments, they measure all wires. Calling any of the aforementioned measurements with an empty wire list (e.g.,`qml.sample(wires=[])`

) will raise an error.

- The
`qml.ISWAP`

gate is now natively supported on`default.mixed`

, improving on its efficiency.

`qml.equal`

now supports operators created via`qml.s_prod`

,`qml.pow`

,`qml.exp`

, and`qml.adjoint`

.

- Start-up times for
`lightning.gpu`

have been improved.

## Deprecations and breaking changes 💔

As new things are added, outdated features are removed. To keep track of things in the deprecation pipeline, we’ve created a deprecation page.

Here’s what has changed in this release:

- Python 3.7 support is no longer maintained. PennyLane will be maintained for versions 3.8 and up.

`qml.utils.decompose_hamiltonian()`

has been removed. Please use`qml.pauli.pauli_decompose()`

instead.

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:

Guillermo Alonso, Juan Miguel Arrazola, Utkarsh Azad, Samuel Banning, Thomas Bromley, Astral Cai, Albert Mitjans Coma, Ahmed Darwish, Isaac De Vlugt, Olivia Di Matteo, Amintor Dusko, Pieter Eendebak, Lillian M. A. Frederiksen, Diego Guala, Katharine Hyatt, Josh Izaac, Soran Jahangiri, Edward Jiang, Korbinian Kottmann, Christina Lee, Romain Moyard, Lee James O’Riordan, Mudit Pandey, Kevin Shen, Matthew Silverman, Jay Soni, Antal Száva, David Wierichs, Moritz Willmann, and Filippo Vicentini.