wings-quantum 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.
wings/__init__.py ADDED
@@ -0,0 +1,251 @@
1
+ """
2
+ Gaussian State Optimizer
3
+ ========================
4
+
5
+ GPU-accelerated variational quantum state preparation for Gaussian wavefunctions.
6
+
7
+ This package provides high-performance tools for preparing Gaussian quantum states
8
+ using variational quantum algorithms with support for multiple acceleration backends:
9
+
10
+ - CPU (Qiskit Statevector)
11
+ - GPU (Qiskit Aer)
12
+ - cuStateVec (NVIDIA cuQuantum)
13
+
14
+ Quick Start
15
+ -----------
16
+ >>> from wings import GaussianOptimizer, OptimizerConfig
17
+ >>>
18
+ >>> # Configure optimization
19
+ >>> config = OptimizerConfig(
20
+ ... n_qubits=8,
21
+ ... sigma=0.5,
22
+ ... use_custatevec=True, # Enable GPU acceleration
23
+ ... )
24
+ >>>
25
+ >>> # Create optimizer and run
26
+ >>> optimizer = GaussianOptimizer(config)
27
+ >>> results = optimizer.optimize_ultra_precision(target_infidelity=1e-10)
28
+ >>> print(f"Fidelity: {results['fidelity']:.12f}")
29
+
30
+ For production campaigns with thousands of runs:
31
+
32
+ >>> from wings import run_production_campaign
33
+ >>>
34
+ >>> results = run_production_campaign(
35
+ ... n_qubits=8,
36
+ ... sigma=0.5,
37
+ ... total_runs=1000,
38
+ ... target_infidelity=1e-11,
39
+ ... )
40
+ >>> results.print_summary()
41
+
42
+ Environment Variables
43
+ --------------------
44
+ GSO_BASE_DIR : str
45
+ Base directory for all data storage (default: ~/.wings)
46
+ GSO_CACHE_DIR : str
47
+ Directory for coefficient cache
48
+ GSO_CHECKPOINT_DIR : str
49
+ Directory for optimization checkpoints
50
+ GSO_CAMPAIGN_DIR : str
51
+ Directory for campaign results
52
+
53
+ See Also
54
+ --------
55
+ - Documentation: https://gaussian-state-optimizer.readthedocs.io
56
+ - GitHub: https://github.com/yourusername/gaussian-state-optimizer
57
+ """
58
+
59
+ __version__ = "0.1.0"
60
+ __author__ = "Joshua M. Courtney"
61
+ __email__ = "joshuamcourtney@gmail.com"
62
+ __license__ = "MIT"
63
+
64
+ # Version tuple for programmatic comparison
65
+ VERSION = tuple(int(x) for x in __version__.split(".")[:3])
66
+
67
+
68
+ # Lazy imports to avoid loading heavy dependencies until needed
69
+ def __getattr__(name: str):
70
+ """Lazy import of main classes to speed up package import."""
71
+
72
+ _public_api = {
73
+ # Core classes
74
+ "GaussianOptimizer": ".optimizer",
75
+ "OptimizerConfig": ".config",
76
+ "OptimizationPipeline": ".config",
77
+ "CampaignConfig": ".config",
78
+ "CampaignResults": ".campaign",
79
+ "OptimizationManager": ".campaign",
80
+ "TargetFunction": ".config",
81
+ # Ansatz classes
82
+ "DefaultAnsatz": ".ansatz",
83
+ "CustomHardwareEfficientAnsatz": ".ansatz",
84
+ "AnsatzProtocol": ".ansatz",
85
+ # Evaluators
86
+ "CuStateVecEvaluator": ".evaluators.custatevec",
87
+ "BatchedCuStateVecEvaluator": ".evaluators.custatevec",
88
+ "GPUCircuitEvaluator": ".evaluators.gpu",
89
+ "ThreadSafeCircuitEvaluator": ".evaluators.cpu",
90
+ "MultiGPUBatchEvaluator": ".evaluators.custatevec",
91
+ # Convenience functions
92
+ "run_production_campaign": ".campaign",
93
+ "quick_optimization": ".campaign",
94
+ "load_campaign_results": ".campaign",
95
+ "list_campaigns": ".campaign",
96
+ # Path configuration
97
+ "PathConfig": ".paths",
98
+ "get_path_config": ".paths",
99
+ # Compatibility
100
+ "CuQuantumCompat": ".compat",
101
+ "get_cuda_dtype": ".compat",
102
+ "get_compute_type": ".compat",
103
+ "optimize_gaussian_state": ".convenience",
104
+ "quick_optimize": ".convenience",
105
+ # Benchmarking
106
+ "benchmark_gpu": ".benchmarks",
107
+ "find_gpu_crossover": ".benchmarks",
108
+ "benchmark_all_backends": ".benchmarks",
109
+ # Circuit export
110
+ "build_optimized_circuit": ".export",
111
+ "export_to_qasm": ".export",
112
+ "export_to_qasm3": ".export",
113
+ "save_circuit": ".export",
114
+ }
115
+
116
+ if name in _public_api:
117
+ import importlib
118
+
119
+ module_path = _public_api[name]
120
+ module = importlib.import_module(module_path, package=__name__)
121
+ return getattr(module, name)
122
+
123
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
124
+
125
+
126
+ # Explicitly define __all__ for `from wings import *`
127
+ __all__ = [
128
+ # Version info
129
+ "__version__",
130
+ "VERSION",
131
+ # Core classes
132
+ "GaussianOptimizer",
133
+ "OptimizerConfig",
134
+ "OptimizationPipeline",
135
+ "TargetFunction",
136
+ "CampaignConfig",
137
+ "CampaignResults",
138
+ "OptimizationManager",
139
+ # Ansatz
140
+ "DefaultAnsatz",
141
+ "CustomHardwareEfficientAnsatz",
142
+ "AnsatzProtocol",
143
+ # Evaluators
144
+ "CuStateVecEvaluator",
145
+ "BatchedCuStateVecEvaluator",
146
+ "MultiGPUBatchEvaluator",
147
+ "GPUCircuitEvaluator",
148
+ "ThreadSafeCircuitEvaluator",
149
+ # Functions
150
+ "run_production_campaign",
151
+ "quick_optimization",
152
+ "load_campaign_results",
153
+ "list_campaigns",
154
+ # Configuration
155
+ "PathConfig",
156
+ "get_path_config",
157
+ # Compatibility
158
+ "CuQuantumCompat",
159
+ "get_cuda_dtype",
160
+ "get_compute_type",
161
+ "optimize_gaussian_state",
162
+ "quick_optimize",
163
+ # Benchmarks
164
+ "benchmark_gpu",
165
+ "find_gpu_crossover",
166
+ "benchmark_all_backends",
167
+ "build_optimized_circuit",
168
+ "export_to_qasm",
169
+ "export_to_qasm3",
170
+ "save_circuit",
171
+ ]
172
+
173
+
174
+ def get_backend_info() -> dict:
175
+ """
176
+ Get information about available backends.
177
+
178
+ Returns
179
+ -------
180
+ dict
181
+ Dictionary containing:
182
+ - 'cpu': Always True
183
+ - 'gpu_aer': True if qiskit-aer is available
184
+ - 'custatevec': True if cuQuantum is available
185
+ - 'cuda_version': CUDA version string or None
186
+ - 'gpu_name': GPU device name or None
187
+ """
188
+ from typing import Any
189
+
190
+ info: dict[str, Any] = {
191
+ "cpu": True,
192
+ "gpu_aer": False,
193
+ "custatevec": False,
194
+ "cuda_version": None,
195
+ "gpu_name": None,
196
+ }
197
+
198
+ # Check Qiskit Aer
199
+ try:
200
+ from qiskit_aer import AerSimulator
201
+
202
+ backend = AerSimulator(method="statevector")
203
+ info["gpu_aer"] = "GPU" in backend.available_devices()
204
+ except ImportError:
205
+ pass
206
+ except Exception:
207
+ pass
208
+
209
+ # Check cuStateVec
210
+ try:
211
+ import cupy as cp
212
+ from cuquantum.bindings import custatevec
213
+
214
+ info["custatevec"] = True
215
+
216
+ # Get CUDA info
217
+ device = cp.cuda.Device(0)
218
+ info["gpu_name"] = (
219
+ device.name.decode() if hasattr(device.name, "decode") else str(device.name)
220
+ )
221
+
222
+ cuda_version = cp.cuda.runtime.runtimeGetVersion()
223
+ major = cuda_version // 1000
224
+ minor = (cuda_version % 1000) // 10
225
+ info["cuda_version"] = f"{major}.{minor}"
226
+ except ImportError:
227
+ pass
228
+ except Exception:
229
+ pass
230
+
231
+ return info
232
+
233
+
234
+ def print_backend_info() -> None:
235
+ """Print information about available backends."""
236
+ info = get_backend_info()
237
+
238
+ print("Gaussian State Optimizer - Backend Information")
239
+ print("=" * 50)
240
+ print(f" CPU (Qiskit Statevector): {'✓ Available' if info['cpu'] else '✗ Not available'}")
241
+ print(f" GPU (Qiskit Aer): {'✓ Available' if info['gpu_aer'] else '✗ Not available'}")
242
+ print(
243
+ f" cuStateVec: {'✓ Available' if info['custatevec'] else '✗ Not available'}"
244
+ )
245
+
246
+ if info["cuda_version"]:
247
+ print(f" CUDA Version: {info['cuda_version']}")
248
+ if info["gpu_name"]:
249
+ print(f" GPU Device: {info['gpu_name']}")
250
+
251
+ print("=" * 50)
wings/adam.py ADDED
@@ -0,0 +1,132 @@
1
+ """Adam optimizer implementations."""
2
+
3
+ import numpy as np
4
+
5
+ from .types import FloatArray, ParameterArray
6
+
7
+ __all__ = [
8
+ "AdamOptimizer",
9
+ "AdamWithRestarts",
10
+ ]
11
+
12
+
13
+ class AdamOptimizer:
14
+ """
15
+ Adam optimizer for variational quantum circuits.
16
+
17
+ Adam is effective for:
18
+ - Noisy/stochastic gradients
19
+ - Escaping shallow local minima via momentum
20
+ - Adaptive learning rates per parameter
21
+
22
+ For VQCs, we combine Adam with exact parameter-shift gradients.
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ n_params: int,
28
+ learning_rate: float = 0.01,
29
+ beta1: float = 0.9,
30
+ beta2: float = 0.999,
31
+ epsilon: float = 1e-8,
32
+ amsgrad: bool = False,
33
+ ) -> None:
34
+ self.n_params = n_params
35
+ self.lr = learning_rate
36
+ self.beta1 = beta1
37
+ self.beta2 = beta2
38
+ self.epsilon = epsilon
39
+ self.amsgrad = amsgrad
40
+
41
+ # Initialize moment estimates
42
+ self.m = np.zeros(n_params) # First moment (momentum)
43
+ self.v = np.zeros(n_params) # Second moment (RMSprop)
44
+ self.v_hat_max = np.zeros(n_params) # For AMSGrad
45
+ self.t = 0 # Timestep
46
+
47
+ def step(self, params: ParameterArray, gradient: FloatArray) -> ParameterArray:
48
+ """
49
+ Perform one Adam update step.
50
+
51
+ Args:
52
+ params: Current parameters
53
+ gradient: Gradient of loss w.r.t. parameters
54
+
55
+ Returns:
56
+ Updated parameters
57
+ """
58
+ self.t += 1
59
+
60
+ # Update biased first moment estimate
61
+ self.m = self.beta1 * self.m + (1 - self.beta1) * gradient
62
+
63
+ # Update biased second moment estimate
64
+ self.v = self.beta2 * self.v + (1 - self.beta2) * (gradient**2)
65
+
66
+ # Compute bias-corrected estimates
67
+ m_hat = self.m / (1 - self.beta1**self.t)
68
+ v_hat = self.v / (1 - self.beta2**self.t)
69
+
70
+ if self.amsgrad:
71
+ # AMSGrad: use maximum of past v_hat values
72
+ self.v_hat_max = np.maximum(self.v_hat_max, v_hat)
73
+ v_hat = self.v_hat_max
74
+
75
+ # Compute update
76
+ update = self.lr * m_hat / (np.sqrt(v_hat) + self.epsilon)
77
+
78
+ return params - update
79
+
80
+ def reset(self) -> None:
81
+ """Reset optimizer state (for restarts)"""
82
+ self.m = np.zeros(self.n_params)
83
+ self.v = np.zeros(self.n_params)
84
+ self.v_hat_max = np.zeros(self.n_params)
85
+ self.t = 0
86
+
87
+
88
+ class AdamWithRestarts:
89
+ """
90
+ Adam optimizer with warm restarts (cosine annealing).
91
+
92
+ Learning rate follows cosine schedule and resets periodically,
93
+ which helps escape local minima.
94
+ """
95
+
96
+ def __init__(
97
+ self,
98
+ n_params: int,
99
+ lr_max: float = 0.05,
100
+ lr_min: float = 0.001,
101
+ restart_period: int = 200, # Steps between restarts
102
+ restart_mult: float = 1.5, # Multiply period after each restart
103
+ ):
104
+ self.adam = AdamOptimizer(n_params, learning_rate=lr_max)
105
+ self.lr_max = lr_max
106
+ self.lr_min = lr_min
107
+ self.restart_period = restart_period
108
+ self.restart_mult = restart_mult
109
+ self.current_period = restart_period
110
+ self.steps_since_restart = 0
111
+
112
+ def get_lr(self) -> float:
113
+ """Cosine annealing learning rate"""
114
+ progress = self.steps_since_restart / self.current_period
115
+ return self.lr_min + 0.5 * (self.lr_max - self.lr_min) * (1 + np.cos(np.pi * progress))
116
+
117
+ def step(self, params: np.ndarray, gradient: np.ndarray) -> np.ndarray:
118
+ """Perform Adam step with cosine LR schedule"""
119
+ self.steps_since_restart += 1
120
+
121
+ # Update learning rate
122
+ self.adam.lr = self.get_lr()
123
+
124
+ # Check for restart
125
+ if self.steps_since_restart >= self.current_period:
126
+ self.steps_since_restart = 0
127
+ self.current_period = int(self.current_period * self.restart_mult)
128
+ # Partial momentum reset (keep some history)
129
+ self.adam.m *= 0.5
130
+ self.adam.v *= 0.8
131
+
132
+ return self.adam.step(params, gradient)
wings/ansatz.py ADDED
@@ -0,0 +1,207 @@
1
+ from typing import TYPE_CHECKING, Any, Optional, Protocol, Union, runtime_checkable
2
+
3
+ import numpy as np
4
+ from numpy.typing import NDArray
5
+ from qiskit import QuantumCircuit, QuantumRegister
6
+ from qiskit.circuit import ParameterVector
7
+
8
+ if TYPE_CHECKING:
9
+ from .evaluators.custatevec import CuStateVecSimulator
10
+
11
+ __all__ = [
12
+ "AnsatzProtocol",
13
+ "DefaultAnsatz",
14
+ "CustomHardwareEfficientAnsatz",
15
+ "get_full_variational_quantum_circuit", # Deprecated, for backward compat
16
+ ]
17
+
18
+
19
+ @runtime_checkable
20
+ class AnsatzProtocol(Protocol):
21
+ """Protocol for custom ansatz implementations."""
22
+
23
+ def __call__(
24
+ self, params: Union[np.ndarray, ParameterVector], n_qubits: int, **_kwargs
25
+ ) -> QuantumCircuit:
26
+ """
27
+ Build parameterized quantum circuit.
28
+
29
+ Args:
30
+ params: Parameter values or ParameterVector
31
+ n_qubits: Number of qubits
32
+ **_kwargs: Additional ansatz-specific arguments
33
+
34
+ Returns:
35
+ QuantumCircuit with parameters bound or parameterized
36
+ """
37
+ ...
38
+
39
+ @property
40
+ def n_params(self) -> int:
41
+ """Return number of parameters for given qubit count."""
42
+ ...
43
+
44
+
45
+ class DefaultAnsatz:
46
+ def __init__(self, n_qubits: int, depth: Optional[int] = None) -> None:
47
+ self._n_qubits = n_qubits
48
+ self._depth = depth if depth is not None else n_qubits
49
+ self._n_params = n_qubits * self._depth
50
+
51
+ @property
52
+ def n_params(self) -> int:
53
+ return self._n_params
54
+
55
+ @property
56
+ def n_qubits(self) -> int:
57
+ return self._n_qubits
58
+
59
+ @property
60
+ def layers(self) -> int:
61
+ return self._depth
62
+
63
+ @property
64
+ def depth(self) -> int:
65
+ return self._depth
66
+
67
+ def __call__(
68
+ self,
69
+ params: Union[NDArray[np.float64], ParameterVector],
70
+ n_qubits: Optional[int] = None,
71
+ **_kwargs: Any,
72
+ ) -> QuantumCircuit:
73
+ """Build the ansatz circuit."""
74
+ n = n_qubits if n_qubits is not None else self._n_qubits
75
+ D2 = _kwargs.get("depth", self._depth)
76
+
77
+ qr = QuantumRegister(n, name="q")
78
+ qc = QuantumCircuit(qr)
79
+
80
+ # Initial state: |0...01⟩
81
+ qc.x(n - 1)
82
+ for i in range(n):
83
+ qc.ry(params[i], i)
84
+ for d in range(D2 - 1):
85
+ for i in range(n - 1):
86
+ qc.cx(i, i + 1)
87
+ qc.barrier()
88
+ for i in range(n):
89
+ param_idx = n + n * d + i
90
+ qc.ry(params[param_idx], i)
91
+
92
+ return qc
93
+
94
+ def get_param_count(self, n_qubits: Optional[int] = None, depth: Optional[int] = None) -> int:
95
+ """Calculate parameter count for given configuration."""
96
+ n = n_qubits if n_qubits is not None else self._n_qubits
97
+ d = depth if depth is not None else self._depth
98
+ return n * d
99
+
100
+
101
+ # Keep old function for backward compatibility
102
+ def get_full_variational_quantum_circuit(thetas, D2, qubits_num, input_state=None):
103
+ """
104
+ DEPRECATED: Use DefaultAnsatz class instead.
105
+
106
+ Kept for backward compatibility.
107
+ """
108
+ ansatz = DefaultAnsatz(qubits_num, depth=D2)
109
+ return ansatz(thetas, qubits_num, depth=D2)
110
+
111
+
112
+ class CustomHardwareEfficientAnsatz:
113
+ """
114
+ Example custom ansatz with configurable entanglement pattern.
115
+
116
+ Usage:
117
+ ansatz = CustomHardwareEfficientAnsatz(n_qubits=8, layers=4, entanglement='circular')
118
+ config = OptimizerConfig(n_qubits=8, ansatz=ansatz)
119
+ optimizer = GaussianOptimizer(config)
120
+ """
121
+
122
+ def __init__(
123
+ self,
124
+ n_qubits: int,
125
+ layers: int = 4,
126
+ entanglement: str = "linear",
127
+ rotation_gates: Optional[list[str]] = None,
128
+ ) -> None:
129
+ self._n_qubits = n_qubits
130
+ self._layers = layers
131
+ self._entanglement = entanglement
132
+ self._rotation_gates = rotation_gates or ["ry"]
133
+
134
+ # Calculate parameter count
135
+ rotations_per_layer = n_qubits * len(self._rotation_gates)
136
+ self._n_params = rotations_per_layer * layers
137
+
138
+ @property
139
+ def n_params(self) -> int:
140
+ return self._n_params
141
+
142
+ @property
143
+ def depth(self) -> int:
144
+ return self._layers
145
+
146
+ def __call__(
147
+ self, params: Union[np.ndarray, ParameterVector], n_qubits: int = None, **_kwargs
148
+ ) -> QuantumCircuit:
149
+ n = n_qubits or self._n_qubits
150
+ qc = QuantumCircuit(n)
151
+
152
+ param_idx = 0
153
+ for layer in range(self._layers):
154
+ # Rotation block
155
+ for qubit in range(n):
156
+ for gate_name in self._rotation_gates:
157
+ if gate_name == "ry":
158
+ qc.ry(params[param_idx], qubit)
159
+ elif gate_name == "rx":
160
+ qc.rx(params[param_idx], qubit)
161
+ elif gate_name == "rz":
162
+ qc.rz(params[param_idx], qubit)
163
+ param_idx += 1
164
+
165
+ # Entanglement block (skip on last layer)
166
+ if layer < self._layers - 1:
167
+ if self._entanglement == "linear":
168
+ for i in range(n - 1):
169
+ qc.cx(i, i + 1)
170
+ elif self._entanglement == "circular":
171
+ for i in range(n - 1):
172
+ qc.cx(i, i + 1)
173
+ qc.cx(n - 1, 0)
174
+ elif self._entanglement == "full":
175
+ for i in range(n):
176
+ for j in range(i + 1, n):
177
+ qc.cx(i, j)
178
+
179
+ return qc
180
+
181
+ def apply_custatevec(
182
+ self, simulator: "CuStateVecSimulator", params: NDArray[np.float64]
183
+ ) -> None:
184
+ """Native cuStateVec implementation for speed."""
185
+ n = self._n_qubits
186
+ param_idx = 0
187
+
188
+ simulator.reset_state()
189
+
190
+ for layer in range(self._layers):
191
+ # Rotations
192
+ for qubit in range(n):
193
+ for gate_name in self._rotation_gates:
194
+ if gate_name == "ry":
195
+ simulator.apply_ry(float(params[param_idx]), qubit)
196
+ # Add rx, rz if simulator supports them
197
+ param_idx += 1
198
+
199
+ # Entanglement
200
+ if layer < self._layers - 1:
201
+ if self._entanglement == "linear":
202
+ for i in range(n - 1):
203
+ simulator.apply_cnot(i, i + 1)
204
+ elif self._entanglement == "circular":
205
+ for i in range(n - 1):
206
+ simulator.apply_cnot(i, i + 1)
207
+ simulator.apply_cnot(n - 1, 0)