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,1009 @@
1
+ """
2
+ The real local compute kernel behind the published Composer page
3
+ (docs/composer.md, part of the main site -- not a separate docs project
4
+ anymore). This process is the only thing that runs on your own machine:
5
+ `pip install dense-evolution[composer]` then `dense-evolution serve` (or
6
+ directly `python -m local_site.app.server`), and the public page's
7
+ JavaScript talks to it at http://127.0.0.1:8800 as its execution backend.
8
+ It serves no HTML of its own -- the UI lives in the real published docs
9
+ site, this is API-only.
10
+
11
+ Every endpoint below is a thin wrapper around the real, already-tested
12
+ dashboard_core functions (engine.run_circuit_from_qasm, visuals.*,
13
+ graphical_builder.ops_to_native_tuples) -- dense_evolution's actual
14
+ DenseSVSimulator does the computation; the circuit diagram is drawn
15
+ natively (dashboard_core.circuit_diagram, no Qiskit), the other three
16
+ panels by Qiskit's own visualization functions. No mock data anywhere
17
+ here.
18
+ """
19
+
20
+ import base64
21
+ import io
22
+ import sys
23
+ from pathlib import Path
24
+ from typing import List, Optional
25
+
26
+ import matplotlib
27
+ # FastAPI/Starlette runs sync route handlers in a worker thread, not the
28
+ # main thread -- matplotlib's default backend on Windows tries to open an
29
+ # interactive GUI window there, which either warns ("Starting a
30
+ # Matplotlib GUI outside of the main thread will likely fail") or hangs
31
+ # outright (observed directly: /api/run stuck mid-request with no
32
+ # response). Agg is the headless, thread-safe raster backend -- must be
33
+ # set before any other matplotlib/qiskit.visualization import.
34
+ matplotlib.use("Agg")
35
+
36
+ import numpy as np
37
+ import uvicorn
38
+ from fastapi import FastAPI, HTTPException
39
+ from fastapi.middleware.cors import CORSMiddleware
40
+ from pydantic import BaseModel
41
+
42
+ # dashboard_core / dense_evolution live at the repo root, two levels up
43
+ # from this file (local_site/app/server.py -> local_site/ -> repo root).
44
+ REPO_ROOT = Path(__file__).resolve().parents[2]
45
+ sys.path.insert(0, str(REPO_ROOT))
46
+
47
+ import dashboard_core as dc # noqa: E402
48
+ import dense_evolution # noqa: E402
49
+
50
+ app = FastAPI(title="Dense-Evolution Composer Kernel")
51
+
52
+ # The Composer page (docs/composer.md, part of the main site, published on
53
+ # GitHub Pages at tatopenn-cell.github.io) calls this local server as its
54
+ # compute kernel: the page itself is served from GitHub's origin (or opened
55
+ # straight from a downloaded copy on disk, origin "null"), this server from
56
+ # 127.0.0.1, so the browser treats every /api/* call as cross-origin and
57
+ # blocks it without this. localhost:8000 is `mkdocs serve`'s own dev server,
58
+ # included so the docs site can be developed against a locally-running
59
+ # kernel too, not just the published one. "null" is what browsers send as
60
+ # Origin for a page opened via file:// (the offline-downloaded copy) -- not
61
+ # a wildcard: this API executes real code (arbitrary OpenQASM), so only
62
+ # these known, intended origins are allowed rather than any page on the
63
+ # internet that happens to guess the port.
64
+ ALLOWED_ORIGINS = [
65
+ "https://tatopenn-cell.github.io",
66
+ "http://127.0.0.1:8000",
67
+ "http://localhost:8000",
68
+ "null",
69
+ ]
70
+ app.add_middleware(
71
+ CORSMiddleware,
72
+ allow_origins=ALLOWED_ORIGINS,
73
+ allow_methods=["GET", "POST"],
74
+ allow_headers=["*"],
75
+ )
76
+
77
+
78
+ class PrivateNetworkAccessMiddleware:
79
+ """Chromium (and anything built on it, including VS Code's Simple
80
+ Browser) treats a page loaded from a public HTTPS origin talking to
81
+ 127.0.0.1 as a "Private Network Access" request: on the CORS preflight
82
+ it adds `Access-Control-Request-Private-Network: true` and requires
83
+ the response to answer `Access-Control-Allow-Private-Network: true`,
84
+ on top of ordinary CORS -- Starlette's CORSMiddleware doesn't know
85
+ about this (newer, Chromium-specific) header at all and instead
86
+ rejects the preflight outright with 400 "Disallowed CORS
87
+ private-network" (verified directly: curl'd the exact preflight the
88
+ real browser sends and got that 400). Without this, the Composer
89
+ page's "detect the local kernel" banner never turns green in Chrome/
90
+ Edge/VS Code, even though the kernel is up and every plain curl
91
+ request succeeds -- curl never sends that header, so it doesn't hit
92
+ this path, which is why the failure wasn't visible from the command
93
+ line alone.
94
+
95
+ Installed as ASGI middleware (added after CORSMiddleware, so it runs
96
+ first on the request / last on the response) rather than routed
97
+ through CORSMiddleware itself: only intercepts the specific preflight
98
+ shape this needs (OPTIONS + that header, from an already-allowed
99
+ origin), so it can't accidentally relax CORS for anything else --
100
+ every other request still goes through CORSMiddleware unchanged."""
101
+
102
+ def __init__(self, app):
103
+ self.app = app
104
+
105
+ async def __call__(self, scope, receive, send):
106
+ if scope["type"] != "http":
107
+ await self.app(scope, receive, send)
108
+ return
109
+
110
+ headers = dict(scope.get("headers", []))
111
+ is_preflight = scope["method"] == "OPTIONS"
112
+ wants_private_network = headers.get(b"access-control-request-private-network") == b"true"
113
+ origin = headers.get(b"origin", b"").decode("latin-1")
114
+
115
+ if is_preflight and wants_private_network and origin in ALLOWED_ORIGINS:
116
+ response_headers = [
117
+ (b"access-control-allow-origin", origin.encode("latin-1")),
118
+ (b"access-control-allow-private-network", b"true"),
119
+ (b"access-control-allow-methods", b"GET, POST"),
120
+ (b"access-control-allow-headers", b"*"),
121
+ (b"vary", b"Origin"),
122
+ (b"content-length", b"0"),
123
+ ]
124
+ await send({"type": "http.response.start", "status": 200, "headers": response_headers})
125
+ await send({"type": "http.response.body", "body": b""})
126
+ return
127
+
128
+ await self.app(scope, receive, send)
129
+
130
+
131
+ app.add_middleware(PrivateNetworkAccessMiddleware)
132
+
133
+
134
+ def _has_qiskit() -> bool:
135
+ """qiskit is an optional dependency of this kernel (see pyproject.toml's
136
+ composer extra comment for why: it's the same library independently
137
+ described as destabilizing the process on macOS CI runners, so it's
138
+ opt-in, not required just to start). Histogram/Q-sphere/Bloch are the
139
+ only three panels that need it (the circuit diagram is native) -- this
140
+ lets /api/run skip exactly those three with an honest reason instead of
141
+ the whole endpoint failing with a raw 500 when it isn't installed."""
142
+ try:
143
+ import qiskit # noqa: F401
144
+ return True
145
+ except ImportError:
146
+ return False
147
+
148
+
149
+ @app.get("/api/health")
150
+ def health():
151
+ """Presence probe for the published Composer page: if this responds,
152
+ a real local kernel is installed and running on this machine, so the
153
+ page can unlock live execution instead of showing install instructions.
154
+ version is dense_evolution's own, not this API's -- lets the page warn
155
+ if the installed kernel is old enough to be missing an endpoint it needs.
156
+
157
+ hostname/total_ram_gb are returned so the page can show concrete,
158
+ checkable proof of which real machine answered ("connected to
159
+ DESKTOP-ABC123, 16.0 GB RAM") instead of an unverifiable claim like
160
+ "circuits really run on your PC" -- a visitor can open Task Manager/
161
+ a terminal and confirm this hostname and RAM figure are their own
162
+ machine's, which a generic sentence gives them no way to check."""
163
+ import socket
164
+ import psutil
165
+ mem = psutil.virtual_memory()
166
+ return {
167
+ "status": "ok",
168
+ "dense_evolution_version": dense_evolution.__version__,
169
+ "hostname": socket.gethostname(),
170
+ "total_ram_gb": round(mem.total / 1e9, 1),
171
+ "available_ram_gb": round(mem.available / 1e9, 1),
172
+ "ram_percent_free": round(100 - mem.percent, 1),
173
+ }
174
+
175
+
176
+ def _figure_to_base64_png(fig) -> str:
177
+ buf = io.BytesIO()
178
+ fig.savefig(buf, format="png", bbox_inches="tight", dpi=110)
179
+ buf.seek(0)
180
+ return base64.b64encode(buf.read()).decode("ascii")
181
+
182
+
183
+ class RunRequest(BaseModel):
184
+ qasm: str
185
+ shots: int = 1000
186
+ seed: int = 42
187
+ noise_model: str = "ideal"
188
+ noise_p: float = 0.0
189
+ backend: str = "dense"
190
+
191
+
192
+ class BuildRequest(BaseModel):
193
+ n_qubits: int
194
+ ops: list
195
+
196
+
197
+ @app.post("/api/build_from_ops")
198
+ def build_from_ops(req: BuildRequest):
199
+ """Convert the graphical builder's op list into real OpenQASM text."""
200
+ try:
201
+ native_ops = dc.ops_to_native_tuples(req.n_qubits, req.ops)
202
+ return {"qasm": dc.gate_tuples_to_qasm(native_ops, req.n_qubits)}
203
+ except Exception as exc:
204
+ raise HTTPException(status_code=400, detail=str(exc))
205
+
206
+
207
+ @app.post("/api/run")
208
+ def run(req: RunRequest):
209
+ """Run real OpenQASM on dense_evolution's DenseSVSimulator and return
210
+ every quantity the page displays -- statevector, probabilities,
211
+ shot counts, and the three real Qiskit-rendered figures.
212
+
213
+ Above dc.MPS_DENSE_CONTRACTION_LIMIT qubits with the MPS backend, no
214
+ dense (2**n,) array can exist at all -- dispatches to
215
+ dc.run_large_circuit_mps instead (real top-k most-probable-states via
216
+ greedy beam search, no sampling) and returns a distinctly-shaped
217
+ "large_scale" response instead of pretending a full statevector/
218
+ histogram/Q-sphere exist at that size."""
219
+ try:
220
+ # Never QuantumCircuit.from_qasm_str -- qiskit.circuit.QuantumCircuit
221
+ # itself segfaults on macOS regardless of how it's built (see
222
+ # dashboard_core/engine.py's module docstring), and this endpoint's
223
+ # repeated-same-process shape is exactly what triggers it. The
224
+ # qubit count is read via dense_evolution's own QASMParser instead,
225
+ # never through qiskit at all.
226
+ import dense_evolution as _de
227
+ req_n_qubits = _de.QASMParser().parse(req.qasm).n_qubits
228
+ except Exception as exc:
229
+ raise HTTPException(status_code=400, detail=str(exc))
230
+
231
+ if req.backend == "mps" and req_n_qubits > dc.MPS_DENSE_CONTRACTION_LIMIT:
232
+ try:
233
+ large_result = dc.run_large_circuit_mps(req.qasm, k=32, seed=req.seed)
234
+ except Exception as exc:
235
+ raise HTTPException(status_code=400, detail=str(exc))
236
+ return {
237
+ "large_scale": True,
238
+ "n_qubits": large_result.n_qubits,
239
+ "k_requested": large_result.k_requested,
240
+ "top_k_states": [{"state": s, "probability": p} for s, p in large_result.top_k_states],
241
+ "circuit_png": _figure_to_base64_png(dc.draw_circuit_figure(large_result.ops, large_result.n_qubits)),
242
+ "backend": "mps",
243
+ "mps_max_bond_used": large_result.mps_max_bond_used,
244
+ "mps_memory_mb": large_result.mps_memory_mb,
245
+ "mps_avg_jsd": large_result.mps_avg_jsd,
246
+ }
247
+
248
+ try:
249
+ result = dc.run_circuit_from_qasm(
250
+ req.qasm, n_shots=req.shots, seed=req.seed,
251
+ noise_model=req.noise_model, noise_p=req.noise_p,
252
+ backend=req.backend,
253
+ )
254
+ except Exception as exc:
255
+ raise HTTPException(status_code=400, detail=str(exc))
256
+
257
+ statevector_rows = [
258
+ {
259
+ "state": format(i, f"0{result.n_qubits}b"),
260
+ "re": float(amp.real),
261
+ "im": float(amp.imag),
262
+ "abs": float(abs(amp)),
263
+ "phase": float(np.angle(amp)),
264
+ }
265
+ for i, amp in enumerate(result.statevector)
266
+ if abs(amp) > 1e-10
267
+ ]
268
+
269
+ # Both native (dashboard_core.state_visuals, no Qiskit): measured
270
+ # directly at the dense backend's own practical qubit ceiling (24,
271
+ # MPS_DENSE_CONTRACTION_LIMIT), Q-sphere takes ~1.4s and Bloch ~7s --
272
+ # slow at the extreme edge but nowhere near the qiskit.visualization
273
+ # bottleneck this replaced (~37s/~252s at just 12 qubits), so no skip
274
+ # threshold is needed at all; every panel works at any qubit count the
275
+ # engine itself can produce a statevector for.
276
+ qsphere_png = _figure_to_base64_png(dc.qsphere_figure(result.statevector))
277
+ qsphere_skipped_reason = None
278
+ bloch_png = _figure_to_base64_png(dc.bloch_multivector_figure(result.statevector))
279
+ bloch_skipped_reason = None
280
+
281
+ return {
282
+ "large_scale": False,
283
+ "n_qubits": result.n_qubits,
284
+ "counts": result.counts,
285
+ "probabilities": result.probabilities.tolist(),
286
+ "statevector": statevector_rows,
287
+ "circuit_png": _figure_to_base64_png(dc.draw_circuit_figure(result.ops, result.n_qubits)),
288
+ "histogram_png": _figure_to_base64_png(dc.histogram_figure(result.counts)),
289
+ "qsphere_png": qsphere_png,
290
+ "qsphere_skipped_reason": qsphere_skipped_reason,
291
+ "bloch_png": bloch_png,
292
+ "bloch_skipped_reason": bloch_skipped_reason,
293
+ "fidelity_vs_ideal": result.fidelity_vs_ideal,
294
+ "backend": result.backend,
295
+ "mps_max_bond_used": result.mps_max_bond_used,
296
+ "mps_memory_mb": result.mps_memory_mb,
297
+ "mps_avg_jsd": result.mps_avg_jsd,
298
+ }
299
+
300
+
301
+ @app.get("/api/presets")
302
+ def presets():
303
+ return dc.QASM_LIBRARY
304
+
305
+
306
+ @app.get("/api/palette")
307
+ def palette():
308
+ return dc.GATE_PALETTE
309
+
310
+
311
+ @app.get("/api/noise_models")
312
+ def noise_models():
313
+ """Real Kraus-channel noise models (dense_evolution.NoiseModel)."""
314
+ import dense_evolution as _de
315
+ return _de.NoiseModel.MODELS
316
+
317
+
318
+ @app.get("/api/system_limits")
319
+ def system_limits():
320
+ """Real, per-machine max qubit count for a dense statevector, based on
321
+ actual free RAM right now (dense_evolution.chunk.SafeMemoryGuard) --
322
+ not a fixed number picked for one dev machine."""
323
+ return dc.max_safe_dense_qubits()
324
+
325
+
326
+ @app.get("/api/hamiltonians")
327
+ def hamiltonians(mapping: str = "jordan_wigner"):
328
+ """Every real molecular Hamiltonian in the catalog (PennyLane qchem
329
+ Hartree-Fock, real fermion-to-qubit mapping), each annotated with its
330
+ real qubit count -- unfiltered, so the whole catalog is always
331
+ visible and picking a molecule is what sets the circuit's qubit
332
+ count, not the reverse. mapping is jordan_wigner or bravyi_kitaev --
333
+ both represent the identical physical Hamiltonian (same spectrum),
334
+ just in a different qubit basis."""
335
+ return dc.get_all_molecules(mapping=mapping)
336
+
337
+
338
+ class MoleculeRequest(BaseModel):
339
+ name: str
340
+ mapping: str = "jordan_wigner"
341
+
342
+
343
+ @app.post("/api/hamiltonian/molecule")
344
+ def hamiltonian_molecule(req: MoleculeRequest):
345
+ """Ground-state energy of a catalog molecule -- real Hartree-Fock +
346
+ Jordan-Wigner/Bravyi-Kitaev Hamiltonian, exact dense diagonalization."""
347
+ try:
348
+ H = dc.get_molecular_hamiltonian_matrix(req.name, mapping=req.mapping)
349
+ except KeyError:
350
+ raise HTTPException(status_code=404, detail=f"unknown molecule: {req.name!r}")
351
+ spec = dc.MOLECULE_CATALOG[req.name]
352
+ geometry = spec["geometry"]() if callable(spec["geometry"]) else spec["geometry"]
353
+ return {
354
+ "n_qubits": int(np.log2(H.shape[0])),
355
+ "symbols": spec["symbols"],
356
+ "geometry": np.asarray(geometry).tolist(),
357
+ "charge": spec["charge"],
358
+ "ground_state_energy_hartree": dc.ground_state_energy(H),
359
+ }
360
+
361
+
362
+ class MixRequest(BaseModel):
363
+ name_a: str
364
+ name_b: str
365
+ weight_a: float = 0.5
366
+ weight_b: float = 0.5
367
+ mapping: str = "jordan_wigner"
368
+
369
+
370
+ @app.post("/api/hamiltonian/mix")
371
+ def hamiltonian_mix(req: MixRequest):
372
+ """Real weighted combination of two catalog Hamiltonians that share
373
+ the same qubit count (same electron space) -- H_mix = weight_a*H_a +
374
+ weight_b*H_b, a real Hermitian operator, exact-diagonalized just like
375
+ any single-molecule entry. Mixing molecules with different qubit
376
+ counts is physically meaningless (different Hilbert spaces), so
377
+ that's rejected with a clear error, not silently coerced."""
378
+ try:
379
+ H_a = dc.get_molecular_hamiltonian_matrix(req.name_a, mapping=req.mapping)
380
+ H_b = dc.get_molecular_hamiltonian_matrix(req.name_b, mapping=req.mapping)
381
+ except KeyError as exc:
382
+ raise HTTPException(status_code=404, detail=f"unknown molecule: {exc}")
383
+ try:
384
+ H_mix = dc.mix_hamiltonians(H_a, H_b, req.weight_a, req.weight_b)
385
+ except ValueError as exc:
386
+ raise HTTPException(status_code=400, detail=str(exc))
387
+ return {
388
+ "n_qubits": int(np.log2(H_mix.shape[0])),
389
+ "energy_a": dc.ground_state_energy(H_a),
390
+ "energy_b": dc.ground_state_energy(H_b),
391
+ "energy_mixed": dc.ground_state_energy(H_mix),
392
+ }
393
+
394
+
395
+ class CustomMoleculeRequest(BaseModel):
396
+ symbols: list
397
+ geometry: list # [[x, y, z], ...] in Angstrom
398
+ charge: int = 0
399
+ mapping: str = "jordan_wigner"
400
+
401
+
402
+ @app.post("/api/hamiltonian/custom")
403
+ def hamiltonian_custom(req: CustomMoleculeRequest):
404
+ """Real Hamiltonian for an arbitrary molecule specified on the spot --
405
+ same PennyLane Hartree-Fock pipeline as the catalog, just built from
406
+ whatever symbols/geometry the caller provides instead of a fixed
407
+ preset. Small molecules only (exact dense diagonalization -- this
408
+ simulator's real limit, not an arbitrary cap): reject before
409
+ PennyLane if the electron/orbital count would need too many qubits.
410
+ """
411
+ if len(req.symbols) != len(req.geometry):
412
+ raise HTTPException(
413
+ status_code=400,
414
+ detail=f"{len(req.symbols)} symbols but {len(req.geometry)} geometry rows",
415
+ )
416
+ try:
417
+ H, n_qubits = dc.build_molecular_hamiltonian(req.symbols, req.geometry, req.charge, mapping=req.mapping)
418
+ except Exception as exc:
419
+ raise HTTPException(status_code=400, detail=str(exc))
420
+ if n_qubits > 12:
421
+ raise HTTPException(
422
+ status_code=400,
423
+ detail=f"{n_qubits} qubits needed -- too large for exact dense diagonalization here",
424
+ )
425
+ return {
426
+ "n_qubits": n_qubits,
427
+ "ground_state_energy_hartree": dc.ground_state_energy(H),
428
+ }
429
+
430
+
431
+ class VqeRequest(BaseModel):
432
+ # Either name (catalog molecule) or symbols+geometry (custom) must be given.
433
+ name: str | None = None
434
+ symbols: list | None = None
435
+ geometry: list | None = None
436
+ charge: int = 0
437
+ ansatz_type: str = "hardware_efficient" # or "uccsd"
438
+ n_layers: int = 8
439
+ maxiter: int = 200
440
+ step_size: float = 0.1
441
+ beta1: float = 0.9
442
+ beta2: float = 0.999
443
+ seed: int = 0
444
+
445
+
446
+ @app.post("/api/vqe")
447
+ def vqe(req: VqeRequest):
448
+ """Real VQE, optimized with a real Adam gradient descent
449
+ (lightning.qubit + adjoint differentiation -- see dashboard_core.vqe)
450
+ against the molecule's real Jordan-Wigner Hamiltonian. Not a
451
+ fixed/precomputed circuit -- every call re-runs the optimization from
452
+ a fresh random start. ansatz_type "hardware_efficient" is a generic
453
+ n_layers-deep RY+CNOT template; "uccsd" is the real chemically-
454
+ motivated ansatz built from the molecule's own fermionic single/
455
+ double excitation operators (qml.qchem.excitations) -- fewer
456
+ parameters, converges faster per iteration, but each iteration is
457
+ much more expensive (the decomposed excitation circuits are far
458
+ deeper), so it needs fewer maxiter but more wall-clock time per
459
+ molecule at 10+ qubits."""
460
+ active_electrons = active_orbitals = None
461
+ if req.name:
462
+ try:
463
+ spec = dc.MOLECULE_CATALOG[req.name]
464
+ except KeyError:
465
+ raise HTTPException(status_code=404, detail=f"unknown molecule: {req.name!r}")
466
+ symbols = spec["symbols"]
467
+ geometry = spec["geometry"]() if callable(spec["geometry"]) else spec["geometry"]
468
+ charge = spec["charge"]
469
+ active_electrons = spec.get("active_electrons")
470
+ active_orbitals = spec.get("active_orbitals")
471
+ elif req.symbols and req.geometry:
472
+ if len(req.symbols) != len(req.geometry):
473
+ raise HTTPException(
474
+ status_code=400,
475
+ detail=f"{len(req.symbols)} symbols but {len(req.geometry)} geometry rows",
476
+ )
477
+ symbols, geometry, charge = req.symbols, req.geometry, req.charge
478
+ else:
479
+ raise HTTPException(status_code=400, detail="provide either 'name' or 'symbols'+'geometry'")
480
+
481
+ try:
482
+ result = dc.run_vqe(
483
+ symbols, geometry, charge=charge, ansatz_type=req.ansatz_type,
484
+ n_layers=req.n_layers, maxiter=req.maxiter,
485
+ step_size=req.step_size, beta1=req.beta1, beta2=req.beta2,
486
+ active_electrons=active_electrons, active_orbitals=active_orbitals, seed=req.seed,
487
+ )
488
+ except Exception as exc:
489
+ raise HTTPException(status_code=400, detail=str(exc))
490
+ return result
491
+
492
+
493
+ class QmmmForcesRequest(BaseModel):
494
+ name: str
495
+ mapping: str = "jordan_wigner"
496
+
497
+
498
+ @app.post("/api/qmmm_forces")
499
+ def qmmm_forces(req: QmmmForcesRequest):
500
+ """Real Hellmann-Feynman nuclear forces (dashboard_core.qmmm) on the
501
+ catalog molecule's real Hartree-Fock ground state -- see qmmm.py's
502
+ module docstring for the real differentiable-Hamiltonian derivation
503
+ (F = -d<psi|H(R)|psi>/dR via PennyLane's own autodiff, not a
504
+ finite-difference approximation or a fabricated classical-embedding
505
+ perturbation)."""
506
+ try:
507
+ result = dc.compute_hellmann_feynman_forces(req.name, mapping=req.mapping)
508
+ except Exception as exc:
509
+ raise HTTPException(status_code=400, detail=str(exc))
510
+ return result
511
+
512
+
513
+ class MdTrajectoryRequest(BaseModel):
514
+ name: str
515
+ n_steps: int = 20
516
+ dt_fs: float = 0.5
517
+ mapping: str = "jordan_wigner"
518
+ recompute_electronic_state: bool = False
519
+
520
+
521
+ @app.post("/api/md_trajectory")
522
+ def md_trajectory(req: MdTrajectoryRequest):
523
+ """Real Velocity-Verlet MD trajectory (dashboard_core.qmmm) driven by
524
+ real Hellmann-Feynman forces at every step. recompute_electronic_state
525
+ re-solves real Hartree-Fock at each step's new geometry (true ab-initio
526
+ MD, substantially more expensive per step) instead of holding the
527
+ initial electronic state fixed throughout (the default, real but only
528
+ accurate close to the starting geometry -- see qmmm.run_md_trajectory's
529
+ docstring). n_steps is capped to keep a single request bounded on
530
+ this kernel's real, often RAM/CPU-constrained host machine."""
531
+ if req.n_steps < 1 or req.n_steps > 200:
532
+ raise HTTPException(status_code=400, detail="n_steps must be between 1 and 200")
533
+ if req.recompute_electronic_state and req.n_steps > 30:
534
+ raise HTTPException(
535
+ status_code=400,
536
+ detail="recompute_electronic_state re-solves real Hartree-Fock every step -- "
537
+ "capped at 30 steps to keep a single request's real cost bounded",
538
+ )
539
+ try:
540
+ result = dc.run_md_trajectory(
541
+ req.name, req.n_steps, dt_fs=req.dt_fs, mapping=req.mapping,
542
+ recompute_electronic_state=req.recompute_electronic_state,
543
+ )
544
+ except Exception as exc:
545
+ raise HTTPException(status_code=400, detail=str(exc))
546
+ return result
547
+
548
+
549
+ class MitigateRequest(BaseModel):
550
+ qasm: str
551
+ pauli_string: str
552
+ noise_model: str
553
+ noise_p: float
554
+ seed: int = 42
555
+ extrapolation_method: str = "richardson"
556
+
557
+
558
+ @app.post("/api/mitigate")
559
+ def mitigate(req: MitigateRequest):
560
+ """Real Zero-Noise Extrapolation: <pauli_string> measured on the ideal
561
+ state and on the real noise channel at each noise scale (each an
562
+ ensemble average over many stochastic Kraus draws), extrapolated back
563
+ to zero noise -- Richardson (exact, through 1x/2x/3x noise_p) or a
564
+ degree-2 least-squares polynomial fit through 5 scales (1x..5x, trades
565
+ a little interpolation bias for averaging down statistical noise)."""
566
+ try:
567
+ result = dc.run_zne_mitigation(
568
+ req.qasm, req.pauli_string, req.noise_model, req.noise_p, seed=req.seed,
569
+ extrapolation_method=req.extrapolation_method,
570
+ )
571
+ except Exception as exc:
572
+ raise HTTPException(status_code=400, detail=str(exc))
573
+ return {
574
+ "n_qubits": result.n_qubits,
575
+ "pauli_string": result.pauli_string,
576
+ "ideal_expectation": result.ideal_expectation,
577
+ "noise_factors": result.noise_factors,
578
+ "noisy_expectations": result.noisy_expectations,
579
+ "zne_extrapolated": result.zne_extrapolated,
580
+ "extrapolation_method": result.extrapolation_method,
581
+ }
582
+
583
+
584
+ class MitigateMatrixRequest(BaseModel):
585
+ qasm: str
586
+ noise_model: str
587
+ noise_p: float
588
+ seed: int = 42
589
+
590
+
591
+ @app.post("/api/mitigate_matrix")
592
+ def mitigate_matrix(req: MitigateMatrixRequest):
593
+ """Real density-matrix ZNE (dense_evolution.zne_density_matrix):
594
+ Monte-Carlo density-matrix estimate at 1x/2x/3x noise_p,
595
+ extrapolated + projected onto the nearest physical state, graded by
596
+ real Uhlmann fidelity against the true ideal state."""
597
+ try:
598
+ result = dc.run_density_matrix_zne(req.qasm, req.noise_model, req.noise_p, seed=req.seed)
599
+ except Exception as exc:
600
+ raise HTTPException(status_code=400, detail=str(exc))
601
+ return {
602
+ "n_qubits": result.n_qubits,
603
+ "noise_factors": result.noise_factors,
604
+ "fidelity_raw": result.fidelity_raw,
605
+ "fidelity_corrected": result.fidelity_corrected,
606
+ }
607
+
608
+
609
+ @app.post("/api/mitigate_coherence")
610
+ def mitigate_coherence(req: MitigateMatrixRequest):
611
+ """Coherence-L1-predictive density-matrix ZNE
612
+ (dense_evolution.coherence_predictive_zne_density_matrix): same
613
+ Monte-Carlo construction as /api/mitigate_matrix, but extrapolated via
614
+ a signal that covers phase-type noise (phaseflip, dephasing) the
615
+ classical-JSD signal behind /api/mitigate_matrix is structurally blind
616
+ to (JSD only ever reads the density matrix's diagonal)."""
617
+ try:
618
+ result = dc.run_coherence_zne_mitigation(req.qasm, req.noise_model, req.noise_p, seed=req.seed)
619
+ except Exception as exc:
620
+ raise HTTPException(status_code=400, detail=str(exc))
621
+ return {
622
+ "n_qubits": result.n_qubits,
623
+ "noise_factors": result.noise_factors,
624
+ "fidelity_raw": result.fidelity_raw,
625
+ "fidelity_corrected": result.fidelity_corrected,
626
+ }
627
+
628
+
629
+ class Bb84Request(BaseModel):
630
+ n_rounds: int
631
+ p_channel: float = 0.0
632
+ eve: bool = False
633
+ seed: int | None = None
634
+
635
+
636
+ @app.post("/api/crypto/bb84")
637
+ def crypto_bb84(req: Bb84Request):
638
+ """BB84: prepare -> channel -> measure -> sift -> QBER
639
+ (dense_evolution.protocols.bb84)."""
640
+ try:
641
+ result = dc.run_bb84(req.n_rounds, req.p_channel, eve=req.eve, seed=req.seed)
642
+ except Exception as exc:
643
+ raise HTTPException(status_code=400, detail=str(exc))
644
+ return {
645
+ "n_rounds": result.n_rounds, "p_channel": result.p_channel, "eve": result.eve,
646
+ "qber": result.qber, "sifted_key_length": result.sifted_key_length,
647
+ }
648
+
649
+
650
+ class DiQkdGhzRequest(BaseModel):
651
+ n_rounds: int
652
+ p_dep: float = 0.0
653
+ seed: int | None = None
654
+
655
+
656
+ @app.post("/api/crypto/di_qkd_ghz")
657
+ def crypto_di_qkd_ghz(req: DiQkdGhzRequest):
658
+ """Three-party device-independent conference key agreement via GHZ(3)
659
+ (Ribeiro, Murta & Wehner 2018, arXiv:1708.00798;
660
+ dense_evolution.protocols.di_qkd_ghz)."""
661
+ try:
662
+ result = dc.run_di_qkd_ghz(req.n_rounds, req.p_dep, seed=req.seed)
663
+ except Exception as exc:
664
+ raise HTTPException(status_code=400, detail=str(exc))
665
+ return {
666
+ "n_rounds": result.n_rounds, "p_dep": result.p_dep,
667
+ "win_rate": result.win_rate, "expected_win_rate": result.expected_win_rate,
668
+ "qber_b1": result.qber_b1, "qber_b2": result.qber_b2,
669
+ }
670
+
671
+
672
+ class DickaRequest(BaseModel):
673
+ n_rounds: int
674
+ gamma: float
675
+ beta: float
676
+ p_dep: float = 0.0
677
+ seed: int | None = None
678
+
679
+
680
+ @app.post("/api/crypto/dicka")
681
+ def crypto_dicka(req: DickaRequest):
682
+ """Full multi-round DICKA structure (Appendix Protocol 2 of Ribeiro,
683
+ Murta & Wehner 2018; dense_evolution.protocols.dicka_protocol2). beta
684
+ must sit strictly between the classical bound (0.75) and the quantum
685
+ max (~0.8536) to meaningfully distinguish a device-independent
686
+ channel from a classically-bound one -- the underlying run_protocol
687
+ itself does not enforce this (any beta silently changes only the
688
+ abort decision), so it's checked here instead."""
689
+ from dense_evolution.protocols.dicka_protocol2 import CLASSICAL_BOUND, QUANTUM_MAX
690
+ if not (CLASSICAL_BOUND < req.beta < QUANTUM_MAX):
691
+ raise HTTPException(
692
+ status_code=400,
693
+ detail=f"beta must be strictly between {CLASSICAL_BOUND} and {QUANTUM_MAX}, got {req.beta}",
694
+ )
695
+ try:
696
+ return dc.run_dicka_protocol(req.n_rounds, req.gamma, req.beta, p_dep=req.p_dep, seed=req.seed)
697
+ except Exception as exc:
698
+ raise HTTPException(status_code=400, detail=str(exc))
699
+
700
+
701
+ class NativeHfDiagnosticsRequest(BaseModel):
702
+ symbols: list
703
+ geometry: list
704
+ charge: int = 0
705
+ active_electrons: int | None = None
706
+ active_orbitals: int | None = None
707
+
708
+
709
+ @app.post("/api/native_hf/diagnose")
710
+ def native_hf_diagnose(req: NativeHfDiagnosticsRequest):
711
+ """Real Hartree-Fock SCF (dense_evolution.native_hf) plus Dense-Armor
712
+ anomaly diagnostics on the real per-iteration energy trace, instead of
713
+ trusting the final `converged` flag alone. Needs the `armor` extra."""
714
+ if len(req.symbols) != len(req.geometry):
715
+ raise HTTPException(
716
+ status_code=400,
717
+ detail=f"{len(req.symbols)} symbols but {len(req.geometry)} geometry rows",
718
+ )
719
+ try:
720
+ result = dc.run_native_hf_diagnostics(
721
+ req.symbols, req.geometry, charge=req.charge,
722
+ active_electrons=req.active_electrons, active_orbitals=req.active_orbitals,
723
+ )
724
+ except ImportError as exc:
725
+ raise HTTPException(status_code=400, detail=f"missing optional dependency: {exc}")
726
+ except Exception as exc:
727
+ raise HTTPException(status_code=400, detail=str(exc))
728
+ return {
729
+ "n_qubits": result.n_qubits, "converged": result.converged, "n_iterations": result.n_iterations,
730
+ "electronic_energy_hartree": result.electronic_energy_hartree, "total_energy_hartree": result.total_energy_hartree,
731
+ "n_anomalies_hampel": result.n_anomalies_hampel, "n_anomalies_tukey": result.n_anomalies_tukey,
732
+ "anomaly_fraction_hampel": result.anomaly_fraction_hampel, "anomaly_fraction_tukey": result.anomaly_fraction_tukey,
733
+ }
734
+
735
+
736
+ class MassDecompositionRequest(BaseModel):
737
+ formula: str
738
+ target_mass: float
739
+ max_mass: float | None = None
740
+
741
+
742
+ @app.post("/api/mass_decomposition")
743
+ def mass_decomposition(req: MassDecompositionRequest):
744
+ """Check whether target_mass is a chemically valid, reachable
745
+ sub-formula mass of `formula` (dense_evolution.utils.mass_decomposition,
746
+ promoted from CASMI26 spectral-identification work)."""
747
+ try:
748
+ result = dc.run_mass_decomposition(req.formula, req.target_mass, max_mass=req.max_mass)
749
+ except Exception as exc:
750
+ raise HTTPException(status_code=400, detail=str(exc))
751
+ return {
752
+ "formula_counts": result.formula_counts, "rdbe": result.rdbe,
753
+ "nearest_reachable_mass": result.nearest_reachable_mass,
754
+ "density_at_target": result.density_at_target,
755
+ }
756
+
757
+
758
+ class RagSearchRequest(BaseModel):
759
+ documents: list # [[text, source], ...]
760
+ query: str
761
+ top: int = 5
762
+ rerank: bool = True
763
+ exact: bool = False
764
+ regex: bool = False
765
+ max_hits: int = 10
766
+ context: int = 300
767
+
768
+
769
+ @app.post("/api/rag_search")
770
+ def rag_search(req: RagSearchRequest):
771
+ """Build an ephemeral index over the caller's own documents and search
772
+ it in one call (ia_utils.rag) -- nothing persists server-side between
773
+ requests. exact=True switches to substring/regex search over raw
774
+ chunk text instead of semantic search (no embedding, no reranker).
775
+ Needs the `rag` extra for semantic search; exact mode still needs it
776
+ to build the index (TF-IDF), but not to search once built."""
777
+ try:
778
+ documents = [(d[0], d[1]) for d in req.documents]
779
+ except (IndexError, TypeError):
780
+ raise HTTPException(status_code=400, detail="documents must be a list of [text, source] pairs")
781
+ try:
782
+ result = dc.run_rag_search(
783
+ documents, req.query, top=req.top, rerank=req.rerank,
784
+ exact=req.exact, regex=req.regex, max_hits=req.max_hits, context=req.context,
785
+ )
786
+ except ImportError as exc:
787
+ raise HTTPException(status_code=400, detail=f"missing optional dependency: {exc}")
788
+ except Exception as exc:
789
+ raise HTTPException(status_code=400, detail=str(exc))
790
+ return {"results": result.results}
791
+
792
+
793
+ class CosmicRayBurstRequest(BaseModel):
794
+ baseline_gamma: float
795
+ times_us: Optional[List[float]] = None
796
+
797
+
798
+ @app.post("/api/cosmic_ray_burst")
799
+ def cosmic_ray_burst(req: CosmicRayBurstRequest):
800
+ """Real cosmic-ray/gamma-ray-induced quasiparticle burst profile,
801
+ reproducing a real measured event from arXiv:2104.05219."""
802
+ try:
803
+ result = dc.run_cosmic_ray_burst(req.baseline_gamma, times_us=req.times_us)
804
+ except Exception as exc:
805
+ raise HTTPException(status_code=400, detail=str(exc))
806
+ return {
807
+ "times_us": result.times_us,
808
+ "baseline_gamma": result.baseline_gamma,
809
+ "decay_probabilities": result.decay_probabilities,
810
+ "peak_ratio": result.peak_ratio,
811
+ }
812
+
813
+
814
+ class OscillatingNoiseRequest(BaseModel):
815
+ base_p: float
816
+ freq: float
817
+ amp: float
818
+ factors: Optional[List[float]] = None
819
+
820
+
821
+ @app.post("/api/oscillating_noise")
822
+ def oscillating_noise(req: OscillatingNoiseRequest):
823
+ """Noise strength that oscillates instead of scaling smoothly with a
824
+ ZNE-style scale factor -- for stress-testing a mitigation technique's
825
+ smoothness assumption."""
826
+ try:
827
+ result = dc.run_oscillating_noise(req.base_p, req.freq, req.amp, factors=req.factors)
828
+ except Exception as exc:
829
+ raise HTTPException(status_code=400, detail=str(exc))
830
+ return {
831
+ "base_p": result.base_p,
832
+ "freq": result.freq,
833
+ "amp": result.amp,
834
+ "factors": result.factors,
835
+ "p_eff": result.p_eff,
836
+ }
837
+
838
+
839
+ class DensityMatrixChannelRequest(BaseModel):
840
+ qasm: str
841
+ channel: str
842
+ param: float
843
+
844
+
845
+ @app.post("/api/density_matrix_channel")
846
+ def density_matrix_channel(req: DensityMatrixChannelRequest):
847
+ """Apply a density-matrix-level noise channel ('global_depolarizing'
848
+ or 'amplitude_damping') to the ideal density matrix of a QASM
849
+ circuit -- distinct from NoiseModel's per-qubit statevector channels."""
850
+ try:
851
+ result = dc.run_density_matrix_channel(req.qasm, req.channel, req.param)
852
+ except Exception as exc:
853
+ raise HTTPException(status_code=400, detail=str(exc))
854
+ return {
855
+ "n_qubits": result.n_qubits,
856
+ "channel": result.channel,
857
+ "param": result.param,
858
+ "ideal_diagonal": result.ideal_diagonal,
859
+ "noisy_diagonal": result.noisy_diagonal,
860
+ "trace": result.trace,
861
+ }
862
+
863
+
864
+ class VectorHealingRequest(BaseModel):
865
+ vectors: list # (n_steps, dim) -- list of equal-length rows
866
+ radius_baseline: int | None = None
867
+
868
+
869
+ @app.post("/api/vector_healing")
870
+ def vector_healing(req: VectorHealingRequest):
871
+ """Real predictive-healing pass over a noisy vector sequence
872
+ (dashboard_core.run_vector_healing / ia_utils.vector_healing): per
873
+ step, a Phi-Trigger decides whether the change from a local baseline
874
+ looks like genuine dynamics (kept) or static noise (replaced by the
875
+ local median). NaN/Inf entries are sanitized first regardless."""
876
+ try:
877
+ result = dc.run_vector_healing(req.vectors, radius_baseline=req.radius_baseline)
878
+ except Exception as exc:
879
+ raise HTTPException(status_code=400, detail=str(exc))
880
+ return {
881
+ "healed_vectors": result.healed_vectors,
882
+ "fallback_triggered": result.fallback_triggered,
883
+ "adaptive_radius_used": result.adaptive_radius_used,
884
+ "reconstruction_error": result.reconstruction_error,
885
+ }
886
+
887
+
888
+ class WormholeSelectInstanceRequest(BaseModel):
889
+ n_majorana: int = 8
890
+ k_terms: int = 10
891
+ J: float = float(np.sqrt(2))
892
+ n_candidates: int = 200
893
+ target_commuting: int = 34
894
+
895
+
896
+ @app.post("/api/wormhole_select_instance")
897
+ def wormhole_select_instance(req: WormholeSelectInstanceRequest):
898
+ """Screen `n_candidates` random binary-sparse-SYK realizations (see
899
+ dashboard_core.wormhole) by their exact commuting/anticommuting term-
900
+ pair count and return the seed closest to `target_commuting` --
901
+ arXiv:2604.10090's own instance-selection criterion. A uniformly-
902
+ random seed does not reliably show the protocol's sign-dependent
903
+ teleportation signal; this is the real, verified fix (not a random
904
+ seed dressed up), see /api/wormhole_teleportation."""
905
+ if req.n_majorana < 4 or req.n_majorana % 2 != 0:
906
+ raise HTTPException(status_code=400, detail="n_majorana must be even and >= 4")
907
+ if req.k_terms < 1 or req.k_terms > (req.n_majorana * (req.n_majorana - 1) *
908
+ (req.n_majorana - 2) * (req.n_majorana - 3)) // 24:
909
+ raise HTTPException(status_code=400, detail=f"k_terms must be in [1, C({req.n_majorana},4)]")
910
+ try:
911
+ seed = dc.select_good_instance(
912
+ req.n_majorana, req.k_terms, req.J,
913
+ n_candidates=req.n_candidates, target_commuting=req.target_commuting,
914
+ )
915
+ n_qubits, terms = dc.build_sparse_syk_terms(req.n_majorana, req.k_terms, req.J, seed)
916
+ commuting, anticommuting = dc.commuting_pair_count(terms, n_qubits)
917
+ except Exception as exc:
918
+ raise HTTPException(status_code=400, detail=str(exc))
919
+ return {
920
+ "seed": seed,
921
+ "n_majorana": req.n_majorana,
922
+ "k_terms": req.k_terms,
923
+ "commuting": commuting,
924
+ "anticommuting": anticommuting,
925
+ "target_commuting": req.target_commuting,
926
+ }
927
+
928
+
929
+ class WormholeTeleportationRequest(BaseModel):
930
+ n_majorana: int = 8
931
+ k_terms: int = 10
932
+ J: float = float(np.sqrt(2))
933
+ mu: float = 12.0
934
+ t0: float = 0.3
935
+ t1: float = 0.6
936
+ seed: int = 61
937
+ with_message: bool = True
938
+ backend: str = "exact" # or "trotter"
939
+ n_steps_evolution: int = 8
940
+ n_steps_coupling: int = 16
941
+
942
+
943
+ @app.post("/api/wormhole_teleportation")
944
+ def wormhole_teleportation(req: WormholeTeleportationRequest):
945
+ """Real traversable-wormhole-inspired quantum teleportation
946
+ (Gao-Jafferis-Wall theory, arXiv:2604.10090) on a binary sparse SYK
947
+ model -- see dashboard_core.wormhole's module docstring for the full
948
+ derivation. Returns the mutual information I(P:R[0]) between the
949
+ injected-message reference qubit and a qubit read out from the R
950
+ (right) SYK register after the full TFD-setup / evolve(t0) /
951
+ couple(mu) / evolve(t1) protocol; the qualitative result to look for
952
+ is that this value differs between mu>0 and mu<0 (call twice with
953
+ opposite-sign mu and compare -- see /api/wormhole_scan-adjacent MCP
954
+ tool for a batch version of exactly that sweep).
955
+
956
+ backend="exact" evolves via exact matrix exponentiation
957
+ (eigendecomposition, cheap and exact); backend="trotter" evolves via
958
+ a real Trotterized gate circuit (dense_evolution.trotter_evolve_ops)
959
+ run through DenseSVSimulator -- closer to what actual hardware would
960
+ execute, verified to reproduce the exact backend's result closely at
961
+ the known signal peak (seed 61, t0=0.3, t1=0.60)."""
962
+ if req.n_majorana < 4 or req.n_majorana % 2 != 0:
963
+ raise HTTPException(status_code=400, detail="n_majorana must be even and >= 4")
964
+ n_full = req.n_majorana + 2
965
+ limits = dc.max_safe_dense_qubits()
966
+ if n_full > limits["max_qubits_dense"]:
967
+ raise HTTPException(
968
+ status_code=400,
969
+ detail=f"n_majorana={req.n_majorana} needs {n_full} qubits total (L+R+P+Q), "
970
+ f"too large for exact dense simulation here (max {limits['max_qubits_dense']})",
971
+ )
972
+ if req.backend not in ("exact", "trotter"):
973
+ raise HTTPException(status_code=400, detail="backend must be 'exact' or 'trotter'")
974
+
975
+ try:
976
+ if req.backend == "exact":
977
+ mi = dc.run_wormhole_protocol(
978
+ req.n_majorana, req.k_terms, req.J, req.mu, req.t0, req.t1,
979
+ req.seed, with_message=req.with_message,
980
+ )
981
+ else:
982
+ mi = dc.run_wormhole_protocol_trotter(
983
+ req.n_majorana, req.k_terms, req.J, req.mu, req.t0, req.t1,
984
+ req.seed, with_message=req.with_message,
985
+ n_steps_evolution=req.n_steps_evolution, n_steps_coupling=req.n_steps_coupling,
986
+ )
987
+ except Exception as exc:
988
+ raise HTTPException(status_code=400, detail=str(exc))
989
+ return {
990
+ "mutual_information_pt": mi,
991
+ "backend": req.backend,
992
+ "n_majorana": req.n_majorana,
993
+ "k_terms": req.k_terms,
994
+ "mu": req.mu,
995
+ "t0": req.t0,
996
+ "t1": req.t1,
997
+ "seed": req.seed,
998
+ "with_message": req.with_message,
999
+ }
1000
+
1001
+
1002
+ def main():
1003
+ """Console-script entry point (`dense-evolution serve`, see
1004
+ dense_evolution/cli.py) -- identical to running this file directly."""
1005
+ uvicorn.run(app, host="127.0.0.1", port=8800)
1006
+
1007
+
1008
+ if __name__ == "__main__":
1009
+ main()