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."""