Skip to content

Commit

Permalink
added more bitstring-based benchmark problems, added more doctests, a…
Browse files Browse the repository at this point in the history
…nd fixed several issues
  • Loading branch information
thomasWeise committed Nov 11, 2024
1 parent a525d46 commit d50f286
Show file tree
Hide file tree
Showing 20 changed files with 7,745 additions and 133 deletions.
64 changes: 44 additions & 20 deletions moptipy/examples/bitstrings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,48 @@
The following benchmark problems are provided:
1. :mod:`~moptipy.examples.bitstrings.ising1d`, the one-dimensional
Ising model, where the goal is that all bits should have the same value as
their neighbors.
2. The :mod:`~moptipy.examples.bitstrings.jump` problem is equivalent
to :mod:`~moptipy.examples.bitstrings.onemax`, but has a deceptive
region right before the optimum.
3. The :mod:`~moptipy.examples.bitstrings.leadingones` problem,
where the goal is to find a bit string with the maximum number of leading
ones.
4. The :mod:`~moptipy.examples.bitstrings.onemax` problem, where the
goal is to find a bit string with the maximum number of ones.
5. The :mod:`~moptipy.examples.bitstrings.trap` problem, which is like
OneMax, but with the optimum and worst-possible solution swapped. This
problem is therefore highly deceptive.
6. The :mod:`~moptipy.examples.bitstrings.w_model`, a benchmark
problem with tunable epistasis, uniform neutrality, and
ruggedness/deceptiveness.
7. The :mod:`~moptipy.examples.bitstrings.zeromax` problem, where the
goal is to find a bit string with the maximum number of zeros. This is the
opposite of the OneMax problem.
1. :mod:`~moptipy.examples.bitstrings.ising1d`, the one-dimensional
Ising model, where the goal is that all bits should have the same value as
their neighbors in a ring.
2. :mod:`~moptipy.examples.bitstrings.ising2d`, the two-dimensional
Ising model, where the goal is that all bits should have the same value as
their neighbors on a torus.
3. The :mod:`~moptipy.examples.bitstrings.jump` problem is equivalent
to :mod:`~moptipy.examples.bitstrings.onemax`, but has a deceptive
region right before the optimum.
4. The :mod:`~moptipy.examples.bitstrings.leadingones` problem,
where the goal is to find a bit string with the maximum number of leading
ones.
5. The :mod:`~moptipy.examples.bitstrings.linearharmonic` problem,
where the goal is to find a bit string with the all ones, like in
:mod:`~moptipy.examples.bitstrings.onemax`, but this time all bits have
a different weight (namely their index, starting at 1).
6. The :mod:`~moptipy.examples.bitstrings.nqueens`, where the goal is to
place `k` queens on a `k * k`-sized chess board such that no queen can
beat any other queen.
7. The :mod:`~moptipy.examples.bitstrings.onemax` problem, where the
goal is to find a bit string with the maximum number of ones.
8. The :mod:`~moptipy.examples.bitstrings.plateau` problem similar to the
:mod:`~moptipy.examples.bitstrings.jump` problem, but this time the
optimum is surrounded by a region of neutrality.
9. The :mod:`~moptipy.examples.bitstrings.trap` problem, which is like
OneMax, but with the optimum and worst-possible solution swapped. This
problem is therefore highly deceptive.
10. The :mod:`~moptipy.examples.bitstrings.twomax` problem has the global
optimum at the string of all `1` bits and a local optimum at the string
of all `0` bits. Both have basins of attraction of about the same size.
11. The :mod:`~moptipy.examples.bitstrings.w_model`, a benchmark
problem with tunable epistasis, uniform neutrality, and
ruggedness/deceptiveness.
12. The :mod:`~moptipy.examples.bitstrings.zeromax` problem, where the
goal is to find a bit string with the maximum number of zeros. This is the
opposite of the OneMax problem.
Parts of the code here are related to the research work of
Mr. Jiazheng ZENG (曾嘉政), a Master's student at the Institute of Applied
Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of
Artificial Intelligence and Big Data (人工智能与大数据学院) at
Hefei University (合肥大学) in
Hefei, Anhui, China (中国安徽省合肥市) under the supervision of
Prof. Dr. Thomas Weise (汤卫思教授).
"""
222 changes: 217 additions & 5 deletions moptipy/examples/bitstrings/bitstring_problem.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
"""A base class for bitstring-based problems."""
"""
A base class for bitstring-based problems.
Many benchmark problems from discrete optimization are simple functions
defined over bit strings. We here offer the class :class:`BitStringProblem`,
which provides reasonable default behavior and several utilities for
implementing such problems.
"""

from math import isqrt
from typing import Final

from pycommons.types import check_int_range
Expand All @@ -16,7 +24,26 @@ class BitStringProblem(Objective):
This base class has a set of default behaviors. It has an attribute
:attr:`n` denoting the lengths of the accepted bit strings. Its
:meth:`lower_bound` returns `0` and its :meth:`upper_bound` returns
:attr:`n`. :meth:`is_always_integer` returns `True`.
:attr:`n`. :meth:`is_always_integer` returns `True`. If also offers
the method :meth:`space` which returns an instance of
:class:`~moptipy.spaces.bitstrings.BitStrings` for bit strings of
length :attr:`n`.
>>> bs = BitStringProblem(1)
>>> bs.n
1
>>> try:
... bs = BitStringProblem(0)
... except ValueError as ve:
... print(ve)
n=0 is invalid, must be in 1..1000000000.
>>> try:
... bs = BitStringProblem("a")
... except TypeError as te:
... print(te)
n should be an instance of int but is str, namely 'a'.
"""

