Skip to content

Commit 9982c87

Browse files
committed
[CP-SAT] more fuzzer bugs fixed; reorganize scheduling and packing code; speed up presolve on knapsack constraints
1 parent 3161081 commit 9982c87

22 files changed

+211
-213
lines changed

ortools/sat/2d_orthogonal_packing.cc

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "absl/numeric/bits.h"
2727
#include "absl/random/distributions.h"
2828
#include "absl/types/span.h"
29+
#include "ortools/base/constant_divisor.h"
2930
#include "ortools/base/logging.h"
3031
#include "ortools/sat/2d_packing_brute_force.h"
3132
#include "ortools/sat/integer_base.h"
@@ -404,16 +405,16 @@ void OrthogonalPackingInfeasibilityDetector::GetAllCandidatesForKForDff2(
404405
candidates.Set(i);
405406
}
406407
for (int i = 1; i <= sqrt_bb_size; i++) {
407-
const QuickSmallDivision div(i);
408+
const ::util::math::ConstantDivisor<uint16_t> div(i);
408409
if (i > 1) {
409-
candidates.Set(div.DivideByDivisor(bb_size.value()));
410+
candidates.Set(bb_size.value() / div);
410411
}
411412
for (int k = 0; k < sizes.size(); k++) {
412413
IntegerValue size = sizes[k];
413414
if (2 * size > bb_size && size < bb_size) {
414-
candidates.Set(div.DivideByDivisor(bb_size.value() - size.value() + 1));
415+
candidates.Set((bb_size.value() - size.value() + 1) / div);
415416
} else if (2 * size < bb_size) {
416-
candidates.Set(div.DivideByDivisor(size.value()));
417+
candidates.Set(size.value() / div);
417418
}
418419
}
419420
}

