Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/scipy/sparse/_dok.py: 24%
286 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-23 06:43 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-23 06:43 +0000
1"""Dictionary Of Keys based matrix"""
3__docformat__ = "restructuredtext en"
5__all__ = ['dok_array', 'dok_matrix', 'isspmatrix_dok']
7import itertools
8import numpy as np
10from ._matrix import spmatrix
11from ._base import _spbase, sparray, issparse
12from ._index import IndexMixin
13from ._sputils import (isdense, getdtype, isshape, isintlike, isscalarlike,
14 upcast, upcast_scalar, check_shape)
17class _dok_base(_spbase, IndexMixin):
18 _format = 'dok'
20 def __init__(self, arg1, shape=None, dtype=None, copy=False):
21 _spbase.__init__(self)
22 self._dict = {}
24 self.dtype = getdtype(dtype, default=float)
25 if isinstance(arg1, tuple) and isshape(arg1): # (M,N)
26 M, N = arg1
27 self._shape = check_shape((M, N))
28 elif issparse(arg1): # Sparse ctor
29 if arg1.format == self.format and copy:
30 arg1 = arg1.copy()
31 else:
32 arg1 = arg1.todok()
34 if dtype is not None:
35 arg1 = arg1.astype(dtype, copy=False)
37 self._dict.update(arg1)
38 self._shape = check_shape(arg1.shape)
39 self.dtype = arg1.dtype
40 else: # Dense ctor
41 try:
42 arg1 = np.asarray(arg1)
43 except Exception as e:
44 raise TypeError('Invalid input format.') from e
46 if len(arg1.shape) != 2:
47 raise TypeError('Expected rank <=2 dense array or matrix.')
49 d = self._coo_container(arg1, dtype=dtype).todok()
50 self._dict.update(d)
51 self._shape = check_shape(arg1.shape)
52 self.dtype = d.dtype
54 def update(self, val):
55 # Prevent direct usage of update
56 raise NotImplementedError("Direct modification to dok_array element "
57 "is not allowed.")
59 def _update(self, data):
60 """An update method for dict data defined for direct access to
61 `dok_array` data. Main purpose is to be used for effcient conversion
62 from other _spbase classes. Has no checking if `data` is valid."""
63 return self._dict.update(data)
65 def _getnnz(self, axis=None):
66 if axis is not None:
67 raise NotImplementedError("_getnnz over an axis is not implemented "
68 "for DOK format.")
69 return len(self._dict)
71 def count_nonzero(self):
72 return sum(x != 0 for x in self.values())
74 _getnnz.__doc__ = _spbase._getnnz.__doc__
75 count_nonzero.__doc__ = _spbase.count_nonzero.__doc__
77 def __len__(self):
78 return len(self._dict)
80 def __contains__(self, key):
81 return key in self._dict
83 def setdefault(self, key, default=None, /):
84 return self._dict.setdefault(key, default)
86 def __delitem__(self, key, /):
87 del self._dict[key]
89 def clear(self):
90 return self._dict.clear()
92 def popitem(self):
93 return self._dict.popitem()
95 def items(self):
96 return self._dict.items()
98 def keys(self):
99 return self._dict.keys()
101 def values(self):
102 return self._dict.values()
104 def get(self, key, default=0.):
105 """This overrides the dict.get method, providing type checking
106 but otherwise equivalent functionality.
107 """
108 try:
109 i, j = key
110 assert isintlike(i) and isintlike(j)
111 except (AssertionError, TypeError, ValueError) as e:
112 raise IndexError('Index must be a pair of integers.') from e
113 if (i < 0 or i >= self.shape[0] or j < 0 or j >= self.shape[1]):
114 raise IndexError('Index out of bounds.')
115 return self._dict.get(key, default)
117 def _get_intXint(self, row, col):
118 return self._dict.get((row, col), self.dtype.type(0))
120 def _get_intXslice(self, row, col):
121 return self._get_sliceXslice(slice(row, row+1), col)
123 def _get_sliceXint(self, row, col):
124 return self._get_sliceXslice(row, slice(col, col+1))
126 def _get_sliceXslice(self, row, col):
127 row_start, row_stop, row_step = row.indices(self.shape[0])
128 col_start, col_stop, col_step = col.indices(self.shape[1])
129 row_range = range(row_start, row_stop, row_step)
130 col_range = range(col_start, col_stop, col_step)
131 shape = (len(row_range), len(col_range))
132 # Switch paths only when advantageous
133 # (count the iterations in the loops, adjust for complexity)
134 if len(self) >= 2 * shape[0] * shape[1]:
135 # O(nr*nc) path: loop over <row x col>
136 return self._get_columnXarray(row_range, col_range)
137 # O(nnz) path: loop over entries of self
138 newdok = self._dok_container(shape, dtype=self.dtype)
139 for key in self.keys():
140 i, ri = divmod(int(key[0]) - row_start, row_step)
141 if ri != 0 or i < 0 or i >= shape[0]:
142 continue
143 j, rj = divmod(int(key[1]) - col_start, col_step)
144 if rj != 0 or j < 0 or j >= shape[1]:
145 continue
146 newdok._dict[i, j] = self._dict[key]
147 return newdok
149 def _get_intXarray(self, row, col):
150 col = col.squeeze()
151 return self._get_columnXarray([row], col)
153 def _get_arrayXint(self, row, col):
154 row = row.squeeze()
155 return self._get_columnXarray(row, [col])
157 def _get_sliceXarray(self, row, col):
158 row = list(range(*row.indices(self.shape[0])))
159 return self._get_columnXarray(row, col)
161 def _get_arrayXslice(self, row, col):
162 col = list(range(*col.indices(self.shape[1])))
163 return self._get_columnXarray(row, col)
165 def _get_columnXarray(self, row, col):
166 # outer indexing
167 newdok = self._dok_container((len(row), len(col)), dtype=self.dtype)
169 for i, r in enumerate(row):
170 for j, c in enumerate(col):
171 v = self._dict.get((r, c), 0)
172 if v:
173 newdok._dict[i, j] = v
174 return newdok
176 def _get_arrayXarray(self, row, col):
177 # inner indexing
178 i, j = map(np.atleast_2d, np.broadcast_arrays(row, col))
179 newdok = self._dok_container(i.shape, dtype=self.dtype)
181 for key in itertools.product(range(i.shape[0]), range(i.shape[1])):
182 v = self._dict.get((i[key], j[key]), 0)
183 if v:
184 newdok._dict[key] = v
185 return newdok
187 def _set_intXint(self, row, col, x):
188 key = (row, col)
189 if x:
190 self._dict[key] = x
191 elif key in self._dict:
192 del self._dict[key]
194 def _set_arrayXarray(self, row, col, x):
195 row = list(map(int, row.ravel()))
196 col = list(map(int, col.ravel()))
197 x = x.ravel()
198 self._dict.update(zip(zip(row, col), x))
200 for i in np.nonzero(x == 0)[0]:
201 key = (row[i], col[i])
202 if self._dict[key] == 0:
203 # may have been superseded by later update
204 del self._dict[key]
206 def __add__(self, other):
207 if isscalarlike(other):
208 res_dtype = upcast_scalar(self.dtype, other)
209 new = self._dok_container(self.shape, dtype=res_dtype)
210 # Add this scalar to every element.
211 M, N = self.shape
212 for key in itertools.product(range(M), range(N)):
213 aij = self._dict.get(key, 0) + other
214 if aij:
215 new[key] = aij
216 # new.dtype.char = self.dtype.char
217 elif issparse(other):
218 if other.format == "dok":
219 if other.shape != self.shape:
220 raise ValueError("Matrix dimensions are not equal.")
221 # We could alternatively set the dimensions to the largest of
222 # the two matrices to be summed. Would this be a good idea?
223 res_dtype = upcast(self.dtype, other.dtype)
224 new = self._dok_container(self.shape, dtype=res_dtype)
225 new._dict.update(self._dict)
226 with np.errstate(over='ignore'):
227 new._dict.update((k, new[k] + other[k]) for k in other.keys())
228 else:
229 csc = self.tocsc()
230 new = csc + other
231 elif isdense(other):
232 new = self.todense() + other
233 else:
234 return NotImplemented
235 return new
237 def __radd__(self, other):
238 if isscalarlike(other):
239 new = self._dok_container(self.shape, dtype=self.dtype)
240 M, N = self.shape
241 for key in itertools.product(range(M), range(N)):
242 aij = self._dict.get(key, 0) + other
243 if aij:
244 new[key] = aij
245 elif issparse(other):
246 if other.format == "dok":
247 if other.shape != self.shape:
248 raise ValueError("Matrix dimensions are not equal.")
249 new = self._dok_container(self.shape, dtype=self.dtype)
250 new._dict.update(self._dict)
251 new._dict.update((k, self[k] + other[k]) for k in other)
252 else:
253 csc = self.tocsc()
254 new = csc + other
255 elif isdense(other):
256 new = other + self.todense()
257 else:
258 return NotImplemented
259 return new
261 def __neg__(self):
262 if self.dtype.kind == 'b':
263 raise NotImplementedError('Negating a sparse boolean matrix is not'
264 ' supported.')
265 new = self._dok_container(self.shape, dtype=self.dtype)
266 new._dict.update((k, -self[k]) for k in self.keys())
267 return new
269 def _mul_scalar(self, other):
270 res_dtype = upcast_scalar(self.dtype, other)
271 # Multiply this scalar by every element.
272 new = self._dok_container(self.shape, dtype=res_dtype)
273 new._dict.update(((k, v * other) for k, v in self.items()))
274 return new
276 def _mul_vector(self, other):
277 # matrix * vector
278 result = np.zeros(self.shape[0], dtype=upcast(self.dtype, other.dtype))
279 for (i, j), v in self.items():
280 result[i] += v * other[j]
281 return result
283 def _mul_multivector(self, other):
284 # matrix * multivector
285 result_shape = (self.shape[0], other.shape[1])
286 result_dtype = upcast(self.dtype, other.dtype)
287 result = np.zeros(result_shape, dtype=result_dtype)
288 for (i, j), v in self.items():
289 result[i,:] += v * other[j,:]
290 return result
292 def __imul__(self, other):
293 if isscalarlike(other):
294 self._dict.update((k, v * other) for k, v in self.items())
295 return self
296 return NotImplemented
298 def __truediv__(self, other):
299 if isscalarlike(other):
300 res_dtype = upcast_scalar(self.dtype, other)
301 new = self._dok_container(self.shape, dtype=res_dtype)
302 new._dict.update(((k, v / other) for k, v in self.items()))
303 return new
304 return self.tocsr() / other
306 def __itruediv__(self, other):
307 if isscalarlike(other):
308 self._dict.update((k, v / other) for k, v in self.items())
309 return self
310 return NotImplemented
312 def __reduce__(self):
313 # this approach is necessary because __setstate__ is called after
314 # __setitem__ upon unpickling and since __init__ is not called there
315 # is no shape attribute hence it is not possible to unpickle it.
316 return dict.__reduce__(self)
318 def transpose(self, axes=None, copy=False):
319 if axes is not None and axes != (1, 0):
320 raise ValueError("Sparse arrays/matrices do not support "
321 "an 'axes' parameter because swapping "
322 "dimensions is the only logical permutation.")
324 M, N = self.shape
325 new = self._dok_container((N, M), dtype=self.dtype, copy=copy)
326 new._dict.update((((right, left), val)
327 for (left, right), val in self.items()))
328 return new
330 transpose.__doc__ = _spbase.transpose.__doc__
332 def conjtransp(self):
333 """Return the conjugate transpose."""
334 M, N = self.shape
335 new = self._dok_container((N, M), dtype=self.dtype)
336 new._dict.update((((right, left), np.conj(val))
337 for (left, right), val in self.items()))
338 return new
340 def copy(self):
341 new = self._dok_container(self.shape, dtype=self.dtype)
342 new._dict.update(self._dict)
343 return new
345 copy.__doc__ = _spbase.copy.__doc__
347 def tocoo(self, copy=False):
348 if self.nnz == 0:
349 return self._coo_container(self.shape, dtype=self.dtype)
351 idx_dtype = self._get_index_dtype(maxval=max(self.shape))
352 data = np.fromiter(self.values(), dtype=self.dtype, count=self.nnz)
353 row = np.fromiter((i for i, _ in self.keys()), dtype=idx_dtype, count=self.nnz)
354 col = np.fromiter((j for _, j in self.keys()), dtype=idx_dtype, count=self.nnz)
355 A = self._coo_container(
356 (data, (row, col)), shape=self.shape, dtype=self.dtype
357 )
358 A.has_canonical_format = True
359 return A
361 tocoo.__doc__ = _spbase.tocoo.__doc__
363 def todok(self, copy=False):
364 if copy:
365 return self.copy()
366 return self
368 todok.__doc__ = _spbase.todok.__doc__
370 def tocsc(self, copy=False):
371 return self.tocoo(copy=False).tocsc(copy=copy)
373 tocsc.__doc__ = _spbase.tocsc.__doc__
375 def resize(self, *shape):
376 shape = check_shape(shape)
377 newM, newN = shape
378 M, N = self.shape
379 if newM < M or newN < N:
380 # Remove all elements outside new dimensions
381 for (i, j) in list(self.keys()):
382 if i >= newM or j >= newN:
383 del self._dict[i, j]
384 self._shape = shape
386 resize.__doc__ = _spbase.resize.__doc__
389def isspmatrix_dok(x):
390 """Is `x` of dok_array type?
392 Parameters
393 ----------
394 x
395 object to check for being a dok matrix
397 Returns
398 -------
399 bool
400 True if `x` is a dok matrix, False otherwise
402 Examples
403 --------
404 >>> from scipy.sparse import dok_array, dok_matrix, coo_matrix, isspmatrix_dok
405 >>> isspmatrix_dok(dok_matrix([[5]]))
406 True
407 >>> isspmatrix_dok(dok_array([[5]]))
408 False
409 >>> isspmatrix_dok(coo_matrix([[5]]))
410 False
411 """
412 return isinstance(x, dok_matrix)
415# This namespace class separates array from matrix with isinstance
416class dok_array(_dok_base, sparray):
417 """
418 Dictionary Of Keys based sparse array.
420 This is an efficient structure for constructing sparse
421 arrays incrementally.
423 This can be instantiated in several ways:
424 dok_array(D)
425 where D is a 2-D ndarray
427 dok_array(S)
428 with another sparse array or matrix S (equivalent to S.todok())
430 dok_array((M,N), [dtype])
431 create the array with initial shape (M,N)
432 dtype is optional, defaulting to dtype='d'
434 Attributes
435 ----------
436 dtype : dtype
437 Data type of the array
438 shape : 2-tuple
439 Shape of the array
440 ndim : int
441 Number of dimensions (this is always 2)
442 nnz
443 Number of nonzero elements
444 size
445 T
447 Notes
448 -----
450 Sparse arrays can be used in arithmetic operations: they support
451 addition, subtraction, multiplication, division, and matrix power.
453 - Allows for efficient O(1) access of individual elements.
454 - Duplicates are not allowed.
455 - Can be efficiently converted to a coo_array once constructed.
457 Examples
458 --------
459 >>> import numpy as np
460 >>> from scipy.sparse import dok_array
461 >>> S = dok_array((5, 5), dtype=np.float32)
462 >>> for i in range(5):
463 ... for j in range(5):
464 ... S[i, j] = i + j # Update element
466 """
469class dok_matrix(spmatrix, _dok_base, dict):
470 """
471 Dictionary Of Keys based sparse matrix.
473 This is an efficient structure for constructing sparse
474 matrices incrementally.
476 This can be instantiated in several ways:
477 dok_matrix(D)
478 where D is a 2-D ndarray
480 dok_matrix(S)
481 with another sparse array or matrix S (equivalent to S.todok())
483 dok_matrix((M,N), [dtype])
484 create the matrix with initial shape (M,N)
485 dtype is optional, defaulting to dtype='d'
487 Attributes
488 ----------
489 dtype : dtype
490 Data type of the matrix
491 shape : 2-tuple
492 Shape of the matrix
493 ndim : int
494 Number of dimensions (this is always 2)
495 nnz
496 Number of nonzero elements
497 size
498 T
500 Notes
501 -----
503 Sparse matrices can be used in arithmetic operations: they support
504 addition, subtraction, multiplication, division, and matrix power.
506 - Allows for efficient O(1) access of individual elements.
507 - Duplicates are not allowed.
508 - Can be efficiently converted to a coo_matrix once constructed.
510 Examples
511 --------
512 >>> import numpy as np
513 >>> from scipy.sparse import dok_matrix
514 >>> S = dok_matrix((5, 5), dtype=np.float32)
515 >>> for i in range(5):
516 ... for j in range(5):
517 ... S[i, j] = i + j # Update element
519 """
520 def set_shape(self, shape):
521 new_matrix = self.reshape(shape, copy=False).asformat(self.format)
522 self.__dict__ = new_matrix.__dict__
524 def get_shape(self):
525 """Get shape of a sparse matrix."""
526 return self._shape
528 shape = property(fget=get_shape, fset=set_shape)