qml.metric_tensor

metric_tensor = <pennylane.transforms.batch_transform.batch_transform object>[source]

Returns a function that computes the block-diagonal approximation of the metric tensor of a given QNode or quantum tape.

Note

Currently, only the RX, RY, RZ, and PhaseShift parametrized gates are supported. All other parametrized gates will be decomposed if possible.

Parameters
  • qnode (pennylane.QNode or QuantumTape) – quantum tape or QNode to find the metric tensor of

  • diag_approx (bool) – if True, use the diagonal approximation. If False, a

  • diagonal approximation of the metric tensor is computed. (block) –

  • hybrid (bool) –

    Specifies whether classical processing inside a QNode should be taken into account when transforming a QNode.

    • If True, and classical processing is detected, the Jacobian of the classical processing will be computed and included. When evaluated, the returned metric tensor will be with respect to the QNode arguments.

    • If False, any internal QNode classical processing will be ignored. When evaluated, the returned metric tensor will be with respect to the gate arguments, and not the QNode arguments.

Returns

Function which accepts the same arguments as the QNode. When called, this function will return the metric tensor.

Return type

func

Example

Consider the following QNode:

dev = qml.device("default.qubit", wires=3)

@qml.qnode(dev, interface="autograd")
def circuit(weights):
    # layer 1
    qml.RX(weights[0], wires=0)
    qml.RX(weights[1], wires=1)

    qml.CNOT(wires=[0, 1])
    qml.CNOT(wires=[1, 2])

    # layer 2
    qml.RZ(weights[2], wires=0)
    qml.RZ(weights[3], wires=2)

    qml.CNOT(wires=[0, 1])
    qml.CNOT(wires=[1, 2])
    return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)), qml.expval(qml.PauliY(2))

We can use the metric_tensor transform to generate a new function that returns the metric tensor of this QNode:

>>> met_fn = qml.metric_tensor(circuit)
>>> weights = np.array([0.1, 0.2, 0.4, 0.5], requires_grad=True)
>>> met_fn(weights)
tensor([[0.25  , 0.    , 0.    , 0.    ],
        [0.    , 0.25  , 0.    , 0.    ],
        [0.    , 0.    , 0.0025, 0.0024],
        [0.    , 0.    , 0.0024, 0.0123]], requires_grad=True)

The returned metric tensor is also fully differentiable in all interfaces. For example, we can compute the gradient of the (3, 2) element with respect to the QNode weights:

>>> grad_fn = qml.grad(lambda x: met_fn(x)[3, 2])
>>> grad_fn(weights)
array([[ 0.04867729, -0.00049502,  0.        ],
       [ 0.        ,  0.        ,  0.        ]])

This 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 metric tensor are directly returned:

>>> params = np.array([1.7, 1.0, 0.5], requires_grad=True)
>>> with qml.tape.QuantumTape() as tape:
...     qml.RX(params[0], wires=0)
...     qml.RY(params[1], wires=0)
...     qml.CNOT(wires=[0, 1])
...     qml.PhaseShift(params[2], wires=1)
...     qml.expval(qml.PauliX(0))
>>> tapes, fn = qml.metric_tensor(tape)
>>> tapes
[<QuantumTape: wires=[0, 1], params=0>,
 <QuantumTape: wires=[0, 1], params=1>,
 <QuantumTape: wires=[0, 1], params=3>]

This can be useful if the underlying circuits representing the metric tensor computation need to be analyzed.

The output tapes can then be evaluated and post-processed to retrieve the metric tensor:

>>> dev = qml.device("default.qubit", wires=2)
>>> fn(qml.execute(tapes, dev, None))
array([[0.25      , 0.        , 0.        ],
       [0.        , 0.00415023, 0.        ],
       [0.        , 0.        , 0.24878844]])