OR-Tools  9.1
linear_constraint_manager.cc
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
15
16#include <algorithm>
17#include <cmath>
18#include <limits>
19#include <utility>
20
21#include "absl/container/flat_hash_set.h"
23#include "ortools/sat/integer.h"
25
26namespace operations_research {
27namespace sat {
28
29namespace {
30
31const LinearConstraintManager::ConstraintIndex kInvalidConstraintIndex(-1);
32
33size_t ComputeHashOfTerms(const LinearConstraint& ct) {
34 DCHECK(std::is_sorted(ct.vars.begin(), ct.vars.end()));
35 size_t hash = 0;
36 const int num_terms = ct.vars.size();
37 for (int i = 0; i < num_terms; ++i) {
38 hash = util_hash::Hash(ct.vars[i].value(), hash);
39 hash = util_hash::Hash(ct.coeffs[i].value(), hash);
40 }
41 return hash;
42}
43
44} // namespace
45
47 std::string result;
48 absl::StrAppend(&result, " managed constraints: ", constraint_infos_.size(),
49 "\n");
50 if (num_merged_constraints_ > 0) {
51 absl::StrAppend(&result, " merged constraints: ", num_merged_constraints_,
52 "\n");
53 }
54 if (num_shortened_constraints_ > 0) {
55 absl::StrAppend(
56 &result, " shortened constraints: ", num_shortened_constraints_, "\n");
57 }
58 if (num_splitted_constraints_ > 0) {
59 absl::StrAppend(
60 &result, " splitted constraints: ", num_splitted_constraints_, "\n");
61 }
62 if (num_coeff_strenghtening_ > 0) {
63 absl::StrAppend(&result,
64 " coefficient strenghtenings: ", num_coeff_strenghtening_,
65 "\n");
66 }
67 if (num_simplifications_ > 0) {
68 absl::StrAppend(&result, " num simplifications: ", num_simplifications_,
69 "\n");
70 }
71 absl::StrAppend(&result, " total cuts added: ", num_cuts_, " (out of ",
72 num_add_cut_calls_, " calls)\n");
73 for (const auto& entry : type_to_num_cuts_) {
74 absl::StrAppend(&result, " - '", entry.first, "': ", entry.second, "\n");
75 }
76 if (!result.empty()) result.pop_back(); // Remove last \n.
77 return result;
78}
79
80void LinearConstraintManager::RescaleActiveCounts(const double scaling_factor) {
81 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
82 constraint_infos_[i].active_count *= scaling_factor;
83 }
84 constraint_active_count_increase_ *= scaling_factor;
85 VLOG(2) << "Rescaled active counts by " << scaling_factor;
86}
87
88bool LinearConstraintManager::MaybeRemoveSomeInactiveConstraints(
89 glop::BasisState* solution_state) {
90 if (solution_state->IsEmpty()) return false; // Mainly to simplify tests.
91 const glop::RowIndex num_rows(lp_constraints_.size());
92 const glop::ColIndex num_cols =
93 solution_state->statuses.size() - RowToColIndex(num_rows);
94 int new_size = 0;
95 for (int i = 0; i < num_rows; ++i) {
96 const ConstraintIndex constraint_index = lp_constraints_[i];
97
98 // Constraints that are not tight in the current solution have a basic
99 // status. We remove the ones that have been inactive in the last recent
100 // solves.
101 //
102 // TODO(user): More advanced heuristics might perform better, I didn't do
103 // a lot of tuning experiments yet.
104 const glop::VariableStatus row_status =
105 solution_state->statuses[num_cols + glop::ColIndex(i)];
106 if (row_status == glop::VariableStatus::BASIC) {
107 constraint_infos_[constraint_index].inactive_count++;
108 if (constraint_infos_[constraint_index].inactive_count >
109 sat_parameters_.max_consecutive_inactive_count()) {
110 constraint_infos_[constraint_index].is_in_lp = false;
111 continue; // Remove it.
112 }
113 } else {
114 // Only count consecutive inactivities.
115 constraint_infos_[constraint_index].inactive_count = 0;
116 }
117
118 lp_constraints_[new_size] = constraint_index;
119 solution_state->statuses[num_cols + glop::ColIndex(new_size)] = row_status;
120 new_size++;
121 }
122 const int num_removed_constraints = lp_constraints_.size() - new_size;
123 lp_constraints_.resize(new_size);
124 solution_state->statuses.resize(num_cols + glop::ColIndex(new_size));
125 if (num_removed_constraints > 0) {
126 VLOG(2) << "Removed " << num_removed_constraints << " constraints";
127 }
128 return num_removed_constraints > 0;
129}
130
131// Because sometimes we split a == constraint in two (>= and <=), it makes sense
132// to detect duplicate constraints and merge bounds. This is also relevant if
133// we regenerate identical cuts for some reason.
134LinearConstraintManager::ConstraintIndex LinearConstraintManager::Add(
135 LinearConstraint ct, bool* added) {
136 CHECK(!ct.vars.empty());
138 SimplifyConstraint(&ct);
139 DivideByGCD(&ct);
142
143 // If an identical constraint exists, only updates its bound.
144 const size_t key = ComputeHashOfTerms(ct);
145 if (gtl::ContainsKey(equiv_constraints_, key)) {
146 const ConstraintIndex ct_index = equiv_constraints_[key];
147 if (constraint_infos_[ct_index].constraint.vars == ct.vars &&
148 constraint_infos_[ct_index].constraint.coeffs == ct.coeffs) {
149 if (added != nullptr) *added = false;
150 if (ct.lb > constraint_infos_[ct_index].constraint.lb) {
151 if (constraint_infos_[ct_index].is_in_lp) current_lp_is_changed_ = true;
152 constraint_infos_[ct_index].constraint.lb = ct.lb;
153 if (added != nullptr) *added = true;
154 }
155 if (ct.ub < constraint_infos_[ct_index].constraint.ub) {
156 if (constraint_infos_[ct_index].is_in_lp) current_lp_is_changed_ = true;
157 constraint_infos_[ct_index].constraint.ub = ct.ub;
158 if (added != nullptr) *added = true;
159 }
160 ++num_merged_constraints_;
161 return ct_index;
162 }
163 }
164
165 if (added != nullptr) *added = true;
166 const ConstraintIndex ct_index(constraint_infos_.size());
167 ConstraintInfo ct_info;
168 ct_info.constraint = std::move(ct);
169 ct_info.l2_norm = ComputeL2Norm(ct_info.constraint);
170 ct_info.hash = key;
171 equiv_constraints_[key] = ct_index;
172 ct_info.active_count = constraint_active_count_increase_;
173 constraint_infos_.push_back(std::move(ct_info));
174 return ct_index;
175}
176
177void LinearConstraintManager::ComputeObjectiveParallelism(
178 const ConstraintIndex ct_index) {
179 CHECK(objective_is_defined_);
180 // lazy computation of objective norm.
181 if (!objective_norm_computed_) {
182 objective_l2_norm_ = std::sqrt(sum_of_squared_objective_coeffs_);
183 objective_norm_computed_ = true;
184 }
185 CHECK_GT(objective_l2_norm_, 0.0);
186
187 constraint_infos_[ct_index].objective_parallelism_computed = true;
188 if (constraint_infos_[ct_index].l2_norm == 0.0) {
189 constraint_infos_[ct_index].objective_parallelism = 0.0;
190 return;
191 }
192
193 const LinearConstraint& lc = constraint_infos_[ct_index].constraint;
194 double unscaled_objective_parallelism = 0.0;
195 for (int i = 0; i < lc.vars.size(); ++i) {
196 const IntegerVariable var = lc.vars[i];
197 const auto it = objective_map_.find(var);
198 if (it == objective_map_.end()) continue;
199 unscaled_objective_parallelism += it->second * ToDouble(lc.coeffs[i]);
200 }
201 const double objective_parallelism =
202 unscaled_objective_parallelism /
203 (constraint_infos_[ct_index].l2_norm * objective_l2_norm_);
204 constraint_infos_[ct_index].objective_parallelism =
205 std::abs(objective_parallelism);
206}
207
208// Same as Add(), but logs some information about the newly added constraint.
209// Cuts are also handled slightly differently than normal constraints.
211 LinearConstraint ct, std::string type_name,
213 std::string extra_info) {
214 ++num_add_cut_calls_;
215 if (ct.vars.empty()) return false;
216
217 const double activity = ComputeActivity(ct, lp_solution);
218 const double violation =
219 std::max(activity - ToDouble(ct.ub), ToDouble(ct.lb) - activity);
220 const double l2_norm = ComputeL2Norm(ct);
221
222 // Only add cut with sufficient efficacy.
223 if (violation / l2_norm < 1e-5) return false;
224
225 bool added = false;
226 const ConstraintIndex ct_index = Add(std::move(ct), &added);
227
228 // We only mark the constraint as a cut if it is not an update of an already
229 // existing one.
230 if (!added) return false;
231
232 // TODO(user): Use better heuristic here for detecting good cuts and mark
233 // them undeletable.
234 constraint_infos_[ct_index].is_deletable = true;
235
236 VLOG(1) << "Cut '" << type_name << "'"
237 << " size=" << constraint_infos_[ct_index].constraint.vars.size()
238 << " max_magnitude="
239 << ComputeInfinityNorm(constraint_infos_[ct_index].constraint)
240 << " norm=" << l2_norm << " violation=" << violation
241 << " eff=" << violation / l2_norm << " " << extra_info;
242
243 num_cuts_++;
244 num_deletable_constraints_++;
245 type_to_num_cuts_[type_name]++;
246 return true;
247}
248
249void LinearConstraintManager::PermanentlyRemoveSomeConstraints() {
250 std::vector<double> deletable_constraint_counts;
251 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
252 if (constraint_infos_[i].is_deletable && !constraint_infos_[i].is_in_lp) {
253 deletable_constraint_counts.push_back(constraint_infos_[i].active_count);
254 }
255 }
256 if (deletable_constraint_counts.empty()) return;
257 std::sort(deletable_constraint_counts.begin(),
258 deletable_constraint_counts.end());
259
260 // We will delete the oldest (in the order they where added) cleanup target
261 // constraints with a count lower or equal to this.
262 double active_count_threshold = std::numeric_limits<double>::infinity();
263 if (sat_parameters_.cut_cleanup_target() <
264 deletable_constraint_counts.size()) {
265 active_count_threshold =
266 deletable_constraint_counts[sat_parameters_.cut_cleanup_target()];
267 }
268
269 ConstraintIndex new_size(0);
270 equiv_constraints_.clear();
272 constraint_infos_.size());
273 int num_deleted_constraints = 0;
274 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
275 if (constraint_infos_[i].is_deletable && !constraint_infos_[i].is_in_lp &&
276 constraint_infos_[i].active_count <= active_count_threshold &&
277 num_deleted_constraints < sat_parameters_.cut_cleanup_target()) {
278 ++num_deleted_constraints;
279 continue;
280 }
281
282 if (i != new_size) {
283 constraint_infos_[new_size] = std::move(constraint_infos_[i]);
284 }
285 index_mapping[i] = new_size;
286
287 // Make sure we recompute the hash_map of identical constraints.
288 equiv_constraints_[constraint_infos_[new_size].hash] = new_size;
289 new_size++;
290 }
291 constraint_infos_.resize(new_size.value());
292
293 // Also update lp_constraints_
294 for (int i = 0; i < lp_constraints_.size(); ++i) {
295 lp_constraints_[i] = index_mapping[lp_constraints_[i]];
296 }
297
298 if (num_deleted_constraints > 0) {
299 VLOG(1) << "Constraint manager cleanup: #deleted:"
300 << num_deleted_constraints;
301 }
302 num_deletable_constraints_ -= num_deleted_constraints;
303}
304
306 IntegerValue coeff) {
307 if (coeff == IntegerValue(0)) return;
308 objective_is_defined_ = true;
309 if (!VariableIsPositive(var)) {
310 var = NegationOf(var);
311 coeff = -coeff;
312 }
313 const double coeff_as_double = ToDouble(coeff);
314 const auto insert = objective_map_.insert({var, coeff_as_double});
315 CHECK(insert.second)
316 << "SetObjectiveCoefficient() called twice with same variable";
317 sum_of_squared_objective_coeffs_ += coeff_as_double * coeff_as_double;
318}
319
320bool LinearConstraintManager::SimplifyConstraint(LinearConstraint* ct) {
321 bool term_changed = false;
322
323 IntegerValue min_sum(0);
324 IntegerValue max_sum(0);
325 IntegerValue max_magnitude(0);
326 int new_size = 0;
327 const int num_terms = ct->vars.size();
328 for (int i = 0; i < num_terms; ++i) {
329 const IntegerVariable var = ct->vars[i];
330 const IntegerValue coeff = ct->coeffs[i];
331 const IntegerValue lb = integer_trail_.LevelZeroLowerBound(var);
332 const IntegerValue ub = integer_trail_.LevelZeroUpperBound(var);
333
334 // For now we do not change ct, but just compute its new_size if we where
335 // to remove a fixed term.
336 if (lb == ub) continue;
337 ++new_size;
338
339 max_magnitude = std::max(max_magnitude, IntTypeAbs(coeff));
340 if (coeff > 0.0) {
341 min_sum += coeff * lb;
342 max_sum += coeff * ub;
343 } else {
344 min_sum += coeff * ub;
345 max_sum += coeff * lb;
346 }
347 }
348
349 // Shorten the constraint if needed.
350 if (new_size < num_terms) {
351 term_changed = true;
352 ++num_shortened_constraints_;
353 new_size = 0;
354 for (int i = 0; i < num_terms; ++i) {
355 const IntegerVariable var = ct->vars[i];
356 const IntegerValue coeff = ct->coeffs[i];
357 const IntegerValue lb = integer_trail_.LevelZeroLowerBound(var);
358 const IntegerValue ub = integer_trail_.LevelZeroUpperBound(var);
359 if (lb == ub) {
360 const IntegerValue rhs_adjust = lb * coeff;
361 if (ct->lb > kMinIntegerValue) ct->lb -= rhs_adjust;
362 if (ct->ub < kMaxIntegerValue) ct->ub -= rhs_adjust;
363 continue;
364 }
365 ct->vars[new_size] = var;
366 ct->coeffs[new_size] = coeff;
367 ++new_size;
368 }
369 ct->vars.resize(new_size);
370 ct->coeffs.resize(new_size);
371 }
372
373 // Relax the bound if needed, note that this doesn't require a change to
374 // the equiv map.
375 if (min_sum >= ct->lb) ct->lb = kMinIntegerValue;
376 if (max_sum <= ct->ub) ct->ub = kMaxIntegerValue;
377
378 // Clear constraints that are always true.
379 // We rely on the deletion code to remove them eventually.
380 if (ct->lb == kMinIntegerValue && ct->ub == kMaxIntegerValue) {
381 ct->vars.clear();
382 ct->coeffs.clear();
383 return true;
384 }
385
386 // TODO(user): Split constraint in two if it is boxed and there is possible
387 // reduction?
388 //
389 // TODO(user): Make sure there cannot be any overflow. They shouldn't, but
390 // I am not sure all the generated cuts are safe regarding min/max sum
391 // computation. We should check this.
392 if (ct->ub != kMaxIntegerValue && max_magnitude > max_sum - ct->ub) {
393 if (ct->lb != kMinIntegerValue) {
394 ++num_splitted_constraints_;
395 } else {
396 term_changed = true;
397 ++num_coeff_strenghtening_;
398 const int num_terms = ct->vars.size();
399 const IntegerValue target = max_sum - ct->ub;
400 for (int i = 0; i < num_terms; ++i) {
401 const IntegerValue coeff = ct->coeffs[i];
402 if (coeff > target) {
403 const IntegerVariable var = ct->vars[i];
404 const IntegerValue ub = integer_trail_.LevelZeroUpperBound(var);
405 ct->coeffs[i] = target;
406 ct->ub -= (coeff - target) * ub;
407 } else if (coeff < -target) {
408 const IntegerVariable var = ct->vars[i];
409 const IntegerValue lb = integer_trail_.LevelZeroLowerBound(var);
410 ct->coeffs[i] = -target;
411 ct->ub += (-target - coeff) * lb;
412 }
413 }
414 }
415 }
416
417 if (ct->lb != kMinIntegerValue && max_magnitude > ct->lb - min_sum) {
418 if (ct->ub != kMaxIntegerValue) {
419 ++num_splitted_constraints_;
420 } else {
421 term_changed = true;
422 ++num_coeff_strenghtening_;
423 const int num_terms = ct->vars.size();
424 const IntegerValue target = ct->lb - min_sum;
425 for (int i = 0; i < num_terms; ++i) {
426 const IntegerValue coeff = ct->coeffs[i];
427 if (coeff > target) {
428 const IntegerVariable var = ct->vars[i];
429 const IntegerValue lb = integer_trail_.LevelZeroLowerBound(var);
430 ct->coeffs[i] = target;
431 ct->lb -= (coeff - target) * lb;
432 } else if (coeff < -target) {
433 const IntegerVariable var = ct->vars[i];
434 const IntegerValue ub = integer_trail_.LevelZeroUpperBound(var);
435 ct->coeffs[i] = -target;
436 ct->lb += (-target - coeff) * ub;
437 }
438 }
439 }
440 }
441
442 return term_changed;
443}
444
447 glop::BasisState* solution_state) {
448 VLOG(3) << "Enter ChangeLP, scan " << constraint_infos_.size()
449 << " constraints";
450 const double saved_dtime = dtime_;
451 std::vector<ConstraintIndex> new_constraints;
452 std::vector<double> new_constraints_efficacies;
453 std::vector<double> new_constraints_orthogonalities;
454
455 const bool simplify_constraints =
456 integer_trail_.num_level_zero_enqueues() > last_simplification_timestamp_;
457 last_simplification_timestamp_ = integer_trail_.num_level_zero_enqueues();
458
459 // We keep any constraints that is already present, and otherwise, we add the
460 // ones that are currently not satisfied by at least "tolerance" to the set
461 // of potential new constraints.
462 bool rescale_active_count = false;
463 const double tolerance = 1e-6;
464 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
465 // Inprocessing of the constraint.
466 if (simplify_constraints &&
467 SimplifyConstraint(&constraint_infos_[i].constraint)) {
468 ++num_simplifications_;
469
470 // Note that the canonicalization shouldn't be needed since the order
471 // of the variable is not changed by the simplification, and we only
472 // reduce the coefficients at both end of the spectrum.
473 DivideByGCD(&constraint_infos_[i].constraint);
474 DCHECK(DebugCheckConstraint(constraint_infos_[i].constraint));
475
476 constraint_infos_[i].objective_parallelism_computed = false;
477 constraint_infos_[i].l2_norm =
478 ComputeL2Norm(constraint_infos_[i].constraint);
479
480 if (constraint_infos_[i].is_in_lp) current_lp_is_changed_ = true;
481 equiv_constraints_.erase(constraint_infos_[i].hash);
482 constraint_infos_[i].hash =
483 ComputeHashOfTerms(constraint_infos_[i].constraint);
484
485 // TODO(user): Because we simplified this constraint, it is possible that
486 // it is now a duplicate of another one. Merge them.
487 equiv_constraints_[constraint_infos_[i].hash] = i;
488 }
489
490 if (constraint_infos_[i].is_in_lp) continue;
491
492 // ComputeActivity() often represent the bulk of the time spent in
493 // ChangeLP().
494 dtime_ += 1.7e-9 *
495 static_cast<double>(constraint_infos_[i].constraint.vars.size());
496 const double activity =
497 ComputeActivity(constraint_infos_[i].constraint, lp_solution);
498 const double lb_violation =
499 ToDouble(constraint_infos_[i].constraint.lb) - activity;
500 const double ub_violation =
501 activity - ToDouble(constraint_infos_[i].constraint.ub);
502 const double violation = std::max(lb_violation, ub_violation);
503 if (violation >= tolerance) {
504 constraint_infos_[i].inactive_count = 0;
505 new_constraints.push_back(i);
506 new_constraints_efficacies.push_back(violation /
507 constraint_infos_[i].l2_norm);
508 new_constraints_orthogonalities.push_back(1.0);
509
510 if (objective_is_defined_ &&
511 !constraint_infos_[i].objective_parallelism_computed) {
512 ComputeObjectiveParallelism(i);
513 } else if (!objective_is_defined_) {
514 constraint_infos_[i].objective_parallelism = 0.0;
515 }
516
517 constraint_infos_[i].current_score =
518 new_constraints_efficacies.back() +
519 constraint_infos_[i].objective_parallelism;
520
521 if (constraint_infos_[i].is_deletable) {
522 constraint_infos_[i].active_count += constraint_active_count_increase_;
523 if (constraint_infos_[i].active_count >
524 sat_parameters_.cut_max_active_count_value()) {
525 rescale_active_count = true;
526 }
527 }
528 }
529 }
530
531 // Bump activities of active constraints in LP.
532 if (solution_state != nullptr) {
533 const glop::RowIndex num_rows(lp_constraints_.size());
534 const glop::ColIndex num_cols =
535 solution_state->statuses.size() - RowToColIndex(num_rows);
536
537 for (int i = 0; i < num_rows; ++i) {
538 const ConstraintIndex constraint_index = lp_constraints_[i];
539 const glop::VariableStatus row_status =
540 solution_state->statuses[num_cols + glop::ColIndex(i)];
541 if (row_status != glop::VariableStatus::BASIC &&
542 constraint_infos_[constraint_index].is_deletable) {
543 constraint_infos_[constraint_index].active_count +=
544 constraint_active_count_increase_;
545 if (constraint_infos_[constraint_index].active_count >
546 sat_parameters_.cut_max_active_count_value()) {
547 rescale_active_count = true;
548 }
549 }
550 }
551 }
552
553 if (rescale_active_count) {
554 CHECK_GT(sat_parameters_.cut_max_active_count_value(), 0.0);
555 RescaleActiveCounts(1.0 / sat_parameters_.cut_max_active_count_value());
556 }
557
558 // Update the increment counter.
559 constraint_active_count_increase_ *=
560 1.0 / sat_parameters_.cut_active_count_decay();
561
562 // Remove constraints from the current LP that have been inactive for a while.
563 // We do that after we computed new_constraints so we do not need to iterate
564 // over the just deleted constraints.
565 if (MaybeRemoveSomeInactiveConstraints(solution_state)) {
566 current_lp_is_changed_ = true;
567 }
568
569 // Note that the algo below is in O(limit * new_constraint). In order to
570 // limit spending too much time on this, we first sort all the constraints
571 // with an imprecise score (no orthogonality), then limit the size of the
572 // vector of constraints to precisely score, then we do the actual scoring.
573 //
574 // On problem crossword_opt_grid-19.05_dict-80_sat with linearization_level=2,
575 // new_constraint.size() > 1.5M.
576 //
577 // TODO(user): This blowup factor could be adaptative w.r.t. the constraint
578 // limit.
579 const int kBlowupFactor = 4;
580 int constraint_limit = std::min(sat_parameters_.new_constraints_batch_size(),
581 static_cast<int>(new_constraints.size()));
582 if (lp_constraints_.empty()) {
583 constraint_limit = std::min(1000, static_cast<int>(new_constraints.size()));
584 }
585 VLOG(3) << " - size = " << new_constraints.size()
586 << ", limit = " << constraint_limit;
587
588 std::stable_sort(new_constraints.begin(), new_constraints.end(),
589 [&](ConstraintIndex a, ConstraintIndex b) {
590 return constraint_infos_[a].current_score >
591 constraint_infos_[b].current_score;
592 });
593 if (new_constraints.size() > kBlowupFactor * constraint_limit) {
594 VLOG(3) << "Resize candidate constraints from " << new_constraints.size()
595 << " down to " << kBlowupFactor * constraint_limit;
596 new_constraints.resize(kBlowupFactor * constraint_limit);
597 }
598
599 int num_added = 0;
600 int num_skipped_checks = 0;
601 const int kCheckFrequency = 100;
602 ConstraintIndex last_added_candidate = kInvalidConstraintIndex;
603 for (int i = 0; i < constraint_limit; ++i) {
604 // Iterate through all new constraints and select the one with the best
605 // score.
606 double best_score = 0.0;
607 ConstraintIndex best_candidate = kInvalidConstraintIndex;
608 for (int j = 0; j < new_constraints.size(); ++j) {
609 // Checks the time limit, and returns if the lp has changed.
610 if (++num_skipped_checks >= kCheckFrequency) {
611 if (time_limit_->LimitReached()) return current_lp_is_changed_;
612 num_skipped_checks = 0;
613 }
614
615 const ConstraintIndex new_constraint = new_constraints[j];
616 if (constraint_infos_[new_constraint].is_in_lp) continue;
617
618 if (last_added_candidate != kInvalidConstraintIndex) {
619 const double current_orthogonality =
620 1.0 - (std::abs(ScalarProduct(
621 constraint_infos_[last_added_candidate].constraint,
622 constraint_infos_[new_constraint].constraint)) /
623 (constraint_infos_[last_added_candidate].l2_norm *
624 constraint_infos_[new_constraint].l2_norm));
625 new_constraints_orthogonalities[j] =
626 std::min(new_constraints_orthogonalities[j], current_orthogonality);
627 }
628
629 // NOTE(user): It is safe to not add this constraint as the constraint
630 // that is almost parallel to this constraint is present in the LP or is
631 // inactive for a long time and is removed from the LP. In either case,
632 // this constraint is not adding significant value and is only making the
633 // LP larger.
634 if (new_constraints_orthogonalities[j] <
635 sat_parameters_.min_orthogonality_for_lp_constraints()) {
636 continue;
637 }
638
639 // TODO(user): Experiment with different weights or different
640 // functions for computing score.
641 const double score = new_constraints_orthogonalities[j] +
642 constraint_infos_[new_constraint].current_score;
643 CHECK_GE(score, 0.0);
644 if (score > best_score || best_candidate == kInvalidConstraintIndex) {
645 best_score = score;
646 best_candidate = new_constraint;
647 }
648 }
649
650 if (best_candidate != kInvalidConstraintIndex) {
651 // Add the best constraint in the LP.
652 constraint_infos_[best_candidate].is_in_lp = true;
653 // Note that it is important for LP incremental solving that the old
654 // constraints stays at the same position in this list (and thus in the
655 // returned GetLp()).
656 ++num_added;
657 current_lp_is_changed_ = true;
658 lp_constraints_.push_back(best_candidate);
659 last_added_candidate = best_candidate;
660 }
661 }
662
663 if (num_added > 0) {
664 // We update the solution sate to match the new LP size.
665 VLOG(2) << "Added " << num_added << " constraints.";
666 solution_state->statuses.resize(solution_state->statuses.size() + num_added,
668 }
669
670 // TODO(user): Instead of comparing num_deletable_constraints with cut
671 // limit, compare number of deletable constraints not in lp against the limit.
672 if (num_deletable_constraints_ > sat_parameters_.max_num_cuts()) {
673 PermanentlyRemoveSomeConstraints();
674 }
675
676 time_limit_->AdvanceDeterministicTime(dtime_ - saved_dtime);
677
678 // The LP changed only if we added new constraints or if some constraints
679 // already inside changed (simplification or tighter bounds).
680 if (current_lp_is_changed_) {
681 current_lp_is_changed_ = false;
682 return true;
683 }
684 return false;
685}
686
688 for (ConstraintIndex i(0); i < constraint_infos_.size(); ++i) {
689 if (constraint_infos_[i].is_in_lp) continue;
690 constraint_infos_[i].is_in_lp = true;
691 lp_constraints_.push_back(i);
692 }
693}
694
696 const LinearConstraint& cut) {
697 if (model_->Get<DebugSolution>() == nullptr) return true;
698 const auto& debug_solution = *(model_->Get<DebugSolution>());
699 if (debug_solution.empty()) return true;
700
701 IntegerValue activity(0);
702 for (int i = 0; i < cut.vars.size(); ++i) {
703 const IntegerVariable var = cut.vars[i];
704 const IntegerValue coeff = cut.coeffs[i];
705 activity += coeff * debug_solution[var];
706 }
707 if (activity > cut.ub || activity < cut.lb) {
708 LOG(INFO) << "activity " << activity << " not in [" << cut.lb << ","
709 << cut.ub << "]";
710 return false;
711 }
712 return true;
713}
714
716 LinearConstraint ct, const std::string& name,
718 if (ct.vars.empty()) return;
719 const double activity = ComputeActivity(ct, lp_solution);
720 const double violation =
721 std::max(activity - ToDouble(ct.ub), ToDouble(ct.lb) - activity);
722 const double l2_norm = ComputeL2Norm(ct);
723 cuts_.Add({name, ct}, violation / l2_norm);
724}
725
728 LinearConstraintManager* manager) {
729 for (const CutCandidate& candidate : cuts_.UnorderedElements()) {
730 manager->AddCut(candidate.cut, candidate.name, lp_solution);
731 }
732 cuts_.Clear();
733}
734
735} // namespace sat
736} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:491
#define CHECK_GE(val1, val2)
Definition: base/logging.h:702
#define CHECK_GT(val1, val2)
Definition: base/logging.h:703
#define LOG(severity)
Definition: base/logging.h:416
#define DCHECK(condition)
Definition: base/logging.h:885
#define VLOG(verboselevel)
Definition: base/logging.h:979
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
Definition: time_limit.h:533
void AdvanceDeterministicTime(double deterministic_duration)
Advances the deterministic time.
Definition: time_limit.h:226
int64_t num_level_zero_enqueues() const
Definition: integer.h:837
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition: integer.h:1412
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition: integer.h:1407
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)
bool AddCut(LinearConstraint ct, std::string type_name, const absl::StrongVector< IntegerVariable, double > &lp_solution, std::string extra_info="")
T Get(std::function< T(const Model &)> f) const
Similar to Add() but this is const.
Definition: sat/model.h:87
::PROTOBUF_NAMESPACE_ID::int32 new_constraints_batch_size() const
::PROTOBUF_NAMESPACE_ID::int32 max_consecutive_inactive_count() const
::PROTOBUF_NAMESPACE_ID::int32 max_num_cuts() const
::PROTOBUF_NAMESPACE_ID::int32 cut_cleanup_target() const
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)
int64_t b
int64_t a
const std::string name
const Constraint * ct
IntVar * var
Definition: expr_array.cc:1874
const int INFO
Definition: log_severity.h:31
int64_t hash
Definition: matrix_utils.cc:61
bool ContainsKey(const Collection &collection, const Key &key)
Definition: map_util.h:200
ColIndex RowToColIndex(RowIndex row)
Definition: lp_types.h:49
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
IntType IntTypeAbs(IntType t)
Definition: integer.h:78
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
double ScalarProduct(const LinearConstraint &constraint1, const LinearConstraint &constraint2)
void CanonicalizeConstraint(LinearConstraint *ct)
bool NoDuplicateVariable(const LinearConstraint &ct)
double ComputeL2Norm(const LinearConstraint &constraint)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:29
IntegerValue ComputeInfinityNorm(const LinearConstraint &constraint)
bool VariableIsPositive(IntegerVariable i)
Definition: integer.h:138
void DivideByGCD(LinearConstraint *constraint)
double ComputeActivity(const LinearConstraint &constraint, const absl::StrongVector< IntegerVariable, double > &values)
double ToDouble(IntegerValue value)
Definition: integer.h:70
Collection of objects used to extend the Constraint Solver library.
uint64_t Hash(uint64_t num, uint64_t c)
Definition: hash.h:150