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

768 statements  

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

1"""Base class for sparse matrix formats using compressed storage.""" 

2__all__ = [] 

3 

4from warnings import warn 

5import operator 

6 

7import numpy as np 

8from scipy._lib._util import _prune_array, copy_if_needed 

9 

10from ._base import _spbase, issparse, SparseEfficiencyWarning 

11from ._data import _data_matrix, _minmax_mixin 

12from . import _sparsetools 

13from ._sparsetools import (get_csr_submatrix, csr_sample_offsets, csr_todense, 

14 csr_sample_values, csr_row_index, csr_row_slice, 

15 csr_column_index1, csr_column_index2) 

16from ._index import IndexMixin 

17from ._sputils import (upcast, upcast_char, to_native, isdense, isshape, 

18 getdtype, isscalarlike, isintlike, downcast_intp_index, 

19 get_sum_dtype, check_shape, is_pydata_spmatrix) 

20 

21 

22class _cs_matrix(_data_matrix, _minmax_mixin, IndexMixin): 

23 """ 

24 base array/matrix class for compressed row- and column-oriented arrays/matrices 

25 """ 

26 

27 def __init__(self, arg1, shape=None, dtype=None, copy=False): 

28 _data_matrix.__init__(self) 

29 

30 if issparse(arg1): 

31 if arg1.format == self.format and copy: 

32 arg1 = arg1.copy() 

33 else: 

34 arg1 = arg1.asformat(self.format) 

35 self.indptr, self.indices, self.data, self._shape = ( 

36 arg1.indptr, arg1.indices, arg1.data, arg1._shape 

37 ) 

38 

39 elif isinstance(arg1, tuple): 

40 if isshape(arg1): 

41 # It's a tuple of matrix dimensions (M, N) 

42 # create empty matrix 

43 self._shape = check_shape(arg1) 

44 M, N = self.shape 

45 # Select index dtype large enough to pass array and 

46 # scalar parameters to sparsetools 

47 idx_dtype = self._get_index_dtype(maxval=max(M, N)) 

48 self.data = np.zeros(0, getdtype(dtype, default=float)) 

49 self.indices = np.zeros(0, idx_dtype) 

50 self.indptr = np.zeros(self._swap((M, N))[0] + 1, 

51 dtype=idx_dtype) 

52 else: 

53 if len(arg1) == 2: 

54 # (data, ij) format 

55 coo = self._coo_container(arg1, shape=shape, dtype=dtype) 

56 arrays = coo._coo_to_compressed(self._swap) 

57 self.indptr, self.indices, self.data, self._shape = arrays 

58 elif len(arg1) == 3: 

59 # (data, indices, indptr) format 

60 (data, indices, indptr) = arg1 

61 

62 # Select index dtype large enough to pass array and 

63 # scalar parameters to sparsetools 

64 maxval = None 

65 if shape is not None: 

66 maxval = max(shape) 

67 idx_dtype = self._get_index_dtype((indices, indptr), 

68 maxval=maxval, 

69 check_contents=True) 

70 

71 if not copy: 

72 copy = copy_if_needed 

73 self.indices = np.array(indices, copy=copy, dtype=idx_dtype) 

74 self.indptr = np.array(indptr, copy=copy, dtype=idx_dtype) 

75 self.data = np.array(data, copy=copy, dtype=dtype) 

76 else: 

77 raise ValueError(f"unrecognized {self.format}_matrix " 

78 "constructor usage") 

79 

80 else: 

81 # must be dense 

82 try: 

83 arg1 = np.asarray(arg1) 

84 except Exception as e: 

85 msg = f"unrecognized {self.format}_matrix constructor usage" 

86 raise ValueError(msg) from e 

87 coo = self._coo_container(arg1, dtype=dtype) 

88 arrays = coo._coo_to_compressed(self._swap) 

89 self.indptr, self.indices, self.data, self._shape = arrays 

90 

91 # Read matrix dimensions given, if any 

92 if shape is not None: 

93 self._shape = check_shape(shape) 

94 else: 

95 if self.shape is None: 

96 # shape not already set, try to infer dimensions 

97 try: 

98 major_dim = len(self.indptr) - 1 

99 minor_dim = self.indices.max() + 1 

100 except Exception as e: 

101 raise ValueError('unable to infer matrix dimensions') from e 

102 else: 

103 self._shape = check_shape(self._swap((major_dim, minor_dim))) 

104 

105 if dtype is not None: 

106 self.data = self.data.astype(dtype, copy=False) 

107 

108 self.check_format(full_check=False) 

109 

110 def _getnnz(self, axis=None): 

111 if axis is None: 

112 return int(self.indptr[-1]) 

113 else: 

114 if axis < 0: 

115 axis += 2 

116 axis, _ = self._swap((axis, 1 - axis)) 

117 _, N = self._swap(self.shape) 

118 if axis == 0: 

119 return np.bincount(downcast_intp_index(self.indices), 

120 minlength=N) 

121 elif axis == 1: 

122 return np.diff(self.indptr) 

123 raise ValueError('axis out of bounds') 

124 

125 _getnnz.__doc__ = _spbase._getnnz.__doc__ 

126 

127 def check_format(self, full_check=True): 

128 """Check whether the array/matrix respects the CSR or CSC format. 

129 

130 Parameters 

131 ---------- 

132 full_check : bool, optional 

133 If `True`, run rigorous check, scanning arrays for valid values. 

134 Note that activating those check might copy arrays for casting, 

135 modifying indices and index pointers' inplace. 

136 If `False`, run basic checks on attributes. O(1) operations. 

137 Default is `True`. 

138 """ 

139 # use _swap to determine proper bounds 

140 major_name, minor_name = self._swap(('row', 'column')) 

141 major_dim, minor_dim = self._swap(self.shape) 

142 

143 # index arrays should have integer data types 

144 if self.indptr.dtype.kind != 'i': 

145 warn(f"indptr array has non-integer dtype ({self.indptr.dtype.name})", 

146 stacklevel=3) 

147 if self.indices.dtype.kind != 'i': 

148 warn(f"indices array has non-integer dtype ({self.indices.dtype.name})", 

149 stacklevel=3) 

150 

151 # check array shapes 

152 for x in [self.data.ndim, self.indices.ndim, self.indptr.ndim]: 

153 if x != 1: 

154 raise ValueError('data, indices, and indptr should be 1-D') 

155 

156 # check index pointer 

157 if (len(self.indptr) != major_dim + 1): 

158 raise ValueError("index pointer size ({}) should be ({})" 

159 "".format(len(self.indptr), major_dim + 1)) 

160 if (self.indptr[0] != 0): 

161 raise ValueError("index pointer should start with 0") 

162 

163 # check index and data arrays 

164 if (len(self.indices) != len(self.data)): 

165 raise ValueError("indices and data should have the same size") 

166 if (self.indptr[-1] > len(self.indices)): 

167 raise ValueError("Last value of index pointer should be less than " 

168 "the size of index and data arrays") 

169 

170 self.prune() 

171 

172 if full_check: 

173 # check format validity (more expensive) 

