From b905296c40968adec76e440ea1386bbb1386af31 Mon Sep 17 00:00:00 2001 From: Abel Abate <61546411+abelaba@users.noreply.github.com> Date: Tue, 29 Sep 2026 09:30:29 +0200 Subject: [PATCH 1/2] Mypy ty migration (#682) --- .github/workflows/main.yml | 8 +- .tools/create_algo_selection_code.py | 32 +-- .tools/update_algo_selection_hook.py | 10 +- docs/source/development/ep-02-typing.md | 7 + docs/source/development/how_to_contribute.md | 2 +- pixi.lock | 203 ++++---------- pyproject.toml | 255 ++++++++---------- src/estimagic/__init__.py | 8 +- src/estimagic/estimation_table.py | 26 +- src/optimagic/algorithms.py | 6 +- .../benchmarking/benchmark_reports.py | 2 +- .../benchmarking/get_benchmark_problems.py | 10 +- .../benchmarking/process_benchmark_results.py | 2 +- src/optimagic/config.py | 3 +- src/optimagic/deprecations.py | 64 +++-- src/optimagic/differentiation/derivatives.py | 6 +- .../differentiation/numdiff_options.py | 4 +- src/optimagic/logging/logger.py | 3 +- src/optimagic/logging/read_log.py | 5 +- src/optimagic/logging/sqlalchemy.py | 21 +- src/optimagic/mark.py | 54 ++-- src/optimagic/optimization/algorithm.py | 17 +- .../create_optimization_problem.py | 5 +- src/optimagic/optimization/error_penalty.py | 2 +- src/optimagic/optimization/history.py | 18 +- .../internal_optimization_problem.py | 7 +- src/optimagic/optimization/multistart.py | 4 +- .../optimization/multistart_options.py | 2 +- .../optimization/optimization_logging.py | 2 +- src/optimagic/optimization/optimize.py | 42 ++- src/optimagic/optimization/optimize_result.py | 6 +- src/optimagic/optimizers/_pounders/gqtpar.py | 4 +- .../optimizers/_pounders/pounders_history.py | 32 ++- src/optimagic/optimizers/iminuit_migrad.py | 4 +- src/optimagic/optimizers/neldermead.py | 2 +- src/optimagic/parameters/bounds.py | 6 +- .../parameters/consolidate_constraints.py | 2 +- .../parameters/constraints/resolution.py | 3 +- src/optimagic/pytree.py | 2 +- src/optimagic/typing.py | 2 +- src/optimagic/visualization/backends.py | 6 +- src/optimagic/visualization/history_plots.py | 70 +++-- src/optimagic/visualization/slice_plot.py | 2 +- src/optimagic/visualization/slice_plot_3d.py | 37 ++- tests/estimagic/test_estimation_table.py | 31 ++- tests/estimagic/test_msm_sensitivity.py | 2 +- tests/estimagic/test_shared.py | 4 +- .../test_compare_derivatives_with_jax.py | 2 +- .../differentiation/test_derivatives.py | 6 +- .../differentiation/test_numdiff_options.py | 10 +- tests/optimagic/logging/test_base.py | 14 +- tests/optimagic/logging/test_sqlalchemy.py | 2 +- .../test_algo_option_validation.py | 8 +- ...test_algo_option_validation_stringified.py | 8 +- .../optimagic/optimization/test_algorithm.py | 6 +- tests/optimagic/optimization/test_callback.py | 7 +- .../optimization/test_convergence_report.py | 8 +- .../optimization/test_error_penalty.py | 4 +- tests/optimagic/optimization/test_history.py | 18 +- .../optimization/test_history_collection.py | 2 + .../test_internal_optimization_problem.py | 4 +- .../optimization/test_jax_derivatives.py | 2 +- .../optimagic/optimization/test_multistart.py | 8 +- .../optimization/test_multistart_options.py | 20 +- tests/optimagic/optimization/test_optimize.py | 7 +- .../optimization/test_with_constraints.py | 21 +- .../optimization/test_with_multistart.py | 13 +- .../test_with_nonlinear_constraints.py | 2 +- .../_pounders/test_pounders_history.py | 14 +- .../optimizers/test_bayesian_optimizer.py | 2 +- .../optimizers/test_gfo_optimizers.py | 2 +- .../optimizers/test_iminuit_migrad.py | 4 +- .../optimizers/test_pyswarms_optimizers.py | 4 +- .../parameters/constraints/test_resolution.py | 8 +- tests/optimagic/parameters/test_bounds.py | 3 + .../parameters/test_nonlinear_constraints.py | 2 +- .../parameters/test_scale_conversion.py | 2 + tests/optimagic/parameters/test_scaling.py | 12 +- .../parameters/test_space_conversion.py | 2 +- tests/optimagic/test_batch_evaluators.py | 6 +- tests/optimagic/test_constraints.py | 3 + tests/optimagic/test_deprecations.py | 38 ++- tests/optimagic/test_pytree.py | 4 +- tests/optimagic/test_timing.py | 2 +- tests/optimagic/test_typing.py | 18 +- .../optimagic/visualization/test_backends.py | 2 +- .../visualization/test_convergence_plot.py | 2 +- .../visualization/test_history_plots.py | 18 +- 88 files changed, 708 insertions(+), 657 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 829965b9e..eb115397e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -131,8 +131,8 @@ jobs: run: >- pixi run -e tests-linux-py314 python -m doctest -v docs/source/how_to/how_to_constraints.md - run-mypy: - name: Run mypy + run-ty: + name: Run ty runs-on: ubuntu-latest steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 @@ -145,6 +145,6 @@ jobs: cache-write: ${{ github.event_name == 'push' && github.ref_name == 'main' }} frozen: true environments: type-checking - - name: Run mypy + - name: Run ty shell: bash -el {0} - run: pixi run -e type-checking mypy + run: pixi run -e type-checking ty --output-format github diff --git a/.tools/create_algo_selection_code.py b/.tools/create_algo_selection_code.py index 17e79989f..1000ca64f 100644 --- a/.tools/create_algo_selection_code.py +++ b/.tools/create_algo_selection_code.py @@ -104,14 +104,14 @@ def _get_all_algorithms(modules: list[ModuleType]) -> dict[str, Type[Algorithm]] def _get_algorithms_in_module(module: ModuleType) -> dict[str, Type[Algorithm]]: """Collect all algorithms in a single module.""" candidate_dict = dict(inspect.getmembers(module, inspect.isclass)) - candidate_dict = { - k: v for k, v in candidate_dict.items() if hasattr(v, "__algo_info__") - } algos = {} for candidate in candidate_dict.values(): - name = candidate.algo_info.name - if issubclass(candidate, Algorithm) and candidate is not Algorithm: - algos[name] = candidate + if ( + issubclass(candidate, Algorithm) + and candidate is not Algorithm + and candidate.__algo_info__ is not None + ): + algos[candidate.__algo_info__.name] = candidate return algos @@ -119,7 +119,7 @@ def _get_algorithms_in_module(module: ModuleType) -> dict[str, Type[Algorithm]]: # Functions to filter algorithms by selectors # ====================================================================================== def _is_gradient_based(algo: Type[Algorithm]) -> bool: - return algo.algo_info.needs_jac # type: ignore + return algo.algo_info.needs_jac def _is_gradient_free(algo: Type[Algorithm]) -> bool: @@ -127,7 +127,7 @@ def _is_gradient_free(algo: Type[Algorithm]) -> bool: def _is_global(algo: Type[Algorithm]) -> bool: - return algo.algo_info.is_global # type: ignore + return algo.algo_info.is_global def _is_local(algo: Type[Algorithm]) -> bool: @@ -135,31 +135,31 @@ def _is_local(algo: Type[Algorithm]) -> bool: def _is_bounded(algo: Type[Algorithm]) -> bool: - return algo.algo_info.supports_bounds # type: ignore + return algo.algo_info.supports_bounds def _is_linear_constrained(algo: Type[Algorithm]) -> bool: - return algo.algo_info.supports_linear_constraints # type: ignore + return algo.algo_info.supports_linear_constraints def _is_nonlinear_constrained(algo: Type[Algorithm]) -> bool: - return algo.algo_info.supports_nonlinear_constraints # type: ignore + return algo.algo_info.supports_nonlinear_constraints def _is_scalar(algo: Type[Algorithm]) -> bool: - return algo.algo_info.solver_type == AggregationLevel.SCALAR # type: ignore + return algo.algo_info.solver_type == AggregationLevel.SCALAR def _is_least_squares(algo: Type[Algorithm]) -> bool: - return algo.algo_info.solver_type == AggregationLevel.LEAST_SQUARES # type: ignore + return algo.algo_info.solver_type == AggregationLevel.LEAST_SQUARES def _is_likelihood(algo: Type[Algorithm]) -> bool: - return algo.algo_info.solver_type == AggregationLevel.LIKELIHOOD # type: ignore + return algo.algo_info.solver_type == AggregationLevel.LIKELIHOOD def _is_parallel(algo: Type[Algorithm]) -> bool: - return algo.algo_info.supports_parallelism # type: ignore + return algo.algo_info.supports_parallelism def _get_filters() -> dict[str, Callable[[Type[Algorithm]], bool]]: @@ -385,7 +385,7 @@ def _all(self) -> list[Type[Algorithm]]: def _available(self) -> list[Type[Algorithm]]: _all = self._all() return [ - a for a in _all if a.algo_info.is_available # type: ignore + a for a in _all if a.algo_info.is_available ] @property diff --git a/.tools/update_algo_selection_hook.py b/.tools/update_algo_selection_hook.py index 91715414d..b5d5f0dbd 100644 --- a/.tools/update_algo_selection_hook.py +++ b/.tools/update_algo_selection_hook.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -import importlib.util import subprocess import sys from pathlib import Path @@ -15,13 +14,14 @@ def run(cmd: list[str], **kwargs: Any) -> None: subprocess.check_call(cmd, cwd=ROOT, **kwargs) -def ensure_optimagic_is_locally_installed() -> None: - if importlib.util.find_spec("optimagic") is None: - run(["uv", "pip", "install", "--python", sys.executable, "-e", "."]) +def install_optimagic_locally() -> None: + # Always (re)install so that dependencies added after the hook environment was + # created are picked up. This is fast if nothing changed. + run(["uv", "pip", "install", "--quiet", "--python", sys.executable, "-e", "."]) def main() -> int: - ensure_optimagic_is_locally_installed() + install_optimagic_locally() run(PYTHON + [".tools/create_algo_selection_code.py"]) ruff_args = [ diff --git a/docs/source/development/ep-02-typing.md b/docs/source/development/ep-02-typing.md index dd9af67e4..0d8c2dcb3 100644 --- a/docs/source/development/ep-02-typing.md +++ b/docs/source/development/ep-02-typing.md @@ -1653,6 +1653,13 @@ access to currently internal objects such as the MSM objective function. We choose mypy as static type checker and run it as part of our continuous integration. +```{note} +We have since replaced mypy by [ty](https://docs.astral.sh/ty/). ty is much faster and +also checks the bodies of unannotated functions and our tests. Its configuration lives +in the `[tool.ty]` section of `pyproject.toml`. The mypy settings below are kept for +reference. +``` + Once this enhancement proposal is fully implemented, we want to use the following settings: diff --git a/docs/source/development/how_to_contribute.md b/docs/source/development/how_to_contribute.md index 91f07dd07..a281e4af5 100644 --- a/docs/source/development/how_to_contribute.md +++ b/docs/source/development/how_to_contribute.md @@ -64,7 +64,7 @@ For regular contributors: **Clone** the [repository](https://github.com/optimagi use: ```console - $ pixi run mypy + $ pixi run ty ``` To see all available pixi tasks, run: diff --git a/pixi.lock b/pixi.lock index 14eb85725..b7711dda6 100644 --- a/pixi.lock +++ b/pixi.lock @@ -9523,7 +9523,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mumps-include-5.8.2-h5a610fb_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mumps-seq-5.8.2-hc1b3267_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/mypy-1.19.1-py314h5bd0f2a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/nlopt-2.10.1-np2py314h6477eea_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/numba-0.64.0-py314h8169c2f_0.conda @@ -9543,7 +9542,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/pygmo-2.19.7-py314h26074fa_7.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyside6-6.10.2-py314hf36963e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.3-h32b2ec7_101_cp314.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-librt-0.8.1-py314h0f05182_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.10.0-cpu_mkl_py314_h6018c46_103.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-cpu-2.10.0-cpu_mkl_hd61e0f4_103.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda @@ -9560,6 +9558,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2022.3.0-hb700be7_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.3-py314h5bd0f2a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ty-0.0.59-h4e94fc0_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-17.0.1-py314h5bd0f2a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.24.0-hd6090a7_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.1-h4f16b4b_2.conda @@ -9664,7 +9663,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mypy_extensions-1.1.0-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.17.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.0-pyhcf101f3_0.conda @@ -9676,7 +9674,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.0.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/patsy-1.0.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.2-pyhcf101f3_0.conda @@ -9745,7 +9742,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/51/fe/53ac0cd932db5dcaf55961bc7cb7afdca8d80d8cc7406ed661f0c7dc111a/pdbp-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/6aa79ba3570bddd1bf7e951c6123f806751e58e8cce736bad77b2cf348d7/logistro-2.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/62/ae/cb215ab25b76228bc90c90444b87e323ffba58c212321a53d5bc92903098/sqlalchemy_stubs-0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7c/2f/f91e4eee21585ff548e83358332d5632ee49f6b2dcd96cb5dca4e0468951/pandas_stubs-3.0.0.260204-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ac/5c/f176b0a5af2a69bd2ffa04cbdd937268373cb639e02196121cfc2f691647/fides-0.7.4.tar.gz @@ -9834,7 +9830,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mypy_extensions-1.1.0-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.17.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.0-pyhcf101f3_0.conda @@ -9847,7 +9842,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.0.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/patsy-1.0.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.2-pyhcf101f3_0.conda @@ -9976,7 +9970,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpfr-4.2.1-hb693164_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mumps-include-5.8.2-h2ca763e_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mumps-seq-5.8.2-h49d0c1d_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mypy-1.19.1-py314hbdd0d06_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nlopt-2.10.1-np2py314hdd732f0_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numba-0.64.0-py314hb38061f_0.conda @@ -9994,7 +9987,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py314h3a4d195_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py314h36abed7_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.14.3-h4c637c5_101_cp314.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-librt-0.8.1-py314ha14b1ff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytorch-2.10.0-cpu_generic_py314_he36690f_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytorch-cpu-2.10.0-cpu_generic_hcc7c195_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py314h6e9b3f0_1.conda @@ -10010,6 +10002,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tbb-2022.3.0-h4ddebb9_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.4-py314h0612a62_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ty-0.0.59-hdfcc030_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/unicodedata2-17.0.1-py314h6c2aa35_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.12-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxdmcp-1.1.5-hc919400_1.conda @@ -10027,7 +10020,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/51/fe/53ac0cd932db5dcaf55961bc7cb7afdca8d80d8cc7406ed661f0c7dc111a/pdbp-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/6aa79ba3570bddd1bf7e951c6123f806751e58e8cce736bad77b2cf348d7/logistro-2.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/62/ae/cb215ab25b76228bc90c90444b87e323ffba58c212321a53d5bc92903098/sqlalchemy_stubs-0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/b7/9366ed44ced9b7ef357ab48c94205280276db9d7f064aa3012a97227e966/h5py-3.16.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/7c/2f/f91e4eee21585ff548e83358332d5632ee49f6b2dcd96cb5dca4e0468951/pandas_stubs-3.0.0.260204-py3-none-any.whl @@ -10121,7 +10113,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mypy_extensions-1.1.0-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.17.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.0-pyhcf101f3_0.conda @@ -10133,7 +10124,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.0.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/patsy-1.0.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda @@ -10262,7 +10252,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/matplotlib-base-3.10.8-py314hfa45d96_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda - conda: https://conda.anaconda.org/conda-forge/win-64/mumps-seq-5.8.2-h607cc0b_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/mypy-1.19.1-py314h5a2d7ad_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/nlopt-2.10.1-np2py314hb7a55bc_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/numba-0.64.0-py314h36f8cf2_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.2-py314h06c3c77_1.conda @@ -10280,7 +10269,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/pygmo-2.19.7-py314h322d9f3_7.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pyside6-6.10.2-py314h2c9462b_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.14.3-h4b44e0e_101_cp314.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/python-librt-0.8.1-py314hc5dbbe4_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pytorch-2.10.0-cpu_mkl_py314_hf6ce55f_103.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pytorch-cpu-2.10.0-cpu_mkl_hf38594e_103.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py314h8f8f202_1.conda @@ -10298,6 +10286,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2022.3.0-h3155e25_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.4-py314h5a2d7ad_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ty-0.0.59-hc21aad4_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/unicodedata2-17.0.1-py314h5a2d7ad_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda @@ -10323,7 +10312,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/6aa79ba3570bddd1bf7e951c6123f806751e58e8cce736bad77b2cf348d7/logistro-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/62/ae/cb215ab25b76228bc90c90444b87e323ffba58c212321a53d5bc92903098/sqlalchemy_stubs-0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/76/18/a8def8f91b18cd1ba6e05dbe02540168cb24d47e8dcf69e8d00b7da42a08/scikit_learn-1.8.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/7c/2f/f91e4eee21585ff548e83358332d5632ee49f6b2dcd96cb5dca4e0468951/pandas_stubs-3.0.0.260204-py3-none-any.whl @@ -13458,25 +13446,6 @@ packages: purls: [] size: 2711532 timestamp: 1771833764229 -- conda: https://conda.anaconda.org/conda-forge/linux-64/mypy-1.19.1-py314h5bd0f2a_0.conda - sha256: 4e607095b92cac2ec6dbb8de348d8e006408291c9c2805926f01e4a30e94edbb - md5: 0490f2b08d179719201fdb9514d67157 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - mypy_extensions >=1.0.0 - - pathspec >=0.9.0 - - psutil >=4.0 - - python >=3.14,<3.15.0a0 - - python-librt >=0.6.2 - - python_abi 3.14.* *_cp314 - - typing_extensions >=4.6.0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/mypy?source=hash-mapping - size: 18632958 - timestamp: 1765795548407 - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 md5: 47e340acb35de30501a76c7c799c41d7 @@ -14625,20 +14594,6 @@ packages: size: 36702440 timestamp: 1770675584356 python_site_packages_path: lib/python3.14/site-packages -- conda: https://conda.anaconda.org/conda-forge/linux-64/python-librt-0.8.1-py314h0f05182_0.conda - sha256: 53a19b531000edd487bfba24a04a341d49088a39a4abe9be858edaff05a1d065 - md5: 9a021120e52c4b44761c30d950e6a8c6 - depends: - - python - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - python_abi 3.14.* *_cp314 - license: MIT - license_family: MIT - purls: - - pkg:pypi/librt?source=hash-mapping - size: 76241 - timestamp: 1771423015190 - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.10.0-cpu_generic_py312_h2e1582b_1.conda sha256: f43e4cb022ad0405d082c7c710221fdf47e4bda366840e557c4a2ebb2cccccf9 md5: e3e0999fb8a15f65e05eea8a2f9c4fcb @@ -15514,6 +15469,25 @@ packages: - pkg:pypi/tornado?source=hash-mapping size: 905436 timestamp: 1765458949518 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ty-0.0.59-h4e94fc0_0.conda + noarch: python + sha256: 7cf135185dee0b985520985d5e626d04a9c8d085a041418104b7ccf646dbd9f7 + md5: c2ba304bba1fbf79906b3312677edb5d + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - _python_abi3_support 1.* + - cpython >=3.10 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ty?source=hash-mapping + run_exports: {} + size: 10303828 + timestamp: 1783908696596 - conda: https://conda.anaconda.org/conda-forge/linux-64/ucc-1.4.4-h2b97398_0.conda sha256: 768309ff1a98171bbb5424b0d6e6365f7e01f31428dfd4527c1c97d875dfadc1 md5: bd93aa12125acee057b56d590ee6f65c @@ -17441,17 +17415,6 @@ packages: - pkg:pypi/munkres?source=hash-mapping size: 15851 timestamp: 1749895533014 -- conda: https://conda.anaconda.org/conda-forge/noarch/mypy_extensions-1.1.0-pyha770c72_0.conda - sha256: 6ed158e4e5dd8f6a10ad9e525631e35cee8557718f83de7a4e3966b1f772c4b1 - md5: e9c622e0d00fa24a6292279af3ab6d06 - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/mypy-extensions?source=hash-mapping - size: 11766 - timestamp: 1745776666688 - conda: https://conda.anaconda.org/conda-forge/noarch/myst-nb-1.4.0-pyhcf101f3_0.conda sha256: c81d0c8c74c3da66808f8da09d8e48f2af2d173d357d45239defaf466838edba md5: da07c7b1588ad0a44118d28aeb31b6a6 @@ -17683,17 +17646,6 @@ packages: - pkg:pypi/parso?source=hash-mapping size: 82287 timestamp: 1770676243987 -- conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.0.4-pyhd8ed1ab_0.conda - sha256: 29ea20d0faf20374fcd61c25f6d32fb8e9a2c786a7f1473a0c3ead359470fbe1 - md5: 2908273ac396d2cd210a8127f5f1c0d6 - depends: - - python >=3.10 - license: MPL-2.0 - license_family: MOZILLA - purls: - - pkg:pypi/pathspec?source=hash-mapping - size: 53739 - timestamp: 1769677743677 - conda: https://conda.anaconda.org/conda-forge/noarch/patsy-1.0.2-pyhcf101f3_0.conda sha256: 9678f4745e6b82b36fab9657a19665081862268cb079cf9acf878ab2c4fadee9 md5: 8678577a52161cc4e1c93fcc18e8a646 @@ -20452,25 +20404,6 @@ packages: purls: [] size: 2710681 timestamp: 1771833866358 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/mypy-1.19.1-py314hbdd0d06_0.conda - sha256: c5c9a691dc00ce9a726426f971fbe21d0501ec8c6228513b945210898f26c761 - md5: 584f58048dc4af70f6c647b40a7049a6 - depends: - - __osx >=11.0 - - mypy_extensions >=1.0.0 - - pathspec >=0.9.0 - - psutil >=4.0 - - python >=3.14,<3.15.0a0 - - python >=3.14,<3.15.0a0 *_cp314 - - python-librt >=0.6.2 - - python_abi 3.14.* *_cp314 - - typing_extensions >=4.6.0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/mypy?source=hash-mapping - size: 11320681 - timestamp: 1765795843941 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda sha256: 2827ada40e8d9ca69a153a45f7fd14f32b2ead7045d3bbb5d10964898fe65733 md5: 068d497125e4bf8a66bf707254fff5ae @@ -21317,20 +21250,6 @@ packages: size: 13522698 timestamp: 1770675365241 python_site_packages_path: lib/python3.14/site-packages -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-librt-0.8.1-py314ha14b1ff_0.conda - sha256: 10667ff7dec7aa0a4c1e7cc0d0fb16c168e4128ffeba57fdd247810f1a71d247 - md5: 8c322be8d5466a35f48d5559def4c225 - depends: - - python - - python 3.14.* *_cp314 - - __osx >=11.0 - - python_abi 3.14.* *_cp314 - license: MIT - license_family: MIT - purls: - - pkg:pypi/librt?source=hash-mapping - size: 72731 - timestamp: 1771423079377 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytorch-2.10.0-cpu_generic_py312_h2470ad0_3.conda sha256: 6da6392a6d89f043c914c658cdaf98c49e6860ab7e91a0afcb8463723bca4364 md5: b4c7ecd785628fbd767d9cb854ec2c0c @@ -21831,6 +21750,24 @@ packages: - pkg:pypi/tornado?source=hash-mapping size: 909298 timestamp: 1765836779269 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ty-0.0.59-hdfcc030_0.conda + noarch: python + sha256: 4026d41475ebf53f3810913e80618cecd4459b4d497839ba044ad5a2f89183da + md5: 5e6c3112ca11b9c047a60a2b17f3bf1c + depends: + - python + - __osx >=11.0 + - _python_abi3_support 1.* + - cpython >=3.10 + constrains: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ty?source=hash-mapping + run_exports: {} + size: 9330666 + timestamp: 1783908711033 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/unicodedata2-17.0.1-py312h2bbb03f_0.conda sha256: e935d0c11581e31e89ce4899a28b16f924d1a3c1af89f18f8a2c5f5728b3107f md5: 45b836f333fd3e282c16fff7dc82994e @@ -23545,26 +23482,6 @@ packages: purls: [] size: 6225640 timestamp: 1771833767934 -- conda: https://conda.anaconda.org/conda-forge/win-64/mypy-1.19.1-py314h5a2d7ad_0.conda - sha256: 59c5f9046ad3ab9a449dee682392799d9589c12e470f0c6fed7f2aaa9b8e8ca2 - md5: 4ca21331a3962c4fcf658d6d47df4c0b - depends: - - mypy_extensions >=1.0.0 - - pathspec >=0.9.0 - - psutil >=4.0 - - python >=3.14,<3.15.0a0 - - python-librt >=0.6.2 - - python_abi 3.14.* *_cp314 - - typing_extensions >=4.6.0 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: MIT - license_family: MIT - purls: - - pkg:pypi/mypy?source=hash-mapping - size: 9114684 - timestamp: 1765795714379 - conda: https://conda.anaconda.org/conda-forge/win-64/nlopt-2.10.1-np2py312ha76dc74_0.conda sha256: 3499df0c1c06b1592d286d1f745151173c5d1b1dcf2f751f52c30c1a928a5f64 md5: b26775517ac366a0fd41ecf8bb048d7f @@ -24407,21 +24324,6 @@ packages: size: 18273230 timestamp: 1770675442998 python_site_packages_path: Lib/site-packages -- conda: https://conda.anaconda.org/conda-forge/win-64/python-librt-0.8.1-py314hc5dbbe4_0.conda - sha256: 25d152c9354a8a95eca1f9754cfe4e3bf8f85dec6229c692d7bc8bcd1613f02f - md5: 29edadadd2768a75eff4788fd105cc46 - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.14.* *_cp314 - license: MIT - license_family: MIT - purls: - - pkg:pypi/librt?source=hash-mapping - size: 50410 - timestamp: 1771423050838 - conda: https://conda.anaconda.org/conda-forge/win-64/pytorch-2.10.0-cpu_mkl_py312_h9991af1_103.conda sha256: 76be0f78976ae08c75a7e581bbc91e06b5bd84bb76e846e5fad840036a5a3134 md5: 4c42b5a7c6d466fc3f53f8113f620d51 @@ -25056,6 +24958,24 @@ packages: - pkg:pypi/tornado?source=hash-mapping size: 908399 timestamp: 1765836848636 +- conda: https://conda.anaconda.org/conda-forge/win-64/ty-0.0.59-hc21aad4_0.conda + noarch: python + sha256: 83ecfac9ac09d73f3325714f67551e4b32af6c1840f15b056204b13c24ede597 + md5: d35c871f7af02f796e3ec32d9c1b3451 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - _python_abi3_support 1.* + - cpython >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ty?source=hash-mapping + run_exports: {} + size: 10435580 + timestamp: 1783908745042 - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda sha256: 3005729dce6f3d3f5ec91dfc49fc75a0095f9cd23bab49efb899657297ac91a5 md5: 71b24316859acd00bdb8b38f5e2ce328 @@ -25855,13 +25775,6 @@ packages: - pytest ; extra == 'dev' - twine ; extra == 'dev' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/62/ae/cb215ab25b76228bc90c90444b87e323ffba58c212321a53d5bc92903098/sqlalchemy_stubs-0.4-py3-none-any.whl - name: sqlalchemy-stubs - version: '0.4' - sha256: 5eec7aa110adf9b957b631799a72fef396b23ff99fe296df726645d01e312aa5 - requires_dist: - - mypy>=0.790 - - typing-extensions>=3.7.4 - pypi: https://files.pythonhosted.org/packages/65/44/bb509c3d2c0b5a87e7a5af1d5917a402a32ff026f777a6d7cb6990746cbb/tabcompleter-1.4.0-py3-none-any.whl name: tabcompleter version: 1.4.0 diff --git a/pyproject.toml b/pyproject.toml index b23b5fcca..234170da6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -124,6 +124,8 @@ select = [ "ISC", # pydocstyle "D", + # flake8-annotations: require type annotations on all function signatures + "ANN", ] extend-ignore = [ @@ -163,17 +165,111 @@ extend-ignore = [ "B028", # Incompatible with formatting "ISC001", + # Dynamically typed expressions (typing.Any) are disallowed + "ANN401", ] [tool.ruff.lint.per-file-ignores] "docs/source/conf.py" = ["E501", "ERA001", "DTZ005"] -"src/optimagic/parameters/kernel_transformations.py" = ["ARG001", "N806"] +"src/optimagic/parameters/kernel_transformations.py" = ["ARG001", "N806", "ANN"] "docs/source/*" = ["B018"] "src/optimagic/algorithms.py" = ["E501"] +"docs/*" = ["ANN"] +"tests/*" = ["ANN"] +"src/optimagic/benchmarking/benchmark_reports.py" = ["ANN"] +"src/optimagic/benchmarking/cartis_roberts.py" = ["ANN"] +"src/optimagic/benchmarking/get_benchmark_problems.py" = ["ANN"] +"src/optimagic/benchmarking/more_wild.py" = ["ANN"] +"src/optimagic/benchmarking/noise_distributions.py" = ["ANN"] +"src/optimagic/benchmarking/process_benchmark_results.py" = ["ANN"] +"src/optimagic/benchmarking/run_benchmark.py" = ["ANN"] + +"src/optimagic/differentiation/derivatives.py" = ["ANN"] +"src/optimagic/differentiation/finite_differences.py" = ["ANN"] +"src/optimagic/differentiation/generate_steps.py" = ["ANN"] +"src/optimagic/differentiation/richardson_extrapolation.py" = ["ANN"] + +"src/optimagic/examples/numdiff_functions.py" = ["ANN"] + +"src/optimagic/optimization/algo_options.py" = ["ANN"] +"src/optimagic/optimization/optimize_result.py" = ["ANN"] +"src/optimagic/optimization/multistart.py" = ["ANN"] +"src/optimagic/optimization/scipy_aliases.py" = ["ANN"] +"src/optimagic/optimization/create_optimization_problem.py" = ["ANN"] + +"src/optimagic/optimizers/_pounders/pounders_auxiliary.py" = ["ANN"] +"src/optimagic/optimizers/_pounders/pounders_history.py" = ["ANN"] +"src/optimagic/optimizers/_pounders/_conjugate_gradient.py" = ["ANN"] +"src/optimagic/optimizers/_pounders/_steihaug_toint.py" = ["ANN"] +"src/optimagic/optimizers/_pounders/_trsbox.py" = ["ANN"] +"src/optimagic/optimizers/_pounders/bntr.py" = ["ANN"] +"src/optimagic/optimizers/_pounders/gqtpar.py" = ["ANN"] +"src/optimagic/optimizers/_pounders/linear_subsolvers.py" = ["ANN"] + +"src/optimagic/optimizers/pygmo_optimizers.py" = ["ANN"] +"src/optimagic/optimizers/scipy_optimizers.py" = ["ANN"] +"src/optimagic/optimizers/nag_optimizers.py" = ["ANN"] +"src/optimagic/optimizers/neldermead.py" = ["ANN"] +"src/optimagic/optimizers/nlopt_optimizers.py" = ["ANN"] +"src/optimagic/optimizers/ipopt.py" = ["ANN"] +"src/optimagic/optimizers/fides.py" = ["ANN"] +"src/optimagic/optimizers/pounders.py" = ["ANN"] +"src/optimagic/optimizers/tao_optimizers.py" = ["ANN"] + + +"src/optimagic/parameters/block_trees.py" = ["ANN"] +"src/optimagic/parameters/check_constraints.py" = ["ANN"] +"src/optimagic/parameters/consolidate_constraints.py" = ["ANN"] +"src/optimagic/parameters/constraint_tools.py" = ["ANN"] +"src/optimagic/parameters/conversion.py" = ["ANN"] +"src/optimagic/parameters/nonlinear_constraints.py" = ["ANN"] +"src/optimagic/parameters/process_constraints.py" = ["ANN"] +"src/optimagic/parameters/space_conversion.py" = ["ANN"] +"src/optimagic/parameters/tree_conversion.py" = ["ANN"] + + +"src/optimagic/shared/check_option_dicts.py" = ["ANN"] +"src/optimagic/shared/compat.py" = ["ANN"] +"src/optimagic/shared/process_user_function.py" = ["ANN"] + +"src/optimagic/visualization/convergence_plot.py" = ["ANN"] +"src/optimagic/visualization/deviation_plot.py" = ["ANN"] +"src/optimagic/visualization/history_plots.py" = ["ANN"] +"src/optimagic/visualization/plotting_utilities.py" = ["ANN"] +"src/optimagic/visualization/profile_plot.py" = ["ANN"] +"src/optimagic/visualization/slice_plot.py" = ["ANN"] + +"src/optimagic/decorators.py" = ["ANN"] +"src/optimagic/exceptions.py" = ["ANN"] +"src/optimagic/utilities.py" = ["ANN"] +"src/optimagic/pytree.py" = ["ANN"] +"src/optimagic/deprecations.py" = ["ANN"] + +"src/estimagic/__init__.py" = ["ANN"] +"src/estimagic/examples/logit.py" = ["ANN"] +"src/estimagic/estimate_ml.py" = ["ANN"] +"src/estimagic/estimate_msm.py" = ["ANN"] +"src/estimagic/msm_weighting.py" = ["ANN"] +"src/estimagic/bootstrap_ci.py" = ["ANN"] +"src/estimagic/bootstrap_helpers.py" = ["ANN"] +"src/estimagic/bootstrap_outcomes.py" = ["ANN"] +"src/estimagic/bootstrap_samples.py" = ["ANN"] +"src/estimagic/bootstrap.py" = ["ANN"] +"src/estimagic/ml_covs.py" = ["ANN"] +"src/estimagic/msm_covs.py" = ["ANN"] +"src/estimagic/shared_covs.py" = ["ANN"] +"src/estimagic/msm_sensitivity.py" = ["ANN"] +"src/estimagic/estimation_table.py" = ["ANN"] +"src/estimagic/lollipop_plot.py" = ["ANN"] +"src/optimagic/visualization/slice_plot_3d.py" = ["ANN"] [tool.ruff.lint.pydocstyle] convention = "google" +[tool.ruff.lint.flake8-annotations] +# Like mypy, allow omitting `-> None` on `__init__` if an argument is annotated. +mypy-init-return = true + # ====================================================================================== # Pytest configuration @@ -220,137 +316,19 @@ none_representation = "null" # ====================================================================================== -# Mypy configuration +# Ty configuration # ====================================================================================== -[tool.mypy] -files = ["src", "tests", ".tools"] -check_untyped_defs = true -disallow_any_generics = true -disallow_untyped_defs = true -disallow_incomplete_defs = true -no_implicit_optional = true -warn_redundant_casts = true -warn_unused_ignores = true - -[[tool.mypy.overrides]] -module = [ - "optimagic.benchmarking", - "optimagic.benchmarking.benchmark_reports", - "optimagic.benchmarking.cartis_roberts", - "optimagic.benchmarking.get_benchmark_problems", - "optimagic.benchmarking.more_wild", - "optimagic.benchmarking.noise_distributions", - "optimagic.benchmarking.process_benchmark_results", - "optimagic.benchmarking.run_benchmark", - - "optimagic.differentiation", - "optimagic.differentiation.derivatives", - "optimagic.differentiation.finite_differences", - "optimagic.differentiation.generate_steps", - "optimagic.differentiation.richardson_extrapolation", - - "optimagic.examples", - "optimagic.examples.numdiff_functions", - - "optimagic.optimization", - "optimagic.optimization.algo_options", - "optimagic.optimization.convergence_report", - "optimagic.optimization.optimization_logging", - "optimagic.optimization.optimize_result", - "optimagic.optimization.optimize", - "optimagic.optimization.multistart", - "optimagic.optimization.scipy_aliases", - "optimagic.optimization.create_optimization_problem", - - "optimagic.optimizers._pounders", - "optimagic.optimizers._pounders.pounders_auxiliary", - "optimagic.optimizers._pounders.pounders_history", - "optimagic.optimizers._pounders._conjugate_gradient", - "optimagic.optimizers._pounders._steihaug_toint", - "optimagic.optimizers._pounders._trsbox", - "optimagic.optimizers._pounders.bntr", - "optimagic.optimizers._pounders.gqtpar", - "optimagic.optimizers._pounders.linear_subsolvers", - - "optimagic.optimizers", - "optimagic.optimizers.tranquilo", - "optimagic.optimizers.pygmo_optimizers", - "optimagic.optimizers.scipy_optimizers", - "optimagic.optimizers.nag_optimizers", - "optimagic.optimizers.neldermead", - "optimagic.optimizers.nlopt_optimizers", - "optimagic.optimizers.ipopt", - "optimagic.optimizers.fides", - "optimagic.optimizers.pounders", - "optimagic.optimizers.tao_optimizers", - - - "optimagic.parameters", - "optimagic.parameters.block_trees", - "optimagic.parameters.check_constraints", - "optimagic.parameters.consolidate_constraints", - "optimagic.parameters.constraint_tools", - "optimagic.parameters.conversion", - "optimagic.parameters.kernel_transformations", - "optimagic.parameters.nonlinear_constraints", - "optimagic.parameters.process_constraints", - "optimagic.parameters.space_conversion", - "optimagic.parameters.tree_conversion", - - - "optimagic.shared", - "optimagic.shared.check_option_dicts", - "optimagic.shared.compat", - "optimagic.shared.process_user_function", - - "optimagic.visualization", - "optimagic.visualization.convergence_plot", - "optimagic.visualization.backends", - "optimagic.visualization.deviation_plot", - "optimagic.visualization.history_plots", - "optimagic.visualization.plotting_utilities", - "optimagic.visualization.profile_plot", - "optimagic.visualization.slice_plot", - - "optimagic", - "optimagic.decorators", - "optimagic.exceptions", - "optimagic.utilities", - "optimagic.pytree", - "optimagic.deprecations", - - "estimagic", - "estimagic.examples", - "estimagic.examples.logit", - "estimagic.estimate_ml", - "estimagic.estimate_msm", - "estimagic.estimation_summaries", - "estimagic.msm_weighting", - "estimagic.bootstrap_ci", - "estimagic.bootstrap_helpers", - "estimagic.bootstrap_outcomes", - "estimagic.bootstrap_samples", - "estimagic.bootstrap", - "estimagic.ml_covs", - "estimagic.msm_covs", - "estimagic.shared_covs", - "estimagic.msm_sensitivity", - "estimagic.estimation_table", - "estimagic.lollipop_plot", - +[tool.ty] +src.include = ["src", ".tools", "tests"] + +[tool.ty.analysis] +allowed-unresolved-imports = [ + "petsc4py.**", + "jax.**", + "pathos.**", + "nevergrad.**", ] -check_untyped_defs = false -disallow_any_generics = false -disallow_untyped_defs = false - - -[[tool.mypy.overrides]] -module = "tests.*" -disallow_untyped_defs = false -ignore_errors = true - -[[tool.mypy.overrides]] -module = [ +replace-imports-with-any = [ "scipy", "scipy.linalg", "scipy.linalg.lapack", @@ -379,8 +357,6 @@ module = [ "bokeh.command.util", "fides", "fides.hessian_approximation", - "petsc4py", - "petsc4py.PETSc", "tranquilo", "tranquilo.tranquilo", "tranquilo.options", @@ -388,18 +364,13 @@ module = [ "dfols", "pybobyqa", "pygmo", - "jax", "joblib", "cloudpickle", "numba", - "pathos", - "pathos.pools", "optimagic._version", "annotated_types", "pdbp", "iminuit", - "nevergrad", - "nevergrad.optimization.base", "pygad", "pyswarms", "pyswarms.backend.topology", @@ -407,7 +378,6 @@ module = [ "gradient_free_optimizers", "gradient_free_optimizers.optimizers.base_optimizer", ] -ignore_missing_imports = true # ====================================================================================== @@ -473,19 +443,20 @@ tests = { cmd = "pytest", description = "Run the full test suite" } tests-fast = { cmd = "pytest -m 'not slow and not jax'", description = "Run tests excluding slow and jax tests" } tests-with-cov = { cmd = "pytest --cov-report=xml --cov=src", description = "Run tests with XML coverage report" } -# --- Feature: type-checking (mypy + type stubs) -------------------------------------- +# --- Feature: type-checking (ty + type stubs) -------------------------------------- [tool.pixi.feature.type-checking.dependencies] -mypy = "==1.19.1" +ty = ">=0.0.59,<0.0.60" [tool.pixi.feature.type-checking.pypi-dependencies] pandas-stubs = "*" types-cffi = "*" types-openpyxl = "*" types-jinja2 = "*" -sqlalchemy-stubs = "*" [tool.pixi.feature.type-checking.tasks] -mypy = { cmd = "mypy", description = "Run mypy type checker" } +ty = { cmd = "ty check", description = "Run ty type checker" } +ty-concise = { cmd = "ty check --output-format concise", description = "Run ty type checker with concise output" } +ty-fix = { cmd = "ty check --fix", description = "Run ty type checker and apply safe fixes" } # --- Feature: linux (Linux-only deps) ------------------------------------------------ [tool.pixi.feature.linux] diff --git a/src/estimagic/__init__.py b/src/estimagic/__init__.py index 44a640486..e9dc5f361 100644 --- a/src/estimagic/__init__.py +++ b/src/estimagic/__init__.py @@ -32,6 +32,8 @@ from optimagic import slice_plot as _slice_plot from optimagic import traceback_report as _traceback_report from optimagic.decorators import deprecated +from optimagic.logging import SQLiteLogOptions as _SQLiteLogOptions +from optimagic.logging.logger import LogReader as _LogReader MSG = ( "estimagic.{name} has been deprecated in version 0.5.0. Use optimagic.{name} " @@ -61,14 +63,16 @@ class OptimizeLogReader(_OptimizeLogReader): - def __init__(self, path): + # The parent class returns a SQLiteLogReader from __new__, so __init__ of this + # class would never run. Hence, the warning needs to be raised in __new__. + def __new__(cls, *args, **kwargs) -> _LogReader[_SQLiteLogOptions]: warnings.warn( "estimagic.OptimizeLogReader has been deprecated in version 0.5.0. Use " "optimagic.OptimizeLogReader instead. This class will be removed in version" " 0.6.0.", FutureWarning, ) - super().__init__(path) + return super().__new__(cls, *args, **kwargs) @dataclass diff --git a/src/estimagic/estimation_table.py b/src/estimagic/estimation_table.py index 731d925ac..fc8f94275 100644 --- a/src/estimagic/estimation_table.py +++ b/src/estimagic/estimation_table.py @@ -1,6 +1,7 @@ import re +import warnings from copy import deepcopy -from functools import partial +from functools import partial, wraps from pathlib import Path from warnings import warn @@ -9,8 +10,17 @@ from optimagic.shared.compat import pd_df_map -suppress_performance_warnings = np.testing.suppress_warnings() -suppress_performance_warnings.filter(category=pd.errors.PerformanceWarning) + +def suppress_performance_warnings(func): + """Suppress pandas PerformanceWarnings raised while calling func.""" + + @wraps(func) + def wrapper(*args, **kwargs): + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=pd.errors.PerformanceWarning) + return func(*args, **kwargs) + + return wrapper @suppress_performance_warnings @@ -227,7 +237,7 @@ def estimation_table( if return_type.suffix not in (".html", ".tex"): return out else: - return_type.write_text(out) + return_type.write_text(out) # ty:ignore[invalid-argument-type] @suppress_performance_warnings @@ -952,7 +962,7 @@ def _customize_col_groups(default_col_groups, custom_col_groups): else: raise TypeError( f"""Invalid type for custom_col_groups. Can be either list - or dictionary, or NoneType. Not: {type(col_groups)}.""" + or dictionary, or NoneType. Not: {type(custom_col_groups)}.""" ) else: col_groups = default_col_groups @@ -988,8 +998,8 @@ def _customize_col_names(default_col_names, custom_col_names): col_names = custom_col_names else: raise TypeError( - f"""Invalid type for custom_col_names. - Can be either list or dictionary, or NoneType. Not: {col_names}.""" + f"""Invalid type for custom_col_names. Can be either list or + dictionary, or NoneType. Not: {type(custom_col_names)}.""" ) return col_names @@ -1180,7 +1190,7 @@ def _create_statistics_sr( stat_ind = np.concatenate( [stat_sr.index.values.reshape(len(stat_sr), 1), stat_ind], axis=1 ).T - stat_sr.index = pd.MultiIndex.from_arrays(stat_ind) + stat_sr.index = pd.MultiIndex.from_arrays(stat_ind) # ty:ignore[invalid-argument-type] return stat_sr.astype("str").replace("nan", "") diff --git a/src/optimagic/algorithms.py b/src/optimagic/algorithms.py index ed68c9e34..139476489 100644 --- a/src/optimagic/algorithms.py +++ b/src/optimagic/algorithms.py @@ -124,11 +124,7 @@ def _all(self) -> list[Type[Algorithm]]: def _available(self) -> list[Type[Algorithm]]: _all = self._all() - return [ - a - for a in _all - if a.algo_info.is_available # type: ignore - ] + return [a for a in _all if a.algo_info.is_available] @property def All(self) -> list[Type[Algorithm]]: diff --git a/src/optimagic/benchmarking/benchmark_reports.py b/src/optimagic/benchmarking/benchmark_reports.py index 2698aecc5..003761ba9 100644 --- a/src/optimagic/benchmarking/benchmark_reports.py +++ b/src/optimagic/benchmarking/benchmark_reports.py @@ -180,7 +180,7 @@ def traceback_report(problems, results, return_type="dataframe"): tracebacks[algorithm_name] = tracebacks.setdefault(algorithm_name, {}) tracebacks[algorithm_name][problem_name] = result["solution"] - report = pd.DataFrame.from_dict(tracebacks, orient="index").stack().to_frame() + report = pd.DataFrame.from_dict(tracebacks, orient="index").stack().to_frame() # ty:ignore[call-non-callable] report.index.set_names(["algorithm", "problem"], inplace=True) report.columns = ["traceback"] report["dimensionality"] = 0 diff --git a/src/optimagic/benchmarking/get_benchmark_problems.py b/src/optimagic/benchmarking/get_benchmark_problems.py index 340599e43..2986891ed 100644 --- a/src/optimagic/benchmarking/get_benchmark_problems.py +++ b/src/optimagic/benchmarking/get_benchmark_problems.py @@ -1,4 +1,5 @@ from functools import partial, wraps +from typing import Any import numpy as np @@ -197,7 +198,7 @@ def _get_raw_problems(name): problem = v.copy() raw_func = problem["fun"] - problem["fun"] = wraps(raw_func)(partial(_step_func, raw_func=raw_func)) + problem["fun"] = wraps(raw_func)(partial(_step_func, raw_func=raw_func)) # ty:ignore[invalid-argument-type, invalid-assignment] raw_problems[f"{k}_with_steps"] = problem for k, v in CARTIS_ROBERTS_PROBLEMS.items(): @@ -328,7 +329,12 @@ def _sample_from_distribution(distribution, mean, std, size, rng, correlation=0) def _process_noise_options(options, is_multiplicative): options = {} if options is None else options - defaults = {"std": 0.01, "distribution": "normal", "correlation": 0, "mean": 0} + defaults: dict[str, Any] = { + "std": 0.01, + "distribution": "normal", + "correlation": 0, + "mean": 0, + } if is_multiplicative: defaults["clipping_value"] = 1 diff --git a/src/optimagic/benchmarking/process_benchmark_results.py b/src/optimagic/benchmarking/process_benchmark_results.py index df443f7b0..e7d493400 100644 --- a/src/optimagic/benchmarking/process_benchmark_results.py +++ b/src/optimagic/benchmarking/process_benchmark_results.py @@ -67,7 +67,7 @@ def process_benchmark_results( histories = pd.concat(histories, ignore_index=True) infos = pd.DataFrame(infos).set_index(["problem", "algorithm"]).unstack() - infos.columns = [tup[1] for tup in infos.columns] + infos.columns = [tup[1] for tup in infos.columns] # ty:ignore[invalid-assignment] return histories, infos diff --git a/src/optimagic/config.py b/src/optimagic/config.py index 859ef3803..4eced1e4b 100644 --- a/src/optimagic/config.py +++ b/src/optimagic/config.py @@ -1,3 +1,4 @@ +import importlib.metadata import importlib.util from pathlib import Path @@ -59,7 +60,7 @@ def _is_installed(module_name: str) -> bool: # so if nevergrad is installed, bayes_opt will not work and vice-versa. IS_BAYESOPT_INSTALLED_AND_VERSION_NEWER_THAN_2 = ( _is_installed("bayes_opt") - and importlib.metadata.version("bayesian_optimization") > "2.0.0" + and int(importlib.metadata.version("bayesian_optimization").split(".")[0]) >= 2 ) IS_GRADIENT_FREE_OPTIMIZERS_INSTALLED = _is_installed("gradient_free_optimizers") IS_PYGAD_INSTALLED = _is_installed("pygad") diff --git a/src/optimagic/deprecations.py b/src/optimagic/deprecations.py index 12e34b766..81819395f 100644 --- a/src/optimagic/deprecations.py +++ b/src/optimagic/deprecations.py @@ -1,9 +1,10 @@ import logging import warnings +from collections.abc import Sequence from dataclasses import dataclass, replace from functools import wraps from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable, ParamSpec, cast +from typing import TYPE_CHECKING, Any, Callable, ParamSpec import numpy as np import pandas as pd @@ -384,7 +385,10 @@ def throw_key_warning_in_derivatives(): def throw_dict_constraints_future_warning_if_required( - constraints: list[dict[str, Any]] | dict[str, Any], + constraints: Constraint + | dict[str, Any] + | Sequence[Constraint | dict[str, Any]] + | None, ) -> None: replacements = { "fixed": "optimagic.FixedConstraint", @@ -399,12 +403,13 @@ def throw_dict_constraints_future_warning_if_required( "nonlinear": "optimagic.NonlinearConstraint", } - if not isinstance(constraints, list): - constraints = [constraints] + candidates: list[Any] = ( + list(constraints) if isinstance(constraints, Sequence) else [constraints] + ) types_or_none = [ constraint.get("type", None) if isinstance(constraint, dict) else None - for constraint in constraints + for constraint in candidates ] types = [t for t in types_or_none if t is not None] @@ -563,7 +568,7 @@ def handle_log_options_throw_deprecated_warning( log_options = { k: v for k, v in log_options.items() if k != "if_table_exists" } - return SQLiteLogOptions(cast(str | Path, logger), **log_options) + return SQLiteLogOptions(logger, **log_options) elif not log_options_is_compatible: raise ValueError( f"Found string or path for logger argument, but parameter" @@ -575,7 +580,10 @@ def handle_log_options_throw_deprecated_warning( def pre_process_constraints( - constraints: list[Constraint | dict[str, Any]] | Constraint | dict[str, Any] | None, + constraints: Constraint + | dict[str, Any] + | Sequence[Constraint | dict[str, Any]] + | None, ) -> list[Constraint]: """Convert all ways of specifying constraints to a list of Constraint objects. @@ -589,27 +597,12 @@ def pre_process_constraints( if constraints is None: return [] + # The types of the elements are validated below. + candidates: list[Any] if isinstance(constraints, dict | Constraint): - constraints = [constraints] - - if isinstance(constraints, list): - out = [] - invalid_types: list[type] = [] - for constr in constraints: - if isinstance(constr, Constraint): - out.append(constr) - elif isinstance(constr, dict): - out.append(_constraint_from_dict(constr)) - else: - invalid_types.append(type(constr)) - - if invalid_types: - msg = ( - f"Invalid constraint types: {set(invalid_types)}. Must be a constraint " - "object imported from `optimagic`." - ) - raise InvalidConstraintError(msg) - + candidates = [constraints] + elif isinstance(constraints, Sequence) and not isinstance(constraints, str): + candidates = list(constraints) else: msg = ( f"Invalid constraint type: {type(constraints)}. Must be a constraint " @@ -619,6 +612,23 @@ def pre_process_constraints( ) raise InvalidConstraintError(msg) + out = [] + invalid_types: list[type] = [] + for constr in candidates: + if isinstance(constr, Constraint): + out.append(constr) + elif isinstance(constr, dict): + out.append(_constraint_from_dict(constr)) + else: + invalid_types.append(type(constr)) + + if invalid_types: + msg = ( + f"Invalid constraint types: {set(invalid_types)}. Must be a constraint " + "object imported from `optimagic`." + ) + raise InvalidConstraintError(msg) + return out diff --git a/src/optimagic/differentiation/derivatives.py b/src/optimagic/differentiation/derivatives.py index 537da2715..7f12b5a70 100644 --- a/src/optimagic/differentiation/derivatives.py +++ b/src/optimagic/differentiation/derivatives.py @@ -389,7 +389,7 @@ def first_derivative( step_size, evals, updated_candidates, target="first_derivative" ) result = {**result, **info} - return NumdiffResult(**result) + return NumdiffResult(**result) # ty:ignore[invalid-argument-type] def second_derivative( @@ -597,7 +597,7 @@ def second_derivative( step_size = cast(NDArray[np.float64], step_size) # generate parameter vectors at which func has to be evaluated as numpy arrays - evaluation_points = { # type: ignore + evaluation_points = { "one_step": [], "two_step": [], "cross_step": [], @@ -728,7 +728,7 @@ def second_derivative( step_size, evals, updated_candidates, target="second_derivative" ) result = {**result, **info} - return NumdiffResult(**result) + return NumdiffResult(**result) # ty:ignore[invalid-argument-type] def _is_1d_array(candidate: Any) -> bool: diff --git a/src/optimagic/differentiation/numdiff_options.py b/src/optimagic/differentiation/numdiff_options.py index d6c2ff3c2..4ad1a5348 100644 --- a/src/optimagic/differentiation/numdiff_options.py +++ b/src/optimagic/differentiation/numdiff_options.py @@ -39,7 +39,7 @@ class NumdiffOptions: scaling_factor: float = 1 min_steps: float | None = None n_cores: int = DEFAULT_N_CORES - batch_evaluator: BatchEvaluatorLiteral | Callable = "joblib" # type: ignore + batch_evaluator: BatchEvaluatorLiteral | Callable = "joblib" def __post_init__(self) -> None: _validate_attribute_types_and_values(self) @@ -53,7 +53,7 @@ class NumdiffOptionsDict(TypedDict): scaling_factor: NotRequired[float] min_steps: NotRequired[float | None] n_cores: NotRequired[int] - batch_evaluator: NotRequired[BatchEvaluatorLiteral | Callable] # type: ignore + batch_evaluator: NotRequired[BatchEvaluatorLiteral | Callable] def pre_process_numdiff_options( diff --git a/src/optimagic/logging/logger.py b/src/optimagic/logging/logger.py index f1a02ef85..0e92ec682 100644 --- a/src/optimagic/logging/logger.py +++ b/src/optimagic/logging/logger.py @@ -8,6 +8,7 @@ import numpy as np import pandas as pd import sqlalchemy as sql +import sqlalchemy.event from sqlalchemy.engine import Engine from optimagic.logging.base import ( @@ -51,7 +52,7 @@ class LogOptions: def __init_subclass__( cls: Type[LogOptions], abstract: bool = False, **kwargs: dict[Any, Any] - ): + ) -> None: if not abstract: LogOptions._subclass_registry.append(cls) super().__init_subclass__(**kwargs) diff --git a/src/optimagic/logging/read_log.py b/src/optimagic/logging/read_log.py index bd4253773..4e75a51da 100644 --- a/src/optimagic/logging/read_log.py +++ b/src/optimagic/logging/read_log.py @@ -14,13 +14,14 @@ import warnings from dataclasses import dataclass +from typing import Any -from optimagic.logging.logger import SQLiteLogOptions, SQLiteLogReader +from optimagic.logging.logger import LogReader, SQLiteLogOptions, SQLiteLogReader @dataclass class OptimizeLogReader: - def __new__(cls, *args, **kwargs): # type: ignore + def __new__(cls, *args: Any, **kwargs: Any) -> LogReader[SQLiteLogOptions]: warnings.warn( "OptimizeLogReader is deprecated and will be removed in a future " "version. Please use optimagic.logging.SQLiteLogReader instead.", diff --git a/src/optimagic/logging/sqlalchemy.py b/src/optimagic/logging/sqlalchemy.py index a21f4fc45..a3d3c9512 100644 --- a/src/optimagic/logging/sqlalchemy.py +++ b/src/optimagic/logging/sqlalchemy.py @@ -7,7 +7,8 @@ from typing import Any, Sequence, Type, cast import sqlalchemy as sql -from sqlalchemy import Column, Integer, PickleType, String +import sqlalchemy.event +from sqlalchemy import Column, Integer, PickleType, Row, String from sqlalchemy.engine.base import Engine from sqlalchemy.sql.base import Executable from sqlalchemy.sql.schema import MetaData @@ -82,7 +83,7 @@ def _setup_pickletype( inspector: Any, table: sql.Table, column_info: dict[str, Any] ) -> None: # noqa: ARG001 if isinstance(column_info["type"], sql.BLOB): - column_info["type"] = sql.PickleType(pickler=RobustPickler) # type:ignore + column_info["type"] = sql.PickleType(pickler=RobustPickler) @dataclass @@ -160,17 +161,17 @@ def table(self) -> sql.Table: def engine(self) -> Engine: return self._engine - def _select_row_by_key(self, key: int) -> list[Any]: + def _select_row_by_key(self, key: int) -> Sequence[Row[Any]]: stmt = self._table.select().where( getattr(self._table.c, self._table_config.primary_key) == key ) return self._execute_read_statement(stmt) - def _select_all_rows(self) -> list[Any]: + def _select_all_rows(self) -> Sequence[Row[Any]]: stmt = self._table.select() return self._execute_read_statement(stmt) - def _select_last_rows(self, n_rows: int) -> list[Any]: + def _select_last_rows(self, n_rows: int) -> Sequence[Row[Any]]: stmt = ( self._table.select() .order_by(getattr(self._table.c, self._table_config.primary_key).desc()) @@ -183,7 +184,7 @@ def _insert(self, insert_values: dict[str, Any]) -> None: stmt = self._table.insert().values(**insert_values) self._execute_write_statement(stmt) - def _execute_read_statement(self, statement: Executable) -> list[Any]: + def _execute_read_statement(self, statement: Executable) -> Sequence[Row[Any]]: with self._engine.connect() as connection: return connection.execute(statement).fetchall() @@ -230,7 +231,7 @@ def __init__( super().__init__(input_type, output_type, primary_key) columns = [ sql.Column(primary_key, sql.Integer, primary_key=True, autoincrement=True), - sql.Column(self._value_column, sql.PickleType(pickler=RobustPickler)), # type:ignore + sql.Column(self._value_column, sql.PickleType(pickler=RobustPickler)), ] table_config = TableConfig(table_name, columns, self.primary_key) @@ -280,7 +281,7 @@ def select_last_rows(self, n_rows: int) -> list[OutputType]: result = self._select_last_rows(n_rows) return self._post_process(result) - def _post_process(self, results: Sequence[sql.Row]) -> list[OutputType]: # type:ignore + def _post_process(self, results: Sequence[sql.Row]) -> list[OutputType]: output_list = [] for row in results: row_dict = {self.primary_key: row[0]} @@ -370,7 +371,7 @@ def select_last_rows(self, n_rows: int) -> list[OutputType]: result = self._select_last_rows(n_rows) return self._post_process(result) - def _post_process(self, results: Sequence[sql.Row]) -> list[OutputType]: # type:ignore + def _post_process(self, results: Sequence[sql.Row]) -> list[OutputType]: return [ self._output_type(**dict(zip(self.column_names, row, strict=False))) for row in results @@ -460,7 +461,7 @@ def __init__( columns = [ Column(self._PRIMARY_KEY, Integer, primary_key=True, autoincrement=True), Column("direction", String), - Column("params", PickleType(pickler=RobustPickler)), # type:ignore + Column("params", PickleType(pickler=RobustPickler)), ] table_config = TableConfig( diff --git a/src/optimagic/mark.py b/src/optimagic/mark.py index 5a2916abf..f6d7b7ec4 100644 --- a/src/optimagic/mark.py +++ b/src/optimagic/mark.py @@ -1,5 +1,5 @@ from functools import wraps -from typing import Any, Callable, ParamSpec, TypeVar, cast +from typing import Any, Callable, ParamSpec, Protocol, TypeVar, cast import pydantic @@ -16,57 +16,43 @@ ScalarFuncT = TypeVar("ScalarFuncT", bound=Callable[..., Any]) VectorFuncT = TypeVar("VectorFuncT", bound=Callable[..., Any]) +FuncT = TypeVar("FuncT", bound=Callable[..., Any]) def scalar(func: ScalarFuncT) -> ScalarFuncT: """Mark a function as a scalar function.""" - wrapper = func - try: - wrapper._problem_type = AggregationLevel.SCALAR # type: ignore - except (KeyboardInterrupt, SystemExit): - raise - except Exception: - - @wraps(func) - def wrapper(*args, **kwargs): # type: ignore - return func(*args, **kwargs) - - wrapper._problem_type = AggregationLevel.SCALAR # type: ignore - return wrapper + return _mark_problem_type(func, AggregationLevel.SCALAR) def least_squares(func: VectorFuncT) -> VectorFuncT: """Mark a function as a least squares function.""" - wrapper = func - try: - wrapper._problem_type = AggregationLevel.LEAST_SQUARES # type: ignore - except (KeyboardInterrupt, SystemExit): - raise - except Exception: - - @wraps(func) - def wrapper(*args, **kwargs): # type: ignore - return func(*args, **kwargs) - - wrapper._problem_type = AggregationLevel.LEAST_SQUARES # type: ignore - return wrapper + return _mark_problem_type(func, AggregationLevel.LEAST_SQUARES) def likelihood(func: VectorFuncT) -> VectorFuncT: """Mark a function as a likelihood function.""" - wrapper = func + return _mark_problem_type(func, AggregationLevel.LIKELIHOOD) + + +class _MarkedFunction(Protocol): + """A callable that carries the problem type set by the mark decorators.""" + + _problem_type: AggregationLevel + + +def _mark_problem_type(func: FuncT, problem_type: AggregationLevel) -> FuncT: + """Attach problem_type to func or, if that fails, to a wrapper around func.""" try: - wrapper._problem_type = AggregationLevel.LIKELIHOOD # type: ignore - except (KeyboardInterrupt, SystemExit): - raise + cast(_MarkedFunction, func)._problem_type = problem_type except Exception: @wraps(func) - def wrapper(*args, **kwargs): # type: ignore + def wrapper(*args: Any, **kwargs: Any) -> Any: return func(*args, **kwargs) - wrapper._problem_type = AggregationLevel.LIKELIHOOD # type: ignore - return wrapper + cast(_MarkedFunction, wrapper)._problem_type = problem_type + return cast(FuncT, wrapper) + return func # TODO: I get an error when adding bound=Algorithm to AlgorithmSubclass. Why? diff --git a/src/optimagic/optimization/algorithm.py b/src/optimagic/optimization/algorithm.py index 1677f7dc8..c66e55808 100644 --- a/src/optimagic/optimization/algorithm.py +++ b/src/optimagic/optimization/algorithm.py @@ -2,7 +2,7 @@ import warnings from abc import ABC, ABCMeta, abstractmethod from dataclasses import dataclass, replace -from typing import Any +from typing import Any, ClassVar import numpy as np import pydantic @@ -98,8 +98,10 @@ class InternalOptimizeResult: class AlgorithmMeta(ABCMeta): """Metaclass to get repr, algo_info and name for classes, not just instances.""" + __algo_info__: AlgoInfo | None + def __repr__(self) -> str: - if hasattr(self, "__algo_info__") and self.__algo_info__ is not None: + if self.__algo_info__ is not None: out = f"om.algos.{self.__algo_info__.name}" else: out = self.__class__.__name__ @@ -107,7 +109,7 @@ def __repr__(self) -> str: @property def name(self) -> str: - if hasattr(self, "__algo_info__") and self.__algo_info__ is not None: + if self.__algo_info__ is not None: out = self.__algo_info__.name else: out = self.__class__.__name__ @@ -115,7 +117,7 @@ def name(self) -> str: @property def algo_info(self) -> AlgoInfo: - if not hasattr(self, "__algo_info__") or self.__algo_info__ is None: + if self.__algo_info__ is None: msg = ( f"The algorithm {self.name} does not have have the __algo_info__ " "attribute. Use the `mark.minimizer` decorator to add this attribute." @@ -134,6 +136,9 @@ class Algorithm(ABC, metaclass=AlgorithmMeta): """ + __algo_info__: ClassVar[AlgoInfo | None] = None + """Information about the algorithm; set by the `mark.minimizer` decorator.""" + @abstractmethod def _solve_internal_problem( self, problem: InternalOptimizationProblem, x0: NDArray[np.float64] @@ -222,14 +227,14 @@ def with_option_if_applicable(self, **kwargs: Any) -> Self: def name(self) -> str: """The name of the algorithm.""" # cannot call algo_info here because it would be an infinite recursion - if hasattr(self, "__algo_info__") and self.__algo_info__ is not None: + if self.__algo_info__ is not None: return self.__algo_info__.name return self.__class__.__name__ @property def algo_info(self) -> AlgoInfo: """Information about the algorithm.""" - if not hasattr(self, "__algo_info__") or self.__algo_info__ is None: + if self.__algo_info__ is None: msg = ( f"The algorithm {self.name} does not have have the __algo_info__ " "attribute. Use the `mark.minimizer` decorator to add this attribute." diff --git a/src/optimagic/optimization/create_optimization_problem.py b/src/optimagic/optimization/create_optimization_problem.py index 7fd95c100..a74ee918d 100644 --- a/src/optimagic/optimization/create_optimization_problem.py +++ b/src/optimagic/optimization/create_optimization_problem.py @@ -1,4 +1,5 @@ import warnings +from collections.abc import Sequence from dataclasses import dataclass from pathlib import Path from typing import Any, Callable, Type @@ -164,9 +165,9 @@ def create_optimization_problem( raise MissingInputError(msg) if fun_and_jac is not None and fun is None and criterion is None: - if isinstance(fun_and_jac, list): + if isinstance(fun_and_jac, Sequence): raise NotImplementedError( - "If `fun_and_jac` is a list of callables, `fun` is not optional. " + "If `fun_and_jac` is a sequence of callables, `fun` is not optional. " ) fun = split_fun_and_jac(fun_and_jac, target="fun") diff --git a/src/optimagic/optimization/error_penalty.py b/src/optimagic/optimization/error_penalty.py index cf260ca82..eab3463af 100644 --- a/src/optimagic/optimization/error_penalty.py +++ b/src/optimagic/optimization/error_penalty.py @@ -78,7 +78,7 @@ def get_error_penalty_function( dim_out = ( 1 if solver_type == AggregationLevel.SCALAR - else len(start_criterion.internal_value(solver_type)) # type: ignore + else len(start_criterion.internal_value(solver_type)) # ty:ignore[invalid-argument-type] ) _penalty: Callable[ diff --git a/src/optimagic/optimization/history.py b/src/optimagic/optimization/history.py index 8fb8af212..c0cc7cafa 100644 --- a/src/optimagic/optimization/history.py +++ b/src/optimagic/optimization/history.py @@ -138,7 +138,7 @@ def fun_data(self, cost_model: CostModel, monotone: bool = False) -> pd.DataFram fun = _apply_reduction_to_batches( data=fun, batch_ids=self.batches, - reduction_function=min_or_max, # type: ignore[arg-type] + reduction_function=min_or_max, # ty:ignore[invalid-argument-type] ) # Verify that tasks are homogeneous in each batch, and select first if true. @@ -408,8 +408,6 @@ def _get_flat_params(params: list[PyTree]) -> list[list[float]]: def _get_flat_param_names(param: PyTree) -> list[str]: fast_path = _is_1d_array(param) if fast_path: - # Mypy raises an error here because .tolist() returns a str for zero-dimensional - # arrays, but the fast path is only taken for 1d arrays, so it can be ignored. return np.arange(param.size).astype(str).tolist() return leaf_names(param, namespace=PyTreeNamespace.VALUE) @@ -449,7 +447,7 @@ def _validate_args_are_all_none_or_lists_of_same_length( if not all_none: if all_list: - unique_list_lengths = set(map(len, args)) # type: ignore[arg-type] + unique_list_lengths = set(map(len, args)) # ty:ignore[invalid-argument-type] if len(unique_list_lengths) != 1: raise ValueError("All list arguments must have the same length.") @@ -487,6 +485,8 @@ def _apply_reduction_to_batches( """ batch_starts, batch_stops = _get_batch_starts_and_stops(batch_ids) + func_name = getattr(reduction_function, "__name__", repr(reduction_function)) + batch_results: list[float] = [] for start, stop in zip(batch_starts, batch_stops, strict=True): @@ -500,9 +500,9 @@ def _apply_reduction_to_batches( reduced = reduction_function(batch_data) except Exception as e: msg = ( - f"Calling function {reduction_function.__name__} on batch {batch_id} " + f"Calling function {func_name} on batch {batch_id} " "of the History raised an Exception. Please verify that " - f"{reduction_function.__name__} is well-defined, takes an iterable of " + f"{func_name} is well-defined, takes an iterable of " "floats as input and returns a scalar. The function must be able to " "handle NaN's." ) @@ -510,14 +510,14 @@ def _apply_reduction_to_batches( if not np.isscalar(reduced): msg = ( - f"Function {reduction_function.__name__} did not return a scalar for " - f"batch {batch_id}. Please verify that {reduction_function.__name__} " + f"Function {func_name} did not return a scalar for " + f"batch {batch_id}. Please verify that {func_name} " "returns a scalar when called on an iterable of floats. The function " "must be able to handle NaN's." ) raise ValueError(msg) - batch_results.append(float(reduced)) # type: ignore[arg-type,unused-ignore] + batch_results.append(float(reduced)) # ty:ignore[invalid-argument-type] return np.array(batch_results, dtype=np.float64) diff --git a/src/optimagic/optimization/internal_optimization_problem.py b/src/optimagic/optimization/internal_optimization_problem.py index 9c7b52b04..f6d628739 100644 --- a/src/optimagic/optimization/internal_optimization_problem.py +++ b/src/optimagic/optimization/internal_optimization_problem.py @@ -561,7 +561,10 @@ def _pure_evaluate_jac( _, jac_value = self._error_penalty_func(x) out_jac = _process_jac_value( - value=jac_value, direction=self._direction, converter=self._converter, x=x + value=jac_value, # ty:ignore[invalid-argument-type] + direction=self._direction, + converter=self._converter, + x=x, ) _assert_finite_jac( out_jac=out_jac, jac_value=jac_value, params=params, origin="jac" @@ -646,7 +649,7 @@ def func(x: NDArray[np.float64]) -> SpecificFunctionValue: ) algo_fun_value, hist_fun_value = _process_fun_value( - value=fun_value, # type: ignore + value=fun_value, # ty:ignore[invalid-argument-type] solver_type=self._solver_type, direction=self._direction, ) diff --git a/src/optimagic/optimization/multistart.py b/src/optimagic/optimization/multistart.py index 6048eb779..47ec38c2a 100644 --- a/src/optimagic/optimization/multistart.py +++ b/src/optimagic/optimization/multistart.py @@ -176,7 +176,7 @@ def single_optimization(x0, step_id): } raw_res = state["best_res"] - res = replace(raw_res, multistart_info=multistart_info) + res = replace(raw_res, multistart_info=multistart_info) # ty:ignore[invalid-argument-type] return res @@ -445,7 +445,7 @@ def update_convergence_state( # array as solution_criterion. for res in valid_results: if np.isscalar(res.fun): - fun = float(res.fun) + fun = float(res.fun) # ty:ignore[invalid-argument-type] elif solver_type == AggregationLevel.LIKELIHOOD: fun = float(np.sum(res.fun)) elif solver_type == AggregationLevel.LEAST_SQUARES: diff --git a/src/optimagic/optimization/multistart_options.py b/src/optimagic/optimization/multistart_options.py index 450c2a951..aacc2f671 100644 --- a/src/optimagic/optimization/multistart_options.py +++ b/src/optimagic/optimization/multistart_options.py @@ -370,7 +370,7 @@ def get_internal_multistart_options_from_public( n_samples = len(options.sample) else: sample = None - n_samples = options.n_samples # type: ignore + n_samples = options.n_samples batch_size = options.n_cores if options.batch_size is None else options.batch_size batch_evaluator = process_batch_evaluator(options.batch_evaluator) diff --git a/src/optimagic/optimization/optimization_logging.py b/src/optimagic/optimization/optimization_logging.py index 59946eaae..24c2b0e8a 100644 --- a/src/optimagic/optimization/optimization_logging.py +++ b/src/optimagic/optimization/optimization_logging.py @@ -23,7 +23,7 @@ def log_scheduled_steps_and_get_ids( default_row = {"status": StepStatus.SCHEDULED.value} if logger: for row in steps: - data = StepResult(**{**default_row, **row}) + data = StepResult(**{**default_row, **row}) # ty:ignore[invalid-argument-type] logger.step_store.insert(data) last_steps = logger.step_store.select_last_rows(len(steps)) diff --git a/src/optimagic/optimization/optimize.py b/src/optimagic/optimization/optimize.py index a98203885..cfefb8e6f 100644 --- a/src/optimagic/optimization/optimize.py +++ b/src/optimagic/optimization/optimize.py @@ -15,7 +15,7 @@ from __future__ import annotations from pathlib import Path -from typing import Any, Callable, Sequence, Type, cast +from typing import Any, Callable, Literal, Sequence, Type, cast import numpy as np from scipy.optimize import Bounds as ScipyBounds @@ -72,7 +72,7 @@ FunType = Callable[..., float | PyTree | FunctionValue] AlgorithmType = str | Algorithm | Type[Algorithm] -ConstraintsType = Constraint | list[Constraint] | dict[str, Any] | list[dict[str, Any]] +ConstraintsType = Constraint | dict[str, Any] | Sequence[Constraint | dict[str, Any]] JacType = Callable[..., PyTree] FunAndJacType = Callable[..., tuple[float | PyTree | FunctionValue, PyTree]] HessType = Callable[..., PyTree] @@ -94,9 +94,12 @@ def maximize( constraints: ConstraintsType | None = None, fun_kwargs: dict[str, Any] | None = None, algo_options: dict[str, Any] | None = None, - jac: JacType | list[JacType] | None = None, + jac: JacType | Sequence[JacType] | Literal[True] | None = None, jac_kwargs: dict[str, Any] | None = None, - fun_and_jac: FunAndJacType | CriterionAndDerivativeType | None = None, + fun_and_jac: FunAndJacType + | CriterionAndDerivativeType + | Sequence[FunAndJacType] + | None = None, fun_and_jac_kwargs: dict[str, Any] | None = None, numdiff_options: NumdiffOptions | NumdiffOptionsDict | None = None, # TODO: add typed-dict support? @@ -167,12 +170,16 @@ def maximize( jac: The first derivative of `fun`. Providing a closed form derivative can be a great way to speed up your optimization. The easiest way to get a derivative for your objective function are autodiff frameworks like - JAX. For details and examples see :ref:`how-to-jac`. + JAX. If you provide a sequence of derivatives, the one that matches the + aggregation level of the algorithm is used. For compatibility with scipy, + `jac=True` means that `fun` returns a tuple of the function value and its + derivative. For details and examples see :ref:`how-to-jac`. jac_kwargs: Additional keyword arguments for `jac`. fun_and_jac: A function that returns both the objective value and the derivative. This can be used do exploit synergies in the calculation of the - function value and its derivative. For details and examples see - :ref:`how-to-jac`. + function value and its derivative. If you provide a sequence, the element + that matches the aggregation level of the algorithm is used and `fun` must + also be provided. For details and examples see :ref:`how-to-jac`. fun_and_jac_kwargs: Additional keyword arguments for `fun_and_jac`. numdiff_options: Options for numerical differentiation. Can be a dictionary or an instance of :class:`optimagic.NumdiffOptions`. @@ -197,7 +204,7 @@ def maximize( To choose which heuristic is used and to customize the scaling, provide a dictionary or an instance of :class:`optimagic.ScalingOptions`. For details and examples see :ref:`scaling`. - multistart: If None or False, no multistart approach is used. If True, the + multistart: If False, no multistart approach is used. If True, the optimization is restarted from multiple starting points. Note that this requires finite bounds or soft bounds for all parameters. To customize the multistart approach, provide a dictionary or an instance of @@ -298,9 +305,12 @@ def minimize( constraints: ConstraintsType | None = None, fun_kwargs: dict[str, Any] | None = None, algo_options: dict[str, Any] | None = None, - jac: JacType | list[JacType] | None = None, + jac: JacType | Sequence[JacType] | Literal[True] | None = None, jac_kwargs: dict[str, Any] | None = None, - fun_and_jac: FunAndJacType | CriterionAndDerivativeType | None = None, + fun_and_jac: FunAndJacType + | CriterionAndDerivativeType + | Sequence[FunAndJacType] + | None = None, fun_and_jac_kwargs: dict[str, Any] | None = None, numdiff_options: NumdiffOptions | NumdiffOptionsDict | None = None, # TODO: add typed-dict support? @@ -371,12 +381,16 @@ def minimize( jac: The first derivative of `fun`. Providing a closed form derivative can be a great way to speed up your optimization. The easiest way to get a derivative for your objective function are autodiff frameworks like - JAX. For details and examples see :ref:`how-to-jac`. + JAX. If you provide a sequence of derivatives, the one that matches the + aggregation level of the algorithm is used. For compatibility with scipy, + `jac=True` means that `fun` returns a tuple of the function value and its + derivative. For details and examples see :ref:`how-to-jac`. jac_kwargs: Additional keyword arguments for `jac`. fun_and_jac: A function that returns both the objective value and the derivative. This can be used do exploit synergies in the calculation of the - function value and its derivative. For details and examples see - :ref:`how-to-jac`. + function value and its derivative. If you provide a sequence, the element + that matches the aggregation level of the algorithm is used and `fun` must + also be provided. For details and examples see :ref:`how-to-jac`. fun_and_jac_kwargs: Additional keyword arguments for `fun_and_jac`. numdiff_options: Options for numerical differentiation. Can be a dictionary or an instance of :class:`optimagic.NumdiffOptions`. @@ -401,7 +415,7 @@ def minimize( To choose which heuristic is used and to customize the scaling, provide a dictionary or an instance of :class:`optimagic.ScalingOptions`. For details and examples see :ref:`scaling`. - multistart: If None or False, no multistart approach is used. If True, the + multistart: If False, no multistart approach is used. If True, the optimization is restarted from multiple starting points. Note that this requires finite bounds or soft bounds for all parameters. To customize the multistart approach, provide a dictionary or an instance of diff --git a/src/optimagic/optimization/optimize_result.py b/src/optimagic/optimization/optimize_result.py index f2895cf53..231b491ef 100644 --- a/src/optimagic/optimization/optimize_result.py +++ b/src/optimagic/optimization/optimize_result.py @@ -1,6 +1,6 @@ import warnings from dataclasses import dataclass -from typing import Any, Dict, Optional +from typing import Any, Optional import numpy as np import pandas as pd @@ -61,10 +61,10 @@ class OptimizeResult: history: History | None = None - convergence_report: Dict | None = None + convergence_report: dict[str, Any] | None = None multistart_info: Optional["MultistartInfo"] = None - algorithm_output: Dict[str, Any] | None = None + algorithm_output: dict[str, Any] | None = None logger: LogReader | None = None # ================================================================================== diff --git a/src/optimagic/optimizers/_pounders/gqtpar.py b/src/optimagic/optimizers/_pounders/gqtpar.py index 7b03c9ba6..4da498674 100644 --- a/src/optimagic/optimizers/_pounders/gqtpar.py +++ b/src/optimagic/optimizers/_pounders/gqtpar.py @@ -203,8 +203,8 @@ def _get_initial_guess_for_lambdas( lambdas = DampingFactors( candidate=lambda_candidate, - lower_bound=lambda_lower_bound, - upper_bound=lambda_upper_bound, + lower_bound=lambda_lower_bound, # ty:ignore[invalid-argument-type] + upper_bound=lambda_upper_bound, # ty:ignore[invalid-argument-type] ) return lambdas diff --git a/src/optimagic/optimizers/_pounders/pounders_history.py b/src/optimagic/optimizers/_pounders/pounders_history.py index 643b23995..50a6d190a 100644 --- a/src/optimagic/optimizers/_pounders/pounders_history.py +++ b/src/optimagic/optimizers/_pounders/pounders_history.py @@ -1,6 +1,7 @@ """History class for pounders and similar optimizers.""" import numpy as np +from numpy.typing import NDArray class LeastSquaresHistory: @@ -24,11 +25,13 @@ class LeastSquaresHistory: """ def __init__(self): - self.xs = None - self.best_x = None - self.residuals = None - self.best_residuals = None - self.critvals = None + # The arrays are created when the first entries are added because their + # shape is only known then. + self.xs: NDArray[np.float64] | None = None + self.best_x: NDArray[np.float64] | None = None + self.residuals: NDArray[np.float64] | None = None + self.best_residuals: NDArray[np.float64] | None = None + self.critvals: NDArray[np.float64] | None = None self.n_fun = 0 self.best_index = 0 self.best_critval = np.inf @@ -96,9 +99,9 @@ def get_entries(self, index=None): np.ndarray: Float or 1d array with criterion values. """ - names = ["xs", "residuals", "critvals"] + arrays = [self.xs, self.residuals, self.critvals] - out = (getattr(self, name)[: self.n_fun] for name in names) + out = (_get_first_n_entries(arr, self.n_fun) for arr in arrays) # Reducing arrays to length n_fun ensures that invalid indices raise IndexError if index is not None: @@ -117,7 +120,7 @@ def get_xs(self, index=None): np.ndarray: 1d or 2d array with parameter vectors """ - out = self.xs[: self.n_fun] + out = _get_first_n_entries(self.xs, self.n_fun) out = out[index] if index is not None else out return out @@ -133,7 +136,7 @@ def get_residuals(self, index=None): np.ndarray: 1d or 2d array with residuals. """ - out = self.residuals[: self.n_fun] + out = _get_first_n_entries(self.residuals, self.n_fun) out = out[index] if index is not None else out return out @@ -149,7 +152,7 @@ def get_critvals(self, index=None): np.ndarray: Float or 1d array with criterion values. """ - out = self.critvals[: self.n_fun] + out = _get_first_n_entries(self.critvals, self.n_fun) out = out[index] if index is not None else out return out @@ -250,8 +253,13 @@ def get_best_residuals(self): def get_best_critval(self): return self.get_critvals(index=self.best_index) - def get_best_centered_entries(self, center_info): - return self.get_centered_entries(self, center_info, index=self.best_index) + +def _get_first_n_entries( + arr: NDArray[np.float64] | None, n: int +) -> NDArray[np.float64]: + if arr is None: + raise ValueError("No entries have been added to the history yet.") + return arr[:n] def _add_entries_to_array(arr, new, position): diff --git a/src/optimagic/optimizers/iminuit_migrad.py b/src/optimagic/optimizers/iminuit_migrad.py index 6ee25b034..666308e02 100644 --- a/src/optimagic/optimizers/iminuit_migrad.py +++ b/src/optimagic/optimizers/iminuit_migrad.py @@ -82,7 +82,7 @@ class IminuitMigrad(Algorithm): """ def _solve_internal_problem( - self, problem: InternalOptimizationProblem, params: NDArray[np.float64] + self, problem: InternalOptimizationProblem, x0: NDArray[np.float64] ) -> InternalOptimizeResult: if not IS_IMINUIT_INSTALLED: raise NotInstalledError( # pragma: no cover @@ -96,7 +96,7 @@ def _solve_internal_problem( def wrapped_objective(x: NDArray[np.float64]) -> float: return float(problem.fun(x)) - m = Minuit(wrapped_objective, params, grad=problem.jac) + m = Minuit(wrapped_objective, x0, grad=problem.jac) bounds = _convert_bounds_to_minuit_limits( problem.bounds.lower, problem.bounds.upper diff --git a/src/optimagic/optimizers/neldermead.py b/src/optimagic/optimizers/neldermead.py index ab5ddce84..9d08f1297 100644 --- a/src/optimagic/optimizers/neldermead.py +++ b/src/optimagic/optimizers/neldermead.py @@ -267,7 +267,7 @@ def func_parallel(args): m, ) for i in range(p) - ), + ), # ty:ignore[invalid-argument-type] n_cores=p, ) ), diff --git a/src/optimagic/parameters/bounds.py b/src/optimagic/parameters/bounds.py index e548fed95..8e26022db 100644 --- a/src/optimagic/parameters/bounds.py +++ b/src/optimagic/parameters/bounds.py @@ -157,9 +157,9 @@ def get_internal_bounds( raise InvalidBoundsError(msg) if np.isinf(lower_flat).all(): - lower_flat = None # type: ignore[assignment] + lower_flat = None if np.isinf(upper_flat).all(): - upper_flat = None # type: ignore[assignment] + upper_flat = None return lower_flat, upper_flat @@ -195,7 +195,7 @@ def _update_bounds_and_flatten( flat_nan_dict = dict(zip(params_names, flat_nan_tree, strict=False)) - invalid = {"names": [], "bounds": []} # type: ignore + invalid = {"names": [], "bounds": []} for bounds_name, bounds_leaf in zip(bounds_names, flat_bounds, strict=False): # if a bounds leaf is None we treat it as saying the the corresponding # subtree of params has no bounds. diff --git a/src/optimagic/parameters/consolidate_constraints.py b/src/optimagic/parameters/consolidate_constraints.py index 365c702a8..cf8ff9f9e 100644 --- a/src/optimagic/parameters/consolidate_constraints.py +++ b/src/optimagic/parameters/consolidate_constraints.py @@ -582,7 +582,7 @@ def _plug_equality_constraints_into_linear_weights(weights, post_replacements): """ w = weights.T plugged_iloc = pd.Series(post_replacements) - plugged_iloc = plugged_iloc.where(plugged_iloc >= 0, np.arange(len(plugged_iloc))) + plugged_iloc = plugged_iloc.where(plugged_iloc >= 0, np.arange(len(plugged_iloc))) # ty:ignore[invalid-argument-type] w["plugged_iloc"] = plugged_iloc plugged_weights = w.groupby("plugged_iloc").sum() diff --git a/src/optimagic/parameters/constraints/resolution.py b/src/optimagic/parameters/constraints/resolution.py index 713ba99d3..ec787d4e9 100644 --- a/src/optimagic/parameters/constraints/resolution.py +++ b/src/optimagic/parameters/constraints/resolution.py @@ -17,6 +17,7 @@ import warnings from collections import Counter +from collections.abc import Sequence from dataclasses import dataclass from typing import Any, Callable @@ -98,7 +99,7 @@ def _fail_if_duplicates(self, index: IntArray) -> None: def resolve_constraints( - constraints: list[Constraint], + constraints: Sequence[Constraint], params: PyTree, tree_converter: TreeConverter, param_names: list[str], diff --git a/src/optimagic/pytree.py b/src/optimagic/pytree.py index 2a62f6dd6..59df59209 100644 --- a/src/optimagic/pytree.py +++ b/src/optimagic/pytree.py @@ -15,7 +15,7 @@ if IS_JAX_INSTALLED: import jax - import jax.numpy as jnp # type: ignore[import-not-found] + import jax.numpy as jnp JAX_ARRAY_TYPE: type = type(jnp.empty(0)) JAX_TRACER_TYPE: type = jax.core.Tracer diff --git a/src/optimagic/typing.py b/src/optimagic/typing.py index a61d0f7d1..8717261f1 100644 --- a/src/optimagic/typing.py +++ b/src/optimagic/typing.py @@ -212,7 +212,7 @@ def __init__(self: Any, *args: Any, **kwargs: Any) -> None: except pydantic.ValidationError as e: raise make_error(e) from e - out.__init__ = __init__ # type: ignore[method-assign] + out.__init__ = __init__ return typing.cast("type[DataclassT]", out) return decorator diff --git a/src/optimagic/visualization/backends.py b/src/optimagic/visualization/backends.py index 01e5bc644..f5c94741f 100644 --- a/src/optimagic/visualization/backends.py +++ b/src/optimagic/visualization/backends.py @@ -493,7 +493,7 @@ def _line_plot_bokeh( ) if line.show_in_legend: - _legend_items.append(LegendItem(label=line.name, renderers=[glyph])) # type: ignore[list-item] + _legend_items.append(LegendItem(label=line.name, renderers=[glyph])) if horizontal_line is not None: span = Span( @@ -606,7 +606,7 @@ def _grid_line_plot_bokeh( subplot_row.append(p) plots.append(subplot_row) - grid = gridplot( # type: ignore[call-overload] + grid = gridplot( plots, height=height // n_rows if height else None, width=width // n_cols if width else None, @@ -651,7 +651,7 @@ def _line_plot_altair( if template is None: template = "default" - alt.theme.enable(template) + alt.theme.enable(template) # ty:ignore[invalid-argument-type] dfs = [] for line in lines: diff --git a/src/optimagic/visualization/history_plots.py b/src/optimagic/visualization/history_plots.py index a3468fa52..23a649ab5 100644 --- a/src/optimagic/visualization/history_plots.py +++ b/src/optimagic/visualization/history_plots.py @@ -1,5 +1,6 @@ import inspect import itertools +from collections.abc import Mapping, Sequence from dataclasses import dataclass from pathlib import Path from typing import Any, Callable, Literal @@ -44,7 +45,7 @@ def criterion_plot( - results: ResultOrPath | list[ResultOrPath] | dict[str, ResultOrPath], + results: ResultOrPath | Sequence[ResultOrPath] | Mapping[Any, ResultOrPath], names: list[str] | str | None = None, max_evaluations: int | None = None, backend: Literal["plotly", "matplotlib", "bokeh", "altair"] = "plotly", @@ -118,7 +119,7 @@ def criterion_plot( def _harmonize_inputs_to_dict( - results: ResultOrPath | list[ResultOrPath] | dict[str, ResultOrPath], + results: ResultOrPath | Sequence[ResultOrPath] | Mapping[Any, ResultOrPath], names: list[str] | str | None, ) -> dict[str, ResultOrPath]: """Convert all valid inputs for results and names to dict[str, OptimizeResult].""" @@ -133,11 +134,10 @@ def _harmonize_inputs_to_dict( raise ValueError("len(results) needs to be equal to len(names).") # handle dict case - if isinstance(results, dict): + if isinstance(results, Mapping): + results_dict = dict(results) if names is not None: - results_dict = dict(zip(names, list(results.values()), strict=False)) - else: - results_dict = results + results_dict = dict(zip(names, results_dict.values(), strict=False)) # unlabeled iterable of results else: @@ -356,18 +356,10 @@ def _retrieve_optimization_data_from_result_object( if stack_multistart: stacked = _get_stacked_local_histories(local_histories, res.direction) if show_exploration: - fun = res.multistart_info.exploration_results[::-1] + stacked.fun - params = res.multistart_info.exploration_sample[::-1] + stacked.params - - stacked = History( - direction=stacked.direction, - fun=fun, - params=params, - # TODO: This needs to be fixed - start_time=len(fun) * [None], # type: ignore - stop_time=len(fun) * [None], # type: ignore - batches=len(fun) * [None], # type: ignore - task=len(fun) * [None], # type: ignore + stacked = _prepend_exploration( + stacked, + exploration_fun=res.multistart_info.exploration_results, + exploration_params=res.multistart_info.exploration_sample, ) else: stacked = None @@ -419,9 +411,12 @@ def _retrieve_optimization_data_from_database( if stack_multistart and local_histories is not None: stacked = _get_stacked_local_histories(local_histories, direction, _history) - if show_exploration: - stacked["params"] = exploration["params"][::-1] + stacked["params"] # type: ignore - stacked["criterion"] = exploration["criterion"][::-1] + stacked["criterion"] # type: ignore + if show_exploration and exploration is not None: + stacked = _prepend_exploration( + stacked, + exploration_fun=exploration.fun, + exploration_params=exploration.params, + ) else: stacked = None @@ -432,8 +427,8 @@ def _retrieve_optimization_data_from_database( start_time=_history["time"], # TODO (@janosg): Retrieve `stop_time` from `hist` once it is available. # https://github.com/optimagic-dev/optimagic/pull/553 - stop_time=len(_history["fun"]) * [None], # type: ignore - task=len(_history["fun"]) * [None], # type: ignore + stop_time=len(_history["fun"]) * [None], # ty:ignore[invalid-argument-type] + task=len(_history["fun"]) * [None], # ty:ignore[invalid-argument-type] batches=list(range(len(_history["fun"]))), ) @@ -480,12 +475,37 @@ def _get_stacked_local_histories( # TODO (@janosg): Retrieve `stop_time` from `hist` once it is available for the # IterationHistory. # https://github.com/optimagic-dev/optimagic/pull/553 - stop_time=len(stacked["criterion"]) * [None], # type: ignore - task=len(stacked["criterion"]) * [None], # type: ignore + stop_time=len(stacked["criterion"]) * [None], # ty:ignore[invalid-argument-type] + task=len(stacked["criterion"]) * [None], # ty:ignore[invalid-argument-type] batches=list(range(len(stacked["criterion"]))), ) +def _prepend_exploration( + history: History, + exploration_fun: list[float], + exploration_params: list[PyTree], +) -> History: + """Prepend the exploration samples in reverse order to a stacked history. + + The exploration samples are sorted from best to worst, so reversing them puts the + best samples right before the local optimizations. + + """ + fun = exploration_fun[::-1] + history.fun + params = exploration_params[::-1] + history.params + return History( + direction=history.direction, + fun=fun, + params=params, + # TODO: This needs to be fixed + start_time=len(fun) * [None], # ty:ignore[invalid-argument-type] + stop_time=len(fun) * [None], # ty:ignore[invalid-argument-type] + batches=len(fun) * [None], # ty:ignore[invalid-argument-type] + task=len(fun) * [None], # ty:ignore[invalid-argument-type] + ) + + def _extract_criterion_plot_lines( data: list[_PlottingMultistartHistory], max_evaluations: int | None, diff --git a/src/optimagic/visualization/slice_plot.py b/src/optimagic/visualization/slice_plot.py index da0a500ef..859c4948d 100644 --- a/src/optimagic/visualization/slice_plot.py +++ b/src/optimagic/visualization/slice_plot.py @@ -290,7 +290,7 @@ def _get_plot_data( metadata.append(meta) plot_data = pd.DataFrame(metadata) - plot_data["Function Value"] = func_values # type: ignore[assignment] + plot_data["Function Value"] = func_values # ty:ignore[invalid-assignment] return plot_data, internal_params diff --git a/src/optimagic/visualization/slice_plot_3d.py b/src/optimagic/visualization/slice_plot_3d.py index ebeb36707..f83439619 100644 --- a/src/optimagic/visualization/slice_plot_3d.py +++ b/src/optimagic/visualization/slice_plot_3d.py @@ -24,7 +24,7 @@ from optimagic.typing import AggregationLevel, PyTreeNamespace -def slice_plot_3d( # type: ignore[no-untyped-def] +def slice_plot_3d( func, params, bounds=None, @@ -340,7 +340,7 @@ def slice_plot_3d( # type: ignore[no-untyped-def] fig = plot_contour( x, y, - z, # type: ignore[arg-type] + z, scatter_point, plot_kwargs, layout_kwargs, @@ -358,7 +358,7 @@ def slice_plot_3d( # type: ignore[no-untyped-def] return combine_plots(plots, make_subplot_kwargs, layout_kwargs, expand_yrange) -def generate_evaluation_points( # type: ignore[no-untyped-def] +def generate_evaluation_points( projection, selected, internal_params, params_data, converter ): """Create the list of parameter sets for function evaluation. @@ -411,9 +411,7 @@ def generate_evaluation_points( # type: ignore[no-untyped-def] return evaluation_points -def plot_data_cache( # type: ignore[no-untyped-def] - projection, selected, internal_params, func_values, n_gridpoints -): +def plot_data_cache(projection, selected, internal_params, func_values, n_gridpoints): """Caches and maps evaluated function values to their parameters. This function takes the flat array of criterion function outputs and maps @@ -469,7 +467,7 @@ def plot_data_cache( # type: ignore[no-untyped-def] return plot_data -def plot_line( # type: ignore[no-untyped-def] +def plot_line( x: list[float], y: list[float], display_name: str, @@ -523,7 +521,7 @@ def plot_line( # type: ignore[no-untyped-def] return fig -def plot_surface( # type: ignore[no-untyped-def] +def plot_surface( x: NDArray[np.float64], y: NDArray[np.float64], z, @@ -565,7 +563,7 @@ def plot_surface( # type: ignore[no-untyped-def] return fig -def plot_contour( # type: ignore[no-untyped-def] +def plot_contour( x: NDArray[np.float64], y: NDArray[np.float64], z: list[float], @@ -615,7 +613,7 @@ class ProjectionConfig(str, Enum): SURFACE = "surface" @classmethod - def validate(cls, value): # type: ignore[no-untyped-def] + def validate(cls, value): if value is None: return None if isinstance(value, str): @@ -647,14 +645,14 @@ class Projection: """ - def __init__(self, value): # type: ignore[no-untyped-def] + def __init__(self, value): self._univariate = False self.lower = None self.upper = None self._parse(value) - def _parse(self, value): # type: ignore[no-untyped-def] + def _parse(self, value): if isinstance(value, str): value = value.lower() if value == ProjectionConfig.UNIVARIATE: @@ -681,7 +679,7 @@ def is_univariate(self) -> bool: def is_dict(self) -> bool: return not self._univariate - def get_config(self): # type: ignore[no-untyped-def] + def get_config(self): if self._univariate: return ProjectionConfig.UNIVARIATE return {"lower": self.lower, "upper": self.upper} @@ -694,7 +692,7 @@ def compute_yaxis_range(y: list[float], expand_yrange: float) -> list[float]: return [y_min - expand_yrange * y_range, y_max + expand_yrange * y_range] -def combine_plots( # type: ignore[no-untyped-def] +def combine_plots( plots: dict[tuple[int, int], go.Figure], make_subplot_kwargs, layout_kwargs, @@ -806,7 +804,7 @@ def combine_plots( # type: ignore[no-untyped-def] return fig -def _get_subplot_spec( # type: ignore[no-untyped-def] +def _get_subplot_spec( i: int, j: int, projection, n_selected: int ) -> dict[str | None, str | None]: # Determine subplot spec type (xy, scene, contour) for a given subplot position. @@ -830,7 +828,7 @@ def _get_subplot_spec( # type: ignore[no-untyped-def] return {} -def evaluate_plot_kwargs(plot_kwargs): # type: ignore[no-untyped-def] +def evaluate_plot_kwargs(plot_kwargs): # Set default styling for plots if not provided by the user. if plot_kwargs is None: plot_kwargs = {} @@ -860,7 +858,7 @@ def evaluate_plot_kwargs(plot_kwargs): # type: ignore[no-untyped-def] return plot_kwargs_defaults -def evaluate_make_subplot_kwargs( # type: ignore[no-untyped-def] +def evaluate_make_subplot_kwargs( make_subplot_kwargs, n_selected: int, projection, @@ -908,13 +906,12 @@ def evaluate_make_subplot_kwargs( # type: ignore[no-untyped-def] "horizontal_spacing": 1 / (make_subplot_defaults["cols"] * 5), "vertical_spacing": (1 / max(make_subplot_defaults["rows"] - 1, 1)) / 5, } - ) + ) # ty:ignore[no-matching-overload] make_subplot_defaults.update(make_subplot_kwargs) return make_subplot_defaults -# mypy: disable-error-code="dict-item" -def evaluate_layout_kwargs( # type: ignore[no-untyped-def] +def evaluate_layout_kwargs( layout_kwargs, projection, subplot_config, diff --git a/tests/estimagic/test_estimation_table.py b/tests/estimagic/test_estimation_table.py index e7a935182..4c4351e3b 100644 --- a/tests/estimagic/test_estimation_table.py +++ b/tests/estimagic/test_estimation_table.py @@ -1,5 +1,6 @@ import io import textwrap +import warnings import numpy as np import pandas as pd @@ -27,6 +28,7 @@ estimation_table, render_html, render_latex, + suppress_performance_warnings, ) @@ -250,7 +252,7 @@ def test_convert_model_to_series_without_inference(): # test create stat series def test_create_statistics_sr(): df = pd.DataFrame(np.empty((10, 3)), columns=["a", "b", "c"]) - df.index = pd.MultiIndex.from_arrays(np.array([np.arange(10), np.arange(10)])) + df.index = pd.MultiIndex.from_arrays(np.array([np.arange(10), np.arange(10)])) # ty:ignore[invalid-argument-type] info = {"rsquared": 0.45, "n_obs": 400, "rsquared_adj": 0.0002} number_format = ("{0:.3g}", "{0:.5f}", "{0:.4g}") add_trailing_zeros = True @@ -273,7 +275,7 @@ def test_create_statistics_sr(): ) exp = pd.Series(["0.4500", "0.0002", "400"]) exp.index = pd.MultiIndex.from_arrays( - np.array([np.array(["R2", "R2 Adj.", "Observations"]), np.array(["", "", ""])]) + np.array([np.array(["R2", "R2 Adj.", "Observations"]), np.array(["", "", ""])]) # ty:ignore[invalid-argument-type] ) ase(exp.sort_index(), res.sort_index()) @@ -282,7 +284,7 @@ def test_create_statistics_sr(): def test_process_frame_indices_index(): df = pd.DataFrame(np.ones((3, 3)), columns=["", "", ""]) df.index = pd.MultiIndex.from_arrays( - np.array([["today", "today", "today"], ["var1", "var2", "var3"]]) + np.array([["today", "today", "today"], ["var1", "var2", "var3"]]) # ty:ignore[invalid-argument-type] ) df.index.names = ["l1", "l2"] par_name_map = {"today": "tomorrow", "var1": "1stvar"} @@ -433,6 +435,18 @@ def test_customize_col_names_list(): assert exp == res +def test_customize_col_groups_invalid_type(): + default = ["a_name", "a_name", "third_name"] + with pytest.raises(TypeError, match="Invalid type for custom_col_groups"): + _customize_col_groups(default, "invalid") + + +def test_customize_col_names_invalid_type(): + default = list("abc") + with pytest.raises(TypeError, match="Invalid type for custom_col_names"): + _customize_col_names(default_col_names=default, custom_col_names="invalid") + + def test_get_params_frames_with_common_index(): m1 = { "params": pd.DataFrame(np.ones(5), index=list("abcde")), @@ -495,3 +509,14 @@ def test_manual_extra_info(): for i, r in footer.iterrows(): res = _center_align_integers_and_non_numeric_strings(r) ase(exp.loc[i], res) + + +def test_suppress_performance_warnings(): + @suppress_performance_warnings + def raise_performance_warning(): + warnings.warn("slow", pd.errors.PerformanceWarning) + return 1 + + with warnings.catch_warnings(): + warnings.simplefilter("error") + assert raise_performance_warning() == 1 diff --git a/tests/estimagic/test_msm_sensitivity.py b/tests/estimagic/test_msm_sensitivity.py index fd2d0c1da..35332e2de 100644 --- a/tests/estimagic/test_msm_sensitivity.py +++ b/tests/estimagic/test_msm_sensitivity.py @@ -78,7 +78,7 @@ def func_kwargs(): @pytest.fixture() def jac(params, func_kwargs): derivative_dict = first_derivative( - func=simulate_aggregated_moments, + func=simulate_aggregated_moments, # ty:ignore[invalid-argument-type] params=params, func_kwargs=func_kwargs, ) diff --git a/tests/estimagic/test_shared.py b/tests/estimagic/test_shared.py index 4f3c53893..8d070e394 100644 --- a/tests/estimagic/test_shared.py +++ b/tests/estimagic/test_shared.py @@ -53,7 +53,7 @@ def _from_internal(x, return_type="flat"): # noqa: ARG001 class FakeConverter(NamedTuple): has_transforming_constraints: bool = True - params_from_internal: callable = _from_internal + params_from_internal: callable = _from_internal # ty:ignore[invalid-type-form] class FakeInternalParams(NamedTuple): @@ -211,7 +211,7 @@ def test_get_derivative_case(): def test_to_numpy_invalid(): with pytest.raises(TypeError): - _to_numpy(15) + _to_numpy(15) # ty:ignore[missing-argument] def test_calculate_estimation_summary(): diff --git a/tests/optimagic/differentiation/test_compare_derivatives_with_jax.py b/tests/optimagic/differentiation/test_compare_derivatives_with_jax.py index 45cb7002d..219d1092b 100644 --- a/tests/optimagic/differentiation/test_compare_derivatives_with_jax.py +++ b/tests/optimagic/differentiation/test_compare_derivatives_with_jax.py @@ -27,7 +27,7 @@ def _tree_equal_numpy_leaves(tree1, tree2): equality_checkers = {np.ndarray: lambda x, y: aaae(x, y, decimal=DECIMALS)} - tree_equal(tree1, tree2, equality_checkers=equality_checkers) + tree_equal(tree1, tree2, equality_checkers=equality_checkers) # ty:ignore[invalid-argument-type] def _compute_testable_optimagic_and_jax_derivatives(func, params, func_jax=None): diff --git a/tests/optimagic/differentiation/test_derivatives.py b/tests/optimagic/differentiation/test_derivatives.py index 670c1c0ab..20805095d 100644 --- a/tests/optimagic/differentiation/test_derivatives.py +++ b/tests/optimagic/differentiation/test_derivatives.py @@ -240,7 +240,7 @@ def test_convert_evaluation_data_to_frame(): arr = np.arange(4).reshape(2, 2) arr2 = arr.reshape(2, 1, 2) steps = Steps(pos=arr, neg=-arr) - evals = Evals(pos=arr2, neg=-arr2) + evals = Evals(pos=arr2, neg=-arr2) # ty:ignore[invalid-argument-type] expected = [ [1, 0, 0, 0, 0, 0], [1, 0, 1, 0, 1, 1], @@ -393,8 +393,8 @@ def test_numdiff_result_getitem(): ) assert res["derivative"] == res.derivative assert res["func_value"] == res.func_value - assert_frame_equal(res["_func_evals"], res._func_evals) - assert_frame_equal(res["_derivative_candidates"], res._derivative_candidates) + assert_frame_equal(res["_func_evals"], res._func_evals) # ty:ignore[invalid-argument-type] + assert_frame_equal(res["_derivative_candidates"], res._derivative_candidates) # ty:ignore[invalid-argument-type] def test_first_and_second_derivative_have_same_type_hints(): diff --git a/tests/optimagic/differentiation/test_numdiff_options.py b/tests/optimagic/differentiation/test_numdiff_options.py index 796e27c61..ba3c46b93 100644 --- a/tests/optimagic/differentiation/test_numdiff_options.py +++ b/tests/optimagic/differentiation/test_numdiff_options.py @@ -34,22 +34,22 @@ def test_pre_process_numdiff_options_dict_case(): def test_pre_process_numdiff_options_invalid_type(): with pytest.raises(InvalidNumdiffOptionsError): - pre_process_numdiff_options(numdiff_options="invalid") + pre_process_numdiff_options(numdiff_options="invalid") # ty:ignore[invalid-argument-type] def test_pre_process_numdiff_options_invalid_dict_key(): with pytest.raises(InvalidNumdiffOptionsError, match="Invalid numdiff options"): - pre_process_numdiff_options(numdiff_options={"wrong_key": "central"}) + pre_process_numdiff_options(numdiff_options={"wrong_key": "central"}) # ty:ignore[invalid-argument-type, invalid-key] def test_pre_process_numdiff_options_invalid_dict_value(): with pytest.raises(InvalidNumdiffOptionsError, match="Invalid numdiff `method`:"): - pre_process_numdiff_options(numdiff_options={"method": "invalid"}) + pre_process_numdiff_options(numdiff_options={"method": "invalid"}) # ty:ignore[invalid-argument-type] def test_numdiff_options_invalid_method(): with pytest.raises(InvalidNumdiffOptionsError, match="Invalid numdiff `method`:"): - NumdiffOptions(method="invalid") + NumdiffOptions(method="invalid") # ty:ignore[invalid-argument-type] def test_numdiff_options_invalid_step_size(): @@ -82,4 +82,4 @@ def test_numdiff_options_invalid_batch_evaluator(): with pytest.raises( InvalidNumdiffOptionsError, match="Invalid batch evaluator: invalid" ): - NumdiffOptions(batch_evaluator="invalid") + NumdiffOptions(batch_evaluator="invalid") # ty:ignore[invalid-argument-type] diff --git a/tests/optimagic/logging/test_base.py b/tests/optimagic/logging/test_base.py index 1d903cecd..02381195d 100644 --- a/tests/optimagic/logging/test_base.py +++ b/tests/optimagic/logging/test_base.py @@ -9,18 +9,18 @@ def test_key_value_store_raise_errors(): class NoDataClass(NonUpdatableKeyValueStore): def __init__(self): - super().__init__({1}, [], "key") + super().__init__({1}, [], "key") # ty:ignore[invalid-argument-type] def insert(self, value: InputType) -> None: pass - def _select_by_key(self, key: int) -> list[OutputType]: + def _select_by_key(self, key: int) -> list[OutputType]: # ty:ignore[empty-body] pass - def _select_all(self) -> list[OutputType]: + def _select_all(self) -> list[OutputType]: # ty:ignore[empty-body] pass - def select_last_rows(self, n_rows: int) -> list[OutputType]: + def select_last_rows(self, n_rows: int) -> list[OutputType]: # ty:ignore[empty-body] pass class WrongPrimaryKey(NonUpdatableKeyValueStore): @@ -41,13 +41,13 @@ def __init__(self): def insert(self, value: InputType) -> None: pass - def _select_by_key(self, key: int) -> list[OutputType]: + def _select_by_key(self, key: int) -> list[OutputType]: # ty:ignore[empty-body] pass - def _select_all(self) -> list[OutputType]: + def _select_all(self) -> list[OutputType]: # ty:ignore[empty-body] pass - def select_last_rows(self, n_rows: int) -> list[OutputType]: + def select_last_rows(self, n_rows: int) -> list[OutputType]: # ty:ignore[empty-body] pass with pytest.raises(ValueError): diff --git a/tests/optimagic/logging/test_sqlalchemy.py b/tests/optimagic/logging/test_sqlalchemy.py index 5d1ae4d2d..9c3665622 100644 --- a/tests/optimagic/logging/test_sqlalchemy.py +++ b/tests/optimagic/logging/test_sqlalchemy.py @@ -70,7 +70,7 @@ def test_update_raise(self, store): store.update(key=1, value=updated_result) with pytest.raises(AttributeError): - store.sellect_typo # type:ignore # noqa: B018 + store.sellect_typo # noqa: B018 def test_serialization(self, store): """Test the serialization and deserialization of the IterationStore.""" diff --git a/tests/optimagic/optimization/test_algo_option_validation.py b/tests/optimagic/optimization/test_algo_option_validation.py index edf9d5a98..7ef9442a8 100644 --- a/tests/optimagic/optimization/test_algo_option_validation.py +++ b/tests/optimagic/optimization/test_algo_option_validation.py @@ -48,19 +48,19 @@ def test_field_types_are_type_objects(): def test_str_option_is_converted(): - algo = DummyAlgorithm(initial_radius="2.0") + algo = DummyAlgorithm(initial_radius="2.0") # ty:ignore[invalid-argument-type] assert isinstance(algo.initial_radius, float) assert algo.initial_radius == 2.0 def test_float_option_is_converted_to_int(): - algo = DummyAlgorithm(stopping_maxiter=500.0) + algo = DummyAlgorithm(stopping_maxiter=500.0) # ty:ignore[invalid-argument-type] assert isinstance(algo.stopping_maxiter, int) assert algo.stopping_maxiter == 500 def test_optional_option_is_converted(): - algo = DummyAlgorithm(n_points=3.0) + algo = DummyAlgorithm(n_points=3.0) # ty:ignore[invalid-argument-type] assert isinstance(algo.n_points, int) assert algo.n_points == 3 assert DummyAlgorithm(n_points=None).n_points is None @@ -73,7 +73,7 @@ def test_invalid_option_value_raises_error(): def test_invalid_option_name_raises_error(): with pytest.raises(InvalidAlgoOptionError): - DummyAlgorithm(this_is_not_an_option=1) + DummyAlgorithm(this_is_not_an_option=1) # ty:ignore[unknown-argument] def test_conversion_works_in_with_option(): diff --git a/tests/optimagic/optimization/test_algo_option_validation_stringified.py b/tests/optimagic/optimization/test_algo_option_validation_stringified.py index 7721c33cb..32ecdabcd 100644 --- a/tests/optimagic/optimization/test_algo_option_validation_stringified.py +++ b/tests/optimagic/optimization/test_algo_option_validation_stringified.py @@ -55,19 +55,19 @@ def test_field_types_are_annotation_strings(): def test_str_option_is_converted(): - algo = DummyAlgorithm(initial_radius="2.0") + algo = DummyAlgorithm(initial_radius="2.0") # ty:ignore[invalid-argument-type] assert isinstance(algo.initial_radius, float) assert algo.initial_radius == 2.0 def test_float_option_is_converted_to_int(): - algo = DummyAlgorithm(stopping_maxiter=500.0) + algo = DummyAlgorithm(stopping_maxiter=500.0) # ty:ignore[invalid-argument-type] assert isinstance(algo.stopping_maxiter, int) assert algo.stopping_maxiter == 500 def test_optional_option_is_converted(): - algo = DummyAlgorithm(n_points=3.0) + algo = DummyAlgorithm(n_points=3.0) # ty:ignore[invalid-argument-type] assert isinstance(algo.n_points, int) assert algo.n_points == 3 assert DummyAlgorithm(n_points=None).n_points is None @@ -80,7 +80,7 @@ def test_invalid_option_value_raises_error(): def test_invalid_option_name_raises_error(): with pytest.raises(InvalidAlgoOptionError): - DummyAlgorithm(this_is_not_an_option=1) + DummyAlgorithm(this_is_not_an_option=1) # ty:ignore[unknown-argument] def test_conversion_works_in_with_option(): diff --git a/tests/optimagic/optimization/test_algorithm.py b/tests/optimagic/optimization/test_algorithm.py index dfc92bdeb..8fe39e18b 100644 --- a/tests/optimagic/optimization/test_algorithm.py +++ b/tests/optimagic/optimization/test_algorithm.py @@ -57,7 +57,7 @@ def test_algo_info_validation(kwargs): combined_kwargs = {**valid_kwargs, **kwargs} msg = "The following arguments to AlgoInfo or `mark.minimizer` are invalid" with pytest.raises(InvalidAlgoInfoError, match=msg): - AlgoInfo(**combined_kwargs) + AlgoInfo(**combined_kwargs) # ty:ignore[invalid-argument-type] # ====================================================================================== @@ -103,7 +103,7 @@ def test_internal_optimize_result_validation(kwargs): combined_kwargs = {**valid_kwargs, **kwargs} msg = "The following arguments to InternalOptimizeResult are invalid" with pytest.raises(TypeError, match=msg): - InternalOptimizeResult(**combined_kwargs) + InternalOptimizeResult(**combined_kwargs) # ty:ignore[invalid-argument-type] # ====================================================================================== @@ -139,7 +139,7 @@ def _solve_internal_problem(self, problem, x0): fun=0.0, start_time=0.0, task=EvalTask.FUN, - ) + ) # ty:ignore[missing-argument] problem.history.add_entry(hist_entry) return InternalOptimizeResult(x=x0, fun=0.0, success=True) diff --git a/tests/optimagic/optimization/test_callback.py b/tests/optimagic/optimization/test_callback.py index e2d2a3d9d..16fe86004 100644 --- a/tests/optimagic/optimization/test_callback.py +++ b/tests/optimagic/optimization/test_callback.py @@ -103,6 +103,7 @@ def test_callback_not_called_on_jac(): ) aae(xs, [np.arange(3) + 2.0]) + assert res.history is not None assert len(res.history.params) == 3 @@ -117,7 +118,7 @@ def bad_callback(): fun=lambda x: x @ x, x0=np.arange(3, dtype=float), algorithm="scipy_neldermead", - callback=bad_callback, + callback=bad_callback, # ty:ignore[invalid-argument-type] ) @@ -132,7 +133,7 @@ def bad_callback(xk, extra): fun=lambda x: x @ x, x0=np.arange(3, dtype=float), algorithm="scipy_neldermead", - callback=bad_callback, + callback=bad_callback, # ty:ignore[invalid-argument-type] ) @@ -142,7 +143,7 @@ def test_invalid_callback_not_callable(): fun=lambda x: x @ x, x0=np.arange(3, dtype=float), algorithm="scipy_neldermead", - callback="not-a-callable", + callback="not-a-callable", # ty:ignore[invalid-argument-type] ) diff --git a/tests/optimagic/optimization/test_convergence_report.py b/tests/optimagic/optimization/test_convergence_report.py index ea527f2bc..d3c0c3a7a 100644 --- a/tests/optimagic/optimization/test_convergence_report.py +++ b/tests/optimagic/optimization/test_convergence_report.py @@ -18,7 +18,9 @@ def test_get_convergence_report_minimize(): batches=[0, 1, 2, 3], ) - calculated = pd.DataFrame.from_dict(get_convergence_report(hist)) + report = get_convergence_report(hist) + assert report is not None + calculated = pd.DataFrame.from_dict(report) expected = np.array([[0.025, 0.25], [0.05, 1.0], [0.1, 1], [0.1, 2.0]]) aaae(calculated.to_numpy(), expected) @@ -35,7 +37,9 @@ def test_get_convergence_report_maximize(): batches=[0, 1, 2, 3], ) - calculated = pd.DataFrame.from_dict(get_convergence_report(hist)) + report = get_convergence_report(hist) + assert report is not None + calculated = pd.DataFrame.from_dict(report) expected = np.array([[0.025, 0.25], [0.05, 1.0], [0.1, 1], [0.1, 2.0]]) aaae(calculated.to_numpy(), expected) diff --git a/tests/optimagic/optimization/test_error_penalty.py b/tests/optimagic/optimization/test_error_penalty.py index d1100cf31..4fc4ac168 100644 --- a/tests/optimagic/optimization/test_error_penalty.py +++ b/tests/optimagic/optimization/test_error_penalty.py @@ -101,5 +101,5 @@ def test_penalty_aggregations_via_get_error_penalty(seed): contribs, _ = contribs_func(x) root_contribs, _ = root_contribs_func(x) - assert np.isclose(scalar.value, contribs.value.sum()) - assert np.isclose(scalar.value, (root_contribs.value**2).sum()) + assert np.isclose(scalar.value, contribs.value.sum()) # ty:ignore[unresolved-attribute] + assert np.isclose(scalar.value, (root_contribs.value**2).sum()) # ty:ignore[unresolved-attribute] diff --git a/tests/optimagic/optimization/test_history.py b/tests/optimagic/optimization/test_history.py index 4f8400150..f707dc750 100644 --- a/tests/optimagic/optimization/test_history.py +++ b/tests/optimagic/optimization/test_history.py @@ -67,7 +67,7 @@ def test_history_add_entry(history_entries): ] assert history.task == [EvalTask.FUN, EvalTask.FUN, EvalTask.FUN] assert history.batches == [0, 1, 2] - aaae(history.fun, [1, 3, 2]) + aaae(history.fun, [1, 3, 2]) # ty:ignore[invalid-argument-type] aaae(history.start_time, [0.1, 0.2, 0.3]) aaae(history.stop_time, [0.2, 0.3, 0.4]) @@ -90,7 +90,7 @@ def test_history_add_batch(history_entries): ] assert history.task == [EvalTask.FUN, EvalTask.FUN, EvalTask.FUN] assert history.batches == [0, 0, 0] - aaae(history.fun, [1, 3, 2]) + aaae(history.fun, [1, 3, 2]) # ty:ignore[invalid-argument-type] aaae(history.start_time, [0.1, 0.2, 0.3]) aaae(history.stop_time, [0.2, 0.3, 0.4]) @@ -112,7 +112,7 @@ def test_history_from_data(): history = History( direction=Direction.MAXIMIZE, - **data, + **data, # ty:ignore[invalid-argument-type] ) assert history.direction == Direction.MAXIMIZE @@ -120,7 +120,7 @@ def test_history_from_data(): assert history.params == data["params"] assert history.task == data["task"] assert history.batches == data["batches"] - aaae(history.fun, data["fun"]) + aaae(history.fun, data["fun"]) # ty:ignore[invalid-argument-type] aaae(history.start_time, data["start_time"]) aaae(history.stop_time, data["stop_time"]) @@ -496,7 +496,7 @@ def test_get_total_timings_invalid_cost_model(history: History): with pytest.raises( TypeError, match="cost_model must be a CostModel or 'wall_time'." ): - history._get_total_timings(cost_model="invalid") + history._get_total_timings(cost_model="invalid") # ty:ignore[invalid-argument-type] def test_start_time_property(history: History): @@ -576,14 +576,14 @@ def test_get_flat_param_names_fast_path(): def test_calculate_monotone_sequence_maximize(): sequence = [0, 1, 0, 0, 2, 10, 0] exp = [0, 1, 1, 1, 2, 10, 10] - got = _calculate_monotone_sequence(sequence, direction=Direction.MAXIMIZE) + got = _calculate_monotone_sequence(sequence, direction=Direction.MAXIMIZE) # ty:ignore[invalid-argument-type] assert_array_equal(exp, got) def test_calculate_monotone_sequence_minimize(): sequence = [10, 11, 8, 12, 0, 5] exp = [10, 10, 8, 8, 0, 0] - got = _calculate_monotone_sequence(sequence, direction=Direction.MINIMIZE) + got = _calculate_monotone_sequence(sequence, direction=Direction.MINIMIZE) # ty:ignore[invalid-argument-type] assert_array_equal(exp, got) @@ -640,5 +640,7 @@ def test_apply_to_batch_func_with_non_scalar_return(): batch_ids = [0, 0, 1, 1, 2] with pytest.raises(ValueError, match="Function did not return a scalar"): _apply_reduction_to_batches( - data, batch_ids, reduction_function=lambda _list: _list + data, + batch_ids, + reduction_function=lambda _list: _list, # ty:ignore[invalid-argument-type] ) diff --git a/tests/optimagic/optimization/test_history_collection.py b/tests/optimagic/optimization/test_history_collection.py index 0adb6a521..c51a94d4e 100644 --- a/tests/optimagic/optimization/test_history_collection.py +++ b/tests/optimagic/optimization/test_history_collection.py @@ -53,6 +53,7 @@ def test_history_collection_with_parallelization(algorithm, tmp_path): log_hist = reader.read_history() # We cannot expect the order to be the same + assert collected_hist is not None aaae(sorted(collected_hist.fun), sorted(log_hist.fun)) @@ -147,6 +148,7 @@ def test_history_collection_with_dummy_optimizer(n_cores, batch_size): ) got_history = res.history + assert got_history is not None expected_history = _get_fake_history(batch_size) diff --git a/tests/optimagic/optimization/test_internal_optimization_problem.py b/tests/optimagic/optimization/test_internal_optimization_problem.py index 0eeb217b6..9e3de30a8 100644 --- a/tests/optimagic/optimization/test_internal_optimization_problem.py +++ b/tests/optimagic/optimization/test_internal_optimization_problem.py @@ -69,7 +69,7 @@ def fun_and_jac(params): bounds=bounds, numdiff_options=numdiff_options, error_handling=error_handling, - error_penalty_func=None, + error_penalty_func=None, # ty:ignore[invalid-argument-type] batch_evaluator=batch_evaluator, linear_constraints=linear_constraints, nonlinear_constraints=nonlinear_constraints, @@ -477,7 +477,7 @@ def derivative_flatten(tree, x): bounds=bounds, numdiff_options=numdiff_options, error_handling=error_handling, - error_penalty_func=None, + error_penalty_func=None, # ty:ignore[invalid-argument-type] batch_evaluator=batch_evaluator, linear_constraints=linear_constraints, nonlinear_constraints=nonlinear_constraints, diff --git a/tests/optimagic/optimization/test_jax_derivatives.py b/tests/optimagic/optimization/test_jax_derivatives.py index 92433afae..6605fda5b 100644 --- a/tests/optimagic/optimization/test_jax_derivatives.py +++ b/tests/optimagic/optimization/test_jax_derivatives.py @@ -92,7 +92,7 @@ def ls_wrapper(x): fun=criterion, params=jnp.array([1.0, 2.0, 3.0]), algorithm=algorithm, - jac=deriv_dict, + jac=deriv_dict, # ty:ignore[invalid-argument-type] ) assert isinstance(res.params, jnp.ndarray) diff --git a/tests/optimagic/optimization/test_multistart.py b/tests/optimagic/optimization/test_multistart.py index a6a2f90e2..3edb14108 100644 --- a/tests/optimagic/optimization/test_multistart.py +++ b/tests/optimagic/optimization/test_multistart.py @@ -75,8 +75,8 @@ def with_step_id(self, step_id): return self calculated = run_explorations( - internal_problem=Dummy(), - sample=np.arange(6).reshape(3, 2), + internal_problem=Dummy(), # ty:ignore[invalid-argument-type] + sample=np.arange(6).reshape(3, 2), # ty:ignore[invalid-argument-type] n_cores=1, step_id=0, ) @@ -104,7 +104,7 @@ def test_get_batched_optimization_sample(): assert isinstance(calc_batch, list) for calc_entry, exp_entry in zip(calc_batch, exp_batch, strict=False): assert isinstance(calc_entry, np.ndarray) - assert calc_entry.tolist() == exp_entry + assert calc_entry.tolist() == exp_entry # ty:ignore[no-matching-overload] @pytest.fixture() @@ -130,7 +130,7 @@ def starts(): @pytest.fixture() def results(): res = InternalOptimizeResult( - x=np.arange(3) + 1e-10, + x=np.arange(3) + 1e-10, # ty:ignore[invalid-argument-type] fun=4, ) return [res] diff --git a/tests/optimagic/optimization/test_multistart_options.py b/tests/optimagic/optimization/test_multistart_options.py index a8f356fb8..cf3b3d439 100644 --- a/tests/optimagic/optimization/test_multistart_options.py +++ b/tests/optimagic/optimization/test_multistart_options.py @@ -40,17 +40,17 @@ def test_pre_process_multistart_dict_case(): def test_pre_process_multistart_invalid_type(): with pytest.raises(InvalidMultistartError, match="Invalid multistart options"): - pre_process_multistart(multistart="invalid") + pre_process_multistart(multistart="invalid") # ty:ignore[invalid-argument-type] def test_pre_process_multistart_invalid_dict_key(): with pytest.raises(InvalidMultistartError, match="Invalid multistart options"): - pre_process_multistart(multistart={"invalid": "invalid"}) + pre_process_multistart(multistart={"invalid": "invalid"}) # ty:ignore[invalid-argument-type, invalid-key] def test_pre_process_multistart_invalid_dict_value(): with pytest.raises(InvalidMultistartError, match="Invalid number of samples"): - pre_process_multistart(multistart={"n_samples": "invalid"}) + pre_process_multistart(multistart={"n_samples": "invalid"}) # ty:ignore[invalid-argument-type] @pytest.mark.parametrize("value", ["invalid", -1]) @@ -72,17 +72,17 @@ def test_multistart_options_stopping_maxopt_less_than_n_samples(): def test_multistart_options_invalid_sampling_distribution(): with pytest.raises(InvalidMultistartError, match="Invalid sampling distribution"): - MultistartOptions(sampling_distribution="invalid") + MultistartOptions(sampling_distribution="invalid") # ty:ignore[invalid-argument-type] def test_multistart_options_invalid_sampling_method(): with pytest.raises(InvalidMultistartError, match="Invalid sampling method"): - MultistartOptions(sampling_method="invalid") + MultistartOptions(sampling_method="invalid") # ty:ignore[invalid-argument-type] def test_multistart_options_invalid_mixing_weight_method(): with pytest.raises(InvalidMultistartError, match="Invalid mixing weight method"): - MultistartOptions(mixing_weight_method="invalid") + MultistartOptions(mixing_weight_method="invalid") # ty:ignore[invalid-argument-type] @pytest.mark.parametrize("value", [("a", "b"), (1, 2, 3), {"a": 1.0, "b": 3.0}]) @@ -93,7 +93,7 @@ def test_multistart_options_invalid_mixing_weight_bounds(value): def test_multistart_options_invalid_convergence_xtol_rel(): with pytest.raises(InvalidMultistartError, match="Invalid relative params"): - MultistartOptions(convergence_xtol_rel="invalid") + MultistartOptions(convergence_xtol_rel="invalid") # ty:ignore[invalid-argument-type] @pytest.mark.parametrize("value", ["invalid", -1]) @@ -121,17 +121,17 @@ def test_multistart_options_batch_size_smaller_than_n_cores(): def test_multistart_options_invalid_batch_evaluator(): with pytest.raises(InvalidMultistartError, match="Invalid batch evaluator"): - MultistartOptions(batch_evaluator="invalid") + MultistartOptions(batch_evaluator="invalid") # ty:ignore[invalid-argument-type] def test_multistart_options_invalid_seed(): with pytest.raises(InvalidMultistartError, match="Invalid seed"): - MultistartOptions(seed="invalid") + MultistartOptions(seed="invalid") # ty:ignore[invalid-argument-type] def test_multistart_options_invalid_error_handling(): with pytest.raises(InvalidMultistartError, match="Invalid error handling"): - MultistartOptions(error_handling="invalid") + MultistartOptions(error_handling="invalid") # ty:ignore[invalid-argument-type] def test_linear_weights(): diff --git a/tests/optimagic/optimization/test_optimize.py b/tests/optimagic/optimization/test_optimize.py index b666f2ac2..f74c307df 100644 --- a/tests/optimagic/optimization/test_optimize.py +++ b/tests/optimagic/optimization/test_optimize.py @@ -43,7 +43,7 @@ def test_with_invalid_numdiff_options(): fun=lambda x: x @ x, params=np.arange(5), algorithm="scipy_lbfgsb", - numdiff_options={"bla": 15}, + numdiff_options={"bla": 15}, # ty:ignore[invalid-argument-type, invalid-key] ) @@ -58,10 +58,11 @@ def test_with_optional_fun_argument(): aaae(res.x, expected) -def test_fun_and_jac_list(): +@pytest.mark.parametrize("container", [list, tuple]) +def test_fun_and_jac_sequence_without_fun(container): with pytest.raises(NotImplementedError): minimize( - fun_and_jac=[lambda x: (x @ x, 2 * x)], + fun_and_jac=container([lambda x: (x @ x, 2 * x)]), params=np.arange(5), algorithm="scipy_lbfgsb", ) diff --git a/tests/optimagic/optimization/test_with_constraints.py b/tests/optimagic/optimization/test_with_constraints.py index 71626404b..0f76dfd88 100644 --- a/tests/optimagic/optimization/test_with_constraints.py +++ b/tests/optimagic/optimization/test_with_constraints.py @@ -169,7 +169,7 @@ def logit_loglike(params, y, x): (crit_name, "scipy_lbfgsb", deriv, constr_name, ptype) ) - if "root_contributions" in FUNC_INFO[crit_name]["entries"]: + if "root_contributions" in FUNC_INFO[crit_name]["entries"]: # ty:ignore[unsupported-operator] for deriv in [FUNC_INFO[crit_name].get("ls_jacobian"), None]: test_cases.append( (crit_name, "scipy_ls_dogbox", deriv, constr_name, ptype) @@ -191,7 +191,7 @@ def test_constrained_minimization( params = np.array(START_INFO[constraint_name]) res = minimize( - fun=criterion, + fun=criterion, # ty:ignore[invalid-argument-type] params=params, algorithm=algorithm, jac=derivative, @@ -208,7 +208,7 @@ def test_constrained_minimization( f"{constraint_name}_result", FUNC_INFO[criterion_name]["default_result"] ) - aaae(calculated, expected, decimal=4) + aaae(calculated, expected, decimal=4) # ty:ignore[invalid-argument-type] @pytest.mark.filterwarnings("ignore:Specifying constraints as a dictionary is") @@ -250,6 +250,21 @@ def test_three_independent_constraints(): aaae(res.params, expected, decimal=3) +def test_constraints_as_tuple(): + constraints = ( + om.FixedConstraint(lambda x: x[[0]]), + om.IncreasingConstraint(lambda x: x[[1, 2]]), + ) + + res = minimize( + fun=lambda x: x @ x, + params=np.array([1.0, 2.0, 3.0]), + algorithm="scipy_lbfgsb", + constraints=constraints, + ) + aaae(res.params, [1, 0, 0], decimal=4) + + INVALID_CONSTRAINT_COMBIS = [ [ om.FlatCovConstraint(lambda x: x[[1, 0, 2]]), diff --git a/tests/optimagic/optimization/test_with_multistart.py b/tests/optimagic/optimization/test_with_multistart.py index bc4d083b1..f6007c665 100644 --- a/tests/optimagic/optimization/test_with_multistart.py +++ b/tests/optimagic/optimization/test_with_multistart.py @@ -79,6 +79,7 @@ def test_multistart_optimization_with_sum_of_squares_at_defaults( assert hasattr(res, "multistart_info") ms_info = res.multistart_info + assert ms_info is not None assert len(ms_info.exploration_sample) == 400 assert isinstance(ms_info.exploration_results, list) assert len(ms_info.exploration_results) == 400 @@ -100,10 +101,13 @@ def test_multistart_with_existing_sample(params): multistart=options, ) + assert res.multistart_info is not None assert all( got.equals(expected) for expected, got in zip( - sample, res.multistart_info.exploration_sample, strict=False + sample, + res.multistart_info.exploration_sample, + strict=False, ) ) @@ -121,6 +125,7 @@ def test_convergence_via_max_discoveries_works(params): multistart=options, ) + assert res.multistart_info is not None assert len(res.multistart_info.local_optima) == 2 @@ -146,7 +151,7 @@ def test_steps_are_logged_as_skipped_if_convergence(tmp_path, params): def test_all_steps_occur_in_optimization_iterations_if_no_convergence(params): options = om.MultistartOptions( - convergence_max_discoveries=np.inf, + convergence_max_discoveries=np.inf, # ty:ignore[invalid-argument-type] n_samples=10 * len(params), ) @@ -261,7 +266,7 @@ def ackley(x): } minimize( - **kwargs, + **kwargs, # ty:ignore[invalid-argument-type] algorithm="scipy_lbfgsb", multistart=om.MultistartOptions( n_samples=200, @@ -303,7 +308,7 @@ def ackley(x): } minimize( - **kwargs, + **kwargs, # ty:ignore[invalid-argument-type] algorithm="scipy_lbfgsb", multistart={ "n_samples": 200, diff --git a/tests/optimagic/optimization/test_with_nonlinear_constraints.py b/tests/optimagic/optimization/test_with_nonlinear_constraints.py index 6a5012a4b..b0f9a9412 100644 --- a/tests/optimagic/optimization/test_with_nonlinear_constraints.py +++ b/tests/optimagic/optimization/test_with_nonlinear_constraints.py @@ -161,7 +161,7 @@ def test_documentation_example(algorithm): selector=lambda x: x[:-1], value=1.0, ), - **kwargs, + **kwargs, # ty:ignore[invalid-argument-type] ) diff --git a/tests/optimagic/optimizers/_pounders/test_pounders_history.py b/tests/optimagic/optimizers/_pounders/test_pounders_history.py index f02d7df99..bd8bd69de 100644 --- a/tests/optimagic/optimizers/_pounders/test_pounders_history.py +++ b/tests/optimagic/optimizers/_pounders/test_pounders_history.py @@ -24,7 +24,7 @@ def test_add_entries_not_initialized(entries, is_center): if is_center: c_info = {"x": np.zeros(3), "residuals": np.zeros(5), "radius": 1} - history.add_centered_entries(*entries, c_info) + history.add_centered_entries(*entries, c_info) # ty:ignore[too-many-positional-arguments] else: history.add_entries(*entries) @@ -51,7 +51,7 @@ def test_add_entries_initialized_with_space(entries, is_center): if is_center: c_info = {"x": np.zeros(3), "residuals": np.zeros(5), "radius": 1} - history.add_centered_entries(*entries, c_info) + history.add_centered_entries(*entries, c_info) # ty:ignore[too-many-positional-arguments] else: history.add_entries(*entries) @@ -74,6 +74,9 @@ def test_add_entries_initialized_with_space(entries, is_center): def test_add_entries_initialized_extension_needed(): history = LeastSquaresHistory() history.add_entries(np.ones((4, 3)), np.zeros((4, 5))) + assert history.xs is not None + assert history.residuals is not None + assert history.critvals is not None history.xs = history.xs[:5] history.residuals = history.residuals[:5] history.critvals = history.critvals[:5] @@ -131,3 +134,10 @@ def test_get_centered_entries(): aaae(residuals, np.arange(1, -4, -1)) assert critvals == 15 assert history.get_n_fun() == 4 + + +@pytest.mark.parametrize("getter", ["get_entries", "get_xs", "get_residuals"]) +def test_get_entries_from_empty_history_raises(getter): + history = LeastSquaresHistory() + with pytest.raises(ValueError, match="No entries have been added"): + getattr(history, getter)() diff --git a/tests/optimagic/optimizers/test_bayesian_optimizer.py b/tests/optimagic/optimizers/test_bayesian_optimizer.py index 39bab2df5..1e4d69712 100644 --- a/tests/optimagic/optimizers/test_bayesian_optimizer.py +++ b/tests/optimagic/optimizers/test_bayesian_optimizer.py @@ -149,7 +149,7 @@ def test_process_acquisition_function_invalid_type(): """Test processing invalid acquisition function type.""" with pytest.raises(TypeError, match="acquisition_function must be None, a string"): _process_acquisition_function( - acquisition_function=123, + acquisition_function=123, # ty:ignore[invalid-argument-type] kappa=2.576, xi=0.01, exploration_decay=None, diff --git a/tests/optimagic/optimizers/test_gfo_optimizers.py b/tests/optimagic/optimizers/test_gfo_optimizers.py index 4601bf0c2..9d9a31532 100644 --- a/tests/optimagic/optimizers/test_gfo_optimizers.py +++ b/tests/optimagic/optimizers/test_gfo_optimizers.py @@ -48,7 +48,7 @@ def test_get_search_space_gfo(): "x0": 5, "x1": 5, } - got = _get_search_space_gfo(bounds, n_grid_points, problem.converter) + got = _get_search_space_gfo(bounds, n_grid_points, problem.converter) # ty:ignore[invalid-argument-type] expected = { "x0": np.array([-10.0, -5.0, 0.0, 5.0, 10.0]), "x1": np.array([-10.0, -5.0, 0.0, 5.0, 10.0]), diff --git a/tests/optimagic/optimizers/test_iminuit_migrad.py b/tests/optimagic/optimizers/test_iminuit_migrad.py index 48e435ef4..89dc56a4e 100644 --- a/tests/optimagic/optimizers/test_iminuit_migrad.py +++ b/tests/optimagic/optimizers/test_iminuit_migrad.py @@ -91,5 +91,5 @@ def test_iminuit_migrad(): assert res.success aaae(res.x, np.zeros(3), decimal=6) - assert res.n_fun_evals > 0 - assert res.n_jac_evals > 0 + assert res.n_fun_evals is not None and res.n_fun_evals > 0 + assert res.n_jac_evals is not None and res.n_jac_evals > 0 diff --git a/tests/optimagic/optimizers/test_pyswarms_optimizers.py b/tests/optimagic/optimizers/test_pyswarms_optimizers.py index 02ce85a8c..d3c3bb22b 100644 --- a/tests/optimagic/optimizers/test_pyswarms_optimizers.py +++ b/tests/optimagic/optimizers/test_pyswarms_optimizers.py @@ -181,11 +181,11 @@ def test_resolve_topology_config_by_instance( def test_resolve_topology_config_invalid_string(): """Test topology resolution with invalid string.""" with pytest.raises(ValueError, match="Unknown topology string: 'invalid'"): - _resolve_topology_config("invalid") + _resolve_topology_config("invalid") # ty:ignore[invalid-argument-type] @pytest.mark.skipif(not IS_PYSWARMS_INSTALLED, reason="PySwarms not installed") def test_resolve_topology_config_invalid_type(): """Test topology resolution with invalid type.""" with pytest.raises(TypeError, match="Unsupported topology configuration type"): - _resolve_topology_config(123) + _resolve_topology_config(123) # ty:ignore[invalid-argument-type] diff --git a/tests/optimagic/parameters/constraints/test_resolution.py b/tests/optimagic/parameters/constraints/test_resolution.py index 829fd07f9..48087214b 100644 --- a/tests/optimagic/parameters/constraints/test_resolution.py +++ b/tests/optimagic/parameters/constraints/test_resolution.py @@ -39,7 +39,7 @@ def tree_params_converter(tree_params): tree_leaves(params, namespace=PyTreeNamespace.VALUE) ), params_unflatten=lambda x: tree_unflatten(treedef, x.tolist()), - derivative_flatten=None, + derivative_flatten=None, # ty:ignore[invalid-argument-type] ) return converter @@ -116,9 +116,9 @@ def test_provenance_is_attached(np_params_converter): param_names=PARAM_NAMES, ) for position, resolved in enumerate(calculated): - assert len(resolved.sources) == 1 - assert resolved.sources[0].position == position - assert resolved.sources[0].constraint is constraints[position] + assert len(resolved.sources) == 1 # ty:ignore[unresolved-attribute] + assert resolved.sources[0].position == position # ty:ignore[unresolved-attribute] + assert resolved.sources[0].constraint is constraints[position] # ty:ignore[unresolved-attribute] def test_empty_selections_are_dropped(np_params_converter): diff --git a/tests/optimagic/parameters/test_bounds.py b/tests/optimagic/parameters/test_bounds.py index 3c2ae9a62..a6c3f487a 100644 --- a/tests/optimagic/parameters/test_bounds.py +++ b/tests/optimagic/parameters/test_bounds.py @@ -42,6 +42,7 @@ def test_pre_process_bounds_none_case(): def test_pre_process_bounds_sequence(): got = pre_process_bounds([(0, 1), (None, 1)]) expected = Bounds(lower=[0, -np.inf], upper=[1, 1]) + assert got is not None assert_array_equal(got.lower, expected.lower) assert_array_equal(got.upper, expected.upper) @@ -64,6 +65,8 @@ def test_get_bounds_subdataframe(pytree_params): lb, ub = get_internal_bounds(pytree_params, bounds=bounds) + assert lb is not None + assert ub is not None assert np.all(lb[1:3] == np.ones(2)) assert np.all(ub[2:4] == 2 * np.ones(2)) diff --git a/tests/optimagic/parameters/test_nonlinear_constraints.py b/tests/optimagic/parameters/test_nonlinear_constraints.py index 964bac727..33a7dcff7 100644 --- a/tests/optimagic/parameters/test_nonlinear_constraints.py +++ b/tests/optimagic/parameters/test_nonlinear_constraints.py @@ -258,7 +258,7 @@ def test_process_nonlinear_constraints(): assert g["n_constr"] == e["n_constr"] for value in [0.1, 0.2, 1.2, -2.0]: x = np.array([value]) - assert_array_equal(g["fun"](x), e["fun"](x)) + assert_array_equal(g["fun"](x), e["fun"](x)) # ty:ignore[call-non-callable] assert "jac" in g assert "tol" in g diff --git a/tests/optimagic/parameters/test_scale_conversion.py b/tests/optimagic/parameters/test_scale_conversion.py index 0b64da03a..c7fdd9cc9 100644 --- a/tests/optimagic/parameters/test_scale_conversion.py +++ b/tests/optimagic/parameters/test_scale_conversion.py @@ -47,6 +47,8 @@ def test_get_scale_converter_active(method, expected): ) aaae(scaled.values, expected.values) + assert scaled.lower_bounds is not None + assert scaled.upper_bounds is not None aaae(scaled.lower_bounds, expected.lower_bounds) aaae(scaled.upper_bounds, expected.upper_bounds) diff --git a/tests/optimagic/parameters/test_scaling.py b/tests/optimagic/parameters/test_scaling.py index 13be92823..a91db082b 100644 --- a/tests/optimagic/parameters/test_scaling.py +++ b/tests/optimagic/parameters/test_scaling.py @@ -39,32 +39,32 @@ def test_pre_process_scaling_dict_case(): def test_pre_process_scaling_invalid_type(): with pytest.raises(InvalidScalingError, match="Invalid scaling options"): - pre_process_scaling(scaling="invalid") + pre_process_scaling(scaling="invalid") # ty:ignore[invalid-argument-type] def test_pre_process_scaling_invalid_dict_key(): with pytest.raises(InvalidScalingError, match="Invalid scaling options of type:"): - pre_process_scaling(scaling={"wrong_key": "start_values"}) + pre_process_scaling(scaling={"wrong_key": "start_values"}) # ty:ignore[invalid-argument-type, invalid-key] def test_pre_process_scaling_invalid_dict_value(): with pytest.raises(InvalidScalingError, match="Invalid clipping value:"): - pre_process_scaling(scaling={"clipping_value": "invalid"}) + pre_process_scaling(scaling={"clipping_value": "invalid"}) # ty:ignore[invalid-argument-type] def test_scaling_options_invalid_method_value(): with pytest.raises(InvalidScalingError, match="Invalid scaling method:"): - ScalingOptions(method="invalid") + ScalingOptions(method="invalid") # ty:ignore[invalid-argument-type] def test_scaling_options_invalid_clipping_value_type(): with pytest.raises(InvalidScalingError, match="Invalid clipping value:"): - ScalingOptions(clipping_value="invalid") + ScalingOptions(clipping_value="invalid") # ty:ignore[invalid-argument-type] def test_scaling_options_invalid_magnitude_value_type(): with pytest.raises(InvalidScalingError, match="Invalid scaling magnitude:"): - ScalingOptions(magnitude="invalid") + ScalingOptions(magnitude="invalid") # ty:ignore[invalid-argument-type] def test_scaling_options_invalid_magnitude_value_range(): diff --git a/tests/optimagic/parameters/test_space_conversion.py b/tests/optimagic/parameters/test_space_conversion.py index 0c4b22365..30143078e 100644 --- a/tests/optimagic/parameters/test_space_conversion.py +++ b/tests/optimagic/parameters/test_space_conversion.py @@ -214,7 +214,7 @@ def _get_test_case_normalized_covariance(): internal = InternalParams( values=np.array([0.05, 1.4133294025, 0.1, 0.2087269956, 1.7165177078, 10]), - lower_bounds=[-np.inf, 0, -np.inf, -np.inf, 0, -np.inf], + lower_bounds=[-np.inf, 0, -np.inf, -np.inf, 0, -np.inf], # ty:ignore[invalid-argument-type] upper_bounds=np.full(6, np.inf), names=None, ) diff --git a/tests/optimagic/test_batch_evaluators.py b/tests/optimagic/test_batch_evaluators.py index aa17cce6c..d71791ede 100644 --- a/tests/optimagic/test_batch_evaluators.py +++ b/tests/optimagic/test_batch_evaluators.py @@ -101,13 +101,13 @@ def test_batch_evaluator_with_dict_unpacking(batch_evaluator, n_cores): def test_get_batch_evaluator_invalid_value(): with pytest.raises(ValueError): - process_batch_evaluator("bla") + process_batch_evaluator("bla") # ty:ignore[invalid-argument-type] def test_get_batch_evaluator_invalid_type(): with pytest.raises(TypeError): - process_batch_evaluator(3) + process_batch_evaluator(3) # ty:ignore[invalid-argument-type] def test_get_batch_evaluator_with_callable(): - assert callable(process_batch_evaluator(lambda x: x)) + assert callable(process_batch_evaluator(lambda x: x)) # ty:ignore[invalid-argument-type] diff --git a/tests/optimagic/test_constraints.py b/tests/optimagic/test_constraints.py index bdc287b80..b45b126f7 100644 --- a/tests/optimagic/test_constraints.py +++ b/tests/optimagic/test_constraints.py @@ -257,6 +257,7 @@ def test_resolve_empty_selection_returns_none(constraint): def test_resolve_fixed_constraint_has_no_explicit_value(): constr = FixedConstraint(selector=lambda x: x[[0, 2]]) resolved = constr._resolve(make_context(constr)) + assert isinstance(resolved, ResolvedFixedConstraint) assert resolved.value is None @@ -306,6 +307,7 @@ def test_resolve_linear_constraint_aligns_weight_sequence(): selector=lambda x: x[[0, 2, 4]], weights=[1, 2, 3], upper_bound=5 ) resolved = constr._resolve(make_context(constr)) + assert isinstance(resolved, ResolvedLinearConstraint) aae(resolved.index, np.array([0, 2, 4])) aae(resolved.weights, np.array([1.0, 2.0, 3.0])) @@ -313,6 +315,7 @@ def test_resolve_linear_constraint_aligns_weight_sequence(): def test_resolve_linear_constraint_fills_absent_bounds_with_sentinels(): constr = LinearConstraint(selector=lambda x: x[[0, 2]], weights=1, lower_bound=1) resolved = constr._resolve(make_context(constr)) + assert isinstance(resolved, ResolvedLinearConstraint) assert resolved.lower_bound == 1 assert resolved.upper_bound == np.inf assert np.isnan(resolved.value) diff --git a/tests/optimagic/test_deprecations.py b/tests/optimagic/test_deprecations.py index c5e40096a..9f95ffd8d 100644 --- a/tests/optimagic/test_deprecations.py +++ b/tests/optimagic/test_deprecations.py @@ -160,8 +160,7 @@ def _crit(params): def test_estimagic_log_reader_is_deprecated(example_db): - msg = "OptimizeLogReader is deprecated and will be removed in a future " - "version. Please use optimagic.logging.SQLiteLogger instead." + msg = "estimagic.OptimizeLogReader has been deprecated" with pytest.warns(FutureWarning, match=msg): OptimizeLogReader(example_db) @@ -565,8 +564,8 @@ def test_old_bounds_are_deprecated_in_slice_plot(): om.slice_plot( lambda x: x @ x, np.arange(3), - lower_bounds=np.full(3, -1), - upper_bounds=np.full(3, 2), + lower_bounds=np.full(3, -1), # ty:ignore[invalid-argument-type] + upper_bounds=np.full(3, 2), # ty:ignore[invalid-argument-type] ) @@ -706,6 +705,7 @@ def test_deprecated_dict_access_of_multistart_info(): bounds=om.Bounds(lower=np.full(3, -1), upper=np.full(3, 2)), ) msg = "The dictionary access for 'local_optima' is deprecated and will be removed" + assert res.multistart_info is not None with pytest.warns(FutureWarning, match=msg): _ = res.multistart_info["local_optima"] @@ -808,7 +808,7 @@ def test_jac_dicts_are_deprecated_in_minimize(): lambda x: x @ x, np.arange(3), algorithm="scipy_lbfgsb", - jac={"value": lambda x: 2 * x}, + jac={"value": lambda x: 2 * x}, # ty:ignore[invalid-argument-type] ) aaae(res.params, np.zeros(3)) @@ -820,7 +820,7 @@ def test_jac_dicts_are_deprecated_in_maximize(): lambda x: -x @ x, np.arange(3), algorithm="scipy_lbfgsb", - jac={"value": lambda x: -2 * x}, + jac={"value": lambda x: -2 * x}, # ty:ignore[invalid-argument-type] ) aaae(res.params, np.zeros(3)) @@ -832,7 +832,7 @@ def test_fun_and_jac_dicts_are_deprecated_in_minimize(): lambda x: x @ x, np.arange(3), algorithm="scipy_lbfgsb", - fun_and_jac={"value": lambda x: (x @ x, 2 * x)}, + fun_and_jac={"value": lambda x: (x @ x, 2 * x)}, # ty:ignore[invalid-argument-type] ) aaae(res.params, np.zeros(3)) @@ -844,7 +844,7 @@ def test_fun_and_jac_dicts_are_deprecated_in_maximize(): lambda x: -x @ x, np.arange(3), algorithm="scipy_lbfgsb", - fun_and_jac={"value": lambda x: (-x @ x, -2 * x)}, + fun_and_jac={"value": lambda x: (-x @ x, -2 * x)}, # ty:ignore[invalid-argument-type] ) aaae(res.params, np.zeros(3)) @@ -1085,6 +1085,18 @@ def test_pre_process_constraints_list_of_constraints(dummy_func): assert pre_process_constraints(constraints) == expected +def test_pre_process_constraints_tuple_of_constraints(dummy_func): + constraints = ( + om.FixedConstraint(selector=dummy_func), + {"type": "increasing", "selector": dummy_func}, + ) + expected = [ + om.FixedConstraint(selector=dummy_func), + om.IncreasingConstraint(selector=dummy_func), + ] + assert pre_process_constraints(constraints) == expected + + def test_pre_process_constraints_none_case(): assert pre_process_constraints(None) == [] @@ -1111,7 +1123,7 @@ def test_pre_process_constraints_invalid_case(): constraints = "invalid" msg = "Invalid constraint type: " with pytest.raises(InvalidConstraintError, match=msg): - pre_process_constraints(constraints) + pre_process_constraints(constraints) # ty:ignore[invalid-argument-type] def test_pre_process_constraints_invalid_mixed_case(): @@ -1122,7 +1134,7 @@ def test_pre_process_constraints_invalid_mixed_case(): ] msg = "Invalid constraint types: {}" with pytest.raises(InvalidConstraintError, match=msg): - pre_process_constraints(constraints) + pre_process_constraints(constraints) # ty:ignore[invalid-argument-type] # ====================================================================================== @@ -1206,7 +1218,7 @@ def test_nonlinear_dict_without_selection_field_selects_all_params(): func = lambda x: x @ x # noqa: E731 got = pre_process_constraints([{"type": "nonlinear", "func": func, "value": 1}]) params = np.arange(3) - assert got[0].selector(params) is params + assert got[0].selector(params) is params # ty:ignore[unresolved-attribute] def test_nonlinear_dict_loc_selector_has_no_value_indexing(df_params): @@ -1221,7 +1233,7 @@ def test_nonlinear_dict_loc_selector_has_no_value_indexing(df_params): } ] ) - selected = got[0].selector(df_params) + selected = got[0].selector(df_params) # ty:ignore[unresolved-attribute] pd.testing.assert_frame_equal(selected, df_params.loc[["b", "e"]]) @@ -1230,7 +1242,7 @@ def test_pairwise_equality_dict_with_selectors_is_converted(): got = pre_process_constraints( [{"type": "pairwise_equality", "selectors": selectors}] ) - assert got == [om.PairwiseEqualityConstraint(selectors=selectors)] + assert got == [om.PairwiseEqualityConstraint(selectors=selectors)] # ty:ignore[invalid-argument-type] def test_loc_on_numpy_params(): diff --git a/tests/optimagic/test_pytree.py b/tests/optimagic/test_pytree.py index ca7c727c7..a9f3be2ca 100644 --- a/tests/optimagic/test_pytree.py +++ b/tests/optimagic/test_pytree.py @@ -275,14 +275,14 @@ def test_tree_equal_runs_raising_checkers_on_all_leaves(): first = {"a": np.array([1.0]), "b": np.array([2.0])} second = {"a": np.array([1.0]), "b": np.array([99.0])} with pytest.raises(AssertionError): - tree_equal(first, second, equality_checkers=checkers) + tree_equal(first, second, equality_checkers=checkers) # ty:ignore[invalid-argument-type] def test_tree_equal_returns_bool_with_none_returning_checkers(): checkers = {np.ndarray: lambda x, y: aaae(x, y, decimal=5)} first = {"a": np.array([1.0]), "b": np.array([2.0])} second = {"a": np.array([1.0]), "b": np.array([2.0])} - assert tree_equal(first, second, equality_checkers=checkers) is True + assert tree_equal(first, second, equality_checkers=checkers) is True # ty:ignore[invalid-argument-type] @pytest.mark.parametrize("namespace", list(PyTreeNamespace)) diff --git a/tests/optimagic/test_timing.py b/tests/optimagic/test_timing.py index fd2edfc3c..a45a0441b 100644 --- a/tests/optimagic/test_timing.py +++ b/tests/optimagic/test_timing.py @@ -10,5 +10,5 @@ def test_invalid_aggregate_batch_time(): jac=None, fun_and_jac=None, label="label", - aggregate_batch_time="Not callable", + aggregate_batch_time="Not callable", # ty:ignore[invalid-argument-type] ) diff --git a/tests/optimagic/test_typing.py b/tests/optimagic/test_typing.py index a18bd0b4c..cac1512b4 100644 --- a/tests/optimagic/test_typing.py +++ b/tests/optimagic/test_typing.py @@ -43,7 +43,7 @@ class StrictOptions: def test_values_are_coerced_to_annotated_types(): - options = Options(n_points="2", share=1) + options = Options(n_points="2", share=1) # ty:ignore[invalid-argument-type] assert isinstance(options.n_points, int) assert options.n_points == 2 assert isinstance(options.share, float) @@ -52,7 +52,7 @@ def test_values_are_coerced_to_annotated_types(): def test_fractional_float_for_int_field_raises(): with pytest.raises(CustomError): - Options(n_points=2.5) + Options(n_points=2.5) # ty:ignore[invalid-argument-type] def test_constraint_violations_raise_the_translated_error(): @@ -68,12 +68,12 @@ def test_original_validation_error_is_chained(): def test_unknown_keyword_arguments_raise_the_translated_error(): with pytest.raises(CustomError): - Options(this_is_not_an_option=1) + Options(this_is_not_an_option=1) # ty:ignore[unknown-argument] def test_all_invalid_fields_are_reported_at_once(): with pytest.raises(CustomError, match="(?s)n_points.*share"): - Options(n_points=0, share="not a number") + Options(n_points=0, share="not a number") # ty:ignore[invalid-argument-type] def test_defaults_are_validated(): @@ -90,7 +90,7 @@ def test_defaults_are_coerced(): @validated_dataclass(config=DEFAULT_PYDANTIC_CONFIG, make_error=_make_error) @dataclass(frozen=True) class CoercibleDefault: - n_points: PositiveInt = 2.0 # type: ignore[assignment] + n_points: PositiveInt = 2.0 # ty:ignore[invalid-assignment] assert isinstance(CoercibleDefault().n_points, int) assert CoercibleDefault().n_points == 2 @@ -106,7 +106,7 @@ def test_replace_revalidates(): def test_decorated_class_is_frozen(): options = Options() with pytest.raises(FrozenInstanceError): - options.n_points = 2 + options.n_points = 2 # ty:ignore[invalid-assignment] def test_docstring_and_annotations_are_preserved(): @@ -125,7 +125,7 @@ class Base: class Child(Base): share: float = 0.5 - assert Child(n_points="2").n_points == 2 + assert Child(n_points="2").n_points == 2 # ty:ignore[invalid-argument-type] with pytest.raises(CustomError): Child(n_points=0) @@ -133,6 +133,6 @@ class Child(Base): def test_strict_config_rejects_values_that_need_conversion(): assert StrictOptions(n_points=2, label="b").n_points == 2 with pytest.raises(CustomError): - StrictOptions(n_points="2") + StrictOptions(n_points="2") # ty:ignore[invalid-argument-type] with pytest.raises(CustomError): - StrictOptions(label=3) + StrictOptions(label=3) # ty:ignore[invalid-argument-type] diff --git a/tests/optimagic/visualization/test_backends.py b/tests/optimagic/visualization/test_backends.py index a46fd59c9..862abb285 100644 --- a/tests/optimagic/visualization/test_backends.py +++ b/tests/optimagic/visualization/test_backends.py @@ -25,7 +25,7 @@ def test_line_plot_all_backends(sample_lines, backend, close_mpl_figures): def test_line_plot_invalid_backend(sample_lines): with pytest.raises(InvalidPlottingBackendError): - line_plot(sample_lines, backend="bla") + line_plot(sample_lines, backend="bla") # ty:ignore[invalid-argument-type] def test_line_plot_unavailable_backend(sample_lines, monkeypatch): diff --git a/tests/optimagic/visualization/test_convergence_plot.py b/tests/optimagic/visualization/test_convergence_plot.py index 931786e30..28290ef68 100644 --- a/tests/optimagic/visualization/test_convergence_plot.py +++ b/tests/optimagic/visualization/test_convergence_plot.py @@ -80,7 +80,7 @@ def test_convergence_plot_stopping_criterion_none(benchmark_results): problems=problems, results=results, problem_subset=["bard_good_start"], - stopping_criterion=None, + stopping_criterion=None, # ty:ignore[invalid-argument-type] ) diff --git a/tests/optimagic/visualization/test_history_plots.py b/tests/optimagic/visualization/test_history_plots.py index 1680b66e7..7078223c9 100644 --- a/tests/optimagic/visualization/test_history_plots.py +++ b/tests/optimagic/visualization/test_history_plots.py @@ -40,7 +40,7 @@ def minimize_result(): multistart=( om.MultistartOptions(n_samples=1000, convergence_max_discoveries=5) if multistart - else None + else False ), ) res.append(_res) @@ -109,7 +109,7 @@ def test_criterion_plot_name_input(minimize_result): def test_criterion_plot_wrong_results(): with pytest.raises(TypeError): - criterion_plot([10, np.array([1, 2, 3])]) + criterion_plot([10, np.array([1, 2, 3])]) # ty:ignore[invalid-argument-type] def test_criterion_plot_different_input_types(): @@ -139,12 +139,13 @@ def test_criterion_plot_different_input_types(): criterion_plot(results, stack_multistart=True) criterion_plot(results, monotone=True, stack_multistart=True) criterion_plot(results, show_exploration=True) + criterion_plot(results, stack_multistart=True, show_exploration=True) criterion_plot("test.db") def test_criterion_plot_wrong_inputs(): with pytest.raises(ValueError): - criterion_plot("bla", names=[1, 2]) + criterion_plot("bla", names=[1, 2]) # ty:ignore[invalid-argument-type] with pytest.raises(ValueError): criterion_plot(["bla", "bla"], names="blub") @@ -181,6 +182,12 @@ def test_harmonize_inputs_to_dict_list_results(): } +def test_harmonize_inputs_to_dict_tuple_results(): + res = minimize(fun=lambda x: x @ x, params=np.arange(5), algorithm="scipy_lbfgsb") + got = _harmonize_inputs_to_dict(results=(res, res), names=["a", "b"]) + assert got == {"a": res, "b": res} + + def test_harmonize_inputs_to_dict_dict_input(): res = minimize(fun=lambda x: x @ x, params=np.arange(5), algorithm="scipy_lbfgsb") results = {"bla": res, om.algos.scipy_lbfgsb(): res, om.algos.scipy_neldermead: res} @@ -201,7 +208,7 @@ def test_harmonize_inputs_to_dict_invalid_names(): results = [None] names = ["a", "b"] with pytest.raises(ValueError): - _harmonize_inputs_to_dict(results=results, names=names) + _harmonize_inputs_to_dict(results=results, names=names) # ty:ignore[invalid-argument-type] def test_harmonize_inputs_to_dict_str_input(): @@ -216,6 +223,7 @@ def test_harmonize_inputs_to_dict_path_input(): def _compare_plotting_multistart_history_with_result( data: _PlottingMultistartHistory, res: om.OptimizeResult, res_name: str ): + assert res.history is not None assert_array_equal(data.history.fun, res.history.fun) assert data.name == res_name assert_array_equal(data.start_params, res.start_params) @@ -272,9 +280,11 @@ def test_retrieve_data_from_multistart_result(minimize_result, stack_multistart) assert isinstance(data, list) and len(data) == 1 assert data[0].is_multistart + assert data[0].local_histories is not None assert len(data[0].local_histories) == 5 if stack_multistart: + assert data[0].stacked_local_histories is not None assert_array_equal( data[0].stacked_local_histories.fun, np.concatenate([hist.fun for hist in data[0].local_histories]), From 4db800b7f8b24b0388daa59ed3dc01f8b47d7aa7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2026 07:32:24 +0000 Subject: [PATCH 2/2] Bump prefix-dev/setup-pixi from 0.9.4 to 0.10.2 Bumps [prefix-dev/setup-pixi](https://github.com/prefix-dev/setup-pixi) from 0.9.4 to 0.10.2. - [Release notes](https://github.com/prefix-dev/setup-pixi/releases) - [Commits](https://github.com/prefix-dev/setup-pixi/compare/a0af7a228712d6121d37aba47adf55c1332c9c2e...d3f436a425481402e6a95a1d1fc10331c708cd9e) --- updated-dependencies: - dependency-name: prefix-dev/setup-pixi dependency-version: 0.10.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/main.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index eb115397e..664fa15ed 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: persist-credentials: false - - uses: prefix-dev/setup-pixi@a0af7a228712d6121d37aba47adf55c1332c9c2e # v0.9.4 + - uses: prefix-dev/setup-pixi@d3f436a425481402e6a95a1d1fc10331c708cd9e # v0.10.2 with: pixi-version: v0.71.2 cache: true @@ -59,7 +59,7 @@ jobs: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: persist-credentials: false - - uses: prefix-dev/setup-pixi@a0af7a228712d6121d37aba47adf55c1332c9c2e # v0.9.4 + - uses: prefix-dev/setup-pixi@d3f436a425481402e6a95a1d1fc10331c708cd9e # v0.10.2 with: pixi-version: v0.71.2 cache: true @@ -76,7 +76,7 @@ jobs: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: persist-credentials: false - - uses: prefix-dev/setup-pixi@a0af7a228712d6121d37aba47adf55c1332c9c2e # v0.9.4 + - uses: prefix-dev/setup-pixi@d3f436a425481402e6a95a1d1fc10331c708cd9e # v0.10.2 with: pixi-version: v0.71.2 cache: true @@ -100,7 +100,7 @@ jobs: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: persist-credentials: false - - uses: prefix-dev/setup-pixi@a0af7a228712d6121d37aba47adf55c1332c9c2e # v0.9.4 + - uses: prefix-dev/setup-pixi@d3f436a425481402e6a95a1d1fc10331c708cd9e # v0.10.2 with: pixi-version: v0.71.2 cache: true @@ -119,7 +119,7 @@ jobs: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: persist-credentials: false - - uses: prefix-dev/setup-pixi@a0af7a228712d6121d37aba47adf55c1332c9c2e # v0.9.4 + - uses: prefix-dev/setup-pixi@d3f436a425481402e6a95a1d1fc10331c708cd9e # v0.10.2 with: pixi-version: v0.71.2 cache: true @@ -138,7 +138,7 @@ jobs: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: persist-credentials: false - - uses: prefix-dev/setup-pixi@a0af7a228712d6121d37aba47adf55c1332c9c2e # v0.9.4 + - uses: prefix-dev/setup-pixi@d3f436a425481402e6a95a1d1fc10331c708cd9e # v0.10.2 with: pixi-version: v0.71.2 cache: true