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