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.
Files changed (46) hide show
  1. superquantx/__init__.py +321 -0
  2. superquantx/algorithms/__init__.py +55 -0
  3. superquantx/algorithms/base_algorithm.py +413 -0
  4. superquantx/algorithms/hybrid_classifier.py +628 -0
  5. superquantx/algorithms/qaoa.py +406 -0
  6. superquantx/algorithms/quantum_agents.py +1006 -0
  7. superquantx/algorithms/quantum_kmeans.py +575 -0
  8. superquantx/algorithms/quantum_nn.py +544 -0
  9. superquantx/algorithms/quantum_pca.py +499 -0
  10. superquantx/algorithms/quantum_svm.py +346 -0
  11. superquantx/algorithms/vqe.py +553 -0
  12. superquantx/algorithms.py +863 -0
  13. superquantx/backends/__init__.py +265 -0
  14. superquantx/backends/base_backend.py +321 -0
  15. superquantx/backends/braket_backend.py +420 -0
  16. superquantx/backends/cirq_backend.py +466 -0
  17. superquantx/backends/ocean_backend.py +491 -0
  18. superquantx/backends/pennylane_backend.py +419 -0
  19. superquantx/backends/qiskit_backend.py +451 -0
  20. superquantx/backends/simulator_backend.py +455 -0
  21. superquantx/backends/tket_backend.py +519 -0
  22. superquantx/circuits.py +447 -0
  23. superquantx/cli/__init__.py +28 -0
  24. superquantx/cli/commands.py +528 -0
  25. superquantx/cli/main.py +254 -0
  26. superquantx/client.py +298 -0
  27. superquantx/config.py +326 -0
  28. superquantx/exceptions.py +287 -0
  29. superquantx/gates.py +588 -0
  30. superquantx/logging_config.py +347 -0
  31. superquantx/measurements.py +702 -0
  32. superquantx/ml.py +936 -0
  33. superquantx/noise.py +760 -0
  34. superquantx/utils/__init__.py +83 -0
  35. superquantx/utils/benchmarking.py +523 -0
  36. superquantx/utils/classical_utils.py +575 -0
  37. superquantx/utils/feature_mapping.py +467 -0
  38. superquantx/utils/optimization.py +410 -0
  39. superquantx/utils/quantum_utils.py +456 -0
  40. superquantx/utils/visualization.py +654 -0
  41. superquantx/version.py +33 -0
  42. superquantx-0.1.0.dist-info/METADATA +365 -0
  43. superquantx-0.1.0.dist-info/RECORD +46 -0
  44. superquantx-0.1.0.dist-info/WHEEL +4 -0
  45. superquantx-0.1.0.dist-info/entry_points.txt +2 -0
  46. superquantx-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,553 @@
