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.
Files changed (60) hide show
  1. api/__init__.py +1 -0
  2. api/auth.py +208 -0
  3. api/main.py +403 -0
  4. api/models.py +137 -0
  5. api/routes/__init__.py +1 -0
  6. api/routes/auth_routes.py +234 -0
  7. api/routes/teleport_routes.py +415 -0
  8. db/__init__.py +15 -0
  9. db/crud.py +319 -0
  10. db/database.py +93 -0
  11. db/models.py +197 -0
  12. quantumflow/__init__.py +47 -0
  13. quantumflow/algorithms/__init__.py +48 -0
  14. quantumflow/algorithms/compression/__init__.py +7 -0
  15. quantumflow/algorithms/compression/amplitude_amplification.py +189 -0
  16. quantumflow/algorithms/compression/qft_compression.py +133 -0
  17. quantumflow/algorithms/compression/token_compression.py +261 -0
  18. quantumflow/algorithms/cryptography/__init__.py +6 -0
  19. quantumflow/algorithms/cryptography/qkd.py +205 -0
  20. quantumflow/algorithms/cryptography/qrng.py +231 -0
  21. quantumflow/algorithms/machine_learning/__init__.py +7 -0
  22. quantumflow/algorithms/machine_learning/qnn.py +276 -0
  23. quantumflow/algorithms/machine_learning/qsvm.py +249 -0
  24. quantumflow/algorithms/machine_learning/vqe.py +229 -0
  25. quantumflow/algorithms/optimization/__init__.py +7 -0
  26. quantumflow/algorithms/optimization/grover.py +223 -0
  27. quantumflow/algorithms/optimization/qaoa.py +251 -0
  28. quantumflow/algorithms/optimization/quantum_annealing.py +237 -0
  29. quantumflow/algorithms/utility/__init__.py +6 -0
  30. quantumflow/algorithms/utility/circuit_optimizer.py +194 -0
  31. quantumflow/algorithms/utility/error_correction.py +330 -0
  32. quantumflow/api/__init__.py +1 -0
  33. quantumflow/api/routes/__init__.py +4 -0
  34. quantumflow/api/routes/billing_routes.py +520 -0
  35. quantumflow/backends/__init__.py +33 -0
  36. quantumflow/backends/base_backend.py +184 -0
  37. quantumflow/backends/braket_backend.py +345 -0
  38. quantumflow/backends/ibm_backend.py +112 -0
  39. quantumflow/backends/simulator_backend.py +86 -0
  40. quantumflow/billing/__init__.py +25 -0
  41. quantumflow/billing/models.py +126 -0
  42. quantumflow/billing/stripe_service.py +619 -0
  43. quantumflow/core/__init__.py +12 -0
  44. quantumflow/core/entanglement.py +164 -0
  45. quantumflow/core/memory.py +147 -0
  46. quantumflow/core/quantum_backprop.py +394 -0
  47. quantumflow/core/quantum_compressor.py +309 -0
  48. quantumflow/core/teleportation.py +386 -0
  49. quantumflow/integrations/__init__.py +107 -0
  50. quantumflow/integrations/autogen_tools.py +501 -0
  51. quantumflow/integrations/crewai_agents.py +425 -0
  52. quantumflow/integrations/crewai_tools.py +407 -0
  53. quantumflow/integrations/langchain_memory.py +385 -0
  54. quantumflow/integrations/langchain_tools.py +366 -0
  55. quantumflow/integrations/mcp_server.py +575 -0
  56. quantumflow_sdk-0.1.0.dist-info/METADATA +190 -0
  57. quantumflow_sdk-0.1.0.dist-info/RECORD +60 -0
  58. quantumflow_sdk-0.1.0.dist-info/WHEEL +5 -0
  59. quantumflow_sdk-0.1.0.dist-info/entry_points.txt +2 -0
  60. 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