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
local_site/app/server.py
ADDED
|
@@ -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()
|