Coverage for moptipy / api / space.py: 94%
18 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"""
2Provide the functionality to access search and solution spaces.
4A :class:`Space` is the abstraction of the data structures for solutions and
5points in the search space that need to be generated, copied, and stored
6during the optimization process. This allows us to develop black-box
7algorithms while still being able to properly remember the best solutions,
8storing them as text strings in log files, and to validate whether they are
9correct.
11All search or solution spaces in `moptipy` inherit from :class:`Space`. If
12you implement a new space, you should test it with the pre-defined unit test
13routine :func:`~moptipy.tests.space.validate_space`.
15The following pre-defined spaces are currently available:
17- :class:`~moptipy.spaces.bitstrings.BitStrings`, the space of `n`-dimensional
18 bit strings
19- :class:`~moptipy.spaces.intspace.IntSpace`, a space of `n`-dimensional
20 integer strings, where each element is between predefined inclusive bounds
21 `min_value...max_value`.
22- :class:`~moptipy.spaces.permutations.Permutations` is a special version of
23 the :class:`~moptipy.spaces.intspace.IntSpace` where all elements are
24 permutations of a base string
25 :attr:`~moptipy.spaces.permutations.Permutations.blueprint`. This means that
26 it can represent permutations both with and without repetitions. Depending
27 on the base string, each element may occur an element-specific number of
28 times. For the base string `(-1, -1, 2, 7, 7, 7)`, for example, `-1` may
29 occur twice, `2` can occur once, and `7` three times.
30- :class:`~moptipy.spaces.vectorspace.VectorSpace` is the space of
31 `n`-dimensional floating point number vectors.
32"""
34from typing import Any
36from pycommons.types import type_error
38from moptipy.api.component import Component
41# start book
42class Space(Component):
43 """
44 A class to represent both search and solution spaces.
46 The space basically defines a container data structure and basic
47 operations that we can apply to them. For example, a solution
48 space contains all the possible solutions to an optimization
49 problem. All of them are instances of one data structure. An
50 optimization as well as a black-box process needs to be able to
51 create and copy such objects. In order to store the solutions we
52 found in a text file, we must further be able to translate them to
53 strings. We should also be able to parse such strings. It is also
54 important to detect whether two objects are the same and whether
55 the contents of an object are valid. All of this functionality is
56 offered by the `Space` class.
57 """
59 def create(self) -> Any:
60 # end book
61 """
62 Generate an instance of the data structure managed by the space.
64 The state/contents of this data structure are undefined. It may
65 not pass the :meth:`validate` method.
67 :return: the new instance
68 """
70 def copy(self, dest, source) -> None: # +book
71 """
72 Copy one instance of the data structure to another one.
74 Notice that the first parameter of this method is the destination,
75 which will be overwritten by copying the contents of the second
76 parameter over it.
78 :param dest: the destination data structure,
79 whose contents will be overwritten with those from `source`
80 :param source: the source data structure, which remains
81 unchanged and whose contents will be copied to `dest`
82 """
84 def to_str(self, x) -> str: # +book
85 """
86 Obtain a textual representation of an instance of the data structure.
88 This method should convert an element of the space to a string
89 representation that is parseable by :meth:from_str: and should ideally
90 not be too verbose. For example, when converting a list or array `x`
91 of integers to a string, one could simply do
92 `";".join([str(xx) for xx in x])`, which would convert it to a
93 semicolon-separated list without any wasted space.
95 Notice that this method is used by the
96 :class:`~moptipy.utils.logger.Logger` when storing the final
97 optimization results in the log files in form of a
98 :class:`~moptipy.utils.logger.TextLogSection` created via
99 :meth:`~moptipy.utils.logger.Logger.text`. By implementing
100 :meth:from_str: and :meth:to_str: as exact inverse of each other, you
101 can thus ensure that you can always automatically load the results of
102 your optimization runs from the log files created via
103 :meth:`~moptipy.api.execution.Execution.set_log_file` of the
104 :class:`~moptipy.api.execution.Execution` class.
106 :param x: the instance
107 :return: the string representation of x
108 """
110 def from_str(self, text: str) -> Any: # +book
111 """
112 Transform a string `text` to one element of the space.
114 This method should be implemented as inverse to :meth:to_str:.
115 It should check the validity of the result before returning it.
116 It may not always be possible to implement this method, but you
117 should try.
119 :param text: the input string
120 :return: the element in the space corresponding to `text`
121 """
123 def is_equal(self, x1, x2) -> bool: # +book
124 """
125 Check if the contents of two instances of the data structure are equal.
127 :param x1: the first instance
128 :param x2: the second instance
129 :return: `True` if the contents are equal, `False` otherwise
130 """
132 def validate(self, x) -> None: # +book
133 """
134 Check whether a given point in the space is valid.
136 This function should be implemented such that it very carefully checks
137 whether the argument `x` is a valid element of this space. It should
138 check the Python data type of `x` and the type of its components and
139 raise a `TypeError` if it does not match the exact requirements. It
140 should also check the value of each element of `x` whether it is
141 permitted. Once this function returns without throwing an exception,
142 the user can rely on that the data structure `x` is correct.
144 For example, if we have a space of
145 :mod:`~moptipy.spaces.permutations` of the values from `1` to `n`,
146 where the elements are represented as :class:`numpy.ndarray` objects,
147 then this function should first check whether `x` is indeed an
148 instance of :class:`numpy.ndarray`. If not, it should raise a
149 `TypeError`. Then it could check whether the length of `x` is indeed
150 `n` and raise a `ValueError` otherwise. It could then check whether
151 each element of `x` is from `1..n` *and* occurs exactly one time (and
152 otherwise raise a `ValueError`). Moreover, it should also check
153 whether the numpy `dtype` of `x` is an appropriate integer data type
154 and raise a `ValueError` otherwise. Hence, if this function checks an
155 element `x` and does not raise any error, the user can rely on that
156 this element `x` is, indeed, a fully valid permutation.
158 In our system, we use this method for example at the end of
159 optimization processes. Every solution that is written to a log file
160 must pass through this method. In other words, we ensure that only
161 valid solutions are stored. If the optimization algorithm or a search
162 operator has a bug and sometimes may produce invalid data structures,
163 this a) helps us in finding the bug and b) prevents us from storing
164 invalid solutions. It is also strongly encouraged that the
165 :meth:`from_str` method of any :class:`Space` implementation should
166 run its results through :meth:`validate`. Since :meth:`from_str` can
167 be used to, e.g., parse the data from the result sections of log
168 files, this ensures that no corrupted or otherwise invalid data is
169 parsed into an application.
170 See https://thomasweise.github.io/moptipy/#data-formats for more
171 information on log files.
173 :param x: the point
174 :raises TypeError: if the point `x` (or one of its elements, if
175 applicable) has the wrong data type
176 :raises ValueError: if the point `x` is invalid and/or simply is not
177 an element of this space
178 """
180 def n_points(self) -> int:
181 """
182 Get the approximate number of different elements in the space.
184 This operation can help us when doing tests of the space API
185 implementations. If we know how many points exist in the space,
186 then we can judge whether a method that randomly generates
187 points is sufficiently random, for instance.
189 By default, this method simply returns `2`. If you have a better
190 approximation of the size of the space, then you should override it.
192 :return: the approximate scale of the space
193 """
194 return 2
197def check_space(space: Any, none_is_ok: bool = False) -> Space | None:
198 """
199 Check whether an object is a valid instance of :class:`Space`.
201 :param space: the object
202 :param none_is_ok: is it ok if `None` is passed in?
203 :return: the object
204 :raises TypeError: if `space` is not an instance of
205 :class:`~moptipy.api.space.Space`
207 >>> check_space(Space())
208 Space
209 >>> check_space(Space(), True)
210 Space
211 >>> check_space(Space(), False)
212 Space
213 >>> try:
214 ... check_space('A')
215 ... except TypeError as te:
216 ... print(te)
217 space should be an instance of moptipy.api.space.\
218Space but is str, namely 'A'.
219 >>> try:
220 ... check_space('A', True)
221 ... except TypeError as te:
222 ... print(te)
223 space should be an instance of moptipy.api.space.\
224Space but is str, namely 'A'.
225 >>> try:
226 ... check_space('A', False)
227 ... except TypeError as te:
228 ... print(te)
229 space should be an instance of moptipy.api.space.\
230Space but is str, namely 'A'.
231 >>>
232 >>> try:
233 ... check_space(None)
234 ... except TypeError as te:
235 ... print(te)
236 space should be an instance of moptipy.api.space.\
237Space but is None.
238 >>> print(check_space(None, True))
239 None
240 >>> try:
241 ... check_space(None, False)
242 ... except TypeError as te:
243 ... print(te)
244 space should be an instance of moptipy.api.space.\
245Space but is None.
246 """
247 if isinstance(space, Space):
248 return space
249 if none_is_ok and (space is None):
250 return None
251 raise type_error(space, "space", Space)