PennyLane v0.24 released

PennyLane team

The latest-and-greatest release of PennyLane is now out and available for everyone to use. It comes with many new additions, including a brand new quantum information module, parameter broadcasting, improvements to the JAX JIT interface, and more.

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

All new quantum information quantities 📏

Quantum information functionalities have entered the fold! QNodes can now measure and be transformed to output quantum information quantities while remaining differentiable on statevector devices.

There is a multitude of high-impact research and development showing how quantum information quantities enhance our understanding, efficiency, and speed of optimizing quantum circuits.

Bolster your PennyLane experience with these new features 👇

Two new QNode measurements

Two new quantities can now be measured in QNodes, including the Von Neumann entropy via qml.vn_entropy:

>>> dev = qml.device("default.qubit", wires=2)
>>> @qml.qnode(dev)
... def circuit_entropy(x):
...     qml.IsingXX(x, wires=[0,1])
...     return qml.vn_entropy(wires=[0], log_base=2)
>>> circuit_entropy(np.pi/2)

and mutual information via qml.mutual_info:

>>> dev = qml.device("default.qubit", wires=2)
>>> @qml.qnode(dev)
... def circuit(x):
...     qml.IsingXX(x, wires=[0,1])
...     return qml.mutual_info(wires0=[0], wires1=[1], log_base=2)
>>> circuit(np.pi/2)

Quantum information transforms for QNodes

Transform your existing QNode to suit your quantum information needs, including classical and quantum Fisher information via qml.qinfo.classical_fisher and qml.qinfo.quantum_fisher, respectively:

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

def circ(params):
    qml.RY(params[0], wires=1)
    qml.RY(params[1], wires=1)
    qml.RZ(params[2], wires=1)
    return qml.expval(qml.PauliX(0) @ qml.PauliX(1) - 0.5 * qml.PauliZ(1))

params = np.array([0.5, 1., 0.2], requires_grad=True)
cfim = qml.qinfo.classical_fisher(circ)(params)
qfim = qml.qinfo.quantum_fisher(circ)(params)

These quantities are typically employed in variational optimization schemes to tilt the gradient in a more favourable direction — producing what is known as the natural gradient. For example:

>>> grad = qml.grad(circ)(params)
>>> cfim @ grad  # natural gradient
[ 5.94225615e-01 -2.61509542e-02 -1.18674655e-18]
>>> qfim @ grad  # quantum natural gradient
[ 0.59422561 -0.02615095 -0.03989212]

Additional transforms,

also exist with similar functionality.

Currently, all quantum information measurements and transforms are differentiable, but only support statevector devices, with hardware support to come in a future release (with the exception of qml.qinfo.classical_fisher and qml.qinfo.quantum_fisher, which are both hardware compatible).

More exciting features can be found in the new qinfo module.

Support for quantum parameter broadcasting 📡

Are you tired of having to call your circuit repeatedly with different values? Well, now you can simply pass your circuit multidimensional arrays of parameters that you want to evaluate it with!

>>> dev = qml.device('default.qubit', wires=1)
>>> @qml.qnode(dev)
... def circuit_rx(x, z):
...     qml.RX(x, wires=0)
...     qml.RZ(z, wires=0)
...     qml.RY(0.3, wires=0)
...     return qml.probs(wires=0)
>>> circuit_rx([0.1, 0.2], [0.3, 0.4])
tensor([[0.97092256, 0.02907744],
        [0.95671515, 0.04328485]], requires_grad=True)

Parameter broadcasting refers to passing parameters with additional leading dimensions to quantum operators; additional dimensions will flow through the computation, and produce additional dimensions at the output.

Parameter broadcasting is supported on all devices, hardware and simulator. Note that if not natively supported by the underlying device, parameter broadcasting may result in additional quantum device evaluations. We are working on native broadcast support for all devices, which will make parameter broadcasting faster than serially evaluating your circuit with, say, a for loop.

Improved JAX JIT support 🏎

While Python is a great environment for rapid prototyping, as PennyLane scales up we often need the speed that comes from compiled languages such as C++. However, with just-in-time (JIT) compilation with JAX, you get the best of both worlds: the flexibility of Python, with the speed of compilation.

With this release, PennyLane’s support of JAX JIT compilation gets even better, with support for vector-valued QNodes. This enables new types of workflows and significant performance boosts.

Vector-valued QNodes include those with:

  • qml.probs;
  • qml.state;
  • qml.sample or
  • multiple qml.expval / qml.var measurements.

Consider a QNode that returns basis-state probabilities:

dev = qml.device('default.qubit', wires=2)
x = jnp.array(0.543)
y = jnp.array(-0.654)

@qml.qnode(dev, diff_method="parameter-shift", interface="jax")
def circuit(x, y):
    qml.RX(x, wires=[0])
    qml.RY(y, wires=[1])
    qml.CNOT(wires=[0, 1])
    return qml.probs(wires=[1])
