QuLab 2.1.0__cp310-cp310-macosx_10_9_universal2.whl → 2.1.1__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.
qulab/scan/scan.py CHANGED
@@ -1,35 +1,52 @@
1
1
  import asyncio
2
2
  import copy
3
- import datetime
4
3
  import inspect
5
4
  import itertools
6
5
  import os
7
6
  import re
8
7
  import sys
9
8
  import uuid
10
- import warnings
11
9
  from concurrent.futures import ProcessPoolExecutor
12
10
  from graphlib import TopologicalSorter
13
11
  from pathlib import Path
14
- from types import MethodType
15
- from typing import Any, Awaitable, Callable, Iterable, Type
12
+ from typing import Any, Awaitable, Callable, Iterable
16
13
 
17
14
  import dill
18
15
  import numpy as np
19
- import skopt
20
16
  import zmq
21
- from skopt.space import Categorical, Integer, Real
22
- from tqdm.notebook import tqdm
23
17
 
24
18
  from ..sys.rpc.zmq_socket import ZMQContextManager
25
19
  from .expression import Env, Expression, Symbol
26
20
  from .optimize import NgOptimizer
27
- from .recorder import Record, default_record_port
28
- from .utils import async_zip, call_function
21
+ from .record import Record
22
+ from .recorder import default_record_port
23
+ from .space import Optimizer, OptimizeSpace, Space
24
+ from .utils import async_zip, call_function, dump_globals
25
+
26
+ try:
27
+ from tqdm.notebook import tqdm
28
+ except:
29
+
30
+ class tqdm():
31
+
32
+ def update(self, n):
33
+ pass
34
+
35
+ def close(self):
36
+ pass
37
+
38
+ def reset(self):
39
+ pass
40
+
29
41
 
30
42
  __process_uuid = uuid.uuid1()
31
43
  __task_counter = itertools.count()
32
44
 
45
+ if os.getenv('QULAB_SERVER'):
46
+ default_server = os.getenv('QULAB_SERVER')
47
+ else:
48
+ default_server = f'tcp://127.0.0.1:{default_record_port}'
49
+
33
50
 
34
51
  def task_uuid():
35
52
  return uuid.uuid3(__process_uuid, str(next(__task_counter)))
@@ -55,79 +72,6 @@ def _get_depends(func: Callable):
55
72
  return args
56
73
 
57
74
 
58
- class OptimizeSpace():
59
-
60
- def __init__(self, optimizer: 'Optimizer', space):
61
- self.optimizer = optimizer
62
- self.space = space
63
- self.name = None
64
-
65
- def __len__(self):
66
- return self.optimizer.maxiter
67
-
68
-
69
- class Optimizer():
70
-
71
- def __init__(self,
72
- scanner: 'Scan',
73
- name: str,
74
- level: int,
75
- method: str | Type = skopt.Optimizer,
76
- maxiter: int = 1000,
77
- minimize: bool = True,
78
- **kwds):
79
- self.scanner = scanner
80
- self.method = method
81
- self.maxiter = maxiter
82
- self.dimensions = {}
83
- self.name = name
84
- self.level = level
85
- self.kwds = kwds
86
- self.minimize = minimize
87
-
88
- def create(self):
89
- return self.method(list(self.dimensions.values()), **self.kwds)
90
-
91
- def Categorical(self,
92
- categories,
93
- prior=None,
94
- transform=None,
95
- name=None) -> OptimizeSpace:
96
- return OptimizeSpace(self,
97
- Categorical(categories, prior, transform, name))
98
-
99
- def Integer(self,
100
- low,
101
- high,
102
- prior="uniform",
103
- base=10,
104
- transform=None,
105
- name=None,
106
- dtype=np.int64) -> OptimizeSpace:
107
- return OptimizeSpace(
108
- self, Integer(low, high, prior, base, transform, name, dtype))
109
-
110
- def Real(self,
111
- low,
112
- high,
113
- prior="uniform",
114
- base=10,
115
- transform=None,
116
- name=None,
117
- dtype=float) -> OptimizeSpace:
118
- return OptimizeSpace(
119
- self, Real(low, high, prior, base, transform, name, dtype))
120
-
121
- def __getstate__(self) -> dict:
122
- state = self.__dict__.copy()
123
- del state['scanner']
124
- return state
125
-
126
- def __setstate__(self, state: dict) -> None:
127
- self.__dict__.update(state)
128
- self.scanner = None
129
-
130
-
131
75
  class Promise():
132
76
  __slots__ = ['task', 'key', 'attr']
133
77
 
@@ -180,7 +124,7 @@ class Scan():
180
124
  app: str = 'task',
181
125
  tags: tuple[str] = (),
182
126
  database: str | Path
183
- | None = f'tcp://127.0.0.1:{default_record_port}',
127
+ | None = default_server,
184
128
  dump_globals: bool = False,
