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

286 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-23 06:43 +0000

1"""Dictionary Of Keys based matrix""" 

2 

3__docformat__ = "restructuredtext en" 

4 

5__all__ = ['dok_array', 'dok_matrix', 'isspmatrix_dok'] 

6 

7import itertools 

8import numpy as np 

9 

10from ._matrix import spmatrix 

11from ._base import _spbase, sparray, issparse 

12from ._index import IndexMixin 

13from ._sputils import (isdense, getdtype, isshape, isintlike, isscalarlike, 

14 upcast, upcast_scalar, check_shape) 

15 

16 

17class _dok_base(_spbase, IndexMixin): 

18 _format = 'dok' 

19 

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

21 _spbase.__init__(self) 

22 self._dict = {} 

23 

24 self.dtype = getdtype(dtype, default=float) 

25 if isinstance(arg1, tuple) and isshape(arg1): # (M,N) 

26 M, N = arg1 

27 self._shape = check_shape((M, N)) 

28 elif issparse(arg1): # Sparse ctor 

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

30 arg1 = arg1.copy() 

31 else: 

32 arg1 = arg1.todok() 

33 

34 if dtype is not None: 

35 arg1 = arg1.astype(dtype, copy=False) 

36 

37 self._dict.update(arg1) 

38 self._shape = check_shape(arg1.shape) 

39 self.dtype = arg1.dtype 

40 else: # Dense ctor 

41 try: 

42 arg1 = np.asarray(arg1) 

43 except Exception as e: 

44 raise TypeError('Invalid input format.') from e 

45 

46 if len(arg1.shape) != 2: 

47 raise TypeError('Expected rank <=2 dense array or matrix.') 

48 

49 d = self._coo_container(arg1, dtype=dtype).todok() 

50 self._dict.update(d) 

51 self._shape = check_shape(arg1.shape) 

52 self.dtype = d.dtype 

53 

54 def update(self, val): 

55 # Prevent direct usage of update 

56 raise NotImplementedError("Direct modification to dok_array element " 

57 "is not allowed.") 

58 

59 def _update(self, data): 

60 """An update method for dict data defined for direct access to 

61 `dok_array` data. Main purpose is to be used for effcient conversion 

62 from other _spbase classes. Has no checking if `data` is valid.""" 

63 return self._dict.update(data) 

64 

65 def _getnnz(self, axis=None): 

66 if axis is not None: 

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

68 "for DOK format.") 

69 return len(self._dict) 

70 

71 def count_nonzero(self): 

72 return sum(x != 0 for x in self.values()) 

73 

74 _getnnz.__doc__ = _spbase._getnnz.__doc__ 

75 count_nonzero.__doc__ = _spbase.count_nonzero.__doc__ 

76 

77 def __len__(self): 

78 return len(self._dict) 

79 

80 def __contains__(self, key): 

81 return key in self._dict 

82 

83 def setdefault(self, key, default=None, /): 

84 return self._dict.setdefault(key, default) 

85 

86 def __delitem__(self, key, /): 

87 del self._dict[key] 

88 

89 def clear(self): 

90 return self._dict.clear() 

91 

92 def popitem(self): 

93 return self._dict.popitem() 

94 

95 def items(self): 

96 return self._dict.items() 

97 

98 def keys(self): 

99 return self._dict.keys() 

100 

101 def values(self): 

102 return self._dict.values() 

103 

104 def get(self, key, default=0.): 

105 """This overrides the dict.get method, providing type checking 

106 but otherwise equivalent functionality. 

107 """ 

108 try: 

109 i, j = key 

110 assert isintlike(i) and isintlike(j) 

111 except (AssertionError, TypeError, ValueError) as e: 

112 raise IndexError('Index must be a pair of integers.') from e 

113 if (i < 0 or i >= self.shape[0] or j < 0 or j >= self.shape[1]): 

114 raise IndexError('Index out of bounds.') 

115 return self._dict.get(key, default) 

116 

117 def _get_intXint(self, row, col): 

118 return self._dict.get((row, col), self.dtype.type(0)) 

119 

120 def _get_intXslice(self, row, col): 

121 return self._get_sliceXslice(slice(row, row+1), col) 

122 

123 def _get_sliceXint(self, row, col): 

124 return self._get_sliceXslice(row, slice(col, col+1)) 

125 

126 def _get_sliceXslice(self, row, col): 

127 row_start, row_stop, row_step = row.indices(self.shape[0]) 

128 col_start, col_stop, col_step = col.indices(self.shape[1]) 

129 row_range = range(row_start, row_stop, row_step) 

130 col_range = range(col_start, col_stop, col_step) 

