quantumflow-sdk 0.2.1__py3-none-any.whl → 0.4.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.
@@ -0,0 +1,1029 @@
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
+ # SES VQE - Single Excitation Subspace (arXiv:2601.00247)
511
+ # ============================================================
512
+
513
+ class SESVQERequest(BaseModel):
514
+ """Request for Single Excitation Subspace VQE.
515
+
516
+ Uses logarithmic qubit encoding from arXiv:2601.00247:
517
+ - N lattice sites encoded in log₂(N) qubits
518
+ - Exponential reduction in quantum resources
519
+ - Ideal for tight-binding Hamiltonians
520
+ """
521
+ n_sites: int = Field(..., ge=2, le=1024, description="Number of lattice sites")
522
+ on_site_energies: List[float] = Field(
523
+ ...,
524
+ description="Diagonal energies [h_11, h_22, ...] for each site"
525
+ )
526
+ hopping_terms: List[List] = Field(
527
+ ...,
528
+ description="Hopping terms [[i, j, t_ij], ...] between sites"
529
+ )
530
+ max_iterations: int = Field(default=100, ge=10, le=1000)
531
+ backend: str = Field(default="simulator")
532
+ shots: int = Field(default=1024, ge=100, le=10000)
533
+
534
+
535
+ class SESVQEResponse(BaseModel):
536
+ """Response from SES VQE computation."""
537
+ ground_energy: float
538
+ n_sites: int
539
+ n_qubits_used: int = Field(description="Logarithmic encoding: ⌈log₂(n_sites)⌉")
540
+ qubit_reduction_percent: float
541
+ iterations: int
542
+ energy_history: List[float]
543
+ volumetric_cost: int
544
+ original_volumetric_cost: int
545
+ speedup: float
546
+ execution_time_ms: float
547
+ ansatz_type: str = "ses_binary"
548
+
549
+
550
+ @router.post("/ses/solve", response_model=SESVQEResponse)
551
+ async def ses_vqe_solve(
552
+ request: SESVQERequest,
553
+ user: User = Depends(get_current_user), # Requires API key
554
+ ):
555
+ """
556
+ Run VQE with Single Excitation Subspace (SES) ansatz.
557
+
558
+ Based on arXiv:2601.00247 - achieves exponential reduction in
559
+ quantum resources for tight-binding Hamiltonians:
560
+
561
+ - **Qubit reduction**: N sites → log₂(N) qubits
562
+ - **Volumetric speedup**: O(N²) → O((log N)³)
563
+
564
+ Use cases:
565
+ - Tight-binding band structure calculations
566
+ - Solid-state physics simulations
567
+ - Material property prediction
568
+
569
+ Example (8-site chain):
570
+ ```json
571
+ {
572
+ "n_sites": 8,
573
+ "on_site_energies": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
574
+ "hopping_terms": [[0,1,-1.0], [1,2,-1.0], [2,3,-1.0], ...]
575
+ }
576
+ ```
577
+ Uses only 3 qubits instead of 8!
578
+ """
579
+ import math
580
+ from quantumflow.algorithms.machine_learning.vqe import run_ses_vqe, calculate_volumetric_cost
581
+
582
+ start_time = time.perf_counter()
583
+
584
+ try:
585
+ # Validate input
586
+ if len(request.on_site_energies) != request.n_sites:
587
+ raise ValueError(
588
+ f"on_site_energies length ({len(request.on_site_energies)}) "
589
+ f"must match n_sites ({request.n_sites})"
590
+ )
591
+
592
+ # Convert hopping terms to tuples
593
+ hopping_tuples = [
594
+ (int(h[0]), int(h[1]), complex(h[2]) if len(h) > 2 else complex(-1.0))
595
+ for h in request.hopping_terms
596
+ ]
597
+
598
+ # Run SES VQE
599
+ result = run_ses_vqe(
600
+ n_sites=request.n_sites,
601
+ on_site_energies=request.on_site_energies,
602
+ hopping_terms=hopping_tuples,
603
+ max_iterations=request.max_iterations,
604
+ backend=request.backend,
605
+ )
606
+
607
+ execution_time = (time.perf_counter() - start_time) * 1000
608
+
609
+ # Calculate efficiency metrics
610
+ n_qubits = math.ceil(math.log2(request.n_sites))
611
+ costs = calculate_volumetric_cost(request.n_sites, "ses_binary")
612
+
613
+ return SESVQEResponse(
614
+ ground_energy=result.ground_energy,
615
+ n_sites=request.n_sites,
616
+ n_qubits_used=n_qubits,
617
+ qubit_reduction_percent=(1 - n_qubits / request.n_sites) * 100,
618
+ iterations=result.n_iterations,
619
+ energy_history=result.energy_history,
620
+ volumetric_cost=costs["volumetric_cost"],
621
+ original_volumetric_cost=costs["original_volumetric_cost"],
622
+ speedup=costs["speedup"],
623
+ execution_time_ms=execution_time,
624
+ ansatz_type="ses_binary",
625
+ )
626
+
627
+ except Exception as e:
628
+ raise HTTPException(status_code=400, detail=str(e))
629
+
630
+
631
+ # ============================================================
632
+ # Algorithm Info Endpoint
633
+ # ============================================================
634
+
635
+ # ============================================================
636
+ # QSVM Models & Endpoints
637
+ # ============================================================
638
+
639
+ class QSVMTrainRequest(BaseModel):
640
+ """Request for QSVM training."""
641
+ X_train: List[List[float]] = Field(..., description="Training features")
642
+ y_train: List[int] = Field(..., description="Training labels (-1 or 1)")
643
+ feature_map: str = Field(default="zz", description="Feature map: zz, pauli, amplitude")
644
+ reps: int = Field(default=2, ge=1, le=5)
645
+ shots: int = Field(default=1024)
646
+ backend: str = Field(default="simulator")
647
+
648
+
649
+ class QSVMTrainResponse(BaseModel):
650
+ """Response from QSVM training."""
651
+ n_support_vectors: int
652
+ kernel_matrix_shape: List[int]
653
+ training_accuracy: float
654
+ execution_time_ms: float
655
+
656
+
657
+ class QSVMPredictRequest(BaseModel):
658
+ """Request for QSVM prediction."""
659
+ X_train: List[List[float]] = Field(..., description="Training features")
660
+ y_train: List[int] = Field(..., description="Training labels")
661
+ X_test: List[List[float]] = Field(..., description="Test features")
662
+ feature_map: str = Field(default="zz")
663
+ reps: int = Field(default=2)
664
+ shots: int = Field(default=1024)
665
+
666
+
667
+ class QSVMPredictResponse(BaseModel):
668
+ """Response from QSVM prediction."""
669
+ predictions: List[int]
670
+ execution_time_ms: float
671
+
672
+
673
+ @router.post("/qsvm/train", response_model=QSVMTrainResponse)
674
+ async def qsvm_train(
675
+ request: QSVMTrainRequest,
676
+ user: User = Depends(get_current_user), # Requires API key - heavy compute
677
+ ):
678
+ """
679
+ Train a Quantum Support Vector Machine.
680
+
681
+ Uses quantum feature maps to compute kernel in high-dimensional Hilbert space.
682
+ """
683
+ start_time = time.perf_counter()
684
+
685
+ try:
686
+ X = np.array(request.X_train)
687
+ y = np.array(request.y_train)
688
+
689
+ qsvm = QSVM(
690
+ n_features=X.shape[1],
691
+ backend=request.backend,
692
+ feature_map=request.feature_map,
693
+ reps=request.reps,
694
+ shots=request.shots,
695
+ )
696
+
697
+ qsvm.fit(X, y)
698
+ accuracy = qsvm.score(X, y)
699
+
700
+ n_support = int(np.sum(qsvm.alphas > 1e-6))
701
+
702
+ execution_time = (time.perf_counter() - start_time) * 1000
703
+
704
+ return QSVMTrainResponse(
705
+ n_support_vectors=n_support,
706
+ kernel_matrix_shape=list(qsvm.kernel_matrix.shape),
707
+ training_accuracy=accuracy,
708
+ execution_time_ms=execution_time,
709
+ )
710
+
711
+ except Exception as e:
712
+ raise HTTPException(status_code=400, detail=str(e))
713
+
714
+
715
+ @router.post("/qsvm/predict", response_model=QSVMPredictResponse)
716
+ async def qsvm_predict(
717
+ request: QSVMPredictRequest,
718
+ user: Optional[User] = Depends(get_optional_user),
719
+ ):
720
+ """Make predictions using QSVM."""
721
+ start_time = time.perf_counter()
722
+
723
+ try:
724
+ X_train = np.array(request.X_train)
725
+ y_train = np.array(request.y_train)
726
+ X_test = np.array(request.X_test)
727
+
728
+ qsvm = QSVM(
729
+ n_features=X_train.shape[1],
730
+ feature_map=request.feature_map,
731
+ reps=request.reps,
732
+ shots=request.shots,
733
+ )
734
+
735
+ qsvm.fit(X_train, y_train)
736
+ predictions = qsvm.predict(X_test)
737
+
738
+ execution_time = (time.perf_counter() - start_time) * 1000
739
+
740
+ return QSVMPredictResponse(
741
+ predictions=predictions.tolist(),
742
+ execution_time_ms=execution_time,
743
+ )
744
+
745
+ except Exception as e:
746
+ raise HTTPException(status_code=400, detail=str(e))
747
+
748
+
749
+ # ============================================================
750
+ # QKD Models & Endpoints
751
+ # ============================================================
752
+
753
+ class QKDGenerateRequest(BaseModel):
754
+ """Request for QKD key generation."""
755
+ key_length: int = Field(default=256, ge=32, le=4096, description="Desired key length in bits")
756
+ simulate_eavesdropper: bool = Field(default=False, description="Simulate eavesdropping attack")
757
+ backend: str = Field(default="simulator")
758
+
759
+
760
+ class QKDGenerateResponse(BaseModel):
761
+ """Response from QKD key generation."""
762
+ shared_key: str
763
+ key_length: int
764
+ raw_bits_used: int
765
+ error_rate: float
766
+ is_secure: bool
767
+ eavesdropper_detected: bool
768
+ protocol: str
769
+ execution_time_ms: float
770
+
771
+
772
+ @router.post("/qkd/generate", response_model=QKDGenerateResponse)
773
+ async def qkd_generate(
774
+ request: QKDGenerateRequest,
775
+ user: Optional[User] = Depends(get_optional_user),
776
+ ):
777
+ """
778
+ Generate a quantum-secure cryptographic key using BB84 protocol.
779
+
780
+ The key is provably secure against eavesdropping due to quantum mechanics.
781
+ """
782
+ start_time = time.perf_counter()
783
+
784
+ try:
785
+ from quantumflow.algorithms.cryptography import QKD
786
+
787
+ qkd = QKD(backend=request.backend)
788
+
789
+ result = qkd.generate_key(
790
+ key_length=request.key_length,
791
+ with_eavesdropper=request.simulate_eavesdropper,
792
+ )
793
+
794
+ execution_time = (time.perf_counter() - start_time) * 1000
795
+
796
+ return QKDGenerateResponse(
797
+ shared_key=result.shared_key,
798
+ key_length=result.key_length,
799
+ raw_bits_used=result.raw_key_length,
800
+ error_rate=result.error_rate,
801
+ is_secure=result.is_secure,
802
+ eavesdropper_detected=not result.is_secure and request.simulate_eavesdropper,
803
+ protocol="BB84",
804
+ execution_time_ms=execution_time,
805
+ )
806
+
807
+ except Exception as e:
808
+ raise HTTPException(status_code=400, detail=str(e))
809
+
810
+
811
+ # ============================================================
812
+ # QRNG Models & Endpoints
813
+ # ============================================================
814
+
815
+ class QRNGRequest(BaseModel):
816
+ """Request for quantum random number generation."""
817
+ output_type: str = Field(default="bits", description="Output type: bits, integer, float, bytes")
818
+ count: int = Field(default=64, ge=1, le=1024, description="Number of bits/bytes or max value for integer")
819
+ min_value: int = Field(default=0, description="Min value for integer output")
820
+ max_value: Optional[int] = Field(None, description="Max value for integer output")
821
+ backend: str = Field(default="simulator")
822
+
823
+
824
+ class QRNGResponse(BaseModel):
825
+ """Response from quantum random number generation."""
826
+ output_type: str
827
+ bits: Optional[str] = None
828
+ integer: Optional[int] = None
829
+ float_value: Optional[float] = None
830
+ bytes_hex: Optional[str] = None
831
+ n_qubits_used: int
832
+ entropy_bits: int
833
+ execution_time_ms: float
834
+
835
+
836
+ @router.post("/qrng/generate", response_model=QRNGResponse)
837
+ async def qrng_generate(
838
+ request: QRNGRequest,
839
+ user: Optional[User] = Depends(get_optional_user),
840
+ ):
841
+ """
842
+ Generate true random numbers using quantum mechanics.
843
+
844
+ Unlike classical PRNGs, quantum randomness is fundamentally unpredictable.
845
+ """
846
+ start_time = time.perf_counter()
847
+
848
+ try:
849
+ from quantumflow.algorithms.cryptography import QRNG
850
+
851
+ qrng = QRNG(backend=request.backend)
852
+
853
+ bits = None
854
+ integer = None
855
+ float_value = None
856
+ bytes_hex = None
857
+ entropy_bits = request.count
858
+
859
+ if request.output_type == "bits":
860
+ bits = qrng.random_bits(request.count)
861
+ entropy_bits = request.count
862
+ elif request.output_type == "integer":
863
+ max_val = request.max_value if request.max_value else (2 ** request.count - 1)
864
+ integer = qrng.random_int(request.min_value, max_val)
865
+ entropy_bits = int(np.ceil(np.log2(max_val - request.min_value + 1)))
866
+ elif request.output_type == "float":
867
+ float_value = qrng.random_float()
868
+ entropy_bits = 53
869
+ elif request.output_type == "bytes":
870
+ random_bytes = qrng.random_bytes(request.count)
871
+ bytes_hex = random_bytes.hex()
872
+ entropy_bits = request.count * 8
873
+
874
+ execution_time = (time.perf_counter() - start_time) * 1000
875
+
876
+ return QRNGResponse(
877
+ output_type=request.output_type,
878
+ bits=bits,
879
+ integer=integer,
880
+ float_value=float_value,
881
+ bytes_hex=bytes_hex,
882
+ n_qubits_used=min(20, entropy_bits),
883
+ entropy_bits=entropy_bits,
884
+ execution_time_ms=execution_time,
885
+ )
886
+
887
+ except Exception as e:
888
+ raise HTTPException(status_code=400, detail=str(e))
889
+
890
+
891
+ # ============================================================
892
+ # Quantum Fourier Transform Endpoint
893
+ # ============================================================
894
+
895
+ class QFTRequest(BaseModel):
896
+ """Request for QFT."""
897
+ input_data: List[float] = Field(..., description="Input data to transform")
898
+ n_qubits: Optional[int] = Field(None, description="Number of qubits (auto if not specified)")
899
+ inverse: bool = Field(default=False, description="Apply inverse QFT")
900
+ backend: str = Field(default="simulator")
901
+
902
+
903
+ class QFTResponse(BaseModel):
904
+ """Response from QFT."""
905
+ output_coefficients: List[float]
906
+ n_qubits: int
907
+ transform_type: str
908
+ circuit_depth: int
909
+ execution_time_ms: float
910
+
911
+
912
+ @router.post("/qft/transform", response_model=QFTResponse)
913
+ async def qft_transform(
914
+ request: QFTRequest,
915
+ user: Optional[User] = Depends(get_optional_user),
916
+ ):
917
+ """
918
+ Apply Quantum Fourier Transform.
919
+
920
+ Exponentially faster than classical FFT for certain applications.
921
+ """
922
+ start_time = time.perf_counter()
923
+
924
+ try:
925
+ from quantumflow.algorithms.compression import QFTCompression
926
+
927
+ n_qubits = request.n_qubits or int(np.ceil(np.log2(len(request.input_data))))
928
+ n_qubits = max(2, min(n_qubits, 16))
929
+
930
+ qft = QFTCompression(n_qubits=n_qubits)
931
+
932
+ if request.inverse:
933
+ result = qft.inverse_transform(request.input_data)
934
+ else:
935
+ result = qft.transform(request.input_data)
936
+
937
+ execution_time = (time.perf_counter() - start_time) * 1000
938
+
939
+ return QFTResponse(
940
+ output_coefficients=result.tolist() if hasattr(result, 'tolist') else list(result),
941
+ n_qubits=n_qubits,
942
+ transform_type="inverse_QFT" if request.inverse else "QFT",
943
+ circuit_depth=n_qubits * (n_qubits + 1) // 2,
944
+ execution_time_ms=execution_time,
945
+ )
946
+
947
+ except Exception as e:
948
+ raise HTTPException(status_code=400, detail=str(e))
949
+
950
+
951
+ # ============================================================
952
+ # Algorithm Info Endpoint
953
+ # ============================================================
954
+
955
+ @router.get("/info")
956
+ async def list_algorithms():
957
+ """List all available quantum algorithms with descriptions."""
958
+ return {
959
+ "algorithms": [
960
+ {
961
+ "id": "qnn",
962
+ "name": "Quantum Neural Network",
963
+ "description": "Parameterized quantum circuits for machine learning",
964
+ "endpoints": ["/v1/algorithms/qnn/forward", "/v1/algorithms/qnn/train", "/v1/algorithms/qnn/predict"],
965
+ "use_cases": ["Binary classification", "Pattern recognition", "Feature extraction"],
966
+ },
967
+ {
968
+ "id": "grover",
969
+ "name": "Grover's Search",
970
+ "description": "Quantum search with quadratic speedup O(√N)",
971
+ "endpoints": ["/v1/algorithms/grover/search"],
972
+ "use_cases": ["Database search", "SAT solving", "Optimization"],
973
+ },
974
+ {
975
+ "id": "qaoa",
976
+ "name": "QAOA",
977
+ "description": "Quantum Approximate Optimization Algorithm",
978
+ "endpoints": ["/v1/algorithms/qaoa/optimize"],
979
+ "use_cases": ["MaxCut", "Portfolio optimization", "Scheduling"],
980
+ },
981
+ {
982
+ "id": "vqe",
983
+ "name": "VQE",
984
+ "description": "Variational Quantum Eigensolver",
985
+ "endpoints": ["/v1/algorithms/vqe/compute"],
986
+ "use_cases": ["Molecular simulation", "Ground state energy", "Quantum chemistry"],
987
+ },
988
+ {
989
+ "id": "ses",
990
+ "name": "SES VQE",
991
+ "description": "Single Excitation Subspace VQE with logarithmic qubit encoding (arXiv:2601.00247)",
992
+ "endpoints": ["/v1/algorithms/ses/solve"],
993
+ "use_cases": ["Tight-binding Hamiltonians", "Band structure", "Solid-state physics"],
994
+ "features": {
995
+ "qubit_reduction": "N sites → log₂(N) qubits",
996
+ "speedup": "O(N²) → O((log N)³)",
997
+ "ansatz": "Binary-encoded SES with Gray-code measurement"
998
+ }
999
+ },
1000
+ {
1001
+ "id": "qsvm",
1002
+ "name": "Quantum SVM",
1003
+ "description": "Quantum kernel-based classification",
1004
+ "endpoints": ["/v1/algorithms/qsvm/train", "/v1/algorithms/qsvm/predict"],
1005
+ "use_cases": ["Classification", "Pattern recognition", "Anomaly detection"],
1006
+ },
1007
+ {
1008
+ "id": "qkd",
1009
+ "name": "Quantum Key Distribution",
1010
+ "description": "BB84 protocol for quantum-secure key exchange",
1011
+ "endpoints": ["/v1/algorithms/qkd/generate"],
1012
+ "use_cases": ["Secure communication", "Cryptography", "Key exchange"],
1013
+ },
1014
+ {
1015
+ "id": "qrng",
1016
+ "name": "Quantum Random Number Generator",
1017
+ "description": "True random numbers from quantum measurements",
1018
+ "endpoints": ["/v1/algorithms/qrng/generate"],
1019
+ "use_cases": ["Cryptography", "Simulations", "Gaming"],
1020
+ },
1021
+ {
1022
+ "id": "qft",
1023
+ "name": "Quantum Fourier Transform",
1024
+ "description": "Exponentially faster Fourier transform",
1025
+ "endpoints": ["/v1/algorithms/qft/transform"],
1026
+ "use_cases": ["Signal processing", "Phase estimation", "Quantum algorithms"],
1027
+ },
1028
+ ]
1029
+ }