QuLab 2.0.3__cp310-cp310-win_amd64.whl → 2.0.5__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: QuLab
3
- Version: 2.0.3
3
+ Version: 2.0.5
4
4
  Summary: contral instruments and manage data
5
5
  Author-email: feihoo87 <feihoo87@gmail.com>
6
6
  Maintainer-email: feihoo87 <feihoo87@gmail.com>
@@ -1,7 +1,7 @@
1
1
  qulab/__init__.py,sha256=8zLGg-DfQhnDl2Ky0n-zXpN-8e-g7iR0AcaI4l4Vvpk,32
2
- qulab/__main__.py,sha256=OlYQe7go_eFLKR0uU6D1kIpTKvbpx3WRJFFdoYoFjdE,456
3
- qulab/fun.cp310-win_amd64.pyd,sha256=fmKTeNwow5U3R6yqIbv77flyO5b9_60K9FdOyY2OVxc,31232
4
- qulab/version.py,sha256=HFL2NgNf74s56viRPRlgp_rAmKP1MzrtnJeQ8LxF_8M,21
2
+ qulab/__main__.py,sha256=XN2wrhlmEkTIPq_ZeSaO8rWXfYgD2Czkm9DVFVoCw_U,515
3
+ qulab/fun.cp310-win_amd64.pyd,sha256=NWydFgI4610rGBrChumAoWPfE2BQu9vuRI3AEGeU2MU,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=gRUBjablvjWhDM-nZEunMVxQbUeaFF8qG7sROy9kIbs,11882
21
- qulab/scan/recorder.py,sha256=EgS7NzARFqZeWUJp188iFQWr-_fa7Akm0Op-tvGqh7A,15849
22
- qulab/scan/scan.py,sha256=jDsjyOPf15DV8rk1q2CYH7aDO6ypaP6n7t3LmNaY03o,23187
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.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,,
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=lO6CqtLHCAi38X3Es1a4R1lAjZFvN010IMRCFo2S7Mc,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()
Binary file
@@ -20,7 +20,6 @@ def get_record(id, database='tcp://127.0.0.1:6789'):
20
20
  'record_id': id
21
21
  })
22
22
  d = dill.loads(socket.recv_pyobj())
23
- print(d.keys())
24
23
  return Record(id, database, d)
25
24
  else:
26
25
  from .models import Record as RecordInDB
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
- datapath = Path.home() / 'qulab' / 'data'
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
- if self.pos_file is not None:
51
- with open(self.pos_file, 'ab') as f:
52
- for pos in self._pos:
53
- dill.dump(pos, f)
54
- self._pos.clear()
55
- if self.value_file is not None:
56
- with open(self.value_file, 'ab') as f:
57
- for value in self._value:
58
- dill.dump(value, f)
59
- self._value.clear()
60
-
61
- def append(self, pos, value):
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 open(self.value_file, 'rb') as f:
73
- while True:
74
- try:
75
- v.append(dill.load(f))
76
- except EOFError:
77
- break
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 open(self.pos_file, 'rb') as f:
85
- while True:
86
- try:
87
- p.append(dill.load(f))
88
- except EOFError:
89
- break
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] = (level, iterable)
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 create_record(session, description, datapath):
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, create_record(session, description, datapath))
405
+ await reply(request, record_create(session, description, datapath))
331
406
  case 'record_append':
332
- record = get_record(session, msg['record_id'], datapath)
333
- record.append(msg['level'], msg['step'], msg['position'],
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'Unknown method: {msg["method"]}')
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', default=6789, help='Port of the server.')
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 | None = 'tcp://127.0.0.1:6789',
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.variables = {}
194
- self._task = None
195
- self.sock = None
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._hide_patterns = [r'^__.*', r'.*__$']
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['sock']
207
- del state['_task']
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.sock = None
215
- self._task = None
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.sock is not None:
230
- await self.sock.send_pyobj({
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, variables)
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._hide_patterns.append(re.compile(name))
247
- self._hide_pattern_re = re.compile('|'.join(self._hide_patterns))
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
- import __main__
264
- from IPython import get_ipython
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.sock.recv_pyobj()
290
- return Record(record_id, self.database, self.description)
291
- return Record(None, self.database, self.description)
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.variables = self.description['consts'].copy()
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(self.database,
406
- str) and self.database.startswith("tcp://"):
407
- async with ZMQContextManager(zmq.DEALER,
408
- connect=self.database) as socket:
409
- self.sock = socket
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._task is not None:
459
+ if self._main_task is not None:
422
460
  try:
423
- await self._task
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._task = asyncio.create_task(self._run())
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._task is not None:
433
- self._task.cancel()
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
- task = asyncio.create_task(
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.3"
1
+ __version__ = "2.0.5"
File without changes
File without changes