Skip to content

Commit d50f286

Browse files
committed
added more bitstring-based benchmark problems, added more doctests, and fixed several issues
1 parent a525d46 commit d50f286

20 files changed

+7745
-133
lines changed

moptipy/examples/bitstrings/__init__.py

+44-20
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,48 @@
88
99
The following benchmark problems are provided:
1010
11-
1. :mod:`~moptipy.examples.bitstrings.ising1d`, the one-dimensional
12-
Ising model, where the goal is that all bits should have the same value as
13-
their neighbors.
14-
2. The :mod:`~moptipy.examples.bitstrings.jump` problem is equivalent
15-
to :mod:`~moptipy.examples.bitstrings.onemax`, but has a deceptive
16-
region right before the optimum.
17-
3. The :mod:`~moptipy.examples.bitstrings.leadingones` problem,
18-
where the goal is to find a bit string with the maximum number of leading
19-
ones.
20-
4. The :mod:`~moptipy.examples.bitstrings.onemax` problem, where the
21-
goal is to find a bit string with the maximum number of ones.
22-
5. The :mod:`~moptipy.examples.bitstrings.trap` problem, which is like
23-
OneMax, but with the optimum and worst-possible solution swapped. This
24-
problem is therefore highly deceptive.
25-
6. The :mod:`~moptipy.examples.bitstrings.w_model`, a benchmark
26-
problem with tunable epistasis, uniform neutrality, and
27-
ruggedness/deceptiveness.
28-
7. The :mod:`~moptipy.examples.bitstrings.zeromax` problem, where the
29-
goal is to find a bit string with the maximum number of zeros. This is the
30-
opposite of the OneMax problem.
11+
1. :mod:`~moptipy.examples.bitstrings.ising1d`, the one-dimensional
12+
Ising model, where the goal is that all bits should have the same value as
13+
their neighbors in a ring.
14+
2. :mod:`~moptipy.examples.bitstrings.ising2d`, the two-dimensional
15+
Ising model, where the goal is that all bits should have the same value as
16+
their neighbors on a torus.
17+
3. The :mod:`~moptipy.examples.bitstrings.jump` problem is equivalent
18+
to :mod:`~moptipy.examples.bitstrings.onemax`, but has a deceptive
19+
region right before the optimum.
20+
4. The :mod:`~moptipy.examples.bitstrings.leadingones` problem,
21+
where the goal is to find a bit string with the maximum number of leading
22+
ones.
23+
5. The :mod:`~moptipy.examples.bitstrings.linearharmonic` problem,
24+
where the goal is to find a bit string with the all ones, like in
25+
:mod:`~moptipy.examples.bitstrings.onemax`, but this time all bits have
26+
a different weight (namely their index, starting at 1).
27+
6. The :mod:`~moptipy.examples.bitstrings.nqueens`, where the goal is to
28+
place `k` queens on a `k * k`-sized chess board such that no queen can
29+
beat any other queen.
30+
7. The :mod:`~moptipy.examples.bitstrings.onemax` problem, where the
31+
goal is to find a bit string with the maximum number of ones.
32+
8. The :mod:`~moptipy.examples.bitstrings.plateau` problem similar to the
33+
:mod:`~moptipy.examples.bitstrings.jump` problem, but this time the
34+
optimum is surrounded by a region of neutrality.
35+
9. The :mod:`~moptipy.examples.bitstrings.trap` problem, which is like
36+
OneMax, but with the optimum and worst-possible solution swapped. This
37+
problem is therefore highly deceptive.
38+
10. The :mod:`~moptipy.examples.bitstrings.twomax` problem has the global
39+
optimum at the string of all `1` bits and a local optimum at the string
40+
of all `0` bits. Both have basins of attraction of about the same size.
41+
11. The :mod:`~moptipy.examples.bitstrings.w_model`, a benchmark
42+
problem with tunable epistasis, uniform neutrality, and
43+
ruggedness/deceptiveness.
44+
12. The :mod:`~moptipy.examples.bitstrings.zeromax` problem, where the
45+
goal is to find a bit string with the maximum number of zeros. This is the
46+
opposite of the OneMax problem.
47+
48+
Parts of the code here are related to the research work of
49+
Mr. Jiazheng ZENG (曾嘉政), a Master's student at the Institute of Applied
50+
Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of
51+
Artificial Intelligence and Big Data (人工智能与大数据学院) at
52+
Hefei University (合肥大学) in
53+
Hefei, Anhui, China (中国安徽省合肥市) under the supervision of
54+
Prof. Dr. Thomas Weise (汤卫思教授).
3155
"""

moptipy/examples/bitstrings/bitstring_problem.py

+217-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
"""A base class for bitstring-based problems."""
1+
"""
2+
A base class for bitstring-based problems.
23
4+
Many benchmark problems from discrete optimization are simple functions
5+
defined over bit strings. We here offer the class :class:`BitStringProblem`,
6+
which provides reasonable default behavior and several utilities for
7+
implementing such problems.
8+
"""
9+
10+
from math import isqrt
311
from typing import Final
412

513
from pycommons.types import check_int_range
@@ -16,7 +24,26 @@ class BitStringProblem(Objective):
1624
This base class has a set of default behaviors. It has an attribute
1725
:attr:`n` denoting the lengths of the accepted bit strings. Its
1826
:meth:`lower_bound` returns `0` and its :meth:`upper_bound` returns
19-
:attr:`n`. :meth:`is_always_integer` returns `True`.
27+
:attr:`n`. :meth:`is_always_integer` returns `True`. If also offers
28+
the method :meth:`space` which returns an instance of
29+
:class:`~moptipy.spaces.bitstrings.BitStrings` for bit strings of
30+
length :attr:`n`.
31+
32+
>>> bs = BitStringProblem(1)
33+
>>> bs.n
34+
1
35+
36+
>>> try:
37+
... bs = BitStringProblem(0)
38+
... except ValueError as ve:
39+
... print(ve)
40+
n=0 is invalid, must be in 1..1000000000.
41+
42+
>>> try:
43+
... bs = BitStringProblem("a")
44+
... except TypeError as te:
45+
... print(te)
46+
n should be an instance of int but is str, namely 'a'.
2047
"""
2148

