[Scipy-svn] r3272 - in trunk/scipy/sparse: . tests

scipy-svn at scipy.org scipy-svn at scipy.org
Tue Aug 28 12:30:01 EDT 2007


Author: stefan
Date: 2007-08-28 11:29:41 -0500 (Tue, 28 Aug 2007)
New Revision: 3272

Modified:
   trunk/scipy/sparse/sparse.py
   trunk/scipy/sparse/tests/test_sparse.py
Log:
Reject invalid shape assignments.  Support reshaping.  Fix whitespace.
Closes ticket #335.


Modified: trunk/scipy/sparse/sparse.py
===================================================================
--- trunk/scipy/sparse/sparse.py	2007-08-27 23:14:25 UTC (rev 3271)
+++ trunk/scipy/sparse/sparse.py	2007-08-28 16:29:41 UTC (rev 3272)
@@ -6,8 +6,8 @@
 
 
 __all__ = ['spmatrix','csc_matrix','csr_matrix','coo_matrix',
-            'lil_matrix','dok_matrix', 
-            'spdiags','speye','spidentity','extract_diagonal', 
+            'lil_matrix','dok_matrix',
+            'spdiags','speye','spidentity','extract_diagonal',
             'isspmatrix','issparse','isspmatrix_csc','isspmatrix_csr',
             'isspmatrix_lil','isspmatrix_dok', 'lil_eye', 'lil_diags' ]
 
@@ -16,14 +16,14 @@
 from numpy import zeros, isscalar, real, imag, asarray, asmatrix, matrix, \
                   ndarray, amax, amin, rank, conj, searchsorted, ndarray,   \
                   less, where, greater, array, transpose, empty, ones, \
-                  arange, shape, intc, clip
+                  arange, shape, intc, clip, prod, unravel_index
 import numpy
 from scipy.sparse.sparsetools import cscmux, csrmux, \
      cootocsr, csrtocoo, cootocsc, csctocoo, csctocsr, csrtocsc, \
      densetocsr, csrtodense, \
      csrmucsr, cscmucsc, \
      csr_plus_csr, csc_plus_csc, csr_minus_csr, csc_minus_csc, \
-     csr_elmul_csr, csc_elmul_csc, csr_eldiv_csr, csc_eldiv_csc 
+     csr_elmul_csr, csc_elmul_csc, csr_eldiv_csr, csc_eldiv_csc
 
 import sparsetools
 import itertools, operator, copy
@@ -92,12 +92,34 @@
     ndim = 2
     def __init__(self, maxprint=MAXPRINT, allocsize=ALLOCSIZE):
         self.format = self.__class__.__name__[:3]
+        self._shape = None
         if self.format == 'spm':
             raise ValueError, "This class is not intended" \
                   " to be instantiated directly."
         self.maxprint = maxprint
         self.allocsize = allocsize
 
+    def set_shape(self,shape):
+        s = tuple(shape)
+        if len(s) != 2:
+            raise ValueError("Only two-dimensional sparse arrays "
+                             "are supported.")
+        if (self._shape != shape) and (self._shape is not None):
+            try:
+                self = self.reshape(shape)
+            except NotImplementedError:
+                raise NotImplementedError("Reshaping not implemented for %s." %
+                                          self.__class__.__name__)
+        self._shape = shape
+
+    def get_shape(self):
+        return self._shape
+
+    shape = property(fget=get_shape, fset=set_shape)
+
+    def reshape(self,shape):
+        raise NotImplementedError
+
     def astype(self, t):
         csc = self.tocsc()
         return csc.astype(t)
@@ -159,7 +181,7 @@
         return "<%dx%d sparse matrix of type '%s'\n" \
                "\twith %d stored elements in %s format>" % \
                (self.shape + (self.dtype.type, nnz, _formats[format][1]))
-    
+
     def __str__(self):
         nnz = self.getnnz()
         maxprint = self.getmaxprint()
