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