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