QuLab 2.11.7__py3-none-any.whl → 2.11.9__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.
qulab/executor/analyze.py CHANGED
@@ -4,6 +4,7 @@ import time
4
4
 
5
5
  import pyperclip
6
6
  import zmq
7
+ from rich import print
7
8
 
8
9
  from .storage import Report
9
10
 
qulab/executor/cli.py CHANGED
@@ -1,7 +1,9 @@
1
+ import ast
1
2
  import functools
2
3
  import graphlib
3
4
  import importlib
4
5
  import os
6
+ import re
5
7
  import sys
6
8
  from pathlib import Path
7
9
 
@@ -23,7 +25,12 @@ from .utils import workflow_template
23
25
 
24
26
  @logger.catch(reraise=True)
25
27
  def run_script(script_path, extra_paths=None):
26
- """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
+ """
27
34
  import subprocess
28
35
  import sys
29
36
 
@@ -101,8 +108,17 @@ def command_option(command_name):
101
108
  help='The path of the code.')
102
109
  @log_options('create')
103
110
  def create(workflow, code):
104
- """
105
- Create a new workflow file.
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
106
122
  """
107
123
  logger.info(f'[CMD]: create {workflow} --code {code}')
108
124
  if code is None:
@@ -131,8 +147,19 @@ def create(workflow, code):
131
147
  help='The modlule name of the api.')
132
148
  @log_options('set')
133
149
  def set(key, value, api):
134
- """
135
- Set a config.
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
136
163
  """
137
164
  logger.info(f'[CMD]: reg set {key} {value} --api {api}')
138
165
  reg = Registry()
@@ -155,8 +182,17 @@ def set(key, value, api):
155
182
  help='The modlule name of the api.')
156
183
  @log_options('get')
157
184
  def get(key, api):
158
- """
159
- Get a config.
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
160
196
  """
161
197
  logger.info(f'[CMD]: reg get {key} --api {api}')
162
198
  reg = Registry()
@@ -175,8 +211,16 @@ def get(key, api):
175
211
  help='The modlule name of the api.')
176
212
  @log_options('delete')
177
213
  def delete(key, api):
178
- """
179
- Delete a config key.
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
180
224
  """
181
225
  logger.info(f'[CMD]: reg delete {key} --api {api}')
182
226
  reg = Registry()
@@ -199,8 +243,24 @@ def delete(key, api):
199
243
  help='The format of the config.')
200
244
  @log_options('export')
201
245
  def export(file, api, format):
202
- """
203
- Export a config.
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
204
264
  """
205
265
  logger.info(f'[CMD]: reg export {file} --api {api}')
206
266
  reg = Registry()
@@ -237,8 +297,27 @@ def export(file, api, format):
237
297
  help='The format of the config.')
238
298
  @log_options('load')
239
299
  def load(file, api, format):
240
- """
241
- Load a config.
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
242
321
  """
243
322
  logger.info(f'[CMD]: reg load {file} --api {api}')
244
323
  reg = Registry()
@@ -276,14 +355,87 @@ def load(file, api, format):
276
355
  default=lambda: get_config_value("bootstrap", Path),
277
356
  help='The path of the bootstrap.')
278
357
  def boot(bootstrap):
279
- """
280
- Run a bootstrap script.
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
281
368
  """
282
369
  if bootstrap is not None:
283
370
  run_script(bootstrap)
284
371
 
285
372
 
