
New Year, new features π₯³! Ring in 2024 with the latest and greatest functionality in PennyLane v0.34 and Catalyst v0.4 π
Contents
- Catalyst integration with PennyLane, and much more! βοΈ
- Statistics and drawings for mid-circuit measurements π¨
- Decompose circuits into the Clifford+T gateset π§©
- Use an iterative approach for quantum phase estimation π
- New Catalyst features: asynchronous execution, dynamic arrays, transforms, and error-mitigation π
- Transforms π€
- Improvements π οΈ
- Deprecations and breaking changes π
- Contributors βοΈ
Catalyst integration with PennyLane, and much more! βοΈ

Catalyst enables just-in-time (JIT) compilation of PennyLane programs, allowing you to compile the entire quantum-classical workflow.
Now, Catalyst can be accessed more easily from PennyLane using
the qml.qjit
decorator:
import pennylane as qml dev = qml.device("lightning.qubit", wires=2) @qml.qjit @qml.qnode(dev) def circuit(theta): qml.Hadamard(wires=0) qml.RX(theta, wires=1) qml.CNOT(wires=[0,1]) return qml.expval(qml.PauliZ(wires=1))
With qml.qjit
, both quantum and classical processing is
JIT compiled β down to a machine binary on first-function execution.
Subsequent calls to the compiled function will execute the previously-compiled
binary, resulting in significant performance improvements.
To access these features, make sure that you pip install pennylane-catalyst
.
Check out
more new Catalyst features below!
Statistics and drawings for mid-circuit measurements π¨
Weβre not middling around with mid-circuit measurements π§ !

In the last couple of releases, we've made a concerted effort to making our mid-circuit measurement ecosystem more versatile and easier to interact with. We're continuing on that path in v0.34 with a couple of great new features:
-
Mid-circuit measurements including qubit reuse and reset, postselection, conditioning, and collecting statistics is now supported with the text-based
qml.draw()
and the graphicalqml.draw_mpl()
methods.def circuit(): m0 = qml.measure(0, reset=True) m1 = qml.measure(1, postselect=1) qml.cond(m0 - m1 == 0, qml.S)(0) m2 = qml.measure(1) qml.cond(m0 + m1 == 2, qml.T)(0) qml.cond(m2, qml.PauliX)(1)
The text-based drawer outputs:
>>> print(qml.draw(circuit)()) 0: βββ€ββ β0β©ββββββββSβββββββTβββββ€ 1: βββββββββββββ€βββββββββ€βββββββXββ€ ββββββββββββββββ¬ββββββββ£ β ββββββ©ββββββββ β ββββββββ
The graphical drawer outputs:
>>> print(qml.draw_mpl(circuit)())
-
Mid-circuit measurement results can now be composed using basic arithmetic operations and then statistics can be calculated by putting the result within a PennyLane measurement like
qml.expval()
. For example:dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(phi, theta): qml.RX(phi, wires=0) m0 = qml.measure(wires=0) qml.RY(theta, wires=1) m1 = qml.measure(wires=1) return qml.expval(~m0 + m1) print(circuit(1.23, 4.56))
1.2430187928114291
Another option, for ease-of-use when using
qml.sample()
,qml.probs()
, orqml.counts()
, is to provide a simple list of mid-circuit measurement results:@qml.qnode(dev) def circuit(phi, theta): qml.RX(phi, wires=0) m0 = qml.measure(wires=0) qml.RY(theta, wires=1) m1 = qml.measure(wires=1) return qml.sample(op=[m0, m1]) print(circuit(1.23, 4.56, shots=5))
[[0 1] [0 1] [0 0] [1 0] [0 1]]
Composite mid-circuit measurement statistics are supported on
default.qubit
anddefault.mixed
. To learn more about which measurements and arithmetic operators are supported, refer to the measurements page and the documentation forqml.measure
.
Decompose circuits into the Clifford+T gateset π§©
We made sure to have this as a New Year's resolution decomposition π
Circuits can now be decomposed into the
Clifford+T gateset with the new
qml.clifford_t_decomposition()
transform. Behind the scenes, this decomposition is enacted via the
Solovay-Kitaev algorithm, which approximately decomposes a quantum circuit
into the Clifford+T gateset. To account for errors in the approximation, a
desired total circuit decomposition error, epsilon
, must be specified when
using qml.clifford_t_decomposition
:
dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(): qml.RX(1.1, 0) return qml.state() circuit = qml.clifford_t_decomposition(circuit, epsilon=0.1)
>>> circuit() [ 0.86033365-0.03792839j -0.01571045-0.50807542j] >>> print(qml.draw(circuit)()) 0: ββTβ ββHββTβ ββHββTββHββTββHββTββHββTββHββTβ ββHββTβ ββTβ ββHββTβ ββHββTββHββTββHββTββHββTββHββTβ ββHββTβ ββHββTββHββGlobalPhase(0.39)ββ€ State
The resource requirements of this circuit can also be evaluated:
with qml.Tracker(dev) as tracker: circuit() resources_lst = tracker.history['resources'] print(resources_lst[0])
wires: 1 gates: 34 depth: 34 shots: Shots(total=None) gate_types: {'Adjoint(T)': 8, 'Hadamard': 16, 'T': 9, 'GlobalPhase': 1} gate_sizes: {1: 33, 0: 1}
Use an iterative approach for quantum phase estimation π
More versatility for one of quantum's prized algorithms πͺ

