# Release notes¶

This page contains the release notes for PennyLane.

- orphan

## Release 0.28.0-dev (development release)¶

### New features since last release

Add the controlled CZ gate: CCZ.

>>> ccz = qml.CCZ(wires=[0, 1, 2]) >>> matrix = ccz.compute_matrix() [[ 1 0 0 0 0 0 0 0] [ 0 1 0 0 0 0 0 0] [ 0 0 1 0 0 0 0 0] [ 0 0 0 1 0 0 0 0] [ 0 0 0 0 1 0 0 0] [ 0 0 0 0 0 1 0 0] [ 0 0 0 0 0 0 1 0] [ 0 0 0 0 0 0 0 -1]]

Add the controlled Hadamard gate.

>>> ch = qml.CH(wires=[0, 1]) >>> matrix = ch.compute_matrix() [[ 1. 0. 0. 0. ] [ 0. 1. 0. 0. ] [ 0. 0. 0.70710678 0.70710678] [ 0. 0. 0.70710678 -0.70710678]]

Support custom measurement processes:

`SampleMeasurement`

and`StateMeasurement`

classes have been added. They contain an abstract method to process samples/quantum state. #3286Add

`_Expectation`

class. #3343Add

`_Sample`

class. #3288Add

`_Var`

class. #3312Add

`_Probability`

class. #3287Add

`_Counts`

class. #3292Add

`_State`

class. #3287Add

`_VnEntropy`

class. #3326Add

`_MutualInfo`

class. #3327

Functionality for fetching symbols and geometry of a compound from the PubChem Database using

`qchem.mol_data`

