qml.qinfo.transforms.fidelity

fidelity(qnode0, qnode1, wires0, wires1)[source]

Compute the fidelity for two QNode returning a state() (a state can be a state vector or a density matrix, depending on the device) acting on quantum systems with the same size.

The fidelity for two mixed states given by density matrices \(\rho\) and \(\sigma\) is defined as

\[F( \rho , \sigma ) = \text{Tr}( \sqrt{\sqrt{\rho} \sigma \sqrt{\rho}})^2\]

If one of the states is pure, say \(\rho=\ket{\psi}\bra{\psi}\), then the expression for fidelity simplifies to

\[F( \ket{\psi} , \sigma ) = \bra{\psi} \sigma \ket{\psi}\]

Finally, if both states are pure, \(\sigma=\ket{\phi}\bra{\phi}\), then the fidelity is simply

\[F( \ket{\psi} , \ket{\phi}) = \left|\braket{\psi, \phi}\right|^2\]

Note

The second state is coerced to the type and dtype of the first state. The fidelity is returned in the type of the interface of the first state.

Parameters
  • state0 (QNode) – A QNode returning a state().

  • state1 (QNode) – A QNode returning a state().

  • wires0 (Sequence[int]) – the wires of the first subsystem

  • wires1 (Sequence[int]) – the wires of the second subsystem

Returns

A function that returns the fidelity between the states outputted by the QNodes.

Return type

func

Example

First, let’s consider two QNodes with potentially different signatures: a circuit with two parameters and another circuit with a single parameter. The output of the fidelity() transform then requires two tuples to be passed as arguments, each containing the args and kwargs of their respective circuit, e.g. all_args0 = (0.1, 0.3) and all_args1 = (0.2) in the following case:

dev = qml.device('default.qubit', wires=1)

@qml.qnode(dev)
def circuit_rx(x, y):
    qml.RX(x, wires=0)
    qml.RZ(y, wires=0)
    return qml.state()

@qml.qnode(dev)
def circuit_ry(y):
    qml.RY(y, wires=0)
    return qml.state()
>>> qml.qinfo.fidelity(circuit_rx, circuit_ry, wires0=[0], wires1=[0])((0.1, 0.3), (0.2))
0.9905158135644924

It is also possible to use QNodes that do not depend on any parameters. When it is the case for the first QNode, it is required to pass an empty tuple as an argument for the first QNode.

dev = qml.device('default.qubit', wires=1)

@qml.qnode(dev)
def circuit_rx():
    return qml.state()

@qml.qnode(dev)
def circuit_ry(x):
    qml.RY(x, wires=0)
    return qml.state()
>>> qml.qinfo.fidelity(circuit_rx, circuit_ry, wires0=[0], wires1=[0])(None, (0.2))
0.9900332889206207

On the other hand, if the second QNode is the one that does not depend on parameters then a single tuple can also be passed:

>>> qml.qinfo.fidelity(circuit_ry, circuit_rx, wires0=[0], wires1=[0])((0.2))
0.9900332889206207

The fidelity() transform is also differentiable and the gradient can be obtained in the different frameworks with backpropagation, the following example uses jax and backprop.

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

@qml.qnode(dev, interface="jax")
def circuit0(x):
    qml.RX(x, wires=0)
    return qml.state()

@qml.qnode(dev, interface="jax")
def circuit1():
    qml.Z(0)
    return qml.state()
>>> jax.grad(qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0]))((jax.numpy.array(0.3)))
Array(-0.14776011, dtype=float64, weak_type=True)

There is also the possibility to pass a single dictionary at the end of the tuple for fixing args, you can follow this example:

dev = qml.device('default.qubit', wires=1)

@qml.qnode(dev)
def circuit_rx(x, y):
    qml.RX(x, wires=0)
    qml.RZ(y, wires=0)
    return qml.state()

@qml.qnode(dev)
def circuit_ry(y, use_ry):
    if use_ry:
        qml.RY(y, wires=0)
    return qml.state()
>>> fidelity(circuit_rx, circuit_ry, wires0=[0], wires1=[0])((0.1, 0.3), (0.9, {'use_ry': True}))
0.8208074192135424