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
qcodes/data/format.py ADDED
@@ -0,0 +1,309 @@
1
+ from collections import namedtuple
2
+ from traceback import format_exc
3
+ from operator import attrgetter
4
+ import logging
5
+
6
+
7
+ class Formatter:
8
+ """
9
+ Data file formatters
10
+
11
+ Formatters translate between DataSets and data files.
12
+
13
+ Each Formatter is expected to implement writing methods:
14
+ - ``write``: to write the ``DataArray``s
15
+ - ``write_metadata``: to write the metadata JSON structure
16
+
17
+ Optionally, if this Formatter keeps the data file(s) open
18
+ between write calls, it may implement:
19
+ - ``close_file``: to perform any final cleanup and release the
20
+ file and any other resources.
21
+
22
+ and reading methods:
23
+ - ``read`` or ``read_one_file`` to reconstruct the ``DataArray``s, either
24
+ all at once (``read``) or one file at a time, supplied by the base class
25
+ ``read`` method that loops over all data files at the correct location.
26
+
27
+ - ``read_metadata``: to reload saved metadata. If a subclass overrides
28
+ ``read``, this method should call ``read_metadata``, but keep it also
29
+ as a separate method because it occasionally gets called independently.
30
+
31
+ All of these methods accept a ``data_set`` argument, which should be a
32
+ ``DataSet`` object. Even if you are loading a new data set from disk, this
33
+ object should already have attributes:
34
+ io: an IO manager (see qcodes.data.io)
35
+ location: a string, like a file path, that identifies the DataSet and
36
+ tells the IO manager where to store it
37
+ arrays: a dict of ``{array_id:DataArray}`` to read into.
38
+ - read will create entries that don't yet exist.
39
+ - write will write ALL DataArrays in the DataSet, using
40
+ last_saved_index and modified_range, as well as whether or not
41
+ it found the specified file, to determine how much to write.
42
+ """
43
+ ArrayGroup = namedtuple('ArrayGroup', 'shape set_arrays data name')
44
+
45
+ def write(self, data_set, io_manager, location):
46
+ """
47
+ Write the DataSet to storage.
48
+
49
+ Subclasses must override this method.
50
+
51
+ It is up to the Formatter to decide when to overwrite completely,
52
+ and when to just append or otherwise update the file(s).
53
+
54
+ Args:
55
+ data_set (DataSet): the data we are writing.
56
+ io_manager (io_manager): base physical location to write to.
57
+ location (str): the file location within the io_manager.
58
+ """
59
+ raise NotImplementedError
60
+
61
+ def read(self, data_set):
62
+ """
63
+ Read the entire ``DataSet``.
64
+
65
+ Find all files matching ``data_set.location`` (using io_manager.list)
66
+ and call ``read_one_file`` on each. Subclasses may either override
67
+ this method (if they use only one file or want to do their own
68
+ searching) or override ``read_one_file`` to use the search and
69
+ initialization functionality defined here.
70
+
71
+ Args:
72
+ data_set (DataSet): the data to read into. Should already have
73
+ attributes ``io`` (an io manager), ``location`` (string),
74
+ and ``arrays`` (dict of ``{array_id: array}``, can be empty
75
+ or can already have some or all of the arrays present, they
76
+ expect to be overwritten)
77
+ """
78
+ io_manager = data_set.io
79
+ location = data_set.location
80
+
81
+ data_files = io_manager.list(location)
82
+ if not data_files:
83
+ raise IOError('no data found at ' + location)
84
+
85
+ # in case the DataArrays exist but haven't been initialized
86
+ for array in data_set.arrays.values():
87
+ if array.ndarray is None:
88
+ array.init_data()
89
+
90
+ self.read_metadata(data_set)
91
+
92
+ ids_read = set()
93
+ for fn in data_files:
94
+ with io_manager.open(fn, 'r') as f:
95
+ try:
96
+ self.read_one_file(data_set, f, ids_read)
97
+ except ValueError:
98
+ logging.warning('error reading file ' + fn)
99
+ logging.warning(format_exc())
100
+
101
+ def write_metadata(self, data_set, io_manager, location, read_first=True):
102
+ """
103
+ Write the metadata for this DataSet to storage.
104
+
105
+ Subclasses must override this method.
106
+
107
+ Args:
108
+ data_set (DataSet): the data we are writing.
109
+ io_manager (io_manager): base physical location to write to.
110
+ location (str): the file location within the io_manager.
111
+ read_first (bool, optional): whether to first look for previously
112
+ saved metadata that may contain more information than the local
113
+ copy.
114
+ """
115
+ raise NotImplementedError
116
+
117
+ def read_metadata(self, data_set):
118
+ """
119
+ Read the metadata from this DataSet from storage.
120
+
121
+ Subclasses must override this method.
122
+
123
+ Args:
124
+ data_set (DataSet): the data to read metadata into
125
+ """
126
+ raise NotImplementedError
127
+
128
+ def read_one_file(self, data_set, f, ids_read):
129
+ """
130
+ Read data from a single file into a ``DataSet``.
131
+
132
+ Formatter subclasses that break a DataSet into multiple data files may
133
+ choose to override either this method, which handles one file at a
134
+ time, or ``read`` which finds matching files on its own.
135
+
136
+ Args:
137
+ data_set (DataSet): the data we are reading into.
138
+
139
+ f (file-like): a file-like object to read from, as provided by
140
+ ``io_manager.open``.
141
+
142
+ ids_read (set): ``array_id``s that we have already read.
143
+ When you read an array, check that it's not in this set (except
144
+ setpoints, which can be in several files with different inner
145
+ loops) then add it to the set so other files know it should not
146
+ be read again.
147
+
148
+ Raises:
149
+ ValueError: if a duplicate array_id of measured data is found
150
+ """
151
+ raise NotImplementedError
152
+
153
+ def match_save_range(self, group, file_exists, only_complete=True):
154
+ """
155
+ Find the save range that will joins all changes in an array group.
156
+
157
+ Matches all full-sized arrays: the data arrays plus the inner loop
158
+ setpoint array.
159
+
160
+ Note: if an outer loop has changed values (without the inner
161
+ loop or measured data changing) we won't notice it here. We assume
162
+ that before an iteration of the inner loop starts, the outer loop
163
+ setpoint gets set and then does not change later.
164
+
165
+ Args:
166
+ group (Formatter.ArrayGroup): a ``namedtuple`` containing the
167
+ arrays that go together in one file, as tuple ``group.data``.
168
+
169
+ file_exists (bool): Does this file already exist? If True, and
170
+ all arrays in the group agree on ``last_saved_index``, we
171
+ assume the file has been written up to this index and we can
172
+ append to it. Otherwise we will set the returned range to start
173
+ from zero (so if the file does exist, it gets completely
174
+ overwritten).
175
+
176
+ only_complete (bool): Should we write all available new data,
177
+ or only complete rows? If True, we write only the range of
178
+ array indices which all arrays in the group list as modified,
179
+ so that future writes will be able to do a clean append to
180
+ the data file as more data arrives.
181
+ Default True.
182
+
183
+ Returns:
184
+ Tuple(int, int): the first and last raveled indices that should
185
+ be saved.
186
+ """
187
+ inner_setpoint = group.set_arrays[-1]
188
+ full_dim_data = (inner_setpoint, ) + group.data
189
+
190
+ # always return None if there are no modifications,
191
+ # even if there are last_saved_index inconsistencies
192
+ # so we don't do extra writing just to reshape the file
193
+ for array in full_dim_data:
194
+ if array.modified_range:
195
+ break
196
+ else:
197
+ return None
198
+
199
+ last_saved_index = inner_setpoint.last_saved_index
200
+
201
+ if last_saved_index is None or not file_exists:
202
+ return self._match_save_range_whole_file(
203
+ full_dim_data, only_complete)
204
+
205
+ # force overwrite if inconsistent last_saved_index
206
+ for array in group.data:
207
+ if array.last_saved_index != last_saved_index:
208
+ return self._match_save_range_whole_file(
209
+ full_dim_data, only_complete)
210
+
211
+ return self._match_save_range_incremental(
212
+ full_dim_data, last_saved_index, only_complete)
213
+
214
+ @staticmethod
215
+ def _match_save_range_whole_file(arrays, only_complete):
216
+ max_save = None
217
+ agg = (min if only_complete else max)
218
+ for array in arrays:
219
+ array_max = array.last_saved_index
220
+ if array_max is None:
221
+ array_max = -1
222
+ mr = array.modified_range
223
+ if mr:
224
+ array_max = max(array_max, mr[1])
225
+ max_save = (array_max if max_save is None else
226
+ agg(max_save, array_max))
227
+
228
+ if max_save >= 0:
229
+ return (0, max_save)
230
+ else:
231
+ return None
232
+
233
+ @staticmethod
234
+ def _match_save_range_incremental(arrays, last_saved_index, only_complete):
235
+ mod_ranges = []
236
+ for array in arrays:
237
+ mr = array.modified_range
238
+ if not mr:
239
+ if only_complete:
240
+ return None
241
+ else:
242
+ continue
243
+ mod_ranges.append(mr)
244
+
245
+ mod_range = mod_ranges[0]
246
+ agg = (min if only_complete else max)
247
+ for mr in mod_ranges[1:]:
248
+ mod_range = (min(mod_range[0], mr[0]),
249
+ agg(mod_range[1], mr[1]))
250
+
251
+ if last_saved_index >= mod_range[1]:
252
+ return (0, last_saved_index)
253
+ elif last_saved_index >= mod_range[0]:
254
+ return (0, mod_range[1])
255
+ else:
256
+ return (last_saved_index + 1, mod_range[1])
257
+
258
+ def group_arrays(self, arrays):
259
+ """
260
+ Find the sets of arrays which share all the same setpoint arrays.
261
+
262
+ Some Formatters use this grouping to determine which arrays to save
263
+ together in one file.
264
+
265
+ Args:
266
+ arrays (Dict[DataArray]): all the arrays in a DataSet
267
+
268
+ Returns:
269
+ List[Formatter.ArrayGroup]: namedtuples giving:
270
+ shape (Tuple[int]): dimensions as in numpy
271
+ set_arrays (Tuple[DataArray]): the setpoints of this group
272
+ data (Tuple[DataArray]): measured arrays in this group
273
+ name (str): a unique name of this group, obtained by joining
274
+ the setpoint array ids.
275
+ """
276
+
277
+ set_array_sets = tuple(set(array.set_arrays
278
+ for array in arrays.values()))
279
+ all_set_arrays = set()
280
+ for set_array_set in set_array_sets:
281
+ all_set_arrays.update(set_array_set)
282
+
283
+ grouped_data = [[] for _ in set_array_sets]
284
+
285
+ for array in arrays.values():
286
+ i = set_array_sets.index(array.set_arrays)
287
+ if array not in all_set_arrays: # array.set_arrays[-1] != array:
288
+ # don't include the setpoint array itself in the data
289
+ grouped_data[i].append(array)
290
+
291
+ out = []
292
+ id_getter = attrgetter('array_id')
293
+ for set_arrays, data in zip(set_array_sets, grouped_data):
294
+ leni = len(set_arrays)
295
+ if not data and any(1 for other_set_arrays in set_array_sets if
296
+ len(other_set_arrays) > leni and
297
+ other_set_arrays[:leni] == set_arrays):
298
+ # this is an outer loop that doesn't have any data of its own,
299
+ # so skip it.
300
+ # Inner-loop setpoints with no data is weird (we set values
301
+ # but didn't measure anything there?) but we should keep it.
302
+ continue
303
+
304
+ group_name = '_'.join(sai.array_id for sai in set_arrays)
305
+ out.append(self.ArrayGroup(shape=set_arrays[-1].shape,
306
+ set_arrays=set_arrays,
307
+ data=tuple(sorted(data, key=id_getter)),
308
+ name=group_name))
309
+ return out
@@ -0,0 +1,366 @@
1
+ import numpy as np
2
+ import re
3
+ import math
4
+ import json
5
+
6
+ from qcodes.utils.helpers import deep_update, NumpyJSONEncoder
7
+ from .data_array import DataArray
8
+ from .format import Formatter
9
+
10
+
11
+ class GNUPlotFormat(Formatter):
12
+ """
13
+ Saves data in one or more gnuplot-format files. We make one file for
14
+ each set of matching dependent variables in the loop.
15
+
16
+ options:
17
+
18
+ extension (default 'dat'): file extension for data files
19
+
20
+ terminator (default '\\n'): newline character(s) to use on write
21
+ not used for reading, we will read any combination of \\r and \\n
22
+
23
+ separator (default '\\t'): field (column) separator, must be whitespace.
24
+ Only used for writing, we will read with any whitespace separation.
25
+
26
+ comment (default '# '): lines starting with this are not data
27
+ Comments are written with this full string, and identified on read
28
+ by just the string after stripping whitespace.
29
+
30
+ number_format (default 'g'): from the format mini-language, how to
31
+ format numeric data into a string
32
+
33
+ always_nest (default True): whether to always make a folder for files
34
+ or just make a single data file if all data has the same setpoints
35
+
36
+ These files are basically tab-separated values, but any quantity of
37
+ any whitespace characters is accepted.
38
+
39
+ Each row represents one setting of the setpoint variable(s)
40
+ the setpoint variable(s) are in the first column(s)
41
+ measured variable(s) come after.
42
+
43
+ The data is preceded by comment lines (starting with #).
44
+ We use three:
45
+ - one for the variable name
46
+ - the (longer) axis label, in quotes so a label can contain whitespace.
47
+ - for each dependent var, the (max) number of points in that dimension
48
+ (this also tells us how many dependent vars we have in this file)
49
+
50
+ # id1\tid2\t\id3...
51
+ # "label1"\t"label2"\t"label3"...
52
+ # 100\t250
53
+ 1\t2\t3...
54
+ 2\t3\t4...
55
+
56
+ For data of 2 dependent variables, gnuplot puts each inner loop into one
57
+ block, then increments the outer loop in the next block, separated by a
58
+ blank line.
59
+
60
+ We extend this to an arbitrary quantity of dependent variables by using
61
+ one blank line for each loop level that resets. (gnuplot *does* seem to
62
+ use 2 blank lines sometimes, to denote a whole new dataset, which sort
63
+ of corresponds to our situation.)
64
+ """
65
+ def __init__(self, extension='dat', terminator='\n', separator='\t',
66
+ comment='# ', number_format='g', metadata_file=None):
67
+ self.metadata_file = metadata_file or 'snapshot.json'
68
+ # file extension: accept either with or without leading dot
69
+ self.extension = '.' + extension.lstrip('.')
70
+
71
+ # line terminator (only used for writing; will read any \r\n combo)
72
+ if terminator not in ('\r', '\n', '\r\n'):
73
+ raise ValueError(
74
+ r'GNUPlotFormat terminator must be \r, \n, or \r\n')
75
+ self.terminator = terminator
76
+
77
+ # field separator (only used for writing; will read any whitespace)
78
+ if not re.fullmatch(r'\s+', separator):
79
+ raise ValueError('GNUPlotFormat separator must be whitespace')
80
+ self.separator = separator
81
+
82
+ # beginning of a comment line. (when reading, just checks the
83
+ # non-whitespace character(s) of comment
84
+ self.comment = comment
85
+ self.comment_chars = comment.rstrip()
86
+ if not self.comment_chars:
87
+ raise ValueError('comment must have some non-whitespace')
88
+ self.comment_len = len(self.comment_chars)
89
+
90
+ # number format (only used for writing; will read any number)
91
+ self.number_format = '{:' + number_format + '}'
92
+
93
+ def read_one_file(self, data_set, f, ids_read):
94
+ """
95
+ Called by Formatter.read to bring one data file into
96
+ a DataSet. Setpoint data may be duplicated across multiple files,
97
+ but each measured DataArray must only map to one file.
98
+
99
+ data_set: the DataSet we are reading into
100
+ f: a file-like object to read from
101
+ ids_read: a `set` of array_ids that we have already read.
102
+ when you read an array, check that it's not in this set (except
103
+ setpoints, which can be in several files with different inner loop)
104
+ then add it to the set so other files know not to read it again
105
+ """
106
+ if not f.name.endswith(self.extension):
107
+ return
108
+
109
+ arrays = data_set.arrays
110
+ ids = self._read_comment_line(f).split()
111
+ labels = self._get_labels(self._read_comment_line(f))
112
+ shape = tuple(map(int, self._read_comment_line(f).split()))
113
+ ndim = len(shape)
114
+
115
+ set_arrays = ()
116
+ data_arrays = []
117
+ indexed_ids = list(enumerate(ids))
118
+
119
+ for i, array_id in indexed_ids[:ndim]:
120
+ snap = data_set.get_array_metadata(array_id)
121
+
122
+ # setpoint arrays
123
+ set_shape = shape[: i + 1]
124
+ if array_id in arrays:
125
+ set_array = arrays[array_id]
126
+ if set_array.shape != set_shape:
127
+ raise ValueError(
128
+ 'shapes do not match for set array: ' + array_id)
129
+ if array_id not in ids_read:
130
+ # it's OK for setpoints to be duplicated across
131
+ # multiple files, but we should only empty the
132
+ # array out the first time we see it, so subsequent
133
+ # reads can check for consistency
134
+ set_array.clear()
135
+ else:
136
+ set_array = DataArray(label=labels[i], array_id=array_id,
137
+ set_arrays=set_arrays, shape=set_shape,
138
+ is_setpoint=True, snapshot=snap)
139
+ set_array.init_data()
140
+ data_set.add_array(set_array)
141
+
142
+ set_arrays = set_arrays + (set_array, )
143
+ ids_read.add(array_id)
144
+
145
+ for i, array_id in indexed_ids[ndim:]:
146
+ snap = data_set.get_array_metadata(array_id)
147
+
148
+ # data arrays
149
+ if array_id in ids_read:
150
+ raise ValueError('duplicate data id found: ' + array_id)
151
+
152
+ if array_id in arrays:
153
+ data_array = arrays[array_id]
154
+ data_array.clear()
155
+ else:
156
+ data_array = DataArray(label=labels[i], array_id=array_id,
157
+ set_arrays=set_arrays, shape=shape,
158
+ snapshot=snap)
159
+ data_array.init_data()
160
+ data_set.add_array(data_array)
161
+ data_arrays.append(data_array)
162
+ ids_read.add(array_id)
163
+
164
+ indices = [0] * ndim
165
+ first_point = True
166
+ resetting = 0
167
+ for line in f:
168
+ if self._is_comment(line):
169
+ continue
170
+
171
+ # ignore leading or trailing whitespace (including in blank lines)
172
+ line = line.strip()
173
+
174
+ if not line:
175
+ # each consecutive blank line implies one more loop to reset
176
+ # when we read the next data point. Don't depend on the number
177
+ # of setpoints that change, as there could be weird cases, like
178
+ # bidirectional sweeps, or highly diagonal sweeps, where this
179
+ # is incorrect. Anyway this really only matters for >2D sweeps.
180
+ if not first_point:
181
+ resetting += 1
182
+ continue
183
+
184
+ values = tuple(map(float, line.split()))
185
+
186
+ if resetting:
187
+ indices[-resetting - 1] += 1
188
+ indices[-resetting:] = [0] * resetting
189
+ resetting = 0
190
+
191
+ for value, set_array in zip(values[:ndim], set_arrays):
192
+ nparray = set_array.ndarray
193
+ myindices = tuple(indices[:nparray.ndim])
194
+ stored_value = nparray[myindices]
195
+ if math.isnan(stored_value):
196
+ nparray[myindices] = value
197
+ elif stored_value != value:
198
+ raise ValueError('inconsistent setpoint values',
199
+ stored_value, value, set_array.name,
200
+ myindices, indices)
201
+
202
+ for value, data_array in zip(values[ndim:], data_arrays):
203
+ # set .ndarray directly to avoid the overhead of __setitem__
204
+ # which updates modified_range on every call
205
+ data_array.ndarray[tuple(indices)] = value
206
+
207
+ indices[-1] += 1
208
+ first_point = False
209
+
210
+ # Since we skipped __setitem__, back up to the last read point and
211
+ # mark it as saved that far.
212
+ # Using mark_saved is better than directly setting last_saved_index
213
+ # because it also ensures modified_range is set correctly.
214
+ indices[-1] -= 1
215
+ for array in set_arrays + tuple(data_arrays):
216
+ array.mark_saved(array.flat_index(indices[:array.ndim]))
217
+
218
+ def _is_comment(self, line):
219
+ return line[:self.comment_len] == self.comment_chars
220
+
221
+ def _read_comment_line(self, f):
222
+ s = f.readline()
223
+ if not self._is_comment(s):
224
+ raise ValueError('expected a comment line, found:\n' + s)
225
+ return s[self.comment_len:]
226
+
227
+ def _get_labels(self, labelstr):
228
+ labelstr = labelstr.strip()
229
+ if labelstr[0] != '"' or labelstr[-1] != '"':
230
+ # fields are *not* quoted
231
+ return labelstr.split()
232
+ else:
233
+ # fields *are* quoted (and escaped)
234
+ parts = re.split('"\s+"', labelstr[1:-1])
235
+ return [l.replace('\\"', '"').replace('\\\\', '\\') for l in parts]
236
+
237
+ def write(self, data_set, io_manager, location, force_write=False):
238
+ """
239
+ Write updates in this DataSet to storage.
240
+
241
+ Will choose append if possible, overwrite if not.
242
+
243
+ Args:
244
+ data_set (DataSet): the data we're storing
245
+ io_manager (io_manager): the base location to write to
246
+ location (str): the file location within io_manager
247
+ """
248
+ arrays = data_set.arrays
249
+
250
+ # puts everything with same dimensions together
251
+ groups = self.group_arrays(arrays)
252
+ existing_files = set(io_manager.list(location))
253
+ written_files = set()
254
+
255
+ # Every group gets it's own datafile
256
+ for group in groups:
257
+ fn = io_manager.join(location, group.name + self.extension)
258
+
259
+ written_files.add(fn)
260
+
261
+ file_exists = fn in existing_files
262
+ save_range = self.match_save_range(group, file_exists)
263
+
264
+ if save_range is None:
265
+ continue
266
+
267
+ overwrite = save_range[0] == 0 or force_write
268
+ open_mode = 'w' if overwrite else 'a'
269
+ shape = group.set_arrays[-1].shape
270
+
271
+ with io_manager.open(fn, open_mode) as f:
272
+ if overwrite:
273
+ f.write(self._make_header(group))
274
+
275
+ for i in range(save_range[0], save_range[1] + 1):
276
+ indices = np.unravel_index(i, shape)
277
+
278
+ # insert a blank line for each loop that reset (to index 0)
279
+ # note that if *all* indices are zero (the first point)
280
+ # we won't put any blanks
281
+ for j, index in enumerate(reversed(indices)):
282
+ if index != 0:
283
+ if j:
284
+ f.write(self.terminator * j)
285
+ break
286
+
287
+ one_point = self._data_point(group, indices)
288
+ f.write(self.separator.join(one_point) + self.terminator)
289
+
290
+ # now that we've saved the data, mark it as such in the data.
291
+ # we mark the data arrays and the inner setpoint array. Outer
292
+ # setpoint arrays have different dimension (so would need a
293
+ # different unraveled index) but more importantly could have
294
+ # a different saved range anyway depending on whether there
295
+ # is outer data taken before or after the inner loop. Anyway we
296
+ # never look at the outer setpoint last_saved_index or
297
+ # modified_range, we just assume it's got the values we need.
298
+ for array in group.data + (group.set_arrays[-1],):
299
+ array.mark_saved(save_range[1])
300
+
301
+ def write_metadata(self, data_set, io_manager, location, read_first=True):
302
+ """
303
+ Write all metadata in this DataSet to storage.
304
+
305
+ Args:
306
+ data_set (DataSet): the data we're storing
307
+
308
+ io_manager (io_manager): the base location to write to
309
+
310
+ location (str): the file location within io_manager
311
+
312
+ read_first (bool, optional): read previously saved metadata before
313
+ writing? The current metadata will still be the used if
314
+ there are changes, but if the saved metadata has information
315
+ not present in the current metadata, it will be retained.
316
+ Default True.
317
+ """
318
+ if read_first:
319
+ # In case the saved file has more metadata than we have here,
320
+ # read it in first. But any changes to the in-memory copy should
321
+ # override the saved file data.
322
+ memory_metadata = data_set.metadata
323
+ data_set.metadata = {}
324
+ self.read_metadata(data_set)
325
+ deep_update(data_set.metadata, memory_metadata)
326
+
327
+ fn = io_manager.join(location, self.metadata_file)
328
+ with io_manager.open(fn, 'w', encoding='utf8') as snap_file:
329
+ json.dump(data_set.metadata, snap_file, sort_keys=True,
330
+ indent=4, ensure_ascii=False, cls=NumpyJSONEncoder)
331
+
332
+ def read_metadata(self, data_set):
333
+ io_manager = data_set.io
334
+ location = data_set.location
335
+ fn = io_manager.join(location, self.metadata_file)
336
+ if io_manager.list(fn):
337
+ with io_manager.open(fn, 'r') as snap_file:
338
+ metadata = json.load(snap_file, encoding='utf8')
339
+ data_set.metadata.update(metadata)
340
+
341
+ def _make_header(self, group):
342
+ ids, labels = [], []
343
+ for array in group.set_arrays + group.data:
344
+ ids.append(array.array_id)
345
+ label = getattr(array, 'label', array.array_id)
346
+ label = label.replace('\\', '\\\\').replace('"', '\\"')
347
+ labels.append('"' + label + '"')
348
+
349
+ shape = [str(size) for size in group.set_arrays[-1].shape]
350
+ if len(shape) != len(group.set_arrays):
351
+ raise ValueError('array dimensionality does not match setpoints')
352
+
353
+ out = (self._comment_line(ids) + self._comment_line(labels) +
354
+ self._comment_line(shape))
355
+
356
+ return out
357
+
358
+ def _comment_line(self, items):
359
+ return self.comment + self.separator.join(items) + self.terminator
360
+
361
+ def _data_point(self, group, indices):
362
+ for array in group.set_arrays:
363
+ yield self.number_format.format(array[indices[:array.ndim]])
364
+
365
+ for array in group.data:
366
+ yield self.number_format.format(array[indices])