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