We’re excited to announce our latest PennyLane release, version 0.14.0, containing a lot of new and exciting features and improvements.
New JAX interface
JAX joins TensorFlow, PyTorch and NumPy as a supported PennyLane interface, bringing its powers of automatic differentiation to the PennyLane ecosystem.
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, wires=0) qml.Rot(x, x, x, wires=0) return qml.expval(qml.PauliZ(0)) weights = jnp.array([0.2, 0.5, 0.1]) grad_fn = jax.grad(circuit) print(grad_fn(weights))
Speed improvements across the board
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.
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] qml.grad(f)(params)
A faster, leaner, and more flexible core
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.
dev = qml.device("default.qubit", wires=1) @qml.qnode(dev, interface="tf") def circuit(p): qml.RX(tf.sin(p)**2 + p, 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.
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 * p2, 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. ```pycon >>> 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.
dev = qml.device('default.qubit', wires=2) @qml.qnode(dev) def circuit(x): qml.RX(x, wires=0) return [ qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) ]
and many more.
New Orquestra plugin
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
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.
def cost(x, y, data, scale=1.0): return scale * (x-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 https://pennylane.readthedocs.io/en/stable/development/release_notes.html.
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.