qcodes 0.1.0__py3-none-any.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.
Files changed (128) hide show
  1. qcodes/__init__.py +65 -0
  2. qcodes/actions.py +207 -0
  3. qcodes/config/__init__.py +2 -0
  4. qcodes/config/config.py +396 -0
  5. qcodes/config/qcodesrc.json +11 -0
  6. qcodes/config/qcodesrc_schema.json +55 -0
  7. qcodes/data/__init__.py +0 -0
  8. qcodes/data/data_array.py +520 -0
  9. qcodes/data/data_set.py +803 -0
  10. qcodes/data/format.py +309 -0
  11. qcodes/data/gnuplot_format.py +366 -0
  12. qcodes/data/hdf5_format.py +385 -0
  13. qcodes/data/io.py +266 -0
  14. qcodes/data/location.py +166 -0
  15. qcodes/data/manager.py +163 -0
  16. qcodes/instrument/__init__.py +0 -0
  17. qcodes/instrument/base.py +630 -0
  18. qcodes/instrument/function.py +134 -0
  19. qcodes/instrument/ip.py +236 -0
  20. qcodes/instrument/metaclass.py +45 -0
  21. qcodes/instrument/mock.py +278 -0
  22. qcodes/instrument/parameter.py +1001 -0
  23. qcodes/instrument/remote.py +529 -0
  24. qcodes/instrument/server.py +189 -0
  25. qcodes/instrument/sweep_values.py +286 -0
  26. qcodes/instrument/visa.py +198 -0
  27. qcodes/instrument_drivers/AlazarTech/ATS.py +1001 -0
  28. qcodes/instrument_drivers/AlazarTech/ATS9870.py +266 -0
  29. qcodes/instrument_drivers/AlazarTech/ATS_acquisition_controllers.py +143 -0
  30. qcodes/instrument_drivers/AlazarTech/__init__.py +0 -0
  31. qcodes/instrument_drivers/QuTech/IVVI.py +390 -0
  32. qcodes/instrument_drivers/QuTech/__init__.py +0 -0
  33. qcodes/instrument_drivers/__init__.py +0 -0
  34. qcodes/instrument_drivers/agilent/Agilent_34400A.py +155 -0
  35. qcodes/instrument_drivers/agilent/E8527D.py +79 -0
  36. qcodes/instrument_drivers/agilent/HP33210A.py +104 -0
  37. qcodes/instrument_drivers/agilent/__init__.py +0 -0
  38. qcodes/instrument_drivers/agilent/test_suite.py +96 -0
  39. qcodes/instrument_drivers/ithaco/Ithaco_1211.py +110 -0
  40. qcodes/instrument_drivers/ithaco/__init__.py +0 -0
  41. qcodes/instrument_drivers/oxford/__init__.py +0 -0
  42. qcodes/instrument_drivers/oxford/mercuryiPS.py +358 -0
  43. qcodes/instrument_drivers/oxford/triton.py +227 -0
  44. qcodes/instrument_drivers/rigol/DG4000.py +591 -0
  45. qcodes/instrument_drivers/rigol/__init__.py +0 -0
  46. qcodes/instrument_drivers/rohde_schwarz/SGS100A.py +99 -0
  47. qcodes/instrument_drivers/rohde_schwarz/ZNB20.py +154 -0
  48. qcodes/instrument_drivers/rohde_schwarz/__init__.py +0 -0
  49. qcodes/instrument_drivers/signal_hound/USB_SA124B.py +567 -0
  50. qcodes/instrument_drivers/signal_hound/__init__.py +0 -0
  51. qcodes/instrument_drivers/stanford_research/SR560.py +116 -0
  52. qcodes/instrument_drivers/stanford_research/SR830.py +314 -0
  53. qcodes/instrument_drivers/stanford_research/SR865.py +305 -0
  54. qcodes/instrument_drivers/stanford_research/__init__.py +0 -0
  55. qcodes/instrument_drivers/tektronix/AWG5014.py +1228 -0
  56. qcodes/instrument_drivers/tektronix/AWG520.py +659 -0
  57. qcodes/instrument_drivers/tektronix/Keithley_2000.py +195 -0
  58. qcodes/instrument_drivers/tektronix/Keithley_2600.py +83 -0
  59. qcodes/instrument_drivers/tektronix/Keithley_2700.py +395 -0
  60. qcodes/instrument_drivers/tektronix/__init__.py +0 -0
  61. qcodes/instrument_drivers/test.py +94 -0
  62. qcodes/instrument_drivers/weinschel/Weinschel_8320.py +21 -0
  63. qcodes/instrument_drivers/weinschel/__init__.py +0 -0
  64. qcodes/instrument_drivers/weinschel/test_suite.py +27 -0
  65. qcodes/loops.py +1009 -0
  66. qcodes/measure.py +155 -0
  67. qcodes/plots/__init__.py +0 -0
  68. qcodes/plots/base.py +280 -0
  69. qcodes/plots/colors.py +135 -0
  70. qcodes/plots/pyqtgraph.py +384 -0
  71. qcodes/plots/qcmatplotlib.py +206 -0
  72. qcodes/process/__init__.py +0 -0
  73. qcodes/process/helpers.py +60 -0
  74. qcodes/process/qcodes_process.py +72 -0
  75. qcodes/process/server.py +395 -0
  76. qcodes/process/stream_queue.py +152 -0
  77. qcodes/station.py +178 -0
  78. qcodes/test.py +119 -0
  79. qcodes/tests/__init__.py +0 -0
  80. qcodes/tests/common.py +7 -0
  81. qcodes/tests/data_mocks.py +173 -0
  82. qcodes/tests/instrument_mocks.py +327 -0
  83. qcodes/tests/py35_syntax.py +13 -0
  84. qcodes/tests/test_combined_par.py +156 -0
  85. qcodes/tests/test_command.py +120 -0
  86. qcodes/tests/test_config.py +222 -0
  87. qcodes/tests/test_data.py +668 -0
  88. qcodes/tests/test_deferred_operations.py +105 -0
  89. qcodes/tests/test_driver_testcase.py +58 -0
  90. qcodes/tests/test_format.py +414 -0
  91. qcodes/tests/test_hdf5formatter.py +333 -0
  92. qcodes/tests/test_helpers.py +630 -0
  93. qcodes/tests/test_instrument.py +981 -0
  94. qcodes/tests/test_instrument_server.py +156 -0
  95. qcodes/tests/test_json.py +48 -0
  96. qcodes/tests/test_location_provider.py +109 -0
  97. qcodes/tests/test_loop.py +825 -0
  98. qcodes/tests/test_measure.py +49 -0
  99. qcodes/tests/test_metadata.py +49 -0
  100. qcodes/tests/test_multiprocessing.py +471 -0
  101. qcodes/tests/test_nested_attrs.py +100 -0
  102. qcodes/tests/test_parameter.py +151 -0
  103. qcodes/tests/test_plots.py +44 -0
  104. qcodes/tests/test_sweep_values.py +159 -0
  105. qcodes/tests/test_validators.py +482 -0
  106. qcodes/tests/test_visa.py +184 -0
  107. qcodes/utils/__init__.py +0 -0
  108. qcodes/utils/command.py +176 -0
  109. qcodes/utils/deferred_operations.py +276 -0
  110. qcodes/utils/helpers.py +417 -0
  111. qcodes/utils/metadata.py +42 -0
  112. qcodes/utils/nested_attrs.py +177 -0
  113. qcodes/utils/reload_code.py +98 -0
  114. qcodes/utils/threading.py +72 -0
  115. qcodes/utils/timing.py +89 -0
  116. qcodes/utils/validators.py +393 -0
  117. qcodes/version.py +1 -0
  118. qcodes/widgets/__init__.py +0 -0
  119. qcodes/widgets/display.py +47 -0
  120. qcodes/widgets/widgets.css +77 -0
  121. qcodes/widgets/widgets.js +237 -0
  122. qcodes/widgets/widgets.py +215 -0
  123. qcodes-0.1.0.dist-info/DESCRIPTION.rst +165 -0
  124. qcodes-0.1.0.dist-info/METADATA +194 -0
  125. qcodes-0.1.0.dist-info/RECORD +128 -0
  126. qcodes-0.1.0.dist-info/WHEEL +5 -0
  127. qcodes-0.1.0.dist-info/metadata.json +1 -0
  128. qcodes-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,55 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "type" : "object",