174 if self.nnz > 0: 

175 if self.indices.max() >= minor_dim: 

176 raise ValueError(f"{minor_name} index values must be < {minor_dim}") 

177 if self.indices.min() < 0: 

178 raise ValueError(f"{minor_name} index values must be >= 0") 

179 if np.diff(self.indptr).min() < 0: 

180 raise ValueError("index pointer values must form a " 

181 "non-decreasing sequence") 

182 

183 idx_dtype = self._get_index_dtype((self.indptr, self.indices)) 

184 self.indptr = np.asarray(self.indptr, dtype=idx_dtype) 

185 self.indices = np.asarray(self.indices, dtype=idx_dtype) 

186 self.data = to_native(self.data) 

187 

188 # if not self.has_sorted_indices(): 

189 # warn('Indices were not in sorted order. Sorting indices.') 

190 # self.sort_indices() 

191 # assert(self.has_sorted_indices()) 

192 # TODO check for duplicates? 

193 

194 ####################### 

195 # Boolean comparisons # 

196 ####################### 

197 

198 def _scalar_binopt(self, other, op): 

199 """Scalar version of self._binopt, for cases in which no new nonzeros 

200 are added. Produces a new sparse array in canonical form. 

201 """ 

202 self.sum_duplicates() 

203 res = self._with_data(op(self.data, other), copy=True) 

204 res.eliminate_zeros() 

205 return res 

206 

207 def __eq__(self, other): 

208 # Scalar other. 

209 if isscalarlike(other): 

210 if np.isnan(other): 

211 return self.__class__(self.shape, dtype=np.bool_) 

212 

213 if other == 0: 

214 warn("Comparing a sparse matrix with 0 using == is inefficient" 

215 ", try using != instead.", SparseEfficiencyWarning, 

216 stacklevel=3) 

217 all_true = self.__class__(np.ones(self.shape, dtype=np.bool_)) 

218 inv = self._scalar_binopt(other, operator.ne) 

219 return all_true - inv 

220 else: 

221 return self._scalar_binopt(other, operator.eq) 

222 # Dense other. 

223 elif isdense(other): 

224 return self.todense() == other 

225 # Pydata sparse other. 

226 elif is_pydata_spmatrix(other): 

227 return NotImplemented 

228 # Sparse other. 

229 elif issparse(other): 

230 warn("Comparing sparse matrices using == is inefficient, try using" 

231 " != instead.", SparseEfficiencyWarning, stacklevel=3) 

232 # TODO sparse broadcasting 

233 if self.shape != other.shape: 

234 return False 

235 elif self.format != other.format: 

236 other = other.asformat(self.format) 

237 res = self._binopt(other, '_ne_') 

238 all_true = self.__class__(np.ones(self.shape, dtype=np.bool_)) 

239 return all_true - res 

240 else: 

241 return NotImplemented 

242 

243 def __ne__(self, other): 

244 # Scalar other. 

245 if isscalarlike(other): 

246 if np.isnan(other): 

247 warn("Comparing a sparse matrix with nan using != is" 

248 " inefficient", SparseEfficiencyWarning, stacklevel=3) 

249 all_true = self.__class__(np.ones(self.shape, dtype=np.bool_)) 

250 return all_true 

251 elif other != 0: 

252 warn("Comparing a sparse matrix with a nonzero scalar using !=" 

253 " is inefficient, try using == instead.", 

254 SparseEfficiencyWarning, stacklevel=3) 

255 all_true = self.__class__(np.ones(self.shape), dtype=np.bool_) 

256 inv = self._scalar_binopt(other, operator.eq) 

257 return all_true - inv 

258 else: 

259 return self._scalar_binopt(other, operator.ne) 

260 # Dense other. 

261 elif isdense(other): 

262 return self.todense() != other 

263 # Pydata sparse other. 

264 elif is_pydata_spmatrix(other): 

265 return NotImplemented 

266 # Sparse other. 

267 elif issparse(other): 

268 # TODO sparse broadcasting 

269 if self.shape != other.shape: 

270 return True 

271 elif self.format != other.format: 

272 other = other.asformat(self.format) 

273 return self._binopt(other, '_ne_') 

274 else: 

275 return NotImplemented 

276 

277 def _inequality(self, other, op, op_name, bad_scalar_msg): 

278 # Scalar other. 

279 if isscalarlike(other): 

280 if 0 == other and op_name in ('_le_', '_ge_'): 

281 raise NotImplementedError(" >= and <= don't work with 0.") 

282 elif op(0, other): 

283 warn(bad_scalar_msg, SparseEfficiencyWarning, stacklevel=3) 

284 other_arr = np.empty(self.shape, dtype=np.result_type(other)) 

285 other_arr.fill(other) 

286 other_arr = self.__class__(other_arr) 

287 return self._binopt(other_arr, op_name) 

288 else: 

289 return self._scalar_binopt(other, op) 

290 # Dense other. 

291 elif isdense(other): 

292 return op(self.todense(), other) 

293 # Sparse other. 

294 elif issparse(other): 

295 # TODO sparse broadcasting 

296 if self.shape != other.shape: 

297 raise ValueError("inconsistent shapes") 

298 elif self.format != other.format: 

299 other = other.asformat(self.format) 

300 if op_name not in ('_ge_', '_le_'): 

301 return self._binopt(other, op_name) 

302 

303 warn("Comparing sparse matrices using >= and <= is inefficient, " 

304 "using <, >, or !=, instead.", 

305 SparseEfficiencyWarning, stacklevel=3) 

306 all_true = self.__class__(np.ones(self.shape, dtype=np.bool_)) 

307 res = self._binopt(other, '_gt_' if op_name == '_le_' else '_lt_') 

308 return all_true - res 

309 else: 

310 return NotImplemented 

311 

312 def __lt__(self, other): 

313 return self._inequality(other, operator.lt, '_lt_', 

314 "Comparing a sparse matrix with a scalar " 

315 "greater than zero using < is inefficient, " 

316 "try using >= instead.") 

317 

318 def __gt__(self, other): 

319 return self._inequality(other, operator.gt, '_gt_', 

320 "Comparing a sparse matrix with a scalar " 

321 "less than zero using > is inefficient, " 

322 "try using <= instead.") 

323 

324 def __le__(self, other): 

325 return self._inequality(other, operator.le, '_le_', 

326 "Comparing a sparse matrix with a scalar " 

327 "greater than zero using <= is inefficient, " 

328 "try using > instead.") 

329 

330 def __ge__(self, other): 

331 return self._inequality(other, operator.ge, '_ge_', 

332 "Comparing a sparse matrix with a scalar " 

333 "less than zero using >= is inefficient, " 

334 "try using < instead.") 

335 

336 ################################# 

337 # Arithmetic operator overrides # 

338 ################################# 

339 

340 def _add_dense(self, other): 

341 if other.shape != self.shape: 

342 raise ValueError(f'Incompatible shapes ({self.shape} and {other.shape})') 

343 dtype = upcast_char(self.dtype.char, other.dtype.char) 

