sherman morrison Algorithm
The Sherman-Morrison Algorithm is an efficient mathematical method used for solving linear systems of equations, particularly when dealing with the inversion of a matrix. It is a special case of the more general Woodbury matrix identity, which is used to compute the inverse of a rank-one perturbed matrix. The Sherman-Morrison Algorithm is named after Jack Sherman and Winifred J. Morrison, who introduced the method in their 1950 paper. The algorithm is particularly useful when dealing with the iterative update of the inverse of a matrix, as it allows for faster computations than recalculating the entire inverse after each update.
The main idea behind the Sherman-Morrison Algorithm is that if we have a matrix A and its inverse A_inv, and we want to find the inverse of a new matrix B, which is a rank-one update of A (i.e., B = A + uv^T, where u and v are column vectors), then we can compute the inverse of B (B_inv) using the formula B_inv = A_inv - (A_inv * u * v^T * A_inv) / (1 + v^T * A_inv * u). This formula reduces the computational complexity of finding the inverse of B from O(n^3) for a direct computation to O(n^2) using the Sherman-Morrison Algorithm. In applications where the matrix inversions need to be updated frequently, such as optimization problems, control systems, and machine learning, the Sherman-Morrison Algorithm provides a significant computational advantage.
class Matrix:
"""
<class Matrix>
Matrix structure.
"""
def __init__(self, row: int, column: int, default_value: float = 0):
"""
<method Matrix.__init__>
Initialize matrix with given size and default value.
Example:
>>> a = Matrix(2, 3, 1)
>>> a
Matrix consist of 2 rows and 3 columns
[1, 1, 1]
[1, 1, 1]
"""
self.row, self.column = row, column
self.array = [[default_value for c in range(column)] for r in range(row)]
def __str__(self):
"""
<method Matrix.__str__>
Return string representation of this matrix.
"""
# Prefix
s = "Matrix consist of %d rows and %d columns\n" % (self.row, self.column)
# Make string identifier
max_element_length = 0
for row_vector in self.array:
for obj in row_vector:
max_element_length = max(max_element_length, len(str(obj)))
string_format_identifier = "%%%ds" % (max_element_length,)
# Make string and return
def single_line(row_vector):
nonlocal string_format_identifier
line = "["
line += ", ".join(string_format_identifier % (obj,) for obj in row_vector)
line += "]"
return line
s += "\n".join(single_line(row_vector) for row_vector in self.array)
return s
def __repr__(self):
return str(self)
def validateIndices(self, loc: tuple):
"""
<method Matrix.validateIndices>
Check if given indices are valid to pick element from matrix.
Example:
>>> a = Matrix(2, 6, 0)
>>> a.validateIndices((2, 7))
False
>>> a.validateIndices((0, 0))
True
"""
if not (isinstance(loc, (list, tuple)) and len(loc) == 2):
return False
elif not (0 <= loc[0] < self.row and 0 <= loc[1] < self.column):
return False
else:
return True
def __getitem__(self, loc: tuple):
"""
<method Matrix.__getitem__>
Return array[row][column] where loc = (row, column).
Example:
>>> a = Matrix(3, 2, 7)
>>> a[1, 0]
7
"""
assert self.validateIndices(loc)
return self.array[loc[0]][loc[1]]
def __setitem__(self, loc: tuple, value: float):
"""
<method Matrix.__setitem__>
Set array[row][column] = value where loc = (row, column).
Example:
>>> a = Matrix(2, 3, 1)
>>> a[1, 2] = 51
>>> a
Matrix consist of 2 rows and 3 columns
[ 1, 1, 1]
[ 1, 1, 51]
"""
assert self.validateIndices(loc)
self.array[loc[0]][loc[1]] = value
def __add__(self, another):
"""
<method Matrix.__add__>
Return self + another.
Example:
>>> a = Matrix(2, 1, -4)
>>> b = Matrix(2, 1, 3)
>>> a+b
Matrix consist of 2 rows and 1 columns
[-1]
[-1]
"""
# Validation
assert isinstance(another, Matrix)
assert self.row == another.row and self.column == another.column
# Add
result = Matrix(self.row, self.column)
for r in range(self.row):
for c in range(self.column):
result[r, c] = self[r, c] + another[r, c]
return result
def __neg__(self):
"""
<method Matrix.__neg__>
Return -self.
Example:
>>> a = Matrix(2, 2, 3)
>>> a[0, 1] = a[1, 0] = -2
>>> -a
Matrix consist of 2 rows and 2 columns
[-3, 2]
[ 2, -3]
"""
result = Matrix(self.row, self.column)
for r in range(self.row):
for c in range(self.column):
result[r, c] = -self[r, c]
return result
def __sub__(self, another):
return self + (-another)
def __mul__(self, another):
"""
<method Matrix.__mul__>
Return self * another.
Example:
>>> a = Matrix(2, 3, 1)
>>> a[0,2] = a[1,2] = 3
>>> a * -2
Matrix consist of 2 rows and 3 columns
[-2, -2, -6]
[-2, -2, -6]
"""
if isinstance(another, (int, float)): # Scalar multiplication
result = Matrix(self.row, self.column)
for r in range(self.row):
for c in range(self.column):
result[r, c] = self[r, c] * another
return result
elif isinstance(another, Matrix): # Matrix multiplication
assert self.column == another.row
result = Matrix(self.row, another.column)
for r in range(self.row):
for c in range(another.column):
for i in range(self.column):
result[r, c] += self[r, i] * another[i, c]
return result
else:
raise TypeError(
"Unsupported type given for another ({})".format(type(another))
)
def transpose(self):
"""
<method Matrix.transpose>
Return self^T.
Example:
>>> a = Matrix(2, 3)
>>> for r in range(2):
... for c in range(3):
... a[r,c] = r*c
...
>>> a.transpose()
Matrix consist of 3 rows and 2 columns
[0, 0]
[0, 1]
[0, 2]
"""
result = Matrix(self.column, self.row)
for r in range(self.row):
for c in range(self.column):
result[c, r] = self[r, c]
return result
def ShermanMorrison(self, u, v):
"""
<method Matrix.ShermanMorrison>
Apply Sherman-Morrison formula in O(n^2).
To learn this formula, please look this: https://en.wikipedia.org/wiki/Sherman%E2%80%93Morrison_formula
This method returns (A + uv^T)^(-1) where A^(-1) is self. Returns None if it's impossible to calculate.
Warning: This method doesn't check if self is invertible.
Make sure self is invertible before execute this method.
Example:
>>> ainv = Matrix(3, 3, 0)
>>> for i in range(3): ainv[i,i] = 1
...
>>> u = Matrix(3, 1, 0)
>>> u[0,0], u[1,0], u[2,0] = 1, 2, -3
>>> v = Matrix(3, 1, 0)
>>> v[0,0], v[1,0], v[2,0] = 4, -2, 5
>>> ainv.ShermanMorrison(u, v)
Matrix consist of 3 rows and 3 columns
[ 1.2857142857142856, -0.14285714285714285, 0.3571428571428571]
[ 0.5714285714285714, 0.7142857142857143, 0.7142857142857142]
[ -0.8571428571428571, 0.42857142857142855, -0.0714285714285714]
"""
# Size validation
assert isinstance(u, Matrix) and isinstance(v, Matrix)
assert self.row == self.column == u.row == v.row # u, v should be column vector
assert u.column == v.column == 1 # u, v should be column vector
# Calculate
vT = v.transpose()
numerator_factor = (vT * self * u)[0, 0] + 1
if numerator_factor == 0:
return None # It's not invertable
return self - ((self * u) * (vT * self) * (1.0 / numerator_factor))
# Testing
if __name__ == "__main__":
def test1():
# a^(-1)
ainv = Matrix(3, 3, 0)
for i in range(3):
ainv[i, i] = 1
print(f"a^(-1) is {ainv}")
# u, v
u = Matrix(3, 1, 0)
u[0, 0], u[1, 0], u[2, 0] = 1, 2, -3
v = Matrix(3, 1, 0)
v[0, 0], v[1, 0], v[2, 0] = 4, -2, 5
print(f"u is {u}")
print(f"v is {v}")
print("uv^T is %s" % (u * v.transpose()))
# Sherman Morrison
print("(a + uv^T)^(-1) is {}".format(ainv.ShermanMorrison(u, v)))
def test2():
import doctest
doctest.testmod()
test2()