QuLab 2.0.0__py3-none-any.whl → 2.10.12__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.
Files changed (69) hide show
  1. qulab/__init__.py +33 -1
  2. qulab/__main__.py +4 -0
  3. qulab/cli/commands.py +31 -0
  4. qulab/cli/config.py +170 -0
  5. qulab/cli/decorators.py +28 -0
  6. qulab/executor/__init__.py +5 -0
  7. qulab/executor/analyze.py +188 -0
  8. qulab/executor/cli.py +447 -0
  9. qulab/executor/load.py +563 -0
  10. qulab/executor/registry.py +185 -0
  11. qulab/executor/schedule.py +543 -0
  12. qulab/executor/storage.py +615 -0
  13. qulab/executor/template.py +259 -0
  14. qulab/executor/utils.py +194 -0
  15. qulab/monitor/__init__.py +1 -93
  16. qulab/monitor/__main__.py +8 -0
  17. qulab/monitor/monitor.py +115 -0
  18. qulab/scan/__init__.py +2 -4
  19. qulab/scan/curd.py +221 -0
  20. qulab/scan/models.py +554 -0
  21. qulab/scan/optimize.py +76 -0
  22. qulab/scan/query.py +387 -0
  23. qulab/scan/record.py +603 -0
  24. qulab/scan/scan.py +1166 -0
  25. qulab/scan/server.py +450 -0
  26. qulab/scan/space.py +213 -0
  27. qulab/scan/utils.py +230 -34
  28. qulab/sys/__init__.py +2 -0
  29. qulab/sys/device/basedevice.py +50 -16
  30. qulab/sys/device/loader.py +1 -1
  31. qulab/sys/device/utils.py +46 -13
  32. qulab/sys/drivers/FakeInstrument.py +36 -20
  33. qulab/sys/net/nginx.py +16 -14
  34. qulab/sys/rpc/router.py +35 -0
  35. qulab/sys/rpc/zmq_socket.py +227 -0
  36. qulab/tools/__init__.py +0 -0
  37. qulab/tools/connection_helper.py +39 -0
  38. qulab/typing.py +2 -0
  39. qulab/utils.py +95 -0
  40. qulab/version.py +1 -1
  41. qulab/visualization/__init__.py +188 -0
  42. qulab/visualization/__main__.py +71 -0
  43. qulab/visualization/_autoplot.py +464 -0
  44. qulab/visualization/plot_circ.py +319 -0
  45. qulab/visualization/plot_layout.py +408 -0
  46. qulab/visualization/plot_seq.py +242 -0
  47. qulab/visualization/qdat.py +152 -0
  48. qulab/visualization/rot3d.py +23 -0
  49. qulab/visualization/widgets.py +86 -0
  50. {QuLab-2.0.0.dist-info → qulab-2.10.12.dist-info}/METADATA +28 -12
  51. qulab-2.10.12.dist-info/RECORD +104 -0
  52. {QuLab-2.0.0.dist-info → qulab-2.10.12.dist-info}/WHEEL +1 -1
  53. qulab-2.10.12.dist-info/entry_points.txt +2 -0
  54. QuLab-2.0.0.dist-info/RECORD +0 -71
  55. qulab/monitor/multiploter/__init__.py +0 -1
  56. qulab/scan/base.py +0 -544
  57. qulab/scan/expression.py +0 -360
  58. qulab/scan/scanner.py +0 -244
  59. qulab/scan/transforms.py +0 -16
  60. /qulab/{scan/dataset.py → cli/__init__.py} +0 -0
  61. /qulab/monitor/{multiploter/config.py → config.py} +0 -0
  62. /qulab/monitor/{multiploter/dataset.py → dataset.py} +0 -0
  63. /qulab/monitor/{multiploter/event_queue.py → event_queue.py} +0 -0
  64. /qulab/monitor/{multiploter/main.py → mainwindow.py} +0 -0
  65. /qulab/monitor/{multiploter/ploter.py → ploter.py} +0 -0
  66. /qulab/monitor/{multiploter/qt_compat.py → qt_compat.py} +0 -0
  67. /qulab/monitor/{multiploter/toolbar.py → toolbar.py} +0 -0
  68. {QuLab-2.0.0.dist-info → qulab-2.10.12.dist-info/licenses}/LICENSE +0 -0
  69. {QuLab-2.0.0.dist-info → qulab-2.10.12.dist-info}/top_level.txt +0 -0
