Skip to content

Commit

Permalink
Merge branch 'main' into bootstrap_weights
Browse files Browse the repository at this point in the history
  • Loading branch information
timmens authored Nov 18, 2024
2 parents 79f2d83 + 84be118 commit 8f976ab
Show file tree
Hide file tree
Showing 15 changed files with 205 additions and 64 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ jobs:
- '3.10'
- '3.11'
- '3.12'
- '3.13'
steps:
- uses: actions/checkout@v4
- name: create build environment
Expand Down Expand Up @@ -55,6 +56,8 @@ jobs:
python-version:
- '3.10'
- '3.11'
- '3.12'
- '3.13'
steps:
- uses: actions/checkout@v4
- name: create build environment
Expand Down
45 changes: 25 additions & 20 deletions .tools/create_algo_selection_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def _get_algorithms_in_module(module: ModuleType) -> dict[str, Type[Algorithm]]:
}
algos = {}
for candidate in candidate_dict.values():
name = candidate.__algo_info__.name
name = candidate.algo_info.name
if issubclass(candidate, Algorithm) and candidate is not Algorithm:
algos[name] = candidate
return algos
Expand All @@ -119,47 +119,47 @@ 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 # type: ignore


def _is_gradient_free(algo: Type[Algorithm]) -> bool:
return not _is_gradient_based(algo)


def _is_global(algo: Type[Algorithm]) -> bool:
return algo.__algo_info__.is_global # type: ignore
return algo.algo_info.is_global # type: ignore


def _is_local(algo: Type[Algorithm]) -> bool:
return not _is_global(algo)


def _is_bounded(algo: Type[Algorithm]) -> bool:
return algo.__algo_info__.supports_bounds # type: ignore
return algo.algo_info.supports_bounds # type: ignore


def _is_linear_constrained(algo: Type[Algorithm]) -> bool:
return algo.__algo_info__.supports_linear_constraints # type: ignore
return algo.algo_info.supports_linear_constraints # type: ignore


def _is_nonlinear_constrained(algo: Type[Algorithm]) -> bool:
return algo.__algo_info__.supports_nonlinear_constraints # type: ignore
return algo.algo_info.supports_nonlinear_constraints # type: ignore


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 # type: ignore


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 # type: ignore


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 # type: ignore


def _is_parallel(algo: Type[Algorithm]) -> bool:
return algo.__algo_info__.supports_parallelism # type: ignore
return algo.algo_info.supports_parallelism # type: ignore


