New features since last release
Differentiable Hartree-Fock solver
-
A differentiable Hartree-Fock (HF) solver has been added. It can be used to construct molecular Hamiltonians that can be differentiated with respect to nuclear coordinates and basis-set parameters. (#1610)
The HF solver computes the integrals over basis functions, constructs the relevant matrices, and performs self-consistent-field iterations to obtain a set of optimized molecular orbital coefficients. These coefficients and the computed integrals over basis functions are used to construct the one- and two-body electron integrals in the molecular orbital basis which can be used to generate a differentiable second-quantized Hamiltonian in the fermionic and qubit basis.
The following code shows the construction of the Hamiltonian for the hydrogen molecule where the geometry of the molecule is differentiable.
symbols = ["H", "H"] geometry = np.array([[0.0000000000, 0.0000000000, -0.6943528941], [0.0000000000, 0.0000000000, 0.6943528941]], requires_grad=True) mol = qml.hf.Molecule(symbols, geometry) args_mol = [geometry] hamiltonian = qml.hf.generate_hamiltonian(mol)(*args_mol)
>>> hamiltonian.coeffs tensor([-0.09041082+0.j, 0.17220382+0.j, 0.17220382+0.j, 0.16893367+0.j, 0.04523101+0.j, -0.04523101+0.j, -0.04523101+0.j, 0.04523101+0.j, -0.22581352+0.j, 0.12092003+0.j, -0.22581352+0.j, 0.16615103+0.j, 0.16615103+0.j, 0.12092003+0.j, 0.17464937+0.j], requires_grad=True)
The generated Hamiltonian can be used in a circuit where the atomic coordinates and circuit parameters are optimized simultaneously.
symbols = ["H", "H"] geometry = np.array([[0.0000000000, 0.0000000000, 0.0], [0.0000000000, 0.0000000000, 2.0]], requires_grad=True) mol = qml.hf.Molecule(symbols, geometry) dev = qml.device("default.qubit", wires=4) params = [np.array([0.0], requires_grad=True)] def generate_circuit(mol): @qml.qnode(dev) def circuit(*args): qml.BasisState(np.array([1, 1, 0, 0]), wires=[0, 1, 2, 3]) qml.DoubleExcitation(*args[0][0], wires=[0, 1, 2, 3]) return qml.expval(qml.hf.generate_hamiltonian(mol)(*args[1:])) return circuit for n in range(25): mol = qml.hf.Molecule(symbols, geometry) args = [params, geometry] # initial values of the differentiable parameters g_params = qml.grad(generate_circuit(mol), argnum = 0)(*args) params = params - 0.5 * g_params[0] forces = qml.grad(generate_circuit(mol), argnum = 1)(*args) geometry = geometry - 0.5 * forces print(f'Step: {n}, Energy: {generate_circuit(mol)(*args)}, Maximum Force: {forces.max()}')
In addition, the new Hartree-Fock solver can further be used to optimize the basis set parameters. For details, please refer to the differentiable Hartree-Fock solver documentation.
Integration with Mitiq
-
Error mitigation using the zero-noise extrapolation method is now available through the
transforms.mitigate_with_zne
transform. This transform can integrate with the Mitiq package for unitary folding and extrapolation functionality. (#1813)Consider the following noisy device:
noise_strength = 0.05 dev = qml.device("default.mixed", wires=2) dev = qml.transforms.insert(qml.AmplitudeDamping, noise_strength)(dev)
We can mitigate the effects of this noise for circuits run on this device by using the added
transform:from mitiq.zne.scaling import fold_global from mitiq.zne.inference import RichardsonFactory n_wires = 2 n_layers = 2 shapes = qml.SimplifiedTwoDesign.shape(n_wires, n_layers) np.random.seed(0) w1, w2 = [np.random.random(s) for s in shapes] @qml.transforms.mitigate_with_zne([1, 2, 3], fold_global, RichardsonFactory.extrapolate) @qml.beta.qnode(dev) def circuit(w1, w2): qml.SimplifiedTwoDesign(w1, w2, wires=range(2)) return qml.expval(qml.PauliZ(0))
Now, when we execute
circuit
, errors will be automatically mitigated:>>> circuit(w1, w2) 0.19113067083636542
Powerful new transforms
-
The unitary matrix corresponding to a quantum circuit can now be generated using the new
get_unitary_matrix()
transform. (#1609) (#1786)This transform is fully differentiable across all supported PennyLane autodiff frameworks.
def circuit(theta): qml.RX(theta, wires=1) qml.PauliZ(wires=0) qml.CNOT(wires=[0, 1])
>>> theta = torch.tensor(0.3, requires_grad=True) >>> matrix = qml.transforms.get_unitary_matrix(circuit)(theta) >>> print(matrix) tensor([[ 0.9888+0.0000j, 0.0000+0.0000j, 0.0000-0.1494j, 0.0000+0.0000j], [ 0.0000+0.0000j, 0.0000+0.1494j, 0.0000+0.0000j, -0.9888+0.0000j], [ 0.0000-0.1494j, 0.0000+0.0000j, 0.9888+0.0000j, 0.0000+0.0000j], [ 0.0000+0.0000j, -0.9888+0.0000j, 0.0000+0.0000j, 0.0000+0.1494j]], grad_fn=<MmBackward>) >>> loss = torch.real(torch.trace(matrix)) >>> loss.backward() >>> theta.grad tensor(-0.1494)
-
Arbitrary two-qubit unitaries can now be decomposed into elementary gates. This functionality has been incorporated into the
qml.transforms.unitary_to_rot
transform, and is available separately asqml.transforms.two_qubit_decomposition
. (#1552)As an example, consider the following randomly-generated matrix and circuit that uses it:
U = np.array([ [-0.03053706-0.03662692j, 0.01313778+0.38162226j, 0.4101526 -0.81893687j, -0.03864617+0.10743148j], [-0.17171136-0.24851809j, 0.06046239+0.1929145j, -0.04813084-0.01748555j, -0.29544883-0.88202604j], [ 0.39634931-0.78959795j, -0.25521689-0.17045233j, -0.1391033 -0.09670952j, -0.25043606+0.18393466j], [ 0.29599198-0.19573188j, 0.55605806+0.64025769j, 0.06140516+0.35499559j, 0.02674726+0.1563311j ] ]) dev = qml.device('default.qubit', wires=2) @qml.qnode(dev) @qml.transforms.unitary_to_rot def circuit(x, y): qml.QubitUnitary(U, wires=[0, 1]) return qml.expval(qml.PauliZ(wires=0))
If we run the circuit, we can see the new decomposition:
>>> circuit(0.3, 0.4) tensor(-0.81295986, requires_grad=True) >>> print(qml.draw(circuit)(0.3, 0.4)) 0: ──Rot(2.78, 0.242, -2.28)──╭X──RZ(0.176)───╭C─────────────╭X──Rot(-3.87, 0.321, -2.09)──┤ ⟨Z⟩ 1: ──Rot(4.64, 2.69, -1.56)───╰C──RY(-0.883)──╰X──RY(-1.47)──╰C──Rot(1.68, 0.337, 0.587)───┤
-
A new transform,
@qml.batch_params
, has been added, that makes QNodes handle a batch dimension in trainable parameters. (#1710) (#1761)This transform will create multiple circuits, one per batch dimension. As a result, it is both simulator and hardware compatible.
@qml.batch_params @qml.beta.qnode(dev) def circuit(x, weights): qml.RX(x, wires=0) qml.RY(0.2, wires=1) qml.templates.StronglyEntanglingLayers(weights, wires=[0, 1, 2]) return qml.expval(qml.Hadamard(0))
The
qml.batch_params
decorator allows us to pass argumentsx
andweights
that have a batch dimension. For example,>>> batch_size = 3 >>> x = np.linspace(0.1, 0.5, batch_size) >>> weights = np.random.random((batch_size, 10, 3, 3))
If we evaluate the QNode with these inputs, we will get an output of shape
(batch_size,)
:>>> circuit(x, weights) tensor([0.08569816, 0.12619101, 0.21122004], requires_grad=True)
-
The
insert
transform has now been added, providing a way to insert single-qubit operations into a quantum circuit. The transform can apply to quantum functions, tapes, and devices. (#1795)The following QNode can be transformed to add noise to the circuit:
dev = qml.device("default.mixed", wires=2) @qml.qnode(dev) @qml.transforms.insert(qml.AmplitudeDamping, 0.2, position="end") def f(w, x, y, z): qml.RX(w, wires=0) qml.RY(x, wires=1) qml.CNOT(wires=[0, 1]) qml.RY(y, wires=0) qml.RX(z, wires=1) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
Executions of this circuit will differ from the noise-free value:
>>> f(0.9, 0.4, 0.5, 0.6) tensor(0.754847, requires_grad=True) >>> print(qml.draw(f)(0.9, 0.4, 0.5, 0.6)) 0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.2)──╭┤ ⟨Z ⊗ Z⟩ 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.2)──╰┤ ⟨Z ⊗ Z⟩
-
Common tape expansion functions are now available in
qml.transforms
, alongside a newcreate_expand_fn
function for easily creating expansion functions from stopping criteria. (#1734) (#1760)create_expand_fn
takes the default depth to which the expansion function should expand a tape, a stopping criterion, an optional device, and a docstring to be set for the created function. The stopping criterion must take a queuable object and return a boolean.For example, to create an expansion function that decomposes all trainable, multi-parameter operations:
>>> stop_at = ~(qml.operation.has_multipar & qml.operation.is_trainable) >>> expand_fn = qml.transforms.create_expand_fn(depth=5, stop_at=stop_at)
The created expansion function can be used within a custom transform. Devices can also be provided, producing expansion functions that decompose tapes to support the native gate set of the device.
Batch execution of circuits
-
A new, experimental QNode has been added, that adds support for batch execution of circuits, custom quantum gradient support, and arbitrary order derivatives. This QNode is available via
qml.beta.QNode
, and@qml.beta.qnode
. (#1642) (#1646) (#1651) (#1804)It differs from the standard QNode in several ways:
-
Custom gradient transforms can be specified as the differentiation method:
@qml.gradients.gradient_transform def my_gradient_transform(tape): ... return tapes, processing_fn @qml.beta.qnode(dev, diff_method=my_gradient_transform) def circuit():
-
Arbitrary :math:
n
-th order derivatives are supported on hardware using gradient transforms such as the parameter-shift rule. To specify that an :math:n
-th order derivative of a QNode will be computed, themax_diff
argument should be set. By default, this is set to 1 (first-order derivatives only). -
Internally, if multiple circuits are generated for execution simultaneously, they will be packaged into a single job for execution on the device. This can lead to significant performance improvement when executing the QNode on remote quantum hardware.
-
When decomposing the circuit, the default decomposition strategy will prioritize decompositions that result in the smallest number of parametrized operations required to satisfy the differentiation method. Additional decompositions required to satisfy the native gate set of the quantum device will be performed later, by the device at execution time. While this may lead to a slight increase in classical processing, it significantly reduces the number of circuit evaluations needed to compute gradients of complex unitaries.
In an upcoming release, this QNode will replace the existing one. If you come across any bugs while using this QNode, please let us know via a bug report on our GitHub bug tracker.
Currently, this beta QNode does not support the following features:
- Non-mutability via the
mutable
keyword argument - The
reversible
QNode differentiation method - The ability to specify a
dtype
when using PyTorch and TensorFlow.
It is also not tested with the
qml.qnn
module. -
New operations and templates
-
Added a new operation
OrbitalRotation
, which implements the spin-adapted spatial orbital rotation gate. (#1665)An example circuit that uses
OrbitalRotation
operation is:dev = qml.device('default.qubit', wires=4) @qml.qnode(dev) def circuit(phi): qml.BasisState(np.array([1, 1, 0, 0]), wires=[0, 1, 2, 3]) qml.OrbitalRotation(phi, wires=[0, 1, 2, 3]) return qml.state()
If we run this circuit, we will get the following output
>>> circuit(0.1) array([ 0. +0.j, 0. +0.j, 0. +0.j, 0.00249792+0.j, 0. +0.j, 0. +0.j, -0.04991671+0.j, 0. +0.j, 0. +0.j, -0.04991671+0.j, 0. +0.j, 0. +0.j, 0.99750208+0.j, 0. +0.j, 0. +0.j, 0. +0.j])
-
Added a new template
GateFabric
, which implements a local, expressive, quantum-number-preserving ansatz proposed by Anselmetti et al. in arXiv:2104.05692. (#1687)An example of a circuit using
GateFabric
template is:coordinates = np.array([0.0, 0.0, -0.6614, 0.0, 0.0, 0.6614]) H, qubits = qml.qchem.molecular_hamiltonian(["H", "H"], coordinates) ref_state = qml.qchem.hf_state(electrons=2, orbitals=qubits) dev = qml.device('default.qubit', wires=qubits) @qml.qnode(dev) def ansatz(weights): qml.templates.GateFabric(weights, wires=[0,1,2,3], init_state=ref_state, include_pi=True) return qml.expval(H)
For more details, see the GateFabric documentation.
-
Added a new template
kUpCCGSD
, which implements a unitary coupled cluster ansatz with generalized singles and pair doubles excitation operators, proposed by Joonho Lee et al. in arXiv:1810.02327. (#1743)An example of a circuit using
kUpCCGSD
template is:coordinates = np.array([0.0, 0.0, -0.6614, 0.0, 0.0, 0.6614]) H, qubits = qml.qchem.molecular_hamiltonian(["H", "H"], coordinates) ref_state = qml.qchem.hf_state(electrons=2, orbitals=qubits) dev = qml.device('default.qubit', wires=qubits) @qml.qnode(dev) def ansatz(weights): qml.templates.kUpCCGSD(weights, wires=[0,1,2,3], k=0, delta_sz=0, init_state=ref_state) return qml.expval(H)
Improved utilities for quantum compilation and characterization
-
The new
qml.fourier.qnode_spectrum
function extends the formerqml.fourier.spectrum
function and takes classical processing of QNode arguments into account. The frequencies are computed per (requested) QNode argument instead of per gateid
. The gateid
s are ignored. (#1681) (#1720)Consider the following example, which uses non-trainable inputs
x
,y
andz
as well as trainable parametersw
as arguments to the QNode.import pennylane as qml import numpy as np n_qubits = 3 dev = qml.device("default.qubit", wires=n_qubits) @qml.qnode(dev) def circuit(x, y, z, w): for i in range(n_qubits): qml.RX(0.5*x[i], wires=i) qml.Rot(w[0,i,0], w[0,i,1], w[0,i,2], wires=i) qml.RY(2.3*y[i], wires=i) qml.Rot(w[1,i,0], w[1,i,1], w[1,i,2], wires=i) qml.RX(z, wires=i) return qml.expval(qml.PauliZ(wires=0)) x = np.array([1., 2., 3.]) y = np.array([0.1, 0.3, 0.5]) z = -1.8 w = np.random.random((2, n_qubits, 3))
This circuit looks as follows:
>>> print(qml.draw(circuit)(x, y, z, w)) 0: ──RX(0.5)──Rot(0.598, 0.949, 0.346)───RY(0.23)──Rot(0.693, 0.0738, 0.246)──RX(-1.8)──┤ ⟨Z⟩ 1: ──RX(1)────Rot(0.0711, 0.701, 0.445)──RY(0.69)──Rot(0.32, 0.0482, 0.437)───RX(-1.8)──┤ 2: ──RX(1.5)──Rot(0.401, 0.0795, 0.731)──RY(1.15)──Rot(0.756, 0.38, 0.38)─────RX(-1.8)──┤
Applying the
qml.fourier.qnode_spectrum
function to the circuit for the non-trainable parameters, we obtain:>>> spec = qml.fourier.qnode_spectrum(circuit, encoding_args={"x", "y", "z"})(x, y, z, w) >>> for inp, freqs in spec.items(): ... print(f"{inp}: {freqs}") "x": {(0,): [-0.5, 0.0, 0.5], (1,): [-0.5, 0.0, 0.5], (2,): [-0.5, 0.0, 0.5]} "y": {(0,): [-2.3, 0.0, 2.3], (1,): [-2.3, 0.0, 2.3], (2,): [-2.3, 0.0, 2.3]} "z": {(): [-3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0]}
We can see that all three parameters in the QNode arguments
x
andy
contribute the spectrum of a Pauli rotation[-1.0, 0.0, 1.0]
, rescaled with the prefactor of the respective parameter in the circuit. The threeRX
rotations using the parameterz
accumulate, yielding a more complex frequency spectrum.For details on how to control for which parameters the spectrum is computed, a comparison to
qml.fourier.circuit_spectrum
, and other usage details, please see the fourier.qnode_spectrum docstring. -
Two new methods were added to the Device API, allowing PennyLane devices increased control over circuit decompositions. (#1651)
-
Device.expand_fn(tape) -> tape
: expands a tape such that it is supported by the device. By default, performs the standard device-specific gate set decomposition done in the default QNode. Devices may overwrite this method in order to define their own decomposition logic.Note that the numerical result after applying this method should remain unchanged; PennyLane will assume that the expanded tape returns exactly the same value as the original tape when executed.
-
Device.batch_transform(tape) -> (tapes, processing_fn)
: preprocesses the tape in the case where the device needs to generate multiple circuits to execute from the input circuit. The requirement of a post-processing function makes this distinct to theexpand_fn
method above.By default, this method applies the transform
.. math:: \left\langle \sum_i c_i h_i\right\rangle → \sum_i c_i \left\langle h_i \right\rangle
if
expval(H)
is present on devices that do not natively support Hamiltonians with non-commuting terms.
-
-
A new class has been added to store operator attributes, such as
self_inverses
, andcomposable_rotation
, as a list of operation names. (#1763)A number of such attributes, for the purpose of compilation transforms, can be found in
ops/qubit/attributes.py
, but the class can also be used to create your own. For example, we can create a new Attribute,pauli_ops
, like so:>>> from pennylane.ops.qubit.attributes import Attribute >>> pauli_ops = Attribute(["PauliX", "PauliY", "PauliZ"])
We can check either a string or an Operation for inclusion in this set:
>>> qml.PauliX(0) in pauli_ops True >>> "Hadamard" in pauli_ops False
We can also dynamically add operators to the sets at runtime. This is useful for adding custom operations to the attributes such as
composable_rotations
andself_inverses
that are used in compilation transforms. For example, suppose you have created a new Operation,MyGate
, which you know to be its own inverse. Adding it to the set, like so>>> from pennylane.ops.qubit.attributes import self_inverses >>> self_inverses.add("MyGate")
will enable the gate to be considered by the
cancel_inverses
compilation transform if two such gates are adjacent in a circuit.
Improvements
-
The
qml.metric_tensor
transform has been improved with regards to both function and performance. (#1638) (#1721)-
If the underlying device supports batch execution of circuits, the quantum circuits required to compute the metric tensor elements will be automatically submitted as a batched job. This can lead to significant performance improvements for devices with a non-trivial job submission overhead.
-
Previously, the transform would only return the metric tensor with respect to gate arguments, and ignore any classical processing inside the QNode, even very trivial classical processing such as parameter permutation. The metric tensor now takes into account classical processing, and returns the metric tensor with respect to QNode arguments, not simply gate arguments:
>>> @qml.qnode(dev) ... def circuit(x): ... qml.Hadamard(wires=1) ... qml.RX(x[0], wires=0) ... qml.CNOT(wires=[0, 1]) ... qml.RY(x[1] ** 2, wires=1) ... qml.RY(x[1], wires=0) ... return qml.expval(qml.PauliZ(0)) >>> x = np.array([0.1, 0.2], requires_grad=True) >>> qml.metric_tensor(circuit)(x) array([[0.25 , 0. ], [0. , 0.28750832]])
To revert to the previous behaviour of returning the metric tensor with respect to gate arguments,
qml.metric_tensor(qnode, hybrid=False)
can be passed.>>> qml.metric_tensor(circuit, hybrid=False)(x) array([[0.25 , 0. , 0. ], [0. , 0.25 , 0. ], [0. , 0. , 0.24750832]])
-
The metric tensor transform now works with a larger set of operations. In particular, all operations that have a single variational parameter and define a generator are now
supported. In addition to a reduction in decomposition overhead, the change also results in fewer circuit evaluations.
-
-
The expansion rule in the
qml.metric_tensor
transform has been changed. (#1721)If
hybrid=False
, the changed expansion rule might lead to a changed output. -
The
ApproxTimeEvolution
template can now be used with Hamiltonians that have trainable coefficients. (#1789)The resulting QNodes can be differentiated with respect to both the time parameter and the Hamiltonian coefficients.
dev = qml.device('default.qubit', wires=2) obs = [qml.PauliX(0) @ qml.PauliY(1), qml.PauliY(0) @ qml.PauliX(1)] @qml.qnode(dev) def circuit(coeffs, t): H = qml.Hamiltonian(coeffs, obs) qml.templates.ApproxTimeEvolution(H, t, 2) return qml.expval(qml.PauliZ(0))
>>> t = np.array(0.54, requires_grad=True) >>> coeffs = np.array([-0.6, 2.0], requires_grad=True) >>> qml.grad(circuit)(coeffs, t) (array([-1.07813375, -1.07813375]), array(-2.79516158))
All differentiation methods, including backpropagation and the parameter-shift rule, are supported.
-
Quantum function transforms and batch transforms can now be applied to devices. Once applied to a device, any quantum function executed on the modified device will be transformed prior to execution. (#1809) (#1810)
dev = qml.device("default.mixed", wires=1) dev = qml.transforms.merge_rotations()(dev) @qml.beta.qnode(dev) def f(w, x, y, z): qml.RX(w, wires=0) qml.RX(x, wires=0) qml.RX(y, wires=0) qml.RX(z, wires=0) return qml.expval(qml.PauliZ(0))
>>> print(f(0.9, 0.4, 0.5, 0.6)) -0.7373937155412453 >>> print(qml.draw(f, expansion_strategy="device")(0.9, 0.4, 0.5, 0.6)) 0: ──RX(2.4)──┤ ⟨Z⟩
-
It is now possible to draw QNodes that have been transformed by a 'batch transform'; that is, a transform that maps a single QNode into multiple circuits under the hood. Examples of batch transforms include
@qml.metric_tensor
and@qml.gradients
. (#1762)For example, consider the parameter-shift rule, which generates two circuits per parameter; one circuit that has the parameter shifted forward, and another that has the parameter shifted backwards:
dev = qml.device("default.qubit", wires=2) @qml.gradients.param_shift @qml.beta.qnode(dev) def circuit(x): qml.RX(x, wires=0) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(wires=0))
>>> print(qml.draw(circuit)(0.6)) 0: ──RX(2.17)──╭C──┤ ⟨Z⟩ 1: ────────────╰X──┤ 0: ──RX(-0.971)──╭C──┤ ⟨Z⟩ 1: ──────────────╰X──┤
-
Support for differentiable execution of batches of circuits has been extended to the JAX interface for scalar functions, via the beta
pennylane.interfaces.batch
module. (#1634) (#1685)For example using the
execute
function from thepennylane.interfaces.batch
module:from pennylane.interfaces.batch import execute def cost_fn(x): with qml.tape.JacobianTape() as tape1: qml.RX(x[0], wires=[0]) qml.RY(x[1], wires=[1]) qml.CNOT(wires=[0, 1]) qml.var(qml.PauliZ(0) @ qml.PauliX(1)) with qml.tape.JacobianTape() as tape2: qml.RX(x[0], wires=0) qml.RY(x[0], wires=1) qml.CNOT(wires=[0, 1]) qml.probs(wires=1) result = execute( [tape1, tape2], dev, gradient_fn=qml.gradients.param_shift, interface="autograd" ) return (result[0] + result[1][0, 0])[0] res = jax.grad(cost_fn)(params)
-
All qubit operations have been re-written to use the
qml.math
framework for internal classical processing and the generation of their matrix representations. As a result these representations are now fully differentiable, and the framework-specific device classes no longer need to maintain framework-specific versions of these matrices. (#1749) (#1802) -
The use of
expval(H)
, whereH
is a cost Hamiltonian generated by theqaoa
module, has been sped up. This was achieved by making PennyLane decompose a circuit with anexpval(H)
measurement into subcircuits if theHamiltonian.grouping_indices
attribute is set, and setting this attribute in the relevantqaoa
module functions. (#1718) -
Operations can now have gradient recipes that depend on the state of the operation. (#1674)
For example, this allows for gradient recipes that are parameter dependent:
class RX(qml.RX): @property def grad_recipe(self): # The gradient is given by [f(2x) - f(0)] / (2 sin(x)), by subsituting # shift = x into the two term parameter-shift rule. x = self.data[0] c = 0.5 / np.sin(x) return ([[c, 0.0, 2 * x], [-c, 0.0, 0.0]],)
-
Shots can now be passed as a runtime argument to transforms that execute circuits in batches, similarly to QNodes. (#1707)
An example of such a transform are the gradient transforms in the
qml.gradients
module. As a result, we can now call gradient transforms (such asqml.gradients.param_shift
) and set the number of shots at runtime.>>> dev = qml.device("default.qubit", wires=1, shots=1000) >>> @qml.beta.qnode(dev) ... def circuit(x): ... qml.RX(x, wires=0) ... return qml.expval(qml.PauliZ(0)) >>> grad_fn = qml.gradients.param_shift(circuit) >>> param = np.array(0.564, requires_grad=True) >>> grad_fn(param, shots=[(1, 10)]).T array([[-1., -1., -1., -1., -1., 0., -1., 0., -1., 0.]]) >>> param2 = np.array(0.1233, requires_grad=True) >>> grad_fn(param2, shots=None) array([[-0.12298782]])
-
Templates are now top level imported and can be used directly e.g.
qml.QFT(wires=0)
. (#1779) -
qml.probs
now accepts an attributeop
that allows to rotate the computational basis and get the probabilities in the rotated basis. (#1692) -
Refactored the
expand_fn
functionality in the Device class to avoid any edge cases leading to failures with plugins. (#1838) -
Updated the
qml.QNGOptimizer.step_and_cost
method to avoid the use of deprecated functionality. (#1834) -
Added a custom
torch.to_numpy
implementation topennylane/math/single_dispatch.py
to ensure compabilitity with PyTorch 1.10. (#1824) (#1825) -
The default for an
Operation
'scontrol_wires
attribute is now an emptyWires
object instead of the attribute raising aNonImplementedError
. (#1821) -
qml.circuit_drawer.MPLDrawer
will now automatically rotate and resize text to fit inside the rectangle created by thebox_gate
method. (#1764) -
Operators now have a
label
method to determine how they are drawn. This will eventually override theRepresentationResolver
class. (#1678) -
The operation
label
method now supports string variables. (#1815) -
A new utility class
qml.BooleanFn
is introduced. It wraps a function that takes a single argument and returns a Boolean. (#1734)After wrapping,
qml.BooleanFn
can be called like the wrapped function, and multiple instances can be manipulated and combined with the bitwise operators&
,|
and~
. -
There is a new utility function
qml.math.is_independent
that checks whether a callable is independent of its arguments. (#1700)This function is experimental and might behave differently than expected.
Note that the test relies on both numerical and analytical checks, except when using the PyTorch interface which only performs a numerical check. It is known that there are edge cases on which this test will yield wrong results, in particular non-smooth functions may be problematic. For details, please refer to the is_indpendent docstring.
-
The
qml.beta.QNode
now supports theqml.qnn
module. (#1748) -
@qml.beta.QNode
now supports theqml.specs
transform. (#1739) -
qml.circuit_drawer.drawable_layers
andqml.circuit_drawer.drawable_grid
process a list of operations to layer positions for drawing. (#1639) -
qml.transforms.batch_transform
now acceptsexpand_fn
s that take additional arguments and keyword arguments. In fact,expand_fn
andtransform_fn
now must have the same signature. (#1721) -
The
qml.batch_transform
decorator is now ignored during Sphinx builds, allowing the correct signature to display in the built documentation. (#1733) -
The tests for qubit operations are split into multiple files. (#1661)
-
The transform for the Jacobian of the classical preprocessing within a QNode,
qml.transforms.classical_jacobian
, now takes a keyword argumentargnum
to specify the QNode argument indices with respect to which the Jacobian is computed. (#1645)An example for the usage of
argnum
is@qml.qnode(dev) def circuit(x, y, z): qml.RX(qml.math.sin(x), wires=0) qml.CNOT(wires=[0, 1]) qml.RY(y ** 2, wires=1) qml.RZ(1 / z, wires=1) return qml.expval(qml.PauliZ(0)) jac_fn = qml.transforms.classical_jacobian(circuit, argnum=[1, 2])
The Jacobian can then be computed at specified parameters.
>>> x, y, z = np.array([0.1, -2.5, 0.71]) >>> jac_fn(x, y, z) (array([-0., -5., -0.]), array([-0. , -0. , -1.98373339]))
The returned arrays are the derivatives of the three parametrized gates in the circuit with respect to
y
andz
respectively.There also are explicit tests for
classical_jacobian
now, which previously was tested implicitly via its use in themetric_tensor
transform.For more usage details, please see the classical Jacobian docstring.
-
A new utility function
qml.math.is_abstract(tensor)
has been added. This function returnsTrue
if the tensor is abstract; that is, it has no value or shape. This can occur if within a function that has been just-in-time compiled. (#1845) -
qml.circuit_drawer.CircuitDrawer
can accept a string for thecharset
keyword, instead of aCharSet
object. (#1640) -
qml.math.sort
will now return only the sorted torch tensor and not the corresponding indices, making sort consistent across interfaces. (#1691) -
Specific QNode execution options are now re-used by batch transforms to execute transformed QNodes. (#1708)
-
To standardize across all optimizers,
qml.optimize.AdamOptimizer
now also usesaccumulation
(in form ofcollections.namedtuple
) to keep track of running quantities. Before it used three variablesfm
,sm
andt
. (#1757)
Breaking changes
-
The operator attributes
has_unitary_generator
,is_composable_rotation
,is_self_inverse
,is_symmetric_over_all_wires
, andis_symmetric_over_control_wires
have been removed as attributes from the base class. They have been replaced by the sets that store the names of operations with similar properties inops/qubit/attributes.py
. (#1763) -
The
qml.inv
function has been removed,qml.adjoint
should be used instead. (#1778) -
The input signature of an
expand_fn
used in abatch_transform
now must have the same signature as the providedtransform_fn
, and vice versa. (#1721) -
The
default.qubit.torch
device automatically determines if computations should be run on a CPU or a GPU and doesn't take atorch_device
argument anymore. (#1705) -
The utility function
qml.math.requires_grad
now returnsTrue
when using Autograd if and only if therequires_grad=True
attribute is set on the NumPy array. Previously, this function would returnTrue
for all NumPy arrays and Python floats, unlessrequires_grad=False
was explicitly set. (#1638) -
The operation
qml.Interferometer
has been renamedqml.InterferometerUnitary
in order to distinguish it from the templateqml.templates.Interferometer
. (#1714) -
The
qml.transforms.invisible
decorator has been replaced withqml.tape.stop_recording
, which may act as a context manager as well as a decorator to ensure that contained logic is non-recordable or non-queueable within a QNode or quantum tape context. (#1754) -
Templates
SingleExcitationUnitary
andDoubleExcitationUnitary
have been renamed toFermionicSingleExcitation
andFermionicDoubleExcitation
, respectively. (#1822)
Deprecations
-
Allowing cost functions to be differentiated using
qml.grad
orqml.jacobian
without explicitly marking parameters as trainable is being deprecated, and will be removed in an upcoming release. Please specify therequires_grad
attribute for every argument, or specifyargnum
when usingqml.grad
orqml.jacobian
. (#1773)The following raises a warning in v0.19.0 and will raise an error in an upcoming release:
import pennylane as qml dev = qml.device('default.qubit', wires=1) @qml.qnode(dev) def test(x): qml.RY(x, wires=[0]) return qml.expval(qml.PauliZ(0)) par = 0.3 qml.grad(test)(par)
Preferred approaches include specifying the
requires_grad
attribute:import pennylane as qml from pennylane import numpy as np dev = qml.device('default.qubit', wires=1) @qml.qnode(dev) def test(x): qml.RY(x, wires=[0]) return qml.expval(qml.PauliZ(0)) par = np.array(0.3, requires_grad=True) qml.grad(test)(par)
Or specifying the
argnum
argument when usingqml.grad
orqml.jacobian
:import pennylane as qml dev = qml.device('default.qubit', wires=1) @qml.qnode(dev) def test(x): qml.RY(x, wires=[0]) return qml.expval(qml.PauliZ(0)) par = 0.3 qml.grad(test, argnum=0)(par)
-
The
default.tensor
device from the beta folder is no longer maintained and has been deprecated. It will be removed in future releases. (#1851) -
The
qml.metric_tensor
andqml.QNGOptimizer
keyword argumentdiag_approx
is deprecated. Approximations can be controlled with the more fine-grainedapprox
keyword argument, withapprox="block-diag"
(the default) reproducing the old behaviour. (#1721) (#1834) -
The
template
decorator is now deprecated with a warning message and will be removed in releasev0.20.0
. It has been removed from different PennyLane functions. (#1794) (#1808) -
The
qml.fourier.spectrum
function has been renamed toqml.fourier.circuit_spectrum
, in order to clearly separate the newqnode_spectrum
function from this one.qml.fourier.spectrum
is now an alias forcircuit_spectrum
but is flagged for deprecation and will be removed soon. (#1681) -
The
init
module, which contains functions to generate random parameter tensors for templates, is flagged for deprecation and will be removed in the next release cycle. Instead, the templates'shape
method can be used to get the desired shape of the tensor, which can then be generated manually. (#1689)To generate the parameter tensors, the
np.random.normal
andnp.random.uniform
functions can be used (just like in theinit
module). Considering the default arguments of these functions as of NumPy v1.21, some non-default options were used by theinit
module:-
All functions generating normally distributed parameters used
np.random.normal
by passingscale=0.1
; -
Most functions generating uniformly distributed parameters (except for certain CVQNN initializers) used
np.random.uniform
by passinghigh=2*math.pi
; -
The
cvqnn_layers_r_uniform
,cvqnn_layers_a_uniform
,cvqnn_layers_kappa_uniform
functions usednp.random.uniform
by passinghigh=0.1
.
-
-
The
QNode.draw
method has been deprecated, and will be removed in an upcoming release. Please use theqml.draw
transform instead. (#1746) -
The
QNode.metric_tensor
method has been deprecated, and will be removed in an upcoming release. Please use theqml.metric_tensor
transform instead. (#1638) -
The
pad
parameter of theqml.AmplitudeEmbedding
template has been removed. It has instead been renamed to thepad_with
parameter. (#1805)
Bug fixes
-
Fixes a bug where
qml.math.dot
failed to work with@tf.function
autograph mode. (#1842) -
Fixes a bug where in rare instances the parameters of a tape are returned unsorted by
Tape.get_parameters
. (#1836) -
Fixes a bug with the arrow width in the
measure
ofqml.circuit_drawer.MPLDrawer
. (#1823) -
The helper functions
qml.math.block_diag
andqml.math.scatter_element_add
now are entirely differentiable when using Autograd. Previously only indexed entries of the block diagonal could be differentiated, while the derivative w.r.t to the second argument ofqml.math.scatter_element_add
dispatched to NumPy instead of Autograd. (#1816) (#1818) -
Fixes a bug such that the original shot vector information of a device is preserved, so that outside the context manager the device remains unchanged. (#1792)
-
Modifies
qml.math.take
to be compatible with a breaking change released in JAX 0.2.24 and ensure that PennyLane supports this JAX version. (#1769) -
Fixes a bug where the GPU cannot be used with
qml.qnn.TorchLayer
. (#1705) -
Fix a bug where the devices cache the same result for different observables return types. (#1719)
-
Fixed a bug of the default circuit drawer where having more measurements compared to the number of measurements on any wire raised a
KeyError
. (#1702) -
Fix a bug where it was not possible to use
jax.jit
on aQNode
when usingQubitStateVector
. (#1683) -
The device suite tests can now execute successfully if no shots configuration variable is given. (#1641)
-
Fixes a bug where the
qml.gradients.param_shift
transform would raise an error while attempting to compute the variance of a QNode with ragged output. (#1646) -
Fixes a bug in
default.mixed
, to ensure that returned probabilities are always non-negative. (#1680) -
Fixes a bug where gradient transforms would fail to apply to QNodes containing classical processing. (#1699)
-
Fixes a bug where the parameter-shift method was not correctly using the fallback gradient function when all circuit parameters required the fallback. (#1782)
Documentation
-
Adds a link to https://pennylane.ai/qml/demonstrations.html in the navbar. (#1624)
-
Corrects the docstring of
ExpvalCost
by addingwires
to the signature of theansatz
argument. (#1715) -
Updated docstring examples using the
qchem.molecular_hamiltonian
function. (#1724) -
Updates the 'Gradients and training' quickstart guide to provide information on gradient transforms. (#1751)
-
All instances of
qnode.draw()
have been updated to instead use the transformqml.draw(qnode)
. (#1750) -
Add the
jax
interface in QNode Documentation. (#1755) -
Reorganized all the templates related to quantum chemistry under a common header
Quantum Chemistry templates
. (#1822)
Contributors
This release contains contributions from (in alphabetical order):
Catalina Albornoz, Juan Miguel Arrazola, Utkarsh Azad, Akash Narayanan B, Sam Banning, Thomas Bromley, Jack Ceroni, Alain Delgado, Olivia Di Matteo, Andrew Gardhouse, Anthony Hayes, Theodor Isacsson, David Ittah, Josh Izaac, Soran Jahangiri, Nathan Killoran, Christina Lee, Guillermo Alonso-Linaje, Romain Moyard, Lee James O'Riordan, Carrie-Anne Rubidge, Maria Schuld, Rishabh Singh, Jay Soni, Ingrid Strandberg, Antal Száva, Teresa Tamayo-Mendoza, Rodrigo Vargas, Cody Wang, David Wierichs, Moritz Willmann.