Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
linear_constraint_manager.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_LINEAR_CONSTRAINT_MANAGER_H_
15#define OR_TOOLS_SAT_LINEAR_CONSTRAINT_MANAGER_H_
16
17#include <cstddef>
18#include <cstdint>
19#include <string>
20#include <vector>
21
22#include "absl/container/btree_map.h"
23#include "absl/container/flat_hash_map.h"
24#include "absl/strings/string_view.h"
28#include "ortools/sat/integer.h"
30#include "ortools/sat/model.h"
31#include "ortools/sat/sat_parameters.pb.h"
32#include "ortools/sat/util.h"
36
37namespace operations_research {
38namespace sat {
39
40// Stores for each IntegerVariable its temporary LP solution.
41//
42// This is shared between all LinearProgrammingConstraint because in the corner
43// case where we have many different LinearProgrammingConstraint and a lot of
44// variable, we could theoretically use up a quadratic amount of memory
45// otherwise.
47 : public util_intops::StrongVector<IntegerVariable, double> {
48 ModelLpValues() = default;
49};
50
51// This class holds a list of globally valid linear constraints and has some
52// logic to decide which one should be part of the LP relaxation. We want more
53// for a better relaxation, but for efficiency we do not want to have too much
54// constraints while solving the LP.
55//
56// This class is meant to contain all the initial constraints of the LP
57// relaxation and to get new cuts as they are generated. Thus, it can both
58// manage cuts but also only add the initial constraints lazily if there is too
59// many of them.
61 public:
63 // Note that this constraint always contains "tight" lb/ub, some of these
64 // bound might be trivial level zero bounds, and one can know this by
65 // looking at lb_is_trivial/ub_is_trivial.
67
68 double l2_norm = 0.0;
70 size_t hash;
71
72 // Updated only for deletable constraints. This is incremented every time
73 // ChangeLp() is called and the constraint is active in the LP or not in the
74 // LP and violated.
75 double active_count = 0.0;
76
77 // TODO(user): This is the number of time the constraint was consecutively
78 // inactive, and go up to 100 with the default param, so we could optimize
79 // the space used more.
80 uint16_t inactive_count = 0;
81
82 // TODO(user): Pack bool and in general optimize the memory of this class.
84 bool is_in_lp = false;
85 bool ub_is_trivial = false;
86 bool lb_is_trivial = false;
87
88 // For now, we mark all the generated cuts as deletable and the problem
89 // constraints as undeletable.
90 // TODO(user): We can have a better heuristics. Some generated good cuts
91 // can be marked undeletable and some unused problem specified constraints
92 // can be marked deletable.
93 bool is_deletable = false;
94 };
95
97 : sat_parameters_(*model->GetOrCreate<SatParameters>()),
98 integer_trail_(*model->GetOrCreate<IntegerTrail>()),
99 time_limit_(model->GetOrCreate<TimeLimit>()),
100 expanded_lp_solution_(*model->GetOrCreate<ModelLpValues>()),
101 model_(model),
102 logger_(model->GetOrCreate<SolverLogger>()) {}
104
105 // Add a new constraint to the manager. Note that we canonicalize constraints
106 // and merge the bounds of constraints with the same terms. We also perform
107 // basic preprocessing. If added is given, it will be set to true if this
108 // constraint was actually a new one and to false if it was dominated by an
109 // already existing one.
110 DEFINE_STRONG_INDEX_TYPE(ConstraintIndex);
111 ConstraintIndex Add(LinearConstraint ct, bool* added = nullptr);
112
113 // Same as Add(), but logs some information about the newly added constraint.
114 // Cuts are also handled slightly differently than normal constraints.
115 //
116 // Returns true if a new cut was added and false if this cut is not
117 // efficacious or if it is a duplicate of an already existing one.
118 bool AddCut(LinearConstraint ct, std::string type_name,
119 std::string extra_info = "");
120
121 // These must be level zero bounds.
122 bool UpdateConstraintLb(glop::RowIndex index_in_lp, IntegerValue new_lb);
123 bool UpdateConstraintUb(glop::RowIndex index_in_lp, IntegerValue new_ub);
124
125 // The objective is used as one of the criterion to score cuts.
126 // The more a cut is parallel to the objective, the better its score is.
127 //
128 // Currently this should only be called once per IntegerVariable (Checked). It
129 // is easy to support dynamic modification if it becomes needed.
130 void SetObjectiveCoefficient(IntegerVariable var, IntegerValue coeff);
131
132 // Heuristic to decides what LP is best solved next. We use the model lp
133 // solutions as an heuristic, and it should usually be updated with the last
134 // known solution before this call.
135 //
136 // The current solution state is used for detecting inactive constraints. It
137 // is also updated correctly on constraint deletion/addition so that the
138 // simplex can be fully iterative on restart by loading this modified state.
139 //
140 // Returns true iff LpConstraints() will return a different LP than before.
141 bool ChangeLp(glop::BasisState* solution_state,
142 int* num_new_constraints = nullptr);
143
144 // This can be called initially to add all the current constraint to the LP
145 // returned by GetLp().
147
148 // All the constraints managed by this class.
151 return constraint_infos_;
152 }
153
154 // The set of constraints indices in AllConstraints() that should be part
155 // of the next LP to solve.
156 const std::vector<ConstraintIndex>& LpConstraints() const {
157 return lp_constraints_;
158 }
159
160 // To simplify CutGenerator api.
162 return expanded_lp_solution_;
163 }
164
165 // Stats.
166 int64_t num_constraints() const { return constraint_infos_.size(); }
167 int64_t num_constraint_updates() const { return num_constraint_updates_; }
168 int64_t num_simplifications() const { return num_simplifications_; }
169 int64_t num_merged_constraints() const { return num_merged_constraints_; }
171 return num_shortened_constraints_;
172 }
173 int64_t num_split_constraints() const { return num_split_constraints_; }
174 int64_t num_coeff_strenghtening() const { return num_coeff_strenghtening_; }
175 int64_t num_cuts() const { return num_cuts_; }
176 int64_t num_add_cut_calls() const { return num_add_cut_calls_; }
177
178 const absl::btree_map<std::string, int>& type_to_num_cuts() const {
179 return type_to_num_cuts_;
180 }
181
182 // If a debug solution has been loaded, this checks if the given constraint
183 // cut it or not. Returns true if and only if everything is fine and the cut
184 // does not violate the loaded solution.
185 bool DebugCheckConstraint(const LinearConstraint& cut);
186
187 private:
188 // Heuristic that decide which constraints we should remove from the current
189 // LP. Note that such constraints can be added back later by the heuristic
190 // responsible for adding new constraints from the pool.
191 //
192 // Returns true if and only if one or more constraints where removed.
193 //
194 // If the solutions_state is empty, then this function does nothing and
195 // returns false (this is used for tests). Otherwise, the solutions_state is
196 // assumed to correspond to the current LP and to be of the correct size.
197 bool MaybeRemoveSomeInactiveConstraints(glop::BasisState* solution_state);
198
199 // Apply basic inprocessing simplification rules:
200 // - remove fixed variable
201 // - reduce large coefficient (i.e. coeff strenghtenning or big-M reduction).
202 // This uses level-zero bounds.
203 // Returns true if the terms of the constraint changed.
204 bool SimplifyConstraint(LinearConstraint* ct);
205
206 // Helper method to compute objective parallelism for a given constraint. This
207 // also lazily computes objective norm.
208 void ComputeObjectiveParallelism(ConstraintIndex ct_index);
209
210 // Multiplies all active counts and the increment counter by the given
211 // 'scaling_factor'. This should be called when at least one of the active
212 // counts is too high.
213 void RescaleActiveCounts(double scaling_factor);
214
215 // Removes some deletable constraints with low active counts. For now, we
216 // don't remove any constraints which are already in LP.
217 void PermanentlyRemoveSomeConstraints();
218
219 // Make sure the lb/ub are tight and fill lb_is_trivial/ub_is_trivial.
220 void FillDerivedFields(ConstraintInfo* info);
221
222 const SatParameters& sat_parameters_;
223 const IntegerTrail& integer_trail_;
224
225 // Set at true by Add()/SimplifyConstraint() and at false by ChangeLp().
226 bool current_lp_is_changed_ = false;
227
228 // Optimization to avoid calling SimplifyConstraint() when not needed.
229 int64_t last_simplification_timestamp_ = 0;
230
232
233 // The subset of constraints currently in the lp.
234 std::vector<ConstraintIndex> lp_constraints_;
235
236 // We keep a map from the hash of our constraint terms to their position in
237 // constraints_. This is an optimization to detect duplicate constraints. We
238 // are robust to collisions because we always relies on the ground truth
239 // contained in constraints_ and the code is still okay if we do not merge the
240 // constraints.
241 absl::flat_hash_map<size_t, ConstraintIndex> equiv_constraints_;
242
243 int64_t num_constraint_updates_ = 0;
244 int64_t num_simplifications_ = 0;
245 int64_t num_merged_constraints_ = 0;
246 int64_t num_shortened_constraints_ = 0;
247 int64_t num_split_constraints_ = 0;
248 int64_t num_coeff_strenghtening_ = 0;
249
250 int64_t num_cuts_ = 0;
251 int64_t num_add_cut_calls_ = 0;
252 absl::btree_map<std::string, int> type_to_num_cuts_;
253
254 bool objective_is_defined_ = false;
255 bool objective_norm_computed_ = false;
256 double objective_l2_norm_ = 0.0;
257
258 // Total deterministic time spent in this class.
259 double dtime_ = 0.0;
260
261 // Sparse representation of the objective coeffs indexed by positive variables
262 // indices. Important: We cannot use a dense representation here in the corner
263 // case where we have many independent LPs. Alternatively, we could share a
264 // dense vector between all LinearConstraintManager.
265 double sum_of_squared_objective_coeffs_ = 0.0;
266 absl::flat_hash_map<IntegerVariable, double> objective_map_;
267
268 TimeLimit* time_limit_;
269 ModelLpValues& expanded_lp_solution_;
270 Model* model_;
271 SolverLogger* logger_;
272
273 // We want to decay the active counts of all constraints at each call and
274 // increase the active counts of active/violated constraints. However this can
275 // be too slow in practice. So instead, we keep an increment counter and
276 // update only the active/violated constraints. The counter itself is
277 // increased by a factor at each call. This has the same effect as decaying
278 // all the active counts at each call. This trick is similar to sat clause
279 // management.
280 double constraint_active_count_increase_ = 1.0;
281
282 int32_t num_deletable_constraints_ = 0;
283};
284
285// Before adding cuts to the global pool, it is a classical thing to only keep
286// the top n of a given type during one generation round. This is there to help
287// doing that.
288//
289// TODO(user): Avoid computing efficacity twice.
290// TODO(user): We don't use any orthogonality consideration here.
291// TODO(user): Detect duplicate cuts?
292class TopNCuts {
293 public:
294 explicit TopNCuts(int n) : cuts_(n) {}
295
296 // Adds a cut to the local pool.
297 void AddCut(
298 LinearConstraint ct, absl::string_view name,
300
301 // Empty the local pool and add all its content to the manager.
303
304 private:
305 struct CutCandidate {
306 std::string name;
308 };
309 TopN<CutCandidate, double> cuts_;
310};
311
312} // namespace sat
313} // namespace operations_research
314
315#endif // OR_TOOLS_SAT_LINEAR_CONSTRAINT_MANAGER_H_
const std::vector< ConstraintIndex > & LpConstraints() const
bool ChangeLp(glop::BasisState *solution_state, int *num_new_constraints=nullptr)
ConstraintIndex Add(LinearConstraint ct, bool *added=nullptr)
bool AddCut(LinearConstraint ct, std::string type_name, std::string extra_info="")
bool UpdateConstraintUb(glop::RowIndex index_in_lp, IntegerValue new_ub)
bool UpdateConstraintLb(glop::RowIndex index_in_lp, IntegerValue new_lb)
These must be level zero bounds.
const util_intops::StrongVector< ConstraintIndex, ConstraintInfo > & AllConstraints() const
All the constraints managed by this class.
const absl::btree_map< std::string, int > & type_to_num_cuts() const
const util_intops::StrongVector< IntegerVariable, double > & LpValues()
To simplify CutGenerator api.
void SetObjectiveCoefficient(IntegerVariable var, IntegerValue coeff)
void TransferToManager(LinearConstraintManager *manager)
Empty the local pool and add all its content to the manager.
void AddCut(LinearConstraint ct, absl::string_view name, const util_intops::StrongVector< IntegerVariable, double > &lp_solution)
Adds a cut to the local pool.
const std::string name
A name for logging purposes.
const Constraint * ct
IntVar * var
GRBmodel * model
int ct_index
In SWIG mode, we don't want anything besides these top-level includes.