Inverse of a matrix after a row-and-column removal

Input: M or M_inverse Output: Inverse Remove row-col, calculate inverse Manipulate M_inverse, remove row-col

Is using the matrix inverse faster than calculating the inverse of the smaller matrix?

This is a generalization and a detailed explanation of a neat little trick I came across in the paper Optimal Brain Compression. The solution uses the same approach as this answer but also extends it to a more general case. A Python implementation is also provided for both the cases.


The Original Problem

You are given an invertible n-by-n matrix MM and its inverse M−1M^{-1}. Now let's say the i-th row and i-th column of the matrix MM are removed. Let's call this matrix M−iM_{-i}. We would like to calculate the inverse of this matrix M−iM_{-i} efficiently.

import numpy as np

def remove_row(matrix: np.matrix, index: int):
    return np.delete(matrix, index, axis=0)

def remove_column(matrix: np.matrix, index: int):
    return np.delete(matrix, index, axis=1)

def remove_row_and_column(matrix: np.matrix, row_index: int, col_index: int):
    return remove_column(remove_row(matrix, row_index), col_index)
    
matrix = np.random.randn(5,5)
matrix_inverse = np.linalg.inv(matrix)

index = 3
smaller_matrix = remove_row_and_column(matrix, index, index)

If you do not care about efficiency, the problem is trivial - you simply calculate the inverse of M−iM_{-i}. But because we also know M−1M^{-1}, can we somehow make use of it? In other words, can we somehow manipulate M−1M^{-1} to calculate the inverse of M−iM_{-i}, and if so, is it more efficient (or faster) compared to the trivial method?

# a solution that is easy to come up with implement but inefficient:
smaller_matrix_inverse = np.linalg.inv(smaller_matrix)
def computer_smaller_matrix_inverse_efficiently(matrix_inverse, index):
    # can we only use "matrix_inverse" alongwith the index of the row and column removed
    # to calculate the inverse of the smaller matrix?
    ...

A more general problem

We can extend this idea to a case where the indices of the removed row and column are different - if the i-th row and j-th column of the matrix MM are removed, can we calculate the inverse of this (n-1)x(n-1) matrix by directly manipulating M−1M^{-1}? We will explore this idea in a bit.

row_index = 3
col_index = 1
smaller_matrix = remove_row_and_column(matrix, row_index, col_index)
def computer_smaller_matrix_inverse_efficiently(matrix_inverse, row_index, col_index):
    # can we only use "matrix_inverse" alongwith the indices of the row and column removed
    # to calculate the inverse of the smaller matrix?
    ...

The Solution

Solving a specific problem first

Consider the case in which the last row and last column are removed. In other words, we want to calculate the inverse of M−nM_{-n}. Note that it is a (n-1)x(n-1) matrix. We can write it in block matrix form as:

M=[Abcd]M = \begin{bmatrix} A & b \\ c & d \end{bmatrix}

Note that:

Similarly, we can write M−1M^{-1} in the block matrix form as:

M−1=[Pqrs]M^{-1} = \begin{bmatrix} P & q \\ r & s \end{bmatrix}

Note that:

Also note that M−nM_{-n} is actually equal to AA. We would like to find A−1A^{-1} by manipulating M−1M^{-1}.

Now let's do some elementary algebra:

MM−1=M−1M=In⇒[Abcd][Pqrs]=InMM^{-1} = M^{-1}M = I_{n} \Rightarrow \begin{bmatrix} A & b \\ c & d \end{bmatrix} \begin{bmatrix} P & q \\ r & s \end{bmatrix} = I_n ⇒AP+br=In−1 and Aq+bs=0\Rightarrow AP + br = I_{n-1} \text{ and } Aq + bs = 0 ⇒b=−1sAq\Rightarrow b = \frac{-1}{s} Aq ⇒AP+−1s(Aq)r=AP−A1s(qr)=In−1\Rightarrow AP + \frac{-1}{s} (Aq)r = AP - A \frac{1}{s} (qr) = I_{n-1} ⇒A(P−1sqr)=In−1=AA−1\Rightarrow A(P - \frac{1}{s} qr) = I_{n-1} = AA^{-1} ⇒A−1=P−1sqr\Rightarrow A^{-1} = P - \frac{1}{s} qr

Note that:

Now let's denote the i-th column of the matrix MM as M:,iM_{:,i} and j-th row of the matrix as Mj,:M_{j,:}. Then notice that qq is actually equal to M:,nM_{:,n} with the last element missing. Similarly rr is actually equal to Mn,:M_{n,:} with the last element missing. Then we make the following series of observations:

In simple words, the inverse of a matrix MM with the last row and column removed is equal to the inverse of MM minus the product of last column and last row divided by the last diagonal value of M−1M^{-1}.

Is it faster than just calculating the inverse again?

Well of course it is! Note that we already had access to M−1M^{-1}. All we had to calculate was the product of the n-dimensional column vector with a n-dimensional row vector. Calculating this product has a complexity of O(n2)O(n^2). Calculating the inverse of a matrix has complexity O(n3)O(n^3).

