diff --git a/graphblas/core/ss/matrix.py b/graphblas/core/ss/matrix.py index 245c6c276..4073ec740 100644 --- a/graphblas/core/ss/matrix.py +++ b/graphblas/core/ss/matrix.py @@ -1,6 +1,5 @@ import itertools import warnings -from numbers import Integral, Number import numba import numpy as np @@ -13,21 +12,23 @@ from ...dtypes import _INDEX, BOOL, INT64, _string_to_dtype, lookup_dtype from ...exceptions import _error_code_lookup, check_status, check_status_carg from .. import NULL, ffi, lib -from ..base import call, record_raw +from ..base import call from ..scalar import Scalar, _as_scalar, _scalar_index from ..utils import ( _CArray, + _MatrixArray, _Pointer, + get_order, get_shape, ints_to_numpy_buffer, libget, + normalize_chunks, output_type, values_to_numpy_buffer, wrapdoc, ) from .config import BaseConfig from .descriptor import get_compression_descriptor, get_nthreads_descriptor -from .utils import get_order ffi_new = ffi.new @@ -177,96 +178,6 @@ def head(matrix, n=10, dtype=None, *, sort=False): return rows, cols, vals -def normalize_chunks(chunks, shape): - """Normalize chunks argument for use by `Matrix.ss.split`. - - Examples - -------- - >>> shape = (10, 20) - >>> normalize_chunks(10, shape) - [(10,), (10, 10)] - >>> normalize_chunks((10, 10), shape) - [(10,), (10, 10)] - >>> normalize_chunks([None, (5, 15)], shape) - [(10,), (5, 15)] - >>> normalize_chunks((5, (5, None)), shape) - [(5, 5), (5, 15)] - """ - if isinstance(chunks, (list, tuple)): - pass - elif isinstance(chunks, Number): - chunks = (chunks,) * len(shape) - elif isinstance(chunks, np.ndarray): - chunks = chunks.tolist() - else: - raise TypeError( - f"chunks argument must be a list, tuple, or numpy array; got: {type(chunks)}" - ) - if len(chunks) != len(shape): - typ = "Vector" if len(shape) == 1 else "Matrix" - raise ValueError( - f"chunks argument must be of length {len(shape)} (one for each dimension of a {typ})" - ) - chunksizes = [] - for size, chunk in zip(shape, chunks): - if chunk is None: - cur_chunks = [size] - elif isinstance(chunk, Integral) or isinstance(chunk, float) and chunk.is_integer(): - chunk = int(chunk) - if chunk < 0: - raise ValueError(f"Chunksize must be greater than 0; got: {chunk}") - div, mod = divmod(size, chunk) - cur_chunks = [chunk] * div - if mod: - cur_chunks.append(mod) - elif isinstance(chunk, (list, tuple)): - cur_chunks = [] - none_index = None - for c in chunk: - if isinstance(c, Integral) or isinstance(c, float) and c.is_integer(): - c = int(c) - if c < 0: - raise ValueError(f"Chunksize must be greater than 0; got: {c}") - elif c is None: - if none_index is not None: - raise TypeError( - 'None value in chunks for "the rest" can only appear once per dimension' - ) - none_index = len(cur_chunks) - c = 0 - else: - raise TypeError( - "Bad type for element in chunks; expected int or None, but got: " - f"{type(chunks)}" - ) - cur_chunks.append(c) - if none_index is not None: - fill = size - sum(cur_chunks) - if fill < 0: - raise ValueError( - "Chunks are too large; None value in chunks would need to be negative " - "to match size of input" - ) - cur_chunks[none_index] = fill - elif isinstance(chunk, np.ndarray): - if not np.issubdtype(chunk.dtype, np.integer): - raise TypeError(f"numpy array for chunks must be integer dtype; got {chunk.dtype}") - if chunk.ndim != 1: - raise TypeError( - f"numpy array for chunks must be 1-dimension; got ndim={chunk.ndim}" - ) - if (chunk < 0).any(): - raise ValueError(f"Chunksize must be greater than 0; got: {chunk[chunk < 0]}") - cur_chunks = chunk.tolist() - else: - raise TypeError( - "Chunks for a dimension must be an integer, a list or tuple of integers, or None." - f" Got: {type(chunk)}" - ) - chunksizes.append(cur_chunks) - return chunksizes - - def _concat_mn(tiles, *, is_matrix=None): """Argument checking for `Matrix.ss.concat` and returns number of tiles in each dimension""" from ..matrix import Matrix, TransposedMatrix @@ -326,16 +237,6 @@ def _as_matrix(x): return x._as_matrix() if hasattr(x, "_as_matrix") else x -class MatrixArray: - __slots__ = "_carg", "_exc_arg", "name" - - def __init__(self, matrices, exc_arg=None, *, name): - self._carg = matrices - self._exc_arg = exc_arg - self.name = name - record_raw(f"GrB_Matrix {name}[{len(matrices)}];") - - class MatrixConfig(BaseConfig): """Get and set configuration options for this Matrix. @@ -539,7 +440,7 @@ def split(self, chunks, *, name=None): call( "GxB_Matrix_split", [ - MatrixArray(tiles, self._parent, name="tiles"), + _MatrixArray(tiles, self._parent, name="tiles"), _as_scalar(m, _INDEX, is_cscalar=True), _as_scalar(n, _INDEX, is_cscalar=True), _CArray(tile_nrows), @@ -580,7 +481,7 @@ def _concat(self, tiles, m, n): "GxB_Matrix_concat", [ self._parent, - MatrixArray(ctiles, name="tiles"), + _MatrixArray(ctiles, name="tiles"), _as_scalar(m, _INDEX, is_cscalar=True), _as_scalar(n, _INDEX, is_cscalar=True), None, diff --git a/graphblas/core/ss/utils.py b/graphblas/core/ss/utils.py deleted file mode 100644 index b9f87981f..000000000 --- a/graphblas/core/ss/utils.py +++ /dev/null @@ -1,11 +0,0 @@ -def get_order(order): - val = order.lower() - if val in {"c", "row", "rows", "rowwise"}: - return "rowwise" - elif val in {"f", "col", "cols", "column", "columns", "colwise", "columnwise"}: - return "columnwise" - else: - raise ValueError( - f"Bad value for order: {order!r}. " - 'Expected "rowwise", "columnwise", "rows", "columns", "C", or "F"' - ) diff --git a/graphblas/core/ss/vector.py b/graphblas/core/ss/vector.py index 79da58c97..870246dcf 100644 --- a/graphblas/core/ss/vector.py +++ b/graphblas/core/ss/vector.py @@ -13,12 +13,20 @@ from .. import NULL, ffi, lib from ..base import call from ..scalar import Scalar, _as_scalar -from ..utils import _CArray, ints_to_numpy_buffer, libget, values_to_numpy_buffer, wrapdoc +from ..utils import ( + _CArray, + _MatrixArray, + get_order, + ints_to_numpy_buffer, + libget, + normalize_chunks, + values_to_numpy_buffer, + wrapdoc, +) from .config import BaseConfig from .descriptor import get_compression_descriptor, get_nthreads_descriptor -from .matrix import MatrixArray, _concat_mn, normalize_chunks +from .matrix import _concat_mn from .prefix_scan import prefix_scan -from .utils import get_order ffi_new = ffi.new @@ -257,7 +265,7 @@ def split(self, chunks, *, name=None): call( "GxB_Matrix_split", [ - MatrixArray(tiles, parent, name="tiles"), + _MatrixArray(tiles, parent, name="tiles"), _as_scalar(m, _INDEX, is_cscalar=True), _as_scalar(1, _INDEX, is_cscalar=True), _CArray(tile_nrows), @@ -286,7 +294,7 @@ def _concat(self, tiles, m): "GxB_Matrix_concat", [ self._parent._as_matrix(), - MatrixArray(ctiles, name="tiles"), + _MatrixArray(ctiles, name="tiles"), _as_scalar(m, _INDEX, is_cscalar=True), _as_scalar(1, _INDEX, is_cscalar=True), None, diff --git a/graphblas/core/utils.py b/graphblas/core/utils.py index 48831a3c6..8a236bbfd 100644 --- a/graphblas/core/utils.py +++ b/graphblas/core/utils.py @@ -1,3 +1,5 @@ +from numbers import Integral, Number + import numpy as np from ..dtypes import _INDEX, lookup_dtype @@ -109,6 +111,109 @@ def get_shape(nrows, ncols, dtype=None, **arrays): return nrows, ncols +def get_order(order): + val = order.lower() + if val in {"c", "row", "rows", "rowwise"}: + return "rowwise" + elif val in {"f", "col", "cols", "column", "columns", "colwise", "columnwise"}: + return "columnwise" + else: + raise ValueError( + f"Bad value for order: {order!r}. " + 'Expected "rowwise", "columnwise", "rows", "columns", "C", or "F"' + ) + + +def normalize_chunks(chunks, shape): + """Normalize chunks argument for use by `Matrix.ss.split`. + + Examples + -------- + >>> shape = (10, 20) + >>> normalize_chunks(10, shape) + [(10,), (10, 10)] + >>> normalize_chunks((10, 10), shape) + [(10,), (10, 10)] + >>> normalize_chunks([None, (5, 15)], shape) + [(10,), (5, 15)] + >>> normalize_chunks((5, (5, None)), shape) + [(5, 5), (5, 15)] + """ + if isinstance(chunks, (list, tuple)): + pass + elif isinstance(chunks, Number): + chunks = (chunks,) * len(shape) + elif isinstance(chunks, np.ndarray): + chunks = chunks.tolist() + else: + raise TypeError( + f"chunks argument must be a list, tuple, or numpy array; got: {type(chunks)}" + ) + if len(chunks) != len(shape): + typ = "Vector" if len(shape) == 1 else "Matrix" + raise ValueError( + f"chunks argument must be of length {len(shape)} (one for each dimension of a {typ})" + ) + chunksizes = [] + for size, chunk in zip(shape, chunks): + if chunk is None: + cur_chunks = [size] + elif isinstance(chunk, Integral) or isinstance(chunk, float) and chunk.is_integer(): + chunk = int(chunk) + if chunk < 0: + raise ValueError(f"Chunksize must be greater than 0; got: {chunk}") + div, mod = divmod(size, chunk) + cur_chunks = [chunk] * div + if mod: + cur_chunks.append(mod) + elif isinstance(chunk, (list, tuple)): + cur_chunks = [] + none_index = None + for c in chunk: + if isinstance(c, Integral) or isinstance(c, float) and c.is_integer(): + c = int(c) + if c < 0: + raise ValueError(f"Chunksize must be greater than 0; got: {c}") + elif c is None: + if none_index is not None: + raise TypeError( + 'None value in chunks for "the rest" can only appear once per dimension' + ) + none_index = len(cur_chunks) + c = 0 + else: + raise TypeError( + "Bad type for element in chunks; expected int or None, but got: " + f"{type(chunks)}" + ) + cur_chunks.append(c) + if none_index is not None: + fill = size - sum(cur_chunks) + if fill < 0: + raise ValueError( + "Chunks are too large; None value in chunks would need to be negative " + "to match size of input" + ) + cur_chunks[none_index] = fill + elif isinstance(chunk, np.ndarray): + if not np.issubdtype(chunk.dtype, np.integer): + raise TypeError(f"numpy array for chunks must be integer dtype; got {chunk.dtype}") + if chunk.ndim != 1: + raise TypeError( + f"numpy array for chunks must be 1-dimension; got ndim={chunk.ndim}" + ) + if (chunk < 0).any(): + raise ValueError(f"Chunksize must be greater than 0; got: {chunk[chunk < 0]}") + cur_chunks = chunk.tolist() + else: + raise TypeError( + "Chunks for a dimension must be an integer, a list or tuple of integers, or None." + f" Got: {type(chunk)}" + ) + chunksizes.append(cur_chunks) + return chunksizes + + class class_property: __slots__ = "classval", "member_property" @@ -175,6 +280,18 @@ def name(self): return f"&{name}" +class _MatrixArray: + __slots__ = "_carg", "_exc_arg", "name" + + def __init__(self, matrices, exc_arg=None, *, name): + from .base import record_raw + + self._carg = matrices + self._exc_arg = exc_arg + self.name = name + record_raw(f"GrB_Matrix {name}[{len(matrices)}];") + + def _autogenerate_code( filename, text, diff --git a/graphblas/tests/test_matrix.py b/graphblas/tests/test_matrix.py index da9cbdfc5..07051edeb 100644 --- a/graphblas/tests/test_matrix.py +++ b/graphblas/tests/test_matrix.py @@ -2527,7 +2527,7 @@ def test_diag(A, params): def test_normalize_chunks(): - from graphblas.core.ss.matrix import normalize_chunks + from graphblas.core.utils import normalize_chunks shape = (20, 20) assert normalize_chunks(10, shape) == [[10, 10], [10, 10]]