Source code for moptipyapps.binpacking2d.packing

"""A two-dimensional packing."""

from typing import Final

import numpy as np
from moptipy.api.component import Component
from moptipy.api.logging import SECTION_RESULT_Y, SECTION_SETUP
from moptipy.evaluation.log_parser import LogParser
from pycommons.types import type_error

from moptipyapps.binpacking2d.instance import Instance

#: the index of the ID in a :class:`Packing` row
IDX_ID: Final[int] = 0
#: the index of the bin in a :class:`Packing` row
IDX_BIN: Final[int] = 1
#: the index of the left x coordinate in a :class:`Packing` row
IDX_LEFT_X: Final[int] = 2
#: the index of the bottom y coordinate in a :class:`Packing` row
IDX_BOTTOM_Y: Final[int] = 3
#: the index of the right x coordinate in a :class:`Packing` row
IDX_RIGHT_X: Final[int] = 4
#: the index of the top y coordinate in a :class:`Packing` row
IDX_TOP_Y: Final[int] = 5


[docs] class Packing(Component, np.ndarray): """ A packing, i.e., a solution to an 2D bin packing instance. A packing is a two-dimensional numpy array. In each row, the position of one item is stored: 1. the item ID (starts at 1), 2. the bin into which the item is packaged (starts at 1), 3. the left x coordinate, 4. the bottom y coordinate, 5. the right x coordinate, 6. the top y coordinate. """ #: the 2d bin packing instance instance: Instance #: the number of bins n_bins: int def __new__(cls, instance: Instance) -> "Packing": """ Create a solution record for the 2D bin packing problem. :param cls: the class :param instance: the solution record """ if not isinstance(instance, Instance): raise type_error(instance, "instance", Instance) obj: Final[Packing] = super().__new__( cls, (instance.n_items, 6), instance.dtype) #: the 2d bin packing instance obj.instance = instance #: the number of bins obj.n_bins = -1 return obj def __str__(self): """ Convert the packing to a compact string. :return: the compact string """ return "\n".join(";".join(str(self[i, j]) for j in range(6)) for i in range(self.shape[0]))
[docs] @staticmethod def from_log(file: str, instance: Instance | None = None) -> "Packing": """ Load a packing from a log file. :param file: the log file path :param instance: the optional Packing instance: if `None` is provided, we try to load it from the resources :returns: the Packing """ parser: Final[_PackingParser] = _PackingParser(instance) parser.parse_file(file) # noinspection PyProtectedMember res = parser._result if not isinstance(res, Packing): raise type_error(res, f"packing from {file!r}", Packing) return res
class _PackingParser(LogParser): """The log parser for loading packings.""" def __init__(self, instance: Instance | None = None): """ Create the packing parser. :param instance: the optional packing instance: if `None` is provided, we try to load it from the resources """ super().__init__() if (instance is not None) and (not isinstance(instance, Instance)): raise type_error(instance, "instance", Instance) #: the internal instance self.__instance: Instance | None = instance #: the internal section mode: 0=none, 1=setup, 2=y self.__sec_mode: int = 0 #: the packing string self.__packing_str: str | None = None #: the result packing self._result: Packing | None = None def start_section(self, title: str) -> bool: """Start a section.""" super().start_section(title) self.__sec_mode = 0 if title == SECTION_SETUP: if self.__instance is None: self.__sec_mode = 1 return True return False if title == SECTION_RESULT_Y: self.__sec_mode = 2 return True return False def lines(self, lines: list[str]) -> bool: """Parse the lines.""" if self.__sec_mode == 1: if self.__instance is not None: raise ValueError( f"instance is already set to {self.__instance}.") key_1: Final[str] = "y.inst.name: " for line in lines: if line.startswith(key_1): self.__instance = Instance.from_resource( line[len(key_1):].strip()) if self.__instance is None: raise ValueError(f"Did not find instance key {key_1!r} " f"in section {SECTION_SETUP}!") elif self.__sec_mode == 2: self.__packing_str = " ".join(lines).strip() else: raise ValueError("Should not be in section?") return (self.__instance is None) or (self.__packing_str is None) def end_file(self) -> bool: """End the file.""" if self.__packing_str is None: raise ValueError(f"Section {SECTION_RESULT_Y} missing!") if self.__instance is None: raise ValueError(f"Section {SECTION_SETUP} missing or empty!") if self._result is not None: raise ValueError("Applied parser to more than one log file?") # pylint: disable=C0415,R0401 from moptipyapps.binpacking2d.packing_space import ( PackingSpace, # pylint: disable=C0415,R0401 ) self._result = PackingSpace(self.__instance)\ .from_str(self.__packing_str) self.__packing_str = None return False