344 order = self._swap('CF')[0] 

345 result = np.array(other, dtype=dtype, order=order, copy=True) 

346 M, N = self._swap(self.shape) 

347 y = result if result.flags.c_contiguous else result.T 

348 csr_todense(M, N, self.indptr, self.indices, self.data, y) 

349 return self._container(result, copy=False) 

350 

351 def _add_sparse(self, other): 

352 return self._binopt(other, '_plus_') 

353 

354 def _sub_sparse(self, other): 

355 return self._binopt(other, '_minus_') 

356 

357 def multiply(self, other): 

358 """Point-wise multiplication by another array/matrix, vector, or 

359 scalar. 

360 """ 

361 # Scalar multiplication. 

362 if isscalarlike(other): 

363 return self._mul_scalar(other) 

364 # Sparse matrix or vector. 

365 if issparse(other): 

366 if self.shape == other.shape: 

367 other = self.__class__(other) 

368 return self._binopt(other, '_elmul_') 

369 if other.ndim == 1: 

370 raise TypeError("broadcast from a 1d array not yet supported") 

371 # Single element. 

372 elif other.shape == (1, 1): 

373 return self._mul_scalar(other.toarray()[0, 0]) 

374 elif self.shape == (1, 1): 

375 return other._mul_scalar(self.toarray()[0, 0]) 

376 # A row times a column. 

377 elif self.shape[1] == 1 and other.shape[0] == 1: 

378 return self._matmul_sparse(other.tocsc()) 

379 elif self.shape[0] == 1 and other.shape[1] == 1: 

380 return other._matmul_sparse(self.tocsc()) 

381 # Row vector times matrix. other is a row. 

382 elif other.shape[0] == 1 and self.shape[1] == other.shape[1]: 

383 other = self._dia_container( 

384 (other.toarray().ravel(), [0]), 

385 shape=(other.shape[1], other.shape[1]) 

386 ) 

387 return self._matmul_sparse(other) 

388 # self is a row. 

389 elif self.shape[0] == 1 and self.shape[1] == other.shape[1]: 

390 copy = self._dia_container( 

391 (self.toarray().ravel(), [0]), 

392 shape=(self.shape[1], self.shape[1]) 

393 ) 

394 return other._matmul_sparse(copy) 

395 # Column vector times matrix. other is a column. 

396 elif other.shape[1] == 1 and self.shape[0] == other.shape[0]: 

397 other = self._dia_container( 

398 (other.toarray().ravel(), [0]), 

399 shape=(other.shape[0], other.shape[0]) 

400 ) 

401 return other._matmul_sparse(self) 

402 # self is a column. 

403 elif self.shape[1] == 1 and self.shape[0] == other.shape[0]: 

404 copy = self._dia_container( 

405 (self.toarray().ravel(), [0]), 

406 shape=(self.shape[0], self.shape[0]) 

407 ) 

408 return copy._matmul_sparse(other) 

409 else: 

410 raise ValueError("inconsistent shapes") 

411 

412 # Assume other is a dense matrix/array, which produces a single-item 

413 # object array if other isn't convertible to ndarray. 

414 other = np.atleast_2d(other) 

415 

416 if other.ndim != 2: 

417 return np.multiply(self.toarray(), other) 

418 # Single element / wrapped object. 

419 if other.size == 1: 

420 if other.dtype == np.object_: 

421 # 'other' not convertible to ndarray. 

422 return NotImplemented 

423 return self._mul_scalar(other.flat[0]) 

424 # Fast case for trivial sparse matrix. 

425 elif self.shape == (1, 1): 

426 return np.multiply(self.toarray()[0, 0], other) 

427 

428 ret = self.tocoo() 

429 # Matching shapes. 

430 if self.shape == other.shape: 

431 data = np.multiply(ret.data, other[ret.row, ret.col]) 

432 # Sparse row vector times... 

433 elif self.shape[0] == 1: 

434 if other.shape[1] == 1: # Dense column vector. 

435 data = np.multiply(ret.data, other) 

436 elif other.shape[1] == self.shape[1]: # Dense matrix. 

437 data = np.multiply(ret.data, other[:, ret.col]) 

438 else: 

439 raise ValueError("inconsistent shapes") 

440 row = np.repeat(np.arange(other.shape[0]), len(ret.row)) 

441 col = np.tile(ret.col, other.shape[0]) 

442 return self._coo_container( 

443 (data.view(np.ndarray).ravel(), (row, col)), 

444 shape=(other.shape[0], self.shape[1]), 

445 copy=False 

446 ) 

447 # Sparse column vector times... 

448 elif self.shape[1] == 1: 

449 if other.shape[0] == 1: # Dense row vector. 

450 data = np.multiply(ret.data[:, None], other) 

451 elif other.shape[0] == self.shape[0]: # Dense matrix. 

452 data = np.multiply(ret.data[:, None], other[ret.row]) 

453 else: 

454 raise ValueError("inconsistent shapes") 

455 row = np.repeat(ret.row, other.shape[1]) 

456 col = np.tile(np.arange(other.shape[1]), len(ret.col)) 

457 return self._coo_container( 

458 (data.view(np.ndarray).ravel(), (row, col)), 

459 shape=(self.shape[0], other.shape[1]), 

460 copy=False 

461 ) 

462 # Sparse matrix times dense row vector. 

463 elif other.shape[0] == 1 and self.shape[1] == other.shape[1]: 

464 data = np.multiply(ret.data, other[:, ret.col].ravel()) 

465 # Sparse matrix times dense column vector. 

466 elif other.shape[1] == 1 and self.shape[0] == other.shape[0]: 

467 data = np.multiply(ret.data, other[ret.row].ravel()) 

468 else: 

469 raise ValueError("inconsistent shapes") 

470 ret.data = data.view(np.ndarray).ravel() 

471 return ret 

472 

473 ########################### 

474 # Multiplication handlers # 

475 ########################### 

476 

477 def _matmul_vector(self, other): 

478 M, N = self.shape 

479 

480 # output array 

481 result = np.zeros(M, dtype=upcast_char(self.dtype.char, 

482 other.dtype.char)) 

483 

484 # csr_matvec or csc_matvec 

485 fn = getattr(_sparsetools, self.format + '_matvec') 

486 fn(M, N, self.indptr, self.indices, self.data, other, result) 

487 

488 return result 

489 

490 def _matmul_multivector(self, other): 

491 M, N = self.shape 

492 n_vecs = other.shape[1] # number of column vectors 

493 

494 result = np.zeros((M, n_vecs), 

495 dtype=upcast_char(self.dtype.char, other.dtype.char)) 

496 

497 # csr_matvecs or csc_matvecs 

498 fn = getattr(_sparsetools, self.format + '_matvecs') 

499 fn(M, N, n_vecs, self.indptr, self.indices, self.data, 

500 other.ravel(), result.ravel()) 

501 

502 return result 

503 

504 def _matmul_sparse(self, other): 

505 M, K1 = self.shape 

506 K2, N = other.shape 

507 

508 major_axis = self._swap((M, N))[0] 

