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.
- dashboard_core/__init__.py +115 -0
- dashboard_core/_gate_tables.py +30 -0
- dashboard_core/band_structure.py +71 -0
- dashboard_core/circuit_builder_component.py +232 -0
- dashboard_core/circuit_diagram.py +216 -0
- dashboard_core/crypto_protocols.py +77 -0
- dashboard_core/engine.py +326 -0
- dashboard_core/graphical_builder.py +114 -0
- dashboard_core/hamiltonians.py +593 -0
- dashboard_core/mass_decomposition_tool.py +47 -0
- dashboard_core/mitigation.py +343 -0
- dashboard_core/native_hf_diagnostics.py +62 -0
- dashboard_core/noise_tools.py +125 -0
- dashboard_core/qasm_library.py +233 -0
- dashboard_core/qmmm.py +16 -0
- dashboard_core/rag_tool.py +45 -0
- dashboard_core/state_visuals.py +288 -0
- dashboard_core/system_limits.py +60 -0
- dashboard_core/vector_healing.py +102 -0
- dashboard_core/visuals.py +158 -0
- dashboard_core/vqe.py +533 -0
- dashboard_core/wormhole.py +580 -0
- dense_evolution/__init__.py +114 -0
- dense_evolution/autodiff.py +10 -0
- dense_evolution/backends/__init__.py +5 -0
- dense_evolution/backends/chunk/__init__.py +37 -0
- dense_evolution/backends/chunk/_engine_imports.py +57 -0
- dense_evolution/backends/chunk/circuit_chunker.py +55 -0
- dense_evolution/backends/chunk/core.py +432 -0
- dense_evolution/backends/chunk/disk_overflow.py +232 -0
- dense_evolution/backends/chunk/geometry.py +95 -0
- dense_evolution/backends/chunk/guard.py +190 -0
- dense_evolution/backends/chunk/kernels.py +531 -0
- dense_evolution/backends/mps.py +1569 -0
- dense_evolution/backends/statevector.py +616 -0
- dense_evolution/chunk.py +25 -0
- dense_evolution/circuits/__init__.py +20 -0
- dense_evolution/circuits/compiler.py +488 -0
- dense_evolution/circuits/diagram.py +94 -0
- dense_evolution/circuits/gates.py +91 -0
- dense_evolution/circuits/parser.py +632 -0
- dense_evolution/circuits/qft.py +66 -0
- dense_evolution/circuits/random_circuit.py +85 -0
- dense_evolution/circuits/registry.py +74 -0
- dense_evolution/circuits/topology.py +79 -0
- dense_evolution/circuits/trotter.py +265 -0
- dense_evolution/circuits/uccsd.py +275 -0
- dense_evolution/cli.py +199 -0
- dense_evolution/compiler.py +9 -0
- dense_evolution/config.py +49 -0
- dense_evolution/drawing.py +10 -0
- dense_evolution/entropy.py +9 -0
- dense_evolution/fermions.py +9 -0
- dense_evolution/gates.py +9 -0
- dense_evolution/harrison_tb.py +16 -0
- dense_evolution/healing.py +18 -0
- dense_evolution/interop/__init__.py +18 -0
- dense_evolution/interop/qiskit_pennylane.py +406 -0
- dense_evolution/measurement.py +10 -0
- dense_evolution/mitigation/__init__.py +54 -0
- dense_evolution/mitigation/healing.py +215 -0
- dense_evolution/mitigation/kl_divergence.py +93 -0
- dense_evolution/mitigation/magic_entropy.py +163 -0
- dense_evolution/mitigation/magic_entropy_shadows.py +262 -0
- dense_evolution/mitigation/renyi.py +168 -0
- dense_evolution/mitigation/stabilizer_renyi_entropy.py +103 -0
- dense_evolution/mitigation/zne.py +990 -0
- dense_evolution/mps.py +9 -0
- dense_evolution/native_hf/__init__.py +26 -0
- dense_evolution/native_hf/_libcint/LICENSE-libcint +10 -0
- dense_evolution/native_hf/_libcint/libdecint.dll +0 -0
- dense_evolution/native_hf/assembly.py +304 -0
- dense_evolution/native_hf/basis.py +117 -0
- dense_evolution/native_hf/boys.py +35 -0
- dense_evolution/native_hf/bridge.py +112 -0
- dense_evolution/native_hf/cartesian.py +64 -0
- dense_evolution/native_hf/coulomb.py +196 -0
- dense_evolution/native_hf/differentiable.py +53 -0
- dense_evolution/native_hf/gaussians.py +79 -0
- dense_evolution/native_hf/kinetic.py +52 -0
- dense_evolution/native_hf/libcint_bridge.py +167 -0
- dense_evolution/native_hf/overlap.py +91 -0
- dense_evolution/native_hf/scf.py +404 -0
- dense_evolution/noise/__init__.py +79 -0
- dense_evolution/noise/coherent_attack.py +264 -0
- dense_evolution/noise/cosmic_ray.py +61 -0
- dense_evolution/noise/density_matrix_channels.py +78 -0
- dense_evolution/noise/differentiable.py +66 -0
- dense_evolution/noise/kraus/__init__.py +6 -0
- dense_evolution/noise/kraus/amplitude_damping.py +47 -0
- dense_evolution/noise/kraus/bitflip.py +22 -0
- dense_evolution/noise/kraus/combined.py +16 -0
- dense_evolution/noise/kraus/depolarizing.py +47 -0
- dense_evolution/noise/kraus/ideal.py +10 -0
- dense_evolution/noise/kraus/phaseflip.py +21 -0
- dense_evolution/noise/kraus_channels.py +285 -0
- dense_evolution/noise/oscillating.py +32 -0
- dense_evolution/noise/pink.py +80 -0
- dense_evolution/observables.py +11 -0
- dense_evolution/parser.py +9 -0
- dense_evolution/physics/__init__.py +27 -0
- dense_evolution/physics/entropy.py +161 -0
- dense_evolution/physics/fermions.py +322 -0
- dense_evolution/physics/observables.py +523 -0
- dense_evolution/physics/qec.py +1113 -0
- dense_evolution/physics/spectral.py +143 -0
- dense_evolution/physics/states.py +43 -0
- dense_evolution/protocols/__init__.py +27 -0
- dense_evolution/protocols/bb84.py +133 -0
- dense_evolution/protocols/di_qkd_ghz.py +199 -0
- dense_evolution/protocols/dicka_protocol2.py +124 -0
- dense_evolution/qec.py +20 -0
- dense_evolution/qft.py +9 -0
- dense_evolution/qmmm/__init__.py +13 -0
- dense_evolution/qmmm/ase_bridge.py +97 -0
- dense_evolution/qmmm/forces.py +388 -0
- dense_evolution/qmmm/propagation.py +80 -0
- dense_evolution/qmmm/region.py +137 -0
- dense_evolution/random_circuit.py +15 -0
- dense_evolution/registry.py +9 -0
- dense_evolution/simulator.py +10 -0
- dense_evolution/solvers/__init__.py +19 -0
- dense_evolution/solvers/autodiff.py +169 -0
- dense_evolution/solvers/harrison_tb.py +189 -0
- dense_evolution/solvers/vhd_tb.py +187 -0
- dense_evolution/states.py +9 -0
- dense_evolution/topology.py +9 -0
- dense_evolution/trotter.py +9 -0
- dense_evolution/utils/__init__.py +13 -0
- dense_evolution/utils/drawing.py +101 -0
- dense_evolution/utils/mass_decomposition.py +246 -0
- dense_evolution/utils/measurement.py +94 -0
- dense_evolution/vhd_tb.py +16 -0
- dense_evolution-8.3.0.dist-info/METADATA +366 -0
- dense_evolution-8.3.0.dist-info/RECORD +165 -0
- dense_evolution-8.3.0.dist-info/WHEEL +5 -0
- dense_evolution-8.3.0.dist-info/entry_points.txt +2 -0
- dense_evolution-8.3.0.dist-info/licenses/license.md +58 -0
- dense_evolution-8.3.0.dist-info/top_level.txt +5 -0
- ia_utils/__init__.py +0 -0
- ia_utils/adversarial_vector_attack.py +196 -0
- ia_utils/rag.py +288 -0
- ia_utils/vector_healing.py +399 -0
- local_site/__init__.py +0 -0
- local_site/app/__init__.py +0 -0
- local_site/app/server.py +1009 -0
- mcp_server/__init__.py +0 -0
- mcp_server/client.py +324 -0
- mcp_server/config.py +32 -0
- mcp_server/models.py +347 -0
- mcp_server/molecules.py +71 -0
- mcp_server/server.py +119 -0
- mcp_server/tools/__init__.py +0 -0
- mcp_server/tools/chemistry_tools.py +225 -0
- mcp_server/tools/circuit_tools.py +83 -0
- mcp_server/tools/crypto_tools.py +66 -0
- mcp_server/tools/mitigation_tools.py +81 -0
- mcp_server/tools/noise_tools.py +60 -0
- mcp_server/tools/retrieval_tools.py +44 -0
- mcp_server/tools/system_tools.py +149 -0
- mcp_server/tools/wormhole_tools.py +142 -0
- mcp_server/utils/__init__.py +0 -0
- mcp_server/utils/cache.py +55 -0
- mcp_server/utils/images.py +67 -0
- 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
|