Skip to content

Commit 0c6c0ef

Browse files
committed
Add Cython utils needed for zero-copy import and export.
1 parent 620bf5d commit 0c6c0ef

File tree

9 files changed

+187
-4
lines changed

9 files changed

+187
-4
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@
186186
same "printed page" as the copyright notice for easier
187187
identification within third-party archives.
188188

189-
Copyright [yyyy] [name of copyright owner]
189+
Copyright 2021 Anaconda Inc., Graphegon, and contributors
190190

191191
Licensed under the Apache License, Version 2.0 (the "License");
192192
you may not use this file except in compliance with the License.

MANIFEST.in

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
include setup.py
2+
include README.md
3+
include LICENSE
4+
include suitesparse_graphblas/*.pxd
5+
include suitesparse_graphblas/*.pyx
6+
include suitesparse_graphblas/*.h

pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[build-system]
2+
requires = ["setuptools", "wheel", "numpy >= 1.15"]
3+
4+
[tool.black]
5+
line-length = 100

setup.cfg

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[aliases]
2+
test=pytest
3+
4+
[flake8]
5+
max-line-length = 100
6+
exclude =
7+
versioneer.py,
8+
grblas/tests/test_formatting.py,
9+
ignore =
10+
E203, # whitespace before ':'
11+
E231, # Multiple spaces around ","
12+
W503, # line break before binary operator
13+
14+
[tool:pytest]
15+
testpaths = suitesparse_graphblas/tests
16+

setup.py

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,66 @@
1-
from setuptools import setup, find_packages
1+
from setuptools import setup, find_packages, Extension
2+
from glob import glob
3+
try:
4+
from Cython.Build import cythonize
5+
from Cython.Compiler.Options import get_directive_defaults
6+
7+
use_cython = True
8+
except ImportError:
9+
use_cython = False
10+
import numpy as np
11+
import os
12+
import sys
13+
14+
define_macros = [("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")]
15+
16+
if use_cython:
17+
suffix = ".pyx"
18+
directive_defaults = get_directive_defaults()
19+
directive_defaults["binding"] = True
20+
directive_defaults["language_level"] = 3
21+
if os.environ.get("CYTHON_COVERAGE"):
22+
directive_defaults["linetrace"] = True
23+
define_macros.append(("CYTHON_TRACE_NOGIL", "1"))
24+
else:
25+
suffix = ".c"
26+
27+
include_dirs = [np.get_include(), os.path.join(sys.prefix, "include")]
28+
ext_modules = [
29+
Extension(
30+
name[: -len(suffix)].replace("/", ".").replace("\\", "."),
31+
[name],
32+
include_dirs=include_dirs,
33+
define_macros=define_macros,
34+
)
35+
for name in glob(f"suitesparse_graphblas/**/*{suffix}", recursive=True)
36+
]
37+
if use_cython:
38+
ext_modules = cythonize(ext_modules, include_path=include_dirs)
39+
40+
with open("README.md") as f:
41+
long_description = f.read()
42+
43+
package_data = {"suitesparse_graphblas": ["*.pyx", "*.pxd", "*.h"]}
44+
if sys.platform == "win32":
45+
package_data["suitesparse_graphblas"].append("*.dll")
246

347
setup(
448
name='suitesparse-graphblas',
549
version='4.0.3',
650
description='SuiteSparse:GraphBLAS Python bindings.',
51+
long_description=long_description,
52+
long_description_content_type="text/markdown",
753
packages=find_packages(),
854
author='Michel Pelletier, James Kitchen, Erik Welch',
55+
56+
url="https://github.com/GraphBLAS/python-suitesparse-graphblas",
57+
ext_modules=ext_modules,
958
cffi_modules=["suitesparse_graphblas/build.py:ffibuilder"],
10-
install_requires=["cffi>=1.0.0"],
59+
python_requires=">=3.7",
60+
install_requires=["cffi>=1.0.0", "numpy>=1.15"],
1161
setup_requires=["cffi>=1.0.0", "pytest-runner"],
1262
tests_require=["pytest"],
63+
license="Apache License 2.0",
64+
package_data=package_data,
65+
include_package_data=True,
1366
)
14-

suitesparse_graphblas/tests/__init__.py

Whitespace-only changes.
File renamed without changes.

