qml.gradients.finite_diff

finite_diff(tape, argnum=None, h=1e-07, approx_order=1, n=1, strategy='forward', f0=None, validate_params=True)[source]

Transform a circuit to compute the finite-difference gradient of all gate parameters with respect to its inputs.

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

  • argnum (int or list[int] or None) – Trainable 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.

  • h (float) – finite difference method step size

  • approx_order (int) – The approximation order of the finite-difference method to use.

  • n (int) – compute the \(n\)-th derivative

  • strategy (str) – The strategy of the finite difference method. Must be one of "forward", "center", or "backward". For the "forward" strategy, the finite-difference shifts occur at the points \(x_0, x_0+h, x_0+2h,\dots\), where \(h\) is some small stepsize. The "backwards" strategy is similar, but in reverse: \(x_0, x_0-h, x_0-2h, \dots\). Finally, the "center" strategy results in shifts symmetric around the unshifted point: \(\dots, x_0-2h, x_0-h, x_0, x_0+h, x_0+2h,\dots\).

  • f0 (tensor_like[float] or None) – Output of the evaluated input tape. If provided, and the gradient recipe contains an unshifted term, this value is used, saving a quantum evaluation.

  • validate_params (bool) – Whether to validate the tape parameters or not. If True, the Operation.grad_method attribute and the circuit structure will be analyzed to determine if the trainable parameters support the finite-difference method. If False, the finite-difference method will be applied to all parameters.

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]

Example

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

>>> dev = qml.device("default.qubit", wires=2)
>>> @qml.qnode(dev, interface="autograd", diff_method="finite-diff")
... 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.jacobian(circuit)(params)
array([-0.38751725, -0.18884792, -0.38355708])

When differentiating QNodes with multiple measurements using Autograd or TensorFlow, the outputs of the QNode first need to be stacked. The reason is that those two frameworks only allow differentiating functions with array or tensor outputs, instead of functions that output sequences. In contrast, Jax and Torch require no additional post-processing.

>>> import jax
>>> dev = qml.device("default.qubit", wires=2)
>>> @qml.qnode(dev, interface="jax", diff_method="finite-diff")
... 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.

>>> @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)), qml.var(qml.Z(0))
>>> params = np.array([0.1, 0.2, 0.3], requires_grad=True)
>>> qml.gradients.finite_diff(circuit)(params)
((tensor(-0.38751724, requires_grad=True),
  tensor(-0.18884792, requires_grad=True),
  tensor(-0.38355709, requires_grad=True)),
 (tensor(0.69916868, requires_grad=True),
  tensor(0.34072432, requires_grad=True),
  tensor(0.69202366, 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)), qml.var(qml.Z(0))]
>>> tape = qml.tape.QuantumTape(ops, measurements)
>>> gradient_tapes, fn = qml.gradients.finite_diff(tape)
>>> gradient_tapes
[<QuantumTape: wires=[0], params=3>,
 <QuantumTape: wires=[0], params=3>,
 <QuantumTape: wires=[0], params=3>,
 <QuantumTape: wires=[0], 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.finite_diff(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))
((tensor(-0.56464251, requires_grad=True),
 tensor(-0.56464251, requires_grad=True),
 tensor(-0.56464251, requires_grad=True)),
(tensor(0.93203912, requires_grad=True),
 tensor(0.93203912, requires_grad=True),
 tensor(0.93203912, requires_grad=True)))

This gradient transform is compatible with devices that use shot vectors for execution.

>>> shots = (10, 100, 1000)
>>> dev = qml.device("default.qubit", wires=2, shots=shots)
>>> @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)), qml.var(qml.Z(0))
>>> params = np.array([0.1, 0.2, 0.3], requires_grad=True)
>>> qml.gradients.finite_diff(circuit, h=10e-2)(params)
(((array(-2.), array(-2.), array(0.)), (array(3.6), array(3.6), array(0.))),
 ((array(1.), array(0.4), array(1.)),
  (array(-1.62), array(-0.624), array(-1.62))),
 ((array(-0.48), array(-0.34), array(-0.46)),
  (array(0.84288), array(0.6018), array(0.80868))))

The outermost tuple contains results corresponding to each element of the shot vector.