qulab/__init__.py CHANGED
@@ -1 +1,33 @@
1
- from .version import __version__
1
+ from qlisp import (CR, CX, CZ, SWAP, A, B, BellPhiM, BellPhiP, BellPsiM,
2
+ BellPsiP, H, S, Sdag, SQiSWAP, T, Tdag, U, Unitary2Angles,
3
+ applySeq, draw, fSim, iSWAP, kak_decomposition, kak_vector,
4
+ make_immutable, measure, phiminus, phiplus, psiminus,
5
+ psiplus, regesterGateMatrix, rfUnitary, seq2mat, sigmaI,
6
+ sigmaM, sigmaP, sigmaX, sigmaY, sigmaZ,
7
+ synchronize_global_phase)
8
+ from qlispc import (COMMAND, FREE, NOTSET, PUSH, READ, SYNC, TRIG, WRITE,
9
+ compile, get_arch, libraries, mapping_qubits,
10
+ register_arch)
11
+ from qlispc.kernel_utils import qcompile
12
+ from wath import (Interval, Primes, Transmon, Z2probs, complex_amp_to_real,
13
+ effective_temperature, exception, find_axis_of_symmetry,
14
+ find_center_of_symmetry, find_cross_point, fit_circle,
15
+ fit_cosine, fit_k, fit_max, fit_peaks, fit_pole, getFTMatrix,
16
+ graph, inv_poly, lin_fit, point_in_ellipse, point_in_polygon,
17
+ point_on_ellipse, point_on_segment, poly_fit, probs2Z,
18
+ relative_delay_to_absolute, thermal_excitation, viterbi_hmm)
19
+ from waveforms import (D, chirp, const, cos, cosh, coshPulse, cosPulse, cut,
20
+ drag, drag_sin, drag_sinx, exp, function, gaussian,
21
+ general_cosine, hanning, interp, mixing, one, poly,
22
+ registerBaseFunc, registerDerivative, samplingPoints,
23
+ sign, sin, sinc, sinh, square, step, t, wave_eval, zero)
24
+
25
+ from .executor.analyze import manual_analysis
26
+ from .executor.registry import Registry
27
+ from .executor.storage import find_report
28
+ from .executor.storage import get_report_by_index as get_report
29
+ from .executor.template import VAR
30
+ from .executor.utils import debug_analyze
31
+ from .scan import Scan, get_record, load_record, lookup, lookup_list
32
+ from .version import __version__
33
+ from .visualization import autoplot
qulab/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .cli.commands import cli
2
+
3
+ if __name__ == '__main__':
4
+ cli()
qulab/cli/commands.py ADDED
@@ -0,0 +1,31 @@
1
+ import click
2
+
3
+ from ..executor.cli import boot, create, get, maintain, reproduce, run, set
4
+ from ..monitor.__main__ import main as monitor
5
+ from ..scan.server import server
6
+ from ..sys.net.cli import dht
7
+ from ..visualization.__main__ import plot
8
+
9
+
10
+ @click.group()
11
+ def cli():
12
+ pass
13
+
14
+
15
+ @cli.command()
16
+ def hello():
17
+ """Print hello world."""
18
+ click.echo('hello, world')
19
+
20
+
21
+ cli.add_command(monitor)
22
+ cli.add_command(plot)
23
+ cli.add_command(dht)
24
+ cli.add_command(server)
25
+ cli.add_command(maintain)
26
+ cli.add_command(run)
27
+ cli.add_command(reproduce)
28
+ cli.add_command(create)
29
+ cli.add_command(set)
30
+ cli.add_command(get)
31
+ cli.add_command(boot)
qulab/cli/config.py ADDED
@@ -0,0 +1,170 @@
1
+ import configparser
2
+ import functools
3
+ import os
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ import click
8
+ from loguru import logger
9
+
10
+ CONFIG_PATH = os.path.expanduser("~/.qulab.ini")
11
+ ENV_PREFIX = "QULAB_"
12
+
13
+
14
+ def _get_config_value(option_name,
15
+ type_cast=str,
16
+ command_name=None,
17
+ default=None):
18
+ """支持命令专属配置的优先级获取"""
19
+ # 构造环境变量名
20
+ if command_name:
21
+ env_var = f"{ENV_PREFIX}{command_name.upper()}_{option_name.upper()}"
22
+ config_section = command_name
23
+ else:
24
+ env_var = f"{ENV_PREFIX}{option_name.upper()}"
25
+ config_section = "common"
26
+
27
+ # 1. 检查环境变量
28
+ if env_value := os.getenv(env_var):
29
+ if type_cast is bool:
30
+ return env_value.lower() in ("true", "1", "yes")
31
+ if "path" in option_name or issubclass(type_cast, Path):
32
+ return Path(os.path.expanduser(env_value))
33
+ return type_cast(env_value)
34
+
35
+ # 2. 检查配置文件
36
+ config = configparser.ConfigParser()
37
+ # 先加载默认配置防止段不存在
38
+ config.read_dict({config_section: {}})
39
+ if Path(CONFIG_PATH).exists():
40
+ config.read(CONFIG_PATH)
41
+
42
+ # 从对应配置段读取
43
+ if config.has_section(config_section):
44
+ if config_value := config.get(config_section,
45
+ option_name,
46
+ fallback=None):
47
+ if type_cast is bool:
48
+ return config_value.lower() in ("true", "1", "yes")
49
+ if "path" in option_name or issubclass(type_cast, Path):
50
+ return Path(os.path.expanduser(config_value))
51
+ return type_cast(config_value)
52
+
53
+ return default # 交给 Click 处理默认值
54
+
55
+
56
+ def get_config_value(option_name,
57
+ type_cast=str,
58
+ command_name=None,
59
+ default=None):
60
+ """
61
+ 获取配置值,支持命令专属配置的优先级获取
62
+
63
+ 优先级:
64
+ 1. 命令行参数
65
+ 2. 配置文件专属配置
66
+ 3. 配置文件公共配置
67
+ 4. 环境变量专属配置
68
+ 5. 环境变量公共配置
69
+ 6. 默认值
70
+ 7. Click 处理默认值
71
+ 8. None
72
+
73
+ Parameters
74
+ ----------
75
+ option_name : str
76
+ 配置项名称
77
+ type_cast : type
78
+ 转换类型
79
+ command_name : str
80
+ 命令名称
81
+ 如果为 None,则不使用命令专属配置
82
+ default : any
83
+ 默认值
84
+ 如果为 None,则不使用默认值
85
+
86
+ Returns
87
+ -------
88
+ any
89
+ 配置值
90
+ 如果没有找到配置值,则返回 None
91
+ """
92
+ value = _get_config_value(option_name,
93
+ type_cast,
94
+ command_name,
95
+ default=default)
96
+ if value is None and command_name is not None:
97
+ return _get_config_value(option_name, type_cast, default=default)
98
+ return value
99
+
100
+
101
+ def log_options(func=None, command_name=None):
102
+ """通用配置装饰器(所有命令共用)
103
+
104
+ 添加 --debug, --log, --debug-log, --quiet 选项
105
+ 1. --debug: 是否开启调试模式
106
+ 2. --log: 日志文件路径
107
+ 3. --debug-log: 调试日志文件路径
108
+ 4. --quiet: 是否静默输出
109
+ """
110
+
111
+ if isinstance(func, str):
112
+ return functools.partial(log_options, command_name=func)
113
+ if func is None:
114
+ return functools.partial(log_options, command_name=command_name)
115
+
116
+ @click.option("--debug",
117
+ is_flag=True,
118
+ default=get_config_value("debug",
119
+ bool,
120
+ command_name=command_name),
121
+ help=f"Enable debug mode")
122
+ @click.option("--log",
123
+ type=click.Path(),
124
+ default=lambda: get_config_value(
125
+ "log", Path, command_name=command_name),
126
+ help=f"Log file path")
127
+ @click.option("--debug-log",
128
+ type=click.Path(),
129
+ default=lambda: get_config_value(
130
+ "debug_log", Path, command_name=command_name),
131
+ help=f"Debug log file path")
132
+ @click.option("--quiet",
133
+ is_flag=True,
134
+ default=get_config_value("quiet",
135
+ bool,
136
+ command_name=command_name),
137
+ help=f"Disable log output")
138
+ @functools.wraps(func)
139
+ def wrapper(*args, **kwargs):
140
+ debug = bool(kwargs.pop("debug"))
141
+ log = kwargs.pop("log")
142
+ debug_log = kwargs.pop("debug_log")
143
+ quiet = bool(kwargs.pop("quiet"))
144
+
145
+ if debug:
146
+ log_level = "DEBUG"
147
+ else:
148
+ log_level = "INFO"
149
+
150
+ handlers = []
151
+ if log is not None:
152
+ handlers.append(
153
+ dict(sink=log,
154
+ level="INFO",
155
+ rotation="monday at 7:00",
156
+ compression="zip"))
157
+ if debug_log is not None:
158
+ handlers.append(
159
+ dict(sink=debug_log,
160
+ level="DEBUG",
161
+ rotation="monday at 7:00",
162
+ compression="zip"))
163
+ if not quiet or debug:
164
+ handlers.append(dict(sink=sys.stderr, level=log_level))
165
+
166
+ logger.configure(handlers=handlers)
167
+
168
+ return func(*args, **kwargs)
169
+
170
+ return wrapper
@@ -0,0 +1,28 @@
1
+ import asyncio
2
+ import functools
3
+ import inspect
4
+
5
+
6
+ def async_command(func=None):
7
+ """
8
+ Decorator to mark a function as an asynchronous command.
9
+ """
10
+ if func is None:
11
+ return async_command
12
+
13
+ @functools.wraps(func)
14
+ def wrapper(*args, **kwargs):
15
+ return run(func, *args, **kwargs)
16
+
17
+ return wrapper
18
+
19
+
20
+ def run(main, *args, **kwds):
21
+ if inspect.iscoroutinefunction(main):
22
+ try:
23
+ import uvloop
24
+ uvloop.run(main(*args, **kwds))
25
+ except (ImportError, ModuleNotFoundError):
26
+ asyncio.run(main(*args, **kwds))
27
+ else:
28
+ main(*args, **kwds)
@@ -0,0 +1,5 @@
1
+ from loguru import logger
2
+
3
+ from .schedule import maintain, run
4
+
5
+ # logger.configure(handlers=[])
@@ -0,0 +1,188 @@
1
+ import pickle
2
+ import threading
3
+ import time
4
+
5
+ import pyperclip
6
+ import zmq
7
+
8
+ from .storage import Report
9
+
10
+ # 需要复制到 Notebook 的代码模板
11
+ clip_template = """
12
+ from qulab.executor.analyze import get_report as get_report_{server_port}
13
+
14
+ report, history = get_report_{server_port}("tcp://{server_address}:{server_port}")
15
+ # 在这里插入数据处理逻辑
16
+
17
+ {analysis_code}
18
+ """
19
+
20
+ analysis_code = """
21
+ # report.state = 'OK'
22
+ # report.parameters = {}
23
+ # report.oracle = {}
24
+ # report.other_infomation = {}
25
+
26
+ report.parameters
27
+
28
+ """
29
+
30
+
31
+ # ZeroMQ 服务线程,用于响应 Notebook 的请求
32
+ class ServerThread(threading.Thread):
33
+
34
+ def __init__(self, data, timeout):
35
+ super().__init__()
36
+ self.data = data
37
+ self.result = None
38
+ self.port = 0
39
+ self.timeout = timeout
40
+ self.running = True
41
+ self.finished = threading.Event()
42
+ self.context = zmq.Context()
43
+
44
+ def find_free_port(self):
45
+ with zmq.Socket(self.context, zmq.REQ) as s:
46
+ s.bind_to_random_port("tcp://*")
47
+ self.port = int(
48
+ s.getsockopt(zmq.LAST_ENDPOINT).decode().split(":")[-1])
49
+ s.unbind(s.getsockopt(zmq.LAST_ENDPOINT))
50
+ return self.port
51
+
52
+ def run(self):
53
+ self.port = self.find_free_port()
54
+ socket = self.context.socket(zmq.REP)
55
+ socket.bind(f"tcp://*:{self.port}")
56
+ # 设置 recv 超时 1 秒
57
+ socket.RCVTIMEO = 1000
58
+ start_time = time.time()
59
+ try:
60
+ while self.running and (time.time() - start_time < self.timeout):
61
+ try:
62
+ msg = socket.recv()
63
+ except zmq.Again:
64
+ continue # 超时后继续等待
65
+
66
+ # Notebook 端请求数据
67
+ if msg == b"GET":
68
+ socket.send(pickle.dumps(self.data))
69
+ else:
70
+ # Notebook 端提交了处理结果
71
+ try:
72
+ self.result = pickle.loads(msg)
73
+ except Exception as e:
74
+ # 如果解析失败,也返回默认 ACK
75
+ self.result = None
76
+ socket.send(b"ACK")
77
+ self.running = False
78
+ break
79
+ finally:
80
+ socket.close()
81
+ self.context.term()
82
+ self.finished.set()
83
+
84
+ def stop(self):
85
+ self.running = False
86
+ self.finished.set()
87
+
88
+ def wait_for_result(self):
89
+ self.finished.wait()
90
+ return self.result
91
+
92
+
93
+ # 进入分析流程,启动服务并打印等待提示
94
+ def get_result_or_wait_until_timeout(report: Report, history: Report | None,
95
+ timeout: float) -> Report:
96
+ server = ServerThread((report, history), timeout)
97
+ server.start()
98
+
99
+ parameters = report.parameters
100
+ oracle = report.oracle
101
+ other_infomation = report.other_infomation
102
+ state = report.state
103
+
104
+ # 格式化代码模板
105
+ code = clip_template.format(server_address="127.0.0.1",
106
+ server_port=server.port,
107
+ analysis_code=analysis_code)
108
+
109
+ # 将代码复制到剪切板
110
+ pyperclip.copy(code)
111
+
112
+ # 同时打印到终端,防止误操作导致剪切板内容丢失
113
+ print("请在 Jupyter Notebook 中运行下面这段代码:")
114
+ print("-" * 60)
115
+ print(code)
116
+ print("-" * 60)
117
+ print("等待 Notebook 提交处理结果,或等待超时({} 秒)...".format(timeout))
118
+
119
+ start_time = time.time()
120
+ # 采用循环等待提交结果,间隔 0.5 秒检测一次
121
+ while server.finished.wait(timeout=0.5) is False:
122
+ elapsed = time.time() - start_time
123
+ if elapsed >= timeout:
124
+ # 超时后结束等待
125
+ server.stop()
126
+ break
127
+
128
+ result = server.wait_for_result()
129
+ if result is None:
130
+ return (state, parameters, oracle, other_infomation, code)
131
+ else:
132
+ return result
133
+
134
+
135
+ def manual_analysis(report: Report, history=None, timeout=3600):
136
+ try:
137
+ (state, parameters, oracle, other_infomation,
138
+ code) = get_result_or_wait_until_timeout(report, history, timeout)
139
+ report.parameters = parameters
140
+ report.oracle = oracle
141
+ report.state = state
142
+ report.other_infomation = other_infomation
143
+ except Exception as e:
144
+ pass
145
+ return report
146
+
147
+
148
+ def get_report(address: str) -> Report:
149
+ import IPython
150
+
151
+ ipy = IPython.get_ipython()
152
+ if ipy is None:
153
+ raise RuntimeError("请在 Jupyter Notebook 中运行此函数。")
154
+ ipy.set_next_input(("from qulab.executor.analyze import submit_report\n"
155
+ "# 处理完成后,提交结果\n"
156
+ f"# submit_report(report, \"{address}\")"),
157
+ replace=False)
158
+ context = zmq.Context()
159
+ sock = context.socket(zmq.REQ)
160
+ sock.connect(address)
161
+ # 请求数据
162
+ sock.send(b"GET")
163
+ report, history = pickle.loads(sock.recv())
164
+ return report, history
165
+
166
+
167
+ def submit_report(report: Report, address: str):
168
+ import IPython
169
+
170
+ ipy = IPython.get_ipython()
171
+ if ipy is None:
172
+ raise RuntimeError("请在 Jupyter Notebook 中运行此函数。")
173
+
174
+ code = ipy.user_ns['In'][-2]
175
+
176
+ parameters = report.parameters
177
+ oracle = report.oracle
178
+ other_infomation = report.other_infomation
179
+ state = report.state
180
+
181
+ context = zmq.Context()
182
+ sock = context.socket(zmq.REQ)
183
+ sock.connect(address)
184
+ # 提交处理后的结果
185
+ sock.send(pickle.dumps(
186
+ (state, parameters, oracle, other_infomation, code)))
187
+ ack = sock.recv()
188
+ print("结果已提交。")