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

250 statements  

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

1"""Sparse DIAgonal format""" 

2 

3__docformat__ = "restructuredtext en" 

4 

5__all__ = ['dia_array', 'dia_matrix', 'isspmatrix_dia'] 

6 

7import numpy as np 

8 

9from .._lib._util import copy_if_needed 

10from ._matrix import spmatrix 

11from ._base import issparse, _formats, _spbase, sparray 

12from ._data import _data_matrix 

13from ._sputils import ( 

14 isshape, upcast_char, getdtype, get_sum_dtype, validateaxis, check_shape 

15) 

16from ._sparsetools import dia_matvec 

17 

18 

19class _dia_base(_data_matrix): 

20 _format = 'dia' 

21 

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

23 _data_matrix.__init__(self) 

24 

25 if issparse(arg1): 

26 if arg1.format == "dia": 

27 if copy: 

28 arg1 = arg1.copy() 

29 self.data = arg1.data 

30 self.offsets = arg1.offsets 

31 self._shape = check_shape(arg1.shape) 

32 else: 

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

34 A = arg1.copy() 

35 else: 

36 A = arg1.todia() 

37 self.data = A.data 

38 self.offsets = A.offsets 

39 self._shape = check_shape(A.shape) 

40 elif isinstance(arg1, tuple): 

41 if isshape(arg1): 

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

43 # create empty matrix 

44 self._shape = check_shape(arg1) 

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

46 idx_dtype = self._get_index_dtype(maxval=max(self.shape)) 

47 self.offsets = np.zeros((0), dtype=idx_dtype) 

48 else: 

49 try: 

50 # Try interpreting it as (data, offsets) 

51 data, offsets = arg1 

52 except Exception as e: 

53 message = 'unrecognized form for dia_array constructor' 

54 raise ValueError(message) from e 

55 else: 

56 if shape is None: 

57 raise ValueError('expected a shape argument') 

58 if not copy: 

59 copy = copy_if_needed 

60 self.data = np.atleast_2d(np.array(arg1[0], dtype=dtype, copy=copy)) 

61 offsets = np.array(arg1[1], 

62 dtype=self._get_index_dtype(maxval=max(shape)), 

63 copy=copy) 

64 self.offsets = np.atleast_1d(offsets) 

65 self._shape = check_shape(shape) 

66 else: 

67 #must be dense, convert to COO first, then to DIA 

68 try: 

69 arg1 = np.asarray(arg1) 

70 except Exception as e: 

71 raise ValueError("unrecognized form for" 

72 " %s_matrix constructor" % self.format) from e 

73 A = self._coo_container(arg1, dtype=dtype, shape=shape).todia() 

74 self.data = A.data 

75 self.offsets = A.offsets 

76 self._shape = check_shape(A.shape) 

77 

78 if dtype is not None: 

79 self.data = self.data.astype(dtype) 

80 

81 #check format 

82 if self.offsets.ndim != 1: 

83 raise ValueError('offsets array must have rank 1') 

84 

85 if self.data.ndim != 2: 

86 raise ValueError('data array must have rank 2') 

87 

88 if self.data.shape[0] != len(self.offsets): 

89 raise ValueError('number of diagonals (%d) ' 

90 'does not match the number of offsets (%d)' 

91 % (self.data.shape[0], len(self.offsets))) 

92 

93 if len(np.unique(self.offsets)) != len(self.offsets): 

94 raise ValueError('offset array contains duplicate values') 

95 

96 def __repr__(self): 

97 _, fmt = _formats[self.format] 

98 sparse_cls = 'array' if isinstance(self, sparray) else 'matrix' 

99 shape_str = 'x'.join(str(x) for x in self.shape) 

100 ndiag = self.data.shape[0] 

101 return ( 

102 f"<{shape_str} sparse {sparse_cls} of type '{self.dtype.type}'\n" 

103 f"\twith {self.nnz} stored elements ({ndiag} diagonals) in {fmt} format>" 

104 ) 

105 

106 def _data_mask(self): 

107 """Returns a mask of the same shape as self.data, where 

108 mask[i,j] is True when data[i,j] corresponds to a stored element.""" 

109 num_rows, num_cols = self.shape 

