qml.gradients.classical_jacobian¶
- classical_jacobian(qnode, argnum=None, expand_fn=None, trainable_only=True)[source]¶
Returns a function to extract the Jacobian matrix of the classical part of a QNode.
This transform allows the classical dependence between the QNode arguments and the quantum gate arguments to be extracted.
- Parameters
qnode (pennylane.QNode) – QNode to compute the (classical) Jacobian of
argnum (int or Sequence[int]) – indices of QNode arguments with respect to which the (classical) Jacobian is computed
expand_fn (None or function) – an expansion function (if required) to be applied to the QNode quantum tape before the classical Jacobian is computed
- Returns
Function which accepts the same arguments as the QNode. When called, this function will return the Jacobian of the QNode gate arguments with respect to the QNode arguments indexed by
argnum
.- Return type
function
Example
Consider the following QNode:
>>> @qml.qnode(dev) ... def circuit(weights): ... qml.RX(weights[0], wires=0) ... qml.RY(0.2 * weights[0], wires=1) ... qml.RY(2.5, wires=0) ... qml.RZ(weights[1] ** 2, wires=1) ... qml.RX(weights[2], wires=1) ... return qml.expval(qml.Z(0) @ qml.Z(1))
We can use this transform to extract the relationship \(f: \mathbb{R}^n \rightarrow \mathbb{R}^m\) between the input QNode arguments \(w\) and the gate arguments \(g\), for a given value of the QNode arguments:
>>> cjac_fn = qml.gradients.classical_jacobian(circuit) >>> weights = np.array([1., 1., 0.6], requires_grad=True) >>> cjac = cjac_fn(weights) >>> print(cjac) [[1. 0. 0. ] [0.2 0. 0. ] [0. 2. 0. ] [0. 0. 1. ]]
The returned Jacobian has rows corresponding to gate arguments, and columns corresponding to QNode arguments; that is,
\[J_{ij} = \frac{\partial}{\partial g_i} f(w_j).\]We can see that:
The zeroth element of
weights
is repeated on the first two gates generated by the QNode.The third row consisting of all zeros indicates that the third gate
RY(2.5)
does not depend on theweights
.The quadratic dependence of the fourth gate argument yields \(2\cdot 0.6=1.2\).
Note
The QNode is constructed during this operation.
For a QNode with multiple QNode arguments, the arguments with respect to which the Jacobian is computed can be controlled with the
argnum
keyword argument. The output and its format depend on the backend:¶ Interface
argnum=None
type(argnum)=int
type(argnum) = Sequence[int]
'autograd'
tuple(array)
[1]array
tuple(array)
'jax'
array
[2]array
tuple(array)
'tf'
tuple(array)
array
tuple(array)
'torch'
tuple(array)
array
tuple(array)
[1] If there only is one trainable QNode argument, the tuple is unpacked to a single
array
, as is the case forjacobian()
.[2] For JAX,
argnum=None
defaults toargnum=0
in contrast to all other interfaces. This means that only the classical Jacobian with respect to the first QNode argument is computed if noargnum
is provided.Example with ``argnum``
>>> @qml.qnode(dev) ... def circuit(x, y, z): ... qml.RX(qml.math.sin(x), wires=0) ... qml.CNOT(wires=[0, 1]) ... qml.RY(y ** 2, wires=1) ... qml.RZ(1 / z, wires=1) ... return qml.expval(qml.Z(0) @ qml.Z(1)) >>> jac_fn = qml.gradients.classical_jacobian(circuit, argnum=[1, 2]) >>> x, y, z = np.array([0.1, -2.5, 0.71]) >>> jac_fn(x, y, z) (array([-0., -5., -0.]), array([-0. , -0. , -1.98373339]))
Only the Jacobians with respect to the arguments
x
andy
were computed, and returned as a tuple ofarrays
.