Skip to content

Commit 188cfcb

Browse files
lperronMizux
authored andcommitted
[CP-SAT] work on precedences; improve scheduling cuts
1 parent b5ca2d2 commit 188cfcb

37 files changed

Lines changed: 1760 additions & 402 deletions
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
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+
14+
#include "ortools/sat/2d_distances_propagator.h"
15+
16+
#include <cstdint>
17+
#include <string>
18+
#include <utility>
19+
#include <vector>
20+
21+
#include "absl/container/flat_hash_map.h"
22+
#include "absl/log/check.h"
23+
#include "absl/log/log.h"
24+
#include "absl/types/span.h"
25+
#include "ortools/base/stl_util.h"
26+
#include "ortools/sat/cp_model.pb.h"
27+
#include "ortools/sat/integer.h"
28+
#include "ortools/sat/integer_base.h"
29+
#include "ortools/sat/linear_propagation.h"
30+
#include "ortools/sat/model.h"
31+
#include "ortools/sat/no_overlap_2d_helper.h"
32+
#include "ortools/sat/precedences.h"
33+
#include "ortools/sat/sat_base.h"
34+
#include "ortools/sat/scheduling_helpers.h"
35+
#include "ortools/sat/synchronization.h"
36+
37+
namespace operations_research {
38+
namespace sat {
39+
40+
Precedences2DPropagator::Precedences2DPropagator(
41+
NoOverlap2DConstraintHelper* helper, Model* model)
42+
: helper_(*helper),
43+
binary_relations_maps_(model->GetOrCreate<BinaryRelationsMaps>()),
44+
shared_stats_(model->GetOrCreate<SharedStatistics>()) {
45+
model->GetOrCreate<LinearPropagator>()->SetPushAffineUbForBinaryRelation();
46+
}
47+
48+
void Precedences2DPropagator::CollectPairsOfBoxesWithNonTrivialDistance() {
49+
helper_.SynchronizeAndSetDirection();
50+
non_trivial_pairs_.clear();
51+
52+
struct VarUsage {
53+
// boxes[0=x, 1=y][0=start, 1=end]
54+
std::vector<int> boxes[2][2];
55+
};
56+
absl::flat_hash_map<IntegerVariable, VarUsage> var_to_box_and_coeffs;
57+
58+
for (int dim = 0; dim < 2; ++dim) {
59+
const SchedulingConstraintHelper& dim_helper =
60+
dim == 0 ? helper_.x_helper() : helper_.y_helper();
61+
for (int i = 0; i < helper_.NumBoxes(); ++i) {
62+
const absl::Span<const AffineExpression> interval_points =
63+
i == 0 ? dim_helper.Starts() : dim_helper.Ends();
64+
for (int j = 0; j < 2; ++j) {
65+
if (interval_points[i].var != kNoIntegerVariable) {
66+
var_to_box_and_coeffs[PositiveVariable(interval_points[i].var)]
67+
.boxes[dim][j]
68+
.push_back(i);
69+
}
70+
}
71+
}
72+
}
73+
74+
VLOG(2) << "CollectPairsOfBoxesWithNonTrivialDistance called, num_exprs: "
75+
<< binary_relations_maps_->GetAllExpressionsWithAffineBounds().size();
76+
for (const LinearExpression2& expr :
77+
binary_relations_maps_->GetAllExpressionsWithAffineBounds()) {
78+
auto it1 = var_to_box_and_coeffs.find(PositiveVariable(expr.vars[0]));
79+
auto it2 = var_to_box_and_coeffs.find(PositiveVariable(expr.vars[1]));
80+
if (it1 == var_to_box_and_coeffs.end() ||
81+
it2 == var_to_box_and_coeffs.end()) {
82+
continue;
83+
}
84+
85+
const VarUsage& usage1 = it1->second;
86+
const VarUsage& usage2 = it2->second;
87+
for (int dim = 0; dim < 2; ++dim) {
88+
const SchedulingConstraintHelper& dim_helper =
89+
dim == 0 ? helper_.x_helper() : helper_.y_helper();
90+
for (const int box1 : usage1.boxes[dim][0 /* start */]) {
91+
for (const int box2 : usage2.boxes[dim][1 /* end */]) {
92+
const AffineExpression& start = dim_helper.Starts()[box1];
93+
const AffineExpression& end = dim_helper.Ends()[box2];
94+
LinearExpression2 expr2;
95+
expr2.vars[0] = start.var;
96+
expr2.vars[1] = NegationOf(end.var);
97+
expr2.coeffs[0] = start.coeff;
98+
expr2.coeffs[1] = end.coeff;
99+
expr2.SimpleCanonicalization();
100+
expr2.DivideByGcd();
101+
if (expr == expr2) {
102+
if (box1 < box2) {
103+
non_trivial_pairs_.push_back({box1, box2});
104+
} else {
105+
non_trivial_pairs_.push_back({box2, box1});
106+
}
107+
}
108+
}
109+
}
110+
}
111+
}
112+
113+
gtl::STLSortAndRemoveDuplicates(&non_trivial_pairs_);
114+
}
115+
116+
bool Precedences2DPropagator::Propagate() {
117+
if (!helper_.SynchronizeAndSetDirection()) return false;
118+
if (last_helper_inprocessing_count_ != helper_.InProcessingCount() ||
119+
helper_.x_helper().CurrentDecisionLevel() == 0 ||
120+
last_num_expressions_ !=
121+
binary_relations_maps_->NumExpressionsWithAffineBounds()) {
122+
last_helper_inprocessing_count_ = helper_.InProcessingCount();
123+
last_num_expressions_ =
124+
binary_relations_maps_->NumExpressionsWithAffineBounds();
125+
CollectPairsOfBoxesWithNonTrivialDistance();
126+
}
127+
128+
num_calls_++;
129+
130+
SchedulingConstraintHelper* helpers[2] = {&helper_.x_helper(),
131+
&helper_.y_helper()};
132+
133+
for (const auto& [box1, box2] : non_trivial_pairs_) {
134+
DCHECK(box1 < helper_.NumBoxes());
135+
DCHECK(box2 < helper_.NumBoxes());
136+
if (!helper_.IsPresent(box1) && !helper_.IsPresent(box2)) {
137+
continue;
138+
}
139+
140+
bool is_unfeasible = true;
141+
for (int dim = 0; dim < 2; dim++) {
142+
const SchedulingConstraintHelper* helper = helpers[dim];
143+
for (int j = 0; j < 2; j++) {
144+
int b1 = box1;
145+
int b2 = box2;
146+
if (j == 1) {
147+
std::swap(b1, b2);
148+
}
149+
LinearExpression2 expr;
150+
expr.vars[0] = helper->Starts()[b1].var;
151+
expr.vars[1] = NegationOf(helper->Ends()[b2].var);
152+
expr.coeffs[0] = helper->Starts()[b1].coeff;
153+
expr.coeffs[1] = helper->Ends()[b2].coeff;
154+
const IntegerValue ub_of_start_minus_end_value =
155+
binary_relations_maps_->UpperBound(expr) +
156+
helper->Starts()[b1].constant - helper->Ends()[b2].constant;
157+
if (ub_of_start_minus_end_value >= 0) {
158+
is_unfeasible = false;
159+
break;
160+
}
161+
if (!is_unfeasible) break;
162+
}
163+
}
164+
if (!is_unfeasible) continue;
165+
166+
// We have a mandatory overlap on both x and y! Explain and propagate.
167+
168+
helper_.ClearReason();
169+
num_conflicts_++;
170+
std::vector<IntegerLiteral> reason;
171+
std::vector<Literal> lit_reason;
172+
173+
for (int dim = 0; dim < 2; dim++) {
174+
SchedulingConstraintHelper* helper = helpers[dim];
175+
for (int j = 0; j < 2; j++) {
176+
int b1 = box1;
177+
int b2 = box2;
178+
if (j == 1) {
179+
std::swap(b1, b2);
180+
}
181+
LinearExpression2 expr;
182+
expr.vars[0] = helper->Starts()[b1].var;
183+
expr.vars[1] = NegationOf(helper->Ends()[b2].var);
184+
expr.coeffs[0] = helper->Starts()[b1].coeff;
185+
expr.coeffs[1] = helper->Ends()[b2].coeff;
186+
binary_relations_maps_->AddReasonForUpperBoundLowerThan(
187+
expr,
188+
-(helper->Starts()[b1].constant - helper->Ends()[b2].constant),
189+
&lit_reason, &reason);
190+
}
191+
}
192+
helper_.AddPresenceReason(box1);
193+
helper_.AddPresenceReason(box2);
194+
helper_.x_helper().MutableIntegerReason()->insert(
195+
helper_.x_helper().MutableIntegerReason()->end(), reason.begin(),
196+
reason.end());
197+
helper_.x_helper().MutableLiteralReason()->insert(
198+
helper_.x_helper().MutableLiteralReason()->end(), lit_reason.begin(),
199+
lit_reason.end());
200+
return helper_.ReportConflict();
201+
}
202+
return true;
203+
}
204+
205+
int Precedences2DPropagator::RegisterWith(GenericLiteralWatcher* watcher) {
206+
const int id = watcher->Register(this);
207+
helper_.WatchAllBoxes(id);
208+
// TODO(user): add an API to BinaryRelationsMaps to watch linear2
209+
return id;
210+
}
211+
212+
Precedences2DPropagator::~Precedences2DPropagator() {
213+
if (!VLOG_IS_ON(1)) return;
214+
std::vector<std::pair<std::string, int64_t>> stats;
215+
stats.push_back({"Precedences2DPropagator/called", num_calls_});
216+
stats.push_back({"Precedences2DPropagator/conflicts", num_conflicts_});
217+
stats.push_back({"Precedences2DPropagator/pairs", non_trivial_pairs_.size()});
218+
219+
shared_stats_->AddStats(stats);
220+
}
221+
222+
} // namespace sat
223+
} // namespace operations_research
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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+
14+
#ifndef OR_TOOLS_SAT_2D_DISTANCES_PROPAGATOR_H_
15+
#define OR_TOOLS_SAT_2D_DISTANCES_PROPAGATOR_H_
16+
17+
#include <cstdint>
18+
#include <utility>
19+
#include <vector>
20+
21+
#include "ortools/sat/integer.h"
22+
#include "ortools/sat/model.h"
23+
#include "ortools/sat/no_overlap_2d_helper.h"
24+
#include "ortools/sat/precedences.h"
25+
#include "ortools/sat/synchronization.h"
26+
27+
namespace operations_research {
28+
namespace sat {
29+
30+
// This class implements a propagator for non_overlap_2d constraints that uses
31+
// the BinaryRelationsMaps to detect precedences between pairs of boxes and
32+
// detect a conflict if the precedences implies an overlap between the two
33+
// boxes. For doing this efficiently, it keep track of pairs of boxes that have
34+
// non-fixed precedences in the BinaryRelationsMaps and only check those in the
35+
// propagation.
36+
class Precedences2DPropagator : public PropagatorInterface {
37+
public:
38+
Precedences2DPropagator(NoOverlap2DConstraintHelper* helper, Model* model);
39+
40+
~Precedences2DPropagator() override;
41+
42+
bool Propagate() final;
43+
int RegisterWith(GenericLiteralWatcher* watcher);
44+
45+
private:
46+
void CollectPairsOfBoxesWithNonTrivialDistance();
47+
48+
std::vector<std::pair<int, int>> non_trivial_pairs_;
49+
50+
NoOverlap2DConstraintHelper& helper_;
51+
BinaryRelationsMaps* binary_relations_maps_;
52+
SharedStatistics* shared_stats_;
53+
54+
int last_helper_inprocessing_count_ = -1;
55+
int last_num_expressions_ = -1;
56+
57+
int64_t num_conflicts_ = 0;
58+
int64_t num_calls_ = 0;
59+
60+
Precedences2DPropagator(const Precedences2DPropagator&) = delete;
61+
Precedences2DPropagator& operator=(const Precedences2DPropagator&) = delete;
62+
};
63+
64+
} // namespace sat
65+
} // namespace operations_research
66+
67+
#endif // OR_TOOLS_SAT_2D_DISTANCES_PROPAGATOR_H_

