Ready for a new decade of adventures β bring on the 30s πΊπ! PennyLane v0.30 is out! Check out all of the awesome new functionality below.
Contents
- Pulse programming on hardware βοΈπ¬
- Quantum singular value transformation π¦π
- Intuitive QNode returns β©οΈ
- New devices and capabilities π€©
- Improvements π
- Deprecations and breaking changes π
- Contributors βοΈ
Pulse programming on hardware βοΈπ¬
In v0.29, we introduced pulse-based circuit simulation. In v0.30, we have extended this to the real deal: hardware. You can now run pulse programs on the Aquila device from QuEra, giving you access to 256 rubidium-atom qubits through the Amazon Braket service! π
Shout out to the Amazon Braket and QuEra teams for this collab. π€

We're so excited about pulse programming on hardware that we'll be releasing more content over the rest of this month. Stay tuned for a demo that will walk you through the details of hardware device access.
Loading a hardware-compatible Hamiltonian
PennyLane now provides time-dependent Hamiltonians of a specific form that are compatible
with quantum hardware devices. The Aquila device consists of
Rydberg atoms β the foundational
unit for neutral atom quantum computing. In PennyLane, a Rydberg-system Hamiltonian can be
constructed from a
drive term
β qml.pulse.rydberg_drive
β and an
interaction term
β qml.pulse.rydberg_interaction
:
import pennylane as qml from jax import numpy as jnp atom_coordinates = [[0, 0], [0, 4], [4, 0], [4, 4]] wires = [0, 1, 2, 3] amplitude = lambda p, t: p * jnp.sin(jnp.pi * t) ** 2 phase = 0 detuning = 3 * jnp.pi / 4 H_d = qml.pulse.rydberg_drive(amplitude, phase, detuning, wires) H_i = qml.pulse.rydberg_interaction(atom_coordinates, wires) H = H_d + H_i
The Aquila device
The QuEra Aquila device can be loaded in PennyLane using the PennyLane-Braket Plugin:
s3 = ("my-bucket", "my-prefix") # update to your choice of bucket arn = "arn:aws:braket:us-east-1::device/qpu/quera/Aquila" # where Aquila lives dev = qml.device("braket.aws.ahs", device_arn=arn, s3_destination_folder=s3, wires=4) # local device to use if you do not yet have access to Braket # or if Aquila is offline: # dev = qml.device("braket.local.ahs", wires=4)
You will need to take a few steps to get the above to work:
To see when the Aquila device is accessible, visit the device's page on the Amazon Braket website.
Note that running the following circuit will result in a charge to your AWS account. Check out the pricing details for more information.
Executing on quantum hardware
It's now time to create a complete PennyLane circuit, combining the target time-dependent Hamiltonian with the hardware it will be run on:
@qml.qnode(dev) def circuit(params): qml.evolve(H)(params, t=[0, 4]) return qml.expval(qml.PauliZ(0))
All that is left to do is to execute it in the usual PennyLane way and your circuit will be whisked off behind the scenes to be run on Aquila.
>>> params = jnp.array([2.4]) >>> circuit(params) Array(0.94307977, dtype=float32)
What's next?
As well as excitement, we also want to offer a note of caution: this is a brand new device and pulse-level programming is a new paradigm in PennyLane β there are bound to be a few rough edges here and there. If you notice any issues or bugs, please reach out to us on the PennyLane discussion forum or open an issue on GitHub π.
After this, we'll be releasing more pulse-programming content in the coming weeks as well as improving support for this functionality in future releases.
Quantum singular value transformation π¦π

