uniqc.algorithms.core.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.algorithms.core.measurement.classical_shadow.ClassicalShadow(circuit, qubits=None, shots=4096, n_shadow=None)[source]

Bases: object

Class-based interface for classical-shadow tomography.

The constructor takes a clean state-preparation circuit (no measurements).

Parameters:
execute(backend='statevector', *, program_type='qasm', **kwargs)[source]

Run classical-shadow tomography and return the snapshot list.

get_readout_circuits()[source]

Snapshots are random; this is a no-op stub returning an empty list.

class uniqc.algorithms.core.measurement.classical_shadow.ShadowSnapshot(unitary_indices, outcomes, counts=<factory>, _expectation_cache=<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.

  • _expectation_cache (dict[int, float]) – Internal cache keyed by the non-identity Pauli support bitmask. It reuses the counts-derived Born expectation across repeated shadow_expectation() calls on the same snapshots.

Parameters:
counts: dict[int, int]
outcomes: tuple[int, ...]
unitary_indices: tuple[int, ...]
uniqc.algorithms.core.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.algorithms.core.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.algorithms.core.measurement.classical_shadow.classical_shadow_example()[source]

Tiny classical-shadow demo on a 2-qubit Bell state.

uniqc.algorithms.core.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>