Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/scipy/sparse/_sputils.py: 34%

188 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-12 06:31 +0000

1""" Utility functions for sparse matrix module 

2""" 

3 

4import sys 

5import operator 

6import numpy as np 

7from scipy._lib._util import prod 

8import scipy.sparse as sp 

9 

10 

11__all__ = ['upcast', 'getdtype', 'getdata', 'isscalarlike', 'isintlike', 

12 'isshape', 'issequence', 'isdense', 'ismatrix', 'get_sum_dtype'] 

13 

14supported_dtypes = [np.bool_, np.byte, np.ubyte, np.short, np.ushort, np.intc, 

15 np.uintc, np.int_, np.uint, np.longlong, np.ulonglong, 

16 np.single, np.double, 

17 np.longdouble, np.csingle, np.cdouble, np.clongdouble] 

18 

19_upcast_memo = {} 

20 

21 

22def upcast(*args): 

23 """Returns the nearest supported sparse dtype for the 

24 combination of one or more types. 

25 

26 upcast(t0, t1, ..., tn) -> T where T is a supported dtype 

27 

28 Examples 

29 -------- 

30 

31 >>> upcast('int32') 

32 <type 'numpy.int32'> 

33 >>> upcast('bool') 

34 <type 'numpy.bool_'> 

35 >>> upcast('int32','float32') 

36 <type 'numpy.float64'> 

37 >>> upcast('bool',complex,float) 

38 <type 'numpy.complex128'> 

39 

40 """ 

41 

42 t = _upcast_memo.get(hash(args)) 

43 if t is not None: 

44 return t 

45 

46 upcast = np.result_type(*args) 

47 

48 for t in supported_dtypes: 

49 if np.can_cast(upcast, t): 

50 _upcast_memo[hash(args)] = t 

51 return t 

52 

53 raise TypeError('no supported conversion for types: %r' % (args,)) 

54 

55 

56def upcast_char(*args): 

57 """Same as `upcast` but taking dtype.char as input (faster).""" 

58 t = _upcast_memo.get(args) 

59 if t is not None: 

60 return t 

61 t = upcast(*map(np.dtype, args)) 

62 _upcast_memo[args] = t 

63 return t 

64 

65 

66def upcast_scalar(dtype, scalar): 

67 """Determine data type for binary operation between an array of 

68 type `dtype` and a scalar. 

69 """ 

70 return (np.array([0], dtype=dtype) * scalar).dtype 

71 

72 

73def downcast_intp_index(arr): 

74 """ 

75 Down-cast index array to np.intp dtype if it is of a larger dtype. 

76 

77 Raise an error if the array contains a value that is too large for 

78 intp. 

79 """ 

80 if arr.dtype.itemsize > np.dtype(np.intp).itemsize: 

81 if arr.size == 0: 

82 return arr.astype(np.intp) 

83 maxval = arr.max() 

84 minval = arr.min() 

85 if maxval > np.iinfo(np.intp).max or minval < np.iinfo(np.intp).min: 

86 raise ValueError("Cannot deal with arrays with indices larger " 

87 "than the machine maximum address size " 

88 "(e.g. 64-bit indices on 32-bit machine).") 

89 return arr.astype(np.intp) 

90 return arr 

91 

92 

93def to_native(A): 

94 """ 

95 Ensure that the data type of the NumPy array `A` has native byte order. 

96 

97 `A` must be a NumPy array. If the data type of `A` does not have native 

98 byte order, a copy of `A` with a native byte order is returned. Otherwise 

99 `A` is returned. 

100 """ 

101 dt = A.dtype 

102 if dt.isnative: 

103 # Don't call `asarray()` if A is already native, to avoid unnecessarily 

104 # creating a view of the input array. 

105 return A 

106 return np.asarray(A, dtype=dt.newbyteorder('native')) 

107 

108 

109def getdtype(dtype, a=None, default=None): 

110 """Function used to simplify argument processing. If 'dtype' is not 

111 specified (is None), returns a.dtype; otherwise returns a np.dtype 

112 object created from the specified dtype argument. If 'dtype' and 'a' 

113 are both None, construct a data type out of the 'default' parameter. 

114 Furthermore, 'dtype' must be in 'allowed' set. 

115 """ 

116 # TODO is this really what we want? 

117 if dtype is None: 

118 try: 

119 newdtype = a.dtype 

120 except AttributeError as e: 

121 if default is not None: 

122 newdtype = np.dtype(default) 

123 else: 

124 raise TypeError("could not interpret data type") from e 

125 else: 

126 newdtype = np.dtype(dtype) 

127 if newdtype == np.object_: 

128 raise ValueError( 

129 "object dtype is not supported by sparse matrices" 

130 ) 

131 

132 return newdtype 

133 

134 

135def getdata(obj, dtype=None, copy=False): 

136 """ 

137 This is a wrapper of `np.array(obj, dtype=dtype, copy=copy)` 

138 that will generate a warning if the result is an object array. 

139 """ 

