dense-evolution 8.3.0__py3-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. dashboard_core/__init__.py +115 -0
  2. dashboard_core/_gate_tables.py +30 -0
  3. dashboard_core/band_structure.py +71 -0
  4. dashboard_core/circuit_builder_component.py +232 -0
  5. dashboard_core/circuit_diagram.py +216 -0
  6. dashboard_core/crypto_protocols.py +77 -0
  7. dashboard_core/engine.py +326 -0
  8. dashboard_core/graphical_builder.py +114 -0
  9. dashboard_core/hamiltonians.py +593 -0
  10. dashboard_core/mass_decomposition_tool.py +47 -0
  11. dashboard_core/mitigation.py +343 -0
  12. dashboard_core/native_hf_diagnostics.py +62 -0
  13. dashboard_core/noise_tools.py +125 -0
  14. dashboard_core/qasm_library.py +233 -0
  15. dashboard_core/qmmm.py +16 -0
  16. dashboard_core/rag_tool.py +45 -0
  17. dashboard_core/state_visuals.py +288 -0
  18. dashboard_core/system_limits.py +60 -0
  19. dashboard_core/vector_healing.py +102 -0
  20. dashboard_core/visuals.py +158 -0
  21. dashboard_core/vqe.py +533 -0
  22. dashboard_core/wormhole.py +580 -0
  23. dense_evolution/__init__.py +114 -0
  24. dense_evolution/autodiff.py +10 -0
  25. dense_evolution/backends/__init__.py +5 -0
  26. dense_evolution/backends/chunk/__init__.py +37 -0
  27. dense_evolution/backends/chunk/_engine_imports.py +57 -0
  28. dense_evolution/backends/chunk/circuit_chunker.py +55 -0
  29. dense_evolution/backends/chunk/core.py +432 -0
  30. dense_evolution/backends/chunk/disk_overflow.py +232 -0
  31. dense_evolution/backends/chunk/geometry.py +95 -0
  32. dense_evolution/backends/chunk/guard.py +190 -0
  33. dense_evolution/backends/chunk/kernels.py +531 -0
  34. dense_evolution/backends/mps.py +1569 -0
  35. dense_evolution/backends/statevector.py +616 -0
  36. dense_evolution/chunk.py +25 -0
  37. dense_evolution/circuits/__init__.py +20 -0
  38. dense_evolution/circuits/compiler.py +488 -0
  39. dense_evolution/circuits/diagram.py +94 -0
  40. dense_evolution/circuits/gates.py +91 -0
  41. dense_evolution/circuits/parser.py +632 -0
  42. dense_evolution/circuits/qft.py +66 -0
  43. dense_evolution/circuits/random_circuit.py +85 -0
  44. dense_evolution/circuits/registry.py +74 -0
  45. dense_evolution/circuits/topology.py +79 -0
  46. dense_evolution/circuits/trotter.py +265 -0
  47. dense_evolution/circuits/uccsd.py +275 -0
  48. dense_evolution/cli.py +199 -0
  49. dense_evolution/compiler.py +9 -0
  50. dense_evolution/config.py +49 -0
  51. dense_evolution/drawing.py +10 -0
  52. dense_evolution/entropy.py +9 -0
  53. dense_evolution/fermions.py +9 -0
  54. dense_evolution/gates.py +9 -0
  55. dense_evolution/harrison_tb.py +16 -0
  56. dense_evolution/healing.py +18 -0
  57. dense_evolution/interop/__init__.py +18 -0
  58. dense_evolution/interop/qiskit_pennylane.py +406 -0
  59. dense_evolution/measurement.py +10 -0
  60. dense_evolution/mitigation/__init__.py +54 -0
  61. dense_evolution/mitigation/healing.py +215 -0
  62. dense_evolution/mitigation/kl_divergence.py +93 -0
  63. dense_evolution/mitigation/magic_entropy.py +163 -0
  64. dense_evolution/mitigation/magic_entropy_shadows.py +262 -0
  65. dense_evolution/mitigation/renyi.py +168 -0
  66. dense_evolution/mitigation/stabilizer_renyi_entropy.py +103 -0
  67. dense_evolution/mitigation/zne.py +990 -0
  68. dense_evolution/mps.py +9 -0
  69. dense_evolution/native_hf/__init__.py +26 -0
  70. dense_evolution/native_hf/_libcint/LICENSE-libcint +10 -0
  71. dense_evolution/native_hf/_libcint/libdecint.dll +0 -0
  72. dense_evolution/native_hf/assembly.py +304 -0
  73. dense_evolution/native_hf/basis.py +117 -0
  74. dense_evolution/native_hf/boys.py +35 -0
  75. dense_evolution/native_hf/bridge.py +112 -0
  76. dense_evolution/native_hf/cartesian.py +64 -0
  77. dense_evolution/native_hf/coulomb.py +196 -0
  78. dense_evolution/native_hf/differentiable.py +53 -0
  79. dense_evolution/native_hf/gaussians.py +79 -0
  80. dense_evolution/native_hf/kinetic.py +52 -0
  81. dense_evolution/native_hf/libcint_bridge.py +167 -0
  82. dense_evolution/native_hf/overlap.py +91 -0
  83. dense_evolution/native_hf/scf.py +404 -0
  84. dense_evolution/noise/__init__.py +79 -0
  85. dense_evolution/noise/coherent_attack.py +264 -0
  86. dense_evolution/noise/cosmic_ray.py +61 -0
  87. dense_evolution/noise/density_matrix_channels.py +78 -0
  88. dense_evolution/noise/differentiable.py +66 -0
  89. dense_evolution/noise/kraus/__init__.py +6 -0
  90. dense_evolution/noise/kraus/amplitude_damping.py +47 -0
  91. dense_evolution/noise/kraus/bitflip.py +22 -0
  92. dense_evolution/noise/kraus/combined.py +16 -0
  93. dense_evolution/noise/kraus/depolarizing.py +47 -0
  94. dense_evolution/noise/kraus/ideal.py +10 -0
  95. dense_evolution/noise/kraus/phaseflip.py +21 -0
  96. dense_evolution/noise/kraus_channels.py +285 -0
  97. dense_evolution/noise/oscillating.py +32 -0
  98. dense_evolution/noise/pink.py +80 -0
  99. dense_evolution/observables.py +11 -0
  100. dense_evolution/parser.py +9 -0
  101. dense_evolution/physics/__init__.py +27 -0
  102. dense_evolution/physics/entropy.py +161 -0
  103. dense_evolution/physics/fermions.py +322 -0
  104. dense_evolution/physics/observables.py +523 -0
  105. dense_evolution/physics/qec.py +1113 -0
  106. dense_evolution/physics/spectral.py +143 -0
  107. dense_evolution/physics/states.py +43 -0
  108. dense_evolution/protocols/__init__.py +27 -0
  109. dense_evolution/protocols/bb84.py +133 -0
  110. dense_evolution/protocols/di_qkd_ghz.py +199 -0
  111. dense_evolution/protocols/dicka_protocol2.py +124 -0
  112. dense_evolution/qec.py +20 -0
  113. dense_evolution/qft.py +9 -0
  114. dense_evolution/qmmm/__init__.py +13 -0
  115. dense_evolution/qmmm/ase_bridge.py +97 -0
  116. dense_evolution/qmmm/forces.py +388 -0
  117. dense_evolution/qmmm/propagation.py +80 -0
  118. dense_evolution/qmmm/region.py +137 -0
  119. dense_evolution/random_circuit.py +15 -0
  120. dense_evolution/registry.py +9 -0
  121. dense_evolution/simulator.py +10 -0
  122. dense_evolution/solvers/__init__.py +19 -0
  123. dense_evolution/solvers/autodiff.py +169 -0
  124. dense_evolution/solvers/harrison_tb.py +189 -0
  125. dense_evolution/solvers/vhd_tb.py +187 -0
  126. dense_evolution/states.py +9 -0
  127. dense_evolution/topology.py +9 -0
  128. dense_evolution/trotter.py +9 -0
  129. dense_evolution/utils/__init__.py +13 -0
  130. dense_evolution/utils/drawing.py +101 -0
  131. dense_evolution/utils/mass_decomposition.py +246 -0
  132. dense_evolution/utils/measurement.py +94 -0
  133. dense_evolution/vhd_tb.py +16 -0
  134. dense_evolution-8.3.0.dist-info/METADATA +366 -0
  135. dense_evolution-8.3.0.dist-info/RECORD +165 -0
  136. dense_evolution-8.3.0.dist-info/WHEEL +5 -0
  137. dense_evolution-8.3.0.dist-info/entry_points.txt +2 -0
  138. dense_evolution-8.3.0.dist-info/licenses/license.md +58 -0
  139. dense_evolution-8.3.0.dist-info/top_level.txt +5 -0
  140. ia_utils/__init__.py +0 -0
  141. ia_utils/adversarial_vector_attack.py +196 -0
  142. ia_utils/rag.py +288 -0
  143. ia_utils/vector_healing.py +399 -0
  144. local_site/__init__.py +0 -0
  145. local_site/app/__init__.py +0 -0
  146. local_site/app/server.py +1009 -0
  147. mcp_server/__init__.py +0 -0
  148. mcp_server/client.py +324 -0
  149. mcp_server/config.py +32 -0
  150. mcp_server/models.py +347 -0
  151. mcp_server/molecules.py +71 -0
  152. mcp_server/server.py +119 -0
  153. mcp_server/tools/__init__.py +0 -0
  154. mcp_server/tools/chemistry_tools.py +225 -0
  155. mcp_server/tools/circuit_tools.py +83 -0
  156. mcp_server/tools/crypto_tools.py +66 -0
  157. mcp_server/tools/mitigation_tools.py +81 -0
  158. mcp_server/tools/noise_tools.py +60 -0
  159. mcp_server/tools/retrieval_tools.py +44 -0
  160. mcp_server/tools/system_tools.py +149 -0
  161. mcp_server/tools/wormhole_tools.py +142 -0
  162. mcp_server/utils/__init__.py +0 -0
  163. mcp_server/utils/cache.py +55 -0
  164. mcp_server/utils/images.py +67 -0
  165. mcp_server/utils/truncation.py +38 -0
