Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/scipy/sparse/_dia.py: 15%
241 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-12 06:31 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-12 06:31 +0000
1"""Sparse DIAgonal format"""
3__docformat__ = "restructuredtext en"
5__all__ = ['dia_matrix', 'isspmatrix_dia']
7import numpy as np
9from ._base import isspmatrix, _formats, spmatrix
10from ._data import _data_matrix
11from ._sputils import (isshape, upcast_char, getdtype, get_index_dtype,
12 get_sum_dtype, validateaxis, check_shape)
13from ._sparsetools import dia_matvec
16class dia_matrix(_data_matrix):
17 """Sparse matrix with DIAgonal storage
19 This can be instantiated in several ways:
20 dia_matrix(D)
21 with a dense matrix
23 dia_matrix(S)
24 with another sparse matrix S (equivalent to S.todia())
26 dia_matrix((M, N), [dtype])
27 to construct an empty matrix with shape (M, N),
28 dtype is optional, defaulting to dtype='d'.
30 dia_matrix((data, offsets), shape=(M, N))
31 where the ``data[k,:]`` stores the diagonal entries for
32 diagonal ``offsets[k]`` (See example below)
34 Attributes
35 ----------
36 dtype : dtype
37 Data type of the matrix
38 shape : 2-tuple
39 Shape of the matrix
40 ndim : int
41 Number of dimensions (this is always 2)
42 nnz
43 Number of stored values, including explicit zeros
44 data
45 DIA format data array of the matrix
46 offsets
47 DIA format offset array of the matrix
49 Notes
50 -----
52 Sparse matrices can be used in arithmetic operations: they support
53 addition, subtraction, multiplication, division, and matrix power.
55 Examples
56 --------
58 >>> import numpy as np
59 >>> from scipy.sparse import dia_matrix
60 >>> dia_matrix((3, 4), dtype=np.int8).toarray()
61 array([[0, 0, 0, 0],
62 [0, 0, 0, 0],
63 [0, 0, 0, 0]], dtype=int8)
65 >>> data = np.array([[1, 2, 3, 4]]).repeat(3, axis=0)
66 >>> offsets = np.array([0, -1, 2])
67 >>> dia_matrix((data, offsets), shape=(4, 4)).toarray()
68 array([[1, 0, 3, 0],
69 [1, 2, 0, 4],
70 [0, 2, 3, 0],
71 [0, 0, 3, 4]])
73 >>> from scipy.sparse import dia_matrix
74 >>> n = 10
75 >>> ex = np.ones(n)
76 >>> data = np.array([ex, 2 * ex, ex])
77 >>> offsets = np.array([-1, 0, 1])
78 >>> dia_matrix((data, offsets), shape=(n, n)).toarray()
79 array([[2., 1., 0., ..., 0., 0., 0.],
80 [1., 2., 1., ..., 0., 0., 0.],
81 [0., 1., 2., ..., 0., 0., 0.],
82 ...,
83 [0., 0., 0., ..., 2., 1., 0.],
84 [0., 0., 0., ..., 1., 2., 1.],
85 [0., 0., 0., ..., 0., 1., 2.]])
86 """
87 format = 'dia'
89 def __init__(self, arg1, shape=None, dtype=None, copy=False):
90 _data_matrix.__init__(self)
92 if isspmatrix_dia(arg1):
93 if copy:
94 arg1 = arg1.copy()
95 self.data = arg1.data
96 self.offsets = arg1.offsets
97 self._shape = check_shape(arg1.shape)
98 elif isspmatrix(arg1):
99 if isspmatrix_dia(arg1) and copy:
100 A = arg1.copy()
101 else:
102 A = arg1.todia()
103 self.data = A.data
104 self.offsets = A.offsets
105 self._shape = check_shape(A.shape)
106 elif isinstance(arg1, tuple):
107 if isshape(arg1):
108 # It's a tuple of matrix dimensions (M, N)
109 # create empty matrix
110 self._shape = check_shape(arg1)
111 self.data = np.zeros((0,0), getdtype(dtype, default=float))
112 idx_dtype = get_index_dtype(maxval=max(self.shape))
113 self.offsets = np.zeros((0), dtype=idx_dtype)
114 else:
115 try:
116 # Try interpreting it as (data, offsets)
117 data, offsets = arg1
118 except Exception as e:
119 raise ValueError('unrecognized form for dia_matrix constructor') from e
120 else:
121 if shape is None:
122 raise ValueError('expected a shape argument')
123 self.data = np.atleast_2d(np.array(arg1[0], dtype=dtype, copy=copy))
124 self.offsets = np.atleast_1d(np.array(arg1[1],
125 dtype=get_index_dtype(maxval=max(shape)),
126 copy=copy))
127 self._shape = check_shape(shape)
128 else:
129 #must be dense, convert to COO first, then to DIA
130 try:
131 arg1 = np.asarray(arg1)
132 except Exception as e:
133 raise ValueError("unrecognized form for"
134 " %s_matrix constructor" % self.format) from e
135 A = self._coo_container(arg1, dtype=dtype, shape=shape).todia()
136 self.data = A.data
137 self.offsets = A.offsets
138 self._shape = check_shape(A.shape)
140 if dtype is not None:
141 self.data = self.data.astype(dtype)
143 #check format
144 if self.offsets.ndim != 1:
145 raise ValueError('offsets array must have rank 1')
147 if self.data.ndim != 2:
148 raise ValueError('data array must have rank 2')
150 if self.data.shape[0] != len(self.offsets):
151 raise ValueError('number of diagonals (%d) '
152 'does not match the number of offsets (%d)'
153 % (self.data.shape[0], len(self.offsets)))
155 if len(np.unique(self.offsets)) != len(self.offsets):
156 raise ValueError('offset array contains duplicate values')
158 def __repr__(self):
159 format = _formats[self.getformat()][1]
160 return "<%dx%d sparse matrix of type '%s'\n" \
161 "\twith %d stored elements (%d diagonals) in %s format>" % \
162 (self.shape + (self.dtype.type, self.nnz, self.data.shape[0],
163 format))
165 def _data_mask(self):
166 """Returns a mask of the same shape as self.data, where
167 mask[i,j] is True when data[i,j] corresponds to a stored element."""
168 num_rows, num_cols = self.shape
169 offset_inds = np.arange(self.data.shape[1])
170 row = offset_inds - self.offsets[:,None]
171 mask = (row >= 0)
172 mask &= (row < num_rows)
173 mask &= (offset_inds < num_cols)
174 return mask
176 def count_nonzero(self):
177 mask = self._data_mask()
178 return np.count_nonzero(self.data[mask])
180 def getnnz(self, axis=None):
181 if axis is not None:
182 raise NotImplementedError("getnnz over an axis is not implemented "
183 "for DIA format")
184 M,N = self.shape
185 nnz = 0
186 for k in self.offsets:
187 if k > 0:
188 nnz += min(M,N-k)
189 else:
190 nnz += min(M+k,N)
191 return int(nnz)
193 getnnz.__doc__ = spmatrix.getnnz.__doc__
194 count_nonzero.__doc__ = spmatrix.count_nonzero.__doc__
196 def sum(self, axis=None, dtype=None, out=None):
197 validateaxis(axis)
199 if axis is not None and axis < 0:
200 axis += 2
202 res_dtype = get_sum_dtype(self.dtype)
203 num_rows, num_cols = self.shape
204 ret = None
206 if axis == 0:
207 mask = self._data_mask()
208 x = (self.data * mask).sum(axis=0)
209 if x.shape[0] == num_cols:
210 res = x
211 else:
212 res = np.zeros(num_cols, dtype=x.dtype)
213 res[:x.shape[0]] = x
214 ret = self._ascontainer(res, dtype=res_dtype)
216 else:
217 row_sums = np.zeros((num_rows, 1), dtype=res_dtype)
218 one = np.ones(num_cols, dtype=res_dtype)
219 dia_matvec(num_rows, num_cols, len(self.offsets),
220 self.data.shape[1], self.offsets, self.data, one, row_sums)
222 row_sums = self._ascontainer(row_sums)
224 if axis is None:
225 return row_sums.sum(dtype=dtype, out=out)
227 ret = self._ascontainer(row_sums.sum(axis=axis))
229 if out is not None and out.shape != ret.shape:
230 raise ValueError("dimensions do not match")
232 return ret.sum(axis=(), dtype=dtype, out=out)
234 sum.__doc__ = spmatrix.sum.__doc__
236 def _add_sparse(self, other):
238 # Check if other is also of type dia_matrix
239 if not isinstance(other, type(self)):
240 # If other is not of type dia_matrix, default to
241 # converting to csr_matrix, as is done in the _add_sparse
242 # method of parent class spmatrix
243 return self.tocsr()._add_sparse(other)
245 # The task is to compute m = self + other
246 # Start by making a copy of self, of the datatype
247 # that should result from adding self and other
248 dtype = np.promote_types(self.dtype, other.dtype)
249 m = self.astype(dtype, copy=True)
251 # Then, add all the stored diagonals of other.
252 for d in other.offsets:
253 # Check if the diagonal has already been added.
254 if d in m.offsets:
255 # If the diagonal is already there, we need to take
256 # the sum of the existing and the new
257 m.setdiag(m.diagonal(d) + other.diagonal(d), d)
258 else:
259 m.setdiag(other.diagonal(d), d)
260 return m
262 def _mul_vector(self, other):
263 x = other
265 y = np.zeros(self.shape[0], dtype=upcast_char(self.dtype.char,
266 x.dtype.char))
268 L = self.data.shape[1]
270 M,N = self.shape
272 dia_matvec(M,N, len(self.offsets), L, self.offsets, self.data, x.ravel(), y.ravel())
274 return y
276 def _mul_multimatrix(self, other):
277 return np.hstack([self._mul_vector(col).reshape(-1,1) for col in other.T])
279 def _setdiag(self, values, k=0):
280 M, N = self.shape
282 if values.ndim == 0:
283 # broadcast
284 values_n = np.inf
285 else:
286 values_n = len(values)
288 if k < 0:
289 n = min(M + k, N, values_n)
290 min_index = 0
291 max_index = n
292 else:
293 n = min(M, N - k, values_n)
294 min_index = k
295 max_index = k + n
297 if values.ndim != 0:
298 # allow also longer sequences
299 values = values[:n]
301 data_rows, data_cols = self.data.shape
302 if k in self.offsets:
303 if max_index > data_cols:
304 data = np.zeros((data_rows, max_index), dtype=self.data.dtype)
305 data[:, :data_cols] = self.data
306 self.data = data
307 self.data[self.offsets == k, min_index:max_index] = values
308 else:
309 self.offsets = np.append(self.offsets, self.offsets.dtype.type(k))
310 m = max(max_index, data_cols)
311 data = np.zeros((data_rows + 1, m), dtype=self.data.dtype)
312 data[:-1, :data_cols] = self.data
313 data[-1, min_index:max_index] = values
314 self.data = data
316 def todia(self, copy=False):
317 if copy:
318 return self.copy()
319 else:
320 return self
322 todia.__doc__ = spmatrix.todia.__doc__
324 def transpose(self, axes=None, copy=False):
325 if axes is not None:
326 raise ValueError(("Sparse matrices do not support "
327 "an 'axes' parameter because swapping "
328 "dimensions is the only logical permutation."))
330 num_rows, num_cols = self.shape
331 max_dim = max(self.shape)
333 # flip diagonal offsets
334 offsets = -self.offsets
336 # re-align the data matrix
337 r = np.arange(len(offsets), dtype=np.intc)[:, None]
338 c = np.arange(num_rows, dtype=np.intc) - (offsets % max_dim)[:, None]
339 pad_amount = max(0, max_dim-self.data.shape[1])
340 data = np.hstack((self.data, np.zeros((self.data.shape[0], pad_amount),
341 dtype=self.data.dtype)))
342 data = data[r, c]
343 return self._dia_container((data, offsets), shape=(
344 num_cols, num_rows), copy=copy)
346 transpose.__doc__ = spmatrix.transpose.__doc__
348 def diagonal(self, k=0):
349 rows, cols = self.shape
350 if k <= -rows or k >= cols:
351 return np.empty(0, dtype=self.data.dtype)
352 idx, = np.nonzero(self.offsets == k)
353 first_col = max(0, k)
354 last_col = min(rows + k, cols)
355 result_size = last_col - first_col
356 if idx.size == 0:
357 return np.zeros(result_size, dtype=self.data.dtype)
358 result = self.data[idx[0], first_col:last_col]
359 padding = result_size - len(result)
360 if padding > 0:
361 result = np.pad(result, (0, padding), mode='constant')
362 return result
364 diagonal.__doc__ = spmatrix.diagonal.__doc__
366 def tocsc(self, copy=False):
367 if self.nnz == 0:
368 return self._csc_container(self.shape, dtype=self.dtype)
370 num_rows, num_cols = self.shape
371 num_offsets, offset_len = self.data.shape
372 offset_inds = np.arange(offset_len)
374 row = offset_inds - self.offsets[:,None]
375 mask = (row >= 0)
376 mask &= (row < num_rows)
377 mask &= (offset_inds < num_cols)
378 mask &= (self.data != 0)
380 idx_dtype = get_index_dtype(maxval=max(self.shape))
381 indptr = np.zeros(num_cols + 1, dtype=idx_dtype)
382 indptr[1:offset_len+1] = np.cumsum(mask.sum(axis=0)[:num_cols])
383 if offset_len < num_cols:
384 indptr[offset_len+1:] = indptr[offset_len]
385 indices = row.T[mask.T].astype(idx_dtype, copy=False)
386 data = self.data.T[mask.T]
387 return self._csc_container((data, indices, indptr), shape=self.shape,
388 dtype=self.dtype)
390 tocsc.__doc__ = spmatrix.tocsc.__doc__
392 def tocoo(self, copy=False):
393 num_rows, num_cols = self.shape
394 num_offsets, offset_len = self.data.shape
395 offset_inds = np.arange(offset_len)
397 row = offset_inds - self.offsets[:,None]
398 mask = (row >= 0)
399 mask &= (row < num_rows)
400 mask &= (offset_inds < num_cols)
401 mask &= (self.data != 0)
402 row = row[mask]
403 col = np.tile(offset_inds, num_offsets)[mask.ravel()]
404 data = self.data[mask]
406 A = self._coo_container(
407 (data, (row, col)), shape=self.shape, dtype=self.dtype
408 )
409 A.has_canonical_format = True
410 return A
412 tocoo.__doc__ = spmatrix.tocoo.__doc__
414 # needed by _data_matrix
415 def _with_data(self, data, copy=True):
416 """Returns a matrix with the same sparsity structure as self,
417 but with different data. By default the structure arrays are copied.
418 """
419 if copy:
420 return self._dia_container(
421 (data, self.offsets.copy()), shape=self.shape
422 )
423 else:
424 return self._dia_container(
425 (data, self.offsets), shape=self.shape
426 )
428 def resize(self, *shape):
429 shape = check_shape(shape)
430 M, N = shape
431 # we do not need to handle the case of expanding N
432 self.data = self.data[:, :N]
434 if (M > self.shape[0] and
435 np.any(self.offsets + self.shape[0] < self.data.shape[1])):
436 # explicitly clear values that were previously hidden
437 mask = (self.offsets[:, None] + self.shape[0] <=
438 np.arange(self.data.shape[1]))
439 self.data[mask] = 0
441 self._shape = shape
443 resize.__doc__ = spmatrix.resize.__doc__
446def isspmatrix_dia(x):
447 """Is x of dia_matrix type?
449 Parameters
450 ----------
451 x
452 object to check for being a dia matrix
454 Returns
455 -------
456 bool
457 True if x is a dia matrix, False otherwise
459 Examples
460 --------
461 >>> from scipy.sparse import dia_matrix, isspmatrix_dia
462 >>> isspmatrix_dia(dia_matrix([[5]]))
463 True
465 >>> from scipy.sparse import dia_matrix, csr_matrix, isspmatrix_dia
466 >>> isspmatrix_dia(csr_matrix([[5]]))
467 False
468 """
469 from ._arrays import dia_array
470 return isinstance(x, dia_matrix) or isinstance(x, dia_array)