binsparse 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.
- binsparse/__init__.py +66 -0
- binsparse/container.py +328 -0
- binsparse/conversions/__init__.py +17 -0
- binsparse/conversions/numpy.py +64 -0
- binsparse/conversions/scipy.py +98 -0
- binsparse/conversions/sparse.py +92 -0
- binsparse/conversions/torch.py +179 -0
- binsparse/errors.py +5 -0
- binsparse/io.py +147 -0
- binsparse/tensor.py +577 -0
- binsparse/version.py +29 -0
- binsparse-0.1.0.dist-info/METADATA +18 -0
- binsparse-0.1.0.dist-info/RECORD +18 -0
- binsparse-0.1.0.dist-info/WHEEL +4 -0
- reference_cli/__init__.py +1 -0
- reference_cli/converters.py +178 -0
- reference_cli/framework.py +81 -0
- reference_cli/reformat.py +290 -0
binsparse/tensor.py
ADDED
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
"""In-memory representations and container parsing for Binsparse tensors."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any, ClassVar
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from .container import BinsparseContainer
|
|
12
|
+
from .errors import BinsparseParseError
|
|
13
|
+
from .version import BINSPARSE_VERSION, check_binsparse_version
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BinsparseLevel:
|
|
17
|
+
level_desc: ClassVar[str]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class IndexableLevel(BinsparseLevel):
|
|
22
|
+
rank: int
|
|
23
|
+
|
|
24
|
+
def __post_init__(self) -> None:
|
|
25
|
+
if self.rank < 1:
|
|
26
|
+
raise ValueError("an indexable level must have rank >= 1")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class ElementLevel(BinsparseLevel):
|
|
31
|
+
values: np.ndarray
|
|
32
|
+
level_desc: ClassVar[str] = "element"
|
|
33
|
+
rank: ClassVar[int] = 0
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class DenseLevel(IndexableLevel):
|
|
38
|
+
level: BinsparseLevel
|
|
39
|
+
level_desc: ClassVar[str] = "dense"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class SparseLevel(IndexableLevel):
|
|
44
|
+
level: BinsparseLevel
|
|
45
|
+
indices: tuple[np.ndarray, ...]
|
|
46
|
+
pointers_to_next: np.ndarray | None = None
|
|
47
|
+
level_desc: ClassVar[str] = "sparse"
|
|
48
|
+
|
|
49
|
+
def __post_init__(self) -> None:
|
|
50
|
+
super().__post_init__()
|
|
51
|
+
if len(self.indices) != self.rank:
|
|
52
|
+
raise ValueError("the number of indices arrays must equal rank")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class BinsparseTensor(ABC):
|
|
57
|
+
shape: tuple[int, ...]
|
|
58
|
+
number_of_stored_values: int
|
|
59
|
+
fill: bool | None = None
|
|
60
|
+
fill_value: Any = None
|
|
61
|
+
|
|
62
|
+
version: ClassVar[str] = BINSPARSE_VERSION
|
|
63
|
+
format: ClassVar[str]
|
|
64
|
+
|
|
65
|
+
def __post_init__(self) -> None:
|
|
66
|
+
self.shape = tuple(self.shape)
|
|
67
|
+
if any(size < 0 for size in self.shape):
|
|
68
|
+
raise ValueError("shape dimensions must be non-negative")
|
|
69
|
+
if self.number_of_stored_values < 0:
|
|
70
|
+
raise ValueError("number_of_stored_values must be non-negative")
|
|
71
|
+
if self.fill is not True and self.fill_value is not None:
|
|
72
|
+
raise ValueError("fill_value requires fill=True")
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def parse(
|
|
76
|
+
cls,
|
|
77
|
+
container: BinsparseContainer,
|
|
78
|
+
*,
|
|
79
|
+
alias: bool | None = None,
|
|
80
|
+
copy: bool | None = None,
|
|
81
|
+
) -> BinsparseTensor:
|
|
82
|
+
"""Parse a tensor and all required arrays from *container*."""
|
|
83
|
+
header = container.read_header()
|
|
84
|
+
required = ("version", "format", "shape", "number_of_stored_values")
|
|
85
|
+
missing = next((key for key in required if key not in header), None)
|
|
86
|
+
if missing is not None:
|
|
87
|
+
raise BinsparseParseError(f"missing required descriptor key {missing!r}")
|
|
88
|
+
check_binsparse_version(header["version"])
|
|
89
|
+
format_name = header["format"]
|
|
90
|
+
try:
|
|
91
|
+
if hasattr(cls, "format"):
|
|
92
|
+
if cls.format != format_name:
|
|
93
|
+
raise BinsparseParseError(
|
|
94
|
+
f"descriptor format {format_name!r} cannot be parsed as "
|
|
95
|
+
f"{cls.__name__}"
|
|
96
|
+
)
|
|
97
|
+
tensor_cls = cls
|
|
98
|
+
elif alias is False:
|
|
99
|
+
tensor_cls = CustomTensor
|
|
100
|
+
elif alias is True and format_name == "custom":
|
|
101
|
+
alias_name = _custom_alias(header)
|
|
102
|
+
tensor_cls = (
|
|
103
|
+
CustomTensor
|
|
104
|
+
if alias_name is None
|
|
105
|
+
else _FORMAT_CLASSES[alias_name]
|
|
106
|
+
)
|
|
107
|
+
else:
|
|
108
|
+
tensor_cls = _FORMAT_CLASSES[format_name]
|
|
109
|
+
except KeyError as error:
|
|
110
|
+
raise BinsparseParseError(f"unrecognized format {format_name!r}") from error
|
|
111
|
+
common: dict[str, Any] = {
|
|
112
|
+
"shape": tuple(header["shape"]),
|
|
113
|
+
"number_of_stored_values": header["number_of_stored_values"],
|
|
114
|
+
"fill": header.get("fill"),
|
|
115
|
+
}
|
|
116
|
+
if common["fill"] is True:
|
|
117
|
+
fill = container.read_buffer("fill_value", 1, copy=copy)
|
|
118
|
+
if fill.size != 1:
|
|
119
|
+
raise BinsparseParseError("fill_value must contain exactly one value")
|
|
120
|
+
common["fill_value"] = fill.reshape(-1)[0]
|
|
121
|
+
return tensor_cls._parse(container, header, common, copy)
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
@abstractmethod
|
|
125
|
+
def _parse(
|
|
126
|
+
cls,
|
|
127
|
+
container: BinsparseContainer,
|
|
128
|
+
header: dict[str, Any],
|
|
129
|
+
common: dict[str, Any],
|
|
130
|
+
copy: bool | None,
|
|
131
|
+
) -> BinsparseTensor:
|
|
132
|
+
"""Parse format-specific data after shared metadata has been parsed."""
|
|
133
|
+
|
|
134
|
+
def serialize(
|
|
135
|
+
self,
|
|
136
|
+
container: BinsparseContainer,
|
|
137
|
+
*,
|
|
138
|
+
alias: bool | None = None,
|
|
139
|
+
copy: bool | None = None,
|
|
140
|
+
) -> None:
|
|
141
|
+
"""Write shared metadata, format-specific data, and the descriptor."""
|
|
142
|
+
header: dict[str, Any] = {
|
|
143
|
+
"version": BINSPARSE_VERSION,
|
|
144
|
+
"format": self.format,
|
|
145
|
+
"shape": list(self.shape),
|
|
146
|
+
"number_of_stored_values": self.number_of_stored_values,
|
|
147
|
+
}
|
|
148
|
+
if self.fill is not None:
|
|
149
|
+
header["fill"] = self.fill
|
|
150
|
+
if self.fill is True:
|
|
151
|
+
fill = np.asarray([self.fill_value])
|
|
152
|
+
container.write_buffer("fill_value", fill, copy=copy)
|
|
153
|
+
header.update(self._serialize(container, copy))
|
|
154
|
+
if alias is False and self.format != "custom":
|
|
155
|
+
descriptor, transpose = _PREDEFINED_LEVELS[self.format]
|
|
156
|
+
custom: dict[str, Any] = {"level": descriptor}
|
|
157
|
+
if transpose is not None:
|
|
158
|
+
custom["transpose"] = list(transpose)
|
|
159
|
+
header.update(format="custom", custom=custom)
|
|
160
|
+
elif alias is True and self.format == "custom":
|
|
161
|
+
alias_name = _custom_alias(header)
|
|
162
|
+
if alias_name is not None:
|
|
163
|
+
header["format"] = alias_name
|
|
164
|
+
header.pop("custom")
|
|
165
|
+
container.write_header(header)
|
|
166
|
+
|
|
167
|
+
@abstractmethod
|
|
168
|
+
def _serialize(
|
|
169
|
+
self,
|
|
170
|
+
container: BinsparseContainer,
|
|
171
|
+
copy: bool | None,
|
|
172
|
+
) -> dict[str, Any]:
|
|
173
|
+
"""Write format-specific arrays and return additional descriptor data."""
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@dataclass
|
|
177
|
+
class CustomTensor(BinsparseTensor):
|
|
178
|
+
level: BinsparseLevel | None = None
|
|
179
|
+
transpose: tuple[int, ...] | None = None
|
|
180
|
+
format: ClassVar[str] = "custom"
|
|
181
|
+
|
|
182
|
+
def __post_init__(self) -> None:
|
|
183
|
+
super().__post_init__()
|
|
184
|
+
if self.level is None:
|
|
185
|
+
raise ValueError("a custom tensor requires a root level")
|
|
186
|
+
if self.transpose is not None:
|
|
187
|
+
dimensions = set(range(len(self.shape)))
|
|
188
|
+
if len(self.transpose) != len(self.shape) or set(self.transpose) != dimensions:
|
|
189
|
+
raise ValueError("transpose must be a permutation of the dimensions")
|
|
190
|
+
if self._level_rank(self.level) != len(self.shape):
|
|
191
|
+
raise ValueError("custom levels must consume exactly the tensor rank")
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def _level_rank(level: BinsparseLevel) -> int:
|
|
195
|
+
if isinstance(level, ElementLevel):
|
|
196
|
+
return 0
|
|
197
|
+
if isinstance(level, (DenseLevel, SparseLevel)):
|
|
198
|
+
return level.rank + CustomTensor._level_rank(level.level)
|
|
199
|
+
raise TypeError(f"unsupported level type {type(level).__name__}")
|
|
200
|
+
|
|
201
|
+
@classmethod
|
|
202
|
+
def _parse(
|
|
203
|
+
cls,
|
|
204
|
+
container: BinsparseContainer,
|
|
205
|
+
header: dict[str, Any],
|
|
206
|
+
common: dict[str, Any],
|
|
207
|
+
copy: bool | None,
|
|
208
|
+
) -> BinsparseTensor:
|
|
209
|
+
format_name = header["format"]
|
|
210
|
+
if format_name == "custom":
|
|
211
|
+
try:
|
|
212
|
+
custom = header["custom"]
|
|
213
|
+
descriptor = custom["level"]
|
|
214
|
+
except (KeyError, TypeError) as error:
|
|
215
|
+
raise BinsparseParseError("custom format requires custom.level") from error
|
|
216
|
+
transpose = custom.get("transpose")
|
|
217
|
+
else:
|
|
218
|
+
try:
|
|
219
|
+
descriptor, transpose = _PREDEFINED_LEVELS[format_name]
|
|
220
|
+
except KeyError as error:
|
|
221
|
+
raise BinsparseParseError(f"unrecognized format {format_name!r}") from error
|
|
222
|
+
transpose_tuple = None if transpose is None else tuple(transpose)
|
|
223
|
+
level = cls._parse_level(container, header, descriptor, 0, copy)
|
|
224
|
+
return cls(
|
|
225
|
+
**common,
|
|
226
|
+
level=level,
|
|
227
|
+
transpose=transpose_tuple,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
@staticmethod
|
|
231
|
+
def _parse_level(
|
|
232
|
+
container: BinsparseContainer,
|
|
233
|
+
header: dict[str, Any],
|
|
234
|
+
descriptor: dict[str, Any],
|
|
235
|
+
depth: int,
|
|
236
|
+
copy: bool | None,
|
|
237
|
+
) -> BinsparseLevel:
|
|
238
|
+
try:
|
|
239
|
+
level_desc = descriptor["level_desc"]
|
|
240
|
+
except (KeyError, TypeError) as error:
|
|
241
|
+
raise BinsparseParseError("each custom level requires level_desc") from error
|
|
242
|
+
match level_desc:
|
|
243
|
+
case "element":
|
|
244
|
+
count = header["number_of_stored_values"]
|
|
245
|
+
return ElementLevel(container.read_buffer("values", count, copy=copy))
|
|
246
|
+
case "dense":
|
|
247
|
+
try:
|
|
248
|
+
rank = descriptor["rank"]
|
|
249
|
+
child_descriptor = descriptor["level"]
|
|
250
|
+
except KeyError as error:
|
|
251
|
+
raise BinsparseParseError(
|
|
252
|
+
f"dense level requires {error.args[0]!r}"
|
|
253
|
+
) from error
|
|
254
|
+
if rank < 1:
|
|
255
|
+
raise BinsparseParseError("dense level rank must be >= 1")
|
|
256
|
+
child = CustomTensor._parse_level(
|
|
257
|
+
container,
|
|
258
|
+
header,
|
|
259
|
+
child_descriptor,
|
|
260
|
+
depth + rank,
|
|
261
|
+
copy,
|
|
262
|
+
)
|
|
263
|
+
return DenseLevel(rank, child)
|
|
264
|
+
case "sparse":
|
|
265
|
+
try:
|
|
266
|
+
rank = descriptor["rank"]
|
|
267
|
+
child_descriptor = descriptor["level"]
|
|
268
|
+
except KeyError as error:
|
|
269
|
+
raise BinsparseParseError(
|
|
270
|
+
f"sparse level requires {error.args[0]!r}"
|
|
271
|
+
) from error
|
|
272
|
+
if rank < 1:
|
|
273
|
+
raise BinsparseParseError("sparse level rank must be >= 1")
|
|
274
|
+
pointers = (
|
|
275
|
+
None
|
|
276
|
+
if depth == 0
|
|
277
|
+
else container.read_buffer(f"pointers_to_{depth}", copy=copy)
|
|
278
|
+
)
|
|
279
|
+
indices = tuple(
|
|
280
|
+
container.read_buffer(f"indices_{dimension}", copy=copy)
|
|
281
|
+
for dimension in range(depth, depth + rank)
|
|
282
|
+
)
|
|
283
|
+
child = CustomTensor._parse_level(
|
|
284
|
+
container,
|
|
285
|
+
header,
|
|
286
|
+
child_descriptor,
|
|
287
|
+
depth + rank,
|
|
288
|
+
copy,
|
|
289
|
+
)
|
|
290
|
+
return SparseLevel(rank, child, indices, pointers)
|
|
291
|
+
case _:
|
|
292
|
+
raise BinsparseParseError(
|
|
293
|
+
f"unrecognized level descriptor {level_desc!r}"
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
def _serialize(
|
|
297
|
+
self,
|
|
298
|
+
container: BinsparseContainer,
|
|
299
|
+
copy: bool | None,
|
|
300
|
+
) -> dict[str, Any]:
|
|
301
|
+
descriptor = self._serialize_level(container, self.level, 0, copy)
|
|
302
|
+
custom: dict[str, Any] = {"level": descriptor}
|
|
303
|
+
if self.transpose is not None:
|
|
304
|
+
custom["transpose"] = list(self.transpose)
|
|
305
|
+
return {"custom": custom}
|
|
306
|
+
|
|
307
|
+
@staticmethod
|
|
308
|
+
def _serialize_level(
|
|
309
|
+
container: BinsparseContainer,
|
|
310
|
+
level: BinsparseLevel | None,
|
|
311
|
+
depth: int,
|
|
312
|
+
copy: bool | None,
|
|
313
|
+
) -> dict[str, Any]:
|
|
314
|
+
match level:
|
|
315
|
+
case ElementLevel(values):
|
|
316
|
+
container.write_buffer("values", np.asarray(values), copy=copy)
|
|
317
|
+
return {"level_desc": "element"}
|
|
318
|
+
case DenseLevel(rank, child_level):
|
|
319
|
+
child = CustomTensor._serialize_level(
|
|
320
|
+
container, child_level, depth + rank, copy
|
|
321
|
+
)
|
|
322
|
+
return {"level_desc": "dense", "rank": rank, "level": child}
|
|
323
|
+
case SparseLevel(rank, child_level, indices, pointers):
|
|
324
|
+
if depth > 0:
|
|
325
|
+
if pointers is None:
|
|
326
|
+
raise ValueError(
|
|
327
|
+
f"sparse level at depth {depth} requires pointers"
|
|
328
|
+
)
|
|
329
|
+
name = f"pointers_to_{depth}"
|
|
330
|
+
container.write_buffer(name, np.asarray(pointers), copy=copy)
|
|
331
|
+
for offset, index in enumerate(indices):
|
|
332
|
+
name = f"indices_{depth + offset}"
|
|
333
|
+
container.write_buffer(name, np.asarray(index), copy=copy)
|
|
334
|
+
child = CustomTensor._serialize_level(
|
|
335
|
+
container, child_level, depth + rank, copy
|
|
336
|
+
)
|
|
337
|
+
return {"level_desc": "sparse", "rank": rank, "level": child}
|
|
338
|
+
case _:
|
|
339
|
+
raise TypeError(f"unsupported level type {type(level).__name__}")
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
@dataclass
|
|
343
|
+
class DVECVector(BinsparseTensor):
|
|
344
|
+
values: Any = None
|
|
345
|
+
format: ClassVar[str] = "DVEC"
|
|
346
|
+
|
|
347
|
+
@classmethod
|
|
348
|
+
def _parse(cls, container, header, common, copy):
|
|
349
|
+
count = common["number_of_stored_values"]
|
|
350
|
+
return cls(**common, values=container.read_buffer("values", count, copy=copy))
|
|
351
|
+
|
|
352
|
+
def _serialize(self, container, copy):
|
|
353
|
+
container.write_buffer("values", np.asarray(self.values), copy=copy)
|
|
354
|
+
return {}
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@dataclass
|
|
358
|
+
class DMATRMatrix(BinsparseTensor):
|
|
359
|
+
values: Any = None
|
|
360
|
+
format: ClassVar[str] = "DMATR"
|
|
361
|
+
|
|
362
|
+
@classmethod
|
|
363
|
+
def _parse(cls, container, header, common, copy):
|
|
364
|
+
count = common["number_of_stored_values"]
|
|
365
|
+
return cls(**common, values=container.read_buffer("values", count, copy=copy))
|
|
366
|
+
|
|
367
|
+
def _serialize(self, container, copy):
|
|
368
|
+
container.write_buffer("values", np.asarray(self.values), copy=copy)
|
|
369
|
+
return {}
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
@dataclass
|
|
373
|
+
class DMATCMatrix(BinsparseTensor):
|
|
374
|
+
values: Any = None
|
|
375
|
+
format: ClassVar[str] = "DMATC"
|
|
376
|
+
|
|
377
|
+
@classmethod
|
|
378
|
+
def _parse(cls, container, header, common, copy):
|
|
379
|
+
count = common["number_of_stored_values"]
|
|
380
|
+
return cls(**common, values=container.read_buffer("values", count, copy=copy))
|
|
381
|
+
|
|
382
|
+
def _serialize(self, container, copy):
|
|
383
|
+
container.write_buffer("values", np.asarray(self.values), copy=copy)
|
|
384
|
+
return {}
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
DMATMatrix = DMATRMatrix
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
@dataclass
|
|
391
|
+
class CVECVector(BinsparseTensor):
|
|
392
|
+
indices_0: Any = None
|
|
393
|
+
values: Any = None
|
|
394
|
+
format: ClassVar[str] = "CVEC"
|
|
395
|
+
|
|
396
|
+
@classmethod
|
|
397
|
+
def _parse(cls, container, header, common, copy):
|
|
398
|
+
count = common["number_of_stored_values"]
|
|
399
|
+
return cls(
|
|
400
|
+
**common,
|
|
401
|
+
indices_0=container.read_buffer("indices_0", copy=copy),
|
|
402
|
+
values=container.read_buffer("values", count, copy=copy),
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
def _serialize(self, container, copy):
|
|
406
|
+
container.write_buffer("indices_0", np.asarray(self.indices_0), copy=copy)
|
|
407
|
+
container.write_buffer("values", np.asarray(self.values), copy=copy)
|
|
408
|
+
return {}
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
@dataclass
|
|
412
|
+
class CSRMatrix(BinsparseTensor):
|
|
413
|
+
pointers_to_1: Any = None
|
|
414
|
+
indices_1: Any = None
|
|
415
|
+
values: Any = None
|
|
416
|
+
format: ClassVar[str] = "CSR"
|
|
417
|
+
|
|
418
|
+
@classmethod
|
|
419
|
+
def _parse(cls, container, header, common, copy):
|
|
420
|
+
count = common["number_of_stored_values"]
|
|
421
|
+
return cls(
|
|
422
|
+
**common,
|
|
423
|
+
pointers_to_1=container.read_buffer("pointers_to_1", copy=copy),
|
|
424
|
+
indices_1=container.read_buffer("indices_1", copy=copy),
|
|
425
|
+
values=container.read_buffer("values", count, copy=copy),
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
def _serialize(self, container, copy):
|
|
429
|
+
container.write_buffer(
|
|
430
|
+
"pointers_to_1", np.asarray(self.pointers_to_1), copy=copy
|
|
431
|
+
)
|
|
432
|
+
container.write_buffer("indices_1", np.asarray(self.indices_1), copy=copy)
|
|
433
|
+
container.write_buffer("values", np.asarray(self.values), copy=copy)
|
|
434
|
+
return {}
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
@dataclass
|
|
438
|
+
class CSCMatrix(CSRMatrix):
|
|
439
|
+
format: ClassVar[str] = "CSC"
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
@dataclass
|
|
443
|
+
class DCSRMatrix(BinsparseTensor):
|
|
444
|
+
indices_0: Any = None
|
|
445
|
+
pointers_to_1: Any = None
|
|
446
|
+
indices_1: Any = None
|
|
447
|
+
values: Any = None
|
|
448
|
+
format: ClassVar[str] = "DCSR"
|
|
449
|
+
|
|
450
|
+
@classmethod
|
|
451
|
+
def _parse(cls, container, header, common, copy):
|
|
452
|
+
count = common["number_of_stored_values"]
|
|
453
|
+
indices_0 = container.read_buffer("indices_0", copy=copy)
|
|
454
|
+
return cls(
|
|
455
|
+
**common,
|
|
456
|
+
indices_0=indices_0,
|
|
457
|
+
pointers_to_1=container.read_buffer("pointers_to_1", copy=copy),
|
|
458
|
+
indices_1=container.read_buffer("indices_1", copy=copy),
|
|
459
|
+
values=container.read_buffer("values", count, copy=copy),
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
def _serialize(self, container, copy):
|
|
463
|
+
container.write_buffer("indices_0", np.asarray(self.indices_0), copy=copy)
|
|
464
|
+
container.write_buffer(
|
|
465
|
+
"pointers_to_1", np.asarray(self.pointers_to_1), copy=copy
|
|
466
|
+
)
|
|
467
|
+
container.write_buffer("indices_1", np.asarray(self.indices_1), copy=copy)
|
|
468
|
+
container.write_buffer("values", np.asarray(self.values), copy=copy)
|
|
469
|
+
return {}
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
@dataclass
|
|
473
|
+
class DCSCMatrix(DCSRMatrix):
|
|
474
|
+
format: ClassVar[str] = "DCSC"
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
@dataclass
|
|
478
|
+
class COORMatrix(BinsparseTensor):
|
|
479
|
+
indices_0: Any = None
|
|
480
|
+
indices_1: Any = None
|
|
481
|
+
values: Any = None
|
|
482
|
+
format: ClassVar[str] = "COOR"
|
|
483
|
+
|
|
484
|
+
@classmethod
|
|
485
|
+
def _parse(cls, container, header, common, copy):
|
|
486
|
+
count = common["number_of_stored_values"]
|
|
487
|
+
return cls(
|
|
488
|
+
**common,
|
|
489
|
+
indices_0=container.read_buffer("indices_0", copy=copy),
|
|
490
|
+
indices_1=container.read_buffer("indices_1", copy=copy),
|
|
491
|
+
values=container.read_buffer("values", count, copy=copy),
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
def _serialize(self, container, copy):
|
|
495
|
+
container.write_buffer("indices_0", np.asarray(self.indices_0), copy=copy)
|
|
496
|
+
container.write_buffer("indices_1", np.asarray(self.indices_1), copy=copy)
|
|
497
|
+
container.write_buffer("values", np.asarray(self.values), copy=copy)
|
|
498
|
+
return {}
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
@dataclass
|
|
502
|
+
class COOCMatrix(COORMatrix):
|
|
503
|
+
format: ClassVar[str] = "COOC"
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
COOMatrix = COORMatrix
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
_FORMAT_CLASSES: dict[str, type[BinsparseTensor]] = {
|
|
510
|
+
"custom": CustomTensor,
|
|
511
|
+
"DVEC": DVECVector,
|
|
512
|
+
"DMAT": DMATRMatrix,
|
|
513
|
+
"DMATR": DMATRMatrix,
|
|
514
|
+
"DMATC": DMATCMatrix,
|
|
515
|
+
"CVEC": CVECVector,
|
|
516
|
+
"CSR": CSRMatrix,
|
|
517
|
+
"CSC": CSCMatrix,
|
|
518
|
+
"DCSR": DCSRMatrix,
|
|
519
|
+
"DCSC": DCSCMatrix,
|
|
520
|
+
"COO": COORMatrix,
|
|
521
|
+
"COOR": COORMatrix,
|
|
522
|
+
"COOC": COOCMatrix,
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
_ELEMENT = {"level_desc": "element"}
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def _dense(level: dict[str, Any], rank: int = 1) -> dict[str, Any]:
|
|
530
|
+
return {"level_desc": "dense", "rank": rank, "level": level}
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def _sparse(level: dict[str, Any], rank: int = 1) -> dict[str, Any]:
|
|
534
|
+
return {"level_desc": "sparse", "rank": rank, "level": level}
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
_PREDEFINED_LEVELS: dict[str, tuple[dict[str, Any], tuple[int, ...] | None]] = {
|
|
538
|
+
"DVEC": (_dense(_ELEMENT), None),
|
|
539
|
+
"DMATR": (_dense(_dense(_ELEMENT)), None),
|
|
540
|
+
"DMAT": (_dense(_dense(_ELEMENT)), None),
|
|
541
|
+
"DMATC": (_dense(_dense(_ELEMENT)), (1, 0)),
|
|
542
|
+
"CVEC": (_sparse(_ELEMENT), None),
|
|
543
|
+
"CSR": (_dense(_sparse(_ELEMENT)), None),
|
|
544
|
+
"CSC": (_dense(_sparse(_ELEMENT)), (1, 0)),
|
|
545
|
+
"DCSR": (_sparse(_sparse(_ELEMENT)), None),
|
|
546
|
+
"DCSC": (_sparse(_sparse(_ELEMENT)), (1, 0)),
|
|
547
|
+
"COOR": (_sparse(_ELEMENT, 2), None),
|
|
548
|
+
"COO": (_sparse(_ELEMENT, 2), None),
|
|
549
|
+
"COOC": (_sparse(_ELEMENT, 2), (1, 0)),
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def _custom_alias(header: dict[str, Any]) -> str | None:
|
|
554
|
+
try:
|
|
555
|
+
custom = header["custom"]
|
|
556
|
+
descriptor = custom["level"]
|
|
557
|
+
transpose = custom.get("transpose")
|
|
558
|
+
except (KeyError, TypeError) as error:
|
|
559
|
+
raise BinsparseParseError("custom format requires custom.level") from error
|
|
560
|
+
transpose_tuple = None if transpose is None else tuple(transpose)
|
|
561
|
+
return next(
|
|
562
|
+
(
|
|
563
|
+
alias
|
|
564
|
+
for alias, (alias_descriptor, alias_transpose) in _PREDEFINED_LEVELS.items()
|
|
565
|
+
if descriptor == alias_descriptor and transpose_tuple == alias_transpose
|
|
566
|
+
),
|
|
567
|
+
None,
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
__all__ = [
|
|
572
|
+
"BinsparseLevel", "IndexableLevel", "ElementLevel",
|
|
573
|
+
"DenseLevel", "SparseLevel", "BinsparseTensor", "CustomTensor",
|
|
574
|
+
"DVECVector", "DMATRMatrix", "DMATCMatrix", "DMATMatrix", "CVECVector",
|
|
575
|
+
"CSRMatrix", "CSCMatrix", "DCSRMatrix", "DCSCMatrix", "COORMatrix",
|
|
576
|
+
"COOCMatrix", "COOMatrix",
|
|
577
|
+
]
|
binsparse/version.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from semver import Version
|
|
4
|
+
|
|
5
|
+
from .errors import BinsparseParseError
|
|
6
|
+
|
|
7
|
+
BINSPARSE_VERSION = "0.1.0"
|
|
8
|
+
_SUPPORTED_VERSION = Version.parse(BINSPARSE_VERSION)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def check_binsparse_version(version: object) -> None:
|
|
12
|
+
"""Raise if *version* is not a SemVer-compatible Binsparse version."""
|
|
13
|
+
parsed = parse_binsparse_version(version)
|
|
14
|
+
if not _SUPPORTED_VERSION.is_compatible(parsed):
|
|
15
|
+
raise BinsparseParseError(
|
|
16
|
+
f"unsupported Binsparse version {version!r}; "
|
|
17
|
+
f"expected a SemVer-compatible version with {BINSPARSE_VERSION!r}"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def parse_binsparse_version(version: object) -> Version:
|
|
22
|
+
if not isinstance(version, str):
|
|
23
|
+
raise BinsparseParseError("Binsparse version must be a string")
|
|
24
|
+
try:
|
|
25
|
+
return Version.parse(version)
|
|
26
|
+
except ValueError as error:
|
|
27
|
+
raise BinsparseParseError(
|
|
28
|
+
f"invalid Binsparse SemVer version {version!r}"
|
|
29
|
+
) from error
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: binsparse
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Author-email: Willow Ahrens <ahrens@gatech.edu>
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: h5py<4,>=3.16.0
|
|
7
|
+
Requires-Dist: numpy<3,>=2
|
|
8
|
+
Requires-Dist: semver<4,>=3
|
|
9
|
+
Provides-Extra: conversions
|
|
10
|
+
Requires-Dist: scipy; extra == 'conversions'
|
|
11
|
+
Requires-Dist: sparse; extra == 'conversions'
|
|
12
|
+
Requires-Dist: torch; extra == 'conversions'
|
|
13
|
+
Provides-Extra: scipy
|
|
14
|
+
Requires-Dist: scipy; extra == 'scipy'
|
|
15
|
+
Provides-Extra: sparse
|
|
16
|
+
Requires-Dist: sparse; extra == 'sparse'
|
|
17
|
+
Provides-Extra: torch
|
|
18
|
+
Requires-Dist: torch; extra == 'torch'
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
binsparse/__init__.py,sha256=5u8dntzUBcs_n_j_1MEi9QS7O5cmwmjYNOwUdOg7Iqk,1373
|
|
2
|
+
binsparse/container.py,sha256=OKX96l7oPLB-B47MS5t-ExMq7_0r7PGSCePqjkMjZKY,12473
|
|
3
|
+
binsparse/errors.py,sha256=2m5yLYLAl7aKrSBHD5GAhE54oVkpWEb7C9wZ8VOO5YY,171
|
|
4
|
+
binsparse/io.py,sha256=wCSEnMVXXaL880Lf9zyMXjpdKSkiX6vEERM00c0uEuQ,4252
|
|
5
|
+
binsparse/tensor.py,sha256=2Sg-oP1vqpaWK_Cw92kis242GwMAfTCrMCKAxEHqYtk,20060
|
|
6
|
+
binsparse/version.py,sha256=wWzDlzR0NAxmjMBcuUiB6iCt95Xj919IXtEmnkd-WSU,963
|
|
7
|
+
binsparse/conversions/__init__.py,sha256=Dny-FQagLMBsD-x6Nj6ePEUtbTA1mqXfsKVqEyLCBfY,400
|
|
8
|
+
binsparse/conversions/numpy.py,sha256=LqwaFE77T1UC-TRTPbSykFvCP0NxuMEB3f_odyLVoHY,2010
|
|
9
|
+
binsparse/conversions/scipy.py,sha256=TBVdRvt-55G24rAW9Y8JS3SGSA-iV9y33C0bq1Y5OK8,3344
|
|
10
|
+
binsparse/conversions/sparse.py,sha256=ogRQD1MjC_iWfgmvhdLpXH94heGgW6rBlZcPztuMS1Q,2831
|
|
11
|
+
binsparse/conversions/torch.py,sha256=BWJWGkrnNLii11FctCE8xAbMSCqA02x50rFVEl6bqJY,5916
|
|
12
|
+
reference_cli/__init__.py,sha256=g9DI-PGXVrJ8CakgxLUg9dEWzW4LbEl2K8Mc26dCDx4,72
|
|
13
|
+
reference_cli/converters.py,sha256=8EWMwwgdrg3B66QgOAI8CAztnot6thCdFDCF6PuUh6w,5802
|
|
14
|
+
reference_cli/framework.py,sha256=5mXnsI0aWNYqfKIOyEJc07BjstJvXj-QGjCiGGy2eKg,2585
|
|
15
|
+
reference_cli/reformat.py,sha256=JmRVvk49CwiScmwKRDH_2K7ix4hamB_xoOYt2puv0k0,10551
|
|
16
|
+
binsparse-0.1.0.dist-info/METADATA,sha256=zc7iTfZEa4IvEna9MYQ4knCSwk_zHh9hFoDxuss3bG0,561
|
|
17
|
+
binsparse-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
18
|
+
binsparse-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Reference converter executables for Binsparse compliance testing."""
|