source: inundation/ga/storm_surge/pyvolution/sparse.py @ 1266

Last change on this file since 1266 was 605, checked in by ole, 20 years ago

Implemented matrix-matrix mult in c-extension using CSR format - all tests work again.

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