Skip to content
Open
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
28 changes: 1 addition & 27 deletions graphblas/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

from .. import backend, config
from .. import replace as replace_singleton
from ..dtypes import BOOL
from ..exceptions import check_status
from . import NULL
from .descriptor import lookup as descriptor_lookup
from .expr import AmbiguousAssignOrExtract, Updater
from .mask import Mask
from .mask import Mask, _check_mask
from .operator import UNKNOWN_OPCLASS, binary_from_string, find_opclass, get_typed_op
from .utils import _Pointer, libget, output_type

Expand Down Expand Up @@ -171,31 +170,6 @@ def _expect_op(self, op, values, *, within, **kwargs):
AmbiguousAssignOrExtract._expect_type = _expect_type


def _check_mask(mask, output=None, strict_kind=False):
if not isinstance(mask, Mask):
# Convert bool objects to value masks
if output_type(mask).__name__ in {"Vector", "Matrix"}:
if mask.dtype != BOOL:
raise TypeError(
f"Mask must be boolean objects (got {mask.dtype}) "
"or indicate values (M.V) or structure (M.S)"
)
mask = mask.V # auto-compute (will raise if disabled)
else:
raise TypeError(f"Invalid mask: {type(mask)}")
if output is not None:
if output.ndim == 1 and mask.parent.ndim != 1:
raise TypeError(f"Mask object must be type Vector; got {type(mask.parent)}")
# A full-tensor op (ewise, mxm, apply, extract, ...) into a Matrix needs
# a Matrix mask. Assignment is exempt (`strict_kind` stays False for it):
# a Vector mask on a Matrix row/column assign is valid and is validated
# separately in Matrix.__setitem__. Without this, a Vector mask on a
# full-Matrix op leaked a raw cffi "struct GB_Matrix_opaque" error.
if strict_kind and output.ndim == 2 and mask.parent.ndim != 2:
raise TypeError(f"Mask object must be type Matrix; got {type(mask.parent)}")
return mask


# Curated hints for common attribute-access mistakes on Vector/Matrix/Scalar.
# Only consulted from __getattr__, which fires solely on a genuine attribute
# miss, so the normal (slotted) attribute hot path is untouched.
Expand Down
2 changes: 1 addition & 1 deletion graphblas/core/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ def new(self, dtype=None, *, mask=None, input_mask=None, name=None, **opts):
if input_mask is not None:
if mask is not None:
raise TypeError("mask and input_mask arguments cannot both be given")
from .base import _check_mask
from .mask import _check_mask

input_mask = _check_mask(input_mask, self.parent)
mask = self._input_mask_to_mask(input_mask, **opts)
Expand Down
31 changes: 25 additions & 6 deletions graphblas/core/mask.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,6 @@ def new(self, dtype=None, *, complement=False, mask=None, name=None, **opts):
val(self, **opts) << True
return val

from .base import _check_mask

mask = _check_mask(mask)
d = _COMPLEMENT_MASKS if complement else _COMBINE_MASKS
func = d[type(self), type(mask)]
Expand All @@ -95,8 +93,6 @@ def __and__(self, other, **opts):
This uses faster recipes than the above for all combinations of input mask types,
and aims to be memory efficient when operating on complemented masks.
"""
from .base import _check_mask

other = _check_mask(other)
complement = self.complement or other.complement
d = _COMPLEMENT_MASKS if complement else _COMBINE_MASKS
Expand All @@ -121,8 +117,6 @@ def __or__(self, other, **opts):
This uses faster recipes than the above for all combinations of input mask types,
and aims to be memory efficient when operating on complemented masks.
"""
from .base import _check_mask

other = _check_mask(other)
func = _MASK_OR[type(self), type(other)]
return func(self, other, opts)
Expand Down Expand Up @@ -202,6 +196,31 @@ def _name_html(self):
return f"~{self.parent._name_html}.V"


