January 13, 2026
PennyLane v0.44 and Catalyst v0.14 released
PennyLane v0.44 and Catalyst v0.14 are here to help you solve the biggest mysteries in quantum computing ๐ต๏ธ
New features this release include QRAM templates, IQP circuits, Pauli-based computation functionality, resource estimation for FTQC algorithms, detailed qjit'd circuit inspection, and much more โ get the details down below!
A reminder as well that your feedback is key, and will help shape the future of PennyLane. Please take a moment to fill out our quantum programming survey before Thursday, February 12 โ weโre listening!
Contents
- Write your FTQC algorithm
- Compile your FTQC algorithms
- Understand your FTQC algorithms
- Deprecations and breaking changes ๐
- Community contributions
- Contributors โ๏ธ
Write your FTQC algorithm
Quantum Random Access Memory (QRAM) ๐พ
Can't remember the clues? Try to stir your memory ๐ค
Three implementations of QRAM are now available in PennyLane as part of a continuing collaboration with the Yale Quantum Institute, including:
BBQRAM: a bucket-brigade style QRAM implementation that is also resilient to noise.SelectOnlyQRAM: a QRAM implementation that comprises a series ofMultiControlledXgates.HybridQRAM: a QRAM implementation that combinesBBQRAMandSelectOnlyQRAMin a manner that allows for tradeoffs between depth and width.
The choice of QRAM implementation depends on the application, ranging from width versus depth tradeoffs to noise resilience. For more theoretical details, check out QRAM: A Survey and Critique.
An example of using
BBQRAM
to read data into a target register is given below, where
the data set in question is given by a list of bitstrings and we wish to read its second entry
("110"):
import pennylane as qml
import numpy as np
bitstrings = ["010", "111", "110", "000"]
bitstring_size = 3
num_control_wires = 2 # len(bistrings) = 4 = 2**2
num_work_wires = 1 + 3 * ((1 << num_control_wires) - 1) # 10
reg = qml.registers({"control": num_control_wires,
"target": bitstring_size,
"work_wires": num_work_wires})
dev = qml.device("default.qubit")
@qml.qnode(dev)
def bb_quantum():
# prepare an address, e.g., |10> (index 2)
qml.BasisEmbedding(2, wires=reg["control"])
qml.BBQRAM(
bitstrings,
control_wires=reg["control"],
target_wires=reg["target"],
work_wires=reg["work_wires"],
)
return qml.probs(wires=reg["target"])
>>> print(np.round(bb_quantum()))
[0. 0. 0. 0. 0. 0. 1. 0.]
Note that "110" in binary is equal to 6 in decimal, which is the position of the only
non-zero entry in the target_wires register.
For more information on each implementation of QRAM in this release, check out their respective
documentation pages:
BBQRAM,
SelectOnlyQRAM,
and HybridQRAM.
Instantaneous Quantum Polynomial Circuits ๐จ
What if we could train quantum machine learning models on classically, and then deploy on quantum hardware? ๐คฉ We can, with IQP optimization.
A new template for defining an Instantaneous Quantum Polynomial (IQP) circuit has been added,
as well as compatibility with qml.estimator.estimate for fast resource estimation.
These new features facilitate the simulation and resource estimation of large-scale
generative quantum machine learning tasks. For more theoretical details, check out our Fast optimization of instantaneous quantum polynomial circuits demo.
While IQP circuits are believed to be hard to sample from, their expectation values for Pauli-Z operators can be evaluated efficiently. With this PennyLane release, you can use the new qml.qnn.iqp_expval function to rapidly evaluate expectation values for circuits with thousands of qubits and millions of gates via a Monte Carlo method.
Here is a simple example showing how to define an IQP
circuit, estimate the resources for its simulation, and compute the expectation values for the operators Z_1, Z_0 and Z_0 Z_1:
import pennylane as qml
import pennylane.estimator as qre
import jax
pattern= [[[0]],[[1]],[[0,1]]] # binary array representing gates X0, X1, X0X1
ops = np.array([[0, 1], [1, 0], [1, 1]]) # binary array representing ops Z1, Z0, Z0Z1
num_wires = 2
weights = np.ones(len(pattern))
n_samples = 1000
key = jax.random.PRNGKey(42)
@qml.qnode(qml.device('lightning.qubit', wires=2))
def circuit():
qml.IQP(weights=weights, num_wires=num_wires, pattern=pattern)
return qml.state()
expvals = qml.qnn.iqp_expval(ops, weights, pattern, num_wires, n_samples, key)[0]
>>> res = qre.estimate(circuit)()
>>> print(res)
--- Resources: ---
Total wires: 2
algorithmic wires: 2
allocated wires: 0
zero state: 0
any state: 0
Total gates : 138
'T': 132,
'CNOT': 2,
'Hadamard': 4
>>> print(expvals)
[0.18971464 0.14175898 0.17152457]
Compile your FTQC algorithms
Pauli-based computation ๐ป
Compiling the facts never felt this smooth ๐ฎโ๐จ
New tools dedicated to fault-tolerant quantum computing (FTQC) research based on the Pauli-based computation (PBC) framework are now available! With this release, you can express, compile, and inspect workflows written in terms of Pauli product rotations (PPRs) and Pauli product measurements (PPMs), which are the building blocks of the PBC framework.
Writing circuits in terms of
Pauli product measurements (PPMs) in
PennyLane is now possible with the new
pauli_measure function.
Using this function in tandem with
PauliRot to represent
PPRs unlocks surface-code FTQC research spurred from
A Game of Surface Codes.
Using pauli_measure
in a circuit is similar to measure,
but requires that a pauli_word be specified for the measurement basis:
import pennylane as qml
import jax.numpy as jnp
dev = qml.device("null.qubit", wires=3)
def qfunc():
qml.Hadamard(0)
qml.Hadamard(2)
qml.SWAP([0, 1])
qml.RZ(0.1, wires=1)
qml.PauliRot(jnp.pi / 4, pauli_word="XYZ", wires=[0, 1, 2])
ppm = qml.pauli_measure(pauli_word="XY", wires=[0, 2])
qml.cond(ppm, qml.X)(0)
>>> print(qml.draw(qfunc)())
0: โโHโโญSWAPโโโโโโโโโโโโญRXYZ(0.79)โโญโคโXโโโXโโค
1: โโโโโฐSWAPโโRZ(0.10)โโRXYZ(0.79)โโโโโโโโโโโค
2: โโHโโโโโโโโโโโโโโโโโโฐRXYZ(0.79)โโฐโคโYโโโโโโค
โโโโโโ
Additionally, you can compile circuits expressed in terms of PPRs, PPMs, and/or Clifford+T gates
with several compilation passes that are compatible with
qjit (e.g.,
transforms.gridsynth
and
transforms.ppm_compilation)
and inspect the results pass-by-pass with specs:
qml.capture.enable()
qml.decomposition.enable_graph()
gate_set = {qml.T, qml.S, qml.CNOT, qml.H, qml.GlobalPhase, qml.PauliRot, qml.ops.PauliMeasure}
@qml.qjit(target="mlir")
@qml.transforms.ppm_compilation
@qml.transforms.gridsynth
@qml.transforms.decompose(gate_set=gate_set)
@qml.qnode(dev)
def circuit():
qfunc()
return qml.expval(qml.Z(0))
>>> print(qml.specs(circuit, level=5)())
...
Resource specifications:
Total wire allocations: 18
Total gates: 53
Circuit depth: Not computed
Gate types:
PPM: 33
PPR-pi/2: 17
PauliRot: 1
qec.fabricate: 1
GlobalPhase: 1
...
For a complete list of available compilation passes available for PBC workflows, check out the full release notes!
Flexible and modular compilation pipelines ๐ฆ
Compile the evidence with these new features ๐ง
Defining large and complex compilation pipelines in intuitive, modular, and flexible ways is now
possible with the new
CompilePipeline class.
CompilePipeline
allows you to chain together multiple transforms to create custom circuit optimization pipelines in
the following ways:
- Compounding different
CompilePipelineinstances together. - Adding
CompilePipelineinstances together or multiplying by a scalar (to repeat the givenpipelinean integer number of times). - Using
listoperations likeappend,extend, andinsert.
Here is an all-encompassing example of creating a complex compilation pipeline:
>>> import pennylane as qml
>>> pipeline = qml.CompilePipeline(qml.transforms.commute_controlled, qml.transforms.cancel_inverses)
>>> pipeline = qml.CompilePipeline(pipeline, qml.transforms.merge_rotations)
>>> pipeline.extend(2 * qml.transforms.cancel_inverses(recursive=True))
>>> pipeline
CompilePipeline(commute_controlled, cancel_inverses, merge_rotations, cancel_inverses, cancel_inverses)
You can then apply a pipeline directly on a circuit as a decorator, applying each compilation
pass therein:
@pipeline
@qml.qnode(qml.device("default.qubit"))
def circuit():
qml.H(0)
qml.H(0)
qml.Y(1)
qml.CRY(0.1, wires=[0, 1])
qml.PhaseShift(0.2, wires=0)
qml.RX(0.5, 1)
qml.RX(0.2, 1)
return qml.expval(qml.Z(0) @ qml.Z(1))
>>> print(qml.draw(circuit)())
0: โโญโโโโโโโโโโRฯ(0.20)โโโโโโโโโโโโค โญ<Z@Z>
1: โโฐRY(0.10)โโYโโโโโโโโโRX(0.70)โโค โฐ<Z@Z>
Understand your FTQC algorithms
FTQC algorithm resource estimation ๐
Suspicious activity in your account? Balance the books with new resource estimation tools ๐งโ๐ป
In the last release, we launched the estimator module for quantum resource estimation. Check out the How to use PennyLane for Resource Estimation
demo to learn how estimator makes it easy to investigate which quantum programs will be worth running on upcoming FTQC hardware and which use too many qubits or gates.
In this release, we've added support for many new state-of-the-art algorithms, so you can determine which of these are the most promising for your quantum programs without spending weeks combing through research and calculations.
For example, estimate the cost of the versatile Quantum Singular Value Transformation (QSVT) algorithm in milliseconds using a light-weight operator that only requires a few simple inputs:
import pennylane.estimator as qre
block_encoding = qre.MultiRZ(wires=range(1000))
qsvt = qre.QSVT(block_encoding, encoding_dims=(2, 2), poly_deg=10**10)
>>> print(qre.estimate(qsvt))
--- Resources: ---
Total wires: 1997
algorithmic wires: 1000
allocated wires: 997
zero state: 997
any state: 0
Total gates : 1.613E+14
'Toffoli': 1.994E+13,
'T': 1.760E+12,
'CNOT': 3.992E+13,
'X': 3.994E+13,
'Hadamard': 5.976E+13
It's also easy to fully define a quantum program, verify its output via simulation, and then swap to resource estimation to determine its cost or scale up.
First we define the quantum program:
import numpy as np
poly = [0, -1, 0, 0.5, 0, 0.5]
hamiltonian = qml.dot([0.3, 0.7], [qml.Z(1), qml.X(1) @ qml.Z(2)])
dev = qml.device("lightning.qubit")
@qml.qnode(dev)
def circuit():
qml.qsvt(hamiltonian, poly, encoding_wires=[0], block_encoding="prepselprep")
return qml.probs()
Then we simulate:
>>> print(circuit())
[0.14483425 0. 0.78854205 0. 0.03331185 0.
0.03331185 0. ]
And we easily obtain qubit and gate counts using
qre.estimate:
>>> print(qre.estimate(circuit)())
--- Resources: ---
Total wires: 3
algorithmic wires: 3
allocated wires: 0
zero state: 0
any state: 0
Total gates : 1.219E+3
'Toffoli': 5,
'T': 1.144E+3,
'CNOT': 25,
'X': 10,
'Hadamard': 35
Other new algorithms include qre.Qubitization,
qre.QSP, and
qre.UnaryIterationQPE.
See the
v0.44 release notes
for a full list of new algorithms.
Finally, you can compute algorithm-specific errors from quantum circuits with a new algo_error
function. This provides a dedicated entry point for retrieving error information
that was previously accessible through specs.
The function works with QNodes and returns a dictionary of error types and their computed values:
import pennylane as qml
Hamiltonian = qml.dot([1.0, 0.5], [qml.X(0), qml.Y(0)])
dev = qml.device("default.qubit")
@qml.qnode(dev)
def circuit():
qml.TrotterProduct(Hamiltonian, time=1.0, n=4, order=2)
return qml.state()
>>> qml.resource.algo_error(circuit)()
{'SpectralNormError': SpectralNormError(0.25)}
Detailed qjitโd circuit inspection ๐
Don't be afraid to get your hands dirty... get your rubber gloves on and understand what's inside your qjit'd circuits
with our new inspection tools ๐ฌ
Analyzing resources throughout each step of a compilation pipeline can now be done for qjit'd workflows using
the new and improved specs function,
providing a pass-by-pass overview of quantum circuit resources.
Consider the following qjit'd circuit with two compilation passes applied:
import pennylane as qml
@qml.qjit
@qml.transforms.merge_rotations
@qml.transforms.cancel_inverses
@qml.qnode(qml.device('lightning.qubit', wires=2))
def circuit():
qml.RX(1.23, wires=0)
qml.RX(3.45, wires=0)
qml.RY(6.78, wires=1)
qml.X(0)
qml.X(0)
qml.CNOT([0, 1])
return qml.probs()
The supplied level to
specs
can be an individual int value or an iterable
of multiple levels. Additionally, consider the strings "all" and "all-mlir" to receive
circuit resources for all user-applied transforms and MLIR passes,
or specifically user-applied MLIR passes, respectively.
>>> qml.specs(circuit, level=3)()
Device: lightning.qubit
Device wires: 2
Shots: Shots(total=None)
Level: 3
Resource specifications:
Total wire allocations: 2
Total gates: 3
Circuit depth: Not computed
Gate types:
RX: 1
RY: 1
CNOT: 1
Measurements:
probs(all wires): 1
Resources are one thing, the logic of an algorithm another โ use the new
catalyst.draw_graph
to see your dynamic qjit'd circuits visualized as graphs.
Let's visualize a structured circuit using
catalyst.draw_graph,
which also allows you to provide a level corresponding to the stage of compilation at which you would like
to visualize your program!
import pennylane as qml
import catalyst
qml.capture.enable()
@qml.qjit(autograph=True)
@qml.transforms.cancel_inverses
@qml.transforms.merge_rotations
@qml.qnode(qml.device("null.qubit", wires=3))
def circuit(x, y):
qml.X(0)
qml.Y(1)
qml.H(x)
for i in range(3):
qml.S(0)
qml.RX(0.1, wires=1)
qml.RX(0.2, wires=1)
qml.RX(0.2, wires=2)
if i == 3:
qml.T(0)
else:
qml.H(0)
qml.H(0)
return qml.expval(qml.Z(y))
>>> fig, ax = catalyst.draw_graph(circuit, level=2)(1,2)
>>> fig.savefig('path_to_file.png', dpi=200, bbox_inches="tight")
Notice how we're able to see the structure of the program even after we've made optimizations using the compilation passes!
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:
- Maintenance support of NumPy<2.0 is deprecated as of v0.44 and will be completely dropped in v0.45. Future versions of PennyLane will only work with NumPy>=2.0. We recommend upgrading your version of NumPy to benefit from enhanced support and features.
- The
custom_decompskeyword argument topennylane.devicehas been deprecated and will be removed in 0.45. Instead, withdecomposition.enable_graph, new decomposition rules can be defined as quantum functions with registered resources. Seepennylane.decompositionfor more details. - The value
level=Noneis no longer a valid argument in the following:workflow.get_transform_program,workflow.construct_batch,drawer.draw,drawer.draw_mpl, andspecs. Please uselevel='device'instead to apply all transforms. QuantumScript.to_openqasmhas been removed. Please useto_openqasminstead. This removes duplicated functionality for converting a circuit to OpenQASM code.
These highlights are just scratching the surface โ check out the full release notes for PennyLane and Catalyst for more details.
And don't forget to let your voice be heard by taking a few minutes to fill out our quantum programming survey before Thursday, February 12, to help shape future PennyLane development.
Community contributions
In this release cycle we merged 12 PRs from a number of community contributors (with some still in the pipeline for the next release!): Shifan Xu, Dantong Li, and โโRoberto Turrado Camblor.
Thank you for your contributions! ๐
Contributors โ๏ธ
As always, this release would not have been possible without the hard work of our development team and contributors:
Runor Agbaire, Guillermo Alonso, Utkarsh Azad,ย Joseph Bowles, Astral Cai, Yushao Chen, Isaac De Vlugt, Diksha Dhawan, Marcus Edwards, Tarik El-Khateeb, Ashley Enman, Lillian Frederiksen, Sengthai Heng, Austin Huang, David Ittah, Soran Jahangiri, Jeffrey Kam, Jacob Kitchen, Christina Lee, Joseph Lee, Lee J. O'Riordan, Gabriela Sanchez Diaz, Mudit Pandey, Shuli Shu, Jay Soni, Nate Stemen, Theodoros Trochatos, David Wierichs, Shifan Xu, Hongsheng Zheng, Zinan Zhou, Diego Guala, Anton Naim Ibrahim, David Ittah, Korbinian Kottmann, Ali Asadi, Joey Carter, Mehrdad Malekmohammadi, River McCubbin, Mudit Pandey, Andrija Paurevic, Roberto Turrado, Luis Alfredo Nuรฑez Meneses, Paul Haochen Wang, and Jake Zaia.
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.
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
Physicist and Technical Product Manager. Exploring uncharted territory.