July 10, 2024
PennyLane v0.37 and Catalyst v0.7 released

Cool off from the summer heat with these new features in PennyLane v0.37 and Catalyst v0.7!
Contents
- A new tensor network and matrix product state device π¦Ύ
- Add noise models to your quantum circuits πΊ
- Debugging quantum algorithms π«π
- Accelerate classical processing on GPUs with Catalyst and qjit π
- Just-in-time compile workflows with the Qrack GPU simulator π
- Better control over when drawing and specs take place ποΈ
- Contributions from UnitaryHACK 2024 π
- Improvements π οΈ
- Deprecations and breaking changes π
- Contributors βοΈ
A new tensor network and matrix product state device π¦Ύ
Wide circuits, meet default.tensor
π€
A new default.tensor
device is now available for performing
tensor network (TN) and
matrix product state (MPS) simulations of quantum circuits
using the quimb backend.
This device is meant to simulate very wide circuits, where the backend method used can be set when instantiating
the default.tensor
device with the method
keyword: method = "tn"
(tensor network) or method = "mps"
(matrix product state).
An important feature of the MPS simulation method is that it allows for a tradeoff between simulation
speed and accuracy by setting the max_bond_dim
argument when instantiating the device:
import pennylane as qml import numpy as np dev = qml.device("default.tensor", max_bond_dim=5) n_layers = 10 n_wires = 10 initial_shape, weights_shape = qml.SimplifiedTwoDesign.shape(n_layers, n_wires) np.random.seed(1967) initial_layer_weights = np.random.random(initial_shape) weights = np.random.random(weights_shape) @qml.qnode(dev) def f(): qml.SimplifiedTwoDesign(initial_layer_weights, weights, range(n_wires)) return qml.expval(qml.Z(0))
>>> f() -0.13818212192515317
Learn more about default.tensor
and how to configure it by
visiting our how-to guide!
Add noise models to your quantum circuits πΊ
Make some π₯ NOISE π₯
Support for building noise models and applying them to a quantum circuit has been added
via the NoiseModel
class
and an add_noise
transform.
Under the hood, PennyLane's approach to noise models is insertion-based, meaning that noise is included
by inserting additional operators (gates or channels) that describe the noise into the quantum circuit itself.
Creating a NoiseModel
boils down to defining Boolean conditions under which specific noisy operations
are inserted. There are several ways to specify conditions for
adding noisy operations:
-
qml.noise.op_eq(op)
: if the operatorop
is encountered in the circuit, add noise. -
qml.noise.op_in(ops)
: if any operators inops
are encountered in the circuit, add noise. -
qml.noise.wires_eq(wires)
: if an operator is applied towires
, add noise. -
qml.noise.wires_in(wires)
: if an operator is applied to any wire inwires
, add noise. -
custom noise conditions: custom conditions can be defined as functions decorated with
qml.BooleanFn
that return a Boolean value. For example, the following function will insert noise if aqml.RY
operator is encountered with an angle of rotation that is smaller than0.5
:@qml.BooleanFn def c0(op): return isinstance(op, qml.RY) and op.parameters[0] < 0.5
Conditions can also be combined together with &
, and
, |
, etc. Once the conditions under which
noise is to be inserted have been stated, we can specify exactly what noise is inserted
with the following:
-
qml.noise.partial_wires(op)
: insertop
on the wires that are specified by the condition that triggers adding this noise -
custom noise operations: custom noise can be specified by defining a standard quantum function like below.
def n0(op, **kwargs): qml.RY(op.parameters[0] * 0.05, wires=op.wires)
With that, we can create a qml.NoiseModel
object whose argument must be a dictionary mapping conditions
to noise:
c1 = qml.noise.op_eq(qml.X) & qml.noise.wires_in([0, 1]) n1 = qml.noise.partial_wires(qml.AmplitudeDamping, 0.4) noise_model = qml.NoiseModel({c0: n0, c1: n1})
>>> noise_model NoiseModel({ BooleanFn(c0): n0 OpEq(PauliX) | WiresIn([0, 1]): AmplitudeDamping(gamma=0.4) })
The noise model created can then be added to a QNode with qml.add_noise
:
dev = qml.device("default.mixed", wires=3) @qml.qnode(dev) def circuit(): qml.Y(0) qml.CNOT([0, 1]) qml.RY(0.3, wires=2) # triggers c0 qml.X(1) # triggers c1 return qml.state()
>>> print(qml.draw(circuit)()) 0: ββYβββββββββββββββ€ State 1: ββββββββββββ°XββXββ€ State 2: ββRY(0.30)ββββββββ€ State >>> circuit = qml.add_noise(circuit, noise_model) >>> print(qml.draw(circuit)()) 0: ββYββββββββββββββββββββββββββββββββββββββββββββββ€ State 1: ββββββββββββ°XβββββββββXββAmplitudeDamping(0.40)ββ€ State 2: ββRY(0.30)ββRY(0.01)βββββββββββββββββββββββββββββ€ State
To learn more about this new functionality, check out our noise module documentation and keep your eyes peeled for an in-depth guide in PennyLane Demos!
Debugging quantum algorithms π«π
The ultimate bug zapper β‘οΈ (batteries not included π€ )
The new PennyLane quantum debugger allows pausing simulations via the
qml.breakpoint()
command
and provides tools for analyzing quantum circuits during execution.
This includes the monitoring of the circuit via measurements using
qml.debug_state()
,
qml.debug_probs()
,
qml.debug_expval()
, and
qml.debug_tape()
, stepping
through the operations in a quantum circuit, and interactively adding operations during execution.
Including qml.breakpoint()
in a circuit will cause the simulation to pause during execution and bring
up the interactive console (even in a notebook). For example, consider the following code in a Python
file called script.py
:
@qml.qnode(qml.device('default.qubit', wires=(0,1,2))) def circuit(x): qml.Hadamard(wires=0) qml.CNOT(wires=(0,2)) qml.breakpoint() qml.RX(x, wires=1) qml.RY(x, wires=2) qml.breakpoint() return qml.sample() circuit(1.2345)
Upon executing script.py
, the simulation pauses at the first breakpoint:
> /Users/your/path/to/script.py(8)circuit() -> qml.RX(x, wires=1) [pldb]
While debugging, we can look at the current quantum state with qml.debug_state()
(similar to qml.state()
):
[pldb] print(qml.debug_state()) [0.70710678+0.j 0. +0.j 0. +0.j 0. +0.j 1. +0.j 0.70710678+0.j 0. +0.j 0. +0.j]
Other debugger functions like qml.debug_probs()
and qml.debug_expval()
also function like their
simulation counterparts (qml.probs
and qml.expval
, respectively) and are described in more detail
in the debugger documentation
Stay tuned for an in-depth demonstration on using this feature with real-world examples!
Accelerate classical processing on GPUs with Catalyst and qjit π
Your GPU is on the phone. They'd like you to callback π

