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
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-12 06:31 +0000
1""" Utility functions for sparse matrix module
2"""
4import sys
5import operator
6import numpy as np
7from scipy._lib._util import prod
8import scipy.sparse as sp
11__all__ = ['upcast', 'getdtype', 'getdata', 'isscalarlike', 'isintlike',
12 'isshape', 'issequence', 'isdense', 'ismatrix', 'get_sum_dtype']
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]
19_upcast_memo = {}
22def upcast(*args):
23 """Returns the nearest supported sparse dtype for the
24 combination of one or more types.
26 upcast(t0, t1, ..., tn) -> T where T is a supported dtype
28 Examples
29 --------
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'>
40 """
42 t = _upcast_memo.get(hash(args))
43 if t is not None:
44 return t
46 upcast = np.result_type(*args)
48 for t in supported_dtypes:
49 if np.can_cast(upcast, t):
50 _upcast_memo[hash(args)] = t
51 return t
53 raise TypeError('no supported conversion for types: %r' % (args,))
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
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
73def downcast_intp_index(arr):
74 """
75 Down-cast index array to np.intp dtype if it is of a larger dtype.
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
93def to_native(A):
94 """
95 Ensure that the data type of the NumPy array `A` has native byte order.
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'))
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 )
132 return newdtype
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
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.
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)
162 Returns
163 -------
164 dtype : dtype
165 Suitable index data type (int32 or int64)
167 """
169 int32min = np.int32(np.iinfo(np.int32).min)
170 int32max = np.int32(np.iinfo(np.int32).max)
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
179 if isinstance(arrays, np.ndarray):
180 arrays = (arrays,)
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
196 dtype = np.int64
197 break
199 return dtype
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
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)
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
238def isshape(x, nonneg=False):
239 """Is x a valid 2-tuple of dimensions?
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
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)))
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))
268def isdense(x):
269 return isinstance(x, np.ndarray)
272def validateaxis(axis):
273 if axis is not None:
274 axis_type = type(axis)
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}."))
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__))
291 if not (-2 <= axis <= 1):
292 raise ValueError("axis out of range")
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)
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")
316 else:
317 # Check the current size only if needed
318 current_size = prod(current_shape)
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')
339 if len(new_shape) != 2:
340 raise ValueError('matrix shape must be two-dimensional')
342 return new_shape
345def check_reshape_kwargs(kwargs):
346 """Unpack keyword arguments for reshape function.
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 """
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
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)
370###############################################################################
371# Wrappers for NumPy types that are deprecated
373# Numpy versions of these functions raise deprecation warnings, the
374# ones below do not.
376def matrix(*args, **kwargs):
377 return np.array(*args, **kwargs).view(np.matrix)
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)
385###############################################################################
388def _todata(s: 'sp.spmatrix') -> np.ndarray:
389 """Access nonzero values, possibly after summing duplicates.
391 Parameters
392 ----------
393 s : sparse matrix
394 Input sparse matrix.
396 Returns
397 -------
398 data: ndarray
399 Nonzero values of the array, with shape (s.nnz,)
401 """
402 if isinstance(s, sp._data._data_matrix):
403 return s._deduped_data()
405 if isinstance(s, sp.dok_matrix):
406 return np.fromiter(s.values(), dtype=s.dtype, count=s.nnz)
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
413 return s.tocoo()._deduped_data()