PennyLane v0.38 and Catalyst v0.8 are here and ready to knock your socks off ๐งฆ๐ฅ!
Contents
- Registers of wires ๐งธ
- Quantum arithmetic operations ๐งฎ
- Converting noise models from Qiskit โป๏ธ
- Substantial tree-traversal upgrades ๐ณ
- Differentiable callbacks with Catalyst ๐
- Improvements ๐ ๏ธ
- Deprecations and breaking changes ๐
- Contributors โ๏ธ
Registers of wires ๐งธ
Have you registered for registers ๐ณ๏ธ?
With quantum algorithms getting larger and larger, the less you need to deal with gates and operations
on the level of individual wire labels/indices the better. With this release, a
new function qml.registers
has been added that lets you seamlessly create registers of wires by providing dictionaries of register names and the number of wires required.
>>> wire_reg = qml.registers({"alice": 4, "bob": 3}) >>> wire_reg {'alice': Wires([0, 1, 2, 3]), 'bob': Wires([4, 5, 6])}
The resulting data structure from qml.registers
is a dictionary with the same register names as keys, but the values are
Wires
instances.
Nesting registers within other registers can be easily done by providing a nested dictionary, where the ordering of wire labels is based on the order of appearance and nestedness:
>>> wire_reg = qml.registers({"alice": {"alice1": 1, "alice2": 2}, "bob": {"bob1": 2, "bob2": 1}}) >>> wire_reg {'alice1': Wires([0]), 'alice2': Wires([1, 2]), 'alice': Wires([0, 1, 2]), 'bob1': Wires([3, 4]), 'bob2': Wires([5]), 'bob': Wires([3, 4, 5])}
Since the values of the dictionary are Wires
instances, their use within quantum circuits is very
similar to that of a list
of integers:
dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(): for w in wire_reg["alice"]: qml.Hadamard(w) for w in wire_reg["bob1"]: qml.RX(0.1967, wires=w) qml.CNOT(wires=[wire_reg["alice1"][0], wire_reg["bob2"][0]]) return [qml.expval(qml.Y(w)) for w in wire_reg["bob1"]] print(qml.draw(circuit)())
0: โโHโโโโโโโโโญโโโค 1: โโHโโโโโโโโโโโโค 2: โโHโโโโโโโโโโโโค 3: โโRX(0.20)โโโโโค <Y> 4: โโRX(0.20)โโโโโค <Y> 5: โโโโโโโโโโโโฐXโโค
In tandem with qml.registers
, we've improved Wires
by allowing for set-based combination operators.
This new feature unlocks the ability to combine Wires
instances in the following ways:
- intersection with
&
orintersection()
, - symmetric difference with
^
orsymmetric_difference()
, - union with
|
orunion()
, and - difference with
-
ordifference()
.
>>> wires1 = Wires([1, 2, 3]) >>> wires2 = Wires([2, 3, 4]) >>> wires1.intersection(wires2) # or wires1 & wires2 Wires([2, 3])
For more information on using qml.registers
in the wild, stay tuned for a demo in the
coming days!
Quantum arithmetic operations ๐งฎ
Doing math on a quantum computer? It adds up ๐ค .
Several new operator templates have been added to PennyLane that let you perform quantum arithmetic operations, including:
-
qml.Adder
for in-place modular addition. -
qml.PhaseAdder
for in-place modular addition in the Fourier basis. -
qml.Multiplier
for in-place multiplication. -
qml.OutAdder
for out-place modular addition. -
qml.OutMultiplier
for out-place modular multiplication. -
qml.ModExp
for modular exponentiation.
Here is a comprehensive example that performs the following calculation: (2 + 1) * 3 mod 7 = 2
(or
010
in binary). Notice how great these templates are to use with qml.registers
๐คฉ!
dev = qml.device("default.qubit", shots=1) reg = qml.registers({ "x": 2, # |x>: stores the result of 2 + 1 = 3 "y": 2, # |y>: multiples x by 3 "result": 3, # stores the result of (2 + 1) * 3 m 7 = 2 "work": 2 # for qml.OutMultiplier }) @qml.qnode(dev) def circuit(): # In-place addition qml.BasisEmbedding(2, wires=reg["x"]) qml.Adder(1, x_wires=reg["x"]) # add 1 to wires [0, 1] # Out-place multiplication qml.BasisEmbedding(3, wires=reg["y"]) qml.OutMultiplier( reg["x"], reg["y"], reg["result"], work_wires=reg["work"], mod=7 ) return qml.sample(wires=reg["result"])
>>> circuit() array([0, 1, 0])
Converting noise models from Qiskit โป๏ธ
We'll listen to anyone's noise ๐ฆป.
In the last few releases, we've added substantial improvements and new features to the
Pennylane-Qiskit plugin.
With this release, a new qml.from_qiskit_noise
function
allows you to convert a Qiskit noise model into a PennyLane NoiseModel
.
Here is a simple example with two quantum errors that add two different depolarizing errors based on the presence of different gates in the circuit:
import pennylane as qml import qiskit_aer.noise as noise error_1 = noise.depolarizing_error(0.001, 1) # 1-qubit noise error_2 = noise.depolarizing_error(0.01, 2) # 2-qubit noise noise_model = noise.NoiseModel() noise_model.add_all_qubit_quantum_error(error_1, ['rz', 'ry']) noise_model.add_all_qubit_quantum_error(error_2, ['cx'])
>>> qml.from_qiskit_noise(noise_model) NoiseModel({ OpIn(['RZ', 'RY']): QubitChannel(num_kraus=4, num_wires=1) OpIn(['CNOT']): QubitChannel(num_kraus=16, num_wires=2) })
Under the hood, PennyLane converts each quantum error in the Qiskit noise model into an equivalent
qml.QubitChannel
operator
with the same canonical Kraus representation.
Currently, noise models in PennyLane do not support readout errors. As such, those will be skipped during
conversion if they are present in the Qiskit noise model.
Make sure to install or upgrade the PennyLane-Qiskit plugin via pip install pennylane-qiskit
to access this new
feature! Also be on the lookout for a demo on using noise models in PennyLane in the coming weeks after
the release.
Substantial tree-traversal upgrades ๐ณ
Even monkeys can fall from trees, but not with v0.38 ๐.
With the last release of PennyLane,
we introduced the "tree-traversal"
mid-circuit measurement (MCM) method. The recursive way that this method was implemented
had the unintended consequence of very deep stack calls
for circuits with many MCMs, resulting in stack overflows
in some cases.
With this release, we've redesigned the implementation of mcm_method="tree-traversal"
into an iterative
approach, which lets you have circuits with many, many mid-circuit measurements ๐.
In addition to that internal upgrade, the tree-traversal
algorithm is now compatible with analytic-mode
execution (shots=None
)!
dev = qml.device("default.qubit") n_qubits = 5 @qml.qnode(dev, mcm_method="tree-traversal") def circuit(): for w in range(n_qubits): qml.Hadamard(w) for w in range(n_qubits - 1): qml.CNOT(wires=[w, w+1]) for w in range(n_qubits): m = qml.measure(w) qml.cond(m == 1, qml.RX)(0.1967 * (w + 1), w) return [qml.expval(qml.Z(w)) for w in range(n_qubits)]
>>> circuit() [tensor(0.00964158, requires_grad=True), tensor(0.03819446, requires_grad=True), tensor(0.08455748, requires_grad=True), tensor(0.14694258, requires_grad=True), tensor(0.2229438, requires_grad=True)]
Differentiable callbacks with Catalyst ๐
Your GPU is on the phone... again! They'd like you to differentiate and callback ๐
In the previous release of Catalyst, we added support for accelerating classical code within qjit-compiled
functions on GPUs
with catalyst.accelerate
.
With this release, you can now differentiate through catalyst.accelerate
!
import jax import jax.numpy as jnp @qml.qjit @catalyst.grad def f(x): expm = catalyst.accelerate(jax.scipy.linalg.expm, dev=jax.devices("gpu")[0]) return jnp.sum(expm(jnp.sin(x)) ** 2)
>>> x = jnp.array([[0.1, 0.2], [0.3, 0.4]]) >>> f(x) Array([[2.80120452, 1.67518663], [1.61605839, 4.42856163]], dtype=float64)
For more details, see the
catalyst.accelerate
documentation.
Improvements ๐ ๏ธ
In addition to the new features listed above, this release contains a wide array of improvements and optimizations:
-
Molecules and Hamiltonians can now be constructed for all the elements present in the periodic table. This new feature is made possible by integrating with the
basis-set-exchange
package. If loading basis sets frombasis-set-exchange
is needed for your molecule, make sure that youpip install basis-set-exchange
and setload_data=True
.symbols = ['Ti', 'Ti'] geometry = np.array([[0.0, 0.0, -1.1967], [0.0, 0.0, 1.1967]], requires_grad=True) mol = qml.qchem.Molecule(symbols, geometry, load_data=True)
>>> mol.n_electrons 44
-
A new template called
qml.PrepSelPrep
has been added that implements a block-encoding of a linear combination of unitaries. This operator acts as a nice wrapper for having to performqml.StatePrep
,qml.Select
, andqml.adjoint(qml.StatePrep)
in succession, which is quite common in many quantum algorithms. Here is an example showing the equivalence between usingqml.PrepSelPrep
andqml.StatePrep
,qml.Select
, andqml.adjoint(qml.StatePrep)
:coeffs = [0.3, 0.1] alphas = (np.sqrt(coeffs) / np.linalg.norm(np.sqrt(coeffs))) unitaries = [qml.X(2), qml.Z(2)] lcu = qml.dot(coeffs, unitaries) control = [0, 1] def my_prep_sel_prep(alphas, unitaries): qml.StatePrep(alphas, wires=control, pad_with=0) qml.Select(unitaries, control=control) qml.adjoint(qml.StatePrep)(alphas, wires=control, pad_with=0) @qml.qnode(qml.device("default.qubit")) def circuit(lcu, control, alphas, unitaries): qml.PrepSelPrep(lcu, control) qml.adjoint(my_prep_sel_prep)(alphas, unitaries) return qml.state()
>>> import numpy as np >>> np.round(circuit(lcu, control, alphas, unitaries), decimals=2) [ 1.+0.j -0.+0.j -0.+0.j -0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
-
qml.pauli.group_observables
now usesrustworkx
colouring algorithms to solve the minimum clique cover problem, resulting in orders of magnitude performance improvements. -
qml.StatePrep
andqml.PauliRot
are now much more performant withlightning.qubit
.
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:
-
qml.from_qasm
no longer removes measurements from the QASM code. Usemeasurements=[]
to remove measurements from the original circuit. -
qml.qinfo.classical_fisher
andqml.qinfo.quantum_fisher
have been deprecated. Instead, useqml.gradients.classical_fisher
andqml.gradients.quantum_fisher
. -
The legacy devices
default.qubit.{autograd,torch,tf,jax,legacy}
have been deprecated. Instead, usedefault.qubit
, as it now supports backpropagation through these backends. -
The
max_expansion
argument and theexpansion_strategy
attribute inqml.QNode
have been deprecated. -
The
expansion_strategy
argument has been deprecated in all ofqml.draw
,qml.draw_mpl
, andqml.specs
. Thelevel
argument should be used instead.
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, Ali Asadi, Utkarsh Azad, Tonmoy T. Bhattacharya, Gabriel Bottrill, Thomas Bromley, Jack Brown, Ahmed Darwish, Astral Cai, Joey Carter, Alessandro Cosentino, Yushao Chen, Ahmed Darwish, Isaac De Vlugt, Diksha Dhawan, Amintor Dusko, Tarik El-Khateeb, Maja Franz, Lillian M. A. Frederiksen, Pietropaolo Frisoni, Emiliano Godinez, Diego Guala, Austin Huang, Renke Huang, David Ittah, Josh Izaac, Soran Jahangiri, Tzung-Han Juang, Korbinian Kottmann, Ivana Kureฤiฤ, Christina Lee, Jorge Martinez de Lejarza, William Maxwell, Vincent Michaud-Rioux, Anurav Modak, Erick Ochoa Lopez, Lee J. O'Riordan, Mudit Pandey, Andrija Paurevic, Erik Schultheis, Kunwar Maheep Singh, Mehrdad Malekmohammadi, Romain Moyard, Shuli Shu, Nate Stemen, Raul Torres, Paul Haochen 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.