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