Skip to content

Commit ac1718e

Browse files
authored
Add matrix inversion (#582)
* adds matrix inversion for n x n matrices * adds link in readme and edge cases in file * fixes typo
1 parent 5f9838d commit ac1718e

File tree

3 files changed

+157
-0
lines changed

3 files changed

+157
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ If you want to uninstall algorithms, it is as simple as:
228228
- [copy_transform](algorithms/matrix/copy_transform.py)
229229
- [count_paths](algorithms/matrix/count_paths.py)
230230
- [matrix_rotation.txt](algorithms/matrix/matrix_rotation.txt)
231+
- [matrix_inversion](algorithms/matrix/matrix_inversion.py)
231232
- [matrix_multiplication](algorithms/matrix/multiply.py)
232233
- [rotate_image](algorithms/matrix/rotate_image.py)
233234
- [search_in_sorted_matrix](algorithms/matrix/search_in_sorted_matrix.py)
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
"""
2+
Inverts an invertible n x n matrix -- i.e., given an n x n matrix A, returns
3+
an n x n matrix B such that AB = BA = In, the n x n identity matrix.
4+
5+
For a 2 x 2 matrix, inversion is simple using the cofactor equation. For
6+
larger matrices, this is a four step process:
7+
1. calculate the matrix of minors: create an n x n matrix by considering each
8+
position in the original matrix in turn. Exclude the current row and column
9+
and calculate the determinant of the remaining matrix, then place that value
10+
in the current position's equivalent in the matrix of minors.
11+
2. create the matrix of cofactors: take the matrix of minors and multiply
12+
alternate values by -1 in a checkerboard pattern.
13+
3. adjugate: hold the top left to bottom right diagonal constant, but swap all
14+
other values over it.
15+
4. multiply the adjugated matrix by 1 / the determinant of the original matrix
16+
17+
This code combines steps 1 and 2 into one method to reduce traversals of the
18+
matrix.
19+
20+
Possible edge cases: will not work for 0x0 or 1x1 matrix, though these are
21+
trivial to calculate without use of this file.
22+
"""
23+
import fractions
24+
25+
26+
def invert_matrix(m):
27+
"""invert an n x n matrix"""
28+
# Error conditions
29+
if not array_is_matrix(m):
30+
print("Invalid matrix: array is not a matrix")
31+
return [[-1]];
32+
elif len(m) != len(m[0]):
33+
print("Invalid matrix: matrix is not square")
34+
return [[-2]];
35+
elif len(m) < 2:
36+
print("Invalid matrix: matrix is too small")
37+
return [[-3]];
38+
elif get_determinant(m) == 0:
39+
print("Invalid matrix: matrix is square, but singular (determinant = 0)")
40+
return [[-4]];
41+
42+
# Calculation
43+
elif len(m) == 2:
44+
# simple case
45+
multiplier = 1 / get_determinant(m)
46+
inverted = [[multiplier] * len(m) for n in range(len(m))]
47+
inverted[0][1] = inverted[0][1] * -1 * m[0][1]
48+
inverted[1][0] = inverted[1][0] * -1 * m[1][0]
49+
inverted[0][0] = multiplier * m[1][1]
50+
inverted[1][1] = multiplier * m[0][0]
51+
return inverted
52+
else:
53+
"""some steps combined in helpers to reduce traversals"""
54+
# get matrix of minors w/ "checkerboard" signs
55+
m_of_minors = get_matrix_of_minors(m)
56+
57+
# calculate determinant (we need to know 1/det)
58+
multiplier = fractions.Fraction(1, get_determinant(m))
59+
60+
# adjugate (swap on diagonals) and multiply by 1/det
61+
inverted = transpose_and_multiply(m_of_minors, multiplier)
62+
63+
return inverted
64+
65+
66+
def get_determinant(m):
67+
"""recursively calculate the determinant of an n x n matrix, n >= 2"""
68+
if len(m) == 2:
69+
# trivial case
70+
return (m[0][0] * m[1][1]) - (m[0][1] * m[1][0])
71+
else:
72+
sign = 1
73+
det = 0
74+
for i in range(len(m)):
75+
det += sign * m[0][i] * get_determinant(get_minor(m, 0, i))
76+
sign *= -1
77+
return det
78+
79+
80+
def get_matrix_of_minors(m):
81+
"""get the matrix of minors and alternate signs"""
82+
matrix_of_minors = [[0 for i in range(len(m))] for j in range(len(m))]
83+
for row in range(len(m)):
84+
for col in range(len(m[0])):
85+
if (row + col) % 2 == 0:
86+
sign = 1
87+
else:
88+
sign = -1
89+
matrix_of_minors[row][col] = sign * get_determinant(get_minor(m, row, col))
90+
return matrix_of_minors
91+
92+
93+
def get_minor(m, row, col):
94+
"""
95+
get the minor of the matrix position m[row][col]
96+
(all values m[r][c] where r != row and c != col)
97+
"""
98+
minors = []
99+
for i in range(len(m)):
100+
if i != row:
101+
new_row = m[i][:col]
102+
new_row.extend(m[i][col + 1:])
103+
minors.append(new_row)
104+
return minors
105+
106+
107+
def transpose_and_multiply(m, multiplier=1):
108+
"""swap values along diagonal, optionally adding multiplier"""
109+
for row in range(len(m)):
110+
for col in range(row + 1):
111+
temp = m[row][col] * multiplier
112+
m[row][col] = m[col][row] * multiplier
113+
m[col][row] = temp
114+
return m
115+
116+
117+
def array_is_matrix(m):
118+
if len(m) == 0:
119+
return False
120+
first_col = len(m[0])
121+
for row in m:
122+
if len(row) != first_col:
123+
return False
124+
return True

tests/test_matrix.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
copy_transform,
44
crout_matrix_decomposition,
55
cholesky_matrix_decomposition,
6+
matrix_inversion,
67
multiply,
78
rotate_image,
89
sparse_dot_vector,
@@ -127,6 +128,37 @@ def test_cholesky_matrix_decomposition(self):
127128
[[5, 1.2, 0.3, -0.6], [1.2, 6, -0.4, 0.9],
128129
[0.3, -0.4, 8, 1.7], [-0.6, 0.9, 1.7, 10]]))
129130

