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