Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
2d_orthogonal_packing.h
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
14#ifndef OR_TOOLS_SAT_2D_ORTHOGONAL_PACKING_H_
15#define OR_TOOLS_SAT_2D_ORTHOGONAL_PACKING_H_
16
17#include <algorithm>
18#include <cstdint>
19#include <limits>
20#include <utility>
21#include <vector>
22
23#include "absl/log/check.h"
24#include "absl/random/bit_gen_ref.h"
25#include "absl/types/span.h"
26#include "ortools/sat/integer.h"
28
29namespace operations_research {
30namespace sat {
31
33 bool use_pairwise = true;
34 bool use_dff_f0 = true;
35 bool use_dff_f2 = true;
37 int dff2_max_number_of_parameters_to_check = std::numeric_limits<int>::max();
38};
39
41 public:
42 explicit OrthogonalPackingResult() : result_(Status::UNKNOWN) {}
43 enum class Status {
46 UNKNOWN,
47 };
48
49 Status GetResult() const { return result_; }
50
51 struct Item {
52 // Index of the item on the original sizes_x/sizes_y input.
53 int index;
54 // New size for item of index `i` which is smaller or equal to the initial
55 // size. The subproblem remain infeasible if every item is shrinked to its
56 // new size.
57 IntegerValue size_x;
58 IntegerValue size_y;
59 };
60 const std::vector<Item>& GetItemsParticipatingOnConflict() const {
61 return items_participating_on_conflict_;
62 }
63
64 bool HasSlack() const { return slack_ > IntegerValue(0); }
65
66 enum class Coord { kCoordX, kCoordY };
67 // Use an eventual slack to reduce the size of item corresponding to the
68 // `i`-th element on GetItemsParticipatingOnConflict(). It will not use any
69 // slack to reduce it beyond lower_bound. This is a no-op if HasSlack() is
70 // false.
71 bool TryUseSlackToReduceItemSize(int i, Coord coord,
72 IntegerValue lower_bound = 0);
73
74 // If *this is identical or not easily comparable to other, returns false.
75 bool IsBetterThan(const OrthogonalPackingResult& other) const {
76 if (result_ == Status::UNKNOWN && other.result_ == Status::UNKNOWN) {
77 return false;
78 }
79 if (other.result_ == Status::UNKNOWN) {
80 return true;
81 }
82 if (result_ == Status::UNKNOWN) {
83 return false;
84 }
85 if (other.result_ == Status::FEASIBLE) {
86 CHECK(result_ != Status::INFEASIBLE);
87 return result_ == Status::FEASIBLE;
88 }
89
90 // other.result_ == Status::INFEASIBLE
91 CHECK(result_ == Status::INFEASIBLE);
92 if (other.items_participating_on_conflict_.size() <
93 items_participating_on_conflict_.size()) {
94 return false;
95 }
96 if (other.items_participating_on_conflict_.size() >
97 items_participating_on_conflict_.size()) {
98 return true;
99 }
100
101 IntegerValue total_area_this = 0;
102 IntegerValue total_area_other = 0;
103 for (int i = 0; i < items_participating_on_conflict_.size(); ++i) {
104 total_area_this += items_participating_on_conflict_[i].size_x *
105 items_participating_on_conflict_[i].size_y;
106 total_area_other += other.items_participating_on_conflict_[i].size_x *
107 other.items_participating_on_conflict_[i].size_y;
108 }
109 return total_area_this - slack_ < total_area_other - other.slack_;
110 }
111
112 private:
114
115 explicit OrthogonalPackingResult(Status result) : result_(result) {}
116
117 enum class ConflictType {
118 NO_CONFLICT,
119 TRIVIAL,
120 PAIRWISE,
121 DFF_F0,
122 DFF_F2,
123 BRUTE_FORCE,
124 };
125
126 Status result_;
127 ConflictType conflict_type_ = ConflictType::NO_CONFLICT;
128 IntegerValue slack_ = 0;
129 std::vector<Item> items_participating_on_conflict_;
130};
131
132// Class for solving the orthogonal packing problem when it can be done
133// efficiently (ie., not applying any heuristic slower than O(N^2)).
135 public:
137 absl::BitGenRef random, SharedStatistics* shared_stats)
138 : random_(random), shared_stats_(shared_stats) {}
139
141
143 absl::Span<const IntegerValue> sizes_x,
144 absl::Span<const IntegerValue> sizes_y,
145 std::pair<IntegerValue, IntegerValue> bounding_box_size,
147
148 private:
149 bool RelaxConflictWithBruteForce(
151 std::pair<IntegerValue, IntegerValue> bounding_box_size,
152 int brute_force_threshold);
153
154 OrthogonalPackingResult TestFeasibilityImpl(
155 absl::Span<const IntegerValue> sizes_x,
156 absl::Span<const IntegerValue> sizes_y,
157 std::pair<IntegerValue, IntegerValue> bounding_box_size,
158 const OrthogonalPackingOptions& options);
159
160 OrthogonalPackingResult GetDffConflict(
161 absl::Span<const IntegerValue> sizes_x,
162 absl::Span<const IntegerValue> sizes_y,
163 absl::Span<const int> index_by_decreasing_x_size,
164 absl::Span<const IntegerValue> g_x, IntegerValue g_max,
165 IntegerValue x_bb_size, IntegerValue total_energy, IntegerValue bb_area,
166 IntegerValue* best_k);
167
168 // Returns a minimum set of values of the parameter `k` of f_2^k that is
169 // sufficient to find a conflict. This function runs in
170 // O(num_items * sqrt(bb_size)) operations.
171 // All sizes must be positive values less than UINT16_MAX.
172 // The returned bitset will contain less elements than
173 // min(sqrt_bb_size * num_items, x_bb_size/4+1).
174 void GetAllCandidatesForKForDff2(absl::Span<const IntegerValue> sizes,
175 IntegerValue bb_size,
176 IntegerValue sqrt_bb_size,
177 Bitset64<IntegerValue>& candidates);
178
179 OrthogonalPackingResult CheckFeasibilityWithDualFunction2(
180 absl::Span<const IntegerValue> sizes_x,
181 absl::Span<const IntegerValue> sizes_y,
182 absl::Span<const int> index_by_decreasing_x_size, IntegerValue x_bb_size,
183 IntegerValue y_bb_size, int max_number_of_parameters_to_check);
184
185 // Buffers cleared and reused at each call of TestFeasibility()
186 std::vector<int> index_by_decreasing_x_size_;
187 std::vector<int> index_by_decreasing_y_size_;
188 std::vector<std::pair<IntegerValue, IntegerValue>> scheduling_profile_;
189 std::vector<std::pair<IntegerValue, IntegerValue>> new_scheduling_profile_;
190
191 int64_t num_calls_ = 0;
192 int64_t num_conflicts_ = 0;
193 int64_t num_conflicts_two_items_ = 0;
194 int64_t num_trivial_conflicts_ = 0;
195 int64_t num_conflicts_dff2_ = 0;
196 int64_t num_conflicts_dff0_ = 0;
197 int64_t num_scheduling_possible_ = 0;
198 int64_t num_brute_force_calls_ = 0;
199 int64_t num_brute_force_conflicts_ = 0;
200 int64_t num_brute_force_relaxation_ = 0;
201
202 absl::BitGenRef random_;
203 SharedStatistics* shared_stats_;
204};
205
206// If we have a container of size `C` and a parameter `k` taking values in
207// [0, C/2], the Dual Feasible Function often named `f_0^k(x)` is equivalent to
208// the operation of removing all values of size less than `k`, and symmetrically
209// increasing to `C` the size of the large values. It is defined as:
210//
211// / C, if x > C - k,
212// f_0^k(x) = | x, if k <= x <= C - k,
213// \ 0, if x < k.
214//
215// This is a Maximal DFF. See for example [1] for some discussion about it.
216//
217// [1] Clautiaux, François, Cláudio Alves, and José Valério de Carvalho. "A
218// survey of dual-feasible and superadditive functions." Annals of Operations
219// Research 179 (2010): 317-342.
220class DualFeasibleFunctionF0 {
221 public:
222 DualFeasibleFunctionF0(IntegerValue max_x, IntegerValue k)
223 : k_(k), max_x_(max_x) {
224 DCHECK_GE(k_, 0);
225 DCHECK_LE(2 * k_, max_x_);
226 }
227
228 // `x` must be in [0, max_x].
229 IntegerValue operator()(IntegerValue x) const {
230 DCHECK_GE(x, 0);
231 DCHECK_LE(x, max_x_);
232 if (x > max_x_ - k_) {
233 return max_x_;
234 } else if (x < k_) {
235 return 0;
236 } else {
237 return x;
238 }
239 }
240
241 // Return the lowest integer y so that Dff(x) >= y.
242 // y must be in [0, Dff(max_x)].
243 IntegerValue LowestInverse(IntegerValue x) const {
244 DCHECK_GE(x, 0);
245 DCHECK_LE(x, max_x_);
246 if (x > max_x_ - k_) {
247 return max_x_ - k_ + 1;
248 } else if (x == 0) {
249 return 0;
250 } else if (x < k_) {
251 return k_;
252 } else {
253 return x;
254 }
255 }
256
257 private:
258 const IntegerValue k_;
259 const IntegerValue max_x_;
260};
261
262// Dual Feasible Function based on rounding. Called f_2 on [1].
263//
264// The `f_2^k(x)` function for an integer x in [0, C] and a parameter `k` taking
265// values in [0, C/2] is defined as:
266//
267// / 2 * [ floor(C / k) - floor( (C - x) / k) ], if x > C / 2,
268// f_2^k(x) = | floor(C / k), if k = C / 2,
269// \ floor(x / k), if x < C / 2.
270//
271// This function is a Maximal Dual Feasible Function. Ie., it satisfies:
272// - f_2 is nondecreasing,
273// - f_2 is superadditive, i.e., f_2(x) + f_2(y) <= f_2(x + y),
274// - f_2 is symmetric, i.e., f_2(x) + f_2(C - x) = f_2(C),
275// - f_2(0) = 0.
276//
277// [1] Carlier, Jacques, François Clautiaux, and Aziz Moukrim. "New reduction
278// procedures and lower bounds for the two-dimensional bin packing problem with
279// fixed orientation." Computers & Operations Research 34.8 (2007): 2223-2250.
280class RoundingDualFeasibleFunction {
281 public:
282 // `max_x` must fit in a uint16_t and `k` in [0, max_x/2].
283 RoundingDualFeasibleFunction(IntegerValue max_x, IntegerValue k)
284 : div_(k.value()),
285 max_x_(max_x),
286 c_k_(div_.DivideByDivisor(max_x_.value())),
287 k_(k) {
288 DCHECK_GT(k, 0);
289 DCHECK_LE(2 * k, max_x_);
290 DCHECK_LE(max_x_, std::numeric_limits<uint16_t>::max());
291 }
292
293 // `x` must be in [0, max_x].
294 IntegerValue operator()(IntegerValue x) const {
295 DCHECK_GE(x, 0);
296 DCHECK_LE(x, max_x_);
297
298 if (2 * x > max_x_) {
299 return 2 * (c_k_ - div_.DivideByDivisor(max_x_.value() - x.value()));
300 } else if (2 * x == max_x_) {
301 return c_k_;
302 } else {
303 return 2 * div_.DivideByDivisor(x.value());
304 }
305 }
306
307 // Return the lowest integer y so that Dff(x) >= y.
308 // y must be in [0, Dff(max_x)].
309 IntegerValue LowestInverse(IntegerValue y) const;
310
311 private:
312 const QuickSmallDivision div_;
313 const IntegerValue max_x_;
314 const IntegerValue c_k_;
315 const IntegerValue k_;
316};
317
318// Same as above for k = 2^log2_k.
319class RoundingDualFeasibleFunctionPowerOfTwo {
320 public:
322 IntegerValue log2_k)
323 : log2_k_(log2_k), max_x_(max_x), c_k_(max_x_ >> log2_k_) {
324 DCHECK_GE(log2_k_, 0);
325 DCHECK_LT(log2_k_, 63);
326 DCHECK_LE(2 * (1 << log2_k), max_x_);
327 DCHECK_LE(max_x_, std::numeric_limits<int64_t>::max() / 2);
328 }
329
330 IntegerValue operator()(IntegerValue x) const {
331 DCHECK_GE(x, 0);
332 DCHECK_LE(x, max_x_);
333
334 if (2 * x > max_x_) {
335 return 2 * (c_k_ - ((max_x_ - x) >> log2_k_));
336 } else if (2 * x == max_x_) {
337 return c_k_;
338 } else {
339 return 2 * (x >> log2_k_);
340 }
341 }
342
343 // Return the lowest integer y so that Dff(x) >= y.
344 // y must be in [0, Dff(max_x)].
345 IntegerValue LowestInverse(IntegerValue y) const;
346
347 private:
348 const IntegerValue log2_k_;
349 const IntegerValue max_x_;
350 const IntegerValue c_k_;
351};
352
353// Using our definition for the inverse, composition produces a valid
354// DFF with a valid inverse. This class defines f2(f0(x)).
355class DFFComposedF2F0 {
356 public:
357 DFFComposedF2F0(IntegerValue max_x, IntegerValue k_f0, IntegerValue k_f2)
358 : f0_(max_x, k_f0), f2_(max_x, k_f2) {}
360 IntegerValue operator()(IntegerValue x) const { return f2_(f0_(x)); }
361
362 IntegerValue LowestInverse(IntegerValue x) const {
363 return f0_.LowestInverse(f2_.LowestInverse(x));
365
366 private:
367 const DualFeasibleFunctionF0 f0_;
369};
370
371} // namespace sat
372} // namespace operations_research
373
374#endif // OR_TOOLS_SAT_2D_ORTHOGONAL_PACKING_H_
IntegerValue y
IntegerValue LowestInverse(IntegerValue x) const
DFFComposedF2F0(IntegerValue max_x, IntegerValue k_f0, IntegerValue k_f2)
IntegerValue operator()(IntegerValue x) const
DualFeasibleFunctionF0(IntegerValue max_x, IntegerValue k)
IntegerValue operator()(IntegerValue x) const
x must be in [0, max_x].
OrthogonalPackingInfeasibilityDetector(absl::BitGenRef random, SharedStatistics *shared_stats)
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)
const std::vector< Item > & GetItemsParticipatingOnConflict() const
bool IsBetterThan(const OrthogonalPackingResult &other) const
If *this is identical or not easily comparable to other, returns false.
uint16_t DivideByDivisor(uint16_t dividend) const
Definition integer.h:138
RoundingDualFeasibleFunctionPowerOfTwo(IntegerValue max_x, IntegerValue log2_k)
RoundingDualFeasibleFunction(IntegerValue max_x, IntegerValue k)
max_x must fit in a uint16_t and k in [0, max_x/2].
IntegerValue operator()(IntegerValue x) const
x must be in [0, max_x].
Simple class to add statistics by name and print them at the end.
int64_t value
double lower_bound
In SWIG mode, we don't want anything besides these top-level includes.
const Variable x
Definition qp_tests.cc:127
int index
Index of the item on the original sizes_x/sizes_y input.