Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
linear_relaxation.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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 <algorithm>
17#include <cstdint>
18#include <limits>
19#include <numeric>
20#include <optional>
21#include <utility>
22#include <vector>
23
24#include "absl/base/attributes.h"
25#include "absl/container/btree_map.h"
26#include "absl/container/flat_hash_map.h"
27#include "absl/container/flat_hash_set.h"
28#include "absl/log/check.h"
29#include "absl/meta/type_traits.h"
30#include "absl/types/span.h"
31#include "google/protobuf/message.h"
35#include "ortools/sat/circuit.h" // for ReindexArcs.
36#include "ortools/sat/clause.h"
37#include "ortools/sat/cp_model.pb.h"
40#include "ortools/sat/cuts.h"
43#include "ortools/sat/integer.h"
47#include "ortools/sat/model.h"
52#include "ortools/sat/sat_parameters.pb.h"
55#include "ortools/sat/util.h"
60
61namespace operations_research {
62namespace sat {
63
64namespace {
65
66std::pair<IntegerValue, IntegerValue> GetMinAndMaxNotEncoded(
67 IntegerVariable var,
68 const absl::flat_hash_set<IntegerValue>& encoded_values,
69 const Model& model) {
70 CHECK(VariableIsPositive(var));
71 const PositiveOnlyIndex index = GetPositiveOnlyIndex(var);
72
73 const auto* domains = model.Get<IntegerDomains>();
74 if (domains == nullptr || index >= domains->size()) {
76 }
77
78 // The domain can be large, but the list of values shouldn't, so this
79 // runs in O(encoded_values.size());
80 IntegerValue min = kMaxIntegerValue;
81 for (const int64_t v : (*domains)[index].Values()) {
82 if (!encoded_values.contains(IntegerValue(v))) {
83 min = IntegerValue(v);
84 break;
85 }
86 }
87
88 IntegerValue max = kMinIntegerValue;
89 const Domain negated_domain = (*domains)[index].Negation();
90 for (const int64_t v : negated_domain.Values()) {
91 if (!encoded_values.contains(IntegerValue(-v))) {
92 max = IntegerValue(-v);
93 break;
94 }
95 }
96
97 return {min, max};
98}
99
100// Collect all the affines expressions in a LinMax constraint.
101// It checks that these are indeed affine expressions, and that they all share
102// the same variable.
103// It returns the shared variable, as well as a vector of pairs
104// (coefficient, offset) when each affine is coefficient * shared_var + offset.
105void CollectAffineExpressionWithSingleVariable(
106 const ConstraintProto& ct, CpModelMapping* mapping, IntegerVariable* var,
107 std::vector<std::pair<IntegerValue, IntegerValue>>* affines) {
108 DCHECK(ExpressionsContainsOnlyOneVar(ct.lin_max().exprs()));
109 CHECK_EQ(ct.constraint_case(), ConstraintProto::ConstraintCase::kLinMax);
111 affines->clear();
112 for (const LinearExpressionProto& expr : ct.lin_max().exprs()) {
113 if (expr.vars().empty()) {
114 affines->push_back({IntegerValue(0), IntegerValue(expr.offset())});
115 } else {
116 CHECK_EQ(expr.vars().size(), 1);
117 const IntegerVariable affine_var = mapping->Integer(expr.vars(0));
118 if (*var == kNoIntegerVariable) {
119 *var = PositiveVariable(affine_var);
120 }
121 if (VariableIsPositive(affine_var)) {
122 CHECK_EQ(affine_var, *var);
123 affines->push_back(
124 {IntegerValue(expr.coeffs(0)), IntegerValue(expr.offset())});
125 } else {
126 CHECK_EQ(NegationOf(affine_var), *var);
127 affines->push_back(
128 {IntegerValue(-expr.coeffs(0)), IntegerValue(expr.offset())});
129 }
130 }
131 }
132}
133
134} // namespace
135
137 const Model& model,
138 LinearRelaxation* relaxation,
139 int* num_tight, int* num_loose) {
140 const auto* encoder = model.Get<IntegerEncoder>();
141 const auto* integer_trail = model.Get<IntegerTrail>();
142 if (encoder == nullptr || integer_trail == nullptr) return;
143
144 std::vector<Literal> at_most_one_ct;
145 absl::flat_hash_set<IntegerValue> encoded_values;
146 std::vector<ValueLiteralPair> encoding;
147 {
148 const std::vector<ValueLiteralPair>& initial_encoding =
149 encoder->PartialDomainEncoding(var);
150 if (initial_encoding.empty()) return;
151 for (const auto value_literal : initial_encoding) {
152 const Literal literal = value_literal.literal;
153
154 // Note that we skip pairs that do not have an Integer view.
155 if (encoder->GetLiteralView(literal) == kNoIntegerVariable &&
156 encoder->GetLiteralView(literal.Negated()) == kNoIntegerVariable) {
157 continue;
158 }
159
160 encoding.push_back(value_literal);
161 at_most_one_ct.push_back(literal);
162 encoded_values.insert(value_literal.value);
163 }
164 }
165 if (encoded_values.empty()) return;
166
167 // TODO(user): PartialDomainEncoding() filter pair corresponding to literal
168 // set to false, however the initial variable Domain is not always updated. As
169 // a result, these min/max can be larger than in reality. Try to fix this even
170 // if in practice this is a rare occurrence, as the presolve should have
171 // propagated most of what we can.
172 const auto [min_not_encoded, max_not_encoded] =
173 GetMinAndMaxNotEncoded(var, encoded_values, model);
174
175 // This means that there are no non-encoded value and we have a full encoding.
176 // We substract the minimum value to reduce its size.
177 if (min_not_encoded == kMaxIntegerValue) {
178 const IntegerValue rhs = encoding[0].value;
179 LinearConstraintBuilder at_least_one(&model, IntegerValue(1),
181 LinearConstraintBuilder encoding_ct(&model, rhs, rhs);
182 encoding_ct.AddTerm(var, IntegerValue(1));
183 for (const auto value_literal : encoding) {
184 const Literal lit = value_literal.literal;
185 CHECK(at_least_one.AddLiteralTerm(lit, IntegerValue(1)));
186
187 const IntegerValue delta = value_literal.value - rhs;
188 if (delta != IntegerValue(0)) {
189 CHECK_GE(delta, IntegerValue(0));
190 CHECK(encoding_ct.AddLiteralTerm(lit, -delta));
191 }
192 }
193
194 // It is possible that the linear1 encoding respect our overflow
195 // precondition but not the Var = sum bool * value one. In this case, we
196 // just don't encode it this way. Hopefully, most normal model will not run
197 // into this.
198 LinearConstraint lc = encoding_ct.Build();
199 if (!PossibleOverflow(*integer_trail, lc)) {
200 relaxation->linear_constraints.push_back(at_least_one.Build());
201 relaxation->linear_constraints.push_back(std::move(lc));
202 relaxation->at_most_ones.push_back(at_most_one_ct);
203 ++*num_tight;
204 }
205 return;
206 }
207
208 // In this special case, the two constraints below can be merged into an
209 // equality: var = rhs + sum l_i * (value_i - rhs).
210 if (min_not_encoded == max_not_encoded) {
211 const IntegerValue rhs = min_not_encoded;
212 LinearConstraintBuilder encoding_ct(&model, rhs, rhs);
213 encoding_ct.AddTerm(var, IntegerValue(1));
214 for (const auto value_literal : encoding) {
215 CHECK(encoding_ct.AddLiteralTerm(value_literal.literal,
216 rhs - value_literal.value));
217 }
218
219 LinearConstraint lc = encoding_ct.Build();
220 if (!PossibleOverflow(*integer_trail, lc)) {
221 relaxation->at_most_ones.push_back(at_most_one_ct);
222 relaxation->linear_constraints.push_back(std::move(lc));
223 ++*num_tight;
224 }
225 return;
226 }
227
228 // min + sum l_i * (value_i - min) <= var.
229 // Note that this might overflow in corner cases, so we need to prevent that.
230 const IntegerValue d_min = min_not_encoded;
231 LinearConstraintBuilder lower_bound_ct(&model, d_min, kMaxIntegerValue);
232 lower_bound_ct.AddTerm(var, IntegerValue(1));
233 for (const auto value_literal : encoding) {
234 CHECK(lower_bound_ct.AddLiteralTerm(value_literal.literal,
235 d_min - value_literal.value));
236 }
237 LinearConstraint built_lb_ct = lower_bound_ct.Build();
238 if (!PossibleOverflow(*integer_trail, built_lb_ct)) {
239 relaxation->linear_constraints.push_back(std::move(built_lb_ct));
240 ++*num_loose;
241 }
242
243 // var <= max + sum l_i * (value_i - max).
244 // Note that this might overflow in corner cases, so we need to prevent that.
245 const IntegerValue d_max = max_not_encoded;
246 LinearConstraintBuilder upper_bound_ct(&model, kMinIntegerValue, d_max);
247 upper_bound_ct.AddTerm(var, IntegerValue(1));
248 for (const auto value_literal : encoding) {
249 CHECK(upper_bound_ct.AddLiteralTerm(value_literal.literal,
250 d_max - value_literal.value));
251 }
252 LinearConstraint built_ub_ct = upper_bound_ct.Build();
253 if (!PossibleOverflow(*integer_trail, built_ub_ct)) {
254 relaxation->linear_constraints.push_back(std::move(built_ub_ct));
255 ++*num_loose;
256 }
257
258 // Note that empty/trivial constraints will be filtered later.
259 relaxation->at_most_ones.push_back(at_most_one_ct);
260}
261
263 const Model& model,
264 LinearRelaxation* relaxation) {
265 const auto* integer_trail = model.Get<IntegerTrail>();
266 const auto* encoder = model.Get<IntegerEncoder>();
267 if (integer_trail == nullptr || encoder == nullptr) return;
268
269 const auto& greater_than_encoding = encoder->PartialGreaterThanEncoding(var);
270 if (greater_than_encoding.empty()) return;
271
272 // Start by the var >= side.
273 // And also add the implications between used literals.
274 {
275 IntegerValue prev_used_bound = integer_trail->LowerBound(var);
276 LinearConstraintBuilder builder(&model, prev_used_bound, kMaxIntegerValue);
277 builder.AddTerm(var, IntegerValue(1));
278 LiteralIndex prev_literal_index = kNoLiteralIndex;
279 for (const auto entry : greater_than_encoding) {
280 if (entry.value <= prev_used_bound) continue;
281
282 const LiteralIndex literal_index = entry.literal.Index();
283 const IntegerValue diff = prev_used_bound - entry.value;
284
285 // Skip the entry if the literal doesn't have a view.
286 if (!builder.AddLiteralTerm(entry.literal, diff)) continue;
287 if (prev_literal_index != kNoLiteralIndex) {
288 // Add var <= prev_var, which is the same as var + not(prev_var) <= 1
289 relaxation->at_most_ones.push_back(
290 {Literal(literal_index), Literal(prev_literal_index).Negated()});
291 }
292 prev_used_bound = entry.value;
293 prev_literal_index = literal_index;
294 }
295
296 // Note that by construction, this shouldn't be able to overflow.
297 relaxation->linear_constraints.push_back(builder.Build());
298 }
299
300 // Do the same for the var <= side by using NegationOfVar().
301 // Note that we do not need to add the implications between literals again.
302 {
303 IntegerValue prev_used_bound = integer_trail->LowerBound(NegationOf(var));
304 LinearConstraintBuilder builder(&model, prev_used_bound, kMaxIntegerValue);
305 builder.AddTerm(var, IntegerValue(-1));
306 for (const auto entry :
307 encoder->PartialGreaterThanEncoding(NegationOf(var))) {
308 if (entry.value <= prev_used_bound) continue;
309 const IntegerValue diff = prev_used_bound - entry.value;
310
311 // Skip the entry if the literal doesn't have a view.
312 if (!builder.AddLiteralTerm(entry.literal, diff)) continue;
313 prev_used_bound = entry.value;
314 }
315
316 // Note that by construction, this shouldn't be able to overflow.
317 relaxation->linear_constraints.push_back(builder.Build());
318 }
319}
320
321namespace {
322
323bool AllLiteralsHaveViews(const IntegerEncoder& encoder,
324 absl::Span<const Literal> literals) {
325 for (const Literal lit : literals) {
326 if (!encoder.LiteralOrNegationHasView(lit)) return false;
327 }
328 return true;
329}
330
331} // namespace
332
333void AppendBoolOrRelaxation(const ConstraintProto& ct, Model* model,
334 LinearRelaxation* relaxation) {
335 auto* mapping = model->GetOrCreate<CpModelMapping>();
336 LinearConstraintBuilder lc(model, IntegerValue(1), kMaxIntegerValue);
337 for (const int enforcement_ref : ct.enforcement_literal()) {
338 CHECK(lc.AddLiteralTerm(mapping->Literal(NegatedRef(enforcement_ref)),
339 IntegerValue(1)));
340 }
341 for (const int ref : ct.bool_or().literals()) {
342 CHECK(lc.AddLiteralTerm(mapping->Literal(ref), IntegerValue(1)));
343 }
344 relaxation->linear_constraints.push_back(lc.Build());
345}
346
347void AppendBoolAndRelaxation(const ConstraintProto& ct, Model* model,
348 LinearRelaxation* relaxation,
349 ActivityBoundHelper* activity_helper) {
350 if (!HasEnforcementLiteral(ct)) return;
351
352 // TODO(user): These constraints can be many, and if they are not regrouped
353 // in big at most ones, then they should probably only added lazily as cuts.
354 // Regroup this with future clique-cut separation logic.
355 //
356 // Note that for the case with only one enforcement, what we do below is
357 // already done by the clique merging code.
358 auto* mapping = model->GetOrCreate<CpModelMapping>();
359 if (ct.enforcement_literal().size() == 1) {
360 const Literal enforcement = mapping->Literal(ct.enforcement_literal(0));
361 for (const int ref : ct.bool_and().literals()) {
362 relaxation->at_most_ones.push_back(
363 {enforcement, mapping->Literal(ref).Negated()});
364 }
365 return;
366 }
367
368 // If we have many_literals => many_fixed literal, it is important to
369 // try to use a tight big-M if we can. This is important on neos-957323.pb.gz
370 // for instance.
371 //
372 // We split the literal into disjoint AMO and we encode each with
373 // sum Not(literals) <= sum Not(enforcement)
374 //
375 // Note that what we actually do is use the decomposition into at most one
376 // and add a constraint for each part rather than just adding the sum of them.
377 //
378 // TODO(user): More generally, do not miss the same structure if the bool_and
379 // was expanded into many clauses!
380 //
381 // TODO(user): It is not 100% clear that just not adding one constraint is
382 // worse. Relaxation is worse, but then we have less constraint.
384 if (activity_helper != nullptr) {
385 std::vector<int> negated_lits;
386 for (const int ref : ct.bool_and().literals()) {
387 negated_lits.push_back(NegatedRef(ref));
388 }
389 for (absl::Span<const int> part :
390 activity_helper->PartitionLiteralsIntoAmo(negated_lits)) {
391 builder.Clear();
392 for (const int negated_ref : part) {
393 CHECK(builder.AddLiteralTerm(mapping->Literal(negated_ref)));
394 }
395 for (const int enforcement_ref : ct.enforcement_literal()) {
396 CHECK(builder.AddLiteralTerm(
397 mapping->Literal(NegatedRef(enforcement_ref)), IntegerValue(-1)));
398 }
399 relaxation->linear_constraints.push_back(
400 builder.BuildConstraint(kMinIntegerValue, IntegerValue(0)));
401 }
402 } else {
403 for (const int ref : ct.bool_and().literals()) {
404 builder.Clear();
405 CHECK(builder.AddLiteralTerm(mapping->Literal(NegatedRef(ref))));
406 for (const int enforcement_ref : ct.enforcement_literal()) {
407 CHECK(builder.AddLiteralTerm(
408 mapping->Literal(NegatedRef(enforcement_ref)), IntegerValue(-1)));
409 }
410 relaxation->linear_constraints.push_back(
411 builder.BuildConstraint(kMinIntegerValue, IntegerValue(0)));
412 }
413 }
414}
415
416void AppendAtMostOneRelaxation(const ConstraintProto& ct, Model* model,
417 LinearRelaxation* relaxation) {
418 if (HasEnforcementLiteral(ct)) return;
419
420 auto* mapping = model->GetOrCreate<CpModelMapping>();
421 relaxation->at_most_ones.push_back(
422 mapping->Literals(ct.at_most_one().literals()));
423}
424
425void AppendExactlyOneRelaxation(const ConstraintProto& ct, Model* model,
426 LinearRelaxation* relaxation) {
427 if (HasEnforcementLiteral(ct)) return;
428 auto* mapping = model->GetOrCreate<CpModelMapping>();
429 auto* encoder = model->GetOrCreate<IntegerEncoder>();
430
431 const std::vector<Literal> literals =
432 mapping->Literals(ct.exactly_one().literals());
433 if (AllLiteralsHaveViews(*encoder, literals)) {
434 LinearConstraintBuilder lc(model, IntegerValue(1), IntegerValue(1));
435 for (const Literal lit : literals) {
436 CHECK(lc.AddLiteralTerm(lit, IntegerValue(1)));
437 }
438 relaxation->linear_constraints.push_back(lc.Build());
439 } else {
440 // We just encode the at most one part that might be partially linearized
441 // later.
442 relaxation->at_most_ones.push_back(literals);
443 }
444}
445
447 int num_literals, Model* model, LinearRelaxation* relaxation) {
448 auto* encoder = model->GetOrCreate<IntegerEncoder>();
449
450 if (num_literals == 1) {
451 // This is not supposed to happen, but it is easy enough to cover, just
452 // in case. We might however want to use encoder->GetTrueLiteral().
453 const IntegerVariable var = model->Add(NewIntegerVariable(1, 1));
454 const Literal lit =
455 encoder->GetOrCreateLiteralAssociatedToEquality(var, IntegerValue(1));
456 return {lit};
457 }
458
459 if (num_literals == 2) {
460 const IntegerVariable var = model->Add(NewIntegerVariable(0, 1));
461 const Literal lit =
462 encoder->GetOrCreateLiteralAssociatedToEquality(var, IntegerValue(1));
463
464 // TODO(user): We shouldn't need to create this view ideally. Even better,
465 // we should be able to handle Literal natively in the linear relaxation,
466 // but that is a lot of work.
467 const IntegerVariable var2 = model->Add(NewIntegerVariable(0, 1));
468 encoder->AssociateToIntegerEqualValue(lit.Negated(), var2, IntegerValue(1));
469
470 return {lit, lit.Negated()};
471 }
472
473 std::vector<Literal> literals;
474 LinearConstraintBuilder lc_builder(model, IntegerValue(1), IntegerValue(1));
475 for (int i = 0; i < num_literals; ++i) {
476 const IntegerVariable var = model->Add(NewIntegerVariable(0, 1));
477 const Literal lit =
478 encoder->GetOrCreateLiteralAssociatedToEquality(var, IntegerValue(1));
479 literals.push_back(lit);
480 CHECK(lc_builder.AddLiteralTerm(lit, IntegerValue(1)));
481 }
482 model->Add(ExactlyOneConstraint(literals));
483 relaxation->linear_constraints.push_back(lc_builder.Build());
484 return literals;
485}
486
487void AppendCircuitRelaxation(const ConstraintProto& ct, Model* model,
488 LinearRelaxation* relaxation) {
489 if (HasEnforcementLiteral(ct)) return;
490 auto* mapping = model->GetOrCreate<CpModelMapping>();
491 const int num_arcs = ct.circuit().literals_size();
492 CHECK_EQ(num_arcs, ct.circuit().tails_size());
493 CHECK_EQ(num_arcs, ct.circuit().heads_size());
494
495 // Each node must have exactly one incoming and one outgoing arc (note
496 // that it can be the unique self-arc of this node too).
497 absl::btree_map<int, std::vector<Literal>> incoming_arc_constraints;
498 absl::btree_map<int, std::vector<Literal>> outgoing_arc_constraints;
499 for (int i = 0; i < num_arcs; i++) {
500 const Literal arc = mapping->Literal(ct.circuit().literals(i));
501 const int tail = ct.circuit().tails(i);
502 const int head = ct.circuit().heads(i);
503 outgoing_arc_constraints[tail].push_back(arc);
504 incoming_arc_constraints[head].push_back(arc);
505 }
506 for (const auto* node_map :
507 {&outgoing_arc_constraints, &incoming_arc_constraints}) {
508 for (const auto& entry : *node_map) {
509 const std::vector<Literal>& exactly_one = entry.second;
510 if (exactly_one.size() > 1) {
511 LinearConstraintBuilder at_least_one_lc(model, IntegerValue(1),
513 for (const Literal l : exactly_one) {
514 CHECK(at_least_one_lc.AddLiteralTerm(l, IntegerValue(1)));
515 }
516
517 // We separate the two constraints.
518 relaxation->at_most_ones.push_back(exactly_one);
519 relaxation->linear_constraints.push_back(at_least_one_lc.Build());
520 }
521 }
522 }
523}
524
525void AppendRoutesRelaxation(const ConstraintProto& ct, Model* model,
526 LinearRelaxation* relaxation) {
527 if (HasEnforcementLiteral(ct)) return;
528 auto* mapping = model->GetOrCreate<CpModelMapping>();
529 const int num_arcs = ct.routes().literals_size();
530 CHECK_EQ(num_arcs, ct.routes().tails_size());
531 CHECK_EQ(num_arcs, ct.routes().heads_size());
532
533 // Each node except node zero must have exactly one incoming and one outgoing
534 // arc (note that it can be the unique self-arc of this node too). For node
535 // zero, the number of incoming arcs should be the same as the number of
536 // outgoing arcs.
537 absl::btree_map<int, std::vector<Literal>> incoming_arc_constraints;
538 absl::btree_map<int, std::vector<Literal>> outgoing_arc_constraints;
539 for (int i = 0; i < num_arcs; i++) {
540 const Literal arc = mapping->Literal(ct.routes().literals(i));
541 const int tail = ct.routes().tails(i);
542 const int head = ct.routes().heads(i);
543 outgoing_arc_constraints[tail].push_back(arc);
544 incoming_arc_constraints[head].push_back(arc);
545 }
546 for (const auto* node_map :
547 {&outgoing_arc_constraints, &incoming_arc_constraints}) {
548 for (const auto& entry : *node_map) {
549 if (entry.first == 0) continue;
550 const std::vector<Literal>& exactly_one = entry.second;
551 if (exactly_one.size() > 1) {
552 LinearConstraintBuilder at_least_one_lc(model, IntegerValue(1),
554 for (const Literal l : exactly_one) {
555 CHECK(at_least_one_lc.AddLiteralTerm(l, IntegerValue(1)));
556 }
557
558 // We separate the two constraints.
559 relaxation->at_most_ones.push_back(exactly_one);
560 relaxation->linear_constraints.push_back(at_least_one_lc.Build());
561 }
562 }
563 }
564 LinearConstraintBuilder zero_node_balance_lc(model, IntegerValue(0),
565 IntegerValue(0));
566 for (const Literal& incoming_arc : incoming_arc_constraints[0]) {
567 CHECK(zero_node_balance_lc.AddLiteralTerm(incoming_arc, IntegerValue(1)));
568 }
569 for (const Literal& outgoing_arc : outgoing_arc_constraints[0]) {
570 CHECK(zero_node_balance_lc.AddLiteralTerm(outgoing_arc, IntegerValue(-1)));
571 }
572 relaxation->linear_constraints.push_back(zero_node_balance_lc.Build());
573}
574
575void AddCircuitCutGenerator(const ConstraintProto& ct, Model* m,
576 LinearRelaxation* relaxation) {
577 std::vector<int> tails(ct.circuit().tails().begin(),
578 ct.circuit().tails().end());
579 std::vector<int> heads(ct.circuit().heads().begin(),
580 ct.circuit().heads().end());
581 auto* mapping = m->GetOrCreate<CpModelMapping>();
582 std::vector<Literal> literals = mapping->Literals(ct.circuit().literals());
583 const int num_nodes = ReindexArcs(&tails, &heads);
584
586 num_nodes, tails, heads, literals, m));
587}
588
589void AddRoutesCutGenerator(const ConstraintProto& ct, Model* m,
590 LinearRelaxation* relaxation) {
591 std::vector<int> tails(ct.routes().tails().begin(),
592 ct.routes().tails().end());
593 std::vector<int> heads(ct.routes().heads().begin(),
594 ct.routes().heads().end());
595 auto* mapping = m->GetOrCreate<CpModelMapping>();
596 std::vector<Literal> literals = mapping->Literals(ct.routes().literals());
597
598 int num_nodes = 0;
599 for (int i = 0; i < ct.routes().tails_size(); ++i) {
600 num_nodes = std::max(num_nodes, 1 + ct.routes().tails(i));
601 num_nodes = std::max(num_nodes, 1 + ct.routes().heads(i));
602 }
603 if (ct.routes().demands().empty() || ct.routes().capacity() == 0) {
604 relaxation->cut_generators.push_back(
605 CreateStronglyConnectedGraphCutGenerator(num_nodes, tails, heads,
606 literals, m));
607 } else {
608 const std::vector<int64_t> demands(ct.routes().demands().begin(),
609 ct.routes().demands().end());
610 relaxation->cut_generators.push_back(CreateCVRPCutGenerator(
611 num_nodes, tails, heads, literals, demands, ct.routes().capacity(), m));
612 }
613}
614
615// Scan the intervals of a cumulative/no_overlap constraint, and its capacity (1
616// for the no_overlap). It returns the index of the makespan interval if found,
617// or std::nullopt otherwise.
618//
619// Currently, this requires the capacity to be fixed in order to scan for a
620// makespan interval.
621//
622// The makespan interval has the following property:
623// - its end is fixed at the horizon
624// - it is always present
625// - its demand is the capacity of the cumulative/no_overlap.
626// - its size is > 0.
627//
628// These property ensures that all other intervals ends before the start of
629// the makespan interval.
630std::optional<int> DetectMakespan(
631 const std::vector<IntervalVariable>& intervals,
632 const std::vector<AffineExpression>& demands,
633 const AffineExpression& capacity, Model* model) {
634 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
635 IntervalsRepository* repository = model->GetOrCreate<IntervalsRepository>();
636
637 // TODO(user): Supports variable capacity.
638 if (!integer_trail->IsFixed(capacity)) {
639 return std::nullopt;
640 }
641
642 // Detect the horizon (max of all end max of all intervals).
643 IntegerValue horizon = kMinIntegerValue;
644 for (int i = 0; i < intervals.size(); ++i) {
645 if (repository->IsAbsent(intervals[i])) continue;
646 horizon = std::max(
647 horizon, integer_trail->UpperBound(repository->End(intervals[i])));
648 }
649
650 const IntegerValue capacity_value = integer_trail->FixedValue(capacity);
651 for (int i = 0; i < intervals.size(); ++i) {
652 if (!repository->IsPresent(intervals[i])) continue;
653 const AffineExpression& end = repository->End(intervals[i]);
654 if (integer_trail->IsFixed(demands[i]) &&
655 integer_trail->FixedValue(demands[i]) == capacity_value &&
656 integer_trail->IsFixed(end) &&
657 integer_trail->FixedValue(end) == horizon &&
658 integer_trail->LowerBound(repository->Size(intervals[i])) > 0) {
659 return i;
660 }
661 }
662 return std::nullopt;
663}
664
665namespace {
666
667std::optional<AffineExpression> DetectMakespanFromPrecedences(
668 const SchedulingConstraintHelper& helper, Model* model) {
669 if (helper.NumTasks() == 0) return {};
670
671 const absl::Span<const AffineExpression> ends = helper.Ends();
672 std::vector<IntegerVariable> end_vars;
673 for (const AffineExpression& end : ends) {
674 // TODO(user): Deal with constant end.
675 if (end.var == kNoIntegerVariable) return {};
676 if (end.coeff != 1) return {};
677 end_vars.push_back(end.var);
678 }
679
680 std::vector<FullIntegerPrecedence> output;
681 auto* precedences = model->GetOrCreate<PrecedenceRelations>();
682 precedences->ComputeFullPrecedences(end_vars, &output);
683 for (const auto& p : output) {
684 // TODO(user): What if we have more than one candidate makespan ?
685 if (p.indices.size() != ends.size()) continue;
686
687 // We have a Makespan!
688 // We want p.var - min_delta >= max(end).
689 IntegerValue min_delta = kMaxIntegerValue;
690 for (int i = 0; i < p.indices.size(); ++i) {
691 // end_vars[indices[i]] + p.offset[i] <= p.var
692 // [interval_end - end_offset][indices[i]] + p.offset[i] <= p.var
693 //
694 // TODO(user): It is possible this min_delta becomes positive but for a
695 // real makespan, we could make sure it is <= 0. This can happen if we
696 // have an optional interval because we didn't compute the offset assuming
697 // this interval was present. We could potentially fix it if we know that
698 // presence_literal => p.var >= end.
699 min_delta =
700 std::min(min_delta, p.offsets[i] - ends[p.indices[i]].constant);
701 }
702 VLOG(2) << "Makespan detected >= ends + " << min_delta;
703 return AffineExpression(p.var, IntegerValue(1), -min_delta);
704 }
705
706 return {};
707}
708
709} // namespace
710
712 Model* model,
713 LinearRelaxation* relaxation) {
714 if (HasEnforcementLiteral(ct)) return;
715
716 auto* mapping = model->GetOrCreate<CpModelMapping>();
717 std::vector<IntervalVariable> intervals =
718 mapping->Intervals(ct.no_overlap().intervals());
719 const IntegerValue one(1);
720 std::vector<AffineExpression> demands(intervals.size(), one);
721 IntervalsRepository* repository = model->GetOrCreate<IntervalsRepository>();
722
723 std::optional<AffineExpression> makespan;
724 const std::optional<int> makespan_index =
725 DetectMakespan(intervals, demands, /*capacity=*/one, model);
726 if (makespan_index.has_value()) {
727 makespan = repository->Start(intervals[makespan_index.value()]);
728 demands.pop_back(); // the vector is filled with ones.
729 intervals.erase(intervals.begin() + makespan_index.value());
730 }
731
732 SchedulingConstraintHelper* helper = repository->GetOrCreateHelper(intervals);
733 if (!helper->SynchronizeAndSetTimeDirection(true)) return;
734 SchedulingDemandHelper* demands_helper =
735 repository->GetOrCreateDemandHelper(helper, demands);
736
737 if (!makespan.has_value()) {
738 makespan = DetectMakespanFromPrecedences(*helper, model);
739 }
740
741 AddCumulativeRelaxation(/*capacity=*/one, helper, demands_helper, makespan,
742 model, relaxation);
743 if (model->GetOrCreate<SatParameters>()->linearization_level() > 1) {
744 AddNoOverlapCutGenerator(helper, makespan, model, relaxation);
745 }
746}
747
749 Model* model,
750 LinearRelaxation* relaxation) {
751 if (HasEnforcementLiteral(ct)) return;
752 auto* mapping = model->GetOrCreate<CpModelMapping>();
753 std::vector<IntervalVariable> intervals =
754 mapping->Intervals(ct.cumulative().intervals());
755 std::vector<AffineExpression> demands =
756 mapping->Affines(ct.cumulative().demands());
757 const AffineExpression capacity = mapping->Affine(ct.cumulative().capacity());
758 const std::optional<int> makespan_index =
759 DetectMakespan(intervals, demands, capacity, model);
760 std::optional<AffineExpression> makespan;
761 IntervalsRepository* repository = model->GetOrCreate<IntervalsRepository>();
762 if (makespan_index.has_value()) {
763 // We remove the makespan data from the intervals the demands vector.
764 makespan = repository->Start(intervals[makespan_index.value()]);
765 demands.erase(demands.begin() + makespan_index.value());
766 intervals.erase(intervals.begin() + makespan_index.value());
767 }
768
769 // We try to linearize the energy of each task (size * demand).
770 SchedulingConstraintHelper* helper = repository->GetOrCreateHelper(intervals);
771 if (!helper->SynchronizeAndSetTimeDirection(true)) return;
772 SchedulingDemandHelper* demands_helper =
773 repository->GetOrCreateDemandHelper(helper, demands);
774
775 if (!makespan.has_value()) {
776 makespan = DetectMakespanFromPrecedences(*helper, model);
777 }
778
779 // We can now add the relaxation and the cut generators.
780 AddCumulativeRelaxation(capacity, helper, demands_helper, makespan, model,
781 relaxation);
782 if (model->GetOrCreate<SatParameters>()->linearization_level() > 1) {
783 AddCumulativeCutGenerator(capacity, helper, demands_helper, makespan, model,
784 relaxation);
785 }
786}
787
788// This relaxation will compute the bounding box of all tasks in the cumulative,
789// and add the constraint that the sum of energies of each task must fit in the
790// capacity * span area.
793 SchedulingDemandHelper* demands_helper,
794 const std::optional<AffineExpression>& makespan,
795 Model* model, LinearRelaxation* relaxation) {
796 const int num_intervals = helper->NumTasks();
797 demands_helper->CacheAllEnergyValues();
798
799 IntegerValue min_of_starts = kMaxIntegerValue;
800 IntegerValue max_of_ends = kMinIntegerValue;
801 int num_variable_energies = 0;
802 int num_optionals = 0;
803 int64_t sizes_gcd = 0;
804 int64_t demands_gcd = 0;
805 int64_t num_active_intervals = 0;
806 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
807 for (int index = 0; index < num_intervals; ++index) {
808 if (helper->IsAbsent(index)) continue;
809 if (helper->SizeMax(index) == 0 || demands_helper->DemandMax(index) == 0) {
810 continue;
811 }
812
813 if (helper->IsOptional(index)) {
814 if (demands_helper->EnergyMin(index) > 0) {
815 num_optionals++;
816 } else {
817 continue;
818 }
819 }
820
821 if (!helper->SizeIsFixed(index) || !demands_helper->DemandIsFixed(index)) {
822 num_variable_energies++;
823 }
824 if (sizes_gcd != 1) {
825 if (helper->SizeIsFixed(index)) {
826 sizes_gcd = std::gcd(helper->SizeMin(index).value(), sizes_gcd);
827 } else {
828 sizes_gcd = 1;
829 }
830 }
831 if (demands_gcd != 1) {
832 if (demands_helper->DemandIsFixed(index)) {
833 demands_gcd =
834 std::gcd(demands_helper->DemandMin(index).value(), demands_gcd);
835 } else {
836 demands_gcd = 1;
837 }
838 }
839 num_active_intervals++;
840 min_of_starts = std::min(min_of_starts, helper->StartMin(index));
841 max_of_ends = std::max(max_of_ends, helper->EndMax(index));
842 }
843
844 VLOG(2) << "Span [" << min_of_starts << ".." << max_of_ends << "] with "
845 << num_optionals << " optional intervals, and "
846 << num_variable_energies << " variable energy tasks out of "
847 << num_active_intervals << "/" << num_intervals << " intervals"
848 << (makespan.has_value() ? ", and 1 makespan" : "")
849 << " sizes_gcd: " << sizes_gcd << " demands_gcd: " << demands_gcd;
850
851 // There are no active intervals, no need to add the relaxation.
852 if (num_active_intervals == 0) return;
853
854 // If nothing is variable, and the coefficients cannot be reduced, the linear
855 // relaxation will already be enforced by the scheduling propagators.
856 if (num_variable_energies + num_optionals == 0 && sizes_gcd == 1 &&
857 demands_gcd == 1) {
858 return;
859 }
860
861 // Specialized case 1: sizes are fixed with a non 1 gcd and no makespan.
862 if (sizes_gcd != 1 && !makespan.has_value()) {
863 VLOG(2) << "Cumulative relaxation: sizes_gcd = " << sizes_gcd
864 << ", demands_gcd = " << demands_gcd
865 << ", no makespan, capacity is " << capacity.DebugString();
866 // We can simplify the capacity only if it is fixed.
867 // TODO(user): We could use (capacity / demands_gcd) * demands_gcd.
868 if (!integer_trail->IsFixed(capacity)) demands_gcd = 1;
869 LinearConstraintBuilder lc(model, kMinIntegerValue, IntegerValue(0));
870 for (int index = 0; index < num_intervals; ++index) {
871 if (helper->IsAbsent(index)) continue;
872 if (helper->SizeMax(index) == 0 ||
873 demands_helper->DemandMax(index) == 0) {
874 continue;
875 }
876
877 if (helper->IsOptional(index)) {
878 const IntegerValue energy_min = demands_helper->EnergyMin(index);
879 if (energy_min == 0) continue;
880 DCHECK_EQ(energy_min % (sizes_gcd * demands_gcd), 0);
881 if (!lc.AddLiteralTerm(helper->PresenceLiteral(index),
882 energy_min / sizes_gcd / demands_gcd)) {
883 return;
884 }
885 } else {
886 // Copy the decomposed energy.
887 std::vector<LiteralValueValue> product =
888 demands_helper->DecomposedEnergies()[index];
889 if (!product.empty()) {
890 // The energy is defined if the vector is not empty.
891 // Let's reduce the coefficients.
892 for (LiteralValueValue& entry : product) {
893 DCHECK_EQ(entry.left_value % sizes_gcd, 0);
894 entry.left_value /= sizes_gcd;
895 DCHECK_EQ(entry.right_value % demands_gcd, 0);
896 entry.right_value /= demands_gcd;
897 }
898 if (!lc.AddDecomposedProduct(product)) return;
899 } else {
900 // We know the size is fixed.
901 const IntegerValue local_size =
902 integer_trail->FixedValue(helper->Sizes()[index]);
903 DCHECK_EQ(local_size % sizes_gcd, 0);
904 if (demands_gcd == 1) {
905 lc.AddTerm(demands_helper->Demands()[index],
906 local_size / sizes_gcd);
907 } else {
908 const IntegerValue local_demand =
909 integer_trail->FixedValue(demands_helper->Demands()[index]);
910 DCHECK_EQ(local_demand % demands_gcd, 0);
911 lc.AddConstant(local_size * local_demand / sizes_gcd / demands_gcd);
912 }
913 }
914 }
915 }
916
917 // Add the available energy of the cumulative.
918 if (integer_trail->IsFixed(capacity)) {
919 const IntegerValue span = max_of_ends - min_of_starts;
920 const IntegerValue fixed_capacity = integer_trail->FixedValue(capacity);
921 lc.AddConstant(-FloorOfRatio(fixed_capacity.value(), demands_gcd) *
922 FloorOfRatio(span.value(), sizes_gcd));
923 } else {
924 DCHECK_EQ(demands_gcd, 1);
925 lc.AddTerm(capacity, -(max_of_ends - min_of_starts) / sizes_gcd);
926 }
927 relaxation->linear_constraints.push_back(lc.Build());
928 return;
929 }
930
931 // TODO(user): Implement demands_gcd != 1 && capacity is fixed.
932
933 LinearConstraintBuilder lc(model, kMinIntegerValue, IntegerValue(0));
934 for (int index = 0; index < num_intervals; ++index) {
935 if (helper->IsAbsent(index)) continue;
936 if (helper->IsOptional(index)) {
937 const IntegerValue energy_min = demands_helper->EnergyMin(index);
938 if (energy_min == 0) continue;
939 if (!lc.AddLiteralTerm(helper->PresenceLiteral(index), energy_min)) {
940 return;
941 }
942 } else {
943 const std::vector<LiteralValueValue>& product =
944 demands_helper->DecomposedEnergies()[index];
945 if (!product.empty()) {
946 // The energy is defined if the vector is not empty.
947 if (!lc.AddDecomposedProduct(product)) return;
948 } else {
949 // The energy is not a decomposed product, but it could still be
950 // constant or linear. If not, a McCormick relaxation will be
951 // introduced. AddQuadraticLowerBound() supports all cases.
952 lc.AddQuadraticLowerBound(helper->Sizes()[index],
953 demands_helper->Demands()[index],
954 integer_trail);
955 }
956 }
957 }
958
959 // Create and link span_start and span_end to the starts and ends of the
960 // tasks.
961 //
962 // TODO(user): In some cases, we could have only one task that can be
963 // first.
964 const AffineExpression span_start = min_of_starts;
965 const AffineExpression span_end =
966 makespan.has_value() ? makespan.value() : max_of_ends;
967 lc.AddTerm(span_end, -integer_trail->UpperBound(capacity));
968 lc.AddTerm(span_start, integer_trail->UpperBound(capacity));
969 relaxation->linear_constraints.push_back(lc.Build());
970}
971
972// Adds the energetic relaxation sum(areas) <= bounding box area.
973void AppendNoOverlap2dRelaxation(const ConstraintProto& ct, Model* model,
974 LinearRelaxation* relaxation) {
975 CHECK(ct.has_no_overlap_2d());
976 if (HasEnforcementLiteral(ct)) return;
977
978 auto* mapping = model->GetOrCreate<CpModelMapping>();
979 std::vector<IntervalVariable> x_intervals =
980 mapping->Intervals(ct.no_overlap_2d().x_intervals());
981 std::vector<IntervalVariable> y_intervals =
982 mapping->Intervals(ct.no_overlap_2d().y_intervals());
983
984 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
985 auto* intervals_repository = model->GetOrCreate<IntervalsRepository>();
986
987 IntegerValue x_min = kMaxIntegerValue;
988 IntegerValue x_max = kMinIntegerValue;
989 IntegerValue y_min = kMaxIntegerValue;
990 IntegerValue y_max = kMinIntegerValue;
991 std::vector<AffineExpression> x_sizes;
992 std::vector<AffineExpression> y_sizes;
993 for (int i = 0; i < ct.no_overlap_2d().x_intervals_size(); ++i) {
994 x_sizes.push_back(intervals_repository->Size(x_intervals[i]));
995 y_sizes.push_back(intervals_repository->Size(y_intervals[i]));
996 x_min = std::min(x_min, integer_trail->LevelZeroLowerBound(
997 intervals_repository->Start(x_intervals[i])));
998 x_max = std::max(x_max, integer_trail->LevelZeroUpperBound(
999 intervals_repository->End(x_intervals[i])));
1000 y_min = std::min(y_min, integer_trail->LevelZeroLowerBound(
1001 intervals_repository->Start(y_intervals[i])));
1002 y_max = std::max(y_max, integer_trail->LevelZeroUpperBound(
1003 intervals_repository->End(y_intervals[i])));
1004 }
1005
1006 const IntegerValue max_area =
1007 IntegerValue(CapProd(CapSub(x_max.value(), x_min.value()),
1008 CapSub(y_max.value(), y_min.value())));
1009 if (max_area == kMaxIntegerValue) return;
1010
1011 auto* product_decomposer = model->GetOrCreate<ProductDecomposer>();
1012 LinearConstraintBuilder lc(model, IntegerValue(0), max_area);
1013 for (int i = 0; i < ct.no_overlap_2d().x_intervals_size(); ++i) {
1014 if (intervals_repository->IsPresent(x_intervals[i]) &&
1015 intervals_repository->IsPresent(y_intervals[i])) {
1016 const std::vector<LiteralValueValue> energy =
1017 product_decomposer->TryToDecompose(x_sizes[i], y_sizes[i]);
1018 if (!energy.empty()) {
1019 if (!lc.AddDecomposedProduct(energy)) return;
1020 } else {
1021 lc.AddQuadraticLowerBound(x_sizes[i], y_sizes[i], integer_trail);
1022 }
1023 } else if (intervals_repository->IsPresent(x_intervals[i]) ||
1024 intervals_repository->IsPresent(y_intervals[i]) ||
1025 (intervals_repository->PresenceLiteral(x_intervals[i]) ==
1026 intervals_repository->PresenceLiteral(y_intervals[i]))) {
1027 // We have only one active literal.
1028 const Literal presence_literal =
1029 intervals_repository->IsPresent(x_intervals[i])
1030 ? intervals_repository->PresenceLiteral(y_intervals[i])
1031 : intervals_repository->PresenceLiteral(x_intervals[i]);
1032 const IntegerValue area_min =
1033 integer_trail->LevelZeroLowerBound(x_sizes[i]) *
1034 integer_trail->LevelZeroLowerBound(y_sizes[i]);
1035 if (area_min != 0) {
1036 // Not including the term if we don't have a view is ok.
1037 (void)lc.AddLiteralTerm(presence_literal, area_min);
1038 }
1039 }
1040 }
1041 relaxation->linear_constraints.push_back(lc.Build());
1042}
1043
1044void AppendLinMaxRelaxationPart1(const ConstraintProto& ct, Model* model,
1045 LinearRelaxation* relaxation) {
1046 auto* mapping = model->GetOrCreate<CpModelMapping>();
1047
1048 // We want to linearize target = max(exprs[1], exprs[2], ..., exprs[d]).
1049 // Part 1: Encode target >= max(exprs[1], exprs[2], ..., exprs[d])
1050 const LinearExpression negated_target =
1051 NegationOf(mapping->GetExprFromProto(ct.lin_max().target()));
1052 for (int i = 0; i < ct.lin_max().exprs_size(); ++i) {
1053 const LinearExpression expr =
1054 mapping->GetExprFromProto(ct.lin_max().exprs(i));
1055 LinearConstraintBuilder lc(model, kMinIntegerValue, IntegerValue(0));
1056 lc.AddLinearExpression(negated_target);
1057 lc.AddLinearExpression(expr);
1058 relaxation->linear_constraints.push_back(lc.Build());
1059 }
1060}
1061
1062// TODO(user): experiment with:
1063// 1) remove this code
1064// 2) keep this code
1065// 3) remove this code and create the cut generator at level 1.
1066void AppendMaxAffineRelaxation(const ConstraintProto& ct, Model* model,
1067 LinearRelaxation* relaxation) {
1068 IntegerVariable var;
1069 std::vector<std::pair<IntegerValue, IntegerValue>> affines;
1070 auto* mapping = model->GetOrCreate<CpModelMapping>();
1071 CollectAffineExpressionWithSingleVariable(ct, mapping, &var, &affines);
1072 if (var == kNoIntegerVariable ||
1073 model->GetOrCreate<IntegerTrail>()->IsFixed(var)) {
1074 return;
1075 }
1076
1077 CHECK(VariableIsPositive(var));
1078 const LinearExpression target_expr =
1079 PositiveVarExpr(mapping->GetExprFromProto(ct.lin_max().target()));
1081 if (BuildMaxAffineUpConstraint(target_expr, var, affines, model, &builder)) {
1082 relaxation->linear_constraints.push_back(builder.Build());
1083 }
1084}
1085
1086void AddMaxAffineCutGenerator(const ConstraintProto& ct, Model* model,
1087 LinearRelaxation* relaxation) {
1088 IntegerVariable var;
1089 std::vector<std::pair<IntegerValue, IntegerValue>> affines;
1090 auto* mapping = model->GetOrCreate<CpModelMapping>();
1091 CollectAffineExpressionWithSingleVariable(ct, mapping, &var, &affines);
1092 if (var == kNoIntegerVariable ||
1093 model->GetOrCreate<IntegerTrail>()->IsFixed(var)) {
1094 return;
1095 }
1096
1097 // If the target is constant, propagation is enough.
1098 if (ct.lin_max().target().vars().empty()) return;
1099
1100 const LinearExpression target_expr =
1101 PositiveVarExpr(mapping->GetExprFromProto(ct.lin_max().target()));
1102 relaxation->cut_generators.push_back(CreateMaxAffineCutGenerator(
1103 target_expr, var, affines, "AffineMax", model));
1104}
1105
1106// Part 2: Encode upper bound on X.
1107//
1108// Add linking constraint to the CP solver
1109// sum zi = 1 and for all i, zi => max = expr_i.
1111 IntegerVariable target, const std::vector<Literal>& alternative_literals,
1112 const std::vector<LinearExpression>& exprs, Model* model,
1113 LinearRelaxation* relaxation) {
1114 const int num_exprs = exprs.size();
1115 GenericLiteralWatcher* watcher = model->GetOrCreate<GenericLiteralWatcher>();
1116
1117 // First add the CP constraints.
1118 for (int i = 0; i < num_exprs; ++i) {
1119 LinearExpression local_expr;
1120 local_expr.vars = NegationOf(exprs[i].vars);
1121 local_expr.vars.push_back(target);
1122 local_expr.coeffs = exprs[i].coeffs;
1123 local_expr.coeffs.push_back(IntegerValue(1));
1125 new IntegerSumLE({alternative_literals[i]}, local_expr.vars,
1126 local_expr.coeffs, exprs[i].offset, model);
1127 upper_bound->RegisterWith(watcher);
1128 model->TakeOwnership(upper_bound);
1129 }
1130
1131 // For the relaxation, we use different constraints with a stronger linear
1132 // relaxation as explained in the .h
1133 std::vector<std::vector<IntegerValue>> sum_of_max_corner_diff(
1134 num_exprs, std::vector<IntegerValue>(num_exprs, IntegerValue(0)));
1135
1136 // Cache coefficients.
1137 // TODO(user): Remove hash_map ?
1138 absl::flat_hash_map<std::pair<int, IntegerVariable>, IntegerValue> cache;
1139 for (int i = 0; i < num_exprs; ++i) {
1140 for (int j = 0; j < exprs[i].vars.size(); ++j) {
1141 cache[std::make_pair(i, exprs[i].vars[j])] = exprs[i].coeffs[j];
1142 }
1143 }
1144 const auto get_coeff = [&cache](IntegerVariable var, int index) {
1145 const auto it = cache.find(std::make_pair(index, var));
1146 if (it == cache.end()) return IntegerValue(0);
1147 return it->second;
1148 };
1149
1150 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1151 std::vector<IntegerVariable> active_vars;
1152 for (int i = 0; i + 1 < num_exprs; ++i) {
1153 for (int j = i + 1; j < num_exprs; ++j) {
1154 active_vars = exprs[i].vars;
1155 active_vars.insert(active_vars.end(), exprs[j].vars.begin(),
1156 exprs[j].vars.end());
1157 gtl::STLSortAndRemoveDuplicates(&active_vars);
1158 for (const IntegerVariable x_var : active_vars) {
1159 const IntegerValue diff = get_coeff(x_var, j) - get_coeff(x_var, i);
1160 if (diff == 0) continue;
1161
1162 const IntegerValue lb = integer_trail->LevelZeroLowerBound(x_var);
1163 const IntegerValue ub = integer_trail->LevelZeroUpperBound(x_var);
1164 sum_of_max_corner_diff[i][j] += std::max(diff * lb, diff * ub);
1165 sum_of_max_corner_diff[j][i] += std::max(-diff * lb, -diff * ub);
1166 }
1167 }
1168 }
1169
1170 for (int i = 0; i < num_exprs; ++i) {
1171 LinearConstraintBuilder lc(model, kMinIntegerValue, IntegerValue(0));
1172 lc.AddTerm(target, IntegerValue(1));
1173 for (int j = 0; j < exprs[i].vars.size(); ++j) {
1174 lc.AddTerm(exprs[i].vars[j], -exprs[i].coeffs[j]);
1175 }
1176 for (int j = 0; j < num_exprs; ++j) {
1177 CHECK(lc.AddLiteralTerm(alternative_literals[j],
1178 -exprs[j].offset - sum_of_max_corner_diff[i][j]));
1179 }
1180 relaxation->linear_constraints.push_back(lc.Build());
1181 }
1182}
1183
1184void AppendLinearConstraintRelaxation(const ConstraintProto& ct,
1185 bool linearize_enforced_constraints,
1186 Model* model,
1187 LinearRelaxation* relaxation,
1188 ActivityBoundHelper* activity_helper) {
1189 auto* mapping = model->Get<CpModelMapping>();
1190
1191 // Note that we ignore the holes in the domain.
1192 //
1193 // TODO(user): In LoadLinearConstraint() we already created intermediate
1194 // Booleans for each disjoint interval, we should reuse them here if
1195 // possible.
1196 //
1197 // TODO(user): process the "at most one" part of a == 1 separately?
1198 const IntegerValue rhs_domain_min = IntegerValue(ct.linear().domain(0));
1199 const IntegerValue rhs_domain_max =
1200 IntegerValue(ct.linear().domain(ct.linear().domain_size() - 1));
1201 if (rhs_domain_min == std::numeric_limits<int64_t>::min() &&
1202 rhs_domain_max == std::numeric_limits<int64_t>::max())
1203 return;
1204
1205 if (!HasEnforcementLiteral(ct)) {
1206 LinearConstraintBuilder lc(model, rhs_domain_min, rhs_domain_max);
1207 for (int i = 0; i < ct.linear().vars_size(); i++) {
1208 const int ref = ct.linear().vars(i);
1209 const int64_t coeff = ct.linear().coeffs(i);
1210 lc.AddTerm(mapping->Integer(ref), IntegerValue(coeff));
1211 }
1212 relaxation->linear_constraints.push_back(lc.Build());
1213 return;
1214 }
1215
1216 // Reified version.
1217 if (!linearize_enforced_constraints) return;
1218
1219 // We linearize fully reified constraints of size 1 all together for a given
1220 // variable. But we need to process half-reified ones or constraint with
1221 // more than one enforcement.
1222 //
1223 // TODO(user): Use cleaner "already loaded" logic, and mark as such constraint
1224 // already encoded by code like AppendRelaxationForEqualityEncoding().
1225 if (!mapping->IsHalfEncodingConstraint(&ct) && ct.linear().vars_size() <= 1 &&
1226 ct.enforcement_literal().size() <= 1) {
1227 return;
1228 }
1229
1230 std::vector<Literal> enforcing_literals;
1231 enforcing_literals.reserve(ct.enforcement_literal_size());
1232 for (const int enforcement_ref : ct.enforcement_literal()) {
1233 enforcing_literals.push_back(mapping->Literal(enforcement_ref));
1234 }
1235
1236 // Compute min/max activity.
1237 std::vector<std::pair<int, int64_t>> bool_terms;
1238 IntegerValue min_activity(0);
1239 IntegerValue max_activity(0);
1240 const auto integer_trail = model->GetOrCreate<IntegerTrail>();
1241 for (int i = 0; i < ct.linear().vars_size(); i++) {
1242 const int ref = ct.linear().vars(i);
1243 const IntegerValue coeff(ct.linear().coeffs(i));
1244 const IntegerVariable int_var = mapping->Integer(ref);
1245
1246 // Everything here should have a view.
1247 CHECK_NE(int_var, kNoIntegerVariable);
1248
1249 const IntegerValue lb = integer_trail->LowerBound(int_var);
1250 const IntegerValue ub = integer_trail->UpperBound(int_var);
1251 if (lb == 0 && ub == 1 && activity_helper != nullptr) {
1252 bool_terms.push_back({ref, coeff.value()});
1253 } else {
1254 if (coeff > 0) {
1255 min_activity += coeff * lb;
1256 max_activity += coeff * ub;
1257 } else {
1258 min_activity += coeff * ub;
1259 max_activity += coeff * lb;
1260 }
1261 }
1262 }
1263 if (activity_helper != nullptr) {
1264 min_activity +=
1265 IntegerValue(activity_helper->ComputeMinActivity(bool_terms));
1266 max_activity +=
1267 IntegerValue(activity_helper->ComputeMaxActivity(bool_terms));
1268 }
1269
1270 if (rhs_domain_min > min_activity) {
1271 // And(ei) => terms >= rhs_domain_min
1272 // <=> Sum_i (~ei * (rhs_domain_min - min_activity)) + terms >=
1273 // rhs_domain_min
1274 LinearConstraintBuilder lc(model, rhs_domain_min, kMaxIntegerValue);
1275 for (const Literal& literal : enforcing_literals) {
1276 CHECK(
1277 lc.AddLiteralTerm(literal.Negated(), rhs_domain_min - min_activity));
1278 }
1279 for (int i = 0; i < ct.linear().vars_size(); i++) {
1280 const int ref = ct.linear().vars(i);
1281 const IntegerValue coeff(ct.linear().coeffs(i));
1282 const IntegerVariable int_var = mapping->Integer(ref);
1283 lc.AddTerm(int_var, coeff);
1284 }
1285 relaxation->linear_constraints.push_back(lc.Build());
1286 }
1287 if (rhs_domain_max < max_activity) {
1288 // And(ei) => terms <= rhs_domain_max
1289 // <=> Sum_i (~ei * (rhs_domain_max - max_activity)) + terms <=
1290 // rhs_domain_max
1291 LinearConstraintBuilder lc(model, kMinIntegerValue, rhs_domain_max);
1292 for (const Literal& literal : enforcing_literals) {
1293 CHECK(
1294 lc.AddLiteralTerm(literal.Negated(), rhs_domain_max - max_activity));
1295 }
1296 for (int i = 0; i < ct.linear().vars_size(); i++) {
1297 const int ref = ct.linear().vars(i);
1298 const IntegerValue coeff(ct.linear().coeffs(i));
1299 const IntegerVariable int_var = mapping->Integer(ref);
1300 lc.AddTerm(int_var, coeff);
1301 }
1302 relaxation->linear_constraints.push_back(lc.Build());
1303 }
1304}
1305
1306// Add a static and a dynamic linear relaxation of the CP constraint to the set
1307// of linear constraints. The highest linearization_level is, the more types of
1308// constraint we encode. This method should be called only for
1309// linearization_level > 0. The static part is just called a relaxation and is
1310// called at the root node of the search. The dynamic part is implemented
1311// through a set of linear cut generators that will be called throughout the
1312// search.
1313//
1314// TODO(user): In full generality, we could encode all the constraint as an LP.
1315// TODO(user): Add unit tests for this method.
1316// TODO(user): Remove and merge with model loading.
1317void TryToLinearizeConstraint(const CpModelProto& /*model_proto*/,
1318 const ConstraintProto& ct,
1319 int linearization_level, Model* model,
1320 LinearRelaxation* relaxation,
1321 ActivityBoundHelper* activity_helper) {
1322 CHECK_EQ(model->GetOrCreate<SatSolver>()->CurrentDecisionLevel(), 0);
1323 DCHECK_GT(linearization_level, 0);
1324
1325 const int old_index = relaxation->linear_constraints.size();
1326 switch (ct.constraint_case()) {
1327 case ConstraintProto::ConstraintCase::kBoolOr: {
1328 if (linearization_level > 1) {
1329 AppendBoolOrRelaxation(ct, model, relaxation);
1330 }
1331 break;
1332 }
1333 case ConstraintProto::ConstraintCase::kBoolAnd: {
1334 if (linearization_level > 1) {
1335 AppendBoolAndRelaxation(ct, model, relaxation, activity_helper);
1336 }
1337 break;
1338 }
1339 case ConstraintProto::ConstraintCase::kAtMostOne: {
1340 AppendAtMostOneRelaxation(ct, model, relaxation);
1341 break;
1342 }
1343 case ConstraintProto::ConstraintCase::kExactlyOne: {
1344 AppendExactlyOneRelaxation(ct, model, relaxation);
1345 break;
1346 }
1347 case ConstraintProto::ConstraintCase::kIntProd: {
1348 const LinearArgumentProto& int_prod = ct.int_prod();
1349 if (int_prod.exprs_size() == 2 &&
1350 LinearExpressionProtosAreEqual(int_prod.exprs(0),
1351 int_prod.exprs(1))) {
1352 AppendSquareRelaxation(ct, model, relaxation);
1353 AddSquareCutGenerator(ct, linearization_level, model, relaxation);
1354 } else {
1355 // No relaxation, just a cut generator .
1356 AddIntProdCutGenerator(ct, linearization_level, model, relaxation);
1357 }
1358 break;
1359 }
1360 case ConstraintProto::ConstraintCase::kLinMax: {
1361 AppendLinMaxRelaxationPart1(ct, model, relaxation);
1362 const bool is_affine_max =
1363 ExpressionsContainsOnlyOneVar(ct.lin_max().exprs());
1364 if (is_affine_max) {
1365 AppendMaxAffineRelaxation(ct, model, relaxation);
1366 }
1367
1368 // Add cut generators.
1369 if (linearization_level > 1) {
1370 if (is_affine_max) {
1371 AddMaxAffineCutGenerator(ct, model, relaxation);
1372 } else if (ct.lin_max().exprs().size() < 100) {
1373 AddLinMaxCutGenerator(ct, model, relaxation);
1374 }
1375 }
1376 break;
1377 }
1378 case ConstraintProto::ConstraintCase::kAllDiff: {
1379 AddAllDiffRelaxationAndCutGenerator(ct, linearization_level, model,
1380 relaxation);
1381 break;
1382 }
1383 case ConstraintProto::ConstraintCase::kLinear: {
1385 ct, /*linearize_enforced_constraints=*/linearization_level > 1, model,
1386 relaxation, activity_helper);
1387 break;
1388 }
1389 case ConstraintProto::ConstraintCase::kCircuit: {
1390 AppendCircuitRelaxation(ct, model, relaxation);
1391 if (linearization_level > 1) {
1392 AddCircuitCutGenerator(ct, model, relaxation);
1393 }
1394 break;
1395 }
1396 case ConstraintProto::ConstraintCase::kRoutes: {
1397 AppendRoutesRelaxation(ct, model, relaxation);
1398 if (linearization_level > 1) {
1399 AddRoutesCutGenerator(ct, model, relaxation);
1400 }
1401 break;
1402 }
1403 case ConstraintProto::ConstraintCase::kNoOverlap: {
1405 break;
1406 }
1407 case ConstraintProto::ConstraintCase::kCumulative: {
1409 break;
1410 }
1411 case ConstraintProto::ConstraintCase::kNoOverlap2D: {
1412 // TODO(user): Use the same pattern as the other 2 scheduling methods:
1413 // - single function
1414 // - generate helpers once
1415 //
1416 // Adds an energetic relaxation (sum of areas fits in bounding box).
1417 AppendNoOverlap2dRelaxation(ct, model, relaxation);
1418 if (linearization_level > 1) {
1419 // Adds a completion time cut generator and an energetic cut generator.
1420 AddNoOverlap2dCutGenerator(ct, model, relaxation);
1421 }
1422 break;
1423 }
1424 default: {
1425 }
1426 }
1427
1428 if (DEBUG_MODE) {
1429 const auto& integer_trail = *model->GetOrCreate<IntegerTrail>();
1430 for (int i = old_index; i < relaxation->linear_constraints.size(); ++i) {
1431 const bool issue =
1432 PossibleOverflow(integer_trail, relaxation->linear_constraints[i]);
1433 if (issue) {
1434 LOG(INFO) << "Possible overflow in linearization of: "
1435 << google::protobuf::ShortFormat(ct);
1436 }
1437 }
1438 }
1439}
1440
1441// Cut generators.
1442
1443void AddIntProdCutGenerator(const ConstraintProto& ct, int linearization_level,
1444 Model* m, LinearRelaxation* relaxation) {
1445 if (HasEnforcementLiteral(ct)) return;
1446 if (ct.int_prod().exprs_size() != 2) return;
1447 auto* mapping = m->GetOrCreate<CpModelMapping>();
1448
1449 // Constraint is z == x * y.
1450 AffineExpression z = mapping->Affine(ct.int_prod().target());
1451 AffineExpression x = mapping->Affine(ct.int_prod().exprs(0));
1452 AffineExpression y = mapping->Affine(ct.int_prod().exprs(1));
1453
1454 IntegerTrail* const integer_trail = m->GetOrCreate<IntegerTrail>();
1455 IntegerValue x_lb = integer_trail->LowerBound(x);
1456 IntegerValue x_ub = integer_trail->UpperBound(x);
1457 IntegerValue y_lb = integer_trail->LowerBound(y);
1458 IntegerValue y_ub = integer_trail->UpperBound(y);
1459
1460 // We currently only support variables with non-negative domains.
1461 if (x_lb < 0 && x_ub > 0) return;
1462 if (y_lb < 0 && y_ub > 0) return;
1463
1464 // Change signs to return to the case where all variables are a domain
1465 // with non negative values only.
1466 if (x_ub <= 0) {
1467 x = x.Negated();
1468 z = z.Negated();
1469 }
1470 if (y_ub <= 0) {
1471 y = y.Negated();
1472 z = z.Negated();
1473 }
1474
1476 z, x, y, linearization_level, m));
1477}
1478
1479void AppendSquareRelaxation(const ConstraintProto& ct, Model* m,
1480 LinearRelaxation* relaxation) {
1481 if (HasEnforcementLiteral(ct)) return;
1482 auto* mapping = m->GetOrCreate<CpModelMapping>();
1483 IntegerTrail* const integer_trail = m->GetOrCreate<IntegerTrail>();
1484
1485 // Constraint is square == x * x.
1486 AffineExpression square = mapping->Affine(ct.int_prod().target());
1487 AffineExpression x = mapping->Affine(ct.int_prod().exprs(0));
1488 IntegerValue x_lb = integer_trail->LowerBound(x);
1489 IntegerValue x_ub = integer_trail->UpperBound(x);
1490
1491 if (x_lb == x_ub) return;
1492
1493 // We currently only support variables with non-negative domains.
1494 if (x_lb < 0 && x_ub > 0) return;
1495
1496 // Change the sigh of x if its domain is non-positive.
1497 if (x_ub <= 0) {
1498 x = x.Negated();
1499 const IntegerValue tmp = x_ub;
1500 x_ub = -x_lb;
1501 x_lb = -tmp;
1502 }
1503
1504 // Check for potential overflows.
1505 if (x_ub > (int64_t{1} << 31)) return;
1506 DCHECK_GE(x_lb, 0);
1507
1508 relaxation->linear_constraints.push_back(
1509 ComputeHyperplanAboveSquare(x, square, x_lb, x_ub, m));
1510
1511 relaxation->linear_constraints.push_back(
1512 ComputeHyperplanBelowSquare(x, square, x_lb, m));
1513 // TODO(user): We could add all or some below_hyperplans.
1514 if (x_lb + 1 < x_ub) {
1515 // The hyperplan will use x_ub - 1 and x_ub.
1516 relaxation->linear_constraints.push_back(
1517 ComputeHyperplanBelowSquare(x, square, x_ub - 1, m));
1518 }
1519}
1520
1521void AddSquareCutGenerator(const ConstraintProto& ct, int linearization_level,
1522 Model* m, LinearRelaxation* relaxation) {
1523 if (HasEnforcementLiteral(ct)) return;
1524 auto* mapping = m->GetOrCreate<CpModelMapping>();
1525 IntegerTrail* const integer_trail = m->GetOrCreate<IntegerTrail>();
1526
1527 // Constraint is square == x * x.
1528 const AffineExpression square = mapping->Affine(ct.int_prod().target());
1529 AffineExpression x = mapping->Affine(ct.int_prod().exprs(0));
1530 const IntegerValue x_lb = integer_trail->LowerBound(x);
1531 const IntegerValue x_ub = integer_trail->UpperBound(x);
1532
1533 // We currently only support variables with non-negative domains.
1534 if (x_lb < 0 && x_ub > 0) return;
1535
1536 // Change the sigh of x if its domain is non-positive.
1537 if (x_ub <= 0) {
1538 x = x.Negated();
1539 }
1540
1541 relaxation->cut_generators.push_back(
1542 CreateSquareCutGenerator(square, x, linearization_level, m));
1543}
1544
1545void AddAllDiffRelaxationAndCutGenerator(const ConstraintProto& ct,
1546 int linearization_level, Model* m,
1547 LinearRelaxation* relaxation) {
1548 if (HasEnforcementLiteral(ct)) return;
1549 auto* mapping = m->GetOrCreate<CpModelMapping>();
1550 auto* integer_trail = m->GetOrCreate<IntegerTrail>();
1551 const int num_exprs = ct.all_diff().exprs_size();
1552
1553 const std::vector<AffineExpression> exprs =
1554 mapping->Affines(ct.all_diff().exprs());
1555
1556 // Build union of affine expressions domains to check if this is a
1557 // permutation.
1558 Domain union_of_domains;
1559 for (const AffineExpression& expr : exprs) {
1560 if (integer_trail->IsFixed(expr)) {
1561 union_of_domains = union_of_domains.UnionWith(
1562 Domain(integer_trail->FixedValue(expr).value()));
1563 } else {
1564 union_of_domains = union_of_domains.UnionWith(
1565 integer_trail->InitialVariableDomain(expr.var)
1566 .MultiplicationBy(expr.coeff.value())
1567 .AdditionWith(Domain(expr.constant.value())));
1568 }
1569 }
1570
1571 if (union_of_domains.Size() == num_exprs) {
1572 // In case of a permutation, the linear constraint is tight.
1573 int64_t sum_of_values = 0;
1574 for (const int64_t v : union_of_domains.Values()) {
1575 sum_of_values += v;
1576 }
1577 LinearConstraintBuilder relax(m, sum_of_values, sum_of_values);
1578 for (const AffineExpression& expr : exprs) {
1579 relax.AddTerm(expr, 1);
1580 }
1581 relaxation->linear_constraints.push_back(relax.Build());
1582 } else if (num_exprs <=
1583 m->GetOrCreate<SatParameters>()->max_all_diff_cut_size() &&
1584 linearization_level > 1) {
1585 relaxation->cut_generators.push_back(
1587 }
1588}
1589
1590bool IntervalIsVariable(const IntervalVariable interval,
1591 IntervalsRepository* intervals_repository) {
1592 // Ignore absent rectangles.
1593 if (intervals_repository->IsAbsent(interval)) {
1594 return false;
1595 }
1596
1597 // Checks non-present intervals.
1598 if (!intervals_repository->IsPresent(interval)) {
1599 return true;
1600 }
1601
1602 // Checks variable sized intervals.
1603 if (intervals_repository->MinSize(interval) !=
1604 intervals_repository->MaxSize(interval)) {
1605 return true;
1606 }
1607
1608 return false;
1609}
1610
1613 SchedulingDemandHelper* demands_helper,
1614 const std::optional<AffineExpression>& makespan,
1615 Model* m, LinearRelaxation* relaxation) {
1617 helper, demands_helper, capacity, m));
1618 relaxation->cut_generators.push_back(
1619 CreateCumulativeCompletionTimeCutGenerator(helper, demands_helper,
1620 capacity, m));
1622 helper, demands_helper, capacity, m));
1623
1624 // Checks if at least one rectangle has a variable size, is optional, or if
1625 // the demand or the capacity are variable.
1626 bool has_variable_part = false;
1627 IntegerTrail* integer_trail = m->GetOrCreate<IntegerTrail>();
1628 for (int i = 0; i < helper->NumTasks(); ++i) {
1629 if (!helper->SizeIsFixed(i)) {
1630 has_variable_part = true;
1631 break;
1632 }
1633 // Checks variable demand.
1634 if (!demands_helper->DemandIsFixed(i)) {
1635 has_variable_part = true;
1636 break;
1637 }
1638 }
1639 if (has_variable_part || !integer_trail->IsFixed(capacity)) {
1641 helper, demands_helper, capacity, makespan, m));
1642 }
1643}
1644
1646 const std::optional<AffineExpression>& makespan,
1647 Model* m, LinearRelaxation* relaxation) {
1648 relaxation->cut_generators.push_back(
1650 relaxation->cut_generators.push_back(
1652
1653 // Checks if at least one rectangle has a variable size or is optional.
1654 bool has_variable_or_optional_part = false;
1655 for (int i = 0; i < helper->NumTasks(); ++i) {
1656 if (helper->IsAbsent(i)) continue;
1657 if (!helper->SizeIsFixed(i) || !helper->IsPresent(i)) {
1658 has_variable_or_optional_part = true;
1659 break;
1660 }
1661 }
1662 if (has_variable_or_optional_part) {
1663 relaxation->cut_generators.push_back(
1664 CreateNoOverlapEnergyCutGenerator(helper, makespan, m));
1665 }
1666}
1667
1668void AddNoOverlap2dCutGenerator(const ConstraintProto& ct, Model* m,
1669 LinearRelaxation* relaxation) {
1670 if (HasEnforcementLiteral(ct)) return;
1671
1672 auto* mapping = m->GetOrCreate<CpModelMapping>();
1673 std::vector<IntervalVariable> x_intervals =
1674 mapping->Intervals(ct.no_overlap_2d().x_intervals());
1675 std::vector<IntervalVariable> y_intervals =
1676 mapping->Intervals(ct.no_overlap_2d().y_intervals());
1677
1678 IntervalsRepository* intervals_repository =
1680 SchedulingConstraintHelper* x_helper =
1681 intervals_repository->GetOrCreateHelper(x_intervals);
1682 SchedulingConstraintHelper* y_helper =
1683 intervals_repository->GetOrCreateHelper(y_intervals);
1684
1685 relaxation->cut_generators.push_back(
1686 CreateNoOverlap2dCompletionTimeCutGenerator(x_helper, y_helper, m));
1687
1688 // Checks if at least one rectangle has a variable dimension or is optional.
1689 bool has_variable_part = false;
1690 for (int i = 0; i < x_intervals.size(); ++i) {
1691 // Ignore absent rectangles.
1692 if (intervals_repository->IsAbsent(x_intervals[i]) ||
1693 intervals_repository->IsAbsent(y_intervals[i])) {
1694 continue;
1695 }
1696
1697 // Checks non-present intervals.
1698 if (!intervals_repository->IsPresent(x_intervals[i]) ||
1699 !intervals_repository->IsPresent(y_intervals[i])) {
1700 has_variable_part = true;
1701 break;
1702 }
1703
1704 // Checks variable sized intervals.
1705 if (intervals_repository->MinSize(x_intervals[i]) !=
1706 intervals_repository->MaxSize(x_intervals[i]) ||
1707 intervals_repository->MinSize(y_intervals[i]) !=
1708 intervals_repository->MaxSize(y_intervals[i])) {
1709 has_variable_part = true;
1710 break;
1711 }
1712 }
1713
1714 if (!has_variable_part) return;
1715
1716 SchedulingDemandHelper* x_demands_helper =
1717 intervals_repository->GetOrCreateDemandHelper(y_helper,
1718 x_helper->Sizes());
1719 SchedulingDemandHelper* y_demands_helper =
1720 intervals_repository->GetOrCreateDemandHelper(x_helper,
1721 y_helper->Sizes());
1722
1723 std::vector<std::vector<LiteralValueValue>> energies;
1724 const int num_rectangles = x_helper->NumTasks();
1725 auto* product_decomposer = m->GetOrCreate<ProductDecomposer>();
1726 for (int i = 0; i < num_rectangles; ++i) {
1727 energies.push_back(product_decomposer->TryToDecompose(
1728 x_helper->Sizes()[i], y_helper->Sizes()[i]));
1729 }
1730
1732 x_helper, y_helper, x_demands_helper, y_demands_helper, energies, m));
1733}
1734
1735void AddLinMaxCutGenerator(const ConstraintProto& ct, Model* m,
1736 LinearRelaxation* relaxation) {
1737 if (!m->GetOrCreate<SatParameters>()->add_lin_max_cuts()) return;
1738 if (HasEnforcementLiteral(ct)) return;
1739
1740 // TODO(user): Support linearization of general target expression.
1741 auto* mapping = m->GetOrCreate<CpModelMapping>();
1742 if (ct.lin_max().target().vars_size() != 1) return;
1743 if (ct.lin_max().target().coeffs(0) != 1) return;
1744 if (ct.lin_max().target().offset() != 0) return;
1745
1746 const IntegerVariable target =
1747 mapping->Integer(ct.lin_max().target().vars(0));
1748 std::vector<LinearExpression> exprs;
1749 exprs.reserve(ct.lin_max().exprs_size());
1750 for (int i = 0; i < ct.lin_max().exprs_size(); ++i) {
1751 // Note: Cut generator requires all expressions to contain only positive
1752 // vars.
1753 exprs.push_back(
1754 PositiveVarExpr(mapping->GetExprFromProto(ct.lin_max().exprs(i))));
1755 }
1756
1757 const std::vector<Literal> alternative_literals =
1758 CreateAlternativeLiteralsWithView(exprs.size(), m, relaxation);
1759
1760 // TODO(user): Move this out of here.
1761 //
1762 // Add initial big-M linear relaxation.
1763 // z_vars[i] == 1 <=> target = exprs[i].
1764 AppendLinMaxRelaxationPart2(target, alternative_literals, exprs, m,
1765 relaxation);
1766
1767 std::vector<IntegerVariable> z_vars;
1768 auto* encoder = m->GetOrCreate<IntegerEncoder>();
1769 for (const Literal lit : alternative_literals) {
1770 z_vars.push_back(encoder->GetLiteralView(lit));
1771 CHECK_NE(z_vars.back(), kNoIntegerVariable);
1772 }
1773 relaxation->cut_generators.push_back(
1774 CreateLinMaxCutGenerator(target, exprs, z_vars, m));
1775}
1776
1777// If we have an exactly one between literals l_i, and each l_i => var ==
1778// value_i, then we can add a strong linear relaxation: var = sum l_i * value_i.
1779//
1780// This codes detect this and add the corresponding linear equations.
1781//
1782// TODO(user): We can do something similar with just an at most one, however
1783// it is harder to detect that if all literal are false then none of the implied
1784// value can be taken.
1786 auto* integer_trail = m->GetOrCreate<IntegerTrail>();
1787 auto* element_encodings = m->GetOrCreate<ElementEncodings>();
1788
1789 int num_exactly_one_elements = 0;
1790 for (const IntegerVariable var :
1791 element_encodings->GetElementEncodedVariables()) {
1792 for (const auto& [index, literal_value_list] :
1793 element_encodings->Get(var)) {
1794 IntegerValue min_value = kMaxIntegerValue;
1795 for (const auto& literal_value : literal_value_list) {
1796 min_value = std::min(min_value, literal_value.value);
1797 }
1798
1799 LinearConstraintBuilder builder(m, -min_value, -min_value);
1800 builder.AddTerm(var, IntegerValue(-1));
1801 for (const auto& [value, literal] : literal_value_list) {
1802 const IntegerValue delta_min = value - min_value;
1803 if (delta_min != 0) {
1804 // If the term has no view, we abort.
1805 if (!builder.AddLiteralTerm(literal, delta_min)) {
1806 return;
1807 }
1808 }
1809 }
1810 ++num_exactly_one_elements;
1811 LinearConstraint lc = builder.Build();
1812 if (!PossibleOverflow(*integer_trail, lc)) {
1813 relaxation->linear_constraints.push_back(std::move(lc));
1814 }
1815 }
1816 }
1817
1818 if (num_exactly_one_elements != 0) {
1819 auto* logger = m->GetOrCreate<SolverLogger>();
1820 SOLVER_LOG(logger,
1821 "[ElementLinearRelaxation]"
1822 " #from_exactly_one:",
1823 num_exactly_one_elements);
1824 }
1825}
1826
1827LinearRelaxation ComputeLinearRelaxation(const CpModelProto& model_proto,
1828 Model* m) {
1829 LinearRelaxation relaxation;
1830 const SatParameters& params = *m->GetOrCreate<SatParameters>();
1831
1832 // Collect AtMostOne to compute better Big-M.
1833 ActivityBoundHelper activity_bound_helper;
1834 if (params.linearization_level() > 1) {
1835 activity_bound_helper.AddAllAtMostOnes(model_proto);
1836 }
1837
1838 // Linearize the constraints.
1839 for (const auto& ct : model_proto.constraints()) {
1840 TryToLinearizeConstraint(model_proto, ct, params.linearization_level(), m,
1841 &relaxation, &activity_bound_helper);
1842 }
1843
1844 // Linearize the encoding of variable that are fully encoded.
1845 int num_loose_equality_encoding_relaxations = 0;
1846 int num_tight_equality_encoding_relaxations = 0;
1847 int num_inequality_encoding_relaxations = 0;
1848 auto* mapping = m->GetOrCreate<CpModelMapping>();
1849 for (int i = 0; i < model_proto.variables_size(); ++i) {
1850 if (mapping->IsBoolean(i)) continue;
1851
1852 const IntegerVariable var = mapping->Integer(i);
1853 if (m->Get(IsFixed(var))) continue;
1854
1855 // We first try to linearize the values encoding.
1857 var, *m, &relaxation, &num_tight_equality_encoding_relaxations,
1858 &num_loose_equality_encoding_relaxations);
1859
1860 // Then we try to linearize the inequality encoding. Note that on some
1861 // problem like pizza27i.mps.gz, adding both equality and inequality
1862 // encoding is a must.
1863 //
1864 // Even if the variable is fully encoded, sometimes not all its associated
1865 // literal have a view (if they are not part of the original model for
1866 // instance).
1867 //
1868 // TODO(user): Should we add them to the LP anyway? this isn't clear as
1869 // we can sometimes create a lot of Booleans like this.
1870 const int old = relaxation.linear_constraints.size();
1872 if (relaxation.linear_constraints.size() > old) {
1873 ++num_inequality_encoding_relaxations;
1874 }
1875 }
1876
1877 // TODO(user): This is similar to AppendRelaxationForEqualityEncoding() above.
1878 // Investigate if we can merge the code.
1879 if (params.linearization_level() >= 2) {
1880 AppendElementEncodingRelaxation(m, &relaxation);
1881 }
1882
1883 // TODO(user): I am not sure this is still needed. Investigate and explain why
1884 // or remove.
1885 if (!m->GetOrCreate<SatSolver>()->FinishPropagation()) {
1886 return relaxation;
1887 }
1888
1889 // We display the stats before linearizing the at most ones.
1890 auto* logger = m->GetOrCreate<SolverLogger>();
1891 if (num_tight_equality_encoding_relaxations != 0 ||
1892 num_loose_equality_encoding_relaxations != 0 ||
1893 num_inequality_encoding_relaxations != 0) {
1894 SOLVER_LOG(logger,
1895 "[EncodingLinearRelaxation]"
1896 " #tight_equality:",
1897 num_tight_equality_encoding_relaxations,
1898 " #loose_equality:", num_loose_equality_encoding_relaxations,
1899 " #inequality:", num_inequality_encoding_relaxations);
1900 }
1901 if (!relaxation.linear_constraints.empty() ||
1902 !relaxation.at_most_ones.empty()) {
1903 SOLVER_LOG(logger,
1904 "[LinearRelaxationBeforeCliqueExpansion]"
1905 " #linear:",
1906 relaxation.linear_constraints.size(),
1907 " #at_most_ones:", relaxation.at_most_ones.size());
1908 }
1909
1910 // Linearize the at most one constraints. Note that we transform them
1911 // into maximum "at most one" first and we removes redundant ones.
1912 m->GetOrCreate<BinaryImplicationGraph>()->TransformIntoMaxCliques(
1913 &relaxation.at_most_ones,
1914 SafeDoubleToInt64(params.merge_at_most_one_work_limit()));
1915 for (const std::vector<Literal>& at_most_one : relaxation.at_most_ones) {
1916 if (at_most_one.empty()) continue;
1917
1918 LinearConstraintBuilder lc(m, kMinIntegerValue, IntegerValue(1));
1919 for (const Literal literal : at_most_one) {
1920 // Note that it is okay to simply ignore the literal if it has no
1921 // integer view.
1922 const bool unused ABSL_ATTRIBUTE_UNUSED =
1923 lc.AddLiteralTerm(literal, IntegerValue(1));
1924 }
1925 relaxation.linear_constraints.push_back(lc.Build());
1926 }
1927
1928 // We converted all at_most_one to LP constraints, so we need to clear them
1929 // so that we don't do extra work in the connected component computation.
1930 relaxation.at_most_ones.clear();
1931
1932 // Propagate unary constraints.
1933 {
1934 SatSolver* sat_solver = m->GetOrCreate<SatSolver>();
1935 CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
1936 IntegerTrail* integer_trail = m->GetOrCreate<IntegerTrail>();
1937 for (const LinearConstraint& lc : relaxation.linear_constraints) {
1938 if (lc.num_terms == 0) {
1939 if (lc.lb > 0 || lc.ub < 0) {
1940 sat_solver->NotifyThatModelIsUnsat();
1941 return relaxation;
1942 }
1943 } else if (lc.num_terms == 1) {
1944 const AffineExpression expr(lc.vars[0], lc.coeffs[0]);
1945 if (lc.lb > integer_trail->LevelZeroLowerBound(expr)) {
1946 if (!integer_trail->Enqueue(expr.GreaterOrEqual(lc.lb), {}, {})) {
1947 sat_solver->NotifyThatModelIsUnsat();
1948 return relaxation;
1949 }
1950 }
1951 if (lc.ub < integer_trail->LevelZeroUpperBound(expr)) {
1952 if (!integer_trail->Enqueue(expr.LowerOrEqual(lc.ub), {}, {})) {
1953 sat_solver->NotifyThatModelIsUnsat();
1954 return relaxation;
1955 }
1956 }
1957 }
1958 }
1959 if (!sat_solver->FinishPropagation()) return relaxation;
1960 }
1961
1962 // Remove size one LP constraints from the main algorithms, they are not
1963 // useful.
1964 relaxation.linear_constraints.erase(
1965 std::remove_if(
1966 relaxation.linear_constraints.begin(),
1967 relaxation.linear_constraints.end(),
1968 [](const LinearConstraint& lc) { return lc.num_terms <= 1; }),
1969 relaxation.linear_constraints.end());
1970
1971 // We add a clique cut generation over all Booleans of the problem.
1972 // Note that in practice this might regroup independent LP together.
1973 //
1974 // TODO(user): compute connected components of the original problem and
1975 // split these cuts accordingly.
1976 if (params.linearization_level() > 1 && params.add_clique_cuts()) {
1977 LinearConstraintBuilder builder(m);
1978 for (int i = 0; i < model_proto.variables_size(); ++i) {
1979 if (!mapping->IsBoolean(i)) continue;
1980
1981 // Note that it is okay to simply ignore the literal if it has no
1982 // integer view.
1983 const bool unused ABSL_ATTRIBUTE_UNUSED =
1984 builder.AddLiteralTerm(mapping->Literal(i), IntegerValue(1));
1985 }
1986
1987 // We add a generator touching all the variable in the builder.
1988 const LinearExpression& expr = builder.BuildExpression();
1989 if (!expr.vars.empty()) {
1990 relaxation.cut_generators.push_back(
1992 }
1993 }
1994
1995 if (!relaxation.linear_constraints.empty() ||
1996 !relaxation.cut_generators.empty()) {
1997 SOLVER_LOG(logger,
1998 "[FinalLinearRelaxation]"
1999 " #linear:",
2000 relaxation.linear_constraints.size(),
2001 " #cut_generators:", relaxation.cut_generators.size());
2002 }
2003
2004 return relaxation;
2005}
2006
2007} // namespace sat
2008} // namespace operations_research
IntegerValue y
int64_t max
int64_t min
DomainIteratorBeginEnd Values() const &
Domain UnionWith(const Domain &domain) const
int64_t ComputeMaxActivity(absl::Span< const std::pair< int, int64_t > > terms, std::vector< std::array< int64_t, 2 > > *conditional=nullptr)
int64_t ComputeMinActivity(absl::Span< const std::pair< int, int64_t > > terms, std::vector< std::array< int64_t, 2 > > *conditional=nullptr)
std::vector< absl::Span< const int > > PartitionLiteralsIntoAmo(absl::Span< const int > literals)
Similar algo as above for this simpler case.
std::vector< IntervalVariable > Intervals(const ProtoIndices &indices) const
std::vector< sat::Literal > Literals(const ProtoIndices &indices) const
IntegerVariable Integer(int ref) const
IntegerValue LowerBound(IntegerVariable i) const
Returns the current lower/upper bound of the given integer variable.
Definition integer.h:1717
IntegerValue UpperBound(IntegerVariable i) const
Definition integer.h:1721
IntegerValue FixedValue(IntegerVariable i) const
Checks that the variable is fixed and returns its value.
Definition integer.h:1729
bool IsFixed(IntegerVariable i) const
Checks if the variable is fixed.
Definition integer.h:1725
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition integer.h:1817
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Returns globally valid lower/upper bound on the given integer variable.
Definition integer.h:1812
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit)
Definition integer.h:972
SchedulingDemandHelper * GetOrCreateDemandHelper(SchedulingConstraintHelper *helper, absl::Span< const AffineExpression > demands)
Definition intervals.cc:206
AffineExpression Start(IntervalVariable i) const
Definition intervals.h:111
bool IsAbsent(IntervalVariable i) const
Definition intervals.h:98
IntegerValue MaxSize(IntervalVariable i) const
Return the maximum size of the given IntervalVariable.
Definition intervals.h:120
IntegerValue MinSize(IntervalVariable i) const
Return the minimum size of the given IntervalVariable.
Definition intervals.h:115
SchedulingConstraintHelper * GetOrCreateHelper(const std::vector< IntervalVariable > &variables, bool register_as_disjunctive_helper=false)
Definition intervals.cc:190
bool IsPresent(IntervalVariable i) const
Definition intervals.h:94
void AddQuadraticLowerBound(AffineExpression left, AffineExpression right, IntegerTrail *integer_trail, bool *is_quadratic=nullptr)
ABSL_MUST_USE_RESULT bool AddLiteralTerm(Literal lit, IntegerValue coeff=IntegerValue(1))
void AddTerm(IntegerVariable var, IntegerValue coeff)
void AddLinearExpression(const LinearExpression &expr)
void AddConstant(IntegerValue value)
Adds the corresponding term to the current linear expression.
void Clear()
Clears all added terms and constants. Keeps the original bounds.
LinearConstraint BuildConstraint(IntegerValue lb, IntegerValue ub)
ABSL_MUST_USE_RESULT bool AddDecomposedProduct(absl::Span< const LiteralValueValue > product)
void RegisterWith(GenericLiteralWatcher *watcher)
T Get(std::function< T(const Model &)> f) const
Similar to Add() but this is const.
Definition model.h:95
Helper class to express a product as a linear constraint.
ABSL_MUST_USE_RESULT bool FinishPropagation()
int NumTasks() const
Returns the number of task.
Definition intervals.h:293
absl::Span< const AffineExpression > Sizes() const
Definition intervals.h:481
ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward)
Definition intervals.cc:483
const std::vector< std::vector< LiteralValueValue > > & DecomposedEnergies() const
Definition intervals.h:694
const std::vector< AffineExpression > & Demands() const
Definition intervals.h:650
const Constraint * ct
int64_t value
IntVar * var
double upper_bound
GRBmodel * model
int lit
int literal
int arc
int index
const bool DEBUG_MODE
Definition macros.h:24
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:58
std::vector< typename Map::mapped_type > Values(const Map &map, const Keys &keys)
Definition key_types.h:136
void AppendNoOverlapRelaxationAndCutGenerator(const ConstraintProto &ct, Model *model, LinearRelaxation *relaxation)
CutGenerator CreateCliqueCutGenerator(const std::vector< IntegerVariable > &base_variables, Model *model)
Definition cuts.cc:2724
void AppendCumulativeRelaxationAndCutGenerator(const ConstraintProto &ct, Model *model, LinearRelaxation *relaxation)
CutGenerator CreateCumulativeCompletionTimeCutGenerator(SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const AffineExpression &capacity, Model *model)
void AddSquareCutGenerator(const ConstraintProto &ct, int linearization_level, Model *m, LinearRelaxation *relaxation)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
void AppendExactlyOneRelaxation(const ConstraintProto &ct, Model *model, LinearRelaxation *relaxation)
CutGenerator CreateCVRPCutGenerator(int num_nodes, std::vector< int > tails, std::vector< int > heads, std::vector< Literal > literals, std::vector< int64_t > demands, int64_t capacity, Model *model)
void AddCumulativeCutGenerator(const AffineExpression &capacity, SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const std::optional< AffineExpression > &makespan, Model *m, LinearRelaxation *relaxation)
void AppendRoutesRelaxation(const ConstraintProto &ct, Model *model, LinearRelaxation *relaxation)
bool BuildMaxAffineUpConstraint(const LinearExpression &target, IntegerVariable var, const std::vector< std::pair< IntegerValue, IntegerValue > > &affines, Model *model, LinearConstraintBuilder *builder)
Definition cuts.cc:2648
CutGenerator CreatePositiveMultiplicationCutGenerator(AffineExpression z, AffineExpression x, AffineExpression y, int linearization_level, Model *model)
A cut generator for z = x * y (x and y >= 0).
Definition cuts.cc:1859
const LiteralIndex kNoLiteralIndex(-1)
void AddMaxAffineCutGenerator(const ConstraintProto &ct, Model *model, LinearRelaxation *relaxation)
void AddCircuitCutGenerator(const ConstraintProto &ct, Model *m, LinearRelaxation *relaxation)
LinearConstraint ComputeHyperplanBelowSquare(AffineExpression x, AffineExpression square, IntegerValue x_value, Model *model)
Definition cuts.cc:1966
CutGenerator CreateCumulativePrecedenceCutGenerator(SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const AffineExpression &capacity, Model *model)
LinearRelaxation ComputeLinearRelaxation(const CpModelProto &model_proto, Model *m)
Builds the linear relaxation of a CpModelProto.
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Returns the vector of the negated variables.
Definition integer.cc:51
bool HasEnforcementLiteral(const ConstraintProto &ct)
Small utility functions to deal with half-reified constraints.
void AppendLinearConstraintRelaxation(const ConstraintProto &ct, bool linearize_enforced_constraints, Model *model, LinearRelaxation *relaxation, ActivityBoundHelper *activity_helper)
std::function< bool(const Model &)> IsFixed(IntegerVariable v)
Definition integer.h:1967
void AddIntProdCutGenerator(const ConstraintProto &ct, int linearization_level, Model *m, LinearRelaxation *relaxation)
Cut generators.
CutGenerator CreateLinMaxCutGenerator(const IntegerVariable target, const std::vector< LinearExpression > &exprs, const std::vector< IntegerVariable > &z_vars, Model *model)
Definition cuts.cc:2563
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
CutGenerator CreateNoOverlap2dCompletionTimeCutGenerator(SchedulingConstraintHelper *x_helper, SchedulingConstraintHelper *y_helper, Model *model)
void AddRoutesCutGenerator(const ConstraintProto &ct, Model *m, LinearRelaxation *relaxation)
CutGenerator CreateCumulativeEnergyCutGenerator(SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const AffineExpression &capacity, const std::optional< AffineExpression > &makespan, Model *model)
CutGenerator CreateStronglyConnectedGraphCutGenerator(int num_nodes, std::vector< int > tails, std::vector< int > heads, std::vector< Literal > literals, Model *model)
const IntegerVariable kNoIntegerVariable(-1)
bool PossibleOverflow(const IntegerTrail &integer_trail, const LinearConstraint &constraint)
IntType FloorOfRatio(IntType numerator, IntType denominator)
Definition util.h:734
void AddCumulativeRelaxation(const AffineExpression &capacity, SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const std::optional< AffineExpression > &makespan, Model *model, LinearRelaxation *relaxation)
Scheduling relaxations and cut generators.
CutGenerator CreateSquareCutGenerator(AffineExpression y, AffineExpression x, int linearization_level, Model *model)
Definition cuts.cc:1978
void AppendPartialGreaterThanEncodingRelaxation(IntegerVariable var, const Model &model, LinearRelaxation *relaxation)
void TryToLinearizeConstraint(const CpModelProto &, const ConstraintProto &ct, int linearization_level, Model *model, LinearRelaxation *relaxation, ActivityBoundHelper *activity_helper)
Adds linearization of different types of constraints.
IntegerVariable PositiveVariable(IntegerVariable i)
Definition integer.h:193
CutGenerator CreateCumulativeTimeTableCutGenerator(SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const AffineExpression &capacity, Model *model)
std::optional< int > DetectMakespan(const std::vector< IntervalVariable > &intervals, const std::vector< AffineExpression > &demands, const AffineExpression &capacity, Model *model)
int64_t SafeDoubleToInt64(double value)
Definition util.h:688
void AppendLinMaxRelaxationPart1(const ConstraintProto &ct, Model *model, LinearRelaxation *relaxation)
CutGenerator CreateNoOverlapCompletionTimeCutGenerator(SchedulingConstraintHelper *helper, Model *model)
CutGenerator CreateMaxAffineCutGenerator(LinearExpression target, IntegerVariable var, std::vector< std::pair< IntegerValue, IntegerValue > > affines, const std::string cut_name, Model *model)
Definition cuts.cc:2702
int ReindexArcs(IntContainer *tails, IntContainer *heads, absl::flat_hash_map< int, int > *mapping_output=nullptr)
Definition circuit.h:210
void AppendCircuitRelaxation(const ConstraintProto &ct, Model *model, LinearRelaxation *relaxation)
Routing relaxation and cut generators.
void AppendElementEncodingRelaxation(Model *m, LinearRelaxation *relaxation)
LinearConstraint ComputeHyperplanAboveSquare(AffineExpression x, AffineExpression square, IntegerValue x_lb, IntegerValue x_ub, Model *model)
Definition cuts.cc:1954
void AppendMaxAffineRelaxation(const ConstraintProto &ct, Model *model, LinearRelaxation *relaxation)
void AppendLinMaxRelaxationPart2(IntegerVariable target, const std::vector< Literal > &alternative_literals, const std::vector< LinearExpression > &exprs, Model *model, LinearRelaxation *relaxation)
bool ExpressionsContainsOnlyOneVar(const ExpressionList &exprs)
Returns true if there exactly one variable appearing in all the expressions.
std::function< IntegerVariable(Model *)> NewIntegerVariable(int64_t lb, int64_t ub)
Definition integer.h:1907
CutGenerator CreateNoOverlapEnergyCutGenerator(SchedulingConstraintHelper *helper, const std::optional< AffineExpression > &makespan, Model *model)
CutGenerator CreateAllDifferentCutGenerator(const std::vector< AffineExpression > &exprs, Model *model)
Definition cuts.cc:2452
bool IntervalIsVariable(const IntervalVariable interval, IntervalsRepository *intervals_repository)
void AppendSquareRelaxation(const ConstraintProto &ct, Model *m, LinearRelaxation *relaxation)
CutGenerator CreateNoOverlapPrecedenceCutGenerator(SchedulingConstraintHelper *helper, Model *model)
void AddLinMaxCutGenerator(const ConstraintProto &ct, Model *m, LinearRelaxation *relaxation)
LinearExpression PositiveVarExpr(const LinearExpression &expr)
Returns the same expression with positive variables.
void AppendBoolAndRelaxation(const ConstraintProto &ct, Model *model, LinearRelaxation *relaxation, ActivityBoundHelper *activity_helper)
void AddAllDiffRelaxationAndCutGenerator(const ConstraintProto &ct, int linearization_level, Model *m, LinearRelaxation *relaxation)
std::function< void(Model *)> ExactlyOneConstraint(const std::vector< Literal > &literals)
Definition sat_solver.h:905
void AppendNoOverlap2dRelaxation(const ConstraintProto &ct, Model *model, LinearRelaxation *relaxation)
Adds the energetic relaxation sum(areas) <= bounding box area.
PositiveOnlyIndex GetPositiveOnlyIndex(IntegerVariable var)
Definition integer.h:199
void AppendAtMostOneRelaxation(const ConstraintProto &ct, Model *model, LinearRelaxation *relaxation)
void AddNoOverlapCutGenerator(SchedulingConstraintHelper *helper, const std::optional< AffineExpression > &makespan, Model *m, LinearRelaxation *relaxation)
void AddNoOverlap2dCutGenerator(const ConstraintProto &ct, Model *m, LinearRelaxation *relaxation)
int NegatedRef(int ref)
Small utility functions to deal with negative variable/literal references.
CutGenerator CreateNoOverlap2dEnergyCutGenerator(SchedulingConstraintHelper *x_helper, SchedulingConstraintHelper *y_helper, SchedulingDemandHelper *x_demands_helper, SchedulingDemandHelper *y_demands_helper, const std::vector< std::vector< LiteralValueValue > > &energies, Model *model)
bool VariableIsPositive(IntegerVariable i)
Definition integer.h:189
std::vector< Literal > CreateAlternativeLiteralsWithView(int num_literals, Model *model, LinearRelaxation *relaxation)
bool LinearExpressionProtosAreEqual(const LinearExpressionProto &a, const LinearExpressionProto &b, int64_t b_scaling)
Returns true iff a == b * b_scaling.
LinearConstraintPropagator< false > IntegerSumLE
void AppendBoolOrRelaxation(const ConstraintProto &ct, Model *model, LinearRelaxation *relaxation)
void AppendRelaxationForEqualityEncoding(IntegerVariable var, const Model &model, LinearRelaxation *relaxation, int *num_tight, int *num_loose)
In SWIG mode, we don't want anything besides these top-level includes.
int64_t CapSub(int64_t x, int64_t y)
int64_t CapProd(int64_t x, int64_t y)
const Variable x
Definition qp_tests.cc:127
int64_t energy
Definition resource.cc:355
int64_t delta
Definition resource.cc:1709
IntervalVar * interval
Definition resource.cc:101
int head
int tail
std::optional< int64_t > end
IntegerLiteral GreaterOrEqual(IntegerValue bound) const
var * coeff + constant >= bound.
Definition integer.h:1696
IntegerLiteral LowerOrEqual(IntegerValue bound) const
var * coeff + constant <= bound.
Definition integer.h:1708
std::unique_ptr< IntegerValue[]> coeffs
std::unique_ptr< IntegerVariable[]> vars
std::vector< std::vector< Literal > > at_most_ones
std::vector< LinearConstraint > linear_constraints
#define SOLVER_LOG(logger,...)
Definition logging.h:109