Solving the original problem

We want to calculate the inverse of M−iM_{-i} for any i. We just solved the problem when i=ni = n. To solve for any i, we will deploy the following strategy:

N=PTMP, M−i=N−n⇒(M−i)−1=(N−n)−1N = P^TMP \text{, } M_{-i} = N_{-n} \Rightarrow (M_{-i})^{-1} = (N_{-n})^{-1} N−1=PM−1P⇒M−1=PN−1PN^{-1} = PM^{-1}P \Rightarrow M^{-1} = PN^{-1}P (N−n)−1=(N−1)−n−(1s[(N−1):,n∗(N−1)n,:])−n=(M−i)−1(N_{-n})^{-1} = (N^{-1})_{-n} - (\frac{1}{s}[(N^{-1})_{:,n} * (N^{-1})_{n,:}])_{-n} = (M_{-i})^{-1} (N−1)−n=(M−1)−i(N^{-1})_{-n} = (M^{-1})_{-i} (N−1):,n=(M−1):,i(N^{-1})_{:,n} = (M^{-1})_{:,i} (N−1)n,:=(M−1)i,:(N^{-1})_{n,:} = (M^{-1})_{i,:} (M−i)−1=(M−1)−i−1Mii([M:,i−1∗Mi,:−1])−i=(M−1−1Mii[M:,i−1∗Mi,:−1])−i(M_{-i})^{-1} = (M^{-1})_{-i} - \frac{1}{M_{ii}}([M^{-1}_{:,i} * M^{-1}_{i,:}])_{-i} = (M^{-1} - \frac{1}{M_{ii}}[M^{-1}_{:,i} * M^{-1}_{i,:}])_{-i}
def calculate_smaller_matrix_inverse(matrix_inverse: np.matrix, index: int):
    '''
    a faster way to solve
    np.linalg.inv( remove_row_and_column(matrix, index, index) )
    '''
    to_subtract = matrix_inverse[:, index].reshape(-1,1) @ matrix_inverse[index, :].reshape(1,-1)
    to_subtract /= matrix_inverse[index, index]
    result = matrix_inverse - to_subtract
    return remove_row_and_column(result, index, index)

Solution to a more general problem

Now that we know how to calculate (M−i)−1(M_{-i})^{-1}, we can use the same approach to calculate the inverse of a matrix whose i-th row and j-th column have been removed. We use the notation M−ijM_{-ij} to denote the matrix MM with i-th row and j-th column removed.

N=PrMPcN = P_rMP_c N−1=PcTM−1PrT⇒M−1=PcN−1PrN^{-1} = P_c^TM^{-1}P_r^T \Rightarrow M^{-1} = P_cN^{-1}P_r (N−n)−1=(N−1)−n−(1Nnn−1[(N−1):,n∗(N−1)n,:])−n=(M−ij)−1(N_{-n})^{-1} = (N^{-1})_{-n} - (\frac{1}{N^{-1}_{nn}}[(N^{-1})_{:,n} * (N^{-1})_{n,:}])_{-n} = (M_{-ij})^{-1} (N−1)−n=(M−1)−ji(N^{-1})_{-n} = (M^{-1})_{-ji}

In other words, removal of the last row and column of N−1N^{-1} is equivalent to removal of j-th row and i-th column of M−1M^{-1}.

(N−1):,n=(M−1):,i(N^{-1})_{:,n} = (M^{-1})_{:,i} (N−1)n,:=(M−1)j,:(N^{-1})_{n,:} = (M^{-1})_{j,:}

Similarly, the last column of N−1N^{-1} is the same as the i-th column of M−1M^{-1} and the last row of N−1N^{-1} is the same as the j-th row of M−1M^{-1}. Using similar logic, we can see that:

Nnn−1=Mji−1N^{-1}_{nn} = M^{-1}_{ji}

Thus the final solution is given as:

(M−ij)−1=(M−1)−ji−1Mji−1([(M−1):,i∗(M−1)j,:])−ji(M_{-ij})^{-1} = (M^{-1})_{-ji} - \frac{1}{M^{-1}_{ji}}([(M^{-1})_{:,i} * (M^{-1})_{j,:}])_{-ji} ⇒(M−ij)−1=(M−1−1Mji−1[(M−1):,i∗(M−1)j,:])−ji\Rightarrow (M_{-ij})^{-1} = (M^{-1} - \frac{1}{M^{-1}_{ji}}[(M^{-1})_{:,i} * (M^{-1})_{j,:}])_{-ji}
def calculate_smaller_matrix_inverse(matrix_inverse: np.matrix, row_index: int, col_index: int):
    '''
    a faster way to solve:
    np.linalg.inv( remove_row_and_column(matrix, row_index, col_index) )
    '''
    to_subtract = matrix_inverse[:, row_index].reshape(-1,1) @ matrix_inverse[col_index, :].reshape(1,-1)
    to_subtract /= matrix_inverse[col_index, row_index]
    result = matrix_inverse - to_subtract
    return remove_row_and_column(result, col_index, row_index)

Related articles: