跳转到主要内容

GraphBLAS的Python接口

项目描述

grblas

conda-forge pypi License Tests Docs Coverage Code style Binder

已弃用grblas 已重命名。请使用 python-graphblas (PyPI) (github)

GraphBLAS的Python包装器

安装,请使用 conda install -c conda-forge grblaspip install grblas。这将还会安装SuiteSparse graphblas 编译的C库。

目前与 SuiteSparse:GraphBLAS 兼容,但目标是使其与所有GraphBLAS规范的实现兼容。

本库采用的方法是尽可能遵循C-API规范,同时在允许的Python语法范围内进行改进。因为规范总是传递要写入的输出对象,所以我们遵循同样的方法,这与Python通常的操作方式非常不同。实际上,熟悉其他Python数据库(如numpy、pandas等)的人会发现,对于每次调用都不创建新对象感到很奇怪。

在最高层面,目标是把输出、掩码和累加器放在赋值运算符=的左侧,把计算放在右侧。不幸的是,这种方法并不总是与Python处理赋值的方式很好地结合,所以我们(滥用)左移<<的符号来赋予相同的赋值感觉。这打开了许多美好的可能性。

这是一个说明映射如何工作的例子

// C call
GrB_Matrix_mxm(M, mask, GrB_PLUS_INT64, GrB_MIN_PLUS_INT64, A, B, NULL)
# Python call
M(mask.V, accum=binary.plus) << A.mxm(B, semiring.min_plus)

右侧的表达式A.mxm(B)创建一个延迟对象,它不会进行计算。一旦它被用于与M一起的<<表达式中,整个表达式就会被翻译成等价的GraphBLAS调用。

延迟对象还有一个.new()方法,可以用来强制计算并返回一个新的对象。这很方便,而且通常很合适,但如果在循环中使用,将会创建许多不必要的对象。它还失去了使用现有结果进行累加的能力。为了获得最佳性能,遵循GraphBLAS的标准方法(1)在循环外创建对象和(2)在每次循环中重复使用对象,即使这并不符合Python的风格,也是一个更好的方法。

在适当元素上设置描述符标志,以保持逻辑与它所影响的内容紧密相关。以下是设置描述符位相同的调用。ttcsr表示转置第一和第二矩阵,补充掩码的结构,并在输出上进行替换。

// C call
GrB_Matrix_mxm(M, mask, GrB_PLUS_INT64, GrB_MIN_PLUS_INT64, A, B, desc.ttcsr)
# Python call
M(~mask.S, accum=binary.plus, replace=True) << A.T.mxm(B.T, semiring.min_plus)

接收标志操作的对象(如A.T、~mask等)也是延迟对象。它们保持状态但不进行计算,允许在单个GraphBLAS调用中设置正确的描述符位。

如果没有使用掩码或累加器,调用看起来像这样:

M << A.mxm(B, semiring.min_plus)

使用<<来表示更新实际上只是真实.update()方法的语法糖。上面的表达式可以写成

M.update(A.mxm(B, semiring.min_plus))

操作

M(mask, accum) << A.mxm(B, semiring)        # mxm
w(mask, accum) << A.mxv(v, semiring)        # mxv
w(mask, accum) << v.vxm(B, semiring)        # vxm
M(mask, accum) << A.ewise_add(B, binaryop)  # eWiseAdd
M(mask, accum) << A.ewise_mult(B, binaryop) # eWiseMult
M(mask, accum) << A.kronecker(B, binaryop)  # kronecker
M(mask, accum) << A.T                       # transpose

提取

M(mask, accum) << A[rows, cols]             # rows and cols are a list or a slice
w(mask, accum) << A[rows, col_index]        # extract column
w(mask, accum) << A[row_index, cols]        # extract row
s = A[row_index, col_index].value           # extract single element

分配

M(mask, accum)[rows, cols] << A             # rows and cols are a list or a slice
M(mask, accum)[rows, col_index] << v        # assign column
M(mask, accum)[row_index, cols] << v        # assign row
M(mask, accum)[rows, cols] << s             # assign scalar to many elements
M[row_index, col_index] << s                # assign scalar to single element
                                            # (mask and accum not allowed)
del M[row_index, col_index]                 # remove single element

应用

M(mask, accum) << A.apply(unaryop)
M(mask, accum) << A.apply(binaryop, left=s)   # bind-first
M(mask, accum) << A.apply(binaryop, right=s)  # bind-second

归约

v(mask, accum) << A.reduce_rowwise(op)      # reduce row-wise
v(mask, accum) << A.reduce_columnwise(op)   # reduce column-wise
s(accum) << A.reduce_scalar(op)
s(accum) << v.reduce(op)

创建新的向量/矩阵

A = Matrix.new(dtype, num_rows, num_cols)   # new_type
B = A.dup()                                 # dup
A = Matrix.from_values([row_indices], [col_indices], [values])  # build

从延迟创建

延迟对象可以用于使用.new()方法创建新对象

C = A.mxm(B, semiring).new()

属性

size = v.size                               # size
nrows = M.nrows                             # nrows
ncols = M.ncols                             # ncols
nvals = M.nvals                             # nvals
rindices, cindices, vals = M.to_values()    # extractTuples

初始化

存在一种机制,可以在使用之前使用上下文初始化grblas。这允许设置要使用的后端以及阻塞/非阻塞模式。如果没有初始化上下文,将自动执行默认初始化。

import grblas as gb
# Context initialization must happen before any other imports
gb.init('suitesparse', blocking=True)

# Now we can import other items from grblas
from grblas import binary, semiring
from grblas import Matrix, Vector, Scalar

高效的用户定义函数

grblas需要numba,这可以使用户定义的Python函数编译成原生C,以便在GraphBLAS中使用。

示例自定义一元运算符

from grblas import unary
from grblas.operator import UnaryOp

def force_odd_func(x):
    if x % 2 == 0:
        return x + 1
    return x

UnaryOp.register_new('force_odd', force_odd_func)

v = Vector.from_values([0, 1, 3], [1, 2, 3])
w = v.apply(unary.force_odd).new()
w  # indexes=[0, 1, 3], values=[1, 3, 3]

类似的函数也存在于二元运算符、幺半群和半环。

导入/导出连接到Python生态系统

grblas.io包含用于转换到和从的功能

import grblas as gb

# numpy arrays
# 1-D array becomes Vector, 2-D array becomes Matrix
A = gb.io.from_numpy(m)
m = gb.io.to_numpy(A)

# scipy.sparse matrices
A = gb.io.from_scipy_sparse_matrix(m)
m = gb.io.to_scipy_sparse_matrix(m, format='csr')

# networkx graphs
A = gb.io.from_networkx(g)
g = gb.io.to_networkx(A)

归属

这个库借鉴了pygraphblas的一些优秀想法,特别是在解析SuiteSparse运算符名称和关于标量(后端实现不需要了解)的概念。

支持者

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误日志 StatusPage StatusPage 状态页面