Providing access to state-of-the-art quantum algorithms is part of PennyLane's DNA, and in this release we've gone with one of the big ones. The quantum singular value transformation (QSVT) is a powerful quantum algorithm that can be seen as a generalization of established protocols like amplitude amplification. It's now available for you to play with in PennyLane!
My first QSVT
At its core, QSVT describes how a quantum circuit can be constructed to apply a polynomial
transformation to the singular values of an input matrix.
Consider a matrix A
along with a vector angles
that describes the target polynomial
transformation. The qml.qsvt
function creates a corresponding circuit:
import pennylane as qml from pennylane import numpy as np dev = qml.device("default.qubit", wires=2) A = np.array([[0.1, 0.2], [0.3, 0.4]]) angles = np.array([0.1, 0.2, 0.3]) @qml.qnode(dev) def example_circuit(A): qml.qsvt(A, angles, wires=[0, 1]) return qml.expval(qml.PauliZ(wires=0))
This circuit is composed of
qml.BlockEncode
and
qml.PCPhase
operations.
>>> example_circuit(A) tensor(0.97777078, requires_grad=True) >>> print(example_circuit.qtape.expand(depth=1).draw(decimals=2)) 0: βββ_Ο(0.30)ββBlockEncode(M0)βββ_Ο(0.20)ββBlockEncode(M0)β βββ_Ο(0.10)ββ€ <Z> 1: ββ°β_Ο(0.30)ββ°BlockEncode(M0)ββ°β_Ο(0.20)ββ°BlockEncode(M0)β ββ°β_Ο(0.10)ββ€
A more flexible QSVT
The qml.qsvt
function
creates a circuit that is targeted at simulators due to the use of matrix-based operations.
For advanced users, you can use the
qml.QSVT
template to perform
the transformation with a custom choice of unitary and projector operations, which may be
hardware-compatible if a decomposition is provided.
For example, matrix- and phase-encoding operations can be defined manually and fed
into qml.QSVT
:
block_encode = qml.BlockEncode(A, wires=[0, 1]) shifts = [qml.PCPhase(i, dim=2, wires=[0, 1]) for i in reversed(angles)] @qml.qnode(dev) def example_circuit(): qml.QSVT(block_encode, shifts) return qml.expval(qml.PauliZ(wires=0))
Evaluating this circuit gives the same result as above, but we have the option to swap out
block_encode
and shifts
with our own choice of operations. If these operations have a
defined decomposition into elementary gates, then the QSVT algorithm can be hardware-compatible.
Intuitive QNode returns β©οΈ

QNodes now return exactly what you tell them to! π
This was an experimental feature introduced in v0.25 of PennyLane that was enabled
via
qml.enable_return()
.
Now, it's the default return system. Here's how it works.
Consider the following circuit:
import pennylane as qml dev = qml.device("default.qubit", wires=1) @qml.qnode(dev) def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)), qml.probs(0)
In v0.29 and earlier, circuit()
would return a single length-3 array:
>>> circuit(0.5) tensor([0.87758256, 0.93879128, 0.06120872], requires_grad=True)
In v0.30 and above, circuit()
returns a length-2 tuple containing the expectation
value and probabilities separately:
>>> circuit(0.5) (tensor(0.87758256, requires_grad=True), tensor([0.93879128, 0.06120872], requires_grad=True))
More details about this change, along with help and troubleshooting tips to solve any issues, can be found on our dedicated QNode returns page. If you can't find the information you need here, you're confused, or have general questions about the new return system, post on the PennyLane discussion forum and let us know.
New devices and capabilities π€©
As always, more devices are being added to the PennyLane ecosystem. At the same time, we're squeezing more and more capabilities out of existing devices. Here are some of the highlights from the past few months.
Access to Quantum Inspire

The Quantum Inspire platform provides access to a range of simulator and hardware backends. Run by QuTech, it allows you to interact with quantum-dot based hardware as well as transmon qubits. Now, you can do all of this from the comfort of PennyLane πππ. Check out the PennyLane-QuantumInspire plugin to get started β and thanks to the Quantum Inspire team for creating this plugin π!
PennyLane goes Kokkos

