Skip to content

Commit 85bc484

Browse files
committed
Update matrix ops
1. Removed __init__.py 2. Added pytests for matrix operations as it was difficult to see if things were working correclty just running it through print statements. There were edge cases that the algorithms did not account for. Made the following changes to matrix_operation.py 1. added matrix subtraction to matrix_operation.py 2. added matrix size checks for addition and subtraction as there were previously no checks 3. fixed typo in matrix multiplication loop 4. PEP8 changes
1 parent 7f6ea74 commit 85bc484

File tree

5 files changed

+152
-31
lines changed

5 files changed

+152
-31
lines changed

matrix/__init__.py

-1
This file was deleted.

matrix/matrix_operation.py

+67-29
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,121 @@
11
from __future__ import print_function
22

3+
34
def add(matrix_a, matrix_b):
4-
rows = len(matrix_a)
5-
columns = len(matrix_a[0])
5+
try:
6+
rows = [len(matrix_a), len(matrix_b)]
7+
cols = [len(matrix_a[0]), len(matrix_b[0])]
8+
except TypeError:
9+
raise TypeError("Cannot input an integer value, it must be a matrix")
10+
11+
if rows[0] != rows[1] & cols[0] != cols[1]:
12+
raise ValueError(f"operands could not be broadcast together with shapes "
13+
f"({rows[0],cols[0]}), ({rows[1], cols[1]})")
14+
615
matrix_c = []
7-
for i in range(rows):
16+
for i in range(rows[0]):
817
list_1 = []
9-
for j in range(columns):
18+
for j in range(cols[0]):
1019
val = matrix_a[i][j] + matrix_b[i][j]
1120
list_1.append(val)
1221
matrix_c.append(list_1)
1322
return matrix_c
1423

15-
def scalarMultiply(matrix , n):
24+
25+
def subtract(matrix_a, matrix_b):
26+
try:
27+
rows = [len(matrix_a), len(matrix_b)]
28+
cols = [len(matrix_a[0]), len(matrix_b)]
29+
except TypeError:
30+
raise TypeError("Cannot input an integer value, it must be a matrix")
31+
32+
if rows[0] != rows[1] & cols[0] != cols[1]:
33+
raise ValueError(f"operands could not be broadcast together with shapes "
34+
f"({rows[0], cols[0]}), ({rows[1], cols[1]})")
35+
36+
matrix_c = []
37+
for i in range(rows[0]):
38+
list_1 = []
39+
for j in range(cols[0]):
40+
val = matrix_a[i][j] - matrix_b[i][j]
41+
list_1.append(val)
42+
matrix_c.append(list_1)
43+
return matrix_c
44+
45+
46+
def scalar_multiply(matrix, n):
1647
return [[x * n for x in row] for row in matrix]
1748

49+
1850
def multiply(matrix_a, matrix_b):
1951
matrix_c = []
2052
num_rows_a = len(matrix_a)
2153
num_cols_a = len(matrix_a[0])
2254
num_rows_b = len(matrix_b)
2355
num_cols_b = len(matrix_b[0])
2456

25-
if num_cols_a != num_rows_b :
26-
raise ValueError('Cannot multiply matrix of dimensions {},{} and {},{}'.format(num_rows_a,num_cols_a,num_rows_b,num_cols_b))
57+
if num_cols_a != num_rows_b:
58+
raise ValueError(f'Cannot multiply matrix of dimensions ({num_rows_a},{num_cols_a}) '
59+
f'and ({num_rows_b},{num_cols_b})')
2760

2861
for i in range(num_rows_a):
2962
list_1 = []
3063
for j in range(num_cols_b):
3164
val = 0
32-
for k in range(num_rows_a):
65+
for k in range(num_cols_b):
3366
val = val + matrix_a[i][k] * matrix_b[k][j]
3467
list_1.append(val)
3568
matrix_c.append(list_1)
3669
return matrix_c
3770

