QuLab 2.10.10__cp313-cp313-win_amd64.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 (107) hide show
  1. qulab/__init__.py +33 -0
  2. qulab/__main__.py +4 -0
  3. qulab/cli/__init__.py +0 -0
  4. qulab/cli/commands.py +30 -0
  5. qulab/cli/config.py +170 -0
  6. qulab/cli/decorators.py +28 -0
  7. qulab/dicttree.py +523 -0
  8. qulab/executor/__init__.py +5 -0
  9. qulab/executor/analyze.py +188 -0
  10. qulab/executor/cli.py +434 -0
  11. qulab/executor/load.py +563 -0
  12. qulab/executor/registry.py +185 -0
  13. qulab/executor/schedule.py +543 -0
  14. qulab/executor/storage.py +615 -0
  15. qulab/executor/template.py +259 -0
  16. qulab/executor/utils.py +194 -0
  17. qulab/expression.py +827 -0
  18. qulab/fun.cp313-win_amd64.pyd +0 -0
  19. qulab/monitor/__init__.py +1 -0
  20. qulab/monitor/__main__.py +8 -0
  21. qulab/monitor/config.py +41 -0
  22. qulab/monitor/dataset.py +77 -0
  23. qulab/monitor/event_queue.py +54 -0
  24. qulab/monitor/mainwindow.py +234 -0
  25. qulab/monitor/monitor.py +115 -0
  26. qulab/monitor/ploter.py +123 -0
  27. qulab/monitor/qt_compat.py +16 -0
  28. qulab/monitor/toolbar.py +265 -0
  29. qulab/scan/__init__.py +2 -0
  30. qulab/scan/curd.py +221 -0
  31. qulab/scan/models.py +554 -0
  32. qulab/scan/optimize.py +76 -0
  33. qulab/scan/query.py +387 -0
  34. qulab/scan/record.py +603 -0
  35. qulab/scan/scan.py +1166 -0
  36. qulab/scan/server.py +450 -0
  37. qulab/scan/space.py +213 -0
  38. qulab/scan/utils.py +234 -0
  39. qulab/storage/__init__.py +0 -0
  40. qulab/storage/__main__.py +51 -0
  41. qulab/storage/backend/__init__.py +0 -0
  42. qulab/storage/backend/redis.py +204 -0
  43. qulab/storage/base_dataset.py +352 -0
  44. qulab/storage/chunk.py +60 -0
  45. qulab/storage/dataset.py +127 -0
  46. qulab/storage/file.py +273 -0
  47. qulab/storage/models/__init__.py +22 -0
  48. qulab/storage/models/base.py +4 -0
  49. qulab/storage/models/config.py +28 -0
  50. qulab/storage/models/file.py +89 -0
  51. qulab/storage/models/ipy.py +58 -0
  52. qulab/storage/models/models.py +88 -0
  53. qulab/storage/models/record.py +161 -0
  54. qulab/storage/models/report.py +22 -0
  55. qulab/storage/models/tag.py +93 -0
  56. qulab/storage/storage.py +95 -0
  57. qulab/sys/__init__.py +2 -0
  58. qulab/sys/chat.py +688 -0
  59. qulab/sys/device/__init__.py +3 -0
  60. qulab/sys/device/basedevice.py +255 -0
  61. qulab/sys/device/loader.py +86 -0
  62. qulab/sys/device/utils.py +79 -0
  63. qulab/sys/drivers/FakeInstrument.py +68 -0
  64. qulab/sys/drivers/__init__.py +0 -0
  65. qulab/sys/ipy_events.py +125 -0
  66. qulab/sys/net/__init__.py +0 -0
  67. qulab/sys/net/bencoder.py +205 -0
  68. qulab/sys/net/cli.py +169 -0
  69. qulab/sys/net/dhcp.py +543 -0
  70. qulab/sys/net/dhcpd.py +176 -0
  71. qulab/sys/net/kad.py +1142 -0
  72. qulab/sys/net/kcp.py +192 -0
  73. qulab/sys/net/nginx.py +194 -0
  74. qulab/sys/progress.py +190 -0
  75. qulab/sys/rpc/__init__.py +0 -0
  76. qulab/sys/rpc/client.py +0 -0
  77. qulab/sys/rpc/exceptions.py +96 -0
  78. qulab/sys/rpc/msgpack.py +1052 -0
  79. qulab/sys/rpc/msgpack.pyi +41 -0
  80. qulab/sys/rpc/router.py +35 -0
  81. qulab/sys/rpc/rpc.py +412 -0
  82. qulab/sys/rpc/serialize.py +139 -0
  83. qulab/sys/rpc/server.py +29 -0
  84. qulab/sys/rpc/socket.py +29 -0
  85. qulab/sys/rpc/utils.py +25 -0
  86. qulab/sys/rpc/worker.py +0 -0
  87. qulab/sys/rpc/zmq_socket.py +227 -0
  88. qulab/tools/__init__.py +0 -0
  89. qulab/tools/connection_helper.py +39 -0
  90. qulab/typing.py +2 -0
  91. qulab/utils.py +95 -0
  92. qulab/version.py +1 -0
  93. qulab/visualization/__init__.py +188 -0
  94. qulab/visualization/__main__.py +71 -0
  95. qulab/visualization/_autoplot.py +464 -0
  96. qulab/visualization/plot_circ.py +319 -0
  97. qulab/visualization/plot_layout.py +408 -0
  98. qulab/visualization/plot_seq.py +242 -0
  99. qulab/visualization/qdat.py +152 -0
  100. qulab/visualization/rot3d.py +23 -0
  101. qulab/visualization/widgets.py +86 -0
  102. qulab-2.10.10.dist-info/METADATA +110 -0
  103. qulab-2.10.10.dist-info/RECORD +107 -0
  104. qulab-2.10.10.dist-info/WHEEL +5 -0
  105. qulab-2.10.10.dist-info/entry_points.txt +2 -0
  106. qulab-2.10.10.dist-info/licenses/LICENSE +21 -0
  107. qulab-2.10.10.dist-info/top_level.txt +1 -0
