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