Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion graphblas/core/ss/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import suitesparse_graphblas as _ssgb

version_major, version_minor, version_bug = map(int, _ssgb.__version__.split(".")[:3])
# Version of the SuiteSparse:GraphBLAS C library, which is what every version
# gate in this package is really asking about. Read from the library's own
# constants rather than parsed out of ``_ssgb.__version__``: the wrapper's
# version normally tracks the library, but a development build between
# releases reports the previous one (10.4.1.0 against a 10.5.0 library), and a
# gate keyed on that takes the wrong branch without saying so. These are cffi
# ``#define`` constants, so they read at import time, before ``GrB_init``.
try:
version_major = _ssgb.lib.GxB_IMPLEMENTATION_MAJOR
version_minor = _ssgb.lib.GxB_IMPLEMENTATION_MINOR
version_bug = _ssgb.lib.GxB_IMPLEMENTATION_SUB
except AttributeError: # pragma: no cover (build without the GxB defines)
version_major, version_minor, version_bug = map(int, _ssgb.__version__.split(".")[:3])

_IS_SSGB7 = version_major == 7
7 changes: 6 additions & 1 deletion graphblas/tests/test_formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -5032,7 +5032,12 @@ def test_empty():

@pytest.mark.skipif("not pd")
def test_vector_as_matrix():
v = Vector.from_coo([1], [2], name="v_A")
# Built iso outright: the repr asserted below names the format, and
# SuiteSparse 10.5.0 no longer infers iso from a build whose values happen
# to be equal, so ``from_coo`` would render "bitmapc" on 10.5.0 and
# "bitmapc (iso)" on everything older.
v = Vector(int, 2, name="v_A")
v.ss.build_scalar([1], 2)
A = v._as_matrix()
repr_printer(A, "A")
assert repr(A) == (
Expand Down
14 changes: 12 additions & 2 deletions graphblas/tests/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,18 +430,28 @@ def test_awkward_roundtrip():
@pytest.mark.skipif("not ak")
@pytest.mark.xfail(np.__version__[:5] in {"1.25.", "1.26."}, reason="awkward bug with numpy >=1.25")
def test_awkward_iso_roundtrip():
# Build iso outright rather than relying on all-equal values: SuiteSparse
# 10.5.0 no longer checks after a build whether every value is the same,
# so ``from_coo`` alone would leave these non-iso and the test would stop
# exercising the iso path it is named for.
# Vector
v = gb.Vector.from_coo([1, 3, 5], [20, 20, 20], size=22)
if suitesparse:
v = gb.Vector(int, 22)
v.ss.build_scalar([1, 3, 5], 20)
assert v.ss.is_iso
else:
v = gb.Vector.from_coo([1, 3, 5], [20, 20, 20], size=22)
kv = gb.io.to_awkward(v)
assert isinstance(kv, ak.Array)
v2 = gb.io.from_awkward(kv)
assert v2.isequal(v)
# Matrix
m = gb.Matrix.from_coo([0, 0, 3, 5], [1, 4, 0, 2], [1, 1, 1, 1], nrows=7, ncols=6)
if suitesparse:
m = gb.Matrix(int, 7, 6)
m.ss.build_scalar([0, 0, 3, 5], [1, 4, 0, 2], 1)
assert m.ss.is_iso
else:
m = gb.Matrix.from_coo([0, 0, 3, 5], [1, 4, 0, 2], [1, 1, 1, 1], nrows=7, ncols=6)
for format in ["csr", "csc", "hypercsr", "hypercsc"]:
km = gb.io.to_awkward(m, format=format)
assert isinstance(km, ak.Array)
Expand Down
58 changes: 49 additions & 9 deletions graphblas/tests/test_ss_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,24 @@
pytest.skip("gb.ss and A.ss only available with suitesparse backend", allow_module_level=True)


# ``from_coo`` with all-equal values used to come back iso, because SuiteSparse
# checked after a build whether every value was the same. 10.5.0 dropped that
# check ("GrB_Matrix_build and GrB_Vector_build: no longer do a post-iso
# check"), so the iso arm of these tests has to ask for iso outright.
# ``ss.build_scalar`` does, and produces the same storage formats on every
# version, which is what the tests below are really selecting for.
def _iso_vector(indices, value, size):
v = Vector(int, size)
v.ss.build_scalar(indices, value)
return v


def _iso_matrix(rows, cols, value, nrows, ncols):
A = Matrix(int, nrows, ncols)
A.ss.build_scalar(rows, cols, value)
return A


@pytest.mark.parametrize("do_iso", [False, True])
def test_vector_head(do_iso):
v0 = Vector(int, 5)
Expand All @@ -19,9 +37,14 @@ def test_vector_head(do_iso):
values1 = [10, 20, 30]
values2 = [2, 4, 6]
values3 = [1, 2, 3]
v1 = Vector.from_coo([0, 1, 2], values1) # full
v2 = Vector.from_coo([1, 3, 5], values2) # bitmap
v3 = Vector.from_coo([100, 200, 300], values3) # sparse
if do_iso:
v1 = _iso_vector([0, 1, 2], values1[0], 3) # full
v2 = _iso_vector([1, 3, 5], values2[0], 6) # bitmap
v3 = _iso_vector([100, 200, 300], values3[0], 301) # sparse
else:
v1 = Vector.from_coo([0, 1, 2], values1) # full
v2 = Vector.from_coo([1, 3, 5], values2) # bitmap
v3 = Vector.from_coo([100, 200, 300], values3) # sparse
assert v1.ss.export()["format"] == "full"
assert v2.ss.export()["format"] == "bitmap"
assert v3.ss.export()["format"] == "sparse"
Expand Down Expand Up @@ -72,10 +95,16 @@ def test_matrix_head(do_iso):
values2 = [1, 2, 4]
values3 = values4 = [1, 2, 3]

A1 = Matrix.from_coo([0, 0, 1, 1], [0, 1, 0, 1], values1) # fullr
A2 = Matrix.from_coo([0, 0, 1], [0, 1, 1], values2) # Bitmap
A3 = Matrix.from_coo([5, 5, 10], [4, 5, 10], values3) # CSR
A4 = Matrix.from_coo([500, 500, 1000], [400, 500, 1000], values4) # HyperCSR
if do_iso:
A1 = _iso_matrix([0, 0, 1, 1], [0, 1, 0, 1], values1[0], 2, 2) # fullr
A2 = _iso_matrix([0, 0, 1], [0, 1, 1], values2[0], 2, 2) # Bitmap
A3 = _iso_matrix([5, 5, 10], [4, 5, 10], values3[0], 11, 11) # CSR
A4 = _iso_matrix([500, 500, 1000], [400, 500, 1000], values4[0], 1001, 1001) # HyperCSR
else:
A1 = Matrix.from_coo([0, 0, 1, 1], [0, 1, 0, 1], values1) # fullr
A2 = Matrix.from_coo([0, 0, 1], [0, 1, 1], values2) # Bitmap
A3 = Matrix.from_coo([5, 5, 10], [4, 5, 10], values3) # CSR
A4 = Matrix.from_coo([500, 500, 1000], [400, 500, 1000], values4) # HyperCSR
d = A1.ss.export(raw=True)
assert d["format"] == "fullr"
d["format"] = "fullc"
Expand Down Expand Up @@ -200,8 +229,19 @@ def test_about():


def test_openmp_enabled():
# SuiteSparse:GraphBLAS without OpenMP enabled is very undesirable
assert gb.ss.about["openmp"]
# A SuiteSparse:GraphBLAS built without OpenMP is single-threaded, which is
# a large and entirely silent performance loss: nothing else about the
# library reports it, and the conda package still depends on an OpenMP
# runtime whether or not the build actually found one. Keep this assertion
# hard; it is how the conda-forge osx-arm64 regression in graphblas 10.5.0
# was noticed.
assert gb.ss.about["openmp"], (
"libgraphblas was built without OpenMP and will run single-threaded. "
"This is a defect in the installed build, not in python-graphblas. "
"Reinstall or rebuild graphblas with OpenMP enabled "
"(SuiteSparse only warns when its CMake cannot find OpenMP, so a "
"serial library can ship looking normal)."
)


def test_global_config():
Expand Down
Loading