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

241 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-23 06:43 +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 (isshape, upcast_char, getdtype, get_sum_dtype, validateaxis, check_shape) 

13from ._sparsetools import dia_matvec 

14 

15 

16class _dia_base(_data_matrix): 

17 _format = 'dia' 

18 

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

20 _data_matrix.__init__(self) 

21 

22 if issparse(arg1): 

23 if arg1.format == "dia": 

24 if copy: 

25 arg1 = arg1.copy() 

26 self.data = arg1.data 

27 self.offsets = arg1.offsets 

28 self._shape = check_shape(arg1.shape) 

29 else: 

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

31 A = arg1.copy() 

32 else: 

33 A = arg1.todia() 

34 self.data = A.data 

35 self.offsets = A.offsets 

36 self._shape = check_shape(A.shape) 

37 elif isinstance(arg1, tuple): 

38 if isshape(arg1): 

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

40 # create empty matrix 

41 self._shape = check_shape(arg1) 

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

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

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

45 else: 

46 try: 

47 # Try interpreting it as (data, offsets) 

48 data, offsets = arg1 

49 except Exception as e: 

50 raise ValueError('unrecognized form for dia_array constructor') from e 

51 else: 

52 if shape is None: 

53 raise ValueError('expected a shape argument') 

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

55 self.offsets = np.atleast_1d(np.array(arg1[1], 

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

57 copy=copy)) 

58 self._shape = check_shape(shape) 

59 else: 

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

61 try: 

62 arg1 = np.asarray(arg1) 

63 except Exception as e: 

64 raise ValueError("unrecognized form for" 

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

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

67 self.data = A.data 

68 self.offsets = A.offsets 

69 self._shape = check_shape(A.shape) 

70 

71 if dtype is not None: 

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

73 

74 #check format 

75 if self.offsets.ndim != 1: 

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

77 

78 if self.data.ndim != 2: 

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

80 

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

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

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

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

85 

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

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

88 

89 def __repr__(self): 

90 format = _formats[self.getformat()][1] 

91 return "<%dx%d sparse matrix of type '%s'\n" \ 

92 "\twith %d stored elements (%d diagonals) in %s format>" % \ 

93 (self.shape + (self.dtype.type, self.nnz, self.data.shape[0], 

94 format)) 

95 

96 def _data_mask(self): 

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

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

99 num_rows, num_cols = self.shape 

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

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

102 mask = (row >= 0) 

103 mask &= (row < num_rows) 

104 mask &= (offset_inds < num_cols) 

105 return mask 

106 

107 def count_nonzero(self): 

108 mask = self._data_mask() 

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

110 

111 def _getnnz(self, axis=None): 

112 if axis is not None: 

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

114 "for DIA format") 

115 M,N = self.shape 

116 nnz = 0 

117 for k in self.offsets: 

118 if k > 0: 

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

120 else: 

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

122 return int(nnz) 

123 

124 _getnnz.__doc__ = _spbase._getnnz.__doc__ 

125 count_nonzero.__doc__ = _spbase.count_nonzero.__doc__ 

126 

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

128 validateaxis(axis) 

129 

130 if axis is not None and axis < 0: 

131 axis += 2 

132 

133 res_dtype = get_sum_dtype(self.dtype) 

134 num_rows, num_cols = self.shape 

135 ret = None 

136 

137 if axis == 0: 

138 mask = self._data_mask() 

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

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

141 res = x 

142 else: 

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

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

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

146 

147 else: 

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

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

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

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

152 

153 row_sums = self._ascontainer(row_sums) 

154 

155 if axis is None: 

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

157 

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

159 

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

161 raise ValueError("dimensions do not match") 

162 

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

164 

165 sum.__doc__ = _spbase.sum.__doc__ 

166 

167 def _add_sparse(self, other): 

168 

169 # Check if other is also of type dia_array 

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

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

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

173 # method of parent class _spbase 

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

175 

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

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

178 # that should result from adding self and other 

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

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

