Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/scipy/sparse/_data.py: 24%

200 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-03-22 06:44 +0000

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 @property 

24 def dtype(self): 

25 return self.data.dtype 

26 

27 @dtype.setter 

28 def dtype(self, newtype): 

29 self.data.dtype = newtype 

30 

31 def _deduped_data(self): 

32 if hasattr(self, 'sum_duplicates'): 

33 self.sum_duplicates() 

34 return self.data 

35 

36 def __abs__(self): 

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

38 

39 def __round__(self, ndigits=0): 

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

41 

42 def _real(self): 

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

44 

45 def _imag(self): 

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

47 

48 def __neg__(self): 

49 if self.dtype.kind == 'b': 

50 raise NotImplementedError('negating a boolean sparse array is not ' 

51 'supported') 

52 return self._with_data(-self.data) 

53 

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

55 if isscalarlike(other): 

56 self.data *= other 

57 return self 

58 else: 

59 return NotImplemented 

60 

61 def __itruediv__(self, other): # self /= other 

62 if isscalarlike(other): 

63 recip = 1.0 / other 

64 self.data *= recip 

65 return self 

66 else: 

67 return NotImplemented 

68 

69 def astype(self, dtype, casting='unsafe', copy=True): 

70 dtype = np.dtype(dtype) 

71 if self.dtype != dtype: 

72 matrix = self._with_data( 

73 self.data.astype(dtype, casting=casting, copy=True), 

74 copy=True 

75 ) 

76 return matrix._with_data(matrix._deduped_data(), copy=False) 

77 elif copy: 

78 return self.copy() 

79 else: 

80 return self 

81 

82 astype.__doc__ = _spbase.astype.__doc__ 

83 

84 def conjugate(self, copy=True): 

85 if np.issubdtype(self.dtype, np.complexfloating): 

86 return self._with_data(self.data.conjugate(), copy=copy) 

87 elif copy: 

88 return self.copy() 

89 else: 

90 return self 

91 

92 conjugate.__doc__ = _spbase.conjugate.__doc__ 

93 

94 def copy(self): 

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

96 

97 copy.__doc__ = _spbase.copy.__doc__ 

98 

99 def count_nonzero(self): 

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

101 

102 count_nonzero.__doc__ = _spbase.count_nonzero.__doc__ 

103 

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

105 """ 

106 This function performs element-wise power. 

107 

108 Parameters 

109 ---------- 

110 n : scalar 

111 n is a non-zero scalar (nonzero avoids dense ones creation) 

112 If zero power is desired, special case it to use `np.ones` 

113 

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

115 

116 Raises 

117 ------ 

118 NotImplementedError : if n is a zero scalar 

119 If zero power is desired, special case it to use 

120 `np.ones(A.shape, dtype=A.dtype)` 

121 """ 

122 if not isscalarlike(n): 

123 raise NotImplementedError("input is not scalar") 

124 if not n: 

125 raise NotImplementedError( 

126 "zero power is not supported as it would densify the matrix.\n" 

127 "Use `np.ones(A.shape, dtype=A.dtype)` for this case." 

128 ) 

129 

130 data = self._deduped_data() 

131 if dtype is not None: 

132 data = data.astype(dtype) 

133 return self._with_data(data ** n) 

134 

135 ########################### 

136 # Multiplication handlers # 

137 ########################### 

138 

139 def _mul_scalar(self, other): 

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

141 

142 

143# Add the numpy unary ufuncs for which func(0) = 0 to _data_matrix. 

144for npfunc in _ufuncs_with_fixed_point_at_zero: 

145 name = npfunc.__name__ 

146 

147 def _create_method(op): 

148 def method(self): 

149 result = op(self._deduped_data()) 

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

151 

152 method.__doc__ = (f"Element-wise {name}.\n\n" 

153 f"See `numpy.{name}` for more information.") 

154 method.__name__ = name 

155 

156 return method 

157 

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

159 

160 

161def _find_missing_index(ind, n): 

