qml.gradients.hadamard_grad

hadamard_grad(tape, argnum=None, aux_wire=None, device_wires=None)[source]

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

Parameters
  • tape (QNode or QuantumTape) – quantum circuit 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. Note that the indices are with respect to the list of trainable parameters.

  • 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

The transformed circuit as described in qml.transform. Executing this circuit will provide the Jacobian in the form of a tensor, a tuple, or a nested tuple depending upon the nesting structure of measurements in the original circuit.

Return type

qnode (QNode) 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 transform can be registered directly as the quantum gradient transform to use during autodifferentiation:

>>> import jax
>>> 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.Z(0)), qml.var(qml.Z(0))
>>> params = jax.numpy.array([0.1, 0.2, 0.3])
>>> jax.jacobian(circuit)(params)
(Array([-0.38751727, -0.18884793, -0.3835571 ], dtype=float32),
Array([0.6991687 , 0.34072432, 0.6920237 ], dtype=float32))

This gradient transform can be applied directly to QNode objects. However, for performance reasons, we recommend providing the gradient transform as the diff_method argument of the QNode decorator, and differentiating with your preferred machine learning framework.

>>> 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.Z(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.Z(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.

Note that argnum refers to the index of a parameter within the list of trainable parameters. For example, if we have:

>>> tape = qml.tape.QuantumScript(
...     [qml.RX(1.2, wires=0), qml.RY(2.3, wires=0), qml.RZ(3.4, wires=0)],
...     [qml.expval(qml.Z(0))],
...     trainable_params = [1, 2]
... )
>>> qml.gradients.hadamard_grad(tape, argnum=1)

The code above will differentiate the third parameter rather than the second.

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.Z(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.Z("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.