140 data = np.array(obj, dtype=dtype, copy=copy) 

141 # Defer to getdtype for checking that the dtype is OK. 

142 # This is called for the validation only; we don't need the return value. 

143 getdtype(data.dtype) 

144 return data 

145 

146 

147def get_index_dtype(arrays=(), maxval=None, check_contents=False): 

148 """ 

149 Based on input (integer) arrays `a`, determine a suitable index data 

150 type that can hold the data in the arrays. 

151 

152 Parameters 

153 ---------- 

154 arrays : tuple of array_like 

155 Input arrays whose types/contents to check 

156 maxval : float, optional 

157 Maximum value needed 

158 check_contents : bool, optional 

159 Whether to check the values in the arrays and not just their types. 

160 Default: False (check only the types) 

161 

162 Returns 

163 ------- 

164 dtype : dtype 

165 Suitable index data type (int32 or int64) 

166 

167 """ 

168 

169 int32min = np.int32(np.iinfo(np.int32).min) 

170 int32max = np.int32(np.iinfo(np.int32).max) 

171 

172 # not using intc directly due to misinteractions with pythran 

173 dtype = np.int32 if np.intc().itemsize == 4 else np.int64 

174 if maxval is not None: 

175 maxval = np.int64(maxval) 

176 if maxval > int32max: 

177 dtype = np.int64 

178 

179 if isinstance(arrays, np.ndarray): 

180 arrays = (arrays,) 

181 

182 for arr in arrays: 

183 arr = np.asarray(arr) 

184 if not np.can_cast(arr.dtype, np.int32): 

185 if check_contents: 

186 if arr.size == 0: 

187 # a bigger type not needed 

188 continue 

189 elif np.issubdtype(arr.dtype, np.integer): 

190 maxval = arr.max() 

191 minval = arr.min() 

192 if minval >= int32min and maxval <= int32max: 

193 # a bigger type not needed 

194 continue 

195 

196 dtype = np.int64 

197 break 

198 

199 return dtype 

200 

201 

202def get_sum_dtype(dtype): 

203 """Mimic numpy's casting for np.sum""" 

204 if dtype.kind == 'u' and np.can_cast(dtype, np.uint): 

205 return np.uint 

206 if np.can_cast(dtype, np.int_): 

207 return np.int_ 

208 return dtype 

209 

210 

211def isscalarlike(x): 

212 """Is x either a scalar, an array scalar, or a 0-dim array?""" 

213 return np.isscalar(x) or (isdense(x) and x.ndim == 0) 

214 

215 

216def isintlike(x): 

217 """Is x appropriate as an index into a sparse matrix? Returns True 

218 if it can be cast safely to a machine int. 

219 """ 

220 # Fast-path check to eliminate non-scalar values. operator.index would 

221 # catch this case too, but the exception catching is slow. 

222 if np.ndim(x) != 0: 

223 return False 

224 try: 

225 operator.index(x) 

226 except (TypeError, ValueError): 

227 try: 

228 loose_int = bool(int(x) == x) 

229 except (TypeError, ValueError): 

230 return False 

231 if loose_int: 

232 msg = "Inexact indices into sparse matrices are not allowed" 

233 raise ValueError(msg) 

234 return loose_int 

235 return True 

236 

237 

238def isshape(x, nonneg=False): 

239 """Is x a valid 2-tuple of dimensions? 

240 

241 If nonneg, also checks that the dimensions are non-negative. 

242 """ 

243 try: 

244 # Assume it's a tuple of matrix dimensions (M, N) 

245 (M, N) = x 

246 except Exception: 

247 return False 

248 else: 

249 if isintlike(M) and isintlike(N): 

250 if np.ndim(M) == 0 and np.ndim(N) == 0: 

251 if not nonneg or (M >= 0 and N >= 0): 

252 return True 

253 return False 

254 

255 

256def issequence(t): 

257 return ((isinstance(t, (list, tuple)) and 

258 (len(t) == 0 or np.isscalar(t[0]))) or 

259 (isinstance(t, np.ndarray) and (t.ndim == 1))) 

260 

261 

262def ismatrix(t): 

263 return ((isinstance(t, (list, tuple)) and 

264 len(t) > 0 and issequence(t[0])) or 

265 (isinstance(t, np.ndarray) and t.ndim == 2)) 

266 

267 

268def isdense(x): 

269 return isinstance(x, np.ndarray) 

270 

271 

272def validateaxis(axis): 

273 if axis is not None: 

274 axis_type = type(axis) 

275 

276 # In NumPy, you can pass in tuples for 'axis', but they are 

277 # not very useful for sparse matrices given their limited 

278 # dimensions, so let's make it explicit that they are not 

279 # allowed to be passed in 

280 if axis_type == tuple: 

281 raise TypeError(("Tuples are not accepted for the 'axis' " 

282 "parameter. Please pass in one of the " 

283 "following: {-2, -1, 0, 1, None}.")) 

284 

