python-graphblas 2024.2.0__py3-none-any.whl → 2025.2.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
graphblas/core/base.py CHANGED
@@ -513,7 +513,7 @@ class BaseType:
513
513
  _expect_op = _expect_op
514
514
 
515
515
  # Don't let non-scalars be coerced to numpy arrays
516
- def __array__(self, dtype=None):
516
+ def __array__(self, dtype=None, *, copy=None):
517
517
  raise TypeError(
518
518
  f"{type(self).__name__} can't be directly converted to a numpy array; "
519
519
  f"perhaps use `{self.name}.to_coo()` method instead."
graphblas/core/dtypes.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import warnings
2
+ from ast import literal_eval
2
3
 
3
4
  import numpy as np
4
5
  from numpy import promote_types, result_type
@@ -97,7 +98,7 @@ def register_anonymous(dtype, name=None):
97
98
  # Allow dtypes such as `"INT64[3, 4]"` for convenience
98
99
  base_dtype, shape = dtype.split("[", 1)
99
100
  base_dtype = lookup_dtype(base_dtype)
100
- shape = np.lib.format.safe_eval(f"[{shape}")
101
+ shape = literal_eval(f"[{shape}")
101
102
  dtype = np.dtype((base_dtype.np_type, shape))
102
103
  else:
103
104
  raise
@@ -115,7 +116,17 @@ def register_anonymous(dtype, name=None):
115
116
  from ..exceptions import check_status_carg
116
117
 
117
118
  gb_obj = ffi.new("GrB_Type*")
118
- if backend == "suitesparse":
119
+
120
+ if hasattr(lib, "GrB_Type_set_String"):
121
+ # We name this so that we can serialize and deserialize UDTs
122
+ # We don't yet have C definitions
123
+ np_repr = _dtype_to_string(dtype)
124
+ status = lib.GrB_Type_new(gb_obj, dtype.itemsize)
125
+ check_status_carg(status, "Type", gb_obj[0])
126
+ val_obj = ffi.new("char[]", np_repr.encode())
127
+ status = lib.GrB_Type_set_String(gb_obj[0], val_obj, lib.GrB_NAME)
128
+ elif backend == "suitesparse":
129
+ # For SuiteSparse < 9
119
130
  # We name this so that we can serialize and deserialize UDTs
120
131
  # We don't yet have C definitions
121
132
  np_repr = _dtype_to_string(dtype).encode()
@@ -429,7 +440,7 @@ def _dtype_to_string(dtype):
429
440
  np_type = dtype.np_type
430
441
  s = str(np_type)
431
442
  try:
432
- if np.dtype(np.lib.format.safe_eval(s)) == np_type: # pragma: no branch (safety)
443
+ if np.dtype(literal_eval(s)) == np_type: # pragma: no branch (safety)
433
444
  return s
434
445
  except Exception:
435
446
  pass
@@ -448,5 +459,5 @@ def _string_to_dtype(s):
448
459
  return lookup_dtype(s)
449
460
  except Exception:
450
461
  pass
451
- np_type = np.dtype(np.lib.format.safe_eval(s))
462
+ np_type = np.dtype(literal_eval(s))
452
463
  return lookup_dtype(np_type)
@@ -630,7 +630,7 @@ def create_header(type_name, keys, vals, *, lower_border=False, name="", quote=T
630
630
  name = f'"{name}"'
631
631
  key_text = []
632
632
  val_text = []
633
- for key, val in zip(keys, vals):
633
+ for key, val in zip(keys, vals, strict=True):
634
634
  width = max(len(key), len(val)) + 2
635
635
  key_text.append(key.rjust(width))
636
636
  val_text.append(val.rjust(width))
graphblas/core/infix.py CHANGED
@@ -316,6 +316,7 @@ class MatrixInfixExpr(InfixExprBase):
316
316
  ndim = 2
317
317
  output_type = MatrixExpression
318
318
  _is_transposed = False
319
+ __networkx_backend__ = "graphblas"
319
320
  __networkx_plugin__ = "graphblas"
320
321
 
321
322
  def __init__(self, left, right):
graphblas/core/matrix.py CHANGED
@@ -184,6 +184,7 @@ class Matrix(BaseType):
184
184
  ndim = 2
185
185
  _is_transposed = False
186
186
  _name_counter = itertools.count()
187
+ __networkx_backend__ = "graphblas"
187
188
  __networkx_plugin__ = "graphblas"
188
189
 
189
190
  def __new__(cls, dtype=FP64, nrows=0, ncols=0, *, name=None):
@@ -360,7 +361,7 @@ class Matrix(BaseType):
360
361
  def __iter__(self):
361
362
  """Iterate over (row, col) indices which are present in the matrix."""
362
363
  rows, columns, _ = self.to_coo(values=False)
363
- return zip(rows.flat, columns.flat)
364
+ return zip(rows.flat, columns.flat, strict=True)
364
365
 
365
366
  def __sizeof__(self):
366
367
  if backend == "suitesparse":
@@ -961,7 +962,7 @@ class Matrix(BaseType):
961
962
  rows = edgelist[:, 0]
962
963
  cols = edgelist[:, 1]
963
964
  else:
964
- unzipped = list(zip(*edgelist))
965
+ unzipped = list(zip(*edgelist, strict=True))
965
966
  if len(unzipped) == 2:
966
967
  rows, cols = unzipped
967
968
  elif len(unzipped) == 3:
@@ -1826,10 +1827,11 @@ class Matrix(BaseType):
1826
1827
  cols = cols.tolist()
1827
1828
  values = values.tolist()
1828
1829
  return {
1829
- row: dict(zip(cols[start:stop], values[start:stop]))
1830
+ row: dict(zip(cols[start:stop], values[start:stop], strict=True))
1830
1831
  for row, (start, stop) in zip(
1831
1832
  compressed_rows.tolist(),
1832
1833
  np.lib.stride_tricks.sliding_window_view(indptr, 2).tolist(),
1834
+ strict=True,
1833
1835
  )
1834
1836
  }
1835
1837
  # Alternative
@@ -3582,6 +3584,7 @@ class MatrixExpression(BaseExpression):
3582
3584
  ndim = 2
3583
3585
  output_type = Matrix
3584
3586
  _is_transposed = False
3587
+ __networkx_backend__ = "graphblas"
3585
3588
  __networkx_plugin__ = "graphblas"
3586
3589
 
3587
3590
  def __init__(
@@ -3723,6 +3726,7 @@ class MatrixIndexExpr(AmbiguousAssignOrExtract):
3723
3726
  ndim = 2
3724
3727
  output_type = Matrix
3725
3728
  _is_transposed = False
3729
+ __networkx_backend__ = "graphblas"
3726
3730
  __networkx_plugin__ = "graphblas"
3727
3731
 
3728
3732
  def __init__(self, parent, resolved_indexes, nrows, ncols):
@@ -3823,6 +3827,7 @@ class TransposedMatrix:
3823
3827
  ndim = 2
3824
3828
  _is_scalar = False
3825
3829
  _is_transposed = True
3830
+ __networkx_backend__ = "graphblas"
3826
3831
  __networkx_plugin__ = "graphblas"
3827
3832
 
3828
3833
  def __init__(self, matrix):
@@ -251,8 +251,7 @@ class TypedOpBase:
251
251
 
252
252
  def __repr__(self):
253
253
  classname = self.opclass.lower()
254
- if classname.endswith("op"):
255
- classname = classname[:-2]
254
+ classname = classname.removesuffix("op")
256
255
  dtype2 = "" if self._type2 is None else f", {self._type2.name}"
257
256
  return f"{classname}.{self.name}[{self.type.name}{dtype2}]"
258
257
 
graphblas/core/scalar.py CHANGED
@@ -165,7 +165,7 @@ class Scalar(BaseType):
165
165
  return self.__int__
166
166
  raise AttributeError("Scalar object only has `__index__` for integral dtypes")
167
167
 
168
- def __array__(self, dtype=None):
168
+ def __array__(self, dtype=None, *, copy=None):
169
169
  if dtype is None:
170
170
  dtype = self.dtype.np_type
171
171
  return np.array(self.value, dtype=dtype)
@@ -1,3 +1,5 @@
1
1
  import suitesparse_graphblas as _ssgb
2
2
 
3
- _IS_SSGB7 = _ssgb.__version__.split(".", 1)[0] == "7"
3
+ (version_major, version_minor, version_bug) = map(int, _ssgb.__version__.split(".")[:3])
4
+
5
+ _IS_SSGB7 = version_major == 7
@@ -99,7 +99,7 @@ class BaseConfig(MutableMapping):
99
99
  return {reverse_bitwise[val]}
100
100
  rv = set()
101
101
  for k, v in self._bitwise[key].items():
102
- if isinstance(k, str) and val & v and bin(v).count("1") == 1:
102
+ if isinstance(k, str) and val & v and (v).bit_count() == 1:
103
103
  rv.add(k)
104
104
  return rv
105
105
  if is_bool:
@@ -58,7 +58,7 @@ def head(matrix, n=10, dtype=None, *, sort=False):
58
58
  dtype = matrix.dtype
59
59
  else:
60
60
  dtype = lookup_dtype(dtype)
61
- rows, cols, vals = zip(*itertools.islice(matrix.ss.iteritems(), n))
61
+ rows, cols, vals = zip(*itertools.islice(matrix.ss.iteritems(), n), strict=True)
62
62
  return np.array(rows, np.uint64), np.array(cols, np.uint64), np.array(vals, dtype.np_type)
63
63
 
64
64
 
@@ -3650,8 +3650,10 @@ class ss:
3650
3650
  def unpack_hyperhash(self, *, compute=False, name=None, **opts):
3651
3651
  """Unpacks the hyper_hash of a hypersparse matrix if possible.
3652
3652
 
3653
- Will return None if the matrix is not hypersparse or if the hash is not computed.
3654
- Use ``compute=True`` to compute the hyper_hash if the input is hypersparse.
3653
+ Will return None if the matrix is not hypersparse, if the hash is not computed,
3654
+ or if the hash is not needed. Use ``compute=True`` to try to compute the hyper_hash
3655
+ if the input is hypersparse. The hyper_hash is optional in SuiteSparse:GraphBLAS,
3656
+ so it may not be computed even with ``compute=True``.
3655
3657
 
3656
3658
  Use ``pack_hyperhash`` to move a hyper_hash matrix that was previously unpacked
3657
3659
  back into a matrix.
@@ -4079,6 +4081,21 @@ class ss:
4079
4081
  blob_handle = ffi_new("void**")
4080
4082
  blob_size_handle = ffi_new("GrB_Index*")
4081
4083
  parent = self._parent
4084
+ if parent.dtype._is_udt and hasattr(lib, "GrB_Type_get_String"):
4085
+ # Get the name from the dtype and set it to the name of the matrix so we can
4086
+ # recreate the UDT. This is a bit hacky and we should restore the original name.
4087
+ # First get the size of name.
4088
+ dtype_size = ffi_new("size_t*")
4089
+ status = lib.GrB_Type_get_SIZE(parent.dtype.gb_obj[0], dtype_size, lib.GrB_NAME)
4090
+ check_status_carg(status, "Type", parent.dtype.gb_obj[0])
4091
+ # Then get the name
4092
+ dtype_char = ffi_new(f"char[{dtype_size[0]}]")
4093
+ status = lib.GrB_Type_get_String(parent.dtype.gb_obj[0], dtype_char, lib.GrB_NAME)
4094
+ check_status_carg(status, "Type", parent.dtype.gb_obj[0])
4095
+ # Then set the name
4096
+ status = lib.GrB_Matrix_set_String(parent._carg, dtype_char, lib.GrB_NAME)
4097
+ check_status_carg(status, "Matrix", parent._carg)
4098
+
4082
4099
  check_status(
4083
4100
  lib.GxB_Matrix_serialize(
4084
4101
  blob_handle,
@@ -4120,8 +4137,8 @@ class ss:
4120
4137
  else:
4121
4138
  data = np.frombuffer(data, np.uint8)
4122
4139
  data_obj = ffi.from_buffer("void*", data)
4123
- # Get the dtype name first
4124
4140
  if dtype is None:
4141
+ # Get the dtype name first (for non-UDTs)
4125
4142
  cname = ffi_new(f"char[{lib.GxB_MAX_NAME_LEN}]")
4126
4143
  info = lib.GxB_deserialize_type_name(
4127
4144
  cname,
@@ -4131,6 +4148,22 @@ class ss:
4131
4148
  if info != lib.GrB_SUCCESS:
4132
4149
  raise _error_code_lookup[info]("Matrix deserialize failed to get the dtype name")
4133
4150
  dtype_name = b"".join(itertools.takewhile(b"\x00".__ne__, cname)).decode()
4151
+ if not dtype_name and hasattr(lib, "GxB_Serialized_get_String"):
4152
+ # Handle UDTs. First get the size of name
4153
+ dtype_size = ffi_new("size_t*")
4154
+ info = lib.GxB_Serialized_get_SIZE(data_obj, dtype_size, lib.GrB_NAME, data.nbytes)
4155
+ if info != lib.GrB_SUCCESS:
4156
+ raise _error_code_lookup[info](
4157
+ "Matrix deserialize failed to get the size of name"
4158
+ )
4159
+ # Then get the name
4160
+ dtype_char = ffi_new(f"char[{dtype_size[0]}]")
4161
+ info = lib.GxB_Serialized_get_String(
4162
+ data_obj, dtype_char, lib.GrB_NAME, data.nbytes
4163
+ )
4164
+ if info != lib.GrB_SUCCESS:
4165
+ raise _error_code_lookup[info]("Matrix deserialize failed to get the name")
4166
+ dtype_name = ffi.string(dtype_char).decode()
4134
4167
  dtype = _string_to_dtype(dtype_name)
4135
4168
  else:
4136
4169
  dtype = lookup_dtype(dtype)
@@ -43,7 +43,7 @@ def head(vector, n=10, dtype=None, *, sort=False):
43
43
  dtype = vector.dtype
44
44
  else:
45
45
  dtype = lookup_dtype(dtype)
46
- indices, vals = zip(*itertools.islice(vector.ss.iteritems(), n))
46
+ indices, vals = zip(*itertools.islice(vector.ss.iteritems(), n), strict=True)
47
47
  return np.array(indices, np.uint64), np.array(vals, dtype.np_type)
48
48
 
49
49
 
@@ -1652,6 +1652,21 @@ class ss:
1652
1652
  blob_handle = ffi_new("void**")
1653
1653
  blob_size_handle = ffi_new("GrB_Index*")
1654
1654
  parent = self._parent
1655
+ if parent.dtype._is_udt and hasattr(lib, "GrB_Type_get_String"):
1656
+ # Get the name from the dtype and set it to the name of the vector so we can
1657
+ # recreate the UDT. This is a bit hacky and we should restore the original name.
1658
+ # First get the size of name.
1659
+ dtype_size = ffi_new("size_t*")
1660
+ status = lib.GrB_Type_get_SIZE(parent.dtype.gb_obj[0], dtype_size, lib.GrB_NAME)
1661
+ check_status_carg(status, "Type", parent.dtype.gb_obj[0])
1662
+ # Then get the name
1663
+ dtype_char = ffi_new(f"char[{dtype_size[0]}]")
1664
+ status = lib.GrB_Type_get_String(parent.dtype.gb_obj[0], dtype_char, lib.GrB_NAME)
1665
+ check_status_carg(status, "Type", parent.dtype.gb_obj[0])
1666
+ # Then set the name
1667
+ status = lib.GrB_Vector_set_String(parent._carg, dtype_char, lib.GrB_NAME)
1668
+ check_status_carg(status, "Vector", parent._carg)
1669
+
1655
1670
  check_status(
1656
1671
  lib.GxB_Vector_serialize(
1657
1672
  blob_handle,
@@ -1694,7 +1709,7 @@ class ss:
1694
1709
  data = np.frombuffer(data, np.uint8)
1695
1710
  data_obj = ffi.from_buffer("void*", data)
1696
1711
  if dtype is None:
1697
- # Get the dtype name first
1712
+ # Get the dtype name first (for non-UDTs)
1698
1713
  cname = ffi_new(f"char[{lib.GxB_MAX_NAME_LEN}]")
1699
1714
  info = lib.GxB_deserialize_type_name(
1700
1715
  cname,
@@ -1704,6 +1719,22 @@ class ss:
1704
1719
  if info != lib.GrB_SUCCESS:
1705
1720
  raise _error_code_lookup[info]("Vector deserialize failed to get the dtype name")
1706
1721
  dtype_name = b"".join(itertools.takewhile(b"\x00".__ne__, cname)).decode()
1722
+ if not dtype_name and hasattr(lib, "GxB_Serialized_get_String"):
1723
+ # Handle UDTs. First get the size of name
1724
+ dtype_size = ffi_new("size_t*")
1725
+ info = lib.GxB_Serialized_get_SIZE(data_obj, dtype_size, lib.GrB_NAME, data.nbytes)
1726
+ if info != lib.GrB_SUCCESS:
1727
+ raise _error_code_lookup[info](
1728
+ "Vector deserialize failed to get the size of name"
1729
+ )
1730
+ # Then get the name
1731
+ dtype_char = ffi_new(f"char[{dtype_size[0]}]")
1732
+ info = lib.GxB_Serialized_get_String(
1733
+ data_obj, dtype_char, lib.GrB_NAME, data.nbytes
1734
+ )
1735
+ if info != lib.GrB_SUCCESS:
1736
+ raise _error_code_lookup[info]("Vector deserialize failed to get the name")
1737
+ dtype_name = ffi.string(dtype_char).decode()
1707
1738
  dtype = _string_to_dtype(dtype_name)
1708
1739
  else:
1709
1740
  dtype = lookup_dtype(dtype)
graphblas/core/utils.py CHANGED
@@ -5,6 +5,8 @@ import numpy as np
5
5
  from ..dtypes import _INDEX, lookup_dtype
6
6
  from . import ffi, lib
7
7
 
8
+ _NP2 = np.__version__.startswith("2.")
9
+
8
10
 
9
11
  def libget(name):
10
12
  """Helper to get items from GraphBLAS which might be GrB or GxB."""
@@ -60,7 +62,8 @@ def ints_to_numpy_buffer(array, dtype, *, name="array", copy=False, ownable=Fals
60
62
  and not np.issubdtype(array.dtype, np.bool_)
61
63
  ):
62
64
  raise ValueError(f"{name} must be integers, not {array.dtype.name}")
63
- array = np.array(array, dtype, copy=copy, order=order)
65
+ # https://numpy.org/doc/stable/release/2.0.0-notes.html#new-copy-keyword-meaning-for-array-and-asarray-constructors
66
+ array = np.array(array, dtype, copy=copy or _NP2 and None, order=order)
64
67
  if ownable and (not array.flags.owndata or not array.flags.writeable):
65
68
  array = array.copy(order)
66
69
  return array
@@ -90,10 +93,14 @@ def values_to_numpy_buffer(
90
93
  """
91
94
  if dtype is not None:
92
95
  dtype = lookup_dtype(dtype)
93
- array = np.array(array, _get_subdtype(dtype.np_type), copy=copy, order=order)
96
+ # https://numpy.org/doc/stable/release/2.0.0-notes.html#new-copy-keyword-meaning-for-array-and-asarray-constructors
97
+ array = np.array(
98
+ array, _get_subdtype(dtype.np_type), copy=copy or _NP2 and None, order=order
99
+ )
94
100
  else:
95
101
  is_input_np = isinstance(array, np.ndarray)
96
- array = np.array(array, copy=copy, order=order)
102
+ # https://numpy.org/doc/stable/release/2.0.0-notes.html#new-copy-keyword-meaning-for-array-and-asarray-constructors
103
+ array = np.array(array, copy=copy or _NP2 and None, order=order)
97
104
  if array.dtype.hasobject:
98
105
  raise ValueError("object dtype for values is not allowed")
99
106
  if not is_input_np and array.dtype == np.int32: # pragma: no cover
@@ -202,7 +209,7 @@ def normalize_chunks(chunks, shape):
202
209
  f"chunks argument must be of length {len(shape)} (one for each dimension of a {typ})"
203
210
  )
204
211
  chunksizes = []
205
- for size, chunk in zip(shape, chunks):
212
+ for size, chunk in zip(shape, chunks, strict=True):
206
213
  if chunk is None:
207
214
  cur_chunks = [size]
208
215
  elif (c := maybe_integral(chunk)) is not None:
@@ -312,7 +319,10 @@ class _CArray:
312
319
  if size is not None:
313
320
  self.array = np.empty(size, dtype=dtype.np_type)
314
321
  else:
315
- self.array = np.array(array, dtype=_get_subdtype(dtype.np_type), copy=False, order="C")
322
+ # https://numpy.org/doc/stable/release/2.0.0-notes.html#new-copy-keyword-meaning-for-array-and-asarray-constructors
323
+ self.array = np.array(
324
+ array, dtype=_get_subdtype(dtype.np_type), copy=_NP2 and None, order="C"
325
+ )
316
326
  c_type = dtype.c_type if dtype._is_udt else f"{dtype.c_type}*"
317
327
  self._carg = ffi.cast(c_type, ffi.from_buffer(self.array))
318
328
  self.dtype = dtype
graphblas/core/vector.py CHANGED
@@ -772,7 +772,7 @@ class Vector(BaseType):
772
772
  """
773
773
  if isinstance(pairs, np.ndarray):
774
774
  raise TypeError("pairs as NumPy array is not supported; use `Vector.from_coo` instead")
775
- unzipped = list(zip(*pairs))
775
+ unzipped = list(zip(*pairs, strict=True))
776
776
  if len(unzipped) == 2:
777
777
  indices, values = unzipped
778
778
  elif not unzipped:
@@ -2105,7 +2105,7 @@ class Vector(BaseType):
2105
2105
 
2106
2106
  """
2107
2107
  indices, values = self.to_coo(sort=False)
2108
- return dict(zip(indices.tolist(), values.tolist()))
2108
+ return dict(zip(indices.tolist(), values.tolist(), strict=True))
2109
2109
 
2110
2110
 
2111
2111
  if backend == "suitesparse":
graphblas/exceptions.py CHANGED
@@ -1,4 +1,3 @@
1
- from . import backend as _backend
2
1
  from .core import ffi as _ffi
3
2
  from .core import lib as _lib
4
3
  from .core.utils import _Pointer
@@ -85,9 +84,14 @@ class NotImplementedException(GraphblasException):
85
84
  """
86
85
 
87
86
 
87
+ # SuiteSparse errors
88
+ class JitError(GraphblasException):
89
+ """SuiteSparse:GraphBLAS error using JIT."""
90
+
91
+
88
92
  # Our errors
89
93
  class UdfParseError(GraphblasException):
90
- """Unable to parse the user-defined function."""
94
+ """SuiteSparse:GraphBLAS unable to parse the user-defined function."""
91
95
 
92
96
 
93
97
  _error_code_lookup = {
@@ -112,8 +116,12 @@ _error_code_lookup = {
112
116
  }
113
117
  GrB_SUCCESS = _lib.GrB_SUCCESS
114
118
  GrB_NO_VALUE = _lib.GrB_NO_VALUE
115
- if _backend == "suitesparse":
119
+
120
+ # SuiteSparse-specific errors
121
+ if hasattr(_lib, "GxB_EXHAUSTED"):
116
122
  _error_code_lookup[_lib.GxB_EXHAUSTED] = StopIteration
123
+ if hasattr(_lib, "GxB_JIT_ERROR"): # Added in 9.4
124
+ _error_code_lookup[_lib.GxB_JIT_ERROR] = JitError
117
125
 
118
126
 
119
127
  def check_status(response_code, args):
graphblas/io/_networkx.py CHANGED
@@ -55,7 +55,9 @@ def to_networkx(m, edge_attribute="weight"):
55
55
  cols = cols.tolist()
56
56
  G = nx.DiGraph()
57
57
  if edge_attribute is None:
58
- G.add_edges_from(zip(rows, cols))
58
+ G.add_edges_from(zip(rows, cols, strict=True))
59
59
  else:
60
- G.add_weighted_edges_from(zip(rows, cols, vals.tolist()), weight=edge_attribute)
60
+ G.add_weighted_edges_from(
61
+ zip(rows, cols, vals.tolist(), strict=True), weight=edge_attribute
62
+ )
61
63
  return G
@@ -3,6 +3,7 @@ import contextlib
3
3
  import functools
4
4
  import itertools
5
5
  import platform
6
+ import sys
6
7
  from pathlib import Path
7
8
 
8
9
  import numpy as np
@@ -156,3 +157,10 @@ def compute(x):
156
157
  def shouldhave(module, opname):
157
158
  """Whether an "operator" module should have the given operator."""
158
159
  return supports_udfs or hasattr(module, opname)
160
+
161
+
162
+ def dprint(*args, **kwargs): # pragma: no cover (debug)
163
+ """Print to stderr for debugging purposes."""
164
+ kwargs["file"] = sys.stderr
165
+ kwargs["flush"] = True
166
+ print(*args, **kwargs)
@@ -224,6 +224,10 @@ def test_record_dtype_from_dict():
224
224
  def test_dtype_to_from_string():
225
225
  types = [dtypes.BOOL, dtypes.FP64]
226
226
  for c in string.ascii_letters:
227
+ if c == "T":
228
+ # See NEP 55 about StringDtype "T". Notably, this doesn't work:
229
+ # >>> np.dtype(np.dtype("T").str)
230
+ continue
227
231
  try:
228
232
  dtype = np.dtype(c)
229
233
  types.append(dtype)
@@ -146,7 +146,7 @@ def test_matrix_to_from_networkx():
146
146
  M = gb.io.from_networkx(G, nodelist=range(7))
147
147
  if suitesparse:
148
148
  assert M.ss.is_iso
149
- rows, cols = zip(*edges)
149
+ rows, cols = zip(*edges, strict=True)
150
150
  expected = gb.Matrix.from_coo(rows, cols, 1)
151
151
  assert expected.isequal(M)
152
152
  # Test empty
@@ -2603,12 +2603,14 @@ def test_iter(A):
2603
2603
  zip(
2604
2604
  [3, 0, 3, 5, 6, 0, 6, 1, 6, 2, 4, 1],
2605
2605
  [0, 1, 2, 2, 2, 3, 3, 4, 4, 5, 5, 6],
2606
+ strict=True,
2606
2607
  )
2607
2608
  )
2608
2609
  assert set(A.T) == set(
2609
2610
  zip(
2610
2611
  [0, 1, 2, 2, 2, 3, 3, 4, 4, 5, 5, 6],
2611
2612
  [3, 0, 3, 5, 6, 0, 6, 1, 6, 2, 4, 1],
2613
+ strict=True,
2612
2614
  )
2613
2615
  )
2614
2616
 
@@ -2731,8 +2733,8 @@ def test_ss_split(A):
2731
2733
  for results in [A.ss.split([4, 3]), A.ss.split([[4, None], 3], name="split")]:
2732
2734
  row_boundaries = [0, 4, 7]
2733
2735
  col_boundaries = [0, 3, 6, 7]
2734
- for i, (i1, i2) in enumerate(zip(row_boundaries[:-1], row_boundaries[1:])):
2735
- for j, (j1, j2) in enumerate(zip(col_boundaries[:-1], col_boundaries[1:])):
2736
+ for i, (i1, i2) in enumerate(itertools.pairwise(row_boundaries)):
2737
+ for j, (j1, j2) in enumerate(itertools.pairwise(col_boundaries)):
2736
2738
  expected = A[i1:i2, j1:j2].new()
2737
2739
  assert expected.isequal(results[i][j])
2738
2740
  with pytest.raises(DimensionMismatch):
@@ -3068,7 +3070,7 @@ def test_ss_flatten(A):
3068
3070
  [3, 2, 3, 1, 5, 3, 7, 8, 3, 1, 7, 4],
3069
3071
  ]
3070
3072
  # row-wise
3071
- indices = [row * A.ncols + col for row, col in zip(data[0], data[1])]
3073
+ indices = [row * A.ncols + col for row, col in zip(data[0], data[1], strict=True)]
3072
3074
  expected = Vector.from_coo(indices, data[2], size=A.nrows * A.ncols)
3073
3075
  for fmt in ["csr", "hypercsr", "bitmapr"]:
3074
3076
  B = Matrix.ss.import_any(**A.ss.export(format=fmt))
@@ -3087,7 +3089,7 @@ def test_ss_flatten(A):
3087
3089
  assert C.isequal(B)
3088
3090
 
3089
3091
  # column-wise
3090
- indices = [col * A.nrows + row for row, col in zip(data[0], data[1])]
3092
+ indices = [col * A.nrows + row for row, col in zip(data[0], data[1], strict=True)]
3091
3093
  expected = Vector.from_coo(indices, data[2], size=A.nrows * A.ncols)
3092
3094
  for fmt in ["csc", "hypercsc", "bitmapc"]:
3093
3095
  B = Matrix.ss.import_any(**A.ss.export(format=fmt))
@@ -3626,9 +3628,9 @@ def test_ss_iteration(A):
3626
3628
  assert not list(B.ss.itervalues())
3627
3629
  assert not list(B.ss.iteritems())
3628
3630
  rows, columns, values = A.to_coo()
3629
- assert sorted(zip(rows, columns)) == sorted(A.ss.iterkeys())
3631
+ assert sorted(zip(rows, columns, strict=True)) == sorted(A.ss.iterkeys())
3630
3632
  assert sorted(values) == sorted(A.ss.itervalues())
3631
- assert sorted(zip(rows, columns, values)) == sorted(A.ss.iteritems())
3633
+ assert sorted(zip(rows, columns, values, strict=True)) == sorted(A.ss.iteritems())
3632
3634
  N = rows.size
3633
3635
 
3634
3636
  A = Matrix.ss.import_bitmapr(**A.ss.export("bitmapr"))
@@ -4072,10 +4074,11 @@ def test_ss_pack_hyperhash(A):
4072
4074
  Y = C.ss.unpack_hyperhash()
4073
4075
  Y = C.ss.unpack_hyperhash(compute=True)
4074
4076
  assert C.ss.unpack_hyperhash() is None
4075
- assert Y.nrows == C.nrows
4076
- C.ss.pack_hyperhash(Y)
4077
- assert Y.gb_obj[0] == gb.core.NULL
4078
- assert C.ss.unpack_hyperhash() is not None
4077
+ if Y is not None: # hyperhash may or may not be computed
4078
+ assert Y.nrows == C.nrows
4079
+ C.ss.pack_hyperhash(Y)
4080
+ assert Y.gb_obj[0] == gb.core.NULL
4081
+ assert C.ss.unpack_hyperhash() is not None # May or may not be computed
4079
4082
 
4080
4083
 
4081
4084
  def test_to_dicts_from_dicts(A):
@@ -5,6 +5,7 @@ import sys
5
5
 
6
6
  import numpy as np
7
7
  import pytest
8
+ from packaging.version import parse
8
9
 
9
10
  import graphblas as gb
10
11
  import graphblas.binary.numpy as npbinary
@@ -112,6 +113,15 @@ def test_npunary():
112
113
  match(accum=gb.binary.lor) << gb_result.apply(npunary.isnan)
113
114
  compare = match.reduce(gb.monoid.land).new()
114
115
  if not compare: # pragma: no cover (debug)
116
+ import numba
117
+
118
+ if (
119
+ unary_name in {"sign"}
120
+ and np.__version__.startswith("2.")
121
+ and parse(numba.__version__) < parse("0.61.0")
122
+ ):
123
+ # numba <0.61.0 does not match numpy 2.0
124
+ continue
115
125
  print(unary_name, gb_input.dtype)
116
126
  print(compute(gb_result))
117
127
  print(np_result)
@@ -50,7 +50,7 @@ def test_dup(s):
50
50
  s_empty = Scalar(dtypes.FP64)
51
51
  s_unempty = Scalar.from_value(0.0)
52
52
  if s_empty.is_cscalar:
53
- # NumPy wraps around
53
+ # NumPy <2 wraps around; >=2 raises OverflowError
54
54
  uint_data = [
55
55
  ("UINT8", 2**8 - 2),
56
56
  ("UINT16", 2**16 - 2),
@@ -73,6 +73,10 @@ def test_dup(s):
73
73
  ("FP32", -2.5),
74
74
  *uint_data,
75
75
  ]:
76
+ if dtype.startswith("UINT") and s_empty.is_cscalar and not np.__version__.startswith("1."):
77
+ with pytest.raises(OverflowError, match="out of bounds for uint"):
78
+ s4.dup(dtype=dtype, name="s5")
79
+ continue
76
80
  s5 = s4.dup(dtype=dtype, name="s5")
77
81
  assert s5.dtype == dtype
78
82
  assert s5.value == val
@@ -1,6 +1,8 @@
1
1
  import os
2
2
  import pathlib
3
+ import platform
3
4
  import sys
5
+ import sysconfig
4
6
 
5
7
  import numpy as np
6
8
  import pytest
@@ -26,11 +28,48 @@ if backend != "suitesparse":
26
28
 
27
29
  @pytest.fixture(scope="module", autouse=True)
28
30
  def _setup_jit():
31
+ """Set up the SuiteSparse:GraphBLAS JIT."""
32
+ if _IS_SSGB7:
33
+ # SuiteSparse JIT was added in SSGB 8
34
+ yield
35
+ return
36
+
37
+ if not os.environ.get("GITHUB_ACTIONS"):
38
+ # Try to run the tests with defaults from sysconfig if not running in CI
39
+ prev = gb.ss.config["jit_c_control"]
40
+ cc = sysconfig.get_config_var("CC")
41
+ cflags = sysconfig.get_config_var("CFLAGS")
42
+ include = sysconfig.get_path("include")
43
+ libs = sysconfig.get_config_var("LIBS")
44
+ if not (cc is None or cflags is None or include is None or libs is None):
45
+ gb.ss.config["jit_c_control"] = "on"
46
+ gb.ss.config["jit_c_compiler_name"] = cc
47
+ gb.ss.config["jit_c_compiler_flags"] = f"{cflags} -I{include}"
48
+ gb.ss.config["jit_c_libraries"] = libs
49
+ else:
50
+ # Should we skip or try to run if sysconfig vars aren't set?
51
+ gb.ss.config["jit_c_control"] = "on" # "off"
52
+ try:
53
+ yield
54
+ finally:
55
+ gb.ss.config["jit_c_control"] = prev
56
+ return
57
+
58
+ if (
59
+ sys.platform == "darwin"
60
+ or sys.platform == "linux"
61
+ and "conda" not in gb.ss.config["jit_c_compiler_name"]
62
+ ):
63
+ # XXX TODO: tests for SuiteSparse JIT are not passing on linux when using wheels or on osx
64
+ # This should be understood and fixed!
65
+ gb.ss.config["jit_c_control"] = "off"
66
+ yield
67
+ return
68
+
29
69
  # Configuration values below were obtained from the output of the JIT config
30
70
  # in CI, but with paths changed to use `{conda_prefix}` where appropriate.
31
- if "CONDA_PREFIX" not in os.environ or _IS_SSGB7:
32
- return
33
71
  conda_prefix = os.environ["CONDA_PREFIX"]
72
+ prev = gb.ss.config["jit_c_control"]
34
73
  gb.ss.config["jit_c_control"] = "on"
35
74
  if sys.platform == "linux":
36
75
  gb.ss.config["jit_c_compiler_name"] = f"{conda_prefix}/bin/x86_64-conda-linux-gnu-cc"
@@ -59,7 +98,7 @@ def _setup_jit():
59
98
  gb.ss.config["jit_c_compiler_flags"] = (
60
99
  "-march=core2 -mtune=haswell -mssse3 -ftree-vectorize -fPIC -fPIE "
61
100
  f"-fstack-protector-strong -O2 -pipe -isystem {conda_prefix}/include -DGBNCPUFEAT "
62
- "-Wno-pointer-sign -O3 -DNDEBUG -fopenmp=libomp -fPIC -arch x86_64"
101
+ f"-Wno-pointer-sign -O3 -DNDEBUG -fopenmp=libomp -fPIC -arch {platform.machine()}"
63
102
  )
64
103
  gb.ss.config["jit_c_linker_flags"] = (
65
104
  "-Wl,-pie -Wl,-headerpad_max_install_names -Wl,-dead_strip_dylibs "
@@ -72,6 +111,7 @@ def _setup_jit():
72
111
  # This probably means we're testing a `python-suitesparse-graphblas` wheel
73
112
  # in a conda environment. This is not yet working.
74
113
  gb.ss.config["jit_c_control"] = "off"
114
+ yield
75
115
  return
76
116
 
77
117
  gb.ss.config["jit_c_compiler_name"] = f"{conda_prefix}/bin/cc"
@@ -86,6 +126,12 @@ def _setup_jit():
86
126
  if not pathlib.Path(gb.ss.config["jit_c_compiler_name"]).exists():
87
127
  # Can't use the JIT if we don't have a compiler!
88
128
  gb.ss.config["jit_c_control"] = "off"
129
+ yield
130
+ return
131
+ try:
132
+ yield
133
+ finally:
134
+ gb.ss.config["jit_c_control"] = prev
89
135
 
90
136
 
91
137
  @pytest.fixture
@@ -29,6 +29,8 @@ from graphblas import Matrix, Scalar, Vector # isort:skip (for dask-graphblas)
29
29
 
30
30
 
31
31
  suitesparse = backend == "suitesparse"
32
+ if suitesparse:
33
+ ss_version_major = gb.core.ss.version_major
32
34
 
33
35
 
34
36
  @pytest.fixture
@@ -2205,7 +2207,10 @@ def test_udt():
2205
2207
 
2206
2208
  long_dtype = np.dtype([("x", np.bool_), ("y" * 1000, np.float64)], align=True)
2207
2209
  if suitesparse:
2208
- with pytest.warns(UserWarning, match="too large"):
2210
+ if ss_version_major < 9:
2211
+ with pytest.warns(UserWarning, match="too large"):
2212
+ long_udt = dtypes.register_anonymous(long_dtype)
2213
+ else:
2209
2214
  long_udt = dtypes.register_anonymous(long_dtype)
2210
2215
  else:
2211
2216
  # UDTs don't currently have a name in vanilla GraphBLAS
@@ -2216,13 +2221,19 @@ def test_udt():
2216
2221
  if suitesparse:
2217
2222
  vv = Vector.ss.deserialize(v.ss.serialize(), dtype=long_udt)
2218
2223
  assert v.isequal(vv, check_dtype=True)
2219
- with pytest.raises(SyntaxError):
2220
- # The size of the UDT name is limited
2224
+ if ss_version_major < 9:
2225
+ with pytest.raises(SyntaxError):
2226
+ # The size of the UDT name is limited
2227
+ Vector.ss.deserialize(v.ss.serialize())
2228
+ else:
2221
2229
  Vector.ss.deserialize(v.ss.serialize())
2222
2230
  # May be able to look up non-anonymous dtypes by name if their names are too long
2223
2231
  named_long_dtype = np.dtype([("x", np.bool_), ("y" * 1000, np.float64)], align=False)
2224
2232
  if suitesparse:
2225
- with pytest.warns(UserWarning, match="too large"):
2233
+ if ss_version_major < 9:
2234
+ with pytest.warns(UserWarning, match="too large"):
2235
+ named_long_udt = dtypes.register_new("LongUDT", named_long_dtype)
2236
+ else:
2226
2237
  named_long_udt = dtypes.register_new("LongUDT", named_long_dtype)
2227
2238
  else:
2228
2239
  named_long_udt = dtypes.register_new("LongUDT", named_long_dtype)
@@ -2270,7 +2281,7 @@ def test_ss_iteration(v):
2270
2281
  # This is what I would expect
2271
2282
  assert sorted(indices) == sorted(v.ss.iterkeys())
2272
2283
  assert sorted(values) == sorted(v.ss.itervalues())
2273
- assert sorted(zip(indices, values)) == sorted(v.ss.iteritems())
2284
+ assert sorted(zip(indices, values, strict=True)) == sorted(v.ss.iteritems())
2274
2285
 
2275
2286
  N = indices.size
2276
2287
  v = Vector.ss.import_bitmap(**v.ss.export("bitmap"))
@@ -1,11 +1,11 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: python-graphblas
3
- Version: 2024.2.0
3
+ Version: 2025.2.0a1
4
4
  Summary: Python library for GraphBLAS: high-performance sparse linear algebra for scalable graph analytics
5
5
  Author: Jim Kitchen, Python-graphblas contributors
6
6
  Author-email: Erik Welch <erik.n.welch@gmail.com>
7
7
  Maintainer-email: Erik Welch <erik.n.welch@gmail.com>, Jim Kitchen <jim22k@gmail.com>, Sultan Orazbayev <contact@econpoint.com>
8
- License: Apache License
8
+ License: Apache License
9
9
  Version 2.0, January 2004
10
10
  https://www.apache.org/licenses/
11
11
 
@@ -219,10 +219,10 @@ Classifier: Operating System :: POSIX :: Linux
219
219
  Classifier: Operating System :: Microsoft :: Windows
220
220
  Classifier: Programming Language :: Python
221
221
  Classifier: Programming Language :: Python :: 3
222
- Classifier: Programming Language :: Python :: 3.9
223
222
  Classifier: Programming Language :: Python :: 3.10
224
223
  Classifier: Programming Language :: Python :: 3.11
225
224
  Classifier: Programming Language :: Python :: 3.12
225
+ Classifier: Programming Language :: Python :: 3.13
226
226
  Classifier: Programming Language :: Python :: 3 :: Only
227
227
  Classifier: Intended Audience :: Developers
228
228
  Classifier: Intended Audience :: Other Audience
@@ -231,52 +231,52 @@ Classifier: Topic :: Scientific/Engineering
231
231
  Classifier: Topic :: Scientific/Engineering :: Information Analysis
232
232
  Classifier: Topic :: Scientific/Engineering :: Mathematics
233
233
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
234
- Requires-Python: >=3.9
234
+ Requires-Python: >=3.10
235
235
  Description-Content-Type: text/markdown
236
236
  License-File: LICENSE
237
- Requires-Dist: numpy >=1.22
238
- Requires-Dist: donfig >=0.6
239
- Requires-Dist: pyyaml >=5.4
240
- Requires-Dist: suitesparse-graphblas <9,>=7.4.0.0
241
- Requires-Dist: numba >=0.55 ; python_version < "3.13"
242
- Provides-Extra: all
243
- Requires-Dist: python-graphblas[default,io,test,viz] ; extra == 'all'
244
- Requires-Dist: python-graphblas[datashade] ; (python_version < "3.13") and extra == 'all'
245
- Provides-Extra: datashade
246
- Requires-Dist: python-graphblas[numba,pandas,scipy] ; extra == 'datashade'
247
- Requires-Dist: datashader >=0.12 ; extra == 'datashade'
248
- Requires-Dist: hvplot >=0.7 ; extra == 'datashade'
249
- Provides-Extra: default
250
- Requires-Dist: python-graphblas[pandas,scipy,suitesparse] ; extra == 'default'
251
- Requires-Dist: python-graphblas[numba] ; (python_version < "3.13") and extra == 'default'
252
- Provides-Extra: io
253
- Requires-Dist: python-graphblas[networkx,scipy] ; extra == 'io'
254
- Requires-Dist: awkward >=1.9 ; extra == 'io'
255
- Requires-Dist: fast-matrix-market >=1.4.5 ; extra == 'io'
256
- Requires-Dist: python-graphblas[numba] ; (python_version < "3.13") and extra == 'io'
257
- Requires-Dist: sparse >=0.13 ; (python_version < "3.13") and extra == 'io'
237
+ Requires-Dist: numpy>=1.23
238
+ Requires-Dist: donfig>=0.6
239
+ Requires-Dist: pyyaml>=5.4
240
+ Requires-Dist: suitesparse-graphblas<10,>=7.4.0.0
241
+ Requires-Dist: numba>=0.55; python_version < "3.14"
242
+ Provides-Extra: suitesparse
243
+ Requires-Dist: suitesparse-graphblas<10,>=7.4.0.0; extra == "suitesparse"
258
244
  Provides-Extra: networkx
259
- Requires-Dist: networkx >=2.8 ; extra == 'networkx'
245
+ Requires-Dist: networkx>=2.8; extra == "networkx"
260
246
  Provides-Extra: numba
261
- Requires-Dist: numba >=0.55 ; extra == 'numba'
247
+ Requires-Dist: numba>=0.55; extra == "numba"
262
248
  Provides-Extra: pandas
263
- Requires-Dist: pandas >=1.2 ; extra == 'pandas'
264
- Provides-Extra: repr
265
- Requires-Dist: python-graphblas[pandas] ; extra == 'repr'
249
+ Requires-Dist: pandas>=1.5; extra == "pandas"
266
250
  Provides-Extra: scipy
267
- Requires-Dist: scipy >=1.9 ; extra == 'scipy'
268
- Provides-Extra: suitesparse
269
- Requires-Dist: suitesparse-graphblas <9,>=7.4.0.0 ; extra == 'suitesparse'
251
+ Requires-Dist: scipy>=1.9; extra == "scipy"
270
252
  Provides-Extra: suitesparse-udf
271
- Requires-Dist: python-graphblas[numba,suitesparse] ; extra == 'suitesparse-udf'
272
- Provides-Extra: test
273
- Requires-Dist: python-graphblas[pandas,scipy,suitesparse] ; extra == 'test'
274
- Requires-Dist: packaging >=21 ; extra == 'test'
275
- Requires-Dist: pytest >=6.2 ; extra == 'test'
276
- Requires-Dist: tomli >=1 ; extra == 'test'
253
+ Requires-Dist: python-graphblas[numba,suitesparse]; extra == "suitesparse-udf"
254
+ Provides-Extra: repr
255
+ Requires-Dist: python-graphblas[pandas]; extra == "repr"
256
+ Provides-Extra: io
257
+ Requires-Dist: python-graphblas[networkx,scipy]; extra == "io"
258
+ Requires-Dist: python-graphblas[numba]; python_version < "3.14" and extra == "io"
259
+ Requires-Dist: awkward>=2.0; extra == "io"
260
+ Requires-Dist: sparse>=0.14; python_version < "3.13" and extra == "io"
261
+ Requires-Dist: fast-matrix-market>=1.4.5; python_version < "3.13" and extra == "io"
277
262
  Provides-Extra: viz
278
- Requires-Dist: python-graphblas[networkx,scipy] ; extra == 'viz'
279
- Requires-Dist: matplotlib >=3.5 ; extra == 'viz'
263
+ Requires-Dist: python-graphblas[networkx,scipy]; extra == "viz"
264
+ Requires-Dist: matplotlib>=3.6; extra == "viz"
265
+ Provides-Extra: datashade
266
+ Requires-Dist: python-graphblas[numba,pandas,scipy]; extra == "datashade"
267
+ Requires-Dist: datashader>=0.14; extra == "datashade"
268
+ Requires-Dist: hvplot>=0.8; extra == "datashade"
269
+ Provides-Extra: test
270
+ Requires-Dist: python-graphblas[pandas,scipy,suitesparse]; extra == "test"
271
+ Requires-Dist: packaging>=21; extra == "test"
272
+ Requires-Dist: pytest>=6.2; extra == "test"
273
+ Requires-Dist: tomli>=1; extra == "test"
274
+ Provides-Extra: default
275
+ Requires-Dist: python-graphblas[pandas,scipy,suitesparse]; extra == "default"
276
+ Requires-Dist: python-graphblas[numba]; python_version < "3.14" and extra == "default"
277
+ Provides-Extra: all
278
+ Requires-Dist: python-graphblas[default,io,test,viz]; extra == "all"
279
+ Requires-Dist: python-graphblas[datashade]; python_version < "3.14" and extra == "all"
280
280
 
281
281
  ![Python-graphblas](https://raw.githubusercontent.com/python-graphblas/python-graphblas/main/docs/_static/img/logo-horizontal-medium-big.svg)
282
282
 
@@ -315,14 +315,19 @@ For algorithms, see
315
315
  </p>
316
316
 
317
317
  ## Install
318
+
318
319
  Install the latest version of Python-graphblas via conda:
320
+
319
321
  ```
320
322
  $ conda install -c conda-forge python-graphblas
321
323
  ```
324
+
322
325
  or pip:
326
+
323
327
  ```
324
- $ pip install python-graphblas[default]
328
+ $ pip install 'python-graphblas[default]'
325
329
  ```
330
+
326
331
  This will also install the [SuiteSparse:GraphBLAS](https://github.com/DrTimothyAldenDavis/GraphBLAS) compiled C library.
327
332
  We currently support the [GraphBLAS C API 2.0 specification](https://graphblas.org/docs/GraphBLAS_API_C_v2.0.0.pdf).
328
333
 
@@ -337,6 +342,7 @@ The following are not required by python-graphblas, but may be needed for certai
337
342
  - `fast-matrix-market` - for faster read/write of Matrix Market files with `gb.io.mmread` and `gb.io.mmwrite`.
338
343
 
339
344
  ## Description
345
+
340
346
  Currently works with [SuiteSparse:GraphBLAS](https://github.com/DrTimothyAldenDavis/GraphBLAS), but the goal is to make it work with all implementations of the GraphBLAS spec.
341
347
 
342
348
  The approach taken with this library is to follow the C-API 2.0 specification as closely as possible while making improvements
@@ -350,10 +356,12 @@ with how Python handles assignment, so instead we (ab)use the left-shift `<<` no
350
356
  assignment. This opens up all kinds of nice possibilities.
351
357
 
352
358
  This is an example of how the mapping works:
359
+
353
360
  ```C
354
361
  // C call
355
362
  GrB_Matrix_mxm(M, mask, GrB_PLUS_INT64, GrB_MIN_PLUS_INT64, A, B, NULL)
356
363
  ```
364
+
357
365
  ```python
358
366
  # Python call
359
367
  M(mask.V, accum=binary.plus) << A.mxm(B, semiring.min_plus)
@@ -371,10 +379,12 @@ is a much better approach, even if it doesn't feel very Pythonic.
371
379
  Descriptor flags are set on the appropriate elements to keep logic close to what it affects. Here is the same call
372
380
  with descriptor bits set. `ttcsr` indicates transpose the first and second matrices, complement the structure of the mask,
373
381
  and do a replacement on the output.
382
+
374
383
  ```C
375
384
  // C call
376
385
  GrB_Matrix_mxm(M, mask, GrB_PLUS_INT64, GrB_MIN_PLUS_INT64, A, B, desc.ttcsr)
377
386
  ```
387
+
378
388
  ```python
379
389
  # Python call
380
390
  M(~mask.S, accum=binary.plus, replace=True) << A.T.mxm(B.T, semiring.min_plus)
@@ -384,16 +394,20 @@ The objects receiving the flag operations (A.T, ~mask, etc) are also delayed obj
384
394
  do no computation, allowing the correct descriptor bits to be set in a single GraphBLAS call.
385
395
 
386
396
  **If no mask or accumulator is used, the call looks like this**:
397
+
387
398
  ```python
388
399
  M << A.mxm(B, semiring.min_plus)
389
400
  ```
401
+
390
402
  The use of `<<` to indicate updating is actually just syntactic sugar for a real `.update()` method. The above
391
403
  expression could be written as:
404
+
392
405
  ```python
393
406
  M.update(A.mxm(B, semiring.min_plus))
394
407
  ```
395
408
 
396
409
  ## Operations
410
+
397
411
  ```python
398
412
  M(mask, accum) << A.mxm(B, semiring) # mxm
399
413
  w(mask, accum) << A.mxv(v, semiring) # mxv
@@ -403,14 +417,18 @@ M(mask, accum) << A.ewise_mult(B, binaryop) # eWiseMult
403
417
  M(mask, accum) << A.kronecker(B, binaryop) # kronecker
404
418
  M(mask, accum) << A.T # transpose
405
419
  ```
420
+
406
421
  ## Extract
422
+
407
423
  ```python
408
424
  M(mask, accum) << A[rows, cols] # rows and cols are a list or a slice
409
425
  w(mask, accum) << A[rows, col_index] # extract column
410
426
  w(mask, accum) << A[row_index, cols] # extract row
411
427
  s = A[row_index, col_index].value # extract single element
412
428
  ```
429
+
413
430
  ## Assign
431
+
414
432
  ```python
415
433
  M(mask, accum)[rows, cols] << A # rows and cols are a list or a slice
416
434
  M(mask, accum)[rows, col_index] << v # assign column
@@ -420,31 +438,42 @@ M[row_index, col_index] << s # assign scalar to single element
420
438
  # (mask and accum not allowed)
421
439
  del M[row_index, col_index] # remove single element
422
440
  ```
441
+
423
442
  ## Apply
443
+
424
444
  ```python
425
445
  M(mask, accum) << A.apply(unaryop)
426
446
  M(mask, accum) << A.apply(binaryop, left=s) # bind-first
427
447
  M(mask, accum) << A.apply(binaryop, right=s) # bind-second
428
448
  ```
449
+
429
450
  ## Reduce
451
+
430
452
  ```python
431
453
  v(mask, accum) << A.reduce_rowwise(op) # reduce row-wise
432
454
  v(mask, accum) << A.reduce_columnwise(op) # reduce column-wise
433
455
  s(accum) << A.reduce_scalar(op)
434
456
  s(accum) << v.reduce(op)
435
457
  ```
458
+
436
459
  ## Creating new Vectors / Matrices
460
+
437
461
  ```python
438
462
  A = Matrix.new(dtype, num_rows, num_cols) # new_type
439
463
  B = A.dup() # dup
440
464
  A = Matrix.from_coo([row_indices], [col_indices], [values]) # build
441
465
  ```
466
+
442
467
  ## New from delayed
468
+
443
469
  Delayed objects can be used to create a new object using `.new()` method
470
+
444
471
  ```python
445
472
  C = A.mxm(B, semiring).new()
446
473
  ```
474
+
447
475
  ## Properties
476
+
448
477
  ```python
449
478
  size = v.size # size
450
479
  nrows = M.nrows # nrows
@@ -452,10 +481,13 @@ ncols = M.ncols # ncols
452
481
  nvals = M.nvals # nvals
453
482
  rindices, cindices, vals = M.to_coo() # extractTuples
454
483
  ```
484
+
455
485
  ## Initialization
486
+
456
487
  There is a mechanism to initialize `graphblas` with a context prior to use. This allows for setting the backend to
457
488
  use as well as the blocking/non-blocking mode. If the context is not initialized, a default initialization will
458
489
  be performed automatically.
490
+
459
491
  ```python
460
492
  import graphblas as gb
461
493
 
@@ -466,10 +498,13 @@ gb.init("suitesparse", blocking=True)
466
498
  from graphblas import binary, semiring
467
499
  from graphblas import Matrix, Vector, Scalar
468
500
  ```
501
+
469
502
  ## Performant User Defined Functions
503
+
470
504
  Python-graphblas requires `numba` which enables compiling user-defined Python functions to native C for use in GraphBLAS.
471
505
 
472
506
  Example customized UnaryOp:
507
+
473
508
  ```python
474
509
  from graphblas import unary
475
510
 
@@ -484,9 +519,11 @@ v = Vector.from_coo([0, 1, 3], [1, 2, 3])
484
519
  w = v.apply(unary.force_odd).new()
485
520
  w # indexes=[0, 1, 3], values=[1, 3, 3]
486
521
  ```
522
+
487
523
  Similar methods exist for BinaryOp, Monoid, and Semiring.
488
524
 
489
525
  ## Relation to other network analysis libraries
526
+
490
527
  Python-graphblas aims to provide an efficient and consistent expression
491
528
  of graph operations using linear algebra. This allows the development of
492
529
  high-performance implementations of existing and new graph algorithms
@@ -503,7 +540,9 @@ other libraries, `graphblas.io` contains multiple connectors, see the
503
540
  following section.
504
541
 
505
542
  ## Import/Export connectors to the Python ecosystem
543
+
506
544
  `graphblas.io` contains functions for converting to and from:
545
+
507
546
  ```python
508
547
  import graphblas as gb
509
548
 
@@ -1,5 +1,5 @@
1
1
  graphblas/__init__.py,sha256=hXLvBYOSgP210Z04K9hJ_k80zLJ1X7_u6HUpDJfZVRw,7131
2
- graphblas/exceptions.py,sha256=qF80UWYZ7GRvrSSFt3pKFglvISci3rn1QMg3zPefjfo,4272
2
+ graphblas/exceptions.py,sha256=y_e_2erCA4o1DSBfKgyiYrh_1CagKmDLfXCP8wj-pw4,4509
3
3
  graphblas/graphblas.yaml,sha256=AuJWCjDwiUYuUYSwn3W5vbeqAenobqrM5HC081V28Ic,33
4
4
  graphblas/viz.py,sha256=5JVpiFsYNG9AetXKXX9Jh7T4OmbN-_JJ-GwVi9983As,7558
5
5
  graphblas/agg/__init__.py,sha256=fsFwEV5IOiP_a8w12X-NA-8Ltlhg81UFYOkc2UG_7Xg,3282
@@ -9,23 +9,23 @@ graphblas/binary/numpy.py,sha256=mxqRnWmzX9kofjvY-XhYvxkvMyojHPBOpi90MGPW7hE,537
9
9
  graphblas/binary/ss.py,sha256=7m-x5oihpD4CiNEf9aD3AQNGNu9T1XSL6ubU1iaVq7k,113
10
10
  graphblas/core/__init__.py,sha256=hgtNRdWJ5nC3zJo1vYE1L3oxXXBiTSWTtHTDPuQAyKQ,345
11
11
  graphblas/core/automethods.py,sha256=wJymJjfHQNWCZ-TePxXNJg71llhbMEhWOND2iASLDvI,12680
12
- graphblas/core/base.py,sha256=CWUHnMrcho6gEZThe3s_Zj9pOox7Vst6RabeKR7o52M,24496
12
+ graphblas/core/base.py,sha256=FuLn2U6AadiMo5ZnAbEpxef67qFaF6zVVc3wxkcILug,24510
13
13
  graphblas/core/descriptor.py,sha256=dAdGs780GRK-a3lw_tI9u1sMhAP38LYoIxS_kZlezMQ,5979
14
- graphblas/core/dtypes.py,sha256=xgwlC035fyFNnbB7oCcAy_hiwooK_xbtKbgL7DPmWq8,13012
14
+ graphblas/core/dtypes.py,sha256=Tob23pR5__jsJ9xqapd31KW0oB7AXp79u7mm6W83JUM,13478
15
15
  graphblas/core/expr.py,sha256=xf7XcdFWI_UEDGfLZZC31hLesb_dRwBIwJuASIrWVuQ,21154
16
- graphblas/core/formatting.py,sha256=lmD2hQULFhbh_8JVDi_tQTy9mWjR7Ah83dcd3AWfTxg,30152
17
- graphblas/core/infix.py,sha256=9RaSxkHyBM-0e71kCmCwQ99CSEwChYaIgAFCGas8luM,23038
16
+ graphblas/core/formatting.py,sha256=o0HXpUfbivD04MGcNkn6wBH4p0DAUR7vd-4L0PxJ6JQ,30165
17
+ graphblas/core/infix.py,sha256=g48IIhyaSHhk3pWyrdLJNNugjmJBUz4TO4cUSGmSrt4,23077
18
18
  graphblas/core/infixmethods.py,sha256=Ig36RjmHXW63uBWAqTa5QgpJcn4lV0u2V4S0zUdeXbo,11860
19
19
  graphblas/core/mask.py,sha256=aPC17q9fLH_VP2kfNYayetBU6tN-4Nx0HUKIT2fbBok,15877
20
- graphblas/core/matrix.py,sha256=-oUdeXd4MrtS9Hd5ICAqoUZoeYQ1u8HM6QHht3x7aVk,153043
20
+ graphblas/core/matrix.py,sha256=GrW7fJZmTENX2UU3V027PQ-Ky9tV8n8qKrFB_1_PROc,153267
21
21
  graphblas/core/recorder.py,sha256=KJjVoS83LYDrRcqzc4zDL52m4rnuf-kTO5y2pQ_8qo8,5935
22
- graphblas/core/scalar.py,sha256=7er-mWsDwPjJncu67FFYEWScQ4Fp6cAzXeeBlZNOGC0,43034
22
+ graphblas/core/scalar.py,sha256=QuBHl-0GdSPnUe3cBp84nw9gAbhn46c1vBys9tsnkNk,43048
23
23
  graphblas/core/slice.py,sha256=hmuEEdwdiXPYkyx67Ywqs1EBdYFyP_Jg7KFpZYc7vTA,1826
24
- graphblas/core/utils.py,sha256=OrVrI1-qvMQHZC1p_z2kLP9z0jFlvSBwTA3PHYfvR0A,13188
25
- graphblas/core/vector.py,sha256=1BVebkll8-mdZVkbj_DFZ0-aE8HeGVltwFOYXp4XVAc,86023
24
+ graphblas/core/utils.py,sha256=uHBC1AlYf2Pp6OmXYVSgo6rdwp6d7d0uiPvdWopwTd0,13848
25
+ graphblas/core/vector.py,sha256=ELHHA3cpwrHvPTwLgSSZlMwWCK3-djblL_3SUBjxRvA,86049
26
26
  graphblas/core/operator/__init__.py,sha256=uUTI1uat6ndA-qYpvbliyAt6arDTxXIVstDEdpmPyYo,743
27
27
  graphblas/core/operator/agg.py,sha256=aoUnd9oHK1QXi0dQ4lZZBpzijD2IAeTKa6h0EB6nEWU,24816
28
- graphblas/core/operator/base.py,sha256=eiM4jqrfQZqHQErZDsIIgkc9eV-HooW1R1-zgllZeeM,18160
28
+ graphblas/core/operator/base.py,sha256=69SvKsP8L3TQZpCs67GB2xa_0lJww038hehBq2jt0qQ,18133
29
29
  graphblas/core/operator/binary.py,sha256=y0frqF0vmCdH5BP_JtVm6rKfVkCrwmlZn_dJz7antVI,36343
30
30
  graphblas/core/operator/indexunary.py,sha256=ZDOXra06AAyPRY_9XDHgxGfjldsXG6lYYHcX0Ccv_B8,19396
31
31
  graphblas/core/operator/monoid.py,sha256=1oQ2qQMENMoMD9WbyC3Q8b9lpRGDz_tgg-MYtDE7N4A,15893
@@ -33,18 +33,18 @@ graphblas/core/operator/select.py,sha256=CnOHQIBuI5ANgDqsnAFHiWnJVc8pKtesZNY2PEq
33
33
  graphblas/core/operator/semiring.py,sha256=aF4hv8G46gXgmgM_4w-6A3m935riTYvLP8vygnG2dsE,21708
34
34
  graphblas/core/operator/unary.py,sha256=Nrn-rSx4uZZ4OAJvrzsDkKTXRvFTNcGh_CyjLlW6iao,18340
35
35
  graphblas/core/operator/utils.py,sha256=WxxyqtlBg783-vLOWic4JPv0VpdzQ5Et42F_MaYjd6Q,15383
36
- graphblas/core/ss/__init__.py,sha256=TMqas3DedLi89UC_AENkdefnuUvi0B_395teuQl3zco,93
36
+ graphblas/core/ss/__init__.py,sha256=546hNJvo_OeRrphaLMyJ18EmJrT_qGDiAF7AVnny65s,160
37
37
  graphblas/core/ss/binary.py,sha256=TDmFG5RIOO7sbSx2MpLnOuMlyBzel7Qcrd75uNUz-AU,4735
38
- graphblas/core/ss/config.py,sha256=QlDzYho7WkRt7yt0hFIyfj0zmTUj7vpOl7uO_rvoiHo,8278
38
+ graphblas/core/ss/config.py,sha256=4DIkEl7_ETISlB6PY_GawjK-POwyjXcfUzi2IeoKea8,8276
39
39
  graphblas/core/ss/context.py,sha256=t2v9W1Lnwi4JJJp90tQxJjmdY4HDXSDhXU6DGE4aHHI,4620
40
40
  graphblas/core/ss/descriptor.py,sha256=0m4eyMbg355ev2mFPy53N9gVebBjYbRVXTbyxroxfoM,8304
41
41
  graphblas/core/ss/dtypes.py,sha256=QGRscMdb3WVygBXjLocW3k9XNN3WYr2qDKzVSnj3OvI,3583
42
42
  graphblas/core/ss/indexunary.py,sha256=G-GyCHZuTLInxcMCKiuOLY-pX4kfFIiwvo3dW8-j3jM,5941
43
- graphblas/core/ss/matrix.py,sha256=n8qsTBoPMBFy_4BNQgcpFj3V0YsledrQ4QXWe1MpAMI,150051
43
+ graphblas/core/ss/matrix.py,sha256=k4ZomCKrSotNYwjkwyUZu30quC33WuXfeKFwcPas9NY,152100
44
44
  graphblas/core/ss/prefix_scan.py,sha256=JtxeKCQ4NNvhSgqmyaDVyMGdLEOgr3ARjNSBRfugsw4,5999
45
45
  graphblas/core/ss/select.py,sha256=QqgOQx3NemYYKgz8UWaaxix7syKLpHWxANqVs8z_cU4,3437
46
46
  graphblas/core/ss/unary.py,sha256=zuTYY3iHNhmA1yEPHO-Vt2yA6bM-KZppJj8nC_AVBR4,4036
47
- graphblas/core/ss/vector.py,sha256=Yd1N9M344wFADSuXyzx9T5CK0AUNnJykOWb53J-IJvI,59186
47
+ graphblas/core/ss/vector.py,sha256=sEO3n7CRoeVprpXHR74kELv20eaZgsCNW8bPkMJ2eL4,61073
48
48
  graphblas/dtypes/__init__.py,sha256=veA5FgUQaBAoqyb3NPWJae3mP1dai2IeoLGI8nsyE10,943
49
49
  graphblas/dtypes/ss.py,sha256=_Xexm1SeoI3zr5AxSWUtWWTZC3Fg2AkZx82QK-4ZHDs,56
50
50
  graphblas/indexunary/__init__.py,sha256=PNubKaG9yiijl4sJJRcc7p4EFonUuk8y5Mqv10z3TyM,878
@@ -52,7 +52,7 @@ graphblas/indexunary/ss.py,sha256=FqVm91nMeOeU6sU3GaiGBTDTmjtABDjuO6Nl818_EZ8,11
52
52
  graphblas/io/__init__.py,sha256=YZHXdMGSc2cEmISECYrAaiNRKxupFaiCFhtgb2qvp-Y,253
53
53
  graphblas/io/_awkward.py,sha256=WCq3lwcH1L54qh79TykJlh-W3IkPLGr-zQElvL4ObUs,6598
54
54
  graphblas/io/_matrixmarket.py,sha256=qRJ3m87gifOaazgg0c35CyGQWHoTC_ZumLfGw-XKNSc,5484
55
- graphblas/io/_networkx.py,sha256=2ZI65KiWAxMgfWGO3DTRys3bb6FoVwnUaOm2Jteivds,1619
55
+ graphblas/io/_networkx.py,sha256=w2dJXXTI1ZH6xCsL5wFYtwscrM5CZVK179k61BKGx98,1667
56
56
  graphblas/io/_scipy.py,sha256=JLELo8lDWH5eza0AdqyEuI4y60FlNTjKyZ4v9GZba24,4111
57
57
  graphblas/io/_sparse.py,sha256=7GQsn6-a5duO5G6uW0Nh-lNxaQL-rUU0dzvDUwsjqOw,3247
58
58
  graphblas/monoid/__init__.py,sha256=VF-uQiH7DXLoAIOcLOO0ftMy875nBXN1Tv5klnc7I4w,1076
@@ -69,7 +69,7 @@ graphblas/semiring/ss.py,sha256=_ZS0Iyx0O2oYoTOXza2SLjWNXeGBYuzKEvEljBIww4Y,57
69
69
  graphblas/ss/__init__.py,sha256=MRUECkNFgmY5BfMn7J5NE_N_oGZ5hCRETqOhzWsA2yo,235
70
70
  graphblas/ss/_core.py,sha256=i4XApSUWlSBmWvRxK3WJ29LKbooPPF3d7mRRvGfpjXo,12543
71
71
  graphblas/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
- graphblas/tests/conftest.py,sha256=oXZLqUUD-pOy5iaQeQDDyTH1QWz_Sghq5t0WZWuNDto,4668
72
+ graphblas/tests/conftest.py,sha256=BqQqTD9qjw7hU5IByRMsBlsipX-xWC7tWD_G4GeTELc,4874
73
73
  graphblas/tests/pickle1-vanilla.pkl,sha256=pJqNg_K-NtM8cmSGNcWJGGklFFAVYgJA0c8P8ofhenQ,2755
74
74
  graphblas/tests/pickle1.pkl,sha256=4E7TctG64NyxoyDawj6tawZolKyE_-T6zVp4RNgtPHQ,2998
75
75
  graphblas/tests/pickle2-vanilla.pkl,sha256=y62DcVUC0IBLeC8Xq_VAE6hL7gZIRKlJ20GOcJVFOBY,1453
@@ -79,29 +79,29 @@ graphblas/tests/pickle3.pkl,sha256=OOsg91sHvrZbp_LFmzufsZIaAIwdxq0QH6ZmprUbI2E,8
79
79
  graphblas/tests/test_auto_init.py,sha256=uCFe7KCk_rT_Hx2yKHaFm6yX1V00SnEdbXL5E5j80vY,506
80
80
  graphblas/tests/test_core.py,sha256=vZaVZfxogXYhk_9q77DwOmBcLMX4NM1ZM1F4LPdu6Q0,3278
81
81
  graphblas/tests/test_descriptor.py,sha256=H-ZClw8ZNSb22V446MR3kdHeGGvpGeF24g3FLTuch0g,579
82
- graphblas/tests/test_dtype.py,sha256=Z13JMeiTDGgakpLUEPXk5J0Gsq9ee-TBh_Xbk88nTUs,8715
82
+ graphblas/tests/test_dtype.py,sha256=bbFgJIdBtQvuYaOtQjwkZcqmnQx1Z-5cjQ27LcVw4bw,8879
83
83
  graphblas/tests/test_external_init.py,sha256=AqtKhi9w-gdkerMa_MEYH4Av1gV-dFdy_840Z7Ka1Do,302
84
84
  graphblas/tests/test_formatting.py,sha256=HaIzQJU9FUSJxlYOb61Z1Lp5Mm84UPJ3C7ErEojdAoE,187980
85
85
  graphblas/tests/test_infix.py,sha256=n3ltg07DIlqT7sUNubUHxC2EBSPkYb9RJtn-t65qvoo,29058
86
- graphblas/tests/test_io.py,sha256=_HQEqXdvKacN_Ql3IoOSA8LnCL2OhOTKaJCZk7RBjq4,17241
86
+ graphblas/tests/test_io.py,sha256=t_iYWAM8EJEYvpmpJe-ljMg-UtlMOle-l4yigBAqAHo,17254
87
87
  graphblas/tests/test_mask.py,sha256=LYYy46dzzJlOMLAU5zZ2Yivony8wFxAESTkiL9RJLKM,4693
88
- graphblas/tests/test_matrix.py,sha256=eqh7Dy2nG-3NuBwidmeRRFRNS-xl8AEAHaftlyvOR2s,157367
89
- graphblas/tests/test_numpyops.py,sha256=67dLqyv32Of5EsPdesBae4w26jHLq_tnKHdcbwyB8UE,13296
88
+ graphblas/tests/test_matrix.py,sha256=mYshUu2OHV61xXhA7iVrOHSlcqU8WwiS7h5_XaVZUBo,157557
89
+ graphblas/tests/test_numpyops.py,sha256=by3MVs5NtONLNSkAqghyyK1KCMz82jDaNas5UfOmejk,13658
90
90
  graphblas/tests/test_op.py,sha256=SX-actrjLqEcGSdlf-y8bFonLhYo93rAAgMisqUnu8g,55332
91
91
  graphblas/tests/test_operator_types.py,sha256=JJY8BlHmyK8GVzyPf1eirBfahGMJ12wP_a1e40ZEqvU,11007
92
92
  graphblas/tests/test_pickle.py,sha256=uzFH9WzpdYbYy1pN_a7oGX-mZS2QvU2fF-Tta6FDKDQ,12735
93
93
  graphblas/tests/test_prefix_scan.py,sha256=nvwIywIDj6earTyZ6QLwpcWEZNssQebuV5P1XlSbyHw,2476
94
94
  graphblas/tests/test_recorder.py,sha256=YcqGt99PpV2iBYtOEQlwh_rWOY0CHp44e21IqRiiAA8,9199
95
95
  graphblas/tests/test_resolving.py,sha256=oQDCjTc2dimLavAJ7JeaEsQ1UH_ezsbigQq4zLAm3RQ,10923
96
- graphblas/tests/test_scalar.py,sha256=XEYUE1wdu4n2Yi3E9wB1sB2wzmgUtGtjqQO30ix9tos,19471
96
+ graphblas/tests/test_scalar.py,sha256=x4-9-ptNA9E1rubqQSMNL5OWoVgihhFwb0Ff3yoO-w0,19747
97
97
  graphblas/tests/test_ss_utils.py,sha256=8-GYG7ITeXYcsU0qbOof_5dosKZaXiVt3fsgzFLKKJs,10985
98
- graphblas/tests/test_ssjit.py,sha256=IRjrNqzbc8XylX1MqoaH7D9MrH-Q5SNA6Sqk-QAn5mA,16194
99
- graphblas/tests/test_vector.py,sha256=WAHb26vXQSecGovu3PQZlHMRlwJJn8kcJ79cvY6rCsg,89881
98
+ graphblas/tests/test_ssjit.py,sha256=P6HCsWXJfl7Krh0M8ieyK3G2DM8zSUHSxyjPzHHdJDw,17784
99
+ graphblas/tests/test_vector.py,sha256=ZU4g1vV5PEChVkLaOZED5hlabVolxStbzpjjc_J0zs8,90318
100
100
  graphblas/unary/__init__.py,sha256=PT-rSt4DfhPR7Go7HG6qc5CsXOy7I3XNq8M91e9ugrQ,1561
101
101
  graphblas/unary/numpy.py,sha256=nnuoHrOlOftovU_gQHQKH-oNO87uD6i23QseYNNqL8o,4158
102
102
  graphblas/unary/ss.py,sha256=0cetCBuSFKSE8Wt9JjduIP02581q83tg_tUJ4MrzC4I,112
103
- python_graphblas-2024.2.0.dist-info/LICENSE,sha256=tc3W3NZiv6HV_-4e16TQoM-zm7fohpX2t283tjKC_hg,11368
104
- python_graphblas-2024.2.0.dist-info/METADATA,sha256=CtSsgEEKM67Xp8fR9CoKiPUVlAjJLsumETxuVltyYjs,29547
105
- python_graphblas-2024.2.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
106
- python_graphblas-2024.2.0.dist-info/top_level.txt,sha256=QC8r4KyBl_LkZYZUdrsd0-NAJuQQcD_2fwGGiX104OM,10
107
- python_graphblas-2024.2.0.dist-info/RECORD,,
103
+ python_graphblas-2025.2.0a1.dist-info/LICENSE,sha256=tc3W3NZiv6HV_-4e16TQoM-zm7fohpX2t283tjKC_hg,11368
104
+ python_graphblas-2025.2.0a1.dist-info/METADATA,sha256=gWZldzcL_xz0wv7WvorToTIxEAU3p26fqX4nBi41oqw,29602
105
+ python_graphblas-2025.2.0a1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
106
+ python_graphblas-2025.2.0a1.dist-info/top_level.txt,sha256=QC8r4KyBl_LkZYZUdrsd0-NAJuQQcD_2fwGGiX104OM,10
107
+ python_graphblas-2025.2.0a1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5