Skip to content

add full function to simplify COO creation #150

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ flags:
project:
enabled: yes
target: 95%
threshold: 1%
if_no_uploads: error
if_not_found: success
if_ci_failed: error
Expand All @@ -21,7 +20,6 @@ flags:
default:
enabled: yes
target: 95%
threshold: 1%
if_no_uploads: error
if_not_found: success
if_ci_failed: error
6 changes: 6 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ source=
omit=
sparse/_version.py
sparse/tests/*

[report]
exclude_lines =
pragma: no cover
return NotImplemented
raise NotImplementedError
6 changes: 6 additions & 0 deletions docs/generated/sparse.full.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
full
====

.. currentmodule:: sparse

.. autofunction:: full
2 changes: 2 additions & 0 deletions docs/generated/sparse.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ API

elemwise

full

nanmax

nanmin
Expand Down
13 changes: 7 additions & 6 deletions docs/operations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,20 @@ results for both Numpy arrays, COO arrays, or a mix of the two:

np.log(X.dot(beta.T) + 1)

However some operations are not supported, like inplace operations,
operations that implicitly cause dense structures,
or numpy functions that are not yet implemented for sparse arrays
However some operations are not supported, like operations that
implicitly cause dense structures, or numpy functions that are not
yet implemented for sparse arrays.

.. code-block:: python

x += y # inplace operations not supported
x + 1 # operations that produce dense results not supported
np.svd(x) # sparse svd not implemented


This page describes those valid operations, and their limitations.

:obj:`elemwise`
~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~
This function allows you to apply any arbitrary broadcasting function to any number of arguments
where the arguments can be :obj:`SparseArray` objects or :obj:`scipy.sparse.spmatrix` objects.
For example, the following will add two arrays:
Expand Down Expand Up @@ -155,7 +154,9 @@ be a :obj:`scipy.sparse.spmatrix` The following operators are supported:
* :obj:`operator.lshift` (:code:`x << y`)
* :obj:`operator.rshift` (:code:`x >> y`)

.. note:: In-place operators are not supported at this time.
.. warning::
While in-place operations are supported for compatibility with Numpy,
they are not truly in-place, and will effectively calculate the result separately.

.. _operations-elemwise:

Expand Down
2 changes: 1 addition & 1 deletion sparse/coo/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .core import COO
from .umath import elemwise
from .common import tensordot, dot, concatenate, stack, triu, tril, where, \
nansum, nanprod, nanmin, nanmax, nanreduce
nansum, nanprod, nanmin, nanmax, nanreduce, full
31 changes: 31 additions & 0 deletions sparse/coo/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -616,3 +616,34 @@ def nanreduce(x, method, identity=None, axis=None, keepdims=False, **kwargs):
"""
arr = _replace_nan(x, method.identity if identity is None else identity)
return arr.reduce(method, axis, keepdims, **kwargs)


def full(coords, fill_value, dtype=None, **kwargs):
"""
Return a new COO of with a given sparsity pattern, filled with fill_value.

Parameters
----------
coords : numpy.ndarray (COO.ndim, COO.nnz)
Index locations of nonzero values.
fill_value: scalar
Value of array at nonzero indices.
dtype : data-type (optional)
The data-type for the array. If None, defaults to
`np.array(fill_value).dtype`.
kwargs : dict (optional)
Additional arguments to pass to ``COO`` constructor.

Returns
-------
COO
Newly constructed sparse array.

See Also
--------
numpy.full : Analogous Numpy function.
"""
from .core import COO

d = np.full(np.asarray(coords).shape[1], fill_value, dtype=dtype)
return COO(coords, data=d, **kwargs)
44 changes: 26 additions & 18 deletions sparse/coo/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,9 @@ def __init__(self, coords, data=None, shape=None, has_duplicates=True,
data = coords[0]
coords = np.stack(coords[1], axis=0)

self.data = np.asarray(data)
self.coords = np.asarray(coords)
self.data = np.broadcast_to(data, self.coords.shape[-1])

if self.coords.ndim == 1:
self.coords = self.coords[None, :]

Expand All @@ -243,7 +244,7 @@ def __init__(self, coords, data=None, shape=None, has_duplicates=True,
else:
dtype = np.uint8
self.coords = self.coords.astype(dtype)
assert not self.shape or len(data) == self.coords.shape[1]
assert not self.shape or len(self.data) == self.coords.shape[1]

if not sorted:
self._sort_indices()
Expand Down Expand Up @@ -676,8 +677,7 @@ def sum(self, axis=None, keepdims=False, dtype=None, out=None):
>>> s.sum()
25
"""
assert out is None
return self.reduce(np.add, axis=axis, keepdims=keepdims, dtype=dtype)
return np.add.reduce(self, out=out, axis=axis, keepdims=keepdims, dtype=dtype)

def max(self, axis=None, keepdims=False, out=None):
"""
Expand Down Expand Up @@ -738,8 +738,7 @@ def max(self, axis=None, keepdims=False, out=None):
>>> s.max()
8
"""
assert out is None
return self.reduce(np.maximum, axis=axis, keepdims=keepdims)
return np.maximum.reduce(self, out=out, axis=axis, keepdims=keepdims)

def min(self, axis=None, keepdims=False, out=None):
"""
Expand Down Expand Up @@ -800,8 +799,7 @@ def min(self, axis=None, keepdims=False, out=None):
>>> s.min()
0
"""
assert out is None
return self.reduce(np.minimum, axis=axis, keepdims=keepdims)
return np.minimum.reduce(self, out=out, axis=axis, keepdims=keepdims)

def prod(self, axis=None, keepdims=False, dtype=None, out=None):
"""
Expand Down Expand Up @@ -867,8 +865,7 @@ def prod(self, axis=None, keepdims=False, dtype=None, out=None):
>>> s.prod()
0
"""
assert out is None
return self.reduce(np.multiply, axis=axis, keepdims=keepdims, dtype=dtype)
return np.multiply.reduce(self, out=out, axis=axis, keepdims=keepdims, dtype=dtype)

def transpose(self, axes=None):
"""
Expand Down Expand Up @@ -1039,16 +1036,27 @@ def __rmatmul__(self, other):

def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
out = kwargs.pop('out', None)
if out is not None:
if out is not None and not all(isinstance(x, COO) for x in out):
return NotImplemented

if method == '__call__':
return elemwise(ufunc, *inputs, **kwargs)
result = elemwise(ufunc, *inputs, **kwargs)
elif method == 'reduce':
return COO._reduce(ufunc, *inputs, **kwargs)
result = COO._reduce(ufunc, *inputs, **kwargs)
else:
return NotImplemented

if out is not None:
(out,) = out
if out.shape != result.shape:
raise ValueError('non-broadcastable output operand with shape %s'
'doesn\'t match the broadcast shape %s' % (out.shape, result.shape))

out._make_shallow_copy_of(result)
return out

return result

def __array__(self, dtype=None, **kwargs):
x = self.todense()
if dtype and x.dtype != dtype:
Expand Down Expand Up @@ -1366,10 +1374,11 @@ def round(self, decimals=0, out=None):
The :code:`out` parameter is provided just for compatibility with Numpy and isn't
actually supported.
"""
assert out is None
return elemwise(np.round, self, decimals=decimals)
if out is not None and not isinstance(out, tuple):
out = (out,)
return self.__array_ufunc__(np.round, '__call__', self, decimals=decimals, out=out)

def astype(self, dtype, out=None):
def astype(self, dtype):
"""
Copy of the array, cast to a specified type.

Expand All @@ -1385,8 +1394,7 @@ def astype(self, dtype, out=None):
The :code:`out` parameter is provided just for compatibility with Numpy and isn't
actually supported.
"""
assert out is None
return elemwise(np.ndarray.astype, self, dtype=dtype)
return self.__array_ufunc__(np.ndarray.astype, '__call__', self, dtype=dtype)

def maybe_densify(self, max_size=1000, min_density=0.25):
"""
Expand Down
3 changes: 1 addition & 2 deletions sparse/coo/umath.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ def elemwise(func, *args, **kwargs):
elif isinstance(arg, SparseArray) and not isinstance(arg, COO):
args[i] = COO(arg)
elif not isinstance(arg, COO):
raise ValueError("Performing this operation would produce "
"a dense result: %s" % str(func))
return NotImplemented

# Filter out scalars as they are 'baked' into the function.
func = PositinalArgumentPartial(func, pos, posargs)
Expand Down
Loading