Skip to content
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

Move scipy to optional requirements #1233

Merged
merged 7 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from 4 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: 1 addition & 1 deletion .github/workflows/run_coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
- name: Install package with optional dependencies
if: ${{ matrix.opt_req }}
run: |
python -m pip install ".[test,tqdm,zarr,termset]"
python -m pip install ".[test,tqdm,sparse,zarr,termset]"
- name: Run tests and generate coverage report
run: |
Expand Down
2 changes: 1 addition & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ formats: all
# Optionally set the version of Python and requirements required to build your docs
python:
install:
- path: .[docs,tqdm,zarr,termset] # path to the package relative to the root
- path: .[docs,tqdm,sparse,zarr,termset] # path to the package relative to the root

# Optionally include all submodules
submodules:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Importing from `hdmf.build.map` is no longer supported. Import from `hdmf.build` instead. @rly [#1221](https://github.com/hdmf-dev/hdmf/pull/1221)
- Python 3.8 has reached end of life. Dropped support for Python 3.8 and add support for Python 3.13. @mavaylon1 [#1209](https://github.com/hdmf-dev/hdmf/pull/1209)
- Support for Zarr is limited to versions < 3. @rly [#1229](https://github.com/hdmf-dev/hdmf/pull/1229)
- Scipy is no longer a required dependency. Users using the `CSRMatrix` data type should install `scipy` separately or with `pip install "hdmf[sparse]"`. @rly [#1140](https://github.com/hdmf-dev/hdmf/pull/1140)

### Changed
- Added checks to ensure that group and dataset spec names and default names do not contain slashes. @bendichter [#1219](https://github.com/hdmf-dev/hdmf/pull/1219)
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ dynamic = ["version"]
[project.optional-dependencies]
tqdm = ["tqdm>=4.41.0"]
zarr = ["zarr>=2.12.0,<3"]
sparse = ["scipy>=1.7"]
termset = [
"linkml-runtime>=1.5.5",
"schemasheets>=0.1.23",
Expand Down Expand Up @@ -70,7 +71,7 @@ docs = [
]

# all possible dependencies
all = ["hdmf[tqdm,zarr,termset,test,docs]"]
all = ["hdmf[tqdm,zarr,sparse,termset,test,docs]"]

[project.urls]
"Homepage" = "https://github.com/hdmf-dev/hdmf"
Expand Down
22 changes: 17 additions & 5 deletions src/hdmf/common/sparse.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import scipy.sparse as sps
try:
from scipy.sparse import csr_matrix
SCIPY_INSTALLED = True
except ImportError:
SCIPY_INSTALLED = False
class csr_matrix: # dummy class to prevent import errors
pass

from . import register_class
from ..container import Container
from ..utils import docval, popargs, to_uint_array, get_data_shape, AllowPositional
Expand All @@ -7,7 +14,7 @@
@register_class('CSRMatrix')
class CSRMatrix(Container):

@docval({'name': 'data', 'type': (sps.csr_matrix, 'array_data'),
@docval({'name': 'data', 'type': (csr_matrix, 'array_data'),
'doc': 'the data to use for this CSRMatrix or CSR data array.'
'If passing CSR data array, *indices*, *indptr*, and *shape* must also be provided'},
{'name': 'indices', 'type': 'array_data', 'doc': 'CSR index array', 'default': None},
Expand All @@ -16,13 +23,17 @@ class CSRMatrix(Container):
{'name': 'name', 'type': str, 'doc': 'the name to use for this when storing', 'default': 'csr_matrix'},
allow_positional=AllowPositional.WARNING)
def __init__(self, **kwargs):
if not SCIPY_INSTALLED:
raise ImportError(
"scipy must be installed to use CSRMatrix. Please install scipy using `pip install scipy`."
)
data, indices, indptr, shape = popargs('data', 'indices', 'indptr', 'shape', kwargs)
super().__init__(**kwargs)
if not isinstance(data, sps.csr_matrix):
if not isinstance(data, csr_matrix):
temp_shape = get_data_shape(data)
temp_ndim = len(temp_shape)
if temp_ndim == 2:
data = sps.csr_matrix(data)
data = csr_matrix(data)
elif temp_ndim == 1:
if any(_ is None for _ in (indptr, indices, shape)):
raise ValueError("Must specify 'indptr', 'indices', and 'shape' arguments when passing data array.")
Expand All @@ -31,9 +42,10 @@ def __init__(self, **kwargs):
shape = self.__check_arr(shape, 'shape')
if len(shape) != 2:
raise ValueError("'shape' argument must specify two and only two dimensions.")
data = sps.csr_matrix((data, indices, indptr), shape=shape)
data = csr_matrix((data, indices, indptr), shape=shape)
else:
raise ValueError("'data' argument cannot be ndarray of dimensionality > 2.")
# self.__data is a scipy.sparse.csr_matrix
self.__data = data

@staticmethod
Expand Down
23 changes: 20 additions & 3 deletions tests/unit/common/test_sparse.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
from hdmf.common import CSRMatrix
from hdmf.testing import TestCase, H5RoundTripMixin

import scipy.sparse as sps
import numpy as np
import unittest

try:
import scipy.sparse as sps
SCIPY_INSTALLED = True
except ImportError:
SCIPY_INSTALLED = False


@unittest.skipIf(SCIPY_INSTALLED, "scipy is installed")
class TestCSRMatrixNoScipy(TestCase):

def test_import_error(self):
data = np.array([[1, 0, 2], [0, 0, 3], [4, 5, 6]])
with self.assertRaises(ImportError):
CSRMatrix(data=data)


@unittest.skipIf(not SCIPY_INSTALLED, "scipy is not installed")
class TestCSRMatrix(TestCase):

def test_from_sparse_matrix(self):
Expand All @@ -18,6 +33,7 @@ def test_from_sparse_matrix(self):
received = CSRMatrix(data=sps_mat)
self.assertContainerEqual(received, expected, ignore_hdmf_attrs=True)

@unittest.skipIf(not SCIPY_INSTALLED, "scipy is not installed")
rly marked this conversation as resolved.
Show resolved Hide resolved
def test_2d_data(self):
data = np.array([[1, 0, 2], [0, 0, 3], [4, 5, 6]])
csr_mat = CSRMatrix(data=data)
Expand Down Expand Up @@ -153,7 +169,7 @@ def test_array_bad_dim(self):
with self.assertRaisesWith(ValueError, msg):
CSRMatrix(data=data, indices=indices, indptr=indptr, shape=shape)


@unittest.skipIf(not SCIPY_INSTALLED, "scipy is not installed")
class TestCSRMatrixRoundTrip(H5RoundTripMixin, TestCase):

def setUpContainer(self):
Expand All @@ -164,6 +180,7 @@ def setUpContainer(self):
return CSRMatrix(data=data, indices=indices, indptr=indptr, shape=shape)


@unittest.skipIf(not SCIPY_INSTALLED, "scipy is not installed")
class TestCSRMatrixRoundTripFromLists(H5RoundTripMixin, TestCase):
"""Test that CSRMatrix works with lists as well"""

Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ extras =
# which optional dependency set(s) to use (default: none)
pytest: test
gallery: doc
optional: tqdm,zarr,termset
optional: tqdm,sparse,zarr,termset
commands =
# commands to run for every environment
python --version # print python version for debugging
Expand Down
Loading