1
2 """
3 Created on Wed Jul 23 19:15:22 2014
4
5 Copyright (c) 2013-2014, CEA/DSV/I2BM/Neurospin. All rights reserved.
6
7 @author: Tommy Löfstedt
8 @email: tommy.loefstedt@cea.fr
9 @license: BSD 3-clause.
10 """
11 import abc
12 import time
13
14 import numpy as np
15 import scipy.sparse as sparse
16
17 import consts
18
19 __all__ = ["MultipartArray"]
20
21
23
24 - def __init__(self, parts, vertical=True):
25
26 self.parts = list(parts)
27 self.vertical = vertical
28
29 self.common_dim = 1 if vertical else 0
30 self.shape = [0, 0]
31 self.shape[self.common_dim] = self.parts[0].shape[self.common_dim]
32 self.multipart_shape = []
33
34 for i in xrange(len(self.parts)):
35 if len(self.parts[i].shape) != 2:
36 raise ValueError("MultipartArray is only defined for 2D " \
37 "arrays")
38 if not self.parts[0].shape[self.common_dim] \
39 == self.parts[i].shape[self.common_dim]:
40 raise ValueError("Input vectors must have a common dimension")
41 self.multipart_shape.append(
42 self.parts[i].shape[1 - self.common_dim])
43 self.shape[1 - self.common_dim] += self.multipart_shape[-1]
44
45 self.shape = tuple(self.shape)
46 self.multipart_shape = tuple(self.multipart_shape)
47
50
52 if self.vertical:
53 return np.vstack(self.parts)
54 else:
55 return np.hstack(self.parts)
56
62
63 - def __iop(self, other, op):
64 if np.isscalar(other):
65 for i in xrange(len(self.parts)):
66 if op == self.__ops.add:
67 self.parts[i] += other
68 elif op == self.__ops.sub:
69 self.parts[i] -= other
70 elif op == self.__ops.mul:
71 self.parts[i] *= other
72 elif op == self.__ops.div:
73 self.parts[i] *= 1.0 / other
74 else:
75 raise ValueError("Operator not yet implemented!")
76 elif isinstance(other, MultipartArray):
77 other_parts = other.get_parts()
78 for i in xrange(len(self.parts)):
79 if op == self.__ops.add:
80 self.parts[i] += other_parts[i]
81 elif op == self.__ops.sub:
82 self.parts[i] -= other_parts[i]
83 elif op == self.__ops.mul:
84 self.parts[i] *= other_parts[i]
85 else:
86 raise ValueError("Operator not yet implemented!")
87 elif self.shape == other.shape:
88 start = 0
89 end = 0
90 for i in xrange(len(self.parts)):
91 if self.vertical:
92 end += self.parts[i].shape[0]
93 if op == self.__ops.add:
94 self.parts[i] += other[start:end, :]
95 elif op == self.__ops.sub:
96 self.parts[i] -= other[start:end, :]
97 elif op == self.__ops.mul:
98 self.parts[i] *= other[start:end, :]
99 else:
100 raise ValueError("Operator not yet implemented!")
101 else:
102 end += self.parts[i].shape[1]
103 if op == self.__ops.add:
104 self.parts[i] += other[:, start:end]
105 elif op == self.__ops.sub:
106 self.parts[i] -= other[:, start:end]
107 elif op == self.__ops.mul:
108 self.parts[i] *= other[:, start:end]
109 else:
110 raise ValueError("Operator not yet implemented!")
111 start = end
112 else:
113 raise ValueError("Unknown type")
114
115 return self
116
119
122
125
127 if not np.isscalar(other):
128 raise ValueError("Operator not yet implemented for type!")
129
130 return self.__iop(other, self.__ops.div)
131
133 if not np.isscalar(other):
134 raise ValueError("Operator not yet implemented for type!")
135
136 return self.__iop(float(other), self.__ops.div)
137
138 - def __op(self, other, op):
139 new_parts = [0] * len(self.parts)
140 if np.isscalar(other):
141 for i in xrange(len(self.parts)):
142 if op == self.__ops.add:
143 new_parts[i] = self.parts[i] + other
144 elif op == self.__ops.sub:
145 new_parts[i] = self.parts[i] - other
146 elif op == self.__ops.mul:
147 new_parts[i] = self.parts[i] * other
148 elif op == self.__ops.div:
149 new_parts[i] = self.parts[i] * (1.0 / other)
150 else:
151 raise ValueError("Operator not yet implemented!")
152 elif isinstance(other, MultipartArray):
153 other_parts = other.get_parts()
154 for i in xrange(len(self.parts)):
155 if op == self.__ops.add:
156 new_parts[i] = self.parts[i] + other_parts[i]
157 elif op == self.__ops.sub:
158 new_parts[i] = self.parts[i] - other_parts[i]
159 elif op == self.__ops.mul:
160 new_parts[i] = self.parts[i] * other_parts[i]
161 else:
162 raise ValueError("Operator not yet implemented!")
163 elif self.shape == other.shape:
164 start = 0
165 end = 0
166 for i in xrange(len(self.parts)):
167 if self.vertical:
168 end += self.parts[i].shape[0]
169 if op == self.__ops.add:
170 new_parts[i] = self.parts[i] + other[start:end, :]
171 elif op == self.__ops.sub:
172 new_parts[i] = self.parts[i] - other[start:end, :]
173 elif op == self.__ops.mul:
174 new_parts[i] = self.parts[i] * other[start:end, :]
175 else:
176 raise ValueError("Operator not yet implemented!")
177 else:
178 end += self.parts[i].shape[1]
179 if op == self.__ops.add:
180 new_parts[i] = self.parts[i] + other[:, start:end]
181 elif op == self.__ops.sub:
182 new_parts[i] = self.parts[i] - other[:, start:end]
183 elif op == self.__ops.mul:
184 new_parts[i] = self.parts[i] * other[:, start:end]
185 else:
186 raise ValueError("Operator not yet implemented!")
187 start = end
188 else:
189 raise ValueError("Unknown type")
190
191 return MultipartArray(new_parts, vertical=self.vertical)
192
195
198
201
203 if not np.isscalar(other):
204 raise ValueError("Operator not yet implemented for type!")
205
206 return self.__op(other, self.__ops.div)
207
209 if not np.isscalar(other):
210 raise ValueError("Operator not yet implemented for type!")
211
212 return self.__op(float(other), self.__ops.div)
213
214 - def dot(self, other):
215 if self.vertical:
216 v = [0] * len(self.parts)
217 for i in xrange(len(self.parts)):
218 v[i] = self.parts[i].dot(other)
219 v = MultipartArray(v, vertical=True)
220 else:
221 v = np.zeros((self.shape[0], 1))
222 if isinstance(other, MultipartArray):
223 other_parts = other.get_parts()
224 for i in xrange(len(self.parts)):
225 v += self.parts[i].dot(other_parts[i])
226 elif self.shape[1] == other.shape[0]:
227 start = 0
228 end = 0
229 for i in xrange(len(self.parts)):
230 end += self.parts[i].shape[1]
231 v += self.parts[i].dot(other[start:end, :])
232 start = end
233 else:
234 raise ValueError("Type or shape unknown")
235
236 return v
237
239 new_parts = [0] * len(self.parts)
240 for i in xrange(len(self.parts)):
241 new_parts[i] = self.parts[i].transpose()
242 vertical = not self.vertical
243
244 return MultipartArray(new_parts, vertical=vertical)
245
248
250 raise AttributeError("attribute 'T' of 'MultipartArray' objects " \
251 "is not writable")
252
254 raise AttributeError("attribute 'T' of 'MultipartArray' objects " \
255 "is not writable")
256
257 T = property(_get_T, _set_T, _del_T, 'Transpose of the array.')
258
260 new_parts = [0] * len(self.parts)
261 for i in xrange(len(self.parts)):
262 new_parts[i] = self.parts[i].copy()
263
264 return MultipartArray(new_parts, vertical=self.vertical)
265
267 string = "["
268 if self.vertical:
269 for k in xrange(len(self.parts)):
270 for i in xrange(self.parts[k].shape[0]):
271 if i > 0 or k > 0:
272 string += ' '
273 string += str(self.parts[k][i, :])
274 if k < len(self.parts) - 1 \
275 or i < self.parts[k].shape[0] - 1:
276 string += '\n'
277 if k < len(self.parts) - 1:
278 string += ' '
279 string += '-' * (len(str(self.parts[k][i, :])) - 3)
280 string += "\n"
281 else:
282 for i in xrange(self.parts[0].shape[0]):
283 for k in xrange(len(self.parts)):
284 if k == 0 and i > 0:
285 string += ' '
286 string += str(self.parts[k][i, :])
287
288 if i < self.parts[len(self.parts) - 1].shape[0] - 1:
289 string += '\n'
290
291 string += "]"
292
293 return string
294
296 string = "MultipartArray(\n" + str(self.parts)
297 if self.vertical:
298 string += ")"
299 else:
300 string += ",\nvertical=" + str(self.vertical) + ")"
301
302 return string
303
304
306 __metaclass__ = abc.ABCMeta
307
309 """Solves a linear system on the form
310
311 A.x = b,
312
313 for x.
314
315 Parameters
316 ----------
317 A : A matrix with shape n-by-p. The coefficient matrix.
318
319 b : Numpy array, n-by-1. The right-hand-side vector.
320 """
321 raise NotImplementedError('Abstract method "solve" must be '
322 'specialised!')
323
324
326
327 - def solve(self, A, b, **kwargs):
328 """Solves linear systems on the form
329
330 A.x = d,
331
332 for x.
333
334 Parameters
335 ----------
336 A : A sparse matrix with shape n-by-p. The coefficient matrix.
337
338 b : Numpy array, n-by-1. The right-hand-side vector.
339
340 Examples
341 --------
342 >>> import numpy as np
343 >>> import scipy.sparse as sparse
344 >>> import parsimony.utils.linalgs as linalgs
345 >>> np.random.seed(42)
346 >>>
347 >>> n = 10
348 >>> a = np.random.rand(n); a[-1] = 0.0
349 >>> b = np.random.rand(n)
350 >>> c = np.random.rand(n); c[0] = 0.0
351 >>> A_ = np.random.rand(n, n)
352 >>> A_[A_ < 0.5] = 0.0
353 >>> A = sparse.csr_matrix(A_)
354 >>> d = np.random.rand(n, 1)
355 >>>
356 >>> solver = linalgs.SparseSolver()
357 >>> x = solver.solve(A, d)
358 >>> x_ = np.linalg.solve(A.toarray(), d)
359 >>> round(np.linalg.norm(x - x_), 15)
360 1e-15
361 >>>
362 >>> import time
363 >>> n = 100
364 >>> a = np.random.rand(n); a[-1] = 0.0
365 >>> b = np.random.rand(n)
366 >>> c = np.random.rand(n); c[0] = 0.0
367 >>> A_ = np.random.rand(n, n)
368 >>> A_[A_ < 0.5] = 0.0
369 >>> A = sparse.csr_matrix(A_)
370 >>> d = np.random.rand(n, 1)
371 >>>
372 >>> t = time.time()
373 >>> x = solver.solve(A, d)
374 >>> print "Time:", time.time() - t # doctest: +SKIP
375 >>>
376 >>> t = time.time()
377 >>> x_ = np.linalg.solve(A.toarray(), d)
378 >>> print "Time:", time.time() - t # doctest: +SKIP
379 >>> np.linalg.norm(x - x_) < 5e-14
380 True
381 >>>
382 >>> n = 1000
383 >>> a = np.random.rand(n); a[-1] = 0.0
384 >>> b = np.random.rand(n)
385 >>> c = np.random.rand(n); c[0] = 0.0
386 >>> A_ = np.random.rand(n, n)
387 >>> A_[A_ < 0.5] = 0.0
388 >>> A = sparse.csr_matrix(A_)
389 >>> d = np.random.rand(n, 1)
390 >>>
391 >>> t = time.time()
392 >>> x = solver.solve(A, d)
393 >>> print "Time:", time.time() - t # doctest: +SKIP
394 >>>
395 >>> t = time.time()
396 >>> x_ = np.linalg.solve(A.toarray(), d)
397 >>> print "Time:", time.time() - t # doctest: +SKIP
398 >>>
399 >>> np.linalg.norm(x - x_) < 5e-11
400 True
401 """
402 n, p = A.shape
403
404 x = sparse.linalg.spsolve(A, b)
405
406 return x.reshape((n, 1))
407
408
410
411 - def solve(self, A, d, **kwargs):
412 """Solves linear systems with a tridiagonal coefficient matrix.
413
414 A solver that uses the Thomas algorithm (the Tridiagonal matrix
415 algorithm) for systems on the form
416
417 0
418 [b c ] [x] [d]
419 A.x = [a b c ] [x] = [d] = d.
420 [ a b c] [x] [d]
421 [ a b] [x] [d]
422 0
423
424 Parameters
425 ----------
426 A : A sparse diagonal matrix (dia format) with shape n-by-p. The
427 coefficient matrix.
428
429 b : Numpy array, n-by-1. The right-hand-side vector.
430
431 Examples
432 --------
433 >>> import numpy as np
434 >>> import scipy.sparse as sparse
435 >>> import parsimony.utils.linalgs as linalgs
436 >>> np.random.seed(42)
437 >>>
438 >>> n = 10
439 >>> a = np.random.rand(n); a[-1] = 0.0
440 >>> b = np.random.rand(n)
441 >>> c = np.random.rand(n); c[0] = 0.0
442 >>> abc = np.vstack((a, b, c))
443 >>> A = sparse.dia_matrix((abc, [-1, 0, 1]), shape=(n, n))
444 >>> d = np.random.rand(n, 1)
445 >>>
446 >>> solver = linalgs.TridiagonalSolver()
447 >>> x = solver.solve(A, d)
448 >>> print x
449 [[ -1.84339326]
450 [ 4.62737333]
451 [-12.41571989]
452 [ 16.38029815]
453 [ 14.38143172]
454 [-14.58969243]
455 [ 6.21233944]
456 [ 1.34271395]
457 [ -1.63358708]
458 [ 4.88318651]]
459 >>> x_ = np.linalg.solve(A.toarray(), d)
460 >>> print x_
461 [[ -1.84339326]
462 [ 4.62737333]
463 [-12.41571989]
464 [ 16.38029815]
465 [ 14.38143172]
466 [-14.58969243]
467 [ 6.21233944]
468 [ 1.34271395]
469 [ -1.63358708]
470 [ 4.88318651]]
471 >>> np.linalg.norm(x - x_) < 5e-14
472 True
473 >>>
474 >>> import time
475 >>> n = 100
476 >>> a = np.random.rand(n); a[-1] = 0.0
477 >>> b = np.random.rand(n)
478 >>> c = np.random.rand(n); c[0] = 0.0
479 >>> abc = np.vstack((a, b, c))
480 >>> A = sparse.dia_matrix((abc, [-1, 0, 1]), shape=(n, n))
481 >>> d = np.random.rand(n, 1)
482 >>>
483 >>> t = time.time()
484 >>> x = solver.solve(A, d)
485 >>> print "Time:", time.time() - t # doctest: +SKIP
486 >>>
487 >>> t = time.time()
488 >>> x_ = np.linalg.solve(A.toarray(), d)
489 >>> print "Time:", time.time() - t # doctest: +SKIP
490 >>>
491 >>> np.linalg.norm(x - x_) < 5e-12
492 True
493 >>>
494 >>> n = 1000
495 >>> a = np.random.rand(n); a[-1] = 0.0
496 >>> b = np.random.rand(n)
497 >>> c = np.random.rand(n); c[0] = 0.0
498 >>> abc = np.vstack((a, b, c))
499 >>> A = sparse.dia_matrix((abc, [-1, 0, 1]), shape=(n, n))
500 >>> d = np.random.rand(n, 1)
501 >>>
502 >>> t = time.time()
503 >>> x = solver.solve(A, d)
504 >>> print "Time:", time.time() - t # doctest: +SKIP
505 >>>
506 >>> t = time.time()
507 >>> x_ = np.linalg.solve(A.toarray(), d)
508 >>> print "Time:", time.time() - t # doctest: +SKIP
509 >>>
510 >>> np.linalg.norm(x - x_) < 5e-9
511 True
512 """
513
514
515 if not sparse.isspmatrix_dia(A):
516 A = A.todia()
517
518 abc = A.data
519 a = abc[0, :]
520 b = abc[1, :]
521 c = abc[2, :]
522
523 if abc.dtype != np.float:
524 a = np.asarray(a, np.float)
525 b = np.asarray(b, np.float)
526 c = np.asarray(c, np.float)
527
528 n = len(a)
529 x = np.zeros(n)
530
531
532 c_ = np.zeros(n)
533 d_ = np.zeros(n)
534 i = 0
535 if abs(b[i]) < consts.TOLERANCE:
536
537
538 solver = SparseSolver()
539 return solver.solve(A, d)
540 c_[i + 1] = c[i + 1] / b[i]
541 d_[i] = d[i] / b[i]
542 for i in range(1, n - 1):
543 i_1 = i - 1
544 den = (b[i] - a[i_1] * c_[i])
545 if abs(den) < consts.TOLERANCE:
546
547 solver = SparseSolver()
548 return solver.solve(A, d)
549 c_[i + 1] = c[i + 1] / den
550 d_[i] = (d[i] - a[i_1] * d_[i_1]) / den
551 i = n - 1
552 d_[i] = (d[i] - a[i - 1] * d_[i - 1]) / (b[i] - a[i - 1] * c_[i])
553
554
555 i = n - 1
556 x[i] = d_[i]
557 for i in reversed(xrange(n - 1)):
558 x[i] = d_[i] - c_[i + 1] * x[i + 1]
559
560 return x.reshape((n, 1))
561
562 if __name__ == "__main__":
563 import doctest
564 doctest.testmod()
565