The dreamiest version of PennyLane is now available for everyone to use. It comes with many new additions, including a brand new quantum resource estimation module, upgrades to operator arithmetic, differentiable error mitigation, more parameter broadcasting support, new measurement types, and more.
Check out the table of contents below, or keep reading to find out more.
Estimate computational resource requirements 🧠
If you ever find yourself asking “How many qubits and gates do I need to run this algorithm?”, then you’ll love this brand new module!
The new resource module allows you to estimate the number of nonClifford gates and logical qubits needed to implement quantum phase estimation algorithms for simulating materials and molecules. This includes support for quantum algorithms using first and second quantization with specific bases:
 First quantization using a planewave basis via the
FirstQuantization
class:
>>> n = 100000 # number of plane waves
>>> eta = 156 # number of electrons
>>> omega = 1145.166 # unit cell volume in atomic units
>>> algo = FirstQuantization(n, eta, omega)
>>> algo.gates
1.10e+13
>>> algo.qubits
4416
 Second quantization with a doublefactorized Hamiltonian via the
DoubleFactorization
class:
symbols = ['O', 'H', 'H']
geometry = np.array([[0.00000000, 0.00000000, 0.28377432],
[0.00000000, 1.45278171, 1.00662237],
[0.00000000, 1.45278171, 1.00662237]], requires_grad = False)
mol = qml.qchem.Molecule(symbols, geometry, basis_name='sto3g')
core, one, two = qml.qchem.electron_integrals(mol)()
algo = DoubleFactorization(one, two)
>>> print(algo.gates, algo.qubits)
103969925, 290
Intuitive operator arithmetic 🧮
It seems impossible to think that we can make adding, subtracting, and multiplying operators easier, but we did it 😤. So, what’s changed?
 You can now sum and product arbitrary operators; previously, you could only take the sum and product of observables:
>>> qml.RX(0.2, wires=0) + qml.RX(0.1, wires=0)
RX(0.2, wires=[0]) + RX(0.1, wires=[0])
>>> qml.CRX(0.2, wires=[0, 1]) + qml.Projector([0, 1], wires=[0, 1])
CRX(0.2, wires=[0, 1]) + Projector([0, 1], wires=[0, 1])
 You can now add scalars to operators:
>>> 4 + qml.PauliZ(0)
PauliZ(wires=[0]) + 4*(Identity(wires=[0]))
 All existing operator functions you know and love, such as
qml.eigvals
andqml.matrix
, continue to work with this upgraded operator arithemtic:
>>> op = 2 + qml.RX(0.2, wires=0)  3.2 * qml.CRX(0.2, wires=[0, 1])
>>> qml.eigvals(op)
array([0.53837236, 0.23245866, 0.17509958, 0.1579206 ])
 New operator functions — such as
