PennyLane is a cross-platform Python library for quantum computing, quantum machine learning, and quantum chemistry. Train a quantum computer the same way as a neural network.
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!
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
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)
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)
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.
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)
Three qutrit rotation operators have been added that are analogous to RX
, RY
, and RZ
:
qml.TRX
: an X rotationqml.TRY
: a Y rotationqml.TRZ
: a Z rotationQutrit devices now support parameter-shift differentiation. (#2845)
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)
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)
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-readablestr
: converting Shots
instances to human-readable strings==
: equating two different Shots
instanceshash
: obtaining the hash values of Shots
instancesqml.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)
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)
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)
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)
LieAlgebraOptimizer
has been renamed to RiemannianGradientOptimizer
. (#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)
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)
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)
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.
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 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)]
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!
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.
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
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)
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)
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)
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.
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)
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)
Nothing for this release!
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)
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.
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. #3864
Ensured 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)
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.
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!
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.
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.
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])]
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
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 calledqml.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)
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)
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)
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)
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)
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)
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)
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)
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)
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.
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 measurementStateMeasurement
: represents a state-based measurementMeasurementTransform
: represents a measurement process that requires the application of a batch transformCreating 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
.
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.
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]
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^{ix\hat{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
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]])
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
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)
>>> pw = qml.pauli.PauliWord({0: 'X', 1: 'Y'})
>>> print(pw.operation())
PauliX(wires=[0]) @ PauliY(wires=[1])
>>> print(pw.hamiltonian())
(1) [X0 Y1]
>>> ps = qml.pauli.PauliSentence({pw: -1.23})
>>> print(ps.operation())
-1.23*(PauliX(wires=[0]) @ PauliY(wires=[1]))
>>> print(ps.hamiltonian())
(-1.23) [X0 Y1]
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)
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)))
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)
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 from
def statistics(self, observables, shot_range=None, bin_size=None, circuit=None):
to
def statistics(self, circuit: QuantumTape, shot_range=None, bin_size=None):
MeasurementProcess
class is now an abstract class and return_type
is now a property of the class. (#3434)
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()
insteadqml.transforms.qcut.remap_tape_wires
: Use qml.map_wires
insteadqml.tape.QuantumTape.inv()
: Use qml.tape.QuantumTape.adjoint()
insteadqml.tape.stop_recording()
: Use qml.QueuingManager.stop_recording()
insteadqml.tape.QuantumTape.stop_recording()
: Use qml.QueuingManager.stop_recording()
insteadqml.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)
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)
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)
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.
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.
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.
QNodes now accept an auto
interface argument which automatically detects the machine learning library to use. (#3132)
from pennylane import numpy as np
import torch
import tensorflow as tf
from jax import numpy as jnp
dev = qml.device("default.qubit", wires=2)
@qml.qnode(dev, interface="auto")
def circuit(weight):
qml.RX(weight[0], wires=0)
qml.RY(weight[1], wires=1)
return qml.expval(qml.PauliZ(0))
interface_tensors = [[0, 1], np.array([0, 1]), torch.Tensor([0, 1]), tf.Variable([0, 1], dtype=float), jnp.array([0, 1])]
for tensor in interface_tensors:
res = circuit(weight=tensor)
print(f"Result value: {res:.2f}; Result type: {type(res)}")
Result value: 1.00; Result type: <class 'pennylane.numpy.tensor.tensor'>
Result value: 1.00; Result type: <class 'pennylane.numpy.tensor.tensor'>
Result value: 1.00; Result type: <class 'torch.Tensor'>
Result value: 1.00; Result type: <class 'tensorflow.python.framework.ops.EagerTensor'>
Result value: 1.00; Result type: <class 'jaxlib.xla_extension.DeviceArray'>
JAX-JIT support for computing the gradient of QNodes that return a single vector of probabilities or multiple expectation values is now available. (#3244) (#3261)
import jax
from jax import numpy as jnp
from jax.config import config
config.update("jax_enable_x64", True)
dev = qml.device("lightning.qubit", wires=2)
@jax.jit
@qml.qnode(dev, diff_method="parameter-shift", interface="jax")
def circuit(x, y):
qml.RY(x, wires=0)
qml.RY(y, wires=1)
qml.CNOT(wires=[0, 1])
return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1))
x = jnp.array(1.0)
y = jnp.array(2.0)
>>> jax.jacobian(circuit, argnums=[0, 1])(x, y)
(DeviceArray([-0.84147098, 0.35017549], dtype=float64, weak_type=True),
DeviceArray([ 4.47445479e-18, -4.91295496e-01], dtype=float64, weak_type=True))
Note that this change depends on jax.pure_callback
, which requires jax>=0.3.17
.
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]])
qml.enable_return()
now supports QNodes returning multiple measurements, including shots vectors, and gradient output types. (#2886) (#3052) (#3041) (#3090) (#3069) (#3137) (#3127) (#3099) (#3098) (#3095) (#3091) (#3176) (#3170) (#3194) (#3267) (#3234) (#3232) (#3223) (#3222) (#3315)
In v0.25, we introduced qml.enable_return()
, which separates measurements into their own tensors. The motivation of this change is the deprecation of ragged ndarray
creation in NumPy.
With this release, we're continuing to elevate this feature by adding support for:
Execution (qml.execute
)
Jacobian vector product (JVP) computation
Gradient transforms (qml.gradients.param_shift
, qml.gradients.finite_diff
, qml.gradients.hessian_transform
, qml.gradients.param_shift_hessian
).
Interfaces (Autograd, TensorFlow, and JAX, although without JIT)
With this added support, the JAX interface can handle multiple shots (shots vectors), measurements, and gradient output types with qml.enable_return()
:
import jax
qml.enable_return()
dev = qml.device("default.qubit", wires=2, shots=(1, 10000))
params = jax.numpy.array([0.1, 0.2])
@qml.qnode(dev, interface="jax", diff_method="parameter-shift", max_diff=2)
def circuit(x):
qml.RX(x[0], wires=[0])
qml.RY(x[1], wires=[1])
qml.CNOT(wires=[0, 1])
return qml.var(qml.PauliZ(0) @ qml.PauliX(1)), qml.probs(wires=[0])
>>> jax.hessian(circuit)(params)
((DeviceArray([[ 0., 0.],
[ 2., -3.]], dtype=float32),
DeviceArray([[[-0.5, 0. ],
[ 0. , 0. ]],
[[ 0.5, 0. ],
[ 0. , 0. ]]], dtype=float32)),
(DeviceArray([[ 0.07677898, 0.0563341 ],
[ 0.07238522, -1.830669 ]], dtype=float32),
DeviceArray([[[-4.9707499e-01, 2.9999996e-04],
[-6.2500127e-04, 1.2500001e-04]],
[[ 4.9707499e-01, -2.9999996e-04],
[ 6.2500127e-04, -1.2500001e-04]]], dtype=float32)))
For more details, please refer to the documentation.
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 :math:\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)
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 with
arbitrary 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 :math:\vert n \rangle
, where :math:n
is a positive integer, and a fixed positive integer :math:L
, qml.IntegerComparator
flips a target qubit if :math:n \geq L
. Alternatively, the flipping condition can be :math: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)
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.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. (#3049)
>>> 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)
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)
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)
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)
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)
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.
A minor postfix release to update the documentation styling.
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)
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:
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.qml.TAdd
and qml.TSWAP
operations which are the qutrit analogs of the CNOT and SWAP operations, respectively.qml.QutritUnitary
.qml.state
and qml.probs
measurements.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.
The qml.simplify()
function has several intuitive improvements with this release. (#2978) (#2982) (#2922) (#3012)
qml.simplify
can now perform the following:
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])]
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 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.
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']
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)
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)
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)
Fixed a bug with qml.ops.Exp
operators when the coefficient is autograd but the diagonalizing gates don't act on all wires. (#3057)
Fixed a bug where the tape transform single_qubit_fusion
computed wrong rotation angles for specific combinations of rotations. (#3024)
Jax gradients now work with a QNode when the quantum function was transformed by qml.simplify
. (#3017)
Operators that have num_wires = AnyWires
or num_wires = AnyWires
now raise an error, with certain exceptions, when instantiated with wires=[]
. (#2979)
Fixed a bug where printing qml.Hamiltonian
with complex coefficients raises TypeError
in some cases. (#3005)
Added a more descriptive error message when measuring non-commuting observables at the end of a circuit with probs
, samples
, counts
and allcounts
. (#3065)
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.
qml.math.array
and qml.math.eye
to preserve the Torch device used. (#2967)
This release contains contributions from (in alphabetical order):
Romain Moyard, Rashid N H M, Lee James O'Riordan, Antal Száva.
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 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
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)
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
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)
The quantum information module now supports computation of relative entropy. (#2772)
We've enabled two cases for calculating the relative entropy:
A QNode transform via qml.qinfo.relative_entropy
:
dev = qml.device('default.qubit', wires=2)
@qml.qnode(dev)
def circuit(param):
qml.RY(param, wires=0)
qml.CNOT(wires=[0, 1])
return qml.state()
>>> relative_entropy_circuit = qml.qinfo.relative_entropy(circuit, circuit, wires0=[0], wires1=[0])
>>> x, y = np.array(0.4), np.array(0.6)
>>> relative_entropy_circuit((x,), (y,))
0.017750012490703237
Support in qml.math
for flexible post-processing:
>>> rho = np.array([[0.3, 0], [0, 0.7]])
>>> sigma = np.array([[0.5, 0], [0, 0.5]])
>>> qml.math.relative_entropy(rho, sigma)
tensor(0.08228288, requires_grad=True)
A new measurement called qml.counts
is available. (#2686) (#2839) (#2876)
QNodes with shots != None
that return qml.counts
will yield a dictionary whose keys are bitstrings representing computational basis states that were measured, and whose values are the corresponding counts (i.e., how many times that computational basis state was measured):
dev = qml.device("default.qubit", wires=2, shots=1000)
@qml.qnode(dev)
def circuit():
qml.Hadamard(wires=0)
qml.CNOT(wires=[0, 1])
return qml.counts()
>>> circuit()
{'00': 495, '11': 505}
qml.counts
can also accept observables, where the resulting dictionary is ordered by the eigenvalues of the observable.
dev = qml.device("default.qubit", wires=2, shots=1000)
@qml.qnode(dev)
def circuit():
qml.Hadamard(wires=0)
qml.CNOT(wires=[0, 1])
return qml.counts(qml.PauliZ(0)), qml.counts(qml.PauliZ(1))
>>> circuit()
({-1: 470, 1: 530}, {-1: 470, 1: 530})
A new experimental return type for QNodes with multiple measurements has been added. (#2814) (#2815) (#2860)
QNodes returning a list or tuple of different measurements return an intuitive data structure via qml.enable_return()
, where the individual measurements are separated into their own tensors:
qml.enable_return()
dev = qml.device("default.qubit", wires=2)
@qml.qnode(dev)
def circuit(x):
qml.Hadamard(wires=[0])
qml.CRX(x, wires=[0, 1])
return (qml.probs(wires=[0]), qml.vn_entropy(wires=[0]), qml.probs(wires=0), qml.expval(wires=1))
>>> circuit(0.5)
(tensor([0.5, 0.5], requires_grad=True), tensor(0.08014815, requires_grad=True), tensor([0.5, 0.5], requires_grad=True), tensor(0.93879128, requires_grad=True))
In addition, QNodes that utilize this new return type support backpropagation. This new return type can be disabled thereafter via qml.disable_return()
.
An operator called qml.FlipSign
is now available. (#2780)
Mathematically, qml.FlipSign
functions as follows: $\text{FlipSign}(n) \vert m \rangle = (-1)^\delta_{n,m} \vert m \rangle$, where $\vert m \rangle$ is an arbitrary qubit state and $n$ is a qubit configuration:
basis_state = [0, 1]
dev = qml.device("default.qubit", wires=2)
@qml.qnode(dev)
def circuit():
for wire in list(range(2)):
qml.Hadamard(wires = wire)
qml.FlipSign(basis_state, wires = list(range(2)))
return qml.state()
>>> circuit()
tensor([ 0.5+0.j -0.5+0.j 0.5+0.j 0.5+0.j], requires_grad=True)
The simultaneous perturbation stochastic approximation (SPSA) optimizer is available via qml.SPSAOptimizer
. (#2661)
The SPSA optimizer is suitable for cost functions whose evaluation may involve noise. Use the SPSA optimizer like you would any other optimizer:
max_iterations = 50
opt = qml.SPSAOptimizer(maxiter=max_iterations)
for _ in range(max_iterations):
params, cost = opt.step_and_cost(cost, params)
sketch
and sketch_dark
styles are now available for drawing circuit diagram graphics. (#2709)
default.qubit
now natively executes any operation that defines a matrix except for trainable Pow
operations. (#2836)
Added expm
to the qml.math
module for matrix exponentiation. (#2890)
When adjoint differentiation is requested, circuits are now decomposed so that all trainable operations have a generator. (#2836)
A warning is now emitted for qml.state
, qml.density_matrix
, qml.vn_entropy
, and qml.mutual_info
when using a device with finite shots or a shot list since these measurements are always analytic. (#2918)
The efficiency of the Hartree-Fock workflow has been improved by removing repetitive steps. (#2850)
The coefficients of the non-differentiable molecular Hamiltonians generated with openfermion now have requires_grad = False
by default. (#2865)
Upgraded performance of the compute_matrix
method of broadcastable parametric operations. (#2759)
Jacobians are now cached with the Autograd interface when using the parameter-shift rule. (#2645)
The qml.state
and qml.density_matrix
measurements now support custom wire labels. (#2779)
Add trivial behaviour logic to qml.operation.expand_matrix
. (#2785)
Added an are_pauli_words_qwc
function which checks if certain Pauli words are pairwise qubit-wise commuting. This new function improves performance when measuring hamiltonians with many commuting terms. (#2789)
Adjoint differentiation now uses the adjoint symbolic wrapper instead of in-place inversion. (#2855)
The deprecated qml.hf
module is removed. Users with code that calls qml.hf
can simply replace qml.hf
with qml.qchem
in most cases, or refer to the qchem documentation and demos for more information. (#2795)
default.qubit
now uses stopping_condition
to specify support for anything with a matrix. To override this behavior in inheriting devices and to support only a specific subset of operations, developers need to override stopping_condition
. (#2836)
Custom devices inheriting from DefaultQubit
or QubitDevice
can break due to the introduction of parameter broadcasting. (#2627)
A custom device should only break if all three following statements hold simultaneously:
DefaultQubit
, not QubitDevice
.expval
, apply_operation
or analytic_probability
)."supports_broadcasting": True
in its capabilities
dictionary or it overwrites Device.batch_transform
without applying broadcast_expand
(or both).The capabilities["supports_broadcasting"]
is set to True
for DefaultQubit
. Typically, the easiest fix will be to change capabilities["supports_broadcasting"]
flag to False
for the child device and/or to include a call to broadcast_expand
in CustomDevice.batch_transform
, similar to how Device.batch_transform
calls it.
Separately from the above, custom devices that inherit from QubitDevice
and implement a custom _gather
method need to allow for the kwarg axis
to be passed to this _gather
method.
The argument argnum
of the function qml.batch_input
has been redefined: now it indicates the indices of the batched parameters, which need to be non-trainable, in the quantum tape. Consequently, its default value (set to 0) has been removed. (#2873)
Before this breaking change, one could call qml.batch_input
without any arguments when using batched inputs as the first argument of the quantum circuit.
dev = qml.device("default.qubit", wires=2, shots=None)
@qml.batch_input() # argnum = 0
@qml.qnode(dev, diff_method="parameter-shift", interface="tf")
def circuit(inputs, weights): # argument `inputs` is batched
qml.RY(weights[0], wires=0)
qml.AngleEmbedding(inputs, wires=range(2), rotation="Y")
qml.RY(weights[1], wires=1)
return qml.expval(qml.PauliZ(1))
With this breaking change, users must set a value to argnum
specifying the index of the batched inputs with respect to all quantum tape parameters. In this example the quantum tape parameters are [ weights[0], inputs, weights[1] ]
, thus argnum
should be set to 1, specifying that inputs
is batched:
dev = qml.device("default.qubit", wires=2, shots=None)
@qml.batch_input(argnum=1)
@qml.qnode(dev, diff_method="parameter-shift", interface="tf")
def circuit(inputs, weights):
qml.RY(weights[0], wires=0)
qml.AngleEmbedding(inputs, wires=range(2), rotation="Y")
qml.RY(weights[1], wires=1)
return qml.expval(qml.PauliZ(1))
PennyLane now depends on newer versions (>=2.7) of the semantic_version
package, which provides an updated API that is incompatible which versions of the package prior to 2.7. If you run into issues relating to this package, please reinstall PennyLane. (#2744) (#2767)
Added a dedicated docstring for the QubitDevice.sample
method. (#2812)
Optimization examples of using JAXopt and Optax with the JAX interface have been added. (#2769)
Updated IsingXY gate docstring. (#2858)
Fixes qml.equal
so that operators with different inverse properties are not equal. (#2947)
Cleans up interactions between operator arithmetic and batching by testing supported cases and adding errors when batching is not supported. (#2900)
Fixed a bug where the parameter-shift rule wasn't defined for qml.kUpCCGSD
. (#2913)
Reworked the Hermiticity check in qml.Hermitian
by using qml.math
calls because calling .conj()
on an EagerTensor
from TensorFlow raised an error. (#2895)
Fixed a bug where the parameter-shift gradient breaks when using both custom grad_recipe
s that contain unshifted terms and recipes that do not contain any unshifted terms. (#2834)
Fixed mixed CPU-GPU data-locality issues for the Torch interface. (#2830)
Fixed a bug where the parameter-shift Hessian of circuits with untrainable parameters might be computed with respect to the wrong parameters or might raise an error. (#2822)
Fixed a bug where the custom implementation of the states_to_binary
device method was not used. (#2809)
qml.grouping.group_observables
now works when individual wire labels are iterable. (#2752)
The adjoint of an adjoint now has a correct expand
result. (#2766)
Fixed the ability to return custom objects as the expectation value of a QNode with the Autograd interface. (#2808)
The WireCut operator now raises an error when instantiating it with an empty list. (#2826)
Hamiltonians with grouped observables are now allowed to be measured on devices which were transformed using qml.transform.insert()
. (#2857)
Fixed a bug where qml.batch_input
raised an error when using a batched operator that was not located at the beginning of the circuit. In addition, now qml.batch_input
raises an error when using trainable batched inputs, which avoids an unwanted behaviour with duplicated parameters. (#2873)
Calling qml.equal
with nested operators now raises a NotImplementedError
. (#2877)
Fixed a bug where a non-sensible error message was raised when using qml.counts
with shots=False
. (#2928)
Fixed a bug where no error was raised and a wrong value was returned when using qml.counts
with another non-commuting observable. (#2928)
Operator Arithmetic now allows Hamiltonian
objects to be used and produces correct matrices. (#2957)
This release contains contributions from (in alphabetical order):
Juan Miguel Arrazola, Utkarsh Azad, Samuel Banning, Prajwal Borkar, Isaac De Vlugt, Olivia Di Matteo, Kristiyan Dilov, David Ittah, Josh Izaac, Soran Jahangiri, Edward Jiang, Ankit Khandelwal, Korbinian Kottmann, Meenu Kumari, Christina Lee, Sergio Martínez-Losa, Albert Mitjans Coma, Ixchel Meza Chavez, Romain Moyard, Lee James O'Riordan, Mudit Pandey, Bogdan Reznychenko, Shuli Shu, Jay Soni, Modjtaba Shokrian-Zini, Antal Száva, David Wierichs, Moritz Willmann.