Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
2d_try_edge_propagator.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 <cstdint>
17#include <optional>
18#include <string>
19#include <utility>
20#include <vector>
21
22#include "absl/algorithm/container.h"
23#include "absl/log/check.h"
30#include "ortools/sat/integer.h"
32#include "ortools/sat/model.h"
35#include "ortools/sat/util.h"
36
37namespace operations_research {
38namespace sat {
39
41 const int id = watcher->Register(this);
42 helper_.WatchAllBoxes(id);
44 return id;
45}
46
48 if (!VLOG_IS_ON(1)) return;
49 std::vector<std::pair<std::string, int64_t>> stats;
50 stats.push_back({"TryEdgeRectanglePropagator/called", num_calls_});
51 stats.push_back({"TryEdgeRectanglePropagator/conflicts", num_conflicts_});
52 stats.push_back(
53 {"TryEdgeRectanglePropagator/propagations", num_propagations_});
54
55 shared_stats_->AddStats(stats);
56}
57
58void TryEdgeRectanglePropagator::PopulateActiveBoxRanges() {
59 const int num_boxes = helper_.NumBoxes();
60 placed_boxes_.resize(num_boxes);
61 active_box_ranges_.resize(num_boxes);
62 is_active_.resize(num_boxes);
64 mandatory_regions_.resize(num_boxes);
65 is_in_cache_.resize(num_boxes);
66
67 changed_mandatory_.clear();
68 changed_item_.clear();
69 for (int box = 0; box < num_boxes; ++box) {
70 bool inactive = !helper_.IsPresent(box);
72 if (!inactive) {
73 rec = helper_.GetItemRangeForSizeMin(box);
74 if (rec.x_size == 0 || rec.y_size == 0) {
75 inactive = true;
76 }
77 }
78 is_active_[box] = !inactive;
79 if (inactive) {
80 is_in_cache_[box] = false;
81 has_mandatory_region_.Set(box, false);
82 continue;
83 }
84 if (is_in_cache_[box] && rec == active_box_ranges_[box]) {
85 DCHECK(mandatory_regions_[box] == rec.GetMandatoryRegion());
86 DCHECK(has_mandatory_region_[box] ==
88 continue;
89 }
90 active_box_ranges_[box] = rec;
91 changed_item_.push_back(box);
92 const Rectangle mandatory_region = rec.GetMandatoryRegion();
93 const bool has_mandatory_region =
94 (mandatory_region != Rectangle::GetEmpty());
95 if (has_mandatory_region) {
96 if (!has_mandatory_region_[box] || !is_in_cache_[box] ||
97 mandatory_region != mandatory_regions_[box]) {
98 changed_mandatory_.push_back(box);
99 }
100 }
101 mandatory_regions_[box] = mandatory_region;
102 has_mandatory_region_.Set(box, has_mandatory_region);
103 is_in_cache_[box] = false;
104 }
105}
106
107bool TryEdgeRectanglePropagator::CanPlace(
108 int box_index, const std::pair<IntegerValue, IntegerValue>& position,
109 CompactVectorVector<int>* with_conflict) const {
110 bool can_place = true;
111 if (with_conflict != nullptr) {
112 with_conflict->Add({});
113 }
114 const Rectangle placed_box = {
115 .x_min = position.first,
116 .x_max = position.first + active_box_ranges_[box_index].x_size,
117 .y_min = position.second,
118 .y_max = position.second + active_box_ranges_[box_index].y_size};
119 for (const int i : has_mandatory_region_) {
120 if (i == box_index) continue;
121 const Rectangle& mandatory_region = mandatory_regions_[i];
122 if (!mandatory_region.IsDisjoint(placed_box)) {
123 if (with_conflict != nullptr) {
124 with_conflict->AppendToLastVector(i);
125 can_place = false;
126 } else {
127 return false;
128 }
129 }
130 }
131 return can_place;
132}
133
135 if (!helper_.SynchronizeAndSetDirection(
136 x_is_forward_after_swap_, y_is_forward_after_swap_, swap_x_and_y_)) {
137 return false;
138 }
139
140 num_calls_++;
141
142 PopulateActiveBoxRanges();
143
144 // Our algo is quadratic, so we don't want to run it on really large problems.
145 if (changed_item_.size() > 1000) {
146 return true;
147 }
148
149 // If a mandatory region is changed, we need to replace any cached box that
150 // now became overlapping with it.
151 for (const int mandatory_idx : changed_mandatory_) {
152 for (int i = 0; i < active_box_ranges_.size(); i++) {
153 if (i == mandatory_idx || !is_in_cache_[i]) continue;
154 if (!placed_boxes_[i].IsDisjoint(mandatory_regions_[mandatory_idx])) {
155 changed_item_.push_back(i);
156 is_in_cache_[i] = false;
157 }
158 }
159 }
160
161 if (changed_item_.empty()) {
162 return true;
163 }
165
166 potential_x_positions_.clear();
167 potential_y_positions_.clear();
168 for (const int i : has_mandatory_region_) {
169 const Rectangle& mandatory_region = mandatory_regions_[i];
170 potential_x_positions_.push_back(mandatory_region.x_max);
171 potential_y_positions_.push_back(mandatory_region.y_max);
172 }
173
174 gtl::STLSortAndRemoveDuplicates(&potential_x_positions_);
175 gtl::STLSortAndRemoveDuplicates(&potential_y_positions_);
176
177 std::vector<std::pair<int, std::optional<IntegerValue>>> found_propagations;
178 for (const int i : changed_item_) {
179 DCHECK(!is_in_cache_[i]);
180 DCHECK(is_active_[i]);
182
183 if (CanPlace(i, {box.bounding_area.x_min, box.bounding_area.y_min})) {
184 placed_boxes_[i] = {.x_min = box.bounding_area.x_min,
185 .x_max = box.bounding_area.x_min + box.x_size,
186 .y_min = box.bounding_area.y_min,
187 .y_max = box.bounding_area.y_min + box.y_size};
188 is_in_cache_[i] = true;
189 continue;
190 }
191
192 bool placed_at_x_min = false;
193 const int y_start =
194 absl::c_lower_bound(potential_y_positions_, box.bounding_area.y_min) -
195 potential_y_positions_.begin();
196 for (int j = y_start; j < potential_y_positions_.size(); ++j) {
197 if (potential_y_positions_[j] > box.bounding_area.y_max - box.y_size) {
198 // potential_y_positions is sorted, so we can stop here.
199 break;
200 }
201 if (CanPlace(i, {box.bounding_area.x_min, potential_y_positions_[j]})) {
202 placed_at_x_min = true;
203 placed_boxes_[i] = {.x_min = box.bounding_area.x_min,
204 .x_max = box.bounding_area.x_min + box.x_size,
205 .y_min = potential_y_positions_[j],
206 .y_max = potential_y_positions_[j] + box.y_size};
207 is_in_cache_[i] = true;
208 break;
209 }
210 }
211 if (placed_at_x_min) continue;
212
213 // We could not find any placement of the box at its current lower bound!
214 // Thus, we are sure we have something to propagate. Let's find the new
215 // lower bound (or a conflict). Note that the code below is much less
216 // performance critical than the code above, since it only triggers on
217 // propagations.
218 std::optional<IntegerValue> new_x_min = std::nullopt;
219 for (int j = 0; j < potential_x_positions_.size(); ++j) {
220 if (potential_x_positions_[j] < box.bounding_area.x_min) {
221 continue;
222 }
223 if (potential_x_positions_[j] > box.bounding_area.x_max - box.x_size) {
224 continue;
225 }
226 if (CanPlace(i, {potential_x_positions_[j], box.bounding_area.y_min})) {
227 new_x_min = potential_x_positions_[j];
228 break;
229 }
230 for (int k = y_start; k < potential_y_positions_.size(); ++k) {
231 const IntegerValue potential_y_position = potential_y_positions_[k];
232 if (potential_y_position > box.bounding_area.y_max - box.y_size) {
233 break;
234 }
235 if (CanPlace(i, {potential_x_positions_[j], potential_y_position})) {
236 // potential_x_positions is sorted, so the first we found is the
237 // lowest one.
238 new_x_min = potential_x_positions_[j];
239 break;
240 }
241 }
242 if (new_x_min.has_value()) {
243 break;
244 }
245 }
246 found_propagations.push_back({i, new_x_min});
247 }
248
249 return ExplainAndPropagate(found_propagations);
250}
251
253 int box_index, IntegerValue new_x_min) {
254 // We know that we can't place the box at x < new_x_min (which can be
255 // start_max for a conflict). The explanation for the propagation is complex:
256 // we tried a lot of positions, and each one overlaps with the mandatory part
257 // of at least one box. We want to find the smallest set of "conflicting
258 // boxes" that would still forbid every possible placement. To do that, we
259 // build a vector with, for each placement position, the list boxes that
260 // conflict when placing the box at that position. Then we solve
261 // (approximately) a set cover problem to find the smallest set of boxes that
262 // will still makes all positions conflicting.
263 const RectangleInRange& box = active_box_ranges_[box_index];
264
265 // We need to rerun the main propagator loop logic, but this time keeping
266 // track of which boxes conflicted for each position.
267 const int y_start =
268 absl::c_lower_bound(potential_y_positions_, box.bounding_area.y_min) -
269 potential_y_positions_.begin();
270 conflicts_per_x_and_y_.clear();
271 CHECK(!CanPlace(box_index, {box.bounding_area.x_min, box.bounding_area.y_min},
272 &conflicts_per_x_and_y_));
273 for (int j = y_start; j < potential_y_positions_.size(); ++j) {
274 if (potential_y_positions_[j] > box.bounding_area.y_max - box.y_size) {
275 // potential_y_positions is sorted, so we can stop here.
276 break;
277 }
278 CHECK(!CanPlace(box_index,
279 {box.bounding_area.x_min, potential_y_positions_[j]},
280 &conflicts_per_x_and_y_));
281 }
282 for (int j = 0; j < potential_x_positions_.size(); ++j) {
283 if (potential_x_positions_[j] < box.bounding_area.x_min) {
284 continue;
285 }
286 if (potential_x_positions_[j] >= new_x_min) {
287 continue;
288 }
289 CHECK(!CanPlace(box_index,
290 {potential_x_positions_[j], box.bounding_area.y_min},
291 &conflicts_per_x_and_y_));
292 for (int k = y_start; k < potential_y_positions_.size(); ++k) {
293 const IntegerValue potential_y_position = potential_y_positions_[k];
294 if (potential_y_position > box.bounding_area.y_max - box.y_size) {
295 break;
296 }
297 CHECK(!CanPlace(box_index,
298 {potential_x_positions_[j], potential_y_position},
299 &conflicts_per_x_and_y_));
300 }
301 }
302
303 // Now gather the data per box to make easier to use the set cover solver API.
304 // TODO(user): skip the boxes that are fixed at level zero. They do not
305 // contribute to the size of the explanation (so we shouldn't minimize their
306 // number) and make the SetCover problem harder to solve.
307 std::vector<std::vector<int>> conflicting_position_per_box(
308 active_box_ranges_.size(), std::vector<int>());
309 for (int i = 0; i < conflicts_per_x_and_y_.size(); ++i) {
310 DCHECK(!conflicts_per_x_and_y_[i].empty());
311 for (const int j : conflicts_per_x_and_y_[i]) {
312 conflicting_position_per_box[j].push_back(i);
313 }
314 }
315 SetCoverModel model;
316 for (const auto& conflicts : conflicting_position_per_box) {
317 if (conflicts.empty()) continue;
318 model.AddEmptySubset(/*cost=*/1);
319 for (const int i : conflicts) {
321 }
322 }
323 DCHECK(model.ComputeFeasibility());
324 SetCoverInvariant inv(&model);
325 GreedySolutionGenerator greedy_search(&inv);
326 CHECK(greedy_search.NextSolution());
327 GuidedLocalSearch search(&inv);
328 CHECK(search.NextSolution(100));
329 DCHECK(inv.CheckConsistency(
331
332 int count = 0;
333 const auto& solution = inv.is_selected();
334 std::vector<int> boxes_participating_in_propagation;
335 boxes_participating_in_propagation.reserve(model.num_subsets() + 1);
336 boxes_participating_in_propagation.push_back(box_index);
337 for (int i = 0; i < conflicting_position_per_box.size(); ++i) {
338 const auto& conflicts = conflicting_position_per_box[i];
339 if (conflicts.empty()) continue;
340 if (solution[SubsetIndex(count)]) {
341 boxes_participating_in_propagation.push_back(i);
342 }
343 count++;
344 }
345 VLOG_EVERY_N_SEC(3, 2) << "Found no_overlap_2d constraint propagation with "
346 << boxes_participating_in_propagation.size() << "/"
347 << (model.num_subsets() + 1) << " items";
348
349 // TODO(user): We now know for each box the list of placements that it
350 // contributes to the conflict. We could use this information to relax the
351 // bounds of this box on the explanation of the propagation. For example, for
352 // a box that always overlaps at least five units to the right when it does,
353 // we could call AddStartMinReason(x_min - 4) instead of
354 // AddStartMinReason(x_min).
355 return boxes_participating_in_propagation;
356}
357
359 const std::vector<std::pair<int, std::optional<IntegerValue>>>&
360 found_propagations) {
361 for (const auto& [box_index, new_x_min] : found_propagations) {
362 const RectangleInRange& box = active_box_ranges_[box_index];
363 helper_.ClearReason();
364
365 const std::vector<int> minimum_problem_with_propagator =
367 box_index, new_x_min.has_value()
368 ? *new_x_min
369 : box.bounding_area.x_max - box.x_size);
370 for (const int j : minimum_problem_with_propagator) {
371 DCHECK(is_active_[j]);
372 // Important: we also add to the reason the actual box we are changing the
373 // x_min. This is important, since we don't check if there are any
374 // feasible placement before its current x_min, so it needs to be part of
375 // the reason.
376 const RectangleInRange& box_reason = active_box_ranges_[j];
377 const int b = box_reason.box_index;
378
379 helper_.AddLeftMinReason(b, box_reason.bounding_area.x_min);
380 helper_.AddBottomMinReason(b, box_reason.bounding_area.y_min);
381
382 if (j != box_index || !new_x_min.has_value()) {
383 // We don't need to add to the reason the x_max for the box we are
384 // pushing the x_min, except if we found a conflict.
385 helper_.AddLeftMaxReason(
386 b, box_reason.bounding_area.x_max - box_reason.x_size);
387 }
388 helper_.AddBottomMaxReason(
389 b, box_reason.bounding_area.y_max - box_reason.y_size);
390
391 helper_.AddSizeMinReason(b);
392 helper_.AddPresenceReason(b);
393 }
394 if (new_x_min.has_value()) {
395 num_propagations_++;
396 if (!helper_.IncreaseLeftMin(box_index, *new_x_min)) {
397 return false;
398 }
399 } else {
400 num_conflicts_++;
401 return helper_.ReportConflict();
402 }
403 }
404 return true;
405}
406
408 Model* model,
409 GenericLiteralWatcher* watcher,
410 int priority) {
411 TryEdgeRectanglePropagator* try_edge_propagator =
412 new TryEdgeRectanglePropagator(true, true, false, helper, model);
413 watcher->SetPropagatorPriority(try_edge_propagator->RegisterWith(watcher),
414 priority);
415 model->TakeOwnership(try_edge_propagator);
416
417 TryEdgeRectanglePropagator* try_edge_propagator_mirrored =
418 new TryEdgeRectanglePropagator(false, true, false, helper, model);
419 watcher->SetPropagatorPriority(
420 try_edge_propagator_mirrored->RegisterWith(watcher), priority);
421 model->TakeOwnership(try_edge_propagator_mirrored);
422
423 TryEdgeRectanglePropagator* try_edge_propagator_swap =
424 new TryEdgeRectanglePropagator(true, true, true, helper, model);
425 watcher->SetPropagatorPriority(
426 try_edge_propagator_swap->RegisterWith(watcher), priority);
427 model->TakeOwnership(try_edge_propagator_swap);
428
429 TryEdgeRectanglePropagator* try_edge_propagator_swap_mirrored =
430 new TryEdgeRectanglePropagator(false, true, true, helper, model);
431 watcher->SetPropagatorPriority(
432 try_edge_propagator_swap_mirrored->RegisterWith(watcher), priority);
433 model->TakeOwnership(try_edge_propagator_swap_mirrored);
434}
435
436} // namespace sat
437} // namespace operations_research
void resize(int size)
Resizes the Bitset64 to the given number of bits. New bits are sets to 0.
Definition bitset.h:474
void Set(IndexType i)
Sets the bit at position i to 1.
Definition bitset.h:544
The consistency level is maintained up to kFreeAndUncovered.
The consistency level is maintained up to kRedundancy.
bool CheckConsistency(ConsistencyLevel consistency) const
Checks the consistency of the invariant at the given consistency level.
const SubsetBoolVector & is_selected() const
Returns the subset assignment vector.
Main class for describing a weighted set-covering problem.
void AddElementToLastSubset(BaseInt element)
void SetPropagatorPriority(int id, int priority)
Definition integer.cc:2353
int Register(PropagatorInterface *propagator)
Registers a propagator and returns its unique ids.
Definition integer.cc:2327
std::vector< int > GetMinimumProblemWithPropagation(int box_index, IntegerValue new_x_min)
virtual bool ExplainAndPropagate(const std::vector< std::pair< int, std::optional< IntegerValue > > > &found_propagations)
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:58
void CreateAndRegisterTryEdgePropagator(NoOverlap2DConstraintHelper *helper, Model *model, GenericLiteralWatcher *watcher, int priority)
In SWIG mode, we don't want anything besides these top-level includes.
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid