QuLab 2.0.2__cp310-cp310-macosx_10_9_universal2.whl → 2.0.3__cp310-cp310-macosx_10_9_universal2.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.2
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=h84t4vjTH6Gu7SrBhudkzvbqfH1oNudDtM11LIY1h4Q,430
3
- qulab/fun.cpython-310-darwin.so,sha256=0z1iTDlNIT1TovOPS1xdhj0zPGtRhhj8KNVLIMz70yk,159632
4
- qulab/version.py,sha256=xARS6PXiulA1Bog5UZUM6hqQfydeRzKLyWtOyUBL-Ss,21
3
+ qulab/fun.cpython-310-darwin.so,sha256=ylfsTbVj1rgZIq55YnorLbEiJ580WOfXdWodSLPNI6E,159632
4
+ qulab/version.py,sha256=HFL2NgNf74s56viRPRlgp_rAmKP1MzrtnJeQ8LxF_8M,21
5
5
  qulab/monitor/__init__.py,sha256=nTHelnDpxRS_fl_B38TsN0njgq8eVTEz9IAnN3NbDlM,42
6
6
  qulab/monitor/__main__.py,sha256=w3yUcqq195LzSnXTkQcuC1RSFRhy4oQ_PEBmucXguME,97
7
7
  qulab/monitor/config.py,sha256=fQ5JcsMApKc1UwANEnIvbDQZl8uYW0tle92SaYtX9lI,744
@@ -19,8 +19,8 @@ qulab/scan/models.py,sha256=S8Q9hC8nOzxyoNB10EYg-miDKqoNMnjyAECjD-TuORw,17117
19
19
  qulab/scan/optimize.py,sha256=vErjRTCtn2MwMF5Xyhs1P4gHF2IFHv_EqxsUvH_4y7k,2287
20
20
  qulab/scan/query_record.py,sha256=ed40efBQxtkwUxZHT0zB9SYlMxgNUFqOtCiseOCeBe0,11521
21
21
  qulab/scan/recorder.py,sha256=jIkFY-Mirvkpyvh-8nKFiTqrLbGAgp9h9kBX25q_RIs,15402
22
- qulab/scan/scan.py,sha256=hWKDSosn7_eOAiiA548vdBRYu34OvvWPEtfXLk7fxcY,23030
23
- qulab/scan/utils.py,sha256=2bj7ggTNTK5LXwBDi8w7IzFqFjchoE6EhgSASC0fpDQ,1024
22
+ qulab/scan/scan.py,sha256=dA5yr6jOaULs9BVs4J-RxXl_CW-cgfSngCU1iZvsXX4,22494
23
+ qulab/scan/utils.py,sha256=n5yquKlz2QYMzciPgD9vkpBJVgzVzOqAlfvB4Qu6oOk,2551
24
24
  qulab/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
25
  qulab/storage/__main__.py,sha256=3emxxRry8BB0m8hUZvJ_oBqkPy7ksV7flHB_KEDXZuI,1692
26
26
  qulab/storage/base_dataset.py,sha256=4aKhqBNdZfdlm_z1Qy5dv0HrvgpvMdy8pbyfua8uE-4,11865
@@ -76,9 +76,9 @@ qulab/visualization/plot_layout.py,sha256=clNw9QjE_kVNpIIx2Ob4YhAz2fucPGMuzkoIrO
76
76
  qulab/visualization/plot_seq.py,sha256=lphYF4VhkEdc_wWr1kFBwrx2yujkyFPFaJ3pjr61awI,2693
77
77
  qulab/visualization/qdat.py,sha256=ZeevBYWkzbww4xZnsjHhw7wRorJCBzbG0iEu-XQB4EA,5735
78
78
  qulab/visualization/widgets.py,sha256=6KkiTyQ8J-ei70LbPQZAK35wjktY47w2IveOa682ftA,3180
79
- QuLab-2.0.2.dist-info/LICENSE,sha256=PRzIKxZtpQcH7whTG6Egvzl1A0BvnSf30tmR2X2KrpA,1065
80
- QuLab-2.0.2.dist-info/METADATA,sha256=cIK74-RBs-jNnttEcwVdgMtNd4WAh7OmcVn2ShZBIt4,3477
81
- QuLab-2.0.2.dist-info/WHEEL,sha256=r7U64H7df5k5VoE41bE2otJ6YmrMps4wUd2_S2hHvHQ,115
82
- QuLab-2.0.2.dist-info/entry_points.txt,sha256=ohBzutEnQimP_BZWiuXdSliu4QAYSHHcN0PZD8c7ZCY,46
83
- QuLab-2.0.2.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
84
- QuLab-2.0.2.dist-info/RECORD,,
79
+ QuLab-2.0.3.dist-info/LICENSE,sha256=PRzIKxZtpQcH7whTG6Egvzl1A0BvnSf30tmR2X2KrpA,1065
80
+ QuLab-2.0.3.dist-info/METADATA,sha256=wKvjwqsE30Ij0VVCngl-OyYRBKlV-Xi2ZipoZPcXXLY,3510
81
+ QuLab-2.0.3.dist-info/WHEEL,sha256=r7U64H7df5k5VoE41bE2otJ6YmrMps4wUd2_S2hHvHQ,115
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,,
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
- try:
39
- sig = inspect.signature(func)
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
- self.id = f"{app}({str(uuid.uuid1())})"
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': 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.assymbly()
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 assymbly(self):
472
- if self.description['compiled']:
473
- return
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
- if -1 in self.description['actions']:
484
- mapping[-1] = max(mapping.values()) + 1
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
- if self.current_level in self._bar:
644
- self._bar[self.current_level].reset()
645
- async for variables in self._iter_level(self.current_level,
646
- self.variables):
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
- if self.current_level in self._bar:
657
- self._bar[self.current_level].update(1)
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
- return Promise(asyncio.create_task(self._await(awaitable)))
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 concurrent.futures import Future
3
-
4
-
5
- def call_func_with_kwds(func, args, kwds, log=None):
6
- funcname = getattr(func, '__name__', repr(func))
7
- sig = inspect.signature(func)
8
- for p in sig.parameters.values():
9
- if p.kind == p.VAR_KEYWORD:
10
- return func(*args, **kwds)
11
- kw = {
12
- k: v
13
- for k, v in kwds.items()
14
- if k in list(sig.parameters.keys())[len(args):]
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
- args = [
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
- if log:
27
- log.exception(f'Call {funcname} with {args} and {kw}')
28
- raise
29
- finally:
30
- if log:
31
- log.debug(f'Call {funcname} with {args} and {kw}')
32
-
33
-
34
- def try_to_call(x, args, kwds, log=None):
35
- if callable(x):
36
- return call_func_with_kwds(x, args, kwds, log)
37
- return x
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.2"
1
+ __version__ = "2.0.3"
File without changes
File without changes