QuLab 2.0.8__cp311-cp311-win_amd64.whl → 2.1.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: QuLab
3
- Version: 2.0.8
3
+ Version: 2.1.0
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
2
  qulab/__main__.py,sha256=XN2wrhlmEkTIPq_ZeSaO8rWXfYgD2Czkm9DVFVoCw_U,515
3
- qulab/fun.cp311-win_amd64.pyd,sha256=RVdR8t8ofl2iVJXcHPSjmH-cN4u8CEtqlaQ9wqCJlHk,31232
4
- qulab/version.py,sha256=DfI-RPnMF5c2VXIHTsOnE09mUWlPr23ZlXLyX4lCZxY,21
3
+ qulab/fun.cp311-win_amd64.pyd,sha256=U8P2nkeDFljBCP5CuqmzcFRZWyOspJ_rrIkfa6FTwUk,31232
4
+ qulab/version.py,sha256=5VFOC9N5P61WNNAC14BfsqZmQ2X-yL6b-UnG_oixpYM,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
@@ -18,8 +18,8 @@ qulab/scan/expression.py,sha256=vwUM9E0OFQal4bljlUtLR3NJu4zGRyuWYrdyZSs3QTU,1619
18
18
  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=rpw4U3NjLzlv9QMwKdCvEUGHjzPF8u1UpodfLW8aoTY,11853
21
- qulab/scan/recorder.py,sha256=lbIASqH4-4eTzqX1sG9K1LnUkqvkcRK5ab2OpXSeE-Y,22801
22
- qulab/scan/scan.py,sha256=jLYEn5WpAEdyCz-gGKz1My-6YK5xV-WlJ5kSfkINizY,29292
21
+ qulab/scan/recorder.py,sha256=cTKYP6vQzOXl9E5bj50qkxJPOh1virpJRW21pi53SaY,26426
22
+ qulab/scan/scan.py,sha256=2Ekfj3c7O91FR4eFSAb0G0wm8g9_f3xXfH7tIVnqrDc,30657
23
23
  qulab/scan/server.py,sha256=zDZfG6bOB3EUubfByQMq0BSQ9C6IV_Av0tDinzgpGjQ,2950
24
24
  qulab/scan/utils.py,sha256=XM-eKL5Xkm0hihhGS7Kq4g654Ye7n7TcU_f95gxtXq8,2634
25
25
  qulab/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -77,9 +77,9 @@ qulab/visualization/plot_layout.py,sha256=yAnMONOms7_szCdng-8wPpUMPis5UnbaNNzV4K
77
77
  qulab/visualization/plot_seq.py,sha256=h9D0Yl_yO64IwlvBgzMu9EBKr9gg6y8QE55gu2PfTns,2783
78
78
  qulab/visualization/qdat.py,sha256=HubXFu4nfcA7iUzghJGle1C86G6221hicLR0b-GqhKQ,5887
79
79
  qulab/visualization/widgets.py,sha256=HcYwdhDtLreJiYaZuN3LfofjJmZcLwjMfP5aasebgDo,3266
80
- QuLab-2.0.8.dist-info/LICENSE,sha256=b4NRQ-GFVpJMT7RuExW3NwhfbrYsX7AcdB7Gudok-fs,1086
81
- QuLab-2.0.8.dist-info/METADATA,sha256=INSqm390STs1Mj_4DDCF6cb3B99R9d-H9Jmk4NelB9Q,3609
82
- QuLab-2.0.8.dist-info/WHEEL,sha256=nSybvzWlmdJnHiUQSY-d7V1ycwEVUTqXiTvr2eshg44,102
83
- QuLab-2.0.8.dist-info/entry_points.txt,sha256=ohBzutEnQimP_BZWiuXdSliu4QAYSHHcN0PZD8c7ZCY,46
84
- QuLab-2.0.8.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
85
- QuLab-2.0.8.dist-info/RECORD,,
80
+ QuLab-2.1.0.dist-info/LICENSE,sha256=b4NRQ-GFVpJMT7RuExW3NwhfbrYsX7AcdB7Gudok-fs,1086
81
+ QuLab-2.1.0.dist-info/METADATA,sha256=w7Kk5WbBivHCA5jWGexSPxsiwY_PBVr85_-wrXz3BW0,3609
82
+ QuLab-2.1.0.dist-info/WHEEL,sha256=nSybvzWlmdJnHiUQSY-d7V1ycwEVUTqXiTvr2eshg44,102
83
+ QuLab-2.1.0.dist-info/entry_points.txt,sha256=ohBzutEnQimP_BZWiuXdSliu4QAYSHHcN0PZD8c7ZCY,46
84
+ QuLab-2.1.0.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
85
+ QuLab-2.1.0.dist-info/RECORD,,
Binary file
qulab/scan/recorder.py CHANGED
@@ -69,14 +69,14 @@ class BufferList():
69
69
  self._list = []
