qml.classical_shadow

classical_shadow(wires, seed=None)[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 (Union[None, int]) – Seed used to randomly sample Pauli measurements during the classical shadows protocol. If None, a random seed will be generated. If a tape with a classical_shadow measurement is copied, the seed will also be copied. Different seeds are still generated for different constructed tapes.

Returns:

measurement process instance

Return type:

ClassicalShadowMP

Example

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

from functools import partial
dev = qml.device("default.qubit", wires=2)

@partial(qml.set_shots, 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)

ops = [qml.Hadamard(wires=0), qml.CNOT(wires=(0,1))]
measurements = [qml.classical_shadow(wires=(0,1))]
tape = qml.tape.QuantumTape(ops, measurements, shots=5)
>>> bits1, recipes1 = qml.execute([tape], device=dev, diff_method=None)[0]
>>> bits2, recipes2 = qml.execute([tape], device=dev, diff_method=None)[0]
>>> np.all(recipes1 == recipes2)
True
>>> np.all(bits1 == bits2)
False

If using different Pauli recipes is desired for the QuantumTape interface, different seeds should be used for the classical shadow:

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

measurements1 = [qml.classical_shadow(wires=(0,1), seed=10)]
tape1 = qml.tape.QuantumTape(ops, measurements1, shots=5)

measurements2 = [qml.classical_shadow(wires=(0,1), seed=15)]
tape2 = qml.tape.QuantumTape(ops, measurements2, shots=5)
>>> bits1, recipes1 = qml.execute([tape1], device=dev, diff_method=None)[0]
>>> bits2, recipes2 = qml.execute([tape2], device=dev, diff_method=None)[0]
>>> np.all(recipes1 == recipes2)
False
>>> np.all(bits1 == bits2)
False