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.
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
qml.pulse.rydberg_drive — and an
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)
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
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
>>> 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
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
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
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
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
allowing you to run lightning-fast quantum simulations everywhere, from CPU to GPU ⚡🧑💻! If you
haven't seen it already, check out our recent
for more details.
MCMC sampling in Lightning
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
dev = qml.device("lightning.qubit", wires=2, shots=1000, mcmc=True)
Take a look here for more details!
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
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.jitwith 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 parameter
bis 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=) jac_jit = jax.jit(jac) jac_jit(a, b) assert len(circuit.tape.trainable_params) == 1
qml.QubitStateVector.state_vectornow supports broadcasting.
qml.SparseHamiltoniancan 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 the
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.
Evolutionnow initializes the coefficient with a factor of
The keyword argument
argnumsis now used for gradient transforms using JAX instead of
argnumis automatically converted to
argnumswhen 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.
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, Lillian M. A. Frederiksen, Diego Guala, Soran Jahangiri, Korbinian Kottmann, 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.