python-graphblas 2023.3.0__py3-none-any.whl → 2023.7.0__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/__init__.py +15 -1
- graphblas/agg/__init__.py +4 -4
- graphblas/binary/__init__.py +16 -0
- graphblas/binary/numpy.py +14 -2
- graphblas/binary/ss.py +3 -0
- graphblas/core/__init__.py +9 -0
- graphblas/core/agg.py +3 -3
- graphblas/core/automethods.py +1 -1
- graphblas/core/base.py +3 -3
- graphblas/core/descriptor.py +1 -0
- graphblas/{dtypes.py → core/dtypes.py} +162 -86
- graphblas/core/expr.py +1 -1
- graphblas/core/formatting.py +1 -0
- graphblas/core/mask.py +5 -5
- graphblas/core/matrix.py +32 -28
- graphblas/core/operator/agg.py +24 -3
- graphblas/core/operator/base.py +78 -93
- graphblas/core/operator/binary.py +150 -36
- graphblas/core/operator/indexunary.py +101 -25
- graphblas/core/operator/monoid.py +38 -11
- graphblas/core/operator/select.py +109 -7
- graphblas/core/operator/semiring.py +41 -21
- graphblas/core/operator/unary.py +77 -14
- graphblas/core/recorder.py +3 -3
- graphblas/core/scalar.py +18 -3
- graphblas/core/ss/__init__.py +3 -0
- graphblas/core/ss/binary.py +72 -0
- graphblas/core/ss/config.py +46 -11
- graphblas/core/ss/context.py +146 -0
- graphblas/core/ss/descriptor.py +21 -9
- graphblas/core/ss/dtypes.py +88 -0
- graphblas/core/ss/indexunary.py +77 -0
- graphblas/core/ss/matrix.py +116 -105
- graphblas/core/ss/select.py +45 -0
- graphblas/core/ss/unary.py +62 -0
- graphblas/core/ss/vector.py +40 -40
- graphblas/core/utils.py +15 -12
- graphblas/core/vector.py +69 -22
- graphblas/dtypes/__init__.py +43 -0
- graphblas/dtypes/ss.py +1 -0
- graphblas/indexunary/__init__.py +13 -1
- graphblas/indexunary/ss.py +6 -0
- graphblas/io/__init__.py +7 -0
- graphblas/io/_awkward.py +187 -0
- graphblas/io/_matrixmarket.py +140 -0
- graphblas/io/_networkx.py +59 -0
- graphblas/io/_numpy.py +104 -0
- graphblas/io/_scipy.py +118 -0
- graphblas/io/_sparse.py +99 -0
- graphblas/io/_viz.py +21 -0
- graphblas/monoid/__init__.py +13 -1
- graphblas/monoid/numpy.py +12 -3
- graphblas/monoid/ss.py +5 -0
- graphblas/op/__init__.py +9 -1
- graphblas/op/numpy.py +5 -1
- graphblas/op/ss.py +2 -0
- graphblas/select/__init__.py +19 -7
- graphblas/select/ss.py +6 -0
- graphblas/semiring/__init__.py +27 -0
- graphblas/semiring/numpy.py +12 -1
- graphblas/semiring/ss.py +2 -0
- graphblas/ss/__init__.py +5 -1
- graphblas/ss/_core.py +67 -13
- graphblas/tests/conftest.py +43 -7
- graphblas/tests/test_core.py +5 -2
- graphblas/tests/test_dtype.py +23 -9
- graphblas/tests/test_formatting.py +70 -3
- graphblas/tests/test_infix.py +7 -0
- graphblas/tests/test_io.py +28 -8
- graphblas/tests/test_matrix.py +100 -47
- graphblas/tests/test_numpyops.py +44 -18
- graphblas/tests/test_op.py +193 -82
- graphblas/tests/test_operator_types.py +6 -0
- graphblas/tests/test_pickle.py +6 -0
- graphblas/tests/test_scalar.py +12 -10
- graphblas/tests/test_ssjit.py +269 -0
- graphblas/tests/test_vector.py +58 -16
- graphblas/unary/numpy.py +12 -1
- graphblas/unary/ss.py +3 -0
- graphblas/viz.py +4 -5
- {python_graphblas-2023.3.0.dist-info → python_graphblas-2023.7.0.dist-info}/LICENSE +1 -1
- {python_graphblas-2023.3.0.dist-info → python_graphblas-2023.7.0.dist-info}/METADATA +72 -32
- python_graphblas-2023.7.0.dist-info/RECORD +110 -0
- graphblas/io.py +0 -697
- python_graphblas-2023.3.0.dist-info/RECORD +0 -91
- {python_graphblas-2023.3.0.dist-info → python_graphblas-2023.7.0.dist-info}/WHEEL +0 -0
- {python_graphblas-2023.3.0.dist-info → python_graphblas-2023.7.0.dist-info}/top_level.txt +0 -0
graphblas/__init__.py
CHANGED
|
@@ -137,7 +137,21 @@ def _init(backend_arg, blocking, automatic=False):
|
|
|
137
137
|
|
|
138
138
|
backend = backend_arg
|
|
139
139
|
if backend in {"suitesparse", "suitesparse-vanilla"}:
|
|
140
|
-
|
|
140
|
+
try:
|
|
141
|
+
from suitesparse_graphblas import ffi, initialize, is_initialized, lib
|
|
142
|
+
except ImportError: # pragma: no cover (import)
|
|
143
|
+
raise ImportError(
|
|
144
|
+
f"suitesparse_graphblas is required for {backend!r} backend. "
|
|
145
|
+
"It may be installed with pip or conda:\n\n"
|
|
146
|
+
" $ pip install suitesparse-graphblas\n"
|
|
147
|
+
" $ conda install -c conda-forge python-suitesparse-graphblas\n\n"
|
|
148
|
+
"SuiteSparse:GraphBLAS is the primary C implementation and backend of "
|
|
149
|
+
"python-graphblas and is what we recommend to most users. If you are "
|
|
150
|
+
"installing python-graphblas with pip, we recommend installing with one "
|
|
151
|
+
"of the following to automatically include suitespare-graphblas:\n\n"
|
|
152
|
+
" $ pip install python-graphblas[suitesparse]\n"
|
|
153
|
+
" $ pip install python-graphblas[default]"
|
|
154
|
+
) from None
|
|
141
155
|
|
|
142
156
|
if is_initialized():
|
|
143
157
|
mode = ffi.new("int32_t*")
|
graphblas/agg/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""``graphblas.agg`` is an experimental module for exploring Aggregators.
|
|
2
2
|
|
|
3
3
|
Aggregators may be used in reduce methods:
|
|
4
4
|
- Matrix.reduce_rowwise
|
|
@@ -59,9 +59,9 @@ Custom recipes (specific to SuiteSparse:GraphBLAS):
|
|
|
59
59
|
- ss.argmax
|
|
60
60
|
|
|
61
61
|
.. deprecated:: 2023.1.0
|
|
62
|
-
Aggregators
|
|
63
|
-
deprecated in the
|
|
64
|
-
instead such as
|
|
62
|
+
Aggregators ``first``, ``last``, ``first_index``, ``last_index``, ``argmin``, and ``argmax``
|
|
63
|
+
are deprecated in the ``agg`` namespace such as ``agg.first``. Use them from ``agg.ss``
|
|
64
|
+
namespace instead such as ``agg.ss.first``. Will be removed in version 2023.9.0 or later.
|
|
65
65
|
|
|
66
66
|
# Possible aggregators:
|
|
67
67
|
# - absolute_deviation, sum(abs(x - mean(x))), sum_absminus(x, mean(x))
|
graphblas/binary/__init__.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# All items are dynamically added by classes in operator.py
|
|
2
2
|
# This module acts as a container of BinaryOp instances
|
|
3
|
+
from ..core import _supports_udfs
|
|
4
|
+
|
|
3
5
|
_delayed = {}
|
|
4
6
|
_delayed_commutes_to = {
|
|
5
7
|
"absfirst": "abssecond",
|
|
@@ -9,6 +11,15 @@ _delayed_commutes_to = {
|
|
|
9
11
|
"rpow": "pow",
|
|
10
12
|
}
|
|
11
13
|
_deprecated = {}
|
|
14
|
+
_udfs = {
|
|
15
|
+
"absfirst",
|
|
16
|
+
"abssecond",
|
|
17
|
+
"binom",
|
|
18
|
+
"floordiv",
|
|
19
|
+
"isclose",
|
|
20
|
+
"rfloordiv",
|
|
21
|
+
"rpow",
|
|
22
|
+
}
|
|
12
23
|
|
|
13
24
|
|
|
14
25
|
def __dir__():
|
|
@@ -50,6 +61,11 @@ def __getattr__(key):
|
|
|
50
61
|
ss = import_module(".ss", __name__)
|
|
51
62
|
globals()["ss"] = ss
|
|
52
63
|
return ss
|
|
64
|
+
if not _supports_udfs and key in _udfs:
|
|
65
|
+
raise AttributeError(
|
|
66
|
+
f"module {__name__!r} unable to compile UDF for {key!r}; "
|
|
67
|
+
"install numba for UDF support"
|
|
68
|
+
)
|
|
53
69
|
raise AttributeError(f"module {__name__!r} has no attribute {key!r}")
|
|
54
70
|
|
|
55
71
|
|
graphblas/binary/numpy.py
CHANGED
|
@@ -10,6 +10,7 @@ import numpy as _np
|
|
|
10
10
|
from .. import _STANDARD_OPERATOR_NAMES
|
|
11
11
|
from .. import binary as _binary
|
|
12
12
|
from .. import config as _config
|
|
13
|
+
from ..core import _supports_udfs
|
|
13
14
|
|
|
14
15
|
_delayed = {}
|
|
15
16
|
_binary_names = {
|
|
@@ -130,7 +131,13 @@ _commutes_to = {
|
|
|
130
131
|
|
|
131
132
|
|
|
132
133
|
def __dir__():
|
|
133
|
-
|
|
134
|
+
if not _supports_udfs and not _config["mapnumpy"]:
|
|
135
|
+
# float_power is special: it's constructed from builtin operators
|
|
136
|
+
return globals().keys() | {"float_power"} # FLAKY COVERAGE
|
|
137
|
+
attrs = _delayed.keys() | _binary_names
|
|
138
|
+
if not _supports_udfs:
|
|
139
|
+
attrs &= _numpy_to_graphblas.keys()
|
|
140
|
+
return attrs | globals().keys()
|
|
134
141
|
|
|
135
142
|
|
|
136
143
|
def __getattr__(name):
|
|
@@ -141,7 +148,7 @@ def __getattr__(name):
|
|
|
141
148
|
return rv
|
|
142
149
|
if name not in _binary_names:
|
|
143
150
|
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
144
|
-
if _config.get("mapnumpy") and name in _numpy_to_graphblas:
|
|
151
|
+
if _config.get("mapnumpy") and name in _numpy_to_graphblas or name == "float_power":
|
|
145
152
|
if name == "float_power":
|
|
146
153
|
from ..core.operator import binary
|
|
147
154
|
from ..dtypes import FP64
|
|
@@ -166,6 +173,11 @@ def __getattr__(name):
|
|
|
166
173
|
globals()[name] = new_op
|
|
167
174
|
else:
|
|
168
175
|
globals()[name] = getattr(_binary, _numpy_to_graphblas[name])
|
|
176
|
+
elif not _supports_udfs:
|
|
177
|
+
raise AttributeError(
|
|
178
|
+
f"module {__name__!r} unable to compile UDF for {name!r}; "
|
|
179
|
+
"install numba for UDF support"
|
|
180
|
+
)
|
|
169
181
|
else:
|
|
170
182
|
numpy_func = getattr(_np, name)
|
|
171
183
|
|
graphblas/binary/ss.py
CHANGED
graphblas/core/__init__.py
CHANGED
graphblas/core/agg.py
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"""graphblas.core.agg namespace is deprecated; please use graphblas.core.operator.agg instead.
|
|
2
2
|
|
|
3
3
|
.. deprecated:: 2023.3.0
|
|
4
|
-
|
|
5
|
-
Use
|
|
4
|
+
``graphblas.core.agg`` will be removed in a future release.
|
|
5
|
+
Use ``graphblas.core.operator.agg`` instead.
|
|
6
6
|
Will be removed in version 2023.11.0 or later.
|
|
7
7
|
|
|
8
8
|
"""
|
|
9
9
|
import warnings
|
|
10
10
|
|
|
11
|
-
from .operator.agg import *
|
|
11
|
+
from .operator.agg import * # pylint: disable=wildcard-import,unused-wildcard-import
|
|
12
12
|
|
|
13
13
|
warnings.warn(
|
|
14
14
|
"graphblas.core.agg namespace is deprecated; please use graphblas.core.operator.agg instead.",
|
graphblas/core/automethods.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Define functions to use as property methods on expressions.
|
|
2
2
|
|
|
3
|
-
These will automatically compute the value and avoid the need for
|
|
3
|
+
These will automatically compute the value and avoid the need for ``.new()``.
|
|
4
4
|
|
|
5
5
|
To automatically create the functions, run:
|
|
6
6
|
|
graphblas/core/base.py
CHANGED
|
@@ -348,7 +348,7 @@ class BaseType:
|
|
|
348
348
|
return
|
|
349
349
|
if opts:
|
|
350
350
|
# Ignore opts for now
|
|
351
|
-
descriptor_lookup(**opts)
|
|
351
|
+
desc = descriptor_lookup(**opts)
|
|
352
352
|
self.value = expr
|
|
353
353
|
return
|
|
354
354
|
|
|
@@ -371,7 +371,7 @@ class BaseType:
|
|
|
371
371
|
else:
|
|
372
372
|
if opts:
|
|
373
373
|
# Ignore opts for now
|
|
374
|
-
descriptor_lookup(**opts)
|
|
374
|
+
desc = descriptor_lookup(**opts)
|
|
375
375
|
self.value = expr
|
|
376
376
|
return
|
|
377
377
|
else:
|
|
@@ -571,7 +571,7 @@ class BaseExpression:
|
|
|
571
571
|
):
|
|
572
572
|
if opts:
|
|
573
573
|
# Ignore opts for now
|
|
574
|
-
descriptor_lookup(**opts)
|
|
574
|
+
desc = descriptor_lookup(**opts) # noqa: F841 (keep desc in scope for context)
|
|
575
575
|
if self._is_scalar and self._value._is_cscalar != is_cscalar:
|
|
576
576
|
return self._value.dup(is_cscalar=is_cscalar, name=name)
|
|
577
577
|
rv = self._value
|
graphblas/core/descriptor.py
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import warnings
|
|
1
|
+
import warnings
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
from numpy import find_common_type as _find_common_type
|
|
6
|
-
from numpy import promote_types as _promote_types
|
|
3
|
+
import numpy as np
|
|
4
|
+
from numpy import promote_types, result_type
|
|
7
5
|
|
|
8
|
-
from
|
|
9
|
-
from
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
from .. import backend, dtypes
|
|
7
|
+
from ..core import NULL, _has_numba, ffi, lib
|
|
8
|
+
|
|
9
|
+
if _has_numba:
|
|
10
|
+
import numba
|
|
12
11
|
|
|
13
12
|
# Default assumption unless FC32/FC64 are found in lib
|
|
14
|
-
_supports_complex = hasattr(
|
|
13
|
+
_supports_complex = hasattr(lib, "GrB_FC64") or hasattr(lib, "GxB_FC64")
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
class DataType:
|
|
@@ -23,7 +22,7 @@ class DataType:
|
|
|
23
22
|
self.gb_name = gb_name
|
|
24
23
|
self.c_type = c_type
|
|
25
24
|
self.numba_type = numba_type
|
|
26
|
-
self.np_type =
|
|
25
|
+
self.np_type = np.dtype(np_type) if np_type is not None else None
|
|
27
26
|
|
|
28
27
|
def __repr__(self):
|
|
29
28
|
return self.name
|
|
@@ -59,7 +58,7 @@ class DataType:
|
|
|
59
58
|
|
|
60
59
|
@property
|
|
61
60
|
def _is_anonymous(self):
|
|
62
|
-
return
|
|
61
|
+
return getattr(dtypes, self.name, None) is not self
|
|
63
62
|
|
|
64
63
|
@property
|
|
65
64
|
def _is_udt(self):
|
|
@@ -77,27 +76,29 @@ class DataType:
|
|
|
77
76
|
def register_new(name, dtype):
|
|
78
77
|
if not name.isidentifier():
|
|
79
78
|
raise ValueError(f"`name` argument must be a valid Python identifier; got: {name!r}")
|
|
80
|
-
if name in _registry or name
|
|
79
|
+
if name in _registry or hasattr(dtypes, name):
|
|
81
80
|
raise ValueError(f"{name!r} name for dtype is unavailable")
|
|
82
81
|
rv = register_anonymous(dtype, name)
|
|
83
82
|
_registry[name] = rv
|
|
84
|
-
|
|
83
|
+
setattr(dtypes, name, rv)
|
|
85
84
|
return rv
|
|
86
85
|
|
|
87
86
|
|
|
88
87
|
def register_anonymous(dtype, name=None):
|
|
89
88
|
try:
|
|
90
|
-
dtype =
|
|
89
|
+
dtype = np.dtype(dtype)
|
|
91
90
|
except TypeError:
|
|
92
91
|
if isinstance(dtype, dict):
|
|
93
92
|
# Allow dtypes such as `{'x': int, 'y': float}` for convenience
|
|
94
|
-
dtype =
|
|
93
|
+
dtype = np.dtype(
|
|
94
|
+
[(key, lookup_dtype(val).np_type) for key, val in dtype.items()], align=True
|
|
95
|
+
)
|
|
95
96
|
elif isinstance(dtype, str) and "[" in dtype and dtype.endswith("]"):
|
|
96
97
|
# Allow dtypes such as `"INT64[3, 4]"` for convenience
|
|
97
98
|
base_dtype, shape = dtype.split("[", 1)
|
|
98
99
|
base_dtype = lookup_dtype(base_dtype)
|
|
99
|
-
shape =
|
|
100
|
-
dtype =
|
|
100
|
+
shape = np.lib.format.safe_eval(f"[{shape}")
|
|
101
|
+
dtype = np.dtype((base_dtype.np_type, shape))
|
|
101
102
|
else:
|
|
102
103
|
raise
|
|
103
104
|
if dtype in _registry:
|
|
@@ -111,112 +112,194 @@ def register_anonymous(dtype, name=None):
|
|
|
111
112
|
if dtype.hasobject:
|
|
112
113
|
raise ValueError("dtype must not allow Python objects")
|
|
113
114
|
|
|
114
|
-
from
|
|
115
|
+
from ..exceptions import check_status_carg
|
|
115
116
|
|
|
116
|
-
gb_obj =
|
|
117
|
+
gb_obj = ffi.new("GrB_Type*")
|
|
117
118
|
if backend == "suitesparse":
|
|
118
119
|
# We name this so that we can serialize and deserialize UDTs
|
|
119
120
|
# We don't yet have C definitions
|
|
120
121
|
np_repr = _dtype_to_string(dtype).encode()
|
|
121
|
-
if len(np_repr) >
|
|
122
|
+
if len(np_repr) > lib.GxB_MAX_NAME_LEN:
|
|
122
123
|
msg = (
|
|
123
124
|
f"UDT repr is too large to serialize ({len(repr(dtype).encode())} > "
|
|
124
|
-
f"{
|
|
125
|
+
f"{lib.GxB_MAX_NAME_LEN})."
|
|
125
126
|
)
|
|
126
127
|
if name is not None:
|
|
127
|
-
np_repr = name.encode()[:
|
|
128
|
+
np_repr = name.encode()[: lib.GxB_MAX_NAME_LEN]
|
|
128
129
|
else:
|
|
129
|
-
np_repr = np_repr[:
|
|
130
|
-
|
|
130
|
+
np_repr = np_repr[: lib.GxB_MAX_NAME_LEN]
|
|
131
|
+
warnings.warn(
|
|
131
132
|
f"{msg}. It will use the following name, "
|
|
132
133
|
f"and the dtype may need to be specified when deserializing: {np_repr}",
|
|
133
134
|
stacklevel=2,
|
|
134
135
|
)
|
|
135
|
-
status =
|
|
136
|
+
status = lib.GxB_Type_new(gb_obj, dtype.itemsize, np_repr, NULL)
|
|
136
137
|
else:
|
|
137
|
-
status =
|
|
138
|
+
status = lib.GrB_Type_new(gb_obj, dtype.itemsize)
|
|
138
139
|
check_status_carg(status, "Type", gb_obj[0])
|
|
139
140
|
|
|
140
141
|
# For now, let's use "opaque" unsigned bytes for the c type.
|
|
141
142
|
if name is None:
|
|
142
143
|
name = _default_name(dtype)
|
|
143
|
-
numba_type =
|
|
144
|
+
numba_type = numba.typeof(dtype).dtype if _has_numba else None
|
|
144
145
|
rv = DataType(name, gb_obj, None, f"uint8_t[{dtype.itemsize}]", numba_type, dtype)
|
|
145
146
|
_registry[gb_obj] = rv
|
|
146
147
|
_registry[dtype] = rv
|
|
147
|
-
|
|
148
|
-
|
|
148
|
+
if _has_numba:
|
|
149
|
+
_registry[numba_type] = rv
|
|
150
|
+
_registry[numba_type.name] = rv
|
|
149
151
|
return rv
|
|
150
152
|
|
|
151
153
|
|
|
152
|
-
BOOL = DataType(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
BOOL = DataType(
|
|
155
|
+
"BOOL",
|
|
156
|
+
lib.GrB_BOOL,
|
|
157
|
+
"GrB_BOOL",
|
|
158
|
+
"_Bool",
|
|
159
|
+
numba.types.bool_ if _has_numba else None,
|
|
160
|
+
np.bool_,
|
|
161
|
+
)
|
|
162
|
+
INT8 = DataType(
|
|
163
|
+
"INT8", lib.GrB_INT8, "GrB_INT8", "int8_t", numba.types.int8 if _has_numba else None, np.int8
|
|
164
|
+
)
|
|
165
|
+
UINT8 = DataType(
|
|
166
|
+
"UINT8",
|
|
167
|
+
lib.GrB_UINT8,
|
|
168
|
+
"GrB_UINT8",
|
|
169
|
+
"uint8_t",
|
|
170
|
+
numba.types.uint8 if _has_numba else None,
|
|
171
|
+
np.uint8,
|
|
172
|
+
)
|
|
173
|
+
INT16 = DataType(
|
|
174
|
+
"INT16",
|
|
175
|
+
lib.GrB_INT16,
|
|
176
|
+
"GrB_INT16",
|
|
177
|
+
"int16_t",
|
|
178
|
+
numba.types.int16 if _has_numba else None,
|
|
179
|
+
np.int16,
|
|
180
|
+
)
|
|
156
181
|
UINT16 = DataType(
|
|
157
|
-
"UINT16",
|
|
182
|
+
"UINT16",
|
|
183
|
+
lib.GrB_UINT16,
|
|
184
|
+
"GrB_UINT16",
|
|
185
|
+
"uint16_t",
|
|
186
|
+
numba.types.uint16 if _has_numba else None,
|
|
187
|
+
np.uint16,
|
|
188
|
+
)
|
|
189
|
+
INT32 = DataType(
|
|
190
|
+
"INT32",
|
|
191
|
+
lib.GrB_INT32,
|
|
192
|
+
"GrB_INT32",
|
|
193
|
+
"int32_t",
|
|
194
|
+
numba.types.int32 if _has_numba else None,
|
|
195
|
+
np.int32,
|
|
158
196
|
)
|
|
159
|
-
INT32 = DataType("INT32", _lib.GrB_INT32, "GrB_INT32", "int32_t", _numba.types.int32, _np.int32)
|
|
160
197
|
UINT32 = DataType(
|
|
161
|
-
"UINT32",
|
|
198
|
+
"UINT32",
|
|
199
|
+
lib.GrB_UINT32,
|
|
200
|
+
"GrB_UINT32",
|
|
201
|
+
"uint32_t",
|
|
202
|
+
numba.types.uint32 if _has_numba else None,
|
|
203
|
+
np.uint32,
|
|
204
|
+
)
|
|
205
|
+
INT64 = DataType(
|
|
206
|
+
"INT64",
|
|
207
|
+
lib.GrB_INT64,
|
|
208
|
+
"GrB_INT64",
|
|
209
|
+
"int64_t",
|
|
210
|
+
numba.types.int64 if _has_numba else None,
|
|
211
|
+
np.int64,
|
|
162
212
|
)
|
|
163
|
-
INT64 = DataType("INT64", _lib.GrB_INT64, "GrB_INT64", "int64_t", _numba.types.int64, _np.int64)
|
|
164
213
|
# _Index (like UINT64) is for internal use only and shouldn't be exposed to the user
|
|
165
214
|
_INDEX = DataType(
|
|
166
|
-
"UINT64",
|
|
215
|
+
"UINT64",
|
|
216
|
+
lib.GrB_UINT64,
|
|
217
|
+
"GrB_Index",
|
|
218
|
+
"GrB_Index",
|
|
219
|
+
numba.types.uint64 if _has_numba else None,
|
|
220
|
+
np.uint64,
|
|
167
221
|
)
|
|
168
222
|
UINT64 = DataType(
|
|
169
|
-
"UINT64",
|
|
223
|
+
"UINT64",
|
|
224
|
+
lib.GrB_UINT64,
|
|
225
|
+
"GrB_UINT64",
|
|
226
|
+
"uint64_t",
|
|
227
|
+
numba.types.uint64 if _has_numba else None,
|
|
228
|
+
np.uint64,
|
|
229
|
+
)
|
|
230
|
+
FP32 = DataType(
|
|
231
|
+
"FP32",
|
|
232
|
+
lib.GrB_FP32,
|
|
233
|
+
"GrB_FP32",
|
|
234
|
+
"float",
|
|
235
|
+
numba.types.float32 if _has_numba else None,
|
|
236
|
+
np.float32,
|
|
237
|
+
)
|
|
238
|
+
FP64 = DataType(
|
|
239
|
+
"FP64",
|
|
240
|
+
lib.GrB_FP64,
|
|
241
|
+
"GrB_FP64",
|
|
242
|
+
"double",
|
|
243
|
+
numba.types.float64 if _has_numba else None,
|
|
244
|
+
np.float64,
|
|
170
245
|
)
|
|
171
|
-
FP32 = DataType("FP32", _lib.GrB_FP32, "GrB_FP32", "float", _numba.types.float32, _np.float32)
|
|
172
|
-
FP64 = DataType("FP64", _lib.GrB_FP64, "GrB_FP64", "double", _numba.types.float64, _np.float64)
|
|
173
246
|
|
|
174
|
-
if _supports_complex and hasattr(
|
|
247
|
+
if _supports_complex and hasattr(lib, "GxB_FC32"):
|
|
175
248
|
FC32 = DataType(
|
|
176
|
-
"FC32",
|
|
249
|
+
"FC32",
|
|
250
|
+
lib.GxB_FC32,
|
|
251
|
+
"GxB_FC32",
|
|
252
|
+
"float _Complex",
|
|
253
|
+
numba.types.complex64 if _has_numba else None,
|
|
254
|
+
np.complex64,
|
|
177
255
|
)
|
|
178
|
-
if _supports_complex and hasattr(
|
|
256
|
+
if _supports_complex and hasattr(lib, "GrB_FC32"): # pragma: no cover (unused)
|
|
179
257
|
FC32 = DataType(
|
|
180
|
-
"FC32",
|
|
258
|
+
"FC32",
|
|
259
|
+
lib.GrB_FC32,
|
|
260
|
+
"GrB_FC32",
|
|
261
|
+
"float _Complex",
|
|
262
|
+
numba.types.complex64 if _has_numba else None,
|
|
263
|
+
np.complex64,
|
|
181
264
|
)
|
|
182
|
-
if _supports_complex and hasattr(
|
|
265
|
+
if _supports_complex and hasattr(lib, "GxB_FC64"):
|
|
183
266
|
FC64 = DataType(
|
|
184
267
|
"FC64",
|
|
185
|
-
|
|
268
|
+
lib.GxB_FC64,
|
|
186
269
|
"GxB_FC64",
|
|
187
270
|
"double _Complex",
|
|
188
|
-
|
|
189
|
-
|
|
271
|
+
numba.types.complex128 if _has_numba else None,
|
|
272
|
+
np.complex128,
|
|
190
273
|
)
|
|
191
|
-
if _supports_complex and hasattr(
|
|
274
|
+
if _supports_complex and hasattr(lib, "GrB_FC64"): # pragma: no cover (unused)
|
|
192
275
|
FC64 = DataType(
|
|
193
276
|
"FC64",
|
|
194
|
-
|
|
277
|
+
lib.GrB_FC64,
|
|
195
278
|
"GrB_FC64",
|
|
196
279
|
"double _Complex",
|
|
197
|
-
|
|
198
|
-
|
|
280
|
+
numba.types.complex128 if _has_numba else None,
|
|
281
|
+
np.complex128,
|
|
199
282
|
)
|
|
200
283
|
|
|
201
284
|
# Used for testing user-defined functions
|
|
202
285
|
_sample_values = {
|
|
203
|
-
INT8:
|
|
204
|
-
UINT8:
|
|
205
|
-
INT16:
|
|
206
|
-
UINT16:
|
|
207
|
-
INT32:
|
|
208
|
-
UINT32:
|
|
209
|
-
INT64:
|
|
210
|
-
UINT64:
|
|
211
|
-
FP32:
|
|
212
|
-
FP64:
|
|
213
|
-
BOOL:
|
|
286
|
+
INT8: np.int8(1),
|
|
287
|
+
UINT8: np.uint8(1),
|
|
288
|
+
INT16: np.int16(1),
|
|
289
|
+
UINT16: np.uint16(1),
|
|
290
|
+
INT32: np.int32(1),
|
|
291
|
+
UINT32: np.uint32(1),
|
|
292
|
+
INT64: np.int64(1),
|
|
293
|
+
UINT64: np.uint64(1),
|
|
294
|
+
FP32: np.float32(0.5),
|
|
295
|
+
FP64: np.float64(0.5),
|
|
296
|
+
BOOL: np.bool_(True),
|
|
214
297
|
}
|
|
215
298
|
if _supports_complex:
|
|
216
299
|
_sample_values.update(
|
|
217
300
|
{
|
|
218
|
-
FC32:
|
|
219
|
-
FC64:
|
|
301
|
+
FC32: np.complex64(complex(0, 0.5)),
|
|
302
|
+
FC64: np.complex128(complex(0, 0.5)),
|
|
220
303
|
}
|
|
221
304
|
)
|
|
222
305
|
|
|
@@ -246,8 +329,9 @@ for dtype in _dtypes_to_register:
|
|
|
246
329
|
_registry[dtype.gb_name.lower()] = dtype
|
|
247
330
|
_registry[dtype.c_type] = dtype
|
|
248
331
|
_registry[dtype.c_type.upper()] = dtype
|
|
249
|
-
|
|
250
|
-
|
|
332
|
+
if _has_numba:
|
|
333
|
+
_registry[dtype.numba_type] = dtype
|
|
334
|
+
_registry[dtype.numba_type.name] = dtype
|
|
251
335
|
val = _sample_values[dtype]
|
|
252
336
|
_registry[val.dtype] = dtype
|
|
253
337
|
_registry[val.dtype.name] = dtype
|
|
@@ -303,19 +387,11 @@ def unify(type1, type2, *, is_left_scalar=False, is_right_scalar=False):
|
|
|
303
387
|
if type1 is type2:
|
|
304
388
|
return type1
|
|
305
389
|
if is_left_scalar:
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
elif
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
else:
|
|
312
|
-
scalar_types = []
|
|
313
|
-
array_types = [type1.np_type]
|
|
314
|
-
if is_right_scalar:
|
|
315
|
-
scalar_types.append(type2.np_type)
|
|
316
|
-
else:
|
|
317
|
-
array_types.append(type2.np_type)
|
|
318
|
-
return lookup_dtype(_find_common_type(array_types, scalar_types))
|
|
390
|
+
if not is_right_scalar:
|
|
391
|
+
return lookup_dtype(result_type(np.array(0, type1.np_type), type2.np_type))
|
|
392
|
+
elif is_right_scalar:
|
|
393
|
+
return lookup_dtype(result_type(type1.np_type, np.array(0, type2.np_type)))
|
|
394
|
+
return lookup_dtype(promote_types(type1.np_type, type2.np_type))
|
|
319
395
|
|
|
320
396
|
|
|
321
397
|
def _default_name(dtype):
|
|
@@ -345,7 +421,7 @@ def _dtype_to_string(dtype):
|
|
|
345
421
|
>>> dtype == new_dtype
|
|
346
422
|
True
|
|
347
423
|
"""
|
|
348
|
-
if isinstance(dtype,
|
|
424
|
+
if isinstance(dtype, np.dtype) and dtype not in _registry:
|
|
349
425
|
np_type = dtype
|
|
350
426
|
else:
|
|
351
427
|
dtype = lookup_dtype(dtype)
|
|
@@ -354,11 +430,11 @@ def _dtype_to_string(dtype):
|
|
|
354
430
|
np_type = dtype.np_type
|
|
355
431
|
s = str(np_type)
|
|
356
432
|
try:
|
|
357
|
-
if
|
|
433
|
+
if np.dtype(np.lib.format.safe_eval(s)) == np_type: # pragma: no branch (safety)
|
|
358
434
|
return s
|
|
359
435
|
except Exception:
|
|
360
436
|
pass
|
|
361
|
-
if
|
|
437
|
+
if np.dtype(np_type.str) != np_type: # pragma: no cover (safety)
|
|
362
438
|
raise ValueError(f"Unable to reliably convert dtype to string and back: {dtype}")
|
|
363
439
|
return repr(np_type.str)
|
|
364
440
|
|
|
@@ -373,5 +449,5 @@ def _string_to_dtype(s):
|
|
|
373
449
|
return lookup_dtype(s)
|
|
374
450
|
except Exception:
|
|
375
451
|
pass
|
|
376
|
-
np_type =
|
|
452
|
+
np_type = np.dtype(np.lib.format.safe_eval(s))
|
|
377
453
|
return lookup_dtype(np_type)
|
graphblas/core/expr.py
CHANGED
|
@@ -421,7 +421,7 @@ class Updater:
|
|
|
421
421
|
# Fast path using assignElement
|
|
422
422
|
if self.opts:
|
|
423
423
|
# Ignore opts for now
|
|
424
|
-
descriptor_lookup(**self.opts)
|
|
424
|
+
desc = descriptor_lookup(**self.opts) # noqa: F841 (keep desc in scope for context)
|
|
425
425
|
self.parent._assign_element(resolved_indexes, obj)
|
|
426
426
|
else:
|
|
427
427
|
mask = self.kwargs.get("mask")
|
graphblas/core/formatting.py
CHANGED
graphblas/core/mask.py
CHANGED
|
@@ -35,7 +35,7 @@ class Mask:
|
|
|
35
35
|
"""Return a new object with True values determined by the mask(s).
|
|
36
36
|
|
|
37
37
|
By default, the result is True wherever the mask(s) would have been applied,
|
|
38
|
-
and empty otherwise. If
|
|
38
|
+
and empty otherwise. If ``complement`` is True, then these are switched:
|
|
39
39
|
the result is empty where the mask(s) would have been applied, and True otherwise.
|
|
40
40
|
|
|
41
41
|
In other words, these are equivalent if complement is False (and mask keyword is None):
|
|
@@ -48,14 +48,14 @@ class Mask:
|
|
|
48
48
|
>>> C(self) << expr
|
|
49
49
|
>>> C(~result.S) << expr # equivalent when complement is True
|
|
50
50
|
|
|
51
|
-
This can also efficiently merge two masks by using the
|
|
51
|
+
This can also efficiently merge two masks by using the ``mask=`` argument.
|
|
52
52
|
This is equivalent to the following (but uses more efficient recipes):
|
|
53
53
|
|
|
54
54
|
>>> val = Matrix(...)
|
|
55
55
|
>>> val(self) << True
|
|
56
56
|
>>> val(mask, replace=True) << val
|
|
57
57
|
|
|
58
|
-
If
|
|
58
|
+
If ``complement=`` argument is True, then the *complement* will be returned.
|
|
59
59
|
This is equivalent to the following (but uses more efficient recipes):
|
|
60
60
|
|
|
61
61
|
>>> val = Matrix(...)
|
|
@@ -83,7 +83,7 @@ class Mask:
|
|
|
83
83
|
def __and__(self, other, **opts):
|
|
84
84
|
"""Return the intersection of two masks as a new mask.
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
``new_mask = mask1 & mask2`` is equivalent to the following:
|
|
87
87
|
|
|
88
88
|
>>> val = Matrix(bool, nrows, ncols)
|
|
89
89
|
>>> val(mask1) << True
|
|
@@ -109,7 +109,7 @@ class Mask:
|
|
|
109
109
|
def __or__(self, other, **opts):
|
|
110
110
|
"""Return the union of two masks as a new mask.
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
``new_mask = mask1 | mask2`` is equivalent to the following:
|
|
113
113
|
|
|
114
114
|
>>> val = Matrix(bool, nrows, ncols)
|
|
115
115
|
>>> val(mask1) << True
|