110 offset_inds = np.arange(self.data.shape[1]) 

111 row = offset_inds - self.offsets[:,None] 

112 mask = (row >= 0) 

113 mask &= (row < num_rows) 

114 mask &= (offset_inds < num_cols) 

115 return mask 

116 

117 def count_nonzero(self): 

118 mask = self._data_mask() 

119 return np.count_nonzero(self.data[mask]) 

120 

121 def _getnnz(self, axis=None): 

122 if axis is not None: 

123 raise NotImplementedError("_getnnz over an axis is not implemented " 

124 "for DIA format") 

125 M,N = self.shape 

126 nnz = 0 

127 for k in self.offsets: 

128 if k > 0: 

129 nnz += min(M,N-k) 

130 else: 

131 nnz += min(M+k,N) 

132 return int(nnz) 

133 

134 _getnnz.__doc__ = _spbase._getnnz.__doc__ 

135 count_nonzero.__doc__ = _spbase.count_nonzero.__doc__ 

136 

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

138 validateaxis(axis) 

139 

140 if axis is not None and axis < 0: 

141 axis += 2 

142 

143 res_dtype = get_sum_dtype(self.dtype) 

144 num_rows, num_cols = self.shape 

145 ret = None 

146 

147 if axis == 0: 

148 mask = self._data_mask() 

149 x = (self.data * mask).sum(axis=0) 

150 if x.shape[0] == num_cols: 

151 res = x 

152 else: 

153 res = np.zeros(num_cols, dtype=x.dtype) 

154 res[:x.shape[0]] = x 

155 ret = self._ascontainer(res, dtype=res_dtype) 

156 

157 else: 

158 row_sums = np.zeros((num_rows, 1), dtype=res_dtype) 

159 one = np.ones(num_cols, dtype=res_dtype) 

160 dia_matvec(num_rows, num_cols, len(self.offsets), 

161 self.data.shape[1], self.offsets, self.data, one, row_sums) 

162 

163 row_sums = self._ascontainer(row_sums) 

164 

165 if axis is None: 

166 return row_sums.sum(dtype=dtype, out=out) 

167 

168 ret = self._ascontainer(row_sums.sum(axis=axis)) 

169 

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

171 raise ValueError("dimensions do not match") 

172 

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

174 

175 sum.__doc__ = _spbase.sum.__doc__ 

176 

177 def _add_sparse(self, other): 

178 

179 # Check if other is also of type dia_array 

180 if not isinstance(other, type(self)): 

181 # If other is not of type dia_array, default to 

182 # converting to csr_matrix, as is done in the _add_sparse 

183 # method of parent class _spbase 

184 return self.tocsr()._add_sparse(other) 

185 

186 # The task is to compute m = self + other 

187 # Start by making a copy of self, of the datatype 

188 # that should result from adding self and other 

189 dtype = np.promote_types(self.dtype, other.dtype) 

190 m = self.astype(dtype, copy=True) 

191 

192 # Then, add all the stored diagonals of other. 

193 for d in other.offsets: 

194 # Check if the diagonal has already been added. 

195 if d in m.offsets: 

196 # If the diagonal is already there, we need to take 

197 # the sum of the existing and the new 

198 m.setdiag(m.diagonal(d) + other.diagonal(d), d) 

199 else: 

200 m.setdiag(other.diagonal(d), d) 

201 return m 

202 

203 def _matmul_vector(self, other): 

204 x = other 

205 

206 y = np.zeros(self.shape[0], dtype=upcast_char(self.dtype.char, 

207 x.dtype.char)) 

208 

209 L = self.data.shape[1] 

210 

211 M,N = self.shape 

212 

213 dia_matvec(M,N, len(self.offsets), L, self.offsets, self.data, 

214 x.ravel(), y.ravel()) 

215 

216 return y 

217 

218 def _setdiag(self, values, k=0): 

219 M, N = self.shape 

220 

221 if values.ndim == 0: 

222 # broadcast 

223 values_n = np.inf 

224 else: 

225 values_n = len(values) 

226 

227 if k < 0: 

228 n = min(M + k, N, values_n) 

229 min_index = 0 

230 max_index = n 

231 else: 

232 n = min(M, N - k, values_n) 

233 min_index = k 