ortools/sat/2d_orthogonal_packing.h

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "absl/log/check.h"
2323
#include "absl/random/bit_gen_ref.h"
2424
#include "absl/types/span.h"
25+
#include "ortools/base/constant_divisor.h"
2526
#include "ortools/sat/integer_base.h"
2627
#include "ortools/sat/synchronization.h"
2728
#include "ortools/util/bitset.h"
@@ -281,10 +282,7 @@ class RoundingDualFeasibleFunction {
281282
public:
282283
// `max_x` must fit in a uint16_t and `k` in [0, max_x/2].
283284
RoundingDualFeasibleFunction(IntegerValue max_x, IntegerValue k)
284-
: div_(k.value()),
285-
max_x_(max_x),
286-
c_k_(div_.DivideByDivisor(max_x_.value())),
287-
k_(k) {
285+
: div_(k.value()), max_x_(max_x), c_k_(max_x_.value() / div_), k_(k) {
288286
DCHECK_GT(k, 0);
289287
DCHECK_LE(2 * k, max_x_);
290288
DCHECK_LE(max_x_, std::numeric_limits<uint16_t>::max());
@@ -296,11 +294,11 @@ class RoundingDualFeasibleFunction {
296294
DCHECK_LE(x, max_x_);
297295

298296
if (2 * x > max_x_) {
299-
return 2 * (c_k_ - div_.DivideByDivisor(max_x_.value() - x.value()));
297+
return 2 * (c_k_ - (max_x_.value() - x.value()) / div_);
300298
} else if (2 * x == max_x_) {
301299
return c_k_;
302300
} else {
303-
return 2 * div_.DivideByDivisor(x.value());
301+
return 2 * (x.value() / div_);
304302
}
305303
}
306304

@@ -309,7 +307,7 @@ class RoundingDualFeasibleFunction {
309307
IntegerValue LowestInverse(IntegerValue y) const;
310308

311309
private:
312-
const QuickSmallDivision div_;
310+
const ::util::math::ConstantDivisor<uint16_t> div_;
313311
const IntegerValue max_x_;
314312
const IntegerValue c_k_;
315313
const IntegerValue k_;

ortools/sat/2d_orthogonal_packing_testing.cc

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,12 @@ std::vector<RectangleInRange> MakeItemsFromRectangles(
178178
return ranges;
179179
}
180180

181-
std::vector<ItemForPairwiseRestriction>
182-
GenerateItemsRectanglesWithNoPairwiseConflict(
181+
std::vector<ItemWithVariableSize> GenerateItemsRectanglesWithNoPairwiseConflict(
183182
absl::Span<const Rectangle> rectangles, double slack_factor,
184183
absl::BitGenRef random) {
185184
const std::vector<RectangleInRange> range_items =
186185
MakeItemsFromRectangles(rectangles, slack_factor, random);
187-
std::vector<ItemForPairwiseRestriction> items;
186+
std::vector<ItemWithVariableSize> items;
188187
items.reserve(rectangles.size());
189188
for (int i = 0; i < range_items.size(); ++i) {
190189
const RectangleInRange& rec = range_items[i];
@@ -201,13 +200,13 @@ GenerateItemsRectanglesWithNoPairwiseConflict(
201200
return items;
202201
}
203202

204-
std::vector<ItemForPairwiseRestriction>
203+
std::vector<ItemWithVariableSize>
205204
GenerateItemsRectanglesWithNoPairwisePropagation(int num_rectangles,
206205
double slack_factor,
207206
absl::BitGenRef random) {
208207
const std::vector<Rectangle> rectangles =
209208
GenerateNonConflictingRectangles(num_rectangles, random);
210-
std::vector<ItemForPairwiseRestriction> items =
209+
std::vector<ItemWithVariableSize> items =
211210
GenerateItemsRectanglesWithNoPairwiseConflict(rectangles, slack_factor,
212211
random);
213212
bool done = false;

ortools/sat/2d_orthogonal_packing_testing.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,11 @@ std::vector<RectangleInRange> MakeItemsFromRectangles(
3838
absl::Span<const Rectangle> rectangles, double slack_factor,
3939
absl::BitGenRef random);
4040

41-
std::vector<ItemForPairwiseRestriction>
42-
GenerateItemsRectanglesWithNoPairwiseConflict(
41+
std::vector<ItemWithVariableSize> GenerateItemsRectanglesWithNoPairwiseConflict(
4342
absl::Span<const Rectangle> rectangles, double slack_factor,
4443
absl::BitGenRef random);
4544

46-
std::vector<ItemForPairwiseRestriction>
45+
std::vector<ItemWithVariableSize>
4746
GenerateItemsRectanglesWithNoPairwisePropagation(int num_rectangles,
4847
double slack_factor,
4948
absl::BitGenRef random);

ortools/sat/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1444,6 +1444,7 @@ cc_library(
14441444
"//ortools/util:saturated_arithmetic",
14451445
"//ortools/util:sorted_interval_list",
14461446
"//ortools/util:strong_integers",
1447+
"@com_google_absl//absl/container:btree",
14471448
"@com_google_absl//absl/container:flat_hash_map",
14481449
"@com_google_absl//absl/container:flat_hash_set",
14491450
"@com_google_absl//absl/log:check",
@@ -2957,6 +2958,7 @@ cc_library(
29572958
":integer_base",
29582959
":synchronization",
29592960
":util",
2961+
"//ortools/base:constant_divisor",
29602962
"//ortools/util:bitset",
29612963
"@com_google_absl//absl/log",
29622964
"@com_google_absl//absl/log:check",

ortools/sat/all_different.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,9 @@ std::function<void(Model*)> AllDifferentOnBounds(
8383
}
8484

8585
std::function<void(Model*)> AllDifferentOnBounds(
86-
const std::vector<IntegerVariable>& vars) {
87-
return [=](Model* model) {
86+
absl::Span<const IntegerVariable> vars) {
87+
return [=, vars = std::vector<IntegerVariable>(vars.begin(), vars.end())](
88+
Model* model) {
8889
if (vars.empty()) return;
8990
std::vector<AffineExpression> expressions;
9091
expressions.reserve(vars.size());

ortools/sat/all_different.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ std::function<void(Model*)> AllDifferentBinary(
4545
// this will not remove already taken values from inside a domain, but it will
4646
// propagates more the domain bounds.
4747
std::function<void(Model*)> AllDifferentOnBounds(
48-
const std::vector<IntegerVariable>& vars);
48+
absl::Span<const IntegerVariable> vars);
4949
std::function<void(Model*)> AllDifferentOnBounds(
5050
const std::vector<AffineExpression>& expressions);
5151

ortools/sat/cp_model_presolve.cc

Lines changed: 75 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@
4242
#include "absl/status/statusor.h"
4343
#include "absl/strings/str_cat.h"
4444
#include "absl/types/span.h"
45+
#include "google/protobuf/arena.h"
4546
#include "google/protobuf/repeated_field.h"
47+
#include "google/protobuf/repeated_ptr_field.h"
4648
#include "google/protobuf/text_format.h"
4749
#include "ortools/base/logging.h"
4850
#include "ortools/base/mathutil.h"
@@ -5836,9 +5838,8 @@ bool CpModelPresolver::PresolveNoOverlap2D(int /*c*/, ConstraintProto* ct) {
58365838

58375839
// Filter absent boxes.
58385840
int new_size = 0;
5839-
std::vector<Rectangle> bounding_boxes, fixed_boxes;
5841+
std::vector<Rectangle> bounding_boxes, fixed_boxes, non_fixed_bounding_boxes;
58405842
std::vector<RectangleInRange> non_fixed_boxes;
5841-
std::vector<int> active_boxes;
58425843
absl::flat_hash_set<int> fixed_item_indexes;
58435844
for (int i = 0; i < proto.x_intervals_size(); ++i) {
58445845
const int x_interval_index = proto.x_intervals(i);
@@ -5877,14 +5878,14 @@ bool CpModelPresolver::PresolveNoOverlap2D(int /*c*/, ConstraintProto* ct) {
58775878
IntegerValue(context_->EndMax(x_interval_index)),
58785879
IntegerValue(context_->StartMin(y_interval_index)),
58795880
IntegerValue(context_->EndMax(y_interval_index))});
5880-
active_boxes.push_back(new_size);
58815881
if (context_->IntervalIsConstant(x_interval_index) &&
58825882
context_->IntervalIsConstant(y_interval_index) &&
58835883
context_->SizeMax(x_interval_index) > 0 &&
58845884
context_->SizeMax(y_interval_index) > 0) {
58855885
fixed_boxes.push_back(bounding_boxes.back());
58865886
fixed_item_indexes.insert(new_size);
58875887
} else {
5888+
non_fixed_bounding_boxes.push_back(bounding_boxes.back());
58885889
non_fixed_boxes.push_back(
58895890
{.box_index = new_size,
58905891
.bounding_area = bounding_boxes.back(),
@@ -5909,15 +5910,25 @@ bool CpModelPresolver::PresolveNoOverlap2D(int /*c*/, ConstraintProto* ct) {
59095910
}
59105911
}
59115912

5912-
const CompactVectorVector<int> components = GetOverlappingRectangleComponents(
5913-
bounding_boxes, absl::MakeSpan(active_boxes));
5914-
// The result of GetOverlappingRectangleComponents() omit singleton components
5915-
// thus to check whether a graph is fully connected we must check also the
5916-
// size of the unique component.
5917-
const bool is_fully_connected =
5918-
(components.size() == 1 && components[0].size() == active_boxes.size()) ||
5919-
(active_boxes.size() <= 1);
5920-
if (!is_fully_connected) {
5913+
if (new_size < initial_num_boxes) {
5914+
context_->UpdateRuleStats("no_overlap_2d: removed inactive boxes");
5915+
ct->mutable_no_overlap_2d()->mutable_x_intervals()->Truncate(new_size);
5916+
ct->mutable_no_overlap_2d()->mutable_y_intervals()->Truncate(new_size);
5917+
}
5918+
5919+
if (new_size == 0) {
5920+
context_->UpdateRuleStats("no_overlap_2d: no boxes");
5921+
return RemoveConstraint(ct);
5922+
}
5923+
5924+
if (new_size == 1) {
5925+
context_->UpdateRuleStats("no_overlap_2d: only one box");
5926+
return RemoveConstraint(ct);
5927+
}
5928+
5929+
const CompactVectorVector<int> components =
5930+
GetOverlappingRectangleComponents(bounding_boxes);
5931+
if (components.size() > 1) {
59215932
for (int i = 0; i < components.size(); ++i) {
59225933
absl::Span<const int> boxes = components[i];
59235934
if (boxes.size() <= 1) continue;
@@ -5962,30 +5973,14 @@ bool CpModelPresolver::PresolveNoOverlap2D(int /*c*/, ConstraintProto* ct) {
59625973
return RemoveConstraint(ct);
59635974
}
59645975

5965-
if (new_size < initial_num_boxes) {
5966-
context_->UpdateRuleStats("no_overlap_2d: removed inactive boxes");
5967-
ct->mutable_no_overlap_2d()->mutable_x_intervals()->Truncate(new_size);
5968-
ct->mutable_no_overlap_2d()->mutable_y_intervals()->Truncate(new_size);
5969-
}
5970-
5971-
if (new_size == 0) {
5972-
context_->UpdateRuleStats("no_overlap_2d: no boxes");
5973-
return RemoveConstraint(ct);
5974-
}
5975-
5976-
if (new_size == 1) {
5977-
context_->UpdateRuleStats("no_overlap_2d: only one box");
5978-
return RemoveConstraint(ct);
5979-
}
5980-
59815976
// We check if the fixed boxes are not overlapping so downstream code can
59825977
// assume it to be true.
59835978
if (!FindPartialRectangleIntersections(fixed_boxes).empty()) {
59845979
return context_->NotifyThatModelIsUnsat(
59855980
"Two fixed boxes in no_overlap_2d overlap");
59865981
}
59875982

5988-
if (fixed_boxes.size() == active_boxes.size()) {
5983+
if (non_fixed_bounding_boxes.empty()) {
59895984
context_->UpdateRuleStats("no_overlap_2d: all boxes are fixed");
59905985
return RemoveConstraint(ct);
59915986
}
@@ -6035,6 +6030,36 @@ bool CpModelPresolver::PresolveNoOverlap2D(int /*c*/, ConstraintProto* ct) {
60356030
return RemoveConstraint(ct);
60366031
}
60376032
}
6033+
// If the non-fixed boxes are disjoint but connected by fixed boxes, we can
6034+
// split the constraint and duplicate the fixed boxes. To avoid duplicating
6035+
// too many fixed boxes, we do this after we we applied the presolve reducing
6036+
// their number to as few as possible.
6037+
const CompactVectorVector<int> non_fixed_components =
6038+
GetOverlappingRectangleComponents(non_fixed_bounding_boxes);
6039+
if (non_fixed_components.size() > 1) {
6040+
for (int i = 0; i < non_fixed_components.size(); ++i) {
6041+
// Note: we care about components of size 1 because they might be
6042+
// overlapping with the fixed boxes.
6043+
absl::Span<const int> indexes = non_fixed_components[i];
6044+
6045+
NoOverlap2DConstraintProto* new_no_overlap_2d =
6046+
context_->working_model->add_constraints()->mutable_no_overlap_2d();
6047+
for (const int idx : indexes) {
6048+
const int b = non_fixed_boxes[idx].box_index;
6049+
new_no_overlap_2d->add_x_intervals(proto.x_intervals(b));
6050+
new_no_overlap_2d->add_y_intervals(proto.y_intervals(b));
6051+
}
6052+
for (const int b : fixed_item_indexes) {
6053+
new_no_overlap_2d->add_x_intervals(proto.x_intervals(b));
6054+
new_no_overlap_2d->add_y_intervals(proto.y_intervals(b));
6055+
}
6056+
}
6057+
context_->UpdateNewConstraintsVariableUsage();
6058+
context_->UpdateRuleStats(
6059+
"no_overlap_2d: split into disjoint components duplicating fixed "
6060+
"boxes");
6061+
return RemoveConstraint(ct);
6062+
}
60386063
RunPropagatorsForConstraint(*ct);
60396064
return new_size < initial_num_boxes;
60406065
}
@@ -9295,8 +9320,9 @@ void CpModelPresolver::DetectDuplicateColumns() {
92959320
if (rep_to_dups[var].empty()) continue;
92969321

92979322
// Since columns are the same, we can introduce a new variable = sum all
9298-
// columns. Note that we shouldn't have any overflow here by the
9299-
// precondition on our variable domains.
9323+
// columns. Note that the linear expression will not overflow, but the
9324+
// overflow check also requires that max_sum < int_max/2, which might
9325+
// happen.
93009326
//
93019327
// In the corner case where there is a lot of holes in the domain, and the
93029328
// sum domain is too complex, we skip. Hopefully this should be rare.
@@ -9318,7 +9344,10 @@ void CpModelPresolver::DetectDuplicateColumns() {
93189344
}
93199345
const int new_var = context_->NewIntVarWithDefinition(
93209346
domain, definition, /*append_constraint_to_mapping_model=*/true);
9321-
CHECK_NE(new_var, -1);
9347+
if (new_var == -1) {
9348+
context_->UpdateRuleStats("TODO duplicate: possible overflow");
9349+
continue;
9350+
}
93229351

93239352
var_to_remove.push_back(var);
93249353
CHECK_EQ(var_to_rep[var], -1);
@@ -14357,19 +14386,25 @@ void ApplyVariableMapping(absl::Span<int> mapping,
1435714386
}
1435814387

1435914388
// Move the variable definitions.
14360-
std::vector<IntegerVariableProto> new_variables;
14389+
google::protobuf::RepeatedPtrField<IntegerVariableProto>
14390+
new_variables_storage;
14391+
google::protobuf::RepeatedPtrField<IntegerVariableProto>* new_variables;
14392+
if (proto->GetArena() == nullptr) {
14393+
new_variables = &new_variables_storage;
14394+
} else {
14395+
new_variables = google::protobuf::Arena::Create<
14396+
google::protobuf::RepeatedPtrField<IntegerVariableProto>>(
14397+
proto->GetArena());
14398+
}
1436114399
for (int i = 0; i < mapping.size(); ++i) {
1436214400
const int image = mapping[i];
1436314401
if (image < 0) continue;
14364-
if (image >= new_variables.size()) {
14365-
new_variables.resize(image + 1, IntegerVariableProto());
14402+
while (image >= new_variables->size()) {
14403+
new_variables->Add();
1436614404
}
14367-
new_variables[image].Swap(proto->mutable_variables(i));
14368-
}
14369-
proto->clear_variables();
14370-
for (IntegerVariableProto& proto_ref : new_variables) {
14371-
proto->add_variables()->Swap(&proto_ref);
14405+
(*new_variables)[image].Swap(proto->mutable_variables(i));
1437214406
}
14407+
proto->mutable_variables()->Swap(new_variables);
1437314408

1437414409
// Check that all variables have a non-empty domain.
1437514410
for (const IntegerVariableProto& v : proto->variables()) {

ortools/sat/cuts.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2382,7 +2382,7 @@ IntegerValue SumOfAllDiffLowerBounder::SumOfMinDomainValues() {
23822382
int count = 0;
23832383
IntegerValue sum = 0;
23842384
for (const IntegerValue value : min_values_) {
2385-
sum += value;
2385+
sum = CapAddI(sum, value);
23862386
if (++count >= expr_mins_.size()) return sum;
23872387
}
23882388
return sum;
@@ -2439,6 +2439,8 @@ void TryToGenerateAllDiffCut(
24392439
std::string max_suffix;
24402440
const IntegerValue required_max_sum =
24412441
-negated_diff_maxes.GetBestLowerBound(max_suffix);
2442+
if (required_max_sum == std::numeric_limits<IntegerValue>::max()) continue;
2443+
DCHECK_LE(required_min_sum, required_max_sum);
24422444
if (sum < ToDouble(required_min_sum) - kMinCutViolation ||
24432445
sum > ToDouble(required_max_sum) + kMinCutViolation) {
24442446
LinearConstraintBuilder cut(model, required_min_sum, required_max_sum);

ortools/sat/cuts.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,7 @@ class SumOfAllDiffLowerBounder {
727727
void Add(const AffineExpression& expr, int num_expr,
728728
const IntegerTrail& integer_trail);
729729

730+
// Return int_max if the sum overflows.
730731
IntegerValue SumOfMinDomainValues();
731732
IntegerValue SumOfDifferentMins();
732733
IntegerValue GetBestLowerBound(std::string& suffix);

0 commit comments

Comments
 (0)