285 # If not a tuple, check that the provided axis is actually 

286 # an integer and raise a TypeError similar to NumPy's 

287 if not np.issubdtype(np.dtype(axis_type), np.integer): 

288 raise TypeError("axis must be an integer, not {name}" 

289 .format(name=axis_type.__name__)) 

290 

291 if not (-2 <= axis <= 1): 

292 raise ValueError("axis out of range") 

293 

294 

295def check_shape(args, current_shape=None): 

296 """Imitate numpy.matrix handling of shape arguments""" 

297 if len(args) == 0: 

298 raise TypeError("function missing 1 required positional argument: " 

299 "'shape'") 

300 elif len(args) == 1: 

301 try: 

302 shape_iter = iter(args[0]) 

303 except TypeError: 

304 new_shape = (operator.index(args[0]), ) 

305 else: 

306 new_shape = tuple(operator.index(arg) for arg in shape_iter) 

307 else: 

308 new_shape = tuple(operator.index(arg) for arg in args) 

309 

310 if current_shape is None: 

311 if len(new_shape) != 2: 

312 raise ValueError('shape must be a 2-tuple of positive integers') 

313 elif any(d < 0 for d in new_shape): 

314 raise ValueError("'shape' elements cannot be negative") 

315 

316 else: 

317 # Check the current size only if needed 

318 current_size = prod(current_shape) 

319 

320 # Check for negatives 

321 negative_indexes = [i for i, x in enumerate(new_shape) if x < 0] 

322 if len(negative_indexes) == 0: 

323 new_size = prod(new_shape) 

324 if new_size != current_size: 

325 raise ValueError('cannot reshape array of size {} into shape {}' 

326 .format(current_size, new_shape)) 

327 elif len(negative_indexes) == 1: 

328 skip = negative_indexes[0] 

329 specified = prod(new_shape[0:skip] + new_shape[skip+1:]) 

330 unspecified, remainder = divmod(current_size, specified) 

331 if remainder != 0: 

332 err_shape = tuple('newshape' if x < 0 else x for x in new_shape) 

333 raise ValueError('cannot reshape array of size {} into shape {}' 

334 ''.format(current_size, err_shape)) 

335 new_shape = new_shape[0:skip] + (unspecified,) + new_shape[skip+1:] 

336 else: 

337 raise ValueError('can only specify one unknown dimension') 

338 

339 if len(new_shape) != 2: 

340 raise ValueError('matrix shape must be two-dimensional') 

341 

342 return new_shape 

343 

344 

345def check_reshape_kwargs(kwargs): 

346 """Unpack keyword arguments for reshape function. 

347 

348 This is useful because keyword arguments after star arguments are not 

349 allowed in Python 2, but star keyword arguments are. This function unpacks 

350 'order' and 'copy' from the star keyword arguments (with defaults) and 

351 throws an error for any remaining. 

352 """ 

353 

354 order = kwargs.pop('order', 'C') 

355 copy = kwargs.pop('copy', False) 

356 if kwargs: # Some unused kwargs remain 

357 raise TypeError('reshape() got unexpected keywords arguments: {}' 

358 .format(', '.join(kwargs.keys()))) 

359 return order, copy 

360 

361 

362def is_pydata_spmatrix(m): 

363 """ 

364 Check whether object is pydata/sparse matrix, avoiding importing the module. 

365 """ 

366 base_cls = getattr(sys.modules.get('sparse'), 'SparseArray', None) 

367 return base_cls is not None and isinstance(m, base_cls) 

368 

369 

370############################################################################### 

371# Wrappers for NumPy types that are deprecated 

372 

373# Numpy versions of these functions raise deprecation warnings, the 

374# ones below do not. 

375 

376def matrix(*args, **kwargs): 

377 return np.array(*args, **kwargs).view(np.matrix) 

378 

379 

380def asmatrix(data, dtype=None): 

381 if isinstance(data, np.matrix) and (dtype is None or data.dtype == dtype): 

382 return data 

383 return np.asarray(data, dtype=dtype).view(np.matrix) 

384 

385############################################################################### 

386 

387 

388def _todata(s: 'sp.spmatrix') -> np.ndarray: 

389 """Access nonzero values, possibly after summing duplicates. 

390 

391 Parameters 

392 ---------- 

393 s : sparse matrix 

394 Input sparse matrix. 

395 

396 Returns 

397 ------- 

398 data: ndarray 

399 Nonzero values of the array, with shape (s.nnz,) 

400 

401 """ 

402 if isinstance(s, sp._data._data_matrix): 

403 return s._deduped_data() 

404 

405 if isinstance(s, sp.dok_matrix): 

406 return np.fromiter(s.values(), dtype=s.dtype, count=s.nnz) 

407 

408 if isinstance(s, sp.lil_matrix): 

409 data = np.empty(s.nnz, dtype=s.dtype) 

410 sp._csparsetools.lil_flatten_to_array(s.data, data) 

411 return data 

412 

413 return s.tocoo()._deduped_data()