''' Multidimensional sparse array.
Interface could be very similar to :class:`scipy:scipy.sparse.dok_matrix`.
However, this class is developed "on demand", so that the implementation
is not fully completed.
.. codeauthor:: Yoshifumi Futaana
'''
import itertools
import copy
import numpy as np
from irfpy.util.exception import PyanaError
[docs]class ndsparse_container:
''' N-dimensional sparse array.
This class provide a container part.
'''
def __init__(self, arg1):
''' Construct multi-dimensional sparse array.
1. From a dense array:
>>> mat = ndsparse([[0, 0, 0, 0, 2], [0, 3, 0, 0, 1], [0, 1, -1, 0, 0]])
will create (3, 5) shaped array.
>>> print(mat.shape)
(3, 5)
2. From a sparse array
>>> mat2 = ndsparse(mat)
>>> print(mat2.shape)
(3, 5)
3. By specifying the shape, if ``arg1`` is a tuple.
>>> mat3 = ndsparse((5, 3, 2))
>>> print(mat3.shape)
(5, 3, 2)
'''
self.data = {}
if isinstance(arg1, tuple):
self.shape = copy.copy(arg1)
self.ndim = len(self.shape)
elif isinstance(arg1, ndsparse):
self.data = copy.copy(arg1.sparsearray)
self.shape = copy.copy(arg1.shape)
self.ndim = arg1.ndim
else:
arr = np.array(arg1)
self.shape = arr.shape
self.ndim = arr.ndim
idx = np.array(np.where(arr != 0)) # (array(...), array(...), ...) -> (ndim, ndat) array
ndat = idx.shape[1]
for idat in range(ndat):
iidx = tuple(idx[:, idat])
self.data[iidx] = arr[iidx]
if self.ndim <= 1:
raise PyanaError('Use scipy.sparse module for 1-dimension purpose...')
[docs] def todense(self):
''' Convert to dense matrix.
>>> mat = ndsparse_container([[0, 0, 0, 0, 2], [0, 3, 0, 0, 1], [0, 1, -1, 0, 0]])
>>> print(mat.todense())
[[ 0. 0. 0. 0. 2.]
[ 0. 3. 0. 0. 1.]
[ 0. 1. -1. 0. 0.]]
'''
densearray = np.zeros(self.shape)
for idx in self.data:
densearray[idx] = self.data[idx]
return densearray
def __getitem__(self, key):
''' Get item by index.
>>> mat = ndsparse_container([[0, 0, 0, 0, 2], [0, 3, 0, 0, 1], [0, 1, -1, 0, 0]])
>>> print(mat.shape)
(3, 5)
>>> print(mat[0, 0])
0
>>> print(mat[0, 4])
2
>>> print(mat[2, 2])
-1
>>> try:
... print(mat[3, 2])
... print('Error not raised')
... except IndexError as e:
... print('Error raised')
Error raised
>>> print(mat[1:3, 2:-1])
[[ 0 0 1]
[-1 0 0]]
.. note::
Due to itertools.islice limitation, negative step is not
supported...
'''
if len(key) != self.ndim:
raise PyanaError('Dimension mismatch: Given={}/Mustbe={}'.format(
len(key), self.ndim))
key_slice = []
for idim, kk in enumerate(key):
if isinstance(kk, int):
if kk < 0:
k = self.shape[idim] - kk
else:
k = kk
key_slice.append([i for i in itertools.islice(range(self.shape[idim]), k, k + 1)])
elif isinstance(kk, slice):
if kk.start < 0:
k0 = self.shape[idim] - kk.start
else:
k0 = kk.start
if kk.stop < 0:
k1 = self.shape[idim] - kk.stop
else:
k1 = kk.stop
key_slice.append([i for i in itertools.islice(range(self.shape[idim]), k0, k1, kk.step)])
else:
raise PyanaError('Whats up? Type of key={}'.format(type(key)))
shape = np.array(self.shape, dtype=int)
val = []
for idx in itertools.product(*key_slice):
if idx in self.data:
val.append(self.data[idx])
else:
npkey = np.array(idx, dtype=int)
if (npkey < 0).any() or (npkey >= shape).any():
raise IndexError('No index, {}'.format(key))
val.append(0)
newshape = [len(k) for k in key_slice]
if (np.array(newshape) == 0).any():
raise IndexError('No index, {}'.format(key))
if (np.array(newshape) == 1).all():
return np.array(val).flatten()[0]
else:
return np.array(val).reshape(newshape)
# if key in self.data:
# return self.data[key]
# else:
# shape = np.array(self.shape, dtype=int)
# npkey = np.array(key, dtype=int)
# if (npkey < 0).any() or (npkey >= shape).any():
# raise IndexError('No index, {}'.format(key))
# else:
# return 0
def __setitem__(self, key, val):
''' Set item by index.
>>> mat = ndsparse_container([[0, 0, 0, 0, 2], [0, 3, 0, 0, 1], [0, 1, -1, 0, 0]])
>>> print(mat.shape)
(3, 5)
>>> print(mat[1, 4])
1
>>> mat[1, 4] = 3.5
>>> print(mat[1, 4])
3.5
>>> mat[1, 4] = 0
>>> print(mat[1, 4])
0
.. todo::
SLICE support.
'''
if len(key) != self.ndim:
raise PyanaError('Dimension mismatch: Given={}/Mustbe={}'.format(
len(key), self.ndim))
shape = np.array(self.shape, dtype=int)
npkey = np.array(key, dtype=int)
if (npkey < 0).any() or (npkey >= shape).any():
raise IndexError('No index, {}'.format(key))
if val == 0:
if key in self.data:
self.data.pop(key)
else:
self.data[key] = val
[docs]class ndsparse:
''' N-dimensional sparse array.
'''
def __init__(self, arg1, **argv):
''' Create a sparse array container.
See :class:`ndsparse_container`.
'''
self.sparsearray = ndsparse_container(arg1, **argv)
self.shape = self.sparsearray.shape
self.ndim = self.sparsearray.ndim
def __getitem__(self, key):
''' Return the stored value.
'''
return self.sparsearray[key]
def __setitem__(self, key, value):
self.sparsearray[key] = value
[docs] def todense(self):
return self.sparsearray.todense()
[docs] def sum(self, axis=None):
raise NotImplementedError('')
import unittest
import doctest
[docs]def doctests():
return unittest.TestSuite((
doctest.DocTestSuite(),
))
if __name__ == '__main__':
unittest.main(defaultTest='doctests')