In the previous release, we added support for callbacks β seamlessly integrate arbitrary classical code within a quantum just-in-time (QJIT) compiled function.
This release, they have gotten even better; you can accelerate classical code within qjit-compiled
functions on GPUs with
catalyst.accelerate
and differentiate through arbitrary callbacks!
catalyst.accelerate
allows you to seamlessly mix quantum and classical GPU computation.
Simply wrap your classical, jax.jit
-compatible functionality, and specify the device
you want it to execute on:
@catalyst.accelerate(dev=jax.devices("gpu")[0]) def gpu_fn(x): return jnp.sin(x) ** 2 dev = qml.device("lightning.qubit", wires=2) @qml.qnode(dev) def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) @qml.qjit def hybrid_fn(x): y = circuit(jnp.sin(x)) z = gpu_fn(y) # gpu_fn will be executed on a GPU! return jnp.cos(z)
For more details, see the
catalyst.accelerate
documentation.
In addition,
catalyst.pure_callback
and catalyst.debug.callback
now support autodifferentiation.
catalyst.debug.callback
can be used in any function that will be differentiated, while
catalyst.pure_callback
allows you to define custom VJP rules that will be registered
with the Catalyst compiler via pure_callback.fwd
and pure_callback.bwd
.
See the catalyst.pure_callback
documentation
for more details on usage.
Just-in-time compile workflows with the Qrack GPU simulator π
!!!qjit
on Qrack!!!