4
+ "description": "schema for a qcodes config file",
5
+ "properties":{
6
+ "core" : {
7
+ "description": "controls core settings of qcodes",
8
+ "type" : "object",
9
+ "properties" : {
10
+ "legacy_mp": {
11
+ "type" : "boolean",
12
+ "description": "control legacy buggy multiprocess",
13
+ "default": false
14
+ },
15
+ "loglevel" :{
16
+ "type" : "string",
17
+ "description": "control logging level",
18
+ "default": "DEBUG",
19
+ "enum" : [
20
+ "CRITICAL",
21
+ "ERROR",
22
+ "WARNING",
23
+ "INFO",
24
+ "DEBUG"
25
+ ]
26
+ }
27
+ },
28
+ "required":[ "legacy_mp", "loglevel" ]
29
+ },
30
+ "gui" : {
31
+ "type" : "object",
32
+ "description": "controls gui of qcodes",
33
+ "properties" : {
34
+ "notebook": {
35
+ "description": "Use notebook frontend",
36
+ "type" : "boolean",
37
+ "default": true
38
+ },
39
+ "plotlib": {
40
+ "description": "Plotting library",
41
+ "type": "string",
42
+ "enum": ["QT", "matplotlib" ],
43
+ "default": "matplotlib"
44
+ }
45
+ },
46
+ "required":[ "notebook", "plotlib" ]
47
+ },
48
+ "user":{
49
+ "type" : "object",
50
+ "description": "controls user settings of qcodes",
51
+ "properties" : {}
52
+ }
53
+ },
54
+ "required":[ "gui", "core" , "user"]
55
+ }
File without changes
@@ -0,0 +1,520 @@
1
+ import numpy as np
2
+ import collections
3
+
4
+ from qcodes.utils.helpers import DelegateAttributes, full_class
5
+
6
+
7
+ class DataArray(DelegateAttributes):
8
+
9
+ """
10
+ A container for one parameter in a measurement loop.
11
+
12
+ If this is a measured parameter, This object doesn't contain
13
+ the data of the setpoints it was measured at, but it references
14
+ the DataArray objects of these parameters. Those objects only have
15
+ the dimensionality at which they were set - ie the inner loop setpoint
16
+ the same dimensionality as the measured parameter, but the outer
17
+ loop setpoint(s) have lower dimensionality
18
+
19
+ When it's first created, a DataArray has no dimensionality, you must call
20
+ .nest for each dimension.
21
+
22
+ If preset_data is provided it is used to initialize the data, and the array
23
+ can still be nested around it (making many copies of the data).
24
+ Otherwise it is an error to nest an array that already has data.
25
+
26
+ Once the array is initialized, a DataArray acts a lot like a numpy array,
27
+ because we delegate attributes through to the numpy array
28
+
29
+ Args:
30
+ parameter (Optional[Parameter]): The parameter whose values will
31
+ populate this array, if any. Will copy ``name``, ``full_name``,
32
+ ``label``, ``units``, and ``snapshot`` from here unless you
33
+ provide them explicitly.
34
+
35
+ name (Optional[str]): The short name of this array.
36
+ TODO: use full_name as name, and get rid of short name
37
+
38
+ full_name (Optional[str]): The complete name of this array. If the
39
+ array is based on a parameter linked to an instrument, this is
40
+ typically '<instrument_name>_<param_name>'
41
+
42
+ label (Optional[str]): A description of the values in this array to
43
+ use for axis and colorbar labels on plots.
44
+
45
+ snapshot (Optional[dict]): Metadata snapshot to save with this array.
46
+
47
+ array_id (Optional[str]): A name for this array that's unique within
48
+ its ``DataSet``. Typically the full_name, but when the ``DataSet``
49
+ is constructed we will append '_<i>' (``i`` is an integer starting
50
+ from 1) if necessary to differentiate arrays with the same id.
51
+ TODO: this only happens for arrays provided to the DataSet
52
+ constructor, not those added with add_array. Fix this!
53
+ Also, do we really need array_id *and* full_name (let alone name
54
+ but I've already said we should remove this)?
55
+
56
+ set_arrays (Optional[Tuple[DataArray]]): If this array is being
57
+ created with shape already, you can provide one setpoint array
58
+ per dimension. The first should have one dimension, the second
59
+ two dimensions, etc.
60
+
61
+ shape (Optional[Tuple[int]]): The shape (as in numpy) of the array.
62
+ Will be prepended with new dimensions by any calls to ``nest``.
63
+
64
+ action_indices (Optional[Tuple[int]]): If used within a ``Loop``,
65
+ these are the indices at each level of nesting within the
66
+ ``Loop`` of the loop action that's populating this array.
67
+ TODO: this shouldn't be in DataArray at all, the loop should
68
+ handle converting this to array_id internally (maybe it
69
+ already does?)
70
+
71
+ units (Optional[str]): The units of the values stored in this array.
72
+
73
+ is_setpoint (bool): True if this is a setpoint array, False if it
74
+ is measured. Default False.
75
+
76
+ preset_data (Optional[Union[ndarray, sequence]]): Contents of the
77
+ array, if already known (for example if this is a setpoint
78
+ array). ``shape`` will be inferred from this array instead of
79
+ from the ``shape`` argument.
80
+ """
81
+
82
+ # attributes of self to include in the snapshot
83
+ SNAP_ATTRS = (
84
+ 'array_id',
85
+ 'name',
86
+ 'shape',
87
+ 'units',
88
+ 'label',
89
+ 'action_indices',
90
+ 'is_setpoint')
91
+
92
+ # attributes of the parameter (or keys in the incoming snapshot)
93
+ # to copy to DataArray attributes, if they aren't set some other way
94
+ COPY_ATTRS_FROM_INPUT = (
95
+ 'name',
96
+ 'label',
97
+ 'units')
98
+
99
+ # keys in the parameter snapshot to omit from our snapshot
100
+ SNAP_OMIT_KEYS = (
101
+ 'ts',
102
+ 'value',
103
+ '__class__',
104
+ 'set_arrays',
105
+ 'shape',
106
+ 'array_id',
107
+ 'action_indices')
108
+
109
+ def __init__(self, parameter=None, name=None, full_name=None, label=None,
110
+ snapshot=None, array_id=None, set_arrays=(), shape=None,
111
+ action_indices=(), units=None, is_setpoint=False,
112
+ preset_data=None):
113
+ self.name = name
114
+ self.full_name = full_name or name
115
+ self.label = label
116
+ self.shape = shape
117
+ self.units = units
118
+ self.array_id = array_id
119
+ self.is_setpoint = is_setpoint
120
+ self.action_indices = action_indices
121
+ self.set_arrays = set_arrays
122
+
123
+ self._preset = False
124
+
125
+ # store a reference up to the containing DataSet
126
+ # this also lets us make sure a DataArray is only in one DataSet
127
+ self._data_set = None
128
+
129
+ self.last_saved_index = None
130
+ self.modified_range = None
131
+
132
+ self.ndarray = None
133
+ if snapshot is None:
134
+ snapshot = {}
135
+ self._snapshot_input = {}
136
+
137
+ if parameter is not None:
138
+ param_full_name = getattr(parameter, 'full_name', None)
139
+ if param_full_name and not full_name:
140
+ self.full_name = parameter.full_name
141
+
142
+ if hasattr(parameter, 'snapshot') and not snapshot:
143
+ snapshot = parameter.snapshot()
144
+ else:
145
+ # TODO: why is this in an else clause?
146
+ for attr in self.COPY_ATTRS_FROM_INPUT:
147
+ if (hasattr(parameter, attr) and
148
+ not getattr(self, attr, None)):
149
+ setattr(self, attr, getattr(parameter, attr))
150
+
151
+ for key, value in snapshot.items():
152
+ if key not in self.SNAP_OMIT_KEYS:
153
+ self._snapshot_input[key] = value
154
+
155
+ if (key in self.COPY_ATTRS_FROM_INPUT and
156
+ not getattr(self, key, None)):
157
+ setattr(self, key, value)
158
+
159
+ if not self.label:
160
+ self.label = self.name
161
+
162
+ if preset_data is not None:
163
+ self.init_data(preset_data)
164
+ elif shape is None:
165
+ self.shape = ()
166
+
167
+ @property
168
+ def data_set(self):
169
+ """
170
+ The DataSet this array belongs to.
171
+
172
+ A DataArray can belong to at most one DataSet.
173
+ TODO: make this a weakref
174
+ """
175
+ return self._data_set
176
+
177
+ @data_set.setter
178
+ def data_set(self, new_data_set):
179
+ if (self._data_set is not None and
180
+ new_data_set is not None and
181
+ self._data_set != new_data_set):
182
+ raise RuntimeError('A DataArray can only be part of one DataSet')
183
+ self._data_set = new_data_set
184
+
185
+ def nest(self, size, action_index=None, set_array=None):
186
+ """
187
+ Nest this array inside a new outer loop.
188
+
189
+ You cannot call ``nest`` after ``init_data`` unless this is a
190
+ setpoint array.
191
+ TODO: is this restriction really useful? And should we maintain
192
+ a distinction between _preset and is_setpoint, or can wejust use
193
+ is_setpoint?
194
+
195
+ Args:
196
+ size (int): Length of the new loop.
197
+
198
+ action_index (Optional[int]): Within the outer loop at this
199
+ nesting level, which action does this array derive from?
200
+
201
+ set_array (Optional[DataArray]): The setpoints of the new outer
202
+ loop. If this DataArray *is* a setpoint array, you should
203
+ omit both ``action_index`` and ``set_array``, and it will
204
+ reference itself as the inner setpoint array.
205
+
206
+ Returns:
207
+ DataArray: self, in case you want to construct the array with
208
+ chained method calls.
209
+ """
210
+ if self.ndarray is not None and not self._preset:
211
+ raise RuntimeError('Only preset arrays can be nested after data '
212
+ 'is initialized! {}'.format(self))
213
+
214
+ if set_array is None:
215
+ if self.set_arrays:
216
+ raise TypeError('a setpoint array must be its own inner loop')
217
+ set_array = self
218
+
219
+ self.shape = (size, ) + self.shape
220
+
221
+ if action_index is not None:
222
+ self.action_indices = (action_index, ) + self.action_indices
223
+
224
+ self.set_arrays = (set_array, ) + self.set_arrays
225
+
226
+ if self._preset:
227
+ inner_data = self.ndarray
228
+ self.ndarray = np.ndarray(self.shape)
229
+ # existing preset array copied to every index of the nested array.
230
+ for i in range(size):
231
+ self.ndarray[i] = inner_data
232
+
233
+ # update modified_range so the entire array still looks modified
234
+ self.modified_range = (0, self.ndarray.size - 1)
235
+
236
+ self._set_index_bounds()
237
+
238
+ return self
239
+
240
+ def init_data(self, data=None):
241
+ """
242
+ Create the actual numpy array to hold data.
243
+
244
+ The array will be sized based on either ``self.shape`` or
245
+ data provided here.
246
+
247
+ Idempotent: will do nothing if the array already exists.
248
+
249
+ If data is provided, this array is marked as a preset
250
+ meaning it can still be nested around this data.
251
+ TODO: per above, perhaps remove this distinction entirely?
252
+
253
+ Args:
254
+ data (Optional[Union[ndarray, sequence]]): If provided,
255
+ we fill the array with this data. Otherwise the new
256
+ array will be filled with NaN.
257
+
258
+ Raises:
259
+ ValueError: if ``self.shape`` does not match ``data.shape``
260
+ ValueError: if the array was already initialized with a
261
+ different shape than we're about to create
262
+ """
263
+ if data is not None:
264
+ if not isinstance(data, np.ndarray):
265
+ if isinstance(data, collections.Iterator):
266
+ # faster than np.array(tuple(data)) (or via list)
267
+ # but requires us to assume float
268
+ data = np.fromiter(data, float)
269
+ else:
270
+ data = np.array(data)
271
+
272
+ if self.shape is None:
273
+ self.shape = data.shape
274
+ elif data.shape != self.shape:
275
+ raise ValueError('preset data must be a sequence '
276
+ 'with shape matching the array shape',
277
+ data.shape, self.shape)
278
+ self.ndarray = data
279
+ self._preset = True
280
+
281
+ # mark the entire array as modified
282
+ self.modified_range = (0, data.size - 1)
283
+
284
+ elif self.ndarray is not None:
285
+ if self.ndarray.shape != self.shape:
286
+ raise ValueError('data has already been initialized, '
287
+ 'but its shape doesn\'t match self.shape')
288
+ return
289
+ else:
290
+ self.ndarray = np.ndarray(self.shape)
291
+ self.clear()
292
+ self._set_index_bounds()
293
+
294
+ def _set_index_bounds(self):
295
+ self._min_indices = [0 for d in self.shape]
296
+ self._max_indices = [d - 1 for d in self.shape]
297
+
298
+ def clear(self):
299
+ """Fill the (already existing) data array with nan."""
300
+ # only floats can hold nan values. I guess we could
301
+ # also raise an error in this case? But generally float is
302
+ # what people want anyway.
303
+ if self.ndarray.dtype != float:
304
+ self.ndarray = self.ndarray.astype(float)
305
+ self.ndarray.fill(float('nan'))
306
+
307
+ def __setitem__(self, loop_indices, value):
308
+ """
309
+ Set data values.
310
+
311
+ Follows numpy syntax, allowing indices of lower dimensionality than
312
+ the array, if value makes up the extra dimension(s)
313
+
314
+ Also update the record of modifications to the array. If you don't
315
+ want this overhead, you can access ``self.ndarray`` directly.
316
+ """
317
+ if isinstance(loop_indices, collections.Iterable):
318
+ min_indices = list(loop_indices)
319
+ max_indices = list(loop_indices)
320
+ else:
321
+ min_indices = [loop_indices]
322
+ max_indices = [loop_indices]
323
+
324
+ for i, index in enumerate(min_indices):
325
+ if isinstance(index, slice):
326
+ start, stop, step = index.indices(self.shape[i])
327
+ min_indices[i] = start
328
+ max_indices[i] = start + (
329
+ ((stop - start - 1)//step) * step)
330
+
331
+ min_li = self.flat_index(min_indices, self._min_indices)
332
+ max_li = self.flat_index(max_indices, self._max_indices)
333
+ self._update_modified_range(min_li, max_li)
334
+
335
+ self.ndarray.__setitem__(loop_indices, value)
336
+
337
+ def __getitem__(self, loop_indices):
338
+ return self.ndarray[loop_indices]
339
+
340
+ delegate_attr_objects = ['ndarray']
341
+
342
+ def __len__(self):
343
+ """
344
+ Array length.
345
+
346
+ Must be explicitly delegated, because len() will look for this
347
+ attribute to already exist.
348
+ """
349
+ return len(self.ndarray)
350
+
351
+ def flat_index(self, indices, index_fill=None):
352
+ """
353
+ Generate the raveled index for the given indices.
354
+
355
+ This is the index you would have if the array is reshaped to 1D,
356
+ looping over the indices from inner to outer.
357
+
358
+ Args:
359
+ indices (sequence): indices of an element or slice of this array.
360
+
361
+ index_fill (sequence, optional): extra indices to use if
362
+ ``indices`` has less dimensions than the array, ie it points
363
+ to a slice rather than a single element. Use zeros to get the
364
+ beginning of this slice, and [d - 1 for d in shape] to get the
365
+ end of the slice.
366
+
367
+ Returns:
368
+ int: the resulting flat index.
369
+ """
370
+ if len(indices) < len(self.shape):
371
+ indices = indices + index_fill[len(indices):]
372
+ return np.ravel_multi_index(tuple(zip(indices)), self.shape)[0]
373
+
374
+ def _update_modified_range(self, low, high):
375
+ if self.modified_range:
376
+ self.modified_range = (min(self.modified_range[0], low),
377
+ max(self.modified_range[1], high))
378
+ else:
379
+ self.modified_range = (low, high)
380
+
381
+ def mark_saved(self, last_saved_index):
382
+ """
383
+ Mark certain outstanding modifications as saved.
384
+
385
+ Args:
386
+ last_saved_index (int): The flat index of the last point
387
+ saved. If ``modified_range`` extends beyond this, the
388
+ data past ``last_saved_index`` will still be marked
389
+ modified, otherwise ``modified_range`` is cleared
390
+ entirely.
391
+ """
392
+ if self.modified_range:
393
+ if last_saved_index >= self.modified_range[1]:
394
+ self.modified_range = None
395
+ else:
396
+ self.modified_range = (max(self.modified_range[0],
397
+ last_saved_index + 1),
398
+ self.modified_range[1])
399
+ self.last_saved_index = last_saved_index
400
+
401
+ def clear_save(self):
402
+ """
403
+ Make previously saved parts of this array look unsaved (modified).
404
+
405
+ This can be used to force overwrite or rewrite, like if we're
406
+ moving or copying the ``DataSet``.
407
+ """
408
+ if self.last_saved_index is not None:
409
+ self._update_modified_range(0, self.last_saved_index)
410
+
411
+ self.last_saved_index = None
412
+
413
+ def get_synced_index(self):
414
+ """
415
+ Get the last index which has been synced from the server.
416
+
417
+ Will also initialize the array if this hasn't happened already.
418
+ TODO: seems hacky to init_data here.
419
+
420
+ Returns:
421
+ int: the last flat index which has been synced from the server,
422
+ or -1 if no data has been synced.
423
+ """
424
+ if not hasattr(self, 'synced_index'):
425
+ self.init_data()
426
+ self.synced_index = -1
427
+
428
+ return self.synced_index
429
+
430
+ def get_changes(self, synced_index):
431
+ """
432
+ Find changes since the last sync of this array.
433
+
434
+ Args:
435
+ synced_index (int): The last flat index which has already
436
+ been synced.
437
+
438
+ Returns:
439
+ Union[dict, None]: None if there is no new data. If there is,
440
+ returns a dict with keys:
441
+ start (int): the flat index of the first returned value.
442
+ stop (int): the flat index of the last returned value.
443
+ vals (List[float]): the new values
444
+ """
445
+ latest_index = self.last_saved_index
446
+ if latest_index is None:
447
+ latest_index = -1
448
+ if self.modified_range:
449
+ latest_index = max(latest_index, self.modified_range[1])
450
+
451
+ vals = [
452
+ self.ndarray[np.unravel_index(i, self.ndarray.shape)]
453
+ for i in range(synced_index + 1, latest_index + 1)
454
+ ]
455
+
456
+ if vals:
457
+ return {
458
+ 'start': synced_index + 1,
459
+ 'stop': latest_index,
460
+ 'vals': vals
461
+ }
462
+
463
+ def apply_changes(self, start, stop, vals):
464
+ """
465
+ Insert new synced values into the array.
466
+
467
+ To be be called in a ``PULL_FROM_SERVER`` ``DataSet`` using results
468
+ returned by ``get_changes`` from the ``DataServer``.
469
+
470
+ TODO: check that vals has the right length?
471
+
472
+ Args:
473
+ start (int): the flat index of the first new value.
474
+ stop (int): the flat index of the last new value.
475
+ vals (List[float]): the new values
476
+ """
477
+ for i, val in enumerate(vals):
478
+ index = np.unravel_index(i + start, self.ndarray.shape)
479
+ self.ndarray[index] = val
480
+ self.synced_index = stop
481
+
482
+ def __repr__(self):
483
+ array_id_or_none = ' {}'.format(self.array_id) if self.array_id else ''
484
+ return '{}[{}]:{}\n{}'.format(self.__class__.__name__,
485
+ ','.join(map(str, self.shape)),
486
+ array_id_or_none, repr(self.ndarray))
487
+
488
+ def snapshot(self, update=False):
489
+ """JSON representation of this DataArray."""
490
+ snap = {'__class__': full_class(self)}
491
+
492
+ snap.update(self._snapshot_input)
493
+
494
+ for attr in self.SNAP_ATTRS:
495
+ snap[attr] = getattr(self, attr)
496
+
497
+ return snap
498
+
499
+ def fraction_complete(self):
500
+ """
501
+ Get the fraction of this array which has data in it.
502
+
503
+ Or more specifically, the fraction of the latest point in the array
504
+ where we have touched it.
505
+
506
+ Returns:
507
+ float: fraction of array which is complete, from 0.0 to 1.0
508
+ """
509
+ if self.ndarray is None:
510
+ return 0.0
511
+
512
+ last_index = -1
513
+ if self.last_saved_index is not None:
514
+ last_index = max(last_index, self.last_saved_index)
515
+ if self.modified_range is not None:
516
+ last_index = max(last_index, self.modified_range[1])
517
+ if getattr(self, 'synced_index', None) is not None:
518
+ last_index = max(last_index, self.synced_index)
519
+
520
+ return (last_index + 1) / self.ndarray.size