qml.classical_shadow

classical_shadow(wires, seed=None, 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 (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:

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