ortools/sat/BUILD.bazel

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,29 @@ proto_library(
114114
srcs = ["cp_model.proto"],
115115
)
116116

117+
cc_library(
118+
name = "2d_distances_propagator",
119+
srcs = ["2d_distances_propagator.cc"],
120+
hdrs = ["2d_distances_propagator.h"],
121+
deps = [
122+
":cp_model_cc_proto",
123+
":integer",
124+
":integer_base",
125+
":linear_propagation",
126+
":model",
127+
":no_overlap_2d_helper",
128+
":precedences",
129+
":sat_base",
130+
":scheduling_helpers",
131+
":synchronization",
132+
"//ortools/base:stl_util",
133+
"@abseil-cpp//absl/container:flat_hash_map",
134+
"@abseil-cpp//absl/log",
135+
"@abseil-cpp//absl/log:check",
136+
"@abseil-cpp//absl/types:span",
137+
],
138+
)
139+
117140
cc_library(
118141
name = "2d_mandatory_overlap_propagator",
119142
srcs = ["2d_mandatory_overlap_propagator.cc"],
@@ -809,6 +832,7 @@ cc_library(
809832
srcs = ["cp_model_loader.cc"],
810833
hdrs = ["cp_model_loader.h"],
811834
deps = [
835+
":2d_distances_propagator",
812836
":all_different",
813837
":circuit",
814838
":clause",
@@ -3175,6 +3199,7 @@ cc_test(
31753199
"//ortools/util:random_engine",
31763200
"//ortools/util:sorted_interval_list",
31773201
"@abseil-cpp//absl/container:btree",
3202+
"@abseil-cpp//absl/container:flat_hash_set",
31783203
"@abseil-cpp//absl/log:check",
31793204
"@abseil-cpp//absl/numeric:int128",
31803205
"@abseil-cpp//absl/random",
@@ -3514,8 +3539,8 @@ cc_test(
35143539
"@abseil-cpp//absl/random:distributions",
35153540
"@abseil-cpp//absl/strings",
35163541
"@abseil-cpp//absl/types:span",
3517-
"@google_benchmark//:benchmark",
35183542
"@fuzztest//fuzztest:fuzztest_gtest_main",
3543+
"@google_benchmark//:benchmark",
35193544
],
35203545
)
35213546

@@ -3524,6 +3549,7 @@ cc_library(
35243549
srcs = ["diffn.cc"],
35253550
hdrs = ["diffn.h"],
35263551
deps = [
3552+
":2d_distances_propagator",
35273553
":2d_mandatory_overlap_propagator",
35283554
":2d_orthogonal_packing",
35293555
":2d_try_edge_propagator",

ortools/sat/cp_model.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ BoolVar BoolVar::WithName(absl::string_view name) {
4747

4848
std::string BoolVar::Name() const {
4949
if (builder_ == nullptr) return "null";
50-
const std::string& name =
50+
absl::string_view name =
5151
builder_->Proto().variables(PositiveRef(index_)).name();
5252
if (RefIsPositive(index_)) {
53-
return name;
53+
return std::string(name);
5454
} else {
5555
return absl::StrCat("Not(", name, ")");
5656
}

0 commit comments

Comments
 (0)