509 other = self.__class__(other) # convert to this format 

510 

511 idx_dtype = self._get_index_dtype((self.indptr, self.indices, 

512 other.indptr, other.indices)) 

513 

514 fn = getattr(_sparsetools, self.format + '_matmat_maxnnz') 

515 nnz = fn(M, N, 

516 np.asarray(self.indptr, dtype=idx_dtype), 

517 np.asarray(self.indices, dtype=idx_dtype), 

518 np.asarray(other.indptr, dtype=idx_dtype), 

519 np.asarray(other.indices, dtype=idx_dtype)) 

520 

521 idx_dtype = self._get_index_dtype((self.indptr, self.indices, 

522 other.indptr, other.indices), 

523 maxval=nnz) 

524 

525 indptr = np.empty(major_axis + 1, dtype=idx_dtype) 

526 indices = np.empty(nnz, dtype=idx_dtype) 

527 data = np.empty(nnz, dtype=upcast(self.dtype, other.dtype)) 

528 

529 fn = getattr(_sparsetools, self.format + '_matmat') 

530 fn(M, N, np.asarray(self.indptr, dtype=idx_dtype), 

531 np.asarray(self.indices, dtype=idx_dtype), 

532 self.data, 

533 np.asarray(other.indptr, dtype=idx_dtype), 

534 np.asarray(other.indices, dtype=idx_dtype), 

535 other.data, 

536 indptr, indices, data) 

537 

538 return self.__class__((data, indices, indptr), shape=(M, N)) 

539 

540 def diagonal(self, k=0): 

541 rows, cols = self.shape 

542 if k <= -rows or k >= cols: 

543 return np.empty(0, dtype=self.data.dtype) 

544 fn = getattr(_sparsetools, self.format + "_diagonal") 

545 y = np.empty(min(rows + min(k, 0), cols - max(k, 0)), 

546 dtype=upcast(self.dtype)) 

547 fn(k, self.shape[0], self.shape[1], self.indptr, self.indices, 

548 self.data, y) 

549 return y 

550 

551 diagonal.__doc__ = _spbase.diagonal.__doc__ 

552 

553 ##################### 

554 # Other binary ops # 

555 ##################### 

556 

557 def _maximum_minimum(self, other, npop, op_name, dense_check): 

558 if isscalarlike(other): 

559 if dense_check(other): 

560 warn("Taking maximum (minimum) with > 0 (< 0) number results" 

561 " to a dense matrix.", SparseEfficiencyWarning, 

562 stacklevel=3) 

563 other_arr = np.empty(self.shape, dtype=np.asarray(other).dtype) 

564 other_arr.fill(other) 

565 other_arr = self.__class__(other_arr) 

566 return self._binopt(other_arr, op_name) 

567 else: 

568 self.sum_duplicates() 

569 new_data = npop(self.data, np.asarray(other)) 

570 mat = self.__class__((new_data, self.indices, self.indptr), 

571 dtype=new_data.dtype, shape=self.shape) 

572 return mat 

573 elif isdense(other): 

574 return npop(self.todense(), other) 

575 elif issparse(other): 

576 return self._binopt(other, op_name) 

577 else: 

578 raise ValueError("Operands not compatible.") 

579 

580 def maximum(self, other): 

581 return self._maximum_minimum(other, np.maximum, 

582 '_maximum_', lambda x: np.asarray(x) > 0) 

583 

584 maximum.__doc__ = _spbase.maximum.__doc__ 

585 

586 def minimum(self, other): 

587 return self._maximum_minimum(other, np.minimum, 

588 '_minimum_', lambda x: np.asarray(x) < 0) 

589 

590 minimum.__doc__ = _spbase.minimum.__doc__ 

591 

592 ##################### 

593 # Reduce operations # 

594 ##################### 

595 

596 def sum(self, axis=None, dtype=None, out=None): 

597 """Sum the array/matrix over the given axis. If the axis is None, sum 

598 over both rows and columns, returning a scalar. 

599 """ 

600 # The _spbase base class already does axis=0 and axis=1 efficiently 

601 # so we only do the case axis=None here 

602 if (not hasattr(self, 'blocksize') and 

603 axis in self._swap(((1, -1), (0, 2)))[0]): 

604 # faster than multiplication for large minor axis in CSC/CSR 

605 res_dtype = get_sum_dtype(self.dtype) 

606 ret = np.zeros(len(self.indptr) - 1, dtype=res_dtype) 

607 

608 major_index, value = self._minor_reduce(np.add) 

609 ret[major_index] = value 

610 ret = self._ascontainer(ret) 

611 if axis % 2 == 1: 

612 ret = ret.T 

613 

614 if out is not None and out.shape != ret.shape: 

615 raise ValueError('dimensions do not match') 

616 

617 return ret.sum(axis=(), dtype=dtype, out=out) 

618 # _spbase will handle the remaining situations when axis 

619 # is in {None, -1, 0, 1} 

620 else: 

621 return _spbase.sum(self, axis=axis, dtype=dtype, out=out) 

622 

623 sum.__doc__ = _spbase.sum.__doc__ 

624 

625 def _minor_reduce(self, ufunc, data=None): 

626 """Reduce nonzeros with a ufunc over the minor axis when non-empty 

627 

628 Can be applied to a function of self.data by supplying data parameter. 

629 

630 Warning: this does not call sum_duplicates() 

631 

632 Returns 

633 ------- 

634 major_index : array of ints 

635 Major indices where nonzero 

636 

637 value : array of self.dtype 

638 Reduce result for nonzeros in each major_index 

639 """ 

640 if data is None: 

641 data = self.data 

642 major_index = np.flatnonzero(np.diff(self.indptr)) 

643 value = ufunc.reduceat(data, 

644 downcast_intp_index(self.indptr[major_index])) 

645 return major_index, value 

646 

647 ####################### 

648 # Getting and Setting # 

649 ####################### 

650 

651 def _get_intXint(self, row, col): 

652 M, N = self._swap(self.shape) 

653 major, minor = self._swap((row, col)) 

654 indptr, indices, data = get_csr_submatrix( 

655 M, N, self.indptr, self.indices, self.data, 

656 major, major + 1, minor, minor + 1) 

657 return data.sum(dtype=self.dtype) 

658 

659 def _get_sliceXslice(self, row, col): 

660 major, minor = self._swap((row, col)) 

661 if major.step in (1, None) and minor.step in (1, None): 

662 return self._get_submatrix(major, minor, copy=True) 

663 return self._major_slice(major)._minor_slice(minor) 

664 

665 def _get_arrayXarray(self, row, col): 

666 # inner indexing 

667 idx_dtype = self.indices.dtype 

668 M, N = self._swap(self.shape) 

669 major, minor = self._swap((row, col)) 

670 major = np.asarray(major, dtype=idx_dtype) 

671 minor = np.asarray(minor, dtype=idx_dtype) 

672 

673 val = np.empty(major.size, dtype=self.dtype) 

674 csr_sample_values(M, N, self.indptr, self.indices, self.data, 

675 major.size, major.ravel(), minor.ravel(), val) 

