"""
Provides the Algorithm `bobyqa` from the Library `pdfo`.
The library "Powell's Derivative-Free Optimization solvers" (`pdfo`) at
https://www.pdfo.net provides an implementation of the "Bound Optimization BY
Quadratic Approximation" algorithm, or BOBYQA for short.
The library is dedicated to the late Professor M. J. D. Powell FRS (1936—2015)
and maintained by Tom M. Ragonneau and Zaikun Zhang.
Here, we wrap it into a class that complies to our `moptipy` API.
This class offers no additional logic but directly defers to the function
in the `pdfo` library.
1. Michael James David Powell. The BOBYQA Algorithm for Bound Constrained
Optimization without Derivatives. Technical Report DAMTP 2009/NA06.
Department of Applied Mathematics and Theoretical Physics, Cambridge
University, Cambridge, UK, 2009.
https://www.damtp.cam.ac.uk/user/na/NA_papers/NA2009_06.pdf
2. Tom M. Ragonneau and Zaikun Zhang. *PDFO: a cross-platform package for
Powell's derivative-free optimization solvers,* arXiv preprint.
Ithaca, NY, USA: Cornell University Library February, 2023.
arXiv:2302.13246v1 [math.OC] 26 Feb 202.
https://arxiv.org/pdf/2302.13246v1
- https://github.com/pdfo/pdfo
- https://www.pdfo.net
- https://pypi.org/project/pdfo
"""
import warnings
from typing import Any, Callable, Final, cast
import numpy as np
import pdfo # type: ignore
# noinspection PyProtectedMember
from pdfo._bobyqa import bobyqa # type: ignore
from pycommons.types import type_error
from moptipy.api.algorithm import Algorithm0
from moptipy.api.operators import Op0
from moptipy.api.process import Process
from moptipy.api.subprocesses import (
get_remaining_fes,
without_should_terminate,
)
from moptipy.spaces.vectorspace import VectorSpace
from moptipy.utils.logger import KeyValueLogSection
#: pdfo with version 1.3 and below is incompatible with numpy
#: of version 1.24.0 and above. It will crash with an exception.
#: So for this case, we will later just invoke a single random sample and
#: exit. See https://github.com/pdfo/pdfo/issues/55
_CANNOT_DO_PDFO: Final[bool] = \
hasattr(np, "__version__") and hasattr(pdfo, "__version__") \
and (list(map(int, np.__version__.split("."))) >= [1, 24])\
and (list(map(int, pdfo.__version__.split("."))) <= [1, 3])
[docs]
class BOBYQA(Algorithm0):
"""
A wrapper for the `bobyqa` algorithm from `pdfo`.
The Bound Optimization BY Quadratic Approximation (BOBYQA) developed by
Michael James David Powell and published by the `pdfo` library.
1. Michael James David Powell. The BOBYQA Algorithm for Bound Constrained
Optimization without Derivatives. Technical Report DAMTP 2009/NA06.
Department of Applied Mathematics and Theoretical Physics, Cambridge
University, Cambridge, UK, 2009.
https://www.damtp.cam.ac.uk/user/na/NA_papers/NA2009_06.pdf
"""
def __init__(self, op0: Op0, space: VectorSpace) -> None:
"""
Create the BOBYQA algorithm.
:param op0: the nullary search operator
:param space: the vector space
"""
super().__init__("bobyqa_pdfo", op0)
if not isinstance(space, VectorSpace):
raise type_error(space, "space", VectorSpace)
#: the vector space defining the dimensions and bounds
self.space: Final[VectorSpace] = space
def __run(self, process: Process) -> None:
"""
Execute the algorithm.
:param process: the process
"""
x0: Final[np.ndarray] = process.create()
npt: int = (2 * len(x0)) + 1 # the default npt value
max_fes: int = max(npt + 1, get_remaining_fes(process))
self.op0.op0(process.get_random(), x0) # sample start point
if _CANNOT_DO_PDFO: # PDFO incompatible to current setup
process.evaluate(x0) # do single random sample
return # and quit
with warnings.catch_warnings():
warnings.simplefilter("ignore")
bobyqa(self.space.clipped(process.evaluate),
x0, bounds=np.stack((self.space.lower_bound,
self.space.upper_bound)).transpose(),
options={
"rhoend": 1e-320, # the end rho value
"maxfev": max_fes, # the maximum FEs
"npt": npt, # the number of interpolation points
"honour_x0": True, # enforce our x0
"quiet": True}) # do not print any messages
[docs]
def solve(self, process: Process) -> None:
"""
Apply the external `bobyqa` implementation to an optimization problem.
:param process: the black-box process object
"""
# invoke the SciPy algorithm implementation
without_should_terminate(
cast(Callable[[Process], Any], self.__run), process)
[docs]
def log_parameters_to(self, logger: KeyValueLogSection) -> None:
"""
Log the parameters of the algorithm to a logger.
:param logger: the logger for the parameters
"""
super().log_parameters_to(logger) # log algorithm/operator
self.space.log_bounds(logger) # log bounds