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

1"""Base class for sparse matrice with a .data attribute 

2 

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 

6 

7""" 

8 

9import numpy as np 

10 

11from ._base import _spbase, _ufuncs_with_fixed_point_at_zero 

12from ._sputils import isscalarlike, validateaxis 

13 

14__all__ = [] 

15 

16 

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) 

22 

23 def _get_dtype(self): 

24 return self.data.dtype 

25 

26 def _set_dtype(self, newtype): 

27 self.data.dtype = newtype 

28 dtype = property(fget=_get_dtype, fset=_set_dtype) 

29 

30 def _deduped_data(self): 

31 if hasattr(self, 'sum_duplicates'): 

32 self.sum_duplicates() 

33 return self.data 

34 

35 def __abs__(self): 

36 return self._with_data(abs(self._deduped_data())) 

37 

38 def __round__(self, ndigits=0): 

39 return self._with_data(np.around(self._deduped_data(), decimals=ndigits)) 

40 

41 def _real(self): 

42 return self._with_data(self.data.real) 

43 

44 def _imag(self): 

45 return self._with_data(self.data.imag) 

46 

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) 

52 

53 def __imul__(self, other): # self *= other 

54 if isscalarlike(other): 

55 self.data *= other 

56 return self 

57 else: 

58 return NotImplemented 

59 

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 

67 

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 

80 

81 astype.__doc__ = _spbase.astype.__doc__ 

82 

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 

90 

91 conjugate.__doc__ = _spbase.conjugate.__doc__ 

92 

93 def copy(self): 

94 return self._with_data(self.data.copy(), copy=True) 

95 

96 copy.__doc__ = _spbase.copy.__doc__ 

97 

98 def count_nonzero(self): 

99 return np.count_nonzero(self._deduped_data()) 

100 

101 count_nonzero.__doc__ = _spbase.count_nonzero.__doc__ 

102 

103 def power(self, n, dtype=None): 

104 """ 

105 This function performs element-wise power. 

106 

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` 

112 

113 dtype : If dtype is not specified, the current dtype will be preserved. 

114 

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 ) 

128 

129 data = self._deduped_data() 

130 if dtype is not None: 

131 data = data.astype(dtype) 

132 return self._with_data(data ** n) 

133 

134 ########################### 

135 # Multiplication handlers # 

136 ########################### 

137 

138 def _mul_scalar(self, other): 

139 return self._with_data(self.data * other) 

140 

141 

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__ 

145 

146 def _create_method(op): 

147 def method(self): 

148 result = op(self._deduped_data()) 

149 return self._with_data(result, copy=True) 

150 

151 method.__doc__ = ("Element-wise {}.\n\n" 

152 "See `numpy.{}` for more information.".format(name, name)) 

153 method.__name__ = name 

154 

155 return method 

156 

157 setattr(_data_matrix, name, _create_method(npfunc)) 

158 

159 

160def _find_missing_index(ind, n): 

161 for k, a in enumerate(ind): 

162 if k != a: 

163 return k 

164 

165 k += 1 

166 if k < n: 

167 return k 

168 else: 

169 return -1 

170 

171 

172class _minmax_mixin: 

173 """Mixin for min and max methods. 

174 

175 These are not implemented for dia_matrix, hence the separate class. 

176 """ 

177 

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) 

184 

185 mat = self.tocsc() if axis == 0 else self.tocsr() 

186 mat.sum_duplicates() 

187 

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) 

191 

192 mask = value != 0 

193 major_index = np.compress(mask, major_index) 

194 value = np.compress(mask, value) 

195 

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 ) 

206 

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.") 

211 

212 validateaxis(axis) 

213 

214 if axis is None: 

215 if 0 in self.shape: 

216 raise ValueError("zero-size array to reduction operation") 

217 

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 

225 

226 if axis < 0: 

227 axis += 2 

228 

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") 

233 

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.") 

238 

239 if axis < 0: 

240 axis += 2 

241 

242 zero = self.dtype.type(0) 

243 

244 mat = self.tocsc() if axis == 0 else self.tocsr() 

245 mat.sum_duplicates() 

246 

247 ret_size, line_size = mat._swap(mat.shape) 

248 ret = np.zeros(ret_size, dtype=int) 

249 

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 

265 

266 if axis == 1: 

267 ret = ret.reshape(-1, 1) 

268 

269 return self._ascontainer(ret) 

270 

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.") 

274 

275 validateaxis(axis) 

276 

277 if axis is not None: 

278 return self._arg_min_or_max_axis(axis, argmin_or_argmax, compare) 

279 

280 if 0 in self.shape: 

281 raise ValueError("Can't apply the operation to an empty matrix.") 

282 

283 if self.nnz == 0: 

284 return 0 

285 

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 

293 

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])) 

300 

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])) 

306 

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 

315 

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. 

320 

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`). 

327 

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. 

332 

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``. 

339 

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 

344 

345 """ 

346 return self._min_or_max(axis, out, np.maximum) 

347 

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. 

352 

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`). 

359 

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. 

364 

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``. 

371 

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 

376 

377 """ 

378 return self._min_or_max(axis, out, np.minimum) 

379 

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. 

385 

386 .. versionadded:: 1.11.0 

387 

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`). 

394 

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. 

399 

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``. 

406 

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'. 

414 

415 """ 

416 return self._min_or_max(axis, out, np.fmax) 

417 

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. 

423 

424 .. versionadded:: 1.11.0 

425 

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`). 

432 

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. 

437 

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``. 

444 

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'. 

452 

453 """ 

454 return self._min_or_max(axis, out, np.fmin) 

455 

456 def argmax(self, axis=None, out=None): 

457 """Return indices of maximum elements along an axis. 

458 

459 Implicit zero elements are also taken into account. If there are 

460 several maximum values, the index of the first occurrence is returned. 

461 

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. 

471 

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) 

478 

479 def argmin(self, axis=None, out=None): 

480 """Return indices of minimum elements along an axis. 

481 

482 Implicit zero elements are also taken into account. If there are 

483 several minimum values, the index of the first occurrence is returned. 

484 

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. 

494 

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)