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

247 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2024-02-14 06:37 +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 ._matrix import spmatrix 

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

11from ._data import _data_matrix 

12from ._sputils import ( 

13 isshape, upcast_char, getdtype, get_sum_dtype, validateaxis, check_shape 

14) 

15from ._sparsetools import dia_matvec 

16 

17 

18class _dia_base(_data_matrix): 

19 _format = 'dia' 

20 

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

22 _data_matrix.__init__(self) 

23 

24 if issparse(arg1): 

25 if arg1.format == "dia": 

26 if copy: 

27 arg1 = arg1.copy() 

28 self.data = arg1.data 

29 self.offsets = arg1.offsets 

30 self._shape = check_shape(arg1.shape) 

31 else: 

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

33 A = arg1.copy() 

34 else: 

35 A = arg1.todia() 

36 self.data = A.data 

37 self.offsets = A.offsets 

38 self._shape = check_shape(A.shape) 

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 self.data = np.zeros((0,0), getdtype(dtype, default=float)) 

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

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

47 else: 

48 try: 

49 # Try interpreting it as (data, offsets) 

50 data, offsets = arg1 

51 except Exception as e: 

52 message = 'unrecognized form for dia_array constructor' 

53 raise ValueError(message) from e 

54 else: 

55 if shape is None: 

56 raise ValueError('expected a shape argument') 

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

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

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

60 copy=copy) 

61 self.offsets = np.atleast_1d(offsets) 

62 self._shape = check_shape(shape) 

63 else: 

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

65 try: 

66 arg1 = np.asarray(arg1) 

67 except Exception as e: 

68 raise ValueError("unrecognized form for" 

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

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

71 self.data = A.data 

72 self.offsets = A.offsets 

73 self._shape = check_shape(A.shape) 

74 

75 if dtype is not None: 

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

77 

78 #check format 

79 if self.offsets.ndim != 1: 

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

81 

82 if self.data.ndim != 2: 

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

84 

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

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

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

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

89 

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

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

92 

93 def __repr__(self): 

94 _, fmt = _formats[self.format] 

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

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

97 ndiag = self.data.shape[0] 

98 return ( 

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

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

101 ) 

102 

103 def _data_mask(self): 

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

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

106 num_rows, num_cols = self.shape 

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

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

109 mask = (row >= 0) 

110 mask &= (row < num_rows) 

111 mask &= (offset_inds < num_cols) 

112 return mask 

113 

114 def count_nonzero(self): 

115 mask = self._data_mask() 

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

117 

118 def _getnnz(self, axis=None): 

119 if axis is not None: 

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

121 "for DIA format") 

122 M,N = self.shape 

123 nnz = 0 

124 for k in self.offsets: 

125 if k > 0: 

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

127 else: 

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

129 return int(nnz) 

130 

131 _getnnz.__doc__ = _spbase._getnnz.__doc__ 

132 count_nonzero.__doc__ = _spbase.count_nonzero.__doc__ 

133 

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

135 validateaxis(axis) 

136 

137 if axis is not None and axis < 0: 

138 axis += 2 

139 

140 res_dtype = get_sum_dtype(self.dtype) 

141 num_rows, num_cols = self.shape 

142 ret = None 

143 

144 if axis == 0: 

145 mask = self._data_mask() 

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

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

148 res = x 

149 else: 

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

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

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

153 

154 else: 

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

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

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

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

159 

160 row_sums = self._ascontainer(row_sums) 

161 

162 if axis is None: 

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

164 

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

166 

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

168 raise ValueError("dimensions do not match") 

169 

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

171 

172 sum.__doc__ = _spbase.sum.__doc__ 

173 

174 def _add_sparse(self, other): 

175 

176 # Check if other is also of type dia_array 

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

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

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

180 # method of parent class _spbase 

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

182 

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

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

185 # that should result from adding self and other 

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

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

188 

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

190 for d in other.offsets: 

191 # Check if the diagonal has already been added. 

192 if d in m.offsets: 

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

194 # the sum of the existing and the new 

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

196 else: 

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

198 return m 

199 

200 def _matmul_vector(self, other): 

201 x = other 

202 

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

204 x.dtype.char)) 

205 

206 L = self.data.shape[1] 

207 

208 M,N = self.shape 

209 

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

211 x.ravel(), y.ravel()) 

212 

213 return y 

214 

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

216 M, N = self.shape 

217 

218 if values.ndim == 0: 

219 # broadcast 

220 values_n = np.inf 

221 else: 

222 values_n = len(values) 

223 

224 if k < 0: 

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

226 min_index = 0 

227 max_index = n 

228 else: 

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

230 min_index = k 

231 max_index = k + n 

232 

233 if values.ndim != 0: 

234 # allow also longer sequences 

235 values = values[:n] 

236 

237 data_rows, data_cols = self.data.shape 

238 if k in self.offsets: 

239 if max_index > data_cols: 

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

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

242 self.data = data 

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

244 else: 

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

246 m = max(max_index, data_cols) 

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

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

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

250 self.data = data 

251 

252 def todia(self, copy=False): 

253 if copy: 

254 return self.copy() 

255 else: 

256 return self 

257 

258 todia.__doc__ = _spbase.todia.__doc__ 

259 

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

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

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

263 "an 'axes' parameter because swapping " 

264 "dimensions is the only logical permutation.") 

265 

266 num_rows, num_cols = self.shape 

267 max_dim = max(self.shape) 

268 

269 # flip diagonal offsets 

270 offsets = -self.offsets 

271 

272 # re-align the data matrix 

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

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

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

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

277 dtype=self.data.dtype))) 

278 data = data[r, c] 

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