We're excited to announce the
lightning.kokkos
device,
allowing you to run lightning-fast quantum simulations everywhere, from CPU to GPU β‘π§βπ»! If you
haven't seen it already, check out our recent
blog post
for more details.
MCMC sampling in Lightning
PennyLane's lightning.qubit
device now offers a Markov chain Monte Carlo (MCMC) backend for
finite-shot calculations. Using MCMC sampling saves you from having to compute the full
2^N-dimensional probability distribution of your N-qubit circuit, unlocking potential
performance improvements. You can explore MCMC sampling by enabling it when instantiating your
device:
dev = qml.device("lightning.qubit", wires=2, shots=1000, mcmc=True)
Take a look here for more details!
Improvements π
In addition to the new features listed above, the release contains a wide array of improvements and optimizations:
- Single-qubit operations that have multi-qubit control can now be decomposed more efficiently
using fewer CNOT gates. Three decompositions from
arXiv:2302.06377 are provided and
compare favourably to the already-available
qml.ops.ctrl_decomp_zyz
:
wires = [0, 1, 2, 3, 4, 5] control_wires = wires[1:] @qml.qnode(qml.device('default.qubit', wires=6)) def circuit(): with qml.QueuingManager.stop_recording(): # the decomposition does not un-queue the target target = qml.RX(np.pi/2, wires=0) qml.ops.ctrl_decomp_bisect(target, (1, 2, 3, 4, 5)) return qml.state()
>>> print(qml.draw(circuit, expansion_strategy="device")()) 0: ββHββXββU(M0)ββXββU(M0)β ββXββU(M0)ββXββU(M0)β ββHββ€ State 1: ββββββββββββββββββββββββββββββββββββββββββββββββββ€ State 2: ββββββββββββββββββββββββββββββββββββββββββββββββββ€ State 3: βββββ°βββββββββββββββββββββ°ββββββββββββββββββββββββ€ State 4: ββββββββββββββββββββββββββββββββββββββββββββββββββ€ State 5: βββββββββββββββ°βββββββββββββββββββββ°ββββββββββββββ€ State
-
The adjoint differentiation method can now be more efficient, avoiding the decomposition of operations that can be differentiated directly. Any operation that defines a
generator()
can be differentiated with the adjoint method. -
Derivatives are computed more efficiently when using
jax.jit
with gradient transforms; the trainable parameters are now set correctly instead of every parameter having to be set as trainable. In the circuit below, only the derivative with respect to parameterb
is now calculated:
import jax dev = qml.device("default.qubit", wires=2) @qml.qnode(dev, interface="jax-jit") def circuit(a, b): qml.RX(a, wires=0) qml.RY(b, wires=0) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)) a = jnp.array(0.4) b = jnp.array(0.5) jac = jax.jacobian(circuit, argnums=[1]) jac_jit = jax.jit(jac) jac_jit(a, b) assert len(circuit.tape.trainable_params) == 1
-
qml.QubitStateVector.state_vector
now supports broadcasting. -
qml.SparseHamiltonian
can now be applied to any wires in a circuit rather than being restricted to all wires in the circuit. -
Operators can now be divided by scalars with
/
with the addition of theOperation.__truediv__
dunder method.
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:
-
Both JIT interfaces are no longer compatible with JAX
>0.4.3
. We raise an error for those versions. -
Evolution
now initializes the coefficient with a factor of-1j
instead of1j
. -
The keyword argument
argnums
is now used for gradient transforms using JAX instead ofargnum
.argnum
is automatically converted toargnums
when using JAX and will no longer be supported in v0.31 of PennyLane.
These highlights are just scratching the surface β check out the full release notes for more details.
Contributors βοΈ
As always, this release would not have been possible without the hard work of our development team and contributors:
Komi Amiko, Ali Asadi, Utkarsh Azad, Thomas Bromley, Isaac De Vlugt, Olivia Di Matteo, Amintor Dusko, Tarik El-Khateeb, Lillian M. A. Frederiksen, Diego Guala, Soran Jahangiri, Korbinian Kottmann, Ivana KureΔiΔ, Christina Lee, Vincent Michaud-Rioux, Albert Mitjans Coma, Romain Moyard, Lee J. O'Riordan, Mudit Pandey, Shuli Shu, Matthew Silverman, Jay Soni, David Wierichs, Trevor Vincent.
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 π€