Coverage for /usr/lib/python3/dist-packages/scipy/sparse/_data.py: 25%
191 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""Base class for sparse matrice with a .data attribute
3 subclasses must provide a _with_data() method that
4 creates a new matrix with the same sparsity pattern
5 as self but with a different data array
7"""
9import numpy as np
11from ._base import _spbase, _ufuncs_with_fixed_point_at_zero
12from ._sputils import isscalarlike, validateaxis
14__all__ = []
17# TODO implement all relevant operations
18# use .data.__methods__() instead of /=, *=, etc.
19class _data_matrix(_spbase):
20 def __init__(self):
21 _spbase.__init__(self)
23 def _get_dtype(self):
24 return self.data.dtype
26 def _set_dtype(self, newtype):
27 self.data.dtype = newtype
28 dtype = property(fget=_get_dtype, fset=_set_dtype)
30 def _deduped_data(self):
31 if hasattr(self, 'sum_duplicates'):
32 self.sum_duplicates()
33 return self.data
35 def __abs__(self):
36 return self._with_data(abs(self._deduped_data()))
38 def __round__(self, ndigits=0):
39 return self._with_data(np.around(self._deduped_data(), decimals=ndigits))
41 def _real(self):
42 return self._with_data(self.data.real)
44 def _imag(self):
45 return self._with_data(self.data.imag)
47 def __neg__(self):
48 if self.dtype.kind == 'b':
49 raise NotImplementedError('negating a boolean sparse array is not '
50 'supported')
51 return self._with_data(-self.data)
53 def __imul__(self, other): # self *= other
54 if isscalarlike(other):
55 self.data *= other
56 return self
57 else:
58 return NotImplemented
60 def __itruediv__(self, other): # self /= other
61 if isscalarlike(other):
62 recip = 1.0 / other
63 self.data *= recip
64 return self
65 else:
66 return NotImplemented
68 def astype(self, dtype, casting='unsafe', copy=True):
69 dtype = np.dtype(dtype)
70 if self.dtype != dtype:
71 matrix = self._with_data(
72 self.data.astype(dtype, casting=casting, copy=True),
73 copy=True
74 )
75 return matrix._with_data(matrix._deduped_data(), copy=False)
76 elif copy:
77 return self.copy()
78 else:
79 return self
81 astype.__doc__ = _spbase.astype.__doc__
83 def conjugate(self, copy=True):
84 if np.issubdtype(self.dtype, np.complexfloating):
85 return self._with_data(self.data.conjugate(), copy=copy)
86 elif copy:
87 return self.copy()
88 else:
89 return self
91 conjugate.__doc__ = _spbase.conjugate.__doc__
93 def copy(self):
94 return self._with_data(self.data.copy(), copy=True)
96 copy.__doc__ = _spbase.copy.__doc__
98 def count_nonzero(self):
99 return np.count_nonzero(self._deduped_data())
101 count_nonzero.__doc__ = _spbase.count_nonzero.__doc__
103 def power(self, n, dtype=None):
104 """
105 This function performs element-wise power.
107 Parameters
108 ----------
109 n : scalar
110 n is a non-zero scalar (nonzero avoids dense ones creation)
111 If zero power is desired, special case it to use `np.ones`
113 dtype : If dtype is not specified, the current dtype will be preserved.
115 Raises
116 ------
117 NotImplementedError : if n is a zero scalar
118 If zero power is desired, special case it to use
119 `np.ones(A.shape, dtype=A.dtype)`
120 """
121 if not isscalarlike(n):
122 raise NotImplementedError("input is not scalar")
123 if not n:
124 raise NotImplementedError(
125 "zero power is not supported as it would densify the matrix.\n"
126 "Use `np.ones(A.shape, dtype=A.dtype)` for this case."
127 )
129 data = self._deduped_data()
130 if dtype is not None:
131 data = data.astype(dtype)
132 return self._with_data(data ** n)
134 ###########################
135 # Multiplication handlers #
136 ###########################
138 def _mul_scalar(self, other):
139 return self._with_data(self.data * other)
142# Add the numpy unary ufuncs for which func(0) = 0 to _data_matrix.
143for npfunc in _ufuncs_with_fixed_point_at_zero:
144 name = npfunc.__name__
146 def _create_method(op):
147 def method(self):
148 result = op(self._deduped_data())
149 return self._with_data(result, copy=True)
151 method.__doc__ = ("Element-wise {}.\n\n"
152 "See `numpy.{}` for more information.".format(name, name))
153 method.__name__ = name
155 return method
157 setattr(_data_matrix, name, _create_method(npfunc))
160def _find_missing_index(ind, n):
161 for k, a in enumerate(ind):
162 if k != a:
163 return k
165 k += 1
166 if k < n:
167 return k
168 else:
169 return -1
172class _minmax_mixin:
173 """Mixin for min and max methods.
175 These are not implemented for dia_matrix, hence the separate class.
176 """
178 def _min_or_max_axis(self, axis, min_or_max):
179 N = self.shape[axis]
180 if N == 0:
181 raise ValueError("zero-size array to reduction operation")
182 M = self.shape[1 - axis]
183 idx_dtype = self._get_index_dtype(maxval=M)
185 mat = self.tocsc() if axis == 0 else self.tocsr()
186 mat.sum_duplicates()
188 major_index, value = mat._minor_reduce(min_or_max)
189 not_full = np.diff(mat.indptr)[major_index] < N
190 value[not_full] = min_or_max(value[not_full], 0)
192 mask = value != 0
193 major_index = np.compress(mask, major_index)
194 value = np.compress(mask, value)
196 if axis == 0:
197 return self._coo_container(
198 (value, (np.zeros(len(value), dtype=idx_dtype), major_index)),
199 dtype=self.dtype, shape=(1, M)
200 )
201 else:
202 return self._coo_container(
203 (value, (major_index, np.zeros(len(value), dtype=idx_dtype))),
204 dtype=self.dtype, shape=(M, 1)
205 )
207 def _min_or_max(self, axis, out, min_or_max):
208 if out is not None:
209 raise ValueError("Sparse matrices do not support "
210 "an 'out' parameter.")
212 validateaxis(axis)
214 if axis is None:
215 if 0 in self.shape:
216 raise ValueError("zero-size array to reduction operation")
218 zero = self.dtype.type(0)
219 if self.nnz == 0:
220 return zero
221 m = min_or_max.reduce(self._deduped_data().ravel())
222 if self.nnz != np.prod(self.shape):
223 m = min_or_max(zero, m)
224 return m
226 if axis < 0:
227 axis += 2
229 if (axis == 0) or (axis == 1):
230 return self._min_or_max_axis(axis, min_or_max)
231 else:
232 raise ValueError("axis out of range")
234 def _arg_min_or_max_axis(self, axis, argmin_or_argmax, compare):
235 if self.shape[axis] == 0:
236 raise ValueError("Can't apply the operation along a zero-sized "
237 "dimension.")
239 if axis < 0:
240 axis += 2
242 zero = self.dtype.type(0)
244 mat = self.tocsc() if axis == 0 else self.tocsr()
245 mat.sum_duplicates()
247 ret_size, line_size = mat._swap(mat.shape)
248 ret = np.zeros(ret_size, dtype=int)
250 nz_lines, = np.nonzero(np.diff(mat.indptr))
251 for i in nz_lines:
252 p, q = mat.indptr[i:i + 2]
253 data = mat.data[p:q]
254 indices = mat.indices[p:q]
255 extreme_index = argmin_or_argmax(data)
256 extreme_value = data[extreme_index]
257 if compare(extreme_value, zero) or q - p == line_size:
258 ret[i] = indices[extreme_index]
259 else:
260 zero_ind = _find_missing_index(indices, line_size)
261 if extreme_value == zero:
262 ret[i] = min(extreme_index, zero_ind)
263 else:
264 ret[i] = zero_ind
266 if axis == 1:
267 ret = ret.reshape(-1, 1)
269 return self._ascontainer(ret)
271 def _arg_min_or_max(self, axis, out, argmin_or_argmax, compare):
272 if out is not None:
273 raise ValueError("Sparse types do not support an 'out' parameter.")
275 validateaxis(axis)
277 if axis is not None:
278 return self._arg_min_or_max_axis(axis, argmin_or_argmax, compare)
280 if 0 in self.shape:
281 raise ValueError("Can't apply the operation to an empty matrix.")
283 if self.nnz == 0:
284 return 0
286 zero = self.dtype.type(0)
287 mat = self.tocoo()
288 # Convert to canonical form: no duplicates, sorted indices.
289 mat.sum_duplicates()
290 extreme_index = argmin_or_argmax(mat.data)
291 extreme_value = mat.data[extreme_index]
292 num_row, num_col = mat.shape
294 # If the min value is less than zero, or max is greater than zero,
295 # then we don't need to worry about implicit zeros.
296 if compare(extreme_value, zero):
297 # cast to Python int to avoid overflow and RuntimeError
298 return (int(mat.row[extreme_index]) * num_col +
299 int(mat.col[extreme_index]))
301 # Cheap test for the rare case where we have no implicit zeros.
302 size = num_row * num_col
303 if size == mat.nnz:
304 return (int(mat.row[extreme_index]) * num_col +
305 int(mat.col[extreme_index]))
307 # At this stage, any implicit zero could be the min or max value.
308 # After sum_duplicates(), the `row` and `col` arrays are guaranteed to
309 # be sorted in C-order, which means the linearized indices are sorted.
310 linear_indices = mat.row * num_col + mat.col
311 first_implicit_zero_index = _find_missing_index(linear_indices, size)
312 if extreme_value == zero:
313 return min(first_implicit_zero_index, extreme_index)
314 return first_implicit_zero_index
316 def max(self, axis=None, out=None):
317 """
318 Return the maximum of the matrix or maximum along an axis.
319 This takes all elements into account, not just the non-zero ones.
321 Parameters
322 ----------
323 axis : {-2, -1, 0, 1, None} optional
324 Axis along which the sum is computed. The default is to
325 compute the maximum over all the matrix elements, returning
326 a scalar (i.e., `axis` = `None`).
328 out : None, optional
329 This argument is in the signature *solely* for NumPy
330 compatibility reasons. Do not pass in anything except
331 for the default value, as this argument is not used.
333 Returns
334 -------
335 amax : coo_matrix or scalar
336 Maximum of `a`. If `axis` is None, the result is a scalar value.
337 If `axis` is given, the result is a sparse.coo_matrix of dimension
338 ``a.ndim - 1``.
340 See Also
341 --------
342 min : The minimum value of a sparse matrix along a given axis.
343 numpy.matrix.max : NumPy's implementation of 'max' for matrices
345 """
346 return self._min_or_max(axis, out, np.maximum)
348 def min(self, axis=None, out=None):
349 """
350 Return the minimum of the matrix or maximum along an axis.
351 This takes all elements into account, not just the non-zero ones.
353 Parameters
354 ----------
355 axis : {-2, -1, 0, 1, None} optional
356 Axis along which the sum is computed. The default is to
357 compute the minimum over all the matrix elements, returning
358 a scalar (i.e., `axis` = `None`).
360 out : None, optional
361 This argument is in the signature *solely* for NumPy
362 compatibility reasons. Do not pass in anything except for
363 the default value, as this argument is not used.
365 Returns
366 -------
367 amin : coo_matrix or scalar
368 Minimum of `a`. If `axis` is None, the result is a scalar value.
369 If `axis` is given, the result is a sparse.coo_matrix of dimension
370 ``a.ndim - 1``.
372 See Also
373 --------
374 max : The maximum value of a sparse matrix along a given axis.
375 numpy.matrix.min : NumPy's implementation of 'min' for matrices
377 """
378 return self._min_or_max(axis, out, np.minimum)
380 def nanmax(self, axis=None, out=None):
381 """
382 Return the maximum of the matrix or maximum along an axis, ignoring any
383 NaNs. This takes all elements into account, not just the non-zero
384 ones.
386 .. versionadded:: 1.11.0
388 Parameters
389 ----------
390 axis : {-2, -1, 0, 1, None} optional
391 Axis along which the maximum is computed. The default is to
392 compute the maximum over all the matrix elements, returning
393 a scalar (i.e., `axis` = `None`).
395 out : None, optional
396 This argument is in the signature *solely* for NumPy
397 compatibility reasons. Do not pass in anything except
398 for the default value, as this argument is not used.
400 Returns
401 -------
402 amax : coo_matrix or scalar
403 Maximum of `a`. If `axis` is None, the result is a scalar value.
404 If `axis` is given, the result is a sparse.coo_matrix of dimension
405 ``a.ndim - 1``.
407 See Also
408 --------
409 nanmin : The minimum value of a sparse matrix along a given axis,
410 ignoring NaNs.
411 max : The maximum value of a sparse matrix along a given axis,
412 propagating NaNs.
413 numpy.nanmax : NumPy's implementation of 'nanmax'.
415 """
416 return self._min_or_max(axis, out, np.fmax)
418 def nanmin(self, axis=None, out=None):
419 """
420 Return the minimum of the matrix or minimum along an axis, ignoring any
421 NaNs. This takes all elements into account, not just the non-zero
422 ones.
424 .. versionadded:: 1.11.0
426 Parameters
427 ----------
428 axis : {-2, -1, 0, 1, None} optional
429 Axis along which the minimum is computed. The default is to
430 compute the minimum over all the matrix elements, returning
431 a scalar (i.e., `axis` = `None`).
433 out : None, optional
434 This argument is in the signature *solely* for NumPy
435 compatibility reasons. Do not pass in anything except for
436 the default value, as this argument is not used.
438 Returns
439 -------
440 amin : coo_matrix or scalar
441 Minimum of `a`. If `axis` is None, the result is a scalar value.
442 If `axis` is given, the result is a sparse.coo_matrix of dimension
443 ``a.ndim - 1``.
445 See Also
446 --------
447 nanmax : The maximum value of a sparse matrix along a given axis,
448 ignoring NaNs.
449 min : The minimum value of a sparse matrix along a given axis,
450 propagating NaNs.
451 numpy.nanmin : NumPy's implementation of 'nanmin'.
453 """
454 return self._min_or_max(axis, out, np.fmin)
456 def argmax(self, axis=None, out=None):
457 """Return indices of maximum elements along an axis.
459 Implicit zero elements are also taken into account. If there are
460 several maximum values, the index of the first occurrence is returned.
462 Parameters
463 ----------
464 axis : {-2, -1, 0, 1, None}, optional
465 Axis along which the argmax is computed. If None (default), index
466 of the maximum element in the flatten data is returned.
467 out : None, optional
468 This argument is in the signature *solely* for NumPy
469 compatibility reasons. Do not pass in anything except for
470 the default value, as this argument is not used.
472 Returns
473 -------
474 ind : numpy.matrix or int
475 Indices of maximum elements. If matrix, its size along `axis` is 1.
476 """
477 return self._arg_min_or_max(axis, out, np.argmax, np.greater)
479 def argmin(self, axis=None, out=None):
480 """Return indices of minimum elements along an axis.
482 Implicit zero elements are also taken into account. If there are
483 several minimum values, the index of the first occurrence is returned.
485 Parameters
486 ----------
487 axis : {-2, -1, 0, 1, None}, optional
488 Axis along which the argmin is computed. If None (default), index
489 of the minimum element in the flatten data is returned.
490 out : None, optional
491 This argument is in the signature *solely* for NumPy
492 compatibility reasons. Do not pass in anything except for
493 the default value, as this argument is not used.
495 Returns
496 -------
497 ind : numpy.matrix or int
498 Indices of minimum elements. If matrix, its size along `axis` is 1.
499 """
500 return self._arg_min_or_max(axis, out, np.argmin, np.less)