280 num_cols, num_rows), copy=copy) 

281 

282 transpose.__doc__ = _spbase.transpose.__doc__ 

283 

284 def diagonal(self, k=0): 

285 rows, cols = self.shape 

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

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

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

289 first_col = max(0, k) 

290 last_col = min(rows + k, cols) 

291 result_size = last_col - first_col 

292 if idx.size == 0: 

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

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

295 padding = result_size - len(result) 

296 if padding > 0: 

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

298 return result 

299 

300 diagonal.__doc__ = _spbase.diagonal.__doc__ 

301 

302 def tocsc(self, copy=False): 

303 if self.nnz == 0: 

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

305 

306 num_rows, num_cols = self.shape 

307 num_offsets, offset_len = self.data.shape 

308 offset_inds = np.arange(offset_len) 

309 

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

311 mask = (row >= 0) 

312 mask &= (row < num_rows) 

313 mask &= (offset_inds < num_cols) 

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

315 

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

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

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

319 if offset_len < num_cols: 

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

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

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

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

324 dtype=self.dtype) 

325 

326 tocsc.__doc__ = _spbase.tocsc.__doc__ 

327 

328 def tocoo(self, copy=False): 

329 num_rows, num_cols = self.shape 

330 num_offsets, offset_len = self.data.shape 

331 offset_inds = np.arange(offset_len) 

332 

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

334 mask = (row >= 0) 

335 mask &= (row < num_rows) 

336 mask &= (offset_inds < num_cols) 

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

338 row = row[mask] 

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

340 idx_dtype = self._get_index_dtype( 

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

342 ) 

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

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

345 data = self.data[mask] 

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

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

348 return self._coo_container( 

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

350 ) 

351 

352 tocoo.__doc__ = _spbase.tocoo.__doc__ 

353 

354 # needed by _data_matrix 

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

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

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

358 """ 

359 if copy: 

360 return self._dia_container( 

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

362 ) 

363 else: 

364 return self._dia_container( 

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

366 ) 

367 

368 def resize(self, *shape): 

369 shape = check_shape(shape) 

370 M, N = shape 

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

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

373 

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

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

376 # explicitly clear values that were previously hidden 

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

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

379 self.data[mask] = 0 

380 

381 self._shape = shape 

382 

383 resize.__doc__ = _spbase.resize.__doc__ 

384 

385 

386def isspmatrix_dia(x): 

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

388 

389 Parameters 

390 ---------- 

391 x 

392 object to check for being a dia matrix 

393 

394 Returns 

395 ------- 

396 bool 

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

398 

399 Examples 

400 -------- 

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

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

403 True 

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

405 False 

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

407 False 

408 """ 

409 return isinstance(x, dia_matrix) 

410 

411 

412# This namespace class separates array from matrix with isinstance 

413class dia_array(_dia_base, sparray): 

414 """ 

415 Sparse array with DIAgonal storage. 

416 

417 This can be instantiated in several ways: 

418 dia_array(D) 

419 where D is a 2-D ndarray 

420 

421 dia_array(S) 

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

423 

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

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

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

427 

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

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

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

431 

432 Attributes 

433 ---------- 

434 dtype : dtype 

435 Data type of the array 

436 shape : 2-tuple 

437 Shape of the array 

438 ndim : int 

439 Number of dimensions (this is always 2) 

440 nnz 

441 size 

442 data 

443 DIA format data array of the array 

444 offsets 

445 DIA format offset array of the array 

446 T 

447 

448 Notes 

449 ----- 

450 

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

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

453 

454 Examples 

455 -------- 

456 

457 >>> import numpy as np 

458 >>> from scipy.sparse import dia_array 

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

460 array([[0, 0, 0, 0], 

461 [0, 0, 0, 0], 

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

463 

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

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

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

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

468 [1, 2, 0, 4], 

469 [0, 2, 3, 0], 

470 [0, 0, 3, 4]]) 

471 

472 >>> from scipy.sparse import dia_array 

473 >>> n = 10 

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

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

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

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

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

479 [1., 2., 1., ..., 0., 0., 0.], 

480 [0., 1., 2., ..., 0., 0., 0.], 

481 ..., 

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

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

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

485 """ 

486 

487 

488class dia_matrix(spmatrix, _dia_base): 

489 """ 

490 Sparse matrix with DIAgonal storage. 

491 

492 This can be instantiated in several ways: 

493 dia_matrix(D) 

494 where D is a 2-D ndarray 

495 

496 dia_matrix(S) 

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

498 

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

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

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

502 

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

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

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

506 

507 Attributes 

508 ---------- 

509 dtype : dtype 

510 Data type of the matrix 

511 shape : 2-tuple 

512 Shape of the matrix 

513 ndim : int 

514 Number of dimensions (this is always 2) 

515 nnz 

516 size 

517 data 

518 DIA format data array of the matrix 

519 offsets 

520 DIA format offset array of the matrix 

521 T 

522 

523 Notes 

524 ----- 

525 

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

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

528 

529 Examples 

530 -------- 

531 

532 >>> import numpy as np 

533 >>> from scipy.sparse import dia_matrix 

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

535 array([[0, 0, 0, 0], 

536 [0, 0, 0, 0], 

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

538 

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

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

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

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

543 [1, 2, 0, 4], 

544 [0, 2, 3, 0], 

545 [0, 0, 3, 4]]) 

546 

547 >>> from scipy.sparse import dia_matrix 

548 >>> n = 10 

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

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

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

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

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

554 [1., 2., 1., ..., 0., 0., 0.], 

555 [0., 1., 2., ..., 0., 0., 0.], 

556 ..., 

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

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

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

560 """