Coverage for moptipyapps / binpacking2d / packing.py: 85%
74 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 04:40 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 04:40 +0000
1"""A two-dimensional packing."""
3from typing import Final
5import numpy as np
6from moptipy.api.component import Component
7from moptipy.api.logging import SECTION_RESULT_Y, SECTION_SETUP
8from moptipy.evaluation.log_parser import LogParser
9from pycommons.io.path import Path
10from pycommons.types import type_error
12from moptipyapps.binpacking2d.instance import Instance
14#: the index of the ID in a :class:`Packing` row
15IDX_ID: Final[int] = 0
16#: the index of the bin in a :class:`Packing` row
17IDX_BIN: Final[int] = 1
18#: the index of the left x coordinate in a :class:`Packing` row
19IDX_LEFT_X: Final[int] = 2
20#: the index of the bottom y coordinate in a :class:`Packing` row
21IDX_BOTTOM_Y: Final[int] = 3
22#: the index of the right x coordinate in a :class:`Packing` row
23IDX_RIGHT_X: Final[int] = 4
24#: the index of the top y coordinate in a :class:`Packing` row
25IDX_TOP_Y: Final[int] = 5
28class Packing(Component, np.ndarray):
29 """
30 A packing, i.e., a solution to an 2D bin packing instance.
32 A packing is a two-dimensional numpy array. In each row, the position of
33 one item is stored: 1. the item ID (starts at 1), 2. the bin into which
34 the item is packaged (starts at 1), 3. the left x coordinate, 4. the
35 bottom y coordinate, 5. the right x coordinate, 6. the top y coordinate.
36 """
38 #: the 2d bin packing instance
39 instance: Instance
40 #: the number of bins
41 n_bins: int
43 def __new__(cls, instance: Instance) -> "Packing":
44 """
45 Create a solution record for the 2D bin packing problem.
47 :param cls: the class
48 :param instance: the solution record
49 """
50 if not isinstance(instance, Instance):
51 raise type_error(instance, "instance", Instance)
52 obj: Final[Packing] = super().__new__(
53 cls, (instance.n_items, 6), instance.dtype)
54 #: the 2d bin packing instance
55 obj.instance = instance
56 #: the number of bins
57 obj.n_bins = -1
58 return obj
60 def __str__(self):
61 """
62 Convert the packing to a compact string.
64 :return: the compact string
65 """
66 return "\n".join(";".join(str(self[i, j]) for j in range(6))
67 for i in range(self.shape[0]))
69 @staticmethod
70 def from_log(file: str, instance: Instance | None = None) -> "Packing":
71 """
72 Load a packing from a log file.
74 :param file: the log file path
75 :param instance: the optional Packing instance: if `None` is provided,
76 we try to load it from the resources
77 :returns: the Packing
78 """
79 return _PackingParser(instance).parse_file(file)
82class _PackingParser(LogParser[Packing]):
83 """The log parser for loading packings."""
85 def __init__(self, instance: Instance | None = None):
86 """
87 Create the packing parser.
89 :param instance: the optional packing instance: if `None` is provided,
90 we try to load it from the resources
91 """
92 super().__init__()
93 if (instance is not None) and (not isinstance(instance, Instance)):
94 raise type_error(instance, "instance", Instance)
95 #: the internal instance
96 self.__instance: Instance | None = instance
97 #: the internal section mode: 0=none, 1=setup, 2=y
98 self.__sec_mode: int = 0
99 #: the packing string
100 self.__packing_str: str | None = None
102 def _start_section(self, title: str) -> bool:
103 """Start a section."""
104 super()._start_section(title)
105 self.__sec_mode = 0
106 if title == SECTION_SETUP:
107 if self.__instance is None:
108 self.__sec_mode = 1
109 return True
110 return False
111 if title == SECTION_RESULT_Y:
112 self.__sec_mode = 2
113 return True
114 return False
116 def _lines(self, lines: list[str]) -> bool:
117 """Parse the lines."""
118 if self.__sec_mode == 1:
119 if self.__instance is not None:
120 raise ValueError(
121 f"instance is already set to {self.__instance}.")
122 key_1: Final[str] = "y.inst.name: "
123 for line in lines:
124 if line.startswith(key_1):
125 self.__instance = Instance.from_resource(
126 line[len(key_1):].strip())
127 if self.__instance is None:
128 raise ValueError(f"Did not find instance key {key_1!r} "
129 f"in section {SECTION_SETUP}!")
130 elif self.__sec_mode == 2:
131 self.__packing_str = " ".join(lines).strip()
132 else:
133 raise ValueError("Should not be in section?")
134 return (self.__instance is None) or (self.__packing_str is None)
136 def _parse_file(self, file: Path) -> Packing:
137 """End the file."""
138 super()._parse_file(file)
139 if self.__packing_str is None:
140 raise ValueError(f"Section {SECTION_RESULT_Y} missing!")
141 if self.__instance is None:
142 raise ValueError(f"Section {SECTION_SETUP} missing or empty!")
143 # pylint: disable=C0415,R0401
144 from moptipyapps.binpacking2d.packing_space import ( # noqa: PLC0415
145 PackingSpace, # pylint: disable=C0415,R0401 # noqa: PLC0415
146 )
147 return PackingSpace(self.__instance).from_str(self.__packing_str)
149 def _end_parse_file(self, file: Path) -> None:
150 """Cleanup."""
151 self.__packing_str = None
152 super()._end_parse_file(file)