-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathmatrix.py
More file actions
246 lines (198 loc) · 8.89 KB
/
matrix.py
File metadata and controls
246 lines (198 loc) · 8.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
#TODO: Make custom exceptions
#TODO: You shouldn't be able to delete a single element from a row, only full rows and columns
from random import randint
from copy import deepcopy
class Matrix(object):
def __init__(self, rows, columns):
self.rows = rows
self.columns = columns
self.matrix = []
for i in range(rows):
self.matrix.append([]) # Initialize empty rows
for row in self.matrix:
for i in range(columns):
row.append(0) # Fill the rows with 0s
def __repr__(self):
'''Print the matrix row after row.'''
rep = ""
for row in self.matrix:
rep += str(row)
rep += "\n"
return rep.rstrip()
def __getitem__(self, key):
return self.matrix[key]
def __setitem__(self, key, value):
if isinstance(value, list):
self.matrix[key] = value
else:
raise TypeError("A matrix object can only contain lists of numbers")
return
def __delitem__(self, key):
del(self.matrix[key])
self.rows = self.rows - 1
return
def __contains__(self, value):
for row in self.matrix:
for element in row:
if element == value:
return True
else:
pass
return False
def __eq__(self, otherMatrix):
if isinstance(otherMatrix, Matrix):
if (self.rows != otherMatrix.rows) or (self.columns != otherMatrix.columns):
return False # They don't have the same dimensions, they can't be equal
for row in range(self.rows): # Check the elements one by one
for column in range(self.columns):
if self.matrix[row][column] != otherMatrix[row][column]:
return False
return True
else:
return False
def __ne__(self, otherMatrix):
return not self.__eq__(otherMatrix) # Check for equality and reverse the result
def __add__(self, otherMatrix):
'''Add 2 matrices of the same type.'''
return self.__add_or_sub(otherMatrix, "add")
def __sub__(self, otherMatrix):
'''Subtracts otherMatrix from self.'''
return self.__add_or_sub(otherMatrix, "sub")
def __mul__(self, secondTerm):
if isinstance(secondTerm, (int, float, complex)):
return self.__scalar_product(secondTerm)
elif isinstance(secondTerm, Matrix):
if self.columns == secondTerm.rows:
newMatrix = Matrix(self.rows, secondTerm.columns)
transposeMatrix = secondTerm.transpose()
'''
Matrix multiplication is done iterating through each column of the
second term. We calculate the transpose of the second matrix because
it gives us a list for each column, which is far easier to iterate
through.
'''
for row_self in range(self.rows):
for row_transpose in range(transposeMatrix.rows):
'''
The rows of the transpose correspond to the columns
of the original matrix.
'''
new_element = 0
for column_self in range(self.columns):
new_element += (self[row_self][column_self] * transposeMatrix[row_transpose][column_self])
newMatrix[row_self][row_transpose] = new_element
return newMatrix
else:
raise Exception(
"Can't multiply (%d, %d) matrix with (%d, %d) matrix" %
(self.rows, self.columns, secondTerm.rows, secondTerm.columns)
)
else:
raise TypeError("Can't multiply a matrix by non-int of type " + type(secondTerm).__name__)
def __rmul__(self, secondTerm):
return self.__mul__(secondTerm)
def __scalar_product(self, number):
newMatrix = Matrix(self.rows, self.columns)
for row in range(self.rows):
for column in range(self.columns):
newMatrix[row][column] = self[row][column] * number
return newMatrix
def __add_or_sub(self, secondTerm, operation):
newMatrix = Matrix(self.rows, self.columns)
if isinstance(secondTerm, (int, float, complex)):
for row in range(self.rows):
for column in range(self.columns):
if operation == "add":
newMatrix[row][column] = self[row][column] + secondTerm
if operation == "sub":
newMatrix[row][column] = self[row][column] - secondTerm
elif isinstance(secondTerm, Matrix):
if (self.rows == secondTerm.rows) and (self.columns == secondTerm.columns):
for row in range(self.rows):
for column in range(self.columns):
if operation == "add":
newMatrix[row][column] = self[row][column] + secondTerm[row][column]
elif operation == "sub":
newMatrix[row][column] = self[row][column] - secondTerm[row][column]
else:
raise Exception("Invalid operation type")
else:
raise TypeError(
"Can't add or subtract (%d, %d) matrix with (%d, %d) matrix" %
(self.rows, self.columns, secondTerm.rows, secondTerm.columns)
)
else:
raise TypeError("Can only add or subtract a matrix with another matrix or a number")
return newMatrix
def is_square(self):
return self.rows == self.columns
def transpose(self):
newMatrix = Matrix(self.columns, self.rows)
for row in range(self.rows):
for column in range(self.columns):
newMatrix[column][row] = self.matrix[row][column] # a(i,j) = a(j,i)
return newMatrix
def complement_matrix(self, rowToDelete, columnToDelete):
newMatrix = deepcopy(self)
del(newMatrix[rowToDelete])
for row in range(newMatrix.rows):
del(newMatrix[row][columnToDelete])
newMatrix.columns -= 1
return newMatrix
def algebric_complement(self, row, column):
complementMatrix = self.complement_matrix(row, column)
algebricComplement = (-1)**(row+column) * complementMatrix.determinant()
return algebricComplement
def determinant(self):
'''
Return the determinant.
This function uses Laplace's theorem to calculate the determinant.
It is a very rough implementation, which means it becomes slower and
slower as the size of the matrix grows.
'''
if self.is_square():
if self.rows == 1:
# If it's a square matrix with only 1 row, it has only 1 element
det = self[0][0] # The determinant is equal to the element
elif self.rows == 2:
det = (self[0][0] * self[1][1]) - (self[0][1] * self[1][0])
else:
# We calculate the determinant using Laplace's theorem
det = 0
for element in range(self.columns):
det += self[0][element] * self.algebric_complement(0, element)
return det
else:
raise TypeError("Can only calculate the determinant of a square matrix")
def algebric_complements_matrix(self):
'''Return the matrix of all algebric complements.'''
if self.is_square():
newMatrix = Matrix(self.rows, self.columns)
for row in range(self.rows):
for column in range(self.columns):
newMatrix[row][column] = self.algebric_complement(row, column)
return newMatrix
else:
raise TypeError("Algebric complements can only be calculated on a square matrix")
def inverse_matrix(self):
'''Return the inverse matrix.'''
det = self.determinant()
if det == 0:
raise Exception("Matrix not invertible")
else:
algebricComplementsMatrix = self.algebric_complements_matrix()
inverseMatrix = 1/det * algebricComplementsMatrix.transpose()
return inverseMatrix
def symmetric_part(self):
'''Return the symmetric part of the matrix.'''
newMatrix = 1/2 * (self + self.transpose())
return newMatrix
def antisymmetric_part(self):
'''Return the antisymmetric part of the matrix.'''
newMatrix = 1/2 * (self - self.transpose())
return newMatrix
def random(self, lower=-5, upper=5):
'''Fill the matrix with random numbers (integers).'''
for row in self.matrix:
for i in range(self.columns):
row[i] = randint(lower, upper)