October 16, 2025
PennyLane v0.43 and Catalyst v0.13 released
PennyLane v0.43 and Catalyst v0.13 are here so that your quantum programming experience feels like fine dining π©!
Contents
- Dynamic wire allocation π½οΈ
- Resource estimation π
- Track your resources when using Catalyst π§Ύ
- Quantum optimization with qjit π«
- Optimize controlled compute-uncompute patterns π΄
- Deprecations and breaking changes π
- Contributors βοΈ
Dynamic wire allocation π½οΈ
Call ahead to reserve your wires π€΅
Wires can now be dynamically allocated and deallocated in quantum functions with
qml.allocate and
qml.deallocate,
unlocking decompositions with better resource usage, complex dynamic subroutines, and more. Works
with qjit!
The qml.allocate function
can accept three arguments that dictate how dynamically allocated wires are handled:
num_wires: the number of wires to dynamically allocate.state = "zero"or"any": the initial state that the dynamically allocated wires are requested to be in. Currently, supported values are"zero"(initialized in the all-zero state) or"any"(any arbitrary state).restored = TrueorFalse: a guarantee that the allocated wires will be restored to their original state (True) or not (False) when those wires are deallocated.
The recommended way to safely allocate and deallocate wires is to use
qml.allocate as a
context manager:
import pennylane as qml
@qml.qnode(qml.device("default.qubit"), mcm_method="tree-traversal")
def circuit():
qml.H(0)
for i in range(2):
with qml.allocate(1, state="zero", restored=True) as new_wire1:
with qml.allocate(1, state="any", restored=False) as new_wire2:
m0 = qml.measure(new_wire1[0], reset=True)
qml.cond(m0 == 1, qml.Z)(new_wire2[0])
qml.CNOT((0, new_wire2[0]))
return qml.expval(qml.Z(0))
In the above example, the high-level circuit shows four separate allocations and deallocations (two
per loop iteration). However, the circuit that the device receives gets automatically compiled to
only use two additional wires (wires labelled 1 and 2 in the diagram below).
>>> print(qml.draw(circuit, level="device")())
0: ββHβββββββββββββββββββββββββββββββ€ <Z>
1: βββ€ββ β0β©βββββββββ€ββ β0β©ββββββββ€
2: ββββββββββββZββ°XββββββββββββZββ°Xββ€
ββββββββββ ββββββββββ
This is due to the fact that new_wire1 and new_wire2 can both be reused after they've been
deallocated in the first iteration of the for loop.
Support for using
qml.allocate and
qml.deallocate with
@qml.qjit is also available
with some key differences. For more information, check out the
Catalyst documentation!
Resource estimation π
Quickly check what's in the budget with an all-new resource estimation module π³
A new toolkit dedicated to resource estimation is now available in
the estimator module! The
functionality therein is designed to rapidly and flexibly estimate the quantum resources required to
execute programs.
The main entry point to these new features is
the estimate function,
which allows you to estimate the quantum resources (such as qubits and gate counts) required to execute a circuit on a device with a specific target gate set.
The estimate function can be used on circuits written at different levels of detail to get high-level estimates
of gate counts and additional wires fast.
For workflows that are already defined in detail, like
executable QNodes, the estimate
function works as follows:
import pennylane as qml
import pennylane.estimator as qre
dev = qml.device("null.qubit")
@qml.qnode(dev)
def circ():
for w in range(2):
qml.Hadamard(wires=w)
qml.CNOT(wires=[0,1])
qml.RX(1.23*np.pi, wires=0)
qml.RY(1.23*np.pi, wires=1)
qml.QFT(wires=[0, 1, 2])
return qml.state()
>>> res = qre.estimate(circ)()
>>> print(res)
--- Resources: ---
Total wires: 3
algorithmic wires: 3
allocated wires: 0
zero state: 0
any state: 0
Total gates : 408
'T': 396,
'CNOT': 9,
'Hadamard': 3
If exact argument values or detailed operator descriptions are unknown, unavailable, tedious or even
infeasible to compute, quantum programs can be expressed using new lightweight representations of
PennyLane operations that require minimal information to obtain high-level resource estimates. As
part of this release, the estimator
module has added lightweight versions of many PennyLane operations that avoid the need to provide computationally expensive inputs, and potentially expensive pre-processing.
The example below uses lightweight "resource" operators to create a circuit with 50 wires, including a
QROMStatePreparation
acting on 48 wires. Defining this state preparation for execution would require a prohibitively
large state vector, but we are able to estimate the required resources with only metadata, bypassing
this computational barrier. Even at this scale, the resource estimate is computed in a
fraction of a second!
def my_circuit():
qre.QROMStatePreparation(num_state_qubits=48)
for w in range(2):
qre.Hadamard(wires=w)
qre.QROM(num_bitstrings=32, size_bitstring=8, restored=False)
qre.CNOT(wires=[0,1])
qre.RX(wires=0)
qre.RY(wires=1)
qre.QFT(num_wires=30)
return
>>> res = qre.estimate(my_circuit)()
>>> print(res)
--- Resources: ---
Total wires: 129
algorithmic wires: 50
allocated wires: 79
zero state: 71
any state: 8
Total gates : 2.702E+16
'Toffoli': 1.126E+15,
'T': 5.751E+4,
'CNOT': 2.027E+16,
'X': 2.252E+15,
'Z': 32,
'S': 64,
'Hadamard': 3.378E+15
This just scratches the surfaceβgo check out the full release notes for a complete list of resource estimation features added in PennyLane v0.43!
Track your resources when using Catalyst π§Ύ
Dig into the details to pick your sides and sauces with
qjit-compatible circuit
resource tracking π€€
Use qml.specs to track the
resources of programs compiled with
qml.qjit! This new feature
is currently supported when using level="device", which provides exact gate counts of the
quantum-compiled program that the device executes.
import pennylane as qml
from functools import partial
gateset = {qml.H, qml.S, qml.CNOT, qml.T, qml.RX, qml.RY, qml.RZ}
@qml.qjit
@partial(qml.transforms.decompose, gate_set=gateset)
@qml.qnode(qml.device("null.qubit", wires=100))
def circuit():
qml.QFT(wires=range(100))
qml.Hadamard(wires=0)
qml.CNOT(wires=[0, 1])
qml.OutAdder(
x_wires=range(10),
y_wires=range(10,20),
output_wires=range(20,31)
)
return qml.expval(qml.Z(0) @ qml.Z(1))
>>> circ_specs = qml.specs(circuit, level="device")()
>>> print(circ_specs['resources'])
num_wires: 100
num_gates: 138134
depth: 90142
shots: Shots(total=None)
gate_types:
{'CNOT': 55313, 'RZ': 82698, 'Hadamard': 123}
gate_sizes:
{2: 55313, 1: 82821}
Quantum optimization with qjit π«
Get on the gravy train with quantum optimizers that work with
qjit π
Leveraging qjit to optimize
hybrid workflows with the momentum quantum natural gradient optimizer is now possible with
qml.MomentumQNGOptimizerQJIT,
providing better runtime scaling than its non-JIT-compatible counterpart.
The v0.42 release saw the addition of the
qml.QNGOptimizerQJIT
optimizer, which is a qjit-compatible
analogue to
qml.QNGOptimizer.
Both qml.QNGOptimizerQJIT and
qml.MomentumQNGOptimizerQJIT
have an
Optax-like
interface:
import pennylane as qml
import jax.numpy as jnp
dev = qml.device("lightning.qubit", wires=2)
@qml.qnode(dev)
def circuit(params):
qml.RX(params[0], wires=0)
qml.RY(params[1], wires=1)
return qml.expval(qml.Z(0) + qml.X(1))
opt = qml.MomentumQNGOptimizerQJIT(stepsize=0.1, momentum=0.2)
def update_step_qjit(i, args):
params, state = args
return opt.step(circuit, params, state)
@qml.qjit
def optimization_qjit(params, iters):
state = opt.init(params)
args = (params, state)
params, state = qml.for_loop(iters)(update_step_qjit)(args)
return params
Quantum just-in-time compilation works exceptionally well with repeatedly executing the same
function in a for loop. As you can see, 10^5 iterations takes seconds:
>>> import time
>>> params = jnp.array([0.1, 0.2])
>>> iters = 100_000
>>> start = time.process_time()
>>> optimization_qjit(params=params, iters=iters)
Array([ 3.14159265, -1.57079633], dtype=float64)
>>> time.process_time() - start
21.319525
Optimize controlled compute-uncompute patterns π΄
Work some fibre into your quantum regimen with an easy-to-use and versatile change-of-basis operation π«π₯
Benefit from an optimization on controlled compute-uncompute patterns with the new
qml.change_op_basis
function. Operators arranged in a compute-uncompute pattern (U V U^\dagger, which is equivalent to
changing the basis in which V is expressed) can be efficiently controlled, as only the central
(target) operator V needs to be controlled.
These new features leverage the graph-based decomposition system, enabled with
qml.decompostion.enable_graph().
To illustrate their use, consider the following example. The compute-uncompute pattern is composed
of a QFT, followed by a PhaseAdder, and finally an inverse QFT.
import pennylane as qml
from functools import partial
qml.decomposition.enable_graph()
dev = qml.device("default.qubit")
@partial(qml.transforms.decompose, max_expansion=1)
@qml.qnode(dev)
def circuit():
qml.H(0)
qml.CNOT([1,2])
qml.ctrl(
qml.change_op_basis(qml.QFT([1,2]), qml.PhaseAdder(1, x_wires=[1,2])),
control=0
)
return qml.state()
When this circuit is decomposed, the QFT and Adjoint(QFT) are not controlled, resulting in a
much more resource-efficient decomposition:
>>> print(qml.draw(circuit)())
0: ββHβββββββββββββββββββββββββ€ State
1: βββββQFTββPhaseAdderββQFTβ ββ€ State
2: ββ°Xββ°QFTββ°PhaseAdderββ°QFTβ ββ€ State
Additionally, the decompositions for several templates have been updated to use
this optimization, including
qml.Adder,
qml.Multiplier,
qml.OutAdder,
qml.OutMultiplier, and
qml.PrepSelPrep.
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:
- The PennyLane ecosystem will no longer support Intel MacOS platforms for v0.44 and newer now that PennyLane-Lightning has dropped support. If needed, MacOS x86 wheels can be built manually. Additionally, MacOS ARM wheels will require a minimum OS version of 14.0 for continued use with v0.44 and newer. This change is needed to account for MacOS officially deprecating support for Intel CPUs (see their blog post for more details).
- Support for Python 3.10 has been removed and support for Python 3.13 has been added.
- Setting shots on a device through the
shotskeyword argument (e.g.,qml.device("default.qubit", wires=2, shots=1000)) and in QNode calls (e.g.,qml.QNode(circuit, dev)(shots=1000)) has been deprecated. Please use theqml.set_shotstransform to set the number of shots instead. - Support for using TensorFlow with PennyLane has been deprecated and will be dropped in Pennylane v0.44. Instead, we recommend using the JAX or PyTorch interfaces for machine learning applications to benefit from enhanced support and features. Please consult the following demos for more usage information: Turning quantum nodes into Torch Layers and How to optimize a QML model using JAX and Optax.
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:
Guillermo Alonso, Ali Asadi, Utkarsh Azad, Astral Cai, Joey Carter, Pablo Antonio Moreno Casares, Yushao Chen, Amintor Dusko, Isaac De Vlugt, Diksha Dhawan, Gabriela Sanchez Diaz, Marcus Edwards, Tarik El-Khateeb, Ashley Enman, Lillian Frederiksen, Pietropaolo Frisoni, Simone Gasperini, Diego Guala, Sengthai Heng, Austin Huang, David Ittah, Anton Naim Ibrahim, Soran Jahangiri, Jeffrey Kam, Korbinian Kottmann, Elton Law, Christina Lee, Joseph Lee, Mehrdad Malekmohammadi, Luis Alfredo NuΓ±ez Meneses, Lee James O'Riordan, Erick Ochoa, Mudit Pandey, Andrija Paurevic, Justin Pickering, Alex Preciado, Shuli Shu, Jay Soni, Ritu Thombre, Roberto Turrado, Marc Vandelle, Paul Haochen Wang, David Wierichs, Jake Zaia, and Hongsheng Zheng.
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 π€
Diego Guala
Diego is a quantum scientist at Xanadu. His work is focused on supporting the development of the datasets service and PennyLane features.
Anton Naim Ibrahim
Exploring uncharted territory.