def _get_filters() -> dict[str, Callable[[Type[Algorithm]], bool]]:
Expand Down Expand Up @@ -385,27 +385,32 @@ 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 # type: ignore
]
@property
def All(self) -> list[str]:
return [a.__algo_info__.name for a in self._all()] # type: ignore
def All(self) -> list[Type[Algorithm]]:
return self._all()
@property
def Available(self) -> list[str]:
return [a.__algo_info__.name for a in self._available()] # type: ignore
def Available(self) -> list[Type[Algorithm]]:
return self._available()
@property
def AllNames(self) -> list[str]:
return [str(a.name) for a in self._all()]
@property
def AvailableNames(self) -> list[str]:
return [str(a.name) for a in self._available()]
@property
def _all_algorithms_dict(self) -> dict[str, Type[Algorithm]]:
return {a.__algo_info__.name: a for a in self._all()} # type: ignore
return {str(a.name): a for a in self._all()}
@property
def _available_algorithms_dict(self) -> dict[str, Type[Algorithm]]:
return {
a.__algo_info__.name: a # type: ignore
for a in self._available()
}
return {str(a.name): a for a in self._available()}
""")
return out
Expand Down
22 changes: 20 additions & 2 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,27 @@ This is a record of all past optimagic releases and what went into them in rever
chronological order. We follow [semantic versioning](https://semver.org/) and all
releases are available on [Anaconda.org](https://anaconda.org/optimagic-dev/optimagic).

Following the [scientific python guidelines](https://scientific-python.org/specs/spec-0000/)
we drop the official support for Python 3.9.

## 0.5.1

This is a minor release that introduces the new algorithm selection tool and several
small improvements.

To learn more about the algorithm selection feature check out the following resources:

- [How to specify and configure algorithms](https://optimagic.readthedocs.io/en/latest/how_to/how_to_specify_algorithm_and_algo_options.html)
- [How to select local optimizers](https://optimagic.readthedocs.io/en/latest/how_to/how_to_algorithm_selection.html)

- {gh}`549` Add support for Python 3.13 ({ghuser}`timmens`)
- {gh}`550` and {gh}`534` implement the new algorithm selection tool ({ghuser}`janosg`)
- {gh}`548` and {gh}`531` improve the documentation ({ghuser}`ChristianZimpelmann`)
- {gh}`544` Adjusts the results processing of the nag optimizers to be compatible
with the latest releases ({ghuser}`timmens`)
- {gh}`543` Adds support for numpy 2.x ({ghuser}`timmens`)
- {gh}`536` Adds a how-to guide for choosing local optimizers ({ghuser}`mpetrosian`)
- {gh}`535` Allows algorithm classes and instances in estimation functions
({ghuser}`timmens`)
- {gh}`532` Makes several small improvements to the documentation.

## 0.5.0

Expand Down
2 changes: 1 addition & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ dependencies:
- pygmo>=2.19.0 # dev, tests
- jupyterlab # dev, docs
- nlopt # dev, tests, docs
- pdbpp # dev
- pip # dev, tests, docs
- pytest # dev, tests
- pytest-cov # tests
Expand Down Expand Up @@ -51,3 +50,4 @@ dependencies:
- types-jinja2 # dev, tests
- sqlalchemy-stubs # dev, tests
- sphinxcontrib-mermaid # dev, tests, docs
- pdbp # dev
7 changes: 3 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Scientific/Engineering",
]
authors = [
Expand Down Expand Up @@ -183,7 +184,7 @@ filterwarnings = [
"ignore:The following exception was caught when calculating",
"ignore:Usage of the parameter log_options",
]
addopts = ["--doctest-modules"]
addopts = ["--doctest-modules", "--pdbcls=pdbp:Pdb"]
markers = [
"wip: Tests that are work-in-progress.",
"slow: Tests that take a long time to run and are skipped in continuous integration.",
Expand All @@ -195,9 +196,6 @@ norecursedirs = ["docs", ".tools"]
# ======================================================================================
# Misc configuration
# ======================================================================================
[tool.pytask]
infer_latex_dependencies = true

[tool.yamlfix]
line_length = 88
sequence_style = "block_style"
Expand Down Expand Up @@ -379,5 +377,6 @@ module = [
"pathos.pools",
"optimagic._version",
"annotated_types",
"pdbp",
]
ignore_missing_imports = true
25 changes: 15 additions & 10 deletions src/optimagic/algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,27 +90,32 @@ def _available(self) -> list[Type[Algorithm]]:
return [
a
for a in _all
if a.__algo_info__.is_available # type: ignore
if a.algo_info.is_available # type: ignore
]

@property
def All(self) -> list[str]:
return [a.__algo_info__.name for a in self._all()] # type: ignore
def All(self) -> list[Type[Algorithm]]:
return self._all()

@property
def Available(self) -> list[str]:
return [a.__algo_info__.name for a in self._available()] # type: ignore
def Available(self) -> list[Type[Algorithm]]:
return self._available()

@property
def AllNames(self) -> list[str]:
return [str(a.name) for a in self._all()]

@property
def AvailableNames(self) -> list[str]:
return [str(a.name) for a in self._available()]

@property
def _all_algorithms_dict(self) -> dict[str, Type[Algorithm]]:
return {a.__algo_info__.name: a for a in self._all()} # type: ignore
return {str(a.name): a for a in self._all()}

@property
def _available_algorithms_dict(self) -> dict[str, Type[Algorithm]]:
return {
a.__algo_info__.name: a # type: ignore
for a in self._available()
}
return {str(a.name): a for a in self._available()}


@dataclass(frozen=True)
Expand Down
34 changes: 32 additions & 2 deletions src/optimagic/optimization/algorithm.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import typing
import warnings
from abc import ABC, abstractmethod
from abc import ABC, ABCMeta, abstractmethod
from dataclasses import dataclass, replace
from typing import Any

Expand Down Expand Up @@ -143,8 +143,38 @@ def __post_init__(self) -> None:
raise TypeError(msg)


class AlgorithmMeta(ABCMeta):
"""Metaclass to get repr, algo_info and name for classes, not just instances."""

def __repr__(self) -> str:
if hasattr(self, "__algo_info__") and self.__algo_info__ is not None:
out = f"om.algos.{self.__algo_info__.name}"
else:
out = self.__class__.__name__
return out

@property
def name(self) -> str:
if hasattr(self, "__algo_info__") and self.__algo_info__ is not None:
out = self.__algo_info__.name
else:
out = self.__class__.__name__
return out

@property
def algo_info(self) -> AlgoInfo:
if not hasattr(self, "__algo_info__") or 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."
)
raise AttributeError(msg)

return self.__algo_info__


@dataclass(frozen=True)
class Algorithm(ABC):
class Algorithm(ABC, metaclass=AlgorithmMeta):
@abstractmethod
def _solve_internal_problem(
self, problem: InternalOptimizationProblem, x0: NDArray[np.float64]
Expand Down
5 changes: 3 additions & 2 deletions src/optimagic/optimizers/pygmo_optimizers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

from __future__ import annotations

import contextlib
import warnings
from dataclasses import dataclass
from typing import Any, List, Literal
Expand Down Expand Up @@ -48,8 +47,10 @@

STOPPING_MAX_ITERATIONS_GENETIC = 250

with contextlib.suppress(ImportError):
try:
import pygmo as pg
except ImportError:
pass


@mark.minimizer(
Expand Down
5 changes: 3 additions & 2 deletions src/optimagic/optimizers/tao_optimizers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""This module implements the POUNDERs algorithm."""

import contextlib
import functools
from dataclasses import dataclass

Expand All @@ -23,8 +22,10 @@
from optimagic.typing import AggregationLevel, NonNegativeFloat, PositiveInt
from optimagic.utilities import calculate_trustregion_initial_radius

with contextlib.suppress(ImportError):
try:
from petsc4py import PETSc
except ImportError:
pass


@mark.minimizer(
Expand Down
Loading

0 comments on commit 8f976ab

Please sign in to comment.