From a159e818976be8ba45a2d413a4f6d47f6186d116 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 12 Jun 2024 09:50:38 -0400 Subject: [PATCH 01/14] MAINT: Test against latest NumPy --- .github/workflows/test.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a91458da..7325017e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,6 +21,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: + # We test NumPy dev on 3.11 python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] requires: ['requirements.txt'] include: @@ -37,9 +38,13 @@ jobs: allow-prereleases: true - name: Install run: | + set -eo pipefail python -m pip install --upgrade pip python -m pip install -r ${{ matrix.requires }} python -m pip install -r requirements-dev.txt + if [[ "${{ matrix.python-version }}" == "3.11" ]]; then + python -m pip install --only-binary numpy --pre --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple "numpy>=2.1.0.dev0" + fi python -m pip install . - name: Lint run: | From d52cc5aa29e8aa8183b0add333ad4df650817111 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 12 Jun 2024 09:55:01 -0400 Subject: [PATCH 02/14] FIX: Failing successfully --- .github/workflows/test.yml | 1 + nitime/algorithms/event_related.py | 2 +- nitime/algorithms/tests/test_spectral.py | 6 ++---- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7325017e..ade1112a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,6 +19,7 @@ jobs: test: runs-on: ubuntu-latest + continue-on-error: true strategy: matrix: # We test NumPy dev on 3.11 diff --git a/nitime/algorithms/event_related.py b/nitime/algorithms/event_related.py index acf7d34b..e0141a39 100644 --- a/nitime/algorithms/event_related.py +++ b/nitime/algorithms/event_related.py @@ -10,7 +10,7 @@ def fir(timeseries, design): - """ + r""" Calculate the FIR (finite impulse response) HRF, according to [Burock2000]_ Parameters diff --git a/nitime/algorithms/tests/test_spectral.py b/nitime/algorithms/tests/test_spectral.py index 6c00a581..20d95df5 100644 --- a/nitime/algorithms/tests/test_spectral.py +++ b/nitime/algorithms/tests/test_spectral.py @@ -246,8 +246,7 @@ def test_mtm_lin_combo(): mtm_cross = tsa.mtm_cross_spectrum( spec1, spec2, (weights[0], weights[1]), sides=sides ) - npt.assert_(mtm_cross.dtype in np.sctypes['complex'], - 'Wrong dtype for crossspectrum') + assert mtm_cross.dtype == np.complex128, 'Wrong dtype for crossspectrum' npt.assert_(len(mtm_cross) == 51, 'Wrong length for halfband spectrum') sides = 'twosided' @@ -260,8 +259,7 @@ def test_mtm_lin_combo(): mtm_auto = tsa.mtm_cross_spectrum( spec1, spec1, weights[0], sides=sides ) - npt.assert_(mtm_auto.dtype in np.sctypes['float'], - 'Wrong dtype for autospectrum') + assert mtm_auto.dtype == np.float64, 'Wrong dtype for autospectrum' npt.assert_(len(mtm_auto) == 51, 'Wrong length for halfband spectrum') sides = 'twosided' From eb2994373db95420ba63c22920e2dba94c7b1602 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 12 Jun 2024 10:03:58 -0400 Subject: [PATCH 03/14] FIX: Fixes --- nitime/tests/test_timeseries.py | 6 +++++- nitime/timeseries.py | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/nitime/tests/test_timeseries.py b/nitime/tests/test_timeseries.py index 7aaeac93..80ee8a9e 100644 --- a/nitime/tests/test_timeseries.py +++ b/nitime/tests/test_timeseries.py @@ -925,7 +925,11 @@ def test_timearray_math_functions(): npt.assert_(getattr(b, f)().__class__ == ts.TimeArray) npt.assert_(getattr(b, f)().time_unit == b.time_unit) # comparison with unitless should convert to the TimeArray's units - npt.assert_(getattr(b, f)() == getattr(a, f)()) + if f == "ptp": + want = np.ptp(a) # ndarray.ptp removed in 2.0 + else: + want = getattr(a, f)() + npt.assert_(getattr(b, f)() == want) def test_timearray_var_prod(): diff --git a/nitime/timeseries.py b/nitime/timeseries.py index 4fd44a92..e36708c4 100644 --- a/nitime/timeseries.py +++ b/nitime/timeseries.py @@ -152,7 +152,7 @@ class instance, or an int64 array in the base unit of the module e_s += 'TimeArray in object, or int64 times, in %s' % base_unit raise ValueError(e_s) - time = np.array(data, copy=False) + time = np.asarray(data) else: if isinstance(data, TimeInterface): time = data.copy() @@ -309,7 +309,7 @@ def mean(self, *args, **kwargs): return ret def ptp(self, *args, **kwargs): - ret = TimeArray(np.ndarray.ptp(self, *args, **kwargs), + ret = TimeArray(np.ptp(self, *args, **kwargs), time_unit=base_unit) ret.convert_unit(self.time_unit) return ret From 8b8202f4c02b249e1143eda4000d7387fa15ade3 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 12 Jun 2024 10:30:17 -0400 Subject: [PATCH 04/14] FIX: Try again [build wheels] --- nitime/tests/test_timeseries.py | 24 ++++++++++++------------ nitime/timeseries.py | 16 ++++++++++++++-- pyproject.toml | 6 ++++++ 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/nitime/tests/test_timeseries.py b/nitime/tests/test_timeseries.py index 80ee8a9e..d8ca5e6a 100644 --- a/nitime/tests/test_timeseries.py +++ b/nitime/tests/test_timeseries.py @@ -916,20 +916,20 @@ def test_index_int64(): assert repr(b[0]) == repr(b[np.int32(0)]) -def test_timearray_math_functions(): +@pytest.mark.parametrize('f', ['min', 'max', 'mean', 'ptp', 'sum']) +@pytest.mark.parametrize('tu', ['s', 'ms', 'ps', 'D']) +def test_timearray_math_functions(f, tu): "Calling TimeArray.min() .max(), mean() should return TimeArrays" a = np.arange(2, 11) - for f in ['min', 'max', 'mean', 'ptp', 'sum']: - for tu in ['s', 'ms', 'ps', 'D']: - b = ts.TimeArray(a, time_unit=tu) - npt.assert_(getattr(b, f)().__class__ == ts.TimeArray) - npt.assert_(getattr(b, f)().time_unit == b.time_unit) - # comparison with unitless should convert to the TimeArray's units - if f == "ptp": - want = np.ptp(a) # ndarray.ptp removed in 2.0 - else: - want = getattr(a, f)() - npt.assert_(getattr(b, f)() == want) + b = ts.TimeArray(a, time_unit=tu) + if f == "ptp" and ts._NP_2: + with pytest.raises(AttributeError, match='`ptp` was removed'): + a.ptp() # ndarray.ptp removed in 2.0 + return + npt.assert_(getattr(b, f)().__class__ == ts.TimeArray) + npt.assert_(getattr(b, f)().time_unit == b.time_unit) + # comparison with unitless should convert to the TimeArray's units + npt.assert_(getattr(b, f)() == getattr(a, f)()) def test_timearray_var_prod(): diff --git a/nitime/timeseries.py b/nitime/timeseries.py index e36708c4..6e278aac 100644 --- a/nitime/timeseries.py +++ b/nitime/timeseries.py @@ -33,6 +33,11 @@ # Our own from nitime import descriptors as desc +try: + _NP_2 = int(np.__version__.split(".")[0]) >= 2 +except Exception: + _NP_2 = True + #----------------------------------------------------------------------------- # Module globals #----------------------------------------------------------------------------- @@ -112,7 +117,9 @@ def __new__(cls, data, time_unit=None, copy=True): which are SI units of time. Default: 's' copy : bool, optional - Whether to create this instance by copy of a + Whether to create this instance by copy of a. If False, + a copy will not be forced but might still be required depending + on the data array. Note ---- @@ -309,7 +316,12 @@ def mean(self, *args, **kwargs): return ret def ptp(self, *args, **kwargs): - ret = TimeArray(np.ptp(self, *args, **kwargs), + if _NP_2: + raise AttributeError( + "`ptp` was removed from the ndarray class in NumPy 2.0. " + "Use np.ptp(arr, ...) instead." + ) + ret = TimeArray(np.ndarray.ptp(self, *args, **kwargs), time_unit=base_unit) ret.convert_unit(self.time_unit) return ret diff --git a/pyproject.toml b/pyproject.toml index 2b605570..7e7dc635 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,12 @@ skip = "pp* cp38-*_aarch64 cp38-musllinux_*" # don't bother unless someone asks archs = ["native"] +test-requires = [ + "pytest", + "pytest-cov", +] +test-command = "pytest {project}/tests" + [tool.cibuildwheel.linux] archs = ["x86_64", "aarch64"] From 73c6c13dc098cdf4bbb4b916ccb989bd975f4231 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 12 Jun 2024 10:39:02 -0400 Subject: [PATCH 05/14] FIX: Try again [build wheels] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7e7dc635..7c63048f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,7 @@ test-requires = [ "pytest", "pytest-cov", ] -test-command = "pytest {project}/tests" +test-command = "pytest {project}/nitime/tests" [tool.cibuildwheel.linux] archs = ["x86_64", "aarch64"] From 4ff8ccd78924583f6579c43340e9f00b663e6725 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 12 Jun 2024 10:49:56 -0400 Subject: [PATCH 06/14] FIX: Try again [build wheels] --- nitime/index_utils.py | 6 +++--- nitime/tests/test_utils.py | 19 +++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/nitime/index_utils.py b/nitime/index_utils.py index be5de816..e845c60d 100644 --- a/nitime/index_utils.py +++ b/nitime/index_utils.py @@ -6,11 +6,11 @@ 'tril_indices_from', 'triu_indices', 'triu_indices_from', ] -from numpy.core.numeric import asanyarray, subtract, arange, \ +from numpy import asanyarray, subtract, arange, \ greater_equal, multiply, ones, asarray, where -# Need to import numpy for the doctests! -import numpy as np +# Need to import numpy for the doctests! +import numpy as np def tri(N, M=None, k=0, dtype=float): """ diff --git a/nitime/tests/test_utils.py b/nitime/tests/test_utils.py index 7770227f..c5f0a81f 100644 --- a/nitime/tests/test_utils.py +++ b/nitime/tests/test_utils.py @@ -230,26 +230,27 @@ def test_detect_lines(): """ Tests detect_lines utility in the reliable low-SNR scenario. """ + rng = np.random.RandomState(0) N = 1000 fft_pow = int( np.ceil(np.log2(N) + 2) ) NW = 4 - lines = np.sort(np.random.randint(100, 2**(fft_pow-4), size=(3,))) + lines = np.sort(rng.randint(100, 2**(fft_pow-4), size=(3,))) while np.any( np.diff(lines) < 2*NW ): - lines = np.sort(np.random.randint(2**(fft_pow-4), size=(3,))) + lines = np.sort(rng.randint(2**(fft_pow-4), size=(3,))) lines = lines.astype('d') - #lines += np.random.randn(3) # displace from grid locations + #lines += rng.randn(3) # displace from grid locations lines /= 2.0**(fft_pow-2) # ensure they are well separated - phs = np.random.rand(3) * 2 * np.pi + phs = rng.rand(3) * 2 * np.pi # amps approximately such that RMS power = 1 +/- N(0,1) - amps = np.sqrt(2)/2 + np.abs( np.random.randn(3) ) + amps = np.sqrt(2)/2 + np.abs( rng.randn(3) ) nz_sig = 0.05 tx = np.arange(N) harmonics = amps[:,None]*np.cos( 2*np.pi*tx*lines[:,None] + phs[:,None] ) harmonic = np.sum(harmonics, axis=0) - nz = np.random.randn(N) * nz_sig + nz = rng.randn(N) * nz_sig sig = harmonic + nz f, b = utils.detect_lines(sig, (NW, 2*NW), low_bias=True, NFFT=2**fft_pow) @@ -286,11 +287,13 @@ def test_detect_lines_2dmode(): Test multi-sequence operation """ + rng = np.random.RandomState(0) + N = 1000 - sig = np.cos( 2*np.pi*np.arange(N) * 20./N ) + np.random.randn(N) * .01 + sig = np.cos( 2*np.pi*np.arange(N) * 20./N ) + rng.randn(N) * .01 - sig2d = np.row_stack( (sig, sig, sig) ) + sig2d = np.vstack( (sig, sig, sig) ) lines = utils.detect_lines(sig2d, (4, 8), low_bias=True, NFFT=2**12) From 345424828dd6278a47e2ff12c58462cd1bc6e56a Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 12 Jun 2024 11:41:59 -0400 Subject: [PATCH 07/14] Apply suggestions from code review Co-authored-by: Chris Markiewicz --- nitime/timeseries.py | 9 ++++----- pyproject.toml | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/nitime/timeseries.py b/nitime/timeseries.py index 6e278aac..c13ced22 100644 --- a/nitime/timeseries.py +++ b/nitime/timeseries.py @@ -317,11 +317,10 @@ def mean(self, *args, **kwargs): def ptp(self, *args, **kwargs): if _NP_2: - raise AttributeError( - "`ptp` was removed from the ndarray class in NumPy 2.0. " - "Use np.ptp(arr, ...) instead." - ) - ret = TimeArray(np.ndarray.ptp(self, *args, **kwargs), + ptp = np.ptp + else: + ptp = np.ndarray.ptp + ret = TimeArray(ptp(self, *args, **kwargs), time_unit=base_unit) ret.convert_unit(self.time_unit) return ret diff --git a/pyproject.toml b/pyproject.toml index 7c63048f..0ba485b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,9 +64,9 @@ archs = ["native"] test-requires = [ "pytest", - "pytest-cov", + "nitime[full]", # Enable all optional behavior ] -test-command = "pytest {project}/nitime/tests" +test-command = "pytest -rsx --pyargs nitime" [tool.cibuildwheel.linux] archs = ["x86_64", "aarch64"] From 74c2723750c7ccd9900f20e84c6e723d4ca5e754 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 12 Jun 2024 11:42:16 -0400 Subject: [PATCH 08/14] FIX: cov --- requirements-dev.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index afb33a3c..2df682a8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,5 @@ sphinx pytest -pytest-cov nibabel networkx tomli; python_version < '3.11' From a5eccec45612aedf318cc72b788f86a3c49fec0e Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 12 Jun 2024 11:42:35 -0400 Subject: [PATCH 09/14] TST: Wheels, too [build wheels] From 1a4c5cc96c226a8685a2002902d34ee499cb018b Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 12 Jun 2024 11:46:31 -0400 Subject: [PATCH 10/14] FIX: Put back [build wheels] --- requirements-dev.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-dev.txt b/requirements-dev.txt index 2df682a8..afb33a3c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,6 @@ sphinx pytest +pytest-cov nibabel networkx tomli; python_version < '3.11' From 6645dba802e7c668daf895becb15228b00d3f0dd Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 12 Jun 2024 11:52:18 -0400 Subject: [PATCH 11/14] FIX: Test --- nitime/tests/test_timeseries.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nitime/tests/test_timeseries.py b/nitime/tests/test_timeseries.py index d8ca5e6a..edf05ea1 100644 --- a/nitime/tests/test_timeseries.py +++ b/nitime/tests/test_timeseries.py @@ -923,13 +923,13 @@ def test_timearray_math_functions(f, tu): a = np.arange(2, 11) b = ts.TimeArray(a, time_unit=tu) if f == "ptp" and ts._NP_2: - with pytest.raises(AttributeError, match='`ptp` was removed'): - a.ptp() # ndarray.ptp removed in 2.0 - return + want = np.ptp(a) + else: + want = getattr(a, f)() npt.assert_(getattr(b, f)().__class__ == ts.TimeArray) npt.assert_(getattr(b, f)().time_unit == b.time_unit) # comparison with unitless should convert to the TimeArray's units - npt.assert_(getattr(b, f)() == getattr(a, f)()) + npt.assert_(getattr(b, f)() == want) def test_timearray_var_prod(): From e7ba859740d82a3625b8d7379594dee34fd4aa90 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 12 Jun 2024 11:52:31 -0400 Subject: [PATCH 12/14] TST: Wheels too [build wheels] From 5f5fae5ca123a5fb5608a82d57493705dda36146 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 12 Jun 2024 12:06:39 -0400 Subject: [PATCH 13/14] FIX: Seed directly [build wheels] --- nitime/tests/test_utils.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/nitime/tests/test_utils.py b/nitime/tests/test_utils.py index c5f0a81f..dc4ffaba 100644 --- a/nitime/tests/test_utils.py +++ b/nitime/tests/test_utils.py @@ -230,27 +230,28 @@ def test_detect_lines(): """ Tests detect_lines utility in the reliable low-SNR scenario. """ - rng = np.random.RandomState(0) + np.random.seed(0) + N = 1000 fft_pow = int( np.ceil(np.log2(N) + 2) ) NW = 4 - lines = np.sort(rng.randint(100, 2**(fft_pow-4), size=(3,))) + lines = np.sort(np.random.randint(100, 2**(fft_pow-4), size=(3,))) while np.any( np.diff(lines) < 2*NW ): - lines = np.sort(rng.randint(2**(fft_pow-4), size=(3,))) + lines = np.sort(np.random.randint(2**(fft_pow-4), size=(3,))) lines = lines.astype('d') - #lines += rng.randn(3) # displace from grid locations + #lines += np.random.randn(3) # displace from grid locations lines /= 2.0**(fft_pow-2) # ensure they are well separated - phs = rng.rand(3) * 2 * np.pi + phs = np.random.rand(3) * 2 * np.pi # amps approximately such that RMS power = 1 +/- N(0,1) - amps = np.sqrt(2)/2 + np.abs( rng.randn(3) ) + amps = np.sqrt(2)/2 + np.abs( np.random.randn(3) ) nz_sig = 0.05 tx = np.arange(N) harmonics = amps[:,None]*np.cos( 2*np.pi*tx*lines[:,None] + phs[:,None] ) harmonic = np.sum(harmonics, axis=0) - nz = rng.randn(N) * nz_sig + nz = np.random.randn(N) * nz_sig sig = harmonic + nz f, b = utils.detect_lines(sig, (NW, 2*NW), low_bias=True, NFFT=2**fft_pow) @@ -287,11 +288,13 @@ def test_detect_lines_2dmode(): Test multi-sequence operation """ - rng = np.random.RandomState(0) + # This seed affects not just the signal we generate below, but then also + # detect_lines->dpss_windows->tridi_inverse_iteration + np.random.seed(0) N = 1000 - sig = np.cos( 2*np.pi*np.arange(N) * 20./N ) + rng.randn(N) * .01 + sig = np.cos( 2*np.pi*np.arange(N) * 20./N ) + np.random.randn(N) * .01 sig2d = np.vstack( (sig, sig, sig) ) From 3e65a1219c4a3d30f127705997b0da68d2b13d70 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 12 Jun 2024 12:20:23 -0400 Subject: [PATCH 14/14] FIX: See why [build wheels] --- nitime/tests/test_utils.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/nitime/tests/test_utils.py b/nitime/tests/test_utils.py index dc4ffaba..d31c7491 100644 --- a/nitime/tests/test_utils.py +++ b/nitime/tests/test_utils.py @@ -302,9 +302,7 @@ def test_detect_lines_2dmode(): npt.assert_(len(lines)==3, 'Detect lines failed multi-sequence mode') - consistent1 = (lines[0][0] == lines[1][0]).all() and \ - (lines[1][0] == lines[2][0]).all() - consistent2 = (lines[0][1] == lines[1][1]).all() and \ - (lines[1][1] == lines[2][1]).all() - - npt.assert_(consistent1 and consistent2, 'Inconsistent results') + npt.assert_allclose(lines[0][0], lines[1][0]) + npt.assert_allclose(lines[0][0], lines[2][0]) + npt.assert_allclose(lines[0][1], lines[1][1]) + npt.assert_allclose(lines[0][1], lines[2][1])