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