# Release notesΒΆ

This page contains the release notes for PennyLane.

- orphan

## Release 0.20.0 (current release)ΒΆ

### New features since last release

#### Shiny new circuit drawer!π¨ποΈ

PennyLane now supports drawing a QNode with matplotlib! (#1803) (#1811) (#1931) (#1954)

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()

#### New and improved quantum-aware optimizers

Added

`qml.LieAlgebraOptimizer`

, a new quantum-aware Lie Algebra optimizer that allows one to perform gradient descent on the special unitary group. (#1911)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)

For more details, see the LieAlgebraOptimizer documentation.

The

`qml.metric_tensor`

transform can now be used to compute the full tensor, beyond the block diagonal approximation. (#1725)This is performed using Hadamard tests, and requires an additional wire on the device to execute the circuits produced by the transform, as compared to the number of wires required by the original circuit. The transform defaults to computing the full tensor, which can be controlled by the

`approx`

keyword argument.As an example, consider the QNode

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)

Then we can compute the (block) diagonal metric tensor as before, now using the

`approx="block-diag"`

keyword:>>> qml.metric_tensor(circuit, approx="block-diag")(weights) [[0.25 0. 0. ] [0. 0.24013262 0. ] [0. 0. 0.21846983]]

Instead, we now can also compute the full metric tensor, using Hadamard tests on the additional wire of the device:

>>> qml.metric_tensor(circuit)(weights) [[ 0.25 0. -0.23300977] [ 0. 0.24013262 0.01763859] [-0.23300977 0.01763859 0.21846983]]

See the metric tensor documentation. for more information and usage details.

#### Faster performance with optimized quantum workflows

The QNode has been re-written to support batch execution across the board, custom gradients, better decomposition strategies, and higher-order derivatives. (#1807) (#1969)

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.

Custom gradient transforms can be specified as the differentiation method:

@qml.gradients.gradient_transform def my_gradient_transform(tape): ... return tapes, processing_fn @qml.qnode(dev, diff_method=my_gradient_transform) def circuit():

For breaking changes related to the use of the new QNode, refer to the Breaking Changes section.

Note that the old QNode remains accessible at

`@qml.qnode_old.qnode`

, however this will be removed in the next release.Custom decompositions can now be applied to operations at the device level. (#1900)

For example, suppose we would like to implement the following QNode:

def circuit(weights): qml.BasicEntanglerLayers(weights, wires=[0, 1, 2]) return qml.expval(qml.PauliZ(0)) original_dev = qml.device("default.qubit", wires=3) original_qnode = qml.QNode(circuit, original_dev)

>>> weights = np.array([[0.4, 0.5, 0.6]]) >>> print(qml.draw(original_qnode, expansion_strategy="device")(weights)) 0: ββRX(0.4)βββCβββββββXβββ€ β¨Zβ© 1: ββRX(0.5)βββ°XβββCβββββββ€ 2: ββRX(0.6)βββββββ°Xβββ°Cβββ€

Now, letβs swap out the decomposition of the

`CNOT`

gate into`CZ`

and`Hadamard`

, and furthermore the decomposition of`Hadamard`

into`RZ`

and`RY`

rather than the decomposition already available in PennyLane. We define the two decompositions like so, and pass them to a device:def custom_cnot(wires): return [ qml.Hadamard(wires=wires[1]), qml.CZ(wires=[wires[0], wires[1]]), qml.Hadamard(wires=wires[1]) ] def custom_hadamard(wires): return [ qml.RZ(np.pi, wires=wires), qml.RY(np.pi / 2, wires=wires) ] # Can pass the operation itself, or a string custom_decomps = {qml.CNOT : custom_cnot, "Hadamard" : custom_hadamard} decomp_dev = qml.device("default.qubit", wires=3, custom_decomps=custom_decomps) decomp_qnode = qml.QNode(circuit, decomp_dev)

Now when we draw or run a QNode on this device, the gates will be expanded according to our specifications:

>>> print(qml.draw(decomp_qnode, 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,

`set_decomposition`

, has also been implemented to enable application of custom decompositions on devices that have already been created.>>> with qml.transforms.set_decomposition(custom_decomps, original_dev): ... print(qml.draw(original_qnode, 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βββββββββββββββββββββββ€

Given an operator of the form \(U=e^{iHt}\), where \(H\) has commuting terms and known eigenvalues,

`qml.gradients.generate_shift_rule`

computes the generalized parameter shift rules for determining the gradient of the expectation value \(f(t) = \langle 0|U(t)^\dagger \hat{O} U(t)|0\rangle\) on hardware. (#1788) (#1932)Given

\[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`

.`qml.gradients.generate_shift_rule`

can then be used to compute the parameter shift rules to compute \(f'(t)\) using`2R`

shifted cost function evaluations. This becomes cheaper than the standard application of the chain rule and two-term shift rule when`R`

is less than the number of Pauli words in the generator.For example, consider the case where \(H\) has eigenspectrum

`(-1, 0, 1)`

:>>> frequencies = qml.gradients.eigvals_to_frequencies((-1, 0, 1)) >>> frequencies (1, 2) >>> coeffs, shifts = qml.gradients.generate_shift_rule(frequencies) >>> coeffs array([ 0.85355339, -0.85355339, -0.14644661, 0.14644661]) >>> shifts array([ 0.78539816, -0.78539816, 2.35619449, -2.35619449])

As we can see,

`generate_shift_rule`

returns four coefficients \(c_i\) and shifts \(s_i\) corresponding to a four term parameter shift rule. The gradient can then be reconstructed via:\[\frac{\partial}{\partial\phi}f = \sum_{i} c_i f(\phi + s_i),\]where \(f(\phi) = \langle 0|U(\phi)^\dagger \hat{O} U(\phi)|0\rangle\) for some observable \(\hat{O}\) and the unitary \(U(\phi)=e^{iH\phi}\).

#### 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 support

`diff_method="backprop"`

. This will result in significantly more performant model execution, at the cost of a more expensive initial compilation. (#1866)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.

#### Characterize your quantum models with classical QNode reconstruction

The

`qml.fourier.reconstruct`

function is added. It can be used to reconstruct QNodes outputting expectation values along a specified parameter dimension, with a minimal number of calls to the original QNode. The returned reconstruction is exact and purely classical, and can be evaluated without any quantum executions. (#1864)The reconstruction technique differs for functions with equidistant frequencies that are reconstructed using the function value at equidistant sampling points, and for functions with arbitrary frequencies reconstructed using arbitrary sampling points.

As an example, consider the following QNode:

dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(x, Y, f=1.0): qml.RX(f * x, wires=0) qml.RY(Y[0], wires=0) qml.RY(Y[1], wires=1) qml.CNOT(wires=[0, 1]) qml.RY(3 * Y[1], wires=1) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))

It has three variational parameters overall: A scalar input

`x`

and an array-valued input`Y`

with two entries. Additionally, we can tune the dependence on`x`

with the frequency`f`

. We then can reconstruct the QNode output function with respect to`x`

via>>> x = 0.3 >>> Y = np.array([0.1, -0.9]) >>> rec = qml.fourier.reconstruct(circuit, ids="x", nums_frequency={"x": {0: 1}})(x, Y) >>> rec {'x': {0: <function pennylane.fourier.reconstruct._reconstruct_equ.<locals>._reconstruction(x)>}}

As we can see, we get a nested dictionary in the format of the input

`nums_frequency`

with functions as values. These functions are simple float-to-float callables:>>> univariate = rec["x"][0] >>> univariate(x) -0.880208251507

For more details on usage, reconstruction cost and differentiability support, please see the fourier.reconstruct docstring.

#### State-of-the-art operations and templates

A circuit template for time evolution under a commuting Hamiltonian utilizing generalized parameter shift rules for cost function gradients is available as

`qml.CommutingEvolution`

. (#1788)If the template is handed a frequency spectrum during its instantiation, then

`generate_shift_rule`

is internally called to obtain the general parameter shift rules with respect to`CommutingEvolution`

βs \(t\) parameter, otherwise the shift rule for a decomposition of`CommutingEvolution`

will be used.The template can be initialized within QNode as:

import pennylane as qml n_wires = 2 dev = qml.device('default.qubit', wires=n_wires) coeffs = [1, -1] obs = [qml.PauliX(0) @ qml.PauliY(1), qml.PauliY(0) @ qml.PauliX(1)] hamiltonian = qml.Hamiltonian(coeffs, obs) frequencies = (2,4) @qml.qnode(dev) def circuit(time): qml.PauliX(0) qml.CommutingEvolution(hamiltonian, time, frequencies) return qml.expval(qml.PauliZ(0))

Note that there is no internal validation that 1) the input

`qml.Hamiltonian`

is fully commuting and 2) the eigenvalue frequency spectrum is correct, since these checks become prohibitively expensive for large Hamiltonians.The

`qml.Barrier()`

operator has been added. With it we can separate blocks in compilation or use it as a visual tool. (#1844)Added the identity observable to be an operator. Now we can explicitly call the identity operation on our quantum circuits for both qubit and CV devices. (#1829)

Added the

`qml.QubitDensityMatrix`

initialization gate for mixed state simulation. (#1850)A thermal relaxation channel is added to the Noisy channels. The channel description can be found on the supplementary information of Quantum classifier with tailored quantum kernels. (#1766)

Added a new

`qml.PauliError`

channel that allows the application of an arbitrary number of Pauli operators on an arbitrary number of wires. (#1781)

#### Manipulate QNodes to your β€οΈs content with new transforms

The

`merge_amplitude_embedding`

transformation has been created to automatically merge all gates of this type into one. (#1933)from pennylane.transforms import merge_amplitude_embedding dev = qml.device("default.qubit", wires = 3) @qml.qnode(dev) @merge_amplitude_embedding def qfunc(): qml.AmplitudeEmbedding([0,1,0,0], wires = [0,1]) qml.AmplitudeEmbedding([0,1], wires = 2) return qml.expval(qml.PauliZ(wires = 0))

>>> print(qml.draw(qnode)()) 0: βββAmplitudeEmbedding(M0)βββ€ β¨Zβ© 1: βββAmplitudeEmbedding(M0)βββ€ 2: βββ°AmplitudeEmbedding(M0)βββ€ M0 = [0.+0.j 0.+0.j 0.+0.j 1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]

The

`undo_swaps`

transformation has been created to automatically remove all swaps of a circuit. (#1960)dev = qml.device('default.qubit', wires=3) @qml.qnode(dev) @qml.transforms.undo_swaps def qfunc(): qml.Hadamard(wires=0) qml.PauliX(wires=1) qml.SWAP(wires=[0,1]) qml.SWAP(wires=[0,2]) qml.PauliY(wires=0) return qml.expval(qml.PauliZ(0))

>>> print(qml.draw(qfunc)()) 0: ββYβββ€ β¨Zβ© 1: ββHβββ€ 2: ββXβββ€

### Improvements

Added functions for computing the values of atomic and molecular orbitals at a given position. (#1867)

The functions

`atomic_orbital`

and`molecular_orbital`

can be used, as shown in the following codeblock, to evaluate the orbitals. By generating values of the orbitals at different positions, one can plot the spatial shape of a desired orbital.symbols = ['H', 'H'] geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]], requires_grad = False) mol = hf.Molecule(symbols, geometry) hf.generate_scf(mol)() ao = mol.atomic_orbital(0) mo = mol.molecular_orbital(1)

>>> print(ao(0.0, 0.0, 0.0)) >>> print(mo(0.0, 0.0, 0.0)) 0.6282468778183719 0.018251285973461928

Added support for Python 3.10. (#1964)

The execution of QNodes that have

multiple return types;

a return type other than Variance and Expectation

now raises a descriptive error message when using the JAX interface. (#2011)

The PennyLane

`qchem`

package is now lazily imported; it will only be imported the first time it is accessed. (#1962)`qml.math.scatter_element_add`

now supports adding multiple values at multiple indices with a single function call, in all interfaces (#1864)For example, we may set five values of a three-dimensional tensor in the following way:

>>> X = tf.zeros((3, 2, 9), dtype=tf.float64) >>> indices = [(0, 0, 1, 2, 2), (0, 0, 0, 0, 1), (1, 3, 8, 6, 7)] >>> values = [1 * i for i in range(1,6)] >>> qml.math.scatter_element_add(X, indices, values) <tf.Tensor: shape=(3, 2, 9), dtype=float64, numpy= array([[[0., 1., 0., 2., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0.]], [[0., 0., 0., 0., 0., 0., 0., 0., 3.], [0., 0., 0., 0., 0., 0., 0., 0., 0.]], [[0., 0., 0., 0., 0., 0., 4., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 5., 0.]]])>

All instances of

`str.format`

have been replace with f-strings. (#1970)Tests do not loop over automatically imported and instantiated operations any more, which was opaque and created unnecessarily many tests. (#1895)

A

`decompose()`

method has been added to the`Operator`

class such that we can obtain (and queue) decompositions directly from instances of operations. (#1873)>>> op = qml.PhaseShift(0.3, wires=0) >>> op.decompose() [RZ(0.3, wires=[0])]

`qml.circuit_drawer.tape_mpl`

produces a matplotlib figure and axes given a tape. (#1787)The

`AngleEmbedding`

,`BasicEntanglerLayers`

and`MottonenStatePreparation`

templates now support parameters with batch dimension when using the`@qml.batch_params`

decorator. (#1812) (#1883) (#1893)`qml.draw`

now supports a`max_length`

argument to help prevent text overflows when printing circuits. (#1892)`Identity`

operation is now part of both the`ops.qubit`

and`ops.cv`

modules. (#1956)

### Breaking changes

The QNode has been re-written to support batch execution across the board, custom gradients, better decomposition strategies, and higher-order derivatives. (#1807) (#1969)

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.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.

Note that the old QNode remains accessible at

`@qml.qnode_old.qnode`

, however this will be removed in the next release.Certain features deprecated in

`v0.19.0`

have been removed: (#1981) (#1963)The

`qml.template`

decorator (use a ` QuantumTape <https://pennylane.readthedocs.io/en/stable/code/api/pennylane.tape.QuantumTape.html>`_ as a context manager to record operations and its`operations`

attribute to return them, see the linked page for examples);The

`default.tensor`

and`default.tensor.tf`

experimental devices;The

`qml.fourier.spectrum`

function (use the`qml.fourier.circuit_spectrum`

or`qml.fourier.qnode_spectrum`

functions instead);The

`diag_approx`

keyword argument of`qml.metric_tensor`

and`qml.QNGOptimizer`

(pass`approx='diag'`

instead).

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 that>>> qml.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. (#1725)

The

`circuit_drawer`

module has been renamed`drawer`

. (#1949)The

`par_domain`

attribute in the operator class has been removed. (#1907)The

`mutable`

keyword argument has been removed from the QNode, due to underlying bugs that result in incorrect results being returned from immutable QNodes. This functionality will return in an upcoming release. (#1807)The reversible QNode differentiation method has been removed; the adjoint differentiation method is preferred instead (

`diff_method='adjoint'`

). (#1807)`QuantumTape.trainable_params`

now is a list instead of a set. This means that`tape.trainable_params`

will return a list unlike before, but setting the`trainable_params`

with a set works exactly as before. (#1904)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. (#1898) (#1909)The static method

`decomposition()`

, formerly in the`Operation`

class, has been moved to the base`Operator`

class. (#1873)`DiagonalOperation`

is not a separate subclass any more. (#1889)Instead, devices can check for the diagonal property using attributes:

from pennylane.ops.qubit.attributes import diagonal_in_z_basis if op in diagonal_in_z_basis: # do something

Custom operations can be added to this attribute at runtime via

`diagonal_in_z_basis.add("MyCustomOp")`

.

### Bug fixes

Fixes a bug with

`qml.probs`

when using`default.qubit.jax`

. (#1998)Fixes a bug where output tensors of a QNode would always be put on the default GPU with

`default.qubit.torch`

. (#1982)Device test suite doesnβt use empty circuits so that it can also test the IonQ plugin, and it checks if operations are supported in more places. (#1979)

Fixes a bug where the metric tensor was computed incorrectly when using gates with

`gate.inverse=True`

. (#1987)Corrects the documentation of

`qml.transforms.classical_jacobian`

for the Autograd interface (and improves test coverage). (#1978)Fixes a bug where differentiating a QNode with

`qml.state`

using the JAX interface raised an error. (#1906)Fixes a bug with the adjoint of

`qml.QFT`

. (#1955)Fixes a bug where the

`ApproxTimeEvolution`

template was not correctly computing the operation wires from the input Hamiltonian. This did not affect computation with the`ApproxTimeEvolution`

template, but did cause circuit drawing to fail. (#1952)Fixes a bug where the classical preprocessing Jacobian computed by

`qml.transforms.classical_jacobian`

with JAX returned a reduced submatrix of the Jacobian. (#1948)Fixes a bug where the operations are not accessed in the correct order in

`qml.fourier.qnode_spectrum`

, leading to wrong outputs. (#1935)Fixes several Pylint errors. (#1951)

Fixes a bug where the device test suite wasnβt testing certain operations. (#1943)

Fixes a bug where batch transforms would mutate a QNodes execution options. (#1934)

`qml.draw`

now supports arbitrary templates with matrix parameters. (#1917)`QuantumTape.trainable_params`

now is a list instead of a set, making it more stable in very rare edge cases. (#1904)`ExpvalCost`

now returns corrects results shape when`optimize=True`

with shots batch. (#1897)`qml.circuit_drawer.MPLDrawer`

was slightly modified to work with matplotlib version 3.5. (#1899)`qml.CSWAP`

and`qml.CRot`

now define`control_wires`

, and`qml.SWAP`

returns the default empty wires object. (#1830)The

`requires_grad`

attribute of`qml.numpy.tensor`

objects is now preserved when pickling/unpickling the object. (#1856)Device tests no longer throw warnings about the

`requires_grad`

attribute of variational parameters. (#1913)`AdamOptimizer`

and`AdagradOptimizer`

had small fixes to their optimization step updates. (#1929)Fixes a bug where differentiating a QNode with multiple array arguments via

`qml.gradients.param_shift`

throws an error. (#1989)`AmplitudeEmbedding`

template no longer produces a`ComplexWarning`

when the`features`

parameter is batched and provided as a 2D array. (#1990)`qml.circuit_drawer.CircuitDrawer`

no longer produces an error when attempting to draw tapes inside of circuits (e.g. from decomposition of an operation or manual placement). (#1994)Fixes a bug where using SciPy sparse matrices with the new QNode could lead to a warning being raised about prioritizing the TensorFlow and PyTorch interfaces. (#2001)

Fixed a bug where the

`QueueContext`

was not empty when first importing PennyLane. (#1957)Fixed circuit drawing problem with

`Interferometer`

and`CVNeuralNet`

. (#1953)

### Documentation

Added examples in documentation for some operations. (#1902)

Improves the Developerβs Guide Testing document. (#1896)

Added documentation examples for

`AngleEmbedding`

,`BasisEmbedding`

,`StronglyEntanglingLayers`

,`SqueezingEmbedding`

,`DisplacementEmbedding`

,`MottonenStatePreparation`

and`Interferometer`

. (#1910) (#1908) (#1912) (#1920) (#1936) (#1937)

### Contributors

This release contains contributions from (in alphabetical order):

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.

- orphan

## Release 0.19.1ΒΆ

### Bug fixes

Fixes several bugs when using parametric operations with the

`default.qubit.tensor`

device on GPU. The device takes the`torch_device`

argument once again to allow running non-parametric QNodes on the GPU. (#1927)Fixes a bug where using JAXβs jit function on certain QNodes that contain the

`qml.QubitStateVector`

operation raised an error with earlier JAX versions (e.g.,`jax==0.2.10`

and`jaxlib==0.1.64`

). (#1924)

### Contributors

This release contains contributions from (in alphabetical order):

Josh Izaac, Christina Lee, Romain Moyard, Lee James OβRiordan, Antal SzΓ‘va.

- orphan

## Release 0.19.0ΒΆ

### New features since last release

#### Differentiable Hartree-Fock solver

A differentiable Hartree-Fock (HF) solver has been added. It can be used to construct molecular Hamiltonians that can be differentiated with respect to nuclear coordinates and basis-set parameters. (#1610)

The HF solver computes the integrals over basis functions, constructs the relevant matrices, and performs self-consistent-field iterations to obtain a set of optimized molecular orbital coefficients. These coefficients and the computed integrals over basis functions are used to construct the one- and two-body electron integrals in the molecular orbital basis which can be used to generate a differentiable second-quantized Hamiltonian in the fermionic and qubit basis.

The following code shows the construction of the Hamiltonian for the hydrogen molecule where the geometry of the molecule is differentiable.

symbols = ["H", "H"] geometry = np.array([[0.0000000000, 0.0000000000, -0.6943528941], [0.0000000000, 0.0000000000, 0.6943528941]], requires_grad=True) mol = qml.hf.Molecule(symbols, geometry) args_mol = [geometry] hamiltonian = qml.hf.generate_hamiltonian(mol)(*args_mol)

>>> hamiltonian.coeffs tensor([-0.09041082+0.j, 0.17220382+0.j, 0.17220382+0.j, 0.16893367+0.j, 0.04523101+0.j, -0.04523101+0.j, -0.04523101+0.j, 0.04523101+0.j, -0.22581352+0.j, 0.12092003+0.j, -0.22581352+0.j, 0.16615103+0.j, 0.16615103+0.j, 0.12092003+0.j, 0.17464937+0.j], requires_grad=True)

The generated Hamiltonian can be used in a circuit where the atomic coordinates and circuit parameters are optimized simultaneously.

symbols = ["H", "H"] geometry = np.array([[0.0000000000, 0.0000000000, 0.0], [0.0000000000, 0.0000000000, 2.0]], requires_grad=True) mol = qml.hf.Molecule(symbols, geometry) dev = qml.device("default.qubit", wires=4) params = [np.array([0.0], requires_grad=True)] def generate_circuit(mol): @qml.qnode(dev) def circuit(*args): qml.BasisState(np.array([1, 1, 0, 0]), wires=[0, 1, 2, 3]) qml.DoubleExcitation(*args[0][0], wires=[0, 1, 2, 3]) return qml.expval(qml.hf.generate_hamiltonian(mol)(*args[1:])) return circuit for n in range(25): mol = qml.hf.Molecule(symbols, geometry) args = [params, geometry] # initial values of the differentiable parameters g_params = qml.grad(generate_circuit(mol), argnum = 0)(*args) params = params - 0.5 * g_params[0] forces = qml.grad(generate_circuit(mol), argnum = 1)(*args) geometry = geometry - 0.5 * forces print(f'Step: {n}, Energy: {generate_circuit(mol)(*args)}, Maximum Force: {forces.max()}')

In addition, the new Hartree-Fock solver can further be used to optimize the basis set parameters. For details, please refer to the differentiable Hartree-Fock solver documentation.

#### Integration with Mitiq

Error mitigation using the zero-noise extrapolation method is now available through the

`transforms.mitigate_with_zne`

transform. This transform can integrate with the Mitiq package for unitary folding and extrapolation functionality. (#1813)Consider the following noisy device:

noise_strength = 0.05 dev = qml.device("default.mixed", wires=2) dev = qml.transforms.insert(qml.AmplitudeDamping, noise_strength)(dev)

We can mitigate the effects of this noise for circuits run on this device by using the added transform:

from mitiq.zne.scaling import fold_global from mitiq.zne.inference import RichardsonFactory n_wires = 2 n_layers = 2 shapes = qml.SimplifiedTwoDesign.shape(n_wires, n_layers) np.random.seed(0) w1, w2 = [np.random.random(s) for s in shapes] @qml.transforms.mitigate_with_zne([1, 2, 3], fold_global, RichardsonFactory.extrapolate) @qml.beta.qnode(dev) def circuit(w1, w2): qml.SimplifiedTwoDesign(w1, w2, wires=range(2)) return qml.expval(qml.PauliZ(0))

Now, when we execute

`circuit`

, errors will be automatically mitigated:>>> circuit(w1, w2) 0.19113067083636542

#### Powerful new transforms

The unitary matrix corresponding to a quantum circuit can now be generated using the new

`get_unitary_matrix()`

transform. (#1609) (#1786)This transform is fully differentiable across all supported PennyLane autodiff frameworks.

def circuit(theta): qml.RX(theta, wires=1) qml.PauliZ(wires=0) qml.CNOT(wires=[0, 1])

>>> theta = torch.tensor(0.3, requires_grad=True) >>> matrix = qml.transforms.get_unitary_matrix(circuit)(theta) >>> print(matrix) tensor([[ 0.9888+0.0000j, 0.0000+0.0000j, 0.0000-0.1494j, 0.0000+0.0000j], [ 0.0000+0.0000j, 0.0000+0.1494j, 0.0000+0.0000j, -0.9888+0.0000j], [ 0.0000-0.1494j, 0.0000+0.0000j, 0.9888+0.0000j, 0.0000+0.0000j], [ 0.0000+0.0000j, -0.9888+0.0000j, 0.0000+0.0000j, 0.0000+0.1494j]], grad_fn=<MmBackward>) >>> loss = torch.real(torch.trace(matrix)) >>> loss.backward() >>> theta.grad tensor(-0.1494)

Arbitrary two-qubit unitaries can now be decomposed into elementary gates. This functionality has been incorporated into the

`qml.transforms.unitary_to_rot`

transform, and is available separately as`qml.transforms.two_qubit_decomposition`

. (#1552)As an example, consider the following randomly-generated matrix and circuit that uses it:

U = np.array([ [-0.03053706-0.03662692j, 0.01313778+0.38162226j, 0.4101526 -0.81893687j, -0.03864617+0.10743148j], [-0.17171136-0.24851809j, 0.06046239+0.1929145j, -0.04813084-0.01748555j, -0.29544883-0.88202604j], [ 0.39634931-0.78959795j, -0.25521689-0.17045233j, -0.1391033 -0.09670952j, -0.25043606+0.18393466j], [ 0.29599198-0.19573188j, 0.55605806+0.64025769j, 0.06140516+0.35499559j, 0.02674726+0.1563311j ] ]) dev = qml.device('default.qubit', wires=2) @qml.qnode(dev) @qml.transforms.unitary_to_rot def circuit(x, y): qml.QubitUnitary(U, wires=[0, 1]) return qml.expval(qml.PauliZ(wires=0))

If we run the circuit, we can see the new decomposition:

>>> circuit(0.3, 0.4) tensor(-0.81295986, requires_grad=True) >>> print(qml.draw(circuit)(0.3, 0.4)) 0: ββRot(2.78, 0.242, -2.28)βββXββRZ(0.176)ββββCββββββββββββββXββRot(-3.87, 0.321, -2.09)βββ€ β¨Zβ© 1: ββRot(4.64, 2.69, -1.56)ββββ°CββRY(-0.883)βββ°XββRY(-1.47)βββ°CββRot(1.68, 0.337, 0.587)ββββ€

A new transform,

`@qml.batch_params`

, has been added, that makes QNodes handle a batch dimension in trainable parameters. (#1710) (#1761)This transform will create multiple circuits, one per batch dimension. As a result, it is both simulator and hardware compatible.

@qml.batch_params @qml.beta.qnode(dev) def circuit(x, weights): qml.RX(x, wires=0) qml.RY(0.2, wires=1) qml.templates.StronglyEntanglingLayers(weights, wires=[0, 1, 2]) return qml.expval(qml.Hadamard(0))

The

`qml.batch_params`

decorator allows us to pass arguments`x`

and`weights`

that have a batch dimension. For example,>>> batch_size = 3 >>> x = np.linspace(0.1, 0.5, batch_size) >>> weights = np.random.random((batch_size, 10, 3, 3))

If we evaluate the QNode with these inputs, we will get an output of shape

`(batch_size,)`

:>>> circuit(x, weights) tensor([0.08569816, 0.12619101, 0.21122004], requires_grad=True)

The

`insert`

transform has now been added, providing a way to insert single-qubit operations into a quantum circuit. The transform can apply to quantum functions, tapes, and devices. (#1795)The following QNode can be transformed to add noise to the circuit:

dev = qml.device("default.mixed", wires=2) @qml.qnode(dev) @qml.transforms.insert(qml.AmplitudeDamping, 0.2, position="end") def f(w, x, y, z): qml.RX(w, wires=0) qml.RY(x, wires=1) qml.CNOT(wires=[0, 1]) qml.RY(y, wires=0) qml.RX(z, wires=1) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))

Executions of this circuit will differ from the noise-free value:

>>> f(0.9, 0.4, 0.5, 0.6) tensor(0.754847, requires_grad=True) >>> print(qml.draw(f)(0.9, 0.4, 0.5, 0.6)) 0: ββRX(0.9)βββCββRY(0.5)ββAmplitudeDamping(0.2)ββββ€ β¨Z β Zβ© 1: ββRY(0.4)βββ°XββRX(0.6)ββAmplitudeDamping(0.2)βββ°β€ β¨Z β Zβ©

Common tape expansion functions are now available in

`qml.transforms`

, alongside a new`create_expand_fn`

function for easily creating expansion functions from stopping criteria. (#1734) (#1760)`create_expand_fn`

takes the default depth to which the expansion function should expand a tape, a stopping criterion, an optional device, and a docstring to be set for the created function. The stopping criterion must take a queuable object and return a boolean.For example, to create an expansion function that decomposes all trainable, multi-parameter operations:

>>> stop_at = ~(qml.operation.has_multipar & qml.operation.is_trainable) >>> expand_fn = qml.transforms.create_expand_fn(depth=5, stop_at=stop_at)

The created expansion function can be used within a custom transform. Devices can also be provided, producing expansion functions that decompose tapes to support the native gate set of the device.

#### Batch execution of circuits

A new, experimental QNode has been added, that adds support for batch execution of circuits, custom quantum gradient support, and arbitrary order derivatives. This QNode is available via

`qml.beta.QNode`

, and`@qml.beta.qnode`

. (#1642) (#1646) (#1651) (#1804)It differs from the standard QNode in several ways:

Custom gradient transforms can be specified as the differentiation method:

@qml.gradients.gradient_transform def my_gradient_transform(tape): ... return tapes, processing_fn @qml.beta.qnode(dev, diff_method=my_gradient_transform) def circuit():

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).Internally, if multiple circuits are generated for execution simultaneously, 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.

When decomposing the circuit, the default decomposition strategy will prioritize decompositions that result in the smallest number of parametrized operations required to satisfy the differentiation method. Additional decompositions required to satisfy the native gate set of the quantum device will be performed later, by the device at execution time. While this may lead to a slight increase in classical processing, it significantly reduces the number of circuit evaluations needed to compute gradients of complex unitaries.

In an upcoming release, this QNode will replace the existing one. If you come across any bugs while using this QNode, please let us know via a bug report on our GitHub bug tracker.

Currently, this beta QNode does not support the following features:

Non-mutability via the

`mutable`

keyword argumentThe

`reversible`

QNode differentiation methodThe ability to specify a

`dtype`

when using PyTorch and TensorFlow.

It is also not tested with the

`qml.qnn`

module.

#### New operations and templates

Added a new operation

`OrbitalRotation`

, which implements the spin-adapted spatial orbital rotation gate. (#1665)An example circuit that uses

`OrbitalRotation`

operation is:dev = qml.device('default.qubit', wires=4) @qml.qnode(dev) def circuit(phi): qml.BasisState(np.array([1, 1, 0, 0]), wires=[0, 1, 2, 3]) qml.OrbitalRotation(phi, wires=[0, 1, 2, 3]) return qml.state()

If we run this circuit, we will get the following output

>>> circuit(0.1) array([ 0. +0.j, 0. +0.j, 0. +0.j, 0.00249792+0.j, 0. +0.j, 0. +0.j, -0.04991671+0.j, 0. +0.j, 0. +0.j, -0.04991671+0.j, 0. +0.j, 0. +0.j, 0.99750208+0.j, 0. +0.j, 0. +0.j, 0. +0.j])

Added a new template

`GateFabric`

, which implements a local, expressive, quantum-number-preserving ansatz proposed by Anselmetti*et al.*in arXiv:2104.05692. (#1687)An example of a circuit using

`GateFabric`

template is:coordinates = np.array([0.0, 0.0, -0.6614, 0.0, 0.0, 0.6614]) H, qubits = qml.qchem.molecular_hamiltonian(["H", "H"], coordinates) ref_state = qml.qchem.hf_state(electrons=2, orbitals=qubits) dev = qml.device('default.qubit', wires=qubits) @qml.qnode(dev) def ansatz(weights): qml.templates.GateFabric(weights, wires=[0,1,2,3], init_state=ref_state, include_pi=True) return qml.expval(H)

For more details, see the GateFabric documentation.

Added a new template

`kUpCCGSD`

, which implements a unitary coupled cluster ansatz with generalized singles and pair doubles excitation operators, proposed by Joonho Lee*et al.*in arXiv:1810.02327. (#1743)An example of a circuit using

`kUpCCGSD`

template is:coordinates = np.array([0.0, 0.0, -0.6614, 0.0, 0.0, 0.6614]) H, qubits = qml.qchem.molecular_hamiltonian(["H", "H"], coordinates) ref_state = qml.qchem.hf_state(electrons=2, orbitals=qubits) dev = qml.device('default.qubit', wires=qubits) @qml.qnode(dev) def ansatz(weights): qml.templates.kUpCCGSD(weights, wires=[0,1,2,3], k=0, delta_sz=0, init_state=ref_state) return qml.expval(H)

#### Improved utilities for quantum compilation and characterization

The new

`qml.fourier.qnode_spectrum`

function extends the former`qml.fourier.spectrum`

function and takes classical processing of QNode arguments into account. The frequencies are computed per (requested) QNode argument instead of per gate`id`

. The gate`id`

s are ignored. (#1681) (#1720)Consider the following example, which uses non-trainable inputs

`x`

,`y`

and`z`

as well as trainable parameters`w`

as arguments to the QNode.import pennylane as qml import numpy as np n_qubits = 3 dev = qml.device("default.qubit", wires=n_qubits) @qml.qnode(dev) def circuit(x, y, z, w): for i in range(n_qubits): qml.RX(0.5*x[i], wires=i) qml.Rot(w[0,i,0], w[0,i,1], w[0,i,2], wires=i) qml.RY(2.3*y[i], wires=i) qml.Rot(w[1,i,0], w[1,i,1], w[1,i,2], wires=i) qml.RX(z, wires=i) return qml.expval(qml.PauliZ(wires=0)) x = np.array([1., 2., 3.]) y = np.array([0.1, 0.3, 0.5]) z = -1.8 w = np.random.random((2, n_qubits, 3))

This circuit looks as follows:

>>> print(qml.draw(circuit)(x, y, z, w)) 0: ββRX(0.5)ββRot(0.598, 0.949, 0.346)βββRY(0.23)ββRot(0.693, 0.0738, 0.246)ββRX(-1.8)βββ€ β¨Zβ© 1: ββRX(1)ββββRot(0.0711, 0.701, 0.445)ββRY(0.69)ββRot(0.32, 0.0482, 0.437)βββRX(-1.8)βββ€ 2: ββRX(1.5)ββRot(0.401, 0.0795, 0.731)ββRY(1.15)ββRot(0.756, 0.38, 0.38)βββββRX(-1.8)βββ€

Applying the

`qml.fourier.qnode_spectrum`

function to the circuit for the non-trainable parameters, we obtain:>>> spec = qml.fourier.qnode_spectrum(circuit, encoding_args={"x", "y", "z"})(x, y, z, w) >>> for inp, freqs in spec.items(): ... print(f"{inp}: {freqs}") "x": {(0,): [-0.5, 0.0, 0.5], (1,): [-0.5, 0.0, 0.5], (2,): [-0.5, 0.0, 0.5]} "y": {(0,): [-2.3, 0.0, 2.3], (1,): [-2.3, 0.0, 2.3], (2,): [-2.3, 0.0, 2.3]} "z": {(): [-3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0]}

We can see that all three parameters in the QNode arguments

`x`

and`y`

contribute the spectrum of a Pauli rotation`[-1.0, 0.0, 1.0]`

, rescaled with the prefactor of the respective parameter in the circuit. The three`RX`

rotations using the parameter`z`

accumulate, yielding a more complex frequency spectrum.For details on how to control for which parameters the spectrum is computed, a comparison to

`qml.fourier.circuit_spectrum`

, and other usage details, please see the fourier.qnode_spectrum docstring.Two new methods were added to the Device API, allowing PennyLane devices increased control over circuit decompositions. (#1651)

`Device.expand_fn(tape) -> tape`

: expands a tape such that it is supported by the device. By default, performs the standard device-specific gate set decomposition done in the default QNode. Devices may overwrite this method in order to define their own decomposition logic.Note that the numerical result after applying this method should remain unchanged; PennyLane will assume that the expanded tape returns exactly the same value as the original tape when executed.

`Device.batch_transform(tape) -> (tapes, processing_fn)`

: preprocesses the tape in the case where the device needs to generate multiple circuits to execute from the input circuit. The requirement of a post-processing function makes this distinct to the`expand_fn`

method above.By default, this method applies the transform

\[\left\langle \sum_i c_i h_i\right\rangle β \sum_i c_i \left\langle h_i \right\rangle\]if

`expval(H)`

is present on devices that do not natively support Hamiltonians with non-commuting terms.

A new class has been added to store operator attributes, such as

`self_inverses`

, and`composable_rotation`

, as a list of operation names. (#1763)A number of such attributes, for the purpose of compilation transforms, can be found in

`ops/qubit/attributes.py`

, but the class can also be used to create your own. For example, we can create a new Attribute,`pauli_ops`

, like so:>>> from pennylane.ops.qubit.attributes import Attribute >>> pauli_ops = Attribute(["PauliX", "PauliY", "PauliZ"])

We can check either a string or an Operation for inclusion in this set:

>>> qml.PauliX(0) in pauli_ops True >>> "Hadamard" in pauli_ops False

We can also dynamically add operators to the sets at runtime. This is useful for adding custom operations to the attributes such as

`composable_rotations`

and`self_inverses`

that are used in compilation transforms. For example, suppose you have created a new Operation,`MyGate`

, which you know to be its own inverse. Adding it to the set, like so>>> from pennylane.ops.qubit.attributes import self_inverses >>> self_inverses.add("MyGate")

will enable the gate to be considered by the

`cancel_inverses`

compilation transform if two such gates are adjacent in a circuit.

### Improvements

The

`qml.metric_tensor`

transform has been improved with regards to both function and performance. (#1638) (#1721)If the underlying device supports batch execution of circuits, the quantum circuits required to compute the metric tensor elements will be automatically submitted as a batched job. This can lead to significant performance improvements for devices with a non-trivial job submission overhead.

Previously, the transform would only return the metric tensor with respect to gate arguments, and ignore any classical processing inside the QNode, even very trivial classical processing such as parameter permutation. The metric tensor now takes into account classical processing, and returns the metric tensor with respect to QNode arguments, not simply gate arguments:

>>> @qml.qnode(dev) ... def circuit(x): ... qml.Hadamard(wires=1) ... qml.RX(x[0], wires=0) ... qml.CNOT(wires=[0, 1]) ... qml.RY(x[1] ** 2, wires=1) ... qml.RY(x[1], wires=0) ... return qml.expval(qml.PauliZ(0)) >>> x = np.array([0.1, 0.2], requires_grad=True) >>> qml.metric_tensor(circuit)(x) array([[0.25 , 0. ], [0. , 0.28750832]])

To revert to the previous behaviour of returning the metric tensor with respect to gate arguments,

`qml.metric_tensor(qnode, hybrid=False)`

can be passed.>>> qml.metric_tensor(circuit, hybrid=False)(x) array([[0.25 , 0. , 0. ], [0. , 0.25 , 0. ], [0. , 0. , 0.24750832]])

The metric tensor transform now works with a larger set of operations. In particular, all operations that have a single variational parameter and define a generator are now supported. In addition to a reduction in decomposition overhead, the change also results in fewer circuit evaluations.

The expansion rule in the

`qml.metric_tensor`

transform has been changed. (#1721)If

`hybrid=False`

, the changed expansion rule might lead to a changed output.The

`ApproxTimeEvolution`

template can now be used with Hamiltonians that have trainable coefficients. (#1789)Resulting QNodes can be differentiated with respect to both the time parameter

*and*the Hamiltonian coefficients.dev = qml.device('default.qubit', wires=2) obs = [qml.PauliX(0) @ qml.PauliY(1), qml.PauliY(0) @ qml.PauliX(1)] @qml.qnode(dev) def circuit(coeffs, t): H = qml.Hamiltonian(coeffs, obs) qml.templates.ApproxTimeEvolution(H, t, 2) return qml.expval(qml.PauliZ(0))

>>> t = np.array(0.54, requires_grad=True) >>> coeffs = np.array([-0.6, 2.0], requires_grad=True) >>> qml.grad(circuit)(coeffs, t) (array([-1.07813375, -1.07813375]), array(-2.79516158))

All differentiation methods, including backpropagation and the parameter-shift rule, are supported.

Quantum function transforms and batch transforms can now be applied to devices. Once applied to a device, any quantum function executed on the modified device will be transformed prior to execution. (#1809) (#1810)

dev = qml.device("default.mixed", wires=1) dev = qml.transforms.merge_rotations()(dev) @qml.beta.qnode(dev) def f(w, x, y, z): qml.RX(w, wires=0) qml.RX(x, wires=0) qml.RX(y, wires=0) qml.RX(z, wires=0) return qml.expval(qml.PauliZ(0))

>>> print(f(0.9, 0.4, 0.5, 0.6)) -0.7373937155412453 >>> print(qml.draw(f, expansion_strategy="device")(0.9, 0.4, 0.5, 0.6)) 0: ββRX(2.4)βββ€ β¨Zβ©

It is now possible to draw QNodes that have been transformed by a βbatch transformβ; that is, a transform that maps a single QNode into multiple circuits under the hood. Examples of batch transforms include

`@qml.metric_tensor`

and`@qml.gradients`

. (#1762)For example, consider the parameter-shift rule, which generates two circuits per parameter; one circuit that has the parameter shifted forward, and another that has the parameter shifted backwards:

dev = qml.device("default.qubit", wires=2) @qml.gradients.param_shift @qml.beta.qnode(dev) def circuit(x): qml.RX(x, wires=0) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(wires=0))

>>> print(qml.draw(circuit)(0.6)) 0: ββRX(2.17)βββCβββ€ β¨Zβ© 1: βββββββββββββ°Xβββ€ 0: ββRX(-0.971)βββCβββ€ β¨Zβ© 1: βββββββββββββββ°Xβββ€

Support for differentiable execution of batches of circuits has been extended to the JAX interface for scalar functions, via the beta

`pennylane.interfaces.batch`

module. (#1634) (#1685)For example using the

`execute`

function from the`pennylane.interfaces.batch`

module:from pennylane.interfaces.batch import execute def cost_fn(x): with qml.tape.JacobianTape() as tape1: qml.RX(x[0], wires=[0]) qml.RY(x[1], wires=[1]) qml.CNOT(wires=[0, 1]) qml.var(qml.PauliZ(0) @ qml.PauliX(1)) with qml.tape.JacobianTape() as tape2: qml.RX(x[0], wires=0) qml.RY(x[0], wires=1) qml.CNOT(wires=[0, 1]) qml.probs(wires=1) result = execute( [tape1, tape2], dev, gradient_fn=qml.gradients.param_shift, interface="autograd" ) return (result[0] + result[1][0, 0])[0] res = jax.grad(cost_fn)(params)

All qubit operations have been re-written to use the

`qml.math`

framework for internal classical processing and the generation of their matrix representations. As a result these representations are now fully differentiable, and the framework-specific device classes no longer need to maintain framework-specific versions of these matrices. (#1749) (#1802)The use of

`expval(H)`

, where`H`

is a cost Hamiltonian generated by the`qaoa`

module, has been sped up. This was achieved by making PennyLane decompose a circuit with an`expval(H)`

measurement into subcircuits if the`Hamiltonian.grouping_indices`

attribute is set, and setting this attribute in the relevant`qaoa`

module functions. (#1718)Operations can now have gradient recipes that depend on the state of the operation. (#1674)

For example, this allows for gradient recipes that are parameter dependent:

class RX(qml.RX): @property def grad_recipe(self): # The gradient is given by [f(2x) - f(0)] / (2 sin(x)), by subsituting # shift = x into the two term parameter-shift rule. x = self.data[0] c = 0.5 / np.sin(x) return ([[c, 0.0, 2 * x], [-c, 0.0, 0.0]],)

Shots can now be passed as a runtime argument to transforms that execute circuits in batches, similarly to QNodes. (#1707)

An example of such a transform are the gradient transforms in the

`qml.gradients`

module. As a result, we can now call gradient transforms (such as`qml.gradients.param_shift`

) and set the number of shots at runtime.>>> dev = qml.device("default.qubit", wires=1, shots=1000) >>> @qml.beta.qnode(dev) ... def circuit(x): ... qml.RX(x, wires=0) ... return qml.expval(qml.PauliZ(0)) >>> grad_fn = qml.gradients.param_shift(circuit) >>> param = np.array(0.564, requires_grad=True) >>> grad_fn(param, shots=[(1, 10)]).T array([[-1., -1., -1., -1., -1., 0., -1., 0., -1., 0.]]) >>> param2 = np.array(0.1233, requires_grad=True) >>> grad_fn(param2, shots=None) array([[-0.12298782]])

Templates are now top level imported and can be used directly e.g.

`qml.QFT(wires=0)`

. (#1779)`qml.probs`

now accepts an attribute`op`

that allows to rotate the computational basis and get the probabilities in the rotated basis. (#1692)Refactored the

`expand_fn`

functionality in the Device class to avoid any edge cases leading to failures with plugins. (#1838)Updated the

`qml.QNGOptimizer.step_and_cost`

method to avoid the use of deprecated functionality. (#1834)Added a custom

`torch.to_numpy`

implementation to`pennylane/math/single_dispatch.py`

to ensure compabilitity with PyTorch 1.10. (#1824) (#1825)The default for an

`Operation`

βs`control_wires`

attribute is now an empty`Wires`

object instead of the attribute raising a`NonImplementedError`

. (#1821)`qml.circuit_drawer.MPLDrawer`

will now automatically rotate and resize text to fit inside the rectangle created by the`box_gate`

method. (#1764)Operators now have a

`label`

method to determine how they are drawn. This will eventually override the`RepresentationResolver`

class. (#1678)The operation

`label`

method now supports string variables. (#1815)A new utility class

`qml.BooleanFn`

is introduced. It wraps a function that takes a single argument and returns a Boolean. (#1734)After wrapping,

`qml.BooleanFn`

can be called like the wrapped function, and multiple instances can be manipulated and combined with the bitwise operators`&`

,`|`

and`~`

.There is a new utility function

`qml.math.is_independent`

that checks whether a callable is independent of its arguments. (#1700)This function is experimental and might behave differently than expected.

Note that the test relies on both numerical and analytical checks, except when using the PyTorch interface which only performs a numerical check. It is known that there are edge cases on which this test will yield wrong results, in particular non-smooth functions may be problematic. For details, please refer to the is_indpendent docstring.

The

`qml.beta.QNode`

now supports the`qml.qnn`

module. (#1748)`@qml.beta.QNode`

now supports the`qml.specs`

transform. (#1739)`qml.circuit_drawer.drawable_layers`

and`qml.circuit_drawer.drawable_grid`

process a list of operations to layer positions for drawing. (#1639)`qml.transforms.batch_transform`

now accepts`expand_fn`

s that take additional arguments and keyword arguments. In fact,`expand_fn`

and`transform_fn`

now**must**have the same signature. (#1721)The

`qml.batch_transform`

decorator is now ignored during Sphinx builds, allowing the correct signature to display in the built documentation. (#1733)The tests for qubit operations are split into multiple files. (#1661)

The transform for the Jacobian of the classical preprocessing within a QNode,

`qml.transforms.classical_jacobian`

, now takes a keyword argument`argnum`

to specify the QNode argument indices with respect to which the Jacobian is computed. (#1645)An example for the usage of

`argnum`

is@qml.qnode(dev) def circuit(x, y, z): qml.RX(qml.math.sin(x), wires=0) qml.CNOT(wires=[0, 1]) qml.RY(y ** 2, wires=1) qml.RZ(1 / z, wires=1) return qml.expval(qml.PauliZ(0)) jac_fn = qml.transforms.classical_jacobian(circuit, argnum=[1, 2])

The Jacobian can then be computed at specified parameters.

>>> x, y, z = np.array([0.1, -2.5, 0.71]) >>> jac_fn(x, y, z) (array([-0., -5., -0.]), array([-0. , -0. , -1.98373339]))

The returned arrays are the derivatives of the three parametrized gates in the circuit with respect to

`y`

and`z`

respectively.There also are explicit tests for

`classical_jacobian`

now, which previously was tested implicitly via its use in the`metric_tensor`

transform.For more usage details, please see the classical Jacobian docstring.

A new utility function

`qml.math.is_abstract(tensor)`

has been added. This function returns`True`

if the tensor is*abstract*; that is, it has no value or shape. This can occur if within a function that has been just-in-time compiled. (#1845)`qml.circuit_drawer.CircuitDrawer`

can accept a string for the`charset`

keyword, instead of a`CharSet`

object. (#1640)`qml.math.sort`

will now return only the sorted torch tensor and not the corresponding indices, making sort consistent across interfaces. (#1691)Specific QNode execution options are now re-used by batch transforms to execute transformed QNodes. (#1708)

To standardize across all optimizers,

`qml.optimize.AdamOptimizer`

now also uses`accumulation`

(in form of`collections.namedtuple`

) to keep track of running quantities. Before it used three variables`fm`

,`sm`

and`t`

. (#1757)

### Breaking changes

The operator attributes

`has_unitary_generator`

,`is_composable_rotation`

,`is_self_inverse`

,`is_symmetric_over_all_wires`

, and`is_symmetric_over_control_wires`

have been removed as attributes from the base class. They have been replaced by the sets that store the names of operations with similar properties in`ops/qubit/attributes.py`

. (#1763)The

`qml.inv`

function has been removed,`qml.adjoint`

should be used instead. (#1778)The input signature of an

`expand_fn`

used in a`batch_transform`

now**must**have the same signature as the provided`transform_fn`

, and vice versa. (#1721)The

`default.qubit.torch`

device automatically determines if computations should be run on a CPU or a GPU and doesnβt take a`torch_device`

argument anymore. (#1705)The utility function

`qml.math.requires_grad`

now returns`True`

when using Autograd if and only if the`requires_grad=True`

attribute is set on the NumPy array. Previously, this function would return`True`

for*all*NumPy arrays and Python floats, unless`requires_grad=False`

was explicitly set. (#1638)The operation

`qml.Interferometer`

has been renamed`qml.InterferometerUnitary`

in order to distinguish it from the template`qml.templates.Interferometer`

. (#1714)The

`qml.transforms.invisible`

decorator has been replaced with`qml.tape.stop_recording`

, which may act as a context manager as well as a decorator to ensure that contained logic is non-recordable or non-queueable within a QNode or quantum tape context. (#1754)Templates

`SingleExcitationUnitary`

and`DoubleExcitationUnitary`

have been renamed to`FermionicSingleExcitation`

and`FermionicDoubleExcitation`

, respectively. (#1822)

### Deprecations

Allowing cost functions to be differentiated using

`qml.grad`

or`qml.jacobian`

without explicitly marking parameters as trainable is being deprecated, and will be removed in an upcoming release. Please specify the`requires_grad`

attribute for every argument, or specify`argnum`

when using`qml.grad`

or`qml.jacobian`

. (#1773)The following raises a warning in v0.19.0 and will raise an error in an upcoming release:

import pennylane as qml dev = qml.device('default.qubit', wires=1) @qml.qnode(dev) def test(x): qml.RY(x, wires=[0]) return qml.expval(qml.PauliZ(0)) par = 0.3 qml.grad(test)(par)

Preferred approaches include specifying the

`requires_grad`

attribute:import pennylane as qml from pennylane import numpy as np dev = qml.device('default.qubit', wires=1) @qml.qnode(dev) def test(x): qml.RY(x, wires=[0]) return qml.expval(qml.PauliZ(0)) par = np.array(0.3, requires_grad=True) qml.grad(test)(par)

Or specifying the

`argnum`

argument when using`qml.grad`

or`qml.jacobian`

:import pennylane as qml dev = qml.device('default.qubit', wires=1) @qml.qnode(dev) def test(x): qml.RY(x, wires=[0]) return qml.expval(qml.PauliZ(0)) par = 0.3 qml.grad(test, argnum=0)(par)

The

`default.tensor`

device from the beta folder is no longer maintained and has been deprecated. It will be removed in future releases. (#1851)The

`qml.metric_tensor`

and`qml.QNGOptimizer`

keyword argument`diag_approx`

is deprecated. Approximations can be controlled with the more fine-grained`approx`

keyword argument, with`approx="block-diag"`

(the default) reproducing the old behaviour. (#1721) (#1834)The

`template`

decorator is now deprecated with a warning message and will be removed in release`v0.20.0`

. It has been removed from different PennyLane functions. (#1794) (#1808)The

`qml.fourier.spectrum`

function has been renamed to`qml.fourier.circuit_spectrum`

, in order to clearly separate the new`qnode_spectrum`

function from this one.`qml.fourier.spectrum`

is now an alias for`circuit_spectrum`

but is flagged for deprecation and will be removed soon. (#1681)The

`init`

module, which contains functions to generate random parameter tensors for templates, is flagged for deprecation and will be removed in the next release cycle. Instead, the templatesβ`shape`

method can be used to get the desired shape of the tensor, which can then be generated manually. (#1689)To generate the parameter tensors, the

`np.random.normal`

and`np.random.uniform`

functions can be used (just like in the`init`

module). Considering the default arguments of these functions as of NumPy v1.21, some non-default options were used by the`init`

module:All functions generating normally distributed parameters used

`np.random.normal`

by passing`scale=0.1`

;Most functions generating uniformly distributed parameters (except for certain CVQNN initializers) used

`np.random.uniform`

by passing`high=2*math.pi`

;The

`cvqnn_layers_r_uniform`

,`cvqnn_layers_a_uniform`

,`cvqnn_layers_kappa_uniform`

functions used`np.random.uniform`

by passing`high=0.1`

.

The

`QNode.draw`

method has been deprecated, and will be removed in an upcoming release. Please use the`qml.draw`

transform instead. (#1746)The

`QNode.metric_tensor`

method has been deprecated, and will be removed in an upcoming release. Please use the`qml.metric_tensor`

transform instead. (#1638)The

`pad`

parameter of the`qml.AmplitudeEmbedding`

template has been removed. It has instead been renamed to the`pad_with`

parameter. (#1805)

### Bug fixes

Fixes a bug where

`qml.math.dot`

failed to work with`@tf.function`

autograph mode. (#1842)Fixes a bug where in rare instances the parameters of a tape are returned unsorted by

`Tape.get_parameters`

. (#1836)Fixes a bug with the arrow width in the

`measure`

of`qml.circuit_drawer.MPLDrawer`

. (#1823)The helper functions

`qml.math.block_diag`

and`qml.math.scatter_element_add`

now are entirely differentiable when using Autograd. Previously only indexed entries of the block diagonal could be differentiated, while the derivative w.r.t to the second argument of`qml.math.scatter_element_add`

dispatched to NumPy instead of Autograd. (#1816) (#1818)Fixes a bug such that the original shot vector information of a device is preserved, so that outside the context manager the device remains unchanged. (#1792)

Modifies

`qml.math.take`

to be compatible with a breaking change released in JAX 0.2.24 and ensure that PennyLane supports this JAX version. (#1769)Fixes a bug where the GPU cannot be used with

`qml.qnn.TorchLayer`

. (#1705)Fix a bug where the devices cache the same result for different observables return types. (#1719)

Fixed a bug of the default circuit drawer where having more measurements compared to the number of measurements on any wire raised a

`KeyError`

. (#1702)Fix a bug where it was not possible to use

`jax.jit`

on a`QNode`

when using`QubitStateVector`

. (#1683)The device suite tests can now execute successfully if no shots configuration variable is given. (#1641)

Fixes a bug where the

`qml.gradients.param_shift`

transform would raise an error while attempting to compute the variance of a QNode with ragged output. (#1646)Fixes a bug in

`default.mixed`

, to ensure that returned probabilities are always non-negative. (#1680)Fixes a bug where gradient transforms would fail to apply to QNodes containing classical processing. (#1699)

Fixes a bug where the the parameter-shift method was not correctly using the fallback gradient function when

*all*circuit parameters required the fallback. (#1782)

### Documentation

Adds a link to https://pennylane.ai/qml/demonstrations.html in the navbar. (#1624)

Corrects the docstring of

`ExpvalCost`

by adding`wires`

to the signature of the`ansatz`

argument. (#1715)Updated docstring examples using the

`qchem.molecular_hamiltonian`

function. (#1724)Updates the βGradients and trainingβ quickstart guide to provide information on gradient transforms. (#1751)

All instances of

`qnode.draw()`

have been updated to instead use the transform`qml.draw(qnode)`

. (#1750)Add the

`jax`

interface in QNode Documentation. (#1755)Reorganized all the templates related to quantum chemistry under a common header

`Quantum Chemistry templates`

. (#1822)

### Contributors

This release contains contributions from (in alphabetical order):

Catalina Albornoz, Juan Miguel Arrazola, Utkarsh Azad, Akash Narayanan B, Sam Banning, Thomas Bromley, Jack Ceroni, Alain Delgado, Olivia Di Matteo, Andrew Gardhouse, Anthony Hayes, Theodor Isacsson, David Ittah, Josh Izaac, Soran Jahangiri, Nathan Killoran, Christina Lee, Guillermo Alonso-Linaje, Romain Moyard, Lee James OβRiordan, Carrie-Anne Rubidge, Maria Schuld, Rishabh Singh, Jay Soni, Ingrid Strandberg, Antal SzΓ‘va, Teresa Tamayo-Mendoza, Rodrigo Vargas, Cody Wang, David Wierichs, Moritz Willmann.

- orphan

## Release 0.18.0ΒΆ

### New features since last release

#### PennyLane now comes packaged with `lightning.qubit`

The C++-based lightning.qubit device is now included with installations of PennyLane. (#1663)

The

`lightning.qubit`

device is a fast state-vector simulator equipped with the efficient adjoint method for differentiating quantum circuits, check out the plugin release notes for more details! The device can be accessed in the following way:import pennylane as qml wires = 3 layers = 2 dev = qml.device("lightning.qubit", wires=wires) @qml.qnode(dev, diff_method="adjoint") def circuit(weights): qml.templates.StronglyEntanglingLayers(weights, wires=range(wires)) return qml.expval(qml.PauliZ(0)) weights = qml.init.strong_ent_layers_normal(layers, wires, seed=1967)

Evaluating circuits and their gradients on the device can be achieved using the standard approach:

>>> print(f"Circuit evaluated: {circuit(weights)}") Circuit evaluated: 0.9801286266677633 >>> print(f"Circuit gradient:\n{qml.grad(circuit)(weights)}") Circuit gradient: [[[-9.35301749e-17 -1.63051504e-01 -4.14810501e-04] [-7.88816484e-17 -1.50136528e-04 -1.77922957e-04] [-5.20670796e-17 -3.92874550e-02 8.14523075e-05]] [[-1.14472273e-04 3.85963953e-02 -9.39190132e-18] [-5.76791765e-05 -9.78478343e-02 0.00000000e+00] [ 0.00000000e+00 0.00000000e+00 0.00000000e+00]]]

The adjoint method operates after a forward pass by iteratively applying inverse gates to scan backwards through the circuit. The method is already available in PennyLaneβs

`default.qubit`

device, but the version provided by`lightning.qubit`

integrates with the C++ backend and is more performant, as shown in the plot below:

#### Support for native backpropagation using PyTorch

The built-in PennyLane simulator

`default.qubit`

now supports backpropogation with PyTorch. (#1360) (#1598)As a result,

`default.qubit`

can now use end-to-end classical backpropagation as a means to compute gradients. End-to-end backpropagation can be faster than the parameter-shift rule for computing quantum gradients when the number of parameters to be optimized is large. This is now the default differentiation method when using`default.qubit`

with PyTorch.Using this method, the created QNode is a βwhite-boxβ that is tightly integrated with your PyTorch computation, including TorchScript and GPU support.

x = torch.tensor(0.43316321, dtype=torch.float64, requires_grad=True) y = torch.tensor(0.2162158, dtype=torch.float64, requires_grad=True) z = torch.tensor(0.75110998, dtype=torch.float64, requires_grad=True) p = torch.tensor([x, y, z], requires_grad=True) dev = qml.device("default.qubit", wires=1) @qml.qnode(dev, interface="torch", diff_method="backprop") def circuit(x): qml.Rot(x[0], x[1], x[2], wires=0) return qml.expval(qml.PauliZ(0)) res = circuit(p) res.backward()

>>> res = circuit(p) >>> res.backward() >>> print(p.grad) tensor([-9.1798e-17, -2.1454e-01, -1.0511e-16], dtype=torch.float64)

#### Improved quantum optimization methods

The

`RotosolveOptimizer`

now can tackle general parametrized circuits, and is no longer restricted to single-qubit Pauli rotations. (#1489)This includes:

layers of gates controlled by the same parameter,

controlled variants of parametrized gates, and

Hamiltonian time evolution.

Note that the eigenvalue spectrum of the gate generator needs to be known to use

`RotosolveOptimizer`

for a general gate, and it is required to produce equidistant frequencies. For details see Vidal and Theis, 2018 and Wierichs, Izaac, Wang, Lin 2021.Consider a circuit with a mixture of Pauli rotation gates, controlled Pauli rotations, and single-parameter layers of Pauli rotations:

dev = qml.device('default.qubit', wires=3, shots=None) @qml.qnode(dev) def cost_function(rot_param, layer_par, crot_param): for i, par in enumerate(rot_param): qml.RX(par, wires=i) for w in dev.wires: qml.RX(layer_par, wires=w) for i, par in enumerate(crot_param): qml.CRY(par, wires=[i, (i+1) % 3]) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2))

This cost function has one frequency for each of the first

`RX`

rotation angles, three frequencies for the layer of`RX`

gates that depend on`layer_par`

, and two frequencies for each of the`CRY`

gate parameters. Rotosolve can then be used to minimize the`cost_function`

:# Initial parameters init_param = [ np.array([0.3, 0.2, 0.67], requires_grad=True), np.array(1.1, requires_grad=True), np.array([-0.2, 0.1, -2.5], requires_grad=True), ] # Numbers of frequencies per parameter num_freqs = [[1, 1, 1], 3, [2, 2, 2]] opt = qml.RotosolveOptimizer() param = init_param.copy()

In addition, the optimization technique for the Rotosolve substeps can be chosen via the

`optimizer`

and`optimizer_kwargs`

keyword arguments and the minimized cost of the intermediate univariate reconstructions can be read out via`full_output`

, including the cost*after*the full Rotosolve step:for step in range(3): param, cost, sub_cost = opt.step_and_cost( cost_function, *param, num_freqs=num_freqs, full_output=True, optimizer="brute", ) print(f"Cost before step: {cost}") print(f"Minimization substeps: {np.round(sub_cost, 6)}")

Cost before step: 0.042008210392535605 Minimization substeps: [-0.230905 -0.863336 -0.980072 -0.980072 -1. -1. -1. ] Cost before step: -0.999999999068121 Minimization substeps: [-1. -1. -1. -1. -1. -1. -1.] Cost before step: -1.0 Minimization substeps: [-1. -1. -1. -1. -1. -1. -1.]

For usage details please consider the docstring of the optimizer.

#### Faster, trainable, Hamiltonian simulations

Hamiltonians are now trainable with respect to their coefficients. (#1483)

from pennylane import numpy as np dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(coeffs, param): qml.RX(param, wires=0) qml.RY(param, wires=0) return qml.expval( qml.Hamiltonian(coeffs, [qml.PauliX(0), qml.PauliZ(0)], simplify=True) ) coeffs = np.array([-0.05, 0.17]) param = np.array(1.7) grad_fn = qml.grad(circuit)

>>> grad_fn(coeffs, param) (array([-0.12777055, 0.0166009 ]), array(0.0917819))

Furthermore, a gradient recipe for Hamiltonian coefficients has been added. This makes it possible to compute parameter-shift gradients of these coefficients on devices that natively support Hamiltonians. (#1551)

Hamiltonians are now natively supported on the

`default.qubit`

device if`shots=None`

. This makes VQE workflows a lot faster in some cases. (#1551) (#1596)The Hamiltonian can now store grouping information, which can be accessed by a device to speed up computations of the expectation value of a Hamiltonian. (#1515)

obs = [qml.PauliX(0), qml.PauliX(1), qml.PauliZ(0)] coeffs = np.array([1., 2., 3.]) H = qml.Hamiltonian(coeffs, obs, grouping_type='qwc')

Initialization with a

`grouping_type`

other than`None`

stores the indices required to make groups of commuting observables and their coefficients.>>> H.grouping_indices [[0, 1], [2]]

#### Create multi-circuit quantum transforms and custom gradient rules

Custom gradient transforms can now be created using the new

`@qml.gradients.gradient_transform`

decorator on a batch-tape transform. (#1589)Quantum gradient transforms are a specific case of

`qml.batch_transform`

.Supported gradient transforms must be of the following form:

@qml.gradients.gradient_transform def my_custom_gradient(tape, argnum=None, **kwargs): ... return gradient_tapes, processing_fn

Various built-in quantum gradient transforms are provided within the

`qml.gradients`

module, including`qml.gradients.param_shift`

. Once defined, quantum gradient transforms can be applied directly to QNodes:>>> @qml.qnode(dev) ... def circuit(x): ... qml.RX(x, wires=0) ... qml.CNOT(wires=[0, 1]) ... return qml.expval(qml.PauliZ(0)) >>> circuit(0.3) tensor(0.95533649, requires_grad=True) >>> qml.gradients.param_shift(circuit)(0.5) array([[-0.47942554]])

Quantum gradient transforms are fully differentiable, allowing higher order derivatives to be accessed:

>>> qml.grad(qml.gradients.param_shift(circuit))(0.5) tensor(-0.87758256, requires_grad=True)

Refer to the page of quantum gradient transforms for more details.

The ability to define

*batch*transforms has been added via the new`@qml.batch_transform`

decorator. (#1493)A batch transform is a transform that takes a single tape or QNode as input, and executes multiple tapes or QNodes independently. The results may then be post-processed before being returned.

For example, consider the following batch transform:

@qml.batch_transform def my_transform(tape, a, b): """Generates two tapes, one with all RX replaced with RY, and the other with all RX replaced with RZ.""" tape1 = qml.tape.JacobianTape() tape2 = qml.tape.JacobianTape() # loop through all operations on the input tape for op in tape.operations + tape.measurements: if op.name == "RX": with tape1: qml.RY(a * qml.math.abs(op.parameters[0]), wires=op.wires) with tape2: qml.RZ(b * qml.math.abs(op.parameters[0]), wires=op.wires) else: for t in [tape1, tape2]: with t: qml.apply(op) def processing_fn(results): return qml.math.sum(qml.math.stack(results)) return [tape1, tape2], processing_fn

We can transform a QNode directly using decorator syntax:

>>> @my_transform(0.65, 2.5) ... @qml.qnode(dev) ... def circuit(x): ... qml.Hadamard(wires=0) ... qml.RX(x, wires=0) ... return qml.expval(qml.PauliX(0)) >>> print(circuit(-0.5)) 1.2629730888100839

Batch tape transforms are fully differentiable:

>>> gradient = qml.grad(circuit)(-0.5) >>> print(gradient) 2.5800122591960153

Batch transforms can also be applied to existing QNodes,

>>> new_qnode = my_transform(existing_qnode, *transform_weights) >>> new_qnode(weights)

or to tapes (in which case, the processed tapes and classical post-processing functions are returned):

>>> tapes, fn = my_transform(tape, 0.65, 2.5) >>> from pennylane.interfaces.batch import execute >>> dev = qml.device("default.qubit", wires=1) >>> res = execute(tapes, dev, interface="autograd", gradient_fn=qml.gradients.param_shift) 1.2629730888100839

Vector-Jacobian product transforms have been added to the

`qml.gradients`

package. (#1494)The new transforms include:

`qml.gradients.vjp`

`qml.gradients.batch_vjp`

Support for differentiable execution of batches of circuits has been added, via the beta

`pennylane.interfaces.batch`

module. (#1501) (#1508) (#1542) (#1549) (#1608) (#1618) (#1637)For now, this is a low-level feature, and will be integrated into the QNode in a future release. For example:

from pennylane.interfaces.batch import execute def cost_fn(x): with qml.tape.JacobianTape() as tape1: qml.RX(x[0], wires=[0]) qml.RY(x[1], wires=[1]) qml.CNOT(wires=[0, 1]) qml.var(qml.PauliZ(0) @ qml.PauliX(1)) with qml.tape.JacobianTape() as tape2: qml.RX(x[0], wires=0) qml.RY(x[0], wires=1) qml.CNOT(wires=[0, 1]) qml.probs(wires=1) result = execute( [tape1, tape2], dev, gradient_fn=qml.gradients.param_shift, interface="autograd" ) return result[0] + result[1][0, 0] res = qml.grad(cost_fn)(params)

### Improvements

A new operation

`qml.SISWAP`

has been added, the square-root of the`qml.ISWAP`

operation. (#1563)The

`frobenius_inner_product`

function has been moved to the`qml.math`

module, and is now differentiable using all autodiff frameworks. (#1388)A warning is raised to inform the user that specifying a list of shots is only supported for

`QubitDevice`

based devices. (#1659)The

`qml.circuit_drawer.MPLDrawer`

class provides manual circuit drawing functionality using Matplotlib. While not yet integrated with automatic circuit drawing, this class provides customization and control. (#1484)from pennylane.circuit_drawer import MPLDrawer drawer = MPLDrawer(n_wires=3, n_layers=3) drawer.label([r"$|\Psi\rangle$", r"$|\theta\rangle$", "aux"]) drawer.box_gate(layer=0, wires=[0, 1, 2], text="Entangling Layers", text_options={'rotation': 'vertical'}) drawer.box_gate(layer=1, wires=[0, 1], text="U(ΞΈ)") drawer.CNOT(layer=2, wires=[1, 2]) drawer.measure(layer=3, wires=2) drawer.fig.suptitle('My Circuit', fontsize='xx-large')

The slowest tests, more than 1.5 seconds, now have the pytest mark

`slow`

, and can be selected or deselected during local execution of tests. (#1633)The device test suite has been expanded to cover more qubit operations and observables. (#1510)

The

`MultiControlledX`

class now inherits from`Operation`

instead of`ControlledQubitUnitary`

which makes the`MultiControlledX`

gate a non-parameterized gate. (#1557)The

`utils.sparse_hamiltonian`

function can now deal with non-integer wire labels, and it throws an error for the edge case of observables that are created from multi-qubit operations. (#1550)Added the matrix attribute to

`qml.templates.subroutines.GroverOperator`

(#1553)The

`tape.to_openqasm()`

method now has a`measure_all`

argument that specifies whether the serialized OpenQASM script includes computational basis measurements on all of the qubits or just those specified by the tape. (#1559)An error is now raised when no arguments are passed to an observable, to inform that wires have not been supplied. (#1547)

The

`group_observables`

transform is now differentiable. (#1483)For example:

import jax from jax import numpy as jnp coeffs = jnp.array([1., 2., 3.]) obs = [PauliX(wires=0), PauliX(wires=1), PauliZ(wires=1)] def group(coeffs, select=None): _, grouped_coeffs = qml.grouping.group_observables(obs, coeffs) # in this example, grouped_coeffs is a list of two jax tensors # [DeviceArray([1., 2.], dtype=float32), DeviceArray([3.], dtype=float32)] return grouped_coeffs[select] jac_fn = jax.jacobian(group)

>>> jac_fn(coeffs, select=0) [[1. 0. 0.] [0. 1. 0.]] >>> jac_fn(coeffs, select=1) [[0., 0., 1.]]

The tape does not verify any more that all Observables have owners in the annotated queue. (#1505)

This allows manipulation of Observables inside a tape context. An example is

`expval(Tensor(qml.PauliX(0), qml.Identity(1)).prune())`

which makes the expval an owner of the pruned tensor and its constituent observables, but leaves the original tensor in the queue without an owner.The

`qml.ResetError`

is now supported for`default.mixed`

device. (#1541)`QNode.diff_method`

will now reflect which method was selected from`diff_method="best"`

. (#1568)QNodes now support

`diff_method=None`

. This works the same as`interface=None`

. Such QNodes accept floats, ints, lists and NumPy arrays and return NumPy output but can not be differentiated. (#1585)QNodes now include validation to warn users if a supplied keyword argument is not one of the recognized arguments. (#1591)

### Breaking changes

The

`QFT`

operation has been moved, and is now accessible via`pennylane.templates.QFT`

. (#1548)Specifying

`shots=None`

with`qml.sample`

was previously deprecated. From this release onwards, setting`shots=None`

when sampling will raise an error also for`default.qubit.jax`

. (#1629)An error is raised during QNode creation when a user requests backpropagation on a device with finite-shots. (#1588)

The class

`qml.Interferometer`

is deprecated and will be renamed`qml.InterferometerUnitary`

after one release cycle. (#1546)All optimizers except for Rotosolve and Rotoselect now have a public attribute

`stepsize`

. Temporary backward compatibility has been added to support the use of`_stepsize`

for one release cycle.`update_stepsize`

method is deprecated. (#1625)

### Bug fixes

Fixed a bug with shot vectors and

`Device`

base class. (#1666)Fixed a bug where

`@jax.jit`

would fail on a QNode that used`qml.QubitStateVector`

. (#1649)Fixed a bug related to an edge case of single-qubit

`zyz_decomposition`

when only off-diagonal elements are present. (#1643)`MottonenStatepreparation`

can now be run with a single wire label not in a list. (#1620)Fixed the circuit representation of CY gates to align with CNOT and CZ gates when calling the circuit drawer. (#1504)

Dask and CVXPY dependent tests are skipped if those packages are not installed. (#1617)

The

`qml.layer`

template now works with tensorflow variables. (#1615)Remove

`QFT`

from possible operations in`default.qubit`

and`default.mixed`

. (#1600)Fixed a bug when computing expectations of Hamiltonians using TensorFlow. (#1586)

Fixed a bug when computing the specs of a circuit with a Hamiltonian observable. (#1533)

### Documentation

The

`qml.Identity`

operation is placed under the sections Qubit observables and CV observables. (#1576)Updated the documentation of

`qml.grouping`

,`qml.kernels`

and`qml.qaoa`

modules to present the list of functions first followed by the technical details of the module. (#1581)Recategorized Qubit operations into new and existing categories so that code for each operation is easier to locate. (#1566)

### Contributors

This release contains contributions from (in alphabetical order):

Vishnu Ajith, Akash Narayanan B, Thomas Bromley, Olivia Di Matteo, Sahaj Dhamija, Tanya Garg, Anthony Hayes, Theodor Isacsson, Josh Izaac, Prateek Jain, Ankit Khandelwal, Nathan Killoran, Christina Lee, Ian McLean, Johannes Jakob Meyer, Romain Moyard, Lee James OβRiordan, Esteban Payares, Pratul Saini, Maria Schuld, Arshpreet Singh, Jay Soni, Ingrid Strandberg, Antal SzΓ‘va, Slimane Thabet, David Wierichs, Vincent Wong.

- orphan

## Release 0.17.0ΒΆ

### New features since the last release

#### Circuit optimization

PennyLane can now perform quantum circuit optimization using the top-level transform

`qml.compile`

. The`compile`

transform allows you to chain together sequences of tape and quantum function transforms into custom circuit optimization pipelines. (#1475)For example, take the following decorated quantum function:

dev = qml.device('default.qubit', wires=[0, 1, 2]) @qml.qnode(dev) @qml.compile() def qfunc(x, y, z): qml.Hadamard(wires=0) qml.Hadamard(wires=1) qml.Hadamard(wires=2) qml.RZ(z, wires=2) qml.CNOT(wires=[2, 1]) qml.RX(z, wires=0) qml.CNOT(wires=[1, 0]) qml.RX(x, wires=0) qml.CNOT(wires=[1, 0]) qml.RZ(-z, wires=2) qml.RX(y, wires=2) qml.PauliY(wires=2) qml.CZ(wires=[1, 2]) return qml.expval(qml.PauliZ(wires=0))

The default behaviour of

`qml.compile`

is to apply a sequence of three transforms:`commute_controlled`

,`cancel_inverses`

, and then`merge_rotations`

.>>> print(qml.draw(qfunc)(0.2, 0.3, 0.4)) 0: ββHβββRX(0.6)βββββββββββββββββββ€ β¨Zβ© 1: ββHβββXβββββββββββββββββββββCβββ€ 2: ββHβββ°CββββββββRX(0.3)ββYβββ°Zβββ€

The

`qml.compile`

transform is flexible and accepts a custom pipeline of tape and quantum function transforms (you can even write your own!). For example, if we wanted to only push single-qubit gates through controlled gates and cancel adjacent inverses, we could do:from pennylane.transforms import commute_controlled, cancel_inverses pipeline = [commute_controlled, cancel_inverses] @qml.qnode(dev) @qml.compile(pipeline=pipeline) def qfunc(x, y, z): qml.Hadamard(wires=0) qml.Hadamard(wires=1) qml.Hadamard(wires=2) qml.RZ(z, wires=2) qml.CNOT(wires=[2, 1]) qml.RX(z, wires=0) qml.CNOT(wires=[1, 0]) qml.RX(x, wires=0) qml.CNOT(wires=[1, 0]) qml.RZ(-z, wires=2) qml.RX(y, wires=2) qml.PauliY(wires=2) qml.CZ(wires=[1, 2]) return qml.expval(qml.PauliZ(wires=0))

>>> print(qml.draw(qfunc)(0.2, 0.3, 0.4)) 0: ββHβββRX(0.4)ββRX(0.2)βββββββββββββββββββββββββββββ€ β¨Zβ© 1: ββHβββXββββββββββββββββββββββββββββββββββββββββCβββ€ 2: ββHβββ°CββββββββRZ(0.4)ββRZ(-0.4)ββRX(0.3)ββYβββ°Zβββ€

The following compilation transforms have been added and are also available to use, either independently, or within a

`qml.compile`

pipeline:`commute_controlled`

: push commuting single-qubit gates through controlled operations. (#1464)`cancel_inverses`

: removes adjacent pairs of operations that cancel out. (#1455)`merge_rotations`

: combines adjacent rotation gates of the same type into a single gate, including controlled rotations. (#1455)`single_qubit_fusion`

: acts on all sequences of single-qubit operations in a quantum function, and converts each sequence to a single`Rot`

gate. (#1458)

For more details on

`qml.compile`

and the available compilation transforms, see the compilation documentation.

#### QNodes are even more powerful

Computational basis samples directly from the underlying device can now be returned directly from QNodes via

`qml.sample()`

. (#1441)dev = qml.device("default.qubit", wires=3, shots=5) @qml.qnode(dev) def circuit_1(): qml.Hadamard(wires=0) qml.Hadamard(wires=1) return qml.sample() @qml.qnode(dev) def circuit_2(): qml.Hadamard(wires=0) qml.Hadamard(wires=1) return qml.sample(wires=[0,2]) # no observable provided and wires specified

>>> print(circuit_1()) [[1, 0, 0], [1, 1, 0], [1, 0, 0], [0, 0, 0], [0, 1, 0]] >>> print(circuit_2()) [[1, 0], [1, 0], [1, 0], [0, 0], [0, 0]] >>> print(qml.draw(circuit_2)()) 0: ββHββββ€ Sample[basis] 1: ββHββββ€ 2: ββββββ°β€ Sample[basis]

The new

`qml.apply`

function can be used to add operations that might have already been instantiated elsewhere to the QNode and other queuing contexts: (#1433)op = qml.RX(0.4, wires=0) dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(x): qml.RY(x, wires=0) qml.apply(op) return qml.expval(qml.PauliZ(0))

>>> print(qml.draw(circuit)(0.6)) 0: ββRY(0.6)ββRX(0.4)βββ€ β¨Zβ©

Previously instantiated measurements can also be applied to QNodes.

#### Device Resource Tracker

The new Device Tracker capabilities allows for flexible and versatile tracking of executions, even inside parameter-shift gradients. This functionality will improve the ease of monitoring large batches and remote jobs. (#1355)

dev = qml.device('default.qubit', wires=1, shots=100) @qml.qnode(dev, diff_method="parameter-shift") def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) x = np.array(0.1) with qml.Tracker(circuit.device) as tracker: qml.grad(circuit)(x)

>>> tracker.totals {'executions': 3, 'shots': 300, 'batches': 1, 'batch_len': 2} >>> tracker.history {'executions': [1, 1, 1], 'shots': [100, 100, 100], 'batches': [1], 'batch_len': [2]} >>> tracker.latest {'batches': 1, 'batch_len': 2}

Users can also provide a custom function to the

`callback`

keyword that gets called each time the information is updated. This functionality allows users to monitor remote jobs or large parameter-shift batches.>>> def shots_info(totals, history, latest): ... print("Total shots: ", totals['shots']) >>> with qml.Tracker(circuit.device, callback=shots_info) as tracker: ... qml.grad(circuit)(0.1) Total shots: 100 Total shots: 200 Total shots: 300 Total shots: 300

#### Containerization support

Docker support for building PennyLane with support for all interfaces (TensorFlow, Torch, and Jax), as well as device plugins and QChem, for GPUs and CPUs, has been added. (#1391)

The build process using Docker and

`make`

requires that the repository source code is cloned or downloaded from GitHub. Visit the the detailed description for an extended list of options.

#### Improved Hamiltonian simulations

Added a sparse Hamiltonian observable and the functionality to support computing its expectation value with

`default.qubit`

. (#1398)For example, the following QNode returns the expectation value of a sparse Hamiltonian:

dev = qml.device("default.qubit", wires=2) @qml.qnode(dev, diff_method="parameter-shift") def circuit(param, H): qml.PauliX(0) qml.SingleExcitation(param, wires=[0, 1]) return qml.expval(qml.SparseHamiltonian(H, [0, 1]))

We can execute this QNode, passing in a sparse identity matrix:

>>> print(circuit([0.5], scipy.sparse.eye(4).tocoo())) 0.9999999999999999

The expectation value of the sparse Hamiltonian is computed directly, which leads to executions that are faster by orders of magnitude. Note that βparameter-shiftβ is the only differentiation method that is currently supported when the observable is a sparse Hamiltonian.

VQE problems can now be intuitively set up by passing the Hamiltonian as an observable. (#1474)

dev = qml.device("default.qubit", wires=2) H = qml.Hamiltonian([1., 2., 3.], [qml.PauliZ(0), qml.PauliY(0), qml.PauliZ(1)]) w = qml.init.strong_ent_layers_uniform(1, 2, seed=1967) @qml.qnode(dev) def circuit(w): qml.templates.StronglyEntanglingLayers(w, wires=range(2)) return qml.expval(H)

>>> print(circuit(w)) -1.5133943637878295 >>> print(qml.grad(circuit)(w)) [[[-8.32667268e-17 1.39122955e+00 -9.12462052e-02] [ 1.02348685e-16 -7.77143238e-01 -1.74708049e-01]]]

Note that other measurement types like

`var(H)`

or`sample(H)`

, as well as multiple expectations like`expval(H1), expval(H2)`

are not supported.Added functionality to compute the sparse matrix representation of a

`qml.Hamiltonian`

object. (#1394)

#### New gradients module

A new gradients module

`qml.gradients`

has been added, which provides differentiable quantum gradient transforms. (#1476) (#1479) (#1486)Available quantum gradient transforms include:

`qml.gradients.finite_diff`

`qml.gradients.param_shift`

`qml.gradients.param_shift_cv`

For example,

>>> params = np.array([0.3,0.4,0.5], requires_grad=True) >>> with qml.tape.JacobianTape() as tape: ... qml.RX(params[0], wires=0) ... qml.RY(params[1], wires=0) ... qml.RX(params[2], wires=0) ... qml.expval(qml.PauliZ(0)) ... qml.var(qml.PauliZ(0)) >>> tape.trainable_params = {0, 1, 2} >>> gradient_tapes, fn = qml.gradients.finite_diff(tape) >>> res = dev.batch_execute(gradient_tapes) >>> fn(res) array([[-0.69688381, -0.32648317, -0.68120105], [ 0.8788057 , 0.41171179, 0.85902895]])

#### Even more new operations and templates

Grover Diffusion Operator template added. (#1442)

For example, if we have an oracle that marks the βall onesβ state with a negative sign:

n_wires = 3 wires = list(range(n_wires)) def oracle(): qml.Hadamard(wires[-1]) qml.Toffoli(wires=wires) qml.Hadamard(wires[-1])

We can perform Groverβs Search Algorithm:

dev = qml.device('default.qubit', wires=wires) @qml.qnode(dev) def GroverSearch(num_iterations=1): for wire in wires: qml.Hadamard(wire) for _ in range(num_iterations): oracle() qml.templates.GroverOperator(wires=wires) return qml.probs(wires)

We can see this circuit yields the marked state with high probability:

>>> GroverSearch(num_iterations=1) tensor([0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.78125], requires_grad=True) >>> GroverSearch(num_iterations=2) tensor([0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.9453125], requires_grad=True)

A decomposition has been added to

`QubitUnitary`

that makes the single-qubit case fully differentiable in all interfaces. Furthermore, a quantum function transform,`unitary_to_rot()`

, has been added to decompose all single-qubit instances of`QubitUnitary`

in a quantum circuit. (#1427)Instances of

`QubitUnitary`

may now be decomposed directly to`Rot`

operations, or`RZ`

operations if the input matrix is diagonal. For example, let>>> U = np.array([ [-0.28829348-0.78829734j, 0.30364367+0.45085995j], [ 0.53396245-0.10177564j, 0.76279558-0.35024096j] ])

Then, we can compute the decomposition as:

>>> qml.QubitUnitary.decomposition(U, wires=0) [Rot(-0.24209530281458358, 1.1493817777199102, 1.733058145303424, wires=[0])]

We can also apply the transform directly to a quantum function, and compute the gradients of parameters used to construct the unitary matrices.

def qfunc_with_qubit_unitary(angles): z, x = angles[0], angles[1] Z_mat = np.array([[np.exp(-1j * z / 2), 0.0], [0.0, np.exp(1j * z / 2)]]) c = np.cos(x / 2) s = np.sin(x / 2) * 1j X_mat = np.array([[c, -s], [-s, c]]) qml.Hadamard(wires="a") qml.QubitUnitary(Z_mat, wires="a") qml.QubitUnitary(X_mat, wires="b") qml.CNOT(wires=["b", "a"]) return qml.expval(qml.PauliX(wires="a"))

>>> dev = qml.device("default.qubit", wires=["a", "b"]) >>> transformed_qfunc = qml.transforms.unitary_to_rot(qfunc_with_qubit_unitary) >>> transformed_qnode = qml.QNode(transformed_qfunc, dev) >>> input = np.array([0.3, 0.4], requires_grad=True) >>> transformed_qnode(input) tensor(0.95533649, requires_grad=True) >>> qml.grad(transformed_qnode)(input) array([-0.29552021, 0. ])

Ising YY gate functionality added. (#1358)

### Improvements

The tape does not verify any more that all Observables have owners in the annotated queue. (#1505)

This allows manipulation of Observables inside a tape context. An example is

`expval(Tensor(qml.PauliX(0), qml.Identity(1)).prune())`

which makes the expval an owner of the pruned tensor and its constituent observables, but leaves the original tensor in the queue without an owner.The

`step`

and`step_and_cost`

methods of`QNGOptimizer`

now accept a custom`grad_fn`

keyword argument to use for gradient computations. (#1487)The precision used by

`default.qubit.jax`

now matches the float precision indicated byfrom jax.config import config config.read('jax_enable_x64')

where

`True`

means`float64`

/`complex128`

and`False`

means`float32`

/`complex64`

. (#1485)The

`./pennylane/ops/qubit.py`

file is broken up into a folder of six separate files. (#1467)Changed to using commas as the separator of wires in the string representation of

`qml.Hamiltonian`

objects for multi-qubit terms. (#1465)Changed to using

`np.object_`

instead of`np.object`

as per the NumPy deprecations starting version 1.20. (#1466)Change the order of the covariance matrix and the vector of means internally in

`default.gaussian`

. (#1331)Added the

`id`

attribute to templates. (#1438)The

`qml.math`

module, for framework-agnostic tensor manipulation, has two new functions available: (#1490)`qml.math.get_trainable_indices(sequence_of_tensors)`

: returns the indices corresponding to trainable tensors in the input sequence.`qml.math.unwrap(sequence_of_tensors)`

: unwraps a sequence of tensor-like objects to NumPy arrays.

In addition, the behaviour of

`qml.math.requires_grad`

has been improved in order to correctly determine trainability during Autograd and JAX backwards passes.A new tape method,

`tape.unwrap()`

is added. This method is a context manager; inside the context, the tapeβs parameters are unwrapped to NumPy arrays and floats, and the trainable parameter indices are set. (#1491)These changes are temporary, and reverted on exiting the context.

>>> with tf.GradientTape(): ... with qml.tape.QuantumTape() as tape: ... qml.RX(tf.Variable(0.1), wires=0) ... qml.RY(tf.constant(0.2), wires=0) ... qml.RZ(tf.Variable(0.3), wires=0) ... with tape.unwrap(): ... print("Trainable params:", tape.trainable_params) ... print("Unwrapped params:", tape.get_parameters()) Trainable params: {0, 2} Unwrapped params: [0.1, 0.3] >>> print("Original parameters:", tape.get_parameters()) Original parameters: [<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.1>, <tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.3>]

In addition,

`qml.tape.Unwrap`

is a context manager that unwraps multiple tapes:>>> with qml.tape.Unwrap(tape1, tape2):

### Breaking changes

Removed the deprecated tape methods

`get_resources`

and`get_depth`

as they are superseded by the`specs`

tape attribute. (#1522)Specifying

`shots=None`

with`qml.sample`

was previously deprecated. From this release onwards, setting`shots=None`

when sampling will raise an error. (#1522)The existing

`pennylane.collections.apply`

function is no longer accessible via`qml.apply`

, and needs to be imported directly from the`collections`

package. (#1358)

### Bug fixes

Fixes a bug in

`qml.adjoint`

and`qml.ctrl`

where the adjoint of operations outside of a`QNode`

or a`QuantumTape`

could not be obtained. (#1532)Fixes a bug in

`GradientDescentOptimizer`

and`NesterovMomentumOptimizer`

where a cost function with one trainable parameter and non-trainable parameters raised an error. (#1495)Fixed an example in the documentationβs introduction to numpy gradients, where the wires were a non-differentiable argument to the QNode. (#1499)

Fixed a bug where the adjoint of

`qml.QFT`

when using the`qml.adjoint`

function was not correctly computed. (#1451)Fixed the differentiability of the operation

`IsingYY`

for Autograd, Jax and Tensorflow. (#1425)Fixed a bug in the

`torch`

interface that prevented gradients from being computed on a GPU. (#1426)Quantum function transforms now preserve the format of the measurement results, so that a single measurement returns a single value rather than an array with a single element. (#1434)

Fixed a bug in the parameter-shift Hessian implementation, which resulted in the incorrect Hessian being returned for a cost function that performed post-processing on a vector-valued QNode. (#1436)

Fixed a bug in the initialization of

`QubitUnitary`

where the size of the matrix was not checked against the number of wires. (#1439)

### Documentation

Improved Contribution Guide and Pull Requests Guide. (#1461)

Examples have been added to clarify use of the continuous-variable

`FockStateVector`

operation in the multi-mode case. (#1472)

### Contributors

This release contains contributions from (in alphabetical order):

Juan Miguel Arrazola, Olivia Di Matteo, Anthony Hayes, Theodor Isacsson, Josh Izaac, Soran Jahangiri, Nathan Killoran, Arshpreet Singh Khangura, Leonhard Kunczik, Christina Lee, Romain Moyard, Lee James OβRiordan, Ashish Panigrahi, Nahum SΓ‘, Maria Schuld, Jay Soni, Antal SzΓ‘va, David Wierichs.

- orphan

## Release 0.16.0ΒΆ

#### First class support for quantum kernels

The new

`qml.kernels`

module provides basic functionalities for working with quantum kernels as well as post-processing methods to mitigate sampling errors and device noise: (#1102)num_wires = 6 wires = range(num_wires) dev = qml.device('default.qubit', wires=wires) @qml.qnode(dev) def kernel_circuit(x1, x2): qml.templates.AngleEmbedding(x1, wires=wires) qml.adjoint(qml.templates.AngleEmbedding)(x2, wires=wires) return qml.probs(wires) kernel = lambda x1, x2: kernel_circuit(x1, x2)[0] X_train = np.random.random((10, 6)) X_test = np.random.random((5, 6)) # Create symmetric square kernel matrix (for training) K = qml.kernels.square_kernel_matrix(X_train, kernel) # Compute kernel between test and training data. K_test = qml.kernels.kernel_matrix(X_train, X_test, kernel) K1 = qml.kernels.mitigate_depolarizing_noise(K, num_wires, method='single')

#### Extract the fourier representation of quantum circuits

PennyLane now has a

`fourier`

module, which hosts a growing library of methods that help with investigating the Fourier representation of functions implemented by quantum circuits. The Fourier representation can be used to examine and characterize the expressivity of the quantum circuit. (#1160) (#1378)For example, one can plot distributions over Fourier series coefficients like this one:

#### Seamless support for working with the Pauli group

Added functionality for constructing and manipulating the Pauli group (#1181).

The function

`qml.grouping.pauli_group`

provides a generator to easily loop over the group, or construct and store it in its entirety. For example, we can construct the single-qubit Pauli group like so:>>> from pennylane.grouping import pauli_group >>> pauli_group_1_qubit = list(pauli_group(1)) >>> pauli_group_1_qubit [Identity(wires=[0]), PauliZ(wires=[0]), PauliX(wires=[0]), PauliY(wires=[0])]

We can multiply together its members at the level of Pauli words using the

`pauli_mult`

and`pauli_multi_with_phase`

functions. This can be done on arbitrarily-labeled wires as well, by defining a wire map.>>> from pennylane.grouping import pauli_group, pauli_mult >>> wire_map = {'a' : 0, 'b' : 1, 'c' : 2} >>> pg = list(pauli_group(3, wire_map=wire_map)) >>> pg[3] PauliZ(wires=['b']) @ PauliZ(wires=['c']) >>> pg[55] PauliY(wires=['a']) @ PauliY(wires=['b']) @ PauliZ(wires=['c']) >>> pauli_mult(pg[3], pg[55], wire_map=wire_map) PauliY(wires=['a']) @ PauliX(wires=['b'])

Functions for conversion of Pauli observables to strings (and back), are included.

>>> from pennylane.grouping import pauli_word_to_string, string_to_pauli_word >>> pauli_word_to_string(pg[55], wire_map=wire_map) 'YYZ' >>> string_to_pauli_word('ZXY', wire_map=wire_map) PauliZ(wires=['a']) @ PauliX(wires=['b']) @ PauliY(wires=['c'])

Calculation of the matrix representation for arbitrary Paulis and wire maps is now also supported.

>>> from pennylane.grouping import pauli_word_to_matrix >>> wire_map = {'a' : 0, 'b' : 1} >>> pauli_word = qml.PauliZ('b') # corresponds to Pauli 'IZ' >>> pauli_word_to_matrix(pauli_word, wire_map=wire_map) array([[ 1., 0., 0., 0.], [ 0., -1., 0., -0.], [ 0., 0., 1., 0.], [ 0., -0., 0., -1.]])

#### New transforms

The

`qml.specs`

QNode transform creates a function that returns specifications or details about the QNode, including depth, number of gates, and number of gradient executions required. (#1245)For example:

dev = qml.device('default.qubit', wires=4) @qml.qnode(dev, diff_method='parameter-shift') def circuit(x, y): qml.RX(x[0], wires=0) qml.Toffoli(wires=(0, 1, 2)) qml.CRY(x[1], wires=(0, 1)) qml.Rot(x[2], x[3], y, wires=0) return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliX(1))

We can now use the

`qml.specs`

transform to generate a function that returns details and resource information:>>> x = np.array([0.05, 0.1, 0.2, 0.3], requires_grad=True) >>> y = np.array(0.4, requires_grad=False) >>> specs_func = qml.specs(circuit) >>> specs_func(x, y) {'gate_sizes': defaultdict(int, {1: 2, 3: 1, 2: 1}), 'gate_types': defaultdict(int, {'RX': 1, 'Toffoli': 1, 'CRY': 1, 'Rot': 1}), 'num_operations': 4, 'num_observables': 2, 'num_diagonalizing_gates': 1, 'num_used_wires': 3, 'depth': 4, 'num_trainable_params': 4, 'num_parameter_shift_executions': 11, 'num_device_wires': 4, 'device_name': 'default.qubit', 'diff_method': 'parameter-shift'}

The tape methods

`get_resources`

and`get_depth`

are superseded by`specs`

and will be deprecated after one release cycle.Adds a decorator

`@qml.qfunc_transform`

to easily create a transformation that modifies the behaviour of a quantum function. (#1315)For example, consider the following transform, which scales the parameter of all

`RX`

gates by \(x \rightarrow \sin(a) \sqrt{x}\), and the parameters of all`RY`

gates by \(y \rightarrow \cos(a * b) y\):@qml.qfunc_transform def my_transform(tape, a, b): for op in tape.operations + tape.measurements: if op.name == "RX": x = op.parameters[0] qml.RX(qml.math.sin(a) * qml.math.sqrt(x), wires=op.wires) elif op.name == "RY": y = op.parameters[0] qml.RX(qml.math.cos(a * b) * y, wires=op.wires) else: op.queue()

We can now apply this transform to any quantum function:

dev = qml.device("default.qubit", wires=2) def ansatz(x): qml.Hadamard(wires=0) qml.RX(x[0], wires=0) qml.RY(x[1], wires=1) qml.CNOT(wires=[0, 1]) @qml.qnode(dev) def circuit(params, transform_weights): qml.RX(0.1, wires=0) # apply the transform to the ansatz my_transform(*transform_weights)(ansatz)(params) return qml.expval(qml.PauliZ(1))

We can print this QNode to show that the qfunc transform is taking place:

>>> x = np.array([0.5, 0.3], requires_grad=True) >>> transform_weights = np.array([0.1, 0.6], requires_grad=True) >>> print(qml.draw(circuit)(x, transform_weights)) 0: ββRX(0.1)ββββHββRX(0.0706)βββCβββ€ 1: ββRX(0.299)ββββββββββββββββββ°Xβββ€ β¨Zβ©

Evaluating the QNode, as well as the derivative, with respect to the gate parameter

*and*the transform weights:>>> circuit(x, transform_weights) tensor(0.00672829, requires_grad=True) >>> qml.grad(circuit)(x, transform_weights) (array([ 0.00671711, -0.00207359]), array([6.69695008e-02, 3.73694364e-06]))

Adds a

`hamiltonian_expand`

tape transform. This takes a tape ending in`qml.expval(H)`

, where`H`

is a Hamiltonian, and maps it to a collection of tapes which can be executed and passed into a post-processing function yielding the expectation value. (#1142)Example use:

H = qml.PauliZ(0) + 3 * qml.PauliZ(0) @ qml.PauliX(1) with qml.tape.QuantumTape() as tape: qml.Hadamard(wires=1) qml.expval(H) tapes, fn = qml.transforms.hamiltonian_expand(tape)

We can now evaluate the transformed tapes, and apply the post-processing function:

>>> dev = qml.device("default.qubit", wires=3) >>> res = dev.batch_execute(tapes) >>> fn(res) 3.999999999999999

The

`quantum_monte_carlo`

transform has been added, allowing an input circuit to be transformed into the full quantum Monte Carlo algorithm. (#1316)Suppose we want to measure the expectation value of the sine squared function according to a standard normal distribution. We can calculate the expectation value analytically as

`0.432332`

, but we can also estimate using the quantum Monte Carlo algorithm. The first step is to discretize the problem:from scipy.stats import norm m = 5 M = 2 ** m xmax = np.pi # bound to region [-pi, pi] xs = np.linspace(-xmax, xmax, M) probs = np.array([norm().pdf(x) for x in xs]) probs /= np.sum(probs) func = lambda i: np.sin(xs[i]) ** 2 r_rotations = np.array([2 * np.arcsin(np.sqrt(func(i))) for i in range(M)])

The

`quantum_monte_carlo`

transform can then be used:from pennylane.templates.state_preparations.mottonen import ( _uniform_rotation_dagger as r_unitary, ) n = 6 N = 2 ** n a_wires = range(m) wires = range(m + 1) target_wire = m estimation_wires = range(m + 1, n + m + 1) dev = qml.device("default.qubit", wires=(n + m + 1)) def fn(): qml.templates.MottonenStatePreparation(np.sqrt(probs), wires=a_wires) r_unitary(qml.RY, r_rotations, control_wires=a_wires[::-1], target_wire=target_wire) @qml.qnode(dev) def qmc(): qml.quantum_monte_carlo(fn, wires, target_wire, estimation_wires)() return qml.probs(estimation_wires) phase_estimated = np.argmax(qmc()[:int(N / 2)]) / N

The estimated value can be retrieved using:

>>> (1 - np.cos(np.pi * phase_estimated)) / 2 0.42663476277231915

The resources required to perform the quantum Monte Carlo algorithm can also be inspected using the

`specs`

transform.

#### Extended QAOA module

Functionality to support solving the maximum-weighted cycle problem has been added to the

`qaoa`

module. (#1207) (#1209) (#1251) (#1213) (#1220) (#1214) (#1283) (#1297) (#1396) (#1403)The

`max_weight_cycle`

function returns the appropriate cost and mixer Hamiltonians:>>> a = np.random.random((3, 3)) >>> np.fill_diagonal(a, 0) >>> g = nx.DiGraph(a) # create a random directed graph >>> cost, mixer, mapping = qml.qaoa.max_weight_cycle(g) >>> print(cost) (-0.9775906842165344) [Z2] + (-0.9027248603361988) [Z3] + (-0.8722207409852838) [Z0] + (-0.6426184210832898) [Z5] + (-0.2832594164291379) [Z1] + (-0.0778133996933755) [Z4] >>> print(mapping) {0: (0, 1), 1: (0, 2), 2: (1, 0), 3: (1, 2), 4: (2, 0), 5: (2, 1)}

Additional functionality can be found in the qml.qaoa.cycle module.

#### Extended operations and templates

Added functionality to compute the sparse matrix representation of a

`qml.Hamiltonian`

object. (#1394)coeffs = [1, -0.45] obs = [qml.PauliZ(0) @ qml.PauliZ(1), qml.PauliY(0) @ qml.PauliZ(1)] H = qml.Hamiltonian(coeffs, obs) H_sparse = qml.utils.sparse_hamiltonian(H)

The resulting matrix is a sparse matrix in scipy coordinate list (COO) format:

>>> H_sparse <4x4 sparse matrix of type '<class 'numpy.complex128'>' with 8 stored elements in COOrdinate format>

The sparse matrix can be converted to an array as:

>>> H_sparse.toarray() array([[ 1.+0.j , 0.+0.j , 0.+0.45j, 0.+0.j ], [ 0.+0.j , -1.+0.j , 0.+0.j , 0.-0.45j], [ 0.-0.45j, 0.+0.j , -1.+0.j , 0.+0.j ], [ 0.+0.j , 0.+0.45j, 0.+0.j , 1.+0.j ]])

Adds the new template

`AllSinglesDoubles`

to prepare quantum states of molecules using the`SingleExcitation`

and`DoubleExcitation`

operations. The new template reduces significantly the number of operations and the depth of the quantum circuit with respect to the traditional UCCSD unitary. (#1383)For example, consider the case of two particles and four qubits. First, we define the Hartree-Fock initial state and generate all possible single and double excitations.

import pennylane as qml from pennylane import numpy as np electrons = 2 qubits = 4 hf_state = qml.qchem.hf_state(electrons, qubits) singles, doubles = qml.qchem.excitations(electrons, qubits)

Now we can use the template

`AllSinglesDoubles`

to define the quantum circuit,from pennylane.templates import AllSinglesDoubles wires = range(qubits) dev = qml.device('default.qubit', wires=wires) @qml.qnode(dev) def circuit(weights, hf_state, singles, doubles): AllSinglesDoubles(weights, wires, hf_state, singles, doubles) return qml.expval(qml.PauliZ(0)) params = np.random.normal(0, np.pi, len(singles) + len(doubles))

and execute it:

>>> circuit(params, hf_state, singles=singles, doubles=doubles) tensor(-0.73772194, requires_grad=True)

Adds

`QubitCarry`

and`QubitSum`

operations for basic arithmetic. (#1169)The following example adds two 1-bit numbers, returning a 2-bit answer:

dev = qml.device('default.qubit', wires = 4) a = 0 b = 1 @qml.qnode(dev) def circuit(): qml.BasisState(np.array([a, b]), wires=[1, 2]) qml.QubitCarry(wires=[0, 1, 2, 3]) qml.CNOT(wires=[1, 2]) qml.QubitSum(wires=[0, 1, 2]) return qml.probs(wires=[3, 2]) probs = circuit() bitstrings = tuple(itertools.product([0, 1], repeat = 2)) indx = np.argwhere(probs == 1).flatten()[0] output = bitstrings[indx]

>>> print(output) (0, 1)

Added the

`qml.Projector`

observable, which is available on all devices inheriting from the`QubitDevice`

class. (#1356) (#1368)Using

`qml.Projector`

, we can define the basis state projectors to use when computing expectation values. Let us take for example a circuit that prepares Bell states:dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(basis_state): qml.Hadamard(wires=[0]) qml.CNOT(wires=[0, 1]) return qml.expval(qml.Projector(basis_state, wires=[0, 1]))

We can then specify the

`|00>`

basis state to construct the`|00><00|`

projector and compute the expectation value:>>> basis_state = [0, 0] >>> circuit(basis_state) tensor(0.5, requires_grad=True)

As expected, we get similar results when specifying the

`|11>`

basis state:>>> basis_state = [1, 1] >>> circuit(basis_state) tensor(0.5, requires_grad=True)

The following new operations have been added:

### Improvements

The

`argnum`

keyword argument can now be specified for a QNode to define a subset of trainable parameters used to estimate the Jacobian. (#1371)For example, consider two trainable parameters and a quantum function:

dev = qml.device("default.qubit", wires=2) x = np.array(0.543, requires_grad=True) y = np.array(-0.654, requires_grad=True) def circuit(x,y): qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0) @ qml.PauliX(1))

When computing the gradient of the QNode, we can specify the trainable parameters to consider by passing the

`argnum`

keyword argument:>>> qnode1 = qml.QNode(circuit, dev, diff_method="parameter-shift", argnum=[0,1]) >>> print(qml.grad(qnode1)(x,y)) (array(0.31434679), array(0.67949903))

Specifying a proper subset of the trainable parameters will estimate the Jacobian:

>>> qnode2 = qml.QNode(circuit, dev, diff_method="parameter-shift", argnum=[0]) >>> print(qml.grad(qnode2)(x,y)) (array(0.31434679), array(0.))

Allows creating differentiable observables that return custom objects such that the observable is supported by devices. (1291)

As an example, first we define

`NewObservable`

class:from pennylane.devices import DefaultQubit class NewObservable(qml.operation.Observable): """NewObservable""" num_wires = qml.operation.AnyWires num_params = 0 par_domain = None def diagonalizing_gates(self): """Diagonalizing gates""" return []

Once we have this new observable class, we define a

`SpecialObject`

class that can be used to encode data in an observable and a new device that supports our new observable and returns a`SpecialObject`

as the expectation value (the code is shortened for brevity, the extended example can be found as a test in the previously referenced pull request):class SpecialObject: def __init__(self, val): self.val = val def __mul__(self, other): new = SpecialObject(self.val) new *= other return new ... class DeviceSupportingNewObservable(DefaultQubit): name = "Device supporting NewObservable" short_name = "default.qubit.newobservable" observables = DefaultQubit.observables.union({"NewObservable"}) def expval(self, observable, **kwargs): if self.shots is None and isinstance(observable, NewObservable): val = super().expval(qml.PauliZ(wires=0), **kwargs) return SpecialObject(val) return super().expval(observable, **kwargs)

At this point, we can create a device that will support the differentiation of a

`NewObservable`

object:dev = DeviceSupportingNewObservable(wires=1, shots=None) @qml.qnode(dev, diff_method="parameter-shift") def qnode(x): qml.RY(x, wires=0) return qml.expval(NewObservable(wires=0))

We can then compute the jacobian of this object:

>>> result = qml.jacobian(qnode)(0.2) >>> print(result) <__main__.SpecialObject object at 0x7fd2c54721f0> >>> print(result.item().val) -0.19866933079506116

PennyLane NumPy now includes the random moduleβs

`Generator`

objects, the recommended way of random number generation. This allows for random number generation using a local, rather than global seed. (#1267)from pennylane import numpy as np rng = np.random.default_rng() random_mat1 = rng.random((3,2)) random_mat2 = rng.standard_normal(3, requires_grad=False)

The performance of adjoint jacobian differentiation was significantly improved as the method now reuses the state computed on the forward pass. This can be turned off to save memory with the Torch and TensorFlow interfaces by passing

`adjoint_cache=False`

during QNode creation. (#1341)The

`Operator`

(and by inheritance, the`Operation`

and`Observable`

class and their children) now have an`id`

attribute, which can mark an operator in a circuit, for example to identify it on the tape by a tape transform. (#1377)The

`benchmark`

module was deleted, since it was outdated and is superseded by the new separate benchmark repository. (#1343)Decompositions in terms of elementary gates has been added for:

`qml.CSWAP`

(#1306)`qml.SWAP`

(#1329)`qml.SingleExcitation`

(#1303)`qml.SingleExcitationPlus`

and`qml.SingleExcitationMinus`

(#1278)`qml.DoubleExcitation`

(#1303)`qml.Toffoli`

(#1320)`qml.MultiControlledX`

. (#1287) When controlling on three or more wires, an ancilla register of worker wires is required to support the decomposition.ctrl_wires = [f"c{i}" for i in range(5)] work_wires = [f"w{i}" for i in range(3)] target_wires = ["t0"] all_wires = ctrl_wires + work_wires + target_wires dev = qml.device("default.qubit", wires=all_wires) with qml.tape.QuantumTape() as tape: qml.MultiControlledX(control_wires=ctrl_wires, wires=target_wires, work_wires=work_wires)

>>> tape = tape.expand(depth=1) >>> print(tape.draw(wire_order=qml.wires.Wires(all_wires))) c0: βββββββββββββββCβββββββββββββββββββββββCβββββββββββ€ c1: βββββββββββββββCβββββββββββββββββββββββCβββββββββββ€ c2: βββββββββββCβββββββCβββββββββββββββCβββββββCβββββββ€ c3: βββββββCβββββββββββββββCβββββββCβββββββββββββββCβββ€ c4: βββCβββββββββββββββββββββββCβββββββββββββββββββββββ€ w0: βββββββββββCβββ°XβββCβββββββββββββββCβββ°XβββCβββββββ€ w1: βββββββCβββ°Xβββββββ°XβββCβββββββCβββ°Xβββββββ°XβββCβββ€ w2: βββCβββ°Xβββββββββββββββ°XβββCβββ°Xβββββββββββββββ°Xβββ€ t0: βββ°Xβββββββββββββββββββββββ°Xβββββββββββββββββββββββ€

Added

`qml.CPhase`

as an alias for the existing`qml.ControlledPhaseShift`

operation. (#1319).The

`Device`

class now uses caching when mapping wires. (#1270)The

`Wires`

class now uses caching for computing its`hash`

. (#1270)Added custom gate application for Toffoli in

`default.qubit`

. (#1249)Added validation for noise channel parameters. Invalid noise parameters now raise a

`ValueError`

. (#1357)The device test suite now provides test cases for checking gates by comparing expectation values. (#1212)

PennyLaneβs test suite is now code-formatted using

`black -l 100`

. (#1222)PennyLaneβs

`qchem`

package and tests are now code-formatted using`black -l 100`

. (#1311)

### Breaking changes

The

`qml.inv()`

function is now deprecated with a warning to use the more general`qml.adjoint()`

. (#1325)Removes support for Python 3.6 and adds support for Python 3.9. (#1228)

The tape methods

`get_resources`

and`get_depth`

are superseded by`specs`

and will be deprecated after one release cycle. (#1245)Using the

`qml.sample()`

measurement on devices with`shots=None`

continue to raise a warning with this functionality being fully deprecated and raising an error after one release cycle. (#1079) (#1196)

### Bug fixes

QNodes now display readable information when in interactive environments or when printed. (#1359).

Fixes a bug with

`qml.math.cast`

where the`MottonenStatePreparation`

operation expected a float type instead of double. (#1400)Fixes a bug where a copy of

`qml.ControlledQubitUnitary`

was non-functional as it did not have all the necessary information. (#1411)Warns when adjoint or reversible differentiation specified or called on a device with finite shots. (#1406)

Fixes the differentiability of the operations

`IsingXX`

and`IsingZZ`

for Autograd, Jax and Tensorflow. (#1390)Fixes a bug where multiple identical Hamiltonian terms will produce a different result with

`optimize=True`

using`ExpvalCost`

. (#1405)Fixes bug where

`shots=None`

was not reset when changing shots temporarily in a QNode call like`circuit(0.1, shots=3)`

. (#1392)Fixes floating point errors with

`diff_method="finite-diff"`

and`order=1`

when parameters are`float32`

. (#1381)Fixes a bug where

`qml.ctrl`

would fail to transform gates that had no control defined and no decomposition defined. (#1376)Copying the

`JacobianTape`

now correctly also copies the`jacobian_options`

attribute. This fixes a bug allowing the JAX interface to support adjoint differentiation. (#1349)Fixes drawing QNodes that contain multiple measurements on a single wire. (#1353)

Fixes drawing QNodes with no operations. (#1354)

Fixes incorrect wires in the decomposition of the

`ControlledPhaseShift`

operation. (#1338)Fixed tests for the

`Permute`

operation that used a QNode and hence expanded tapes twice instead of once due to QNode tape expansion and an explicit tape expansion call. (#1318).Prevent Hamiltonians that share wires from being multiplied together. (#1273)

Fixed a bug where the custom range sequences could not be passed to the

`StronglyEntanglingLayers`

template. (#1332)Fixed a bug where

`qml.sum()`

and`qml.dot()`

do not support the JAX interface. (#1380)

### Documentation

Math present in the

`QubitParamShiftTape`

class docstring now renders correctly. (#1402)Fix typo in the documentation of

`qml.StronglyEntanglingLayers`

. (#1367)Fixed typo in TensorFlow interface documentation (#1312)

Fixed typos in the mathematical expressions in documentation of

`qml.DoubleExcitation`

. (#1278)Remove unsupported

`None`

option from the`qml.QNode`

docstrings. (#1271)Updated the docstring of

`qml.PolyXP`

to reference the new location of internal usage. (#1262)Removes occurrences of the deprecated device argument

`analytic`

from the documentation. (#1261)Updated PyTorch and TensorFlow interface introductions. (#1333)

Updates the quantum chemistry quickstart to reflect recent changes to the

`qchem`

module. (#1227)

### Contributors

This release contains contributions from (in alphabetical order):

Marius Aglitoiu, Vishnu Ajith, Juan Miguel Arrazola, Thomas Bromley, Jack Ceroni, Alaric Cheng, Miruna Daian, Olivia Di Matteo, Tanya Garg, Christian Gogolin, Alain Delgado Gran, Diego Guala, Anthony Hayes, Ryan Hill, Theodor Isacsson, Josh Izaac, Soran Jahangiri, Pavan Jayasinha, Nathan Killoran, Christina Lee, Ryan Levy, Alberto Maldonado, Johannes Jakob Meyer, Romain Moyard, Ashish Panigrahi, Nahum SΓ‘, Maria Schuld, Brian Shi, Antal SzΓ‘va, David Wierichs, Vincent Wong.

- orphan

## Release 0.15.1ΒΆ

### Bug fixes

Fixes two bugs in the parameter-shift Hessian. (#1260)

Fixes a bug where having an unused parameter in the Autograd interface would result in an indexing error during backpropagation.

The parameter-shift Hessian only supports the two-term parameter-shift rule currently, so raises an error if asked to differentiate any unsupported gates (such as the controlled rotation gates).

A bug which resulted in

`qml.adjoint()`

and`qml.inv()`

failing to work with templates has been fixed. (#1243)Deprecation warning instances in PennyLane have been changed to

`UserWarning`

, to account for recent changes to how Python warnings are filtered in PEP565. (#1211)

### Documentation

Updated the order of the parameters to the

`GaussianState`

operation to match the way that the PennyLane-SF plugin uses them. (#1255)

### Contributors

This release contains contributions from (in alphabetical order):

Thomas Bromley, Olivia Di Matteo, Diego Guala, Anthony Hayes, Ryan Hill, Josh Izaac, Christina Lee, Maria Schuld, Antal SzΓ‘va.

- orphan

## Release 0.15.0ΒΆ

### New features since last release

#### Better and more flexible shot control

Adds a new optimizer

`qml.ShotAdaptiveOptimizer`

, a gradient-descent optimizer where the shot rate is adaptively calculated using the variances of the parameter-shift gradient. (#1139)By keeping a running average of the parameter-shift gradient and the

*variance*of the parameter-shift gradient, this optimizer frugally distributes a shot budget across the partial derivatives of each parameter.In addition, if computing the expectation value of a Hamiltonian, weighted random sampling can be used to further distribute the shot budget across the local terms from which the Hamiltonian is constructed.

This optimizer is based on both the iCANS1 and Rosalin shot-adaptive optimizers.

Once constructed, the cost function can be passed directly to the optimizerβs

`step`

method. The attribute`opt.total_shots_used`

can be used to track the number of shots per iteration.>>> coeffs = [2, 4, -1, 5, 2] >>> obs = [ ... qml.PauliX(1), ... qml.PauliZ(1), ... qml.PauliX(0) @ qml.PauliX(1), ... qml.PauliY(0) @ qml.PauliY(1), ... qml.PauliZ(0) @ qml.PauliZ(1) ... ] >>> H = qml.Hamiltonian(coeffs, obs) >>> dev = qml.device("default.qubit", wires=2, shots=100) >>> cost = qml.ExpvalCost(qml.templates.StronglyEntanglingLayers, H, dev) >>> params = qml.init.strong_ent_layers_uniform(n_layers=2, n_wires=2) >>> opt = qml.ShotAdaptiveOptimizer(min_shots=10) >>> for i in range(5): ... params = opt.step(cost, params) ... print(f"Step {i}: cost = {cost(params):.2f}, shots_used = {opt.total_shots_used}") Step 0: cost = -5.68, shots_used = 240 Step 1: cost = -2.98, shots_used = 336 Step 2: cost = -4.97, shots_used = 624 Step 3: cost = -5.53, shots_used = 1054 Step 4: cost = -6.50, shots_used = 1798

Batches of shots can now be specified as a list, allowing measurement statistics to be course-grained with a single QNode evaluation. (#1103)

>>> shots_list = [5, 10, 1000] >>> dev = qml.device("default.qubit", wires=2, shots=shots_list)

When QNodes are executed on this device, a single execution of 1015 shots will be submitted. However, three sets of measurement statistics will be returned; using the first 5 shots, second set of 10 shots, and final 1000 shots, separately.

For example, executing a circuit with two outputs will lead to a result of shape

`(3, 2)`

:>>> @qml.qnode(dev) ... def circuit(x): ... qml.RX(x, wires=0) ... qml.CNOT(wires=[0, 1]) ... return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)), qml.expval(qml.PauliZ(0)) >>> circuit(0.5) [[0.33333333 1. ] [0.2 1. ] [0.012 0.868 ]]

This output remains fully differentiable.

The number of shots can now be specified on a per-call basis when evaluating a QNode. (#1075).

For this, the qnode should be called with an additional

`shots`

keyword argument:>>> dev = qml.device('default.qubit', wires=1, shots=10) # default is 10 >>> @qml.qnode(dev) ... def circuit(a): ... qml.RX(a, wires=0) ... return qml.sample(qml.PauliZ(wires=0)) >>> circuit(0.8) [ 1 1 1 -1 -1 1 1 1 1 1] >>> circuit(0.8, shots=3) [ 1 1 1] >>> circuit(0.8) [ 1 1 1 -1 -1 1 1 1 1 1]

#### New differentiable quantum transforms

A new module is available,
qml.transforms,
which contains *differentiable quantum transforms*. These are functions that act
on QNodes, quantum functions, devices, and tapes, transforming them while remaining
fully differentiable.

A new adjoint transform has been added. (#1111) (#1135)

This new method allows users to apply the adjoint of an arbitrary sequence of operations.

def subroutine(wire): qml.RX(0.123, wires=wire) qml.RY(0.456, wires=wire) dev = qml.device('default.qubit', wires=1) @qml.qnode(dev) def circuit(): subroutine(0) qml.adjoint(subroutine)(0) return qml.expval(qml.PauliZ(0))

This creates the following circuit:

>>> print(qml.draw(circuit)()) 0: --RX(0.123)--RY(0.456)--RY(-0.456)--RX(-0.123)--| <Z>

Directly applying to a gate also works as expected.

qml.adjoint(qml.RX)(0.123, wires=0) # applies RX(-0.123)

A new transform

`qml.ctrl`

is now available that adds control wires to subroutines. (#1157)def my_ansatz(params): qml.RX(params[0], wires=0) qml.RZ(params[1], wires=1) # Create a new operation that applies `my_ansatz` # controlled by the "2" wire. my_ansatz2 = qml.ctrl(my_ansatz, control=2) @qml.qnode(dev) def circuit(params): my_ansatz2(params) return qml.state()

This is equivalent to:

@qml.qnode(...) def circuit(params): qml.CRX(params[0], wires=[2, 0]) qml.CRZ(params[1], wires=[2, 1]) return qml.state()

The

`qml.transforms.classical_jacobian`

transform has been added. (#1186)This transform returns a function to extract the Jacobian matrix of the classical part of a QNode, allowing the classical dependence between the QNode arguments and the quantum gate arguments to be extracted.

For example, given the following QNode:

>>> @qml.qnode(dev) ... def circuit(weights): ... qml.RX(weights[0], wires=0) ... qml.RY(weights[0], wires=1) ... qml.RZ(weights[2] ** 2, wires=1) ... return qml.expval(qml.PauliZ(0))

We can use this transform to extract the relationship \(f: \mathbb{R}^n \rightarrow\mathbb{R}^m\) between the input QNode arguments \(w\) and the gate arguments \(g\), for a given value of the QNode arguments:

>>> cjac_fn = qml.transforms.classical_jacobian(circuit) >>> weights = np.array([1., 1., 1.], requires_grad=True) >>> cjac = cjac_fn(weights) >>> print(cjac) [[1. 0. 0.] [1. 0. 0.] [0. 0. 2.]]

The returned Jacobian has rows corresponding to gate arguments, and columns corresponding to QNode arguments; that is, \(J_{ij} = \frac{\partial}{\partial g_i} f(w_j)\).

#### More operations and templates

Added the

`SingleExcitation`

two-qubit operation, which is useful for quantum chemistry applications. (#1121)It can be used to perform an SO(2) rotation in the subspace spanned by the states \(|01\rangle\) and \(|10\rangle\). For example, the following circuit performs the transformation \(|10\rangle \rightarrow \cos(\phi/2)|10\rangle - \sin(\phi/2)|01\rangle\):

dev = qml.device('default.qubit', wires=2) @qml.qnode(dev) def circuit(phi): qml.PauliX(wires=0) qml.SingleExcitation(phi, wires=[0, 1])

The

`SingleExcitation`

operation supports analytic gradients on hardware using only four expectation value calculations, following results from Kottmann et al.Added the

`DoubleExcitation`

four-qubit operation, which is useful for quantum chemistry applications. (#1123)It can be used to perform an SO(2) rotation in the subspace spanned by the states \(|1100\rangle\) and \(|0011\rangle\). For example, the following circuit performs the transformation \(|1100\rangle\rightarrow \cos(\phi/2)|1100\rangle - \sin(\phi/2)|0011\rangle\):

dev = qml.device('default.qubit', wires=2) @qml.qnode(dev) def circuit(phi): qml.PauliX(wires=0) qml.PauliX(wires=1) qml.DoubleExcitation(phi, wires=[0, 1, 2, 3])

The

`DoubleExcitation`

operation supports analytic gradients on hardware using only four expectation value calculations, following results from Kottmann et al..Added the

`QuantumMonteCarlo`

template for performing quantum Monte Carlo estimation of an expectation value on simulator. (#1130)The following example shows how the expectation value of sine squared over a standard normal distribution can be approximated:

from scipy.stats import norm m = 5 M = 2 ** m n = 10 N = 2 ** n target_wires = range(m + 1) estimation_wires = range(m + 1, n + m + 1) xmax = np.pi # bound to region [-pi, pi] xs = np.linspace(-xmax, xmax, M) probs = np.array([norm().pdf(x) for x in xs]) probs /= np.sum(probs) func = lambda i: np.sin(xs[i]) ** 2 dev = qml.device("default.qubit", wires=(n + m + 1)) @qml.qnode(dev) def circuit(): qml.templates.QuantumMonteCarlo( probs, func, target_wires=target_wires, estimation_wires=estimation_wires, ) return qml.probs(estimation_wires) phase_estimated = np.argmax(circuit()[:int(N / 2)]) / N expectation_estimated = (1 - np.cos(np.pi * phase_estimated)) / 2

Added the

`QuantumPhaseEstimation`

template for performing quantum phase estimation for an input unitary matrix. (#1095)Consider the matrix corresponding to a rotation from an

`RX`

gate:>>> phase = 5 >>> target_wires = [0] >>> unitary = qml.RX(phase, wires=0).matrix

The

`phase`

parameter can be estimated using`QuantumPhaseEstimation`

. For example, using five phase-estimation qubits:n_estimation_wires = 5 estimation_wires = range(1, n_estimation_wires + 1) dev = qml.device("default.qubit", wires=n_estimation_wires + 1) @qml.qnode(dev) def circuit(): # Start in the |+> eigenstate of the unitary qml.Hadamard(wires=target_wires) QuantumPhaseEstimation( unitary, target_wires=target_wires, estimation_wires=estimation_wires, ) return qml.probs(estimation_wires) phase_estimated = np.argmax(circuit()) / 2 ** n_estimation_wires # Need to rescale phase due to convention of RX gate phase_estimated = 4 * np.pi * (1 - phase)

Added the

`ControlledPhaseShift`

gate as well as the`QFT`

operation for applying quantum Fourier transforms. (#1064)@qml.qnode(dev) def circuit_qft(basis_state): qml.BasisState(basis_state, wires=range(3)) qml.templates.QFT(wires=range(3)) return qml.state()

Added the

`ControlledQubitUnitary`

operation. This enables implementation of multi-qubit gates with a variable number of control qubits. It is also possible to specify a different state for the control qubits using the`control_values`

argument (also known as a mixed-polarity multi-controlled operation). (#1069) (#1104)For example, we can create a multi-controlled T gate using:

T = qml.T._matrix() qml.ControlledQubitUnitary(T, control_wires=[0, 1, 3], wires=2, control_values="110")

Here, the T gate will be applied to wire

`2`

if control wires`0`

and`1`

are in state`1`

, and control wire`3`

is in state`0`

. If no value is passed to`control_values`

, the gate will be applied if all control wires are in the`1`

state.Added

`MultiControlledX`

for multi-controlled`NOT`

gates. This is a special case of`ControlledQubitUnitary`

that applies a Pauli X gate conditioned on the state of an arbitrary number of control qubits. (#1104)

#### Support for higher-order derivatives on hardware

Computing second derivatives and Hessians of QNodes is now supported with the parameter-shift differentiation method, on all machine learning interfaces. (#1130) (#1129) (#1110)

Hessians are computed using the parameter-shift rule, and can be evaluated on both hardware and simulator devices.

dev = qml.device('default.qubit', wires=1) @qml.qnode(dev, diff_method="parameter-shift") def circuit(p): qml.RY(p[0], wires=0) qml.RX(p[1], wires=0) return qml.expval(qml.PauliZ(0)) x = np.array([1.0, 2.0], requires_grad=True)

>>> hessian_fn = qml.jacobian(qml.grad(circuit)) >>> hessian_fn(x) [[0.2248451 0.7651474] [0.7651474 0.2248451]]

Added the function

`finite_diff()`

to compute finite-difference approximations to the gradient and the second-order derivatives of arbitrary callable functions. (#1090)This is useful to compute the derivative of parametrized

`pennylane.Hamiltonian`

observables with respect to their parameters.For example, in quantum chemistry simulations it can be used to evaluate the derivatives of the electronic Hamiltonian with respect to the nuclear coordinates:

>>> def H(x): ... return qml.qchem.molecular_hamiltonian(['H', 'H'], x)[0] >>> x = np.array([0., 0., -0.66140414, 0., 0., 0.66140414]) >>> grad_fn = qml.finite_diff(H, N=1) >>> grad = grad_fn(x) >>> deriv2_fn = qml.finite_diff(H, N=2, idx=[0, 1]) >>> deriv2_fn(x)

The JAX interface now supports all devices, including hardware devices, via the parameter-shift differentiation method. (#1076)

For example, using the JAX interface with Cirq:

dev = qml.device('cirq.simulator', wires=1) @qml.qnode(dev, interface="jax", diff_method="parameter-shift") def circuit(x): qml.RX(x[1], wires=0) qml.Rot(x[0], x[1], x[2], wires=0) return qml.expval(qml.PauliZ(0)) weights = jnp.array([0.2, 0.5, 0.1]) print(circuit(weights))

Currently, when used with the parameter-shift differentiation method, only a single returned expectation value or variance is supported. Multiple expectations/variances, as well as probability and state returns, are not currently allowed.

### Improvements

```
dev = qml.device("default.qubit", wires=2)
inputstate = [np.sqrt(0.2), np.sqrt(0.3), np.sqrt(0.4), np.sqrt(0.1)]
@qml.qnode(dev)
def circuit():
mottonen.MottonenStatePreparation(inputstate,wires=[0, 1])
return qml.expval(qml.PauliZ(0))
Previously returned:
```

```
>>> print(qml.draw(circuit)())
0: ββRY(1.57)βββCββββββββββββββCβββCβββCβββ€ β¨Zβ©
1: ββRY(1.35)βββ°XββRY(0.422)βββ°Xβββ°Xβββ°Xβββ€
In this release, it now returns:
```

```
>>> print(qml.draw(circuit)())
0: ββRY(1.57)βββCββββββββββββββCβββ€ β¨Zβ©
1: ββRY(1.35)βββ°XββRY(0.422)βββ°Xβββ€
```

The templates are now classes inheriting from

`Operation`

, and define the ansatz in their`expand()`

method. This change does not affect the user interface. (#1138) (#1156) (#1163) (#1192)For convenience, some templates have a new method that returns the expected shape of the trainable parameter tensor, which can be used to create random tensors.

shape = qml.templates.BasicEntanglerLayers.shape(n_layers=2, n_wires=4) weights = np.random.random(shape) qml.templates.BasicEntanglerLayers(weights, wires=range(4))

`QubitUnitary`

now validates to ensure the input matrix is two dimensional. (#1128)Most layers in Pytorch or Keras accept arbitrary dimension inputs, where each dimension barring the last (in the case where the actual weight function of the layer operates on one-dimensional vectors) is broadcast over. This is now also supported by KerasLayer and TorchLayer. (#1062).

Example use:

dev = qml.device("default.qubit", wires=4) x = tf.ones((5, 4, 4)) @qml.qnode(dev) def layer(weights, inputs): qml.templates.AngleEmbedding(inputs, wires=range(4)) qml.templates.StronglyEntanglingLayers(weights, wires=range(4)) return [qml.expval(qml.PauliZ(i)) for i in range(4)] qlayer = qml.qnn.KerasLayer(layer, {"weights": (4, 4, 3)}, output_dim=4) out = qlayer(x)

The output tensor has the following shape:

>>> out.shape (5, 4, 4)

If only one argument to the function

`qml.grad`

has the`requires_grad`

attribute set to True, then the returned gradient will be a NumPy array, rather than a tuple of length 1. (#1067) (#1081)An improvement has been made to how

`QubitDevice`

generates and post-processess samples, allowing QNode measurement statistics to work on devices with more than 32 qubits. (#1088)Due to the addition of

`density_matrix()`

as a return type from a QNode, tuples are now supported by the`output_dim`

parameter in`qnn.KerasLayer`

. (#1070)Two new utility methods are provided for working with quantum tapes. (#1175)

`qml.tape.get_active_tape()`

gets the currently recording tape.`tape.stop_recording()`

is a context manager that temporarily stops the currently recording tape from recording additional tapes or quantum operations.

For example:

>>> with qml.tape.QuantumTape(): ... qml.RX(0, wires=0) ... current_tape = qml.tape.get_active_tape() ... with current_tape.stop_recording(): ... qml.RY(1.0, wires=1) ... qml.RZ(2, wires=1) >>> current_tape.operations [RX(0, wires=[0]), RZ(2, wires=[1])]

When printing

`qml.Hamiltonian`

objects, the terms are sorted by number of wires followed by coefficients. (#981)Adds

`qml.math.conj`

to the PennyLane math module. (#1143)This new method will do elementwise conjugation to the given tensor-like object, correctly dispatching to the required tensor-manipulation framework to preserve differentiability.

>>> a = np.array([1.0 + 2.0j]) >>> qml.math.conj(a) array([1.0 - 2.0j])

The four-term parameter-shift rule, as used by the controlled rotation operations, has been updated to use coefficients that minimize the variance as per https://arxiv.org/abs/2104.05695. (#1206)

A new transform

`qml.transforms.invisible`

has been added, to make it easier to transform QNodes. (#1175)

### Breaking changes

Devices do not have an

`analytic`

argument or attribute anymore. Instead,`shots`

is the source of truth for whether a simulator estimates return values from a finite number of shots, or whether it returns analytic results (`shots=None`

). (#1079) (#1196)dev_analytic = qml.device('default.qubit', wires=1, shots=None) dev_finite_shots = qml.device('default.qubit', wires=1, shots=1000) def circuit(): qml.Hadamard(wires=0) return qml.expval(qml.PauliZ(wires=0)) circuit_analytic = qml.QNode(circuit, dev_analytic) circuit_finite_shots = qml.QNode(circuit, dev_finite_shots)

Devices with

`shots=None`

return deterministic, exact results:>>> circuit_analytic() 0.0 >>> circuit_analytic() 0.0

Devices with

`shots > 0`

return stochastic results estimated from samples in each run:>>> circuit_finite_shots() -0.062 >>> circuit_finite_shots() 0.034

The

`qml.sample()`

measurement can only be used on devices on which the number of shots is set explicitly.If creating a QNode from a quantum function with an argument named

`shots`

, a`UserWarning`

is raised, warning the user that this is a reserved argument to change the number of shots on a per-call basis. (#1075)For devices inheriting from

`QubitDevice`

, the methods`expval`

,`var`

,`sample`

accept two new keyword arguments β`shot_range`

and`bin_size`

. (#1103)These new arguments allow for the statistics to be performed on only a subset of device samples. This finer level of control is accessible from the main UI by instantiating a device with a batch of shots.

For example, consider the following device:

>>> dev = qml.device("my_device", shots=[5, (10, 3), 100])

This device will execute QNodes using 135 shots, however measurement statistics will be

**course grained**across these 135 shots:All measurement statistics will first be computed using the first 5 shots β that is,

`shots_range=[0, 5]`

,`bin_size=5`

.Next, the tuple

`(10, 3)`

indicates 10 shots, repeated 3 times. This will use`shot_range=[5, 35]`

, performing the expectation value in bins of size 10 (`bin_size=10`

).Finally, we repeat the measurement statistics for the final 100 shots,

`shot_range=[35, 135]`

,`bin_size=100`

.

The old PennyLane core has been removed, including the following modules: (#1100)

`pennylane.variables`

`pennylane.qnodes`

As part of this change, the location of the new core within the Python module has been moved:

Moves

`pennylane.tape.interfaces`

β`pennylane.interfaces`

Merges

`pennylane.CircuitGraph`

and`pennylane.TapeCircuitGraph`

β`pennylane.CircuitGraph`

Merges

`pennylane.OperationRecorder`

and`pennylane.TapeOperationRecorder`

β`pennylane.tape.operation_recorder`

Merges

`pennylane.measure`

and`pennylane.tape.measure`

β`pennylane.measure`

Merges

`pennylane.operation`

and`pennylane.tape.operation`

β`pennylane.operation`

Merges

`pennylane._queuing`

and`pennylane.tape.queuing`

β`pennylane.queuing`

This has no affect on import location.

In addition,

All tape-mode functions have been removed (

`qml.enable_tape()`

,`qml.tape_mode_active()`

),All tape fixtures have been deleted,

Tests specifically for non-tape mode have been deleted.

The device test suite no longer accepts the

`analytic`

keyword. (#1216)

### Bug fixes

Fixes a bug where using the circuit drawer with a

`ControlledQubitUnitary`

operation raised an error. (#1174)Fixes a bug and a test where the

`QuantumTape.is_sampled`

attribute was not being updated. (#1126)Fixes a bug where

`BasisEmbedding`

would not accept inputs whose bits are all ones or all zeros. (#1114)The

`ExpvalCost`

class raises an error if instantiated with non-expectation measurement statistics. (#1106)Fixes a bug where decompositions would reset the differentiation method of a QNode. (#1117)

Fixes a bug where the second-order CV parameter-shift rule would error if attempting to compute the gradient of a QNode with more than one second-order observable. (#1197)

Fixes a bug where repeated Torch interface applications after expansion caused an error. (#1223)

Sampling works correctly with batches of shots specified as a list. (#1232)

### Documentation

Updated the diagram used in the Architectural overview page of the Development guide such that it doesnβt mention Variables. (#1235)

Typos addressed in templates documentation. (#1094)

Upgraded the documentation to use Sphinx 3.5.3 and the new m2r2 package. (#1186)

Added

`flaky`

as dependency for running tests in the documentation. (#1113)

### Contributors

This release contains contributions from (in alphabetical order):

Shahnawaz Ahmed, Juan Miguel Arrazola, Thomas Bromley, Olivia Di Matteo, Alain Delgado Gran, Kyle Godbey, Diego Guala, Theodor Isacsson, Josh Izaac, Soran Jahangiri, Nathan Killoran, Christina Lee, Daniel Polatajko, Chase Roberts, Sankalp Sanand, Pritish Sehzpaul, Maria Schuld, Antal SzΓ‘va, David Wierichs.

- orphan

## Release 0.14.1ΒΆ

### Bug fixes

Fixes a testing bug where tests that required JAX would fail if JAX was not installed. The tests will now instead be skipped if JAX can not be imported. (#1066)

Fixes a bug where inverse operations could not be differentiated using backpropagation on

`default.qubit`

. (#1072)The QNode has a new keyword argument,

`max_expansion`

, that determines the maximum number of times the internal circuit should be expanded when executed on a device. In addition, the default number of max expansions has been increased from 2 to 10, allowing devices that require more than two operator decompositions to be supported. (#1074)Fixes a bug where

`Hamiltonian`

objects created with non-list arguments raised an error for arithmetic operations. (#1082)Fixes a bug where

`Hamiltonian`

objects with no coefficients or operations would return a faulty result when used with`ExpvalCost`

. (#1082)

### Documentation

Updates mentions of

`generate_hamiltonian`

to`molecular_hamiltonian`

in the docstrings of the`ExpvalCost`

and`Hamiltonian`

classes. (#1077)

### Contributors

This release contains contributions from (in alphabetical order):

Thomas Bromley, Josh Izaac, Antal SzΓ‘va.

- orphan

## Release 0.14.0ΒΆ

### New features since last release

#### Perform quantum machine learning with JAX

QNodes created with

`default.qubit`

now support a JAX interface, allowing JAX to be used to create, differentiate, and optimize hybrid quantum-classical models. (#947)This is supported internally via a new

`default.qubit.jax`

device. This device runs end to end in JAX, meaning that it supports all of the awesome JAX transformations (`jax.vmap`

,`jax.jit`

,`jax.hessian`

, etc).Here is an example of how to use the new JAX interface:

dev = qml.device("default.qubit", wires=1) @qml.qnode(dev, interface="jax", diff_method="backprop") def circuit(x): qml.RX(x[1], wires=0) qml.Rot(x[0], x[1], x[2], wires=0) return qml.expval(qml.PauliZ(0)) weights = jnp.array([0.2, 0.5, 0.1]) grad_fn = jax.grad(circuit) print(grad_fn(weights))

Currently, only

`diff_method="backprop"`

is supported, with plans to support more in the future.

#### New, faster, quantum gradient methods

A new differentiation method has been added for use with simulators. The

`"adjoint"`

method operates after a forward pass by iteratively applying inverse gates to scan backwards through the circuit. (#1032)This method is similar to the reversible method, but has a lower time overhead and a similar memory overhead. It follows the approach provided by Jones and Gacon. This method is only compatible with certain statevector-based devices such as

`default.qubit`

.Example use:

import pennylane as qml wires = 1 device = qml.device("default.qubit", wires=wires) @qml.qnode(device, diff_method="adjoint") def f(params): qml.RX(0.1, wires=0) qml.Rot(*params, wires=0) qml.RX(-0.3, wires=0) return qml.expval(qml.PauliZ(0)) params = [0.1, 0.2, 0.3] qml.grad(f)(params)

The default logic for choosing the βbestβ differentiation method has been altered to improve performance. (#1008)

If the quantum device provides its own gradient, this is now the preferred differentiation method.

If the quantum device natively supports classical backpropagation, this is now preferred over the parameter-shift rule.

This will lead to marked speed improvement during optimization when using

`default.qubit`

, with a sight penalty on the forward-pass evaluation.

More details are available below in the βImprovementsβ section for plugin developers.

PennyLane now supports analytical quantum gradients for noisy channels, in addition to its existing support for unitary operations. The noisy channels

`BitFlip`

,`PhaseFlip`

, and`DepolarizingChannel`

all support analytic gradients out of the box. (#968)A method has been added for calculating the Hessian of quantum circuits using the second-order parameter shift formula. (#961)

The following example shows the calculation of the Hessian:

n_wires = 5 weights = [2.73943676, 0.16289932, 3.4536312, 2.73521126, 2.6412488] dev = qml.device("default.qubit", wires=n_wires) with qml.tape.QubitParamShiftTape() as tape: for i in range(n_wires): qml.RX(weights[i], wires=i) qml.CNOT(wires=[0, 1]) qml.CNOT(wires=[2, 1]) qml.CNOT(wires=[3, 1]) qml.CNOT(wires=[4, 3]) qml.expval(qml.PauliZ(1)) print(tape.hessian(dev))

The Hessian is not yet supported via classical machine learning interfaces, but will be added in a future release.

#### More operations and templates

Two new error channels,

`BitFlip`

and`PhaseFlip`

have been added. (#954)They can be used in the same manner as existing error channels:

dev = qml.device("default.mixed", wires=2) @qml.qnode(dev) def circuit(): qml.RX(0.3, wires=0) qml.RY(0.5, wires=1) qml.BitFlip(0.01, wires=0) qml.PhaseFlip(0.01, wires=1) return qml.expval(qml.PauliZ(0))

Apply permutations to wires using the

`Permute`

subroutine. (#952)import pennylane as qml dev = qml.device('default.qubit', wires=5) @qml.qnode(dev) def apply_perm(): # Send contents of wire 4 to wire 0, of wire 2 to wire 1, etc. qml.templates.Permute([4, 2, 0, 1, 3], wires=dev.wires) return qml.expval(qml.PauliZ(0))

#### QNode transformations

The

`qml.metric_tensor`

function transforms a QNode to produce the Fubini-Study metric tensor with full autodifferentiation supportβeven on hardware. (#1014)Consider the following QNode:

dev = qml.device("default.qubit", wires=3) @qml.qnode(dev, interface="autograd") def circuit(weights): # layer 1 qml.RX(weights[0, 0], wires=0) qml.RX(weights[0, 1], wires=1) qml.CNOT(wires=[0, 1]) qml.CNOT(wires=[1, 2]) # layer 2 qml.RZ(weights[1, 0], wires=0) qml.RZ(weights[1, 1], wires=2) qml.CNOT(wires=[0, 1]) qml.CNOT(wires=[1, 2]) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)), qml.expval(qml.PauliY(2))

We can use the

`metric_tensor`

function to generate a new function, that returns the metric tensor of this QNode:>>> met_fn = qml.metric_tensor(circuit) >>> weights = np.array([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]], requires_grad=True) >>> met_fn(weights) tensor([[0.25 , 0. , 0. , 0. ], [0. , 0.25 , 0. , 0. ], [0. , 0. , 0.0025, 0.0024], [0. , 0. , 0.0024, 0.0123]], requires_grad=True)

The returned metric tensor is also fully differentiable, in all interfaces. For example, differentiating the

`(3, 2)`

element:>>> grad_fn = qml.grad(lambda x: met_fn(x)[3, 2]) >>> grad_fn(weights) array([[ 0.04867729, -0.00049502, 0. ], [ 0. , 0. , 0. ]])

Differentiation is also supported using Torch, Jax, and TensorFlow.

Adds the new function

`qml.math.cov_matrix()`

. This function accepts a list of commuting observables, and the probability distribution in the shared observable eigenbasis after the application of an ansatz. It uses these to construct the covariance matrix in a*framework independent*manner, such that the output covariance matrix is autodifferentiable. (#1012)For example, consider the following ansatz and observable list:

obs_list = [qml.PauliX(0) @ qml.PauliZ(1), qml.PauliY(2)] ansatz = qml.templates.StronglyEntanglingLayers

We can construct a QNode to output the probability distribution in the shared eigenbasis of the observables:

dev = qml.device("default.qubit", wires=3) @qml.qnode(dev, interface="autograd") def circuit(weights): ansatz(weights, wires=[0, 1, 2]) # rotate into the basis of the observables for o in obs_list: o.diagonalizing_gates() return qml.probs(wires=[0, 1, 2])

We can now compute the covariance matrix:

>>> weights = qml.init.strong_ent_layers_normal(n_layers=2, n_wires=3) >>> cov = qml.math.cov_matrix(circuit(weights), obs_list) >>> cov array([[0.98707611, 0.03665537], [0.03665537, 0.99998377]])

Autodifferentiation is fully supported using all interfaces:

>>> cost_fn = lambda weights: qml.math.cov_matrix(circuit(weights), obs_list)[0, 1] >>> qml.grad(cost_fn)(weights)[0] array([[[ 4.94240914e-17, -2.33786398e-01, -1.54193959e-01], [-3.05414996e-17, 8.40072236e-04, 5.57884080e-04], [ 3.01859411e-17, 8.60411436e-03, 6.15745204e-04]], [[ 6.80309533e-04, -1.23162742e-03, 1.08729813e-03], [-1.53863193e-01, -1.38700657e-02, -1.36243323e-01], [-1.54665054e-01, -1.89018172e-02, -1.56415558e-01]]])

A new

`qml.draw`

function is available, allowing QNodes to be easily drawn without execution by providing example input. (#962)@qml.qnode(dev) def circuit(a, w): qml.Hadamard(0) qml.CRX(a, wires=[0, 1]) qml.Rot(*w, wires=[1]) qml.CRX(-a, wires=[0, 1]) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))

The QNode circuit structure may depend on the input arguments; this is taken into account by passing example QNode arguments to the

`qml.draw()`

drawing function:>>> drawer = qml.draw(circuit) >>> result = drawer(a=2.3, w=[1.2, 3.2, 0.7]) >>> print(result) 0: ββHβββCβββββββββββββββββββββββββββββCβββββββββββ€ β¨Z β Zβ© 1: ββββββ°RX(2.3)ββRot(1.2, 3.2, 0.7)βββ°RX(-2.3)βββ°β€ β¨Z β Zβ©

#### A faster, leaner, and more flexible core

The new core of PennyLane, rewritten from the ground up and developed over the last few release cycles, has achieved feature parity and has been made the new default in PennyLane v0.14. The old core has been marked as deprecated, and will be removed in an upcoming release. (#1046) (#1040) (#1034) (#1035) (#1027) (#1026) (#1021) (#1054) (#1049)

While high-level PennyLane code and tutorials remain unchanged, the new core provides several advantages and improvements:

**Faster and more optimized**: The new core provides various performance optimizations, reducing pre- and post-processing overhead, and reduces the number of quantum evaluations in certain cases.**Support for in-QNode classical processing**: this allows for differentiable classical processing within the QNode.dev = qml.device("default.qubit", wires=1) @qml.qnode(dev, interface="tf") def circuit(p): qml.RX(tf.sin(p[0])**2 + p[1], wires=0) return qml.expval(qml.PauliZ(0))

The classical processing functions used within the QNode must match the QNode interface. Here, we use TensorFlow:

>>> params = tf.Variable([0.5, 0.1], dtype=tf.float64) >>> with tf.GradientTape() as tape: ... res = circuit(params) >>> grad = tape.gradient(res, params) >>> print(res) tf.Tensor(0.9460913127754935, shape=(), dtype=float64) >>> print(grad) tf.Tensor([-0.27255248 -0.32390003], shape=(2,), dtype=float64)

As a result of this change, quantum decompositions that require classical processing are fully supported and end-to-end differentiable in tape mode.

**No more Variable wrapping**: QNode arguments no longer become`Variable`

objects within the QNode.dev = qml.device("default.qubit", wires=1) @qml.qnode(dev) def circuit(x): print("Parameter value:", x) qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0))

Internal QNode parameters can be easily inspected, printed, and manipulated:

>>> circuit(0.5) Parameter value: 0.5 tensor(0.87758256, requires_grad=True)

**Less restrictive QNode signatures**: There is no longer any restriction on the QNode signature; the QNode can be defined and called following the same rules as standard Python functions.For example, the following QNode uses positional, named, and variable keyword arguments:

x = torch.tensor(0.1, requires_grad=True) y = torch.tensor([0.2, 0.3], requires_grad=True) z = torch.tensor(0.4, requires_grad=True) @qml.qnode(dev, interface="torch") def circuit(p1, p2=y, **kwargs): qml.RX(p1, wires=0) qml.RY(p2[0] * p2[1], wires=0) qml.RX(kwargs["p3"], wires=0) return qml.var(qml.PauliZ(0))

When we call the QNode, we may pass the arguments by name even if defined positionally; any argument not provided will use the default value.

>>> res = circuit(p1=x, p3=z) >>> print(res) tensor(0.2327, dtype=torch.float64, grad_fn=<SelectBackward>) >>> res.backward() >>> print(x.grad, y.grad, z.grad) tensor(0.8396) tensor([0.0289, 0.0193]) tensor(0.8387)

This extends to the

`qnn`

module, where`KerasLayer`

and`TorchLayer`

modules can be created from QNodes with unrestricted signatures.**Smarter measurements:**QNodes can now measure wires more than once, as long as all observables are commuting:@qml.qnode(dev) def circuit(x): qml.RX(x, wires=0) return [ qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) ]

Further, the

`qml.ExpvalCost()`

function allows for optimizing measurements to reduce the number of quantum evaluations required.

With the new PennyLane core, there are a few small breaking changes, detailed below in the βBreaking Changesβ section.

### Improvements

The built-in PennyLane optimizers allow more flexible cost functions. The cost function passed to most optimizers may accept any combination of trainable arguments, non-trainable arguments, and keyword arguments. (#959) (#1053)

The full changes apply to:

`AdagradOptimizer`

`AdamOptimizer`

`GradientDescentOptimizer`

`MomentumOptimizer`

`NesterovMomentumOptimizer`

`RMSPropOptimizer`

`RotosolveOptimizer`

The

`requires_grad=False`

property must mark any non-trainable constant argument. The`RotoselectOptimizer`

allows passing only keyword arguments.Example use:

def cost(x, y, data, scale=1.0): return scale * (x[0]-data)**2 + scale * (y-data)**2 x = np.array([1.], requires_grad=True) y = np.array([1.0]) data = np.array([2.], requires_grad=False) opt = qml.GradientDescentOptimizer() # the optimizer step and step_and_cost methods can # now update multiple parameters at once x_new, y_new, data = opt.step(cost, x, y, data, scale=0.5) (x_new, y_new, data), value = opt.step_and_cost(cost, x, y, data, scale=0.5) # list and tuple unpacking is also supported params = (x, y, data) params = opt.step(cost, *params)

The circuit drawer has been updated to support the inclusion of unused or inactive wires, by passing the

`show_all_wires`

argument. (#1033)dev = qml.device('default.qubit', wires=[-1, "a", "q2", 0]) @qml.qnode(dev) def circuit(): qml.Hadamard(wires=-1) qml.CNOT(wires=[-1, "q2"]) return qml.expval(qml.PauliX(wires="q2"))

>>> print(qml.draw(circuit, show_all_wires=True)()) >>> -1: ββHβββCβββ€ a: ββββββββββ€ q2: ββββββ°Xβββ€ β¨Xβ© 0: ββββββββββ€

The logic for choosing the βbestβ differentiation method has been altered to improve performance. (#1008)

If the device provides its own gradient, this is now the preferred differentiation method.

If a device provides additional interface-specific versions that natively support classical backpropagation, this is now preferred over the parameter-shift rule.

Devices define additional interface-specific devices via their

`capabilities()`

dictionary. For example,`default.qubit`

supports supplementary devices for TensorFlow, Autograd, and JAX:{ "passthru_devices": { "tf": "default.qubit.tf", "autograd": "default.qubit.autograd", "jax": "default.qubit.jax", }, }

As a result of this change, if the QNode

`diff_method`

is not explicitly provided, it is possible that the QNode will run on a*supplementary device*of the device that was specifically provided:dev = qml.device("default.qubit", wires=2) qml.QNode(dev) # will default to backprop on default.qubit.autograd qml.QNode(dev, interface="tf") # will default to backprop on default.qubit.tf qml.QNode(dev, interface="jax") # will default to backprop on default.qubit.jax

The

`default.qubit`

device has been updated so that internally it applies operations in a more functional style, i.e., by accepting an input state and returning an evolved state. (#1025)A new test series,

`pennylane/devices/tests/test_compare_default_qubit.py`

, has been added, allowing to test if a chosen device gives the same result as`default.qubit`

. (#897)Three tests are added:

`test_hermitian_expectation`

,`test_pauliz_expectation_analytic`

, and`test_random_circuit`

.

Adds the following agnostic tensor manipulation functions to the

`qml.math`

module:`abs`

,`angle`

,`arcsin`

,`concatenate`

,`dot`

,`squeeze`

,`sqrt`

,`sum`

,`take`

,`where`

. These functions are required to fully support end-to-end differentiable Mottonen and Amplitude embedding. (#922) (#1011)The

`qml.math`

module now supports JAX. (#985)Several improvements have been made to the

`Wires`

class to reduce overhead and simplify the logic of how wire labels are interpreted: (#1019) (#1010) (#1005) (#983) (#967)If the input

`wires`

to a wires class instantiation`Wires(wires)`

can be iterated over, its elements are interpreted as wire labels. Otherwise,`wires`

is interpreted as a single wire label. The only exception to this are strings, which are always interpreted as a single wire label, so users can address wires with labels such as`"ancilla"`

.Any type can now be a wire label as long as it is hashable. The hash is used to establish the uniqueness of two labels.

Indexing wires objects now returns a label, instead of a new

`Wires`

object. For example:>>> w = Wires([0, 1, 2]) >>> w[1] >>> 1

The check for uniqueness of wires moved from

`Wires`

instantiation to the`qml.wires._process`

function in order to reduce overhead from repeated creation of`Wires`

instances.Calls to the

`Wires`

class are substantially reduced, for example by avoiding to call Wires on Wires instances on`Operation`

instantiation, and by using labels instead of`Wires`

objects inside the default qubit device.

Adds the

`PauliRot`

generator to the`qml.operation`

module. This generator is required to construct the metric tensor. (#963)The templates are modified to make use of the new

`qml.math`

module, for framework-agnostic tensor manipulation. This allows the template library to be differentiable in backpropagation mode (`diff_method="backprop"`

). (#873)The circuit drawer now allows for the wire order to be (optionally) modified: (#992)

>>> dev = qml.device('default.qubit', wires=["a", -1, "q2"]) >>> @qml.qnode(dev) ... def circuit(): ... qml.Hadamard(wires=-1) ... qml.CNOT(wires=["a", "q2"]) ... qml.RX(0.2, wires="a") ... return qml.expval(qml.PauliX(wires="q2"))

Printing with default wire order of the device:

>>> print(circuit.draw()) a: ββββββCββRX(0.2)βββ€ -1: ββHββββββββββββββββ€ q2: ββββββ°Xββββββββββββ€ β¨Xβ©

Changing the wire order:

>>> print(circuit.draw(wire_order=["q2", "a", -1])) q2: βββXββββββββββββ€ β¨Xβ© a: βββ°CββRX(0.2)βββ€ -1: βββHββββββββββββ€

### Breaking changes

QNodes using the new PennyLane core will no longer accept ragged arrays as inputs.

When using the new PennyLane core and the Autograd interface, non-differentiable data passed as a QNode argument or a gate must have the

`requires_grad`

property set to`False`

:@qml.qnode(dev) def circuit(weights, data): basis_state = np.array([1, 0, 1, 1], requires_grad=False) qml.BasisState(basis_state, wires=[0, 1, 2, 3]) qml.templates.AmplitudeEmbedding(data, wires=[0, 1, 2, 3]) qml.templates.BasicEntanglerLayers(weights, wires=[0, 1, 2, 3]) return qml.probs(wires=0) data = np.array(data, requires_grad=False) weights = np.array(weights, requires_grad=True) circuit(weights, data)

### Bug fixes

Fixes an issue where if the constituent observables of a tensor product do not exist in the queue, an error is raised. With this fix, they are first queued before annotation occurs. (#1038)

Fixes an issue with tape expansions where information about sampling (specifically the

`is_sampled`

tape attribute) was not preserved. (#1027)Tape expansion was not properly taking into devices that supported inverse operations, causing inverse operations to be unnecessarily decomposed. The QNode tape expansion logic, as well as the

`Operation.expand()`

method, has been modified to fix this. (#956)Fixes an issue where the Autograd interface was not unwrapping non-differentiable PennyLane tensors, which can cause issues on some devices. (#941)

`qml.vqe.Hamiltonian`

prints any observable with any number of strings. (#987)Fixes a bug where parameter-shift differentiation would fail if the QNode contained a single probability output. (#1007)

Fixes an issue when using trainable parameters that are lists/arrays with

`tape.vjp`

. (#1042)The

`TensorN`

observable is updated to support being copied without any parameters or wires passed. (#1047)Fixed deprecation warning when importing

`Sequence`

from`collections`

instead of`collections.abc`

in`vqe/vqe.py`

. (#1051)

### Contributors

This release contains contributions from (in alphabetical order):

Juan Miguel Arrazola, Thomas Bromley, Olivia Di Matteo, Theodor Isacsson, Josh Izaac, Christina Lee, Alejandro Montanez, Steven Oud, Chase Roberts, Sankalp Sanand, Maria Schuld, Antal SzΓ‘va, David Wierichs, Jiahao Yao.

- orphan

## Release 0.13.0ΒΆ

### New features since last release

#### Automatically optimize the number of measurements

QNodes in tape mode now support returning observables on the same wire whenever the observables are qubit-wise commuting Pauli words. Qubit-wise commuting observables can be evaluated with a

*single*device run as they are diagonal in the same basis, via a shared set of single-qubit rotations. (#882)The following example shows a single QNode returning the expectation values of the qubit-wise commuting Pauli words

`XX`

and`XI`

:qml.enable_tape() @qml.qnode(dev) def f(x): qml.Hadamard(wires=0) qml.Hadamard(wires=1) qml.CRot(0.1, 0.2, 0.3, wires=[1, 0]) qml.RZ(x, wires=1) return qml.expval(qml.PauliX(0) @ qml.PauliX(1)), qml.expval(qml.PauliX(0))

>>> f(0.4) tensor([0.89431013, 0.9510565 ], requires_grad=True)

The

`ExpvalCost`

class (previously`VQECost`

) now provides observable optimization using the`optimize`

argument, resulting in potentially fewer device executions. (#902)This is achieved by separating the observables composing the Hamiltonian into qubit-wise commuting groups and evaluating those groups on a single QNode using functionality from the

`qml.grouping`

module:qml.enable_tape() commuting_obs = [qml.PauliX(0), qml.PauliX(0) @ qml.PauliZ(1)] H = qml.vqe.Hamiltonian([1, 1], commuting_obs) dev = qml.device("default.qubit", wires=2) ansatz = qml.templates.StronglyEntanglingLayers cost_opt = qml.ExpvalCost(ansatz, H, dev, optimize=True) cost_no_opt = qml.ExpvalCost(ansatz, H, dev, optimize=False) params = qml.init.strong_ent_layers_uniform(3, 2)

Grouping these commuting observables leads to fewer device executions:

>>> cost_opt(params) >>> ex_opt = dev.num_executions >>> cost_no_opt(params) >>> ex_no_opt = dev.num_executions - ex_opt >>> print("Number of executions:", ex_no_opt) Number of executions: 2 >>> print("Number of executions (optimized):", ex_opt) Number of executions (optimized): 1

#### New quantum gradient features

Compute the analytic gradient of quantum circuits in parallel on supported devices. (#840)

This release introduces support for batch execution of circuits, via a new device API method

`Device.batch_execute()`

. Devices that implement this new API support submitting a batch of circuits for*parallel*evaluation simultaneously, which can significantly reduce the computation time.Furthermore, if using tape mode and a compatible device, gradient computations will automatically make use of the new batch APIβproviding a speedup during optimization.

Gradient recipes are now much more powerful, allowing for operations to define their gradient via an arbitrary linear combination of circuit evaluations. (#909) (#915)

With this change, gradient recipes can now be of the form \(\frac{\partial}{\partial\phi_k}f(\phi_k) = \sum_{i} c_i f(a_i \phi_k + s_i )\), and are no longer restricted to two-term shifts with identical (but opposite in sign) shift values.

As a result, PennyLane now supports native analytic quantum gradients for the controlled rotation operations

`CRX`

,`CRY`

,`CRZ`

, and`CRot`

. This allows for parameter-shift analytic gradients on hardware, without decomposition.Note that this is a breaking change for developers; please see the

*Breaking Changes*section for more details.The

`qnn.KerasLayer`

class now supports differentiating the QNode through classical backpropagation in tape mode. (#869)qml.enable_tape() dev = qml.device("default.qubit.tf", wires=2) @qml.qnode(dev, interface="tf", diff_method="backprop") def f(inputs, weights): qml.templates.AngleEmbedding(inputs, wires=range(2)) qml.templates.StronglyEntanglingLayers(weights, wires=range(2)) return [qml.expval(qml.PauliZ(i)) for i in range(2)] weight_shapes = {"weights": (3, 2, 3)} qlayer = qml.qnn.KerasLayer(f, weight_shapes, output_dim=2) inputs = tf.constant(np.random.random((4, 2)), dtype=tf.float32) with tf.GradientTape() as tape: out = qlayer(inputs) tape.jacobian(out, qlayer.trainable_weights)

#### New operations, templates, and measurements

Adds the

`qml.density_matrix`

QNode return with partial trace capabilities. (#878)The density matrix over the provided wires is returned, with all other subsystems traced out.

`qml.density_matrix`

currently works for both the`default.qubit`

and`default.mixed`

devices.qml.enable_tape() dev = qml.device("default.qubit", wires=2) def circuit(x): qml.PauliY(wires=0) qml.Hadamard(wires=1) return qml.density_matrix(wires=[1]) # wire 0 is traced out

Adds the square-root X gate

`SX`

. (#871)dev = qml.device("default.qubit", wires=1) @qml.qnode(dev) def circuit(): qml.SX(wires=[0]) return qml.expval(qml.PauliZ(wires=[0]))

Two new hardware-efficient particle-conserving templates have been implemented to perform VQE-based quantum chemistry simulations. The new templates apply several layers of the particle-conserving entanglers proposed in Figs. 2a and 2b of Barkoutsos

*et al*., arXiv:1805.04340 (#875) (#876)

#### Estimate and track resources

The

`QuantumTape`

class now contains basic resource estimation functionality. The method`tape.get_resources()`

returns a dictionary with a list of the constituent operations and the number of times they appear in the circuit. Similarly,`tape.get_depth()`

computes the circuit depth. (#862)>>> with qml.tape.QuantumTape() as tape: ... qml.Hadamard(wires=0) ... qml.RZ(0.26, wires=1) ... qml.CNOT(wires=[1, 0]) ... qml.Rot(1.8, -2.7, 0.2, wires=0) ... qml.Hadamard(wires=1) ... qml.CNOT(wires=[0, 1]) ... qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) >>> tape.get_resources() {'Hadamard': 2, 'RZ': 1, 'CNOT': 2, 'Rot': 1} >>> tape.get_depth() 4

The number of device executions over a QNodeβs lifetime can now be returned using

`num_executions`

. (#853)>>> dev = qml.device("default.qubit", wires=2) >>> @qml.qnode(dev) ... def circuit(x, y): ... qml.RX(x, wires=[0]) ... qml.RY(y, wires=[1]) ... qml.CNOT(wires=[0, 1]) ... return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)) >>> for _ in range(10): ... circuit(0.432, 0.12) >>> print(dev.num_executions) 10

### Improvements

Support for tape mode has improved across PennyLane. The following features now work in tape mode:

A new function,

`qml.refresh_devices()`

, has been added, allowing PennyLane to rescan installed PennyLane plugins and refresh the device list. In addition, the`qml.device`

loader will attempt to refresh devices if the required plugin device cannot be found. This will result in an improved experience if installing PennyLane and plugins within a running Python session (for example, on Google Colab), and avoid the need to restart the kernel/runtime. (#907)When using

`grad_fn = qml.grad(cost)`

to compute the gradient of a cost function with the Autograd interface, the value of the intermediate forward pass is now available via the`grad_fn.forward`

property (#914):def cost_fn(x, y): return 2 * np.sin(x[0]) * np.exp(-x[1]) + x[0] ** 3 + np.cos(y) params = np.array([0.1, 0.5], requires_grad=True) data = np.array(0.65, requires_grad=False) grad_fn = qml.grad(cost_fn) grad_fn(params, data) # perform backprop and evaluate the gradient grad_fn.forward # the cost function value

Gradient-based optimizers now have a

`step_and_cost`

method that returns both the next step as well as the objective (cost) function output. (#916)>>> opt = qml.GradientDescentOptimizer() >>> params, cost = opt.step_and_cost(cost_fn, params)

PennyLane provides a new experimental module

`qml.proc`

which provides framework-agnostic processing functions for array and tensor manipulations. (#886)Given the input tensor-like object, the call is dispatched to the corresponding array manipulation framework, allowing for end-to-end differentiation to be preserved.

>>> x = torch.tensor([1., 2.]) >>> qml.proc.ones_like(x) tensor([1, 1]) >>> y = tf.Variable([[0], [5]]) >>> qml.proc.ones_like(y, dtype=np.complex128) <tf.Tensor: shape=(2, 1), dtype=complex128, numpy= array([[1.+0.j], [1.+0.j]])>

Note that these functions are experimental, and only a subset of common functionality is supported. Furthermore, the names and behaviour of these functions may differ from similar functions in common frameworks; please refer to the function docstrings for more details.

The gradient methods in tape mode now fully separate the quantum and classical processing. Rather than returning the evaluated gradients directly, they now return a tuple containing the required quantum and classical processing steps. (#840)

def gradient_method(idx, param, **options): # generate the quantum tapes that must be computed # to determine the quantum gradient tapes = quantum_gradient_tapes(self) def processing_fn(results): # perform classical processing on the evaluated tapes # returning the evaluated quantum gradient return classical_processing(results) return tapes, processing_fn

The

`JacobianTape.jacobian()`

method has been similarly modified to accumulate all gradient quantum tapes and classical processing functions, evaluate all quantum tapes simultaneously, and then apply the post-processing functions to the evaluated tape results.The MultiRZ gate now has a defined generator, allowing it to be used in quantum natural gradient optimization. (#912)

The CRot gate now has a

`decomposition`

method, which breaks the gate down into rotations and CNOT gates. This allows`CRot`

to be used on devices that do not natively support it. (#908)The classical processing in the

`MottonenStatePreparation`

template has been largely rewritten to use dense matrices and tensor manipulations wherever possible. This is in preparation to support differentiation through the template in the future. (#864)Device-based caching has replaced QNode caching. Caching is now accessed by passing a

`cache`

argument to the device. (#851)The

`cache`

argument should be an integer specifying the size of the cache. For example, a cache of size 10 is created using:>>> dev = qml.device("default.qubit", wires=2, cache=10)

The

`Operation`

,`Tensor`

, and`MeasurementProcess`

classes now have the`__copy__`

special method defined. (#840)This allows us to ensure that, when a shallow copy is performed of an operation, the mutable list storing the operation parameters is

*also*shallow copied. Both the old operation and the copied operation will continue to share the same parameter data,>>> import copy >>> op = qml.RX(0.2, wires=0) >>> op2 = copy.copy(op) >>> op.data[0] is op2.data[0] True

however the

*list container*is not a reference:>>> op.data is op2.data False

This allows the parameters of the copied operation to be modified, without mutating the parameters of the original operation.

The

`QuantumTape.copy`

method has been tweaked so that (#840):Optionally, the tapeβs operations are shallow copied in addition to the tape by passing the

`copy_operations=True`

boolean flag. This allows the copied tapeβs parameters to be mutated without affecting the original tapeβs parameters. (Note: the two tapes will share parameter data*until*one of the tapes has their parameter list modified.)Copied tapes can be cast to another

`QuantumTape`

subclass by passing the`tape_cls`

keyword argument.

### Breaking changes

Updated how parameter-shift gradient recipes are defined for operations, allowing for gradient recipes that are specified as an arbitrary number of terms. (#909)

Previously,

`Operation.grad_recipe`

was restricted to two-term parameter-shift formulas. With this change, the gradient recipe now contains elements of the form \([c_i, a_i, s_i]\), resulting in a gradient recipe of \(\frac{\partial}{\partial\phi_k}f(\phi_k) = \sum_{i} c_i f(a_i \phi_k + s_i )\).As this is a breaking change, all custom operations with defined gradient recipes must be updated to continue working with PennyLane 0.13. Note though that if

`grad_recipe = None`

, the default gradient recipe remains unchanged, and corresponds to the two terms \([c_0, a_0, s_0]=[1/2, 1, \pi/2]\) and \([c_1, a_1, s_1]=[-1/2, 1, -\pi/2]\) for every parameter.The

`VQECost`

class has been renamed to`ExpvalCost`

to reflect its general applicability beyond VQE. Use of`VQECost`

is still possible but will result in a deprecation warning. (#913)

### Bug fixes

The

`default.qubit.tf`

device is updated to handle TensorFlow objects (e.g.,`tf.Variable`

) as gate parameters correctly when using the`MultiRZ`

and`CRot`

operations. (#921)PennyLane tensor objects are now unwrapped in BaseQNode when passed as a keyword argument to the quantum function. (#903) (#893)

The new tape mode now prevents multiple observables from being evaluated on the same wire if the observables are not qubit-wise commuting Pauli words. (#882)

Fixes a bug in

`default.qubit`

whereby inverses of common gates were not being applied via efficient gate-specific methods, instead falling back to matrix-vector multiplication. The following gates were affected:`PauliX`

,`PauliY`

,`PauliZ`

,`Hadamard`

,`SWAP`

,`S`

,`T`

,`CNOT`

,`CZ`

. (#872)The

`PauliRot`

operation now gracefully handles single-qubit Paulis, and all-identity Paulis (#860).Fixes a bug whereby binary Python operators were not properly propagating the

`requires_grad`

attribute to the output tensor. (#889)Fixes a bug which prevents

`TorchLayer`

from doing`backward`

when CUDA is enabled. (#899)Fixes a bug where multi-threaded execution of

`QNodeCollection`

sometimes fails because of simultaneous queuing. This is fixed by adding thread locking during queuing. (#910)Fixes a bug in

`QuantumTape.set_parameters()`

. The previous implementation assumed that the`self.trainable_parms`

set would always be iterated over in increasing integer order. However, this is not guaranteed behaviour, and can lead to the incorrect tape parameters being set if this is not the case. (#923)Fixes broken error message if a QNode is instantiated with an unknown exception. (#930)

### Contributors

This release contains contributions from (in alphabetical order):

Juan Miguel Arrazola, Thomas Bromley, Christina Lee, Alain Delgado Gran, Olivia Di Matteo, Anthony Hayes, Theodor Isacsson, Josh Izaac, Soran Jahangiri, Nathan Killoran, Shumpei Kobayashi, Romain Moyard, Zeyue Niu, Maria Schuld, Antal SzΓ‘va.

- orphan

## Release 0.12.0ΒΆ

### New features since last release

#### New and improved simulators

PennyLane now supports a new device,

`default.mixed`

, designed for simulating mixed-state quantum computations. This enables native support for implementing noisy channels in a circuit, which generally map pure states to mixed states. (#794) (#807) (#819)The device can be initialized as

>>> dev = qml.device("default.mixed", wires=1)

This allows the construction of QNodes that include non-unitary operations, such as noisy channels:

>>> @qml.qnode(dev) ... def circuit(params): ... qml.RX(params[0], wires=0) ... qml.RY(params[1], wires=0) ... qml.AmplitudeDamping(0.5, wires=0) ... return qml.expval(qml.PauliZ(0)) >>> print(circuit([0.54, 0.12])) 0.9257702929524184 >>> print(circuit([0, np.pi])) 0.0

#### New tools for optimizing measurements

The new

`grouping`

module provides functionality for grouping simultaneously measurable Pauli word observables. (#761) (#850) (#852)The

`optimize_measurements`

function will take as input a list of Pauli word observables and their corresponding coefficients (if any), and will return the partitioned Pauli terms diagonalized in the measurement basis and the corresponding diagonalizing circuits.from pennylane.grouping import optimize_measurements h, nr_qubits = qml.qchem.molecular_hamiltonian("h2", "h2.xyz") rotations, grouped_ops, grouped_coeffs = optimize_measurements(h.ops, h.coeffs, grouping="qwc")

The diagonalizing circuits of

`rotations`

correspond to the diagonalized Pauli word groupings of`grouped_ops`

.Pauli word partitioning utilities are performed by the

`PauliGroupingStrategy`

class. An input list of Pauli words can be partitioned into mutually commuting, qubit-wise-commuting, or anticommuting groupings.For example, partitioning Pauli words into anticommutative groupings by the Recursive Largest First (RLF) graph colouring heuristic:

from pennylane import PauliX, PauliY, PauliZ, Identity from pennylane.grouping import group_observables pauli_words = [ Identity('a') @ Identity('b'), Identity('a') @ PauliX('b'), Identity('a') @ PauliY('b'), PauliZ('a') @ PauliX('b'), PauliZ('a') @ PauliY('b'), PauliZ('a') @ PauliZ('b') ] groupings = group_observables(pauli_words, grouping_type='anticommuting', method='rlf')

Various utility functions are included for obtaining and manipulating Pauli words in the binary symplectic vector space representation.

For instance, two Pauli words may be converted to their binary vector representation:

>>> from pennylane.grouping import pauli_to_binary >>> from pennylane.wires import Wires >>> wire_map = {Wires('a'): 0, Wires('b'): 1} >>> pauli_vec_1 = pauli_to_binary(qml.PauliX('a') @ qml.PauliY('b')) >>> pauli_vec_2 = pauli_to_binary(qml.PauliZ('a') @ qml.PauliZ('b')) >>> pauli_vec_1 [1. 1. 0. 1.] >>> pauli_vec_2 [0. 0. 1. 1.]

Their product up to a phase may be computed by taking the sum of their binary vector representations, and returned in the operator representation.

>>> from pennylane.grouping import binary_to_pauli >>> binary_to_pauli((pauli_vec_1 + pauli_vec_2) % 2, wire_map) Tensor product ['PauliY', 'PauliX']: 0 params, wires ['a', 'b']

For more details on the grouping module, see the grouping module documentation

#### Returning the quantum state from simulators

The quantum state of a QNode can now be returned using the

`qml.state()`

return function. (#818)import pennylane as qml dev = qml.device("default.qubit", wires=3) qml.enable_tape() @qml.qnode(dev) def qfunc(x, y): qml.RZ(x, wires=0) qml.CNOT(wires=[0, 1]) qml.RY(y, wires=1) qml.CNOT(wires=[0, 2]) return qml.state() >>> qfunc(0.56, 0.1) array([0.95985437-0.27601028j, 0. +0.j , 0.04803275-0.01381203j, 0. +0.j , 0. +0.j , 0. +0.j , 0. +0.j , 0. +0.j ])

Differentiating the state is currently available when using the classical backpropagation differentiation method (

`diff_method="backprop"`

) with a compatible device, and when using the new tape mode.

#### New operations and channels

PennyLane now includes standard channels such as the Amplitude-damping, Phase-damping, and Depolarizing channels, as well as the ability to make custom qubit channels. (#760) (#766) (#778)

The controlled-Y operation is now available via

`qml.CY`

. For devices that do not natively support the controlled-Y operation, it will be decomposed into`qml.RY`

,`qml.CNOT`

, and`qml.S`

operations. (#806)

#### Preview the next-generation PennyLane QNode

The new PennyLane

`tape`

module provides a re-formulated QNode class, rewritten from the ground-up, that uses a new`QuantumTape`

object to represent the QNodeβs quantum circuit. Tape mode provides several advantages over the standard PennyLane QNode. (#785) (#792) (#796) (#800) (#803) (#804) (#805) (#808) (#810) (#811) (#815) (#820) (#823) (#824) (#829)Support for in-QNode classical processing: Tape mode allows for differentiable classical processing within the QNode.

No more Variable wrapping: In tape mode, QNode arguments no longer become

`Variable`

objects within the QNode.Less restrictive QNode signatures: There is no longer any restriction on the QNode signature; the QNode can be defined and called following the same rules as standard Python functions.

Unifying all QNodes: The tape-mode QNode merges all QNodes (including the

`JacobianQNode`

and the`PassthruQNode`

) into a single unified QNode, with identical behaviour regardless of the differentiation type.Optimizations: Tape mode provides various performance optimizations, reducing pre- and post-processing overhead, and reduces the number of quantum evaluations in certain cases.

Note that tape mode is

**experimental**, and does not currently have feature-parity with the existing QNode. Feedback and bug reports are encouraged and will help improve the new tape mode.Tape mode can be enabled globally via the

`qml.enable_tape`

function, without changing your PennyLane code:qml.enable_tape() dev = qml.device("default.qubit", wires=1) @qml.qnode(dev, interface="tf") def circuit(p): print("Parameter value:", p) qml.RX(tf.sin(p[0])**2 + p[1], wires=0) return qml.expval(qml.PauliZ(0))

For more details, please see the tape mode documentation.

### Improvements

QNode caching has been introduced, allowing the QNode to keep track of the results of previous device executions and reuse those results in subsequent calls. Note that QNode caching is only supported in the new and experimental tape-mode. (#817)

Caching is available by passing a

`caching`

argument to the QNode:dev = qml.device("default.qubit", wires=2) qml.enable_tape() @qml.qnode(dev, caching=10) # cache up to 10 evaluations def qfunc(x): qml.RX(x, wires=0) qml.RX(0.3, wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(1)) qfunc(0.1) # first evaluation executes on the device qfunc(0.1) # second evaluation accesses the cached result

Sped up the application of certain gates in

`default.qubit`

by using array/tensor manipulation tricks. The following gates are affected:`PauliX`

,`PauliY`

,`PauliZ`

,`Hadamard`

,`SWAP`

,`S`

,`T`

,`CNOT`

,`CZ`

. (#772)The computation of marginal probabilities has been made more efficient for devices with a large number of wires, achieving in some cases a 5x speedup. (#799)

Adds arithmetic operations (addition, tensor product, subtraction, and scalar multiplication) between

`Hamiltonian`

,`Tensor`

, and`Observable`

objects, and inline arithmetic operations between Hamiltonians and other observables. (#765)Hamiltonians can now easily be defined as sums of observables:

>>> H = 3 * qml.PauliZ(0) - (qml.PauliX(0) @ qml.PauliX(1)) + qml.Hamiltonian([4], [qml.PauliZ(0)]) >>> print(H) (7.0) [Z0] + (-1.0) [X0 X1]

Adds

`compare()`

method to`Observable`

and`Hamiltonian`

classes, which allows for comparison between observable quantities. (#765)>>> H = qml.Hamiltonian([1], [qml.PauliZ(0)]) >>> obs = qml.PauliZ(0) @ qml.Identity(1) >>> print(H.compare(obs)) True

>>> H = qml.Hamiltonian([2], [qml.PauliZ(0)]) >>> obs = qml.PauliZ(1) @ qml.Identity(0) >>> print(H.compare(obs)) False

Adds

`simplify()`

method to the`Hamiltonian`

class. (#765)>>> H = qml.Hamiltonian([1, 2], [qml.PauliZ(0), qml.PauliZ(0) @ qml.Identity(1)]) >>> H.simplify() >>> print(H) (3.0) [Z0]

Added a new bit-flip mixer to the

`qml.qaoa`

module. (#774)Summation of two

`Wires`

objects is now supported and will return a`Wires`

object containing the set of all wires defined by the terms in the summation. (#812)

### Breaking changes

The PennyLane NumPy module now returns scalar (zero-dimensional) arrays where Python scalars were previously returned. (#820) (#833)

For example, this affects array element indexing, and summation:

>>> x = np.array([1, 2, 3], requires_grad=False) >>> x[0] tensor(1, requires_grad=False) >>> np.sum(x) tensor(6, requires_grad=True)

This may require small updates to user code. A convenience method,

`np.tensor.unwrap()`

, has been added to help ease the transition. This converts PennyLane NumPy tensors to standard NumPy arrays and Python scalars:>>> x = np.array(1.543, requires_grad=False) >>> x.unwrap() 1.543

Note, however, that information regarding array differentiability will be lost.

The device capabilities dictionary has been redesigned, for clarity and robustness. In particular, the capabilities dictionary is now inherited from the parent class, various keys have more expressive names, and all keys are now defined in the base device class. For more details, please refer to the developer documentation. (#781)

### Bug fixes

Changed to use lists for storing variable values inside

`BaseQNode`

allowing complex matrices to be passed to`QubitUnitary`

. (#773)Fixed a bug within

`default.qubit`

, resulting in greater efficiency when applying a state vector to all wires on the device. (#849)

### Documentation

Equations have been added to the

`qml.sample`

and`qml.probs`

docstrings to clarify the mathematical foundation of the performed measurements. (#843)

### Contributors

This release contains contributions from (in alphabetical order):

Aroosa Ijaz, Juan Miguel Arrazola, Thomas Bromley, Jack Ceroni, Alain Delgado Gran, Josh Izaac, Soran Jahangiri, Nathan Killoran, Robert Lang, Cedric Lin, Olivia Di Matteo, NicolΓ‘s Quesada, Maria Schuld, Antal SzΓ‘va.

- orphan

## Release 0.11.0ΒΆ

### New features since last release

#### New and improved simulators

Added a new device,

`default.qubit.autograd`

, a pure-state qubit simulator written using Autograd. This device supports classical backpropagation (`diff_method="backprop"`

); this can be faster than the parameter-shift rule for computing quantum gradients when the number of parameters to be optimized is large. (#721)>>> dev = qml.device("default.qubit.autograd", wires=1) >>> @qml.qnode(dev, diff_method="backprop") ... def circuit(x): ... qml.RX(x[1], wires=0) ... qml.Rot(x[0], x[1], x[2], wires=0) ... return qml.expval(qml.PauliZ(0)) >>> weights = np.array([0.2, 0.5, 0.1]) >>> grad_fn = qml.grad(circuit) >>> print(grad_fn(weights)) array([-2.25267173e-01, -1.00864546e+00, 6.93889390e-18])

See the device documentation for more details.

A new experimental C++ state-vector simulator device is now available,

`lightning.qubit`

. It uses the C++ Eigen library to perform fast linear algebra calculations for simulating quantum state-vector evolution.`lightning.qubit`

is currently in beta; it can be installed via`pip`

:`$ pip install pennylane-lightning`

Once installed, it can be used as a PennyLane device:

>>> dev = qml.device("lightning.qubit", wires=2)

For more details, please see the lightning qubit documentation.

#### New algorithms and templates

Added built-in QAOA functionality via the new

`qml.qaoa`

module. (#712) (#718) (#741) (#720)This includes the following features:

New

`qml.qaoa.x_mixer`

and`qml.qaoa.xy_mixer`

functions for defining Pauli-X and XY mixer Hamiltonians.MaxCut: The

`qml.qaoa.maxcut`

function allows easy construction of the cost Hamiltonian and recommended mixer Hamiltonian for solving the MaxCut problem for a supplied graph.Layers:

`qml.qaoa.cost_layer`

and`qml.qaoa.mixer_layer`

take cost and mixer Hamiltonians, respectively, and apply the corresponding QAOA cost and mixer layers to the quantum circuit

For example, using PennyLane to construct and solve a MaxCut problem with QAOA:

wires = range(3) graph = Graph([(0, 1), (1, 2), (2, 0)]) cost_h, mixer_h = qaoa.maxcut(graph) def qaoa_layer(gamma, alpha): qaoa.cost_layer(gamma, cost_h) qaoa.mixer_layer(alpha, mixer_h) def antatz(params, **kwargs): for w in wires: qml.Hadamard(wires=w) # repeat the QAOA layer two times qml.layer(qaoa_layer, 2, params[0], params[1]) dev = qml.device('default.qubit', wires=len(wires)) cost_function = qml.VQECost(ansatz, cost_h, dev)

Added an

`ApproxTimeEvolution`

template to the PennyLane templates module, which can be used to implement Trotterized time-evolution under a Hamiltonian. (#710)Added a

`qml.layer`

template-constructing function, which takes a unitary, and repeatedly applies it on a set of wires to a given depth. (#723)def subroutine(): qml.Hadamard(wires=[0]) qml.CNOT(wires=[0, 1]) qml.PauliX(wires=[1]) dev = qml.device('default.qubit', wires=3) @qml.qnode(dev) def circuit(): qml.layer(subroutine, 3) return [qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1))]

This creates the following circuit:

>>> circuit() >>> print(circuit.draw()) 0: ββHβββCββXββHβββCββXββHβββCββXβββ€ β¨Zβ© 1: ββββββ°Xβββββββββ°Xβββββββββ°Xββββββ€ β¨Zβ©

Added the

`qml.utils.decompose_hamiltonian`

function. This function can be used to decompose a Hamiltonian into a linear combination of Pauli operators. (#671)>>> A = np.array( ... [[-2, -2+1j, -2, -2], ... [-2-1j, 0, 0, -1], ... [-2, 0, -2, -1], ... [-2, -1, -1, 0]]) >>> coeffs, obs_list = decompose_hamiltonian(A)

#### New device features

It is now possible to specify custom wire labels, such as

`['anc1', 'anc2', 0, 1, 3]`

, where the labels can be strings or numbers. (#666)Custom wire labels are defined by passing a list to the

`wires`

argument when creating the device:>>> dev = qml.device("default.qubit", wires=['anc1', 'anc2', 0, 1, 3])

Quantum operations should then be invoked with these custom wire labels:

>>> @qml.qnode(dev) >>> def circuit(): ... qml.Hadamard(wires='anc2') ... qml.CNOT(wires=['anc1', 3]) ... ...

The existing behaviour, in which the number of wires is specified on device initialization, continues to work as usual. This gives a default behaviour where wires are labelled by consecutive integers.

>>> dev = qml.device("default.qubit", wires=5)

An integrated device test suite has been added, which can be used to run basic integration tests on core or external devices. (#695) (#724) (#733)

The test can be invoked against a particular device by calling the

`pl-device-test`

command line program:$ pl-device-test --device=default.qubit --shots=1234 --analytic=False

If the tests are run on external devices, the device and its dependencies must be installed locally. For more details, please see the plugin test documentation.

### Improvements

The functions implementing the quantum circuits building the Unitary Coupled-Cluster (UCCSD) VQE ansatz have been improved, with a more consistent naming convention and improved docstrings. (#748)

The changes include:

The terms

*1particle-1hole (ph)*and*2particle-2hole (pphh)*excitations were replaced with the names*single*and*double*excitations, respectively.The non-differentiable arguments in the

`UCCSD`

template were renamed accordingly:`ph`

β`s_wires`

,`pphh`

β`d_wires`

The term

*virtual*, previously used to refer the*unoccupied*orbitals, was discarded.The Usage Details sections were updated and improved.

Added support for TensorFlow 2.3 and PyTorch 1.6. (#725)

Returning probabilities is now supported from photonic QNodes. As with qubit QNodes, photonic QNodes returning probabilities are end-to-end differentiable. (#699)

>>> dev = qml.device("strawberryfields.fock", wires=2, cutoff_dim=5) >>> @qml.qnode(dev) ... def circuit(a): ... qml.Displacement(a, 0, wires=0) ... return qml.probs(wires=0) >>> print(circuit(0.5)) [7.78800783e-01 1.94700196e-01 2.43375245e-02 2.02812704e-03 1.26757940e-04]

### Breaking changes

The

`pennylane.plugins`

and`pennylane.beta.plugins`

folders have been renamed to`pennylane.devices`

and`pennylane.beta.devices`

, to reflect their content better. (#726)

### Bug fixes

The PennyLane interface conversion functions can now convert QNodes with pre-existing interfaces. (#707)

### Documentation

The interfaces section of the documentation has been renamed to βInterfaces and trainingβ, and updated with the latest variable handling details. (#753)

### Contributors

This release contains contributions from (in alphabetical order):

Juan Miguel Arrazola, Thomas Bromley, Jack Ceroni, Alain Delgado Gran, Shadab Hussain, Theodor Isacsson, Josh Izaac, Nathan Killoran, Maria Schuld, Antal SzΓ‘va, Nicola Vitucci.

- orphan

## Release 0.10.0ΒΆ

### New features since last release

#### New and improved simulators

Added a new device,

`default.qubit.tf`

, a pure-state qubit simulator written using TensorFlow. As a result, it supports classical backpropagation as a means to compute the Jacobian. This can be faster than the parameter-shift rule for computing quantum gradients when the number of parameters to be optimized is large.`default.qubit.tf`

is designed to be used with end-to-end classical backpropagation (`diff_method="backprop"`

) with the TensorFlow interface. This is the default method of differentiation when creating a QNode with this device.Using this method, the created QNode is a βwhite-boxβ that is tightly integrated with your TensorFlow computation, including AutoGraph support:

>>> dev = qml.device("default.qubit.tf", wires=1) >>> @tf.function ... @qml.qnode(dev, interface="tf", diff_method="backprop") ... def circuit(x): ... qml.RX(x[1], wires=0) ... qml.Rot(x[0], x[1], x[2], wires=0) ... return qml.expval(qml.PauliZ(0)) >>> weights = tf.Variable([0.2, 0.5, 0.1]) >>> with tf.GradientTape() as tape: ... res = circuit(weights) >>> print(tape.gradient(res, weights)) tf.Tensor([-2.2526717e-01 -1.0086454e+00 1.3877788e-17], shape=(3,), dtype=float32)

See the

`default.qubit.tf`

documentation for more details.The default.tensor plugin has been significantly upgraded. It now allows two different tensor network representations to be used:

`"exact"`

and`"mps"`

. The former uses a exact factorized representation of quantum states, while the latter uses a matrix product state representation. (#572) (#599)

#### New machine learning functionality and integrations

PennyLane QNodes can now be converted into Torch layers, allowing for creation of quantum and hybrid models using the

`torch.nn`

API. (#588)A PennyLane QNode can be converted into a

`torch.nn`

layer using the`qml.qnn.TorchLayer`

class:>>> @qml.qnode(dev) ... def qnode(inputs, weights_0, weight_1): ... # define the circuit ... # ... >>> weight_shapes = {"weights_0": 3, "weight_1": 1} >>> qlayer = qml.qnn.TorchLayer(qnode, weight_shapes)

A hybrid model can then be easily constructed:

>>> model = torch.nn.Sequential(qlayer, torch.nn.Linear(2, 2))

Added a new βreversibleβ differentiation method which can be used in simulators, but not hardware.

The reversible approach is similar to backpropagation, but trades off extra computation for enhanced memory efficiency. Where backpropagation caches the state tensors at each step during a simulated evolution, the reversible method only caches the final pre-measurement state.

Compared to the parameter-shift method, the reversible method can be faster or slower, depending on the density and location of parametrized gates in a circuit (circuits with higher density of parametrized gates near the end of the circuit will see a benefit). (#670)

>>> dev = qml.device("default.qubit", wires=2) ... @qml.qnode(dev, diff_method="reversible") ... def circuit(x): ... qml.RX(x, wires=0) ... qml.RX(x, wires=0) ... qml.CNOT(wires=[0,1]) ... return qml.expval(qml.PauliZ(0)) >>> qml.grad(circuit)(0.5) (array(-0.47942554),)

#### New templates and cost functions

Added the new templates

`UCCSD`

,`SingleExcitationUnitary`

, and`DoubleExcitationUnitary`

, which together implement the Unitary Coupled-Cluster Singles and Doubles (UCCSD) ansatz to perform VQE-based quantum chemistry simulations using PennyLane-QChem. (#622) (#638) (#654) (#659) (#622)Added module

`pennylane.qnn.cost`

with class`SquaredErrorLoss`

. The module contains classes to calculate losses and cost functions on circuits with trainable parameters. (#642)

### Improvements

Improves the wire management by making the

`Operator.wires`

attribute a`wires`

object. (#666)A significant improvement with respect to how QNodes and interfaces mark quantum function arguments as differentiable when using Autograd, designed to improve performance and make QNodes more intuitive. (#648) (#650)

In particular, the following changes have been made:

A new

`ndarray`

subclass`pennylane.numpy.tensor`

, which extends NumPy arrays with the keyword argument and attribute`requires_grad`

. Tensors which have`requires_grad=False`

are treated as non-differentiable by the Autograd interface.A new subpackage

`pennylane.numpy`

, which wraps`autograd.numpy`

such that NumPy functions accept the`requires_grad`

keyword argument, and allows Autograd to differentiate`pennylane.numpy.tensor`

objects.The

`argnum`

argument to`qml.grad`

is now optional; if not provided, arguments explicitly marked as`requires_grad=False`

are excluded for the list of differentiable arguments. The ability to pass`argnum`

has been retained for backwards compatibility, and if present the old behaviour persists.

The QNode Torch interface now inspects QNode positional arguments. If any argument does not have the attribute

`requires_grad=True`

, it is automatically excluded from quantum gradient computations. (#652) (#660)The QNode TF interface now inspects QNode positional arguments. If any argument is not being watched by a

`tf.GradientTape()`

, it is automatically excluded from quantum gradient computations. (#655) (#660)QNodes have two new public methods:

`QNode.set_trainable_args()`

and`QNode.get_trainable_args()`

. These are designed to be called by interfaces, to specify to the QNode which of its input arguments are differentiable. Arguments which are non-differentiable will not be converted to PennyLane Variable objects within the QNode. (#660)Added

`decomposition`

method to PauliX, PauliY, PauliZ, S, T, Hadamard, and PhaseShift gates, which decomposes each of these gates into rotation gates. (#668)The

`CircuitGraph`

class now supports serializing contained circuit operations and measurement basis rotations to an OpenQASM2.0 script via the new`CircuitGraph.to_openqasm()`

method. (#623)

### Breaking changes

Removes support for Python 3.5. (#639)

### Documentation

Various small typos were fixed.

### Contributors

This release contains contributions from (in alphabetical order):

Thomas Bromley, Jack Ceroni, Alain Delgado Gran, Theodor Isacsson, Josh Izaac, Nathan Killoran, Maria Schuld, Antal SzΓ‘va, Nicola Vitucci.

- orphan

## Release 0.9.0ΒΆ

### New features since last release

#### New machine learning integrations

PennyLane QNodes can now be converted into Keras layers, allowing for creation of quantum and hybrid models using the Keras API. (#529)

A PennyLane QNode can be converted into a Keras layer using the

`KerasLayer`

class:from pennylane.qnn import KerasLayer @qml.qnode(dev) def circuit(inputs, weights_0, weight_1): # define the circuit # ... weight_shapes = {"weights_0": 3, "weight_1": 1} qlayer = qml.qnn.KerasLayer(circuit, weight_shapes, output_dim=2)

A hybrid model can then be easily constructed:

model = tf.keras.models.Sequential([qlayer, tf.keras.layers.Dense(2)])

Added a new type of QNode,

`qml.qnodes.PassthruQNode`

. For simulators which are coded in an external library which supports automatic differentiation, PennyLane will treat a PassthruQNode as a βwhite boxβ, and rely on the external library to directly provide gradients via backpropagation. This can be more efficient than the using parameter-shift rule for a large number of parameters. (#488)Currently this behaviour is supported by PennyLaneβs

`default.tensor.tf`

device backend, compatible with the`'tf'`

interface using TensorFlow 2:dev = qml.device('default.tensor.tf', wires=2) @qml.qnode(dev, diff_method="backprop") def circuit(params): qml.RX(params[0], wires=0) qml.RX(params[1], wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)) qnode = PassthruQNode(circuit, dev) params = tf.Variable([0.3, 0.1]) with tf.GradientTape() as tape: tape.watch(params) res = qnode(params) grad = tape.gradient(res, params)

#### New optimizers

Added the

`qml.RotosolveOptimizer`

, a gradient-free optimizer that minimizes the quantum function by updating each parameter, one-by-one, via a closed-form expression while keeping other parameters fixed. (#636) (#539)Added the

`qml.RotoselectOptimizer`

, which uses Rotosolve to minimizes a quantum function with respect to both the rotation operations applied and the rotation parameters. (#636) (#539)For example, given a quantum function

`f`

that accepts parameters`x`

and a list of corresponding rotation operations`generators`

, the Rotoselect optimizer will, at each step, update both the parameter values and the list of rotation gates to minimize the loss:>>> opt = qml.optimize.RotoselectOptimizer() >>> x = [0.3, 0.7] >>> generators = [qml.RX, qml.RY] >>> for _ in range(100): ... x, generators = opt.step(f, x, generators)

#### New operations

Added the

`PauliRot`

gate, which performs an arbitrary Pauli rotation on multiple qubits, and the`MultiRZ`

gate, which performs a rotation generated by a tensor product of Pauli Z operators. (#559)dev = qml.device('default.qubit', wires=4) @qml.qnode(dev) def circuit(angle): qml.PauliRot(angle, "IXYZ", wires=[0, 1, 2, 3]) return [qml.expval(qml.PauliZ(wire)) for wire in [0, 1, 2, 3]]

>>> circuit(0.4) [1. 0.92106099 0.92106099 1. ] >>> print(circuit.draw()) 0: βββRI(0.4)βββ€ β¨Zβ© 1: βββRX(0.4)βββ€ β¨Zβ© 2: βββRY(0.4)βββ€ β¨Zβ© 3: βββ°RZ(0.4)βββ€ β¨Zβ©

If the

`PauliRot`

gate is not supported on the target device, it will be decomposed into`Hadamard`

,`RX`

and`MultiRZ`

gates. Note that identity gates in the Pauli word result in untouched wires:>>> print(circuit.draw()) 0: ββββββββββββββββββββββββββββββββββββ€ β¨Zβ© 1: ββHβββββββββββRZ(0.4)ββHββββββββββββ€ β¨Zβ© 2: ββRX(1.571)βββRZ(0.4)ββRX(-1.571)βββ€ β¨Zβ© 3: ββββββββββββββ°RZ(0.4)βββββββββββββββ€ β¨Zβ©

If the

`MultiRZ`

gate is not supported, it will be decomposed into`CNOT`

and`RZ`

gates:>>> print(circuit.draw()) 0: βββββββββββββββββββββββββββββββββββββββββββββββββββ€ β¨Zβ© 1: ββHβββββββββββββββXββRZ(0.4)βββXββββββHββββββββββββ€ β¨Zβ© 2: ββRX(1.571)βββXβββ°Cββββββββββββ°CβββXββRX(-1.571)βββ€ β¨Zβ© 3: ββββββββββββββ°Cββββββββββββββββββββ°Cβββββββββββββββ€ β¨Zβ©

PennyLane now provides

`DiagonalQubitUnitary`

for diagonal gates, that are e.g., encountered in IQP circuits. These kinds of gates can be evaluated much faster on a simulator device. (#567)The gate can be used, for example, to efficiently simulate oracles:

dev = qml.device('default.qubit', wires=3) # Function as a bitstring f = np.array([1, 0, 0, 1, 1, 0, 1, 0]) @qml.qnode(dev) def circuit(weights1, weights2): qml.templates.StronglyEntanglingLayers(weights1, wires=[0, 1, 2]) # Implements the function as a phase-kickback oracle qml.DiagonalQubitUnitary((-1)**f, wires=[0, 1, 2]) qml.templates.StronglyEntanglingLayers(weights2, wires=[0, 1, 2]) return [qml.expval(qml.PauliZ(w)) for w in range(3)]

Added the

`TensorN`

CVObservable that can represent the tensor product of the`NumberOperator`

on photonic backends. (#608)

#### New templates

Added the

`ArbitraryUnitary`

and`ArbitraryStatePreparation`

templates, which use`PauliRot`

gates to perform an arbitrary unitary and prepare an arbitrary basis state with the minimal number of parameters. (#590)dev = qml.device('default.qubit', wires=3) @qml.qnode(dev) def circuit(weights1, weights2): qml.templates.ArbitraryStatePreparation(weights1, wires=[0, 1, 2]) qml.templates.ArbitraryUnitary(weights2, wires=[0, 1, 2]) return qml.probs(wires=[0, 1, 2])

Added the

`IQPEmbedding`

template, which encodes inputs into the diagonal gates of an IQP circuit. (#605)Added the

`SimplifiedTwoDesign`

template, which implements the circuit design of Cerezo et al. (2020). (#556)Added the

`BasicEntanglerLayers`

template, which is a simple layer architecture of rotations and CNOT nearest-neighbour entanglers. (#555)PennyLane now offers a broadcasting function to easily construct templates:

`qml.broadcast()`

takes single quantum operations or other templates and applies them to wires in a specific pattern. (#515) (#522) (#526) (#603)For example, we can use broadcast to repeat a custom template across multiple wires:

from pennylane.templates import template @template def mytemplate(pars, wires): qml.Hadamard(wires=wires) qml.RY(pars, wires=wires) dev = qml.device('default.qubit', wires=3) @qml.qnode(dev) def circuit(pars): qml.broadcast(mytemplate, pattern="single", wires=[0,1,2], parameters=pars) return qml.expval(qml.PauliZ(0))

>>> circuit([1, 1, 0.1]) -0.841470984807896 >>> print(circuit.draw()) 0: ββHββRY(1.0)βββ€ β¨Zβ© 1: ββHββRY(1.0)βββ€ 2: ββHββRY(0.1)βββ€

For other available patterns, see the broadcast function documentation.

### Breaking changes

The

`QAOAEmbedding`

now uses the new`MultiRZ`

gate as a`ZZ`

entangler, which changes the convention. While previously, the`ZZ`

gate in the embedding was implemented asCNOT(wires=[wires[0], wires[1]]) RZ(2 * parameter, wires=wires[0]) CNOT(wires=[wires[0], wires[1]])

the

`MultiRZ`

corresponds toCNOT(wires=[wires[1], wires[0]]) RZ(parameter, wires=wires[0]) CNOT(wires=[wires[1], wires[0]])

which differs in the factor of

`2`

, and fixes a bug in the wires that the`CNOT`

was applied to. (#609)Probability methods are handled by

`QubitDevice`

and device method requirements are modified to simplify plugin development. (#573)The internal variables

`All`

and`Any`

to mark an`Operation`

as acting on all or any wires have been renamed to`AllWires`

and`AnyWires`

. (#614)

### Improvements

A new

`Wires`

class was introduced for the internal bookkeeping of wire indices. (#615)Improvements to the speed/performance of the

`default.qubit`

device. (#567) (#559)Added the

`"backprop"`

and`"device"`

differentiation methods to the`qnode`

decorator. (#552)`"backprop"`

: Use classical backpropagation. Default on simulator devices that are classically end-to-end differentiable. The returned QNode can only be used with the same machine learning framework (e.g.,`default.tensor.tf`

simulator with the`tensorflow`

interface).`"device"`

: Queries the device directly for the gradient.

Using the

`"backprop"`

differentiation method with the`default.tensor.tf`

device, the created QNode is a βwhite-boxβ, and is tightly integrated with the overall TensorFlow computation:>>> dev = qml.device("default.tensor.tf", wires=1) >>> @qml.qnode(dev, interface="tf", diff_method="backprop") >>> def circuit(x): ... qml.RX(x[1], wires=0) ... qml.Rot(x[0], x[1], x[2], wires=0) ... return qml.expval(qml.PauliZ(0)) >>> vars = tf.Variable([0.2, 0.5, 0.1]) >>> with tf.GradientTape() as tape: ... res = circuit(vars) >>> tape.gradient(res, vars) <tf.Tensor: shape=(3,), dtype=float32, numpy=array([-2.2526717e-01, -1.0086454e+00, 1.3877788e-17], dtype=float32)>

The circuit drawer now displays inverted operations, as well as wires where probabilities are returned from the device: (#540)

>>> @qml.qnode(dev) ... def circuit(theta): ... qml.RX(theta, wires=0) ... qml.CNOT(wires=[0, 1]) ... qml.S(wires=1).inv() ... return qml.probs(wires=[0, 1]) >>> circuit(0.2) array([0.99003329, 0. , 0. , 0.00996671]) >>> print(circuit.draw()) 0: ββRX(0.2)βββCβββββββββ€ Probs 1: ββββββββββββ°XββSβ»ΒΉβββ°β€ Probs

You can now evaluate the metric tensor of a VQE Hamiltonian via the new

`VQECost.metric_tensor`

method. This allows`VQECost`

objects to be directly optimized by the quantum natural gradient optimizer (`qml.QNGOptimizer`

). (#618)The input check functions in

`pennylane.templates.utils`

are now public and visible in the API documentation. (#566)Added keyword arguments for step size and order to the

`qnode`

decorator, as well as the`QNode`

and`JacobianQNode`

classes. This enables the user to set the step size and order when using finite difference methods. These options are also exposed when creating QNode collections. (#530) (#585) (#587)The decomposition for the

`CRY`

gate now uses the simpler form`RY @ CNOT @ RY @ CNOT`

(#547)The underlying queuing system was refactored, removing the

`qml._current_context`

property that held the currently active`QNode`

or`OperationRecorder`

. Now, all objects that expose a queue for operations inherit from`QueuingContext`

and register their queue globally. (#548)The PennyLane repository has a new benchmarking tool which supports the comparison of different git revisions. (#568) (#560) (#516)

### Documentation

Updated the development section by creating a landing page with links to sub-pages containing specific guides. (#596)

Extended the developerβs guide by a section explaining how to add new templates. (#564)

### Bug fixes

`tf.GradientTape().jacobian()`

can now be evaluated on QNodes using the TensorFlow interface. (#626)`RandomLayers()`

is now compatible with the qiskit devices. (#597)`DefaultQubit.probability()`

now returns the correct probability when called with`device.analytic=False`

. (#563)Fixed a bug in the

`StronglyEntanglingLayers`

template, allowing it to work correctly when applied to a single wire. (544)Fixed a bug when inverting operations with decompositions; operations marked as inverted are now correctly inverted when the fallback decomposition is called. (#543)

The

`QNode.print_applied()`

method now correctly displays wires where`qml.prob()`

is being returned. #542

### Contributors

This release contains contributions from (in alphabetical order):

Ville Bergholm, Lana Bozanic, Thomas Bromley, Theodor Isacsson, Josh Izaac, Nathan Killoran, Maggie Li, Johannes Jakob Meyer, Maria Schuld, Sukin Sim, Antal SzΓ‘va.

- orphan

## Release 0.8.0ΒΆ

### New features since last release

Added a quantum chemistry package,

`pennylane.qchem`

, which supports integration with OpenFermion, Psi4, PySCF, and OpenBabel. (#453)Features include:

Generate the qubit Hamiltonians directly starting with the atomic structure of the molecule.

Calculate the mean-field (Hartree-Fock) electronic structure of molecules.

Allow to define an active space based on the number of active electrons and active orbitals.

Perform the fermionic-to-qubit transformation of the electronic Hamiltonian by using different functions implemented in OpenFermion.

Convert OpenFermionβs QubitOperator to a Pennylane

`Hamiltonian`

class.Perform a Variational Quantum Eigensolver (VQE) computation with this Hamiltonian in PennyLane.

Check out the quantum chemistry quickstart, as well the quantum chemistry and VQE tutorials.

PennyLane now has some functions and classes for creating and solving VQE problems. (#467)

`qml.Hamiltonian`

: a lightweight class for representing qubit Hamiltonians`qml.VQECost`

: a class for quickly constructing a differentiable cost function given a circuit ansatz, Hamiltonian, and one or more devices>>> H = qml.vqe.Hamiltonian(coeffs, obs) >>> cost = qml.VQECost(ansatz, hamiltonian, dev, interface="torch") >>> params = torch.rand([4, 3]) >>> cost(params) tensor(0.0245, dtype=torch.float64)

Added a circuit drawing feature that provides a text-based representation of a QNode instance. It can be invoked via

`qnode.draw()`

. The user can specify to display variable names instead of variable values and choose either an ASCII or Unicode charset. (#446)Consider the following circuit as an example:

@qml.qnode(dev) def qfunc(a, w): qml.Hadamard(0) qml.CRX(a, wires=[0, 1]) qml.Rot(w[0], w[1], w[2], wires=[1]) qml.CRX(-a, wires=[0, 1]) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))

We can draw the circuit after it has been executed:

>>> result = qfunc(2.3, [1.2, 3.2, 0.7]) >>> print(qfunc.draw()) 0: ββHβββCβββββββββββββββββββββββββββββCβββββββββββ€ β¨Z β Zβ© 1: ββββββ°RX(2.3)ββRot(1.2, 3.2, 0.7)βββ°RX(-2.3)βββ°β€ β¨Z β Zβ© >>> print(qfunc.draw(charset="ascii")) 0: --H--+C----------------------------+C---------+| <Z @ Z> 1: -----+RX(2.3)--Rot(1.2, 3.2, 0.7)--+RX(-2.3)--+| <Z @ Z> >>> print(qfunc.draw(show_variable_names=True)) 0: ββHβββCββββββββββββββββββββββββββββββCβββββββββββ€ β¨Z β Zβ© 1: ββββββ°RX(a)ββRot(w[0], w[1], w[2])βββ°RX(-1*a)βββ°β€ β¨Z β Zβ©

Added

`QAOAEmbedding`

and its parameter initialization as a new trainable template. (#442)Added the

`qml.probs()`

measurement function, allowing QNodes to differentiate variational circuit probabilities on simulators and hardware. (#432)@qml.qnode(dev) def circuit(x): qml.Hadamard(wires=0) qml.RY(x, wires=0) qml.RX(x, wires=1) qml.CNOT(wires=[0, 1]) return qml.probs(wires=[0])

Executing this circuit gives the marginal probability of wire 1:

>>> circuit(0.2) [0.40066533 0.59933467]

QNodes that return probabilities fully support autodifferentiation.

Added the convenience load functions

`qml.from_pyquil`

,`qml.from_quil`

and`qml.from_quil_file`

that convert pyQuil objects and Quil code to PennyLane templates. This feature requires version 0.8 or above of the PennyLane-Forest plugin. (#459)Added a

`qml.inv`

method that inverts templates and sequences of Operations. Added a`@qml.template`

decorator that makes templates return the queued Operations. (#462)For example, using this function to invert a template inside a QNode:

@qml.template def ansatz(weights, wires): for idx, wire in enumerate(wires): qml.RX(weights[idx], wires=[wire]) for idx in range(len(wires) - 1): qml.CNOT(wires=[wires[idx], wires[idx + 1]]) dev = qml.device('default.qubit', wires=2) @qml.qnode(dev) def circuit(weights): qml.inv(ansatz(weights, wires=[0, 1])) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))

Added the

`QNodeCollection`

container class, that allows independent QNodes to be stored and evaluated simultaneously. Experimental support for asynchronous evaluation of contained QNodes is provided with the`parallel=True`

keyword argument. (#466)Added a high level

`qml.map`

function, that maps a quantum circuit template over a list of observables or devices, returning a`QNodeCollection`

. (#466)For example:

>>> def my_template(params, wires, **kwargs): >>> qml.RX(params[0], wires=wires[0]) >>> qml.RX(params[1], wires=wires[1]) >>> qml.CNOT(wires=wires) >>> obs_list = [qml.PauliX(0) @ qml.PauliZ(1), qml.PauliZ(0) @ qml.PauliX(1)] >>> dev = qml.device("default.qubit", wires=2) >>> qnodes = qml.map(my_template, obs_list, dev, measure="expval") >>> qnodes([0.54, 0.12]) array([-0.06154835 0.99280864])

Added high level

`qml.sum`

,`qml.dot`

,`qml.apply`

functions that act on QNode collections. (#466)`qml.apply`

allows vectorized functions to act over the entire QNode collection:>>> qnodes = qml.map(my_template, obs_list, dev, measure="expval") >>> cost = qml.apply(np.sin, qnodes) >>> cost([0.54, 0.12]) array([-0.0615095 0.83756375])

`qml.sum`

and`qml.dot`

take the sum of a QNode collection, and a dot product of tensors/arrays/QNode collections, respectively.

### Breaking changes

Deprecated the old-style

`QNode`

such that only the new-style`QNode`

and its syntax can be used, moved all related files from the`pennylane/beta`

folder to`pennylane`

. (#440)

### Improvements

Added the

`Tensor.prune()`

method and the`Tensor.non_identity_obs`

property for extracting non-identity instances from the observables making up a`Tensor`

instance. (#498)Renamed the

`expt.tensornet`

and`expt.tensornet.tf`

devices to`default.tensor`

and`default.tensor.tf`

. (#495)Added a serialization method to the

`CircuitGraph`

class that is used to create a unique hash for each quantum circuit graph. (#470)Added the

`Observable.eigvals`

method to return the eigenvalues of observables. (#449)Added the

`Observable.diagonalizing_gates`

method to return the gates that diagonalize an observable in the computational basis. (#454)Added the

`Operator.matrix`

method to return the matrix representation of an operator in the computational basis. (#454)Added a

`QubitDevice`

class which implements common functionalities of plugin devices such that plugin devices can rely on these implementations. The new`QubitDevice`

also includes a new`execute`

method, which allows for more convenient plugin design. In addition,`QubitDevice`

also unifies the way samples are generated on qubit-based devices. (#452) (#473)Improved documentation of

`AmplitudeEmbedding`

and`BasisEmbedding`

templates. (#441) (#439)Codeblocks in the documentation now have a βcopyβ button for easily copying examples. (#437)

### Documentation

Update the developers plugin guide to use QubitDevice. (#483)

### Bug fixes

Fixed a bug in

`CVQNode._pd_analytic`

, where non-descendant observables were not Heisenberg-transformed before evaluating the partial derivatives when using the order-2 parameter-shift method, resulting in an erroneous Jacobian for some circuits. (#433)

### Contributors

This release contains contributions from (in alphabetical order):

Juan Miguel Arrazola, Ville Bergholm, Alain Delgado Gran, Olivia Di Matteo, Theodor Isacsson, Josh Izaac, Soran Jahangiri, Nathan Killoran, Johannes Jakob Meyer, Zeyue Niu, Maria Schuld, Antal SzΓ‘va.

- orphan

## Release 0.7.0ΒΆ

### New features since last release

Custom padding constant in

`AmplitudeEmbedding`

is supported (see βBreaking changesβ.) (#419)`StronglyEntanglingLayer`

and`RandomLayer`

now work with a single wire. (#409) (#413)Added support for applying the inverse of an

`Operation`

within a circuit. (#377)Added an

`OperationRecorder()`

context manager, that allows templates and quantum functions to be executed while recording events. The recorder can be used with and without QNodes as a debugging utility. (#388)Operations can now specify a decomposition that is used when the desired operation is not supported on the target device. (#396)

The ability to load circuits from external frameworks as templates has been added via the new

`qml.load()`

function. This feature requires plugin support β this initial release provides support for Qiskit circuits and QASM files when`pennylane-qiskit`

is installed, via the functions`qml.from_qiskit`

and`qml.from_qasm`

. (#418)An experimental tensor network device has been added (#416) (#395) (#394) (#380)

An experimental tensor network device which uses TensorFlow for backpropagation has been added (#427)

Custom padding constant in

`AmplitudeEmbedding`

is supported (see βBreaking changesβ.) (#419)

### Breaking changes

The

`pad`

parameter in`AmplitudeEmbedding()`

is now either`None`

(no automatic padding), or a number that is used as the padding constant. (#419)Initialization functions now return a single array of weights per function. Utilities for multi-weight templates

`Interferometer()`

and`CVNeuralNetLayers()`

are provided. (#412)The single layer templates

`RandomLayer()`

,`CVNeuralNetLayer()`

and`StronglyEntanglingLayer()`

have been turned into private functions`_random_layer()`

,`_cv_neural_net_layer()`

and`_strongly_entangling_layer()`

. Recommended use is now via the corresponding`Layers()`

templates. (#413)

### Improvements

Added extensive input checks in templates. (#419)

Templates integration tests are rewritten - now cover keyword/positional argument passing, interfaces and combinations of templates. (#409) (#419)

State vector preparation operations in the

`default.qubit`

plugin can now be applied to subsets of wires, and are restricted to being the first operation in a circuit. (#346)The

`QNode`

class is split into a hierarchy of simpler classes. (#354) (#398) (#415) (#417) (#425)Added the gates U1, U2 and U3 parametrizing arbitrary unitaries on 1, 2 and 3 qubits and the Toffoli gate to the set of qubit operations. (#396)

Changes have been made to accomodate the movement of the main function in

`pytest._internal`

to`pytest._internal.main`

in pip 19.3. (#404)Added the templates

`BasisStatePreparation`

and`MottonenStatePreparation`

that use gates to prepare a basis state and an arbitrary state respectively. (#336)Added decompositions for

`BasisState`

and`QubitStateVector`

based on state preparation templates. (#414)Replaces the pseudo-inverse in the quantum natural gradient optimizer (which can be numerically unstable) with

`np.linalg.solve`

. (#428)

### Contributors

This release contains contributions from (in alphabetical order):

Ville Bergholm, Josh Izaac, Nathan Killoran, Angus Lowe, Johannes Jakob Meyer, Oluwatobi Ogunbayo, Maria Schuld, Antal SzΓ‘va.

- orphan

## Release 0.6.0ΒΆ

### New features since last release

The devices

`default.qubit`

and`default.gaussian`

have a new initialization parameter`analytic`

that indicates if expectation values and variances should be calculated analytically and not be estimated from data. (#317)Added C-SWAP gate to the set of qubit operations (#330)

The TensorFlow interface has been renamed from

`"tfe"`

to`"tf"`

, and now supports TensorFlow 2.0. (#337)Added the S and T gates to the set of qubit operations. (#343)

Tensor observables are now supported within the

`expval`

,`var`

, and`sample`

functions, by using the`@`

operator. (#267)

### Breaking changes

The argument

`n`

specifying the number of samples in the method`Device.sample`

was removed. Instead, the method will always return`Device.shots`

many samples. (#317)

### Improvements

The number of shots / random samples used to estimate expectation values and variances,

`Device.shots`

, can now be changed after device creation. (#317)Unified import shortcuts to be under qml in qnode.py and test_operation.py (#329)

The quantum natural gradient now uses

`scipy.linalg.pinvh`

which is more efficient for symmetric matrices than the previously used`scipy.linalg.pinv`

. (#331)The deprecated

`qml.expval.Observable`

syntax has been removed. (#267)Remainder of the unittest-style tests were ported to pytest. (#310)

The

`do_queue`

argument for operations now only takes effect within QNodes. Outside of QNodes, operations can now be instantiated without needing to specify`do_queue`

. (#359)

### Documentation

The docs are rewritten and restructured to contain a code introduction section as well as an API section. (#314)

Added Ising model example to the tutorials (#319)

Added tutorial for QAOA on MaxCut problem (#328)

Added QGAN flow chart figure to its tutorial (#333)

Added missing figures for gallery thumbnails of state-preparation and QGAN tutorials (#326)

Fixed typos in the state preparation tutorial (#321)

Fixed bug in VQE tutorial 3D plots (#327)

### Bug fixes

Fixed typo in measurement type error message in qnode.py (#341)

### Contributors

This release contains contributions from (in alphabetical order):

Shahnawaz Ahmed, Ville Bergholm, Aroosa Ijaz, Josh Izaac, Nathan Killoran, Angus Lowe, Johannes Jakob Meyer, Maria Schuld, Antal SzΓ‘va, Roeland Wiersema.

- orphan

## Release 0.5.0ΒΆ

### New features since last release

Adds a new optimizer,

`qml.QNGOptimizer`

, which optimizes QNodes using quantum natural gradient descent. See https://arxiv.org/abs/1909.02108 for more details. (#295) (#311)Adds a new QNode method,

`QNode.metric_tensor()`

, which returns the block-diagonal approximation to the Fubini-Study metric tensor evaluated on the attached device. (#295)Sampling support: QNodes can now return a specified number of samples from a given observable via the top-level

`pennylane.sample()`

function. To support this on plugin devices, there is a new`Device.sample`

method.Calculating gradients of QNodes that involve sampling is not possible. (#256)

`default.qubit`

has been updated to provide support for sampling. (#256)Added controlled rotation gates to PennyLane operations and

`default.qubit`

plugin. (#251)

### Breaking changes

The method

`Device.supported`

was removed, and replaced with the methods`Device.supports_observable`

and`Device.supports_operation`

. Both methods can be called with string arguments (`dev.supports_observable('PauliX')`

) and class arguments (`dev.supports_observable(qml.PauliX)`

). (#276)The following CV observables were renamed to comply with the new Operation/Observable scheme:

`MeanPhoton`

to`NumberOperator`

,`Homodyne`

to`QuadOperator`

and`NumberState`

to`FockStateProjector`

. (#254)

### Improvements

The

`AmplitudeEmbedding`

function now provides options to normalize and pad features to ensure a valid state vector is prepared. (#275)Operations can now optionally specify generators, either as existing PennyLane operations, or by providing a NumPy array. (#295) (#313)

Adds a

`Device.parameters`

property, so that devices can view a dictionary mapping free parameters to operation parameters. This will allow plugin devices to take advantage of parametric compilation. (#283)Introduces two enumerations:

`Any`

and`All`

, representing any number of wires and all wires in the system respectively. They can be imported from`pennylane.operation`

, and can be used when defining the`Operation.num_wires`

class attribute of operations. (#277)As part of this change:

`All`

is equivalent to the integer 0, for backwards compatibility with the existing test suite`Any`

is equivalent to the integer -1 to allow numeric comparison operators to continue workingAn additional validation is now added to the

`Operation`

class, which will alert the user that an operation with`num_wires = All`

is being incorrectly.

The one-qubit rotations in

`pennylane.plugins.default_qubit`

no longer depend on Scipyβs`expm`

. Instead they are calculated with Eulerβs formula. (#292)Creates an

`ObservableReturnTypes`

enumeration class containing`Sample`

,`Variance`

and`Expectation`

. These new values can be assigned to the`return_type`

attribute of an`Observable`

. (#290)Changed the signature of the

`RandomLayer`

and`RandomLayers`

templates to have a fixed seed by default. (#258)`setup.py`

has been cleaned up, removing the non-working shebang, and removing unused imports. (#262)

### Documentation

A documentation refactor to simplify the tutorials and include Sphinx-Gallery. (#291)

Examples and tutorials previously split across the

`examples/`

and`doc/tutorials/`

directories, in a mixture of ReST and Jupyter notebooks, have been rewritten as Python scripts with ReST comments in a single location, the`examples/`

folder.Sphinx-Gallery is used to automatically build and run the tutorials. Rendered output is displayed in the Sphinx documentation.

Links are provided at the top of every tutorial page for downloading the tutorial as an executable python script, downloading the tutorial as a Jupyter notebook, or viewing the notebook on GitHub.

The tutorials table of contents have been moved to a single quick start page.

Fixed a typo in

`QubitStateVector`

. (#296)Fixed a typo in the

`default_gaussian.gaussian_state`

function. (#293)Fixed a typo in the gradient recipe within the

`RX`

,`RY`

,`RZ`

operation docstrings. (#248)Fixed a broken link in the tutorial documentation, as a result of the

`qml.expval.Observable`

deprecation. (#246)

### Bug fixes

Fixed a bug where a

`PolyXP`

observable would fail if applied to subsets of wires on`default.gaussian`

. (#277)

### Contributors

This release contains contributions from (in alphabetical order):

Simon Cross, Aroosa Ijaz, Josh Izaac, Nathan Killoran, Johannes Jakob Meyer, Rohit Midha, NicolΓ‘s Quesada, Maria Schuld, Antal SzΓ‘va, Roeland Wiersema.

- orphan

## Release 0.4.0ΒΆ

### New features since last release

`pennylane.expval()`

is now a top-level*function*, and is no longer a package of classes. For now, the existing`pennylane.expval.Observable`

interface continues to work, but will raise a deprecation warning. (#232)Variance support: QNodes can now return the variance of observables, via the top-level

`pennylane.var()`

function. To support this on plugin devices, there is a new`Device.var`

method.The following observables support analytic gradients of variances:

All qubit observables (requiring 3 circuit evaluations for involutory observables such as

`Identity`

,`X`

,`Y`

,`Z`

; and 5 circuit evals for non-involutary observables, currently only`qml.Hermitian`

)First-order CV observables (requiring 5 circuit evaluations)

Second-order CV observables support numerical variance gradients.

`pennylane.about()`

function added, providing details on current PennyLane version, installed plugins, Python, platform, and NumPy versions (#186)Removed the logic that allowed

`wires`

to be passed as a positional argument in quantum operations. This allows us to raise more useful error messages for the user if incorrect syntax is used. (#188)Adds support for multi-qubit expectation values of the

`pennylane.Hermitian()`

observable (#192)Adds support for multi-qubit expectation values in

`default.qubit`

. (#202)Organize templates into submodules (#195). This included the following improvements:

Distinguish embedding templates from layer templates.

New random initialization functions supporting the templates available in the new submodule

`pennylane.init`

.Added a random circuit template (

`RandomLayers()`

), in which rotations and 2-qubit gates are randomly distributed over the wiresAdd various embedding strategies

### Breaking changes

The

`Device`

methods`expectations`

,`pre_expval`

, and`post_expval`

have been renamed to`observables`

,`pre_measure`

, and`post_measure`

respectively. (#232)

### Improvements

`default.qubit`

plugin now uses`np.tensordot`

when applying quantum operations and evaluating expectations, resulting in significant speedup (#239), (#241)PennyLane now allows division of quantum operation parameters by a constant (#179)

Portions of the test suite are in the process of being ported to pytest. Note: this is still a work in progress.

Ported tests include:

`test_ops.py`

`test_about.py`

`test_classical_gradients.py`

`test_observables.py`

`test_measure.py`

`test_init.py`

`test_templates*.py`

`test_ops.py`

`test_variable.py`

`test_qnode.py`

(partial)

### Bug fixes

Fixed a bug in

`Device.supported`

, which would incorrectly mark an operation as supported if it shared a name with an observable (#203)Fixed a bug in

`Operation.wires`

, by explicitly casting the type of each wire to an integer (#206)Removed code in PennyLane which configured the logger, as this would clash with usersβ configurations (#208)

Fixed a bug in

`default.qubit`

, in which`QubitStateVector`

operations were accidentally being cast to`np.float`

instead of`np.complex`

. (#211)

### Contributors

This release contains contributions from:

Shahnawaz Ahmed, riveSunder, Aroosa Ijaz, Josh Izaac, Nathan Killoran, Maria Schuld.

- orphan

## Release 0.3.1ΒΆ

### Bug fixes

Fixed a bug where the interfaces submodule was not correctly being packaged via setup.py

- orphan

## Release 0.3.0ΒΆ

### New features since last release

PennyLane now includes a new

`interfaces`

submodule, which enables QNode integration with additional machine learning libraries.Adds support for an experimental PyTorch interface for QNodes

Adds support for an experimental TensorFlow eager execution interface for QNodes

Adds a PyTorch+GPU+QPU tutorial to the documentation

Documentation now includes links and tutorials including the new PennyLane-Forest plugin.

### Improvements

Printing a QNode object, via

`print(qnode)`

or in an interactive terminal, now displays more useful information regarding the QNode, including the device it runs on, the number of wires, itβs interface, and the quantum function it uses:>>> print(qnode) <QNode: device='default.qubit', func=circuit, wires=2, interface=PyTorch>

### Contributors

This release contains contributions from:

Josh Izaac and Nathan Killoran.

- orphan

## Release 0.2.0ΒΆ

### New features since last release

Added the

`Identity`

expectation value for both CV and qubit models (#135)Added the

`templates.py`

submodule, containing some commonly used QML models to be used as ansatz in QNodes (#133)Added the

`qml.Interferometer`

CV operation (#152)Wires are now supported as free QNode parameters (#151)

Added ability to update stepsizes of the optimizers (#159)

### Improvements

Removed use of hardcoded values in the optimizers, made them parameters (see #131 and #132)

Created the new

`PlaceholderExpectation`

, to be used when both CV and qubit expval modules contain expectations with the same nameProvide a way for plugins to view the operation queue

*before*applying operations. This allows for on-the-fly modifications of the queue, allowing hardware-based plugins to support the full range of qubit expectation values. (#143)QNode return values now support

*any*form of sequence, such as lists, sets, etc. (#144)CV analytic gradient calculation is now more robust, allowing for operations which may not themselves be differentiated, but have a well defined

`_heisenberg_rep`

method, and so may succeed operations that are analytically differentiable (#152)

### Bug fixes

Fixed a bug where the variational classifier example was not batching when learning parity (see #128 and #129)

Fixed an inconsistency where some initial state operations were documented as accepting complex parameters - all operations now accept real values (#146)

### Contributors

This release contains contributions from:

Christian Gogolin, Josh Izaac, Nathan Killoran, and Maria Schuld.

- orphan

## Release 0.1.0ΒΆ

Initial public release.

### Contributors

This release contains contributions from:Ville Bergholm, Josh Izaac, Maria Schuld, Christian Gogolin, and Nathan Killoran.

## Contents

## Downloads