|
40 | 40 | namespace operations_research { |
41 | 41 | namespace sat { |
42 | 42 |
|
43 | | -bool PresolveFixed2dRectangles( |
| 43 | +namespace { |
| 44 | +std::vector<Rectangle> FindSpacesThatCannotBeOccupied( |
| 45 | + absl::Span<const Rectangle> fixed_boxes, |
44 | 46 | absl::Span<const RectangleInRange> non_fixed_boxes, |
45 | | - std::vector<Rectangle>* fixed_boxes) { |
46 | | - // This implementation compiles a set of areas that cannot be occupied by any |
47 | | - // item, then calls ReduceNumberofBoxes() to use these areas to minimize |
48 | | - // `fixed_boxes`. |
49 | | - bool changed = false; |
50 | | - |
51 | | - DCHECK(FindPartialRectangleIntersections(*fixed_boxes).empty()); |
52 | | - IntegerValue original_area = 0; |
53 | | - std::vector<Rectangle> fixed_boxes_copy; |
54 | | - if (VLOG_IS_ON(1)) { |
55 | | - for (const Rectangle& r : *fixed_boxes) { |
56 | | - original_area += r.Area(); |
57 | | - } |
58 | | - } |
59 | | - if (VLOG_IS_ON(2)) { |
60 | | - fixed_boxes_copy = *fixed_boxes; |
61 | | - } |
62 | | - |
63 | | - const int original_num_boxes = fixed_boxes->size(); |
64 | | - |
65 | | - // The greedy algorithm is really fast. Run it first since it might greatly |
66 | | - // reduce the size of large trivial instances. |
67 | | - std::vector<Rectangle> empty_vec; |
68 | | - if (ReduceNumberofBoxesGreedy(fixed_boxes, &empty_vec)) { |
69 | | - changed = true; |
70 | | - } |
71 | | - |
72 | | - IntegerValue min_x_size = std::numeric_limits<IntegerValue>::max(); |
73 | | - IntegerValue min_y_size = std::numeric_limits<IntegerValue>::max(); |
74 | | - |
75 | | - CHECK(!non_fixed_boxes.empty()); |
76 | | - Rectangle bounding_box = non_fixed_boxes[0].bounding_area; |
77 | | - |
78 | | - for (const RectangleInRange& box : non_fixed_boxes) { |
79 | | - bounding_box.GrowToInclude(box.bounding_area); |
80 | | - min_x_size = std::min(min_x_size, box.x_size); |
81 | | - min_y_size = std::min(min_y_size, box.y_size); |
82 | | - } |
83 | | - DCHECK_GT(min_x_size, 0); |
84 | | - DCHECK_GT(min_y_size, 0); |
85 | | - |
86 | | - // Fixed items are only useful to constraint where the non-fixed items can be |
87 | | - // placed. This means in particular that any part of a fixed item outside the |
88 | | - // bounding box of the non-fixed items is useless. Clip them. |
89 | | - int new_size = 0; |
90 | | - while (new_size < fixed_boxes->size()) { |
91 | | - Rectangle& rectangle = (*fixed_boxes)[new_size]; |
92 | | - DCHECK_GT(rectangle.SizeX(), 0); |
93 | | - DCHECK_GT(rectangle.SizeY(), 0); |
94 | | - if (rectangle.x_min < bounding_box.x_min) { |
95 | | - rectangle.x_min = bounding_box.x_min; |
96 | | - changed = true; |
97 | | - } |
98 | | - if (rectangle.x_max > bounding_box.x_max) { |
99 | | - rectangle.x_max = bounding_box.x_max; |
100 | | - changed = true; |
101 | | - } |
102 | | - if (rectangle.y_min < bounding_box.y_min) { |
103 | | - rectangle.y_min = bounding_box.y_min; |
104 | | - changed = true; |
105 | | - } |
106 | | - if (rectangle.y_max > bounding_box.y_max) { |
107 | | - rectangle.y_max = bounding_box.y_max; |
108 | | - changed = true; |
109 | | - } |
110 | | - if (rectangle.SizeX() <= 0 || rectangle.SizeY() <= 0) { |
111 | | - // The whole rectangle was outside of the domain, remove it. |
112 | | - std::swap(rectangle, (*fixed_boxes)[fixed_boxes->size() - 1]); |
113 | | - fixed_boxes->resize(fixed_boxes->size() - 1); |
114 | | - changed = true; |
115 | | - continue; |
116 | | - } else { |
117 | | - new_size++; |
118 | | - } |
119 | | - } |
120 | | - |
121 | | - std::vector<Rectangle> optional_boxes = *fixed_boxes; |
| 47 | + const Rectangle& bounding_box, IntegerValue min_x_size, |
| 48 | + IntegerValue min_y_size) { |
| 49 | + std::vector<Rectangle> optional_boxes = {fixed_boxes.begin(), |
| 50 | + fixed_boxes.end()}; |
122 | 51 |
|
123 | 52 | if (bounding_box.x_min > std::numeric_limits<IntegerValue>::min() && |
124 | 53 | bounding_box.y_min > std::numeric_limits<IntegerValue>::min() && |
@@ -228,6 +157,91 @@ bool PresolveFixed2dRectangles( |
228 | 157 | } |
229 | 158 | optional_boxes.erase(optional_boxes.begin(), |
230 | 159 | optional_boxes.begin() + num_optional_boxes_to_remove); |
| 160 | + return optional_boxes; |
| 161 | +} |
| 162 | + |
| 163 | +} // namespace |
| 164 | + |
| 165 | +bool PresolveFixed2dRectangles( |
| 166 | + absl::Span<const RectangleInRange> non_fixed_boxes, |
| 167 | + std::vector<Rectangle>* fixed_boxes) { |
| 168 | + // This implementation compiles a set of areas that cannot be occupied by any |
| 169 | + // item, then calls ReduceNumberofBoxes() to use these areas to minimize |
| 170 | + // `fixed_boxes`. |
| 171 | + bool changed = false; |
| 172 | + |
| 173 | + DCHECK(FindPartialRectangleIntersections(*fixed_boxes).empty()); |
| 174 | + IntegerValue original_area = 0; |
| 175 | + std::vector<Rectangle> fixed_boxes_copy; |
| 176 | + if (VLOG_IS_ON(1)) { |
| 177 | + for (const Rectangle& r : *fixed_boxes) { |
| 178 | + original_area += r.Area(); |
| 179 | + } |
| 180 | + } |
| 181 | + if (VLOG_IS_ON(2)) { |
| 182 | + fixed_boxes_copy = *fixed_boxes; |
| 183 | + } |
| 184 | + |
| 185 | + const int original_num_boxes = fixed_boxes->size(); |
| 186 | + |
| 187 | + // The greedy algorithm is really fast. Run it first since it might greatly |
| 188 | + // reduce the size of large trivial instances. |
| 189 | + std::vector<Rectangle> empty_vec; |
| 190 | + if (ReduceNumberofBoxesGreedy(fixed_boxes, &empty_vec)) { |
| 191 | + changed = true; |
| 192 | + } |
| 193 | + |
| 194 | + IntegerValue min_x_size = std::numeric_limits<IntegerValue>::max(); |
| 195 | + IntegerValue min_y_size = std::numeric_limits<IntegerValue>::max(); |
| 196 | + |
| 197 | + CHECK(!non_fixed_boxes.empty()); |
| 198 | + Rectangle bounding_box = non_fixed_boxes[0].bounding_area; |
| 199 | + |
| 200 | + for (const RectangleInRange& box : non_fixed_boxes) { |
| 201 | + bounding_box.GrowToInclude(box.bounding_area); |
| 202 | + min_x_size = std::min(min_x_size, box.x_size); |
| 203 | + min_y_size = std::min(min_y_size, box.y_size); |
| 204 | + } |
| 205 | + DCHECK_GT(min_x_size, 0); |
| 206 | + DCHECK_GT(min_y_size, 0); |
| 207 | + |
| 208 | + // Fixed items are only useful to constraint where the non-fixed items can be |
| 209 | + // placed. This means in particular that any part of a fixed item outside the |
| 210 | + // bounding box of the non-fixed items is useless. Clip them. |
| 211 | + int new_size = 0; |
| 212 | + while (new_size < fixed_boxes->size()) { |
| 213 | + Rectangle& rectangle = (*fixed_boxes)[new_size]; |
| 214 | + DCHECK_GT(rectangle.SizeX(), 0); |
| 215 | + DCHECK_GT(rectangle.SizeY(), 0); |
| 216 | + if (rectangle.x_min < bounding_box.x_min) { |
| 217 | + rectangle.x_min = bounding_box.x_min; |
| 218 | + changed = true; |
| 219 | + } |
| 220 | + if (rectangle.x_max > bounding_box.x_max) { |
| 221 | + rectangle.x_max = bounding_box.x_max; |
| 222 | + changed = true; |
| 223 | + } |
| 224 | + if (rectangle.y_min < bounding_box.y_min) { |
| 225 | + rectangle.y_min = bounding_box.y_min; |
| 226 | + changed = true; |
| 227 | + } |
| 228 | + if (rectangle.y_max > bounding_box.y_max) { |
| 229 | + rectangle.y_max = bounding_box.y_max; |
| 230 | + changed = true; |
| 231 | + } |
| 232 | + if (rectangle.SizeX() <= 0 || rectangle.SizeY() <= 0) { |
| 233 | + // The whole rectangle was outside of the domain, remove it. |
| 234 | + std::swap(rectangle, (*fixed_boxes)[fixed_boxes->size() - 1]); |
| 235 | + fixed_boxes->resize(fixed_boxes->size() - 1); |
| 236 | + changed = true; |
| 237 | + continue; |
| 238 | + } else { |
| 239 | + new_size++; |
| 240 | + } |
| 241 | + } |
| 242 | + |
| 243 | + std::vector<Rectangle> optional_boxes = FindSpacesThatCannotBeOccupied( |
| 244 | + *fixed_boxes, non_fixed_boxes, bounding_box, min_x_size, min_y_size); |
231 | 245 |
|
232 | 246 | // TODO(user): instead of doing the greedy algorithm first with optional |
233 | 247 | // boxes, and then the one that is exact for mandatory boxes but weak for |
@@ -1467,5 +1481,85 @@ bool ReduceNumberOfBoxesExactMandatory( |
1467 | 1481 | return true; |
1468 | 1482 | } |
1469 | 1483 |
|
| 1484 | +Disjoint2dPackingResult DetectDisjointRegionIn2dPacking( |
| 1485 | + absl::Span<const RectangleInRange> non_fixed_boxes, |
| 1486 | + absl::Span<const Rectangle> fixed_boxes, int max_num_components) { |
| 1487 | + if (max_num_components <= 1) return {}; |
| 1488 | + |
| 1489 | + IntegerValue min_x_size = std::numeric_limits<IntegerValue>::max(); |
| 1490 | + IntegerValue min_y_size = std::numeric_limits<IntegerValue>::max(); |
| 1491 | + |
| 1492 | + CHECK(!non_fixed_boxes.empty()); |
| 1493 | + Rectangle bounding_box = non_fixed_boxes[0].bounding_area; |
| 1494 | + |
| 1495 | + for (const RectangleInRange& box : non_fixed_boxes) { |
| 1496 | + bounding_box.GrowToInclude(box.bounding_area); |
| 1497 | + min_x_size = std::min(min_x_size, box.x_size); |
| 1498 | + min_y_size = std::min(min_y_size, box.y_size); |
| 1499 | + } |
| 1500 | + DCHECK_GT(min_x_size, 0); |
| 1501 | + DCHECK_GT(min_y_size, 0); |
| 1502 | + |
| 1503 | + std::vector<Rectangle> optional_boxes = FindSpacesThatCannotBeOccupied( |
| 1504 | + fixed_boxes, non_fixed_boxes, bounding_box, min_x_size, min_y_size); |
| 1505 | + std::vector<Rectangle> unoccupiable_space = {fixed_boxes.begin(), |
| 1506 | + fixed_boxes.end()}; |
| 1507 | + unoccupiable_space.insert(unoccupiable_space.end(), optional_boxes.begin(), |
| 1508 | + optional_boxes.end()); |
| 1509 | + |
| 1510 | + std::vector<Rectangle> occupiable_space = |
| 1511 | + FindEmptySpaces(bounding_box, unoccupiable_space); |
| 1512 | + |
| 1513 | + std::vector<Rectangle> empty; |
| 1514 | + ReduceNumberofBoxesGreedy(&occupiable_space, &empty); |
| 1515 | + std::vector<std::vector<int>> space_components = |
| 1516 | + SplitInConnectedComponents(BuildNeighboursGraph(occupiable_space)); |
| 1517 | + |
| 1518 | + if (space_components.size() == 1 || |
| 1519 | + space_components.size() > max_num_components) { |
| 1520 | + return {}; |
| 1521 | + } |
| 1522 | + |
| 1523 | + // If we are here, that means that the space where boxes can be placed is not |
| 1524 | + // connected. |
| 1525 | + Disjoint2dPackingResult result; |
| 1526 | + absl::flat_hash_set<int> component_set; |
| 1527 | + for (const std::vector<int>& component : space_components) { |
| 1528 | + Rectangle bin_bounding_box = occupiable_space[component[0]]; |
| 1529 | + for (int i = 1; i < component.size(); ++i) { |
| 1530 | + bin_bounding_box.GrowToInclude(occupiable_space[component[i]]); |
| 1531 | + } |
| 1532 | + std::optional<Rectangle> reachable_area_bounding_box; |
| 1533 | + Disjoint2dPackingResult::Bin& bin = result.bins.emplace_back(); |
| 1534 | + for (int idx : component) { |
| 1535 | + bin.bin_area.push_back(occupiable_space[idx]); |
| 1536 | + } |
| 1537 | + for (int i = 0; i < non_fixed_boxes.size(); i++) { |
| 1538 | + if (!non_fixed_boxes[i].bounding_area.IsDisjoint(bin_bounding_box)) { |
| 1539 | + if (reachable_area_bounding_box.has_value()) { |
| 1540 | + reachable_area_bounding_box->GrowToInclude( |
| 1541 | + non_fixed_boxes[i].bounding_area); |
| 1542 | + } else { |
| 1543 | + reachable_area_bounding_box = non_fixed_boxes[i].bounding_area; |
| 1544 | + } |
| 1545 | + bin.non_fixed_box_indexes.push_back(i); |
| 1546 | + } |
| 1547 | + } |
| 1548 | + if (bin.non_fixed_box_indexes.empty()) { |
| 1549 | + result.bins.pop_back(); |
| 1550 | + continue; |
| 1551 | + } |
| 1552 | + bin.fixed_boxes = |
| 1553 | + FindEmptySpaces(*reachable_area_bounding_box, bin.bin_area); |
| 1554 | + ReduceNumberofBoxesGreedy(&bin.fixed_boxes, &empty); |
| 1555 | + } |
| 1556 | + VLOG_EVERY_N_SEC(1, 1) << "Detected a bin packing problem with " |
| 1557 | + << result.bins.size() |
| 1558 | + << " bins. Original problem sizes: " |
| 1559 | + << non_fixed_boxes.size() << " non-fixed boxes, " |
| 1560 | + << fixed_boxes.size() << " fixed boxes."; |
| 1561 | + return result; |
| 1562 | +} |
| 1563 | + |
1470 | 1564 | } // namespace sat |
1471 | 1565 | } // namespace operations_research |
0 commit comments