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