162 for k, a in enumerate(ind): 

163 if k != a: 

164 return k 

165 

166 k += 1 

167 if k < n: 

168 return k 

169 else: 

170 return -1 

171 

172 

173class _minmax_mixin: 

174 """Mixin for min and max methods. 

175 

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

177 """ 

178 

179 def _min_or_max_axis(self, axis, min_or_max): 

180 N = self.shape[axis] 

181 if N == 0: 

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

183 M = self.shape[1 - axis] 

184 idx_dtype = self._get_index_dtype(maxval=M) 

185 

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

187 mat.sum_duplicates() 

188 

189 major_index, value = mat._minor_reduce(min_or_max) 

190 not_full = np.diff(mat.indptr)[major_index] < N 

191 value[not_full] = min_or_max(value[not_full], 0) 

192 

193 mask = value != 0 

194 major_index = np.compress(mask, major_index) 

195 value = np.compress(mask, value) 

196 

197 if axis == 0: 

198 return self._coo_container( 

199 (value, (np.zeros(len(value), dtype=idx_dtype), major_index)), 

200 dtype=self.dtype, shape=(1, M) 

201 ) 

202 else: 

203 return self._coo_container( 

204 (value, (major_index, np.zeros(len(value), dtype=idx_dtype))), 

205 dtype=self.dtype, shape=(M, 1) 

206 ) 

207 

208 def _min_or_max(self, axis, out, min_or_max): 

209 if out is not None: 

210 raise ValueError("Sparse arrays do not support an 'out' parameter.") 

211 

212 validateaxis(axis) 

213 if self.ndim == 1: 

214 if axis not in (None, 0, -1): 

215 raise ValueError("axis out of range") 

216 axis = None # avoid calling special axis case. no impact on 1d 

217 

218 if axis is None: 

219 if 0 in self.shape: 

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

221 

222 zero = self.dtype.type(0) 

223 if self.nnz == 0: 

224 return zero 

225 m = min_or_max.reduce(self._deduped_data().ravel()) 

226 if self.nnz != np.prod(self.shape): 

227 m = min_or_max(zero, m) 

228 return m 

229 

230 if axis < 0: 

231 axis += 2 

232 

233 if (axis == 0) or (axis == 1): 

234 return self._min_or_max_axis(axis, min_or_max) 

235 else: 

236 raise ValueError("axis out of range") 

237 

238 def _arg_min_or_max_axis(self, axis, argmin_or_argmax, compare): 

239 if self.shape[axis] == 0: 

240 raise ValueError("Cannot apply the operation along a zero-sized dimension.") 

241 

242 if axis < 0: 

243 axis += 2 

244 

245 zero = self.dtype.type(0) 

246 

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

248 mat.sum_duplicates() 

249 

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

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

252 

253 nz_lines, = np.nonzero(np.diff(mat.indptr)) 

254 for i in nz_lines: 

255 p, q = mat.indptr[i:i + 2] 

256 data = mat.data[p:q] 

257 indices = mat.indices[p:q] 

258 extreme_index = argmin_or_argmax(data) 

259 extreme_value = data[extreme_index] 

260 if compare(extreme_value, zero) or q - p == line_size: 

261 ret[i] = indices[extreme_index] 

262 else: 

263 zero_ind = _find_missing_index(indices, line_size) 

264 if extreme_value == zero: 

265 ret[i] = min(extreme_index, zero_ind) 

266 else: 

267 ret[i] = zero_ind 

268 

269 if axis == 1: 

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

271 

272 return self._ascontainer(ret) 

273 

274 def _arg_min_or_max(self, axis, out, argmin_or_argmax, compare): 

275 if out is not None: 

276 raise ValueError("Sparse types do not support an 'out' parameter.") 

277 

278 validateaxis(axis) 

279 

280 if self.ndim == 1: 

281 if axis not in (None, 0, -1): 

282 raise ValueError("axis out of range") 

283 axis = None # avoid calling special axis case. no impact on 1d 

284 

