qml.classical_shadow

classical_shadow(wires, seed_recipes=True)[source]

The classical shadow measurement protocol.

The protocol is described in detail in the paper Predicting Many Properties of a Quantum System from Very Few Measurements. This measurement process returns the randomized Pauli measurements (the recipes) that are performed for each qubit and snapshot as an integer:

  • 0 for Pauli X,

  • 1 for Pauli Y, and

  • 2 for Pauli Z.

It also returns the measurement results (the bits); 0 if the 1 eigenvalue is sampled, and 1 if the -1 eigenvalue is sampled.

The device shots are used to specify the number of snapshots. If T is the number of shots and n is the number of qubits, then both the measured bits and the Pauli measurements have shape (T, n).

Parameters
  • wires (Sequence[int]) – the wires to perform Pauli measurements on

  • seed_recipes (bool) – If True, a seed will be generated that is used for the randomly sampled Pauli measurements. This is to ensure that the same recipes are used when a tape containing this measurement is copied. Different seeds are still generated for different constructed tapes.

Example

Consider the following QNode that prepares a Bell state and performs a classical shadow measurement:

dev = qml.device("default.qubit", wires=2, shots=5)

@qml.qnode(dev)
def circuit():
    qml.Hadamard(wires=0)
    qml.CNOT(wires=[0, 1])
    return qml.classical_shadow(wires=[0, 1])

Executing this QNode produces the sampled bits and the Pauli measurements used:

>>> bits, recipes = circuit()
>>> bits
tensor([[0, 0],
        [1, 0],
        [1, 0],
        [0, 0],
        [0, 1]], dtype=uint8, requires_grad=True)
>>> recipes
tensor([[2, 2],
        [0, 2],
        [1, 0],
        [0, 2],
        [0, 2]], dtype=uint8, requires_grad=True)

Consider again the QNode in the above example. Since the Pauli observables are randomly sampled, executing this QNode again would produce different bits and Pauli recipes:

>>> bits, recipes = circuit()
>>> bits
tensor([[0, 1],
        [0, 1],
        [0, 0],
        [0, 1],
        [1, 1]], dtype=uint8, requires_grad=True)
>>> recipes
tensor([[1, 0],
        [2, 1],
        [2, 2],
        [1, 0],
        [0, 0]], dtype=uint8, requires_grad=True)

To use the same Pauli recipes for different executions, the QuantumTape interface should be used instead:

dev = qml.device("default.qubit", wires=2, shots=5)

with qml.tape.QuantumTape() as tape:
    qml.Hadamard(wires=0)
    qml.CNOT(wires=[0, 1])
    qml.classical_shadow(wires=[0, 1])
>>> bits1, recipes1 = qml.execute([tape], device=dev, gradient_fn=None)[0][0]
>>> bits2, recipes2 = qml.execute([tape], device=dev, gradient_fn=None)[0][0]
>>> np.all(recipes1 == recipes2)
True
>>> np.all(bits1 == bits2)
False

If using different Pauli recipes is desired for the QuantumTape interface, the seed_recipes flag should be explicitly set to False:

dev = qml.device("default.qubit", wires=2, shots=5)

with qml.tape.QuantumTape() as tape:
    qml.Hadamard(wires=0)
    qml.CNOT(wires=[0, 1])
    qml.classical_shadow(wires=[0, 1], seed_recipes=False)
>>> bits1, recipes1 = qml.execute([tape], device=dev, gradient_fn=None)[0][0]
>>> bits2, recipes2 = qml.execute([tape], device=dev, gradient_fn=None)[0][0]
>>> np.all(recipes1 == recipes2)
False
>>> np.all(bits1 == bits2)
False