QuLab 2.11.8__tar.gz → 2.11.9__tar.gz
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.
- {qulab-2.11.8 → qulab-2.11.9}/PKG-INFO +1 -1
- {qulab-2.11.8 → qulab-2.11.9}/QuLab.egg-info/PKG-INFO +1 -1
- {qulab-2.11.8 → qulab-2.11.9}/QuLab.egg-info/SOURCES.txt +0 -1
- {qulab-2.11.8 → qulab-2.11.9}/qulab/executor/analyze.py +1 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/executor/cli.py +180 -42
- {qulab-2.11.8 → qulab-2.11.9}/qulab/executor/utils.py +16 -2
- qulab-2.11.9/qulab/monitor/__init__.py +1 -0
- qulab-2.11.9/qulab/monitor/__main__.py +36 -0
- qulab-2.11.9/qulab/monitor/config.py +66 -0
- qulab-2.11.9/qulab/monitor/dataset.py +184 -0
- qulab-2.11.9/qulab/monitor/event_queue.py +127 -0
- qulab-2.11.9/qulab/monitor/mainwindow.py +268 -0
- qulab-2.11.9/qulab/monitor/monitor.py +305 -0
- qulab-2.11.9/qulab/monitor/ploter.py +148 -0
- qulab-2.11.9/qulab/monitor/qt_compat.py +45 -0
- qulab-2.11.9/qulab/monitor/toolbar.py +296 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/utils.py +16 -17
- qulab-2.11.9/qulab/version.py +1 -0
- qulab-2.11.8/qulab/monitor/__init__.py +0 -1
- qulab-2.11.8/qulab/monitor/__main__.py +0 -8
- qulab-2.11.8/qulab/monitor/config.py +0 -41
- qulab-2.11.8/qulab/monitor/dataset.py +0 -77
- qulab-2.11.8/qulab/monitor/event_queue.py +0 -54
- qulab-2.11.8/qulab/monitor/mainwindow.py +0 -234
- qulab-2.11.8/qulab/monitor/monitor.py +0 -115
- qulab-2.11.8/qulab/monitor/ploter.py +0 -123
- qulab-2.11.8/qulab/monitor/qt_compat.py +0 -16
- qulab-2.11.8/qulab/monitor/toolbar.py +0 -265
- qulab-2.11.8/qulab/version.py +0 -1
- qulab-2.11.8/tests/test_kad.py +0 -341
- {qulab-2.11.8 → qulab-2.11.9}/LICENSE +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/MANIFEST.in +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/QuLab.egg-info/dependency_links.txt +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/QuLab.egg-info/entry_points.txt +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/QuLab.egg-info/requires.txt +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/QuLab.egg-info/top_level.txt +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/README.md +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/pyproject.toml +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/__init__.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/__main__.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/cli/__init__.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/cli/commands.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/cli/config.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/cli/decorators.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/executor/__init__.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/executor/load.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/executor/registry.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/executor/schedule.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/executor/storage.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/executor/template.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/scan/__init__.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/scan/curd.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/scan/models.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/scan/optimize.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/scan/query.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/scan/record.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/scan/scan.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/scan/server.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/scan/space.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/scan/utils.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/storage/__init__.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/storage/__main__.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/storage/backend/__init__.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/storage/backend/redis.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/storage/base_dataset.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/storage/chunk.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/storage/dataset.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/storage/file.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/storage/models/__init__.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/storage/models/base.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/storage/models/config.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/storage/models/file.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/storage/models/ipy.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/storage/models/models.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/storage/models/record.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/storage/models/report.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/storage/models/tag.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/storage/storage.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/__init__.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/chat.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/device/__init__.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/device/basedevice.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/device/loader.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/device/utils.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/drivers/FakeInstrument.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/drivers/__init__.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/ipy_events.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/net/__init__.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/net/bencoder.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/net/cli.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/net/dhcp.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/net/dhcpd.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/net/kad.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/net/kcp.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/net/nginx.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/progress.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/rpc/__init__.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/rpc/client.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/rpc/exceptions.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/rpc/msgpack.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/rpc/msgpack.pyi +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/rpc/router.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/rpc/rpc.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/rpc/serialize.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/rpc/server.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/rpc/socket.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/rpc/utils.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/rpc/worker.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/sys/rpc/zmq_socket.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/tools/__init__.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/tools/connection_helper.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/typing.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/visualization/__init__.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/visualization/__main__.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/visualization/_autoplot.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/visualization/plot_circ.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/visualization/plot_layout.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/visualization/plot_seq.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/visualization/qdat.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/visualization/rot3d.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/qulab/visualization/widgets.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/setup.cfg +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/setup.py +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/src/qulab.h +0 -0
- {qulab-2.11.8 → qulab-2.11.9}/tests/test_scan.py +0 -0
@@ -25,7 +25,12 @@ from .utils import workflow_template
|
|
25
25
|
|
26
26
|
@logger.catch(reraise=True)
|
27
27
|
def run_script(script_path, extra_paths=None):
|
28
|
-
"""Run a script in a new process, inheriting current PYTHONPATH plus any extra paths.
|
28
|
+
"""Run a script in a new process, inheriting current PYTHONPATH plus any extra paths.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
script_path (str): Path to the script to be executed
|
32
|
+
extra_paths (list, optional): Additional paths to be added to PYTHONPATH
|
33
|
+
"""
|
29
34
|
import subprocess
|
30
35
|
import sys
|
31
36
|
|
@@ -103,8 +108,17 @@ def command_option(command_name):
|
|
103
108
|
help='The path of the code.')
|
104
109
|
@log_options('create')
|
105
110
|
def create(workflow, code):
|
106
|
-
"""
|
107
|
-
|
111
|
+
"""Create a new workflow file.
|
112
|
+
|
113
|
+
This command creates a new workflow file with a template structure. The template includes
|
114
|
+
basic workflow setup and any unreferenced workflows as potential dependencies.
|
115
|
+
|
116
|
+
Args:
|
117
|
+
workflow: Name of the workflow to create
|
118
|
+
code: Directory path where the workflow file will be created. Defaults to current directory.
|
119
|
+
|
120
|
+
Example:
|
121
|
+
$ qulab create my_workflow --code ./workflows
|
108
122
|
"""
|
109
123
|
logger.info(f'[CMD]: create {workflow} --code {code}')
|
110
124
|
if code is None:
|
@@ -133,8 +147,19 @@ def create(workflow, code):
|
|
133
147
|
help='The modlule name of the api.')
|
134
148
|
@log_options('set')
|
135
149
|
def set(key, value, api):
|
136
|
-
"""
|
137
|
-
|
150
|
+
"""Set a configuration value in the registry.
|
151
|
+
|
152
|
+
This command allows you to set key-value pairs in the configuration registry.
|
153
|
+
The value can be any valid Python expression that can be evaluated.
|
154
|
+
|
155
|
+
Args:
|
156
|
+
key: The configuration key to set
|
157
|
+
value: The value to set (can be a Python expression)
|
158
|
+
api: Optional API module for custom config handling
|
159
|
+
|
160
|
+
Example:
|
161
|
+
$ qulab set Q1.channel.Z NS_DDS.CH1
|
162
|
+
$ qulab set gate.R.Q3.params.frequency 5.0e9
|
138
163
|
"""
|
139
164
|
logger.info(f'[CMD]: reg set {key} {value} --api {api}')
|
140
165
|
reg = Registry()
|
@@ -157,8 +182,17 @@ def set(key, value, api):
|
|
157
182
|
help='The modlule name of the api.')
|
158
183
|
@log_options('get')
|
159
184
|
def get(key, api):
|
160
|
-
"""
|
161
|
-
|
185
|
+
"""Get a configuration value from the registry.
|
186
|
+
|
187
|
+
Retrieves and displays the value associated with a given key from the configuration registry.
|
188
|
+
|
189
|
+
Args:
|
190
|
+
key: The configuration key to retrieve
|
191
|
+
api: Optional API module for custom config handling
|
192
|
+
|
193
|
+
Example:
|
194
|
+
$ qulab get Q1.channel
|
195
|
+
$ qulab get gate.R.Q3.params.frequency
|
162
196
|
"""
|
163
197
|
logger.info(f'[CMD]: reg get {key} --api {api}')
|
164
198
|
reg = Registry()
|
@@ -177,8 +211,16 @@ def get(key, api):
|
|
177
211
|
help='The modlule name of the api.')
|
178
212
|
@log_options('delete')
|
179
213
|
def delete(key, api):
|
180
|
-
"""
|
181
|
-
|
214
|
+
"""Delete a configuration key from the registry.
|
215
|
+
|
216
|
+
Removes a key and its associated value from the configuration registry.
|
217
|
+
|
218
|
+
Args:
|
219
|
+
key: The configuration key to delete
|
220
|
+
api: Optional API module for custom config handling
|
221
|
+
|
222
|
+
Example:
|
223
|
+
$ qulab delete gate.R.Q3.params.block_freq
|
182
224
|
"""
|
183
225
|
logger.info(f'[CMD]: reg delete {key} --api {api}')
|
184
226
|
reg = Registry()
|
@@ -201,8 +243,24 @@ def delete(key, api):
|
|
201
243
|
help='The format of the config.')
|
202
244
|
@log_options('export')
|
203
245
|
def export(file, api, format):
|
204
|
-
"""
|
205
|
-
|
246
|
+
"""Export the configuration registry to a file.
|
247
|
+
|
248
|
+
Exports all configuration settings to a file in the specified format.
|
249
|
+
|
250
|
+
Args:
|
251
|
+
file: Path to the output file
|
252
|
+
api: Optional API module for custom config handling
|
253
|
+
format: Output format (pickle, json, or yaml)
|
254
|
+
|
255
|
+
Supported formats:
|
256
|
+
- pickle: Binary format (default)
|
257
|
+
- json: JSON text format
|
258
|
+
- yaml: YAML text format
|
259
|
+
|
260
|
+
Example:
|
261
|
+
$ qulab export config.pkl
|
262
|
+
$ qulab export config.json --format json
|
263
|
+
$ qulab export config.yaml --format yaml
|
206
264
|
"""
|
207
265
|
logger.info(f'[CMD]: reg export {file} --api {api}')
|
208
266
|
reg = Registry()
|
@@ -239,8 +297,27 @@ def export(file, api, format):
|
|
239
297
|
help='The format of the config.')
|
240
298
|
@log_options('load')
|
241
299
|
def load(file, api, format):
|
242
|
-
"""
|
243
|
-
|
300
|
+
"""Load configuration settings from a file.
|
301
|
+
|
302
|
+
Imports configuration settings from a file in the specified format.
|
303
|
+
Existing configuration will be cleared before loading the new settings.
|
304
|
+
|
305
|
+
Args:
|
306
|
+
file: Path to the input file or report index number
|
307
|
+
api: Optional API module for custom config handling
|
308
|
+
format: Input format (pickle, json, yaml, or report)
|
309
|
+
|
310
|
+
Supported formats:
|
311
|
+
- pickle: Binary format (default)
|
312
|
+
- json: JSON text format
|
313
|
+
- yaml: YAML text format
|
314
|
+
- report: Load from a saved report by index
|
315
|
+
|
316
|
+
Example:
|
317
|
+
$ qulab load config.pkl
|
318
|
+
$ qulab load config.json --format json
|
319
|
+
$ qulab load config.yaml --format yaml
|
320
|
+
$ qulab load 123 --format report # Load from report #123
|
244
321
|
"""
|
245
322
|
logger.info(f'[CMD]: reg load {file} --api {api}')
|
246
323
|
reg = Registry()
|
@@ -278,8 +355,16 @@ def load(file, api, format):
|
|
278
355
|
default=lambda: get_config_value("bootstrap", Path),
|
279
356
|
help='The path of the bootstrap.')
|
280
357
|
def boot(bootstrap):
|
281
|
-
"""
|
282
|
-
|
358
|
+
"""Run a bootstrap script.
|
359
|
+
|
360
|
+
Executes a bootstrap script to set up the environment or perform initialization tasks.
|
361
|
+
The bootstrap script runs in a new process with the current Python environment.
|
362
|
+
|
363
|
+
Args:
|
364
|
+
bootstrap: Path to the bootstrap script
|
365
|
+
|
366
|
+
Example:
|
367
|
+
$ qulab boot --bootstrap setup.py
|
283
368
|
"""
|
284
369
|
if bootstrap is not None:
|
285
370
|
run_script(bootstrap)
|
@@ -291,7 +376,16 @@ def parse_dynamic_option_value(value):
|
|
291
376
|
parsed_value = ast.literal_eval(value)
|
292
377
|
except (ValueError, SyntaxError):
|
293
378
|
# 如果解析失败,返回原始字符串
|
294
|
-
|
379
|
+
if ',' in value:
|
380
|
+
parsed_value = []
|
381
|
+
for item in value.split(','):
|
382
|
+
try:
|
383
|
+
parsed_value.append(ast.literal_eval(item))
|
384
|
+
except (ValueError, SyntaxError):
|
385
|
+
parsed_value.append(item)
|
386
|
+
parsed_value = tuple(parsed_value)
|
387
|
+
else:
|
388
|
+
parsed_value = value
|
295
389
|
return parsed_value
|
296
390
|
|
297
391
|
|
@@ -311,17 +405,31 @@ def parse_dynamic_options(args):
|
|
311
405
|
return result
|
312
406
|
|
313
407
|
|
314
|
-
help_doc = """
|
315
|
-
|
408
|
+
help_doc = """Run a workflow with specified parameters and options.
|
409
|
+
|
410
|
+
This command executes a workflow and its dependencies based on the provided configuration.
|
411
|
+
|
412
|
+
Arguments:
|
413
|
+
workflow: The name or path of the workflow to run
|
414
|
+
|
415
|
+
Options:
|
416
|
+
--code, -c: The path containing the workflow code (default: current directory)
|
417
|
+
--data, -d: The path for storing logs and data (default: ./logs)
|
418
|
+
--api, -a: The module name of the API for configuration handling
|
419
|
+
--plot, -p: Generate plots after workflow execution
|
420
|
+
--no-dependents, -n: Run only the specified workflow without dependencies
|
421
|
+
--retry, -r: Number of retry attempts for failed calibrations (default: 1)
|
422
|
+
--freeze: Freeze the configuration table during execution
|
423
|
+
--fail-fast, -f: Stop execution on first error
|
424
|
+
--veryfy-source-code: Verify source code before execution
|
316
425
|
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
If `--data` is not set, use the `logs` directory in the code path.
|
426
|
+
Additional parameters can be passed as key=value pairs and will be available to the workflow.
|
427
|
+
|
428
|
+
Examples:
|
429
|
+
$ qulab run my_workflow
|
430
|
+
$ qulab run my_workflow --plot --retry 3
|
431
|
+
$ qulab run my_workflow param1=value1 param2=value2
|
432
|
+
$ qulab run my_workflow --no-dependents --freeze
|
325
433
|
"""
|
326
434
|
|
327
435
|
|
@@ -478,15 +586,30 @@ async def maintain(workflow,
|
|
478
586
|
plot,
|
479
587
|
fail_fast,
|
480
588
|
veryfy_source_code=True):
|
481
|
-
"""
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
589
|
+
"""Maintain a workflow and its dependencies.
|
590
|
+
|
591
|
+
This command checks and maintains the workflow and its dependencies without executing them.
|
592
|
+
It verifies configurations, dependencies, and can generate plots from existing data.
|
593
|
+
|
594
|
+
Args:
|
595
|
+
workflow: Name or path of the workflow to maintain
|
596
|
+
code: Directory containing the workflow code
|
597
|
+
data: Directory for logs and data
|
598
|
+
api: Module name for configuration API
|
599
|
+
retry: Number of retry attempts for failed calibrations
|
600
|
+
plot: Generate plots from existing data
|
601
|
+
fail_fast: Stop on first error
|
602
|
+
veryfy_source_code: Verify source code integrity
|
603
|
+
|
604
|
+
The maintenance process includes:
|
605
|
+
1. Verifying workflow dependencies
|
606
|
+
2. Checking configuration consistency
|
607
|
+
3. Validating source code (if enabled)
|
608
|
+
4. Generating plots (if enabled)
|
609
|
+
|
610
|
+
Example:
|
611
|
+
$ qulab maintain my_workflow --retry 3 --plot
|
612
|
+
$ qulab maintain my_workflow --fail-fast
|
490
613
|
"""
|
491
614
|
logger.info(
|
492
615
|
f'[CMD]: maintain {workflow} --code {code} --data {data} --api {api}'
|
@@ -556,13 +679,28 @@ async def maintain(workflow,
|
|
556
679
|
@command_option('reproduce')
|
557
680
|
@async_command
|
558
681
|
async def reproduce(report_id, code, data, api, plot):
|
559
|
-
"""
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
682
|
+
"""Reproduce a workflow execution from a saved report.
|
683
|
+
|
684
|
+
This command loads a previous execution report and attempts to reproduce the workflow
|
685
|
+
with the exact same configuration and conditions.
|
686
|
+
|
687
|
+
Args:
|
688
|
+
report_id: The ID number of the report to reproduce
|
689
|
+
code: Directory containing the workflow code
|
690
|
+
data: Directory for logs and data
|
691
|
+
api: Module name for configuration API
|
692
|
+
plot: Generate plots after reproduction
|
693
|
+
|
694
|
+
The reproduction process:
|
695
|
+
1. Loads the original report by ID
|
696
|
+
2. Restores the exact configuration state
|
697
|
+
3. Executes the workflow with frozen configuration
|
698
|
+
4. Optionally generates plots
|
699
|
+
5. Restores the previous configuration state
|
700
|
+
|
701
|
+
Example:
|
702
|
+
$ qulab reproduce 123 --plot
|
703
|
+
$ qulab reproduce 456 --code ./workflows --data ./results
|
566
704
|
"""
|
567
705
|
logger.info(
|
568
706
|
f'[CMD]: reproduce {report_id} --code {code} --data {data} --api {api}'
|
@@ -38,6 +38,7 @@ import numpy as np
|
|
38
38
|
from loguru import logger
|
39
39
|
|
40
40
|
from qulab import VAR
|
41
|
+
from qulab import manual_analysis
|
41
42
|
from qulab.typing import Report
|
42
43
|
|
43
44
|
|
@@ -73,7 +74,8 @@ async def analyze(report: Report, history: Report | None = None) -> Report:
|
|
73
74
|
history: Report | None
|
74
75
|
上次校准实验数据和分析结果,如果有的话。
|
75
76
|
\"\"\"
|
76
|
-
|
77
|
+
# 如果需要手动分析,请取消注释下面这行
|
78
|
+
# return manual_analysis(report, history)
|
77
79
|
|
78
80
|
# 这里添加你的分析过程,运行 calibrate 得到的数据,在 report.data 里
|
79
81
|
# 你可以得到校准的结果,然后根据这个结果进行分析。
|
@@ -82,6 +84,7 @@ async def analyze(report: Report, history: Report | None = None) -> Report:
|
|
82
84
|
# 完整校准后的状态有两种:OK 和 Bad,分别对应校准成功和校准失败。
|
83
85
|
# 校准失败是指出现坏数据,无法简单通过重新运行本次校准解决,需要
|
84
86
|
# 检查前置步骤。
|
87
|
+
import random
|
85
88
|
report.state = random.choice(['OK', 'Bad'])
|
86
89
|
|
87
90
|
# 参数是一个字典,包含了本次校准得到的参数,后续会更新到config表中。
|
@@ -144,7 +147,7 @@ async def oracle(report: Report,
|
|
144
147
|
|
145
148
|
当校准失败时,根据 analyze 的结果,尝试改变某些配置再重新校准整个系统。
|
146
149
|
比如通常我们在死活测不到 rabi 或能谱时,会换一个 idle bias 再试试。这
|
147
|
-
里我们凭直觉设的那个 bias
|
150
|
+
里我们凭直觉设的那个 bias 值,就称作一个谕示,可以通过 oracle 来设定。
|
148
151
|
|
149
152
|
该函数代入的参数 report 是 analyze 函数的返回值。
|
150
153
|
\"\"\"
|
@@ -161,6 +164,17 @@ async def debug_analyze(
|
|
161
164
|
code_path: str | Path = get_config_value('code', Path),
|
162
165
|
data_path: str | Path = get_config_value('data', Path),
|
163
166
|
) -> None:
|
167
|
+
"""
|
168
|
+
调试 workflow 的分析过程。
|
169
|
+
|
170
|
+
该函数会从 data_path 中读取报告,并重新加载对应的 workflow 代码,然后
|
171
|
+
运行 analyze 函数,如果有 plot 函数,还会运行 plot 函数。最后返回包含
|
172
|
+
分析结果的 report 对象。
|
173
|
+
|
174
|
+
当你完成一个 workflow 的 calibrate 开发后,可以现运行该 workflow 采
|
175
|
+
集一组数据,然后通过反复修改 analyze 函数,运行该函数,并查看分析结果,
|
176
|
+
来调试 analyze 函数,以期达到满意的效果。
|
177
|
+
"""
|
164
178
|
from .storage import get_report_by_index
|
165
179
|
|
166
180
|
report = get_report_by_index(report_index, data_path)
|
@@ -0,0 +1 @@
|
|
1
|
+
from .monitor import MonitorUI, get_monitor
|
@@ -0,0 +1,36 @@
|
|
1
|
+
"""
|
2
|
+
QuLab Monitor Command Line Interface
|
3
|
+
|
4
|
+
This module provides a command-line interface for launching the QuLab monitor.
|
5
|
+
It allows users to start a standalone monitor server with configurable settings.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import click
|
9
|
+
|
10
|
+
from .monitor import MonitorServer
|
11
|
+
|
12
|
+
|
13
|
+
@click.command(name='monitor')
|
14
|
+
@click.option('--columns', '-c', default=4, help='Number of columns in the plot grid')
|
15
|
+
@click.option('--height', '-h', default=400, help='Minimum height of each plot in pixels')
|
16
|
+
@click.option('--address', '-a', default='127.0.0.1', help='Address to bind the server')
|
17
|
+
@click.option('--port', '-p', default=5555, help='Port to bind the server')
|
18
|
+
def main(columns: int, height: int, address: str, port: int):
|
19
|
+
"""Launch a QuLab monitor server.
|
20
|
+
|
21
|
+
Args:
|
22
|
+
columns: Number of columns in the plot grid
|
23
|
+
height: Minimum height of each plot in pixels
|
24
|
+
address: Address to bind the server (default: 127.0.0.1)
|
25
|
+
port: Port to bind the server (default: 5555)
|
26
|
+
"""
|
27
|
+
server = MonitorServer(address=address,
|
28
|
+
port=port,
|
29
|
+
number_of_columns=columns,
|
30
|
+
minimum_height=height)
|
31
|
+
try:
|
32
|
+
while True:
|
33
|
+
import time
|
34
|
+
time.sleep(1) # Keep the script running while the server is active
|
35
|
+
except KeyboardInterrupt:
|
36
|
+
print("\nShutting down monitor server...")
|
@@ -0,0 +1,66 @@
|
|
1
|
+
"""
|
2
|
+
QuLab Monitor Configuration Module
|
3
|
+
|
4
|
+
This module defines the global configuration settings for the QuLab monitor
|
5
|
+
application, including:
|
6
|
+
- Visual styles and themes
|
7
|
+
- Data transformation functions
|
8
|
+
- Plot appearance settings
|
9
|
+
- Buffer sizes and indices
|
10
|
+
|
11
|
+
The configuration values are used throughout the monitor application to maintain
|
12
|
+
consistent appearance and behavior.
|
13
|
+
"""
|
14
|
+
|
15
|
+
from typing import Callable, Dict, List, Tuple, Union
|
16
|
+
import numpy as np
|
17
|
+
|
18
|
+
# Qt stylesheet for the application
|
19
|
+
STYLE = '''
|
20
|
+
QWidget {
|
21
|
+
font: medium Ubuntu;
|
22
|
+
background-color: #011F2F;
|
23
|
+
font-size: 16px;
|
24
|
+
color: #FFFFFF;
|
25
|
+
}
|
26
|
+
'''
|
27
|
+
|
28
|
+
# Number of data frames to keep in the rolling buffer
|
29
|
+
ROLL_BUFFER_SIZE = 6
|
30
|
+
|
31
|
+
# Data transformation functions for complex data visualization
|
32
|
+
DataTransform = Callable[[Union[np.ndarray, list], float], np.ndarray]
|
33
|
+
|
34
|
+
TRANSFORMS: Dict[str, DataTransform] = {
|
35
|
+
"mag": lambda data, _: np.abs(data),
|
36
|
+
"phase": lambda data, _: np.angle(data),
|
37
|
+
"real": lambda data, _: np.real(data),
|
38
|
+
"imag": lambda data, _: np.imag(data),
|
39
|
+
"rot": lambda data, angle: np.real(np.exp(1j * angle) * np.array(data))
|
40
|
+
}
|
41
|
+
|
42
|
+
# List of available transformation names
|
43
|
+
TRANSFORM_NAMES: List[str] = list(TRANSFORMS.keys())
|
44
|
+
|
45
|
+
# Colors for selected and unselected states (RGB values)
|
46
|
+
COLOR_SELECTED: Tuple[int, int, int] = (0, 0, 0)
|
47
|
+
COLOR_UNSELECTED: Tuple[int, int, int] = (6, 6, 8)
|
48
|
+
|
49
|
+
# Default colors for plot lines (RGB values)
|
50
|
+
DEFAULT_COLORS: List[Tuple[int, int, int]] = [
|
51
|
+
(200, 0, 0), # Bright red
|
52
|
+
(55, 100, 180), # Blue
|
53
|
+
(40, 80, 150), # Medium blue
|
54
|
+
(30, 50, 110), # Dark blue
|
55
|
+
(25, 40, 70), # Navy
|
56
|
+
(25, 30, 50), # Dark navy
|
57
|
+
]
|
58
|
+
|
59
|
+
# Line widths for each plot line
|
60
|
+
LINE_WIDTHS: List[int] = [3, 2, 2, 2, 1, 1]
|
61
|
+
|
62
|
+
# Symbol sizes for each plot line (0 means no symbols)
|
63
|
+
SYMBOL_SIZES: List[int] = [5, 0, 0, 0, 0, 0]
|
64
|
+
|
65
|
+
# Reversed indices for the rolling buffer (newest to oldest)
|
66
|
+
ROLL_INDICES: List[int] = list(range(ROLL_BUFFER_SIZE))[::-1]
|
@@ -0,0 +1,184 @@
|
|
1
|
+
"""
|
2
|
+
QuLab Monitor Dataset Module
|
3
|
+
|
4
|
+
This module provides data management functionality for the QuLab monitor application.
|
5
|
+
It handles storage and retrieval of time-series data with support for both point-by-point
|
6
|
+
and trace-based data collection.
|
7
|
+
|
8
|
+
The Dataset class maintains a rolling buffer of data frames, where each frame can
|
9
|
+
contain multiple named columns of numerical data.
|
10
|
+
"""
|
11
|
+
|
12
|
+
from collections import defaultdict, deque
|
13
|
+
from typing import Union, Sequence
|
14
|
+
|
15
|
+
from .config import ROLL_BUFFER_SIZE
|
16
|
+
|
17
|
+
PAUSE_TIME = 1
|
18
|
+
|
19
|
+
Number = Union[int, float, complex]
|
20
|
+
DataPoint = Union[Number, Sequence[Number]]
|
21
|
+
DataFrame = Union[Sequence[Number], Sequence[Sequence[Number]]]
|
22
|
+
|
23
|
+
|
24
|
+
def remove_duplicates(input_list: list[str]) -> list[str]:
|
25
|
+
"""
|
26
|
+
Remove duplicates from a list of strings while preserving order.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
input_list: List of strings that may contain duplicates
|
30
|
+
|
31
|
+
Returns:
|
32
|
+
List of unique strings in their original order
|
33
|
+
"""
|
34
|
+
return list(dict.fromkeys(input_list))
|
35
|
+
|
36
|
+
|
37
|
+
class Dataset:
|
38
|
+
"""
|
39
|
+
A data management class for storing and retrieving time-series data.
|
40
|
+
|
41
|
+
This class maintains a rolling buffer of data frames, where each frame
|
42
|
+
contains multiple columns of numerical data. It supports both point-by-point
|
43
|
+
data collection and trace-based data collection.
|
44
|
+
|
45
|
+
Attributes:
|
46
|
+
column_names: List of column names for the dataset
|
47
|
+
box: Rolling buffer containing data frames
|
48
|
+
dirty: Flag indicating if data has been modified since last read
|
49
|
+
"""
|
50
|
+
|
51
|
+
def __init__(self):
|
52
|
+
"""Initialize an empty dataset with a rolling buffer."""
|
53
|
+
self.column_names: list[str] = []
|
54
|
+
self.box: deque[dict] = deque(maxlen=ROLL_BUFFER_SIZE)
|
55
|
+
self.dirty: bool = True
|
56
|
+
|
57
|
+
def clear(self) -> None:
|
58
|
+
"""Clear all data from the dataset."""
|
59
|
+
self.box.clear()
|
60
|
+
|
61
|
+
def clear_history(self) -> None:
|
62
|
+
"""
|
63
|
+
Clear historical data while preserving the most recent frame.
|
64
|
+
|
65
|
+
This is useful for maintaining the current state while discarding
|
66
|
+
historical data that is no longer needed.
|
67
|
+
"""
|
68
|
+
if self.box:
|
69
|
+
current_frame = self.box.popleft()
|
70
|
+
self.box.clear()
|
71
|
+
self.box.appendleft(current_frame)
|
72
|
+
|
73
|
+
def set_column_names(self, column_names: list[str]) -> None:
|
74
|
+
"""
|
75
|
+
Set or update the column names for the dataset.
|
76
|
+
|
77
|
+
If the new column names differ from the current ones, all existing
|
78
|
+
data will be cleared to maintain consistency.
|
79
|
+
|
80
|
+
Args:
|
81
|
+
column_names: List of unique column names
|
82
|
+
"""
|
83
|
+
column_names = remove_duplicates(column_names)
|
84
|
+
if column_names != self.column_names:
|
85
|
+
self.clear()
|
86
|
+
self.column_names = column_names
|
87
|
+
|
88
|
+
def get_data(self, step: int, x_name: str,
|
89
|
+
y_name: str) -> tuple[list[Number], list[Number]]:
|
90
|
+
"""
|
91
|
+
Retrieve X-Y data pairs from a specific step in the dataset.
|
92
|
+
|
93
|
+
Args:
|
94
|
+
step: Index of the data frame to retrieve
|
95
|
+
x_name: Name of the column to use for X values
|
96
|
+
y_name: Name of the column to use for Y values
|
97
|
+
|
98
|
+
Returns:
|
99
|
+
Tuple containing lists of X and Y values. Returns empty lists if
|
100
|
+
the requested step is not available.
|
101
|
+
"""
|
102
|
+
try:
|
103
|
+
frame = self.box[step]
|
104
|
+
return frame[x_name], frame[y_name]
|
105
|
+
except IndexError:
|
106
|
+
return [], []
|
107
|
+
|
108
|
+
def append(self, dataframe: DataFrame) -> None:
|
109
|
+
"""
|
110
|
+
Append new data to the dataset.
|
111
|
+
|
112
|
+
This method automatically determines whether the input is point data
|
113
|
+
or trace data based on its structure.
|
114
|
+
|
115
|
+
Args:
|
116
|
+
dataframe: Either a list of values for point data or a list of
|
117
|
+
lists for trace data. The length must match the number
|
118
|
+
of columns.
|
119
|
+
|
120
|
+
Raises:
|
121
|
+
AssertionError: If the input data length doesn't match the number
|
122
|
+
of columns.
|
123
|
+
TypeError: If the input data format is invalid.
|
124
|
+
"""
|
125
|
+
if not dataframe:
|
126
|
+
return
|
127
|
+
try:
|
128
|
+
# Test if dataframe is a list of lists (trace data)
|
129
|
+
iter(dataframe[0])
|
130
|
+
self._append_traces(dataframe)
|
131
|
+
except TypeError:
|
132
|
+
self._append_points(dataframe)
|
133
|
+
|
134
|
+
def roll(self) -> None:
|
135
|
+
"""
|
136
|
+
Add a new empty frame to the dataset.
|
137
|
+
|
138
|
+
This creates a new defaultdict that will automatically create empty
|
139
|
+
lists for any new column names accessed.
|
140
|
+
"""
|
141
|
+
self.box.appendleft(defaultdict(list))
|
142
|
+
|
143
|
+
def _append_points(self, points: Sequence[Number]) -> None:
|
144
|
+
"""
|
145
|
+
Append point data to the current frame.
|
146
|
+
|
147
|
+
Args:
|
148
|
+
points: List of values, one for each column
|
149
|
+
|
150
|
+
Raises:
|
151
|
+
AssertionError: If the number of points doesn't match the number
|
152
|
+
of columns
|
153
|
+
"""
|
154
|
+
self.dirty = True
|
155
|
+
if len(points) != len(self.column_names):
|
156
|
+
raise AssertionError(
|
157
|
+
"Point data length mismatch\n"
|
158
|
+
f"Expected {len(self.column_names)} values for columns: {self.column_names}\n"
|
159
|
+
f"Received {len(points)} values: {points}"
|
160
|
+
)
|
161
|
+
|
162
|
+
for name, value in zip(self.column_names, points):
|
163
|
+
self.box[0][name].append(value)
|
164
|
+
|
165
|
+
def _append_traces(self, traces: Sequence[Sequence[Number]]) -> None:
|
166
|
+
"""
|
167
|
+
Append trace data as a new frame.
|
168
|
+
|
169
|
+
Args:
|
170
|
+
traces: List of data sequences, one for each column
|
171
|
+
|
172
|
+
Raises:
|
173
|
+
AssertionError: If the number of traces doesn't match the number
|
174
|
+
of columns
|
175
|
+
"""
|
176
|
+
self.dirty = True
|
177
|
+
if len(traces) != len(self.column_names):
|
178
|
+
raise AssertionError(
|
179
|
+
"Trace data length mismatch\n"
|
180
|
+
f"Expected {len(self.column_names)} sequences for columns: {self.column_names}\n"
|
181
|
+
f"Received {len(traces)} sequences"
|
182
|
+
)
|
183
|
+
|
184
|
+
self.box.appendleft(dict(zip(self.column_names, traces)))
|