676 if major.ndim == 1: 

677 return self._ascontainer(val) 

678 return self.__class__(val.reshape(major.shape)) 

679 

680 def _get_columnXarray(self, row, col): 

681 # outer indexing 

682 major, minor = self._swap((row, col)) 

683 return self._major_index_fancy(major)._minor_index_fancy(minor) 

684 

685 def _major_index_fancy(self, idx): 

686 """Index along the major axis where idx is an array of ints. 

687 """ 

688 idx_dtype = self._get_index_dtype((self.indptr, self.indices)) 

689 indices = np.asarray(idx, dtype=idx_dtype).ravel() 

690 

691 _, N = self._swap(self.shape) 

692 M = len(indices) 

693 new_shape = self._swap((M, N)) 

694 if M == 0: 

695 return self.__class__(new_shape, dtype=self.dtype) 

696 

697 row_nnz = (self.indptr[indices + 1] - self.indptr[indices]).astype(idx_dtype) 

698 

699 res_indptr = np.zeros(M+1, dtype=idx_dtype) 

700 np.cumsum(row_nnz, out=res_indptr[1:]) 

701 

702 nnz = res_indptr[-1] 

703 res_indices = np.empty(nnz, dtype=idx_dtype) 

704 res_data = np.empty(nnz, dtype=self.dtype) 

705 csr_row_index( 

706 M, 

707 indices, 

708 self.indptr.astype(idx_dtype, copy=False), 

709 self.indices.astype(idx_dtype, copy=False), 

710 self.data, 

711 res_indices, 

712 res_data 

713 ) 

714 

715 return self.__class__((res_data, res_indices, res_indptr), 

716 shape=new_shape, copy=False) 

717 

718 def _major_slice(self, idx, copy=False): 

719 """Index along the major axis where idx is a slice object. 

720 """ 

721 if idx == slice(None): 

722 return self.copy() if copy else self 

723 

724 M, N = self._swap(self.shape) 

725 start, stop, step = idx.indices(M) 

726 M = len(range(start, stop, step)) 

727 new_shape = self._swap((M, N)) 

728 if M == 0: 

729 return self.__class__(new_shape, dtype=self.dtype) 

730 

731 # Work out what slices are needed for `row_nnz` 

732 # start,stop can be -1, only if step is negative 

733 start0, stop0 = start, stop 

734 if stop == -1 and start >= 0: 

735 stop0 = None 

736 start1, stop1 = start + 1, stop + 1 

737 

738 row_nnz = self.indptr[start1:stop1:step] - \ 

739 self.indptr[start0:stop0:step] 

740 idx_dtype = self.indices.dtype 

741 res_indptr = np.zeros(M+1, dtype=idx_dtype) 

742 np.cumsum(row_nnz, out=res_indptr[1:]) 

743 

744 if step == 1: 

745 all_idx = slice(self.indptr[start], self.indptr[stop]) 

746 res_indices = np.array(self.indices[all_idx], copy=copy) 

747 res_data = np.array(self.data[all_idx], copy=copy) 

748 else: 

749 nnz = res_indptr[-1] 

750 res_indices = np.empty(nnz, dtype=idx_dtype) 

751 res_data = np.empty(nnz, dtype=self.dtype) 

752 csr_row_slice(start, stop, step, self.indptr, self.indices, 

753 self.data, res_indices, res_data) 

754 

755 return self.__class__((res_data, res_indices, res_indptr), 

756 shape=new_shape, copy=False) 

757 

758 def _minor_index_fancy(self, idx): 

759 """Index along the minor axis where idx is an array of ints. 

760 """ 

761 idx_dtype = self._get_index_dtype((self.indices, self.indptr)) 

762 indices = self.indices.astype(idx_dtype, copy=False) 

763 indptr = self.indptr.astype(idx_dtype, copy=False) 

764 

765 idx = np.asarray(idx, dtype=idx_dtype).ravel() 

766 

767 M, N = self._swap(self.shape) 

768 k = len(idx) 

769 new_shape = self._swap((M, k)) 

770 if k == 0: 

771 return self.__class__(new_shape, dtype=self.dtype) 

772 

773 # pass 1: count idx entries and compute new indptr 

774 col_offsets = np.zeros(N, dtype=idx_dtype) 

775 res_indptr = np.empty_like(self.indptr, dtype=idx_dtype) 

776 csr_column_index1( 

777 k, 

778 idx, 

779 M, 

780 N, 

781 indptr, 

782 indices, 

783 col_offsets, 

784 res_indptr, 

785 ) 

786 

787 # pass 2: copy indices/data for selected idxs 

788 col_order = np.argsort(idx).astype(idx_dtype, copy=False) 

789 nnz = res_indptr[-1] 

790 res_indices = np.empty(nnz, dtype=idx_dtype) 

791 res_data = np.empty(nnz, dtype=self.dtype) 

792 csr_column_index2(col_order, col_offsets, len(self.indices), 

793 indices, self.data, res_indices, res_data) 

794 return self.__class__((res_data, res_indices, res_indptr), 

795 shape=new_shape, copy=False) 

796 

797 def _minor_slice(self, idx, copy=False): 

798 """Index along the minor axis where idx is a slice object. 

799 """ 

800 if idx == slice(None): 

801 return self.copy() if copy else self 

802 

803 M, N = self._swap(self.shape) 

804 start, stop, step = idx.indices(N) 

805 N = len(range(start, stop, step)) 

806 if N == 0: 

807 return self.__class__(self._swap((M, N)), dtype=self.dtype) 

808 if step == 1: 

809 return self._get_submatrix(minor=idx, copy=copy) 

810 # TODO: don't fall back to fancy indexing here 

811 return self._minor_index_fancy(np.arange(start, stop, step)) 

812 

813 def _get_submatrix(self, major=None, minor=None, copy=False): 

814 """Return a submatrix of this matrix. 

815 

816 major, minor: None, int, or slice with step 1 

817 """ 

818 M, N = self._swap(self.shape) 

819 i0, i1 = _process_slice(major, M) 

820 j0, j1 = _process_slice(minor, N) 

821 

822 if i0 == 0 and j0 == 0 and i1 == M and j1 == N: 

823 return self.copy() if copy else self 

824 

825 indptr, indices, data = get_csr_submatrix( 

826 M, N, self.indptr, self.indices, self.data, i0, i1, j0, j1) 

827 

828 shape = self._swap((i1 - i0, j1 - j0)) 

829 return self.__class__((data, indices, indptr), shape=shape, 

830 dtype=self.dtype, copy=False) 

831 

832 def _set_intXint(self, row, col, x): 

833 i, j = self._swap((row, col)) 

834 self._set_many(i, j, x) 

835 

836 def _set_arrayXarray(self, row, col, x): 

837 i, j = self._swap((row, col)) 

838 self._set_many(i, j, x) 

839 

840 def _set_arrayXarray_sparse(self, row, col, x): 

841 # clear entries that will be overwritten 

842 self._zero_many(*self._swap((row, col))) 

843 

844 M, N = row.shape # matches col.shape 

