QuLab 2.10.10__cp313-cp313-macosx_10_13_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/__init__.py +33 -0
- qulab/__main__.py +4 -0
- qulab/cli/__init__.py +0 -0
- qulab/cli/commands.py +30 -0
- qulab/cli/config.py +170 -0
- qulab/cli/decorators.py +28 -0
- qulab/dicttree.py +523 -0
- qulab/executor/__init__.py +5 -0
- qulab/executor/analyze.py +188 -0
- qulab/executor/cli.py +434 -0
- qulab/executor/load.py +563 -0
- qulab/executor/registry.py +185 -0
- qulab/executor/schedule.py +543 -0
- qulab/executor/storage.py +615 -0
- qulab/executor/template.py +259 -0
- qulab/executor/utils.py +194 -0
- qulab/expression.py +827 -0
- qulab/fun.cpython-313-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 +115 -0
- qulab/monitor/ploter.py +123 -0
- qulab/monitor/qt_compat.py +16 -0
- qulab/monitor/toolbar.py +265 -0
- qulab/scan/__init__.py +2 -0
- qulab/scan/curd.py +221 -0
- qulab/scan/models.py +554 -0
- qulab/scan/optimize.py +76 -0
- qulab/scan/query.py +387 -0
- qulab/scan/record.py +603 -0
- qulab/scan/scan.py +1166 -0
- qulab/scan/server.py +450 -0
- qulab/scan/space.py +213 -0
- qulab/scan/utils.py +234 -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 +2 -0
- qulab/sys/chat.py +688 -0
- qulab/sys/device/__init__.py +3 -0
- qulab/sys/device/basedevice.py +255 -0
- qulab/sys/device/loader.py +86 -0
- qulab/sys/device/utils.py +79 -0
- qulab/sys/drivers/FakeInstrument.py +68 -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 +194 -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/router.py +35 -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 +227 -0
- qulab/tools/__init__.py +0 -0
- qulab/tools/connection_helper.py +39 -0
- qulab/typing.py +2 -0
- qulab/utils.py +95 -0
- qulab/version.py +1 -0
- qulab/visualization/__init__.py +188 -0
- qulab/visualization/__main__.py +71 -0
- qulab/visualization/_autoplot.py +464 -0
- qulab/visualization/plot_circ.py +319 -0
- qulab/visualization/plot_layout.py +408 -0
- qulab/visualization/plot_seq.py +242 -0
- qulab/visualization/qdat.py +152 -0
- qulab/visualization/rot3d.py +23 -0
- qulab/visualization/widgets.py +86 -0
- qulab-2.10.10.dist-info/METADATA +110 -0
- qulab-2.10.10.dist-info/RECORD +107 -0
- qulab-2.10.10.dist-info/WHEEL +5 -0
- qulab-2.10.10.dist-info/entry_points.txt +2 -0
- qulab-2.10.10.dist-info/licenses/LICENSE +21 -0
- qulab-2.10.10.dist-info/top_level.txt +1 -0
qulab/scan/record.py
ADDED
@@ -0,0 +1,603 @@
|
|
1
|
+
import itertools
|
2
|
+
import lzma
|
3
|
+
import pickle
|
4
|
+
import sys
|
5
|
+
import uuid
|
6
|
+
import zipfile
|
7
|
+
from pathlib import Path
|
8
|
+
from threading import Lock
|
9
|
+
from types import EllipsisType
|
10
|
+
|
11
|
+
import dill
|
12
|
+
import numpy as np
|
13
|
+
import zmq
|
14
|
+
|
15
|
+
from qulab.sys.rpc.zmq_socket import ZMQContextManager
|
16
|
+
|
17
|
+
from .curd import get_config
|
18
|
+
from .space import OptimizeSpace, Space
|
19
|
+
|
20
|
+
_not_given = object()
|
21
|
+
|
22
|
+
|
23
|
+
def random_path(base):
|
24
|
+
while True:
|
25
|
+
s = uuid.uuid4().hex
|
26
|
+
path = base / s[:2] / s[2:4] / s[4:6] / s[6:]
|
27
|
+
if not path.exists():
|
28
|
+
return path
|
29
|
+
|
30
|
+
|
31
|
+
def index_in_slice(slice_obj: slice | int, index: int):
|
32
|
+
if isinstance(slice_obj, int):
|
33
|
+
return slice_obj == index
|
34
|
+
start, stop, step = slice_obj.start, slice_obj.stop, slice_obj.step
|
35
|
+
if start is None:
|
36
|
+
start = 0
|
37
|
+
if step is None:
|
38
|
+
step = 1
|
39
|
+
if stop is None:
|
40
|
+
stop = sys.maxsize
|
41
|
+
|
42
|
+
if step > 0:
|
43
|
+
return start <= index < stop and (index - start) % step == 0
|
44
|
+
else:
|
45
|
+
return stop < index <= start and (index - start) % step == 0
|
46
|
+
|
47
|
+
|
48
|
+
class BufferList():
|
49
|
+
|
50
|
+
def __init__(self, file=None, slice=None):
|
51
|
+
self._list = []
|
52
|
+
self.lu = ()
|
53
|
+
self.rd = ()
|
54
|
+
self.inner_shape = ()
|
55
|
+
self.file = file
|
56
|
+
self._slice = slice
|
57
|
+
self._lock = Lock()
|
58
|
+
self._data_id = None
|
59
|
+
self._sock = None
|
60
|
+
|
61
|
+
def __repr__(self):
|
62
|
+
return f"<BufferList: shape={self.shape}, lu={self.lu}, rd={self.rd}, slice={self._slice}>"
|
63
|
+
|
64
|
+
def __getstate__(self):
|
65
|
+
self.flush()
|
66
|
+
if isinstance(self.file, Path):
|
67
|
+
file = '/'.join(self.file.parts[-4:])
|
68
|
+
else:
|
69
|
+
file = self.file
|
70
|
+
return {
|
71
|
+
'file': file,
|
72
|
+
'lu': self.lu,
|
73
|
+
'rd': self.rd,
|
74
|
+
'inner_shape': self.inner_shape,
|
75
|
+
}
|
76
|
+
|
77
|
+
def __setstate__(self, state):
|
78
|
+
self.file = state['file']
|
79
|
+
self.lu = state['lu']
|
80
|
+
self.rd = state['rd']
|
81
|
+
self.inner_shape = state['inner_shape']
|
82
|
+
self._list = []
|
83
|
+
self._slice = None
|
84
|
+
self._lock = Lock()
|
85
|
+
self._data_id = None
|
86
|
+
self._sock = None
|
87
|
+
|
88
|
+
@property
|
89
|
+
def shape(self):
|
90
|
+
return tuple([i - j
|
91
|
+
for i, j in zip(self.rd, self.lu)]) + self.inner_shape
|
92
|
+
|
93
|
+
def flush(self):
|
94
|
+
if not self._list:
|
95
|
+
return
|
96
|
+
if isinstance(self.file, Path):
|
97
|
+
with self._lock:
|
98
|
+
with open(self.file, 'ab') as f:
|
99
|
+
for item in self._list:
|
100
|
+
dill.dump(item, f)
|
101
|
+
self._list.clear()
|
102
|
+
|
103
|
+
def delete(self):
|
104
|
+
if isinstance(self.file, Path):
|
105
|
+
self.file.unlink()
|
106
|
+
self.file = None
|
107
|
+
|
108
|
+
def append(self, pos, value, dims=None):
|
109
|
+
if dims is not None:
|
110
|
+
if any([p != 0 for i, p in enumerate(pos) if i not in dims]):
|
111
|
+
return
|
112
|
+
pos = tuple([pos[i] for i in dims])
|
113
|
+
self.lu = tuple([min(i, j) for i, j in zip(pos, self.lu)])
|
114
|
+
self.rd = tuple([max(i + 1, j) for i, j in zip(pos, self.rd)])
|
115
|
+
if hasattr(value, 'shape'):
|
116
|
+
if self.inner_shape is None:
|
117
|
+
self.inner_shape = value.shape
|
118
|
+
elif self.inner_shape != value.shape:
|
119
|
+
self.inner_shape = ()
|
120
|
+
with self._lock:
|
121
|
+
self._list.append((pos, value))
|
122
|
+
if len(self._list) > 1000:
|
123
|
+
self.flush()
|
124
|
+
|
125
|
+
def _iter_file(self):
|
126
|
+
if isinstance(self.file, Path) and self.file.exists():
|
127
|
+
with self._lock:
|
128
|
+
with open(self.file, 'rb') as f:
|
129
|
+
while True:
|
130
|
+
try:
|
131
|
+
pos, value = dill.load(f)
|
132
|
+
yield pos, value
|
133
|
+
except EOFError:
|
134
|
+
break
|
135
|
+
elif isinstance(
|
136
|
+
self.file, tuple) and len(self.file) == 2 and isinstance(
|
137
|
+
self.file[0], str) and self.file[0].endswith('.zip'):
|
138
|
+
f, name = self.file
|
139
|
+
with zipfile.ZipFile(f, 'r') as z:
|
140
|
+
with z.open(name, 'r') as f:
|
141
|
+
while True:
|
142
|
+
try:
|
143
|
+
pos, value = dill.load(f)
|
144
|
+
yield pos, value
|
145
|
+
except EOFError:
|
146
|
+
break
|
147
|
+
|
148
|
+
def iter(self):
|
149
|
+
if self._data_id is None:
|
150
|
+
for pos, value in itertools.chain(self._iter_file(), self._list):
|
151
|
+
if not self._slice:
|
152
|
+
yield pos, value
|
153
|
+
elif all(
|
154
|
+
[index_in_slice(s, i) for s, i in zip(self._slice, pos)]):
|
155
|
+
if self.inner_shape:
|
156
|
+
yield pos, value[self._slice[len(pos):]]
|
157
|
+
else:
|
158
|
+
yield pos, value
|
159
|
+
else:
|
160
|
+
server, record_id, key = self._data_id
|
161
|
+
with ZMQContextManager(zmq.DEALER,
|
162
|
+
connect=server,
|
163
|
+
socket=self._sock) as socket:
|
164
|
+
iter_id = b''
|
165
|
+
start = 0
|
166
|
+
try:
|
167
|
+
while True:
|
168
|
+
socket.send_pyobj({
|
169
|
+
'method': 'bufferlist_iter',
|
170
|
+
'record_id': record_id,
|
171
|
+
'iter_id': iter_id,
|
172
|
+
'key': key,
|
173
|
+
'slice': self._slice,
|
174
|
+
'start': start
|
175
|
+
})
|
176
|
+
iter_id, lst, finished = socket.recv_pyobj()
|
177
|
+
start += len(lst)
|
178
|
+
yield from lst
|
179
|
+
if finished:
|
180
|
+
break
|
181
|
+
finally:
|
182
|
+
if iter_id:
|
183
|
+
socket.send_pyobj({
|
184
|
+
'method': 'bufferlist_iter_exit',
|
185
|
+
'iter_id': iter_id
|
186
|
+
})
|
187
|
+
|
188
|
+
def value(self):
|
189
|
+
d = []
|
190
|
+
for pos, value in self.iter():
|
191
|
+
d.append(value)
|
192
|
+
return d
|
193
|
+
|
194
|
+
def pos(self):
|
195
|
+
p = []
|
196
|
+
for pos, value in self.iter():
|
197
|
+
p.append(pos)
|
198
|
+
return p
|
199
|
+
|
200
|
+
def items(self):
|
201
|
+
p, d = [], []
|
202
|
+
for pos, value in self.iter():
|
203
|
+
p.append(pos)
|
204
|
+
d.append(value)
|
205
|
+
return p, d
|
206
|
+
|
207
|
+
def toarray(self):
|
208
|
+
pos, data = self.items()
|
209
|
+
if self._slice:
|
210
|
+
pos = np.asarray(pos)
|
211
|
+
lu = tuple(np.min(pos, axis=0))
|
212
|
+
rd = tuple(np.max(pos, axis=0) + 1)
|
213
|
+
pos = np.asarray(pos) - np.asarray(lu)
|
214
|
+
shape = []
|
215
|
+
for k, (s, i, j) in enumerate(zip(self._slice, rd, lu)):
|
216
|
+
if s.step is not None:
|
217
|
+
pos[:, k] = pos[:, k] / s.step
|
218
|
+
shape.append(round(np.ceil((i - j) / s.step)))
|
219
|
+
else:
|
220
|
+
shape.append(i - j)
|
221
|
+
shape = tuple(shape)
|
222
|
+
else:
|
223
|
+
shape = tuple([i - j for i, j in zip(self.rd, self.lu)])
|
224
|
+
pos = np.asarray(pos) - np.asarray(self.lu)
|
225
|
+
data = np.asarray(data)
|
226
|
+
inner_shape = data.shape[1:]
|
227
|
+
x = np.full(shape + inner_shape, np.nan, dtype=data[0].dtype)
|
228
|
+
x.__setitem__(tuple(pos.T), data)
|
229
|
+
return x
|
230
|
+
|
231
|
+
def _full_slice(self, slice_tuple: slice
|
232
|
+
| tuple[slice | int | EllipsisType, ...]):
|
233
|
+
ndim = len(self.lu)
|
234
|
+
if self.inner_shape:
|
235
|
+
ndim += len(self.inner_shape)
|
236
|
+
|
237
|
+
if isinstance(slice_tuple, slice):
|
238
|
+
slice_tuple = (
|
239
|
+
slice_tuple, ) + (slice(0, sys.maxsize, 1), ) * (ndim - 1)
|
240
|
+
if slice_tuple is Ellipsis:
|
241
|
+
slice_tuple = (slice(0, sys.maxsize, 1), ) * ndim
|
242
|
+
else:
|
243
|
+
head, tail = (), ()
|
244
|
+
for i, s in enumerate(slice_tuple):
|
245
|
+
if s is Ellipsis:
|
246
|
+
head = slice_tuple[:i]
|
247
|
+
tail = slice_tuple[i + 1:]
|
248
|
+
break
|
249
|
+
else:
|
250
|
+
head = slice_tuple
|
251
|
+
tail = ()
|
252
|
+
slice_tuple = head + (slice(
|
253
|
+
0, sys.maxsize, 1), ) * (ndim - len(head) - len(tail)) + tail
|
254
|
+
slice_list = []
|
255
|
+
contract = []
|
256
|
+
reversed = []
|
257
|
+
for i, s in enumerate(slice_tuple):
|
258
|
+
if isinstance(s, int):
|
259
|
+
if s >= 0:
|
260
|
+
slice_list.append(slice(s, s + 1, 1))
|
261
|
+
elif i < len(self.lu):
|
262
|
+
s = self.rd[i] + s
|
263
|
+
slice_list.append(slice(s, s + 1, 1))
|
264
|
+
else:
|
265
|
+
slice_list.append(slice(s, s - 1, -1))
|
266
|
+
contract.append(i)
|
267
|
+
else:
|
268
|
+
start, stop, step = s.start, s.stop, s.step
|
269
|
+
if step is None:
|
270
|
+
step = 1
|
271
|
+
if step < 0 and i < len(self.lu):
|
272
|
+
step = -step
|
273
|
+
reversed.append(i)
|
274
|
+
if start is None and stop is None:
|
275
|
+
start, stop = 0, sys.maxsize
|
276
|
+
elif start is None:
|
277
|
+
start, stop = self.lu[i], sys.maxsize
|
278
|
+
elif stop is None:
|
279
|
+
start, stop = 0, start + self.lu[i]
|
280
|
+
else:
|
281
|
+
start, stop = stop + self.lu[i] + 1, start + self.lu[
|
282
|
+
i] + 1
|
283
|
+
|
284
|
+
if start is None:
|
285
|
+
start = 0
|
286
|
+
elif start < 0 and i < len(self.lu):
|
287
|
+
start = self.rd[i] + start
|
288
|
+
if step is None:
|
289
|
+
step = 1
|
290
|
+
if stop is None:
|
291
|
+
stop = sys.maxsize
|
292
|
+
elif stop < 0 and i < len(self.lu):
|
293
|
+
stop = self.rd[i] + stop
|
294
|
+
|
295
|
+
slice_list.append(slice(start, stop, step))
|
296
|
+
return tuple(slice_list), contract, reversed
|
297
|
+
|
298
|
+
def __getitem__(self, slice_tuple: slice | EllipsisType
|
299
|
+
| tuple[slice | int | EllipsisType, ...]):
|
300
|
+
self._slice, contract, reversed = self._full_slice(slice_tuple)
|
301
|
+
ret = self.toarray()
|
302
|
+
slices = []
|
303
|
+
for i, s in enumerate(self._slice):
|
304
|
+
if i in contract:
|
305
|
+
slices.append(0)
|
306
|
+
elif isinstance(s, slice):
|
307
|
+
if i in reversed:
|
308
|
+
slices.append(slice(None, None, -1))
|
309
|
+
else:
|
310
|
+
slices.append(slice(None, None, 1))
|
311
|
+
ret = ret.__getitem__(tuple(slices))
|
312
|
+
self._slice = None
|
313
|
+
return ret
|
314
|
+
|
315
|
+
|
316
|
+
class Record():
|
317
|
+
|
318
|
+
def __init__(self, id, database, description=None):
|
319
|
+
self.id = id
|
320
|
+
self.database = database
|
321
|
+
self.description = description
|
322
|
+
self._items = {}
|
323
|
+
self._pos = []
|
324
|
+
self._last_vars = set()
|
325
|
+
self._file = None
|
326
|
+
self._sock = None
|
327
|
+
|
328
|
+
for name, value in self.description['intrinsic_loops'].items():
|
329
|
+
self._items[name] = value
|
330
|
+
|
331
|
+
for level, range_list in description['loops'].items():
|
332
|
+
for name, iterable in range_list:
|
333
|
+
if name in self.description['independent_variables']:
|
334
|
+
self._items[name] = iterable
|
335
|
+
|
336
|
+
if self.is_local_record():
|
337
|
+
self.database = Path(self.database)
|
338
|
+
self._file = random_path(self.database / 'objects')
|
339
|
+
self._file.parent.mkdir(parents=True, exist_ok=True)
|
340
|
+
|
341
|
+
def __getstate__(self) -> dict:
|
342
|
+
return {
|
343
|
+
'id': self.id,
|
344
|
+
'description': self.description,
|
345
|
+
'_items': self._items,
|
346
|
+
}
|
347
|
+
|
348
|
+
def __setstate__(self, state: dict):
|
349
|
+
self.id = state['id']
|
350
|
+
self.description = state['description']
|
351
|
+
self._items = state['_items']
|
352
|
+
self._pos = []
|
353
|
+
self._last_vars = set()
|
354
|
+
self.database = None
|
355
|
+
self._file = None
|
356
|
+
self._sock = None
|
357
|
+
|
358
|
+
@property
|
359
|
+
def axis(self):
|
360
|
+
return self.description.get('axis', {})
|
361
|
+
|
362
|
+
def config(self, cls=dict, session=None, datapath=None):
|
363
|
+
config_id = self.description.get('config', None)
|
364
|
+
if config_id is None:
|
365
|
+
return None
|
366
|
+
if isinstance(config_id, dict):
|
367
|
+
return cls(config_id)
|
368
|
+
if self.is_remote_record():
|
369
|
+
with ZMQContextManager(zmq.DEALER,
|
370
|
+
connect=self.database,
|
371
|
+
socket=self._sock) as socket:
|
372
|
+
socket.send_pyobj({
|
373
|
+
'method': 'config_get',
|
374
|
+
'config_id': config_id
|
375
|
+
})
|
376
|
+
buf = socket.recv_pyobj()
|
377
|
+
return cls(pickle.loads(lzma.decompress(buf)))
|
378
|
+
else:
|
379
|
+
assert session is not None, "session is required for local record"
|
380
|
+
assert datapath is not None, "datapath is required for local record"
|
381
|
+
config = get_config(session, config_id, base=datapath / 'objects')
|
382
|
+
session.commit()
|
383
|
+
return cls(config)
|
384
|
+
|
385
|
+
def scripts(self, session=None):
|
386
|
+
scripts = self.description['entry']['scripts']
|
387
|
+
if isinstance(scripts, list):
|
388
|
+
return scripts
|
389
|
+
else:
|
390
|
+
cell_id = scripts
|
391
|
+
|
392
|
+
if self.is_remote_record():
|
393
|
+
with ZMQContextManager(zmq.DEALER,
|
394
|
+
connect=self.database,
|
395
|
+
socket=self._sock) as socket:
|
396
|
+
socket.send_pyobj({
|
397
|
+
'method': 'notebook_history',
|
398
|
+
'cell_id': cell_id
|
399
|
+
})
|
400
|
+
return socket.recv_pyobj()
|
401
|
+
elif self.is_local_record():
|
402
|
+
from .models import Cell
|
403
|
+
assert session is not None, "session is required for local record"
|
404
|
+
cell = session.get(Cell, cell_id)
|
405
|
+
return [
|
406
|
+
cell.input.text
|
407
|
+
for cell in cell.notebook.cells[1:cell.index + 2]
|
408
|
+
]
|
409
|
+
|
410
|
+
def is_local_record(self):
|
411
|
+
return not self.is_cache_record() and not self.is_remote_record()
|
412
|
+
|
413
|
+
def is_cache_record(self):
|
414
|
+
return self.database is None
|
415
|
+
|
416
|
+
def is_remote_record(self):
|
417
|
+
return isinstance(self.database,
|
418
|
+
str) and self.database.startswith("tcp://")
|
419
|
+
|
420
|
+
def __del__(self):
|
421
|
+
self.flush()
|
422
|
+
|
423
|
+
def __getitem__(self, key):
|
424
|
+
return self.get(key, buffer_to_array=True)
|
425
|
+
|
426
|
+
def get(self, key, default=_not_given, buffer_to_array=False):
|
427
|
+
if self.is_remote_record():
|
428
|
+
with ZMQContextManager(zmq.DEALER,
|
429
|
+
connect=self.database,
|
430
|
+
socket=self._sock) as socket:
|
431
|
+
socket.send_pyobj({
|
432
|
+
'method': 'record_getitem',
|
433
|
+
'record_id': self.id,
|
434
|
+
'key': key
|
435
|
+
})
|
436
|
+
ret = socket.recv_pyobj()
|
437
|
+
if isinstance(ret, BufferList):
|
438
|
+
ret._data_id = self.database, self.id, key
|
439
|
+
ret._sock = socket
|
440
|
+
if buffer_to_array:
|
441
|
+
return ret.toarray()
|
442
|
+
else:
|
443
|
+
return ret
|
444
|
+
elif isinstance(ret, Space) and buffer_to_array:
|
445
|
+
return ret.toarray()
|
446
|
+
else:
|
447
|
+
return ret
|
448
|
+
else:
|
449
|
+
if default is _not_given:
|
450
|
+
d = self._items.get(key)
|
451
|
+
else:
|
452
|
+
d = self._items.get(key, default)
|
453
|
+
if isinstance(d, BufferList):
|
454
|
+
if isinstance(d.file, str):
|
455
|
+
d.file = self._file.parent.parent.parent.parent / d.file
|
456
|
+
if buffer_to_array:
|
457
|
+
return d.toarray()
|
458
|
+
else:
|
459
|
+
return d
|
460
|
+
elif isinstance(d, Space):
|
461
|
+
if buffer_to_array:
|
462
|
+
return d.toarray()
|
463
|
+
else:
|
464
|
+
return d
|
465
|
+
else:
|
466
|
+
return d
|
467
|
+
|
468
|
+
def keys(self):
|
469
|
+
if self.is_remote_record():
|
470
|
+
with ZMQContextManager(zmq.DEALER,
|
471
|
+
connect=self.database,
|
472
|
+
socket=self._sock) as socket:
|
473
|
+
socket.send_pyobj({
|
474
|
+
'method': 'record_keys',
|
475
|
+
'record_id': self.id
|
476
|
+
})
|
477
|
+
keys = socket.recv_pyobj()
|
478
|
+
self._items = {key: None for key in keys}
|
479
|
+
return keys
|
480
|
+
else:
|
481
|
+
return list(self._items.keys())
|
482
|
+
|
483
|
+
def append(self, level, step, position, variables):
|
484
|
+
if level < 0:
|
485
|
+
self.flush()
|
486
|
+
return
|
487
|
+
|
488
|
+
for key in set(variables.keys()) - self._last_vars:
|
489
|
+
if key not in self.axis:
|
490
|
+
self.axis[key] = tuple(range(level + 1))
|
491
|
+
|
492
|
+
self._last_vars = set(variables.keys())
|
493
|
+
|
494
|
+
if level >= len(self._pos):
|
495
|
+
l = level + 1 - len(self._pos)
|
496
|
+
self._pos.extend(([0] * (l - 1)) + [position])
|
497
|
+
pos = tuple(self._pos)
|
498
|
+
elif level == len(self._pos) - 1:
|
499
|
+
self._pos[-1] = position
|
500
|
+
pos = tuple(self._pos)
|
501
|
+
else:
|
502
|
+
self._pos = self._pos[:level + 1]
|
503
|
+
self._pos[-1] = position
|
504
|
+
pos = tuple(self._pos)
|
505
|
+
self._pos[-1] += 1
|
506
|
+
|
507
|
+
for key, value in variables.items():
|
508
|
+
if self.axis[key] == ():
|
509
|
+
if key not in self._items:
|
510
|
+
self._items[key] = value
|
511
|
+
elif level == self.axis[key][-1]:
|
512
|
+
if key not in self._items:
|
513
|
+
if self.is_local_record():
|
514
|
+
bufferlist_file = random_path(self.database /
|
515
|
+
'objects')
|
516
|
+
bufferlist_file.parent.mkdir(parents=True,
|
517
|
+
exist_ok=True)
|
518
|
+
self._items[key] = BufferList(bufferlist_file)
|
519
|
+
else:
|
520
|
+
self._items[key] = BufferList()
|
521
|
+
self._items[key].lu = pos
|
522
|
+
self._items[key].rd = tuple([i + 1 for i in pos])
|
523
|
+
self._items[key].append(pos, value, self.axis[key])
|
524
|
+
elif isinstance(self._items[key], BufferList):
|
525
|
+
self._items[key].append(pos, value, self.axis[key])
|
526
|
+
|
527
|
+
def flush(self):
|
528
|
+
if self.is_remote_record() or self.is_cache_record():
|
529
|
+
return
|
530
|
+
|
531
|
+
for key, value in self._items.items():
|
532
|
+
if isinstance(value, BufferList):
|
533
|
+
value.flush()
|
534
|
+
|
535
|
+
with open(self._file, 'wb') as f:
|
536
|
+
dill.dump(self, f)
|
537
|
+
|
538
|
+
def delete(self):
|
539
|
+
if self.is_remote_record():
|
540
|
+
with ZMQContextManager(zmq.DEALER,
|
541
|
+
connect=self.database,
|
542
|
+
socket=self._sock) as socket:
|
543
|
+
socket.send_pyobj({
|
544
|
+
'method': 'record_delete',
|
545
|
+
'record_id': self.id
|
546
|
+
})
|
547
|
+
elif self.is_local_record():
|
548
|
+
for key, value in self._items.items():
|
549
|
+
if isinstance(value, BufferList):
|
550
|
+
value.delete()
|
551
|
+
self._file.unlink()
|
552
|
+
|
553
|
+
def export(self, file):
|
554
|
+
with zipfile.ZipFile(file,
|
555
|
+
'w',
|
556
|
+
compression=zipfile.ZIP_DEFLATED,
|
557
|
+
compresslevel=9) as z:
|
558
|
+
items = {}
|
559
|
+
for key in self.keys():
|
560
|
+
value = self.get(key)
|
561
|
+
if isinstance(value, BufferList):
|
562
|
+
v = BufferList()
|
563
|
+
v.lu = value.lu
|
564
|
+
v.rd = value.rd
|
565
|
+
v.inner_shape = value.inner_shape
|
566
|
+
items[key] = v
|
567
|
+
with z.open(f'{key}.buf', 'w') as f:
|
568
|
+
for pos, data in value.iter():
|
569
|
+
dill.dump((pos, data), f)
|
570
|
+
else:
|
571
|
+
items[key] = value
|
572
|
+
with z.open('record.pkl', 'w') as f:
|
573
|
+
self.description['entry']['scripts'] = self.scripts()
|
574
|
+
dill.dump((self.description, items), f)
|
575
|
+
with z.open('config.pkl', 'w') as f:
|
576
|
+
f.write(dill.dumps(self.config()))
|
577
|
+
|
578
|
+
@classmethod
|
579
|
+
def load(cls, file: str):
|
580
|
+
with zipfile.ZipFile(file, 'r') as z:
|
581
|
+
with z.open('record.pkl', 'r') as f:
|
582
|
+
description, items = dill.load(f)
|
583
|
+
with z.open('config.pkl', 'r') as f:
|
584
|
+
config = dill.load(f)
|
585
|
+
description['config'] = config
|
586
|
+
record = cls(None, None, description)
|
587
|
+
for key, value in items.items():
|
588
|
+
if isinstance(value, BufferList):
|
589
|
+
value.file = file, f'{key}.buf'
|
590
|
+
record._items[key] = value
|
591
|
+
return record
|
592
|
+
|
593
|
+
def __repr__(self):
|
594
|
+
if self.is_remote_record() and not self._items:
|
595
|
+
self._items = {key: None for key in self.keys()}
|
596
|
+
return f"<Record: id={self.id} app={self.description['app']}, keys={self._items.keys()}>"
|
597
|
+
|
598
|
+
# def _repr_html_(self):
|
599
|
+
# return f"""
|
600
|
+
# <h3>Record: id={self.id}, app={self.description['app']}</h3>
|
601
|
+
# <p>keys={self.keys()}</p>
|
602
|
+
# <p>axis={self.axis}</p>
|
603
|
+
# """
|