Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
2d_packing_brute_force.cc
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <array>
18#include <limits>
19#include <tuple>
20#include <utility>
21#include <vector>
22
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"
29#include "ortools/sat/integer.h"
30#include "ortools/util/bitset.h"
31
32namespace operations_research {
33namespace sat {
34
35static constexpr int kMaxProblemSize = 16;
36
37namespace {
38
39enum class RectangleRelationship {
40 TOUCHING_NEITHER_LEFT_OR_BOTTOM,
41 TOUCHING_BOTTOM,
42 TOUCHING_LEFT,
43 OVERLAP,
44};
45
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;
51 }
52
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;
56 }
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;
60 }
61 return RectangleRelationship::TOUCHING_NEITHER_LEFT_OR_BOTTOM;
62}
63
64bool ShouldPlaceItemAtPosition(
65 int i, const Rectangle& item_position,
66 std::pair<IntegerValue, IntegerValue> bounding_box_size,
67 absl::Span<const Rectangle> item_positions,
68 const Bitset64<int>& placed_item_indexes) {
69 // Check if it fits in the BB.
70 if (item_position.x_max > bounding_box_size.first ||
71 item_position.y_max > bounding_box_size.second) {
72 return false;
73 }
74
75 // Break symmetry: force 0th item to be in the bottom left quarter.
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())) {
80 return false;
81 }
82
83 // Check if it is conflicting with another item.
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) {
87 DCHECK_NE(i, j);
88 const RectangleRelationship pos =
89 GetRectangleRelationship(item_position, item_positions[j]);
90 if (pos == RectangleRelationship::OVERLAP) {
91 return false;
92 }
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;
97 }
98
99 // Finally, check if it touching something both on the bottom and to the left.
100 if (!touches_something_on_left || !touches_something_on_bottom) {
101 return false;
102 }
103 return true;
104}
105
106struct PotentialPositionForItem {
107 IntegerValue x;
108 IntegerValue y;
110
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};
113 }
114};
115
116// This implementation search for a solution in the following order:
117// - first place the 0-th item in the bottom left corner;
118// - then place the 1-th item either on the bottom of the bounding box to the
119// right of the 0-th item, or on the left of the bounding box on top of it;
120// - keep placing items, while respecting that each item should touch something
121// on both its bottom and left sides until either all items are placed (in
122// this case a solution is found and return) or we found an item that cannot
123// be placed on any possible solution.
124// - if an item cannot be placed, backtrack: try to place the last successfully
125// placed item in another position.
126//
127// This is a recursive implementation, each call will place the first non placed
128// item in a fixed order. Backtrack occur when we return from a recursive call.
129//
130// This return false iff it is infeasible to place the other items given the
131// already placed ones.
132//
133// This implementation is very similar to the "Left-Most Active Only" method
134// described in Clautiaux, François, Jacques Carlier, and Aziz Moukrim. "A new
135// exact method for the two-dimensional orthogonal packing problem." European
136// Journal of Operational Research 183.3 (2007): 1196-1211.
137//
138// TODO(user): try the graph-based algorithm by S. Fekete, J. Shepers, and
139// J. Van Der Ween, https://arxiv.org/abs/cs/0604045.
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],
156 .y_min = y,
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});
161 }
162 };
163
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]) {
168 continue;
169 }
170 if (potential_item_positions[i].empty()) {
171 return false;
172 }
173
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) {
179 continue;
180 }
181 // Place the item on its candidate position.
182 item_positions[i] =
183 potential_position.GetRectangle(sizes_x[i], sizes_y[i]);
184 const Rectangle& item_position = item_positions[i];
185
186 IntegerValue slack_loss = 0;
187 if (bounding_box_size.first - item_position.x_max < smallest_x) {
188 // After placing this item, nothing will fit between it and the top of
189 // the bounding box. Thus we have some space that will remain empty and
190 // we can deduce it from our budget.
191 slack_loss += item_position.SizeY() *
192 (bounding_box_size.first - item_position.x_max);
193 }
194 if (bounding_box_size.second - item_position.y_max < smallest_y) {
195 // Same as above but with the right edge.
196 slack_loss += item_position.SizeX() *
197 (bounding_box_size.second - item_position.y_max);
198 }
199 if (slack < slack_loss) {
200 continue;
201 }
202
203 // Now the hard part of the algorithm: create the new "potential
204 // positions" vector after placing this item. Describing the actual set of
205 // acceptable places to put consider for the next item in the search would
206 // be pretty complex. For example:
207 // +----------------------------+
208 // | |
209 // |x |
210 // |--------+ |
211 // |88888888| |
212 // |88888888| |
213 // |--------+ |
214 // |####| |
215 // |####|x x |
216 // |####| +------+ |
217 // |####| |......| |
218 // |####| |......| |
219 // |####| |......| |
220 // |####|x x |......| |
221 // |####+---------+......| |
222 // |####|OOOOOOOOO|......| |
223 // |####|OOOOOOOOO|......| |
224 // |####|OOOOOOOOO|......|x |
225 // +----+---------+------+------+
226 //
227 // We consider that every item must be touching something (other item or
228 // the box boundaries) to the left and to the bottom. Thus, when we add a
229 // new item, it is enough to consider at all positions where it would
230 // touch the new item on the bottom and something else on the left or
231 // touch the new item on the left and something else on the bottom. So we
232 // consider the following points:
233 // - all previous positions if they didn't got invalid due to the new
234 // item;
235 // - new position are derived getting the right-top most corner of the
236 // added item and connecting it to the bottom and the left with a line.
237 // New potential positions are the intersection of this line with either
238 // the current items or the box. For example, if we add a box to the
239 // example above (representing the two lines by '*'):
240 // +----------------------------+
241 // | |
242 // | |
243 // |--------+ |
244 // |88888888| |
245 // |88888888| |
246 // |--------+ |
247 // |####| |
248 // |####| |
249 // |####| +------+ |
250 // |x###|x |......|x |
251 // |************************** |
252 // |####| |......|@@@* |
253 // |####| |......|@@@* |
254 // |####+---------+......|@@@* |
255 // |####|OOOOOOOOO|......|@@@* |
256 // |####|OOOOOOOOO|......|@@@* |
257 // |####|OOOOOOOOO|......|@@@*x |
258 // +----+---------+------+------+
259 //
260 // This method finds potential locations that are not useful for any item,
261 // (like the point in the left boundary in the example above) but we will
262 // detect that by testing each item one by one. Importantly, we only pass
263 // valid positions down to the next search level.
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(),
269 num_items);
270 for (const int k : placed_item_indexes) {
271 if (k == i) {
272 continue;
273 }
274
275 const bool add_below =
276 // We only add points below this one...
277 item_positions[k].y_max <= item_position.y_max &&
278 // ...and where we can fit at least the smallest element.
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]) {
287 continue;
288 }
289 if (add_below) {
290 add_position_if_valid(new_potential_positions[j], j,
291 item_position.x_max, item_positions[k].y_max);
292 }
293 if (add_left) {
294 add_position_if_valid(new_potential_positions[j], j,
295 item_positions[k].x_max, item_position.y_max);
296 }
297 }
298 }
299 bool is_unfeasible = false;
300 for (int j = 0; j < num_items; ++j) {
301 // No positions to attribute to the item we just placed.
302 if (i == j || placed_item_indexes[j]) {
303 continue;
304 }
305 // First copy previously valid positions that remain valid.
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)) {
310 // That was a valid position for item j, but now it is in conflict
311 // with newly added item i.
312 continue;
313 }
314 if (j < i) {
315 // We already explored all items of index less than i in all their
316 // current possible positions and they are all unfeasible. We still
317 // keep track of whether it fit there or not, since having any item
318 // that don't fit anywhere is a good stopping criteria. But we don't
319 // have to retest those positions down in the search tree.
320 PotentialPositionForItem position = original_position;
321 position.already_explored = true;
322 new_potential_positions[j].push_back(position);
323 } else {
324 new_potential_positions[j].push_back(original_position);
325 }
326 }
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()) {
332 // After placing the item i, there is no valid place to choose for the
333 // item j. We must pick another placement for i.
334 is_unfeasible = true;
335 break;
336 }
337 }
338 if (is_unfeasible) {
339 continue;
340 }
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)) {
345 return true;
346 }
347 }
348 // Placing this item at the current bottom-left positions level failed.
349 // Restore placed_item_indexes to its original value and try another one.
350 placed_item_indexes.Set(i, false);
351 }
352 return !has_unplaced_item;
353}
354
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();
361 CHECK_LE(num_items, kMaxProblemSize);
362 IntegerValue slack = bounding_box_size.first * bounding_box_size.second;
363
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) {
370 return false;
371 }
372 }
373 if (slack < 0) {
374 return false;
375 }
376
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);
381 });
382
383 std::array<IntegerValue, kMaxProblemSize> new_sizes_x_storage,
384 new_sizes_y_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(),
391 num_items);
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});
396 }
397 std::array<Rectangle, kMaxProblemSize> item_positions_storage;
398 absl::Span<Rectangle> item_positions(item_positions_storage.data(),
399 num_items);
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) {
405 return false;
406 }
407 for (int i = 0; i < num_items; ++i) {
408 items[i].position = item_positions[i];
409 }
410 return true;
411}
412
413// This function tries to pack a set of "tall" items with all the "shallow"
414// items that can fit in the area around them. See discussion about
415// the identically-feasible function v_1 in [1]. For example, for the packing:
416//
417// +----------------------------+
418// |------+ |
419// |888888+----| |
420// |888888|&&&&| |
421// |------+&&&&| |
422// |####| |&&&&| |
423// |####| +----+ |
424// |####| +------+ |
425// |####| |......| |
426// |####| |......|@@@ |
427// |####+---------+......|@@@ |
428// |####|OOOOOOOOO|......|@@@ |
429// |####|OOOOOOOOO|......|@@@ |
430// |####|OOOOOOOOO|......|@@@ |
431// |####|OOOOOOOOO|......|@@@ |
432// |####|OOOOOOOOO|......|@@@ |
433// +----+---------+------+------+
434//
435// We can move all "tall" and "shallow" items to the left and pack all the
436// shallow items on the space remaining on the top of the tall items:
437//
438// +----------------------------+
439// |------+ |
440// |888888+----| |
441// |888888|&&&&| |
442// |------+&&&&| |
443// |####| |&&&&| |
444// |####| +----+ |
445// |####+------+ |
446// |####|......| |
447// |####|......| @@@ |
448// |####|......+---------+@@@ |
449// |####|......|OOOOOOOOO|@@@ |
450// |####|......|OOOOOOOOO|@@@ |
451// |####|......|OOOOOOOOO|@@@ |
452// |####|......|OOOOOOOOO|@@@ |
453// |####|......|OOOOOOOOO|@@@ |
454// +----+------+---------+------+
455//
456// If the packing is successful, we can remove both set from the problem:
457// +----------------+
458// | |
459// | |
460// | |
461// | |
462// | |
463// | |
464// | |
465// | |
466// | @@@ |
467// |---------+@@@ |
468// |OOOOOOOOO|@@@ |
469// |OOOOOOOOO|@@@ |
470// |OOOOOOOOO|@@@ |
471// |OOOOOOOOO|@@@ |
472// |OOOOOOOOO|@@@ |
473// +---------+------+
474//
475// [1] Carlier, Jacques, François Clautiaux, and Aziz Moukrim. "New reduction
476// procedures and lower bounds for the two-dimensional bin packing problem with
477// fixed orientation." Computers & Operations Research 34.8 (2007): 2223-2250.
478//
479// See Preprocess() for the API documentation.
480bool PreprocessLargeWithSmallX(
481 absl::Span<PermutableItem>& items,
482 std::pair<IntegerValue, IntegerValue>& bounding_box_size,
483 int max_complexity) {
484 // The simplest way to implement this is to sort the shallow items alongside
485 // their corresponding tall items. More precisely, the smallest and largest
486 // items are at the end of the list. The reason we put the most interesting
487 // values in the end even if this means we want to iterate on the list
488 // backward is that if the heuristic is successful we will trim them from the
489 // back of the list of unfixed items.
490 std::sort(
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);
501 });
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;
507 continue;
508 }
509 DCHECK_LE(items[i].size_x + largest_small_item_height,
510 bounding_box_size.first);
511 // We found a big item. So all values that we visited before are either
512 // taller than it or shallow enough to fit on top of it. Try to fit all that
513 // together!
514 if (items.subspan(i).size() > max_complexity) {
515 return false;
516 }
517 total_large_item_width += items[i].size_y;
518 if (BruteForceOrthogonalPackingNoPreprocessing(
519 items.subspan(i),
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;
525 }
526 items = items.subspan(0, i);
527 return true;
528 }
529 }
530
531 return false;
532}
533
534// Same API as Preprocess().
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);
544 }
545 std::swap(bounding_box_size.first, bounding_box_size.second);
546 const bool ret =
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);
553 }
554 return ret;
555}
556
557} // namespace
558
559// Try to find an equivalent smaller OPP problem by fixing large items.
560// The API is a bit unusual: it takes a reference to a mutable Span of sizes and
561// rectangles. When this function finds an item that can be fixed, it sets
562// the position of the PermutableItem, reorders `items` to put that item in the
563// end of the span and then resizes the span so it contain only non-fixed items.
564//
565// Note that the position of input items is not used and the position of
566// non-fixed items will not be modified by this function.
567bool Preprocess(absl::Span<PermutableItem>& items,
568 std::pair<IntegerValue, IntegerValue>& bounding_box_size,
569 int max_complexity) {
570 const int num_items = items.size();
571 if (num_items == 1) {
572 return false;
573 }
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;
583 largest_x_idx = i;
584 }
585 if (items[i].size_y > largest_y) {
586 largest_y = items[i].size_y;
587 largest_y_idx = i;
588 }
589 smallest_x = std::min(smallest_x, items[i].size_x);
590 smallest_y = std::min(smallest_y, items[i].size_y);
591 }
592
593 if (largest_x > bounding_box_size.first ||
594 largest_y > bounding_box_size.second) {
595 // No point in optimizing obviously infeasible instance.
596 return false;
597 }
598 const auto remove_item = [&items](int index_to_remove) {
599 std::swap(items[index_to_remove], items.back());
600 items.remove_suffix(1);
601 };
602 if (largest_x + smallest_x > bounding_box_size.first) {
603 // No item (not even the narrowest one) fit alongside the widest item. So we
604 // care only about fitting the remaining items in the remaining space.
605 bounding_box_size.second -= items[largest_x_idx].size_y;
606 items[largest_x_idx].position = {
607 .x_min = 0,
608 .x_max = largest_x,
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);
613 return true;
614 }
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,
620 .y_min = 0,
621 .y_max = largest_y};
622 remove_item(largest_y_idx);
623 Preprocess(items, bounding_box_size, max_complexity);
624 return true;
625 }
626
627 if (PreprocessLargeWithSmallX(items, bounding_box_size, max_complexity)) {
628 Preprocess(items, bounding_box_size, max_complexity);
629 return true;
630 }
631
632 if (PreprocessLargeWithSmallY(items, bounding_box_size, max_complexity)) {
633 Preprocess(items, bounding_box_size, max_complexity);
634 return true;
635 }
636
637 return false;
638}
639
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();
646
647 if (num_items > 2 * max_complexity) {
648 // It is unlikely that preprocessing will remove half of the items, so don't
649 // lose time trying.
650 return {.status = BruteForceResult::Status::kTooBig};
651 }
652 CHECK_LE(num_items, kMaxProblemSize);
653
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) {
657 items[i] = {
658 .size_x = sizes_x[i], .size_y = sizes_y[i], .index = i, .position = {}};
659 }
660 absl::Span<PermutableItem> post_processed_items = items;
661 std::pair<IntegerValue, IntegerValue> post_processed_bounding_box_size =
662 bounding_box_size;
663 const bool post_processed =
664 Preprocess(post_processed_items, post_processed_bounding_box_size,
665 max_complexity - 1);
666 if (post_processed_items.size() > max_complexity) {
667 return {.status = BruteForceResult::Status::kTooBig};
668 }
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)")
675 : "")
676 << ": solution " << (is_feasible ? "found" : "not found") << ".";
677 if (!is_feasible) {
679 }
680 std::vector<Rectangle> result(num_items);
681 for (const PermutableItem& item : items) {
682 result[item.index] = item.position;
683 }
684 // VLOG_EVERY_N_SEC(3, 3) << "Found a feasible packing by brute force. Dot:\n "
685 // << RenderDot(bounding_box_size, result);
687 .positions_for_solution = result};
688}
689
690} // namespace sat
691} // namespace operations_research
IntegerValue y
bool already_explored
int64_t a
Definition table.cc:44
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.
const Variable x
Definition qp_tests.cc:127