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/cli.py ADDED
@@ -0,0 +1,377 @@
1
+ """Command-line interface for gaussian-state-optimizer."""
2
+
3
+ import argparse
4
+ import sys
5
+ from typing import Optional
6
+
7
+
8
+ def main(args: Optional[list] = None) -> int:
9
+ """
10
+ Main entry point for the CLI.
11
+
12
+ Usage:
13
+ gso optimize --qubits 8 --sigma 0.5
14
+ gso benchmark --qubits 12
15
+ gso crossover
16
+ gso info
17
+ gso campaigns list
18
+ """
19
+ parser = argparse.ArgumentParser(
20
+ prog="gso",
21
+ description="Gaussian State Optimizer - GPU-accelerated quantum state preparation",
22
+ formatter_class=argparse.RawDescriptionHelpFormatter,
23
+ epilog="""
24
+ Examples:
25
+ gso optimize --qubits 8 --sigma 0.5
26
+ gso optimize --qubits 10 --sigma 0.5 --target-infidelity 1e-10 --max-time 3600
27
+ gso benchmark --qubits 12
28
+ gso crossover
29
+ gso info
30
+ gso campaigns list
31
+ gso campaigns load campaign_q8_s0.50_20240101_120000
32
+ """,
33
+ )
34
+
35
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
36
+
37
+ # ========================================
38
+ # optimize command
39
+ # ========================================
40
+ opt_parser = subparsers.add_parser(
41
+ "optimize",
42
+ help="Run Gaussian state optimization",
43
+ description="Optimize quantum circuit parameters to prepare a Gaussian state",
44
+ )
45
+ opt_parser.add_argument(
46
+ "-q",
47
+ "--qubits",
48
+ type=int,
49
+ default=8,
50
+ help="Number of qubits (default: 8)",
51
+ )
52
+ opt_parser.add_argument(
53
+ "-s",
54
+ "--sigma",
55
+ type=float,
56
+ default=0.5,
57
+ help="Gaussian width sigma (default: 0.5)",
58
+ )
59
+ opt_parser.add_argument(
60
+ "--box-size",
61
+ type=float,
62
+ default=None,
63
+ help="Box size for position grid (default: auto)",
64
+ )
65
+ opt_parser.add_argument(
66
+ "--target-infidelity",
67
+ type=float,
68
+ default=1e-8,
69
+ help="Target infidelity 1-F (default: 1e-8)",
70
+ )
71
+ opt_parser.add_argument(
72
+ "--max-time",
73
+ type=float,
74
+ default=600,
75
+ help="Maximum optimization time in seconds (default: 600)",
76
+ )
77
+ opt_parser.add_argument(
78
+ "--no-gpu",
79
+ action="store_true",
80
+ help="Disable GPU acceleration",
81
+ )
82
+ opt_parser.add_argument(
83
+ "--no-custatevec",
84
+ action="store_true",
85
+ help="Disable cuStateVec (use Aer GPU instead)",
86
+ )
87
+ opt_parser.add_argument(
88
+ "--no-plot",
89
+ action="store_true",
90
+ help="Disable result plotting",
91
+ )
92
+ opt_parser.add_argument(
93
+ "--no-save",
94
+ action="store_true",
95
+ help="Disable saving results to files",
96
+ )
97
+ opt_parser.add_argument(
98
+ "-v",
99
+ "--verbose",
100
+ action="store_true",
101
+ default=True,
102
+ help="Verbose output (default: True)",
103
+ )
104
+ opt_parser.add_argument(
105
+ "-Q",
106
+ "--quiet",
107
+ action="store_true",
108
+ help="Suppress output",
109
+ )
110
+
111
+ # ========================================
112
+ # campaign command
113
+ # ========================================
114
+ camp_parser = subparsers.add_parser(
115
+ "campaign",
116
+ help="Run production optimization campaign",
117
+ description="Run large-scale optimization campaign with checkpointing",
118
+ )
119
+ camp_parser.add_argument(
120
+ "-q",
121
+ "--qubits",
122
+ type=int,
123
+ default=8,
124
+ help="Number of qubits (default: 8)",
125
+ )
126
+ camp_parser.add_argument(
127
+ "-s",
128
+ "--sigma",
129
+ type=float,
130
+ default=0.5,
131
+ help="Gaussian width sigma (default: 0.5)",
132
+ )
133
+ camp_parser.add_argument(
134
+ "-n",
135
+ "--runs",
136
+ type=int,
137
+ default=100,
138
+ help="Total optimization runs (default: 100)",
139
+ )
140
+ camp_parser.add_argument(
141
+ "--target-infidelity",
142
+ type=float,
143
+ default=1e-10,
144
+ help="Target infidelity (default: 1e-10)",
145
+ )
146
+ camp_parser.add_argument(
147
+ "--name",
148
+ type=str,
149
+ default=None,
150
+ help="Campaign name (default: auto-generated)",
151
+ )
152
+ camp_parser.add_argument(
153
+ "--resume",
154
+ action="store_true",
155
+ help="Resume from checkpoint if available",
156
+ )
157
+
158
+ # ========================================
159
+ # benchmark command
160
+ # ========================================
161
+ bench_parser = subparsers.add_parser(
162
+ "benchmark",
163
+ help="Benchmark GPU vs CPU performance",
164
+ )
165
+ bench_parser.add_argument(
166
+ "-q",
167
+ "--qubits",
168
+ type=int,
169
+ default=10,
170
+ help="Number of qubits (default: 10)",
171
+ )
172
+ bench_parser.add_argument(
173
+ "-s",
174
+ "--sigma",
175
+ type=float,
176
+ default=0.5,
177
+ help="Gaussian width (default: 0.5)",
178
+ )
179
+
180
+ # ========================================
181
+ # crossover command
182
+ # ========================================
183
+ cross_parser = subparsers.add_parser(
184
+ "crossover",
185
+ help="Find GPU crossover point",
186
+ description="Determine at what qubit count GPU becomes faster than CPU",
187
+ )
188
+ cross_parser.add_argument(
189
+ "--min-qubits",
190
+ type=int,
191
+ default=6,
192
+ help="Minimum qubits to test (default: 6)",
193
+ )
194
+ cross_parser.add_argument(
195
+ "--max-qubits",
196
+ type=int,
197
+ default=18,
198
+ help="Maximum qubits to test (default: 18)",
199
+ )
200
+
201
+ # ========================================
202
+ # info command
203
+ # ========================================
204
+ subparsers.add_parser(
205
+ "info",
206
+ help="Show backend and system information",
207
+ )
208
+
209
+ # ========================================
210
+ # campaigns subcommand
211
+ # ========================================
212
+ campaigns_parser = subparsers.add_parser(
213
+ "campaigns",
214
+ help="Manage optimization campaigns",
215
+ )
216
+ campaigns_sub = campaigns_parser.add_subparsers(dest="campaigns_command")
217
+
218
+ campaigns_sub.add_parser("list", help="List all campaigns")
219
+
220
+ load_parser = campaigns_sub.add_parser("load", help="Load campaign results")
221
+ load_parser.add_argument("name", help="Campaign name or path")
222
+
223
+ # Parse arguments
224
+ parsed = parser.parse_args(args)
225
+
226
+ if parsed.command is None:
227
+ parser.print_help()
228
+ return 0
229
+
230
+ # Execute command
231
+ try:
232
+ if parsed.command == "optimize":
233
+ return _cmd_optimize(parsed)
234
+ elif parsed.command == "campaign":
235
+ return _cmd_campaign(parsed)
236
+ elif parsed.command == "benchmark":
237
+ return _cmd_benchmark(parsed)
238
+ elif parsed.command == "crossover":
239
+ return _cmd_crossover(parsed)
240
+ elif parsed.command == "info":
241
+ return _cmd_info(parsed)
242
+ elif parsed.command == "campaigns":
243
+ return _cmd_campaigns(parsed)
244
+ else:
245
+ parser.print_help()
246
+ return 1
247
+ except KeyboardInterrupt:
248
+ print("\nInterrupted by user")
249
+ return 130
250
+ except Exception as e:
251
+ print(f"Error: {e}", file=sys.stderr)
252
+ return 1
253
+
254
+
255
+ def _cmd_optimize(args) -> int:
256
+ """Handle optimize command."""
257
+ from .convenience import optimize_gaussian_state
258
+
259
+ verbose = not args.quiet if hasattr(args, "quiet") else True
260
+
261
+ results, optimizer = optimize_gaussian_state(
262
+ n_qubits=args.qubits,
263
+ sigma=args.sigma,
264
+ box_size=args.box_size,
265
+ target_infidelity=args.target_infidelity,
266
+ max_time=args.max_time,
267
+ use_gpu=not args.no_gpu,
268
+ use_custatevec=not args.no_custatevec,
269
+ plot=not args.no_plot,
270
+ save=not args.no_save,
271
+ verbose=verbose,
272
+ )
273
+
274
+ # Print final result for scripting
275
+ print(f"\nFinal fidelity: {results['fidelity']:.15f}")
276
+ print(f"Final infidelity: {results['infidelity']:.3e}")
277
+
278
+ return 0 if results["fidelity"] >= (1 - args.target_infidelity) else 1
279
+
280
+
281
+ def _cmd_campaign(args) -> int:
282
+ """Handle campaign command."""
283
+ from .campaign import run_production_campaign
284
+
285
+ results = run_production_campaign(
286
+ n_qubits=args.qubits,
287
+ sigma=args.sigma,
288
+ total_runs=args.runs,
289
+ target_infidelity=args.target_infidelity,
290
+ campaign_name=args.name,
291
+ resume=args.resume,
292
+ )
293
+
294
+ results.print_summary()
295
+
296
+ return (
297
+ 0
298
+ if results.best_result and results.best_result.fidelity >= (1 - args.target_infidelity)
299
+ else 1
300
+ )
301
+
302
+
303
+ def _cmd_benchmark(args) -> int:
304
+ """Handle benchmark command."""
305
+ from .benchmarks import benchmark_gpu
306
+
307
+ benchmark_gpu(
308
+ n_qubits=args.qubits,
309
+ sigma=args.sigma,
310
+ verbose=True,
311
+ )
312
+
313
+ return 0
314
+
315
+
316
+ def _cmd_crossover(args) -> int:
317
+ """Handle crossover command."""
318
+ from .benchmarks import find_gpu_crossover
319
+
320
+ qubit_range = list(range(args.min_qubits, args.max_qubits + 1, 2))
321
+
322
+ find_gpu_crossover(
323
+ qubit_range=qubit_range,
324
+ verbose=True,
325
+ )
326
+
327
+ return 0
328
+
329
+
330
+ def _cmd_info(args) -> int:
331
+ """Handle info command."""
332
+ from . import __version__, print_backend_info
333
+
334
+ print(f"Gaussian State Optimizer v{__version__}")
335
+ print()
336
+ print_backend_info()
337
+
338
+ return 0
339
+
340
+
341
+ def _cmd_campaigns(args) -> int:
342
+ """Handle campaigns subcommands."""
343
+ if args.campaigns_command == "list":
344
+ from .campaign import list_campaigns
345
+
346
+ campaigns = list_campaigns()
347
+
348
+ if not campaigns:
349
+ print("No campaigns found.")
350
+ return 0
351
+
352
+ print(f"{'Name':<50} {'Runs':<8} {'Best Fidelity':<20}")
353
+ print("-" * 80)
354
+
355
+ for c in campaigns:
356
+ runs = c.get("total_runs", "N/A")
357
+ fid = c.get("best_fidelity")
358
+ fid_str = f"{fid:.12f}" if fid else "N/A"
359
+ print(f"{c['name']:<50} {runs:<8} {fid_str:<20}")
360
+
361
+ return 0
362
+
363
+ elif args.campaigns_command == "load":
364
+ from .campaign import load_campaign_results
365
+
366
+ results = load_campaign_results(args.name)
367
+ results.print_summary()
368
+
369
+ return 0
370
+
371
+ else:
372
+ print("Usage: gso campaigns {list|load}")
373
+ return 1
374
+
375
+
376
+ if __name__ == "__main__":
377
+ sys.exit(main())
wings/compat.py ADDED
@@ -0,0 +1,132 @@
1
+ import logging
2
+ from typing import Optional
3
+
4
+ logger = logging.getLogger(__name__)
5
+
6
+ # Conditional imports for cuQuantum
7
+ try:
8
+ import cupy as cp
9
+ from cuquantum.bindings import custatevec as cusv
10
+
11
+ HAS_CUSTATEVEC = True
12
+ except ImportError:
13
+ HAS_CUSTATEVEC = False
14
+ cp = None
15
+ cusv = None
16
+
17
+
18
+ class CuQuantumCompat:
19
+ """
20
+ Compatibility layer for cuQuantum API differences across versions.
21
+
22
+ Handles the cudaDataType and ComputeType enum access which varies
23
+ between cuQuantum versions (pre-24.x vs 24.x+).
24
+ """
25
+
26
+ # Standard CUDA data type values (from cuda_runtime_api.h)
27
+ _CUDA_DTYPE_MAP = {
28
+ "CUDA_R_32F": 0,
29
+ "CUDA_R_64F": 1,
30
+ "CUDA_C_32F": 4,
31
+ "CUDA_C_64F": 5,
32
+ "CUDA_C_128F": 5, # Alias for complex128
33
+ }
34
+
35
+ _COMPUTE_TYPE_MAP = {
36
+ "COMPUTE_32F": 4,
37
+ "COMPUTE_64F": 16,
38
+ }
39
+
40
+ def __init__(self):
41
+ self._cuda_c_64f: Optional[int] = None
42
+ self._compute_64f: Optional[int] = None
43
+ self._initialized = False
44
+
45
+ def _initialize(self) -> None:
46
+ """Lazy initialization to detect available API."""
47
+ if self._initialized:
48
+ return
49
+
50
+ self._cuda_c_64f = self._detect_cuda_dtype()
51
+ self._compute_64f = self._detect_compute_type()
52
+ self._initialized = True
53
+
54
+ def _detect_cuda_dtype(self) -> int:
55
+ """Detect CUDA_C_64F value from cusv or fall back to known constant."""
56
+ if not HAS_CUSTATEVEC:
57
+ return self._CUDA_DTYPE_MAP["CUDA_C_64F"]
58
+
59
+ # Try various API locations (differs by cuQuantum version)
60
+ access_paths = [
61
+ lambda: int(cusv.cudaDataType.CUDA_C_64F),
62
+ lambda: int(cusv.cudaDataType.CUDA_C_128F),
63
+ lambda: int(cusv.CUDA_C_64F),
64
+ lambda: cusv.cudaDataType["CUDA_C_64F"],
65
+ ]
66
+
67
+ for accessor in access_paths:
68
+ try:
69
+ value = accessor()
70
+ logger.debug(f"Found CUDA_C_64F via {accessor.__code__.co_consts}: {value}")
71
+ return value
72
+ except (AttributeError, TypeError, KeyError):
73
+ continue
74
+
75
+ logger.debug("Using fallback CUDA_C_64F value: 5")
76
+ return self._CUDA_DTYPE_MAP["CUDA_C_64F"]
77
+
78
+ def _detect_compute_type(self) -> int:
79
+ """Detect COMPUTE_64F value from cusv or fall back to known constant."""
80
+ if not HAS_CUSTATEVEC:
81
+ return self._COMPUTE_TYPE_MAP["COMPUTE_64F"]
82
+
83
+ access_paths = [
84
+ lambda: int(cusv.ComputeType.COMPUTE_64F),
85
+ lambda: int(cusv.COMPUTE_64F),
86
+ lambda: cusv.ComputeType["COMPUTE_64F"],
87
+ ]
88
+
89
+ for accessor in access_paths:
90
+ try:
91
+ value = accessor()
92
+ logger.debug(f"Found COMPUTE_64F: {value}")
93
+ return value
94
+ except (AttributeError, TypeError, KeyError):
95
+ continue
96
+
97
+ logger.debug("Using fallback COMPUTE_64F value: 16")
98
+ return self._COMPUTE_TYPE_MAP["COMPUTE_64F"]
99
+
100
+ @property
101
+ def CUDA_C_64F(self) -> int:
102
+ self._initialize()
103
+ return self._cuda_c_64f
104
+
105
+ @property
106
+ def COMPUTE_64F(self) -> int:
107
+ self._initialize()
108
+ return self._compute_64f
109
+
110
+
111
+ # Global compatibility instance
112
+ cuquantum_compat = CuQuantumCompat()
113
+
114
+
115
+ # Convenience accessors (replaces the old global variables)
116
+ def get_cuda_dtype() -> int:
117
+ """Get the CUDA data type constant for complex128."""
118
+ return cuquantum_compat.CUDA_C_64F
119
+
120
+
121
+ def get_compute_type() -> int:
122
+ """Get the compute type constant for 64-bit precision."""
123
+ return cuquantum_compat.COMPUTE_64F
124
+
125
+
126
+ __all__ = [
127
+ "HAS_CUSTATEVEC",
128
+ "CuQuantumCompat",
129
+ "cuquantum_compat",
130
+ "get_cuda_dtype",
131
+ "get_compute_type",
132
+ ]