70
70
  self.lu = ()
71
71
  self.rd = ()
72
- self.inner_shape = None
72
+ self.inner_shape = ()
73
73
  self.file = file
74
74
  self._slice = slice
75
75
  self._lock = Lock()
76
- self._database = None
76
+ self._data_id = None
77
77
 
78
78
  def __repr__(self):
79
- return f"<BufferList: lu={self.lu}, rd={self.rd}, slice={self._slice}>"
79
+ return f"<BufferList: shape={self.shape}, lu={self.lu}, rd={self.rd}, slice={self._slice}>"
80
80
 
81
81
  def __getstate__(self):
82
82
  self.flush()
@@ -99,11 +99,12 @@ class BufferList():
99
99
  self._list = []
100
100
  self._slice = None
101
101
  self._lock = Lock()
102
- self._database = None
102
+ self._data_id = None
103
103
 
104
104
  @property
105
105
  def shape(self):
106
- return tuple([i - j for i, j in zip(self.rd, self.lu)])
106
+ return tuple([i - j
107
+ for i, j in zip(self.rd, self.lu)]) + self.inner_shape
107
108
 
108
109
  def flush(self):
109
110
  if not self._list:
@@ -143,11 +144,27 @@ class BufferList():
143
144
  break
144
145
 
145
146
  def iter(self):
146
- for pos, value in itertools.chain(self._iter_file(), self._list):
147
- if not self._slice:
148
- yield pos, value
149
- elif all([index_in_slice(s, i) for s, i in zip(self._slice, pos)]):
150
- yield pos, value[self._slice[len(pos):]]
147
+ if self._data_id is None:
148
+ for pos, value in itertools.chain(self._iter_file(), self._list):
149
+ if not self._slice:
150
+ yield pos, value
151
+ elif all(
152
+ [index_in_slice(s, i) for s, i in zip(self._slice, pos)]):
153
+ if self.inner_shape:
154
+ yield pos, value[self._slice[len(pos):]]
155
+ else:
156
+ yield pos, value
157
+ else:
158
+ server, record_id, key = self._data_id
159
+ with ZMQContextManager(zmq.DEALER, connect=server) as socket:
160
+ socket.send_pyobj({
161
+ 'method': 'bufferlist_slice',
162
+ 'record_id': record_id,
163
+ 'key': key,
164
+ 'slice': self._slice
165
+ })
166
+ ret = socket.recv_pyobj()
167
+ yield from ret
151
168
 
152
169
  def value(self):
153
170
  d = []
@@ -170,52 +187,118 @@ class BufferList():
170
187
 
171
188
  def array(self):
172
189
  pos, data = self.items()
173
- pos = np.asarray(pos) - np.asarray(self.lu)
190
+ if self._slice:
191
+ pos = np.asarray(pos)
192
+ lu = tuple(np.min(pos, axis=0))
193
+ rd = tuple(np.max(pos, axis=0) + 1)
194
+ pos = np.asarray(pos) - np.asarray(lu)
195
+ shape = []
196
+ for k, (s, i, j) in enumerate(zip(self._slice, rd, lu)):
197
+ if s.step is not None:
198
+ pos[:, k] = pos[:, k] / s.step
199
+ shape.append(round(np.ceil((i - j) / s.step)))
200
+ else:
201
+ shape.append(i - j)
202
+ shape = tuple(shape)
203
+ else:
204
+ shape = tuple([i - j for i, j in zip(self.rd, self.lu)])
205
+ pos = np.asarray(pos) - np.asarray(self.lu)
174
206
  data = np.asarray(data)