def __init__(self, n: int) -> None: # +book
Expand All @@ -33,6 +60,9 @@ def lower_bound(self) -> int:
"""
Get the lower bound of the bit string based problem.
By default, this method returns `0`. Problems where the lower bound
differs should override this method.
:return: 0
>>> print(BitStringProblem(10).lower_bound())
Expand All @@ -44,7 +74,10 @@ def upper_bound(self) -> int:
"""
Get the upper bound of the bit string based problem.
:return: the length of the bit string
The default value is the length of the bit string. Problems where the
upper bound differs should overrrite this method.
:return: by default, this is the length of the bit string
>>> print(BitStringProblem(7).upper_bound())
7
Expand All @@ -56,7 +89,8 @@ def is_always_integer(self) -> bool:
Return `True` if the `evaluate` function always returns an `int`.
This pre-defined function for bit-string based problems will always
return `True`.
return `True`. Problems where this is not the case should overwrite
this method.
:retval True: always
"""
Expand Down Expand Up @@ -84,7 +118,7 @@ def log_parameters_to(self, logger: KeyValueLogSection) -> None:
... BitStringProblem(5).log_parameters_to(kv)
... text = l.get_log()
>>> text[1]
'name: BitStringProblem'
'name: bitstringproblem_5'
>>> text[3]
'lowerBound: 0'
>>> text[4]
Expand All @@ -96,3 +130,181 @@ def log_parameters_to(self, logger: KeyValueLogSection) -> None:
"""
super().log_parameters_to(logger)
logger.key_value("n", self.n)

def __str__(self) -> str:
"""
Get the name of the problem.
:returns: the name of the problem, which by default is the class name
in lower case, followed by an underscore and the number of bits
"""
return f"{super().__str__().lower()}_{self.n}"


class SquareBitStringProblem(BitStringProblem):
"""
A bitstring problem which requires that the string length is square.
>>> sb = SquareBitStringProblem(9)
>>> sb.n
9
>>> sb.k
3
>>> try:
... bs = SquareBitStringProblem(0)
... except ValueError as ve:
... print(ve)
n=0 is invalid, must be in 4..1000000000.
>>> try:
... bs = SquareBitStringProblem(3)
... except ValueError as ve:
... print(ve)
n=3 is invalid, must be in 4..1000000000.
>>> try:
... bs = SquareBitStringProblem(21)
... except ValueError as ve:
... print(ve)
n=21 must be a square number, but isqrt(n)=4 does not satisfy n = k*k.
>>> try:
... bs = SquareBitStringProblem("a")
... except TypeError as te:
... print(te)
n should be an instance of int but is str, namely 'a'.
"""

