Skip to content

Commit bef3002

Browse files
committed
[CP-SAT] experimental integration of variable shaving; tentative fix for #4615; more bugfixes
1 parent 2734d60 commit bef3002

14 files changed

+2548
-200
lines changed

ortools/sat/BUILD.bazel

+21-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,26 @@ cc_library(
4141
],
4242
)
4343

44+
cc_test(
45+
name = "cp_model_test",
46+
size = "small",
47+
srcs = ["cp_model_test.cc"],
48+
deps = [
49+
":cp_model",
50+
":cp_model_cc_proto",
51+
":cp_model_checker",
52+
":cp_model_solver",
53+
":model",
54+
":sat_parameters_cc_proto",
55+
"//ortools/base",
56+
"//ortools/base:container_logging",
57+
"//ortools/base:gmock_main",
58+
"//ortools/base:parse_test_proto",
59+
"//ortools/util:sorted_interval_list",
60+
"@abseil-cpp//absl/types:span",
61+
],
62+
)
63+
4464
cc_library(
4565
name = "model",
4666
hdrs = ["model.h"],
@@ -2838,7 +2858,6 @@ cc_library(
28382858
":cuts",
28392859
":integer",
28402860
":integer_base",
2841-
":intervals",
28422861
":linear_constraint",
28432862
":linear_constraint_manager",
28442863
":model",
@@ -2854,6 +2873,7 @@ cc_library(
28542873
"@abseil-cpp//absl/base:core_headers",
28552874
"@abseil-cpp//absl/container:btree",
28562875
"@abseil-cpp//absl/container:flat_hash_map",
2876+
"@abseil-cpp//absl/log",
28572877
"@abseil-cpp//absl/log:check",
28582878
"@abseil-cpp//absl/strings",
28592879
"@abseil-cpp//absl/types:span",

ortools/sat/constraint_violation.cc

+44-67
Original file line numberDiff line numberDiff line change
@@ -59,23 +59,6 @@ int64_t AffineValue(const ViewOfAffineLinearExpressionProto& affine,
5959
return affine.coeff * solution[affine.var] + affine.offset;
6060
}
6161

62-
LinearExpressionProto ExprDiff(const LinearExpressionProto& a,
63-
const LinearExpressionProto& b) {
64-
LinearExpressionProto result;
65-
result.set_offset(a.offset() - b.offset());
66-
result.mutable_vars()->Reserve(a.vars().size() + b.vars().size());
67-
result.mutable_coeffs()->Reserve(a.vars().size() + b.vars().size());
68-
for (int i = 0; i < a.vars().size(); ++i) {
69-
result.add_vars(a.vars(i));
70-
result.add_coeffs(a.coeffs(i));
71-
}
72-
for (int i = 0; i < b.vars().size(); ++i) {
73-
result.add_vars(b.vars(i));
74-
result.add_coeffs(-b.coeffs(i));
75-
}
76-
return result;
77-
}
78-
7962
LinearExpressionProto LinearExprSum(LinearExpressionProto a,
8063
LinearExpressionProto b) {
8164
LinearExpressionProto result;
@@ -1105,56 +1088,38 @@ int64_t CompiledAllDiffConstraint::ComputeViolation(
11051088
return violation;
11061089
}
11071090

1108-
// ----- NoOverlapBetweenTwoIntervals -----
1091+
// ----- CompiledNoOverlapWithTwoIntervals -----
11091092

1110-
NoOverlapBetweenTwoIntervals::NoOverlapBetweenTwoIntervals(
1111-
int interval_0, int interval_1, const CpModelProto& cp_model) {
1112-
const ConstraintProto& ct0 = cp_model.constraints(interval_0);
1113-
const ConstraintProto& ct1 = cp_model.constraints(interval_1);
1114-
1115-
// The more compact the better, hence the size + int[].
1116-
num_enforcements_ =
1117-
ct0.enforcement_literal().size() + ct1.enforcement_literal().size();
1118-
if (num_enforcements_ > 0) {
1119-
enforcements_.reset(new int[num_enforcements_]);
1120-
int i = 0;
1121-
for (const int lit : ct0.enforcement_literal()) enforcements_[i++] = lit;
1122-
for (const int lit : ct1.enforcement_literal()) enforcements_[i++] = lit;
1093+
template <bool has_enforcement>
1094+
int64_t CompiledNoOverlapWithTwoIntervals<has_enforcement>::ViolationDelta(
1095+
int /*var*/, int64_t /*old_value*/, absl::Span<const int64_t> solution) {
1096+
if (has_enforcement) {
1097+
for (const int lit : enforcements_) {
1098+
if (!LiteralValue(lit, solution)) return -violation_;
1099+
}
11231100
}
11241101

1125-
// We prefer to use start + size instead of end so that moving "start" moves
1126-
// the whole interval around (for the non-fixed duration case).
1127-
end_minus_start_1_ =
1128-
ExprDiff(LinearExprSum(ct0.interval().start(), ct0.interval().size()),
1129-
ct1.interval().start());
1130-
end_minus_start_2_ =
1131-
ExprDiff(LinearExprSum(ct1.interval().start(), ct1.interval().size()),
1132-
ct0.interval().start());
1102+
const int64_t s1 = AffineValue(interval1_.start, solution);
1103+
const int64_t e1 = AffineValue(interval1_.end, solution);
1104+
const int64_t s2 = AffineValue(interval2_.start, solution);
1105+
const int64_t e2 = AffineValue(interval2_.end, solution);
1106+
const int64_t repair = std::min(e2 - s1, e1 - s2);
1107+
if (repair <= 0) return -violation_; // disjoint
1108+
return repair - violation_;
11331109
}
11341110

1135-
// Same as NoOverlapMinRepairDistance().
1136-
int64_t NoOverlapBetweenTwoIntervals::ComputeViolationInternal(
1137-
absl::Span<const int64_t> solution) {
1138-
for (int i = 0; i < num_enforcements_; ++i) {
1139-
if (!LiteralValue(enforcements_[i], solution)) return 0;
1140-
}
1141-
const int64_t diff1 = ExprValue(end_minus_start_1_, solution);
1142-
const int64_t diff2 = ExprValue(end_minus_start_2_, solution);
1143-
return std::max(std::min(diff1, diff2), int64_t{0});
1144-
}
1145-
1146-
std::vector<int> NoOverlapBetweenTwoIntervals::UsedVariables(
1111+
template <bool has_enforcement>
1112+
std::vector<int>
1113+
CompiledNoOverlapWithTwoIntervals<has_enforcement>::UsedVariables(
11471114
const CpModelProto& /*model_proto*/) const {
11481115
std::vector<int> result;
1149-
for (int i = 0; i < num_enforcements_; ++i) {
1150-
result.push_back(PositiveRef(enforcements_[i]));
1151-
}
1152-
for (const int var : end_minus_start_1_.vars()) {
1153-
result.push_back(PositiveRef(var));
1154-
}
1155-
for (const int var : end_minus_start_2_.vars()) {
1156-
result.push_back(PositiveRef(var));
1116+
if (has_enforcement) {
1117+
for (const int ref : enforcements_) result.push_back(PositiveRef(ref));
11571118
}
1119+
interval1_.start.AppendVarTo(result);
1120+
interval1_.end.AppendVarTo(result);
1121+
interval2_.start.AppendVarTo(result);
1122+
interval2_.end.AppendVarTo(result);
11581123
gtl::STLSortAndRemoveDuplicates(&result);
11591124
result.shrink_to_fit();
11601125
return result;
@@ -1291,6 +1256,7 @@ CompiledNoOverlap2dWithTwoBoxes<has_enforcement>::UsedVariables(
12911256
box2_.y_min.AppendVarTo(result);
12921257
box2_.y_max.AppendVarTo(result);
12931258
gtl::STLSortAndRemoveDuplicates(&result);
1259+
result.shrink_to_fit();
12941260
return result;
12951261
}
12961262

@@ -1722,20 +1688,31 @@ void LsEvaluator::CompileOneConstraint(const ConstraintProto& ct) {
17221688
// We expand the no_overlap constraints into a quadratic number of
17231689
// disjunctions.
17241690
for (int i = 0; i + 1 < size; ++i) {
1725-
const IntervalConstraintProto& interval_i =
1726-
cp_model_.constraints(ct.no_overlap().intervals(i)).interval();
1691+
const ConstraintProto& proto_i =
1692+
cp_model_.constraints(ct.no_overlap().intervals(i));
1693+
const IntervalConstraintProto& interval_i = proto_i.interval();
17271694
const int64_t min_start_i = ExprMin(interval_i.start(), cp_model_);
17281695
const int64_t max_end_i = ExprMax(interval_i.end(), cp_model_);
17291696
for (int j = i + 1; j < size; ++j) {
1730-
const IntervalConstraintProto& interval_j =
1731-
cp_model_.constraints(ct.no_overlap().intervals(j)).interval();
1697+
const ConstraintProto& proto_j =
1698+
cp_model_.constraints(ct.no_overlap().intervals(j));
1699+
const IntervalConstraintProto& interval_j = proto_j.interval();
17321700
const int64_t min_start_j = ExprMin(interval_j.start(), cp_model_);
17331701
const int64_t max_end_j = ExprMax(interval_j.end(), cp_model_);
17341702
if (min_start_i >= max_end_j || min_start_j >= max_end_i) continue;
17351703

1736-
constraints_.emplace_back(new NoOverlapBetweenTwoIntervals(
1737-
ct.no_overlap().intervals(i), ct.no_overlap().intervals(j),
1738-
cp_model_));
1704+
const bool has_enforcement =
1705+
!proto_i.enforcement_literal().empty() ||
1706+
!proto_j.enforcement_literal().empty();
1707+
if (has_enforcement) {
1708+
constraints_.emplace_back(
1709+
new CompiledNoOverlapWithTwoIntervals<true>(proto_i,
1710+
proto_j));
1711+
} else {
1712+
constraints_.emplace_back(
1713+
new CompiledNoOverlapWithTwoIntervals<false>(proto_i,
1714+
proto_j));
1715+
}
17391716
}
17401717
}
17411718
}
@@ -2202,7 +2179,7 @@ int64_t CompiledReservoirConstraint::IncrementalViolation(
22022179
int64_t previous_time = std::numeric_limits<int64_t>::min();
22032180

22042181
// TODO(user): This code is the hotspot for our local search on cumulative.
2205-
// It can probably be slighlty improved. We might also be able to abort early
2182+
// It can probably be slightly improved. We might also be able to abort early
22062183
// if we know that capacity is high enough compared to the highest point of
22072184
// the profile.
22082185
int i = 0;

ortools/sat/constraint_violation.h

+58-40
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <vector>
2323

2424
#include "absl/container/flat_hash_map.h"
25+
#include "absl/log/check.h"
2526
#include "absl/types/span.h"
2627
#include "ortools/base/stl_util.h"
2728
#include "ortools/sat/cp_model.pb.h"
@@ -540,36 +541,75 @@ class CompiledAllDiffConstraint : public CompiledConstraintWithProto {
540541
std::vector<int64_t> values_;
541542
};
542543

544+
// This is more compact and faster to destroy than storing a
545+
// LinearExpressionProto.
546+
struct ViewOfAffineLinearExpressionProto {
547+
explicit ViewOfAffineLinearExpressionProto(
548+
const LinearExpressionProto& proto) {
549+
if (!proto.vars().empty()) {
550+
DCHECK_EQ(proto.vars().size(), 1);
551+
var = proto.vars(0);
552+
coeff = proto.coeffs(0);
553+
}
554+
offset = proto.offset();
555+
}
556+
557+
void AppendVarTo(std::vector<int>& result) const {
558+
if (coeff != 0) result.push_back(var);
559+
}
560+
561+
int var = 0;
562+
int64_t coeff = 0;
563+
int64_t offset = 0;
564+
};
565+
543566
// Special constraint for no overlap between two intervals.
544567
// We usually expand small no-overlap in n^2 such constraint, so we want to
545568
// be compact and efficient here.
546-
class NoOverlapBetweenTwoIntervals : public CompiledConstraint {
569+
template <bool has_enforcement = true>
570+
class CompiledNoOverlapWithTwoIntervals : public CompiledConstraint {
547571
public:
548-
NoOverlapBetweenTwoIntervals(int interval_0, int interval_1,
549-
const CpModelProto& cp_model);
550-
~NoOverlapBetweenTwoIntervals() override = default;
572+
struct Interval {
573+
explicit Interval(const ConstraintProto& x)
574+
: start(x.interval().start()), end(x.interval().end()) {}
575+
ViewOfAffineLinearExpressionProto start;
576+
ViewOfAffineLinearExpressionProto end;
577+
};
578+
579+
CompiledNoOverlapWithTwoIntervals(const ConstraintProto& x1,
580+
const ConstraintProto& x2)
581+
: interval1_(x1), interval2_(x2) {
582+
if (has_enforcement) {
583+
enforcements_.assign(x1.enforcement_literal().begin(),
584+
x1.enforcement_literal().end());
585+
enforcements_.insert(enforcements_.end(),
586+
x2.enforcement_literal().begin(),
587+
x2.enforcement_literal().end());
588+
gtl::STLSortAndRemoveDuplicates(&enforcements_);
589+
}
590+
}
591+
592+
~CompiledNoOverlapWithTwoIntervals() final = default;
551593

552594
int64_t ComputeViolation(absl::Span<const int64_t> solution) final {
553-
return ComputeViolationInternal(solution);
595+
// Optimization hack: If we create a ComputeViolationInternal() that we call
596+
// from here and in ViolationDelta(), then the later is not inlined below in
597+
// ViolationDelta() where it matter a lot for performance.
598+
violation_ = 0;
599+
violation_ = ViolationDelta(0, 0, solution);
600+
return violation_;
554601
}
555602

556-
// Note(user): this is the same implementation as the base one, but it
557-
// avoid one virtual call !
558603
int64_t ViolationDelta(
559604
int /*var*/, int64_t /*old_value*/,
560-
absl::Span<const int64_t> solution_with_new_value) final {
561-
return ComputeViolationInternal(solution_with_new_value) - violation();
562-
}
605+
absl::Span<const int64_t> solution_with_new_value) final;
563606

564607
std::vector<int> UsedVariables(const CpModelProto& model_proto) const final;
565608

566609
private:
567-
int64_t ComputeViolationInternal(absl::Span<const int64_t> solution);
568-
569-
int num_enforcements_;
570-
std::unique_ptr<int[]> enforcements_;
571-
LinearExpressionProto end_minus_start_1_;
572-
LinearExpressionProto end_minus_start_2_;
610+
std::vector<int> enforcements_;
611+
const Interval interval1_;
612+
const Interval interval2_;
573613
};
574614

575615
class CompiledNoOverlap2dConstraint : public CompiledConstraintWithProto {
@@ -584,28 +624,6 @@ class CompiledNoOverlap2dConstraint : public CompiledConstraintWithProto {
584624
const CpModelProto& cp_model_;
585625
};
586626

587-
// This is more compact and faster to destroy than storing a
588-
// LinearExpressionProto.
589-
struct ViewOfAffineLinearExpressionProto {
590-
explicit ViewOfAffineLinearExpressionProto(
591-
const LinearExpressionProto& proto) {
592-
if (!proto.vars().empty()) {
593-
DCHECK_EQ(proto.vars().size(), 1);
594-
var = proto.vars(0);
595-
coeff = proto.coeffs(0);
596-
}
597-
offset = proto.offset();
598-
}
599-
600-
void AppendVarTo(std::vector<int>& result) const {
601-
if (coeff != 0) result.push_back(var);
602-
}
603-
604-
int var = 0;
605-
int64_t coeff = 0;
606-
int64_t offset = 0;
607-
};
608-
609627
template <bool has_enforcement = true>
610628
class CompiledNoOverlap2dWithTwoBoxes : public CompiledConstraint {
611629
public:
@@ -668,8 +686,8 @@ class CompiledNoOverlap2dWithTwoBoxes : public CompiledConstraint {
668686
};
669687

670688
// This can be used to encode reservoir or a cumulative constraints for LS. We
671-
// have a set of event time, and we use for overal violation the sum of overload
672-
// over time.
689+
// have a set of event time, and we use for overall violation the sum of
690+
// overload over time.
673691
//
674692
// This version support an incremental computation when just a few events
675693
// changes, which is roughly O(n) instead of O(n log n) which makes it

ortools/sat/cp_model_copy.cc

+7-2
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ bool ModelCopy::ImportAndSimplifyConstraints(
8484

8585
starting_constraint_index_ = context_->working_model->constraints_size();
8686
for (int c = 0; c < in_model.constraints_size(); ++c) {
87-
if (active_constraints != nullptr && !active_constraints(c)) continue;
87+
if (active_constraints != nullptr && !active_constraints(c)) {
88+
continue;
89+
}
8890
const ConstraintProto& ct = in_model.constraints(c);
8991
if (first_copy) {
9092
if (!PrepareEnforcementCopyWithDup(ct)) continue;
@@ -915,14 +917,17 @@ bool ImportModelWithBasicPresolveIntoContext(const CpModelProto& in_model,
915917

916918
bool ImportModelAndDomainsWithBasicPresolveIntoContext(
917919
const CpModelProto& in_model, absl::Span<const Domain> domains,
918-
std::function<bool(int)> active_constraints, PresolveContext* context) {
920+
std::function<bool(int)> active_constraints, PresolveContext* context,
921+
std::vector<int>* interval_mapping) {
919922
CHECK_EQ(domains.size(), in_model.variables_size());
920923
ModelCopy copier(context);
921924
copier.CreateVariablesFromDomains(domains);
922925
if (copier.ImportAndSimplifyConstraints(in_model, /*first_copy=*/false,
923926
active_constraints)) {
924927
CopyEverythingExceptVariablesAndConstraintsFieldsIntoContext(in_model,
925928
context);
929+
interval_mapping->assign(copier.InternalIntervalMapping().begin(),
930+
copier.InternalIntervalMapping().end());
926931
return true;
927932
}
928933
return !context->ModelIsUnsat();

0 commit comments

Comments
 (0)