Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/scipy/sparse/_compressed.py: 11%
768 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-03-22 06:44 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-03-22 06:44 +0000
1"""Base class for sparse matrix formats using compressed storage."""
2__all__ = []
4from warnings import warn
5import operator
7import numpy as np
8from scipy._lib._util import _prune_array, copy_if_needed
10from ._base import _spbase, issparse, SparseEfficiencyWarning
11from ._data import _data_matrix, _minmax_mixin
12from . import _sparsetools
13from ._sparsetools import (get_csr_submatrix, csr_sample_offsets, csr_todense,
14 csr_sample_values, csr_row_index, csr_row_slice,
15 csr_column_index1, csr_column_index2)
16from ._index import IndexMixin
17from ._sputils import (upcast, upcast_char, to_native, isdense, isshape,
18 getdtype, isscalarlike, isintlike, downcast_intp_index,
19 get_sum_dtype, check_shape, is_pydata_spmatrix)
22class _cs_matrix(_data_matrix, _minmax_mixin, IndexMixin):
23 """
24 base array/matrix class for compressed row- and column-oriented arrays/matrices
25 """
27 def __init__(self, arg1, shape=None, dtype=None, copy=False):
28 _data_matrix.__init__(self)
30 if issparse(arg1):
31 if arg1.format == self.format and copy:
32 arg1 = arg1.copy()
33 else:
34 arg1 = arg1.asformat(self.format)
35 self.indptr, self.indices, self.data, self._shape = (
36 arg1.indptr, arg1.indices, arg1.data, arg1._shape
37 )
39 elif isinstance(arg1, tuple):
40 if isshape(arg1):
41 # It's a tuple of matrix dimensions (M, N)
42 # create empty matrix
43 self._shape = check_shape(arg1)
44 M, N = self.shape
45 # Select index dtype large enough to pass array and
46 # scalar parameters to sparsetools
47 idx_dtype = self._get_index_dtype(maxval=max(M, N))
48 self.data = np.zeros(0, getdtype(dtype, default=float))
49 self.indices = np.zeros(0, idx_dtype)
50 self.indptr = np.zeros(self._swap((M, N))[0] + 1,
51 dtype=idx_dtype)
52 else:
53 if len(arg1) == 2:
54 # (data, ij) format
55 coo = self._coo_container(arg1, shape=shape, dtype=dtype)
56 arrays = coo._coo_to_compressed(self._swap)
57 self.indptr, self.indices, self.data, self._shape = arrays
58 elif len(arg1) == 3:
59 # (data, indices, indptr) format
60 (data, indices, indptr) = arg1
62 # Select index dtype large enough to pass array and
63 # scalar parameters to sparsetools
64 maxval = None
65 if shape is not None:
66 maxval = max(shape)
67 idx_dtype = self._get_index_dtype((indices, indptr),
68 maxval=maxval,
69 check_contents=True)
71 if not copy:
72 copy = copy_if_needed
73 self.indices = np.array(indices, copy=copy, dtype=idx_dtype)
74 self.indptr = np.array(indptr, copy=copy, dtype=idx_dtype)
75 self.data = np.array(data, copy=copy, dtype=dtype)
76 else:
77 raise ValueError(f"unrecognized {self.format}_matrix "
78 "constructor usage")
80 else:
81 # must be dense
82 try:
83 arg1 = np.asarray(arg1)
84 except Exception as e:
85 msg = f"unrecognized {self.format}_matrix constructor usage"
86 raise ValueError(msg) from e
87 coo = self._coo_container(arg1, dtype=dtype)
88 arrays = coo._coo_to_compressed(self._swap)
89 self.indptr, self.indices, self.data, self._shape = arrays
91 # Read matrix dimensions given, if any
92 if shape is not None:
93 self._shape = check_shape(shape)
94 else:
95 if self.shape is None:
96 # shape not already set, try to infer dimensions
97 try:
98 major_dim = len(self.indptr) - 1
99 minor_dim = self.indices.max() + 1
100 except Exception as e:
101 raise ValueError('unable to infer matrix dimensions') from e
102 else:
103 self._shape = check_shape(self._swap((major_dim, minor_dim)))
105 if dtype is not None:
106 self.data = self.data.astype(dtype, copy=False)
108 self.check_format(full_check=False)
110 def _getnnz(self, axis=None):
111 if axis is None:
112 return int(self.indptr[-1])
113 else:
114 if axis < 0:
115 axis += 2
116 axis, _ = self._swap((axis, 1 - axis))
117 _, N = self._swap(self.shape)
118 if axis == 0:
119 return np.bincount(downcast_intp_index(self.indices),
120 minlength=N)
121 elif axis == 1:
122 return np.diff(self.indptr)
123 raise ValueError('axis out of bounds')
125 _getnnz.__doc__ = _spbase._getnnz.__doc__
127 def check_format(self, full_check=True):
128 """Check whether the array/matrix respects the CSR or CSC format.
130 Parameters
131 ----------
132 full_check : bool, optional
133 If `True`, run rigorous check, scanning arrays for valid values.
134 Note that activating those check might copy arrays for casting,
135 modifying indices and index pointers' inplace.
136 If `False`, run basic checks on attributes. O(1) operations.
137 Default is `True`.
138 """
139 # use _swap to determine proper bounds
140 major_name, minor_name = self._swap(('row', 'column'))
141 major_dim, minor_dim = self._swap(self.shape)
143 # index arrays should have integer data types
144 if self.indptr.dtype.kind != 'i':
145 warn(f"indptr array has non-integer dtype ({self.indptr.dtype.name})",
146 stacklevel=3)
147 if self.indices.dtype.kind != 'i':
148 warn(f"indices array has non-integer dtype ({self.indices.dtype.name})",
149 stacklevel=3)
151 # check array shapes
152 for x in [self.data.ndim, self.indices.ndim, self.indptr.ndim]:
153 if x != 1:
154 raise ValueError('data, indices, and indptr should be 1-D')
156 # check index pointer
157 if (len(self.indptr) != major_dim + 1):
158 raise ValueError("index pointer size ({}) should be ({})"
159 "".format(len(self.indptr), major_dim + 1))
160 if (self.indptr[0] != 0):
161 raise ValueError("index pointer should start with 0")
163 # check index and data arrays
164 if (len(self.indices) != len(self.data)):
165 raise ValueError("indices and data should have the same size")
166 if (self.indptr[-1] > len(self.indices)):
167 raise ValueError("Last value of index pointer should be less than "
168 "the size of index and data arrays")
170 self.prune()
172 if full_check:
173 # check format validity (more expensive)
174 if self.nnz > 0:
175 if self.indices.max() >= minor_dim:
176 raise ValueError(f"{minor_name} index values must be < {minor_dim}")
177 if self.indices.min() < 0:
178 raise ValueError(f"{minor_name} index values must be >= 0")
179 if np.diff(self.indptr).min() < 0:
180 raise ValueError("index pointer values must form a "
181 "non-decreasing sequence")
183 idx_dtype = self._get_index_dtype((self.indptr, self.indices))
184 self.indptr = np.asarray(self.indptr, dtype=idx_dtype)
185 self.indices = np.asarray(self.indices, dtype=idx_dtype)
186 self.data = to_native(self.data)
188 # if not self.has_sorted_indices():
189 # warn('Indices were not in sorted order. Sorting indices.')
190 # self.sort_indices()
191 # assert(self.has_sorted_indices())
192 # TODO check for duplicates?
194 #######################
195 # Boolean comparisons #
196 #######################
198 def _scalar_binopt(self, other, op):
199 """Scalar version of self._binopt, for cases in which no new nonzeros
200 are added. Produces a new sparse array in canonical form.
201 """
202 self.sum_duplicates()
203 res = self._with_data(op(self.data, other), copy=True)
204 res.eliminate_zeros()
205 return res
207 def __eq__(self, other):
208 # Scalar other.
209 if isscalarlike(other):
210 if np.isnan(other):
211 return self.__class__(self.shape, dtype=np.bool_)
213 if other == 0:
214 warn("Comparing a sparse matrix with 0 using == is inefficient"
215 ", try using != instead.", SparseEfficiencyWarning,
216 stacklevel=3)
217 all_true = self.__class__(np.ones(self.shape, dtype=np.bool_))
218 inv = self._scalar_binopt(other, operator.ne)
219 return all_true - inv
220 else:
221 return self._scalar_binopt(other, operator.eq)
222 # Dense other.
223 elif isdense(other):
224 return self.todense() == other
225 # Pydata sparse other.
226 elif is_pydata_spmatrix(other):
227 return NotImplemented
228 # Sparse other.
229 elif issparse(other):
230 warn("Comparing sparse matrices using == is inefficient, try using"
231 " != instead.", SparseEfficiencyWarning, stacklevel=3)
232 # TODO sparse broadcasting
233 if self.shape != other.shape:
234 return False
235 elif self.format != other.format:
236 other = other.asformat(self.format)
237 res = self._binopt(other, '_ne_')
238 all_true = self.__class__(np.ones(self.shape, dtype=np.bool_))
239 return all_true - res
240 else:
241 return NotImplemented
243 def __ne__(self, other):
244 # Scalar other.
245 if isscalarlike(other):
246 if np.isnan(other):
247 warn("Comparing a sparse matrix with nan using != is"
248 " inefficient", SparseEfficiencyWarning, stacklevel=3)
249 all_true = self.__class__(np.ones(self.shape, dtype=np.bool_))
250 return all_true
251 elif other != 0:
252 warn("Comparing a sparse matrix with a nonzero scalar using !="
253 " is inefficient, try using == instead.",
254 SparseEfficiencyWarning, stacklevel=3)
255 all_true = self.__class__(np.ones(self.shape), dtype=np.bool_)
256 inv = self._scalar_binopt(other, operator.eq)
257 return all_true - inv
258 else:
259 return self._scalar_binopt(other, operator.ne)
260 # Dense other.
261 elif isdense(other):
262 return self.todense() != other
263 # Pydata sparse other.
264 elif is_pydata_spmatrix(other):
265 return NotImplemented
266 # Sparse other.
267 elif issparse(other):
268 # TODO sparse broadcasting
269 if self.shape != other.shape:
270 return True
271 elif self.format != other.format:
272 other = other.asformat(self.format)
273 return self._binopt(other, '_ne_')
274 else:
275 return NotImplemented
277 def _inequality(self, other, op, op_name, bad_scalar_msg):
278 # Scalar other.
279 if isscalarlike(other):
280 if 0 == other and op_name in ('_le_', '_ge_'):
281 raise NotImplementedError(" >= and <= don't work with 0.")
282 elif op(0, other):
283 warn(bad_scalar_msg, SparseEfficiencyWarning, stacklevel=3)
284 other_arr = np.empty(self.shape, dtype=np.result_type(other))
285 other_arr.fill(other)
286 other_arr = self.__class__(other_arr)
287 return self._binopt(other_arr, op_name)
288 else:
289 return self._scalar_binopt(other, op)
290 # Dense other.
291 elif isdense(other):
292 return op(self.todense(), other)
293 # Sparse other.
294 elif issparse(other):
295 # TODO sparse broadcasting
296 if self.shape != other.shape:
297 raise ValueError("inconsistent shapes")
298 elif self.format != other.format:
299 other = other.asformat(self.format)
300 if op_name not in ('_ge_', '_le_'):
301 return self._binopt(other, op_name)
303 warn("Comparing sparse matrices using >= and <= is inefficient, "
304 "using <, >, or !=, instead.",
305 SparseEfficiencyWarning, stacklevel=3)
306 all_true = self.__class__(np.ones(self.shape, dtype=np.bool_))
307 res = self._binopt(other, '_gt_' if op_name == '_le_' else '_lt_')
308 return all_true - res
309 else:
310 return NotImplemented
312 def __lt__(self, other):
313 return self._inequality(other, operator.lt, '_lt_',
314 "Comparing a sparse matrix with a scalar "
315 "greater than zero using < is inefficient, "
316 "try using >= instead.")
318 def __gt__(self, other):
319 return self._inequality(other, operator.gt, '_gt_',
320 "Comparing a sparse matrix with a scalar "
321 "less than zero using > is inefficient, "
322 "try using <= instead.")
324 def __le__(self, other):
325 return self._inequality(other, operator.le, '_le_',
326 "Comparing a sparse matrix with a scalar "
327 "greater than zero using <= is inefficient, "
328 "try using > instead.")
330 def __ge__(self, other):
331 return self._inequality(other, operator.ge, '_ge_',
332 "Comparing a sparse matrix with a scalar "
333 "less than zero using >= is inefficient, "
334 "try using < instead.")
336 #################################
337 # Arithmetic operator overrides #
338 #################################
340 def _add_dense(self, other):
341 if other.shape != self.shape:
342 raise ValueError(f'Incompatible shapes ({self.shape} and {other.shape})')
343 dtype = upcast_char(self.dtype.char, other.dtype.char)
344 order = self._swap('CF')[0]
345 result = np.array(other, dtype=dtype, order=order, copy=True)
346 M, N = self._swap(self.shape)
347 y = result if result.flags.c_contiguous else result.T
348 csr_todense(M, N, self.indptr, self.indices, self.data, y)
349 return self._container(result, copy=False)
351 def _add_sparse(self, other):
352 return self._binopt(other, '_plus_')
354 def _sub_sparse(self, other):
355 return self._binopt(other, '_minus_')
357 def multiply(self, other):
358 """Point-wise multiplication by another array/matrix, vector, or
359 scalar.
360 """
361 # Scalar multiplication.
362 if isscalarlike(other):
363 return self._mul_scalar(other)
364 # Sparse matrix or vector.
365 if issparse(other):
366 if self.shape == other.shape:
367 other = self.__class__(other)
368 return self._binopt(other, '_elmul_')
369 if other.ndim == 1:
370 raise TypeError("broadcast from a 1d array not yet supported")
371 # Single element.
372 elif other.shape == (1, 1):
373 return self._mul_scalar(other.toarray()[0, 0])
374 elif self.shape == (1, 1):
375 return other._mul_scalar(self.toarray()[0, 0])
376 # A row times a column.
377 elif self.shape[1] == 1 and other.shape[0] == 1:
378 return self._matmul_sparse(other.tocsc())
379 elif self.shape[0] == 1 and other.shape[1] == 1:
380 return other._matmul_sparse(self.tocsc())
381 # Row vector times matrix. other is a row.
382 elif other.shape[0] == 1 and self.shape[1] == other.shape[1]:
383 other = self._dia_container(
384 (other.toarray().ravel(), [0]),
385 shape=(other.shape[1], other.shape[1])
386 )
387 return self._matmul_sparse(other)
388 # self is a row.
389 elif self.shape[0] == 1 and self.shape[1] == other.shape[1]:
390 copy = self._dia_container(
391 (self.toarray().ravel(), [0]),
392 shape=(self.shape[1], self.shape[1])
393 )
394 return other._matmul_sparse(copy)
395 # Column vector times matrix. other is a column.
396 elif other.shape[1] == 1 and self.shape[0] == other.shape[0]:
397 other = self._dia_container(
398 (other.toarray().ravel(), [0]),
399 shape=(other.shape[0], other.shape[0])
400 )
401 return other._matmul_sparse(self)
402 # self is a column.
403 elif self.shape[1] == 1 and self.shape[0] == other.shape[0]:
404 copy = self._dia_container(
405 (self.toarray().ravel(), [0]),
406 shape=(self.shape[0], self.shape[0])
407 )
408 return copy._matmul_sparse(other)
409 else:
410 raise ValueError("inconsistent shapes")
412 # Assume other is a dense matrix/array, which produces a single-item
413 # object array if other isn't convertible to ndarray.
414 other = np.atleast_2d(other)
416 if other.ndim != 2:
417 return np.multiply(self.toarray(), other)
418 # Single element / wrapped object.
419 if other.size == 1:
420 if other.dtype == np.object_:
421 # 'other' not convertible to ndarray.
422 return NotImplemented
423 return self._mul_scalar(other.flat[0])
424 # Fast case for trivial sparse matrix.
425 elif self.shape == (1, 1):
426 return np.multiply(self.toarray()[0, 0], other)
428 ret = self.tocoo()
429 # Matching shapes.
430 if self.shape == other.shape:
431 data = np.multiply(ret.data, other[ret.row, ret.col])
432 # Sparse row vector times...
433 elif self.shape[0] == 1:
434 if other.shape[1] == 1: # Dense column vector.
435 data = np.multiply(ret.data, other)
436 elif other.shape[1] == self.shape[1]: # Dense matrix.
437 data = np.multiply(ret.data, other[:, ret.col])
438 else:
439 raise ValueError("inconsistent shapes")
440 row = np.repeat(np.arange(other.shape[0]), len(ret.row))
441 col = np.tile(ret.col, other.shape[0])
442 return self._coo_container(
443 (data.view(np.ndarray).ravel(), (row, col)),
444 shape=(other.shape[0], self.shape[1]),
445 copy=False
446 )
447 # Sparse column vector times...
448 elif self.shape[1] == 1:
449 if other.shape[0] == 1: # Dense row vector.
450 data = np.multiply(ret.data[:, None], other)
451 elif other.shape[0] == self.shape[0]: # Dense matrix.
452 data = np.multiply(ret.data[:, None], other[ret.row])
453 else:
454 raise ValueError("inconsistent shapes")
455 row = np.repeat(ret.row, other.shape[1])
456 col = np.tile(np.arange(other.shape[1]), len(ret.col))
457 return self._coo_container(
458 (data.view(np.ndarray).ravel(), (row, col)),
459 shape=(self.shape[0], other.shape[1]),
460 copy=False
461 )
462 # Sparse matrix times dense row vector.
463 elif other.shape[0] == 1 and self.shape[1] == other.shape[1]:
464 data = np.multiply(ret.data, other[:, ret.col].ravel())
465 # Sparse matrix times dense column vector.
466 elif other.shape[1] == 1 and self.shape[0] == other.shape[0]:
467 data = np.multiply(ret.data, other[ret.row].ravel())
468 else:
469 raise ValueError("inconsistent shapes")
470 ret.data = data.view(np.ndarray).ravel()
471 return ret
473 ###########################
474 # Multiplication handlers #
475 ###########################
477 def _matmul_vector(self, other):
478 M, N = self.shape
480 # output array
481 result = np.zeros(M, dtype=upcast_char(self.dtype.char,
482 other.dtype.char))
484 # csr_matvec or csc_matvec
485 fn = getattr(_sparsetools, self.format + '_matvec')
486 fn(M, N, self.indptr, self.indices, self.data, other, result)
488 return result
490 def _matmul_multivector(self, other):
491 M, N = self.shape
492 n_vecs = other.shape[1] # number of column vectors
494 result = np.zeros((M, n_vecs),
495 dtype=upcast_char(self.dtype.char, other.dtype.char))
497 # csr_matvecs or csc_matvecs
498 fn = getattr(_sparsetools, self.format + '_matvecs')
499 fn(M, N, n_vecs, self.indptr, self.indices, self.data,
500 other.ravel(), result.ravel())
502 return result
504 def _matmul_sparse(self, other):
505 M, K1 = self.shape
506 K2, N = other.shape
508 major_axis = self._swap((M, N))[0]
509 other = self.__class__(other) # convert to this format
511 idx_dtype = self._get_index_dtype((self.indptr, self.indices,
512 other.indptr, other.indices))
514 fn = getattr(_sparsetools, self.format + '_matmat_maxnnz')
515 nnz = fn(M, N,
516 np.asarray(self.indptr, dtype=idx_dtype),
517 np.asarray(self.indices, dtype=idx_dtype),
518 np.asarray(other.indptr, dtype=idx_dtype),
519 np.asarray(other.indices, dtype=idx_dtype))
521 idx_dtype = self._get_index_dtype((self.indptr, self.indices,
522 other.indptr, other.indices),
523 maxval=nnz)
525 indptr = np.empty(major_axis + 1, dtype=idx_dtype)
526 indices = np.empty(nnz, dtype=idx_dtype)
527 data = np.empty(nnz, dtype=upcast(self.dtype, other.dtype))
529 fn = getattr(_sparsetools, self.format + '_matmat')
530 fn(M, N, np.asarray(self.indptr, dtype=idx_dtype),
531 np.asarray(self.indices, dtype=idx_dtype),
532 self.data,
533 np.asarray(other.indptr, dtype=idx_dtype),
534 np.asarray(other.indices, dtype=idx_dtype),
535 other.data,
536 indptr, indices, data)
538 return self.__class__((data, indices, indptr), shape=(M, N))
540 def diagonal(self, k=0):
541 rows, cols = self.shape
542 if k <= -rows or k >= cols:
543 return np.empty(0, dtype=self.data.dtype)
544 fn = getattr(_sparsetools, self.format + "_diagonal")
545 y = np.empty(min(rows + min(k, 0), cols - max(k, 0)),
546 dtype=upcast(self.dtype))
547 fn(k, self.shape[0], self.shape[1], self.indptr, self.indices,
548 self.data, y)
549 return y
551 diagonal.__doc__ = _spbase.diagonal.__doc__
553 #####################
554 # Other binary ops #
555 #####################
557 def _maximum_minimum(self, other, npop, op_name, dense_check):
558 if isscalarlike(other):
559 if dense_check(other):
560 warn("Taking maximum (minimum) with > 0 (< 0) number results"
561 " to a dense matrix.", SparseEfficiencyWarning,
562 stacklevel=3)
563 other_arr = np.empty(self.shape, dtype=np.asarray(other).dtype)
564 other_arr.fill(other)
565 other_arr = self.__class__(other_arr)
566 return self._binopt(other_arr, op_name)
567 else:
568 self.sum_duplicates()
569 new_data = npop(self.data, np.asarray(other))
570 mat = self.__class__((new_data, self.indices, self.indptr),
571 dtype=new_data.dtype, shape=self.shape)
572 return mat
573 elif isdense(other):
574 return npop(self.todense(), other)
575 elif issparse(other):
576 return self._binopt(other, op_name)
577 else:
578 raise ValueError("Operands not compatible.")
580 def maximum(self, other):
581 return self._maximum_minimum(other, np.maximum,
582 '_maximum_', lambda x: np.asarray(x) > 0)
584 maximum.__doc__ = _spbase.maximum.__doc__
586 def minimum(self, other):
587 return self._maximum_minimum(other, np.minimum,
588 '_minimum_', lambda x: np.asarray(x) < 0)
590 minimum.__doc__ = _spbase.minimum.__doc__
592 #####################
593 # Reduce operations #
594 #####################
596 def sum(self, axis=None, dtype=None, out=None):
597 """Sum the array/matrix over the given axis. If the axis is None, sum
598 over both rows and columns, returning a scalar.
599 """
600 # The _spbase base class already does axis=0 and axis=1 efficiently
601 # so we only do the case axis=None here
602 if (not hasattr(self, 'blocksize') and
603 axis in self._swap(((1, -1), (0, 2)))[0]):
604 # faster than multiplication for large minor axis in CSC/CSR
605 res_dtype = get_sum_dtype(self.dtype)
606 ret = np.zeros(len(self.indptr) - 1, dtype=res_dtype)
608 major_index, value = self._minor_reduce(np.add)
609 ret[major_index] = value
610 ret = self._ascontainer(ret)
611 if axis % 2 == 1:
612 ret = ret.T
614 if out is not None and out.shape != ret.shape:
615 raise ValueError('dimensions do not match')
617 return ret.sum(axis=(), dtype=dtype, out=out)
618 # _spbase will handle the remaining situations when axis
619 # is in {None, -1, 0, 1}
620 else:
621 return _spbase.sum(self, axis=axis, dtype=dtype, out=out)
623 sum.__doc__ = _spbase.sum.__doc__
625 def _minor_reduce(self, ufunc, data=None):
626 """Reduce nonzeros with a ufunc over the minor axis when non-empty
628 Can be applied to a function of self.data by supplying data parameter.
630 Warning: this does not call sum_duplicates()
632 Returns
633 -------
634 major_index : array of ints
635 Major indices where nonzero
637 value : array of self.dtype
638 Reduce result for nonzeros in each major_index
639 """
640 if data is None:
641 data = self.data
642 major_index = np.flatnonzero(np.diff(self.indptr))
643 value = ufunc.reduceat(data,
644 downcast_intp_index(self.indptr[major_index]))
645 return major_index, value
647 #######################
648 # Getting and Setting #
649 #######################
651 def _get_intXint(self, row, col):
652 M, N = self._swap(self.shape)
653 major, minor = self._swap((row, col))
654 indptr, indices, data = get_csr_submatrix(
655 M, N, self.indptr, self.indices, self.data,
656 major, major + 1, minor, minor + 1)
657 return data.sum(dtype=self.dtype)
659 def _get_sliceXslice(self, row, col):
660 major, minor = self._swap((row, col))
661 if major.step in (1, None) and minor.step in (1, None):
662 return self._get_submatrix(major, minor, copy=True)
663 return self._major_slice(major)._minor_slice(minor)
665 def _get_arrayXarray(self, row, col):
666 # inner indexing
667 idx_dtype = self.indices.dtype
668 M, N = self._swap(self.shape)
669 major, minor = self._swap((row, col))
670 major = np.asarray(major, dtype=idx_dtype)
671 minor = np.asarray(minor, dtype=idx_dtype)
673 val = np.empty(major.size, dtype=self.dtype)
674 csr_sample_values(M, N, self.indptr, self.indices, self.data,
675 major.size, major.ravel(), minor.ravel(), val)
676 if major.ndim == 1:
677 return self._ascontainer(val)
678 return self.__class__(val.reshape(major.shape))
680 def _get_columnXarray(self, row, col):
681 # outer indexing
682 major, minor = self._swap((row, col))
683 return self._major_index_fancy(major)._minor_index_fancy(minor)
685 def _major_index_fancy(self, idx):
686 """Index along the major axis where idx is an array of ints.
687 """
688 idx_dtype = self._get_index_dtype((self.indptr, self.indices))
689 indices = np.asarray(idx, dtype=idx_dtype).ravel()
691 _, N = self._swap(self.shape)
692 M = len(indices)
693 new_shape = self._swap((M, N))
694 if M == 0:
695 return self.__class__(new_shape, dtype=self.dtype)
697 row_nnz = (self.indptr[indices + 1] - self.indptr[indices]).astype(idx_dtype)
699 res_indptr = np.zeros(M+1, dtype=idx_dtype)
700 np.cumsum(row_nnz, out=res_indptr[1:])
702 nnz = res_indptr[-1]
703 res_indices = np.empty(nnz, dtype=idx_dtype)
704 res_data = np.empty(nnz, dtype=self.dtype)
705 csr_row_index(
706 M,
707 indices,
708 self.indptr.astype(idx_dtype, copy=False),
709 self.indices.astype(idx_dtype, copy=False),
710 self.data,
711 res_indices,
712 res_data
713 )
715 return self.__class__((res_data, res_indices, res_indptr),
716 shape=new_shape, copy=False)
718 def _major_slice(self, idx, copy=False):
719 """Index along the major axis where idx is a slice object.
720 """
721 if idx == slice(None):
722 return self.copy() if copy else self
724 M, N = self._swap(self.shape)
725 start, stop, step = idx.indices(M)
726 M = len(range(start, stop, step))
727 new_shape = self._swap((M, N))
728 if M == 0:
729 return self.__class__(new_shape, dtype=self.dtype)
731 # Work out what slices are needed for `row_nnz`
732 # start,stop can be -1, only if step is negative
733 start0, stop0 = start, stop
734 if stop == -1 and start >= 0:
735 stop0 = None
736 start1, stop1 = start + 1, stop + 1
738 row_nnz = self.indptr[start1:stop1:step] - \
739 self.indptr[start0:stop0:step]
740 idx_dtype = self.indices.dtype
741 res_indptr = np.zeros(M+1, dtype=idx_dtype)
742 np.cumsum(row_nnz, out=res_indptr[1:])
744 if step == 1:
745 all_idx = slice(self.indptr[start], self.indptr[stop])
746 res_indices = np.array(self.indices[all_idx], copy=copy)
747 res_data = np.array(self.data[all_idx], copy=copy)
748 else:
749 nnz = res_indptr[-1]
750 res_indices = np.empty(nnz, dtype=idx_dtype)
751 res_data = np.empty(nnz, dtype=self.dtype)
752 csr_row_slice(start, stop, step, self.indptr, self.indices,
753 self.data, res_indices, res_data)
755 return self.__class__((res_data, res_indices, res_indptr),
756 shape=new_shape, copy=False)
758 def _minor_index_fancy(self, idx):
759 """Index along the minor axis where idx is an array of ints.
760 """
761 idx_dtype = self._get_index_dtype((self.indices, self.indptr))
762 indices = self.indices.astype(idx_dtype, copy=False)
763 indptr = self.indptr.astype(idx_dtype, copy=False)
765 idx = np.asarray(idx, dtype=idx_dtype).ravel()
767 M, N = self._swap(self.shape)
768 k = len(idx)
769 new_shape = self._swap((M, k))
770 if k == 0:
771 return self.__class__(new_shape, dtype=self.dtype)
773 # pass 1: count idx entries and compute new indptr
774 col_offsets = np.zeros(N, dtype=idx_dtype)
775 res_indptr = np.empty_like(self.indptr, dtype=idx_dtype)
776 csr_column_index1(
777 k,
778 idx,
779 M,
780 N,
781 indptr,
782 indices,
783 col_offsets,
784 res_indptr,
785 )
787 # pass 2: copy indices/data for selected idxs
788 col_order = np.argsort(idx).astype(idx_dtype, copy=False)
789 nnz = res_indptr[-1]
790 res_indices = np.empty(nnz, dtype=idx_dtype)
791 res_data = np.empty(nnz, dtype=self.dtype)
792 csr_column_index2(col_order, col_offsets, len(self.indices),
793 indices, self.data, res_indices, res_data)
794 return self.__class__((res_data, res_indices, res_indptr),
795 shape=new_shape, copy=False)
797 def _minor_slice(self, idx, copy=False):
798 """Index along the minor axis where idx is a slice object.
799 """
800 if idx == slice(None):
801 return self.copy() if copy else self
803 M, N = self._swap(self.shape)
804 start, stop, step = idx.indices(N)
805 N = len(range(start, stop, step))
806 if N == 0:
807 return self.__class__(self._swap((M, N)), dtype=self.dtype)
808 if step == 1:
809 return self._get_submatrix(minor=idx, copy=copy)
810 # TODO: don't fall back to fancy indexing here
811 return self._minor_index_fancy(np.arange(start, stop, step))
813 def _get_submatrix(self, major=None, minor=None, copy=False):
814 """Return a submatrix of this matrix.
816 major, minor: None, int, or slice with step 1
817 """
818 M, N = self._swap(self.shape)
819 i0, i1 = _process_slice(major, M)
820 j0, j1 = _process_slice(minor, N)
822 if i0 == 0 and j0 == 0 and i1 == M and j1 == N:
823 return self.copy() if copy else self
825 indptr, indices, data = get_csr_submatrix(
826 M, N, self.indptr, self.indices, self.data, i0, i1, j0, j1)
828 shape = self._swap((i1 - i0, j1 - j0))
829 return self.__class__((data, indices, indptr), shape=shape,
830 dtype=self.dtype, copy=False)
832 def _set_intXint(self, row, col, x):
833 i, j = self._swap((row, col))
834 self._set_many(i, j, x)
836 def _set_arrayXarray(self, row, col, x):
837 i, j = self._swap((row, col))
838 self._set_many(i, j, x)
840 def _set_arrayXarray_sparse(self, row, col, x):
841 # clear entries that will be overwritten
842 self._zero_many(*self._swap((row, col)))
844 M, N = row.shape # matches col.shape
845 broadcast_row = M != 1 and x.shape[0] == 1
846 broadcast_col = N != 1 and x.shape[1] == 1
847 r, c = x.row, x.col
849 x = np.asarray(x.data, dtype=self.dtype)
850 if x.size == 0:
851 return
853 if broadcast_row:
854 r = np.repeat(np.arange(M), len(r))
855 c = np.tile(c, M)
856 x = np.tile(x, M)
857 if broadcast_col:
858 r = np.repeat(r, N)
859 c = np.tile(np.arange(N), len(c))
860 x = np.repeat(x, N)
861 # only assign entries in the new sparsity structure
862 i, j = self._swap((row[r, c], col[r, c]))
863 self._set_many(i, j, x)
865 def _setdiag(self, values, k):
866 if 0 in self.shape:
867 return
869 M, N = self.shape
870 broadcast = (values.ndim == 0)
872 if k < 0:
873 if broadcast:
874 max_index = min(M + k, N)
875 else:
876 max_index = min(M + k, N, len(values))
877 i = np.arange(-k, max_index - k, dtype=self.indices.dtype)
878 j = np.arange(max_index, dtype=self.indices.dtype)
880 else:
881 if broadcast:
882 max_index = min(M, N - k)
883 else:
884 max_index = min(M, N - k, len(values))
885 i = np.arange(max_index, dtype=self.indices.dtype)
886 j = np.arange(k, k + max_index, dtype=self.indices.dtype)
888 if not broadcast:
889 values = values[:len(i)]
891 x = np.atleast_1d(np.asarray(values, dtype=self.dtype)).ravel()
892 if x.squeeze().shape != i.squeeze().shape:
893 x = np.broadcast_to(x, i.shape)
894 if x.size == 0:
895 return
897 M, N = self._swap((M, N))
898 i, j = self._swap((i, j))
899 n_samples = x.size
900 offsets = np.empty(n_samples, dtype=self.indices.dtype)
901 ret = csr_sample_offsets(M, N, self.indptr, self.indices, n_samples,
902 i, j, offsets)
903 if ret == 1:
904 # rinse and repeat
905 self.sum_duplicates()
906 csr_sample_offsets(M, N, self.indptr, self.indices, n_samples,
907 i, j, offsets)
908 if -1 not in offsets:
909 # only affects existing non-zero cells
910 self.data[offsets] = x
911 return
913 mask = (offsets <= -1)
914 # Boundary between csc and convert to coo
915 # The value 0.001 is justified in gh-19962#issuecomment-1920499678
916 if mask.sum() < self.nnz * 0.001:
917 # create new entries
918 i = i[mask]
919 j = j[mask]
920 self._insert_many(i, j, x[mask])
921 # replace existing entries
922 mask = ~mask
923 self.data[offsets[mask]] = x[mask]
924 else:
925 # convert to coo for _set_diag
926 coo = self.tocoo()
927 coo._setdiag(values, k)
928 arrays = coo._coo_to_compressed(self._swap)
929 self.indptr, self.indices, self.data, _ = arrays
931 def _prepare_indices(self, i, j):
932 M, N = self._swap(self.shape)
934 def check_bounds(indices, bound):
935 idx = indices.max()
936 if idx >= bound:
937 raise IndexError('index (%d) out of range (>= %d)' %
938 (idx, bound))
939 idx = indices.min()
940 if idx < -bound:
941 raise IndexError('index (%d) out of range (< -%d)' %
942 (idx, bound))
944 i = np.atleast_1d(np.asarray(i, dtype=self.indices.dtype)).ravel()
945 j = np.atleast_1d(np.asarray(j, dtype=self.indices.dtype)).ravel()
946 check_bounds(i, M)
947 check_bounds(j, N)
948 return i, j, M, N
950 def _set_many(self, i, j, x):
951 """Sets value at each (i, j) to x
953 Here (i,j) index major and minor respectively, and must not contain
954 duplicate entries.
955 """
956 i, j, M, N = self._prepare_indices(i, j)
957 x = np.atleast_1d(np.asarray(x, dtype=self.dtype)).ravel()
959 n_samples = x.size
960 offsets = np.empty(n_samples, dtype=self.indices.dtype)
961 ret = csr_sample_offsets(M, N, self.indptr, self.indices, n_samples,
962 i, j, offsets)
963 if ret == 1:
964 # rinse and repeat
965 self.sum_duplicates()
966 csr_sample_offsets(M, N, self.indptr, self.indices, n_samples,
967 i, j, offsets)
969 if -1 not in offsets:
970 # only affects existing non-zero cells
971 self.data[offsets] = x
972 return
974 else:
975 warn("Changing the sparsity structure of a {}_matrix is expensive."
976 " lil_matrix is more efficient.".format(self.format),
977 SparseEfficiencyWarning, stacklevel=3)
978 # replace where possible
979 mask = offsets > -1
980 self.data[offsets[mask]] = x[mask]
981 # only insertions remain
982 mask = ~mask
983 i = i[mask]
984 i[i < 0] += M
985 j = j[mask]
986 j[j < 0] += N
987 self._insert_many(i, j, x[mask])
989 def _zero_many(self, i, j):
990 """Sets value at each (i, j) to zero, preserving sparsity structure.
992 Here (i,j) index major and minor respectively.
993 """
994 i, j, M, N = self._prepare_indices(i, j)
996 n_samples = len(i)
997 offsets = np.empty(n_samples, dtype=self.indices.dtype)
998 ret = csr_sample_offsets(M, N, self.indptr, self.indices, n_samples,
999 i, j, offsets)
1000 if ret == 1:
1001 # rinse and repeat
1002 self.sum_duplicates()
1003 csr_sample_offsets(M, N, self.indptr, self.indices, n_samples,
1004 i, j, offsets)
1006 # only assign zeros to the existing sparsity structure
1007 self.data[offsets[offsets > -1]] = 0
1009 def _insert_many(self, i, j, x):
1010 """Inserts new nonzero at each (i, j) with value x
1012 Here (i,j) index major and minor respectively.
1013 i, j and x must be non-empty, 1d arrays.
1014 Inserts each major group (e.g. all entries per row) at a time.
1015 Maintains has_sorted_indices property.
1016 Modifies i, j, x in place.
1017 """
1018 order = np.argsort(i, kind='mergesort') # stable for duplicates
1019 i = i.take(order, mode='clip')
1020 j = j.take(order, mode='clip')
1021 x = x.take(order, mode='clip')
1023 do_sort = self.has_sorted_indices
1025 # Update index data type
1026 idx_dtype = self._get_index_dtype((self.indices, self.indptr),
1027 maxval=(self.indptr[-1] + x.size))
1028 self.indptr = np.asarray(self.indptr, dtype=idx_dtype)
1029 self.indices = np.asarray(self.indices, dtype=idx_dtype)
1030 i = np.asarray(i, dtype=idx_dtype)
1031 j = np.asarray(j, dtype=idx_dtype)
1033 # Collate old and new in chunks by major index
1034 indices_parts = []
1035 data_parts = []
1036 ui, ui_indptr = np.unique(i, return_index=True)
1037 ui_indptr = np.append(ui_indptr, len(j))
1038 new_nnzs = np.diff(ui_indptr)
1039 prev = 0
1040 for c, (ii, js, je) in enumerate(zip(ui, ui_indptr, ui_indptr[1:])):
1041 # old entries
1042 start = self.indptr[prev]
1043 stop = self.indptr[ii]
1044 indices_parts.append(self.indices[start:stop])
1045 data_parts.append(self.data[start:stop])
1047 # handle duplicate j: keep last setting
1048 uj, uj_indptr = np.unique(j[js:je][::-1], return_index=True)
1049 if len(uj) == je - js:
1050 indices_parts.append(j[js:je])
1051 data_parts.append(x[js:je])
1052 else:
1053 indices_parts.append(j[js:je][::-1][uj_indptr])
1054 data_parts.append(x[js:je][::-1][uj_indptr])
1055 new_nnzs[c] = len(uj)
1057 prev = ii
1059 # remaining old entries
1060 start = self.indptr[ii]
1061 indices_parts.append(self.indices[start:])
1062 data_parts.append(self.data[start:])
1064 # update attributes
1065 self.indices = np.concatenate(indices_parts)
1066 self.data = np.concatenate(data_parts)
1067 nnzs = np.empty(self.indptr.shape, dtype=idx_dtype)
1068 nnzs[0] = idx_dtype(0)
1069 indptr_diff = np.diff(self.indptr)
1070 indptr_diff[ui] += new_nnzs
1071 nnzs[1:] = indptr_diff
1072 self.indptr = np.cumsum(nnzs, out=nnzs)
1074 if do_sort:
1075 # TODO: only sort where necessary
1076 self.has_sorted_indices = False
1077 self.sort_indices()
1079 self.check_format(full_check=False)
1081 ######################
1082 # Conversion methods #
1083 ######################
1085 def tocoo(self, copy=True):
1086 major_dim, minor_dim = self._swap(self.shape)
1087 minor_indices = self.indices
1088 major_indices = np.empty(len(minor_indices), dtype=self.indices.dtype)
1089 _sparsetools.expandptr(major_dim, self.indptr, major_indices)
1090 coords = self._swap((major_indices, minor_indices))
1092 return self._coo_container(
1093 (self.data, coords), self.shape, copy=copy, dtype=self.dtype
1094 )
1096 tocoo.__doc__ = _spbase.tocoo.__doc__
1098 def toarray(self, order=None, out=None):
1099 if out is None and order is None:
1100 order = self._swap('cf')[0]
1101 out = self._process_toarray_args(order, out)
1102 if not (out.flags.c_contiguous or out.flags.f_contiguous):
1103 raise ValueError('Output array must be C or F contiguous')
1104 # align ideal order with output array order
1105 if out.flags.c_contiguous:
1106 x = self.tocsr()
1107 y = out
1108 else:
1109 x = self.tocsc()
1110 y = out.T
1111 M, N = x._swap(x.shape)
1112 csr_todense(M, N, x.indptr, x.indices, x.data, y)
1113 return out
1115 toarray.__doc__ = _spbase.toarray.__doc__
1117 ##############################################################
1118 # methods that examine or modify the internal data structure #
1119 ##############################################################
1121 def eliminate_zeros(self):
1122 """Remove zero entries from the array/matrix
1124 This is an *in place* operation.
1125 """
1126 M, N = self._swap(self.shape)
1127 _sparsetools.csr_eliminate_zeros(M, N, self.indptr, self.indices,
1128 self.data)
1129 self.prune() # nnz may have changed
1131 @property
1132 def has_canonical_format(self) -> bool:
1133 """Whether the array/matrix has sorted indices and no duplicates
1135 Returns
1136 - True: if the above applies
1137 - False: otherwise
1139 has_canonical_format implies has_sorted_indices, so if the latter flag
1140 is False, so will the former be; if the former is found True, the
1141 latter flag is also set.
1142 """
1143 # first check to see if result was cached
1144 if not getattr(self, '_has_sorted_indices', True):
1145 # not sorted => not canonical
1146 self._has_canonical_format = False
1147 elif not hasattr(self, '_has_canonical_format'):
1148 self.has_canonical_format = bool(
1149 _sparsetools.csr_has_canonical_format(
1150 len(self.indptr) - 1, self.indptr, self.indices)
1151 )
1152 return self._has_canonical_format
1154 @has_canonical_format.setter
1155 def has_canonical_format(self, val: bool):
1156 self._has_canonical_format = bool(val)
1157 if val:
1158 self.has_sorted_indices = True
1160 def sum_duplicates(self):
1161 """Eliminate duplicate entries by adding them together
1163 This is an *in place* operation.
1164 """
1165 if self.has_canonical_format:
1166 return
1167 self.sort_indices()
1169 M, N = self._swap(self.shape)
1170 _sparsetools.csr_sum_duplicates(M, N, self.indptr, self.indices,
1171 self.data)
1173 self.prune() # nnz may have changed
1174 self.has_canonical_format = True
1176 @property
1177 def has_sorted_indices(self) -> bool:
1178 """Whether the indices are sorted
1180 Returns
1181 - True: if the indices of the array/matrix are in sorted order
1182 - False: otherwise
1183 """
1184 # first check to see if result was cached
1185 if not hasattr(self, '_has_sorted_indices'):
1186 self._has_sorted_indices = bool(
1187 _sparsetools.csr_has_sorted_indices(
1188 len(self.indptr) - 1, self.indptr, self.indices)
1189 )
1190 return self._has_sorted_indices
1192 @has_sorted_indices.setter
1193 def has_sorted_indices(self, val: bool):
1194 self._has_sorted_indices = bool(val)
1197 def sorted_indices(self):
1198 """Return a copy of this array/matrix with sorted indices
1199 """
1200 A = self.copy()
1201 A.sort_indices()
1202 return A
1204 # an alternative that has linear complexity is the following
1205 # although the previous option is typically faster
1206 # return self.toother().toother()
1208 def sort_indices(self):
1209 """Sort the indices of this array/matrix *in place*
1210 """
1212 if not self.has_sorted_indices:
1213 _sparsetools.csr_sort_indices(len(self.indptr) - 1, self.indptr,
1214 self.indices, self.data)
1215 self.has_sorted_indices = True
1217 def prune(self):
1218 """Remove empty space after all non-zero elements.
1219 """
1220 major_dim = self._swap(self.shape)[0]
1222 if len(self.indptr) != major_dim + 1:
1223 raise ValueError('index pointer has invalid length')
1224 if len(self.indices) < self.nnz:
1225 raise ValueError('indices array has fewer than nnz elements')
1226 if len(self.data) < self.nnz:
1227 raise ValueError('data array has fewer than nnz elements')
1229 self.indices = _prune_array(self.indices[:self.nnz])
1230 self.data = _prune_array(self.data[:self.nnz])
1232 def resize(self, *shape):
1233 shape = check_shape(shape)
1234 if hasattr(self, 'blocksize'):
1235 bm, bn = self.blocksize
1236 new_M, rm = divmod(shape[0], bm)
1237 new_N, rn = divmod(shape[1], bn)
1238 if rm or rn:
1239 raise ValueError("shape must be divisible into {} blocks. "
1240 "Got {}".format(self.blocksize, shape))
1241 M, N = self.shape[0] // bm, self.shape[1] // bn
1242 else:
1243 new_M, new_N = self._swap(shape)
1244 M, N = self._swap(self.shape)
1246 if new_M < M:
1247 self.indices = self.indices[:self.indptr[new_M]]
1248 self.data = self.data[:self.indptr[new_M]]
1249 self.indptr = self.indptr[:new_M + 1]
1250 elif new_M > M:
1251 self.indptr = np.resize(self.indptr, new_M + 1)
1252 self.indptr[M + 1:].fill(self.indptr[M])
1254 if new_N < N:
1255 mask = self.indices < new_N
1256 if not np.all(mask):
1257 self.indices = self.indices[mask]
1258 self.data = self.data[mask]
1259 major_index, val = self._minor_reduce(np.add, mask)
1260 self.indptr.fill(0)
1261 self.indptr[1:][major_index] = val
1262 np.cumsum(self.indptr, out=self.indptr)
1264 self._shape = shape
1266 resize.__doc__ = _spbase.resize.__doc__
1268 ###################
1269 # utility methods #
1270 ###################
1272 # needed by _data_matrix
1273 def _with_data(self, data, copy=True):
1274 """Returns a matrix with the same sparsity structure as self,
1275 but with different data. By default the structure arrays
1276 (i.e. .indptr and .indices) are copied.
1277 """
1278 if copy:
1279 return self.__class__((data, self.indices.copy(),
1280 self.indptr.copy()),
1281 shape=self.shape,
1282 dtype=data.dtype)
1283 else:
1284 return self.__class__((data, self.indices, self.indptr),
1285 shape=self.shape, dtype=data.dtype)
1287 def _binopt(self, other, op):
1288 """apply the binary operation fn to two sparse matrices."""
1289 other = self.__class__(other)
1291 # e.g. csr_plus_csr, csr_minus_csr, etc.
1292 fn = getattr(_sparsetools, self.format + op + self.format)
1294 maxnnz = self.nnz + other.nnz
1295 idx_dtype = self._get_index_dtype((self.indptr, self.indices,
1296 other.indptr, other.indices),
1297 maxval=maxnnz)
1298 indptr = np.empty(self.indptr.shape, dtype=idx_dtype)
1299 indices = np.empty(maxnnz, dtype=idx_dtype)
1301 bool_ops = ['_ne_', '_lt_', '_gt_', '_le_', '_ge_']
1302 if op in bool_ops:
1303 data = np.empty(maxnnz, dtype=np.bool_)
1304 else:
1305 data = np.empty(maxnnz, dtype=upcast(self.dtype, other.dtype))
1307 fn(self.shape[0], self.shape[1],
1308 np.asarray(self.indptr, dtype=idx_dtype),
1309 np.asarray(self.indices, dtype=idx_dtype),
1310 self.data,
1311 np.asarray(other.indptr, dtype=idx_dtype),
1312 np.asarray(other.indices, dtype=idx_dtype),
1313 other.data,
1314 indptr, indices, data)
1316 A = self.__class__((data, indices, indptr), shape=self.shape)
1317 A.prune()
1319 return A
1321 def _divide_sparse(self, other):
1322 """
1323 Divide this matrix by a second sparse matrix.
1324 """
1325 if other.shape != self.shape:
1326 raise ValueError('inconsistent shapes')
1328 r = self._binopt(other, '_eldiv_')
1330 if np.issubdtype(r.dtype, np.inexact):
1331 # Eldiv leaves entries outside the combined sparsity
1332 # pattern empty, so they must be filled manually.
1333 # Everything outside of other's sparsity is NaN, and everything
1334 # inside it is either zero or defined by eldiv.
1335 out = np.empty(self.shape, dtype=self.dtype)
1336 out.fill(np.nan)
1337 row, col = other.nonzero()
1338 out[row, col] = 0
1339 r = r.tocoo()
1340 out[r.row, r.col] = r.data
1341 out = self._container(out)
1342 else:
1343 # integers types go with nan <-> 0
1344 out = r
1346 return out
1349def _process_slice(sl, num):
1350 if sl is None:
1351 i0, i1 = 0, num
1352 elif isinstance(sl, slice):
1353 i0, i1, stride = sl.indices(num)
1354 if stride != 1:
1355 raise ValueError('slicing with step != 1 not supported')
1356 i0 = min(i0, i1) # give an empty slice when i0 > i1
1357 elif isintlike(sl):
1358 if sl < 0:
1359 sl += num
1360 i0, i1 = sl, sl + 1
1361 if i0 < 0 or i1 > num:
1362 raise IndexError('index out of bounds: 0 <= %d < %d <= %d' %
1363 (i0, i1, num))
1364 else:
1365 raise TypeError('expected slice or scalar')
1367 return i0, i1