OR-Tools  9.0
var_domination.h
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 
14 #ifndef OR_TOOLS_SAT_VAR_DOMINATION_H_
15 #define OR_TOOLS_SAT_VAR_DOMINATION_H_
16 
17 #include <cstdint>
18 
22 #include "ortools/sat/integer.h"
24 
25 namespace operations_research {
26 namespace sat {
27 
28 // A variable X is say to dominate a variable Y if, from any feasible solution,
29 // doing X++ and Y-- is also feasible (modulo the domain of X and Y) and has the
30 // same or a better objective value.
31 //
32 // Note that we also look for dominance between the negation of the variables.
33 // So we detect all (X++, Y++), (X--, Y--), (X++, Y--) and (X--, Y++) cases.
34 // We reuse both ref / Negated(ref) and translate that to IntegerVariable for
35 // indexing vectors.
36 //
37 // Once detected, dominance relation can lead to more propagation. Note however,
38 // that we will loose feasible solution that are dominated by better solutions.
39 // In particular, in a linear constraint sum coeff * Xi <= rhs with positive
40 // coeff, if an X is dominated by a set of other variable in the constraint,
41 // then its upper bound can be propagated assuming the dominating variables are
42 // at their upper bound. This can in many case result in X being fixed to its
43 // lower bound.
44 //
45 // TODO(user): We have a lot of benchmarks and tests that shows that we don't
46 // report wrong relations, but we lack unit test that make sure we don't miss
47 // any. Try to improve the situation.
49  public:
51 
52  // This is the translation used from "ref" to IntegerVariable. The API
53  // understand the cp_mode.proto ref, but internally we only store
54  // IntegerVariable.
55  static IntegerVariable RefToIntegerVariable(int ref) {
56  return RefIsPositive(ref) ? IntegerVariable(2 * ref)
57  : IntegerVariable(2 * NegatedRef(ref) + 1);
58  }
59  static int IntegerVariableToRef(IntegerVariable var) {
60  return VariableIsPositive(var) ? var.value() / 2
61  : NegatedRef(var.value() / 2);
62  }
63 
64  // Reset the class to a clean state.
65  // At the beginning, we assume that there is no constraint.
66  void Reset(int num_variables);
67 
68  // These functions are used to encode all of our constraints.
69  // The algorithm work in two passes, so one should do:
70  // - 1/ Convert all problem constraints to one or more calls
71  // - 2/ Call EndFirstPhase()
72  // - 3/ Redo 1. Only the one sided constraint need to be processed again. But
73  // calling the others will just do nothing, so it is fine too.
74  // - 4/ Call EndSecondPhase()
75  //
76  // The names are pretty self-explanatory. A few linear constraint ex:
77  // - To encode terms = cte, one should call ActivityShouldNotChange()
78  // - To encode terms >= cte, one should call ActivityShouldNotDecrease()
79  // - To encode terms <= cte, one should call ActivityShouldNotIncrease()
80  //
81  // The coeffs vector can be left empty, in which case all variable are assumed
82  // to have the same coefficients. CanOnlyDominateEachOther() is basically the
83  // same as ActivityShouldNotChange() without any coefficients.
84  //
85  // Note(user): It is better complexity wise to first refine the underlying
86  // partition as much as possible, and then process all
87  // ActivityShouldNotIncrease() and ActivityShouldNotDecrease() in two passes.
88  // Experiment with it, it might require changing the API slightly since the
89  // increase / decrease functions also refine the partition.
90  void CanOnlyDominateEachOther(absl::Span<const int> refs);
91  void ActivityShouldNotChange(absl::Span<const int> refs,
92  absl::Span<const int64_t> coeffs);
93  void ActivityShouldNotDecrease(absl::Span<const int> enforcements,
94  absl::Span<const int> refs,
95  absl::Span<const int64_t> coeffs);
96  void ActivityShouldNotIncrease(absl::Span<const int> enforcements,
97  absl::Span<const int> refs,
98  absl::Span<const int64_t> coeffs);
99 
100  // EndFirstPhase() must be called once all constraints have been processed
101  // once. One then needs to redo the calls to ActivityShouldNotIncrease() and
102  // ActivityShouldNotDecrease(). And finally call EndSecondPhase() before
103  // querying the domination information.
104  void EndFirstPhase();
105  void EndSecondPhase();
106 
107  // This is true if this variable was never restricted by any call. We can thus
108  // fix it to its lower bound.
109  bool CanFreelyDecrease(int ref) const;
110  bool CanFreelyDecrease(IntegerVariable var) const;
111 
112  // Returns a set of variable dominating the given ones. Note that to keep the
113  // algo efficient, this might not include all the possible dominations.
114  //
115  // Note: we never include as part of the dominating candidate variables that
116  // can freely increase.
117  absl::Span<const IntegerVariable> DominatingVariables(int ref) const;
118  absl::Span<const IntegerVariable> DominatingVariables(
119  IntegerVariable var) const;
120 
121  // Returns readable string with the possible valid combinations of the form
122  // (var++/--, dom++/--) to facilitate debugging.
123  std::string DominationDebugString(IntegerVariable var) const;
124 
125  private:
126  struct IntegerVariableWithRank {
127  IntegerVariable var;
128  int part;
129  int64_t rank;
130 
131  bool operator<(const IntegerVariableWithRank& o) const {
132  return rank < o.rank;
133  }
134  };
135 
136  // This refine the partition can_dominate_partition_ with the given set.
137  void RefinePartition(std::vector<int>* vars);
138 
139  // Convert the input from the public API into tmp_ranks_.
140  void MakeRankEqualToStartOfPart(absl::Span<IntegerVariableWithRank> span);
141  void FillTempRanks(bool reverse_references,
142  absl::Span<const int> enforcements,
143  absl::Span<const int> refs,
144  absl::Span<const int64_t> coeffs);
145 
146  // First phase functions. We will keep for each variable a list of possible
147  // candidates which is as short as possible.
148  absl::Span<const IntegerVariable> InitialDominatingCandidates(
149  IntegerVariable var) const;
150  void ProcessTempRanks();
151  void Initialize(absl::Span<IntegerVariableWithRank> span);
152 
153  // Second phase function to filter the current candidate lists.
154  void FilterUsingTempRanks();
155 
156  // Debug function.
157  void CheckUsingTempRanks();
158 
159  // Starts at zero on Reset(), move to one on EndFirstPhase() and to 2 on
160  // EndSecondPhase(). This is used for debug checks and to control what happen
161  // on the constraint processing functions.
162  int phase_ = 0;
163 
164  // The variables will be sorted by non-decreasking rank. The rank is also the
165  // start of the first variable in tmp_ranks_ with this rank.
166  //
167  // Note that the rank should be int, but to reuse the same vector when we
168  // construct it, we need int64_t. See FillTempRanks().
169  std::vector<IntegerVariableWithRank> tmp_ranks_;
170 
171  // This do not change after EndFirstPhase().
172  //
173  // We will add to the Dynamic partion, a set of subset S, each meaning that
174  // any variable in S can only dominate or be dominated by another variable in
175  // S.
176  std::vector<int> tmp_vars_;
177  std::unique_ptr<DynamicPartition> partition_;
178  absl::StrongVector<IntegerVariable, bool> can_freely_decrease_;
179 
180  // For all one sided constraints, we keep the bitmap of constraint indices
181  // modulo 64 that block on the lower side each variable.
182  int64_t ct_index_for_signature_ = 0;
183  absl::StrongVector<IntegerVariable, uint64_t> block_down_signatures_;
184 
185  // Used by FilterUsingTempRanks().
186  int num_vars_with_negation_;
188 
189  // We don't use absl::Span() because the underlying buffer can be resized.
190  // This however serve the same purpose.
191  struct IntegerVariableSpan {
192  int start = 0;
193  int size = 0;
194  };
195 
196  // This hold the first phase best candidate.
197  // Warning, the initial candidates span can overlap in the shared_buffer_.
198  std::vector<IntegerVariable> shared_buffer_;
200 
201  // This will hold the final result.
202  // Buffer with independent content for each vars.
203  std::vector<IntegerVariable> buffer_;
205 };
206 
207 // This detects variables that can move freely in one direction, or that can
208 // move freely as long as their value do not cross a bound.
209 //
210 // TODO(user): This is actually an important step to do before scaling as it can
211 // usually reduce really large bounds!
213  public:
214  // Reset the class to a clean state.
215  // This must be called before processing the constraints.
216  void Reset(int num_variables) {
217  can_freely_decrease_until_.assign(2 * num_variables, kMinIntegerValue);
218  num_locks_.assign(2 * num_variables, 0);
219  locking_ct_index_.assign(2 * num_variables, -1);
220  }
221 
222  // All constraints should be mapped to one of more call to these functions.
223  void CannotDecrease(absl::Span<const int> refs, int ct_index = -1);
224  void CannotIncrease(absl::Span<const int> refs, int ct_index = -1);
225  void CannotMove(absl::Span<const int> refs);
226 
227  // Most of the logic here deals with linear constraints.
228  template <typename LinearProto>
229  void ProcessLinearConstraint(bool is_objective,
230  const PresolveContext& context,
231  const LinearProto& linear, int64_t min_activity,
232  int64_t max_activity);
233 
234  // Once ALL constraints have been processed, call this to fix variables or
235  // reduce their domain if possible.
236  //
237  // Note that this also tighten some constraint that are the only one blocking
238  // in one direction. Currently we only do that for implication, so that if we
239  // have two Booleans such that a + b <= 1 we transform that to = 1 and we
240  // remove one variable since we have now an equivalence relation.
242 
243  // The given ref can always freely decrease until the returned value.
244  // Note that this does not take into account the domain of the variable.
245  int64_t CanFreelyDecreaseUntil(int ref) const {
246  return can_freely_decrease_until_[RefToIntegerVariable(ref)].value();
247  }
248 
249  private:
250  // We encode proto ref as IntegerVariable for indexing vectors.
251  static IntegerVariable RefToIntegerVariable(int ref) {
252  return RefIsPositive(ref) ? IntegerVariable(2 * ref)
253  : IntegerVariable(2 * NegatedRef(ref) + 1);
254  }
255 
256  // Starts with kMaxIntegerValue, and decrease as constraints are processed.
257  absl::StrongVector<IntegerVariable, IntegerValue> can_freely_decrease_until_;
258 
259  // How many times can_freely_decrease_until_[var] was set by a constraints.
260  // If only one constraint is blocking, we can do more presolve.
262 
263  // If num_locks_[var] == 1, this will be the unique constraint that block var
264  // in this direction. Note that it can be set to -1 if this wasn't recorded.
266 };
267 
268 // Detect the variable dominance relations within the given model. Note that
269 // to avoid doing too much work, we might miss some relations. This does two
270 // full scan of the model.
271 void DetectDominanceRelations(const PresolveContext& context,
272  VarDomination* var_domination,
273  DualBoundStrengthening* dual_bound_strengthening);
274 
275 // Once detected, exploit the dominance relations that appear in the same
276 // constraint. This does a full scan of the model.
277 //
278 // Return false if the problem is infeasible.
279 bool ExploitDominanceRelations(const VarDomination& var_domination,
280  PresolveContext* context);
281 
282 } // namespace sat
283 } // namespace operations_research
284 
285 #endif // OR_TOOLS_SAT_VAR_DOMINATION_H_
void assign(size_type n, const value_type &val)
void ProcessLinearConstraint(bool is_objective, const PresolveContext &context, const LinearProto &linear, int64_t min_activity, int64_t max_activity)
void CannotMove(absl::Span< const int > refs)
void CannotIncrease(absl::Span< const int > refs, int ct_index=-1)
void CannotDecrease(absl::Span< const int > refs, int ct_index=-1)
void ActivityShouldNotIncrease(absl::Span< const int > enforcements, absl::Span< const int > refs, absl::Span< const int64_t > coeffs)
void ActivityShouldNotChange(absl::Span< const int > refs, absl::Span< const int64_t > coeffs)
static int IntegerVariableToRef(IntegerVariable var)
void ActivityShouldNotDecrease(absl::Span< const int > enforcements, absl::Span< const int > refs, absl::Span< const int64_t > coeffs)
absl::Span< const IntegerVariable > DominatingVariables(int ref) const
std::string DominationDebugString(IntegerVariable var) const
static IntegerVariable RefToIntegerVariable(int ref)
void CanOnlyDominateEachOther(absl::Span< const int > refs)
IntVar * var
Definition: expr_array.cc:1874
GurobiMPCallbackContext * context
bool RefIsPositive(int ref)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
void DetectDominanceRelations(const PresolveContext &context, VarDomination *var_domination, DualBoundStrengthening *dual_bound_strengthening)
bool ExploitDominanceRelations(const VarDomination &var_domination, PresolveContext *context)
bool VariableIsPositive(IntegerVariable i)
Definition: integer.h:135
Collection of objects used to extend the Constraint Solver library.