Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
2d_rectangle_presolve.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_RECTANGLE_PRESOLVE_H_
15#define OR_TOOLS_SAT_2D_RECTANGLE_PRESOLVE_H_
16
17#include <tuple>
18#include <utility>
19#include <vector>
20
21#include "absl/algorithm/container.h"
22#include "absl/container/flat_hash_map.h"
23#include "absl/container/inlined_vector.h"
24#include "absl/types/span.h"
27
28namespace operations_research {
29namespace sat {
30
31// Given a set of fixed boxes and a set of boxes that are not yet
32// fixed (but attributed a range), look for a more optimal set of fixed
33// boxes that are equivalent to the initial set of fixed boxes. This
34// uses "equivalent" in the sense that a placement of the non-fixed boxes will
35// be non-overlapping with all other boxes if and only if it was with the
36// original set of fixed boxes too.
38 absl::Span<const RectangleInRange> non_fixed_boxes,
39 std::vector<Rectangle>* fixed_boxes);
40
41// Detect whether the fixed boxes of a no_overlap_2d constraint are splitting
42// the space into separate components and thus can be replaced by one
43// no_overlap_2d constraint per component. If this is not possible, return an
44// empty result. Otherwise, return a struct containing what boxes (fixed and
45// non-fixed) are needed in each new constraint.
46//
47// Note that for this to be correct, we need to introduce new boxes to "fill"
48// the space occupied by the other components. For example, if we have a
49// no_overlap_2d constraint with the fixed boxes and the bounding box of the
50// non-fixed boxes as follows:
51// +---------------------+
52// | |
53// | |
54// | |
55// | |
56// | |
57// | ++++++++++++++++++|
58// | ++++++++++++++++++|
59// | ++++++++++++++++++|
60// |***** |
61// |***** |
62// |***** |
63// | |
64// | |
65// | |
66// +---------------------+
67// and we are building the new no_overlap_2d constraint for the space below, we
68// would need to add two new fixed boxes (the '.' and the 'o' one):
69// +---------------------+
70// |ooooooooooooooooooooo|
71// |ooooooooooooooooooooo|
72// |ooooooooooooooooooooo|
73// |ooooooooooooooooooooo|
74// |ooooooooooooooooooooo|
75// |...++++++++++++++++++|
76// |...++++++++++++++++++|
77// |...++++++++++++++++++|
78// |***** |
79// |***** |
80// |***** |
81// | |
82// | |
83// | |
84// +---------------------+
85// This ensures that the new no_overlap_2d constraint will impose the box to be
86// in that component as it should. Note that in the example above, the number of
87// boxes won't really increase after a presolve pass, since the presolve for
88// fixed boxes will simplify it to two fixed boxes again.
90 struct Bin {
91 // Fixed boxes that the non-fixed boxes in this bin cannot overlap with.
92 std::vector<Rectangle> fixed_boxes;
93 // Non-fixed boxes on the original problem to copy to this new constraint.
94 std::vector<int> non_fixed_box_indexes;
95 // Area that is covered by the connected component bin represents encoded as
96 // a non-overlapping set of rectangles.
97 std::vector<Rectangle> bin_area;
98 };
99 std::vector<Bin> bins;
100};
102 absl::Span<const RectangleInRange> non_fixed_boxes,
103 absl::Span<const Rectangle> fixed_boxes, int max_num_components);
104
105// Given two vectors of non-overlapping rectangles defining two regions of the
106// space: one mandatory region that must be occupied and one optional region
107// that can be occupied, try to build a vector of as few non-overlapping
108// rectangles as possible defining a region R that satisfy:
109// - R \subset (mandatory \union optional);
110// - mandatory \subset R.
111//
112// The function updates the vector of `mandatory_rectangles` with `R` and
113// `optional_rectangles` with `optional_rectangles \setdiff R`. It returns
114// true if the `mandatory_rectangles` was updated.
115//
116// This function uses a greedy algorithm that merge rectangles that share an
117// edge.
118bool ReduceNumberofBoxesGreedy(std::vector<Rectangle>* mandatory_rectangles,
119 std::vector<Rectangle>* optional_rectangles);
120
121// Same as above, but this implementation returns the optimal solution in
122// minimizing the number of boxes if `optional_rectangles` is empty. On the
123// other hand, its handling of optional boxes is rather limited. It simply fills
124// the holes in the mandatory boxes with optional boxes, if possible.
126 std::vector<Rectangle>* mandatory_rectangles,
127 std::vector<Rectangle>* optional_rectangles);
128
129enum EdgePosition { TOP = 0, RIGHT = 1, BOTTOM = 2, LEFT = 3 };
130
131template <typename Sink>
132void AbslStringify(Sink& sink, EdgePosition e) {
133 switch (e) {
135 sink.Append("TOP");
136 break;
138 sink.Append("RIGHT");
139 break;
141 sink.Append("BOTTOM");
142 break;
144 sink.Append("LEFT");
145 break;
146 }
147}
148
149// Given a set of non-overlapping rectangles, precompute a data-structure that
150// allow for each rectangle to find the adjacent rectangle along an edge.
151//
152// Note that it only consider adjacent rectangles whose segments has a
153// intersection of non-zero size. In particular, rectangles as following are not
154// considered adjacent:
155//
156// ********
157// ********
158// ********
159// ********
160// +++++++++
161// +++++++++
162// +++++++++
163// +++++++++
164//
165// Precondition: All rectangles must be disjoint.
167 public:
169 public:
170 explicit CompareClockwise(EdgePosition edge) : edge_(edge) {}
171
172 bool operator()(const Rectangle& a, const Rectangle& b) const {
173 switch (edge_) {
175 return std::tie(a.x_min, a.x_max) > std::tie(b.x_min, b.x_max);
177 return std::tie(a.x_min, a.x_max) < std::tie(b.x_min, b.x_max);
179 return std::tie(a.y_min, a.y_max) < std::tie(b.y_min, b.y_max);
181 return std::tie(a.y_min, a.y_max) > std::tie(b.y_min, b.y_max);
182 }
183 }
185 };
186
187 explicit Neighbours(
188 absl::Span<const Rectangle> rectangles,
189 absl::Span<const std::tuple<int, EdgePosition, int>> neighbors)
190 : size_(rectangles.size()) {
191 for (const auto& [box_index, edge, neighbor] : neighbors) {
192 neighbors_[edge][box_index].push_back(neighbor);
193 }
194 for (int edge = 0; edge < 4; ++edge) {
195 for (auto& [box_index, neighbors] : neighbors_[edge]) {
196 absl::c_sort(neighbors, [&rectangles, edge](int a, int b) {
197 return CompareClockwise(static_cast<EdgePosition>(edge))(
198 rectangles[a], rectangles[b]);
199 });
200 }
201 }
202 }
203
204 int NumRectangles() const { return size_; }
205
206 // Neighbors are sorted in the clockwise order.
207 absl::Span<const int> GetSortedNeighbors(int rectangle_index,
208 EdgePosition edge) const {
209 if (auto it = neighbors_[edge].find(rectangle_index);
210 it != neighbors_[edge].end()) {
211 return it->second;
212 } else {
213 return {};
214 }
215 }
216
217 private:
218 absl::flat_hash_map<int, absl::InlinedVector<int, 3>> neighbors_[4];
219 int size_;
220};
221
222Neighbours BuildNeighboursGraph(absl::Span<const Rectangle> rectangles);
223
224std::vector<std::vector<int>> SplitInConnectedComponents(
225 const Neighbours& neighbours);
226
227// Generally, given a set of non-overlapping rectangles and a path that doesn't
228// cross itself, the path can be cut into segments that touch only one single
229// rectangle in the interior of the region delimited by the path. This struct
230// holds a path cut into such segments. In particular, for the contour of an
231// union of rectangles, the path is a subset of the union of all the rectangle's
232// edges.
233struct ShapePath {
234 // The two vectors should have exactly the same size.
235 std::vector<std::pair<IntegerValue, IntegerValue>> step_points;
236 // touching_box_index[i] contains the index of the unique interior rectangle
237 // touching the segment step_points[i]->step_points[(i+1)%size].
238 std::vector<int> touching_box_index;
239};
240
243 std::vector<ShapePath> holes;
244};
245
246// Given a set of rectangles, split it into connected components and transform
247// each individual set into a shape described by its boundary and holes paths.
248std::vector<SingleShape> BoxesToShapes(absl::Span<const Rectangle> rectangles,
249 const Neighbours& neighbours);
250
251std::vector<Rectangle> CutShapeIntoRectangles(SingleShape shapes);
252
253} // namespace sat
254} // namespace operations_research
255
256#endif // OR_TOOLS_SAT_2D_RECTANGLE_PRESOLVE_H_
bool operator()(const Rectangle &a, const Rectangle &b) const
absl::Span< const int > GetSortedNeighbors(int rectangle_index, EdgePosition edge) const
Neighbors are sorted in the clockwise order.
Neighbours(absl::Span< const Rectangle > rectangles, absl::Span< const std::tuple< int, EdgePosition, int > > neighbors)
bool ReduceNumberOfBoxesExactMandatory(std::vector< Rectangle > *mandatory_rectangles, std::vector< Rectangle > *optional_rectangles)
std::vector< std::vector< int > > SplitInConnectedComponents(const Neighbours &neighbours)
std::vector< SingleShape > BoxesToShapes(absl::Span< const Rectangle > rectangles, const Neighbours &neighbours)
Disjoint2dPackingResult DetectDisjointRegionIn2dPacking(absl::Span< const RectangleInRange > non_fixed_boxes, absl::Span< const Rectangle > fixed_boxes, int max_num_components)
bool ReduceNumberofBoxesGreedy(std::vector< Rectangle > *mandatory_rectangles, std::vector< Rectangle > *optional_rectangles)
std::vector< Rectangle > CutShapeIntoRectangles(SingleShape shape)
void AbslStringify(Sink &sink, EdgePosition e)
Neighbours BuildNeighboursGraph(absl::Span< const Rectangle > rectangles)
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.
std::vector< Rectangle > fixed_boxes
Fixed boxes that the non-fixed boxes in this bin cannot overlap with.
std::vector< int > non_fixed_box_indexes
Non-fixed boxes on the original problem to copy to this new constraint.
std::vector< std::pair< IntegerValue, IntegerValue > > step_points
The two vectors should have exactly the same size.