计算有多少个矩阵对于所有子矩阵都具有满秩

12

我想计算有多少个m行n列的矩阵,其中元素为1或-1,并且满足所有floor(m/2)+1 by n子矩阵都是满秩的。 我目前的方法很朴素且速度较慢,以下是使用Python / numpy编写的代码。 它只是遍历所有矩阵并测试所有子矩阵。

import numpy as np
import itertools
from scipy.misc import comb

m = 8
n = 4

rowstochoose = int(np.floor(m/2)+1)

maxnumber = comb(m, rowstochoose, exact = True)

matrix_g=(np.array(x).reshape(m,n) for x in itertools.product([-1,1], repeat = m*n))

nofound = 0
for A in matrix_g:
    count = 0
    for rows in itertools.combinations(range(m), int(rowstochoose)):
       if (np.linalg.matrix_rank(A[list(rows)]) == int(min(n,rowstochoose))):
           count+=1
       else:
           break
    if (count == maxnumber):
         nofound+=1   
print nofound, 2**(m*n)

有没有更好/更快的方法来做这件事?我想计算n和m都小于等于20的情况,但如果有任何显著的改进都是好的。

上下文。 我对获取一些精确解 https://math.stackexchange.com/questions/640780/probability-that-every-vector-is-not-orthogonal-to-half-of-the-others 感兴趣。


作为用来比较实现的数据点。n,m = 4,4 应该输出26880。对于n,m = 5,5,它运行得太慢了。对于n = 2和m = 2,3,4,5,6,输出应该是8、0、96、0、1280


当前状态 Feb 2, 2014:

  • leewangzhong的答案很快,但m > n时不正确。leewangzhong正在考虑如何修复它。
  • Hooked的答案对于m > n无法运行。

@Teepeemm 很好的观点。谢谢。 - marshall
1
之前你说你需要这个用于模拟,但对我来说这没有任何意义。不过,无论如何,如果你能告诉我们更多关于你实际想要实现的目标,我们或许可以更快地找到问题的核心。 - Eelco Hoogendoorn
@EelcoHoogendoorn 我在这里的兴趣实际上是针对链接的数学问题本身。我不知道如何得到它的精确答案,如果你看一下答案,它也只是近似的。我希望能够计算出一些小n和m的精确答案。 - marshall
1
你应该将排名与 min(n, rowstochoose) 进行比较,一个 JxK 矩阵的最大可能排名是 min(J, K),我认为这就是你所说的“满秩”。 - Tim Peters
你有没有注意到 matrix_g 生成了2^32个元素? - leewz
显示剩余8条评论
4个回答

3

