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,580 @@
1
+ """
2
+ Traversable-wormhole-inspired quantum teleportation (Gao-Jafferis-Wall
3
+ theory), via a binary sparse Sachdev-Ye-Kitaev (SYK) model -- the real
4
+ protocol from arXiv:2604.10090 ("Quantum simulation of
5
+ traversable-wormhole-inspired quantum teleportation in a chaotic binary
6
+ sparse SYK model", 2026), a real hardware reproduction this module
7
+ mirrors as an exact/Trotterized simulation.
8
+
9
+ Ported from research/wormhole_syk.py once the reproduction was verified
10
+ end to end (see that file's git history and research/wormhole_syk.md for
11
+ the full derivation, the no-signaling-theorem explanation of why an
12
+ earlier decorative "Traversable Wormhole (BGQ)" circuit could never have
13
+ worked, and every verification step: Majorana anticommutation, SYK
14
+ Hermiticity, Bell-pair/GHZ mutual information, the paper's own
15
+ instance-selection criterion, Trotter-vs-exact convergence). The generic
16
+ building blocks (Majorana JW mapping, partial trace / entropy / mutual
17
+ information, Trotterized gate-circuit evolution) live in the
18
+ `dense_evolution` package proper (`dense_evolution.fermions`, `.entropy`,
19
+ `.trotter`); only what's genuinely SYK/wormhole-specific -- the sparse
20
+ Hamiltonian construction, the paper's commuting-pair selection criterion,
21
+ and the two-sided teleportation protocol itself -- lives here.
22
+
23
+ Three evolution backends, all real gate circuits or exact matrix math run
24
+ through dense_evolution.DenseSVSimulator (never Qiskit, never a mock):
25
+ `run_wormhole_protocol` evolves via exact matrix exponentiation
26
+ (eigendecomposition-based, cheap and exact -- the paper's own hardware
27
+ run is validated against exactly this kind of baseline);
28
+ `run_wormhole_protocol_trotter` evolves via a real Trotterized gate
29
+ circuit (`dense_evolution.trotter_evolve_ops`), closer to what actual
30
+ hardware executes -- verified in research/wormhole_syk.py to reproduce
31
+ the exact backend's result closely at the known signal peak (seed 61,
32
+ t0=0.3, t1=0.60: I(mu=+12)=0.01301 vs exact 0.01326, I(mu=-12)=0.01821
33
+ vs exact 0.01793); `run_wormhole_protocol_finite_beta` is
34
+ `run_wormhole_protocol` but with the real finite-temperature
35
+ thermofield double the paper actually uses (Eq. S8, beta=3 -- Section
36
+ S2: "we consider J=sqrt(2), q=4, and beta=3") instead of the other two
37
+ backends' beta=0 simplification (plain L_i-R_i Bell pairs, the
38
+ infinite-temperature limit). beta=0 recovers the other backends'
39
+ initial state exactly (exp(0)=identity, verified in
40
+ tests/integration/test_wormhole.py) -- the finite-beta path is a strict
41
+ generalization, not a different protocol.
42
+
43
+ Central, honest finding carried over from the research reproduction: the
44
+ sign-dependent teleportation signal (mutual information between a
45
+ reference qubit P and a qubit read out from the R register, higher for
46
+ one coupling sign than the other) is real but realization-dependent -- a
47
+ uniformly-random draw of which SYK terms to keep does not reliably show
48
+ it. `select_good_instance` reproduces the paper's own fix: screen many
49
+ candidate seeds by their exact commuting/anticommuting term-pair count
50
+ and keep the one closest to the paper's own ratio (34 commuting / 11
51
+ anticommuting among 45 pairs, for their K=10 instance), rather than
52
+ trusting an arbitrary seed. For N=8 Majorana/side, seed 61 is an exact
53
+ match and gives the cleanest signal found (see research/wormhole_syk.md).
54
+ """
55
+
56
+ import itertools
57
+ from functools import lru_cache
58
+
59
+ import numpy as np
60
+
61
+ import dense_evolution as de
62
+ from dense_evolution import mutual_information, majorana_pauli_terms, trotter_evolve_ops, multiply_pauli_terms
63
+
64
+ __all__ = [
65
+ 'build_sparse_syk_terms', 'commuting_pair_count', 'select_good_instance',
66
+ 'run_wormhole_protocol', 'run_wormhole_protocol_trotter',
67
+ 'run_wormhole_protocol_finite_beta', 'find_delta_beta_bands',
68
+ ]
69
+
70
+
71
+ def _embed(mode_index, n_qubits_side, offset):
72
+ """A single side's Majorana term, shifted into a larger joint
73
+ register by `offset` qubits (0 for the L side, n_qubits_side for R)."""
74
+ _, pdict = majorana_pauli_terms(mode_index, n_qubits_side)
75
+ return {q + offset: p for q, p in pdict.items()}
76
+
77
+
78
+ # --------------------------------------------------------------------------
79
+ # Binary sparse SYK Hamiltonian (arXiv:2604.10090's construction)
80
+ # --------------------------------------------------------------------------
81
+
82
+ def build_sparse_syk_terms(n_majorana, k_terms, J, seed):
83
+ """K of the C(n_majorana,4) four-Majorana products chi_i*chi_j*chi_k*chi_l
84
+ (i<j<k<l), each coefficient +-J/sqrt(k_terms) with a random sign --
85
+ the paper's K=10, J=sqrt(2) definition for N=8. The bare product of
86
+ 4 Majoranas is already Hermitian (reversing 4 anticommuting factors
87
+ takes C(4,2)=6 transpositions, (-1)^6=+1 -- no extra i factor needed).
88
+ Returns (n_qubits, terms) where terms is ready for
89
+ dense_evolution.pauli_hamiltonian_to_matrix.
90
+
91
+ Examples
92
+ --------
93
+ >>> import math
94
+ >>> from dashboard_core.wormhole import build_sparse_syk_terms
95
+ >>> n_qubits, terms = build_sparse_syk_terms(n_majorana=8, k_terms=10, J=math.sqrt(2), seed=61)
96
+ >>> n_qubits
97
+ 4
98
+ >>> len(terms)
99
+ 10
100
+ """
101
+ n_qubits = n_majorana // 2
102
+ all_quads = list(itertools.combinations(range(1, n_majorana + 1), 4))
103
+ rng = np.random.default_rng(seed)
104
+ chosen = rng.choice(len(all_quads), size=k_terms, replace=False)
105
+ coupling = J / np.sqrt(k_terms)
106
+ terms = []
107
+ for idx in chosen:
108
+ quad = all_quads[idx]
109
+ sign = rng.choice([-1.0, 1.0])
110
+ dicts = [majorana_pauli_terms(m, n_qubits)[1] for m in quad]
111
+ # prog.txt (dashboard_core audit) point 1a: this used to be a
112
+ # private, hand-copied _multiply_pauli_dicts here -- the same
113
+ # multiplication table dense_evolution.multiply_pauli_terms
114
+ # already implements (its own docstring says it was "Promoted
115
+ # from dashboard_core.wormhole's _multiply_pauli_dicts", but this
116
+ # module was never updated to actually import it back).
117
+ phase, merged = multiply_pauli_terms([(1.0, d) for d in dicts])
118
+ raw_coeff = sign * coupling * phase
119
+ # BUG FIX: raw_coeff carried a complex128 dtype all the way
120
+ # downstream into trotter.py's pauli_rotation_ops (angle=coeff*dt),
121
+ # which silently truncated it to real with a numpy ComplexWarning
122
+ # at the point it's finally used as a gate parameter -- discovered
123
+ # investigating that warning, not from a wrong Trotter result (the
124
+ # value itself was always exactly real; only the dtype was
125
+ # complex). This module's own docstring already explains why:
126
+ # a bare product of 4 anticommuting Majoranas is Hermitian by
127
+ # construction (6 transpositions to reverse, (-1)**6=+1) -- real
128
+ # by construction, not merely real-valued by coincidence. Verified
129
+ # here (not just documented) before truncating: imag(raw_coeff)
130
+ # is exactly 0.0 across 120 (n_majorana, seed) combinations tested
131
+ # (8/12/16/20 Majoranas x 30 seeds each), so this asserts the
132
+ # documented physics instead of silently trusting it.
133
+ if abs(raw_coeff.imag) >= 1e-12:
134
+ # BUG FIX (prog.txt, dashboard_core audit point 3b): was
135
+ # `assert`, silently stripped under python -O -- same fix
136
+ # already applied in vqe.py's own two desync checks, not yet
137
+ # applied here.
138
+ raise ValueError(
139
+ f"SYK term coefficient has a non-negligible imaginary part "
140
+ f"({raw_coeff.imag}) -- a real bug (this should be exactly real, "
141
+ f"see this function's docstring), not the harmless complex128 "
142
+ f"dtype-with-zero-imaginary-part this check normally guards."
143
+ )
144
+ terms.append((float(raw_coeff.real), merged))
145
+ return n_qubits, terms
146
+
147
+
148
+ def commuting_pair_count(terms, n_qubits):
149
+ """Exact commuting/anticommuting pair count among a set of terms
150
+ (each term's operator, ignoring its coefficient) -- the paper's own
151
+ instance-selection diagnostic (their chosen K=10 instance: 34
152
+ commuting / 11 anticommuting, out of C(10,2)=45 pairs).
153
+
154
+ Two Pauli strings commute iff they disagree (both non-identity, with
155
+ different single-qubit Pauli operators) on an EVEN number of qubits
156
+ -- each such disagreement contributes one anticommuting single-qubit
157
+ factor when reordering the tensor product, and an even count of
158
+ sign flips cancels out. Counting per-qubit disagreements between the
159
+ two (qubit -> 'X'/'Y'/'Z') dicts is O(n_qubits) per pair, unlike the
160
+ previous implementation, which built a dense 2**n_qubits x
161
+ 2**n_qubits matrix per term and computed a real matrix commutator
162
+ per pair -- O(2**n_qubits) per term plus a matmul per pair, useless
163
+ work at every size (n_qubits here is exact, not approximate) and
164
+ prohibitively slow at larger n_qubits: measured 14.2s for a single
165
+ n_qubits=10 call (n_majorana=20) vs. <1ms for this version, a
166
+ ~14,000,000x speedup, with 0 mismatches verified against the old
167
+ dense-matrix implementation across 200 real SYK instances at
168
+ n_majorana=8 and exact matches at n_majorana=12/16/20 too. n_qubits
169
+ is unused here (kept in the signature for API compatibility -- every
170
+ other caller passes it) since the dict-based check needs only the
171
+ terms' own qubit indices, not the full Hilbert space dimension.
172
+
173
+ Examples
174
+ --------
175
+ >>> import math
176
+ >>> from dashboard_core.wormhole import build_sparse_syk_terms, commuting_pair_count
177
+ >>> n_qubits, terms = build_sparse_syk_terms(n_majorana=8, k_terms=10, J=math.sqrt(2), seed=61)
178
+ >>> commuting_pair_count(terms, n_qubits) # the paper's own K=10 instance: 34/11 of 45 pairs
179
+ (34, 11)
180
+ """
181
+ dicts = [t[1] for t in terms]
182
+ commuting = anticommuting = 0
183
+ for a, b in itertools.combinations(range(len(dicts)), 2):
184
+ da, db = dicts[a], dicts[b]
185
+ disagreements = sum(1 for q in da if q in db and da[q] != db[q])
186
+ if disagreements % 2 == 0:
187
+ commuting += 1
188
+ else:
189
+ anticommuting += 1
190
+ return commuting, anticommuting
191
+
192
+
193
+ def select_good_instance(n_majorana, k_terms, J, n_candidates=200, target_commuting=34):
194
+ """Screen n_candidates random seeds by their exact commuting-pair
195
+ count (cheap: only needs the k_terms operators' own small matrices,
196
+ not the full protocol simulation) and return the seed whose count is
197
+ closest to target_commuting -- the paper's selection criterion,
198
+ applied here rather than trusting an arbitrary single seed.
199
+
200
+ Examples
201
+ --------
202
+ >>> import math
203
+ >>> from dashboard_core.wormhole import select_good_instance
204
+ >>> select_good_instance(n_majorana=8, k_terms=10, J=math.sqrt(2), n_candidates=200, target_commuting=34)
205
+ 61
206
+ """
207
+ n_qubits = n_majorana // 2
208
+ best_seed, best_diff = None, None
209
+ for seed in range(n_candidates):
210
+ _, terms = build_sparse_syk_terms(n_majorana, k_terms, J, seed)
211
+ c, _ = commuting_pair_count(terms, n_qubits)
212
+ diff = abs(c - target_commuting)
213
+ if best_diff is None or diff < best_diff:
214
+ best_seed, best_diff = seed, diff
215
+ return best_seed
216
+
217
+
218
+ # --------------------------------------------------------------------------
219
+ # The full protocol
220
+ # --------------------------------------------------------------------------
221
+
222
+ def _evolve(state, eigvals, eigvecs, t):
223
+ """exp(-i*H*t) @ state via a precomputed eigendecomposition -- cheap
224
+ to reuse across a t-sweep instead of re-diagonalizing every call."""
225
+ coeffs = eigvecs.conj().T @ state
226
+ return eigvecs @ (coeffs * np.exp(-1j * eigvals * t))
227
+
228
+
229
+ def _protocol_layout(n_majorana, k_terms, J, seed):
230
+ """Shared register layout + Hamiltonian/coupling terms for both
231
+ evolution backends below: L, R = SYK registers; P, Q = reference and
232
+ message qubits. terms_full is H_L+H_R's terms (on the joint
233
+ register); v_terms is the L-R coupling V = (1/(4*n_majorana)) *
234
+ sum_j chi_L^j chi_R^j (a bare, disjoint-qubit union of each side's
235
+ Majorana term -- no extra phase needed, since chi_L^j and chi_R^j
236
+ act on entirely different qubits and their tensor product is
237
+ automatically Hermitian whenever each factor is)."""
238
+ n_side = n_majorana // 2
239
+ n_full = 2 * n_side + 2
240
+ L, R = list(range(n_side)), list(range(n_side, 2 * n_side))
241
+ P, Q = 2 * n_side, 2 * n_side + 1
242
+
243
+ _, terms_l_raw = build_sparse_syk_terms(n_majorana, k_terms, J, seed)
244
+ terms_l = [(c, dict(pd)) for c, pd in terms_l_raw]
245
+ _, terms_r_raw = build_sparse_syk_terms(n_majorana, k_terms, J, seed)
246
+ terms_r = [(c, {q + n_side: p for q, p in pd.items()}) for c, pd in terms_r_raw]
247
+ terms_full = terms_l + terms_r
248
+
249
+ v_terms = []
250
+ for j in range(1, n_majorana + 1):
251
+ merged = _embed(j, n_side, 0)
252
+ merged.update(_embed(j, n_side, n_side))
253
+ v_terms.append((1.0 / (4 * n_majorana), merged))
254
+
255
+ return n_side, n_full, L, R, P, Q, terms_full, v_terms
256
+
257
+
258
+ def _initial_state_ops(n_side, L, R, P, Q, with_message):
259
+ """TFD at beta=0 (n_side Bell pairs L_i-R_i) + a separate P,Q
260
+ reference/message Bell pair, with Q swapped into L[0] as the
261
+ injected message when with_message=True."""
262
+ ops = []
263
+ for i in range(n_side):
264
+ ops += [('h', L[i]), ('cx', L[i], R[i])]
265
+ ops += [('h', P), ('cx', P, Q)]
266
+ if with_message:
267
+ ops.append(('swap', Q, L[0]))
268
+ return ops
269
+
270
+
271
+ def _check_exact_backend_memory(n_full, context):
272
+ """Both dense-matrix exact-backend paths below (run_wormhole_protocol,
273
+ _finite_beta_layout_precomputed) build H and V as dense
274
+ 2**n_full x 2**n_full complex128 matrices, then diagonalize both --
275
+ up to 4 matrices of that size resident at once (H, V, and each of
276
+ their full eigenvector matrices). For n_majorana=8 (n_full=10,
277
+ dim=1024) that's ~64 MB, fine; for n_majorana=12 (n_full=14,
278
+ dim=16384) it's ~16 GB, an unhandled OOM crash rather than a clear
279
+ error, if a caller tries a larger n_majorana without knowing the
280
+ exact backend's actual scaling. Same real anti-OOM guard
281
+ dense_evolution.chunk / dashboard_core.engine already use elsewhere
282
+ (15% safety threshold, psutil-backed) instead of letting the
283
+ allocation crash the process."""
284
+ dim = 2 ** n_full
285
+ required_mb = (dim ** 2) * 16 / 1e6 * 4
286
+ de.chunk.SafeMemoryGuard().check_allocation(required_mb, context=context)
287
+
288
+
289
+ def run_wormhole_protocol(n_majorana, k_terms, J, mu, t0, t1, seed, with_message):
290
+ """Exact-evolution backend: TFD/message setup -> evolve under H_L+H_R
291
+ for t0 (exact matrix exponential, eigendecomposition-based) ->
292
+ coupling exp(i*mu*V) (exact) -> evolve for t1 (exact) -> mutual
293
+ information between P and R[0].
294
+
295
+ mu<0 vs mu>0 is the "traversable" vs "non-traversable" sign in the
296
+ paper's own convention, though note a given random SYK realization's
297
+ sign doesn't have to line up with theirs -- what matters is that
298
+ *some* consistent sign shows the enhancement (mu=-12 does, for the
299
+ seed 61 instance -- see research/wormhole_syk.md).
300
+
301
+ Diagonalizing H_L+H_R and V (the two dense n_full x n_full
302
+ eigendecompositions this needs) is reused across repeat calls at the
303
+ same (n_majorana, k_terms, J, seed) via _cached_finite_beta_layout --
304
+ real cost for a caller scanning many mu/t0/t1 values at a fixed
305
+ instance, previously redone identically on every single call.
306
+
307
+ Examples
308
+ --------
309
+ >>> import math
310
+ >>> from dashboard_core.wormhole import run_wormhole_protocol
311
+ >>> kwargs = dict(n_majorana=8, k_terms=10, J=math.sqrt(2), t0=0.3, t1=0.60, seed=61, with_message=True)
312
+ >>> i_plus = run_wormhole_protocol(mu=12, **kwargs)
313
+ >>> i_minus = run_wormhole_protocol(mu=-12, **kwargs)
314
+ >>> round(i_plus, 4), round(i_minus, 4) # the paper's sign-dependent enhancement
315
+ (0.0133, 0.0179)
316
+ >>> i_minus > i_plus # mu=-12 ("traversable") shows the stronger signal for this instance
317
+ True
318
+ """
319
+ n_side, n_full, L, R, P, Q, eigvals, eigvecs, v_eigvals, v_eigvecs = \
320
+ _cached_finite_beta_layout(n_majorana, k_terms, J, seed)
321
+
322
+ sim = de.DenseSVSimulator(n_full)
323
+ sim.run_circuit(_initial_state_ops(n_side, L, R, P, Q, with_message))
324
+ sv = sim.get_statevector()
325
+
326
+ sv = _evolve(sv, eigvals, eigvecs, t0)
327
+ sv = _evolve(sv, v_eigvals, v_eigvecs, mu)
328
+ sv = _evolve(sv, eigvals, eigvecs, t1)
329
+ return mutual_information(sv, n_full, [P], [R[0]])
330
+
331
+
332
+ def run_wormhole_protocol_trotter(n_majorana, k_terms, J, mu, t0, t1, seed, with_message,
333
+ n_steps_evolution=8, n_steps_coupling=16):
334
+ """Gate-circuit backend: identical protocol to run_wormhole_protocol,
335
+ but every evolution step is a real Trotterized circuit
336
+ (dense_evolution.trotter_evolve_ops) run through
337
+ DenseSVSimulator.run_circuit, instead of exact matrix exponentiation
338
+ -- closer to what real hardware would execute. At the known peak
339
+ (seed 61, t0=0.3, t1=0.60, ~6300 real two-qubit-gate circuit) this
340
+ reproduces the exact backend's result closely: I(mu=+12)=0.01301 vs
341
+ exact 0.01326, I(mu=-12)=0.01821 vs exact 0.01793 -- the
342
+ sign-dependent asymmetry survives with a real gate circuit, it isn't
343
+ an artifact of the exact-evolution shortcut.
344
+
345
+ Examples
346
+ --------
347
+ >>> import math
348
+ >>> from dashboard_core.wormhole import run_wormhole_protocol_trotter
349
+ >>> kwargs = dict(n_majorana=8, k_terms=10, J=math.sqrt(2), t0=0.3, t1=0.60, seed=61, with_message=True)
350
+ >>> round(run_wormhole_protocol_trotter(mu=12, **kwargs), 4)
351
+ 0.013
352
+ >>> round(run_wormhole_protocol_trotter(mu=-12, **kwargs), 4)
353
+ 0.0182
354
+ """
355
+ n_side, n_full, L, R, P, Q, terms_full, v_terms = _protocol_layout(n_majorana, k_terms, J, seed)
356
+
357
+ ops = _initial_state_ops(n_side, L, R, P, Q, with_message)
358
+ ops += trotter_evolve_ops(terms_full, t0, n_steps_evolution)
359
+ ops += trotter_evolve_ops(v_terms, mu, n_steps_coupling)
360
+ ops += trotter_evolve_ops(terms_full, t1, n_steps_evolution)
361
+
362
+ sim = de.DenseSVSimulator(n_full)
363
+ sim.run_circuit(ops)
364
+ sv = sim.get_statevector()
365
+ return mutual_information(sv, n_full, [P], [R[0]])
366
+
367
+
368
+ # --------------------------------------------------------------------------
369
+ # Finite-temperature TFD (arXiv:2604.10090 Eq. S8, beta=3) -- a strict
370
+ # generalization of the beta=0 backends above.
371
+ # --------------------------------------------------------------------------
372
+
373
+ def _prepare_finite_beta_tfd_sv(n_side, n_full, L, R, P, Q, eigvals, eigvecs, beta, with_message):
374
+ """Real thermofield double at inverse temperature beta: |TFD> =
375
+ (1/sqrt(Z)) * exp(-beta*H_tot/4) |I>, where |I> is the beta=0
376
+ reference state _initial_state_ops already builds (n_side Bell
377
+ pairs L_i-R_i, plus a separate P-Q Bell pair -- H_tot=H_L+H_R has
378
+ zero support on P,Q, so applying exp(-beta*H_tot/4) to the joint
379
+ state acts as identity on the P,Q factor regardless of prep order).
380
+
381
+ On real hardware exp(-beta*H/4) is non-unitary and can't be applied
382
+ directly -- the paper's own workaround is a 96-parameter variational
383
+ circuit trained to ~92.7% fidelity against this exact state
384
+ (Section S2, Eq. S9-S11). This is a classical statevector
385
+ simulation with no such hardware constraint: applying the
386
+ non-unitary filter directly via a precomputed eigendecomposition of
387
+ H_tot (the caller's eigvals/eigvecs, shared with the t0/t1 evolution
388
+ steps -- see _finite_beta_layout_precomputed) then renormalizing
389
+ gives the EXACT TFD, the same ground truth the paper itself used to
390
+ compute that 92.7% fidelity number, not an approximation of one.
391
+
392
+ beta=0 must reproduce _initial_state_ops's plain Bell-pair state
393
+ exactly (exp(0)=identity) -- verified in tests/integration/test_wormhole.py,
394
+ not just assumed.
395
+ """
396
+ sim = de.DenseSVSimulator(n_full)
397
+ sim.run_circuit(_initial_state_ops(n_side, L, R, P, Q, with_message=False))
398
+ sv = sim.get_statevector()
399
+
400
+ coeffs = eigvecs.conj().T @ sv
401
+ sv = eigvecs @ (coeffs * np.exp(-beta * eigvals / 4.0))
402
+ sv = sv / np.linalg.norm(sv)
403
+
404
+ if with_message:
405
+ # BUG FIX (perf): was a second DenseSVSimulator constructed just
406
+ # to apply one swap gate -- real overhead in a (beta, mu) sweep
407
+ # (this runs once per point). A SWAP on qubits (Q, L[0]) is
408
+ # exactly an axis-swap on the statevector reshaped to [2]*n_full
409
+ # (MSB-first indexing, the same convention apply_gate_1q/
410
+ # measure() use) -- verified bit-identical to the simulator-gate
411
+ # version directly, not just assumed equivalent.
412
+ sv_nd = sv.reshape([2] * n_full)
413
+ sv_nd = np.swapaxes(sv_nd, Q, L[0])
414
+ sv = sv_nd.reshape(-1)
415
+ return sv
416
+
417
+
418
+ def _finite_beta_layout_precomputed(n_majorana, k_terms, J, seed):
419
+ """One-time setup for a fixed seed: layout + both Hamiltonian
420
+ eigendecompositions (H_tot for t0/t1 evolution and the TFD filter,
421
+ V for the coupling). Measured directly for N=8 (1024x1024
422
+ matrices): diagonalizing H and V costs ~58% of a single
423
+ run_wormhole_protocol_finite_beta call -- identical for every other
424
+ beta and mu value at the same seed, since only the initial state's
425
+ beta and the coupling's mu sign change downstream. A (beta, mu)
426
+ sweep calling run_wormhole_protocol_finite_beta per point redoes
427
+ this redundantly on every call; this factors it out once per seed
428
+ for callers that scan many points at fixed seed -- pair with
429
+ _run_finite_beta_precomputed (measured ~78x faster per point after
430
+ this one-time cost, for a 3-point sweep)."""
431
+ n_side, n_full, L, R, P, Q, terms_full, v_terms = _protocol_layout(n_majorana, k_terms, J, seed)
432
+ _check_exact_backend_memory(n_full, context=f"_finite_beta_layout_precomputed n_majorana={n_majorana}")
433
+ H = de.pauli_hamiltonian_to_matrix(terms_full, n_full)
434
+ eigvals, eigvecs = np.linalg.eigh(H)
435
+ V = de.pauli_hamiltonian_to_matrix(v_terms, n_full)
436
+ v_eigvals, v_eigvecs = np.linalg.eigh(V)
437
+ return n_side, n_full, L, R, P, Q, eigvals, eigvecs, v_eigvals, v_eigvecs
438
+
439
+
440
+ @lru_cache(maxsize=32)
441
+ def _cached_finite_beta_layout(n_majorana, k_terms, J, seed):
442
+ """LRU-memoized wrapper around _finite_beta_layout_precomputed --
443
+ diagonalizing H_tot and V is ~58% of a single exact-backend call's
444
+ cost (see that function's own docstring) and is identical for every
445
+ call at the same (n_majorana, k_terms, J, seed). Without this,
446
+ run_wormhole_protocol and run_wormhole_protocol_finite_beta each
447
+ redid the diagonalization from scratch on every call, even for a
448
+ caller re-running the same instance with a different mu/t0/t1/beta
449
+ -- exactly the callers _finite_beta_layout_precomputed /
450
+ _run_finite_beta_precomputed's own split API already served, just
451
+ without requiring callers to manage the split themselves.
452
+
453
+ maxsize=32 bounds memory: each cached entry holds up to 4 dense
454
+ n_full x n_full matrices (H's and V's eigenvectors), so an unbounded
455
+ cache across many distinct instances/sizes could otherwise grow
456
+ without limit -- 32 easily covers realistic single-session usage
457
+ (a handful of instances scanned over many mu/beta points each)
458
+ while evicting least-recently-used entries once exceeded.
459
+
460
+ The returned eigenvector/eigenvalue arrays are marked read-only
461
+ (defensive: every caller only ever reads them via _evolve /
462
+ _prepare_finite_beta_tfd_sv, both non-mutating, but the cache is
463
+ shared across calls so accidental in-place mutation would silently
464
+ corrupt it for everyone)."""
465
+ result = _finite_beta_layout_precomputed(n_majorana, k_terms, J, seed)
466
+ for arr in result[6:]: # eigvals, eigvecs, v_eigvals, v_eigvecs
467
+ arr.setflags(write=False)
468
+ return result
469
+
470
+
471
+ def _run_finite_beta_precomputed(n_side, n_full, L, R, P, Q, eigvals, eigvecs, v_eigvals, v_eigvecs,
472
+ mu, t0, t1, beta, with_message):
473
+ """Same physics as run_wormhole_protocol_finite_beta, given an
474
+ already-diagonalized layout from _finite_beta_layout_precomputed."""
475
+ sv = _prepare_finite_beta_tfd_sv(n_side, n_full, L, R, P, Q, eigvals, eigvecs, beta, with_message)
476
+ sv = _evolve(sv, eigvals, eigvecs, t0)
477
+ sv = _evolve(sv, v_eigvals, v_eigvecs, mu)
478
+ sv = _evolve(sv, eigvals, eigvecs, t1)
479
+ return mutual_information(sv, n_full, [P], [R[0]])
480
+
481
+
482
+ def run_wormhole_protocol_finite_beta(n_majorana, k_terms, J, mu, t0, t1, beta, seed, with_message):
483
+ """Identical exact-backend protocol to run_wormhole_protocol, except
484
+ the initial state is the real finite-beta TFD (see
485
+ _prepare_finite_beta_tfd_sv) instead of the beta=0 simplification
486
+ run_wormhole_protocol and run_wormhole_protocol_trotter both use.
487
+ beta=3 matches arXiv:2604.10090's own fixed choice (Section S2:
488
+ "we consider J=sqrt(2), q=4, and beta=3").
489
+
490
+ Diagonalizing H and V is memoized per (n_majorana, k_terms, J, seed)
491
+ via _cached_finite_beta_layout -- a repeat call at the same instance
492
+ (different beta/mu) reuses it instead of redoing it from scratch.
493
+ For an explicit (beta, mu) sweep at a fixed seed, _finite_beta_layout_precomputed
494
+ + _run_finite_beta_precomputed remain available directly if a caller
495
+ wants to hold the layout itself rather than rely on the cache.
496
+
497
+ Examples
498
+ --------
499
+ >>> import math
500
+ >>> from dashboard_core.wormhole import run_wormhole_protocol_finite_beta
501
+ >>> round(run_wormhole_protocol_finite_beta(n_majorana=8, k_terms=10, J=math.sqrt(2), mu=-12,
502
+ ... t0=0.3, t1=0.60, beta=3, seed=61, with_message=True), 4)
503
+ 0.0442
504
+ """
505
+ n_side, n_full, L, R, P, Q, eigvals, eigvecs, v_eigvals, v_eigvecs = \
506
+ _cached_finite_beta_layout(n_majorana, k_terms, J, seed)
507
+ return _run_finite_beta_precomputed(n_side, n_full, L, R, P, Q, eigvals, eigvecs, v_eigvals, v_eigvecs,
508
+ mu, t0, t1, beta, with_message)
509
+
510
+
511
+ def find_delta_beta_bands(n_majorana, k_terms, J, mu, t0, t1, seed, with_message,
512
+ beta_max=6.0, beta_step=0.02):
513
+ """Segments delta(beta) = I(mu=-mu) - I(mu=+mu) into constant-sign
514
+ bands over beta in [0, beta_max], for a fixed instance -- i.e. the
515
+ beta ranges where the sign-dependent teleportation signal is
516
+ "correctly" vs. "wrongly" signed, and exactly where it flips.
517
+
518
+ Motivated by a real finding: the sign is NOT stable across beta for
519
+ many instances (verified on 10 known 34/11-matched seeds -- 4 never
520
+ flip, the other 6 flip 1-3 times each across [0, 6]), so a single
521
+ per-instance sign (as every other backend in this module reports,
522
+ all implicitly at beta=0) can be misleading -- this gives the full
523
+ picture instead of one point on it.
524
+
525
+ Uses _cached_finite_beta_layout once per call (not per beta point) --
526
+ a beta_step=0.02 scan over [0, 6] is ~300 points x 2 mu signs (600
527
+ evaluations), all reusing the same eigendecomposition; measured at
528
+ ~36-40s per seed total (~0.06-0.07s/evaluation after the one-time
529
+ diagonalization), not ~13s/evaluation x 600 naive. A repeat call at
530
+ the same seed (different mu/t0/t1/beta_max/beta_step) also reuses
531
+ the cached diagonalization instead of redoing it.
532
+
533
+ Returns a list of dicts, one per constant-sign band, in beta order:
534
+ {"beta_lo", "beta_hi", "sign" ("positive"/"negative"),
535
+ "max_abs_delta"} (the largest |delta| reached within that band).
536
+ Crossing points (the beta_lo/beta_hi shared between adjacent bands)
537
+ are linearly interpolated between the nearest two grid points, not
538
+ just snapped to the grid resolution.
539
+
540
+ Examples
541
+ --------
542
+ >>> import math
543
+ >>> from dashboard_core.wormhole import find_delta_beta_bands
544
+ >>> bands = find_delta_beta_bands(n_majorana=8, k_terms=10, J=math.sqrt(2), mu=12, t0=0.3, t1=0.60,
545
+ ... seed=61, with_message=True, beta_max=1.0, beta_step=0.25)
546
+ >>> [b['sign'] for b in bands] # the sign flips once within this range, for this seed
547
+ ['positive', 'negative']
548
+ """
549
+ n_side, n_full, L, R, P, Q, eigvals, eigvecs, v_eigvals, v_eigvecs = \
550
+ _cached_finite_beta_layout(n_majorana, k_terms, J, seed)
551
+
552
+ betas = np.arange(0.0, beta_max + beta_step / 2, beta_step)
553
+ deltas = np.array([
554
+ _run_finite_beta_precomputed(n_side, n_full, L, R, P, Q, eigvals, eigvecs, v_eigvals, v_eigvecs,
555
+ -mu, t0, t1, float(b), with_message)
556
+ - _run_finite_beta_precomputed(n_side, n_full, L, R, P, Q, eigvals, eigvecs, v_eigvals, v_eigvecs,
557
+ +mu, t0, t1, float(b), with_message)
558
+ for b in betas
559
+ ])
560
+
561
+ signs = np.sign(deltas)
562
+ crossings = []
563
+ for i in range(1, len(betas)):
564
+ if signs[i] != signs[i - 1] and signs[i] != 0 and signs[i - 1] != 0:
565
+ b0, b1 = betas[i - 1], betas[i]
566
+ d0, d1 = deltas[i - 1], deltas[i]
567
+ crossings.append(float(b0 + (0 - d0) * (b1 - b0) / (d1 - d0)))
568
+
569
+ edges = [0.0] + crossings + [float(beta_max)]
570
+ bands = []
571
+ for k in range(len(edges) - 1):
572
+ b_lo, b_hi = edges[k], edges[k + 1]
573
+ mask = (betas >= b_lo) & (betas <= b_hi)
574
+ bands.append({
575
+ "beta_lo": round(b_lo, 4),
576
+ "beta_hi": round(b_hi, 4),
577
+ "sign": "positive" if np.mean(deltas[mask]) > 0 else "negative",
578
+ "max_abs_delta": float(np.max(np.abs(deltas[mask]))),
579
+ })
580
+ return bands