def __init__(self, n: int) -> None:
"""
Initialize the square bitstring problem.
:param n: the dimension of the problem (must be a perfect square)
"""
super().__init__(check_int_range(n, "n", 4))
k: Final[int] = check_int_range(isqrt(n), "k", 2)
if (k * k) != n:
raise ValueError(f"n={n} must be a square number, but"
f" isqrt(n)={k} does not satisfy n = k*k.")
#: the k value, i.e., the number of bits per row and column of
#: the square
self.k: Final[int] = k

def log_parameters_to(self, logger: KeyValueLogSection) -> None:
"""
Log all parameters of this component as key-value pairs.
:param logger: the logger for the parameters
>>> from moptipy.utils.logger import InMemoryLogger
>>> with InMemoryLogger() as l:
... with l.key_values("C") as kv:
... SquareBitStringProblem(49).log_parameters_to(kv)
... text = l.get_log()
>>> text[1]
'name: squarebitstringproblem_49'
>>> text[3]
'lowerBound: 0'
>>> text[4]
'upperBound: 49'
>>> text[5]
'n: 49'
>>> text[6]
'k: 7'
>>> len(text)
8
"""
super().log_parameters_to(logger)
logger.key_value("k", self.k)


class BitStringNKProblem(BitStringProblem):
"""
A bit string problem with a second parameter `k` with `1 < k < n/2`.
>>> sb = BitStringNKProblem(9, 3)
>>> sb.n
9
>>> sb.k
3
>>> try:
... bs = BitStringNKProblem(0, 3)
... except ValueError as ve:
... print(ve)
n=0 is invalid, must be in 6..1000000000.
>>> try:
... bs = BitStringNKProblem(5, 2)
... except ValueError as ve:
... print(ve)
n=5 is invalid, must be in 6..1000000000.
>>> try:
... bs = BitStringNKProblem(21, 20)
... except ValueError as ve:
... print(ve)
k=20 is invalid, must be in 2..9.
>>> try:
... bs = BitStringNKProblem("a", 3)
... except TypeError as te:
... print(te)
n should be an instance of int but is str, namely 'a'.
>>> try:
... bs = BitStringNKProblem(13, "x")
... except TypeError as te:
... print(te)
k should be an instance of int but is str, namely 'x'.
"""

def __init__(self, n: int, k: int) -> None: # +book
"""
Initialize the n-k objective function.
:param n: the dimension of the problem
:param k: the second parameter
"""
super().__init__(check_int_range(n, "n", 6))
#: the second parameter, with `1 < k < n/2`
self.k: Final[int] = check_int_range(k, "k", 2, (n >> 1) - 1)

def __str__(self) -> str:
"""
Get the name of the objective function.
:return: `class_` + length of string + `_` + k
>>> BitStringNKProblem(13, 4)
bitstringnkproblem_13_4
"""
return f"{super().__str__()}_{self.k}"

def log_parameters_to(self, logger: KeyValueLogSection) -> None:
"""
Log all parameters of this component as key-value pairs.
:param logger: the logger for the parameters
>>> from moptipy.utils.logger import InMemoryLogger
>>> with InMemoryLogger() as l:
... with l.key_values("C") as kv:
... BitStringNKProblem(23, 7).log_parameters_to(kv)
... text = l.get_log()
>>> text[1]
'name: bitstringnkproblem_23_7'
>>> text[3]
'lowerBound: 0'
>>> text[4]
'upperBound: 23'
>>> text[5]
'n: 23'
>>> text[6]
'k: 7'
>>> len(text)
8
"""
super().log_parameters_to(logger)
logger.key_value("k", self.k)
Loading

0 comments on commit d50f286

Please sign in to comment.