(现在是对于n = m//2+1的部分解决方案和所需代码)

令k := m//2+1

这有点相当于问:"有多少个集合包含m个n维{-1,1}向量,且其中没有大小为min(k,n)的线性相关组?"

对于那些矩阵,我们知道或可以假设:

  • 每个向量的第一个条目都为1(如果不是,则将整个向量乘以-1)。这将使计数减少2 ** m倍。
  • 列表中的所有向量都是唯一的(如果不是,则具有两个相同向量的任何子矩阵都具有非满秩)。这消除了很多问题。有choose(2 ** m,n)个不同向量的矩阵。
  • 向量列表按字典顺序排序(排名不受排列影响)。因此,我们实际上是在思考向量而不是列表的集合。这将使计数减少m!倍(因为我们要求不同)。

有了这个,我们就有了n=4,m = 8的解决方案。只有八个不同的向量具有第一个条目是正数的属性。有一个由8个不同向量组成的排列(排序列表)。

array([[ 1,  1,  1,  1],
       [ 1,  1,  1, -1],
       [ 1,  1, -1,  1],
       [ 1,  1, -1, -1],
       [ 1, -1,  1,  1],
       [ 1, -1,  1, -1],
       [ 1, -1, -1,  1],
       [ 1, -1, -1, -1]], dtype=int8)

这个列表中有100种大小为4的组合其排名为3。因此,符合条件的矩阵数量为0。


对于更一般的解决方案:

请注意,有2 ** (n-1)个第一个坐标为-1的向量,以及choose(2 ** (n-1),m)个要检查的矩阵。对于n=8和m=8,有128个向量和1.4297027e + 12个矩阵。回答“对于i=1,...,k,有多少组合的排名为i?”可能会有所帮助。

或者,“什么样的矩阵(在上述假设下)具有不到满秩?”我认为答案是准确的,一个充分的条件是,“两列相互倍增”。 我感觉这是正确的,并且我已经测试了所有4x4、5x5和6x6矩阵。(可能测试出现问题了)由于选择了第一列为齐次列,并且所有的齐次向量都是相互倍增的,任何具有除第一列外其他齐次列的k大小的子矩阵的排名将小于k。

尽管如此,这并不是必要条件。下面的矩阵是奇异的(第一列加上第四列等于第三列加上第二列)。

array([[ 1,  1,  1,  1,  1],
       [ 1,  1,  1,  1, -1],
       [ 1,  1, -1, -1,  1],
       [ 1,  1, -1, -1, -1],
       [ 1, -1,  1, -1,  1]], dtype=int8)

由于只有两个可能的值(-1和1),所有当m>2, k := m//2+1, n = k且第一列为-1的mxn矩阵都有每列的多数成员(即至少有k个成员相同)。所以对于n=k,答案为0。


对于n<=8,以下是生成向量的代码。

from numpy import unpackbits, arange, uint8, int8

#all distinct n-length vectors from -1,1 with first entry -1
def nvectors(n):
    if n > 8:
        raise ValueError #is that the right error?
    return -1 + 2 * (
        #explode binary numbers to arrays of 8 zeroes and ones
        unpackbits(arange(2**(n-1),dtype=uint8)) #unpackbits only takes uint
            .reshape((-1,8)) #unpackbits flattens, so we need to shape it to 8 bits
            [:,-n:] #only take the last n bytes
            .view(int8) #need signed
    )

矩阵生成器:
#generate all length-m matrices that are combinations of distinct n-vectors
def matrix_g(n,m):
    return (array(mat) for mat in combinations(nvectors(n),m))

下面是一个检查所有长度为maxrank的子矩阵是否具有满秩的函数。如果任何子矩阵的秩小于maxrank,它将停止运行,而不是检查所有组合。
rankof = np.linalg.matrix_rank
#all submatrices of at least half size have maxrank
#(we only need to check the maxrank-sized matrices)
def halfrank(matrix,maxrank):
return all(rankof(submatr) == maxrank for submatr in combinations(matrix,maxrank))

生成全部半矩阵均为满秩的矩阵
def nicematrices(m,n): 最大秩数 = min(m//2+1,n) return (matr for matr in matrix_g(n,m) if halfrank(matr,最大秩数))

将所有内容组合起来:

import numpy as np
from numpy import unpackbits, arange, uint8, int8, array
from itertools import combinations

#all distinct n-length vectors from -1,1 with first entry -1
def nvectors(n):
    if n > 8:
        raise ValueError #is that the right error?
    if n==0:
        return array([])
    return -1 + 2 * (
        #explode binary numbers to arrays of 8 zeroes and ones
        unpackbits(arange(2**(n-1),dtype=uint8)) #unpackbits only takes uint
            .reshape((-1,8)) #unpackbits flattens, so we need to shape it to 8 bits
            [:,-n:] #only take the last n bytes
            .view(int8) #need signed
    )

#generate all length-m matrices that are combinations of distinct n-vectors
def matrix_g(n,m):
    return (array(mat) for mat in combinations(nvectors(n),m))

rankof = np.linalg.matrix_rank
#all submatrices of at least half size have maxrank
#(we only need to check the maxrank-sized matrices)
def halfrank(matrix,maxrank):
    return all(rankof(submatr) == maxrank for submatr in combinations(matrix,maxrank))

#generate all matrices that have all half-matrices with full rank
def nicematrices(m,n):
    maxrank = min(m//2+1,n)
    return (matr for matr in matrix_g(n,m) if halfrank(matr,maxrank))

#returns (number of nice matrices, number of all matrices)
def count_nicematrices(m,n):
    from math import factorial
    return (len(list(nicematrices(m,n)))*factorial(m)*2**m, 2**(m*n))

for i in range(0,6):
    print (i, count_nicematrices(i,i))

count_nicematrices(5,5) 大约需要15秒钟,其中绝大部分时间都被 matrix_rank 函数占用。


我删除了关于8x4情况的这行文字:“该矩阵具有完全秩,因此每个子矩阵都必须具有完全秩。” 这是错误的。 - leewz
解决 n >= m//2+1 的问题。 - leewz
我觉得我可能有点迟钝,但为什么这显然是错误的? - marshall
我的回答指出 n>=m//2+1 没有解,但实际上我数了更多的解。 - leewz
我们需要每组6/2+1=4行都具有等级2。[[-1 -1] [-1 -1] [-1 -1] [-1 1] [-1 1] [-1 1]] - marshall
显示剩余6条评论

2

您需要从数学角度重新思考这个问题。即使使用暴力方法,也可以使用一些编程技巧来加快处理速度(因为SO是一个编程网站)。小技巧,如不重新计算int(min(n,rowstochoose))itertools.combinations(range(m), int(rowstochoose)),可以节省几个百分点,但真正的收益来自于记忆化。其他人已经提到了它,但我认为有一个完整、可工作的代码示例可能会有用:

import numpy as np
from scipy.misc import comb
import itertools, hashlib

m,n = 4,4

rowstochoose = int(np.floor(m/2)+1)
maxnumber    = comb(m, rowstochoose, exact = True)
combo_itr    = (x for x in itertools.product([-1,1], repeat = m*n))
matrix_itr   = (np.array(x,dtype=np.int8).reshape((n,m)) for x in combo_itr)

sub_shapes = map(list,(itertools.combinations(range(m), int(rowstochoose))))
required_rank = int(min(n,rowstochoose))

memo = {}

no_found = 0
for A in matrix_itr:
    check = True
    for s in sub_shapes:
        view  = A[s].view(np.int8)
        h    = hashlib.sha1(view).hexdigest()

        if h not in memo: 
            memo[h] =  np.linalg.matrix_rank(view)

        if memo[h] != required_rank:
            check = False
            break
    if check: no_found+=1   

print no_found, 2**(m*n)

这将为4x4的情况提供近10倍的速度增益-如果您愿意等待足够长的时间,那么对于更大的矩阵,您将看到显着的改进。对于更大的矩阵,秩的成本比例上升得更多,因此可以提前在哈希上对矩阵进行排序:

idx  = np.lexsort(view.T)
h    = hashlib.sha1(view[idx]).hexdigest()

对于4x4的情况,这使得情况稍微变差了一点,但我预计在5x5的情况下会反转。


通常将linalg.matrix_rank替换为linalg.det会使计算结果有很大的不同,因为计算矩阵行列式比计算矩阵秩要快得多。然而,在这里我不知道如何做到这一点。 - marshall
@marshall 这要求您拥有一个方阵 - 对于您的问题来说,这通常不是真的。 - Hooked
1
@marshall 到目前为止,你最好的选择似乎是将记忆化和 leewangzhong 的答案生成的缩小矩阵集合相结合。 - Hooked

2
由于还没有人回答,这里提供一个不带代码的答案。我看到了一些有用的对称性,如下所示。
  1. 将一行乘以-1。
  2. 将一列乘以-1。
  3. 置换行。
  4. 置换列。
我会通过 穷举生成非同构矩阵,进行过滤,并计算它们轨道大小的总和来解决这个问题。在第一步和第三步中,nauty 将非常有用。假设大多数矩阵都具有较少的对称性(对于大的 n 肯定是一个很好的假设,但先验地不明显),我预计 8x8 可行,9x9 边缘,10x10 不可行。
扩展伪代码:
将(m-1)乘(n-1)的0-1矩阵轨道中的一个代表生成,该轨道由行和列置换生成,以及轨道大小(=(m-1)!(n-1)!/自同构群的大小)。也许Tim链接的论文的作者愿意分享他的代码;否则,请参见下文。对于每个矩阵,请将x的条目替换为(-1)^ x。添加一个1行和一列。将其轨道大小乘以2^(m+n-1)。这解决了符号更改对称性的问题。过滤矩阵并求出剩余矩阵的轨道大小之和。您可以通过自己实现Gram-Schmidt来节省一些计算时间,以便在按字典顺序尝试所有组合时,有机会重用共享前缀的部分结果。
无同构枚举:
麦凯模板可用于从m x n的0-1矩阵的代表中生成(m + 1) x n的0-1矩阵的代表,这种方法适合深度优先搜索。对于每个m x n的0-1矩阵,将其与一个包含m个黑点和n个白点的二分图相关联,并为每个1条目添加适当的边缘。对于每个m x n的代表,执行以下步骤:
  1. 对于每个长度为n的向量,构造由代表和新向量组成的(m + 1) x n矩阵的图,并运行nauty以获得规范标记和顶点轨道。
  2. 过滤掉新向量对应的顶点与编号最低的黑点不在同一轨道上的可能性。
  3. 过滤掉重复的规范标记。
nauty还计算自同构群的阶数。

1
相比之下,小型(0,1)矩阵的分类详细介绍了用于各种方式对(0,1)NxN矩阵进行分类的算法,这是一个“简单”(搜索空间较小)的任务。8x8对作者来说是可行的,但需要在5台PC上分散计算一个月;他估计完成9x9需要1000倍的时间。不过,他所做的分类看起来比这个更难。无论如何,这并不是一个真正的编程问题 - 这里真正需要的是深入的分析。 - Tim Peters
@TimPeters 翻转符号有效地为我们提供了一个 "免费" 的行和一列,因此这个问题的8x8情况对应于那个问题的7x7情况。 - David Eisenstat
另一方面,非奇异的 NxN {-1, 1} 矩阵的数量是非奇异的 (N-1)x(N-1) {0, 1} 矩阵的 2**(2*N-1) 倍。无论如何,它们都有很多;-) 参见"Number of nonsingular n X n (-1,1)-matrices." - Tim Peters
谢谢你的回答。你能扩展一下伪代码,这样别人就可以尝试实现你的想法了吗? - marshall
1
@marshall 我现在尽可能地扩展了这个内容。虽然它有点棘手,但它适用于这个问题、你的其他问题以及未来可能遇到的其他问题。 - David Eisenstat
@TimPeters 我认为我们在互相说过去了。是的,有相当多的7x7矩阵具有0-1条目。我不打算枚举它们中的所有内容,只是每个对称群轨道的代表,这应该约有一亿个。我上次提出的包含排除技巧看起来不太有前途,因为列子空间的数量将会更加庞大。 - David Eisenstat

1

算法1 - 记忆小的矩阵

我会使用已经检查过的较小矩阵的记忆。

你可以简单地以二进制格式(-1为0,1为1)记录所有较小的矩阵。顺便提一下,你可以直接检查(0和1)范围的矩阵而不是(-1和1) - 这是相同的。我们称这些编码为图像。使用long类型,你可以拥有最多64个单元的矩阵,因此,最多8x8。 它很快。使用String,您可以拥有任意大小的矩阵。实际上,8x8已经足够了 - 在8GB内存中,我们可以放置1G longs。这大约是2 ^ 30,因此,您可以记住大约25-28个元素的矩阵。

对于每个大小,您将拥有一组图像:

对于2x2:1001、0110、1000、0100、0010、0001、0111、1011、1101、1110。

所以,您将拥有archive= NxN 的数组,其中每个元素都是好矩阵的二进制图像的有序列表。 -(对于矩阵大小为MxN,其中M≥N,适当的存档位置将具有坐标M,N。如果M < N,则应将其旋转90度)。
  • 当您检查新的大矩阵时,请将其分成小矩阵
  • 对于每个小矩阵T
    • 如果T的适当位置在存档中没有列表,请创建它并填充所有T大小和顺序图像的全秩矩阵的图像。如果内存不足,请停止存档填充过程。
    • 如果根据大小可以将T存档:
      • 制作T的图像
      • 在列表中查找图像(t)-如果在其中,则可以,否则应该丢弃大矩阵。
    • 如果T太大无法存档,则按照您所做的方式进行检查。

算法2-增加大小

另一种可能性是通过向已发现的较小矩阵添加片段来创建更大的矩阵。 您应该决定矩阵将增长到什么大小。

当您找到大小为MxN的“正确”矩阵时,请尝试添加一行以使其顶部。新矩阵应仅针对包括新行的子矩阵进行检查。 新列也是如此。

您应该设置确切的算法,哪些大小是从哪些大小派生的。因此,您可以将记住的矩阵数量最小化。我考虑过那个序列:

  • 从2x2矩阵开始。
  • 继续使用3x2
  • 4x2,3x3
  • 5x2,4x3
  • 6x2,5x3,4x4
  • 7x2,6x3,5x4
  • ...

因此,您只能记住(M + N) / 2-1个矩阵以在大小MxN中搜索。

如果每次我们可以从两个旧矩阵创建新大小时,我们从更多的正方形中派生,我们还可以大大节省记忆矩阵的空间:对于“长”矩阵如7x2,我们只需要记住并检查最后一行1x2。对于6x3矩阵,我们应该记住它们的2x3桩,依此类推。

此外,您不需要记住最大的矩阵-您将不会将它们用于进一步计数。

再次使用“图像”来记住矩阵。


我觉得这个答案有点令人困惑。如果您能够明确展示一些适用于小n和m的代码,那么我可以测试它以查看它是否给出了我期望的答案。 - marshall

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接