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