-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy path__init__.py
More file actions
218 lines (177 loc) · 6.96 KB
/
Copy path__init__.py
File metadata and controls
218 lines (177 loc) · 6.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
import sys as _sys
from importlib import import_module as _import_module
class replace:
"""Singleton to indicate ``replace=True`` when updating objects.
>>> C(mask, replace) << A.mxm(B)
"""
def __repr__(self):
return "replace"
def __reduce__(self):
return "replace"
replace = replace()
def get_config():
from pathlib import Path
import donfig
import yaml
config = donfig.Config("graphblas")
path = Path(__file__).parent / "graphblas.yaml"
with path.open() as f:
defaults = yaml.safe_load(f)
config.update_defaults(defaults)
return config
config = get_config()
del get_config
backend = None
_init_params = None
_SPECIAL_ATTRS = {
"MAX_SIZE", # The maximum size of Vector and Matrix dimensions (GrB_INDEX_MAX + 1)
"Matrix",
"Recorder",
"Scalar",
"Vector",
"agg",
"binary",
"core",
"dtypes",
"exceptions",
"indexunary",
"io",
"monoid",
"op",
"select",
"semiring",
"ss",
"unary",
"viz",
}
def __getattr__(name):
"""Auto-initialize if special attrs used without explicit init call by user."""
if name in _SPECIAL_ATTRS:
if _init_params is None:
_init("suitesparse", None, automatic=True)
# _init("suitesparse-vanilla", None, automatic=True)
if name == "ss" and backend != "suitesparse":
raise AttributeError(
f'module {__name__!r} only has attribute "ss" when backend is "suitesparse"'
)
if name not in globals():
if f"graphblas.{name}" in _sys.modules:
globals()[name] = _sys.modules[f"graphblas.{name}"]
else:
_load(name)
return globals()[name]
if name == "_autoinit":
if _init_params is None:
_init("suitesparse", None, automatic=True)
return
if name == "__version__":
from importlib.metadata import version
try:
return globals().setdefault("__version__", version("python-graphblas"))
except Exception as exc: # pragma: no cover (safety)
raise AttributeError(
"`graphblas.__version__` not available. This may mean python-graphblas was "
"incorrectly installed or not installed at all. For local development, you may "
"want to do an editable install via `python -m pip install -e path/to/graphblas`."
) from exc
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
def __dir__():
names = globals().keys() | _SPECIAL_ATTRS
if backend is not None and backend != "suitesparse":
names.remove("ss")
names.add("__version__")
return list(names)
def init(backend="suitesparse", blocking=False):
"""Initialize the chosen backend.
Parameters
----------
backend : str, one of {"suitesparse", "suitesparse-vanilla"}
blocking : bool
Whether to call GrB_init with GrB_BLOCKING or GrB_NONBLOCKING
"""
_init(backend, blocking)
def _init(backend_arg, blocking, automatic=False):
global _init_params, backend
passed_params = {"backend": backend_arg, "blocking": blocking, "automatic": automatic}
if _init_params is not None:
if blocking is None:
passed_params["blocking"] = _init_params["blocking"]
if _init_params != passed_params:
from .exceptions import GraphblasException
if _init_params.get("automatic"):
raise GraphblasException(
"graphblas objects accessed prior to manual initialization"
)
raise GraphblasException(
"graphblas initialized multiple times with different init parameters"
)
# Already initialized with these parameters; nothing more to do
return
backend = backend_arg
if backend in {"suitesparse", "suitesparse-vanilla"}:
try:
from suitesparse_graphblas import ffi, initialize, is_initialized, lib
except ImportError: # pragma: no cover (import)
raise ImportError(
f"suitesparse_graphblas is required for {backend!r} backend. "
"It may be installed with pip or conda:\n\n"
" $ pip install suitesparse-graphblas\n"
" $ conda install -c conda-forge python-suitesparse-graphblas\n\n"
"SuiteSparse:GraphBLAS is the primary C implementation and backend of "
"python-graphblas and is what we recommend to most users. If you are "
"installing python-graphblas with pip, we recommend installing with one "
"of the following to automatically include suitespare-graphblas:\n\n"
" $ pip install python-graphblas[suitesparse]\n"
" $ pip install python-graphblas[default]"
) from None
if is_initialized():
mode = ffi.new("int32_t*")
if lib.GxB_Global_Option_get_INT32(lib.GxB_MODE, mode) != 0:
raise RuntimeError("Could not get GraphBLAS mode") # pragma: no cover (safety)
is_blocking = mode[0] == lib.GrB_BLOCKING
if blocking is None:
passed_params["blocking"] = is_blocking
elif is_blocking != blocking:
raise RuntimeError(
f"GraphBLAS has already been initialized with `blocking={is_blocking}`"
)
else:
if blocking is None:
blocking = False
passed_params["blocking"] = blocking
initialize(blocking=blocking, memory_manager="numpy")
if backend == "suitesparse-vanilla":
# Exclude functions that start with GxB
class Lib:
pass
orig_lib = lib
lib = Lib()
for key, val in vars(orig_lib).items():
# TODO: handle GxB objects
if callable(val) and key.startswith("GxB") or "FC32" in key or "FC64" in key:
continue
setattr(lib, key, getattr(orig_lib, key))
for key in ["GxB_BACKWARDS", "GxB_STRIDE"]:
delattr(lib, key)
else:
raise ValueError(
f'Bad backend name. Must be "suitesparse" or "suitesparse-vanilla". Got: {backend}'
)
_init_params = passed_params
from . import core
core.ffi = ffi
core.lib = lib
core.NULL = ffi.NULL
# Ideally this is in operator.py, but lives here to avoid circular references
_STANDARD_OPERATOR_NAMES = set()
def _load(name):
if name in {"Matrix", "Vector", "Scalar", "Recorder"}:
module = _import_module(f".core.{name.lower()}", __name__)
globals()[name] = getattr(module, name)
elif name == "MAX_SIZE":
from .core import lib
globals()[name] = lib.GrB_INDEX_MAX + 1
else:
# Everything else is a module
globals()[name] = _import_module(f".{name}", __name__)
__all__ = [key for key in __dir__() if not key.startswith("_")]