QuLab 2.0.2__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-2.0.2.dist-info/LICENSE +21 -0
- QuLab-2.0.2.dist-info/METADATA +98 -0
- QuLab-2.0.2.dist-info/RECORD +84 -0
- QuLab-2.0.2.dist-info/WHEEL +5 -0
- QuLab-2.0.2.dist-info/entry_points.txt +2 -0
- QuLab-2.0.2.dist-info/top_level.txt +1 -0
- qulab/__init__.py +1 -0
- qulab/__main__.py +26 -0
- qulab/fun.cpython-310-darwin.so +0 -0
- qulab/monitor/__init__.py +1 -0
- qulab/monitor/__main__.py +8 -0
- qulab/monitor/config.py +41 -0
- qulab/monitor/dataset.py +77 -0
- qulab/monitor/event_queue.py +54 -0
- qulab/monitor/mainwindow.py +234 -0
- qulab/monitor/monitor.py +93 -0
- qulab/monitor/ploter.py +123 -0
- qulab/monitor/qt_compat.py +16 -0
- qulab/monitor/toolbar.py +265 -0
- qulab/scan/__init__.py +3 -0
- qulab/scan/curd.py +144 -0
- qulab/scan/expression.py +505 -0
- qulab/scan/models.py +540 -0
- qulab/scan/optimize.py +69 -0
- qulab/scan/query_record.py +361 -0
- qulab/scan/recorder.py +447 -0
- qulab/scan/scan.py +701 -0
- qulab/scan/utils.py +37 -0
- qulab/storage/__init__.py +0 -0
- qulab/storage/__main__.py +51 -0
- qulab/storage/backend/__init__.py +0 -0
- qulab/storage/backend/redis.py +204 -0
- qulab/storage/base_dataset.py +352 -0
- qulab/storage/chunk.py +60 -0
- qulab/storage/dataset.py +127 -0
- qulab/storage/file.py +273 -0
- qulab/storage/models/__init__.py +22 -0
- qulab/storage/models/base.py +4 -0
- qulab/storage/models/config.py +28 -0
- qulab/storage/models/file.py +89 -0
- qulab/storage/models/ipy.py +58 -0
- qulab/storage/models/models.py +88 -0
- qulab/storage/models/record.py +161 -0
- qulab/storage/models/report.py +22 -0
- qulab/storage/models/tag.py +93 -0
- qulab/storage/storage.py +95 -0
- qulab/sys/__init__.py +0 -0
- qulab/sys/chat.py +688 -0
- qulab/sys/device/__init__.py +3 -0
- qulab/sys/device/basedevice.py +221 -0
- qulab/sys/device/loader.py +86 -0
- qulab/sys/device/utils.py +46 -0
- qulab/sys/drivers/FakeInstrument.py +52 -0
- qulab/sys/drivers/__init__.py +0 -0
- qulab/sys/ipy_events.py +125 -0
- qulab/sys/net/__init__.py +0 -0
- qulab/sys/net/bencoder.py +205 -0
- qulab/sys/net/cli.py +169 -0
- qulab/sys/net/dhcp.py +543 -0
- qulab/sys/net/dhcpd.py +176 -0
- qulab/sys/net/kad.py +1142 -0
- qulab/sys/net/kcp.py +192 -0
- qulab/sys/net/nginx.py +192 -0
- qulab/sys/progress.py +190 -0
- qulab/sys/rpc/__init__.py +0 -0
- qulab/sys/rpc/client.py +0 -0
- qulab/sys/rpc/exceptions.py +96 -0
- qulab/sys/rpc/msgpack.py +1052 -0
- qulab/sys/rpc/msgpack.pyi +41 -0
- qulab/sys/rpc/rpc.py +412 -0
- qulab/sys/rpc/serialize.py +139 -0
- qulab/sys/rpc/server.py +29 -0
- qulab/sys/rpc/socket.py +29 -0
- qulab/sys/rpc/utils.py +25 -0
- qulab/sys/rpc/worker.py +0 -0
- qulab/sys/rpc/zmq_socket.py +209 -0
- qulab/version.py +1 -0
- qulab/visualization/__init__.py +188 -0
- qulab/visualization/__main__.py +71 -0
- qulab/visualization/_autoplot.py +463 -0
- qulab/visualization/plot_layout.py +408 -0
- qulab/visualization/plot_seq.py +90 -0
- qulab/visualization/qdat.py +152 -0
- qulab/visualization/widgets.py +86 -0
qulab/scan/utils.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
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
|
+
}
|
|
16
|
+
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)
|
|
25
|
+
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
|
|
File without changes
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@click.command()
|
|
5
|
+
@click.option('--executor', default='', help='Executor address to use.')
|
|
6
|
+
@click.option('--port', default=8080, help='Port to run the server on.')
|
|
7
|
+
@click.option('--host', default='127.0.0.1', help='Host to run the server on.')
|
|
8
|
+
@click.option('--db-url', default=None, help='Database URL to use.')
|
|
9
|
+
@click.option('--data-path',
|
|
10
|
+
default='waveforms/data',
|
|
11
|
+
help='Path to the data directory.')
|
|
12
|
+
@click.option('--debug', is_flag=True, help='Run in debug mode.')
|
|
13
|
+
@click.option('--workers',
|
|
14
|
+
default=1,
|
|
15
|
+
help='Number of workers to run the server with.')
|
|
16
|
+
@click.option('--timeout', default=60, help='Timeout for requests.')
|
|
17
|
+
@click.option('--log-level',
|
|
18
|
+
default='INFO',
|
|
19
|
+
help='Log level to run the server with.')
|
|
20
|
+
@click.option('--log-file',
|
|
21
|
+
default='/var/log/waveforms/server.log',
|
|
22
|
+
help='Log file to run the server with.')
|
|
23
|
+
@click.option('--log-format',
|
|
24
|
+
default='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
25
|
+
help='Log format to run the server with.')
|
|
26
|
+
def main(executor, port, host, db_url, data_path, debug, workers, timeout,
|
|
27
|
+
log_level, log_file, log_format):
|
|
28
|
+
"""
|
|
29
|
+
Main entry point for the server.
|
|
30
|
+
"""
|
|
31
|
+
from waveforms.server import create_app
|
|
32
|
+
|
|
33
|
+
app = create_app(
|
|
34
|
+
executor=executor,
|
|
35
|
+
port=port,
|
|
36
|
+
host=host,
|
|
37
|
+
db_url=db_url,
|
|
38
|
+
data_path=data_path,
|
|
39
|
+
debug=debug,
|
|
40
|
+
workers=workers,
|
|
41
|
+
timeout=timeout,
|
|
42
|
+
log_level=log_level,
|
|
43
|
+
log_file=log_file,
|
|
44
|
+
log_format=log_format,
|
|
45
|
+
)
|
|
46
|
+
app.run()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if __name__ == '__main__':
|
|
50
|
+
main()
|
|
51
|
+
|
|
File without changes
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import dill
|
|
2
|
+
import numpy as np
|
|
3
|
+
import redis
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def try_dumps(value):
|
|
7
|
+
try:
|
|
8
|
+
value_b = dill.dumps(value)
|
|
9
|
+
except:
|
|
10
|
+
value_b = dill.dumps(str(value))
|
|
11
|
+
finally:
|
|
12
|
+
return value_b
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class redisClient(object):
|
|
16
|
+
|
|
17
|
+
def __init__(self,
|
|
18
|
+
name,
|
|
19
|
+
server=None,
|
|
20
|
+
addr='redis://localhost:6379/0',
|
|
21
|
+
expire_time=172800):
|
|
22
|
+
self._r = redis.Redis.from_url(addr) if server is None else server
|
|
23
|
+
self.name = name
|
|
24
|
+
self.expire_time = int(expire_time) # 默认数据两天过期,单位秒
|
|
25
|
+
|
|
26
|
+
def delete(self):
|
|
27
|
+
self._r.delete(self.name)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class redisString(redisClient):
|
|
31
|
+
'''Redis String client'''
|
|
32
|
+
|
|
33
|
+
def set(self, value):
|
|
34
|
+
if value is not None:
|
|
35
|
+
value_b = try_dumps(value)
|
|
36
|
+
self._r.set(self.name, value_b, ex=self.expire_time)
|
|
37
|
+
|
|
38
|
+
def get(self):
|
|
39
|
+
v_b = self._r.get(self.name)
|
|
40
|
+
value = dill.loads(v_b) if v_b is not None else None
|
|
41
|
+
return value
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def data(self):
|
|
45
|
+
return self.get()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class redisList(redisClient):
|
|
49
|
+
'''Redis List client'''
|
|
50
|
+
|
|
51
|
+
def add(self, *arg):
|
|
52
|
+
arg_b = [dill.dumps(i) for i in arg]
|
|
53
|
+
self._r.rpush(self.name, *arg_b)
|
|
54
|
+
self._r.expire(self.name, self.expire_time)
|
|
55
|
+
|
|
56
|
+
def read(self, start=0, end=-1):
|
|
57
|
+
data_b = self._r.lrange(self.name, start, end)
|
|
58
|
+
data = [dill.loads(i) for i in data_b]
|
|
59
|
+
return data
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def size(self):
|
|
63
|
+
return self._r.llen(self.name)
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def data(self):
|
|
67
|
+
return self.read()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class redisSet(redisClient):
|
|
71
|
+
'''Redis Set client'''
|
|
72
|
+
|
|
73
|
+
def add(self, *arg):
|
|
74
|
+
arg_b = {dill.dumps(i) for i in arg}
|
|
75
|
+
self._r.sadd(self.name, *arg_b)
|
|
76
|
+
self._r.expire(self.name, self.expire_time)
|
|
77
|
+
|
|
78
|
+
def read(self):
|
|
79
|
+
data_b = self._r.smembers(self.name)
|
|
80
|
+
data = {dill.loads(i) for i in data_b}
|
|
81
|
+
return data
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def size(self):
|
|
85
|
+
return self._r.scard(self.name)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def data(self):
|
|
89
|
+
return self.read()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class redisZSet(redisClient):
|
|
93
|
+
'''有序集合'''
|
|
94
|
+
|
|
95
|
+
def __init__(self,
|
|
96
|
+
name,
|
|
97
|
+
server=None,
|
|
98
|
+
addr='redis://localhost:6379/0',
|
|
99
|
+
expire_time=172800):
|
|
100
|
+
super().__init__(name, server, addr, expire_time)
|
|
101
|
+
self.__score = 0
|
|
102
|
+
|
|
103
|
+
def delete(self):
|
|
104
|
+
super().delete()
|
|
105
|
+
self.__score = 0
|
|
106
|
+
|
|
107
|
+
def add(self, *elements):
|
|
108
|
+
mapping = {}
|
|
109
|
+
for ele in elements:
|
|
110
|
+
ele_b = dill.dumps(ele)
|
|
111
|
+
self.__score += 1
|
|
112
|
+
mapping.update({ele_b: self.__score})
|
|
113
|
+
self._r.zadd(self.name, mapping, nx=True) # 只添加新元素
|
|
114
|
+
self._r.expire(self.name, self.expire_time)
|
|
115
|
+
|
|
116
|
+
def read(self, start=0, end=-1):
|
|
117
|
+
data_b = self._r.zrange(self.name, start, end, withscores=False)
|
|
118
|
+
data = [dill.loads(i) for i in data_b]
|
|
119
|
+
return data
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def size(self):
|
|
123
|
+
return self._r.zcard(self.name)
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def data(self):
|
|
127
|
+
return self.read()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class redisHash(redisClient):
|
|
131
|
+
'''Redis Hash client'''
|
|
132
|
+
|
|
133
|
+
def add(self, **kw):
|
|
134
|
+
kw_b = {k: try_dumps(v) for k, v in kw.items()}
|
|
135
|
+
self._r.hmset(self.name, kw_b)
|
|
136
|
+
self._r.expire(self.name, self.expire_time)
|
|
137
|
+
|
|
138
|
+
def read(self):
|
|
139
|
+
data_b = self._r.hgetall(self.name)
|
|
140
|
+
data = {k_b.decode(): dill.loads(v_b) for k_b, v_b in data_b.items()}
|
|
141
|
+
return data
|
|
142
|
+
|
|
143
|
+
def get(self, key):
|
|
144
|
+
'''读取Hash中的一个key'''
|
|
145
|
+
v_b = self._r.hget(self.name, key)
|
|
146
|
+
value = dill.loads(v_b) if v_b is not None else None
|
|
147
|
+
return value
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def size(self):
|
|
151
|
+
return self._r.hlen(self.name)
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def data(self):
|
|
155
|
+
return self.read()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class redisArray(redisClient):
|
|
159
|
+
'''Redis np.array client'''
|
|
160
|
+
|
|
161
|
+
def __init__(self,
|
|
162
|
+
name,
|
|
163
|
+
server=None,
|
|
164
|
+
addr='redis://localhost:6379/0',
|
|
165
|
+
expire_time=172800,
|
|
166
|
+
dtype='complex128'):
|
|
167
|
+
super().__init__(name, server, addr, expire_time)
|
|
168
|
+
_r_dtype = self._r.get(f'{name}.dtype')
|
|
169
|
+
if _r_dtype is None:
|
|
170
|
+
self._r.set(f'{name}.dtype', dtype, ex=self.expire_time)
|
|
171
|
+
self.dtype = dtype
|
|
172
|
+
else:
|
|
173
|
+
self.dtype = _r_dtype
|
|
174
|
+
|
|
175
|
+
def delete(self):
|
|
176
|
+
self._r.delete(self.name)
|
|
177
|
+
self._r.delete(f'{self.name}.dtype')
|
|
178
|
+
|
|
179
|
+
def add(self, *args):
|
|
180
|
+
for arg in args:
|
|
181
|
+
buf = np.asarray(arg).astype(self.dtype).tobytes()
|
|
182
|
+
# self._r.append(self.name, buf)
|
|
183
|
+
self._r.rpush(self.name, buf)
|
|
184
|
+
self._r.expire(self.name, self.expire_time)
|
|
185
|
+
|
|
186
|
+
def read(self):
|
|
187
|
+
# buf = self._r.get(self.name)
|
|
188
|
+
buf_list = self._r.lrange(self.name, 0, -1)
|
|
189
|
+
buf = b''.join(buf_list)
|
|
190
|
+
data = np.frombuffer(buf,
|
|
191
|
+
dtype=self.dtype) if buf is not None else None
|
|
192
|
+
return data
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def size(self):
|
|
196
|
+
array = self.data
|
|
197
|
+
if array is None:
|
|
198
|
+
return 0
|
|
199
|
+
else:
|
|
200
|
+
return array.size
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def data(self):
|
|
204
|
+
return self.read()
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import bisect
|
|
2
|
+
from concurrent.futures import Future
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from itertools import chain
|
|
5
|
+
from multiprocessing import Lock
|
|
6
|
+
from queue import Queue
|
|
7
|
+
from typing import Any, Sequence
|
|
8
|
+
|
|
9
|
+
from ..scan.base import StepStatus, Tracker, _get_all_dependence
|
|
10
|
+
|
|
11
|
+
_NODEFAULT = object()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BaseDataset(Tracker):
|
|
15
|
+
"""
|
|
16
|
+
A tracker that stores the results of the steps.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
data : dict
|
|
21
|
+
The data of the results.
|
|
22
|
+
shape : tuple
|
|
23
|
+
The shape of the results.
|
|
24
|
+
ctime : datetime.datetime
|
|
25
|
+
The creation time of the tracker.
|
|
26
|
+
mtime : datetime.datetime
|
|
27
|
+
The modification time of the tracker.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
data: dict = None,
|
|
33
|
+
shape: tuple = (),
|
|
34
|
+
save_kwds: bool | Sequence[str] = True,
|
|
35
|
+
frozen_keys: tuple = (),
|
|
36
|
+
ignores: tuple = (),
|
|
37
|
+
):
|
|
38
|
+
self.ctime = datetime.utcnow()
|
|
39
|
+
self.mtime = datetime.utcnow()
|
|
40
|
+
self.data = data if data is not None else {}
|
|
41
|
+
self.cache = {}
|
|
42
|
+
self.pos = {}
|
|
43
|
+
self.timestamps = {}
|
|
44
|
+
self.iteration = {}
|
|
45
|
+
self._init_keys = list(self.data.keys())
|
|
46
|
+
self._frozen_keys = frozen_keys
|
|
47
|
+
self._ignores = ignores
|
|
48
|
+
self._key_levels = ()
|
|
49
|
+
self.depends = {}
|
|
50
|
+
self.dims = {}
|
|
51
|
+
self.vars_dims = {}
|
|
52
|
+
self.shape = shape
|
|
53
|
+
self.count = 0
|
|
54
|
+
self.save_kwds = save_kwds
|
|
55
|
+
self.queue = Queue()
|
|
56
|
+
self._queue_buffer = None
|
|
57
|
+
self._lock = Lock()
|
|
58
|
+
|
|
59
|
+
def init(self, loops: dict, functions: dict, constants: dict, graph: dict,
|
|
60
|
+
order: list):
|
|
61
|
+
"""
|
|
62
|
+
Initialize the tracker.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
loops : dict
|
|
67
|
+
The map of iterables.
|
|
68
|
+
functions : dict
|
|
69
|
+
The map of functions.
|
|
70
|
+
constants : dict
|
|
71
|
+
The map of constants.
|
|
72
|
+
graph : dict
|
|
73
|
+
The dependence graph.
|
|
74
|
+
order : list
|
|
75
|
+
The order of the dependence graph.
|
|
76
|
+
"""
|
|
77
|
+
from numpy import ndarray
|
|
78
|
+
|
|
79
|
+
self.depends = graph
|
|
80
|
+
|
|
81
|
+
for level, (keys, iters) in enumerate(loops.items()):
|
|
82
|
+
self._key_levels = self._key_levels + ((keys, level), )
|
|
83
|
+
if isinstance(keys, str):
|
|
84
|
+
keys = (keys, )
|
|
85
|
+
iters = (iters, )
|
|
86
|
+
if (len(keys) > 1 and len(iters) == 1
|
|
87
|
+
and isinstance(iters[0], ndarray) and iters[0].ndim == 2
|
|
88
|
+
and iters[0].shape[1] == len(keys)):
|
|
89
|
+
iters = iters[0]
|
|
90
|
+
for i, key in enumerate(keys):
|
|
91
|
+
self.data[key] = iters[:, i]
|
|
92
|
+
self._frozen_keys = self._frozen_keys + (key, )
|
|
93
|
+
self._init_keys.append(key)
|
|
94
|
+
continue
|
|
95
|
+
if not isinstance(iters, tuple) or len(keys) != len(iters):
|
|
96
|
+
continue
|
|
97
|
+
for key, iter in zip(keys, iters):
|
|
98
|
+
if self.depends.get(key, set()):
|
|
99
|
+
dims = set()
|
|
100
|
+
for dep in self.depends[key]:
|
|
101
|
+
if dep in self.vars_dims:
|
|
102
|
+
dims.update(set(self.vars_dims[dep]))
|
|
103
|
+
dims.add(level)
|
|
104
|
+
self.vars_dims[key] = tuple(sorted(dims))
|
|
105
|
+
else:
|
|
106
|
+
self.vars_dims[key] = (level, )
|
|
107
|
+
if level not in self.dims:
|
|
108
|
+
self.dims[level] = ()
|
|
109
|
+
self.dims[level] = self.dims[level] + (key, )
|
|
110
|
+
if key not in self.data and isinstance(iter,
|
|
111
|
+
(list, range, ndarray)):
|
|
112
|
+
if key.startswith('__'):
|
|
113
|
+
continue
|
|
114
|
+
self.data[key] = iter
|
|
115
|
+
self._frozen_keys = self._frozen_keys + (key, )
|
|
116
|
+
self._init_keys.append(key)
|
|
117
|
+
|
|
118
|
+
for key, value in constants.items():
|
|
119
|
+
if key.startswith('__'):
|
|
120
|
+
continue
|
|
121
|
+
self.data[key] = value
|
|
122
|
+
self._init_keys.append(key)
|
|
123
|
+
self.vars_dims[key] = ()
|
|
124
|
+
|
|
125
|
+
for ready in order:
|
|
126
|
+
for key in ready:
|
|
127
|
+
if key in functions:
|
|
128
|
+
deps = _get_all_dependence(key, graph)
|
|
129
|
+
dims = set()
|
|
130
|
+
for k in deps:
|
|
131
|
+
dims.update(set(self.vars_dims.get(k, ())))
|
|
132
|
+
self.vars_dims[key] = tuple(sorted(dims))
|
|
133
|
+
|
|
134
|
+
for k, v in self.vars_dims.items():
|
|
135
|
+
if len(v) == 1:
|
|
136
|
+
if v[0] in self.dims and k not in self.dims[v[0]]:
|
|
137
|
+
self.dims[v[0]] = self.dims[v[0]] + (k, )
|
|
138
|
+
elif v[0] not in self.dims:
|
|
139
|
+
self.dims[v[0]] = (k, )
|
|
140
|
+
|
|
141
|
+
def feed(self,
|
|
142
|
+
step: StepStatus,
|
|
143
|
+
dataframe: dict | Future,
|
|
144
|
+
store=False,
|
|
145
|
+
**options):
|
|
146
|
+
"""
|
|
147
|
+
Feed the results of the step to the dataset.
|
|
148
|
+
|
|
149
|
+
Parameters
|
|
150
|
+
----------
|
|
151
|
+
step : StepStatus
|
|
152
|
+
The step.
|
|
153
|
+
dataframe : dict
|
|
154
|
+
The results of the step.
|
|
155
|
+
"""
|
|
156
|
+
import numpy as np
|
|
157
|
+
|
|
158
|
+
if not store:
|
|
159
|
+
return
|
|
160
|
+
self.mtime = datetime.utcnow()
|
|
161
|
+
if not self.shape:
|
|
162
|
+
self.shape = tuple([i + 1 for i in step.pos])
|
|
163
|
+
else:
|
|
164
|
+
self.shape = tuple(
|
|
165
|
+
[max(i + 1, j) for i, j in zip(step.pos, self.shape)])
|
|
166
|
+
if self.save_kwds:
|
|
167
|
+
if isinstance(self.save_kwds, bool):
|
|
168
|
+
kwds = step.kwds
|
|
169
|
+
else:
|
|
170
|
+
kwds = {
|
|
171
|
+
key: step.kwds.get(key, np.nan)
|
|
172
|
+
for key in self.save_kwds
|
|
173
|
+
}
|
|
174
|
+
else:
|
|
175
|
+
kwds = {}
|
|
176
|
+
|
|
177
|
+
if isinstance(dataframe, dict):
|
|
178
|
+
dataframe = self._prune(dataframe)
|
|
179
|
+
self.queue.put_nowait(
|
|
180
|
+
(step.iteration, step.pos, dataframe, kwds, self.mtime))
|
|
181
|
+
self.flush()
|
|
182
|
+
|
|
183
|
+
def _prune(self, dataframe: dict[str, Any]) -> dict[str, Any]:
|
|
184
|
+
return {
|
|
185
|
+
k: v
|
|
186
|
+
for k, v in dataframe.items() if k not in self._ignores
|
|
187
|
+
and k not in self._frozen_keys and not k.startswith('__')
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
def _append(self, iteration, pos, dataframe, kwds, now):
|
|
191
|
+
for k, v in chain(kwds.items(), dataframe.items()):
|
|
192
|
+
if k in self._frozen_keys or k in self._ignores:
|
|
193
|
+
continue
|
|
194
|
+
if k.startswith('__'):
|
|
195
|
+
continue
|
|
196
|
+
if self.vars_dims.get(k, ()) == () and k not in dataframe:
|
|
197
|
+
continue
|
|
198
|
+
self.count += 1
|
|
199
|
+
if k not in self.data:
|
|
200
|
+
self.data[k] = [v]
|
|
201
|
+
if k in self.vars_dims:
|
|
202
|
+
self.pos[k] = tuple([pos[i]] for i in self.vars_dims[k])
|
|
203
|
+
else:
|
|
204
|
+
self.pos[k] = tuple([i] for i in pos)
|
|
205
|
+
self.timestamps[k] = [now.timestamp()]
|
|
206
|
+
self.iteration[k] = [iteration]
|
|
207
|
+
else:
|
|
208
|
+
if k in self.vars_dims:
|
|
209
|
+
pos_k = tuple(pos[i] for i in self.vars_dims[k])
|
|
210
|
+
if k not in dataframe and pos_k in zip(*self.pos[k]):
|
|
211
|
+
continue
|
|
212
|
+
for i, l in zip(pos_k, self.pos[k]):
|
|
213
|
+
l.append(i)
|
|
214
|
+
else:
|
|
215
|
+
for i, l in zip(pos, self.pos[k]):
|
|
216
|
+
l.append(i)
|
|
217
|
+
self.timestamps[k].append(now.timestamp())
|
|
218
|
+
self.iteration[k].append(iteration)
|
|
219
|
+
self.data[k].append(v)
|
|
220
|
+
|
|
221
|
+
def flush(self, block=False):
|
|
222
|
+
with self._lock:
|
|
223
|
+
self._flush(block=block)
|
|
224
|
+
|
|
225
|
+
def _dataframe_done(self, dataframe: Future | dict) -> bool:
|
|
226
|
+
if isinstance(dataframe, Future):
|
|
227
|
+
return dataframe.done()
|
|
228
|
+
else:
|
|
229
|
+
return all(x.done() for x in dataframe.values()
|
|
230
|
+
if isinstance(x, Future))
|
|
231
|
+
|
|
232
|
+
def _dataframe_result(self, dataframe: Future | dict) -> dict:
|
|
233
|
+
if isinstance(dataframe, Future):
|
|
234
|
+
return dataframe.result()
|
|
235
|
+
else:
|
|
236
|
+
return {
|
|
237
|
+
k: v.result() if isinstance(v, Future) else v
|
|
238
|
+
for k, v in dataframe.items()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
def _flush(self, block=False):
|
|
242
|
+
if self._queue_buffer is not None:
|
|
243
|
+
iteration, pos, dataframe, kwds, now = self._queue_buffer
|
|
244
|
+
if self._dataframe_done(dataframe) or block:
|
|
245
|
+
self._append(iteration, pos,
|
|
246
|
+
self._prune(self._dataframe_result(dataframe)),
|
|
247
|
+
kwds, now)
|
|
248
|
+
self._queue_buffer = None
|
|
249
|
+
else:
|
|
250
|
+
return
|
|
251
|
+
while not self.queue.empty():
|
|
252
|
+
iteration, pos, dataframe, kwds, now = self.queue.get()
|
|
253
|
+
if not self._dataframe_done(dataframe) and not block:
|
|
254
|
+
self._queue_buffer = (iteration, pos, dataframe, kwds, now)
|
|
255
|
+
return
|
|
256
|
+
else:
|
|
257
|
+
self._append(iteration, pos,
|
|
258
|
+
self._prune(self._dataframe_result(dataframe)),
|
|
259
|
+
kwds, now)
|
|
260
|
+
|
|
261
|
+
def _get_array(self, key, shape, count):
|
|
262
|
+
import numpy as np
|
|
263
|
+
|
|
264
|
+
if key in self.vars_dims:
|
|
265
|
+
shape = tuple([shape[i] for i in self.vars_dims[key]])
|
|
266
|
+
|
|
267
|
+
data, data_shape, data_count = self.cache.get(key, (None, (), 0))
|
|
268
|
+
if (data_shape, data_count) == (shape, count):
|
|
269
|
+
return data
|
|
270
|
+
try:
|
|
271
|
+
tmp = np.asarray(self.data[key])
|
|
272
|
+
if data_shape != shape:
|
|
273
|
+
data = np.full(shape + tmp.shape[1:], np.nan, dtype=tmp.dtype)
|
|
274
|
+
except:
|
|
275
|
+
tmp = self.data[key]
|
|
276
|
+
if data_shape != shape:
|
|
277
|
+
data = np.full(shape, np.nan, dtype=object)
|
|
278
|
+
try:
|
|
279
|
+
data[self.pos[key]] = tmp
|
|
280
|
+
except:
|
|
281
|
+
raise
|
|
282
|
+
self.cache[key] = (data, shape, count)
|
|
283
|
+
return data
|
|
284
|
+
|
|
285
|
+
def _get_part(self, key, skip):
|
|
286
|
+
i = bisect.bisect_left(self.iteration[key], skip)
|
|
287
|
+
pos = tuple(p[i:] for p in self.pos[key])
|
|
288
|
+
iteration = self.iteration[key][i:]
|
|
289
|
+
data = self.data[key][i:]
|
|
290
|
+
return data, iteration, pos
|
|
291
|
+
|
|
292
|
+
def keys(self):
|
|
293
|
+
"""
|
|
294
|
+
Get the keys of the dataset.
|
|
295
|
+
"""
|
|
296
|
+
self.flush()
|
|
297
|
+
return self.data.keys()
|
|
298
|
+
|
|
299
|
+
def values(self):
|
|
300
|
+
"""
|
|
301
|
+
Get the values of the dataset.
|
|
302
|
+
"""
|
|
303
|
+
self.flush()
|
|
304
|
+
return [self[k] for k in self.data]
|
|
305
|
+
|
|
306
|
+
def items(self):
|
|
307
|
+
"""
|
|
308
|
+
Get the items of the dataset.
|
|
309
|
+
"""
|
|
310
|
+
self.flush()
|
|
311
|
+
return list(zip(self.keys(), self.values()))
|
|
312
|
+
|
|
313
|
+
def get(self, key, default=_NODEFAULT, skip=None, block=False):
|
|
314
|
+
"""
|
|
315
|
+
Get the value of the dataset.
|
|
316
|
+
"""
|
|
317
|
+
self.flush(block)
|
|
318
|
+
if key in self._init_keys:
|
|
319
|
+
return self.data[key]
|
|
320
|
+
elif key in self.data:
|
|
321
|
+
if skip is None:
|
|
322
|
+
return self._get_array(key, self.shape, self.count)
|
|
323
|
+
else:
|
|
324
|
+
return self._get_part(key, skip)
|
|
325
|
+
elif default is _NODEFAULT:
|
|
326
|
+
raise KeyError(key)
|
|
327
|
+
else:
|
|
328
|
+
return default
|
|
329
|
+
|
|
330
|
+
def __getitem__(self, key):
|
|
331
|
+
return self.get(key)
|
|
332
|
+
|
|
333
|
+
def __getstate__(self):
|
|
334
|
+
self.flush()
|
|
335
|
+
data = dict(self.items())
|
|
336
|
+
return {
|
|
337
|
+
'data': data,
|
|
338
|
+
'pos': self.pos,
|
|
339
|
+
'timestamps': self.timestamps,
|
|
340
|
+
'iteration': self.iteration,
|
|
341
|
+
'depends': self.depends,
|
|
342
|
+
'shape': self.shape,
|
|
343
|
+
'dims': self.dims,
|
|
344
|
+
'vars_dims': self.vars_dims,
|
|
345
|
+
'ctime': self.ctime,
|
|
346
|
+
'mtime': self.mtime,
|
|
347
|
+
'_init_keys': self._init_keys,
|
|
348
|
+
'_frozen_keys': self._frozen_keys,
|
|
349
|
+
'_ignores': self._ignores,
|
|
350
|
+
'_key_levels': self._key_levels,
|
|
351
|
+
'save_kwds': self.save_kwds,
|
|
352
|
+
}
|
qulab/storage/chunk.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import zlib
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
DATAPATH = Path.home() / 'data'
|
|
6
|
+
CHUNKSIZE = 1024 * 1024 * 4 # 4 MB
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def set_data_path(base_path: str) -> None:
|
|
10
|
+
global DATAPATH
|
|
11
|
+
DATAPATH = Path(base_path)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_data_path() -> Path:
|
|
15
|
+
return DATAPATH
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def save_chunk(data: bytes, compressed: bool = False) -> tuple[str, str]:
|
|
19
|
+
if compressed:
|
|
20
|
+
data = zlib.compress(data)
|
|
21
|
+
hashstr = hashlib.sha1(data).hexdigest()
|
|
22
|
+
file = get_data_path(
|
|
23
|
+
) / 'chunks' / hashstr[:2] / hashstr[2:4] / hashstr[4:]
|
|
24
|
+
file.parent.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
with open(file, 'wb') as f:
|
|
26
|
+
f.write(data)
|
|
27
|
+
return str('/'.join(file.parts[-4:])), len(data)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def load_chunk(file: str, compressed: bool = False) -> bytes:
|
|
31
|
+
if file.startswith('chunks/'):
|
|
32
|
+
with open(get_data_path() / file, 'rb') as f:
|
|
33
|
+
data = f.read()
|
|
34
|
+
elif file.startswith('packs/'):
|
|
35
|
+
*filepath, start, size = file.split('/')
|
|
36
|
+
filepath = '/'.join(filepath)
|
|
37
|
+
with open(get_data_path() / filepath, 'rb') as f:
|
|
38
|
+
f.seek(int(start))
|
|
39
|
+
data = f.read(int(size))
|
|
40
|
+
else:
|
|
41
|
+
raise ValueError('Invalid file path: ' + file)
|
|
42
|
+
if compressed:
|
|
43
|
+
data = zlib.decompress(data)
|
|
44
|
+
return data
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def pack_chunk(pack: str, chunkfile: str) -> str:
|
|
48
|
+
pack = get_data_path() / 'packs' / pack
|
|
49
|
+
pack.parent.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
with open(pack, 'ab') as f:
|
|
51
|
+
buf = load_chunk(chunkfile)
|
|
52
|
+
start = f.tell()
|
|
53
|
+
size = len(buf)
|
|
54
|
+
f.write(buf)
|
|
55
|
+
return str('/'.join(pack.parts[-2:])) + '/' + str(start) + '/' + str(size)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def delete_chunk(file: str):
|
|
59
|
+
file = get_data_path() / file
|
|
60
|
+
file.unlink()
|