Coverage for moptipy / api / algorithm.py: 96%

53 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-11-24 08:49 +0000

1""" 

2The base classes for implementing optimization algorithms. 

3 

4All optimization algorithms implemented based on the `moptipy` API inherit 

5from :class:`~moptipy.api.algorithm.Algorithm`. If you implement a new 

6algorithm, you will want to override the following methods: 

7 

81. :meth:`~moptipy.api.algorithm.Algorithm.solve` implements the algorithm 

9 itself. It receives an instance of :class:`~moptipy.api.process.Process` as 

10 parameter that allows for the creation and evaluation of candidate 

11 solutions and that provides a random number generator. The optimization 

12 algorithm then will sample solutions and pass them to 

13 :meth:`~moptipy.api.process.Process.evaluate` to obtain their objective 

14 value, striving sampling better and better solutions. 

152. The dunder method `__str__` should be overridden to return a short mnemonic 

16 name of the algorithm. 

173. :meth:`~moptipy.api.component.Component.log_parameters_to` needs to be 

18 overridden if the algorithm has any parameters. This methods then should 

19 store the values of all the parameters to the logging context. It should 

20 also invoke the :meth:`~moptipy.api.component.Component.log_parameters_to` 

21 routines of all sub-components of the algorithm. 

224. :meth:`~moptipy.api.component.Component.initialize` needs to be overridden 

23 to reset/initialize all internal data structures and to invoke all the 

24 :meth:`~moptipy.api.component.Component.initialize` of all components (such 

25 as search operators) of the algorithm. 

26 

27Notice that we already provide specialized algorithm sub-classes for several 

28common scenarios, such as: 

29 

301. :class:`~moptipy.api.algorithm.Algorithm0` for algorithms that have a 

31 nullary search operator (:class:`~moptipy.api.operators.Op0`). 

322. :class:`~moptipy.api.algorithm.Algorithm1` for algorithms that have a 

33 nullary (:class:`~moptipy.api.operators.Op0`) and an unary 

34 (:class:`~moptipy.api.operators.Op1`) search operator. 

353. :class:`~moptipy.api.algorithm.Algorithm2` for algorithms that have a 

36 nullary (:class:`~moptipy.api.operators.Op0`), an unary 

37 (:class:`~moptipy.api.operators.Op1`), and a binary 

38 (:class:`~moptipy.api.operators.Op2`) search operator. 

394. :class:`~moptipy.api.mo_algorithm.MOAlgorithm` for multi-objective 

40 optimization problems. 

41 

42These classes automatically invoke the 

43:meth:`~moptipy.api.component.Component.log_parameters_to` and 

44:meth:`~moptipy.api.component.Component.initialize` routines of their 

45operators. 

46 

47If you implement a new algorithm, you can and should test with the pre-defined 

48unit test routine :func:`~moptipy.tests.algorithm.validate_algorithm`, or its 

49specialized versions 

50 

511. for bit-string based search spaces based on 

52 :func:`~moptipy.tests.on_bitstrings.validate_algorithm_on_bitstrings`): 

53 a. :func:`~moptipy.tests.on_bitstrings.validate_algorithm_on_onemax`, 

54 b. :func:`~moptipy.tests.on_bitstrings.validate_algorithm_on_leadingones` 

552. for the JSSP based on 

56 :func:`~moptipy.tests.on_jssp.validate_algorithm_on_1_jssp`: 

57 a. :func:`~moptipy.tests.on_jssp.validate_algorithm_on_jssp` 

583. on real-valued vector search spaces based on 

59 :func:`~moptipy.tests.on_vectors.validate_algorithm_on_vectors`): 

60 a. :func:`~moptipy.tests.on_vectors.validate_algorithm_on_ackley` 

61""" 

62from typing import Any, Final 

63 

64from pycommons.types import type_error 

65 

66from moptipy.api.component import Component 

67from moptipy.api.logging import SCOPE_OP0, SCOPE_OP1, SCOPE_OP2 

68from moptipy.api.operators import ( 

69 Op0, 

70 Op1, 

71 Op2, 

72 check_op0, 

73 check_op1, 

74 check_op2, 

75) 

76from moptipy.api.process import Process 

77from moptipy.utils.logger import KeyValueLogSection 

78from moptipy.utils.strings import PART_SEPARATOR 

79 

80 

81# start book 

82class Algorithm(Component): 

83 """A base class for implementing optimization algorithms.""" 

84 

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

86 """ 

87 Apply this optimization algorithm to the given process. 

88 

89 :param process: the process which provides methods to access the 

90 search space, the termination criterion, and a source of 

91 randomness. It also wraps the objective function, remembers the 

92 best-so-far solution, and takes care of creating log files (if 

93 this is wanted). 

94 """ 