285 if axis is not None: 

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

287 

288 if 0 in self.shape: 

289 raise ValueError("Cannot apply the operation to an empty matrix.") 

290 

291 if self.nnz == 0: 

292 return 0 

293 

294 zero = self.dtype.type(0) 

295 mat = self.tocoo() 

296 # Convert to canonical form: no duplicates, sorted indices. 

297 mat.sum_duplicates() 

298 extreme_index = argmin_or_argmax(mat.data) 

299 extreme_value = mat.data[extreme_index] 

300 num_col = mat.shape[-1] 

301 

302 # If the min value is less than zero, or max is greater than zero, 

303 # then we do not need to worry about implicit zeros. 

304 if compare(extreme_value, zero): 

305 # cast to Python int to avoid overflow and RuntimeError 

306 return int(mat.row[extreme_index]) * num_col + int(mat.col[extreme_index]) 

307 

308 # Cheap test for the rare case where we have no implicit zeros. 

309 size = np.prod(self.shape) 

310 if size == mat.nnz: 

311 return int(mat.row[extreme_index]) * num_col + int(mat.col[extreme_index]) 

312 

313 # At this stage, any implicit zero could be the min or max value. 

314 # After sum_duplicates(), the `row` and `col` arrays are guaranteed to 

315 # be sorted in C-order, which means the linearized indices are sorted. 

316 linear_indices = mat.row * num_col + mat.col 

317 first_implicit_zero_index = _find_missing_index(linear_indices, size) 

318 if extreme_value == zero: 

319 return min(first_implicit_zero_index, extreme_index) 

320 return first_implicit_zero_index 

321 

322 def max(self, axis=None, out=None): 

323 """ 

324 Return the maximum of the array/matrix or maximum along an axis. 

325 This takes all elements into account, not just the non-zero ones. 

326 

327 Parameters 

328 ---------- 

329 axis : {-2, -1, 0, 1, None} optional 

330 Axis along which the sum is computed. The default is to 

331 compute the maximum over all elements, returning 

332 a scalar (i.e., `axis` = `None`). 

333 

334 out : None, optional 

335 This argument is in the signature *solely* for NumPy 

336 compatibility reasons. Do not pass in anything except 

337 for the default value, as this argument is not used. 

338 

339 Returns 

340 ------- 

341 amax : coo_matrix or scalar 

342 Maximum of `a`. If `axis` is None, the result is a scalar value. 

343 If `axis` is given, the result is a sparse.coo_matrix of dimension 

344 ``a.ndim - 1``. 

345 

346 See Also 

347 -------- 

348 min : The minimum value of a sparse array/matrix along a given axis. 

349 numpy.matrix.max : NumPy's implementation of 'max' for matrices 

350 

351 """ 

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

353 

354 def min(self, axis=None, out=None): 

355 """ 

356 Return the minimum of the array/matrix or maximum along an axis. 

357 This takes all elements into account, not just the non-zero ones. 

358 

359 Parameters 

360 ---------- 

361 axis : {-2, -1, 0, 1, None} optional 

362 Axis along which the sum is computed. The default is to 

363 compute the minimum over all elements, returning 

364 a scalar (i.e., `axis` = `None`). 

365 

366 out : None, optional 

367 This argument is in the signature *solely* for NumPy 

368 compatibility reasons. Do not pass in anything except for 

369 the default value, as this argument is not used. 

370 

371 Returns 

372 ------- 

373 amin : coo_matrix or scalar 

374 Minimum of `a`. If `axis` is None, the result is a scalar value. 

375 If `axis` is given, the result is a sparse.coo_matrix of dimension 

376 ``a.ndim - 1``. 

377 

378 See Also 

379 -------- 

380 max : The maximum value of a sparse array/matrix along a given axis. 

381 numpy.matrix.min : NumPy's implementation of 'min' for matrices 

382 

383 """ 

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

385 

386 def nanmax(self, axis=None, out=None): 

