quantumflow-sdk 0.1.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/__init__.py +1 -0
- api/auth.py +208 -0
- api/main.py +403 -0
- api/models.py +137 -0
- api/routes/__init__.py +1 -0
- api/routes/auth_routes.py +234 -0
- api/routes/teleport_routes.py +415 -0
- db/__init__.py +15 -0
- db/crud.py +319 -0
- db/database.py +93 -0
- db/models.py +197 -0
- quantumflow/__init__.py +47 -0
- quantumflow/algorithms/__init__.py +48 -0
- quantumflow/algorithms/compression/__init__.py +7 -0
- quantumflow/algorithms/compression/amplitude_amplification.py +189 -0
- quantumflow/algorithms/compression/qft_compression.py +133 -0
- quantumflow/algorithms/compression/token_compression.py +261 -0
- quantumflow/algorithms/cryptography/__init__.py +6 -0
- quantumflow/algorithms/cryptography/qkd.py +205 -0
- quantumflow/algorithms/cryptography/qrng.py +231 -0
- quantumflow/algorithms/machine_learning/__init__.py +7 -0
- quantumflow/algorithms/machine_learning/qnn.py +276 -0
- quantumflow/algorithms/machine_learning/qsvm.py +249 -0
- quantumflow/algorithms/machine_learning/vqe.py +229 -0
- quantumflow/algorithms/optimization/__init__.py +7 -0
- quantumflow/algorithms/optimization/grover.py +223 -0
- quantumflow/algorithms/optimization/qaoa.py +251 -0
- quantumflow/algorithms/optimization/quantum_annealing.py +237 -0
- quantumflow/algorithms/utility/__init__.py +6 -0
- quantumflow/algorithms/utility/circuit_optimizer.py +194 -0
- quantumflow/algorithms/utility/error_correction.py +330 -0
- quantumflow/api/__init__.py +1 -0
- quantumflow/api/routes/__init__.py +4 -0
- quantumflow/api/routes/billing_routes.py +520 -0
- quantumflow/backends/__init__.py +33 -0
- quantumflow/backends/base_backend.py +184 -0
- quantumflow/backends/braket_backend.py +345 -0
- quantumflow/backends/ibm_backend.py +112 -0
- quantumflow/backends/simulator_backend.py +86 -0
- quantumflow/billing/__init__.py +25 -0
- quantumflow/billing/models.py +126 -0
- quantumflow/billing/stripe_service.py +619 -0
- quantumflow/core/__init__.py +12 -0
- quantumflow/core/entanglement.py +164 -0
- quantumflow/core/memory.py +147 -0
- quantumflow/core/quantum_backprop.py +394 -0
- quantumflow/core/quantum_compressor.py +309 -0
- quantumflow/core/teleportation.py +386 -0
- quantumflow/integrations/__init__.py +107 -0
- quantumflow/integrations/autogen_tools.py +501 -0
- quantumflow/integrations/crewai_agents.py +425 -0
- quantumflow/integrations/crewai_tools.py +407 -0
- quantumflow/integrations/langchain_memory.py +385 -0
- quantumflow/integrations/langchain_tools.py +366 -0
- quantumflow/integrations/mcp_server.py +575 -0
- quantumflow_sdk-0.1.0.dist-info/METADATA +190 -0
- quantumflow_sdk-0.1.0.dist-info/RECORD +60 -0
- quantumflow_sdk-0.1.0.dist-info/WHEEL +5 -0
- quantumflow_sdk-0.1.0.dist-info/entry_points.txt +2 -0
- quantumflow_sdk-0.1.0.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Quantum Support Vector Machine (QSVM).
|
|
3
|
+
|
|
4
|
+
Uses quantum feature maps for kernel-based classification.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Optional
|
|
9
|
+
import numpy as np
|
|
10
|
+
from qiskit import QuantumCircuit
|
|
11
|
+
|
|
12
|
+
from quantumflow.backends.base_backend import QuantumBackend, get_backend, BackendType
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class QSVMResult:
|
|
17
|
+
"""Result from QSVM classification."""
|
|
18
|
+
|
|
19
|
+
predictions: np.ndarray
|
|
20
|
+
kernel_matrix: Optional[np.ndarray]
|
|
21
|
+
support_vectors: Optional[np.ndarray]
|
|
22
|
+
accuracy: float
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class QSVM:
|
|
26
|
+
"""
|
|
27
|
+
Quantum Support Vector Machine.
|
|
28
|
+
|
|
29
|
+
Uses quantum circuits as feature maps to compute kernel values,
|
|
30
|
+
enabling classification in high-dimensional Hilbert space.
|
|
31
|
+
|
|
32
|
+
Feature maps:
|
|
33
|
+
- ZZ Feature Map: Entangling features with ZZ gates
|
|
34
|
+
- Pauli Feature Map: General Pauli rotations
|
|
35
|
+
- Amplitude Encoding: Direct amplitude encoding
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
>>> qsvm = QSVM(n_features=2)
|
|
39
|
+
>>> qsvm.fit(X_train, y_train)
|
|
40
|
+
>>> predictions = qsvm.predict(X_test)
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
n_features: int,
|
|
46
|
+
backend: BackendType | str = BackendType.AUTO,
|
|
47
|
+
feature_map: str = "zz",
|
|
48
|
+
reps: int = 2,
|
|
49
|
+
shots: int = 1024,
|
|
50
|
+
):
|
|
51
|
+
"""
|
|
52
|
+
Initialize QSVM.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
n_features: Number of input features
|
|
56
|
+
backend: Quantum backend
|
|
57
|
+
feature_map: Type of feature map ('zz', 'pauli', 'amplitude')
|
|
58
|
+
reps: Number of feature map repetitions
|
|
59
|
+
shots: Measurement shots for kernel estimation
|
|
60
|
+
"""
|
|
61
|
+
self.n_features = n_features
|
|
62
|
+
self.n_qubits = n_features
|
|
63
|
+
self.backend = get_backend(backend)
|
|
64
|
+
self.feature_map_type = feature_map
|
|
65
|
+
self.reps = reps
|
|
66
|
+
self.shots = shots
|
|
67
|
+
self._connected = False
|
|
68
|
+
|
|
69
|
+
# Training data
|
|
70
|
+
self.X_train: Optional[np.ndarray] = None
|
|
71
|
+
self.y_train: Optional[np.ndarray] = None
|
|
72
|
+
self.alphas: Optional[np.ndarray] = None
|
|
73
|
+
self.kernel_matrix: Optional[np.ndarray] = None
|
|
74
|
+
|
|
75
|
+
def _ensure_connected(self):
|
|
76
|
+
if not self._connected:
|
|
77
|
+
self.backend.connect()
|
|
78
|
+
self._connected = True
|
|
79
|
+
|
|
80
|
+
def fit(self, X: np.ndarray, y: np.ndarray) -> 'QSVM':
|
|
81
|
+
"""
|
|
82
|
+
Fit the QSVM classifier.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
X: Training features (n_samples, n_features)
|
|
86
|
+
y: Training labels (n_samples,) with values in {-1, 1}
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
self
|
|
90
|
+
"""
|
|
91
|
+
self._ensure_connected()
|
|
92
|
+
|
|
93
|
+
self.X_train = X
|
|
94
|
+
self.y_train = y
|
|
95
|
+
|
|
96
|
+
# Compute kernel matrix
|
|
97
|
+
n_samples = len(X)
|
|
98
|
+
self.kernel_matrix = np.zeros((n_samples, n_samples))
|
|
99
|
+
|
|
100
|
+
for i in range(n_samples):
|
|
101
|
+
for j in range(i, n_samples):
|
|
102
|
+
k = self._compute_kernel(X[i], X[j])
|
|
103
|
+
self.kernel_matrix[i, j] = k
|
|
104
|
+
self.kernel_matrix[j, i] = k
|
|
105
|
+
|
|
106
|
+
# Solve dual SVM problem (simplified)
|
|
107
|
+
self.alphas = self._solve_dual(self.kernel_matrix, y)
|
|
108
|
+
|
|
109
|
+
return self
|
|
110
|
+
|
|
111
|
+
def predict(self, X: np.ndarray) -> np.ndarray:
|
|
112
|
+
"""
|
|
113
|
+
Predict class labels.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
X: Test features (n_samples, n_features)
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Predicted labels
|
|
120
|
+
"""
|
|
121
|
+
if self.X_train is None:
|
|
122
|
+
raise ValueError("Model not fitted. Call fit() first.")
|
|
123
|
+
|
|
124
|
+
self._ensure_connected()
|
|
125
|
+
|
|
126
|
+
predictions = []
|
|
127
|
+
|
|
128
|
+
for x in X:
|
|
129
|
+
# Compute kernel with all training points
|
|
130
|
+
decision = 0.0
|
|
131
|
+
for i, (x_train, y_train, alpha) in enumerate(
|
|
132
|
+
zip(self.X_train, self.y_train, self.alphas)
|
|
133
|
+
):
|
|
134
|
+
if alpha > 1e-6: # Support vector
|
|
135
|
+
k = self._compute_kernel(x, x_train)
|
|
136
|
+
decision += alpha * y_train * k
|
|
137
|
+
|
|
138
|
+
predictions.append(1 if decision >= 0 else -1)
|
|
139
|
+
|
|
140
|
+
return np.array(predictions)
|
|
141
|
+
|
|
142
|
+
def score(self, X: np.ndarray, y: np.ndarray) -> float:
|
|
143
|
+
"""Compute classification accuracy."""
|
|
144
|
+
predictions = self.predict(X)
|
|
145
|
+
return np.mean(predictions == y)
|
|
146
|
+
|
|
147
|
+
def _compute_kernel(self, x1: np.ndarray, x2: np.ndarray) -> float:
|
|
148
|
+
"""Compute quantum kernel between two data points."""
|
|
149
|
+
# Build circuit: U(x1)† U(x2)
|
|
150
|
+
circuit = QuantumCircuit(self.n_qubits, self.n_qubits)
|
|
151
|
+
|
|
152
|
+
# Apply feature map for x2
|
|
153
|
+
self._apply_feature_map(circuit, x2)
|
|
154
|
+
|
|
155
|
+
# Apply inverse feature map for x1
|
|
156
|
+
self._apply_feature_map_inverse(circuit, x1)
|
|
157
|
+
|
|
158
|
+
# Measure
|
|
159
|
+
circuit.measure(range(self.n_qubits), range(self.n_qubits))
|
|
160
|
+
|
|
161
|
+
# Execute
|
|
162
|
+
result = self.backend.execute(circuit, shots=self.shots)
|
|
163
|
+
|
|
164
|
+
# Kernel = probability of measuring |0...0⟩
|
|
165
|
+
zero_state = '0' * self.n_qubits
|
|
166
|
+
zero_count = result.counts.get(zero_state, 0)
|
|
167
|
+
|
|
168
|
+
return zero_count / self.shots
|
|
169
|
+
|
|
170
|
+
def _apply_feature_map(self, circuit: QuantumCircuit, x: np.ndarray):
|
|
171
|
+
"""Apply feature map encoding data point x."""
|
|
172
|
+
for rep in range(self.reps):
|
|
173
|
+
# Hadamard layer
|
|
174
|
+
circuit.h(range(self.n_qubits))
|
|
175
|
+
|
|
176
|
+
# Feature encoding
|
|
177
|
+
if self.feature_map_type == "zz":
|
|
178
|
+
self._zz_feature_map(circuit, x)
|
|
179
|
+
elif self.feature_map_type == "pauli":
|
|
180
|
+
self._pauli_feature_map(circuit, x)
|
|
181
|
+
else:
|
|
182
|
+
self._amplitude_feature_map(circuit, x)
|
|
183
|
+
|
|
184
|
+
def _apply_feature_map_inverse(self, circuit: QuantumCircuit, x: np.ndarray):
|
|
185
|
+
"""Apply inverse feature map."""
|
|
186
|
+
for rep in range(self.reps - 1, -1, -1):
|
|
187
|
+
# Inverse feature encoding
|
|
188
|
+
if self.feature_map_type == "zz":
|
|
189
|
+
self._zz_feature_map(circuit, -x)
|
|
190
|
+
elif self.feature_map_type == "pauli":
|
|
191
|
+
self._pauli_feature_map(circuit, -x)
|
|
192
|
+
else:
|
|
193
|
+
self._amplitude_feature_map(circuit, -x)
|
|
194
|
+
|
|
195
|
+
# Hadamard layer
|
|
196
|
+
circuit.h(range(self.n_qubits))
|
|
197
|
+
|
|
198
|
+
def _zz_feature_map(self, circuit: QuantumCircuit, x: np.ndarray):
|
|
199
|
+
"""ZZ feature map."""
|
|
200
|
+
# Single qubit rotations
|
|
201
|
+
for i in range(min(len(x), self.n_qubits)):
|
|
202
|
+
circuit.rz(2 * x[i], i)
|
|
203
|
+
|
|
204
|
+
# Two-qubit ZZ interactions
|
|
205
|
+
for i in range(self.n_qubits - 1):
|
|
206
|
+
j = i + 1
|
|
207
|
+
if i < len(x) and j < len(x):
|
|
208
|
+
circuit.cx(i, j)
|
|
209
|
+
circuit.rz(2 * (np.pi - x[i]) * (np.pi - x[j]), j)
|
|
210
|
+
circuit.cx(i, j)
|
|
211
|
+
|
|
212
|
+
def _pauli_feature_map(self, circuit: QuantumCircuit, x: np.ndarray):
|
|
213
|
+
"""Pauli feature map with X, Y, Z rotations."""
|
|
214
|
+
for i in range(min(len(x), self.n_qubits)):
|
|
215
|
+
circuit.rx(x[i], i)
|
|
216
|
+
circuit.ry(x[i], i)
|
|
217
|
+
circuit.rz(x[i], i)
|
|
218
|
+
|
|
219
|
+
def _amplitude_feature_map(self, circuit: QuantumCircuit, x: np.ndarray):
|
|
220
|
+
"""Simple amplitude-based encoding."""
|
|
221
|
+
for i in range(min(len(x), self.n_qubits)):
|
|
222
|
+
circuit.ry(x[i] * np.pi, i)
|
|
223
|
+
|
|
224
|
+
def _solve_dual(self, K: np.ndarray, y: np.ndarray) -> np.ndarray:
|
|
225
|
+
"""
|
|
226
|
+
Solve SVM dual problem (simplified version).
|
|
227
|
+
|
|
228
|
+
In practice, use proper QP solver like CVXOPT.
|
|
229
|
+
"""
|
|
230
|
+
n = len(y)
|
|
231
|
+
# Simple heuristic: set alphas based on kernel values
|
|
232
|
+
alphas = np.zeros(n)
|
|
233
|
+
|
|
234
|
+
for i in range(n):
|
|
235
|
+
# Points with low kernel similarity to same-class points
|
|
236
|
+
same_class = y == y[i]
|
|
237
|
+
diff_class = ~same_class
|
|
238
|
+
|
|
239
|
+
same_kernel = np.mean(K[i, same_class]) if any(same_class) else 0
|
|
240
|
+
diff_kernel = np.mean(K[i, diff_class]) if any(diff_class) else 0
|
|
241
|
+
|
|
242
|
+
# Higher alpha for boundary points
|
|
243
|
+
alphas[i] = max(0, 1 - same_kernel + diff_kernel)
|
|
244
|
+
|
|
245
|
+
# Normalize
|
|
246
|
+
if np.sum(alphas) > 0:
|
|
247
|
+
alphas = alphas / np.sum(alphas)
|
|
248
|
+
|
|
249
|
+
return alphas
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Variational Quantum Eigensolver (VQE).
|
|
3
|
+
|
|
4
|
+
Finds ground state energy of molecular Hamiltonians.
|
|
5
|
+
Hybrid quantum-classical algorithm for quantum chemistry.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import math
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Optional, Callable
|
|
11
|
+
import numpy as np
|
|
12
|
+
from qiskit import QuantumCircuit
|
|
13
|
+
|
|
14
|
+
from quantumflow.backends.base_backend import QuantumBackend, get_backend, BackendType
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class VQEResult:
|
|
19
|
+
"""Result from VQE optimization."""
|
|
20
|
+
|
|
21
|
+
circuit: QuantumCircuit
|
|
22
|
+
optimal_params: np.ndarray
|
|
23
|
+
ground_energy: float
|
|
24
|
+
energy_history: list[float]
|
|
25
|
+
n_iterations: int
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class VQE:
|
|
29
|
+
"""
|
|
30
|
+
Variational Quantum Eigensolver.
|
|
31
|
+
|
|
32
|
+
Finds the ground state energy of a Hamiltonian by:
|
|
33
|
+
1. Preparing parameterized ansatz state
|
|
34
|
+
2. Measuring expectation value
|
|
35
|
+
3. Classically optimizing parameters
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
>>> vqe = VQE(n_qubits=2)
|
|
39
|
+
>>> # Simple H2 molecule Hamiltonian
|
|
40
|
+
>>> H = [("ZZ", 0.5), ("XI", 0.3), ("IX", 0.3)]
|
|
41
|
+
>>> result = vqe.run(H)
|
|
42
|
+
>>> print(f"Ground energy: {result.ground_energy:.4f}")
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
n_qubits: int,
|
|
48
|
+
backend: BackendType | str = BackendType.AUTO,
|
|
49
|
+
ansatz: str = "ry_linear",
|
|
50
|
+
depth: int = 2,
|
|
51
|
+
shots: int = 1024,
|
|
52
|
+
):
|
|
53
|
+
"""
|
|
54
|
+
Initialize VQE.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
n_qubits: Number of qubits
|
|
58
|
+
backend: Quantum backend
|
|
59
|
+
ansatz: Ansatz type ('ry_linear', 'ry_full', 'hardware_efficient')
|
|
60
|
+
depth: Circuit depth (layers)
|
|
61
|
+
shots: Measurement shots
|
|
62
|
+
"""
|
|
63
|
+
self.n_qubits = n_qubits
|
|
64
|
+
self.backend = get_backend(backend)
|
|
65
|
+
self.ansatz_type = ansatz
|
|
66
|
+
self.depth = depth
|
|
67
|
+
self.shots = shots
|
|
68
|
+
self._connected = False
|
|
69
|
+
|
|
70
|
+
def _ensure_connected(self):
|
|
71
|
+
if not self._connected:
|
|
72
|
+
self.backend.connect()
|
|
73
|
+
self._connected = True
|
|
74
|
+
|
|
75
|
+
def run(
|
|
76
|
+
self,
|
|
77
|
+
hamiltonian: list[tuple[str, float]],
|
|
78
|
+
max_iterations: int = 100,
|
|
79
|
+
initial_params: Optional[np.ndarray] = None,
|
|
80
|
+
) -> VQEResult:
|
|
81
|
+
"""
|
|
82
|
+
Run VQE to find ground state energy.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
hamiltonian: List of (Pauli string, coefficient) tuples
|
|
86
|
+
e.g., [("ZZ", 0.5), ("XI", 0.3)]
|
|
87
|
+
max_iterations: Maximum optimization iterations
|
|
88
|
+
initial_params: Initial parameter values
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
VQEResult with ground state energy
|
|
92
|
+
"""
|
|
93
|
+
self._ensure_connected()
|
|
94
|
+
|
|
95
|
+
# Initialize parameters
|
|
96
|
+
n_params = self._count_params()
|
|
97
|
+
if initial_params is None:
|
|
98
|
+
params = np.random.uniform(-np.pi, np.pi, n_params)
|
|
99
|
+
else:
|
|
100
|
+
params = initial_params.copy()
|
|
101
|
+
|
|
102
|
+
energy_history = []
|
|
103
|
+
best_energy = float('inf')
|
|
104
|
+
best_params = params.copy()
|
|
105
|
+
|
|
106
|
+
# Optimization loop
|
|
107
|
+
for iteration in range(max_iterations):
|
|
108
|
+
energy = self._evaluate_energy(params, hamiltonian)
|
|
109
|
+
energy_history.append(energy)
|
|
110
|
+
|
|
111
|
+
if energy < best_energy:
|
|
112
|
+
best_energy = energy
|
|
113
|
+
best_params = params.copy()
|
|
114
|
+
|
|
115
|
+
# Simple gradient descent with finite differences
|
|
116
|
+
grad = self._compute_gradient(params, hamiltonian)
|
|
117
|
+
params -= 0.1 * grad
|
|
118
|
+
|
|
119
|
+
# Build final circuit
|
|
120
|
+
final_circuit = self._build_ansatz(best_params)
|
|
121
|
+
|
|
122
|
+
return VQEResult(
|
|
123
|
+
circuit=final_circuit,
|
|
124
|
+
optimal_params=best_params,
|
|
125
|
+
ground_energy=best_energy,
|
|
126
|
+
energy_history=energy_history,
|
|
127
|
+
n_iterations=max_iterations,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def _count_params(self) -> int:
|
|
131
|
+
"""Count parameters in ansatz."""
|
|
132
|
+
if self.ansatz_type == "ry_linear":
|
|
133
|
+
return self.n_qubits * self.depth
|
|
134
|
+
elif self.ansatz_type == "ry_full":
|
|
135
|
+
return self.n_qubits * self.depth * 2
|
|
136
|
+
elif self.ansatz_type == "hardware_efficient":
|
|
137
|
+
return self.n_qubits * self.depth * 3
|
|
138
|
+
return self.n_qubits * self.depth
|
|
139
|
+
|
|
140
|
+
def _build_ansatz(self, params: np.ndarray) -> QuantumCircuit:
|
|
141
|
+
"""Build parameterized ansatz circuit."""
|
|
142
|
+
qc = QuantumCircuit(self.n_qubits, name="vqe_ansatz")
|
|
143
|
+
param_idx = 0
|
|
144
|
+
|
|
145
|
+
for layer in range(self.depth):
|
|
146
|
+
# Single qubit rotations
|
|
147
|
+
for i in range(self.n_qubits):
|
|
148
|
+
if self.ansatz_type == "hardware_efficient":
|
|
149
|
+
qc.ry(params[param_idx], i)
|
|
150
|
+
param_idx += 1
|
|
151
|
+
qc.rz(params[param_idx], i)
|
|
152
|
+
param_idx += 1
|
|
153
|
+
else:
|
|
154
|
+
qc.ry(params[param_idx], i)
|
|
155
|
+
param_idx += 1
|
|
156
|
+
|
|
157
|
+
# Entangling layer
|
|
158
|
+
for i in range(self.n_qubits - 1):
|
|
159
|
+
qc.cx(i, i + 1)
|
|
160
|
+
|
|
161
|
+
return qc
|
|
162
|
+
|
|
163
|
+
def _evaluate_energy(
|
|
164
|
+
self,
|
|
165
|
+
params: np.ndarray,
|
|
166
|
+
hamiltonian: list[tuple[str, float]],
|
|
167
|
+
) -> float:
|
|
168
|
+
"""Evaluate expectation value of Hamiltonian."""
|
|
169
|
+
total_energy = 0.0
|
|
170
|
+
|
|
171
|
+
for pauli_string, coeff in hamiltonian:
|
|
172
|
+
expectation = self._measure_pauli(params, pauli_string)
|
|
173
|
+
total_energy += coeff * expectation
|
|
174
|
+
|
|
175
|
+
return total_energy
|
|
176
|
+
|
|
177
|
+
def _measure_pauli(self, params: np.ndarray, pauli_string: str) -> float:
|
|
178
|
+
"""Measure expectation value of a Pauli string."""
|
|
179
|
+
qc = self._build_ansatz(params)
|
|
180
|
+
|
|
181
|
+
# Add measurement basis rotations
|
|
182
|
+
for i, p in enumerate(pauli_string):
|
|
183
|
+
if p == 'X':
|
|
184
|
+
qc.h(i)
|
|
185
|
+
elif p == 'Y':
|
|
186
|
+
qc.sdg(i)
|
|
187
|
+
qc.h(i)
|
|
188
|
+
# Z needs no rotation
|
|
189
|
+
|
|
190
|
+
qc.measure_all()
|
|
191
|
+
|
|
192
|
+
# Execute
|
|
193
|
+
result = self.backend.execute(qc, shots=self.shots)
|
|
194
|
+
|
|
195
|
+
# Calculate expectation
|
|
196
|
+
expectation = 0.0
|
|
197
|
+
for bitstring, count in result.counts.items():
|
|
198
|
+
# Parity of measured bits
|
|
199
|
+
parity = 1
|
|
200
|
+
for i, p in enumerate(pauli_string):
|
|
201
|
+
if p != 'I':
|
|
202
|
+
bit_idx = self.n_qubits - 1 - i
|
|
203
|
+
if bit_idx < len(bitstring) and bitstring[bit_idx] == '1':
|
|
204
|
+
parity *= -1
|
|
205
|
+
expectation += parity * count
|
|
206
|
+
|
|
207
|
+
return expectation / self.shots
|
|
208
|
+
|
|
209
|
+
def _compute_gradient(
|
|
210
|
+
self,
|
|
211
|
+
params: np.ndarray,
|
|
212
|
+
hamiltonian: list[tuple[str, float]],
|
|
213
|
+
) -> np.ndarray:
|
|
214
|
+
"""Compute parameter gradient using parameter shift rule."""
|
|
215
|
+
grad = np.zeros_like(params)
|
|
216
|
+
shift = np.pi / 2
|
|
217
|
+
|
|
218
|
+
for i in range(len(params)):
|
|
219
|
+
params_plus = params.copy()
|
|
220
|
+
params_plus[i] += shift
|
|
221
|
+
energy_plus = self._evaluate_energy(params_plus, hamiltonian)
|
|
222
|
+
|
|
223
|
+
params_minus = params.copy()
|
|
224
|
+
params_minus[i] -= shift
|
|
225
|
+
energy_minus = self._evaluate_energy(params_minus, hamiltonian)
|
|
226
|
+
|
|
227
|
+
grad[i] = (energy_plus - energy_minus) / 2
|
|
228
|
+
|
|
229
|
+
return grad
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Optimization Algorithms."""
|
|
2
|
+
|
|
3
|
+
from quantumflow.algorithms.optimization.qaoa import QAOA
|
|
4
|
+
from quantumflow.algorithms.optimization.grover import GroverSearch
|
|
5
|
+
from quantumflow.algorithms.optimization.quantum_annealing import QuantumAnnealing
|
|
6
|
+
|
|
7
|
+
__all__ = ["QAOA", "GroverSearch", "QuantumAnnealing"]
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Grover's Search Algorithm.
|
|
3
|
+
|
|
4
|
+
Provides quadratic speedup for unstructured search: O(sqrt(N)) vs O(N).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import math
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Optional, Callable, Union
|
|
10
|
+
import numpy as np
|
|
11
|
+
from qiskit import QuantumCircuit
|
|
12
|
+
|
|
13
|
+
from quantumflow.backends.base_backend import QuantumBackend, get_backend, BackendType
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class GroverResult:
|
|
18
|
+
"""Result from Grover search."""
|
|
19
|
+
|
|
20
|
+
circuit: QuantumCircuit
|
|
21
|
+
found_state: str
|
|
22
|
+
probability: float
|
|
23
|
+
iterations: int
|
|
24
|
+
n_qubits: int
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class GroverSearch:
|
|
28
|
+
"""
|
|
29
|
+
Grover's Quantum Search Algorithm.
|
|
30
|
+
|
|
31
|
+
Finds marked items in an unstructured database with quadratic speedup.
|
|
32
|
+
|
|
33
|
+
Features:
|
|
34
|
+
- Automatic iteration count optimization
|
|
35
|
+
- Custom oracle support
|
|
36
|
+
- Multi-target search
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
>>> grover = GroverSearch()
|
|
40
|
+
>>> result = grover.search(n_qubits=4, marked_states=[5, 10])
|
|
41
|
+
>>> print(f"Found: {result.found_state} with P={result.probability:.2%}")
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, backend: BackendType | str = BackendType.AUTO):
|
|
45
|
+
self.backend = get_backend(backend)
|
|
46
|
+
self._connected = False
|
|
47
|
+
|
|
48
|
+
def _ensure_connected(self):
|
|
49
|
+
if not self._connected:
|
|
50
|
+
self.backend.connect()
|
|
51
|
+
self._connected = True
|
|
52
|
+
|
|
53
|
+
def search(
|
|
54
|
+
self,
|
|
55
|
+
n_qubits: int,
|
|
56
|
+
marked_states: list[int],
|
|
57
|
+
iterations: Optional[int] = None,
|
|
58
|
+
shots: int = 1024,
|
|
59
|
+
) -> GroverResult:
|
|
60
|
+
"""
|
|
61
|
+
Search for marked states.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
n_qubits: Number of qubits (search space = 2^n)
|
|
65
|
+
marked_states: States to find
|
|
66
|
+
iterations: Grover iterations (optimal if None)
|
|
67
|
+
shots: Measurement shots
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
GroverResult with found state
|
|
71
|
+
"""
|
|
72
|
+
self._ensure_connected()
|
|
73
|
+
|
|
74
|
+
N = 2 ** n_qubits
|
|
75
|
+
M = len(marked_states)
|
|
76
|
+
|
|
77
|
+
# Calculate optimal iterations
|
|
78
|
+
if iterations is None:
|
|
79
|
+
if M > 0 and M < N:
|
|
80
|
+
theta = math.asin(math.sqrt(M / N))
|
|
81
|
+
iterations = max(1, int(round(math.pi / (4 * theta) - 0.5)))
|
|
82
|
+
else:
|
|
83
|
+
iterations = 1
|
|
84
|
+
|
|
85
|
+
# Build circuit
|
|
86
|
+
oracle = self._build_oracle(n_qubits, marked_states)
|
|
87
|
+
circuit = self._build_circuit(n_qubits, oracle, iterations)
|
|
88
|
+
|
|
89
|
+
# Execute
|
|
90
|
+
result = self.backend.execute(circuit, shots=shots)
|
|
91
|
+
|
|
92
|
+
# Find most likely state
|
|
93
|
+
found_state = max(result.counts, key=result.counts.get)
|
|
94
|
+
probability = result.counts[found_state] / shots
|
|
95
|
+
|
|
96
|
+
return GroverResult(
|
|
97
|
+
circuit=circuit,
|
|
98
|
+
found_state=found_state,
|
|
99
|
+
probability=probability,
|
|
100
|
+
iterations=iterations,
|
|
101
|
+
n_qubits=n_qubits,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def search_with_oracle(
|
|
105
|
+
self,
|
|
106
|
+
n_qubits: int,
|
|
107
|
+
oracle: QuantumCircuit,
|
|
108
|
+
n_solutions: int = 1,
|
|
109
|
+
iterations: Optional[int] = None,
|
|
110
|
+
shots: int = 1024,
|
|
111
|
+
) -> GroverResult:
|
|
112
|
+
"""
|
|
113
|
+
Search using a custom oracle.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
n_qubits: Number of qubits
|
|
117
|
+
oracle: Custom oracle circuit
|
|
118
|
+
n_solutions: Expected number of solutions
|
|
119
|
+
iterations: Grover iterations
|
|
120
|
+
shots: Measurement shots
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
GroverResult
|
|
124
|
+
"""
|
|
125
|
+
self._ensure_connected()
|
|
126
|
+
|
|
127
|
+
N = 2 ** n_qubits
|
|
128
|
+
|
|
129
|
+
if iterations is None:
|
|
130
|
+
if n_solutions > 0 and n_solutions < N:
|
|
131
|
+
theta = math.asin(math.sqrt(n_solutions / N))
|
|
132
|
+
iterations = max(1, int(round(math.pi / (4 * theta) - 0.5)))
|
|
133
|
+
else:
|
|
134
|
+
iterations = 1
|
|
135
|
+
|
|
136
|
+
circuit = self._build_circuit(n_qubits, oracle, iterations)
|
|
137
|
+
result = self.backend.execute(circuit, shots=shots)
|
|
138
|
+
|
|
139
|
+
found_state = max(result.counts, key=result.counts.get)
|
|
140
|
+
probability = result.counts[found_state] / shots
|
|
141
|
+
|
|
142
|
+
return GroverResult(
|
|
143
|
+
circuit=circuit,
|
|
144
|
+
found_state=found_state,
|
|
145
|
+
probability=probability,
|
|
146
|
+
iterations=iterations,
|
|
147
|
+
n_qubits=n_qubits,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def _build_oracle(
|
|
151
|
+
self,
|
|
152
|
+
n_qubits: int,
|
|
153
|
+
marked_states: list[int],
|
|
154
|
+
) -> QuantumCircuit:
|
|
155
|
+
"""Build oracle marking specified states."""
|
|
156
|
+
oracle = QuantumCircuit(n_qubits, name="oracle")
|
|
157
|
+
|
|
158
|
+
for state in marked_states:
|
|
159
|
+
binary = format(state, f'0{n_qubits}b')
|
|
160
|
+
|
|
161
|
+
# Flip qubits that should be |0⟩
|
|
162
|
+
for i, bit in enumerate(reversed(binary)):
|
|
163
|
+
if bit == '0':
|
|
164
|
+
oracle.x(i)
|
|
165
|
+
|
|
166
|
+
# Multi-controlled Z gate
|
|
167
|
+
if n_qubits == 1:
|
|
168
|
+
oracle.z(0)
|
|
169
|
+
elif n_qubits == 2:
|
|
170
|
+
oracle.cz(0, 1)
|
|
171
|
+
else:
|
|
172
|
+
oracle.h(n_qubits - 1)
|
|
173
|
+
oracle.mcx(list(range(n_qubits - 1)), n_qubits - 1)
|
|
174
|
+
oracle.h(n_qubits - 1)
|
|
175
|
+
|
|
176
|
+
# Unflip
|
|
177
|
+
for i, bit in enumerate(reversed(binary)):
|
|
178
|
+
if bit == '0':
|
|
179
|
+
oracle.x(i)
|
|
180
|
+
|
|
181
|
+
return oracle
|
|
182
|
+
|
|
183
|
+
def _build_diffuser(self, n_qubits: int) -> QuantumCircuit:
|
|
184
|
+
"""Build Grover diffusion operator."""
|
|
185
|
+
diffuser = QuantumCircuit(n_qubits, name="diffuser")
|
|
186
|
+
|
|
187
|
+
diffuser.h(range(n_qubits))
|
|
188
|
+
diffuser.x(range(n_qubits))
|
|
189
|
+
|
|
190
|
+
# Multi-controlled Z
|
|
191
|
+
diffuser.h(n_qubits - 1)
|
|
192
|
+
if n_qubits > 1:
|
|
193
|
+
diffuser.mcx(list(range(n_qubits - 1)), n_qubits - 1)
|
|
194
|
+
diffuser.h(n_qubits - 1)
|
|
195
|
+
|
|
196
|
+
diffuser.x(range(n_qubits))
|
|
197
|
+
diffuser.h(range(n_qubits))
|
|
198
|
+
|
|
199
|
+
return diffuser
|
|
200
|
+
|
|
201
|
+
def _build_circuit(
|
|
202
|
+
self,
|
|
203
|
+
n_qubits: int,
|
|
204
|
+
oracle: QuantumCircuit,
|
|
205
|
+
iterations: int,
|
|
206
|
+
) -> QuantumCircuit:
|
|
207
|
+
"""Build complete Grover circuit."""
|
|
208
|
+
circuit = QuantumCircuit(n_qubits, n_qubits, name="grover")
|
|
209
|
+
|
|
210
|
+
# Initial superposition
|
|
211
|
+
circuit.h(range(n_qubits))
|
|
212
|
+
|
|
213
|
+
# Grover iterations
|
|
214
|
+
diffuser = self._build_diffuser(n_qubits)
|
|
215
|
+
|
|
216
|
+
for _ in range(iterations):
|
|
217
|
+
circuit.compose(oracle, inplace=True)
|
|
218
|
+
circuit.compose(diffuser, inplace=True)
|
|
219
|
+
|
|
220
|
+
# Measure
|
|
221
|
+
circuit.measure(range(n_qubits), range(n_qubits))
|
|
222
|
+
|
|
223
|
+
return circuit
|