1
+ """Variational Quantum Eigensolver (VQE) implementation.
2
+
3
+ This module provides a VQE implementation for finding ground state energies
4
+ and eigenvalues of quantum systems using parameterized quantum circuits.
5
+ """
6
+
7
+ import logging
8
+ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
9
+
10
+ import numpy as np
11
+ from scipy.optimize import minimize
12
+
13
+ from .base_algorithm import OptimizationQuantumAlgorithm
14
+
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ class VQE(OptimizationQuantumAlgorithm):
19
+ """Variational Quantum Eigensolver for finding ground states.
20
+
21
+ VQE is a hybrid quantum-classical algorithm that uses a parameterized
22
+ quantum circuit (ansatz) to find the ground state energy of a given
23
+ Hamiltonian by minimizing the expectation value.
24
+
25
+ The algorithm works by:
26
+ 1. Preparing a parameterized quantum state |ψ(θ)⟩
27
+ 2. Measuring the expectation value ⟨ψ(θ)|H|ψ(θ)⟩
28
+ 3. Classically optimizing parameters θ to minimize energy
29
+ 4. Iterating until convergence
30
+
31
+ Args:
32
+ backend: Quantum backend for circuit execution
33
+ hamiltonian: Target Hamiltonian (matrix or operator)
34
+ ansatz: Parameterized circuit ansatz ('UCCSD', 'RealAmplitudes', etc.)
35
+ optimizer: Classical optimizer ('COBYLA', 'L-BFGS-B', etc.)
36
+ shots: Number of measurement shots
37
+ maxiter: Maximum optimization iterations
38
+ initial_params: Initial parameter values
39
+ **kwargs: Additional parameters
40
+
41
+ Example:
42
+ >>> # Define H2 molecule Hamiltonian
43
+ >>> H2_hamiltonian = create_h2_hamiltonian(bond_distance=0.74)
44
+ >>> vqe = VQE(backend='pennylane', hamiltonian=H2_hamiltonian, ansatz='UCCSD')
45
+ >>> result = vqe.optimize()
46
+ >>> ground_energy = result.result['optimal_value']
47
+
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ hamiltonian: Union[np.ndarray, Any],
53
+ ansatz: Union[str, Callable] = 'RealAmplitudes',
54
+ backend: Union[str, Any] = 'simulator',
55
+ optimizer: str = 'COBYLA',
56
+ shots: int = 1024,
57
+ maxiter: int = 1000,
58
+ initial_params: Optional[np.ndarray] = None,
59
+ include_custom_gates: bool = False,
60
+ client = None,
61
+ **kwargs
62
+ ) -> None:
63
+ super().__init__(backend=backend, shots=shots, **kwargs)
64
+
65
+ self.hamiltonian = hamiltonian
66
+ self.ansatz = ansatz
67
+ self.optimizer = optimizer
68
+ self.maxiter = maxiter
69
+ self.initial_params = initial_params
70
+ self.include_custom_gates = include_custom_gates
71
+ self.client = client
72
+
73
+ # VQE-specific attributes
74
+ self.n_qubits = None
75
+ self.n_params = None
76
+ self.ansatz_circuit = None
77
+ self.hamiltonian_terms = None
78
+
79
+ # Convergence tracking
80
+ self.energy_history = []
81
+ self.gradient_history = []
82
+ self.convergence_threshold = 1e-6
83
+
84
+ self._initialize_hamiltonian()
85
+
86
+ logger.info(f"Initialized VQE with ansatz={ansatz}, optimizer={optimizer}")
87
+
88
+ def _initialize_hamiltonian(self) -> None:
89
+ """Initialize and validate Hamiltonian."""
90
+ if isinstance(self.hamiltonian, np.ndarray):
91
+ if len(self.hamiltonian.shape) != 2 or self.hamiltonian.shape[0] != self.hamiltonian.shape[1]:
92
+ raise ValueError("Hamiltonian must be a square matrix")
93
+ self.n_qubits = int(np.log2(self.hamiltonian.shape[0]))
94
+ if 2**self.n_qubits != self.hamiltonian.shape[0]:
95
+ raise ValueError("Hamiltonian dimension must be a power of 2")
96
+
97
+ # Decompose Hamiltonian into Pauli strings if needed
98
+ self.hamiltonian_terms = self._decompose_hamiltonian()
99
+ else:
100
+ # Assume it's already in the correct format for the backend
101
+ self.hamiltonian_terms = self.hamiltonian
102
+ self.n_qubits = self._infer_qubits_from_hamiltonian()
103
+
104
+ def _decompose_hamiltonian(self) -> List[Tuple[float, str]]:
105
+ """Decompose Hamiltonian into Pauli string representation."""
106
+ if hasattr(self.backend, 'decompose_hamiltonian'):
107
+ return self.backend.decompose_hamiltonian(self.hamiltonian)
108
+ else:
109
+ return self._fallback_decomposition()
110
+
111
+ def _fallback_decomposition(self) -> List[Tuple[float, str]]:
112
+ """Fallback Hamiltonian decomposition."""
113
+ logger.warning("Using fallback Hamiltonian decomposition")
114
+ # Simple placeholder - would need proper Pauli decomposition
115
+ return [(1.0, 'Z0'), (0.5, 'Z1')]
116
+
117
+ def _infer_qubits_from_hamiltonian(self) -> int:
118
+ """Infer number of qubits from Hamiltonian representation."""
119
+ if hasattr(self.hamiltonian_terms, '__len__'):
120
+ return 2 # Default fallback
121
+ return 2
122
+
123
+ def _create_ansatz_circuit(self, params: np.ndarray) -> Any:
124
+ """Create ansatz circuit with given parameters.
125
+
126
+ Args:
127
+ params: Circuit parameters
128
+
129
+ Returns:
130
+ Parameterized quantum circuit
131
+
132
+ """
133
+ try:
134
+ if hasattr(self.backend, 'create_ansatz'):
135
+ return self.backend.create_ansatz(
136
+ ansatz_type=self.ansatz,
137
+ n_qubits=self.n_qubits,
138
+ params=params,
139
+ include_custom_gates=self.include_custom_gates
140
+ )
141
+ else:
142
+ return self._fallback_ansatz(params)
143
+ except Exception as e:
144
+ logger.error(f"Failed to create ansatz circuit: {e}")
145
+ return self._fallback_ansatz(params)
146
+
147
+ def _fallback_ansatz(self, params: np.ndarray) -> Any:
148
+ """Fallback ansatz implementation."""
149
+ logger.warning("Using fallback ansatz implementation")
150
+ return None
151
+
152
+ def _compute_expectation_value(self, params: np.ndarray) -> float:
153
+ """Compute expectation value ⟨ψ(θ)|H|ψ(θ)⟩.
154
+
155
+ Args:
156
+ params: Circuit parameters
157
+
158
+ Returns:
159
+ Hamiltonian expectation value
160
+
161
+ """
162
+ try:
163
+ # Create ansatz circuit
164
+ circuit = self._create_ansatz_circuit(params)
165
+
166
+ # Compute expectation value
167
+ if hasattr(self.backend, 'compute_expectation'):
168
+ expectation = self.backend.compute_expectation(
169
+ circuit=circuit,
170
+ hamiltonian=self.hamiltonian_terms,
171
+ shots=self.shots
172
+ )
173
+ else:
174
+ expectation = self._fallback_expectation(circuit, params)
175
+
176
+ # Store energy history
177
+ self.energy_history.append(expectation)
178
+
179
+ return float(expectation)
180
+
181
+ except Exception as e:
182
+ logger.error(f"Error computing expectation value: {e}")
183
+ return float('inf')
184
+
185
+ def _fallback_expectation(self, circuit: Any, params: np.ndarray) -> float:
186
+ """Fallback expectation value computation."""
187
+ logger.warning("Using fallback expectation computation")
188
+ # Simple placeholder - would compute ⟨ψ|H|ψ⟩ classically
189
+ return np.random.random() - 0.5
190
+
191
+ def _compute_gradient(self, params: np.ndarray) -> np.ndarray:
192
+ """Compute parameter gradients using parameter-shift rule.
193
+
194
+ Args:
195
+ params: Current parameters
196
+
197
+ Returns:
198
+ Gradient vector
199
+
200
+ """
201
+ gradients = np.zeros_like(params)
202
+ shift = np.pi / 2 # Parameter-shift rule
203
+
204
+ for i in range(len(params)):
205
+ # Forward shift
206
+ params_plus = params.copy()
207
+ params_plus[i] += shift
208
+ energy_plus = self._compute_expectation_value(params_plus)
209
+
210
+ # Backward shift
211
+ params_minus = params.copy()
212
+ params_minus[i] -= shift
213
+ energy_minus = self._compute_expectation_value(params_minus)
214
+
215
+ # Gradient via parameter-shift rule
216
+ gradients[i] = 0.5 * (energy_plus - energy_minus)
217
+
218
+ self.gradient_history.append(np.linalg.norm(gradients))
219
+ return gradients
220
+
221
+ def fit(self, X: Optional[np.ndarray] = None, y: Optional[np.ndarray] = None, **kwargs) -> 'VQE':
222
+ """Fit VQE (setup for optimization).
223
+
224
+ Args:
225
+ X: Not used in VQE
226
+ y: Not used in VQE
227
+ **kwargs: Additional parameters
228
+
229
+ Returns:
230
+ Self for method chaining
231
+
232
+ """
233
+ logger.info(f"Setting up VQE for {self.n_qubits} qubits")
234
+
235
+ # Determine number of parameters based on ansatz
236
+ self.n_params = self._get_ansatz_param_count()
237
+
238
+ # Initialize parameters if not provided
239
+ if self.initial_params is None:
240
+ self.initial_params = self._generate_initial_params()
241
+
242
+ # Reset histories
243
+ self.energy_history = []
244
+ self.gradient_history = []
245
+ self.optimization_history_ = []
246
+
247
+ self.is_fitted = True
248
+ return self
249
+
250
+ def _get_ansatz_param_count(self) -> int:
251
+ """Get number of parameters for the ansatz."""
252
+ if hasattr(self.backend, 'get_ansatz_param_count'):
253
+ return self.backend.get_ansatz_param_count(self.ansatz, self.n_qubits)
254
+ else:
255
+ # Default parameter counts for common ansatzes
256
+ param_counts = {
257
+ 'RealAmplitudes': 2 * self.n_qubits,
258
+ 'UCCSD': 4 * self.n_qubits, # Simplified estimate
259
+ 'EfficientSU2': 3 * self.n_qubits,
260
+ 'TwoLocal': 2 * self.n_qubits,
261
+ }
262
+ return param_counts.get(self.ansatz, 2 * self.n_qubits)
263
+
264
+ def _generate_initial_params(self) -> np.ndarray:
265
+ """Generate random initial parameters."""
266
+ return np.random.uniform(-np.pi, np.pi, self.n_params)
267
+
268
+ def predict(self, X: Optional[np.ndarray] = None, **kwargs) -> np.ndarray:
269
+ """Get ground state wavefunction coefficients.
270
+
271
+ Args:
272
+ X: Not used
273
+ **kwargs: Additional parameters
274
+
275
+ Returns:
276
+ Ground state wavefunction
277
+
278
+ """
279
+ if not self.optimal_params_:
280
+ raise ValueError("VQE must be optimized before prediction")
281
+
282
+ # Create circuit with optimal parameters
283
+ circuit = self._create_ansatz_circuit(self.optimal_params_)
284
+
285
+ # Get state vector
286
+ if hasattr(self.backend, 'get_statevector'):
287
+ statevector = self.backend.get_statevector(circuit)
288
+ else:
289
+ # Fallback: return random normalized state
290
+ statevector = np.random.random(2**self.n_qubits) + 1j * np.random.random(2**self.n_qubits)
291
+ statevector /= np.linalg.norm(statevector)
292
+
293
+ return np.array(statevector)
294
+
295
+ def _run_optimization(self, objective_function=None, initial_params: Optional[np.ndarray] = None, **kwargs):
296
+ """Run VQE optimization.
297
+
298
+ Args:
299
+ objective_function: Not used (VQE has its own objective)
300
+ initial_params: Initial parameter guess
301
+ **kwargs: Additional optimization parameters
302
+
303
+ Returns:
304
+ Optimization result
305
+
306
+ """
307
+ if not self.is_fitted:
308
+ raise ValueError("VQE must be fitted before optimization")
309
+
310
+ # Use provided initial parameters or default
311
+ if initial_params is None:
312
+ initial_params = self.initial_params
313
+
314
+ logger.info(f"Starting VQE optimization with {len(initial_params)} parameters")
315
+
316
+ # Define objective function for minimization
317
+ def objective(params):
318
+ energy = self._compute_expectation_value(params)
319
+
320
+ # Store optimization history
321
+ self.optimization_history_.append({
322
+ 'params': params.copy(),
323
+ 'energy': energy,
324
+ 'iteration': len(self.optimization_history_)
325
+ })
326
+
327
+ return energy
328
+
329
+ # Run classical optimization
330
+ try:
331
+ result = minimize(
332
+ fun=objective,
333
+ x0=initial_params,
334
+ method=self.optimizer,
335
+ options={
336
+ 'maxiter': self.maxiter,
337
+ 'disp': True
338
+ },
339
+ jac=self._compute_gradient if self.optimizer in ['L-BFGS-B', 'SLSQP'] else None
340
+ )
341
+
342
+ self.optimal_params_ = result.x
343
+ self.optimal_value_ = result.fun
344
+
345
+ logger.info(f"VQE optimization completed. Ground energy: {self.optimal_value_:.6f}")
346
+
347
+ return {
348
+ 'optimal_params': self.optimal_params_,
349
+ 'ground_energy': self.optimal_value_,
350
+ 'success': result.success,
351
+ 'message': result.message,
352
+ 'n_iterations': result.nfev,
353
+ }
354
+
355
+ except Exception as e:
356
+ logger.error(f"VQE optimization failed: {e}")
357
+ raise
358
+
359
+ def get_energy_landscape(self, param_indices: List[int], param_ranges: List[Tuple[float, float]],
360
+ resolution: int = 20) -> Dict[str, Any]:
361
+ """Compute energy landscape for visualization.
362
+
363
+ Args:
364
+ param_indices: Indices of parameters to vary
365
+ param_ranges: Ranges for each parameter
366
+ resolution: Number of points per dimension
367
+
368
+ Returns:
369
+ Dictionary with landscape data
370
+
371
+ """
372
+ if len(param_indices) != 2:
373
+ raise ValueError("Energy landscape visualization supports only 2 parameters")
374
+
375
+ if not self.optimal_params_:
376
+ raise ValueError("VQE must be optimized to compute landscape")
377
+
378
+ param1_range = np.linspace(*param_ranges[0], resolution)
379
+ param2_range = np.linspace(*param_ranges[1], resolution)
380
+
381
+ landscape = np.zeros((resolution, resolution))
382
+ base_params = self.optimal_params_.copy()
383
+
384
+ for i, p1 in enumerate(param1_range):
385
+ for j, p2 in enumerate(param2_range):
386
+ params = base_params.copy()
387
+ params[param_indices[0]] = p1
388
+ params[param_indices[1]] = p2
389
+ landscape[i, j] = self._compute_expectation_value(params)
390
+
391
+ return {
392
+ 'param1_range': param1_range,
393
+ 'param2_range': param2_range,
394
+ 'landscape': landscape,
395
+ 'optimal_params': self.optimal_params_[param_indices],
396
+ 'param_indices': param_indices
397
+ }
398
+
399
+ def analyze_convergence(self) -> Dict[str, Any]:
400
+ """Analyze VQE convergence properties.
401
+
402
+ Returns:
403
+ Convergence analysis results
404
+
405
+ """
406
+ if not self.energy_history:
407
+ raise ValueError("No optimization history available")
408
+
409
+ energies = np.array(self.energy_history)
410
+ gradients = np.array(self.gradient_history) if self.gradient_history else None
411
+
412
+ # Basic convergence metrics
413
+ analysis = {
414
+ 'final_energy': energies[-1],
415
+ 'energy_variance': np.var(energies[-10:]) if len(energies) >= 10 else np.var(energies),
416
+ 'total_iterations': len(energies),
417
+ 'energy_change': abs(energies[-1] - energies[0]) if len(energies) > 1 else 0,
418
+ }
419
+
420
+ # Convergence detection
421
+ if len(energies) >= 10:
422
+ recent_change = abs(energies[-1] - energies[-10])
423
+ analysis['converged'] = recent_change < self.convergence_threshold
424
+ else:
425
+ analysis['converged'] = False
426
+
427
+ # Gradient analysis
428
+ if gradients is not None and len(gradients) > 0:
429
+ analysis.update({
430
+ 'final_gradient_norm': gradients[-1],
431
+ 'gradient_trend': 'decreasing' if gradients[-1] < gradients[0] else 'increasing',
432
+ 'min_gradient_norm': np.min(gradients),
433
+ })
434
+
435
+ # Identify plateaus and oscillations
436
+ if len(energies) >= 20:
437
+ # Check for plateaus (little change over many iterations)
438
+ plateau_threshold = self.convergence_threshold * 10
439
+ recent_energies = energies[-20:]
440
+ energy_std = np.std(recent_energies)
441
+ analysis['plateau_detected'] = energy_std < plateau_threshold
442
+
443
+ # Check for oscillations
444
+ energy_diff = np.diff(energies[-20:])
445
+ sign_changes = np.sum(np.diff(np.sign(energy_diff)) != 0)
446
+ analysis['oscillation_detected'] = sign_changes > len(energy_diff) * 0.7
447
+
448
+ return analysis
449
+
450
+ def compare_with_exact(self, exact_ground_energy: float) -> Dict[str, Any]:
451
+ """Compare VQE result with exact ground state energy.
452
+
453
+ Args:
454
+ exact_ground_energy: Known exact ground state energy
455
+
456
+ Returns:
457
+ Comparison analysis
458
+
459
+ """
460
+ if not self.optimal_value_:
461
+ raise ValueError("VQE must be optimized for comparison")
462
+
463
+ error = abs(self.optimal_value_ - exact_ground_energy)
464
+ relative_error = error / abs(exact_ground_energy) if exact_ground_energy != 0 else float('inf')
465
+
466
+ return {
467
+ 'vqe_energy': self.optimal_value_,
468
+ 'exact_energy': exact_ground_energy,
469
+ 'absolute_error': error,
470
+ 'relative_error': relative_error,
471
+ 'chemical_accuracy': error < 1.6e-3, # 1 kcal/mol in Hartree
472
+ 'energy_above_ground': max(0, self.optimal_value_ - exact_ground_energy)
473
+ }
474
+
475
+ def get_params(self, deep: bool = True) -> Dict[str, Any]:
476
+ """Get VQE parameters."""
477
+ params = super().get_params(deep)
478
+ params.update({
479
+ 'ansatz': self.ansatz,
480
+ 'optimizer': self.optimizer,
481
+ 'maxiter': self.maxiter,
482
+ 'include_custom_gates': self.include_custom_gates,
483
+ 'convergence_threshold': self.convergence_threshold,
484
+ })
485
+ return params
486
+
487
+ def set_params(self, **params) -> 'VQE':
488
+ """Set VQE parameters."""
489
+ if self.is_fitted and any(key in params for key in ['ansatz', 'hamiltonian']):
490
+ logger.warning("Changing core parameters requires refitting the model")
491
+ self.is_fitted = False
492
+
493
+ return super().set_params(**params)
494
+
495
+
496
+ def create_vqe_for_molecule(
497
+ molecule_name: str,
498
+ bond_distance: float = None,
499
+ backend: str = 'simulator',
500
+ ansatz: str = 'UCCSD',
501
+ optimizer: str = 'COBYLA',
502
+ client = None
503
+ ) -> VQE:
504
+ """Create a VQE instance pre-configured for molecular simulation.
505
+
506
+ Args:
507
+ molecule_name: Name of the molecule (e.g., 'H2', 'LiH')
508
+ bond_distance: Bond distance for the molecule (uses default if None)
509
+ backend: Quantum backend to use
510
+ ansatz: Ansatz circuit type
511
+ optimizer: Classical optimizer
512
+ client: Optional client for quantum execution
513
+
514
+ Returns:
515
+ Configured VQE instance
516
+
517
+ """
518
+ # Import molecular data utilities
519
+ try:
520
+ from ..datasets.molecular import get_molecular_hamiltonian
521
+ hamiltonian = get_molecular_hamiltonian(molecule_name, bond_distance)
522
+ except ImportError:
523
+ # Fallback: create simple hamiltonian for testing
524
+ from ..gates import Hamiltonian
525
+
526
+ if molecule_name.upper() == 'H2':
527
+ # Simple H2 Hamiltonian approximation as Pauli strings
528
+ hamiltonian_dict = {
529
+ "ZZ": -1.0523732,
530
+ "ZI": -0.39793742,
531
+ "IZ": -0.39793742,
532
+ "XX": -0.01128010,
533
+ "YY": 0.01128010
534
+ }
535
+ hamiltonian = Hamiltonian.from_dict(hamiltonian_dict)
536
+ elif molecule_name.upper() in ['LIH', 'H2O', 'NH3']:
537
+ # Generic 2-qubit Hamiltonian for other molecules
538
+ hamiltonian_dict = {
539
+ "ZI": -1.0,
540
+ "IZ": 0.5,
541
+ "XX": 0.2
542
+ }
543
+ hamiltonian = Hamiltonian.from_dict(hamiltonian_dict)
544
+ else:
545
+ raise ValueError(f"Unknown molecule: {molecule_name}")
546
+
547
+ return VQE(
548
+ hamiltonian=hamiltonian,
549
+ ansatz=ansatz,
550
+ backend=backend,
551
+ optimizer=optimizer,
552
+ client=client
553
+ )