Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
var_domination.h
Go to the documentation of this file.
1// Copyright 2010-2024 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:
54 VarDomination() = default;
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_;
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;
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_;
208 initial_candidates_;
209
210 // This will hold the final result.
211 // Buffer with independent content for each vars.
212 std::vector<IntegerVariable> buffer_;
213 std::vector<IntegerVariable> other_buffer_;
215 dominating_vars_;
216};
217
218// This detects variables that can move freely in one direction, or that can
219// move freely as long as their value do not cross a bound.
220//
221// TODO(user): This is actually an important step to do before scaling as it can
222// usually reduce really large bounds!
224 public:
225 // Reset the class to a clean state.
226 // This must be called before processing the constraints.
227 void Reset(int num_variables) {
228 can_freely_decrease_until_.assign(2 * num_variables, kMinIntegerValue);
229 num_locks_.assign(2 * num_variables, 0);
230 locking_ct_index_.assign(2 * num_variables, -1);
231 }
232
233 // All constraints should be mapped to one of more call to these functions.
234 void CannotDecrease(absl::Span<const int> refs, int ct_index = -1);
235 void CannotIncrease(absl::Span<const int> refs, int ct_index = -1);
236 void CannotMove(absl::Span<const int> refs, int ct_index = -1);
237
238 // Most of the logic here deals with linear constraints.
239 template <typename LinearProto>
240 void ProcessLinearConstraint(bool is_objective,
242 const LinearProto& linear, int64_t min_activity,
243 int64_t max_activity, int ct_index = -1);
244
245 // Once ALL constraints have been processed, call this to fix variables or
246 // reduce their domain if possible.
247 //
248 // Note that this also tighten some constraint that are the only one blocking
249 // in one direction. Currently we only do that for implication, so that if we
250 // have two Booleans such that a + b <= 1 we transform that to = 1 and we
251 // remove one variable since we have now an equivalence relation.
253
254 // The given ref can always freely decrease until the returned value.
255 // Note that this does not take into account the domain of the variable.
256 int64_t CanFreelyDecreaseUntil(int ref) const {
257 return can_freely_decrease_until_[RefToIntegerVariable(ref)].value();
258 }
259
260 // Reset on each Strengthen() call.
261 int NumDeletedConstraints() const { return num_deleted_constraints_; }
262
263 private:
264 // We encode proto ref as IntegerVariable for indexing vectors.
265 static IntegerVariable RefToIntegerVariable(int ref) {
266 return RefIsPositive(ref) ? IntegerVariable(2 * ref)
267 : IntegerVariable(2 * NegatedRef(ref) + 1);
268 }
269
270 // Starts with kMaxIntegerValue, and decrease as constraints are processed.
272 can_freely_decrease_until_;
273
274 // How many times can_freely_decrease_until_[var] was set by a constraints.
275 // If only one constraint is blocking, we can do more presolve.
277
278 // If num_locks_[var] == 1, this will be the unique constraint that block var
279 // in this direction. Note that it can be set to -1 if this wasn't recorded.
281
282 int num_deleted_constraints_ = 0;
283};
284
285// Detects the variable dominance relations within the given model. Note that
286// to avoid doing too much work, we might miss some relations.
287void ScanModelForDominanceDetection(PresolveContext& context,
288 VarDomination* var_domination);
289
290// Once detected, exploit the dominance relations that appear in the same
291// constraint. This does a full scan of the model.
292//
293// Return false if the problem is infeasible.
294bool ExploitDominanceRelations(const VarDomination& var_domination,
295 PresolveContext* context);
296
297// Scan the model so that dual_bound_strengthening.Strenghten() works.
299 const PresolveContext& context,
300 DualBoundStrengthening* dual_bound_strengthening);
301
302} // namespace sat
303} // namespace operations_research
304
305#endif // OR_TOOLS_SAT_VAR_DOMINATION_H_
void CannotIncrease(absl::Span< const int > refs, int ct_index=-1)
void CannotDecrease(absl::Span< const int > refs, int ct_index=-1)
All constraints should be mapped to one of more call to these functions.
void CannotMove(absl::Span< const int > refs, int ct_index=-1)
int NumDeletedConstraints() const
Reset on each Strengthen() call.
void ProcessLinearConstraint(bool is_objective, const PresolveContext &context, const LinearProto &linear, int64_t min_activity, int64_t max_activity, int ct_index=-1)
Most of the logic here deals with linear constraints.
static int IntegerVariableToRef(IntegerVariable var)
absl::Span< const IntegerVariable > DominatingVariables(int ref) const
static IntegerVariable RefToIntegerVariable(int ref)
std::string DominationDebugString(IntegerVariable var) const
void CanOnlyDominateEachOther(absl::Span< const int > refs)
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)
void ActivityShouldNotDecrease(absl::Span< const int > enforcements, absl::Span< const int > refs, absl::Span< const int64_t > coeffs)
void assign(size_type n, const value_type &val)
IntVar * var
GurobiMPCallbackContext * context
int ct_index
bool ExploitDominanceRelations(const VarDomination &var_domination, PresolveContext *context)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
void ScanModelForDualBoundStrengthening(const PresolveContext &context, DualBoundStrengthening *dual_bound_strengthening)
Scan the model so that dual_bound_strengthening.Strenghten() works.
void ScanModelForDominanceDetection(PresolveContext &context, VarDomination *var_domination)
int NegatedRef(int ref)
Small utility functions to deal with negative variable/literal references.
bool VariableIsPositive(IntegerVariable i)
Definition integer.h:189
In SWIG mode, we don't want anything besides these top-level includes.