def _check_mask(mask, output=None, strict_kind=False):
if not isinstance(mask, Mask):
# Convert bool objects to value masks
if utils.output_type(mask).__name__ in {"Vector", "Matrix"}:
if mask.dtype != BOOL:
raise TypeError(
f"Mask must be boolean objects (got {mask.dtype}) "
"or indicate values (M.V) or structure (M.S)"
)
mask = mask.V # auto-compute (will raise if disabled)
else:
raise TypeError(f"Invalid mask: {type(mask)}")
if output is not None:
if output.ndim == 1 and mask.parent.ndim != 1:
raise TypeError(f"Mask object must be type Vector; got {type(mask.parent)}")
# A full-tensor op (ewise, mxm, apply, extract, ...) into a Matrix needs
# a Matrix mask. Assignment is exempt (`strict_kind` stays False for it):
# a Vector mask on a Matrix row/column assign is valid and is validated
# separately in Matrix.__setitem__. Without this, a Vector mask on a
# full-Matrix op leaked a raw cffi "struct GB_Matrix_opaque" error.
if strict_kind and output.ndim == 2 and mask.parent.ndim != 2:
raise TypeError(f"Mask object must be type Matrix; got {type(mask.parent)}")
return mask


# Recipes to combine two masks.
# Legend:
# A: any
Expand Down
9 changes: 3 additions & 6 deletions graphblas/core/matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
check_status_carg,
)
from . import _supports_udfs, automethods, ffi, lib, utils
from .base import BaseExpression, BaseType, _check_mask, _is_recording, call
from .base import BaseExpression, BaseType, _is_recording, call
from .descriptor import lookup as descriptor_lookup
from .dtypes import _raise_dtype_or_arraylike
from .expr import _ALL_INDICES, AmbiguousAssignOrExtract, IndexerResolver, InfixExprBase, Updater
from .mask import Mask, StructuralMask, ValueMask
from .mask import Mask, StructuralMask, ValueMask, _check_mask
from .operator import (
UNKNOWN_OPCLASS,
_get_typed_op_from_exprs,
Expand Down Expand Up @@ -1727,10 +1727,7 @@ def from_dicts(
else:
# If we know the dtype, then using `np.fromiter` is much faster
dtype = lookup_dtype(dtype)
if dtype.np_type.subdtype is not None and np.__version__[:5] in {"1.21.", "1.22."}:
values, dtype = values_to_numpy_buffer(list(iter_values), dtype) # FLAKY COVERAGE
else:
values = np.fromiter(iter_values, dtype.np_type)
values = np.fromiter(iter_values, dtype.np_type)
return getattr(cls, methodname)(
*args, indptr, col_indices, values, dtype, nrows=nrows, ncols=ncols, name=name
)
Expand Down
9 changes: 3 additions & 6 deletions graphblas/core/vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
from ..dtypes import _INDEX, FP64, INT64, lookup_dtype, unify
from ..exceptions import DimensionMismatch, GrB_NO_VALUE, NoValue, check_status, check_status_carg
from . import _supports_udfs, automethods, ffi, lib, utils
from .base import BaseExpression, BaseType, _check_mask, _is_recording, call
from .base import BaseExpression, BaseType, _is_recording, call
from .descriptor import lookup as descriptor_lookup
from .dtypes import _raise_dtype_or_arraylike
from .expr import _ALL_INDICES, AmbiguousAssignOrExtract, IndexerResolver, InfixExprBase, Updater
from .mask import Mask, StructuralMask, ValueMask
from .mask import Mask, StructuralMask, ValueMask, _check_mask
from .operator import (
UNKNOWN_OPCLASS,
_get_typed_op_from_exprs,
Expand Down Expand Up @@ -2177,10 +2177,7 @@ def from_dict(cls, d, dtype=None, *, size=None, name=None):
else:
# If we know the dtype, then using `np.fromiter` is much faster
dtype = lookup_dtype(dtype)
if dtype.np_type.subdtype is not None and np.__version__[:5] in {"1.21.", "1.22."}:
values, dtype = values_to_numpy_buffer(list(d.values()), dtype) # FLAKY COVERAGE
else:
values = np.fromiter(d.values(), dtype.np_type)
values = np.fromiter(d.values(), dtype.np_type)
if size is None and indices.size == 0:
size = 0
return cls.from_coo(indices, values, dtype, size=size, name=name)
Expand Down
Loading