Package pod :: Module linalg
[frames] | no frames]

Source Code for Module pod.linalg

  1  """ 
  2  Basic linear algebra components and functions. 
  3   
  4  @author: Christophe Alexandre <ch.alexandre at bluewin dot ch> 
  5   
  6  @license: 
  7  Copyright(C) 2010 Christophe Alexandre 
  8   
  9  This program is free software: you can redistribute it and/or modify 
 10  it under the terms of the GNU Lesser General Public License as published by 
 11  the Free Software Foundation, either version 3 of the License, or 
 12  (at your option) any later version. 
 13   
 14  This program is distributed in the hope that it will be useful, 
 15  but WITHOUT ANY WARRANTY; without even the implied warranty of 
 16  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 17  GNU General Public License for more details. 
 18   
 19  You should have received a copy of the GNU Lesser General Public License 
 20  along with this program.  If not, see <http://www.gnu.org/licenses/lgpl.txt>. 
 21  """ 
 22   
 23  import math 
 24  import os 
 25  import logging 
 26   
 27  from util import NullHandler 
 28  from util import numbering 
 29  from util import prod_scalar 
 30   
 31  _h = NullHandler() 
 32  _logger = logging.getLogger('linalg') 
 33  _logger.addHandler(_h) 
 34   
35 -class Matrix(object):
36
37 - def __init__(self, dim_row, dim_col=None):
38 if dim_col is None: 39 dim_col = dim_row 40 self.dim_row = dim_row 41 self.dim_col = dim_col 42 self._vectors = dict()
43
44 - def transpose(self):
45 m = Matrix(self.get_dim_col(), self.get_dim_row()) 46 for i in range(self.get_dim_row()): 47 for j in range(self.get_dim_col()): 48 m.set_value(j, i, self.get_value(i, j)) 49 return m
50
51 - def get_value(self, i, j):
52 assert i < self.get_dim_row(), 'row %d exceeding dimension %d' % (i, self.get_dim_row()) 53 assert j < self.get_dim_col(), 'column %d exceeding dimension %d' % (j, self.get_dim_col()) 54 if self._vectors.has_key(i): 55 v = self._vectors[i] 56 else: 57 v = self._create_vector() 58 return v.get_component(j)
59
60 - def set_table(self, table):
61 assert len(table) == self.get_dim_row(), 'expected %d rows instead of %d'% (self.get_dim_row(), len(table)) 62 for i in range(self.get_dim_row()): 63 assert len(table[i]) == self.get_dim_col(), 'expected %d columns instead of %d'% (self.get_dim_col(), len(table[i])) 64 for j in range(self.get_dim_col()): 65 self.set_value(i, j, table[i][j])
66
67 - def get_table(self):
68 table = list() 69 for i in range(self.get_dim_row()): 70 row = list() 71 for j in range(self.get_dim_col()): 72 row.append(self.get_value(i, j)) 73 table.append(row) 74 return table
75
76 - def get_dimension(self):
77 return (self.dim_row, self.dim_col)
78
79 - def get_dim_row(self):
80 return self.get_dimension()[0]
81
82 - def get_dim_col(self):
83 return self.get_dimension()[1]
84
85 - def _create_vector(self):
86 return Vector(self.get_dimension()[1])
87
88 - def set_value(self, i, j, val):
89 msg = str(self.get_dimension()) 90 assert i < self.get_dim_row(), 'row %d exceeding dimension %s' % (i, msg) 91 assert j < self.get_dim_col(), 'column %d exceeding dimension %s' % (j, msg) 92 if self._vectors.has_key(i): 93 v = self._vectors[i] 94 else: 95 v = self._create_vector() 96 self._vectors[i] = v 97 try: 98 v.set_component(j, float(val)) 99 except TypeError, e: 100 logging.error('not a number: %s' % str(val)) 101 raise e
102
103 - def minor(self, r, c):
104 """ 105 Sub-matrix excluding the specified row and column. 106 """ 107 m = Matrix(self.get_dim_row() - 1, self.get_dim_col() - 1) 108 dr = self.get_dim_row() 109 dc = self.get_dim_col() 110 for k, i in numbering(range(0, r) + range(r+1, dr)): 111 for l, j in numbering(range(0, c) + range(c+1, dc)): 112 v = self.get_value(i, j) 113 m.set_value(k, l, v) 114 return m
115
116 - def determinant(self):
117 size = self.get_dim_row() 118 det = 0.0 119 # stopping condition 120 if size == 1: 121 det = self.get_value(0, 0) 122 else: 123 for i in range(size): 124 minor = self.minor(0, i) 125 if i % 2 == 0: 126 sign = 1.0 127 else: 128 sign = -1.0 129 det += sign * self.get_value(0, i) * minor.determinant() 130 return det
131
132 - def cofactors(self):
133 m = Matrix(self.get_dim_row()) 134 for row in range(self.get_dim_row()): 135 for col in range(self.get_dim_col()): 136 if row % 2 == col % 2: 137 sign = 1.0 138 else: 139 sign = -1.0 140 v = sign * self.minor(row, col).determinant() 141 m.set_value(row, col, v) 142 return m
143
144 - def multiply(self, m):
145 return prod_matrix(self, m)
146
147 - def sub(self, m):
148 msg = 'matrices with different dimensions (%s - %s)' % (str(self.get_dimension()), str(m.get_dimension())) 149 assert m.get_dimension() == self.get_dimension(), msg 150 n = Matrix(self.get_dim_row(), self.get_dim_col()) 151 for row in range(self.get_dim_row()): 152 for col in range(self.get_dim_col()): 153 v = self.get_value(row, col) - m.get_value(row, col) 154 n.set_value(row, col, v) 155 return n
156
157 - def add(self, m):
158 assert m.get_dimension() == self.get_dimension(), 'incompatible dimensions' 159 n = Matrix(self.get_dim_row(), self.get_dim_col()) 160 for row in range(self.get_dim_row()): 161 for col in range(self.get_dim_col()): 162 v = self.get_value(row, col) + m.get_value(row, col) 163 n.set_value(row, col, v) 164 return n
165
166 - def scale(self, factor):
167 m = Matrix(self.get_dim_row(), self.get_dim_col()) 168 for row in range(self.get_dim_row()): 169 for col in range(self.get_dim_col()): 170 v = self.get_value(row, col) * factor 171 m.set_value(row, col, v) 172 return m
173
174 - def inverse(self):
175 if self.get_dimension() == (1,1): 176 m = Matrix(1) 177 m.set_value(0, 0, 1.0 / self.get_value(0, 0)) 178 return m 179 det_inv = 1.0 / self.determinant() 180 cofactors = self.cofactors() 181 cofactors = cofactors.transpose() 182 i = cofactors.scale(det_inv) 183 return i
184
185 - def pseudo_inverse(self):
186 """ 187 Full row rank pseudo inverse. 188 189 A+ = transp(A).inv(A.transp(A)) 190 """ 191 a = self 192 t_a = a.transpose() 193 a_t_a = a.multiply(t_a) 194 inv_a_transp_a = a_t_a.inverse() 195 return t_a.multiply(inv_a_transp_a)
196
197 - def copy(self):
198 c = Matrix(self.get_dim_row(), self.get_dim_col()) 199 for row in range(self.get_dim_row()): 200 for col in range(self.get_dim_col()): 201 c.set_value(row, col, self.get_value(row, col)) 202 return c
203
204 - def __repr__(self):
205 out = '(M%dx%d)' % (self.get_dim_row(), self.get_dim_col()) + os.linesep 206 for row in range(self.get_dim_row()): 207 line = [] 208 for col in range(self.get_dim_col()): 209 line.append(self.get_value(row, col)) 210 out += ', '.join(map(str, line)) + os.linesep 211 return out
212
213 -class VectorMatrix(Matrix):
214 """ 215 Makes a vector compatible with Matrix operations. 216 """ 217
218 - def __init__(self, vector):
219 """ 220 Column vector. 221 222 Use transpose() to turn it into a row vector. 223 """ 224 Matrix.__init__(self, vector.get_length(), 1) 225 for row in range(vector.get_length()): 226 self.set_value(row, 0, vector.get_component(row))
227
228 -class Vector(object):
229 """ 230 @todo: should somehow be merged with VectorMatrix. 231 """ 232
233 - def __init__(self, dim):
234 self._dim = dim 235 self._values = dict()
236
237 - def get_length(self):
238 return self._dim
239
240 - def set_values(self, *values):
241 """ Using specified values to initialize the vector. """ 242 for n, v in numbering(values): 243 self.set_component(n, v) 244 return self
245
246 - def set_data(self, data):
247 """ Using raw data (python list) to initialize the vector. """ 248 self.set_values(*data)
249
250 - def get_component(self, i):
251 assert i < self.get_length(), 'index %d exceeding dimension %d' % (i, self.get_length()) 252 assert i >= 0, 'non positive index %d' % i 253 if not self._values.has_key(i): 254 return 0.0 255 else: 256 return self._values[i]
257
258 - def set_component(self, i, v):
259 assert i < self.get_length(), 'index %d exceeding dimension %d' % (i, self.get_length()) 260 assert i >= 0, 'non positive index %d' % i 261 self._values[i] = v
262
263 - def get_data(self):
264 """ Raw data as built-in python list.""" 265 l = list() 266 for i in range(self.get_length()): 267 l.append(self.get_component(i)) 268 return l
269
270 - def sub(self, vector):
271 result = Vector(self.get_length()) 272 for i in range(self.get_length()): 273 result.set_component(i, self.get_component(i) - vector.get_component(i)) 274 return result
275
276 - def add(self, vector):
277 result = Vector(self.get_length()) 278 for i in range(self.get_length()): 279 result.set_component(i, self.get_component(i) + vector.get_component(i)) 280 return result
281
282 - def scale(self, a):
283 result = Vector(self.get_length()) 284 for i in range(self.get_length()): 285 result.set_component(i, a * self.get_component(i)) 286 return result
287
288 - def product(self, vector):
289 return prod_scalar(vector.get_data(), self.get_data())
290
291 - def norm(self):
292 return math.sqrt(self.product(self))
293
294 - def symmetric(self):
295 null_vector = Vector(self.get_length()) 296 return null_vector.sub(self)
297
298 - def units(self, unit_vector):
299 """ 300 How many times the current vector fits in the specified units (in norm). 301 302 The sign has a meaning only if both vectors are colinear. 303 """ 304 ratio = self.norm() / unit_vector.norm() 305 if self.sub(unit_vector).norm() > unit_vector.norm(): 306 # vectors are in opposite directions 307 ratio = -ratio 308 return ratio
309
310 - def __eq__(self, other):
311 return self._values == other._values
312
313 - def __ne__(self, other):
314 return self._values != other._values
315
316 - def __repr__(self):
317 line = [] 318 for col in range(self.get_length()): 319 line.append(self.get_component(col)) 320 out = '(V)[' + ', '.join(map(str, line)) + ']' 321 return out
322
323 - def __hash__(self):
324 return hash(tuple(self.get_data()))
325
326 -class Point(Vector):
327 """ 328 Equivalent to a Vector 329 """
330 - def __init__(self, *coordinates):
331 Vector.__init__(self, len(coordinates)) 332 self.set_values(*coordinates)
333
334 - def __repr__(self):
335 line = [] 336 for col in range(self.get_length()): 337 line.append(self.get_component(col)) 338 out = '(P)[' + ', '.join(map(str, line)) + ']' 339 return out
340
341 -def identity(dimension):
342 matrix = Matrix(dimension) 343 for i in range(dimension): 344 matrix.set_value(i, i, 1.0) 345 return matrix
346
347 -def zero(dimension):
348 zeros = [0.0] * dimension 349 return Point(*zeros)
350
351 -def prod_matrix(m1, m2):
352 msg = 'incompatible dimensions: %s x %s' % (str(m1.get_dimension()), str(m2.get_dimension())) 353 assert m1.get_dim_col() == m2.get_dim_row(), msg 354 result = Matrix(m1.get_dim_row(), m2.get_dim_col()) 355 for i in range(m1.get_dim_row()): 356 for j in range(m2.get_dim_col()): 357 v = 0.0 358 for k in range(m1.get_dim_col()): 359 v += m1.get_value(i, k) * m2.get_value(k, j) 360 result.set_value(i, j, v) 361 return result
362