Skip to content

Commit 136d180

Browse files
committed
[CP-SAT] disable problematic presolve on no_overlap_2d with constant dimension; fix bug in diffn propagation, fix #4068
1 parent 4bec69d commit 136d180

File tree

3 files changed

+39
-30
lines changed

3 files changed

+39
-30
lines changed

ortools/sat/cp_model_presolve.cc

+8-3
Original file line numberDiff line numberDiff line change
@@ -5383,6 +5383,7 @@ bool CpModelPresolver::PresolveNoOverlap2D(int /*c*/, ConstraintProto* ct) {
53835383

53845384
bool x_constant = true;
53855385
bool y_constant = true;
5386+
bool has_zero_sized_interval = false;
53865387

53875388
// Filter absent boxes.
53885389
int new_size = 0;
@@ -5413,6 +5414,10 @@ bool CpModelPresolver::PresolveNoOverlap2D(int /*c*/, ConstraintProto* ct) {
54135414
if (y_constant && !context_->IntervalIsConstant(y_interval_index)) {
54145415
y_constant = false;
54155416
}
5417+
if (context_->SizeMax(x_interval_index) == 0 ||
5418+
context_->SizeMax(y_interval_index) == 0) {
5419+
has_zero_sized_interval = true;
5420+
}
54165421
}
54175422

54185423
std::vector<absl::Span<int>> components = GetOverlappingRectangleComponents(
@@ -5433,10 +5438,10 @@ bool CpModelPresolver::PresolveNoOverlap2D(int /*c*/, ConstraintProto* ct) {
54335438
return RemoveConstraint(ct);
54345439
}
54355440

5436-
if (x_constant || y_constant) {
5441+
if (!has_zero_sized_interval && (x_constant || y_constant)) {
54375442
context_->UpdateRuleStats(
5438-
"no_overlap_2d: a dimension is constant, splitting into many no "
5439-
"overlaps");
5443+
"no_overlap_2d: a dimension is constant, splitting into many "
5444+
"no_overlaps");
54405445
std::vector<IndexedInterval> indexed_intervals;
54415446
for (int i = 0; i < new_size; ++i) {
54425447
int x = proto.x_intervals(i);

ortools/sat/diffn.cc

+29-25
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,6 @@ bool NonOverlappingRectanglesEnergyPropagator::Propagate() {
272272
.x_max = std::numeric_limits<IntegerValue>::min(),
273273
.y_min = std::numeric_limits<IntegerValue>::max(),
274274
.y_max = std::numeric_limits<IntegerValue>::min()};
275-
IntegerValue max_x_item_size = IntegerValue(0);
276-
IntegerValue max_y_item_size = IntegerValue(0);
277275
std::vector<RectangleInRange> active_box_ranges;
278276
active_box_ranges.reserve(num_boxes);
279277
for (int box = 0; box < num_boxes; ++box) {
@@ -293,8 +291,6 @@ bool NonOverlappingRectanglesEnergyPropagator::Propagate() {
293291
.y_max = y_.StartMax(box) + y_.SizeMin(box)},
294292
.x_size = x_.SizeMin(box),
295293
.y_size = y_.SizeMin(box)});
296-
max_x_item_size = std::max(max_x_item_size, x_.SizeMin(box));
297-
max_y_item_size = std::max(max_y_item_size, y_.SizeMin(box));
298294
}
299295

300296
if (active_box_ranges.size() < 2) {
@@ -314,8 +310,8 @@ bool NonOverlappingRectanglesEnergyPropagator::Propagate() {
314310
return true;
315311
}
316312

317-
std::optional<Conflict> best_conflict = FindConflict(
318-
std::move(active_box_ranges), max_x_item_size, max_y_item_size);
313+
std::optional<Conflict> best_conflict =
314+
FindConflict(std::move(active_box_ranges));
319315
if (!best_conflict.has_value()) {
320316
return true;
321317
}
@@ -328,8 +324,7 @@ bool NonOverlappingRectanglesEnergyPropagator::Propagate() {
328324
IntegerValue best_explanation_size = best_conflict->items.size();
329325
bool refined = false;
330326
while (true) {
331-
const std::optional<Conflict> conflict =
332-
FindConflict(best_conflict->items, max_x_item_size, max_y_item_size);
327+
const std::optional<Conflict> conflict = FindConflict(best_conflict->items);
333328
if (!conflict.has_value()) break;
334329
// We prefer an explanation with the least number of boxes.
335330
if (conflict->items.size() >= best_explanation_size) {
@@ -356,8 +351,7 @@ bool NonOverlappingRectanglesEnergyPropagator::Propagate() {
356351

357352
std::optional<NonOverlappingRectanglesEnergyPropagator::Conflict>
358353
NonOverlappingRectanglesEnergyPropagator::FindConflict(
359-
std::vector<RectangleInRange> active_box_ranges,
360-
const IntegerValue& max_x_item_size, const IntegerValue& max_y_item_size) {
354+
std::vector<RectangleInRange> active_box_ranges) {
361355
const auto rectangles_with_too_much_energy =
362356
FindRectanglesWithEnergyConflictMC(active_box_ranges, *random_, 1.0, 0.8);
363357

@@ -385,25 +379,35 @@ NonOverlappingRectanglesEnergyPropagator::FindConflict(
385379
int opp_problem_size = 0;
386380
// Also try the DFF on known conflicts, hopefully we will get a smaller
387381
// explanation.
388-
std::vector<Rectangle> filtered_rectangles;
389-
filtered_rectangles.reserve(
390-
rectangles_with_too_much_energy.conflicts.size() +
391-
rectangles_with_too_much_energy.candidates.size());
392-
filtered_rectangles = rectangles_with_too_much_energy.conflicts;
393-
// Our current DFF does nothing if all elements are smaller than half of the
394-
// size of the rectangle. So we filter all rectangles that are twice the
395-
// dimensions of the largest item of our problem.
396-
for (const Rectangle& r : rectangles_with_too_much_energy.candidates) {
397-
if (r.SizeX() <= 2 * max_x_item_size || r.SizeY() <= 2 * max_y_item_size) {
398-
filtered_rectangles.push_back(r);
399-
}
400-
}
401382
// Sample 10 rectangles for looking for a conflict with DFF. This avoids
402383
// making this heuristic too costly.
403384
constexpr int kSampleSize = 10;
404385
absl::InlinedVector<Rectangle, kSampleSize> sampled_rectangles;
405-
std::sample(filtered_rectangles.begin(), filtered_rectangles.end(),
406-
std::back_inserter(sampled_rectangles), kSampleSize, *random_);
386+
std::sample(rectangles_with_too_much_energy.conflicts.begin(),
387+
rectangles_with_too_much_energy.conflicts.end(),
388+
std::back_inserter(sampled_rectangles), 5, *random_);
389+
std::sample(rectangles_with_too_much_energy.candidates.begin(),
390+
rectangles_with_too_much_energy.candidates.end(),
391+
std::back_inserter(sampled_rectangles),
392+
kSampleSize - sampled_rectangles.size(), *random_);
393+
std::sort(sampled_rectangles.begin(), sampled_rectangles.end(),
394+
[](const Rectangle& a, const Rectangle& b) {
395+
const bool larger = std::make_pair(a.SizeX(), a.SizeY()) >
396+
std::make_pair(b.SizeX(), b.SizeY());
397+
// Double-check the invariant from
398+
// FindRectanglesWithEnergyConflictMC() that given two returned
399+
// rectangles there is one that is fully inside the other.
400+
if (larger) {
401+
// Rectangle b is fully contained inside a
402+
DCHECK(a.x_min <= b.x_min && a.x_max >= b.x_max &&
403+
a.y_min <= b.y_min && a.y_max >= b.y_max);
404+
} else {
405+
// Rectangle a is fully contained inside b
406+
DCHECK(a.x_min >= b.x_min && a.x_max <= b.x_max &&
407+
a.y_min >= b.y_min && a.y_max <= b.y_max);
408+
}
409+
return larger;
410+
});
407411
std::vector<IntegerValue> sizes_x, sizes_y;
408412
sizes_x.reserve(active_box_ranges.size());
409413
sizes_y.reserve(active_box_ranges.size());

ortools/sat/diffn.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include <cstdint>
1818
#include <functional>
19+
#include <optional>
1920
#include <vector>
2021

2122
#include "absl/container/flat_hash_set.h"
@@ -60,8 +61,7 @@ class NonOverlappingRectanglesEnergyPropagator : public PropagatorInterface {
6061
int opp_problem_size;
6162
};
6263
std::optional<Conflict> FindConflict(
63-
std::vector<RectangleInRange> active_box_ranges,
64-
const IntegerValue& max_x_item_size, const IntegerValue& max_y_item_size);
64+
std::vector<RectangleInRange> active_box_ranges);
6565

6666
std::vector<RectangleInRange> GeneralizeExplanation(const Conflict& conflict);
6767

0 commit comments

Comments
 (0)