Last release, we added a couple of great features β
ControlledSequence
and
CosineWindow
β for modularizing QPE. In this release, we've added
iterative quantum phase estimation
(IQPE) with
qml.iterative_qpe
.
The subroutine can be used similarly to mid-circuit measurements:
dev = qml.device("default.qubit", shots=5) @qml.qnode(dev) def circuit(): qml.PauliX(wires=[0]) measurements = qml.iterative_qpe(qml.RZ(2., wires=[0]), ancilla=[1], iters=3) return qml.sample(op=measurements)
>>> print(circuit()) [[0 0 1] [0 0 1] [0 0 1] [0 0 1] [0 0 1]]
The i-th element in the list refers to the 5 samples generated by the i-th measurement of the algorithm.
New Catalyst features: asynchronous execution, dynamic arrays, transforms, and error-mitigation π
With this release, we've added a bunch of new and exciting features to Catalyst, such as asynchronous execution of multiple QNodes, new compilation routines, and support for arrays with dynamic shapes.
Asynchronous execution of QNodes
Just-in-time compiled functions now support asynchronuous execution of QNodes.
Simply specify async_qnodes=True
when using the @qml.qjit
decorator to enable the async
execution of QNodes. Currently, asynchronous execution is only supported by
lightning.qubit
and lightning.kokkos
.
Asynchronous execution will be most beneficial for just-in-time compiled functions that contain β or generate β multiple QNodes.
For example,
from jax import numpy as jnp dev = qml.device("lightning.qubit", wires=2) @qml.qnode(device=dev) def circuit(params): qml.RX(params[0], wires=0) qml.RY(params[1], wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(wires=0)) @qml.qjit(async_qnodes=True) def multiple_qnodes(params): x = jnp.sin(params) y = jnp.cos(params) z = jnp.array([circuit(x), circuit(y)]) # will be executed in parallel return circuit(z)
>>> func(jnp.array([1.0, 2.0])) 1.0
Here, the first two circuit executions will occur in parallel across multiple threads, as their execution can occur independently.
Support for PennyLane transforms
Preliminary support for PennyLane transforms has been added. These transformations will be applied in Python, prior to compilation occurring:
@qml.qjit @qml.transforms.split_non_commuting @qml.qnode(dev) def circuit(x): qml.RX(x,wires=0) return [qml.expval(qml.PauliY(0)), qml.expval(qml.PauliZ(0))]
>>> circuit(0.4) [array(-0.51413599), array(0.85770868)]
Dynamically-shaped arrays
The @qml.qjit
decorator can now be used to compile functions that accepts or contain tensors
whose dimensions are not known at compile time; runtime execution with different shapes
is supported without recompilation.
In addition, standard tensor initialisation functions jax.numpy.ones
, jnp.zeros
, and
jnp.empty
now accept dynamic variables (where the value is only known at
runtime).
For more details, see the qjit documentation.
Built-in error mitigation
Error mitigation using the zero-noise extrapolation method is now available through the
catalyst.mitigate_with_zne
transform. This transform can be applied to any qjit
-compiled QNode, allowing for error
mitigation on quantum programs with dynamic circuit structure.
Transforms π€
You name it, PennyLane can transform it π€

