diff --git a/.github/workflows/nox.yml b/.github/workflows/nox.yml index f021d3b2c..a03b2d7b2 100644 --- a/.github/workflows/nox.yml +++ b/.github/workflows/nox.yml @@ -12,7 +12,7 @@ jobs: fail-fast: false matrix: platform: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 diff --git a/adaptive/learner/average_learner.py b/adaptive/learner/average_learner.py index c3d4892b4..3252530b4 100644 --- a/adaptive/learner/average_learner.py +++ b/adaptive/learner/average_learner.py @@ -1,7 +1,7 @@ from __future__ import annotations +from collections.abc import Callable from math import sqrt -from typing import Callable import cloudpickle import numpy as np diff --git a/adaptive/learner/average_learner1D.py b/adaptive/learner/average_learner1D.py index 9678b4f64..cb844f011 100644 --- a/adaptive/learner/average_learner1D.py +++ b/adaptive/learner/average_learner1D.py @@ -3,10 +3,9 @@ import math import sys from collections import defaultdict -from collections.abc import Iterable, Sequence +from collections.abc import Callable, Iterable, Sequence from copy import deepcopy from math import hypot -from typing import Callable import numpy as np import scipy.stats diff --git a/adaptive/learner/balancing_learner.py b/adaptive/learner/balancing_learner.py index e9a4a661e..b8a340f95 100644 --- a/adaptive/learner/balancing_learner.py +++ b/adaptive/learner/balancing_learner.py @@ -1,13 +1,12 @@ from __future__ import annotations import itertools -import sys from collections import defaultdict -from collections.abc import Iterable, Sequence +from collections.abc import Callable, Iterable, Sequence from contextlib import suppress from functools import partial from operator import itemgetter -from typing import Any, Callable, Union, cast +from typing import Any, Literal, TypeAlias, cast import numpy as np @@ -16,13 +15,6 @@ from adaptive.types import Int, Real from adaptive.utils import cache_latest, named_product, restore -if sys.version_info >= (3, 10): - from typing import TypeAlias -else: - from typing_extensions import TypeAlias - -from typing import Literal - try: import pandas @@ -38,11 +30,9 @@ def dispatch(child_functions: list[Callable], arg: Any) -> Any: STRATEGY_TYPE: TypeAlias = Literal["loss_improvements", "loss", "npoints", "cycle"] -CDIMS_TYPE: TypeAlias = Union[ - Sequence[dict[str, Any]], - tuple[Sequence[str], Sequence[tuple[Any, ...]]], - None, -] +CDIMS_TYPE: TypeAlias = ( + Sequence[dict[str, Any]] | tuple[Sequence[str], Sequence[tuple[Any, ...]]] | None +) class BalancingLearner(BaseLearner): diff --git a/adaptive/learner/base_learner.py b/adaptive/learner/base_learner.py index 73720dd52..f5ef73eca 100644 --- a/adaptive/learner/base_learner.py +++ b/adaptive/learner/base_learner.py @@ -1,8 +1,9 @@ from __future__ import annotations import abc +from collections.abc import Callable from contextlib import suppress -from typing import TYPE_CHECKING, Any, Callable, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar import cloudpickle diff --git a/adaptive/learner/data_saver.py b/adaptive/learner/data_saver.py index a69807389..644691a57 100644 --- a/adaptive/learner/data_saver.py +++ b/adaptive/learner/data_saver.py @@ -2,7 +2,8 @@ import functools from collections import OrderedDict -from typing import Any, Callable +from collections.abc import Callable +from typing import Any from adaptive.learner.base_learner import BaseLearner, LearnerType from adaptive.utils import copy_docstring_from diff --git a/adaptive/learner/integrator_learner.py b/adaptive/learner/integrator_learner.py index 0fc97df67..d6ee9ef1d 100644 --- a/adaptive/learner/integrator_learner.py +++ b/adaptive/learner/integrator_learner.py @@ -3,9 +3,10 @@ import sys from collections import defaultdict +from collections.abc import Callable from math import sqrt from operator import attrgetter -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING import cloudpickle import numpy as np diff --git a/adaptive/learner/learner1D.py b/adaptive/learner/learner1D.py index bf04743bd..6cd88b4ad 100644 --- a/adaptive/learner/learner1D.py +++ b/adaptive/learner/learner1D.py @@ -3,10 +3,9 @@ import collections.abc import itertools import math -import sys -from collections.abc import Sequence +from collections.abc import Callable, Sequence from copy import copy, deepcopy -from typing import TYPE_CHECKING, Any, Callable, Optional, Union +from typing import TYPE_CHECKING, Any, TypeAlias import cloudpickle import numpy as np @@ -24,12 +23,6 @@ partial_function_from_dataframe, ) -if sys.version_info >= (3, 10): - from typing import TypeAlias -else: - from typing_extensions import TypeAlias - - try: import pandas @@ -42,28 +35,21 @@ # -- types -- # Commonly used types - Interval: TypeAlias = Union[tuple[float, float], tuple[float, float, int]] - NeighborsType: TypeAlias = SortedDict[float, list[Optional[float]]] + Interval: TypeAlias = tuple[float, float] | tuple[float, float, int] + NeighborsType: TypeAlias = SortedDict[float, list[float | None]] # Types for loss_per_interval functions XsType0: TypeAlias = tuple[float, float] - YsType0: TypeAlias = Union[tuple[float, float], tuple[np.ndarray, np.ndarray]] - XsType1: TypeAlias = tuple[ - Optional[float], Optional[float], Optional[float], Optional[float] - ] - YsType1: TypeAlias = Union[ - tuple[Optional[float], Optional[float], Optional[float], Optional[float]], - tuple[ - Optional[np.ndarray], - Optional[np.ndarray], - Optional[np.ndarray], - Optional[np.ndarray], - ], - ] - XsTypeN: TypeAlias = tuple[Optional[float], ...] - YsTypeN: TypeAlias = Union[ - tuple[Optional[float], ...], tuple[Optional[np.ndarray], ...] - ] + YsType0: TypeAlias = tuple[float, float] | tuple[np.ndarray, np.ndarray] + XsType1: TypeAlias = tuple[float | None, float | None, float | None, float | None] + YsType1: TypeAlias = ( + tuple[float | None, float | None, float | None, float | None] + | tuple[ + np.ndarray | None, np.ndarray | None, np.ndarray | None, np.ndarray | None + ] + ) + XsTypeN: TypeAlias = tuple[float | None, ...] + YsTypeN: TypeAlias = tuple[float | None, ...] | tuple[np.ndarray | None, ...] __all__ = [ @@ -598,7 +584,7 @@ def tell(self, x: float, y: Float | Sequence[Float] | np.ndarray) -> None: ) # either it is a float/int, if not, try casting to a np.array - if not isinstance(y, (float, int)): + if not isinstance(y, float | int): y = np.asarray(y, dtype=float) # Add point to the real data dict diff --git a/adaptive/learner/learner2D.py b/adaptive/learner/learner2D.py index 15864a808..49b9cef56 100644 --- a/adaptive/learner/learner2D.py +++ b/adaptive/learner/learner2D.py @@ -3,10 +3,9 @@ import itertools import warnings from collections import OrderedDict -from collections.abc import Iterable +from collections.abc import Callable, Iterable from copy import copy from math import sqrt -from typing import Callable import cloudpickle import numpy as np diff --git a/adaptive/learner/sequence_learner.py b/adaptive/learner/sequence_learner.py index c307744fd..e8d83af5d 100644 --- a/adaptive/learner/sequence_learner.py +++ b/adaptive/learner/sequence_learner.py @@ -1,8 +1,7 @@ from __future__ import annotations -import sys from copy import copy -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, TypeAlias import cloudpickle from sortedcontainers import SortedDict, SortedSet @@ -16,8 +15,7 @@ ) if TYPE_CHECKING: - from collections.abc import Sequence - from typing import Callable + from collections.abc import Callable, Sequence try: import pandas @@ -27,10 +25,6 @@ except ModuleNotFoundError: with_pandas = False -if sys.version_info >= (3, 10): - from typing import TypeAlias -else: - from typing_extensions import TypeAlias PointType: TypeAlias = tuple[Int, Any] diff --git a/adaptive/runner.py b/adaptive/runner.py index 4f877096f..592fca4a1 100644 --- a/adaptive/runner.py +++ b/adaptive/runner.py @@ -8,77 +8,48 @@ import itertools import pickle import platform -import sys import time import traceback import warnings +from collections.abc import Callable from contextlib import suppress from datetime import datetime, timedelta from importlib.util import find_spec -from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, Union +from typing import TYPE_CHECKING, Any, Literal, TypeAlias import loky -from adaptive import ( - BalancingLearner, - DataSaver, - IntegratorLearner, - SequenceLearner, -) +from adaptive import BalancingLearner, DataSaver, IntegratorLearner, SequenceLearner from adaptive.learner.base_learner import LearnerType from adaptive.notebook_integration import in_ipynb, live_info, live_plot from adaptive.utils import SequentialExecutor -ExecutorTypes: TypeAlias = Union[ - concurrent.ProcessPoolExecutor, - concurrent.ThreadPoolExecutor, - SequentialExecutor, - loky.reusable_executor._ReusablePoolExecutor, -] -FutureTypes: TypeAlias = Union[concurrent.Future, asyncio.Future, asyncio.Task] - if TYPE_CHECKING: import holoviews -if sys.version_info >= (3, 10): - from typing import TypeAlias -else: - from typing_extensions import TypeAlias - - with_ipyparallel = find_spec("ipyparallel") is not None with_distributed = find_spec("distributed") is not None with_mpi4py = find_spec("mpi4py") is not None if TYPE_CHECKING: - ExecutorTypes = Optional[()] - FutureTypes = Optional[()] - - if with_distributed: - import distributed - - ExecutorTypes = Optional[ - Union[ - ExecutorTypes, distributed.Client, distributed.cfexecutor.ClientExecutor - ] - ] - - if with_mpi4py: - import mpi4py.futures - - ExecutorTypes = Optional[Union[ExecutorTypes, mpi4py.futures.MPIPoolExecutor]] - - if with_ipyparallel: - import ipyparallel - from ipyparallel.client.asyncresult import AsyncResult + import distributed + import ipyparallel + import mpi4py.futures + + ExecutorTypes: TypeAlias = ( + concurrent.ProcessPoolExecutor + | concurrent.ThreadPoolExecutor + | SequentialExecutor + | loky.reusable_executor._ReusablePoolExecutor + | distributed.Client + | distributed.cfexecutor.ClientExecutor + | mpi4py.futures.MPIPoolExecutor + | ipyparallel.Client + | ipyparallel.client.view.ViewExecutor + ) + FutureTypes: TypeAlias = concurrent.Future | asyncio.Future - ExecutorTypes = Optional[ - Union[ - ExecutorTypes, ipyparallel.Client, ipyparallel.client.view.ViewExecutor - ] - ] - FutureTypes = Optional[Union[FutureTypes, AsyncResult]] with suppress(ModuleNotFoundError): import uvloop @@ -203,7 +174,7 @@ def __init__( self._max_tasks = ntasks - self._pending_tasks: dict[concurrent.Future, int] = {} + self._pending_tasks: dict[FutureTypes, int] = {} # if we instantiate our own executor, then we are also responsible # for calling 'shutdown' @@ -292,7 +263,8 @@ def _process_futures( pid = self._pending_tasks.pop(fut) try: y = fut.result() - t = time.time() - fut.start_time # total execution time + # total execution time + t = time.time() - fut.start_time # type: ignore[union-attr] except Exception as e: self._tracebacks[pid] = traceback.format_exc() self._to_retry[pid] = self._to_retry.get(pid, 0) + 1 @@ -508,12 +480,12 @@ def _run(self) -> None: try: while not self.goal(self.learner): futures = self._get_futures() - done, _ = concurrent.wait(futures, return_when=first_completed) - self._process_futures(done) + done, _ = concurrent.wait(futures, return_when=first_completed) # type: ignore[arg-type] + self._process_futures(done) # type: ignore[arg-type] finally: remaining = self._remove_unfinished() if remaining: - concurrent.wait(remaining) + concurrent.wait(remaining) # type: ignore[arg-type] # Some futures get their result set, despite being cancelled. # see https://github.com/python-adaptive/adaptive/issues/319 with_result = {f for f in remaining if not f.cancelled() and f.done()} @@ -835,13 +807,12 @@ async def _run(self) -> None: try: while not self.goal(self.learner): futures = self._get_futures() - kw = {"loop": self.ioloop} if sys.version_info[:2] < (3, 10) else {} - done, _ = await asyncio.wait(futures, return_when=first_completed, **kw) # type: ignore[arg-type] + done, _ = await asyncio.wait(futures, return_when=first_completed) # type: ignore[arg-type,type-var] self._process_futures(done) finally: remaining = self._remove_unfinished() if remaining: - await asyncio.wait(remaining) + await asyncio.wait(remaining) # type: ignore[type-var] self._cleanup() def elapsed_time(self) -> float: @@ -1062,9 +1033,7 @@ def _get_ncores( import mpi4py.futures if with_ipyparallel and isinstance(ex, ipyparallel.client.view.ViewExecutor): return len(ex.view) - elif isinstance( - ex, (concurrent.ProcessPoolExecutor, concurrent.ThreadPoolExecutor) - ): + elif isinstance(ex, concurrent.ProcessPoolExecutor | concurrent.ThreadPoolExecutor): return ex._max_workers # type: ignore[union-attr] elif isinstance(ex, loky.reusable_executor._ReusablePoolExecutor): return ex._max_workers # type: ignore[union-attr] @@ -1119,7 +1088,7 @@ def stop_after(*, seconds=0, minutes=0, hours=0) -> Callable[[LearnerType], bool class _TimeGoal: def __init__(self, dt: timedelta | datetime | int | float): - self.dt = dt if isinstance(dt, (timedelta, datetime)) else timedelta(seconds=dt) + self.dt = dt if isinstance(dt, timedelta | datetime) else timedelta(seconds=dt) self.start_time = None def __call__(self, _): diff --git a/adaptive/tests/algorithm_4.py b/adaptive/tests/algorithm_4.py index 27832298e..8741fdc53 100644 --- a/adaptive/tests/algorithm_4.py +++ b/adaptive/tests/algorithm_4.py @@ -3,8 +3,8 @@ from __future__ import annotations from collections import defaultdict +from collections.abc import Callable from fractions import Fraction -from typing import Callable import numpy as np from numpy.testing import assert_allclose diff --git a/adaptive/tests/test_average_learner1d.py b/adaptive/tests/test_average_learner1d.py index c0148c5e9..a9be7fce2 100644 --- a/adaptive/tests/test_average_learner1d.py +++ b/adaptive/tests/test_average_learner1d.py @@ -21,7 +21,7 @@ def almost_equal_dicts(a, b): if ( v1 is None or v2 is None - or isinstance(v1, (tuple, list)) + or isinstance(v1, tuple | list) and any(x is None for x in chain(v1, v2)) ): assert v1 == v2 diff --git a/adaptive/tests/test_notebook_integration.py b/adaptive/tests/test_notebook_integration.py index 3e4ddb298..2fb266f2a 100644 --- a/adaptive/tests/test_notebook_integration.py +++ b/adaptive/tests/test_notebook_integration.py @@ -1,7 +1,5 @@ from __future__ import annotations -import os -import sys from typing import TYPE_CHECKING import pytest @@ -16,13 +14,9 @@ except ImportError: with_notebook_dependencies = False -# XXX: remove when is fixed https://github.com/ipython/ipykernel/issues/468 -skip_because_of_bug = os.name == "nt" and sys.version_info[:2] == (3, 8) - @pytest.mark.skipif( - not with_notebook_dependencies or skip_because_of_bug, - reason="notebook dependencies are not installed", + not with_notebook_dependencies, reason="notebook dependencies are not installed" ) def test_private_api_used_in_live_info(): """We are catching all errors in diff --git a/adaptive/types.py b/adaptive/types.py index 367445ee4..e2869d46e 100644 --- a/adaptive/types.py +++ b/adaptive/types.py @@ -1,17 +1,11 @@ -import sys -from typing import Union +from typing import TypeAlias import numpy as np -if sys.version_info >= (3, 10): - from typing import TypeAlias -else: - from typing_extensions import TypeAlias - -Float: TypeAlias = Union[float, np.float64] -Bool: TypeAlias = Union[bool, np.bool_] -Int: TypeAlias = Union[int, np.int_] -Real: TypeAlias = Union[Float, Int] +Float: TypeAlias = float | np.float64 +Bool: TypeAlias = bool | np.bool_ +Int: TypeAlias = int | np.int_ +Real: TypeAlias = Float | Int __all__ = ["Float", "Bool", "Int", "Real"] diff --git a/adaptive/utils.py b/adaptive/utils.py index ff80f62f1..2a1680cac 100644 --- a/adaptive/utils.py +++ b/adaptive/utils.py @@ -7,11 +7,11 @@ import os import pickle import warnings -from collections.abc import Awaitable, Iterator, Sequence +from collections.abc import Awaitable, Callable, Iterator, Sequence from contextlib import contextmanager from functools import wraps from itertools import product -from typing import TYPE_CHECKING, Any, Callable, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar import cloudpickle diff --git a/docs/environment.yml b/docs/environment.yml index 3e99ae627..caa8badcf 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -4,7 +4,7 @@ channels: - conda-forge dependencies: - - python=3.10 + - python=3.11 - sortedcollections=2.1.0 - scipy=1.10.1 - holoviews=1.18.3 diff --git a/docs/source/algorithms_and_examples.md b/docs/source/algorithms_and_examples.md index eda3c2fff..0aff9f1fa 100644 --- a/docs/source/algorithms_and_examples.md +++ b/docs/source/algorithms_and_examples.md @@ -4,7 +4,7 @@ jupytext: extension: .md format_name: myst format_version: 0.13 - jupytext_version: 1.14.5 + jupytext_version: 1.17.1 kernelspec: display_name: python3 name: python3 diff --git a/docs/source/tutorial/tutorial.LearnerND.md b/docs/source/tutorial/tutorial.LearnerND.md index 46f948708..37705b79d 100644 --- a/docs/source/tutorial/tutorial.LearnerND.md +++ b/docs/source/tutorial/tutorial.LearnerND.md @@ -4,7 +4,7 @@ jupytext: extension: .md format_name: myst format_version: 0.13 - jupytext_version: 1.14.5 + jupytext_version: 1.17.1 kernelspec: display_name: python3 name: python3 diff --git a/environment.yml b/environment.yml index a31560f15..1dfb8c86d 100644 --- a/environment.yml +++ b/environment.yml @@ -4,7 +4,7 @@ channels: - conda-forge dependencies: - - python=3.9 + - python=3.13 - sortedcontainers - sortedcollections - scipy diff --git a/noxfile.py b/noxfile.py index 6a6114bb3..71a2217a4 100644 --- a/noxfile.py +++ b/noxfile.py @@ -6,7 +6,7 @@ nox.options.default_venv_backend = "uv" -python = ["3.9", "3.10", "3.11", "3.12", "3.13"] +python = ["3.11", "3.12", "3.13"] num_cpus = os.cpu_count() or 1 xdist = ("-n", "auto") if num_cpus > 2 else () diff --git a/pyproject.toml b/pyproject.toml index 5e59cad3b..5c7186e98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,15 +8,14 @@ dynamic = ["version"] description = "Parallel active learning of mathematical functions" maintainers = [{ name = "Adaptive authors" }] license = { text = "BSD" } -requires-python = ">=3.9" +requires-python = ">=3.11" classifiers = [ "Development Status :: 4 - Beta", "License :: OSI Approved :: BSD License", "Intended Audience :: Science/Research", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] dependencies = [ "scipy", @@ -24,7 +23,6 @@ dependencies = [ "sortedcontainers >= 2.0", "cloudpickle", "loky >= 2.9", - "typing_extensions; python_version < '3.10'", "versioningit", ] @@ -100,11 +98,11 @@ output = ".coverage.xml" [tool.mypy] ignore_missing_imports = true -python_version = "3.9" +python_version = "3.11" [tool.ruff] line-length = 88 -target-version = "py39" +target-version = "py311" [tool.ruff.lint] select = ["B", "C", "E", "F", "W", "T", "B9", "I", "UP"] @@ -118,6 +116,7 @@ ignore = [ "PLW0603", # Using the global statement to update `X` is discouraged "D401", # First line of docstring should be in imperative mood "E501", # Line too long + "B905", # `zip()` without an explicit `strict=` parameter ] [tool.ruff.lint.mccabe]