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 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:>>> 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.