QuLab 2.0.2__cp310-cp310-win_amd64.whl → 2.0.3__cp310-cp310-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.
- {QuLab-2.0.2.dist-info → QuLab-2.0.3.dist-info}/METADATA +2 -1
- {QuLab-2.0.2.dist-info → QuLab-2.0.3.dist-info}/RECORD +10 -10
- qulab/fun.cp310-win_amd64.pyd +0 -0
- qulab/scan/scan.py +257 -265
- qulab/scan/utils.py +80 -34
- qulab/version.py +1 -1
- {QuLab-2.0.2.dist-info → QuLab-2.0.3.dist-info}/LICENSE +0 -0
- {QuLab-2.0.2.dist-info → QuLab-2.0.3.dist-info}/WHEEL +0 -0
- {QuLab-2.0.2.dist-info → QuLab-2.0.3.dist-info}/entry_points.txt +0 -0
- {QuLab-2.0.2.dist-info → QuLab-2.0.3.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: QuLab
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.3
|
|
4
4
|
Summary: contral instruments and manage data
|
|
5
5
|
Author-email: feihoo87 <feihoo87@gmail.com>
|
|
6
6
|
Maintainer-email: feihoo87 <feihoo87@gmail.com>
|
|
@@ -32,6 +32,7 @@ Requires-Dist: ipython >=7.4.0
|
|
|
32
32
|
Requires-Dist: ipywidgets >=7.4.2
|
|
33
33
|
Requires-Dist: loguru >=0.7.2
|
|
34
34
|
Requires-Dist: matplotlib >=3.7.2
|
|
35
|
+
Requires-Dist: nevergrad >=1.0.2
|
|
35
36
|
Requires-Dist: numpy >=1.13.3
|
|
36
37
|
Requires-Dist: ply >=3.11
|
|
37
38
|
Requires-Dist: pyzmq >=25.1.0
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
qulab/__init__.py,sha256=8zLGg-DfQhnDl2Ky0n-zXpN-8e-g7iR0AcaI4l4Vvpk,32
|
|
2
2
|
qulab/__main__.py,sha256=OlYQe7go_eFLKR0uU6D1kIpTKvbpx3WRJFFdoYoFjdE,456
|
|
3
|
-
qulab/fun.cp310-win_amd64.pyd,sha256=
|
|
4
|
-
qulab/version.py,sha256=
|
|
3
|
+
qulab/fun.cp310-win_amd64.pyd,sha256=fmKTeNwow5U3R6yqIbv77flyO5b9_60K9FdOyY2OVxc,31232
|
|
4
|
+
qulab/version.py,sha256=HFL2NgNf74s56viRPRlgp_rAmKP1MzrtnJeQ8LxF_8M,21
|
|
5
5
|
qulab/monitor/__init__.py,sha256=xEVDkJF8issrsDeLqQmDsvtRmrf-UiViFcGTWuzdlFU,43
|
|
6
6
|
qulab/monitor/__main__.py,sha256=k2H1H5Zf9LLXTDLISJkbikLH-z0f1e5i5i6wXXYPOrE,105
|
|
7
7
|
qulab/monitor/config.py,sha256=y_5StMkdrbZO1ziyKBrvIkB7Jclp9RCPK1QbsOhCxnY,785
|
|
@@ -19,8 +19,8 @@ qulab/scan/models.py,sha256=TkiVHF_fUZzYHs4MsCTRh391thpf4Ozd3R_LAU0Gxkg,17657
|
|
|
19
19
|
qulab/scan/optimize.py,sha256=MlT4y422CnP961IR384UKryyZh8riNvrPSd2z_MXLEg,2356
|
|
20
20
|
qulab/scan/query_record.py,sha256=gRUBjablvjWhDM-nZEunMVxQbUeaFF8qG7sROy9kIbs,11882
|
|
21
21
|
qulab/scan/recorder.py,sha256=EgS7NzARFqZeWUJp188iFQWr-_fa7Akm0Op-tvGqh7A,15849
|
|
22
|
-
qulab/scan/scan.py,sha256=
|
|
23
|
-
qulab/scan/utils.py,sha256=
|
|
22
|
+
qulab/scan/scan.py,sha256=jDsjyOPf15DV8rk1q2CYH7aDO6ypaP6n7t3LmNaY03o,23187
|
|
23
|
+
qulab/scan/utils.py,sha256=XM-eKL5Xkm0hihhGS7Kq4g654Ye7n7TcU_f95gxtXq8,2634
|
|
24
24
|
qulab/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
25
|
qulab/storage/__main__.py,sha256=6-EjN0waX1yfcMPJXqpIr9UlrIEsSCFApm5G-ZeaPMQ,1742
|
|
26
26
|
qulab/storage/base_dataset.py,sha256=28y3-OZrqJ52p5sbirEpUgjb7hqwLLpd38KU9DCkD24,12217
|
|
@@ -76,9 +76,9 @@ qulab/visualization/plot_layout.py,sha256=yAnMONOms7_szCdng-8wPpUMPis5UnbaNNzV4K
|
|
|
76
76
|
qulab/visualization/plot_seq.py,sha256=h9D0Yl_yO64IwlvBgzMu9EBKr9gg6y8QE55gu2PfTns,2783
|
|
77
77
|
qulab/visualization/qdat.py,sha256=HubXFu4nfcA7iUzghJGle1C86G6221hicLR0b-GqhKQ,5887
|
|
78
78
|
qulab/visualization/widgets.py,sha256=HcYwdhDtLreJiYaZuN3LfofjJmZcLwjMfP5aasebgDo,3266
|
|
79
|
-
QuLab-2.0.
|
|
80
|
-
QuLab-2.0.
|
|
81
|
-
QuLab-2.0.
|
|
82
|
-
QuLab-2.0.
|
|
83
|
-
QuLab-2.0.
|
|
84
|
-
QuLab-2.0.
|
|
79
|
+
QuLab-2.0.3.dist-info/LICENSE,sha256=b4NRQ-GFVpJMT7RuExW3NwhfbrYsX7AcdB7Gudok-fs,1086
|
|
80
|
+
QuLab-2.0.3.dist-info/METADATA,sha256=CBLJJtH8PMO1np50hxS8-Xbs1Bj4lFN1T7AwYCFI47Q,3609
|
|
81
|
+
QuLab-2.0.3.dist-info/WHEEL,sha256=lO6CqtLHCAi38X3Es1a4R1lAjZFvN010IMRCFo2S7Mc,102
|
|
82
|
+
QuLab-2.0.3.dist-info/entry_points.txt,sha256=ohBzutEnQimP_BZWiuXdSliu4QAYSHHcN0PZD8c7ZCY,46
|
|
83
|
+
QuLab-2.0.3.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
|
|
84
|
+
QuLab-2.0.3.dist-info/RECORD,,
|
qulab/fun.cp310-win_amd64.pyd
CHANGED
|
Binary file
|
qulab/scan/scan.py
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import ast
|
|
2
1
|
import asyncio
|
|
2
|
+
import datetime
|
|
3
3
|
import inspect
|
|
4
4
|
import itertools
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
5
7
|
import sys
|
|
6
8
|
import uuid
|
|
7
9
|
from graphlib import TopologicalSorter
|
|
8
10
|
from pathlib import Path
|
|
9
11
|
from types import MethodType
|
|
10
|
-
from typing import Any, Callable, Type
|
|
12
|
+
from typing import Any, Awaitable, Callable, Iterable, Type
|
|
11
13
|
|
|
12
14
|
import dill
|
|
13
15
|
import numpy as np
|
|
@@ -20,72 +22,14 @@ from ..sys.rpc.zmq_socket import ZMQContextManager
|
|
|
20
22
|
from .expression import Env, Expression, Symbol
|
|
21
23
|
from .optimize import NgOptimizer
|
|
22
24
|
from .recorder import Record
|
|
25
|
+
from .utils import async_zip, call_function
|
|
23
26
|
|
|
27
|
+
__process_uuid = uuid.uuid1()
|
|
28
|
+
__task_counter = itertools.count()
|
|
24
29
|
|
|
25
|
-
async def call_function(func: Callable | Expression, variables: dict[str,
|
|
26
|
-
Any]):
|
|
27
|
-
if isinstance(func, Expression):
|
|
28
|
-
env = Env()
|
|
29
|
-
for name in func.symbols():
|
|
30
|
-
if name in variables:
|
|
31
|
-
if inspect.isawaitable(variables[name]):
|
|
32
|
-
variables[name] = await variables[name]
|
|
33
|
-
env.variables[name] = variables[name]
|
|
34
|
-
else:
|
|
35
|
-
raise ValueError(f'{name} is not provided.')
|
|
36
|
-
return func.eval(env)
|
|
37
30
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
except:
|
|
41
|
-
return func()
|
|
42
|
-
args = []
|
|
43
|
-
for name, param in sig.parameters.items():
|
|
44
|
-
if param.kind == param.POSITIONAL_OR_KEYWORD:
|
|
45
|
-
if name in variables:
|
|
46
|
-
if inspect.isawaitable(variables[name]):
|
|
47
|
-
variables[name] = await variables[name]
|
|
48
|
-
args.append(variables[name])
|
|
49
|
-
elif param.default is not param.empty:
|
|
50
|
-
args.append(param.default)
|
|
51
|
-
else:
|
|
52
|
-
raise ValueError(f'parameter {name} is not provided.')
|
|
53
|
-
elif param.kind == param.VAR_POSITIONAL:
|
|
54
|
-
raise ValueError('not support VAR_POSITIONAL')
|
|
55
|
-
elif param.kind == param.VAR_KEYWORD:
|
|
56
|
-
ret = func(**variables)
|
|
57
|
-
if inspect.isawaitable(ret):
|
|
58
|
-
ret = await ret
|
|
59
|
-
return ret
|
|
60
|
-
ret = func(*args)
|
|
61
|
-
if inspect.isawaitable(ret):
|
|
62
|
-
ret = await ret
|
|
63
|
-
return ret
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
async def async_next(aiter):
|
|
67
|
-
try:
|
|
68
|
-
if hasattr(aiter, '__anext__'):
|
|
69
|
-
return await aiter.__anext__()
|
|
70
|
-
else:
|
|
71
|
-
return next(aiter)
|
|
72
|
-
except StopIteration:
|
|
73
|
-
raise StopAsyncIteration from None
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
async def async_zip(*aiters):
|
|
77
|
-
aiters = [
|
|
78
|
-
ait.__aiter__() if hasattr(ait, '__aiter__') else iter(ait)
|
|
79
|
-
for ait in aiters
|
|
80
|
-
]
|
|
81
|
-
try:
|
|
82
|
-
while True:
|
|
83
|
-
# 使用 asyncio.gather 等待所有异步生成器返回下一个元素
|
|
84
|
-
result = await asyncio.gather(*(async_next(ait) for ait in aiters))
|
|
85
|
-
yield tuple(result)
|
|
86
|
-
except StopAsyncIteration:
|
|
87
|
-
# 当任一异步生成器耗尽时停止迭代
|
|
88
|
-
return
|
|
31
|
+
def task_uuid():
|
|
32
|
+
return uuid.uuid3(__process_uuid, str(next(__task_counter)))
|
|
89
33
|
|
|
90
34
|
|
|
91
35
|
def _get_depends(func: Callable):
|
|
@@ -108,17 +52,6 @@ def _get_depends(func: Callable):
|
|
|
108
52
|
return args
|
|
109
53
|
|
|
110
54
|
|
|
111
|
-
def is_valid_identifier(s: str) -> bool:
|
|
112
|
-
"""
|
|
113
|
-
Check if a string is a valid identifier.
|
|
114
|
-
"""
|
|
115
|
-
try:
|
|
116
|
-
ast.parse(f"f({s}=0)")
|
|
117
|
-
return True
|
|
118
|
-
except SyntaxError:
|
|
119
|
-
return False
|
|
120
|
-
|
|
121
|
-
|
|
122
55
|
class OptimizeSpace():
|
|
123
56
|
|
|
124
57
|
def __init__(self, optimizer: 'Optimizer', space):
|
|
@@ -224,11 +157,23 @@ class Promise():
|
|
|
224
157
|
|
|
225
158
|
class Scan():
|
|
226
159
|
|
|
160
|
+
def __new__(cls, *args, mixin=None, **kwds):
|
|
161
|
+
if mixin is None:
|
|
162
|
+
return super().__new__(cls)
|
|
163
|
+
for k in dir(mixin):
|
|
164
|
+
if not hasattr(cls, k):
|
|
165
|
+
try:
|
|
166
|
+
setattr(cls, k, getattr(mixin, k))
|
|
167
|
+
except:
|
|
168
|
+
pass
|
|
169
|
+
return super().__new__(cls)
|
|
170
|
+
|
|
227
171
|
def __init__(self,
|
|
228
172
|
app: str = 'task',
|
|
229
173
|
tags: tuple[str] = (),
|
|
230
|
-
database: str | Path | None = 'tcp://127.0.0.1:6789'
|
|
231
|
-
|
|
174
|
+
database: str | Path | None = 'tcp://127.0.0.1:6789',
|
|
175
|
+
mixin=None):
|
|
176
|
+
self.id = task_uuid()
|
|
232
177
|
self.record = None
|
|
233
178
|
self.namespace = {}
|
|
234
179
|
self.description = {
|
|
@@ -242,17 +187,18 @@ class Scan():
|
|
|
242
187
|
'dependents': {},
|
|
243
188
|
'order': {},
|
|
244
189
|
'filters': {},
|
|
245
|
-
'total': {}
|
|
246
|
-
'compiled': False,
|
|
190
|
+
'total': {}
|
|
247
191
|
}
|
|
248
192
|
self._current_level = 0
|
|
249
193
|
self.variables = {}
|
|
250
194
|
self._task = None
|
|
251
195
|
self.sock = None
|
|
252
196
|
self.database = database
|
|
253
|
-
self._variables = {}
|
|
254
197
|
self._sem = asyncio.Semaphore(100)
|
|
255
|
-
self._bar = {}
|
|
198
|
+
self._bar: dict[int, tqdm] = {}
|
|
199
|
+
self._hide_patterns = [r'^__.*', r'.*__$']
|
|
200
|
+
self._hide_pattern_re = re.compile('|'.join(self._hide_patterns))
|
|
201
|
+
self._task_queue = asyncio.Queue()
|
|
256
202
|
|
|
257
203
|
def __getstate__(self) -> dict:
|
|
258
204
|
state = self.__dict__.copy()
|
|
@@ -278,7 +224,7 @@ class Scan():
|
|
|
278
224
|
async def emit(self, current_level, step, position, variables: dict[str,
|
|
279
225
|
Any]):
|
|
280
226
|
for key, value in list(variables.items()):
|
|
281
|
-
if inspect.isawaitable(value):
|
|
227
|
+
if inspect.isawaitable(value) and not self.hiden(key):
|
|
282
228
|
variables[key] = await value
|
|
283
229
|
if self.sock is not None:
|
|
284
230
|
await self.sock.send_pyobj({
|
|
@@ -288,11 +234,21 @@ class Scan():
|
|
|
288
234
|
'level': current_level,
|
|
289
235
|
'step': step,
|
|
290
236
|
'position': position,
|
|
291
|
-
'variables':
|
|
237
|
+
'variables': {
|
|
238
|
+
k: v
|
|
239
|
+
for k, v in variables.items() if not self.hiden(k)
|
|
240
|
+
}
|
|
292
241
|
})
|
|
293
242
|
else:
|
|
294
243
|
self.record.append(current_level, step, position, variables)
|
|
295
244
|
|
|
245
|
+
def hide(self, name: str):
|
|
246
|
+
self._hide_patterns.append(re.compile(name))
|
|
247
|
+
self._hide_pattern_re = re.compile('|'.join(self._hide_patterns))
|
|
248
|
+
|
|
249
|
+
def hiden(self, name: str) -> bool:
|
|
250
|
+
return bool(self._hide_pattern_re.match(name))
|
|
251
|
+
|
|
296
252
|
async def _filter(self, variables: dict[str, Any], level: int = 0):
|
|
297
253
|
try:
|
|
298
254
|
return all([
|
|
@@ -317,6 +273,9 @@ class Scan():
|
|
|
317
273
|
except:
|
|
318
274
|
scripts = ('', [])
|
|
319
275
|
|
|
276
|
+
self.description['ctime'] = datetime.datetime.now()
|
|
277
|
+
self.description['scripts'] = scripts
|
|
278
|
+
self.description['env'] = {k: v for k, v in os.environ.items()}
|
|
320
279
|
if self.sock is not None:
|
|
321
280
|
await self.sock.send_pyobj({
|
|
322
281
|
'task':
|
|
@@ -324,12 +283,7 @@ class Scan():
|
|
|
324
283
|
'method':
|
|
325
284
|
'record_create',
|
|
326
285
|
'description':
|
|
327
|
-
dill.dumps(self.description)
|
|
328
|
-
# 'env':
|
|
329
|
-
# dill.dumps(__main__.__dict__),
|
|
330
|
-
'scripts':
|
|
331
|
-
scripts,
|
|
332
|
-
'tags': []
|
|
286
|
+
dill.dumps(self.description)
|
|
333
287
|
})
|
|
334
288
|
|
|
335
289
|
record_id = await self.sock.recv_pyobj()
|
|
@@ -356,7 +310,7 @@ class Scan():
|
|
|
356
310
|
self.description['dependents'][name] = set()
|
|
357
311
|
self.description['dependents'][name].update(depends)
|
|
358
312
|
|
|
359
|
-
def add_filter(self, func, level):
|
|
313
|
+
def add_filter(self, func: Callable, level: int):
|
|
360
314
|
"""
|
|
361
315
|
Add a filter function to the scan.
|
|
362
316
|
|
|
@@ -427,8 +381,17 @@ class Scan():
|
|
|
427
381
|
self.description['optimizers'][name] = opt
|
|
428
382
|
return opt
|
|
429
383
|
|
|
384
|
+
async def _update_progress(self):
|
|
385
|
+
while True:
|
|
386
|
+
task = await self._task_queue.get()
|
|
387
|
+
if isinstance(task, asyncio.Event):
|
|
388
|
+
task.set()
|
|
389
|
+
elif inspect.isawaitable(task):
|
|
390
|
+
await task
|
|
391
|
+
|
|
430
392
|
async def _run(self):
|
|
431
|
-
self.
|
|
393
|
+
assymbly(self.description)
|
|
394
|
+
task = asyncio.create_task(self._update_progress())
|
|
432
395
|
self.variables = self.description['consts'].copy()
|
|
433
396
|
for level, total in self.description['total'].items():
|
|
434
397
|
if total == np.inf:
|
|
@@ -451,6 +414,7 @@ class Scan():
|
|
|
451
414
|
await self.work()
|
|
452
415
|
for level, bar in self._bar.items():
|
|
453
416
|
bar.close()
|
|
417
|
+
task.cancel()
|
|
454
418
|
return self.variables
|
|
455
419
|
|
|
456
420
|
async def done(self):
|
|
@@ -468,171 +432,13 @@ class Scan():
|
|
|
468
432
|
if self._task is not None:
|
|
469
433
|
self._task.cancel()
|
|
470
434
|
|
|
471
|
-
def
|
|
472
|
-
if self.
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
mapping = {
|
|
476
|
-
label: level
|
|
477
|
-
for level, label in enumerate(
|
|
478
|
-
sorted(
|
|
479
|
-
set(self.description['loops'].keys())
|
|
480
|
-
| set(self.description['actions'].keys()) - {-1}))
|
|
481
|
-
}
|
|
435
|
+
async def _reset_progress_bar(self, level):
|
|
436
|
+
if level in self._bar:
|
|
437
|
+
self._bar[level].reset()
|
|
482
438
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
self.description['loops'] = dict(
|
|
487
|
-
sorted([(mapping[k], v)
|
|
488
|
-
for k, v in self.description['loops'].items()]))
|
|
489
|
-
self.description['actions'] = {
|
|
490
|
-
mapping[k]: v
|
|
491
|
-
for k, v in self.description['actions'].items()
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
for level, loops in self.description['loops'].items():
|
|
495
|
-
self.description['total'][level] = np.inf
|
|
496
|
-
for name, space in loops:
|
|
497
|
-
try:
|
|
498
|
-
self.description['total'][level] = min(
|
|
499
|
-
self.description['total'][level], len(space))
|
|
500
|
-
except:
|
|
501
|
-
pass
|
|
502
|
-
|
|
503
|
-
dependents = self.description['dependents'].copy()
|
|
504
|
-
|
|
505
|
-
for level in range(len(mapping)):
|
|
506
|
-
range_list = self.description['loops'].get(level, [])
|
|
507
|
-
if level > 0:
|
|
508
|
-
if f'#__loop_{level}' not in self.description['dependents']:
|
|
509
|
-
dependents[f'#__loop_{level}'] = []
|
|
510
|
-
dependents[f'#__loop_{level}'].append(f'#__loop_{level-1}')
|
|
511
|
-
for name, _ in range_list:
|
|
512
|
-
if name not in self.description['dependents']:
|
|
513
|
-
dependents[name] = []
|
|
514
|
-
dependents[name].append(f'#__loop_{level}')
|
|
515
|
-
|
|
516
|
-
def _get_all_depends(key, graph):
|
|
517
|
-
ret = set()
|
|
518
|
-
if key not in graph:
|
|
519
|
-
return ret
|
|
520
|
-
|
|
521
|
-
for e in graph[key]:
|
|
522
|
-
ret.update(_get_all_depends(e, graph))
|
|
523
|
-
ret.update(graph[key])
|
|
524
|
-
return ret
|
|
525
|
-
|
|
526
|
-
full_depends = {}
|
|
527
|
-
for key in dependents:
|
|
528
|
-
full_depends[key] = _get_all_depends(key, dependents)
|
|
529
|
-
|
|
530
|
-
levels = {}
|
|
531
|
-
passed = set()
|
|
532
|
-
all_keys = set()
|
|
533
|
-
for level in reversed(self.description['loops'].keys()):
|
|
534
|
-
tag = f'#__loop_{level}'
|
|
535
|
-
for key, deps in full_depends.items():
|
|
536
|
-
all_keys.update(deps)
|
|
537
|
-
all_keys.add(key)
|
|
538
|
-
if key.startswith('#__loop_'):
|
|
539
|
-
continue
|
|
540
|
-
if tag in deps:
|
|
541
|
-
if level not in levels:
|
|
542
|
-
levels[level] = set()
|
|
543
|
-
if key not in passed:
|
|
544
|
-
passed.add(key)
|
|
545
|
-
levels[level].add(key)
|
|
546
|
-
levels[-1] = {
|
|
547
|
-
key
|
|
548
|
-
for key in all_keys - passed if not key.startswith('#__loop_')
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
order = []
|
|
552
|
-
ts = TopologicalSorter(dependents)
|
|
553
|
-
ts.prepare()
|
|
554
|
-
while ts.is_active():
|
|
555
|
-
ready = ts.get_ready()
|
|
556
|
-
order.append(ready)
|
|
557
|
-
for k in ready:
|
|
558
|
-
ts.done(k)
|
|
559
|
-
|
|
560
|
-
self.description['order'] = {}
|
|
561
|
-
|
|
562
|
-
for level in sorted(levels):
|
|
563
|
-
keys = set(levels[level])
|
|
564
|
-
self.description['order'][level] = []
|
|
565
|
-
for ready in order:
|
|
566
|
-
ready = list(keys & set(ready))
|
|
567
|
-
if ready:
|
|
568
|
-
self.description['order'][level].append(ready)
|
|
569
|
-
keys -= set(ready)
|
|
570
|
-
|
|
571
|
-
self.description['compiled'] = True
|
|
572
|
-
|
|
573
|
-
async def _iter_level(self, level, variables):
|
|
574
|
-
iters = {}
|
|
575
|
-
env = Env()
|
|
576
|
-
env.variables = variables
|
|
577
|
-
opts = {}
|
|
578
|
-
|
|
579
|
-
for name, iter in self.description['loops'][level]:
|
|
580
|
-
if isinstance(iter, OptimizeSpace):
|
|
581
|
-
if iter.optimizer.name not in opts:
|
|
582
|
-
opts[iter.optimizer.name] = iter.optimizer.create()
|
|
583
|
-
elif isinstance(iter, Expression):
|
|
584
|
-
iters[name] = iter.eval(env)
|
|
585
|
-
elif callable(iter):
|
|
586
|
-
iters[name] = await call_function(iter, variables)
|
|
587
|
-
else:
|
|
588
|
-
iters[name] = iter
|
|
589
|
-
|
|
590
|
-
maxiter = 0xffffffff
|
|
591
|
-
for name, opt in opts.items():
|
|
592
|
-
opt_cfg = self.description['optimizers'][name]
|
|
593
|
-
maxiter = min(maxiter, opt_cfg.maxiter)
|
|
594
|
-
|
|
595
|
-
async for args in async_zip(*iters.values(), range(maxiter)):
|
|
596
|
-
variables.update(dict(zip(iters.keys(), args[:-1])))
|
|
597
|
-
for name, opt in opts.items():
|
|
598
|
-
args = opt.ask()
|
|
599
|
-
opt_cfg = self.description['optimizers'][name]
|
|
600
|
-
variables.update({
|
|
601
|
-
n: v
|
|
602
|
-
for n, v in zip(opt_cfg.dimensions.keys(), args)
|
|
603
|
-
})
|
|
604
|
-
|
|
605
|
-
for group in self.description['order'].get(level, []):
|
|
606
|
-
for name in group:
|
|
607
|
-
if name in self.description['functions']:
|
|
608
|
-
variables[name] = await call_function(
|
|
609
|
-
self.description['functions'][name], variables)
|
|
610
|
-
|
|
611
|
-
yield variables
|
|
612
|
-
|
|
613
|
-
for name, opt in opts.items():
|
|
614
|
-
opt_cfg = self.description['optimizers'][name]
|
|
615
|
-
args = [variables[n] for n in opt_cfg.dimensions.keys()]
|
|
616
|
-
if name not in variables:
|
|
617
|
-
raise ValueError(f'{name} not in variables.')
|
|
618
|
-
fun = variables[name]
|
|
619
|
-
if inspect.isawaitable(fun):
|
|
620
|
-
fun = await fun
|
|
621
|
-
if opt_cfg.minimize:
|
|
622
|
-
opt.tell(args, fun)
|
|
623
|
-
else:
|
|
624
|
-
opt.tell(args, -fun)
|
|
625
|
-
|
|
626
|
-
for name, opt in opts.items():
|
|
627
|
-
opt_cfg = self.description['optimizers'][name]
|
|
628
|
-
result = opt.get_result()
|
|
629
|
-
variables.update({
|
|
630
|
-
n: v
|
|
631
|
-
for n, v in zip(opt_cfg.dimensions.keys(), result.x)
|
|
632
|
-
})
|
|
633
|
-
variables[name] = result.fun
|
|
634
|
-
if opts:
|
|
635
|
-
yield variables
|
|
439
|
+
async def _update_progress_bar(self, level, n: int):
|
|
440
|
+
if level in self._bar:
|
|
441
|
+
self._bar[level].update(n)
|
|
636
442
|
|
|
637
443
|
async def iter(self, **kwds):
|
|
638
444
|
if self.current_level >= len(self.description['loops']):
|
|
@@ -640,10 +446,13 @@ class Scan():
|
|
|
640
446
|
step = 0
|
|
641
447
|
position = 0
|
|
642
448
|
task = None
|
|
643
|
-
|
|
644
|
-
self.
|
|
645
|
-
async for variables in
|
|
646
|
-
|
|
449
|
+
self._task_queue.put_nowait(
|
|
450
|
+
self._reset_progress_bar(self.current_level))
|
|
451
|
+
async for variables in _iter_level(
|
|
452
|
+
self.variables,
|
|
453
|
+
self.description['loops'].get(self.current_level, []),
|
|
454
|
+
self.description['order'].get(self.current_level, []),
|
|
455
|
+
self.description['functions'], self.description['optimizers']):
|
|
647
456
|
self._current_level += 1
|
|
648
457
|
if await self._filter(variables, self.current_level - 1):
|
|
649
458
|
yield variables
|
|
@@ -653,12 +462,19 @@ class Scan():
|
|
|
653
462
|
step += 1
|
|
654
463
|
position += 1
|
|
655
464
|
self._current_level -= 1
|
|
656
|
-
|
|
657
|
-
self.
|
|
465
|
+
self._task_queue.put_nowait(
|
|
466
|
+
self._update_progress_bar(self.current_level, 1))
|
|
658
467
|
if task is not None:
|
|
659
468
|
await task
|
|
660
469
|
if self.current_level == 0:
|
|
661
470
|
await self.emit(self.current_level - 1, 0, 0, {})
|
|
471
|
+
for name, value in self.variables.items():
|
|
472
|
+
if inspect.isawaitable(value):
|
|
473
|
+
self.variables[name] = await value
|
|
474
|
+
while not self._task_queue.empty():
|
|
475
|
+
task = self._task_queue.get_nowait()
|
|
476
|
+
if inspect.isawaitable(task):
|
|
477
|
+
await task
|
|
662
478
|
|
|
663
479
|
async def work(self, **kwds):
|
|
664
480
|
if self.current_level in self.description['actions']:
|
|
@@ -683,7 +499,7 @@ class Scan():
|
|
|
683
499
|
"""
|
|
684
500
|
self.description['actions'][level] = action
|
|
685
501
|
|
|
686
|
-
async def promise(self, awaitable):
|
|
502
|
+
async def promise(self, awaitable: Awaitable) -> Promise:
|
|
687
503
|
"""
|
|
688
504
|
Promise to calculate asynchronous function and return the result in future.
|
|
689
505
|
|
|
@@ -694,8 +510,184 @@ class Scan():
|
|
|
694
510
|
Promise: A promise object.
|
|
695
511
|
"""
|
|
696
512
|
async with self._sem:
|
|
697
|
-
|
|
513
|
+
task = asyncio.create_task(self._await(awaitable))
|
|
514
|
+
self._task_queue.put_nowait(task)
|
|
515
|
+
return Promise(task)
|
|
698
516
|
|
|
699
|
-
async def _await(self, awaitable):
|
|
517
|
+
async def _await(self, awaitable: Awaitable):
|
|
700
518
|
async with self._sem:
|
|
701
519
|
return await awaitable
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def assymbly(description):
|
|
523
|
+
mapping = {
|
|
524
|
+
label: level
|
|
525
|
+
for level, label in enumerate(
|
|
526
|
+
sorted(
|
|
527
|
+
set(description['loops'].keys())
|
|
528
|
+
| {k
|
|
529
|
+
for k in description['actions'].keys() if k >= 0}))
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if -1 in description['actions']:
|
|
533
|
+
mapping[-1] = max(mapping.values()) + 1
|
|
534
|
+
|
|
535
|
+
levels = sorted(mapping.values())
|
|
536
|
+
for k in description['actions'].keys():
|
|
537
|
+
if k < -1:
|
|
538
|
+
mapping[k] = levels[k]
|
|
539
|
+
|
|
540
|
+
description['loops'] = dict(
|
|
541
|
+
sorted([(mapping[k], v) for k, v in description['loops'].items()]))
|
|
542
|
+
description['actions'] = {
|
|
543
|
+
mapping[k]: v
|
|
544
|
+
for k, v in description['actions'].items()
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
for level, loops in description['loops'].items():
|
|
548
|
+
description['total'][level] = np.inf
|
|
549
|
+
for name, space in loops:
|
|
550
|
+
try:
|
|
551
|
+
description['total'][level] = min(description['total'][level],
|
|
552
|
+
len(space))
|
|
553
|
+
except:
|
|
554
|
+
pass
|
|
555
|
+
|
|
556
|
+
dependents = description['dependents'].copy()
|
|
557
|
+
|
|
558
|
+
for level in levels:
|
|
559
|
+
range_list = description['loops'].get(level, [])
|
|
560
|
+
if level > 0:
|
|
561
|
+
if f'#__loop_{level}' not in description['dependents']:
|
|
562
|
+
dependents[f'#__loop_{level}'] = []
|
|
563
|
+
dependents[f'#__loop_{level}'].append(f'#__loop_{level-1}')
|
|
564
|
+
for name, _ in range_list:
|
|
565
|
+
if name not in description['dependents']:
|
|
566
|
+
dependents[name] = []
|
|
567
|
+
dependents[name].append(f'#__loop_{level}')
|
|
568
|
+
|
|
569
|
+
def _get_all_depends(key, graph):
|
|
570
|
+
ret = set()
|
|
571
|
+
if key not in graph:
|
|
572
|
+
return ret
|
|
573
|
+
|
|
574
|
+
for e in graph[key]:
|
|
575
|
+
ret.update(_get_all_depends(e, graph))
|
|
576
|
+
ret.update(graph[key])
|
|
577
|
+
return ret
|
|
578
|
+
|
|
579
|
+
full_depends = {}
|
|
580
|
+
for key in dependents:
|
|
581
|
+
full_depends[key] = _get_all_depends(key, dependents)
|
|
582
|
+
|
|
583
|
+
levels = {}
|
|
584
|
+
passed = set()
|
|
585
|
+
all_keys = set()
|
|
586
|
+
for level in reversed(description['loops'].keys()):
|
|
587
|
+
tag = f'#__loop_{level}'
|
|
588
|
+
for key, deps in full_depends.items():
|
|
589
|
+
all_keys.update(deps)
|
|
590
|
+
all_keys.add(key)
|
|
591
|
+
if key.startswith('#__loop_'):
|
|
592
|
+
continue
|
|
593
|
+
if tag in deps:
|
|
594
|
+
if level not in levels:
|
|
595
|
+
levels[level] = set()
|
|
596
|
+
if key not in passed:
|
|
597
|
+
passed.add(key)
|
|
598
|
+
levels[level].add(key)
|
|
599
|
+
levels[-1] = {
|
|
600
|
+
key
|
|
601
|
+
for key in all_keys - passed if not key.startswith('#__loop_')
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
order = []
|
|
605
|
+
ts = TopologicalSorter(dependents)
|
|
606
|
+
ts.prepare()
|
|
607
|
+
while ts.is_active():
|
|
608
|
+
ready = ts.get_ready()
|
|
609
|
+
order.append(ready)
|
|
610
|
+
for k in ready:
|
|
611
|
+
ts.done(k)
|
|
612
|
+
|
|
613
|
+
description['order'] = {}
|
|
614
|
+
|
|
615
|
+
for level in sorted(levels):
|
|
616
|
+
keys = set(levels[level])
|
|
617
|
+
description['order'][level] = []
|
|
618
|
+
for ready in order:
|
|
619
|
+
ready = list(keys & set(ready))
|
|
620
|
+
if ready:
|
|
621
|
+
description['order'][level].append(ready)
|
|
622
|
+
keys -= set(ready)
|
|
623
|
+
return description
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
async def _iter_level(variables,
|
|
627
|
+
iters: list[tuple[str, Iterable | Expression | Callable
|
|
628
|
+
| OptimizeSpace]],
|
|
629
|
+
order: list[list[str]],
|
|
630
|
+
functions: dict[str, Callable | Expression],
|
|
631
|
+
optimizers: dict[str, Optimizer]):
|
|
632
|
+
iters_d = {}
|
|
633
|
+
env = Env()
|
|
634
|
+
env.variables = variables
|
|
635
|
+
opts = {}
|
|
636
|
+
|
|
637
|
+
for name, iter in iters:
|
|
638
|
+
if isinstance(iter, OptimizeSpace):
|
|
639
|
+
if iter.optimizer.name not in opts:
|
|
640
|
+
opts[iter.optimizer.name] = iter.optimizer.create()
|
|
641
|
+
elif isinstance(iter, Expression):
|
|
642
|
+
iters_d[name] = iter.eval(env)
|
|
643
|
+
elif callable(iter):
|
|
644
|
+
iters_d[name] = await call_function(iter, variables)
|
|
645
|
+
else:
|
|
646
|
+
iters_d[name] = iter
|
|
647
|
+
|
|
648
|
+
maxiter = 0xffffffff
|
|
649
|
+
for name, opt in opts.items():
|
|
650
|
+
opt_cfg = optimizers[name]
|
|
651
|
+
maxiter = min(maxiter, opt_cfg.maxiter)
|
|
652
|
+
|
|
653
|
+
async for args in async_zip(*iters_d.values(), range(maxiter)):
|
|
654
|
+
variables.update(dict(zip(iters_d.keys(), args[:-1])))
|
|
655
|
+
for name, opt in opts.items():
|
|
656
|
+
args = opt.ask()
|
|
657
|
+
opt_cfg = optimizers[name]
|
|
658
|
+
variables.update({
|
|
659
|
+
n: v
|
|
660
|
+
for n, v in zip(opt_cfg.dimensions.keys(), args)
|
|
661
|
+
})
|
|
662
|
+
|
|
663
|
+
for group in order:
|
|
664
|
+
for name in group:
|
|
665
|
+
if name in functions:
|
|
666
|
+
variables[name] = await call_function(
|
|
667
|
+
functions[name], variables)
|
|
668
|
+
|
|
669
|
+
yield variables
|
|
670
|
+
|
|
671
|
+
for name, opt in opts.items():
|
|
672
|
+
opt_cfg = optimizers[name]
|
|
673
|
+
args = [variables[n] for n in opt_cfg.dimensions.keys()]
|
|
674
|
+
if name not in variables:
|
|
675
|
+
raise ValueError(f'{name} not in variables.')
|
|
676
|
+
fun = variables[name]
|
|
677
|
+
if inspect.isawaitable(fun):
|
|
678
|
+
fun = await fun
|
|
679
|
+
if opt_cfg.minimize:
|
|
680
|
+
opt.tell(args, fun)
|
|
681
|
+
else:
|
|
682
|
+
opt.tell(args, -fun)
|
|
683
|
+
|
|
684
|
+
for name, opt in opts.items():
|
|
685
|
+
opt_cfg = optimizers[name]
|
|
686
|
+
result = opt.get_result()
|
|
687
|
+
variables.update({
|
|
688
|
+
n: v
|
|
689
|
+
for n, v in zip(opt_cfg.dimensions.keys(), result.x)
|
|
690
|
+
})
|
|
691
|
+
variables[name] = result.fun
|
|
692
|
+
if opts:
|
|
693
|
+
yield variables
|
qulab/scan/utils.py
CHANGED
|
@@ -1,37 +1,83 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import asyncio
|
|
1
3
|
import inspect
|
|
2
|
-
from
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
4
|
+
from typing import Any, Callable
|
|
5
|
+
|
|
6
|
+
from .expression import Env, Expression
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def is_valid_identifier(s: str) -> bool:
|
|
10
|
+
"""
|
|
11
|
+
Check if a string is a valid identifier.
|
|
12
|
+
"""
|
|
13
|
+
try:
|
|
14
|
+
ast.parse(f"f({s}=0)")
|
|
15
|
+
return True
|
|
16
|
+
except SyntaxError:
|
|
17
|
+
return False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def async_next(aiter):
|
|
21
|
+
try:
|
|
22
|
+
if hasattr(aiter, '__anext__'):
|
|
23
|
+
return await aiter.__anext__()
|
|
24
|
+
else:
|
|
25
|
+
return next(aiter)
|
|
26
|
+
except StopIteration:
|
|
27
|
+
raise StopAsyncIteration from None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def async_zip(*aiters):
|
|
31
|
+
aiters = [
|
|
32
|
+
ait.__aiter__() if hasattr(ait, '__aiter__') else iter(ait)
|
|
33
|
+
for ait in aiters
|
|
34
|
+
]
|
|
35
|
+
try:
|
|
36
|
+
while True:
|
|
37
|
+
# 使用 asyncio.gather 等待所有异步生成器返回下一个元素
|
|
38
|
+
result = await asyncio.gather(*(async_next(ait) for ait in aiters))
|
|
39
|
+
yield tuple(result)
|
|
40
|
+
except StopAsyncIteration:
|
|
41
|
+
# 当任一异步生成器耗尽时停止迭代
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def call_function(func: Callable | Expression, variables: dict[str,
|
|
46
|
+
Any]):
|
|
47
|
+
if isinstance(func, Expression):
|
|
48
|
+
env = Env()
|
|
49
|
+
for name in func.symbols():
|
|
50
|
+
if name in variables:
|
|
51
|
+
if inspect.isawaitable(variables[name]):
|
|
52
|
+
variables[name] = await variables[name]
|
|
53
|
+
env.variables[name] = variables[name]
|
|
54
|
+
else:
|
|
55
|
+
raise ValueError(f'{name} is not provided.')
|
|
56
|
+
return func.eval(env)
|
|
57
|
+
|
|
16
58
|
try:
|
|
17
|
-
|
|
18
|
-
arg.result() if isinstance(arg, Future) else arg for arg in args
|
|
19
|
-
]
|
|
20
|
-
kw = {
|
|
21
|
-
k: v.result() if isinstance(v, Future) else v
|
|
22
|
-
for k, v in kw.items()
|
|
23
|
-
}
|
|
24
|
-
return func(*args, **kw)
|
|
59
|
+
sig = inspect.signature(func)
|
|
25
60
|
except:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
61
|
+
return func()
|
|
62
|
+
args = []
|
|
63
|
+
for name, param in sig.parameters.items():
|
|
64
|
+
if param.kind == param.POSITIONAL_OR_KEYWORD:
|
|
65
|
+
if name in variables:
|
|
66
|
+
if inspect.isawaitable(variables[name]):
|
|
67
|
+
variables[name] = await variables[name]
|
|
68
|
+
args.append(variables[name])
|
|
69
|
+
elif param.default is not param.empty:
|
|
70
|
+
args.append(param.default)
|
|
71
|
+
else:
|
|
72
|
+
raise ValueError(f'parameter {name} is not provided.')
|
|
73
|
+
elif param.kind == param.VAR_POSITIONAL:
|
|
74
|
+
raise ValueError('not support VAR_POSITIONAL')
|
|
75
|
+
elif param.kind == param.VAR_KEYWORD:
|
|
76
|
+
ret = func(**variables)
|
|
77
|
+
if inspect.isawaitable(ret):
|
|
78
|
+
ret = await ret
|
|
79
|
+
return ret
|
|
80
|
+
ret = func(*args)
|
|
81
|
+
if inspect.isawaitable(ret):
|
|
82
|
+
ret = await ret
|
|
83
|
+
return ret
|
qulab/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2.0.
|
|
1
|
+
__version__ = "2.0.3"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|