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
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-24 08:49 +0000
1"""
2The base classes for implementing optimization algorithms.
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:
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.
27Notice that we already provide specialized algorithm sub-classes for several
28common scenarios, such as:
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.
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.
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
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
64from pycommons.types import type_error
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
81# start book
82class Algorithm(Component):
83 """A base class for implementing optimization algorithms."""
85 def solve(self, process: Process) -> None:
86 """
87 Apply this optimization algorithm to the given process.
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
98class Algorithm0(Algorithm):
99 """An algorithm with a nullary search operator."""
101 def __init__(self, name: str, op0: Op0) -> None:
102 """
103 Create the algorithm with nullary search operator.
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
118 def __str__(self) -> str:
119 """
120 Get the name of the algorithm.
122 :return: the name of the algorithm
123 """
124 return self.name
126 def initialize(self) -> None:
127 """Initialize the algorithm."""
128 super().initialize()
129 self.op0.initialize()
131 def log_parameters_to(self, logger: KeyValueLogSection) -> None:
132 """
133 Log the parameters of the algorithm to a logger.
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)
142class Algorithm1(Algorithm0):
143 """An algorithm with a unary search operator."""
145 def __init__(self, name: str, op0: Op0, op1: Op1) -> None:
146 """
147 Create the algorithm with nullary and unary search operator.
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)
158 def initialize(self) -> None:
159 """Initialize the algorithm."""
160 super().initialize()
161 self.op1.initialize()
163 def log_parameters_to(self, logger: KeyValueLogSection) -> None:
164 """
165 Log the parameters of the algorithm to a logger.
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)
174class Algorithm2(Algorithm1):
175 """An algorithm with a binary and unary operator."""
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.
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)
192 def initialize(self) -> None:
193 """Initialize the algorithm."""
194 super().initialize()
195 self.op2.initialize()
197 def log_parameters_to(self, logger: KeyValueLogSection) -> None:
198 """
199 Log the parameters of the algorithm to a logger.
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)
208def check_algorithm(algorithm: Any) -> Algorithm:
209 """
210 Check whether an object is a valid instance of :class:`Algorithm`.
212 :param algorithm: the algorithm object
213 :return: the object
214 :raises TypeError: if `algorithm` is not an instance of :class:`Algorithm`
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)