qml.transforms.classical_jacobian

classical_jacobian(qnode, argnum=None)[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 (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

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.PauliZ(0) @ qml.PauliZ(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.transforms.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.  0.  0. ]
 [0.  1.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 the weights.

  • 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 for argnum=None depends on the backend:

Output format of classical_jacobian

Interface

argnum=None

type(argnum)=int

argnum=Sequence[int]

'autograd'

tuple(arrays) [1]

array

tuple(array)

'jax'

array

array

tuple(array)

'tf'

tuple(arrays)

array

tuple(array)

'torch'

tuple(arrays)

array

tuple(array)

[1] If all QNode argument are scalars, the tuple is unpacked and the one-dimensional Jacobian arrays are stacked into one array. If there only is one QNode argument, the tuple is unpacked as well. Both is due to the behaviour of qml.jacobian.

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.PauliZ(0) @ qml.PauliZ(1))
>>> jac_fn = qml.transforms.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 and y were computed, and returned as a tuple of arrays.