Welcome, guest | Sign In | My Account | Store | Cart
#!/usr/bin/env python

import vector
import math, types, operator

"""
A sparse matrix class based on a dictionary, supporting matrix (dot)
product and a conjugate gradient solver.

In this version, the sparse class inherits from the dictionary; this
requires Python 2.2 or later.
"""


class sparse(dict):
       
"""
        A complex sparse matrix
        A. Pletzer 5 Jan 00/12 April 2002

        Dictionary storage format { (i,j): value, ... }
        where (i,j) are the matrix indices
        """


       
# no c'tor

       
def size(self):
               
" returns # of rows and columns "
                nrow
= 0
                ncol
= 0
               
for key in self.keys():
                        nrow
= max([nrow, key[0]+1])
                        ncol
= max([ncol, key[1]+1])
               
return (nrow, ncol)

       
def __add__(self, other):
                res
= sparse(self.copy())
               
for ij in other:
                        res
[ij] = self.get(ij,0.) + other[ij]
               
return res
               
       
def __neg__(self):
               
return sparse(zip(self.keys(), map(operator.neg, self.values())))

       
def __sub__(self, other):
                res
= sparse(self.copy())
               
for ij in other:
                        res
[ij] = self.get(ij,0.) - other[ij]
               
return res
               
       
def __mul__(self, other):
               
" element by element multiplication: other can be scalar or sparse "
               
try:
                       
# other is sparse
                        nval
= len(other)
                        res
= sparse()
                       
if nval < len(self):
                               
for ij in other:
                                        res
[ij] = self.get(ij,0.)*other[ij]
                       
else:
                               
for ij in self:
                                        res
[ij] = self[ij]*other.get(ij,0j)
                       
return res
               
except:
                       
# other is scalar
                       
return sparse(zip(self.keys(), map(lambda x: x*other, self.values())))


       
def __rmul__(self, other): return self.__mul__(other)

       
def __div__(self, other):
               
" element by element division self/other: other is scalar"
               
return sparse(zip(self.keys(), map(lambda x: x/other, self.values())))
               
       
def __rdiv__(self, other):
               
" element by element division other/self: other is scalar"
               
return sparse(zip(self.keys(), map(lambda x: other/x, self.values())))

       
def abs(self):
               
return sparse(zip(self.keys(), map(operator.abs, self.values())))

       
def out(self):
               
print '# (i, j) -- value'
               
for k in self.keys():
                       
print k, self[k]

       
def plot(self, width_in=400, height_in=400):

               
import colormap
               
import Tkinter

                cmax
= max(self.values())
                cmin
= min(self.values())
               
                offset
=  0.05*min(width_in, height_in)
                xmin
, ymin, xmax, ymax = 0,0,self.size()[0], self.size()[1]
                scale
=  min(0.9*width_in, 0.9*height_in)/max(xmax-xmin, ymax-ymin)

                root
= Tkinter.Tk()
                frame
= Tkinter.Frame(root)
                frame
.pack()
               
                text
= Tkinter.Label(width=20, height=10, text='matrix sparsity')
                text
.pack()
               

                canvas
= Tkinter.Canvas(bg="black", width=width_in, height=height_in)
                canvas
.pack()

                button
= Tkinter.Button(frame, text="OK?", fg="red", command=frame.quit)
                button
.pack()

               
for index in self.keys():
                        ix
, iy = index[0], ymax-index[1]-1
                        ya
, xa = offset+scale*(ix  ), height_in -offset-scale*(iy  )
                        yb
, xb = offset+scale*(ix+1), height_in -offset-scale*(iy  )
                        yc
, xc = offset+scale*(ix+1), height_in -offset-scale*(iy+1)
                        yd
, xd = offset+scale*(ix  ), height_in -offset-scale*(iy+1)
                        color
= colormap.strRgb(self[index], cmin, cmax)
                        canvas
.create_polygon(xa, ya, xb, yb, xc, yc, xd, yd, fill=color)
               
                root
.mainloop()

       
def CGsolve(self, x0, b, tol=1.0e-10, nmax = 1000, verbose=1):
               
"""
                Solve self*x = b and return x using the conjugate gradient method
                """

               
if not vector.isVector(b):
                       
raise TypeError, self.__class__,' in solve '
               
else:
                       
if self.size()[0] != len(b) or self.size()[1] != len(b):
                               
print '**Incompatible sizes in solve'
                               
print '**size()=', self.size()[0], self.size()[1]
                               
print '**len=', len(b)
                       