@@ -0,0 +1,259 @@
1
+ import ast
2
+ import base64
3
+ import hashlib
4
+ import lzma
5
+ import pickle
6
+ import re
7
+ import string
8
+ import textwrap
9
+ from typing import Any
10
+
11
+ _notset = object()
12
+
13
+
14
+ def VAR(name: str, /, *, default: Any = _notset) -> Any:
15
+ return name
16
+
17
+
18
+ def encode_mapping(mapping):
19
+ mapping_bytes = lzma.compress(pickle.dumps(mapping))
20
+ hash_str = hashlib.md5(mapping_bytes).hexdigest()[:8]
21
+ mappping_code = '\n'.join(
22
+ textwrap.wrap(base64.b64encode(mapping_bytes).decode(),
23
+ 90,
24
+ initial_indent=' ',
25
+ subsequent_indent=' '))
26
+ return hash_str, mappping_code
27
+
28
+
29
+ def decode_mapping(hash_str, mappping_code):
30
+ mapping_bytes = base64.b64decode(mappping_code)
31
+ if hash_str != hashlib.md5(mapping_bytes).hexdigest()[:8]:
32
+ raise ValueError("Hash does not match")
33
+ mapping = pickle.loads(lzma.decompress(mapping_bytes))
34
+ return mapping
35
+
36
+
37
+ class TemplateTypeError(TypeError):
38
+ pass
39
+
40
+
41
+ class TemplateKeyError(KeyError):
42
+ pass
43
+
44
+
45
+ class TemplateVarExtractor(ast.NodeVisitor):
46
+
47
+ def __init__(self, fname, mapping):
48
+ self.var_func_def = (0, 0)
49
+ self.variables = set()
50
+ self.str_variables = set()
51
+ self.replacements = {}
52
+ self.fname = fname
53
+ self.mapping = mapping
54
+
55
+ def visit_Constant(self, node):
56
+ if isinstance(node.value, str):
57
+ self._process_string(node.value, node.lineno, node.col_offset,
58
+ node.end_lineno, node.end_col_offset)
59
+
60
+ def visit_JoinedStr(self, node):
61
+ for value in node.values:
62
+ if isinstance(value, ast.Constant) and isinstance(
63
+ value.value, str):
64
+ self._process_string(value.value, value.lineno,
65
+ value.col_offset, value.end_lineno,
66
+ value.end_col_offset)
67
+ self.generic_visit(node)
68
+
69
+ def visit_FunctionDef(self, node):
70
+ if node.name == 'VAR':
71
+ self.var_func_def = (node.lineno, node.end_lineno)
72
+ self.generic_visit(node)
73
+
74
+ def visit_Call(self, node):
75
+ if isinstance(node.func, ast.Name) and node.func.id == 'VAR':
76
+ arg = node.args[0]
77
+ if not isinstance(arg, ast.Constant) or not isinstance(
78
+ arg.value, str):
79
+ raise SyntaxError(
80
+ f"Argument of VAR function must be a string."
81
+ f" {self.fname}:{node.lineno}"
82
+ )
83
+ if len(node.args) != 1:
84
+ raise SyntaxError(
85
+ f"VAR function only accept one argument."
86
+ f" {self.fname}:{node.lineno}"
87
+ )
88
+ default = _notset
89
+ for k in node.keywords:
90
+ if k.arg == 'default':
91
+ default = k.value
92
+ # if isinstance(k.value, ast.Constant):
93
+ # # default = k.value.value
94
+ # default = k.value
95
+ # else:
96
+ # default = k.value
97
+ # # raise SyntaxError(
98
+ # # f"Argument of 'default' must be a constant. {self.fname}:{node.lineno}"
99
+ # # )
100
+ else:
101
+ raise SyntaxError(
102
+ f"VAR function only accept keyword argument 'default'."
103
+ f" {self.fname}:{node.lineno}"
104
+ )
105
+
106
+ if default is _notset:
107
+ # new_node = ast.Subscript(value=ast.Name(id="__VAR",
108
+ # ctx=ast.Load()),
109
+ # slice=ast.Constant(value=arg.value),
110
+ # ctx=ast.Load())
111
+ if arg.value not in self.mapping:
112
+ raise TemplateKeyError(
113
+ f"The variable '{arg.value}' is not provided in mapping."
114
+ f" {self.fname}:{node.lineno}"
115
+ )
116
+ self.replacements[(node.lineno, node.end_lineno,
117
+ node.col_offset,
118
+ node.end_col_offset)] = ('VAR', arg.value,
119
+ None, None)
120
+ else:
121
+ # new_node = ast.Call(
122
+ # func=ast.Attribute(value=ast.Name(id='__VAR',
123
+ # ctx=ast.Load()),
124
+ # attr='get',
125
+ # ctx=ast.Load()),
126
+ # args=[ast.Constant(value=arg.value)],
127
+ # keywords=[
128
+ # ast.keyword(arg='default',
129
+ # value=value)
130
+ # ],
131
+ # ctx=ast.Load())
132
+ self.replacements[(node.lineno, node.end_lineno,
133
+ node.col_offset,
134
+ node.end_col_offset)] = ('VAR', arg.value,
135
+ None, None)
136
+ # ast.fix_missing_locations(new_node)
137
+ # new_source = ast.unparse(new_node)
138
+ # print(new_source)
139
+
140
+ self.variables.add(arg.value)
141
+
142
+ self.generic_visit(node)
143
+
144
+ def _process_string(self, s: str, lineno: int, col_offset: int,
145
+ end_lineno: int, end_col_offset: int):
146
+ """解析字符串内容,提取模板变量"""
147
+ lines = s.split('\n')
148
+ for offset, line in enumerate(lines):
149
+ current_lineno = lineno + offset
150
+ template = string.Template(line)
151
+ for var_name in template.get_identifiers():
152
+ if var_name not in self.mapping:
153
+ raise TemplateKeyError(
154
+ f"The variable '{var_name}' is not provided in mapping."
155
+ f" {self.fname}:{current_lineno}")
156
+ if not isinstance(self.mapping[var_name], str):
157
+ raise TemplateTypeError(
158
+ f"Mapping value for '{var_name}' must be a string,"
159
+ f" but got {type(self.mapping[var_name])}."
160
+ f" {self.fname}:{current_lineno}")
161
+ self.str_variables.add(var_name)
162
+ start, stop = 0, len(line)
163
+ if current_lineno == lineno:
164
+ start = col_offset
165
+ if current_lineno == end_lineno:
166
+ stop = end_col_offset
167
+ self.replacements[(current_lineno, current_lineno, start,
168
+ stop)] = ('STR', var_name, None, None)
169
+
170
+
171
+ def inject_mapping(source: str, mapping: dict[str, Any],
172
+ fname: str) -> list[tuple[str, int]]:
173
+ hash_str, mapping_code = encode_mapping(mapping)
174
+
175
+ tree = ast.parse(source)
176
+
177
+ lines = source.splitlines()
178
+ lines_offset = [0 for _ in range(len(lines))]
179
+
180
+ extractor = TemplateVarExtractor(fname, mapping)
181
+ extractor.visit(tree)
182
+
183
+ # remove VAR function definition
184
+ if extractor.var_func_def != (0, 0):
185
+ for i in range(extractor.var_func_def[0] - 1,
186
+ extractor.var_func_def[1]):
187
+ lines[i] = ''
188
+
189
+ for (lineno, end_lineno, col_offset,
190
+ end_col_offset), (kind, name, old_source,
191
+ new_source) in extractor.replacements.items():
192
+ head = lines[lineno - 1][:col_offset + lines_offset[lineno - 1]]
193
+ tail = lines[end_lineno - 1][end_col_offset +
194
+ lines_offset[end_lineno - 1]:]
195
+ length_of_last_line = len(lines[end_lineno - 1])
196
+ content = lines[lineno - 1:end_lineno]
197
+ content[0] = content[0].removeprefix(head)
198
+ content[-1] = content[-1].removesuffix(tail)
199
+ content = '\n'.join(content)
200
+
201
+ if kind == 'STR':
202
+ template = string.Template(content)
203
+ formated_lines = template.substitute(mapping).splitlines()
204
+ formated_lines[0] = head + formated_lines[0]
205
+ formated_lines[-1] = formated_lines[-1] + tail
206
+ if len(formated_lines) == 1:
207
+ lines[lineno - 1] = formated_lines[0]
208
+ lines_offset[lineno -
209
+ 1] += len(lines[lineno - 1]) - length_of_last_line
210
+ else:
211
+ lines[lineno - 1:end_lineno] = formated_lines
212
+ lines_offset[end_lineno - 1] += len(
213
+ lines[end_lineno - 1]) - length_of_last_line
214
+ else:
215
+ pattern = re.compile(
216
+ r'VAR\s*\(\s*' # VAR(
217
+ r'(["\'])(\w+)\1' # 捕获引号和变量名
218
+ r'(?:\s*,\s*default\s*=\s*' # 匹配 default 参数
219
+ r'([^),]*)' # 捕获 default 值(排除逗号和括号)
220
+ r')?' # 可选参数组
221
+ r'\s*\)', # 闭合括号
222
+ re.DOTALL
223
+ ) # yapf: disable
224
+
225
+ def replacement(match):
226
+ quote = match.group(1)
227
+ var_name = match.group(2)
228
+ default_value = match.group(3)
229
+
230
+ base = f'__VAR_{hash_str}'
231
+ if default_value is not None:
232
+ # 清除 default 值前后的空格
233
+ clean_value = default_value.strip()
234
+ return f'{base}.get({quote}{var_name}{quote}, {clean_value})'
235
+ else:
236
+ return f'{base}[{quote}{var_name}{quote}]'
237
+
238
+ new_content = re.sub(pattern, replacement, content)
239
+
240
+ if lineno == end_lineno:
241
+ lines[lineno - 1] = head + new_content + tail
242
+ lines_offset[lineno -
243
+ 1] += len(lines[lineno - 1]) - length_of_last_line
244
+ else:
245
+ lines[lineno - 1] = head + new_content[:-1]
246
+ for i in range(lineno, end_lineno - 1):
247
+ lines[i] = ''
248
+ lines[end_lineno - 1] = new_content[-1] + tail
249
+ lines_offset[end_lineno - 1] += len(
250
+ lines[end_lineno - 1]) - length_of_last_line
251
+
252
+ injected_code = '\n'.join([
253
+ f"__QULAB_TEMPLATE__ = \"{fname}\"",
254
+ f"from qulab.executor.template import decode_mapping as __decode_{hash_str}",
255
+ f"__VAR_{hash_str} = __decode_{hash_str}(\"{hash_str}\", \"\"\"",
256
+ mapping_code, " \"\"\")", *lines
257
+ ])
258
+
259
+ return injected_code, hash_str
@@ -0,0 +1,194 @@
1
+ import inspect
2
+ from pathlib import Path
3
+
4
+ from ..cli.config import get_config_value
5
+ from .load import load_workflow
6
+
7
+
8
+ class Node:
9
+
10
+ def __init__(self, name: str):
11
+ self.name = name
12
+ self.dependents = []
13
+
14
+
15
+ class Tree:
16
+
17
+ def __init__(self):
18
+ self.nodes = {}
19
+ self.heads = []
20
+
21
+ def add_node(self, node: str):
22
+ self.nodes[node] = Node(node)
23
+
24
+
25
+ def dependent_tree(node: str, code_path: str | Path) -> dict[str, list[str]]:
26
+ '''
27
+ Returns a dict of nodes and their dependents.
28
+ '''
29
+ tree = {}
30
+ for n in load_workflow(node, code_path).depends():
31
+ tree[n] = dependent_tree(n, code_path)
32
+ return tree
33
+
34
+
35
+ def workflow_template(workflow: str, deps: list[str]) -> str:
36
+ return f"""
37
+ import numpy as np
38
+ from loguru import logger
39
+
40
+ from qulab import VAR
41
+ from qulab.typing import Report
42
+
43
+
44
+ # 多长时间应该检查一次校准实验,单位是秒。
45
+ __timeout__ = 7*24*3600
46
+
47
+ def depends():
48
+ return {deps!r}
49
+
50
+
51
+ async def calibrate():
52
+ logger.info(f"running {workflow} ...")
53
+
54
+ # calibrate 是一个完整的校准实验,如power Rabi,Ramsey等。
55
+ # 你需要足够的扫描点,以使得后续的 analyze 可以拟合出合适的参数。
56
+
57
+ # 这里只是一个示例,实际上你需要在这里写上你的校准代码。
58
+ x = np.linspace(0, 2*np.pi, 101)
59
+ y = []
60
+ for i in x:
61
+ y.append(np.sin(i))
62
+
63
+ logger.info(f"running {workflow} ... finished!")
64
+ return x, y
65
+
66
+
67
+ async def analyze(report: Report, history: Report | None = None) -> Report:
68
+ \"\"\"
69
+ 分析校准结果。
70
+
71
+ report: Report
72
+ 本次校准实验的数据。
73
+ history: Report | None
74
+ 上次校准实验数据和分析结果,如果有的话。
75
+ \"\"\"
76
+ import random
77
+
78
+ # 这里添加你的分析过程,运行 calibrate 得到的数据,在 report.data 里
79
+ # 你可以得到校准的结果,然后根据这个结果进行分析。
80
+ x, y = report.data
81
+
82
+ # 完整校准后的状态有两种:OK 和 Bad,分别对应校准成功和校准失败。
83
+ # 校准失败是指出现坏数据,无法简单通过重新运行本次校准解决,需要
84
+ # 检查前置步骤。
85
+ report.state = random.choice(['OK', 'Bad'])
86
+
87
+ # 参数是一个字典,包含了本次校准得到的参数,后续会更新到config表中。
88
+ report.parameters = {{'gate.R.Q1.params.amp':1}}
89
+
90
+ # 其他信息可以是任何可序列化的内容,你可以将你想要记录的信息放在这里。
91
+ # 下次校准分析时,这些信息也会在 history 参数中一起传入,帮助你在下
92
+ # 次分析时对比参考。
93
+ report.other_infomation = {{}}
94
+
95
+ return report
96
+
97
+
98
+ async def check():
99
+ logger.info(f"checking {workflow} ...")
100
+
101
+ # check 是一个快速检查实验,用于检查校准是否过时。
102
+ # 你只需要少数扫描点,让后续的 check_analyze 知道参数是否漂移,数据
103
+ # 坏没坏就够了,不要求拟合。
104
+
105
+ # 这里只是一个示例,实际上你需要在这里写上你的检查代码。
106
+ x = np.linspace(0, 2*np.pi, 5)
107
+ y = []
108
+ for i in x:
109
+ y.append(np.sin(i))
110
+
111
+ logger.info(f"checking {workflow} ... finished!")
112
+ return x, y
113
+
114
+
115
+ async def check_analyze(report: Report, history: Report | None = None) -> Report:
116
+ \"\"\"
117
+ 分析检查结果。
118
+
119
+ report: Report
120
+ 本次检查实验的数据。
121
+ history: Report | None
122
+ 上次检查实验数据和分析结果,如果有的话。
123
+ \"\"\"
124
+ import random
125
+
126
+ # 这里添加你的分析过程,运行 check 得到的数据,在 report.data 里
127
+ # 你可以得到校准的结果,然后根据这个结果进行分析。
128
+ x, y = report.data
129
+
130
+ # 状态有三种:Outdated, OK, Bad,分别对应过时、正常、坏数据。
131
+ # Outdated 是指数据过时,即参数漂了,需要重新校准。
132
+ # OK 是指数据正常,参数也没漂,不用重新校准。
133
+ # Bad 是指数据坏了,无法校准,需要检查前置步骤。
134
+ report.state = random.choice(['Outdated', 'OK', 'Bad'])
135
+
136
+ return report
137
+
138
+
139
+ async def oracle(report: Report,
140
+ history: Report | None = None,
141
+ system_state: dict[str:str] | None = None) -> Report:
142
+ \"\"\"
143
+ 谕示:指凭直觉或经验判断,改动某些配置,以期望下次校准成功。
144
+
145
+ 当校准失败时,根据 analyze 的结果,尝试改变某些配置再重新校准整个系统。
146
+ 比如通常我们在死活测不到 rabi 或能谱时,会换一个 idle bias 再试试。这
147
+ 里我们凭直觉设的那个 bias 值,就是一个谕示,可以通过 oracle 来设定。
148
+
149
+ 该函数代入的参数 report 是 analyze 函数的返回值。
150
+ \"\"\"
151
+
152
+ # report.oracle['Q0.bias'] = 0.1
153
+ # report.oracle['Q1.bias'] = -0.03
154
+
155
+ return report
156
+ """
157
+
158
+
159
+ async def debug_analyze(
160
+ report_index: int,
161
+ code_path: str | Path = get_config_value('code', Path),
162
+ data_path: str | Path = get_config_value('data', Path),
163
+ ) -> None:
164
+ from .storage import get_report_by_index
165
+
166
+ report = get_report_by_index(report_index, data_path)
167
+ if report is None:
168
+ raise ValueError(f'Invalid report index: {report_index}')
169
+ workflow = report.workflow
170
+ wf = load_workflow(workflow, code_path)
171
+ if wf is None:
172
+ raise ValueError(f'Invalid workflow: {workflow}')
173
+ if hasattr(wf, '__QULAB_TEMPLATE__'):
174
+ template_mtime = (Path(code_path) /
175
+ wf.__QULAB_TEMPLATE__).stat().st_mtime
176
+ if template_mtime > wf.__mtime__:
177
+ for k in dir(wf):
178
+ if k.startswith('__VAR_') and len(k) == len('__VAR_17fb4dde'):
179
+ var_dict = getattr(wf, k)
180
+ break
181
+ else:
182
+ var_dict = {}
183
+ wf = load_workflow((wf.__QULAB_TEMPLATE__, workflow, var_dict),
184
+ code_path)
185
+
186
+ report = wf.analyze(report, report.previous)
187
+ if inspect.isawaitable(report):
188
+ report = await report
189
+ if hasattr(wf, 'plot'):
190
+ if inspect.iscoroutinefunction(wf.plot):
191
+ await wf.plot(report)
192
+ else:
193
+ wf.plot(report)
194
+ return report