OR-Tools  9.2
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
25namespace operations_research {
26namespace 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_;
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;
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,
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.
271void 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.
279bool 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:139
Collection of objects used to extend the Constraint Solver library.
int64_t start