71+
3872
def identity(n):
3973
return [[int(row == column) for column in range(n)] for row in range(n)]
4074

75+
4176
def transpose(matrix):
42-
return map(list , zip(*matrix))
77+
return map(list, zip(*matrix))
78+
4379

4480
def minor(matrix, row, column):
4581
minor = matrix[:row] + matrix[row + 1:]
4682
minor = [row[:column] + row[column + 1:] for row in minor]
4783
return minor
4884

85+
4986
def determinant(matrix):
50-
if len(matrix) == 1: return matrix[0][0]
87+
if len(matrix) == 1:
88+
return matrix[0][0]
5189

5290
res = 0
5391
for x in range(len(matrix)):
54-
res += matrix[0][x] * determinant(minor(matrix , 0 , x)) * (-1) ** x
92+
res += matrix[0][x] * determinant(minor(matrix, 0, x)) * (-1) ** x
5593
return res
5694

95+
5796
def inverse(matrix):
5897
det = determinant(matrix)
59-
if det == 0: return None
98+
if det == 0:
99+
return None
60100

61-
matrixMinor = [[] for _ in range(len(matrix))]
101+
matrix_minor = [[] for _ in range(len(matrix))]
62102
for i in range(len(matrix)):
63103
for j in range(len(matrix)):
64-
matrixMinor[i].append(determinant(minor(matrix , i , j)))
104+
matrix_minor[i].append(determinant(minor(matrix, i, j)))
65105

66-
cofactors = [[x * (-1) ** (row + col) for col, x in enumerate(matrixMinor[row])] for row in range(len(matrix))]
106+
cofactors = [[x * (-1) ** (row + col) for col, x in enumerate(matrix_minor[row])] for row in range(len(matrix))]
67107
adjugate = transpose(cofactors)
68-
return scalarMultiply(adjugate , 1/det)
69-
70-
def main():
71-
matrix_a = [[12, 10], [3, 9]]
72-
matrix_b = [[3, 4], [7, 4]]
73-
matrix_c = [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34], [41, 42, 43, 44]]
74-
matrix_d = [[3, 0, 2], [2, 0, -2], [0, 1, 1]]
75-
print('Add Operation, %s + %s = %s \n' %(matrix_a, matrix_b, (add(matrix_a, matrix_b))))
76-
print('Multiply Operation, %s * %s = %s \n' %(matrix_a, matrix_b, multiply(matrix_a, matrix_b)))
77-
print('Identity: %s \n' %identity(5))
78-
print('Minor of %s = %s \n' %(matrix_c, minor(matrix_c , 1 , 2)))
79-
print('Determinant of %s = %s \n' %(matrix_b, determinant(matrix_b)))
80-
print('Inverse of %s = %s\n'%(matrix_d, inverse(matrix_d)))
108+
return scalar_multiply(adjugate, 1/det)
109+
81110

82111
if __name__ == '__main__':
83-
main()
112+
mat_a = [[12, 10], [3, 9]]
113+
mat_b = [[3, 4], [7, 4]]
114+
mat_c = [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34], [41, 42, 43, 44]]
115+
mat_d = [[3, 0, 2], [2, 0, -2], [0, 1, 1]]
116+
print('Add Operation, %s + %s = %s \n' %(mat_a, mat_b, (add(mat_a, mat_b))))
117+
print('Multiply Operation, %s * %s = %s \n' %(mat_a, mat_b, multiply(mat_a, mat_b)))
118+
print('Identity: %s \n' %identity(5))
119+
print('Minor of %s = %s \n' %(mat_c, minor(mat_c , 1 , 2)))
120+
print('Determinant of %s = %s \n' %(mat_b, determinant(mat_b)))
121+
print('Inverse of %s = %s\n'%(mat_d, inverse(mat_d)))

matrix/tests/pytest.ini

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# content of pytest.ini
2+
[pytest]
3+
markers =
4+
mat_ops: mark a test as a matrix operations test

matrix/tests/test_matrix-operations.py

