qml.gradients.hadamard_grad¶
-
hadamard_grad
(tape, argnum=None, shots=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.
shots (None, int, list[int]) – The device shots that will be used to execute the tapes outputted by this transform. Note that this argument doesn’t influence the shots used for tape execution, but provides information about the shots.
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 anddevice_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
ifaux_wire
isNone
.
- 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:>>> qml.enable_return() >>> 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:>>> with qml.tape.QuantumTape() as tape: ... qml.RX(params[0], wires=0) ... qml.RY(params[1], wires=0) ... qml.RX(params[2], wires=0) ... qml.expval(qml.PauliZ(0)) >>> 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.
>>> qml.enable_return() >>> 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.