Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" Utility functions for sparse matrix module 

2""" 

3 

4import sys 

5import operator 

6import warnings 

7import numpy as np 

8from scipy._lib._util import prod 

9 

10__all__ = ['upcast', 'getdtype', 'isscalarlike', 'isintlike', 

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

12 

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

14 np.uintc, np.int_, np.uint, np.longlong, np.ulonglong, np.single, np.double, 

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

16 

17_upcast_memo = {} 

18 

19 

20def upcast(*args): 

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

22 combination of one or more types. 

23 

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

25 

26 Examples 

27 -------- 

28 

29 >>> upcast('int32') 

30 <type 'numpy.int32'> 

31 >>> upcast('bool') 

32 <type 'numpy.bool_'> 

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

34 <type 'numpy.float64'> 

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

36 <type 'numpy.complex128'> 

37 

38 """ 

39 

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

41 if t is not None: 

42 return t 

43 

44 upcast = np.find_common_type(args, []) 

45 

46 for t in supported_dtypes: 

47 if np.can_cast(upcast, t): 

48 _upcast_memo[hash(args)] = t 

49 return t 

50 

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

52 

53 

54def upcast_char(*args): 

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

56 t = _upcast_memo.get(args) 

57 if t is not None: 

58 return t 

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

60 _upcast_memo[args] = t 

61 return t 

62 

63 

64def upcast_scalar(dtype, scalar): 

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

66 type `dtype` and a scalar. 

67 """ 

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

69 

70 

71def downcast_intp_index(arr): 

72 """ 

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

74 

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

76 intp. 

77 """ 

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

79 if arr.size == 0: 

80 return arr.astype(np.intp) 

81 maxval = arr.max() 

82 minval = arr.min() 

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

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

85 "than the machine maximum address size " 

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

87 return arr.astype(np.intp) 

88 return arr 

89 

90 

91def to_native(A): 

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

93 

94 

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

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

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

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

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

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

101 """ 

102 # TODO is this really what we want? 

103 if dtype is None: 

104 try: 

105 newdtype = a.dtype 

106 except AttributeError: 

107 if default is not None: 

108 newdtype = np.dtype(default) 

109 else: 

110 raise TypeError("could not interpret data type") 

111 else: 

112 newdtype = np.dtype(dtype) 

113 if newdtype == np.object_: 

114 warnings.warn("object dtype is not supported by sparse matrices") 

115 

116 return newdtype 

117 

118 

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

120 """ 

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

122 type that can hold the data in the arrays. 

123 

124 Parameters 

125 ---------- 

126 arrays : tuple of array_like 

127 Input arrays whose types/contents to check 

128 maxval : float, optional 

129 Maximum value needed 

130 check_contents : bool, optional 

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

132 Default: False (check only the types) 

133 

134 Returns 

135 ------- 

136 dtype : dtype 

137 Suitable index data type (int32 or int64) 

138 

139 """ 

140 

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

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

143 

144 dtype = np.intc 

145 if maxval is not None: 

146 if maxval > int32max: 

147 dtype = np.int64 

148 

149 if isinstance(arrays, np.ndarray): 

150 arrays = (arrays,) 

151 

152 for arr in arrays: 

153 arr = np.asarray(arr) 

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

155 if check_contents: 

156 if arr.size == 0: 

157 # a bigger type not needed 

158 continue 

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

160 maxval = arr.max() 

161 minval = arr.min() 

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

163 # a bigger type not needed 

164 continue 

165 

166 dtype = np.int64 

167 break 

168 

169 return dtype 

170 

171 

172def get_sum_dtype(dtype): 

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

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

175 return np.uint 

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

177 return np.int_ 

178 return dtype 

179 

180 

181def isscalarlike(x): 

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

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

184 

185 

186def isintlike(x): 

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

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

189 """ 

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

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

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

193 return False 

194 try: 

195 operator.index(x) 

196 except (TypeError, ValueError): 

197 try: 

198 loose_int = bool(int(x) == x) 

199 except (TypeError, ValueError): 

200 return False 

201 if loose_int: 

202 warnings.warn("Inexact indices into sparse matrices are deprecated", 

203 DeprecationWarning) 

204 return loose_int 

205 return True 

206 

207 

208def isshape(x, nonneg=False): 

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

210 

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

212 """ 

213 try: 

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

215 (M, N) = x 

216 except Exception: 

217 return False 

218 else: 

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

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

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

222 return True 

223 return False 

224 

225 

226def issequence(t): 

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

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

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

230 

231 

232def ismatrix(t): 

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

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

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

236 

237 

238def isdense(x): 

239 return isinstance(x, np.ndarray) 

240 

241 

242def validateaxis(axis): 

243 if axis is not None: 

244 axis_type = type(axis) 

245 

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

247 # not very useful for sparse matrices given their limited 

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

249 # allowed to be passed in 

250 if axis_type == tuple: 

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

252 "parameter. Please pass in one of the " 

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

254 

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

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

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

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

259 .format(name=axis_type.__name__)) 

260 

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

262 raise ValueError("axis out of range") 

263 

264 

265def check_shape(args, current_shape=None): 

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

267 if len(args) == 0: 

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

269 "'shape'") 

270 elif len(args) == 1: 

271 try: 

272 shape_iter = iter(args[0]) 

273 except TypeError: 

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

275 else: 

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

277 else: 

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

279 

280 if current_shape is None: 

281 if len(new_shape) != 2: 

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

283 elif new_shape[0] < 0 or new_shape[1] < 0: 

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

285 

286 else: 

287 # Check the current size only if needed 

288 current_size = prod(current_shape) 

289 

290 # Check for negatives 

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

292 if len(negative_indexes) == 0: 

293 new_size = prod(new_shape) 

294 if new_size != current_size: 

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

296 .format(current_size, new_shape)) 

297 elif len(negative_indexes) == 1: 

298 skip = negative_indexes[0] 

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

300 unspecified, remainder = divmod(current_size, specified) 

301 if remainder != 0: 

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

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

304 ''.format(current_size, err_shape)) 

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

306 else: 

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

308 

309 if len(new_shape) != 2: 

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

311 

312 return new_shape 

313 

314 

315def check_reshape_kwargs(kwargs): 

316 """Unpack keyword arguments for reshape function. 

317 

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

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

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

321 throws an error for any remaining. 

322 """ 

323 

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

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

326 if kwargs: # Some unused kwargs remain 

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

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

329 return order, copy 

330 

331 

332def is_pydata_spmatrix(m): 

333 """ 

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

335 """ 

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

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

338 

339 

340############################################################################### 

341# Wrappers for NumPy types that are deprecated 

342 

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

344 with warnings.catch_warnings(record=True): 

345 warnings.filterwarnings( 

346 'ignore', '.*the matrix subclass is not the recommended way.*') 

347 return np.matrix(*args, **kwargs) 

348 

349 

350def asmatrix(*args, **kwargs): 

351 with warnings.catch_warnings(record=True): 

352 warnings.filterwarnings( 

353 'ignore', '.*the matrix subclass is not the recommended way.*') 

354 return np.asmatrix(*args, **kwargs) 

355 

356 

357def bmat(*args, **kwargs): 

358 with warnings.catch_warnings(record=True): 

359 warnings.filterwarnings( 

360 'ignore', '.*the matrix subclass is not the recommended way.*') 

361 return np.bmat(*args, **kwargs)