qml.simplify
— make working with operator arithmetic even easier:
>>> op = qml.adjoint(qml.adjoint(qml.RX(0.5, wires=0)))
>>> qml.simplify(op)
RX(0.5, wires=[0])
Each of these new functionalities can be used within QNodes as operators or observables, where applicable, while also maintaining differentiability:
dev = qml.device("default.qubit", wires=1)
@qml.qnode(dev)
def circuit(theta):
qml.RX(theta, 0)**2
return qml.expval(qml.PauliZ(0))
>>> theta = np.array([7.89], requires_grad=True)
>>> circuit(theta)
tensor(0.99740648, requires_grad=True)
>>> qml.grad(circuit)(theta)
tensor([0.14394889], requires_grad=True)
Behind the scenes, this is enabled by some new operator functions, qml.op_sum
for taking sums of operators, qml.prod
for operator products, and qml.s_prod
for scalar products of operators. For more details on using these low level arithmetic functions, check out the documentation.
Differentiable error mitigation ⚙
You know what they say: differentiable errors fix your circuit’s noisy terrors.
Elevate any variational quantum algorithm to a mitigated algorithm with improved results on noisy hardware while maintaining differentiability throughout.
In order to do so, use the qml.transforms.mitigate_with_zne
transform on your QNode and provide the PennyLane proprietary
qml.transforms.fold_global
folding function and qml.transforms.poly_extrapolate
extrapolation function.
Here is an example for a noisy simulation device where we mitigate a QNode and are still
able to compute the gradient:
# Describe noise
noise_gate = qml.DepolarizingChannel
noise_strength = 0.1
# Load devices
dev_ideal = qml.device("default.mixed", wires=n_wires)
dev_noisy = qml.transforms.insert(noise_gate, noise_strength)(dev_ideal)
scale_factors = [1, 2, 3]
@mitigate_with_zne(
scale_factors,
qml.transforms.fold_global,
qml.transforms.poly_extrapolate,
extrapolate_kwargs={'order': 2}
)
@qml.qnode(dev_noisy)
def qnode_mitigated(theta):
qml.RY(theta, wires=0)
return qml.expval(qml.PauliX(0))
>>> theta = np.array(0.5, requires_grad=True)
>>> qml.grad(qnode_mitigated)(theta)
0.5712737447327619
More native support for parameter broadcasting 📡
The introduction of parameter broadcasting last release was a huge hit 🏏. In this release, we have exciting additions to an alreadyuseful feature.
default.qubit
now natively supports parameter broadcasting, providing
increased performance when executing the same circuit at various parameter positions
compared to manually looping over parameters.
dev = qml.device("default.qubit", wires=1)
@qml.qnode(dev)
def circuit(x):
qml.RX(x, wires=0)
return qml.expval(qml.PauliZ(0))
>>> circuit(np.array([0.1, 0.3, 0.2]))
tensor([0.99500417, 0.95533649, 0.98006658], requires_grad=True)
In addition, parametershift gradients now allow for parameter broadcasting internally,
which can result in a significant speedup when computing gradients of
circuits with many parameters. qml.gradients.param_shift
now accepts the keyword argument
broadcast
. If set to True
, broadcasting is used to compute the derivative:
dev = qml.device("default.qubit", wires=2)
@qml.qnode(dev)
def circuit(x, y):
qml.RX(x, wires=0)
qml.RY(y, wires=1)
return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
>>> x = np.array([np.pi/3, np.pi/2], requires_grad=True)
>>> y = np.array([np.pi/6, np.pi/5], requires_grad=True)
>>> qml.gradients.param_shift(circuit, broadcast=True)(x, y)
(tensor([[0.7795085, 0. ],
[ 0. , 0.7795085]], requires_grad=True),
tensor([[0.125, 0. ],
[0. , 0.125]], requires_grad=True))
The following simpler example also makes use of broadcasting:
@qml.qnode(dev, diff_method="parametershift", broadcast=True)
def circuit(x, y):
qml.RX(x, wires=0)
qml.RY(y, wires=1)
return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
>>> x = np.array(0.1, requires_grad=True)
>>> y = np.array(0.4, requires_grad=True)
>>> qml.grad(circuit)(x, y)
(array(0.09195267), array(0.38747287))
Here, only two circuits are created internally, compared to four with broadcast=False
.
Check out this speedup!
Finally, quantum chemistry operations and templates have also been updated to support parameter broadcasting.
>>> op = qml.SingleExcitation(np.array([0.3, 1.2, 0.7]), wires=[0, 1])
>>> op.matrix().shape
(3, 4, 4)
Allnew measurements ✨
We’re upping our measurement game with new ergonomic features.
Counts
QNodes with shots != None
that return qml.counts
will yield a dictionary
whose keys are bitstrings representing computational basis states that were measured,
and whose values are the corresponding counts (i.e., how many times that computational
basis state was measured):
dev = qml.device("default.qubit", wires=2, shots=1000)
@qml.qnode(dev)
def circuit():
qml.Hadamard(wires=0)
qml.CNOT(wires=[0, 1])
return qml.counts()
>>> circuit()
{'00': 495, '11': 505}
qml.counts
can also accept observables, where the resulting dictionary is ordered
by the eigenvalues of the observable.
dev = qml.device("default.qubit", wires=2, shots=1000)
@qml.qnode(dev)
def circuit():
qml.Hadamard(wires=0)
qml.CNOT(wires=[0, 1])
return qml.counts(qml.PauliZ(0)), qml.counts(qml.PauliZ(1))
>>> circuit()
({1: 470, 1: 530}, {1: 470, 1: 530})
New return types for QNodes with multiple measurements
Over the next couple of releases, we will be making a change to the behaviour of QNodes that return multiple measurements. This will ensure that PennyLane continues to seamlessly integrate with frameworks such as NumPy and SciPy, and unlock new and exciting features and improvements down the line.
Currently, QNodes that return multiple measurements either return a multidimensional array if the measurement dimension matches,
>>> @qml.qnode(dev)
... def circuit(x):
... qml.RX(x, wires=0)
... return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1))
>>> circuit(0.5)
tensor([0.87758256, 1. ], requires_grad=True)
or a flattened ragged array if the measurement dimensions do not match:
>>> @qml.qnode(dev)
... def circuit(x):
... qml.RX(x, wires=0)
... return qml.expval(qml.PauliZ(0)), qml.probs(wires=[0, 1])
>>> circuit(0.5)
tensor([0.87758256, 0.93879128, 0. , 0.06120872, 0. ], requires_grad=True)
To match consistency with other scientific Python packages, we will be moving to having multiple measurements be represented by a tuple of tensors, rather than a stacked or flattened array, over the next couple of releases.
This new behaviour is experimental and off by default, but can be tested
today by using the new qml.enable_return()
function:
>>> qml.enable_return()
>>> @qml.qnode(dev)
... def circuit(x):
... qml.RX(x, wires=0)
... return qml.expval(qml.PauliZ(0)), qml.probs(wires=[0, 1])
>>> circuit(0.5)
(tensor(0.87758256, requires_grad=True),
tensor([0.93879128, 0. , 0.06120872, 0. ], requires_grad=True))
In addition, the boolean function qml.active_return()
can be queried
to determine if this new behaviour is activated, and disabled via
qml.disable_return()
.
Over the upcoming releases, we will work to enable feature parity with this new return type behaviour, before making it the default.
Improvements 🛠
Get better everyday; that’s our motto.
 When adjoint differentiation is requested, circuits are now decomposed so that all trainable operations have a generator.
 The efficiency of the HartreeFock workflow has been improved by removing repetitive steps.
 The
