QuLab 2.0.3__cp311-cp311-win_amd64.whl → 2.0.5__cp311-cp311-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.3.dist-info → QuLab-2.0.5.dist-info}/METADATA +1 -1
- {QuLab-2.0.3.dist-info → QuLab-2.0.5.dist-info}/RECORD +13 -12
- qulab/__main__.py +2 -0
- qulab/fun.cp311-win_amd64.pyd +0 -0
- qulab/scan/query_record.py +0 -1
- qulab/scan/recorder.py +119 -48
- qulab/scan/scan.py +172 -54
- qulab/scan/server.py +106 -0
- qulab/version.py +1 -1
- {QuLab-2.0.3.dist-info → QuLab-2.0.5.dist-info}/LICENSE +0 -0
- {QuLab-2.0.3.dist-info → QuLab-2.0.5.dist-info}/WHEEL +0 -0
- {QuLab-2.0.3.dist-info → QuLab-2.0.5.dist-info}/entry_points.txt +0 -0
- {QuLab-2.0.3.dist-info → QuLab-2.0.5.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
qulab/__init__.py,sha256=8zLGg-DfQhnDl2Ky0n-zXpN-8e-g7iR0AcaI4l4Vvpk,32
|
|
2
|
-
qulab/__main__.py,sha256=
|
|
3
|
-
qulab/fun.cp311-win_amd64.pyd,sha256=
|
|
4
|
-
qulab/version.py,sha256=
|
|
2
|
+
qulab/__main__.py,sha256=XN2wrhlmEkTIPq_ZeSaO8rWXfYgD2Czkm9DVFVoCw_U,515
|
|
3
|
+
qulab/fun.cp311-win_amd64.pyd,sha256=ay6Srz8qiZP0Hsxatsz1d-2YK0FTjJ0Nsyp9TEiduMA,31232
|
|
4
|
+
qulab/version.py,sha256=W2bKmLHMwuaZj0IuSoqothHJumPaUDyIwYJTzE6Hdd0,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
|
|
@@ -17,9 +17,10 @@ qulab/scan/curd.py,sha256=bEXtcmiaoAv5APToXx5O5tvmqAhE2_LkvdwLsI_8D5M,4662
|
|
|
17
17
|
qulab/scan/expression.py,sha256=vwUM9E0OFQal4bljlUtLR3NJu4zGRyuWYrdyZSs3QTU,16199
|
|
18
18
|
qulab/scan/models.py,sha256=TkiVHF_fUZzYHs4MsCTRh391thpf4Ozd3R_LAU0Gxkg,17657
|
|
19
19
|
qulab/scan/optimize.py,sha256=MlT4y422CnP961IR384UKryyZh8riNvrPSd2z_MXLEg,2356
|
|
20
|
-
qulab/scan/query_record.py,sha256=
|
|
21
|
-
qulab/scan/recorder.py,sha256=
|
|
22
|
-
qulab/scan/scan.py,sha256=
|
|
20
|
+
qulab/scan/query_record.py,sha256=rpw4U3NjLzlv9QMwKdCvEUGHjzPF8u1UpodfLW8aoTY,11853
|
|
21
|
+
qulab/scan/recorder.py,sha256=wv8o_teAYYM_RaRQHkfa4-cF-ak68tzcb_QH9jlTH7A,18456
|
|
22
|
+
qulab/scan/scan.py,sha256=nvvkGWmKWueeJ1pRAax3yKZn-vqlMvt10_oPSWd2hJw,26742
|
|
23
|
+
qulab/scan/server.py,sha256=zDZfG6bOB3EUubfByQMq0BSQ9C6IV_Av0tDinzgpGjQ,2950
|
|
23
24
|
qulab/scan/utils.py,sha256=XM-eKL5Xkm0hihhGS7Kq4g654Ye7n7TcU_f95gxtXq8,2634
|
|
24
25
|
qulab/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
26
|
qulab/storage/__main__.py,sha256=6-EjN0waX1yfcMPJXqpIr9UlrIEsSCFApm5G-ZeaPMQ,1742
|
|
@@ -76,9 +77,9 @@ qulab/visualization/plot_layout.py,sha256=yAnMONOms7_szCdng-8wPpUMPis5UnbaNNzV4K
|
|
|
76
77
|
qulab/visualization/plot_seq.py,sha256=h9D0Yl_yO64IwlvBgzMu9EBKr9gg6y8QE55gu2PfTns,2783
|
|
77
78
|
qulab/visualization/qdat.py,sha256=HubXFu4nfcA7iUzghJGle1C86G6221hicLR0b-GqhKQ,5887
|
|
78
79
|
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.
|
|
80
|
+
QuLab-2.0.5.dist-info/LICENSE,sha256=b4NRQ-GFVpJMT7RuExW3NwhfbrYsX7AcdB7Gudok-fs,1086
|
|
81
|
+
QuLab-2.0.5.dist-info/METADATA,sha256=siWaIXTJ0sU7pu5cBNaFGe2ecL8-tlbywjkxHYzGF-0,3609
|
|
82
|
+
QuLab-2.0.5.dist-info/WHEEL,sha256=nSybvzWlmdJnHiUQSY-d7V1ycwEVUTqXiTvr2eshg44,102
|
|
83
|
+
QuLab-2.0.5.dist-info/entry_points.txt,sha256=ohBzutEnQimP_BZWiuXdSliu4QAYSHHcN0PZD8c7ZCY,46
|
|
84
|
+
QuLab-2.0.5.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
|
|
85
|
+
QuLab-2.0.5.dist-info/RECORD,,
|
qulab/__main__.py
CHANGED
|
@@ -2,6 +2,7 @@ import click
|
|
|
2
2
|
|
|
3
3
|
from .monitor.__main__ import main as monitor
|
|
4
4
|
from .scan.recorder import record
|
|
5
|
+
from .scan.server import server
|
|
5
6
|
from .sys.net.cli import dht
|
|
6
7
|
from .visualization.__main__ import plot
|
|
7
8
|
|
|
@@ -21,6 +22,7 @@ main.add_command(monitor)
|
|
|
21
22
|
main.add_command(plot)
|
|
22
23
|
main.add_command(dht)
|
|
23
24
|
main.add_command(record)
|
|
25
|
+
main.add_command(server)
|
|
24
26
|
|
|
25
27
|
if __name__ == '__main__':
|
|
26
28
|
main()
|
qulab/fun.cp311-win_amd64.pyd
CHANGED
|
Binary file
|
qulab/scan/query_record.py
CHANGED
qulab/scan/recorder.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import os
|
|
2
3
|
import pickle
|
|
3
4
|
import sys
|
|
4
5
|
import time
|
|
5
6
|
import uuid
|
|
7
|
+
from collections import defaultdict
|
|
6
8
|
from pathlib import Path
|
|
9
|
+
from threading import Lock
|
|
7
10
|
|
|
8
11
|
import click
|
|
9
12
|
import dill
|
|
@@ -18,7 +21,16 @@ from .models import Record as RecordInDB
|
|
|
18
21
|
from .models import Session, create_engine, create_tables, sessionmaker, utcnow
|
|
19
22
|
|
|
20
23
|
_notgiven = object()
|
|
21
|
-
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
default_record_port = int(os.getenv('QULAB_RECORD_PORT', 6789))
|
|
27
|
+
except:
|
|
28
|
+
default_record_port = 6789
|
|
29
|
+
|
|
30
|
+
if os.getenv('QULAB_RECORD_PATH'):
|
|
31
|
+
datapath = Path(os.getenv('QULAB_RECORD_PATH'))
|
|
32
|
+
else:
|
|
33
|
+
datapath = Path.home() / 'qulab' / 'data'
|
|
22
34
|
datapath.mkdir(parents=True, exist_ok=True)
|
|
23
35
|
|
|
24
36
|
record_cache = {}
|
|
@@ -41,24 +53,49 @@ class BufferList():
|
|
|
41
53
|
self.rd = ()
|
|
42
54
|
self.pos_file = pos_file
|
|
43
55
|
self.value_file = value_file
|
|
56
|
+
self._lock = Lock()
|
|
57
|
+
|
|
58
|
+
def __getstate__(self):
|
|
59
|
+
return {
|
|
60
|
+
'pos_file': self.pos_file,
|
|
61
|
+
'value_file': self.value_file,
|
|
62
|
+
'_pos': self._pos,
|
|
63
|
+
'_value': self._value,
|
|
64
|
+
'lu': self.lu,
|
|
65
|
+
'rd': self.rd
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
def __setstate__(self, state):
|
|
69
|
+
self.pos_file = state['pos_file']
|
|
70
|
+
self.value_file = state['value_file']
|
|
71
|
+
self._pos = state['_pos']
|
|
72
|
+
self._value = state['_value']
|
|
73
|
+
self.lu = state['lu']
|
|
74
|
+
self.rd = state['rd']
|
|
75
|
+
self._lock = Lock()
|
|
44
76
|
|
|
45
77
|
@property
|
|
46
78
|
def shape(self):
|
|
47
79
|
return tuple([i - j for i, j in zip(self.rd, self.lu)])
|
|
48
80
|
|
|
49
81
|
def flush(self):
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
82
|
+
with self._lock:
|
|
83
|
+
if self.pos_file is not None:
|
|
84
|
+
with open(self.pos_file, 'ab') as f:
|
|
85
|
+
for pos in self._pos:
|
|
86
|
+
dill.dump(pos, f)
|
|
87
|
+
self._pos.clear()
|
|
88
|
+
if self.value_file is not None:
|
|
89
|
+
with open(self.value_file, 'ab') as f:
|
|
90
|
+
for value in self._value:
|
|
91
|
+
dill.dump(value, f)
|
|
92
|
+
self._value.clear()
|
|
93
|
+
|
|
94
|
+
def append(self, pos, value, dims=None):
|
|
95
|
+
if dims is not None:
|
|
96
|
+
if any([p != 0 for i, p in enumerate(pos) if i not in dims]):
|
|
97
|
+
return
|
|
98
|
+
pos = tuple([pos[i] for i in dims])
|
|
62
99
|
self.lu = tuple([min(i, j) for i, j in zip(pos, self.lu)])
|
|
63
100
|
self.rd = tuple([max(i + 1, j) for i, j in zip(pos, self.rd)])
|
|
64
101
|
self._pos.append(pos)
|
|
@@ -68,25 +105,27 @@ class BufferList():
|
|
|
68
105
|
|
|
69
106
|
def value(self):
|
|
70
107
|
v = []
|
|
71
|
-
if self.value_file is not None:
|
|
72
|
-
with
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
108
|
+
if self.value_file is not None and self.value_file.exists():
|
|
109
|
+
with self._lock:
|
|
110
|
+
with open(self.value_file, 'rb') as f:
|
|
111
|
+
while True:
|
|
112
|
+
try:
|
|
113
|
+
v.append(dill.load(f))
|
|
114
|
+
except EOFError:
|
|
115
|
+
break
|
|
78
116
|
v.extend(self._value)
|
|
79
117
|
return v
|
|
80
118
|
|
|
81
119
|
def pos(self):
|
|
82
120
|
p = []
|
|
83
|
-
if self.pos_file is not None:
|
|
84
|
-
with
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
121
|
+
if self.pos_file is not None and self.pos_file.exists():
|
|
122
|
+
with self._lock:
|
|
123
|
+
with open(self.pos_file, 'rb') as f:
|
|
124
|
+
while True:
|
|
125
|
+
try:
|
|
126
|
+
p.append(dill.load(f))
|
|
127
|
+
except EOFError:
|
|
128
|
+
break
|
|
90
129
|
p.extend(self._pos)
|
|
91
130
|
return p
|
|
92
131
|
|
|
@@ -114,21 +153,32 @@ class Record():
|
|
|
114
153
|
self._file = None
|
|
115
154
|
self.independent_variables = {}
|
|
116
155
|
self.constants = {}
|
|
117
|
-
|
|
118
|
-
for level, group in self.description['order'].items():
|
|
119
|
-
for names in group:
|
|
120
|
-
for name in names:
|
|
121
|
-
self._levels[name] = level
|
|
156
|
+
self.dims = {}
|
|
122
157
|
|
|
123
158
|
for name, value in self.description['consts'].items():
|
|
124
159
|
if name not in self._items:
|
|
125
160
|
self._items[name] = value
|
|
126
161
|
self.constants[name] = value
|
|
162
|
+
self.dims[name] = ()
|
|
127
163
|
for level, range_list in self.description['loops'].items():
|
|
128
164
|
for name, iterable in range_list:
|
|
129
165
|
if isinstance(iterable, (np.ndarray, list, tuple, range)):
|
|
130
166
|
self._items[name] = iterable
|
|
131
|
-
self.independent_variables[name] =
|
|
167
|
+
self.independent_variables[name] = iterable
|
|
168
|
+
self.dims[name] = (level, )
|
|
169
|
+
|
|
170
|
+
for level, group in self.description['order'].items():
|
|
171
|
+
for names in group:
|
|
172
|
+
for name in names:
|
|
173
|
+
self._levels[name] = level
|
|
174
|
+
if name not in self.dims:
|
|
175
|
+
if name not in self.description['dependents']:
|
|
176
|
+
self.dims[name] = (level, )
|
|
177
|
+
else:
|
|
178
|
+
d = set()
|
|
179
|
+
for n in self.description['dependents'][name]:
|
|
180
|
+
d.update(self.dims[n])
|
|
181
|
+
self.dims[name] = tuple(sorted(d))
|
|
132
182
|
|
|
133
183
|
if self.is_local_record():
|
|
134
184
|
self.database = Path(self.database)
|
|
@@ -203,6 +253,7 @@ class Record():
|
|
|
203
253
|
for key in set(variables.keys()) - self._last_vars:
|
|
204
254
|
if key not in self._levels:
|
|
205
255
|
self._levels[key] = level
|
|
256
|
+
self.dims[key] = tuple(range(level + 1))
|
|
206
257
|
|
|
207
258
|
self._last_vars = set(variables.keys())
|
|
208
259
|
self._keys.update(variables.keys())
|
|
@@ -237,9 +288,9 @@ class Record():
|
|
|
237
288
|
self._items[key] = BufferList()
|
|
238
289
|
self._items[key].lu = pos
|
|
239
290
|
self._items[key].rd = tuple([i + 1 for i in pos])
|
|
240
|
-
self._items[key].append(pos, value)
|
|
291
|
+
self._items[key].append(pos, value, self.dims[key])
|
|
241
292
|
elif isinstance(self._items[key], BufferList):
|
|
242
|
-
self._items[key].append(pos, value)
|
|
293
|
+
self._items[key].append(pos, value, self.dims[key])
|
|
243
294
|
elif self._levels[key] == -1 and key not in self._items:
|
|
244
295
|
self._items[key] = value
|
|
245
296
|
|
|
@@ -254,6 +305,16 @@ class Record():
|
|
|
254
305
|
with open(self._file, 'wb') as f:
|
|
255
306
|
dill.dump(self, f)
|
|
256
307
|
|
|
308
|
+
def __repr__(self):
|
|
309
|
+
return f"<Record: id={self.id} app={self.description['app']}, keys={self.keys()}>"
|
|
310
|
+
|
|
311
|
+
# def _repr_html_(self):
|
|
312
|
+
# return f"""
|
|
313
|
+
# <h3>Record: id={self.id}, app={self.description['app']}</h3>
|
|
314
|
+
# <p>keys={self.keys()}</p>
|
|
315
|
+
# <p>dims={self.dims}</p>
|
|
316
|
+
# """
|
|
317
|
+
|
|
257
318
|
|
|
258
319
|
class Request():
|
|
259
320
|
__slots__ = ['sock', 'identity', 'msg', 'method']
|
|
@@ -283,7 +344,7 @@ def flush_cache():
|
|
|
283
344
|
r.flush()
|
|
284
345
|
|
|
285
346
|
|
|
286
|
-
def get_record(session, id, datapath):
|
|
347
|
+
def get_record(session: Session, id: int, datapath: Path) -> Record:
|
|
287
348
|
if id not in record_cache:
|
|
288
349
|
record_in_db = session.get(RecordInDB, id)
|
|
289
350
|
record_in_db.atime = utcnow()
|
|
@@ -297,7 +358,7 @@ def get_record(session, id, datapath):
|
|
|
297
358
|
return record
|
|
298
359
|
|
|
299
360
|
|
|
300
|
-
def
|
|
361
|
+
def record_create(session: Session, description: dict, datapath: Path) -> int:
|
|
301
362
|
record = Record(None, datapath, description)
|
|
302
363
|
record_in_db = RecordInDB()
|
|
303
364
|
if 'app' in description:
|
|
@@ -317,6 +378,20 @@ def create_record(session, description, datapath):
|
|
|
317
378
|
raise
|
|
318
379
|
|
|
319
380
|
|
|
381
|
+
def record_append(session: Session, record_id: int, level: int, step: int,
|
|
382
|
+
position: int, variables: dict, datapath: Path):
|
|
383
|
+
record = get_record(session, record_id, datapath)
|
|
384
|
+
record.append(level, step, position, variables)
|
|
385
|
+
try:
|
|
386
|
+
record_in_db = session.get(RecordInDB, record_id)
|
|
387
|
+
record_in_db.mtime = utcnow()
|
|
388
|
+
record_in_db.atime = utcnow()
|
|
389
|
+
session.commit()
|
|
390
|
+
except:
|
|
391
|
+
session.rollback()
|
|
392
|
+
raise
|
|
393
|
+
|
|
394
|
+
|
|
320
395
|
@logger.catch
|
|
321
396
|
async def handle(session: Session, request: Request, datapath: Path):
|
|
322
397
|
|
|
@@ -327,16 +402,10 @@ async def handle(session: Session, request: Request, datapath: Path):
|
|
|
327
402
|
await reply(request, 'pong')
|
|
328
403
|
case 'record_create':
|
|
329
404
|
description = dill.loads(msg['description'])
|
|
330
|
-
await reply(request,
|
|
405
|
+
await reply(request, record_create(session, description, datapath))
|
|
331
406
|
case 'record_append':
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
msg['variables'])
|
|
335
|
-
if msg['level'] < 0:
|
|
336
|
-
record_in_db = session.get(RecordInDB, msg['record_id'])
|
|
337
|
-
record_in_db.mtime = utcnow()
|
|
338
|
-
record_in_db.atime = utcnow()
|
|
339
|
-
session.commit()
|
|
407
|
+
record_append(session, msg['record_id'], msg['level'], msg['step'],
|
|
408
|
+
msg['position'], msg['variables'], datapath)
|
|
340
409
|
case 'record_description':
|
|
341
410
|
record = get_record(session, msg['record_id'], datapath)
|
|
342
411
|
await reply(request, dill.dumps(record.description))
|
|
@@ -365,7 +434,7 @@ async def handle(session: Session, request: Request, datapath: Path):
|
|
|
365
434
|
case 'record_replace_tags':
|
|
366
435
|
update_tags(session, msg['record_id'], msg['tags'], False)
|
|
367
436
|
case _:
|
|
368
|
-
logger.error(f
|
|
437
|
+
logger.error(f"Unknown method: {msg['method']}")
|
|
369
438
|
|
|
370
439
|
|
|
371
440
|
async def _handle(session: Session, request: Request, datapath: Path):
|
|
@@ -431,7 +500,9 @@ async def main(port, datapath, url, timeout=1, buffer=1024, interval=60):
|
|
|
431
500
|
|
|
432
501
|
|
|
433
502
|
@click.command()
|
|
434
|
-
@click.option('--port',
|
|
503
|
+
@click.option('--port',
|
|
504
|
+
default=os.getenv('QULAB_RECORD_PORT', 6789),
|
|
505
|
+
help='Port of the server.')
|
|
435
506
|
@click.option('--datapath', default=datapath, help='Path of the data.')
|
|
436
507
|
@click.option('--url', default=None, help='URL of the database.')
|
|
437
508
|
@click.option('--timeout', default=1, help='Timeout of ping.')
|
qulab/scan/scan.py
CHANGED
|
@@ -6,6 +6,7 @@ import os
|
|
|
6
6
|
import re
|
|
7
7
|
import sys
|
|
8
8
|
import uuid
|
|
9
|
+
import warnings
|
|
9
10
|
from graphlib import TopologicalSorter
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from types import MethodType
|
|
@@ -21,7 +22,7 @@ from tqdm.notebook import tqdm
|
|
|
21
22
|
from ..sys.rpc.zmq_socket import ZMQContextManager
|
|
22
23
|
from .expression import Env, Expression, Symbol
|
|
23
24
|
from .optimize import NgOptimizer
|
|
24
|
-
from .recorder import Record
|
|
25
|
+
from .recorder import Record, default_record_port
|
|
25
26
|
from .utils import async_zip, call_function
|
|
26
27
|
|
|
27
28
|
__process_uuid = uuid.uuid1()
|
|
@@ -171,7 +172,9 @@ class Scan():
|
|
|
171
172
|
def __init__(self,
|
|
172
173
|
app: str = 'task',
|
|
173
174
|
tags: tuple[str] = (),
|
|
174
|
-
database: str | Path
|
|
175
|
+
database: str | Path
|
|
176
|
+
| None = f'tcp://127.0.0.1:{default_record_port}',
|
|
177
|
+
dump_globals: bool = False,
|
|
175
178
|
mixin=None):
|
|
176
179
|
self.id = task_uuid()
|
|
177
180
|
self.record = None
|
|
@@ -183,36 +186,61 @@ class Scan():
|
|
|
183
186
|
'consts': {},
|
|
184
187
|
'functions': {},
|
|
185
188
|
'optimizers': {},
|
|
189
|
+
'namespace': {} if dump_globals else None,
|
|
186
190
|
'actions': {},
|
|
187
191
|
'dependents': {},
|
|
188
192
|
'order': {},
|
|
189
193
|
'filters': {},
|
|
190
|
-
'total': {}
|
|
194
|
+
'total': {},
|
|
195
|
+
'database': database,
|
|
196
|
+
'hiden': ['self', r'^__.*', r'.*__$'],
|
|
197
|
+
'entry': {
|
|
198
|
+
'env': {},
|
|
199
|
+
'shell': '',
|
|
200
|
+
'cmds': []
|
|
201
|
+
},
|
|
191
202
|
}
|
|
192
203
|
self._current_level = 0
|
|
193
|
-
self.
|
|
194
|
-
self.
|
|
195
|
-
self.
|
|
196
|
-
self.database = database
|
|
204
|
+
self._variables = {}
|
|
205
|
+
self._main_task = None
|
|
206
|
+
self._sock = None
|
|
197
207
|
self._sem = asyncio.Semaphore(100)
|
|
198
208
|
self._bar: dict[int, tqdm] = {}
|
|
199
|
-
self.
|
|
200
|
-
self._hide_pattern_re = re.compile('|'.join(self._hide_patterns))
|
|
209
|
+
self._hide_pattern_re = re.compile('|'.join(self.description['hiden']))
|
|
201
210
|
self._task_queue = asyncio.Queue()
|
|
211
|
+
self._task_pool = []
|
|
212
|
+
self._single_step = True
|
|
213
|
+
|
|
214
|
+
def __del__(self):
|
|
215
|
+
try:
|
|
216
|
+
self._main_task.cancel()
|
|
217
|
+
except:
|
|
218
|
+
pass
|
|
219
|
+
for task in self._task_pool:
|
|
220
|
+
try:
|
|
221
|
+
task.cancel()
|
|
222
|
+
except:
|
|
223
|
+
pass
|
|
202
224
|
|
|
203
225
|
def __getstate__(self) -> dict:
|
|
204
226
|
state = self.__dict__.copy()
|
|
205
227
|
del state['record']
|
|
206
|
-
del state['
|
|
207
|
-
del state['
|
|
228
|
+
del state['_sock']
|
|
229
|
+
del state['_main_task']
|
|
230
|
+
del state['_bar']
|
|
231
|
+
del state['_task_queue']
|
|
232
|
+
del state['_task_pool']
|
|
208
233
|
del state['_sem']
|
|
209
234
|
return state
|
|
210
235
|
|
|
211
236
|
def __setstate__(self, state: dict) -> None:
|
|
212
237
|
self.__dict__.update(state)
|
|
213
238
|
self.record = None
|
|
214
|
-
self.
|
|
215
|
-
self.
|
|
239
|
+
self._sock = None
|
|
240
|
+
self._main_task = None
|
|
241
|
+
self._bar = {}
|
|
242
|
+
self._task_queue = asyncio.Queue()
|
|
243
|
+
self._task_pool = []
|
|
216
244
|
self._sem = asyncio.Semaphore(100)
|
|
217
245
|
for opt in self.description['optimizers'].values():
|
|
218
246
|
opt.scanner = self
|
|
@@ -221,13 +249,17 @@ class Scan():
|
|
|
221
249
|
def current_level(self):
|
|
222
250
|
return self._current_level
|
|
223
251
|
|
|
252
|
+
@property
|
|
253
|
+
def variables(self) -> dict[str, Any]:
|
|
254
|
+
return self._variables
|
|
255
|
+
|
|
224
256
|
async def emit(self, current_level, step, position, variables: dict[str,
|
|
225
257
|
Any]):
|
|
226
258
|
for key, value in list(variables.items()):
|
|
227
259
|
if inspect.isawaitable(value) and not self.hiden(key):
|
|
228
260
|
variables[key] = await value
|
|
229
|
-
if self.
|
|
230
|
-
await self.
|
|
261
|
+
if self._sock is not None:
|
|
262
|
+
await self._sock.send_pyobj({
|
|
231
263
|
'task': self.id,
|
|
232
264
|
'method': 'record_append',
|
|
233
265
|
'record_id': self.record.id,
|
|
@@ -240,11 +272,14 @@ class Scan():
|
|
|
240
272
|
}
|
|
241
273
|
})
|
|
242
274
|
else:
|
|
243
|
-
self.record.append(current_level, step, position,
|
|
275
|
+
self.record.append(current_level, step, position, {
|
|
276
|
+
k: v
|
|
277
|
+
for k, v in variables.items() if not self.hiden(k)
|
|
278
|
+
})
|
|
244
279
|
|
|
245
280
|
def hide(self, name: str):
|
|
246
|
-
self.
|
|
247
|
-
self._hide_pattern_re = re.compile('|'.join(self.
|
|
281
|
+
self.description['hiden'].append(name)
|
|
282
|
+
self._hide_pattern_re = re.compile('|'.join(self.description['hiden']))
|
|
248
283
|
|
|
249
284
|
def hiden(self, name: str) -> bool:
|
|
250
285
|
return bool(self._hide_pattern_re.match(name))
|
|
@@ -260,24 +295,8 @@ class Scan():
|
|
|
260
295
|
return True
|
|
261
296
|
|
|
262
297
|
async def create_record(self):
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
ipy = get_ipython()
|
|
267
|
-
if ipy is not None:
|
|
268
|
-
scripts = ('ipython', ipy.user_ns['In'])
|
|
269
|
-
else:
|
|
270
|
-
try:
|
|
271
|
-
scripts = ('shell',
|
|
272
|
-
[sys.executable, __main__.__file__, *sys.argv[1:]])
|
|
273
|
-
except:
|
|
274
|
-
scripts = ('', [])
|
|
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()}
|
|
279
|
-
if self.sock is not None:
|
|
280
|
-
await self.sock.send_pyobj({
|
|
298
|
+
if self._sock is not None:
|
|
299
|
+
await self._sock.send_pyobj({
|
|
281
300
|
'task':
|
|
282
301
|
self.id,
|
|
283
302
|
'method':
|
|
@@ -286,9 +305,10 @@ class Scan():
|
|
|
286
305
|
dill.dumps(self.description)
|
|
287
306
|
})
|
|
288
307
|
|
|
289
|
-
record_id = await self.
|
|
290
|
-
return Record(record_id, self.database,
|
|
291
|
-
|
|
308
|
+
record_id = await self._sock.recv_pyobj()
|
|
309
|
+
return Record(record_id, self.description['database'],
|
|
310
|
+
self.description)
|
|
311
|
+
return Record(None, self.description['database'], self.description)
|
|
292
312
|
|
|
293
313
|
def get(self, name: str):
|
|
294
314
|
if name in self.description['consts']:
|
|
@@ -323,6 +343,10 @@ class Scan():
|
|
|
323
343
|
self.description['filters'][level].append(func)
|
|
324
344
|
|
|
325
345
|
def set(self, name: str, value):
|
|
346
|
+
try:
|
|
347
|
+
dill.dumps(value)
|
|
348
|
+
except:
|
|
349
|
+
raise ValueError('value is not serializable.')
|
|
326
350
|
if isinstance(value, Expression):
|
|
327
351
|
self.add_depends(name, value.symbols())
|
|
328
352
|
self.description['functions'][name] = value
|
|
@@ -392,7 +416,9 @@ class Scan():
|
|
|
392
416
|
async def _run(self):
|
|
393
417
|
assymbly(self.description)
|
|
394
418
|
task = asyncio.create_task(self._update_progress())
|
|
395
|
-
self.
|
|
419
|
+
self._task_pool.append(task)
|
|
420
|
+
self._variables = {'self': self}
|
|
421
|
+
self._variables.update(self.description['consts'])
|
|
396
422
|
for level, total in self.description['total'].items():
|
|
397
423
|
if total == np.inf:
|
|
398
424
|
total = None
|
|
@@ -402,11 +428,13 @@ class Scan():
|
|
|
402
428
|
if name in self.description['functions']:
|
|
403
429
|
self.variables[name] = await call_function(
|
|
404
430
|
self.description['functions'][name], self.variables)
|
|
405
|
-
if isinstance(
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
431
|
+
if isinstance(
|
|
432
|
+
self.description['database'],
|
|
433
|
+
str) and self.description['database'].startswith("tcp://"):
|
|
434
|
+
async with ZMQContextManager(
|
|
435
|
+
zmq.DEALER,
|
|
436
|
+
connect=self.description['database']) as socket:
|
|
437
|
+
self._sock = socket
|
|
410
438
|
self.record = await self.create_record()
|
|
411
439
|
await self.work()
|
|
412
440
|
else:
|
|
@@ -414,23 +442,49 @@ class Scan():
|
|
|
414
442
|
await self.work()
|
|
415
443
|
for level, bar in self._bar.items():
|
|
416
444
|
bar.close()
|
|
445
|
+
|
|
446
|
+
while not self._task_queue.empty():
|
|
447
|
+
evt = self._task_queue.get_nowait()
|
|
448
|
+
if isinstance(evt, asyncio.Event):
|
|
449
|
+
evt.set()
|
|
450
|
+
elif inspect.isawaitable(evt):
|
|
451
|
+
await evt
|
|
417
452
|
task.cancel()
|
|
453
|
+
if self._single_step:
|
|
454
|
+
await self.emit(0, 0, 0, self.variables.copy())
|
|
455
|
+
await self.emit(-1, 0, 0, {})
|
|
418
456
|
return self.variables
|
|
419
457
|
|
|
420
458
|
async def done(self):
|
|
421
|
-
if self.
|
|
459
|
+
if self._main_task is not None:
|
|
422
460
|
try:
|
|
423
|
-
await self.
|
|
461
|
+
await self._main_task
|
|
424
462
|
except asyncio.CancelledError:
|
|
425
463
|
pass
|
|
426
464
|
|
|
465
|
+
def finished(self):
|
|
466
|
+
return self._main_task.done()
|
|
467
|
+
|
|
427
468
|
def start(self):
|
|
428
469
|
import asyncio
|
|
429
|
-
self.
|
|
470
|
+
self._main_task = asyncio.create_task(self._run())
|
|
471
|
+
|
|
472
|
+
async def submit(self, server='tcp://127.0.0.1:6788'):
|
|
473
|
+
assymbly(self.description)
|
|
474
|
+
async with ZMQContextManager(zmq.DEALER, connect=server) as socket:
|
|
475
|
+
await socket.send_pyobj({
|
|
476
|
+
'method': 'submit',
|
|
477
|
+
'description': dill.dumps(self.description)
|
|
478
|
+
})
|
|
479
|
+
self.id = await socket.recv_pyobj()
|
|
480
|
+
await socket.send_pyobj({'method': 'get_record_id', 'id': self.id})
|
|
481
|
+
record_id = await socket.recv_pyobj()
|
|
482
|
+
self.record = Record(record_id, self.description['database'],
|
|
483
|
+
self.description)
|
|
430
484
|
|
|
431
485
|
def cancel(self):
|
|
432
|
-
if self.
|
|
433
|
-
self.
|
|
486
|
+
if self._main_task is not None:
|
|
487
|
+
self._main_task.cancel()
|
|
434
488
|
|
|
435
489
|
async def _reset_progress_bar(self, level):
|
|
436
490
|
if level in self._bar:
|
|
@@ -445,7 +499,6 @@ class Scan():
|
|
|
445
499
|
return
|
|
446
500
|
step = 0
|
|
447
501
|
position = 0
|
|
448
|
-
task = None
|
|
449
502
|
self._task_queue.put_nowait(
|
|
450
503
|
self._reset_progress_bar(self.current_level))
|
|
451
504
|
async for variables in _iter_level(
|
|
@@ -456,7 +509,8 @@ class Scan():
|
|
|
456
509
|
self._current_level += 1
|
|
457
510
|
if await self._filter(variables, self.current_level - 1):
|
|
458
511
|
yield variables
|
|
459
|
-
|
|
512
|
+
self._single_step = False
|
|
513
|
+
asyncio.create_task(
|
|
460
514
|
self.emit(self.current_level - 1, step, position,
|
|
461
515
|
variables.copy()))
|
|
462
516
|
step += 1
|
|
@@ -464,8 +518,6 @@ class Scan():
|
|
|
464
518
|
self._current_level -= 1
|
|
465
519
|
self._task_queue.put_nowait(
|
|
466
520
|
self._update_progress_bar(self.current_level, 1))
|
|
467
|
-
if task is not None:
|
|
468
|
-
await task
|
|
469
521
|
if self.current_level == 0:
|
|
470
522
|
await self.emit(self.current_level - 1, 0, 0, {})
|
|
471
523
|
for name, value in self.variables.items():
|
|
@@ -519,7 +571,73 @@ class Scan():
|
|
|
519
571
|
return await awaitable
|
|
520
572
|
|
|
521
573
|
|
|
574
|
+
class Unpicklable:
|
|
575
|
+
|
|
576
|
+
def __init__(self, obj):
|
|
577
|
+
self.type = str(type(obj))
|
|
578
|
+
self.id = id(obj)
|
|
579
|
+
|
|
580
|
+
def __repr__(self):
|
|
581
|
+
return f'<Unpicklable: {self.type} at 0x{id(self):x}>'
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
class TooLarge:
|
|
585
|
+
|
|
586
|
+
def __init__(self, obj):
|
|
587
|
+
self.type = str(type(obj))
|
|
588
|
+
self.id = id(obj)
|
|
589
|
+
|
|
590
|
+
def __repr__(self):
|
|
591
|
+
return f'<TooLarge: {self.type} at 0x{id(self):x}>'
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def dump_globals(ns=None, *, size_limit=10 * 1024 * 1024, warn=False):
|
|
595
|
+
import __main__
|
|
596
|
+
|
|
597
|
+
if ns is None:
|
|
598
|
+
ns = __main__.__dict__
|
|
599
|
+
|
|
600
|
+
namespace = {}
|
|
601
|
+
|
|
602
|
+
for name, value in ns.items():
|
|
603
|
+
try:
|
|
604
|
+
buf = dill.dumps(value)
|
|
605
|
+
except:
|
|
606
|
+
namespace[name] = Unpicklable(value)
|
|
607
|
+
if warn:
|
|
608
|
+
warnings.warn(f'Unpicklable: {name} {type(value)}')
|
|
609
|
+
if len(buf) > size_limit:
|
|
610
|
+
namespace[name] = TooLarge(value)
|
|
611
|
+
if warn:
|
|
612
|
+
warnings.warn(f'TooLarge: {name} {type(value)}')
|
|
613
|
+
else:
|
|
614
|
+
namespace[name] = buf
|
|
615
|
+
|
|
616
|
+
return namespace
|
|
617
|
+
|
|
618
|
+
|
|
522
619
|
def assymbly(description):
|
|
620
|
+
import __main__
|
|
621
|
+
from IPython import get_ipython
|
|
622
|
+
|
|
623
|
+
if isinstance(description['namespace'], dict):
|
|
624
|
+
description['namespace'] = dump_globals()
|
|
625
|
+
|
|
626
|
+
ipy = get_ipython()
|
|
627
|
+
if ipy is not None:
|
|
628
|
+
description['entry']['shell'] = 'ipython'
|
|
629
|
+
description['entry']['cmds'] = ipy.user_ns['In']
|
|
630
|
+
else:
|
|
631
|
+
try:
|
|
632
|
+
description['entry']['shell'] = 'shell'
|
|
633
|
+
description['entry']['cmds'] = [
|
|
634
|
+
sys.executable, __main__.__file__, *sys.argv[1:]
|
|
635
|
+
]
|
|
636
|
+
except:
|
|
637
|
+
pass
|
|
638
|
+
|
|
639
|
+
description['entry']['env'] = {k: v for k, v in os.environ.items()}
|
|
640
|
+
|
|
523
641
|
mapping = {
|
|
524
642
|
label: level
|
|
525
643
|
for level, label in enumerate(
|
qulab/scan/server.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import pickle
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
import uuid
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from .scan import Scan
|
|
8
|
+
import click
|
|
9
|
+
import dill
|
|
10
|
+
import numpy as np
|
|
11
|
+
import zmq
|
|
12
|
+
from loguru import logger
|
|
13
|
+
|
|
14
|
+
from qulab.sys.rpc.zmq_socket import ZMQContextManager
|
|
15
|
+
|
|
16
|
+
pool = {}
|
|
17
|
+
|
|
18
|
+
class Request():
|
|
19
|
+
__slots__ = ['sock', 'identity', 'msg', 'method']
|
|
20
|
+
|
|
21
|
+
def __init__(self, sock, identity, msg):
|
|
22
|
+
self.sock = sock
|
|
23
|
+
self.identity = identity
|
|
24
|
+
self.msg = pickle.loads(msg)
|
|
25
|
+
self.method = self.msg.get('method', '')
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def reply(req, resp):
|
|
29
|
+
await req.sock.send_multipart([req.identity, pickle.dumps(resp)])
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@logger.catch
|
|
33
|
+
async def handle(request: Request):
|
|
34
|
+
|
|
35
|
+
msg = request.msg
|
|
36
|
+
|
|
37
|
+
match request.method:
|
|
38
|
+
case 'ping':
|
|
39
|
+
await reply(request, 'pong')
|
|
40
|
+
case 'submit':
|
|
41
|
+
description = dill.loads(msg['description'])
|
|
42
|
+
task = Scan()
|
|
43
|
+
task.description = description
|
|
44
|
+
task.start()
|
|
45
|
+
pool[task.id] = task
|
|
46
|
+
await reply(request, task.id)
|
|
47
|
+
case 'get_record_id':
|
|
48
|
+
task = pool.get(msg['id'])
|
|
49
|
+
for _ in range(10):
|
|
50
|
+
if task.record:
|
|
51
|
+
await reply(request, task.record.id)
|
|
52
|
+
break
|
|
53
|
+
await asyncio.sleep(1)
|
|
54
|
+
else:
|
|
55
|
+
await reply(request, None)
|
|
56
|
+
case _:
|
|
57
|
+
logger.error(f"Unknown method: {msg['method']}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
async def _handle(request: Request):
|
|
61
|
+
try:
|
|
62
|
+
await handle(request)
|
|
63
|
+
except:
|
|
64
|
+
await reply(request, 'error')
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
async def serv(port):
|
|
68
|
+
logger.info('Server starting.')
|
|
69
|
+
async with ZMQContextManager(zmq.ROUTER, bind=f"tcp://*:{port}") as sock:
|
|
70
|
+
logger.info('Server started.')
|
|
71
|
+
while True:
|
|
72
|
+
identity, msg = await sock.recv_multipart()
|
|
73
|
+
req = Request(sock, identity, msg)
|
|
74
|
+
asyncio.create_task(_handle(req))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def watch(port, timeout=1):
|
|
78
|
+
with ZMQContextManager(zmq.DEALER,
|
|
79
|
+
connect=f"tcp://127.0.0.1:{port}") as sock:
|
|
80
|
+
sock.setsockopt(zmq.LINGER, 0)
|
|
81
|
+
while True:
|
|
82
|
+
try:
|
|
83
|
+
sock.send_pyobj({"method": "ping"})
|
|
84
|
+
if sock.poll(int(1000 * timeout)):
|
|
85
|
+
sock.recv()
|
|
86
|
+
else:
|
|
87
|
+
raise asyncio.TimeoutError()
|
|
88
|
+
except (zmq.error.ZMQError, asyncio.TimeoutError):
|
|
89
|
+
return asyncio.create_task(serv(port))
|
|
90
|
+
await asyncio.sleep(timeout)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
async def main(port, timeout=1):
|
|
94
|
+
task = await watch(port=port, timeout=timeout)
|
|
95
|
+
await task
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@click.command()
|
|
99
|
+
@click.option('--port', default=6788, help='Port of the server.')
|
|
100
|
+
@click.option('--timeout', default=1, help='Timeout of ping.')
|
|
101
|
+
def server(port, timeout):
|
|
102
|
+
asyncio.run(main(port, timeout))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
server()
|
qulab/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2.0.
|
|
1
|
+
__version__ = "2.0.5"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|