Source code for moptipy.evaluation.base

"""Some internal helper functions and base classes."""

from dataclasses import dataclass
from typing import Any, Callable, Final

from pycommons.types import check_int_range, type_error
from pycommons.version import __version__ as pycommons_version

from moptipy.utils.nputils import rand_seed_check
from moptipy.utils.strings import sanitize_name
from moptipy.version import __version__ as moptipy_version

#: The key for the total number of runs.
KEY_N: Final[str] = "n"
#: a key for the objective function name
KEY_OBJECTIVE_FUNCTION: Final[str] = "objective"
#: a key for the encoding name
KEY_ENCODING: Final[str] = "encoding"

#: The unit of the time axis if time is measured in milliseconds.
TIME_UNIT_MILLIS: Final[str] = "ms"
#: The unit of the time axis of time is measured in FEs
TIME_UNIT_FES: Final[str] = "FEs"

#: The name of the raw objective values data.
F_NAME_RAW: Final[str] = "plainF"
#: The name of the scaled objective values data.
F_NAME_SCALED: Final[str] = "scaledF"
#: The name of the normalized objective values data.
F_NAME_NORMALIZED: Final[str] = "normalizedF"


[docs] def check_time_unit(time_unit: Any) -> str: """ Check that the time unit is OK. :param time_unit: the time unit :return: the time unit string >>> check_time_unit("FEs") 'FEs' >>> check_time_unit("ms") 'ms' >>> try: ... check_time_unit(1) ... except TypeError as te: ... print(te) time_unit should be an instance of str but is int, namely '1'. >>> try: ... check_time_unit("blabedibla") ... except ValueError as ve: ... print(ve) Invalid time unit 'blabedibla', only 'FEs' and 'ms' are permitted. """ if not isinstance(time_unit, str): raise type_error(time_unit, "time_unit", str) if time_unit in (TIME_UNIT_FES, TIME_UNIT_MILLIS): return time_unit raise ValueError( f"Invalid time unit {time_unit!r}, only {TIME_UNIT_FES!r} " f"and {TIME_UNIT_MILLIS!r} are permitted.")
[docs] def check_f_name(f_name: Any) -> str: """ Check whether an objective value name is valid. :param f_name: the name of the objective function dimension :return: the name of the objective function dimension >>> check_f_name("plainF") 'plainF' >>> check_f_name("scaledF") 'scaledF' >>> check_f_name("normalizedF") 'normalizedF' >>> try: ... check_f_name(1.0) ... except TypeError as te: ... print(te) f_name should be an instance of str but is float, namely '1.0'. >>> try: ... check_f_name("oops") ... except ValueError as ve: ... print(ve) Invalid f name 'oops', only 'plainF', 'scaledF', and 'normalizedF' \ are permitted. """ if not isinstance(f_name, str): raise type_error(f_name, "f_name", str) if f_name in (F_NAME_RAW, F_NAME_SCALED, F_NAME_NORMALIZED): return f_name raise ValueError( f"Invalid f name {f_name!r}, only {F_NAME_RAW!r}, " f"{F_NAME_SCALED!r}, and {F_NAME_NORMALIZED!r} are permitted.")
def _set_name(dest: object, name: str, what: str, none_allowed: bool = False, empty_to_none: bool = True) -> None: """ Check and set a name. :param dest: the destination :param name: the name to set :param what: the name's type :param none_allowed: is `None` allowed? :param empty_to_none: If both `none_allowed` and `empty_to_none` are `True`, then empty strings are converted to `None` >>> class TV: ... algorithm: str ... instance: str | None >>> t = TV() >>> _set_name(t, "bla", "algorithm", False) >>> t.algorithm 'bla' >>> _set_name(t, "xbla", "instance", True) >>> t.instance 'xbla' >>> _set_name(t, None, "instance", True) >>> print(t.instance) None >>> t.instance = "x" >>> _set_name(t, " ", "instance", True) >>> print(t.instance) None >>> try: ... _set_name(t, 1, "algorithm") ... except TypeError as te: ... print(te) algorithm name should be an instance of str but is int, namely '1'. >>> t.algorithm 'bla' >>> try: ... _set_name(t, " ", "algorithm") ... except ValueError as ve: ... print(ve) algorithm name cannot be empty of just consist of white space, but \ ' ' does. >>> t.algorithm 'bla' >>> try: ... _set_name(t, "a a", "instance") ... except ValueError as ve: ... print(ve) Invalid instance name 'a a'. >>> print(t.instance) None >>> try: ... _set_name(t, " ", "instance", True, False) ... except ValueError as ve: ... print(ve) instance name cannot be empty of just consist of white space, but \ ' ' does. >>> print(t.instance) None """ use_name = name if isinstance(name, str): use_name = use_name.strip() if len(use_name) <= 0: if empty_to_none and none_allowed: use_name = None else: raise ValueError(f"{what} name cannot be empty of just cons" f"ist of white space, but {name!r} does.") elif use_name != sanitize_name(use_name): raise ValueError(f"Invalid {what} name {name!r}.") elif not ((name is None) and none_allowed): raise type_error(name, f"{what} name", (str, None) if none_allowed else str) object.__setattr__(dest, what, use_name)
[docs] class EvaluationDataElement: """A base class for all the data classes in this module.""" def _tuple(self) -> tuple[Any, ...]: """ Create a tuple with all the data of this data class for comparison. All the relevant data of an instance of this class is stored in a tuple. The tuple is then used in the dunder methods for comparisons. The returned tuple *must* be based on the scheme `tuple[str, str, str, str, str, int, int, str, str]`. They can be shorter than this and they can be longer, but they must adhere to this basic scheme: 1. class name 2. algorithm name, or `""` if algorithm name is `None` 3. instance name, or `""` if instance name is `None` 4. objective name, `""` objective name is `None` 5. encoding name, or `""` encoding name is `None` 6. number of runs, or `0` if no number of runs is specified or `1` if the data concerns exactly one run 7. the random seed, or `-1` if no random seed is specified 8. the string time unit, or `""` if no time unit is given 9. the scaling name of the objective function, or `""` if no scaling name is given If the tuples are longer, then all values following after this must be integers or floats. >>> EvaluationDataElement()._tuple() ('EvaluationDataElement',) :returns: a tuple with all the data of this class, where `None` values are masked out """ return (self.__class__.__name__, ) def __eq__(self, other) -> bool: """ Compare for `==` with another object based on the `_tuple()` value. :param other: the other object to compare to, must be an instance of :class:`EvaluationDataElement` :retval `True`: if the `other` object's `_tuple()` representation is `==` with this object's `_tuple()` representation :retval `False`: otherwise :raises NotImplementedError: if the other object is not an instance of :class:`EvaluationDataElement` and therefore cannot be compared. >>> PerRunData("a", "i", "f", "e", 234) == PerRunData( ... "a", "i", "f", "e", 234) True >>> PerRunData("a", "i", "f", "e", 234) == PerRunData( ... "a", "j", "f", "e", 234) False >>> try: ... PerRunData("a", "i", "f", "e", 234) == 3 ... except NotImplementedError as ni: ... print(ni) Cannot compare PerRunData(algorithm='a', instance='i', \ objective='f', encoding='e', rand_seed=234) with 3 for ==. """ if isinstance(other, EvaluationDataElement): return self._tuple() == other._tuple() raise NotImplementedError( f"Cannot compare {self} with {other} for ==.") def __ne__(self, other) -> bool: """ Compare for `!=` with another object based on the `_tuple()` value. :param other: the other object to compare to, must be an instance of :class:`EvaluationDataElement` :retval `True`: if the `other` object's `_tuple()` representation is `!=` with this object's `_tuple()` representation :retval `False`: otherwise :raises NotImplementedError: if the other object is not an instance of :class:`EvaluationDataElement` and therefore cannot be compared. >>> PerRunData("a", "i", "f", "e", 234) != PerRunData( ... "a", "i", "f", "e", 234) False >>> PerRunData("a", "i", "f", "e", 234) != PerRunData( ... "a", "j", "f", "e", 234) True >>> try: ... PerRunData("a", "i", "f", "e", 234) != 3 ... except NotImplementedError as ni: ... print(ni) Cannot compare PerRunData(algorithm='a', instance='i', \ objective='f', encoding='e', rand_seed=234) with 3 for !=. """ if isinstance(other, EvaluationDataElement): return self._tuple() != other._tuple() raise NotImplementedError( f"Cannot compare {self} with {other} for !=.") def __lt__(self, other) -> bool: """ Compare for `<` with another object based on the `_tuple()` value. :param other: the other object to compare to, must be an instance of :class:`EvaluationDataElement` :retval `True`: if the `other` object's `_tuple()` representation is `<` with this object's `_tuple()` representation :retval `False`: otherwise :raises NotImplementedError: if the other object is not an instance of :class:`EvaluationDataElement` and therefore cannot be compared. >>> PerRunData("a", "i", "f", "e", 234) < PerRunData( ... "a", "i", "f", "e", 234) False >>> PerRunData("a", "i", "f", "e", 234) < PerRunData( ... "a", "j", "f", "e", 234) True >>> PerRunData("a", "j", "f", "e", 234) < PerRunData( ... "a", "i", "f", "e", 234) False >>> try: ... PerRunData("a", "i", "f", "e", 234) < 3 ... except NotImplementedError as ni: ... print(ni) Cannot compare PerRunData(algorithm='a', instance='i', \ objective='f', encoding='e', rand_seed=234) with 3 for <. """ if isinstance(other, EvaluationDataElement): return self._tuple() < other._tuple() raise NotImplementedError( f"Cannot compare {self} with {other} for <.") def __le__(self, other) -> bool: """ Compare for `<=` with another object based on the `_tuple()` value. :param other: the other object to compare to, must be an instance of :class:`EvaluationDataElement` :retval `True`: if the `other` object's `_tuple()` representation is `<=` with this object's `_tuple()` representation :retval `False`: otherwise :raises NotImplementedError: if the other object is not an instance of :class:`EvaluationDataElement` and therefore cannot be compared. >>> PerRunData("a", "i", "f", "e", 234) <= PerRunData( ... "a", "i", "f", "e", 234) True >>> PerRunData("a", "i", "f", "e", 234) <= PerRunData( ... "a", "j", "f", "e", 234) True >>> PerRunData("a", "j", "f", "e", 234) < PerRunData( ... "a", "i", "f", "e", 234) False >>> try: ... PerRunData("a", "i", "f", "e", 234) <= 3 ... except NotImplementedError as ni: ... print(ni) Cannot compare PerRunData(algorithm='a', instance='i', \ objective='f', encoding='e', rand_seed=234) with 3 for <=. """ if isinstance(other, EvaluationDataElement): return self._tuple() <= other._tuple() raise NotImplementedError( f"Cannot compare {self} with {other} for <=.") def __gt__(self, other) -> bool: """ Compare for `>` with another object based on the `_tuple()` value. :param other: the other object to compare to, must be an instance of :class:`EvaluationDataElement` :retval `True`: if the `other` object's `_tuple()` representation is `>` with this object's `_tuple()` representation :retval `False`: otherwise :raises NotImplementedError: if the other object is not an instance of :class:`EvaluationDataElement` and therefore cannot be compared. >>> PerRunData("a", "i", "f", "e", 234) > PerRunData( ... "a", "i", "f", "e", 234) False >>> PerRunData("a", "i", "f", "e", 234) > PerRunData( ... "a", "j", "f", "e", 234) False >>> PerRunData("a", "j", "f", "e", 234) > PerRunData( ... "a", "i", "f", "e", 234) True >>> try: ... PerRunData("a", "i", "f", "e", 234) > 3 ... except NotImplementedError as ni: ... print(ni) Cannot compare PerRunData(algorithm='a', instance='i', \ objective='f', encoding='e', rand_seed=234) with 3 for >. """ if isinstance(other, EvaluationDataElement): return self._tuple() > other._tuple() raise NotImplementedError( f"Cannot compare {self} with {other} for >.") def __ge__(self, other) -> bool: """ Compare for `>=` with another object based on the `_tuple()` value. :param other: the other object to compare to, must be an instance of :class:`EvaluationDataElement` :retval `True`: if the `other` object's `_tuple()` representation is `>=` with this object's `_tuple()` representation :retval `False`: otherwise :raises NotImplementedError: if the other object is not an instance of :class:`EvaluationDataElement` and therefore cannot be compared. >>> PerRunData("a", "i", "f", "e", 234) >= PerRunData( ... "a", "i", "f", "e", 234) True >>> PerRunData("a", "i", "f", "e", 234) >= PerRunData( ... "a", "j", "f", "e", 234) False >>> PerRunData("a", "j", "f", "e", 234) >= PerRunData( ... "a", "i", "f", "e", 234) True >>> try: ... PerRunData("a", "i", "f", "e", 234) >= 3 ... except NotImplementedError as ni: ... print(ni) Cannot compare PerRunData(algorithm='a', instance='i', \ objective='f', encoding='e', rand_seed=234) with 3 for >=. """ if isinstance(other, EvaluationDataElement): return self._tuple() >= other._tuple() raise NotImplementedError( f"Cannot compare {self} with {other} for >=.")
[docs] @dataclass(frozen=True, init=False, order=False, eq=False) class PerRunData(EvaluationDataElement): """ An immutable record of information over a single run. >>> p = PerRunData("a", "i", "f", None, 234) >>> p.instance 'i' >>> p.algorithm 'a' >>> p.objective 'f' >>> print(p.encoding) None >>> p.rand_seed 234 >>> p = PerRunData("a", "i", "f", "e", 234) >>> p.instance 'i' >>> p.algorithm 'a' >>> p.objective 'f' >>> p.encoding 'e' >>> p.rand_seed 234 >>> try: ... PerRunData(3, "i", "f", "e", 234) ... except TypeError as te: ... print(te) algorithm name should be an instance of str but is int, namely '3'. >>> try: ... PerRunData("@1 2", "i", "f", "e", 234) ... except ValueError as ve: ... print(ve) Invalid algorithm name '@1 2'. >>> try: ... PerRunData("x", 3.2, "f", "e", 234) ... except TypeError as te: ... print(te) instance name should be an instance of str but is float, namely '3.2'. >>> try: ... PerRunData("x", "sdf i", "f", "e", 234) ... except ValueError as ve: ... print(ve) Invalid instance name 'sdf i'. >>> try: ... PerRunData("a", "i", True, "e", 234) ... except TypeError as te: ... print(te) objective name should be an instance of str but is bool, namely 'True'. >>> try: ... PerRunData("x", "i", "d-f", "e", 234) ... except ValueError as ve: ... print(ve) Invalid objective name 'd-f'. >>> try: ... PerRunData("x", "i", "f", 54.2, 234) ... except TypeError as te: ... print(te) encoding name should be an instance of any in {None, str} but is float, \ namely '54.2'. >>> try: ... PerRunData("y", "i", "f", "x x", 234) ... except ValueError as ve: ... print(ve) Invalid encoding name 'x x'. >>> try: ... PerRunData("x", "i", "f", "e", 3.3) ... except TypeError as te: ... print(te) rand_seed should be an instance of int but is float, namely '3.3'. >>> try: ... PerRunData("x", "i", "f", "e", -234) ... except ValueError as ve: ... print(ve) rand_seed=-234 is invalid, must be in 0..18446744073709551615. """ #: The algorithm that was applied. algorithm: str #: The problem instance that was solved. instance: str #: the name of the objective function objective: str #: the encoding, if any, or `None` if no encoding was used encoding: str | None #: The seed of the random number generator. rand_seed: int def __init__(self, algorithm: str, instance: str, objective: str, encoding: str | None, rand_seed: int): """ Create a per-run data record. :param algorithm: the algorithm name :param instance: the instance name :param objective: the name of the objective function :param encoding: the name of the encoding that was used, if any, or `None` if no encoding was used :param rand_seed: the random seed """ _set_name(self, algorithm, "algorithm") _set_name(self, instance, "instance") _set_name(self, objective, "objective") _set_name(self, encoding, "encoding", True, False) object.__setattr__(self, "rand_seed", rand_seed_check(rand_seed)) def _tuple(self) -> tuple[Any, ...]: """ Get the tuple representation of this object used in comparisons. :return: the comparison-relevant data of this object in a tuple >>> PerRunData("a", "i", "f", "e", 234)._tuple() ('PerRunData', 'a', 'i', 'f', 'e', 1, 234) >>> PerRunData("a", "i", "f", None, 234)._tuple() ('PerRunData', 'a', 'i', 'f', '', 1, 234) """ return (self.__class__.__name__, self.algorithm, self.instance, self.objective, "" if self.encoding is None else self.encoding, 1, self.rand_seed)
[docs] @dataclass(frozen=True, init=False, order=False, eq=False) class MultiRunData(EvaluationDataElement): """ A class that represents statistics over a set of runs. If one algorithm*instance is used, then `algorithm` and `instance` are defined. Otherwise, only the parameter which is the same over all recorded runs is defined. >>> p = MultiRunData("a", "i", "f", None, 3) >>> p.instance 'i' >>> p.algorithm 'a' >>> p.objective 'f' >>> print(p.encoding) None >>> p.n 3 >>> p = MultiRunData(None, None, None, "x", 3) >>> print(p.instance) None >>> print(p.algorithm) None >>> print(p.objective) None >>> p.encoding 'x' >>> p.n 3 >>> try: ... MultiRunData(1, "i", "f", "e", 234) ... except TypeError as te: ... print(te) algorithm name should be an instance of any in {None, str} but is int, \ namely '1'. >>> try: ... MultiRunData("x x", "i", "f", "e", 234) ... except ValueError as ve: ... print(ve) Invalid algorithm name 'x x'. >>> try: ... MultiRunData("a", 5.5, "f", "e", 234) ... except TypeError as te: ... print(te) instance name should be an instance of any in {None, str} but is float, \ namely '5.5'. >>> try: ... MultiRunData("x", "a-i", "f", "e", 234) ... except ValueError as ve: ... print(ve) Invalid instance name 'a-i'. >>> try: ... MultiRunData("a", "i", True, "e", 234) ... except TypeError as te: ... print(te) objective name should be an instance of any in {None, str} but is bool, \ namely 'True'. >>> try: ... MultiRunData("xx", "i", "d'@f", "e", 234) ... except ValueError as ve: ... print(ve) Invalid objective name "d'@f". >>> try: ... MultiRunData("yy", "i", "f", -9.4, 234) ... except TypeError as te: ... print(te) encoding name should be an instance of any in {None, str} but is float, \ namely '-9.4'. >>> try: ... MultiRunData("xx", "i", "f", "e-{a", 234) ... except ValueError as ve: ... print(ve) Invalid encoding name 'e-{a'. >>> try: ... MultiRunData("x", "i", "f", "e", -1.234) ... except TypeError as te: ... print(te) n should be an instance of int but is float, namely '-1.234'. >>> try: ... MultiRunData("xx", "i", "f", "e", 1_000_000_000_000_000_000_000) ... except ValueError as ve: ... print(ve) n=1000000000000000000000 is invalid, must be in 1..1000000000000000. """ #: The algorithm that was applied, if the same over all runs. algorithm: str | None #: The problem instance that was solved, if the same over all runs. instance: str | None #: the name of the objective function, if the same over all runs objective: str | None #: the encoding, if any, or `None` if no encoding was used or if it was #: not the same over all runs encoding: str | None #: The number of runs over which the statistic information is computed. n: int def __init__(self, algorithm: str | None, instance: str | None, objective: str | None, encoding: str | None, n: int): """ Create the dataset of an experiment-setup combination. :param algorithm: the algorithm name, if all runs are with the same algorithm, `None` otherwise :param instance: the instance name, if all runs are on the same instance, `None` otherwise :param objective: the objective name, if all runs are on the same objective function, `None` otherwise :param encoding: the encoding name, if all runs are on the same encoding and an encoding was actually used, `None` otherwise :param n: the total number of runs """ _set_name(self, algorithm, "algorithm", True, False) _set_name(self, instance, "instance", True, False) _set_name(self, objective, "objective", True, False) _set_name(self, encoding, "encoding", True, False) object.__setattr__(self, "n", check_int_range( n, "n", 1, 1_000_000_000_000_000)) def _tuple(self) -> tuple[Any, ...]: """ Get the tuple representation of this object used in comparisons. :return: the comparison-relevant data of this object in a tuple >>> MultiRunData("a", "i", "f", None, 3)._tuple() ('MultiRunData', 'a', 'i', 'f', '', 3, -1) >>> MultiRunData(None, "i", "f", "e", 31)._tuple() ('MultiRunData', '', 'i', 'f', 'e', 31, -1) >>> MultiRunData("x", None, "fy", "e1", 131)._tuple() ('MultiRunData', 'x', '', 'fy', 'e1', 131, -1) >>> MultiRunData("yx", "z", None, "xe1", 2131)._tuple() ('MultiRunData', 'yx', 'z', '', 'xe1', 2131, -1) """ return (self.__class__.__name__, "" if self.algorithm is None else self.algorithm, "" if self.instance is None else self.instance, "" if self.objective is None else self.objective, "" if self.encoding is None else self.encoding, self.n, -1)
[docs] @dataclass(frozen=True, init=False, order=False, eq=False) class MultiRun2DData(MultiRunData): """ A multi-run data based on one time and one objective dimension. >>> p = MultiRun2DData("a", "i", "f", None, 3, ... TIME_UNIT_FES, F_NAME_SCALED) >>> p.instance 'i' >>> p.algorithm 'a' >>> p.objective 'f' >>> print(p.encoding) None >>> p.n 3 >>> print(p.time_unit) FEs >>> print(p.f_name) scaledF >>> try: ... MultiRun2DData("a", "i", "f", None, 3, ... 3, F_NAME_SCALED) ... except TypeError as te: ... print(te) time_unit should be an instance of str but is int, namely '3'. >>> try: ... MultiRun2DData("a", "i", "f", None, 3, ... "sdfjsdf", F_NAME_SCALED) ... except ValueError as ve: ... print(ve) Invalid time unit 'sdfjsdf', only 'FEs' and 'ms' are permitted. >>> try: ... MultiRun2DData("a", "i", "f", None, 3, ... TIME_UNIT_FES, True) ... except TypeError as te: ... print(te) f_name should be an instance of str but is bool, namely 'True'. >>> try: ... MultiRun2DData("a", "i", "f", None, 3, ... TIME_UNIT_FES, "blablue") ... except ValueError as ve: ... print(ve) Invalid f name 'blablue', only 'plainF', 'scaledF', and 'normalizedF' \ are permitted. """ #: The unit of the time axis. time_unit: str #: the name of the objective value axis. f_name: str def __init__(self, algorithm: str | None, instance: str | None, objective: str | None, encoding: str | None, n: int, time_unit: str, f_name: str): """ Create multi-run data based on one time and one objective dimension. :param algorithm: the algorithm name, if all runs are with the same algorithm :param instance: the instance name, if all runs are on the same instance :param objective: the objective name, if all runs are on the same objective function, `None` otherwise :param encoding: the encoding name, if all runs are on the same encoding and an encoding was actually used, `None` otherwise :param n: the total number of runs :param time_unit: the time unit :param f_name: the objective dimension name """ super().__init__(algorithm, instance, objective, encoding, n) object.__setattr__(self, "time_unit", check_time_unit(time_unit)) object.__setattr__(self, "f_name", check_f_name(f_name)) def _tuple(self) -> tuple[Any, ...]: """ Get the tuple representation of this object used in comparisons. :return: the comparison-relevant data of this object in a tuple >>> MultiRun2DData("a", "i", "f", None, 3, ... TIME_UNIT_FES, F_NAME_SCALED)._tuple() ('MultiRun2DData', 'a', 'i', 'f', '', 3, -1, 'FEs', 'scaledF') >>> MultiRun2DData(None, "ix", None, "x", 43, ... TIME_UNIT_MILLIS, F_NAME_RAW)._tuple() ('MultiRun2DData', '', 'ix', '', 'x', 43, -1, 'ms', 'plainF') >>> MultiRun2DData("xa", None, None, None, 143, ... TIME_UNIT_MILLIS, F_NAME_NORMALIZED)._tuple() ('MultiRun2DData', 'xa', '', '', '', 143, -1, 'ms', 'normalizedF') """ return (self.__class__.__name__, "" if self.algorithm is None else self.algorithm, "" if self.instance is None else self.instance, "" if self.objective is None else self.objective, "" if self.encoding is None else self.encoding, self.n, -1, self.time_unit, self.f_name)
[docs] def get_instance(obj: PerRunData | MultiRunData) -> str | None: """ Get the instance of a given object. :param obj: the object :return: the instance string, or `None` if no instance is specified >>> p1 = MultiRunData("a", "i1", None, "x", 3) >>> get_instance(p1) 'i1' >>> p2 = PerRunData("a", "i2", "f", "x", 31) >>> get_instance(p2) 'i2' """ return obj.instance
[docs] def get_algorithm(obj: PerRunData | MultiRunData) -> str | None: """ Get the algorithm of a given object. :param obj: the object :return: the algorithm string, or `None` if no algorithm is specified >>> p1 = MultiRunData("a1", "i1", "f", "y", 3) >>> get_algorithm(p1) 'a1' >>> p2 = PerRunData("a2", "i2", "y", None, 31) >>> get_algorithm(p2) 'a2' """ return obj.algorithm
[docs] def sort_key(obj: PerRunData | MultiRunData) -> tuple[Any, ...]: """ Get the default sort key for the given object. The sort key is a tuple with well-defined field elements that should allow for a default and consistent sorting over many different elements of the experiment evaluation data API. Sorting should work also for lists containing elements of different classes. :param obj: the object :return: the sort key >>> p1 = MultiRunData("a1", "i1", "f", None, 3) >>> p2 = PerRunData("a2", "i2", "f", None, 31) >>> sort_key(p1) < sort_key(p2) True >>> sort_key(p1) >= sort_key(p2) False >>> p3 = MultiRun2DData("a", "i", "f", None, 3, ... TIME_UNIT_FES, F_NAME_SCALED) >>> sort_key(p3) < sort_key(p1) True >>> sort_key(p3) >= sort_key(p1) False """ # noinspection PyProtectedMember return obj._tuple()
def _csv_motipy_footer(dest: Callable[[str], Any]) -> None: """ Print the standard csv footer. :param dest: the destination to write to """ dest("") dest("This data has been generated with moptipy version " f"{moptipy_version} using pycommons version " f"{pycommons_version}.") dest("You can find moptipy at https://thomasweise.github.io/mopitpy.") dest( "You can find pycommons at https://thomasweise.github.io/pycommons.") #: a description of the algorithm field DESC_ALGORITHM: Final[str] = "the name of the algorithm setup that was used." #: a description of the instance field DESC_INSTANCE: Final[str] = ("the name of the problem instance to which the " "algorithm was applied.") #: a description of the objective function field DESC_OBJECTIVE_FUNCTION: Final[str] = \ ("the name of the objective function (often also called fitness function " "or cost function) that was used to rate the solution quality.") #: a description of the encoding field DESC_ENCODING: Final[str] = \ ("the name of the encoding, often also called genotype-phenotype mapping" ", used. In some problems, the search space on which the algorithm " "works is different from the space of possible solutions. For example, " "when solving a scheduling problem, maybe our optimization algorithm " "navigates in the space of permutations, but the solutions are Gantt " "charts. The encoding is the function that translates the points in " "the search space (e.g., permutations) to the points in the solution " "space (e.g., Gantt charts). Nothing if no encoding was used.")