qml.gradients.hadamard_grad

hadamard_grad(tape, argnum=None, aux_wire=None, device_wires=None)

Transform a QNode to compute the Hadamard test gradient of all gates with respect to their inputs.

Parameters
  • tape (pennylane.QNode or QuantumTape) – quantum tape or QNode to differentiate

  • argnum (int or list[int] or None) – Trainable tape parameter indices to differentiate with respect to. If not provided, the derivatives with respect to all trainable parameters are returned.

  • aux_wire (pennylane.wires.Wires) – Auxiliary wire to be used for the Hadamard tests. If None (the default), a suitable wire is inferred from the wires used in the original circuit and device_wires.

  • device_wires (pennylane.wires.Wires) – Wires of the device that are going to be used for the gradient. Facilitates finding a default for aux_wire if aux_wire is None.

Returns

  • If the input is a QNode, an object representing the Jacobian (function) of the QNode that can be executed to obtain the Jacobian. The type of the Jacobian returned is either a tensor, a tuple or a nested tuple depending on the nesting structure of the original QNode output.

  • If the input is a tape, a tuple containing a list of generated tapes, together with a post-processing function to be applied to the results of the evaluated tapes in order to obtain the Jacobian.

Return type

function or tuple[list[QuantumTape], function]

For a variational evolution \(U(\mathbf{p}) \vert 0\rangle\) with \(N\) parameters \(\mathbf{p}\), consider the expectation value of an observable \(O\):

\[f(\mathbf{p}) = \langle \hat{O} \rangle(\mathbf{p}) = \langle 0 \vert U(\mathbf{p})^\dagger \hat{O} U(\mathbf{p}) \vert 0\rangle.\]

The gradient of this expectation value can be calculated via the Hadamard test gradient:

\[\frac{\partial f}{\partial \mathbf{p}} = -2 \Im[\bra{0} \hat{O} G \ket{0}] = i \left(\bra{0} \hat{O} G \ket{ 0} - \bra{0} G\hat{O} \ket{0}\right) = -2 \bra{+}\bra{0} ctrl-G^{\dagger} (\hat{Y} \otimes \hat{O}) ctrl-G \ket{+}\ket{0}\]

Here, \(G\) is the generator of the unitary \(U\).

Example

This gradient transform can be applied directly to QNode objects:

>>> 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 quantum gradient transform can also be applied to low-level QuantumTape objects. This will result in no implicit quantum device evaluation. Instead, the processed tapes, and post-processing function, which together define the gradient are directly returned:

>>> ops = [qml.RX(p, wires=0) for p in params]
>>> measurements = [qml.expval(qml.PauliZ(0))]
>>> tape = qml.tape.QuantumTape(ops, measurements)
>>> gradient_tapes, fn = qml.gradients.hadamard_grad(tape)
>>> gradient_tapes
[<QuantumTape: wires=[0, 1], params=3>,
 <QuantumTape: wires=[0, 1], params=3>,
 <QuantumTape: wires=[0, 1], params=3>]

This can be useful if the underlying circuits representing the gradient computation need to be analyzed.

The output tapes can then be evaluated and post-processed to retrieve the gradient:

>>> dev = qml.device("default.qubit", wires=2)
>>> fn(qml.execute(gradient_tapes, dev, None))
(array(-0.3875172), array(-0.18884787), array(-0.38355704))

This transform can be registered directly as the quantum gradient transform to use during autodifferentiation:

>>> dev = qml.device("default.qubit", wires=3)
>>> @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)
[-0.3875172  -0.18884787 -0.38355704]

If you use custom wires on your device, you need to pass an auxiliary wire.

>>> dev_wires = ("a", "c")
>>> dev = qml.device("default.qubit", wires=dev_wires)
>>> @qml.qnode(dev, interface="jax", diff_method="hadamard", aux_wire="c", device_wires=dev_wires)
>>> def circuit(params):
...    qml.RX(params[0], wires="a")
...    qml.RY(params[1], wires="a")
...    qml.RX(params[2], wires="a")
...    return qml.expval(qml.PauliZ("a"))
>>> params = jax.numpy.array([0.1, 0.2, 0.3])
>>> jax.jacobian(circuit)(params)
[-0.3875172  -0.18884787 -0.38355704]

Note

hadamard_grad will decompose the operations that are not in the list of supported operations.

  • pennylane.RX

  • pennylane.RY

  • pennylane.RZ

  • pennylane.Rot

  • pennylane.PhaseShift

  • pennylane.U1

  • pennylane.CRX

  • pennylane.CRY

  • pennylane.CRZ

  • pennylane.IsingXX

  • pennylane.IsingYY

  • pennylane.IsingZZ

The expansion will fail if a suitable decomposition in terms of supported operation is not found. The number of trainable parameters may increase due to the decomposition.