binsparse 0.1.2__tar.gz → 0.1.4__tar.gz
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-0.1.2 → binsparse-0.1.4}/.github/workflows/publish.yml +2 -3
- binsparse-0.1.4/PKG-INFO +92 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/README.md +6 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/pyproject.toml +5 -3
- {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/conversions/scipy.py +4 -1
- {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/conversions/sparse.py +17 -1
- {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/conversions/torch.py +15 -7
- {binsparse-0.1.2 → binsparse-0.1.4}/src/tests/test_conversion_scipy.py +21 -4
- {binsparse-0.1.2 → binsparse-0.1.4}/src/tests/test_conversion_sparse.py +33 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/src/tests/test_conversion_torch.py +52 -11
- binsparse-0.1.2/PKG-INFO +0 -18
- {binsparse-0.1.2 → binsparse-0.1.4}/.github/workflows/ci.yml +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/.gitignore +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/bin/binsparse_to_binsparse +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/bin/binsparse_to_npy +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/bin/npy_to_binsparse +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/compliance-skips/numpy.txt +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/compliance-skips/reference.txt +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/compliance-skips/scipy.txt +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/compliance-skips/sparse.txt +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/pixi.lock +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/__init__.py +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/container.py +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/conversions/__init__.py +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/conversions/numpy.py +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/errors.py +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/io.py +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/tensor.py +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/version.py +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/src/reference_cli/__init__.py +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/src/reference_cli/converters.py +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/src/reference_cli/framework.py +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/src/reference_cli/reformat.py +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/src/tests/test_conversion_numpy.py +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/src/tests/test_conversions.py +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/src/tests/test_executables.py +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/src/tests/test_io.py +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/src/tests/test_reformat.py +0 -0
- {binsparse-0.1.2 → binsparse-0.1.4}/src/tests/test_tensor.py +0 -0
|
@@ -21,8 +21,6 @@ jobs:
|
|
|
21
21
|
- uses: prefix-dev/setup-pixi@v0.10.0
|
|
22
22
|
with:
|
|
23
23
|
pixi-version: v0.71.3
|
|
24
|
-
auth-host: prefix.dev
|
|
25
|
-
auth-token: ${{ secrets.PREFIX_API_KEY }}
|
|
26
24
|
- name: Get version
|
|
27
25
|
run: |
|
|
28
26
|
VERSION=$(python -c 'import tomllib; print("v" + tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')
|
|
@@ -33,7 +31,7 @@ jobs:
|
|
|
33
31
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
34
32
|
with:
|
|
35
33
|
skip-existing: true
|
|
36
|
-
- name: Build
|
|
34
|
+
- name: Build Conda package
|
|
37
35
|
run: pixi publish --target-dir "$RUNNER_TEMP/binsparse-conda"
|
|
38
36
|
- name: Publish to Prefix.dev
|
|
39
37
|
env:
|
|
@@ -42,6 +40,7 @@ jobs:
|
|
|
42
40
|
pixi upload prefix
|
|
43
41
|
--channel willow-ahrens/binsparse
|
|
44
42
|
--api-key "$PREFIX_API_KEY"
|
|
43
|
+
--skip-existing
|
|
45
44
|
"$RUNNER_TEMP"/binsparse-conda/*.conda
|
|
46
45
|
- name: Create a release
|
|
47
46
|
uses: softprops/action-gh-release@v3
|
binsparse-0.1.4/PKG-INFO
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: binsparse
|
|
3
|
+
Version: 0.1.4
|
|
4
|
+
Summary: Python reference implementation of the Binsparse binary sparse format
|
|
5
|
+
Author-email: Willow Ahrens <ahrens@gatech.edu>
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: h5py<4,>=3.16.0
|
|
8
|
+
Requires-Dist: numpy<3,>=2
|
|
9
|
+
Requires-Dist: semver<4,>=3
|
|
10
|
+
Provides-Extra: conversions
|
|
11
|
+
Requires-Dist: scipy; extra == 'conversions'
|
|
12
|
+
Requires-Dist: sparse; extra == 'conversions'
|
|
13
|
+
Requires-Dist: torch; extra == 'conversions'
|
|
14
|
+
Provides-Extra: scipy
|
|
15
|
+
Requires-Dist: scipy; extra == 'scipy'
|
|
16
|
+
Provides-Extra: sparse
|
|
17
|
+
Requires-Dist: sparse; extra == 'sparse'
|
|
18
|
+
Provides-Extra: torch
|
|
19
|
+
Requires-Dist: torch; extra == 'torch'
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# Binsparse Python Reference Implementation
|
|
23
|
+
|
|
24
|
+
This library is a reference implementation of the [Binsparse Binary Sparse Format Specification](https://binsparse.org) written using Python.
|
|
25
|
+
|
|
26
|
+
Binsparse is a cross-platform, embeddable format for storing sparse matrices and
|
|
27
|
+
tensors. This library implements Binsparse bindings for
|
|
28
|
+
[NumPy NPZ](https://numpy.org/doc/stable/reference/generated/numpy.savez.html),
|
|
29
|
+
[HDF5](https://www.hdfgroup.org/solutions/hdf5/), and
|
|
30
|
+
[Zarr](https://zarr.dev/) containers, with conversion interfaces for
|
|
31
|
+
[NumPy](https://numpy.org/), [SciPy](https://scipy.org/),
|
|
32
|
+
[PyTorch](https://pytorch.org/), and
|
|
33
|
+
[PyData/Sparse](https://sparse.pydata.org/).
|
|
34
|
+
|
|
35
|
+
## Python Binsparse Interface
|
|
36
|
+
|
|
37
|
+
The primary interface consists of `load_binsparse` and `save_binsparse`. These
|
|
38
|
+
functions select a container from the path extension (`.npz`, `.h5`/`.hdf5`, or
|
|
39
|
+
`.zarr`) and read or write a `BinsparseTensor`:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from binsparse import load_binsparse, save_binsparse
|
|
43
|
+
|
|
44
|
+
tensor = load_binsparse("input.h5")
|
|
45
|
+
save_binsparse(tensor, "output.npz")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Parsed tensors expose their logical `shape`, `number_of_stored_values`, optional
|
|
49
|
+
fill value, and the arrays that define their storage format. Concrete tensor
|
|
50
|
+
classes represent the predefined Binsparse formats, including dense vectors and
|
|
51
|
+
matrices, compressed sparse rows and columns, doubly compressed matrices, and
|
|
52
|
+
coordinate formats. `CustomTensor` and the level classes (`DenseLevel`,
|
|
53
|
+
`SparseLevel`, and `ElementLevel`) represent arbitrary level-based format
|
|
54
|
+
descriptors.
|
|
55
|
+
|
|
56
|
+
For applications that already manage their own storage objects, the lower-level
|
|
57
|
+
`BinsparseContainer` interface separates tensor parsing and serialization from
|
|
58
|
+
the physical container. Adapters are provided for NPZ mappings, HDF5 files,
|
|
59
|
+
Zarr groups, and in-memory descriptors and buffers. Use
|
|
60
|
+
`BinsparseTensor.parse(container)` to read through an adapter and
|
|
61
|
+
`tensor.serialize(container)` to write through one.
|
|
62
|
+
|
|
63
|
+
The `alias` option controls whether predefined format names such as `CSR` are
|
|
64
|
+
preserved or expanded to custom level descriptors. The `copy` option allows a
|
|
65
|
+
caller to request a copy, permit whichever representation is required, or
|
|
66
|
+
require a zero-copy operation when the source and destination support it.
|
|
67
|
+
|
|
68
|
+
`binsparse.conversions` exports the following conversion functions:
|
|
69
|
+
|
|
70
|
+
- NumPy: `from_numpy` and `to_numpy`
|
|
71
|
+
- SciPy: `from_scipy` and `to_scipy`
|
|
72
|
+
- PyTorch: `from_torch` and `to_torch`
|
|
73
|
+
- PyData/Sparse: `from_sparse` and `to_sparse`
|
|
74
|
+
|
|
75
|
+
NumPy support is included by default; the other adapters require their
|
|
76
|
+
corresponding optional dependency.
|
|
77
|
+
|
|
78
|
+
SciPy, PyData/Sparse, and PyTorch COO imports sort indices and sum duplicate
|
|
79
|
+
entries before constructing a Binsparse tensor, without modifying the source.
|
|
80
|
+
`copy=False` requires canonical (sorted, duplicate-free) input; PyTorch COO
|
|
81
|
+
tensors must be coalesced first. PyTorch CSR/CSC imports rely on PyTorch's
|
|
82
|
+
sorted, unique index invariants.
|
|
83
|
+
|
|
84
|
+
## Source
|
|
85
|
+
The source code for `binsparse` is available on GitHub at [https://github.com/Binsparse/binsparse-reference-python](https://github.com/Binsparse/binsparse-reference-python)
|
|
86
|
+
|
|
87
|
+
## Installation
|
|
88
|
+
|
|
89
|
+
`binsparse` is available on PyPi, and can be installed with pip:
|
|
90
|
+
```bash
|
|
91
|
+
pip install binsparse
|
|
92
|
+
```
|
|
@@ -54,6 +54,12 @@ require a zero-copy operation when the source and destination support it.
|
|
|
54
54
|
NumPy support is included by default; the other adapters require their
|
|
55
55
|
corresponding optional dependency.
|
|
56
56
|
|
|
57
|
+
SciPy, PyData/Sparse, and PyTorch COO imports sort indices and sum duplicate
|
|
58
|
+
entries before constructing a Binsparse tensor, without modifying the source.
|
|
59
|
+
`copy=False` requires canonical (sorted, duplicate-free) input; PyTorch COO
|
|
60
|
+
tensors must be coalesced first. PyTorch CSR/CSC imports rely on PyTorch's
|
|
61
|
+
sorted, unique index invariants.
|
|
62
|
+
|
|
57
63
|
## Source
|
|
58
64
|
The source code for `binsparse` is available on GitHub at [https://github.com/Binsparse/binsparse-reference-python](https://github.com/Binsparse/binsparse-reference-python)
|
|
59
65
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
authors = [{name = "Willow Ahrens", email = "ahrens@gatech.edu"}]
|
|
3
3
|
name = "binsparse"
|
|
4
|
+
description = "Python reference implementation of the Binsparse binary sparse format"
|
|
5
|
+
readme = "README.md"
|
|
4
6
|
requires-python = ">= 3.11"
|
|
5
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.4"
|
|
6
8
|
dependencies = [
|
|
7
9
|
"h5py>=3.16.0,<4",
|
|
8
10
|
"numpy>=2,<3",
|
|
@@ -60,8 +62,8 @@ preview = ["pixi-build"]
|
|
|
60
62
|
requires-pixi = ">=0.71.0"
|
|
61
63
|
|
|
62
64
|
[tool.pixi.package]
|
|
63
|
-
name =
|
|
64
|
-
version =
|
|
65
|
+
name = { workspace = true }
|
|
66
|
+
version = { workspace = true }
|
|
65
67
|
|
|
66
68
|
[tool.pixi.package.build]
|
|
67
69
|
backend = { name = "pixi-build-python", version = "0.*", channels = ["https://prefix.dev/conda-forge"] }
|
|
@@ -26,7 +26,10 @@ def _prepare(value: Any, copy: bool | None) -> Any:
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def from_scipy(value: Any, *, copy: bool | None = None) -> BinsparseTensor:
|
|
29
|
-
"""Convert
|
|
29
|
+
"""Convert SciPy CSR, CSC, or COO to Binsparse, sorting and summing duplicates.
|
|
30
|
+
|
|
31
|
+
The input is not modified. ``copy=False`` requires canonical input.
|
|
32
|
+
"""
|
|
30
33
|
scipy_sparse = _scipy_sparse()
|
|
31
34
|
if not scipy_sparse.issparse(value) or value.ndim != 2:
|
|
32
35
|
raise TypeError("expected a two-dimensional SciPy sparse array or matrix")
|
|
@@ -25,12 +25,28 @@ def _sparse() -> Any:
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
def from_sparse(value: Any, *, copy: bool | None = None) -> BinsparseTensor:
|
|
28
|
-
"""Convert
|
|
28
|
+
"""Convert PyData/Sparse COO to Binsparse, sorting and summing duplicates.
|
|
29
|
+
|
|
30
|
+
The input is not modified. ``copy=False`` requires canonical input.
|
|
31
|
+
"""
|
|
29
32
|
sparse = _sparse()
|
|
30
33
|
if not isinstance(value, sparse.COO) or value.ndim < 1:
|
|
31
34
|
raise TypeError("expected a non-scalar PyData/Sparse COO array")
|
|
32
35
|
if copy is True:
|
|
33
36
|
value = value.copy(deep=True)
|
|
37
|
+
if copy is False:
|
|
38
|
+
linear = value.linear_loc()
|
|
39
|
+
if not np.all(linear[1:] > linear[:-1]):
|
|
40
|
+
raise ValueError("copy=False cannot canonicalize a PyData/Sparse array")
|
|
41
|
+
else:
|
|
42
|
+
value = sparse.COO(
|
|
43
|
+
value.coords,
|
|
44
|
+
value.data,
|
|
45
|
+
shape=value.shape,
|
|
46
|
+
fill_value=value.fill_value,
|
|
47
|
+
sorted=False,
|
|
48
|
+
has_duplicates=True,
|
|
49
|
+
)
|
|
34
50
|
fill_value = np.asarray(value.fill_value).item()
|
|
35
51
|
shape = tuple(value.shape)
|
|
36
52
|
count = int(value.data.size)
|
|
@@ -51,7 +51,12 @@ def _numpy(value: Any, copy: bool | None) -> np.ndarray:
|
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
def from_torch(value: Any, *, copy: bool | None = None) -> BinsparseTensor:
|
|
54
|
-
"""Convert
|
|
54
|
+
"""Convert PyTorch dense, COO, CSR, or CSC to Binsparse.
|
|
55
|
+
|
|
56
|
+
COO entries are sorted and duplicates summed without modifying the input.
|
|
57
|
+
``copy=False`` requires coalesced COO input. CSR/CSC inputs are assumed to
|
|
58
|
+
satisfy PyTorch's sorted, unique index invariants.
|
|
59
|
+
"""
|
|
55
60
|
torch = _torch()
|
|
56
61
|
if not isinstance(value, torch.Tensor):
|
|
57
62
|
raise TypeError("expected a PyTorch tensor")
|
|
@@ -61,10 +66,16 @@ def from_torch(value: Any, *, copy: bool | None = None) -> BinsparseTensor:
|
|
|
61
66
|
return from_numpy(array, copy=False if copy is True else copy)
|
|
62
67
|
|
|
63
68
|
if value.layout == torch.sparse_coo:
|
|
64
|
-
indices = _numpy(value._indices(), copy)
|
|
65
|
-
values = _numpy(value._values(), copy)
|
|
66
69
|
if value.dense_dim() != 0:
|
|
67
70
|
raise TypeError("hybrid sparse COO tensors are not supported")
|
|
71
|
+
if not value.is_coalesced():
|
|
72
|
+
if copy is False:
|
|
73
|
+
raise ValueError(
|
|
74
|
+
"copy=False cannot canonicalize a PyTorch sparse tensor"
|
|
75
|
+
)
|
|
76
|
+
value = value.coalesce()
|
|
77
|
+
indices = _numpy(value._indices(), copy)
|
|
78
|
+
values = _numpy(value._values(), copy)
|
|
68
79
|
if value.sparse_dim() == 2:
|
|
69
80
|
return COORMatrix(
|
|
70
81
|
tuple(value.shape),
|
|
@@ -79,10 +90,7 @@ def from_torch(value: Any, *, copy: bool | None = None) -> BinsparseTensor:
|
|
|
79
90
|
level=SparseLevel(
|
|
80
91
|
value.sparse_dim(),
|
|
81
92
|
ElementLevel(values),
|
|
82
|
-
tuple(
|
|
83
|
-
indices[dimension, :]
|
|
84
|
-
for dimension in range(value.sparse_dim())
|
|
85
|
-
),
|
|
93
|
+
tuple(indices[dimension, :] for dimension in range(value.sparse_dim())),
|
|
86
94
|
),
|
|
87
95
|
)
|
|
88
96
|
|
|
@@ -28,22 +28,36 @@ def test_scipy_round_trip(constructor, tensor_type) -> None:
|
|
|
28
28
|
assert result.dtype == source.dtype
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
@pytest.mark.parametrize(
|
|
32
|
+
"constructor", [scipy_sparse.csr_array, scipy_sparse.csc_array]
|
|
33
|
+
)
|
|
34
|
+
def test_scipy_compressed_is_sorted_and_unique(constructor) -> None:
|
|
35
|
+
source = constructor(
|
|
33
36
|
(
|
|
34
37
|
np.array([1, 2, 3, 4, 5, 6]),
|
|
35
38
|
np.array([2, 1, 2, 1, 0, 1]),
|
|
36
39
|
np.array([0, 3, 6]),
|
|
37
40
|
),
|
|
38
|
-
shape=(2, 3),
|
|
41
|
+
shape=(2, 3) if constructor is scipy_sparse.csr_array else (3, 2),
|
|
39
42
|
)
|
|
43
|
+
original = (source.indptr.copy(), source.indices.copy(), source.data.copy())
|
|
40
44
|
tensor = from_scipy(source)
|
|
41
45
|
result = to_scipy(tensor)
|
|
42
46
|
|
|
47
|
+
assert isinstance(tensor, (CSRMatrix, CSCMatrix))
|
|
43
48
|
assert tensor.number_of_stored_values == 4
|
|
49
|
+
np.testing.assert_array_equal(tensor.pointers_to_1, [0, 2, 4])
|
|
50
|
+
np.testing.assert_array_equal(tensor.indices_1, [1, 2, 0, 1])
|
|
51
|
+
np.testing.assert_array_equal(tensor.values, [2, 4, 5, 10])
|
|
44
52
|
assert result.has_canonical_format
|
|
45
53
|
np.testing.assert_array_equal(result.toarray(), source.toarray())
|
|
46
54
|
assert not source.has_canonical_format
|
|
55
|
+
for actual, expected in zip(
|
|
56
|
+
(source.indptr, source.indices, source.data), original, strict=True
|
|
57
|
+
):
|
|
58
|
+
np.testing.assert_array_equal(actual, expected)
|
|
59
|
+
with pytest.raises(ValueError, match="canonicalize"):
|
|
60
|
+
from_scipy(source, copy=False)
|
|
47
61
|
|
|
48
62
|
|
|
49
63
|
def test_scipy_coo_is_sorted_and_unique() -> None:
|
|
@@ -57,7 +71,11 @@ def test_scipy_coo_is_sorted_and_unique() -> None:
|
|
|
57
71
|
tensor = from_scipy(source)
|
|
58
72
|
result = to_scipy(tensor)
|
|
59
73
|
|
|
74
|
+
assert isinstance(tensor, COORMatrix)
|
|
60
75
|
assert tensor.number_of_stored_values == 2
|
|
76
|
+
np.testing.assert_array_equal(tensor.indices_0, [0, 1])
|
|
77
|
+
np.testing.assert_array_equal(tensor.indices_1, [2, 0])
|
|
78
|
+
np.testing.assert_array_equal(tensor.values, [6, 4])
|
|
61
79
|
assert result.has_canonical_format
|
|
62
80
|
np.testing.assert_array_equal(result.toarray(), source.toarray())
|
|
63
81
|
assert not source.has_canonical_format
|
|
@@ -88,7 +106,6 @@ def test_scipy_copy_false_rejects_required_input_canonicalization() -> None:
|
|
|
88
106
|
from_scipy(source, copy=False)
|
|
89
107
|
|
|
90
108
|
|
|
91
|
-
|
|
92
109
|
def test_scipy_canonical_coo_copy_false_shares_buffers() -> None:
|
|
93
110
|
source = scipy_sparse.coo_array(
|
|
94
111
|
(np.array([3]), (np.array([0]), np.array([1]))),
|
|
@@ -9,6 +9,39 @@ from binsparse.tensor import COORMatrix, CustomTensor, ElementLevel, SparseLevel
|
|
|
9
9
|
sparse = pytest.importorskip("sparse")
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
@pytest.mark.parametrize("ndim", [1, 2, 3])
|
|
13
|
+
@pytest.mark.parametrize("copy", [None, True, False])
|
|
14
|
+
@pytest.mark.parametrize("duplicates", [False, True])
|
|
15
|
+
def test_pydata_sparse_is_sorted_and_unique(ndim, copy, duplicates) -> None:
|
|
16
|
+
coordinates = [2, 0, 2, 1] if duplicates else [2, 0, 1]
|
|
17
|
+
coords = np.array([coordinates] * ndim)
|
|
18
|
+
data = np.arange(1, len(coordinates) + 1, dtype=np.float32)
|
|
19
|
+
# Bypass the constructor's normalization to exercise noncanonical buffers.
|
|
20
|
+
source = sparse.COO(
|
|
21
|
+
coords.copy(),
|
|
22
|
+
data.copy(),
|
|
23
|
+
shape=(3,) * ndim,
|
|
24
|
+
sorted=True,
|
|
25
|
+
has_duplicates=False,
|
|
26
|
+
fill_value=7,
|
|
27
|
+
)
|
|
28
|
+
if copy is False:
|
|
29
|
+
with pytest.raises(ValueError, match="canonicalize"):
|
|
30
|
+
from_sparse(source, copy=copy)
|
|
31
|
+
else:
|
|
32
|
+
tensor = from_sparse(source, copy=copy)
|
|
33
|
+
result = to_sparse(tensor)
|
|
34
|
+
assert tensor.number_of_stored_values == 3
|
|
35
|
+
assert tensor.fill_value == 7
|
|
36
|
+
assert result.dtype == data.dtype
|
|
37
|
+
np.testing.assert_array_equal(result.coords, [[0, 1, 2]] * ndim)
|
|
38
|
+
np.testing.assert_array_equal(
|
|
39
|
+
result.data, [2, 4, 4] if duplicates else [2, 3, 1]
|
|
40
|
+
)
|
|
41
|
+
np.testing.assert_array_equal(source.coords, coords)
|
|
42
|
+
np.testing.assert_array_equal(source.data, data)
|
|
43
|
+
|
|
44
|
+
|
|
12
45
|
def test_pydata_sparse_round_trip() -> None:
|
|
13
46
|
source = sparse.COO.from_numpy(np.array([[0, 2, 0], [3, 0, 4]], dtype=np.float32))
|
|
14
47
|
tensor = from_sparse(source)
|
|
@@ -6,6 +6,7 @@ import pytest
|
|
|
6
6
|
from binsparse.conversions import from_torch, to_torch
|
|
7
7
|
from binsparse.tensor import (
|
|
8
8
|
COORMatrix,
|
|
9
|
+
CSCMatrix,
|
|
9
10
|
CSRMatrix,
|
|
10
11
|
CustomTensor,
|
|
11
12
|
DenseLevel,
|
|
@@ -48,22 +49,63 @@ def test_torch_csr_round_trip() -> None:
|
|
|
48
49
|
assert torch.equal(result.to_dense(), source.to_dense())
|
|
49
50
|
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
@pytest.mark.parametrize("ndim", [1, 2, 3])
|
|
53
|
+
@pytest.mark.parametrize("copy", [None, True, False])
|
|
54
|
+
def test_torch_uncoalesced_coo_is_sorted_and_unique(ndim, copy) -> None:
|
|
55
|
+
coords = torch.tensor([[2, 0, 2, 1]] * ndim)
|
|
56
|
+
values = torch.tensor([1.0, 2.0, -1.0, 4.0])
|
|
52
57
|
source = torch.sparse_coo_tensor(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
(
|
|
58
|
+
coords.clone(),
|
|
59
|
+
values.clone(),
|
|
60
|
+
(3,) * ndim,
|
|
56
61
|
)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
if copy is False:
|
|
63
|
+
with pytest.raises(ValueError, match="canonicalize"):
|
|
64
|
+
from_torch(source, copy=copy)
|
|
65
|
+
else:
|
|
66
|
+
tensor = from_torch(source, copy=copy)
|
|
67
|
+
result = to_torch(tensor)
|
|
68
|
+
assert tensor.number_of_stored_values == 3
|
|
69
|
+
assert result.dtype == source.dtype
|
|
70
|
+
assert torch.equal(result._indices(), torch.tensor([[0, 1, 2]] * ndim))
|
|
71
|
+
assert torch.equal(result._values(), torch.tensor([2.0, 4.0, 0.0]))
|
|
72
|
+
assert torch.equal(result.to_dense(), source.to_dense())
|
|
73
|
+
assert not source.is_coalesced()
|
|
74
|
+
assert torch.equal(source._indices(), coords)
|
|
75
|
+
assert torch.equal(source._values(), values)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@pytest.mark.parametrize("layout", ["coo", "csr", "csc"])
|
|
79
|
+
@pytest.mark.parametrize("empty", [False, True])
|
|
80
|
+
def test_torch_canonical_sparse_copy_policy(layout, empty) -> None:
|
|
81
|
+
dense = torch.zeros((3, 3)) if empty else torch.eye(3)
|
|
82
|
+
source = dense.to_sparse(layout=getattr(torch, f"sparse_{layout}"))
|
|
83
|
+
shared = from_torch(source, copy=False)
|
|
84
|
+
copied = from_torch(source, copy=True)
|
|
85
|
+
assert isinstance(shared, (COORMatrix, CSRMatrix, CSCMatrix))
|
|
86
|
+
assert isinstance(copied, (COORMatrix, CSRMatrix, CSCMatrix))
|
|
87
|
+
original_values = source.values().numpy()
|
|
88
|
+
np.testing.assert_array_equal(copied.values, original_values)
|
|
89
|
+
if not empty:
|
|
90
|
+
assert np.shares_memory(shared.values, original_values)
|
|
91
|
+
assert not np.shares_memory(copied.values, original_values)
|
|
92
|
+
if layout == "coo":
|
|
93
|
+
assert isinstance(shared, COORMatrix)
|
|
94
|
+
assert isinstance(copied, COORMatrix)
|
|
95
|
+
assert np.shares_memory(shared.indices_0, source.indices().numpy())
|
|
96
|
+
assert not np.shares_memory(copied.indices_0, source.indices().numpy())
|
|
97
|
+
else:
|
|
98
|
+
indices = source.col_indices() if layout == "csr" else source.row_indices()
|
|
99
|
+
assert np.shares_memory(shared.indices_1, indices.numpy())
|
|
100
|
+
assert not np.shares_memory(copied.indices_1, indices.numpy())
|
|
61
101
|
|
|
62
102
|
|
|
63
103
|
@pytest.mark.parametrize("shape", [(5,), (2, 3, 4)])
|
|
64
104
|
def test_torch_nd_coo_round_trip(shape) -> None:
|
|
65
105
|
coordinates = torch.tensor([[0, size - 1] for size in shape])
|
|
66
|
-
source = torch.sparse_coo_tensor(
|
|
106
|
+
source = torch.sparse_coo_tensor(
|
|
107
|
+
coordinates, torch.tensor([2.0, 3.0]), shape
|
|
108
|
+
).coalesce()
|
|
67
109
|
tensor = from_torch(source, copy=False)
|
|
68
110
|
result = to_torch(tensor)
|
|
69
111
|
|
|
@@ -72,8 +114,7 @@ def test_torch_nd_coo_round_trip(shape) -> None:
|
|
|
72
114
|
assert tensor.level.rank == len(shape)
|
|
73
115
|
assert isinstance(tensor.level.level, ElementLevel)
|
|
74
116
|
assert all(
|
|
75
|
-
index.__array_interface__["data"][0]
|
|
76
|
-
== source._indices()[dimension].data_ptr()
|
|
117
|
+
index.__array_interface__["data"][0] == source._indices()[dimension].data_ptr()
|
|
77
118
|
for dimension, index in enumerate(tensor.level.indices)
|
|
78
119
|
)
|
|
79
120
|
assert torch.equal(result.to_dense(), source.to_dense())
|
binsparse-0.1.2/PKG-INFO
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: binsparse
|
|
3
|
-
Version: 0.1.2
|
|
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'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|