. (#3289) (#3378)>>> mol_data("BeH2") (['Be', 'H', 'H'], array([[ 4.79405604, 0.29290815, 0. ], [ 3.77946 , -0.29290815, 0. ], [ 5.80884105, -0.29290815, 0. ]])) >>> mol_data(223, "CID") (['N', 'H', 'H', 'H', 'H'], array([[ 4.79404621, 0. , 0. ], [ 5.80882913, 0.5858151 , 0. ], [ 3.77945225, -0.5858151 , 0. ], [ 4.20823111, 1.01459396, 0. ], [ 5.3798613 , -1.01459396, 0. ]]))

New basis sets,

`6-311g`

and`CC-PVDZ`

, are added to the qchem basis set repo. #3279New parametric qubit ops

`qml.CPhaseShift00`

,`qml.CPhaseShift01`

and`qml.CPhaseShift10`

which perform a phaseshift, similar to`qml.ControlledPhaseShift`

but on different positions of the state vector. (#2715)Support for purity computation is added. The

`qml.math.purity`

function computes the purity from a state vector or a density matrix:>>> x = [1, 0, 0, 1] / np.sqrt(2) >>> qml.math.purity(x, [0, 1]) 1.0 >>> qml.math.purity(x, [0]) 0.5 >>> x = [[1 / 2, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 1 / 2]] >>> qml.math.purity(x, [0, 1]) 0.5

The

`qml.qinfo.purity`

can be used to transform a QNode returning a state to a function that returns the purity:dev = qml.device("default.mixed", wires=2) @qml.qnode(dev) def circuit(x): qml.IsingXX(x, wires=[0, 1]) return qml.state()

>>> qml.qinfo.purity(circuit, wires=[0])(np.pi / 2) 0.5 >>> qml.qinfo.purity(circuit, wires=[0, 1])(np.pi / 2) 1.0

Taking the gradient is also supported:

>>> param = np.array(np.pi / 4, requires_grad=True) >>> qml.grad(qml.qinfo.purity(circuit, wires=[0]))(param) -0.5

### Improvements

Added more input validation to

`hamiltonian_expand`

such that Hamiltonian objects with no terms raise an error. (#3339)Continuous integration checks are now performed for Python 3.11 and Torch v1.13. Python 3.7 is dropped. (#3276)

`qml.Tracker`

now also logs results in`tracker.history`

when tracking execution of a circuit.

Improve performance of

`Wires.all_wires`

. (#3302)A representation has been added to the

`Molecule`

class. (#3364)Add detail to the error message when the

`insert`

transform fails to diagonalize non-qubit-wise-commuting observables. (#3381)Extended the

`qml.equal`

function to`Hamiltonian`

and`Tensor`

objects. (#3390)Remove private

`_wires`

setter from the`Controlled.map_wires`

method. 3405`QuantumTape._process_queue`

has been moved to`qml.queuing.process_queue`

to disentangle its functionality from the`QuantumTape`

class. (#3401)

#### Return types project

The autograd interface for the new return types now supports devices with shot vectors. #3374

Example with a single measurement:

dev = qml.device("default.qubit", wires=1, shots=[1000, 2000, 3000]) @qml.qnode(dev, diff_method="parameter-shift") def circuit(a): qml.RY(a, wires=0) qml.RX(0.2, wires=0) return qml.expval(qml.PauliZ(0)) def cost(a): return qml.math.stack(circuit(a))

>>> qml.enable_return() >>> a = np.array(0.4) >>> circuit(a) (array(0.902), array(0.922), array(0.896)) >>> cost(a) array([0.9 , 0.907 , 0.89733333]) >>> qml.jacobian(cost)(a) array([-0.391 , -0.389 , -0.38433333])

Example with multiple measurements:

dev = qml.device("default.qubit", wires=2, shots=[1000, 2000, 3000]) @qml.qnode(dev, diff_method="parameter-shift") def circuit(a): qml.RY(a, wires=0) qml.RX(0.2, wires=0) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)), qml.probs([0, 1]) def cost(a): res = circuit(a) return qml.math.stack([qml.math.hstack(r) for r in res])

>>> circuit(a) ((array(0.904), array([0.952, 0. , 0. , 0.048])), (array(0.915), array([0.9575, 0. , 0. , 0.0425])), (array(0.902), array([0.951, 0. , 0. , 0.049]))) >>> cost(a) array([[0.91 , 0.955 , 0. , 0. , 0.045 ], [0.895 , 0.9475 , 0. , 0. , 0.0525 ], [0.90666667, 0.95333333, 0. , 0. , 0.04666667]]) >>> qml.jacobian(cost)(a) array([[-0.37 , -0.185 , 0. , 0. , 0.185 ], [-0.409 , -0.2045 , 0. , 0. , 0.2045 ], [-0.37133333, -0.18566667, 0. , 0. , 0.18566667]])

The TensorFlow interface for the new return types now supports devices with shot vectors. #3400

Example with a single measurement:

dev = qml.device("default.qubit", wires=1, shots=[1000, 2000, 3000]) @qml.qnode(dev, diff_method="parameter-shift", interface="tf") def circuit(a): qml.RY(a, wires=0) qml.RX(0.2, wires=0) return qml.expval(qml.PauliZ(0))

>>> qml.enable_return() >>> a = tf.Variable(0.4) >>> with tf.GradientTape() as tape: ... res = circuit(a) ... res = tf.stack(res) ... >>> res <tf.Tensor: shape=(3,), dtype=float64, numpy=array([0.902 , 0.904 , 0.89533333])> >>> tape.jacobian(res, a) <tf.Tensor: shape=(3,), dtype=float64, numpy=array([-0.365 , -0.3765 , -0.37533333])>

Example with multiple measurements:

dev = qml.device("default.qubit", wires=2, shots=[1000, 2000, 3000]) @qml.qnode(dev, diff_method="parameter-shift", interface="tf") def circuit(a): qml.RY(a, wires=0) qml.RX(0.2, wires=0) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)), qml.probs([0, 1])

>>> with tf.GradientTape() as tape: ... res = circuit(a) ... res = tf.stack([tf.experimental.numpy.hstack(r) for r in res]) ... >>> res <tf.Tensor: shape=(3, 5), dtype=float64, numpy= array([[0.902, 0.951, 0. , 0. , 0.049], [0.898, 0.949, 0. , 0. , 0.051], [0.892, 0.946, 0. , 0. , 0.054]])> >>> tape.jacobian(res, a) <tf.Tensor: shape=(3, 5), dtype=float64, numpy= array([[-0.345 , -0.1725 , 0. , 0. , 0.1725 ], [-0.383 , -0.1915 , 0. , 0. , 0.1915 ], [-0.38466667, -0.19233333, 0. , 0. , 0.19233333]])>

### Breaking changes

The

`log_base`

attribute has been moved from`MeasurementProcess`

to the new`_VnEntropy`

and`_MutualInfo`

classes, which inherit from`MeasurementProcess`

. #3326Python 3.7 support is no longer maintained. (#3276)

Instead of having an

`OrderedDict`

attribute called`_queue`

,`AnnotatedQueue`

now inherits from`OrderedDict`

and encapsulates the queue. Consequentially, this also applies to the`QuantumTape`

class which inherits from`AnnotatedQueue`

. (#3401)

### Deprecations

Deprecations cycles are tracked at doc/developement/deprecations.rst.

The following deprecated methods are removed: (#3281)

`qml.tape.get_active_tape`

: Use`qml.QueuingManager.active_context()`

`qml.transforms.qcut.remap_tape_wires`

: Use`qml.map_wires`

`qml.tape.QuantumTape.inv()`

: Use`qml.tape.QuantumTape.adjoint()`

`qml.tape.stop_recording()`

: Use`qml.QueuingManager.stop_recording()`

`qml.tape.QuantumTape.stop_recording()`

: Use`qml.QueuingManager.stop_recording()`

`qml.QueuingContext`

is now`qml.QueuingManager`

`QueuingManager.safe_update_info`

and`AnnotatedQueue.safe_update_info`

: Use plain`update_info`

`qml.transforms.measurement_grouping`

has been deprecated. Use`qml.transforms.hamiltonian_expand`

instead. (#3417)

### Documentation

Adds developer documentation for the queuing module. (#3268)

Corrects more mentions for diagonalizing gates for all relevant operations. The docstrings for

`compute_eigvals`

used to say that the diagonalizing gates implemented $U$, the unitary such that $O = U Sigma U^{dagger}$, where $O$ is the original observable and $Sigma$ a diagonal matrix. However, the diagonalizing gates actually implement $U^{dagger}$, since $langle psi | O | psi rangle = langle psi | U Sigma U^{dagger} | psi rangle$, making $U^{dagger} | psi rangle$ the actual state being measured in the $Z$-basis. (#3409)

### Bug fixes

Fixed a bug where

`hamiltonian_expand`

didn’t preserve the type of the inputted results in its output. (#3339)Fixed a bug that made

`gradients.param_shift`

raise an error when used with unshifted terms only in a custom recipe, and when using any unshifted terms at all under the new return type system. (#3177)Original tape

`_obs_sharing_wires`

attribute is updated during its expansion. #3293Small fix of

`MeasurementProcess.map_wires`

, where both the`self.obs`

and`self._wires`

attributes were modified. #3292An issue with

`drain=False`

in the adaptive optimizer is fixed. Before the fix, the operator pool needed to be re-constructed inside the optimization pool when`drain=False`

. With the new fix, this reconstruction is not needed. #3361If the device originally has no shots but finite shots are dynamically specified, Hamiltonian expansion now occurs. (#3369)

`qml.matrix(op)`

now fails if the operator truly has no matrix (eg.`Barrier`

) to match`op.matrix()`

(#3386)The

`pad_with`

argument in the`AmplitudeEmbedding`

template is now compatible with all interfaces (#3392)

### Contributors

This release contains contributions from (in alphabetical order):

Juan Miguel Arrazola Utkarsh Azad Astral Cai Pieter Eendebak Lillian M. A. Frederiksen Soran Jahangiri Edward Jiang Christina Lee Albert Mitjans Coma Romain Moyard Matthew Silverman Antal Száva David Wierichs Moritz Willmann

- orphan

## Release 0.27.0 (current release)¶

### New features since last release

#### An all-new data module 💾

The

`qml.data`

module is now available, allowing users to download, load, and create quantum datasets. (#3156)Datasets are hosted on Xanadu Cloud and can be downloaded by using

`qml.data.load()`

:>>> H2_datasets = qml.data.load( ... data_name="qchem", molname="H2", basis="STO-3G", bondlength=1.1 ... ) >>> H2data = H2_datasets[0] >>> H2data <Dataset = description: qchem/H2/STO-3G/1.1, attributes: ['molecule', 'hamiltonian', ...]>

Datasets available to be downloaded can be listed with

`qml.data.list_datasets()`

.To download or load only specific properties of a dataset, we can specify the desired properties in

`qml.data.load`

with the`attributes`

keyword argument:>>> H2_hamiltonian = qml.data.load( ... data_name="qchem", molname="H2", basis="STO-3G", bondlength=1.1, ... attributes=["molecule", "hamiltonian"] ... )[0] >>> H2_hamiltonian.hamiltonian <Hamiltonian: terms=15, wires=[0, 1, 2, 3]>

The available attributes can be found using

`qml.data.list_attributes()`

:To select data interactively, we can use

`qml.data.load_interactive()`

:>>> qml.data.load_interactive() Please select a data name: 1) qspin 2) qchem Choice [1-2]: 1 Please select a sysname: ... Please select a periodicity: ... Please select a lattice: ... Please select a layout: ... Please select attributes: ... Force download files? (Default is no) [y/N]: N Folder to download to? (Default is pwd, will download to /datasets subdirectory): Please confirm your choices: dataset: qspin/Ising/open/rectangular/4x4 attributes: ['parameters', 'ground_states'] force: False dest folder: datasets Would you like to continue? (Default is yes) [Y/n]: <Dataset = description: qspin/Ising/open/rectangular/4x4, attributes: ['parameters', 'ground_states']>

Once a dataset is loaded, its properties can be accessed as follows:

>>> dev = qml.device("default.qubit",wires=4) >>> @qml.qnode(dev) ... def circuit(): ... qml.BasisState(H2data.hf_state, wires = [0, 1, 2, 3]) ... for op in H2data.vqe_gates: ... qml.apply(op) ... return qml.expval(H2data.hamiltonian) >>> print(circuit()) -1.0791430411076344

It’s also possible to create custom datasets with

`qml.data.Dataset`

:>>> example_hamiltonian = qml.Hamiltonian(coeffs=[1,0.5], observables=[qml.PauliZ(wires=0),qml.PauliX(wires=1)]) >>> example_energies, _ = np.linalg.eigh(qml.matrix(example_hamiltonian)) >>> example_dataset = qml.data.Dataset( ... data_name = 'Example', hamiltonian=example_hamiltonian, energies=example_energies ... ) >>> example_dataset.data_name 'Example' >>> example_dataset.hamiltonian (0.5) [X1] + (1) [Z0] >>> example_dataset.energies array([-1.5, -0.5, 0.5, 1.5])

Custom datasets can be saved and read with the

`qml.data.Dataset.write()`

and`qml.data.Dataset.read()`

methods, respectively.>>> example_dataset.write('./path/to/dataset.dat') >>> read_dataset = qml.data.Dataset() >>> read_dataset.read('./path/to/dataset.dat') >>> read_dataset.data_name 'Example' >>> read_dataset.hamiltonian (0.5) [X1] + (1) [Z0] >>> read_dataset.energies array([-1.5, -0.5, 0.5, 1.5])

We will continue to work on adding more datasets and features for

`qml.data`

in future releases.

#### Adaptive optimization 🏃🏋️🏊

Optimizing quantum circuits can now be done

*adaptively*with`qml.AdaptiveOptimizer`

. (#3192)The

`qml.AdaptiveOptimizer`

takes an initial circuit and a collection of operators as input and adds a selected gate to the circuit at each optimization step. The process of growing the circuit can be repeated until the circuit gradients converge to zero within a given threshold. The adaptive optimizer can be used to implement algorithms such as ADAPT-VQE as shown in the following example.Firstly, we define some preliminary variables needed for VQE:

symbols = ["H", "H", "H"] geometry = np.array([[0.01076341, 0.04449877, 0.0], [0.98729513, 1.63059094, 0.0], [1.87262415, -0.00815842, 0.0]], requires_grad=False) H, qubits = qml.qchem.molecular_hamiltonian(symbols, geometry, charge = 1)

The collection of gates to grow the circuit is built to contain all single and double excitations:

n_electrons = 2 singles, doubles = qml.qchem.excitations(n_electrons, qubits) singles_excitations = [qml.SingleExcitation(0.0, x) for x in singles] doubles_excitations = [qml.DoubleExcitation(0.0, x) for x in doubles] operator_pool = doubles_excitations + singles_excitations

Next, an initial circuit that prepares a Hartree-Fock state and returns the expectation value of the Hamiltonian is defined:

hf_state = qml.qchem.hf_state(n_electrons, qubits) dev = qml.device("default.qubit", wires=qubits) @qml.qnode(dev) def circuit(): qml.BasisState(hf_state, wires=range(qubits)) return qml.expval(H)

Finally, the optimizer is instantiated and then the circuit is created and optimized adaptively:

opt = qml.optimize.AdaptiveOptimizer() for i in range(len(operator_pool)): circuit, energy, gradient = opt.step_and_cost(circuit, operator_pool, drain_pool=True) print('Energy:', energy) print(qml.draw(circuit)()) print('Largest Gradient:', gradient) print() if gradient < 1e-3: break

Energy: -1.246549938420637 0: ─╭BasisState(M0)─╭G²(0.20)─┤ ╭<𝓗> 1: ─├BasisState(M0)─├G²(0.20)─┤ ├<𝓗> 2: ─├BasisState(M0)─│─────────┤ ├<𝓗> 3: ─├BasisState(M0)─│─────────┤ ├<𝓗> 4: ─├BasisState(M0)─├G²(0.20)─┤ ├<𝓗> 5: ─╰BasisState(M0)─╰G²(0.20)─┤ ╰<𝓗> Largest Gradient: 0.14399872776755085 Energy: -1.2613740231529604 0: ─╭BasisState(M0)─╭G²(0.20)─╭G²(0.19)─┤ ╭<𝓗> 1: ─├BasisState(M0)─├G²(0.20)─├G²(0.19)─┤ ├<𝓗> 2: ─├BasisState(M0)─│─────────├G²(0.19)─┤ ├<𝓗> 3: ─├BasisState(M0)─│─────────╰G²(0.19)─┤ ├<𝓗> 4: ─├BasisState(M0)─├G²(0.20)───────────┤ ├<𝓗> 5: ─╰BasisState(M0)─╰G²(0.20)───────────┤ ╰<𝓗> Largest Gradient: 0.1349349562423238 Energy: -1.2743971719780331 0: ─╭BasisState(M0)─╭G²(0.20)─╭G²(0.19)──────────┤ ╭<𝓗> 1: ─├BasisState(M0)─├G²(0.20)─├G²(0.19)─╭G(0.00)─┤ ├<𝓗> 2: ─├BasisState(M0)─│─────────├G²(0.19)─│────────┤ ├<𝓗> 3: ─├BasisState(M0)─│─────────╰G²(0.19)─╰G(0.00)─┤ ├<𝓗> 4: ─├BasisState(M0)─├G²(0.20)────────────────────┤ ├<𝓗> 5: ─╰BasisState(M0)─╰G²(0.20)────────────────────┤ ╰<𝓗> Largest Gradient: 0.00040841755397108586

For a detailed breakdown of its implementation, check out the Adaptive circuits for quantum chemistry demo.

#### Automatic interface detection 🧩

QNodes now accept an

`auto`

interface argument which automatically detects the machine learning library to use. (#3132)from pennylane import numpy as np import torch import tensorflow as tf from jax import numpy as jnp dev = qml.device("default.qubit", wires=2) @qml.qnode(dev, interface="auto") def circuit(weight): qml.RX(weight[0], wires=0) qml.RY(weight[1], wires=1) return qml.expval(qml.PauliZ(0)) interface_tensors = [[0, 1], np.array([0, 1]), torch.Tensor([0, 1]), tf.Variable([0, 1], dtype=float), jnp.array([0, 1])] for tensor in interface_tensors: res = circuit(weight=tensor) print(f"Result value: {res:.2f}; Result type: {type(res)}")

Result value: 1.00; Result type: <class 'pennylane.numpy.tensor.tensor'> Result value: 1.00; Result type: <class 'pennylane.numpy.tensor.tensor'> Result value: 1.00; Result type: <class 'torch.Tensor'> Result value: 1.00; Result type: <class 'tensorflow.python.framework.ops.EagerTensor'> Result value: 1.00; Result type: <class 'jaxlib.xla_extension.DeviceArray'>

#### Upgraded JAX-JIT gradient support 🏎

JAX-JIT support for computing the gradient of QNodes that return a single vector of probabilities or multiple expectation values is now available. (#3244) (#3261)

import jax from jax import numpy as jnp from jax.config import config config.update("jax_enable_x64", True) dev = qml.device("lightning.qubit", wires=2) @jax.jit @qml.qnode(dev, diff_method="parameter-shift", interface="jax") def circuit(x, y): qml.RY(x, wires=0) qml.RY(y, wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) x = jnp.array(1.0) y = jnp.array(2.0)

>>> jax.jacobian(circuit, argnums=[0, 1])(x, y) (DeviceArray([-0.84147098, 0.35017549], dtype=float64, weak_type=True), DeviceArray([ 4.47445479e-18, -4.91295496e-01], dtype=float64, weak_type=True))

Note that this change depends on

`jax.pure_callback`

, which requires`jax>=0.3.17`

.

#### Construct Pauli words and sentences 🔤

We’ve reorganized and grouped everything in PennyLane responsible for manipulating Pauli operators into a

`pauli`

module. The`grouping`

module has been deprecated as a result, and logic was moved from`pennylane/grouping`

to`pennylane/pauli/grouping`

. (#3179)`qml.pauli.PauliWord`

and`qml.pauli.PauliSentence`

can be used to represent tensor products and linear combinations of Pauli operators, respectively. These provide a more performant method to compute sums and products of Pauli operators. (#3195)`qml.pauli.PauliWord`

represents tensor products of Pauli operators. We can efficiently multiply and extract the matrix of these operators using this representation.>>> pw1 = qml.pauli.PauliWord({0:"X", 1:"Z"}) >>> pw2 = qml.pauli.PauliWord({0:"Y", 1:"Z"}) >>> pw1, pw2 (X(0) @ Z(1), Y(0) @ Z(1)) >>> pw1 * pw2 (Z(0), 1j) >>> pw1.to_mat(wire_order=[0,1]) array([[ 0, 0, 1, 0], [ 0, 0, 0, -1], [ 1, 0, 0, 0], [ 0, -1, 0, 0]])

`qml.pauli.PauliSentence`

represents linear combinations of Pauli words. We can efficiently add, multiply and extract the matrix of these operators in this representation.>>> ps1 = qml.pauli.PauliSentence({pw1: 1.2, pw2: 0.5j}) >>> ps2 = qml.pauli.PauliSentence({pw1: -1.2}) >>> ps1 1.2 * X(0) @ Z(1) + 0.5j * Y(0) @ Z(1) >>> ps1 + ps2 0.0 * X(0) @ Z(1) + 0.5j * Y(0) @ Z(1) >>> ps1 * ps2 -1.44 * I + (-0.6+0j) * Z(0) >>> (ps1 + ps2).to_mat(wire_order=[0,1]) array([[ 0. +0.j, 0. +0.j, 0.5+0.j, 0. +0.j], [ 0. +0.j, 0. +0.j, 0. +0.j, -0.5+0.j], [-0.5+0.j, 0. +0.j, 0. +0.j, 0. +0.j], [ 0. +0.j, 0.5+0.j, 0. +0.j, 0. +0.j]])

#### (Experimental) More support for multi-measurement and gradient output types 🧪

`qml.enable_return()`

now supports QNodes returning multiple measurements, including shots vectors, and gradient output types. (#2886) (#3052) (#3041) (#3090) (#3069) (#3137) (#3127) (#3099) (#3098) (#3095) (#3091) (#3176) (#3170) (#3194) (#3267) (#3234) (#3232) (#3223) (#3222) (#3315)In v0.25, we introduced

`qml.enable_return()`

, which separates measurements into their own tensors. The motivation of this change is the deprecation of ragged`ndarray`

creation in NumPy.With this release, we’re continuing to elevate this feature by adding support for:

Execution (

`qml.execute`

)Jacobian vector product (JVP) computation

Gradient transforms (

`qml.gradients.param_shift`

,`qml.gradients.finite_diff`

,`qml.gradients.hessian_transform`

,`qml.gradients.param_shift_hessian`

).Interfaces (Autograd, TensorFlow, and JAX, although without JIT)

With this added support, the JAX interface can handle multiple shots (shots vectors), measurements, and gradient output types with

`qml.enable_return()`

:import jax qml.enable_return() dev = qml.device("default.qubit", wires=2, shots=(1, 10000)) params = jax.numpy.array([0.1, 0.2]) @qml.qnode(dev, interface="jax", diff_method="parameter-shift", max_diff=2) def circuit(x): qml.RX(x[0], wires=[0]) qml.RY(x[1], wires=[1]) qml.CNOT(wires=[0, 1]) return qml.var(qml.PauliZ(0) @ qml.PauliX(1)), qml.probs(wires=[0])

>>> jax.hessian(circuit)(params) ((DeviceArray([[ 0., 0.], [ 2., -3.]], dtype=float32), DeviceArray([[[-0.5, 0. ], [ 0. , 0. ]], [[ 0.5, 0. ], [ 0. , 0. ]]], dtype=float32)), (DeviceArray([[ 0.07677898, 0.0563341 ], [ 0.07238522, -1.830669 ]], dtype=float32), DeviceArray([[[-4.9707499e-01, 2.9999996e-04], [-6.2500127e-04, 1.2500001e-04]], [[ 4.9707499e-01, -2.9999996e-04], [ 6.2500127e-04, -1.2500001e-04]]], dtype=float32)))

For more details, please refer to the documentation.

#### New basis rotation and tapering features in qml.qchem 🤓

Grouped coefficients, observables, and basis rotation transformation matrices needed to construct a qubit Hamiltonian in the rotated basis of molecular orbitals are now calculable via

`qml.qchem.basis_rotation()`

. (#3011)>>> symbols = ['H', 'H'] >>> geometry = np.array([[0.0, 0.0, 0.0], [1.398397361, 0.0, 0.0]], requires_grad = False) >>> mol = qml.qchem.Molecule(symbols, geometry) >>> core, one, two = qml.qchem.electron_integrals(mol)() >>> coeffs, ops, unitaries = qml.qchem.basis_rotation(one, two, tol_factor=1.0e-5) >>> unitaries [tensor([[-1.00000000e+00, -5.46483514e-13], [ 5.46483514e-13, -1.00000000e+00]], requires_grad=True), tensor([[-1.00000000e+00, 3.17585063e-14], [-3.17585063e-14, -1.00000000e+00]], requires_grad=True), tensor([[-0.70710678, -0.70710678], [-0.70710678, 0.70710678]], requires_grad=True), tensor([[ 2.58789009e-11, 1.00000000e+00], [-1.00000000e+00, 2.58789009e-11]], requires_grad=True)]

Any gate operation can now be tapered according to \(\mathbb{Z}_2\) symmetries of the Hamiltonian via

`qml.qchem.taper_operation`

. (#3002) (#3121)>>> symbols = ['He', 'H'] >>> geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.4589]]) >>> mol = qml.qchem.Molecule(symbols, geometry, charge=1) >>> H, n_qubits = qml.qchem.molecular_hamiltonian(symbols, geometry) >>> generators = qml.qchem.symmetry_generators(H) >>> paulixops = qml.qchem.paulix_ops(generators, n_qubits) >>> paulix_sector = qml.qchem.optimal_sector(H, generators, mol.n_electrons) >>> tap_op = qml.qchem.taper_operation(qml.SingleExcitation, generators, paulixops, ... paulix_sector, wire_order=H.wires, op_wires=[0, 2]) >>> tap_op(3.14159) [Exp(1.5707949999999993j PauliY)]

Moreover, the obtained tapered operation can be used directly within a QNode.

>>> dev = qml.device('default.qubit', wires=[0, 1]) >>> @qml.qnode(dev) ... def circuit(params): ... tap_op(params[0]) ... return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) >>> drawer = qml.draw(circuit, show_all_wires=True) >>> print(drawer(params=[3.14159])) 0: ──Exp(0.00+1.57j Y)─┤ ╭<[email protected]> 1: ────────────────────┤ ╰<[email protected]>

Functionality has been added to estimate the number of measurements required to compute an expectation value with a target error and estimate the error in computing an expectation value with a given number of measurements. (#3000)

#### New functions, operations, and observables 🤩

Wires of operators or entire QNodes can now be mapped to other wires via

`qml.map_wires()`

. (#3143) (#3145)The

`qml.map_wires()`

function requires a dictionary representing a wire map. Use it witharbitrary operators:

>>> op = qml.RX(0.54, wires=0) + qml.PauliX(1) + (qml.PauliZ(2) @ qml.RY(1.23, wires=3)) >>> op (RX(0.54, wires=[0]) + PauliX(wires=[1])) + (PauliZ(wires=[2]) @ RY(1.23, wires=[3])) >>> wire_map = {0: 10, 1: 11, 2: 12, 3: 13} >>> qml.map_wires(op, wire_map) (RX(0.54, wires=[10]) + PauliX(wires=[11])) + (PauliZ(wires=[12]) @ RY(1.23, wires=[13]))

A

`map_wires`

method has also been added to operators, which returns a copy of the operator with its wires changed according to the given wire map.entire QNodes:

dev = qml.device("default.qubit", wires=["A", "B", "C", "D"]) wire_map = {0: "A", 1: "B", 2: "C", 3: "D"} @qml.qnode(dev) def circuit(): qml.RX(0.54, wires=0) qml.PauliX(1) qml.PauliZ(2) qml.RY(1.23, wires=3) return qml.probs(wires=0)

>>> mapped_circuit = qml.map_wires(circuit, wire_map) >>> mapped_circuit() tensor([0.92885434, 0.07114566], requires_grad=True) >>> print(qml.draw(mapped_circuit)()) A: ──RX(0.54)─┤ Probs B: ──X────────┤ C: ──Z────────┤ D: ──RY(1.23)─┤

The

`qml.IntegerComparator`

arithmetic operation is now available. (#3113)Given a basis state \(\vert n \rangle\), where \(n\) is a positive integer, and a fixed positive integer \(L\),

`qml.IntegerComparator`

flips a target qubit if \(n \geq L\). Alternatively, the flipping condition can be \(n < L\) as demonstrated below:dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(): qml.BasisState(np.array([0, 1]), wires=range(2)) qml.broadcast(qml.Hadamard, wires=range(2), pattern='single') qml.IntegerComparator(2, geq=False, wires=[0, 1]) return qml.state()

>>> circuit() [-0.5+0.j 0.5+0.j -0.5+0.j 0.5+0.j]

The

`qml.GellMann`

qutrit observable, the ternary generalization of the Pauli observables, is now available. (#3035)When using

`qml.GellMann`

, the`index`

keyword argument determines which of the 8 Gell-Mann matrices is used.dev = qml.device("default.qutrit", wires=2) @qml.qnode(dev) def circuit(): qml.TClock(wires=0) qml.TShift(wires=1) qml.TAdd(wires=[0, 1]) return qml.expval(qml.GellMann(wires=0, index=8) + qml.GellMann(wires=1, index=3))

>>> circuit() -0.42264973081037416

Controlled qutrit operations can now be performed with

`qml.ControlledQutritUnitary`

. (#2844)The control wires and values that define the operation are defined analogously to the qubit operation.

dev = qml.device("default.qutrit", wires=3) @qml.qnode(dev) def circuit(U): qml.TShift(wires=0) qml.TAdd(wires=[0, 1]) qml.ControlledQutritUnitary(U, control_wires=[0, 1], control_values='12', wires=2) return qml.state()

>>> U = np.array([[1, 1, 0], [1, -1, 0], [0, 0, np.sqrt(2)]]) / np.sqrt(2) >>> circuit(U) tensor([0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], requires_grad=True)

### Improvements

PennyLane now supports Python 3.11! (#3297)

`qml.sample`

and`qml.counts`

work more efficiently and track if computational basis samples are being generated when they are called without specifying an observable. (#3207)The parameters of a basis set containing a different number of Gaussian functions are now easier to differentiate. (#3213)

Printing a

`qml.MultiControlledX`

operator now shows the`control_values`

keyword argument. (#3113)`qml.simplify`

and transforms like`qml.matrix`

,`batch_transform`

,`hamiltonian_expand`

, and`split_non_commuting`

now work with`QuantumScript`

as well as`QuantumTape`

. (#3209)A redundant flipping of the initial state in the UCCSD and kUpCCGSD templates has been removed. (#3148)

`qml.adjoint`

now supports batching if the base operation supports batching. (#3168)`qml.OrbitalRotation`

is now decomposed into two`qml.SingleExcitation`

operations for faster execution and more efficient parameter-shift gradient calculations on devices that natively support`qml.SingleExcitation`

. (#3171)The

`Exp`

class decomposes into a`PauliRot`

class if the coefficient is imaginary and the base operator is a Pauli Word. (#3249)Added the operator attributes

`has_decomposition`

and`has_adjoint`

that indicate whether a corresponding`decomposition`

or`adjoint`

method is available. (#2986)Structural improvements are made to

`QueuingManager`

, formerly`QueuingContext`

, and`AnnotatedQueue`

. (#2794) (#3061) (#3085)`QueuingContext`

is renamed to`QueuingManager`

.`QueuingManager`

should now be the global communication point for putting queuable objects into the active queue.`QueuingManager`

is no longer an abstract base class.`AnnotatedQueue`

and its children no longer inherit from`QueuingManager`

.`QueuingManager`

is no longer a context manager.Recording queues should start and stop recording via the

`QueuingManager.add_active_queue`

and`QueuingContext.remove_active_queue`

class methods instead of directly manipulating the`_active_contexts`

property.`AnnotatedQueue`

and its children no longer provide global information about actively recording queues. This information is now only available through`QueuingManager`

.`AnnotatedQueue`

and its children no longer have the private`_append`

,`_remove`

,`_update_info`

,`_safe_update_info`

, and`_get_info`

methods. The public analogues should be used instead.`QueuingManager.safe_update_info`

and`AnnotatedQueue.safe_update_info`

are deprecated. Their functionality is moved to`update_info`

.

`qml.Identity`

now accepts multiple wires.

>>> id_op = qml.Identity([0, 1]) >>> id_op.matrix() array([[1., 0., 0., 0.], [0., 1., 0., 0.], [0., 0., 1., 0.], [0., 0., 0., 1.]]) >>> id_op.sparse_matrix() <4x4 sparse matrix of type '<class 'numpy.float64'>' with 4 stored elements in Compressed Sparse Row format> >>> id_op.eigvals() array([1., 1., 1., 1.])

Added

`unitary_check`

keyword argument to the constructor of the`QubitUnitary`

class which indicates whether the user wants to check for unitarity of the input matrix or not. Its default value is`false`

. (#3063)Modified the representation of

`WireCut`

by using`qml.draw_mpl`

. (#3067)Improved the performance of

`qml.math.expand_matrix`

function for dense and sparse matrices. (#3060) (#3064)Added support for sums and products of operator classes with scalar tensors of any interface (NumPy, JAX, Tensorflow, PyTorch…). (#3149)

>>> s_prod = torch.tensor(4) * qml.RX(1.23, 0) >>> s_prod 4*(RX(1.23, wires=[0])) >>> s_prod.scalar tensor(4)

Added

`overlapping_ops`

property to the`Composite`

class to improve the performance of the`eigvals`

,`diagonalizing_gates`

and`Prod.matrix`

methods. (#3084)Added the

`map_wires`

method to the operators, which returns a copy of the operator with its wires changed according to the given wire map. (#3143)>>> op = qml.Toffoli([0, 1, 2]) >>> wire_map = {0: 2, 2: 0} >>> op.map_wires(wire_map=wire_map) Toffoli(wires=[2, 1, 0])

Calling

`compute_matrix`

and`compute_sparse_matrix`

of simple non-parametric operations is now faster and more memory-efficient with the addition of caching. (#3134)Added details to the output of

`Exp.label()`

. (#3126)`qml.math.unwrap`

no longer creates ragged arrays. Lists remain lists. (#3163)New

`null.qubit`

device. The`null.qubit`

performs no operations or memory allocations. (#2589)`default.qubit`

favours decomposition and avoids matrix construction for`QFT`

and`GroverOperator`

at larger qubit numbers. (#3193)`qml.ControlledQubitUnitary`

now has a`control_values`

property. (#3206)Added a new

`qml.tape.QuantumScript`

class that contains all the non-queuing behavior of`QuantumTape`

. Now,`QuantumTape`

inherits from`QuantumScript`

as well as`AnnotatedQueue`

. (#3097)Extended the

`qml.equal`

function to MeasurementProcesses (#3189)`qml.drawer.draw.draw_mpl`

now accepts a`style`

kwarg to select a style for plotting, rather than calling`qml.drawer.use_style(style)`

before plotting. Setting a style for`draw_mpl`

does not change the global configuration for matplotlib plotting. If no`style`

is passed, the function defaults to plotting with the`black_white`

style. (#3247)

### Breaking changes

`QuantumTape._par_info`

is now a list of dictionaries, instead of a dictionary whose keys are integers starting from zero. (#3185)`QueuingContext`

has been renamed to`QueuingManager`

. (#3061)Deprecation patches for the return types enum’s location and

`qml.utils.expand`

are removed. (#3092)`_multi_dispatch`

functionality has been moved inside the`get_interface`

function. This function can now be called with one or multiple tensors as arguments. (#3136)>>> torch_scalar = torch.tensor(1) >>> torch_tensor = torch.Tensor([2, 3, 4]) >>> numpy_tensor = np.array([5, 6, 7]) >>> qml.math.get_interface(torch_scalar) 'torch' >>> qml.math.get_interface(numpy_tensor) 'numpy'

`_multi_dispatch`

previously had only one argument which contained a list of the tensors to be dispatched:>>> qml.math._multi_dispatch([torch_scalar, torch_tensor, numpy_tensor]) 'torch'

To differentiate whether the user wants to get the interface of a single tensor or multiple tensors,

`get_interface`

now accepts a different argument per tensor to be dispatched:>>> qml.math.get_interface(*[torch_scalar, torch_tensor, numpy_tensor]) 'torch' >>> qml.math.get_interface(torch_scalar, torch_tensor, numpy_tensor) 'torch'

`Operator.compute_terms`

is removed. On a specific instance of an operator,`op.terms()`

can be used instead. There is no longer a static method for this. (#3215)

### Deprecations

`QueuingManager.safe_update_info`

and`AnnotatedQueue.safe_update_info`

are deprecated. Instead,`update_info`

no longer raises errorsif the object isn’t in the queue. (#3085)

`qml.tape.stop_recording`

and`QuantumTape.stop_recording`

have been moved to`qml.QueuingManager.stop_recording`

. The old functions will still be available until v0.29. (#3068)`qml.tape.get_active_tape`

has been deprecated. Use`qml.QueuingManager.active_context()`

instead. (#3068)`Operator.compute_terms`

has been removed. On a specific instance of an operator, use`op.terms()`

instead. There is no longer a static method for this. (#3215)`qml.tape.QuantumTape.inv()`

has been deprecated. Use`qml.tape.QuantumTape.adjoint`

instead. (#3237)`qml.transforms.qcut.remap_tape_wires`

has been deprecated. Use`qml.map_wires`

instead. (#3186)The grouping module

`qml.grouping`

has been deprecated. Use`qml.pauli`

or`qml.pauli.grouping`

instead. The module will still be available until v0.28. (#3262)

### Documentation

The code block in the usage details of the UCCSD template has been updated. (#3140)

Added a “Deprecations” page to the developer documentation. (#3093)

The example of the

`qml.FlipSign`

template has been updated. (#3219)

### Bug fixes

`qml.SparseHamiltonian`

now validates the size of the input matrix. (#3278)Users no longer see unintuitive errors when inputing sequences to

`qml.Hermitian`

. (#3181)The evaluation of QNodes that return either

`vn_entropy`

or`mutual_info`

raises an informative error message when using devices that define a vector of shots. (#3180)Fixed a bug that made

`qml.AmplitudeEmbedding`

incompatible with JITting. (#3166)Fixed the

`qml.transforms.transpile`

transform to work correctly for all two-qubit operations. (#3104)Fixed a bug with the control values of a controlled version of a

`ControlledQubitUnitary`

. (#3119)Fixed a bug where

`qml.math.fidelity(non_trainable_state, trainable_state)`

failed unexpectedly. (#3160)Fixed a bug where

`qml.QueuingManager.stop_recording`

did not clean up if yielded code raises an exception. (#3182)Returning

`qml.sample()`

or`qml.counts()`

with other measurements of non-commuting observables now raises a QuantumFunctionError (e.g.,`return qml.expval(PauliX(wires=0)), qml.sample()`

now raises an error). (#2924)Fixed a bug where

`op.eigvals()`

would return an incorrect result if the operator was a non-hermitian composite operator. (#3204)Fixed a bug where

`qml.BasisStatePreparation`

and`qml.BasisEmbedding`

were not jit-compilable with JAX. (#3239)Fixed a bug where

`qml.MottonenStatePreparation`

was not jit-compilable with JAX. (#3260)Fixed a bug where

`qml.MottonenStatePreparation`

was not jit-compilable with JAX. (#3260)Fixed a bug where

`qml.expval(qml.Hamiltonian())`

would not raise an error if the Hamiltonian involved some wires that are not present on the device. (#3266)Fixed a bug where

`qml.tape.QuantumTape.shape()`

did not account for the batch dimension of the tape (#3269)

### Contributors

This release contains contributions from (in alphabetical order):

Kamal Mohamed Ali, Guillermo Alonso-Linaje, Juan Miguel Arrazola, Utkarsh Azad, Thomas Bromley, Albert Mitjans Coma, Isaac De Vlugt, Olivia Di Matteo, Amintor Dusko, Lillian M. A. Frederiksen, Diego Guala, Josh Izaac, Soran Jahangiri, Edward Jiang, Korbinian Kottmann, Christina Lee, Romain Moyard, Lee J. O’Riordan, Mudit Pandey, Matthew Silverman, Jay Soni, Antal Száva, David Wierichs,

- orphan

## Release 0.26.0¶

### New features since last release

#### Classical shadows 👤

PennyLane now provides built-in support for implementing the classical-shadows measurement protocol. (#2820) (#2821) (#2871) (#2968) (#2959) (#2968)

The classical-shadow measurement protocol is described in detail in the paper Predicting Many Properties of a Quantum System from Very Few Measurements. As part of the support for classical shadows in this release, two new finite-shot and fully-differentiable measurements are available:

QNodes returning the new measurement

`qml.classical_shadow()`

will return two entities;`bits`

(0 or 1 if the 1 or -1 eigenvalue is sampled, respectively) and`recipes`

(the randomized Pauli measurements that are performed for each qubit, labelled by integer):dev = qml.device("default.qubit", wires=2, shots=3) @qml.qnode(dev) def circuit(): qml.Hadamard(wires=0) qml.CNOT(wires=[0, 1]) return qml.classical_shadow(wires=[0, 1])

>>> bits, recipes = circuit() >>> bits tensor([[0, 0], [1, 0], [0, 1]], dtype=uint8, requires_grad=True) >>> recipes tensor([[2, 2], [0, 2], [0, 2]], dtype=uint8, requires_grad=True)

QNodes returning

`qml.shadow_expval()`

yield the expectation value estimation using classical shadows:dev = qml.device("default.qubit", wires=range(2), shots=10000) @qml.qnode(dev) def circuit(x, H): qml.Hadamard(0) qml.CNOT((0,1)) qml.RX(x, wires=0) return qml.shadow_expval(H) x = np.array(0.5, requires_grad=True) H = qml.Hamiltonian( [1., 1.], [qml.PauliZ(0) @ qml.PauliZ(1), qml.PauliX(0) @ qml.PauliX(1)] )

>>> circuit(x, H) tensor(1.8486, requires_grad=True) >>> qml.grad(circuit)(x, H) -0.4797000000000001

Fully-differentiable QNode transforms for both new classical-shadows measurements are also available via

`qml.shadows.shadow_state`

and`qml.shadows.shadow_expval`

, respectively.For convenient post-processing, we’ve also added the ability to calculate general Renyi entropies by way of the

`ClassicalShadow`

class’`entropy`

method, which requires the wires of the subsystem of interest and the Renyi entropy order:>>> shadow = qml.ClassicalShadow(bits, recipes) >>> vN_entropy = shadow.entropy(wires=[0, 1], alpha=1)

#### Qutrits: quantum circuits for tertiary degrees of freedom ☘️

An entirely new framework for quantum computing is now simulatable with the addition of qutrit functionalities. (#2699) (#2781) (#2782) (#2783) (#2784) (#2841) (#2843)

Qutrits are like qubits, but instead live in a

*three*-dimensional Hilbert space; they are not binary degrees of freedom, they are*tertiary*. The advent of qutrits allows for all sorts of interesting theoretical, practical, and algorithmic capabilities that have yet to be discovered.To facilitate qutrit circuits requires a new device:

`default.qutrit`

. The`default.qutrit`

device is a Python-based simulator, akin to`default.qubit`

, and is defined as per usual:>>> dev = qml.device("default.qutrit", wires=1)

The following operations are supported on

`default.qutrit`

devices:The qutrit shift operator,

`qml.TShift`

, and the ternary clock operator,`qml.TClock`

, as defined in this paper by Yeh et al. (2022), which are the qutrit analogs of the Pauli X and Pauli Z operations, respectively.The

`qml.TAdd`

and`qml.TSWAP`

operations which are the qutrit analogs of the CNOT and SWAP operations, respectively.Custom unitary operations via

`qml.QutritUnitary`

.`qml.state`

and`qml.probs`

measurements.Measuring user-specified Hermitian matrix observables via

`qml.THermitian`

.

A comprehensive example of these features is given below:

dev = qml.device("default.qutrit", wires=1) U = np.array([ [1, 1, 1], [1, 1, 1], [1, 1, 1] ] ) / np.sqrt(3) obs = np.array([ [1, 1, 0], [1, -1, 0], [0, 0, np.sqrt(2)] ] ) / np.sqrt(2) @qml.qnode(dev) def qutrit_state(U, obs): qml.TShift(0) qml.TClock(0) qml.QutritUnitary(U, wires=0) return qml.state() @qml.qnode(dev) def qutrit_expval(U, obs): qml.TShift(0) qml.TClock(0) qml.QutritUnitary(U, wires=0) return qml.expval(qml.THermitian(obs, wires=0))

>>> qutrit_state(U, obs) tensor([-0.28867513+0.5j, -0.28867513+0.5j, -0.28867513+0.5j], requires_grad=True) >>> qutrit_expval(U, obs) tensor(0.80473785, requires_grad=True)

We will continue to add more and more support for qutrits in future releases.

#### Simplifying just got... simpler 😌

The

`qml.simplify()`

function has several intuitive improvements with this release. (#2978) (#2982) (#2922) (#3012)`qml.simplify`

can now perform the following:simplify parametrized operations

simplify the adjoint and power of specific operators

group like terms in a sum

resolve products of Pauli operators

combine rotation angles of identical rotation gates

Here is an example of

`qml.simplify`

in action with parameterized rotation gates. In this case, the angles of rotation are simplified to be modulo \(4\pi\).>>> op1 = qml.RX(30.0, wires=0) >>> qml.simplify(op1) RX(4.867258771281655, wires=[0]) >>> op2 = qml.RX(4 * np.pi, wires=0) >>> qml.simplify(op2) Identity(wires=[0])

All of these simplification features can be applied directly to quantum functions, QNodes, and tapes via decorating with

`@qml.simplify`

, as well:dev = qml.device("default.qubit", wires=2) @qml.simplify @qml.qnode(dev) def circuit(): qml.adjoint(qml.prod(qml.RX(1, 0) ** 1, qml.RY(1, 0), qml.RZ(1, 0))) return qml.probs(wires=0)

>>> circuit() >>> list(circuit.tape) [RZ(11.566370614359172, wires=[0]) @ RY(11.566370614359172, wires=[0]) @ RX(11.566370614359172, wires=[0]), probs(wires=[0])]

#### QNSPSA optimizer 💪

A new optimizer called

`qml.QNSPSAOptimizer`

is available that implements the quantum natural simultaneous perturbation stochastic approximation (QNSPSA) method based on Simultaneous Perturbation Stochastic Approximation of the Quantum Fisher Information. (#2818)`qml.QNSPSAOptimizer`

is a second-order SPSA algorithm, which combines the convergence power of the quantum-aware Quantum Natural Gradient (QNG) optimization method with the reduced quantum evaluations of SPSA methods.While the QNSPSA optimizer requires additional circuit executions (10 executions per step) compared to standard SPSA optimization (3 executions per step), these additional evaluations are used to provide a stochastic estimation of a second-order metric tensor, which often helps the optimizer to achieve faster convergence.

Use

`qml.QNSPSAOptimizer`

like you would any other optimizer:max_iterations = 50 opt = qml.QNSPSAOptimizer() for _ in range(max_iterations): params, cost = opt.step_and_cost(cost, params)

Check out our demo on the QNSPSA optimizer for more information.

#### Operator and parameter broadcasting supplements 📈

Operator methods for exponentiation and raising to a power have been added. (#2799) (#3029)

The

`qml.exp`

function can be used to create observables or generic rotation gates:>>> x = 1.234 >>> t = qml.PauliX(0) @ qml.PauliX(1) + qml.PauliY(0) @ qml.PauliY(1) >>> isingxy = qml.exp(t, 0.25j * x) >>> isingxy.matrix() array([[1. +0.j , 0. +0.j , 1. +0.j , 0. +0.j ], [0. +0.j , 0.8156179+0.j , 1. +0.57859091j, 0. +0.j ], [0. +0.j , 0. +0.57859091j, 0.8156179+0.j , 0. +0.j ], [0. +0.j , 0. +0.j , 1. +0.j , 1. +0.j ]])

The

`qml.pow`

function raises a given operator to a power:>>> op = qml.pow(qml.PauliX(0), 2) >>> op.matrix() array([[1, 0], [0, 1]])

An operator called

`qml.PSWAP`

is now available. (#2667)The

`qml.PSWAP`

gate – or phase-SWAP gate – was previously available within the PennyLane-Braket plugin only. Enjoy it natively in PennyLane with v0.26.Check whether or not an operator is hermitian or unitary with

`qml.is_hermitian`

and`qml.is_unitary`

. (#2960)>>> op1 = qml.PauliX(wires=0) >>> qml.is_hermitian(op1) True >>> op2 = qml.PauliX(0) + qml.RX(np.pi/3, 0) >>> qml.is_unitary(op2) False

Embedding templates now support parameter broadcasting. (#2810)

Embedding templates like

`AmplitudeEmbedding`

or`IQPEmbedding`

now support parameter broadcasting with a leading broadcasting dimension in their variational parameters.`AmplitudeEmbedding`

, for example, would usually use a one-dimensional input vector of features. With broadcasting, we can now compute>>> features = np.array([ ... [0.5, 0.5, 0., 0., 0.5, 0., 0.5, 0.], ... [1., 0., 0., 0., 0., 0., 0., 0.], ... [0.5, 0.5, 0., 0., 0., 0., 0.5, 0.5], ... ]) >>> op = qml.AmplitudeEmbedding(features, wires=[1, 5, 2]) >>> op.batch_size 3

An exception is

`BasisEmbedding`

, which is not broadcastable.

### Improvements

The

`qml.math.expand_matrix()`

method now allows the sparse matrix representation of an operator to be extended to a larger hilbert space. (#2998)>>> from scipy import sparse >>> mat = sparse.csr_matrix([[0, 1], [1, 0]]) >>> qml.math.expand_matrix(mat, wires=[1], wire_order=[0,1]).toarray() array([[0., 1., 0., 0.], [1., 0., 0., 0.], [0., 0., 0., 1.], [0., 0., 1., 0.]])

`qml.ctrl`

now uses`Controlled`

instead of`ControlledOperation`

. The new`Controlled`

class wraps individual`Operator`

‘s instead of a tape. It provides improved representations and integration. (#2990)`qml.matrix`

can now compute the matrix of tapes and QNodes that contain multiple broadcasted operations or non-broadcasted operations after broadcasted ones. (#3025)A common scenario in which this becomes relevant is the decomposition of broadcasted operations: the decomposition in general will contain one or multiple broadcasted operations as well as operations with no or fixed parameters that are not broadcasted.

Lists of operators are now internally sorted by their respective wires while also taking into account their commutativity property. (#2995)

Some methods of the

`QuantumTape`

class have been simplified and reordered to improve both readability and performance. (#2963)The

`qml.qchem.molecular_hamiltonian`

function is modified to support observable grouping. (#2997)`qml.ops.op_math.Controlled`

now has basic decomposition functionality. (#2938)Automatic circuit cutting has been improved by making better partition imbalance derivations. Now it is more likely to generate optimal cuts for larger circuits. (#2517)

By default,

`qml.counts`

only returns the outcomes observed in sampling. Optionally, specifying`qml.counts(all_outcomes=True)`

will return a dictionary containing all possible outcomes. (#2889)>>> dev = qml.device("default.qubit", wires=2, shots=1000) >>> >>> @qml.qnode(dev) >>> def circuit(): ... qml.Hadamard(wires=0) ... qml.CNOT(wires=[0, 1]) ... return qml.counts(all_outcomes=True) >>> result = circuit() >>> result {'00': 495, '01': 0, '10': 0, '11': 505}

Internal use of in-place inversion is eliminated in preparation for its deprecation. (#2965)

`Controlled`

operators now work with`qml.is_commuting`

. (#2994)`qml.prod`

and`qml.op_sum`

now support the`sparse_matrix()`

method. (#3006)>>> xy = qml.prod(qml.PauliX(1), qml.PauliY(1)) >>> op = qml.op_sum(xy, qml.Identity(0)) >>> >>> sparse_mat = op.sparse_matrix(wire_order=[0,1]) >>> type(sparse_mat) <class 'scipy.sparse.csr.csr_matrix'> >>> sparse_mat.toarray() [[1.+1.j 0.+0.j 0.+0.j 0.+0.j] [0.+0.j 1.-1.j 0.+0.j 0.+0.j] [0.+0.j 0.+0.j 1.+1.j 0.+0.j] [0.+0.j 0.+0.j 0.+0.j 1.-1.j]]

Provided

`sparse_matrix()`

support for single qubit observables. (#2964)`qml.Barrier`

with`only_visual=True`

now simplifies via`op.simplify()`

to the identity operator or a product of identity operators. (#3016)More accurate and intuitive outputs for printing some operators have been added. (#3013)

Results for the matrix of the sum or product of operators are stored in a more efficient manner. (#3022)

The computation of the (sparse) matrix for the sum or product of operators is now more efficient. (#3030)

When the factors of

`qml.prod`

don’t share any wires, the matrix and sparse matrix are computed using a kronecker product for improved efficiency. (#3040)`qml.grouping.is_pauli_word`

now returns`False`

for operators that don’t inherit from`qml.Observable`

instead of raising an error. (#3039)Added functionality to iterate over operators created from

`qml.op_sum`

and`qml.prod`

. (#3028)>>> op = qml.op_sum(qml.PauliX(0), qml.PauliY(1), qml.PauliZ(2)) >>> len(op) 3 >>> op[1] PauliY(wires=[1]) >>> [o.name for o in op] ['PauliX', 'PauliY', 'PauliZ']

### Deprecations

In-place inversion is now deprecated. This includes

`op.inv()`

and`op.inverse=value`

. Please use`qml.adjoint`

or`qml.pow`

instead. Support for these methods will remain till v0.28. (#2988)Don’t use:

>>> v1 = qml.PauliX(0).inv() >>> v2 = qml.PauliX(0) >>> v2.inverse = True

Instead use:

>>> qml.adjoint(qml.PauliX(0)) Adjoint(PauliX(wires=[0])) >>> qml.pow(qml.PauliX(0), -1) PauliX(wires=[0])**-1 >>> qml.pow(qml.PauliX(0), -1, lazy=False) PauliX(wires=[0]) >>> qml.PauliX(0) ** -1 PauliX(wires=[0])**-1

`qml.adjoint`

takes the conjugate transpose of an operator, while`qml.pow(op, -1)`

indicates matrix inversion. For unitary operators,`adjoint`

will be more efficient than`qml.pow(op, -1)`

, even though they represent the same thing.The

`supports_reversible_diff`

device capability is unused and has been removed. (#2993)

### Breaking changes

Measuring an operator that might not be hermitian now raises a warning instead of an error. To definitively determine whether or not an operator is hermitian, use

`qml.is_hermitian`

. (#2960)The

`ControlledOperation`

class has been removed. This was a developer-only class, so the change should not be evident to any users. It is replaced by`Controlled`

. (#2990)The default

`execute`

method for the`QubitDevice`

base class now calls`self.statistics`

with an additional keyword argument`circuit`

, which represents the quantum tape being executed. Any device that overrides`statistics`

should edit the signature of the method to include the new`circuit`

keyword argument. (#2820)The

`expand_matrix()`

has been moved from`pennylane.operation`

to`pennylane.math.matrix_manipulation`

(#3008)`qml.grouping.utils.is_commuting`

has been removed, and its Pauli word logic is now part of`qml.is_commuting`

. (#3033)`qml.is_commuting`

has been moved from`pennylane.transforms.commutation_dag`

to`pennylane.ops.functions`

. (#2991)

### Documentation

Updated the Fourier transform docs to use

`circuit_spectrum`

instead of`spectrum`

, which has been deprecated. (#3018)Corrected the docstrings for diagonalizing gates for all relevant operations. The docstrings used to say that the diagonalizing gates implemented \(U\), the unitary such that \(O = U \Sigma U^{\dagger}\), where \(O\) is the original observable and \(\Sigma\) a diagonal matrix. However, the diagonalizing gates actually implement \(U^{\dagger}\), since \(\langle \psi | O | \psi \rangle = \langle \psi | U \Sigma U^{\dagger} | \psi \rangle\), making \(U^{\dagger} | \psi \rangle\) the actual state being measured in the Z-basis. (#2981)

### Bug fixes

Fixed a bug with

`qml.ops.Exp`

operators when the coefficient is autograd but the diagonalizing gates don’t act on all wires. (#3057)Fixed a bug where the tape transform

`single_qubit_fusion`

computed wrong rotation angles for specific combinations of rotations. (#3024)Jax gradients now work with a QNode when the quantum function was transformed by

`qml.simplify`

. (#3017)Operators that have

`num_wires = AnyWires`

or`num_wires = AnyWires`

now raise an error, with certain exceptions, when instantiated with`wires=[]`

. (#2979)Fixed a bug where printing

`qml.Hamiltonian`

with complex coefficients raises`TypeError`

in some cases. (#3005)Added a more descriptive error message when measuring non-commuting observables at the end of a circuit with

`probs`

,`samples`

,`counts`

and`allcounts`

. (#3065)

### Contributors

This release contains contributions from (in alphabetical order):

Juan Miguel Arrazola, Utkarsh Azad, Tom Bromley, Olivia Di Matteo, Isaac De Vlugt, Yiheng Duan, Lillian Marie Austin Frederiksen, Josh Izaac, Soran Jahangiri, Edward Jiang, Ankit Khandelwal, Korbinian Kottmann, Meenu Kumari, Christina Lee, Albert Mitjans Coma, Romain Moyard, Rashid N H M, Zeyue Niu, Mudit Pandey, Matthew Silverman, Jay Soni, Antal Száva, Cody Wang, David Wierichs.

- orphan

## Release 0.25.1¶

### Bug fixes

Fixed Torch device discrepencies for certain parametrized operations by updating

`qml.math.array`

and`qml.math.eye`

to preserve the Torch device used. (#2967)

### Contributors

This release contains contributions from (in alphabetical order):

Romain Moyard, Rashid N H M, Lee James O’Riordan, Antal Száva

- orphan

## Release 0.25.0¶

### New features since last release

#### Estimate computational resource requirements 🧠

Functionality for estimating molecular simulation computations has been added with

`qml.resource`

. (#2646) (#2653) (#2665) (#2694) (#2720) (#2723) (#2746) (#2796) (#2797) (#2874) (#2944) (#2644)The new resource module allows you to estimate the number of non-Clifford gates and logical qubits needed to implement quantum phase estimation algorithms for simulating materials and molecules. This includes support for quantum algorithms using first and second quantization with specific bases:

First quantization using a plane-wave basis via the

`FirstQuantization`

class:>>> n = 100000 # number of plane waves >>> eta = 156 # number of electrons >>> omega = 1145.166 # unit cell volume in atomic units >>> algo = FirstQuantization(n, eta, omega) >>> print(algo.gates, algo.qubits) 1.10e+13, 4416

Second quantization with a double-factorized Hamiltonian via the

`DoubleFactorization`

class:symbols = ["O", "H", "H"] geometry = np.array( [ [0.00000000, 0.00000000, 0.28377432], [0.00000000, 1.45278171, -1.00662237], [0.00000000, -1.45278171, -1.00662237], ], requires_grad=False, ) mol = qml.qchem.Molecule(symbols, geometry, basis_name="sto-3g") core, one, two = qml.qchem.electron_integrals(mol)() algo = DoubleFactorization(one, two)

>>> print(algo.gates, algo.qubits) 103969925, 290

The methods of the

`FirstQuantization`

and the`DoubleFactorization`

classes, such as`qubit_cost`

(number of logical qubits) and`gate_cost`

(number of non-Clifford gates), can be also accessed as static methods:>>> qml.resource.FirstQuantization.qubit_cost(100000, 156, 169.69608, 0.01) 4377 >>> qml.resource.FirstQuantization.gate_cost(100000, 156, 169.69608, 0.01) 3676557345574

#### Differentiable error mitigation ⚙️

Differentiable zero-noise-extrapolation (ZNE) error mitigation is now available. (#2757)

Elevate any variational quantum algorithm to a

*mitigated*algorithm with improved results on noisy hardware while maintaining differentiability throughout.In order to do so, use the

`qml.transforms.mitigate_with_zne`

transform on your QNode and provide the PennyLane proprietary`qml.transforms.fold_global`

folding function and`qml.transforms.poly_extrapolate`

extrapolation function. Here is an example for a noisy simulation device where we mitigate a QNode and are still able to compute the gradient:# Describe noise noise_gate = qml.DepolarizingChannel noise_strength = 0.1 # Load devices dev_ideal = qml.device("default.mixed", wires=1) dev_noisy = qml.transforms.insert(noise_gate, noise_strength)(dev_ideal) scale_factors = [1, 2, 3] @mitigate_with_zne( scale_factors, qml.transforms.fold_global, qml.transforms.poly_extrapolate, extrapolate_kwargs={'order': 2} ) @qml.qnode(dev_noisy) def qnode_mitigated(theta): qml.RY(theta, wires=0) return qml.expval(qml.PauliX(0))

>>> theta = np.array(0.5, requires_grad=True) >>> qml.grad(qnode_mitigated)(theta) 0.5712737447327619

#### More native support for parameter broadcasting 📡

`default.qubit`

now natively supports parameter broadcasting, providing increased performance when executing the same circuit at various parameter positions compared to manually looping over parameters, or directly using the`qml.transforms.broadcast_expand`

transform. (#2627)dev = qml.device("default.qubit", wires=1) @qml.qnode(dev) def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0))

>>> circuit(np.array([0.1, 0.3, 0.2])) tensor([0.99500417, 0.95533649, 0.98006658], requires_grad=True)

Currently, not all templates have been updated to support broadcasting.

Parameter-shift gradients now allow for parameter broadcasting internally, which can result in a significant speedup when computing gradients of circuits with many parameters. (#2749)

The gradient transform

`qml.gradients.param_shift`

now accepts the keyword argument`broadcast`

. If set to`True`

, broadcasting is used to compute the derivative:dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(x, y): qml.RX(x, wires=0) qml.RY(y, wires=1) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))

>>> x = np.array([np.pi/3, np.pi/2], requires_grad=True) >>> y = np.array([np.pi/6, np.pi/5], requires_grad=True) >>> qml.gradients.param_shift(circuit, broadcast=True)(x, y) (tensor([[-0.7795085, 0. ], [ 0. , -0.7795085]], requires_grad=True), tensor([[-0.125, 0. ], [0. , -0.125]], requires_grad=True))

The following example highlights how to make use of broadcasting gradients at the QNode level. Internally, broadcasting is used to compute the parameter-shift rule when required, which may result in performance improvements.

@qml.qnode(dev, diff_method="parameter-shift", broadcast=True) def circuit(x, y): qml.RX(x, wires=0) qml.RY(y, wires=1) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))

>>> x = np.array(0.1, requires_grad=True) >>> y = np.array(0.4, requires_grad=True) >>> qml.grad(circuit)(x, y) (array(-0.09195267), array(-0.38747287))

Here, only 2 circuits are created internally, rather than 4 with

`broadcast=False`

.To illustrate the speedup, for a constant-depth circuit with Pauli rotations and controlled Pauli rotations, the time required to compute

`qml.gradients.param_shift(circuit, broadcast=False)(params)`

(“No broadcasting”) and`qml.gradients.param_shift(circuit, broadcast=True)(params)`

(“Broadcasting”) as a function of the number of qubits is given here.Operations for quantum chemistry now support parameter broadcasting. (#2726)

>>> op = qml.SingleExcitation(np.array([0.3, 1.2, -0.7]), wires=[0, 1]) >>> op.matrix().shape (3, 4, 4)

#### Intuitive operator arithmetic 🧮

New functionality for representing the sum, product, and scalar-product of operators is available. (#2475) (#2625) (#2622) (#2721)

The following functionalities have been added to facilitate creating new operators whose matrix, terms, and eigenvalues can be accessed as per usual, while maintaining differentiability. Operators created from these new features can be used within QNodes as operations or as observables (where physically applicable).

Summing any number of operators via

`qml.op_sum`

results in a “summed” operator:>>> ops_to_sum = [qml.PauliX(0), qml.PauliY(1), qml.PauliZ(0)] >>> summed_ops = qml.op_sum(*ops_to_sum) >>> summed_ops PauliX(wires=[0]) + PauliY(wires=[1]) + PauliZ(wires=[0]) >>> qml.matrix(summed_ops) array([[ 1.+0.j, 0.-1.j, 1.+0.j, 0.+0.j], [ 0.+1.j, 1.+0.j, 0.+0.j, 1.+0.j], [ 1.+0.j, 0.+0.j, -1.+0.j, 0.-1.j], [ 0.+0.j, 1.+0.j, 0.+1.j, -1.+0.j]]) >>> summed_ops.terms() ([1.0, 1.0, 1.0], (PauliX(wires=[0]), PauliY(wires=[1]), PauliZ(wires=[0])))

Multiplying any number of operators via

`qml.prod`

results in a “product” operator, where the matrix product or tensor product is used correspondingly:>>> theta = 1.23 >>> prod_op = qml.prod(qml.PauliZ(0), qml.RX(theta, 1)) >>> prod_op PauliZ(wires=[0]) @ RX(1.23, wires=[1]) >>> qml.eigvals(prod_op) [-1.39373197 -0.23981492 0.23981492 1.39373197]

Taking the product of a coefficient and an operator via

`qml.s_prod`

produces a “scalar-product” operator:>>> sprod_op = qml.s_prod(2.0, qml.PauliX(0)) >>> sprod_op 2.0*(PauliX(wires=[0])) >>> sprod_op.matrix() array([[ 0., 2.], [ 2., 0.]]) >>> sprod_op.terms() ([2.0], [PauliX(wires=[0])])

Each of these new functionalities can be used within QNodes as operators or observables, where applicable, while also maintaining differentiability. For example:

dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(angles): qml.prod(qml.PauliZ(0), qml.RY(angles[0], 1)) qml.op_sum(qml.PauliX(1), qml.RY(angles[1], 0)) return qml.expval(qml.op_sum(qml.PauliX(0), qml.PauliZ(1)))

>>> angles = np.array([1.23, 4.56], requires_grad=True) >>> circuit(angles) tensor(0.33423773, requires_grad=True) >>> qml.grad(circuit)(angles) array([-0.9424888, 0. ])

All PennyLane operators can now be added, subtracted, multiplied, scaled, and raised to powers using

`+`

,`-`

,`@`

,`*`

,`**`

, respectively. (#2849) (#2825) (#2891)You can now add scalars to operators, where the interpretation is that the scalar is a properly-sized identity matrix;

>>> sum_op = 5 + qml.PauliX(0) >>> sum_op.matrix() array([[5., 1.], [1., 5.]])

The

`+`

and`-`

operators can be used to combine all Pennylane operators:>>> sum_op = qml.RX(phi=1.23, wires=0) + qml.RZ(phi=3.14, wires=0) - qml.RY(phi=0.12, wires=0) >>> sum_op RX(1.23, wires=[0]) + RZ(3.14, wires=[0]) + -1*(RY(0.12, wires=[0])) >>> qml.matrix(sum_op) array([[-0.18063077-0.99999968j, 0.05996401-0.57695852j], [-0.05996401-0.57695852j, -0.18063077+0.99999968j]])

Note that the behavior of

`+`

and`-`

with*observables*is different; it still creates a Hamiltonian.The

`*`

and`@`

operators can be used to scale and compose all PennyLane operators.>>> prod_op = 2*qml.RX(1, wires=0) @ qml.RY(2, wires=0) >>> prod_op 2*(RX(1, wires=[0])) @ RY(2, wires=[0]) >>> qml.matrix(prod_op) array([[ 0.94831976-0.80684536j, -1.47692053-0.51806945j], [ 1.47692053-0.51806945j, 0.94831976+0.80684536j]])

The

`**`

operator can be used to raise PennyLane operators to a power.>>> exp_op = qml.RZ(1.0, wires=0) ** 2 >>> exp_op RZ**2(1.0, wires=[0]) >>> qml.matrix(exp_op) array([[0.54030231-0.84147098j, 0. +0.j ], [0. +0.j , 0.54030231+0.84147098j]])

A new class called

`Controlled`

is available in`qml.ops.op_math`

to represent a controlled version of any operator. This will eventually be integrated into`qml.ctrl`

to provide a performance increase and more feature coverage. (#2634)Arithmetic operations can now be simplified using

`qml.simplify`

. (#2835) (#2854)>>> op = qml.adjoint(qml.adjoint(qml.RX(x, wires=0))) >>> op Adjoint(Adjoint(RX))(tensor([1.04719755, 1.57079633], requires_grad=True), wires=[0]) >>> qml.simplify(op) RX(tensor([1.04719755, 1.57079633], requires_grad=True), wires=[0])

A new function called

`qml.equal`

can be used to compare the equality of parametric operators. (#2651)>>> qml.equal(qml.RX(1.23, 0), qml.RX(1.23, 0)) True >>> qml.equal(qml.RY(4.56, 0), qml.RY(7.89, 0)) False

#### Marvelous mixed state features 🙌

The

`default.mixed`

device now supports backpropagation with the`"jax"`

interface, which can result in significant speedups. (#2754) (#2776)dev = qml.device("default.mixed", wires=2) @qml.qnode(dev, diff_method="backprop", interface="jax") def circuit(angles): qml.RX(angles[0], wires=0) qml.RY(angles[1], wires=1) return qml.expval(qml.PauliZ(0) + qml.PauliZ(1))

>>> angles = np.array([np.pi/6, np.pi/5], requires_grad=True) >>> qml.grad(circuit)(angles) array([-0.8660254 , -0.25881905])

Additionally, quantum channels now support Jax and TensorFlow tensors. This allows quantum channels to be used inside QNodes decorated by

`tf.function`

,`jax.jit`

, or`jax.vmap`

.The

`default.mixed`

device now supports readout error. (#2786)A new keyword argument called

`readout_prob`

can be specified when creating a`default.mixed`

device. Any circuits running on a`default.mixed`

device with a finite`readout_prob`

(upper-bounded by 1) will alter the measurements performed at the end of the circuit similarly to how a`qml.BitFlip`

channel would affect circuit measurements:>>> dev = qml.device("default.mixed", wires=2, readout_prob=0.1) >>> @qml.qnode(dev) ... def circuit(): ... return qml.expval(qml.PauliZ(0)) >>> circuit() array(0.8)

#### Relative entropy is now available in qml.qinfo 💥

The quantum information module now supports computation of relative entropy. (#2772)

We’ve enabled two cases for calculating the relative entropy:

A QNode transform via

`qml.qinfo.relative_entropy`

:dev = qml.device('default.qubit', wires=2) @qml.qnode(dev) def circuit(param): qml.RY(param, wires=0) qml.CNOT(wires=[0, 1]) return qml.state()

>>> relative_entropy_circuit = qml.qinfo.relative_entropy(circuit, circuit, wires0=[0], wires1=[0]) >>> x, y = np.array(0.4), np.array(0.6) >>> relative_entropy_circuit((x,), (y,)) 0.017750012490703237

Support in

`qml.math`

for flexible post-processing:>>> rho = np.array([[0.3, 0], [0, 0.7]]) >>> sigma = np.array([[0.5, 0], [0, 0.5]]) >>> qml.math.relative_entropy(rho, sigma) tensor(0.08228288, requires_grad=True)

#### New measurements, operators, and more! ✨

A new measurement called

`qml.counts`

is available. (#2686) (#2839) (#2876)QNodes with

`shots != None`

that return`qml.counts`

will yield a dictionary whose keys are bitstrings representing computational basis states that were measured, and whose values are the corresponding counts (i.e., how many times that computational basis state was measured):dev = qml.device("default.qubit", wires=2, shots=1000) @qml.qnode(dev) def circuit(): qml.Hadamard(wires=0) qml.CNOT(wires=[0, 1]) return qml.counts()

>>> circuit() {'00': 495, '11': 505}

`qml.counts`

can also accept observables, where the resulting dictionary is ordered by the eigenvalues of the observable.dev = qml.device("default.qubit", wires=2, shots=1000) @qml.qnode(dev) def circuit(): qml.Hadamard(wires=0) qml.CNOT(wires=[0, 1]) return qml.counts(qml.PauliZ(0)), qml.counts(qml.PauliZ(1))

>>> circuit() ({-1: 470, 1: 530}, {-1: 470, 1: 530})

A new experimental return type for QNodes with multiple measurements has been added. (#2814) (#2815) (#2860)

QNodes returning a list or tuple of different measurements return an intuitive data structure via

`qml.enable_return()`

, where the individual measurements are separated into their own tensors:qml.enable_return() dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(x): qml.Hadamard(wires=[0]) qml.CRX(x, wires=[0, 1]) return (qml.probs(wires=[0]), qml.vn_entropy(wires=[0]), qml.probs(wires=0), qml.expval(wires=1))

>>> circuit(0.5) (tensor([0.5, 0.5], requires_grad=True), tensor(0.08014815, requires_grad=True), tensor([0.5, 0.5], requires_grad=True), tensor(0.93879128, requires_grad=True))

In addition, QNodes that utilize this new return type support backpropagation. This new return type can be disabled thereafter via

`qml.disable_return()`

.An operator called

`qml.FlipSign`

is now available. (#2780)Mathematically,

`qml.FlipSign`

functions as follows: \(\text{FlipSign}(n) \vert m \rangle = (-1)^\delta_{n,m} \vert m \rangle\), where \(\vert m \rangle\) is an arbitrary qubit state and $n$ is a qubit configuration:basis_state = [0, 1] dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(): for wire in list(range(2)): qml.Hadamard(wires = wire) qml.FlipSign(basis_state, wires = list(range(2))) return qml.state()

>>> circuit() tensor([ 0.5+0.j -0.5+0.j 0.5+0.j 0.5+0.j], requires_grad=True)

The simultaneous perturbation stochastic approximation (SPSA) optimizer is available via

`qml.SPSAOptimizer`

. (#2661)The SPSA optimizer is suitable for cost functions whose evaluation may involve noise. Use the SPSA optimizer like you would any other optimizer:

max_iterations = 50 opt = qml.SPSAOptimizer(maxiter=max_iterations) for _ in range(max_iterations): params, cost = opt.step_and_cost(cost, params)

#### More drawing styles 🎨

New PennyLane-inspired

`sketch`

and`sketch_dark`

styles are now available for drawing circuit diagram graphics. (#2709)

### Improvements 📈

`default.qubit`

now natively executes any operation that defines a matrix except for trainable`Pow`

operations. (#2836)Added

`expm`

to the`qml.math`

module for matrix exponentiation. (#2890)When adjoint differentiation is requested, circuits are now decomposed so that all trainable operations have a generator. (#2836)

A warning is now emitted for

`qml.state`

,`qml.density_matrix`

,`qml.vn_entropy`

, and`qml.mutual_info`

when using a device with finite shots or a shot list since these measurements are always analytic. (#2918)The efficiency of the Hartree-Fock workflow has been improved by removing repetitive steps. (#2850)

The coefficients of the non-differentiable molecular Hamiltonians generated with openfermion now have

`requires_grad = False`

by default. (#2865)Upgraded performance of the

`compute_matrix`

method of broadcastable parametric operations. (#2759)Jacobians are now cached with the Autograd interface when using the parameter-shift rule. (#2645)

The

`qml.state`

and`qml.density_matrix`

measurements now support custom wire labels. (#2779)Add trivial behaviour logic to

`qml.operation.expand_matrix`

. (#2785)Added an

`are_pauli_words_qwc`

function which checks if certain Pauli words are pairwise qubit-wise commuting. This new function improves performance when measuring hamiltonians with many commuting terms. (#2789)Adjoint differentiation now uses the adjoint symbolic wrapper instead of in-place inversion. (#2855)

### Breaking changes 💔

The deprecated

`qml.hf`

module is removed. Users with code that calls`qml.hf`

can simply replace`qml.hf`

with`qml.qchem`

in most cases, or refer to the qchem documentation and demos for more information. (#2795)`default.qubit`

now uses`stopping_condition`

to specify support for anything with a matrix. To override this behavior in inheriting devices and to support only a specific subset of operations, developers need to override`stopping_condition`

. (#2836)Custom devices inheriting from

`DefaultQubit`

or`QubitDevice`

can break due to the introduction of parameter broadcasting. (#2627)A custom device should only break if all three following statements hold simultaneously:

The custom device inherits from

`DefaultQubit`

, not`QubitDevice`

.The device implements custom methods in the simulation pipeline that are incompatible with broadcasting (for example

`expval`

,`apply_operation`

or`analytic_probability`

).The custom device maintains the flag

`"supports_broadcasting": True`

in its`capabilities`

dictionary*or*it overwrites`Device.batch_transform`

without applying`broadcast_expand`

(or both).

The

`capabilities["supports_broadcasting"]`

is set to`True`

for`DefaultQubit`

. Typically, the easiest fix will be to change the`capabilities["supports_broadcasting"]`

flag to`False`

for the child device and/or to include a call to`broadcast_expand`

in`CustomDevice.batch_transform`

, similar to how`Device.batch_transform`

calls it.Separately from the above, custom devices that inherit from

`QubitDevice`

and implement a custom`_gather`

method need to allow for the kwarg`axis`

to be passed to this`_gather`

method.The argument

`argnum`

of the function`qml.batch_input`

has been redefined: now it indicates the indices of the batched parameters, which need to be non-trainable, in the quantum tape. Consequently, its default value (set to 0) has been removed. (#2873)Before this breaking change, one could call

`qml.batch_input`

without any arguments when using batched inputs as the first argument of the quantum circuit.dev = qml.device("default.qubit", wires=2, shots=None) @qml.batch_input() # argnum = 0 @qml.qnode(dev, diff_method="parameter-shift", interface="tf") def circuit(inputs, weights): # argument `inputs` is batched qml.RY(weights[0], wires=0) qml.AngleEmbedding(inputs, wires=range(2), rotation="Y") qml.RY(weights[1], wires=1) return qml.expval(qml.PauliZ(1))

With this breaking change, users must set a value to

`argnum`

specifying the index of the batched inputs with respect to all quantum tape parameters. In this example the quantum tape parameters are`[ weights[0], inputs, weights[1] ]`

, thus`argnum`

should be set to 1, specifying that`inputs`

is batched:dev = qml.device("default.qubit", wires=2, shots=None) @qml.batch_input(argnum=1) @qml.qnode(dev, diff_method="parameter-shift", interface="tf") def circuit(inputs, weights): qml.RY(weights[0], wires=0) qml.AngleEmbedding(inputs, wires=range(2), rotation="Y") qml.RY(weights[1], wires=1) return qml.expval(qml.PauliZ(1))

PennyLane now depends on newer versions (>=2.7) of the

`semantic_version`

package, which provides an updated API that is incompatible which versions of the package prior to 2.7. If you run into issues relating to this package, please reinstall PennyLane. (#2744) (#2767)

### Documentation 📕

Added a dedicated docstring for the

`QubitDevice.sample`

method. (#2812)Optimization examples of using JAXopt and Optax with the JAX interface have been added. (#2769)

Updated IsingXY gate docstring. (#2858)

### Bug fixes 🐞

Fixes

`qml.equal`

so that operators with different inverse properties are not equal. (#2947)Cleans up interactions between operator arithmetic and batching by testing supported cases and adding errors when batching is not supported. (#2900)

Fixed a bug where the parameter-shift rule wasn’t defined for

`qml.kUpCCGSD`

. (#2913)Reworked the Hermiticity check in

`qml.Hermitian`

by using`qml.math`

calls because calling`.conj()`

on an`EagerTensor`

from TensorFlow raised an error. (#2895)Fixed a bug where the parameter-shift gradient breaks when using both custom

`grad_recipe`

s that contain unshifted terms and recipes that do not contain any unshifted terms. (#2834)Fixed mixed CPU-GPU data-locality issues for the Torch interface. (#2830)

Fixed a bug where the parameter-shift Hessian of circuits with untrainable parameters might be computed with respect to the wrong parameters or might raise an error. (#2822)

Fixed a bug where the custom implementation of the

`states_to_binary`

device method was not used. (#2809)`qml.grouping.group_observables`

now works when individual wire labels are iterable. (#2752)The adjoint of an adjoint now has a correct

`expand`

result. (#2766)Fixed the ability to return custom objects as the expectation value of a QNode with the Autograd interface. (#2808)

The WireCut operator now raises an error when instantiating it with an empty list. (#2826)

Hamiltonians with grouped observables are now allowed to be measured on devices which were transformed using

`qml.transform.insert()`

. (#2857)Fixed a bug where

`qml.batch_input`

raised an error when using a batched operator that was not located at the beginning of the circuit. In addition, now`qml.batch_input`

raises an error when using trainable batched inputs, which avoids an unwanted behaviour with duplicated parameters. (#2873)Calling

`qml.equal`

with nested operators now raises a`NotImplementedError`

. (#2877)Fixed a bug where a non-sensible error message was raised when using

`qml.counts`

with`shots=False`

. (#2928)Fixed a bug where no error was raised and a wrong value was returned when using

`qml.counts`

with another non-commuting observable. (#2928)Operator Arithmetic now allows

`Hamiltonian`

objects to be used and produces correct matrices. (#2957)

### Contributors

This release contains contributions from (in alphabetical order):

Juan Miguel Arrazola, Utkarsh Azad, Samuel Banning, Prajwal Borkar, Isaac De Vlugt, Olivia Di Matteo, Kristiyan Dilov, David Ittah, Josh Izaac, Soran Jahangiri, Edward Jiang, Ankit Khandelwal, Korbinian Kottmann, Meenu Kumari, Christina Lee, Sergio Martínez-Losa, Albert Mitjans Coma, Ixchel Meza Chavez, Romain Moyard, Lee James O’Riordan, Mudit Pandey, Bogdan Reznychenko, Shuli Shu, Jay Soni, Modjtaba Shokrian-Zini, Antal Száva, David Wierichs, Moritz Willmann

- orphan

## Release 0.24.0¶

### New features since last release

#### All new quantum information quantities 📏

Functionality for computing quantum information quantities for QNodes has been added. (#2554) (#2569) (#2598) (#2617) (#2631) (#2640) (#2663) (#2684) (#2688) (#2695) (#2710) (#2712)

This includes two new QNode measurements:

The Von Neumann entropy via

`qml.vn_entropy`

:>>> dev = qml.device("default.qubit", wires=2) >>> @qml.qnode(dev) ... def circuit_entropy(x): ... qml.IsingXX(x, wires=[0,1]) ... return qml.vn_entropy(wires=[0], log_base=2) >>> circuit_entropy(np.pi/2) 1.0

The mutual information via

`qml.mutual_info`

:>>> dev = qml.device("default.qubit", wires=2) >>> @qml.qnode(dev) ... def circuit(x): ... qml.IsingXX(x, wires=[0,1]) ... return qml.mutual_info(wires0=[0], wires1=[1], log_base=2) >>> circuit(np.pi/2) 2.0

New differentiable transforms are also available in the

`qml.qinfo`

module:The classical and quantum Fisher information via

`qml.qinfo.classical_fisher`

,`qml.qinfo.quantum_fisher`

, respectively:dev = qml.device("default.qubit", wires=3) @qml.qnode(dev) def circ(params): qml.RY(params[0], wires=1) qml.CNOT(wires=(1,0)) qml.RY(params[1], wires=1) qml.RZ(params[2], wires=1) return qml.expval(qml.PauliX(0) @ qml.PauliX(1) - 0.5 * qml.PauliZ(1)) params = np.array([0.5, 1., 0.2], requires_grad=True) cfim = qml.qinfo.classical_fisher(circ)(params) qfim = qml.qinfo.quantum_fisher(circ)(params)

These quantities are typically employed in variational optimization schemes to tilt the gradient in a more favourable direction — producing what is known as the natural gradient. For example:

>>> grad = qml.grad(circ)(params) >>> cfim @ grad # natural gradient [ 5.94225615e-01 -2.61509542e-02 -1.18674655e-18] >>> qfim @ grad # quantum natural gradient [ 0.59422561 -0.02615095 -0.03989212]

The fidelity between two arbitrary states via

`qml.qinfo.fidelity`

:dev = qml.device('default.qubit', wires=1) @qml.qnode(dev) def circuit_rx(x): qml.RX(x[0], wires=0) qml.RZ(x[1], wires=0) return qml.state() @qml.qnode(dev) def circuit_ry(y): qml.RY(y, wires=0) return qml.state()

>>> x = np.array([0.1, 0.3], requires_grad=True) >>> y = np.array(0.2, requires_grad=True) >>> fid_func = qml.qinfo.fidelity(circuit_rx, circuit_ry, wires0=[0], wires1=[0]) >>> fid_func(x, y) 0.9905158135644924 >>> df = qml.grad(fid_func) >>> df(x, y) (array([-0.04768725, -0.29183666]), array(-0.09489803))

Reduced density matrices of arbitrary states via

`qml.qinfo.reduced_dm`

:dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(x): qml.IsingXX(x, wires=[0,1]) return qml.state()

>>> qml.qinfo.reduced_dm(circuit, wires=[0])(np.pi/2) [[0.5+0.j 0.+0.j] [0.+0.j 0.5+0.j]]

Similar transforms,

`qml.qinfo.vn_entropy`

and`qml.qinfo.mutual_info`

exist for transforming QNodes.

Currently, all quantum information measurements and transforms are differentiable, but only support statevector devices, with hardware support to come in a future release (with the exception of

`qml.qinfo.classical_fisher`

and`qml.qinfo.quantum_fisher`

, which are both hardware compatible).For more information, check out the new qinfo module and measurements page.

In addition to the QNode transforms and measurements above, functions for computing and differentiating quantum information metrics with numerical statevectors and density matrices have been added to the

`qml.math`

module. This enables flexible custom post-processing.Added functions include:

`qml.math.reduced_dm`

`qml.math.vn_entropy`

`qml.math.mutual_info`

`qml.math.fidelity`

For example:

>>> x = torch.tensor([1.0, 0.0, 0.0, 1.0], requires_grad=True) >>> en = qml.math.vn_entropy(x / np.sqrt(2.), indices=[0]) >>> en tensor(0.6931, dtype=torch.float64, grad_fn=<DivBackward0>) >>> en.backward() >>> x.grad tensor([-0.3069, 0.0000, 0.0000, -0.3069])

#### Faster mixed-state training with backpropagation 📉

The

`default.mixed`

device now supports differentiation via backpropagation with the Autograd, TensorFlow, and PyTorch (CPU) interfaces, leading to significantly more performant optimization and training. (#2615) (#2670) (#2680)As a result, the default differentiation method for the device is now

`"backprop"`

. To continue using the old default`"parameter-shift"`

, explicitly specify this differentiation method in the QNode:dev = qml.device("default.mixed", wires=2) @qml.qnode(dev, interface="autograd", diff_method="backprop") def circuit(x): qml.RY(x, wires=0) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(wires=1))

>>> x = np.array(0.5, requires_grad=True) >>> circuit(x) array(0.87758256) >>> qml.grad(circuit)(x) -0.479425538604203

#### Support for quantum parameter broadcasting 📡

Quantum operators, functions, and tapes now support broadcasting across parameter dimensions, making it more convenient for developers to execute their PennyLane programs with multiple sets of parameters. (#2575) (#2609)

Parameter broadcasting refers to passing tensor parameters with additional leading dimensions to quantum operators; additional dimensions will flow through the computation, and produce additional dimensions at the output.

For example, instantiating a rotation gate with a one-dimensional array leads to a broadcasted

`Operation`

:>>> x = np.array([0.1, 0.2, 0.3], requires_grad=True) >>> op = qml.RX(x, 0) >>> op.batch_size 3

Its matrix correspondingly is augmented by a leading dimension of size

`batch_size`

:>>> np.round(qml.matrix(op), 4) tensor([[[0.9988+0.j , 0. -0.05j ], [0. -0.05j , 0.9988+0.j ]], [[0.995 +0.j , 0. -0.0998j], [0. -0.0998j, 0.995 +0.j ]], [[0.9888+0.j , 0. -0.1494j], [0. -0.1494j, 0.9888+0.j ]]], requires_grad=True) >>> qml.matrix(op).shape (3, 2, 2)

This can be extended to quantum functions, where we may mix-and-match operations with batched parameters and those without. However, the

`batch_size`

of each batched`Operator`

within the quantum function must be the same:>>> dev = qml.device('default.qubit', wires=1) >>> @qml.qnode(dev) ... def circuit_rx(x, z): ... qml.RX(x, wires=0) ... qml.RZ(z, wires=0) ... qml.RY(0.3, wires=0) ... return qml.probs(wires=0) >>> circuit_rx([0.1, 0.2], [0.3, 0.4]) tensor([[0.97092256, 0.02907744], [0.95671515, 0.04328485]], requires_grad=True)

Parameter broadcasting is supported on all devices, hardware and simulator. Note that if not natively supported by the underlying device, parameter broadcasting may result in additional quantum device evaluations.

A new transform,

`qml.transforms.broadcast_expand`

, has been added, which automates the process of transforming quantum functions (and tapes) to multiple quantum evaluations with no parameter broadcasting. (#2590)>>> dev = qml.device('default.qubit', wires=1) >>> @qml.transforms.broadcast_expand() >>> @qml.qnode(dev) ... def circuit_rx(x, z): ... qml.RX(x, wires=0) ... qml.RZ(z, wires=0) ... qml.RY(0.3, wires=0) ... return qml.probs(wires=0) >>> print(qml.draw(circuit_rx)([0.1, 0.2], [0.3, 0.4])) 0: ──RX(0.10)──RZ(0.30)──RY(0.30)─┤ Probs \ 0: ──RX(0.20)──RZ(0.40)──RY(0.30)─┤ Probs

Under-the-hood, this transform is used for devices that don’t natively support parameter broadcasting.

To specify that a device natively supports broadcasted tapes, the new flag

`Device.capabilities()["supports_broadcasting"]`

should be set to`True`

.To support parameter broadcasting for new or custom operations, the following new

`Operator`

class attributes must be specified:`Operator.ndim_params`

specifies expected number of dimensions for each parameter

Once set,

`Operator.batch_size`

and`QuantumTape.batch_size`

will dynamically compute the parameter broadcasting axis dimension, if present.

#### Improved JAX JIT support 🏎

JAX just-in-time (JIT) compilation now supports vector-valued QNodes, enabling new types of workflows and significant performance boosts. (#2034)

Vector-valued QNodes include those with:

`qml.probs`

;`qml.state`

;`qml.sample`

ormultiple

`qml.expval`

/`qml.var`

measurements.

Consider a QNode that returns basis-state probabilities:

dev = qml.device('default.qubit', wires=2) x = jnp.array(0.543) y = jnp.array(-0.654) @jax.jit @qml.qnode(dev, diff_method="parameter-shift", interface="jax") def circuit(x, y): qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) return qml.probs(wires=[1])

>>> circuit(x, y) DeviceArray([0.8397495 , 0.16025047], dtype=float32)

Note that computing the jacobian of vector-valued QNode is not supported with JAX JIT. The output of vector-valued QNodes can, however, be used in the definition of scalar-valued cost functions whose gradients can be computed.

For example, one can define a cost function that outputs the first element of the probability vector:

def cost(x, y): return circuit(x, y)[0]

>>> jax.grad(cost, argnums=[0])(x, y) (DeviceArray(-0.2050439, dtype=float32),)

#### More drawing styles 🎨

New

`solarized_light`

and`solarized_dark`

styles are available for drawing circuit diagram graphics. (#2662)

#### New operations & transforms 🤖

The

`qml.IsingXY`

gate is now available (see 1912.04424). (#2649)The

`qml.ECR`

(echoed cross-resonance) operation is now available (see 2105.01063). This gate is a maximally-entangling gate and is equivalent to a CNOT gate up to single-qubit pre-rotations. (#2613)The adjoint transform

`adjoint`

can now accept either a single instantiated operator or a quantum function. It returns an entity of the same type / call signature as what it was given: (#2222) (#2672)>>> qml.adjoint(qml.PauliX(0)) Adjoint(PauliX)(wires=[0]) >>> qml.adjoint(qml.RX)(1.23, wires=0) Adjoint(RX)(1.23, wires=[0])

Now,

`adjoint`

wraps operators in a symbolic operator class`qml.ops.op_math.Adjoint`

. This class should not be constructed directly; the`adjoint`

constructor should always be used instead. The class behaves just like any other`Operator`

:>>> op = qml.adjoint(qml.S(0)) >>> qml.matrix(op) array([[1.-0.j, 0.-0.j], [0.-0.j, 0.-1.j]]) >>> qml.eigvals(op) array([1.-0.j, 0.-1.j])

A new symbolic operator class

`qml.ops.op_math.Pow`

represents an operator raised to a power. When`decomposition()`

is called, a list of new operators equal to this one raised to the given power is given: (#2621)>>> op = qml.ops.op_math.Pow(qml.PauliX(0), 0.5) >>> op.decomposition() [SX(wires=[0])] >>> qml.matrix(op) array([[0.5+0.5j, 0.5-0.5j], [0.5-0.5j, 0.5+0.5j]])

A new transform

`qml.batch_partial`

is available which behaves similarly to`functools.partial`

, but supports batching in the unevaluated parameters. (#2585)This is useful for executing a circuit with a batch dimension in some of its parameters:

dev = qml.device("default.qubit", wires=1) @qml.qnode(dev) def circuit(x, y): qml.RX(x, wires=0) qml.RY(y, wires=0) return qml.expval(qml.PauliZ(wires=0))

>>> batched_partial_circuit = qml.batch_partial(circuit, x=np.array(np.pi / 4)) >>> y = np.array([0.2, 0.3, 0.4]) >>> batched_partial_circuit(y=y) tensor([0.69301172, 0.67552491, 0.65128847], requires_grad=True)

A new transform

`qml.split_non_commuting`

is available, which splits a quantum function or tape into multiple functions/tapes determined by groups of commuting observables: (#2587)dev = qml.device("default.qubit", wires=1) @qml.transforms.split_non_commuting @qml.qnode(dev) def circuit(x): qml.RX(x,wires=0) return [qml.expval(qml.PauliX(0)), qml.expval(qml.PauliZ(0))]

>>> print(qml.draw(circuit)(0.5)) 0: ──RX(0.50)─┤ <X> \ 0: ──RX(0.50)─┤ <Z>

### Improvements

Expectation values of multiple non-commuting observables from within a single QNode are now supported: (#2587)

>>> dev = qml.device('default.qubit', wires=1) >>> @qml.qnode(dev) ... def circuit_rx(x, z): ... qml.RX(x, wires=0) ... qml.RZ(z, wires=0) ... return qml.expval(qml.PauliX(0)), qml.expval(qml.PauliY(0)) >>> circuit_rx(0.1, 0.3) tensor([ 0.02950279, -0.09537451], requires_grad=True)

Selecting which parts of parameter-shift Hessians are computed is now possible. (#2538)

The

`argnum`

keyword argument for`qml.gradients.param_shift_hessian`

is now allowed to be a two-dimensional Boolean`array_like`

. Only the indicated entries of the Hessian will then be computed.A particularly useful example is the computation of the diagonal of the Hessian:

dev = qml.device("default.qubit", wires=1) @qml.qnode(dev) def circuit(x): qml.RX(x[0], wires=0) qml.RY(x[1], wires=0) qml.RX(x[2], wires=0) return qml.expval(qml.PauliZ(0)) argnum = qml.math.eye(3, dtype=bool) x = np.array([0.2, -0.9, 1.1], requires_grad=True)

>>> qml.gradients.param_shift_hessian(circuit, argnum=argnum)(x) tensor([[-0.09928388, 0. , 0. ], [ 0. , -0.27633945, 0. ], [ 0. , 0. , -0.09928388]], requires_grad=True)

Commuting Pauli operators are now measured faster. (#2425)

The logic that checks for qubit-wise commuting (QWC) observables has been improved, resulting in a performance boost that is noticable when many commuting Pauli operators of the same type are measured.

It is now possible to add

`Observable`

objects to the integer`0`

, for example`qml.PauliX(wires=[0]) + 0`

. (#2603)Wires can now be passed as the final argument to an

`Operator`

, instead of requiring the wires to be explicitly specified with keyword`wires`

. This functionality already existed for`Observable`

s, but now extends to all`Operator`

s: (#2432)>>> qml.S(0) S(wires=[0]) >>> qml.CNOT((0,1)) CNOT(wires=[0, 1])

The

`qml.taper`

function can now be used to consistently taper any additional observables such as dipole moment, particle number, and spin operators using the symmetries obtained from the Hamiltonian. (#2510)Sparse Hamiltonians’ representation has changed from Coordinate (COO) to Compressed Sparse Row (CSR) format. (#2561)

The CSR representation is more performant for arithmetic operations and matrix-vector products. This change decreases the

`expval()`

calculation time for`qml.SparseHamiltonian`

, specially for large workflows. In addition, the CSR format consumes less memory for`qml.SparseHamiltonian`

storage.IPython now displays the

`str`

representation of a`Hamiltonian`

, rather than the`repr`

. This displays more information about the object. (#2648)The

`qml.qchem`

tests have been restructured. (#2593) (#2545)OpenFermion-dependent tests are now localized and collected in

`tests.qchem.of_tests`

. The new module`test_structure`

is created to collect the tests of the`qchem.structure`

module in one place and remove their dependency to OpenFermion.Test classes have been created to group the integrals and matrices unit tests.

An

`operations_only`

argument is introduced to the`tape.get_parameters`

method. (#2543)The

`gradients`

module now uses faster subroutines and uniform formats of gradient rules. (#2452)Instead of checking types, objects are now processed in the

`QuantumTape`

based on a new`_queue_category`

property. This is a temporary fix that will disappear in the future. (#2408)The

`QNode`

class now contains a new method`best_method_str`

that returns the best differentiation method for a provided device and interface, in human-readable format. (#2533)Using

`Operation.inv()`

in a queuing environment no longer updates the queue’s metadata, but merely updates the operation in place. (#2596)A new method

`safe_update_info`

is added to`qml.QueuingContext`

. This method is substituted for`qml.QueuingContext.update_info`

in a variety of places. (#2612) (#2675)`BasisEmbedding`

can accept an int as argument instead of a list of bits. (#2601)For example,

`qml.BasisEmbedding(4, wires = range(4))`

is now equivalent to`qml.BasisEmbedding([0,1,0,0], wires = range(4))`

(as`4==0b100`

).Introduced a new

`is_hermitian`

property to`Operator`

s to determine if an operator can be used in a measurement process. (#2629)Added separate

`requirements_dev.txt`

for separation of concerns for code development and just using PennyLane. (#2635)The performance of building sparse Hamiltonians has been improved by accumulating the sparse representation of coefficient-operator pairs in a temporary storage and by eliminating unnecessary

`kron`

operations on identity matrices. (#2630)Control values are now displayed distinctly in text and matplotlib drawings of circuits. (#2668)

The

`TorchLayer`

`init_method`

argument now accepts either a`torch.nn.init`

function or a dictionary which should specify a`torch.nn.init`

/`torch.Tensor`

for each different weight. (#2678)The unused keyword argument

`do_queue`

for`Operation.adjoint`

is now fully removed. (#2583)Several non-decomposable

`Adjoint`

operators are added to the device test suite. (#2658)The developer-facing

`pow`

method has been added to`Operator`

with concrete implementations for many classes. (#2225)The

`ctrl`

transform and`ControlledOperation`

have been moved to the new`qml.ops.op_math`

submodule. The developer-facing`ControlledOperation`

class is no longer imported top-level. (#2656)

### Deprecations

`qml.ExpvalCost`

has been deprecated, and usage will now raise a warning. (#2571)Instead, it is recommended to simply pass Hamiltonians to the

`qml.expval`

function inside QNodes:@qml.qnode(dev) def ansatz(params): some_qfunc(params) return qml.expval(Hamiltonian)

### Breaking changes

When using

`qml.TorchLayer`

, weights with negative shapes will now raise an error, while weights with`size = 0`

will result in creating empty Tensor objects. (#2678)PennyLane no longer supports TensorFlow

`<=2.3`

. (#2683)The

`qml.queuing.Queue`

class has been removed. (#2599)The

`qml.utils.expand`

function is now removed;`qml.operation.expand_matrix`

should be used instead. (#2654)The module

`qml.gradients.param_shift_hessian`

has been renamed to`qml.gradients.parameter_shift_hessian`

in order to distinguish it from the identically named function. Note that the`param_shift_hessian`

function is unaffected by this change and can be invoked in the same manner as before via the`qml.gradients`

module. (#2528)The properties

`eigval`

and`matrix`

from the`Operator`

class were replaced with the methods`eigval()`

and`matrix(wire_order=None)`

. (#2498)`Operator.decomposition()`

is now an instance method, and no longer accepts parameters. (#2498)Adds tests, adds no-coverage directives, and removes inaccessible logic to improve code coverage. (#2537)

The base classes

`QubitDevice`

and`DefaultQubit`

now accept data-types for a statevector. This enables a derived class (device) in a plugin to choose correct data-types: (#2448)>>> dev = qml.device("default.qubit", wires=4, r_dtype=np.float32, c_dtype=np.complex64) >>> dev.R_DTYPE <class 'numpy.float32'> >>> dev.C_DTYPE <class 'numpy.complex64'>

### Bug fixes

Fixed a bug where returning

`qml.density_matrix`

using the PyTorch interface would return a density matrix with wrong shape. (#2643)Fixed a bug to make

`param_shift_hessian`

work with QNodes in which gates marked as trainable do not have any impact on the QNode output. (#2584)QNodes can now interpret variations on the interface name, like

`"tensorflow"`

or`"jax-jit"`

, when requesting backpropagation. (#2591)Fixed a bug for

`diff_method="adjoint"`

where incorrect gradients were computed for QNodes with parametrized observables (e.g.,`qml.Hermitian`

). (#2543)Fixed a bug where

`QNGOptimizer`

did not work with operators whose generator was a Hamiltonian. (#2524)Fixed a bug with the decomposition of

`qml.CommutingEvolution`

. (#2542)Fixed a bug enabling PennyLane to work with the latest version of Autoray. (#2549)

Fixed a bug which caused different behaviour for

`Hamiltonian @ Observable`

and`Observable @ Hamiltonian`

. (#2570)Fixed a bug in

`DiagonalQubitUnitary._controlled`

where an invalid operation was queued instead of the controlled version of the diagonal unitary. (#2525)Updated the gradients fix to only apply to the

`strawberryfields.gbs`

device, since the original logic was breaking some devices. (#2485) (#2595)Fixed a bug in

`qml.transforms.insert`

where operations were not inserted after gates within a template. (#2704)`Hamiltonian.wires`

is now properly updated after in place operations. (#2738)

### Documentation

The centralized Xanadu Sphinx Theme is now used to style the Sphinx documentation. (#2450)

Added a reference to

`qml.utils.sparse_hamiltonian`

in`qml.SparseHamiltonian`

to clarify how to construct sparse Hamiltonians in PennyLane. (2572)Added a new section in the Gradients and Training page that summarizes the supported device configurations and provides justification. In addition, code examples were added for some selected configurations. (#2540)

Added a note for the Depolarization Channel that specifies how the channel behaves for the different values of depolarization probability

`p`

. (#2669)The quickstart documentation has been improved. (#2530) (#2534) (#2564 (#2565 (#2566) (#2607) (#2608)

The quantum chemistry quickstart documentation has been improved. (#2500)

Testing documentation has been improved. (#2536)

Documentation for the

`pre-commit`

package has been added. (#2567)Documentation for draw control wires change has been updated. (#2682)

### Contributors

This release contains contributions from (in alphabetical order):

Guillermo Alonso-Linaje, Mikhail Andrenkov, Juan Miguel Arrazola, Ali Asadi, Utkarsh Azad, Samuel Banning, Avani Bhardwaj, Thomas Bromley, Albert Mitjans Coma, Isaac De Vlugt, Amintor Dusko, Trent Fridey, Christian Gogolin, Qi Hu, Katharine Hyatt, David Ittah, Josh Izaac, Soran Jahangiri, Edward Jiang, Nathan Killoran, Korbinian Kottmann, Ankit Khandelwal, Christina Lee, Chae-Yeun Park, Mason Moreland, Romain Moyard, Maria Schuld, Jay Soni, Antal Száva, tal66, David Wierichs, Roeland Wiersema, WingCode.

- orphan

## Release 0.23.1¶

### Bug fixes

Fixed a bug enabling PennyLane to work with the latest version of Autoray. (#2548)

### Contributors

This release contains contributions from (in alphabetical order):

Josh Izaac

- orphan

## Release 0.23.0¶

### New features since last release

#### More powerful circuit cutting ✂️

Quantum circuit cutting (running

`N`

-wire circuits on devices with fewer than`N`

wires) is now supported for QNodes of finite-shots using the new`@qml.cut_circuit_mc`

transform. (#2313) (#2321) (#2332) (#2358) (#2382) (#2399) (#2407) (#2444)With these new additions, samples from the original circuit can be simulated using a Monte Carlo method, using fewer qubits at the expense of more device executions. Additionally, this transform can take an optional classical processing function as an argument and return an expectation value.

The following

`3`

-qubit circuit contains a`WireCut`

operation and a`sample`

measurement. When decorated with`@qml.cut_circuit_mc`

, we can cut the circuit into two`2`

-qubit fragments:dev = qml.device("default.qubit", wires=2, shots=1000) @qml.cut_circuit_mc @qml.qnode(dev) def circuit(x): qml.RX(0.89, wires=0) qml.RY(0.5, wires=1) qml.RX(1.3, wires=2) qml.CNOT(wires=[0, 1]) qml.WireCut(wires=1) qml.CNOT(wires=[1, 2]) qml.RX(x, wires=0) qml.RY(0.7, wires=1) qml.RX(2.3, wires=2) return qml.sample(wires=[0, 2])

we can then execute the circuit as usual by calling the QNode:

>>> x = 0.3 >>> circuit(x) tensor([[1, 1], [0, 1], [0, 1], ..., [0, 1], [0, 1], [0, 1]], requires_grad=True)

Furthermore, the number of shots can be temporarily altered when calling the QNode:

>>> results = circuit(x, shots=123) >>> results.shape (123, 2)

The

`cut_circuit_mc`

transform also supports returning sample-based expectation values of observables using the`classical_processing_fn`

argument. Refer to the`UsageDetails`

section of the transform documentation for an example.The

`cut_circuit`

transform now supports automatic graph partitioning by specifying`auto_cutter=True`

to cut arbitrary tape-converted graphs using the general purpose graph partitioning framework KaHyPar. (#2330) (#2428)Note that

`KaHyPar`

needs to be installed separately with the`auto_cutter=True`

option.For integration with the existing low-level manual cut pipeline, refer to the documentation of the function .

@qml.cut_circuit(auto_cutter=True) @qml.qnode(dev) def circuit(x): qml.RX(x, wires=0) qml.RY(0.9, wires=1) qml.RX(0.3, wires=2) qml.CZ(wires=[0, 1]) qml.RY(-0.4, wires=0) qml.CZ(wires=[1, 2]) return qml.expval(qml.grouping.string_to_pauli_word("ZZZ"))

>>> x = np.array(0.531, requires_grad=True) >>> circuit(x) 0.47165198882111165 >>> qml.grad(circuit)(x) -0.276982865449393

#### Grand QChem unification ⚛️ 🏰

Quantum chemistry functionality — previously split between an external

`pennylane-qchem`

package and internal`qml.hf`

differentiable Hartree-Fock solver — is now unified into a single, included,`qml.qchem`

module. (#2164) (#2385) (#2352) (#2420) (#2454)

(#2199) (#2371) (#2272) (#2230) (#2415) (#2426) (#2465)The

`qml.qchem`

module provides a differentiable Hartree-Fock solver and the functionality to construct a fully-differentiable molecular Hamiltonian.For example, one can continue to generate molecular Hamiltonians using

`qml.qchem.molecular_hamiltonian`

:symbols = ["H", "H"] geometry = np.array([[0., 0., -0.66140414], [0., 0., 0.66140414]]) hamiltonian, qubits = qml.qchem.molecular_hamiltonian(symbols, geometry, method="dhf")

By default, this will use the differentiable Hartree-Fock solver; however, simply set

`method="pyscf"`

to continue to use PySCF for Hartree-Fock calculations.Functions are added for building a differentiable dipole moment observable. Functions for computing multipole moment molecular integrals, needed for building the dipole moment observable, are also added. (#2173) (#2166)

The dipole moment observable can be constructed using

`qml.qchem.dipole_moment`

:symbols = ['H', 'H'] geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]]) mol = qml.qchem.Molecule(symbols, geometry) args = [geometry] D = qml.qchem.dipole_moment(mol)(*args)

The efficiency of computing molecular integrals and Hamiltonian is improved. This has been done by adding optimized functions for building fermionic and qubit observables and optimizing the functions used for computing the electron repulsion integrals. (#2316)

The

`6-31G`

basis set is added to the qchem basis set repo. This addition allows performing differentiable Hartree-Fock calculations with basis sets beyond the minimal`sto-3g`

basis set for atoms with atomic number 1-10. (#2372)The

`6-31G`

basis set can be used to construct a Hamiltonian assymbols = ["H", "H"] geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]]) H, qubits = qml.qchem.molecular_hamiltonian(symbols, geometry, basis="6-31g")

External dependencies are replaced with local functions for spin and particle number observables. (#2197) (#2362)

#### Pattern matching optimization 🔎 💎

Added an optimization transform that matches pieces of user-provided identity templates in a circuit and replaces them with an equivalent component. (#2032)

For example, consider the following circuit where we want to replace sequence of two

`pennylane.S`

gates with a`pennylane.PauliZ`

gate.def circuit(): qml.S(wires=0) qml.PauliZ(wires=0) qml.S(wires=1) qml.CZ(wires=[0, 1]) qml.S(wires=1) qml.S(wires=2) qml.CZ(wires=[1, 2]) qml.S(wires=2) return qml.expval(qml.PauliX(wires=0))

We specify use the following pattern that implements the identity:

with qml.tape.QuantumTape() as pattern: qml.S(wires=0) qml.S(wires=0) qml.PauliZ(wires=0)

To optimize the circuit with this identity pattern, we apply the

`qml.transforms.pattern_matching`

transform.>>> dev = qml.device('default.qubit', wires=5) >>> qnode = qml.QNode(circuit, dev) >>> optimized_qfunc = qml.transforms.pattern_matching_optimization(pattern_tapes=[pattern])(circuit) >>> optimized_qnode = qml.QNode(optimized_qfunc, dev) >>> print(qml.draw(qnode)()) 0: ──S──Z─╭C──────────┤ <X> 1: ──S────╰Z──S─╭C────┤ 2: ──S──────────╰Z──S─┤ >>> print(qml.draw(optimized_qnode)()) 0: ──S⁻¹─╭C────┤ <X> 1: ──Z───╰Z─╭C─┤ 2: ──Z──────╰Z─┤

For more details on using pattern matching optimization you can check the corresponding documentation and also the following paper.

#### Measure the distance between two unitaries📏

Added the

`HilbertSchmidt`

and the`LocalHilbertSchmidt`

templates to be used for computing distance measures between unitaries. (#2364)Given a unitary

`U`

,`qml.HilberSchmidt`

can be used to measure the distance between unitaries and to define a cost function (`cost_hst`

) used for learning a unitary`V`

that is equivalent to`U`

up to a global phase:# Represents unitary U with qml.tape.QuantumTape(do_queue=False) as u_tape: qml.Hadamard(wires=0) # Represents unitary V def v_function(params): qml.RZ(params[0], wires=1) @qml.qnode(dev) def hilbert_test(v_params, v_function, v_wires, u_tape): qml.HilbertSchmidt(v_params, v_function=v_function, v_wires=v_wires, u_tape=u_tape) return qml.probs(u_tape.wires + v_wires) def cost_hst(parameters, v_function, v_wires, u_tape): return (1 - hilbert_test(v_params=parameters, v_function=v_function, v_wires=v_wires, u_tape=u_tape)[0])

>>> cost_hst(parameters=[0.1], v_function=v_function, v_wires=[1], u_tape=u_tape) tensor(0.999, requires_grad=True)

For more information refer to the documentation of qml.HilbertSchmidt.

#### More tensor network support 🕸️

Adds the

`qml.MERA`

template for implementing quantum circuits with the shape of a multi-scale entanglement renormalization ansatz (MERA). (#2418)MERA follows the style of previous tensor network templates and is similar to quantum convolutional neural networks.

def block(weights, wires): qml.CNOT(wires=[wires[0],wires[1]]) qml.RY(weights[0], wires=wires[0]) qml.RY(weights[1], wires=wires[1]) n_wires = 4 n_block_wires = 2 n_params_block = 2 n_blocks = qml.MERA.get_n_blocks(range(n_wires),n_block_wires) template_weights = [[0.1,-0.3]]*n_blocks dev= qml.device('default.qubit',wires=range(n_wires)) @qml.qnode(dev) def circuit(template_weights): qml.MERA(range(n_wires),n_block_wires,block, n_params_block, template_weights) return qml.expval(qml.PauliZ(wires=1))

It may be necessary to reorder the wires to see the MERA architecture clearly:

>>> print(qml.draw(circuit,expansion_strategy='device',wire_order=[2,0,1,3])(template_weights)) 2: ───────────────╭C──RY(0.10)──╭X──RY(-0.30)───────────────┤ 0: ─╭X──RY(-0.30)─│─────────────╰C──RY(0.10)──╭C──RY(0.10)──┤ 1: ─╰C──RY(0.10)──│─────────────╭X──RY(-0.30)─╰X──RY(-0.30)─┤ <Z> 3: ───────────────╰X──RY(-0.30)─╰C──RY(0.10)────────────────┤

#### New transform for transpilation ⚙️

Added a swap based transpiler transform. (#2118)

The transpile function takes a quantum function and a coupling map as inputs and compiles the circuit to ensure that it can be executed on corresponding hardware. The transform can be used as a decorator in the following way:

dev = qml.device('default.qubit', wires=4) @qml.qnode(dev) @qml.transforms.transpile(coupling_map=[(0, 1), (1, 2), (2, 3)]) def circuit(param): qml.CNOT(wires=[0, 1]) qml.CNOT(wires=[0, 2]) qml.CNOT(wires=[0, 3]) qml.PhaseShift(param, wires=0) return qml.probs(wires=[0, 1, 2, 3])

>>> print(qml.draw(circuit)(0.3)) 0: ─╭C───────╭C──────────╭C──Rϕ(0.30)─┤ ╭Probs 1: ─╰X─╭SWAP─╰X────╭SWAP─╰X───────────┤ ├Probs 2: ────╰SWAP─╭SWAP─╰SWAP──────────────┤ ├Probs 3: ──────────╰SWAP────────────────────┤ ╰Probs

### Improvements

`QuantumTape`

objects are now iterable, allowing iteration over the contained operations and measurements. (#2342)with qml.tape.QuantumTape() as tape: qml.RX(0.432, wires=0) qml.RY(0.543, wires=0) qml.CNOT(wires=[0, 'a']) qml.RX(0.133, wires='a') qml.expval(qml.PauliZ(wires=[0]))

Given a

`QuantumTape`

object the underlying quantum circuit can be iterated over using a`for`

loop:>>> for op in tape: ... print(op) RX(0.432, wires=[0]) RY(0.543, wires=[0]) CNOT(wires=[0, 'a']) RX(0.133, wires=['a']) expval(PauliZ(wires=[0]))

Indexing into the circuit is also allowed via

`tape[i]`

:>>> tape[0] RX(0.432, wires=[0])

A tape object can also be converted to a sequence (e.g., to a

`list`

) of operations and measurements:>>> list(tape) [RX(0.432, wires=[0]), RY(0.543, wires=[0]), CNOT(wires=[0, 'a']), RX(0.133, wires=['a']), expval(PauliZ(wires=[0]))]

Added the

`QuantumTape.shape`

method and`QuantumTape.numeric_type`

attribute to allow extracting information about the shape and numeric type of the output returned by a quantum tape after execution. (#2044)dev = qml.device("default.qubit", wires=2) a = np.array([0.1, 0.2, 0.3]) def func(a): qml.RY(a[0], wires=0) qml.RX(a[1], wires=0) qml.RY(a[2], wires=0) with qml.tape.QuantumTape() as tape: func(a) qml.state()

>>> tape.shape(dev) (1, 4) >>> tape.numeric_type complex

Defined a

`MeasurementProcess.shape`

method and a`MeasurementProcess.numeric_type`

attribute to allow extracting information about the shape and numeric type of results obtained when evaluating QNodes using the specific measurement process. (#2044)The parameter-shift Hessian can now be computed for arbitrary operations that support the general parameter-shift rule for gradients, using

`qml.gradients.param_shift_hessian`

(#2319)Multiple ways to obtain the gradient recipe are supported, in the following order of preference:

A custom

`grad_recipe`

. It is iterated to obtain the shift rule for the second-order derivatives in the diagonal entries of the Hessian.Custom

`parameter_frequencies`

. The second-order shift rule can directly be computed using them.An operation’s

`generator`

. Its eigenvalues will be used to obtain`parameter_frequencies`

, if they are not given explicitly for an operation.

The strategy for expanding a circuit can now be specified with the

`qml.specs`

transform, for example to calculate the specifications of the circuit that will actually be executed by the device (`expansion_strategy="device"`

). (#2395)The

`default.qubit`

and`default.mixed`

devices now skip over identity operators instead of performing matrix multiplication with the identity. (#2356) (#2365)The function

`qml.eigvals`

is modified to use the efficient`scipy.sparse.linalg.eigsh`

method for obtaining the eigenvalues of a`SparseHamiltonian`

. This`scipy`

method is called to compute \(k\) eigenvalues of a sparse \(N \times N\) matrix if`k`

is smaller than \(N-1\). If a larger \(k\) is requested, the dense matrix representation of the Hamiltonian is constructed and the regular`qml.math.linalg.eigvalsh`

is applied. (#2333)The function

`qml.ctrl`

was given the optional argument`control_values=None`

. If overridden,`control_values`

takes an integer or a list of integers corresponding to the binary value that each control value should take. The same change is reflected in`ControlledOperation`

. Control values of`0`

are implemented by`qml.PauliX`

applied before and after the controlled operation (#2288)Operators now have a

`has_matrix`

property denoting whether or not the operator defines a matrix. (#2331) (#2476)Circuit cutting now performs expansion to search for wire cuts in contained operations or tapes. (#2340)

The

`qml.draw`

and`qml.draw_mpl`

transforms are now located in the`drawer`

module. They can still be accessed via the top-level`qml`

namespace. (#2396)Raise a warning where caching produces identical shot noise on execution results with finite shots. (#2478)

### Deprecations

The

`ObservableReturnTypes`

`Sample`

,`Variance`

,`Expectation`

,`Probability`

,`State`

, and`MidMeasure`

have been moved to`measurements`

from`operation`

. (#2329) (#2481)

### Breaking changes

The caching ability of devices has been removed. Using the caching on the QNode level is the recommended alternative going forward. (#2443)

One way for replicating the removed

`QubitDevice`

caching behaviour is by creating a`cache`

object (e.g., a dictionary) and passing it to the`QNode`

:n_wires = 4 wires = range(n_wires) dev = qml.device('default.qubit', wires=n_wires) cache = {} @qml.qnode(dev, diff_method='parameter-shift', cache=cache) def expval_circuit(params): qml.templates.BasicEntanglerLayers(params, wires=wires, rotation=qml.RX) return qml.expval(qml.PauliZ(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliZ(3)) shape = qml.templates.BasicEntanglerLayers.shape(5, n_wires) params = np.random.random(shape)

>>> expval_circuit(params) tensor(0.20598436, requires_grad=True) >>> dev.num_executions 1 >>> expval_circuit(params) tensor(0.20598436, requires_grad=True) >>> dev.num_executions 1

The

`qml.finite_diff`

function has been removed. Please use`qml.gradients.finite_diff`

to compute the gradient of tapes of QNodes. Otherwise, manual implementation is required. (#2464)The

`get_unitary_matrix`

transform has been removed, please use`qml.matrix`

instead. (#2457)The

`update_stepsize`

method has been removed from`GradientDescentOptimizer`

and its child optimizers. The`stepsize`

property can be interacted with directly instead. (#2370)Most optimizers no longer flatten and unflatten arguments during computation. Due to this change, user provided gradient functions

*must*return the same shape as`qml.grad`

. (#2381)The old circuit text drawing infrastructure has been removed. (#2310)

`RepresentationResolver`

was replaced by the`Operator.label`

method.`qml.drawer.CircuitDrawer`

was replaced by`qml.drawer.tape_text`

.`qml.drawer.CHARSETS`

was removed because unicode is assumed to be accessible.`Grid`

and`qml.drawer.drawable_grid`

were removed because the custom data class was replaced by list of sets of operators or measurements.`qml.transforms.draw_old`

was replaced by`qml.draw`

.`qml.CircuitGraph.greedy_layers`

was deleted, as it was no longer needed by the circuit drawer and did not seem to have uses outside of that situation.`qml.CircuitGraph.draw`

was deleted, as we draw tapes instead.The tape method

`qml.tape.QuantumTape.draw`

now simply calls`qml.drawer.tape_text`

.In the new pathway, the

`charset`

keyword was deleted, the`max_length`

keyword defaults to`100`

, and the`decimals`

and`show_matrices`

keywords were added.

The deprecated QNode, available via

`qml.qnode_old.QNode`

, has been removed. Please transition to using the standard`qml.QNode`

. (#2336) (#2337) (#2338)In addition, several other components which powered the deprecated QNode have been removed:

The deprecated, non-batch compatible interfaces, have been removed.

The deprecated tape subclasses

`QubitParamShiftTape`

,`JacobianTape`

,`CVParamShiftTape`

, and`ReversibleTape`

have been removed.

The deprecated tape execution method

`tape.execute(device)`

has been removed. Please use`qml.execute([tape], device)`

instead. (#2339)

### Bug fixes

Fixed a bug in the

`qml.PauliRot`

operation, where computing the generator was not taking into account the operation wires. (#2466)Fixed a bug where non-trainable arguments were shifted in the

`NesterovMomentumOptimizer`

if a trainable argument was after it in the argument list. (#2466)Fixed a bug with

`@jax.jit`

for grad when`diff_method="adjoint"`

and`mode="backward"`

. (#2460)Fixed a bug where

`qml.DiagonalQubitUnitary`

did not support`@jax.jit`

and`@tf.function`

. (#2445)Fixed a bug in the

`qml.PauliRot`

operation, where computing the generator was not taking into account the operation wires. (#2442)Fixed a bug with the padding capability of

`AmplitudeEmbedding`

where the inputs are on the GPU. (#2431)Fixed a bug by adding a comprehensible error message for calling

`qml.probs`

without passing wires or an observable. (#2438)The behaviour of

`qml.about()`

was modified to avoid warnings being emitted due to legacy behaviour of`pip`

. (#2422)Fixed a bug where observables were not considered when determining the use of the

`jax-jit`

interface. (#2427) (#2474)Fixed a bug where computing statistics for a relatively few number of shots (e.g.,

`shots=10`

), an error arose due to indexing into an array using a`DeviceArray`

. (#2427)PennyLane Lightning version in Docker container is pulled from latest wheel-builds. (#2416)

Optimizers only consider a variable trainable if they have

`requires_grad = True`

. (#2381)Fixed a bug with

`qml.expval`

,`qml.var`

,`qml.state`

and`qml.probs`

(when`qml.probs`

is the only measurement) where the`dtype`

specified on the device did not match the`dtype`

of the QNode output. (#2367)Fixed a bug where the output shapes from batch transforms are inconsistent with the QNode output shape. (#2215)

Fixed a bug caused by the squeezing in

`qml.gradients.param_shift_hessian`

. (#2215)Fixed a bug in which the

`expval`

/`var`

of a`Tensor(Observable)`

would depend on the order in which the observable is defined: (#2276)>>> @qml.qnode(dev) ... def circ(op): ... qml.RX(0.12, wires=0) ... qml.RX(1.34, wires=1) ... qml.RX(3.67, wires=2) ... return qml.expval(op) >>> op1 = qml.Identity(wires=0) @ qml.Identity(wires=1) @ qml.PauliZ(wires=2) >>> op2 = qml.PauliZ(wires=2) @ qml.Identity(wires=0) @ qml.Identity(wires=1) >>> print(circ(op1), circ(op2)) -0.8636111153905662 -0.8636111153905662

Fixed a bug where

`qml.hf.transform_hf()`

would fail due to missing wires in the qubit operator that is prepared for tapering the HF state. (#2441)Fixed a bug with custom device defined jacobians not being returned properly. (#2485)

### Documentation

The sections on adding operator and observable support in the “How to add a plugin” section of the plugins page have been updated. (#2389)

The missing arXiv reference in the

`LieAlgebra`

optimizer has been fixed. (#2325)

### Contributors

This release contains contributions from (in alphabetical order):

Karim Alaa El-Din, Guillermo Alonso-Linaje, Juan Miguel Arrazola, Ali Asadi, Utkarsh Azad, Sam Banning, Thomas Bromley, Alain Delgado, Isaac De Vlugt, Olivia Di Matteo, Amintor Dusko, Anthony Hayes, David Ittah, Josh Izaac, Soran Jahangiri, Nathan Killoran, Christina Lee, Angus Lowe, Romain Moyard, Zeyue Niu, Matthew Silverman, Lee James O’Riordan, Maria Schuld, Jay Soni, Antal Száva, Maurice Weber, David Wierichs.

- orphan

## Release 0.22.2¶

### Bug fixes

Most compilation transforms, and relevant subroutines, have been updated to support just-in-time compilation with jax.jit. This fix was intended to be included in

`v0.22.0`

, but due to a bug was incomplete. (#2397)

### Documentation

The documentation run has been updated to require

`jinja2==3.0.3`

due to an issue that arises with`jinja2`

`v3.1.0`

and`sphinx`

`v3.5.3`

. (#2378)

### Contributors

This release contains contributions from (in alphabetical order):

Olivia Di Matteo, Christina Lee, Romain Moyard, Antal Száva.

- orphan

## Release 0.22.1¶

### Bug fixes

Fixes cases with

`qml.measure`

where unexpected operations were added to the circuit. (#2328)

### Contributors

This release contains contributions from (in alphabetical order):

Guillermo Alonso-Linaje, Antal Száva.

- orphan

## Release 0.22.0¶

### New features since last release

#### Quantum circuit cutting ✂️

You can now run

`N`

-wire circuits on devices with fewer than`N`

wires, by strategically placing`WireCut`

operations that allow their circuit to be partitioned into smaller fragments, at a cost of needing to perform a greater number of device executions. Circuit cutting is enabled by decorating a QNode with the`@qml.cut_circuit`

transform. (#2107) (#2124) (#2153) (#2165) (#2158) (#2169) (#2192) (#2216) (#2168) (#2223) (#2231) (#2234) (#2244) (#2251) (#2265) (#2254) (#2260) (#2257) (#2279)The example below shows how a three-wire circuit can be run on a two-wire device:

dev = qml.device("default.qubit", wires=2) @qml.cut_circuit @qml.qnode(dev) def circuit(x): qml.RX(x, wires=0) qml.RY(0.9, wires=1) qml.RX(0.3, wires=2) qml.CZ(wires=[0, 1]) qml.RY(-0.4, wires=0) qml.WireCut(wires=1) qml.CZ(wires=[1, 2]) return qml.expval(qml.grouping.string_to_pauli_word("ZZZ"))

Instead of executing the circuit directly, it will be partitioned into smaller fragments according to the

`WireCut`

locations, and each fragment executed multiple times. Combining the results of the fragment executions will recover the expected output of the original uncut circuit.>>> x = np.array(0.531, requires_grad=True) >>> circuit(0.531) 0.47165198882111165

Circuit cutting support is also differentiable:

>>> qml.grad(circuit)(x) -0.276982865449393

For more details on circuit cutting, check out the qml.cut_circuit documentation page or Peng et. al.

#### Conditional operations: quantum teleportation unlocked 🔓🌀

Support for mid-circuit measurements and conditional operations has been added, to enable use cases like quantum teleportation, quantum error correction and quantum error mitigation. (#2211) (#2236) (#2275)

Two new functions have been added to support this capability:

`qml.measure()`

places mid-circuit measurements in the middle of a quantum function.`qml.cond()`

allows operations and quantum functions to be conditioned on the result of a previous measurement.

For example, the code below shows how to teleport a qubit from wire 0 to wire 2:

dev = qml.device("default.qubit", wires=3) input_state = np.array([1, -1], requires_grad=False) / np.sqrt(2) @qml.qnode(dev) def teleport(state): # Prepare input state qml.QubitStateVector(state, wires=0) # Prepare Bell state qml.Hadamard(wires=1) qml.CNOT(wires=[1, 2]) # Apply gates qml.CNOT(wires=[0, 1]) qml.Hadamard(wires=0) # Measure first two wires m1 = qml.measure(0) m2 = qml.measure(1) # Condition final wire on results qml.cond(m2 == 1, qml.PauliX)(wires=2) qml.cond(m1 == 1, qml.PauliZ)(wires=2) # Return state on final wire return qml.density_matrix(wires=2)

We can double-check that the qubit has been teleported by computing the overlap between the input state and the resulting state on wire 2:

>>> output_state = teleport(input_state) >>> output_state tensor([[ 0.5+0.j, -0.5+0.j], [-0.5+0.j, 0.5+0.j]], requires_grad=True) >>> input_state.conj() @ output_state @ input_state tensor(1.+0.j, requires_grad=True)

For a full description of new capabilities, refer to the Mid-circuit measurements and conditional operations section in the documentation.

Train mid-circuit measurements by deferring them, via the new

`@qml.defer_measurements`

transform. (#2211) (#2236) (#2275)If a device doesn’t natively support mid-circuit measurements, the

`@qml.defer_measurements`

transform can be applied to the QNode to transform the QNode into one with*terminal*measurements and*controlled*operations:dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) @qml.defer_measurements def circuit(x): qml.Hadamard(wires=0) m = qml.measure(0) def op_if_true(): return qml.RX(x**2, wires=1) def op_if_false(): return qml.RY(x, wires=1) qml.cond(m==1, op_if_true, op_if_false)() return qml.expval(qml.PauliZ(1))

>>> x = np.array(0.7, requires_grad=True) >>> print(qml.draw(circuit, expansion_strategy="device")(x)) 0: ──H─╭C─────────X─╭C─────────X─┤ 1: ────╰RX(0.49)────╰RY(0.70)────┤ <Z> >>> circuit(x) tensor(0.82358752, requires_grad=True)

Deferring mid-circuit measurements also enables differentiation:

>>> qml.grad(circuit)(x) -0.651546965338656

#### Debug with mid-circuit quantum snapshots 📷

A new operation

`qml.Snapshot`

has been added to assist in debugging quantum functions. (#2233) (#2289) (#2291) (#2315)`qml.Snapshot`

saves the internal state of devices at arbitrary points of execution.Currently supported devices include:

`default.qubit`

: each snapshot saves the quantum state vector`default.mixed`

: each snapshot saves the density matrix`default.gaussian`

: each snapshot saves the covariance matrix and vector of means

During normal execution, the snapshots are ignored:

dev = qml.device("default.qubit", wires=2) @qml.qnode(dev, interface=None) def circuit(): qml.Snapshot() qml.Hadamard(wires=0) qml.Snapshot("very_important_state") qml.CNOT(wires=[0, 1]) qml.Snapshot() return qml.expval(qml.PauliX(0))

However, when using the

`qml.snapshots`

transform, intermediate device states will be stored and returned alongside the results.>>> qml.snapshots(circuit)() {0: array([1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]), 'very_important_state': array([0.70710678+0.j, 0. +0.j, 0.70710678+0.j, 0. +0.j]), 2: array([0.70710678+0.j, 0. +0.j, 0. +0.j, 0.70710678+0.j]), 'execution_results': array(0.)}

#### Batch embedding and state preparation data 📦

Added the

`@qml.batch_input`

transform to enable batching non-trainable gate parameters. In addition, the`qml.qnn.KerasLayer`

class has been updated to natively support batched training data. (#2069)As with other transforms,

`@qml.batch_input`

can be used to decorate QNodes:dev = qml.device("default.qubit", wires=2, shots=None) @qml.batch_input(argnum=0) @qml.qnode(dev, diff_method="parameter-shift", interface="tf") def circuit(inputs, weights): # add a batch dimension to the embedding data qml.AngleEmbedding(inputs, wires=range(2), rotation="Y") qml.RY(weights[0], wires=0) qml.RY(weights[1], wires=1) return qml.expval(qml.PauliZ(1))

Batched input parameters can then be passed during QNode evaluation:

>>> x = tf.random.uniform((10, 2), 0, 1) >>> w = tf.random.uniform((2,), 0, 1) >>> circuit(x, w) <tf.Tensor: shape=(10,), dtype=float64, numpy= array([0.46230079, 0.73971315, 0.95666004, 0.5355225 , 0.66180948, 0.44519553, 0.93874261, 0.9483197 , 0.78737918, 0.90866411])>

#### Even more mighty quantum transforms 🐛➡🦋

New functions and transforms of operators have been added:

`qml.matrix()`

for computing the matrix representation of one or more unitary operators. (#2241)`qml.eigvals()`

for computing the eigenvalues of one or more operators. (#2248)`qml.generator()`

for computing the generator of a single-parameter unitary operation. (#2256)

All operator transforms can be used on instantiated operators,

>>> op = qml.RX(0.54, wires=0) >>> qml.matrix(op) [[0.9637709+0.j 0. -0.26673144j] [0. -0.26673144j 0.9637709+0.j ]]

Operator transforms can also be used in a functional form:

>>> x = torch.tensor(0.6, requires_grad=True) >>> matrix_fn = qml.matrix(qml.RX) >>> matrix_fn(x, wires=[0]) tensor([[0.9553+0.0000j, 0.0000-0.2955j], [0.0000-0.2955j, 0.9553+0.0000j]], grad_fn=<AddBackward0>)

In its functional form, it is fully differentiable with respect to gate arguments:

>>> loss = torch.real(torch.trace(matrix_fn(x, wires=0))) >>> loss.backward() >>> x.grad tensor(-0.2955)

Some operator transform can also act on multiple operations, by passing quantum functions or tapes:

>>> def circuit(theta): ... qml.RX(theta, wires=1) ... qml.PauliZ(wires=0) >>> qml.matrix(circuit)(np.pi / 4) array([[ 0.92387953+0.j, 0.+0.j , 0.-0.38268343j, 0.+0.j], [ 0.+0.j, -0.92387953+0.j, 0.+0.j, 0. +0.38268343j], [ 0. -0.38268343j, 0.+0.j, 0.92387953+0.j, 0.+0.j], [ 0.+0.j, 0.+0.38268343j, 0.+0.j, -0.92387953+0.j]])

A new transform has been added to construct the pairwise-commutation directed acyclic graph (DAG) representation of a quantum circuit. (#1712)

In the DAG, each node represents a quantum operation, and edges represent non-commutation between two operations.

This transform takes into account that not all operations can be moved next to each other by pairwise commutation:

>>> def circuit(x, y, z): ... qml.RX(x, wires=0) ... qml.RX(y, wires=0) ... qml.CNOT(wires=[1, 2]) ... qml.RY(y, wires=1) ... qml.Hadamard(wires=2) ... qml.CRZ(z, wires=[2, 0]) ... qml.RY(-y, wires=1) ... return qml.expval(qml.PauliZ(0)) >>> dag_fn = qml.commutation_dag(circuit) >>> dag = dag_fn(np.pi / 4, np.pi / 3, np.pi / 2)

Nodes in the commutation DAG can be accessed via the

`get_nodes()`

method, returning a list of the form`(ID, CommutationDAGNode)`

:>>> nodes = dag.get_nodes() >>> nodes NodeDataView({0: <pennylane.transforms.commutation_dag.CommutationDAGNode object at 0x7f461c4bb580>, ...}, data='node')

Specific nodes in the commutation DAG can be accessed via the

`get_node()`

method:>>> second_node = dag.get_node(2) >>> second_node <pennylane.transforms.commutation_dag.CommutationDAGNode object at 0x136f8c4c0> >>> second_node.op CNOT(wires=[1, 2]) >>> second_node.successors [3, 4, 5, 6] >>> second_node.predecessors []

### Improvements

The text-based drawer accessed via

`qml.draw()`

has been optimized and improved. (#2128) (#2198)The new drawer has:

a

`decimals`

keyword for controlling parameter roundinga

`show_matrices`

keyword for controlling display of matricesa different algorithm for determining positions

deprecation of the

`charset`

keywordadditional minor cosmetic changes

@qml.qnode(qml.device('lightning.qubit', wires=2)) 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))

>>> print(qml.draw(circuit, decimals=2)(a=2.3, w=[1.2, 3.2, 0.7])) 0: ──H─╭C─────────────────────────────╭C─────────┤ ╭<[email protected]> 1: ────╰RX(2.30)──Rot(1.20,3.20,0.70)─╰RX(-2.30)─┤ ╰<[email protected]>

The frequencies of gate parameters are now accessible as an operation property and can be used for circuit analysis, optimization via the

`RotosolveOptimizer`

and differentiation with the parameter-shift rule (including the general shift rule). (#2180) (#2182) (#2227)>>> op = qml.CRot(0.4, 0.1, 0.3, wires=[0, 1]) >>> op.parameter_frequencies [(0.5, 1.0), (0.5, 1.0), (0.5, 1.0)]

When using

`qml.gradients.param_shift`

, either a custom`grad_recipe`

or the parameter frequencies are used to obtain the shift rule for the operation, in that order of preference.See Vidal and Theis (2018) and Wierichs et al. (2021) for theoretical background information on the general parameter-shift rule.

No two-term parameter-shift rule is assumed anymore by default. (#2227)

Previously, operations marked for analytic differentiation that did not provide a

`generator`

,`parameter_frequencies`

or a custom`grad_recipe`

were assumed to satisfy the two-term shift rule. This now has to be made explicit for custom operations by adding any of the above attributes.Most compilation transforms, and relevant subroutines, have been updated to support just-in-time compilation with

`jax.jit`

. (#1894)The

`qml.draw_mpl`

transform supports a`expansion_strategy`

keyword argument. (#2271)The

`qml.gradients`

module has been streamlined and special-purpose functions moved closer to their use cases, while preserving existing behaviour. (#2200)Added a new

`partition_pauli_group`

function to the`grouping`

module for efficiently measuring the`N`

-qubit Pauli group with`3 ** N`

qubit-wise commuting terms. (#2185)The Operator class has undergone a major refactor with the following changes:

**Matrices**: the static method`Operator.compute_matrices()`

defines the matrix representation of the operator, and the function`qml.matrix(op)`

computes this for a given instance. (#1996)**Eigvals**: the static method`Operator.compute_eigvals()`

defines the matrix representation of the operator, and the function`qml.eigvals(op)`

computes this for a given instance. (#2048)**Decompositions**: the static method`Operator.compute_decomposition()`

defines the matrix representation of the operator, and the method`op.decomposition()`

computes this for a given instance. (#2024) (#2053)**Sparse matrices**: the static method`Operator.compute_sparse_matrix()`

defines the sparse matrix representation of the operator, and the method`op.sparse_matrix()`

computes this for a given instance. (#2050)**Linear combinations of operators**: The static method`compute_terms()`

, used for representing the linear combination of coefficients and operators representing the operator, has been added. The method`op.terms()`

computes this for a given instance. Currently, only the`Hamiltonian`

class overwrites`compute_terms()`

to store coefficients and operators. The`Hamiltonian.terms`

property hence becomes a proper method called by`Hamiltonian.terms()`

. (#2036)**Diagonalization**: The`diagonalizing_gates()`

representation has been moved to the highest-level`Operator`

class and is therefore available to all subclasses. A condition`qml.operation.defines_diagonalizing_gates`

has been added, which can be used in tape contexts without queueing. In addition, a static`compute_diagonalizing_gates`

method has been added, which is called by default in`diagonalizing_gates()`

. (#1985) (#1993)Error handling has been improved for Operator representations. Custom errors subclassing

`OperatorPropertyUndefined`

are raised if a representation has not been defined. This replaces the`NotImplementedError`

and allows finer control for developers. (#2064) (#2287)A

`Operator.hyperparameters`

attribute, used for storing operation parameters that are*never*trainable, has been added to the operator class. (#2017)The

`string_for_inverse`

attribute is removed. (#2021)The

`expand()`

method was moved from the`Operation`

class to the main`Operator`

class. (#2053) (#2239)

### Deprecations

There are several important changes when creating custom operations: (#2214) (#2227) (#2030) (#2061)

The

`Operator.matrix`

method has been deprecated and`Operator.compute_matrix`

should be defined instead. Operator matrices should be accessed using`qml.matrix(op)`

. If you were previously defining the class method`Operator._matrix()`

, this is a a**breaking change**— please update your operation to instead overwrite`Operator.compute_matrix`

.The

`Operator.decomposition`

method has been deprecated and`Operator.compute_decomposition`

should be defined instead. Operator decompositions should be accessed using`Operator.decomposition()`

.The

`Operator.eigvals`

method has been deprecated and`Operator.compute_eigvals`

should be defined instead. Operator eigenvalues should be accessed using`qml.eigvals(op)`

.The

`Operator.generator`

property is now a method, and should return an*operator instance*representing the generator. Note that unlike the other representations above, this is a**breaking change**. Operator generators should be accessed using`qml.generator(op)`

.The

`Operation.get_parameter_shift`

method has been deprecated and will be removed in a future release.Instead, the functionalities for general parameter-shift rules in the

`qml.gradients`

module should be used, together with the operation attributes`parameter_frequencies`

or`grad_recipe`

.

Executing tapes using

`tape.execute(dev)`

is deprecated. Please use the`qml.execute([tape], dev)`

function instead. (#2306)The subclasses of the quantum tape, including

`JacobianTape`

,`QubitParamShiftTape`

,`CVParamShiftTape`

, and`ReversibleTape`

are deprecated. Instead of calling`JacobianTape.jacobian()`

and`JacobianTape.hessian()`

, please use a standard`QuantumTape`

, and apply gradient transforms using the`qml.gradients`

module. (#2306)`qml.transforms.get_unitary_matrix()`

has been deprecated and will be removed in a future release. For extracting matrices of operations and quantum functions, please use`qml.matrix()`

. (#2248)The

`qml.finite_diff()`

function has been deprecated and will be removed in an upcoming release. Instead,`qml.gradients.finite_diff()`

can be used to compute purely quantum gradients (that is, gradients of tapes or QNode). (#2212)The

`MultiControlledX`

operation now accepts a single`wires`

keyword argument for both`control_wires`

and`wires`

. The single`wires`

keyword should be all the control wires followed by a single target wire. (#2121) (#2278)

### Breaking changes

The representation of an operator as a matrix has been overhauled. (#1996)

The “canonical matrix”, which is independent of wires, is now defined in the static method

`compute_matrix()`

instead of`_matrix`

. By default, this method is assumed to take all parameters and non-trainable hyperparameters that define the operation.>>> qml.RX.compute_matrix(0.5) [[0.96891242+0.j 0. -0.24740396j] [0. -0.24740396j 0.96891242+0.j ]]

If no canonical matrix is specified for a gate,

`compute_matrix()`

raises a`MatrixUndefinedError`

.The generator property has been updated to an instance method,

`Operator.generator()`

. It now returns an instantiated operation, representing the generator of the instantiated operator. (#2030) (#2061)Various operators have been updated to specify the generator as either an

`Observable`

,`Tensor`

,`Hamiltonian`

,`SparseHamiltonian`

, or`Hermitian`

operator.In addition,

`qml.generator(operation)`

has been added to aid in retrieving generator representations of operators.The argument

`wires`

in`heisenberg_obs`

,`heisenberg_expand`

and`heisenberg_tr`

was renamed to`wire_order`

to be consistent with other matrix representations. (#2051)The property

`kraus_matrices`

has been changed to a method, and`_kraus_matrices`

renamed to`compute_kraus_matrices`

, which is now a static method. (#2055)The

`pennylane.measure`

module has been renamed to`pennylane.measurements`

. (#2236)

### Bug fixes

The

`basis`

property of`qml.SWAP`

was set to`"X"`

, which is incorrect; it is now set to`None`

. (#2287)The

`qml.RandomLayers`

template now decomposes when the weights are a list of lists. (#2266)The

`qml.QubitUnitary`

operation now supports just-in-time compilation using JAX. (#2249)Fixes a bug in the JAX interface where

`DeviceArray`

objects were not being converted to NumPy arrays before executing an external device. (#2255)The

`qml.ctrl`

transform now works correctly with gradient transforms such as the parameter-shift rule. (#2238)Fixes a bug in which passing required arguments into operations as keyword arguments would throw an error because the documented call signature didn’t match the function definition. (#1976)

The operation

`OrbitalRotation`

previously was wrongfully registered to satisfy the four-term parameter shift rule. The correct eight-term rule will now be used when using the parameter-shift rule. (#2180)Fixes a bug where

`qml.gradients.param_shift_hessian`

would produce an error whenever all elements of the Hessian are known in advance to be 0. (#2299)

### Documentation

The developer guide on adding templates and the architecture overview were rewritten to reflect the past and planned changes of the operator refactor. (#2066)

Links to the Strawberry Fields documentation for information on the CV model. (#2259)

Fixes the documentation example for

`qml.QFT`

. (#2232)Fixes the documentation example for using

`qml.sample`

with`jax.jit`

. (#2196)The

`qml.numpy`

subpackage is now included in the PennyLane API documentation. (#2179)Improves the documentation of

`RotosolveOptimizer`

regarding the usage of the passed`substep_optimizer`

and its keyword arguments. (#2160)Ensures that signatures of

`@qml.qfunc_transform`

decorated functions display correctly in the docs. (#2286)Docstring examples now display using the updated text-based circuit drawer. (#2252)

Add docstring to

`OrbitalRotation.grad_recipe`

. (#2193)

### Contributors

This release contains contributions from (in alphabetical order):

Catalina Albornoz, Jack Y. Araz, Juan Miguel Arrazola, Ali Asadi, Utkarsh Azad, Sam Banning, Thomas Bromley, Olivia Di Matteo, Christian Gogolin, Diego Guala, Anthony Hayes, David Ittah, Josh Izaac, Soran Jahangiri, Nathan Killoran, Christina Lee, Angus Lowe, Maria Fernanda Morris, Romain Moyard, Zeyue Niu, Lee James O’Riordan, Chae-Yeun Park, Maria Schuld, Jay Soni, Antal Száva, David Wierichs.

- orphan

## Release 0.21.0¶

### New features since last release

#### Reduce qubit requirements of simulating Hamiltonians ⚛️

Functions for tapering qubits based on molecular symmetries have been added, following results from Setia et al. (#1966) (#1974) (#2041) (#2042)

With this functionality, a molecular Hamiltonian and the corresponding Hartree-Fock (HF) state can be transformed to a new Hamiltonian and HF state that acts on a reduced number of qubits, respectively.

# molecular geometry symbols = ["He", "H"] geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.4588684632]]) mol = qml.hf.Molecule(symbols, geometry, charge=1) # generate the qubit Hamiltonian H = qml.hf.generate_hamiltonian(mol)(geometry) # determine Hamiltonian symmetries generators, paulix_ops = qml.hf.generate_symmetries(H, len(H.wires)) opt_sector = qml.hf.optimal_sector(H, generators, mol.n_electrons) # taper the Hamiltonian H_tapered = qml.hf.transform_hamiltonian(H, generators, paulix_ops, opt_sector)

We can compare the number of qubits required by the original Hamiltonian and the tapered Hamiltonian:

>>> len(H.wires) 4 >>> len(H_tapered.wires) 2

For quantum chemistry algorithms, the Hartree-Fock state can also be tapered:

n_elec = mol.n_electrons n_qubits = mol.n_orbitals * 2 hf_tapered = qml.hf.transform_hf( generators, paulix_ops, opt_sector, n_elec, n_qubits )

>>> hf_tapered tensor([1, 1], requires_grad=True)

#### New tensor network templates 🪢

Quantum circuits with the shape of a matrix product state tensor network can now be easily implemented using the new

`qml.MPS`

template, based on the work arXiv:1803.11537. (#1871)def block(weights, wires): qml.CNOT(wires=[wires[0], wires[1]]) qml.RY(weights[0], wires=wires[0]) qml.RY(weights[1], wires=wires[1]) n_wires = 4 n_block_wires = 2 n_params_block = 2 template_weights = np.array([[0.1, -0.3], [0.4, 0.2], [-0.15, 0.5]], requires_grad=True) dev = qml.device("default.qubit", wires=range(n_wires)) @qml.qnode(dev) def circuit(weights): qml.MPS(range(n_wires), n_block_wires, block, n_params_block, weights) return qml.expval(qml.PauliZ(wires=n_wires - 1))

The resulting circuit is:

>>> print(qml.draw(circuit, expansion_strategy="device")(template_weights)) 0: ──╭C──RY(0.1)───────────────────────────────┤ 1: ──╰X──RY(-0.3)──╭C──RY(0.4)─────────────────┤ 2: ────────────────╰X──RY(0.2)──╭C──RY(-0.15)──┤ 3: ─────────────────────────────╰X──RY(0.5)────┤ ⟨Z⟩

Added a template for tree tensor networks,

`qml.TTN`

. (#2043)def block(weights, wires): qml.CNOT(wires=[wires[0], wires[1]]) qml.RY(weights[0], wires=wires[0]) qml.RY(weights[1], wires=wires[1]) n_wires = 4 n_block_wires = 2 n_params_block = 2 n_blocks = qml.MPS.get_n_blocks(range(n_wires), n_block_wires) template_weights = [[0.1, -0.3]] * n_blocks dev = qml.device("default.qubit", wires=range(n_wires)) @qml.qnode(dev) def circuit(template_weights): qml.TTN(range(n_wires), n_block_wires, block, n_params_block, template_weights) return qml.expval(qml.PauliZ(wires=n_wires - 1))

The resulting circuit is:

>>> print(qml.draw(circuit, expansion_strategy="device")(template_weights)) 0: ──╭C──RY(0.1)─────────────────┤ 1: ──╰X──RY(-0.3)──╭C──RY(0.1)───┤ 2: ──╭C──RY(0.1)───│─────────────┤ 3: ──╰X──RY(-0.3)──╰X──RY(-0.3)──┤ ⟨Z⟩

#### Generalized RotosolveOptmizer 📉

The

`RotosolveOptimizer`

has been generalized to arbitrary frequency spectra in the cost function. Also note the changes in behaviour listed under*Breaking changes*. (#2081)Previously, the RotosolveOptimizer only supported variational circuits using special gates such as single-qubit Pauli rotations. Now, circuits with arbitrary gates are supported natively without decomposition, as long as the frequencies of the gate parameters are known. This new generalization extends the Rotosolve optimization method to a larger class of circuits, and can reduce the cost of the optimization compared to decomposing all gates to single-qubit rotations.

Consider the QNode

dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def qnode(x, Y): qml.RX(2.5 * x, wires=0) qml.CNOT(wires=[0, 1]) qml.RZ(0.3 * Y[0], wires=0) qml.CRY(1.1 * Y[1], wires=[1, 0]) return qml.expval(qml.PauliX(0) @ qml.PauliZ(1)) x = np.array(0.8, requires_grad=True) Y = np.array([-0.2, 1.5], requires_grad=True)

Its frequency spectra can be easily obtained via

`qml.fourier.qnode_spectrum`

:>>> spectra = qml.fourier.qnode_spectrum(qnode)(x, Y) >>> spectra {'x': {(): [-2.5, 0.0, 2.5]}, 'Y': {(0,): [-0.3, 0.0, 0.3], (1,): [-1.1, -0.55, 0.0, 0.55, 1.1]}}

We may then initialize the

`RotosolveOptimizer`

and minimize the QNode cost function by providing this information about the frequency spectra. We also compare the cost at each step to the initial cost.>>> cost_init = qnode(x, Y) >>> opt = qml.RotosolveOptimizer() >>> for _ in range(2): ... x, Y = opt.step(qnode, x, Y, spectra=spectra) ... print(f"New cost: {np.round(qnode(x, Y), 3)}; Initial cost: {np.round(cost_init, 3)}") New cost: 0.0; Initial cost: 0.706 New cost: -1.0; Initial cost: 0.706

The optimization with

`RotosolveOptimizer`

is performed in substeps. The minimal cost of these substeps can be retrieved by setting`full_output=True`

.>>> x = np.array(0.8, requires_grad=True) >>> Y = np.array([-0.2, 1.5], requires_grad=True) >>> opt = qml.RotosolveOptimizer() >>> for _ in range(2): ... (x, Y), history = opt.step(qnode, x, Y, spectra=spectra, full_output=True) ... print(f"New cost: {np.round(qnode(x, Y), 3)} reached via substeps {np.round(history, 3)}") New cost: 0.0 reached via substeps [-0. 0. 0.] New cost: -1.0 reached via substeps [-1. -1. -1.]

However, note that these intermediate minimal values are evaluations of the

*reconstructions*that Rotosolve creates and uses internally for the optimization, and not of the original objective function. For noisy cost functions, these intermediate evaluations may differ significantly from evaluations of the original cost function.

#### Improved JAX support 💻

The JAX interface now supports evaluating vector-valued QNodes. (#2110)

Vector-valued QNodes include those with:

`qml.probs`

;`qml.state`

;`qml.sample`

ormultiple

`qml.expval`

/`qml.var`

measurements.

Consider a QNode that returns basis-state probabilities:

dev = qml.device('default.qubit', wires=2) x = jnp.array(0.543) y = jnp.array(-0.654) @qml.qnode(dev, diff_method="parameter-shift", interface="jax") def circuit(x, y): qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) return qml.probs(wires=[1])

The QNode can be evaluated and its jacobian can be computed:

>>> circuit(x, y) DeviceArray([0.8397495 , 0.16025047], dtype=float32) >>> jax.jacobian(circuit, argnums=[0, 1])(x, y) (DeviceArray([-0.2050439, 0.2050439], dtype=float32, weak_type=True), DeviceArray([ 0.26043, -0.26043], dtype=float32, weak_type=True))

Note that

`jax.jit`

is not yet supported for vector-valued QNodes.

#### Speedier quantum natural gradient ⚡

A new function for computing the metric tensor on simulators,

`qml.adjoint_metric_tensor`

, has been added, that uses classically efficient methods to massively improve performance. (#1992)This method, detailed in Jones (2020), computes the metric tensor using four copies of the state vector and a number of operations that scales quadratically in the number of trainable parameters.

Note that as it makes use of state cloning, it is inherently classical and can only be used with statevector simulators and

`shots=None`

.It is particularly useful for larger circuits for which backpropagation requires inconvenient or even unfeasible amounts of storage, but is slower. Furthermore, the adjoint method is only available for analytic computation, not for measurements simulation with

`shots!=None`

.dev = qml.device("default.qubit", wires=3) @qml.qnode(dev) def circuit(x, y): qml.Rot(*x[0], wires=0) qml.Rot(*x[1], wires=1) qml.Rot(*x[2], wires=2) qml.CNOT(wires=[0, 1]) qml.CNOT(wires=[1, 2]) qml.CNOT(wires=[2, 0]) qml.RY(y[0], wires=0) qml.RY(y[1], wires=1) qml.RY(y[0], wires=2) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)), qml.expval(qml.PauliY(1)) x = np.array([[0.2, 0.4, -0.1], [-2.1, 0.5, -0.2], [0.1, 0.7, -0.6]], requires_grad=False) y = np.array([1.3, 0.2], requires_grad=True)

>>> qml.adjoint_metric_tensor(circuit)(x, y) tensor([[ 0.25495723, -0.07086695], [-0.07086695, 0.24945606]], requires_grad=True)

Computational cost

The adjoint method uses \(2P^2+4P+1\) gates and state cloning operations if the circuit is composed only of trainable gates, where \(P\) is the number of trainable operations. If non-trainable gates are included, each of them is applied about \(n^2-n\) times, where \(n\) is the number of trainable operations that follow after the respective non-trainable operation in the circuit. This means that non-trainable gates later in the circuit are executed less often, making the adjoint method a bit cheaper if such gates appear later. The adjoint method requires memory for 4 independent state vectors, which corresponds roughly to storing a state vector of a system with 2 additional qubits.

#### Compute the Hessian on hardware ⬆️

A new gradient transform

`qml.gradients.param_shift_hessian`

has been added to directly compute the Hessian (2nd order partial derivative matrix) of QNodes on hardware. (#1884)The function generates parameter-shifted tapes which allow the Hessian to be computed analytically on hardware and software devices. Compared to using an auto-differentiation framework to compute the Hessian via parameter shifts, this function will use fewer device invocations and can be used to inspect the parameter-shifted “Hessian tapes” directly. The function remains fully differentiable on all supported PennyLane interfaces.

Additionally, the parameter-shift Hessian comes with a new batch transform decorator

`@qml.gradients.hessian_transform`

, which can be used to create custom Hessian functions.The following code demonstrates how to use the parameter-shift Hessian:

dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(x): qml.RX(x[0], wires=0) qml.RY(x[1], wires=0) return qml.expval(qml.PauliZ(0)) x = np.array([0.1, 0.2], requires_grad=True) hessian = qml.gradients.param_shift_hessian(circuit)(x)

>>> hessian tensor([[-0.97517033, 0.01983384], [ 0.01983384, -0.97517033]], requires_grad=True)

### Improvements

The

`qml.transforms.insert`

transform now supports adding operation after or before certain specific gates. (#1980)Added a modified version of the

`simplify`

function to the`hf`

module. (#2103)This function combines redundant terms in a Hamiltonian and eliminates terms with a coefficient smaller than a cutoff value. The new function makes construction of molecular Hamiltonians more efficient. For LiH, as an example, the time to construct the Hamiltonian is reduced roughly by a factor of 20.

The QAOA module now accepts both NetworkX and RetworkX graphs as function inputs. (#1791)

The

`CircuitGraph`

, used to represent circuits via directed acyclic graphs, now uses RetworkX for its internal representation. This results in significant speedup for algorithms that rely on a directed acyclic graph representation. (#1791)For subclasses of

`Operator`

where the number of parameters is known before instantiation, the`num_params`

is reverted back to being a static property. This allows to programmatically know the number of parameters before an operator is instantiated without changing the user interface. A test was added to ensure that different ways of defining`num_params`

work as expected. (#2101) (#2135)A

`WireCut`

operator has been added for manual wire cut placement when constructing a QNode. (#2093)The new function

`qml.drawer.tape_text`

produces a string drawing of a tape. This function differs in implementation and minor stylistic details from the old string circuit drawing infrastructure. (#1885)The

`RotosolveOptimizer`

now raises an error if no trainable arguments are detected, instead of silently skipping update steps for all arguments. (#2109)The function

`qml.math.safe_squeeze`

is introduced and`gradient_transform`

allows for QNode argument axes of size`1`

. (#2080)`qml.math.safe_squeeze`

wraps`qml.math.squeeze`

, with slight modifications:When provided the

`axis`

keyword argument, axes that do not have size`1`

will be ignored, instead of raising an error.The keyword argument

`exclude_axis`

allows to explicitly exclude axes from the squeezing.

The

`adjoint`

transform now raises and error whenever the object it is applied to is not callable. (#2060)An example is a list of operations to which one might apply

`qml.adjoint`

:dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit_wrong(params): # Note the difference: v v qml.adjoint(qml.templates.AngleEmbedding(params, wires=dev.wires)) return qml.state() @qml.qnode(dev) def circuit_correct(params): # Note the difference: v v qml.adjoint(qml.templates.AngleEmbedding)(params, wires=dev.wires) return qml.state() params = list(range(1, 3))

Evaluating

`circuit_wrong(params)`

now raises a`ValueError`

and if we apply`qml.adjoint`

correctly, we get>>> circuit_correct(params) [ 0.47415988+0.j 0. 0.73846026j 0. 0.25903472j -0.40342268+0.j ]

A precision argument has been added to the tape’s

`to_openqasm`

function to control the precision of parameters. (#2071)Interferometer now has a

`shape`

method. (#1946)The Barrier and Identity operations now support the

`adjoint`

method. (#2062) (#2063)`qml.BasisStatePreparation`

now supports the`batch_params`

decorator. (#2091)Added a new

`multi_dispatch`

decorator that helps ease the definition of new functions inside PennyLane. The decorator is used throughout the math module, demonstrating use cases. (#2082) (#2096)We can decorate a function, indicating the arguments that are tensors handled by the interface:

>>> @qml.math.multi_dispatch(argnum=[0, 1]) ... def some_function(tensor1, tensor2, option, like): ... # the interface string is stored in ``like``. ... ...

Previously, this was done using the private utility function

`_multi_dispatch`

.>>> def some_function(tensor1, tensor2, option): ... interface = qml.math._multi_dispatch([tensor1, tensor2]) ... ...

The

`IsingZZ`

gate was added to the`diagonal_in_z_basis`

attribute. For this an explicit`_eigvals`

method was added. (#2113)The

`IsingXX`

,`IsingYY`

and`IsingZZ`

gates were added to the`composable_rotations`

attribute. (#2113)

### Breaking changes

QNode arguments will no longer be considered trainable by default when using the Autograd interface. In order to obtain derivatives with respect to a parameter, it should be instantiated via PennyLane’s NumPy wrapper using the

`requires_grad=True`

attribute. The previous behaviour was deprecated in version v0.19.0 of PennyLane. (#2116) (#2125) (#2139) (#2148) (#2156)from pennylane import numpy as np @qml.qnode(qml.device("default.qubit", wires=2)) def circuit(x): ... x = np.array([0.1, 0.2], requires_grad=True) qml.grad(circuit)(x)

For the

`qml.grad`

and`qml.jacobian`

functions, trainability can alternatively be indicated via the`argnum`

keyword:import numpy as np @qml.qnode(qml.device("default.qubit", wires=2)) def circuit(hyperparam, param): ... x = np.array([0.1, 0.2]) qml.grad(circuit, argnum=1)(0.5, x)

`qml.jacobian`

now follows a different convention regarding its output shape. (#2059)Previously,

`qml.jacobian`

would attempt to stack the Jacobian for multiple QNode arguments, which succeeded whenever the arguments have the same shape. In this case, the stacked Jacobian would also be transposed, leading to the output shape`(*reverse_QNode_args_shape, *reverse_output_shape, num_QNode_args)`

If no stacking and transposing occurs, the output shape instead is a

`tuple`

where each entry corresponds to one QNode argument and has the shape`(*output_shape, *QNode_arg_shape)`

.This breaking change alters the behaviour in the first case and removes the attempt to stack and transpose, so that the output always has the shape of the second type.

Note that the behaviour is unchanged — that is, the Jacobian tuple is unpacked into a single Jacobian — if

`argnum=None`

and there is only one QNode argument with respect to which the differentiation takes place, or if an integer is provided as`argnum`

.A workaround that allowed

`qml.jacobian`

to differentiate multiple QNode arguments will no longer support higher-order derivatives. In such cases, combining multiple arguments into a single array is recommended.`qml.metric_tensor`

,`qml.adjoint_metric_tensor`

and`qml.transforms.classical_jacobian`

now follow a different convention regarding their output shape when being used with the Autograd interface (#2059)See the previous entry for details. This breaking change immediately follows from the change in

`qml.jacobian`

whenever`hybrid=True`

is used in the above methods.The behaviour of

`RotosolveOptimizer`

has been changed regarding its keyword arguments. (#2081)The keyword arguments

`optimizer`

and`optimizer_kwargs`

for the`RotosolveOptimizer`

have been renamed to`substep_optimizer`

and`substep_kwargs`

, respectively. Furthermore they have been moved from`step`

and`step_and_cost`

to the initialization`__init__`

.The keyword argument

`num_freqs`

has been renamed to`nums_frequency`

and is expected to take a different shape now: Previously, it was expected to be an`int`

or a list of entries, with each entry in turn being either an`int`

or a`list`

of`int`

entries. Now the expected structure is a nested dictionary, matching the formatting expected by qml.fourier.reconstruct This also matches the expected formatting of the new keyword arguments`spectra`

and`shifts`

.For more details, see the RotosolveOptimizer documentation.

### Deprecations

Deprecates the caching ability provided by

`QubitDevice`

. (#2154)Going forward, the preferred way is to use the caching abilities of the QNode:

dev = qml.device("default.qubit", wires=2) cache = {} @qml.qnode(dev, diff_method='parameter-shift', cache=cache) def circuit(): qml.RY(0.345, wires=0) return qml.expval(qml.PauliZ(0))

>>> for _ in range(10): ... circuit() >>> dev.num_executions 1

### Bug fixes

Fixes a bug where an incorrect number of executions are recorded by a QNode using a custom cache with

`diff_method="backprop"`

. (#2171)Fixes a bug where the

`default.qubit.jax`

device can’t be used with`diff_method=None`

and jitting. (#2136)Fixes a bug where the Torch interface was not properly unwrapping Torch tensors to NumPy arrays before executing gradient tapes on devices. (#2117)

Fixes a bug for the TensorFlow interface where the dtype of input tensors was not cast. (#2120)

Fixes a bug where batch transformed QNodes would fail to apply batch transforms provided by the underlying device. (#2111)

An error is now raised during QNode creation if backpropagation is requested on a device with finite shots specified. (#2114)

Pytest now ignores any

`DeprecationWarning`

raised within autograd’s`numpy_wrapper`

module. Other assorted minor test warnings are fixed. (#2007)Fixes a bug where the QNode was not correctly diagonalizing qubit-wise commuting observables. (#2097)

Fixes a bug in

`gradient_transform`

where the hybrid differentiation of circuits with a single parametrized gate failed and QNode argument axes of size`1`

where removed from the output gradient. (#2080)The available

`diff_method`

options for QNodes has been corrected in both the error messages and the documentation. (#2078)Fixes a bug in

`DefaultQubit`

where the second derivative of QNodes at positions corresponding to vanishing state vector amplitudes is wrong. (#2057)Fixes a bug where PennyLane didn’t require v0.20.0 of PennyLane-Lightning, but raised an error with versions of Lightning earlier than v0.20.0 due to the new batch execution pipeline. (#2033)

Fixes a bug in

`classical_jacobian`

when used with Torch, where the Jacobian of the preprocessing was also computed for non-trainable parameters. (#2020)Fixes a bug in queueing of the

`two_qubit_decomposition`

method that originally led to circuits with >3 two-qubit unitaries failing when passed through the`unitary_to_rot`

optimization transform. (#2015)Fixes a bug which allows using

`jax.jit`

to be compatible with circuits which return`qml.probs`

when the`default.qubit.jax`

is provided with a custom shot vector. (#2028)Updated the

`adjoint()`

method for non-parametric qubit operations to solve a bug where repeated`adjoint()`

calls don’t return the correct operator. (#2133)Fixed a bug in

`insert()`

which prevented operations that inherited from multiple classes to be inserted. (#2172)

### Documentation

Fixes an error in the signs of equations in the

`DoubleExcitation`

page. (#2072)Extends the interfaces description page to explicitly mention device compatibility. (#2031)

### Contributors

This release contains contributions from (in alphabetical order):

Juan Miguel Arrazola, Ali Asadi, Utkarsh Azad, Sam Banning, Thomas Bromley, Esther Cruz, Olivia Di Matteo, Christian Gogolin, Diego Guala, Anthony Hayes, David Ittah, Josh Izaac, Soran Jahangiri, Edward Jiang, Ankit Khandelwal, Nathan Killoran, Korbinian Kottmann, Christina Lee, Romain Moyard, Lee James O’Riordan, Maria Schuld, Jay Soni, Antal Száva, David Wierichs, Shaoming Zhang.

- orphan

## Release 0.20.0¶

### 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('