Full rank decomposition of matrix theory code practice

Posted by franko75 on Mon, 03 Jan 2022 10:00:40 +0100

principle

  in this article, let's introduce full rank factorization, which is defined as follows: A ∈ F m × n A\in F^{m\times n} A∈Fm × n. rank(A)=r, if there is a matrix with rank r B ∈ F m × r , C ∈ F r × n B\in F^{m\times r},C\in F^{r\times n} B∈Fm × r,C∈Fr × n. Make A = B C A=BC A=BC, which is called the full rank decomposition of A. From the size of the rank, we can know that, r < m ∣ n r< m|n R < m ∣ n, so the full rank decomposition decomposes A into A "thin high" matrix multiplied by A "fat low" matrix.

  for any non-zero matrix A ∈ F m × n A\in F^{m\times n} A∈Fm × n. There is full rank decomposition. This is a strong existence. We can also understand that as long as it is not a zero matrix, the number of its maximum independent groups is at least 1, other column vectors can be linearly expressed by the maximum independent group, and the full rank decomposition can be understood as a process of finding the maximum independent group and giving a linear representation of the original matrix.


Full rank decomposition based on elementary row transformation

   first, there are many different solutions to the full rank decomposition. Here, we choose a simple calculation. We can obtain the full rank decomposition of matrix A without introducing inversion (inversion is often an unstable operation).

   let's take A look at the examples in the textbook first. Let A be:

A = [ 0 1 0 − 1 5 6 0 2 0 0 0 − 14 2 − 1 2 − 4 0 1 − 2 1 − 2 2 10 25 ] A=\begin{bmatrix} 0 & 1 & 0 & -1 & 5 & 6 \\ 0 & 2 & 0 & 0 & 0 & -14 \\ 2 & -1 & 2 & -4 & 0 & 1\\ -2 & 1 & -2 & 2 & 10 & 25\\ \end{bmatrix} A=⎣⎢⎢⎡​002−2​12−11​002−2​−10−42​50010​6−14125​⎦⎥⎥⎤​

   after elementary line transformation, it is obtained
A = [ 1 0 1 0 − 10 − 29 0 1 0 0 0 − 7 0 0 0 1 − 5 − 13 0 0 0 0 0 0 ] A=\begin{bmatrix} 1 & 0 & 1 & 0 & -10 & -29 \\ 0 & 1 & 0 & 0 & 0 & -7 \\ 0 & 0 & 0 & 1 & -5 & -13\\ 0 & 0 & 0 & 0 & 0 & 0\\ \end{bmatrix} A=⎣⎢⎢⎡​1000​0100​1000​0010​−100−50​−29−7−130​⎦⎥⎥⎤​
   the maximum independent group is composed of the column vector corresponding to the first non-zero element of the non-zero row, which is set as the B matrix, that is, take out the first, second and fourth columns:
B = [ 0 1 − 1 0 2 0 2 − 1 − 4 − 2 1 2 ] B=\begin{bmatrix} 0 & 1 & -1 \\ 0 & 2 & 0 \\ 2 & -1 & -4 \\ -2 & 1 & 2 \end{bmatrix} B=⎣⎢⎢⎡​002−2​12−11​−10−42​⎦⎥⎥⎤​

   and the C matrix is the non-zero row of ladder type after simplification:
C = [ 1 0 1 0 − 10 − 29 0 1 0 0 0 − 7 0 0 0 1 − 5 − 13 ] C=\begin{bmatrix} 1 & 0 & 1 & 0 & -10 & -29 \\ 0 & 1 & 0 & 0 & 0 & -7 \\ 0 & 0 & 0 & 1 & -5 & -13\\ \end{bmatrix} C=⎣⎡​100​010​100​001​−100−5​−29−7−13​⎦⎤​


code implementation

@classmethod
def Full_Rank_Fact(self, target, eps=1e-6, test=False):
    """Full Rank Decomposition Solution for 2D-matrix by Junno

    Args:
        target ([np.darray]): [matrix to be processed]
        eps ([float]): numerical threshold.
        test (bool, optional): [whether to show testing information]. Defaults to False.
    
    Returns:
        [np.darray]: B, C
    
    Last edited: 22-01-02
    Author: Junno
    """
    assert len(np.shape(target)) == 2
    # get row ans col numbers
    M, N = np.shape(target)

    # A for processing and raw_A for result extraction for B
    raw_A = deepcopy(target)
    A = deepcopy(target)
    if test:
        print('original matrix:')
        print(target)

    for i in range(M):
        for j in range(i, N):
            if test:
                print('During process in row:{}, col:{}'.format(i, j))
                
			# resort rows if needed
            if sum(abs(A[i:, j])) > eps:
                zero_index = []
                non_zero_index = []
                for k in range(i, M):
                    if abs(A[k, j]) > eps:
                        non_zero_index.append(k)
                    else:
                        zero_index.append(k)

                non_zero_index = sorted(
                    non_zero_index, key=lambda x: abs(A[x, j]))
                sort_cols = non_zero_index+zero_index
                
                if test:
                    print("Sort_cols index: {}".format(sort_cols))

                A[i:, :] = A[sort_cols, :]

                if test:
                    print('After resorting')
                    print(A)
				
				# do primary row transformation to eliminate elements belows
                prefix = -A[i+1:, j]/A[i, j]
                temp = (np.array(prefix).reshape(-1, 1)
                        )@A[i, :].reshape((1, -1))
                A[i+1:, :] += temp

				# eliminate elements aboves
                for k in range(i):
                    if A[k, j] != 0:
                        A[k, :] += -(A[k, j]/A[i, j])*A[i, :]

                if test:
                    print('After primary row transformation:')
                    print(A)

                break

            else:
                continue

    # list to record indexs of principal maximum
    principal_indexs = [[], []]
    for m in range(M):
        for n in range(m, N):
            if A[m, n] != 0:
                principal_indexs[0].append(n)
                principal_indexs[1].append(m)
                # normalize
                if A[m, n] != 1:
                    A[m, :] *= 1./A[m, n]
                break
                
    # -0 to 0, OCD
    A[abs(A) == 0] = 0

    if test:
        print('final result')
        print(A)
        print('principal maximum index: {}'.format(principal_indexs))

    # choose principal maximum and return B and C
    if len(principal_indexs[0]):
        B = raw_A[:, principal_indexs[0]]
        C = A[principal_indexs[1], :]
        return B, C
    else:
        raise ValueError("Can't find any principle maximum @_@")

