OR-Tools  9.3
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#include <memory>
19#include <string>
20#include <vector>
21
22#include "absl/types/span.h"
26#include "ortools/sat/integer.h"
28
29namespace operations_research {
30namespace 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_mode.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 // Reset 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 void EndFirstPhase();
109 void EndSecondPhase();
110
111 // This is true if this variable was never restricted by any call. We can thus
112 // fix it to its lower bound.
113 bool CanFreelyDecrease(int ref) const;
114 bool CanFreelyDecrease(IntegerVariable var) const;
115
116 // Returns a set of variable dominating the given ones. Note that to keep the
117 // algo efficient, this might not include all the possible dominations.
118 //
119 // Note: we never include as part of the dominating candidate variables that
120 // can freely increase.
121 absl::Span<const IntegerVariable> DominatingVariables(int ref) const;
122 absl::Span<const IntegerVariable> DominatingVariables(
123 IntegerVariable var) const;
124
125 // Returns readable string with the possible valid combinations of the form
126 // (var++/--, dom++/--) to facilitate debugging.
127 std::string DominationDebugString(IntegerVariable var) const;
128
129 private:
130 struct IntegerVariableWithRank {
131 IntegerVariable var;
132 int part;
133 int64_t rank;
134
135 bool operator<(const IntegerVariableWithRank& o) const {
136 return rank < o.rank;
137 }
138 };
139
140 // This refine the partition can_dominate_partition_ with the given set.
141 void RefinePartition(std::vector<int>* vars);
142
143 // Convert the input from the public API into tmp_ranks_.
144 void MakeRankEqualToStartOfPart(absl::Span<IntegerVariableWithRank> span);
145 void FillTempRanks(bool reverse_references,
146 absl::Span<const int> enforcements,
147 absl::Span<const int> refs,
148 absl::Span<const int64_t> coeffs);
149
150 // First phase functions. We will keep for each variable a list of possible
151 // candidates which is as short as possible.
152 absl::Span<const IntegerVariable> InitialDominatingCandidates(
153 IntegerVariable var) const;
154 void ProcessTempRanks();
155 void Initialize(absl::Span<IntegerVariableWithRank> span);
156
157 // Second phase function to filter the current candidate lists.
158 void FilterUsingTempRanks();
159
160 // Debug function.
161 void CheckUsingTempRanks();
162
163 // Starts at zero on Reset(), move to one on EndFirstPhase() and to 2 on
164 // EndSecondPhase(). This is used for debug checks and to control what happen
165 // on the constraint processing functions.
166 int phase_ = 0;
167
168 // The variables will be sorted by non-decreasking rank. The rank is also the
169 // start of the first variable in tmp_ranks_ with this rank.
170 //
171 // Note that the rank should be int, but to reuse the same vector when we
172 // construct it, we need int64_t. See FillTempRanks().
173 std::vector<IntegerVariableWithRank> tmp_ranks_;
174
175 // This do not change after EndFirstPhase().
176 //
177 // We will add to the Dynamic partion, a set of subset S, each meaning that
178 // any variable in S can only dominate or be dominated by another variable in
179 // S.
180 std::vector<int> tmp_vars_;
181 std::unique_ptr<DynamicPartition> partition_;
183
184 // For all one sided constraints, we keep the bitmap of constraint indices
185 // modulo 64 that block on the lower side each variable.
186 int64_t ct_index_for_signature_ = 0;
188
189 // Used by FilterUsingTempRanks().
190 int num_vars_with_negation_;
192
193 // We don't use absl::Span() because the underlying buffer can be resized.
194 // This however serve the same purpose.
195 struct IntegerVariableSpan {
196 int start = 0;
197 int size = 0;
198 };
199
200 // This hold the first phase best candidate.
201 // Warning, the initial candidates span can overlap in the shared_buffer_.
202 std::vector<IntegerVariable> shared_buffer_;
204
205 // This will hold the final result.
206 // Buffer with independent content for each vars.
207 std::vector<IntegerVariable> buffer_;
209};
210
211// This detects variables that can move freely in one direction, or that can
212// move freely as long as their value do not cross a bound.
213//
214// TODO(user): This is actually an important step to do before scaling as it can
215// usually reduce really large bounds!
217 public:
218 // Reset the class to a clean state.
219 // This must be called before processing the constraints.
220 void Reset(int num_variables) {
221 can_freely_decrease_until_.assign(2 * num_variables, kMinIntegerValue);
222 num_locks_.assign(2 * num_variables, 0);
223 locking_ct_index_.assign(2 * num_variables, -1);
224 }
225
226 // All constraints should be mapped to one of more call to these functions.
227 void CannotDecrease(absl::Span<const int> refs, int ct_index = -1);
228 void CannotIncrease(absl::Span<const int> refs, int ct_index = -1);
229 void CannotMove(absl::Span<const int> refs);
230
231 // Most of the logic here deals with linear constraints.
232 template <typename LinearProto>
233 void ProcessLinearConstraint(bool is_objective,
235 const LinearProto& linear, int64_t min_activity,
236 int64_t max_activity);
237
238 // Once ALL constraints have been processed, call this to fix variables or
239 // reduce their domain if possible.
240 //
241 // Note that this also tighten some constraint that are the only one blocking
242 // in one direction. Currently we only do that for implication, so that if we
243 // have two Booleans such that a + b <= 1 we transform that to = 1 and we
244 // remove one variable since we have now an equivalence relation.
246
247 // The given ref can always freely decrease until the returned value.
248 // Note that this does not take into account the domain of the variable.
249 int64_t CanFreelyDecreaseUntil(int ref) const {
250 return can_freely_decrease_until_[RefToIntegerVariable(ref)].value();
251 }
252
253 private:
254 // We encode proto ref as IntegerVariable for indexing vectors.
255 static IntegerVariable RefToIntegerVariable(int ref) {
256 return RefIsPositive(ref) ? IntegerVariable(2 * ref)
257 : IntegerVariable(2 * NegatedRef(ref) + 1);
258 }
259
260 // Starts with kMaxIntegerValue, and decrease as constraints are processed.
261 absl::StrongVector<IntegerVariable, IntegerValue> can_freely_decrease_until_;
262
263 // How many times can_freely_decrease_until_[var] was set by a constraints.
264 // If only one constraint is blocking, we can do more presolve.
266
267 // If num_locks_[var] == 1, this will be the unique constraint that block var
268 // in this direction. Note that it can be set to -1 if this wasn't recorded.
270};
271
272// Detect the variable dominance relations within the given model. Note that
273// to avoid doing too much work, we might miss some relations. This does two
274// full scan of the model.
275void DetectDominanceRelations(const PresolveContext& context,
276 VarDomination* var_domination,
277 DualBoundStrengthening* dual_bound_strengthening);
278
279// Once detected, exploit the dominance relations that appear in the same
280// constraint. This does a full scan of the model.
281//
282// Return false if the problem is infeasible.
283bool ExploitDominanceRelations(const VarDomination& var_domination,
284 PresolveContext* context);
285
286} // namespace sat
287} // namespace operations_research
288
289#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.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