From 5c75e7dd927679d9a28271beb9509abdc584e345 Mon Sep 17 00:00:00 2001 From: kangzhiq <709563092@qq.com> Date: Sun, 7 Jul 2019 02:10:29 +0200 Subject: [PATCH 1/7] Implemented LIL format for sp array - Added new class lil, initial version --- sympy/tensor/array/__init__.py | 1 + sympy/tensor/array/sp_utils.py | 123 +++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 sympy/tensor/array/sp_utils.py diff --git a/sympy/tensor/array/__init__.py b/sympy/tensor/array/__init__.py index ecad09e7545d..407594d46979 100644 --- a/sympy/tensor/array/__init__.py +++ b/sympy/tensor/array/__init__.py @@ -205,5 +205,6 @@ from .ndim_array import NDimArray from .arrayop import tensorproduct, tensorcontraction, derive_by_array, permutedims from .array_comprehension import ArrayComprehension +from .sp_utils import lil Array = ImmutableDenseNDimArray diff --git a/sympy/tensor/array/sp_utils.py b/sympy/tensor/array/sp_utils.py new file mode 100644 index 000000000000..f83d84575c17 --- /dev/null +++ b/sympy/tensor/array/sp_utils.py @@ -0,0 +1,123 @@ +from sympy.core.sympify import _sympify +from sympy import Tuple, Basic +from sympy.tensor.array.ndim_array import NDimArray +from sympy.tensor.array.sparse_ndim_array import SparseNDimArray +from sympy.tensor.array.dense_ndim_array import DenseNDimArray +from sympy.core.compatibility import Iterable + +import functools + +# Row-based linked list sparse matrix(LIL) +class lil(Basic, NDimArray): + """ + Create a sparse array with Row-based linked list sparse matrix(LIL) format. + This data structure is efficient in incremental construction of sparse arrays. + + Note + ==== + + This is an experimental implementation of algorithm, the compatibility with + existing format needs to be verified. Only some basic operations are implemented. + + Examples + ======== + + >>> from sympy.tensor.array.sp_utils import lil + >>> a = lil([[0, 1, 0], [2, 0, 0]]) + >>> a._data + [[1], [2]] + >>> a._rows + [[1], [0]] + >>> a[0, 1] + 1 + >>> a[0, 1] = 3 + >>> a.tolist() + [[0, 3, 0], [2, 0, 0]] + """ + def __new__(cls, iterable=None, shape=None, **kwargs): + shape, flat_list = cls._handle_ndarray_creation_inputs(iterable, shape, **kwargs) + shape = Tuple(*map(_sympify, shape)) + cls._check_special_bounds(flat_list, shape) + loop_size = functools.reduce(lambda x,y: x*y, shape) if shape else 0 + + # Ideally we should use the Array module for this empty initialization, but + # it is not yet supported in SymPy + data = cls._empty(shape) + rows = cls._empty(shape) + + self = Basic.__new__(cls, data, rows, shape, **kwargs) + self._shape = shape + self._rank = len(shape) + self._loop_size = loop_size + + new_dict = {} + if isinstance(iterable, Iterable): + iterable = SparseNDimArray(iterable) + + if isinstance(iterable, SparseNDimArray): + new_dict = iterable._sparse_array + # if it is a dense array, it would be cast to a default sparse array + # and then convert to a LIL format + elif isinstance(iterable, DenseNDimArray): + sp = SparseNDimArray(iterable) + new_dict = sp._sparse_array + else: + raise NotImplementedError("Data type not yet supported") + + # Add non zero value to internal list. + # This operation can be simplified once Array module supports tuple index + for k, v in new_dict.items(): + idx = self._get_tuple_index(k) + self._get_row(data, idx).append(v) + self._get_row(rows, idx).append(idx[-1]) + + self._data = data + self._rows = rows + return self + + @classmethod + def _empty(cls, shape): + def f(s): + if len(s) == 0: + return [] + if len(s) == 1: + return [[] for i in range(s[0])] + arr = f(s[1:]) + return [arr for i in range(s[0])] + + if not shape: + raise ValueError("Shape must be defined") + return f(shape[:-1]) + + def _get_row(self, iterable, index): + temp_iter = iterable + for i in index[:-1]: + temp_iter = temp_iter[i] + return temp_iter + + + def __getitem__(self, index): + if not isinstance(index, (tuple, Tuple)): + index = self._get_tuple_index(index) + row_values = self._get_row(self._data, index) + row_indices = self._get_row(self._rows, index) + + if index[-1] in row_indices: + value_index = row_indices.index(index[-1]) + return row_values[value_index] + else: + return 0 + + # For mutable arrays + def __setitem__(self, index, value): + if not isinstance(index, (tuple, Tuple)): + index = self._get_tuple_index(index) + row_values = self._get_row(self._data, index) + row_indices = self._get_row(self._rows, index) + + if index[-1] in row_indices: + value_index = row_indices.index(index[-1]) + row_values[value_index] = value + else: + row_values.append(value) + row_indices.append(index[-1]) From 07c88e64976e90edf876011c3b72191f3ca48be7 Mon Sep 17 00:00:00 2001 From: kangzhiq <709563092@qq.com> Date: Mon, 15 Jul 2019 00:08:34 +0800 Subject: [PATCH 2/7] Update implementation of format - Ameliorated implementation of lil - Added implementation of coo after testing - Added basic implementation of csr --- sympy/tensor/array/__init__.py | 2 +- sympy/tensor/array/sp_utils.py | 166 +++++++++++++++++++++++++++++++-- 2 files changed, 160 insertions(+), 8 deletions(-) diff --git a/sympy/tensor/array/__init__.py b/sympy/tensor/array/__init__.py index 407594d46979..4a602473ea00 100644 --- a/sympy/tensor/array/__init__.py +++ b/sympy/tensor/array/__init__.py @@ -205,6 +205,6 @@ from .ndim_array import NDimArray from .arrayop import tensorproduct, tensorcontraction, derive_by_array, permutedims from .array_comprehension import ArrayComprehension -from .sp_utils import lil +from .sp_utils import lil, coo Array = ImmutableDenseNDimArray diff --git a/sympy/tensor/array/sp_utils.py b/sympy/tensor/array/sp_utils.py index f83d84575c17..fe0a5d01e938 100644 --- a/sympy/tensor/array/sp_utils.py +++ b/sympy/tensor/array/sp_utils.py @@ -7,8 +7,15 @@ import functools +# A set of functions that could be adde to the base class +# in order to avoid code repetition +def check_bound(shape, index): + if len(shape) != len(index): + return False + return all([shape[i] > index[i] for i in range(len(shape))]) + # Row-based linked list sparse matrix(LIL) -class lil(Basic, NDimArray): +class lil(Basic, SparseNDimArray): """ Create a sparse array with Row-based linked list sparse matrix(LIL) format. This data structure is efficient in incremental construction of sparse arrays. @@ -36,7 +43,8 @@ class lil(Basic, NDimArray): """ def __new__(cls, iterable=None, shape=None, **kwargs): shape, flat_list = cls._handle_ndarray_creation_inputs(iterable, shape, **kwargs) - shape = Tuple(*map(_sympify, shape)) + #shape = Tuple(*map(_sympify, shape)) + shape = Tuple.fromiter(_sympify(i) for i in shape) cls._check_special_bounds(flat_list, shape) loop_size = functools.reduce(lambda x,y: x*y, shape) if shape else 0 @@ -51,16 +59,15 @@ def __new__(cls, iterable=None, shape=None, **kwargs): self._loop_size = loop_size new_dict = {} + # if it is a dense array, it would be cast to a default sparse array + # and then convert to a LIL format if isinstance(iterable, Iterable): iterable = SparseNDimArray(iterable) if isinstance(iterable, SparseNDimArray): new_dict = iterable._sparse_array - # if it is a dense array, it would be cast to a default sparse array - # and then convert to a LIL format - elif isinstance(iterable, DenseNDimArray): - sp = SparseNDimArray(iterable) - new_dict = sp._sparse_array + + # TODO: Enable the initialization with (data, rows) as a tuple else: raise NotImplementedError("Data type not yet supported") @@ -95,10 +102,18 @@ def _get_row(self, iterable, index): temp_iter = temp_iter[i] return temp_iter + def __iter__(self): + def iterator(): + for i in range(self._loop_size): + yield self[i] + return iterator() def __getitem__(self, index): if not isinstance(index, (tuple, Tuple)): index = self._get_tuple_index(index) + if not check_bound(self._shape, index): + raise ValueError('Index ' + str(index) + ' out of border') + row_values = self._get_row(self._data, index) row_indices = self._get_row(self._rows, index) @@ -112,6 +127,9 @@ def __getitem__(self, index): def __setitem__(self, index, value): if not isinstance(index, (tuple, Tuple)): index = self._get_tuple_index(index) + if not check_bound(self._shape, index): + raise ValueError('Index ' + str(index) + ' out of border') + row_values = self._get_row(self._data, index) row_indices = self._get_row(self._rows, index) @@ -121,3 +139,137 @@ def __setitem__(self, index, value): else: row_values.append(value) row_indices.append(index[-1]) + + # TODO: convert to other formats + + +class coo(Basic, SparseNDimArray): + ''' + Coordinate list format. This format is not very efficient for calculation. But it + can be easily cast to csr/csc format. + ''' + def __new__(cls, iterable=None, shape=None, **kwargs): + shape, flat_list = cls._handle_ndarray_creation_inputs(iterable, shape, **kwargs) + shape = Tuple.fromiter(_sympify(i) for i in shape) + cls._check_special_bounds(flat_list, shape) + loop_size = functools.reduce(lambda x,y: x*y, shape) if shape else 0 + + data = [] + coor = [[] for i in range(len(shape))] + + self = Basic.__new__(cls, data, coor, shape, **kwargs) + self._data = data + self._coor = coor + self._shape = shape + self._rank = len(shape) + self._loop_size = loop_size + + if isinstance(iterable, Iterable): + iterable = SparseNDimArray(iterable) + + if isinstance(iterable, SparseNDimArray): + for k, v in sorted(iterable._sparse_array.items()): + data.append(v) + idx = self._get_tuple_index(k) + for i in range(len(shape)): + coor[i].append(idx[i]) + # TODO: Enable the initialization with (data, coor) as a tuple + else: + raise NotImplementedError("Data type not yet supported") + + return self + + def __getitem__(self, index): + if not isinstance(index, (tuple, Tuple)): + index = self._get_tuple_index(index) + if not check_bound(self._shape, index): + raise ValueError('Index ' + str(index) + ' out of border') + + for i in range(len(self._data)): + if all([index[j] == self._coor[j][i] for j in range(self._rank)]): + return self._data[i] + return 0 + + def __iter__(self): + def iterator(): + for i in range(self._loop_size): + yield self[i] + return iterator() + + # TODO: convert to other formats + def tocsr(self): + + +class csr(Basic, SparseNDimArray): + ''' + Compressed Sparse Row. This format is widely used. It has the following advantages: + Efficient item access and slicing + Fast arithmetics + Fast vector products + ''' + def __new__(cls, iterable=None, shape=None, **kwargs): + shape, flat_list = cls._handle_ndarray_creation_inputs(iterable, shape, **kwargs) + shape = Tuple.fromiter(_sympify(i) for i in shape) + cls._check_special_bounds(flat_list, shape) + loop_size = functools.reduce(lambda x,y: x*y, shape) if shape else 0 + + data = [] + col_ind = [] + row_ptr = cls._empty(shape) + + self = Basic.__new__(cls, data, col_ind, row_ptr, shape, **kwargs) + self._data = data + self._col_ind = col_ind + self._row_ptr = row_ptr + self._shape = shape + self._rank = len(shape) + self._loop_size = loop_size + + counter = 0 + if isinstance(iterable, Iterable): + iterable = SparseNDimArray(iterable) + + if isinstance(iterable, SparseNDimArray): + for k, v in sorted(iterable._sparse_array.items()): + data.append(v) + idx = self._get_tuple_index(k) + col_ind.append(idx[-1]) + self._get_row(row_ptr, idx) = counter + counter += 1 + + + # TODO: Enable the initialization with (data, col_ind, row_ptr) as a tuple + else: + raise NotImplementedError("Data type not yet supported") + + # Repetitous code, should be regrouped in the base class + @classmethod + def _empty(cls, shape): + def f(s): + if len(s) == 0: + return [] + if len(s) == 1: + return [[] for i in range(s[0])] + arr = f(s[1:]) + return [arr for i in range(s[0])] + + if not shape: + raise ValueError("Shape must be defined") + return f(shape[:-1]) + + def _get_row(self, iterable, index): + temp_iter = iterable + for i in index[:-1]: + temp_iter = temp_iter[i] + return temp_iter + + def __getitem__(self, index): + if not isinstance(index, (tuple, Tuple)): + index = self._get_tuple_index(index) + if not check_bound(self._shape, index): + raise ValueError('Index ' + str(index) + ' out of border') + + row_start = self._row_ptr[index[:-1]] + if row_start == []: + return 0 + \ No newline at end of file From 46432cd37d7d2a0c3760381163bf88b735226fc1 Mon Sep 17 00:00:00 2001 From: kangzhiq <709563092@qq.com> Date: Wed, 17 Jul 2019 01:09:46 +0800 Subject: [PATCH 3/7] Basic structure of CSR format - Implemented basic structure of csr format - Added some illustration for each format --- sympy/tensor/array/__init__.py | 2 +- sympy/tensor/array/sp_utils.py | 152 ++++++++++++++++++++++++--------- 2 files changed, 112 insertions(+), 42 deletions(-) diff --git a/sympy/tensor/array/__init__.py b/sympy/tensor/array/__init__.py index 4a602473ea00..364c797a2f65 100644 --- a/sympy/tensor/array/__init__.py +++ b/sympy/tensor/array/__init__.py @@ -205,6 +205,6 @@ from .ndim_array import NDimArray from .arrayop import tensorproduct, tensorcontraction, derive_by_array, permutedims from .array_comprehension import ArrayComprehension -from .sp_utils import lil, coo +from .sp_utils import lil, coo, csr Array = ImmutableDenseNDimArray diff --git a/sympy/tensor/array/sp_utils.py b/sympy/tensor/array/sp_utils.py index fe0a5d01e938..679c5fb01b3e 100644 --- a/sympy/tensor/array/sp_utils.py +++ b/sympy/tensor/array/sp_utils.py @@ -8,7 +8,7 @@ import functools # A set of functions that could be adde to the base class -# in order to avoid code repetition +# in order to avoid code repetition def check_bound(shape, index): if len(shape) != len(index): return False @@ -23,23 +23,32 @@ class lil(Basic, SparseNDimArray): Note ==== - This is an experimental implementation of algorithm, the compatibility with - existing format needs to be verified. Only some basic operations are implemented. + An example of storage: + A = [[1, 0, 0] + [0, 2, 0] + [0, 0, 3]] + shapeA = (3, 3) + With lil, we will have a multidimensional list '._data' containing all non-zero + value in a row majored order and another list '._rows' containing column index of + each non-zero value in each row. + + It is like we remove all zero from the initial array and only store the non-zero + value and its column index. Examples ======== >>> from sympy.tensor.array.sp_utils import lil - >>> a = lil([[0, 1, 0], [2, 0, 0]]) + >>> a = lil([[1, 0, 0], [0, 2, 0], [0, 0, 3]]) >>> a._data - [[1], [2]] + [[1], [2], [3]] >>> a._rows - [[1], [0]] + [[1], [2], [3]] >>> a[0, 1] 1 >>> a[0, 1] = 3 >>> a.tolist() - [[0, 3, 0], [2, 0, 0]] + [[1, 0, 0], [0, 2, 0], [0, 0, 3]] """ def __new__(cls, iterable=None, shape=None, **kwargs): shape, flat_list = cls._handle_ndarray_creation_inputs(iterable, shape, **kwargs) @@ -146,7 +155,25 @@ def __setitem__(self, index, value): class coo(Basic, SparseNDimArray): ''' Coordinate list format. This format is not very efficient for calculation. But it - can be easily cast to csr/csc format. + can be easily cast to csr/csc format. + + Note + ==== + + An example of storage: + A = [[1, 0, 0] + [0, 2, 0] + [0, 0, 3]] + Here we have a flatten list '._data' for all non-zero values. Another list '._coor' + contains as many as the number of rank lists, which represent the coordinate(the + tuple index) of the value. + In this case: + >>> sp_A = coo(A) + >>> sp_A._data + [1, 2, 3] + >>> sp_A._coor + [[0, 1, 2] + 0, 1, 2]] ''' def __new__(cls, iterable=None, shape=None, **kwargs): shape, flat_list = cls._handle_ndarray_creation_inputs(iterable, shape, **kwargs) @@ -196,16 +223,40 @@ def iterator(): yield self[i] return iterator() - # TODO: convert to other formats - def tocsr(self): - class csr(Basic, SparseNDimArray): ''' Compressed Sparse Row. This format is widely used. It has the following advantages: - Efficient item access and slicing - Fast arithmetics - Fast vector products + - Efficient item access and slicing + - Fast arithmetics + - Fast vector products + + Note + ==== + + An example of storage: + A = [[1, 0, 0] + [0, 2, 0] + [0, 0, 3]] + This format has 3 list. '._data' is a flatten list for all non-zero value. '.col_ind' + has the column index of each non-zero value in their row. '._row_ptr' is the pointer + of the first non-zero value in each row. + The notion of row is the last dimension, which means that for an array of shape (a, b, c, d), + it will have (a, b, c) rows. And the (a, b, c) is also flatten for the purpose of simplicity. + + In this case, we have: + >>> sp_A._data + [1, 2, 3] + >>> sp_A._col_ind + [0, 1, 2] + >>> sp_A._row_ptr + [0, 1, 2, 4] + + For the last list, there is one more element in the end(we have 3 rows but 4 pointers). + This is a convention for csr format, where we store number_of_non_zero_value+1 in the end + of the list, so that the algorithm can handle the operation for the last row. + In this case, last value 4 in '._row_ptr' represents 3+1, that is to say that there are 3 + non-zero values in this array. ''' def __new__(cls, iterable=None, shape=None, **kwargs): shape, flat_list = cls._handle_ndarray_creation_inputs(iterable, shape, **kwargs) @@ -215,7 +266,7 @@ def __new__(cls, iterable=None, shape=None, **kwargs): data = [] col_ind = [] - row_ptr = cls._empty(shape) + row_ptr = [] self = Basic.__new__(cls, data, col_ind, row_ptr, shape, **kwargs) self._data = data @@ -225,43 +276,46 @@ def __new__(cls, iterable=None, shape=None, **kwargs): self._rank = len(shape) self._loop_size = loop_size - counter = 0 if isinstance(iterable, Iterable): iterable = SparseNDimArray(iterable) if isinstance(iterable, SparseNDimArray): - for k, v in sorted(iterable._sparse_array.items()): + current_row = 0 + for i, (k, v) in enumerate(sorted(iterable._sparse_array.items())): data.append(v) idx = self._get_tuple_index(k) col_ind.append(idx[-1]) - self._get_row(row_ptr, idx) = counter - counter += 1 - + row = self.calculate_integer_index(idx[:-1]) + if row == current_row: + self._row_ptr.append(i) + current_row += 1 + elif row > current_row: + last_value = self._row_ptr[-1] + for j in range(current_row, row): + self._row_ptr.append(last_value) + self._row_ptr.append(i) + current_row = row + 1 + self._row_ptr.append(len(iterable._sparse_array) + 1) # TODO: Enable the initialization with (data, col_ind, row_ptr) as a tuple else: raise NotImplementedError("Data type not yet supported") - # Repetitous code, should be regrouped in the base class - @classmethod - def _empty(cls, shape): - def f(s): - if len(s) == 0: - return [] - if len(s) == 1: - return [[] for i in range(s[0])] - arr = f(s[1:]) - return [arr for i in range(s[0])] + return self - if not shape: - raise ValueError("Shape must be defined") - return f(shape[:-1]) + def calculate_integer_index(self, tuple_index): + integer_idx = 0 + for i, idx in enumerate(tuple_index): + integer_idx = integer_idx*self._shape[i] + tuple_index[i] + return integer_idx - def _get_row(self, iterable, index): - temp_iter = iterable - for i in index[:-1]: - temp_iter = temp_iter[i] - return temp_iter + def calculate_tuple_index(self, integer_idx): + index = [] + for i, sh in enumerate(reversed(self._shape[:-1])): + index.append(integer_index % sh) + integer_index //= sh + index.reverse() + return tuple(index) def __getitem__(self, index): if not isinstance(index, (tuple, Tuple)): @@ -269,7 +323,23 @@ def __getitem__(self, index): if not check_bound(self._shape, index): raise ValueError('Index ' + str(index) + ' out of border') - row_start = self._row_ptr[index[:-1]] - if row_start == []: + row_idx = self.calculate_integer_index(index[:-1]) + row_start = self._row_ptr[row_idx] + row_end = self._row_ptr[row_idx + 1] + row_values = self._data[row_start:row_end] + + col_idx_start = self._row_ptr[row_idx] + col_idx_end = self._row_ptr[row_idx + 1] + col_idx = list(self._col_ind[col_idx_start:col_idx_end]) + + if index[-1] in col_idx: + value_index = col_idx.index(index[-1]) + return row_values[value_index] + else: return 0 - \ No newline at end of file + + def __iter__(self): + def iterator(): + for i in range(self._loop_size): + yield self[i] + return iterator() From 2cb732355a2fa117e6a3eca4d85b6030c554fedd Mon Sep 17 00:00:00 2001 From: kangzhiq <709563092@qq.com> Date: Fri, 19 Jul 2019 10:49:07 +0800 Subject: [PATCH 4/7] Added tests and updated codes - Added tests for new class - Ameliorate code for new class --- sympy/core/tests/test_args.py | 18 +++++ sympy/tensor/array/__init__.py | 2 +- sympy/tensor/array/sp_utils.py | 82 +++++++++++++---------- sympy/tensor/array/tests/test_sp_utils.py | 32 +++++++++ 4 files changed, 99 insertions(+), 35 deletions(-) create mode 100644 sympy/tensor/array/tests/test_sp_utils.py diff --git a/sympy/core/tests/test_args.py b/sympy/core/tests/test_args.py index 1a03df681874..6becbbde6f12 100644 --- a/sympy/core/tests/test_args.py +++ b/sympy/core/tests/test_args.py @@ -3915,6 +3915,24 @@ def test_sympy__tensor__array__array_comprehension__ArrayComprehension(): assert _test_args(arrcom) +def test_sympy__tensor__array__sp_utils__LilSparseArray(): + from sympy.tensor.array.sp_utils import LilSparseArray + lil = LilSparseArray([[1, 0, 0], [0, 1, 0]]) + assert _test_args(lil) + + +def test_sympy__tensor__array__sp_utils__CooSparseArray(): + from sympy.tensor.array.sp_utils import CooSparseArray + coo = CooSparseArray([[1, 0, 0], [0, 1, 0]]) + assert _test_args(coo) + + +def test_sympy__tensor__array__sp_utils__CsrSparseArray(): + from sympy.tensor.array.sp_utils import CsrSparseArray + csr = CsrSparseArray([[1, 0, 0], [0, 1, 0]]) + assert _test_args(csr) + + def test_sympy__tensor__functions__TensorProduct(): from sympy.tensor.functions import TensorProduct tp = TensorProduct(3, 4, evaluate=False) diff --git a/sympy/tensor/array/__init__.py b/sympy/tensor/array/__init__.py index 364c797a2f65..e4d0e59bfd0f 100644 --- a/sympy/tensor/array/__init__.py +++ b/sympy/tensor/array/__init__.py @@ -205,6 +205,6 @@ from .ndim_array import NDimArray from .arrayop import tensorproduct, tensorcontraction, derive_by_array, permutedims from .array_comprehension import ArrayComprehension -from .sp_utils import lil, coo, csr +from .sp_utils import LilSparseArray, CooSparseArray, CsrSparseArray Array = ImmutableDenseNDimArray diff --git a/sympy/tensor/array/sp_utils.py b/sympy/tensor/array/sp_utils.py index 679c5fb01b3e..c1934f8cab3d 100644 --- a/sympy/tensor/array/sp_utils.py +++ b/sympy/tensor/array/sp_utils.py @@ -1,21 +1,29 @@ from sympy.core.sympify import _sympify -from sympy import Tuple, Basic +from sympy import Tuple, Basic, S from sympy.tensor.array.ndim_array import NDimArray from sympy.tensor.array.sparse_ndim_array import SparseNDimArray from sympy.tensor.array.dense_ndim_array import DenseNDimArray +from sympy.core.sympify import sympify from sympy.core.compatibility import Iterable import functools -# A set of functions that could be adde to the base class -# in order to avoid code repetition -def check_bound(shape, index): - if len(shape) != len(index): - return False - return all([shape[i] > index[i] for i in range(len(shape))]) + +class SparseArrayFormat(SparseNDimArray): + def _check_bound(self, shape, index): + if len(shape) != len(index): + return False + return all([shape[i] > index[i] for i in range(len(shape))]) + + # A test for mul and rmul, this code should be added to the original + # function once the test is valide + def __mul__(self, other): + other = sympify(other) + return self.toscr()._mul(other) + # Row-based linked list sparse matrix(LIL) -class lil(Basic, SparseNDimArray): +class LilSparseArray(SparseArrayFormat): """ Create a sparse array with Row-based linked list sparse matrix(LIL) format. This data structure is efficient in incremental construction of sparse arrays. @@ -38,17 +46,18 @@ class lil(Basic, SparseNDimArray): Examples ======== - >>> from sympy.tensor.array.sp_utils import lil - >>> a = lil([[1, 0, 0], [0, 2, 0], [0, 0, 3]]) - >>> a._data - [[1], [2], [3]] - >>> a._rows + >>> from sympy.tensor.array.sp_utils import LilSparseArray + >>> A = [[1, 0, 0,], [0, 2, 0], [0, 0, 3]] + >>> sp_A_lil = LilSparseArray(A) + >>> sp_A_lil._data [[1], [2], [3]] - >>> a[0, 1] + >>> sp_A_lil._rows + [[0], [1], [2]] + >>> sp_A_lil[0, 0] 1 - >>> a[0, 1] = 3 - >>> a.tolist() - [[1, 0, 0], [0, 2, 0], [0, 0, 3]] + >>> sp_A_lil[0, 1] = 3 + >>> sp_A_lil.tolist() + [[1, 3, 0], [0, 2, 0], [0, 0, 3]] """ def __new__(cls, iterable=None, shape=None, **kwargs): shape, flat_list = cls._handle_ndarray_creation_inputs(iterable, shape, **kwargs) @@ -62,7 +71,7 @@ def __new__(cls, iterable=None, shape=None, **kwargs): data = cls._empty(shape) rows = cls._empty(shape) - self = Basic.__new__(cls, data, rows, shape, **kwargs) + self = Basic.__new__(cls, data, rows, shape,) self._shape = shape self._rank = len(shape) self._loop_size = loop_size @@ -120,7 +129,7 @@ def iterator(): def __getitem__(self, index): if not isinstance(index, (tuple, Tuple)): index = self._get_tuple_index(index) - if not check_bound(self._shape, index): + if not self._check_bound(self._shape, index): raise ValueError('Index ' + str(index) + ' out of border') row_values = self._get_row(self._data, index) @@ -136,7 +145,7 @@ def __getitem__(self, index): def __setitem__(self, index, value): if not isinstance(index, (tuple, Tuple)): index = self._get_tuple_index(index) - if not check_bound(self._shape, index): + if not self._check_bound(self._shape, index): raise ValueError('Index ' + str(index) + ' out of border') row_values = self._get_row(self._data, index) @@ -152,7 +161,7 @@ def __setitem__(self, index, value): # TODO: convert to other formats -class coo(Basic, SparseNDimArray): +class CooSparseArray(SparseArrayFormat): ''' Coordinate list format. This format is not very efficient for calculation. But it can be easily cast to csr/csc format. @@ -168,12 +177,14 @@ class coo(Basic, SparseNDimArray): contains as many as the number of rank lists, which represent the coordinate(the tuple index) of the value. In this case: - >>> sp_A = coo(A) - >>> sp_A._data + >>> from sympy.tensor.array.sp_utils import CooSparseArray + >>> A = [[1, 0, 0,], [0, 2, 0], [0, 0, 3]] + >>> sp_A_coo = CooSparseArray(A) + >>> sp_A_coo._data [1, 2, 3] - >>> sp_A._coor - [[0, 1, 2] - 0, 1, 2]] + >>> sp_A_coo._coor + [[0, 1, 2], + [0, 1, 2]] ''' def __new__(cls, iterable=None, shape=None, **kwargs): shape, flat_list = cls._handle_ndarray_creation_inputs(iterable, shape, **kwargs) @@ -184,7 +195,7 @@ def __new__(cls, iterable=None, shape=None, **kwargs): data = [] coor = [[] for i in range(len(shape))] - self = Basic.__new__(cls, data, coor, shape, **kwargs) + self = object.__new__(cls) self._data = data self._coor = coor self._shape = shape @@ -209,7 +220,7 @@ def __new__(cls, iterable=None, shape=None, **kwargs): def __getitem__(self, index): if not isinstance(index, (tuple, Tuple)): index = self._get_tuple_index(index) - if not check_bound(self._shape, index): + if not self._check_bound(self._shape, index): raise ValueError('Index ' + str(index) + ' out of border') for i in range(len(self._data)): @@ -224,7 +235,7 @@ def iterator(): return iterator() -class csr(Basic, SparseNDimArray): +class CsrSparseArray(SparseArrayFormat): ''' Compressed Sparse Row. This format is widely used. It has the following advantages: - Efficient item access and slicing @@ -245,11 +256,14 @@ class csr(Basic, SparseNDimArray): it will have (a, b, c) rows. And the (a, b, c) is also flatten for the purpose of simplicity. In this case, we have: - >>> sp_A._data + >>> from sympy.tensor.array.sp_utils import CsrSparseArray + >>> A = [[1, 0, 0,], [0, 2, 0], [0, 0, 3]] + >>> sp_A_csr = CsrSparseArray(A) + >>> sp_A_csr._data [1, 2, 3] - >>> sp_A._col_ind + >>> sp_A_csr._col_ind [0, 1, 2] - >>> sp_A._row_ptr + >>> sp_A_csr._row_ptr [0, 1, 2, 4] For the last list, there is one more element in the end(we have 3 rows but 4 pointers). @@ -268,7 +282,7 @@ def __new__(cls, iterable=None, shape=None, **kwargs): col_ind = [] row_ptr = [] - self = Basic.__new__(cls, data, col_ind, row_ptr, shape, **kwargs) + self = object.__new__(cls) self._data = data self._col_ind = col_ind self._row_ptr = row_ptr @@ -320,7 +334,7 @@ def calculate_tuple_index(self, integer_idx): def __getitem__(self, index): if not isinstance(index, (tuple, Tuple)): index = self._get_tuple_index(index) - if not check_bound(self._shape, index): + if not self._check_bound(self._shape, index): raise ValueError('Index ' + str(index) + ' out of border') row_idx = self.calculate_integer_index(index[:-1]) diff --git a/sympy/tensor/array/tests/test_sp_utils.py b/sympy/tensor/array/tests/test_sp_utils.py new file mode 100644 index 000000000000..b575afdc3205 --- /dev/null +++ b/sympy/tensor/array/tests/test_sp_utils.py @@ -0,0 +1,32 @@ +from sympy.tensor.array.sp_utils import LilSparseArray, CooSparseArray, CsrSparseArray + + +a = [[1, 0, 1, 0], + [0, 0, 0, 1], + [1, 0, 0, 1], + [0, 0, 1, 0]] + +def test_sparse_array_format(): + for sp_format in [LilSparseArray, CooSparseArray, CsrSparseArray]: + A = sp_format(a) + assert len(A) == 16 + assert A.shape == (4, 4) + assert A.rank() == 2 + assert A.tolist() == [[1, 0, 1, 0], [0, 0, 0, 1], [1, 0, 0, 1], [0, 0, 1, 0]] + assert (A + A).tolist() == [[2, 0, 2, 0], [0, 0, 0, 2], [2, 0, 0, 2], [0, 0, 2, 0]] + +def test_lil_format(): + A = LilSparseArray(a) + assert A._data == [[1, 1], [1], [1, 1], [1]] + assert A._rows == [[0, 2], [3], [0, 3], [2]] + +def test_coo_format(): + A = CooSparseArray(a) + assert A._data == [1, 1, 1, 1, 1, 1] + assert A._coor == [[0, 0, 1, 2, 2, 3], [0, 2, 3, 0, 3, 2]] + +def test_csr_format(): + A = CsrSparseArray(a) + assert A._data == [1, 1, 1, 1, 1, 1] + assert A._col_ind == [0, 2, 3, 0, 3, 2] + assert A._row_ptr == [0, 2, 3, 5, 7] From d30af934b45a2be5bdbf6c22b9fb937b7bb9ea7e Mon Sep 17 00:00:00 2001 From: kangzhiq <709563092@qq.com> Date: Wed, 24 Jul 2019 01:39:45 +0800 Subject: [PATCH 5/7] Added new constructor and convertion of format - Added new way to initialize a new format, which can facilitate the convertion of different format - Added an example of converint COO to CSR - Added tests accordingly --- sympy/tensor/array/sp_utils.py | 99 ++++++++++++++++------- sympy/tensor/array/tests/test_sp_utils.py | 9 +++ 2 files changed, 77 insertions(+), 31 deletions(-) diff --git a/sympy/tensor/array/sp_utils.py b/sympy/tensor/array/sp_utils.py index c1934f8cab3d..ae4c6af0354e 100644 --- a/sympy/tensor/array/sp_utils.py +++ b/sympy/tensor/array/sp_utils.py @@ -1,6 +1,6 @@ from sympy.core.sympify import _sympify from sympy import Tuple, Basic, S -from sympy.tensor.array.ndim_array import NDimArray +from sympy.tensor.array.ndim_array import ImmutableNDimArray from sympy.tensor.array.sparse_ndim_array import SparseNDimArray from sympy.tensor.array.dense_ndim_array import DenseNDimArray from sympy.core.sympify import sympify @@ -9,7 +9,10 @@ import functools -class SparseArrayFormat(SparseNDimArray): +class SparseArrayFormat(SparseNDimArray, ImmutableNDimArray): + """ + A base class for all sparse array formats. It is supposed to be Immutable. + """ def _check_bound(self, shape, index): if len(shape) != len(index): return False @@ -21,6 +24,23 @@ def __mul__(self, other): other = sympify(other) return self.toscr()._mul(other) + def __setitem__(self, index, value): + raise TypeError('Immutable N-dim array') + + def _calculate_integer_index(self, tuple_index): + integer_idx = 0 + for i, idx in enumerate(tuple_index): + integer_idx = integer_idx*self._shape[i] + tuple_index[i] + return integer_idx + + def _calculate_tuple_index(self, integer_idx): + index = [] + for i, sh in enumerate(reversed(self._shape[:-1])): + index.append(integer_index % sh) + integer_index //= sh + index.reverse() + return tuple(index) + # Row-based linked list sparse matrix(LIL) class LilSparseArray(SparseArrayFormat): @@ -71,12 +91,19 @@ def __new__(cls, iterable=None, shape=None, **kwargs): data = cls._empty(shape) rows = cls._empty(shape) - self = Basic.__new__(cls, data, rows, shape,) + self = Basic.__new__(cls, data, rows, shape, **kwargs) self._shape = shape self._rank = len(shape) self._loop_size = loop_size new_dict = {} + + # initialization with (data, rows) as a tuple + if isinstance(iterable, (tuple, Tuple)): + self._data = iterable[0] + self._rows = iterable[1] + return self + # if it is a dense array, it would be cast to a default sparse array # and then convert to a LIL format if isinstance(iterable, Iterable): @@ -85,7 +112,6 @@ def __new__(cls, iterable=None, shape=None, **kwargs): if isinstance(iterable, SparseNDimArray): new_dict = iterable._sparse_array - # TODO: Enable the initialization with (data, rows) as a tuple else: raise NotImplementedError("Data type not yet supported") @@ -195,13 +221,19 @@ def __new__(cls, iterable=None, shape=None, **kwargs): data = [] coor = [[] for i in range(len(shape))] - self = object.__new__(cls) + self = Basic.__new__(cls, data, coor, shape, **kwargs) self._data = data self._coor = coor self._shape = shape self._rank = len(shape) self._loop_size = loop_size + # initialization with (data, coor) as a tuple + if isinstance(iterable, (tuple, Tuple)): + self._data = iterable[0] + self._coor = iterable[1] + return self + if isinstance(iterable, Iterable): iterable = SparseNDimArray(iterable) @@ -211,9 +243,7 @@ def __new__(cls, iterable=None, shape=None, **kwargs): idx = self._get_tuple_index(k) for i in range(len(shape)): coor[i].append(idx[i]) - # TODO: Enable the initialization with (data, coor) as a tuple - else: - raise NotImplementedError("Data type not yet supported") + return self @@ -234,6 +264,25 @@ def iterator(): yield self[i] return iterator() + def tocsr(self): + current_row = 0 + row_ptr = [] + for i in range(len(self._data)): + tuple_idx = tuple([self._coor[j][i] for j in range(len(self._shape))]) + int_idx = self._calculate_integer_index(tuple_idx[:-1]) + if int_idx == current_row: + row_ptr.append(i) + current_row += 1 + if int_idx > current_row: + last_value = row_ptr[-1] + row_ptr += [last_value for j in range(current_row, int_idx)] + row_ptr.append(i) + current_row = row + 1 + row_ptr.append(len(self._data) + 1) + return CsrSparseArray((self._data, self._coor[len(self._shape)-1], row_ptr), + self._shape) + + class CsrSparseArray(SparseArrayFormat): ''' @@ -282,7 +331,7 @@ def __new__(cls, iterable=None, shape=None, **kwargs): col_ind = [] row_ptr = [] - self = object.__new__(cls) + self = Basic.__new__(cls, data, col_ind, row_ptr, **kwargs) self._data = data self._col_ind = col_ind self._row_ptr = row_ptr @@ -290,6 +339,13 @@ def __new__(cls, iterable=None, shape=None, **kwargs): self._rank = len(shape) self._loop_size = loop_size + # initialization with (data, col_ind, row_ptr) as a tuple + if isinstance(iterable, (tuple, Tuple)): + self._data = iterable[0] + self._col_ind = iterable[1] + self._row_ptr = iterable[2] + return self + if isinstance(iterable, Iterable): iterable = SparseNDimArray(iterable) @@ -299,45 +355,26 @@ def __new__(cls, iterable=None, shape=None, **kwargs): data.append(v) idx = self._get_tuple_index(k) col_ind.append(idx[-1]) - row = self.calculate_integer_index(idx[:-1]) + row = self._calculate_integer_index(idx[:-1]) if row == current_row: self._row_ptr.append(i) current_row += 1 elif row > current_row: last_value = self._row_ptr[-1] - for j in range(current_row, row): - self._row_ptr.append(last_value) + self._row_ptr += [last_value for j in range(current_row, row)] self._row_ptr.append(i) current_row = row + 1 self._row_ptr.append(len(iterable._sparse_array) + 1) - # TODO: Enable the initialization with (data, col_ind, row_ptr) as a tuple - else: - raise NotImplementedError("Data type not yet supported") - return self - def calculate_integer_index(self, tuple_index): - integer_idx = 0 - for i, idx in enumerate(tuple_index): - integer_idx = integer_idx*self._shape[i] + tuple_index[i] - return integer_idx - - def calculate_tuple_index(self, integer_idx): - index = [] - for i, sh in enumerate(reversed(self._shape[:-1])): - index.append(integer_index % sh) - integer_index //= sh - index.reverse() - return tuple(index) - def __getitem__(self, index): if not isinstance(index, (tuple, Tuple)): index = self._get_tuple_index(index) if not self._check_bound(self._shape, index): raise ValueError('Index ' + str(index) + ' out of border') - row_idx = self.calculate_integer_index(index[:-1]) + row_idx = self._calculate_integer_index(index[:-1]) row_start = self._row_ptr[row_idx] row_end = self._row_ptr[row_idx + 1] row_values = self._data[row_start:row_end] diff --git a/sympy/tensor/array/tests/test_sp_utils.py b/sympy/tensor/array/tests/test_sp_utils.py index b575afdc3205..4931a56e81e0 100644 --- a/sympy/tensor/array/tests/test_sp_utils.py +++ b/sympy/tensor/array/tests/test_sp_utils.py @@ -19,14 +19,23 @@ def test_lil_format(): A = LilSparseArray(a) assert A._data == [[1, 1], [1], [1, 1], [1]] assert A._rows == [[0, 2], [3], [0, 3], [2]] + B = LilSparseArray(([[1, 1], [1], [1, 1], [1]], [[0, 2], [3], [0, 3], [2]]), (4, 4)) + assert A.tolist() == B.tolist() def test_coo_format(): A = CooSparseArray(a) assert A._data == [1, 1, 1, 1, 1, 1] assert A._coor == [[0, 0, 1, 2, 2, 3], [0, 2, 3, 0, 3, 2]] + B = CooSparseArray(([1, 1, 1, 1, 1, 1], [[0, 0, 1, 2, 2, 3], [0, 2, 3, 0, 3, 2]]), (4, 4)) + assert A.tolist() == B.tolist() + + C = B.tocsr() + C.tolist() == CsrSparseArray(a).tolist() def test_csr_format(): A = CsrSparseArray(a) assert A._data == [1, 1, 1, 1, 1, 1] assert A._col_ind == [0, 2, 3, 0, 3, 2] assert A._row_ptr == [0, 2, 3, 5, 7] + B = CsrSparseArray(([1, 1, 1, 1, 1, 1], [0, 2, 3, 0, 3, 2], [0, 2, 3, 5, 7]), (4, 4)) + assert A.tolist() == B.tolist() From 7b7846090cfd81dcd7123c30a524fe8d2ee46a4e Mon Sep 17 00:00:00 2001 From: kangzhiq <709563092@qq.com> Date: Mon, 5 Aug 2019 00:24:16 +0800 Subject: [PATCH 6/7] Added new conversion of format - Removed inheritance of Basic - Added conversion from LIL to CSR format - Added tests accordingly --- sympy/tensor/array/sp_utils.py | 38 ++++++++++++++++++----- sympy/tensor/array/tests/test_sp_utils.py | 5 +++ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/sympy/tensor/array/sp_utils.py b/sympy/tensor/array/sp_utils.py index ae4c6af0354e..6324042e1f91 100644 --- a/sympy/tensor/array/sp_utils.py +++ b/sympy/tensor/array/sp_utils.py @@ -5,11 +5,13 @@ from sympy.tensor.array.dense_ndim_array import DenseNDimArray from sympy.core.sympify import sympify from sympy.core.compatibility import Iterable +from sympy.core.numbers import Integer +from sympy.core.compatibility import SYMPY_INTS import functools -class SparseArrayFormat(SparseNDimArray, ImmutableNDimArray): +class SparseArrayFormat(SparseNDimArray): """ A base class for all sparse array formats. It is supposed to be Immutable. """ @@ -77,7 +79,7 @@ class LilSparseArray(SparseArrayFormat): 1 >>> sp_A_lil[0, 1] = 3 >>> sp_A_lil.tolist() - [[1, 3, 0], [0, 2, 0], [0, 0, 3]] + [[1, 0, 0], [0, 2, 0], [0, 0, 3]] """ def __new__(cls, iterable=None, shape=None, **kwargs): shape, flat_list = cls._handle_ndarray_creation_inputs(iterable, shape, **kwargs) @@ -91,7 +93,7 @@ def __new__(cls, iterable=None, shape=None, **kwargs): data = cls._empty(shape) rows = cls._empty(shape) - self = Basic.__new__(cls, data, rows, shape, **kwargs) + self = object.__new__(cls) self._shape = shape self._rank = len(shape) self._loop_size = loop_size @@ -185,6 +187,28 @@ def __setitem__(self, index, value): row_indices.append(index[-1]) # TODO: convert to other formats + def tocsr(self): + _data = [] + _col_ind = [] + _row_ptr = [] + + def loop(data, rows): + if len(data) > 0 and not isinstance(data[0], (Integer, SYMPY_INTS)): + for i in range(len(data)): + loop(data[i], rows[i]) + elif len(data) == 0: + last_ptr = _row_ptr[-1] if len(_row_ptr) > 0 else 0 + _row_ptr.append(last_ptr) + else: + _row_ptr.append(len(_data)) + for j in range(len(data)): + _data.append(data[j]) + _col_ind.append(rows[j]) + + loop(self._data, self._rows) + _row_ptr.append(self._loop_size + 1) + + return CsrSparseArray((_data, _col_ind, _row_ptr), self._shape) class CooSparseArray(SparseArrayFormat): @@ -221,7 +245,7 @@ def __new__(cls, iterable=None, shape=None, **kwargs): data = [] coor = [[] for i in range(len(shape))] - self = Basic.__new__(cls, data, coor, shape, **kwargs) + self = object.__new__(cls) self._data = data self._coor = coor self._shape = shape @@ -306,7 +330,7 @@ class CsrSparseArray(SparseArrayFormat): In this case, we have: >>> from sympy.tensor.array.sp_utils import CsrSparseArray - >>> A = [[1, 0, 0,], [0, 2, 0], [0, 0, 3]] + >>> A = [[1, 0, 0], [0, 2, 0], [0, 0, 3]] >>> sp_A_csr = CsrSparseArray(A) >>> sp_A_csr._data [1, 2, 3] @@ -331,7 +355,7 @@ def __new__(cls, iterable=None, shape=None, **kwargs): col_ind = [] row_ptr = [] - self = Basic.__new__(cls, data, col_ind, row_ptr, **kwargs) + self = object.__new__(cls) self._data = data self._col_ind = col_ind self._row_ptr = row_ptr @@ -360,7 +384,7 @@ def __new__(cls, iterable=None, shape=None, **kwargs): self._row_ptr.append(i) current_row += 1 elif row > current_row: - last_value = self._row_ptr[-1] + last_value = self._row_ptr[-1] if len(self._row_ptr)>0 else 0 self._row_ptr += [last_value for j in range(current_row, row)] self._row_ptr.append(i) current_row = row + 1 diff --git a/sympy/tensor/array/tests/test_sp_utils.py b/sympy/tensor/array/tests/test_sp_utils.py index 4931a56e81e0..555199d304b5 100644 --- a/sympy/tensor/array/tests/test_sp_utils.py +++ b/sympy/tensor/array/tests/test_sp_utils.py @@ -22,6 +22,10 @@ def test_lil_format(): B = LilSparseArray(([[1, 1], [1], [1, 1], [1]], [[0, 2], [3], [0, 3], [2]]), (4, 4)) assert A.tolist() == B.tolist() + C = B.tocsr() + C.tolist() == CsrSparseArray(a).tolist() + + def test_coo_format(): A = CooSparseArray(a) assert A._data == [1, 1, 1, 1, 1, 1] @@ -32,6 +36,7 @@ def test_coo_format(): C = B.tocsr() C.tolist() == CsrSparseArray(a).tolist() + def test_csr_format(): A = CsrSparseArray(a) assert A._data == [1, 1, 1, 1, 1, 1] From efcf4d7de6ca85d0e448bb9ba00cd30407cd3f06 Mon Sep 17 00:00:00 2001 From: kangzhiq <709563092@qq.com> Date: Wed, 7 Aug 2019 10:03:47 +0800 Subject: [PATCH 7/7] Removed args test --- sympy/core/tests/test_args.py | 18 ------------------ sympy/tensor/array/sp_utils.py | 3 --- 2 files changed, 21 deletions(-) diff --git a/sympy/core/tests/test_args.py b/sympy/core/tests/test_args.py index 6becbbde6f12..1a03df681874 100644 --- a/sympy/core/tests/test_args.py +++ b/sympy/core/tests/test_args.py @@ -3915,24 +3915,6 @@ def test_sympy__tensor__array__array_comprehension__ArrayComprehension(): assert _test_args(arrcom) -def test_sympy__tensor__array__sp_utils__LilSparseArray(): - from sympy.tensor.array.sp_utils import LilSparseArray - lil = LilSparseArray([[1, 0, 0], [0, 1, 0]]) - assert _test_args(lil) - - -def test_sympy__tensor__array__sp_utils__CooSparseArray(): - from sympy.tensor.array.sp_utils import CooSparseArray - coo = CooSparseArray([[1, 0, 0], [0, 1, 0]]) - assert _test_args(coo) - - -def test_sympy__tensor__array__sp_utils__CsrSparseArray(): - from sympy.tensor.array.sp_utils import CsrSparseArray - csr = CsrSparseArray([[1, 0, 0], [0, 1, 0]]) - assert _test_args(csr) - - def test_sympy__tensor__functions__TensorProduct(): from sympy.tensor.functions import TensorProduct tp = TensorProduct(3, 4, evaluate=False) diff --git a/sympy/tensor/array/sp_utils.py b/sympy/tensor/array/sp_utils.py index 6324042e1f91..13e198cf58e4 100644 --- a/sympy/tensor/array/sp_utils.py +++ b/sympy/tensor/array/sp_utils.py @@ -77,7 +77,6 @@ class LilSparseArray(SparseArrayFormat): [[0], [1], [2]] >>> sp_A_lil[0, 0] 1 - >>> sp_A_lil[0, 1] = 3 >>> sp_A_lil.tolist() [[1, 0, 0], [0, 2, 0], [0, 0, 3]] """ @@ -268,7 +267,6 @@ def __new__(cls, iterable=None, shape=None, **kwargs): for i in range(len(shape)): coor[i].append(idx[i]) - return self def __getitem__(self, index): @@ -307,7 +305,6 @@ def tocsr(self): self._shape) - class CsrSparseArray(SparseArrayFormat): ''' Compressed Sparse Row. This format is widely used. It has the following advantages: