Cope 2.5.0
My personal "standard library" of all the generally useful code I've written for various projects over the years
Loading...
Searching...
No Matches
linalg.py
1# from .decorators import reprise, untested
2from ..debugging import debug
3from .imports import dependsOnPackage, ensureImported
4import sympy as sp
5from sympy import Matrix, ImmutableMatrix, latex, sympify, pprint, eye, randMatrix, flatten, sqrt, zeros, Integer, Float
6# TODO:
7# import ezregex as er
8
9def matrix(string, rows=None, cols=None, cp=False, np=False, immutable=False, verbose=False):
10 # Parse params
11 assert rows is None or cols is None
12 if np:
13 type_ = _np.array
14 # Yes, this is a despicable line of code, I know.
15 # All this does is makes a function that casts everything in a nested list to a float
16 # The one 2 lines down does the same thing, except it sympifies it instead
17 _caster = lambda x: list(map(lambda a: list(map(lambda b: float(b), a)), x))
18 else:
19 type_ = Matrix if not immutable else ImmutableMatrix
20 _caster = _caster = lambda x: list(map(lambda a: list(map(lambda b: sympify(b), a)), x))
21
22 if string.startswith('c'):
23 cols = True
24 string = string[1:]
25 elif string.startswith('r'):
26 rows = True
27 string = string[1:]
28 elif rows is None and cols is None:
29 rows = True
30
31 num = er.anyOf(er.optional('-')+er.matchMax(er.anyCharExcept(er.space))+er.space, er.optional('-')+er.optional(r'\.')+er.anything).compile()
32
33 if cols:
34 _cols = string.split(';')
35 _cols = [num.findall(i) for i in _cols]
36 _rows = []
37 debug(_cols, active=verbose)
38 # Rotate them (make the rows columns)
39 # EDIT: Apparently this is called a "transpose", and I couldn've just
40 # done Matrix.T instead
41 # ...but this works, so I'm not gonna touch it
42 for c in range(len(_cols[0])):
43 _rows.append([])
44 for i in _cols:
45 _rows[-1].append(i[c])
46 ans = type_(_caster(_rows))
47 else:
48 _rows = string.split(';')
49 debug(_rows, active=verbose)
50 ans = type_(_caster([num.findall(i) for i in _rows]))
51
52 if cp:
53 from clipboard import copy
54 copy(latex(ans))
55
56 return ans
57
58# This doesn't quite work yet, I'll make it general purpose later
59def combineMatricies(*mats):
60 rtn = mats[0]
61 index = 1
62 for c in mats[1:]:
63 # if cols:
64 rtn = rtn.col_insert(index, c)
65 index += c.cols
66 # elif rows:
67 # rtn = rtn.row_insert(index, c)
68 # index += c.rows - 1
69 return rtn
70
71class Space(sp.matrices.matrices.MatrixSubspaces):
72 def __init__(self, *bases):
73 if len(bases) == 1 and isinstance(bases[0], (list, tuple)):
74 self.bases = self.basis = bases[0]
75 else:
76 self.bases = self.basis = ensureIterable(bases)
77
78 def __contains__(self, mat):
79 try:
80 return any(Matrix.hstack(*self.bases).LUsolve(mat))
81 except (ValueError, ShapeError) as err:
82 return False
83
84 def __str__(self):
85 return prettyMats(self.bases)
86
87 @property
88 def orthogonal_basis(self):
89 return Matrix.orthogonalize(*self.basis)
90
91 @property
92 def orthonormal_basis(self):
93 return Matrix.orthogonalize(*self.basis, normalize=True)
94
95 def orthogonalized(self):
96 return Space(self.orthogonal_basis)
97
98 def orthonormalized(self):
99 return Space(self.orthonormal_basis)
100
101 def projectionMatrix(self):
102 U = combineMatricies(*self.orthonormal_basis)
103 return U*U.T
104
105# Because Matrix.columnspace doesn't work how my teacher says it does
106def columnspace(M):
107 return Space([M.col(pivot) for pivot in M.rref()[1]])
108
109def eigenspaces(mat):
110 return {i[0]: Space(i[2]) for i in mat.eigenvects()}
111
112def convert2Equs(mat, vars):
113 """ Converts a matrix into the list of equations it represents """
114 vars = list(vars)
115 assert mat.cols == len(vars), f'Must have exactly 1 variable for each column ({mat.cols} columns, {len(vars)} variables)'
116 eqs = []
117 for i in range(mat.rows):
118 eq = 0
119 for cnt, e in enumerate(mat.row(i)):
120 eq += e*vars[cnt]
121 eqs.append(eq)
122 return eqs
123
124def isSimilar(*args):
125 debug('This is wrong', clr=-1)
126 first = args[0].eigenvals()
127 return all(i == first for i in args)
128
129# Sympy method
130def steadyState(stochastic, accuracy=10000000000000, verbose=False):
131 import numpy as np
132 if isinstance(stochastic, np.ndarray):
133 stochastic = Matrix(stochastic)
134 numpy = True
135 else:
136 numpy = False
137 # Make sure it's a square matrix
138 assert stochastic.cols == stochastic.rows, "Stochastic matricies must be square"
139 P = stochastic-eye(stochastic.cols)
140 # Yes, this is terrible.
141 # This multiplies everything by a big number, then casts it to an int
142 # so the stupid float rounding errors will go away.
143 # We actually shouldn't need to cast it back, because rref will
144 # just descale it for us
145 # I definitely *didn't* just spend all afternoon getting this to work
146 if verbose:
147 print('P:')
148 pprint(P)
149 P = Matrix(np.array((P*accuracy).tolist(), dtype=int))
150 if verbose:
151 print('Cast P:')
152 pprint(P)
153 print('RREF(P):')
154 pprint(P.rref()[0])
155 # w = ensureNotIterable(P.nullspace())
156 w = [m / sum(flatten(m)) for m in P.nullspace()]
157 if verbose:
158 print('Answers:')
159 pprint(w)
160 # I could double check, but re-fixing the rounding errors isn't worth it
161 # assert stochastic * ans == ans
162 return [(matrix2numpy(ans, dtype=float) if numpy else ans) for ans in w]
163
164@dependsOnPackage('sympy', ('randMatrix', 'flatten', 'Matrix'))
165def randMarkovState(rows, balanced=True, np=False):
166 cast = matrix2numpy if np else lambda a, **_: a
167 if balanced:
168 r = randMatrix(rows, 1)
169 return cast(r / sum(flatten(r.tolist())), dtype=float)
170 else:
171 tmp = ([1] + ([0]*(rows-1)))
172 shuffle(tmp)
173 return cast(Matrix([tmp]).T, dtype=float)
174
175@dependsOnPackage('sympy', 'Matrix')
176def isOrthogonal(*vects, innerProduct=None):
177 if innerProduct is None:
178 innerProduct = Matrix.dot
179 return not any([innerProduct(v1, v2) for v2 in vects for v1 in vects if v1 != v2])
180
181# a = matrix('c111')
182# b = matrix('c5-2-3')
183# c = matrix('c1-10')
184# # a and c are, a and b are, b and c are not
185# assert isOrthogonal(a, b)
186# assert not isOrthogonal(a, b, c)
187
188@dependsOnPackage('sympy', 'sqrt')
189def vectorLength(v):
190 assert v.cols == 1
191 return sqrt(sum([i**2 for i in v]))
192
193def EuclideanDist(u, v):
194 return vectorLength(u-v)
195
196# @untested
197def manhattanDist(a, b):
198 assert len(a) == len(b)
199 return sum(abs(a[i] - b[i]) for i in range(len(a)))
200
201# @untested
202def minkowskiDist(a, b, p):
203 assert len(a) == len(b)
204 return sum([abs(a[i] - b[i])**p for i in range(len(a))])**(1/p)
205
206# assert vectorLength(matrix('c111')) == sqrt(3)
207
208# def project(v, onto) -> '(proj(v), w)':
209# projected = (onto.dot(v) / onto.dot(onto)) * onto
210# return projected, v - projected
211
212# u = matrix('c1-10')
213# v = matrix('c5-2-3')
214# assert sum(project(v, u), start=matrix('c000')) == v
215# assert Matrix.orthogonalize(u, v)[1] == project(v, u)[1]
216
217def orthonormalize(orthogonalSet):
218 return [v / vectorLength(v) for v in orthogonalSet]
219
220# alignedV, w = project(v, u)
221# assert Matrix.orthogonalize(u, w, normalize=True) == orthonormalize((u, w))
222
223def splitVector(y:'Matrix', W:'Space', innerProduct=None) -> list:
224 """ Returns vectors in W which can be linearly combined to get y """
225 if innerProduct is None:
226 innerProduct = Matrix.dot
227 return [(innerProduct(y, u) / innerProduct(u, u))*u for u in W.orthogonal_basis]
228
229# assert splitVector(matrix('c9-7'), Space(matrix('c2-3'), matrix('c64'))) == [matrix('c6-9'), matrix('c32')]
230# for _ in range(100):
231# rows = randint(2, 10)
232# v = randMatrix(rows, 1)
233# assert sum(splitVector(v, Space([randMatrix(rows, 1) for _ in range(rows)])), start=zeros(rows, 1)) == v
234
235def project(y:'Matrix', W:'Space', innerProduct=None) -> '(vector in W, vector in W⟂)':
236 """ W = a subspace of R^n we want to describe y with (to get proj_y)
237 y = some y in R^n
238 proj_y = a vector in W
239 z = a vector in W⟂ (W perp) """
240 if innerProduct is None:
241 innerProduct = Matrix.dot
242 proj_y = sum(splitVector(y, W, innerProduct), start=zeros(y.rows, 1))
243 z = y - proj_y
244 # While this should be true, rounding errors.
245 # assert proj_y + z == y
246 return proj_y, z
247
248# assert project(matrix('c123-1'), Space((matrix('c1111'), matrix('c1-11-1')))) == \
249# (Matrix([
250# [ 2],
251# [1/2],
252# [ 2],
253# [1/2]]),
254# Matrix([
255# [ -1],
256# [ 3/2],
257# [ 1],
258# [-3/2]]))
259
260def normalizePercentage(p, error='Percentage is of the wrong type (int or float expected)'):
261 if isinstance(p, (int, Integer)):
262 return p / 100
263 elif isinstance(p, (float, Float)):
264 return p
265 elif isinstance(p, bool):
266 if p is True:
267 return 1.
268 else:
269 return 0.
270 else:
271 if error is not None:
272 raise TypeError(error)
273
274# Eq(matrix('4-17-4;-53-51;7-802;6-807', cols=1)*matrix('x_1 x_2 x_3', cols=1), matrix('6-80-7', cols=1))
275# debug(matrix('x_1 x_2 x_3 ', cols=1))
276# debug(matrix('1 -2 10 '))
277# debug(matrix('c1 -2 10 '))
278# debug(matrix('c.1 -2 10 '))
279# debug(matrix('c.1 -.2 10 '))
280# debug(matrix('20;02' ,verbose=1))
281# debug(matrix('c1-3' ,verbose=1))
282# debug(matrix('c2-6'))
283# debug(matrix('20;02') )
284# debug(matrix('cab') )
285# Matrix([[2*var('a')], [2*var('b')]])
286# debug(matrix('4-17-4;-53-51;7-802;6-807', cols=1))