@@ -0,0 +1,262 @@
1
+ """Classical-shadows-based estimator for `magic_entropy` (see
2
+ `magic_entropy.py` in this same subpackage) -- estimates the same
3
+ single-qubit non-stabilizerness quantity from randomized measurement
4
+ snapshots instead of requiring the exact density matrix.
5
+
6
+ Originated from a Colab proposal for a `dense_evolution/circuits/shadows.py`
7
+ module (following Huang, Kueng, Preskill 2020, "Predicting Many Properties
8
+ of a Quantum System from Very Few Measurements") with a real bug in its
9
+ purity estimator (a missing transpose in a U-statistic einsum contraction,
10
+ silent whenever every snapshot happened to be real-valued). Fixed, then
11
+ extended -- using the same multi-copy U-statistic trick the paper says
12
+ "readily generalizes to higher order polynomials" -- to estimate
13
+ `magic_entropy`'s reduced convolution matrix from shadow snapshots instead
14
+ of the exact rho. Matured across Dense-Evolution-Discovery Experiment 31
15
+ (three real gaps found and closed there before promotion: the purity
16
+ estimator bug, missing median-of-means robustness, and no
17
+ sample-complexity guidance):
18
+ https://tatopenn-cell.github.io/Dense-Evolution-Discovery/quantum_shadows_magic_entropy/
19
+
20
+ API SHAPE differs from every other function in this subpackage: sampling
21
+ (`sample_classical_shadow`) and estimation (`magic_entropy_from_shadows`)
22
+ are separate steps, matching how classical shadows work in general -- the
23
+ snapshot data can come from this simulator (`sample_classical_shadow` uses
24
+ oracle access to `rho`'s exact Born-rule probabilities, something only a
25
+ simulator has) or, in principle, from real hardware measurement outcomes
26
+ reconstructed the same way (`rho_hat = 3 U^dagger |b><b| U - I` per
27
+ snapshot, from a recorded basis+outcome).
28
+
29
+ Restricted to SINGLE-QUBIT density matrices, matching `magic_entropy`'s
30
+ own scope.
31
+ """
32
+ import functools
33
+
34
+ import jax
35
+ import jax.numpy as jnp
36
+ import numpy as np
37
+
38
+ from .magic_entropy import _key_unitary_k3
39
+
40
+ __all__ = [
41
+ "sample_classical_shadow", "magic_entropy_from_shadows",
42
+ "approx_shadow_std", "fit_shadow_sample_complexity",
43
+ ]
44
+
45
+ # Single-qubit random-Pauli classical shadow protocol (Huang, Kueng,
46
+ # Preskill 2020, eq. 2-3): the three diagonalizing unitaries for X, Y, Z.
47
+ @functools.lru_cache(maxsize=1)
48
+ def _basis_u() -> jnp.ndarray:
49
+ """Cached, lazily-built stack of the 3 diagonalizing unitaries -- same
50
+ "defer past the import-time x64 warning" fix as magic_entropy.py's
51
+ _key_unitary_k3(); see that function's docstring for why a bare
52
+ module-level `jnp.array(..., dtype=jnp.complex128)` here used to fire
53
+ a spurious UserWarning on every `import dense_evolution`."""
54
+ h = jnp.array([[1.0, 1.0], [1.0, -1.0]], dtype=jnp.complex128) / jnp.sqrt(2.0)
55
+ sdag = jnp.array([[1.0, 0.0], [0.0, -1j]], dtype=jnp.complex128)
56
+ return jnp.stack([h, h @ sdag, jnp.eye(2, dtype=jnp.complex128)])
57
+
58
+
59
+ def sample_classical_shadow(rho: jnp.ndarray, n_snapshots: int, seed: int = 0) -> jnp.ndarray:
60
+ """Simulates the real single-qubit random-Pauli classical-shadow
61
+ measurement protocol against a known `rho` (2x2, pure or mixed): for
62
+ each of `n_snapshots` independent draws, picks a random Pauli basis
63
+ uniformly, samples a computational-basis outcome from the true
64
+ Born-rule probability under that basis (this simulator has oracle
65
+ access to `rho`, unlike real hardware), then reconstructs the
66
+ classical snapshot `rho_hat = 3 U^dagger |b><b| U - I`.
67
+
68
+ Returns an `(n_snapshots, 2, 2)` complex128 array -- feed this
69
+ directly into `magic_entropy_from_shadows`.
70
+
71
+ Each individual `rho_hat` is NOT a valid density matrix on its own
72
+ (can have negative eigenvalues) -- only the average over many
73
+ snapshots converges to the true `rho`. Verified directly in
74
+ Dense-Evolution-Discovery Experiment 31: the empirical mean over
75
+ 200,000 snapshots matched the true state to within 0.004.
76
+ """
77
+ rho = jnp.asarray(rho, dtype=jnp.complex128)
78
+ key = jax.random.PRNGKey(seed)
79
+ key_basis, key_bit = jax.random.split(key)
80
+ bases = jax.random.randint(key_basis, (n_snapshots,), 0, 3)
81
+
82
+ basis_u = _basis_u()
83
+
84
+ def prob0(basis_idx):
85
+ u = basis_u[basis_idx]
86
+ rotated = u @ rho @ jnp.conj(u).T
87
+ return jnp.clip(jnp.real(rotated[0, 0]), 0.0, 1.0)
88
+
89
+ probs0 = jax.vmap(prob0)(bases)
90
+ uniforms = jax.random.uniform(key_bit, (n_snapshots,))
91
+ bits = (uniforms > probs0).astype(jnp.int32)
92
+
93
+ def snapshot_matrix(basis_idx, bit):
94
+ u = basis_u[basis_idx]
95
+ b_ket = jnp.array([1.0, 0.0], dtype=jnp.complex128) * (1 - bit) + \
96
+ jnp.array([0.0, 1.0], dtype=jnp.complex128) * bit
97
+ proj = jnp.outer(b_ket, jnp.conj(b_ket))
98
+ return 3.0 * (jnp.conj(u).T @ proj @ u) - jnp.eye(2, dtype=jnp.complex128)
99
+
100
+ return jax.vmap(snapshot_matrix)(bases, bits)
101
+
102
+
103
+ @functools.lru_cache(maxsize=1)
104
+ def _o_operators():
105
+ """O_ab = V^dagger (|b><a| (x) I_4) V such that R_ab = Tr[O_ab . rho^{(x)3}]
106
+ -- the same construction `magic_entropy.py`'s `_self_convolve_3_core`
107
+ applies to exact `rho`, here turned into fixed operators so each entry
108
+ is a LINEAR functional of `rho^{(x)3}` (the shape a shadow-snapshot
109
+ U-statistic estimator needs). Reuses this package's own
110
+ `_key_unitary_k3()` directly rather than duplicating it (unlike
111
+ Dense-Evolution-Discovery's per-script self-containment convention,
112
+ this library's internal modules import from each other freely). The
113
+ `|b><a|` (not `|a><b|`) projector is deliberate: a direct index-expansion
114
+ check (not just the cyclic-trace derivation, which looks right on paper
115
+ but hides the swap) showed `Tr[(|a><b| (x) I) M] = R_ba`, not `R_ab` --
116
+ caught in Discovery Experiment 31 by a unit test checking matrix
117
+ entries directly, not just the downstream entropy.
118
+
119
+ Cached/lazy (functools.lru_cache), not a bare module-level constant
120
+ -- same "defer past the import-time x64 warning" fix as
121
+ magic_entropy.py's _key_unitary_k3() and this module's _basis_u()."""
122
+ i4 = jnp.eye(4, dtype=jnp.complex128)
123
+ v = _key_unitary_k3()
124
+ ops = {}
125
+ for a in range(2):
126
+ for b in range(2):
127
+ proj_ba = jnp.zeros((2, 2), dtype=jnp.complex128).at[b, a].set(1.0)
128
+ ops[(a, b)] = jnp.conj(v).T @ jnp.kron(proj_ba, i4) @ v
129
+ return ops
130
+
131
+
132
+ def _median_of_means(values: np.ndarray, n_groups: int) -> float:
133
+ """Split real-valued `values` into `n_groups` contiguous batches,
134
+ average each batch, then return the median of those batch means --
135
+ Huang et al.'s standard robustification for shadow-estimator
136
+ U-statistics. Unlike a single overall mean, the median tolerates up
137
+ to `n_groups // 2` entirely corrupted/outlier batches (e.g. a
138
+ systematic calibration fault affecting one contiguous stretch of a
139
+ measurement run) without being dragged toward them -- verified
140
+ directly in Dense-Evolution-Discovery Experiment 31: stays within 0.5
141
+ of the true value at 40% of samples corrupted, while a plain mean is
142
+ dragged from 1.0 to -19.4 under the same corruption."""
143
+ values = np.asarray(values, dtype=float)
144
+ n = len(values)
145
+ n_groups = max(1, min(n_groups, n))
146
+ group_size = n // n_groups
147
+ trimmed = values[: group_size * n_groups]
148
+ group_means = trimmed.reshape(n_groups, group_size).mean(axis=1)
149
+ return float(np.median(group_means))
150
+
151
+
152
+ def magic_entropy_from_shadows(shadow_snapshots: jnp.ndarray, n_groups: int = 20) -> float:
153
+ """Estimates `magic_entropy(rho)` from classical shadow snapshots of
154
+ `rho` (from `sample_classical_shadow`, or real hardware measurement
155
+ data reconstructed the same way) instead of the exact density matrix.
156
+
157
+ Groups the snapshots into disjoint triples, estimates each entry of
158
+ the 3-copy self-convolution's reduced matrix `R` via median-of-means
159
+ over `Tr[O_ab . (rho_hat_i (x) rho_hat_j (x) rho_hat_k)]` (unbiased,
160
+ since each triple's three snapshots are independent unbiased
161
+ estimators of `rho`; real and imaginary parts of each entry are
162
+ median-of-means'd separately, the standard practical choice since a
163
+ complex median has no single definition), then computes the von
164
+ Neumann entropy of the (Hermitized, eigenvalue-clipped, trace-
165
+ renormalized) ESTIMATED `R` classically -- entropy itself is never
166
+ shadow-estimated directly, matching how Huang et al. handle their own
167
+ Renyi-2 entanglement entropy example.
168
+
169
+ Not `jax.jit`-compatible (unlike every other function in this
170
+ subpackage): median-of-means uses `numpy.median`, which has no
171
+ equivalent JAX primitive at this scale.
172
+
173
+ See `approx_shadow_std`/`fit_shadow_sample_complexity` for how many
174
+ snapshots this needs for a given error tolerance.
175
+ """
176
+ n = shadow_snapshots.shape[0]
177
+ n_triples = n // 3
178
+ if n_triples < 1:
179
+ raise ValueError(f"need at least 3 shadow snapshots to form one triple, got {n}")
180
+ triples = shadow_snapshots[: n_triples * 3].reshape(n_triples, 3, 2, 2)
181
+
182
+ def triple_kron(t):
183
+ return jnp.kron(jnp.kron(t[0], t[1]), t[2])
184
+
185
+ rho3_batch = jax.vmap(triple_kron)(triples)
186
+
187
+ r_hat = jnp.zeros((2, 2), dtype=jnp.complex128)
188
+ for (a, b), o_ab in _o_operators().items():
189
+ vals = np.array(jnp.einsum("ij,tji->t", o_ab, rho3_batch))
190
+ real_part = _median_of_means(vals.real, n_groups)
191
+ imag_part = _median_of_means(vals.imag, n_groups)
192
+ r_hat = r_hat.at[a, b].set(real_part + 1j * imag_part)
193
+
194
+ r_hat = 0.5 * (r_hat + jnp.conj(r_hat).T) # enforce Hermiticity
195
+ ev = jnp.linalg.eigvalsh(r_hat)
196
+ safe_ev = jnp.clip(ev.real, 1e-9, None)
197
+ safe_ev = safe_ev / jnp.sum(safe_ev) # renormalize (estimation noise can shift trace off 1)
198
+ return float(-jnp.sum(safe_ev * jnp.log2(safe_ev)))
199
+
200
+
201
+ # Empirically fitted in Dense-Evolution-Discovery Experiment 31 (20 trials
202
+ # per snapshot count on a |T> state, n_groups=20): std(n_snapshots) ~
203
+ # _FIT_C / n_snapshots ** _FIT_P. The T-state is a standard maximally-magic
204
+ # single-qubit state, a reasonable but NOT rigorously proven proxy for a
205
+ # "hard" case -- this is a rough built-in guide, not a formal guarantee for
206
+ # an arbitrary rho.
207
+ _FIT_C = 11.751
208
+ _FIT_P = 0.546
209
+
210
+
211
+ def approx_shadow_std(n_snapshots: int) -> float:
212
+ """Rough approximate standard deviation (bits) of
213
+ `magic_entropy_from_shadows`'s estimate at a given snapshot count,
214
+ from an empirical fit (not a formal theorem) calibrated on a `|T>`
215
+ state in Dense-Evolution-Discovery Experiment 31: 20 independent
216
+ trials at each of 4 snapshot counts (3,000-100,000), a log-log linear
217
+ regression gave `std(n) ~ 11.75 / n^0.546` -- the fitted exponent
218
+ (0.546) is close to the ~0.5 ("error shrinks like 1/sqrt(n)") standard
219
+ shadow/median-of-means theory predicts.
220
+
221
+ This is a quick sanity-check fallback, not a guarantee for an
222
+ arbitrary state -- call `fit_shadow_sample_complexity` on YOUR
223
+ specific state if you need a real, state-calibrated error bound.
224
+ """
225
+ return _FIT_C / float(n_snapshots) ** _FIT_P
226
+
227
+
228
+ def fit_shadow_sample_complexity(rho: jnp.ndarray, exact_value: float, n_snapshots_list, n_trials: int, seed_base: int = 0):
229
+ """Empirically measures `magic_entropy_from_shadows`'s standard
230
+ deviation across `n_trials` independent shadow samplings at each
231
+ snapshot count in `n_snapshots_list`, for the SPECIFIC state `rho`
232
+ (rather than trusting `approx_shadow_std`'s built-in T-state-derived
233
+ fallback), then fits `std(n) ~ C / n^p` via log-log linear regression
234
+ -- the same method used to derive `approx_shadow_std`'s constants in
235
+ the first place (Dense-Evolution-Discovery Experiment 31).
236
+
237
+ `exact_value` should be `magic_entropy(rho)` -- used only to also
238
+ report each snapshot count's mean estimation bias alongside the
239
+ fitted curve, not part of the fit itself.
240
+
241
+ Returns `(rows, fit_c, fit_p)`: `rows` is a list of per-snapshot-count
242
+ dicts (`n_snapshots`, `mean_estimate`, `std_estimate`,
243
+ `mean_abs_error`); `fit_c`/`fit_p` are the fitted constants for
244
+ `C / n^p`, usable the same way as `approx_shadow_std` (or pass them to
245
+ `approx_shadow_std`'s formula directly: `fit_c / n ** fit_p`).
246
+ """
247
+ rows = []
248
+ for n_snap in n_snapshots_list:
249
+ estimates = []
250
+ for trial in range(n_trials):
251
+ snaps = sample_classical_shadow(rho, n_snap, seed=seed_base + trial)
252
+ estimates.append(magic_entropy_from_shadows(snaps))
253
+ estimates = np.array(estimates)
254
+ rows.append({
255
+ "n_snapshots": n_snap, "n_trials": n_trials,
256
+ "mean_estimate": float(estimates.mean()), "std_estimate": float(estimates.std()),
257
+ "mean_abs_error": float(abs(estimates.mean() - exact_value)),
258
+ })
259
+ log_n = np.log([r["n_snapshots"] for r in rows])
260
+ log_std = np.log([r["std_estimate"] for r in rows])
261
+ slope, intercept = np.polyfit(log_n, log_std, 1)
262
+ return rows, float(np.exp(intercept)), float(-slope)
@@ -0,0 +1,168 @@
1
+ """Sandwiched Quantum Renyi Divergence for full density-matrix diagnostics
2
+ (Muller-Lennert, Reeb, Wolf, Wilde, "On quantum Renyi entropies: a new
3
+ generalization and some applications", arXiv:1306.3142, Definition 1).
4
+
5
+ D_alpha(rho||sigma) = 1/(alpha-1) * log2 Tr[(sigma^e rho sigma^e)^alpha],
6
+ e = (1-alpha)/(2*alpha), with the alpha->1 limit reducing to the standard
7
+ quantum relative entropy and alpha=1/2 reducing to a fidelity-based form.
8
+
9
+ Originated from a Colab proposal with a real bug in its case_general
10
+ branch: `tr_inner = jnp.maximum(tr_inner, 1.0)` floors the inner trace at
11
+ 1.0 even when the true value is < 1 (the normal case for non-commuting
12
+ rho, sigma), silently forcing every result to log2(1)=0 -- confirmed
13
+ directly in the Colab's own printed output (alpha=1.5 gave exactly
14
+ 0.000000 across an entire rotation sweep). A second, deeper bug survived
15
+ the floor-value fix alone: for alpha > 1, a trace below 1 is not a
16
+ numerical artifact to clamp away, it is the genuine signature of a
17
+ support mismatch (supp(rho) not contained in supp(sigma)), which the
18
+ divergence must report as +inf, not a finite (and wrong-signed) number --
19
+ verified by hand on two different pure states: Tr[Q^1.5] = 0.6759,
20
+ matching the closed-form prediction (|<sigma|rho>|^2)^alpha exactly, and
21
+ plugging that into the naive formula gives a finite NEGATIVE divergence,
22
+ worse than the original bug's silent zero since it looks plausible
23
+ instead of visibly wrong.
24
+
25
+ Fixed and validated in Dense-Evolution-Discovery, Experiment 29
26
+ (https://tatopenn-cell.github.io/Dense-Evolution-Discovery/sandwiched_renyi_density_matrix/):
27
+ against the alpha->1 relative-entropy limit (matches an independent numpy
28
+ reference to 4 decimal places), the commuting/diagonal case (reduces
29
+ exactly to the classical Renyi divergence), and the support-violation
30
+ +inf case (verified at alpha>1, confirmed the branch does not fire
31
+ spuriously at alpha<1).
32
+
33
+ Its originally proposed use case -- replacing the JSD-based truncation
34
+ criterion in dense_evolution.mps's bond-dimension search -- was
35
+ independently disproven: on the diagonal singular-value spectrum used
36
+ there, rho and sigma commute, so a non-commuting-aware divergence induces
37
+ the exact same truncation ordering as JSD (5 benchmark configurations,
38
+ byte-identical chi_used and truncation error every time) -- nothing for
39
+ it to add in that setting. Promoted here instead for the genuinely
40
+ non-commuting full-density-matrix diagnostic use case it WAS validated
41
+ against: alongside uhlmann_fidelity, tracking a Bell state degraded by
42
+ amplitude damping, where the two metrics' noise-sensitivity curves
43
+ visibly diverge from each other.
44
+ """
45
+ import jax
46
+ import jax.numpy as jnp
47
+
48
+ __all__ = ["sandwiched_renyi_divergence", "sandwiched_renyi_divergence_jit"]
49
+
50
+ _EPS = 1e-12
51
+
52
+
53
+ def _case_half(rho, sigma):
54
+ ev_r, ec_r = jnp.linalg.eigh(rho)
55
+ safe_ev_r = jnp.where(ev_r > _EPS, ev_r, 0.0)
56
+ sqrt_rho = (ec_r * jnp.sqrt(safe_ev_r)) @ jnp.conj(ec_r).T
57
+ uhlmann_mat = sqrt_rho @ sigma @ sqrt_rho
58
+ ev_u = jnp.linalg.eigvalsh(uhlmann_mat)
59
+ safe_ev_u = jnp.where(ev_u > _EPS, ev_u, 0.0)
60
+ fidelity = jnp.clip(jnp.sum(jnp.sqrt(safe_ev_u)), 0.0, 1.0)
61
+ return -2.0 * jnp.log2(jnp.maximum(fidelity, _EPS))
62
+
63
+
64
+ def _case_one(rho, sigma):
65
+ ev_r, ec_r = jnp.linalg.eigh(rho)
66
+ safe_ev_r = jnp.where(ev_r > _EPS, ev_r, 1.0)
67
+ log_rho = (ec_r * jnp.where(ev_r > _EPS, jnp.log2(safe_ev_r), 0.0)) @ jnp.conj(ec_r).T
68
+ ev_s, ec_s = jnp.linalg.eigh(sigma)
69
+ safe_ev_s = jnp.where(ev_s > _EPS, ev_s, 1.0)
70
+ log_sigma = (ec_s * jnp.where(ev_s > _EPS, jnp.log2(safe_ev_s), 0.0)) @ jnp.conj(ec_s).T
71
+ return jnp.trace(rho @ (log_rho - log_sigma)).real
72
+
73
+
74
+ def _case_general(rho, sigma, alpha):
75
+ exponent = (1.0 - alpha) / (2.0 * alpha)
76
+ ev_s, ec_s = jnp.linalg.eigh(sigma)
77
+ mask_s = ev_s > _EPS
78
+ safe_ev_s = jnp.where(mask_s, ev_s, 1.0)
79
+ pow_ev_s = jnp.where(mask_s, safe_ev_s ** exponent, 0.0)
80
+ sigma_pow = (ec_s * pow_ev_s) @ jnp.conj(ec_s).T
81
+
82
+ int_m = sigma_pow @ rho @ sigma_pow
83
+ v_int = jnp.linalg.eigvalsh(int_m)
84
+ mask_int = v_int > _EPS
85
+ safe_v_int = jnp.where(mask_int, v_int, 1.0)
86
+ pow_v_int = jnp.where(mask_int, safe_v_int ** alpha, 0.0)
87
+
88
+ tr_inner = jnp.sum(pow_v_int)
89
+
90
+ # For alpha > 1, D_alpha is finite only when supp(rho) subset supp(sigma);
91
+ # tr_inner < 1 there is the genuine signature of a support mismatch, not
92
+ # a numerical artifact -- see the module docstring's bug-history note.
93
+ is_support_violation = (alpha > 1.0) & (tr_inner < 1.0 - 1e-9)
94
+ tr_inner_safe = jnp.maximum(tr_inner, _EPS)
95
+ finite_result = ((1.0 / (alpha - 1.0)) * jnp.log2(tr_inner_safe)).real
96
+ return jnp.where(is_support_violation, jnp.inf, finite_result)
97
+
98
+
99
+ def _sandwiched_renyi_divergence_core(rho: jnp.ndarray, sigma: jnp.ndarray, alpha: float = 1.5) -> jnp.ndarray:
100
+ # alpha must be a jnp value, not a raw Python float, before it reaches
101
+ # _case_general: jax.lax.cond traces BOTH branches regardless of which
102
+ # one runs, and `1.0 / (alpha - 1.0)` at alpha=1.0 raises a Python
103
+ # ZeroDivisionError if alpha is still a plain float at that point (the
104
+ # case_one branch's own guard never gets a chance to skip it) -- under
105
+ # jax.jit this conversion happens automatically (all arguments get
106
+ # traced), which is why the original un-split Discovery version never
107
+ # hit this; the eager (non-jit) core path here needs it explicitly.
108
+ alpha = jnp.asarray(alpha, dtype=jnp.float64)
109
+ is_half = jnp.isclose(alpha, 0.5)
110
+ is_one = jnp.isclose(alpha, 1.0)
111
+ return jax.lax.cond(
112
+ is_half, lambda: _case_half(rho, sigma),
113
+ lambda: jax.lax.cond(is_one, lambda: _case_one(rho, sigma), lambda: _case_general(rho, sigma, alpha)),
114
+ )
115
+
116
+
117
+ def sandwiched_renyi_divergence(rho: jnp.ndarray, sigma: jnp.ndarray, alpha: float = 1.5) -> float:
118
+ """Sandwiched quantum Renyi divergence D_alpha(rho||sigma), in bits
119
+ (log2). `rho`, `sigma` are density matrices of the same dimension;
120
+ `alpha` selects the order (0.5 -> fidelity-based, 1.0 -> standard
121
+ relative entropy, both handled as exact closed-form limits rather
122
+ than through the general formula's own alpha->0.5/1 numerical
123
+ instability).
124
+
125
+ Zero when rho == sigma at every alpha (verified). For alpha > 1,
126
+ returns `+inf` when supp(rho) is not contained in supp(sigma) --
127
+ e.g. two different pure (rank-1) states -- rather than a finite
128
+ number; see the module docstring for why this is the mathematically
129
+ correct behavior, not an edge-case failure.
130
+
131
+ KNOWN LIMITATION at exactly alpha=1.0: the same support-violation
132
+ check is NOT applied to the alpha=1 (relative-entropy) branch, which
133
+ instead clips log(0)-type contributions to 0 rather than diverging --
134
+ e.g. D_1(rho||sigma) for two different pure states returns 0.0, not
135
+ +inf, even though the true relative entropy diverges there too.
136
+ Experiment 29 validated the alpha=1 branch only against full-rank
137
+ (depolarized) inputs specifically to sidestep this exactly-singular
138
+ case (`scipy.linalg.logm` itself raises `LogmExactlySingularWarning`
139
+ on singular inputs -- an inherent ill-conditioning of relative
140
+ entropy near degenerate support, not unique to this implementation).
141
+ Do not rely on alpha=1 to correctly flag a support mismatch; use
142
+ alpha slightly above 1 (e.g. 1.001) if that matters for your use case.
143
+
144
+ Validation-only, like `uhlmann_fidelity`: meant to grade a correction
145
+ against a known reference state, not to feed into one (see
146
+ `uhlmann_fidelity`'s docstring for the full "ideal state as oracle"
147
+ argument, which applies here identically).
148
+
149
+ Examples
150
+ --------
151
+ >>> import numpy as np
152
+ >>> from dense_evolution.mitigation.renyi import sandwiched_renyi_divergence
153
+ >>> rho = np.array([[1, 0], [0, 0]], dtype=complex)
154
+ >>> round(float(sandwiched_renyi_divergence(rho, rho, alpha=1.5)), 4)
155
+ 0.0
156
+ >>> sigma = np.array([[0.5, 0], [0, 0.5]], dtype=complex)
157
+ >>> round(float(sandwiched_renyi_divergence(rho, sigma, alpha=1.5)), 4)
158
+ 1.0
159
+ """
160
+ rho = jnp.asarray(rho, dtype=jnp.complex128)
161
+ sigma = jnp.asarray(sigma, dtype=jnp.complex128)
162
+ return float(_sandwiched_renyi_divergence_core(rho, sigma, alpha))
163
+
164
+
165
+ sandwiched_renyi_divergence_jit = jax.jit(_sandwiched_renyi_divergence_core)
166
+ """`jax.jit`-compiled entry point for `sandwiched_renyi_divergence`. `rho`/
167
+ `sigma` must already be `complex128`. Returns a jnp scalar, not a Python
168
+ `float`."""
@@ -0,0 +1,103 @@
1
+ """Stabilizer Renyi Entropy (SRE): a per-STATE nonstabilizerness ("magic")
2
+ monotone (Leone, Oliviero, Hamma, "Stabilizer Renyi Entropy",
3
+ arXiv:2106.12587, Phys. Rev. Lett. 128, 050402 (2022), Eq. 5-8 there,
4
+ labeled Eq. 14/18 in the paper that motivated promoting this).
5
+
6
+ NOT the same quantity as `dense_evolution.mitigation.magic_entropy`
7
+ (Bu-Gu-Jaffe's 3-fold self-convolution "Key Unitary" construction,
8
+ single-qubit only) or `sandwiched_renyi_divergence` (Muller-Lennert et
9
+ al., a DIVERGENCE between TWO density matrices -- "how different are rho
10
+ and sigma", answering a different question entirely; the shared "Renyi"
11
+ in both names is a coincidence of both being alpha-generalizations of
12
+ entropy applied to different objects, not overlapping math). This SRE is
13
+ a genuinely different, MULTI-qubit, SINGLE-state magic monotone: zero for
14
+ every stabilizer state, positive otherwise.
15
+
16
+ M_2(psi) = -log2[ (1/d) * sum_a sum_b |WHT[c_a](b)|^4 ], where
17
+ c_a(x) = conj(psi(x)) * psi(x XOR a) and WHT is the length-d Walsh-Hadamard
18
+ transform (signmat[b,x] = (-1)^popcount(b AND x)), d = 2**n_qubits.
19
+
20
+ Verified against known values: every computational-basis/stabilizer state
21
+ gives exactly 0; a single T state ((cos(pi/8), sin(pi/8)) in the
22
+ computational basis) gives -log2(0.75) = 0.415037 bits, matching the
23
+ closed-form derivation of Eq. 5 by hand (not a value copied from
24
+ elsewhere).
25
+
26
+ Promoted from Dense-Evolution-Discovery's wormhole_magic_entropy.py
27
+ (2026-08-29), where it was implemented fresh because the existing
28
+ magic_entropy is single-qubit only -- this quantity has no dependency on
29
+ that use case (wormhole teleportation), so it belongs here as a general
30
+ multi-qubit magic diagnostic.
31
+
32
+ VECTORIZATION NOTE: the Discovery script's original version had an
33
+ explicit Python `for a in range(d)` loop, each iteration doing its own
34
+ (d,d)@(d,) matrix-vector product -- d sequential small matmuls. This
35
+ promoted version instead builds the (d,d) matrix of every c_a(x) pair at
36
+ once (broadcasting over x and a together) and applies the Walsh-Hadamard
37
+ transform as ONE (d,d)@(d,d) matmul -- same O(d^3) total FLOP count, but
38
+ expressed as a single large matmul JAX/XLA can execute efficiently
39
+ (GPU/TPU-friendly, no per-iteration Python dispatch overhead), matching
40
+ this package's JAX-by-default convention. Still O(d^3), not the paper's
41
+ own asymptotically-better O(4^n * n) Walsh-Hadamard butterfly algorithm
42
+ (n = log2(d) qubits) -- unimplemented here, same as the Discovery
43
+ original; fine for d up to a few thousand (n up to ~11-12 qubits), the
44
+ sizes this package's exact-statevector backends already target.
45
+ """
46
+ import jax
47
+ import jax.numpy as jnp
48
+
49
+ __all__ = ["stabilizer_renyi_entropy", "stabilizer_renyi_entropy_jit"]
50
+
51
+
52
+ def _stabilizer_renyi_entropy_core(psi):
53
+ d = psi.shape[0]
54
+ idx = jnp.arange(d)
55
+ # Static (trace-time) construction, same style as magic_entropy.py's
56
+ # _cnot_matrix -- one Python loop per distinct d, cached by jax.jit.
57
+ popcount = jnp.array([bin(i).count("1") for i in range(d)])
58
+
59
+ bitwise_and = idx[:, None] & idx[None, :] # [b, x] = b & x
60
+ signmat = (-1.0) ** popcount[bitwise_and] # Walsh-Hadamard sign matrix
61
+
62
+ xor_table = idx[:, None] ^ idx[None, :] # [x, a] = x ^ a
63
+ c = jnp.conj(psi)[:, None] * psi[xor_table] # [x, a] = c_a(x)
64
+
65
+ wht = signmat @ c # [b, a] = WHT[c_a](b)
66
+ total = jnp.sum(jnp.abs(wht) ** 4)
67
+ return -jnp.log2(total / d)
68
+
69
+
70
+ def stabilizer_renyi_entropy(psi):
71
+ """Stabilizer Renyi Entropy of a pure state `psi` (length 2**n_qubits),
72
+ in bits (log2) -- the paper's own convention.
73
+
74
+ Zero for every stabilizer state, positive for non-stabilizer ("magic")
75
+ states -- e.g. a single T state gives 0.415037 bits.
76
+
77
+ Parameters
78
+ ----------
79
+ psi : array-like, shape (2**n_qubits,)
80
+ A normalized pure statevector.
81
+
82
+ Returns
83
+ -------
84
+ float
85
+
86
+ Examples
87
+ --------
88
+ >>> import numpy as np
89
+ >>> psi0 = np.zeros(8, dtype=complex); psi0[0] = 1.0
90
+ >>> round(stabilizer_renyi_entropy(psi0), 6) # computational basis state: stabilizer, expect 0
91
+ 0.0
92
+ """
93
+ psi = jnp.asarray(psi, dtype=jnp.complex128)
94
+ d = psi.shape[0]
95
+ if d < 1 or (d & (d - 1)) != 0:
96
+ raise ValueError(f"psi length {d} is not a power of 2 (must be 2**n_qubits)")
97
+ return float(_stabilizer_renyi_entropy_core(psi))
98
+
99
+
100
+ stabilizer_renyi_entropy_jit = jax.jit(_stabilizer_renyi_entropy_core)
101
+ """`jax.jit`-compiled entry point for `stabilizer_renyi_entropy`. `psi`
102
+ must already be `complex128`. Returns a jnp scalar, not a Python `float`
103
+ -- call `float(...)` yourself if you need one outside a jitted context."""