File size: 6,636 Bytes
2a87ede | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | """Magic entropy: a single-qubit density-matrix diagnostic built from the
3-fold self-convolution "Key Unitary" construction (Bu, Gu, Jaffe,
"Stabilizer testing and magic entropy", arXiv:2306.09292, Definitions 7-8).
Originated from a Colab proposal for a pairwise "Quantum Ruzsa Divergence"
(following Bu, Gu, Jaffe, "A convolutional quantum Ruzsa divergence and
its applications", arXiv:2401.14385) that turned out to have no valid
definition for qubits: that paper's pairwise convolution needs
s^2+t^2=1 mod d, which has no solution at d=2. The companion paper above
does not patch this with a qubit-specific pairwise formula -- it defines
a structurally different, minimum-3-input "Key Unitary" convolution (K
quantum registers, K must be ODD, K>=3; there is no K=2 case). For qubits
the smallest valid object is therefore the 3-fold SELF-convolution of one
state with itself, boxtimes_3(rho,rho,rho), and the entropy of its
reduced output register is what the paper calls "magic entropy": zero
for stabilizer states, positive for non-stabilizer ("magic") states (the
paper's Examples 32/33).
Validated end-to-end in Dense-Evolution-Discovery, Experiment 30
(https://tatopenn-cell.github.io/Dense-Evolution-Discovery/quantum_ruzsa_magic_entropy/):
the Key Unitary circuit was checked basis-state by basis-state against
the paper's own Lemma 9 combinatorial identity; magic_entropy was
checked against all six single-qubit stabilizer states (~0, max observed
4e-11) and the two standard magic states T and H (0.811 bits, matching
each other exactly as expected by symmetry); and it was used as a noise
diagnostic that is qualitatively distinct from uhlmann_fidelity and the
sandwiched Renyi divergence (see renyi.py in this same subpackage) --
under amplitude damping it is non-monotonic (rises then returns to
exactly 0, since the p=1 fixed point |0> is a stabilizer state), unlike
either of those two, which change monotonically over the same sweep.
Restricted to SINGLE-QUBIT density matrices (2x2) -- the Key Unitary here
is built for n=1-qubit registers specifically; a multi-qubit
generalization would need a larger Key Unitary circuit (n-qubit
registers, Definition 7 in general) not implemented here.
A shadow-measurement-based estimator for this same quantity, using
randomized measurement snapshots instead of the exact density matrix, is
promoted alongside this module in magic_entropy_shadows.py -- see that
module for why it has its own API shape (measurement snapshots in, not a
density matrix) rather than a function alongside this one.
"""
import jax
import jax.numpy as jnp
import numpy as np
__all__ = ["magic_entropy", "magic_entropy_jit"]
def _cnot_matrix(control: int, target: int, n: int = 3) -> np.ndarray:
"""Permutation matrix for a CNOT(control->target) gate on n qubits,
computational basis ordered as |q0 q1 ... q(n-1)> (this package's
MSB-first convention, matching dense_evolution.physics.entropy)."""
dim = 2 ** n
mat = np.zeros((dim, dim))
for i in range(dim):
bits = [(i >> (n - 1 - k)) & 1 for k in range(n)]
if bits[control]:
bits[target] ^= 1
j = 0
for bit in bits:
j = (j << 1) | bit
mat[j, i] = 1.0
return mat
def _build_key_unitary_k3() -> jnp.ndarray:
"""Definition 7 (Bu, Gu, Jaffe, arXiv:2306.09292, p.6), specialized to
K=3 registers of n=1 qubit each -- the minimum valid case (K must be
odd). U = (CNOT_{2->1} CNOT_{3->1}) (CNOT_{1->2} CNOT_{1->3}): layer 1
fans register 1 into registers 2,3; layer 2 XORs registers 2,3 (their
post-layer-1 values) back into register 1. Verified basis-state by
basis-state against the paper's own Lemma 9 identity in
Dense-Evolution-Discovery's tests/test_quantum_ruzsa_magic_entropy.py."""
layer1 = _cnot_matrix(0, 2) @ _cnot_matrix(0, 1)
layer2 = _cnot_matrix(2, 0) @ _cnot_matrix(1, 0)
return jnp.array(layer2 @ layer1, dtype=jnp.complex128)
_KEY_UNITARY_K3 = _build_key_unitary_k3()
def _self_convolve_3_core(rho: jnp.ndarray) -> jnp.ndarray:
"""boxtimes_3(rho,rho,rho) = Tr_{2,3}[V (rho (x) rho (x) rho) V^dagger]
(Definition 8). `rho` may be pure or mixed. Not built on
`dense_evolution.physics.entropy.partial_trace` -- that helper is
pure-statevector-only, and this needs to trace out a general (possibly
mixed) 3-qubit density matrix built from a possibly-mixed input."""
rho_full = jnp.kron(jnp.kron(rho, rho), rho)
evolved = _KEY_UNITARY_K3 @ rho_full @ jnp.conj(_KEY_UNITARY_K3).T
tensor = evolved.reshape(2, 2, 2, 2, 2, 2)
return jnp.einsum("ijkljk->il", tensor)
def _magic_entropy_core(rho: jnp.ndarray, eps: float = 1e-12) -> jnp.ndarray:
reduced = _self_convolve_3_core(rho)
ev = jnp.linalg.eigvalsh(reduced)
safe_ev = jnp.clip(jnp.real(ev), eps, 1.0)
return -jnp.sum(safe_ev * jnp.log2(safe_ev))
def magic_entropy(rho: jnp.ndarray) -> float:
"""Magic entropy of a single-qubit density matrix `rho` (2x2), in bits
(log2) -- NOTE this differs from
`dense_evolution.physics.entropy.von_neumann_entropy`'s natural-log
(nats) convention; kept as log2 here to match both the source paper's
own convention and the values already published in
Dense-Evolution-Discovery's Experiment 30.
Zero for every single-qubit stabilizer state (|0>, |1>, |+>, |->,
|+i>, |-i>), positive for non-stabilizer ("magic") states -- e.g. the
T-state and H-state both give 0.811 bits.
Differentiable through `jax.grad`, including at stabilizer states
where the reduced matrix's eigenvalues are exactly degenerate (e.g.
the fully mixed state I/2 gives eigenvalues [0.5, 0.5]): unlike
`uhlmann_fidelity`, which needs a custom `_eigh_degenerate_safe` JVP
rule because it reconstructs eigenVECTORS (ill-defined in a
degenerate eigenspace), this function only ever needs eigenVALUES
(`jnp.linalg.eigvalsh`, no eigenvectors), whose gradient is
well-defined even at exact degeneracies -- confirmed directly:
`jax.grad(magic_entropy)` is finite (no NaN) at both the fully mixed
state and a magic state.
"""
rho = jnp.asarray(rho, dtype=jnp.complex128)
return float(_magic_entropy_core(rho))
magic_entropy_jit = jax.jit(_magic_entropy_core)
"""`jax.jit`-compiled entry point for `magic_entropy`. `rho` must already
be `complex128`. Returns a jnp scalar, not a Python `float` -- call
`float(...)` yourself if you need one outside a jitted context."""
|