Skip to content

Commit

Permalink
Add vanilla recipe for slicing (python-graphblas#341)
Browse files Browse the repository at this point in the history
  • Loading branch information
eriknw authored Nov 30, 2022
1 parent 5eeb17c commit 969f320
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 25 deletions.
9 changes: 6 additions & 3 deletions graphblas/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def init(backend="suitesparse", blocking=False):
Parameters
----------
backend : str, one of {"suitesparse"}
backend : str, one of {"suitesparse", "suitesparse-vanilla"}
blocking : bool
Whether to call GrB_init with GrB_BLOCKING or GrB_NONBLOCKING
Expand Down Expand Up @@ -159,9 +159,12 @@ class Lib:
if callable(val) and key.startswith("GxB") or "FC32" in key or "FC64" in key:
continue
setattr(lib, key, getattr(orig_lib, key))

for key in {"GxB_BACKWARDS", "GxB_STRIDE"}:
delattr(lib, key)
else:
raise ValueError(f'Bad backend name. Must be "suitesparse". Got: {backend}')
raise ValueError(
f'Bad backend name. Must be "suitesparse" or "suitesparse-vanilla". Got: {backend}'
)
_init_params = passed_params

from . import core
Expand Down
12 changes: 8 additions & 4 deletions graphblas/core/expr.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import numpy as np

from .. import backend
from ..dtypes import _INDEX
from . import lib, utils
from .utils import _CArray, output_type
Expand Down Expand Up @@ -57,18 +58,21 @@ def _py_index(self):
return self.index.value
if self.index is _ALL_INDICES:
return slice(None)
from .slice import gxb_backwards, gxb_range, gxb_stride
if backend != "suitesparse":
return self.index.array

from .slice import _gxb_backwards, _gxb_range, _gxb_stride

if self.cscalar is gxb_backwards:
if self.cscalar is _gxb_backwards:
start, stop, step = self.index.array.tolist()
size = self.dimsize
stop -= size + 1
step = -step
elif self.cscalar is gxb_range:
elif self.cscalar is _gxb_range:
start, stop = self.index.array.tolist()
step = None
stop += 1
elif self.cscalar is gxb_stride:
elif self.cscalar is _gxb_stride:
start, stop, step = self.index.array.tolist()
stop += 1
else:
Expand Down
31 changes: 23 additions & 8 deletions graphblas/core/slice.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import numpy as np

from .. import backend
from ..dtypes import _INDEX
from . import lib
from .expr import _ALL_INDICES, AxisIndex
from .scalar import Scalar, _as_scalar
from .utils import _CArray

gxb_range = Scalar.from_value(lib.GxB_RANGE, dtype=_INDEX, name="GxB_RANGE", is_cscalar=True)
gxb_stride = Scalar.from_value(lib.GxB_STRIDE, dtype=_INDEX, name="GxB_STRIDE", is_cscalar=True)
gxb_backwards = Scalar.from_value(
lib.GxB_BACKWARDS, dtype=_INDEX, name="GxB_BACKWARDS", is_cscalar=True
)
if backend == "suitesparse":
_gxb_range = Scalar.from_value(lib.GxB_RANGE, dtype=_INDEX, name="GxB_RANGE", is_cscalar=True)
_gxb_stride = Scalar.from_value(
lib.GxB_STRIDE, dtype=_INDEX, name="GxB_STRIDE", is_cscalar=True
)
_gxb_backwards = Scalar.from_value(
lib.GxB_BACKWARDS, dtype=_INDEX, name="GxB_BACKWARDS", is_cscalar=True
)


def slice_to_index(index, size):
Expand All @@ -17,18 +23,27 @@ def slice_to_index(index, size):
if length == size and step == 1:
# [:] means all indices; use special GrB_ALL indicator
return AxisIndex(size, _ALL_INDICES, _as_scalar(size, _INDEX, is_cscalar=True), size)
if backend != "suitesparse":
# Danger zone: compute all the indices
return AxisIndex(
length,
_CArray(np.arange(start, stop, step, dtype=np.uint64)),
_as_scalar(length, _INDEX, is_cscalar=True),
size,
)

# SS, SuiteSparse-specific: slicing.
# For non-SuiteSparse, do: index = list(range(size)[index])
# SuiteSparse indexing is inclusive for both start and stop, and unsigned.
if step < 0:
if start < 0:
start = stop = 0 # Must be empty
return AxisIndex(length, _CArray([start, stop + 1, -step]), gxb_backwards, size)
return AxisIndex(length, _CArray([start, stop + 1, -step]), _gxb_backwards, size)
if stop > 0:
stop -= 1
elif start == 0:
# [0:0] slice should be empty, so change to [1:0]
start += 1
if step == 1:
return AxisIndex(length, _CArray([start, stop]), gxb_range, size)
return AxisIndex(length, _CArray([start, stop, step]), gxb_stride, size)
return AxisIndex(length, _CArray([start, stop]), _gxb_range, size)
return AxisIndex(length, _CArray([start, stop, step]), _gxb_stride, size)
51 changes: 41 additions & 10 deletions graphblas/tests/test_resolving.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import numpy as np
import pytest

from graphblas import binary, dtypes, replace, unary
from graphblas import backend, binary, dtypes, replace, unary
from graphblas.core.expr import Updater
from graphblas.core.utils import ensure_type

from graphblas import Matrix, Scalar, Vector # isort:skip (for dask-graphblas)

suitesparse = backend == "suitesparse"


def test_from_coo_dtype_resolving():
u = Vector.from_coo([0, 1, 2], [1, 2, 3], dtype=dtypes.INT32)
Expand Down Expand Up @@ -204,46 +206,75 @@ def test_py_indices():
np.testing.assert_array_equal(idx, [0, 2])

idx = v[0:0].resolved_indexes.py_indices
assert idx == slice(1, 1) # strange, but expected
if suitesparse:
assert idx == slice(1, 1) # strange, but expected
else:
np.testing.assert_array_equal(idx, [])
w = v[idx].new()
assert w.size == 0

idx = v[2:0].resolved_indexes.py_indices
assert idx == slice(2, 1) # strange, but expected
if suitesparse:
assert idx == slice(2, 1) # strange, but expected
else:
np.testing.assert_array_equal(idx, [])
w = v[idx].new()
assert w.size == 0

idx = v[::-1].resolved_indexes.py_indices
assert idx == slice(None, None, -1)
if suitesparse:
assert idx == slice(None, None, -1)
else:
np.testing.assert_array_equal(idx, list(range(5))[::-1])
w = v[idx].new()
expected = Vector.from_coo(np.arange(5), np.arange(5)[::-1])
assert w.isequal(expected)

idx = v[4:-3:-1].resolved_indexes.py_indices
assert idx == slice(None, -v.size - 1 + 3, -1)
if suitesparse:
assert idx == slice(None, -v.size - 1 + 3, -1)
else:
np.testing.assert_array_equal(idx, list(range(5))[4:-3:-1])
w = v[idx].new()
expected = Vector.from_coo(np.arange(2), np.arange(5)[4:-3:-1])
assert w.isequal(expected)

idx = v[1::2].resolved_indexes.py_indices
assert idx == slice(1, None, 2)
if suitesparse:
assert idx == slice(1, None, 2)
else:
np.testing.assert_array_equal(idx, list(range(5))[1::2])
w = v[idx].new()
expected = Vector.from_coo(np.arange(2), np.arange(5)[1::2])
assert w.isequal(expected)

A = Matrix.from_coo([0, 1, 2], [2, 0, 1], [0, 2, 3], nrows=10, ncols=10)
idx = A[1:6, 8:-8:-2].resolved_indexes.py_indices
assert idx == (slice(1, 6), slice(8, -8, -2))
if suitesparse:
assert idx == (slice(1, 6), slice(8, -8, -2))
else:
np.testing.assert_array_equal(idx[0], list(range(10))[1:6])
np.testing.assert_array_equal(idx[1], list(range(10))[8:-8:-2])

# start=0 -> None
idx = v[0:2].resolved_indexes.py_indices
assert idx == slice(None, 2)
if suitesparse:
assert idx == slice(None, 2)
else:
np.testing.assert_array_equal(idx, list(range(5))[0:2])

# stop=size -> None
idx = v[1 : v.size].resolved_indexes.py_indices
assert idx == slice(1, None)
if suitesparse:
assert idx == slice(1, None)
else:
np.testing.assert_array_equal(idx, list(range(5))[1 : v.size])
# step=1 -> None
idx = v[1:3:1].resolved_indexes.py_indices
assert idx == slice(1, 3)
if suitesparse:
assert idx == slice(1, 3)
else:
np.testing.assert_array_equal(idx, list(range(5))[1:3:1])
# All together now!
idx = v[0 : v.size : 1].resolved_indexes.py_indices
assert idx == slice(None)
Expand Down

0 comments on commit 969f320

Please sign in to comment.