
Life still goes on after QHack! We've got a load of new features lined up for you with PennyLane v0.35 and Catalyst v0.5. ๐ Let us know how hyped you are for this release by filling out the latest surveyโyour feedback will help shape the future of PennyLane ๐ฎ
Contents
- Qiskit 1.0 integration ๐
- CUDA Quantum integration ๐ง
- Native mid-circuit measurements on default.qubit ๐ก
- A new Clifford device ๐ฆพ
- New Catalyst features: vectorize with vmap, more QJIT functionality, mid-circuit measurement improvements and more! ๐
- Improvements ๐ ๏ธ
- Deprecations and breaking changes ๐
- Contributors โ๏ธ
Qiskit 1.0 integration ๐
Quantum programming is a big, wonderful place, but all roads lead to PennyLane ๐

This version of PennyLane makes it easier to import workflows from Qiskit. The
qml.from_qiskit
function converts a Qiskit
QuantumCircuit
into a PennyLane
quantum function.
Although qml.from_qiskit
already exists in PennyLane, we have made a number of
improvements to make importing from Qiskit easier. And yes โ qml.from_qiskit
functionality is compatible with both Qiskit
1.0 and earlier
versions!
Make sure you install PennyLane's Qiskit plugin to access these features:
-
You can now append PennyLane measurements onto the quantum function returned by
qml.from_qiskit
. Consider this simple Qiskit circuit:import pennylane as qml from qiskit import QuantumCircuit qc = QuantumCircuit(2) qc.rx(0.785, 0) qc.ry(1.57, 1)
We can convert it into a PennyLane QNode in just a few lines, with PennyLane measurements easily included:
>>> dev = qml.device("default.qubit") >>> measurements = qml.expval(qml.Z(0) @ qml.Z(1)) >>> qfunc = qml.from_qiskit(qc, measurements=measurements) >>> qnode = qml.QNode(qfunc, dev) >>> qnode() tensor(0.00056331, requires_grad=True)
-
Quantum circuits that already contain Qiskit-side measurements can be faithfully converted with
qml.from_qiskit
. -
It is now more intuitive to handle and differentiate parametrized Qiskit circuits.
-
In addition to full circuits, it is now also possible to convert operators from Qiskit to PennyLane with a new function called qml.from_qiskit_op.
A Qiskit SparsePauliOp can be converted to a PennyLane operator using
qml.from_qiskit_op
:>>> from qiskit.quantum_info import SparsePauliOp >>> qiskit_op = SparsePauliOp(["II", "XY"]) >>> qiskit_op SparsePauliOp(['II', 'XY'], coeffs=[1.+0.j, 1.+0.j]) >>> pl_op = qml.from_qiskit_op(qiskit_op) >>> pl_op I(0) + X(1) @ Y(0)
Combined with
qml.from_qiskit
, it becomes easy to quickly calculate quantities like expectation values by converting the whole workflow to PennyLane:qc = QuantumCircuit(2) # Create circuit qc.rx(0.785, 0) qc.ry(1.57, 1) measurements = qml.expval(pl_op) # Create QNode qfunc = qml.from_qiskit(qc, measurements) qnode = qml.QNode(qfunc, dev)
>>> qnode() # Evaluate! tensor(0.29317504, requires_grad=True)
To read more about our updated Qiskit conversion capabilities, check out the quick start guide for importing workflows into PennyLane.
CUDA Quantum integration ๐ง

