Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
2d_orthogonal_packing.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 <cstdint>
18#include <limits>
19#include <optional>
20#include <string>
21#include <tuple>
22#include <utility>
23#include <vector>
24
25#include "absl/log/check.h"
26#include "absl/numeric/bits.h"
27#include "absl/random/distributions.h"
28#include "absl/types/span.h"
31#include "ortools/sat/integer.h"
32#include "ortools/sat/util.h"
33#include "ortools/util/bitset.h"
34
35namespace operations_research {
36namespace sat {
37
40 if (!VLOG_IS_ON(1)) return;
41 std::vector<std::pair<std::string, int64_t>> stats;
42 stats.push_back(
43 {"OrthogonalPackingInfeasibilityDetector/called", num_calls_});
44 stats.push_back(
45 {"OrthogonalPackingInfeasibilityDetector/conflicts", num_conflicts_});
46 stats.push_back({"OrthogonalPackingInfeasibilityDetector/dff0_conflicts",
47 num_conflicts_dff0_});
48 stats.push_back({"OrthogonalPackingInfeasibilityDetector/dff2_conflicts",
49 num_conflicts_dff2_});
50 stats.push_back({"OrthogonalPackingInfeasibilityDetector/trivial_conflicts",
51 num_trivial_conflicts_});
52 stats.push_back({"OrthogonalPackingInfeasibilityDetector/conflicts_two_items",
53 num_conflicts_two_items_});
54 stats.push_back({"OrthogonalPackingInfeasibilityDetector/no_energy_conflict",
55 num_scheduling_possible_});
56 stats.push_back({"OrthogonalPackingInfeasibilityDetector/brute_force_calls",
57 num_brute_force_calls_});
58 stats.push_back(
59 {"OrthogonalPackingInfeasibilityDetector/brute_force_conflicts",
60 num_brute_force_conflicts_});
61 stats.push_back(
62 {"OrthogonalPackingInfeasibilityDetector/brute_force_relaxations",
63 num_brute_force_relaxation_});
64
65 shared_stats_->AddStats(stats);
66}
67
68namespace {
69std::optional<std::pair<int, int>> FindPairwiseConflict(
70 absl::Span<const IntegerValue> sizes_x,
71 absl::Span<const IntegerValue> sizes_y,
72 std::pair<IntegerValue, IntegerValue> bounding_box_size,
73 const std::vector<int>& index_by_decreasing_x_size,
74 const std::vector<int>& index_by_decreasing_y_size) {
75 // Look for pairwise incompatible pairs by using the logic such conflict can
76 // only happen between a "tall" item a "wide" item.
77 int x_idx = 0;
78 int y_idx = 0;
79 while (x_idx < index_by_decreasing_x_size.size() &&
80 y_idx < index_by_decreasing_y_size.size()) {
81 if (index_by_decreasing_x_size[x_idx] ==
82 index_by_decreasing_y_size[y_idx]) {
83 if (sizes_x[index_by_decreasing_x_size[x_idx]] >
84 sizes_y[index_by_decreasing_x_size[x_idx]]) {
85 y_idx++;
86 } else {
87 x_idx++;
88 }
89 continue;
90 }
91 const bool overlap_on_x = (sizes_x[index_by_decreasing_x_size[x_idx]] +
92 sizes_x[index_by_decreasing_y_size[y_idx]] >
93 bounding_box_size.first);
94 const bool overlap_on_y = (sizes_y[index_by_decreasing_x_size[x_idx]] +
95 sizes_y[index_by_decreasing_y_size[y_idx]] >
96 bounding_box_size.second);
97 if (overlap_on_x && overlap_on_y) {
98 return std::make_pair(index_by_decreasing_x_size[x_idx],
99 index_by_decreasing_y_size[y_idx]);
100 } else if (overlap_on_x) {
101 x_idx++;
102 } else if (overlap_on_y) {
103 y_idx++;
104 } else {
105 y_idx++;
106 }
107 }
108 return std::nullopt;
109}
110
111IntegerValue RoundingLowestInverse(IntegerValue y, IntegerValue c_k,
112 IntegerValue max_x, IntegerValue k) {
113 DCHECK_GE(y, 0);
114 DCHECK_LE(y, 2 * c_k);
115 IntegerValue ret = std::numeric_limits<IntegerValue>::max();
116
117 // Are we in the case 2 * x == max_x_?
118 if (y <= c_k && (max_x.value() & 1) == 0) {
119 const IntegerValue inverse_mid = max_x / 2;
120 ret = std::min(ret, inverse_mid);
121 if (y == c_k && y.value() & 1) {
122 // This is the only valid case for odd x.
123 return ret;
124 }
125 }
126
127 // The "perfect odd" case is handled above, round up y to an even value.
128 y += y.value() & 1;
129
130 // Check the case 2 * x > max_x_.
131 const IntegerValue inverse_high = max_x - k * (c_k - y / 2);
132 if (2 * inverse_high > max_x) {
133 // We have an inverse in this domain, let's find its minimum value (when
134 // the division rounds down the most) but don't let it go outside the
135 // domain.
136 const IntegerValue lowest_inverse_high =
137 std::max(max_x / 2 + 1, inverse_high - k + 1);
138 ret = std::min(ret, lowest_inverse_high);
139 }
140
141 // Check the case 2 * x < max_x_.
142 const IntegerValue inverse_low = k * y / 2;
143 if (2 * inverse_low < max_x) {
144 ret = std::min(ret, inverse_low);
145 }
146 return ret;
147}
148} // namespace
149
150IntegerValue RoundingDualFeasibleFunction::LowestInverse(IntegerValue y) const {
151 return RoundingLowestInverse(y, c_k_, max_x_, k_);
152}
153
155 IntegerValue y) const {
156 return RoundingLowestInverse(y, c_k_, max_x_, IntegerValue(1) << log2_k_);
157}
158
159// Check for conflict using the `f_0^k` dual feasible function (see
160// documentation for DualFeasibleFunctionF0). This function tries all possible
161// values of the `k` parameter and returns the best conflict found (according to
162// OrthogonalPackingResult::IsBetterThan) if any.
163//
164// The current implementation is a bit more general than a simple check using
165// f_0 described above. This implementation can take a function g(x) that is
166// non-decreasing and satisfy g(0)=0 and it will check for conflict using
167// g(f_0^k(x)) for all values of k, but without recomputing g(x) `k` times. This
168// is handy if g() is a DFF that is slow to compute. g(x) is described by the
169// vector g_x[i] = g(sizes_x[i]) and the variable g_max = g(x_bb_size).
170//
171// The algorithm is the same if we swap the x and y dimension.
172OrthogonalPackingResult OrthogonalPackingInfeasibilityDetector::GetDffConflict(
173 absl::Span<const IntegerValue> sizes_x,
174 absl::Span<const IntegerValue> sizes_y,
175 absl::Span<const int> index_by_decreasing_x_size,
176 absl::Span<const IntegerValue> g_x, IntegerValue g_max,
177 IntegerValue x_bb_size, IntegerValue total_energy, IntegerValue bb_area,
178 IntegerValue* best_k) {
179 // If we found a conflict for a k parameter, which is rare, recompute the
180 // total used energy consumed by the items to find the minimal set of
181 // conflicting items.
182 int num_items = sizes_x.size();
183 auto build_result = [&sizes_x, &sizes_y, num_items, &x_bb_size, &bb_area,
184 &g_max, &g_x](const IntegerValue k) {
185 std::vector<std::pair<int, IntegerValue>> index_to_energy;
186 index_to_energy.reserve(num_items);
187 for (int i = 0; i < num_items; i++) {
188 IntegerValue point_value;
189 if (sizes_x[i] > x_bb_size - k) {
190 point_value = g_max;
191 } else if (sizes_x[i] < k) {
192 continue;
193 } else {
194 point_value = g_x[i];
195 }
196 index_to_energy.push_back({i, point_value * sizes_y[i]});
197 }
198 std::sort(index_to_energy.begin(), index_to_energy.end(),
199 [](const std::pair<int, IntegerValue>& a,
200 const std::pair<int, IntegerValue>& b) {
201 return a.second > b.second;
202 });
203 IntegerValue recomputed_energy = 0;
204 for (int i = 0; i < index_to_energy.size(); i++) {
205 recomputed_energy += index_to_energy[i].second;
206 if (recomputed_energy > bb_area) {
207 OrthogonalPackingResult result(
209 result.conflict_type_ = OrthogonalPackingResult::ConflictType::DFF_F0;
210 result.items_participating_on_conflict_.resize(i + 1);
211 for (int j = 0; j <= i; j++) {
212 const int index = index_to_energy[j].first;
213 result.items_participating_on_conflict_[j] = {
214 .index = index,
215 .size_x = sizes_x[index],
216 .size_y = sizes_y[index]};
217 }
218 result.slack_ = 0;
219 return result;
220 }
221 }
222 LOG(FATAL) << "build_result called with no conflict";
223 };
224
225 // One thing we use in this implementation is that not all values of k are
226 // interesting: what can cause an energy conflict is increasing the size of
227 // the large items, removing the small ones makes it less constrained and we
228 // do it only to preserve correctness. Thus, it is enough to check the values
229 // of k that are just small enough to enlarge a large item. That means that
230 // large items and small ones are not symmetric with respect to what values of
231 // k are important.
232 IntegerValue current_energy = total_energy;
233 OrthogonalPackingResult best_result;
234 if (current_energy > bb_area) {
235 best_result = build_result(0);
236 *best_k = 0;
237 }
238 // We keep an index on the largest item yet-to-be enlarged and the smallest
239 // one yet-to-be removed.
240 int removing_item_index = index_by_decreasing_x_size.size() - 1;
241 int enlarging_item_index = 0;
242 while (enlarging_item_index < index_by_decreasing_x_size.size()) {
243 int index = index_by_decreasing_x_size[enlarging_item_index];
244 IntegerValue size = sizes_x[index];
245 // Note that since `size_x` is decreasing, we test increasingly large
246 // values of k. Also note that a item with size `k` cannot fit alongside
247 // an item with size `size_x`, but smaller ones can.
248 const IntegerValue k = x_bb_size - size + 1;
249 if (2 * k > x_bb_size) {
250 break;
251 }
252 // First, add the area contribution of enlarging the all the items of size
253 // exactly size_x. All larger items were already enlarged in the previous
254 // iterations.
255 do {
256 index = index_by_decreasing_x_size[enlarging_item_index];
257 size = sizes_x[index];
258 current_energy += (g_max - g_x[index]) * sizes_y[index];
259 enlarging_item_index++;
260 } while (enlarging_item_index < index_by_decreasing_x_size.size() &&
261 sizes_x[index_by_decreasing_x_size[enlarging_item_index]] == size);
262
263 // Now remove the area contribution of removing all the items smaller than
264 // k that were not removed before.
265 while (removing_item_index >= 0 &&
266 sizes_x[index_by_decreasing_x_size[removing_item_index]] < k) {
267 const int remove_idx = index_by_decreasing_x_size[removing_item_index];
268 current_energy -= g_x[remove_idx] * sizes_y[remove_idx];
269 removing_item_index--;
270 }
271
272 if (current_energy > bb_area) {
273 OrthogonalPackingResult current_result = build_result(k);
274 if (current_result.IsBetterThan(best_result)) {
275 best_result = current_result;
276 *best_k = k;
277 }
278 }
279 }
280 return best_result;
281}
282
283namespace {
284
285// Tries a simple heuristic to find a solution for the Resource-Constrained
286// Project Scheduling Problem (RCPSP). The RCPSP can be mapped to a
287// 2d bin packing where one dimension (say, x) is chosen to represent the time,
288// and every item is cut into items with size_x = 1 that must remain consecutive
289// in the x-axis but do not need to be aligned on the y axis. This is often
290// called the cumulative relaxation of the 2d bin packing problem.
291//
292// Bin-packing solution RCPSP solution
293// --------------- ---------------
294// | ********** | | ***** |
295// | ********** | | ***** |
296// | ##### | | **#####*** |
297// | ##### | | **#####*** |
298// --------------- ---------------
299//
300// One interesting property is if we find an energy conflict using a
301// superadditive function it means the problem is infeasible both interpreted as
302// a 2d bin packing and as a RCPSP problem. In practice, that means that if we
303// find a RCPSP solution for a 2d bin packing problem, there is no point on
304// using Maximal DFFs to search for energy conflicts.
305//
306// Returns true if it found a feasible solution to the RCPSP problem.
307bool FindHeuristicSchedulingSolution(
308 absl::Span<const IntegerValue> sizes,
309 absl::Span<const IntegerValue> demands,
310 absl::Span<const int> heuristic_order, IntegerValue global_end_max,
311 IntegerValue capacity_max,
312 std::vector<std::pair<IntegerValue, IntegerValue>>& profile,
313 std::vector<std::pair<IntegerValue, IntegerValue>>& new_profile) {
314 // The profile (and new profile) is a set of (time, capa_left) pairs, ordered
315 // by increasing time and capa_left.
316 profile.clear();
317 profile.emplace_back(kMinIntegerValue, capacity_max);
318 profile.emplace_back(kMaxIntegerValue, capacity_max);
319 IntegerValue start_of_previous_task = kMinIntegerValue;
320 for (int i = 0; i < heuristic_order.size(); i++) {
321 const IntegerValue event_size = sizes[heuristic_order[i]];
322 const IntegerValue event_demand = demands[heuristic_order[i]];
323 const IntegerValue event_start_min = 0;
324 const IntegerValue event_start_max = global_end_max - event_size;
325 const IntegerValue start_min =
326 std::max(event_start_min, start_of_previous_task);
327
328 // Iterate on the profile to find the step that contains start_min.
329 // Then push until we find a step with enough capacity.
330 int current = 0;
331 while (profile[current + 1].first <= start_min ||
332 profile[current].second < event_demand) {
333 ++current;
334 }
335
336 const IntegerValue actual_start =
337 std::max(start_min, profile[current].first);
338 start_of_previous_task = actual_start;
339
340 // Compatible with the event.start_max ?
341 if (actual_start > event_start_max) return false;
342
343 const IntegerValue actual_end = actual_start + event_size;
344
345 // No need to update the profile on the last loop.
346 if (i == heuristic_order.size() - 1) break;
347
348 // Update the profile.
349 new_profile.clear();
350 new_profile.push_back(
351 {actual_start, profile[current].second - event_demand});
352 ++current;
353
354 while (profile[current].first < actual_end) {
355 new_profile.push_back(
356 {profile[current].first, profile[current].second - event_demand});
357 ++current;
358 }
359
360 if (profile[current].first > actual_end) {
361 new_profile.push_back(
362 {actual_end, new_profile.back().second + event_demand});
363 }
364 while (current < profile.size()) {
365 new_profile.push_back(profile[current]);
366 ++current;
367 }
368 profile.swap(new_profile);
369 }
370 return true;
371}
372
373} // namespace
374
375// We want to find the minimum set of values of `k` that would always find a
376// conflict if there is a `k` for which it exists. In the literature it is
377// often implied (but not stated) that it is sufficient to test the values of
378// `k` that correspond to the size of an item. This is not true. To find the
379// minimum set of values of `k` we look for all values of `k` that are
380// "extreme": ie., the rounding on the division truncates the most (or the
381// least) amount, depending on the sign it appears in the formula.
382//
383// To find these extreme values, we look for all local minima of the energy
384// slack after applying the DFF (we multiply by `k` for convenience):
385// k * f_k(H) * W - sum_i k * f_k(h_i) * w_i
386// If this value ever becomes negative for a value of `k`, it must happen in a
387// local minimum. Then we use the fact that
388// k * floor(x / k) = x - x % k
389// and that x%k has a local minimum when k=x/i and a local maximum when k=1+x/i
390// for every integer i. The final finer point in the calculation is
391// realizing that if
392// sum_{i, h_i > H/2} w_i > W
393// then you have more "large" objects than it fits in the box, and you will
394// have a conflict using the DFF f_0 for l=H/2. So we can safely ignore this
395// case for the more expensive DFF f_2 calculation.
396void OrthogonalPackingInfeasibilityDetector::GetAllCandidatesForKForDff2(
397 absl::Span<const IntegerValue> sizes, IntegerValue bb_size,
398 IntegerValue sqrt_bb_size, Bitset64<IntegerValue>& candidates) {
399 // x_bb_size is less than 65536, so this fits in only 4kib.
400 candidates.ClearAndResize(bb_size / 2 + 2);
401
402 // `sqrt_bb_size` is lower than 256.
403 for (IntegerValue i = 2; i <= sqrt_bb_size; i++) {
404 candidates.Set(i);
405 }
406 for (int i = 1; i <= sqrt_bb_size; i++) {
407 const QuickSmallDivision div(i);
408 if (i > 1) {
409 candidates.Set(div.DivideByDivisor(bb_size.value()));
410 }
411 for (int k = 0; k < sizes.size(); k++) {
412 IntegerValue size = sizes[k];
413 if (2 * size > bb_size && size < bb_size) {
414 candidates.Set(div.DivideByDivisor(bb_size.value() - size.value() + 1));
415 } else if (2 * size < bb_size) {
416 candidates.Set(div.DivideByDivisor(size.value()));
417 }
418 }
419 }
420
421 // Remove some bogus candidates added by the logic above.
422 candidates.Clear(0);
423 candidates.Clear(1);
424
425 // Apply the nice result described on [1]: if we are testing the DFF
426 // f_2^k(f_0^l(x)) for all values of `l`, the only values of `k` greater than
427 // C/4 we need to test are {C/4+1, C/3+1}.
428 //
429 // In the same reference there is a proof that this way of composing f_0 and
430 // f_2 cover all possible ways of composing the two functions, including
431 // composing several times each.
432 //
433 // [1] F. Clautiaux, PhD thesis, hal/tel-00749411.
434 candidates.Resize(bb_size / 4 + 1); // Erase all >= C/4
435 candidates.Resize(bb_size / 3 + 2); // Make room for the two special values
436 candidates.Set(bb_size / 4 + 1);
437 if (bb_size > 3) {
438 candidates.Set(bb_size / 3 + 1);
439 }
440}
441
442// Check for conflict all combinations of the two Dual Feasible Functions f_0
443// (see documentation for GetDffConflict()) and f_2 (see documentation for
444// RoundingDualFeasibleFunction). More precisely, check whether there exist `l`
445// and `k` so that
446//
447// sum_i f_2^k(f_0^l(sizes_x[i])) * sizes_y[i] > f_2^k(f_0^l(x_bb_size)) *
448// y_bb_size
449//
450// The function returns the smallest subset of items enough to make the
451// inequality above true or an empty vector if impossible.
452OrthogonalPackingResult
453OrthogonalPackingInfeasibilityDetector::CheckFeasibilityWithDualFunction2(
454 absl::Span<const IntegerValue> sizes_x,
455 absl::Span<const IntegerValue> sizes_y,
456 absl::Span<const int> index_by_decreasing_x_size, IntegerValue x_bb_size,
457 IntegerValue y_bb_size, int max_number_of_parameters_to_check) {
458 if (x_bb_size == 1) {
459 return OrthogonalPackingResult();
460 }
461 std::vector<IntegerValue> sizes_x_rescaled;
462 if (x_bb_size >= std::numeric_limits<uint16_t>::max()) {
463 // To do fast division we want our sizes to fit in a uint16_t. The simplest
464 // way of doing that is to just first apply this DFF with the right
465 // power-of-two value of the parameter.
466 const int log2_k =
467 absl::bit_width(static_cast<uint64_t>(x_bb_size.value() + 1)) - 16 + 1;
468 const RoundingDualFeasibleFunctionPowerOfTwo dff(x_bb_size, log2_k);
469 sizes_x_rescaled.resize(sizes_x.size());
470 for (int i = 0; i < sizes_x.size(); i++) {
471 sizes_x_rescaled[i] = dff(sizes_x[i]);
472 }
473 x_bb_size = dff(x_bb_size);
474 CHECK_LT(x_bb_size, std::numeric_limits<uint16_t>::max());
475 sizes_x = sizes_x_rescaled;
476 }
477
478 Bitset64<IntegerValue> candidates;
479 const IntegerValue sqrt_bb_size = FloorSquareRoot(x_bb_size.value());
480 int num_items = sizes_x.size();
481 const IntegerValue max_possible_number_of_parameters =
482 std::min(x_bb_size / 4 + 1, sqrt_bb_size * num_items);
483 if (5ull * max_number_of_parameters_to_check <
484 max_possible_number_of_parameters) {
485 // There are many more possible values than what we want to sample. It is
486 // not worth to pay the price of computing all optimal values to drop most
487 // of them, so let's just pick it randomly.
488 candidates.Resize(x_bb_size / 4 + 1);
489 int num_candidates = 0;
490 while (num_candidates < max_number_of_parameters_to_check) {
491 const IntegerValue pick =
492 absl::Uniform(random_, 1, x_bb_size.value() / 4);
493 if (!candidates.IsSet(pick)) {
494 candidates.Set(pick);
495 num_candidates++;
496 }
497 }
498 } else {
499 GetAllCandidatesForKForDff2(sizes_x, x_bb_size, sqrt_bb_size, candidates);
500
501 if (max_number_of_parameters_to_check < max_possible_number_of_parameters) {
502 // We might have produced too many candidates. Let's count them and if it
503 // is the case, sample them.
504 int count = 0;
505 for (auto it = candidates.begin(); it != candidates.end(); ++it) {
506 count++;
507 }
508 if (count > max_number_of_parameters_to_check) {
509 std::vector<IntegerValue> sampled_candidates(
510 max_number_of_parameters_to_check);
511 std::sample(candidates.begin(), candidates.end(),
512 sampled_candidates.begin(),
513 max_number_of_parameters_to_check, random_);
514 candidates.ClearAll();
515 for (const IntegerValue k : sampled_candidates) {
516 candidates.Set(k);
517 }
518 }
519 }
520 }
521 OrthogonalPackingResult best_result;
522
523 // Finally run our small loop to look for the conflict!
524 std::vector<IntegerValue> modified_sizes(num_items);
525 for (const IntegerValue k : candidates) {
526 const RoundingDualFeasibleFunction dff(x_bb_size, k);
527 IntegerValue energy = 0;
528 for (int i = 0; i < num_items; i++) {
529 modified_sizes[i] = dff(sizes_x[i]);
530 energy += modified_sizes[i] * sizes_y[i];
531 }
532 const IntegerValue modified_x_bb_size = dff(x_bb_size);
533 IntegerValue dff0_k;
534 auto dff0_res =
535 GetDffConflict(sizes_x, sizes_y, index_by_decreasing_x_size,
536 modified_sizes, modified_x_bb_size, x_bb_size, energy,
537 modified_x_bb_size * y_bb_size, &dff0_k);
538 if (dff0_res.result_ != OrthogonalPackingResult::Status::INFEASIBLE) {
539 continue;
540 }
541 DFFComposedF2F0 composed_dff(x_bb_size, dff0_k, k);
542 dff0_res.conflict_type_ = OrthogonalPackingResult::ConflictType::DFF_F2;
543 for (auto& item : dff0_res.items_participating_on_conflict_) {
544 item.size_x =
545 composed_dff.LowestInverse(composed_dff(sizes_x[item.index]));
546
547 // The new size should contribute by the same amount to the energy and
548 // correspond to smaller items.
549 DCHECK_EQ(composed_dff(item.size_x), composed_dff(sizes_x[item.index]));
550 DCHECK_LE(item.size_x, sizes_x[item.index]);
551
552 item.size_y = sizes_y[item.index];
553 }
554 if (dff0_res.IsBetterThan(best_result)) {
555 best_result = dff0_res;
556 }
557 }
558
559 return best_result;
560}
561
562bool OrthogonalPackingInfeasibilityDetector::RelaxConflictWithBruteForce(
563 OrthogonalPackingResult& result,
564 std::pair<IntegerValue, IntegerValue> bounding_box_size,
565 int brute_force_threshold) {
566 const int num_items_originally =
567 result.items_participating_on_conflict_.size();
568 if (num_items_originally > 2 * brute_force_threshold) {
569 // Don't even try on problems too big.
570 return false;
571 }
572 std::vector<IntegerValue> sizes_x;
573 std::vector<IntegerValue> sizes_y;
574 std::vector<int> indexes;
575 std::vector<bool> to_be_removed(num_items_originally, false);
576
577 sizes_x.reserve(num_items_originally - 1);
578 sizes_y.reserve(num_items_originally - 1);
579 for (int i = 0; i < num_items_originally; i++) {
580 sizes_x.clear();
581 sizes_y.clear();
582 // Look for a conflict using all non-removed items but the i-th one.
583 for (int j = 0; j < num_items_originally; j++) {
584 if (i == j || to_be_removed[j]) {
585 continue;
586 }
587 sizes_x.push_back(result.items_participating_on_conflict_[j].size_x);
588 sizes_y.push_back(result.items_participating_on_conflict_[j].size_y);
589 }
591 sizes_x, sizes_y, bounding_box_size, brute_force_threshold);
593 // We still have a conflict if we remove the i-th item!
594 to_be_removed[i] = true;
595 }
596 }
597 if (!std::any_of(to_be_removed.begin(), to_be_removed.end(),
598 [](bool b) { return b; })) {
599 return false;
600 }
601 OrthogonalPackingResult original = result;
602 result.slack_ = 0;
603 result.conflict_type_ = OrthogonalPackingResult::ConflictType::BRUTE_FORCE;
605 result.items_participating_on_conflict_.clear();
606 for (int i = 0; i < num_items_originally; i++) {
607 if (to_be_removed[i]) {
608 continue;
609 }
610 result.items_participating_on_conflict_.push_back(
611 original.items_participating_on_conflict_[i]);
612 }
613 return true;
614}
615
616OrthogonalPackingResult
617OrthogonalPackingInfeasibilityDetector::TestFeasibilityImpl(
618 absl::Span<const IntegerValue> sizes_x,
619 absl::Span<const IntegerValue> sizes_y,
620 std::pair<IntegerValue, IntegerValue> bounding_box_size,
621 const OrthogonalPackingOptions& options) {
622 using ConflictType = OrthogonalPackingResult::ConflictType;
623
624 const int num_items = sizes_x.size();
625 DCHECK_EQ(num_items, sizes_y.size());
626 const IntegerValue bb_area =
627 bounding_box_size.first * bounding_box_size.second;
628 IntegerValue total_energy = 0;
629
630 auto make_item = [sizes_x, sizes_y](int i) {
631 return OrthogonalPackingResult::Item{
632 .index = i, .size_x = sizes_x[i], .size_y = sizes_y[i]};
633 };
634
635 index_by_decreasing_x_size_.resize(num_items);
636 index_by_decreasing_y_size_.resize(num_items);
637 for (int i = 0; i < num_items; i++) {
638 total_energy += sizes_x[i] * sizes_y[i];
639 index_by_decreasing_x_size_[i] = i;
640 index_by_decreasing_y_size_[i] = i;
641 if (sizes_x[i] > bounding_box_size.first ||
642 sizes_y[i] > bounding_box_size.second) {
643 OrthogonalPackingResult result(
645 result.conflict_type_ = ConflictType::TRIVIAL;
646 result.items_participating_on_conflict_ = {make_item(i)};
647 return result;
648 }
649 }
650
651 if (num_items <= 1) {
652 return OrthogonalPackingResult(OrthogonalPackingResult::Status::FEASIBLE);
653 }
654
655 std::sort(index_by_decreasing_x_size_.begin(),
656 index_by_decreasing_x_size_.end(),
657 [&sizes_x, &sizes_y](int a, int b) {
658 // Break ties with y-size
659 return std::tie(sizes_x[a], sizes_y[a]) >
660 std::tie(sizes_x[b], sizes_y[b]);
661 });
662 std::sort(index_by_decreasing_y_size_.begin(),
663 index_by_decreasing_y_size_.end(),
664 [&sizes_y, &sizes_x](int a, int b) {
665 return std::tie(sizes_y[a], sizes_x[a]) >
666 std::tie(sizes_y[b], sizes_x[b]);
667 });
668
669 // First look for pairwise incompatible pairs.
670 if (options.use_pairwise) {
671 if (auto pair = FindPairwiseConflict(sizes_x, sizes_y, bounding_box_size,
672 index_by_decreasing_x_size_,
673 index_by_decreasing_y_size_);
674 pair.has_value()) {
675 OrthogonalPackingResult result(
677 result.conflict_type_ = ConflictType::PAIRWISE;
678 result.items_participating_on_conflict_ = {
679 make_item(pair.value().first), make_item(pair.value().second)};
680 return result;
681 }
682 if (num_items == 2) {
683 return OrthogonalPackingResult(OrthogonalPackingResult::Status::FEASIBLE);
684 }
685 }
686
687 OrthogonalPackingResult result(OrthogonalPackingResult::Status::UNKNOWN);
688 if (total_energy > bb_area) {
689 result.conflict_type_ = ConflictType::TRIVIAL;
691 std::vector<std::pair<int, IntegerValue>> index_to_energy;
692 index_to_energy.reserve(num_items);
693 for (int i = 0; i < num_items; i++) {
694 index_to_energy.push_back({i, sizes_x[i] * sizes_y[i]});
695 }
696 std::sort(index_to_energy.begin(), index_to_energy.end(),
697 [](const std::pair<int, IntegerValue>& a,
698 const std::pair<int, IntegerValue>& b) {
699 return a.second > b.second;
700 });
701 IntegerValue recomputed_energy = 0;
702 for (int i = 0; i < index_to_energy.size(); i++) {
703 recomputed_energy += index_to_energy[i].second;
704 if (recomputed_energy > bb_area) {
705 result.items_participating_on_conflict_.resize(i + 1);
706 for (int j = 0; j <= i; j++) {
707 result.items_participating_on_conflict_[j] =
708 make_item(index_to_energy[j].first);
709 }
710 result.slack_ = recomputed_energy - bb_area - 1;
711 break;
712 }
713 }
714 }
715
716 const int minimum_conflict_size = options.use_pairwise ? 3 : 2;
717 if (result.items_participating_on_conflict_.size() == minimum_conflict_size) {
718 return result;
719 }
720
721 if (options.use_dff_f0) {
722 // If there is no pairwise incompatible pairs, this DFF cannot find a
723 // conflict by enlarging a item on both x and y directions: this would
724 // create an item as long as the whole box and another item as high as the
725 // whole box, which is obviously incompatible, and this incompatibility
726 // would be present already before enlarging the items since it is a DFF. So
727 // it is enough to test making items wide or high, but no need to try both.
728 IntegerValue best_k;
729 auto conflict =
730 GetDffConflict(sizes_x, sizes_y, index_by_decreasing_x_size_, sizes_x,
731 bounding_box_size.first, bounding_box_size.first,
732 total_energy, bb_area, &best_k);
733 if (conflict.IsBetterThan(result)) {
734 result = conflict;
735 }
736
737 conflict =
738 GetDffConflict(sizes_y, sizes_x, index_by_decreasing_y_size_, sizes_y,
739 bounding_box_size.second, bounding_box_size.second,
740 total_energy, bb_area, &best_k);
741 for (auto& item : conflict.items_participating_on_conflict_) {
742 std::swap(item.size_x, item.size_y);
743 }
744 if (conflict.IsBetterThan(result)) {
745 result = conflict;
746 }
747 }
748
749 if (result.items_participating_on_conflict_.size() == minimum_conflict_size) {
750 return result;
751 }
752
753 bool found_scheduling_solution = false;
754 if (options.use_dff_f2) {
755 // Checking for conflicts using f_2 is expensive, so first try a quick
756 // algorithm to check if there is no conflict to be found. See the comments
757 // on top of FindHeuristicSchedulingSolution().
758 if (FindHeuristicSchedulingSolution(
759 sizes_x, sizes_y, index_by_decreasing_x_size_,
760 bounding_box_size.first, bounding_box_size.second,
761 scheduling_profile_, new_scheduling_profile_) ||
762 FindHeuristicSchedulingSolution(
763 sizes_y, sizes_x, index_by_decreasing_y_size_,
764 bounding_box_size.second, bounding_box_size.first,
765 scheduling_profile_, new_scheduling_profile_)) {
766 num_scheduling_possible_++;
767 CHECK(result.result_ != OrthogonalPackingResult::Status::INFEASIBLE);
768 found_scheduling_solution = true;
769 }
770 }
771
772 if (!found_scheduling_solution && options.use_dff_f2) {
773 // We only check for conflicts applying this DFF on heights and widths, but
774 // not on both, which would be too expensive if done naively.
775 auto conflict = CheckFeasibilityWithDualFunction2(
776 sizes_x, sizes_y, index_by_decreasing_x_size_, bounding_box_size.first,
777 bounding_box_size.second,
778 options.dff2_max_number_of_parameters_to_check);
779 if (conflict.IsBetterThan(result)) {
780 result = conflict;
781 }
782
783 if (result.items_participating_on_conflict_.size() ==
784 minimum_conflict_size) {
785 return result;
786 }
787 conflict = CheckFeasibilityWithDualFunction2(
788 sizes_y, sizes_x, index_by_decreasing_y_size_, bounding_box_size.second,
789 bounding_box_size.first,
790 options.dff2_max_number_of_parameters_to_check);
791 for (auto& item : conflict.items_participating_on_conflict_) {
792 std::swap(item.size_x, item.size_y);
793 }
794 if (conflict.IsBetterThan(result)) {
795 result = conflict;
796 }
797 }
798
799 if (result.result_ == OrthogonalPackingResult::Status::UNKNOWN) {
801 sizes_x, sizes_y, bounding_box_size, options.brute_force_threshold);
802 num_brute_force_calls_ +=
805 result.conflict_type_ = ConflictType::BRUTE_FORCE;
807 result.items_participating_on_conflict_.resize(num_items);
808 for (int i = 0; i < num_items; i++) {
809 result.items_participating_on_conflict_[i] = make_item(i);
810 }
813 }
814 }
815
816 if (result.result_ == OrthogonalPackingResult::Status::INFEASIBLE) {
817 num_brute_force_relaxation_ += RelaxConflictWithBruteForce(
818 result, bounding_box_size, options.brute_force_threshold);
819 }
820
821 return result;
822}
823
825 absl::Span<const IntegerValue> sizes_x,
826 absl::Span<const IntegerValue> sizes_y,
827 std::pair<IntegerValue, IntegerValue> bounding_box_size,
828 const OrthogonalPackingOptions& options) {
829 using ConflictType = OrthogonalPackingResult::ConflictType;
830
831 num_calls_++;
833 TestFeasibilityImpl(sizes_x, sizes_y, bounding_box_size, options);
834
835 if (result.result_ == OrthogonalPackingResult::Status::INFEASIBLE) {
836 num_conflicts_++;
837 switch (result.conflict_type_) {
838 case ConflictType::DFF_F0:
839 num_conflicts_dff0_++;
840 break;
841 case ConflictType::DFF_F2:
842 num_conflicts_dff2_++;
843 break;
844 case ConflictType::PAIRWISE:
845 num_conflicts_two_items_++;
846 break;
847 case ConflictType::TRIVIAL:
848 // The total area of the items was larger than the area of the box.
849 num_trivial_conflicts_++;
850 break;
851 case ConflictType::BRUTE_FORCE:
852 num_brute_force_conflicts_++;
853 break;
854 case ConflictType::NO_CONFLICT:
855 LOG(FATAL) << "Should never happen";
856 break;
857 }
858 }
859 return result;
860}
861
863 int i, Coord coord, IntegerValue lower_bound) {
864 Item& item = items_participating_on_conflict_[i];
865 IntegerValue& size = coord == Coord::kCoordX ? item.size_x : item.size_y;
866 const IntegerValue orthogonal_size =
867 coord == Coord::kCoordX ? item.size_y : item.size_x;
868
870 return false;
871 }
872 const IntegerValue new_size =
873 std::max(lower_bound, size - slack_ / orthogonal_size);
874 slack_ -= (size - new_size) * orthogonal_size;
875 DCHECK_NE(size, new_size);
876 DCHECK_GE(slack_, 0);
877 size = new_size;
878 return true;
879}
880
881} // namespace sat
882} // namespace operations_research
IntegerValue y
IntegerValue size
OrthogonalPackingResult TestFeasibility(absl::Span< const IntegerValue > sizes_x, absl::Span< const IntegerValue > sizes_y, std::pair< IntegerValue, IntegerValue > bounding_box_size, const OrthogonalPackingOptions &options=OrthogonalPackingOptions())
bool TryUseSlackToReduceItemSize(int i, Coord coord, IntegerValue lower_bound=0)
void AddStats(absl::Span< const std::pair< std::string, int64_t > > stats)
Adds a bunch of stats, adding count for the same key together.
int64_t a
Definition table.cc:44
double lower_bound
int index
double solution
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
int64_t FloorSquareRoot(int64_t a)
The argument must be non-negative.
Definition util.cc:256
BruteForceResult BruteForceOrthogonalPacking(absl::Span< const IntegerValue > sizes_x, absl::Span< const IntegerValue > sizes_y, std::pair< IntegerValue, IntegerValue > bounding_box_size, int max_complexity)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
In SWIG mode, we don't want anything besides these top-level includes.
int64_t energy
Definition resource.cc:355
Rev< int64_t > start_min