Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Precise determinant of integer NxN matrix

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?

like image 949
pycoder Avatar asked Feb 14 '21 06:02

pycoder


People also ask

What is the determinant of a matrix?

What is Determinant of a Matrix? Determinant of a Matrix is a special number that is defined only for square matrices (matrices which have same number of rows and columns).

What is the meaning of of matrix N?

of matrix. n is current dimension of mat [] []. */ // This code is contributed by Anant Agarwal. # determinant of matrix. # i-th row and j-th column. # of given matrix a. # elements of matrix. # of matrix a. # to co-factor of that sub matrix.

How to find the mother determinant of even and odd numbers?

Replace the odd numbers by 1 and the even numbers by 0. If the new determinant value is odd then the value of the mother determinant is odd else even Thanks for contributing an answer to Stack Overflow!

How do you find the value of Det (A) from a matrix?

Let A be the matrix in the post and let B be the matrix with entries b i j = a i j − 1. Let e be the column vector of 1's. Then A = e e T + B. Then det ( A) = det ( B) ( 1 + e T B − 1 e).


2 Answers

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 than numpy.linalg.det(M),
  • det(M) is ~50 times faster than sympy.Matrix(M).det()

It becomes even faster without the assert line.

like image 59
pycoder Avatar answered Oct 17 '22 17:10

pycoder


@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
like image 35
kaya3 Avatar answered Oct 17 '22 15:10

kaya3