845 broadcast_row = M != 1 and x.shape[0] == 1 

846 broadcast_col = N != 1 and x.shape[1] == 1 

847 r, c = x.row, x.col 

848 

849 x = np.asarray(x.data, dtype=self.dtype) 

850 if x.size == 0: 

851 return 

852 

853 if broadcast_row: 

854 r = np.repeat(np.arange(M), len(r)) 

855 c = np.tile(c, M) 

856 x = np.tile(x, M) 

857 if broadcast_col: 

858 r = np.repeat(r, N) 

859 c = np.tile(np.arange(N), len(c)) 

860 x = np.repeat(x, N) 

861 # only assign entries in the new sparsity structure 

862 i, j = self._swap((row[r, c], col[r, c])) 

863 self._set_many(i, j, x) 

864 

865 def _setdiag(self, values, k): 

866 if 0 in self.shape: 

867 return 

868 

869 M, N = self.shape 

870 broadcast = (values.ndim == 0) 

871 

872 if k < 0: 

873 if broadcast: 

874 max_index = min(M + k, N) 

875 else: 

876 max_index = min(M + k, N, len(values)) 

877 i = np.arange(-k, max_index - k, dtype=self.indices.dtype) 

878 j = np.arange(max_index, dtype=self.indices.dtype) 

879 

880 else: 

881 if broadcast: 

882 max_index = min(M, N - k) 

883 else: 

884 max_index = min(M, N - k, len(values)) 

885 i = np.arange(max_index, dtype=self.indices.dtype) 

886 j = np.arange(k, k + max_index, dtype=self.indices.dtype) 

887 

888 if not broadcast: 

889 values = values[:len(i)] 

890 

891 x = np.atleast_1d(np.asarray(values, dtype=self.dtype)).ravel() 

892 if x.squeeze().shape != i.squeeze().shape: 

893 x = np.broadcast_to(x, i.shape) 

894 if x.size == 0: 

895 return 

896 

897 M, N = self._swap((M, N)) 

898 i, j = self._swap((i, j)) 

899 n_samples = x.size 

900 offsets = np.empty(n_samples, dtype=self.indices.dtype) 

901 ret = csr_sample_offsets(M, N, self.indptr, self.indices, n_samples, 

902 i, j, offsets) 

903 if ret == 1: 

904 # rinse and repeat 

905 self.sum_duplicates() 

906 csr_sample_offsets(M, N, self.indptr, self.indices, n_samples, 

907 i, j, offsets) 

908 if -1 not in offsets: 

909 # only affects existing non-zero cells 

910 self.data[offsets] = x 

911 return 

912 

913 mask = (offsets <= -1) 

914 # Boundary between csc and convert to coo 

915 # The value 0.001 is justified in gh-19962#issuecomment-1920499678 

916 if mask.sum() < self.nnz * 0.001: 

917 # create new entries 

918 i = i[mask] 

919 j = j[mask] 

920 self._insert_many(i, j, x[mask]) 

921 # replace existing entries 

922 mask = ~mask 

923 self.data[offsets[mask]] = x[mask] 

924 else: 

925 # convert to coo for _set_diag 

926 coo = self.tocoo() 

927 coo._setdiag(values, k) 

928 arrays = coo._coo_to_compressed(self._swap) 

929 self.indptr, self.indices, self.data, _ = arrays 

930 

931 def _prepare_indices(self, i, j): 

932 M, N = self._swap(self.shape) 

933 

934 def check_bounds(indices, bound): 

935 idx = indices.max() 

936 if idx >= bound: 

937 raise IndexError('index (%d) out of range (>= %d)' % 

938 (idx, bound)) 

939 idx = indices.min() 

940 if idx < -bound: 

941 raise IndexError('index (%d) out of range (< -%d)' % 

942 (idx, bound)) 

943 

944 i = np.atleast_1d(np.asarray(i, dtype=self.indices.dtype)).ravel() 

945 j = np.atleast_1d(np.asarray(j, dtype=self.indices.dtype)).ravel() 

946 check_bounds(i, M) 

947 check_bounds(j, N) 

948 return i, j, M, N 

949 

950 def _set_many(self, i, j, x): 

951 """Sets value at each (i, j) to x 

952 

953 Here (i,j) index major and minor respectively, and must not contain 

954 duplicate entries. 

955 """ 

956 i, j, M, N = self._prepare_indices(i, j) 

957 x = np.atleast_1d(np.asarray(x, dtype=self.dtype)).ravel() 

958 

959 n_samples = x.size 

960 offsets = np.empty(n_samples, dtype=self.indices.dtype) 

961 ret = csr_sample_offsets(M, N, self.indptr, self.indices, n_samples, 

962 i, j, offsets) 

963 if ret == 1: 

964 # rinse and repeat 

965 self.sum_duplicates() 

966 csr_sample_offsets(M, N, self.indptr, self.indices, n_samples, 

967 i, j, offsets) 

968 

969 if -1 not in offsets: 

970 # only affects existing non-zero cells 

971 self.data[offsets] = x 

972 return 

973 

974 else: 

975 warn("Changing the sparsity structure of a {}_matrix is expensive." 

976 " lil_matrix is more efficient.".format(self.format), 

977 SparseEfficiencyWarning, stacklevel=3) 

978 # replace where possible 

979 mask = offsets > -1 

980 self.data[offsets[mask]] = x[mask] 

981 # only insertions remain 

982 mask = ~mask 

983 i = i[mask] 

984 i[i < 0] += M 

985 j = j[mask] 

986 j[j < 0] += N 

987 self._insert_many(i, j, x[mask]) 

988 

989 def _zero_many(self, i, j): 

990 """Sets value at each (i, j) to zero, preserving sparsity structure. 

991 

992 Here (i,j) index major and minor respectively. 

993 """ 

994 i, j, M, N = self._prepare_indices(i, j) 

995 

996 n_samples = len(i) 

997 offsets = np.empty(n_samples, dtype=self.indices.dtype) 

998 ret = csr_sample_offsets(M, N, self.indptr, self.indices, n_samples, 

999 i, j, offsets) 

1000 if ret == 1: 

1001 # rinse and repeat 

1002 self.sum_duplicates() 

1003 csr_sample_offsets(M, N, self.indptr, self.indices, n_samples, 

1004 i, j, offsets) 

1005 

1006 # only assign zeros to the existing sparsity structure 

1007 self.data[offsets[offsets > -1]] = 0 

1008 

1009 def _insert_many(self, i, j, x): 

1010 """Inserts new nonzero at each (i, j) with value x 

1011 

1012 Here (i,j) index major and minor respectively. 

1013 i, j and x must be non-empty, 1d arrays. 

1014 Inserts each major group (e.g. all entries per row) at a time. 

1015 Maintains has_sorted_indices property. 

1016 Modifies i, j, x in place. 

1017 """ 

1018 order = np.argsort(i, kind='mergesort') # stable for duplicates 

1019 i = i.take(order, mode='clip') 

1020 j = j.take(order, mode='clip') 

1021 x = x.take(order, mode='clip') 

1022 

