There is indeed a way of doing this, but it requires ‘dropping down’ to a lower level of abstraction, and working with
tapes. These are the internal data structures that represents the variational circuit, and are created internally whenever a QNode is executed.
You can create a tape manually:
with qml.tape.QuantumTape() as tape:
or you can extract it from a QNode:
dev = qml.device("default.qubit", wires=2)
# first we 'construct' the QNode with a particular
# parameter value
x = np.array(0.6, requires_grad=True)
# pass a list of QNode arguments and
# a dict of keyword arguments
tape = circuit.tape
Now, we can apply
qml.gradients.param_shift directly to the tape. Unlike when we apply this gradient transform to a QNode, no quantum evaluation will occur.
Instead, we get returned a list of gradient tapes (representing each shifted argument in the parameter-shift rule) and a post-processing function.
>>> g_tapes, fn = qml.gradients.param_shift(tape)
>>> for t in g_tapes:
1: ────────────╰X──┤ ⟨Z⟩
1: ──────────────╰X──┤ ⟨Z⟩
We can now evaluate these ‘gradient tapes’ using
>>> results = qml.execute(g_tapes, dev, None)
and compute the gradient by ‘post-processing’ them with the generated function