OR-Tools  9.2
presolve.cc
Go to the documentation of this file.
1 // Copyright 2010-2021 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 <cstdint>
17 #include <map>
18 #include <set>
19 
20 #include "absl/strings/match.h"
21 #include "absl/strings/str_format.h"
22 #include "absl/strings/str_join.h"
23 #include "absl/strings/string_view.h"
24 #include "ortools/base/map_util.h"
25 #include "ortools/flatzinc/model.h"
26 #include "ortools/graph/cliques.h"
29 
30 ABSL_FLAG(bool, fz_floats_are_ints, false,
31  "Interpret floats as integers in all variables and constraints.");
32 
33 namespace operations_research {
34 namespace fz {
35 namespace {
36 enum PresolveState { ALWAYS_FALSE, ALWAYS_TRUE, UNDECIDED };
37 
38 template <class T>
39 bool IsArrayBoolean(const std::vector<T>& values) {
40  for (int i = 0; i < values.size(); ++i) {
41  if (values[i] != 0 && values[i] != 1) {
42  return false;
43  }
44  }
45  return true;
46 }
47 
48 template <class T>
49 bool AtMostOne0OrAtMostOne1(const std::vector<T>& values) {
50  CHECK(IsArrayBoolean(values));
51  int num_zero = 0;
52  int num_one = 0;
53  for (T val : values) {
54  if (val) {
55  num_one++;
56  } else {
57  num_zero++;
58  }
59  if (num_one > 1 && num_zero > 1) {
60  return false;
61  }
62  }
63  return true;
64 }
65 
66 template <class T>
67 void AppendIfNotInSet(T* value, absl::flat_hash_set<T*>* s,
68  std::vector<T*>* vec) {
69  if (s->insert(value).second) {
70  vec->push_back(value);
71  }
72  DCHECK_EQ(s->size(), vec->size());
73 }
74 
75 } // namespace
76 
77 // Note on documentation
78 //
79 // In order to document presolve rules, we will use the following naming
80 // convention:
81 // - x, x1, xi, y, y1, yi denote integer variables
82 // - b, b1, bi denote boolean variables
83 // - c, c1, ci denote integer constants
84 // - t, t1, ti denote boolean constants
85 // - => x after a constraint denotes the target variable of this constraint.
86 // Arguments are listed in order.
87 
88 // Propagates cast constraint.
89 // Rule 1:
90 // Input: bool2int(b, c) or bool2int(t, x)
91 // Output: int_eq(...)
92 //
93 // Rule 2:
94 // Input: bool2int(b, x)
95 // Action: Replace all instances of x by b.
96 // Output: inactive constraint
97 void Presolver::PresolveBool2Int(Constraint* ct) {
98  DCHECK_EQ(ct->type, "bool2int");
99  if (ct->arguments[0].HasOneValue() || ct->arguments[1].HasOneValue()) {
100  // Rule 1.
101  UpdateRuleStats("bool2int: rename to int_eq");
102  ct->type = "int_eq";
103  } else {
104  // Rule 2.
105  UpdateRuleStats("bool2int: merge boolean and integer variables.");
106  AddVariableSubstitution(ct->arguments[1].Var(), ct->arguments[0].Var());
107  ct->MarkAsInactive();
108  }
109 }
110 
111 // Minizinc flattens 2d element constraints (x = A[y][z]) into 1d element
112 // constraint with an affine mapping between y, z and the new index.
113 // This rule stores the mapping to reconstruct the 2d element constraint.
114 // This mapping can involve 1 or 2 variables depending if y or z in A[y][z]
115 // is a constant in the model).
116 void Presolver::PresolveStoreAffineMapping(Constraint* ct) {
117  CHECK_EQ(2, ct->arguments[1].variables.size());
118  Variable* const var0 = ct->arguments[1].variables[0];
119  Variable* const var1 = ct->arguments[1].variables[1];
120  const int64_t coeff0 = ct->arguments[0].values[0];
121  const int64_t coeff1 = ct->arguments[0].values[1];
122  const int64_t rhs = ct->arguments[2].Value();
123  if (coeff0 == -1 && !affine_map_.contains(var0)) {
124  affine_map_[var0] = AffineMapping(var1, coeff0, -rhs, ct);
125  UpdateRuleStats("int_lin_eq: store affine mapping");
126  } else if (coeff1 == -1 && !affine_map_.contains(var1)) {
127  affine_map_[var1] = AffineMapping(var0, coeff0, -rhs, ct);
128  UpdateRuleStats("int_lin_eq: store affine mapping");
129  }
130 }
131 
132 void Presolver::PresolveStoreFlatteningMapping(Constraint* ct) {
133  CHECK_EQ(3, ct->arguments[1].variables.size());
134  Variable* const var0 = ct->arguments[1].variables[0];
135  Variable* const var1 = ct->arguments[1].variables[1];
136  Variable* const var2 = ct->arguments[1].variables[2];
137  const int64_t coeff0 = ct->arguments[0].values[0];
138  const int64_t coeff1 = ct->arguments[0].values[1];
139  const int64_t coeff2 = ct->arguments[0].values[2];
140  const int64_t rhs = ct->arguments[2].Value();
141  if (coeff0 == -1 && coeff2 == 1 && !array2d_index_map_.contains(var0)) {
142  array2d_index_map_[var0] =
143  Array2DIndexMapping(var1, coeff1, var2, -rhs, ct);
144  UpdateRuleStats("int_lin_eq: store 2d flattening mapping");
145  } else if (coeff0 == -1 && coeff1 == 1 &&
146  !array2d_index_map_.contains(var0)) {
147  array2d_index_map_[var0] =
148  Array2DIndexMapping(var2, coeff2, var1, -rhs, ct);
149  UpdateRuleStats("int_lin_eq: store 2d flattening mapping");
150  } else if (coeff2 == -1 && coeff1 == 1 &&
151  !array2d_index_map_.contains(var2)) {
152  array2d_index_map_[var2] =
153  Array2DIndexMapping(var0, coeff0, var1, -rhs, ct);
154  UpdateRuleStats("int_lin_eq: store 2d flattening mapping");
155  } else if (coeff2 == -1 && coeff0 == 1 &&
156  !array2d_index_map_.contains(var2)) {
157  array2d_index_map_[var2] =
158  Array2DIndexMapping(var1, coeff1, var0, -rhs, ct);
159  UpdateRuleStats("int_lin_eq: store 2d flattening mapping");
160  }
161 }
162 
163 namespace {
164 bool IsIncreasingAndContiguous(const std::vector<int64_t>& values) {
165  for (int i = 0; i < values.size() - 1; ++i) {
166  if (values[i + 1] != values[i] + 1) {
167  return false;
168  }
169  }
170  return true;
171 }
172 
173 bool AreOnesFollowedByMinusOne(const std::vector<int64_t>& coeffs) {
174  CHECK(!coeffs.empty());
175  for (int i = 0; i < coeffs.size() - 1; ++i) {
176  if (coeffs[i] != 1) {
177  return false;
178  }
179  }
180  return coeffs.back() == -1;
181 }
182 
183 template <class T>
184 bool IsStrictPrefix(const std::vector<T>& v1, const std::vector<T>& v2) {
185  if (v1.size() >= v2.size()) {
186  return false;
187  }
188  for (int i = 0; i < v1.size(); ++i) {
189  if (v1[i] != v2[i]) {
190  return false;
191  }
192  }
193  return true;
194 }
195 } // namespace
196 
197 // Rewrite array element: array_int_element:
198 //
199 // Rule 1:
200 // Input : array_int_element(x0, [c1, .., cn], y) with x0 = a * x + b
201 // Output: array_int_element(x, [c_a1, .., c_am], b) with a * i = b = ai
202 //
203 // Rule 2:
204 // Input : array_int_element(x, [c1, .., cn], y) with x = a * x1 + x2 + b
205 // Output: array_int_element([x1, x2], [c_a1, .., c_am], b, [a, b])
206 // to be interpreted by the extraction process.
207 //
208 // Rule 3:
209 // Input: array_int_element(x, [c1, .., cn], y)
210 // Output array_int_element(x, [c1, .., c{max(x)}], y)
211 //
212 // Rule 4:
213 // Input : array_int_element(x, [c1, .., cn], y) with x0 ci = c0 + i
214 // Output: int_lin_eq([-1, 1], [y, x], 1 - c) (e.g. y = x + c - 1)
215 void Presolver::PresolveSimplifyElement(Constraint* ct) {
216  if (ct->arguments[0].variables.size() != 1) return;
217  Variable* const index_var = ct->arguments[0].Var();
218 
219  // Rule 1.
220  if (affine_map_.contains(index_var)) {
221  const AffineMapping& mapping = affine_map_[index_var];
222  const Domain& domain = mapping.variable->domain;
223  if (domain.is_interval && domain.values.empty()) {
224  // Invalid case. Ignore it.
225  return;
226  }
227  if (domain.values[0] == 0 && mapping.coefficient == 1 &&
228  mapping.offset > 1 && index_var->domain.is_interval) {
229  // Simple translation
230  const int offset = mapping.offset - 1;
231  const int size = ct->arguments[1].values.size();
232  for (int i = 0; i < size - offset; ++i) {
233  ct->arguments[1].values[i] = ct->arguments[1].values[i + offset];
234  }
235  ct->arguments[1].values.resize(size - offset);
236  affine_map_[index_var].constraint->arguments[2].values[0] = -1;
237  affine_map_[index_var].offset = 1;
238  index_var->domain.values[0] -= offset;
239  index_var->domain.values[1] -= offset;
240  UpdateRuleStats("array_int_element: simplify using affine mapping.");
241  return;
242  } else if (mapping.offset + mapping.coefficient > 0 &&
243  domain.values[0] > 0) {
244  const std::vector<int64_t>& values = ct->arguments[1].values;
245  std::vector<int64_t> new_values;
246  for (int64_t i = 1; i <= domain.values.back(); ++i) {
247  const int64_t index = i * mapping.coefficient + mapping.offset - 1;
248  if (index < 0) {
249  return;
250  }
251  if (index > values.size()) {
252  break;
253  }
254  new_values.push_back(values[index]);
255  }
256  // Rewrite constraint.
257  UpdateRuleStats("array_int_element: simplify using affine mapping.");
258  ct->arguments[0].variables[0] = mapping.variable;
259  ct->arguments[0].variables[0]->domain.IntersectWithInterval(
260  1, new_values.size());
261  // TODO(user): Encapsulate argument setters.
262  ct->arguments[1].values.swap(new_values);
263  if (ct->arguments[1].values.size() == 1) {
264  ct->arguments[1].type = Argument::INT_VALUE;
265  }
266  // Reset propagate flag.
267  ct->presolve_propagation_done = false;
268  // Mark old index var and affine constraint as presolved out.
269  mapping.constraint->MarkAsInactive();
270  index_var->active = false;
271  return;
272  }
273  }
274 
275  // Rule 2.
276  if (array2d_index_map_.contains(index_var)) {
277  UpdateRuleStats("array_int_element: rewrite as a 2d element");
278  const Array2DIndexMapping& mapping = array2d_index_map_[index_var];
279  // Rewrite constraint.
280  ct->arguments[0] =
281  Argument::VarRefArray({mapping.variable1, mapping.variable2});
282  std::vector<int64_t> coefs;
283  coefs.push_back(mapping.coefficient);
284  coefs.push_back(1);
285  ct->arguments.push_back(Argument::IntegerList(coefs));
286  ct->arguments.push_back(Argument::IntegerValue(mapping.offset));
287  index_var->active = false;
288  mapping.constraint->MarkAsInactive();
289  return;
290  }
291 
292  // Rule 3.
293  if (index_var->domain.Max() < ct->arguments[1].values.size()) {
294  // Reduce array of values.
295  ct->arguments[1].values.resize(index_var->domain.Max());
296  ct->presolve_propagation_done = false;
297  UpdateRuleStats("array_int_element: reduce array");
298  return;
299  }
300 
301  // Rule 4.
302  if (IsIncreasingAndContiguous(ct->arguments[1].values) &&
303  ct->arguments[2].type == Argument::VAR_REF) {
304  const int64_t start = ct->arguments[1].values.front();
305  Variable* const index = ct->arguments[0].Var();
306  Variable* const target = ct->arguments[2].Var();
307  UpdateRuleStats("array_int_element: rewrite as a linear constraint");
308 
309  if (start == 1) {
310  ct->type = "int_eq";
311  ct->RemoveArg(1);
312  } else {
313  // Rewrite constraint into a int_lin_eq
314  ct->type = "int_lin_eq";
315  ct->arguments[0] = Argument::IntegerList({-1, 1});
316  ct->arguments[1] = Argument::VarRefArray({target, index});
317  ct->arguments[2] = Argument::IntegerValue(1 - start);
318  }
319  }
320 }
321 
322 // Simplifies array_var_int_element
323 //
324 // Input : array_var_int_element(x0, [x1, .., xn], y) with x0 = a * x + b
325 // Output: array_var_int_element(x, [x_a1, .., x_an], b) with a * i = b = ai
326 void Presolver::PresolveSimplifyExprElement(Constraint* ct) {
327  if (ct->arguments[0].variables.size() != 1) return;
328 
329  Variable* const index_var = ct->arguments[0].Var();
330  if (affine_map_.contains(index_var)) {
331  const AffineMapping& mapping = affine_map_[index_var];
332  const Domain& domain = mapping.variable->domain;
333  if ((domain.is_interval && domain.values.empty()) ||
334  domain.values[0] != 1 || mapping.offset + mapping.coefficient <= 0) {
335  // Invalid case. Ignore it.
336  return;
337  }
338  const std::vector<Variable*>& vars = ct->arguments[1].variables;
339  std::vector<Variable*> new_vars;
340  for (int64_t i = domain.values.front(); i <= domain.values.back(); ++i) {
341  const int64_t index = i * mapping.coefficient + mapping.offset - 1;
342  if (index < 0) {
343  return;
344  }
345  if (index >= vars.size()) {
346  break;
347  }
348  new_vars.push_back(vars[index]);
349  }
350  // Rewrite constraint.
351  UpdateRuleStats("array_var_int_element: simplify using affine mapping.");
352  ct->arguments[0].variables[0] = mapping.variable;
353  // TODO(user): Encapsulate argument setters.
354  ct->arguments[1].variables.swap(new_vars);
355  // Mark old index var and affine constraint as presolved out.
356  mapping.constraint->MarkAsInactive();
357  index_var->active = false;
358  } else if (index_var->domain.is_interval &&
359  index_var->domain.values.size() == 2 &&
360  index_var->domain.Max() < ct->arguments[1].variables.size()) {
361  // Reduce array of variables.
362  ct->arguments[1].variables.resize(index_var->domain.Max());
363  UpdateRuleStats("array_var_int_element: reduce array");
364  }
365 }
366 
368  // Should rewrite float constraints.
369  if (absl::GetFlag(FLAGS_fz_floats_are_ints)) {
370  // Treat float variables as int variables, convert constraints to int.
371  for (Constraint* const ct : model->constraints()) {
372  const std::string& id = ct->type;
373  if (id == "int2float") {
374  ct->type = "int_eq";
375  } else if (id == "float_lin_le") {
376  ct->type = "int_lin_le";
377  } else if (id == "float_lin_eq") {
378  ct->type = "int_lin_eq";
379  }
380  }
381  }
382 
383  // Regroup increasing sequence of int_lin_eq([1,..,1,-1], [x1, ..., xn, yn])
384  // into sequence of int_plus(x1, x2, y2), int_plus(y2, x3, y3)...
385  std::vector<Variable*> current_variables;
386  Variable* target_variable = nullptr;
387  Constraint* first_constraint = nullptr;
388  for (Constraint* const ct : model->constraints()) {
389  if (target_variable == nullptr) {
390  if (ct->type == "int_lin_eq" && ct->arguments[0].values.size() == 3 &&
391  AreOnesFollowedByMinusOne(ct->arguments[0].values) &&
392  ct->arguments[1].values.empty() && ct->arguments[2].Value() == 0) {
393  current_variables = ct->arguments[1].variables;
394  target_variable = current_variables.back();
395  current_variables.pop_back();
396  first_constraint = ct;
397  }
398  } else {
399  if (ct->type == "int_lin_eq" &&
400  AreOnesFollowedByMinusOne(ct->arguments[0].values) &&
401  ct->arguments[0].values.size() == current_variables.size() + 2 &&
402  IsStrictPrefix(current_variables, ct->arguments[1].variables)) {
403  current_variables = ct->arguments[1].variables;
404  // Rewrite ct into int_plus.
405  ct->type = "int_plus";
406  ct->arguments.clear();
407  ct->arguments.push_back(Argument::VarRef(target_variable));
408  ct->arguments.push_back(
409  Argument::VarRef(current_variables[current_variables.size() - 2]));
410  ct->arguments.push_back(Argument::VarRef(current_variables.back()));
411  target_variable = current_variables.back();
412  current_variables.pop_back();
413 
414  // We clean the first constraint too.
415  if (first_constraint != nullptr) {
416  first_constraint = nullptr;
417  }
418  } else {
419  current_variables.clear();
420  target_variable = nullptr;
421  }
422  }
423  }
424 
425  // First pass.
426  for (Constraint* const ct : model->constraints()) {
427  if (ct->active && ct->type == "bool2int") {
428  PresolveBool2Int(ct);
429  } else if (ct->active && ct->type == "int_lin_eq" &&
430  ct->arguments[1].variables.size() == 2 &&
431  ct->strong_propagation) {
432  PresolveStoreAffineMapping(ct);
433  } else if (ct->active && ct->type == "int_lin_eq" &&
434  ct->arguments[1].variables.size() == 3 &&
435  ct->strong_propagation) {
436  PresolveStoreFlatteningMapping(ct);
437  }
438  }
439  if (!var_representative_map_.empty()) {
440  // Some new substitutions were introduced. Let's process them.
441  SubstituteEverywhere(model);
442  var_representative_map_.clear();
443  var_representative_vector_.clear();
444  }
445 
446  // Second pass.
447  for (Constraint* const ct : model->constraints()) {
448  if (ct->type == "array_int_element" || ct->type == "array_bool_element") {
449  PresolveSimplifyElement(ct);
450  }
451  if (ct->type == "array_var_int_element" ||
452  ct->type == "array_var_bool_element") {
453  PresolveSimplifyExprElement(ct);
454  }
455  }
456 
457  // Report presolve rules statistics.
458  if (!successful_rules_.empty()) {
459  for (const auto& rule : successful_rules_) {
460  if (rule.second == 1) {
461  SOLVER_LOG(logger_, " - rule '", rule.first, "' was applied 1 time");
462  } else {
463  SOLVER_LOG(logger_, " - rule '", rule.first, "' was applied ",
464  rule.second, " times");
465  }
466  }
467  }
468 }
469 
470 // ----- Substitution support -----
471 
472 void Presolver::AddVariableSubstitution(Variable* from, Variable* to) {
473  CHECK(from != nullptr);
474  CHECK(to != nullptr);
475  // Apply the substitutions, if any.
476  from = FindRepresentativeOfVar(from);
477  to = FindRepresentativeOfVar(to);
478  if (to->temporary) {
479  // Let's switch to keep a non temporary as representative.
480  Variable* tmp = to;
481  to = from;
482  from = tmp;
483  }
484  if (from != to) {
485  CHECK(to->Merge(from->name, from->domain, from->temporary));
486  from->active = false;
487  var_representative_map_[from] = to;
488  var_representative_vector_.push_back(from);
489  }
490 }
491 
492 Variable* Presolver::FindRepresentativeOfVar(Variable* var) {
493  if (var == nullptr) return nullptr;
494  Variable* start_var = var;
495  // First loop: find the top parent.
496  for (;;) {
497  Variable* parent = gtl::FindWithDefault(var_representative_map_, var, var);
498  if (parent == var) break;
499  var = parent;
500  }
501  // Second loop: attach all the path to the top parent.
502  while (start_var != var) {
503  Variable* const parent = var_representative_map_[start_var];
504  var_representative_map_[start_var] = var;
505  start_var = parent;
506  }
507  return gtl::FindWithDefault(var_representative_map_, var, var);
508 }
509 
510 void Presolver::SubstituteEverywhere(Model* model) {
511  // Rewrite the constraints.
512  for (Constraint* const ct : model->constraints()) {
513  if (ct != nullptr && ct->active) {
514  for (int i = 0; i < ct->arguments.size(); ++i) {
515  Argument& argument = ct->arguments[i];
516  switch (argument.type) {
517  case Argument::VAR_REF:
519  for (int i = 0; i < argument.variables.size(); ++i) {
520  Variable* const old_var = argument.variables[i];
521  Variable* const new_var = FindRepresentativeOfVar(old_var);
522  if (new_var != old_var) {
523  argument.variables[i] = new_var;
524  }
525  }
526  break;
527  }
528  default: {
529  }
530  }
531  }
532  }
533  }
534  // Rewrite the search.
535  for (Annotation* const ann : model->mutable_search_annotations()) {
536  SubstituteAnnotation(ann);
537  }
538  // Rewrite the output.
539  for (SolutionOutputSpecs* const output : model->mutable_output()) {
540  output->variable = FindRepresentativeOfVar(output->variable);
541  for (int i = 0; i < output->flat_variables.size(); ++i) {
542  output->flat_variables[i] =
543  FindRepresentativeOfVar(output->flat_variables[i]);
544  }
545  }
546  // Do not forget to merge domain that could have evolved asynchronously
547  // during presolve.
548  for (const auto& iter : var_representative_map_) {
549  iter.second->domain.IntersectWithDomain(iter.first->domain);
550  }
551 
552  // Change the objective variable.
553  Variable* const current_objective = model->objective();
554  if (current_objective == nullptr) return;
555  Variable* const new_objective = FindRepresentativeOfVar(current_objective);
556  if (new_objective != current_objective) {
557  model->SetObjective(new_objective);
558  }
559 }
560 
561 void Presolver::SubstituteAnnotation(Annotation* ann) {
562  // TODO(user): Remove recursion.
563  switch (ann->type) {
566  for (int i = 0; i < ann->annotations.size(); ++i) {
567  SubstituteAnnotation(&ann->annotations[i]);
568  }
569  break;
570  }
571  case Annotation::VAR_REF:
573  for (int i = 0; i < ann->variables.size(); ++i) {
574  ann->variables[i] = FindRepresentativeOfVar(ann->variables[i]);
575  }
576  break;
577  }
578  default: {
579  }
580  }
581 }
582 
583 } // namespace fz
584 } // namespace operations_research
#define CHECK(condition)
Definition: base/logging.h:495
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:69
bool IsArrayBoolean(const std::vector< T > &values)
ABSL_FLAG(bool, fz_floats_are_ints, false, "Interpret floats as integers in all variables and constraints.")
GRBmodel * model
static Argument IntegerValue(int64_t value)
Definition: model.cc:490
int index
Definition: pack.cc:509
bool Merge(const std::string &other_name, const Domain &other_domain, bool other_temporary)
Definition: model.cc:772
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:702
static Argument VarRefArray(std::vector< Variable * > vars)
Definition: model.cc:526
const Collection::value_type::second_type & FindWithDefault(const Collection &collection, const typename Collection::value_type::first_type &key, const typename Collection::value_type::second_type &value)
Definition: map_util.h:29
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:890
static Argument VarRef(Variable *const var)
Definition: model.cc:519
Collection of objects used to extend the Constraint Solver library.
IntVar * var
Definition: expr_array.cc:1874
static Argument IntegerList(std::vector< int64_t > values)
Definition: model.cc:505
int64_t value
const Constraint * ct