Over the last couple of releases, we've been rolling out changes to our transforms API to make it more intuitive and versatile. We're never done making changes and improvements, but now is a good time to summarize and highlight these new features to you!
First, let's see how to make a transform following the new style:
@qml.transform def my_quantum_transform(tape): no_rx = filter(lambda op: op.name != "RX", tape.operations) no_ry = filter(lambda op: op.name != "RY", tape.operations) tape1 = qml.tape.QuantumTape(no_rx, tape.measurements) tape2 = qml.tape.QuantumTape(no_ry, tape.measurements) def post_processing_fn(results): return qml.math.sum(results) return [tape1, tape2], post_processing_fn
We decorate our function with @qml.transform
and ensure that it returns
a sequence of
QuantumTape
objects and a postprocessing function. See the
qml.transform
docs for more details.
We can now show off one of our main featuresβit is now possible to arbitrarily compose transforms together. Let's see it in action:
dev = qml.device("default.qubit") @my_quantum_transform @qml.transforms.hamiltonian_expand @qml.qnode(dev) def circuit(x): qml.RX(x, 0) qml.RY(x, 0) return qml.expval(qml.PauliX(0) + qml.PauliZ(0)) circuit(0.5)
In the above, we have combined our custom transform with
hamiltonian_expand
.
For more information, check out our bolstered transforms documentation page!
Improvements π οΈ
In addition to the new features listed above, the release contains a wide array of improvements and optimizations:
-
The Approximate Quantum Fourier Transform (AQFT) is now available with
qml.AQFT
, as well as eleven other community contributions π. -
qml.AmplitudeEmbedding
now supports batching when used with TensorFlow. -
Autograd, PyTorch, and JAX can now use vector-Jacobian products (VJPs) provided by the device from the new device API. If a device provides a VJP, this can be selected by providing
device_vjp=True
to a QNode orqml.execute
.>>> dev = qml.device('default.qubit') >>> @qml.qnode(dev, diff_method="adjoint", device_vjp=True) >>> def circuit(x): ... qml.RX(x, wires=0) ... return qml.expval(qml.PauliZ(0)) >>> with dev.tracker: ... g = qml.grad(circuit)(qml.numpy.array(0.1)) >>> dev.tracker.totals {'batches': 1, 'simulations': 1, 'executions': 1, 'vjp_batches': 1, 'vjps': 1} >>> g -0.09983341664682815
-
ClassicalShadow.entropy
now uses the algorithm outlined in 1106.5458 to project the approximate density matrix (with potentially negative eigenvalues) onto the closest valid density matrix.
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:
-
single_tape_transform
,batch_transform
,qfunc_transform
, andop_transform
are deprecated. Use the newqml.transform
function instead. -
The functions
qml.transforms.one_qubit_decomposition
,qml.transforms.two_qubit_decomposition
, andqml.transforms.sk_decomposition
were moved toqml.ops.one_qubit_decomposition
,qml.ops.two_qubit_decomposition
, andqml.ops.sk_decomposition
, respectively. -
The function
qml.transforms.classical_jacobian
has been moved to the gradients module and is now accessible asqml.gradients.classical_jacobian
. -
The transforms submodule
qml.transforms.qcut
is now its own module:qml.qcut
. -
The
"pennylane"
MPL-drawer style now draws straight lines instead of sketch-style lines.
These highlights are just scratching the surface β check out the full release notes for more details, and let us know how you're liking the new release in the PennyLane v0.34 survey.
Contributors βοΈ
As always, this release would not have been possible without the hard work of our development team and contributors:
Guillermo Alonso, Ali Asadi, Utkarsh Azad, Gabriel Bottrill, Thomas Bromley, Astral Cai, Minh Chau, Isaac De Vlugt, Amintor Dusko, Pieter Eendebak, Tarik El-Khateeb, Lillian Frederiksen, Pietropaolo Frisoni, Ivana KureΔiΔ, David Ittah, Josh Izaac, Juan Giraldo, Emiliano Godinez Ramirez, Ankit Khandelwal, Korbinian Kottmann, Christina Lee, Erick Ochoa Lopez, Vincent Michaud-Rioux, Sergei Mironov, Anurav Modak, Romain Moyard, Lee James O'Riordan, Mudit Pandey, Shuli Shu, Matthew Silverman, Jay Soni, David Wierichs, Justin Woodring.
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.