source: anuga_core/source/anuga/utilities/sparse.py @ 4154

Last change on this file since 4154 was 3832, checked in by ole, 18 years ago

More parallel timings

File size: 10.4 KB
Line 
1"""Proof of concept sparse matrix code
2"""
3
4
5class Sparse:
6
7    def __init__(self, *args):
8        """Create sparse matrix.
9        There are two construction forms
10        Usage:
11
12        Sparse(A)     #Creates sparse matrix from dense matrix A
13        Sparse(M, N)  #Creates empty MxN sparse matrix
14        """
15
16        self.Data = {}
17           
18        if len(args) == 1:
19            from Numeric import array
20            try:
21                A = array(args[0])
22            except:
23                raise 'Input must be convertable to a Numeric array'
24
25            assert len(A.shape) == 2, 'Input must be a 2d matrix'
26           
27            self.M, self.N = A.shape
28            for i in range(self.M):
29                for j in range(self.N):
30                    if A[i, j] != 0.0:
31                        self.Data[i, j] = A[i, j]
32               
33           
34        elif len(args) == 2:
35            self.M = args[0]
36            self.N = args[1]
37        else:
38            raise 'Invalid construction'
39           
40        self.shape = (self.M, self.N) 
41
42
43    def __repr__(self):
44        return '%d X %d sparse matrix:\n' %(self.M, self.N) + `self.Data`
45
46    def __len__(self):
47        """Return number of nonzeros of A
48        """
49        return len(self.Data)
50
51    def nonzeros(self):
52        """Return number of nonzeros of A
53        """       
54        return len(self)
55   
56    def __setitem__(self, key, x):
57
58        i,j = key
59        assert 0 <= i < self.M
60        assert 0 <= j < self.N       
61
62        if x != 0:
63            self.Data[key] = float(x)
64        else:
65            if self.Data.has_key( key ):           
66                del self.Data[key]
67
68    def __getitem__(self, key):
69       
70        i,j = key
71        assert 0 <= i < self.M
72        assert 0 <= j < self.N               
73
74        if self.Data.has_key( key ):
75            return self.Data[ key ]
76        else:
77            return 0.0
78
79    def copy(self):
80        #FIXME: Use the copy module instead
81        new = Sparse(self.M,self.N)
82
83        for key in self.Data.keys():
84            i, j = key
85
86            new[i,j] = self.Data[i,j]
87
88        return new
89
90
91    def todense(self):
92        from Numeric import zeros, Float
93
94        D = zeros( (self.M, self.N), Float)
95       
96        for i in range(self.M):
97            for j in range(self.N):
98                if self.Data.has_key( (i,j) ):               
99                    D[i, j] = self.Data[ (i,j) ]
100        return D
101
102
103   
104    def __mul__(self, other):
105        """Multiply this matrix onto 'other' which can either be
106        a Numeric vector, a Numeric matrix or another sparse matrix.
107        """
108
109        from Numeric import array, zeros, Float
110       
111        try:
112            B = array(other)
113        except:
114            msg = 'FIXME: Only Numeric types implemented so far'
115            raise msg
116           
117
118        #Assume numeric types from now on
119       
120        if len(B.shape) == 0:
121            #Scalar - use __rmul__ method
122            R = B*self
123           
124        elif len(B.shape) == 1:
125            #Vector
126            assert B.shape[0] == self.N, 'Mismatching dimensions'
127
128            R = zeros(self.M, Float) #Result
129           
130            #Multiply nonzero elements
131            for key in self.Data.keys():
132                i, j = key
133
134                R[i] += self.Data[key]*B[j]
135        elif len(B.shape) == 2:
136       
137           
138            R = zeros((self.M, B.shape[1]), Float) #Result matrix
139
140            #Multiply nonzero elements
141            for col in range(R.shape[1]):
142                #For each column
143               
144                for key in self.Data.keys():
145                    i, j = key
146
147                    R[i, col] += self.Data[key]*B[j, col]
148           
149           
150        else:
151            raise ValueError, 'Dimension too high: d=%d' %len(B.shape)
152
153        return R
154   
155
156    def __add__(self, other):
157        """Add this matrix onto 'other'
158        """
159
160        from Numeric import array, zeros, Float
161       
162        new = other.copy()
163        for key in self.Data.keys():
164            i, j = key
165
166            new[i,j] += self.Data[key]
167
168        return new
169
170
171    def __rmul__(self, other):
172        """Right multiply this matrix with scalar
173        """
174
175        from Numeric import array, zeros, Float
176       
177        try:
178            other = float(other)
179        except:
180            msg = 'Sparse matrix can only "right-multiply" onto a scalar'
181            raise TypeError, msg
182        else:
183            new = self.copy()
184            #Multiply nonzero elements
185            for key in new.Data.keys():
186                i, j = key
187
188                new.Data[key] = other*new.Data[key]
189
190        return new
191
192
193    def trans_mult(self, other):
194        """Multiply the transpose of matrix with 'other' which can be
195        a Numeric vector.
196        """
197
198        from Numeric import array, zeros, Float
199       
200        try:
201            B = array(other)
202        except:
203            print 'FIXME: Only Numeric types implemented so far'
204
205
206        #Assume numeric types from now on
207        if len(B.shape) == 1:
208            #Vector
209
210            assert B.shape[0] == self.M, 'Mismatching dimensions'
211
212            R = zeros((self.N,), Float) #Result
213
214            #Multiply nonzero elements
215            for key in self.Data.keys():
216                i, j = key
217
218                R[j] += self.Data[key]*B[i]
219
220        else:
221            raise 'Can only multiply with 1d array'
222
223        return R
224
225class Sparse_CSR:
226
227    def __init__(self, A):
228        """Create sparse matrix in csr format.
229
230        Sparse_CSR(A) #creates csr sparse matrix from sparse matrix
231        Matrices are not built using this format, since it's painful to
232        add values to an existing sparse_CSR instance (hence there are no
233        objects to do this.)
234
235        Rather, build a matrix, and convert it to this format for a speed
236        increase.
237
238        data - a 1D array of the data
239        Colind - The ith item in this 1D array is the column index of the
240                 ith data in the data array
241        rowptr - 1D array, with the index representing the row of the matrix.
242                 The item in the row represents the index into colind of the
243                 first data value of this row.
244                 Regard it as a pointer into the colind array, for the ith row.
245
246                 
247        """
248
249        from Numeric import array, Float, Int
250
251        if isinstance(A,Sparse):
252
253            from Numeric import zeros
254            keys = A.Data.keys()
255            keys.sort()
256            nnz = len(keys)
257            data    = zeros ( (nnz,), Float)
258            colind  = zeros ( (nnz,), Int)
259            row_ptr = zeros ( (A.M+1,), Int)
260            current_row = -1
261            k = 0
262            for key in keys:
263                ikey0 = int(key[0])
264                ikey1 = int(key[1])
265                if ikey0 != current_row:
266                    current_row = ikey0
267                    row_ptr[ikey0] = k
268                data[k] = A.Data[key]
269                colind[k] = ikey1
270                k += 1
271            for row in range(current_row+1, A.M+1):
272                row_ptr[row] = nnz
273            #row_ptr[-1] = nnz
274       
275            self.data    = data
276            self.colind  = colind
277            self.row_ptr = row_ptr
278            self.M       = A.M
279            self.N       = A.N
280        else:
281            raise ValueError, "Sparse_CSR(A) expects A == Sparse Matrix"
282           
283    def __repr__(self):
284        return '%d X %d sparse matrix:\n' %(self.M, self.N) + `self.data`
285
286    def __len__(self):
287        """Return number of nonzeros of A
288        """
289        return self.row_ptr[-1]
290
291    def nonzeros(self):
292        """Return number of nonzeros of A
293        """       
294        return len(self)
295
296    def todense(self):
297        from Numeric import zeros, Float
298
299        D = zeros( (self.M, self.N), Float)
300       
301        for i in range(self.M):
302            for ckey in range(self.row_ptr[i],self.row_ptr[i+1]):
303                j = self.colind[ckey]
304                D[i, j] = self.data[ckey]
305        return D
306
307    def __mul__(self, other):
308        """Multiply this matrix onto 'other' which can either be
309        a Numeric vector, a Numeric matrix or another sparse matrix.
310        """
311
312        from Numeric import array, zeros, Float
313       
314        try:
315            B = array(other)
316        except:
317            print 'FIXME: Only Numeric types implemented so far'
318
319        return csr_mv(self,B) 
320
321
322
323def csr_mv(self, B):
324    """Python version of sparse (CSR) matrix multiplication
325    """
326
327    from Numeric import zeros, Float
328
329
330    #Assume numeric types from now on
331       
332    if len(B.shape) == 0:
333        #Scalar - use __rmul__ method
334        R = B*self
335       
336    elif len(B.shape) == 1:
337        #Vector
338        assert B.shape[0] == self.N, 'Mismatching dimensions'
339       
340        R = zeros(self.M, Float) #Result
341       
342        #Multiply nonzero elements
343        for i in range(self.M):
344            for ckey in range(self.row_ptr[i],self.row_ptr[i+1]):
345                j = self.colind[ckey]
346                R[i] += self.data[ckey]*B[j]           
347               
348    elif len(B.shape) == 2:
349       
350        R = zeros((self.M, B.shape[1]), Float) #Result matrix
351       
352        #Multiply nonzero elements
353       
354        for col in range(R.shape[1]):
355            #For each column
356            for i in range(self.M):
357                for ckey in range(self.row_ptr[i],self.row_ptr[i+1]):
358                    j = self.colind[ckey]
359                    R[i, col] += self.data[ckey]*B[j,col]           
360                   
361    else:
362        raise ValueError, 'Dimension too high: d=%d' %len(B.shape)
363   
364    return R
365
366
367
368#Setup for C extensions
369from anuga.utilities import compile
370if compile.can_use_C_extension('sparse_ext.c'):
371    #Replace python version with c implementation
372    from sparse_ext import csr_mv
373
374if __name__ == '__main__':
375
376    from Numeric import allclose, array, Float
377   
378    A = Sparse(3,3)
379
380    A[1,1] = 4
381
382
383    print A
384    print A.todense()
385
386    A[1,1] = 0
387
388    print A
389    print A.todense()   
390
391    A[1,2] = 0
392
393
394    A[0,0] = 3
395    A[1,1] = 2
396    A[1,2] = 2
397    A[2,2] = 1
398
399    print A
400    print A.todense()
401
402
403    #Right hand side vector
404    v = [2,3,4]
405
406    u = A*v
407    print u
408    assert allclose(u, [6,14,4])
409
410    u = A.trans_mult(v)
411    print u
412    assert allclose(u, [6,6,10])
413
414    #Right hand side column
415    v = array([[2,4],[3,4],[4,4]])
416
417    u = A*v[:,0]
418    assert allclose(u, [6,14,4])
419
420    #u = A*v[:,1]
421    #print u
422    print A.shape
423
424    B = 3*A
425    print B.todense()
426
427    B[1,0] = 2
428
429    C = A+B
430
431    print C.todense()
432
433    C = Sparse_CSR(C)
434
435    y = C*[6,14,4]
436
437    print y
438
439    y2 = C*[[6,4],[4,28],[4,8]]
440
441    print y2
Note: See TracBrowser for help on using the repository browser.