# Release notes¶

This page contains the release notes for PennyLane.

- orphan

## Release 0.36.0 (current release)¶

### New features since last release

#### Estimate errors in a quantum circuit 🧮

This version of PennyLane lays the foundation for estimating the total error in a quantum circuit from the combination of individual gate errors. (#5154) (#5464) (#5465) (#5278) (#5384)

Two new user-facing classes enable calculating and propagating gate errors in PennyLane:

`qml.resource.SpectralNormError`

: the spectral norm error is defined as the distance, in spectral norm, between the true unitary we intend to apply and the approximate unitary that is actually applied.`qml.resource.ErrorOperation`

: a base class that inherits from`qml.operation.Operation`

and represents quantum operations which carry some form of algorithmic error.

`SpectralNormError`

can be used for back-of-the-envelope type calculations like obtaining the spectral norm error between two unitaries via`get_error`

:import pennylane as qml from pennylane.resource import ErrorOperation, SpectralNormError intended_op = qml.RY(0.40, 0) actual_op = qml.RY(0.41, 0) # angle of rotation is slightly off

>>> SpectralNormError.get_error(intended_op, actual_op) 0.004999994791668309

`SpectralNormError`

is also a key tool to specify errors in larger quantum circuits:For operations representing a major building block of an algorithm, we can create a custom operation that inherits from

`ErrorOperation`

. This child class must override the`error`

method and should return a`SpectralNormError`

instance:class MyErrorOperation(ErrorOperation): def __init__(self, error_val, wires): self.error_val = error_val super().__init__(wires=wires) def error(self): return SpectralNormError(self.error_val)

In this toy example,

`MyErrorOperation`

introduces an arbitrary`SpectralNormError`

when called in a QNode. It does not require a decomposition or matrix representation when used with`null.qubit`

(suggested for use with resource and error estimation since circuit executions are not required to calculate resources or errors).dev = qml.device("null.qubit") @qml.qnode(dev) def circuit(): MyErrorOperation(0.1, wires=0) MyErrorOperation(0.2, wires=1) return qml.state()

The total spectral norm error of the circuit can be calculated using

`qml.specs`

:>>> qml.specs(circuit)()['errors'] {'SpectralNormError': SpectralNormError(0.30000000000000004)}

PennyLane already includes a number of built-in building blocks for algorithms like

`QuantumPhaseEstimation`

and`TrotterProduct`

.`TrotterProduct`

now propagates errors based on the number of steps performed in the Trotter product.`QuantumPhaseEstimation`

now propagates errors based on the error of its input unitary.dev = qml.device('null.qubit') hamiltonian = qml.dot([1.0, 0.5, -0.25], [qml.X(0), qml.Y(0), qml.Z(0)]) @qml.qnode(dev) def circuit(): qml.TrotterProduct(hamiltonian, time=0.1, order=2) qml.QuantumPhaseEstimation(MyErrorOperation(0.01, wires=0), estimation_wires=[1, 2, 3]) return qml.state()

Again, the total spectral norm error of the circuit can be calculated using

`qml.specs`

:>>> qml.specs(circuit)()["errors"] {'SpectralNormError': SpectralNormError(0.07616666666666666)}

Check out our error propagation demo to see how to use these new features in a real-world example!

#### Access an extended arsenal of quantum algorithms 🏹

The Fast Approximate BLock-Encodings (FABLE) algorithm for embedding a matrix into a quantum circuit as outlined in arXiv:2205.00081 is now accessible via the

`qml.FABLE`

template. (#5107)The usage of

`qml.FABLE`

is similar to`qml.BlockEncode`

but provides a more efficient circuit construction at the cost of a user-defined approximation level,`tol`

. The number of wires that`qml.FABLE`

operates on is`2*n + 1`

, where`n`

defines the dimension of the \(2^n \times 2^n\) matrix that we want to block-encode.import numpy as np A = np.array([[0.1, 0.2], [0.3, 0.4]]) dev = qml.device('default.qubit', wires=3) @qml.qnode(dev) def circuit(): qml.FABLE(A, tol = 0.001, wires=range(3)) return qml.state()

>>> mat = qml.matrix(circuit)() >>> 2 * mat[0:2, 0:2] array([[0.1+0.j, 0.2+0.j], [0.3+0.j, 0.4+0.j]])

A high-level interface for amplitude amplification and its variants is now available via the new

`qml.AmplitudeAmplification`

template. (#5160)Based on arXiv:quant-ph/0005055, given a state \(\vert \Psi \rangle = \alpha \vert \phi \rangle + \beta \vert \phi^{\perp} \rangle\),

`qml.AmplitudeAmplification`

amplifies the amplitude of \(\vert \phi \rangle\).Here’s an example with a target state \(\vert \phi \rangle = \vert 2 \rangle = \vert 010 \rangle\), an input state \(\vert \Psi \rangle = H^{\otimes 3} \vert 000 \rangle\), as well as an oracle that flips the sign of \(\vert \phi \rangle\) and does nothing to \(\vert \phi^{\perp} \rangle\), which can be achieved in this case through

`qml.FlipSign`

.@qml.prod def generator(wires): for wire in wires: qml.Hadamard(wires=wire) U = generator(wires=range(3)) O = qml.FlipSign(2, wires=range(3))

Here,

`U`

is a quantum operation that is created by decorating a quantum function with`@qml.prod`

. This could alternatively be done by creating a user-defined custom operation with a decomposition. Amplitude amplification can then be set up within a circuit:dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(): generator(wires=range(3)) # prepares |Psi> = U|0> qml.AmplitudeAmplification(U, O, iters=10) return qml.probs(wires=range(3))

>>> print(np.round(circuit(), 3)) [0.01 0.01 0.931 0.01 0.01 0.01 0.01 0.01 ]

As expected, we amplify the \(\vert 2 \rangle\) state.

Reflecting about a given quantum state is now available via

`qml.Reflection`

. This operation is very useful in the amplitude amplification algorithm and offers a generalization of`qml.FlipSign`

, which operates on basis states. (#5159)`qml.Reflection`

works by providing an operation, \(U\), that*prepares*the desired state, \(\vert \psi \rangle\), that we want to reflect about. In other words, \(U\) is such that \(U \vert 0 \rangle = \vert \psi \rangle\). In PennyLane, \(U\) must be an`Operator`

.For example, if we want to reflect about \(\vert \psi \rangle = \vert + \rangle\), then \(U = H\):

U = qml.Hadamard(wires=0) dev = qml.device('default.qubit') @qml.qnode(dev) def circuit(): qml.Reflection(U) return qml.state()

>>> circuit() tensor([0.-6.123234e-17j, 1.+6.123234e-17j], requires_grad=True)

Performing qubitization is now easily accessible with the new

`qml.Qubitization`

operator. (#5500)`qml.Qubitization`

encodes a Hamiltonian into a suitable unitary operator. When applied in conjunction with quantum phase estimation (QPE), it allows for computing the eigenvalue of an eigenvector of the given Hamiltonian.H = qml.dot([0.1, 0.3, -0.3], [qml.Z(0), qml.Z(1), qml.Z(0) @ qml.Z(2)]) @qml.qnode(qml.device("default.qubit")) def circuit(): # initialize the eigenvector qml.PauliX(2) # apply QPE measurements = qml.iterative_qpe( qml.Qubitization(H, control = [3,4]), ancilla = 5, iters = 3 ) return qml.probs(op = measurements)

#### Make use of more methods to map from molecules 🗺️

A new function called

`qml.bravyi_kitaev`

has been added to perform the Bravyi-Kitaev mapping of fermionic Hamiltonians to qubit Hamiltonians. (#5390)This function presents an alternative mapping to

`qml.jordan_wigner`

or`qml.parity_transform`

which can help us measure expectation values more efficiently on hardware. Simply provide a fermionic Hamiltonian (created from`from_string`

,`FermiA`

,`FermiC`

,`FermiSentence`

, or`FermiWord`

) and the number of qubits / spin orbitals in the system,`n`

:>>> fermi_ham = qml.fermi.from_string('0+ 1+ 1- 0-') >>> qubit_ham = qml.bravyi_kitaev(fermi_ham, n=6, tol=0.0) >>> print(qubit_ham) 0.25 * I(0) + -0.25 * Z(0) + -0.25 * (Z(0) @ Z(1)) + 0.25 * Z(1)

The

`qml.qchem.hf_state`

function has been upgraded to be compatible with`qml.parity_transform`

and the new Bravyi-Kitaev mapping (`qml.bravyi_kitaev`

). (#5472) (#5472)>>> state_bk = qml.qchem.hf_state(2, 6, basis="bravyi_kitaev") >>> print(state_bk) [1 0 0 0 0 0] >>> state_parity = qml.qchem.hf_state(2, 6, basis="parity") >>> print(state_parity) [1 0 0 0 0 0]

#### Calculate dynamical Lie algebras 👾

The dynamical Lie algebra (DLA) of a set of operators captures the range of unitary evolutions that the operators can generate. In v0.36 of PennyLane, we have added support for calculating important DLA concepts including:

A new

`qml.lie_closure`

function to compute the Lie closure of a list of operators, providing one way to obtain the DLA. (#5161) (#5169) (#5627)For a list of operators

`ops = [op1, op2, op3, ..]`

, one computes all nested commutators between`ops`

until no new operators are generated from commutation. All these operators together form the DLA, see e.g. section IIB of arXiv:2308.01432.Take for example the following operators:

from pennylane import X, Y, Z ops = [X(0) @ X(1), Z(0), Z(1)]

A first round of commutators between all elements yields the new operators

`Y(0) @ X(1)`

and`X(0) @ Y(1)`

(omitting scalar prefactors).>>> qml.commutator(X(0) @ X(1), Z(0)) -2j * (Y(0) @ X(1)) >>> qml.commutator(X(0) @ X(1), Z(1)) -2j * (X(0) @ Y(1))

A next round of commutators between all elements further yields the new operator

`Y(0) @ Y(1)`

.>>> qml.commutator(X(0) @ Y(1), Z(0)) -2j * (Y(0) @ Y(1))

After that, no new operators emerge from taking nested commutators and we have the resulting DLA. This can now be done in short via

`qml.lie_closure`

as follows.>>> ops = [X(0) @ X(1), Z(0), Z(1)] >>> dla = qml.lie_closure(ops) >>> dla [X(0) @ X(1), Z(0), Z(1), -1.0 * (Y(0) @ X(1)), -1.0 * (X(0) @ Y(1)), -1.0 * (Y(0) @ Y(1))]

Computing the structure constants (the adjoint representation) of a dynamical Lie algebra. (5406)

For example, we can compute the adjoint representation of the transverse field Ising model DLA.

>>> dla = [X(0) @ X(1), Z(0), Z(1), Y(0) @ X(1), X(0) @ Y(1), Y(0) @ Y(1)] >>> structure_const = qml.structure_constants(dla) >>> structure_const.shape (6, 6, 6)

Visit the documentation of qml.structure_constants to understand how structure constants are a useful way to represent a DLA.

Computing the center of a dynamical Lie algebra. (#5477)

Given a DLA

`g`

, we can now compute its centre. The`center`

is the collection of operators that commute with*all*other operators in the DLA.>>> g = [X(0), X(1) @ X(0), Y(1), Z(1) @ X(0)] >>> qml.center(g) [X(0)]

To help explain these concepts, check out the dynamical Lie algebras demo.

### Improvements 🛠

#### Simulate mixed-state qutrit systems

Mixed qutrit states can now be simulated with the

`default.qutrit.mixed`

device. (#5495) (#5451) (#5186) (#5082) (#5213)Thanks to contributors from the University of British Columbia, a mixed-state qutrit device is now available for simulation, providing a noise-capable equivalent to

`default.qutrit`

.dev = qml.device("default.qutrit.mixed") def circuit(): qml.TRY(0.1, wires=0) @qml.qnode(dev) def shots_circuit(): circuit() return qml.sample(), qml.expval(qml.GellMann(wires=0, index=1)) @qml.qnode(dev) def density_matrix_circuit(): circuit() return qml.state()

>>> shots_circuit(shots=5) (array([0, 0, 0, 0, 0]), 0.19999999999999996) >>> density_matrix_circuit() tensor([[0.99750208+0.j, 0.04991671+0.j, 0. +0.j], [0.04991671+0.j, 0.00249792+0.j, 0. +0.j], [0. +0.j, 0. +0.j, 0. +0.j]], requires_grad=True)

However, there’s one crucial ingredient that we still need to add: support for qutrit noise operations. Keep your eyes peeled for this to arrive in the coming releases!

#### Work easily and efficiently with operators

This release completes the main phase of PennyLane’s switchover to an updated approach for handling arithmetic operations between operators. The new approach is now enabled by default and is intended to realize a few objectives:

To make it as easy to work with PennyLane operators as it would be with pen and paper.

To improve the efficiency of operator arithmetic.

In many cases, this update should not break code. If issues do arise, check out the updated operator troubleshooting page and don’t hesitate to reach out to us on the PennyLane discussion forum. As a last resort the old behaviour can be enabled by calling

`qml.operation.disable_new_opmath()`

, but this is not recommended because support will not continue in future PennyLane versions (v0.36 and higher). (#5269)A new class called

`qml.ops.LinearCombination`

has been introduced. In essence, this class is an updated equivalent of the now-deprecated`qml.ops.Hamiltonian`

but for usage with the new operator arithmetic. (#5216)`qml.ops.Sum`

now supports storing grouping information. Grouping type and method can be specified during construction using the`grouping_type`

and`method`

keyword arguments of`qml.dot`

,`qml.sum`

, or`qml.ops.Sum`

. The grouping indices are stored in`Sum.grouping_indices`

. (#5179)a = qml.X(0) b = qml.prod(qml.X(0), qml.X(1)) c = qml.Z(0) obs = [a, b, c] coeffs = [1.0, 2.0, 3.0] op = qml.dot(coeffs, obs, grouping_type="qwc")

>>> op.grouping_indices ((2,), (0, 1))

Additionally,

`grouping_type`

and`method`

can be set or changed after construction using`Sum.compute_grouping()`

:a = qml.X(0) b = qml.prod(qml.X(0), qml.X(1)) c = qml.Z(0) obs = [a, b, c] coeffs = [1.0, 2.0, 3.0] op = qml.dot(coeffs, obs)

>>> op.grouping_indices is None True >>> op.compute_grouping(grouping_type="qwc") >>> op.grouping_indices ((2,), (0, 1))

Note that the grouping indices refer to the lists returned by

`Sum.terms()`

, not`Sum.operands`

.A new function called

`qml.operation.convert_to_legacy_H`

that converts`Sum`

,`SProd`

, and`Prod`

to`Hamiltonian`

instances has been added. This function is intended for developers and will be removed in a future release without a deprecation cycle. (#5309)The

`qml.is_commuting`

function now accepts`Sum`

,`SProd`

, and`Prod`

instances. (#5351)Operators can now be left-multiplied by NumPy arrays (i.e.,

`arr * op`

). (#5361)`op.generator()`

, where`op`

is an`Operator`

instance, now returns operators consistent with the global setting for`qml.operator.active_new_opmath()`

wherever possible.`Sum`

,`SProd`

and`Prod`

instances will be returned even after disabling the new operator arithmetic in cases where they offer additional functionality not available using legacy operators. (#5253) (#5410) (#5411) (#5421)`Prod`

instances temporarily have a new`obs`

property, which helps smoothen the transition of the new operator arithmetic system. In particular, this is aimed at preventing breaking code that uses`Tensor.obs`

. The property has been immediately deprecated. Moving forward, we recommend using`op.operands`

. (#5539)`qml.ApproxTimeEvolution`

is now compatible with any operator that has a defined`pauli_rep`

. (#5362)`Hamiltonian.pauli_rep`

is now defined if the Hamiltonian is a linear combination of Pauli operators. (#5377)`Prod`

instances created with qutrit operators now have a defined`eigvals()`

method. (#5400)`qml.transforms.hamiltonian_expand`

and`qml.transforms.sum_expand`

can now handle multi-term observables with a constant offset (i.e., terms like`qml.I()`

). (#5414) (#5543)`qml.qchem.taper_operation`

is now compatible with the new operator arithmetic. (#5326)The warning for an observable that might not be hermitian in QNode executions has been removed. This enables jit-compilation. (#5506)

`qml.transforms.split_non_commuting`

will now work with single-term operator arithmetic. (#5314)`LinearCombination`

and`Sum`

now accept`_grouping_indices`

on initialization. This addition is relevant to developers only. (#5524)Calculating the dense, differentiable matrix for

`PauliSentence`

and operators with Pauli sentences is now faster. (#5578)

#### Community contributions 🥳

`ExpectationMP`

,`VarianceMP`

,`CountsMP`

, and`SampleMP`

now have a`process_counts`

method (similar to`process_samples`

). This allows for calculating measurements given a`counts`

dictionary. (#5256) (#5395)Type-hinting has been added in the

`Operator`

class for better interpretability. (#5490)An alternate strategy for sampling with multiple different

`shots`

values has been implemented via the`shots.bins()`

method, which samples all shots at once and then processes each separately. (#5476)

#### Mid-circuit measurements and dynamic circuits

A new module called

`qml.capture`

that will contain PennyLane’s own capturing mechanism for hybrid quantum-classical programs has been added. (#5509)The

`dynamic_one_shot`

transform has been introduced, enabling dynamic circuit execution on circuits with finite`shots`

and devices that natively support mid-circuit measurements. (#5266)The

`QubitDevice`

class and children classes support the`dynamic_one_shot`

transform provided that they support mid-circuit measurement operations natively. (#5317)`default.qubit`

can now be provided a random seed for sampling mid-circuit measurements with finite shots. This (1) ensures that random behaviour is more consistent with`dynamic_one_shot`

and`defer_measurements`

and (2) makes our continuous-integration (CI) have less failures due to stochasticity. (#5337)

#### Performance and broadcasting

Gradient transforms may now be applied to batched/broadcasted QNodes as long as the broadcasting is in non-trainable parameters. (#5452)

The performance of computing the matrix of

`qml.QFT`

has been improved. (#5351)`qml.transforms.broadcast_expand`

now supports shot vectors when returning`qml.sample()`

. (#5473)`LightningVJPs`

is now compatible with Lightning devices using the new device API. (#5469)

#### Device capabilities

Obtaining classical shadows using the

`default.clifford`

device is now compatible with stim`v1.13.0`

. (#5409)`default.mixed`

has improved support for sampling-based measurements with non-NumPy interfaces. (#5514) (#5530)`default.mixed`

now supports arbitrary state-based measurements with`qml.Snapshot`

. (#5552)`null.qubit`

has been upgraded to the new device API and has support for all measurements and various modes of differentiation. (#5211)

#### Other improvements

Entanglement entropy can now be calculated with

`qml.math.vn_entanglement_entropy`

, which computes the von Neumann entanglement entropy from a density matrix. A corresponding QNode transform,`qml.qinfo.vn_entanglement_entropy`

, has also been added. (#5306)`qml.draw`

and`qml.draw_mpl`

will now attempt to sort the wires if no wire order is provided by the user or the device. (#5576)A clear error message is added in

`KerasLayer`

when using the newest version of TensorFlow with Keras 3 (which is not currently compatible with`KerasLayer`

), linking to instructions to enable Keras 2. (#5488)`qml.ops.Conditional`

now stores the`data`

,`num_params`

, and`ndim_param`

attributes of the operator it wraps. (#5473)The

`molecular_hamiltonian`

function calls`PySCF`

directly when`method='pyscf'`

is selected. (#5118)`cache_execute`

has been replaced with an alternate implementation based on`@transform`

. (#5318)QNodes now defer

`diff_method`

validation to the device under the new device API. (#5176)The device test suite has been extended to cover gradient methods, templates and arithmetic observables. (#5273) (#5518)

A typo and string formatting mistake have been fixed in the error message for

`ClassicalShadow._convert_to_pauli_words`

when the input is not a valid`pauli_rep`

. (#5572)Circuits running on

`lightning.qubit`

and that return`qml.state()`

now preserve the`dtype`

when specified. (#5547)

### Breaking changes 💔

`qml.matrix()`

called on the following will now raise an error if`wire_order`

is not specified:`single_tape_transform`

,`batch_transform`

,`qfunc_transform`

,`op_transform`

,`gradient_transform`

and`hessian_transform`

have been removed. Instead, switch to using the new`qml.transform`

function. Please refer to the transform docs to see how this can be done. (#5339)Attempting to multiply

`PauliWord`

and`PauliSentence`

with`*`

will raise an error. Instead, use`@`

to conform with the PennyLane convention. (#5341)`DefaultQubit`

now uses a pre-emptive key-splitting strategy to avoid reusing JAX PRNG keys throughout a single`execute`

call. (#5515)`qml.pauli.pauli_mult`

and`qml.pauli.pauli_mult_with_phase`

have been removed. Instead, use`qml.simplify(qml.prod(pauli_1, pauli_2))`

to get the reduced operator. (#5324)>>> op = qml.simplify(qml.prod(qml.PauliX(0), qml.PauliZ(0))) >>> op -1j*(PauliY(wires=[0])) >>> [phase], [base] = op.terms() >>> phase, base (-1j, PauliY(wires=[0]))

The

`dynamic_one_shot`

transform now uses sampling (`SampleMP`

) to get back the values of the mid-circuit measurements. (#5486)`Operator`

dunder methods now combine like-operator arithmetic classes via`lazy=False`

. This reduces the chances of getting a`RecursionError`

and makes nested operators easier to work with. (#5478)The private functions

`_pauli_mult`

,`_binary_matrix`

and`_get_pauli_map`

from the`pauli`

module have been removed. The same functionality can be achieved using newer features in the`pauli`

module. (#5323)`MeasurementProcess.name`

and`MeasurementProcess.data`

have been removed. Use`MeasurementProcess.obs.name`

and`MeasurementProcess.obs.data`

instead. (#5321)`Operator.validate_subspace(subspace)`

has been removed. Instead, use`qml.ops.qutrit.validate_subspace(subspace)`

. (#5311)The contents of

`qml.interfaces`

has been moved inside`qml.workflow`

. The old import path no longer exists. (#5329)Since

`default.mixed`

does not support snapshots with measurements, attempting to do so will result in a`DeviceError`

instead of getting the density matrix. (#5416)`LinearCombination._obs_data`

has been removed. You can still use`LinearCombination.compare`

to check mathematical equivalence between a`LinearCombination`

and another operator. (#5504)

### Deprecations 👋

Accessing

`qml.ops.Hamiltonian`

is deprecated because it points to the old version of the class that may not be compatible with the new approach to operator arithmetic. Instead, using`qml.Hamiltonian`

is recommended because it dispatches to the`LinearCombination`

class when the new approach to operator arithmetic is enabled. This will allow you to continue to use`qml.Hamiltonian`

with existing code without needing to make any changes. (#5393)`qml.load`

has been deprecated. Instead, please use the functions outlined in the Importing workflows quickstart guide. (#5312)Specifying

`control_values`

with a bit string in`qml.MultiControlledX`

has been deprecated. Instead, use a list of booleans or 1s and 0s. (#5352)`qml.from_qasm_file`

has been deprecated. Instead, please open the file and then load its content using`qml.from_qasm`

. (#5331)>>> with open("test.qasm", "r") as f: ... circuit = qml.from_qasm(f.read())

### Documentation 📝

A new page explaining the shapes and nesting of return types has been added. (#5418)

Redundant documentation for the

`evolve`

function has been removed. (#5347)The final example in the

`compile`

docstring has been updated to use transforms correctly. (#5348)A link to the demos for using

`qml.SpecialUnitary`

and`qml.QNGOptimizer`

has been added to their respective docstrings. (#5376)A code example in the

`qml.measure`

docstring has been added that showcases returning mid-circuit measurement statistics from QNodes. (#5441)The computational basis convention used for

`qml.measure`

— 0 and 1 rather than ±1 — has been clarified in its docstring. (#5474)A new

*Release news*section has been added to the table of contents, containing release notes, deprecations, and other pages focusing on recent changes. (#5548)A summary of all changes has been added in the “Updated Operators” page in the new “Release news” section in the docs. (#5483) (#5636)

### Bug fixes 🐛

Patches the QNode so that parameter-shift will be considered best with lightning if

`qml.metric_tensor`

is in the transform program. (#5624)Stopped printing the ID of

`qcut.MeasureNode`

and`qcut.PrepareNode`

in tape drawing. (#5613)Improves the error message for setting shots on the new device interface, or trying to access a property that no longer exists. (#5616)

Fixed a bug where

`qml.draw`

and`qml.draw_mpl`

incorrectly raised errors for circuits collecting statistics on mid-circuit measurements while using`qml.defer_measurements`

. (#5610)Using shot vectors with

`param_shift(... broadcast=True)`

caused a bug. This combination is no longer supported and will be added again in the next release. Fixed a bug with custom gradient recipes that only consist of unshifted terms. (#5612) (#5623)`qml.counts`

now returns the same keys with`dynamic_one_shot`

and`defer_measurements`

. (#5587)`null.qubit`

now automatically supports any operation without a decomposition. (#5582)Fixed a bug where the shape and type of derivatives obtained by applying a gradient transform to a QNode differed based on whether the QNode uses classical coprocessing. (#4945)

`ApproxTimeEvolution`

,`CommutingEvolution`

,`QDrift`

, and`TrotterProduct`

now de-queue their input observable. (#5524)(In)equality of

`qml.HilbertSchmidt`

instances is now reported correctly by`qml.equal`

. (#5538)`qml.ParticleConservingU1`

and`qml.ParticleConservingU2`

no longer raise an error when the initial state is not specified but default to the all-zeros state. (#5535)`qml.counts`

no longer returns negative samples when measuring 8 or more wires. (#5544) (#5556)The

`dynamic_one_shot`

transform now works with broadcasting. (#5473)Diagonalizing gates are now applied when measuring

`qml.probs`

on non-computational basis states on a Lightning device. (#5529)`two_qubit_decomposition`

no longer diverges at a special case of a unitary matrix. (#5448)The

`qml.QNSPSAOptimizer`

now correctly handles optimization for legacy devices that do not follow the new device API. (#5497)Operators applied to all wires are now drawn correctly in a circuit with mid-circuit measurements. (#5501)

Fixed a bug where certain unary mid-circuit measurement expressions would raise an uncaught error. (#5480)

Probabilities now sum to 1 when using the

`torch`

interface with`default_dtype`

set to`torch.float32`

. (#5462)Tensorflow can now handle devices with

`float32`

results but`float64`

input parameters. (#5446)Fixed a bug where the

`argnum`

keyword argument of`qml.gradients.stoch_pulse_grad`

references the wrong parameters in a tape, creating an inconsistency with other differentiation methods and preventing some use cases. (#5458)Bounded value failures due to numerical noise with calls to

`np.random.binomial`

is now avoided. (#5447)Using

`@`

with legacy Hamiltonian instances now properly de-queues the previously existing operations. (#5455)The

`QNSPSAOptimizer`

now properly handles differentiable parameters, resulting in being able to use it for more than one optimization step. (#5439)The QNode interface now resets if an error occurs during execution. (#5449)

Failing tests due to changes with Lightning’s adjoint diff pipeline have been fixed. (#5450)

Failures occurring when making autoray-dispatched calls to Torch with paired CPU data have been fixed. (#5438)

`jax.jit`

now works with`qml.sample`

with a multi-wire observable. (#5422)`qml.qinfo.quantum_fisher`

now works with non-`default.qubit`

devices. (#5423)We no longer perform unwanted

`dtype`

promotion in the`pauli_rep`

of`SProd`

instances when using Tensorflow. (#5246)Fixed

`TestQubitIntegration.test_counts`

in`tests/interfaces/test_jax_qnode.py`

to always produce counts for all outcomes. (#5336)Fixed

`PauliSentence.to_mat(wire_order)`

to support identities with wires. (#5407)`CompositeOp.map_wires`

now correctly maps the`overlapping_ops`

property. (#5430)`DefaultQubit.supports_derivatives`

has been updated to correctly handle circuits containing mid-circuit measurements and adjoint differentiation. (#5434)`SampleMP`

,`ExpectationMP`

,`CountsMP`

, and`VarianceMP`

constructed with`eigvals`

can now properly process samples. (#5463)Fixed a bug in

`hamiltonian_expand`

that produces incorrect output dimensions when shot vectors are combined with parameter broadcasting. (#5494)`default.qubit`

now allows measuring`Identity`

on no wires and observables containing`Identity`

on no wires. (#5570)Fixed a bug where

`TorchLayer`

does not work with shot vectors. (#5492)Fixed a bug where the output shape of a QNode returning a list containing a single measurement is incorrect when combined with shot vectors. (#5492)

Fixed a bug in

`qml.math.kron`

that makes Torch incompatible with NumPy. (#5540)Fixed a bug in

`_group_measurements`

that fails to group measurements with commuting observables when they are operands of`Prod`

. (#5525)`qml.equal`

can now be used with sums and products that contain operators on no wires like`I`

and`GlobalPhase`

. (#5562)`CompositeOp.has_diagonalizing_gates`

now does a more complete check of the base operators to ensure consistency between`op.has_diagonalzing_gates`

and`op.diagonalizing_gates()`

(#5603)Updated the

`method`

kwarg of`qml.TrotterProduct().error()`

to be more clear that we are computing upper-bounds. (#5637)

### Contributors ✍️

This release contains contributions from (in alphabetical order):

Tarun Kumar Allamsetty, Guillermo Alonso, Mikhail Andrenkov, Utkarsh Azad, Gabriel Bottrill, Thomas Bromley, Astral Cai, Diksha Dhawan, Isaac De Vlugt, Amintor Dusko, Pietropaolo Frisoni, Lillian M. A. Frederiksen, Diego Guala, Austin Huang, Soran Jahangiri, Korbinian Kottmann, Christina Lee, Vincent Michaud-Rioux, Mudit Pandey, Kenya Sakka, Jay Soni, Matthew Silverman, David Wierichs.

- orphan

## Release 0.35.0¶

### New features since last release

#### Qiskit 1.0 integration 🔌

This version of PennyLane makes it easier to import circuits from Qiskit. (#5218) (#5168)

The

`qml.from_qiskit`

function converts a Qiskit QuantumCircuit into a PennyLane quantum function. Although`qml.from_qiskit`

already exists in PennyLane, we have made a number of improvements to make importing from Qiskit easier. And yes —`qml.from_qiskit`

functionality is compatible with both Qiskit 1.0 and earlier versions! Here’s a comprehensive list of the improvements:You can now append PennyLane measurements onto the quantum function returned by

`qml.from_qiskit`

. Consider this simple Qiskit circuit:import pennylane as qml from qiskit import QuantumCircuit qc = QuantumCircuit(2) qc.rx(0.785, 0) qc.ry(1.57, 1)

We can convert it into a PennyLane QNode in just a few lines, with PennyLane

`measurements`

easily included:>>> dev = qml.device("default.qubit") >>> measurements = qml.expval(qml.Z(0) @ qml.Z(1)) >>> qfunc = qml.from_qiskit(qc, measurements=measurements) >>> qnode = qml.QNode(qfunc, dev) >>> qnode() tensor(0.00056331, requires_grad=True)

Quantum circuits that already contain Qiskit-side measurements can be faithfully converted with

`qml.from_qiskit`

. Consider this example Qiskit circuit:qc = QuantumCircuit(3, 2) # Teleportation qc.rx(0.9, 0) # Prepare input state on qubit 0 qc.h(1) # Prepare Bell state on qubits 1 and 2 qc.cx(1, 2) qc.cx(0, 1) # Perform teleportation qc.h(0) qc.measure(0, 0) qc.measure(1, 1) with qc.if_test((1, 1)): # Perform first conditional qc.x(2)

This circuit can be converted into PennyLane with the Qiskit measurements still accessible. For example, we can use those results as inputs to a mid-circuit measurement in PennyLane:

@qml.qnode(dev) def teleport(): m0, m1 = qml.from_qiskit(qc)() qml.cond(m0, qml.Z)(2) return qml.density_matrix(2)

>>> teleport() tensor([[0.81080498+0.j , 0. +0.39166345j], [0. -0.39166345j, 0.18919502+0.j ]], requires_grad=True)

It is now more intuitive to handle and differentiate parametrized Qiskit circuits. Consider the following circuit:

from qiskit.circuit import Parameter from pennylane import numpy as np angle0 = Parameter("x") angle1 = Parameter("y") qc = QuantumCircuit(2, 2) qc.rx(angle0, 0) qc.ry(angle1, 1) qc.cx(1, 0)

We can convert this circuit into a QNode with two arguments, corresponding to

`x`

and`y`

:measurements = qml.expval(qml.PauliZ(0)) qfunc = qml.from_qiskit(qc, measurements) qnode = qml.QNode(qfunc, dev)

The QNode can be evaluated and differentiated:

>>> x, y = np.array([0.4, 0.5], requires_grad=True) >>> qnode(x, y) tensor(0.80830707, requires_grad=True) >>> qml.grad(qnode)(x, y) (tensor(-0.34174675, requires_grad=True), tensor(-0.44158016, requires_grad=True))

This shows how easy it is to make a Qiskit circuit differentiable with PennyLane.

In addition to circuits, it is also possible to convert operators from Qiskit to PennyLane with a new function called

`qml.from_qiskit_op`

. (#5251)A Qiskit SparsePauliOp can be converted to a PennyLane operator using

`qml.from_qiskit_op`

:>>> from qiskit.quantum_info import SparsePauliOp >>> qiskit_op = SparsePauliOp(["II", "XY"]) >>> qiskit_op SparsePauliOp(['II', 'XY'], coeffs=[1.+0.j, 1.+0.j]) >>> pl_op = qml.from_qiskit_op(qiskit_op) >>> pl_op I(0) + X(1) @ Y(0)

Combined with

`qml.from_qiskit`

, it becomes easy to quickly calculate quantities like expectation values by converting the whole workflow to PennyLane:qc = QuantumCircuit(2) # Create circuit qc.rx(0.785, 0) qc.ry(1.57, 1) measurements = qml.expval(pl_op) # Create QNode qfunc = qml.from_qiskit(qc, measurements) qnode = qml.QNode(qfunc, dev)

>>> qnode() # Evaluate! tensor(0.29317504, requires_grad=True)

#### Native mid-circuit measurements on Default Qubit 💡

Mid-circuit measurements can now be more scalable and efficient in finite-shots mode with

`default.qubit`

by simulating them in a similar way to what happens on quantum hardware. (#5088) (#5120)Previously, mid-circuit measurements (MCMs) would be automatically replaced with an additional qubit using the

`@qml.defer_measurements`

transform. The circuit below would have required thousands of qubits to simulate.Now, MCMs are performed in a similar way to quantum hardware with finite shots on

`default.qubit`

. For each shot and each time an MCM is encountered, the device evaluates the probability of projecting onto`|0>`

or`|1>`

and makes a random choice to collapse the circuit state. This approach works well when there are a lot of MCMs and the number of shots is not too high.import pennylane as qml dev = qml.device("default.qubit", shots=10) @qml.qnode(dev) def f(): for i in range(1967): qml.Hadamard(0) qml.measure(0) return qml.sample(qml.PauliX(0))

>>> f() tensor([-1, -1, -1, 1, 1, -1, 1, -1, 1, -1], requires_grad=True)

#### Work easily and efficiently with operators 🔧

Over the past few releases, PennyLane’s approach to operator arithmetic has been in the process of being overhauled. We have a few objectives:

To make it as easy to work with PennyLane operators as it would be with pen and paper.

To improve the efficiency of operator arithmetic.

The updated operator arithmetic functionality is still being finalized, but can be activated using

`qml.operation.enable_new_opmath()`

. In the next release, the new behaviour will become the default, so we recommend enabling now to become familiar with the new system!The following updates have been made in this version of PennyLane:

You can now easily access Pauli operators via

`I`

,`X`

,`Y`

, and`Z`

: (#5116)>>> from pennylane import I, X, Y, Z >>> X(0) X(0)

The original long-form names

`Identity`

,`PauliX`

,`PauliY`

, and`PauliZ`

remain available, but use of the short-form names is now recommended.The original long-form names

`Identity`

,`PauliX`

,`PauliY`

, and`PauliZ`

remain available, but use of the short-form names is now recommended.A new

`qml.commutator`

function is now available that allows you to compute commutators between PennyLane operators. (#5051) (#5052) (#5098)>>> qml.commutator(X(0), Y(0)) 2j * Z(0)

Operators in PennyLane can have a backend Pauli representation, which can be used to perform faster operator arithmetic. Now, the Pauli representation will be automatically used for calculations when available. (#4989) (#5001) (#5003) (#5017) (#5027)

The Pauli representation can be optionally accessed via

`op.pauli_rep`

:>>> qml.operation.enable_new_opmath() >>> op = X(0) + Y(0) >>> op.pauli_rep 1.0 * X(0) + 1.0 * Y(0)

Extensive improvements have been made to the string representations of PennyLane operators, making them shorter and possible to copy-paste as valid PennyLane code. (#5116) (#5138)

>>> 0.5 * X(0) 0.5 * X(0) >>> 0.5 * (X(0) + Y(1)) 0.5 * (X(0) + Y(1))

Sums with many terms are broken up into multiple lines, but can still be copied back as valid code:

>>> 0.5 * (X(0) @ X(1)) + 0.7 * (X(1) @ X(2)) + 0.8 * (X(2) @ X(3)) ( 0.5 * (X(0) @ X(1)) + 0.7 * (X(1) @ X(2)) + 0.8 * (X(2) @ X(3)) )

Linear combinations of operators and operator multiplication via

`Sum`

and`Prod`

, respectively, have been updated to reach feature parity with`Hamiltonian`

and`Tensor`

, respectively. This should minimize the effort to port over any existing code. (#5070) (#5132) (#5133)Updates include support for grouping via the

`pauli`

module:>>> obs = [X(0) @ Y(1), Z(0), Y(0) @ Z(1), Y(1)] >>> qml.pauli.group_observables(obs) [[Y(0) @ Z(1)], [X(0) @ Y(1), Y(1)], [Z(0)]]

#### New Clifford device 🦾

A new

`default.clifford`

device enables efficient simulation of large-scale Clifford circuits defined in PennyLane through the use of stim as a backend. (#4936) (#4954) (#5144)Given a circuit with only Clifford gates, one can use this device to obtain the usual range of PennyLane measurements as well as the state represented in the Tableau form of Aaronson & Gottesman (2004):

import pennylane as qml dev = qml.device("default.clifford", tableau=True) @qml.qnode(dev) def circuit(): qml.CNOT(wires=[0, 1]) qml.PauliX(wires=[1]) qml.ISWAP(wires=[0, 1]) qml.Hadamard(wires=[0]) return qml.state()

>>> circuit() array([[0, 1, 1, 0, 0], [1, 0, 1, 1, 1], [0, 0, 0, 1, 0], [1, 0, 0, 1, 1]])

The

`default.clifford`

device also supports the`PauliError`

,`DepolarizingChannel`

,`BitFlip`

and`PhaseFlip`

noise channels when operating in finite-shot mode.

### Improvements 🛠

#### Faster gradients with VJPs and other performance improvements

Vector-Jacobian products (VJPs) can result in faster computations when the output of your quantum Node has a low dimension. They can be enabled by setting

`device_vjp=True`

when loading a QNode. In the next release of PennyLane, VJPs are planned to be used by default, when available.In this release, we have unlocked:

Adjoint device VJPs can be used with

`jax.jacobian`

, meaning that`device_vjp=True`

is always faster when using JAX with`default.qubit`

. (#4963)PennyLane can now use lightning-provided VJPs. (#4914)

VJPs can be used with TensorFlow, though support has not yet been added for

`tf.Function`

and Tensorflow Autograph. (#4676)

Measuring

`qml.probs`

is now faster due to an optimization in converting samples to counts. (#5145)The performance of circuit-cutting workloads with large numbers of generated tapes has been improved. (#5005)

Queueing (

`AnnotatedQueue`

) has been removed from`qml.cut_circuit`

and`qml.cut_circuit_mc`

to improve performance for large workflows. (#5108)

#### Community contributions 🥳

A new function called

`qml.fermi.parity_transform`

has been added for parity mapping of a fermionic Hamiltonian. (#4928)It is now possible to transform a fermionic Hamiltonian to a qubit Hamiltonian with parity mapping.

import pennylane as qml fermi_ham = qml.fermi.FermiWord({(0, 0) : '+', (1, 1) : '-'}) qubit_ham = qml.fermi.parity_transform(fermi_ham, n=6)

>>> print(qubit_ham) -0.25j * Y(0) + (-0.25+0j) * (X(0) @ Z(1)) + (0.25+0j) * X(0) + 0.25j * (Y(0) @ Z(1))

The transform

`split_non_commuting`

now accepts measurements of type`probs`

,`sample`

, and`counts`

, which accept both wires and observables. (#4972)The efficiency of matrix calculations when an operator is symmetric over a given set of wires has been improved. (#3601)

The

`pennylane/math/quantum.py`

module now has support for computing the minimum entropy of a density matrix. (#3959)>>> x = [1, 0, 0, 1] / np.sqrt(2) >>> x = qml.math.dm_from_state_vector(x) >>> qml.math.min_entropy(x, indices=[0]) 0.6931471805599455

A function called

`apply_operation`

that applies operations to device-compatible states has been added to the new`qutrit_mixed`

module found in`qml.devices`

. (#5032)A function called

`measure`

has been added to the new`qutrit_mixed`

module found in`qml.devices`

that measures device-compatible states for a collection of measurement processes. (#5049)A

`partial_trace`

function has been added to`qml.math`

for taking the partial trace of matrices. (#5152)

#### Other operator arithmetic improvements

The following capabilities have been added for Pauli arithmetic: (#4989) (#5001) (#5003) (#5017) (#5027) (#5018)

You can now multiply

`PauliWord`

and`PauliSentence`

instances by scalars (e.g.,`0.5 * PauliWord({0: "X"})`

or`0.5 * PauliSentence({PauliWord({0: "X"}): 1.})`

).You can now intuitively add and subtract

`PauliWord`

and`PauliSentence`

instances and scalars together (scalars are treated implicitly as multiples of the identity,`I`

). For example,`ps1 + pw1 + 1.`

for some Pauli word`pw1 = PauliWord({0: "X", 1: "Y"})`

and Pauli sentence`ps1 = PauliSentence({pw1: 3.})`

.You can now element-wise multiply

`PauliWord`

,`PauliSentence`

, and operators together with`qml.dot`

(e.g.,`qml.dot([0.5, -1.5, 2], [pw1, ps1, id_word])`

with`id_word = PauliWord({})`

).`qml.matrix`

now accepts`PauliWord`

and`PauliSentence`

instances (e.g.,`qml.matrix(PauliWord({0: "X"}))`

).It is now possible to compute commutators with Pauli operators natively with the new

`commutator`

method.>>> op1 = PauliWord({0: "X", 1: "X"}) >>> op2 = PauliWord({0: "Y"}) + PauliWord({1: "Y"}) >>> op1.commutator(op2) 2j * Z(0) @ X(1) + 2j * X(0) @ Z(1)

Composite operations (e.g., those made with

`qml.prod`

and`qml.sum`

) and scalar-product operations convert`Hamiltonian`

and`Tensor`

operands to`Sum`

and`Prod`

types, respectively. This helps avoid the mixing of incompatible operator types. (#5031) (#5063)`qml.Identity()`

can be initialized without wires. Measuring it is currently not possible, though. (#5106)`qml.dot`

now returns a`Sum`

class even when all the coefficients match. (#5143)`qml.pauli.group_observables`

now supports grouping`Prod`

and`SProd`

operators. (#5070)The performance of converting a

`PauliSentence`

to a`Sum`

has been improved. (#5141) (#5150)Akin to

`qml.Hamiltonian`

features, the coefficients and operators that make up composite operators formed via`Sum`

or`Prod`

can now be accessed with the`terms()`

method. (#5132) (#5133) (#5164)>>> qml.operation.enable_new_opmath() >>> op = X(0) @ (0.5 * X(1) + X(2)) >>> op.terms() ([0.5, 1.0], [X(1) @ X(0), X(2) @ X(0)])

String representations of

`ParametrizedHamiltonian`

have been updated to match the style of other PL operators. (#5215)

#### Other improvements

The

`pl-device-test`

suite is now compatible with the`qml.devices.Device`

interface. (#5229)The

`QSVT`

operation now determines its`data`

from the block encoding and projector operator data. (#5226) (#5248)The

`BlockEncode`

operator is now JIT-compatible with JAX. (#5110)The

`qml.qsvt`

function uses`qml.GlobalPhase`

instead of`qml.exp`

to define a global phase. (#5105)The

`tests/ops/functions/conftest.py`

test has been updated to ensure that all operator types are tested for validity. (#4978)A new

`pennylane.workflow`

module has been added. This module now contains`qnode.py`

,`execution.py`

,`set_shots.py`

,`jacobian_products.py`

, and the submodule`interfaces`

. (#5023)A more informative error is now raised when calling

`adjoint_jacobian`

with trainable state-prep operations. (#5026)`qml.workflow.get_transform_program`

and`qml.workflow.construct_batch`

have been added to inspect the transform program and batch of tapes at different stages. (#5084)All custom controlled operations such as

`CRX`

,`CZ`

,`CNOT`

,`ControlledPhaseShift`

now inherit from`ControlledOp`

, giving them additional properties such as`control_wire`

and`control_values`

. Calling`qml.ctrl`

on`RX`

,`RY`

,`RZ`

,`Rot`

, and`PhaseShift`

with a single control wire will return gates of types`CRX`

,`CRY`

, etc. as opposed to a general`Controlled`

operator. (#5069) (#5199)The CI will now fail if coverage data fails to upload to codecov. Previously, it would silently pass and the codecov check itself would never execute. (#5101)

`qml.ctrl`

called on operators with custom controlled versions will now return instances of the custom class, and it will flatten nested controlled operators to a single multi-controlled operation. For`PauliX`

,`CNOT`

,`Toffoli`

, and`MultiControlledX`

, calling`qml.ctrl`

will always resolve to the best option in`CNOT`

,`Toffoli`

, or`MultiControlledX`

depending on the number of control wires and control values. (#5125)Unwanted warning filters have been removed from tests and no

`PennyLaneDeprecationWarning`

s are being raised unexpectedly. (#5122)New error tracking and propagation functionality has been added (#5115) (#5121)

The method

`map_batch_transform`

has been replaced with the method`_batch_transform`

implemented in`TransformDispatcher`

. (#5212)`TransformDispatcher`

can now dispatch onto a batch of tapes, making it easier to compose transforms when working in the tape paradigm. (#5163)`qml.ctrl`

is now a simple wrapper that either calls PennyLane’s built in`create_controlled_op`

or uses the Catalyst implementation. (#5247)Controlled composite operations can now be decomposed using ZYZ rotations. (#5242)

New functions called

`qml.devices.modifiers.simulator_tracking`

and`qml.devices.modifiers.single_tape_support`

have been added to add basic default behavior onto a device class. (#5200)

### Breaking changes 💔

Passing additional arguments to a transform that decorates a QNode must now be done through the use of

`functools.partial`

. (#5046)`qml.ExpvalCost`

has been removed. Users should use`qml.expval()`

moving forward. (#5097)Caching of executions is now turned off by default when

`max_diff == 1`

, as the classical overhead cost outweighs the probability that duplicate circuits exists. (#5243)The entry point convention registering compilers with PennyLane has changed. (#5140)

To allow for packages to register multiple compilers with PennyLane, the

`entry_points`

convention under the designated group name`pennylane.compilers`

has been modified.Previously, compilers would register

`qjit`

(JIT decorator),`ops`

(compiler-specific operations), and`context`

(for tracing and program capture).Now, compilers must register

`compiler_name.qjit`

,`compiler_name.ops`

, and`compiler_name.context`

, where`compiler_name`

is replaced by the name of the provided compiler.For more information, please see the documentation on adding compilers.

PennyLane source code is now compatible with the latest version of

`black`

. (#5112) (#5119)`gradient_analysis_and_validation`

has been renamed to`find_and_validate_gradient_methods`

. Instead of returning a list, it now returns a dictionary of gradient methods for each parameter index, and no longer mutates the tape. (#5035)Multiplying two

`PauliWord`

instances no longer returns a tuple`(new_word, coeff)`

but instead`PauliSentence({new_word: coeff})`

. The old behavior is still available with the private method`PauliWord._matmul(other)`

for faster processing. (#5045)`Observable.return_type`

has been removed. Instead, you should inspect the type of the surrounding measurement process. (#5044)`ClassicalShadow.entropy()`

no longer needs an`atol`

keyword as a better method to estimate entropies from approximate density matrix reconstructions (with potentially negative eigenvalues). (#5048)Controlled operators with a custom controlled version decompose like how their controlled counterpart decomposes as opposed to decomposing into their controlled version. (#5069) (#5125)

For example:

>>> qml.ctrl(qml.RX(0.123, wires=1), control=0).decomposition() [ RZ(1.5707963267948966, wires=[1]), RY(0.0615, wires=[1]), CNOT(wires=[0, 1]), RY(-0.0615, wires=[1]), CNOT(wires=[0, 1]), RZ(-1.5707963267948966, wires=[1]) ]

`QuantumScript.is_sampled`

and`QuantumScript.all_sampled`

have been removed. Users should now validate these properties manually. (#5072)`qml.transforms.one_qubit_decomposition`

and`qml.transforms.two_qubit_decomposition`

have been removed. Instead, you should use`qml.ops.one_qubit_decomposition`

and`qml.ops.two_qubit_decomposition`

. (#5091)

### Deprecations 👋

Calling

`qml.matrix`

without providing a`wire_order`

on objects where the wire order could be ambiguous now raises a warning. In the future, the`wire_order`

argument will be required in these cases. (#5039)`Operator.validate_subspace(subspace)`

has been relocated to the`qml.ops.qutrit.parametric_ops`

module and will be removed from the Operator class in an upcoming release. (#5067)Matrix and tensor products between

`PauliWord`

and`PauliSentence`

instances are done using the`@`

operator,`*`

will be used only for scalar multiplication. Note also the breaking change that the product of two`PauliWord`

instances now returns a`PauliSentence`

instead of a tuple`(new_word, coeff)`

. (#4989) (#5054)`MeasurementProcess.name`

and`MeasurementProcess.data`

are now deprecated, as they contain dummy values that are no longer needed. (#5047) (#5071) (#5076) (#5122)`qml.pauli.pauli_mult`

and`qml.pauli.pauli_mult_with_phase`

are now deprecated. Instead, you should use`qml.simplify(qml.prod(pauli_1, pauli_2))`

to get the reduced operator. (#5057)The private functions

`_pauli_mult`

,`_binary_matrix`

and`_get_pauli_map`

from the`pauli`

module have been deprecated, as they are no longer used anywhere and the same functionality can be achieved using newer features in the`pauli`

module. (#5057)`Sum.ops`

,`Sum.coeffs`

,`Prod.ops`

and`Prod.coeffs`

will be deprecated in the future. (#5164)

### Documentation 📝

The module documentation for

`pennylane.tape`

now explains the difference between`QuantumTape`

and`QuantumScript`

. (#5065)A typo in a code example in the

`qml.transforms`

API has been fixed. (#5014)Documentation for

`qml.data`

has been updated and now mentions a way to access the same dataset simultaneously from multiple environments. (#5029)A clarification for the definition of

`argnum`

added to gradient methods has been made. (#5035)A typo in the code example for

`qml.qchem.dipole_of`

has been fixed. (#5036)A development guide on deprecations and removals has been added. (#5083)

A note about the eigenspectrum of second-quantized Hamiltonians has been added to

`qml.eigvals`

. (#5095)A warning about two mathematically equivalent Hamiltonians undergoing different time evolutions has been added to

`qml.TrotterProduct`

and`qml.ApproxTimeEvolution`

. (#5137)A reference to the paper that provides the image of the

`qml.QAOAEmbedding`

template has been added. (#5130)The docstring of

`qml.sample`

has been updated to advise the use of single-shot expectations instead when differentiating a circuit. (#5237)A quick start page has been added called “Importing Circuits”. This explains how to import quantum circuits and operations defined outside of PennyLane. (#5281)

### Bug fixes 🐛

`QubitChannel`

can now be used with jitting. (#5288)Fixed a bug in the matplotlib drawer where the colour of

`Barrier`

did not match the requested style. (#5276)`qml.draw`

and`qml.draw_mpl`

now apply all applied transforms before drawing. (#5277)`ctrl_decomp_zyz`

is now differentiable. (#5198)`qml.ops.Pow.matrix()`

is now differentiable with TensorFlow with integer exponents. (#5178)The

`qml.MottonenStatePreparation`

template has been updated to include a global phase operation. (#5166)Fixed a queuing bug when using

`qml.prod`

with a quantum function that queues a single operator. (#5170)The

`qml.TrotterProduct`

template has been updated to accept scalar products of operators as an input Hamiltonian. (#5073)Fixed a bug where caching together with JIT compilation and broadcasted tapes yielded wrong results

`Operator.hash`

now depends on the memory location,`id`

, of a JAX tracer instead of its string representation. (#3917)`qml.transforms.undo_swaps`

can now work with operators with hyperparameters or nesting. (#5081)`qml.transforms.split_non_commuting`

will now pass the original shots along. (#5081)If

`argnum`

is provided to a gradient transform, only the parameters specified in`argnum`

will have their gradient methods validated. (#5035)`StatePrep`

operations expanded onto more wires are now compatible with backprop. (#5028)`qml.equal`

works well with`qml.Sum`

operators when wire labels are a mix of integers and strings. (#5037)The return value of

`Controlled.generator`

now contains a projector that projects onto the correct subspace based on the control value specified. (#5068)`CosineWindow`

no longer raises an unexpected error when used on a subset of wires at the beginning of a circuit. (#5080)`tf.function`

now works with`TensorSpec(shape=None)`

by skipping batch size computation. (#5089)`PauliSentence.wires`

no longer imposes a false order. (#5041)`qml.qchem.import_state`

now applies the chemist-to-physicist sign convention when initializing a PennyLane state vector from classically pre-computed wavefunctions. That is, it interleaves spin-up/spin-down operators for the same spatial orbital index, as standard in PennyLane (instead of commuting all spin-up operators to the left, as is standard in quantum chemistry). (#5114)Multi-wire controlled

`CNOT`

and`PhaseShift`

are now be decomposed correctly. (#5125) (#5148)`draw_mpl`

no longer raises an error when drawing a circuit containing an adjoint of a controlled operation. (#5149)`default.mixed`

no longer throws`ValueError`

when applying a state vector that is not of type`complex128`

when used with tensorflow. (#5155)`ctrl_decomp_zyz`

no longer raises a`TypeError`

if the rotation parameters are of type`torch.Tensor`

(#5183)Comparing

`Prod`

and`Sum`

objects now works regardless of nested structure with`qml.equal`

if the operators have a valid`pauli_rep`

property. (#5177)Controlled

`GlobalPhase`

with non-zero control wires no longer throws an error. (#5194)A

`QNode`

transformed with`mitigate_with_zne`

now accepts batch parameters. (#5195)The matrix of an empty

`PauliSentence`

instance is now correct (all-zeros). Further, matrices of empty`PauliWord`

and`PauliSentence`

instances can now be turned into matrices. (#5188)`PauliSentence`

instances can handle matrix multiplication with`PauliWord`

instances. (#5208)`CompositeOp.eigendecomposition`

is now JIT-compatible. (#5207)`QubitDensityMatrix`

now works with JAX-JIT on the`default.mixed`

device. (#5203) (#5236)When a QNode specifies

`diff_method="adjoint"`

,`default.qubit`

no longer tries to decompose non-trainable operations with non-scalar parameters such as`QubitUnitary`

. (#5233)The overwriting of the class names of

`I`

,`X`

,`Y`

, and`Z`

no longer happens in the initialization after causing problems with datasets. This now happens globally. (#5252)The

`adjoint_metric_tensor`

transform now works with`jax`

. (#5271)

### Contributors ✍️

This release contains contributions from (in alphabetical order):

Abhishek Abhishek, Mikhail Andrenkov, Utkarsh Azad, Trenten Babcock, Gabriel Bottrill, Thomas Bromley, Astral Cai, Skylar Chan, Isaac De Vlugt, Diksha Dhawan, Lillian Frederiksen, Pietropaolo Frisoni, Eugenio Gigante, Diego Guala, David Ittah, Soran Jahangiri, Jacky Jiang, Korbinian Kottmann, Christina Lee, Xiaoran Li, Vincent Michaud-Rioux, Romain Moyard, Pablo Antonio Moreno Casares, Erick Ochoa Lopez, Lee J. O’Riordan, Mudit Pandey, Alex Preciado, Matthew Silverman, Jay Soni.

- orphan

## Release 0.34.0¶

### New features since last release

#### Statistics and drawing for mid-circuit measurements 🎨

It is now possible to return statistics of composite mid-circuit measurements. (#4888)

Mid-circuit measurement results can be composed using basic arithmetic operations and then statistics can be calculated by putting the result within a PennyLane measurement like

`qml.expval()`

. For example:import pennylane as qml dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(phi, theta): qml.RX(phi, wires=0) m0 = qml.measure(wires=0) qml.RY(theta, wires=1) m1 = qml.measure(wires=1) return qml.expval(~m0 + m1) print(circuit(1.23, 4.56))

`1.2430187928114291`

Another option, for ease-of-use when using

`qml.sample()`

,`qml.probs()`

, or`qml.counts()`

, is to provide a simple list of mid-circuit measurement results:dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(phi, theta): qml.RX(phi, wires=0) m0 = qml.measure(wires=0) qml.RY(theta, wires=1) m1 = qml.measure(wires=1) return qml.sample(op=[m0, m1]) print(circuit(1.23, 4.56, shots=5))

[[0 1] [0 1] [0 0] [1 0] [0 1]]

Composite mid-circuit measurement statistics are supported on

`default.qubit`

and`default.mixed`

. To learn more about which measurements and arithmetic operators are supported, refer to the measurements page and the documentation for qml.measure.Mid-circuit measurements can now be visualized with the text-based

`qml.draw()`

and the graphical`qml.draw_mpl()`

methods. (#4775) (#4803) (#4832) (#4901) (#4850) (#4917) (#4930) (#4957)Drawing of mid-circuit measurement capabilities including qubit reuse and reset, postselection, conditioning, and collecting statistics is now supported. Here is an all-encompassing example:

def circuit(): m0 = qml.measure(0, reset=True) m1 = qml.measure(1, postselect=1) qml.cond(m0 - m1 == 0, qml.S)(0) m2 = qml.measure(1) qml.cond(m0 + m1 == 2, qml.T)(0) qml.cond(m2, qml.PauliX)(1)

The text-based drawer outputs:

>>> print(qml.draw(circuit)()) 0: ──┤↗│ │0⟩────────S───────T────┤ 1: ───║────────┤↗₁├──║──┤↗├──║──X─┤ ╚═════════║════╬═══║═══╣ ║ ╚════╩═══║═══╝ ║ ╚══════╝

The graphical drawer outputs:

>>> print(qml.draw_mpl(circuit)())

#### Catalyst is seamlessly integrated with PennyLane ⚗️

Catalyst, our next-generation compilation framework, is now accessible within PennyLane, allowing you to more easily benefit from hybrid just-in-time (JIT) compilation.

To access these features, simply install

`pennylane-catalyst`

:pip install pennylane-catalyst

The qml.compiler module provides support for hybrid quantum-classical compilation. (#4692) (#4979)

Through the use of the

`qml.qjit`

decorator, entire workflows can be JIT compiled — including both quantum and classical processing — down to a machine binary on first-function execution. Subsequent calls to the compiled function will execute the previously-compiled binary, resulting in significant performance improvements.import pennylane as qml dev = qml.device("lightning.qubit", wires=2) @qml.qjit @qml.qnode(dev) def circuit(theta): qml.Hadamard(wires=0) qml.RX(theta, wires=1) qml.CNOT(wires=[0,1]) return qml.expval(qml.PauliZ(wires=1))

>>> circuit(0.5) # the first call, compilation occurs here array(0.) >>> circuit(0.5) # the precompiled quantum function is called array(0.)

Currently, PennyLane supports the Catalyst hybrid compiler with the

`qml.qjit`

decorator. A significant benefit of Catalyst is the ability to preserve complex control flow around quantum operations — such as`if`

statements and`for`

loops, and including measurement feedback — during compilation, while continuing to support end-to-end autodifferentiation.The following functions can now be used with the

`qml.qjit`

decorator:`qml.grad`

,`qml.jacobian`

,`qml.vjp`

,`qml.jvp`

, and`qml.adjoint`

. (#4709) (#4724) (#4725) (#4726)When

`qml.grad`

or`qml.jacobian`

are used with`@qml.qjit`

, they are patched to catalyst.grad and catalyst.jacobian, respectively.dev = qml.device("lightning.qubit", wires=1) @qml.qjit def workflow(x): @qml.qnode(dev) def circuit(x): qml.RX(np.pi * x[0], wires=0) qml.RY(x[1], wires=0) return qml.probs() g = qml.jacobian(circuit) return g(x)

>>> workflow(np.array([2.0, 1.0])) array([[ 3.48786850e-16, -4.20735492e-01], [-8.71967125e-17, 4.20735492e-01]])

JIT-compatible functionality for control flow has been added via

`qml.for_loop`

,`qml.while_loop`

, and`qml.cond`

. (#4698) (#5006)`qml.for_loop`

and`qml.while_loop`

can be deployed as decorators on functions that are the body of the loop. The arguments to both follow typical conventions:@qml.for_loop(lower_bound, upper_bound, step)

@qml.while_loop(cond_function)

Here is a concrete example with

`qml.for_loop`

:`qml.for_loop`

and`qml.while_loop`

can be deployed as decorators on functions that are the body of the loop. The arguments to both follow typical conventions:@qml.for_loop(lower_bound, upper_bound, step)

@qml.while_loop(cond_function)

Here is a concrete example with

`qml.for_loop`

:dev = qml.device("lightning.qubit", wires=1) @qml.qjit @qml.qnode(dev) def circuit(n: int, x: float): @qml.for_loop(0, n, 1) def loop_rx(i, x): # perform some work and update (some of) the arguments qml.RX(x, wires=0) # update the value of x for the next iteration return jnp.sin(x) # apply the for loop final_x = loop_rx(x) return qml.expval(qml.PauliZ(0)), final_x

>>> circuit(7, 1.6) (array(0.97926626), array(0.55395718))

#### Decompose circuits into the Clifford+T gateset 🧩

The new

`qml.clifford_t_decomposition()`

transform provides an approximate breakdown of an input circuit into the Clifford+T gateset. Behind the scenes, this decomposition is enacted via the`sk_decomposition()`

function using the Solovay-Kitaev algorithm. (#4801) (#4802)The Solovay-Kitaev algorithm

*approximately*decomposes a quantum circuit into the Clifford+T gateset. To account for this, a desired total circuit decomposition error,`epsilon`

, must be specified when using`qml.clifford_t_decomposition`

:dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(): qml.RX(1.1, 0) return qml.state() circuit = qml.clifford_t_decomposition(circuit, epsilon=0.1)

>>> print(qml.draw(circuit)()) 0: ──T†──H──T†──H──T──H──T──H──T──H──T──H──T†──H──T†──T†──H──T†──H──T──H──T──H──T──H──T──H──T†──H ───T†──H──T──H──GlobalPhase(0.39)─┤

The resource requirements of this circuit can also be evaluated:

>>> with qml.Tracker(dev) as tracker: ... circuit() >>> resources_lst = tracker.history["resources"] >>> resources_lst[0] wires: 1 gates: 34 depth: 34 shots: Shots(total=None) gate_types: {'Adjoint(T)': 8, 'Hadamard': 16, 'T': 9, 'GlobalPhase': 1} gate_sizes: {1: 33, 0: 1}

#### Use an iterative approach for quantum phase estimation 🔄

Iterative Quantum Phase Estimation is now available with

`qml.iterative_qpe`

. (#4804)The subroutine can be used similarly to mid-circuit measurements:

import pennylane as qml dev = qml.device("default.qubit", shots=5) @qml.qnode(dev) def circuit(): # Initial state qml.PauliX(wires=[0]) # Iterative QPE measurements = qml.iterative_qpe(qml.RZ(2., wires=[0]), ancilla=[1], iters=3) return [qml.sample(op=meas) for meas in measurements]

>>> print(circuit()) [array([0, 0, 0, 0, 0]), array([1, 0, 0, 0, 0]), array([0, 1, 1, 1, 1])]

The \(i\)-th element in the list refers to the 5 samples generated by the \(i\)-th measurement of the algorithm.

### Improvements 🛠

#### Community contributions 🥳

The

`+=`

operand can now be used with a`PauliSentence`

, which has also provides a performance boost. (#4662)The Approximate Quantum Fourier Transform (AQFT) is now available with

`qml.AQFT`

. (#4715)`qml.draw`

and`qml.draw_mpl`

now render operator IDs. (#4749)The ID can be specified as a keyword argument when instantiating an operator:

>>> def circuit(): ... qml.RX(0.123, id="data", wires=0) >>> print(qml.draw(circuit)()) 0: ──RX(0.12,"data")─┤

Non-parametric operators such as

`Barrier`

,`Snapshot`

, and`Wirecut`

have been grouped together and moved to`pennylane/ops/meta.py`

. Additionally, the relevant tests have been organized and placed in a new file,`tests/ops/test_meta.py`

. (#4789)The

`TRX`

,`TRY`

, and`TRZ`

operators are now differentiable via backpropagation on`default.qutrit`

. (#4790)The function

`qml.equal`

now supports`ControlledSequence`

operators. (#4829)XZX decomposition has been added to the list of supported single-qubit unitary decompositions. (#4862)

`==`

and`!=`

operands can now be used with`TransformProgram`

and`TransformContainers`

instances. (#4858)A

`qutrit_mixed`

module has been added to`qml.devices`

to store helper functions for a future qutrit mixed-state device. A function called`create_initial_state`

has been added to this module that creates device-compatible initial states. (#4861)The function

`qml.Snapshot`

now supports arbitrary state-based measurements (i.e., measurements of type`StateMeasurement`

). (#4876)`qml.equal`

now supports the comparison of`QuantumScript`

and`BasisRotation`

objects. (#4902) (#4919)The function

`qml.draw_mpl`

now accept a keyword argument`fig`

to specify the output figure window. (#4956)

#### Better support for batching

`qml.AmplitudeEmbedding`

now supports batching when used with Tensorflow. (#4818)`default.qubit`

can now evolve already batched states with`qml.pulse.ParametrizedEvolution`

. (#4863)`qml.ArbitraryUnitary`

now supports batching. (#4745)Operator and tape batch sizes are evaluated lazily, helping run expensive computations less frequently and an issue with Tensorflow pre-computing batch sizes. (#4911)

#### Performance improvements and benchmarking

Autograd, PyTorch, and JAX can now use vector-Jacobian products (VJPs) provided by the device from the new device API. If a device provides a VJP, this can be selected by providing

`device_vjp=True`

to a QNode or`qml.execute`

. (#4935) (#4557) (#4654) (#4878) (#4841)>>> dev = qml.device('default.qubit') >>> @qml.qnode(dev, diff_method="adjoint", device_vjp=True) >>> def circuit(x): ... qml.RX(x, wires=0) ... return qml.expval(qml.PauliZ(0)) >>> with dev.tracker: ... g = qml.grad(circuit)(qml.numpy.array(0.1)) >>> dev.tracker.totals {'batches': 1, 'simulations': 1, 'executions': 1, 'vjp_batches': 1, 'vjps': 1} >>> g -0.09983341664682815

`qml.expval`

with large`Hamiltonian`

objects is now faster and has a significantly lower memory footprint (and constant with respect to the number of`Hamiltonian`

terms) when the`Hamiltonian`

is a`PauliSentence`

. This is due to the introduction of a specialized`dot`

method in the`PauliSentence`

class which performs`PauliSentence`

-`state`

products. (#4839)`default.qubit`

no longer uses a dense matrix for`MultiControlledX`

for more than 8 operation wires. (#4673)Some relevant Pytests have been updated to enable its use as a suite of benchmarks. (#4703)

`default.qubit`

now applies`GroverOperator`

faster by not using its matrix representation but a custom rule for`apply_operation`

. Also, the matrix representation of`GroverOperator`

now runs faster. (#4666)A new pipeline to run benchmarks and plot graphs comparing with a fixed reference has been added. This pipeline will run on a schedule and can be activated on a PR with the label

`ci:run_benchmarks`

. (#4741)`default.qubit`

now supports adjoint differentiation for arbitrary diagonal state-based measurements. (#4865)The benchmarks pipeline has been expanded to export all benchmark data to a single JSON file and a CSV file with runtimes. This includes all references and local benchmarks. (#4873)

#### Final phase of updates to transforms

`qml.quantum_monte_carlo`

and`qml.simplify`

now use the new transform system. (#4708) (#4949)The formal requirement that type hinting be provided when using the

`qml.transform`

decorator has been removed. Type hinting can still be used, but is now optional. Please use a type checker such as mypy if you wish to ensure types are being passed correctly. (#4942)

#### Other improvements

Add PyTree-serialization interface for the

`Wires`

class. (#4998)PennyLane now supports Python 3.12. (#4985)

`SampleMeasurement`

now has an optional method`process_counts`

for computing the measurement results from a counts dictionary. (#4941)A new function called

`ops.functions.assert_valid`

has been added for checking if an`Operator`

class is defined correctly. (#4764)`Shots`

objects can now be multiplied by scalar values. (#4913)`GlobalPhase`

now decomposes to nothing in case devices do not support global phases. (#4855)Custom operations can now provide their matrix directly through the

`Operator.matrix()`

method without needing to update the`has_matrix`

property.`has_matrix`

will now automatically be`True`

if`Operator.matrix`

is overridden, even if`Operator.compute_matrix`

is not. (#4844)The logic for re-arranging states before returning them has been improved. (#4817)

When multiplying

`SparseHamiltonian`

s by a scalar value, the result now stays as a`SparseHamiltonian`

. (#4828)`trainable_params`

can now be set upon initialization of a`QuantumScript`

instead of having to set the parameter after initialization. (#4877)`default.qubit`

now calculates the expectation value of`Hermitian`

operators in a differentiable manner. (#4866)The

`rot`

decomposition now has support for returning a global phase. (#4869)The

`"pennylane_sketch"`

MPL-drawer style has been added. This is the same as the`"pennylane"`

style, but with sketch-style lines. (#4880)Operators now define a

`pauli_rep`

property, an instance of`PauliSentence`

, defaulting to`None`

if the operator has not defined it (or has no definition in the Pauli basis). (#4915)`qml.ShotAdaptiveOptimizer`

can now use a multinomial distribution for spreading shots across the terms of a Hamiltonian measured in a QNode. Note that this is equivalent to what can be done with`qml.ExpvalCost`

, but this is the preferred method because`ExpvalCost`

is deprecated. (#4896)Decomposition of

`qml.PhaseShift`

now uses`qml.GlobalPhase`

for retaining the global phase information. (#4657) (#4947)`qml.equal`

for`Controlled`

operators no longer returns`False`

when equivalent but differently-ordered sets of control wires and control values are compared. (#4944)All PennyLane

`Operator`

subclasses are automatically tested by`ops.functions.assert_valid`

to ensure that they follow PennyLane`Operator`

standards. (#4922)Probability measurements can now be calculated from a

`counts`

dictionary with the addition of a`process_counts`

method in the`ProbabilityMP`

class. (#4952)`ClassicalShadow.entropy`

now uses the algorithm outlined in 1106.5458 to project the approximate density matrix (with potentially negative eigenvalues) onto the closest valid density matrix. (#4959)The

`ControlledSequence.compute_decomposition`

default now decomposes the`Pow`

operators, improving compatibility with machine learning interfaces. (#4995)

### Breaking changes 💔

The function

`qml.transforms.classical_jacobian`

has been moved to the gradients module and is now accessible as`qml.gradients.classical_jacobian`

. (#4900)The transforms submodule

`qml.transforms.qcut`

is now its own module:`qml.qcut`

. (#4819)The decomposition of

`GroverOperator`

now has an additional global phase operation. (#4666)`qml.cond`

and the`Conditional`

operation have been moved from the`transforms`

folder to the`ops/op_math`

folder.`qml.transforms.Conditional`

will now be available as`qml.ops.Conditional`

. (#4860)The

`prep`

keyword argument has been removed from`QuantumScript`

and`QuantumTape`

.`StatePrepBase`

operations should be placed at the beginning of the`ops`

list instead. (#4756)`qml.gradients.pulse_generator`

is now named`qml.gradients.pulse_odegen`

to adhere to paper naming conventions. (#4769)Specifying

`control_values`

passed to`qml.ctrl`

as a string is no longer supported. (#4816)The

`rot`

decomposition will now normalize its rotation angles to the range`[0, 4pi]`

for consistency (#4869)`QuantumScript.graph`

is now built using`tape.measurements`

instead of`tape.observables`

because it depended on the now-deprecated`Observable.return_type`

property. (#4762)The

`"pennylane"`

MPL-drawer style now draws straight lines instead of sketch-style lines. (#4880)The default value for the

`term_sampling`

argument of`ShotAdaptiveOptimizer`

is now`None`

instead of`"weighted_random_sampling"`

. (#4896)

### Deprecations 👋

`single_tape_transform`

,`batch_transform`

,`qfunc_transform`

, and`op_transform`

are deprecated. Use the new`qml.transform`

function instead. (#4774)`Observable.return_type`

is deprecated. Instead, you should inspect the type of the surrounding measurement process. (#4762) (#4798)All deprecations now raise a

`qml.PennyLaneDeprecationWarning`

instead of a`UserWarning`

. (#4814)`QuantumScript.is_sampled`

and`QuantumScript.all_sampled`

are deprecated. Users should now validate these properties manually. (#4773)With an algorithmic improvement to

`ClassicalShadow.entropy`

, the keyword`atol`

becomes obsolete and will be removed in v0.35. (#4959)

### Documentation 📝

Documentation for unitaries and operations’ decompositions has been moved from

`qml.transforms`

to`qml.ops.ops_math`

. (#4906)Documentation for

`qml.metric_tensor`

and`qml.adjoint_metric_tensor`

and`qml.transforms.classical_jacobian`

is now accessible via the gradients API page`qml.gradients`

in the documentation. (#4900)Documentation for

`qml.specs`

has been moved to the`resource`

module. (#4904)Documentation for QCut has been moved to its own API page:

`qml.qcut`

. (#4819)The documentation page for

`qml.measurements`

now links top-level accessible functions (e.g.,`qml.expval`

) to their top-level pages rather than their module-level pages (e.g.,`qml.measurements.expval`

). (#4750)Information for the documentation of

`qml.matrix`

about wire ordering has been added for using`qml.matrix`

on a QNode which uses a device with`device.wires=None`

. (#4874)

### Bug fixes 🐛

`TransformDispatcher`

now stops queuing when performing the transform when applying it to a qfunc. Only the output of the transform will be queued. (#4983)`qml.map_wires`

now works properly with`qml.cond`

and`qml.measure`

. (#4884)`Pow`

operators are now picklable. (#4966)Finite differences and SPSA can now be used with tensorflow-autograph on setups that were seeing a bus error. (#4961)

`qml.cond`

no longer incorrectly queues operators used arguments. (#4948)`Attribute`

objects now return`False`

instead of raising a`TypeError`

when checking if an object is inside the set. (#4933)Fixed a bug where the parameter-shift rule of

`qml.ctrl(op)`

was wrong if`op`

had a generator that has two or more eigenvalues and is stored as a`SparseHamiltonian`

. (#4899)Fixed a bug where trainable parameters in the post-processing of finite-differences were incorrect for JAX when applying the transform directly on a QNode. (#4879)

`qml.grad`

and`qml.jacobian`

now explicitly raise errors if trainable parameters are integers. (#4836)JAX-JIT now works with shot vectors. (#4772)

JAX can now differentiate a batch of circuits where one tape does not have trainable parameters. (#4837)

The decomposition of

`GroverOperator`

now has the same global phase as its matrix. (#4666)The

`tape.to_openqasm`

method no longer mistakenly includes interface information in the parameter string when converting tapes using non-NumPy interfaces. (#4849)`qml.defer_measurements`

now correctly transforms circuits when terminal measurements include wires used in mid-circuit measurements. (#4787)Fixed a bug where the adjoint differentiation method would fail if an operation that has a parameter with

`grad_method=None`

is present. (#4820)`MottonenStatePreparation`

and`BasisStatePreparation`

now raise an error when decomposing a broadcasted state vector. (#4767)Gradient transforms now work with overridden shot vectors and

`default.qubit`

. (#4795)Any

`ScalarSymbolicOp`

, like`Evolution`

, now states that it has a matrix if the target is a`Hamiltonian`

. (#4768)In

`default.qubit`

, initial states are now initialized with the simulator’s wire order, not the circuit’s wire order. (#4781)`qml.compile`

will now always decompose to`expand_depth`

, even if a target basis set is not specified. (#4800)`qml.transforms.transpile`

can now handle measurements that are broadcasted onto all wires. (#4793)Parametrized circuits whose operators do not act on all wires return PennyLane tensors instead of NumPy arrays, as expected. (#4811) (#4817)

`qml.transforms.merge_amplitude_embedding`

no longer depends on queuing, allowing it to work as expected with QNodes. (#4831)`qml.pow(op)`

and`qml.QubitUnitary.pow()`

now also work with Tensorflow data raised to an integer power. (#4827)The text drawer has been fixed to correctly label

`qml.qinfo`

measurements, as well as`qml.classical_shadow`

`qml.shadow_expval`

. (#4803)Removed an implicit assumption that an empty

`PauliSentence`

gets treated as identity under multiplication. (#4887)Using a

`CNOT`

or`PauliZ`

operation with large batched states and the Tensorflow interface no longer raises an unexpected error. (#4889)`qml.map_wires`

no longer fails when mapping nested quantum tapes. (#4901)Conversion of circuits to openqasm now decomposes to a depth of 10, allowing support for operators requiring more than 2 iterations of decomposition, such as the

`ApproxTimeEvolution`

gate. (#4951)`MPLDrawer`

does not add the bonus space for classical wires when no classical wires are present. (#4987)`Projector`

now works with parameter-broadcasting. (#4993)The jax-jit interface can now be used with float32 mode. (#4990)

Keras models with a

`qnn.KerasLayer`

no longer fail to save and load weights properly when they are named “weights”. (#5008)

### Contributors ✍️

This release contains contributions from (in alphabetical order):

Guillermo Alonso, Ali Asadi, Utkarsh Azad, Gabriel Bottrill, Thomas Bromley, Astral Cai, Minh Chau, Isaac De Vlugt, Amintor Dusko, Pieter Eendebak, Lillian Frederiksen, Pietropaolo Frisoni, Josh Izaac, Juan Giraldo, Emiliano Godinez Ramirez, Ankit Khandelwal, Korbinian Kottmann, Christina Lee, Vincent Michaud-Rioux, Anurav Modak, Romain Moyard, Mudit Pandey, Matthew Silverman, Jay Soni, David Wierichs, Justin Woodring, Sergei Mironov.

- orphan

## Release 0.33.1¶

### Bug fixes 🐛

Fix gradient performance regression due to expansion of VJP products. (#4806)

`qml.defer_measurements`

now correctly transforms circuits when terminal measurements include wires used in mid-circuit measurements. (#4787)Any

`ScalarSymbolicOp`

, like`Evolution`

, now states that it has a matrix if the target is a`Hamiltonian`

. (#4768)In

`default.qubit`

, initial states are now initialized with the simulator’s wire order, not the circuit’s wire order. (#4781)

### Contributors ✍️

This release contains contributions from (in alphabetical order):

Christina Lee, Lee James O’Riordan, Mudit Pandey

- orphan

## Release 0.33.0¶

### New features since last release

#### Postselection and statistics in mid-circuit measurements 📌

It is now possible to request postselection on a mid-circuit measurement. (#4604)

This can be achieved by specifying the

`postselect`

keyword argument in`qml.measure`

as either`0`

or`1`

, corresponding to the basis states.import pennylane as qml dev = qml.device("default.qubit") @qml.qnode(dev, interface=None) def circuit(): qml.Hadamard(wires=0) qml.CNOT(wires=[0, 1]) qml.measure(0, postselect=1) return qml.expval(qml.PauliZ(1)), qml.sample(wires=1)

This circuit prepares the \(| \Phi^{+} \rangle\) Bell state and postselects on measuring \(|1\rangle\) in wire

`0`

. The output of wire`1`

is then also \(|1\rangle\) at all times:>>> circuit(shots=10) (-1.0, array([1, 1, 1, 1, 1, 1]))

Note that the number of shots is less than the requested amount because we have thrown away the samples where \(|0\rangle\) was measured in wire

`0`

.Measurement statistics can now be collected for mid-circuit measurements. (#4544)

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

>>> circ(1.0, 2.0, shots=10000) (0.5606, 0.7089, array([0, 1, 1, ..., 1, 1, 1]))

Support is provided for both finite-shot and analytic modes and devices default to using the deferred measurement principle to enact the mid-circuit measurements.

#### Exponentiate Hamiltonians with flexible Trotter products 🐖

Higher-order Trotter-Suzuki methods are now easily accessible through a new operation called

`TrotterProduct`

. (#4661)Trotterization techniques are an affective route towards accurate and efficient Hamiltonian simulation. The Suzuki-Trotter product formula allows for the ability to express higher-order approximations to the matrix exponential of a Hamiltonian, and it is now available to use in PennyLane via the

`TrotterProduct`

operation. Simply specify the`order`

of the approximation and the evolution`time`

.coeffs = [0.25, 0.75] ops = [qml.PauliX(0), qml.PauliZ(0)] H = qml.dot(coeffs, ops) dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(): qml.Hadamard(0) qml.TrotterProduct(H, time=2.4, order=2) return qml.state()

>>> circuit() [-0.13259524+0.59790098j 0. +0.j -0.13259524-0.77932754j 0. +0.j ]

Approximating matrix exponentiation with random product formulas, qDrift, is now available with the new

`QDrift`

operation. (#4671)As shown in 1811.08017, qDrift is a Markovian process that can provide a speedup in Hamiltonian simulation. At a high level, qDrift works by randomly sampling from the Hamiltonian terms with a probability that depends on the Hamiltonian coefficients. This method for Hamiltonian simulation is now ready to use in PennyLane with the

`QDrift`

operator. Simply specify the evolution`time`

and the number of samples drawn from the Hamiltonian,`n`

:coeffs = [0.25, 0.75] ops = [qml.PauliX(0), qml.PauliZ(0)] H = qml.dot(coeffs, ops) dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(): qml.Hadamard(0) qml.QDrift(H, time=1.2, n = 10) return qml.probs()

>>> circuit() array([0.61814334, 0. , 0.38185666, 0. ])

#### Building blocks for quantum phase estimation 🧱

A new operator called

`CosineWindow`

has been added to prepare an initial state based on a cosine wave function. (#4683)As outlined in 2110.09590, the cosine tapering window is part of a modification to quantum phase estimation that can provide a cubic improvement to the algorithm’s error rate. Using

`CosineWindow`

will prepare a state whose amplitudes follow a cosinusoidal distribution over the computational basis.import matplotlib.pyplot as plt dev = qml.device('default.qubit', wires=4) @qml.qnode(dev) def example_circuit(): qml.CosineWindow(wires=range(4)) return qml.state() output = example_circuit() plt.style.use("pennylane.drawer.plot") plt.bar(range(len(output)), output) plt.show()

Controlled gate sequences raised to decreasing powers, a sub-block in quantum phase estimation, can now be created with the new

`ControlledSequence`

operator. (#4707)To use

`ControlledSequence`

, specify the controlled unitary operator and the control wires,`control`

:dev = qml.device("default.qubit", wires = 4) @qml.qnode(dev) def circuit(): for i in range(3): qml.Hadamard(wires = i) qml.ControlledSequence(qml.RX(0.25, wires = 3), control = [0, 1, 2]) qml.adjoint(qml.QFT)(wires = range(3)) return qml.probs(wires = range(3))

>>> print(circuit()) [0.92059345 0.02637178 0.00729619 0.00423258 0.00360545 0.00423258 0.00729619 0.02637178]

#### New device capabilities, integration with Catalyst, and more! ⚗️

`default.qubit`

now uses the new`qml.devices.Device`

API and functionality in`qml.devices.qubit`

. If you experience any issues with the updated`default.qubit`

, please let us know by posting an issue. The old version of the device is still accessible by the short name`default.qubit.legacy`

, or directly via`qml.devices.DefaultQubitLegacy`

. (#4594) (#4436) (#4620) (#4632)This changeover has a number of benefits for

`default.qubit`

, including:The number of wires is now optional — simply having

`qml.device("default.qubit")`

is valid! If wires are not provided at instantiation, the device automatically infers the required number of wires for each circuit provided for execution.dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(): qml.PauliZ(0) qml.RZ(0.1, wires=1) qml.Hadamard(2) return qml.state()

>>> print(qml.draw(circuit)()) 0: ──Z────────┤ State 1: ──RZ(0.10)─┤ State 2: ──H────────┤ State

`default.qubit`

is no longer silently swapped out with an interface-appropriate device when the backpropagation differentiation method is used. For example, consider:import jax dev = qml.device("default.qubit", wires=1) @qml.qnode(dev, diff_method="backprop") def f(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) f(jax.numpy.array(0.2))

In previous versions of PennyLane, the device will be swapped for the JAX equivalent:

>>> f.device <DefaultQubitJax device (wires=1, shots=None) at 0x7f8c8bff50a0> >>> f.device == dev False

Now,

`default.qubit`

can itself dispatch to all the interfaces in a backprop-compatible way and hence does not need to be swapped:>>> f.device <default.qubit device (wires=1) at 0x7f20d043b040> >>> f.device == dev True

A QNode that has been decorated with

`qjit`

from PennyLane’s Catalyst library for just-in-time hybrid compilation is now compatible with`qml.draw`

. (#4609)import catalyst @catalyst.qjit @qml.qnode(qml.device("lightning.qubit", wires=3)) def circuit(x, y, z, c): """A quantum circuit on three wires.""" @catalyst.for_loop(0, c, 1) def loop(i): qml.Hadamard(wires=i) qml.RX(x, wires=0) loop() qml.RY(y, wires=1) qml.RZ(z, wires=2) return qml.expval(qml.PauliZ(0)) draw = qml.draw(circuit, decimals=None)(1.234, 2.345, 3.456, 1)

>>> print(draw) 0: ──RX──H──┤ <Z> 1: ──H───RY─┤ 2: ──RZ─────┤

### Improvements 🛠

#### More PyTrees!

`MeasurementProcess`

and`QuantumScript`

objects are now registered as JAX PyTrees. (#4607) (#4608)It is now possible to JIT-compile functions with arguments that are a

`MeasurementProcess`

or a`QuantumScript`

:import jax tape0 = qml.tape.QuantumTape([qml.RX(1.0, 0), qml.RY(0.5, 0)], [qml.expval(qml.PauliZ(0))]) dev = qml.device('lightning.qubit', wires=5) execute_kwargs = {"device": dev, "gradient_fn": qml.gradients.param_shift, "interface":"jax"} jitted_execute = jax.jit(qml.execute, static_argnames=execute_kwargs.keys()) jitted_execute((tape0, ), **execute_kwargs)

#### Improving QChem and existing algorithms

Computationally expensive functions in

`integrals.py`

,`electron_repulsion`

and`_hermite_coulomb`

, have been modified to replace indexing with slicing for better compatibility with JAX. (#4685)`qml.qchem.import_state`

has been extended to import more quantum chemistry wavefunctions, from MPS, DMRG and SHCI classical calculations performed with the Block2 and Dice libraries. #4523 #4524 #4626 #4634Check out our how-to guide to learn more about how PennyLane integrates with your favourite quantum chemistry libraries.

The qchem

`fermionic_dipole`

and`particle_number`

functions have been updated to use a`FermiSentence`

. The deprecated features for using tuples to represent fermionic operations are removed. (#4546) (#4556)The tensor-network template

`qml.MPS`

now supports changing the`offset`

between subsequent blocks for more flexibility. (#4531)Builtin types support with

`qml.pauli_decompose`

have been improved. (#4577)`AmplitudeEmbedding`

now inherits from`StatePrep`

, allowing for it to not be decomposed when at the beginning of a circuit, thus behaving like`StatePrep`

. (#4583)`qml.cut_circuit`

is now compatible with circuits that compute the expectation values of Hamiltonians with two or more terms. (#4642)

#### Next-generation device API

`default.qubit`

now tracks the number of equivalent qpu executions and total shots when the device is sampling. Note that`"simulations"`

denotes the number of simulation passes, whereas`"executions"`

denotes how many different computational bases need to be sampled in. Additionally, the new`default.qubit`

tracks the results of`device.execute`

. (#4628) (#4649)`DefaultQubit`

can now accept a`jax.random.PRNGKey`

as a`seed`

to set the key for the JAX pseudo random number generator when using the JAX interface. This corresponds to the`prng_key`

on`default.qubit.jax`

in the old API. (#4596)The

`JacobianProductCalculator`

abstract base class and implementations`TransformJacobianProducts`

`DeviceDerivatives`

, and`DeviceJacobianProducts`

have been added to`pennylane.interfaces.jacobian_products`

. (#4435) (#4527) (#4637)`DefaultQubit`

dispatches to a faster implementation for applying`ParametrizedEvolution`

to a state when it is more efficient to evolve the state than the operation matrix. (#4598) (#4620)Wires can be provided to the new device API. (#4538) (#4562)

`qml.sample()`

in the new device API now returns a`np.int64`

array instead of`np.bool8`

. (#4539)The new device API now has a

`repr()`

method. (#4562)`DefaultQubit`

now works as expected with measurement processes that don’t specify wires. (#4580)Various improvements to measurements have been made for feature parity between

`default.qubit.legacy`

and the new`DefaultQubit`

. This includes not trying to squeeze batched`CountsMP`

results and implementing`MutualInfoMP.map_wires`

. (#4574)`devices.qubit.simulate`

now accepts an interface keyword argument. If a QNode with`DefaultQubit`

specifies an interface, the result will be computed with that interface. (#4582)`ShotAdaptiveOptimizer`

has been updated to pass shots to QNode executions instead of overriding device shots before execution. This makes it compatible with the new device API. (#4599)`pennylane.devices.preprocess`

now offers the transforms`decompose`

,`validate_observables`

,`validate_measurements`

,`validate_device_wires`

,`validate_multiprocessing_workers`

,`warn_about_trainable_observables`

, and`no_sampling`

to assist in constructing devices under the new device API. (#4659)Updated

`qml.device`

,`devices.preprocessing`

and the`tape_expand.set_decomposition`

context manager to bring`DefaultQubit`

to feature parity with`default.qubit.legacy`

with regards to using custom decompositions. The`DefaultQubit`

device can now be included in a`set_decomposition`

context or initialized with a`custom_decomps`

dictionary, as well as a custom`max_depth`

for decomposition. (#4675)

#### Other improvements

The

`StateMP`

measurement now accepts a wire order (e.g., a device wire order). The`process_state`

method will re-order the given state to go from the inputted wire-order to the process’s wire-order. If the process’s wire-order contains extra wires, it will assume those are in the zero-state. (#4570) (#4602)Methods called

`add_transform`

and`insert_front_transform`

have been added to`TransformProgram`

. (#4559)Instances of the

`TransformProgram`

class can now be added together. (#4549)Transforms can now be applied to devices following the new device API. (#4667)

All gradient transforms have been updated to the new transform program system. (#4595)

Multi-controlled operations with a single-qubit special unitary target can now automatically decompose. (#4697)

`pennylane.defer_measurements`

will now exit early if the input does not contain mid circuit measurements. (#4659)The density matrix aspects of

`StateMP`

have been split into their own measurement process called`DensityMatrixMP`

. (#4558)`StateMeasurement.process_state`

now assumes that the input is flat.`ProbabilityMP.process_state`

has been updated to reflect this assumption and avoid redundant reshaping. (#4602)`qml.exp`

returns a more informative error message when decomposition is unavailable for non-unitary operators. (#4571)Added

`qml.math.get_deep_interface`

to get the interface of a scalar hidden deep in lists or tuples. (#4603)Updated

`qml.math.ndim`

and`qml.math.shape`

to work with built-in lists or tuples that contain interface-specific scalar dat (e.g.,`[(tf.Variable(1.1), tf.Variable(2.2))]`

). (#4603)When decomposing a unitary matrix with

`one_qubit_decomposition`

and opting to include the`GlobalPhase`

in the decomposition, the phase is no longer cast to`dtype=complex`

. (#4653)`_qfunc_output`

has been removed from`QuantumScript`

, as it is no longer necessary. There is still a`_qfunc_output`

property on`QNode`

instances. (#4651)`qml.data.load`

properly handles parameters that come after`'full'`

(#4663)The

`qml.jordan_wigner`

function has been modified to optionally remove the imaginary components of the computed qubit operator, if imaginary components are smaller than a threshold. (#4639)`qml.data.load`

correctly performs a full download of the dataset after a partial download of the same dataset has already been performed. (#4681)The performance of

`qml.data.load()`

has been improved when partially loading a dataset (#4674)Plots generated with the

`pennylane.drawer.plot`

style of`matplotlib.pyplot`

now have black axis labels and are generated at a default DPI of 300. (#4690)Shallow copies of the

`QNode`

now also copy the`execute_kwargs`

and transform program. When applying a transform to a`QNode`

, the new qnode is only a shallow copy of the original and thus keeps the same device. (#4736)`QubitDevice`

and`CountsMP`

are updated to disregard samples containing failed hardware measurements (record as`np.NaN`

) when tallying samples, rather than counting failed measurements as ground-state measurements, and to display`qml.counts`

coming from these hardware devices correctly. (#4739)

### Breaking changes 💔

`qml.defer_measurements`

now raises an error if a transformed circuit measures`qml.probs`

,`qml.sample`

, or`qml.counts`

without any wires or observable, or if it measures`qml.state`

. (#4701)The device test suite now converts device keyword arguments to integers or floats if possible. (#4640)

`MeasurementProcess.eigvals()`

now raises an`EigvalsUndefinedError`

if the measurement observable does not have eigenvalues. (#4544)The

`__eq__`

and`__hash__`

methods of`Operator`

and`MeasurementProcess`

no longer rely on the object’s address in memory. Using`==`

with operators and measurement processes will now behave the same as`qml.equal`

, and objects of the same type with the same data and hyperparameters will have the same hash. (#4536)In the following scenario, the second and third code blocks show the previous and current behaviour of operator and measurement process equality, determined by

`==`

:op1 = qml.PauliX(0) op2 = qml.PauliX(0) op3 = op1

Old behaviour:

>>> op1 == op2 False >>> op1 == op3 True

New behaviour:

>>> op1 == op2 True >>> op1 == op3 True

The

`__hash__`

dunder method defines the hash of an object. The default hash of an object is determined by the objects memory address. However, the new hash is determined by the properties and attributes of operators and measurement processes. Consider the scenario below. The second and third code blocks show the previous and current behaviour.op1 = qml.PauliX(0) op2 = qml.PauliX(0)

Old behaviour:

>>> print({op1, op2}) {PauliX(wires=[0]), PauliX(wires=[0])}

New behaviour:

>>> print({op1, op2}) {PauliX(wires=[0])}

The old return type and associated functions

`qml.enable_return`

and`qml.disable_return`

have been removed. (#4503)The

`mode`

keyword argument in`QNode`

has been removed. Please use`grad_on_execution`

instead. (#4503)The CV observables

`qml.X`

and`qml.P`

have been removed. Please use`qml.QuadX`

and`qml.QuadP`

instead. (#4533)The

`sampler_seed`

argument of`qml.gradients.spsa_grad`

has been removed. Instead, the`sampler_rng`

argument should be set, either to an integer value, which will be used to create a PRNG internally, or to a NumPy pseudo-random number generator (PRNG) created via`np.random.default_rng(seed)`

. (#4550)The

`QuantumScript.set_parameters`

method and the`QuantumScript.data`

setter have been removed. Please use`QuantumScript.bind_new_parameters`

instead. (#4548)The method

`tape.unwrap()`

and corresponding`UnwrapTape`

and`Unwrap`

classes have been removed. Instead of`tape.unwrap()`

, use`qml.transforms.convert_to_numpy_parameters`

. (#4535)The

`RandomLayers.compute_decomposition`

keyword argument`ratio_imprivitive`

has been changed to`ratio_imprim`

to match the call signature of the operation. (#4552)The private

`TmpPauliRot`

operator used for`SpecialUnitary`

no longer decomposes to nothing when the theta value is trainable. (#4585)`ProbabilityMP.marginal_prob`

has been removed. Its contents have been moved into`process_state`

, which effectively just called`marginal_prob`

with`np.abs(state) ** 2`

. (#4602)

### Deprecations 👋

The following decorator syntax for transforms has been deprecated and will raise a warning: (#4457)

@transform_fn(**transform_kwargs) @qml.qnode(dev) def circuit(): ...

If you are using a transform that has supporting

`transform_kwargs`

, please call the transform directly using`circuit = transform_fn(circuit, **transform_kwargs)`

, or use`functools.partial`

:@functools.partial(transform_fn, **transform_kwargs) @qml.qnode(dev) def circuit(): ...

The

`prep`

keyword argument in`QuantumScript`

has been deprecated and will be removed from`QuantumScript`

.`StatePrepBase`

operations should be placed at the beginning of the`ops`

list instead. (#4554)`qml.gradients.pulse_generator`

has been renamed to`qml.gradients.pulse_odegen`

to adhere to paper naming conventions. During v0.33,`pulse_generator`

is still available but raises a warning. (#4633)

### Documentation 📝

A warning section in the docstring for

`DefaultQubit`

regarding the start method used in multiprocessing has been added. This may help users circumvent issues arising in Jupyter notebooks on macOS for example. (#4622)Documentation improvements to the new device API have been made. The documentation now correctly states that interface-specific parameters are only passed to the device for backpropagation derivatives. (#4542)

Functions for qubit-simulation to the

`qml.devices`

sub-page of the “Internal” section have been added. Note that these functions are unstable while device upgrades are underway. (#4555)A documentation improvement to the usage example in the

`qml.QuantumMonteCarlo`

page has been made. An integral was missing the differential \(dx\). (#4593)A documentation improvement for the use of the

`pennylane`

style of`qml.drawer`

and the`pennylane.drawer.plot`

style of`matplotlib.pyplot`

has been made by clarifying the use of the default font. (#4690)

### Bug fixes 🐛

Jax jit now works when a probability measurement is broadcasted onto all wires. (#4742)

Fixed

`LocalHilbertSchmidt.compute_decomposition`

so that the template can be used in a QNode. (#4719)Fixes

`transforms.transpile`

with arbitrary measurement processes. (#4732)Providing

`work_wires=None`

to`qml.GroverOperator`

no longer interprets`None`

as a wire. (#4668)Fixed an issue where the

`__copy__`

method of the`qml.Select()`

operator attempted to access un-initialized data. (#4551)Fixed the

`skip_first`

option in`expand_tape_state_prep`

. (#4564)`convert_to_numpy_parameters`

now uses`qml.ops.functions.bind_new_parameters`

. This reinitializes the operation and makes sure everything references the new NumPy parameters. (#4540)`tf.function`

no longer breaks`ProbabilityMP.process_state`

, which is needed by new devices. (#4470)Fixed unit tests for

`qml.qchem.mol_data`

. (#4591)Fixed

`ProbabilityMP.process_state`

so that it allows for proper Autograph compilation. Without this, decorating a QNode that returns an`expval`

with`tf.function`

would fail when computing the expectation. (#4590)The

`torch.nn.Module`

properties are now accessible on a`pennylane.qnn.TorchLayer`

. (#4611)`qml.math.take`

with Pytorch now returns`tensor[..., indices]`

when the user requests the last axis (`axis=-1`

). Without the fix, it would wrongly return`tensor[indices]`

. (#4605)Ensured the logging

`TRACE`

level works with gradient-free execution. (#4669)

### Contributors ✍️

This release contains contributions from (in alphabetical order):

Guillermo Alonso, Utkarsh Azad, Thomas Bromley, Isaac De Vlugt, Jack Brown, Stepan Fomichev, Joana Fraxanet, Diego Guala, Soran Jahangiri, Edward Jiang, Korbinian Kottmann, Ivana Kurečić Christina Lee, Lillian M. A. Frederiksen, Vincent Michaud-Rioux, Romain Moyard, Daniel F. Nino, Lee James O’Riordan, Mudit Pandey, Matthew Silverman, Jay Soni.

- orphan

## Release 0.32.0¶

### New features since last release

#### Encode matrices using a linear combination of unitaries ⛓️️

It is now possible to encode an operator

`A`

into a quantum circuit by decomposing it into a linear combination of unitaries using PREP (qml.StatePrep) and SELECT (qml.Select) routines. (#4431) (#4437) (#4444) (#4450) (#4506) (#4526)Consider an operator

`A`

composed of a linear combination of Pauli terms:>>> A = qml.PauliX(2) + 2 * qml.PauliY(2) + 3 * qml.PauliZ(2)

A decomposable block-encoding circuit can be created:

def block_encode(A, control_wires): probs = A.coeffs / np.sum(A.coeffs) state = np.pad(np.sqrt(probs, dtype=complex), (0, 1)) unitaries = A.ops qml.StatePrep(state, wires=control_wires) qml.Select(unitaries, control=control_wires) qml.adjoint(qml.StatePrep)(state, wires=control_wires)

>>> print(qml.draw(block_encode, show_matrices=False)(A, control_wires=[0, 1])) 0: ─╭|Ψ⟩─╭Select─╭|Ψ⟩†─┤ 1: ─╰|Ψ⟩─├Select─╰|Ψ⟩†─┤ 2: ──────╰Select───────┤

This circuit can be used as a building block within a larger QNode to perform algorithms such as QSVT and Hamiltonian simulation.

Decomposing a Hermitian matrix into a linear combination of Pauli words via

`qml.pauli_decompose`

is now faster and differentiable. (#4395) (#4479) (#4493)def find_coeffs(p): mat = np.array([[3, p], [p, 3]]) A = qml.pauli_decompose(mat) return A.coeffs

>>> import jax >>> from jax import numpy as np >>> jax.jacobian(find_coeffs)(np.array(2.)) Array([0., 1.], dtype=float32, weak_type=True)

#### Monitor PennyLane's inner workings with logging 📃

Python-native logging can now be enabled with

`qml.logging.enable_logging()`

. (#4377) (#4383)Consider the following code that is contained in

`my_code.py`

:import pennylane as qml qml.logging.enable_logging() # enables logging dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def f(x): qml.RX(x, wires=0) return qml.state() f(0.5)

Executing

`my_code.py`

with logging enabled will detail every step in PennyLane’s pipeline that gets used to run your code.$ python my_code.py [1967-02-13 15:18:38,591][DEBUG][<PID 8881:MainProcess>] - pennylane.qnode.__init__()::"Creating QNode(func=<function f at 0x7faf2a6fbaf0>, device=<DefaultQubit device (wires=2, shots=None) at 0x7faf2a689b50>, interface=auto, diff_method=best, expansion_strategy=gradient, max_expansion=10, grad_on_execution=best, mode=None, cache=True, cachesize=10000, max_diff=1, gradient_kwargs={}" ...

Additional logging configuration settings can be specified by modifying the contents of the logging configuration file, which can be located by running

`qml.logging.config_path()`

. Follow our logging docs page for more details!

#### More input states for quantum chemistry calculations ⚛️

Input states obtained from advanced quantum chemistry calculations can be used in a circuit. (#4427) (#4433) (#4461) (#4476) (#4505)

Quantum chemistry calculations rely on an initial state that is typically selected to be the trivial Hartree-Fock state. For molecules with a complicated electronic structure, using initial states obtained from affordable post-Hartree-Fock calculations helps to improve the efficiency of the quantum simulations. These calculations can be done with external quantum chemistry libraries such as PySCF.

It is now possible to import a PySCF solver object in PennyLane and extract the corresponding wave function in the form of a state vector that can be directly used in a circuit. First, perform your classical quantum chemistry calculations and then use the qml.qchem.import_state function to import the solver object and return a state vector.

```
>>> from pyscf import gto, scf, ci
>>> mol = gto.M(atom=[['H', (0, 0, 0)], ['H', (0,0,0.71)]], basis='sto6g')
>>> myhf = scf.UHF(mol).run()
>>> myci = ci.UCISD(myhf).run()
>>> wf_cisd = qml.qchem.import_state(myci, tol=1e-1)
>>> print(wf_cisd)
[ 0. +0.j 0. +0.j 0. +0.j 0.1066467 +0.j
1. +0.j 0. +0.j 0. +0.j 0. +0.j
2. +0.j 0. +0.j 0. +0.j 0. +0.j
-0.99429698+0.j 0. +0.j 0. +0.j 0. +0.j]
The state vector can be implemented in a circuit using ``qml.StatePrep``.
```

```
>>> dev = qml.device('default.qubit', wires=4)
>>> @qml.qnode(dev)
... def circuit():
... qml.StatePrep(wf_cisd, wires=range(4))
... return qml.state()
>>> print(circuit())
[ 0. +0.j 0. +0.j 0. +0.j 0.1066467 +0.j
1. +0.j 0. +0.j 0. +0.j 0. +0.j
2. +0.j 0. +0.j 0. +0.j 0. +0.j
-0.99429698+0.j 0. +0.j 0. +0.j 0. +0.j]
The currently supported post-Hartree-Fock methods are RCISD, UCISD, RCCSD, and UCCSD which
denote restricted (R) and unrestricted (U) configuration interaction (CI) and coupled cluster (CC)
calculations with single and double (SD) excitations.
```

#### Reuse and reset qubits after mid-circuit measurements ♻️

PennyLane now allows you to define circuits that reuse a qubit after a mid-circuit measurement has taken place. Optionally, the wire can also be reset to the \(|0\rangle\) state. (#4402) (#4432)

Post-measurement reset can be activated by setting

`reset=True`

when calling qml.measure. In this version of PennyLane, executing circuits with qubit reuse will result in the defer_measurements transform being applied. This transform replaces each reused wire with an*additional*qubit. However, future releases of PennyLane will explore device-level support for qubit reuse without consuming additional qubits.Qubit reuse and reset is also fully differentiable:

dev = qml.device("default.qubit", wires=4) @qml.qnode(dev) def circuit(p): qml.RX(p, wires=0) m = qml.measure(0, reset=True) qml.cond(m, qml.Hadamard)(1) qml.RX(p, wires=0) m = qml.measure(0) qml.cond(m, qml.Hadamard)(1) return qml.expval(qml.PauliZ(1))

>>> jax.grad(circuit)(0.4) Array(-0.35867804, dtype=float32, weak_type=True)

You can read more about mid-circuit measurements in the documentation, and stay tuned for more mid-circuit measurement features in the next few releases!

### Improvements 🛠

#### A new PennyLane drawing style

Circuit drawings and plots can now be created following a PennyLane style. (#3950)

The

`qml.draw_mpl`

function accepts a`style='pennylane'`

argument to create PennyLane themed circuit diagrams: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)) qml.draw_mpl(circuit, style="pennylane")(1, 1)

PennyLane-styled plots can also be drawn by passing

`"pennylane.drawer.plot"`

to Matplotlib’s`plt.style.use`

function:import matplotlib.pyplot as plt plt.style.use("pennylane.drawer.plot") for i in range(3): plt.plot(np.random.rand(10))

If the font Quicksand Bold isn’t available, an available default font is used instead.

#### Making operators immutable and PyTrees

Any class inheriting from

`Operator`

is now automatically registered as a pytree with JAX. This unlocks the ability to jit functions of`Operator`

. (#4458)>>> op = qml.adjoint(qml.RX(1.0, wires=0)) >>> jax.jit(qml.matrix)(op) Array([[0.87758255-0.j , 0. +0.47942555j], [0. +0.47942555j, 0.87758255-0.j ]], dtype=complex64, weak_type=True) >>> jax.tree_util.tree_map(lambda x: x+1, op) Adjoint(RX(2.0, wires=[0]))

All

`Operator`

objects now define`Operator._flatten`

and`Operator._unflatten`

methods that separate trainable from untrainable components. These methods will be used in serialization and pytree registration. Custom operations may need an update to ensure compatibility with new PennyLane features. (#4483) (#4314)The

`QuantumScript`

class now has a`bind_new_parameters`

method that allows creation of new`QuantumScript`

objects with the provided parameters. (#4345)The

`qml.gradients`

module no longer mutates operators in-place for any gradient transforms. Instead, operators that need to be mutated are copied with new parameters. (#4220)PennyLane no longer directly relies on

`Operator.__eq__`

. (#4398)`qml.equal`

no longer raises errors when operators or measurements of different types are compared. Instead, it returns`False`

. (#4315)

#### Transforms

Transform programs are now integrated with the QNode. (#4404)

def null_postprocessing(results: qml.typing.ResultBatch) -> qml.typing.Result: return results[0] @qml.transforms.core.transform def scale_shots(tape: qml.tape.QuantumTape, shot_scaling) -> (Tuple[qml.tape.QuantumTape], Callable): new_shots = tape.shots.total_shots * shot_scaling new_tape = qml.tape.QuantumScript(tape.operations, tape.measurements, shots=new_shots) return (new_tape, ), null_postprocessing dev = qml.devices.experimental.DefaultQubit2() @partial(scale_shots, shot_scaling=2) @qml.qnode(dev, interface=None) def circuit(): return qml.sample(wires=0)

>>> circuit(shots=1) array([False, False])

Transform Programs,

`qml.transforms.core.TransformProgram`

, can now be called on a batch of circuits and return a new batch of circuits and a single post processing function. (#4364)`TransformDispatcher`

now allows registration of custom QNode transforms. (#4466)QNode transforms in

`qml.qinfo`

now support custom wire labels. #4331`qml.transforms.adjoint_metric_tensor`

now uses the simulation tools in`qml.devices.qubit`

instead of private methods of`qml.devices.DefaultQubit`

. (#4456)Auxiliary wires and device wires are now treated the same way in

`qml.transforms.metric_tensor`

as in`qml.gradients.hadamard_grad`

. All valid wire input formats for`aux_wire`

are supported. (#4328)

#### Next-generation device API

The experimental device interface has been integrated with the QNode for JAX, JAX-JIT, TensorFlow and PyTorch. (#4323) (#4352) (#4392) (#4393)

The experimental

`DefaultQubit2`

device now supports computing VJPs and JVPs using the adjoint method. (#4374)New functions called

`adjoint_jvp`

and`adjoint_vjp`

that compute the JVP and VJP of a tape using the adjoint method have been added to`qml.devices.qubit.adjoint_jacobian`

(#4358)`DefaultQubit2`

now accepts a`max_workers`

argument which controls multiprocessing. A`ProcessPoolExecutor`

executes tapes asynchronously using a pool of at most`max_workers`

processes. If`max_workers`

is`None`

or not given, only the current process executes tapes. If you experience any issue, say using JAX, TensorFlow, Torch, try setting`max_workers`

to`None`

. (#4319) (#4425)`qml.devices.experimental.Device`

now accepts a shots keyword argument and has a`shots`

property. This property is only used to set defaults for a workflow, and does not directly influence the number of shots used in executions or derivatives. (#4388)`expand_fn()`

for`DefaultQubit2`

has been updated to decompose`StatePrep`

operations present in the middle of a circuit. (#4444)If no seed is specified on initialization with

`DefaultQubit2`

, the local random number generator will be seeded from NumPy’s global random number generator. (#4394)

#### Improvements to machine learning library interfaces

`pennylane/interfaces`

has been refactored. The`execute_fn`

passed to the machine learning framework boundaries is now responsible for converting parameters to NumPy. The gradients module can now handle TensorFlow parameters, but gradient tapes now retain the original`dtype`

instead of converting to`float64`

. This may cause instability with finite-difference differentiation and`float32`

parameters. The machine learning boundary functions are now uncoupled from their legacy counterparts. (#4415)`qml.interfaces.set_shots`

now accepts a`Shots`

object as well as`int`

‘s and tuples of`int`

‘s. (#4388)Readability improvements and stylistic changes have been made to

`pennylane/interfaces/jax_jit_tuple.py`

(#4379)

#### Pulses

A

`HardwareHamiltonian`

can now be summed with`int`

or`float`

objects. A sequence of`HardwareHamiltonian`

s can now be summed via the builtin`sum`

. (#4343)`qml.pulse.transmon_drive`

has been updated in accordance with 1904.06560. In particular, the functional form has been changed from \(\Omega(t)(\cos(\omega_d t + \phi) X - \sin(\omega_d t + \phi) Y)$ to $\Omega(t) \sin(\omega_d t + \phi) Y\). (#4418) (#4465) (#4478) (#4418)

#### Other improvements

The

`qchem`

module has been upgraded to use the fermionic operators of the`fermi`

module. #4336 #4521The calculation of

`Sum`

,`Prod`

,`SProd`

,`PauliWord`

, and`PauliSentence`

sparse matrices are orders of magnitude faster. (#4475) (#4272) (#4411)A function called

`qml.math.fidelity_statevector`

that computes the fidelity between two state vectors has been added. (#4322)`qml.ctrl(qml.PauliX)`

returns a`CNOT`

,`Toffoli`

, or`MultiControlledX`

operation instead of`Controlled(PauliX)`

. (#4339)When given a callable,

`qml.ctrl`

now does its custom pre-processing on all queued operators from the callable. (#4370)The

`qchem`

functions`primitive_norm`

and`contracted_norm`

have been modified to be compatible with higher versions of SciPy. The private function`_fac2`

for computing double factorials has also been added. #4321`tape_expand`

now uses`Operator.decomposition`

instead of`Operator.expand`

in order to make more performant choices. (#4355)CI now runs tests with TensorFlow 2.13.0 (#4472)

All tests in CI and pre-commit hooks now enable linting. (#4335)

The default label for a

`StatePrepBase`

operator is now`|Ψ⟩`

. (#4340)`Device.default_expand_fn()`

has been updated to decompose`qml.StatePrep`

operations present in the middle of a provided circuit. (#4437)`QNode.construct`

has been updated to only apply the`qml.defer_measurements`

transform if the device does not natively support mid-circuit measurements. (#4516)The application of the

`qml.defer_measurements`

transform has been moved from`QNode.construct`

to`qml.Device.batch_transform`

to allow more fine-grain control over when`defer_measurements`

should be used. (#4432)The label for

`ParametrizedEvolution`

can display parameters with the requested format as set by the kwarg`decimals`

. Array-like parameters are displayed in the same format as matrices and stored in the cache. (#4151)

### Breaking changes 💔

Applying gradient transforms to broadcasted/batched tapes has been deactivated until it is consistently supported for QNodes as well. (#4480)

Gradient transforms no longer implicitly cast

`float32`

parameters to`float64`

. Finite difference differentiation with`float32`

parameters may no longer give accurate results. (#4415)The

`do_queue`

keyword argument in`qml.operation.Operator`

has been removed. Instead of setting`do_queue=False`

, use the`qml.QueuingManager.stop_recording()`

context. (#4317)`Operator.expand`

now uses the output of`Operator.decomposition`

instead of what it queues. (#4355)The gradients module no longer needs shot information passed to it explicitly, as the shots are on the tapes. (#4448)

`qml.StatePrep`

has been renamed to`qml.StatePrepBase`

and`qml.QubitStateVector`

has been renamed to`qml.StatePrep`

.`qml.operation.StatePrep`

and`qml.QubitStateVector`

are still accessible. (#4450)Support for Python 3.8 has been dropped. (#4453)

`MeasurementValue`

‘s signature has been updated to accept a list of`MidMeasureMP`

‘s rather than a list of their IDs. (#4446)The

`grouping_type`

and`grouping_method`

keyword arguments have been removed from`qchem.molecular_hamiltonian`

. (#4301)`zyz_decomposition`

and`xyx_decomposition`

have been removed. Use`one_qubit_decomposition`

instead. (#4301)`LieAlgebraOptimizer`

has been removed. Use`RiemannianGradientOptimizer`

instead. (#4301)`Operation.base_name`

has been removed. (#4301)`QuantumScript.name`

has been removed. (#4301)`qml.math.reduced_dm`

has been removed. Use`qml.math.reduce_dm`

or`qml.math.reduce_statevector`

instead. (#4301)The

`qml.specs`

dictionary no longer supports direct key access to certain keys. (#4301)Instead, these quantities can be accessed as fields of the new

`Resources`

object saved under`specs_dict["resources"]`

:`num_operations`

is no longer supported, use`specs_dict["resources"].num_gates`

`num_used_wires`

is no longer supported, use`specs_dict["resources"].num_wires`

`gate_types`

is no longer supported, use`specs_dict["resources"].gate_types`

`gate_sizes`

is no longer supported, use`specs_dict["resources"].gate_sizes`

`depth`

is no longer supported, use`specs_dict["resources"].depth`

`qml.math.purity`

,`qml.math.vn_entropy`

,`qml.math.mutual_info`

,`qml.math.fidelity`

,`qml.math.relative_entropy`

, and`qml.math.max_entropy`

no longer support state vectors as input. (#4322)The private

`QuantumScript._prep`

list has been removed, and prep operations now go into the`_ops`

list. (#4485)

### Deprecations 👋

`qml.enable_return`

and`qml.disable_return`

have been deprecated. Please avoid calling`disable_return`

, as the old return system has been deprecated along with these switch functions. (#4316)`qml.qchem.jordan_wigner`

has been deprecated. Use`qml.jordan_wigner`

instead. List input to define the fermionic operator has also been deprecated; the fermionic operators in the`qml.fermi`

module should be used instead. (#4332)The

`qml.RandomLayers.compute_decomposition`

keyword argument`ratio_imprimitive`

will be changed to`ratio_imprim`

to match the call signature of the operation. (#4314)The CV observables

`qml.X`

and`qml.P`

have been deprecated. Use`qml.QuadX`

and`qml.QuadP`

instead. (#4330)The method

`tape.unwrap()`

and corresponding`UnwrapTape`

and`Unwrap`

classes have been deprecated. Use`convert_to_numpy_parameters`

instead. (#4344)The

`mode`

keyword argument in QNode has been deprecated, as it was only used in the old return system (which has also been deprecated). Please use`grad_on_execution`

instead. (#4316)The

`QuantumScript.set_parameters`

method and the`QuantumScript.data`

setter have been deprecated. Please use`QuantumScript.bind_new_parameters`

instead. (#4346)The

`__eq__`

and`__hash__`

dunder methods of`Operator`

and`MeasurementProcess`

will now raise warnings to reflect upcoming changes to operator and measurement process equality and hashing. (#4144) (#4454) (#4489) (#4498)The

`sampler_seed`

argument of`qml.gradients.spsa_grad`

has been deprecated, along with a bug fix of the seed-setting behaviour. Instead, the`sampler_rng`

argument should be set, either to an integer value, which will be used to create a PRNG internally or to a NumPy pseudo-random number generator created via`np.random.default_rng(seed)`

. (4165)

### Documentation 📝

The

`qml.pulse.transmon_interaction`

and`qml.pulse.transmon_drive`

documentation has been updated. #4327`qml.ApproxTimeEvolution.compute_decomposition()`

now has a code example. (#4354)The documentation for

`qml.devices.experimental.Device`

has been improved to clarify some aspects of its use. (#4391)Input types and sources for operators in

`qml.import_operator`

are specified. (#4476)

### Bug fixes 🐛

`qml.Projector`

is pickle-able again. (#4452)`_copy_and_shift_params`

does not cast or convert integral types, just relying on`+`

and`*`

‘s casting rules in this case. (#4477)Sparse matrix calculations of

`SProd`

s containing a`Tensor`

are now allowed. When using`Tensor.sparse_matrix()`

, it is recommended to use the`wire_order`

keyword argument over`wires`

. (#4424)`op.adjoint`

has been replaced with`qml.adjoint`

in`QNSPSAOptimizer`

. (#4421)`jax.ad`

(deprecated) has been replaced by`jax.interpreters.ad`

. (#4403)`metric_tensor`

stops accidentally catching errors that stem from flawed wires assignments in the original circuit, leading to recursion errors. (#4328)A warning is now raised if control indicators are hidden when calling

`qml.draw_mpl`

(#4295)`qml.qinfo.purity`

now produces correct results with custom wire labels. (#4331)`default.qutrit`

now supports all qutrit operations used with`qml.adjoint`

. (#4348)The observable data of

`qml.GellMann`

now includes its index, allowing correct comparison between instances of`qml.GellMann`

, as well as Hamiltonians and Tensors containing`qml.GellMann`

. (#4366)`qml.transforms.merge_amplitude_embedding`

now works correctly when the`AmplitudeEmbedding`

s have a batch dimension. (#4353)The

`jordan_wigner`

function has been modified to work with Hamiltonians built with an active space. (#4372)When a

`style`

option is not provided,`qml.draw_mpl`

uses the current style set from`qml.drawer.use_style`

instead of`black_white`

. (#4357)`qml.devices.qubit.preprocess.validate_and_expand_adjoint`

no longer sets the trainable parameters of the expanded tape. (#4365)`qml.default_expand_fn`

now selectively expands operations or measurements allowing more operations to be executed in circuits when measuring non-qwc Hamiltonians. (#4401)`qml.ControlledQubitUnitary`

no longer reports`has_decomposition`

as`True`

when it does not really have a decomposition. (#4407)`qml.transforms.split_non_commuting`

now correctly works on tapes containing both`expval`

and`var`

measurements. (#4426)Subtracting a

`Prod`

from another operator now works as expected. (#4441)The

`sampler_seed`

argument of`qml.gradients.spsa_grad`

has been changed to`sampler_rng`

. One can either provide an integer, which will be used to create a PRNG internally. Previously, this lead to the same direction being sampled, when`num_directions`

is greater than 1. Alternatively, one can provide a NumPy PRNG, which allows reproducibly calling`spsa_grad`

without getting the same results every time. (4165) (4482)`qml.math.get_dtype_name`

now works with autograd array boxes. (#4494)The backprop gradient of

`qml.math.fidelity`

is now correct. (#4380)

### Contributors ✍️

This release contains contributions from (in alphabetical order):

Utkarsh Azad, Thomas Bromley, Isaac De Vlugt, Amintor Dusko, Stepan Fomichev, Lillian M. A. Frederiksen, Soran Jahangiri, Edward Jiang, Korbinian Kottmann, Ivana Kurečić, Christina Lee, Vincent Michaud-Rioux, Romain Moyard, Lee James O’Riordan, Mudit Pandey, Borja Requena, Matthew Silverman, Jay Soni, David Wierichs, Frederik Wilde.

- orphan

## Release 0.31.0¶

### New features since last release

#### Seamlessly create and combine fermionic operators 🔬

Fermionic operators and arithmetic are now available. (#4191) (#4195) (#4200) (#4201) (#4209) (#4229) (#4253) (#4255) (#4262) (#4278)

There are a couple of ways to create fermionic operators with this new feature:

`qml.FermiC`

and`qml.FermiA`

: the fermionic creation and annihilation operators, respectively. These operators are defined by passing the index of the orbital that the fermionic operator acts on. For instance, the operators`a⁺(0)`

and`a(3)`

are respectively constructed as>>> qml.FermiC(0) a⁺(0) >>> qml.FermiA(3) a(3)

These operators can be composed with (

`*`

) and linearly combined with (`+`

and`-`

) other Fermi operators to create arbitrary fermionic Hamiltonians. Multiplying several Fermi operators together creates an operator that we call a Fermi word:>>> word = qml.FermiC(0) * qml.FermiA(0) * qml.FermiC(3) * qml.FermiA(3) >>> word a⁺(0) a(0) a⁺(3) a(3)

Fermi words can be linearly combined to create a fermionic operator that we call a Fermi sentence:

>>> sentence = 1.2 * word - 0.345 * qml.FermiC(3) * qml.FermiA(3) >>> sentence 1.2 * a⁺(0) a(0) a⁺(3) a(3) - 0.345 * a⁺(3) a(3)

via qml.fermi.from_string: create a fermionic operator that represents multiple creation and annihilation operators being multiplied by each other (a Fermi word).

>>> qml.fermi.from_string('0+ 1- 0+ 1-') a⁺(0) a(1) a⁺(0) a(1) >>> qml.fermi.from_string('0^ 1 0^ 1') a⁺(0) a(1) a⁺(0) a(1)

Fermi words created with

`from_string`

can also be linearly combined to create a Fermi sentence:>>> word1 = qml.fermi.from_string('0+ 0- 3+ 3-') >>> word2 = qml.fermi.from_string('3+ 3-') >>> sentence = 1.2 * word1 + 0.345 * word2 >>> sentence 1.2 * a⁺(0) a(0) a⁺(3) a(3) + 0.345 * a⁺(3) a(3)

Additionally, any fermionic operator, be it a single fermionic creation/annihilation operator, a Fermi word, or a Fermi sentence, can be mapped to the qubit basis by using qml.jordan_wigner:

>>> qml.jordan_wigner(sentence) ((0.4725+0j)*(Identity(wires=[0]))) + ((-0.4725+0j)*(PauliZ(wires=[3]))) + ((-0.3+0j)*(PauliZ(wires=[0]))) + ((0.3+0j)*(PauliZ(wires=[0]) @ PauliZ(wires=[3])))

Learn how to create fermionic Hamiltonians describing some simple chemical systems by checking out our fermionic operators demo!

#### Workflow-level resource estimation 🧮

PennyLane’s Tracker now monitors the resource requirements of circuits being executed by the device. (#4045) (#4110)

Suppose we have a workflow that involves executing circuits with different qubit numbers. We can obtain the resource requirements as a function of the number of qubits by executing the workflow with the

`Tracker`

context:dev = qml.device("default.qubit", wires=4) @qml.qnode(dev) def circuit(n_wires): for i in range(n_wires): qml.Hadamard(i) return qml.probs(range(n_wires)) with qml.Tracker(dev) as tracker: for i in range(1, 5): circuit(i)

The resource requirements of individual circuits can then be inspected as follows:

>>> resources = tracker.history["resources"] >>> resources[0] wires: 1 gates: 1 depth: 1 shots: Shots(total=None) gate_types: {'Hadamard': 1} gate_sizes: {1: 1} >>> [r.num_wires for r in resources] [1, 2, 3, 4]

Moreover, it is possible to predict the resource requirements without evaluating circuits using the

`null.qubit`

device, which follows the standard execution pipeline but returns numeric zeros. Consider the following workflow that takes the gradient of a`50`

-qubit circuit:n_wires = 50 dev = qml.device("null.qubit", wires=n_wires) weight_shape = qml.StronglyEntanglingLayers.shape(2, n_wires) weights = np.random.random(weight_shape, requires_grad=True) @qml.qnode(dev, diff_method="parameter-shift") def circuit(weights): qml.StronglyEntanglingLayers(weights, wires=range(n_wires)) return qml.expval(qml.PauliZ(0)) with qml.Tracker(dev) as tracker: qml.grad(circuit)(weights)

The tracker can be inspected to extract resource requirements without requiring a 50-qubit circuit run:

>>> tracker.totals {'executions': 451, 'batches': 2, 'batch_len': 451} >>> tracker.history["resources"][0] wires: 50 gates: 200 depth: 77 shots: Shots(total=None) gate_types: {'Rot': 100, 'CNOT': 100} gate_sizes: {1: 100, 2: 100}

Custom operations can now be constructed that solely define resource requirements — an explicit decomposition or matrix representation is not needed. (#4033)

PennyLane is now able to estimate the total resource requirements of circuits that include one or more of these operations, allowing you to estimate requirements for high-level algorithms composed of abstract subroutines.

These operations can be defined by inheriting from ResourcesOperation and overriding the

`resources()`

method to return an appropriate Resources object:class CustomOp(qml.resource.ResourcesOperation): def resources(self): n = len(self.wires) r = qml.resource.Resources( num_wires=n, num_gates=n ** 2, depth=5, ) return r

>>> wires = [0, 1, 2] >>> c = CustomOp(wires) >>> c.resources() wires: 3 gates: 9 depth: 5 shots: Shots(total=None) gate_types: {} gate_sizes: {}

A quantum circuit that contains

`CustomOp`

can be created and inspected using qml.specs:dev = qml.device("default.qubit", wires=wires) @qml.qnode(dev) def circ(): qml.PauliZ(wires=0) CustomOp(wires) return qml.state()

>>> specs = qml.specs(circ)() >>> specs["resources"].depth 6

#### Community contributions from UnitaryHack 🤝

ParametrizedHamiltonian now has an improved string representation. (#4176)

>>> def f1(p, t): return p[0] * jnp.sin(p[1] * t) >>> def f2(p, t): return p * t >>> coeffs = [2., f1, f2] >>> observables = [qml.PauliX(0), qml.PauliY(0), qml.PauliZ(0)] >>> qml.dot(coeffs, observables) (2.0*(PauliX(wires=[0]))) + (f1(params_0, t)*(PauliY(wires=[0]))) + (f2(params_1, t)*(PauliZ(wires=[0])))

The quantum information module now supports trace distance. (#4181)

Two cases are enabled for calculating the trace distance:

A QNode transform via qml.qinfo.trace_distance:

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

>>> trace_distance_circuit = qml.qinfo.trace_distance(circuit, circuit, wires0=[0], wires1=[0]) >>> x, y = np.array(0.4), np.array(0.6) >>> trace_distance_circuit((x,), (y,)) 0.047862689546603415

Flexible post-processing via qml.math.trace_distance:

>>> rho = np.array([[0.3, 0], [0, 0.7]]) >>> sigma = np.array([[0.5, 0], [0, 0.5]]) >>> qml.math.trace_distance(rho, sigma) 0.19999999999999998

It is now possible to prepare qutrit basis states with qml.QutritBasisState. (#4185)

wires = range(2) dev = qml.device("default.qutrit", wires=wires) @qml.qnode(dev) def qutrit_circuit(): qml.QutritBasisState([1, 1], wires=wires) qml.TAdd(wires=wires) return qml.probs(wires=1)

>>> qutrit_circuit() array([0., 0., 1.])

A new transform called one_qubit_decomposition has been added to provide a unified interface for decompositions of a single-qubit unitary matrix into sequences of X, Y, and Z rotations. All decompositions simplify the rotations angles to be between

`0`

and`4`

pi. (#4210) (#4246)>>> from pennylane.transforms import one_qubit_decomposition >>> U = np.array([[-0.28829348-0.78829734j, 0.30364367+0.45085995j], ... [ 0.53396245-0.10177564j, 0.76279558-0.35024096j]]) >>> one_qubit_decomposition(U, 0, "ZYZ") [RZ(tensor(12.32427531, requires_grad=True), wires=[0]), RY(tensor(1.14938178, requires_grad=True), wires=[0]), RZ(tensor(1.73305815, requires_grad=True), wires=[0])] >>> one_qubit_decomposition(U, 0, "XYX", return_global_phase=True) [RX(tensor(10.84535137, requires_grad=True), wires=[0]), RY(tensor(1.39749741, requires_grad=True), wires=[0]), RX(tensor(0.45246584, requires_grad=True), wires=[0]), (0.38469215914523336-0.9230449299422961j)*(Identity(wires=[0]))]

The

`has_unitary_generator`

attribute in`qml.ops.qubit.attributes`

no longer contains operators with non-unitary generators. (#4183)PennyLane Docker builds have been updated to include the latest plugins and interface versions. (#4178)

#### Extended support for differentiating pulses ⚛️

The stochastic parameter-shift gradient method can now be used with hardware-compatible Hamiltonians. (#4132) (#4215)

This new feature generalizes the stochastic parameter-shift gradient transform for pulses (

`stoch_pulse_grad`

) to support Hermitian generating terms beyond just Pauli words in pulse Hamiltonians, which makes it hardware-compatible.A new differentiation method called qml.gradients.pulse_generator is available, which combines classical processing with the parameter-shift rule for multivariate gates to differentiate pulse programs. Access it in your pulse programs by setting

`diff_method=qml.gradients.pulse_generator`

. (#4160)`qml.pulse.ParametrizedEvolution`

now uses*batched*compressed sparse row (`BCSR`

) format. This allows for computing Jacobians of the unitary directly even when`dense=False`

. (#4126)def U(params): H = jnp.polyval * qml.PauliZ(0) # time dependent Hamiltonian Um = qml.evolve(H, dense=False)(params, t=10.) return qml.matrix(Um) params = jnp.array([[0.5]], dtype=complex) jac = jax.jacobian(U, holomorphic=True)(params)

#### Broadcasting and other tweaks to Torch and Keras layers 🦾

The

`TorchLayer`

and`KerasLayer`

integrations with`torch.nn`

and`Keras`

have been upgraded. Consider the following`TorchLayer`

:n_qubits = 2 dev = qml.device("default.qubit", wires=n_qubits) @qml.qnode(dev) def qnode(inputs, weights): qml.AngleEmbedding(inputs, wires=range(n_qubits)) qml.BasicEntanglerLayers(weights, wires=range(n_qubits)) return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_qubits)] n_layers = 6 weight_shapes = {"weights": (n_layers, n_qubits)} qlayer = qml.qnn.TorchLayer(qnode, weight_shapes)

The following features are now available:

Native support for parameter broadcasting. (#4131)

>>> batch_size = 10 >>> inputs = torch.rand((batch_size, n_qubits)) >>> qlayer(inputs) >>> dev.num_executions == 1 True

The ability to draw a

`TorchLayer`

and`KerasLayer`

using`qml.draw()`

and`qml.draw_mpl()`

. (#4197)>>> print(qml.draw(qlayer, show_matrices=False)(inputs)) 0: ─╭AngleEmbedding(M0)─╭BasicEntanglerLayers(M1)─┤ <Z> 1: ─╰AngleEmbedding(M0)─╰BasicEntanglerLayers(M1)─┤ <Z>

Support for

`KerasLayer`

model saving and clearer instructions on`TorchLayer`

model saving. (#4149) (#4158)>>> torch.save(qlayer.state_dict(), "weights.pt") # Saving >>> qlayer.load_state_dict(torch.load("weights.pt")) # Loading >>> qlayer.eval()

Hybrid models containing

`KerasLayer`

or`TorchLayer`

objects can also be saved and loaded.

### Improvements 🛠

#### A more flexible projector

`qml.Projector`

now accepts a state vector representation, which enables the creation of projectors in any basis. (#4192)dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(state): return qml.expval(qml.Projector(state, wires=[0, 1])) zero_state = [0, 0] plusplus_state = np.array([1, 1, 1, 1]) / 2

>>> circuit(zero_state) tensor(1., requires_grad=True) >>> circuit(plusplus_state) tensor(0.25, requires_grad=True)

#### Do more with qutrits

Three qutrit rotation operators have been added that are analogous to

`RX`

,`RY`

, and`RZ`

:`qml.TRX`

: an X rotation`qml.TRY`

: a Y rotation`qml.TRZ`

: a Z rotation

Qutrit devices now support parameter-shift differentiation. (#2845)

#### The qchem module

`qchem.molecular_hamiltonian()`

,`qchem.qubit_observable()`

,`qchem.import_operator()`

, and`qchem.dipole_moment()`

now return an arithmetic operator if`enable_new_opmath()`

is active. (#4138) (#4159) (#4189) (#4204)Non-cubic lattice support for all electron resource estimation has been added. (3956)

The

`qchem.molecular_hamiltonian()`

function has been upgraded to support custom wires for constructing differentiable Hamiltonians. The zero imaginary component of the Hamiltonian coefficients have been removed. (#4050) (#4094)Jordan-Wigner transforms that cache Pauli gate objects have been accelerated. (#4046)

An error is now raised by

`qchem.molecular_hamiltonian`

when the`dhf`

method is used for an open-shell system. This duplicates a similar error in`qchem.Molecule`

but makes it clear that the`pyscf`

backend can be used for open-shell calculations. (#4058)Updated various qubit tapering methods to support operator arithmetic. (#4252)

#### Next-generation device API

The new device interface has been integrated with

`qml.execute`

for autograd, backpropagation, and no differentiation. (#3903)Support for adjoint differentiation has been added to the

`DefaultQubit2`

device. (#4037)A new function called

`measure_with_samples`

that returns a sample-based measurement result given a state has been added. (#4083) (#4093) (#4162) (#4254)`DefaultQubit2.preprocess`

now returns a new`ExecutionConfig`

object with decisions for`gradient_method`

,`use_device_gradient`

, and`grad_on_execution`

. (#4102)Support for sample-based measurements has been added to the

`DefaultQubit2`

device. (#4105) (#4114) (#4133) (#4172)The

`DefaultQubit2`

device now has a`seed`

keyword argument. (#4120)Added a

`dense`

keyword to`ParametrizedEvolution`

that allows forcing dense or sparse matrices. (#4079) (#4095) (#4285)Adds the Type variables

`pennylane.typing.Result`

and`pennylane.typing.ResultBatch`

for type hinting the result of an execution. (#4018)`qml.devices.ExecutionConfig`

no longer has a`shots`

property, as it is now on the`QuantumScript`

.

It now has a`use_device_gradient`

property.`ExecutionConfig.grad_on_execution = None`

indicates a request for`"best"`

, instead of a string. (#4102)The new device interface for Jax has been integrated with

`qml.execute`

. (#4137)The new device interface is now integrated with

`qml.execute`

for Tensorflow. (#4169)The experimental device

`DefaultQubit2`

now supports`qml.Snapshot`

. (#4193)The experimental device interface is integrated with the

`QNode`

. (#4196)The new device interface in integrated with

`qml.execute`

for Torch. (#4257)

#### Handling shots

`QuantumScript`

now has a`shots`

property, allowing shots to be tied to executions instead of devices. (#4067) (#4103) (#4106) (#4112)Several Python built-in functions are now properly defined for instances of the

`Shots`

class.`print`

: printing`Shots`

instances is now human-readable`str`

: converting`Shots`

instances to human-readable strings`==`

: equating two different`Shots`

instances`hash`

: obtaining the hash values of`Shots`

instances

`qml.devices.ExecutionConfig`

no longer has a`shots`

property, as it is now on the`QuantumScript`

. It now has a`use_device_gradient`

property.`ExecutionConfig.grad_on_execution = None`

indicates a request for`"best"`

instead of a string. (#4102)`QuantumScript.shots`

has been integrated with QNodes so that shots are placed on the`QuantumScript`

during`QNode`

construction. (#4110)The

`gradients`

module has been updated to use the new`Shots`

object internally (#4152)

#### Operators

`qml.prod`

now accepts a single quantum function input for creating new`Prod`

operators. (#4011)`DiagonalQubitUnitary`

now decomposes into`RZ`

,`IsingZZ`

and`MultiRZ`

gates instead of a`QubitUnitary`

operation with a dense matrix. (#4035)All objects being queued in an

`AnnotatedQueue`

are now wrapped so that`AnnotatedQueue`

is not dependent on the has of any operators or measurement processes. (#4087)A

`dense`

keyword to`ParametrizedEvolution`

that allows forcing dense or sparse matrices has been added. (#4079) (#4095)Added a new function

`qml.ops.functions.bind_new_parameters`

that creates a copy of an operator with new parameters without mutating the original operator. (#4113) (#4256)`qml.CY`

has been moved from`qml.ops.qubit.non_parametric_ops`

to`qml.ops.op_math.controlled_ops`

and now inherits from`qml.ops.op_math.ControlledOp`

. (#4116)`qml.CZ`

now inherits from the`ControlledOp`

class and supports exponentiation to arbitrary powers with`pow`

, which is no longer limited to integers. It also supports`sparse_matrix`

and`decomposition`

representations. (#4117)The construction of the Pauli representation for the

`Sum`

class is now faster. (#4142)`qml.drawer.drawable_layers.drawable_layers`

and`qml.CircuitGraph`

have been updated to not rely on`Operator`

equality or hash to work correctly. (#4143)

#### Other improvements

A transform dispatcher and program have been added. (#4109) (#4187)

Reduced density matrix functionality has been added via

`qml.math.reduce_dm`

and`qml.math.reduce_statevector`

. Both functions have broadcasting support. (#4173)The following functions in

`qml.qinfo`

now support parameter broadcasting:`reduced_dm`

`purity`

`vn_entropy`

`mutual_info`

`fidelity`

`relative_entropy`

`trace_distance`

The following functions in

`qml.math`

now support parameter broadcasting:`purity`

`vn_entropy`

`mutual_info`

`fidelity`

`relative_entropy`

`max_entropy`

`sqrt_matrix`

`pulse.ParametrizedEvolution`

now raises an error if the number of input parameters does not match the number of parametrized coefficients in the`ParametrizedHamiltonian`

that generates it. An exception is made for`HardwareHamiltonian`

s which are not checked. (#4216)The default value for the

`show_matrices`

keyword argument in all drawing methods is now`True`

. This allows for quick insights into broadcasted tapes, for example. (#3920)Type variables for

`qml.typing.Result`

and`qml.typing.ResultBatch`

have been added for type hinting the result of an execution. (#4108)The Jax-JIT interface now uses symbolic zeros to determine trainable parameters. (4075)

A new function called

`pauli.pauli_word_prefactor()`

that extracts the prefactor for a given Pauli word has been added. (#4164)Variable-length argument lists of functions and methods in some docstrings is now more clear. (#4242)

`qml.drawer.drawable_layers.drawable_layers`

and`qml.CircuitGraph`

have been updated to not rely on`Operator`

equality or hash to work correctly. (#4143)Drawing mid-circuit measurements connected by classical control signals to conditional operations is now possible. (#4228)

The autograd interface now submits all required tapes in a single batch on the backward pass. (#4245)

### Breaking changes 💔

The default value for the

`show_matrices`

keyword argument in all drawing methods is now`True`

. This allows for quick insights into broadcasted tapes, for example. (#3920)`DiagonalQubitUnitary`

now decomposes into`RZ`

,`IsingZZ`

, and`MultiRZ`

gates rather than a`QubitUnitary`

. (#4035)Jax trainable parameters are now

`Tracer`

instead of`JVPTracer`

. It is not always the right definition for the JIT interface, but we update them in the custom JVP using symbolic zeros. (4075)The experimental Device interface

`qml.devices.experimental.Device`

now requires that the`preprocess`

method also returns an`ExecutionConfig`

object. This allows the device to choose what`"best"`

means for various hyperparameters like`gradient_method`

and`grad_on_execution`

. (#4007) (#4102)Gradient transforms with Jax no longer support

`argnum`

. Use`argnums`

instead. (#4076)`qml.collections`

,`qml.op_sum`

, and`qml.utils.sparse_hamiltonian`

have been removed. (#4071)The

`pennylane.transforms.qcut`

module now uses`(op, id(op))`

as nodes in directed multigraphs that are used within the circuit cutting workflow instead of`op`

. This change removes the dependency of the module on the hash of operators. (#4227)`Operator.data`

now returns a`tuple`

instead of a`list`

. (#4222)The pulse differentiation methods,

`pulse_generator`

and`stoch_pulse_grad`

, now raise an error when they are applied to a QNode directly. Instead, use differentiation via a JAX entry point (`jax.grad`

,`jax.jacobian`

, …). (#4241)

### Deprecations 👋

`LieAlgebraOptimizer`

has been renamed to`RiemannianGradientOptimizer`

. [(#4153)(https://github.com/PennyLaneAI/pennylane/pull/4153)]`Operation.base_name`

has been deprecated. Please use`Operation.name`

or`type(op).__name__`

instead.`QuantumScript`

‘s`name`

keyword argument and property have been deprecated. This also affects`QuantumTape`

and`OperationRecorder`

. (#4141)The

`qml.grouping`

module has been removed. Its functionality has been reorganized in the`qml.pauli`

module.The public methods of

`DefaultQubit`

are pending changes to follow the new device API, as used in`DefaultQubit2`

. Warnings have been added to the docstrings to reflect this. (#4145)`qml.math.reduced_dm`

has been deprecated. Please use`qml.math.reduce_dm`

or`qml.math.reduce_statevector`

instead. (#4173)`qml.math.purity`

,`qml.math.vn_entropy`

,`qml.math.mutual_info`

,`qml.math.fidelity`

,`qml.math.relative_entropy`

, and`qml.math.max_entropy`

no longer support state vectors as input. Please call`qml.math.dm_from_state_vector`

on the input before passing to any of these functions. (#4186)The

`do_queue`

keyword argument in`qml.operation.Operator`

has been deprecated. Instead of setting`do_queue=False`

, use the`qml.QueuingManager.stop_recording()`

context. (#4148)`zyz_decomposition`

and`xyx_decomposition`

are now deprecated in favour of`one_qubit_decomposition`

. (#4230)

### Documentation 📝

The documentation is updated to construct

`QuantumTape`

upon initialization instead of with queuing. (#4243)The docstring for

`qml.ops.op_math.Pow.__new__`

is now complete and it has been updated along with`qml.ops.op_math.Adjoint.__new__`

. (#4231)The docstring for

`qml.grad`

now states that it should be used with the Autograd interface only. (#4202)The description of

`mult`

in the`qchem.Molecule`

docstring now correctly states the value of`mult`

that is supported. (#4058)

### Bug Fixes 🐛

Fixed adjoint jacobian results with

`grad_on_execution=False`

in the JAX-JIT interface. (4217)Fixed the matrix of

`SProd`

when the coefficient is tensorflow and the target matrix is not`complex128`

. (#4249)Fixed a bug where

`stoch_pulse_grad`

would ignore prefactors of rescaled Pauli words in the generating terms of a pulse Hamiltonian. (4156)Fixed a bug where the wire ordering of the

`wires`

argument to`qml.density_matrix`

was not taken into account. (#4072)A patch in

`interfaces/autograd.py`

that checks for the`strawberryfields.gbs`

device has been removed. That device is pinned to PennyLane <= v0.29.0, so that patch is no longer necessary. (#4089)`qml.pauli.are_identical_pauli_words`

now treats all identities as equal. Identity terms on Hamiltonians with non-standard wire orders are no longer eliminated. (#4161)`qml.pauli_sentence()`

is now compatible with empty Hamiltonians`qml.Hamiltonian([], [])`

. (#4171)Fixed a bug with Jax where executing multiple tapes with

`gradient_fn="device"`

would fail. (#4190)A more meaningful error message is raised when broadcasting with adjoint differentiation on

`DefaultQubit`

. (#4203)The

`has_unitary_generator`

attribute in`qml.ops.qubit.attributes`

no longer contains operators with non-unitary generators. (#4183)Fixed a bug where

`op = qml.qsvt()`

was incorrect up to a global phase when using`convention="Wx""`

and`qml.matrix(op)`

. (#4214)Fixed a buggy calculation of the angle in

`xyx_decomposition`

that causes it to give an incorrect decomposition. An`if`

conditional was intended to prevent divide by zero errors, but the division was by the sine of the argument. So, any multiple of $pi$ should trigger the conditional, but it was only checking if the argument was 0. Example:`qml.Rot(2.3, 2.3, 2.3)`

(#4210)Fixed bug that caused

`ShotAdaptiveOptimizer`

to truncate dimensions of parameter-distributed shots during optimization. (#4240)`Sum`

observables can now have trainable parameters. (#4251) (#4275)

### Contributors ✍️

This release contains contributions from (in alphabetical order):

Venkatakrishnan AnushKrishna, Utkarsh Azad, Thomas Bromley, Isaac De Vlugt, Lillian M. A. Frederiksen, Emiliano Godinez Ramirez Nikhil Harle Soran Jahangiri, Edward Jiang, Korbinian Kottmann, Christina Lee, Vincent Michaud-Rioux, Romain Moyard, Tristan Nemoz, Mudit Pandey, Manul Patel, Borja Requena, Modjtaba Shokrian-Zini, Mainak Roy, Matthew Silverman, Jay Soni, Edward Thomas, David Wierichs, Frederik Wilde.

- orphan

## Release 0.30.0¶

### New features since last release

#### Pulse programming on hardware ⚛️🔬

Support for loading time-dependent Hamiltonians that are compatible with quantum hardware has been added, making it possible to load a Hamiltonian that describes an ensemble of Rydberg atoms or a collection of transmon qubits. (#3749) (#3911) (#3930) (#3936) (#3966) (#3987) (#4021) (#4040)

Rydberg atoms are the foundational unit for neutral atom quantum computing. A Rydberg-system Hamiltonian can be constructed from a drive term —

`qml.pulse.rydberg_drive`

— and an interaction term —`qml.pulse.rydberg_interaction`

:from jax import numpy as jnp atom_coordinates = [[0, 0], [0, 4], [4, 0], [4, 4]] wires = [0, 1, 2, 3] amplitude = lambda p, t: p * jnp.sin(jnp.pi * t) phase = jnp.pi / 2 detuning = 3 * jnp.pi / 4 H_d = qml.pulse.rydberg_drive(amplitude, phase, detuning, wires) H_i = qml.pulse.rydberg_interaction(atom_coordinates, wires) H = H_d + H_i

The time-dependent Hamiltonian

`H`

can be used in a PennyLane pulse-level differentiable circuit:dev = qml.device("default.qubit.jax", wires=wires) @qml.qnode(dev, interface="jax") def circuit(params): qml.evolve(H)(params, t=[0, 10]) return qml.expval(qml.PauliZ(0))

>>> params = jnp.array([2.4]) >>> circuit(params) Array(0.6316659, dtype=float32) >>> import jax >>> jax.grad(circuit)(params) Array([1.3116529], dtype=float32)

The qml.pulse page contains additional details. Check out our release blog post for a demonstration of how to perform the execution on actual hardware!

A pulse-level circuit can now be differentiated using a stochastic parameter-shift method. (#3780) (#3900) (#4000) (#4004)

The new qml.gradient.stoch_pulse_grad differentiation method unlocks stochastic-parameter-shift differentiation for pulse-level circuits. The current version of this new method is restricted to Hamiltonians composed of parametrized Pauli words, but future updates to extend to parametrized Pauli sentences can allow this method to be compatible with hardware-based systems such as an ensemble of Rydberg atoms.

This method can be activated by setting

`diff_method`

to qml.gradient.stoch_pulse_grad:>>> dev = qml.device("default.qubit.jax", wires=2) >>> sin = lambda p, t: jax.numpy.sin(p * t) >>> ZZ = qml.PauliZ(0) @ qml.PauliZ(1) >>> H = 0.5 * qml.PauliX(0) + qml.pulse.constant * ZZ + sin * qml.PauliX(1) >>> @qml.qnode(dev, interface="jax", diff_method=qml.gradients.stoch_pulse_grad) >>> def ansatz(params): ... qml.evolve(H)(params, (0.2, 1.)) ... return qml.expval(qml.PauliY(1)) >>> params = [jax.numpy.array(0.4), jax.numpy.array(1.3)] >>> jax.grad(ansatz)(params) [Array(0.16921353, dtype=float32, weak_type=True), Array(-0.2537478, dtype=float32, weak_type=True)]

#### Quantum singular value transformation 🐛➡️🦋

PennyLane now supports the quantum singular value transformation (QSVT), which describes how a quantum circuit can be constructed to apply a polynomial transformation to the singular values of an input matrix. (#3756) (#3757) (#3758) (#3905) (#3909) (#3926) (#4023)

Consider a matrix

`A`

along with a vector`angles`

that describes the target polynomial transformation. The`qml.qsvt`

function creates a corresponding circuit:dev = qml.device("default.qubit", wires=2) A = np.array([[0.1, 0.2], [0.3, 0.4]]) angles = np.array([0.1, 0.2, 0.3]) @qml.qnode(dev) def example_circuit(A): qml.qsvt(A, angles, wires=[0, 1]) return qml.expval(qml.PauliZ(wires=0))

This circuit is composed of

`qml.BlockEncode`

and`qml.PCPhase`

operations.>>> example_circuit(A) tensor(0.97777078, requires_grad=True) >>> print(example_circuit.qtape.expand(depth=1).draw(decimals=2)) 0: ─╭∏_ϕ(0.30)─╭BlockEncode(M0)─╭∏_ϕ(0.20)─╭BlockEncode(M0)†─╭∏_ϕ(0.10)─┤ <Z> 1: ─╰∏_ϕ(0.30)─╰BlockEncode(M0)─╰∏_ϕ(0.20)─╰BlockEncode(M0)†─╰∏_ϕ(0.10)─┤

The qml.qsvt function creates a circuit that is targeted at simulators due to the use of matrix-based operations. For advanced users, you can use the operation-based

`qml.QSVT`

template to perform the transformation with a custom choice of unitary and projector operations, which may be hardware compatible if a decomposition is provided.The QSVT is a complex but powerful transformation capable of generalizing important algorithms like amplitude amplification. Stay tuned for a demo in the coming few weeks to learn more!

#### Intuitive QNode returns ↩️

An updated QNode return system has been introduced. PennyLane QNodes now return exactly what you tell them to! 🎉 (#3957) (#3969) (#3946) (#3913) (#3914) (#3934)

This was an experimental feature introduced in version 0.25 of PennyLane that was enabled via

`qml.enable_return()`

. Now, it’s the default return system. Let’s see how it works.Consider the following circuit:

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

In version 0.29 and earlier of PennyLane,

`circuit()`

would return a single length-3 array:>>> circuit(0.5) tensor([0.87758256, 0.93879128, 0.06120872], requires_grad=True)

In versions 0.30 and above,

`circuit()`

returns a length-2 tuple containing the expectation value and probabilities separately:>>> circuit(0.5) (tensor(0.87758256, requires_grad=True), tensor([0.93879128, 0.06120872], requires_grad=True))

You can find more details about this change, along with help and troubleshooting tips to solve any issues. If you still have questions, comments, or concerns, we encourage you to post on the PennyLane discussion forum.

#### A bunch of performance tweaks 🏃💨

Single-qubit operations that have multi-qubit control can now be decomposed more efficiently using fewer CNOT gates. (#3851)

Three decompositions from arXiv:2302.06377 are provided and compare favourably to the already-available

`qml.ops.ctrl_decomp_zyz`

:wires = [0, 1, 2, 3, 4, 5] control_wires = wires[1:] @qml.qnode(qml.device('default.qubit', wires=6)) def circuit(): with qml.QueuingManager.stop_recording(): # the decomposition does not un-queue the target target = qml.RX(np.pi/2, wires=0) qml.ops.ctrl_decomp_bisect(target, (1, 2, 3, 4, 5)) return qml.state() print(qml.draw(circuit, expansion_strategy="device")())

0: ──H─╭X──U(M0)─╭X──U(M0)†─╭X──U(M0)─╭X──U(M0)†──H─┤ State 1: ────├●────────│──────────├●────────│─────────────┤ State 2: ────├●────────│──────────├●────────│─────────────┤ State 3: ────╰●────────│──────────╰●────────│─────────────┤ State 4: ──────────────├●───────────────────├●────────────┤ State 5: ──────────────╰●───────────────────╰●────────────┤ State

A new decomposition to

`qml.SingleExcitation`

has been added that halves the number of CNOTs required. (3976)>>> qml.SingleExcitation.compute_decomposition(1.23, wires=(0,1)) [Adjoint(T(wires=[0])), Hadamard(wires=[0]), S(wires=[0]), Adjoint(T(wires=[1])), Adjoint(S(wires=[1])), Hadamard(wires=[1]), CNOT(wires=[1, 0]), RZ(-0.615, wires=[0]), RY(0.615, wires=[1]), CNOT(wires=[1, 0]), Adjoint(S(wires=[0])), Hadamard(wires=[0]), T(wires=[0]), Hadamard(wires=[1]), S(wires=[1]), T(wires=[1])]

The adjoint differentiation method can now be more efficient, avoiding the decomposition of operations that can be differentiated directly. Any operation that defines a

`generator()`

can be differentiated with the adjoint method. (#3874)For example, in version 0.29 the

`qml.CRY`

operation would be decomposed when calculating the adjoint-method gradient. Executing the code below shows that this decomposition no longer takes place in version 0.30 and`qml.CRY`

is differentiated directly:import jax from jax import numpy as jnp def compute_decomposition(self, phi, wires): print("A decomposition has been performed!") decomp_ops = [ qml.RY(phi / 2, wires=wires[1]), qml.CNOT(wires=wires), qml.RY(-phi / 2, wires=wires[1]), qml.CNOT(wires=wires), ] return decomp_ops qml.CRY.compute_decomposition = compute_decomposition dev = qml.device("default.qubit", wires=2) @qml.qnode(dev, diff_method="adjoint") def circuit(phi): qml.Hadamard(wires=0) qml.CRY(phi, wires=[0, 1]) return qml.expval(qml.PauliZ(1)) phi = jnp.array(0.5) jax.grad(circuit)(phi)

Derivatives are computed more efficiently when using

`jax.jit`

with gradient transforms; the trainable parameters are now set correctly instead of every parameter having to be set as trainable. (#3697)In the circuit below, only the derivative with respect to parameter

`b`

is now calculated:dev = qml.device("default.qubit", wires=2) @qml.qnode(dev, interface="jax-jit") def circuit(a, b): qml.RX(a, wires=0) qml.RY(b, wires=0) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)) a = jnp.array(0.4) b = jnp.array(0.5) jac = jax.jacobian(circuit, argnums=[1]) jac_jit = jax.jit(jac) jac_jit(a, b) assert len(circuit.tape.trainable_params) == 1

### Improvements 🛠

#### Next-generation device API

In this release and future releases, we will be making changes to our device API with the goal in mind to make developing plugins much easier for developers and unlock new device capabilities. Users shouldn’t yet feel any of these changes when using PennyLane, but here is what has changed this release:

Several functions in

`devices/qubit`

have been added or improved:`sample_state`

: returns a series of samples based on a given state vector and a number of shots. (#3720)`simulate`

: supports measuring expectation values of large observables such as`qml.Hamiltonian`

,`qml.SparseHamiltonian`

, and`qml.Sum`

. (#3759)`apply_operation`

: supports broadcasting. (#3852)`adjoint_jacobian`

: supports adjoint differentiation in the new qubit state-vector device. (#3790)

`qml.devices.qubit.preprocess`

now allows circuits with non-commuting observables. (#3857)`qml.devices.qubit.measure`

now computes the expectation values of`Hamiltonian`

and`Sum`

in a backpropagation-compatible way. (#3862)

#### Pulse programming

Here are the functions, classes, and more that were added or improved to facilitate simulating ensembles of Rydberg atoms: (#3749) (#3911) (#3930) (#3936) (#3966) (#3987) (#3889) (#4021)

`HardwareHamiltonian`

: an internal class that contains additional information about pulses and settings.`rydberg_interaction`

: a user-facing function that returns a`HardwareHamiltonian`

containing the Hamiltonian of the interaction of all the Rydberg atoms.`transmon_interaction`

: a user-facing function for constructing the Hamiltonian that describes the circuit QED interaction Hamiltonian of superconducting transmon systems.`drive`

: a user-facing function function that returns a`ParametrizedHamiltonian`

(`HardwareHamiltonian`

) containing the Hamiltonian of the interaction between a driving electro-magnetic field and a group of qubits.`rydberg_drive`

: a user-facing function that returns a`ParametrizedHamiltonian`

(`HardwareHamiltonian`

) containing the Hamiltonian of the interaction between a driving laser field and a group of Rydberg atoms.`max_distance`

: a keyword argument added to`qml.pulse.rydberg_interaction`

to allow for the removal of negligible contributions from atoms beyond`max_distance`

from each other.

`ParametrizedEvolution`

now takes two new Boolean keyword arguments:`return_intermediate`

and`complementary`

. They allow computing intermediate time evolution matrices. (#3900)Activating

`return_intermediate`

will return intermediate time evolution steps, for example for the matrix of the Operation, or of a quantum circuit when used in a QNode. Activating`complementary`

will make these intermediate steps be the*remaining*time evolution complementary to the output for`complementary=False`

. See the docstring for details.Hardware-compatible pulse sequence gradients with

`qml.gradient.stoch_pulse_grad`

can now be calculated faster using the new keyword argument`use_broadcasting`

. Executing a`ParametrizedEvolution`

that returns intermediate evolutions has increased performance using the state vector ODE solver, as well. (#4000) (#4004)

#### Intuitive QNode returns

The QNode keyword argument

`mode`

has been replaced by the boolean`grad_on_execution`

. (#3969)The

`"default.gaussian"`

device and parameter-shift CV both support the new return system, but only for single measurements. (#3946)Keras and Torch NN modules are now compatible with the new return type system. (#3913) (#3914)

`DefaultQutrit`

now supports the new return system. (#3934)

#### Performance improvements

The efficiency of

`tapering()`

,`tapering_hf()`

and`clifford()`

have been improved. (3942)The peak memory requirements of

`tapering()`

and`tapering_hf()`

have been improved when used for larger observables. (3977)Pauli arithmetic has been updated to convert to a Hamiltonian more efficiently. (#3939)

`Operator`

has a new Boolean attribute`has_generator`

. It returns whether or not the`Operator`

has a`generator`

defined.`has_generator`

is used in`qml.operation.has_gen`

, which improves its performance and extends differentiation support. (#3875)The performance of

`CompositeOp`

has been significantly improved now that it overrides determining whether it is being used with a batch of parameters (see`Operator._check_batching`

).`Hamiltonian`

also now overrides this, but it does nothing since it does not support batching. (#3915)The performance of a

`Sum`

operator has been significantly improved now that`is_hermitian`

checks that all coefficients are real if the operator has a pre-computed Pauli representation. (#3915)The

`coefficients`

function and the`visualize`

submodule of the`qml.fourier`

module now allow assigning different degrees for different parameters of the input function. (#3005)Previously, the arguments

`degree`

and`filter_threshold`

to`qml.fourier.coefficients`

were expected to be integers. Now, they can be a sequences of integers with one integer per function parameter (i.e.`len(degree)==n_inputs`

), resulting in a returned array with shape`(2*degrees[0]+1,..., 2*degrees[-1]+1)`

. The functions in`qml.fourier.visualize`

accordingly accept such arrays of coefficients.

#### Other improvements

A

`Shots`

class has been added to the`measurements`

module to hold shot-related data. (#3682)The custom JVP rules in PennyLane also now support non-scalar and mixed-shape tape parameters as well as multi-dimensional tape return types, like broadcasted

`qml.probs`

, for example. (#3766)The

`qchem.jordan_wigner`

function has been extended to support more fermionic operator orders. (#3754) (#3751)The

`AdaptiveOptimizer`

has been updated to use non-default user-defined QNode arguments. (#3765)Operators now use

`TensorLike`

types dunder methods. (#3749)`qml.QubitStateVector.state_vector`

now supports broadcasting. (#3852)`qml.SparseHamiltonian`

can now be applied to any wires in a circuit rather than being restricted to all wires in the circuit. (#3888)Operators can now be divided by scalars with

`/`

with the addition of the`Operation.__truediv__`

dunder method. (#3749)Printing an instance of

`MutualInfoMP`

now displays the distribution of the wires between the two subsystems. (#3898)`Operator.num_wires`

has been changed from an abstract value to`AnyWires`

. (#3919)`qml.transforms.sum_expand`

is not run in`Device.batch_transform`

if the device supports`Sum`

observables. (#3915)The type of

`n_electrons`

in`qml.qchem.Molecule`

has been set to`int`

. (#3885)Explicit errors have been added to

`QutritDevice`

if`classical_shadow`

or`shadow_expval`

is measured. (#3934)`QubitDevice`

now defines the private`_get_diagonalizing_gates(circuit)`

method and uses it when executing circuits. This allows devices that inherit from`QubitDevice`

to override and customize their definition of diagonalizing gates. (#3938)`retworkx`

has been renamed to`rustworkx`

to accommodate the change in the package name. (#3975)`Exp`

,`Sum`

,`Prod`

, and`SProd`

operator data is now a flat list instead of nested. (#3958) (#3983)`qml.transforms.convert_to_numpy_parameters`

has been added to convert a circuit with interface-specific parameters to one with only numpy parameters. This transform is designed to replace`qml.tape.Unwrap`

. (#3899)`qml.operation.WiresEnum.AllWires`

is now -2 instead of 0 to avoid the ambiguity between`op.num_wires = 0`

and`op.num_wires = AllWires`

. (#3978)Execution code has been updated to use the new

`qml.transforms.convert_to_numpy_parameters`

instead of`qml.tape.Unwrap`

. (#3989)A sub-routine of

`expand_tape`

has been converted into`qml.tape.tape.rotations_and_diagonal_measurements`

, a helper function that computes rotations and diagonal measurements for a tape with measurements with overlapping wires. (#3912)Various operators and templates have been updated to ensure that their decompositions only return lists of operators. (#3243)

The

`qml.operation.enable_new_opmath`

toggle has been introduced to cause dunder methods to return arithmetic operators instead of a`Hamiltonian`

or`Tensor`

. (#4008)>>> type(qml.PauliX(0) @ qml.PauliZ(1)) <class 'pennylane.operation.Tensor'> >>> qml.operation.enable_new_opmath() >>> type(qml.PauliX(0) @ qml.PauliZ(1)) <class 'pennylane.ops.op_math.prod.Prod'> >>> qml.operation.disable_new_opmath() >>> type(qml.PauliX(0) @ qml.PauliZ(1)) <class 'pennylane.operation.Tensor'>

A new data class called

`Resources`

has been added to store resources like the number of gates and circuit depth throughout a quantum circuit. (#3981)A new function called

`_count_resources()`

has been added to count the resources required when executing a`QuantumTape`

for a given number of shots. (#3996)`QuantumScript.specs`

has been modified to make use of the new`Resources`

class. This also modifies the output of`qml.specs()`

. (#4015)A new class called

`ResourcesOperation`

has been added to allow users to define operations with custom resource information. (#4026)For example, users can define a custom operation by inheriting from this new class:

>>> class CustomOp(qml.resource.ResourcesOperation): ... def resources(self): ... return qml.resource.Resources(num_wires=1, num_gates=2, ... gate_types={"PauliX": 2}) ... >>> CustomOp(wires=1) CustomOp(wires=[1])

Then, we can track and display the resources of the workflow using

`qml.specs()`

:>>> dev = qml.device("default.qubit", wires=[0,1]) >>> @qml.qnode(dev) ... def circ(): ... qml.PauliZ(wires=0) ... CustomOp(wires=1) ... return qml.state() ... >>> print(qml.specs(circ)()['resources']) wires: 2 gates: 3 depth: 1 shots: 0 gate_types: {'PauliZ': 1, 'PauliX': 2}

`MeasurementProcess.shape`

now accepts a`Shots`

object as one of its arguments to reduce exposure to unnecessary execution details. (#4012)

### Breaking changes 💔

The

`seed_recipes`

argument has been removed from`qml.classical_shadow`

and`qml.shadow_expval`

. (#4020)The tape method

`get_operation`

has an updated signature. (#3998)Both JIT interfaces are no longer compatible with JAX

`>0.4.3`

(we raise an error for those versions). (#3877)An operation that implements a custom

`generator`

method, but does not always return a valid generator, also has to implement a`has_generator`

property that reflects in which scenarios a generator will be returned. (#3875)Trainable parameters for the Jax interface are the parameters that are

`JVPTracer`

, defined by setting`argnums`

. Previously, all JAX tracers, including those used for JIT compilation, were interpreted to be trainable. (#3697)The keyword argument

`argnums`

is now used for gradient transforms using Jax instead of`argnum`

.`argnum`

is automatically converted to`argnums`

when using Jax and will no longer be supported in v0.31 of PennyLane. (#3697) (#3847)`qml.OrbitalRotation`

and, consequently,`qml.GateFabric`

are now more consistent with the interleaved Jordan-Wigner ordering. Previously, they were consistent with the sequential Jordan-Wigner ordering. (#3861)Some

`MeasurementProcess`

classes can now only be instantiated with arguments that they will actually use. For example, you can no longer create`StateMP(qml.PauliX(0))`

or`PurityMP(eigvals=(-1,1), wires=Wires(0))`

. (#3898)`Exp`

,`Sum`

,`Prod`

, and`SProd`

operator data is now a flat list, instead of nested. (#3958) (#3983)`qml.tape.tape.expand_tape`

and, consequentially,`QuantumScript.expand`

no longer update the input tape with rotations and diagonal measurements. Note that the newly expanded tape that is returned will still have the rotations and diagonal measurements. (#3912)`qml.Evolution`

now initializes the coefficient with a factor of`-1j`

instead of`1j`

. (#4024)

### Deprecations 👋

Nothing for this release!

### Documentation 📝

The documentation of

`QubitUnitary`

and`DiagonalQubitUnitary`

was clarified regarding the parameters of the operations. (#4031)A typo has been corrected in the documentation for the introduction to

`inspecting_circuits`

and`chemistry`

. (#3844)`Usage Details`

and`Theory`

sections have been separated in the documentation for`qml.qchem.taper_operation`

. (3977)

### Bug fixes 🐛

`ctrl_decomp_bisect`

and`ctrl_decomp_zyz`

are no longer used by default when decomposing controlled operations due to the presence of a global phase difference in the zyz decomposition of some target operators. (#4065)Fixed a bug where

`qml.math.dot`

returned a numpy array instead of an autograd array, breaking autograd derivatives in certain circumstances. (#4019)Operators now cast a

`tuple`

to an`np.ndarray`

as well as`list`

. (#4022)Fixed a bug where

`qml.ctrl`

with parametric gates was incompatible with PyTorch tensors on GPUs. (#4002)Fixed a bug where the broadcast expand results were stacked along the wrong axis for the new return system. (#3984)

A more informative error message is raised in

`qml.jacobian`

to explain potential problems with the new return types specification. (#3997)Fixed a bug where calling

`Evolution.generator`

with`coeff`

being a complex ArrayBox raised an error. (#3796)`MeasurementProcess.hash`

now uses the hash property of the observable. The property now depends on all properties that affect the behaviour of the object, such as`VnEntropyMP.log_base`

or the distribution of wires between the two subsystems in`MutualInfoMP`

. (#3898)The enum

`measurements.Purity`

has been added so that`PurityMP.return_type`

is defined.`str`

and`repr`

for`PurityMP`

are also now defined. (#3898)`Sum.hash`

and`Prod.hash`

have been changed slightly to work with non-numeric wire labels.`sum_expand`

should now return correct results and not treat some products as the same operation. (#3898)Fixed bug where the coefficients where not ordered correctly when summing a

`ParametrizedHamiltonian`

with other operators. (#3749) (#3902)The metric tensor transform is now fully compatible with Jax and therefore users can provide multiple parameters. (#3847)

`qml.math.ndim`

and`qml.math.shape`

are now registered for built-ins and autograd to accomodate Autoray 0.6.1. #3864Ensured that

`qml.data.load`

returns datasets in a stable and expected order. (#3856)The

`qml.equal`

function now handles comparisons of`ParametrizedEvolution`

operators. (#3870)`qml.devices.qubit.apply_operation`

catches the`tf.errors.UnimplementedError`

that occurs when`PauliZ`

or`CNOT`

gates are applied to a large (>8 wires) tensorflow state. When that occurs, the logic falls back to the tensordot logic instead. (#3884)Fixed parameter broadcasting support with

`qml.counts`

in most cases and introduced explicit errors otherwise. (#3876)An error is now raised if a QNode with Jax-jit in use returns

`counts`

while having trainable parameters (#3892)A correction has been added to the reference values in

`test_dipole_of`

to account for small changes (~`2e-8`

) in the computed dipole moment values resulting from the new PySCF 2.2.0 release. (#3908)`SampleMP.shape`

is now correct when sampling only occurs on a subset of the device wires. (#3921)An issue has been fixed in

`qchem.Molecule`

to allow basis sets other than the hard-coded ones to be used in the`Molecule`

class. (#3955)Fixed bug where all devices that inherit from

`DefaultQubit`

claimed to support`ParametrizedEvolution`

. Now, only`DefaultQubitJax`

supports the operator, as expected. (#3964)Ensured that parallel

`AnnotatedQueues`

do not queue each other’s contents. (#3924)Added a

`map_wires`

method to`PauliWord`

and`PauliSentence`

, and ensured that operators call it in their respective`map_wires`

methods if they have a Pauli rep. (#3985)Fixed a bug when a

`Tensor`

is multiplied by a`Hamiltonian`

or vice versa. (#4036)

### Contributors ✍️

This release contains contributions from (in alphabetical order):

Komi Amiko, Utkarsh Azad, Thomas Bromley, Isaac De Vlugt, Olivia Di Matteo, Lillian M. A. Frederiksen, Diego Guala, Soran Jahangiri, Korbinian Kottmann, Christina Lee, Vincent Michaud-Rioux, Albert Mitjans Coma, Romain Moyard, Lee J. O’Riordan, Mudit Pandey, Matthew Silverman, Jay Soni, David Wierichs.

- orphan

## Release 0.29.0¶

### New features since last release

#### Pulse programming 🔊

Support for creating pulse-based circuits that describe evolution under a time-dependent Hamiltonian has now been added, as well as the ability to execute and differentiate these pulse-based circuits on simulator. (#3586) (#3617) (#3645) (#3652) (#3665) (#3673) (#3706) (#3730)

A time-dependent Hamiltonian can be created using

`qml.pulse.ParametrizedHamiltonian`

, which holds information representing a linear combination of operators with parametrized coefficents and can be constructed as follows:from jax import numpy as jnp f1 = lambda p, t: p * jnp.sin(t) * (t - 1) f2 = lambda p, t: p[0] * jnp.cos(p[1]* t ** 2) XX = qml.PauliX(0) @ qml.PauliX(1) YY = qml.PauliY(0) @ qml.PauliY(1) ZZ = qml.PauliZ(0) @ qml.PauliZ(1) H = 2 * ZZ + f1 * XX + f2 * YY

>>> H ParametrizedHamiltonian: terms=3 >>> p1 = jnp.array(1.2) >>> p2 = jnp.array([2.3, 3.4]) >>> H((p1, p2), t=0.5) (2*(PauliZ(wires=[0]) @ PauliZ(wires=[1]))) + ((-0.2876553231625218*(PauliX(wires=[0]) @ PauliX(wires=[1]))) + (1.517961235535459*(PauliY(wires=[0]) @ PauliY(wires=[1]))))

The time-dependent Hamiltonian can be used within a circuit with

`qml.evolve`

:def pulse_circuit(params, time): qml.evolve(H)(params, time) return qml.expval(qml.PauliX(0) @ qml.PauliY(1))

Pulse-based circuits can be executed and differentiated on the

`default.qubit.jax`

simulator using JAX as an interface:>>> dev = qml.device("default.qubit.jax", wires=2) >>> qnode = qml.QNode(pulse_circuit, dev, interface="jax") >>> params = (p1, p2) >>> qnode(params, time=0.5) Array(0.72153819, dtype=float64) >>> jax.grad(qnode)(params, time=0.5) (Array(-0.11324919, dtype=float64), Array([-0.64399616, 0.06326374], dtype=float64))

Check out the qml.pulse documentation page for more details!

#### Special unitary operation 🌞

A new operation

`qml.SpecialUnitary`

has been added, providing access to an arbitrary unitary gate via a parametrization in the Pauli basis. (#3650) (#3651) (#3674)`qml.SpecialUnitary`

creates a unitary that exponentiates a linear combination of all possible Pauli words in lexicographical order — except for the identity operator — for`num_wires`

wires, of which there are`4**num_wires - 1`

. As its first argument,`qml.SpecialUnitary`

takes a list of the`4**num_wires - 1`

parameters that are the coefficients of the linear combination.To see all possible Pauli words for

`num_wires`

wires, you can use the`qml.ops.qubit.special_unitary.pauli_basis_strings`

function:>>> qml.ops.qubit.special_unitary.pauli_basis_strings(1) # 4**1-1 = 3 Pauli words ['X', 'Y', 'Z'] >>> qml.ops.qubit.special_unitary.pauli_basis_strings(2) # 4**2-1 = 15 Pauli words ['IX', 'IY', 'IZ', 'XI', 'XX', 'XY', 'XZ', 'YI', 'YX', 'YY', 'YZ', 'ZI', 'ZX', 'ZY', 'ZZ']

To use

`qml.SpecialUnitary`

, for example, on a single qubit, we may define>>> thetas = np.array([0.2, 0.1, -0.5]) >>> U = qml.SpecialUnitary(thetas, 0) >>> qml.matrix(U) array([[ 0.8537127 -0.47537233j, 0.09507447+0.19014893j], [-0.09507447+0.19014893j, 0.8537127 +0.47537233j]])

A single non-zero entry in the parameters will create a Pauli rotation:

>>> x = 0.412 >>> theta = x * np.array([1, 0, 0]) # The first entry belongs to the Pauli word "X" >>> su = qml.SpecialUnitary(theta, wires=0) >>> rx = qml.RX(-2 * x, 0) # RX introduces a prefactor -0.5 that has to be compensated >>> qml.math.allclose(qml.matrix(su), qml.matrix(rx)) True

This operation can be differentiated with hardware-compatible methods like parameter shifts and it supports parameter broadcasting/batching, but not both at the same time. Learn more by visiting the qml.SpecialUnitary documentation.

#### Always differentiable 📈

The Hadamard test gradient transform is now available via

`qml.gradients.hadamard_grad`

. This transform is also available as a differentiation method within`QNode`

s. (#3625) (#3736)`qml.gradients.hadamard_grad`

is a hardware-compatible transform that calculates the gradient of a quantum circuit using the Hadamard test. Note that the device requires an auxiliary wire to calculate the gradient.>>> dev = qml.device("default.qubit", wires=2) >>> @qml.qnode(dev) ... def circuit(params): ... qml.RX(params[0], wires=0) ... qml.RY(params[1], wires=0) ... qml.RX(params[2], wires=0) ... return qml.expval(qml.PauliZ(0)) >>> params = np.array([0.1, 0.2, 0.3], requires_grad=True) >>> qml.gradients.hadamard_grad(circuit)(params) (tensor(-0.3875172, requires_grad=True), tensor(-0.18884787, requires_grad=True), tensor(-0.38355704, requires_grad=True))

This transform can be registered directly as the quantum gradient transform to use during autodifferentiation:

>>> dev = qml.device("default.qubit", wires=2) >>> @qml.qnode(dev, interface="jax", diff_method="hadamard") ... def circuit(params): ... qml.RX(params[0], wires=0) ... qml.RY(params[1], wires=0) ... qml.RX(params[2], wires=0) ... return qml.expval(qml.PauliZ(0)) >>> params = jax.numpy.array([0.1, 0.2, 0.3]) >>> jax.jacobian(circuit)(params) Array([-0.3875172 , -0.18884787, -0.38355705], dtype=float32)

The gradient transform

`qml.gradients.spsa_grad`

is now registered as a differentiation method for QNodes. (#3440)The SPSA gradient transform can now be used implicitly by marking a QNode as differentiable with SPSA. It can be selected via

>>> dev = qml.device("default.qubit", wires=1) >>> @qml.qnode(dev, interface="jax", diff_method="spsa", h=0.05, num_directions=20) ... def circuit(x): ... qml.RX(x, 0) ... return qml.expval(qml.PauliZ(0)) >>> jax.jacobian(circuit)(jax.numpy.array(0.5)) Array(-0.4792258, dtype=float32, weak_type=True)

The argument

`num_directions`

determines how many directions of simultaneous perturbation are used and therefore the number of circuit evaluations, up to a prefactor. See the SPSA gradient transform documentation for details. Note: The full SPSA optimization method is already available as`qml.SPSAOptimizer`

.The default interface is now

`auto`

. There is no need to specify the interface anymore; it is automatically determined by checking your QNode parameters. (#3677) (#3752) (#3829)import jax import jax.numpy as jnp qml.enable_return() a = jnp.array(0.1) b = jnp.array(0.2) dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(a, b): qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(1))

>>> circuit(a, b) (Array(0.9950042, dtype=float32), Array(-0.19767681, dtype=float32)) >>> jac = jax.jacobian(circuit)(a, b) >>> jac (Array(-0.09983341, dtype=float32, weak_type=True), Array(0.01983384, dtype=float32, weak_type=True))

The JAX-JIT interface now supports higher-order gradient computation with the new return types system. (#3498)

Here is an example of using JAX-JIT to compute the Hessian of a circuit:

import pennylane as qml import jax from jax import numpy as jnp jax.config.update("jax_enable_x64", True) qml.enable_return() dev = qml.device("default.qubit", wires=2) @jax.jit @qml.qnode(dev, interface="jax-jit", diff_method="parameter-shift", max_diff=2) def circuit(a, b): qml.RY(a, wires=0) qml.RX(b, wires=1) return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) a, b = jnp.array(1.0), jnp.array(2.0)

>>> jax.hessian(circuit, argnums=[0, 1])(a, b) (((Array(-0.54030231, dtype=float64, weak_type=True), Array(0., dtype=float64, weak_type=True)), (Array(-1.76002563e-17, dtype=float64, weak_type=True), Array(0., dtype=float64, weak_type=True))), ((Array(0., dtype=float64, weak_type=True), Array(-1.00700085e-17, dtype=float64, weak_type=True)), (Array(0., dtype=float64, weak_type=True), Array(0.41614684, dtype=float64, weak_type=True))))

The

`qchem`

workflow has been modified to support both Autograd and JAX frameworks. (#3458) (#3462) (#3495)The JAX interface is automatically used when the differentiable parameters are JAX objects. Here is an example for computing the Hartree-Fock energy gradients with respect to the atomic coordinates.

import pennylane as qml from pennylane import numpy as np import jax 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 = [jax.numpy.array(mol.coordinates)]

>>> jax.grad(qml.qchem.hf_energy(mol))(*args) Array([[ 0. , 0. , 0.3650435], [ 0. , 0. , -0.3650435]], dtype=float64)

The kernel matrix utility functions in

`qml.kernels`

are now autodifferentiation-compatible. In addition, they support batching, for example for quantum kernel execution with shot vectors. (#3742)This allows for the following:

dev = qml.device('default.qubit', wires=2, shots=(100, 100)) @qml.qnode(dev) def circuit(x1, x2): qml.templates.AngleEmbedding(x1, wires=dev.wires) qml.adjoint(qml.templates.AngleEmbedding)(x2, wires=dev.wires) return qml.probs(wires=dev.wires) kernel = lambda x1, x2: circuit(x1, x2)

We can then compute the kernel matrix on a set of 4 (random) feature vectors

`X`

but using two sets of 100 shots each via>>> X = np.random.random((4, 2)) >>> qml.kernels.square_kernel_matrix(X, kernel)[:, 0] tensor([[[1. , 0.86, 0.88, 0.92], [0.86, 1. , 0.75, 0.97], [0.88, 0.75, 1. , 0.91], [0.92, 0.97, 0.91, 1. ]], [[1. , 0.93, 0.91, 0.92], [0.93, 1. , 0.8 , 1. ], [0.91, 0.8 , 1. , 0.91], [0.92, 1. , 0.91, 1. ]]], requires_grad=True)

Note that we have extracted the first probability vector entry for each 100-shot evaluation.

#### Smartly decompose Hamiltonian evolution 💯

Hamiltonian evolution using

`qml.evolve`

or`qml.exp`

can now be decomposed into operations. (#3691) (#3777)If the time-evolved Hamiltonian is equivalent to another PennyLane operation, then that operation is returned as the decomposition:

>>> exp_op = qml.evolve(qml.PauliX(0) @ qml.PauliX(1)) >>> exp_op.decomposition() [IsingXX((2+0j), wires=[0, 1])]

If the Hamiltonian is a Pauli word, then the decomposition is provided as a

`qml.PauliRot`

operation:>>> qml.evolve(qml.PauliZ(0) @ qml.PauliX(1)).decomposition() [PauliRot((2+0j), ZX, wires=[0, 1])]

Otherwise, the Hamiltonian is a linear combination of operators and the Suzuki-Trotter decomposition is used:

>>> qml.evolve(qml.sum(qml.PauliX(0), qml.PauliY(0), qml.PauliZ(0)), num_steps=2).decomposition() [RX((1+0j), wires=[0]), RY((1+0j), wires=[0]), RZ((1+0j), wires=[0]), RX((1+0j), wires=[0]), RY((1+0j), wires=[0]), RZ((1+0j), wires=[0])]

#### Tools for quantum chemistry and other applications 🛠️

A new method called

`qml.qchem.givens_decomposition`

has been added, which decomposes a unitary into a sequence of Givens rotation gates with phase shifts and a diagonal phase matrix. (#3573)unitary = np.array([[ 0.73678+0.27511j, -0.5095 +0.10704j, -0.06847+0.32515j], [-0.21271+0.34938j, -0.38853+0.36497j, 0.61467-0.41317j], [ 0.41356-0.20765j, -0.00651-0.66689j, 0.32839-0.48293j]]) phase_mat, ordered_rotations = qml.qchem.givens_decomposition(unitary)

>>> phase_mat tensor([-0.20604358+0.9785369j , -0.82993272+0.55786114j, 0.56230612-0.82692833j], requires_grad=True) >>> ordered_rotations [(tensor([[-0.65087861-0.63937521j, -0.40933651-0.j ], [-0.29201359-0.28685265j, 0.91238348-0.j ]], requires_grad=True), (0, 1)), (tensor([[ 0.47970366-0.33308926j, -0.8117487 -0.j ], [ 0.66677093-0.46298215j, 0.5840069 -0.j ]], requires_grad=True), (1, 2)), (tensor([[ 0.36147547+0.73779454j, -0.57008306-0.j ], [ 0.2508207 +0.51194108j, 0.82158706-0.j ]], requires_grad=True), (0, 1))]

A new template called

`qml.BasisRotation`

has been added, which performs a basis transformation defined by a set of fermionic ladder operators. (#3573)import pennylane as qml from pennylane import numpy as np V = np.array([[ 0.53672126+0.j , -0.1126064 -2.41479668j], [-0.1126064 +2.41479668j, 1.48694623+0.j ]]) eigen_vals, eigen_vecs = np.linalg.eigh(V) umat = eigen_vecs.T wires = range(len(umat)) def circuit(): qml.adjoint(qml.BasisRotation(wires=wires, unitary_matrix=umat)) for idx, eigenval in enumerate(eigen_vals): qml.RZ(eigenval, wires=[idx]) qml.BasisRotation(wires=wires, unitary_matrix=umat)

>>> circ_unitary = qml.matrix(circuit)() >>> np.round(circ_unitary/circ_unitary[0][0], 3) tensor([[ 1. -0.j , -0. +0.j , -0. +0.j , -0. +0.j ], [-0. +0.j , -0.516-0.596j, -0.302-0.536j, -0. +0.j ], [-0. +0.j , 0.35 +0.506j, -0.311-0.724j, -0. +0.j ], [-0. +0.j , -0. +0.j , -0. +0.j , -0.438+0.899j]], requires_grad=True)

A new function called

`qml.qchem.load_basisset`

has been added to extract`qml.qchem`

basis set data from the Basis Set Exchange library. (#3363)A new function called

`qml.math.max_entropy`

has been added to compute the maximum entropy of a quantum state. (#3594)A new template called

`qml.TwoLocalSwapNetwork`

has been added that implements a canonical 2-complete linear (2-CCL) swap network described in arXiv:1905.05118. (#3447)dev = qml.device('default.qubit', wires=5) weights = np.random.random(size=qml.templates.TwoLocalSwapNetwork.shape(len(dev.wires))) acquaintances = lambda index, wires, param: (qml.CRY(param, wires=index) if np.abs(wires[0]-wires[1]) else qml.CRZ(param, wires=index)) @qml.qnode(dev) def swap_network_circuit(): qml.templates.TwoLocalSwapNetwork(dev.wires, acquaintances, weights, fermionic=False) return qml.state()

>>> print(weights) tensor([0.20308242, 0.91906199, 0.67988804, 0.81290256, 0.08708985, 0.81860084, 0.34448344, 0.05655892, 0.61781612, 0.51829044], requires_grad=True) >>> print(qml.draw(swap_network_circuit, expansion_strategy = 'device')()) 0: ─╭●────────╭SWAP─────────────────╭●────────╭SWAP─────────────────╭●────────╭SWAP─┤ State 1: ─╰RY(0.20)─╰SWAP─╭●────────╭SWAP─╰RY(0.09)─╰SWAP─╭●────────╭SWAP─╰RY(0.62)─╰SWAP─┤ State 2: ─╭●────────╭SWAP─╰RY(0.68)─╰SWAP─╭●────────╭SWAP─╰RY(0.34)─╰SWAP─╭●────────╭SWAP─┤ State 3: ─╰RY(0.92)─╰SWAP─╭●────────╭SWAP─╰RY(0.82)─╰SWAP─╭●────────╭SWAP─╰RY(0.52)─╰SWAP─┤ State 4: ─────────────────╰RY(0.81)─╰SWAP─────────────────╰RY(0.06)─╰SWAP─────────────────┤ State

### Improvements 🛠

#### Pulse programming

A new function called

`qml.pulse.pwc`

has been added as a convenience function for defining a`qml.pulse.ParametrizedHamiltonian`

. This function can be used to create a callable coefficient by setting the timespan over which the function should be non-zero. The resulting callable can be passed an array of parameters and a time. (#3645)>>> timespan = (2, 4) >>> f = qml.pulse.pwc(timespan) >>> f * qml.PauliX(0) ParametrizedHamiltonian: terms=1

The

`params`

array will be used as bin values evenly distributed over the timespan, and the parameter`t`

will determine which of the bins is returned.>>> f(params=[1.2, 2.3, 3.4, 4.5], t=3.9) DeviceArray(4.5, dtype=float32) >>> f(params=[1.2, 2.3, 3.4, 4.5], t=6) # zero outside the range (2, 4) DeviceArray(0., dtype=float32)

A new function called

`qml.pulse.pwc_from_function`

has been added as a decorator for defining a`qml.pulse.ParametrizedHamiltonian`

. This function can be used to decorate a function and create a piecewise constant approximation of it. (#3645)>>> @qml.pulse.pwc_from_function((2, 4), num_bins=10) ... def f1(p, t): ... return p * t

The resulting function approximates the same of

`p**2 * t`

on the interval`t=(2, 4)`

in 10 bins, and returns zero outside the interval.# t=2 and t=2.1 are within the same bin >>> f1(3, 2), f1(3, 2.1) (DeviceArray(6., dtype=float32), DeviceArray(6., dtype=float32)) # next bin >>> f1(3, 2.2) DeviceArray(6.6666665, dtype=float32) # outside the interval t=(2, 4) >>> f1(3, 5) DeviceArray(0., dtype=float32)

Add

`ParametrizedHamiltonianPytree`

class, which is a pytree jax object representing a parametrized Hamiltonian, where the matrix computation is delayed to improve performance. (#3779)

#### Operations and batching

The function

`qml.dot`

has been updated to compute the dot product between a vector and a list of operators. (#3586)>>> coeffs = np.array([1.1, 2.2]) >>> ops = [qml.PauliX(0), qml.PauliY(0)] >>> qml.dot(coeffs, ops) (1.1*(PauliX(wires=[0]))) + (2.2*(PauliY(wires=[0]))) >>> qml.dot(coeffs, ops, pauli=True) 1.1 * X(0) + 2.2 * Y(0)

`qml.evolve`

returns the evolution of an`Operator`

or a`ParametrizedHamiltonian`

. (#3617) (#3706)`qml.ControlledQubitUnitary`

now inherits from`qml.ops.op_math.ControlledOp`

, which defines`decomposition`

,`expand`

, and`sparse_matrix`

rather than raising an error. (#3450)Parameter broadcasting support has been added for the

`qml.ops.op_math.Controlled`

class if the base operator supports broadcasting. (#3450)The

`qml.generator`

function now checks if the generator is Hermitian, rather than whether it is a subclass of`Observable`

. This allows it to return valid generators from`SymbolicOp`

and`CompositeOp`

classes. (#3485)The

`qml.equal`

function has been extended to compare`Prod`

and`Sum`

operators. (#3516)`qml.purity`

has been added as a measurement process for purity (#3551)In-place inversion has been removed for qutrit operations in preparation for the removal of in-place inversion. (#3566)

The

`qml.utils.sparse_hamiltonian`

function has been moved to thee`qml.Hamiltonian.sparse_matrix`

method. (#3585)The

`qml.pauli.PauliSentence.operation()`

method has been improved to avoid instantiating an`SProd`

operator when the coefficient is equal to 1. (#3595)Batching is now allowed in all

`SymbolicOp`

operators, which include`Exp`

,`Pow`

and`SProd`

. (#3597)The

`Sum`

and`Prod`

operations now have broadcasted operands. (#3611)The XYX single-qubit unitary decomposition has been implemented. (#3628)

All dunder methods now return

`NotImplemented`

, allowing the right dunder method (e.g.`__radd__`

) of the other class to be called. (#3631)The

`qml.GellMann`

operators now include their index when displayed. (#3641)`qml.ops.ctrl_decomp_zyz`

has been added to compute the decomposition of a controlled single-qubit operation given a single-qubit operation and the control wires. (#3681)`qml.pauli.is_pauli_word`

now supports`Prod`

and`SProd`

operators, and it returns`False`

when a`Hamiltonian`

contains more than one term. (#3692)`qml.pauli.pauli_word_to_string`

now supports`Prod`

,`SProd`

and`Hamiltonian`

operators. (#3692)`qml.ops.op_math.Controlled`

can now decompose single qubit target operations more effectively using the ZYZ decomposition. (#3726)The

`qml.qchem.Molecule`

class raises an error when the molecule has an odd number of electrons or when the spin multiplicity is not 1. (#3748)

`qml.qchem.basis_rotation`

now accounts for spin, allowing it to perform Basis Rotation Groupings for molecular hamiltonians. (#3714) (#3774)The gradient transforms work for the new return type system with non-trivial classical jacobians. (#3776)

The

`default.mixed`

device has received a performance improvement for multi-qubit operations. This also allows to apply channels that act on more than seven qubits, which was not possible before. (#3584)`qml.dot`

now groups coefficients together. (#3691)>>> qml.dot(coeffs=[2, 2, 2], ops=[qml.PauliX(0), qml.PauliY(1), qml.PauliZ(2)]) 2*(PauliX(wires=[0]) + PauliY(wires=[1]) + PauliZ(wires=[2]))

`qml.generator`

now supports operators with`Sum`

and`Prod`

generators. (#3691)The

`Sum._sort`

method now takes into account the name of the operator when sorting. (#3691)A new tape transform called

`qml.transforms.sign_expand`

has been added. It implements the optimal decomposition of a fast-forwardable Hamiltonian that minimizes the variance of its estimator in the Single-Qubit-Measurement from arXiv:2207.09479. (#2852)

#### Differentiability and interfaces

The

`qml.math`

module now also contains a submodule for fast Fourier transforms,`qml.math.fft`

. (#1440)The submodule in particular provides differentiable versions of the following functions, available in all common interfaces for PennyLane

Note that the output of the derivative of these functions may differ when used with complex-valued inputs, due to different conventions on complex-valued derivatives.

Validation has been added on gradient keyword arguments when initializing a QNode — if unexpected keyword arguments are passed, a

`UserWarning`

is raised. A list of the current expected gradient function keyword arguments can be accessed via`qml.gradients.SUPPORTED_GRADIENT_KWARGS`

. (#3526)The

`numpy`

version has been constrained to`<1.24`

. (#3563)Support for two-qubit unitary decomposition with JAX-JIT has been added. (#3569)

`qml.math.size`

now supports PyTorch tensors. (#3606)Most quantum channels are now fully differentiable on all interfaces. (#3612)

`qml.math.matmul`

now supports PyTorch and Autograd tensors. (#3613)Add

`qml.math.detach`

, which detaches a tensor from its trace. This stops automatic gradient computations. (#3674)Add

`typing.TensorLike`

type. (#3675)`qml.QuantumMonteCarlo`

template is now JAX-JIT compatible when passing`jax.numpy`

arrays to the template. (#3734)`DefaultQubitJax`

now supports evolving the state vector when executing`qml.pulse.ParametrizedEvolution`

gates. (#3743)`SProd.sparse_matrix`

now supports interface-specific variables with a single element as the`scalar`

. (#3770)Added

`argnum`

argument to`metric_tensor`

. By passing a sequence of indices referring to trainable tape parameters, the metric tensor is only computed with respect to these parameters. This reduces the number of tapes that have to be run. (#3587)The parameter-shift derivative of variances saves a redundant evaluation of the corresponding unshifted expectation value tape, if possible (#3744)

#### Next generation device API

The

`apply_operation`

single-dispatch function is added to`devices/qubit`

that applies an operation to a state and returns a new state. (#3637)The

`preprocess`

function is added to`devices/qubit`

that validates, expands, and transforms a batch of`QuantumTape`

objects to abstract preprocessing details away from the device. (#3708)The

`create_initial_state`

function is added to`devices/qubit`

that returns an initial state for an execution. (#3683)The

`simulate`

function is added to`devices/qubit`

that turns a single quantum tape into a measurement result. The function only supports state based measurements with either no observables or observables with diagonalizing gates. It supports simultaneous measurement of non-commuting observables. (#3700)The

`ExecutionConfig`

data class has been added. (#3649)The

`StatePrep`

class has been added as an interface that state-prep operators must implement. (#3654)`qml.QubitStateVector`

now implements the`StatePrep`

interface. (#3685)`qml.BasisState`

now implements the`StatePrep`

interface. (#3693)New Abstract Base Class for devices

`Device`

is added to the`devices.experimental`

submodule. This interface is still in experimental mode and not integrated with the rest of pennylane. (#3602)

#### Other improvements

Writing Hamiltonians to a file using the

`qml.data`

module has been improved by employing a condensed writing format. (#3592)Lazy-loading in the

`qml.data.Dataset.read()`

method is more universally supported. (#3605)The

`qchem.Molecule`

class raises an error when the molecule has an odd number of electrons or when the spin multiplicity is not 1. (#3748)`qml.draw`

and`qml.draw_mpl`

have been updated to draw any quantum function, which allows for visualizing only part of a complete circuit/QNode. (#3760)The string representation of a Measurement Process now includes the

`_eigvals`

property if it is set. (#3820)

### Breaking changes 💔

The argument

`mode`

in execution has been replaced by the boolean`grad_on_execution`

in the new execution pipeline. (#3723)`qml.VQECost`

has been removed. (#3735)The default interface is now

`auto`

. (#3677) (#3752) (#3829)The interface is determined during the QNode call instead of the initialization. It means that the

`gradient_fn`

and`gradient_kwargs`

are only defined on the QNode at the beginning of the call. Moreover, without specifying the interface it is not possible to guarantee that the device will not be changed during the call if you are using backprop (such as`default.qubit`

changing to`default.qubit.jax`

) whereas before it was happening at initialization.The tape method

`get_operation`

can also now return the operation index in the tape, and it can be activated by setting the`return_op_index`

to`True`

:`get_operation(idx, return_op_index=True)`

. It will become the default in version`0.30`

. (#3667)`Operation.inv()`

and the`Operation.inverse`

setter have been removed. Please use`qml.adjoint`

or`qml.pow`

instead. (#3618)For example, instead of

>>> qml.PauliX(0).inv()

use

>>> qml.adjoint(qml.PauliX(0))

The

`Operation.inverse`

property has been removed completely. (#3725)The target wires of

`qml.ControlledQubitUnitary`

are no longer available via`op.hyperparameters["u_wires"]`

. Instead, they can be accesses via`op.base.wires`

or`op.target_wires`

. (#3450)The tape constructed by a

`QNode`

is no longer queued to surrounding contexts. (#3509)Nested operators like

`Tensor`

,`Hamiltonian`

, and`Adjoint`

now remove their owned operators from the queue instead of updating their metadata to have an`"owner"`

. (#3282)`qml.qchem.scf`

,`qml.RandomLayers.compute_decomposition`

, and`qml.Wires.select_random`

now use local random number generators instead of global random number generators. This may lead to slightly different random numbers and an independence of the results from the global random number generation state. Please provide a seed to each individual function instead if you want controllable results. (#3624)`qml.transforms.measurement_grouping`

has been removed. Users should use`qml.transforms.hamiltonian_expand`

instead. (#3701)`op.simplify()`

for operators which are linear combinations of Pauli words will use a builtin Pauli representation to more efficiently compute the simplification of the operator. (#3481)All

`Operator`

‘s input parameters that are lists are cast into vanilla numpy arrays. (#3659)`QubitDevice.expval`

no longer permutes an observable’s wire order before passing it to`QubitDevice.probability`

. The associated downstream changes for`default.qubit`

have been made, but this may still affect expectations for other devices that inherit from`QubitDevice`

and override`probability`

(or any other helper functions that take a wire order such as`marginal_prob`

,`estimate_probability`

or`analytic_probability`

). (#3753)

### Deprecations 👋

`qml.utils.sparse_hamiltonian`

function has been deprecated, and usage will now raise a warning. Instead, one should use the`qml.Hamiltonian.sparse_matrix`

method. (#3585)`qml.op_sum`

has been deprecated. Users should use`qml.sum`

instead. (#3686)The use of

`Evolution`

directly has been deprecated. Users should use`qml.evolve`

instead. This new function changes the sign of the given parameter. (#3706)Use of

`qml.dot`

with a`QNodeCollection`

has been deprecated. (#3586)

### Documentation 📝

Revise note on GPU support in the circuit introduction. (#3836)

Make warning about vanilla version of NumPy for differentiation more prominent. (#3838)

The documentation for

`qml.operation`

has been improved. (#3664)The code example in

`qml.SparseHamiltonian`

has been updated with the correct wire range. (#3643)A hyperlink has been added in the text for a URL in the

`qml.qchem.mol_data`

docstring. (#3644)A typo was corrected in the documentation for

`qml.math.vn_entropy`

. (#3740)

### Bug fixes 🐛

Fixed a bug where measuring

`qml.probs`

in the computational basis with non-commuting measurements returned incorrect results. Now an error is raised. (#3811)Fixed a bug where measuring

`qml.probs`

in the computational basis with non-commuting measurements returned incorrect results. Now an error is raised. (#3811)Fixed a bug in the drawer where nested controlled operations would output the label of the operation being controlled, rather than the control values. (#3745)

Fixed a bug in

`qml.transforms.metric_tensor`

where prefactors of operation generators were taken into account multiple times, leading to wrong outputs for non-standard operations. (#3579)Local random number generators are now used where possible to avoid mutating the global random state. (#3624)

The

`networkx`

version change being broken has been fixed by selectively skipping a`qcut`

TensorFlow-JIT test. (#3609) (#3619)Fixed the wires for the

`Y`

decomposition in the ZX calculus transform. (#3598)`qml.pauli.PauliWord`

is now pickle-able. (#3588)Child classes of

`QuantumScript`

now return their own type when using`SomeChildClass.from_queue`

. (#3501)A typo has been fixed in the calculation and error messages in

`operation.py`

(#3536)`qml.data.Dataset.write()`

now ensures that any lazy-loaded values are loaded before they are written to a file. (#3605)`Tensor._batch_size`

is now set to`None`

during initialization, copying and`map_wires`

. (#3642) (#3661)`Tensor.has_matrix`

is now set to`True`

. (#3647)Fixed typo in the example of

`qml.IsingZZ`

gate decomposition. (#3676)Fixed a bug that made tapes/qnodes using

`qml.Snapshot`

incompatible with`qml.drawer.tape_mpl`

. (#3704)`Tensor._pauli_rep`

is set to`None`

during initialization and`Tensor.data`

has been added to its setter. (#3722)`qml.math.ndim`

has been redirected to`jnp.ndim`

when using it on a`jax`

tensor. (#3730)Implementations of

`marginal_prob`

(and subsequently,`qml.probs`

) now return probabilities with the expected wire order. (#3753)This bug affected most probabilistic measurement processes on devices that inherit from

`QubitDevice`

when the measured wires are out of order with respect to the device wires and 3 or more wires are measured. The assumption was that marginal probabilities would be computed with the device’s state and wire order, then re-ordered according to the measurement process wire order. Instead, the re-ordering went in the inverse direction (that is, from measurement process wire order to device wire order). This is now fixed. Note that this only occurred for 3 or more measured wires because this mapping is identical otherwise. More details and discussion of this bug can be found in the original bug report.Empty iterables can no longer be returned from QNodes. (#3769)

The keyword arguments for

`qml.equal`

now are used when comparing the observables of a Measurement Process. The eigvals of measurements are only requested if both observables are`None`

, saving computational effort. (#3820)Only converts input to

`qml.Hermitian`

to a numpy array if the input is a list. (#3820)

### Contributors ✍

This release contains contributions from (in alphabetical order):

Gian-Luca Anselmetti, Guillermo Alonso-Linaje, Juan Miguel Arrazola, Ikko Ashimine, Utkarsh Azad, Miriam Beddig, Cristian Boghiu, Thomas Bromley, Astral Cai, Isaac De Vlugt, Olivia Di Matteo, Lillian M. A. Frederiksen, Soran Jahangiri, Korbinian Kottmann, Christina Lee, Albert Mitjans Coma, Romain Moyard, Mudit Pandey, Borja Requena, Matthew Silverman, Jay Soni, Antal Száva, Frederik Wilde, David Wierichs, Moritz Willmann.

- orphan

## Release 0.28.0¶

### New features since last release

#### Custom measurement processes 📐

Custom measurements can now be facilitated with the addition of the

`qml.measurements`

module. (#3286) (#3343) (#3288) (#3312) (#3287) (#3292) (#3287) (#3326) (#3327) (#3388) (#3439) (#3466)Within

`qml.measurements`

are new subclasses that allow for the possibility to create*custom*measurements:`SampleMeasurement`

: represents a sample-based measurement`StateMeasurement`

: represents a state-based measurement`MeasurementTransform`

: represents a measurement process that requires the application of a batch transform

Creating a custom measurement involves making a class that inherits from one of the classes above. An example is given below. Here, the measurement computes the number of samples obtained of a given state:

from pennylane.measurements import SampleMeasurement class CountState(SampleMeasurement): def __init__(self, state: str): self.state = state # string identifying the state, e.g. "0101" wires = list(range(len(state))) super().__init__(wires=wires) def process_samples(self, samples, wire_order, shot_range, bin_size): counts_mp = qml.counts(wires=self._wires) counts = counts_mp.process_samples(samples, wire_order, shot_range, bin_size) return counts.get(self.state, 0) def __copy__(self): return CountState(state=self.state)

We can now execute the new measurement in a QNode as follows.

dev = qml.device("default.qubit", wires=1, shots=10000) @qml.qnode(dev) def circuit(x): qml.RX(x, wires=0) return CountState(state="1")

>>> circuit(1.23) tensor(3303., requires_grad=True)

Differentiability is also supported for this new measurement process:

>>> x = qml.numpy.array(1.23, requires_grad=True) >>> qml.grad(circuit)(x) 4715.000000000001

For more information about these new features, see the documentation for ``qml.measurements` <https://docs.pennylane.ai/en/stable/code/qml_measurements.html>`_.

#### ZX Calculus 🧮

QNodes can now be converted into ZX diagrams via the PyZX framework. (#3446)

ZX diagrams are the medium for which we can envision a quantum circuit as a graph in the ZX-calculus language, showing properties of quantum protocols in a visually compact and logically complete fashion.

QNodes decorated with

`@qml.transforms.to_zx`

will return a PyZX graph that represents the computation in the ZX-calculus language.dev = qml.device("default.qubit", wires=2) @qml.transforms.to_zx @qml.qnode(device=dev) def circuit(p): qml.RZ(p[0], wires=1), qml.RZ(p[1], wires=1), qml.RX(p[2], wires=0), qml.PauliZ(wires=0), qml.RZ(p[3], wires=1), qml.PauliX(wires=1), qml.CNOT(wires=[0, 1]), qml.CNOT(wires=[1, 0]), qml.SWAP(wires=[0, 1]), return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))

>>> params = [5 / 4 * np.pi, 3 / 4 * np.pi, 0.1, 0.3] >>> circuit(params) Graph(20 vertices, 23 edges)

Information about PyZX graphs can be found in the PyZX Graphs API.

#### QChem databases and basis sets ⚛️

The symbols and geometry of a compound from the PubChem database can now be accessed via

`qchem.mol_data()`

. (#3289) (#3378)>>> import pennylane as qml >>> from pennylane.qchem import mol_data >>> mol_data("BeH2") (['Be', 'H', 'H'], tensor([[ 4.79404621, 0.29290755, 0. ], [ 3.77945225, -0.29290755, 0. ], [ 5.80882913, -0.29290755, 0. ]], requires_grad=True)) >>> mol_data(223, "CID") (['N', 'H', 'H', 'H', 'H'], tensor([[ 0. , 0. , 0. ], [ 1.82264085, 0.52836742, 0.40402345], [ 0.01417295, -1.67429735, -0.98038991], [-0.98927163, -0.22714508, 1.65369933], [-0.84773114, 1.373075 , -1.07733286]], requires_grad=True))

Perform quantum chemistry calculations with two new basis sets:

`6-311g`

and`CC-PVDZ`

. (#3279)>>> symbols = ["H", "He"] >>> geometry = np.array([[1.0, 0.0, 0.0], [0.0, 0.0, 0.0]], requires_grad=False) >>> charge = 1 >>> basis_names = ["6-311G", "CC-PVDZ"] >>> for basis_name in basis_names: ... mol = qml.qchem.Molecule(symbols, geometry, charge=charge, basis_name=basis_name) ... print(qml.qchem.hf_energy(mol)()) [-2.84429531] [-2.84061284]

#### A bunch of new operators 👀

The controlled CZ gate and controlled Hadamard gate are now available via

`qml.CCZ`

and`qml.CH`

, respectively. (#3408)>>> ccz = qml.CCZ(wires=[0, 1, 2]) >>> qml.matrix(ccz) [[ 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]] >>> ch = qml.CH(wires=[0, 1]) >>> qml.matrix(ch) [[ 1. 0. 0. 0. ] [ 0. 1. 0. 0. ] [ 0. 0. 0.70710678 0.70710678] [ 0. 0. 0.70710678 -0.70710678]]

Three new parametric operators,

`qml.CPhaseShift00`

,`qml.CPhaseShift01`

, and`qml.CPhaseShift10`

, are now available. Each of these operators performs a phase shift akin to`qml.ControlledPhaseShift`

but on different positions of the state vector. (#2715)>>> dev = qml.device("default.qubit", wires=2) >>> @qml.qnode(dev) >>> def circuit(): ... qml.PauliX(wires=1) ... qml.CPhaseShift01(phi=1.23, wires=[0,1]) ... return qml.state() ... >>> circuit() tensor([0. +0.j , 0.33423773+0.9424888j, 1. +0.j , 0. +0.j ], requires_grad=True)

A new gate operation called

`qml.FermionicSWAP`

has been added. This implements the exchange of spin orbitals representing fermionic-modes while maintaining proper anti-symmetrization. (#3380)dev = qml.device('default.qubit', wires=2) @qml.qnode(dev) def circuit(phi): qml.BasisState(np.array([0, 1]), wires=[0, 1]) qml.FermionicSWAP(phi, wires=[0, 1]) return qml.state()

>>> circuit(0.1) tensor([0. +0.j , 0.99750208+0.04991671j, 0.00249792-0.04991671j, 0. +0.j ], requires_grad=True)

Create operators defined from a generator via

`qml.ops.op_math.Evolution`

. (#3375)`qml.ops.op_math.Evolution`

defines the exponential of an operator $hat{O}$ of the form $e^{ixhat{O}}$, with a single trainable parameter, $x$. Limiting to a single trainable parameter allows the use of`qml.gradients.param_shift`

to find the gradient with respect to the parameter $x$.dev = qml.device('default.qubit', wires=2) @qml.qnode(dev, diff_method=qml.gradients.param_shift) def circuit(phi): qml.ops.op_math.Evolution(qml.PauliX(0), -.5 * phi) return qml.expval(qml.PauliZ(0))

>>> phi = np.array(1.2) >>> circuit(phi) tensor(0.36235775, requires_grad=True) >>> qml.grad(circuit)(phi) -0.9320390495504149

The qutrit Hadamard gate,

`qml.THadamard`

, is now available. (#3340)The operation accepts a

`subspace`

keyword argument which determines which variant of the qutrit Hadamard to use.>>> th = qml.THadamard(wires=0, subspace=[0, 1]) >>> qml.matrix(th) array([[ 0.70710678+0.j, 0.70710678+0.j, 0. +0.j], [ 0.70710678+0.j, -0.70710678+0.j, 0. +0.j], [ 0. +0.j, 0. +0.j, 1. +0.j]])

#### New transforms, functions, and more 😯

Calculating the purity of arbitrary quantum states is now supported. (#3290)

The purity can be calculated in an analogous fashion to, say, the Von Neumann entropy:

`qml.math.purity`

can be used as an in-line function:>>> 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

`qml.qinfo.transforms.purity`

can 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.transforms.purity(circuit, wires=[0])(np.pi / 2) 0.5 >>> qml.qinfo.transforms.purity(circuit, wires=[0, 1])(np.pi / 2) 1.0

As with the other methods in

`qml.qinfo`

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

A new gradient transform,

`qml.gradients.spsa_grad`

, that is based on the idea of SPSA is now available. (#3366)This new transform allows users to compute a single estimate of a quantum gradient using simultaneous perturbation of parameters and a stochastic approximation. A QNode that takes, say, an argument

`x`

, the approximate gradient can be computed as follows.>>> dev = qml.device("default.qubit", wires=2) >>> x = np.array(0.4, requires_grad=True) >>> @qml.qnode(dev) ... def circuit(x): ... qml.RX(x, 0) ... qml.RX(x, 1) ... return qml.expval(qml.PauliZ(0)) >>> grad_fn = qml.gradients.spsa_grad(circuit, h=0.1, num_directions=1) >>> grad_fn(x) array(-0.38876964)

The argument

`num_directions`

determines how many directions of simultaneous perturbation are used, which is proportional to the number of circuit evaluations. See the SPSA gradient transform documentation for details. Note that the full SPSA optimizer is already available as`qml.SPSAOptimizer`

.Multiple mid-circuit measurements can now be combined arithmetically to create new conditionals. (#3159)

dev = qml.device("default.qubit", wires=3) @qml.qnode(dev) def circuit(): qml.Hadamard(wires=0) qml.Hadamard(wires=1) m0 = qml.measure(wires=0) m1 = qml.measure(wires=1) combined = 2 * m1 + m0 qml.cond(combined == 2, qml.RX)(1.3, wires=2) return qml.probs(wires=2)

>>> circuit() [0.90843735 0.09156265]

A new method called

`pauli_decompose()`

has been added to the`qml.pauli`

module, which takes a hermitian matrix, decomposes it in the Pauli basis, and returns it either as a`qml.Hamiltonian`

or`qml.PauliSentence`

instance. (#3384)`Operation`

or`Hamiltonian`

instances can now be generated from a`qml.PauliSentence`

or`qml.PauliWord`

via the new`operation()`

and`hamiltonian()`

methods. (#3391)A

`sum_expand`

function has been added for tapes, which splits a tape measuring a`Sum`

expectation into mutliple tapes of summand expectations, and provides a function to recombine the results. (#3230)

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

The autograd and Tensorflow interfaces now support devices with shot vectors when

`qml.enable_return()`

has been called. (#3374) (#3400)Here is an example using Tensorflow:

import tensorflow as tf qml.enable_return() 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])

>>> a = tf.Variable(0.4) >>> 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]])>

The PyTorch interface is now fully supported when

`qml.enable_return()`

has been called, allowing the calculation of the Jacobian and the Hessian using custom differentiation methods (e.g., parameter-shift, finite difference, or adjoint). (#3416)import torch qml.enable_return() dev = qml.device("default.qubit", wires=2) @qml.qnode(dev, diff_method="parameter-shift", interface="torch") def circuit(a, b): qml.RY(a, wires=0) qml.RX(b, wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)), qml.probs([0, 1])

>>> a = torch.tensor(0.1, requires_grad=True) >>> b = torch.tensor(0.2, requires_grad=True) >>> torch.autograd.functional.jacobian(circuit, (a, b)) ((tensor(-0.0998), tensor(0.)), (tensor([-0.0494, -0.0005, 0.0005, 0.0494]), tensor([-0.0991, 0.0991, 0.0002, -0.0002])))

The JAX-JIT interface now supports first-order gradient computation when

`qml.enable_return()`

has been called. (#3235) (#3445)import jax from jax import numpy as jnp jax.config.update("jax_enable_x64", True) qml.enable_return() dev = qml.device("lightning.qubit", wires=2) @jax.jit @qml.qnode(dev, interface="jax-jit", diff_method="parameter-shift") def circuit(a, b): qml.RY(a, wires=0) qml.RX(b, wires=0) return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) a, b = jnp.array(1.0), jnp.array(2.0)

>>> jax.jacobian(circuit, argnums=[0, 1])(a, b) ((Array(0.35017549, dtype=float64, weak_type=True), Array(-0.4912955, dtype=float64, weak_type=True)), (Array(5.55111512e-17, dtype=float64, weak_type=True), Array(0., dtype=float64, weak_type=True)))

### Improvements 🛠

`qml.pauli.is_pauli_word`

now supports instances of`qml.Hamiltonian`

. (#3389)When

`qml.probs`

,`qml.counts`

, and`qml.sample`

are called with no arguments, they measure all wires. Calling any of the aforementioned measurements with an empty wire list (e.g.,`qml.sample(wires=[])`

) will raise an error. (#3299)Made

`qml.gradients.finite_diff`

more convenient to use with custom data type observables/devices by reducing the number of magic methods that need to be defined in the custom data type to support`finite_diff`

. (#3426)The

`qml.ISWAP`

gate is now natively supported on`default.mixed`

, improving on its efficiency. (#3284)Added more input validation to

`qml.transforms.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 the execution of a circuit. (#3306)The execution time of

`Wires.all_wires`

has been improved by avoiding data type changes and making use of`itertools.chain`

. (#3302)Printing an instance of

`qml.qchem.Molecule`

is now more concise and informational. (#3364)The error message for

`qml.transforms.insert`

when it fails to diagonalize non-qubit-wise-commuting observables is now more detailed. (#3381)Extended the

`qml.equal`

function to`qml.Hamiltonian`

and`Tensor`

objects. (#3390)`QuantumTape._process_queue`

has been moved to`qml.queuing.process_queue`

to disentangle its functionality from the`QuantumTape`

class. (#3401)QPE can now accept a target operator instead of a matrix and target wires pair. (#3373)

The

`qml.ops.op_math.Controlled.map_wires`

method now uses`base.map_wires`

internally instead of the private`_wires`

property setter. (#3405)A new function called

`qml.tape.make_qscript`

has been created for converting a quantum function into a quantum script. This replaces`qml.transforms.make_tape`

. (#3429)Add a

`_pauli_rep`

attribute to operators to integrate the new Pauli arithmetic classes with native PennyLane objects. (#3443)Extended the functionality of

`qml.matrix`

to qutrits. (#3508)The

`qcut.py`

file in`pennylane/transforms/`

has been reorganized into multiple files that are now in`pennylane/transforms/qcut/`

. (#3413)A warning now appears when creating a

`Tensor`

object with overlapping wires, informing that this can lead to undefined behaviour. (#3459)Extended the

`qml.equal`

function to`qml.ops.op_math.Controlled`

and`qml.ops.op_math.ControlledOp`

objects. (#3463)Nearly every instance of

`with QuantumTape()`

has been replaced with`QuantumScript`

construction. (#3454)Added

`validate_subspace`

static method to`qml.Operator`

to check the validity of the subspace of certain qutrit operations. (#3340)`qml.equal`

now supports operators created via`qml.s_prod`

,`qml.pow`

,`qml.exp`

, and`qml.adjoint`

. (#3471)Devices can now disregard observable grouping indices in Hamiltonians through the optional

`use_grouping`

attribute. (#3456)Add the optional argument

`lazy=True`

to functions`qml.s_prod`

,`qml.prod`

and`qml.op_sum`

to allow simplification. (#3483)Updated the

`qml.transforms.zyz_decomposition`

function such that it now supports broadcast operators. This means that single-qubit`qml.QubitUnitary`

operators, instantiated from a batch of unitaries, can now be decomposed. (#3477)The performance of executing circuits under the

`jax.vmap`

transformation has been improved by being able to leverage the batch-execution capabilities of some devices. (#3452)The tolerance for converting openfermion Hamiltonian complex coefficients to real ones has been modified to prevent conversion errors. (#3367)

`OperationRecorder`

now inherits from`AnnotatedQueue`

and`QuantumScript`

instead of`QuantumTape`

. (#3496)Updated

`qml.transforms.split_non_commuting`

to support the new return types. (#3414)Updated

`qml.transforms.mitigate_with_zne`

to support the new return types. (#3415)Updated

`qml.transforms.metric_tensor`

,`qml.transforms.adjoint_metric_tensor`

,`qml.qinfo.classical_fisher`

, and`qml.qinfo.quantum_fisher`

to support the new return types. (#3449)Updated

`qml.transforms.batch_params`

and`qml.transforms.batch_input`

to support the new return types. (#3431)Updated

`qml.transforms.cut_circuit`

and`qml.transforms.cut_circuit_mc`

to support the new return types. (#3346)Limit NumPy version to

`<1.24`

. (#3346)

### Breaking changes 💔

Python 3.7 support is no longer maintained. PennyLane will be maintained for versions 3.8 and up. (#3276)

The

`log_base`

attribute has been moved from`MeasurementProcess`

to the new`VnEntropyMP`

and`MutualInfoMP`

classes, which inherit from`MeasurementProcess`

. (#3326)`qml.utils.decompose_hamiltonian()`

has been removed. Please use`qml.pauli.pauli_decompose()`

instead. (#3384)The

`return_type`

attribute of`MeasurementProcess`

has been removed where possible. Use`isinstance`

checks instead. (#3399)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)The

`ShadowMeasurementProcess`

class has been renamed to`ClassicalShadowMP`

. (#3388)The

`qml.Operation.get_parameter_shift`

method has been removed. The`gradients`

module should be used for general parameter-shift rules instead. (#3419)The signature of the

`QubitDevice.statistics`

method has been changed fromdef statistics(self, observables, shot_range=None, bin_size=None, circuit=None):

to

def statistics(self, circuit: QuantumTape, shot_range=None, bin_size=None):

The

`MeasurementProcess`

class is now an abstract class and`return_type`

is now a property of the class. (#3434)

### Deprecations 👋

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

The following methods are deprecated: (#3281)

`qml.tape.get_active_tape`

: Use`qml.QueuingManager.active_context()`

instead`qml.transforms.qcut.remap_tape_wires`

: Use`qml.map_wires`

instead`qml.tape.QuantumTape.inv()`

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

instead`qml.tape.stop_recording()`

: Use`qml.QueuingManager.stop_recording()`

instead`qml.tape.QuantumTape.stop_recording()`

: Use`qml.QueuingManager.stop_recording()`

instead`qml.QueuingContext`

is now`qml.QueuingManager`

`QueuingManager.safe_update_info`

and`AnnotatedQueue.safe_update_info`

: Use`update_info`

instead.

`qml.transforms.measurement_grouping`

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

instead. (#3417)The

`observables`

argument in`QubitDevice.statistics`

is deprecated. Please use`circuit`

instead. (#3433)The

`seed_recipes`

argument in`qml.classical_shadow`

and`qml.shadow_expval`

is deprecated. A new argument`seed`

has been added, which defaults to None and can contain an integer with the wanted seed. (#3388)`qml.transforms.make_tape`

has been deprecated. Please use`qml.tape.make_qscript`

instead. (#3478)

### Documentation 📝

Added documentation on parameter broadcasting regarding both its usage and technical aspects. (#3356)

The quickstart guide on circuits as well as the the documentation of QNodes and Operators now contain introductions and details on parameter broadcasting. The QNode documentation mostly contains usage details, the Operator documentation is concerned with implementation details and a guide to support broadcasting in custom operators.

The return type statements of gradient and Hessian transforms and a series of other functions that are a

`batch_transform`

have been corrected. (#3476)Developer documentation for the queuing module has been added. (#3268)

More mentions of diagonalizing gates for all relevant operations have been corrected. (#3409)

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.A warning about using

`dill`

to pickle and unpickle datasets has been added. (#3505)

### Bug fixes 🐛

Fixed a bug that prevented

`qml.gradients.param_shift`

from being used for broadcasted tapes. (#3528)Fixed a bug where

`qml.transforms.hamiltonian_expand`

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

`qml.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)The original tape

`_obs_sharing_wires`

attribute is updated during its expansion. (#3293)An issue with

`drain=False`

in the adaptive optimizer has been fixed. Before the fix, the operator pool needed to be reconstructed inside the optimization pool when`drain=False`

. With this fix, this reconstruction is no longer needed. (#3361)If 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 (e.g.,`qml.Barrier`

) to match`op.matrix()`

. (#3386)The

`pad_with`

argument in the`qml.AmplitudeEmbedding`

template is now compatible with all interfaces. (#3392)`Operator.pow`

now queues its constituents by default. (#3373)Fixed a bug where a QNode returning

`qml.sample`

would produce incorrect results when run on a device defined with a shot vector. (#3422)The

`qml.data`

module now works as expected on Windows. (#3504)

### Contributors ✍️

This release contains contributions from (in alphabetical order):

Guillermo Alonso, Juan Miguel Arrazola, Utkarsh Azad, Samuel Banning, Thomas Bromley, Astral Cai, Albert Mitjans Coma, Ahmed Darwish, Isaac De Vlugt, Olivia Di Matteo, Amintor Dusko, Pieter Eendebak, Lillian M. A. Frederiksen, Diego Guala, Katharine Hyatt, Josh Izaac, Soran Jahangiri, Edward Jiang, Korbinian Kottmann, Christina Lee, Romain Moyard, Lee James O’Riordan, Mudit Pandey, Kevin Shen, Matthew Silverman, Jay Soni, Antal Száva, David Wierichs, Moritz Willmann, and Filippo Vicentini.

- orphan

## Release 0.27.0¶

### 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.Array'>

#### 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) (Array([-0.84147098, 0.35017549], dtype=float64, weak_type=True), Array([ 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) ((Array([[ 0., 0.], [ 2., -3.]], dtype=float32), Array([[[-0.5, 0. ], [ 0. , 0. ]], [[ 0.5, 0. ], [ 0. , 0. ]]], dtype=float32)), (Array([[ 0.07677898, 0.0563341 ], [ 0.07238522, -1.830669 ]], dtype=float32), Array([[[-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)─┤ ╭<Z@Z> 1: ────────────────────┤ ╰<Z@Z>

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 errors if 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.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. (#3004)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) >>> rel