diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 386f01d..ab3381c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,9 +18,9 @@ jobs: package: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: '3.10' - name: Install dependencies diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index cfe4336..4570f90 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -38,10 +38,10 @@ jobs: OPENBLAS_NUM_THREADS: "1" PYTHONUNBUFFERED: "1" steps: - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: pyvista/setup-headless-display-action@main with: qt: true @@ -81,7 +81,7 @@ jobs: MKL_NUM_THREADS: '1' PYTHONUNBUFFERED: '1' steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: pyvista/setup-headless-display-action@main with: qt: true @@ -115,10 +115,10 @@ jobs: OPENBLAS_NUM_THREADS: "1" PYTHONUNBUFFERED: "1" steps: - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: "3.10" - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: pyvista/setup-headless-display-action@main with: qt: true @@ -126,17 +126,14 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip setuptools wheel - pip install --upgrade --upgrade-strategy eager .[test] + pip install -e . --no-deps + pip install -r tools/requirements_old.txt - name: Display versions and environment information run: | echo $TZ date - python --version which python - - run: | - pip install -e . --no-deps - pip install -r tools/requirements_old.txt - - run: python -c "import pybispectra; import mne; print(f'PyBispectra {pybispectra.__version__}\n'); mne.sys_info()" + python -c "import pybispectra; import mne; print(f'PyBispectra {pybispectra.__version__}\n'); mne.sys_info()" - name: Download testing data run: python -c "from pybispectra.utils import DATASETS, get_example_data_paths; (get_example_data_paths(data) for data in DATASETS.keys())" - name: Run pytest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f7aef92..1c5f94e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ repos: # ruff PyBispectra - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.8 + rev: v0.16.0 hooks: - id: ruff name: ruff lint pybispectra @@ -10,7 +10,7 @@ repos: # ruff examples - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.8 + rev: v0.16.0 hooks: - id: ruff name: ruff lint examples @@ -19,7 +19,7 @@ repos: # codespell - repo: https://github.com/codespell-project/codespell - rev: v2.4.2 + rev: v2.4.3 hooks: - id: codespell additional_dependencies: diff --git a/changelog.md b/changelog.md index 65cc04b..0311962 100644 --- a/changelog.md +++ b/changelog.md @@ -8,6 +8,13 @@ No changes. ## [Version 1.3](https://pybispectra.readthedocs.io/1.3/) +### Version 1.3.2 + +##### Dependencies +- Dropped max supported Python version being pinned. + +
+ ### Version 1.3.1 ##### Dependencies diff --git a/docs/source/_static/css/custom.css b/docs/source/_static/css/custom.css index 7b67808..d4d7a90 100644 --- a/docs/source/_static/css/custom.css +++ b/docs/source/_static/css/custom.css @@ -6,6 +6,29 @@ ul { text-align: left } +/* widen main content area */ +.bd-page-width { + max-width: 100rem; +} + +/* make versionadded smaller and inline with param name */ +/* don't do for deprecated / versionchanged; they have extra info (too long to fit) */ +div.versionadded>p { + margin-top: 0; + margin-bottom: 0; +} + +div.versionadded { + margin: 0; + margin-left: 0.5rem; + display: inline-block; +} + +/* when FF supports :has(), change to → dd > p:has(+div.versionadded) */ +dd>p { + display: inline; +} + @media (max-width: 1199.98px) { .bd-header .navbar-header-items__start { flex-shrink: 1; diff --git a/docs/source/_static/versions.json b/docs/source/_static/versions.json index e947050..ec275f0 100644 --- a/docs/source/_static/versions.json +++ b/docs/source/_static/versions.json @@ -6,7 +6,7 @@ }, { "name": "1.3 (stable)", - "version": "1.3.1", + "version": "1.3.2", "url": "https://pybispectra.readthedocs.io/1.3/" }, { diff --git a/docs/source/conf.py b/docs/source/conf.py index 7a20a04..5a1ac63 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -16,7 +16,7 @@ project = "PyBispectra" copyright = "2023-2026, Thomas S. Binns" author = "Thomas S. Binns" -release = "1.3.1" +release = "1.3.2" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 8ea8185..04ee5e6 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -7,6 +7,10 @@ for version ≥ 1.2.2. PyBispectra requires Python ≥ 3.10. + +Package installation +-------------------- + To install PyBispectra, activate the desired environment or project in which you want the package, then install it using `pip `_: @@ -34,13 +38,29 @@ or `pixi `_: | +.. dropdown:: Compatibility for newly released Python versions + :icon: alert + :color: info + + If you encounter issues installing PyBispectra in environments with newly released Python versions, this may be due to a lack of compatible ``numba`` releases, one of PyBispectra's core dependencies. + + ``numba`` is not always immediately compatible with new Python versions, and there may be a delay of several weeks before a compatible version is released. + + You can find the supported Python versions for ``numba`` in this `table `_. + + If a compatible ``numba`` release is available for your environment's Python version and you are still encountering installation issues, please report this on the `PyBispectra issue page `_. + .. dropdown:: Compatibility with Python ≥ 3.14 on macOS Intel systems + :icon: alert + :color: info - Support for macOS Intel systems is limited to Python < 3.14 due to wheel availability limitations for `llvmlite`, which can lead to installation issues using ``pip`` and ``uv``. + Due to wheel availability limitations for ``llvmlite`` on macOS Intel systems with Python ≥ 3.14, installation issues can arise when using ``pip`` and ``uv``. - If you have a macOS Intel system and need to use Python ≥ 3.14, consider using ``conda`` or ``pixi`` for installation. + If you have a macOS Intel system and need to use Python ≥ 3.14, consider using ``conda`` or ``pixi`` for an easier installation. -| + +Creating an environment or project for installation +--------------------------------------------------- If you need to create an environment or project in which to install PyBispectra, you can do so using `venv `_, @@ -48,7 +68,7 @@ do so using `venv `_, `conda `_. With ``venv`` -------------- +~~~~~~~~~~~~~ In a shell with Python available, navigate to your project location and create the environment: @@ -66,7 +86,7 @@ then install the package: pip install pybispectra With ``uv`` ------------ +~~~~~~~~~~~ In a shell with ``uv`` available, navigate to your project location and create the environment: @@ -84,7 +104,7 @@ then install the package: uv pip install pybispectra With ``pixi`` -------------- +~~~~~~~~~~~~~ In a shell with ``pixi`` available, run the following commands: @@ -95,7 +115,7 @@ In a shell with ``pixi`` available, run the following commands: pixi add pybispectra With ``conda`` --------------- +~~~~~~~~~~~~~~ In a shell with ``conda`` available, run the following commands: diff --git a/environment.yml b/environment.yml index ffc93b9..631712f 100644 --- a/environment.yml +++ b/environment.yml @@ -2,7 +2,7 @@ name: pybispectra channels: - conda-forge dependencies: - - python>=3.10,<3.15 + - python>=3.10 - joblib>=1.2 - matplotlib>=3.6 - mne-base>=1.7 diff --git a/examples/plot_compute_time_resolved.py b/examples/plot_compute_time_resolved.py index ba9cd46..7759b03 100644 --- a/examples/plot_compute_time_resolved.py +++ b/examples/plot_compute_time_resolved.py @@ -18,7 +18,7 @@ from matplotlib import pyplot as plt from numpy.random import RandomState -from pybispectra import WaveShape, get_example_data_paths, compute_tfr +from pybispectra import WaveShape, compute_tfr, get_example_data_paths ######################################################################################## # Background diff --git a/pyproject.toml b/pyproject.toml index bcdbd81..2b80b9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ dynamic = [ ] # check tools/hatch_build.py for details name = "pybispectra" readme = "README.md" -version = "1.3.1" +version = "1.3.2" [project.optional-dependencies] dev = ["pybispectra[doc]", "pybispectra[lint]", "pybispectra[test]"] @@ -126,7 +126,11 @@ ignore_roles = [ report_level = "WARNING" [tool.ruff] -extend-exclude = ["docs", "examples/compute_*.py"] +extend-exclude = [ + "docs", + "examples/compute_*.py", + "src/pybispectra/utils/_docs.py", +] line-length = 88 [tool.ruff.lint.per-file-ignores] diff --git a/src/pybispectra/__init__.py b/src/pybispectra/__init__.py index 9cd9656..1a66d0f 100644 --- a/src/pybispectra/__init__.py +++ b/src/pybispectra/__init__.py @@ -1,6 +1,6 @@ """Initialisation of the PyBispectra package.""" -__version__ = "1.3.1" +__version__ = "1.3.2" from .cfc import AAC, PAC, PPC from .general import Bispectrum, Threenorm diff --git a/src/pybispectra/cfc/aac.py b/src/pybispectra/cfc/aac.py index 9335d8c..5067b9f 100644 --- a/src/pybispectra/cfc/aac.py +++ b/src/pybispectra/cfc/aac.py @@ -7,9 +7,9 @@ from pybispectra.utils._defaults import _precision from pybispectra.utils._process import _ProcessFreqBase from pybispectra.utils._utils import ( + _compute_in_parallel, _compute_pearsonr_2d, _fast_find_first, - _compute_in_parallel, ) @@ -61,7 +61,7 @@ class AAC(_ProcessFreqBase): verbose : bool Whether or not to report the progress of the processing. - """ # noqa: E501 + """ _data_precision: type = _precision.real # Real-valued TFR power @@ -72,9 +72,9 @@ class AAC(_ProcessFreqBase): def compute( self, indices: tuple[tuple[int]] | None = None, - f1s: tuple[int | float] | None = None, - f2s: tuple[int | float] | None = None, - times: tuple[int | float] | None = None, + f1s: tuple[float] | None = None, + f2s: tuple[float] | None = None, + times: tuple[float] | None = None, n_jobs: int = 1, ) -> None: r"""Compute AAC, averaged over epochs. diff --git a/src/pybispectra/cfc/pac.py b/src/pybispectra/cfc/pac.py index 937b69c..feaab72 100644 --- a/src/pybispectra/cfc/pac.py +++ b/src/pybispectra/cfc/pac.py @@ -80,9 +80,9 @@ class PAC(_ProcessBispectrum): def compute( self, indices: tuple[tuple[int]] | None = None, - f1s: tuple[int | float] | None = None, - f2s: tuple[int | float] | None = None, - times: tuple[int | float] | None = None, + f1s: tuple[float] | None = None, + f2s: tuple[float] | None = None, + times: tuple[float] | None = None, antisym: bool | tuple[bool] = False, norm: bool | tuple[bool] = False, n_jobs: int = 1, @@ -181,7 +181,7 @@ def compute( References ---------- .. footbibliography:: - """ # noqa: E501 + """ self._reset_attrs() self._sort_metrics(antisym, norm) diff --git a/src/pybispectra/cfc/ppc.py b/src/pybispectra/cfc/ppc.py index b09f280..361eab2 100644 --- a/src/pybispectra/cfc/ppc.py +++ b/src/pybispectra/cfc/ppc.py @@ -6,7 +6,7 @@ from pybispectra.utils import ResultsCFC from pybispectra.utils._defaults import _precision from pybispectra.utils._process import _ProcessFreqBase -from pybispectra.utils._utils import _fast_find_first, _compute_in_parallel +from pybispectra.utils._utils import _compute_in_parallel, _fast_find_first class PPC(_ProcessFreqBase): @@ -73,9 +73,9 @@ class PPC(_ProcessFreqBase): def compute( self, indices: tuple[tuple[int]] | None = None, - f1s: tuple[int | float] | None = None, - f2s: tuple[int | float] | None = None, - times: tuple[int | float] | None = None, + f1s: tuple[float] | None = None, + f2s: tuple[float] | None = None, + times: tuple[float] | None = None, n_jobs: int = 1, ) -> None: r"""Compute PPC, averaged over epochs. diff --git a/src/pybispectra/general/general.py b/src/pybispectra/general/general.py index 98d3be8..fdbacc2 100644 --- a/src/pybispectra/general/general.py +++ b/src/pybispectra/general/general.py @@ -22,16 +22,14 @@ class _General(_ProcessBispectrum): def _sort_indices(self, indices: tuple[tuple[int]] | None) -> None: """Sort kmn channel indices inputs.""" if indices is None: - indices = tuple( - [ - tuple(np.tile(range(self._n_chans), self._n_chans**2).tolist()), - tuple( - np.repeat( - np.tile(range(self._n_chans), self._n_chans), self._n_chans - ).tolist() - ), - tuple(np.repeat(range(self._n_chans), self._n_chans**2).tolist()), - ] + indices = ( + tuple(np.tile(range(self._n_chans), self._n_chans**2).tolist()), + tuple( + np.repeat( + np.tile(range(self._n_chans), self._n_chans), self._n_chans + ).tolist() + ), + tuple(np.repeat(range(self._n_chans), self._n_chans**2).tolist()), ) if not isinstance(indices, tuple): raise TypeError("`indices` must be a tuple.") @@ -118,9 +116,9 @@ class Bispectrum(_General): def compute( self, indices: tuple[tuple[int]] | None = None, - f1s: tuple[int | float] | None = None, - f2s: tuple[int | float] | None = None, - times: tuple[int | float] | None = None, + f1s: tuple[float] | None = None, + f2s: tuple[float] | None = None, + times: tuple[float] | None = None, n_jobs: int = 1, ) -> None: r"""Compute the bispectrum, averaged over epochs. @@ -167,7 +165,7 @@ def compute( .. warning:: For values of ``f1s`` higher than ``f2s`` or where ``f2s + f1s`` exceeds the Nyquist frequency, a :obj:`numpy.nan` value is returned. - """ # noqa: E501 + """ self._reset_attrs() self._sort_indices(indices) @@ -305,9 +303,9 @@ class Threenorm(_General): def compute( self, indices: tuple[tuple[int]] | None = None, - f1s: tuple[int | float] | None = None, - f2s: tuple[int | float] | None = None, - times: tuple[int | float] | None = None, + f1s: tuple[float] | None = None, + f2s: tuple[float] | None = None, + times: tuple[float] | None = None, n_jobs: int = 1, ) -> None: r"""Compute the threenorm, averaged over epochs. diff --git a/src/pybispectra/tde/tde.py b/src/pybispectra/tde/tde.py index 748cfff..bfffea5 100644 --- a/src/pybispectra/tde/tde.py +++ b/src/pybispectra/tde/tde.py @@ -1,7 +1,8 @@ """Tools for handling TDE analysis.""" +from collections.abc import Callable from copy import deepcopy -from typing import Callable +from typing import ClassVar import numpy as np from numba import njit @@ -10,7 +11,7 @@ from pybispectra.utils import ResultsTDE from pybispectra.utils._defaults import _precision from pybispectra.utils._process import _ProcessBispectrum -from pybispectra.utils._utils import _compute_in_parallel, _number_like, _int_like +from pybispectra.utils._utils import _compute_in_parallel, _int_like, _number_like class TDE(_ProcessBispectrum): @@ -97,7 +98,7 @@ class TDE(_ProcessBispectrum): _tde_iv_nosym: np.ndarray = None _tde_iv_antisym: np.ndarray = None - _kmn: dict = { + _kmn: ClassVar[dict] = { "xxx": (0, 0, 0), "yyy": (1, 1, 1), "xyx": (0, 1, 0), @@ -109,9 +110,9 @@ def __init__( self, data: np.ndarray, freqs: np.ndarray, - sampling_freq: int | float, + sampling_freq: float, verbose: bool = True, - ) -> None: # noqa: D107 + ) -> None: super().__init__(data, freqs, sampling_freq, times=None, verbose=verbose) self._sort_fft_coeffs() @@ -130,8 +131,8 @@ def _sort_fft_coeffs(self) -> None: def compute( self, indices: tuple[tuple[int]] | None = None, - fmin: int | float | tuple[int | float] = 0.0, - fmax: int | float | tuple[int | float] = np.inf, + fmin: float | tuple[float] = 0.0, + fmax: float | tuple[float] = np.inf, antisym: bool | tuple[bool] = False, method: int | tuple[int] = 1, n_jobs: int = 1, @@ -284,9 +285,7 @@ def _reset_attrs(self) -> None: self._xyz = None def _sort_freq_bands( - self, - fmin: int | float | tuple[int | float], - fmax: int | float | tuple[int | float], + self, fmin: float | tuple[float], fmax: float | tuple[float] ) -> None: """Sort inputs for the frequency bounds.""" if not isinstance(fmin, _number_like + (tuple,)): diff --git a/src/pybispectra/utils/_defaults.py b/src/pybispectra/utils/_defaults.py index fc35a9b..14156cf 100644 --- a/src/pybispectra/utils/_defaults.py +++ b/src/pybispectra/utils/_defaults.py @@ -9,7 +9,7 @@ class _Precision: Double precision (i.e., float64 and complex128) used by default. """ - def __init__(self) -> None: # noqa: D107 + def __init__(self) -> None: self.type = "double" self.real = np.float64 self.complex = np.complex128 diff --git a/src/pybispectra/utils/_plot.py b/src/pybispectra/utils/_plot.py index e7eb70d..c28a3e9 100644 --- a/src/pybispectra/utils/_plot.py +++ b/src/pybispectra/utils/_plot.py @@ -44,8 +44,8 @@ def _sort_plot_inputs( nodes: int | tuple[int] | None, n_rows: int, n_cols: int, - major_tick_intervals: int | float, - minor_tick_intervals: int | float, + major_tick_intervals: float, + minor_tick_intervals: float, ) -> tuple[int]: """Sort the plotting inputs. @@ -142,7 +142,7 @@ def _sort_freq_inputs( return f1s, f2s, f1_idcs, f2_idcs def _sort_time_inputs( - self, times: tuple[int | float] | None + self, times: tuple[float] | None ) -> tuple[np.ndarray | None, np.ndarray | None]: """Sort `times` input. @@ -234,7 +234,7 @@ def __init__( f2s: np.ndarray, times: np.ndarray | None, name: str, - ) -> None: # noqa: D107 + ) -> None: super().__init__(data, indices, name) self.f1s = f1s.copy() @@ -244,13 +244,13 @@ def __init__( def plot( self, nodes: int | tuple[int] | None = None, - f1s: tuple[int | float] | None = None, - f2s: tuple[int | float] | None = None, - times: tuple[int | float] | None = None, + f1s: tuple[float] | None = None, + f2s: tuple[float] | None = None, + times: tuple[float] | None = None, n_rows: int = 1, n_cols: int = 1, - major_tick_intervals: int | float = 5.0, - minor_tick_intervals: int | float = 1.0, + major_tick_intervals: float = 5.0, + minor_tick_intervals: float = 1.0, plot_absolute: bool = False, mirror_cbar_range: bool = True, cbar_range_abs: tuple[float] | list[tuple[float]] | None = None, @@ -400,13 +400,13 @@ def plot( def _sort_plot_inputs( self, nodes: int | tuple[int] | None, - f1s: tuple[int | float] | None, - f2s: tuple[int | float] | None, - times: tuple[int | float] | None, + f1s: tuple[float] | None, + f2s: tuple[float] | None, + times: tuple[float] | None, n_rows: int, n_cols: int, - major_tick_intervals: int | float, - minor_tick_intervals: int | float, + major_tick_intervals: float, + minor_tick_intervals: float, plot_absolute: bool, mirror_cbar_range: bool, cbar_range_abs: tuple[float] | list[tuple[float]] | None, @@ -469,8 +469,9 @@ def _sort_plot_inputs( cbar_range_phase, ] cbar_names = ["abs", "real", "imag", "phase"] - cbar_idx = 0 - for cbar_range, cbar_name in zip(cbar_ranges, cbar_names): + for cbar_idx, (cbar_range, cbar_name) in enumerate( + zip(cbar_ranges, cbar_names) + ): if not isinstance(cbar_range, (list, tuple, type(None))): raise TypeError( f"`cbar_range_{cbar_name}` must be a list, tuple, or None." @@ -490,7 +491,6 @@ def _sort_plot_inputs( f"Limits in `cbar_range_{cbar_name}` must have length of 2." ) cbar_ranges[cbar_idx] = cbar_range - cbar_idx += 1 return (nodes, f1s, f2s, f1_idcs, f2_idcs, times, time_idcs, cbar_ranges) @@ -555,8 +555,8 @@ def _plot_results( time_idcs: np.ndarray | None, n_rows: int, n_cols: int, - major_tick_intervals: int | float, - minor_tick_intervals: int | float, + major_tick_intervals: float, + minor_tick_intervals: float, plot_absolute: bool, mirror_cbar_range: bool, cbar_ranges: list[list[tuple[float | None]]], @@ -711,8 +711,8 @@ def _plot_results( def _set_axis_ticks( self, axis: plt.Axes, - major_tick_intervals: int | float, - minor_tick_intervals: int | float, + major_tick_intervals: float, + minor_tick_intervals: float, ) -> None: """Set major and minor tick intervals of x- and y-axes.""" axis.xaxis.set_major_locator(plt.MultipleLocator(major_tick_intervals)) @@ -757,7 +757,7 @@ def __init__( f2s: np.ndarray, times: np.ndarray | None, name: str, - ) -> None: # noqa: D107 + ) -> None: super().__init__(data, indices, name) self.f1s = f1s.copy() @@ -767,13 +767,13 @@ def __init__( def plot( self, nodes: int | tuple[int] | None = None, - f1s: tuple[int | float] | None = None, - f2s: tuple[int | float] | None = None, - times: tuple[int | float] | None = None, + f1s: tuple[float] | None = None, + f2s: tuple[float] | None = None, + times: tuple[float] | None = None, n_rows: int = 1, n_cols: int = 1, - major_tick_intervals: int | float = 5.0, - minor_tick_intervals: int | float = 1.0, + major_tick_intervals: float = 5.0, + minor_tick_intervals: float = 1.0, cbar_range: tuple[float] | list[tuple[float]] | None = None, show: bool = True, ) -> tuple[list[Figure], list[np.ndarray]]: @@ -836,7 +836,7 @@ def plot( ----- ``n_rows`` and ``n_cols`` of ``1`` will plot the results for each node on a new figure. - """ # noqa: E501 + """ nodes, f1s, f2s, f1_idcs, f2_idcs, times, time_idcs, cbar_range = ( self._sort_plot_inputs( nodes, @@ -876,13 +876,13 @@ def plot( def _sort_plot_inputs( self, nodes: int | tuple[int] | None, - f1s: tuple[int | float] | None, - f2s: tuple[int | float] | None, - times: tuple[int | float] | None, + f1s: tuple[float] | None, + f2s: tuple[float] | None, + times: tuple[float] | None, n_rows: int, n_cols: int, - major_tick_intervals: int | float, - minor_tick_intervals: int | float, + major_tick_intervals: float, + minor_tick_intervals: float, cbar_range: tuple[float] | list[tuple[float]] | None, ) -> tuple[ tuple[int], @@ -956,8 +956,8 @@ def _plot_results( time_idcs: np.ndarray | None, n_rows: int, n_cols: int, - major_tick_intervals: int | float, - minor_tick_intervals: int | float, + major_tick_intervals: float, + minor_tick_intervals: float, cbar_range: list[tuple[float | None]], ) -> tuple[list[Figure], list[np.ndarray]]: """Plot results on the relevant figures/subplots.""" @@ -1043,8 +1043,8 @@ def _plot_results( def _set_axis_ticks( self, axis: plt.Axes, - major_tick_intervals: int | float, - minor_tick_intervals: int | float, + major_tick_intervals: float, + minor_tick_intervals: float, ) -> None: """Set major and minor tick intervals of x- and y-axes.""" axis.xaxis.set_major_locator(plt.MultipleLocator(major_tick_intervals)) @@ -1086,7 +1086,7 @@ def __init__( freq_bands: tuple[tuple[float]] | None, times: np.ndarray, name: str, - ) -> None: # noqa: D107 + ) -> None: super().__init__(data, indices, name) self.tau = tau @@ -1102,11 +1102,11 @@ def plot( self, nodes: int | tuple[int] | None = None, freq_bands: int | tuple[int] | None = None, - times: tuple[int | float] | None = None, + times: tuple[float] | None = None, n_rows: int = 1, n_cols: int = 1, - major_tick_intervals: int | float = 500.0, - minor_tick_intervals: int | float = 100.0, + major_tick_intervals: float = 500.0, + minor_tick_intervals: float = 100.0, show: bool = True, ) -> tuple[list[Figure], list[np.ndarray]]: """Plot the results. @@ -1189,7 +1189,7 @@ def _sort_plot_inputs( self, nodes: int | tuple[int] | None, freq_bands: int | tuple[int] | None, - times: tuple[int | float] | None, + times: tuple[float] | None, n_rows: int, n_cols: int, major_tick_intervals: float, @@ -1290,8 +1290,8 @@ def _plot_results( time_idcs: np.ndarray, n_rows: int, n_cols: int, - major_tick_intervals: int | float, - minor_tick_intervals: int | float, + major_tick_intervals: float, + minor_tick_intervals: float, ) -> tuple[list[Figure], list[np.ndarray]]: """Plot results on the relevant figures/subplots.""" fig_i = 0 @@ -1385,8 +1385,8 @@ def _mark_delay( def _set_axis_ticks( self, axis: plt.Axes, - major_tick_intervals: int | float, - minor_tick_intervals: int | float, + major_tick_intervals: float, + minor_tick_intervals: float, ) -> None: """Set major and minor tick intervals of the x-axis.""" axis.xaxis.set_major_locator(plt.MultipleLocator(major_tick_intervals)) diff --git a/src/pybispectra/utils/_process.py b/src/pybispectra/utils/_process.py index ddc49f2..6a66dc5 100644 --- a/src/pybispectra/utils/_process.py +++ b/src/pybispectra/utils/_process.py @@ -39,7 +39,7 @@ def __init__( self, data: np.ndarray, freqs: np.ndarray, - sampling_freq: int | float, + sampling_freq: float, times: np.ndarray | None = None, verbose: bool = True, ) -> None: @@ -49,7 +49,7 @@ def _sort_init_inputs( self, data: np.ndarray, freqs: np.ndarray, - sampling_freq: int | float, + sampling_freq: float, times: np.ndarray | None, verbose: bool, ) -> None: @@ -135,11 +135,9 @@ def _sort_init_inputs( def _sort_indices(self, indices: tuple[tuple[int]] | None) -> None: """Sort seed-target indices inputs.""" if indices is None: - indices = tuple( - [ - tuple(np.tile(range(self._n_chans), self._n_chans).tolist()), - tuple(np.repeat(range(self._n_chans), self._n_chans).tolist()), - ] + indices = ( + tuple(np.tile(range(self._n_chans), self._n_chans).tolist()), + tuple(np.repeat(range(self._n_chans), self._n_chans).tolist()), ) if not isinstance(indices, tuple): raise TypeError("`indices` must be a tuple.") @@ -167,9 +165,7 @@ def _sort_indices(self, indices: tuple[tuple[int]] | None) -> None: self._n_cons = len(seeds) - def _sort_freqs( - self, f1s: tuple[int | float] | None, f2s: tuple[int | float] | None - ) -> None: + def _sort_freqs(self, f1s: tuple[float] | None, f2s: tuple[float] | None) -> None: """Sort frequency inputs.""" check_f1s = True check_f2s = True @@ -208,15 +204,14 @@ def _sort_freqs( ) self._f2s = self.freqs[f2_idcs] - if self.verbose: - if self._f1s.max() >= self._f2s.min(): - warn( - "At least one value in `f1s` is >= a value in `f2s`. The " - "corresponding result(s) will have a value of NaN.", - UserWarning, - ) + if self.verbose and self._f1s.max() >= self._f2s.min(): + warn( + "At least one value in `f1s` is >= a value in `f2s`. The corresponding " + "result(s) will have a value of NaN.", + UserWarning, + ) - def _sort_tmin_tmax(self, times: tuple[int | float] | None) -> None: + def _sort_tmin_tmax(self, times: tuple[float] | None) -> None: """Sort time range inputs.""" if times is None: times = (-np.inf, np.inf) @@ -308,34 +303,33 @@ def _sort_indices(self, indices: tuple[tuple[int]]) -> None: """Sort seed-target indices inputs.""" super()._sort_indices(indices) - if self.verbose: - if self._return_antisym and ( + if ( + self.verbose + and self._return_antisym + and ( any(seed == target for seed, target in zip(self._seeds, self._targets)) - ): - warn( - "The seed and target for at least one connection is the same " - "channel. The corresponding antisymmetrised result(s) will be " - "NaN-valued.", - UserWarning, - ) + ) + ): + warn( + "The seed and target for at least one connection is the same channel. " + "The corresponding antisymmetrised result(s) will be NaN-valued.", + UserWarning, + ) - def _sort_freqs( - self, f1s: tuple[int | float] | None, f2s: tuple[int | float] | None - ) -> None: + def _sort_freqs(self, f1s: tuple[float] | None, f2s: tuple[float] | None) -> None: """Sort frequency inputs.""" super()._sort_freqs(f1s, f2s) - if self.verbose: - if any( - hfreq + lfreq not in self.freqs - for hfreq in self._f2s - for lfreq in self._f1s - ): - warn( - "At least one value of `f2s` + `f1s` is not present in the " - "frequencies. The corresponding result(s) will be NaN-valued.", - UserWarning, - ) + if self.verbose and any( + hfreq + lfreq not in self.freqs + for hfreq in self._f2s + for lfreq in self._f1s + ): + warn( + "At least one value of `f2s` + `f1s` is not present in the " + "frequencies. The corresponding result(s) will be NaN-valued.", + UserWarning, + ) @njit diff --git a/src/pybispectra/utils/_utils.py b/src/pybispectra/utils/_utils.py index d455351..f1eedb3 100644 --- a/src/pybispectra/utils/_utils.py +++ b/src/pybispectra/utils/_utils.py @@ -8,7 +8,6 @@ from pybispectra.utils._defaults import _precision - # Aliases for type checking _int_like = (int, np.integer) _float_like = (float, np.floating) @@ -109,7 +108,7 @@ def _get_block_indices(block_i: int, limit: int, n_jobs: int) -> np.ndarray: @njit def _fast_find_first( - vector: np.ndarray, value: int | float, start_idx: int = 0 + vector: np.ndarray, value: float, start_idx: int = 0 ) -> int: # pragma: no cover """Quickly find the first index of a value in a 1D array using Numba. diff --git a/src/pybispectra/utils/ged.py b/src/pybispectra/utils/ged.py index b04db4c..9131313 100644 --- a/src/pybispectra/utils/ged.py +++ b/src/pybispectra/utils/ged.py @@ -1,14 +1,15 @@ """Tools for performing generalised eigendecompositions.""" -from packaging.version import Version from multiprocessing import cpu_count from warnings import warn import numpy as np import scipy as sp -from mne import Info, __version__ as mne_version +from mne import Info +from mne import __version__ as mne_version from mne.decoding import SSD from mne.time_frequency import csd_array_fourier, csd_array_multitaper +from packaging.version import Version from pybispectra.utils._defaults import _precision from pybispectra.utils._utils import _create_mne_info, _int_like, _number_like @@ -168,9 +169,9 @@ class SpatioSpectralFilter: def __init__( self, data: np.ndarray, - sampling_freq: int | float, + sampling_freq: float, verbose: bool = True, - ) -> None: # noqa: D107 + ) -> None: self.verbose = verbose self._sort_init_inputs(data, sampling_freq) @@ -193,9 +194,9 @@ def _sort_init_inputs(self, data: np.ndarray, sampling_freq: float) -> None: def _sort_freq_bounds( self, - signal_bounds: tuple[int | float], - noise_bounds: tuple[int | float], - signal_noise_gap: int | float, + signal_bounds: tuple[float], + noise_bounds: tuple[float], + signal_noise_gap: float, ) -> None: """Sort frequency bound inputs.""" if not isinstance(signal_bounds, tuple) or not all( @@ -307,9 +308,9 @@ def _sort_csd_method(self, csd_method: str) -> None: def fit_ssd( self, - signal_bounds: tuple[int | float], - noise_bounds: tuple[int | float], - signal_noise_gap: int | float = 1.0, + signal_bounds: tuple[float], + noise_bounds: tuple[float], + signal_noise_gap: float = 1.0, bandpass_filter: bool = False, indices: tuple[int] | None = None, rank: int | None = None, @@ -373,9 +374,9 @@ def fit_ssd( def _create_mne_filt_params( self, - signal_bounds: tuple[int | float], - noise_bounds: tuple[int | float], - signal_noise_gap: int | float, + signal_bounds: tuple[float], + noise_bounds: tuple[float], + signal_noise_gap: float, ) -> tuple[dict, dict]: """Create filter parameters for use with MNE's SSD implementation. @@ -452,14 +453,14 @@ def _compute_ssd( def fit_hpmax( self, - signal_bounds: tuple[int | float], - noise_bounds: tuple[int | float], + signal_bounds: tuple[float], + noise_bounds: tuple[float], n_harmonics: int = -1, indices: tuple[int] | None = None, rank: int | None = None, csd_method: str = "multitaper", n_fft: int | None = None, - mt_bandwidth: int | float = 5.0, + mt_bandwidth: float = 5.0, mt_adaptive: bool = True, mt_low_bias: bool = True, n_jobs: int = 1, @@ -563,7 +564,7 @@ def _compute_csd( self, csd_method: str, n_fft: int | None, - mt_bandwidth: int | float, + mt_bandwidth: float, mt_adaptive: bool, mt_low_bias: bool, n_jobs: int, @@ -837,7 +838,7 @@ def fit_transform_hpmax(self, *args: tuple, **kwargs: dict) -> np.ndarray: return self.transform() def get_transformed_data( - self, min_ratio: int | float = -np.inf, copy: bool = True + self, min_ratio: float = -np.inf, copy: bool = True ) -> np.ndarray: """Return the transformed data. diff --git a/src/pybispectra/utils/results.py b/src/pybispectra/utils/results.py index 8cf3569..184fdd6 100644 --- a/src/pybispectra/utils/results.py +++ b/src/pybispectra/utils/results.py @@ -5,9 +5,9 @@ import numpy as np from matplotlib.figure import Figure +from pybispectra.utils._defaults import _precision from pybispectra.utils._plot import _PlotCFC, _PlotGeneral, _PlotTDE, _PlotWaveShape from pybispectra.utils._utils import _int_like -from pybispectra.utils._defaults import _precision class _ResultsBase(ABC): @@ -316,7 +316,7 @@ def __init__( f2s: np.ndarray, times: np.ndarray | None = None, name: str = "CFC", - ) -> None: # noqa: D107 + ) -> None: super().__init__(data, (3, 4), name) self._sort_init_inputs(indices, f1s, f2s, times) @@ -368,13 +368,13 @@ def _get_compact_results_child(self) -> tuple[np.ndarray, tuple[tuple[int]]]: def plot( self, nodes: int | tuple[int] | None = None, - f1s: tuple[int | float] | None = None, - f2s: tuple[int | float] | None = None, - times: tuple[int | float] | None = None, + f1s: tuple[float] | None = None, + f2s: tuple[float] | None = None, + times: tuple[float] | None = None, n_rows: int = 1, n_cols: int = 1, - major_tick_intervals: int | float = 5.0, - minor_tick_intervals: int | float = 1.0, + major_tick_intervals: float = 5.0, + minor_tick_intervals: float = 1.0, cbar_range: tuple[float] | list[tuple[float]] | None = None, show: bool = True, ) -> tuple[list[Figure], list[np.ndarray]]: @@ -509,9 +509,9 @@ class ResultsTDE(_ResultsBase): tau : ~numpy.ndarray, shape of [nodes, frequency_bands] Estimated time delay (in ms) for each connection and frequency band. - """ # noqa: E501 + """ - freq_bands: tuple[tuple[int | float]] = None + freq_bands: tuple[tuple[float]] = None _n_fbands: int = None def __repr__(self) -> str: @@ -535,9 +535,9 @@ def __init__( data: np.ndarray, indices: tuple[tuple[int]], times: np.ndarray, - freq_bands: tuple[tuple[int | float]] | None = None, + freq_bands: tuple[tuple[float]] | None = None, name: str = "TDE", - ) -> None: # noqa: D107 + ) -> None: super().__init__(data, (3,), name) self._sort_init_inputs(indices, times, freq_bands) @@ -553,10 +553,7 @@ def __init__( ) def _sort_init_inputs( - self, - indices: tuple[tuple[int]], - times: np.ndarray, - freq_bands: tuple[int | float], + self, indices: tuple[tuple[int]], times: np.ndarray, freq_bands: tuple[float] ) -> None: """Sort inputs to the object.""" super()._sort_indices_seeds_targets(indices) @@ -575,7 +572,7 @@ def _sort_times(self, times: np.ndarray) -> None: self.times = times - def _sort_freq_bands(self, freq_bands: tuple[tuple[int | float]]) -> None: + def _sort_freq_bands(self, freq_bands: tuple[tuple[float]]) -> None: """Sort ``freq_bands`` input.""" if freq_bands is not None: if not isinstance(freq_bands, tuple): @@ -621,11 +618,11 @@ def plot( self, nodes: int | tuple[int] | None = None, freq_bands: int | tuple[int] | None = None, - times: tuple[int | float] | None = None, + times: tuple[float] | None = None, n_rows: int = 1, n_cols: int = 1, - major_tick_intervals: int | float = 500.0, - minor_tick_intervals: int | float = 100.0, + major_tick_intervals: float = 500.0, + minor_tick_intervals: float = 100.0, show: bool = True, ) -> tuple[list[Figure], list[np.ndarray]]: """Plot the results. @@ -780,7 +777,7 @@ def __init__( f2s: np.ndarray, times: np.ndarray | None = None, name: str = "Waveshape", - ) -> None: # noqa: D107 + ) -> None: super().__init__(data, (3, 4), name) self._sort_init_inputs(indices, f1s, f2s, times) @@ -831,13 +828,13 @@ def get_results(self, copy: bool = True) -> np.ndarray: def plot( self, nodes: int | tuple[int] | None = None, - f1s: tuple[int | float] | None = None, - f2s: tuple[int | float] | None = None, - times: tuple[int | float] | None = None, + f1s: tuple[float] | None = None, + f2s: tuple[float] | None = None, + times: tuple[float] | None = None, n_rows: int = 1, n_cols: int = 1, - major_tick_intervals: int | float = 5.0, - minor_tick_intervals: int | float = 1.0, + major_tick_intervals: float = 5.0, + minor_tick_intervals: float = 1.0, plot_absolute: bool = False, mirror_cbar_range: bool = True, cbar_range_abs: tuple[float] | list[tuple[float]] | None = None, @@ -1048,7 +1045,7 @@ def __init__( f2s: np.ndarray, times: np.ndarray | None = None, name: str = "General", - ) -> None: # noqa: D107 + ) -> None: super().__init__(data, (3, 4), name) self._sort_init_inputs(indices, f1s, f2s, times) @@ -1145,13 +1142,13 @@ def _get_compact_results_child(self) -> tuple[np.ndarray, tuple[tuple[int]]]: def plot( self, nodes: int | tuple[int] | None = None, - f1s: tuple[int | float] | None = None, - f2s: tuple[int | float] | None = None, - times: tuple[int | float] | None = None, + f1s: tuple[float] | None = None, + f2s: tuple[float] | None = None, + times: tuple[float] | None = None, n_rows: int = 1, n_cols: int = 1, - major_tick_intervals: int | float = 5.0, - minor_tick_intervals: int | float = 1.0, + major_tick_intervals: float = 5.0, + minor_tick_intervals: float = 1.0, plot_absolute: bool = False, mirror_cbar_range: bool = True, cbar_range_abs: tuple[float] | list[tuple[float]] | None = None, @@ -1254,7 +1251,7 @@ def plot( ----- ``n_rows`` and ``n_cols`` of ``1`` will plot the results for each node on a new figure. - """ # noqa: E501 + """ figures, axes = self._plotting.plot( nodes=nodes, f1s=f1s, diff --git a/src/pybispectra/utils/utils.py b/src/pybispectra/utils/utils.py index aa7635b..0ba7134 100644 --- a/src/pybispectra/utils/utils.py +++ b/src/pybispectra/utils/utils.py @@ -1,13 +1,14 @@ """Public tools for handling data and processing results.""" +from collections.abc import Callable from multiprocessing import cpu_count -from typing import Callable -from packaging.version import Version -import pooch import numpy as np +import pooch import scipy as sp -from mne import time_frequency, __version__ as mne_version +from mne import __version__ as mne_version +from mne import time_frequency +from packaging.version import Version from pybispectra import __version__ as version from pybispectra.utils._defaults import _precision @@ -16,7 +17,7 @@ def compute_fft( data: np.ndarray, - sampling_freq: int | float, + sampling_freq: float, n_points: int | None = None, window: str = "hanning", n_jobs: int = 1, @@ -100,7 +101,7 @@ def compute_fft( def _compute_fft_input_checks( data: np.ndarray, - sampling_freq: int | float, + sampling_freq: float, n_points: int | None, window: str, n_jobs: int, @@ -156,13 +157,13 @@ def _compute_fft_input_checks( def compute_tfr( data: np.ndarray, - sampling_freq: int | float, + sampling_freq: float, freqs: np.ndarray, tfr_mode: str = "morlet", - n_cycles: np.ndarray | int | float = 7.0, + n_cycles: np.ndarray | float = 7.0, zero_mean_wavelets: bool | None = None, use_fft: bool = True, - multitaper_time_bandwidth: int | float = 4.0, + multitaper_time_bandwidth: float = 4.0, output: str = "power", n_jobs: int = 1, verbose: bool = True, @@ -206,10 +207,10 @@ def compute_tfr( output : ``"power"`` | ``"complex"`` (default ``"power"``) Type of TFR output to return. + .. versionadded:: 1.3 .. note:: If ``output = "complex"`` and ``tfr_mode = "multitaper"``, returning weights for each taper requires MNE version 1.10 or higher. - .. versionadded:: 1.3 n_jobs : int (default ``1``) Number of jobs to run in parallel. If ``-1``, all available CPUs are used. @@ -290,13 +291,13 @@ def compute_tfr( def _compute_tfr_input_checks( data: np.ndarray, - sampling_freq: int | float, + sampling_freq: float, freqs: np.ndarray, tfr_mode: str, - n_cycles: np.ndarray | int | float, + n_cycles: np.ndarray | float, zero_mean_wavelets: bool | None, use_fft: bool, - multitaper_time_bandwidth: int | float, + multitaper_time_bandwidth: float, output: str, n_jobs: int, verbose: bool, @@ -362,9 +363,10 @@ def _compute_tfr_input_checks( if not isinstance(use_fft, bool): raise TypeError("`use_fft` must be a bool.") - if tfr_mode == "multitaper": - if not isinstance(multitaper_time_bandwidth, _number_like): - raise TypeError("`multitaper_time_bandwidth` must be an int or a float.") + if tfr_mode == "multitaper" and not isinstance( + multitaper_time_bandwidth, _number_like + ): + raise TypeError("`multitaper_time_bandwidth` must be an int or a float.") outputs = ["power", "complex"] if not isinstance(output, str): @@ -394,7 +396,7 @@ def _compute_tfr_input_checks( return tfr_func, return_weights, n_jobs -def compute_rank(data: np.ndarray, sv_tol: int | float = 1e-5) -> int: +def compute_rank(data: np.ndarray, sv_tol: float = 1e-5) -> int: """Compute the minimum rank of data from non-zero singular values. Parameters @@ -536,7 +538,7 @@ def get_example_data_paths(name: str, verbose: bool = False) -> str: If the file is not found in the local cache (see :func:`pooch.os_cache` for the location), it will be downloaded automatically. """ - if name not in DATASETS.keys(): + if name not in DATASETS: raise ValueError(f"`name` must be one of: {list(DATASETS.keys())}") return _pooch.fetch(fname=DATASETS[name], progressbar=verbose) diff --git a/src/pybispectra/waveshape/waveshape.py b/src/pybispectra/waveshape/waveshape.py index 61bd186..79a9641 100644 --- a/src/pybispectra/waveshape/waveshape.py +++ b/src/pybispectra/waveshape/waveshape.py @@ -8,8 +8,8 @@ _compute_threenorm, _ProcessBispectrum, ) -from pybispectra.utils.results import ResultsWaveShape from pybispectra.utils._utils import _compute_in_parallel, _int_like +from pybispectra.utils.results import ResultsWaveShape np.seterr(divide="ignore", invalid="ignore") # no warning for NaN division @@ -78,7 +78,7 @@ class WaveShape(_ProcessBispectrum): References ---------- .. footbibliography:: - """ # noqa: E501 + """ _return_nonorm = False _return_threenorm = False @@ -86,9 +86,9 @@ class WaveShape(_ProcessBispectrum): def compute( self, indices: tuple[int] | None = None, - f1s: tuple[int | float] | None = None, - f2s: tuple[int | float] | None = None, - times: tuple[int | float] | None = None, + f1s: tuple[float] | None = None, + f2s: tuple[float] | None = None, + times: tuple[float] | None = None, norm: bool | tuple[bool] = True, n_jobs: int = 1, ) -> None: diff --git a/tools/hatch_build.py b/tools/hatch_build.py index a49c51e..aac907a 100644 --- a/tools/hatch_build.py +++ b/tools/hatch_build.py @@ -16,8 +16,6 @@ def update(self, metadata): requires_python = ">=3.10" if is_macos_intel: requires_python += ", <3.14" - else: - requires_python += ", <3.15" metadata["requires-python"] = requires_python # dependencies