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