185
129
  max_workers: int = 4,
186
130
  max_promise: int = 100,
@@ -201,6 +145,8 @@ class Scan():
201
145
  'actions': {},
202
146
  'dependents': {},
203
147
  'order': {},
148
+ 'axis': {},
149
+ 'independent_variables': set(),
204
150
  'filters': {},
205
151
  'total': {},
206
152
  'database': database,
@@ -365,7 +311,11 @@ class Scan():
365
311
  self.description['filters'][level] = []
366
312
  self.description['filters'][level].append(func)
367
313
 
368
- def set(self, name: str, value, setter: Callable | None = None):
314
+ def set(self,
315
+ name: str,
316
+ value,
317
+ depends: Iterable[str] | None = None,
318
+ setter: Callable | None = None):
369
319
  try:
370
320
  dill.dumps(value)
371
321
  except:
@@ -374,9 +324,21 @@ class Scan():
374
324
  self.add_depends(name, value.symbols())
375
325
  self.description['functions'][name] = value
376
326
  elif callable(value):
377
- self.add_depends(name, _get_depends(value))
378
- self.description['functions'][name] = value
327
+ if depends:
328
+ self.add_depends(name, depends)
329
+ s = ','.join(depends)
330
+ self.description['functions'][f'_tmp_{name}'] = value
331
+ self.description['functions'][name] = eval(
332
+ f"lambda self, {s}: self.description['functions']['_tmp_{name}']({s})"
333
+ )
334
+ else:
335
+ self.add_depends(name, _get_depends(value))
336
+ self.description['functions'][name] = value
379
337
  else:
338
+ try:
339
+ value = Space.fromarray(value)
340
+ except:
341
+ pass
380
342
  self.description['consts'][name] = value
381
343
  if setter:
382
344
  self.description['setters'][name] = setter
@@ -396,6 +358,10 @@ class Scan():
396
358
  else:
397
359
  if level is None:
398
360
  raise ValueError('level must be provided.')
361
+ try:
362
+ range = Space.fromarray(range)
363
+ except:
364
+ pass
399
365
  self._add_loop_var(name, level, range)
400
366
  if isinstance(range, Expression) or callable(range):
401
367
  self.add_depends(name, range.symbols())
@@ -481,7 +447,14 @@ class Scan():
481
447
 
482
448
  self._variables = {'self': self}
483
449
 
