Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
no_overlap_2d_helper.cc
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
15
16#include <memory>
17#include <tuple>
18#include <utility>
19#include <vector>
20
21#include "absl/base/log_severity.h"
22#include "absl/log/check.h"
23#include "absl/types/span.h"
27#include "ortools/sat/integer.h"
31#include "ortools/sat/util.h"
32
33namespace operations_research {
34namespace sat {
35
37 bool x_is_forward_after_swap, bool y_is_forward_after_swap,
38 bool swap_x_and_y) {
39 if (axes_are_swapped_ != swap_x_and_y) {
40 std::swap(x_helper_, y_helper_);
41 std::swap(x_demands_helper_, y_demands_helper_);
42 axes_are_swapped_ = !axes_are_swapped_;
43 }
44 if (!x_helper_->SynchronizeAndSetTimeDirection(x_is_forward_after_swap))
45 return false;
46 if (!y_helper_->SynchronizeAndSetTimeDirection(y_is_forward_after_swap))
47 return false;
48 return true;
49}
50
52 if (x_demands_helper_ == nullptr) {
53 x_demands_helper_ = std::make_unique<SchedulingDemandHelper>(
54 x_helper_->Sizes(), y_helper_.get(), model_);
55 }
56 return *x_demands_helper_;
57}
58
60 if (y_demands_helper_ == nullptr) {
61 y_demands_helper_ = std::make_unique<SchedulingDemandHelper>(
62 y_helper_->Sizes(), x_helper_.get(), model_);
63 }
64 return *y_demands_helper_;
65}
66
68 int index) const {
69 return RectangleInRange{
70 .box_index = index,
71 .bounding_area = {.x_min = x_helper_->StartMin(index),
72 .x_max = x_helper_->StartMax(index) +
73 x_helper_->SizeMin(index),
74 .y_min = y_helper_->StartMin(index),
75 .y_max = y_helper_->StartMax(index) +
76 y_helper_->SizeMin(index)},
77 .x_size = x_helper_->SizeMin(index),
78 .y_size = y_helper_->SizeMin(index)};
79}
80
82 int index) const {
83 return ItemWithVariableSize{.index = index,
84 .x = {.start_min = x_helper_->StartMin(index),
85 .start_max = x_helper_->StartMax(index),
86 .end_min = x_helper_->EndMin(index),
87 .end_max = x_helper_->EndMax(index)},
88 .y = {.start_min = y_helper_->StartMin(index),
89 .start_max = y_helper_->StartMax(index),
90 .end_min = y_helper_->EndMin(index),
91 .end_max = y_helper_->EndMax(index)}};
92}
93
94namespace {
95void ClearAndAddMandatoryOverlapReason(int box1, int box2,
97 y->ClearReason();
98 y->AddPresenceReason(box1);
99 y->AddPresenceReason(box2);
100 y->AddReasonForBeingBefore(box1, box2);
101 y->AddReasonForBeingBefore(box2, box1);
102}
103} // namespace
104
106 int box2) {
107 DCHECK_NE(box1, box2);
108 if (DEBUG_MODE) {
109 std::vector<PairwiseRestriction> restrictions;
111 {GetItemWithVariableSize(box2)}, &restrictions);
112 DCHECK_EQ(restrictions.size(), 1);
113 DCHECK(restrictions[0].type ==
115 }
116 ClearAndAddMandatoryOverlapReason(box1, box2, x_helper_.get());
117 ClearAndAddMandatoryOverlapReason(box1, box2, y_helper_.get());
118 x_helper_->ImportOtherReasons(*y_helper_);
119 return x_helper_->ReportConflict();
120}
121
123 absl::Span<const RectangleInRange> ranges) {
124 if (ranges.size() == 2) {
125 return ReportConflictFromTwoBoxes(ranges[0].box_index, ranges[1].box_index);
126 }
127 x_helper_->ClearReason();
128 y_helper_->ClearReason();
129 for (const auto& range : ranges) {
130 const int b = range.box_index;
131
132 x_helper_->AddStartMinReason(b, range.bounding_area.x_min);
133 y_helper_->AddStartMinReason(b, range.bounding_area.y_min);
134
135 x_helper_->AddStartMaxReason(b, range.bounding_area.x_max - range.x_size);
136 y_helper_->AddStartMaxReason(b, range.bounding_area.y_max - range.y_size);
137
138 x_helper_->AddSizeMinReason(b);
139 y_helper_->AddSizeMinReason(b);
140
141 x_helper_->AddPresenceReason(b);
142 y_helper_->AddPresenceReason(b);
143 }
144 x_helper_->ImportOtherReasons(*y_helper_);
145 return x_helper_->ReportConflict();
146}
147
148namespace {
149// This function assumes that the left and right boxes overlap on the second
150// dimension, and that left cannot be after right.
151// It checks and pushes the lower bound of the right box and the upper bound
152// of the left box if need.
153//
154// If y is not null, it import the mandatory reason for the overlap on y in
155// the x helper.
156bool LeftBoxBeforeRightBoxOnFirstDimension(int left, int right,
159 // left box2 pushes right box2.
160 const IntegerValue left_end_min = x->EndMin(left);
161 if (left_end_min > x->StartMin(right)) {
162 x->ClearReason();
163 x->AddPresenceReason(left);
164 x->AddPresenceReason(right);
165 x->AddReasonForBeingBefore(left, right);
166 x->AddEndMinReason(left, left_end_min);
167 // left and right must overlap on y.
168 ClearAndAddMandatoryOverlapReason(left, right, y);
169 // Propagate with the complete reason.
170 x->ImportOtherReasons(*y);
171 if (!x->IncreaseStartMin(right, left_end_min)) return false;
172 }
173
174 // right box2 pushes left box2.
175 const IntegerValue right_start_max = x->StartMax(right);
176 if (right_start_max < x->EndMax(left)) {
177 x->ClearReason();
178 x->AddPresenceReason(left);
179 x->AddPresenceReason(right);
180 x->AddReasonForBeingBefore(left, right);
181 x->AddStartMaxReason(right, right_start_max);
182 // left and right must overlap on y.
183 ClearAndAddMandatoryOverlapReason(left, right, y);
184 // Propagate with the complete reason.
185 x->ImportOtherReasons(*y);
186 if (!x->DecreaseEndMax(left, right_start_max)) return false;
187 }
188
189 return true;
190}
191
192} // namespace
193
195 int first, int second, PairwiseRestriction::PairwiseRestrictionType type) {
196 switch (type) {
198 return ReportConflictFromTwoBoxes(first, second);
200 return LeftBoxBeforeRightBoxOnFirstDimension(
201 first, second, x_helper_.get(), y_helper_.get());
203 return LeftBoxBeforeRightBoxOnFirstDimension(
204 second, first, x_helper_.get(), y_helper_.get());
206 return LeftBoxBeforeRightBoxOnFirstDimension(
207 first, second, y_helper_.get(), x_helper_.get());
209 return LeftBoxBeforeRightBoxOnFirstDimension(
210 second, first, y_helper_.get(), x_helper_.get());
211 }
212}
213
214void NoOverlap2DConstraintHelper::Reset(
215 absl::Span<const Rectangle> fixed_boxes,
216 absl::Span<const int> non_fixed_box_indexes) {
217 inprocessing_count_++;
218 std::vector<AffineExpression> x_starts;
219 std::vector<AffineExpression> x_ends;
220 std::vector<AffineExpression> x_sizes;
221 std::vector<LiteralIndex> x_reason_for_presence;
222 std::vector<AffineExpression> y_starts;
223 std::vector<AffineExpression> y_ends;
224 std::vector<AffineExpression> y_sizes;
225 std::vector<LiteralIndex> y_reason_for_presence;
226
227 auto add_non_fixed_box = [&](int box_index) {
228 x_starts.push_back(x_helper_->Starts()[box_index]);
229 x_ends.push_back(x_helper_->Ends()[box_index]);
230 x_sizes.push_back(x_helper_->Sizes()[box_index]);
231 if (x_helper_->IsOptional(box_index)) {
232 x_reason_for_presence.push_back(x_helper_->PresenceLiteral(box_index));
233 } else {
234 x_reason_for_presence.push_back(kNoLiteralIndex);
235 }
236
237 y_starts.push_back(y_helper_->Starts()[box_index]);
238 y_ends.push_back(y_helper_->Ends()[box_index]);
239 y_sizes.push_back(y_helper_->Sizes()[box_index]);
240 if (y_helper_->IsOptional(box_index)) {
241 y_reason_for_presence.push_back(y_helper_->PresenceLiteral(box_index));
242 } else {
243 y_reason_for_presence.push_back(kNoLiteralIndex);
244 }
245
246 return x_starts.size() - 1;
247 };
248
249 auto add_fixed_box = [&](const Rectangle& box) {
250 x_starts.push_back(AffineExpression(box.x_min));
251 x_ends.push_back(AffineExpression(box.x_max));
252 x_sizes.push_back(box.SizeX());
253 x_reason_for_presence.push_back(kNoLiteralIndex);
254
255 y_starts.push_back(AffineExpression(box.y_min));
256 y_ends.push_back(AffineExpression(box.y_max));
257 y_sizes.push_back(box.SizeY());
258 y_reason_for_presence.push_back(kNoLiteralIndex);
259
260 return x_starts.size() - 1;
261 };
262
263 std::vector<Rectangle> active_bounding_boxes;
264 std::vector<std::tuple<int, bool>> active_box_indexes;
265 int new_num_boxes = fixed_boxes.size() + non_fixed_box_indexes.size();
266 active_bounding_boxes.reserve(new_num_boxes);
267 active_box_indexes.reserve(new_num_boxes);
268 for (int box : non_fixed_box_indexes) {
269 active_bounding_boxes.push_back(GetBoundingRectangle(box));
270 active_box_indexes.push_back({box, false});
271 }
272 for (int box = 0; box < fixed_boxes.size(); ++box) {
273 active_bounding_boxes.push_back(fixed_boxes[box]);
274 active_box_indexes.push_back({box, true});
275 }
276 CompactVectorVector<int> components =
277 GetOverlappingRectangleComponents(absl::MakeSpan(active_bounding_boxes));
278 connected_components_.clear();
279 for (absl::Span<const int> component : components.AsVectorOfSpan()) {
280 if (component.size() < 2) continue;
281 connected_components_.Add({});
282 for (int idx : component) {
283 const auto [box_idx, is_fixed] = active_box_indexes[idx];
284 int new_index;
285 if (is_fixed) {
286 new_index = add_fixed_box(fixed_boxes[box_idx]);
287 } else {
288 new_index = add_non_fixed_box(box_idx);
289 }
290 connected_components_.AppendToLastVector(new_index);
291 }
292 }
293 const int old_num_boxes = NumBoxes();
294
295 if (old_num_boxes != connected_components_.num_entries()) {
296 VLOG_EVERY_N_SEC(1, 1) << "Total boxes: "
297 << connected_components_.num_entries()
298 << " connected components: "
299 << connected_components_.size()
300 << " dropped single box components: "
301 << old_num_boxes -
302 connected_components_.num_entries();
303 }
304 VLOG_EVERY_N_SEC(1, 2) << "No_overlap_2d helper inprocessing: "
305 << connected_components_.size() << " components and "
306 << connected_components_.num_entries() << " boxes";
307
308 x_helper_ = std::make_unique<SchedulingConstraintHelper>(
309 std::move(x_starts), std::move(x_ends), std::move(x_sizes),
310 std::move(x_reason_for_presence), model_);
311 y_helper_ = std::make_unique<SchedulingConstraintHelper>(
312 std::move(y_starts), std::move(y_ends), std::move(y_sizes),
313 std::move(y_reason_for_presence), model_);
314 x_demands_helper_ = nullptr;
315 y_demands_helper_ = nullptr;
316}
317
319 for (const int id : propagators_watching_) {
320 watcher_->CallOnNextPropagate(id);
321 }
322 if (!x_helper_->Propagate() || !y_helper_->Propagate()) return false;
323
324 if (x_helper_->CurrentDecisionLevel() == 0) {
325 ++level_zero_bound_change_idx_;
327 int num_boxes = NumBoxes();
328
329 // Subtle: it is tempting to run this logic once for every connected
330 // component. However, this would be buggy, since the
331 // PresolveFixed2dRectangles() might grow a fixed box making it overlap with
332 // something on another component.
333 std::vector<Rectangle> fixed_boxes;
334 std::vector<int> non_fixed_box_indexes;
335 std::vector<RectangleInRange> non_fixed_boxes;
336 fixed_boxes.reserve(num_boxes);
337 non_fixed_box_indexes.reserve(num_boxes);
338 non_fixed_boxes.reserve(num_boxes);
339 bool has_zero_area_boxes = false;
340 for (int box_index = 0; box_index < num_boxes; ++box_index) {
341 if (x_helper_->IsAbsent(box_index) || y_helper_->IsAbsent(box_index)) {
342 continue;
343 }
344 if (x_helper_->SizeMin(box_index) == 0 ||
345 y_helper_->SizeMin(box_index) == 0) {
346 has_zero_area_boxes = true;
347 }
348 if (IsOptional(box_index) || !IsFixed(box_index)) {
349 non_fixed_boxes.push_back(
350 {.bounding_area = GetBoundingRectangle(box_index),
351 .x_size = x_helper_->SizeMin(box_index),
352 .y_size = y_helper_->SizeMin(box_index)});
353 non_fixed_box_indexes.push_back(box_index);
354 } else {
355 fixed_boxes.push_back(GetItemRangeForSizeMin(box_index).bounding_area);
356 }
357 }
358
359 const int original_num_fixed_boxes = fixed_boxes.size();
360 if (!non_fixed_boxes.empty() && !has_zero_area_boxes) {
361 if (FindPartialRectangleIntersections(fixed_boxes).empty()) {
362 PresolveFixed2dRectangles(non_fixed_boxes, &fixed_boxes);
363 }
364 }
365
366 if (fixed_boxes.size() != original_num_fixed_boxes) {
367 VLOG_EVERY_N_SEC(1, 1) << "Num_boxes: " << num_boxes
368 << " fixed_before: " << original_num_fixed_boxes
369 << " fixed_after: " << fixed_boxes.size();
370 }
371 Reset(fixed_boxes, non_fixed_box_indexes);
372 }
373 return true;
374}
375
377 const int id = watcher->Register(this);
378 const int num_boxes = NumBoxes();
379 for (int b = 0; b < num_boxes; ++b) {
380 if (x_helper_->IsOptional(b)) {
381 watcher->WatchLiteral(x_helper_->PresenceLiteral(b), id);
382 }
383 if (y_helper_->IsOptional(b)) {
384 watcher->WatchLiteral(y_helper_->PresenceLiteral(b), id);
385 }
386 watcher->WatchIntegerVariable(x_helper_->Sizes()[b].var, id);
387 watcher->WatchIntegerVariable(x_helper_->Starts()[b].var, id);
388 watcher->WatchIntegerVariable(x_helper_->Ends()[b].var, id);
389 watcher->WatchIntegerVariable(y_helper_->Sizes()[b].var, id);
390 watcher->WatchIntegerVariable(y_helper_->Starts()[b].var, id);
391 watcher->WatchIntegerVariable(y_helper_->Ends()[b].var, id);
392 }
393 watcher->SetPropagatorPriority(id, 0);
394}
395
396} // namespace sat
397} // namespace operations_research
void WatchLiteral(Literal l, int id, int watch_index=-1)
Definition integer.h:1428
void SetPropagatorPriority(int id, int priority)
Definition integer.cc:2353
void WatchIntegerVariable(IntegerVariable i, int id, int watch_index=-1)
Definition integer.h:1460
int Register(PropagatorInterface *propagator)
Registers a propagator and returns its unique ids.
Definition integer.cc:2327
bool SynchronizeAndSetDirection(bool x_is_forward_after_swap=true, bool y_is_forward_after_swap=true, bool swap_x_and_y=false)
bool ReportConflictFromInfeasibleBoxRanges(absl::Span< const RectangleInRange > ranges)
ItemWithVariableSize GetItemWithVariableSize(int index) const
bool PropagateRelativePosition(int first, int second, PairwiseRestriction::PairwiseRestrictionType type)
void AddReasonForBeingBefore(int before, int after)
Produces a relaxed reason for StartMax(before) < EndMin(after).
void ClearReason()
Functions to clear and then set the current reason.
const bool DEBUG_MODE
Definition macros.h:26
const LiteralIndex kNoLiteralIndex(-1)
CompactVectorVector< int > GetOverlappingRectangleComponents(absl::Span< const Rectangle > rectangles)
void AppendPairwiseRestrictions(absl::Span< const ItemWithVariableSize > items, std::vector< PairwiseRestriction > *result)
std::vector< std::pair< int, int > > FindPartialRectangleIntersections(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.