Skip to content

Commit e9fcedb

Browse files
committed
Adjust public API, tests, and format definition language.
1 parent 93f8ef7 commit e9fcedb

File tree

7 files changed

+98
-62
lines changed

7 files changed

+98
-62
lines changed

pixi.toml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ mkdocs-jupyter = "*"
2727

2828
[feature.tests.tasks]
2929
test = "pytest --pyargs sparse -n auto"
30-
test-mlir = { cmd = "pytest --pyargs sparse/mlir_backend -v", env = { SPARSE_BACKEND = "MLIR" } }
31-
test-finch = { cmd = "pytest --pyargs sparse/tests -n auto -v", env = { SPARSE_BACKEND = "Finch", PYTHONFAULTHANDLER = "${HOME}/faulthandler.log" }, depends-on = ["precompile"] }
30+
test-mlir = { cmd = "pytest --pyargs sparse/mlir_backend -v" }
31+
test-finch = { cmd = "pytest --pyargs sparse/tests -n auto -v", depends-on = ["precompile"] }
3232

3333
[feature.tests.dependencies]
3434
pytest = ">=3.5"
@@ -51,10 +51,19 @@ precompile = "python -c 'import finch'"
5151
scipy = ">=0.19"
5252
finch-tensor = ">=0.1.31"
5353

54+
[feature.finch.activation.env]
55+
SPARSE_BACKEND = "Finch"
56+
57+
[feature.finch.target.osx-arm64.activation.env]
58+
PYTHONFAULTHANDLER = "${HOME}/faulthandler.log"
59+
5460
[feature.mlir.dependencies]
5561
scipy = ">=0.19"
5662
mlir-python-bindings = "19.*"
5763

64+
[feature.mlir.activation.env]
65+
SPARSE_BACKEND = "MLIR"
66+
5867
[environments]
5968
tests = ["tests", "extras"]
6069
docs = ["docs", "extras"]

sparse/mlir_backend/__init__.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
try:
22
import mlir # noqa: F401
3+
4+
del mlir
35
except ModuleNotFoundError as e:
46
raise ImportError(
57
"MLIR Python bindings not installed. Run "
68
"`conda install conda-forge::mlir-python-bindings` "
79
"to enable MLIR backend."
810
) from e
911

10-
from ._common import PackedArgumentTuple
11-
from ._conversions import asarray, to_numpy, to_scipy
12+
from . import levels
13+
from ._conversions import asarray, from_constituent_arrays, to_numpy, to_scipy
1214
from ._dtypes import asdtype
13-
from ._levels import StorageFormat
1415
from ._ops import add
1516

16-
__all__ = ["add", "asarray", "asdtype", "to_numpy", "to_scipy", "PackedArgumentTuple", "StorageFormat"]
17+
__all__ = ["add", "asarray", "asdtype", "to_numpy", "to_scipy", "levels", "from_constituent_arrays"]

sparse/mlir_backend/_array.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import dataclasses
22

3+
import numpy as np
4+
35
from ._common import _hold_ref, numpy_to_ranked_memref, ranked_memref_to_numpy
4-
from ._levels import StorageFormat
6+
from .levels import StorageFormat
57

68

79
class Array:
@@ -43,3 +45,8 @@ def copy(self):
4345
arr = Array(storage=storage_format._get_ctypes_type()(*memrefs), shape=self.shape)
4446
for carr in arrs:
4547
_hold_ref(arr, carr)
48+
49+
return arr
50+
51+
def get_constituent_arrays(self) -> tuple[np.ndarray, ...]:
52+
return self._storage.get_constituent_arrays()

sparse/mlir_backend/_common.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import ctypes
22
import functools
33
import weakref
4-
from dataclasses import dataclass
54

65
import mlir.runtime as rt
76

@@ -45,20 +44,6 @@ def free_memref(obj: ctypes.Structure) -> None:
4544
libc.free(ctypes.cast(obj.allocated, ctypes.c_void_p))
4645

4746

48-
@dataclass
49-
class PackedArgumentTuple:
50-
contents: tuple
51-
52-
def __getitem__(self, index):
53-
return self.contents[index]
54-
55-
def __iter__(self):
56-
yield from self.contents
57-
58-
def __len__(self):
59-
return len(self.contents)
60-
61-
6247
def _hold_self_ref_in_ret(fn):
6348
@functools.wraps(fn)
6449
def wrapped(self, *a, **kw):

sparse/mlir_backend/_conversions.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import dataclasses
12
import functools
23

34
import numpy as np
45

56
from ._array import Array
67
from ._common import _hold_ref, numpy_to_ranked_memref, ranked_memref_to_numpy
7-
from ._levels import Level, LevelFormat, LevelProperties, StorageFormat, get_storage_format
8+
from .levels import Level, LevelFormat, LevelProperties, StorageFormat, get_storage_format
89