131 shape = (len(row_range), len(col_range)) 

132 # Switch paths only when advantageous 

133 # (count the iterations in the loops, adjust for complexity) 

134 if len(self) >= 2 * shape[0] * shape[1]: 

135 # O(nr*nc) path: loop over <row x col> 

136 return self._get_columnXarray(row_range, col_range) 

137 # O(nnz) path: loop over entries of self 

138 newdok = self._dok_container(shape, dtype=self.dtype) 

139 for key in self.keys(): 

140 i, ri = divmod(int(key[0]) - row_start, row_step) 

141 if ri != 0 or i < 0 or i >= shape[0]: 

142 continue 

143 j, rj = divmod(int(key[1]) - col_start, col_step) 

144 if rj != 0 or j < 0 or j >= shape[1]: 

145 continue 

146 newdok._dict[i, j] = self._dict[key] 

147 return newdok 

148 

149 def _get_intXarray(self, row, col): 

150 col = col.squeeze() 

151 return self._get_columnXarray([row], col) 

152 

153 def _get_arrayXint(self, row, col): 

154 row = row.squeeze() 

155 return self._get_columnXarray(row, [col]) 

156 

157 def _get_sliceXarray(self, row, col): 

158 row = list(range(*row.indices(self.shape[0]))) 

159 return self._get_columnXarray(row, col) 

160 

161 def _get_arrayXslice(self, row, col): 

162 col = list(range(*col.indices(self.shape[1]))) 

163 return self._get_columnXarray(row, col) 

164 

165 def _get_columnXarray(self, row, col): 

166 # outer indexing 

167 newdok = self._dok_container((len(row), len(col)), dtype=self.dtype) 

168 

169 for i, r in enumerate(row): 

170 for j, c in enumerate(col): 

171 v = self._dict.get((r, c), 0) 

172 if v: 

173 newdok._dict[i, j] = v 

174 return newdok 

175 

176 def _get_arrayXarray(self, row, col): 

177 # inner indexing 

178 i, j = map(np.atleast_2d, np.broadcast_arrays(row, col)) 

179 newdok = self._dok_container(i.shape, dtype=self.dtype) 

180 

181 for key in itertools.product(range(i.shape[0]), range(i.shape[1])): 

182 v = self._dict.get((i[key], j[key]), 0) 

183 if v: 

184 newdok._dict[key] = v 

185 return newdok 

186 

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

188 key = (row, col) 

189 if x: 

190 self._dict[key] = x 

191 elif key in self._dict: 

192 del self._dict[key] 

193 

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

195 row = list(map(int, row.ravel())) 

196 col = list(map(int, col.ravel())) 

197 x = x.ravel() 

198 self._dict.update(zip(zip(row, col), x)) 

199 

200 for i in np.nonzero(x == 0)[0]: 

201 key = (row[i], col[i]) 

202 if self._dict[key] == 0: 

203 # may have been superseded by later update 

204 del self._dict[key] 

205 

206 def __add__(self, other): 

207 if isscalarlike(other): 

208 res_dtype = upcast_scalar(self.dtype, other) 

209 new = self._dok_container(self.shape, dtype=res_dtype) 

210 # Add this scalar to every element. 

211 M, N = self.shape 

212 for key in itertools.product(range(M), range(N)): 

213 aij = self._dict.get(key, 0) + other 

214 if aij: 

215 new[key] = aij 

216 # new.dtype.char = self.dtype.char 

217 elif issparse(other): 

218 if other.format == "dok": 

219 if other.shape != self.shape: 

220 raise ValueError("Matrix dimensions are not equal.") 

221 # We could alternatively set the dimensions to the largest of 

222 # the two matrices to be summed. Would this be a good idea? 

223 res_dtype = upcast(self.dtype, other.dtype) 

224 new = self._dok_container(self.shape, dtype=res_dtype) 

225 new._dict.update(self._dict) 

226 with np.errstate(over='ignore'): 

227 new._dict.update((k, new[k] + other[k]) for k in other.keys()) 

228 else: 

229 csc = self.tocsc() 

230 new = csc + other 

231 elif isdense(other): 

232 new = self.todense() + other 

233 else: 

234 return NotImplemented 

235 return new 

236 

237 def __radd__(self, other): 

238 if isscalarlike(other): 

239 new = self._dok_container(self.shape, dtype=self.dtype) 

240 M, N = self.shape 

241 for key in itertools.product(range(M), range(N)): 

242 aij = self._dict.get(key, 0) + other 

243 if aij: 

244 new[key] = aij 

245 elif issparse(other): 

246 if other.format == "dok": 

