23#include "absl/container/inlined_vector.h"
24#include "absl/log/check.h"
25#include "absl/strings/str_cat.h"
26#include "absl/types/span.h"
39enum class RectangleRelationship {
40 TOUCHING_NEITHER_LEFT_OR_BOTTOM,
46RectangleRelationship GetRectangleRelationship(
const Rectangle& rectangle,
47 const Rectangle& other) {
48 if (rectangle.x_min < other.x_max && other.x_min < rectangle.x_max &&
49 rectangle.y_min < other.y_max && other.y_min < rectangle.y_max) {
50 return RectangleRelationship::OVERLAP;
53 if (rectangle.x_min == other.x_max && rectangle.y_min < other.y_max &&
54 other.y_min < rectangle.y_max) {
55 return RectangleRelationship::TOUCHING_LEFT;
57 if (rectangle.x_min < other.x_max && other.x_min < rectangle.x_max &&
58 rectangle.y_min == other.y_max) {
59 return RectangleRelationship::TOUCHING_BOTTOM;
61 return RectangleRelationship::TOUCHING_NEITHER_LEFT_OR_BOTTOM;
64bool ShouldPlaceItemAtPosition(
65 int i,
const Rectangle& item_position,
66 std::pair<IntegerValue, IntegerValue> bounding_box_size,
67 absl::Span<const Rectangle> item_positions,
70 if (item_position.x_max > bounding_box_size.first ||
71 item_position.y_max > bounding_box_size.second) {
76 if (
i == 0 && (2 * item_position.x_min >
77 bounding_box_size.first - item_position.SizeX() ||
78 2 * item_position.y_min >
79 bounding_box_size.second - item_position.SizeY())) {
84 bool touches_something_on_left = item_position.x_min == 0;
85 bool touches_something_on_bottom = item_position.y_min == 0;
86 for (
const int j : placed_item_indexes) {
88 const RectangleRelationship pos =
89 GetRectangleRelationship(item_position, item_positions[j]);
90 if (pos == RectangleRelationship::OVERLAP) {
93 touches_something_on_left = touches_something_on_left ||
94 pos == RectangleRelationship::TOUCHING_LEFT;
95 touches_something_on_bottom = touches_something_on_bottom ||
96 pos == RectangleRelationship::TOUCHING_BOTTOM;
100 if (!touches_something_on_left || !touches_something_on_bottom) {
106struct PotentialPositionForItem {
111 Rectangle GetRectangle(IntegerValue x_size, IntegerValue y_size)
const {
112 return {.x_min = x, .x_max = x + x_size, .y_min =
y, .y_max =
y + y_size};
140bool BruteForceOrthogonalPackingImpl(
141 absl::Span<const IntegerValue> sizes_x,
142 absl::Span<const IntegerValue> sizes_y,
143 std::pair<IntegerValue, IntegerValue> bounding_box_size,
144 IntegerValue smallest_x, IntegerValue smallest_y,
145 absl::Span<Rectangle> item_positions, Bitset64<int>& placed_item_indexes,
146 absl::Span<
const absl::InlinedVector<PotentialPositionForItem, 16>>
147 potential_item_positions,
148 IntegerValue slack) {
149 const auto add_position_if_valid =
150 [&item_positions, bounding_box_size, &sizes_x, &sizes_y,
151 &placed_item_indexes](
152 absl::InlinedVector<PotentialPositionForItem, 16>& positions,
int i,
153 IntegerValue
x, IntegerValue
y) {
154 const Rectangle rect = {.x_min =
x,
155 .x_max =
x + sizes_x[
i],
157 .y_max =
y + sizes_y[
i]};
158 if (ShouldPlaceItemAtPosition(
i, rect, bounding_box_size,
159 item_positions, placed_item_indexes)) {
160 positions.push_back({
x,
y,
false});
164 const int num_items = sizes_x.size();
165 bool has_unplaced_item =
false;
166 for (
int i = 0;
i < num_items; ++
i) {
167 if (placed_item_indexes[
i]) {
170 if (potential_item_positions[
i].empty()) {
174 has_unplaced_item =
true;
175 placed_item_indexes.Set(
i);
176 for (
const PotentialPositionForItem& potential_position :
177 potential_item_positions[
i]) {
178 if (potential_position.already_explored) {
183 potential_position.GetRectangle(sizes_x[
i], sizes_y[
i]);
184 const Rectangle& item_position = item_positions[
i];
186 IntegerValue slack_loss = 0;
187 if (bounding_box_size.first - item_position.x_max < smallest_x) {
191 slack_loss += item_position.SizeY() *
192 (bounding_box_size.first - item_position.x_max);
194 if (bounding_box_size.second - item_position.y_max < smallest_y) {
196 slack_loss += item_position.SizeX() *
197 (bounding_box_size.second - item_position.y_max);
199 if (slack < slack_loss) {
264 std::array<absl::InlinedVector<PotentialPositionForItem, 16>,
266 new_potential_positions_storage;
267 absl::Span<absl::InlinedVector<PotentialPositionForItem, 16>>
268 new_potential_positions(new_potential_positions_storage.data(),
270 for (
const int k : placed_item_indexes) {
275 const bool add_below =
277 item_positions[k].y_max <= item_position.y_max &&
279 item_position.x_max + smallest_x <= bounding_box_size.first &&
280 item_positions[k].y_max + smallest_y <= bounding_box_size.second;
281 const bool add_left =
282 item_positions[k].x_max <= item_position.x_max &&
283 item_positions[k].x_max + smallest_x <= bounding_box_size.first &&
284 item_position.y_max + smallest_y <= bounding_box_size.second;
285 for (
int j = 0; j < num_items; ++j) {
286 if (k == j || placed_item_indexes[j]) {
290 add_position_if_valid(new_potential_positions[j], j,
291 item_position.x_max, item_positions[k].y_max);
294 add_position_if_valid(new_potential_positions[j], j,
295 item_positions[k].x_max, item_position.y_max);
299 bool is_unfeasible =
false;
300 for (
int j = 0; j < num_items; ++j) {
302 if (
i == j || placed_item_indexes[j]) {
306 for (
const PotentialPositionForItem& original_position :
307 potential_item_positions[j]) {
308 if (!original_position.GetRectangle(sizes_x[j], sizes_y[j])
309 .IsDisjoint(item_position)) {
320 PotentialPositionForItem position = original_position;
321 position.already_explored =
true;
322 new_potential_positions[j].push_back(position);
324 new_potential_positions[j].push_back(original_position);
327 add_position_if_valid(new_potential_positions[j], j,
328 item_positions[
i].x_max, 0);
329 add_position_if_valid(new_potential_positions[j], j, 0,
330 item_positions[
i].y_max);
331 if (new_potential_positions[j].empty()) {
334 is_unfeasible =
true;
341 if (BruteForceOrthogonalPackingImpl(
342 sizes_x, sizes_y, bounding_box_size, smallest_x, smallest_y,
343 item_positions, placed_item_indexes, new_potential_positions,
344 slack - slack_loss)) {
350 placed_item_indexes.Set(
i,
false);
352 return !has_unplaced_item;
355bool BruteForceOrthogonalPackingNoPreprocessing(
356 const absl::Span<PermutableItem> items,
357 const std::pair<IntegerValue, IntegerValue> bounding_box_size) {
358 IntegerValue smallest_x = std::numeric_limits<IntegerValue>::max();
359 IntegerValue smallest_y = std::numeric_limits<IntegerValue>::max();
360 const int num_items = items.size();
362 IntegerValue slack = bounding_box_size.first * bounding_box_size.second;
364 for (
const PermutableItem& item : items) {
365 smallest_x = std::min(smallest_x, item.size_x);
366 smallest_y = std::min(smallest_y, item.size_y);
367 slack -= item.size_x * item.size_y;
368 if (item.size_x > bounding_box_size.first ||
369 item.size_y > bounding_box_size.second) {
377 std::sort(items.begin(), items.end(),
378 [](
const PermutableItem&
a,
const PermutableItem&
b) {
379 return std::make_tuple(a.size_x * a.size_y, a.index) >
380 std::make_tuple(b.size_x * b.size_y, b.index);
383 std::array<IntegerValue, kMaxProblemSize> new_sizes_x_storage,
385 absl::Span<IntegerValue> new_sizes_x(new_sizes_x_storage.data(), num_items);
386 absl::Span<IntegerValue> new_sizes_y(new_sizes_y_storage.data(), num_items);
387 std::array<absl::InlinedVector<PotentialPositionForItem, 16>,
kMaxProblemSize>
388 potential_item_positions_storage;
389 absl::Span<absl::InlinedVector<PotentialPositionForItem, 16>>
390 potential_item_positions(potential_item_positions_storage.data(),
392 for (
int i = 0;
i < num_items; ++
i) {
393 new_sizes_x[
i] = items[
i].size_x;
394 new_sizes_y[
i] = items[
i].size_y;
395 potential_item_positions[
i].push_back({0, 0,
false});
397 std::array<Rectangle, kMaxProblemSize> item_positions_storage;
398 absl::Span<Rectangle> item_positions(item_positions_storage.data(),
400 Bitset64<int> placed_item_indexes(num_items);
401 const bool found_solution = BruteForceOrthogonalPackingImpl(
402 new_sizes_x, new_sizes_y, bounding_box_size, smallest_x, smallest_y,
403 item_positions, placed_item_indexes, potential_item_positions, slack);
404 if (!found_solution) {
407 for (
int i = 0;
i < num_items; ++
i) {
408 items[
i].position = item_positions[
i];
480bool PreprocessLargeWithSmallX(
481 absl::Span<PermutableItem>& items,
482 std::pair<IntegerValue, IntegerValue>& bounding_box_size,
483 int max_complexity) {
491 items.begin(), items.end(),
492 [&bounding_box_size](
const PermutableItem&
a,
const PermutableItem&
b) {
493 const bool a_is_small = 2 * a.size_x <= bounding_box_size.first;
494 const bool b_is_small = 2 * b.size_x <= bounding_box_size.first;
495 const IntegerValue a_size_for_comp =
496 a_is_small ? a.size_x : bounding_box_size.first - a.size_x;
497 const IntegerValue b_size_for_comp =
498 b_is_small ? b.size_x : bounding_box_size.first - b.size_x;
499 return std::make_tuple(a_size_for_comp, !a_is_small, a.index) >
500 std::make_tuple(b_size_for_comp, !b_is_small, b.index);
502 IntegerValue total_large_item_width = 0;
503 IntegerValue largest_small_item_height = 0;
504 for (
int i = items.size() - 1;
i >= 1; --
i) {
505 if (2 * items[
i].size_x <= bounding_box_size.first) {
506 largest_small_item_height = items[
i].size_x;
509 DCHECK_LE(items[
i].size_x + largest_small_item_height,
510 bounding_box_size.first);
514 if (items.subspan(
i).size() > max_complexity) {
517 total_large_item_width += items[
i].size_y;
518 if (BruteForceOrthogonalPackingNoPreprocessing(
520 {bounding_box_size.first, total_large_item_width})) {
521 bounding_box_size.second -= total_large_item_width;
522 for (
auto& item : items.subspan(
i)) {
523 item.position.y_min += bounding_box_size.second;
524 item.position.y_max += bounding_box_size.second;
526 items = items.subspan(0,
i);
535bool PreprocessLargeWithSmallY(
536 absl::Span<PermutableItem>& items,
537 std::pair<IntegerValue, IntegerValue>& bounding_box_size,
538 int max_complexity) {
539 absl::Span<PermutableItem> orig_items = items;
540 for (PermutableItem& item : orig_items) {
541 std::swap(item.size_x, item.size_y);
542 std::swap(item.position.x_min, item.position.y_min);
543 std::swap(item.position.x_max, item.position.y_max);
545 std::swap(bounding_box_size.first, bounding_box_size.second);
547 PreprocessLargeWithSmallX(items, bounding_box_size, max_complexity);
548 std::swap(bounding_box_size.first, bounding_box_size.second);
549 for (PermutableItem& item : orig_items) {
550 std::swap(item.size_x, item.size_y);
551 std::swap(item.position.x_min, item.position.y_min);
552 std::swap(item.position.x_max, item.position.y_max);
568 std::pair<IntegerValue, IntegerValue>& bounding_box_size,
569 int max_complexity) {
570 const int num_items = items.size();
571 if (num_items == 1) {
574 IntegerValue smallest_x = std::numeric_limits<IntegerValue>::max();
575 IntegerValue largest_x = std::numeric_limits<IntegerValue>::min();
576 IntegerValue smallest_y = std::numeric_limits<IntegerValue>::max();
577 IntegerValue largest_y = std::numeric_limits<IntegerValue>::min();
578 int largest_x_idx = -1;
579 int largest_y_idx = -1;
580 for (
int i = 0;
i < num_items; ++
i) {
581 if (items[
i].size_x > largest_x) {
582 largest_x = items[
i].size_x;
585 if (items[
i].size_y > largest_y) {
586 largest_y = items[
i].size_y;
589 smallest_x = std::min(smallest_x, items[
i].size_x);
590 smallest_y = std::min(smallest_y, items[
i].size_y);
593 if (largest_x > bounding_box_size.first ||
594 largest_y > bounding_box_size.second) {
598 const auto remove_item = [&items](
int index_to_remove) {
599 std::swap(items[index_to_remove], items.back());
600 items.remove_suffix(1);
602 if (largest_x + smallest_x > bounding_box_size.first) {
605 bounding_box_size.second -= items[largest_x_idx].size_y;
606 items[largest_x_idx].position = {
609 .y_min = bounding_box_size.second,
610 .y_max = bounding_box_size.second + items[largest_x_idx].size_y};
611 remove_item(largest_x_idx);
612 Preprocess(items, bounding_box_size, max_complexity);
615 if (largest_y + smallest_y > bounding_box_size.second) {
616 bounding_box_size.first -= items[largest_y_idx].size_x;
617 items[largest_y_idx].position = {
618 .x_min = bounding_box_size.first,
619 .x_max = bounding_box_size.first + items[largest_y_idx].size_x,
622 remove_item(largest_y_idx);
623 Preprocess(items, bounding_box_size, max_complexity);
627 if (PreprocessLargeWithSmallX(items, bounding_box_size, max_complexity)) {
628 Preprocess(items, bounding_box_size, max_complexity);
632 if (PreprocessLargeWithSmallY(items, bounding_box_size, max_complexity)) {
633 Preprocess(items, bounding_box_size, max_complexity);
641 absl::Span<const IntegerValue> sizes_x,
642 absl::Span<const IntegerValue> sizes_y,
643 std::pair<IntegerValue, IntegerValue> bounding_box_size,
644 int max_complexity) {
645 const int num_items = sizes_x.size();
647 if (num_items > 2 * max_complexity) {
654 std::array<PermutableItem, kMaxProblemSize> items_storage;
655 absl::Span<PermutableItem> items(items_storage.data(), num_items);
656 for (
int i = 0;
i < num_items; ++
i) {
658 .size_x = sizes_x[
i], .size_y = sizes_y[
i], .index =
i, .position = {}};
660 absl::Span<PermutableItem> post_processed_items = items;
661 std::pair<IntegerValue, IntegerValue> post_processed_bounding_box_size =
663 const bool post_processed =
664 Preprocess(post_processed_items, post_processed_bounding_box_size,
666 if (post_processed_items.size() > max_complexity) {
669 const bool is_feasible = BruteForceOrthogonalPackingNoPreprocessing(
670 post_processed_items, post_processed_bounding_box_size);
671 VLOG_EVERY_N_SEC(2, 3)
672 <<
"Solved by brute force a problem of " << num_items <<
" items"
673 << (post_processed ? absl::StrCat(
" (", post_processed_items.size(),
674 " after preprocessing)")
676 <<
": solution " << (is_feasible ?
"found" :
"not found") <<
".";
680 std::vector<Rectangle> result(num_items);
682 result[item.index] = item.position;
687 .positions_for_solution = result};
BruteForceResult BruteForceOrthogonalPacking(absl::Span< const IntegerValue > sizes_x, absl::Span< const IntegerValue > sizes_y, std::pair< IntegerValue, IntegerValue > bounding_box_size, int max_complexity)
bool Preprocess(absl::Span< PermutableItem > &items, std::pair< IntegerValue, IntegerValue > &bounding_box_size, int max_complexity)
Exposed for testing.
static constexpr int kMaxProblemSize
In SWIG mode, we don't want anything besides these top-level includes.