910
try:
1011
import scipy.sparse as sps
@@ -178,3 +179,9 @@ def asarray(arr, copy: bool | None = None) -> Array:
178179
return arr
179180

180181
return _from_numpy(np.asarray(arr, copy=copy), copy=None)
182+
183+
184+
def from_constituent_arrays(*, format: StorageFormat, arrays: tuple[np.ndarray, ...], shape: tuple[int, ...]) -> Array:
185+
storage_format: StorageFormat = dataclasses.replace(format, owns_memory=False)
186+
storage = storage_format._get_ctypes_type().from_constituent_arrays(arrays)
187+
return Array(storage=storage, shape=shape)

sparse/mlir_backend/_levels.py renamed to sparse/mlir_backend/levels.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import numpy as np
1212

1313
from ._common import (
14-
PackedArgumentTuple,
1514
_hold_ref,
1615
fn_cache,
1716
free_memref,
@@ -24,6 +23,8 @@
2423

2524
_CAMEL_TO_SNAKE = [re.compile("(.)([A-Z][a-z]+)"), re.compile("([a-z0-9])([A-Z])")]
2625

26+
__all__ = ["LevelProperties", "LevelFormat", "StorageFormat", "Level", "get_storage_format"]
27+
2728

2829
def _camel_to_snake(name: str) -> str:
2930
for exp in _CAMEL_TO_SNAKE:
@@ -134,8 +135,11 @@ def to_module_arg(self) -> list:
134135
def get__fields_(self) -> list:
135136
return [getattr(self, field[0]) for field in self._fields_]
136137

137-
def to_constituent_arrays(self) -> PackedArgumentTuple:
138-
return PackedArgumentTuple(tuple(ranked_memref_to_numpy(field) for field in self.get__fields_()))
138+
def get_constituent_arrays(self) -> tuple[np.ndarray, ...]:
139+
arrays = tuple(ranked_memref_to_numpy(field) for field in self.get__fields_())
140+
for arr in arrays:
141+
_hold_ref(arr, self)
142+
return arrays
139143

140144
def get_storage_format(self) -> StorageFormat:
141145
return storage_format
@@ -155,13 +159,6 @@ def __del__(self) -> None:
155159

156160
return Storage
157161

158-
def from_constituent_arrays(self, arrs: list[np.ndarray], shape: tuple[int, ...]):
159-
from ._array import Array
160-
161-
storage = self._get_ctypes_type().from_constituent_arrays(arrs)
162-
163-
return Array(storage=storage, shape=shape)
164-
165162

166163
def get_storage_format(
167164
*,

sparse/mlir_backend/tests/test_simple.py

Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -178,65 +178,95 @@ def test_add(rng, dtype):
178178

179179
@parametrize_dtypes
180180
def test_csf_format(dtype):
181+
format = sparse.levels.get_storage_format(
182+
levels=(
183+
sparse.levels.Level(sparse.levels.LevelFormat.Dense),
184+
sparse.levels.Level(sparse.levels.LevelFormat.Compressed),
185+
sparse.levels.Level(sparse.levels.LevelFormat.Compressed),
186+
),
187+
order="C",
188+
pos_width=64,
189+
crd_width=64,
190+
dtype=sparse.asdtype(dtype),
191+
owns_memory=False,
192+
)
193+
181194
SHAPE = (2, 2, 4)
182195
pos_1, crd_1, pos_2, crd_2, data = get_exampe_csf_arrays(dtype)
183-
csf = [pos_1, crd_1, pos_2, crd_2, data]
196+
constituent_arrays = (pos_1, crd_1, pos_2, crd_2, data)
184197

185-
csf_tensor = sparse.asarray(csf, shape=SHAPE, dtype=sparse.asdtype(dtype), format="csf")
186-
result = csf_tensor.to_scipy_sparse()
187-
for actual, expected in zip(result, csf, strict=False):
198+
csf_array = sparse.from_constituent_arrays(format=format, arrays=constituent_arrays, shape=SHAPE)
199+
result_arrays = csf_array.get_constituent_arrays()
200+
for actual, expected in zip(result_arrays, constituent_arrays, strict=True):
188201
np.testing.assert_array_equal(actual, expected)
189202

190-
res_tensor = sparse.add(csf_tensor, csf_tensor).to_scipy_sparse()
191-
csf_2 = [pos_1, crd_1, pos_2, crd_2, data * 2]
192-
for actual, expected in zip(res_tensor, csf_2, strict=False):
203+
res_arrays = sparse.add(csf_array, csf_array).get_constituent_arrays()
204+
expected_arrays = (pos_1, crd_1, pos_2, crd_2, data * 2)
205+
for actual, expected in zip(res_arrays, expected_arrays, strict=True):
193206
np.testing.assert_array_equal(actual, expected)
194207

195208

196209
@parametrize_dtypes
197210
def test_coo_3d_format(dtype):
211+
format = sparse.levels.get_storage_format(
212+
levels=(
213+
sparse.levels.Level(sparse.levels.LevelFormat.Compressed, sparse.levels.LevelProperties.NonUnique),
214+
sparse.levels.Level(sparse.levels.LevelFormat.Singleton, sparse.levels.LevelProperties.NonUnique),
215+
sparse.levels.Level(sparse.levels.LevelFormat.Singleton, sparse.levels.LevelProperties.NonUnique),
216+
),
217+
order="C",
218+
pos_width=64,
219+
crd_width=64,
220+
dtype=sparse.asdtype(dtype),
221+
owns_memory=False,
222+
)
223+
198224
SHAPE = (2, 2, 4)
199225
pos = np.array([0, 7])
200226
crd = np.array([[0, 1, 0, 0, 1, 1, 0], [1, 3, 1, 0, 0, 1, 0], [3, 1, 1, 0, 1, 1, 1]])
201227
data = np.array([1, 2, 3, 4, 5, 6, 7], dtype=dtype)
202-
coo = [pos, crd, data]
228+
carrs = (pos, crd, data)
203229

204-
coo_tensor = sparse.asarray(coo, shape=SHAPE, dtype=sparse.asdtype(dtype), format="coo")
205-
result = coo_tensor.to_scipy_sparse()
206-
for actual, expected in zip(result, coo, strict=False):
230+
coo_array = sparse.from_constituent_arrays(format=format, arrays=carrs, shape=SHAPE)
231+
result = coo_array.get_constituent_arrays()
232+
for actual, expected in zip(result, carrs, strict=True):
207233
np.testing.assert_array_equal(actual, expected)
208234

209235
# NOTE: Blocked by https://github.com/llvm/llvm-project/pull/109135
210-
# res_tensor = sparse.add(coo_tensor, coo_tensor).to_scipy_sparse()
211-
# coo_2 = [pos, crd, data * 2]
212-
# for actual, expected in zip(res_tensor, coo_2, strict=False):
236+
# res_arrays = sparse.add(coo_array, coo_array).get_constituent_arrays()
237+
# res_expected = (pos, crd, data * 2)
238+
# for actual, expected in zip(res_arrays, res_expected, strict=False):
213239
# np.testing.assert_array_equal(actual, expected)
214240

215241

216242
@parametrize_dtypes
217243
def test_sparse_vector_format(dtype):
244+
format = sparse.levels.get_storage_format(
245+
levels=(sparse.levels.Level(sparse.levels.LevelFormat.Compressed),),
246+
order="C",
247+
pos_width=64,
248+
crd_width=64,
249+
dtype=sparse.asdtype(dtype),
250+
owns_memory=False,
251+
)
252+
218253
SHAPE = (10,)
219254
pos = np.array([0, 6])
220255
crd = np.array([0, 1, 2, 6, 8, 9])
221256
data = np.array([1, 2, 3, 4, 5, 6], dtype=dtype)
222-
sparse_vector = [pos, crd, data]
257+
carrs = (pos, crd, data)
223258

224-
sv_tensor = sparse.asarray(
225-
sparse_vector,
226-
shape=SHAPE,
227-
dtype=sparse.asdtype(dtype),
228-
format="sparse_vector",
229-
)
230-
result = sv_tensor.to_scipy_sparse()
231-
for actual, expected in zip(result, sparse_vector, strict=False):
259+
sv_array = sparse.from_constituent_arrays(format=format, arrays=carrs, shape=SHAPE)
260+
result = sv_array.get_constituent_arrays()
261+
for actual, expected in zip(result, carrs, strict=True):
232262
np.testing.assert_array_equal(actual, expected)
233263

234-
res_tensor = sparse.add(sv_tensor, sv_tensor).to_scipy_sparse()
235-
sparse_vector_2 = [pos, crd, data * 2]
236-
for actual, expected in zip(res_tensor, sparse_vector_2, strict=False):
264+
res_arrs = sparse.add(sv_array, sv_array).get_constituent_arrays()
265+
sv2_expected = (pos, crd, data * 2)
266+
for actual, expected in zip(res_arrs, sv2_expected, strict=True):
237267
np.testing.assert_array_equal(actual, expected)
238268

239269
dense = np.array([1, 2, 3, 0, 0, 0, 4, 0, 5, 6], dtype=dtype)
240-
dense_tensor = sparse.asarray(dense)
241-
res_tensor = sparse.add(dense_tensor, sv_tensor).to_scipy_sparse()
242-
np.testing.assert_array_equal(res_tensor, dense * 2)
270+
dense_array = sparse.asarray(dense)
271+
res = sparse.to_numpy(sparse.add(dense_array, sv_array))
272+
np.testing.assert_array_equal(res, dense * 2)

0 commit comments

Comments
 (0)