181 

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

183 for d in other.offsets: 

184 # Check if the diagonal has already been added. 

185 if d in m.offsets: 

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

187 # the sum of the existing and the new 

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

189 else: 

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

191 return m 

192 

193 def _mul_vector(self, other): 

194 x = other 

195 

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

197 x.dtype.char)) 

198 

199 L = self.data.shape[1] 

200 

201 M,N = self.shape 

202 

203 dia_matvec(M,N, len(self.offsets), L, self.offsets, self.data, x.ravel(), y.ravel()) 

204 

205 return y 

206 

207 def _mul_multimatrix(self, other): 

208 return np.hstack([self._mul_vector(col).reshape(-1,1) for col in other.T]) 

209 

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

211 M, N = self.shape 

212 

213 if values.ndim == 0: 

214 # broadcast 

215 values_n = np.inf 

216 else: 

217 values_n = len(values) 

218 

219 if k < 0: 

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

221 min_index = 0 

222 max_index = n 

223 else: 

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

225 min_index = k 

226 max_index = k + n 

227 

228 if values.ndim != 0: 

229 # allow also longer sequences 

230 values = values[:n] 

231 

232 data_rows, data_cols = self.data.shape 

233 if k in self.offsets: 

234 if max_index > data_cols: 

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

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

237 self.data = data 

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

239 else: 

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

241 m = max(max_index, data_cols) 

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

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

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

245 self.data = data 

246 

247 def todia(self, copy=False): 

248 if copy: 

249 return self.copy() 

250 else: 

251 return self 

252 

253 todia.__doc__ = _spbase.todia.__doc__ 

254 

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

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

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

258 "an 'axes' parameter because swapping " 

259 "dimensions is the only logical permutation.") 

260 

261 num_rows, num_cols = self.shape 

262 max_dim = max(self.shape) 

263 

264 # flip diagonal offsets 

265 offsets = -self.offsets 

266 

267 # re-align the data matrix 

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

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

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

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

272 dtype=self.data.dtype))) 

273 data = data[r, c] 

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

275 num_cols, num_rows), copy=copy) 

276 

277 transpose.__doc__ = _spbase.transpose.__doc__ 

278 

279 def diagonal(self, k=0): 

280 rows, cols = self.shape 

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

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

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

284 first_col = max(0, k) 

285 last_col = min(rows + k, cols) 

286 result_size = last_col - first_col 

287 if idx.size == 0: 

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

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

290 padding = result_size - len(result) 

291 if padding > 0: 

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

293 return result 

294 

295 diagonal.__doc__ = _spbase.diagonal.__doc__ 

296 

297 def tocsc(self, copy=False): 

298 if self.nnz == 0: 

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

300 

301 num_rows, num_cols = self.shape 

302 num_offsets, offset_len = self.data.shape 

303 offset_inds = np.arange(offset_len) 

304 

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

306 mask = (row >= 0) 

307 mask &= (row < num_rows) 

308 mask &= (offset_inds < num_cols) 

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

310 

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

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

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

314 if offset_len < num_cols: 

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

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

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

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

319 dtype=self.dtype) 

320 

321 tocsc.__doc__ = _spbase.tocsc.__doc__ 

322 

323 def tocoo(self, copy=False): 

324 num_rows, num_cols = self.shape 

325 num_offsets, offset_len = self.data.shape 

326 offset_inds = np.arange(offset_len) 

327 

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

329 mask = (row >= 0) 

330 mask &= (row < num_rows) 

331 mask &= (offset_inds < num_cols) 

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

333 row = row[mask] 

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

335 data = self.data[mask] 

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

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

338 return self._coo_container( 

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

340 ) 

341 

342 tocoo.__doc__ = _spbase.tocoo.__doc__ 

343 

344 # needed by _data_matrix 

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

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

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