1023 do_sort = self.has_sorted_indices 

1024 

1025 # Update index data type 

1026 idx_dtype = self._get_index_dtype((self.indices, self.indptr), 

1027 maxval=(self.indptr[-1] + x.size)) 

1028 self.indptr = np.asarray(self.indptr, dtype=idx_dtype) 

1029 self.indices = np.asarray(self.indices, dtype=idx_dtype) 

1030 i = np.asarray(i, dtype=idx_dtype) 

1031 j = np.asarray(j, dtype=idx_dtype) 

1032 

1033 # Collate old and new in chunks by major index 

1034 indices_parts = [] 

1035 data_parts = [] 

1036 ui, ui_indptr = np.unique(i, return_index=True) 

1037 ui_indptr = np.append(ui_indptr, len(j)) 

1038 new_nnzs = np.diff(ui_indptr) 

1039 prev = 0 

1040 for c, (ii, js, je) in enumerate(zip(ui, ui_indptr, ui_indptr[1:])): 

1041 # old entries 

1042 start = self.indptr[prev] 

1043 stop = self.indptr[ii] 

1044 indices_parts.append(self.indices[start:stop]) 

1045 data_parts.append(self.data[start:stop]) 

1046 

1047 # handle duplicate j: keep last setting 

1048 uj, uj_indptr = np.unique(j[js:je][::-1], return_index=True) 

1049 if len(uj) == je - js: 

1050 indices_parts.append(j[js:je]) 

1051 data_parts.append(x[js:je]) 

1052 else: 

1053 indices_parts.append(j[js:je][::-1][uj_indptr]) 

1054 data_parts.append(x[js:je][::-1][uj_indptr]) 

1055 new_nnzs[c] = len(uj) 

1056 

1057 prev = ii 

1058 

1059 # remaining old entries 

1060 start = self.indptr[ii] 

1061 indices_parts.append(self.indices[start:]) 

1062 data_parts.append(self.data[start:]) 

1063 

1064 # update attributes 

1065 self.indices = np.concatenate(indices_parts) 

1066 self.data = np.concatenate(data_parts) 

1067 nnzs = np.empty(self.indptr.shape, dtype=idx_dtype) 

1068 nnzs[0] = idx_dtype(0) 

1069 indptr_diff = np.diff(self.indptr) 

1070 indptr_diff[ui] += new_nnzs 

1071 nnzs[1:] = indptr_diff 

1072 self.indptr = np.cumsum(nnzs, out=nnzs) 

1073 

1074 if do_sort: 

1075 # TODO: only sort where necessary 

1076 self.has_sorted_indices = False 

1077 self.sort_indices() 

1078 

1079 self.check_format(full_check=False) 

1080 

1081 ###################### 

1082 # Conversion methods # 

1083 ###################### 

1084 

1085 def tocoo(self, copy=True): 

1086 major_dim, minor_dim = self._swap(self.shape) 

1087 minor_indices = self.indices 

1088 major_indices = np.empty(len(minor_indices), dtype=self.indices.dtype) 

1089 _sparsetools.expandptr(major_dim, self.indptr, major_indices) 

1090 coords = self._swap((major_indices, minor_indices)) 

1091 

1092 return self._coo_container( 

1093 (self.data, coords), self.shape, copy=copy, dtype=self.dtype 

1094 ) 

1095 

1096 tocoo.__doc__ = _spbase.tocoo.__doc__ 

1097 

1098 def toarray(self, order=None, out=None): 

1099 if out is None and order is None: 

1100 order = self._swap('cf')[0] 

1101 out = self._process_toarray_args(order, out) 

1102 if not (out.flags.c_contiguous or out.flags.f_contiguous): 

1103 raise ValueError('Output array must be C or F contiguous') 

1104 # align ideal order with output array order 

1105 if out.flags.c_contiguous: 

1106 x = self.tocsr() 

1107 y = out 

1108 else: 

1109 x = self.tocsc() 

1110 y = out.T 

1111 M, N = x._swap(x.shape) 

1112 csr_todense(M, N, x.indptr, x.indices, x.data, y) 

1113 return out 

1114 

1115 toarray.__doc__ = _spbase.toarray.__doc__ 

1116 

1117 ############################################################## 

1118 # methods that examine or modify the internal data structure # 

1119 ############################################################## 

1120 

1121 def eliminate_zeros(self): 

1122 """Remove zero entries from the array/matrix 

1123 

1124 This is an *in place* operation. 

1125 """ 

1126 M, N = self._swap(self.shape) 

1127 _sparsetools.csr_eliminate_zeros(M, N, self.indptr, self.indices, 

1128 self.data) 

1129 self.prune() # nnz may have changed 

1130 

1131 @property 

1132 def has_canonical_format(self) -> bool: 

1133 """Whether the array/matrix has sorted indices and no duplicates 

1134 

1135 Returns 

1136 - True: if the above applies 

1137 - False: otherwise 

1138 

1139 has_canonical_format implies has_sorted_indices, so if the latter flag 

1140 is False, so will the former be; if the former is found True, the 

1141 latter flag is also set. 

1142 """ 

1143 # first check to see if result was cached 

1144 if not getattr(self, '_has_sorted_indices', True): 

1145 # not sorted => not canonical 

1146 self._has_canonical_format = False 

1147 elif not hasattr(self, '_has_canonical_format'): 

1148 self.has_canonical_format = bool( 

1149 _sparsetools.csr_has_canonical_format( 

1150 len(self.indptr) - 1, self.indptr, self.indices) 

1151 ) 

1152 return self._has_canonical_format 

1153 

1154 @has_canonical_format.setter 

1155 def has_canonical_format(self, val: bool): 

1156 self._has_canonical_format = bool(val) 

1157 if val: 

1158 self.has_sorted_indices = True 

1159 

1160 def sum_duplicates(self): 

1161 """Eliminate duplicate entries by adding them together 

1162 

1163 This is an *in place* operation. 

1164 """ 

1165 if self.has_canonical_format: 

1166 return 

1167 self.sort_indices() 

1168 

1169 M, N = self._swap(self.shape) 

1170 _sparsetools.csr_sum_duplicates(M, N, self.indptr, self.indices, 

1171 self.data) 

1172 

1173 self.prune() # nnz may have changed 

1174 self.has_canonical_format = True 

1175 

1176 @property 

1177 def has_sorted_indices(self) -> bool: 

1178 """Whether the indices are sorted 

1179 

1180 Returns 

1181 - True: if the indices of the array/matrix are in sorted order 

1182 - False: otherwise 

1183 """ 

1184 # first check to see if result was cached 

1185 if not hasattr(self, '_has_sorted_indices'): 

1186 self._has_sorted_indices = bool( 

1187 _sparsetools.csr_has_sorted_indices( 

1188 len(self.indptr) - 1, self.indptr, self.indices) 

1189 ) 

1190 return self._has_sorted_indices 

1191 

1192 @has_sorted_indices.setter 

1193 def has_sorted_indices(self, val: bool): 

1194 self._has_sorted_indices = bool(val) 

1195 

