Skip to content

Entropy (partial trace, von Neumann entropy, mutual information)

Multi-qubit partial trace, von Neumann entropy, and quantum mutual information — nothing like this existed anywhere in the package before these functions were promoted. The only prior partial trace (dashboard_core/state_visuals.py's private _reduced_density_matrix) is single-qubit-only and uses the opposite, little-endian convention (qubit 0 = least significant bit). This module uses the package's own convention instead, matching observables/ pauli_hamiltonian_to_matrix: qubit 0 is the most significant bit of the basis-state index — do not mix the two, reusing the dashboard's helper here would silently transpose which qubits get traced out.

mutual_information exists because a qubit entangled in a Bell pair (or more generally, maximally mixed on its own) has a marginal <Z> of exactly 0 regardless of what operation was applied to its partner — the no-signaling theorem, not a measurement limitation. Mutual information can reveal correlations a marginal expectation value structurally cannot, since it depends on the joint state of two subsystems, not either one alone. Verified against the exact textbook value for a Bell pair (I = 2*ln(2), maximal) and a GHZ state.

entropy

Multi-qubit partial trace, von Neumann entropy, and mutual information.

Nothing like this existed anywhere in the package before: the only prior partial trace (dashboard_core/state_visuals.py's private _reduced_density_matrix) is single-qubit-only and uses the opposite, little-endian convention (qubit 0 = least significant bit). Everything here uses this package's own convention instead, matching dense_evolution.observables/pauli_hamiltonian_to_matrix: qubit 0 is the most significant bit of the basis-state index. Do not mix the two -- reusing dashboard_core's helper here would silently transpose which qubits get traced out.

Originated in research/wormhole_syk.py (a traversable-wormhole-inspired quantum teleportation reproduction) -- promoted here because these are generic quantum-information utilities, not specific to that experiment. Any state can have a subsystem's reduced density matrix, entropy, or the mutual information between two subsystems computed with these three functions; the wormhole work needed all three because the physically meaningful readout there (a message injected into one system showing up correlated with a reference qubit) is not visible in any single-qubit expectation value -- see mutual_information's docstring.

partial_trace

partial_trace(state, n_qubits, keep_qubits)

Reduced density matrix on keep_qubits, tracing out the rest.

Parameters

state : np.ndarray A pure statevector of length 2**n_qubits. n_qubits : int keep_qubits : list[int] Qubit indices (this package's MSB-first convention) to keep.

Returns

np.ndarray Density matrix of shape (2len(keep_qubits), 2len(keep_qubits)).

Source code in dense_evolution/entropy.py
def partial_trace(state, n_qubits, keep_qubits):
    """Reduced density matrix on `keep_qubits`, tracing out the rest.

    Parameters
    ----------
    state : np.ndarray
        A pure statevector of length 2**n_qubits.
    n_qubits : int
    keep_qubits : list[int]
        Qubit indices (this package's MSB-first convention) to keep.

    Returns
    -------
    np.ndarray
        Density matrix of shape (2**len(keep_qubits), 2**len(keep_qubits)).
    """
    keep_qubits = sorted(keep_qubits)
    trace_qubits = [q for q in range(n_qubits) if q not in keep_qubits]
    psi = np.transpose(np.asarray(state).reshape([2] * n_qubits), keep_qubits + trace_qubits)
    keep_dim, trace_dim = 2 ** len(keep_qubits), 2 ** len(trace_qubits)
    psi = psi.reshape(keep_dim, trace_dim)
    return psi @ psi.conj().T

von_neumann_entropy

von_neumann_entropy(rho)

S(rho) = -Tr(rho log rho), computed from rho's eigenvalues. Nearly- zero eigenvalues (which a numerically pure/near-pure state produces, and which are mathematically forbidden from being exactly negative for a real density matrix but can land at a tiny negative float) are clipped before the log rather than raising or propagating a NaN.

Source code in dense_evolution/entropy.py
def von_neumann_entropy(rho):
    """S(rho) = -Tr(rho log rho), computed from rho's eigenvalues. Nearly-
    zero eigenvalues (which a numerically pure/near-pure state produces,
    and which are mathematically forbidden from being exactly negative
    for a real density matrix but can land at a tiny negative float) are
    clipped before the log rather than raising or propagating a NaN."""
    eigs = np.clip(np.linalg.eigvalsh(rho).real, 1e-14, None)
    return float(-np.sum(eigs * np.log(eigs)))

mutual_information

mutual_information(state, n_qubits, qubits_a, qubits_b)

I(A:B) = S(A) + S(B) - S(A union B), the standard quantum mutual information between two disjoint subsystems of a pure global state.

Why this and not a single-qubit expectation value: a qubit entangled in a Bell pair (or more generally, maximally mixed on its own) has a marginal of exactly 0 regardless of what operation was applied to its partner -- this is the no-signaling theorem, not a measurement limitation, and no amount of clever circuit design around a single-qubit readout can get around it. Mutual information can reveal correlations a marginal expectation value structurally cannot, because it depends on the joint state of A and B, not either one alone. Verified in tests/test_entropy.py against the exact textbook value for a Bell pair (I = 2*ln(2), maximal) and a GHZ state.

Source code in dense_evolution/entropy.py
def mutual_information(state, n_qubits, qubits_a, qubits_b):
    """I(A:B) = S(A) + S(B) - S(A union B), the standard quantum mutual
    information between two disjoint subsystems of a pure global state.

    Why this and not a single-qubit expectation value: a qubit entangled
    in a Bell pair (or more generally, maximally mixed on its own) has a
    marginal <Z> of exactly 0 regardless of what operation was applied to
    its partner -- this is the no-signaling theorem, not a measurement
    limitation, and no amount of clever circuit design around a
    single-qubit readout can get around it. Mutual information *can*
    reveal correlations a marginal expectation value structurally cannot,
    because it depends on the *joint* state of A and B, not either one
    alone. Verified in tests/test_entropy.py against the exact textbook
    value for a Bell pair (I = 2*ln(2), maximal) and a GHZ state.
    """
    s_a = von_neumann_entropy(partial_trace(state, n_qubits, qubits_a))
    s_b = von_neumann_entropy(partial_trace(state, n_qubits, qubits_b))
    s_ab = von_neumann_entropy(partial_trace(state, n_qubits, list(qubits_a) + list(qubits_b)))
    return s_a + s_b - s_ab

See also: fermions and trotter, the other two modules promoted alongside this one from a real traversable- wormhole-inspired quantum teleportation reproduction (arXiv:2604.10090) — see Dense-Evolution-Discovery for the real experiments, including a control run confirming mutual_information correctly returns exactly 0 when two subsystems are structurally disconnected.