-1
This file was deleted.
+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""
2+
Testing here assumes that numpy is ALWAYS correct!!!!
3+
4+
If running from PyCharm you can place the following line in "Additional Arguments" for the pytest run configuration
5+
-vv -m mat_ops -p no:cacheprovider
6+
"""
7+
8+
# standard libraries
9+
import numpy as np
10+
import pytest
11+
12+
# Custom/local libraries
13+
from matrix import matrix_operation as matop
14+
15+
mat_a = [[12, 10], [3, 9]]
16+
mat_b = [[3, 4], [7, 4]]
17+
mat_c = [[3, 0, 2], [2, 0, -2], [0, 1, 1]]
18+
mat_d = [[3, 0, 2], [2, 0, -2], [0, 1, 1]]
19+
mat_e = [[3, 0, 2], [2, 0, -2], [0, 1, 1], [2, 0, -2]]
20+
mat_f = [1]
21+
mat_h = [2]
22+
23+
24+
@pytest.mark.mat_ops
25+
@pytest.mark.parametrize(('mat1', 'mat2'), [(mat_a, mat_b), (mat_c, mat_d), (mat_d, mat_e),
26+
(mat_f, mat_h)])
27+
def test_addition(mat1, mat2):
28+
# Catch when user enters a single integer value rather than a mat
29+
# Check for known errors
30+
if (np.array(mat1)).shape < (2, 2) or (np.array(mat2)).shape < (2, 2):
31+
with pytest.raises(TypeError):
32+
assert matop.add(mat1, mat2)
33+
elif (np.array(mat1)).shape == (np.array(mat2)).shape:
34+
act = (np.array(mat1) + np.array(mat2)).tolist()
35+
theo = matop.add(mat1, mat2)
36+
assert theo == act
37+
else:
38+
# matrices have different dimensions
39+
# Check for known errors
40+
with pytest.raises(ValueError):
41+
assert matop.add(mat1, mat2)
42+
43+
44+
@pytest.mark.mat_ops
45+
@pytest.mark.parametrize(('mat1', 'mat2'), [(mat_a, mat_b), (mat_c, mat_d), (mat_d, mat_e),
46+
(mat_f, mat_h)])
47+
def test_subtraction(mat1, mat2):
48+
# Catch when user enters a single integer value rather than a mat
49+
# Check for known errors
50+
if (np.array(mat1)).shape < (2, 2) or (np.array(mat2)).shape < (2, 2):
51+
with pytest.raises(TypeError):
52+
assert matop.add(mat1, mat2)
53+
elif (np.array(mat1)).shape == (np.array(mat2)).shape:
54+
act = (np.array(mat1) - np.array(mat2)).tolist()
55+
theo = matop.subtract(mat1, mat2)
56+
assert theo == act
57+
else:
58+
# matrices have different dimensions
59+
# Check for known errors
60+
with pytest.raises(ValueError):
61+
assert matop.subtract(mat1, mat2)
62+
63+
64+
@pytest.mark.mat_ops
65+
@pytest.mark.parametrize(('mat1', 'mat2'), [(mat_a, mat_b), (mat_c, mat_d), (mat_d, mat_e),
66+
(mat_f, mat_h)])
67+
def test_multiplication(mat1, mat2):
68+
# Catch when user enters a single integer value rather than a mat
69+
# Check for known errors
70+
if (np.array(mat1)).shape < (2, 2) or (np.array(mat2)).shape < (2, 2):
71+
with pytest.raises(TypeError):
72+
assert matop.add(mat1, mat2)
73+
elif (np.array(mat1)).shape == (np.array(mat2)).shape:
74+
act = (np.matmul(mat1, mat2)).tolist()
75+
theo = matop.multiply(mat1, mat2)
76+
assert theo == act
77+
else:
78+
# matrices have different dimensions
79+
# Check for known errors
80+
with pytest.raises(ValueError):
81+
assert matop.subtract(mat1, mat2)

0 commit comments

Comments
 (0)