Release 0.24.0
New features since last release
All new quantum information quantities 📏
-
Functionality for computing quantum information quantities for QNodes has been added. (#2554) (#2569) (#2598) (#2617) (#2631) (#2640) (#2663) (#2684) (#2688) (#2695) (#2710) (#2712)
This includes two new QNode measurements:
-
The Von Neumann entropy via
qml.vn_entropy
:>>> dev = qml.device("default.qubit", wires=2) >>> @qml.qnode(dev) ... def circuit_entropy(x): ... qml.IsingXX(x, wires=[0,1]) ... return qml.vn_entropy(wires=[0], log_base=2) >>> circuit_entropy(np.pi/2) 1.0
-
The mutual information via
qml.mutual_info
:>>> dev = qml.device("default.qubit", wires=2) >>> @qml.qnode(dev) ... def circuit(x): ... qml.IsingXX(x, wires=[0,1]) ... return qml.mutual_info(wires0=[0], wires1=[1], log_base=2) >>> circuit(np.pi/2) 2.0
New differentiable transforms are also available in the
qml.qinfo
module:-
The classical and quantum Fisher information via
qml.qinfo.classical_fisher
,qml.qinfo.quantum_fisher
, respectively:dev = qml.device("default.qubit", wires=3) @qml.qnode(dev) def circ(params): qml.RY(params[0], wires=1) qml.CNOT(wires=(1,0)) qml.RY(params[1], wires=1) qml.RZ(params[2], wires=1) return qml.expval(qml.PauliX(0) @ qml.PauliX(1) - 0.5 * qml.PauliZ(1)) params = np.array([0.5, 1., 0.2], requires_grad=True) cfim = qml.qinfo.classical_fisher(circ)(params) qfim = qml.qinfo.quantum_fisher(circ)(params)
These quantities are typically employed in variational optimization schemes to tilt the gradient in a more favourable direction --- producing what is known as the natural gradient. For example:
>>> grad = qml.grad(circ)(params) >>> cfim @ grad # natural gradient [ 5.94225615e-01 -2.61509542e-02 -1.18674655e-18] >>> qfim @ grad # quantum natural gradient [ 0.59422561 -0.02615095 -0.03989212]
-
The fidelity between two arbitrary states via
qml.qinfo.fidelity
:dev = qml.device('default.qubit', wires=1) @qml.qnode(dev) def circuit_rx(x): qml.RX(x[0], wires=0) qml.RZ(x[1], wires=0) return qml.state() @qml.qnode(dev) def circuit_ry(y): qml.RY(y, wires=0) return qml.state()
>>> x = np.array([0.1, 0.3], requires_grad=True) >>> y = np.array(0.2, requires_grad=True) >>> fid_func = qml.qinfo.fidelity(circuit_rx, circuit_ry, wires0=[0], wires1=[0]) >>> fid_func(x, y) 0.9905158135644924 >>> df = qml.grad(fid_func) >>> df(x, y) (array([-0.04768725, -0.29183666]), array(-0.09489803))
-
Reduced density matrices of arbitrary states via
qml.qinfo.reduced_dm
:dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(x): qml.IsingXX(x, wires=[0,1]) return qml.state()
>>> qml.qinfo.reduced_dm(circuit, wires=[0])(np.pi/2) [[0.5+0.j 0.+0.j] [0.+0.j 0.5+0.j]]
-
Similar transforms,
qml.qinfo.vn_entropy
andqml.qinfo.mutual_info
exist
for transforming QNodes.
Currently, all quantum information measurements and transforms are differentiable, but only support statevector devices, with hardware support to come in a future release (with the exception of
qml.qinfo.classical_fisher
andqml.qinfo.quantum_fisher
, which are both hardware compatible).For more information, check out the new qinfo module and measurements page.
-
-
In addition to the QNode transforms and measurements above, functions for computing and differentiating quantum information metrics with numerical statevectors and density matrices have been added to the
qml.math
module. This enables flexible custom post-processing.Added functions include:
qml.math.reduced_dm
qml.math.vn_entropy
qml.math.mutual_info
qml.math.fidelity
For example:
>>> x = torch.tensor([1.0, 0.0, 0.0, 1.0], requires_grad=True) >>> en = qml.math.vn_entropy(x / np.sqrt(2.), indices=[0]) >>> en tensor(0.6931, dtype=torch.float64, grad_fn=<DivBackward0>) >>> en.backward() >>> x.grad tensor([-0.3069, 0.0000, 0.0000, -0.3069])
Faster mixed-state training with backpropagation 📉
-
The
default.mixed
device now supports differentiation via backpropagation with the Autograd, TensorFlow, and PyTorch (CPU) interfaces, leading to significantly more performant optimization and training. (#2615) (#2670) (#2680)As a result, the default differentiation method for the device is now
"backprop"
. To continue using the old default"parameter-shift"
, explicitly specify this differentiation method in the QNode:dev = qml.device("default.mixed", wires=2) @qml.qnode(dev, interface="autograd", diff_method="backprop") def circuit(x): qml.RY(x, wires=0) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(wires=1))
>>> x = np.array(0.5, requires_grad=True) >>> circuit(x) array(0.87758256) >>> qml.grad(circuit)(x) -0.479425538604203
Support for quantum parameter broadcasting 📡
-
Quantum operators, functions, and tapes now support broadcasting across parameter dimensions, making it more convenient for developers to execute their PennyLane programs with multiple sets of parameters. (#2575) (#2609)
Parameter broadcasting refers to passing tensor parameters with additional leading dimensions to quantum operators; additional dimensions will flow through the computation, and produce additional dimensions at the output.
For example, instantiating a rotation gate with a one-dimensional array leads to a broadcasted
Operation
:>>> x = np.array([0.1, 0.2, 0.3], requires_grad=True) >>> op = qml.RX(x, 0) >>> op.batch_size 3
Its matrix correspondingly is augmented by a leading dimension of size
batch_size
:>>> np.round(qml.matrix(op), 4) tensor([[[0.9988+0.j , 0. -0.05j ], [0. -0.05j , 0.9988+0.j ]], [[0.995 +0.j , 0. -0.0998j], [0. -0.0998j, 0.995 +0.j ]], [[0.9888+0.j , 0. -0.1494j], [0. -0.1494j, 0.9888+0.j ]]], requires_grad=True) >>> qml.matrix(op).shape (3, 2, 2)
This can be extended to quantum functions, where we may mix-and-match operations with batched parameters and those without. However, the
batch_size
of each batchedOperator
within the quantum function must be the same:>>> dev = qml.device('default.qubit', wires=1) >>> @qml.qnode(dev) ... def circuit_rx(x, z): ... qml.RX(x, wires=0) ... qml.RZ(z, wires=0) ... qml.RY(0.3, wires=0) ... return qml.probs(wires=0) >>> circuit_rx([0.1, 0.2], [0.3, 0.4]) tensor([[0.97092256, 0.02907744], [0.95671515, 0.04328485]], requires_grad=True)
Parameter broadcasting is supported on all devices, hardware and simulator. Note that if not natively supported by the underlying device, parameter broadcasting may result in additional quantum device evaluations.
-
A new transform,
qml.transforms.broadcast_expand
, has been added, which automates the process of transforming quantum functions (and tapes) to multiple quantum evaluations with no parameter broadcasting. (#2590)>>> dev = qml.device('default.qubit', wires=1) >>> @qml.transforms.broadcast_expand() >>> @qml.qnode(dev) ... def circuit_rx(x, z): ... qml.RX(x, wires=0) ... qml.RZ(z, wires=0) ... qml.RY(0.3, wires=0) ... return qml.probs(wires=0) >>> print(qml.draw(circuit_rx)([0.1, 0.2], [0.3, 0.4])) 0: ──RX(0.10)──RZ(0.30)──RY(0.30)─┤ Probs \ 0: ──RX(0.20)──RZ(0.40)──RY(0.30)─┤ Probs
Under-the-hood, this transform is used for devices that don't natively support parameter broadcasting.
-
To specify that a device natively supports broadcasted tapes, the new flag
Device.capabilities()["supports_broadcasting"]
should be set toTrue
. -
To support parameter broadcasting for new or custom operations, the following new
Operator
class attributes must be specified:Operator.ndim_params
specifies expected number of dimensions for each parameter
Once set,
Operator.batch_size
andQuantumTape.batch_size
will dynamically compute the parameter broadcasting axis dimension, if present.
Improved JAX JIT support 🏎
-
JAX just-in-time (JIT) compilation now supports vector-valued QNodes, enabling new types of workflows and significant performance boosts. (#2034)
Vector-valued QNodes include those with:
qml.probs
;qml.state
;qml.sample
or- multiple
qml.expval
/qml.var
measurements.
Consider a QNode that returns basis-state probabilities:
dev = qml.device('default.qubit', wires=2) x = jnp.array(0.543) y = jnp.array(-0.654) @jax.jit @qml.qnode(dev, diff_method="parameter-shift", interface="jax") def circuit(x, y): qml.RX(x, wires=[0]) qml.RY(y, wires=[1]) qml.CNOT(wires=[0, 1]) return qml.probs(wires=[1])
>>> circuit(x, y) DeviceArray([0.8397495 , 0.16025047], dtype=float32)
Note that computing the jacobian of vector-valued QNode is not supported with JAX JIT. The output of vector-valued QNodes can, however, be used in the definition of scalar-valued cost functions whose gradients can be computed.
For example, one can define a cost function that outputs the first element of the probability vector:
def cost(x, y): return circuit(x, y)[0]
>>> jax.grad(cost, argnums=[0])(x, y) (DeviceArray(-0.2050439, dtype=float32),)
More drawing styles 🎨
- New
solarized_light
andsolarized_dark
styles are available for drawing circuit diagram graphics. (#2662)
New operations & transforms 🤖
-
The
qml.IsingXY
gate is now available (see 1912.04424). (#2649) -
The
qml.ECR
(echoed cross-resonance) operation is now available (see 2105.01063). This gate is a maximally-entangling gate and is equivalent to a CNOT gate up to single-qubit pre-rotations. (#2613) -
The adjoint transform
adjoint
can now accept either a single instantiated operator or a quantum function. It returns an entity of the same type / call signature as what it was given: (#2222) (#2672)>>> qml.adjoint(qml.PauliX(0)) Adjoint(PauliX)(wires=[0]) >>> qml.adjoint(qml.RX)(1.23, wires=0) Adjoint(RX)(1.23, wires=[0])
Now,
adjoint
wraps operators in a symbolic operator classqml.ops.op_math.Adjoint
. This class should not be constructed directly; theadjoint
constructor should always be used instead. The class behaves just like any otherOperator
:>>> op = qml.adjoint(qml.S(0)) >>> qml.matrix(op) array([[1.-0.j, 0.-0.j], [0.-0.j, 0.-1.j]]) >>> qml.eigvals(op) array([1.-0.j, 0.-1.j])
-
A new symbolic operator class
qml.ops.op_math.Pow
represents an operator raised to a power. Whendecomposition()
is called, a list of new operators equal to this one raised to the given power is given: (#2621)>>> op = qml.ops.op_math.Pow(qml.PauliX(0), 0.5) >>> op.decomposition() [SX(wires=[0])] >>> qml.matrix(op) array([[0.5+0.5j, 0.5-0.5j], [0.5-0.5j, 0.5+0.5j]])
-
A new transform
qml.batch_partial
is available which behaves similarly tofunctools.partial
, but supports batching in the unevaluated parameters. (#2585)This is useful for executing a circuit with a batch dimension in some of its parameters:
dev = qml.device("default.qubit", wires=1) @qml.qnode(dev) def circuit(x, y): qml.RX(x, wires=0) qml.RY(y, wires=0) return qml.expval(qml.PauliZ(wires=0))
>>> batched_partial_circuit = qml.batch_partial(circuit, x=np.array(np.pi / 4)) >>> y = np.array([0.2, 0.3, 0.4]) >>> batched_partial_circuit(y=y) tensor([0.69301172, 0.67552491, 0.65128847], requires_grad=True)
-
A new transform
qml.split_non_commuting
is available, which splits a quantum function or tape into multiple functions/tapes determined by groups of commuting observables: (#2587)dev = qml.device("default.qubit", wires=1) @qml.transforms.split_non_commuting @qml.qnode(dev) def circuit(x): qml.RX(x,wires=0) return [qml.expval(qml.PauliX(0)), qml.expval(qml.PauliZ(0))]
>>> print(qml.draw(circuit)(0.5)) 0: ──RX(0.50)─┤ <X> \ 0: ──RX(0.50)─┤ <Z>
Improvements
-
Expectation values of multiple non-commuting observables from within a single QNode are now supported: (#2587)
>>> dev = qml.device('default.qubit', wires=1) >>> @qml.qnode(dev) ... def circuit_rx(x, z): ... qml.RX(x, wires=0) ... qml.RZ(z, wires=0) ... return qml.expval(qml.PauliX(0)), qml.expval(qml.PauliY(0)) >>> circuit_rx(0.1, 0.3) tensor([ 0.02950279, -0.09537451], requires_grad=True)
-
Selecting which parts of parameter-shift Hessians are computed is now possible. (#2538)
The
argnum
keyword argument forqml.gradients.param_shift_hessian
is now allowed to be a two-dimensional Booleanarray_like
. Only the indicated entries of the Hessian will then be computed.A particularly useful example is the computation of the diagonal of the Hessian:
dev = qml.device("default.qubit", wires=1) @qml.qnode(dev) def circuit(x): qml.RX(x[0], wires=0) qml.RY(x[1], wires=0) qml.RX(x[2], wires=0) return qml.expval(qml.PauliZ(0)) argnum = qml.math.eye(3, dtype=bool) x = np.array([0.2, -0.9, 1.1], requires_grad=True)
>>> qml.gradients.param_shift_hessian(circuit, argnum=argnum)(x) tensor([[-0.09928388, 0. , 0. ], [ 0. , -0.27633945, 0. ], [ 0. , 0. , -0.09928388]], requires_grad=True)
-
Commuting Pauli operators are now measured faster. (#2425)
The logic that checks for qubit-wise commuting (QWC) observables has been improved, resulting in a performance boost that is noticable when many commuting Pauli operators of the same type are measured.
-
It is now possible to add
Observable
objects to the integer0
, for exampleqml.PauliX(wires=[0]) + 0
. (#2603) -
Wires can now be passed as the final argument to an
Operator
, instead of requiring the wires to be explicitly specified with keywordwires
. This functionality already existed forObservable
s, but now extends to allOperator
s: (#2432)>>> qml.S(0) S(wires=[0]) >>> qml.CNOT((0,1)) CNOT(wires=[0, 1])
-
The
qml.taper
function can now be used to consistently taper any additional observables such as dipole moment, particle number, and spin operators using the symmetries obtained from the Hamiltonian. (#2510) -
Sparse Hamiltonians' representation has changed from Coordinate (COO) to Compressed Sparse Row (CSR) format. (#2561)
The CSR representation is more performant for arithmetic operations and matrix-vector products. This change decreases the
expval()
calculation time forqml.SparseHamiltonian
, specially for large workflows. In addition, the CSR format consumes less memory forqml.SparseHamiltonian
storage. -
IPython now displays the
str
representation of aHamiltonian
, rather than therepr
. This displays more information about the object.(#2648) -
The
qml.qchem
tests have been restructured. (#2593) (#2545)-
OpenFermion-dependent tests are now localized and collected in
tests.qchem.of_tests
. The new moduletest_structure
is created to collect the tests of theqchem.structure
module in one place and remove their dependency to OpenFermion. -
Test classes have been created to group the integrals and matrices unit tests.
-
-
An
operations_only
argument is introduced to thetape.get_parameters
method. (#2543) -
The
gradients
module now uses faster subroutines and uniform formats of gradient rules. (#2452) -
Instead of checking types, objects are now processed in the
QuantumTape
based on a new_queue_category
property. This is a temporary fix that will disappear in the future. (#2408) -
The
QNode
class now contains a new methodbest_method_str
that returns the best differentiation method for a provided device and interface, in human-readable format. (#2533) -
Using
Operation.inv()
in a queuing environment no longer updates the queue's metadata, but merely updates the operation in place. (#2596) -
A new method
safe_update_info
is added toqml.QueuingContext
. This method is substituted forqml.QueuingContext.update_info
in a variety of places. (#2612) (#2675) -
BasisEmbedding
can accept an int as argument instead of a list of bits. (#2601)For example,
qml.BasisEmbedding(4, wires = range(4))
is now equivalent toqml.BasisEmbedding([0,1,0,0], wires = range(4))
(as4==0b100
). -
Introduced a new
is_hermitian
property toOperator
s to determine if an operator can be used in a measurement process. (#2629) -
Added separate
requirements_dev.txt
for separation of concerns for code development and just using PennyLane. (#2635) -
The performance of building sparse Hamiltonians has been improved by accumulating the sparse representation of coefficient-operator pairs in a temporary storage and by eliminating unnecessary
kron
operations on identity matrices. (#2630) -
Control values are now displayed distinctly in text and matplotlib drawings of circuits. (#2668)
-
The
TorchLayer
init_method
argument now accepts either atorch.nn.init
function or a dictionary which should specify atorch.nn.init
/torch.Tensor
for each different weight. (#2678) -
The unused keyword argument
do_queue
forOperation.adjoint
is now fully removed. (#2583) -
Several non-decomposable
Adjoint
operators are added to the device test suite. (#2658) -
The developer-facing
pow
method has been added toOperator
with concrete implementations for many classes. (#2225) -
The
ctrl
transform andControlledOperation
have been moved to the newqml.ops.op_math
submodule. The developer-facingControlledOperation
class is no longer imported top-level. (#2656)
Deprecations
-
qml.ExpvalCost
has been deprecated, and usage will now raise a warning. (#2571)Instead, it is recommended to simply pass Hamiltonians to the
qml.expval
function inside QNodes:@qml.qnode(dev) def ansatz(params): some_qfunc(params) return qml.expval(Hamiltonian)
Breaking changes
-
When using
qml.TorchLayer
, weights with negative shapes will now raise an error, while weights withsize = 0
will result in creating empty Tensor objects. (#2678) -
PennyLane no longer supports TensorFlow
<=2.3
. (#2683) -
The
qml.queuing.Queue
class has been removed. (#2599) -
The
qml.utils.expand
function is now removed;qml.operation.expand_matrix
should be used instead. (#2654) -
The module
qml.gradients.param_shift_hessian
has been renamed toqml.gradients.parameter_shift_hessian
in order to distinguish it from the identically named function. Note that theparam_shift_hessian
function is unaffected by this change and can be invoked in the same manner as before via theqml.gradients
module. (#2528) -
The properties
eigval
andmatrix
from theOperator
class were replaced with the methodseigval()
andmatrix(wire_order=None)
. (#2498) -
Operator.decomposition()
is now an instance method, and no longer accepts parameters. (#2498) -
Adds tests, adds no-coverage directives, and removes inaccessible logic to improve code coverage. (#2537)
-
The base classes
QubitDevice
andDefaultQubit
now accept data-types for a statevector. This enables a derived class (device) in a plugin to choose correct data-types: (#2448)>>> dev = qml.device("default.qubit", wires=4, r_dtype=np.float32, c_dtype=np.complex64) >>> dev.R_DTYPE <class 'numpy.float32'> >>> dev.C_DTYPE <class 'numpy.complex64'>
Bug fixes
-
Fixed a bug where returning
qml.density_matrix
using the PyTorch interface would return a density matrix with wrong shape. (#2643) -
Fixed a bug to make
param_shift_hessian
work with QNodes in which gates marked as trainable do not have any impact on the QNode output. (#2584) -
QNodes can now interpret variations on the interface name, like
"tensorflow"
or"jax-jit"
, when requesting backpropagation. (#2591) -
Fixed a bug for
diff_method="adjoint"
where incorrect gradients were computed for QNodes with parametrized observables (e.g.,qml.Hermitian
). (#2543) -
Fixed a bug where
QNGOptimizer
did not work with operators whose generator was a Hamiltonian. (#2524) -
Fixed a bug with the decomposition of
qml.CommutingEvolution
. (#2542) -
Fixed a bug enabling PennyLane to work with the latest version of Autoray. (#2549)
-
Fixed a bug which caused different behaviour for
Hamiltonian @ Observable
andObservable @ Hamiltonian
. (#2570) -
Fixed a bug in
DiagonalQubitUnitary._controlled
where an invalid operation was queued instead of the controlled version of the diagonal unitary. (#2525) -
Updated the gradients fix to only apply to the
strawberryfields.gbs
device, since the original logic was breaking some devices. (#2485) (#2595) -
Fixed a bug in
qml.transforms.insert
where operations were not inserted after gates within a template. (#2704) -
Hamiltonian.wires
is now properly updated after in place operations. (#2738)
Documentation
-
The centralized Xanadu Sphinx Theme is now used to style the Sphinx documentation. (#2450)
-
Added a reference to
qml.utils.sparse_hamiltonian
inqml.SparseHamiltonian
to clarify how to construct sparse Hamiltonians in PennyLane. (2572) -
Added a new section in the Gradients and Training page that summarizes the supported device configurations and provides justification. In addition, code examples were added for some selected configurations. (#2540)
-
Added a note for the [Depolarization Channel (https://pennylane.readthedocs.io/en/stable/code/api/pennylane.DepolarizingChannel.html) that specifies how the channel behaves for the different values of depolarization probability
p
. (#2669) -
The quickstart documentation has been improved. (#2530) (#2534) (#2564 (#2565 (#2566) (#2607) (#2608)
-
The quantum chemistry quickstart documentation has been improved. (#2500)
-
Testing documentation has been improved. (#2536)
-
Documentation for the
pre-commit
package has been added. (#2567) -
Documentation for draw control wires change has been updated. (#2682)
Contributors
This release contains contributions from (in alphabetical order):
Guillermo Alonso-Linaje, Mikhail Andrenkov, Juan Miguel Arrazola, Ali Asadi, Utkarsh Azad, Samuel Banning, Avani Bhardwaj, Thomas Bromley, Albert Mitjans Coma, Isaac De Vlugt, Amintor Dusko, Trent Fridey, Christian Gogolin, Qi Hu, Katharine Hyatt, David Ittah, Josh Izaac, Soran Jahangiri, Edward Jiang, Nathan Killoran, Korbinian Kottmann, Ankit Khandelwal, Christina Lee, Chae-Yeun Park, Mason Moreland, Romain Moyard, Maria Schuld, Jay Soni, Antal Száva, tal66, David Wierichs, Roeland Wiersema, WingCode.