131+
class TestInversion(unittest.TestCase):
132+
"""[summary]
133+
Test for the file matrix_inversion.py
134+
135+
Arguments:
136+
unittest {[type]} -- [description]
137+
"""
138+
def test_inversion(self):
139+
from fractions import Fraction
140+
141+
m1 = [[1, 1], [1, 2]]
142+
self.assertEqual(matrix_inversion.invert_matrix(m1), [[2, -1], [-1, 1]])
143+
144+
m2 = [[1, 2], [3, 4, 5]]
145+
self.assertEqual(matrix_inversion.invert_matrix(m2), [[-1]])
146+
147+
m3 = [[1, 1, 1, 1], [2, 2, 2, 2]]
148+
self.assertEqual(matrix_inversion.invert_matrix(m3), [[-2]])
149+
150+
m4 = [[1]]
151+
self.assertEqual(matrix_inversion.invert_matrix(m4), [[-3]])
152+
153+
m5 = [[1, 2, 3] , [4, 5, 6], [7, 8, 9]]
154+
self.assertEqual(matrix_inversion.invert_matrix(m5), [[-4]])
155+
156+
m6 = [[3, 5, 1], [2, 5, 0], [1, 9, 8]]
157+
self.assertEqual(matrix_inversion.invert_matrix(m6), [[Fraction(40, 53), Fraction(-31, 53), Fraction(-5, 53)],
158+
[Fraction(-16, 53), Fraction(23, 53), Fraction(2, 53)],
159+
[Fraction(13, 53), Fraction(-22, 53), Fraction(5, 53)]])
160+
161+
130162

131163
class TestMultiply(unittest.TestCase):
132164
"""[summary]

0 commit comments

Comments
 (0)