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 
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  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 
80 void 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 
88 bool 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.
134 LinearConstraintManager::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 
177 void 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 
249 void 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 
320 bool 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,
717  const absl::StrongVector<IntegerVariable, double>& lp_solution) {
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
uint64_t Hash(uint64_t num, uint64_t c)
Definition: hash.h:150
const std::vector< Element > & UnorderedElements() const
#define CHECK(condition)
Definition: base/logging.h:491
ColIndex RowToColIndex(RowIndex row)
Definition: lp_types.h:49
int64_t min
Definition: alldiff_cst.cc:139
double ComputeL2Norm(const LinearConstraint &constraint)
#define CHECK_GE(val1, val2)
Definition: base/logging.h:702
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
#define CHECK_GT(val1, val2)
Definition: base/logging.h:703
#define VLOG(verboselevel)
Definition: base/logging.h:979
const std::string name
::PROTOBUF_NAMESPACE_ID::int32 max_consecutive_inactive_count() const
#define LOG(severity)
Definition: base/logging.h:416
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
int64_t b
double ComputeActivity(const LinearConstraint &constraint, const absl::StrongVector< IntegerVariable, double > &values)
void SetObjectiveCoefficient(IntegerVariable var, IntegerValue coeff)
double ToDouble(IntegerValue value)
Definition: integer.h:70
::PROTOBUF_NAMESPACE_ID::int32 max_num_cuts() const
void DivideByGCD(LinearConstraint *constraint)
int64_t max
Definition: alldiff_cst.cc:140
T Get(std::function< T(const Model &)> f) const
Similar to Add() but this is const.
Definition: sat/model.h:87
IntegerValue ComputeInfinityNorm(const LinearConstraint &constraint)
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition: integer.h:1412
bool ContainsKey(const Collection &collection, const Key &key)
Definition: map_util.h:200
int64_t hash
Definition: matrix_utils.cc:61
void TransferToManager(const absl::StrongVector< IntegerVariable, double > &lp_solution, LinearConstraintManager *manager)
int64_t num_level_zero_enqueues() const
Definition: integer.h:837
void Add(Element e, double score)
bool ChangeLp(const absl::StrongVector< IntegerVariable, double > &lp_solution, glop::BasisState *solution_state)
bool VariableIsPositive(IntegerVariable i)
Definition: integer.h:138
double ScalarProduct(const LinearConstraint &constraint1, const LinearConstraint &constraint2)
::PROTOBUF_NAMESPACE_ID::int32 cut_cleanup_target() const
ConstraintIndex Add(LinearConstraint ct, bool *added=nullptr)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:29
void CanonicalizeConstraint(LinearConstraint *ct)
#define DCHECK(condition)
Definition: base/logging.h:885
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
void AddCut(LinearConstraint ct, const std::string &name, const absl::StrongVector< IntegerVariable, double > &lp_solution)
::PROTOBUF_NAMESPACE_ID::int32 new_constraints_batch_size() const
Collection of objects used to extend the Constraint Solver library.
IntVar * var
Definition: expr_array.cc:1874
bool AddCut(LinearConstraint ct, std::string type_name, const absl::StrongVector< IntegerVariable, double > &lp_solution, std::string extra_info="")
IntType IntTypeAbs(IntType t)
Definition: integer.h:78
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition: integer.h:1407
const Constraint * ct
bool NoDuplicateVariable(const LinearConstraint &ct)
const int INFO
Definition: log_severity.h:31
int64_t a