387 """ 

388 Return the maximum of the array/matrix or maximum along an axis, ignoring any 

389 NaNs. This takes all elements into account, not just the non-zero 

390 ones. 

391 

392 .. versionadded:: 1.11.0 

393 

394 Parameters 

395 ---------- 

396 axis : {-2, -1, 0, 1, None} optional 

397 Axis along which the maximum is computed. The default is to 

398 compute the maximum over all elements, returning 

399 a scalar (i.e., `axis` = `None`). 

400 

401 out : None, optional 

402 This argument is in the signature *solely* for NumPy 

403 compatibility reasons. Do not pass in anything except 

404 for the default value, as this argument is not used. 

405 

406 Returns 

407 ------- 

408 amax : coo_matrix or scalar 

409 Maximum of `a`. If `axis` is None, the result is a scalar value. 

410 If `axis` is given, the result is a sparse.coo_matrix of dimension 

411 ``a.ndim - 1``. 

412 

413 See Also 

414 -------- 

415 nanmin : The minimum value of a sparse array/matrix along a given axis, 

416 ignoring NaNs. 

417 max : The maximum value of a sparse array/matrix along a given axis, 

418 propagating NaNs. 

419 numpy.nanmax : NumPy's implementation of 'nanmax'. 

420 

421 """ 

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

423 

424 def nanmin(self, axis=None, out=None): 

425 """ 

426 Return the minimum of the array/matrix or minimum along an axis, ignoring any 

427 NaNs. This takes all elements into account, not just the non-zero 

428 ones. 

429 

430 .. versionadded:: 1.11.0 

431 

432 Parameters 

433 ---------- 

434 axis : {-2, -1, 0, 1, None} optional 

435 Axis along which the minimum is computed. The default is to 

436 compute the minimum over all elements, returning 

437 a scalar (i.e., `axis` = `None`). 

438 

439 out : None, optional 

440 This argument is in the signature *solely* for NumPy 

441 compatibility reasons. Do not pass in anything except for 

442 the default value, as this argument is not used. 

443 

444 Returns 

445 ------- 

446 amin : coo_matrix or scalar 

447 Minimum of `a`. If `axis` is None, the result is a scalar value. 

448 If `axis` is given, the result is a sparse.coo_matrix of dimension 

449 ``a.ndim - 1``. 

450 

451 See Also 

452 -------- 

453 nanmax : The maximum value of a sparse array/matrix along a given axis, 

454 ignoring NaNs. 

455 min : The minimum value of a sparse array/matrix along a given axis, 

456 propagating NaNs. 

457 numpy.nanmin : NumPy's implementation of 'nanmin'. 

458 

459 """ 

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

461 

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

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

464 

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

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

467 

468 Parameters 

469 ---------- 

470 axis : {-2, -1, 0, 1, None}, optional 

471 Axis along which the argmax is computed. If None (default), index 

472 of the maximum element in the flatten data is returned. 

473 out : None, optional 

474 This argument is in the signature *solely* for NumPy 

475 compatibility reasons. Do not pass in anything except for 

476 the default value, as this argument is not used. 

477 

478 Returns 

479 ------- 

480 ind : numpy.matrix or int 

481 Indices of maximum elements. If matrix, its size along `axis` is 1. 

482 """ 

483 return self._arg_min_or_max(axis, out, np.argmax, np.greater) 

484 

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

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

487 

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

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

490 

491 Parameters 

492 ---------- 

493 axis : {-2, -1, 0, 1, None}, optional 

494 Axis along which the argmin is computed. If None (default), index 

495 of the minimum element in the flatten data is returned. 

496 out : None, optional 

497 This argument is in the signature *solely* for NumPy 

498 compatibility reasons. Do not pass in anything except for 

499 the default value, as this argument is not used. 

500 

501 Returns 

502 ------- 

503 ind : numpy.matrix or int 

504 Indices of minimum elements. If matrix, its size along `axis` is 1. 

505 """ 

506 return self._arg_min_or_max(axis, out, np.argmin, np.less)