@@ -192,7 +214,7 @@
     #   and operations return in csc format
     #  thus, a new sparse matrix format just needs to define
     #  a tocsc method
-    
+
     def __abs__(self):
         csc = self.tocsc()
         return abs(csc)
@@ -200,7 +222,7 @@
     def __add__(self, other):   # self + other
         csc = self.tocsc()
         return csc.__add__(other)
-    
+
     def __radd__(self, other):  # other + self
         return self.__add__(other)
 
@@ -232,7 +254,7 @@
     def __pow__(self, other):
         csc = self.tocsc()
         return csc ** other
-    
+
     def __neg__(self):
         csc = self.tocsc()
         return -csc
@@ -292,7 +314,7 @@
         return self * a
 
     def getrow(self, i):
-        """Returns a copy of row i of the matrix, as a (1 x n) sparse 
+        """Returns a copy of row i of the matrix, as a (1 x n) sparse
         matrix (row vector).
         """
         # Spmatrix subclasses should override this method for efficiency.
@@ -431,7 +453,7 @@
             return self.sum(None) * 1.0 / (self.shape[0]*self.shape[1])
         else:
             raise ValueError, "axis out of bounds"
-    
+
     def setdiag(self, values, k=0):
         """Fills the diagonal elements {a_ii} with the values from the
         given sequence.  If k != 0, fills the off-diagonal elements
@@ -453,7 +475,7 @@
             for i,v in enumerate(values[:max_index]):
                 self[i, i + k] = v
 
-    
+
     def save(self, file_name, format = '%d %d %f\n'):
         try:
             fd = open(file_name, 'w')
@@ -478,7 +500,7 @@
                "elements (space for %d)\n\tin %s format>" % \
                (self.shape + (self.dtype.type, self.getnnz(), self.nzmax, \
                    _formats[format][1]))
-    
+
     def _with_data(self,data,copy=True):
         """
         Return a matrix with the same sparsity structure as self,
@@ -491,17 +513,17 @@
         else:
             return self.__class__((data,self.indices,self.indptr), \
                                    dims=self.shape,dtype=data.dtype,check=False)
-    
+
     def __abs__(self):
         return self._with_data(abs(self.data))
-    
+
     def _real(self):
         return self._with_data(numpy.real(self.data))
-    
+
     def _imag(self):
         return self._with_data(numpy.imag(self.data))
-        
-    
+
+
     def _binopt(self, other, fn, in_shape=None, out_shape=None):
         """apply the binary operation fn to two sparse matrices"""
         other = self._tothis(other)
@@ -510,13 +532,13 @@
             in_shape = self.shape
         if out_shape is None:
             out_shape = self.shape
-            
+
         indptr, ind, data = fn(in_shape[0], in_shape[1], \
-                               self.indptr, self.indices, self.data, 
+                               self.indptr, self.indices, self.data,
                                other.indptr, other.indices, other.data)
         return self.__class__((data, ind, indptr), dims=out_shape, check=False)
-            
-        
+
+
     def __add__(self,other,fn):
         # First check if argument is a scalar
         if isscalarlike(other):
@@ -532,7 +554,7 @@
             return self.todense() + other
         else:
             raise NotImplemented
-   
+
     def __sub__(self,other,fn):
         # First check if argument is a scalar
         if isscalarlike(other):
@@ -548,9 +570,9 @@
             return self.todense() - other
         else:
             raise NotImplemented
-   
-    
-    def __mul__(self, other): # self * other 
+
+
+    def __mul__(self, other): # self * other
         """ Scalar, vector, or matrix multiplication
         """
         if isscalarlike(other):
@@ -559,7 +581,7 @@
             return self.dot(other)
 
 
-    def __rmul__(self, other): # other * self 
+    def __rmul__(self, other): # other * self
         if isscalarlike(other):
             return self.__mul__(other)
         else:
@@ -619,10 +641,10 @@
             # being created on-the-fly like dense matrix objects can.
             #if len(other) != self.shape[1]:
             #    raise ValueError, "dimension mismatch"
-            oth = numpy.ravel(other)            
+            oth = numpy.ravel(other)
             y = fn(self.shape[0], self.shape[1], \
                    self.indptr, self.indices, self.data, oth)
-            if isinstance(other, matrix):                
+            if isinstance(other, matrix):
                 y = asmatrix(y)
             if other.ndim == 2 and other.shape[1] == 1:
                 # If 'other' was an (nx1) column vector, transpose the result
@@ -649,7 +671,7 @@
                               self.indptr, self.indices, self.data)
         return coo_matrix((data, (rows, cols)), self.shape)
 
-    
+
     def sum(self, axis=None):
         """Sum the matrix over the given axis.  If the axis is None, sum
         over both rows and columns, returning a scalar.
