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