-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathumatrix.py
260 lines (253 loc) · 11.2 KB
/
umatrix.py
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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
__version__ = "1.1.3"
def eye(order):
return matrix(*[[int(i == j) for j in range(order)] for i in range(order)])
def fill(x, order, num_cols=None):
num_cols = num_cols or order
return matrix(*[[x for _ in range(num_cols)] for __ in range(order)])
def zeros(order, num_cols=None):
return fill(0, order, num_cols)
def ones(order, num_cols=None):
return fill(1, order, num_cols)
def _round(v, places=0):
if not isinstance(v, complex):
return round(v, places)
return round(v.real, places) + round(v.imag, places) * 1j
def typecheck(val):
return isinstance(val, int) or isinstance(val, float) or isinstance(val, complex)
class matrix:
def __init__(self, *content, are_rows=True):
assert all([typecheck(x) for y in content for x in y]), "Not all coefficients are of type int, float, or complex."
assert all([len(entry) == len(content[0]) for entry in content]), "Given {} do not have the same length.".format("rows" if are_rows else "columns")
self.rows = list(content) if are_rows else [[col[i] for col in content] for i in range(len(content[0]))]
def __repr__(self):
rows = self.rows
shape = len(rows), len(rows[0])
pretty_print = max([len(str(x)) for y in rows for x in y]) if shape[0] > 1 else 1
r = "matrix( "
for i in range(shape[0]):
r += "["
for j in range(shape[1]):
r += (pretty_print-len(str(rows[i][j])))*" "+str(rows[i][j]) + ",\t"*(j!=shape[1]-1)
r += "],\n\t" if i != shape[0]-1 else "]"
return r + " )"
def __str__(self):
rows = self.rows
shape = len(rows), len(rows[0])
pretty_print = max([len(str(x)) for y in rows for x in y]) if shape[0] > 1 else 1
s = "["
for i in range(shape[0]):
if i:
s += " "
for j in range(shape[1]):
s += (pretty_print-len(str(rows[i][j])))*" "+str(rows[i][j]) + ",\t"*(j!=shape[1]-1)
s += ",\n" if i != shape[0]-1 else "]"
return s
@property
def cols(self):
return [[row[i] for row in self.rows] for i in range(len(self.rows[0]))]
@property
def shape(self):
return len(self.rows), len(self.rows[0])
def reshape(self, m, n=None, inplace=False):
shape = len(self.rows), len(self.rows[0])
flat = [self.rows[i][j] for i in range(shape[0]) for j in range(shape[1])]
assert (m if n is None else m*n) == shape[0]*shape[1], "Shape {} is not compatible with current shape {}.".format((1,m) if n is None else (m,n), shape)
if not inplace:
return matrix(flat) if n is None else matrix(*[flat[i*n:i*n+n] for i in range(m)])
self.rows = [flat] if n is None else [flat[i*n:i*n+n] for i in range(m)]
def apply(self, func, inplace=False):
assert callable(func), "First argument must be a callable function with an int, float, or complex return."
rows = self.rows
if not inplace:
return matrix(*[[func(rows[i][j]) for j in range(len(rows[0]))] for i in range(len(rows))])
for i in range(len(rows)):
for j in range(len(rows[0])):
self.rows[i][j] = func(rows[i][j])
def copy(self):
return matrix(*[[self.rows[i][j] for j in range(len(self.rows[0]))] for i in range(len(self.rows))])
def round(self, places=0, inplace=False):
if not inplace:
rows = self.rows
return matrix(*[[_round(rows[i][j],places) for j in range(len(rows[0]))] for i in range(len(rows))])
for i in range(len(rows)):
for j in range(len(rows[0])):
self.rows[i][j] = _round(rows[i][j],places)
def __eq__(self, other):
assert isinstance(other, matrix), "Cannot compare matrix and {}.".format(type(other))
rows = self.rows
shape = len(rows), len(rows[0])
if shape != other.shape:
return False
for i in range(shape[0]):
for j in range(shape[1]):
if rows[i][j] != other.rows[i][j]:
return False
return True
def __ne__(self, other):
return not self.__eq__(other)
def __pos__(self):
return self.copy()
def __neg__(self):
return matrix(*[[-self.rows[i][j] for j in range(len(self.rows[0]))] for i in range(len(self.rows))])
def __add__(self, other):
assert isinstance(other, matrix), "Cannot add matrix and {}.".format(type(other))
rows = self.rows
shape = len(rows), len(rows[0])
assert shape == other.shape
return matrix(*[[rows[i][j]+other.rows[i][j] for j in range(shape[1])] for i in range(shape[0])])
def __radd__(self, other):
return self.__add__(other)
def __iadd__(self, other):
self = self.__add__(other)
return self
def __sub__(self, other):
assert isinstance(other, matrix), "Cannot subtract {} from matrix.".format(type(other))
rows = self.rows
shape = len(rows), len(rows[0])
assert shape == other.shape
return matrix(*[[rows[i][j]-other.rows[i][j] for j in range(shape[1])] for i in range(shape[0])])
def __rsub__(self, other):
return other.__sub__(self)
def __isub__(self, other):
self = self.__sub__(other)
return self
def __mul__(self, other):
assert isinstance(other, matrix) or typecheck(other), "Cannot multiply matrix and {}.".format(type(other))
rows = self.rows
if isinstance(other, matrix):
assert len(rows[0]) == len(other.rows), "Incompatible matrix sizes {} and {}.".format(self.shape, other.shape)
return matrix(*[[sum([k for k in map(lambda x,y: x*y, row, col)]) for col in other.cols] for row in rows])
return matrix(*[[rows[i][j]*other for j in range(len(rows[0]))] for i in range(len(rows))])
def __rmul__(self, other):
return other.__mul__(self) if isinstance(other, matrix) else self.__mul__(other)
def __imul__(self, other):
self = self.__mul__(other)
return self
def __pow__(self, exponent):
assert isinstance(exponent, int), "Exponent must be an int, cannot be {}.".format(type(exponent))
if exponent < 0:
return self.copy() if not exponent%2 else self.inverse()
if not exponent:
return eye(self.order)
result = self.copy()
for i in range(1, exponent):
result *= self
return result
def __abs__(self):
return self.det
def __getitem__(self, *args):
if isinstance(args[0], tuple):
a00, a01, rows = args[0][0], args[0][1], self.rows
if isinstance(a00, slice):
if isinstance(a01, slice):
return matrix(*[row[a01] for row in rows[a00]])
return matrix(*[[row[a01]] for row in rows[a00]])
elif isinstance(a01, slice):
return matrix(*[row[a01] for row in [rows[a00]]])
return matrix(*[[row[a01]] for row in [rows[a00]]])
return self.rows[args[0]]
def __setitem__(self, *args):
sub = args[1]
if isinstance(args[0], tuple):
norm_slice = lambda s, rows: (s.start if s.start is not None else 0, s.stop if s.stop is not None else (len(self.rows) if rows else len(self.rows[0])), s.step if s.step is not None else 1)
a00, a01 = args[0]
a00_slice, a01_slice = [isinstance(x, slice) for x in [a00, a01]]
if a00_slice:
a00_iter = norm_slice(a00, True)
if a01_slice:
a01_iter = norm_slice(a01, False)
if a00_slice:
if a01_slice:
assert all([len(s) == (a01_iter[1]-a01_iter[0])//a01_iter[2] for s in sub])
assert all([typecheck(x) for y in sub for x in y])
for i in range(*a00_iter):
for j in range(*a01_iter):
self.rows[i][j] = sub[(i//a00_iter[2])-a00_iter[0]][(j//a01_iter[2])-a01_iter[0]]
else:
assert len(sub) == (a00_iter[1]-a00_iter[0]+1)//a00_iter[2]
assert all([typecheck(x) for x in sub])
for i in range(*a00_iter):
self.rows[i][a01] = sub[(i-a00_iter[0])//a00_iter[2]]
elif a01_slice:
assert len(sub) == (a01_iter[1]-a01_iter[0]+1)//a01_iter[2]
assert all([typecheck(x) for x in sub])
for j in range(*a01_iter):
self.rows[a00][j] = sub[(j-a01_iter[0])//a01_iter[2]]
else:
check = len(self.rows) - (args[0].start if isinstance(args[0], slice) else 0)
assert len(sub) == check, "Replacement has length {}, should be {}".format(len(sub), check)
assert all([typecheck(x) for y in sub for x in y] if isinstance(args[0], slice) else [typecheck(x) for x in sub])
self.rows[args[0]] = sub
@property
def order(self):
return len(self.rows)
@property
def is_square(self):
return len(self.rows) == len(self.rows[0])
@property
def transpose(self):
return matrix(*[[self.rows[i][j] for i in range(len(self.rows))] for j in range(len(self.rows[0]))])
@property
def trace(self):
rows = self.rows
order = len(rows)
assert order == len(rows[0]), "Matrix must be square."
return sum([rows[i][i] for i in range(order)])
@property
def det(self):
rows = self.rows
order = len(rows)
assert order == len(rows[0]), "Matrix must be square."
if order == 1:
return rows[0][0]
elif order == 2:
a,b,c,d = [rows[i][j] for i in range(2) for j in range(2)]
return a*d - b*c
elif order == 3:
a,b,c,d,e,f,g,h,i = [rows[i][j] for i in range(3) for j in range(3)]
return a*e*i+d*h*c+g*b*f - g*e*c-a*h*f-d*b*i
elif order == 4:
a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p = [rows[i][j] for i in range(4) for j in range(4)]
return a*(f*k*p+j*o*h+n*g*l - n*k*h-f*o*l-j*g*p) - b*(e*k*p+i*o*h+m*g*l - m*k*h-e*o*l-i*g*p) + c*(e*j*p+i*n*h+m*f*l - m*j*h-e*n*l-i*f*p) - d*(e*j*o+i*n*g+m*f*k - m*j*g-e*n*k-i*f*o)
raise NotImplementedError("Can't handle matrices > 4x4.")
@property
def inverse(self):
rows = self.rows
order = len(rows)
assert order == len(rows[0]), "Matrix must be square."
if order == 1:
x = rows[0][0]
assert x, "Matrix is singular."
return matrix([1/x])
elif order == 2:
a,b,c,d = [rows[i][j] for i in range(2) for j in range(2)]
det = a*d-b*c
assert det, "Matrix is singular."
return matrix([d/det,-b/det],[-c/det,a/det])
elif order == 3:
a,b,c,d,e,f,g,h,i = [rows[i][j] for i in range(3) for j in range(3)]
det = a*e*i+d*h*c+g*b*f - g*e*c-a*h*f-d*b*i
assert det, "Matrix is singular."
return matrix([(e*i-f*h)/det,(-b*i+c*h)/det,(b*f-c*e)/det],
[(-d*i+f*g)/det,(a*i-c*g)/det,(-a*f+c*d)/det],
[(d*h-e*g)/det,(-a*h+b*g)/det,(a*e-b*d)/det])
elif order == 4:
a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p = [rows[i][j] for i in range(4) for j in range(4)]
M00, _M01, M02, _M03 = f*k*p+j*o*h+n*g*l-n*k*h-f*o*l-j*g*p, -e*k*p-i*o*h-m*g*l+m*k*h+e*o*l+i*g*p, e*j*p+i*n*h+m*f*l-m*j*h-e*n*l-i*f*p, -e*j*o-i*n*g-m*f*k+m*j*g+e*n*k+i*f*o
det = a*M00 + b*_M01 + c*M02 + d*_M03
assert det, "Matrix is singular"
return matrix([M00/det, (-b*k*p-j*o*d-n*c*l+n*k*d+b*o*l+j*c*p)/det, (b*g*p+f*o*d+n*c*h-n*g*d-b*o*h-f*c*p)/det, (-b*g*l-f*k*d-j*c*h+j*g*d+b*k*h+f*c*l)/det],
[_M01/det, (a*k*p+i*o*d+m*c*l-m*k*d-a*o*l-i*c*p)/det, (-a*g*p-e*o*d-m*c*h+m*g*d+a*o*h+e*c*p)/det, (a*g*l+e*k*d+i*c*h-i*g*d-a*k*h-e*c*l)/det],
[M02/det, (-a*j*p-i*n*d-m*b*l+m*j*d+a*n*l+i*b*p)/det, (a*f*p+e*n*d+m*b*h-m*f*d-a*n*h-e*b*p)/det, (-a*f*l-e*j*d-i*b*h+i*f*d+a*j*h+e*b*l)/det],
[_M03/det, (a*j*o+i*n*c+m*b*k-m*j*c-a*n*k-i*b*o)/det, (-a*f*o-e*n*c-m*b*g+m*f*c+a*n*g+e*b*o)/det, (a*f*k+e*j*c+i*b*g-i*f*c-a*j*g-e*b*k)/det])
raise NotImplementedError("Can't handle matrices > 4x4.")
def is_eigenvalue(self, value):
return not (self - value*eye(self.order)).det
def is_eigenvector(self, vector, value):
assert isinstance(vector, tuple) or isinstance(vector, list) or isinstance(vector, matrix)
if isinstance(vector, matrix):
assert vector.shape == (len(self.rows), 1)
else:
assert len(vector) == len(self.rows)
vector = matrix(vector, are_rows=False)
return self*vector == vector*value