@@ -665,7 +687,7 @@
 
     def copy(self):
         return self._with_data(self.data.copy(),copy=True)
-    
+
     def _get_slice(self, i, start, stop, stride, dims):
         """Returns a view of the elements [i, myslice.start:myslice.stop].
         """
@@ -675,7 +697,7 @@
             raise ValueError, "slice width must be >= 1"
 
         indices = []
-        
+
         for ind in xrange(self.indptr[i], self.indptr[i+1]):
             if self.indices[ind] >= start and self.indices[ind] < stop:
                 indices.append(ind)
@@ -690,8 +712,8 @@
     def _transpose(self, cls, copy=False):
         M, N = self.shape
         return cls((self.data,self.indices,self.indptr),(N,M),copy=copy,check=False)
-        
 
+
     def conj(self, copy=False):
         return self._with_data(self.data.conj(),copy=copy)
 
@@ -843,7 +865,7 @@
             else:
                 # Matrix is completely empty
                 M = max(oldM, M)
-                
+
         self.shape = (M, N)
 
         self._check(check)
@@ -885,9 +907,9 @@
                   "the size of data list"
         if (self.indices.dtype != numpy.intc):
             self.indices = self.indices.astype(numpy.intc)
-        if (self.indptr.dtype != numpy.intc):            
+        if (self.indptr.dtype != numpy.intc):
             self.indptr = self.indptr.astype(numpy.intc)
-            
+
         self.nnz = nnz
         self.nzmax = nzmax
         self.dtype = self.data.dtype
@@ -903,16 +925,16 @@
         else:
             return _cs_matrix.__getattr__(self, attr)
 
-    
+
     def __add__(self, other):
         return _cs_matrix.__add__(self, other, csc_plus_csc)
-    
+
     def __sub__(self, other):
         return _cs_matrix.__sub__(self, other, csc_minus_csc)
 
     def __truediv__(self,other):
         return _cs_matrix.__truediv__(self,other, csc_eldiv_csc)
-    
+
     def __pow__(self, other):
         return _cs_matrix.__pow__(self, other, csc_elmul_csc)
 
@@ -986,21 +1008,21 @@
                     alloc = max(1, self.allocsize)
                     self.data = resize1d(self.data, nzmax + alloc)
                     self.indices = resize1d(self.indices, nzmax + alloc)
-                    
+
                 newindex = self.indptr[col]
                 self.data[newindex+1:]   = self.data[newindex:-1]
                 self.indices[newindex+1:] = self.indices[newindex:-1]
-                
+
                 self.data[newindex]   = val
                 self.indices[newindex] = row
                 self.indptr[col+1:] += 1
-                
+
             elif len(indxs[0]) == 1:
                 #value already present
                 self.data[self.indptr[col]:self.indptr[col+1]][indxs[0]] = val
             else:
                 raise IndexError, "row index occurs more than once"
-            
+
             self._check()
         else:
             # We should allow slices here!