When using PennyLane's @qml.qjit
decorator for just-in-time compilation,
simply specify @qml.qjit(compiler="cuda_quantum")
to utilize CUDA Quantum
to compile your PennyLane workflow, and execute it on CUDA Quantum supported
backends!
dev = qml.device("softwareq.qpp", wires=2) @qml.qjit(compiler="cuda_quantum") @qml.qnode(dev) def circuit(x): qml.RX(x[0], wires=0) qml.RY(x[1], wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliY(0))
>>> circuit(jnp.array([0.5, 1.4])) -0.47244976756708373
The following devices are available when compiling with CUDA Quantum:
softwareq.qpp
: a modern C++ state vector simulatornvidia.custatevec
: The NVIDIA CuStateVec GPU simulator (with multi-GPU support)nvidia.cutensornet
: The NVIDIA CuTensorNet GPU simulator (with support for matrix product states)
Note that CUDA Quantum compilation currently does not have feature parity with Catalyst compilation; in particular, AutoGraph, control flow, differentiation, and various measurement statistics (such as probabilities and variance) are not yet supported.
For more details, please see the Compiling workflows documentation.
Native mid-circuit measurements on default.qubit ๐ก
These features aren't halfmid-baked ๐
One of PennyLane's major focuses for 2024 is to improve support for dynamic circuits, where mid-circuit measurements can determine downstream circuit structure. Building on our existing mid-circuit measurement capabilities, you'll see a continuous stream of improvements over the next few releases. In this release, we decided to focus on the basicsโmaking sure that mid-circuit measurements (MCMs) can be executed efficiently.
MCMs can now be made more scalable and efficient in
finite-shots mode with default.qubit
by simulating them in a way similar to
what happens on quantum hardware.
Previously, MCMs would be automatically replaced with an additional qubit using
the
@qml.defer_measurements
transform, and circuits with a lot of MCMs (like the one below) would have used thousands of qubits to simulate.
With finite shots on default.qubit
, each shot and each time an MCM is
encountered, the device now evaluates the probability of projecting onto
\vert 0 \rangle or \vert 1 \rangle and makes a random choice to collapse the
circuit state. This approach works well when there are a lot of MCMs and the
number of shots is not too high.
import pennylane as qml dev = qml.device("default.qubit", shots=10) @qml.qnode(dev) def f(): for i in range(1967): qml.Hadamard(0) qml.measure(0) return qml.sample(qml.PauliX(0))
>>> f() tensor([-1, -1, -1, 1, 1, -1, 1, -1, 1, -1], requires_grad=True)
A new Clifford device ๐ฆพ
Feel good about being left to your own devices ๐

