Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/scipy/sparse/_dia.py: 15%
247 statements
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-14 06:37 +0000
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-14 06:37 +0000
1"""Sparse DIAgonal format"""
3__docformat__ = "restructuredtext en"
5__all__ = ['dia_array', 'dia_matrix', 'isspmatrix_dia']
7import numpy as np
9from ._matrix import spmatrix
10from ._base import issparse, _formats, _spbase, sparray
11from ._data import _data_matrix
12from ._sputils import (
13 isshape, upcast_char, getdtype, get_sum_dtype, validateaxis, check_shape
14)
15from ._sparsetools import dia_matvec
18class _dia_base(_data_matrix):
19 _format = 'dia'
21 def __init__(self, arg1, shape=None, dtype=None, copy=False):
22 _data_matrix.__init__(self)
24 if issparse(arg1):
25 if arg1.format == "dia":
26 if copy:
27 arg1 = arg1.copy()
28 self.data = arg1.data
29 self.offsets = arg1.offsets
30 self._shape = check_shape(arg1.shape)
31 else:
32 if arg1.format == self.format and copy:
33 A = arg1.copy()
34 else:
35 A = arg1.todia()
36 self.data = A.data
37 self.offsets = A.offsets
38 self._shape = check_shape(A.shape)
39 elif isinstance(arg1, tuple):
40 if isshape(arg1):
41 # It's a tuple of matrix dimensions (M, N)
42 # create empty matrix
43 self._shape = check_shape(arg1)
44 self.data = np.zeros((0,0), getdtype(dtype, default=float))
45 idx_dtype = self._get_index_dtype(maxval=max(self.shape))
46 self.offsets = np.zeros((0), dtype=idx_dtype)
47 else:
48 try:
49 # Try interpreting it as (data, offsets)
50 data, offsets = arg1
51 except Exception as e:
52 message = 'unrecognized form for dia_array constructor'
53 raise ValueError(message) from e
54 else:
55 if shape is None:
56 raise ValueError('expected a shape argument')
57 self.data = np.atleast_2d(np.array(arg1[0], dtype=dtype, copy=copy))
58 offsets = np.array(arg1[1],
59 dtype=self._get_index_dtype(maxval=max(shape)),
60 copy=copy)
61 self.offsets = np.atleast_1d(offsets)
62 self._shape = check_shape(shape)
63 else:
64 #must be dense, convert to COO first, then to DIA
65 try:
66 arg1 = np.asarray(arg1)
67 except Exception as e:
68 raise ValueError("unrecognized form for"
69 " %s_matrix constructor" % self.format) from e
70 A = self._coo_container(arg1, dtype=dtype, shape=shape).todia()
71 self.data = A.data
72 self.offsets = A.offsets
73 self._shape = check_shape(A.shape)
75 if dtype is not None:
76 self.data = self.data.astype(dtype)
78 #check format
79 if self.offsets.ndim != 1:
80 raise ValueError('offsets array must have rank 1')
82 if self.data.ndim != 2:
83 raise ValueError('data array must have rank 2')
85 if self.data.shape[0] != len(self.offsets):
86 raise ValueError('number of diagonals (%d) '
87 'does not match the number of offsets (%d)'
88 % (self.data.shape[0], len(self.offsets)))
90 if len(np.unique(self.offsets)) != len(self.offsets):
91 raise ValueError('offset array contains duplicate values')
93 def __repr__(self):
94 _, fmt = _formats[self.format]
95 sparse_cls = 'array' if isinstance(self, sparray) else 'matrix'
96 shape_str = 'x'.join(str(x) for x in self.shape)
97 ndiag = self.data.shape[0]
98 return (
99 f"<{shape_str} sparse {sparse_cls} of type '{self.dtype.type}'\n"
100 f"\twith {self.nnz} stored elements ({ndiag} diagonals) in {fmt} format>"
101 )
103 def _data_mask(self):
104 """Returns a mask of the same shape as self.data, where
105 mask[i,j] is True when data[i,j] corresponds to a stored element."""
106 num_rows, num_cols = self.shape
107 offset_inds = np.arange(self.data.shape[1])
108 row = offset_inds - self.offsets[:,None]
109 mask = (row >= 0)
110 mask &= (row < num_rows)
111 mask &= (offset_inds < num_cols)
112 return mask
114 def count_nonzero(self):
115 mask = self._data_mask()
116 return np.count_nonzero(self.data[mask])
118 def _getnnz(self, axis=None):
119 if axis is not None:
120 raise NotImplementedError("_getnnz over an axis is not implemented "
121 "for DIA format")
122 M,N = self.shape
123 nnz = 0
124 for k in self.offsets:
125 if k > 0:
126 nnz += min(M,N-k)
127 else:
128 nnz += min(M+k,N)
129 return int(nnz)
131 _getnnz.__doc__ = _spbase._getnnz.__doc__
132 count_nonzero.__doc__ = _spbase.count_nonzero.__doc__
134 def sum(self, axis=None, dtype=None, out=None):
135 validateaxis(axis)
137 if axis is not None and axis < 0:
138 axis += 2
140 res_dtype = get_sum_dtype(self.dtype)
141 num_rows, num_cols = self.shape
142 ret = None
144 if axis == 0:
145 mask = self._data_mask()
146 x = (self.data * mask).sum(axis=0)
147 if x.shape[0] == num_cols:
148 res = x
149 else:
150 res = np.zeros(num_cols, dtype=x.dtype)
151 res[:x.shape[0]] = x
152 ret = self._ascontainer(res, dtype=res_dtype)
154 else:
155 row_sums = np.zeros((num_rows, 1), dtype=res_dtype)
156 one = np.ones(num_cols, dtype=res_dtype)
157 dia_matvec(num_rows, num_cols, len(self.offsets),
158 self.data.shape[1], self.offsets, self.data, one, row_sums)
160 row_sums = self._ascontainer(row_sums)
162 if axis is None:
163 return row_sums.sum(dtype=dtype, out=out)
165 ret = self._ascontainer(row_sums.sum(axis=axis))
167 if out is not None and out.shape != ret.shape:
168 raise ValueError("dimensions do not match")
170 return ret.sum(axis=(), dtype=dtype, out=out)
172 sum.__doc__ = _spbase.sum.__doc__
174 def _add_sparse(self, other):
176 # Check if other is also of type dia_array
177 if not isinstance(other, type(self)):
178 # If other is not of type dia_array, default to
179 # converting to csr_matrix, as is done in the _add_sparse
180 # method of parent class _spbase
181 return self.tocsr()._add_sparse(other)
183 # The task is to compute m = self + other
184 # Start by making a copy of self, of the datatype
185 # that should result from adding self and other
186 dtype = np.promote_types(self.dtype, other.dtype)
187 m = self.astype(dtype, copy=True)
189 # Then, add all the stored diagonals of other.
190 for d in other.offsets:
191 # Check if the diagonal has already been added.
192 if d in m.offsets:
193 # If the diagonal is already there, we need to take
194 # the sum of the existing and the new
195 m.setdiag(m.diagonal(d) + other.diagonal(d), d)
196 else:
197 m.setdiag(other.diagonal(d), d)
198 return m
200 def _matmul_vector(self, other):
201 x = other
203 y = np.zeros(self.shape[0], dtype=upcast_char(self.dtype.char,
204 x.dtype.char))
206 L = self.data.shape[1]
208 M,N = self.shape
210 dia_matvec(M,N, len(self.offsets), L, self.offsets, self.data,
211 x.ravel(), y.ravel())
213 return y
215 def _setdiag(self, values, k=0):
216 M, N = self.shape
218 if values.ndim == 0:
219 # broadcast
220 values_n = np.inf
221 else:
222 values_n = len(values)
224 if k < 0:
225 n = min(M + k, N, values_n)
226 min_index = 0
227 max_index = n
228 else:
229 n = min(M, N - k, values_n)
230 min_index = k
231 max_index = k + n
233 if values.ndim != 0:
234 # allow also longer sequences
235 values = values[:n]
237 data_rows, data_cols = self.data.shape
238 if k in self.offsets:
239 if max_index > data_cols:
240 data = np.zeros((data_rows, max_index), dtype=self.data.dtype)
241 data[:, :data_cols] = self.data
242 self.data = data
243 self.data[self.offsets == k, min_index:max_index] = values
244 else:
245 self.offsets = np.append(self.offsets, self.offsets.dtype.type(k))
246 m = max(max_index, data_cols)
247 data = np.zeros((data_rows + 1, m), dtype=self.data.dtype)
248 data[:-1, :data_cols] = self.data
249 data[-1, min_index:max_index] = values
250 self.data = data
252 def todia(self, copy=False):
253 if copy:
254 return self.copy()
255 else:
256 return self
258 todia.__doc__ = _spbase.todia.__doc__
260 def transpose(self, axes=None, copy=False):
261 if axes is not None and axes != (1, 0):
262 raise ValueError("Sparse arrays/matrices do not support "
263 "an 'axes' parameter because swapping "
264 "dimensions is the only logical permutation.")
266 num_rows, num_cols = self.shape
267 max_dim = max(self.shape)
269 # flip diagonal offsets
270 offsets = -self.offsets
272 # re-align the data matrix
273 r = np.arange(len(offsets), dtype=np.intc)[:, None]
274 c = np.arange(num_rows, dtype=np.intc) - (offsets % max_dim)[:, None]
275 pad_amount = max(0, max_dim-self.data.shape[1])
276 data = np.hstack((self.data, np.zeros((self.data.shape[0], pad_amount),
277 dtype=self.data.dtype)))
278 data = data[r, c]
279 return self._dia_container((data, offsets), shape=(
280 num_cols, num_rows), copy=copy)
282 transpose.__doc__ = _spbase.transpose.__doc__
284 def diagonal(self, k=0):
285 rows, cols = self.shape
286 if k <= -rows or k >= cols:
287 return np.empty(0, dtype=self.data.dtype)
288 idx, = np.nonzero(self.offsets == k)
289 first_col = max(0, k)
290 last_col = min(rows + k, cols)
291 result_size = last_col - first_col
292 if idx.size == 0:
293 return np.zeros(result_size, dtype=self.data.dtype)
294 result = self.data[idx[0], first_col:last_col]
295 padding = result_size - len(result)
296 if padding > 0:
297 result = np.pad(result, (0, padding), mode='constant')
298 return result
300 diagonal.__doc__ = _spbase.diagonal.__doc__
302 def tocsc(self, copy=False):
303 if self.nnz == 0:
304 return self._csc_container(self.shape, dtype=self.dtype)
306 num_rows, num_cols = self.shape
307 num_offsets, offset_len = self.data.shape
308 offset_inds = np.arange(offset_len)
310 row = offset_inds - self.offsets[:,None]
311 mask = (row >= 0)
312 mask &= (row < num_rows)
313 mask &= (offset_inds < num_cols)
314 mask &= (self.data != 0)
316 idx_dtype = self._get_index_dtype(maxval=max(self.shape))
317 indptr = np.zeros(num_cols + 1, dtype=idx_dtype)
318 indptr[1:offset_len+1] = np.cumsum(mask.sum(axis=0)[:num_cols])
319 if offset_len < num_cols:
320 indptr[offset_len+1:] = indptr[offset_len]
321 indices = row.T[mask.T].astype(idx_dtype, copy=False)
322 data = self.data.T[mask.T]
323 return self._csc_container((data, indices, indptr), shape=self.shape,
324 dtype=self.dtype)
326 tocsc.__doc__ = _spbase.tocsc.__doc__
328 def tocoo(self, copy=False):
329 num_rows, num_cols = self.shape
330 num_offsets, offset_len = self.data.shape
331 offset_inds = np.arange(offset_len)
333 row = offset_inds - self.offsets[:,None]
334 mask = (row >= 0)
335 mask &= (row < num_rows)
336 mask &= (offset_inds < num_cols)
337 mask &= (self.data != 0)
338 row = row[mask]
339 col = np.tile(offset_inds, num_offsets)[mask.ravel()]
340 idx_dtype = self._get_index_dtype(
341 arrays=(self.offsets,), maxval=max(self.shape)
342 )
343 row = row.astype(idx_dtype, copy=False)
344 col = col.astype(idx_dtype, copy=False)
345 data = self.data[mask]
346 # Note: this cannot set has_canonical_format=True, because despite the
347 # lack of duplicates, we do not generate sorted indices.
348 return self._coo_container(
349 (data, (row, col)), shape=self.shape, dtype=self.dtype, copy=False
350 )
352 tocoo.__doc__ = _spbase.tocoo.__doc__
354 # needed by _data_matrix
355 def _with_data(self, data, copy=True):
356 """Returns a matrix with the same sparsity structure as self,
357 but with different data. By default the structure arrays are copied.
358 """
359 if copy:
360 return self._dia_container(
361 (data, self.offsets.copy()), shape=self.shape
362 )
363 else:
364 return self._dia_container(
365 (data, self.offsets), shape=self.shape
366 )
368 def resize(self, *shape):
369 shape = check_shape(shape)
370 M, N = shape
371 # we do not need to handle the case of expanding N
372 self.data = self.data[:, :N]
374 if (M > self.shape[0] and
375 np.any(self.offsets + self.shape[0] < self.data.shape[1])):
376 # explicitly clear values that were previously hidden
377 mask = (self.offsets[:, None] + self.shape[0] <=
378 np.arange(self.data.shape[1]))
379 self.data[mask] = 0
381 self._shape = shape
383 resize.__doc__ = _spbase.resize.__doc__
386def isspmatrix_dia(x):
387 """Is `x` of dia_matrix type?
389 Parameters
390 ----------
391 x
392 object to check for being a dia matrix
394 Returns
395 -------
396 bool
397 True if `x` is a dia matrix, False otherwise
399 Examples
400 --------
401 >>> from scipy.sparse import dia_array, dia_matrix, coo_matrix, isspmatrix_dia
402 >>> isspmatrix_dia(dia_matrix([[5]]))
403 True
404 >>> isspmatrix_dia(dia_array([[5]]))
405 False
406 >>> isspmatrix_dia(coo_matrix([[5]]))
407 False
408 """
409 return isinstance(x, dia_matrix)
412# This namespace class separates array from matrix with isinstance
413class dia_array(_dia_base, sparray):
414 """
415 Sparse array with DIAgonal storage.
417 This can be instantiated in several ways:
418 dia_array(D)
419 where D is a 2-D ndarray
421 dia_array(S)
422 with another sparse array or matrix S (equivalent to S.todia())
424 dia_array((M, N), [dtype])
425 to construct an empty array with shape (M, N),
426 dtype is optional, defaulting to dtype='d'.
428 dia_array((data, offsets), shape=(M, N))
429 where the ``data[k,:]`` stores the diagonal entries for
430 diagonal ``offsets[k]`` (See example below)
432 Attributes
433 ----------
434 dtype : dtype
435 Data type of the array
436 shape : 2-tuple
437 Shape of the array
438 ndim : int
439 Number of dimensions (this is always 2)
440 nnz
441 size
442 data
443 DIA format data array of the array
444 offsets
445 DIA format offset array of the array
446 T
448 Notes
449 -----
451 Sparse arrays can be used in arithmetic operations: they support
452 addition, subtraction, multiplication, division, and matrix power.
454 Examples
455 --------
457 >>> import numpy as np
458 >>> from scipy.sparse import dia_array
459 >>> dia_array((3, 4), dtype=np.int8).toarray()
460 array([[0, 0, 0, 0],
461 [0, 0, 0, 0],
462 [0, 0, 0, 0]], dtype=int8)
464 >>> data = np.array([[1, 2, 3, 4]]).repeat(3, axis=0)
465 >>> offsets = np.array([0, -1, 2])
466 >>> dia_array((data, offsets), shape=(4, 4)).toarray()
467 array([[1, 0, 3, 0],
468 [1, 2, 0, 4],
469 [0, 2, 3, 0],
470 [0, 0, 3, 4]])
472 >>> from scipy.sparse import dia_array
473 >>> n = 10
474 >>> ex = np.ones(n)
475 >>> data = np.array([ex, 2 * ex, ex])
476 >>> offsets = np.array([-1, 0, 1])
477 >>> dia_array((data, offsets), shape=(n, n)).toarray()
478 array([[2., 1., 0., ..., 0., 0., 0.],
479 [1., 2., 1., ..., 0., 0., 0.],
480 [0., 1., 2., ..., 0., 0., 0.],
481 ...,
482 [0., 0., 0., ..., 2., 1., 0.],
483 [0., 0., 0., ..., 1., 2., 1.],
484 [0., 0., 0., ..., 0., 1., 2.]])
485 """
488class dia_matrix(spmatrix, _dia_base):
489 """
490 Sparse matrix with DIAgonal storage.
492 This can be instantiated in several ways:
493 dia_matrix(D)
494 where D is a 2-D ndarray
496 dia_matrix(S)
497 with another sparse array or matrix S (equivalent to S.todia())
499 dia_matrix((M, N), [dtype])
500 to construct an empty matrix with shape (M, N),
501 dtype is optional, defaulting to dtype='d'.
503 dia_matrix((data, offsets), shape=(M, N))
504 where the ``data[k,:]`` stores the diagonal entries for
505 diagonal ``offsets[k]`` (See example below)
507 Attributes
508 ----------
509 dtype : dtype
510 Data type of the matrix
511 shape : 2-tuple
512 Shape of the matrix
513 ndim : int
514 Number of dimensions (this is always 2)
515 nnz
516 size
517 data
518 DIA format data array of the matrix
519 offsets
520 DIA format offset array of the matrix
521 T
523 Notes
524 -----
526 Sparse matrices can be used in arithmetic operations: they support
527 addition, subtraction, multiplication, division, and matrix power.
529 Examples
530 --------
532 >>> import numpy as np
533 >>> from scipy.sparse import dia_matrix
534 >>> dia_matrix((3, 4), dtype=np.int8).toarray()
535 array([[0, 0, 0, 0],
536 [0, 0, 0, 0],
537 [0, 0, 0, 0]], dtype=int8)
539 >>> data = np.array([[1, 2, 3, 4]]).repeat(3, axis=0)
540 >>> offsets = np.array([0, -1, 2])
541 >>> dia_matrix((data, offsets), shape=(4, 4)).toarray()
542 array([[1, 0, 3, 0],
543 [1, 2, 0, 4],
544 [0, 2, 3, 0],
545 [0, 0, 3, 4]])
547 >>> from scipy.sparse import dia_matrix
548 >>> n = 10
549 >>> ex = np.ones(n)
550 >>> data = np.array([ex, 2 * ex, ex])
551 >>> offsets = np.array([-1, 0, 1])
552 >>> dia_matrix((data, offsets), shape=(n, n)).toarray()
553 array([[2., 1., 0., ..., 0., 0., 0.],
554 [1., 2., 1., ..., 0., 0., 0.],
555 [0., 1., 2., ..., 0., 0., 0.],
556 ...,
557 [0., 0., 0., ..., 2., 1., 0.],
558 [0., 0., 0., ..., 1., 2., 1.],
559 [0., 0., 0., ..., 0., 1., 2.]])
560 """