New features since last release
Efficient state preparation methods 🦾
-
State preparation tailored for matrix product states (MPS) is now supported with :class:
qml.MPSPrep <pennylane.MPSPrep>
on thelightning.tensor
device. (#6431)Given a list of $n$ tensors that represents an MPS, $[A^{(0)}, ..., A^{(n-1)}]$, :class:
qml.MPSPrep <pennylane.MPSPrep>
lets you directly inject the MPS into a QNode as the initial state of the circuit without any need for pre-processing. The first and last tensors in the list must be rank-2, while all intermediate tensors should be rank-3.import pennylane as qml import numpy as np mps = [ np.array([[0.0, 0.107], [0.994, 0.0]]), np.array( [ [[0.0, 0.0, 0.0, -0.0], [1.0, 0.0, 0.0, -0.0]], [[0.0, 1.0, 0.0, -0.0], [0.0, 0.0, 0.0, -0.0]], ] ), np.array( [ [[-1.0, 0.0], [0.0, 0.0]], [[0.0, 0.0], [0.0, 1.0]], [[0.0, -1.0], [0.0, 0.0]], [[0.0, 0.0], [1.0, 0.0]], ] ), np.array([[-1.0, -0.0], [-0.0, -1.0]]), ] dev = qml.device("lightning.tensor", wires = [0, 1, 2, 3]) @qml.qnode(dev) def circuit(): qml.MPSPrep(mps, wires = [0,1,2]) return qml.state()
>>> print(circuit()) [ 0. +0.j 0. +0.j 0. +0.j -0.1066+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.9943+0.j 0. +0.j 0. +0.j 0. +0.j]
At this time, :class:
qml.MPSPrep <pennylane.MPSPrep>
is only supported on thelightning.tensor
device. -
Custom-made state preparation for linear combinations of quantum states is now available with :class:
qml.Superposition <pennylane.Superposition>
. (#6670)Given a list of $m$ coefficients $c_i$ and basic states $|b_i\rangle$, :class:
qml.Superposition <pennylane.Superposition>
prepares $|\phi\rangle = \sum_i^m c_i |b_i\rangle$. Here is a simple example showing how to use :class:qml.Superposition <pennylane.Superposition>
to prepare $\tfrac{1}{\sqrt{2}} |00\rangle + \tfrac{1}{\sqrt{2}} |10\rangle$.coeffs = np.array([0.70710678, 0.70710678]) basis = np.array([[0, 0], [1, 0]]) @qml.qnode(qml.device('default.qubit')) def circuit(): qml.Superposition(coeffs, basis, wires=[0, 1], work_wire=[2]) return qml.state()
>>> circuit() Array([0.7071068 +0.j, 0. +0.j, 0. +0.j, 0. +0.j, 0.70710677+0.j, 0. +0.j, 0. +0.j, 0. +0.j], dtype=complex64)
Note that specification of one
work_wire
is required.
Enhanced QSVT functionality 🤩
-
New functionality to calculate and convert phase angles for QSP and QSVT has been added with :func:
qml.poly_to_angles <pennylane.poly_to_angles>
and :func:qml.transform_angles <pennylane.transform_angles>
. (#6483)The :func:
qml.poly_to_angles <pennylane.poly_to_angles>
function calculates phase angles directly given polynomial coefficients and the routine in which the angles will be used ("QSVT"
,"QSP"
, or"GQSP"
):>>> poly = [0, 1.0, 0, -1/2, 0, 1/3] >>> qsvt_angles = qml.poly_to_angles(poly, "QSVT") >>> print(qsvt_angles) [-5.49778714 1.57079633 1.57079633 0.5833829 1.61095884 0.74753829]
The :func:
qml.transform_angles <pennylane.transform_angles>
function can be used to convert angles from one routine to another:>>> qsp_angles = np.array([0.2, 0.3, 0.5]) >>> qsvt_angles = qml.transform_angles(qsp_angles, "QSP", "QSVT") >>> print(qsvt_angles) [-6.86858347 1.87079633 -0.28539816]
-
The :func:
qml.qsvt <pennylane.qsvt>
function has been improved to be more user-friendly, with enhanced capabilities. (#6520) (#6693)Block encoding and phase angle computation are now handled automatically, given a matrix to encode, polynomial coefficients, and a block encoding method (
"prepselprep"
,"qubitization"
,"embedding"
, or"fable"
, all implemented with their corresponding operators in PennyLane).# P(x) = -x + 0.5 x^3 + 0.5 x^5 poly = np.array([0, -1, 0, 0.5, 0, 0.5]) hamiltonian = qml.dot([0.3, 0.7], [qml.Z(1), qml.X(1) @ qml.Z(2)]) dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(): qml.qsvt(hamiltonian, poly, encoding_wires=[0], block_encoding="prepselprep") return qml.state() matrix = qml.matrix(circuit, wire_order=[0, 1, 2])()
>>> print(matrix[:4, :4].real) [[-0.1625 0. -0.3793 0. ] [ 0. -0.1625 0. 0.3793] [-0.3793 0. 0.1625 0. ] [ 0. 0.3793 0. 0.1625]]
The old functionality can still be accessed with :func:
qml.qsvt_legacy <pennylane.qsvt_legacy>
. -
A new :class:
qml.GQSP <pennylane.GQSP>
template has been added to perform Generalized Quantum Signal Processing (GQSP). (#6565) Similar to QSVT, GQSP is an algorithm that polynomially transforms an input unitary operator, but with fewer restrictions on the chosen polynomial.You can also use :func:
qml.poly_to_angles <pennylane.poly_to_angles>
to obtain angles for GQSP!# P(x) = 0.1 + 0.2j x + 0.3 x^2 poly = [0.1, 0.2j, 0.3] angles = qml.poly_to_angles(poly, "GQSP") @qml.prod # transforms the qfunc into an Operator def unitary(wires): qml.RX(0.3, wires) dev = qml.device("default.qubit") @qml.qnode(dev) def circuit(angles): qml.GQSP(unitary(wires = 1), angles, control = 0) return qml.state() matrix = qml.matrix(circuit, wire_order=[0, 1])(angles) ``` ```pycon >>> print(np.round(matrix,3)[:2, :2]) [[0.387+0.198j 0.03 -0.089j] [0.03 -0.089j 0.387+0.198j]]
Generalized Trotter products 🐖
-
Trotter products that work on exponentiated operators directly instead of full system hamiltonians can now be encoded into circuits with the addition of :class:
qml.TrotterizedQfunc <pennylane.TrotterizedQfunc>
and :func:qml.trotterize <pennylane.trotterize>
. This allows for custom specification of the first-order expansion of the Suzuki-Trotter product formula and extrapolating it to the $n^{\text{th}}$ order. (#6627)If the first-order of the Suzuki-Trotter product formula for a given problem is known, :class:
qml.TrotterizedQfunc <pennylane.TrotterizedQfunc>
and :func:qml.trotterize <pennylane.trotterize>
let you implement the $n^{\text{th}}$-order product formula while only specifying the first-order term as a quantum function.def my_custom_first_order_expansion(time, theta, phi, wires, flip): qml.RX(time * theta, wires[0]) qml.RY(time * phi, wires[1]) if flip: qml.CNOT(wires=wires[:2])
:func:
qml.trotterize <pennylane.trotterize>
requires the quantum function representing the first-order product formula, the number of Trotter steps, and the desired order. It returns a function with the same call signature as the first-order product formula quantum function:@qml.qnode(qml.device("default.qubit")) def my_circuit(time, theta, phi, num_trotter_steps): qml.trotterize( first_order_expansion, n=num_trotter_steps, order=2, )(time, theta, phi, wires=['a', 'b'], flip=True) return qml.state()
Alternatively, :class:
qml.TrotterizedQfunc <pennylane.TrotterizedQfunc>
can be used as follows:@qml.qnode(qml.device("default.qubit")) def my_circuit(time, theta, phi, num_trotter_steps): qml.TrotterizedQfunc( time, theta, phi, qfunc=my_custom_first_order_expansion, n=num_trotter_steps, order=2, wires=['a', 'b'], flip=True, ) return qml.state()
>>> time = 0.1 >>> theta, phi = (0.12, -3.45) >>> print(qml.draw(my_circuit, level="device")(time, theta, phi, num_trotter_steps=1)) a: ──RX(0.01)──╭●─╭●──RX(0.01)──┤ State b: ──RY(-0.17)─╰X─╰X──RY(-0.17)─┤ State
Both methods produce the same results, but offer different interfaces based on the application or overall preference.
Bosonic operators 🎈
A new module, :mod:qml.bose <pennylane.bose>
, has been added to PennyLane that includes support for constructing and manipulating Bosonic operators and converting between Bosonic operators and qubit operators.
-
Bosonic operators analogous to
qml.FermiWord
andqml.FermiSentence
are now available with :class:qml.BoseWord <pennylane.BoseWord>
and :class:qml.BoseSentence <pennylane.BoseSentence>
. (#6518):class:
qml.BoseWord <pennylane.BoseWord>
and :class:qml.BoseSentence <pennylane.BoseSentence>
operate similarly to their fermionic counterparts. To create a Bose word, a dictionary is required as input, where the keys are tuples of boson indices and values are'+/-'
(denoting the bosonic creation/annihilation operators). For example, the $b^{\dagger}_0 b_1$ can be constructed as follows.>>> w = qml.BoseWord({(0, 0) : '+', (1, 1) : '-'}) >>> print(w) b⁺(0) b(1)
Multiple Bose words can then be combined to form a Bose sentence:
>>> w1 = qml.BoseWord({(0, 0) : '+', (1, 1) : '-'}) >>> w2 = qml.BoseWord({(0, 1) : '+', (1, 2) : '-'}) >>> s = qml.BoseSentence({w1 : 1.2, w2: 3.1}) >>> print(s) 1.2 * b⁺(0) b(1) + 3.1 * b⁺(1) b(2)
-
Functionality for converting bosonic operators to qubit operators is available with :func:
qml.unary_mapping <pennylane.unary_mapping>
, :func:qml.binary_mapping <pennylane.binary_mapping>
, and :func:qml.christiansen_mapping <pennylane.christiansen_mapping>
. (#6623) (#6576) (#6564)All three mappings follow the same syntax, where a :class:
qml.BoseWord <pennylane.BoseWord>
or :class:qml.BoseSentence <pennylane.BoseSentence>
is required as input.>>> w = qml.BoseWord({(0, 0): "+"}) >>> qml.binary_mapping(w, n_states=4) 0.6830127018922193 * X(0) + -0.1830127018922193 * X(0) @ Z(1) + -0.6830127018922193j * Y(0) + 0.1830127018922193j * Y(0) @ Z(1) + 0.3535533905932738 * X(0) @ X(1) + -0.3535533905932738j * X(0) @ Y(1) + 0.3535533905932738j * Y(0) @ X(1) + (0.3535533905932738+0j) * Y(0) @ Y(1)
Additional fine-tuning is available within each function, such as the maximum number of allowed bosonic states and a tolerance for discarding imaginary parts of the coefficients.
Construct vibrational Hamiltonians 🔨
-
Several new features are available in the :mod:
qml.qchem <pennylane.qchem>
module to help with the construction of vibrational Hamiltonians. This includes:- The :class:
~.qchem.VibrationalPES
class to store potential energy surface information. (#6652)
pes_onemode = np.array([[0.309, 0.115, 0.038, 0.008, 0.000, 0.006, 0.020, 0.041, 0.070]]) pes_twomode = np.zeros((1, 1, 9, 9)) dipole_onemode = np.zeros((1, 9, 3)) gauss_weights = np.array([3.96e-05, 4.94e-03, 8.85e-02, 4.33e-01, 7.20e-01, 4.33e-01, 8.85e-02, 4.94e-03, 3.96e-05]) grid = np.array([-3.19, -2.27, -1.47, -0.72, 0.0, 0.72, 1.47, 2.27, 3.19]) pes_object = qml.qchem.VibrationalPES( freqs=np.array([0.025]), grid=grid, uloc=np.array([[1.0]]), gauss_weights=gauss_weights, pes_data=[pes_onemode, pes_twomode], dipole_data=[dipole_onemode], localized=False, dipole_level=1, )
- The :func:
~.qchem.taylor_hamiltonian
function to build a Taylor Hamiltonian from a :class:~.VibrationalPES
object. (#6523)
>>> qml.qchem.taylor_hamiltonian(pes_object, 4, 2) ( 0.016867926879358452 * I(0) + -0.007078617919572303 * Z(0) + 0.0008679410939323631 * X(0) )
- The :func:
~.qchem.taylor_bosonic
function to build a Taylor Hamiltonian in terms of Bosonic operators. (#6523)
>>> coeffs_arr = qml.qchem.taylor_coeffs(pes_object) >>> bose_op = qml.qchem.taylor_bosonic(coeffs_arr, pes_object.freqs, is_local=pes_object.localized, uloc=pes_object.uloc) >>> type(bose_op) pennylane.bose.bosonic.BoseSentence
Additional functionality is also available to optimize molecular geometries and convert between representations:
-
Convert Christiansen Hamiltonian integrals in the harmonic oscillator basis to integrals in the vibrational self-consistent field (VSCF) basis with the :func:
~.qchem.vscf_integrals
function. (#6688) -
Find the lowest energy configuration of molecules with :func:
~.qchem.optimize_geometry
. (#6453) (#6666) -
Separate normal mode frequencies and localize them with :func:
~.qchem.localize_normal_modes
. (#6453)
- The :class:
Labs: a place for unified and rapid prototyping of research software 🧑🔬
The new :mod:qml.labs <pennylane.labs>
module will house experimental research software 🔬. Features here may be useful for state-of-the-art research, beta testing, or getting a sneak peek into potential new features before they are added to PennyLane.
The experimental nature of this module means features may not integrate well with other PennyLane staples like differentiability, JAX, or JIT compatibility. There may also be unexpected sharp bits 🔪 and errors ❌.
.. warning:: This module is experimental! Breaking changes and removals will happen without warning. Please use these features carefully and let us know your thoughts. Your feedback will inform how these features become a part of mainline PennyLane.
Resource estimation
-
Resource estimation functionality in Labs is focused on being light-weight and flexible. The Labs :mod:
qml.labs.resource_estimation <pennylane.labs.resource_estimation>
module involves modifications to core PennyLane that reduce the memory requirements and computational time of resource estimation. These include new or modified base classes and one new function:- :class:
~.labs.resource_estimation.Resources
- This class is simplified inlabs
, removing the arguments:gate_sizes
,depth
, andshots
. (#6428) - :class:
~.labs.resource_estimation.ResourceOperator
- Replaces :class:~.resource.ResourceOperation
, expanded to include decompositions. (#6428) - :class:
~.labs.resource_estimation.CompressedResourceOp
- A new class with the minimum information to estimate resources: the operator type and the parameters needed to decompose it. (#6428) - :class:
~.labs.resource_estimation.ResourceOperator
- versions of many existing PennyLane operations, like Pauli operators, :class:~.labs.resource_estimation.ResourceHadamard
, and :class:~.labs.resource_estimation.ResourceCNOT
. (#6447) (#6579) (#6538) (#6592) - :func:
~.labs.resource_estimation.get_resources()
- The new entry point to efficiently obtain the resources of quantum circuits. (#6500)
Using new Resource versions of existing operations and :func:
~.labs.resource_estimation.get_resources
, we can estimate resources quickly:import pennylane.labs.resource_estimation as re def my_circuit(): for w in range(2): re.ResourceHadamard(w) re.ResourceCNOT([0, 1]) re.ResourceRX(1.23, 0) re.ResourceRY(-4.56, 1) re.ResourceQFT(wires=[0, 1, 2]) return qml.expval(re.ResourceHadamard(2))
>>> res = re.get_resources(my_circuit)() >>> print(res) wires: 3 gates: 202 gate_types: {'Hadamard': 5, 'CNOT': 10, 'T': 187}
We can also set custom gate sets for decompositions:
>>> gate_set={"Hadamard","CNOT","RZ", "RX", "RY", "SWAP"} >>> res = re.get_resources(my_circuit, gate_set=gate_set)() >>> print(res) wires: 3 gates: 24 gate_types: {'Hadamard': 5, 'CNOT': 7, 'RX': 1, 'RY': 1, 'SWAP': 1, 'RZ': 9}
Alternatively, it is possible to manually substitute associated resources:
>>> new_resources = re.substitute(res, "SWAP", re.Resources(2, 3, {"CNOT":3})) >>> print(new_resources) {'Hadamard': 5, 'CNOT': 10, 'RX': 1, 'RY': 1, 'RZ': 9}
- :class:
Experimental functionality for handling dynamical Lie algebras (DLAs)
-
Use the :mod:
qml.labs.dla <pennylane.labs.dla>
module to perform the KAK decomposition:- :func:
~.labs.dla.cartan_decomp
: obtain a Cartan decomposition of an input Lie algebra via an involution. (#6392) - We provide a variety of involutions like :func:
~.labs.dla.concurrence_involution
, :func:~.labs.dla.even_odd_involution
and canonical Cartan involutions. (#6392) (#6396) - :func:
~.labs.dla.cartan_subalgebra
: compute a horizontal Cartan subalgebra. (#6403) - :func:
~.labs.dla.variational_kak_adj
: compute a variational KAK decomposition of a Hermitian operator using a Cartan decomposition and the adjoint representation of a horizontal Cartan subalgebra. (#6446)
To use this functionality we start with a set of Hermitian operators.
>>> n = 3 >>> gens = [qml.X(i) @ qml.X(i + 1) for i in range(n - 1)] >>> gens += [qml.Z(i) for i in range(n)] >>> H = qml.sum(*gens)
We then generate its Lie algebra by computing the Lie closure.
>>> g = qml.lie_closure(gens) >>> g = [op.pauli_rep for op in g] >>> print(g) [1 * X(0) @ X(1), 1 * X(1) @ X(2), 1.0 * Z(0), ...]
We then choose an involution (e.g. :func:
~.labs.dla.concurrence_involution
) that defines a Cartan decompositiong = k + m
.k
is the vertical subalgebra, andm
its horizontal complement (not a subalgebra).>>> from pennylane.labs.dla import concurrence_involution, cartan_decomp >>> involution = concurrence_involution >>> k, m = cartan_decomp(g, involution=involution)
The next step is just re-ordering the basis elements in
g
and computing itsstructure_constants
.>>> g = k + m >>> adj = qml.structure_constants(g)
We can then compute a (horizontal) Cartan subalgebra
a
, that is, a maximal Abelian subalgebra ofm
.>>> from pennylane.labs.dla import cartan_subalgebra >>> g, k, mtilde, a, adj = cartan_subalgebra(g, k, m, adj)
Having determined both subalgebras
k
anda
, we can compute the KAK decomposition variationally like in 2104.00728, see our demo on KAK decomposition in practice.>>> from pennylane.labs.dla import variational_kak_adj >>> dims = (len(k), len(mtilde), len(a)) >>> adjvec_a, theta_opt = variational_kak_adj(H, g, dims, adj, opt_kwargs={"n_epochs": 3000})
- :func:
-
We also provide some additional features that are useful for handling dynamical Lie algebras.
- :func:
~.labs.dla.recursive_cartan_decomp
: perform consecutive recursive Cartan decompositions. (#6396) - :func:
~.labs.dla.lie_closure_dense
: extension ofqml.lie_closure
using dense matrices. (#6371) (#6695) - :func:
~.labs.dla.structure_constants_dense
: extension ofqml.structure_constants
using dense matrices. (#6396) (#6376)
- :func:
Vibrational Hamiltonians
-
New functionality in labs helps with the construction of vibrational Hamiltonians.
>>> symbols = ['H', 'F'] >>> geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]]) >>> mol = qml.qchem.Molecule(symbols, geometry) >>> pes = vibrational_pes(mol)
- Use the
qml.labs.vibrational.christiansen_hamiltonian
function and potential energy surfaces to generate Hamiltonians in the Christiansen form. (#6560)
- Use the
Improvements 🛠
QChem improvements
-
The
qml.qchem.factorize
function now supports new methods for double factorization: Cholesky decomposition (cholesky=True
) and compressed double factorization (compressed=True
). (#6573) (#6611) -
A new function for performing the block-invariant symmetry shift on electronic integrals has been added with
qml.qchem.symmetry_shift
. (#6574) -
The differentiable Hartree-Fock workflow is now compatible with JAX. (#6096) (#6707)
Transform for combining GlobalPhase instances
- A new transform called
qml.transforms.combine_global_phases
has been added. It combines allqml.GlobalPhase
gates in a circuit into a single one applied at the end. This can be useful for circuits that include a lot ofqml.GlobalPhase
gates that are introduced directly during circuit creation, decompositions that includeqml.GlobalPhase
gates, etc. (#6686)
Better drawing functionality
-
qml.draw_mpl
now has awire_options
keyword argument, which allows for global- and per-wire customization with options likecolor
,linestyle
, andlinewidth
. (#6486)Here is an example that would make all wires cyan and bold except for wires 2 and 6, which are dashed and a different colour.
@qml.qnode(qml.device("default.qubit")) def circuit(x): for w in range(5): qml.Hadamard(w) return qml.expval(qml.PauliZ(0) @ qml.PauliY(1)) wire_options = {"color": "cyan", "linewidth": 5, 2: {"linestyle": "--", "color": "red"}, 6: {"linestyle": "--", "color": "orange"} } print(qml.draw_mpl(circuit, wire_options=wire_options)(0.52))
New device capabilities 💾
-
Two new methods,
setup_execution_config
andpreprocess_transforms
, have been added to theDevice
class. Device developers are encouraged to override these two methods separately instead of thepreprocess
method. For now, to avoid ambiguity, a device is allowed to override either these two methods orpreprocess
, but not both. In the long term, we will slowly phase out the use ofpreprocess
in favour of these two methods for better separation of concerns. (#6617) -
Developers of plugin devices now have the option of providing a TOML-formatted configuration file to declare the capabilities of the device. See Device Capabilities for details.
-
An internal module called
qml.devices.capabilities
has been added that defines a newDeviceCapabilites
data class, as well as functions that load and parse the TOML-formatted configuration files. (#6407)>>> from pennylane.devices.capabilities import DeviceCapabilities >>> capabilities = DeviceCapabilities.from_toml_file("my_device.toml") >>> isinstance(capabilities, DeviceCapabilities) True
-
Devices that extend
qml.devices.Device
now have an optional class attribute calledcapabilities
, which is an instance of theDeviceCapabilities
data class constructed from the configuration file if it exists. Otherwise, it is set toNone
. (#6433)from pennylane.devices import Device class MyDevice(Device): config_filepath = "path/to/config.toml" ...
>>> isinstance(MyDevice.capabilities, DeviceCapabilities) True
-
Default implementations of
Device.setup_execution_config
andDevice.preprocess_transforms
have been added to the device API for devices that provide a TOML configuration file and, thus, have acapabilities
property. (#6632) (#6653)
Capturing and representing hybrid programs
-
Support has been added for
if
/else
statements andfor
andwhile
loops in circuits executed withqml.capture.enabled
, via Autograph. Autograph conversion is now used by default inmake_plxpr
, but can be skipped withautograph=False
. (#6406) (#6413) (#6426) (#6645) (#6685) -
qml.transform
now accepts aplxpr_transform
argument. This argument must be a function that can transform plxpr. Note that executing a transformed function will currently raise aNotImplementedError
. To see more details, check out the :func:documentation of qml.transform <pennylane.transform>
. (#6633) (#6722) -
Users can now apply transforms with program capture enabled. Transformed functions cannot be executed by default. To apply the transforms (and to be able to execute the function), it must be decorated with the new
qml.capture.expand_plxpr_transforms
function, which accepts a callable as input and returns a new function for which all present transforms have been applied. (#6722)from functools import partial qml.capture.enable() wire_map = {0: 3, 1: 6, 2: 9} @partial(qml.map_wires, wire_map=wire_map) def circuit(x, y): qml.RX(x, 0) qml.CNOT([0, 1]) qml.CRY(y, [1, 2]) return qml.expval(qml.Z(2))
>>> qml.capture.make_plxpr(circuit)(1.2, 3.4) { lambda ; a:f32[] b:f32[]. let c:AbstractMeasurement(n_wires=None) = _map_wires_transform_transform[ args_slice=slice(0, 2, None) consts_slice=slice(2, 2, None) inner_jaxpr={ lambda ; d:f32[] e:f32[]. let _:AbstractOperator() = RX[n_wires=1] d 0 _:AbstractOperator() = CNOT[n_wires=2] 0 1 _:AbstractOperator() = CRY[n_wires=2] e 1 2 f:AbstractOperator() = PauliZ[n_wires=1] 2 g:AbstractMeasurement(n_wires=None) = expval_obs f in (g,) } targs_slice=slice(2, None, None) tkwargs={'wire_map': {0: 3, 1: 6, 2: 9}, 'queue': False} ] a b in (c,) } >>> transformed_circuit = qml.capture.expand_plxpr_transforms(circuit) >>> jax.make_jaxpr(transformed_circuit)(1.2, 3.4) { lambda ; a:f32[] b:f32[]. let _:AbstractOperator() = RX[n_wires=1] a 3 _:AbstractOperator() = CNOT[n_wires=2] 3 6 _:AbstractOperator() = CRY[n_wires=2] b 6 9 c:AbstractOperator() = PauliZ[n_wires=1] 9 d:AbstractMeasurement(n_wires=None) = expval_obs c in (d,) }
-
The
qml.iterative_qpe
function can now be compactly captured into plxpr. (#6680) -
Three new plxpr interpreters have been added that allow for functions and plxpr to be natively transformed with the same API as the corresponding existing transforms in PennyLane when program capture is enabled:
-
qml.capture.transforms.CancelInterpreter
:this class cancels operators appearing consecutively that are adjoints of each other following the same API asqml.transforms.cancel_inverses
. (#6692) -
qml.capture.transforms.DecomposeInterpreter
: this class decomposes pennylane operators following the same API asqml.transforms.decompose
. (#6691) -
qml.capture.transforms.MapWiresInterpreter
: this class maps wires to new values following the same API asqml.map_wires
. (#6697)
-
-
A
qml.tape.plxpr_to_tape
function is now available that converts plxpr to a tape. (#6343) -
Execution with capture enabled now follows a new execution pipeline and natively passes the captured plxpr to the device. Since it no longer falls back to the old pipeline, execution only works with a reduced feature set. (#6655) (#6596)
-
PennyLane transforms can now be captured as primitives with experimental program capture enabled. (#6633)
-
jax.vmap
can be captured withqml.capture.make_plxpr
and is compatible with quantum circuits. (#6349) (#6422) (#6668) -
A
qml.capture.PlxprInterpreter
base class has been added for easy transformation and execution of plxpr. (#6141) -
A
DefaultQubitInterpreter
class has been added to provide plxpr execution using python based tools, and theDefaultQubit.eval_jaxpr
method has been implemented. (#6594) (#6328) -
An optional method,
eval_jaxpr
, has been added to the device API for native execution of plxpr programs. (#6580) -
qml.capture.qnode_call
has been made private and moved to theworkflow
module. (#6620)
Other Improvements
-
qml.math.grad
andqml.math.jacobian
have been added to differentiate a function with inputs of any interface in a JAX-like manner. (#6741) -
qml.GroverOperator
now has awork_wires
property. (#6738) -
The
Wires
object's usage across Pennylane source code has been tidied up for internal consistency. (#6689) -
qml.equal
now supportsqml.PauliWord
andqml.PauliSentence
instances. (#6703) -
Redundant commutator computations from
qml.lie_closure
have been removed. (#6724) -
A comprehensive error is now raised when using
qml.fourier.qnode_spectrum
with standard Numpy arguments andinterface="auto"
. (#6622) -
Pauli string representations for the gates
{X, Y, Z, S, T, SX, SWAP, ISWAP, ECR, SISWAP}
have been added, and a shape error in the matrix conversion ofqml.PauliSentence
s withlist
orarray
inputs has been fixed. (#6562) (#6587) -
qml.QNode
andqml.execute
now forbid certain keyword arguments from being passed positionally. (#6610) -
The string representations for the
qml.S
,qml.T
, andqml.SX
have been shortened. (#6542) -
Internal class functions and dunder methods have been added to allow for multiplying Resources objects in series and in parallel. (#6567)
-
The
diagonalize_measurements
transform no longer raises an error for unknown observables. Instead, they are left un-diagonalized, with the expectation that observable validation will catch any un-diagonalized observables that are also unsupported by the device. (#6653) -
A
qml.wires.Wires
object can now be converted to a JAX array, if all wire labels are supported as JAX array elements. (#6699) -
A developer focused
run
function has been added to theqml.workflow
module for a cleaner and standardized approach to executing tapes on an ML interface. (#6657) -
Internal changes have been made to standardize execution interfaces, which resolves ambiguities in how the
interface
value is handled during execution. (#6643) -
All interface handling logic has been moved to
interface_utils.py
in theqml.math
module. (#6649) -
qml.execute
can now be used withdiff_method="best"
. Classical cotransform information is now handled lazily by the workflow. Gradient method validation and program setup are now handled inside ofqml.execute
, instead of inQNode
. (#6716) -
PyTree support for measurements in a circuit has been added. (#6378)
@qml.qnode(qml.device("default.qubit")) def circuit(): qml.Hadamard(0) qml.CNOT([0,1]) return {"Probabilities": qml.probs(), "State": qml.state()}
>>> circuit() {'Probabilities': array([0.5, 0. , 0. , 0.5]), 'State': array([0.70710678+0.j, 0. +0.j, 0. +0.j, 0.70710678+0.j])}
-
The
_cache_transform
transform has been moved to its own file located inpennylane/workflow/_cache_transform.py
. (#6624) -
The
qml.BasisRotation
template is now JIT compatible. (#6019) (#6779) -
The Jaxpr primitives for
for_loop
,while_loop
andcond
now store slices instead of numbers of arguments. This helps with keeping track of what order the arguments come in. (#6521) -
The
ExecutionConfig.gradient_method
function has been expanded to storeTransformDispatcher
type. (#6455) -
The string representation of
Resources
instances has been improved to match the attribute names. (#6581) -
The documentation for the
dynamic_one_shot
transform has been improved, and a warning is raised when a user-applieddynamic_one_shot
transform is ignored in favour of the existing transform in a device's preprocessing transform program. (#6701) -
A
qml.devices.qubit_mixed
module has been added for mixed-state qubit device support. This module introduces anapply_operation
helper function that features:- Two density matrix contraction methods using
einsum
andtensordot
- Optimized handling of special cases including: Diagonal operators, Identity operators, CX (controlled-X), Multi-controlled X gates, Grover operators (#6379)
- Two density matrix contraction methods using
-
A function called
create_initial_state
has been added to allow for initializing a circuit with a density matrix usingqml.StatePrep
orqml.QubitDensityMatrix
. (#6503) -
Several additions have been made to eventually migrate the
"default.mixed"
device to the new device API:- A
preprocess
method has been added to theQubitMixed
device class to preprocess the quantum circuit before execution. (#6601) - A new class called
DefaultMixedNewAPI
has been added to theqml.devices.qubit_mixed
module, which will replace the legacyDefaultMixed
. (#6607) - A new submodule called
devices.qubit_mixed.measure
has been added, featuring ameasure
function for measuring qubits in mixed-state devices. (#6637) - A new submodule called
devices.qubit_mixed.simulate
has been added, featuring asimulate
function for simulating mixed states in analytic mode. (#6618) - A new submodule called
devices.qubit_mixed.sampling
has been added, featuring functionssample_state
,measure_with_samples
andsample_probs
for sampling qubits in mixed-state devices. (#6639) - The finite-shot branch of
devices.qubit_mixed.simulate
has been added, which allows for accepting stochastic arguments such asshots
,rng
andprng_key
. (#6665) - Support for
qml.Snapshot
has been added. (#6659)
- A
-
Reporting of test warnings as failures has been added. (#6217)
-
A warning message in the Gradients and training documentation has been added that pertains to
ComplexWarning
s. (#6543) -
A new figure was added to the landing page of the PennyLane website. (#6696)
Breaking changes 💔
-
The default graph coloring method of
qml.dot
,qml.sum
, andqml.pauli.optimize_measurements
for grouping observables was changed from"rlf"
to"lf"
. Internally,qml.pauli.group_observables
has been replaced withqml.pauli.compute_partition_indices
in several places to improve efficiency. (#6706) -
qml.fourier.qnode_spectrum
no longer automatically converts pure Numpy parameters to the Autograd framework. As the function uses automatic differentiation for validation, parameters from such a framework have to be used. (#6622) -
qml.math.jax_argnums_to_tape_trainable
has been moved and made private to avoid an unnecessary QNode dependency in theqml.math
module. (#6609) -
Gradient transforms are now applied after the user's transform program. This ensures user transforms work as expected on initial structures (e.g., embeddings or entangling layers), guarantees that gradient transforms only process compatible operations, aligns transform order with user expectations, and avoids confusion. (#6590)
-
Legacy operator arithmetic has been removed. This includes
qml.ops.Hamiltonian
,qml.operation.Tensor
,qml.operation.enable_new_opmath
,qml.operation.disable_new_opmath
, andqml.operation.convert_to_legacy_H
. Note thatqml.Hamiltonian
will continue to dispatch toqml.ops.LinearCombination
. For more information, check out the updated operator troubleshooting page. (#6548) (#6602) (#6589) -
The developer-facing
qml.utils
module has been removed. (#6588):Specifically, the following 4 sets of functions have been either moved or removed:
qml.utils._flatten
,qml.utils.unflatten
has been moved and renamed toqml.optimize.qng._flatten_np
andqml.optimize.qng._unflatten_np
respectively.qml.utils._inv_dict
andqml._get_default_args
have been removed.qml.utils.pauli_eigs
has been moved toqml.pauli.utils
.qml.utils.expand_vector
has been moved toqml.math.expand_vector
.
-
The
qml.qinfo
module has been removed. Please use the corresponding functions in theqml.math
andqml.measurements
modules instead. (#6584) -
Top level access to
Device
,QubitDevice
, andQutritDevice
have been removed. Instead, they are available asqml.devices.LegacyDevice
,qml.devices.QubitDevice
, andqml.devices.QutritDevice
, respectively. (#6537) -
The
'ancilla'
argument forqml.iterative_qpe
has been removed. Instead, use the'aux_wire'
argument. (#6532) -
The
qml.BasisStatePreparation
template has been removed. Instead, useqml.BasisState
. (#6528) -
The
qml.workflow.set_shots
helper function has been removed. We no longer interact with the legacy device interface in our code. Instead, shots should be specified on the tape, and the device should use these shots. (#6534) -
QNode.gradient_fn
has been removed. Please useQNode.diff_method
instead.QNode.get_gradient_fn
can also be used to process the differentiation method. (#6535) -
The
qml.QubitStateVector
template has been removed. Instead, useqml.StatePrep
. (#6525) -
qml.broadcast
has been removed. Users should usefor
loops instead. (#6527) -
The
max_expansion
argument forqml.transforms.clifford_t_decomposition
has been removed. (#6571) -
The
expand_depth
argument forqml.compile
has been removed. (#6531) -
The
qml.shadows.shadow_expval
transform has been removed. Instead, please use theqml.shadow_expval
measurement process. (#6530) (#6561) -
The developer-facing
qml.drawer.MPLDrawer
argumentn_wires
has been replaced withwire_map
, which contains more complete information about wire labels and order. This allows the new functionality to specifywire_options
for specific wires when using string wire labels or non-sequential wire ordering. (#6805)
Deprecations 👋
-
The
tape
andqtape
properties ofQNode
have been deprecated. Instead, use theqml.workflow.construct_tape
function. (#6583) (#6650) -
The
max_expansion
argument inqml.devices.preprocess.decompose
is deprecated and will be removed in v0.41. (#6400) -
The
decomp_depth
argument inqml.transforms.set_decomposition
is deprecated and will be removed in v0.41. (#6400) -
The
output_dim
property ofqml.tape.QuantumScript
has been deprecated. Instead, use methodshape
ofQuantumScript
orMeasurementProcess
to get the same information. (#6577) -
The
QNode.get_best_method
andQNode.best_method_str
methods have been deprecated. Instead, use theqml.workflow.get_best_diff_method
function. (#6418) -
The
qml.execute
gradient_fn
keyword argument has been renamed todiff_method
to better align with the termionology used by the QNode.gradient_fn
will be removed in v0.41. (#6549) -
The old
qml.qsvt
functionality is moved toqml.qsvt_legacy
and is now deprecated. It will be removed in v0.41. (#6520)
Documentation 📝
-
The docstrings for
qml.qchem.Molecule
andqml.qchem.molecular_hamiltonian
have been updated to include a note that says that they are not compatible withqjit
orjit
. (#6702) -
The documentation of
TrotterProduct
has been updated to include the impact of the operands in the Hamiltonian on the structure of the created circuit. (#6629) -
The documentation of
QSVT
has been updated to include examples for different block encodings. (#6673) -
The link to
qml.ops.one_qubit_transform
was fixed in theQubitUnitary
docstring. (#6745)
Bug fixes 🐛
-
Validation has been added to ensure that the device vjp is only used when the device actually supports it. (#6755)
-
qml.counts
now returns all outcomes when theall_outcomes
argument isTrue
and mid-circuit measurements are present. (#6732) -
qml.ControlledQubitUnitary
now has consistent behaviour with program capture enabled. (#6719) -
The
Wires
object now throws aTypeError
ifwires=None
. (#6713) (#6720) -
The
qml.Hermitian
class no longer checks that the provided matrix is hermitian. The reason for this removal is to allow for faster execution and avoid incompatibilities withjax.jit
. (#6642) -
Subclasses of
qml.ops.Controlled
no longer bind the primitives of their base operators when program capture is enabled. (#6672) -
The
qml.HilbertSchmidt
andqml.LocalHilbertSchmidt
templates now apply the complex conjugate of the unitaries instead of the adjoint, providing the correct result. (#6604) -
QNode return behaviour is now consistent for lists and tuples. (#6568)
-
QNodes now accept arguments with types defined in libraries that are not necessarily in the list of supported interfaces, such as the
Graph
class defined innetworkx
. (#6600) -
qml.math.get_deep_interface
now works properly for Autograd arrays. (#6557) -
Printing instances of
qml.Identity
now returns the correct wires list. (#6506)
Contributors ✍️
This release contains contributions from (in alphabetical order):
Guillermo Alonso, Shiwen An, Utkarsh Azad, Astral Cai, Yushao Chen, Isaac De Vlugt, Diksha Dhawan, Lasse Dierich, Lillian Frederiksen, Pietropaolo Frisoni, Simone Gasperini, Diego Guala, Austin Huang, Korbinian Kottmann, Christina Lee, Alan Martin, William Maxwell, Anton Naim Ibrahim, Andrija Paurevic, Justin Pickering, Jay Soni, David Wierichs.