175
207
  inner_shape = data.shape[1:]
176
- x = np.full(self.shape + inner_shape, np.nan, dtype=data[0].dtype)
208
+ x = np.full(shape + inner_shape, np.nan, dtype=data[0].dtype)
177
209
  x.__setitem__(tuple(pos.T), data)
178
210
  return x
179
211
 
180
212
  def _full_slice(self, slice_tuple: slice
181
213
  | tuple[slice | int | EllipsisType, ...]):
214
+ ndim = len(self.lu)
215
+ if self.inner_shape:
216
+ ndim += len(self.inner_shape)
217
+
182
218
  if isinstance(slice_tuple, slice):
183
- slice_tuple = (slice_tuple, ) + (slice(0, sys.maxsize,
184
- 1), ) * (len(self.lu) - 1)
219
+ slice_tuple = (
220
+ slice_tuple, ) + (slice(0, sys.maxsize, 1), ) * (ndim - 1)
185
221
  if slice_tuple is Ellipsis:
186
- slice_tuple = (slice(0, sys.maxsize, 1), ) * len(self.lu)
222
+ slice_tuple = (slice(0, sys.maxsize, 1), ) * ndim
187
223
  else:
188
- head, tail = [], []
224
+ head, tail = (), ()
189
225
  for i, s in enumerate(slice_tuple):
190
226
  if s is Ellipsis:
191
227
  head = slice_tuple[:i]
192
228
  tail = slice_tuple[i + 1:]
193
229
  break
194
- slice_tuple = head + (slice(0, sys.maxsize, 1), ) * (
195
- len(self.lu) - len(head) - len(tail)) + tail
230
+ else:
231
+ head = slice_tuple
232
+ tail = ()
233
+ slice_tuple = head + (slice(
234
+ 0, sys.maxsize, 1), ) * (ndim - len(head) - len(tail)) + tail
196
235
  slice_list = []
197
- for s in slice_tuple:
236
+ contract = []
237
+ reversed = []
238
+ for i, s in enumerate(slice_tuple):
198
239
  if isinstance(s, int):
199
- slice_list.append(s)
240
+ if s >= 0:
241
+ slice_list.append(slice(s, s + 1, 1))
242
+ elif i < len(self.lu):
243
+ s = self.rd[i] + s
244
+ slice_list.append(slice(s, s + 1, 1))
245
+ else:
246
+ slice_list.append(slice(s, s - 1, -1))
247
+ contract.append(i)
200
248
  else:
201
249
  start, stop, step = s.start, s.stop, s.step
250
+ if step is None:
251
+ step = 1
252
+ if step < 0 and i < len(self.lu):
253
+ step = -step
254
+ reversed.append(i)
255
+ if start is None and stop is None:
256
+ start, stop = 0, sys.maxsize
257
+ elif start is None:
258
+ start, stop = self.lu[i], sys.maxsize
259
+ elif stop is None:
260
+ start, stop = 0, start + self.lu[i]
261
+ else:
262
+ start, stop = stop + self.lu[i] + 1, start + self.lu[
263
+ i] + 1
264
+
202
265
  if start is None:
203
266
  start = 0
267
+ elif start < 0 and i < len(self.lu):
268
+ start = self.rd[i] + start
204
269
  if step is None:
205
270
  step = 1
206
271
  if stop is None:
207
272
  stop = sys.maxsize
273
+ elif stop < 0 and i < len(self.lu):
274
+ stop = self.rd[i] + stop
275
+
208
276
  slice_list.append(slice(start, stop, step))
209
- return tuple(slice_list)
277
+ return tuple(slice_list), contract, reversed
210
278
 
211
279
  def __getitem__(self, slice_tuple: slice | EllipsisType
212
280
  | tuple[slice | int | EllipsisType, ...]):
