在 GF(2) 上快速计算矩阵秩

Fast computation of matrix rank over GF(2)

对于我当前的项目,我需要能够使用 GF(2) 中的条目计算 64*64 矩阵的秩。不知道有没有人有好的解决办法

我一直在为此使用 pyfinite,但它相当慢,因为它是一个纯粹的 python 实现。我也尝试过对我一直在使用的代码进行 cythonise,但由于依赖 pyfinite 而遇到了问题。

我的下一个想法是用 cython 编写我自己的 class,但这对我需要的东西来说似乎有点过分了。

我需要以下功能

matrix = GF2Matrix(size=64) # creating a 64*64 matrix
matrix.setRow(i, [1,0,1....,1]) # set row using list
matrix += matrix2 # addition of matrices
rank(matrix) # then computing the rank

感谢任何想法。

在 GF(2) 上有效表示矩阵的一种方法是将行存储为整数,将每个整数解释为 bit-string。例如,4×4 矩阵

[0 1 1 0]
[1 0 1 1]
[0 0 1 0]
[1 0 0 1]

(排名 3)可以表示为整数列表 [6, 13, 4, 9]。在这里,我认为第一列对应于整数的最低有效位,最后一列对应于最高有效位,但反向约定也可以。

通过这种表示,可以使用 Python 的按位整数运算高效地执行行运算:^ 用于加法,& 用于乘法。 然后您可以使用标准高斯消去法计算排名。

这是一些相当高效的代码。给定一个 rows 表示上述矩阵的非负整数集合,我们重复删除列表中的最后一行,然后使用该行从对应于其最低有效位的列中删除所有 1 条目.如果该行为零,则它没有最低有效位并且不影响排名,因此我们简单地丢弃它并继续前进。

def gf2_rank(rows):
    """
    Find rank of a matrix over GF2.

    The rows of the matrix are given as nonnegative integers, thought
    of as bit-strings.

    This function modifies the input list. Use gf2_rank(rows.copy())
    instead of gf2_rank(rows) to avoid modifying rows.
    """
    rank = 0
    while rows:
        pivot_row = rows.pop()
        if pivot_row:
            rank += 1
            lsb = pivot_row & -pivot_row
            for index, row in enumerate(rows):
                if row & lsb:
                    rows[index] = row ^ pivot_row
    return rank

让我们 运行 GF2 上随机 64×64 矩阵的一些计时。 random_matrices 是一个创建随机 64×64 矩阵集合的函数:

import random

def random_matrix():
    return [random.getrandbits(64) for row in range(64)]

def random_matrices(count):
    return [random_matrix() for _ in range(count)]

这是时间代码:

import timeit

count = 1000
number = 10
timer = timeit.Timer(
    setup="ms = random_matrices({})".format(count),
    stmt="[gf2_rank(m.copy()) for m in ms]",
    globals=globals())
print(min(timer.repeat(number=number)) / count / number)

我的机器(2.7 GHz Intel Core i7,macOS 10.14.5,Python 3.7)上打印的结果是 0.0001984686384,因此对于单秩计算来说,这是不到 200µs 的触摸。

200µs 对于纯 Python 排名计算来说是相当可观的,但如果这不够快,我们可以按照您的建议使用 Cython。这是一个 Cython 函数,它采用 dtype np.uint64 的 1d NumPy 数组,再次将数组的每个元素视为 GF2 上的 64×64 矩阵的一行,以及 returns 的等级矩阵.

# cython: language_level=3, boundscheck=False

from libc.stdint cimport uint64_t, int64_t

def gf2_rank(uint64_t[:] rows):
    """
    Find rank of a matrix over GF2.

    The matrix can have no more than 64 columns, and is represented
    as a 1d NumPy array of dtype `np.uint64`. As before, each integer
    in the array is thought of as a bit-string to give a row of the
    matrix over GF2.

    This function modifies the input array.
    """
    cdef size_t i, j, nrows, rank
    cdef uint64_t pivot_row, row, lsb

    nrows = rows.shape[0]

    rank = 0
    for i in range(nrows):
        pivot_row = rows[i]
        if pivot_row:
            rank += 1
            lsb = pivot_row & -pivot_row
            for j in range(i + 1, nrows):
                row = rows[j]
                if row & lsb:
                    rows[j] = row ^ pivot_row

    return rank

运行 64×64 矩阵的等效时间,现在表示为 dtype np.uint64 和形状 (64,) 的 NumPy 数组,我得到平均 rank-computation 时间7.56µs,比纯 Python 版本快 25 倍以上。

我编写了一个 Python 程序包 galois,它在 Galois 域上扩展了 NumPy 数组。 Galois 域矩阵上的线性代数是预期用例之一。它是用 Python 编写的,但使用 Numba 进行 JIT 编译以提高速度。它非常快,而且大多数线性代数例程也已编译。 (一个例外,截至 08/11/2021,行缩减例程尚未经过 JIT 编译,但可以添加。)

这是一个使用 galois 库来完成您所描述内容的示例。

创建一个GF(2)数组class并创建一个显式数组和一个随机数组。

In [1]: import numpy as np                                                                                                                                                                     

In [2]: import galois                                                                                                                                                                          

In [3]: GF = galois.GF(2)                                                                                                                                                                      

In [4]: A = GF([[0, 0, 1, 0], [0, 1, 1, 1], [1, 0, 1, 0], [1, 0, 1, 0]]); A                                                                                                                                                                
Out[4]: 
GF([[0, 0, 1, 0],
    [0, 1, 1, 1],
    [1, 0, 1, 0],
    [1, 0, 1, 0]], order=2)

In [5]: B = GF.Random((4,4)); B                                                                                                                                                                
Out[5]: 
GF([[1, 1, 1, 0],
    [1, 1, 1, 0],
    [1, 1, 0, 0],
    [0, 0, 1, 0]], order=2)

您可以像这样更新整行(按照您的要求)。

In [6]: B[0,:] = [1,0,0,0]; B                                                                                                                                                                  
Out[6]: 
GF([[1, 0, 0, 0],
    [1, 1, 1, 0],
    [1, 1, 0, 0],
    [0, 0, 1, 0]], order=2)

矩阵运算适用于普通的二元运算符。这里是矩阵加法和矩阵乘法。

In [7]: A + B                                                                                                                                                                                  
Out[7]: 
GF([[1, 0, 1, 0],
    [1, 0, 0, 1],
    [0, 1, 1, 0],
    [1, 0, 0, 0]], order=2)

In [8]: A @ B                                                                                                                                                                                  
Out[8]: 
GF([[1, 1, 0, 0],
    [0, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 1, 0, 0]], order=2)

NumPy 数组中增加了一种方法,称为 row_reduce(),它对矩阵执行高斯消去法。您还可以在 Galois 域数组上调用标准 NumPy 线性代数函数并获得正确的结果。

In [9]: A.row_reduce()                                                                                                                                                                         
Out[9]: 
GF([[1, 0, 0, 0],
    [0, 1, 0, 1],
    [0, 0, 1, 0],
    [0, 0, 0, 0]], order=2)

In [10]: np.linalg.matrix_rank(A)                                                                                                                                                              
Out[10]: 3

希望对您有所帮助!如果需要其他功能,请在 GitHub.

上提出问题