234 max_index = k + n 

235 

236 if values.ndim != 0: 

237 # allow also longer sequences 

238 values = values[:n] 

239 

240 data_rows, data_cols = self.data.shape 

241 if k in self.offsets: 

242 if max_index > data_cols: 

243 data = np.zeros((data_rows, max_index), dtype=self.data.dtype) 

244 data[:, :data_cols] = self.data 

245 self.data = data 

246 self.data[self.offsets == k, min_index:max_index] = values 

247 else: 

248 self.offsets = np.append(self.offsets, self.offsets.dtype.type(k)) 

249 m = max(max_index, data_cols) 

250 data = np.zeros((data_rows + 1, m), dtype=self.data.dtype) 

251 data[:-1, :data_cols] = self.data 

252 data[-1, min_index:max_index] = values 

253 self.data = data 

254 

255 def todia(self, copy=False): 

256 if copy: 

257 return self.copy() 

258 else: 

259 return self 

260 

261 todia.__doc__ = _spbase.todia.__doc__ 

262 

263 def transpose(self, axes=None, copy=False): 

264 if axes is not None and axes != (1, 0): 

265 raise ValueError("Sparse arrays/matrices do not support " 

266 "an 'axes' parameter because swapping " 

267 "dimensions is the only logical permutation.") 

268 

269 num_rows, num_cols = self.shape 

270 max_dim = max(self.shape) 

271 

272 # flip diagonal offsets 

273 offsets = -self.offsets 

274 

275 # re-align the data matrix 

276 r = np.arange(len(offsets), dtype=np.intc)[:, None] 

277 c = np.arange(num_rows, dtype=np.intc) - (offsets % max_dim)[:, None] 

278 pad_amount = max(0, max_dim-self.data.shape[1]) 

279 data = np.hstack((self.data, np.zeros((self.data.shape[0], pad_amount), 

280 dtype=self.data.dtype))) 

281 data = data[r, c] 

282 return self._dia_container((data, offsets), shape=( 

283 num_cols, num_rows), copy=copy) 

284 

285 transpose.__doc__ = _spbase.transpose.__doc__ 

286 

287 def diagonal(self, k=0): 

288 rows, cols = self.shape 

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

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

291 idx, = np.nonzero(self.offsets == k) 

292 first_col = max(0, k) 

293 last_col = min(rows + k, cols) 

294 result_size = last_col - first_col 

295 if idx.size == 0: 

296 return np.zeros(result_size, dtype=self.data.dtype) 

297 result = self.data[idx[0], first_col:last_col] 

298 padding = result_size - len(result) 

299 if padding > 0: 

300 result = np.pad(result, (0, padding), mode='constant') 

301 return result 

302 

303 diagonal.__doc__ = _spbase.diagonal.__doc__ 

304 

305 def tocsc(self, copy=False): 

306 if self.nnz == 0: 

307 return self._csc_container(self.shape, dtype=self.dtype) 

308 

309 num_rows, num_cols = self.shape 

310 num_offsets, offset_len = self.data.shape 

311 offset_inds = np.arange(offset_len) 

312 

313 row = offset_inds - self.offsets[:,None] 

314 mask = (row >= 0) 

315 mask &= (row < num_rows) 

316 mask &= (offset_inds < num_cols) 

317 mask &= (self.data != 0) 

318 

319 idx_dtype = self._get_index_dtype(maxval=max(self.shape)) 

320 indptr = np.zeros(num_cols + 1, dtype=idx_dtype) 

321 indptr[1:offset_len+1] = np.cumsum(mask.sum(axis=0)[:num_cols]) 

322 if offset_len < num_cols: 

323 indptr[offset_len+1:] = indptr[offset_len] 

324 indices = row.T[mask.T].astype(idx_dtype, copy=False) 

325 data = self.data.T[mask.T] 

326 return self._csc_container((data, indices, indptr), shape=self.shape, 

327 dtype=self.dtype) 

328 

329 tocsc.__doc__ = _spbase.tocsc.__doc__ 

330 

331 def tocoo(self, copy=False): 

332 num_rows, num_cols = self.shape 

333 num_offsets, offset_len = self.data.shape 

334 offset_inds = np.arange(offset_len) 

335 