>>> circuit(x, y)
DeviceArray([0.8397495 , 0.16025047], dtype=float32)

Note that computing the jacobian of vector-valued QNodes is not supported with JAX JIT. The output of vector-valued QNodes can, however, be used in the definition of scalar-valued cost functions whose gradients can be computed.

For example, one can define a cost function that outputs the first element of the probability vector:

>>> def cost(x, y):
...    return circuit(x, y)[0]
>>> jax.grad(cost, argnums=[0])(x, y)
(DeviceArray(-0.2050439, dtype=float32),)

New operations & transforms 🤖

There are always new operations and transforms to add as we get feedback from our amazing users 🙋 and as research progresses 🧑‍🔬. Here is what’s new in this release:

  • The qml.ECR (echoed cross-resonance) operation is now available. This gate is a maximally-entangling gate and is equivalent to a CNOT gate up to single-qubit pre-rotations.
  • A new transform qml.batch_partial is available which behaves similarly to functools.partial, but supports batching in the unevaluated parameters. This is useful for executing a circuit with a batch dimension in some of its parameters.
  • A new transform qml.split_non_commuting is available, which splits a quantum function or tape into multiple functions/tapes determined by groups of commuting observables:
dev = qml.device("default.qubit", wires=1)

def circuit(x):
    return [qml.expval(qml.PauliX(0)), qml.expval(qml.PauliZ(0))]
>>> print(qml.draw(circuit)(0.5))
0: ──RX(0.50)─┤  <X>
0: ──RX(0.50)─┤  <Z>

Improvements 🛠

If it ain’t broke, don’t fix it try to improve it 💪. Here’s what we improved in this release:

  • Expectation values of multiple non-commuting observables from within a single QNode are now supported.
  • Selecting which parts of parameter-shift Hessians are computed is now possible. The argnum keyword argument for qml.gradients.param_shift_hessian is now allowed to be a two-dimensional Boolean array_like. Only the indicated entries of the Hessian will then be computed. A particularly useful example is the computation of the diagonal of the Hessian:
dev = qml.device("default.qubit", wires=1)

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

argnum = qml.math.eye(3, dtype=bool)
x = np.array([0.2, -0.9, 1.1], requires_grad=True)
>>> qml.gradients.param_shift_hessian(circuit, argnum=argnum)
tensor([[-0.09928388,  0.        ,  0.        ],
        [ 0.        , -0.27633945,  0.        ],
        [ 0.        ,  0.        , -0.09928388]], requires_grad=True)
  • Commuting Pauli operators are now measured faster. The logic that checks for qubit-wise commuting (QWC) observables has been improved, resulting in a performance boost that is noticable when many commuting Pauli operators of the same type are measured.
  • When using expval() with qml.SparseHamiltonian, computations are now faster and use less memory thanks to changing representations to Compressed Sparse Row (CSR) format.
  • Operations and simulations are now remarkably faster with new highly-performant kernels added to lighting.qubit and lightning.gpu. For more details, please see the release notes for lightning.qubit and lightning.gpu. The new kernels drastically improve the runtime performance, especially for quantum chemistry problems:

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

Deprecations 📟

qml.ExpvalCost has been deprecated, and usage will now raise a warning.

Instead, it is recommended to simply pass Hamiltonians to the qml.expval function inside QNodes.

def ansatz(params):
    return qml.expval(Hamiltonian)

Breaking changes 💔

In with the new, out with the old! Here’s what will be removed in this release.

  • PennyLane no longer supports TensorFlow <=2.3.
  • The module qml.gradients.param_shift_hessian has been renamed to qml.gradients.parameter_shift_hessian in order to distinguish it from the identically named function. Note that the param_shift_hessian function is unaffected by this change and can be invoked in the same manner as before via the qml.gradients module.
  • The properties eigval and matrix from the Operator class were replaced with the methods eigval() and matrix(wire_order=None).

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-Linaje, Mikhail Andrenkov, Juan Miguel Arrazola, Ali Asadi, Utkarsh Azad, Samuel Banning, Avani Bhardwaj, Thomas Bromley, Albert Mitjans Coma, Isaac De Vlugt, Amintor Dusko, Trent Fridey, Christian Gogolin, Qi Hu, Katharine Hyatt, David Ittah, Josh Izaac, Soran Jahangiri, Edward Jiang, Nathan Killoran, Korbinian Kottmann, Ankit Khandelwal, Christina Lee, Lee James O’Riordan, Chae-Yeun Park, Mason Moreland, Romain Moyard, Maria Schuld, Shuli Shu, Jay Soni, Antal Száva, tal66, David Wierichs, Roeland Wiersema, Trevor Vincent, David Wierichs, WingCode.