OR-Tools  9.3
linear_constraint_manager.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_LINEAR_CONSTRAINT_MANAGER_H_
15#define OR_TOOLS_SAT_LINEAR_CONSTRAINT_MANAGER_H_
16
17#include <algorithm>
18#include <cstddef>
19#include <cstdint>
20#include <string>
21#include <utility>
22#include <vector>
23
24#include "absl/container/btree_map.h"
25#include "absl/container/flat_hash_map.h"
26#include "absl/container/flat_hash_set.h"
27#include "absl/strings/string_view.h"
30#include "ortools/sat/integer.h"
32#include "ortools/sat/model.h"
33#include "ortools/sat/sat_parameters.pb.h"
37
38namespace operations_research {
39namespace sat {
40
41// This class holds a list of globally valid linear constraints and has some
42// logic to decide which one should be part of the LP relaxation. We want more
43// for a better relaxation, but for efficiency we do not want to have too much
44// constraints while solving the LP.
45//
46// This class is meant to contain all the initial constraints of the LP
47// relaxation and to get new cuts as they are generated. Thus, it can both
48// manage cuts but also only add the initial constraints lazily if there is too
49// many of them.
51 public:
54 double l2_norm = 0.0;
55 int64_t inactive_count = 0;
58 bool is_in_lp = false;
59 size_t hash;
60 double current_score = 0.0;
61
62 // Updated only for deletable constraints. This is incremented every time
63 // ChangeLp() is called and the constraint is active in the LP or not in the
64 // LP and violated.
65 double active_count = 0.0;
66
67 // For now, we mark all the generated cuts as deletable and the problem
68 // constraints as undeletable.
69 // TODO(user): We can have a better heuristics. Some generated good cuts
70 // can be marked undeletable and some unused problem specified constraints
71 // can be marked deletable.
72 bool is_deletable = false;
73 };
74
76 : sat_parameters_(*model->GetOrCreate<SatParameters>()),
77 integer_trail_(*model->GetOrCreate<IntegerTrail>()),
78 time_limit_(model->GetOrCreate<TimeLimit>()),
79 model_(model),
80 logger_(model->GetOrCreate<SolverLogger>()) {}
81
82 // Add a new constraint to the manager. Note that we canonicalize constraints
83 // and merge the bounds of constraints with the same terms. We also perform
84 // basic preprocessing. If added is given, it will be set to true if this
85 // constraint was actually a new one and to false if it was dominated by an
86 // already existing one.
87 DEFINE_STRONG_INDEX_TYPE(ConstraintIndex);
88 ConstraintIndex Add(LinearConstraint ct, bool* added = nullptr);
89
90 // Same as Add(), but logs some information about the newly added constraint.
91 // Cuts are also handled slightly differently than normal constraints.
92 //
93 // Returns true if a new cut was added and false if this cut is not
94 // efficacious or if it is a duplicate of an already existing one.
95 bool AddCut(LinearConstraint ct, std::string type_name,
97 std::string extra_info = "");
98
99 // The objective is used as one of the criterion to score cuts.
100 // The more a cut is parallel to the objective, the better its score is.
101 //
102 // Currently this should only be called once per IntegerVariable (Checked). It
103 // is easy to support dynamic modification if it becomes needed.
104 void SetObjectiveCoefficient(IntegerVariable var, IntegerValue coeff);
105
106 // Heuristic to decides what LP is best solved next. The given lp_solution
107 // should usually be the optimal solution of the LP returned by GetLp() before
108 // this call, but is just used as an heuristic.
109 //
110 // The current solution state is used for detecting inactive constraints. It
111 // is also updated correctly on constraint deletion/addition so that the
112 // simplex can be fully iterative on restart by loading this modified state.
113 //
114 // Returns true iff LpConstraints() will return a different LP than before.
116 glop::BasisState* solution_state);
117
118 // This can be called initially to add all the current constraint to the LP
119 // returned by GetLp().
121
122 // All the constraints managed by this class.
124 const {
125 return constraint_infos_;
126 }
127
128 // The set of constraints indices in AllConstraints() that should be part
129 // of the next LP to solve.
130 const std::vector<ConstraintIndex>& LpConstraints() const {
131 return lp_constraints_;
132 }
133
134 int64_t num_cuts() const { return num_cuts_; }
136 return num_shortened_constraints_;
137 }
138 int64_t num_coeff_strenghtening() const { return num_coeff_strenghtening_; }
139
140 // If a debug solution has been loaded, this checks if the given constaint cut
141 // it or not. Returns true iff everything is fine and the cut does not violate
142 // the loaded solution.
143 bool DebugCheckConstraint(const LinearConstraint& cut);
144
145 // Returns statistics on the cut added.
146 std::string Statistics() const;
147
148 private:
149 // Heuristic that decide which constraints we should remove from the current
150 // LP. Note that such constraints can be added back later by the heuristic
151 // responsible for adding new constraints from the pool.
152 //
153 // Returns true iff one or more constraints where removed.
154 //
155 // If the solutions_state is empty, then this function does nothing and
156 // returns false (this is used for tests). Otherwise, the solutions_state is
157 // assumed to correspond to the current LP and to be of the correct size.
158 bool MaybeRemoveSomeInactiveConstraints(glop::BasisState* solution_state);
159
160 // Apply basic inprocessing simplification rules:
161 // - remove fixed variable
162 // - reduce large coefficient (i.e. coeff strenghtenning or big-M reduction).
163 // This uses level-zero bounds.
164 // Returns true if the terms of the constraint changed.
165 bool SimplifyConstraint(LinearConstraint* ct);
166
167 // Helper method to compute objective parallelism for a given constraint. This
168 // also lazily computes objective norm.
169 void ComputeObjectiveParallelism(const ConstraintIndex ct_index);
170
171 // Multiplies all active counts and the increment counter by the given
172 // 'scaling_factor'. This should be called when at least one of the active
173 // counts is too high.
174 void RescaleActiveCounts(double scaling_factor);
175
176 // Removes some deletable constraints with low active counts. For now, we
177 // don't remove any constraints which are already in LP.
178 void PermanentlyRemoveSomeConstraints();
179
180 const SatParameters& sat_parameters_;
181 const IntegerTrail& integer_trail_;
182
183 // Set at true by Add()/SimplifyConstraint() and at false by ChangeLp().
184 bool current_lp_is_changed_ = false;
185
186 // Optimization to avoid calling SimplifyConstraint() when not needed.
187 int64_t last_simplification_timestamp_ = 0;
188
190
191 // The subset of constraints currently in the lp.
192 std::vector<ConstraintIndex> lp_constraints_;
193
194 // We keep a map from the hash of our constraint terms to their position in
195 // constraints_. This is an optimization to detect duplicate constraints. We
196 // are robust to collisions because we always relies on the ground truth
197 // contained in constraints_ and the code is still okay if we do not merge the
198 // constraints.
199 absl::flat_hash_map<size_t, ConstraintIndex> equiv_constraints_;
200
201 int64_t num_simplifications_ = 0;
202 int64_t num_merged_constraints_ = 0;
203 int64_t num_shortened_constraints_ = 0;
204 int64_t num_splitted_constraints_ = 0;
205 int64_t num_coeff_strenghtening_ = 0;
206
207 int64_t num_cuts_ = 0;
208 int64_t num_add_cut_calls_ = 0;
209 absl::btree_map<std::string, int> type_to_num_cuts_;
210
211 bool objective_is_defined_ = false;
212 bool objective_norm_computed_ = false;
213 double objective_l2_norm_ = 0.0;
214
215 // Total deterministic time spent in this class.
216 double dtime_ = 0.0;
217
218 // Sparse representation of the objective coeffs indexed by positive variables
219 // indices. Important: We cannot use a dense representation here in the corner
220 // case where we have many indepedent LPs. Alternatively, we could share a
221 // dense vector between all LinearConstraintManager.
222 double sum_of_squared_objective_coeffs_ = 0.0;
223 absl::flat_hash_map<IntegerVariable, double> objective_map_;
224
225 TimeLimit* time_limit_;
226 Model* model_;
227 SolverLogger* logger_;
228
229 // We want to decay the active counts of all constraints at each call and
230 // increase the active counts of active/violated constraints. However this can
231 // be too slow in practice. So instead, we keep an increment counter and
232 // update only the active/violated constraints. The counter itself is
233 // increased by a factor at each call. This has the same effect as decaying
234 // all the active counts at each call. This trick is similar to sat clause
235 // management.
236 double constraint_active_count_increase_ = 1.0;
237
238 int32_t num_deletable_constraints_ = 0;
239};
240
241// Keep the top n elements from a stream of elements.
242//
243// TODO(user): We could use gtl::TopN when/if it gets open sourced. Note that
244// we might be slighlty faster here since we use an indirection and don't move
245// the Element class around as much.
246template <typename Element>
247class TopN {
248 public:
249 explicit TopN(int n) : n_(n) {}
250
251 void Clear() {
252 heap_.clear();
253 elements_.clear();
254 }
255
256 void Add(Element e, double score) {
257 if (heap_.size() < n_) {
258 const int index = elements_.size();
259 heap_.push_back({index, score});
260 elements_.push_back(std::move(e));
261 if (heap_.size() == n_) {
262 // TODO(user): We could delay that on the n + 1 push.
263 std::make_heap(heap_.begin(), heap_.end());
264 }
265 } else {
266 if (score <= heap_.front().score) return;
267 const int index_to_replace = heap_.front().index;
268 elements_[index_to_replace] = std::move(e);
269
270 // If needed, we could be faster here with an update operation.
271 std::pop_heap(heap_.begin(), heap_.end());
272 heap_.back() = {index_to_replace, score};
273 std::push_heap(heap_.begin(), heap_.end());
274 }
275 }
276
277 const std::vector<Element>& UnorderedElements() const { return elements_; }
278
279 private:
280 const int n_;
281
282 // We keep a heap of the n lowest score.
283 struct HeapElement {
284 int index; // in elements_;
285 double score;
286 const double operator<(const HeapElement& other) const {
287 return score > other.score;
288 }
289 };
290 std::vector<HeapElement> heap_;
291 std::vector<Element> elements_;
292};
293
294// Before adding cuts to the global pool, it is a classical thing to only keep
295// the top n of a given type during one generation round. This is there to help
296// doing that.
297//
298// TODO(user): Avoid computing efficacity twice.
299// TODO(user): We don't use any orthogonality consideration here.
300// TODO(user): Detect duplicate cuts?
301class TopNCuts {
302 public:
303 explicit TopNCuts(int n) : cuts_(n) {}
304
305 // Add a cut to the local pool
306 void AddCut(LinearConstraint ct, const std::string& name,
308
309 // Empty the local pool and add all its content to the manager.
312 LinearConstraintManager* manager);
313
314 private:
315 struct CutCandidate {
316 std::string name;
318 };
319 TopN<CutCandidate> cuts_;
320};
321
322} // namespace sat
323} // namespace operations_research
324
325#endif // OR_TOOLS_SAT_LINEAR_CONSTRAINT_MANAGER_H_
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
bool ChangeLp(const absl::StrongVector< IntegerVariable, double > &lp_solution, glop::BasisState *solution_state)
void SetObjectiveCoefficient(IntegerVariable var, IntegerValue coeff)
ConstraintIndex Add(LinearConstraint ct, bool *added=nullptr)
const std::vector< ConstraintIndex > & LpConstraints() const
bool AddCut(LinearConstraint ct, std::string type_name, const absl::StrongVector< IntegerVariable, double > &lp_solution, std::string extra_info="")
const absl::StrongVector< ConstraintIndex, ConstraintInfo > & AllConstraints() const
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
void AddCut(LinearConstraint ct, const std::string &name, const absl::StrongVector< IntegerVariable, double > &lp_solution)
void TransferToManager(const absl::StrongVector< IntegerVariable, double > &lp_solution, LinearConstraintManager *manager)
const std::vector< Element > & UnorderedElements() const
void Add(Element e, double score)
const std::string name
const Constraint * ct
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
int index
Collection of objects used to extend the Constraint Solver library.
const double coeff