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.
Files changed (39) hide show
  1. {binsparse-0.1.2 → binsparse-0.1.4}/.github/workflows/publish.yml +2 -3
  2. binsparse-0.1.4/PKG-INFO +92 -0
  3. {binsparse-0.1.2 → binsparse-0.1.4}/README.md +6 -0
  4. {binsparse-0.1.2 → binsparse-0.1.4}/pyproject.toml +5 -3
  5. {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/conversions/scipy.py +4 -1
  6. {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/conversions/sparse.py +17 -1
  7. {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/conversions/torch.py +15 -7
  8. {binsparse-0.1.2 → binsparse-0.1.4}/src/tests/test_conversion_scipy.py +21 -4
  9. {binsparse-0.1.2 → binsparse-0.1.4}/src/tests/test_conversion_sparse.py +33 -0
  10. {binsparse-0.1.2 → binsparse-0.1.4}/src/tests/test_conversion_torch.py +52 -11
  11. binsparse-0.1.2/PKG-INFO +0 -18
  12. {binsparse-0.1.2 → binsparse-0.1.4}/.github/workflows/ci.yml +0 -0
  13. {binsparse-0.1.2 → binsparse-0.1.4}/.gitignore +0 -0
  14. {binsparse-0.1.2 → binsparse-0.1.4}/bin/binsparse_to_binsparse +0 -0
  15. {binsparse-0.1.2 → binsparse-0.1.4}/bin/binsparse_to_npy +0 -0
  16. {binsparse-0.1.2 → binsparse-0.1.4}/bin/npy_to_binsparse +0 -0
  17. {binsparse-0.1.2 → binsparse-0.1.4}/compliance-skips/numpy.txt +0 -0
  18. {binsparse-0.1.2 → binsparse-0.1.4}/compliance-skips/reference.txt +0 -0
  19. {binsparse-0.1.2 → binsparse-0.1.4}/compliance-skips/scipy.txt +0 -0
  20. {binsparse-0.1.2 → binsparse-0.1.4}/compliance-skips/sparse.txt +0 -0
  21. {binsparse-0.1.2 → binsparse-0.1.4}/pixi.lock +0 -0
  22. {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/__init__.py +0 -0
  23. {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/container.py +0 -0
  24. {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/conversions/__init__.py +0 -0
  25. {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/conversions/numpy.py +0 -0
  26. {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/errors.py +0 -0
  27. {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/io.py +0 -0
  28. {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/tensor.py +0 -0
  29. {binsparse-0.1.2 → binsparse-0.1.4}/src/binsparse/version.py +0 -0
  30. {binsparse-0.1.2 → binsparse-0.1.4}/src/reference_cli/__init__.py +0 -0
  31. {binsparse-0.1.2 → binsparse-0.1.4}/src/reference_cli/converters.py +0 -0
  32. {binsparse-0.1.2 → binsparse-0.1.4}/src/reference_cli/framework.py +0 -0
  33. {binsparse-0.1.2 → binsparse-0.1.4}/src/reference_cli/reformat.py +0 -0
  34. {binsparse-0.1.2 → binsparse-0.1.4}/src/tests/test_conversion_numpy.py +0 -0
  35. {binsparse-0.1.2 → binsparse-0.1.4}/src/tests/test_conversions.py +0 -0
  36. {binsparse-0.1.2 → binsparse-0.1.4}/src/tests/test_executables.py +0 -0
  37. {binsparse-0.1.2 → binsparse-0.1.4}/src/tests/test_io.py +0 -0
  38. {binsparse-0.1.2 → binsparse-0.1.4}/src/tests/test_reformat.py +0 -0
  39. {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 Prefix.dev package
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
@@ -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.2"
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 = "binsparse"
64
- version = "0.1.0"
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 a two-dimensional SciPy CSR, CSC, or COO object to Binsparse."""
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 an N-dimensional PyData/Sparse COO array to Binsparse."""
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 a PyTorch dense, COO, CSR, or CSC tensor to Binsparse."""
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
- def test_scipy_csr_is_sorted_and_unique() -> None:
32
- source = scipy_sparse.csr_array(
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
- def test_torch_uncoalesced_coo_preserves_entries() -> None:
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
- torch.tensor([[0, 0], [1, 1]]),
54
- torch.tensor([2.0, 3.0]),
55
- (2, 2),
58
+ coords.clone(),
59
+ values.clone(),
60
+ (3,) * ndim,
56
61
  )
57
- tensor = from_torch(source)
58
-
59
- assert isinstance(tensor, COORMatrix)
60
- assert tensor.number_of_stored_values == 2
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(coordinates, torch.tensor([2.0, 3.0]), shape)
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