Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
intervals.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 <optional>
17#include <utility>
18#include <vector>
19
20#include "absl/container/flat_hash_map.h"
21#include "absl/meta/type_traits.h"
22#include "absl/types/span.h"
24#include "ortools/sat/integer.h"
28#include "ortools/sat/model.h"
34
35namespace operations_research {
36namespace sat {
37
38IntervalVariable IntervalsRepository::CreateInterval(IntegerVariable start,
39 IntegerVariable end,
40 IntegerVariable size,
41 IntegerValue fixed_size,
42 LiteralIndex is_present) {
44 size == kNoIntegerVariable
45 ? AffineExpression(fixed_size)
46 : AffineExpression(size),
47 is_present, /*add_linear_relation=*/true);
48}
49
53 LiteralIndex is_present,
54 bool add_linear_relation) {
55 // Create the interval.
56 const IntervalVariable i(starts_.size());
57 starts_.push_back(start);
58 ends_.push_back(end);
59 sizes_.push_back(size);
60 is_present_.push_back(is_present);
61
62 std::vector<Literal> enforcement_literals;
63 if (is_present != kNoLiteralIndex) {
64 enforcement_literals.push_back(Literal(is_present));
65 }
66
67 if (add_linear_relation) {
68 LinearConstraintBuilder builder(model_, IntegerValue(0), IntegerValue(0));
69 builder.AddTerm(Start(i), IntegerValue(1));
70 builder.AddTerm(Size(i), IntegerValue(1));
71 builder.AddTerm(End(i), IntegerValue(-1));
72 LoadConditionalLinearConstraint(enforcement_literals, builder.Build(),
73 model_);
74 }
75
76 return i;
77}
78
80 IntervalVariable a, IntervalVariable b) {
82 IntervalDefinition{.start = Start(a),
83 .end = End(a),
84 .size = Size(a),
85 .is_present = IsOptional(a)
86 ? std::optional(PresenceLiteral(a))
87 : std::nullopt},
88 IntervalDefinition{.start = Start(b),
89 .end = End(b),
90 .size = Size(b),
91 .is_present = IsOptional(b)
92 ? std::optional(PresenceLiteral(b))
93 : std::nullopt});
94}
95
97 const IntervalDefinition& a, const IntervalDefinition& b) {
98 auto it = disjunctive_precedences_.find({a, b});
99 if (it != disjunctive_precedences_.end()) return it->second.Index();
100
101 std::vector<Literal> enforcement_literals;
102 if (a.is_present.has_value()) {
103 enforcement_literals.push_back(a.is_present.value());
104 }
105 if (b.is_present.has_value()) {
106 enforcement_literals.push_back(b.is_present.value());
107 }
108
109 auto remove_fixed = [assignment =
110 &assignment_](std::vector<Literal>& literals) {
111 int new_size = 0;
112 for (const Literal l : literals) {
113 // We can ignore always absent interval, and skip the literal of the
114 // interval that are now always present.
115 if (assignment->LiteralIsTrue(l)) continue;
116 if (assignment->LiteralIsFalse(l)) return false;
117 literals[new_size++] = l;
118 }
119 literals.resize(new_size);
120 return true;
121 };
122
123 if (sat_solver_->CurrentDecisionLevel() == 0) {
124 if (!remove_fixed(enforcement_literals)) return kNoLiteralIndex;
125 }
126
127 // task_a is currently before task_b ?
128 // Lets not create a literal that will be propagated right away.
129 if (integer_trail_->UpperBound(a.start) < integer_trail_->LowerBound(b.end)) {
130 if (sat_solver_->CurrentDecisionLevel() == 0) {
131 AddConditionalAffinePrecedence(enforcement_literals, a.end, b.start,
132 model_);
133 }
134 return kNoLiteralIndex;
135 }
136
137 // task_b is before task_a ?
138 if (integer_trail_->UpperBound(b.start) < integer_trail_->LowerBound(a.end)) {
139 if (sat_solver_->CurrentDecisionLevel() == 0) {
140 AddConditionalAffinePrecedence(enforcement_literals, b.end, a.start,
141 model_);
142 }
143 return kNoLiteralIndex;
144 }
145
146 // Create a new literal.
147 const BooleanVariable boolean_var = sat_solver_->NewBooleanVariable();
148 const Literal a_before_b = Literal(boolean_var, true);
149 disjunctive_precedences_.insert({{a, b}, a_before_b});
150 disjunctive_precedences_.insert({{b, a}, a_before_b.Negated()});
151
152 // Also insert it in precedences.
153 if (enforcement_literals.empty()) {
154 // TODO(user): also add the reverse like start_b + 1 <= end_a if negated?
155 precedences_.insert({{a.end, b.start}, a_before_b});
156 precedences_.insert({{b.end, a.start}, a_before_b.Negated()});
157 }
158
159 enforcement_literals.push_back(a_before_b);
160 AddConditionalAffinePrecedence(enforcement_literals, a.end, b.start, model_);
161 enforcement_literals.pop_back();
162
163 enforcement_literals.push_back(a_before_b.Negated());
164 AddConditionalAffinePrecedence(enforcement_literals, b.end, a.start, model_);
165 enforcement_literals.pop_back();
166
167 // The calls to AddConditionalAffinePrecedence() might have fixed some of the
168 // enforcement literals. Remove them if we are at level zero.
169 if (sat_solver_->CurrentDecisionLevel() == 0) {
170 if (!remove_fixed(enforcement_literals)) return kNoLiteralIndex;
171 }
172
173 // Force the value of boolean_var in case the precedence is not active. This
174 // avoids duplicate solutions when enumerating all possible solutions.
175 for (const Literal l : enforcement_literals) {
176 implications_->AddBinaryClause(l, a_before_b);
177 }
178
179 return a_before_b;
180}
181
184 if (precedences_.contains({x, y})) return false;
185
186 // We want l => x <= y and not(l) => x > y <=> y + 1 <= x
187 // Do not create l if the relation is always true or false.
188 if (integer_trail_->UpperBound(x) <= integer_trail_->LowerBound(y)) {
189 return false;
190 }
191 if (integer_trail_->LowerBound(x) > integer_trail_->UpperBound(y)) {
192 return false;
193 }
194
195 // Create a new literal.
196 const BooleanVariable boolean_var = sat_solver_->NewBooleanVariable();
197 const Literal x_before_y = Literal(boolean_var, true);
198
199 // TODO(user): Also add {{y_plus_one, x}, x_before_y.Negated()} ?
200 precedences_.insert({{x, y}, x_before_y});
201
202 AffineExpression y_plus_one = y;
203 y_plus_one.constant += 1;
204 AddConditionalAffinePrecedence({x_before_y}, x, y, model_);
205 AddConditionalAffinePrecedence({x_before_y.Negated()}, y_plus_one, x, model_);
206 return true;
207}
208
211 const auto it = precedences_.find({x, y});
212 if (it != precedences_.end()) return it->second.Index();
213 return kNoLiteralIndex;
214}
215
216// TODO(user): Ideally we should sort the vector of variables, but right now
217// we cannot since we often use this with a parallel vector of demands. So this
218// "sorting" should happen in the presolver so we can share as much as possible.
220 const std::vector<IntervalVariable>& variables,
221 bool register_as_disjunctive_helper) {
222 const auto it = helper_repository_.find(variables);
223 if (it != helper_repository_.end()) return it->second;
224 std::vector<AffineExpression> starts;
225 std::vector<AffineExpression> ends;
226 std::vector<AffineExpression> sizes;
227 std::vector<LiteralIndex> reason_for_presence;
228
229 const int num_variables = variables.size();
230 starts.reserve(num_variables);
231 ends.reserve(num_variables);
232 sizes.reserve(num_variables);
233 reason_for_presence.reserve(num_variables);
234
235 for (const IntervalVariable i : variables) {
236 if (IsOptional(i)) {
237 reason_for_presence.push_back(PresenceLiteral(i).Index());
238 } else {
239 reason_for_presence.push_back(kNoLiteralIndex);
240 }
241 sizes.push_back(Size(i));
242 starts.push_back(Start(i));
243 ends.push_back(End(i));
244 }
245
247 std::move(starts), std::move(ends), std::move(sizes),
248 std::move(reason_for_presence), model_);
249 helper->RegisterWith(model_->GetOrCreate<GenericLiteralWatcher>());
250 helper_repository_[variables] = helper;
251 model_->TakeOwnership(helper);
252 if (register_as_disjunctive_helper) {
253 disjunctive_helpers_.push_back(helper);
254 }
255 return helper;
256}
257
259 const std::vector<IntervalVariable>& x_variables,
260 const std::vector<IntervalVariable>& y_variables) {
261 const auto it =
262 no_overlap_2d_helper_repository_.find({x_variables, y_variables});
263 if (it != no_overlap_2d_helper_repository_.end()) return it->second;
264
265 std::vector<AffineExpression> x_starts;
266 std::vector<AffineExpression> x_ends;
267 std::vector<AffineExpression> x_sizes;
268 std::vector<LiteralIndex> x_reason_for_presence;
269
270 for (const IntervalVariable i : x_variables) {
271 if (IsOptional(i)) {
272 x_reason_for_presence.push_back(PresenceLiteral(i).Index());
273 } else {
274 x_reason_for_presence.push_back(kNoLiteralIndex);
275 }
276 x_sizes.push_back(Size(i));
277 x_starts.push_back(Start(i));
278 x_ends.push_back(End(i));
279 }
280 std::vector<AffineExpression> y_starts;
281 std::vector<AffineExpression> y_ends;
282 std::vector<AffineExpression> y_sizes;
283 std::vector<LiteralIndex> y_reason_for_presence;
284
285 for (const IntervalVariable i : y_variables) {
286 if (IsOptional(i)) {
287 y_reason_for_presence.push_back(PresenceLiteral(i).Index());
288 } else {
289 y_reason_for_presence.push_back(kNoLiteralIndex);
290 }
291 y_sizes.push_back(Size(i));
292 y_starts.push_back(Start(i));
293 y_ends.push_back(End(i));
294 }
296 std::move(x_starts), std::move(x_ends), std::move(x_sizes),
297 std::move(x_reason_for_presence), std::move(y_starts), std::move(y_ends),
298 std::move(y_sizes), std::move(y_reason_for_presence), model_);
299 helper->RegisterWith(model_->GetOrCreate<GenericLiteralWatcher>());
300 no_overlap_2d_helper_repository_[{x_variables, y_variables}] = helper;
301 model_->TakeOwnership(helper);
302 return helper;
303}
304
307 absl::Span<const AffineExpression> demands) {
308 const std::pair<SchedulingConstraintHelper*, std::vector<AffineExpression>>
309 key = {helper,
310 std::vector<AffineExpression>(demands.begin(), demands.end())};
311 const auto it = demand_helper_repository_.find(key);
312 if (it != demand_helper_repository_.end()) return it->second;
313
314 SchedulingDemandHelper* demand_helper =
315 new SchedulingDemandHelper(demands, helper, model_);
316 model_->TakeOwnership(demand_helper);
317 demand_helper_repository_[key] = demand_helper;
318 return demand_helper;
319}
320
322 for (const auto& it : demand_helper_repository_) {
323 it.second->InitDecomposedEnergies();
324 }
325}
326
327} // namespace sat
328} // namespace operations_research
SchedulingDemandHelper * GetOrCreateDemandHelper(SchedulingConstraintHelper *helper, absl::Span< const AffineExpression > demands)
Definition intervals.cc:305
AffineExpression Start(IntervalVariable i) const
Definition intervals.h:98
AffineExpression Size(IntervalVariable i) const
Definition intervals.h:97
AffineExpression End(IntervalVariable i) const
Definition intervals.h:99
LiteralIndex GetPrecedenceLiteral(AffineExpression x, AffineExpression y) const
Definition intervals.cc:209
LiteralIndex GetOrCreateDisjunctivePrecedenceLiteral(const IntervalDefinition &a, const IntervalDefinition &b)
Definition intervals.cc:96
void InitAllDecomposedEnergies()
Calls InitDecomposedEnergies on all SchedulingDemandHelper created.
Definition intervals.cc:321
Literal PresenceLiteral(IntervalVariable i) const
Definition intervals.h:78
void CreateDisjunctivePrecedenceLiteral(IntervalVariable a, IntervalVariable b)
Definition intervals.cc:79
bool IsOptional(IntervalVariable i) const
Returns whether or not a interval is optional and the associated literal.
Definition intervals.h:75
IntervalVariable CreateInterval(IntegerVariable start, IntegerVariable end, IntegerVariable size, IntegerValue fixed_size, LiteralIndex is_present)
Definition intervals.cc:38
SchedulingConstraintHelper * GetOrCreateHelper(const std::vector< IntervalVariable > &variables, bool register_as_disjunctive_helper=false)
Definition intervals.cc:219
NoOverlap2DConstraintHelper * GetOrCreate2DHelper(const std::vector< IntervalVariable > &x_variables, const std::vector< IntervalVariable > &y_variables)
Definition intervals.cc:258
bool CreatePrecedenceLiteral(AffineExpression x, AffineExpression y)
Definition intervals.cc:182
void AddTerm(IntegerVariable var, IntegerValue coeff)
const LiteralIndex kNoLiteralIndex(-1)
void LoadConditionalLinearConstraint(const absl::Span< const Literal > enforcement_literals, const LinearConstraint &cst, Model *model)
LinearConstraint version.
const IntegerVariable kNoIntegerVariable(-1)
void AddConditionalAffinePrecedence(const absl::Span< const Literal > enforcement_literals, AffineExpression left, AffineExpression right, Model *model)
In SWIG mode, we don't want anything besides these top-level includes.