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

1""" 

2Provides the Algorithm `bobyqa` from the Library `pdfo`. 

3 

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. 

12 

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 

23 

24- https://github.com/pdfo/pdfo 

25- https://www.pdfo.net 

26- https://pypi.org/project/pdfo 

27""" 

28 

29import warnings 

30from typing import Any, Callable, Final, cast # pylint: disable=W0611 

31 

32import numpy as np 

33import pdfo # type: ignore 

34from packaging import version 

35 

36# noinspection PyProtectedMember 

37from pdfo._bobyqa import bobyqa # type: ignore # noqa: PLC2701 

38from pycommons.types import type_error 

39 

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 

49 

50 

51def __check_cannot_use_pdfo() -> bool: 

52 """ 

53 Check whether we cannot use pdf. 

54 

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)) 

67 

68 

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() 

76 

77 

78class BOBYQA(Algorithm0): 

79 """ 

80 A wrapper for the `bobyqa` algorithm from `pdfo`. 

81 

82 The Bound Optimization BY Quadratic Approximation (BOBYQA) developed by 

83 Michael James David Powell and published by the `pdfo` library. 

84 

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 """ 

91 

92 def __init__(self, op0: Op0, space: VectorSpace) -> None: 

93 """ 

94 Create the BOBYQA algorithm. 

95 

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 

104 

105 def __run(self, process: Process) -> None: 

106 """ 

107 Execute the algorithm. 

108 

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 

115 

116 if _CANNOT_DO_PDFO: # PDFO incompatible to current setup 

117 process.evaluate(x0) # do single random sample 

118 return # and quit 

119 

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 

131 

132 def solve(self, process: Process) -> None: 

133 """ 

134 Apply the external `bobyqa` implementation to an optimization problem. 

135 

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) 

141 

142 def log_parameters_to(self, logger: KeyValueLogSection) -> None: 

143 """ 

144 Log the parameters of the algorithm to a logger. 

145 

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