From 924118c1b0d59fb9f555073cbc2e289d7bb201bb Mon Sep 17 00:00:00 2001 From: David Mertz Date: Tue, 2 May 2017 08:49:34 -0700 Subject: [PATCH 1/8] Add @ operator --- sparse/core.py | 5 +++++ sparse/tests/test_core.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/sparse/core.py b/sparse/core.py index be9c06c7..680fac4f 100644 --- a/sparse/core.py +++ b/sparse/core.py @@ -328,6 +328,11 @@ def T(self): def dot(self, other): return dot(self, other) + __matmul__ = dot + + def __rmatmul__(self, other): + return dot(other, self) + def reshape(self, shape): if self.shape == shape: return self diff --git a/sparse/tests/test_core.py b/sparse/tests/test_core.py index e531245f..798ecefe 100644 --- a/sparse/tests/test_core.py +++ b/sparse/tests/test_core.py @@ -1,5 +1,6 @@ import pytest +import sys import random import operator import numpy as np @@ -136,6 +137,10 @@ def test_dot(): assert_eq(a.dot(b), sa.dot(sb)) assert_eq(np.dot(a, b), sparse.dot(sa, sb)) + if sys.version_info >= (3, 5): + assert_eq(eval("a @ b"), eval("sa @ sb")) + assert_eq(eval("sa @ sb"), sparse.dot(sa, sb)) + @pytest.mark.parametrize('func', [np.expm1, np.log1p, np.sin, np.tan, np.sinh, np.tanh, np.floor, np.ceil, From 3a7b720939bd79f11947746ee053d1cf1011db01 Mon Sep 17 00:00:00 2001 From: David Mertz Date: Tue, 2 May 2017 12:39:07 -0700 Subject: [PATCH 2/8] test for __rmatmul__ --- sparse/tests/test_core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sparse/tests/test_core.py b/sparse/tests/test_core.py index 798ecefe..f3b3ece5 100644 --- a/sparse/tests/test_core.py +++ b/sparse/tests/test_core.py @@ -140,6 +140,7 @@ def test_dot(): if sys.version_info >= (3, 5): assert_eq(eval("a @ b"), eval("sa @ sb")) assert_eq(eval("sa @ sb"), sparse.dot(sa, sb)) + assert_eq(eval("a @ sb"), sparse.dot(a, sb)) @pytest.mark.parametrize('func', [np.expm1, np.log1p, np.sin, np.tan, From 4e3e3435de91fcdbb162ca798b591cea3548d223 Mon Sep 17 00:00:00 2001 From: David Mertz Date: Tue, 2 May 2017 13:10:19 -0700 Subject: [PATCH 3/8] Better implementation of __rmatmul__ and test that exercises the actual code, not NumPy's --- sparse/core.py | 2 +- sparse/tests/test_core.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sparse/core.py b/sparse/core.py index 680fac4f..7ba3698b 100644 --- a/sparse/core.py +++ b/sparse/core.py @@ -331,7 +331,7 @@ def dot(self, other): __matmul__ = dot def __rmatmul__(self, other): - return dot(other, self) + return dot(np.array(other), self) def reshape(self, shape): if self.shape == shape: diff --git a/sparse/tests/test_core.py b/sparse/tests/test_core.py index f3b3ece5..30eeae2e 100644 --- a/sparse/tests/test_core.py +++ b/sparse/tests/test_core.py @@ -130,6 +130,7 @@ def test_tensordot(a_shape, b_shape, axes): def test_dot(): a = random_x((3, 4, 5)) b = random_x((5, 6)) + lol = [[1, 2, 3, 4, 5]] sa = COO.from_numpy(a) sb = COO.from_numpy(b) @@ -140,7 +141,7 @@ def test_dot(): if sys.version_info >= (3, 5): assert_eq(eval("a @ b"), eval("sa @ sb")) assert_eq(eval("sa @ sb"), sparse.dot(sa, sb)) - assert_eq(eval("a @ sb"), sparse.dot(a, sb)) + assert_eq(eval("lol @ b"), eval("lol @ sb")) @pytest.mark.parametrize('func', [np.expm1, np.log1p, np.sin, np.tan, From 45770f6c6d0dcfefbbf90f81b72c651d3c84970a Mon Sep 17 00:00:00 2001 From: David Mertz Date: Tue, 2 May 2017 15:55:04 -0700 Subject: [PATCH 4/8] Not final (probably), but implement both directions of COO @ list --- sparse/core.py | 6 +++++- sparse/tests/test_core.py | 5 +++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/sparse/core.py b/sparse/core.py index 7ba3698b..1d7fcffc 100644 --- a/sparse/core.py +++ b/sparse/core.py @@ -328,7 +328,11 @@ def T(self): def dot(self, other): return dot(self, other) - __matmul__ = dot + def __matmul__(self, other): + try: + return dot(self, other) + except AttributeError: + return dot(self, np.array(other)) def __rmatmul__(self, other): return dot(np.array(other), self) diff --git a/sparse/tests/test_core.py b/sparse/tests/test_core.py index 30eeae2e..e7cc5b04 100644 --- a/sparse/tests/test_core.py +++ b/sparse/tests/test_core.py @@ -130,7 +130,7 @@ def test_tensordot(a_shape, b_shape, axes): def test_dot(): a = random_x((3, 4, 5)) b = random_x((5, 6)) - lol = [[1, 2, 3, 4, 5]] + l = [1, 2, 3, 4, 5] sa = COO.from_numpy(a) sb = COO.from_numpy(b) @@ -141,7 +141,8 @@ def test_dot(): if sys.version_info >= (3, 5): assert_eq(eval("a @ b"), eval("sa @ sb")) assert_eq(eval("sa @ sb"), sparse.dot(sa, sb)) - assert_eq(eval("lol @ b"), eval("lol @ sb")) + assert_eq(eval("l @ b"), eval("l @ sb")) # __rmatmul__ + assert_eq(eval("a @ sb"), sparse.dot(a, sb)) # __rmatmul__ @pytest.mark.parametrize('func', [np.expm1, np.log1p, np.sin, np.tan, From 9656393114c47d8cef1736cda7f6fb62ab9f28fa Mon Sep 17 00:00:00 2001 From: David Mertz Date: Tue, 2 May 2017 16:48:14 -0700 Subject: [PATCH 5/8] Tweak to pass Travis CI (flake8 is really picky) --- sparse/core.py | 7 +++---- sparse/tests/test_core.py | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sparse/core.py b/sparse/core.py index 1d7fcffc..c4cf407c 100644 --- a/sparse/core.py +++ b/sparse/core.py @@ -326,14 +326,13 @@ def T(self): return self.transpose(list(range(self.ndim))[::-1]) def dot(self, other): - return dot(self, other) - - def __matmul__(self, other): try: return dot(self, other) - except AttributeError: + except AttributeError: # missing .ndim return dot(self, np.array(other)) + __matmul__ = dot + def __rmatmul__(self, other): return dot(np.array(other), self) diff --git a/sparse/tests/test_core.py b/sparse/tests/test_core.py index e7cc5b04..29f4098b 100644 --- a/sparse/tests/test_core.py +++ b/sparse/tests/test_core.py @@ -131,6 +131,7 @@ def test_dot(): a = random_x((3, 4, 5)) b = random_x((5, 6)) l = [1, 2, 3, 4, 5] + l # silencing flake8 sa = COO.from_numpy(a) sb = COO.from_numpy(b) From d385699c6dfd960510e6338f1a2f34e79a0bf55f Mon Sep 17 00:00:00 2001 From: David Mertz Date: Tue, 2 May 2017 22:33:11 -0700 Subject: [PATCH 6/8] Improve logic for coercion to (dense) array --- sparse/core.py | 22 +++++++++++++-------- sparse/tests/test_core.py | 40 +++++++++++++++++++++++++++++++++++---- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/sparse/core.py b/sparse/core.py index c4cf407c..cafd79a7 100644 --- a/sparse/core.py +++ b/sparse/core.py @@ -89,7 +89,10 @@ class COO(object): """ __array_priority__ = 12 - def __init__(self, coords, data=None, shape=None, has_duplicates=True): + def __init__(self, coords, data=None, shape=None, + has_duplicates=True, toarray_other=False): + + self._toarray_other = toarray_other if data is None: # {(i, j, k): x, (i, j, k): y, ...} if isinstance(coords, dict): @@ -325,16 +328,14 @@ def transpose(self, axes=None): def T(self): return self.transpose(list(range(self.ndim))[::-1]) - def dot(self, other): - try: - return dot(self, other) - except AttributeError: # missing .ndim - return dot(self, np.array(other)) + def dot(self, other, make_array=False): + return dot(self, other, + make_array=make_array or self._toarray_other) __matmul__ = dot def __rmatmul__(self, other): - return dot(np.array(other), self) + return dot(other, self) def reshape(self, shape): if self.shape == shape: @@ -688,7 +689,12 @@ def tensordot(a, b, axes=2): return res.reshape(olda + oldb) -def dot(a, b): +def dot(a, b, make_array=True): + if make_array: + if not hasattr(a, 'ndim'): + a = np.array(a) + if not hasattr(b, 'ndim'): + b = np.array(b) return tensordot(a, b, axes=((a.ndim - 1,), (b.ndim - 2,))) diff --git a/sparse/tests/test_core.py b/sparse/tests/test_core.py index 29f4098b..3405b64b 100644 --- a/sparse/tests/test_core.py +++ b/sparse/tests/test_core.py @@ -130,8 +130,10 @@ def test_tensordot(a_shape, b_shape, axes): def test_dot(): a = random_x((3, 4, 5)) b = random_x((5, 6)) - l = [1, 2, 3, 4, 5] - l # silencing flake8 + + la = a.tolist() + lb = b.tolist() + la, lb # silencing flake8 sa = COO.from_numpy(a) sb = COO.from_numpy(b) @@ -140,10 +142,40 @@ def test_dot(): assert_eq(np.dot(a, b), sparse.dot(sa, sb)) if sys.version_info >= (3, 5): + # Coerce to np.array for arg lacking __matmul__ + sa._toarray_other = True + sb._toarray_other = True + + # Basic equivalences assert_eq(eval("a @ b"), eval("sa @ sb")) assert_eq(eval("sa @ sb"), sparse.dot(sa, sb)) - assert_eq(eval("l @ b"), eval("l @ sb")) # __rmatmul__ - assert_eq(eval("a @ sb"), sparse.dot(a, sb)) # __rmatmul__ + + # Exercise __rmatmul__ with naive collection (list) + assert_eq(eval("la @ b"), eval("la @ sb")) + assert_eq(eval("a @ sb"), sparse.dot(a, sb)) + assert_eq(eval("a @ lb"), eval("sa @ lb")) + + # Test that SOO's and np.array's combine correctly + assert_eq(eval("a @ sb"), eval("sa @ b")) + + +@pytest.mark.xfail +def test_dot_nocoercion(): + a = random_x((3, 4, 5)) + b = random_x((5, 6)) + + la = a.tolist() + lb = b.tolist() + la, lb # silencing flake8 + + sa = COO.from_numpy(a) + sb = COO.from_numpy(b) + sa, sb # silencing flake8 + + if sys.version_info >= (3, 5): + # Operations with naive collection (list) + assert_eq(eval("la @ b"), eval("la @ sb")) + assert_eq(eval("a @ lb"), eval("sa @ lb")) @pytest.mark.parametrize('func', [np.expm1, np.log1p, np.sin, np.tan, From e4191ca061f2bd7dcdad0b79b57738b233733696 Mon Sep 17 00:00:00 2001 From: David Mertz Date: Wed, 3 May 2017 14:40:59 -0700 Subject: [PATCH 7/8] PEP8 small fix --- sparse/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sparse/core.py b/sparse/core.py index cafd79a7..9f6b3c6f 100644 --- a/sparse/core.py +++ b/sparse/core.py @@ -695,7 +695,7 @@ def dot(a, b, make_array=True): a = np.array(a) if not hasattr(b, 'ndim'): b = np.array(b) - return tensordot(a, b, axes=((a.ndim - 1,), (b.ndim - 2,))) + return tensordot(a, b, axes=((a.ndim-1,), (b.ndim-2,))) def _dot(a, b): From fe8eef2a3c7ed72a52ae8d3e16d9561efb355f24 Mon Sep 17 00:00:00 2001 From: David Mertz Date: Wed, 3 May 2017 14:52:07 -0700 Subject: [PATCH 8/8] flake8 disrespects PEP 8 :-( --- sparse/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sparse/core.py b/sparse/core.py index 9f6b3c6f..cafd79a7 100644 --- a/sparse/core.py +++ b/sparse/core.py @@ -695,7 +695,7 @@ def dot(a, b, make_array=True): a = np.array(a) if not hasattr(b, 'ndim'): b = np.array(b) - return tensordot(a, b, axes=((a.ndim-1,), (b.ndim-2,))) + return tensordot(a, b, axes=((a.ndim - 1,), (b.ndim - 2,))) def _dot(a, b):