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.
# This code block gets replaced with the table of contents. # Documentation: https://www.gatsbyjs.com/plugins/gatsby-remark-table-of-contents/
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 non-Clifford 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 plane-wave 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 double-factorized 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='sto-3g') 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 already-useful 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, parameter-shift 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="parameter-shift", 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)
All-new 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 Hartree-Fock 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 multi-GPU observable batching using the adjoint differentiation method.
lightning.qubit
added new architecture specific kernel optimizations to ensure the best performance from the get-go.- Jacobians are now cached with the Autograd interface when using the parameter-shift 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ínez-Losa, Albert Mitjans Coma, Ixchel Meza Chavez, Romain Moyard, Lee James O’Riordan, Mudit Pandey, Chae-Yeun Park, Bogdan Reznychenko, Shuli Shu, Jay Soni, Modjtaba Shokrian-Zini, Antal Száva, Trevor Vincent, David Wierichs, Moritz Willmann
About the authors
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.
Isaac De Vlugt
My job is to help manage the PennyLane and Catalyst feature roadmap... and spam lots of emojis in the chat 🤠