qml.shadow_expval

shadow_expval(H, k=1, seed=None)[source]

Estimate expectation values using Classical Shadows with full differentiability support.

The Classical Shadows protocol provide a way to estimate a large number of expectation values (even non-commuting ones) using a single set of random Pauli measurements. See arXiv:2002.08953 for the original proposal and theoretical details.

Parameters:
  • H (Sequence[Operator] | Operator) – Obserable(s) whose expectation values are to be estimated. Provide a single observable or a sequence to estimate the expectation values of multiple observables from the same classical shadows data.

  • k (int) – Number of equal parts for which to split the shadow’s measurements in order to compute the median of means. The default is k=1, which simply computes the mean of all measurements. k>1 provides no expected advantage for Pauli measurements and Pauli observables.

  • seed (int | None) –

    Optional seed for the random Pauli measurement basis in the classical shadows protocol. This controls which bases (X, Y or Z) each qubit is measured in per shot. If None, a random seed will be generated.

    Note

    The seed argument only controls the measurement basis choice. The seed of a simulator device separately controls the sampling outcomes. For fully reproducible results, you must seed both the device and the measurement.

    dev = qml.device("default.qubit", seed=42, shots=100)
    
    @qml.qnode(dev)
    def circuit():
        qml.H(0)
        return qml.shadow_expval(qml.Z(0), seed=99)
    

Returns:

Measurement process instance

Return type:

ShadowExpvalMP

See also

This measurement internally relies on the measurement classical_shadow() and the class ClassicalShadow for post-processing in order to compute expectation values.

Example

With the standard expval() measurement, each group of non-commuting observables requires its own separate circuit execution. However, with shadow_expval we can reuse the shadow data generated from the circuit executions to estimate all expectation values simultaneously.

Let’s say we want to estimate the expectation values of all three (non-commuting) single qubit Paulis (X, Y, Z) on a \(| + \rangle\) state. Theoretically, we would expect that \(\langle X \rangle = 1\), \(\langle Y \rangle = \langle Z \rangle = 0\).

device = qml.device("default.qubit", seed=42)

@qml.set_shots(1_000)
@qml.qnode(device)
def circuit():
    qml.H(0) # Create |+> state
    return qml.shadow_expval((qml.X(0), qml.Y(0), qml.Z(0)), seed=99)
>>> print(circuit())
[0.984 0.    0.03 ]

This is very close to their expected values!

Consider the following observable,

>>> H = qml.Hamiltonian([1., 1.], [qml.Z(0) @ qml.Z(1), qml.X(0) @ qml.X(1)])

We can estimate its expectation value with the classical shadows protocol:

dev = qml.device("default.qubit", seed=42, wires=range(2))

@qml.set_shots(shots=10_000)
@qml.qnode(dev)
def circuit(x, obs):
    qml.Hadamard(0)
    qml.CNOT((0,1))
    qml.RX(x, wires=0)
    return qml.shadow_expval(obs, seed=99)

x = pnp.array(0.5, requires_grad=True)
>>> print(circuit(x, H))
1.8891
>>> print(qml.grad(circuit)(x, H))
-0.4653...

In shadow_expval, we can also pass a list of observables to estimate them all from the same shadow data. Note that each qnode execution internally performs one quantum measurement, so be sure to include all observables that you want to estimate from a single measurement in the same execution.

>>> Hs = [H, qml.X(0), qml.Y(0), qml.Z(0)]
>>> print(circuit(x, Hs))
[ 1.8783  0.0096 -0.0174  0.0138]
>>> print(qml.jacobian(circuit)(x, Hs))
[-0.4851 -0.0063 -0.0099  0.0006]