1196 

1197 def sorted_indices(self): 

1198 """Return a copy of this array/matrix with sorted indices 

1199 """ 

1200 A = self.copy() 

1201 A.sort_indices() 

1202 return A 

1203 

1204 # an alternative that has linear complexity is the following 

1205 # although the previous option is typically faster 

1206 # return self.toother().toother() 

1207 

1208 def sort_indices(self): 

1209 """Sort the indices of this array/matrix *in place* 

1210 """ 

1211 

1212 if not self.has_sorted_indices: 

1213 _sparsetools.csr_sort_indices(len(self.indptr) - 1, self.indptr, 

1214 self.indices, self.data) 

1215 self.has_sorted_indices = True 

1216 

1217 def prune(self): 

1218 """Remove empty space after all non-zero elements. 

1219 """ 

1220 major_dim = self._swap(self.shape)[0] 

1221 

1222 if len(self.indptr) != major_dim + 1: 

1223 raise ValueError('index pointer has invalid length') 

1224 if len(self.indices) < self.nnz: 

1225 raise ValueError('indices array has fewer than nnz elements') 

1226 if len(self.data) < self.nnz: 

1227 raise ValueError('data array has fewer than nnz elements') 

1228 

1229 self.indices = _prune_array(self.indices[:self.nnz]) 

1230 self.data = _prune_array(self.data[:self.nnz]) 

1231 

1232 def resize(self, *shape): 

1233 shape = check_shape(shape) 

1234 if hasattr(self, 'blocksize'): 

1235 bm, bn = self.blocksize 

1236 new_M, rm = divmod(shape[0], bm) 

1237 new_N, rn = divmod(shape[1], bn) 

1238 if rm or rn: 

1239 raise ValueError("shape must be divisible into {} blocks. " 

1240 "Got {}".format(self.blocksize, shape)) 

1241 M, N = self.shape[0] // bm, self.shape[1] // bn 

1242 else: 

1243 new_M, new_N = self._swap(shape) 

1244 M, N = self._swap(self.shape) 

1245 

1246 if new_M < M: 

1247 self.indices = self.indices[:self.indptr[new_M]] 

1248 self.data = self.data[:self.indptr[new_M]] 

1249 self.indptr = self.indptr[:new_M + 1] 

1250 elif new_M > M: 

1251 self.indptr = np.resize(self.indptr, new_M + 1) 

1252 self.indptr[M + 1:].fill(self.indptr[M]) 

1253 

1254 if new_N < N: 

1255 mask = self.indices < new_N 

1256 if not np.all(mask): 

1257 self.indices = self.indices[mask] 

1258 self.data = self.data[mask] 

1259 major_index, val = self._minor_reduce(np.add, mask) 

1260 self.indptr.fill(0) 

1261 self.indptr[1:][major_index] = val 

1262 np.cumsum(self.indptr, out=self.indptr) 

1263 

1264 self._shape = shape 

1265 

1266 resize.__doc__ = _spbase.resize.__doc__ 

1267 

1268 ################### 

1269 # utility methods # 

1270 ################### 

1271 

1272 # needed by _data_matrix 

1273 def _with_data(self, data, copy=True): 

1274 """Returns a matrix with the same sparsity structure as self, 

1275 but with different data. By default the structure arrays 

1276 (i.e. .indptr and .indices) are copied. 

1277 """ 

1278 if copy: 

1279 return self.__class__((data, self.indices.copy(), 

1280 self.indptr.copy()), 

1281 shape=self.shape, 

1282 dtype=data.dtype) 

1283 else: 

1284 return self.__class__((data, self.indices, self.indptr), 

1285 shape=self.shape, dtype=data.dtype) 

1286 

1287 def _binopt(self, other, op): 

1288 """apply the binary operation fn to two sparse matrices.""" 

1289 other = self.__class__(other) 

1290 

1291 # e.g. csr_plus_csr, csr_minus_csr, etc. 

1292 fn = getattr(_sparsetools, self.format + op + self.format) 

1293 

1294 maxnnz = self.nnz + other.nnz 

1295 idx_dtype = self._get_index_dtype((self.indptr, self.indices, 

1296 other.indptr, other.indices), 

1297 maxval=maxnnz) 

1298 indptr = np.empty(self.indptr.shape, dtype=idx_dtype) 

1299 indices = np.empty(maxnnz, dtype=idx_dtype) 

1300 

1301 bool_ops = ['_ne_', '_lt_', '_gt_', '_le_', '_ge_'] 

1302 if op in bool_ops: 

1303 data = np.empty(maxnnz, dtype=np.bool_) 

1304 else: 

1305 data = np.empty(maxnnz, dtype=upcast(self.dtype, other.dtype)) 

1306 

1307 fn(self.shape[0], self.shape[1], 

1308 np.asarray(self.indptr, dtype=idx_dtype), 

1309 np.asarray(self.indices, dtype=idx_dtype), 

1310 self.data, 

1311 np.asarray(other.indptr, dtype=idx_dtype), 

1312 np.asarray(other.indices, dtype=idx_dtype), 

1313 other.data, 

1314 indptr, indices, data) 

1315 

1316 A = self.__class__((data, indices, indptr), shape=self.shape) 

1317 A.prune() 

1318 

1319 return A 

1320 

1321 def _divide_sparse(self, other): 

1322 """ 

1323 Divide this matrix by a second sparse matrix. 

1324 """ 

1325 if other.shape != self.shape: 

1326 raise ValueError('inconsistent shapes') 

1327 

1328 r = self._binopt(other, '_eldiv_') 

1329 

1330 if np.issubdtype(r.dtype, np.inexact): 

1331 # Eldiv leaves entries outside the combined sparsity 

1332 # pattern empty, so they must be filled manually. 

1333 # Everything outside of other's sparsity is NaN, and everything 

1334 # inside it is either zero or defined by eldiv. 

1335 out = np.empty(self.shape, dtype=self.dtype) 

1336 out.fill(np.nan) 

1337 row, col = other.nonzero() 

1338 out[row, col] = 0 

1339 r = r.tocoo() 

1340 out[r.row, r.col] = r.data 

1341 out = self._container(out) 

1342 else: 

1343 # integers types go with nan <-> 0 

1344 out = r 

1345 

1346 return out 

1347 

1348 

1349def _process_slice(sl, num): 

1350 if sl is None: 

1351 i0, i1 = 0, num 

1352 elif isinstance(sl, slice): 

1353 i0, i1, stride = sl.indices(num) 

1354 if stride != 1: 

1355 raise ValueError('slicing with step != 1 not supported') 

1356 i0 = min(i0, i1) # give an empty slice when i0 > i1 

1357 elif isintlike(sl): 

1358 if sl < 0: 

1359 sl += num 

1360 i0, i1 = sl, sl + 1 

1361 if i0 < 0 or i1 > num: 

1362 raise IndexError('index out of bounds: 0 <= %d < %d <= %d' % 

1363 (i0, i1, num)) 

1364 else: 

1365 raise TypeError('expected slice or scalar') 

1366 

1367 return i0, i1