Qrack, a GPU-accelerated simulator supported by the Unitary Fund that supports dynamical
transitions between GPU-based and CPU-based simulation techniques and approximations for maximum
execution speed now supports workflows that have been compiled with @qml.qjit
and Catalyst.
Simply pip install pennylane-qrack
, and use the Qrack simulator device within a qjit-compiled
function:
qubits = 4 dev = qml.device("qrack.simulator", qubits, shots=8) @qml.qjit(autograph=True) @qml.qnode(dev) def circuit(): qml.Hadamard(0) for i in range(1, qubits): qml.CNOT(wires=[i - 1, i]) return qml.sample(wires=range(qubits))
By using Qrack with Catalyst and qjit
, take advantage of just-in-time compilation performance
improvements and features such as AutoGraph and dynamic quantum control flow, alongside
Qrack-supported simulation methods such as near-Clifford simulation.
For more details, see our tutorial on Qrack and Catalyst, as well as the PennyLane-Qrack documentation.
Better control over when drawing and specs take place ποΈ
It is now possible to control the stage at which qml.draw
,
qml.draw_mpl
, and
qml.specs
occur within
a QNode's transform program.
Consider the following circuit, which has multiple transforms applied:
@qml.transforms.cancel_inverses @qml.transforms.merge_rotations @qml.qnode(qml.device("default.qubit")) def f(): qml.Hadamard(0) qml.Y(0) qml.RX(0.4, 0) qml.RX(-0.4, 0) qml.Y(0) return qml.expval(qml.X(0) + 2 * qml.Y(0))
We can specify a level
value when using qml.draw()
.
level = 0
: the original circuit with no transforms applied
>>> print(qml.draw(f, level = 0)()) 0: ββHββYββRX(0.40)ββRX(-0.40)ββYββ€ <X+(2.00*Y)>
level = 1
: apply the first transform (merge_rotations
)
>>> print(qml.draw(f, level = 1)()) 0: ββHββYββYββ€ <X+(2.00*Y)>
level = 2
: apply the second transform (cancel_inverses
)
>>> print(qml.draw(f, level = 2)()) 0: ββHββ€ <X+(2.00*Y)>
Contributions from UnitaryHACK 2024 π
As always, UnitaryHACK was a smooth, fun, and productive experience for everyone involved. Congrats to to this year's bounty hunters and Unitary Fund for another great event π! Here are the contributions made to PennyLane:
-
default.clifford
now supports arbitrary state-based measurements withqml.Snapshot
. -
The implementation for
qml.assert_equal
has been updated forOperator
,Controlled
,Adjoint
,Pow
,Exp
,SProd
,ControlledSequence
,Prod
,Sum
,Tensor
andHamiltonian
instances. -
qml.from_qasm
now supports the ability to convert mid-circuit measurements fromOpenQASM 2
code, and it can now also take an optional argument to specify a list of measurements to be performed at the end of the circuit, just likeqml.from_qiskit
. -
A new operator called
qml.QutritChannel
is now availaible, which allows for specifying noise on thedefault.qutrit.mixed
device using a collection of (3Γ3) Kraus matrices.
Improvements π οΈ
In addition to the new features listed above, this release contains a wide array of improvements and optimizations:
-
catalyst.value_and_grad
returns both the result of a function and its gradient with a single forward and backwards pass. This can be more efficient and reduce overall quantum executions, compared to separately executing the function and then computing its gradient. -
Catalyst now supports the 'dynamic one-shot' method for simulating circuits with mid-circuit measurements, which may be advantageous for circuits with many mid-circuit measurements executed for few shots. Select this option via the
mcm_method="one-shot"
argument of the QNode (Catalyst's existing method for simulating mid-circuit measurements remains available viamcm_method="single-branch-statistics"
). For more details, see the dynamic quantum circuit documentation. -
AutoGraph, which allows standard Python control flow to be used with
@qjit
-compiled dynamic quantum circuits, comes with a host of new features:-
Single-index in-place array assignments written in standard Python syntax
x[i] = y
can now be captured and automatically converted into the JAX-compatible formx = x.at(i).set(y)
:@qml.qjit(autograph=True) def f(array): result = jnp.ones(array.shape, dtype=array.dtype) for i, x in enumerate(array): result[i] = result[i] + x * 3 return result
>>> f(jnp.array([-0.1, 0.12, 0.43, 0.54])) array([0.7 , 1.36, 2.29, 2.62])
-
The decorator
catalyst.disable_autograph
allows one to disable AutoGraph from auto-converting specific external functions when called within a qjit-compiled function withautograph=True
.
-
-
Catalyst now supports
qjit
-compiled functions that have dynamically shaped arrays in control-flow primitives. Arrays with dynamic shapes can now be used withfor_loop
,while_loop
, andcond
primitives:@qml.qjit def f(shape): a = jnp.ones([shape], dtype=float) @for_loop(0, 10, 2) def loop(i, a): return a + i return loop(a)
>>> f(3) array([21., 21., 21.])
-
QChem Hamiltonians can now be converted to and from OpenFermion with
qml.to_openfermion
andqml.from_openfermion
. -
The QROM algorithm is now available in PennyLane with
qml.QROM
. This template allows you to enter classical data in the form of bitstrings. Stay tuned in the coming weeks after this release for a PennyLane Demo on usingqml.QROM
in the wild!
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
simplify
argument inqml.Hamiltonian
andqml.ops.LinearCombination
has been deprecated. Instead,qml.simplify
can be called on the constructed operator. -
The default behaviour of
qml.from_qasm()
to remove measurements in the QASM code has been deprecated. Usemeasurements=[]
to keep this behaviour ormeasurements=None
to keep the measurements from the QASM code. -
qml.load
has been removed in favour of more specific functions, such asqml.from_qiskit
, etc. -
qml.from_qasm_file
has been removed. The user can open files and load their content usingqml.from_qasm
.
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:
Tarun Kumar Allamsetty, Guillermo Alonso-Linaje, Ali Asadi, Utkarsh Azad, Ludmila Botelho, Gabriel Bottrill, Thomas Bromley, Jack Brown, Astral Cai, Ahmed Darwish, Isaac De Vlugt, Diksha Dhawan, Amintor Dusko, Tarik El-Khateeb, Lillian M. A. Frederiksen, Pietropaolo Frisoni, Emiliano Godinez, Diego Guala, Austin Huang, David Ittah, Soran Jahangiri, Rohan Jain, Tzung-Han Juang, Mashhood Khan, Korbinian Kottmann, Ivana KureΔiΔ, Christina Lee, Mehrdad Malekmohammadi, Vincent Michaud-Rioux, Sergei Mironov, Erick Ochoa, Lee James O'Riordan, Mudit Pandey, Kenya Sakka, Shuli Shu, Jay Soni, Raul Torres, Kazuki Tsuoka, Daria Van Hende, Haochen Paul Wang, David Wierichs.
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.