Reputation: 547
Determinant definition has only additions, subtractions and multiplications. So a determinant of a matrix with integer elements must be integer.
However numpy.linalg.det()
returns a "slightly off" floating-point number:
>>> 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
It gets worse for a larger matrix:
>>> 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'
And it's wrong! I'm sure the correct answer is:
>>> 320 * 1024**7
377789318629571617095680
Of course, for a big matrix it may be a rather long integer. But python has long integers built in.
How can I get an exact integer value of the determinant instead of approximate floating point value?
Upvotes: 7
Views: 2161
Reputation: 547
A simple practical way to calculate determinant of an integet matrix is 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]
This algorithm is reasonably fast (O(N³) complexity).
And it's an integer preserving algorithm. It does have a division. But as long as all the elements of M are integer, all intermediate calculations would be integer too (the division remainder will be zero).
As a bonus the same code works for fractions/floating-point/complex elements if you drop the assert
line and replace the integer division //
with a regular division /
.
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.
Upvotes: 15
Reputation: 51037
@pycoder's answer is the preferred solution; for comparison, I wrote a Gaussian elimination function using the Fraction
class which allows exact arithmetic of rational numbers. It is about 11 times slower than the Bareiss algorithm on the same benchmark.
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
Upvotes: 6