example

# test standard example
>>> A=np.array([[0,1,0,-1,5,6],[0,2,0,0,0,-14],[2,-1,2,-4,0,1],[-2,1,-2,2,10,25]]).reshape((4,6)).astype(np.float32) 
>>> A
array([[  0.,   1.,   0.,  -1.,   5.,   6.],
       [  0.,   2.,   0.,   0.,   0., -14.],
       [  2.,  -1.,   2.,  -4.,   0.,   1.],
       [ -2.,   1.,  -2.,   2.,  10.,  25.]], dtype=float32)
>>> B,C=Matrix_Solutions.Full_Rank_Fact(A)
>>> B
array([[ 0.,  1., -1.],
       [ 0.,  2.,  0.],
       [ 2., -1., -4.],
       [-2.,  1.,  2.]], dtype=float32)
>>> C
array([[  1.,   0.,   1.,   0., -10., -29.],
       [  0.,   1.,   0.,   0.,   0.,  -7.],
       [  0.,   0.,   0.,   1.,  -5., -13.]], dtype=float32)
>>> B@C-A
array([[0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0.]], dtype=float32)
       
# test unfull-rank matrix
>>> A=np.array([[1,2,3],[2,4,6],[3,6,9,]]).reshape((3,3)).astype(np.float32) 
>>> A
array([[1., 2., 3.],
       [2., 4., 6.],
       [3., 6., 9.]], dtype=float32)
>>> B,C=Matrix_Solutions.Full_Rank_Fact(A)
>>> B
array([[1.],
       [2.],
       [3.]], dtype=float32)
>>> C
array([[1., 2., 3.]], dtype=float32)

# test all-zeros matrix
>>> A=np.array([[0,0,0],[0,0,0],[0,0,0,]]).reshape((3,3)).astype(np.float32)
>>> A
array([[0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.]], dtype=float32)
>>> B,C=Matrix_Solutions.Full_Rank_Fact(A) 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "............", line 104, in Full_Rank_Fact
    raise ValueError("Can't find any principle maximum @_@")
ValueError: Can't find any principle maximum @_@

Pit filling

  come and fill the pit. Remember we were there LU solution of linear equations The function to judge whether the matrix is full rank is used in. Derived, there are row full rank and column full rank, which can be obtained by the above full rank decomposition. The principle is very simple, directly on the code, seconds to understand!

@classmethod
def Check_full_rank(self, target, eps=1e-6):
    """Check_full_rank Function

    Args:
        target ([np.darray]): input matrix
        eps ([float]): numerical threshold.
        
    Returns:
        [bool]: [whether full rank]
    
    Last edited: 22-01-02
    Author: Junno
    """
    
    if np.sum(target)<eps:
    	# all-zero
    	return False
    # get row ans col numbers
    M, N = np.shape(target)
    B, C = self.Full_Rank_Fact(target, False)
    return B.shape[1] == min(M, N)


@classmethod
def Get_col_rank(self, target, eps=1e-6):
    """Get_col_rank of target

    Args:
        target ([np.darray]): input matrix

    Returns:
        [int]: [rank of cal]

    Last edited: 22-01-02
    Author: Junno
    """
    if np.sum(target)<eps:
    	# all-zero
    	return 0
    B, C = self.Full_Rank_Fact(target, False)
    return B.shape[1]

@classmethod
def Get_row_rank(self, target):
    """Get_row_rank of target

    Args:
        target ([np.darray]): input matrix

    Returns:
        [int]: [rank of row]

    Last edited: 22-01-02
    Author: Junno
    """
    # row rank = col rank
    return Get_col_rank(target, False)
>>> A=np.array([[1,2,3],[2,4,6],[3,6,9,]]).reshape((3,3)).astype(np.float32)
>>> Matrix_Solutions.Check_full_rank(A)
False
>>> Matrix_Solutions.Get_row_rank(A)   
1
>>> Matrix_Solutions.Get_col_rank(A) 
1

>>> A=np.zeros((4,4))
>>> Matrix_Solutions.Get_col_rank(A)
0

>>> A=np.eye(4)
>>> A
array([[1., 0., 0., 0.],
       [0., 1., 0., 0.],
       [0., 0., 1., 0.],
       [0., 0., 0., 1.]])
>>> Matrix_Solutions.Get_col_rank(A)
4

Unfinished to be continued

  continue to fill the pit!

Topics: Python Algorithm linear algebra