@@ -1014,7 +1036,7 @@
         """
         start, stop, stride = myslice.indices(self.shape[0])
         return _cs_matrix._get_slice(self, j, start, stop, stride, (stop - start, 1))
-    
+
     def rowcol(self, ind):
         row = self.indices[ind]
         col = searchsorted(self.indptr, ind+1)-1
@@ -1036,7 +1058,7 @@
 
     def _tothis(self, other):
         return other.tocsc()
-    
+
     def toarray(self):
         return self.tocsr().toarray()
 
@@ -1165,7 +1187,7 @@
         else:
             raise ValueError, "unrecognized form for csr_matrix constructor"
 
-            
+
         # Read matrix dimensions given, if any
         if dims is not None:
             try:
@@ -1188,7 +1210,7 @@
                 N = max(oldN, N)
 
         self.shape = (M, N)
-        
+
         self._check(check)
 
     def _check(self,full_check=True):
@@ -1226,7 +1248,7 @@
                   "the size of data list"
         if (self.indices.dtype != numpy.intc):
             self.indices = self.indices.astype(numpy.intc)
-        if (self.indptr.dtype != numpy.intc):            
+        if (self.indptr.dtype != numpy.intc):
             self.indptr = self.indptr.astype(numpy.intc)
 
         self.nnz = nnz
@@ -1243,13 +1265,13 @@
             return self.indices
         else:
             return _cs_matrix.__getattr__(self, attr)
-    
+
     def __add__(self, other):
         return _cs_matrix.__add__(self, other, csr_plus_csr)
-   
+
     def __sub__(self, other):
         return _cs_matrix.__sub__(self, other, csr_minus_csr)
-    
+
     def __truediv__(self,other):
         return _cs_matrix.__truediv__(self,other, csr_eldiv_csr)
 
@@ -1297,13 +1319,13 @@
 
     def _getslice(self, i, myslice):
         return self._getrowslice(i, myslice)
-    
+
     def _getrowslice(self, i, myslice):
         """Returns a view of the elements [i, myslice.start:myslice.stop].
         """
         start, stop, stride = myslice.indices(self.shape[1])
         return _cs_matrix._get_slice(self, i, start, stop, stride, (1, stop-start))
-    
+
     def __setitem__(self, key, val):
         if isinstance(key, tuple):
             row = key[0]
@@ -1331,21 +1353,21 @@
                     alloc = max(1, self.allocsize)
                     self.data = resize1d(self.data, nzmax + alloc)
                     self.indices = resize1d(self.indices, nzmax + alloc)
-                    
+
                 newindex = self.indptr[row]
                 self.data[newindex+1:]   = self.data[newindex:-1]
                 self.indices[newindex+1:] = self.indices[newindex:-1]
-                
+
                 self.data[newindex]   = val
                 self.indices[newindex] = col
                 self.indptr[row+1:] += 1
-                
+
             elif len(indxs[0]) == 1:
                 #value already present
                 self.data[self.indptr[row]:self.indptr[row+1]][indxs[0]] = val
             else:
                 raise IndexError, "row index occurs more than once"
-            
+
             self._check()
         else:
             # We should allow slices here!
@@ -1372,7 +1394,7 @@
 
     def _tothis(self, other):
         return other.tocsr()
-    
+
     def toarray(self):
         data = numpy.zeros(self.shape, self.data.dtype)
         csrtodense(self.shape[0], self.shape[1], self.indptr, self.indices,
@@ -1423,16 +1445,7 @@
         to copy.
         """
         dict.__init__(self)
-        spmatrix.__init__(self)
-        if shape is None:
-            self.shape = (0, 0)
-        else:
-            try:
-                m, n = shape
-            except:
-                raise "shape not understood"
-            else:
-                self.shape = shape
+        spmatrix.__init__(self,shape)
         self.dtype = getdtype(dtype, A, default=float)
         if A is not None:
             if isinstance(A, tuple):
@@ -1670,7 +1683,7 @@
                         # Not a sequence
                         raise TypeError, "unsupported type for" \
                                          " dok_matrix.__setitem__"
-                    
+
                     # Value is a sequence
                     for element, val in itertools.izip(seq, value):
                         self[element, j] = val   # don't use dict.__setitem__