336 row = offset_inds - self.offsets[:,None] 

337 mask = (row >= 0) 

338 mask &= (row < num_rows) 

339 mask &= (offset_inds < num_cols) 

340 mask &= (self.data != 0) 

341 row = row[mask] 

342 col = np.tile(offset_inds, num_offsets)[mask.ravel()] 

343 idx_dtype = self._get_index_dtype( 

344 arrays=(self.offsets,), maxval=max(self.shape) 

345 ) 

346 row = row.astype(idx_dtype, copy=False) 

347 col = col.astype(idx_dtype, copy=False) 

348 data = self.data[mask] 

349 # Note: this cannot set has_canonical_format=True, because despite the 

350 # lack of duplicates, we do not generate sorted indices. 

351 return self._coo_container( 

352 (data, (row, col)), shape=self.shape, dtype=self.dtype, copy=False 

353 ) 

354 

355 tocoo.__doc__ = _spbase.tocoo.__doc__ 

356 

357 # needed by _data_matrix 

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

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

360 but with different data. By default the structure arrays are copied. 

361 """ 

362 if copy: 

363 return self._dia_container( 

364 (data, self.offsets.copy()), shape=self.shape 

365 ) 

366 else: 

367 return self._dia_container( 

368 (data, self.offsets), shape=self.shape 

369 ) 

370 

371 def resize(self, *shape): 

372 shape = check_shape(shape) 

373 M, N = shape 

374 # we do not need to handle the case of expanding N 

375 self.data = self.data[:, :N] 

376 

377 if (M > self.shape[0] and 

378 np.any(self.offsets + self.shape[0] < self.data.shape[1])): 

379 # explicitly clear values that were previously hidden 

380 mask = (self.offsets[:, None] + self.shape[0] <= 

381 np.arange(self.data.shape[1])) 

382 self.data[mask] = 0 

383 

384 self._shape = shape 

385 

386 resize.__doc__ = _spbase.resize.__doc__ 

387 

388 

389def isspmatrix_dia(x): 

390 """Is `x` of dia_matrix type? 

391 

392 Parameters 

393 ---------- 

394 x 

395 object to check for being a dia matrix 

396 

397 Returns 

398 ------- 

399 bool 

400 True if `x` is a dia matrix, False otherwise 

401 

402 Examples 

403 -------- 

404 >>> from scipy.sparse import dia_array, dia_matrix, coo_matrix, isspmatrix_dia 

405 >>> isspmatrix_dia(dia_matrix([[5]])) 

406 True 

407 >>> isspmatrix_dia(dia_array([[5]])) 

408 False 

409 >>> isspmatrix_dia(coo_matrix([[5]])) 

410 False 

411 """ 

412 return isinstance(x, dia_matrix) 

413 

414 

415# This namespace class separates array from matrix with isinstance 

416class dia_array(_dia_base, sparray): 

417 """ 

418 Sparse array with DIAgonal storage. 

419 

420 This can be instantiated in several ways: 

421 dia_array(D) 

422 where D is a 2-D ndarray 

423 

424 dia_array(S) 

425 with another sparse array or matrix S (equivalent to S.todia()) 

426 

427 dia_array((M, N), [dtype]) 

428 to construct an empty array with shape (M, N), 

429 dtype is optional, defaulting to dtype='d'. 

430 

431 dia_array((data, offsets), shape=(M, N)) 

432 where the ``data[k,:]`` stores the diagonal entries for 

433 diagonal ``offsets[k]`` (See example below) 

434 

435 Attributes 

436 ---------- 

437 dtype : dtype 

438 Data type of the array 

439 shape : 2-tuple 

440 Shape of the array 

441 ndim : int 

442 Number of dimensions (this is always 2) 

443 nnz 

444 size 

445 data 

446 DIA format data array of the array 

447 offsets 

448 DIA format offset array of the array 

449 T 

450 

451 Notes 

452 ----- 

453 

454 Sparse arrays can be used in arithmetic operations: they support 

455 addition, subtraction, multiplication, division, and matrix power. 

456 

457 Examples 

458 -------- 

459 

460 >>> import numpy as np 

461 >>> from scipy.sparse import dia_array 

462 >>> dia_array((3, 4), dtype=np.int8).toarray() 