95# end book 

96 

97 

98class Algorithm0(Algorithm): 

99 """An algorithm with a nullary search operator.""" 

100 

101 def __init__(self, name: str, op0: Op0) -> None: 

102 """ 

103 Create the algorithm with nullary search operator. 

104 

105 :param name: the name of the algorithm 

106 :param op0: the nullary search operator 

107 """ 

108 #: The nullary search operator. 

109 self.op0: Final[Op0] = check_op0(op0) 

110 if not isinstance(name, str): 

111 raise type_error(name, "name", str) 

112 if len(name) <= 0: 

113 raise ValueError(f"Algorithm name cannot be {name!r}.") 

114 #: the name of this optimization algorithm, which is also the return 

115 #: value of `__str__()` 

116 self.name: Final[str] = name 

117 

118 def __str__(self) -> str: 

119 """ 

120 Get the name of the algorithm. 

121 

122 :return: the name of the algorithm 

123 """ 

124 return self.name 

125 

126 def initialize(self) -> None: 

127 """Initialize the algorithm.""" 

128 super().initialize() 

129 self.op0.initialize() 

130 

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

132 """ 

133 Log the parameters of the algorithm to a logger. 

134 

135 :param logger: the logger for the parameters 

136 """ 

137 super().log_parameters_to(logger) 

138 with logger.scope(SCOPE_OP0) as sc: 

139 self.op0.log_parameters_to(sc) 

140 

141 

142class Algorithm1(Algorithm0): 

143 """An algorithm with a unary search operator.""" 

144 

145 def __init__(self, name: str, op0: Op0, op1: Op1) -> None: 

146 """ 

147 Create the algorithm with nullary and unary search operator. 

148 

149 :param name: the name of the algorithm 

150 :param op0: the nullary search operator 

151 :param op1: the unary search operator 

152 """ 

153 super().__init__(name if op1.__class__ == Op1 else 

154 f"{name}{PART_SEPARATOR}{op1}", op0) 

155 #: The unary search operator. 

156 self.op1: Final[Op1] = check_op1(op1) 

157 

158 def initialize(self) -> None: 

159 """Initialize the algorithm.""" 

160 super().initialize() 

161 self.op1.initialize() 

162 

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

164 """ 

165 Log the parameters of the algorithm to a logger. 

166 

167 :param logger: the logger for the parameters 

168 """ 

169 super().log_parameters_to(logger) 

170 with logger.scope(SCOPE_OP1) as sc: 

171 self.op1.log_parameters_to(sc) 

172 

173 

174class Algorithm2(Algorithm1): 

175 """An algorithm with a binary and unary operator.""" 

176 

177 def __init__(self, name: str, op0: Op0, op1: Op1, op2: Op2) -> None: 

178 """ 

179 Create the algorithm with nullary, unary, and binary search operator. 

180 

181 :param name: the name of the algorithm 

182 :param op0: the nullary search operator 

183 :param op1: the unary search operator 

184 :param op2: the binary search operator 

185 """ 

186 super().__init__( 

187 name if op2.__class__ is Op2 else 

188 f"{name}{PART_SEPARATOR}{op2}", op0, op1) 

189 #: The binary search operator. 

190 self.op2: Final[Op2] = check_op2(op2) 

191 

192 def initialize(self) -> None: 

193 """Initialize the algorithm.""" 

194 super().initialize() 

195 self.op2.initialize() 

196 

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

198 """ 

199 Log the parameters of the algorithm to a logger. 

200 

201 :param logger: the logger for the parameters 

202 """ 

203 super().log_parameters_to(logger) 

204 with logger.scope(SCOPE_OP2) as sc: 

205 self.op2.log_parameters_to(sc) 

206 

207 

208def check_algorithm(algorithm: Any) -> Algorithm: 

209 """ 

210 Check whether an object is a valid instance of :class:`Algorithm`. 

211 

212 :param algorithm: the algorithm object 

213 :return: the object 

214 :raises TypeError: if `algorithm` is not an instance of :class:`Algorithm` 

215 

216 >>> check_algorithm(Algorithm()) 

217 Algorithm 

218 >>> try: 

219 ... check_algorithm('A') 

220 ... except TypeError as te: 

221 ... print(te) 

222 algorithm should be an instance of moptipy.api.algorithm.\ 

223Algorithm but is str, namely 'A'. 

224 >>> try: 

225 ... check_algorithm(None) 

226 ... except TypeError as te: 

227 ... print(te) 

228 algorithm should be an instance of moptipy.api.algorithm.\ 

229Algorithm but is None. 

230 """ 

231 if isinstance(algorithm, Algorithm): 

232 return algorithm 

233 raise type_error(algorithm, "algorithm", Algorithm)