286
- @click.command()
373
+ def parse_dynamic_option_value(value):
374
+ """解析动态参数值"""
375
+ try:
376
+ parsed_value = ast.literal_eval(value)
377
+ except (ValueError, SyntaxError):
378
+ # 如果解析失败,返回原始字符串
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
389
+ return parsed_value
390
+
391
+
392
+ def parse_dynamic_options(args):
393
+ """解析格式为 key=value 的未知参数列表"""
394
+ pattern = re.compile(r'^([a-zA-Z_][\w\-]*)=(.+)$')
395
+ result = {}
396
+ for arg in args:
397
+ match = pattern.match(arg)
398
+ if match:
399
+ key, value = match.groups()
400
+ result[key] = parse_dynamic_option_value(value)
401
+ else:
402
+ raise ValueError(
403
+ f"Invalid argument format: {arg}. Expected format is key=value."
404
+ )
405
+ return result
406
+
407
+
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
425
+
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
433
+ """
434
+
435
+
436
+ @click.command(context_settings=dict(ignore_unknown_options=True,
437
+ allow_extra_args=True),
438
+ help=help_doc)
287
439
  @click.argument('workflow')
288
440
  @click.option('--plot', '-p', is_flag=True, help='Plot the report.')
289
441
  @click.option('--no-dependents',
@@ -301,8 +453,10 @@ def boot(bootstrap):
301
453
  help='Veryfy the source code.')
302
454
  @log_options('run')
303
455
  @command_option('run')
456
+ @click.pass_context
304
457
  @async_command
305
- async def run(workflow,
458
+ async def run(ctx,
459
+ workflow,
306
460
  code,
307
461
  data,
308
462
  api,
@@ -312,18 +466,6 @@ async def run(workflow,
312
466
  freeze,
313
467
  fail_fast,
314
468
  veryfy_source_code=True):
315
- """
316
- Run a workflow.
317
-
318
- If the workflow has entries, run all entries.
319
- If `--no-dependents` is set, only run the workflow itself.
320
- If `--retry` is set, retry the workflow when calibration failed.
321
- If `--freeze` is set, freeze the config table.
322
- If `--plot` is set, plot the report.
323
- If `--api` is set, use the api to get and update the config table.
324
- If `--code` is not set, use the current working directory.
325
- If `--data` is not set, use the `logs` directory in the code path.
326
- """
327
469
  logger.info(
328
470
  f'[CMD]: run {workflow} --code {code} --data {data} --api {api}'
329
471
  f'{" --plot" if plot else ""}'
@@ -342,6 +484,13 @@ async def run(workflow,
342
484
  code = Path(os.path.expanduser(code))
343
485
  data = Path(os.path.expanduser(data))
344
486
 
487
+ extra_args = ctx.args
488
+ params = parse_dynamic_options(extra_args)
489
+
490
+ if params:
491
+ workflow = (workflow, params)
492
+ rich.print(workflow)
493
+
345
494
  wf = load_workflow(workflow, code, veryfy_source_code=veryfy_source_code)
346
495
  check_toplogy(wf, code, veryfy_source_code=veryfy_source_code)
347
496
 
@@ -437,15 +586,30 @@ async def maintain(workflow,
437
586
  plot,
438
587
  fail_fast,
439
588
  veryfy_source_code=True):
440
- """
441
- Maintain a workflow.
442
-
443
- If the workflow has entries, run all entries.
444
- If `--retry` is set, retry the workflow when calibration failed.
445
- If `--plot` is set, plot the report.
446
- If `--api` is set, use the api to get and update the config table.
447
- If `--code` is not set, use the current working directory.
448
- If `--data` is not set, use the `logs` directory in the code path.
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
449
613
  """
450
614
  logger.info(
451
615
  f'[CMD]: maintain {workflow} --code {code} --data {data} --api {api}'
@@ -515,13 +679,28 @@ async def maintain(workflow,
515
679
  @command_option('reproduce')
516
680
  @async_command
517
681
  async def reproduce(report_id, code, data, api, plot):
518
- """
519
- Reproduce a report.
520
-
521
- If `--plot` is set, plot the report.
522
- If `--api` is set, use the api to get and update the config table.
523
- If `--code` is not set, use the current working directory.
524
- If `--data` is not set, use the `logs` directory in the code path.
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
525
704
  """
526
705
  logger.info(
527
706
  f'[CMD]: reproduce {report_id} --code {code} --data {data} --api {api}'
qulab/executor/utils.py CHANGED
@@ -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
- import random
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 值,就是一个谕示,可以通过 oracle 来设定。
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)
qulab/monitor/__init__.py CHANGED
@@ -1 +1 @@
1
- from .monitor import Monitor, get_monitor
1
+ from .monitor import MonitorUI, get_monitor
qulab/monitor/__main__.py CHANGED
@@ -1,8 +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
+
1
8
  import click
2
9
 
3
- from .monitor import Monitor
10
+ from .monitor import MonitorServer
4
11
 
5
12
 
6
13
  @click.command(name='monitor')
7
- def main():
8
- pass
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...")
qulab/monitor/config.py CHANGED
@@ -1,41 +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
1
16
  import numpy as np
2
17
 
3
- style = '''
18
+ # Qt stylesheet for the application
19
+ STYLE = '''
4
20
  QWidget {
5
21
  font: medium Ubuntu;
6
22
  background-color: #011F2F;
7
23
  font-size: 16px;
8
- font-size: 16px;
9
- color:#FFFFFF;
24
+ color: #FFFFFF;
10
25
  }
11
26
  '''
12
- #
13
- Nroll = 6
14
-
15
- # Format of the data.
16
- forms = {
17
- "mag": lambda w, ang: np.abs(w),
18
- "phase": lambda w, ang: np.angle(w),
19
- "real": lambda w, ang: np.real(w),
20
- "imag": lambda w, ang: np.imag(w),
21
- "rot": lambda w, ang: np.real(np.exp(1j * ang) * np.array(w))
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))
22
40
  }
23
- form_keys = list(forms.keys())
24
- #
25
- COL_SEL = (0, 0, 0)
26
- COL_UNSEL = (6, 6, 8)
27
- #
28
-
29
- defualt_colors = [
30
- (200, 0, 0),
31
- (55, 100, 180),
32
- (40, 80, 150),
33
- (30, 50, 110),
34
- (25, 40, 70),
35
- (25, 30, 50),
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
36
57
  ]
37
58
 
38
- widths = [3, 2, 2, 2, 1, 1]
39
- SymSize = [5, 0, 0, 0, 0, 0]
40
- ridx = list(range(Nroll))
41
- ridx.reverse()
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]