diff --git a/moptipyapps/binpacking2d/instance.py b/moptipyapps/binpacking2d/instance.py index d2113d94..24f32b35 100644 --- a/moptipyapps/binpacking2d/instance.py +++ b/moptipyapps/binpacking2d/instance.py @@ -114,7 +114,8 @@ from importlib import resources # nosem from os.path import basename -from typing import Final, cast +from statistics import quantiles +from typing import Final, Iterable, cast import moptipy.utils.nputils as npu import numpy as np @@ -146,7 +147,7 @@ #: the index of the repetitions element in an item of an instance IDX_REPETITION: Final[int] = 2 -#: the the list of instance names of the 2DPackLib bin packing set downloaded +#: the list of instance names of the 2DPackLib bin packing set downloaded #: from https://site.unibo.it/operations-research/en/research/2dpacklib #: ('a*','beng*', 'cl*') as well as the four non-trivial 'Almost Squares in #: Almost Squares' instances ('asqas*'). @@ -260,6 +261,109 @@ "cl10_100_09", "cl10_100_10") +def __divide_based_on_size(instances: Iterable[str], + divisions: int) -> list[list[str]]: + """ + Divide the instances based on their size. + + :param instances: the instances + :param divisions: the number of divisions + :return: the divided instances + """ + insts: list[str] = list(instances) + if len(insts) <= 0: + return [insts] + + try: + loaded: list[Instance] = [Instance.from_resource(n) for n in insts] + except (OSError, ValueError): + return [insts] + + # get the quantiles = group divisions + qants: list[float | int] = quantiles(( + inst.n_items for inst in loaded), n=divisions, method="inclusive") + + # now put the instances into the groups + groups: list[list[str]] = [] + for inst in loaded: + inst_size = inst.n_items + idx: int = 0 + for q in qants: + if q > inst_size: + break + idx += 1 + while idx >= len(groups): + groups.append([]) + groups[idx].append(str(inst)) + + # remove useless groups + for idx in range(len(groups) - 1, -1, -1): + if len(groups[idx]) <= 0: + del groups[idx] + return groups + + +def _make_instance_groups(instances: Iterable[str]) \ + -> tuple[tuple[str, str | None, tuple[str, ...]], ...]: + """ + Make the standard instance groups from an instance name list. + + :return: the instance groups + """ + groups: list[tuple[str, str | None, tuple[str, ...]]] = [] + + a_divided: list[list[str]] = __divide_based_on_size(sorted( + b for b in instances if b.startswith("a") + and (len(b) == 3) and b[-1].isdigit() + and b[-2].isdigit()), 3) + if len(a_divided) > 0: + subnames: list[str | None] = [None] if (len(a_divided) <= 1) else ( + ["small", "large"] if (len(a_divided) <= 2) else + ["small", "med", "large"]) + for a_idx, a_group in enumerate(a_divided): + v: tuple[str, str | None, tuple[str, ...]] = ( + "a", subnames[a_idx], tuple(a_group)) + if len(v[2]) > 0: + groups.append(v) + + v = ("beng", "1-8", tuple(sorted( + b for b in instances if b.startswith("beng") + and (int(b[-2:]) < 9)))) + if len(v[2]) > 0: + groups.append(v) + + v = ("beng", "9-10", tuple(sorted( + b for b in instances if b.startswith("beng") + and (int(b[-2:]) >= 9)))) + if len(v[2]) > 0: + groups.append(v) + + for i in range(1, 11): + name: str = f"class {i}" + preprefix: str = f"cl0{i}" if i < 10 else f"cl{i}" + for n in (20, 40, 60, 80, 100): + prefix: str = f"{preprefix}_0{n}_" \ + if n < 100 else f"{preprefix}_{n}_" + v = (name, str(n), tuple(sorted( + b for b in _INSTANCES if b.startswith(prefix)))) + if len(v[2]) > 0: + groups.append(v) + + v = ("asqas", None, tuple(sorted( + b for b in _INSTANCES if b.startswith("asqas")))) + if len(v[2]) > 0: + groups.append(v) + + all_set: set[str] = set() + for g in groups: + all_set.update(g[2]) + inst_set: set[str] = set(instances) + if all_set != inst_set: + raise ValueError(f"group instances is {all_set!r} but " + f"instance set is {inst_set!r}!") + return tuple(groups) + + def __cutsq(matrix: np.ndarray) -> list[int]: """ Cut all items into squares via the CUTSQ procedure. @@ -714,6 +818,65 @@ def list_resources() -> tuple[str, ...]: """ return _INSTANCES + @staticmethod + def list_resources_groups() -> tuple[tuple[ + str, str | None, tuple[str, ...]], ...]: + """ + List the instance groups in the resources. + + One problem of the benchmark set for 2-dimensional bin packing is that + it has many instances: + + >>> len(Instance.list_resources()) + 557 + + With this function, we can group several of these instances together + in a way that is compliant with literature in order to then compute + statistics over these groups. Presenting data gathered over... + + >>> len(Instance.list_resources_groups()) + 56 + + ...groups is much easier than dealing with over 500 instances. + + :return: the instance groups, in a two level hierarchy. The result is + a sequence of tuples. Each tuple has the top-level group name and, + optionally, a second-level group name (or `None` if no second + level group exists). The third tuple element is a sequence of + instance names. + + >>> [(v[0], v[1], len(v[2])) for v in + ... Instance.list_resources_groups()] + [('a', 'small', 14), ('a', 'med', 14), ('a', 'large', 15), \ +('beng', '1-8', 8), ('beng', '9-10', 2), ('class 1', '20', 10), \ +('class 1', '40', 10), ('class 1', '60', 10), ('class 1', '80', 10), \ +('class 1', '100', 10), ('class 2', '20', 10), ('class 2', '40', 10), \ +('class 2', '60', 10), ('class 2', '80', 10), ('class 2', '100', 10), \ +('class 3', '20', 10), ('class 3', '40', 10), ('class 3', '60', 10), \ +('class 3', '80', 10), ('class 3', '100', 10), ('class 4', '20', 10), \ +('class 4', '40', 10), ('class 4', '60', 10), ('class 4', '80', 10), \ +('class 4', '100', 10), ('class 5', '20', 10), ('class 5', '40', 10), \ +('class 5', '60', 10), ('class 5', '80', 10), ('class 5', '100', 10), \ +('class 6', '20', 10), ('class 6', '40', 10), ('class 6', '60', 10), \ +('class 6', '80', 10), ('class 6', '100', 10), ('class 7', '20', 10), \ +('class 7', '40', 10), ('class 7', '60', 10), ('class 7', '80', 10), \ +('class 7', '100', 10), ('class 8', '20', 10), ('class 8', '40', 10), \ +('class 8', '60', 10), ('class 8', '80', 10), ('class 8', '100', 10), \ +('class 9', '20', 10), ('class 9', '40', 10), ('class 9', '60', 10), \ +('class 9', '80', 10), ('class 9', '100', 10), ('class 10', '20', 10), \ +('class 10', '40', 10), ('class 10', '60', 10), ('class 10', '80', 10), \ +('class 10', '100', 10), ('asqas', None, 4)] + """ + obj: object = Instance.list_resources_groups + attr: str = "__gs" + if not hasattr(obj, attr): + gs: tuple[tuple[str, str | None, tuple[str, ...]], ...] =\ + _make_instance_groups(Instance.list_resources()) + setattr(obj, attr, gs) + return gs + return cast(tuple[tuple[str, str | None, tuple[str, ...]], ...], + getattr(obj, attr)) + @staticmethod def from_resource(name: str) -> "Instance": """ diff --git a/moptipyapps/binpacking2d/objectives/bin_count.py b/moptipyapps/binpacking2d/objectives/bin_count.py index 6b77c877..8670f2d6 100644 --- a/moptipyapps/binpacking2d/objectives/bin_count.py +++ b/moptipyapps/binpacking2d/objectives/bin_count.py @@ -15,6 +15,48 @@ BIN_COUNT_NAME: Final[str] = "binCount" +def ceil_div(a: int, b: int) -> int: + """ + Compute a ceiling division. + + This function is needed by sub-classes. + + :param a: the number to be divided by `b` + :param b: the number dividing `a` + :return: the rounded-up result of the division + + >>> ceil_div(1, 1) + 1 + >>> ceil_div(98, 98) + 1 + >>> ceil_div(98, 99) + 1 + >>> ceil_div(98, 97) + 2 + >>> ceil_div(3, 1) + 3 + >>> ceil_div(3, 2) + 2 + >>> ceil_div(3, 3) + 1 + >>> ceil_div(3, 4) + 1 + >>> ceil_div(4, 1) + 4 + >>> ceil_div(4, 2) + 2 + >>> ceil_div(4, 3) + 2 + >>> ceil_div(4, 4) + 1 + >>> ceil_div(4, 5) + 1 + >>> ceil_div(4, 23242398) + 1 + """ + return -((-a) // b) + + class BinCount(Objective): """Compute the number of bins.""" @@ -28,7 +70,7 @@ def __init__(self, instance: Instance) -> None: if not isinstance(instance, Instance): raise type_error(instance, "instance", Instance) #: the internal instance reference - self.__instance: Final[Instance] = instance + self._instance: Final[Instance] = instance def evaluate(self, x) -> int: """ @@ -65,7 +107,7 @@ def lower_bound(self) -> int: >>> BinCount(ins).lower_bound() 4 """ - return self.__instance.lower_bound_bins + return self._instance.lower_bound_bins def is_always_integer(self) -> bool: """ @@ -100,7 +142,16 @@ def upper_bound(self) -> int: >>> BinCount(ins).upper_bound() 31 """ - return self.__instance.n_items + return self._instance.n_items + + def to_bin_count(self, z: int) -> int: + """ + Get the bin count corresponding to an objective value. + + :param z: + :return: the value itself + """ + return z def __str__(self) -> str: """ diff --git a/moptipyapps/binpacking2d/objectives/bin_count_and_last_empty.py b/moptipyapps/binpacking2d/objectives/bin_count_and_last_empty.py index 8cc327cf..8b4f27af 100644 --- a/moptipyapps/binpacking2d/objectives/bin_count_and_last_empty.py +++ b/moptipyapps/binpacking2d/objectives/bin_count_and_last_empty.py @@ -20,10 +20,8 @@ import numba # type: ignore import numpy as np -from moptipy.api.objective import Objective -from moptipy.utils.types import type_error -from moptipyapps.binpacking2d.instance import Instance +from moptipyapps.binpacking2d.objectives.bin_count import BinCount, ceil_div from moptipyapps.binpacking2d.packing import IDX_BIN @@ -69,21 +67,9 @@ def bin_count_and_last_empty(y: np.ndarray) -> int: return (n_items * (current_bin - 1)) + current_size # return objective -class BinCountAndLastEmpty(Objective): +class BinCountAndLastEmpty(BinCount): """Compute the number of bins and the emptiness of the last one.""" - def __init__(self, instance: Instance) -> None: - """ - Initialize the objective function. - - :param instance: the instance to load the bounds from - """ - super().__init__() - if not isinstance(instance, Instance): - raise type_error(instance, "instance", Instance) - #: the internal instance reference - self.__instance: Final[Instance] = instance - def evaluate(self, x) -> int: """ Evaluate the objective function. @@ -114,6 +100,7 @@ def lower_bound(self) -> int: :return: `max(n_items, (lb - 1) * n_items + 1)` + >>> from moptipyapps.binpacking2d.instance import Instance >>> ins = Instance("a", 100, 50, [[10, 5, 1], [3, 3, 1], [5, 5, 1]]) >>> ins.n_items 3 @@ -138,17 +125,9 @@ def lower_bound(self) -> int: >>> BinCountAndLastEmpty(ins).lower_bound() 94 """ - return max(self.__instance.n_items, - ((self.__instance.lower_bound_bins - 1) - * self.__instance.n_items) + 1) - - def is_always_integer(self) -> bool: - """ - Return `True` because there are only integer bins. - - :retval True: always - """ - return True + return max(self._instance.n_items, + ((self._instance.lower_bound_bins - 1) + * self._instance.n_items) + 1) def upper_bound(self) -> int: """ @@ -156,6 +135,7 @@ def upper_bound(self) -> int: :return: the number of items in the instance to the square + >>> from moptipyapps.binpacking2d.instance import Instance >>> ins = Instance("a", 100, 50, [[10, 5, 1], [3, 3, 1], [5, 5, 1]]) >>> ins.n_items 3 @@ -174,7 +154,16 @@ def upper_bound(self) -> int: >>> BinCountAndLastEmpty(ins).upper_bound() 961 """ - return self.__instance.n_items * self.__instance.n_items + return self._instance.n_items * self._instance.n_items + + def to_bin_count(self, z: int) -> int: + """ + Convert an objective value to a bin count. + + :param z: the objective value + :return: the bin count + """ + return ceil_div(z, self._instance.n_items) def __str__(self) -> str: """ diff --git a/moptipyapps/binpacking2d/objectives/bin_count_and_last_small.py b/moptipyapps/binpacking2d/objectives/bin_count_and_last_small.py index 86e6651e..9f2f59c6 100644 --- a/moptipyapps/binpacking2d/objectives/bin_count_and_last_small.py +++ b/moptipyapps/binpacking2d/objectives/bin_count_and_last_small.py @@ -23,10 +23,9 @@ import numba # type: ignore import numpy as np -from moptipy.api.objective import Objective -from moptipy.utils.types import type_error from moptipyapps.binpacking2d.instance import Instance +from moptipyapps.binpacking2d.objectives.bin_count import BinCount, ceil_div from moptipyapps.binpacking2d.packing import ( IDX_BIN, IDX_BOTTOM_Y, @@ -92,7 +91,7 @@ def bin_count_and_last_small(y: np.ndarray, bin_area: int) -> int: return (bin_area * (current_bin - 1)) + current_area # return objective -class BinCountAndLastSmall(Objective): +class BinCountAndLastSmall(BinCount): """Compute the number of bins and the area in the last one.""" def __init__(self, instance: Instance) -> None: @@ -101,11 +100,7 @@ def __init__(self, instance: Instance) -> None: :param instance: the instance to load the bounds from """ - super().__init__() - if not isinstance(instance, Instance): - raise type_error(instance, "instance", Instance) - #: the internal instance reference - self.__instance: Final[Instance] = instance + super().__init__(instance) #: the bin size self._bin_size: Final[int] = instance.bin_width * instance.bin_height @@ -118,6 +113,15 @@ def evaluate(self, x) -> int: """ return bin_count_and_last_small(x, self._bin_size) + def to_bin_count(self, z: int) -> int: + """ + Convert an objective value to a bin count. + + :param z: the objective value + :return: the bin count + """ + return ceil_div(z, self._bin_size) + def lower_bound(self) -> int: """ Get the lower bound of the number of bins and small-size objective. @@ -167,24 +171,16 @@ def lower_bound(self) -> int: >>> BinCountAndLastSmall(ins).lower_bound() 525 """ - if self.__instance.lower_bound_bins == 1: - return self.__instance.total_item_area + if self._instance.lower_bound_bins == 1: + return self._instance.total_item_area smallest_area: int = -1 - for row in self.__instance: + for row in self._instance: area: int = int(row[0]) * int(row[1]) if (smallest_area < 0) or (area < smallest_area): smallest_area = area - return int(((self.__instance.lower_bound_bins - 1) - * self.__instance.bin_height - * self.__instance.bin_width) + smallest_area) - - def is_always_integer(self) -> bool: - """ - Return `True` because there are only integer bins. - - :retval True: always - """ - return True + return int(((self._instance.lower_bound_bins - 1) + * self._instance.bin_height + * self._instance.bin_width) + smallest_area) def upper_bound(self) -> int: """ @@ -210,8 +206,8 @@ def upper_bound(self) -> int: >>> BinCountAndLastSmall(ins).upper_bound() 10500 """ - return self.__instance.n_items * self.__instance.bin_height \ - * self.__instance.bin_width + return self._instance.n_items * self._instance.bin_height \ + * self._instance.bin_width def __str__(self) -> str: """ diff --git a/moptipyapps/binpacking2d/packing_result.py b/moptipyapps/binpacking2d/packing_result.py index 43d281e1..db9aa432 100644 --- a/moptipyapps/binpacking2d/packing_result.py +++ b/moptipyapps/binpacking2d/packing_result.py @@ -129,13 +129,13 @@ def __lb_geometric(inst: Instance) -> int: #: the upper bound of an objective _OBJECTIVE_UPPER: Final[str] = ".upperBound" #: the start string for bin bounds -_BINS_START: Final[str] = f"bins{_OBJECTIVE_LOWER}" +LOWER_BOUNDS_BIN_COUNT: Final[str] = f"bins{_OBJECTIVE_LOWER}" #: the default bounds _DEFAULT_BOUNDS: Final[Mapping[str, Callable[[Instance], int]]] = \ immutable_mapping({ - _BINS_START: lambda i: i.lower_bound_bins, - f"{_BINS_START}.geometric": __lb_geometric, - f"{_BINS_START}.damv": lambda i: _lower_bound_damv( + LOWER_BOUNDS_BIN_COUNT: lambda i: i.lower_bound_bins, + f"{LOWER_BOUNDS_BIN_COUNT}.geometric": __lb_geometric, + f"{LOWER_BOUNDS_BIN_COUNT}.damv": lambda i: _lower_bound_damv( i.bin_width, i.bin_height, i), }) @@ -322,13 +322,33 @@ def from_packing_and_end_result( # pylint: disable=W0102 immutable_mapping(obounds)) if cache is not None: cache[instance.name] = row + + objective_values: dict[str, int | float] = {} + bin_count: int = -1 + bin_count_obj: str = "" + for objf in row[1]: + z: int | float = objf.evaluate(packing) + objfn: str = str(objf) + objective_values[objfn] = z + if not isinstance(z, int): + continue + if not isinstance(objf, BinCount): + continue + bc: int = objf.to_bin_count(z) + if bin_count == -1: + bin_count = bc + bin_count_obj = objfn + elif bin_count != bc: + raise ValueError( + f"found bin count disagreement: {bin_count} of " + f"{bin_count_obj!r} != {bc} of {objf!r}") + return PackingResult( end_result=end_result, n_items=instance.n_items, n_different_items=instance.n_different_items, bin_width=instance.bin_width, bin_height=instance.bin_height, - objectives={str(objf): cast(Objective, objf).evaluate(packing) - for objf in row[1]}, + objectives=objective_values, objective_bounds=row[2], bin_bounds=row[0]) @@ -562,7 +582,7 @@ def from_csv(file: str, idx_max_fes = i elif cell == KEY_MAX_TIME_MILLIS: idx_max_ms = i - elif cell.startswith(_BINS_START): + elif cell.startswith(LOWER_BOUNDS_BIN_COUNT): idx_bounds[cell] = i elif cell.endswith((_OBJECTIVE_LOWER, _OBJECTIVE_UPPER)): idx_objective_bounds[cell] = i diff --git a/requirements.txt b/requirements.txt index a018f28b..c39ac971 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,7 @@ # `moptipy` provides the basic optimization infrastructure and the spaces and # tools that we use for optimization. -moptipy == 0.9.102 +moptipy == 0.9.103 # `numpy` is needed for its efficient data structures. numpy == 1.26.1 diff --git a/setup.cfg b/setup.cfg index c1cbd836..276bafea 100644 --- a/setup.cfg +++ b/setup.cfg @@ -41,7 +41,7 @@ include_package_data = True install_requires = certifi >= 2023.7.22 defusedxml >= 0.7.1 - moptipy >= 0.9.102 + moptipy >= 0.9.103 numpy >= 1.26.1 numba >= 0.58.1 matplotlib >= 3.8.0 diff --git a/tests/binpacking2d/objectives/test_binpacking2d_bin_count.py b/tests/binpacking2d/objectives/test_binpacking2d_bin_count.py new file mode 100644 index 00000000..6fe414a0 --- /dev/null +++ b/tests/binpacking2d/objectives/test_binpacking2d_bin_count.py @@ -0,0 +1,89 @@ +"""Test the bin-count-and--small objective.""" +import numpy.random as rnd +from moptipy.operators.signed_permutations.op0_shuffle_and_flip import ( + Op0ShuffleAndFlip, +) +from moptipy.spaces.signed_permutations import SignedPermutations +from moptipy.tests.objective import validate_objective + +from moptipyapps.binpacking2d.encodings.ibl_encoding_1 import ( + ImprovedBottomLeftEncoding1, +) +from moptipyapps.binpacking2d.encodings.ibl_encoding_2 import ( + ImprovedBottomLeftEncoding2, +) +from moptipyapps.binpacking2d.instance import Instance +from moptipyapps.binpacking2d.objectives.bin_count import BinCount +from moptipyapps.binpacking2d.packing import Packing +from moptipyapps.binpacking2d.packing_space import PackingSpace +from moptipyapps.tests.on_binpacking2d import ( + validate_objective_on_2dbinpacking, +) + + +def __check_for_instance(inst: Instance, random: rnd.Generator) -> None: + """ + Check the objective for one problem instance. + + :param inst: the instance + """ + search_space = SignedPermutations(inst.get_standard_item_sequence()) + solution_space = PackingSpace(inst) + encoding = (ImprovedBottomLeftEncoding1 if random.integers(2) == 0 + else ImprovedBottomLeftEncoding2)(inst) + objective = BinCount(inst) + op0 = Op0ShuffleAndFlip(search_space) + + def __make_valid(ra: rnd.Generator, + y: Packing, ss=search_space, + en=encoding, o0=op0) -> Packing: + x = ss.create() + o0.op0(ra, x) + en.decode(x, y) + return y + + validate_objective(objective, solution_space, __make_valid) + + f1: BinCount = objective + for _ in range(10): + pa = __make_valid(random, Packing(inst)) + assert f1.evaluate(pa) == objective.to_bin_count( + objective.evaluate(pa)) + + +def test_bin_count() -> None: + """Test the bin count objective function.""" + random: rnd.Generator = rnd.default_rng() + + choices = list(Instance.list_resources()) + checks: set[str] = {c for c in choices if c.startswith(("a", "b"))} + min_len: int = len(checks) + 10 + while len(checks) < min_len: + checks.add(choices.pop(random.integers(len(choices)))) + + for s in checks: + __check_for_instance(Instance.from_resource(s), random) + + validate_objective_on_2dbinpacking(BinCount, random) + + +def test_bin_count_objective_2() -> None: + """Test the bin count objective function.""" + random: rnd.Generator = rnd.default_rng() + for inst in Instance.list_resources(): + if not inst.startswith(("a", "b")): + continue + instance = Instance.from_resource(inst) + search_space = SignedPermutations( + instance.get_standard_item_sequence()) + solution_space = PackingSpace(instance) + encoding = (ImprovedBottomLeftEncoding1 if random.integers(2) == 0 + else ImprovedBottomLeftEncoding2)(instance) + objective = BinCount(instance) + op0 = Op0ShuffleAndFlip(search_space) + x = search_space.create() + op0.op0(random, x) + y = solution_space.create() + encoding.decode(x, y) + assert 0 <= objective.lower_bound() <= objective.evaluate(y) \ + <= objective.upper_bound() <= 1_000_000_000_000_000 diff --git a/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_empty.py b/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_empty.py index e2f8fd33..24c1a28d 100644 --- a/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_empty.py +++ b/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_empty.py @@ -13,6 +13,7 @@ ImprovedBottomLeftEncoding2, ) from moptipyapps.binpacking2d.instance import Instance +from moptipyapps.binpacking2d.objectives.bin_count import BinCount from moptipyapps.binpacking2d.objectives.bin_count_and_empty import ( BinCountAndEmpty, ) @@ -46,9 +47,15 @@ def __make_valid(ra: rnd.Generator, validate_objective(objective, solution_space, __make_valid) + f1: BinCount = BinCount(inst) + for _ in range(10): + pa = __make_valid(random, Packing(inst)) + assert f1.evaluate(pa) == objective.to_bin_count( + objective.evaluate(pa)) + def test_bin_count_and_empty_objective() -> None: - """Test the bin-empty objective function.""" + """Test the bin-count-and-empty objective function.""" random: rnd.Generator = rnd.default_rng() checks: set[str] = {"a01", "a10", "a20", "beng03", "beng10", diff --git a/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_last_empty.py b/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_last_empty.py index c92b05a7..28a5512c 100644 --- a/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_last_empty.py +++ b/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_last_empty.py @@ -13,6 +13,7 @@ ImprovedBottomLeftEncoding2, ) from moptipyapps.binpacking2d.instance import Instance +from moptipyapps.binpacking2d.objectives.bin_count import BinCount from moptipyapps.binpacking2d.objectives.bin_count_and_last_empty import ( BinCountAndLastEmpty, ) @@ -46,6 +47,12 @@ def __make_valid(ra: rnd.Generator, validate_objective(objective, solution_space, __make_valid) + f1: BinCount = BinCount(inst) + for _ in range(10): + pa = __make_valid(random, Packing(inst)) + assert f1.evaluate(pa) == objective.to_bin_count( + objective.evaluate(pa)) + def test_bin_count_and_last_empty_objective() -> None: """Test the last-bin-empty objective function.""" diff --git a/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_last_skyline.py b/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_last_skyline.py index 2e7d64cc..b16c9a2e 100644 --- a/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_last_skyline.py +++ b/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_last_skyline.py @@ -13,6 +13,7 @@ ImprovedBottomLeftEncoding2, ) from moptipyapps.binpacking2d.instance import Instance +from moptipyapps.binpacking2d.objectives.bin_count import BinCount from moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline import ( BinCountAndLastSkyline, ) @@ -46,6 +47,12 @@ def __make_valid(ra: rnd.Generator, validate_objective(objective, solution_space, __make_valid) + f1: BinCount = BinCount(inst) + for _ in range(10): + pa = __make_valid(random, Packing(inst)) + assert f1.evaluate(pa) == objective.to_bin_count( + objective.evaluate(pa)) + def test_bin_count_and_last_skyline_objective() -> None: """Test the last-bin-skyline-area objective function.""" diff --git a/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_last_small.py b/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_last_small.py index 1478f186..f3f8403d 100644 --- a/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_last_small.py +++ b/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_last_small.py @@ -13,6 +13,7 @@ ImprovedBottomLeftEncoding2, ) from moptipyapps.binpacking2d.instance import Instance +from moptipyapps.binpacking2d.objectives.bin_count import BinCount from moptipyapps.binpacking2d.objectives.bin_count_and_last_small import ( BinCountAndLastSmall, ) @@ -46,6 +47,12 @@ def __make_valid(ra: rnd.Generator, validate_objective(objective, solution_space, __make_valid) + f1: BinCount = BinCount(inst) + for _ in range(10): + pa = __make_valid(random, Packing(inst)) + assert f1.evaluate(pa) == objective.to_bin_count( + objective.evaluate(pa)) + def test_bin_count_and_last_small_objective() -> None: """Test the last-bin-small-area objective function.""" diff --git a/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_lowest_skyline.py b/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_lowest_skyline.py index 746824bb..e52d8e78 100644 --- a/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_lowest_skyline.py +++ b/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_lowest_skyline.py @@ -13,6 +13,7 @@ ImprovedBottomLeftEncoding2, ) from moptipyapps.binpacking2d.instance import Instance +from moptipyapps.binpacking2d.objectives.bin_count import BinCount from moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline import ( BinCountAndLowestSkyline, ) @@ -46,6 +47,12 @@ def __make_valid(ra: rnd.Generator, validate_objective(objective, solution_space, __make_valid) + f1: BinCount = BinCount(inst) + for _ in range(10): + pa = __make_valid(random, Packing(inst)) + assert f1.evaluate(pa) == objective.to_bin_count( + objective.evaluate(pa)) + def test_bin_count_and_lowest_skyline_objective() -> None: """Test the lowest-skyline objective function.""" diff --git a/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_small.py b/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_small.py index cb44a991..dc29e222 100644 --- a/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_small.py +++ b/tests/binpacking2d/objectives/test_binpacking2d_bin_count_and_small.py @@ -13,6 +13,7 @@ ImprovedBottomLeftEncoding2, ) from moptipyapps.binpacking2d.instance import Instance +from moptipyapps.binpacking2d.objectives.bin_count import BinCount from moptipyapps.binpacking2d.objectives.bin_count_and_small import ( BinCountAndSmall, ) @@ -46,9 +47,15 @@ def __make_valid(ra: rnd.Generator, validate_objective(objective, solution_space, __make_valid) + f1: BinCount = BinCount(inst) + for _ in range(10): + pa = __make_valid(random, Packing(inst)) + assert f1.evaluate(pa) == objective.to_bin_count( + objective.evaluate(pa)) + def test_bin_count_and_small_objective() -> None: - """Test the -bin-small-area objective function.""" + """Test the bin-count-and--small-area objective function.""" random: rnd.Generator = rnd.default_rng() choices = list(Instance.list_resources()) @@ -64,7 +71,7 @@ def test_bin_count_and_small_objective() -> None: def test_bin_count_and_small_objective_2() -> None: - """Test the bin-small-area objective function.""" + """Test the bin-count-and--small-area function.""" random: rnd.Generator = rnd.default_rng() for inst in Instance.list_resources(): if not inst.startswith(("a", "b")):