213
- return super().__getitem__(self._full_slice(slice_tuple))
281
+ self._slice, contract, reversed = self._full_slice(slice_tuple)
282
+ ret = self.array()
283
+ slices = []
284
+ for i, s in enumerate(self._slice):
285
+ if i in contract:
286
+ slices.append(0)
287
+ elif isinstance(s, slice):
288
+ if i in reversed:
289
+ slices.append(slice(None, None, -1))
290
+ else:
291
+ slices.append(slice(None, None, 1))
292
+ ret = ret.__getitem__(tuple(slices))
293
+ self._slice = None
294
+ return ret
214
295
 
215
296
 
216
297
  class Record():
217
298
 
218
299
  def __init__(self, id, database, description=None):
300
+ from .scan import OptimizeSpace
301
+
219
302
  self.id = id
220
303
  self.database = database
221
304
  self.description = description
@@ -236,22 +319,29 @@ class Record():
236
319
  self.dims[name] = ()
237
320
  for level, range_list in self.description['loops'].items():
238
321
  for name, iterable in range_list:
239
- if isinstance(iterable, (np.ndarray, list, tuple, range)):
322
+ if isinstance(iterable, OptimizeSpace):
323
+ self.dims[name] = tuple(range(level + 1))
324
+ continue
325
+ elif isinstance(iterable, (np.ndarray, list, tuple, range)):
240
326
  self._items[name] = iterable
241
327
  self.independent_variables[name] = iterable
242
- self.dims[name] = (level, )
328
+ self.dims[name] = (level, )
243
329
 
244
330
  for level, group in self.description['order'].items():
245
331
  for names in group:
246
332
  for name in names:
247
- if name not in self.dims:
248
- if name not in self.description['dependents']:
333
+ if name not in self.description['dependents']:
334
+ if name not in self.dims:
249
335
  self.dims[name] = (level, )
250
- else:
251
- d = set()
252
- for n in self.description['dependents'][name]:
253
- d.update(self.dims[n])
336
+ else:
337
+ d = set()
338
+ for n in self.description['dependents'][name]:
339
+ d.update(self.dims[n])
340
+ if name not in self.dims:
254
341
  self.dims[name] = tuple(sorted(d))
342
+ else:
343
+ self.dims[name] = tuple(
344
+ sorted(set(self.dims[name]) | d))
255
345
 
256
346
  if self.is_local_record():
257
347
  self.database = Path(self.database)
@@ -301,9 +391,9 @@ class Record():
301
391
  self.flush()
302
392
 
303
393
  def __getitem__(self, key):
304
- return self.get(key)
394
+ return self.get(key, buffer_to_array=True)
305
395
 
306
- def get(self, key, default=_notgiven, buffer_to_array=True, slice=None):
396
+ def get(self, key, default=_notgiven, buffer_to_array=False, slice=None):
307
397
  if self.is_remote_record():
308
398
  with ZMQContextManager(zmq.DEALER,
309
399
  connect=self.database) as socket:
@@ -314,19 +404,19 @@ class Record():
314
404
  })
315
405
  ret = socket.recv_pyobj()
316
406
  if isinstance(ret, BufferList):
317
- socket.send_pyobj({
318
- 'method': 'bufferlist_slice',
319
- 'record_id': self.id,
320
- 'key': key,
321
- 'slice': slice
322
- })
323
- lst = socket.recv_pyobj()
324
- ret._list = lst
325
- ret._slice = slice
326
407
  if buffer_to_array:
408
+ socket.send_pyobj({
409
+ 'method': 'bufferlist_slice',
410
+ 'record_id': self.id,
411
+ 'key': key,
412
+ 'slice': slice
413
+ })
414
+ lst = socket.recv_pyobj()
415
+ ret._list = lst
416
+ ret._slice = slice
327
417
  return ret.array()
328
418
  else:
329
- ret._database = self.database
419
+ ret._data_id = self.database, self.id, key
330
420
  return ret
331
421
  else:
332
422
  return ret