463 array([[0, 0, 0, 0], 

464 [0, 0, 0, 0], 

465 [0, 0, 0, 0]], dtype=int8) 

466 

467 >>> data = np.array([[1, 2, 3, 4]]).repeat(3, axis=0) 

468 >>> offsets = np.array([0, -1, 2]) 

469 >>> dia_array((data, offsets), shape=(4, 4)).toarray() 

470 array([[1, 0, 3, 0], 

471 [1, 2, 0, 4], 

472 [0, 2, 3, 0], 

473 [0, 0, 3, 4]]) 

474 

475 >>> from scipy.sparse import dia_array 

476 >>> n = 10 

477 >>> ex = np.ones(n) 

478 >>> data = np.array([ex, 2 * ex, ex]) 

479 >>> offsets = np.array([-1, 0, 1]) 

480 >>> dia_array((data, offsets), shape=(n, n)).toarray() 

481 array([[2., 1., 0., ..., 0., 0., 0.], 

482 [1., 2., 1., ..., 0., 0., 0.], 

483 [0., 1., 2., ..., 0., 0., 0.], 

484 ..., 

485 [0., 0., 0., ..., 2., 1., 0.], 

486 [0., 0., 0., ..., 1., 2., 1.], 

487 [0., 0., 0., ..., 0., 1., 2.]]) 

488 """ 

489 

490 

491class dia_matrix(spmatrix, _dia_base): 

492 """ 

493 Sparse matrix with DIAgonal storage. 

494 

495 This can be instantiated in several ways: 

496 dia_matrix(D) 

497 where D is a 2-D ndarray 

498 

499 dia_matrix(S) 

500 with another sparse array or matrix S (equivalent to S.todia()) 

501 

502 dia_matrix((M, N), [dtype]) 

503 to construct an empty matrix with shape (M, N), 

504 dtype is optional, defaulting to dtype='d'. 

505 

506 dia_matrix((data, offsets), shape=(M, N)) 

507 where the ``data[k,:]`` stores the diagonal entries for 

508 diagonal ``offsets[k]`` (See example below) 

509 

510 Attributes 

511 ---------- 

512 dtype : dtype 

513 Data type of the matrix 

514 shape : 2-tuple 

515 Shape of the matrix 

516 ndim : int 

517 Number of dimensions (this is always 2) 

518 nnz 

519 size 

520 data 

521 DIA format data array of the matrix 

522 offsets 

523 DIA format offset array of the matrix 

524 T 

525 

526 Notes 

527 ----- 

528 

529 Sparse matrices can be used in arithmetic operations: they support 

530 addition, subtraction, multiplication, division, and matrix power. 

531 

532 Examples 

533 -------- 

534 

535 >>> import numpy as np 

536 >>> from scipy.sparse import dia_matrix 

537 >>> dia_matrix((3, 4), dtype=np.int8).toarray() 

538 array([[0, 0, 0, 0], 

539 [0, 0, 0, 0], 

540 [0, 0, 0, 0]], dtype=int8) 

541 

542 >>> data = np.array([[1, 2, 3, 4]]).repeat(3, axis=0) 

543 >>> offsets = np.array([0, -1, 2]) 

544 >>> dia_matrix((data, offsets), shape=(4, 4)).toarray() 

545 array([[1, 0, 3, 0], 

546 [1, 2, 0, 4], 

547 [0, 2, 3, 0], 

548 [0, 0, 3, 4]]) 

549 

550 >>> from scipy.sparse import dia_matrix 

551 >>> n = 10 

552 >>> ex = np.ones(n) 

553 >>> data = np.array([ex, 2 * ex, ex]) 

554 >>> offsets = np.array([-1, 0, 1]) 

555 >>> dia_matrix((data, offsets), shape=(n, n)).toarray() 

556 array([[2., 1., 0., ..., 0., 0., 0.], 

557 [1., 2., 1., ..., 0., 0., 0.], 

558 [0., 1., 2., ..., 0., 0., 0.], 

559 ..., 

560 [0., 0., 0., ..., 2., 1., 0.], 

561 [0., 0., 0., ..., 1., 2., 1.], 

562 [0., 0., 0., ..., 0., 1., 2.]]) 

563 """