superquantx 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.
- superquantx/__init__.py +321 -0
- superquantx/algorithms/__init__.py +55 -0
- superquantx/algorithms/base_algorithm.py +413 -0
- superquantx/algorithms/hybrid_classifier.py +628 -0
- superquantx/algorithms/qaoa.py +406 -0
- superquantx/algorithms/quantum_agents.py +1006 -0
- superquantx/algorithms/quantum_kmeans.py +575 -0
- superquantx/algorithms/quantum_nn.py +544 -0
- superquantx/algorithms/quantum_pca.py +499 -0
- superquantx/algorithms/quantum_svm.py +346 -0
- superquantx/algorithms/vqe.py +553 -0
- superquantx/algorithms.py +863 -0
- superquantx/backends/__init__.py +265 -0
- superquantx/backends/base_backend.py +321 -0
- superquantx/backends/braket_backend.py +420 -0
- superquantx/backends/cirq_backend.py +466 -0
- superquantx/backends/ocean_backend.py +491 -0
- superquantx/backends/pennylane_backend.py +419 -0
- superquantx/backends/qiskit_backend.py +451 -0
- superquantx/backends/simulator_backend.py +455 -0
- superquantx/backends/tket_backend.py +519 -0
- superquantx/circuits.py +447 -0
- superquantx/cli/__init__.py +28 -0
- superquantx/cli/commands.py +528 -0
- superquantx/cli/main.py +254 -0
- superquantx/client.py +298 -0
- superquantx/config.py +326 -0
- superquantx/exceptions.py +287 -0
- superquantx/gates.py +588 -0
- superquantx/logging_config.py +347 -0
- superquantx/measurements.py +702 -0
- superquantx/ml.py +936 -0
- superquantx/noise.py +760 -0
- superquantx/utils/__init__.py +83 -0
- superquantx/utils/benchmarking.py +523 -0
- superquantx/utils/classical_utils.py +575 -0
- superquantx/utils/feature_mapping.py +467 -0
- superquantx/utils/optimization.py +410 -0
- superquantx/utils/quantum_utils.py +456 -0
- superquantx/utils/visualization.py +654 -0
- superquantx/version.py +33 -0
- superquantx-0.1.0.dist-info/METADATA +365 -0
- superquantx-0.1.0.dist-info/RECORD +46 -0
- superquantx-0.1.0.dist-info/WHEEL +4 -0
- superquantx-0.1.0.dist-info/entry_points.txt +2 -0
- superquantx-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,499 @@
|
|
1
|
+
"""Quantum Principal Component Analysis (QPCA) implementation.
|
2
|
+
|
3
|
+
This module provides quantum algorithms for principal component analysis,
|
4
|
+
including quantum matrix diagonalization and dimensionality reduction.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import logging
|
8
|
+
from typing import Any, Dict, Optional, Tuple, Union
|
9
|
+
|
10
|
+
import numpy as np
|
11
|
+
from sklearn.decomposition import PCA
|
12
|
+
from sklearn.preprocessing import StandardScaler
|
13
|
+
|
14
|
+
from .base_algorithm import UnsupervisedQuantumAlgorithm
|
15
|
+
|
16
|
+
|
17
|
+
logger = logging.getLogger(__name__)
|
18
|
+
|
19
|
+
class QuantumPCA(UnsupervisedQuantumAlgorithm):
|
20
|
+
"""Quantum Principal Component Analysis for dimensionality reduction.
|
21
|
+
|
22
|
+
This implementation uses quantum algorithms to perform PCA, potentially
|
23
|
+
offering exponential speedup for certain types of data matrices.
|
24
|
+
|
25
|
+
The algorithm can use different quantum approaches:
|
26
|
+
- Quantum Matrix Inversion: For density matrix diagonalization
|
27
|
+
- Variational Quantum Eigensolver: For finding principal eigenvectors
|
28
|
+
- Quantum Phase Estimation: For eigenvalue extraction
|
29
|
+
- Quantum Singular Value Decomposition: Direct SVD approach
|
30
|
+
|
31
|
+
Args:
|
32
|
+
backend: Quantum backend for circuit execution
|
33
|
+
n_components: Number of principal components to extract
|
34
|
+
method: Quantum method ('vqe', 'phase_estimation', 'matrix_inversion', 'qsvd')
|
35
|
+
encoding: Data encoding method ('amplitude', 'dense', 'sparse')
|
36
|
+
max_iterations: Maximum iterations for variational methods
|
37
|
+
tolerance: Convergence tolerance
|
38
|
+
shots: Number of measurement shots
|
39
|
+
classical_fallback: Use classical PCA if quantum fails
|
40
|
+
**kwargs: Additional parameters
|
41
|
+
|
42
|
+
Example:
|
43
|
+
>>> qpca = QuantumPCA(backend='pennylane', n_components=3, method='vqe')
|
44
|
+
>>> qpca.fit(X_train)
|
45
|
+
>>> X_reduced = qpca.transform(X_test)
|
46
|
+
>>> X_reconstructed = qpca.inverse_transform(X_reduced)
|
47
|
+
|
48
|
+
"""
|
49
|
+
|
50
|
+
def __init__(
|
51
|
+
self,
|
52
|
+
backend: Union[str, Any],
|
53
|
+
n_components: int = 2,
|
54
|
+
method: str = 'vqe',
|
55
|
+
encoding: str = 'amplitude',
|
56
|
+
max_iterations: int = 1000,
|
57
|
+
tolerance: float = 1e-6,
|
58
|
+
shots: int = 1024,
|
59
|
+
classical_fallback: bool = True,
|
60
|
+
normalize_data: bool = True,
|
61
|
+
**kwargs
|
62
|
+
) -> None:
|
63
|
+
super().__init__(backend=backend, shots=shots, **kwargs)
|
64
|
+
|
65
|
+
self.n_components = n_components
|
66
|
+
self.method = method
|
67
|
+
self.encoding = encoding
|
68
|
+
self.max_iterations = max_iterations
|
69
|
+
self.tolerance = tolerance
|
70
|
+
self.classical_fallback = classical_fallback
|
71
|
+
self.normalize_data = normalize_data
|
72
|
+
|
73
|
+
# PCA components
|
74
|
+
self.components_ = None
|
75
|
+
self.eigenvalues_ = None
|
76
|
+
self.mean_ = None
|
77
|
+
self.explained_variance_ = None
|
78
|
+
self.explained_variance_ratio_ = None
|
79
|
+
|
80
|
+
# Quantum-specific attributes
|
81
|
+
self.density_matrix_ = None
|
82
|
+
self.quantum_state_ = None
|
83
|
+
self.n_qubits = None
|
84
|
+
|
85
|
+
# Classical components for fallback/comparison
|
86
|
+
self.scaler = StandardScaler() if normalize_data else None
|
87
|
+
self.classical_pca = PCA(n_components=n_components)
|
88
|
+
|
89
|
+
# Method-specific parameters
|
90
|
+
self.vqe_params = None
|
91
|
+
self.convergence_history = []
|
92
|
+
|
93
|
+
logger.info(f"Initialized QuantumPCA with method={method}, n_components={n_components}")
|
94
|
+
|
95
|
+
def _prepare_data_matrix(self, X: np.ndarray) -> np.ndarray:
|
96
|
+
"""Prepare data matrix for quantum processing."""
|
97
|
+
if self.normalize_data:
|
98
|
+
X = self.scaler.fit_transform(X)
|
99
|
+
|
100
|
+
# Center the data
|
101
|
+
self.mean_ = np.mean(X, axis=0)
|
102
|
+
X_centered = X - self.mean_
|
103
|
+
|
104
|
+
return X_centered
|
105
|
+
|
106
|
+
def _create_density_matrix(self, X: np.ndarray) -> np.ndarray:
|
107
|
+
"""Create density matrix from data."""
|
108
|
+
# Compute covariance matrix
|
109
|
+
n_samples = X.shape[0]
|
110
|
+
cov_matrix = (X.T @ X) / (n_samples - 1)
|
111
|
+
|
112
|
+
# Convert to density matrix (normalized)
|
113
|
+
trace = np.trace(cov_matrix)
|
114
|
+
if trace > 0:
|
115
|
+
density_matrix = cov_matrix / trace
|
116
|
+
else:
|
117
|
+
density_matrix = cov_matrix
|
118
|
+
|
119
|
+
self.density_matrix_ = density_matrix
|
120
|
+
return density_matrix
|
121
|
+
|
122
|
+
def _determine_qubits(self, n_features: int) -> int:
|
123
|
+
"""Determine number of qubits needed."""
|
124
|
+
if self.encoding == 'amplitude':
|
125
|
+
return int(np.ceil(np.log2(n_features)))
|
126
|
+
elif self.encoding == 'dense':
|
127
|
+
return n_features
|
128
|
+
else:
|
129
|
+
return int(np.ceil(np.log2(n_features)))
|
130
|
+
|
131
|
+
def _encode_density_matrix(self, density_matrix: np.ndarray) -> Any:
|
132
|
+
"""Encode density matrix into quantum state."""
|
133
|
+
try:
|
134
|
+
if hasattr(self.backend, 'encode_density_matrix'):
|
135
|
+
return self.backend.encode_density_matrix(
|
136
|
+
density_matrix=density_matrix,
|
137
|
+
encoding=self.encoding,
|
138
|
+
n_qubits=self.n_qubits
|
139
|
+
)
|
140
|
+
else:
|
141
|
+
return self._fallback_encoding(density_matrix)
|
142
|
+
except Exception as e:
|
143
|
+
logger.error(f"Failed to encode density matrix: {e}")
|
144
|
+
return self._fallback_encoding(density_matrix)
|
145
|
+
|
146
|
+
def _fallback_encoding(self, density_matrix: np.ndarray) -> Any:
|
147
|
+
"""Fallback encoding implementation."""
|
148
|
+
logger.warning("Using fallback density matrix encoding")
|
149
|
+
return None
|
150
|
+
|
151
|
+
def _quantum_eigensolver_vqe(self, density_matrix: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
152
|
+
"""Use VQE to find principal eigenvectors and eigenvalues."""
|
153
|
+
logger.info("Running VQE for quantum PCA")
|
154
|
+
|
155
|
+
try:
|
156
|
+
if hasattr(self.backend, 'run_pca_vqe'):
|
157
|
+
result = self.backend.run_pca_vqe(
|
158
|
+
density_matrix=density_matrix,
|
159
|
+
n_components=self.n_components,
|
160
|
+
max_iterations=self.max_iterations,
|
161
|
+
tolerance=self.tolerance,
|
162
|
+
shots=self.shots
|
163
|
+
)
|
164
|
+
|
165
|
+
eigenvalues = result.get('eigenvalues', np.zeros(self.n_components))
|
166
|
+
eigenvectors = result.get('eigenvectors', np.eye(density_matrix.shape[0], self.n_components))
|
167
|
+
self.convergence_history = result.get('convergence_history', [])
|
168
|
+
|
169
|
+
return eigenvalues, eigenvectors
|
170
|
+
else:
|
171
|
+
return self._fallback_vqe_eigensolver(density_matrix)
|
172
|
+
|
173
|
+
except Exception as e:
|
174
|
+
logger.error(f"VQE eigensolver failed: {e}")
|
175
|
+
return self._fallback_vqe_eigensolver(density_matrix)
|
176
|
+
|
177
|
+
def _fallback_vqe_eigensolver(self, density_matrix: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
178
|
+
"""Fallback VQE implementation using classical eigensolver."""
|
179
|
+
logger.warning("Using classical fallback for VQE eigensolver")
|
180
|
+
eigenvals, eigenvecs = np.linalg.eigh(density_matrix)
|
181
|
+
# Sort by eigenvalue magnitude (descending)
|
182
|
+
idx = np.argsort(eigenvals)[::-1]
|
183
|
+
return eigenvals[idx][:self.n_components], eigenvecs[:, idx][:, :self.n_components]
|
184
|
+
|
185
|
+
def _quantum_phase_estimation(self, density_matrix: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
186
|
+
"""Use quantum phase estimation for eigenvalue extraction."""
|
187
|
+
logger.info("Running quantum phase estimation for PCA")
|
188
|
+
|
189
|
+
try:
|
190
|
+
if hasattr(self.backend, 'run_phase_estimation_pca'):
|
191
|
+
result = self.backend.run_phase_estimation_pca(
|
192
|
+
density_matrix=density_matrix,
|
193
|
+
n_components=self.n_components,
|
194
|
+
precision_bits=8,
|
195
|
+
shots=self.shots
|
196
|
+
)
|
197
|
+
|
198
|
+
eigenvalues = result.get('eigenvalues', np.zeros(self.n_components))
|
199
|
+
eigenvectors = result.get('eigenvectors', np.eye(density_matrix.shape[0], self.n_components))
|
200
|
+
|
201
|
+
return eigenvalues, eigenvectors
|
202
|
+
else:
|
203
|
+
return self._fallback_phase_estimation(density_matrix)
|
204
|
+
|
205
|
+
except Exception as e:
|
206
|
+
logger.error(f"Quantum phase estimation failed: {e}")
|
207
|
+
return self._fallback_phase_estimation(density_matrix)
|
208
|
+
|
209
|
+
def _fallback_phase_estimation(self, density_matrix: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
210
|
+
"""Fallback phase estimation using classical methods."""
|
211
|
+
logger.warning("Using classical fallback for phase estimation")
|
212
|
+
return self._fallback_vqe_eigensolver(density_matrix)
|
213
|
+
|
214
|
+
def _quantum_svd(self, X: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
215
|
+
"""Quantum singular value decomposition approach."""
|
216
|
+
logger.info("Running quantum SVD for PCA")
|
217
|
+
|
218
|
+
try:
|
219
|
+
if hasattr(self.backend, 'run_quantum_svd'):
|
220
|
+
result = self.backend.run_quantum_svd(
|
221
|
+
data_matrix=X,
|
222
|
+
n_components=self.n_components,
|
223
|
+
shots=self.shots
|
224
|
+
)
|
225
|
+
|
226
|
+
singular_values = result.get('singular_values', np.zeros(self.n_components))
|
227
|
+
components = result.get('components', np.eye(X.shape[1], self.n_components))
|
228
|
+
|
229
|
+
# Convert singular values to eigenvalues
|
230
|
+
eigenvalues = singular_values ** 2 / (X.shape[0] - 1)
|
231
|
+
|
232
|
+
return eigenvalues, components.T
|
233
|
+
else:
|
234
|
+
return self._fallback_svd(X)
|
235
|
+
|
236
|
+
except Exception as e:
|
237
|
+
logger.error(f"Quantum SVD failed: {e}")
|
238
|
+
return self._fallback_svd(X)
|
239
|
+
|
240
|
+
def _fallback_svd(self, X: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
241
|
+
"""Fallback SVD using classical methods."""
|
242
|
+
logger.warning("Using classical fallback for SVD")
|
243
|
+
U, s, Vt = np.linalg.svd(X, full_matrices=False)
|
244
|
+
eigenvalues = (s ** 2) / (X.shape[0] - 1)
|
245
|
+
return eigenvalues[:self.n_components], Vt[:self.n_components].T
|
246
|
+
|
247
|
+
def _quantum_matrix_inversion(self, density_matrix: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
248
|
+
"""Quantum matrix inversion approach."""
|
249
|
+
logger.info("Running quantum matrix inversion for PCA")
|
250
|
+
|
251
|
+
try:
|
252
|
+
if hasattr(self.backend, 'quantum_matrix_inversion_pca'):
|
253
|
+
result = self.backend.quantum_matrix_inversion_pca(
|
254
|
+
density_matrix=density_matrix,
|
255
|
+
n_components=self.n_components,
|
256
|
+
condition_threshold=1e-6,
|
257
|
+
shots=self.shots
|
258
|
+
)
|
259
|
+
|
260
|
+
eigenvalues = result.get('eigenvalues', np.zeros(self.n_components))
|
261
|
+
eigenvectors = result.get('eigenvectors', np.eye(density_matrix.shape[0], self.n_components))
|
262
|
+
|
263
|
+
return eigenvalues, eigenvectors
|
264
|
+
else:
|
265
|
+
return self._fallback_matrix_inversion(density_matrix)
|
266
|
+
|
267
|
+
except Exception as e:
|
268
|
+
logger.error(f"Quantum matrix inversion failed: {e}")
|
269
|
+
return self._fallback_matrix_inversion(density_matrix)
|
270
|
+
|
271
|
+
def _fallback_matrix_inversion(self, density_matrix: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
272
|
+
"""Fallback matrix inversion using classical methods."""
|
273
|
+
logger.warning("Using classical fallback for matrix inversion")
|
274
|
+
return self._fallback_vqe_eigensolver(density_matrix)
|
275
|
+
|
276
|
+
def fit(self, X: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> 'QuantumPCA':
|
277
|
+
"""Fit quantum PCA to the data.
|
278
|
+
|
279
|
+
Args:
|
280
|
+
X: Training data
|
281
|
+
y: Ignored (unsupervised learning)
|
282
|
+
**kwargs: Additional fitting parameters
|
283
|
+
|
284
|
+
Returns:
|
285
|
+
Self for method chaining
|
286
|
+
|
287
|
+
"""
|
288
|
+
logger.info(f"Fitting QuantumPCA to data of shape {X.shape}")
|
289
|
+
|
290
|
+
# Validate and preprocess data
|
291
|
+
super().fit(X, y, **kwargs)
|
292
|
+
|
293
|
+
# Prepare data
|
294
|
+
X_processed = self._prepare_data_matrix(X)
|
295
|
+
|
296
|
+
# Determine quantum circuit size
|
297
|
+
self.n_qubits = self._determine_qubits(X.shape[1])
|
298
|
+
|
299
|
+
# Choose quantum method
|
300
|
+
if self.method == 'vqe':
|
301
|
+
density_matrix = self._create_density_matrix(X_processed)
|
302
|
+
eigenvalues, eigenvectors = self._quantum_eigensolver_vqe(density_matrix)
|
303
|
+
elif self.method == 'phase_estimation':
|
304
|
+
density_matrix = self._create_density_matrix(X_processed)
|
305
|
+
eigenvalues, eigenvectors = self._quantum_phase_estimation(density_matrix)
|
306
|
+
elif self.method == 'matrix_inversion':
|
307
|
+
density_matrix = self._create_density_matrix(X_processed)
|
308
|
+
eigenvalues, eigenvectors = self._quantum_matrix_inversion(density_matrix)
|
309
|
+
elif self.method == 'qsvd':
|
310
|
+
eigenvalues, eigenvectors = self._quantum_svd(X_processed)
|
311
|
+
else:
|
312
|
+
raise ValueError(f"Unknown quantum PCA method: {self.method}")
|
313
|
+
|
314
|
+
# Store results
|
315
|
+
self.eigenvalues_ = eigenvalues
|
316
|
+
self.components_ = eigenvectors.T # Store as rows
|
317
|
+
self.explained_variance_ = eigenvalues
|
318
|
+
|
319
|
+
# Calculate explained variance ratio
|
320
|
+
total_variance = np.sum(eigenvalues) if np.sum(eigenvalues) > 0 else 1.0
|
321
|
+
self.explained_variance_ratio_ = eigenvalues / total_variance
|
322
|
+
|
323
|
+
# Fit classical PCA for comparison/fallback
|
324
|
+
if self.classical_fallback:
|
325
|
+
try:
|
326
|
+
self.classical_pca.fit(X_processed)
|
327
|
+
except Exception as e:
|
328
|
+
logger.warning(f"Classical PCA fitting failed: {e}")
|
329
|
+
|
330
|
+
self.is_fitted = True
|
331
|
+
|
332
|
+
logger.info(f"Quantum PCA completed. Explained variance ratio: {self.explained_variance_ratio_}")
|
333
|
+
|
334
|
+
return self
|
335
|
+
|
336
|
+
def transform(self, X: np.ndarray) -> np.ndarray:
|
337
|
+
"""Transform data to lower dimensional space.
|
338
|
+
|
339
|
+
Args:
|
340
|
+
X: Data to transform
|
341
|
+
|
342
|
+
Returns:
|
343
|
+
Transformed data
|
344
|
+
|
345
|
+
"""
|
346
|
+
if not self.is_fitted:
|
347
|
+
raise ValueError("QuantumPCA must be fitted before transform")
|
348
|
+
|
349
|
+
# Preprocess data
|
350
|
+
if self.normalize_data:
|
351
|
+
X = self.scaler.transform(X)
|
352
|
+
|
353
|
+
# Center data
|
354
|
+
X_centered = X - self.mean_
|
355
|
+
|
356
|
+
# Project onto principal components
|
357
|
+
X_transformed = X_centered @ self.components_.T
|
358
|
+
|
359
|
+
return X_transformed
|
360
|
+
|
361
|
+
def inverse_transform(self, X_transformed: np.ndarray) -> np.ndarray:
|
362
|
+
"""Reconstruct data from lower dimensional representation.
|
363
|
+
|
364
|
+
Args:
|
365
|
+
X_transformed: Transformed data
|
366
|
+
|
367
|
+
Returns:
|
368
|
+
Reconstructed data
|
369
|
+
|
370
|
+
"""
|
371
|
+
if not self.is_fitted:
|
372
|
+
raise ValueError("QuantumPCA must be fitted before inverse_transform")
|
373
|
+
|
374
|
+
# Reconstruct in original space
|
375
|
+
X_reconstructed = X_transformed @ self.components_
|
376
|
+
|
377
|
+
# Add back the mean
|
378
|
+
X_reconstructed += self.mean_
|
379
|
+
|
380
|
+
# Inverse scaling if applied
|
381
|
+
if self.normalize_data:
|
382
|
+
X_reconstructed = self.scaler.inverse_transform(X_reconstructed)
|
383
|
+
|
384
|
+
return X_reconstructed
|
385
|
+
|
386
|
+
def fit_transform(self, X: np.ndarray, y: Optional[np.ndarray] = None) -> np.ndarray:
|
387
|
+
"""Fit PCA and transform data in one step."""
|
388
|
+
return self.fit(X, y).transform(X)
|
389
|
+
|
390
|
+
def predict(self, X: np.ndarray, **kwargs) -> np.ndarray:
|
391
|
+
"""Transform data (alias for transform method)."""
|
392
|
+
return self.transform(X)
|
393
|
+
|
394
|
+
def get_quantum_advantage_metrics(self) -> Dict[str, Any]:
|
395
|
+
"""Analyze potential quantum advantage."""
|
396
|
+
if not self.is_fitted:
|
397
|
+
raise ValueError("Must fit model first")
|
398
|
+
|
399
|
+
n_features = self.components_.shape[1]
|
400
|
+
|
401
|
+
metrics = {
|
402
|
+
'data_dimension': n_features,
|
403
|
+
'reduced_dimension': self.n_components,
|
404
|
+
'compression_ratio': n_features / self.n_components,
|
405
|
+
'quantum_circuit_qubits': self.n_qubits,
|
406
|
+
'quantum_vs_classical_qubits': self.n_qubits / int(np.ceil(np.log2(n_features))),
|
407
|
+
}
|
408
|
+
|
409
|
+
# Potential speedup estimates (theoretical)
|
410
|
+
classical_complexity = n_features ** 3 # O(d^3) for eigendecomposition
|
411
|
+
quantum_complexity = self.n_qubits ** 2 * np.log(n_features) # Estimated quantum complexity
|
412
|
+
|
413
|
+
metrics.update({
|
414
|
+
'classical_complexity_estimate': classical_complexity,
|
415
|
+
'quantum_complexity_estimate': quantum_complexity,
|
416
|
+
'theoretical_speedup': classical_complexity / quantum_complexity if quantum_complexity > 0 else 1,
|
417
|
+
})
|
418
|
+
|
419
|
+
return metrics
|
420
|
+
|
421
|
+
def compare_with_classical(self, X: np.ndarray) -> Dict[str, Any]:
|
422
|
+
"""Compare quantum PCA results with classical PCA."""
|
423
|
+
if not self.is_fitted or not hasattr(self.classical_pca, 'components_'):
|
424
|
+
raise ValueError("Both quantum and classical PCA must be fitted")
|
425
|
+
|
426
|
+
# Transform data with both methods
|
427
|
+
X_quantum = self.transform(X)
|
428
|
+
X_classical = self.classical_pca.transform(X - self.mean_)
|
429
|
+
|
430
|
+
# Compute reconstruction errors
|
431
|
+
X_quantum_reconstructed = self.inverse_transform(X_quantum)
|
432
|
+
X_classical_reconstructed = self.classical_pca.inverse_transform(X_classical) + self.mean_
|
433
|
+
|
434
|
+
quantum_error = np.mean((X - X_quantum_reconstructed) ** 2)
|
435
|
+
classical_error = np.mean((X - X_classical_reconstructed) ** 2)
|
436
|
+
|
437
|
+
# Compare explained variance
|
438
|
+
quantum_var_ratio = np.sum(self.explained_variance_ratio_)
|
439
|
+
classical_var_ratio = np.sum(self.classical_pca.explained_variance_ratio_)
|
440
|
+
|
441
|
+
# Component similarity (using absolute cosine similarity)
|
442
|
+
component_similarities = []
|
443
|
+
min_components = min(self.n_components, len(self.classical_pca.components_))
|
444
|
+
|
445
|
+
for i in range(min_components):
|
446
|
+
# Cosine similarity between components
|
447
|
+
cos_sim = np.abs(np.dot(self.components_[i], self.classical_pca.components_[i]))
|
448
|
+
cos_sim /= (np.linalg.norm(self.components_[i]) * np.linalg.norm(self.classical_pca.components_[i]))
|
449
|
+
component_similarities.append(cos_sim)
|
450
|
+
|
451
|
+
return {
|
452
|
+
'quantum_reconstruction_error': quantum_error,
|
453
|
+
'classical_reconstruction_error': classical_error,
|
454
|
+
'error_ratio': quantum_error / classical_error if classical_error > 0 else float('inf'),
|
455
|
+
'quantum_variance_explained': quantum_var_ratio,
|
456
|
+
'classical_variance_explained': classical_var_ratio,
|
457
|
+
'variance_explained_ratio': quantum_var_ratio / classical_var_ratio if classical_var_ratio > 0 else float('inf'),
|
458
|
+
'component_similarities': component_similarities,
|
459
|
+
'mean_component_similarity': np.mean(component_similarities) if component_similarities else 0,
|
460
|
+
}
|
461
|
+
|
462
|
+
def analyze_convergence(self) -> Dict[str, Any]:
|
463
|
+
"""Analyze convergence properties of the quantum algorithm."""
|
464
|
+
if not self.convergence_history:
|
465
|
+
return {'message': 'No convergence history available'}
|
466
|
+
|
467
|
+
convergence_data = np.array(self.convergence_history)
|
468
|
+
|
469
|
+
return {
|
470
|
+
'total_iterations': len(convergence_data),
|
471
|
+
'final_cost': convergence_data[-1],
|
472
|
+
'initial_cost': convergence_data[0],
|
473
|
+
'cost_reduction': convergence_data[0] - convergence_data[-1],
|
474
|
+
'converged': abs(convergence_data[-1] - convergence_data[-2]) < self.tolerance if len(convergence_data) > 1 else False,
|
475
|
+
'convergence_rate': np.mean(np.diff(convergence_data)) if len(convergence_data) > 1 else 0,
|
476
|
+
}
|
477
|
+
|
478
|
+
def get_params(self, deep: bool = True) -> Dict[str, Any]:
|
479
|
+
"""Get quantum PCA parameters."""
|
480
|
+
params = super().get_params(deep)
|
481
|
+
params.update({
|
482
|
+
'n_components': self.n_components,
|
483
|
+
'method': self.method,
|
484
|
+
'encoding': self.encoding,
|
485
|
+
'max_iterations': self.max_iterations,
|
486
|
+
'tolerance': self.tolerance,
|
487
|
+
'classical_fallback': self.classical_fallback,
|
488
|
+
'normalize_data': self.normalize_data,
|
489
|
+
})
|
490
|
+
return params
|
491
|
+
|
492
|
+
def set_params(self, **params) -> 'QuantumPCA':
|
493
|
+
"""Set quantum PCA parameters."""
|
494
|
+
if self.is_fitted and any(key in params for key in
|
495
|
+
['n_components', 'method', 'encoding']):
|
496
|
+
logger.warning("Changing core parameters requires refitting the model")
|
497
|
+
self.is_fitted = False
|
498
|
+
|
499
|
+
return super().set_params(**params)
|