247 if other.shape != self.shape: 

248 raise ValueError("Matrix dimensions are not equal.") 

249 new = self._dok_container(self.shape, dtype=self.dtype) 

250 new._dict.update(self._dict) 

251 new._dict.update((k, self[k] + other[k]) for k in other) 

252 else: 

253 csc = self.tocsc() 

254 new = csc + other 

255 elif isdense(other): 

256 new = other + self.todense() 

257 else: 

258 return NotImplemented 

259 return new 

260 

261 def __neg__(self): 

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

263 raise NotImplementedError('Negating a sparse boolean matrix is not' 

264 ' supported.') 

265 new = self._dok_container(self.shape, dtype=self.dtype) 

266 new._dict.update((k, -self[k]) for k in self.keys()) 

267 return new 

268 

269 def _mul_scalar(self, other): 

270 res_dtype = upcast_scalar(self.dtype, other) 

271 # Multiply this scalar by every element. 

272 new = self._dok_container(self.shape, dtype=res_dtype) 

273 new._dict.update(((k, v * other) for k, v in self.items())) 

274 return new 

275 

276 def _mul_vector(self, other): 

277 # matrix * vector 

278 result = np.zeros(self.shape[0], dtype=upcast(self.dtype, other.dtype)) 

279 for (i, j), v in self.items(): 

280 result[i] += v * other[j] 

281 return result 

282 

283 def _mul_multivector(self, other): 

284 # matrix * multivector 

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

286 result_dtype = upcast(self.dtype, other.dtype) 

287 result = np.zeros(result_shape, dtype=result_dtype) 

288 for (i, j), v in self.items(): 

289 result[i,:] += v * other[j,:] 

290 return result 

291 

292 def __imul__(self, other): 

293 if isscalarlike(other): 

294 self._dict.update((k, v * other) for k, v in self.items()) 

295 return self 

296 return NotImplemented 

297 

298 def __truediv__(self, other): 

299 if isscalarlike(other): 

300 res_dtype = upcast_scalar(self.dtype, other) 

301 new = self._dok_container(self.shape, dtype=res_dtype) 

302 new._dict.update(((k, v / other) for k, v in self.items())) 

303 return new 

304 return self.tocsr() / other 

305 

306 def __itruediv__(self, other): 

307 if isscalarlike(other): 

308 self._dict.update((k, v / other) for k, v in self.items()) 

309 return self 

310 return NotImplemented 

311 

312 def __reduce__(self): 

313 # this approach is necessary because __setstate__ is called after 

314 # __setitem__ upon unpickling and since __init__ is not called there 

315 # is no shape attribute hence it is not possible to unpickle it. 

316 return dict.__reduce__(self) 

317 

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

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

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

321 "an 'axes' parameter because swapping " 

322 "dimensions is the only logical permutation.") 

323 

324 M, N = self.shape 

325 new = self._dok_container((N, M), dtype=self.dtype, copy=copy) 

326 new._dict.update((((right, left), val) 

327 for (left, right), val in self.items())) 

328 return new 

329 

330 transpose.__doc__ = _spbase.transpose.__doc__ 

331 

332 def conjtransp(self): 

333 """Return the conjugate transpose.""" 

334 M, N = self.shape 

335 new = self._dok_container((N, M), dtype=self.dtype) 

336 new._dict.update((((right, left), np.conj(val)) 

337 for (left, right), val in self.items())) 

338 return new 

339 

340 def copy(self): 

341 new = self._dok_container(self.shape, dtype=self.dtype) 

342 new._dict.update(self._dict) 

343 return new 

344 

345 copy.__doc__ = _spbase.copy.__doc__ 

346 

347 def tocoo(self, copy=False): 

348 if self.nnz == 0: 

349 return self._coo_container(self.shape, dtype=self.dtype) 

350 

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

352 data = np.fromiter(self.values(), dtype=self.dtype, count=self.nnz) 

353 row = np.fromiter((i for i, _ in self.keys()), dtype=idx_dtype, count=self.nnz) 

354 col = np.fromiter((j for _, j in self.keys()), dtype=idx_dtype, count=self.nnz) 

355 A = self._coo_container( 

356 (data, (row, col)), shape=self.shape, dtype=self.dtype 

357 ) 

358 A.has_canonical_format = True 

359 return A 

360 

361 tocoo.__doc__ = _spbase.tocoo.__doc__ 

362 

363 def todok(self, copy=False): 

364 if copy: 

365 return self.copy() 

366 return self 

367 

368 todok.__doc__ = _spbase.todok.__doc__ 

369 

370 def tocsc(self, copy=False): 