348 """ 

349 if copy: 

350 return self._dia_container( 

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

352 ) 

353 else: 

354 return self._dia_container( 

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

356 ) 

357 

358 def resize(self, *shape): 

359 shape = check_shape(shape) 

360 M, N = shape 

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

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

363 

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

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

366 # explicitly clear values that were previously hidden 

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

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

369 self.data[mask] = 0 

370 

371 self._shape = shape 

372 

373 resize.__doc__ = _spbase.resize.__doc__ 

374 

375 

376def isspmatrix_dia(x): 

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

378 

379 Parameters 

380 ---------- 

381 x 

382 object to check for being a dia matrix 

383 

384 Returns 

385 ------- 

386 bool 

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

388 

389 Examples 

390 -------- 

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

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

393 True 

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

395 False 

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

397 False 

398 """ 

399 return isinstance(x, dia_matrix) 

400 

401 

402# This namespace class separates array from matrix with isinstance 

403class dia_array(_dia_base, sparray): 

404 """ 

405 Sparse array with DIAgonal storage. 

406 

407 This can be instantiated in several ways: 

408 dia_array(D) 

409 where D is a 2-D ndarray 

410 

411 dia_array(S) 

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

413 

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

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

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

417 

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

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

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

421 

422 Attributes 

423 ---------- 

424 dtype : dtype 

425 Data type of the array 

426 shape : 2-tuple 

427 Shape of the array 

428 ndim : int 

429 Number of dimensions (this is always 2) 

430 nnz 

431 size 

432 data 

433 DIA format data array of the array 

434 offsets 

435 DIA format offset array of the array 

436 T 

437 

438 Notes 

439 ----- 

440 

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

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

443 

444 Examples 

445 -------- 

446 

447 >>> import numpy as np 

448 >>> from scipy.sparse import dia_array 

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

450 array([[0, 0, 0, 0], 

451 [0, 0, 0, 0], 

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

453 

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

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

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

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

458 [1, 2, 0, 4], 

459 [0, 2, 3, 0], 

460 [0, 0, 3, 4]]) 

461 

462 >>> from scipy.sparse import dia_array 

463 >>> n = 10 

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

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

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

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

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

469 [1., 2., 1., ..., 0., 0., 0.], 

470 [0., 1., 2., ..., 0., 0., 0.], 

471 ..., 

472 [0., 0., 0., ..., 2., 1., 0.], 

473 [0., 0., 0., ..., 1., 2., 1.], 

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

475 """ 

476 

477 

478class dia_matrix(spmatrix, _dia_base): 

479 """ 

480 Sparse matrix with DIAgonal storage. 

481 

482 This can be instantiated in several ways: 

483 dia_matrix(D) 

484 where D is a 2-D ndarray 

485 

486 dia_matrix(S) 

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

488 

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

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

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

492 

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

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

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

496 

497 Attributes 

498 ---------- 

499 dtype : dtype 

500 Data type of the matrix 

501 shape : 2-tuple 

502 Shape of the matrix 

503 ndim : int 

504 Number of dimensions (this is always 2) 

505 nnz 

506 size 

507 data 

508 DIA format data array of the matrix 

509 offsets 

510 DIA format offset array of the matrix 

511 T 

512 

513 Notes 

514 ----- 

515 

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

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

518 

519 Examples 

520 -------- 

521 

522 >>> import numpy as np 

523 >>> from scipy.sparse import dia_matrix 

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

525 array([[0, 0, 0, 0], 

526 [0, 0, 0, 0], 

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

528 

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

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

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

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

533 [1, 2, 0, 4], 

534 [0, 2, 3, 0], 

535 [0, 0, 3, 4]]) 

536 

537 >>> from scipy.sparse import dia_matrix 

538 >>> n = 10 

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

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

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

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

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

544 [1., 2., 1., ..., 0., 0., 0.], 

545 [0., 1., 2., ..., 0., 0., 0.], 

546 ..., 

547 [0., 0., 0., ..., 2., 1., 0.], 

548 [0., 0., 0., ..., 1., 2., 1.], 

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

550 """