qulab/scan/scan.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import copy
2
3
  import datetime
3
4
  import inspect
4
5
  import itertools
@@ -7,6 +8,7 @@ import re
7
8
  import sys
8
9
  import uuid
9
10
  import warnings
11
+ from concurrent.futures import ProcessPoolExecutor
10
12
  from graphlib import TopologicalSorter
11
13
  from pathlib import Path
12
14
  from types import MethodType
@@ -156,6 +158,11 @@ class Promise():
156
158
  return Promise(self.task, None, attr)
157
159
 
158
160
 
161
+ def _run_function_in_process(buf):
162
+ func, args, kwds = dill.loads(buf)
163
+ return func(*args, **kwds)
164
+
165
+
159
166
  class Scan():
160
167
 
161
168
  def __new__(cls, *args, mixin=None, **kwds):
@@ -175,6 +182,8 @@ class Scan():
175
182
  database: str | Path
176
183
  | None = f'tcp://127.0.0.1:{default_record_port}',
177
184
  dump_globals: bool = False,
185
+ max_workers: int = 4,
186
+ max_promise: int = 100,
178
187
  mixin=None):
179
188
  self.id = task_uuid()
180
189
  self.record = None
@@ -206,23 +215,21 @@ class Scan():
206
215
  self._variables = {}
207
216
  self._main_task = None
208
217
  self._sock = None
209
- self._sem = asyncio.Semaphore(100)
218
+ self._sem = asyncio.Semaphore(max_promise + 1)
210
219
  self._bar: dict[int, tqdm] = {}
211
220
  self._hide_pattern_re = re.compile('|'.join(self.description['hiden']))
212
- self._task_queue = asyncio.Queue()
213
- self._task_pool = []
221
+ self._msg_queue = asyncio.Queue()
222
+ self._prm_queue = asyncio.Queue()
214
223
  self._single_step = True
224
+ self._max_workers = max_workers
225
+ self._max_promise = max_promise
226
+ self._executors = ProcessPoolExecutor(max_workers=max_workers)
215
227
 
216
228
  def __del__(self):
217
229
  try:
218
230
  self._main_task.cancel()
219
231
  except:
220
232
  pass
221
- for task in self._task_pool:
222
- try:
223
- task.cancel()
224
- except:
225
- pass
226
233
 
227
234
  def __getstate__(self) -> dict:
228
235
  state = self.__dict__.copy()
@@ -230,9 +237,10 @@ class Scan():
230
237
  del state['_sock']
231
238
  del state['_main_task']
232
239
  del state['_bar']
233
- del state['_task_queue']
234
- del state['_task_pool']
240
+ del state['_msg_queue']
241
+ del state['_prm_queue']
235
242
  del state['_sem']
243
+ del state['_executors']
236
244
  return state
237
245
 
238
246
  def __setstate__(self, state: dict) -> None:
@@ -241,12 +249,20 @@ class Scan():
241
249
  self._sock = None
242
250
  self._main_task = None
243
251
  self._bar = {}
244
- self._task_queue = asyncio.Queue()
245
- self._task_pool = []
246
- self._sem = asyncio.Semaphore(100)
252
+ self._prm_queue = asyncio.Queue()
253
+ self._msg_queue = asyncio.Queue()
254
+ self._sem = asyncio.Semaphore(self._max_promise + 1)
255
+ self._executors = ProcessPoolExecutor(max_workers=self._max_workers)
247
256
  for opt in self.description['optimizers'].values():
248
257
  opt.scanner = self
249
258
 
259
+ def __del__(self):
260
+ try:
261
+ self._main_task.cancel()
262
+ except:
263
+ pass
264
+ self._executors.shutdown()
265
+
250
266
  @property
251
267
  def current_level(self):
252
268
  return self._current_level
@@ -255,8 +271,8 @@ class Scan():
255
271
  def variables(self) -> dict[str, Any]:
256
272
  return self._variables
257
273
 
258
- async def emit(self, current_level, step, position, variables: dict[str,
259
- Any]):
274
+ async def _emit(self, current_level, step, position, variables: dict[str,
275
+ Any]):
260
276
  for key, value in list(variables.items()):