371 return self.tocoo(copy=False).tocsc(copy=copy) 

372 

373 tocsc.__doc__ = _spbase.tocsc.__doc__ 

374 

375 def resize(self, *shape): 

376 shape = check_shape(shape) 

377 newM, newN = shape 

378 M, N = self.shape 

379 if newM < M or newN < N: 

380 # Remove all elements outside new dimensions 

381 for (i, j) in list(self.keys()): 

382 if i >= newM or j >= newN: 

383 del self._dict[i, j] 

384 self._shape = shape 

385 

386 resize.__doc__ = _spbase.resize.__doc__ 

387 

388 

389def isspmatrix_dok(x): 

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

391 

392 Parameters 

393 ---------- 

394 x 

395 object to check for being a dok matrix 

396 

397 Returns 

398 ------- 

399 bool 

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

401 

402 Examples 

403 -------- 

404 >>> from scipy.sparse import dok_array, dok_matrix, coo_matrix, isspmatrix_dok 

405 >>> isspmatrix_dok(dok_matrix([[5]])) 

406 True 

407 >>> isspmatrix_dok(dok_array([[5]])) 

408 False 

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

410 False 

411 """ 

412 return isinstance(x, dok_matrix) 

413 

414 

415# This namespace class separates array from matrix with isinstance 

416class dok_array(_dok_base, sparray): 

417 """ 

418 Dictionary Of Keys based sparse array. 

419 

420 This is an efficient structure for constructing sparse 

421 arrays incrementally. 

422 

423 This can be instantiated in several ways: 

424 dok_array(D) 

425 where D is a 2-D ndarray 

426 

427 dok_array(S) 

428 with another sparse array or matrix S (equivalent to S.todok()) 

429 

430 dok_array((M,N), [dtype]) 

431 create the array with initial shape (M,N) 

432 dtype is optional, defaulting to dtype='d' 

433 

434 Attributes 

435 ---------- 

436 dtype : dtype 

437 Data type of the array 

438 shape : 2-tuple 

439 Shape of the array 

440 ndim : int 

441 Number of dimensions (this is always 2) 

442 nnz 

443 Number of nonzero elements 

444 size 

445 T 

446 

447 Notes 

448 ----- 

449 

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

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

452 

453 - Allows for efficient O(1) access of individual elements. 

454 - Duplicates are not allowed. 

455 - Can be efficiently converted to a coo_array once constructed. 

456 

457 Examples 

458 -------- 

459 >>> import numpy as np 

460 >>> from scipy.sparse import dok_array 

461 >>> S = dok_array((5, 5), dtype=np.float32) 

462 >>> for i in range(5): 

463 ... for j in range(5): 

464 ... S[i, j] = i + j # Update element 

465 

466 """ 

467 

468 

469class dok_matrix(spmatrix, _dok_base, dict): 

470 """ 

471 Dictionary Of Keys based sparse matrix. 

472 

473 This is an efficient structure for constructing sparse 

474 matrices incrementally. 

475 

476 This can be instantiated in several ways: 

477 dok_matrix(D) 

478 where D is a 2-D ndarray 

479 

480 dok_matrix(S) 

481 with another sparse array or matrix S (equivalent to S.todok()) 

482 

483 dok_matrix((M,N), [dtype]) 

484 create the matrix with initial shape (M,N) 

485 dtype is optional, defaulting to dtype='d' 

486 

487 Attributes 

488 ---------- 

489 dtype : dtype 

490 Data type of the matrix 

491 shape : 2-tuple 

492 Shape of the matrix 

493 ndim : int 

494 Number of dimensions (this is always 2) 

495 nnz 

496 Number of nonzero elements 

497 size 

498 T 

499 

500 Notes 

501 ----- 

502 

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

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

505 

506 - Allows for efficient O(1) access of individual elements. 

507 - Duplicates are not allowed. 

508 - Can be efficiently converted to a coo_matrix once constructed. 

509 

510 Examples 

511 -------- 

512 >>> import numpy as np 

513 >>> from scipy.sparse import dok_matrix 

514 >>> S = dok_matrix((5, 5), dtype=np.float32) 

515 >>> for i in range(5): 

516 ... for j in range(5): 

517 ... S[i, j] = i + j # Update element 

518 

519 """ 

520 def set_shape(self, shape): 

521 new_matrix = self.reshape(shape, copy=False).asformat(self.format) 

522 self.__dict__ = new_matrix.__dict__ 

523 

524 def get_shape(self): 

525 """Get shape of a sparse matrix.""" 

526 return self._shape 

527 

528 shape = property(fget=get_shape, fset=set_shape)