uniqc.algorithmics.measurement.classical_shadow module#

Classical Shadow state characterisation.

Implements the single-qubit classical shadow protocol from “Aaronson & Rothblum, ‘Measurement Non-Classicality’, 2019” and the multi-qubit extension from “Chen et al., ‘Efficient Classical Shadow Tomography’, 2022”.

The shadow uses the single-qubit Clifford 2-design: random measurement bases drawn uniformly from {I, H, SH} (equivalent to Z, X, Y bases), each followed by computational-basis measurement.

class uniqc.algorithmics.measurement.classical_shadow.ShadowSnapshot(unitary_indices, outcomes, counts=<factory>)[source]#

Bases: object

A single snapshot from the classical shadow protocol.

Variables:
  • unitary_indices (Tuple[int, ...]) –

    Tuple encoding which Clifford unitary was applied to each qubit before measurement.

    Mapping (index → unitary → basis measured):

    0 → I (Z basis) 1 → H (X basis) 2 → S·H (Y basis, S = diag(1, i))

  • outcomes (Tuple[int, ...]) – Tuple of bits (0/1) sampled from the computational-basis distribution for each qubit. Bits are LSB-first — outcomes[i] is qubit i’s measurement.

  • counts (Dict[int, int]) – Empirical outcome counts for this basis setting (integer keys are LSB-first qubit-bit-packed outcomes). Populated from all simulated shots; enables probability-scoring / exact-Born estimators in shadow_expectation() with much lower variance than the single-outcome HKP estimator.

Parameters:
counts: Dict[int, int]#
outcomes: Tuple[int, ...]#
unitary_indices: Tuple[int, ...]#
uniqc.algorithmics.measurement.classical_shadow.classical_shadow(circuit, qubits=None, shots=4096, n_shadow=None)[source]#

Generate classical-shadow snapshots of a quantum state.

Each snapshot is obtained by:

  1. For each qubit, choosing uniformly at random one of the three single-qubit Cliffords {I, H, S·H} — corresponding to measurement in the Z, X, or Y basis.

  2. Injecting the corresponding gates before the existing MEASURE instructions in the circuit QASM.

  3. Simulating the modified circuit once and recording the computational-basis outcomes.

  4. Storing (unitary_indices, outcomes) as one snapshot.

The collection of snapshots enables estimating the expectation value of any Pauli string via shadow_expectation() with sample complexity \(O(\log M / ε^2)\) for M observables.

Parameters:
  • circuit (Circuit) – Quantum circuit (must already contain MEASURE instructions).

  • qubits (List[int] | None) – Indices of qubits to include. None means all qubits used by the circuit.

  • shots (int) – Number of simulated measurement shots per snapshot. Higher shots reduce per-snapshot variance but are slower.

  • n_shadow (int | None) – Number of independent shadow snapshots. None auto-computes as 2 * n * log(2/δ) with δ = 0.01. This bound guarantees fidelity error ≤ ε with high probability for up to M = exp(ε² n / 10) observables.

Returns:

List of ShadowSnapshot objects.

Raises:

ValueErrorshots or n_shadow is not a positive integer.

Return type:

List[ShadowSnapshot]

Example

>>> from uniqc.circuit_builder import Circuit
>>> from uniqc.algorithmics.measurement import (
...     classical_shadow, shadow_expectation
... )
>>> c = Circuit()
>>> c.h(0)
>>> c.cx(0, 1)
>>> c.measure(0, 1)
>>> shadows = classical_shadow(c, shots=1024, n_shadow=32)
>>> est_ZZ = shadow_expectation(shadows, "ZZ")
>>> abs(est_ZZ - 1.0) < 0.1
True
uniqc.algorithmics.measurement.classical_shadow.shadow_expectation(shadows, pauli_string)[source]#

Estimate the expectation value of a Pauli string from classical-shadow snapshots.

Computes the mean of single-snapshot HKP estimators. For a single observable the mean is optimal; median-of-means buys robustness only when estimating many Paulis with uniform tail-bound guarantees.

For each snapshot the single-qubit estimator is (Huang-Kueng-Preskill, single-qubit Clifford shadow inverse channel M^{-1}(X) = 3X - I):

s_i = 1 if Pauli_i = I s_i = 3 * (-1)^outcome_i if Pauli_i ≠ I and aligned with measured basis s_i = 0 if Pauli_i ≠ I and misaligned with measured basis

The n-qubit estimator is the product \(\hat{P}=\prod_i s_i\).

Parameters:
Returns:

Estimated expectation value <P>.

Raises:
  • ValueErrorpauli_string length does not match snapshot size.

  • ValueErrorpauli_string contains invalid characters.

Return type:

float

Example

>>> shadows = classical_shadow(circuit, shots=1024, n_shadow=32)
>>> shadow_expectation(shadows, "ZZ")   # estimate <ZZ>