pyelectrochemreader 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.
@@ -0,0 +1,8 @@
1
+ """Read electrochemical files using only Python and NumPy."""
2
+
3
+ from .core import Dataset, Experiment, ReadError, UnsupportedFormatError
4
+ from .reader import read, supported_formats
5
+
6
+ __version__ = "0.1.0"
7
+ __all__ = ["read", "supported_formats", "Dataset", "Experiment", "ReadError",
8
+ "UnsupportedFormatError"]
@@ -0,0 +1,143 @@
1
+ """Bio-Logic column layouts, adapted from Galvani (modified 2026-11-02).
2
+
3
+ SPDX-FileCopyrightText: 2013-2020 Christopher Kerr, "bcolsen"
4
+ SPDX-License-Identifier: GPL-3.0-or-later
5
+ """
6
+ import numpy as np
7
+
8
+ VMPdata_colID_dtype_map = {
9
+ 4: ("time/s", "<f8"),
10
+ 5: ("control/V/mA", "<f4"),
11
+ 6: ("Ewe/V", "<f4"),
12
+ 7: ("dq/mA.h", "<f8"),
13
+ 8: ("I/mA", "<f4"), # 8 is either I or <I> ??
14
+ 9: ("Ece/V", "<f4"),
15
+ 11: ("<I>/mA", "<f8"),
16
+ 13: ("(Q-Qo)/mA.h", "<f8"),
17
+ 16: ("Analog IN 1/V", "<f4"),
18
+ 17: ("Analog IN 2/V", "<f4"), # Probably column 18 is Analog IN 3/V, if anyone hits this error in the future # noqa: E501
19
+ 19: ("control/V", "<f4"),
20
+ 20: ("control/mA", "<f4"),
21
+ 23: ("dQ/mA.h", "<f8"), # Same as 7?
22
+ 24: ("cycle number", "<f8"),
23
+ 26: ("Rapp/Ohm", "<f4"),
24
+ 27: ("Ewe-Ece/V", "<f4"),
25
+ 32: ("freq/Hz", "<f4"),
26
+ 33: ("|Ewe|/V", "<f4"),
27
+ 34: ("|I|/A", "<f4"),
28
+ 35: ("Phase(Z)/deg", "<f4"),
29
+ 36: ("|Z|/Ohm", "<f4"),
30
+ 37: ("Re(Z)/Ohm", "<f4"),
31
+ 38: ("-Im(Z)/Ohm", "<f4"),
32
+ 39: ("I Range", "<u2"),
33
+ 69: ("R/Ohm", "<f4"),
34
+ 70: ("P/W", "<f4"),
35
+ 74: ("|Energy|/W.h", "<f8"),
36
+ 75: ("Analog OUT/V", "<f4"),
37
+ 76: ("<I>/mA", "<f4"),
38
+ 77: ("<Ewe>/V", "<f4"),
39
+ 78: ("Cs-2/µF-2", "<f4"),
40
+ 96: ("|Ece|/V", "<f4"),
41
+ 98: ("Phase(Zce)/deg", "<f4"),
42
+ 99: ("|Zce|/Ohm", "<f4"),
43
+ 100: ("Re(Zce)/Ohm", "<f4"),
44
+ 101: ("-Im(Zce)/Ohm", "<f4"),
45
+ 123: ("Energy charge/W.h", "<f8"),
46
+ 124: ("Energy discharge/W.h", "<f8"),
47
+ 125: ("Capacitance charge/µF", "<f8"),
48
+ 126: ("Capacitance discharge/µF", "<f8"),
49
+ 131: ("Ns", "<u2"),
50
+ 163: ("|Estack|/V", "<f4"),
51
+ 168: ("Rcmp/Ohm", "<f4"),
52
+ 169: ("Cs/µF", "<f4"),
53
+ 172: ("Cp/µF", "<f4"),
54
+ 173: ("Cp-2/µF-2", "<f4"),
55
+ 174: ("<Ewe>/V", "<f4"),
56
+ 178: ("(Q-Qo)/C", "<f4"),
57
+ 179: ("dQ/C", "<f4"),
58
+ 182: ("step time/s", "<f8"),
59
+ 211: ("Q charge/discharge/mA.h", "<f8"),
60
+ 212: ("half cycle", "<u4"),
61
+ 213: ("z cycle", "<u4"),
62
+ 217: ("THD Ewe/%", "<f4"),
63
+ 218: ("THD I/%", "<f4"),
64
+ 220: ("NSD Ewe/%", "<f4"),
65
+ 221: ("NSD I/%", "<f4"),
66
+ 223: ("NSR Ewe/%", "<f4"),
67
+ 224: ("NSR I/%", "<f4"),
68
+ 230: ("|Ewe h2|/V", "<f4"),
69
+ 231: ("|Ewe h3|/V", "<f4"),
70
+ 232: ("|Ewe h4|/V", "<f4"),
71
+ 233: ("|Ewe h5|/V", "<f4"),
72
+ 234: ("|Ewe h6|/V", "<f4"),
73
+ 235: ("|Ewe h7|/V", "<f4"),
74
+ 236: ("|I h2|/A", "<f4"),
75
+ 237: ("|I h3|/A", "<f4"),
76
+ 238: ("|I h4|/A", "<f4"),
77
+ 239: ("|I h5|/A", "<f4"),
78
+ 240: ("|I h6|/A", "<f4"),
79
+ 241: ("|I h7|/A", "<f4"),
80
+ 242: ("|E2|/V", "<f4"),
81
+ 271: ("Phase(Z1) / deg", "<f4"),
82
+ 272: ("Phase(Z2) / deg", "<f4"),
83
+ 301: ("|Z1|/Ohm", "<f4"),
84
+ 302: ("|Z2|/Ohm", "<f4"),
85
+ 331: ("Re(Z1)/Ohm", "<f4"),
86
+ 332: ("Re(Z2)/Ohm", "<f4"),
87
+ 361: ("-Im(Z1)/Ohm", "<f4"),
88
+ 362: ("-Im(Z2)/Ohm", "<f4"),
89
+ 391: ("<E1>/V", "<f4"),
90
+ 392: ("<E2>/V", "<f4"),
91
+ 422: ("Phase(Zstack)/deg", "<f4"),
92
+ 423: ("|Zstack|/Ohm", "<f4"),
93
+ 424: ("Re(Zstack)/Ohm", "<f4"),
94
+ 425: ("-Im(Zstack)/Ohm", "<f4"),
95
+ 426: ("<Estack>/V", "<f4"),
96
+ 430: ("Phase(Zwe-ce)/deg", "<f4"),
97
+ 431: ("|Zwe-ce|/Ohm", "<f4"),
98
+ 432: ("Re(Zwe-ce)/Ohm", "<f4"),
99
+ 433: ("-Im(Zwe-ce)/Ohm", "<f4"),
100
+ 434: ("(Q-Qo)/C", "<f4"),
101
+ 435: ("dQ/C", "<f4"),
102
+ 438: ("step time/s", "<f8"),
103
+ 441: ("<Ecv>/V", "<f4"),
104
+ 462: ("Temperature/°C", "<f4"),
105
+ 467: ("Q charge/discharge/mA.h", "<f8"),
106
+ 468: ("half cycle", "<u4"),
107
+ 469: ("z cycle", "<u4"),
108
+ 471: ("<Ece>/V", "<f4"),
109
+ 473: ("THD Ewe/%", "<f4"),
110
+ 474: ("THD I/%", "<f4"),
111
+ 476: ("NSD Ewe/%", "<f4"),
112
+ 477: ("NSD I/%", "<f4"),
113
+ 479: ("NSR Ewe/%", "<f4"),
114
+ 480: ("NSR I/%", "<f4"),
115
+ 486: ("|Ewe h2|/V", "<f4"),
116
+ 487: ("|Ewe h3|/V", "<f4"),
117
+ 488: ("|Ewe h4|/V", "<f4"),
118
+ 489: ("|Ewe h5|/V", "<f4"),
119
+ 490: ("|Ewe h6|/V", "<f4"),
120
+ 491: ("|Ewe h7|/V", "<f4"),
121
+ 492: ("|I h2|/A", "<f4"),
122
+ 493: ("|I h3|/A", "<f4"),
123
+ 494: ("|I h4|/A", "<f4"),
124
+ 495: ("|I h5|/A", "<f4"),
125
+ 496: ("|I h6|/A", "<f4"),
126
+ 497: ("|I h7|/A", "<f4"),
127
+ 498: ("Q charge/mA.h", "<f8"),
128
+ 499: ("Q discharge/mA.h", "<f8"),
129
+ 500: ("step time/s", "<f8"),
130
+ 501: ("Efficiency/%", "<f8"),
131
+ 502: ("Capacity/mA.h", "<f8"),
132
+ 505: ("Rdc/Ohm", "<f4"),
133
+ 509: ("Acir/Dcir Control", "<u1"),
134
+ }
135
+
136
+ VMPdata_colID_flag_map = {
137
+ 1: ("mode", 0x03, np.uint8),
138
+ 2: ("ox/red", 0x04, np.bool_),
139
+ 3: ("error", 0x08, np.bool_),
140
+ 21: ("control changes", 0x10, np.bool_),
141
+ 31: ("Ns changes", 0x20, np.bool_),
142
+ 65: ("counter inc.", 0x80, np.bool_),
143
+ }
@@ -0,0 +1,73 @@
1
+ """Neware scales and states from NewareNDA (modified 2026-11-02).
2
+ Copyright (c) 2022-2024 SES AI Corporation. BSD-3-Clause; see licenses/NewareNDA.txt.
3
+ """
4
+
5
+ state_dict = {
6
+ 1: 'CC_Chg',
7
+ 2: 'CC_DChg',
8
+ 3: 'CV_Chg',
9
+ 4: 'Rest',
10
+ 5: 'Cycle',
11
+ 7: 'CCCV_Chg',
12
+ 8: 'CP_DChg',
13
+ 9: 'CP_Chg',
14
+ 10: 'CR_DChg',
15
+ 13: 'Pause',
16
+ 16: 'Pulse',
17
+ 17: 'SIM',
18
+ 19: 'CV_DChg',
19
+ 20: 'CCCV_DChg',
20
+ 21: 'Control',
21
+ 22: 'OCV',
22
+ 26: 'CPCV_DChg',
23
+ 27: 'CPCV_Chg'
24
+ }
25
+
26
+ # Define field scaling based on instrument Range setting
27
+ multiplier_dict = {
28
+ -100000000: 10,
29
+ -200000: 1e-2,
30
+ -100000: 1e-2,
31
+ -60000: 1e-2,
32
+ -30000: 1e-2,
33
+ -50000: 1e-2,
34
+ -20000: 1e-2,
35
+ -12000: 1e-2,
36
+ -10000: 1e-2,
37
+ -6000: 1e-2,
38
+ -5000: 1e-2,
39
+ -3000: 1e-2,
40
+ -2000: 1e-2,
41
+ -1000: 1e-2,
42
+ -500: 1e-3,
43
+ -100: 1e-3,
44
+ -50: 1e-4,
45
+ -25: 1e-4,
46
+ -20: 1e-4,
47
+ -10: 1e-4,
48
+ -5: 1e-5,
49
+ -2: 1e-5,
50
+ -1: 1e-5,
51
+ 0: 0,
52
+ 1: 1e-4,
53
+ 2: 1e-4,
54
+ 5: 1e-4,
55
+ 10: 1e-3,
56
+ 20: 1e-3,
57
+ 50: 1e-3,
58
+ 100: 1e-2,
59
+ 200: 1e-2,
60
+ 250: 1e-2,
61
+ 500: 1e-2,
62
+ 1000: 1e-1,
63
+ 6000: 1e-1,
64
+ 10000: 1e-1,
65
+ 12000: 1e-1,
66
+ 20000: 1e-1,
67
+ 30000: 1e-1,
68
+ 40000: 1e-1,
69
+ 50000: 1e-1,
70
+ 60000: 1e-1,
71
+ 100000: 1e-1,
72
+ 200000: 1e-1,
73
+ }
@@ -0,0 +1,418 @@
1
+ """Small, safe decoder for the records used by NOVA BinaryFormatter files.
2
+
3
+ This module decodes the wire representation into dictionaries and arrays. It
4
+ does not load .NET assemblies or instantiate any of the serialized classes.
5
+ """
6
+
7
+ # SPDX-FileCopyrightText: 2026 OpenAI
8
+ # SPDX-License-Identifier: GPL-3.0-or-later
9
+
10
+ import struct
11
+
12
+ import numpy as np
13
+
14
+
15
+ class NRBFError(ValueError):
16
+ """Raised when an invalid or unsupported NRBF stream is encountered."""
17
+
18
+
19
+ _RECORD_HEADER = 0
20
+ _RECORD_CLASS_WITH_ID = 1
21
+ _RECORD_SYSTEM_CLASS = 4
22
+ _RECORD_CLASS = 5
23
+ _RECORD_STRING = 6
24
+ _RECORD_BINARY_ARRAY = 7
25
+ _RECORD_PRIMITIVE = 8
26
+ _RECORD_REFERENCE = 9
27
+ _RECORD_NULL = 10
28
+ _RECORD_END = 11
29
+ _RECORD_LIBRARY = 12
30
+ _RECORD_NULL_256 = 13
31
+ _RECORD_NULL_MULTIPLE = 14
32
+ _RECORD_PRIMITIVE_ARRAY = 15
33
+ _RECORD_OBJECT_ARRAY = 16
34
+ _RECORD_STRING_ARRAY = 17
35
+
36
+ _TYPE_PRIMITIVE = 0
37
+ _TYPE_SYSTEM_CLASS = 3
38
+ _TYPE_CLASS = 4
39
+ _TYPE_PRIMITIVE_ARRAY = 7
40
+
41
+ _ARRAY_OFFSET_TYPES = (3, 4, 5)
42
+
43
+ # These value-type arrays dominate the size of high-rate NOVA files. NOVA
44
+ # also serializes the corresponding calculated signal arrays, which are what
45
+ # its own public API exposes. Decode the records but do not retain them.
46
+ _DISCARDED_ARRAY_CLASSES = (
47
+ "EcoChemie.Autolab.Adc164RawSample",
48
+ "EcoChemie.Autolab.Adc164RawCrSample",
49
+ )
50
+
51
+ _NUMPY_PRIMITIVES = {
52
+ 1: "?", # Boolean
53
+ 2: "u1", # Byte
54
+ 6: "<f8", # Double
55
+ 7: "<i2", # Int16
56
+ 8: "<i4", # Int32
57
+ 9: "<i8", # Int64
58
+ 10: "i1", # SByte
59
+ 11: "<f4", # Single
60
+ 12: "<i8", # TimeSpan
61
+ 13: "<u8", # DateTime
62
+ 14: "<u2", # UInt16
63
+ 15: "<u4", # UInt32
64
+ 16: "<u8", # UInt64
65
+ }
66
+
67
+
68
+ class NRBFParser:
69
+ """Decode one NRBF stream starting at *offset* in a bytes-like object."""
70
+
71
+ def __init__(self, data, offset=0):
72
+ self.data = memoryview(data)
73
+ self.offset = offset
74
+ self.objects = {}
75
+ self.libraries = {}
76
+ self.class_definitions = {}
77
+
78
+ def parse(self):
79
+ """Return the unresolved root object and stop just after MessageEnd."""
80
+ if self._u8() != _RECORD_HEADER:
81
+ raise self._error("stream does not start with an NRBF header")
82
+ root_id = self._i32()
83
+ self._i32() # header id
84
+ major = self._i32()
85
+ minor = self._i32()
86
+ if (major, minor) != (1, 0):
87
+ raise self._error("unsupported NRBF version %d.%d" % (major, minor))
88
+
89
+ while self.offset < len(self.data):
90
+ record_offset = self.offset
91
+ record = self._u8()
92
+ if record == _RECORD_END:
93
+ return self.objects.get(root_id)
94
+ if record == _RECORD_LIBRARY:
95
+ self._read_library()
96
+ elif record == _RECORD_CLASS:
97
+ self._read_class(False)
98
+ elif record == _RECORD_SYSTEM_CLASS:
99
+ self._read_class(True)
100
+ elif record == _RECORD_CLASS_WITH_ID:
101
+ self._read_class_with_id()
102
+ elif record == _RECORD_STRING:
103
+ self._read_string()
104
+ elif record == _RECORD_PRIMITIVE_ARRAY:
105
+ self._read_primitive_array()
106
+ elif record == _RECORD_OBJECT_ARRAY:
107
+ self._read_object_array()
108
+ elif record == _RECORD_STRING_ARRAY:
109
+ self._read_object_array()
110
+ elif record == _RECORD_BINARY_ARRAY:
111
+ self._read_binary_array()
112
+ else:
113
+ raise self._error(
114
+ "unsupported top-level record %d at offset %d"
115
+ % (record, record_offset)
116
+ )
117
+ raise self._error("NRBF stream has no MessageEnd record")
118
+
119
+ def resolve(self, value):
120
+ """Resolve one MemberReference, without recursively copying a graph."""
121
+ seen = set()
122
+ while isinstance(value, dict) and set(value) == {"__ref__"}:
123
+ object_id = value["__ref__"]
124
+ if object_id in seen:
125
+ return None
126
+ seen.add(object_id)
127
+ value = self.objects.get(object_id)
128
+ return value
129
+
130
+ def _error(self, message):
131
+ return NRBFError("%s (offset %d)" % (message, self.offset))
132
+
133
+ def _read(self, size):
134
+ end = self.offset + size
135
+ if size < 0 or end > len(self.data):
136
+ raise self._error("unexpected end of file")
137
+ result = self.data[self.offset:end]
138
+ self.offset = end
139
+ return result
140
+
141
+ def _unpack(self, fmt):
142
+ size = struct.calcsize(fmt)
143
+ return struct.unpack_from(fmt, self._read(size))[0]
144
+
145
+ def _u8(self):
146
+ return self._unpack("<B")
147
+
148
+ def _i8(self):
149
+ return self._unpack("<b")
150
+
151
+ def _u16(self):
152
+ return self._unpack("<H")
153
+
154
+ def _i16(self):
155
+ return self._unpack("<h")
156
+
157
+ def _u32(self):
158
+ return self._unpack("<I")
159
+
160
+ def _i32(self):
161
+ return self._unpack("<i")
162
+
163
+ def _u64(self):
164
+ return self._unpack("<Q")
165
+
166
+ def _i64(self):
167
+ return self._unpack("<q")
168
+
169
+ def _f32(self):
170
+ return self._unpack("<f")
171
+
172
+ def _f64(self):
173
+ return self._unpack("<d")
174
+
175
+ def _string(self):
176
+ length = 0
177
+ shift = 0
178
+ while True:
179
+ byte = self._u8()
180
+ length |= (byte & 0x7f) << shift
181
+ if not byte & 0x80:
182
+ break
183
+ shift += 7
184
+ if shift > 35:
185
+ raise self._error("invalid length-prefixed string")
186
+ try:
187
+ return self._read(length).tobytes().decode("utf-8")
188
+ except UnicodeDecodeError as exc:
189
+ raise self._error("invalid UTF-8 string: %s" % exc)
190
+
191
+ def _primitive(self, primitive_type):
192
+ readers = {
193
+ 1: self._u8,
194
+ 2: self._u8,
195
+ 6: self._f64,
196
+ 7: self._i16,
197
+ 8: self._i32,
198
+ 9: self._i64,
199
+ 10: self._i8,
200
+ 11: self._f32,
201
+ 12: self._i64,
202
+ 13: self._u64,
203
+ 14: self._u16,
204
+ 15: self._u32,
205
+ 16: self._u64,
206
+ }
207
+ if primitive_type == 1:
208
+ return bool(self._u8())
209
+ if primitive_type == 3: # Char is UTF-8 encoded by BinaryFormatter.
210
+ first = self._u8()
211
+ if first < 0x80:
212
+ return chr(first)
213
+ count = 1 if first < 0xe0 else 2 if first < 0xf0 else 3
214
+ raw = bytes([first] + [self._u8() for unused in range(count)])
215
+ return raw.decode("utf-8")
216
+ if primitive_type == 5: # Decimal is written as a string.
217
+ return self._string()
218
+ try:
219
+ return readers[primitive_type]()
220
+ except KeyError:
221
+ raise self._error("unsupported primitive type %d" % primitive_type)
222
+
223
+ def _read_library(self):
224
+ library_id = self._i32()
225
+ self.libraries[library_id] = self._string()
226
+
227
+ def _read_class_info(self):
228
+ object_id = self._i32()
229
+ name = self._string()
230
+ count = self._i32()
231
+ if count < 0 or count > 100000:
232
+ raise self._error("invalid class member count %d" % count)
233
+ names = [self._string() for unused in range(count)]
234
+ return object_id, name, names
235
+
236
+ def _read_type_info(self, count):
237
+ binary_types = [self._u8() for unused in range(count)]
238
+ additional = []
239
+ for binary_type in binary_types:
240
+ if binary_type in (_TYPE_PRIMITIVE, _TYPE_PRIMITIVE_ARRAY):
241
+ additional.append(self._u8())
242
+ elif binary_type == _TYPE_SYSTEM_CLASS:
243
+ additional.append(self._string())
244
+ elif binary_type == _TYPE_CLASS:
245
+ additional.append((self._string(), self._i32()))
246
+ elif binary_type in (1, 2, 5, 6):
247
+ additional.append(None)
248
+ else:
249
+ raise self._error("unsupported binary type %d" % binary_type)
250
+ return binary_types, additional
251
+
252
+ def _read_class(self, system_class):
253
+ object_id, name, names = self._read_class_info()
254
+ binary_types, additional = self._read_type_info(len(names))
255
+ if not system_class:
256
+ self._i32() # library id
257
+ definition = (name, names, binary_types, additional)
258
+ self.class_definitions[object_id] = definition
259
+ obj = self._read_class_value(object_id, definition)
260
+ return obj
261
+
262
+ def _read_class_with_id(self):
263
+ object_id = self._i32()
264
+ metadata_id = self._i32()
265
+ try:
266
+ definition = self.class_definitions[metadata_id]
267
+ except KeyError:
268
+ raise self._error("unknown class metadata id %d" % metadata_id)
269
+ return self._read_class_value(object_id, definition)
270
+
271
+ def _read_class_value(self, object_id, definition):
272
+ name, names, binary_types, additional = definition
273
+ discard = name in _DISCARDED_ARRAY_CLASSES
274
+ if discard:
275
+ for binary_type, extra in zip(binary_types, additional):
276
+ self._read_value(binary_type, extra)
277
+ return None
278
+ values = [
279
+ self._read_value(binary_type, extra)
280
+ for binary_type, extra in zip(binary_types, additional)
281
+ ]
282
+ obj = dict(zip(names, values))
283
+ obj["__class__"] = name
284
+ self.objects[object_id] = obj
285
+ return obj
286
+
287
+ def _read_value(self, binary_type, additional):
288
+ if binary_type == _TYPE_PRIMITIVE:
289
+ return self._primitive(additional)
290
+ return self._read_inline()
291
+
292
+ def _read_inline(self):
293
+ record_offset = self.offset
294
+ record = self._u8()
295
+ if record == _RECORD_STRING:
296
+ return self._read_string()
297
+ if record == _RECORD_REFERENCE:
298
+ return {"__ref__": self._i32()}
299
+ if record == _RECORD_NULL:
300
+ return None
301
+ if record == _RECORD_CLASS:
302
+ obj = self._read_class(False)
303
+ return obj
304
+ if record == _RECORD_SYSTEM_CLASS:
305
+ return self._read_class(True)
306
+ if record == _RECORD_CLASS_WITH_ID:
307
+ return self._read_class_with_id()
308
+ if record == _RECORD_PRIMITIVE_ARRAY:
309
+ return self._read_primitive_array()
310
+ if record in (_RECORD_OBJECT_ARRAY, _RECORD_STRING_ARRAY):
311
+ return self._read_object_array()
312
+ if record == _RECORD_BINARY_ARRAY:
313
+ return self._read_binary_array()
314
+ if record == _RECORD_PRIMITIVE:
315
+ return self._primitive(self._u8())
316
+ if record == _RECORD_LIBRARY:
317
+ self._read_library()
318
+ return self._read_inline()
319
+ raise self._error(
320
+ "unsupported inline record %d at offset %d" % (record, record_offset)
321
+ )
322
+
323
+ def _read_string(self):
324
+ object_id = self._i32()
325
+ value = self._string()
326
+ self.objects[object_id] = value
327
+ return value
328
+
329
+ def _read_primitive_array(self):
330
+ object_id = self._i32()
331
+ length = self._i32()
332
+ primitive_type = self._u8()
333
+ value = self._primitive_values(length, primitive_type)
334
+ self.objects[object_id] = value
335
+ return value
336
+
337
+ def _primitive_values(self, length, primitive_type):
338
+ if length < 0:
339
+ raise self._error("negative array length")
340
+ dtype = _NUMPY_PRIMITIVES.get(primitive_type)
341
+ if dtype is None:
342
+ return np.asarray(
343
+ [self._primitive(primitive_type) for unused in range(length)]
344
+ )
345
+ dtype = np.dtype(dtype)
346
+ raw = self._read(length * dtype.itemsize)
347
+ return np.frombuffer(raw, dtype=dtype, count=length)
348
+
349
+ def _read_object_array(self):
350
+ object_id = self._i32()
351
+ length = self._i32()
352
+ value = self._read_array_values(length)
353
+ self.objects[object_id] = value
354
+ return value
355
+
356
+ def _read_array_values(self, length, discard=False):
357
+ if length < 0:
358
+ raise self._error("negative array length")
359
+ value = []
360
+ count = 0
361
+ while count < length:
362
+ record_offset = self.offset
363
+ record = self._u8()
364
+ if record == _RECORD_NULL:
365
+ null_count = 1
366
+ elif record == _RECORD_NULL_256:
367
+ null_count = self._u8()
368
+ elif record == _RECORD_NULL_MULTIPLE:
369
+ null_count = self._i32()
370
+ else:
371
+ self.offset = record_offset
372
+ item = self._read_inline()
373
+ if not discard:
374
+ value.append(item)
375
+ count += 1
376
+ continue
377
+ if null_count < 0 or count + null_count > length:
378
+ raise self._error("invalid null run in array")
379
+ if not discard:
380
+ value.extend([None] * null_count)
381
+ count += null_count
382
+ return None if discard else value
383
+
384
+ def _read_binary_array(self):
385
+ object_id = self._i32()
386
+ array_type = self._u8()
387
+ rank = self._i32()
388
+ if rank < 1 or rank > 32:
389
+ raise self._error("invalid array rank %d" % rank)
390
+ lengths = [self._i32() for unused in range(rank)]
391
+ if any(length < 0 for length in lengths):
392
+ raise self._error("negative array length")
393
+ if array_type in _ARRAY_OFFSET_TYPES:
394
+ for unused in range(rank):
395
+ self._i32()
396
+ binary_type = self._u8()
397
+ additional = None
398
+ if binary_type in (_TYPE_PRIMITIVE, _TYPE_PRIMITIVE_ARRAY):
399
+ additional = self._u8()
400
+ elif binary_type == _TYPE_SYSTEM_CLASS:
401
+ additional = self._string()
402
+ elif binary_type == _TYPE_CLASS:
403
+ additional = (self._string(), self._i32())
404
+ elif binary_type not in (1, 2, 5, 6):
405
+ raise self._error("unsupported array binary type %d" % binary_type)
406
+
407
+ length = 1
408
+ for dimension in lengths:
409
+ length *= dimension
410
+ if binary_type == _TYPE_PRIMITIVE:
411
+ value = self._primitive_values(length, additional)
412
+ else:
413
+ class_name = additional[0] if binary_type == _TYPE_CLASS else additional
414
+ value = self._read_array_values(
415
+ length, discard=class_name in _DISCARDED_ARRAY_CLASSES
416
+ )
417
+ self.objects[object_id] = value
418
+ return value