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,205 @@
1
+ """
2
+ Quantum Key Distribution (QKD).
3
+
4
+ Implements BB84 protocol for secure key exchange.
5
+ """
6
+
7
+ import secrets
8
+ from dataclasses import dataclass
9
+ from typing import Optional
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 QKDResult:
18
+ """Result from QKD key generation."""
19
+
20
+ shared_key: str
21
+ key_length: int
22
+ raw_key_length: int
23
+ error_rate: float
24
+ is_secure: bool
25
+ alice_bases: str
26
+ bob_bases: str
27
+
28
+
29
+ class QKD:
30
+ """
31
+ Quantum Key Distribution using BB84 Protocol.
32
+
33
+ Enables two parties (Alice and Bob) to generate a shared secret key
34
+ with information-theoretic security guaranteed by quantum mechanics.
35
+
36
+ Protocol:
37
+ 1. Alice prepares qubits in random states (Z or X basis)
38
+ 2. Bob measures in random bases
39
+ 3. They compare bases publicly
40
+ 4. Keep only matching-basis measurements
41
+ 5. Check for eavesdropping via error rate
42
+
43
+ Example:
44
+ >>> qkd = QKD()
45
+ >>> result = qkd.generate_key(key_length=128)
46
+ >>> print(f"Shared key: {result.shared_key[:32]}...")
47
+ >>> print(f"Secure: {result.is_secure}")
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ backend: BackendType | str = BackendType.AUTO,
53
+ error_threshold: float = 0.11,
54
+ ):
55
+ """
56
+ Initialize QKD.
57
+
58
+ Args:
59
+ backend: Quantum backend
60
+ error_threshold: Max tolerable error rate (11% for BB84)
61
+ """
62
+ self.backend = get_backend(backend)
63
+ self.error_threshold = error_threshold
64
+ self._connected = False
65
+
66
+ def _ensure_connected(self):
67
+ if not self._connected:
68
+ self.backend.connect()
69
+ self._connected = True
70
+
71
+ def generate_key(
72
+ self,
73
+ key_length: int = 256,
74
+ with_eavesdropper: bool = False,
75
+ ) -> QKDResult:
76
+ """
77
+ Generate a shared cryptographic key.
78
+
79
+ Args:
80
+ key_length: Desired final key length in bits
81
+ with_eavesdropper: Simulate eavesdropping (for testing)
82
+
83
+ Returns:
84
+ QKDResult with shared key
85
+ """
86
+ self._ensure_connected()
87
+
88
+ # Need ~4x raw bits due to basis mismatch and error correction
89
+ raw_length = key_length * 4
90
+
91
+ # Step 1: Alice generates random bits and bases
92
+ alice_bits = self._random_bits(raw_length)
93
+ alice_bases = self._random_bits(raw_length) # 0=Z, 1=X
94
+
95
+ # Step 2: Alice prepares qubits
96
+ alice_states = self._prepare_states(alice_bits, alice_bases)
97
+
98
+ # Step 3: (Optional) Eve intercepts
99
+ if with_eavesdropper:
100
+ eve_bases = self._random_bits(raw_length)
101
+ alice_states = self._eavesdrop(alice_states, eve_bases)
102
+
103
+ # Step 4: Bob chooses random bases and measures
104
+ bob_bases = self._random_bits(raw_length)
105
+ bob_bits = self._measure_states(alice_states, bob_bases)
106
+
107
+ # Step 5: Sifting - keep only matching bases
108
+ sifted_alice = []
109
+ sifted_bob = []
110
+
111
+ for i in range(raw_length):
112
+ if alice_bases[i] == bob_bases[i]:
113
+ sifted_alice.append(alice_bits[i])
114
+ sifted_bob.append(bob_bits[i])
115
+
116
+ # Step 6: Error estimation (use subset)
117
+ check_length = min(len(sifted_alice) // 4, 50)
118
+ errors = sum(
119
+ sifted_alice[i] != sifted_bob[i]
120
+ for i in range(check_length)
121
+ )
122
+ error_rate = errors / check_length if check_length > 0 else 0
123
+
124
+ # Remove check bits
125
+ final_alice = sifted_alice[check_length:]
126
+ final_bob = sifted_bob[check_length:]
127
+
128
+ # Step 7: Check security
129
+ is_secure = error_rate < self.error_threshold
130
+
131
+ # Create key (truncate to desired length)
132
+ shared_key = ''.join(str(b) for b in final_alice[:key_length])
133
+
134
+ return QKDResult(
135
+ shared_key=shared_key,
136
+ key_length=len(shared_key),
137
+ raw_key_length=raw_length,
138
+ error_rate=error_rate,
139
+ is_secure=is_secure,
140
+ alice_bases=''.join(str(b) for b in alice_bases[:20]) + "...",
141
+ bob_bases=''.join(str(b) for b in bob_bases[:20]) + "...",
142
+ )
143
+
144
+ def _random_bits(self, n: int) -> list[int]:
145
+ """Generate n random bits."""
146
+ return [secrets.randbelow(2) for _ in range(n)]
147
+
148
+ def _prepare_states(
149
+ self,
150
+ bits: list[int],
151
+ bases: list[int],
152
+ ) -> list[tuple[int, int]]:
153
+ """Prepare quantum states based on bits and bases."""
154
+ return list(zip(bits, bases))
155
+
156
+ def _measure_states(
157
+ self,
158
+ states: list[tuple[int, int]],
159
+ bases: list[int],
160
+ ) -> list[int]:
161
+ """Measure states in given bases using quantum circuit."""
162
+ results = []
163
+
164
+ for (bit, prep_basis), meas_basis in zip(states, bases):
165
+ circuit = QuantumCircuit(1, 1)
166
+
167
+ # Prepare state
168
+ if bit == 1:
169
+ circuit.x(0)
170
+ if prep_basis == 1: # X basis
171
+ circuit.h(0)
172
+
173
+ # Measure in basis
174
+ if meas_basis == 1: # X basis
175
+ circuit.h(0)
176
+ circuit.measure(0, 0)
177
+
178
+ # Execute
179
+ result = self.backend.execute(circuit, shots=1)
180
+ measured = int(list(result.counts.keys())[0])
181
+ results.append(measured)
182
+
183
+ return results
184
+
185
+ def _eavesdrop(
186
+ self,
187
+ states: list[tuple[int, int]],
188
+ eve_bases: list[int],
189
+ ) -> list[tuple[int, int]]:
190
+ """Simulate eavesdropper (introduces errors)."""
191
+ disturbed = []
192
+
193
+ for (bit, basis), eve_basis in zip(states, eve_bases):
194
+ if eve_basis != basis:
195
+ # Eve's measurement disturbs state (50% chance of error)
196
+ if secrets.randbelow(2) == 0:
197
+ bit = 1 - bit # Flip bit
198
+ disturbed.append((bit, basis))
199
+
200
+ return disturbed
201
+
202
+
203
+ class BB84(QKD):
204
+ """Alias for QKD (BB84 protocol)."""
205
+ pass
@@ -0,0 +1,231 @@
1
+ """
2
+ Quantum Random Number Generator (QRNG).
3
+
4
+ Generates true random numbers using quantum mechanics.
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 QRNGResult:
17
+ """Result from quantum random number generation."""
18
+
19
+ bits: str
20
+ integer: int
21
+ float_value: float
22
+ n_qubits_used: int
23
+ entropy_bits: int
24
+
25
+
26
+ class QRNG:
27
+ """
28
+ Quantum Random Number Generator.
29
+
30
+ Generates cryptographically secure random numbers using
31
+ quantum superposition and measurement.
32
+
33
+ Unlike classical PRNGs:
34
+ - Randomness is fundamental, not pseudo
35
+ - Information-theoretically unpredictable
36
+ - Certified by quantum mechanics
37
+
38
+ Example:
39
+ >>> qrng = QRNG()
40
+ >>> bits = qrng.random_bits(128)
41
+ >>> number = qrng.random_int(0, 1000)
42
+ >>> value = qrng.random_float()
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ backend: BackendType | str = BackendType.AUTO,
48
+ max_qubits: int = 20,
49
+ ):
50
+ """
51
+ Initialize QRNG.
52
+
53
+ Args:
54
+ backend: Quantum backend
55
+ max_qubits: Maximum qubits per generation
56
+ """
57
+ self.backend = get_backend(backend)
58
+ self.max_qubits = max_qubits
59
+ self._connected = False
60
+ self._buffer: list[int] = []
61
+
62
+ def _ensure_connected(self):
63
+ if not self._connected:
64
+ self.backend.connect()
65
+ self._connected = True
66
+
67
+ def random_bits(self, n: int) -> str:
68
+ """
69
+ Generate n random bits.
70
+
71
+ Args:
72
+ n: Number of bits to generate
73
+
74
+ Returns:
75
+ String of '0' and '1' characters
76
+ """
77
+ self._ensure_connected()
78
+
79
+ bits = []
80
+
81
+ while len(bits) < n:
82
+ # Generate batch of bits
83
+ batch_size = min(self.max_qubits, n - len(bits))
84
+ batch = self._generate_batch(batch_size)
85
+ bits.extend(batch)
86
+
87
+ return ''.join(str(b) for b in bits[:n])
88
+
89
+ def random_int(self, min_val: int = 0, max_val: int = 2**32 - 1) -> int:
90
+ """
91
+ Generate random integer in range [min_val, max_val].
92
+
93
+ Args:
94
+ min_val: Minimum value (inclusive)
95
+ max_val: Maximum value (inclusive)
96
+
97
+ Returns:
98
+ Random integer
99
+ """
100
+ range_size = max_val - min_val + 1
101
+ bits_needed = int(np.ceil(np.log2(range_size))) + 1
102
+
103
+ # Rejection sampling to avoid bias
104
+ while True:
105
+ bits = self.random_bits(bits_needed)
106
+ value = int(bits, 2)
107
+ if value < range_size:
108
+ return min_val + value
109
+
110
+ def random_float(self) -> float:
111
+ """
112
+ Generate random float in [0, 1).
113
+
114
+ Returns:
115
+ Random float with 53 bits of precision
116
+ """
117
+ # IEEE 754 double has 53 bits of mantissa
118
+ bits = self.random_bits(53)
119
+ value = int(bits, 2)
120
+ return value / (2**53)
121
+
122
+ def random_bytes(self, n: int) -> bytes:
123
+ """
124
+ Generate n random bytes.
125
+
126
+ Args:
127
+ n: Number of bytes
128
+
129
+ Returns:
130
+ Random bytes
131
+ """
132
+ bits = self.random_bits(n * 8)
133
+ return int(bits, 2).to_bytes(n, byteorder='big')
134
+
135
+ def random_gaussian(self, mean: float = 0.0, std: float = 1.0) -> float:
136
+ """
137
+ Generate Gaussian distributed random number.
138
+
139
+ Uses Box-Muller transform.
140
+
141
+ Args:
142
+ mean: Mean of distribution
143
+ std: Standard deviation
144
+
145
+ Returns:
146
+ Gaussian random number
147
+ """
148
+ u1 = self.random_float()
149
+ u2 = self.random_float()
150
+
151
+ # Avoid log(0)
152
+ while u1 < 1e-10:
153
+ u1 = self.random_float()
154
+
155
+ # Box-Muller transform
156
+ z = np.sqrt(-2 * np.log(u1)) * np.cos(2 * np.pi * u2)
157
+
158
+ return mean + std * z
159
+
160
+ def random_choice(self, items: list) -> any:
161
+ """
162
+ Randomly select an item from list.
163
+
164
+ Args:
165
+ items: List to choose from
166
+
167
+ Returns:
168
+ Randomly selected item
169
+ """
170
+ if not items:
171
+ raise ValueError("Cannot choose from empty list")
172
+
173
+ idx = self.random_int(0, len(items) - 1)
174
+ return items[idx]
175
+
176
+ def random_shuffle(self, items: list) -> list:
177
+ """
178
+ Randomly shuffle a list.
179
+
180
+ Args:
181
+ items: List to shuffle
182
+
183
+ Returns:
184
+ Shuffled copy of list
185
+ """
186
+ result = items.copy()
187
+ n = len(result)
188
+
189
+ # Fisher-Yates shuffle
190
+ for i in range(n - 1, 0, -1):
191
+ j = self.random_int(0, i)
192
+ result[i], result[j] = result[j], result[i]
193
+
194
+ return result
195
+
196
+ def _generate_batch(self, n_qubits: int) -> list[int]:
197
+ """Generate batch of random bits using quantum circuit."""
198
+ circuit = QuantumCircuit(n_qubits, n_qubits)
199
+
200
+ # Put all qubits in superposition
201
+ circuit.h(range(n_qubits))
202
+
203
+ # Measure
204
+ circuit.measure(range(n_qubits), range(n_qubits))
205
+
206
+ # Execute with single shot
207
+ result = self.backend.execute(circuit, shots=1)
208
+
209
+ # Extract bits
210
+ bitstring = list(result.counts.keys())[0]
211
+ return [int(b) for b in bitstring]
212
+
213
+ def get_result(self, n_bits: int) -> QRNGResult:
214
+ """
215
+ Generate random bits with full result details.
216
+
217
+ Args:
218
+ n_bits: Number of bits
219
+
220
+ Returns:
221
+ QRNGResult with all details
222
+ """
223
+ bits = self.random_bits(n_bits)
224
+
225
+ return QRNGResult(
226
+ bits=bits,
227
+ integer=int(bits, 2) if bits else 0,
228
+ float_value=int(bits, 2) / (2**n_bits) if bits else 0.0,
229
+ n_qubits_used=min(self.max_qubits, n_bits),
230
+ entropy_bits=n_bits,
231
+ )
@@ -0,0 +1,7 @@
1
+ """Machine Learning Algorithms."""
2
+
3
+ from quantumflow.algorithms.machine_learning.vqe import VQE
4
+ from quantumflow.algorithms.machine_learning.qsvm import QSVM
5
+ from quantumflow.algorithms.machine_learning.qnn import QNN
6
+
7
+ __all__ = ["VQE", "QSVM", "QNN"]
@@ -0,0 +1,276 @@
1
+ """
2
+ Quantum Neural Network (QNN).
3
+
4
+ Parameterized quantum circuits for machine learning tasks.
5
+ Integrates with Paper 2's quantum backpropagation.
6
+ """
7
+
8
+ from dataclasses import dataclass
9
+ from typing import Optional, Callable
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 QNNResult:
18
+ """Result from QNN training."""
19
+
20
+ final_weights: np.ndarray
21
+ loss_history: list[float]
22
+ accuracy: float
23
+ n_epochs: int
24
+
25
+
26
+ class QNN:
27
+ """
28
+ Quantum Neural Network.
29
+
30
+ A parameterized quantum circuit that can be trained for:
31
+ - Binary classification
32
+ - Multi-class classification
33
+ - Regression
34
+
35
+ Architecture:
36
+ - Input encoding layer (amplitude or angle encoding)
37
+ - Variational layers (parameterized rotations + entanglement)
38
+ - Measurement layer
39
+
40
+ Example:
41
+ >>> qnn = QNN(n_qubits=4, n_layers=3)
42
+ >>> result = qnn.fit(X_train, y_train, epochs=100)
43
+ >>> predictions = qnn.predict(X_test)
44
+ """
45
+
46
+ def __init__(
47
+ self,
48
+ n_qubits: int,
49
+ n_layers: int = 2,
50
+ backend: BackendType | str = BackendType.AUTO,
51
+ learning_rate: float = 0.1,
52
+ shots: int = 1024,
53
+ ):
54
+ """
55
+ Initialize QNN.
56
+
57
+ Args:
58
+ n_qubits: Number of qubits
59
+ n_layers: Number of variational layers
60
+ backend: Quantum backend
61
+ learning_rate: Learning rate for optimization
62
+ shots: Measurement shots
63
+ """
64
+ self.n_qubits = n_qubits
65
+ self.n_layers = n_layers
66
+ self.backend = get_backend(backend)
67
+ self.learning_rate = learning_rate
68
+ self.shots = shots
69
+ self._connected = False
70
+
71
+ # Initialize weights: 3 parameters per qubit per layer (rx, ry, rz)
72
+ self.n_params = n_qubits * n_layers * 3
73
+ self.weights = np.random.uniform(-np.pi, np.pi, self.n_params)
74
+
75
+ def _ensure_connected(self):
76
+ if not self._connected:
77
+ self.backend.connect()
78
+ self._connected = True
79
+
80
+ def fit(
81
+ self,
82
+ X: np.ndarray,
83
+ y: np.ndarray,
84
+ epochs: int = 100,
85
+ batch_size: int = 8,
86
+ ) -> QNNResult:
87
+ """
88
+ Train the QNN.
89
+
90
+ Args:
91
+ X: Training features (n_samples, n_features)
92
+ y: Training labels (n_samples,)
93
+ epochs: Number of training epochs
94
+ batch_size: Mini-batch size
95
+
96
+ Returns:
97
+ QNNResult with training history
98
+ """
99
+ self._ensure_connected()
100
+
101
+ loss_history = []
102
+
103
+ for epoch in range(epochs):
104
+ epoch_loss = 0.0
105
+
106
+ # Mini-batch training
107
+ indices = np.random.permutation(len(X))
108
+
109
+ for i in range(0, len(X), batch_size):
110
+ batch_idx = indices[i:i + batch_size]
111
+ X_batch = X[batch_idx]
112
+ y_batch = y[batch_idx]
113
+
114
+ # Forward pass and gradient computation
115
+ batch_loss, gradients = self._compute_batch_gradient(X_batch, y_batch)
116
+ epoch_loss += batch_loss
117
+
118
+ # Update weights
119
+ self.weights -= self.learning_rate * gradients
120
+
121
+ epoch_loss /= (len(X) / batch_size)
122
+ loss_history.append(epoch_loss)
123
+
124
+ # Compute final accuracy
125
+ predictions = self.predict(X)
126
+ accuracy = np.mean(predictions == y)
127
+
128
+ return QNNResult(
129
+ final_weights=self.weights.copy(),
130
+ loss_history=loss_history,
131
+ accuracy=accuracy,
132
+ n_epochs=epochs,
133
+ )
134
+
135
+ def predict(self, X: np.ndarray) -> np.ndarray:
136
+ """
137
+ Make predictions.
138
+
139
+ Args:
140
+ X: Input features (n_samples, n_features)
141
+
142
+ Returns:
143
+ Predicted labels
144
+ """
145
+ self._ensure_connected()
146
+
147
+ predictions = []
148
+
149
+ for x in X:
150
+ prob = self._forward(x)
151
+ predictions.append(1 if prob > 0.5 else 0)
152
+
153
+ return np.array(predictions)
154
+
155
+ def predict_proba(self, X: np.ndarray) -> np.ndarray:
156
+ """
157
+ Predict class probabilities.
158
+
159
+ Args:
160
+ X: Input features
161
+
162
+ Returns:
163
+ Probabilities for class 1
164
+ """
165
+ self._ensure_connected()
166
+
167
+ probs = []
168
+ for x in X:
169
+ prob = self._forward(x)
170
+ probs.append(prob)
171
+
172
+ return np.array(probs)
173
+
174
+ def _forward(self, x: np.ndarray) -> float:
175
+ """Forward pass for a single input."""
176
+ circuit = self._build_circuit(x, self.weights)
177
+ result = self.backend.execute(circuit, shots=self.shots)
178
+
179
+ # Probability of measuring |1⟩ on first qubit
180
+ ones_count = sum(
181
+ count for state, count in result.counts.items()
182
+ if state[-1] == '1'
183
+ )
184
+
185
+ return ones_count / self.shots
186
+
187
+ def _build_circuit(
188
+ self,
189
+ x: np.ndarray,
190
+ weights: np.ndarray,
191
+ ) -> QuantumCircuit:
192
+ """Build QNN circuit."""
193
+ circuit = QuantumCircuit(self.n_qubits, 1, name="qnn")
194
+
195
+ # Input encoding
196
+ self._encode_input(circuit, x)
197
+
198
+ # Variational layers
199
+ param_idx = 0
200
+ for layer in range(self.n_layers):
201
+ # Single qubit rotations
202
+ for i in range(self.n_qubits):
203
+ circuit.rx(weights[param_idx], i)
204
+ param_idx += 1
205
+ circuit.ry(weights[param_idx], i)
206
+ param_idx += 1
207
+ circuit.rz(weights[param_idx], i)
208
+ param_idx += 1
209
+
210
+ # Entangling layer (ring topology)
211
+ for i in range(self.n_qubits):
212
+ circuit.cx(i, (i + 1) % self.n_qubits)
213
+
214
+ # Measure first qubit
215
+ circuit.measure(0, 0)
216
+
217
+ return circuit
218
+
219
+ def _encode_input(self, circuit: QuantumCircuit, x: np.ndarray):
220
+ """Encode input features into quantum state."""
221
+ # Angle encoding: each feature -> rotation angle
222
+ for i in range(min(len(x), self.n_qubits)):
223
+ circuit.ry(x[i] * np.pi, i)
224
+
225
+ # Initialize unmapped qubits
226
+ for i in range(len(x), self.n_qubits):
227
+ circuit.h(i)
228
+
229
+ def _compute_batch_gradient(
230
+ self,
231
+ X_batch: np.ndarray,
232
+ y_batch: np.ndarray,
233
+ ) -> tuple[float, np.ndarray]:
234
+ """Compute gradient using parameter shift rule."""
235
+ batch_loss = 0.0
236
+ gradients = np.zeros_like(self.weights)
237
+ shift = np.pi / 2
238
+
239
+ for x, y in zip(X_batch, y_batch):
240
+ # Forward pass
241
+ pred = self._forward(x)
242
+ loss = (pred - y) ** 2
243
+ batch_loss += loss
244
+
245
+ # Parameter shift gradient
246
+ for i in range(len(self.weights)):
247
+ # Shift +
248
+ weights_plus = self.weights.copy()
249
+ weights_plus[i] += shift
250
+ circuit_plus = self._build_circuit(x, weights_plus)
251
+ result_plus = self.backend.execute(circuit_plus, shots=self.shots)
252
+ ones_plus = sum(
253
+ c for s, c in result_plus.counts.items() if s[-1] == '1'
254
+ )
255
+ pred_plus = ones_plus / self.shots
256
+
257
+ # Shift -
258
+ weights_minus = self.weights.copy()
259
+ weights_minus[i] -= shift
260
+ circuit_minus = self._build_circuit(x, weights_minus)
261
+ result_minus = self.backend.execute(circuit_minus, shots=self.shots)
262
+ ones_minus = sum(
263
+ c for s, c in result_minus.counts.items() if s[-1] == '1'
264
+ )
265
+ pred_minus = ones_minus / self.shots
266
+
267
+ # Gradient
268
+ d_pred = (pred_plus - pred_minus) / 2
269
+ d_loss = 2 * (pred - y) * d_pred
270
+ gradients[i] += d_loss
271
+
272
+ # Average over batch
273
+ batch_loss /= len(X_batch)
274
+ gradients /= len(X_batch)
275
+
276
+ return batch_loss, gradients