From a68a7f512f3d56ef5966f36fb12d7236e6815cbd Mon Sep 17 00:00:00 2001 From: Adam Lugowski Date: Wed, 25 Oct 2023 18:47:17 -0700 Subject: [PATCH] Support sparse 0.13 and expand tests on Python 3.7 * specify torch >=2 * test against any available wheel on each supported Python version * make pytest run regardless of what optional packages installed --- .github/workflows/tests.yml | 40 ++++++++++++++++++--------------- matrepr/adapters/sparse_impl.py | 7 +++++- pyproject.toml | 2 +- tests/test_graphblas.py | 9 +++----- tests/test_html.py | 14 ++++++++---- tests/test_latex.py | 14 ++++++++---- tests/test_numpy.py | 10 ++++++--- tests/test_performance.py | 14 ++++++++---- tests/test_scipy.py | 15 +++++++++---- tests/test_sparse.py | 21 ++++++++++++----- tests/test_str.py | 11 ++++++++- tests/test_torch.py | 8 +++---- 12 files changed, 110 insertions(+), 55 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b166996..180a380 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,48 +22,52 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install base dependencies - run: pip install tabulate pytest + run: pip install tabulate pytest pytest-subtests html5lib - name: Test minimums - run: pytest tests/test_basic.py tests/test_list_like.py + run: pytest - - name: Install test dependencies - if: ${{ !contains(matrix.python-version, 'pypy') }} # no scipy wheels for pypy - run: pip install pytest pytest-subtests html5lib scipy + - name: Install NumPy and SciPy + run: | + pip install --only-binary ":all:" numpy + pip install --only-binary ":all:" scipy || true - - name: Python only scipy - if: ${{ !contains(matrix.python-version, 'pypy') }} # no scipy wheels for pypy + - name: Test only SciPy run: pytest - name: Install other supported packages - if: ${{ !contains(matrix.python-version, 'pypy') && matrix.python-version != '3.7' }} # no wheels for pypy and old python run: | - pip install suitesparse-graphblas==7.4.4.1a1 - pip install python-graphblas - pip install sparse - pip install torch - pip install tensorflow + echo "=== Install Python-graphblas ==================" + pip install --only-binary ":all:" python-graphblas || true + echo "" + echo "=== Install PyData/Sparse =====================" + pip install --only-binary ":all:" sparse || true + echo "" + echo "=== Install PyTorch ===========================" + pip install --only-binary ":all:" "torch>=2.0.0" || true + echo "" + echo "=== Install TensorFlow =========================" + pip install --only-binary ":all:" tensorflow || true - name: Python Test without Jupyter - if: ${{ !contains(matrix.python-version, 'pypy') }} # no scipy wheels for pypy run: pytest - name: Install Jupyter - if: ${{ !contains(matrix.python-version, 'pypy') }} # no scipy wheels for pypy + if: ${{ !contains(matrix.python-version, 'pypy') }} run: pip install jupyter - name: Python Test with Jupyter - if: ${{ !contains(matrix.python-version, 'pypy') }} # no scipy wheels for pypy + if: ${{ !contains(matrix.python-version, 'pypy') }} run: pytest - name: Python Test with Coverage - if: ${{ contains(matrix.os, 'ubuntu') && !contains(matrix.python-version, 'pypy') }} + if: ${{ contains(matrix.os, 'ubuntu') }} run: | pip install pytest-cov pytest --cov=matrepr --cov-report term --cov-report=xml - name: Upload Coverage to Codecov - if: ${{ contains(matrix.os, 'ubuntu') && !contains(matrix.python-version, 'pypy') }} + if: ${{ contains(matrix.os, 'ubuntu') }} uses: codecov/codecov-action@v3 with: gcov: true diff --git a/matrepr/adapters/sparse_impl.py b/matrepr/adapters/sparse_impl.py index c5fe4f4..1035283 100644 --- a/matrepr/adapters/sparse_impl.py +++ b/matrepr/adapters/sparse_impl.py @@ -37,9 +37,14 @@ def describe(self) -> str: if hasattr(self.mat, "nbytes"): parts.append(sizeof_fmt(self.mat.nbytes)) + try: + fmt = self.mat.format + except AttributeError: + fmt = self.mat.__class__.__name__ + return describe(shape=self.mat.shape, nnz=self.mat.nnz, nz_type=self.mat.dtype, - layout=self.mat.format, + layout=fmt, notes=", ".join(parts)) diff --git a/pyproject.toml b/pyproject.toml index 0215957..39abe33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,4 +43,4 @@ repository = "https://github.com/alugowski/matrepr" [project.optional-dependencies] test = ["pytest", "html5lib", "scipy"] -supported = ["scipy", "numpy", "python-graphblas", "sparse", "torch", "tensorflow"] +supported = ["scipy", "numpy", "python-graphblas", "sparse", "torch>=2.0.0", "tensorflow"] diff --git a/tests/test_graphblas.py b/tests/test_graphblas.py index 2e7eb90..f63291b 100644 --- a/tests/test_graphblas.py +++ b/tests/test_graphblas.py @@ -13,10 +13,7 @@ # Context initialization must happen before any other imports gb.init("suitesparse", blocking=False) - - have_gb = True except ImportError: - have_gb = False gb = None @@ -34,7 +31,7 @@ def generate_fixed_value(m, n): return gb.Matrix.from_coo(rows, cols, data, nrows=m, ncols=n, dtype='int64'), data -@unittest.skipIf(not have_gb, "python-graphblas not installed") +@unittest.skipIf(not gb, "python-graphblas not installed") class GraphBLASMatrixTests(unittest.TestCase): def setUp(self): mat = gb.Matrix.from_coo([0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], nrows=5, ncols=5) @@ -81,7 +78,7 @@ def test_truncate(self): self.assertEqual(expected_count, count) -@unittest.skipIf(not have_gb, "python-graphblas not installed") +@unittest.skipIf(not gb, "python-graphblas not installed") class GraphBLASVectorTests(unittest.TestCase): def setUp(self): vec = gb.Vector.from_coo([0, 3, 4, 6], [12.1, -5.4, 2.9, 2.2], size=8) @@ -121,7 +118,7 @@ def test_truncate(self): self.assertIn(f"{value}", res) -@unittest.skipIf(not have_gb, "python-graphblas not installed") +@unittest.skipIf(not gb, "python-graphblas not installed") class PatchGraphBLASTests(unittest.TestCase): def test_patch_graphblas(self): mat = gb.Matrix.from_coo([0, 1], [0, 1], [111, 222], nrows=5, ncols=5), diff --git a/tests/test_html.py b/tests/test_html.py index 88cea9b..b92b112 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -6,12 +6,17 @@ import unittest import html5lib -import numpy.random -import scipy.sparse +try: + import scipy + import scipy.sparse -from matrepr import to_html + import numpy.random + numpy.random.seed(123) +except ImportError: + scipy = None + numpy = None -numpy.random.seed(123) +from matrepr import to_html def generate_fixed_value(m, n): @@ -28,6 +33,7 @@ def generate_fixed_value(m, n): return scipy.sparse.coo_matrix((data, (rows, cols)), shape=(m, n), dtype='int64') +@unittest.skipIf(scipy is None, "scipy not installed") class ToHTMLTests(unittest.TestCase): def setUp(self): self.mats = [ diff --git a/tests/test_latex.py b/tests/test_latex.py index d9c66da..9960628 100644 --- a/tests/test_latex.py +++ b/tests/test_latex.py @@ -4,12 +4,17 @@ import unittest -import numpy.random -import scipy.sparse +try: + import scipy + import scipy.sparse -from matrepr import to_latex + import numpy.random + numpy.random.seed(123) +except ImportError: + scipy = None + numpy = None -numpy.random.seed(123) +from matrepr import to_latex def generate_fixed_value(m, n): @@ -26,6 +31,7 @@ def generate_fixed_value(m, n): return scipy.sparse.coo_matrix((data, (rows, cols)), shape=(m, n), dtype='int64') +@unittest.skipIf(scipy is None, "scipy not installed") class ToLatexTests(unittest.TestCase): def setUp(self): self.mats = [ diff --git a/tests/test_numpy.py b/tests/test_numpy.py index 65e4df1..b2e1712 100644 --- a/tests/test_numpy.py +++ b/tests/test_numpy.py @@ -4,13 +4,17 @@ import unittest -import numpy as np +try: + import numpy as np -from matrepr import to_html, to_latex, to_str + np.random.seed(123) +except ImportError: + np = None -np.random.seed(123) +from matrepr import to_html, to_latex, to_str +@unittest.skipIf(np is None, "numpy not installed") class NumpyTests(unittest.TestCase): def setUp(self): self.mats = [ diff --git a/tests/test_performance.py b/tests/test_performance.py index 6153600..51a166f 100644 --- a/tests/test_performance.py +++ b/tests/test_performance.py @@ -5,14 +5,20 @@ import unittest import time -import numpy.random -import scipy.sparse +try: + import scipy + import scipy.sparse -from matrepr import to_html + import numpy.random + numpy.random.seed(123) +except ImportError: + scipy = None + numpy = None -numpy.random.seed(123) +from matrepr import to_html +@unittest.skipIf(scipy is None, "scipy not installed") class PerformanceTests(unittest.TestCase): def test_to_html_speed(self): # warmup, just in case diff --git a/tests/test_scipy.py b/tests/test_scipy.py index 6401f97..5be7391 100644 --- a/tests/test_scipy.py +++ b/tests/test_scipy.py @@ -4,12 +4,17 @@ import unittest -import numpy.random -import scipy.sparse +try: + import scipy + import scipy.sparse -from matrepr import to_html, to_latex, to_str + import numpy.random + numpy.random.seed(123) +except ImportError: + scipy = None + numpy = None -numpy.random.seed(123) +from matrepr import to_html, to_latex, to_str def generate_fixed_value(m, n): @@ -26,6 +31,7 @@ def generate_fixed_value(m, n): return scipy.sparse.coo_matrix((data, (rows, cols)), shape=(m, n), dtype='int64') +@unittest.skipIf(scipy is None, "scipy not installed") class SciPyTests(unittest.TestCase): def setUp(self): self.mats = [ @@ -66,6 +72,7 @@ def test_formats(self): self.assertEqual(expected[i], res) +@unittest.skipIf(scipy is None, "scipy not installed") class PatchSciPyTests(unittest.TestCase): def test_patch_scipy(self): source_mat = scipy.sparse.coo_matrix(([111, 222], ([0, 1], [0, 1])), shape=(10, 10)) diff --git a/tests/test_sparse.py b/tests/test_sparse.py index 35b6859..7bfec1a 100644 --- a/tests/test_sparse.py +++ b/tests/test_sparse.py @@ -10,11 +10,17 @@ except ImportError: sparse = None -from matrepr import to_html, to_latex, to_str +try: + import scipy + import scipy.sparse -import scipy -import numpy as np -np.random.seed(123) + import numpy.random + numpy.random.seed(123) +except ImportError: + scipy = None + numpy = None + +from matrepr import to_html, to_latex, to_str def generate_fixed_value(m, n): @@ -39,10 +45,13 @@ def setUp(self): sparse.COO(coords=np.array([1, 4]), data=np.array([11, 44]), shape=(10,)), sparse.COO(np.empty(shape=(10, 10))), sparse.random((10, 10), density=0.4), - sparse.COO.from_scipy_sparse(generate_fixed_value(10, 10)), sparse.COO(coords=np.array([[0, 0], [0, 0]]), data=np.array([111, 222]), shape=(13, 13)), # has dupes sparse.COO(coords=np.array([[0, 1], [3, 2], [1, 3]]), data=np.array([111, 222]), shape=(5, 5, 5)), ] + if scipy is not None: + self.mats.append( + sparse.COO.from_scipy_sparse(generate_fixed_value(10, 10)) + ) with warnings.catch_warnings(): # COO will incorrectly complain that the object is not ndarray when it is. @@ -103,6 +112,7 @@ def test_truncate_1d(self): for value in [1000, 1009]: self.assertIn(f"{value}", res) + @unittest.skipIf(scipy is None, "scipy not installed") def test_contents_2d(self): mat = generate_fixed_value(10, 10) sparse_mat = sparse.COO.from_scipy_sparse(mat) @@ -110,6 +120,7 @@ def test_contents_2d(self): for value in mat.data: self.assertIn(f"{value}", res) + @unittest.skipIf(scipy is None, "scipy not installed") def test_truncate_2d(self): mat = generate_fixed_value(20, 20) sparse_mat = sparse.COO.from_scipy_sparse(mat) diff --git a/tests/test_str.py b/tests/test_str.py index ca99636..59311fa 100644 --- a/tests/test_str.py +++ b/tests/test_str.py @@ -4,7 +4,15 @@ import unittest -import scipy.sparse +try: + import scipy + import scipy.sparse + + import numpy.random + numpy.random.seed(123) +except ImportError: + scipy = None + numpy = None from matrepr import to_str from matrepr.string_formatter import max_line_width @@ -24,6 +32,7 @@ def generate_fixed_value(m, n): return scipy.sparse.coo_matrix((data, (rows, cols)), shape=(m, n), dtype='int64') +@unittest.skipIf(scipy is None, "scipy not installed") class ToStrTests(unittest.TestCase): def setUp(self): self.mats = [ diff --git a/tests/test_torch.py b/tests/test_torch.py index 6972bb0..12aa256 100644 --- a/tests/test_torch.py +++ b/tests/test_torch.py @@ -5,17 +5,17 @@ import unittest import warnings -import numpy as np - try: import torch + + import numpy.random + numpy.random.seed(123) except ImportError: torch = None + numpy = None from matrepr import to_html, to_latex, to_str -np.random.seed(123) - def generate_fixed_value(m, n): row_factor = 10**(1+len(str(n)))