@@ -2009,12 +2022,12 @@
     where the dimensions are optional.  If supplied, we set (M, N) = dims.
     If not supplied, we infer these from the index arrays
     ij[0][:] and ij[1][:]
-    
+
     The arguments 'obj' and 'ij' represent three arrays:
         1. obj[:]    the entries of the matrix, in any order
         2. ij[0][:]  the row indices of the matrix entries
         3. ij[1][:]  the column indices of the matrix entries
-    
+
     So the following holds:
         A[ij[0][k], ij[1][k] = obj[k]
     """
@@ -2039,7 +2052,7 @@
             return
         else:
             raise TypeError, "invalid input format"
-        
+
         self.dtype = getdtype(dtype, obj, default=float)
 
         try:
@@ -2047,7 +2060,7 @@
                 raise TypeError
         except TypeError:
             raise TypeError, "invalid input format"
-        
+
         if dims is None:
             if len(ij[0]) == 0 or len(ij[1]) == 0:
                 raise ValueError, "cannot infer dimensions from zero sized index arrays"
@@ -2058,7 +2071,7 @@
             # Use 2 steps to ensure dims has length 2.
             M, N = dims
             self.shape = (M, N)
-            
+
         self.row = asarray(ij[0], dtype=numpy.intc)
         self.col = asarray(ij[1], dtype=numpy.intc)
         self.data = asarray(obj, dtype=self.dtype)
@@ -2096,16 +2109,16 @@
             #sort by increasing rows first, columns second
             if getattr(self, '_is_normalized', None):
                 #columns already sorted, use stable sort for rows
-                P = numpy.argsort(self.row, kind='mergesort')        
+                P = numpy.argsort(self.row, kind='mergesort')
                 return self.data[P], self.row[P], self.col[P]
             else:
                 #nothing already sorted
-                P  = numpy.lexsort(keys=(self.col, self.row))                
+                P  = numpy.lexsort(keys=(self.col, self.row))
                 return self.data[P], self.row[P], self.col[P]
         if getattr(self, '_is_normalized', None):
             return self.data, self.row, self.col
         #sort by increasing rows first, columns second
-        P  = numpy.lexsort(keys=(self.row, self.col))        
+        P  = numpy.lexsort(keys=(self.row, self.col))
         self.data, self.row, self.col = self.data[P], self.row[P], self.col[P]
         setattr(self, '_is_normalized', 1)
         return self.data, self.row, self.col
@@ -2125,23 +2138,23 @@
                                             self.data)
             return csc_matrix((data, rowind, indptr), self.shape, check=False)
 
-    
+
     def tocsr(self):
         if self.nnz == 0:
             return csr_matrix(self.shape, dtype=self.dtype)
         else:
             indptr, colind, data = cootocsr(self.shape[0], self.shape[1], \
                                             self.size, self.row, self.col, \
-                                            self.data)        
+                                            self.data)
             return csr_matrix((data, colind, indptr), self.shape, check=False)
-            
+
     def tocoo(self, copy=False):
         return self.toself(copy)
 
 
 class lil_matrix(spmatrix):
     """Row-based linked list matrix, by Ed Schofield.
-    
+
     This contains a list (self.rows) of rows, each of which is a sorted
     list of column indices of non-zero elements. It also contains a list
     (self.data) of lists of these elements.
@@ -2176,7 +2189,7 @@
                         if not isinstance(A, lil_matrix) and \
                                 not isinstance(A, csr_matrix):
                             raise TypeError, "unsupported matrix type"
-                    
+
                     # Otherwise, try converting to a matrix.  So if it's
                     # a list (rank 1), it will become a row vector
                     else:
@@ -2251,7 +2264,7 @@
 
         if j < 0:
             j += self.shape[1]
-        
+
         if j < 0 or j > self.shape[1]:
             raise IndexError,'column index out of bounds'
 
@@ -2267,7 +2280,7 @@
         elif j.start is None:
             start = 0
         else:
-            start = j.start                    
+            start = j.start
         if j.stop is not None and j.stop < 0:
             stop = shape + j.stop
         elif j.stop is None:
@@ -2277,7 +2290,7 @@
         j = range(start, stop, j.step or 1)
         return j
 
-        
+
     def __getitem__(self, index):
         """Return the element(s) index=(i, j), where j may be a slice.
         This always returns a copy for consistency, since slices into
@@ -2309,7 +2322,7 @@
         else:
             raise IndexError
 
-    
+
     def _insertat(self, i, j, x):
         """ helper for __setitem__: insert a value at (i,j) where i, j and x
         are all scalars """
@@ -2320,13 +2333,13 @@
     def _insertat2(self, row, data, j, x):
         """ helper for __setitem__: insert a value in the given row/data at
         column j. """
-        
+
         if j < 0: #handle negative column indices
             j += self.shape[1]
 
         if j < 0 or j >= self.shape[1]:
             raise IndexError,'column index out of bounds'
-            
+
         pos = bisect_left(row, j)
         if x != 0:
             if pos == len(row):
@@ -2379,7 +2392,7 @@
             row = self.rows[i]
             data = self.data[i]
             self._insertat3(row, data, j, x)
-        elif issequence(i) and issequence(j):                
+        elif issequence(i) and issequence(j):
             if isscalar(x):
                 for ii, jj in zip(i, j):
                     self._insertat(ii, jj, x)
@@ -2450,6 +2463,15 @@
         new.rows = copy.deepcopy(self.rows)
         return new
 
+    def reshape(self,shape):
+        new = lil_matrix(shape,dtype=self.dtype)
+        j_max = self.shape[1]
+        for i,row in enumerate(self.rows):
+            for col,j in enumerate(row):
+                new_r,new_c = unravel_index(i*j_max + j,shape)
+                new[new_r,new_c] = self[i,j]
+        return new
+
     def __add__(self, other):
         if isscalar(other):
             new = self.copy()
@@ -2465,8 +2487,8 @@
             return self.__mul__(other)
         else:
             return spmatrix.__rmul__(self, other)
-    
-    
+
+
     def toarray(self):
         d = zeros(self.shape, dtype=self.dtype)
         for i, row in enumerate(self.rows):
@@ -2480,7 +2502,7 @@
         # Overriding the spmatrix.transpose method here prevents an unnecessary
         # csr -> csc conversion
         return self.tocsr().transpose()
-    
+
     def tocsr(self, nzmax=None):
         """ Return Compressed Sparse Row format arrays for this matrix.
         """
@@ -2636,7 +2658,7 @@
     assert(len(offsets) == diags.shape[0])
     indptr, rowind, data = sparsetools.spdiags(M, N, len(offsets), offsets, diags)
     return csc_matrix((data, rowind, indptr), (M, N))
-   
+
 def extract_diagonal(A):
     """
     extract_diagonal(A) returns the main diagonal of A.
@@ -2739,5 +2761,3 @@
 
 def issequence(t):
     return isinstance(t, (list, tuple))
-
-

Modified: trunk/scipy/sparse/tests/test_sparse.py
===================================================================
--- trunk/scipy/sparse/tests/test_sparse.py	2007-08-27 23:14:25 UTC (rev 3271)
+++ trunk/scipy/sparse/tests/test_sparse.py	2007-08-28 16:29:41 UTC (rev 3272)
@@ -813,6 +813,17 @@
         x = x*0
         assert_equal(x[0,0],0)
 
+    def check_reshape(self):
+        x = lil_matrix((4,3))
+        x[0,0] = 1
+        x[2,1] = 3
+        x[3,2] = 5
+        x[0,2] = 7
+
+        for s in [(12,1),(1,12)]:
+            assert_array_equal(x.reshape(s).todense(),
+                               x.todense().reshape(s))
+
     def check_lil_lil_assignment(self):
         """ Tests whether a row of one lil_matrix can be assigned to
         another.




More information about the Scipy-svn mailing list