PennyLane v0.14.0 Released

We’re excited to announce our latest PennyLane release, version 0.14.0, containing a lot of new and exciting features and improvements. :sun_with_face: :fireworks:

New JAX interface :mechanical_arm:

JAX joins TensorFlow, PyTorch and NumPy as a supported PennyLane interface, bringing its powers of automatic differentiation to the PennyLane ecosystem.

Code example
import pennylane as qml
from jax import numpy as jnp

dev = qml.device("default.qubit", wires=1)

@qml.qnode(dev, interface="jax", diff_method="backprop")
def circuit(x):
    qml.RX(x[1], wires=0)
    qml.Rot(x[0], x[1], x[2], wires=0)
    return qml.expval(qml.PauliZ(0))

weights = jnp.array([0.2, 0.5, 0.1])
grad_fn = jax.grad(circuit)


Speed improvements across the board :rocket: :zap:

We’ve taken a deep dive into the codebase and updated it with many optimizations, tweaks, and improvements to make the PennyLane experience as fast as it gets.

Gradient calculations have been upgraded to better choose the “best” differentiation method for the task at hand.

A new and powerful adjoint method for gradients of simulated circuits has been added. This method is similar to the reversible method, but has a lower time overhead and a similar memory overhead.

Code example
device = qml.device("default.qubit", wires=1)

@qml.qnode(device, diff_method="adjoint")
def f(params):
    qml.RX(0.1, wires=0)
    qml.Rot(*params, wires=0)
    qml.RX(-0.3, wires=0)
    return qml.expval(qml.PauliZ(0))

params = [0.1, 0.2, 0.3]

A faster, leaner, and more flexible core :bullettrain_front: :muscle:

PennyLane’s new core, which has been in development during the last few cycles, has finally become default in v0.14.0, providing several advantages and improvements:

1. Support for in-QNode classical processing allows for differentiable classical processing within the QNode.

Code example
dev = qml.device("default.qubit", wires=1)
@qml.qnode(dev, interface="tf")
def circuit(p):
    qml.RX(tf.sin(p[0])**2 + p[1], wires=0)
    return qml.expval(qml.PauliZ(0))

The classical processing functions used within the QNode must match the QNode interface. Here, we use TensorFlow:

>>> params = tf.Variable([0.5, 0.1], dtype=tf.float64)
>>> with tf.GradientTape() as tape:
...     res = circuit(params)
>>> grad = tape.gradient(res, params)
>>> print(res)
tf.Tensor(0.9460913127754935, shape=(), dtype=float64)
>>> print(grad)
tf.Tensor([-0.27255248 -0.32390003], shape=(2,), dtype=float64)

2. There is no longer any restriction on the QNode signature; the QNode can be defined and called following the same rules as standard Python functions.

Code example

The following QNode uses positional, named, and variable keyword arguments:

x = torch.tensor(0.1, requires_grad=True)
y = torch.tensor([0.2, 0.3], requires_grad=True)
z = torch.tensor(0.4, requires_grad=True)

@qml.qnode(dev, interface="torch")
def circuit(p1, p2=y, **kwargs):
    qml.RX(p1, wires=0)
    qml.RY(p2[0] * p2[1], wires=0)
    qml.RX(kwargs["p3"], wires=0)
    return qml.var(qml.PauliZ(0))

When we call the QNode, we may pass the arguments by name even if defined positionally; any argument not provided will use the default value.

>>> res = circuit(p1=x, p3=z)
>>> print(res)
tensor(0.2327, dtype=torch.float64, grad_fn=<SelectBackward>)
>>> res.backward()
>>> print(x.grad, y.grad, z.grad)
tensor(0.8396) tensor([0.0289, 0.0193]) tensor(0.8387)

This extends to the qnn module, where KerasLayer and TorchLayer modules can be created from QNodes with unrestricted signatures.

3. QNodes can now measure wires more than once, as long as all observables are commuting.

Code example
dev = qml.device('default.qubit', wires=2)

def circuit(x):
    qml.RX(x, wires=0)
    return [
        qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))

and many more.

New Orquestra plugin :drum: :saxophone:

There’s a brand new plugin for Orquestra, Zapata’s quantum-enabled workflow composer. With it, multiple devices offered by Orquestra can be accessed straightaway in PennyLane for cloud-based execution without the need to import new packages, including Rigetti Forest, Qiskit and Qulacs.

Additional improvements and features :chart_with_upwards_trend: :sunrise:

Includes support for more flexible cost functions for the built-in PennyLane optimizers (see code example below), updates to the circuit drawer allowing user-specified wire ordering, along with a lot of bug fixes and other improvements.

Code example
def cost(x, y, data, scale=1.0):
    return scale * (x[0]-data)**2 + scale * (y-data)**2

x = np.array([1.], requires_grad=True)
y = np.array([1.0])
data = np.array([2.], requires_grad=False)

opt = qml.GradientDescentOptimizer()

# the optimizer step and step_and_cost methods can
# now update multiple parameters at once
x_new, y_new, data = opt.step(cost, x, y, data, scale=0.5)
(x_new, y_new, data), value = opt.step_and_cost(cost, x, y, data, scale=0.5)

# list and tuple unpacking is also supported
params = (x, y, data)
params = opt.step(cost, *params)

The full release notes are available at

As always, this release would not have been possible without all the help from our contributors:

Juan Miguel Arrazola, Thomas Bromley, Olivia Di Matteo, Theodor Isacsson, Josh Izaac, Christina Lee, Alejandro Montanez, Steven Oud, Chase Roberts, Sankalp Sanand, Maria Schuld, Antal Száva, David Wierichs, Jiahao Yao.