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