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 +251 -0
- wings/adam.py +132 -0
- wings/ansatz.py +207 -0
- wings/benchmarks.py +605 -0
- wings/campaign.py +661 -0
- wings/cli.py +377 -0
- wings/compat.py +132 -0
- wings/config.py +443 -0
- wings/convenience.py +259 -0
- wings/evaluators/__init__.py +19 -0
- wings/evaluators/cpu.py +72 -0
- wings/evaluators/custatevec.py +783 -0
- wings/evaluators/gpu.py +220 -0
- wings/export.py +243 -0
- wings/optimizer.py +1898 -0
- wings/paths.py +295 -0
- wings/py.typed +2 -0
- wings/results.py +255 -0
- wings/types.py +14 -0
- wings_quantum-0.1.0.dist-info/METADATA +491 -0
- wings_quantum-0.1.0.dist-info/RECORD +25 -0
- wings_quantum-0.1.0.dist-info/WHEEL +5 -0
- wings_quantum-0.1.0.dist-info/entry_points.txt +2 -0
- wings_quantum-0.1.0.dist-info/licenses/LICENSE.txt +21 -0
- wings_quantum-0.1.0.dist-info/top_level.txt +1 -0
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)
|