484
- await update_variables(self._variables, self.description['consts'],
450
+ consts = {}
451
+ for k, v in self.description['consts'].items():
452
+ if isinstance(v, Space):
453
+ consts[k] = v.toarray()
454
+ else:
455
+ consts[k] = v
456
+
457
+ await update_variables(self._variables, consts,
485
458
  self.description['setters'])
486
459
  for level, total in self.description['total'].items():
487
460
  if total == np.inf:
@@ -640,51 +613,6 @@ class Scan():
640
613
  return await awaitable
641
614
 
642
615
 
643
- class Unpicklable:
644
-
645
- def __init__(self, obj):
646
- self.type = str(type(obj))
647
- self.id = id(obj)
648
-
649
- def __repr__(self):
650
- return f'<Unpicklable: {self.type} at 0x{id(self):x}>'
651
-
652
-
653
- class TooLarge:
654
-
655
- def __init__(self, obj):
656
- self.type = str(type(obj))
657
- self.id = id(obj)
658
-
659
- def __repr__(self):
660
- return f'<TooLarge: {self.type} at 0x{id(self):x}>'
661
-
662
-
663
- def dump_globals(ns=None, *, size_limit=10 * 1024 * 1024, warn=False):
664
- import __main__
665
-
666
- if ns is None:
667
- ns = __main__.__dict__
668
-
669
- namespace = {}
670
-
671
- for name, value in ns.items():
672
- try:
673
- buf = dill.dumps(value)
674
- except:
675
- namespace[name] = Unpicklable(value)
676
- if warn:
677
- warnings.warn(f'Unpicklable: {name} {type(value)}')
678
- if len(buf) > size_limit:
679
- namespace[name] = TooLarge(value)
680
- if warn:
681
- warnings.warn(f'TooLarge: {name} {type(value)}')
682
- else:
683
- namespace[name] = buf
684
-
685
- return namespace
686
-
687
-
688
616
  def assymbly(description):
689
617
  import __main__
690
618
  from IPython import get_ipython
@@ -810,6 +738,38 @@ def assymbly(description):
810
738
  if ready:
811
739
  description['order'][level].append(ready)
812
740
  keys -= set(ready)
741
+
742
+ axis = {}
743
+ independent_variables = set()
744
+
745
+ for name in description['consts']:
746
+ axis[name] = ()
747
+ for level, range_list in description['loops'].items():
748
+ for name, iterable in range_list:
749
+ if isinstance(iterable, OptimizeSpace):
750
+ axis[name] = tuple(range(level + 1))
751
+ continue
752
+ elif isinstance(iterable, (np.ndarray, list, tuple, range, Space)):
753
+ independent_variables.add(name)
754
+ axis[name] = (level, )
755
+
756
+ for level, group in description['order'].items():
757
+ for names in group:
758
+ for name in names:
759
+ if name not in description['dependents']:
760
+ if name not in axis:
761
+ axis[name] = (level, )
762
+ else:
763
+ d = set()
764
+ for n in description['dependents'][name]:
765
+ d.update(axis[n])
766
+ if name not in axis:
767
+ axis[name] = tuple(sorted(d))
768
+ else:
769
+ axis[name] = tuple(sorted(set(axis[name]) | d))
770
+ description['axis'] = axis
771
+ description['independent_variables'] = independent_variables
772
+
813
773
  return description
814
774
 
815
775
 
@@ -845,6 +805,8 @@ async def _iter_level(variables,
845
805
  opts[iter.optimizer.name] = iter.optimizer.create()
846
806
  elif isinstance(iter, Expression):
847
807
  iters_d[name] = iter.eval(env)
808
+ elif isinstance(iter, Space):
809
+ iters_d[name] = iter.toarray()
848
810
  elif callable(iter):
849
811
  iters_d[name] = await call_function(iter, variables)
850
812
  else:
qulab/scan/server.py CHANGED
@@ -1,20 +1,18 @@
1
1
  import asyncio
2
2
  import pickle
3
- import sys
4
- import time
5
- import uuid
6
- from pathlib import Path
7
- from .scan import Scan
3
+
8
4
  import click
9
5
  import dill
10
- import numpy as np
11
6
  import zmq
12
7
  from loguru import logger
13
8
 
14
9
  from qulab.sys.rpc.zmq_socket import ZMQContextManager
15
10
 
11
+ from .scan import Scan
12
+
16
13
  pool = {}
17
14
 
15
+
18
16
  class Request():
19
17
  __slots__ = ['sock', 'identity', 'msg', 'method']
20
18
 
qulab/scan/space.py ADDED
@@ -0,0 +1,172 @@
1
+ from typing import Type
2
+
3
+ import numpy as np
4
+ import skopt
5
+ from skopt.space import Categorical, Integer, Real
6
+
7
+
8
+ class Space():
9
+
10
+ def __init__(self, function, *args, **kwds):
11
+ self.function = function
12
+ self.args = args
13
+ self.kwds = kwds
14
+
15
+ def __repr__(self):
16
+ if self.function == 'asarray':
17
+ return repr(self.args[0])
18
+ args = ', '.join(map(repr, self.args))
19
+ kwds = ', '.join(f'{k}={v!r}' for k, v in self.kwds.items())
20
+ return f"{self.function}({args}, {kwds})"
21
+
22
+ def __len__(self):
23
+ return len(self.toarray())
24
+
25
+ @classmethod
26
+ def fromarray(cls, array):
27
+ if isinstance(array, Space):
28
+ return array
29
+ if isinstance(array, (list, tuple)):
30
+ array = np.array(array)
31
+ try:
32
+ a = np.linspace(array[0], array[-1], len(array), dtype=array.dtype)
33
+ if np.allclose(a, array):
34
+ return cls('linspace',
35
+ array[0],
36
+ array[-1],
37
+ len(array),
38
+ dtype=array.dtype)
39
+ except:
40
+ pass
41
+ try:
42
+ a = np.logspace(np.log10(array[0]),
43
+ np.log10(array[-1]),
44
+ len(array),
45
+ base=10,
46
+ dtype=array.dtype)
47
+ if np.allclose(a, array):
48
+ return cls('logspace',
49
+ np.log10(array[0]),
50
+ np.log10(array[-1]),
51
+ len(array),
52
+ base=10,
53
+ dtype=array.dtype)
54
+ except:
55
+ pass
56
+ try:
57
+ a = np.logspace(np.log2(array[0]),
58
+ np.log2(array[-1]),
59
+ len(array),
60
+ base=2,
61
+ dtype=array.dtype)
62
+ if np.allclose(a, array):
63
+ return cls('logspace',
64
+ np.log2(array[0]),
65
+ np.log2(array[-1]),
66
+ len(array),
67
+ base=2,
68
+ dtype=array.dtype)
69
+ except:
70
+ pass
71
+ try:
72
+ a = np.geomspace(array[0],
73
+ array[-1],
74
+ len(array),
75
+ dtype=array.dtype)
76
+ if np.allclose(a, array):
77
+ return cls('geomspace',
78
+ array[0],
79
+ array[-1],
80
+ len(array),
81
+ dtype=array.dtype)
82
+ except:
83
+ pass
84
+ return array
85
+
86
+ def toarray(self):
87
+ return getattr(np, self.function)(*self.args, **self.kwds)
88
+
89
+
90
+ def logspace(start, stop, num=50, endpoint=True, base=10):
91
+ return Space('logspace', start, stop, num, endpoint=endpoint, base=base)
92
+
93
+
94
+ def linspace(start, stop, num=50, endpoint=True):
95
+ return Space('linspace', start, stop, num, endpoint=endpoint)
96
+
97
+
98
+ def geomspace(start, stop, num=50, endpoint=True):
99
+ return Space('geomspace', start, stop, num, endpoint=endpoint)
100
+
101
+
102
+ class OptimizeSpace():
103
+
104
+ def __init__(self, optimizer: 'Optimizer', space):
105
+ self.optimizer = optimizer
106
+ self.space = space
107
+ self.name = None
108
+
109
+ def __len__(self):
110
+ return self.optimizer.maxiter
111
+
112
+
113
+ class Optimizer():
114
+
115
+ def __init__(self,
116
+ scanner,
117
+ name: str,
118
+ level: int,
119
+ method: str | Type = skopt.Optimizer,
120
+ maxiter: int = 1000,
121
+ minimize: bool = True,
122
+ **kwds):
123
+ self.scanner = scanner
124
+ self.method = method
125
+ self.maxiter = maxiter
126
+ self.dimensions = {}
127
+ self.name = name
128
+ self.level = level
129
+ self.kwds = kwds
130
+ self.minimize = minimize
131
+
132
+ def create(self):
133
+ return self.method(list(self.dimensions.values()), **self.kwds)
134
+
135
+ def Categorical(self,
136
+ categories,
137
+ prior=None,
138
+ transform=None,
139
+ name=None) -> OptimizeSpace:
140
+ return OptimizeSpace(self,
141
+ Categorical(categories, prior, transform, name))
142
+
143
+ def Integer(self,
144
+ low,
145
+ high,
146
+ prior="uniform",
147
+ base=10,
148
+ transform=None,
149
+ name=None,
150
+ dtype=np.int64) -> OptimizeSpace:
151
+ return OptimizeSpace(
152
+ self, Integer(low, high, prior, base, transform, name, dtype))
153
+
154
+ def Real(self,
155
+ low,
156
+ high,
157
+ prior="uniform",
158
+ base=10,
159
+ transform=None,
160
+ name=None,
161
+ dtype=float) -> OptimizeSpace:
162
+ return OptimizeSpace(
163
+ self, Real(low, high, prior, base, transform, name, dtype))
164
+
165
+ def __getstate__(self) -> dict:
166
+ state = self.__dict__.copy()
167
+ del state['scanner']
168
+ return state
169
+
170
+ def __setstate__(self, state: dict) -> None:
171
+ self.__dict__.update(state)
172
+ self.scanner = None
qulab/scan/utils.py CHANGED
@@ -1,11 +1,59 @@
1
1
  import ast
2
2
  import asyncio
3
3
  import inspect
4
+ import warnings
4
5
  from typing import Any, Callable
5
6
 
7
+ import dill
8
+
6
9
  from .expression import Env, Expression
7
10
 
8
11
 
12
+ class Unpicklable:
13
+
14
+ def __init__(self, obj):
15
+ self.type = str(type(obj))
16
+ self.id = id(obj)
17
+
18
+ def __repr__(self):
19
+ return f'<Unpicklable: {self.type} at 0x{id(self):x}>'
20
+
21
+
22
+ class TooLarge:
23
+
24
+ def __init__(self, obj):
25
+ self.type = str(type(obj))
26
+ self.id = id(obj)
27
+
28
+ def __repr__(self):
29
+ return f'<TooLarge: {self.type} at 0x{id(self):x}>'
30
+
31
+
32
+ def dump_globals(ns=None, *, size_limit=10 * 1024 * 1024, warn=False):
33
+ import __main__
34
+
35
+ if ns is None:
36
+ ns = __main__.__dict__
37
+
38
+ namespace = {}
39
+
40
+ for name, value in ns.items():
41
+ try:
42
+ buf = dill.dumps(value)
43
+ except:
44
+ namespace[name] = Unpicklable(value)
45
+ if warn:
46
+ warnings.warn(f'Unpicklable: {name} {type(value)}')
47
+ if len(buf) > size_limit:
48
+ namespace[name] = TooLarge(value)
49
+ if warn:
50
+ warnings.warn(f'TooLarge: {name} {type(value)}')
51
+ else:
52
+ namespace[name] = buf
53
+
54
+ return namespace
55
+
56
+
9
57
  def is_valid_identifier(s: str) -> bool:
10
58
  """
11
59
  Check if a string is a valid identifier.
qulab/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "2.1.0"
1
+ __version__ = "2.1.1"
File without changes
File without changes