else:
                                kvec
= diag(self) # preconditionner
                                n
= len(b)
                                x
= x0 # initial guess
                                r
=  b - dot(self, x)
                               
try:
                                        w
= r/kvec
                               
except: print '***singular kvec'
                                p
= vector.zeros(n);
                                beta
= 0.0;
                                rho
= vector.dot(r, w);
                                err
= vector.norm(dot(self,x) - b);
                                k
= 0
                               
if verbose: print " conjugate gradient convergence (log error)"
                               
while abs(err) > tol and k < nmax:
                                        p
= w + beta*p;
                                        z
= dot(self, p);
                                        alpha
= rho/vector.dot(p, z);
                                        r
= r - alpha*z;
                                        w
= r/kvec;
                                        rhoold
= rho;
                                        rho
= vector.dot(r, w);
                                        x
= x + alpha*p;
                                        beta
= rho/rhoold;
                                        err
= vector.norm(dot(self, x) - b);
                                       
if verbose: print k,' %5.1f ' % math.log10(err)
                                        k
= k+1
                               
return x
                               
               
                       
       
def biCGsolve(self,x0, b, tol=1.0e-10, nmax = 1000):
               
               
"""
                Solve self*x = b and return x using the bi-conjugate gradient method
                """


               
try:
                       
if not vector.isVector(b):
                               
raise TypeError, self.__class__,' in solve '
                       
else:
                               
if self.size()[0] != len(b) or self.size()[1] != len(b):
                                       
print '**Incompatible sizes in solve'
                                       
print '**size()=', self.size()[0], self.size()[1]
                                       
print '**len=', len(b)
                               
else:
                                        kvec
= diag(self) # preconditionner
                                        n
= len(b)
                                        x
= x0 # initial guess
                                        r
=  b - dot(self, x)
                                        rbar
=  r
                                        w
= r/kvec;
                                        wbar
= rbar/kvec;
                                        p
= vector.zeros(n);
                                        pbar
= vector.zeros(n);
                                        beta
= 0.0;
                                        rho
= vector.dot(rbar, w);
                                        err
= vector.norm(dot(self,x) - b);
                                        k
= 0
                                       
print " bi-conjugate gradient convergence (log error)"
                                       
while abs(err) > tol and k < nmax:
                                                p
= w + beta*p;
                                                pbar
= wbar + beta*pbar;
                                                z
= dot(self, p);
                                                alpha
= rho/vector.dot(pbar, z);
                                                r
= r - alpha*z;
                                                rbar
= rbar - alpha* dot(pbar, self);
                                                w
= r/kvec;
                                                wbar
= rbar/kvec;
                                                rhoold
= rho;
                                                rho
= vector.dot(rbar, w);
                                                x
= x + alpha*p;
                                                beta
= rho/rhoold;
                                                err
= vector.norm(dot(self, x) - b);
                                               
print k,' %5.1f ' % math.log10(err)
                                                k
= k+1
                                       
return x
                       
               
except: print 'ERROR ',self.__class__,'::biCGsolve'


       
def save(self, filename, OneBased=0):
               
"""
                Save matrix in file <filaname> using format:
                OneBased, nrow, ncol, nnonzeros
                [ii, jj, data]

                """

                m
= n = 0
                nnz
= len(self)
               
for ij in self.keys():
                        m
= max(ij[0], m)
                        n
= max(ij[1], n)

                f
= open(filename,'w')
                f
.write('%d %d %d %d\n' % (OneBased, m+1,n+1,nnz))
               
for ij in self.keys():
                        i
,j = ij
                        f
.write('%d %d %20.17f \n'% \
                               
(i+OneBased,j+OneBased,self[ij]))
                f
.close()
                               
###############################################################################

def isSparse(x):
   
return hasattr(x,'__class__') and x.__class__ is sparse

def transp(a):
       
" transpose "
       
new = sparse({})
       
for ij in a:
               
new[(ij[1], ij[0])] = a[ij]
       
return new

def dotDot(y,a,x):
       
" double dot product y^+ *A*x "
       
if vector.isVector(y) and isSparse(a) and vector.isVector(x):
                res
= 0.
               
for ij in a.keys():
                        i
,j = ij
                        res
+= y[i]*a[ij]*x[j]
               
return res
       
else:
               
print 'sparse::Error: dotDot takes vector, sparse , vector as args'

def dot(a, b):
       
" vector-matrix, matrix-vector or matrix-matrix product "
       