A new default.clifford
device enables efficient simulation of large-scale Clifford circuits defined in
PennyLane through the use of Stim as a
backend.
Given a circuit with only Clifford gates, one can use this device to obtain the usual range of PennyLane measurements as well as the state represented in the Tableau form of Aaronson & Gottesman (2004):
import pennylane as qml dev = qml.device("default.clifford", tableau=True) @qml.qnode(dev) def circuit(): qml.CNOT(wires=[0, 1]) qml.PauliX(wires=[1]) qml.ISWAP(wires=[0, 1]) qml.Hadamard(wires=[0]) return qml.state()
>>> circuit() array([[0, 1, 1, 0, 0], [1, 0, 1, 1, 1], [0, 0, 0, 1, 0], [1, 0, 0, 1, 1]])
The
default.clifford
device also supports the PauliError
, DepolarizingChannel
, BitFlip
and
PhaseFlip
noise channels
when operating in finite-shot mode.
New Catalyst features: vectorize with vmap, more QJIT functionality, mid-circuit measurement improvements and more! ๐
Vectorization support with vmap
When working with tensor/array frameworks in Python, it can be important to ensure that code is written to minimize the usage of Python for loops (which can be slow and inefficient), and instead push as much of the computation through to the array manipulation library, by taking advantage of extra batch dimensions.
To help, Catalyst now provides a QJIT-compatible catalyst.vmap()
function, which makes it even easier to modify functions to map over inputs
with additional batch dimensions.
For example, consider the following QNode:
dev = qml.device("lightning.qubit", wires=1) @qml.qnode(dev) def circuit(x, y): qml.RX(jnp.pi * x[0] + y, wires=0) qml.RY(x[1] ** 2, wires=0) qml.RX(x[1] * x[2], wires=0) return qml.expval(qml.PauliZ(0))
>>> circuit(jnp.array([0.1, 0.2, 0.3]), jnp.pi) Array(-0.93005586, dtype=float64)
We can use catalyst.vmap
to introduce additional batch dimensions to our input arguments,
without needing to use a Python for loop:
>>> from catalyst import vmap >>> x = jnp.array([[0.1, 0.2, 0.3], ... [0.4, 0.5, 0.6], ... [0.7, 0.8, 0.9]]) >>> y = jnp.array([jnp.pi, jnp.pi / 2, jnp.pi / 4]) >>> qjit(vmap(cost))(x, y) array([-0.93005586, -0.97165424, -0.6987465 ])
catalyst.vmap()
has been implemented to match the same behaviour of jax.vmap
, so it should be a drop-in
replacement in most cases. Under the hood, it is automatically inserting Catalyst-compatible for loops,
which will be compiled and executed outside of Python for increased performance.
Post-selection and qubit reset
Mid-circuit measurements now support post-selection and qubit reset when used with the Lightning simulators.
To specify post-selection, simply pass the postselect
argument to the catalyst.measure
function:
dev = qml.device("lightning.qubit", wires=1) @qjit @qml.qnode(dev) def f(): qml.Hadamard(0) m = catalyst.measure(0, postselect=1) return qml.expval(qml.PauliZ(0))
Likewise, to reset a wire after mid-circuit measurement, simply specify reset=True
.
Just-in-time compilation of static (compile-time constant) arguments
The @qml.qjit
decorator now takes a new argument static_argnums
, which specifies that certain
positional arguments of the decorated function should be treated as compile-time static arguments.
This allows any hashable Python object to be passed to the function during compilation; the function will only be recompiled if the value of the static arguments change. Otherwise, reusing previous static argument values will result in no recompilation.
For more details, see specifying compile-time constants.
Improvements ๐ ๏ธ
In addition to the new features listed above, the release contains a wide array of improvements and optimizations:
-
Over the past few releases, we've strived to make working with PennyLane operators as easy as with pen and paper and to improve operator arithmetic efficiency. The updated operator arithmetic functionality is still being finalized, but can be activated using
qml.operation.enable_new_opmath()
. You can check out all of the changes in the full release notes. The new behaviour will become the default in the next release, so we recommend getting familiar with the new system! -
Lightning GPU now supports CUDA version 12, allowing you to get the latest and greatest out of your NVIDIA GPUs.
-
Vector-Jacobian products (VJPs) can result in faster computations when the output of your quantum node has a low dimension. They can be enabled by setting
device_vjp=True
when loading a QNode. In the next release of PennyLane, VJPs are planned to be used by default, when available. You can check out the experimental support offered in PennyLane v0.35 by heading to the improvements section of the release notes. -
Catalyst now supports Python 3.12 and JAX 0.4.23, and the dependence on TensorFlow for use of AutoGraph functionality has been removed.
-
Improvements have been made to the
@qml.qjit
decorator, reducing the need to recompile the function if previously called with known argument types and static values. -
Gradient functions used within a
@qjit
compiled function (such asqml.grad
,qml.jacobian
,qml.vjp
, andqml.jvp
) are now much more flexible, and support being applied to functions that use (nested) container types as inputs and outputs. This includes lists and dictionaries, as well as any data structure implementing the PyTree protocol. -
Capturing quantum circuits for just-in-time compilation with
@qml.qjit
with many gates is now quadratically faster.
Deprecations and breaking changes ๐
As new things are added, outdated features are removed. To keep track of things in the deprecation pipeline, check out the deprecations page.
Here's a summary of what has changed in this release:
-
Passing additional arguments to a transform that decorates a QNode must now be done through the use of
functools.partial
. -
qml.ExpvalCost
has been removed. Users should useqml.expval()
moving forward. -
Calling
qml.matrix
without providing awire_order
on objects where the wire order could be ambiguous now raises a warning. In the future, thewire_order
argument will be required in these cases. -
Gradient functions when used with
@qjit
now match the JAX convention for the returned axes of gradients, Jacobians, VJPs, and JVPs. As a result, the returned tensor shape from various gradient functions may differ compared to previous versions of PennyLane and Catalyst.
These highlights are just scratching the surface โ check out the full release notes for PennyLane and Catalyst for more details.
Contributors โ๏ธ
As always, this release would not have been possible without the hard work of our development team and contributors:
Abhishek Abhishek, Mikhail Andrenkov, Ali Asadi, Utkarsh Azad, Trenten Babcock, Gabriel Bottrill, Thomas Bromley, Astral Cai, Skylar Chan, Isaac De Vlugt, Diksha Dhawan, Tarik El-Khateeb, Lillian Frederiksen, Pietropaolo Frisoni, Eugenio Gigante, Diego Guala, David Ittah, Soran Jahangiri, Jacky Jiang, Tzung-Han Juang, Korbinian Kottmann, Ivana Kureฤiฤ, Christina Lee, Xiaoran Li, Vincent Michaud-Rioux, Romain Moyard, Pablo Antonio Moreno Casares, Erick Ochoa Lopez, Lee J. O'Riordan, Mudit Pandey, Alex Preciado, Matthew Silverman, Jay Soni, Raul Torres, Haochen Paul Wang.
About the authors
Isaac De Vlugt
My job is to help manage the PennyLane and Catalyst feature roadmap... and spam lots of emojis in the chat ๐ค
Josh Izaac
Josh is a theoretical physicist, software tinkerer, and occasional baker. At Xanadu, he contributes to the development and growth of Xanaduโs open-source quantum software products.