Skip to content

Measurement (shot sampling & pure-state fidelity)

Turning a statevector into finite-shot counts (sample_counts, a Qiskit-get_counts() equivalent), and comparing two pure states directly (statevector_fidelity) without building a density matrix first.

measurement

Measurement-related conveniences: turning a statevector into finite-shot counts the way real hardware and every other SDK report results (Qiskit's get_counts(), PennyLane's qml.counts()), and comparing two pure states directly without building a density matrix first.

Sampling a statevector into shot counts is another pattern found hand-duplicated across this package's own experiment scripts -- always the same rng.choice(dim, p=probs) + np.bincount (or np.unique) couple of lines, rewritten fresh each time.

sample_counts

sample_counts(statevector, n_shots, rng=None)

Simulate n_shots projective measurements of every qubit in the computational basis, returning a Qiskit-style counts dict.

Parameters

statevector : array-like, shape (2**n_qubits,) n_shots : int Number of measurement shots to sample, must be >= 1. rng : numpy.random.Generator, optional A fresh numpy.random.default_rng() is used if not given -- pass one explicitly for reproducible sampling.

Returns

dict[str, int] Bitstring keys read left-to-right as qubit 0 upward (matching DenseSVSimulator's own qubit-0-is-MSB layout and pauli_expectation's string convention), each mapped to how many of the n_shots samples landed on it. Only bitstrings with at least one hit appear (unobserved outcomes are simply absent, as in Qiskit's get_counts()).

Examples

import dense_evolution as de sim = de.DenseSVSimulator(2) sim.run_circuit([('h', 0), ('cx', 0, 1)]) de.sample_counts(sim.get_statevector(), 1000) {'00': 494, '11': 506}

Source code in dense_evolution/measurement.py
def sample_counts(statevector, n_shots, rng=None):
    """
    Simulate n_shots projective measurements of every qubit in the
    computational basis, returning a Qiskit-style counts dict.

    Parameters
    ----------
    statevector : array-like, shape (2**n_qubits,)
    n_shots : int
        Number of measurement shots to sample, must be >= 1.
    rng : numpy.random.Generator, optional
        A fresh `numpy.random.default_rng()` is used if not given --
        pass one explicitly for reproducible sampling.

    Returns
    -------
    dict[str, int]
        Bitstring keys read left-to-right as qubit 0 upward (matching
        DenseSVSimulator's own qubit-0-is-MSB layout and
        pauli_expectation's string convention), each mapped to how many
        of the n_shots samples landed on it. Only bitstrings with at
        least one hit appear (unobserved outcomes are simply absent, as
        in Qiskit's get_counts()).

    Examples
    --------
    >>> import dense_evolution as de
    >>> sim = de.DenseSVSimulator(2)
    >>> sim.run_circuit([('h', 0), ('cx', 0, 1)])
    >>> de.sample_counts(sim.get_statevector(), 1000)
    {'00': 494, '11': 506}
    """
    statevector = np.asarray(statevector)
    dim = statevector.shape[0]
    n_qubits = dim.bit_length() - 1
    if 1 << n_qubits != dim:
        raise ValueError(f"statevector length {dim} is not a power of 2")
    if n_shots < 1:
        raise ValueError(f"n_shots must be >= 1, got {n_shots}")

    probs = np.abs(statevector) ** 2
    total = probs.sum()
    if not np.isfinite(total) or total <= 0:
        raise ValueError("statevector has zero or non-finite total probability")
    probs = probs / total

    if rng is None:
        rng = np.random.default_rng()
    outcomes = rng.choice(dim, size=n_shots, p=probs)
    values, freqs = np.unique(outcomes, return_counts=True)

    return {format(int(v), f'0{n_qubits}b'): int(f) for v, f in zip(values, freqs)}

statevector_fidelity

statevector_fidelity(statevector_a, statevector_b)

Fidelity ||^2 between two pure statevectors -- the cheap, direct pure-state counterpart to uhlmann_fidelity (which compares two full density matrices, needed for the mixed/noisy states that zne_density_matrix and friends work with). Use this one whenever both states are pure; it never builds an O(dim^2) density matrix.

Parameters

statevector_a, statevector_b : array-like, shape (2**n_qubits,) Must have the same shape.

Returns

float In [0, 1] up to floating-point error. 1.0 for identical states (up to global phase), 0.0 for orthogonal states.

Source code in dense_evolution/measurement.py
def statevector_fidelity(statevector_a, statevector_b):
    """
    Fidelity |<a|b>|^2 between two pure statevectors -- the cheap, direct
    pure-state counterpart to `uhlmann_fidelity` (which compares two full
    density matrices, needed for the mixed/noisy states that
    `zne_density_matrix` and friends work with). Use this one whenever
    both states are pure; it never builds an O(dim^2) density matrix.

    Parameters
    ----------
    statevector_a, statevector_b : array-like, shape (2**n_qubits,)
        Must have the same shape.

    Returns
    -------
    float
        In [0, 1] up to floating-point error. 1.0 for identical states
        (up to global phase), 0.0 for orthogonal states.
    """
    a = np.asarray(statevector_a)
    b = np.asarray(statevector_b)
    if a.shape != b.shape:
        raise ValueError(f"statevector shapes differ: {a.shape} vs {b.shape}")
    return float(np.abs(np.vdot(a, b)) ** 2)

See also: uhlmann_fidelity for the density-matrix fidelity statevector_fidelity is the pure-state counterpart of.