Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
2d_rectangle_presolve.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 <limits>
18#include <memory>
19#include <optional>
20#include <utility>
21#include <vector>
22
23#include "absl/container/flat_hash_map.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
31namespace operations_research {
32namespace sat {
33
35 absl::Span<const RectangleInRange> non_fixed_boxes,
36 std::vector<Rectangle>* fixed_boxes) {
37 // This implementation compiles a set of areas that cannot be occupied by any
38 // item, then calls ReduceNumberofBoxes() to use these areas to minimize
39 // `fixed_boxes`.
40 bool changed = false;
41
42 IntegerValue original_area = 0;
43 std::vector<Rectangle> fixed_boxes_copy;
44 if (VLOG_IS_ON(1)) {
45 for (const Rectangle& r : *fixed_boxes) {
46 original_area += r.Area();
47 }
48 }
49 if (VLOG_IS_ON(2)) {
50 fixed_boxes_copy = *fixed_boxes;
51 }
52
53 const int original_num_boxes = fixed_boxes->size();
54 IntegerValue min_x_size = std::numeric_limits<IntegerValue>::max();
55 IntegerValue min_y_size = std::numeric_limits<IntegerValue>::max();
56
57 CHECK(!non_fixed_boxes.empty());
58 Rectangle bounding_box = non_fixed_boxes[0].bounding_area;
59
60 for (const RectangleInRange& box : non_fixed_boxes) {
61 bounding_box.GrowToInclude(box.bounding_area);
62 min_x_size = std::min(min_x_size, box.x_size);
63 min_y_size = std::min(min_y_size, box.y_size);
64 }
65
66 // Fixed items are only useful to constraint where the non-fixed items can be
67 // placed. This means in particular that any part of a fixed item outside the
68 // bounding box of the non-fixed items is useless. Clip them.
69 int new_size = 0;
70 while (new_size < fixed_boxes->size()) {
71 Rectangle& rectangle = (*fixed_boxes)[new_size];
72 if (rectangle.x_min < bounding_box.x_min) {
73 rectangle.x_min = bounding_box.x_min;
74 changed = true;
75 }
76 if (rectangle.x_max > bounding_box.x_max) {
77 rectangle.x_max = bounding_box.x_max;
78 changed = true;
79 }
80 if (rectangle.y_min < bounding_box.y_min) {
81 rectangle.y_min = bounding_box.y_min;
82 changed = true;
83 }
84 if (rectangle.y_max > bounding_box.y_max) {
85 rectangle.y_max = bounding_box.y_max;
86 changed = true;
87 }
88 if (rectangle.SizeX() <= 0 || rectangle.SizeY() <= 0) {
89 // The whole rectangle was outside of the domain, remove it.
90 std::swap(rectangle, (*fixed_boxes)[fixed_boxes->size() - 1]);
91 fixed_boxes->resize(fixed_boxes->size() - 1);
92 continue;
93 } else {
94 new_size++;
95 }
96 }
97
98 std::vector<Rectangle> optional_boxes = *fixed_boxes;
99
100 if (bounding_box.x_min > std::numeric_limits<IntegerValue>::min() &&
101 bounding_box.y_min > std::numeric_limits<IntegerValue>::min() &&
102 bounding_box.x_max < std::numeric_limits<IntegerValue>::max() &&
103 bounding_box.y_max < std::numeric_limits<IntegerValue>::max()) {
104 // Add fake rectangles to build a frame around the bounding box. This allows
105 // to find more areas that must be empty. The frame is as follows:
106 // +************
107 // +...........+
108 // +...........+
109 // +...........+
110 // ************+
111 optional_boxes.push_back({.x_min = bounding_box.x_min - 1,
112 .x_max = bounding_box.x_max,
113 .y_min = bounding_box.y_min - 1,
114 .y_max = bounding_box.y_min});
115 optional_boxes.push_back({.x_min = bounding_box.x_max,
116 .x_max = bounding_box.x_max + 1,
117 .y_min = bounding_box.y_min - 1,
118 .y_max = bounding_box.y_max});
119 optional_boxes.push_back({.x_min = bounding_box.x_min,
120 .x_max = bounding_box.x_max + 1,
121 .y_min = bounding_box.y_max,
122 .y_max = bounding_box.y_max + 1});
123 optional_boxes.push_back({.x_min = bounding_box.x_min - 1,
124 .x_max = bounding_box.x_min,
125 .y_min = bounding_box.y_min,
126 .y_max = bounding_box.y_max + 1});
127 }
128
129 // All items we added to `optional_boxes` at this point are only to be used by
130 // the "gap between items" logic below. They are not actual optional boxes and
131 // should be removed right after the logic is applied.
132 const int num_optional_boxes_to_remove = optional_boxes.size();
133
134 // Add a rectangle to `optional_boxes` but respecting that rectangles must
135 // remain disjoint.
136 const auto add_box = [&optional_boxes](Rectangle new_box) {
137 std::vector<Rectangle> to_add = {std::move(new_box)};
138 for (int i = 0; i < to_add.size(); ++i) {
139 Rectangle new_box = to_add[i];
140 bool is_disjoint = true;
141 for (const Rectangle& existing_box : optional_boxes) {
142 if (!new_box.IsDisjoint(existing_box)) {
143 is_disjoint = false;
144 for (const Rectangle& disjoint_box :
145 new_box.SetDifference(existing_box)) {
146 to_add.push_back(disjoint_box);
147 }
148 break;
149 }
150 }
151 if (is_disjoint) {
152 optional_boxes.push_back(std::move(new_box));
153 }
154 }
155 };
156
157 // Now check if there is any space that cannot be occupied by any non-fixed
158 // item.
159 std::vector<Rectangle> bounding_boxes;
160 bounding_boxes.reserve(non_fixed_boxes.size());
161 for (const RectangleInRange& box : non_fixed_boxes) {
162 bounding_boxes.push_back(box.bounding_area);
163 }
164 std::vector<Rectangle> empty_spaces =
165 FindEmptySpaces(bounding_box, std::move(bounding_boxes));
166 for (const Rectangle& r : empty_spaces) {
167 add_box(r);
168 }
169
170 // Now look for gaps between objects that are too small to place anything.
171 for (int i = 1; i < optional_boxes.size(); ++i) {
172 const Rectangle cur_box = optional_boxes[i];
173 for (int j = 0; j < i; ++j) {
174 const Rectangle& other_box = optional_boxes[j];
175 const IntegerValue lower_top = std::min(cur_box.y_max, other_box.y_max);
176 const IntegerValue higher_bottom =
177 std::max(other_box.y_min, cur_box.y_min);
178 const IntegerValue rightmost_left_edge =
179 std::max(other_box.x_min, cur_box.x_min);
180 const IntegerValue leftmost_right_edge =
181 std::min(other_box.x_max, cur_box.x_max);
182 if (rightmost_left_edge < leftmost_right_edge) {
183 if (lower_top < higher_bottom &&
184 higher_bottom - lower_top < min_y_size) {
185 add_box({.x_min = rightmost_left_edge,
186 .x_max = leftmost_right_edge,
187 .y_min = lower_top,
188 .y_max = higher_bottom});
189 }
190 }
191 if (higher_bottom < lower_top) {
192 if (leftmost_right_edge < rightmost_left_edge &&
193 rightmost_left_edge - leftmost_right_edge < min_x_size) {
194 add_box({.x_min = leftmost_right_edge,
195 .x_max = rightmost_left_edge,
196 .y_min = higher_bottom,
197 .y_max = lower_top});
198 }
199 }
200 }
201 }
202 optional_boxes.erase(optional_boxes.begin(),
203 optional_boxes.begin() + num_optional_boxes_to_remove);
204
205 if (ReduceNumberofBoxes(fixed_boxes, &optional_boxes)) {
206 changed = true;
207 }
208 if (changed && VLOG_IS_ON(1)) {
209 IntegerValue area = 0;
210 for (const Rectangle& r : *fixed_boxes) {
211 area += r.Area();
212 }
213 VLOG_EVERY_N_SEC(1, 1) << "Presolved " << original_num_boxes
214 << " fixed rectangles (area=" << original_area
215 << ") into " << fixed_boxes->size()
216 << " (area=" << area << ")";
217
218 VLOG_EVERY_N_SEC(2, 2) << "Presolved rectangles:\n"
219 << RenderDot(bounding_box, fixed_boxes_copy)
220 << "Into:\n"
221 << RenderDot(bounding_box, *fixed_boxes)
222 << (optional_boxes.empty()
223 ? ""
224 : absl::StrCat("Unused optional rects:\n",
225 RenderDot(bounding_box,
226 optional_boxes)));
227 }
228 return changed;
229}
230
231namespace {
232struct Edge {
233 IntegerValue x_start;
234 IntegerValue y_start;
235 IntegerValue size;
236
237 enum class EdgePosition { TOP, BOTTOM, LEFT, RIGHT };
238
239 static Edge GetEdge(const Rectangle& rectangle, EdgePosition pos) {
240 switch (pos) {
241 case EdgePosition::TOP:
242 return {.x_start = rectangle.x_min,
243 .y_start = rectangle.y_max,
244 .size = rectangle.SizeX()};
245 case EdgePosition::BOTTOM:
246 return {.x_start = rectangle.x_min,
247 .y_start = rectangle.y_min,
248 .size = rectangle.SizeX()};
249 case EdgePosition::LEFT:
250 return {.x_start = rectangle.x_min,
251 .y_start = rectangle.y_min,
252 .size = rectangle.SizeY()};
253 case EdgePosition::RIGHT:
254 return {.x_start = rectangle.x_max,
255 .y_start = rectangle.y_min,
256 .size = rectangle.SizeY()};
257 }
258 }
259
260 template <typename H>
261 friend H AbslHashValue(H h, const Edge& e) {
262 return H::combine(std::move(h), e.x_start, e.y_start, e.size);
263 }
264
265 bool operator==(const Edge& other) const {
266 return x_start == other.x_start && y_start == other.y_start &&
267 size == other.size;
268 }
269};
270} // namespace
271
272bool ReduceNumberofBoxes(std::vector<Rectangle>* mandatory_rectangles,
273 std::vector<Rectangle>* optional_rectangles) {
274 // The current implementation just greedly merge rectangles that shares an
275 // edge. This is far from optimal, and it exists a polynomial optimal
276 // algorithm (see page 3 of [1]) for this problem at least for the case where
277 // optional_rectangles is empty.
278 //
279 // TODO(user): improve
280 //
281 // [1] Eppstein, David. "Graph-theoretic solutions to computational geometry
282 // problems." International Workshop on Graph-Theoretic Concepts in Computer
283 // Science. Berlin, Heidelberg: Springer Berlin Heidelberg, 2009.
284 std::vector<std::unique_ptr<Rectangle>> rectangle_storage;
285 enum class OptionalEnum { OPTIONAL, MANDATORY };
286 // bool for is_optional
287 std::vector<std::pair<const Rectangle*, OptionalEnum>> rectangles_ptr;
288 absl::flat_hash_map<Edge, int> top_edges_to_rectangle;
289 absl::flat_hash_map<Edge, int> bottom_edges_to_rectangle;
290 absl::flat_hash_map<Edge, int> left_edges_to_rectangle;
291 absl::flat_hash_map<Edge, int> right_edges_to_rectangle;
292
293 using EdgePosition = Edge::EdgePosition;
294
295 bool changed_optional = false;
296 bool changed_mandatory = false;
297
298 auto add_rectangle = [&](const Rectangle* rectangle_ptr,
299 OptionalEnum optional) {
300 const int index = rectangles_ptr.size();
301 rectangles_ptr.push_back({rectangle_ptr, optional});
302 const Rectangle& rectangle = *rectangles_ptr[index].first;
303 top_edges_to_rectangle[Edge::GetEdge(rectangle, EdgePosition::TOP)] = index;
304 bottom_edges_to_rectangle[Edge::GetEdge(rectangle, EdgePosition::BOTTOM)] =
305 index;
306 left_edges_to_rectangle[Edge::GetEdge(rectangle, EdgePosition::LEFT)] =
307 index;
308 right_edges_to_rectangle[Edge::GetEdge(rectangle, EdgePosition::RIGHT)] =
309 index;
310 };
311 for (const Rectangle& rectangle : *mandatory_rectangles) {
312 add_rectangle(&rectangle, OptionalEnum::MANDATORY);
313 }
314 for (const Rectangle& rectangle : *optional_rectangles) {
315 add_rectangle(&rectangle, OptionalEnum::OPTIONAL);
316 }
317
318 auto remove_rectangle = [&](const int index) {
319 const Rectangle& rectangle = *rectangles_ptr[index].first;
320 const Edge top_edge = Edge::GetEdge(rectangle, EdgePosition::TOP);
321 const Edge bottom_edge = Edge::GetEdge(rectangle, EdgePosition::BOTTOM);
322 const Edge left_edge = Edge::GetEdge(rectangle, EdgePosition::LEFT);
323 const Edge right_edge = Edge::GetEdge(rectangle, EdgePosition::RIGHT);
324 top_edges_to_rectangle.erase(top_edge);
325 bottom_edges_to_rectangle.erase(bottom_edge);
326 left_edges_to_rectangle.erase(left_edge);
327 right_edges_to_rectangle.erase(right_edge);
328 rectangles_ptr[index].first = nullptr;
329 };
330
331 bool iteration_did_merge = true;
332 while (iteration_did_merge) {
333 iteration_did_merge = false;
334 for (int i = 0; i < rectangles_ptr.size(); ++i) {
335 if (!rectangles_ptr[i].first) {
336 continue;
337 }
338 const Rectangle& rectangle = *rectangles_ptr[i].first;
339
340 const Edge top_edge = Edge::GetEdge(rectangle, EdgePosition::TOP);
341 const Edge bottom_edge = Edge::GetEdge(rectangle, EdgePosition::BOTTOM);
342 const Edge left_edge = Edge::GetEdge(rectangle, EdgePosition::LEFT);
343 const Edge right_edge = Edge::GetEdge(rectangle, EdgePosition::RIGHT);
344
345 int index = -1;
346 if (const auto it = right_edges_to_rectangle.find(left_edge);
347 it != right_edges_to_rectangle.end()) {
348 index = it->second;
349 } else if (const auto it = left_edges_to_rectangle.find(right_edge);
350 it != left_edges_to_rectangle.end()) {
351 index = it->second;
352 } else if (const auto it = bottom_edges_to_rectangle.find(top_edge);
353 it != bottom_edges_to_rectangle.end()) {
354 index = it->second;
355 } else if (const auto it = top_edges_to_rectangle.find(bottom_edge);
356 it != top_edges_to_rectangle.end()) {
357 index = it->second;
358 }
359 if (index == -1) {
360 continue;
361 }
362 iteration_did_merge = true;
363
364 // Merge two rectangles!
365 const OptionalEnum new_optional =
366 (rectangles_ptr[i].second == OptionalEnum::MANDATORY ||
367 rectangles_ptr[index].second == OptionalEnum::MANDATORY)
368 ? OptionalEnum::MANDATORY
369 : OptionalEnum::OPTIONAL;
370 changed_mandatory =
371 changed_mandatory || (new_optional == OptionalEnum::MANDATORY);
372 changed_optional =
373 changed_optional ||
374 (rectangles_ptr[i].second == OptionalEnum::OPTIONAL ||
375 rectangles_ptr[index].second == OptionalEnum::OPTIONAL);
376 rectangle_storage.push_back(std::make_unique<Rectangle>(rectangle));
377 Rectangle& new_rectangle = *rectangle_storage.back();
378 new_rectangle.GrowToInclude(*rectangles_ptr[index].first);
379 remove_rectangle(i);
380 remove_rectangle(index);
381 add_rectangle(&new_rectangle, new_optional);
382 }
383 }
384
385 if (changed_mandatory) {
386 std::vector<Rectangle> new_rectangles;
387 for (auto [rectangle, optional] : rectangles_ptr) {
388 if (rectangle && optional == OptionalEnum::MANDATORY) {
389 new_rectangles.push_back(*rectangle);
390 }
391 }
392 *mandatory_rectangles = std::move(new_rectangles);
393 }
394 if (changed_optional) {
395 std::vector<Rectangle> new_rectangles;
396 for (auto [rectangle, optional] : rectangles_ptr) {
397 if (rectangle && optional == OptionalEnum::OPTIONAL) {
398 new_rectangles.push_back(*rectangle);
399 }
400 }
401 *optional_rectangles = std::move(new_rectangles);
402 }
403 return changed_mandatory;
404}
405
406} // namespace sat
407} // namespace operations_research
IntegerValue x_start
IntegerValue y_start
IntegerValue size
int index
bool ReduceNumberofBoxes(std::vector< Rectangle > *mandatory_rectangles, std::vector< Rectangle > *optional_rectangles)
bool operator==(const BoolArgumentProto &lhs, const BoolArgumentProto &rhs)
std::vector< Rectangle > FindEmptySpaces(const Rectangle &bounding_box, std::vector< Rectangle > ocupied_rectangles)
std::string RenderDot(std::optional< Rectangle > bb, absl::Span< const Rectangle > solution)
H AbslHashValue(H h, const IntVar &i)
– ABSL HASHING SUPPORT --------------------------------------------------—
Definition cp_model.h:515
bool PresolveFixed2dRectangles(absl::Span< const RectangleInRange > non_fixed_boxes, std::vector< Rectangle > *fixed_boxes)
In SWIG mode, we don't want anything besides these top-level includes.