2249
def __init__(self, n: int) -> None: # +book
@@ -33,6 +60,9 @@ def lower_bound(self) -> int:
3360
"""
3461
Get the lower bound of the bit string based problem.
3562
63+
By default, this method returns `0`. Problems where the lower bound
64+
differs should override this method.
65+
3666
:return: 0
3767
3868
>>> print(BitStringProblem(10).lower_bound())
@@ -44,7 +74,10 @@ def upper_bound(self) -> int:
4474
"""
4575
Get the upper bound of the bit string based problem.
4676
47-
:return: the length of the bit string
77+
The default value is the length of the bit string. Problems where the
78+
upper bound differs should overrrite this method.
79+
80+
:return: by default, this is the length of the bit string
4881
4982
>>> print(BitStringProblem(7).upper_bound())
5083
7
@@ -56,7 +89,8 @@ def is_always_integer(self) -> bool:
5689
Return `True` if the `evaluate` function always returns an `int`.
5790
5891
This pre-defined function for bit-string based problems will always
59-
return `True`.
92+
return `True`. Problems where this is not the case should overwrite
93+
this method.
6094
6195
:retval True: always
6296
"""
@@ -84,7 +118,7 @@ def log_parameters_to(self, logger: KeyValueLogSection) -> None:
84118
... BitStringProblem(5).log_parameters_to(kv)
85119
... text = l.get_log()
86120
>>> text[1]
87-
'name: BitStringProblem'
121+
'name: bitstringproblem_5'
88122
>>> text[3]
89123
'lowerBound: 0'
90124
>>> text[4]
@@ -96,3 +130,181 @@ def log_parameters_to(self, logger: KeyValueLogSection) -> None:
96130
"""
97131
super().log_parameters_to(logger)
98132
logger.key_value("n", self.n)
133+
134+
def __str__(self) -> str:
135+
"""
136+
Get the name of the problem.
137+
138+
:returns: the name of the problem, which by default is the class name
139+
in lower case, followed by an underscore and the number of bits
140+
"""
141+
return f"{super().__str__().lower()}_{self.n}"
142+
143+
144+
class SquareBitStringProblem(BitStringProblem):
145+
"""
146+
A bitstring problem which requires that the string length is square.
147+
148+
>>> sb = SquareBitStringProblem(9)
149+
>>> sb.n
150+
9
151+
>>> sb.k
152+
3
153+
154+
>>> try:
155+
... bs = SquareBitStringProblem(0)
156+
... except ValueError as ve:
157+
... print(ve)
158+
n=0 is invalid, must be in 4..1000000000.
159+
160+
>>> try:
161+
... bs = SquareBitStringProblem(3)
162+
... except ValueError as ve:
163+
... print(ve)
164+
n=3 is invalid, must be in 4..1000000000.
165+
166+
>>> try:
167+
... bs = SquareBitStringProblem(21)
168+
... except ValueError as ve:
169+
... print(ve)
170+
n=21 must be a square number, but isqrt(n)=4 does not satisfy n = k*k.
171+
172+
>>> try:
173+
... bs = SquareBitStringProblem("a")
174+
... except TypeError as te:
175+
... print(te)
176+
n should be an instance of int but is str, namely 'a'.
177+
"""
178+
179+
def __init__(self, n: int) -> None:
180+
"""
181+
Initialize the square bitstring problem.
182+
183+
:param n: the dimension of the problem (must be a perfect square)
184+
"""
185+
super().__init__(check_int_range(n, "n", 4))
186+
k: Final[int] = check_int_range(isqrt(n), "k", 2)
187+
if (k * k) != n:
188+
raise ValueError(f"n={n} must be a square number, but"
189+
f" isqrt(n)={k} does not satisfy n = k*k.")
190+
#: the k value, i.e., the number of bits per row and column of
191+
#: the square
192+
self.k: Final[int] = k
193+
194+
def log_parameters_to(self, logger: KeyValueLogSection) -> None:
195+
"""
196+
Log all parameters of this component as key-value pairs.
197+
198+
:param logger: the logger for the parameters
199+
200+
>>> from moptipy.utils.logger import InMemoryLogger
201+
>>> with InMemoryLogger() as l:
202+
... with l.key_values("C") as kv:
203+
... SquareBitStringProblem(49).log_parameters_to(kv)
204+
... text = l.get_log()
205+
>>> text[1]
206+
'name: squarebitstringproblem_49'
207+
>>> text[3]
208+
'lowerBound: 0'
209+
>>> text[4]
210+
'upperBound: 49'
211+
>>> text[5]
212+
'n: 49'
213+
>>> text[6]
214+
'k: 7'
215+
>>> len(text)
216+
8
217+
"""
218+
super().log_parameters_to(logger)
219+
logger.key_value("k", self.k)
220+
221+
222+
class BitStringNKProblem(BitStringProblem):
223+
"""
224+
A bit string problem with a second parameter `k` with `1 < k < n/2`.
225+
226+
>>> sb = BitStringNKProblem(9, 3)
227+
>>> sb.n
228+
9
229+
>>> sb.k
230+
3
231+
232+
>>> try:
233+
... bs = BitStringNKProblem(0, 3)
234+
... except ValueError as ve:
235+
... print(ve)
236+
n=0 is invalid, must be in 6..1000000000.
237+
238+
>>> try:
239+
... bs = BitStringNKProblem(5, 2)
240+
... except ValueError as ve:
241+
... print(ve)
242+
n=5 is invalid, must be in 6..1000000000.
243+
244+
>>> try:
245+
... bs = BitStringNKProblem(21, 20)
246+
... except ValueError as ve:
247+
... print(ve)
248+
k=20 is invalid, must be in 2..9.
249+
250+
>>> try:
251+
... bs = BitStringNKProblem("a", 3)
252+
... except TypeError as te:
253+
... print(te)
254+
n should be an instance of int but is str, namely 'a'.
255+
256+
>>> try:
257+
... bs = BitStringNKProblem(13, "x")
258+
... except TypeError as te:
259+
... print(te)
260+
k should be an instance of int but is str, namely 'x'.
261+
"""
262+
263+
def __init__(self, n: int, k: int) -> None: # +book
264+
"""
265+
Initialize the n-k objective function.
266+
267+
:param n: the dimension of the problem
268+
:param k: the second parameter
269+
"""
270+
super().__init__(check_int_range(n, "n", 6))
271+
#: the second parameter, with `1 < k < n/2`
272+
self.k: Final[int] = check_int_range(k, "k", 2, (n >> 1) - 1)
273+
274+
def __str__(self) -> str:
275+
"""
276+
Get the name of the objective function.
277+
278+
:return: `class_` + length of string + `_` + k
279+
280+
>>> BitStringNKProblem(13, 4)
281+
bitstringnkproblem_13_4
282+
"""
283+
return f"{super().__str__()}_{self.k}"
284+
285+
def log_parameters_to(self, logger: KeyValueLogSection) -> None:
286+
"""
287+
Log all parameters of this component as key-value pairs.
288+
289+
:param logger: the logger for the parameters
290+
291+
>>> from moptipy.utils.logger import InMemoryLogger
292+
>>> with InMemoryLogger() as l:
293+
... with l.key_values("C") as kv:
294+
... BitStringNKProblem(23, 7).log_parameters_to(kv)
295+
... text = l.get_log()
296+
>>> text[1]
297+
'name: bitstringnkproblem_23_7'
298+
>>> text[3]
299+
'lowerBound: 0'
300+
>>> text[4]
301+
'upperBound: 23'
302+
>>> text[5]
303+
'n: 23'
304+
>>> text[6]
305+
'k: 7'
306+
>>> len(text)
307+
8
308+
"""
309+
super().log_parameters_to(logger)
310+
logger.key_value("k", self.k)

0 commit comments

Comments
 (0)