261
277
  if inspect.isawaitable(value) and not self.hiden(key):
262
278
  variables[key] = await value
@@ -279,6 +295,11 @@ class Scan():
279
295
  for k, v in variables.items() if not self.hiden(k)
280
296
  })
281
297
 
298
+ def emit(self, current_level, step, position, variables: dict[str, Any]):
299
+ self._msg_queue.put_nowait(
300
+ asyncio.create_task(
301
+ self._emit(current_level, step, position, variables.copy())))
302
+
282
303
  def hide(self, name: str):
283
304
  self.description['hiden'].append(name)
284
305
  self._hide_pattern_re = re.compile('|'.join(self.description['hiden']))
@@ -288,11 +309,11 @@ class Scan():
288
309
 
289
310
  async def _filter(self, variables: dict[str, Any], level: int = 0):
290
311
  try:
291
- return all([
292
- await call_function(fun, variables) for fun in itertools.chain(
312
+ return all(await asyncio.gather(*[
313
+ call_function(fun, variables) for fun in itertools.chain(
293
314
  self.description['filters'].get(level, []),
294
315
  self.description['filters'].get(-1, []))
295
- ])
316
+ ]))
296
317
  except:
297
318
  return True
298
319
 
@@ -431,11 +452,15 @@ class Scan():
431
452
 
432
453
  async def _update_progress(self):
433
454
  while True:
434
- task = await self._task_queue.get()
435
- if isinstance(task, asyncio.Event):
436
- task.set()
437
- elif inspect.isawaitable(task):
438
- await task
455
+ task = await self._prm_queue.get()
456
+ await task
457
+ self._prm_queue.task_done()
458
+
459
+ async def _send_msg(self):
460
+ while True:
461
+ task = await self._msg_queue.get()
462
+ await task
463
+ self._msg_queue.task_done()
439
464
 
440
465
  async def run(self):
441
466
  assymbly(self.description)
@@ -451,44 +476,41 @@ class Scan():
451
476
  await self._run()
452
477
 
453
478
  async def _run(self):
454
- task = asyncio.create_task(self._update_progress())
455
- self._task_pool.append(task)
479
+ send_msg = asyncio.create_task(self._send_msg())
480
+ update_progress_task = asyncio.create_task(self._update_progress())
481
+
456
482
  self._variables = {'self': self}
457
- self._variables.update(self.description['consts'].copy())
483
+
484
+ await update_variables(self._variables, self.description['consts'],
485
+ self.description['setters'])
458
486
  for level, total in self.description['total'].items():
459
487
  if total == np.inf:
460
488
  total = None
461
489
  self._bar[level] = tqdm(total=total)
462
- for group in self.description['order'].get(-1, []):
463
- for name in group:
464
- if name in self.description['functions']:
465
- self.variables[name] = await call_function(
466
- self.description['functions'][name], self.variables)
467
- if name in self.description['setters']:
468
- coro = self.description['setters'][name](
469
- self.variables[name])
470
- if inspect.isawaitable(coro):
471
- await coro
490
+
491
+ updates = await call_many_functions(
492
+ self.description['order'].get(-1, []),
493
+ self.description['functions'], self.variables)
494
+ await update_variables(self.variables, updates,
495
+ self.description['setters'])
496
+
472
497
  self.record = await self.create_record()
473
498
  await self.work()
474
499
  for level, bar in self._bar.items():
475
500
  bar.close()
476
501
 
477
- while not self._task_queue.empty():
478
- evt = self._task_queue.get_nowait()
479
- if isinstance(evt, asyncio.Event):
480
- evt.set()
481
- elif inspect.isawaitable(evt):
482
- await evt
483
502
  if self._single_step:
484
- for group in self.description['order'].get(-1, []):
485
- for name in group:
486
- if name in self.description['getters']:
487
- self.variables[name] = await call_function(
488
- self.description['getters'][name], self.variables)
489
- await self.emit(0, 0, 0, self.variables.copy())
490
- await self.emit(-1, 0, 0, {})
491
- task.cancel()
503
+ self.variables.update(await call_many_functions(
504
+ self.description['order'].get(-1, []),
505
+ self.description['getters'], self.variables))
506
+
507
+ self.emit(0, 0, 0, self.variables)
508
+ self.emit(-1, 0, 0, {})
509
+
510
+ await self._prm_queue.join()
511
+ update_progress_task.cancel()
512
+ await self._msg_queue.join()
513
+ send_msg.cancel()
492
514
  return self.variables
493
515
 
494
516
  async def done(self):
@@ -535,8 +557,8 @@ class Scan():
535
557
  return
536
558
  step = 0
537
559
  position = 0
538
- self._task_queue.put_nowait(
539
- self._reset_progress_bar(self.current_level))
560
+ self._prm_queue.put_nowait(self._reset_progress_bar(
561
+ self.current_level))
540
562
  async for variables in _iter_level(
541
563
  self.variables,
542
564
  self.description['loops'].get(self.current_level, []),
@@ -547,23 +569,18 @@ class Scan():
547
569
  if await self._filter(variables, self.current_level - 1):
548
570
  yield variables
549
571
  self._single_step = False
550
- asyncio.create_task(
551
- self.emit(self.current_level - 1, step, position,
552
- variables.copy()))
572
+ self.emit(self.current_level - 1, step, position, variables)
553
573
  step += 1
554
574
  position += 1
555
575
  self._current_level -= 1
556
- self._task_queue.put_nowait(
576
+ self._prm_queue.put_nowait(
557
577
  self._update_progress_bar(self.current_level, 1))
558
578
  if self.current_level == 0:
559
- await self.emit(self.current_level - 1, 0, 0, {})
579
+ self.emit(self.current_level - 1, 0, 0, {})
560
580
  for name, value in self.variables.items():
561
581
  if inspect.isawaitable(value):
562
582
  self.variables[name] = await value
563
- while not self._task_queue.empty():
564
- task = self._task_queue.get_nowait()
565
- if inspect.isawaitable(task):
566
- await task
583
+ await self._prm_queue.join()
567
584
 
568
585
  async def work(self, **kwds):
569
586
  if self.current_level in self.description['actions']:
@@ -588,7 +605,8 @@ class Scan():
588
605
  """
589
606
  self.description['actions'][level] = action
590
607
 
591
- async def promise(self, awaitable: Awaitable) -> Promise:
608
+ async def promise(self, awaitable: Awaitable | Callable, *args,
609
+ **kwds) -> Promise:
592
610
  """
593
611
  Promise to calculate asynchronous function and return the result in future.
594
612
 
@@ -601,8 +619,19 @@ class Scan():
601
619
  if inspect.isawaitable(awaitable):
602
620
  async with self._sem:
603
621
  task = asyncio.create_task(self._await(awaitable))
604
- self._task_queue.put_nowait(task)
622
+ self._prm_queue.put_nowait(task)
605
623
  return Promise(task)
624
+ elif inspect.iscoroutinefunction(awaitable):
625
+ return await self.promise(awaitable(*args, **kwds))
626
+ elif callable(awaitable):
627
+ try:
628
+ buf = dill.dumps((awaitable, args, kwds))
629
+ task = asyncio.get_running_loop().run_in_executor(
630
+ self._executors, _run_function_in_process, buf)
631
+ self._prm_queue.put_nowait(task)
632
+ return Promise(task)
633
+ except:
634
+ return awaitable(*args, **kwds)
606
635
  else:
607
636
  return awaitable
608
637
 
@@ -711,18 +740,18 @@ def assymbly(description):
711
740
  except:
712
741
  pass
713
742
 
714
- dependents = description['dependents'].copy()
743
+ dependents = copy.deepcopy(description['dependents'])
715
744
 
716
745
  for level in levels:
717
746
  range_list = description['loops'].get(level, [])
718
747
  if level > 0:
719
748
  if f'#__loop_{level}' not in description['dependents']:
720
- dependents[f'#__loop_{level}'] = []
721
- dependents[f'#__loop_{level}'].append(f'#__loop_{level-1}')
749
+ dependents[f'#__loop_{level}'] = set()
750
+ dependents[f'#__loop_{level}'].add(f'#__loop_{level-1}')
722
751
  for name, _ in range_list:
723
752
  if name not in description['dependents']:
724
- dependents[name] = []
725
- dependents[name].append(f'#__loop_{level}')
753
+ dependents[name] = set()
754
+ dependents[name].add(f'#__loop_{level}')
726
755
 
727
756
  def _get_all_depends(key, graph):
728
757
  ret = set()
@@ -784,13 +813,17 @@ def assymbly(description):
784
813
  return description
785
814
 
786
815
 
787
- async def _update_variables(variables, updates, setters):
816
+ async def update_variables(variables: dict[str, Any], updates: dict[str, Any],
817
+ setters: dict[str, Callable]):
818
+ coros = []
788
819
  for name, value in updates.items():
789
820
  if name in setters:
790
821
  coro = setters[name](value)
791
822
  if inspect.isawaitable(coro):
792
- await coro
823
+ coros.append(coro)
793
824
  variables[name] = value
825
+ if coros:
826
+ await asyncio.gather(*coros)
794
827
 
795
828
 
796
829
  async def _iter_level(variables,
@@ -823,31 +856,23 @@ async def _iter_level(variables,
823
856
  maxiter = min(maxiter, opt_cfg.maxiter)
824
857
 
825
858
  async for args in async_zip(*iters_d.values(), range(maxiter)):
826
- await _update_variables(variables, dict(zip(iters_d.keys(),
827
- args[:-1])), setters)
859
+ await update_variables(variables, dict(zip(iters_d.keys(), args[:-1])),
860
+ setters)
828
861
  for name, opt in opts.items():
829
862
  args = opt.ask()
830
863
  opt_cfg = optimizers[name]
831
- await _update_variables(variables, {
864
+ await update_variables(variables, {
832
865
  n: v
833
866
  for n, v in zip(opt_cfg.dimensions.keys(), args)
834
867
  }, setters)
835
868
 
836
- for group in order:
837
- for name in group:
838
- if name in functions:
839
- await _update_variables(variables, {
840
- name:
841
- await call_function(functions[name], variables)
842
- }, setters)
869
+ await update_variables(
870
+ variables, await call_many_functions(order, functions, variables),
871
+ setters)
843
872
 
844
873
  yield variables
845
874
 
846
- for group in order:
847
- for name in group:
848
- if name in getters:
849
- variables[name] = await call_function(
850
- getters[name], variables)
875
+ variables.update(await call_many_functions(order, getters, variables))
851
876
 
852
877
  for name, opt in opts.items():
853
878
  opt_cfg = optimizers[name]
@@ -865,10 +890,28 @@ async def _iter_level(variables,
865
890
  for name, opt in opts.items():
866
891
  opt_cfg = optimizers[name]
867
892
  result = opt.get_result()
868
- variables.update({
869
- n: v
870
- for n, v in zip(opt_cfg.dimensions.keys(), result.x)
871
- })
893
+ await update_variables(
894
+ variables, {
895
+ name: value
896
+ for name, value in zip(opt_cfg.dimensions.keys(), result.x)
897
+ }, setters)
872
898
  variables[name] = result.fun
873
899
  if opts:
874
900
  yield variables
901
+
902
+
903
+ async def call_many_functions(order: list[list[str]],
904
+ functions: dict[str, Callable],
905
+ variables: dict[str, Any]) -> dict[str, Any]:
906
+ ret = {}
907
+ for group in order:
908
+ waited = []
909
+ coros = []
910
+ for name in group:
911
+ if name in functions:
912
+ waited.append(name)
913
+ coros.append(call_function(functions[name], variables | ret))
914
+ if coros:
915
+ results = await asyncio.gather(*coros)
916
+ ret.update(dict(zip(waited, results)))
917
+ return ret
qulab/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "2.0.8"
1
+ __version__ = "2.1.0"
File without changes
File without changes