quantumflow-sdk 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.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.
- api/main.py +6 -2
- api/routes/algorithm_routes.py +895 -0
- quantumflow/__init__.py +1 -1
- quantumflow/integrations/__init__.py +14 -0
- quantumflow/integrations/openai_functions.py +578 -0
- {quantumflow_sdk-0.2.0.dist-info → quantumflow_sdk-0.3.0.dist-info}/METADATA +1 -1
- {quantumflow_sdk-0.2.0.dist-info → quantumflow_sdk-0.3.0.dist-info}/RECORD +10 -8
- {quantumflow_sdk-0.2.0.dist-info → quantumflow_sdk-0.3.0.dist-info}/WHEEL +0 -0
- {quantumflow_sdk-0.2.0.dist-info → quantumflow_sdk-0.3.0.dist-info}/entry_points.txt +0 -0
- {quantumflow_sdk-0.2.0.dist-info → quantumflow_sdk-0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,895 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Algorithm Routes for QuantumFlow API.
|
|
3
|
+
|
|
4
|
+
Provides endpoints for core quantum algorithms:
|
|
5
|
+
- QNN (Quantum Neural Network) - Forward pass, training, inference
|
|
6
|
+
- Grover's Search - Quantum search with quadratic speedup
|
|
7
|
+
- QAOA - Quantum optimization
|
|
8
|
+
- VQE - Variational eigensolver
|
|
9
|
+
|
|
10
|
+
Authentication:
|
|
11
|
+
- Free tier (no key): QNN forward, Grover, QRNG, QFT, QKD
|
|
12
|
+
- Requires API key: QNN train, QAOA, VQE, QSVM (heavy compute)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import time
|
|
16
|
+
from typing import Optional, List
|
|
17
|
+
from fastapi import APIRouter, HTTPException, Depends
|
|
18
|
+
from pydantic import BaseModel, Field
|
|
19
|
+
import numpy as np
|
|
20
|
+
|
|
21
|
+
from api.auth import get_optional_user, get_current_user
|
|
22
|
+
from db.models import User
|
|
23
|
+
|
|
24
|
+
# Import quantum algorithms
|
|
25
|
+
from quantumflow.algorithms.machine_learning import QNN, VQE, QSVM
|
|
26
|
+
from quantumflow.algorithms.optimization import GroverSearch, QAOA
|
|
27
|
+
from quantumflow.backends.base_backend import BackendType
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
router = APIRouter(prefix="/v1/algorithms", tags=["Algorithms"])
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ============================================================
|
|
34
|
+
# QNN (Quantum Neural Network) Models & Endpoints
|
|
35
|
+
# ============================================================
|
|
36
|
+
|
|
37
|
+
class QNNForwardRequest(BaseModel):
|
|
38
|
+
"""Request for QNN forward pass."""
|
|
39
|
+
input_data: List[float] = Field(..., description="Input features (normalized 0-1)")
|
|
40
|
+
weights: Optional[List[float]] = Field(None, description="Model weights (random if not provided)")
|
|
41
|
+
n_qubits: int = Field(default=4, ge=2, le=16, description="Number of qubits")
|
|
42
|
+
n_layers: int = Field(default=2, ge=1, le=10, description="Number of variational layers")
|
|
43
|
+
shots: int = Field(default=1024, ge=100, le=10000)
|
|
44
|
+
backend: str = Field(default="simulator")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class QNNForwardResponse(BaseModel):
|
|
48
|
+
"""Response from QNN forward pass."""
|
|
49
|
+
output: float
|
|
50
|
+
output_probabilities: List[float]
|
|
51
|
+
n_qubits: int
|
|
52
|
+
n_layers: int
|
|
53
|
+
n_parameters: int
|
|
54
|
+
weights_used: List[float]
|
|
55
|
+
execution_time_ms: float
|
|
56
|
+
circuit_depth: int
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class QNNTrainRequest(BaseModel):
|
|
60
|
+
"""Request for QNN training."""
|
|
61
|
+
X_train: List[List[float]] = Field(..., description="Training features")
|
|
62
|
+
y_train: List[int] = Field(..., description="Training labels (0 or 1)")
|
|
63
|
+
n_qubits: int = Field(default=4, ge=2, le=16)
|
|
64
|
+
n_layers: int = Field(default=2, ge=1, le=10)
|
|
65
|
+
epochs: int = Field(default=50, ge=1, le=500)
|
|
66
|
+
learning_rate: float = Field(default=0.1, ge=0.001, le=1.0)
|
|
67
|
+
batch_size: int = Field(default=8, ge=1, le=64)
|
|
68
|
+
shots: int = Field(default=1024)
|
|
69
|
+
backend: str = Field(default="simulator")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class QNNTrainResponse(BaseModel):
|
|
73
|
+
"""Response from QNN training."""
|
|
74
|
+
final_weights: List[float]
|
|
75
|
+
loss_history: List[float]
|
|
76
|
+
final_accuracy: float
|
|
77
|
+
n_epochs: int
|
|
78
|
+
n_parameters: int
|
|
79
|
+
execution_time_ms: float
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class QNNPredictRequest(BaseModel):
|
|
83
|
+
"""Request for QNN prediction."""
|
|
84
|
+
X: List[List[float]] = Field(..., description="Input features to predict")
|
|
85
|
+
weights: List[float] = Field(..., description="Trained model weights")
|
|
86
|
+
n_qubits: int = Field(default=4, ge=2, le=16)
|
|
87
|
+
n_layers: int = Field(default=2, ge=1, le=10)
|
|
88
|
+
return_probabilities: bool = Field(default=False)
|
|
89
|
+
shots: int = Field(default=1024)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class QNNPredictResponse(BaseModel):
|
|
93
|
+
"""Response from QNN prediction."""
|
|
94
|
+
predictions: List[int]
|
|
95
|
+
probabilities: Optional[List[float]] = None
|
|
96
|
+
execution_time_ms: float
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@router.post("/qnn/forward", response_model=QNNForwardResponse)
|
|
100
|
+
async def qnn_forward(
|
|
101
|
+
request: QNNForwardRequest,
|
|
102
|
+
user: Optional[User] = Depends(get_optional_user),
|
|
103
|
+
):
|
|
104
|
+
"""
|
|
105
|
+
Execute QNN forward pass.
|
|
106
|
+
|
|
107
|
+
Runs input data through a parameterized quantum circuit to produce output.
|
|
108
|
+
This is the inference step of a quantum neural network.
|
|
109
|
+
"""
|
|
110
|
+
start_time = time.perf_counter()
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
# Initialize QNN
|
|
114
|
+
qnn = QNN(
|
|
115
|
+
n_qubits=request.n_qubits,
|
|
116
|
+
n_layers=request.n_layers,
|
|
117
|
+
backend=request.backend,
|
|
118
|
+
shots=request.shots,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Use provided weights or random initialization
|
|
122
|
+
if request.weights:
|
|
123
|
+
if len(request.weights) != qnn.n_params:
|
|
124
|
+
raise ValueError(
|
|
125
|
+
f"Expected {qnn.n_params} weights, got {len(request.weights)}"
|
|
126
|
+
)
|
|
127
|
+
qnn.weights = np.array(request.weights)
|
|
128
|
+
|
|
129
|
+
# Prepare input
|
|
130
|
+
x = np.array(request.input_data)
|
|
131
|
+
if len(x) > request.n_qubits:
|
|
132
|
+
x = x[:request.n_qubits] # Truncate to n_qubits
|
|
133
|
+
elif len(x) < request.n_qubits:
|
|
134
|
+
x = np.pad(x, (0, request.n_qubits - len(x))) # Pad with zeros
|
|
135
|
+
|
|
136
|
+
# Forward pass
|
|
137
|
+
output_prob = qnn._forward(x)
|
|
138
|
+
|
|
139
|
+
# Get all probabilities by running predict_proba
|
|
140
|
+
all_probs = [output_prob, 1.0 - output_prob]
|
|
141
|
+
|
|
142
|
+
execution_time = (time.perf_counter() - start_time) * 1000
|
|
143
|
+
|
|
144
|
+
return QNNForwardResponse(
|
|
145
|
+
output=output_prob,
|
|
146
|
+
output_probabilities=all_probs,
|
|
147
|
+
n_qubits=request.n_qubits,
|
|
148
|
+
n_layers=request.n_layers,
|
|
149
|
+
n_parameters=qnn.n_params,
|
|
150
|
+
weights_used=qnn.weights.tolist(),
|
|
151
|
+
execution_time_ms=execution_time,
|
|
152
|
+
circuit_depth=request.n_layers * (request.n_qubits + 1) + 1, # Approximate
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
except Exception as e:
|
|
156
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@router.post("/qnn/train", response_model=QNNTrainResponse)
|
|
160
|
+
async def qnn_train(
|
|
161
|
+
request: QNNTrainRequest,
|
|
162
|
+
user: User = Depends(get_current_user), # Requires API key - heavy compute
|
|
163
|
+
):
|
|
164
|
+
"""
|
|
165
|
+
Train a Quantum Neural Network.
|
|
166
|
+
|
|
167
|
+
Uses parameter-shift rule for gradient computation and
|
|
168
|
+
mini-batch gradient descent for optimization.
|
|
169
|
+
"""
|
|
170
|
+
start_time = time.perf_counter()
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
# Initialize QNN
|
|
174
|
+
qnn = QNN(
|
|
175
|
+
n_qubits=request.n_qubits,
|
|
176
|
+
n_layers=request.n_layers,
|
|
177
|
+
backend=request.backend,
|
|
178
|
+
learning_rate=request.learning_rate,
|
|
179
|
+
shots=request.shots,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Prepare data
|
|
183
|
+
X = np.array(request.X_train)
|
|
184
|
+
y = np.array(request.y_train)
|
|
185
|
+
|
|
186
|
+
# Train
|
|
187
|
+
result = qnn.fit(
|
|
188
|
+
X=X,
|
|
189
|
+
y=y,
|
|
190
|
+
epochs=request.epochs,
|
|
191
|
+
batch_size=request.batch_size,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
execution_time = (time.perf_counter() - start_time) * 1000
|
|
195
|
+
|
|
196
|
+
return QNNTrainResponse(
|
|
197
|
+
final_weights=result.final_weights.tolist(),
|
|
198
|
+
loss_history=result.loss_history,
|
|
199
|
+
final_accuracy=result.accuracy,
|
|
200
|
+
n_epochs=result.n_epochs,
|
|
201
|
+
n_parameters=qnn.n_params,
|
|
202
|
+
execution_time_ms=execution_time,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
except Exception as e:
|
|
206
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@router.post("/qnn/predict", response_model=QNNPredictResponse)
|
|
210
|
+
async def qnn_predict(
|
|
211
|
+
request: QNNPredictRequest,
|
|
212
|
+
user: Optional[User] = Depends(get_optional_user),
|
|
213
|
+
):
|
|
214
|
+
"""
|
|
215
|
+
Make predictions using a trained QNN.
|
|
216
|
+
"""
|
|
217
|
+
start_time = time.perf_counter()
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
# Initialize QNN with trained weights
|
|
221
|
+
qnn = QNN(
|
|
222
|
+
n_qubits=request.n_qubits,
|
|
223
|
+
n_layers=request.n_layers,
|
|
224
|
+
shots=request.shots,
|
|
225
|
+
)
|
|
226
|
+
qnn.weights = np.array(request.weights)
|
|
227
|
+
|
|
228
|
+
# Prepare data
|
|
229
|
+
X = np.array(request.X)
|
|
230
|
+
|
|
231
|
+
# Predict
|
|
232
|
+
predictions = qnn.predict(X)
|
|
233
|
+
|
|
234
|
+
probabilities = None
|
|
235
|
+
if request.return_probabilities:
|
|
236
|
+
probabilities = qnn.predict_proba(X).tolist()
|
|
237
|
+
|
|
238
|
+
execution_time = (time.perf_counter() - start_time) * 1000
|
|
239
|
+
|
|
240
|
+
return QNNPredictResponse(
|
|
241
|
+
predictions=predictions.tolist(),
|
|
242
|
+
probabilities=probabilities,
|
|
243
|
+
execution_time_ms=execution_time,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
except Exception as e:
|
|
247
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# ============================================================
|
|
251
|
+
# Grover's Search Models & Endpoints
|
|
252
|
+
# ============================================================
|
|
253
|
+
|
|
254
|
+
class GroverSearchRequest(BaseModel):
|
|
255
|
+
"""Request for Grover's search."""
|
|
256
|
+
n_qubits: int = Field(..., ge=2, le=20, description="Search space = 2^n_qubits")
|
|
257
|
+
marked_states: List[int] = Field(..., min_length=1, description="States to search for")
|
|
258
|
+
iterations: Optional[int] = Field(None, description="Grover iterations (optimal if not specified)")
|
|
259
|
+
shots: int = Field(default=1024, ge=100, le=10000)
|
|
260
|
+
backend: str = Field(default="simulator")
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class GroverSearchResponse(BaseModel):
|
|
264
|
+
"""Response from Grover's search."""
|
|
265
|
+
found_state: str
|
|
266
|
+
found_state_decimal: int
|
|
267
|
+
probability: float
|
|
268
|
+
iterations_used: int
|
|
269
|
+
optimal_iterations: int
|
|
270
|
+
search_space_size: int
|
|
271
|
+
speedup_factor: str
|
|
272
|
+
execution_time_ms: float
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@router.post("/grover/search", response_model=GroverSearchResponse)
|
|
276
|
+
async def grover_search(
|
|
277
|
+
request: GroverSearchRequest,
|
|
278
|
+
user: Optional[User] = Depends(get_optional_user),
|
|
279
|
+
):
|
|
280
|
+
"""
|
|
281
|
+
Execute Grover's quantum search algorithm.
|
|
282
|
+
|
|
283
|
+
Finds marked states in an unstructured database with O(sqrt(N))
|
|
284
|
+
complexity instead of classical O(N).
|
|
285
|
+
"""
|
|
286
|
+
import math
|
|
287
|
+
start_time = time.perf_counter()
|
|
288
|
+
|
|
289
|
+
try:
|
|
290
|
+
# Validate marked states
|
|
291
|
+
N = 2 ** request.n_qubits
|
|
292
|
+
for state in request.marked_states:
|
|
293
|
+
if state < 0 or state >= N:
|
|
294
|
+
raise ValueError(f"Marked state {state} out of range [0, {N-1}]")
|
|
295
|
+
|
|
296
|
+
# Initialize Grover
|
|
297
|
+
grover = GroverSearch(backend=request.backend)
|
|
298
|
+
|
|
299
|
+
# Calculate optimal iterations
|
|
300
|
+
M = len(request.marked_states)
|
|
301
|
+
if M > 0 and M < N:
|
|
302
|
+
theta = math.asin(math.sqrt(M / N))
|
|
303
|
+
optimal_iterations = max(1, int(round(math.pi / (4 * theta) - 0.5)))
|
|
304
|
+
else:
|
|
305
|
+
optimal_iterations = 1
|
|
306
|
+
|
|
307
|
+
# Execute search
|
|
308
|
+
result = grover.search(
|
|
309
|
+
n_qubits=request.n_qubits,
|
|
310
|
+
marked_states=request.marked_states,
|
|
311
|
+
iterations=request.iterations,
|
|
312
|
+
shots=request.shots,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
execution_time = (time.perf_counter() - start_time) * 1000
|
|
316
|
+
|
|
317
|
+
# Calculate speedup
|
|
318
|
+
classical_ops = N / 2 # Average case classical
|
|
319
|
+
quantum_ops = result.iterations * math.sqrt(N)
|
|
320
|
+
speedup = classical_ops / quantum_ops if quantum_ops > 0 else 1
|
|
321
|
+
|
|
322
|
+
return GroverSearchResponse(
|
|
323
|
+
found_state=result.found_state,
|
|
324
|
+
found_state_decimal=int(result.found_state, 2),
|
|
325
|
+
probability=result.probability,
|
|
326
|
+
iterations_used=result.iterations,
|
|
327
|
+
optimal_iterations=optimal_iterations,
|
|
328
|
+
search_space_size=N,
|
|
329
|
+
speedup_factor=f"{speedup:.1f}x",
|
|
330
|
+
execution_time_ms=execution_time,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
except Exception as e:
|
|
334
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
# ============================================================
|
|
338
|
+
# QAOA Models & Endpoints
|
|
339
|
+
# ============================================================
|
|
340
|
+
|
|
341
|
+
class QAOARequest(BaseModel):
|
|
342
|
+
"""Request for QAOA optimization."""
|
|
343
|
+
problem_type: str = Field(default="maxcut", description="Problem type: maxcut, tsp, portfolio")
|
|
344
|
+
n_nodes: int = Field(..., ge=2, le=20, description="Number of nodes/variables")
|
|
345
|
+
edges: Optional[List[List[int]]] = Field(None, description="Graph edges for MaxCut")
|
|
346
|
+
depth: int = Field(default=2, ge=1, le=10, description="QAOA circuit depth (p)")
|
|
347
|
+
shots: int = Field(default=1024)
|
|
348
|
+
backend: str = Field(default="simulator")
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class QAOAResponse(BaseModel):
|
|
352
|
+
"""Response from QAOA optimization."""
|
|
353
|
+
best_solution: List[int]
|
|
354
|
+
best_cost: float
|
|
355
|
+
approximation_ratio: float
|
|
356
|
+
optimal_gamma: List[float]
|
|
357
|
+
optimal_beta: List[float]
|
|
358
|
+
iterations: int
|
|
359
|
+
execution_time_ms: float
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@router.post("/qaoa/optimize", response_model=QAOAResponse)
|
|
363
|
+
async def qaoa_optimize(
|
|
364
|
+
request: QAOARequest,
|
|
365
|
+
user: User = Depends(get_current_user), # Requires API key - heavy compute
|
|
366
|
+
):
|
|
367
|
+
"""
|
|
368
|
+
Run QAOA for combinatorial optimization.
|
|
369
|
+
|
|
370
|
+
Solves NP-hard problems like MaxCut, TSP, portfolio optimization
|
|
371
|
+
using variational quantum circuits.
|
|
372
|
+
"""
|
|
373
|
+
start_time = time.perf_counter()
|
|
374
|
+
|
|
375
|
+
try:
|
|
376
|
+
import random
|
|
377
|
+
|
|
378
|
+
# Generate random graph if edges not provided
|
|
379
|
+
if request.edges is None:
|
|
380
|
+
edges = []
|
|
381
|
+
for i in range(request.n_nodes):
|
|
382
|
+
for j in range(i + 1, request.n_nodes):
|
|
383
|
+
if random.random() > 0.5:
|
|
384
|
+
edges.append((i, j))
|
|
385
|
+
if not edges:
|
|
386
|
+
edges = [(0, 1)] # At least one edge
|
|
387
|
+
else:
|
|
388
|
+
edges = [tuple(e) for e in request.edges]
|
|
389
|
+
|
|
390
|
+
# Initialize QAOA
|
|
391
|
+
qaoa = QAOA(backend=request.backend, p=request.depth, shots=request.shots)
|
|
392
|
+
|
|
393
|
+
# Run MaxCut optimization
|
|
394
|
+
result = qaoa.maxcut(
|
|
395
|
+
edges=edges,
|
|
396
|
+
n_nodes=request.n_nodes,
|
|
397
|
+
max_iterations=50,
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
execution_time = (time.perf_counter() - start_time) * 1000
|
|
401
|
+
|
|
402
|
+
# Extract gamma and beta from optimal params
|
|
403
|
+
optimal_gamma = result.optimal_params[::2].tolist()
|
|
404
|
+
optimal_beta = result.optimal_params[1::2].tolist()
|
|
405
|
+
|
|
406
|
+
# Calculate approximation ratio (simplified)
|
|
407
|
+
max_possible_cut = len(edges)
|
|
408
|
+
approx_ratio = result.best_cost / max_possible_cut if max_possible_cut > 0 else 0
|
|
409
|
+
|
|
410
|
+
return QAOAResponse(
|
|
411
|
+
best_solution=[int(b) for b in result.best_solution],
|
|
412
|
+
best_cost=result.best_cost,
|
|
413
|
+
approximation_ratio=approx_ratio,
|
|
414
|
+
optimal_gamma=optimal_gamma,
|
|
415
|
+
optimal_beta=optimal_beta,
|
|
416
|
+
iterations=result.n_iterations,
|
|
417
|
+
execution_time_ms=execution_time,
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
except Exception as e:
|
|
421
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
# ============================================================
|
|
425
|
+
# VQE Models & Endpoints
|
|
426
|
+
# ============================================================
|
|
427
|
+
|
|
428
|
+
class VQERequest(BaseModel):
|
|
429
|
+
"""Request for VQE computation."""
|
|
430
|
+
molecule: str = Field(default="H2", description="Molecule: H2, LiH, H2O")
|
|
431
|
+
n_qubits: int = Field(default=4, ge=2, le=16)
|
|
432
|
+
ansatz: str = Field(default="ry_cnot", description="Ansatz type: ry_cnot, hardware_efficient")
|
|
433
|
+
max_iterations: int = Field(default=100, ge=10, le=1000)
|
|
434
|
+
shots: int = Field(default=1024)
|
|
435
|
+
backend: str = Field(default="simulator")
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
class VQEResponse(BaseModel):
|
|
439
|
+
"""Response from VQE computation."""
|
|
440
|
+
ground_state_energy: float
|
|
441
|
+
optimal_parameters: List[float]
|
|
442
|
+
energy_history: List[float]
|
|
443
|
+
iterations: int
|
|
444
|
+
converged: bool
|
|
445
|
+
chemical_accuracy: bool
|
|
446
|
+
execution_time_ms: float
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
@router.post("/vqe/compute", response_model=VQEResponse)
|
|
450
|
+
async def vqe_compute(
|
|
451
|
+
request: VQERequest,
|
|
452
|
+
user: User = Depends(get_current_user), # Requires API key - heavy compute
|
|
453
|
+
):
|
|
454
|
+
"""
|
|
455
|
+
Run VQE to find ground state energy.
|
|
456
|
+
|
|
457
|
+
Variational Quantum Eigensolver finds the lowest eigenvalue
|
|
458
|
+
of a Hamiltonian - useful for quantum chemistry.
|
|
459
|
+
"""
|
|
460
|
+
start_time = time.perf_counter()
|
|
461
|
+
|
|
462
|
+
try:
|
|
463
|
+
# Define simple molecular Hamiltonians
|
|
464
|
+
molecule_hamiltonians = {
|
|
465
|
+
"H2": [("ZZ", 0.5), ("XI", 0.3), ("IX", 0.3), ("II", -0.5)],
|
|
466
|
+
"LiH": [("ZZ", 0.4), ("ZI", 0.2), ("IZ", 0.2), ("XX", 0.1), ("YY", 0.1)],
|
|
467
|
+
"H2O": [("ZZI", 0.3), ("ZIZ", 0.3), ("IZZ", 0.3), ("XII", 0.2), ("IXI", 0.2)],
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
hamiltonian = molecule_hamiltonians.get(request.molecule, molecule_hamiltonians["H2"])
|
|
471
|
+
|
|
472
|
+
# Initialize VQE
|
|
473
|
+
vqe = VQE(
|
|
474
|
+
n_qubits=request.n_qubits,
|
|
475
|
+
backend=request.backend,
|
|
476
|
+
ansatz=request.ansatz.replace("-", "_"),
|
|
477
|
+
shots=request.shots,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
# Run VQE
|
|
481
|
+
result = vqe.run(
|
|
482
|
+
hamiltonian=hamiltonian,
|
|
483
|
+
max_iterations=request.max_iterations,
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
execution_time = (time.perf_counter() - start_time) * 1000
|
|
487
|
+
|
|
488
|
+
# Check convergence - if energy stabilized in last iterations
|
|
489
|
+
converged = len(result.energy_history) > 5 and \
|
|
490
|
+
abs(result.energy_history[-1] - result.energy_history[-5]) < 0.01
|
|
491
|
+
|
|
492
|
+
# Chemical accuracy check
|
|
493
|
+
chemical_accuracy = converged and abs(result.ground_energy) < 2.0
|
|
494
|
+
|
|
495
|
+
return VQEResponse(
|
|
496
|
+
ground_state_energy=result.ground_energy,
|
|
497
|
+
optimal_parameters=result.optimal_params.tolist(),
|
|
498
|
+
energy_history=result.energy_history,
|
|
499
|
+
iterations=result.n_iterations,
|
|
500
|
+
converged=converged,
|
|
501
|
+
chemical_accuracy=chemical_accuracy,
|
|
502
|
+
execution_time_ms=execution_time,
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
except Exception as e:
|
|
506
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
# ============================================================
|
|
510
|
+
# Algorithm Info Endpoint
|
|
511
|
+
# ============================================================
|
|
512
|
+
|
|
513
|
+
# ============================================================
|
|
514
|
+
# QSVM Models & Endpoints
|
|
515
|
+
# ============================================================
|
|
516
|
+
|
|
517
|
+
class QSVMTrainRequest(BaseModel):
|
|
518
|
+
"""Request for QSVM training."""
|
|
519
|
+
X_train: List[List[float]] = Field(..., description="Training features")
|
|
520
|
+
y_train: List[int] = Field(..., description="Training labels (-1 or 1)")
|
|
521
|
+
feature_map: str = Field(default="zz", description="Feature map: zz, pauli, amplitude")
|
|
522
|
+
reps: int = Field(default=2, ge=1, le=5)
|
|
523
|
+
shots: int = Field(default=1024)
|
|
524
|
+
backend: str = Field(default="simulator")
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
class QSVMTrainResponse(BaseModel):
|
|
528
|
+
"""Response from QSVM training."""
|
|
529
|
+
n_support_vectors: int
|
|
530
|
+
kernel_matrix_shape: List[int]
|
|
531
|
+
training_accuracy: float
|
|
532
|
+
execution_time_ms: float
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
class QSVMPredictRequest(BaseModel):
|
|
536
|
+
"""Request for QSVM prediction."""
|
|
537
|
+
X_train: List[List[float]] = Field(..., description="Training features")
|
|
538
|
+
y_train: List[int] = Field(..., description="Training labels")
|
|
539
|
+
X_test: List[List[float]] = Field(..., description="Test features")
|
|
540
|
+
feature_map: str = Field(default="zz")
|
|
541
|
+
reps: int = Field(default=2)
|
|
542
|
+
shots: int = Field(default=1024)
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
class QSVMPredictResponse(BaseModel):
|
|
546
|
+
"""Response from QSVM prediction."""
|
|
547
|
+
predictions: List[int]
|
|
548
|
+
execution_time_ms: float
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
@router.post("/qsvm/train", response_model=QSVMTrainResponse)
|
|
552
|
+
async def qsvm_train(
|
|
553
|
+
request: QSVMTrainRequest,
|
|
554
|
+
user: User = Depends(get_current_user), # Requires API key - heavy compute
|
|
555
|
+
):
|
|
556
|
+
"""
|
|
557
|
+
Train a Quantum Support Vector Machine.
|
|
558
|
+
|
|
559
|
+
Uses quantum feature maps to compute kernel in high-dimensional Hilbert space.
|
|
560
|
+
"""
|
|
561
|
+
start_time = time.perf_counter()
|
|
562
|
+
|
|
563
|
+
try:
|
|
564
|
+
X = np.array(request.X_train)
|
|
565
|
+
y = np.array(request.y_train)
|
|
566
|
+
|
|
567
|
+
qsvm = QSVM(
|
|
568
|
+
n_features=X.shape[1],
|
|
569
|
+
backend=request.backend,
|
|
570
|
+
feature_map=request.feature_map,
|
|
571
|
+
reps=request.reps,
|
|
572
|
+
shots=request.shots,
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
qsvm.fit(X, y)
|
|
576
|
+
accuracy = qsvm.score(X, y)
|
|
577
|
+
|
|
578
|
+
n_support = int(np.sum(qsvm.alphas > 1e-6))
|
|
579
|
+
|
|
580
|
+
execution_time = (time.perf_counter() - start_time) * 1000
|
|
581
|
+
|
|
582
|
+
return QSVMTrainResponse(
|
|
583
|
+
n_support_vectors=n_support,
|
|
584
|
+
kernel_matrix_shape=list(qsvm.kernel_matrix.shape),
|
|
585
|
+
training_accuracy=accuracy,
|
|
586
|
+
execution_time_ms=execution_time,
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
except Exception as e:
|
|
590
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
@router.post("/qsvm/predict", response_model=QSVMPredictResponse)
|
|
594
|
+
async def qsvm_predict(
|
|
595
|
+
request: QSVMPredictRequest,
|
|
596
|
+
user: Optional[User] = Depends(get_optional_user),
|
|
597
|
+
):
|
|
598
|
+
"""Make predictions using QSVM."""
|
|
599
|
+
start_time = time.perf_counter()
|
|
600
|
+
|
|
601
|
+
try:
|
|
602
|
+
X_train = np.array(request.X_train)
|
|
603
|
+
y_train = np.array(request.y_train)
|
|
604
|
+
X_test = np.array(request.X_test)
|
|
605
|
+
|
|
606
|
+
qsvm = QSVM(
|
|
607
|
+
n_features=X_train.shape[1],
|
|
608
|
+
feature_map=request.feature_map,
|
|
609
|
+
reps=request.reps,
|
|
610
|
+
shots=request.shots,
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
qsvm.fit(X_train, y_train)
|
|
614
|
+
predictions = qsvm.predict(X_test)
|
|
615
|
+
|
|
616
|
+
execution_time = (time.perf_counter() - start_time) * 1000
|
|
617
|
+
|
|
618
|
+
return QSVMPredictResponse(
|
|
619
|
+
predictions=predictions.tolist(),
|
|
620
|
+
execution_time_ms=execution_time,
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
except Exception as e:
|
|
624
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
# ============================================================
|
|
628
|
+
# QKD Models & Endpoints
|
|
629
|
+
# ============================================================
|
|
630
|
+
|
|
631
|
+
class QKDGenerateRequest(BaseModel):
|
|
632
|
+
"""Request for QKD key generation."""
|
|
633
|
+
key_length: int = Field(default=256, ge=32, le=4096, description="Desired key length in bits")
|
|
634
|
+
simulate_eavesdropper: bool = Field(default=False, description="Simulate eavesdropping attack")
|
|
635
|
+
backend: str = Field(default="simulator")
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
class QKDGenerateResponse(BaseModel):
|
|
639
|
+
"""Response from QKD key generation."""
|
|
640
|
+
shared_key: str
|
|
641
|
+
key_length: int
|
|
642
|
+
raw_bits_used: int
|
|
643
|
+
error_rate: float
|
|
644
|
+
is_secure: bool
|
|
645
|
+
eavesdropper_detected: bool
|
|
646
|
+
protocol: str
|
|
647
|
+
execution_time_ms: float
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
@router.post("/qkd/generate", response_model=QKDGenerateResponse)
|
|
651
|
+
async def qkd_generate(
|
|
652
|
+
request: QKDGenerateRequest,
|
|
653
|
+
user: Optional[User] = Depends(get_optional_user),
|
|
654
|
+
):
|
|
655
|
+
"""
|
|
656
|
+
Generate a quantum-secure cryptographic key using BB84 protocol.
|
|
657
|
+
|
|
658
|
+
The key is provably secure against eavesdropping due to quantum mechanics.
|
|
659
|
+
"""
|
|
660
|
+
start_time = time.perf_counter()
|
|
661
|
+
|
|
662
|
+
try:
|
|
663
|
+
from quantumflow.algorithms.cryptography import QKD
|
|
664
|
+
|
|
665
|
+
qkd = QKD(backend=request.backend)
|
|
666
|
+
|
|
667
|
+
result = qkd.generate_key(
|
|
668
|
+
key_length=request.key_length,
|
|
669
|
+
with_eavesdropper=request.simulate_eavesdropper,
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
execution_time = (time.perf_counter() - start_time) * 1000
|
|
673
|
+
|
|
674
|
+
return QKDGenerateResponse(
|
|
675
|
+
shared_key=result.shared_key,
|
|
676
|
+
key_length=result.key_length,
|
|
677
|
+
raw_bits_used=result.raw_key_length,
|
|
678
|
+
error_rate=result.error_rate,
|
|
679
|
+
is_secure=result.is_secure,
|
|
680
|
+
eavesdropper_detected=not result.is_secure and request.simulate_eavesdropper,
|
|
681
|
+
protocol="BB84",
|
|
682
|
+
execution_time_ms=execution_time,
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
except Exception as e:
|
|
686
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
# ============================================================
|
|
690
|
+
# QRNG Models & Endpoints
|
|
691
|
+
# ============================================================
|
|
692
|
+
|
|
693
|
+
class QRNGRequest(BaseModel):
|
|
694
|
+
"""Request for quantum random number generation."""
|
|
695
|
+
output_type: str = Field(default="bits", description="Output type: bits, integer, float, bytes")
|
|
696
|
+
count: int = Field(default=64, ge=1, le=1024, description="Number of bits/bytes or max value for integer")
|
|
697
|
+
min_value: int = Field(default=0, description="Min value for integer output")
|
|
698
|
+
max_value: Optional[int] = Field(None, description="Max value for integer output")
|
|
699
|
+
backend: str = Field(default="simulator")
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
class QRNGResponse(BaseModel):
|
|
703
|
+
"""Response from quantum random number generation."""
|
|
704
|
+
output_type: str
|
|
705
|
+
bits: Optional[str] = None
|
|
706
|
+
integer: Optional[int] = None
|
|
707
|
+
float_value: Optional[float] = None
|
|
708
|
+
bytes_hex: Optional[str] = None
|
|
709
|
+
n_qubits_used: int
|
|
710
|
+
entropy_bits: int
|
|
711
|
+
execution_time_ms: float
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
@router.post("/qrng/generate", response_model=QRNGResponse)
|
|
715
|
+
async def qrng_generate(
|
|
716
|
+
request: QRNGRequest,
|
|
717
|
+
user: Optional[User] = Depends(get_optional_user),
|
|
718
|
+
):
|
|
719
|
+
"""
|
|
720
|
+
Generate true random numbers using quantum mechanics.
|
|
721
|
+
|
|
722
|
+
Unlike classical PRNGs, quantum randomness is fundamentally unpredictable.
|
|
723
|
+
"""
|
|
724
|
+
start_time = time.perf_counter()
|
|
725
|
+
|
|
726
|
+
try:
|
|
727
|
+
from quantumflow.algorithms.cryptography import QRNG
|
|
728
|
+
|
|
729
|
+
qrng = QRNG(backend=request.backend)
|
|
730
|
+
|
|
731
|
+
bits = None
|
|
732
|
+
integer = None
|
|
733
|
+
float_value = None
|
|
734
|
+
bytes_hex = None
|
|
735
|
+
entropy_bits = request.count
|
|
736
|
+
|
|
737
|
+
if request.output_type == "bits":
|
|
738
|
+
bits = qrng.random_bits(request.count)
|
|
739
|
+
entropy_bits = request.count
|
|
740
|
+
elif request.output_type == "integer":
|
|
741
|
+
max_val = request.max_value if request.max_value else (2 ** request.count - 1)
|
|
742
|
+
integer = qrng.random_int(request.min_value, max_val)
|
|
743
|
+
entropy_bits = int(np.ceil(np.log2(max_val - request.min_value + 1)))
|
|
744
|
+
elif request.output_type == "float":
|
|
745
|
+
float_value = qrng.random_float()
|
|
746
|
+
entropy_bits = 53
|
|
747
|
+
elif request.output_type == "bytes":
|
|
748
|
+
random_bytes = qrng.random_bytes(request.count)
|
|
749
|
+
bytes_hex = random_bytes.hex()
|
|
750
|
+
entropy_bits = request.count * 8
|
|
751
|
+
|
|
752
|
+
execution_time = (time.perf_counter() - start_time) * 1000
|
|
753
|
+
|
|
754
|
+
return QRNGResponse(
|
|
755
|
+
output_type=request.output_type,
|
|
756
|
+
bits=bits,
|
|
757
|
+
integer=integer,
|
|
758
|
+
float_value=float_value,
|
|
759
|
+
bytes_hex=bytes_hex,
|
|
760
|
+
n_qubits_used=min(20, entropy_bits),
|
|
761
|
+
entropy_bits=entropy_bits,
|
|
762
|
+
execution_time_ms=execution_time,
|
|
763
|
+
)
|
|
764
|
+
|
|
765
|
+
except Exception as e:
|
|
766
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
# ============================================================
|
|
770
|
+
# Quantum Fourier Transform Endpoint
|
|
771
|
+
# ============================================================
|
|
772
|
+
|
|
773
|
+
class QFTRequest(BaseModel):
|
|
774
|
+
"""Request for QFT."""
|
|
775
|
+
input_data: List[float] = Field(..., description="Input data to transform")
|
|
776
|
+
n_qubits: Optional[int] = Field(None, description="Number of qubits (auto if not specified)")
|
|
777
|
+
inverse: bool = Field(default=False, description="Apply inverse QFT")
|
|
778
|
+
backend: str = Field(default="simulator")
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
class QFTResponse(BaseModel):
|
|
782
|
+
"""Response from QFT."""
|
|
783
|
+
output_coefficients: List[float]
|
|
784
|
+
n_qubits: int
|
|
785
|
+
transform_type: str
|
|
786
|
+
circuit_depth: int
|
|
787
|
+
execution_time_ms: float
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
@router.post("/qft/transform", response_model=QFTResponse)
|
|
791
|
+
async def qft_transform(
|
|
792
|
+
request: QFTRequest,
|
|
793
|
+
user: Optional[User] = Depends(get_optional_user),
|
|
794
|
+
):
|
|
795
|
+
"""
|
|
796
|
+
Apply Quantum Fourier Transform.
|
|
797
|
+
|
|
798
|
+
Exponentially faster than classical FFT for certain applications.
|
|
799
|
+
"""
|
|
800
|
+
start_time = time.perf_counter()
|
|
801
|
+
|
|
802
|
+
try:
|
|
803
|
+
from quantumflow.algorithms.compression import QFTCompression
|
|
804
|
+
|
|
805
|
+
n_qubits = request.n_qubits or int(np.ceil(np.log2(len(request.input_data))))
|
|
806
|
+
n_qubits = max(2, min(n_qubits, 16))
|
|
807
|
+
|
|
808
|
+
qft = QFTCompression(n_qubits=n_qubits)
|
|
809
|
+
|
|
810
|
+
if request.inverse:
|
|
811
|
+
result = qft.inverse_transform(request.input_data)
|
|
812
|
+
else:
|
|
813
|
+
result = qft.transform(request.input_data)
|
|
814
|
+
|
|
815
|
+
execution_time = (time.perf_counter() - start_time) * 1000
|
|
816
|
+
|
|
817
|
+
return QFTResponse(
|
|
818
|
+
output_coefficients=result.tolist() if hasattr(result, 'tolist') else list(result),
|
|
819
|
+
n_qubits=n_qubits,
|
|
820
|
+
transform_type="inverse_QFT" if request.inverse else "QFT",
|
|
821
|
+
circuit_depth=n_qubits * (n_qubits + 1) // 2,
|
|
822
|
+
execution_time_ms=execution_time,
|
|
823
|
+
)
|
|
824
|
+
|
|
825
|
+
except Exception as e:
|
|
826
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
# ============================================================
|
|
830
|
+
# Algorithm Info Endpoint
|
|
831
|
+
# ============================================================
|
|
832
|
+
|
|
833
|
+
@router.get("/info")
|
|
834
|
+
async def list_algorithms():
|
|
835
|
+
"""List all available quantum algorithms with descriptions."""
|
|
836
|
+
return {
|
|
837
|
+
"algorithms": [
|
|
838
|
+
{
|
|
839
|
+
"id": "qnn",
|
|
840
|
+
"name": "Quantum Neural Network",
|
|
841
|
+
"description": "Parameterized quantum circuits for machine learning",
|
|
842
|
+
"endpoints": ["/v1/algorithms/qnn/forward", "/v1/algorithms/qnn/train", "/v1/algorithms/qnn/predict"],
|
|
843
|
+
"use_cases": ["Binary classification", "Pattern recognition", "Feature extraction"],
|
|
844
|
+
},
|
|
845
|
+
{
|
|
846
|
+
"id": "grover",
|
|
847
|
+
"name": "Grover's Search",
|
|
848
|
+
"description": "Quantum search with quadratic speedup O(√N)",
|
|
849
|
+
"endpoints": ["/v1/algorithms/grover/search"],
|
|
850
|
+
"use_cases": ["Database search", "SAT solving", "Optimization"],
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
"id": "qaoa",
|
|
854
|
+
"name": "QAOA",
|
|
855
|
+
"description": "Quantum Approximate Optimization Algorithm",
|
|
856
|
+
"endpoints": ["/v1/algorithms/qaoa/optimize"],
|
|
857
|
+
"use_cases": ["MaxCut", "Portfolio optimization", "Scheduling"],
|
|
858
|
+
},
|
|
859
|
+
{
|
|
860
|
+
"id": "vqe",
|
|
861
|
+
"name": "VQE",
|
|
862
|
+
"description": "Variational Quantum Eigensolver",
|
|
863
|
+
"endpoints": ["/v1/algorithms/vqe/compute"],
|
|
864
|
+
"use_cases": ["Molecular simulation", "Ground state energy", "Quantum chemistry"],
|
|
865
|
+
},
|
|
866
|
+
{
|
|
867
|
+
"id": "qsvm",
|
|
868
|
+
"name": "Quantum SVM",
|
|
869
|
+
"description": "Quantum kernel-based classification",
|
|
870
|
+
"endpoints": ["/v1/algorithms/qsvm/train", "/v1/algorithms/qsvm/predict"],
|
|
871
|
+
"use_cases": ["Classification", "Pattern recognition", "Anomaly detection"],
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
"id": "qkd",
|
|
875
|
+
"name": "Quantum Key Distribution",
|
|
876
|
+
"description": "BB84 protocol for quantum-secure key exchange",
|
|
877
|
+
"endpoints": ["/v1/algorithms/qkd/generate"],
|
|
878
|
+
"use_cases": ["Secure communication", "Cryptography", "Key exchange"],
|
|
879
|
+
},
|
|
880
|
+
{
|
|
881
|
+
"id": "qrng",
|
|
882
|
+
"name": "Quantum Random Number Generator",
|
|
883
|
+
"description": "True random numbers from quantum measurements",
|
|
884
|
+
"endpoints": ["/v1/algorithms/qrng/generate"],
|
|
885
|
+
"use_cases": ["Cryptography", "Simulations", "Gaming"],
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
"id": "qft",
|
|
889
|
+
"name": "Quantum Fourier Transform",
|
|
890
|
+
"description": "Exponentially faster Fourier transform",
|
|
891
|
+
"endpoints": ["/v1/algorithms/qft/transform"],
|
|
892
|
+
"use_cases": ["Signal processing", "Phase estimation", "Quantum algorithms"],
|
|
893
|
+
},
|
|
894
|
+
]
|
|
895
|
+
}
|