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