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