Coverage for moptipy / algorithms / so / vector / pdfo.py: 83%
46 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-24 08:49 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-24 08:49 +0000
1"""
2Provides the Algorithm `bobyqa` from the Library `pdfo`.
4The library "Powell's Derivative-Free Optimization solvers" (`pdfo`) at
5https://www.pdfo.net provides an implementation of the "Bound Optimization BY
6Quadratic Approximation" algorithm, or BOBYQA for short.
7The library is dedicated to the late Professor M. J. D. Powell FRS (1936—2015)
8and maintained by Tom M. Ragonneau and Zaikun Zhang.
9Here, we wrap it into a class that complies to our `moptipy` API.
10This class offers no additional logic but directly defers to the function
11in the `pdfo` library.
131. Michael James David Powell. The BOBYQA Algorithm for Bound Constrained
14 Optimization without Derivatives. Technical Report DAMTP 2009/NA06.
15 Department of Applied Mathematics and Theoretical Physics, Cambridge
16 University, Cambridge, UK, 2009.
17 https://www.damtp.cam.ac.uk/user/na/NA_papers/NA2009_06.pdf
182. Tom M. Ragonneau and Zaikun Zhang. *PDFO: a cross-platform package for
19 Powell's derivative-free optimization solvers,* arXiv preprint.
20 Ithaca, NY, USA: Cornell University Library February, 2023.
21 arXiv:2302.13246v1 [math.OC] 26 Feb 202.
22 https://arxiv.org/pdf/2302.13246v1
24- https://github.com/pdfo/pdfo
25- https://www.pdfo.net
26- https://pypi.org/project/pdfo
27"""
29import warnings
30from typing import Any, Callable, Final, cast # pylint: disable=W0611
32import numpy as np
33import pdfo # type: ignore
34from packaging import version
36# noinspection PyProtectedMember
37from pdfo._bobyqa import bobyqa # type: ignore # noqa: PLC2701
38from pycommons.types import type_error
40from moptipy.api.algorithm import Algorithm0
41from moptipy.api.operators import Op0
42from moptipy.api.process import Process
43from moptipy.api.subprocesses import (
44 get_remaining_fes,
45 without_should_terminate,
46)
47from moptipy.spaces.vectorspace import VectorSpace
48from moptipy.utils.logger import KeyValueLogSection
51def __check_cannot_use_pdfo() -> bool:
52 """
53 Check whether we cannot use pdf.
55 :returns: `True` if we cannot use pdfo.
56 """
57 if not hasattr(np, "__version__"):
58 return True
59 if not hasattr(pdfo, "__version__"):
60 return True
61 npv: Final[version.Version] = version.parse(np.__version__)
62 if npv.major >= 2:
63 return True
64 pdv: Final[version.Version] = version.parse(pdfo.__version__)
65 return ((pdv.major <= 1 <= npv.major) and (npv.minor >= 24)
66 and (pdv.minor <= 3))
69#: pdfo with version 1.3 and below is incompatible with numpy
70#: of version 1.24.0 and above. It will crash with an exception.
71#: So for this case, we will later just invoke a single random sample and
72#: exit. See https://github.com/pdfo/pdfo/issues/55
73#: pdfo with version 2.2 is also incompatible with numpy 2.0 and above.
74#: See https://github.com/pdfo/pdfo/issues/112
75_CANNOT_DO_PDFO: Final[bool] = __check_cannot_use_pdfo()
78class BOBYQA(Algorithm0):
79 """
80 A wrapper for the `bobyqa` algorithm from `pdfo`.
82 The Bound Optimization BY Quadratic Approximation (BOBYQA) developed by
83 Michael James David Powell and published by the `pdfo` library.
85 1. Michael James David Powell. The BOBYQA Algorithm for Bound Constrained
86 Optimization without Derivatives. Technical Report DAMTP 2009/NA06.
87 Department of Applied Mathematics and Theoretical Physics, Cambridge
88 University, Cambridge, UK, 2009.
89 https://www.damtp.cam.ac.uk/user/na/NA_papers/NA2009_06.pdf
90 """
92 def __init__(self, op0: Op0, space: VectorSpace) -> None:
93 """
94 Create the BOBYQA algorithm.
96 :param op0: the nullary search operator
97 :param space: the vector space
98 """
99 super().__init__("bobyqa_pdfo", op0)
100 if not isinstance(space, VectorSpace):
101 raise type_error(space, "space", VectorSpace)
102 #: the vector space defining the dimensions and bounds
103 self.space: Final[VectorSpace] = space
105 def __run(self, process: Process) -> None:
106 """
107 Execute the algorithm.
109 :param process: the process
110 """
111 x0: Final[np.ndarray] = process.create()
112 npt: int = (2 * len(x0)) + 1 # the default npt value
113 max_fes: int = max(npt + 1, get_remaining_fes(process))
114 self.op0.op0(process.get_random(), x0) # sample start point
116 if _CANNOT_DO_PDFO: # PDFO incompatible to current setup
117 process.evaluate(x0) # do single random sample
118 return # and quit
120 with warnings.catch_warnings():
121 warnings.simplefilter("ignore")
122 bobyqa(self.space.clipped(process.evaluate),
123 x0, bounds=np.stack((self.space.lower_bound,
124 self.space.upper_bound)).transpose(),
125 options={
126 "rhoend": 1e-320, # the end rho value
127 "maxfev": max_fes, # the maximum FEs
128 "npt": npt, # the number of interpolation points
129 "honour_x0": True, # enforce our x0
130 "quiet": True}) # do not print any messages
132 def solve(self, process: Process) -> None:
133 """
134 Apply the external `bobyqa` implementation to an optimization problem.
136 :param process: the black-box process object
137 """
138 # invoke the SciPy algorithm implementation
139 without_should_terminate(
140 cast("Callable[[Process], Any]", self.__run), process)
142 def log_parameters_to(self, logger: KeyValueLogSection) -> None:
143 """
144 Log the parameters of the algorithm to a logger.
146 :param logger: the logger for the parameters
147 """
148 super().log_parameters_to(logger) # log algorithm/operator
149 self.space.log_bounds(logger) # log bounds