suitesparse_graphblas/utils.pxd

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from numpy cimport ndarray
2+
from libc.stdint cimport uint64_t
3+
4+
cdef extern from "numpy/arrayobject.h" nogil:
5+
# These aren't public (i.e., "extern"), but other projects use them too
6+
void *PyDataMem_NEW(size_t)
7+
void *PyDataMem_NEW_ZEROED(size_t, size_t)
8+
void *PyDataMem_RENEW(void *, size_t)
9+
void PyDataMem_FREE(void *)
10+
# These are available in newer Cython versions
11+
void PyArray_ENABLEFLAGS(ndarray, int flags)
12+
void PyArray_CLEARFLAGS(ndarray, int flags)
13+
14+
ctypedef enum GrB_Mode:
15+
GrB_NONBLOCKING
16+
GrB_BLOCKING
17+
18+
ctypedef uint64_t (*GxB_init)(
19+
GrB_Mode,
20+
void *(*user_malloc_function)(size_t),
21+
void *(*user_calloc_function)(size_t, size_t),
22+
void *(*user_realloc_function)(void *, size_t),
23+
void (*user_free_function)(void *),
24+
bint, # user_malloc_is_thread_safe
25+
)
26+
27+
cpdef int call_gxb_init(ffi, lib, int mode)
28+
29+
cpdef ndarray claim_buffer(ffi, cdata, size_t size, dtype)
30+
31+
cpdef ndarray claim_buffer_2d(ffi, cdata, size_t cdata_size, size_t nrows, size_t ncols, dtype, bint is_c_order)
32+
33+
cpdef unclaim_buffer(ndarray array)
34+

suitesparse_graphblas/utils.pyx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import numpy as np
2+
from numpy cimport (
3+
import_array, ndarray, npy_intp,
4+
PyArray_SimpleNewFromData, PyArray_New,
5+
NPY_ARRAY_OWNDATA, NPY_ARRAY_WRITEABLE, NPY_ARRAY_F_CONTIGUOUS,
6+
)
7+
from libc.stdint cimport uintptr_t
8+
9+
import_array()
10+
11+
cpdef int call_gxb_init(ffi, lib, int mode):
12+
# We need to call `GxB_init`, but we didn't compile Cython against GraphBLAS. So, we get it from cffi.
13+
# Step 1: ffi.addressof(lib, "GxB_init")
14+
# Return type: cffi.cdata object of a function pointer. Can't cast to int.
15+
# Step 2: ffi.cast("uintptr_t", ...)
16+
# Return type: cffi.cdata object of a uintptr_t type, an unsigned pointer. Can cast to int.
17+
# Step 3: int(...)
18+
# Return type: int. The physical address of the function.
19+
# Step 4: <uintptr_t>(...)
20+
# Return type: uintptr_t in Cython. Cast Python int to Cython integer for pointers.
21+
# Step 5: <GsB_init>(...)
22+
# Return: function pointer in Cython!
23+
24+
cdef GxB_init func = <GxB_init><uintptr_t>int(ffi.cast("uintptr_t", ffi.addressof(lib, "GxB_init")))
25+
return func(<GrB_Mode>mode, PyDataMem_NEW, PyDataMem_NEW_ZEROED, PyDataMem_RENEW, PyDataMem_FREE, True)
26+
27+
28+
cpdef ndarray claim_buffer(ffi, cdata, size_t size, dtype):
29+
cdef:
30+
npy_intp dims = size
31+
uintptr_t ptr = int(ffi.cast("uintptr_t", cdata))
32+
ndarray array = PyArray_SimpleNewFromData(1, &dims, dtype.num, <void*>ptr)
33+
PyArray_ENABLEFLAGS(array, NPY_ARRAY_OWNDATA)
34+
return array
35+
36+
37+
cpdef ndarray claim_buffer_2d(ffi, cdata, size_t cdata_size, size_t nrows, size_t ncols, dtype, bint is_c_order):
38+
cdef:
39+
size_t size = nrows * ncols
40+
ndarray array
41+
uintptr_t ptr
42+
npy_intp dims[2]
43+
if cdata_size == size:
44+
ptr = int(ffi.cast("uintptr_t", cdata))
45+
dims[0] = nrows
46+
dims[1] = ncols
47+
if is_c_order:
48+
array = PyArray_SimpleNewFromData(2, dims, dtype.num, <void*>ptr)
49+
else:
50+
array = PyArray_New(
51+
ndarray, 2, dims, dtype.num, NULL, <void*>ptr, -1,
52+
NPY_ARRAY_F_CONTIGUOUS | NPY_ARRAY_WRITEABLE, <object>NULL
53+
)
54+
PyArray_ENABLEFLAGS(array, NPY_ARRAY_OWNDATA)
55+
elif cdata_size > size: # pragma: no cover
56+
array = claim_buffer(ffi, cdata, cdata_size, dtype)
57+
if is_c_order:
58+
array = array[:size].reshape((nrows, ncols))
59+
else:
60+
array = array[:size].reshape((ncols, nrows)).T
61+
else: # pragma: no cover
62+
raise ValueError(
63+
f"Buffer size too small: {cdata_size}. "
64+
f"Unable to create matrix of size {nrows}x{ncols} = {size}"
65+
)
66+
return array
67+
68+
69+
cpdef unclaim_buffer(ndarray array):
70+
PyArray_CLEARFLAGS(array, NPY_ARRAY_OWNDATA | NPY_ARRAY_WRITEABLE)

0 commit comments

Comments
 (0)