The latest release of PennyLane is now out and available for everyone to use. It comes with many new additions, including a new graphical circuit drawer, new quantum-aware optimizers, faster performance, smarter circuit decompositions, general hardware gradient support, 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/
Shiny new circuit drawer! π¨ποΈ
The PennyLane circuit drawer has received a makeover π In addition to our existing text-based circuit drawer, you can now draw your QNode in full graphical glory with qml.draw_mpl():
qml.drawer.use_style('black_white') dev = qml.device("default.qubit", wires=4) @qml.qnode(dev) def circuit(x, z): qml.QFT(wires=(0,1,2,3)) qml.Toffoli(wires=(0,1,2)) qml.CSWAP(wires=(0,2,3)) qml.RX(x, wires=0) qml.CRZ(z, wires=(3,0)) return qml.expval(qml.PauliZ(0)) fig, ax = qml.draw_mpl(circuit)(1.2345, 1.2345) fig.show()
For more details and examples, please refer to the qml.draw_mpl documentation
. You can also view all available circuit styles in the qml.drawer module
.
New and improved quantum-aware optimizers π
When it comes to building variational algorithms, designing your quantum circuit and embedding is only half the battle β care needs to also be taken to choose an optimization method that improves the speed of convergence while minimizing the number of quantum evaluations required!
Quantum-aware optimizers generally provide the best of both worlds here; taking into account the geometry of the quantum landscape to both improve convergence while reducing quantum resources.
With this release, PennyLane introduces a brand new quantum-aware optimizer, qml.LieAlgebraOptimizer
, alongside other optimizer improvements.
Perform gradient descent on the special unitary group
Riemannian gradient descent algorithms can be used to optimize a function directly on a Lie group as opposed to on a Euclidean parameter space.
Extending this to QNodes, qml.LieAlgebraOptimizer
is a new quantum-aware Lie Algebra optimizer that allows one to perform gradient descent directly on the special unitary group:
dev = qml.device("default.qubit", wires=2) H = -1.0 * qml.PauliX(0) - qml.PauliZ(1) - qml.PauliY(0) @ qml.PauliX(1) @qml.qnode(dev) def circuit(): qml.RX(0.1, wires=[0]) qml.RY(0.5, wires=[1]) qml.CNOT(wires=[0,1]) qml.RY(0.6, wires=[0]) return qml.expval(H) opt = qml.LieAlgebraOptimizer(circuit=circuit, stepsize=0.1)
Note that, unlike other optimizers, the LieAlgebraOptimizer
accepts a QNode with no parameters, and instead grows the circuit by appending operations during the optimization:
>>> circuit() tensor(-1.3351865, requires_grad=True) >>> circuit1, cost = opt.step_and_cost() >>> circuit1() tensor(-1.99378872, requires_grad=True) >>> qml.draw(circuit1, expansion_strategy='device')() 0: ββRX(0.1)βββCββRY(0.6)βββββββRZ(0.0634)βββRZ(5.55e-18)βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββRZ(0.142)ββββββββββββββββRZ(-0.0787)ββHββββββββββRZ(-0.0787)ββHββRX(1.57)ββRZ(0.164)ββRX(-1.57)ββRX(1.57)βββRZ(0.145)ββRX(-1.57)ββHβββRZ(-0.179)ββHββHββββββββββRZ(-0.0539)ββHββββββββββRX(1.57)βββRZ(0.0787)ββRX(-1.57)ββRX(1.57)βββRZ(0.205)ββRX(-1.57)ββββ€ β¨Hamiltonian(-1, -1, -1)β© 1: ββRY(0.5)βββ°XββRZ(2.78e-17)βββββββββββββββ°RZ(5.55e-18)ββHββRZ(-3.05e-17)ββHββRX(1.57)ββRZ(-0.0959)ββRX(-1.57)ββHβββ°RZ(0.142)ββHββRX(1.57)βββ°RZ(-0.0787)ββRX(-1.57)βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ°RZ(0.145)ββHββββββββββββββ°RZ(-0.179)ββHββRX(1.57)βββ°RZ(-0.0539)ββRX(-1.57)ββHββββββββββ°RZ(0.0787)ββHββββββββββRX(1.57)βββ°RZ(0.205)ββRX(-1.57)βββ°β€ β¨Hamiltonian(-1, -1, -1)β©
For more details, see the qml.LieAlgebraOptimizer
documentation.
Improved quantum natural gradient support
The qml.metric_tensor
transform, used to perform quantum natural gradient optimization via qml.QNGOptimizer
, can now be used to compute the full tensor on hardware, beyond the block diagonal approximation.
This is performed using a combination of Hadamard tests and covariance matrix computations, minimizing the number of quantum executions required, while requiring an additional wire on the device.
dev = qml.device("default.qubit", wires=3) @qml.qnode(dev) def circuit(weights): qml.RX(weights[0], wires=0) qml.RY(weights[1], wires=0) qml.CNOT(wires=[0, 1]) qml.RZ(weights[2], wires=1) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) weights = np.array([0.2, 1.2, -0.9], requires_grad=True) >>> qml.metric_tensor(circuit)(weights) [[ 0.25 0. -0.23300977] [ 0. 0.24013262 0.01763859] [-0.23300977 0.01763859 0.21846983]] >>> print(qml.draw(qml.metric_tensor(circuit))(weights)) 0: ββHββββ€ Probs 1: ββββββ°β€ Probs 0: ββRX(0.2)ββZββSββHββββ€ Probs 1: βββββββββββββββββββββ°β€ Probs 0: ββRX(0.2)ββRY(1.2)βββCββββ€ Probs 1: βββββββββββββββββββββ°Xβββ°β€ Probs 0: ββββββXββRX(0.2)βββYβββ€ 2: ββHβββ°Cββββββββββββ°Cβββ€ β¨Xβ© 0: ββββββXββRX(0.2)ββRY(1.2)βββCβββββββ€ 1: ββββββββββββββββββββββββββββ°XβββZβββ€ 2: ββHβββ°Cβββββββββββββββββββββββββ°Cβββ€ β¨Xβ© 0: ββRX(0.2)βββYββRY(1.2)βββCβββββββ€ 1: βββββββββββββββββββββββββ°XβββZβββ€ 2: ββHβββββββββ°Cββββββββββββββββ°Cβββ€ β¨Xβ©
Here, three 2-wire circuits have been used to compute the block-diagonal metric tensor elements, with two 3-wire circuits required to compute the off-block-diagonal elements.
As always, the full metric tensor remains fully differentiable:
>>> def cost(weights): ... mt = qml.metric_tensor(circuit)(weights) ... return np.linalg.norm(mt) >>> cost(weights) 0.5264048745278436 >>> qml.grad(cost)(weights) array([-0.03351384, 0.1444742 , 0. ])
Characterize your quantum models with classical QNode reconstruction π¨βπ¬
Being able to characterize your variational circuits provides valuable insight when building quantum models, and the qml.fourier
module provides a smorgasbord of introspection.
New to this release is qml.fourier.reconstruct
, which returns a classical function that exactly reconstructs a QNode along a specified parameter dimension, by sampling the original QNode an optimum number of times.
dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(x, y): qml.RX(x, wires=0) qml.RY(y[0], wires=0) qml.RY(y[1], wires=1) qml.CNOT(wires=[0, 1]) qml.RY(y[1], wires=1) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
We can use qml.fourier.qnode_spectrum
to compute the frequency spectrum of this QNode with respect to argument x, and use this to compute the classical reconstruction:
>>> x = 0.3 >>> y = np.array([0.1, -0.9]) >>> spectra = qml.fourier.qnode_spectrum(circuit, encoding_args={"x"})(x, y) >>> univariate_x = qml.fourier.reconstruct(circuit, spectra=spectra)(x, y)["x"][()]
The returned reconstruction is exact and purely classical, and can be evaluated and differentiated without any quantum executions.
>>> circuit(x + 0.4, y) # will evaluate the quantum device -0.08056443 >>> univariate_x(x + 0.4) # purely classical, no quantum executions -0.0805644294814271
For more details on usage, reconstruction cost and differentiability support, please see the qml.fourier.reconstruct
docstring.
Faster performance with optimized quantum workflows π
The QNode has been re-written from the ground up, to support batch execution across the board, custom gradients, better decomposition strategies, and higher-order derivatives.
Note that the old QNode remains accessible at @qml.qnode_old.qnode
, however this will be removed in the next release.
Batch execute quantum circuits
Internally, if multiple circuits are generated for simultaneous execution, they will be packaged into a single job for execution on the device. This can lead to significant performance improvement when executing the QNode on remote quantum hardware or simulator devices with parallelization capabilities.
nth order derivatives on hardware
Arbitrary n-th order derivatives are supported on hardware using gradient transforms such as the parameter-shift rule. To specify that an n-th order derivative of a QNode will be computed, the max_diff
argument should be set. By default, this is set to 1 (first-order derivatives only).
Increasing this value allows for higher order derivatives to be extracted, at the cost of additional (classical) computational overhead during the backwards pass.
Smarter circuit decomposition strategies
When decomposing the circuit, the default decomposition strategy expansion_strategy="gradient"
will prioritize decompositions that result in the smallest number of parametrized operations required to satisfy the differentiation method.
While this may lead to a slight increase in classical processing, it significantly reduces the number of circuit evaluations needed to compute gradients of complicated unitaries.
To return to the old behaviour, expansion_strategy="device"
can be specified.
Support for TensorFlow AutoGraph mode with quantum hardware
It is now possible to use TensorFlowβs AutoGraph mode with QNodes on all devices and with arbitrary differentiation methods. Previously, AutoGraph mode only supported diff_method="backprop"
. This will result in significantly more performant model execution, at the cost of a more expensive initial compilation.
Use AutoGraph to convert your QNodes or cost functions into TensorFlow graphs by decorating them with @tf.function
:
dev = qml.device("lightning.qubit", wires=2) @qml.qnode(dev, diff_method="adjoint", interface="tf", max_diff=1) def circuit(x): qml.RX(x[0], wires=0) qml.RY(x[1], wires=1) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)), qml.expval(qml.PauliZ(0)) @tf.function def cost(x): return tf.reduce_sum(circuit(x)) x = tf.Variable([0.5, 0.7], dtype=tf.float64) with tf.GradientTape() as tape: loss = cost(x) grad = tape.gradient(loss, x)
The initial execution may take slightly longer than when executing the circuit in eager mode; this is because TensorFlow is tracing the function to create the graph. Subsequent executions will be much more performant.
Note that using AutoGraph with backprop-enabled devices, such as default.qubit
, will yield the best performance.
For more details, please see the TensorFlow AutoGraph documentation.
Hardware gradients of arbitrary operations π£
The parameter-shift rule is used in PennyLane to support analytic derivatives of QNodes on hardware. However, up to now, it has only supported a limited gate set, including single-qubit rotations and controlled rotations, necessitating lengthy decompositions when working with high-level templates and ansΓ€tze.
With this release, we introduce qml.gradients.generate_shift_rule
for computing parameter-shift rules for arbitrary operations; this avoids the need for costly decompositions, and can significantly reduce the number of quantum executions required.
Given an operator of the form U=e^{iHt}, H = \sum_i a_i h_i, where the eigenvalues of H are known and all h_i commute, we can compute the frequencies (the unique positive differences of any two eigenvalues) using qml.gradients.eigvals_to_frequencies
.
For example, consider the case where H
has eigenspectrum (-1, 0, 1)
:
>>> frequencies = qml.gradients.eigvals_to_frequencies((-1, 0, 1)) >>> frequencies (1, 2)
qml.gradients.generate_shift_rule
can then be used to compute parameter shift rules to compute \partial^n f(t) using shifted cost function evaluations:
>>> coeffs, shifts = qml.gradients.generate_shift_rule(frequencies, order=1) >>> coeffs array([ 0.85355339, -0.85355339, -0.14644661, 0.14644661]) >>> shifts array([ 0.78539816, -0.78539816, 2.35619449, -2.35619449])
This becomes cheaper than the standard application of the chain rule and two-term shift rule when the number of frequencies is less than the number of Pauli words in the generator.
For more details, including for generating n
th order partial derivatives, see the documentation for generate_shift_rule
and generate_multi_shift_rule
.
Define device-specific custom decompositions πΌ
By passing the custom_decomps
keyword argument when loading a device, custom operation decompositions can be registered with the device:
def custom_cnot(wires): qml.Hadamard(wires=wires[1]) qml.CZ(wires=[wires[0], wires[1]]) qml.Hadamard(wires=wires[1]) def custom_hadamard(wires): qml.RZ(np.pi, wires=wires) qml.RY(np.pi / 2, wires=wires) custom_decomps = {qml.CNOT : custom_cnot, "Hadamard" : custom_hadamard} dev = qml.device("default.qubit", wires=3, custom_decomps=custom_decomps)
QNodes executed with this device will attempt to satisfy both the custom decomposition and the native device gate set, if possible:
>>> @qml.qnode(dev) >>> def circuit(weights): ... qml.BasicEntanglerLayers(weights, wires=[0, 1, 2]) ... return qml.expval(qml.PauliZ(0)) >>> weights = np.array([[0.4, 0.5, 0.6]]) >>> print(qml.draw(circuit, expansion_strategy="device")(weights)) 0: ββRX(0.4)βββββββββββββββββββββββCββRZ(3.14)ββRY(1.57)βββββββββββββββββββββββββββZββRZ(3.14)ββRY(1.57)βββ€ β¨Zβ© 1: ββRX(0.5)ββRZ(3.14)ββRY(1.57)βββ°ZββRZ(3.14)ββRY(1.57)βββCβββββββββββββββββββββββββββββββββββββββββββββββ€ 2: ββRX(0.6)ββRZ(3.14)ββRY(1.57)βββββββββββββββββββββββββββ°ZββRZ(3.14)ββRY(1.57)βββ°Cβββββββββββββββββββββββ€
A separate context manager, qml.transforms.set_decomposition
, is also available to enable application of custom decompositions on devices that have already been created.
New operations, templates, and transforms π€
Alongside the great new features above, we also have a ton of new operations, templates, and transforms to share. These include:
qml.CommutingEvolution
: A circuit template for time evolution under a commuting Hamiltonian. This template utilizes generalized parameter-shift rules to greatly minimize the number of shifted circuits that must be evaluated for gradient computation.qml.Barrier
: Useful for separating blocks of operations during compilation, or for visualizing quantum circuits.qml.QubitDensityMatrix
: Allows for initialization of mixed states when using mixed state simulators.qml.PauliError
: Pauli operator error channel for an arbitrary number of qubits.qml.ThermalRelaxationError
: A thermal relaxation error channel.qml.transforms.merge_amplitude_embedding
: Automatically merge all amplitude embeddings in a QNode into a single embedding.qml.transforms.undo_swaps
: Automatically remove all SWAP operations within a QNode by permuting the wires.
Improvements
In addition to the new features listed above, the release contains a wide array of improvements and optimizations:
- Methods
atomic_orbital
andmolecular_orbital
have been added to theqml.hf.Molecule
class for computing the values of atomic and molecular orbitals at a given position. - The PennyLane
qchem
package is now lazily imported; it will only be imported the first time it is accessed. - More templates now support the
@qml.batch_params
decorator, allowing them to be evaluated on hardware with parameters that include a batch dimension. These includeqml.AngleEmbedding
,qml.BasicEntanglerLayers
, andqml.MottonenStatePreparation
. - The text circuit drawer
qml.draw()
now supports amax_length
argument to help prevent text overflows when printing a circuit in a terminal. qml.Identity
can now be used as a circuit operation, and is no longer restricted to measurements. For the full list of improvements, please refer to the full release notes.
Breaking changes
As new things are added, outdated features are removed. Hereβs what will be disappearing in this release.
- The
qml.template
decorator has been removed. If the list of operations needs to be extracted from a quantum function, please use theqml.tape.QuantumTape
context manager instead. - The deprecated
default.tensor
anddefault.tensor.tf
experimental devices have been removed. - The
qml.fourier.spectrum
function has been removed (split intoqml.fourier.qnode_spectrum
andqml.fourier.circuit_spectrum
). - The
diag_approx
keyword argument ofqml.metric_tensor
andqml.QNGOptimizer
has been removed. Theapprox="block-diag"|"diag"|None
keyword argument should be used instead. - The
qml.init
module, which contains functions to generate random parameters for templates, has been removed. Instead, the templates provide ashape()
method. - The
par_domain
attribute in the operator class was no longer used internally, and has been removed. - The
mutable
keyword argument has been removed from the QNode, due to underlying buggy behaviour resulting in incorrect numerical results being returned during evaluation. - The reversible QNode differentiation method has been temporarily removed.
- The
DiagonalOperation
subclass of Operator has been removed. Instead, devices can check for the diagonal property using attributes; op inqml.ops.qubit.attributes
.diagonal_in_z_basis
. Custom operations can be added to this attribute at runtime viadiagonal_in_z_basis.add("MyCustomOp")
.
Other breaking changes include:
- When drawing QNodes, the default behaviour is to expand all operations to satisfy the gradient method. The old behaviour β drawing QNodes using the native device gate set β can be returned using
qml.draw(circuit, expansion_strategy="device")
. - By default, QNodes only support first derivatives. Second (and higher) derivative support can be activated by passing
max_diff=2
to the QNode decorator. - The default behaviour of the
qml.metric_tensor
transform has been modified: By default, the full metric tensor is computed, leading to higher cost than the previous default of computing the block diagonal only. At the same time, the Hadamard tests for the full metric tensor require an additional wire on the device, so thatqml.metric_tensor(some_qnode)(weights)
will revert back to the block diagonal restriction and raise a warning if the used device does not have an additional wire. - The
num_params
attribute in the operator class is now dynamic. This makes it easier to define operator subclasses with a flexible number of parameters. QuantumTape.trainable_params
now is a list instead of a set.
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:
Catalina Albornoz, Guillermo Alonso-Linaje, Juan Miguel Arrazola, Ali Asadi, Utkarsh Azad, Samuel Banning, Benjamin Cordier, Alain Delgado, Olivia Di Matteo, Anthony Hayes, David Ittah, Josh Izaac, Soran Jahangiri, Jalani Kanem, Ankit Khandelwal, Nathan Killoran, Shumpei Kobayashi, Robert Lang, Christina Lee, Cedric Lin, Alejandro Montanez, Romain Moyard, Lee James OβRiordan, Chae-Yeun Park, Isidor Schoch, Maria Schuld, Jay Soni, Antal SzΓ‘va, Rodrigo Vargas, David Wierichs, Roeland Wiersema, Moritz Willmann.
About the author
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.