整数 NxN 矩阵的精确行列式
Precise determinant of integer NxN matrix
行列式定义只有加法、减法和乘法。所以具有整数元素的矩阵的行列式必须是整数。
但是numpy.linalg.det()
returns一个“稍微偏离”的浮点数:
>>> import numpy
>>> M = [[-1 if i==j else 1 for j in range(7)] for i in range(7)]
>>> numpy.linalg.det(M)
319.99999999999994
对于更大的矩阵,情况会变得更糟:
>>> M = [[-1024 if i==j else 1024 for j in range(7)] for i in range(7)]
>>> numpy.linalg.det(M)
3.777893186295698e+23
>>> "%.0f" % numpy.linalg.det(M)
'377789318629569805156352'
这是错误的!我确定正确答案是:
>>> 320 * 1024**7
377789318629571617095680
当然,对于一个大矩阵,它可能是一个相当长的整数。但是 python 内置了长整数。
如何获得行列式的精确整数值而不是近似浮点值?
计算整数矩阵行列式的一种简单实用的方法是Bareiss algorithm。
def det(M):
M = [row[:] for row in M] # make a copy to keep original M unmodified
N, sign, prev = len(M), 1, 1
for i in range(N-1):
if M[i][i] == 0: # swap with another row having nonzero i's elem
swapto = next( (j for j in range(i+1,N) if M[j][i] != 0), None )
if swapto is None:
return 0 # all M[*][i] are zero => zero determinant
M[i], M[swapto], sign = M[swapto], M[i], -sign
for j in range(i+1,N):
for k in range(i+1,N):
assert ( M[j][k] * M[i][i] - M[j][i] * M[i][k] ) % prev == 0
M[j][k] = ( M[j][k] * M[i][i] - M[j][i] * M[i][k] ) // prev
prev = M[i][i]
return sign * M[-1][-1]
这个算法相当快(O(N³) 复杂度)。
这是一个整数保留算法。它确实有一个部门。但只要M的所有元素都是整数,中间的计算也都是整数(除法余数为零)。
作为奖励,如果您删除 assert
行并将整数除法 //
替换为常规除法 /
.
PS: Another alternative is to use sympy instead of numpy:
>>> import sympy
>>> sympy.Matrix([ [-1024 if i==j else 1024 for j in range(7)] for i in range(7) ]).det()
377789318629571617095680
But somewhy that is MUCH slower than the above det()
function.
# Performance test: `numpy.linalg.det(M)` vs `det(M)` vs `sympy.Matrix(M).det()`
import timeit
def det(M):
...
M = [[-1024 if i==j else 1024 for j in range(7)] for i in range(7)]
print(timeit.repeat("numpy.linalg.det(M)", setup="import numpy; from __main__ import M", number=100, repeat=5))
#: [0.0035009384155273, 0.0033931732177734, 0.0033941268920898, 0.0033800601959229, 0.0033988952636719]
print(timeit.repeat("det(M)", setup="from __main__ import det, M", number=100, repeat=5))
#: [0.0171120166778564, 0.0171020030975342, 0.0171608924865723, 0.0170948505401611, 0.0171010494232178]
print(timeit.repeat("sympy.Matrix(M).det()", setup="import sympy; from __main__ import M", number=100, repeat=5))
#: [0.9561479091644287, 0.9564781188964844, 0.9539868831634521, 0.9536828994750977, 0.9546608924865723]
Summary:
det(M)
is 5+ times slower than numpy.linalg.det(M)
,
det(M)
is ~50 times faster than sympy.Matrix(M).det()
It becomes even faster without the assert
line.
@pycoder 的回答是首选方案;为了比较,我使用 Fraction
class 编写了一个高斯消去函数,它允许对有理数进行精确运算。在相同的基准测试中,它比 Bareiss 算法慢了大约 11 倍。
from fractions import Fraction
def det(matrix):
matrix = [[Fraction(x, 1) for x in row] for row in matrix]
n = len(matrix)
d, sign = 1, 1
for i in range(n):
if matrix[i][i] == 0:
j = next((j for j in range(i + 1, n) if matrix[j][i] != 0), None)
if j is None:
return 0
matrix[i], matrix[j] = matrix[j], matrix[i]
sign = -sign
d *= matrix[i][i]
for j in range(i + 1, n):
factor = matrix[j][i] / matrix[i][i]
for k in range(i + 1, n):
matrix[j][k] -= factor * matrix[i][k]
return int(d) * sign
行列式定义只有加法、减法和乘法。所以具有整数元素的矩阵的行列式必须是整数。
但是numpy.linalg.det()
returns一个“稍微偏离”的浮点数:
>>> import numpy
>>> M = [[-1 if i==j else 1 for j in range(7)] for i in range(7)]
>>> numpy.linalg.det(M)
319.99999999999994
对于更大的矩阵,情况会变得更糟:
>>> M = [[-1024 if i==j else 1024 for j in range(7)] for i in range(7)]
>>> numpy.linalg.det(M)
3.777893186295698e+23
>>> "%.0f" % numpy.linalg.det(M)
'377789318629569805156352'
这是错误的!我确定正确答案是:
>>> 320 * 1024**7
377789318629571617095680
当然,对于一个大矩阵,它可能是一个相当长的整数。但是 python 内置了长整数。
如何获得行列式的精确整数值而不是近似浮点值?
计算整数矩阵行列式的一种简单实用的方法是Bareiss algorithm。
def det(M):
M = [row[:] for row in M] # make a copy to keep original M unmodified
N, sign, prev = len(M), 1, 1
for i in range(N-1):
if M[i][i] == 0: # swap with another row having nonzero i's elem
swapto = next( (j for j in range(i+1,N) if M[j][i] != 0), None )
if swapto is None:
return 0 # all M[*][i] are zero => zero determinant
M[i], M[swapto], sign = M[swapto], M[i], -sign
for j in range(i+1,N):
for k in range(i+1,N):
assert ( M[j][k] * M[i][i] - M[j][i] * M[i][k] ) % prev == 0
M[j][k] = ( M[j][k] * M[i][i] - M[j][i] * M[i][k] ) // prev
prev = M[i][i]
return sign * M[-1][-1]
这个算法相当快(O(N³) 复杂度)。
这是一个整数保留算法。它确实有一个部门。但只要M的所有元素都是整数,中间的计算也都是整数(除法余数为零)。
作为奖励,如果您删除 assert
行并将整数除法 //
替换为常规除法 /
.
PS: Another alternative is to use sympy instead of numpy:
>>> import sympy >>> sympy.Matrix([ [-1024 if i==j else 1024 for j in range(7)] for i in range(7) ]).det() 377789318629571617095680
But somewhy that is MUCH slower than the above
det()
function.# Performance test: `numpy.linalg.det(M)` vs `det(M)` vs `sympy.Matrix(M).det()` import timeit def det(M): ... M = [[-1024 if i==j else 1024 for j in range(7)] for i in range(7)] print(timeit.repeat("numpy.linalg.det(M)", setup="import numpy; from __main__ import M", number=100, repeat=5)) #: [0.0035009384155273, 0.0033931732177734, 0.0033941268920898, 0.0033800601959229, 0.0033988952636719] print(timeit.repeat("det(M)", setup="from __main__ import det, M", number=100, repeat=5)) #: [0.0171120166778564, 0.0171020030975342, 0.0171608924865723, 0.0170948505401611, 0.0171010494232178] print(timeit.repeat("sympy.Matrix(M).det()", setup="import sympy; from __main__ import M", number=100, repeat=5)) #: [0.9561479091644287, 0.9564781188964844, 0.9539868831634521, 0.9536828994750977, 0.9546608924865723]
Summary:
det(M)
is 5+ times slower thannumpy.linalg.det(M)
,det(M)
is ~50 times faster thansympy.Matrix(M).det()
It becomes even faster without the
assert
line.
@pycoder 的回答是首选方案;为了比较,我使用 Fraction
class 编写了一个高斯消去函数,它允许对有理数进行精确运算。在相同的基准测试中,它比 Bareiss 算法慢了大约 11 倍。
from fractions import Fraction
def det(matrix):
matrix = [[Fraction(x, 1) for x in row] for row in matrix]
n = len(matrix)
d, sign = 1, 1
for i in range(n):
if matrix[i][i] == 0:
j = next((j for j in range(i + 1, n) if matrix[j][i] != 0), None)
if j is None:
return 0
matrix[i], matrix[j] = matrix[j], matrix[i]
sign = -sign
d *= matrix[i][i]
for j in range(i + 1, n):
factor = matrix[j][i] / matrix[i][i]
for k in range(i + 1, n):
matrix[j][k] -= factor * matrix[i][k]
return int(d) * sign