qml.state
andqml.density_matrix
measurements now support custom wire labels.
lightning.gpu
now has support for multiGPU observable batching using the adjoint differentiation method.
lightning.qubit
added new architecture specific kernel optimizations to ensure the best performance from the getgo.
 Jacobians are now cached with the Autograd interface when using the parametershift rule.
Deprecations and breaking changes 💔
 The deprecated
qml.hf
module is removed. Users with code that callsqml.hf
can simply replaceqml.hf
withqml.qchem
in most cases, or refer to the documentation and demos for more information.

Custom devices inheriting from
DefaultQubit
orQubitDevice
can break due to the introduction of parameter broadcasting.A custom device should only break if all three following statements hold simultaneously:
 The custom device inherits from
DefaultQubit
, notQubitDevice
.  The device implements custom methods in the simulation pipeline that are incompatible
with broadcasting (for example
expval
,apply_operation
oranalytic_probability
).  The custom device maintains the flag
"supports_broadcasting": True
in itscapabilities
dictionary or it overwritesDevice.batch_transform
without applyingbroadcast_expand
(or both).
The
capabilities["supports_broadcasting"]
is set toTrue
forDefaultQubit
. Typically, the easiest fix will be to change thecapabilities["supports_broadcasting"]
flag toFalse
for the child device and/or to include a call tobroadcast_expand
inCustomDevice.batch_transform
, similar to howDevice.batch_transform
calls it.Separately from the above, custom devices that inherit from
QubitDevice
and implement a custom_gather
method need to allow for the kwargaxis
to be passed to this_gather
method.  The custom device inherits from
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:
Ali Asadi, Juan Miguel Arrazola, Utkarsh Azad, Samuel Banning, Prajwal Borkar, Isaac De Vlugt, Olivia Di Matteo, Kristiyan Dilov, Amintor Dusko, David Ittah, Josh Izaac, Soran Jahangiri, Edward Jiang, Ankit Khandelwal, Korbinian Kottmann, Meenu Kumari, Christina Lee, Rashid N. H. M., Sergio MartínezLosa, Albert Mitjans Coma, Ixchel Meza Chavez, Romain Moyard, Lee James O’Riordan, Mudit Pandey, ChaeYeun Park, Bogdan Reznychenko, Shuli Shu, Jay Soni, Modjtaba ShokrianZini, Antal Száva, Trevor Vincent, David Wierichs, Moritz Willmann