if isSparse(a) and vector.isVector(b):
               
new = vector.zeros(a.size()[0])
               
for ij in a.keys():
                       
new[ij[0]] += a[ij]* b[ij[1]]
               
return new
       
elif vector.isVector(a) and isSparse(b):
               
new = vector.zeros(b.size()[1])
               
for ij in b.keys():
                       
new[ij[1]] += a[ij[0]]* b[ij]
               
return new
       
elif isSparse(a) and isSparse(b):
               
if a.size()[1] != b.size()[0]:
                       
print '**Warning shapes do not match in dot(sparse, sparse)'
               
new = sparse({})
                n
= min([a.size()[1], b.size()[0]])
               
for i in range(a.size()[0]):
                       
for j in range(b.size()[1]):
                                sum
= 0.
                               
for k in range(n):
                                        sum
+= a.get((i,k),0.)*b.get((k,j),0.)
                               
if sum != 0.:
                                       
new[(i,j)] = sum
               
return new
       
else:
               
raise TypeError, 'in dot'

def diag(b):
       
# given a sparse matrix b return its diagonal
        res
= vector.zeros(b.size()[0])
       
for i in range(b.size()[0]):
                res
[i] = b.get((i,i), 0.)
       
return res
               
def identity(n):
       
if type(n) != types.IntType:
               
raise TypeError, ' in identity: # must be integer'
       
else:
               
new = sparse({})
               
for i in range(n):
                       
new[(i,i)] = 1+0.
               
return new

###############################################################################
if __name__ == "__main__":

       
print 'a = sparse()'
        a
= sparse()

       
print 'a.__doc__=',a.__doc__

       
print 'a[(0,0)] = 1.0'
        a
[(0,0)] = 1.0
        a
.out()

       
print 'a[(2,3)] = 3.0'
        a
[(2,3)] = 3.0
        a
.out()

       
print 'len(a)=',len(a)
       
print 'a.size()=', a.size()
                       
        b
= sparse({(0,0):2.0, (0,1):1.0, (1,0):1.0, (1,1):2.0, (1,2):1.0, (2,1):1.0, (2,2):2.0})
       
print 'a=', a
       
print 'b=', b
        b
.out()

       
print 'a+b'
        c
= a + b
        c
.out()

       
print '-a'
        c
= -a
        c
.out()
        a
.out()

       
print 'a-b'
        c
= a - b
        c
.out()

       
print 'a*1.2'
        c
= a*1.2
        c
.out()


       
print '1.2*a'
        c
= 1.2*a
        c
.out()
       
print 'a=', a

       
print 'dot(a, b)'
       
print 'a.size()[1]=',a.size()[1],' b.size()[0]=', b.size()[0]
        c
= dot(a, b)
        c
.out()

       
print 'dot(b, a)'
       
print 'b.size()[1]=',b.size()[1],' a.size()[0]=', a.size()[0]
        c
= dot(b, a)
        c
.out()

       
try:
               
print 'dot(b, vector.vector([1,2,3]))'
                c
= dot(b, vector.vector([1,2,3]))
                c
.out()
       
               
print 'dot(vector.vector([1,2,3]), b)'
                c
= dot(vector.vector([1,2,3]), b)
                c
.out()

               
print 'b.size()=', b.size()
       
except: pass
       
       
print 'a*b -> element by element product'
        c
= a*b
        c
.out()

       
print 'b*a -> element by element product'
        c
= b*a
        c
.out()
       
       
print 'a/1.2'
        c
= a/1.2
        c
.out()

       
print 'c = identity(4)'
        c
= identity(4)
        c
.out()

       
print 'c = transp(a)'
        c
= transp(a)
        c
.out()


        b
[(2,2)]=-10.0
        b
[(2,0)]=+10.0

       
try:
               
import vector
               
print 'Check conjugate gradient solver'
                s
= vector.vector([1, 0, 0])
               
print 's'
                s
.out()
                x0
= s
               
print 'x = b.biCGsolve(x0, s, 1.0e-10, len(b)+1)'
                x
= b.biCGsolve(x0, s, 1.0e-10,  len(b)+1)
                x
.out()

               
print 'check validity of CG'
                c
= dot(b, x) - s
                c
.out()
       
except: pass

       
print 'plot b matrix'
        b
.out()
        b
.plot()

       
print 'del b[(2,2)]'
       
del b[(2,2)]

       
print 'del a'
       
del a
       
#a.out()

History

  • revision 6 (19 years ago)
  • previous revisions are not available