OR-Tools  9.1
linear_programming_constraint.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 <cstdint>
19 #include <iterator>
20 #include <limits>
21 #include <string>
22 #include <utility>
23 #include <vector>
24 
25 #include "absl/container/flat_hash_map.h"
26 #include "absl/numeric/int128.h"
29 #include "ortools/base/logging.h"
30 #include "ortools/base/map_util.h"
31 #include "ortools/base/mathutil.h"
32 #include "ortools/base/stl_util.h"
36 #include "ortools/glop/status.h"
40 #include "ortools/sat/integer.h"
43 
44 namespace operations_research {
45 namespace sat {
46 
47 using glop::ColIndex;
48 using glop::Fractional;
49 using glop::RowIndex;
50 
52  if (is_sparse_) {
53  for (const glop::ColIndex col : non_zeros_) {
54  dense_vector_[col] = IntegerValue(0);
55  }
56  dense_vector_.resize(size, IntegerValue(0));
57  } else {
58  dense_vector_.assign(size, IntegerValue(0));
59  }
60  for (const glop::ColIndex col : non_zeros_) {
61  is_zeros_[col] = true;
62  }
63  is_zeros_.resize(size, true);
64  non_zeros_.clear();
65  is_sparse_ = true;
66 }
67 
68 bool ScatteredIntegerVector::Add(glop::ColIndex col, IntegerValue value) {
69  const int64_t add = CapAdd(value.value(), dense_vector_[col].value());
70  if (add == std::numeric_limits<int64_t>::min() ||
72  return false;
73  dense_vector_[col] = IntegerValue(add);
74  if (is_sparse_ && is_zeros_[col]) {
75  is_zeros_[col] = false;
76  non_zeros_.push_back(col);
77  }
78  return true;
79 }
80 
82  IntegerValue multiplier,
83  const std::vector<std::pair<glop::ColIndex, IntegerValue>>& terms) {
84  const double threshold = 0.1 * static_cast<double>(dense_vector_.size());
85  if (is_sparse_ && static_cast<double>(terms.size()) < threshold) {
86  for (const std::pair<glop::ColIndex, IntegerValue> term : terms) {
87  if (is_zeros_[term.first]) {
88  is_zeros_[term.first] = false;
89  non_zeros_.push_back(term.first);
90  }
91  if (!AddProductTo(multiplier, term.second, &dense_vector_[term.first])) {
92  return false;
93  }
94  }
95  if (static_cast<double>(non_zeros_.size()) > threshold) {
96  is_sparse_ = false;
97  }
98  } else {
99  is_sparse_ = false;
100  for (const std::pair<glop::ColIndex, IntegerValue> term : terms) {
101  if (!AddProductTo(multiplier, term.second, &dense_vector_[term.first])) {
102  return false;
103  }
104  }
105  }
106  return true;
107 }
108 
110  const std::vector<IntegerVariable>& integer_variables,
111  IntegerValue upper_bound, LinearConstraint* result) {
112  result->vars.clear();
113  result->coeffs.clear();
114  if (is_sparse_) {
115  std::sort(non_zeros_.begin(), non_zeros_.end());
116  for (const glop::ColIndex col : non_zeros_) {
117  const IntegerValue coeff = dense_vector_[col];
118  if (coeff == 0) continue;
119  result->vars.push_back(integer_variables[col.value()]);
120  result->coeffs.push_back(coeff);
121  }
122  } else {
123  const int size = dense_vector_.size();
124  for (glop::ColIndex col(0); col < size; ++col) {
125  const IntegerValue coeff = dense_vector_[col];
126  if (coeff == 0) continue;
127  result->vars.push_back(integer_variables[col.value()]);
128  result->coeffs.push_back(coeff);
129  }
130  }
131  result->lb = kMinIntegerValue;
132  result->ub = upper_bound;
133 }
134 
135 std::vector<std::pair<glop::ColIndex, IntegerValue>>
137  std::vector<std::pair<glop::ColIndex, IntegerValue>> result;
138  if (is_sparse_) {
139  std::sort(non_zeros_.begin(), non_zeros_.end());
140  for (const glop::ColIndex col : non_zeros_) {
141  const IntegerValue coeff = dense_vector_[col];
142  if (coeff != 0) result.push_back({col, coeff});
143  }
144  } else {
145  const int size = dense_vector_.size();
146  for (glop::ColIndex col(0); col < size; ++col) {
147  const IntegerValue coeff = dense_vector_[col];
148  if (coeff != 0) result.push_back({col, coeff});
149  }
150  }
151  return result;
152 }
153 
154 // TODO(user): make SatParameters singleton too, otherwise changing them after
155 // a constraint was added will have no effect on this class.
157  : constraint_manager_(model),
158  sat_parameters_(*(model->GetOrCreate<SatParameters>())),
159  model_(model),
160  time_limit_(model->GetOrCreate<TimeLimit>()),
161  integer_trail_(model->GetOrCreate<IntegerTrail>()),
162  trail_(model->GetOrCreate<Trail>()),
163  integer_encoder_(model->GetOrCreate<IntegerEncoder>()),
164  random_(model->GetOrCreate<ModelRandomGenerator>()),
165  implied_bounds_processor_({}, integer_trail_,
166  model->GetOrCreate<ImpliedBounds>()),
167  dispatcher_(model->GetOrCreate<LinearProgrammingDispatcher>()),
168  expanded_lp_solution_(
169  *model->GetOrCreate<LinearProgrammingConstraintLpSolution>()) {
170  // Tweak the default parameters to make the solve incremental.
172  parameters.set_use_dual_simplex(true);
173  simplex_.SetParameters(parameters);
174  if (sat_parameters_.use_branching_in_lp() ||
175  sat_parameters_.search_branching() == SatParameters::LP_SEARCH) {
176  compute_reduced_cost_averages_ = true;
177  }
178 
179  // Register our local rev int repository.
180  integer_trail_->RegisterReversibleClass(&rc_rev_int_repository_);
181 }
182 
184  const LinearConstraint& ct) {
185  DCHECK(!lp_constraint_is_registered_);
186  constraint_manager_.Add(ct);
187 
188  // We still create the mirror variable right away though.
189  //
190  // TODO(user): clean this up? Note that it is important that the variable
191  // in lp_data_ never changes though, so we can restart from the current
192  // lp solution and be incremental (even if the constraints changed).
193  for (const IntegerVariable var : ct.vars) {
194  GetOrCreateMirrorVariable(PositiveVariable(var));
195  }
196 }
197 
198 glop::ColIndex LinearProgrammingConstraint::GetOrCreateMirrorVariable(
199  IntegerVariable positive_variable) {
200  DCHECK(VariableIsPositive(positive_variable));
201  const auto it = mirror_lp_variable_.find(positive_variable);
202  if (it == mirror_lp_variable_.end()) {
203  const glop::ColIndex col(integer_variables_.size());
204  implied_bounds_processor_.AddLpVariable(positive_variable);
205  mirror_lp_variable_[positive_variable] = col;
206  integer_variables_.push_back(positive_variable);
207  lp_solution_.push_back(std::numeric_limits<double>::infinity());
208  lp_reduced_cost_.push_back(0.0);
209  (*dispatcher_)[positive_variable] = this;
210 
211  const int index = std::max(positive_variable.value(),
212  NegationOf(positive_variable).value());
213  if (index >= expanded_lp_solution_.size()) {
214  expanded_lp_solution_.resize(index + 1, 0.0);
215  }
216  return col;
217  }
218  return it->second;
219 }
220 
222  IntegerValue coeff) {
223  CHECK(!lp_constraint_is_registered_);
224  objective_is_defined_ = true;
225  IntegerVariable pos_var = VariableIsPositive(ivar) ? ivar : NegationOf(ivar);
226  if (ivar != pos_var) coeff = -coeff;
227 
228  constraint_manager_.SetObjectiveCoefficient(pos_var, coeff);
229  const glop::ColIndex col = GetOrCreateMirrorVariable(pos_var);
230  integer_objective_.push_back({col, coeff});
231  objective_infinity_norm_ =
232  std::max(objective_infinity_norm_, IntTypeAbs(coeff));
233 }
234 
235 // TODO(user): As the search progress, some variables might get fixed. Exploit
236 // this to reduce the number of variables in the LP and in the
237 // ConstraintManager? We might also detect during the search that two variable
238 // are equivalent.
239 //
240 // TODO(user): On TSP/VRP with a lot of cuts, this can take 20% of the overall
241 // running time. We should be able to almost remove most of this from the
242 // profile by being more incremental (modulo LP scaling).
243 //
244 // TODO(user): A longer term idea for LP with a lot of variables is to not
245 // add all variables to each LP solve and do some "sifting". That can be useful
246 // for TSP for instance where the number of edges is large, but only a small
247 // fraction will be used in the optimal solution.
248 bool LinearProgrammingConstraint::CreateLpFromConstraintManager() {
249  // Fill integer_lp_.
250  integer_lp_.clear();
251  infinity_norms_.clear();
252  const auto& all_constraints = constraint_manager_.AllConstraints();
253  for (const auto index : constraint_manager_.LpConstraints()) {
254  const LinearConstraint& ct = all_constraints[index].constraint;
255 
256  integer_lp_.push_back(LinearConstraintInternal());
257  LinearConstraintInternal& new_ct = integer_lp_.back();
258  new_ct.lb = ct.lb;
259  new_ct.ub = ct.ub;
260  const int size = ct.vars.size();
261  IntegerValue infinity_norm(0);
262  if (ct.lb > ct.ub) {
263  VLOG(1) << "Trivial infeasible bound in an LP constraint";
264  return false;
265  }
266  if (ct.lb > kMinIntegerValue) {
267  infinity_norm = std::max(infinity_norm, IntTypeAbs(ct.lb));
268  }
269  if (ct.ub < kMaxIntegerValue) {
270  infinity_norm = std::max(infinity_norm, IntTypeAbs(ct.ub));
271  }
272  for (int i = 0; i < size; ++i) {
273  // We only use positive variable inside this class.
274  IntegerVariable var = ct.vars[i];
275  IntegerValue coeff = ct.coeffs[i];
276  if (!VariableIsPositive(var)) {
277  var = NegationOf(var);
278  coeff = -coeff;
279  }
280  infinity_norm = std::max(infinity_norm, IntTypeAbs(coeff));
281  new_ct.terms.push_back({GetOrCreateMirrorVariable(var), coeff});
282  }
283  infinity_norms_.push_back(infinity_norm);
284 
285  // Important to keep lp_data_ "clean".
286  std::sort(new_ct.terms.begin(), new_ct.terms.end());
287  }
288 
289  // Copy the integer_lp_ into lp_data_.
290  lp_data_.Clear();
291  for (int i = 0; i < integer_variables_.size(); ++i) {
292  CHECK_EQ(glop::ColIndex(i), lp_data_.CreateNewVariable());
293  }
294 
295  // We remove fixed variables from the objective. This should help the LP
296  // scaling, but also our integer reason computation.
297  int new_size = 0;
298  objective_infinity_norm_ = 0;
299  for (const auto entry : integer_objective_) {
300  const IntegerVariable var = integer_variables_[entry.first.value()];
301  if (integer_trail_->IsFixedAtLevelZero(var)) {
302  integer_objective_offset_ +=
303  entry.second * integer_trail_->LevelZeroLowerBound(var);
304  continue;
305  }
306  objective_infinity_norm_ =
307  std::max(objective_infinity_norm_, IntTypeAbs(entry.second));
308  integer_objective_[new_size++] = entry;
309  lp_data_.SetObjectiveCoefficient(entry.first, ToDouble(entry.second));
310  }
311  objective_infinity_norm_ =
312  std::max(objective_infinity_norm_, IntTypeAbs(integer_objective_offset_));
313  integer_objective_.resize(new_size);
314  lp_data_.SetObjectiveOffset(ToDouble(integer_objective_offset_));
315 
316  for (const LinearConstraintInternal& ct : integer_lp_) {
317  const ConstraintIndex row = lp_data_.CreateNewConstraint();
318  lp_data_.SetConstraintBounds(row, ToDouble(ct.lb), ToDouble(ct.ub));
319  for (const auto& term : ct.terms) {
320  lp_data_.SetCoefficient(row, term.first, ToDouble(term.second));
321  }
322  }
323  lp_data_.NotifyThatColumnsAreClean();
324 
325  // We scale the LP using the level zero bounds that we later override
326  // with the current ones.
327  //
328  // TODO(user): As part of the scaling, we may also want to shift the initial
329  // variable bounds so that each variable contain the value zero in their
330  // domain. Maybe just once and for all at the beginning.
331  const int num_vars = integer_variables_.size();
332  for (int i = 0; i < num_vars; i++) {
333  const IntegerVariable cp_var = integer_variables_[i];
334  const double lb = ToDouble(integer_trail_->LevelZeroLowerBound(cp_var));
335  const double ub = ToDouble(integer_trail_->LevelZeroUpperBound(cp_var));
336  lp_data_.SetVariableBounds(glop::ColIndex(i), lb, ub);
337  }
338 
339  // TODO(user): As we have an idea of the LP optimal after the first solves,
340  // maybe we can adapt the scaling accordingly.
341  glop::GlopParameters params;
342  params.set_cost_scaling(glop::GlopParameters::MEAN_COST_SCALING);
343  scaler_.Scale(params, &lp_data_);
344  UpdateBoundsOfLpVariables();
345 
346  // Set the information for the step to polish the LP basis. All our variables
347  // are integer, but for now, we just try to minimize the fractionality of the
348  // binary variables.
349  if (sat_parameters_.polish_lp_solution()) {
350  simplex_.ClearIntegralityScales();
351  for (int i = 0; i < num_vars; ++i) {
352  const IntegerVariable cp_var = integer_variables_[i];
353  const IntegerValue lb = integer_trail_->LevelZeroLowerBound(cp_var);
354  const IntegerValue ub = integer_trail_->LevelZeroUpperBound(cp_var);
355  if (lb != 0 || ub != 1) continue;
356  simplex_.SetIntegralityScale(
357  glop::ColIndex(i),
358  1.0 / scaler_.VariableScalingFactor(glop::ColIndex(i)));
359  }
360  }
361 
362  lp_data_.NotifyThatColumnsAreClean();
363  VLOG(1) << "LP relaxation: " << lp_data_.GetDimensionString() << ". "
364  << constraint_manager_.AllConstraints().size()
365  << " Managed constraints.";
366  return true;
367 }
368 
369 LPSolveInfo LinearProgrammingConstraint::SolveLpForBranching() {
370  LPSolveInfo info;
371  glop::BasisState basis_state = simplex_.GetState();
372 
373  const glop::Status status = simplex_.Solve(lp_data_, time_limit_);
374  total_num_simplex_iterations_ += simplex_.GetNumberOfIterations();
375  simplex_.LoadStateForNextSolve(basis_state);
376  if (!status.ok()) {
377  VLOG(1) << "The LP solver encountered an error: " << status.error_message();
378  info.status = glop::ProblemStatus::ABNORMAL;
379  return info;
380  }
381  info.status = simplex_.GetProblemStatus();
382  if (info.status == glop::ProblemStatus::OPTIMAL ||
383  info.status == glop::ProblemStatus::DUAL_FEASIBLE) {
384  // Record the objective bound.
385  info.lp_objective = simplex_.GetObjectiveValue();
386  info.new_obj_bound = IntegerValue(
387  static_cast<int64_t>(std::ceil(info.lp_objective - kCpEpsilon)));
388  }
389  return info;
390 }
391 
392 void LinearProgrammingConstraint::FillReducedCostReasonIn(
393  const glop::DenseRow& reduced_costs,
394  std::vector<IntegerLiteral>* integer_reason) {
395  integer_reason->clear();
396  const int num_vars = integer_variables_.size();
397  for (int i = 0; i < num_vars; i++) {
398  const double rc = reduced_costs[glop::ColIndex(i)];
399  if (rc > kLpEpsilon) {
400  integer_reason->push_back(
401  integer_trail_->LowerBoundAsLiteral(integer_variables_[i]));
402  } else if (rc < -kLpEpsilon) {
403  integer_reason->push_back(
404  integer_trail_->UpperBoundAsLiteral(integer_variables_[i]));
405  }
406  }
407 
408  integer_trail_->RemoveLevelZeroBounds(integer_reason);
409 }
410 
411 bool LinearProgrammingConstraint::BranchOnVar(IntegerVariable positive_var) {
412  // From the current LP solution, branch on the given var if fractional.
413  DCHECK(lp_solution_is_set_);
414  const double current_value = GetSolutionValue(positive_var);
415  DCHECK_GT(std::abs(current_value - std::round(current_value)), kCpEpsilon);
416 
417  // Used as empty reason in this method.
418  integer_reason_.clear();
419 
420  bool deductions_were_made = false;
421 
422  UpdateBoundsOfLpVariables();
423 
424  const IntegerValue current_obj_lb = integer_trail_->LowerBound(objective_cp_);
425  // This will try to branch in both direction around the LP value of the
426  // given variable and push any deduction done this way.
427 
428  const glop::ColIndex lp_var = GetOrCreateMirrorVariable(positive_var);
429  const double current_lb = ToDouble(integer_trail_->LowerBound(positive_var));
430  const double current_ub = ToDouble(integer_trail_->UpperBound(positive_var));
431  const double factor = scaler_.VariableScalingFactor(lp_var);
432  if (current_value < current_lb || current_value > current_ub) {
433  return false;
434  }
435 
436  // Form LP1 var <= floor(current_value)
437  const double new_ub = std::floor(current_value);
438  lp_data_.SetVariableBounds(lp_var, current_lb * factor, new_ub * factor);
439 
440  LPSolveInfo lower_branch_info = SolveLpForBranching();
441  if (lower_branch_info.status != glop::ProblemStatus::OPTIMAL &&
442  lower_branch_info.status != glop::ProblemStatus::DUAL_FEASIBLE &&
443  lower_branch_info.status != glop::ProblemStatus::DUAL_UNBOUNDED) {
444  return false;
445  }
446 
447  if (lower_branch_info.status == glop::ProblemStatus::DUAL_UNBOUNDED) {
448  // Push the other branch.
449  const IntegerLiteral deduction = IntegerLiteral::GreaterOrEqual(
450  positive_var, IntegerValue(std::ceil(current_value)));
451  if (!integer_trail_->Enqueue(deduction, {}, integer_reason_)) {
452  return false;
453  }
454  deductions_were_made = true;
455  } else if (lower_branch_info.new_obj_bound <= current_obj_lb) {
456  return false;
457  }
458 
459  // Form LP2 var >= ceil(current_value)
460  const double new_lb = std::ceil(current_value);
461  lp_data_.SetVariableBounds(lp_var, new_lb * factor, current_ub * factor);
462 
463  LPSolveInfo upper_branch_info = SolveLpForBranching();
464  if (upper_branch_info.status != glop::ProblemStatus::OPTIMAL &&
465  upper_branch_info.status != glop::ProblemStatus::DUAL_FEASIBLE &&
466  upper_branch_info.status != glop::ProblemStatus::DUAL_UNBOUNDED) {
467  return deductions_were_made;
468  }
469 
470  if (upper_branch_info.status == glop::ProblemStatus::DUAL_UNBOUNDED) {
471  // Push the other branch if not infeasible.
472  if (lower_branch_info.status != glop::ProblemStatus::DUAL_UNBOUNDED) {
473  const IntegerLiteral deduction = IntegerLiteral::LowerOrEqual(
474  positive_var, IntegerValue(std::floor(current_value)));
475  if (!integer_trail_->Enqueue(deduction, {}, integer_reason_)) {
476  return deductions_were_made;
477  }
478  deductions_were_made = true;
479  }
480  } else if (upper_branch_info.new_obj_bound <= current_obj_lb) {
481  return deductions_were_made;
482  }
483 
484  IntegerValue approximate_obj_lb = kMinIntegerValue;
485 
486  if (lower_branch_info.status == glop::ProblemStatus::DUAL_UNBOUNDED &&
487  upper_branch_info.status == glop::ProblemStatus::DUAL_UNBOUNDED) {
488  return integer_trail_->ReportConflict(integer_reason_);
489  } else if (lower_branch_info.status == glop::ProblemStatus::DUAL_UNBOUNDED) {
490  approximate_obj_lb = upper_branch_info.new_obj_bound;
491  } else if (upper_branch_info.status == glop::ProblemStatus::DUAL_UNBOUNDED) {
492  approximate_obj_lb = lower_branch_info.new_obj_bound;
493  } else {
494  approximate_obj_lb = std::min(lower_branch_info.new_obj_bound,
495  upper_branch_info.new_obj_bound);
496  }
497 
498  // NOTE: On some problems, the approximate_obj_lb could be inexact which add
499  // some tolerance to CP-SAT where currently there is none.
500  if (approximate_obj_lb <= current_obj_lb) return deductions_were_made;
501 
502  // Push the bound to the trail.
503  const IntegerLiteral deduction =
504  IntegerLiteral::GreaterOrEqual(objective_cp_, approximate_obj_lb);
505  if (!integer_trail_->Enqueue(deduction, {}, integer_reason_)) {
506  return deductions_were_made;
507  }
508 
509  return true;
510 }
511 
513  DCHECK(!lp_constraint_is_registered_);
514  lp_constraint_is_registered_ = true;
515  model->GetOrCreate<LinearProgrammingConstraintCollection>()->push_back(this);
516 
517  // Note fdid, this is not really needed by should lead to better cache
518  // locality.
519  std::sort(integer_objective_.begin(), integer_objective_.end());
520 
521  // Set the LP to its initial content.
522  if (!sat_parameters_.add_lp_constraints_lazily()) {
523  constraint_manager_.AddAllConstraintsToLp();
524  }
525  if (!CreateLpFromConstraintManager()) {
526  model->GetOrCreate<SatSolver>()->NotifyThatModelIsUnsat();
527  return;
528  }
529 
530  GenericLiteralWatcher* watcher = model->GetOrCreate<GenericLiteralWatcher>();
531  const int watcher_id = watcher->Register(this);
532  const int num_vars = integer_variables_.size();
533  for (int i = 0; i < num_vars; i++) {
534  watcher->WatchIntegerVariable(integer_variables_[i], watcher_id, i);
535  }
536  if (objective_is_defined_) {
537  watcher->WatchUpperBound(objective_cp_, watcher_id);
538  }
539  watcher->SetPropagatorPriority(watcher_id, 2);
540  watcher->AlwaysCallAtLevelZero(watcher_id);
541 
542  // Registering it with the trail make sure this class is always in sync when
543  // it is used in the decision heuristics.
544  integer_trail_->RegisterReversibleClass(this);
545  watcher->RegisterReversibleInt(watcher_id, &rev_optimal_constraints_size_);
546 }
547 
549  optimal_constraints_.resize(rev_optimal_constraints_size_);
550  if (lp_solution_is_set_ && level < lp_solution_level_) {
551  lp_solution_is_set_ = false;
552  }
553 
554  // Special case for level zero, we "reload" any previously known optimal
555  // solution from that level.
556  //
557  // TODO(user): Keep all optimal solution in the current branch?
558  // TODO(user): Still try to add cuts/constraints though!
559  if (level == 0 && !level_zero_lp_solution_.empty()) {
560  lp_solution_is_set_ = true;
561  lp_solution_ = level_zero_lp_solution_;
562  lp_solution_level_ = 0;
563  for (int i = 0; i < lp_solution_.size(); i++) {
564  expanded_lp_solution_[integer_variables_[i]] = lp_solution_[i];
565  expanded_lp_solution_[NegationOf(integer_variables_[i])] =
566  -lp_solution_[i];
567  }
568  }
569 }
570 
572  for (const IntegerVariable var : generator.vars) {
573  GetOrCreateMirrorVariable(VariableIsPositive(var) ? var : NegationOf(var));
574  }
575  cut_generators_.push_back(std::move(generator));
576 }
577 
579  const std::vector<int>& watch_indices) {
580  if (!lp_solution_is_set_) return Propagate();
581 
582  // At level zero, if there is still a chance to add cuts or lazy constraints,
583  // we re-run the LP.
584  if (trail_->CurrentDecisionLevel() == 0 && !lp_at_level_zero_is_final_) {
585  return Propagate();
586  }
587 
588  // Check whether the change breaks the current LP solution. If it does, call
589  // Propagate() on the current LP.
590  for (const int index : watch_indices) {
591  const double lb =
592  ToDouble(integer_trail_->LowerBound(integer_variables_[index]));
593  const double ub =
594  ToDouble(integer_trail_->UpperBound(integer_variables_[index]));
595  const double value = lp_solution_[index];
596  if (value < lb - kCpEpsilon || value > ub + kCpEpsilon) return Propagate();
597  }
598 
599  // TODO(user): The saved lp solution is still valid given the current variable
600  // bounds, so the LP optimal didn't change. However we might still want to add
601  // new cuts or new lazy constraints?
602  //
603  // TODO(user): Propagate the last optimal_constraint? Note that we need
604  // to be careful since the reversible int in IntegerSumLE are not registered.
605  // However, because we delete "optimalconstraints" on backtrack, we might not
606  // care.
607  return true;
608 }
609 
610 glop::Fractional LinearProgrammingConstraint::GetVariableValueAtCpScale(
611  glop::ColIndex var) {
612  return scaler_.UnscaleVariableValue(var, simplex_.GetVariableValue(var));
613 }
614 
616  IntegerVariable variable) const {
617  return lp_solution_[gtl::FindOrDie(mirror_lp_variable_, variable).value()];
618 }
619 
621  IntegerVariable variable) const {
622  return lp_reduced_cost_[gtl::FindOrDie(mirror_lp_variable_, variable)
623  .value()];
624 }
625 
626 void LinearProgrammingConstraint::UpdateBoundsOfLpVariables() {
627  const int num_vars = integer_variables_.size();
628  for (int i = 0; i < num_vars; i++) {
629  const IntegerVariable cp_var = integer_variables_[i];
630  const double lb = ToDouble(integer_trail_->LowerBound(cp_var));
631  const double ub = ToDouble(integer_trail_->UpperBound(cp_var));
632  const double factor = scaler_.VariableScalingFactor(glop::ColIndex(i));
633  lp_data_.SetVariableBounds(glop::ColIndex(i), lb * factor, ub * factor);
634  }
635 }
636 
637 bool LinearProgrammingConstraint::SolveLp() {
638  if (trail_->CurrentDecisionLevel() == 0) {
639  lp_at_level_zero_is_final_ = false;
640  }
641 
642  const auto status = simplex_.Solve(lp_data_, time_limit_);
643  total_num_simplex_iterations_ += simplex_.GetNumberOfIterations();
644  if (!status.ok()) {
645  VLOG(1) << "The LP solver encountered an error: " << status.error_message();
646  simplex_.ClearStateForNextSolve();
647  return false;
648  }
649  average_degeneracy_.AddData(CalculateDegeneracy());
650  if (average_degeneracy_.CurrentAverage() >= 1000.0) {
651  VLOG(2) << "High average degeneracy: "
652  << average_degeneracy_.CurrentAverage();
653  }
654 
655  const int status_as_int = static_cast<int>(simplex_.GetProblemStatus());
656  if (status_as_int >= num_solves_by_status_.size()) {
657  num_solves_by_status_.resize(status_as_int + 1);
658  }
659  num_solves_++;
660  num_solves_by_status_[status_as_int]++;
661  VLOG(2) << "lvl:" << trail_->CurrentDecisionLevel() << " "
662  << simplex_.GetProblemStatus()
663  << " iter:" << simplex_.GetNumberOfIterations()
664  << " obj:" << simplex_.GetObjectiveValue();
665 
667  lp_solution_is_set_ = true;
668  lp_solution_level_ = trail_->CurrentDecisionLevel();
669  const int num_vars = integer_variables_.size();
670  for (int i = 0; i < num_vars; i++) {
671  const glop::Fractional value =
672  GetVariableValueAtCpScale(glop::ColIndex(i));
673  lp_solution_[i] = value;
674  expanded_lp_solution_[integer_variables_[i]] = value;
675  expanded_lp_solution_[NegationOf(integer_variables_[i])] = -value;
676  }
677 
678  if (lp_solution_level_ == 0) {
679  level_zero_lp_solution_ = lp_solution_;
680  }
681  }
682  return true;
683 }
684 
685 bool LinearProgrammingConstraint::AddCutFromConstraints(
686  const std::string& name,
687  const std::vector<std::pair<RowIndex, IntegerValue>>& integer_multipliers) {
688  // This is initialized to a valid linear constraint (by taking linear
689  // combination of the LP rows) and will be transformed into a cut if
690  // possible.
691  //
692  // TODO(user): For CG cuts, Ideally this linear combination should have only
693  // one fractional variable (basis_col). But because of imprecision, we get a
694  // bunch of fractional entry with small coefficient (relative to the one of
695  // basis_col). We try to handle that in IntegerRoundingCut(), but it might be
696  // better to add small multiple of the involved rows to get rid of them.
697  IntegerValue cut_ub;
698  if (!ComputeNewLinearConstraint(integer_multipliers, &tmp_scattered_vector_,
699  &cut_ub)) {
700  VLOG(1) << "Issue, overflow!";
701  return false;
702  }
703 
704  // Important: because we use integer_multipliers below, we cannot just
705  // divide by GCD or call PreventOverflow() here.
706  //
707  // TODO(user): the conversion col_index -> IntegerVariable is slow and could
708  // in principle be removed. Easy for cuts, but not so much for
709  // implied_bounds_processor_. Note that in theory this could allow us to
710  // use Literal directly without the need to have an IntegerVariable for them.
711  tmp_scattered_vector_.ConvertToLinearConstraint(integer_variables_, cut_ub,
712  &cut_);
713 
714  // Note that the base constraint we use are currently always tight.
715  // It is not a requirement though.
716  if (DEBUG_MODE) {
717  const double norm = ToDouble(ComputeInfinityNorm(cut_));
718  const double activity = ComputeActivity(cut_, expanded_lp_solution_);
719  if (std::abs(activity - ToDouble(cut_.ub)) / norm > 1e-4) {
720  VLOG(1) << "Cut not tight " << activity << " <= " << ToDouble(cut_.ub);
721  return false;
722  }
723  }
724  CHECK(constraint_manager_.DebugCheckConstraint(cut_));
725 
726  // We will create "artificial" variables after this index that will be
727  // substitued back into LP variables afterwards. Also not that we only use
728  // positive variable indices for these new variables, so that algorithm that
729  // take their negation will not mess up the indexing.
730  const IntegerVariable first_new_var(expanded_lp_solution_.size());
731  CHECK_EQ(first_new_var.value() % 2, 0);
732 
733  LinearConstraint copy_in_debug;
734  if (DEBUG_MODE) {
735  copy_in_debug = cut_;
736  }
737 
738  // Unlike for the knapsack cuts, it might not be always beneficial to
739  // process the implied bounds even though it seems to be better in average.
740  //
741  // TODO(user): Perform more experiments, in particular with which bound we use
742  // and if we complement or not before the MIR rounding. Other solvers seems
743  // to try different complementation strategies in a "potprocessing" and we
744  // don't. Try this too.
745  std::vector<ImpliedBoundsProcessor::SlackInfo> ib_slack_infos;
746  implied_bounds_processor_.ProcessUpperBoundedConstraintWithSlackCreation(
747  /*substitute_only_inner_variables=*/false, first_new_var,
748  expanded_lp_solution_, &cut_, &ib_slack_infos);
749  DCHECK(implied_bounds_processor_.DebugSlack(first_new_var, copy_in_debug,
750  cut_, ib_slack_infos));
751 
752  // Fills data for IntegerRoundingCut().
753  //
754  // Note(user): we use the current bound here, so the reasonement will only
755  // produce locally valid cut if we call this at a non-root node. We could
756  // use the level zero bounds if we wanted to generate a globally valid cut
757  // at another level. For now this is only called at level zero anyway.
758  tmp_lp_values_.clear();
759  tmp_var_lbs_.clear();
760  tmp_var_ubs_.clear();
761  for (const IntegerVariable var : cut_.vars) {
762  if (var >= first_new_var) {
764  const auto& info =
765  ib_slack_infos[(var.value() - first_new_var.value()) / 2];
766  tmp_lp_values_.push_back(info.lp_value);
767  tmp_var_lbs_.push_back(info.lb);
768  tmp_var_ubs_.push_back(info.ub);
769  } else {
770  tmp_lp_values_.push_back(expanded_lp_solution_[var]);
771  tmp_var_lbs_.push_back(integer_trail_->LevelZeroLowerBound(var));
772  tmp_var_ubs_.push_back(integer_trail_->LevelZeroUpperBound(var));
773  }
774  }
775 
776  // Add slack.
777  // definition: integer_lp_[row] + slack_row == bound;
778  const IntegerVariable first_slack(first_new_var +
779  IntegerVariable(2 * ib_slack_infos.size()));
780  tmp_slack_rows_.clear();
781  tmp_slack_bounds_.clear();
782  for (const auto pair : integer_multipliers) {
783  const RowIndex row = pair.first;
784  const IntegerValue coeff = pair.second;
785  const auto status = simplex_.GetConstraintStatus(row);
786  if (status == glop::ConstraintStatus::FIXED_VALUE) continue;
787 
788  tmp_lp_values_.push_back(0.0);
789  cut_.vars.push_back(first_slack +
790  2 * IntegerVariable(tmp_slack_rows_.size()));
791  tmp_slack_rows_.push_back(row);
792  cut_.coeffs.push_back(coeff);
793 
794  const IntegerValue diff(
795  CapSub(integer_lp_[row].ub.value(), integer_lp_[row].lb.value()));
796  if (coeff > 0) {
797  tmp_slack_bounds_.push_back(integer_lp_[row].ub);
798  tmp_var_lbs_.push_back(IntegerValue(0));
799  tmp_var_ubs_.push_back(diff);
800  } else {
801  tmp_slack_bounds_.push_back(integer_lp_[row].lb);
802  tmp_var_lbs_.push_back(-diff);
803  tmp_var_ubs_.push_back(IntegerValue(0));
804  }
805  }
806 
807  bool at_least_one_added = false;
808 
809  // Try cover appraoch to find cut.
810  {
811  if (cover_cut_helper_.TrySimpleKnapsack(cut_, tmp_lp_values_, tmp_var_lbs_,
812  tmp_var_ubs_)) {
813  at_least_one_added |= PostprocessAndAddCut(
814  absl::StrCat(name, "_K"), cover_cut_helper_.Info(), first_new_var,
815  first_slack, ib_slack_infos, cover_cut_helper_.mutable_cut());
816  }
817  }
818 
819  // Try integer rounding heuristic to find cut.
820  {
821  RoundingOptions options;
822  options.max_scaling = sat_parameters_.max_integer_rounding_scaling();
823  integer_rounding_cut_helper_.ComputeCut(options, tmp_lp_values_,
824  tmp_var_lbs_, tmp_var_ubs_,
825  &implied_bounds_processor_, &cut_);
826  at_least_one_added |= PostprocessAndAddCut(
827  name,
828  absl::StrCat("num_lifted_booleans=",
829  integer_rounding_cut_helper_.NumLiftedBooleans()),
830  first_new_var, first_slack, ib_slack_infos, &cut_);
831  }
832  return at_least_one_added;
833 }
834 
835 bool LinearProgrammingConstraint::PostprocessAndAddCut(
836  const std::string& name, const std::string& info,
837  IntegerVariable first_new_var, IntegerVariable first_slack,
838  const std::vector<ImpliedBoundsProcessor::SlackInfo>& ib_slack_infos,
839  LinearConstraint* cut) {
840  // Compute the activity. Warning: the cut no longer have the same size so we
841  // cannot use tmp_lp_values_. Note that the substitution below shouldn't
842  // change the activity by definition.
843  double activity = 0.0;
844  for (int i = 0; i < cut->vars.size(); ++i) {
845  if (cut->vars[i] < first_new_var) {
846  activity +=
847  ToDouble(cut->coeffs[i]) * expanded_lp_solution_[cut->vars[i]];
848  }
849  }
850  const double kMinViolation = 1e-4;
851  const double violation = activity - ToDouble(cut->ub);
852  if (violation < kMinViolation) {
853  VLOG(3) << "Bad cut " << activity << " <= " << ToDouble(cut->ub);
854  return false;
855  }
856 
857  // Substitute any slack left.
858  {
859  int num_slack = 0;
860  tmp_scattered_vector_.ClearAndResize(integer_variables_.size());
861  IntegerValue cut_ub = cut->ub;
862  bool overflow = false;
863  for (int i = 0; i < cut->vars.size(); ++i) {
864  const IntegerVariable var = cut->vars[i];
865 
866  // Simple copy for non-slack variables.
867  if (var < first_new_var) {
868  const glop::ColIndex col =
869  gtl::FindOrDie(mirror_lp_variable_, PositiveVariable(var));
870  if (VariableIsPositive(var)) {
871  tmp_scattered_vector_.Add(col, cut->coeffs[i]);
872  } else {
873  tmp_scattered_vector_.Add(col, -cut->coeffs[i]);
874  }
875  continue;
876  }
877 
878  // Replace slack from bound substitution.
879  if (var < first_slack) {
880  const IntegerValue multiplier = cut->coeffs[i];
881  const int index = (var.value() - first_new_var.value()) / 2;
882  CHECK_LT(index, ib_slack_infos.size());
883 
884  std::vector<std::pair<ColIndex, IntegerValue>> terms;
885  for (const std::pair<IntegerVariable, IntegerValue>& term :
886  ib_slack_infos[index].terms) {
887  terms.push_back(
888  {gtl::FindOrDie(mirror_lp_variable_,
889  PositiveVariable(term.first)),
890  VariableIsPositive(term.first) ? term.second : -term.second});
891  }
892  if (!tmp_scattered_vector_.AddLinearExpressionMultiple(multiplier,
893  terms)) {
894  overflow = true;
895  break;
896  }
897  if (!AddProductTo(multiplier, -ib_slack_infos[index].offset, &cut_ub)) {
898  overflow = true;
899  break;
900  }
901  continue;
902  }
903 
904  // Replace slack from LP constraints.
905  ++num_slack;
906  const int slack_index = (var.value() - first_slack.value()) / 2;
907  const glop::RowIndex row = tmp_slack_rows_[slack_index];
908  const IntegerValue multiplier = -cut->coeffs[i];
909  if (!tmp_scattered_vector_.AddLinearExpressionMultiple(
910  multiplier, integer_lp_[row].terms)) {
911  overflow = true;
912  break;
913  }
914 
915  // Update rhs.
916  if (!AddProductTo(multiplier, tmp_slack_bounds_[slack_index], &cut_ub)) {
917  overflow = true;
918  break;
919  }
920  }
921 
922  if (overflow) {
923  VLOG(1) << "Overflow in slack removal.";
924  return false;
925  }
926 
927  VLOG(3) << " num_slack: " << num_slack;
928  tmp_scattered_vector_.ConvertToLinearConstraint(integer_variables_, cut_ub,
929  cut);
930  }
931 
932  // Display some stats used for investigation of cut generation.
933  const std::string extra_info =
934  absl::StrCat(info, " num_ib_substitutions=", ib_slack_infos.size());
935 
936  const double new_violation =
937  ComputeActivity(*cut, expanded_lp_solution_) - ToDouble(cut_.ub);
938  if (std::abs(violation - new_violation) >= 1e-4) {
939  VLOG(1) << "Violation discrepancy after slack removal. "
940  << " before = " << violation << " after = " << new_violation;
941  }
942 
943  DivideByGCD(cut);
944  return constraint_manager_.AddCut(*cut, name, expanded_lp_solution_,
945  extra_info);
946 }
947 
948 // TODO(user): This can be still too slow on some problems like
949 // 30_70_45_05_100.mps.gz. Not this actual function, but the set of computation
950 // it triggers. We should add heuristics to abort earlier if a cut is not
951 // promising. Or only test a few positions and not all rows.
952 void LinearProgrammingConstraint::AddCGCuts() {
953  const RowIndex num_rows = lp_data_.num_constraints();
954  std::vector<std::pair<RowIndex, double>> lp_multipliers;
955  std::vector<std::pair<RowIndex, IntegerValue>> integer_multipliers;
956  for (RowIndex row(0); row < num_rows; ++row) {
957  ColIndex basis_col = simplex_.GetBasis(row);
958  const Fractional lp_value = GetVariableValueAtCpScale(basis_col);
959 
960  // Only consider fractional basis element. We ignore element that are close
961  // to an integer to reduce the amount of positions we try.
962  //
963  // TODO(user): We could just look at the diff with std::floor() in the hope
964  // that when we are just under an integer, the exact computation below will
965  // also be just under it.
966  if (std::abs(lp_value - std::round(lp_value)) < 0.01) continue;
967 
968  // If this variable is a slack, we ignore it. This is because the
969  // corresponding row is not tight under the given lp values.
970  if (basis_col >= integer_variables_.size()) continue;
971 
972  if (time_limit_->LimitReached()) break;
973 
974  // TODO(user): Avoid code duplication between the sparse/dense path.
975  double magnitude = 0.0;
976  lp_multipliers.clear();
977  const glop::ScatteredRow& lambda = simplex_.GetUnitRowLeftInverse(row);
978  if (lambda.non_zeros.empty()) {
979  for (RowIndex row(0); row < num_rows; ++row) {
980  const double value = lambda.values[glop::RowToColIndex(row)];
981  if (std::abs(value) < kZeroTolerance) continue;
982 
983  // There should be no BASIC status, but they could be imprecision
984  // in the GetUnitRowLeftInverse() code? not sure, so better be safe.
985  const auto status = simplex_.GetConstraintStatus(row);
986  if (status == glop::ConstraintStatus::BASIC) {
987  VLOG(1) << "BASIC row not expected! " << value;
988  continue;
989  }
990 
991  magnitude = std::max(magnitude, std::abs(value));
992  lp_multipliers.push_back({row, value});
993  }
994  } else {
995  for (const ColIndex col : lambda.non_zeros) {
996  const RowIndex row = glop::ColToRowIndex(col);
997  const double value = lambda.values[col];
998  if (std::abs(value) < kZeroTolerance) continue;
999 
1000  const auto status = simplex_.GetConstraintStatus(row);
1001  if (status == glop::ConstraintStatus::BASIC) {
1002  VLOG(1) << "BASIC row not expected! " << value;
1003  continue;
1004  }
1005 
1006  magnitude = std::max(magnitude, std::abs(value));
1007  lp_multipliers.push_back({row, value});
1008  }
1009  }
1010  if (lp_multipliers.empty()) continue;
1011 
1012  Fractional scaling;
1013  for (int i = 0; i < 2; ++i) {
1014  if (i == 1) {
1015  // Try other sign.
1016  //
1017  // TODO(user): Maybe add an heuristic to know beforehand which sign to
1018  // use?
1019  for (std::pair<RowIndex, double>& p : lp_multipliers) {
1020  p.second = -p.second;
1021  }
1022  }
1023 
1024  // TODO(user): We use a lower value here otherwise we might run into
1025  // overflow while computing the cut. This should be fixable.
1026  integer_multipliers =
1027  ScaleLpMultiplier(/*take_objective_into_account=*/false,
1028  lp_multipliers, &scaling, /*max_pow=*/52);
1029  AddCutFromConstraints("CG", integer_multipliers);
1030  }
1031  }
1032 }
1033 
1034 namespace {
1035 
1036 // For each element of a, adds a random one in b and append the pair to output.
1037 void RandomPick(const std::vector<RowIndex>& a, const std::vector<RowIndex>& b,
1038  ModelRandomGenerator* random,
1039  std::vector<std::pair<RowIndex, RowIndex>>* output) {
1040  if (a.empty() || b.empty()) return;
1041  for (const RowIndex row : a) {
1042  const RowIndex other = b[absl::Uniform<int>(*random, 0, b.size())];
1043  if (other != row) {
1044  output->push_back({row, other});
1045  }
1046  }
1047 }
1048 
1049 template <class ListOfTerms>
1050 IntegerValue GetCoeff(ColIndex col, const ListOfTerms& terms) {
1051  for (const auto& term : terms) {
1052  if (term.first == col) return term.second;
1053  }
1054  return IntegerValue(0);
1055 }
1056 
1057 } // namespace
1058 
1059 void LinearProgrammingConstraint::AddMirCuts() {
1060  // Heuristic to generate MIR_n cuts by combining a small number of rows. This
1061  // works greedily and follow more or less the MIR cut description in the
1062  // literature. We have a current cut, and we add one more row to it while
1063  // eliminating a variable of the current cut whose LP value is far from its
1064  // bound.
1065  //
1066  // A notable difference is that we randomize the variable we eliminate and
1067  // the row we use to do so. We still have weights to indicate our preferred
1068  // choices. This allows to generate different cuts when called again and
1069  // again.
1070  //
1071  // TODO(user): We could combine n rows to make sure we eliminate n variables
1072  // far away from their bounds by solving exactly in integer small linear
1073  // system.
1075  integer_variables_.size(), IntegerValue(0));
1076  SparseBitset<ColIndex> non_zeros_(ColIndex(integer_variables_.size()));
1077 
1078  // We compute all the rows that are tight, these will be used as the base row
1079  // for the MIR_n procedure below.
1080  const RowIndex num_rows = lp_data_.num_constraints();
1081  std::vector<std::pair<RowIndex, IntegerValue>> base_rows;
1082  absl::StrongVector<RowIndex, double> row_weights(num_rows.value(), 0.0);
1083  for (RowIndex row(0); row < num_rows; ++row) {
1084  const auto status = simplex_.GetConstraintStatus(row);
1085  if (status == glop::ConstraintStatus::BASIC) continue;
1086  if (status == glop::ConstraintStatus::FREE) continue;
1087 
1090  base_rows.push_back({row, IntegerValue(1)});
1091  }
1094  base_rows.push_back({row, IntegerValue(-1)});
1095  }
1096 
1097  // For now, we use the dual values for the row "weights".
1098  //
1099  // Note that we use the dual at LP scale so that it make more sense when we
1100  // compare different rows since the LP has been scaled.
1101  //
1102  // TODO(user): In Kati Wolter PhD "Implementation of Cutting Plane
1103  // Separators for Mixed Integer Programs" which describe SCIP's MIR cuts
1104  // implementation (or at least an early version of it), a more complex score
1105  // is used.
1106  //
1107  // Note(user): Because we only consider tight rows under the current lp
1108  // solution (i.e. non-basic rows), most should have a non-zero dual values.
1109  // But there is some degenerate problem where these rows have a really low
1110  // weight (or even zero), and having only weight of exactly zero in
1111  // std::discrete_distribution will result in a crash.
1112  row_weights[row] = std::max(1e-8, std::abs(simplex_.GetDualValue(row)));
1113  }
1114 
1115  std::vector<double> weights;
1117  std::vector<std::pair<RowIndex, IntegerValue>> integer_multipliers;
1118  for (const std::pair<RowIndex, IntegerValue>& entry : base_rows) {
1119  if (time_limit_->LimitReached()) break;
1120 
1121  // First try to generate a cut directly from this base row (MIR1).
1122  //
1123  // Note(user): We abort on success like it seems to be done in the
1124  // literature. Note that we don't succeed that often in generating an
1125  // efficient cut, so I am not sure aborting will make a big difference
1126  // speedwise. We might generate similar cuts though, but hopefully the cut
1127  // management can deal with that.
1128  integer_multipliers = {entry};
1129  if (AddCutFromConstraints("MIR_1", integer_multipliers)) {
1130  continue;
1131  }
1132 
1133  // Cleanup.
1134  for (const ColIndex col : non_zeros_.PositionsSetAtLeastOnce()) {
1135  dense_cut[col] = IntegerValue(0);
1136  }
1137  non_zeros_.SparseClearAll();
1138 
1139  // Copy cut.
1140  const IntegerValue multiplier = entry.second;
1141  for (const std::pair<ColIndex, IntegerValue> term :
1142  integer_lp_[entry.first].terms) {
1143  const ColIndex col = term.first;
1144  const IntegerValue coeff = term.second;
1145  non_zeros_.Set(col);
1146  dense_cut[col] += coeff * multiplier;
1147  }
1148 
1149  used_rows.assign(num_rows.value(), false);
1150  used_rows[entry.first] = true;
1151 
1152  // We will aggregate at most kMaxAggregation more rows.
1153  //
1154  // TODO(user): optim + tune.
1155  const int kMaxAggregation = 5;
1156  for (int i = 0; i < kMaxAggregation; ++i) {
1157  // First pick a variable to eliminate. We currently pick a random one with
1158  // a weight that depend on how far it is from its closest bound.
1159  IntegerValue max_magnitude(0);
1160  weights.clear();
1161  std::vector<ColIndex> col_candidates;
1162  for (const ColIndex col : non_zeros_.PositionsSetAtLeastOnce()) {
1163  if (dense_cut[col] == 0) continue;
1164 
1165  max_magnitude = std::max(max_magnitude, IntTypeAbs(dense_cut[col]));
1166  const int col_degree =
1167  lp_data_.GetSparseColumn(col).num_entries().value();
1168  if (col_degree <= 1) continue;
1170  continue;
1171  }
1172 
1173  const IntegerVariable var = integer_variables_[col.value()];
1174  const double lp_value = expanded_lp_solution_[var];
1175  const double lb = ToDouble(integer_trail_->LevelZeroLowerBound(var));
1176  const double ub = ToDouble(integer_trail_->LevelZeroUpperBound(var));
1177  const double bound_distance = std::min(ub - lp_value, lp_value - lb);
1178  if (bound_distance > 1e-2) {
1179  weights.push_back(bound_distance);
1180  col_candidates.push_back(col);
1181  }
1182  }
1183  if (col_candidates.empty()) break;
1184 
1185  const ColIndex var_to_eliminate =
1186  col_candidates[std::discrete_distribution<>(weights.begin(),
1187  weights.end())(*random_)];
1188 
1189  // What rows can we add to eliminate var_to_eliminate?
1190  std::vector<RowIndex> possible_rows;
1191  weights.clear();
1192  for (const auto entry : lp_data_.GetSparseColumn(var_to_eliminate)) {
1193  const RowIndex row = entry.row();
1194  const auto status = simplex_.GetConstraintStatus(row);
1195  if (status == glop::ConstraintStatus::BASIC) continue;
1196  if (status == glop::ConstraintStatus::FREE) continue;
1197 
1198  // We disallow all the rows that contain a variable that we already
1199  // eliminated (or are about to). This mean that we choose rows that
1200  // form a "triangular" matrix on the position we choose to eliminate.
1201  if (used_rows[row]) continue;
1202  used_rows[row] = true;
1203 
1204  // TODO(user): Instead of using FIXED_VALUE consider also both direction
1205  // when we almost have an equality? that is if the LP constraints bounds
1206  // are close from each others (<1e-6 ?). Initial experiments shows it
1207  // doesn't change much, so I kept this version for now. Note that it
1208  // might just be better to use the side that constrain the current lp
1209  // optimal solution (that we get from the status).
1210  bool add_row = false;
1211  if (status == glop::ConstraintStatus::FIXED_VALUE ||
1213  if (entry.coefficient() > 0.0) {
1214  if (dense_cut[var_to_eliminate] < 0) add_row = true;
1215  } else {
1216  if (dense_cut[var_to_eliminate] > 0) add_row = true;
1217  }
1218  }
1219  if (status == glop::ConstraintStatus::FIXED_VALUE ||
1221  if (entry.coefficient() > 0.0) {
1222  if (dense_cut[var_to_eliminate] > 0) add_row = true;
1223  } else {
1224  if (dense_cut[var_to_eliminate] < 0) add_row = true;
1225  }
1226  }
1227  if (add_row) {
1228  possible_rows.push_back(row);
1229  weights.push_back(row_weights[row]);
1230  }
1231  }
1232  if (possible_rows.empty()) break;
1233 
1234  const RowIndex row_to_combine =
1235  possible_rows[std::discrete_distribution<>(weights.begin(),
1236  weights.end())(*random_)];
1237  const IntegerValue to_combine_coeff =
1238  GetCoeff(var_to_eliminate, integer_lp_[row_to_combine].terms);
1239  CHECK_NE(to_combine_coeff, 0);
1240 
1241  IntegerValue mult1 = -to_combine_coeff;
1242  IntegerValue mult2 = dense_cut[var_to_eliminate];
1243  CHECK_NE(mult2, 0);
1244  if (mult1 < 0) {
1245  mult1 = -mult1;
1246  mult2 = -mult2;
1247  }
1248 
1249  const IntegerValue gcd = IntegerValue(
1250  MathUtil::GCD64(std::abs(mult1.value()), std::abs(mult2.value())));
1251  CHECK_NE(gcd, 0);
1252  mult1 /= gcd;
1253  mult2 /= gcd;
1254 
1255  // Overflow detection.
1256  //
1257  // TODO(user): do that in the possible_rows selection? only problem is
1258  // that we do not have the integer coefficient there...
1259  for (std::pair<RowIndex, IntegerValue>& entry : integer_multipliers) {
1260  max_magnitude = std::max(max_magnitude, IntTypeAbs(entry.second));
1261  }
1262  if (CapAdd(CapProd(max_magnitude.value(), std::abs(mult1.value())),
1263  CapProd(infinity_norms_[row_to_combine].value(),
1264  std::abs(mult2.value()))) ==
1266  break;
1267  }
1268 
1269  for (std::pair<RowIndex, IntegerValue>& entry : integer_multipliers) {
1270  entry.second *= mult1;
1271  }
1272  integer_multipliers.push_back({row_to_combine, mult2});
1273 
1274  // TODO(user): Not supper efficient to recombine the rows.
1275  if (AddCutFromConstraints(absl::StrCat("MIR_", i + 2),
1276  integer_multipliers)) {
1277  break;
1278  }
1279 
1280  // Minor optim: the computation below is only needed if we do one more
1281  // iteration.
1282  if (i + 1 == kMaxAggregation) break;
1283 
1284  for (ColIndex col : non_zeros_.PositionsSetAtLeastOnce()) {
1285  dense_cut[col] *= mult1;
1286  }
1287  for (const std::pair<ColIndex, IntegerValue> term :
1288  integer_lp_[row_to_combine].terms) {
1289  const ColIndex col = term.first;
1290  const IntegerValue coeff = term.second;
1291  non_zeros_.Set(col);
1292  dense_cut[col] += coeff * mult2;
1293  }
1294  }
1295  }
1296 }
1297 
1298 void LinearProgrammingConstraint::AddZeroHalfCuts() {
1299  if (time_limit_->LimitReached()) return;
1300 
1301  tmp_lp_values_.clear();
1302  tmp_var_lbs_.clear();
1303  tmp_var_ubs_.clear();
1304  for (const IntegerVariable var : integer_variables_) {
1305  tmp_lp_values_.push_back(expanded_lp_solution_[var]);
1306  tmp_var_lbs_.push_back(integer_trail_->LevelZeroLowerBound(var));
1307  tmp_var_ubs_.push_back(integer_trail_->LevelZeroUpperBound(var));
1308  }
1309 
1310  // TODO(user): See if it make sense to try to use implied bounds there.
1311  zero_half_cut_helper_.ProcessVariables(tmp_lp_values_, tmp_var_lbs_,
1312  tmp_var_ubs_);
1313  for (glop::RowIndex row(0); row < integer_lp_.size(); ++row) {
1314  // Even though we could use non-tight row, for now we prefer to use tight
1315  // ones.
1316  const auto status = simplex_.GetConstraintStatus(row);
1317  if (status == glop::ConstraintStatus::BASIC) continue;
1318  if (status == glop::ConstraintStatus::FREE) continue;
1319 
1320  zero_half_cut_helper_.AddOneConstraint(
1321  row, integer_lp_[row].terms, integer_lp_[row].lb, integer_lp_[row].ub);
1322  }
1323  for (const std::vector<std::pair<RowIndex, IntegerValue>>& multipliers :
1324  zero_half_cut_helper_.InterestingCandidates(random_)) {
1325  if (time_limit_->LimitReached()) break;
1326 
1327  // TODO(user): Make sure that if the resulting linear coefficients are not
1328  // too high, we do try a "divisor" of two and thus try a true zero-half cut
1329  // instead of just using our best MIR heuristic (which might still be better
1330  // though).
1331  AddCutFromConstraints("ZERO_HALF", multipliers);
1332  }
1333 }
1334 
1335 void LinearProgrammingConstraint::UpdateSimplexIterationLimit(
1336  const int64_t min_iter, const int64_t max_iter) {
1337  if (sat_parameters_.linearization_level() < 2) return;
1338  const int64_t num_degenerate_columns = CalculateDegeneracy();
1339  const int64_t num_cols = simplex_.GetProblemNumCols().value();
1340  if (num_cols <= 0) {
1341  return;
1342  }
1343  CHECK_GT(num_cols, 0);
1344  const int64_t decrease_factor = (10 * num_degenerate_columns) / num_cols;
1346  // We reached here probably because we predicted wrong. We use this as a
1347  // signal to increase the iterations or punish less for degeneracy compare
1348  // to the other part.
1349  if (is_degenerate_) {
1350  next_simplex_iter_ /= std::max(int64_t{1}, decrease_factor);
1351  } else {
1352  next_simplex_iter_ *= 2;
1353  }
1354  } else if (simplex_.GetProblemStatus() == glop::ProblemStatus::OPTIMAL) {
1355  if (is_degenerate_) {
1356  next_simplex_iter_ /= std::max(int64_t{1}, 2 * decrease_factor);
1357  } else {
1358  // This is the most common case. We use the size of the problem to
1359  // determine the limit and ignore the previous limit.
1360  next_simplex_iter_ = num_cols / 40;
1361  }
1362  }
1363  next_simplex_iter_ =
1364  std::max(min_iter, std::min(max_iter, next_simplex_iter_));
1365 }
1366 
1368  UpdateBoundsOfLpVariables();
1369 
1370  // TODO(user): It seems the time we loose by not stopping early might be worth
1371  // it because we end up with a better explanation at optimality.
1373  if (/* DISABLES CODE */ (false) && objective_is_defined_) {
1374  // We put a limit on the dual objective since there is no point increasing
1375  // it past our current objective upper-bound (we will already fail as soon
1376  // as we pass it). Note that this limit is properly transformed using the
1377  // objective scaling factor and offset stored in lp_data_.
1378  //
1379  // Note that we use a bigger epsilon here to be sure that if we abort
1380  // because of this, we will report a conflict.
1381  parameters.set_objective_upper_limit(
1382  static_cast<double>(integer_trail_->UpperBound(objective_cp_).value() +
1383  100.0 * kCpEpsilon));
1384  }
1385 
1386  // Put an iteration limit on the work we do in the simplex for this call. Note
1387  // that because we are "incremental", even if we don't solve it this time we
1388  // will make progress towards a solve in the lower node of the tree search.
1389  if (trail_->CurrentDecisionLevel() == 0) {
1390  // TODO(user): Dynamically change the iteration limit for root node as
1391  // well.
1392  parameters.set_max_number_of_iterations(2000);
1393  } else {
1394  parameters.set_max_number_of_iterations(next_simplex_iter_);
1395  }
1396  if (sat_parameters_.use_exact_lp_reason()) {
1397  parameters.set_change_status_to_imprecise(false);
1398  parameters.set_primal_feasibility_tolerance(1e-7);
1399  parameters.set_dual_feasibility_tolerance(1e-7);
1400  }
1401 
1402  simplex_.SetParameters(parameters);
1404  if (!SolveLp()) return true;
1405 
1406  // Add new constraints to the LP and resolve?
1407  const int max_cuts_rounds =
1408  trail_->CurrentDecisionLevel() == 0
1409  ? sat_parameters_.max_cut_rounds_at_level_zero()
1410  : 1;
1411  int cuts_round = 0;
1412  while (simplex_.GetProblemStatus() == glop::ProblemStatus::OPTIMAL &&
1413  cuts_round < max_cuts_rounds) {
1414  // We wait for the first batch of problem constraints to be added before we
1415  // begin to generate cuts. Note that we rely on num_solves_ since on some
1416  // problems there is no other constriants than the cuts.
1417  cuts_round++;
1418  if (num_solves_ > 1) {
1419  // This must be called first.
1420  implied_bounds_processor_.RecomputeCacheAndSeparateSomeImpliedBoundCuts(
1421  expanded_lp_solution_);
1422 
1423  // The "generic" cuts are currently part of this class as they are using
1424  // data from the current LP.
1425  //
1426  // TODO(user): Refactor so that they are just normal cut generators?
1427  if (trail_->CurrentDecisionLevel() == 0) {
1428  if (sat_parameters_.add_mir_cuts()) AddMirCuts();
1429  if (sat_parameters_.add_cg_cuts()) AddCGCuts();
1430  if (sat_parameters_.add_zero_half_cuts()) AddZeroHalfCuts();
1431  }
1432 
1433  // Try to add cuts.
1434  if (!cut_generators_.empty() &&
1435  (trail_->CurrentDecisionLevel() == 0 ||
1436  !sat_parameters_.only_add_cuts_at_level_zero())) {
1437  for (const CutGenerator& generator : cut_generators_) {
1438  if (!generator.generate_cuts(expanded_lp_solution_,
1439  &constraint_manager_)) {
1440  return false;
1441  }
1442  }
1443  }
1444 
1445  implied_bounds_processor_.IbCutPool().TransferToManager(
1446  expanded_lp_solution_, &constraint_manager_);
1447  }
1448 
1449  glop::BasisState state = simplex_.GetState();
1450  if (constraint_manager_.ChangeLp(expanded_lp_solution_, &state)) {
1451  simplex_.LoadStateForNextSolve(state);
1452  if (!CreateLpFromConstraintManager()) {
1453  return integer_trail_->ReportConflict({});
1454  }
1455  const double old_obj = simplex_.GetObjectiveValue();
1456  if (!SolveLp()) return true;
1457  if (simplex_.GetProblemStatus() == glop::ProblemStatus::OPTIMAL) {
1458  VLOG(1) << "Relaxation improvement " << old_obj << " -> "
1459  << simplex_.GetObjectiveValue()
1460  << " diff: " << simplex_.GetObjectiveValue() - old_obj
1461  << " level: " << trail_->CurrentDecisionLevel();
1462  }
1463  } else {
1464  if (trail_->CurrentDecisionLevel() == 0) {
1465  lp_at_level_zero_is_final_ = true;
1466  }
1467  break;
1468  }
1469  }
1470 
1471  // A dual-unbounded problem is infeasible. We use the dual ray reason.
1473  if (sat_parameters_.use_exact_lp_reason()) {
1474  if (!FillExactDualRayReason()) return true;
1475  } else {
1476  FillReducedCostReasonIn(simplex_.GetDualRayRowCombination(),
1477  &integer_reason_);
1478  }
1479  return integer_trail_->ReportConflict(integer_reason_);
1480  }
1481 
1482  // TODO(user): Update limits for DUAL_UNBOUNDED status as well.
1483  UpdateSimplexIterationLimit(/*min_iter=*/10, /*max_iter=*/1000);
1484 
1485  // Optimality deductions if problem has an objective.
1486  if (objective_is_defined_ &&
1489  // TODO(user): Maybe do a bit less computation when we cannot propagate
1490  // anything.
1491  if (sat_parameters_.use_exact_lp_reason()) {
1492  if (!ExactLpReasonning()) return false;
1493 
1494  // Display when the inexact bound would have propagated more.
1495  if (VLOG_IS_ON(2)) {
1496  const double relaxed_optimal_objective = simplex_.GetObjectiveValue();
1497  const IntegerValue approximate_new_lb(static_cast<int64_t>(
1498  std::ceil(relaxed_optimal_objective - kCpEpsilon)));
1499  const IntegerValue propagated_lb =
1500  integer_trail_->LowerBound(objective_cp_);
1501  if (approximate_new_lb > propagated_lb) {
1502  VLOG(2) << "LP objective [ " << ToDouble(propagated_lb) << ", "
1503  << ToDouble(integer_trail_->UpperBound(objective_cp_))
1504  << " ] approx_lb += "
1505  << ToDouble(approximate_new_lb - propagated_lb) << " gap: "
1506  << integer_trail_->UpperBound(objective_cp_) - propagated_lb;
1507  }
1508  }
1509  } else {
1510  // Try to filter optimal objective value. Note that GetObjectiveValue()
1511  // already take care of the scaling so that it returns an objective in the
1512  // CP world.
1513  FillReducedCostReasonIn(simplex_.GetReducedCosts(), &integer_reason_);
1514  const double objective_cp_ub =
1515  ToDouble(integer_trail_->UpperBound(objective_cp_));
1516  const double relaxed_optimal_objective = simplex_.GetObjectiveValue();
1517  ReducedCostStrengtheningDeductions(objective_cp_ub -
1518  relaxed_optimal_objective);
1519  if (!deductions_.empty()) {
1520  deductions_reason_ = integer_reason_;
1521  deductions_reason_.push_back(
1522  integer_trail_->UpperBoundAsLiteral(objective_cp_));
1523  }
1524 
1525  // Push new objective lb.
1526  const IntegerValue approximate_new_lb(static_cast<int64_t>(
1527  std::ceil(relaxed_optimal_objective - kCpEpsilon)));
1528  if (approximate_new_lb > integer_trail_->LowerBound(objective_cp_)) {
1529  const IntegerLiteral deduction =
1530  IntegerLiteral::GreaterOrEqual(objective_cp_, approximate_new_lb);
1531  if (!integer_trail_->Enqueue(deduction, {}, integer_reason_)) {
1532  return false;
1533  }
1534  }
1535 
1536  // Push reduced cost strengthening bounds.
1537  if (!deductions_.empty()) {
1538  const int trail_index_with_same_reason = integer_trail_->Index();
1539  for (const IntegerLiteral deduction : deductions_) {
1540  if (!integer_trail_->Enqueue(deduction, {}, deductions_reason_,
1541  trail_index_with_same_reason)) {
1542  return false;
1543  }
1544  }
1545  }
1546  }
1547  }
1548 
1549  // Copy more info about the current solution.
1550  if (simplex_.GetProblemStatus() == glop::ProblemStatus::OPTIMAL) {
1551  CHECK(lp_solution_is_set_);
1552 
1553  lp_objective_ = simplex_.GetObjectiveValue();
1554  lp_solution_is_integer_ = true;
1555  const int num_vars = integer_variables_.size();
1556  for (int i = 0; i < num_vars; i++) {
1557  lp_reduced_cost_[i] = scaler_.UnscaleReducedCost(
1558  glop::ColIndex(i), simplex_.GetReducedCost(glop::ColIndex(i)));
1559  if (std::abs(lp_solution_[i] - std::round(lp_solution_[i])) >
1560  kCpEpsilon) {
1561  lp_solution_is_integer_ = false;
1562  }
1563  }
1564 
1565  if (compute_reduced_cost_averages_) {
1566  UpdateAverageReducedCosts();
1567  }
1568  }
1569 
1570  if (sat_parameters_.use_branching_in_lp() && objective_is_defined_ &&
1571  trail_->CurrentDecisionLevel() == 0 && !is_degenerate_ &&
1572  lp_solution_is_set_ && !lp_solution_is_integer_ &&
1573  sat_parameters_.linearization_level() >= 2 &&
1574  compute_reduced_cost_averages_ &&
1576  count_since_last_branching_++;
1577  if (count_since_last_branching_ < branching_frequency_) {
1578  return true;
1579  }
1580  count_since_last_branching_ = 0;
1581  bool branching_successful = false;
1582 
1583  // Strong branching on top max_num_branches variable.
1584  const int max_num_branches = 3;
1585  const int num_vars = integer_variables_.size();
1586  std::vector<std::pair<double, IntegerVariable>> branching_vars;
1587  for (int i = 0; i < num_vars; ++i) {
1588  const IntegerVariable var = integer_variables_[i];
1589  const IntegerVariable positive_var = PositiveVariable(var);
1590 
1591  // Skip non fractional variables.
1592  const double current_value = GetSolutionValue(positive_var);
1593  if (std::abs(current_value - std::round(current_value)) <= kCpEpsilon) {
1594  continue;
1595  }
1596 
1597  // Skip ignored variables.
1598  if (integer_trail_->IsCurrentlyIgnored(var)) continue;
1599 
1600  // We can use any metric to select a variable to branch on. Reduced cost
1601  // average is one of the most promissing metric. It captures the history
1602  // of the objective bound improvement in LP due to changes in the given
1603  // variable bounds.
1604  //
1605  // NOTE: We also experimented using PseudoCosts and most recent reduced
1606  // cost as metrics but it doesn't give better results on benchmarks.
1607  const double cost_i = rc_scores_[i];
1608  std::pair<double, IntegerVariable> branching_var =
1609  std::make_pair(-cost_i, positive_var);
1610  auto iterator = std::lower_bound(branching_vars.begin(),
1611  branching_vars.end(), branching_var);
1612 
1613  branching_vars.insert(iterator, branching_var);
1614  if (branching_vars.size() > max_num_branches) {
1615  branching_vars.resize(max_num_branches);
1616  }
1617  }
1618 
1619  for (const std::pair<double, IntegerVariable>& branching_var :
1620  branching_vars) {
1621  const IntegerVariable positive_var = branching_var.second;
1622  VLOG(2) << "Branching on: " << positive_var;
1623  if (BranchOnVar(positive_var)) {
1624  VLOG(2) << "Branching successful.";
1625  branching_successful = true;
1626  } else {
1627  break;
1628  }
1629  }
1630  if (!branching_successful) {
1631  branching_frequency_ *= 2;
1632  }
1633  }
1634  return true;
1635 }
1636 
1637 // Returns kMinIntegerValue in case of overflow.
1638 //
1639 // TODO(user): Because of PreventOverflow(), this should actually never happen.
1640 IntegerValue LinearProgrammingConstraint::GetImpliedLowerBound(
1641  const LinearConstraint& terms) const {
1642  IntegerValue lower_bound(0);
1643  const int size = terms.vars.size();
1644  for (int i = 0; i < size; ++i) {
1645  const IntegerVariable var = terms.vars[i];
1646  const IntegerValue coeff = terms.coeffs[i];
1647  CHECK_NE(coeff, 0);
1648  const IntegerValue bound = coeff > 0 ? integer_trail_->LowerBound(var)
1649  : integer_trail_->UpperBound(var);
1650  if (!AddProductTo(bound, coeff, &lower_bound)) return kMinIntegerValue;
1651  }
1652  return lower_bound;
1653 }
1654 
1655 bool LinearProgrammingConstraint::PossibleOverflow(
1656  const LinearConstraint& constraint) {
1657  IntegerValue lower_bound(0);
1658  const int size = constraint.vars.size();
1659  for (int i = 0; i < size; ++i) {
1660  const IntegerVariable var = constraint.vars[i];
1661  const IntegerValue coeff = constraint.coeffs[i];
1662  CHECK_NE(coeff, 0);
1663  const IntegerValue bound = coeff > 0
1664  ? integer_trail_->LevelZeroLowerBound(var)
1665  : integer_trail_->LevelZeroUpperBound(var);
1666  if (!AddProductTo(bound, coeff, &lower_bound)) {
1667  return true;
1668  }
1669  }
1670  const int64_t slack = CapAdd(lower_bound.value(), -constraint.ub.value());
1671  if (slack == std::numeric_limits<int64_t>::min() ||
1672  slack == std::numeric_limits<int64_t>::max()) {
1673  return true;
1674  }
1675  return false;
1676 }
1677 
1678 namespace {
1679 
1680 absl::int128 FloorRatio128(absl::int128 x, IntegerValue positive_div) {
1681  absl::int128 div128(positive_div.value());
1682  absl::int128 result = x / div128;
1683  if (result * div128 > x) return result - 1;
1684  return result;
1685 }
1686 
1687 } // namespace
1688 
1689 void LinearProgrammingConstraint::PreventOverflow(LinearConstraint* constraint,
1690  int max_pow) {
1691  // First, make all coefficient positive.
1692  MakeAllCoefficientsPositive(constraint);
1693 
1694  // Compute the min/max possible partial sum. Note that we need to use the
1695  // level zero bounds here since we might use this cut after backtrack.
1696  double sum_min = std::min(0.0, ToDouble(-constraint->ub));
1697  double sum_max = std::max(0.0, ToDouble(-constraint->ub));
1698  const int size = constraint->vars.size();
1699  for (int i = 0; i < size; ++i) {
1700  const IntegerVariable var = constraint->vars[i];
1701  const double coeff = ToDouble(constraint->coeffs[i]);
1702  sum_min +=
1703  coeff *
1704  std::min(0.0, ToDouble(integer_trail_->LevelZeroLowerBound(var)));
1705  sum_max +=
1706  coeff *
1707  std::max(0.0, ToDouble(integer_trail_->LevelZeroUpperBound(var)));
1708  }
1709  const double max_value = std::max({sum_max, -sum_min, sum_max - sum_min});
1710 
1711  const IntegerValue divisor(std::ceil(std::ldexp(max_value, -max_pow)));
1712  if (divisor <= 1) return;
1713 
1714  // To be correct, we need to shift all variable so that they are positive.
1715  //
1716  // Important: One might be tempted to think that using the current variable
1717  // bounds is okay here since we only use this to derive cut/constraint that
1718  // only needs to be locally valid. However, in some corner cases (like when
1719  // one term become zero), we might loose the fact that we used one of the
1720  // variable bound to derive the new constraint, so we will miss it in the
1721  // explanation !!
1722  //
1723  // TODO(user): This code is tricky and similar to the one to generate cuts.
1724  // Test and may reduce the duplication? note however that here we use int128
1725  // to deal with potential overflow.
1726  int new_size = 0;
1727  absl::int128 adjust = 0;
1728  for (int i = 0; i < size; ++i) {
1729  const IntegerValue old_coeff = constraint->coeffs[i];
1730  const IntegerValue new_coeff = FloorRatio(old_coeff, divisor);
1731 
1732  // Compute the rhs adjustement.
1733  const absl::int128 remainder =
1734  absl::int128(old_coeff.value()) -
1735  absl::int128(new_coeff.value()) * absl::int128(divisor.value());
1736  adjust +=
1737  remainder *
1738  absl::int128(
1739  integer_trail_->LevelZeroLowerBound(constraint->vars[i]).value());
1740 
1741  if (new_coeff == 0) continue;
1742  constraint->vars[new_size] = constraint->vars[i];
1743  constraint->coeffs[new_size] = new_coeff;
1744  ++new_size;
1745  }
1746  constraint->vars.resize(new_size);
1747  constraint->coeffs.resize(new_size);
1748 
1749  constraint->ub = IntegerValue(static_cast<int64_t>(
1750  FloorRatio128(absl::int128(constraint->ub.value()) - adjust, divisor)));
1751 }
1752 
1753 // TODO(user): combine this with RelaxLinearReason() to avoid the extra
1754 // magnitude vector and the weird precondition of RelaxLinearReason().
1755 void LinearProgrammingConstraint::SetImpliedLowerBoundReason(
1756  const LinearConstraint& terms, IntegerValue slack) {
1757  integer_reason_.clear();
1758  std::vector<IntegerValue> magnitudes;
1759  const int size = terms.vars.size();
1760  for (int i = 0; i < size; ++i) {
1761  const IntegerVariable var = terms.vars[i];
1762  const IntegerValue coeff = terms.coeffs[i];
1763  CHECK_NE(coeff, 0);
1764  if (coeff > 0) {
1765  magnitudes.push_back(coeff);
1766  integer_reason_.push_back(integer_trail_->LowerBoundAsLiteral(var));
1767  } else {
1768  magnitudes.push_back(-coeff);
1769  integer_reason_.push_back(integer_trail_->UpperBoundAsLiteral(var));
1770  }
1771  }
1772  CHECK_GE(slack, 0);
1773  if (slack > 0) {
1774  integer_trail_->RelaxLinearReason(slack, magnitudes, &integer_reason_);
1775  }
1776  integer_trail_->RemoveLevelZeroBounds(&integer_reason_);
1777 }
1778 
1779 std::vector<std::pair<RowIndex, IntegerValue>>
1780 LinearProgrammingConstraint::ScaleLpMultiplier(
1781  bool take_objective_into_account,
1782  const std::vector<std::pair<RowIndex, double>>& lp_multipliers,
1783  Fractional* scaling, int max_pow) const {
1784  double max_sum = 0.0;
1785  tmp_cp_multipliers_.clear();
1786  for (const std::pair<RowIndex, double>& p : lp_multipliers) {
1787  const RowIndex row = p.first;
1788  const Fractional lp_multi = p.second;
1789 
1790  // We ignore small values since these are likely errors and will not
1791  // contribute much to the new lp constraint anyway.
1792  if (std::abs(lp_multi) < kZeroTolerance) continue;
1793 
1794  // Remove trivial bad cases.
1795  //
1796  // TODO(user): It might be better (when possible) to use the OPTIMAL row
1797  // status since in most situation we do want the constraint we add to be
1798  // tight under the current LP solution. Only for infeasible problem we might
1799  // not have access to the status.
1800  if (lp_multi > 0.0 && integer_lp_[row].ub >= kMaxIntegerValue) {
1801  continue;
1802  }
1803  if (lp_multi < 0.0 && integer_lp_[row].lb <= kMinIntegerValue) {
1804  continue;
1805  }
1806 
1807  const Fractional cp_multi = scaler_.UnscaleDualValue(row, lp_multi);
1808  tmp_cp_multipliers_.push_back({row, cp_multi});
1809  max_sum += ToDouble(infinity_norms_[row]) * std::abs(cp_multi);
1810  }
1811 
1812  // This behave exactly like if we had another "objective" constraint with
1813  // an lp_multi of 1.0 and a cp_multi of 1.0.
1814  if (take_objective_into_account) {
1815  max_sum += ToDouble(objective_infinity_norm_);
1816  }
1817 
1818  *scaling = 1.0;
1819  std::vector<std::pair<RowIndex, IntegerValue>> integer_multipliers;
1820  if (max_sum == 0.0) {
1821  // Empty linear combinaison.
1822  return integer_multipliers;
1823  }
1824 
1825  // We want max_sum * scaling to be <= 2 ^ max_pow and fit on an int64_t.
1826  // We use a power of 2 as this seems to work better.
1827  const double threshold = std::ldexp(1, max_pow) / max_sum;
1828  if (threshold < 1.0) {
1829  // TODO(user): we currently do not support scaling down, so we just abort
1830  // in this case.
1831  return integer_multipliers;
1832  }
1833  while (2 * *scaling <= threshold) *scaling *= 2;
1834 
1835  // Scale the multipliers by *scaling.
1836  //
1837  // TODO(user): Maybe use int128 to avoid overflow?
1838  for (const auto entry : tmp_cp_multipliers_) {
1839  const IntegerValue coeff(std::round(entry.second * (*scaling)));
1840  if (coeff != 0) integer_multipliers.push_back({entry.first, coeff});
1841  }
1842  return integer_multipliers;
1843 }
1844 
1845 bool LinearProgrammingConstraint::ComputeNewLinearConstraint(
1846  const std::vector<std::pair<RowIndex, IntegerValue>>& integer_multipliers,
1847  ScatteredIntegerVector* scattered_vector, IntegerValue* upper_bound) const {
1848  // Initialize the new constraint.
1849  *upper_bound = 0;
1850  scattered_vector->ClearAndResize(integer_variables_.size());
1851 
1852  // Compute the new constraint by taking the linear combination given by
1853  // integer_multipliers of the integer constraints in integer_lp_.
1854  for (const std::pair<RowIndex, IntegerValue> term : integer_multipliers) {
1855  const RowIndex row = term.first;
1856  const IntegerValue multiplier = term.second;
1857  CHECK_LT(row, integer_lp_.size());
1858 
1859  // Update the constraint.
1860  if (!scattered_vector->AddLinearExpressionMultiple(
1861  multiplier, integer_lp_[row].terms)) {
1862  return false;
1863  }
1864 
1865  // Update the upper bound.
1866  const IntegerValue bound =
1867  multiplier > 0 ? integer_lp_[row].ub : integer_lp_[row].lb;
1868  if (!AddProductTo(multiplier, bound, upper_bound)) return false;
1869  }
1870 
1871  return true;
1872 }
1873 
1874 // TODO(user): no need to update the multipliers.
1875 void LinearProgrammingConstraint::AdjustNewLinearConstraint(
1876  std::vector<std::pair<glop::RowIndex, IntegerValue>>* integer_multipliers,
1877  ScatteredIntegerVector* scattered_vector, IntegerValue* upper_bound) const {
1878  const IntegerValue kMaxWantedCoeff(1e18);
1879  for (std::pair<RowIndex, IntegerValue>& term : *integer_multipliers) {
1880  const RowIndex row = term.first;
1881  const IntegerValue multiplier = term.second;
1882  if (multiplier == 0) continue;
1883 
1884  // We will only allow change of the form "multiplier += to_add" with to_add
1885  // in [-negative_limit, positive_limit].
1886  IntegerValue negative_limit = kMaxWantedCoeff;
1887  IntegerValue positive_limit = kMaxWantedCoeff;
1888 
1889  // Make sure we never change the sign of the multiplier, except if the
1890  // row is an equality in which case we don't care.
1891  if (integer_lp_[row].ub != integer_lp_[row].lb) {
1892  if (multiplier > 0) {
1893  negative_limit = std::min(negative_limit, multiplier);
1894  } else {
1895  positive_limit = std::min(positive_limit, -multiplier);
1896  }
1897  }
1898 
1899  // Make sure upper_bound + to_add * row_bound never overflow.
1900  const IntegerValue row_bound =
1901  multiplier > 0 ? integer_lp_[row].ub : integer_lp_[row].lb;
1902  if (row_bound != 0) {
1903  const IntegerValue limit1 = FloorRatio(
1904  std::max(IntegerValue(0), kMaxWantedCoeff - IntTypeAbs(*upper_bound)),
1905  IntTypeAbs(row_bound));
1906  const IntegerValue limit2 =
1907  FloorRatio(kMaxWantedCoeff, IntTypeAbs(row_bound));
1908  if ((*upper_bound > 0) == (row_bound > 0)) { // Same sign.
1909  positive_limit = std::min(positive_limit, limit1);
1910  negative_limit = std::min(negative_limit, limit2);
1911  } else {
1912  negative_limit = std::min(negative_limit, limit1);
1913  positive_limit = std::min(positive_limit, limit2);
1914  }
1915  }
1916 
1917  // If we add the row to the scattered_vector, diff will indicate by how much
1918  // |upper_bound - ImpliedLB(scattered_vector)| will change. That correspond
1919  // to increasing the multiplier by 1.
1920  //
1921  // At this stage, we are not sure computing sum coeff * bound will not
1922  // overflow, so we use floating point numbers. It is fine to do so since
1923  // this is not directly involved in the actual exact constraint generation:
1924  // these variables are just used in an heuristic.
1925  double positive_diff = ToDouble(row_bound);
1926  double negative_diff = ToDouble(row_bound);
1927 
1928  // TODO(user): we could relax a bit some of the condition and allow a sign
1929  // change. It is just trickier to compute the diff when we allow such
1930  // changes.
1931  for (const auto entry : integer_lp_[row].terms) {
1932  const ColIndex col = entry.first;
1933  const IntegerValue coeff = entry.second;
1934  const IntegerValue abs_coef = IntTypeAbs(coeff);
1935  CHECK_NE(coeff, 0);
1936 
1937  const IntegerVariable var = integer_variables_[col.value()];
1938  const IntegerValue lb = integer_trail_->LowerBound(var);
1939  const IntegerValue ub = integer_trail_->UpperBound(var);
1940 
1941  // Moving a variable away from zero seems to improve the bound even
1942  // if it reduces the number of non-zero. Note that this is because of
1943  // this that positive_diff and negative_diff are not the same.
1944  const IntegerValue current = (*scattered_vector)[col];
1945  if (current == 0) {
1946  const IntegerValue overflow_limit(
1947  FloorRatio(kMaxWantedCoeff, abs_coef));
1948  positive_limit = std::min(positive_limit, overflow_limit);
1949  negative_limit = std::min(negative_limit, overflow_limit);
1950  if (coeff > 0) {
1951  positive_diff -= ToDouble(coeff) * ToDouble(lb);
1952  negative_diff -= ToDouble(coeff) * ToDouble(ub);
1953  } else {
1954  positive_diff -= ToDouble(coeff) * ToDouble(ub);
1955  negative_diff -= ToDouble(coeff) * ToDouble(lb);
1956  }
1957  continue;
1958  }
1959 
1960  // We don't want to change the sign of current (except if the variable is
1961  // fixed) or to have an overflow.
1962  //
1963  // Corner case:
1964  // - IntTypeAbs(current) can be larger than kMaxWantedCoeff!
1965  // - The code assumes that 2 * kMaxWantedCoeff do not overflow.
1966  const IntegerValue current_magnitude = IntTypeAbs(current);
1967  const IntegerValue other_direction_limit = FloorRatio(
1968  lb == ub
1969  ? kMaxWantedCoeff + std::min(current_magnitude,
1970  kMaxIntegerValue - kMaxWantedCoeff)
1971  : current_magnitude,
1972  abs_coef);
1973  const IntegerValue same_direction_limit(FloorRatio(
1974  std::max(IntegerValue(0), kMaxWantedCoeff - current_magnitude),
1975  abs_coef));
1976  if ((current > 0) == (coeff > 0)) { // Same sign.
1977  negative_limit = std::min(negative_limit, other_direction_limit);
1978  positive_limit = std::min(positive_limit, same_direction_limit);
1979  } else {
1980  negative_limit = std::min(negative_limit, same_direction_limit);
1981  positive_limit = std::min(positive_limit, other_direction_limit);
1982  }
1983 
1984  // This is how diff change.
1985  const IntegerValue implied = current > 0 ? lb : ub;
1986  if (implied != 0) {
1987  positive_diff -= ToDouble(coeff) * ToDouble(implied);
1988  negative_diff -= ToDouble(coeff) * ToDouble(implied);
1989  }
1990  }
1991 
1992  // Only add a multiple of this row if it tighten the final constraint.
1993  // The positive_diff/negative_diff are supposed to be integer modulo the
1994  // double precision, so we only add a multiple if they seems far away from
1995  // zero.
1996  IntegerValue to_add(0);
1997  if (positive_diff <= -1.0 && positive_limit > 0) {
1998  to_add = positive_limit;
1999  }
2000  if (negative_diff >= 1.0 && negative_limit > 0) {
2001  // Pick this if it is better than the positive sign.
2002  if (to_add == 0 ||
2003  std::abs(ToDouble(negative_limit) * negative_diff) >
2004  std::abs(ToDouble(positive_limit) * positive_diff)) {
2005  to_add = -negative_limit;
2006  }
2007  }
2008  if (to_add != 0) {
2009  term.second += to_add;
2010  *upper_bound += to_add * row_bound;
2011 
2012  // TODO(user): we could avoid checking overflow here, but this is likely
2013  // not in the hot loop.
2014  CHECK(scattered_vector->AddLinearExpressionMultiple(
2015  to_add, integer_lp_[row].terms));
2016  }
2017  }
2018 }
2019 
2020 // The "exact" computation go as follow:
2021 //
2022 // Given any INTEGER linear combination of the LP constraints, we can create a
2023 // new integer constraint that is valid (its computation must not overflow
2024 // though). Lets call this "linear_combination <= ub". We can then always add to
2025 // it the inequality "objective_terms <= objective_var", so we get:
2026 // ImpliedLB(objective_terms + linear_combination) - ub <= objective_var.
2027 // where ImpliedLB() is computed from the variable current bounds.
2028 //
2029 // Now, if we use for the linear combination and approximation of the optimal
2030 // negated dual LP values (by scaling them and rounding them to integer), we
2031 // will get an EXACT objective lower bound that is more or less the same as the
2032 // inexact bound given by the LP relaxation. This allows to derive exact reasons
2033 // for any propagation done by this constraint.
2034 bool LinearProgrammingConstraint::ExactLpReasonning() {
2035  // Clear old reason and deductions.
2036  integer_reason_.clear();
2037  deductions_.clear();
2038  deductions_reason_.clear();
2039 
2040  // The row multipliers will be the negation of the LP duals.
2041  //
2042  // TODO(user): Provide and use a sparse API in Glop to get the duals.
2043  const RowIndex num_rows = simplex_.GetProblemNumRows();
2044  std::vector<std::pair<RowIndex, double>> lp_multipliers;
2045  for (RowIndex row(0); row < num_rows; ++row) {
2046  const double value = -simplex_.GetDualValue(row);
2047  if (std::abs(value) < kZeroTolerance) continue;
2048  lp_multipliers.push_back({row, value});
2049  }
2050 
2051  Fractional scaling;
2052  std::vector<std::pair<RowIndex, IntegerValue>> integer_multipliers =
2053  ScaleLpMultiplier(/*take_objective_into_account=*/true, lp_multipliers,
2054  &scaling);
2055 
2056  IntegerValue rc_ub;
2057  if (!ComputeNewLinearConstraint(integer_multipliers, &tmp_scattered_vector_,
2058  &rc_ub)) {
2059  VLOG(1) << "Issue while computing the exact LP reason. Aborting.";
2060  return true;
2061  }
2062 
2063  // The "objective constraint" behave like if the unscaled cp multiplier was
2064  // 1.0, so we will multiply it by this number and add it to reduced_costs.
2065  const IntegerValue obj_scale(std::round(scaling));
2066  if (obj_scale == 0) {
2067  VLOG(1) << "Overflow during exact LP reasoning. scaling=" << scaling;
2068  return true;
2069  }
2070  CHECK(tmp_scattered_vector_.AddLinearExpressionMultiple(obj_scale,
2071  integer_objective_));
2072  CHECK(AddProductTo(-obj_scale, integer_objective_offset_, &rc_ub));
2073  AdjustNewLinearConstraint(&integer_multipliers, &tmp_scattered_vector_,
2074  &rc_ub);
2075 
2076  // Create the IntegerSumLE that will allow to propagate the objective and more
2077  // generally do the reduced cost fixing.
2078  LinearConstraint new_constraint;
2079  tmp_scattered_vector_.ConvertToLinearConstraint(integer_variables_, rc_ub,
2080  &new_constraint);
2081  new_constraint.vars.push_back(objective_cp_);
2082  new_constraint.coeffs.push_back(-obj_scale);
2083  DivideByGCD(&new_constraint);
2084  PreventOverflow(&new_constraint);
2085  DCHECK(!PossibleOverflow(new_constraint));
2086  DCHECK(constraint_manager_.DebugCheckConstraint(new_constraint));
2087 
2088  // Corner case where prevent overflow removed all terms.
2089  if (new_constraint.vars.empty()) {
2090  trail_->MutableConflict()->clear();
2091  return new_constraint.ub >= 0;
2092  }
2093 
2094  IntegerSumLE* cp_constraint =
2095  new IntegerSumLE({}, new_constraint.vars, new_constraint.coeffs,
2096  new_constraint.ub, model_);
2097  if (trail_->CurrentDecisionLevel() == 0) {
2098  // Since we will never ask the reason for a constraint at level 0, we just
2099  // keep the last one.
2100  optimal_constraints_.clear();
2101  }
2102  optimal_constraints_.emplace_back(cp_constraint);
2103  rev_optimal_constraints_size_ = optimal_constraints_.size();
2104  if (!cp_constraint->PropagateAtLevelZero()) return false;
2105  return cp_constraint->Propagate();
2106 }
2107 
2108 bool LinearProgrammingConstraint::FillExactDualRayReason() {
2109  Fractional scaling;
2110  const glop::DenseColumn ray = simplex_.GetDualRay();
2111  std::vector<std::pair<RowIndex, double>> lp_multipliers;
2112  for (RowIndex row(0); row < ray.size(); ++row) {
2113  const double value = ray[row];
2114  if (std::abs(value) < kZeroTolerance) continue;
2115  lp_multipliers.push_back({row, value});
2116  }
2117  std::vector<std::pair<RowIndex, IntegerValue>> integer_multipliers =
2118  ScaleLpMultiplier(/*take_objective_into_account=*/false, lp_multipliers,
2119  &scaling);
2120 
2121  IntegerValue new_constraint_ub;
2122  if (!ComputeNewLinearConstraint(integer_multipliers, &tmp_scattered_vector_,
2123  &new_constraint_ub)) {
2124  VLOG(1) << "Isse while computing the exact dual ray reason. Aborting.";
2125  return false;
2126  }
2127 
2128  AdjustNewLinearConstraint(&integer_multipliers, &tmp_scattered_vector_,
2129  &new_constraint_ub);
2130 
2131  LinearConstraint new_constraint;
2132  tmp_scattered_vector_.ConvertToLinearConstraint(
2133  integer_variables_, new_constraint_ub, &new_constraint);
2134  DivideByGCD(&new_constraint);
2135  PreventOverflow(&new_constraint);
2136  DCHECK(!PossibleOverflow(new_constraint));
2137  DCHECK(constraint_manager_.DebugCheckConstraint(new_constraint));
2138 
2139  const IntegerValue implied_lb = GetImpliedLowerBound(new_constraint);
2140  if (implied_lb <= new_constraint.ub) {
2141  VLOG(1) << "LP exact dual ray not infeasible,"
2142  << " implied_lb: " << implied_lb.value() / scaling
2143  << " ub: " << new_constraint.ub.value() / scaling;
2144  return false;
2145  }
2146  const IntegerValue slack = (implied_lb - new_constraint.ub) - 1;
2147  SetImpliedLowerBoundReason(new_constraint, slack);
2148  return true;
2149 }
2150 
2151 int64_t LinearProgrammingConstraint::CalculateDegeneracy() {
2152  const glop::ColIndex num_vars = simplex_.GetProblemNumCols();
2153  int num_non_basic_with_zero_rc = 0;
2154  for (glop::ColIndex i(0); i < num_vars; ++i) {
2155  const double rc = simplex_.GetReducedCost(i);
2156  if (rc != 0.0) continue;
2157  if (simplex_.GetVariableStatus(i) == glop::VariableStatus::BASIC) {
2158  continue;
2159  }
2160  num_non_basic_with_zero_rc++;
2161  }
2162  const int64_t num_cols = simplex_.GetProblemNumCols().value();
2163  is_degenerate_ = num_non_basic_with_zero_rc >= 0.3 * num_cols;
2164  return num_non_basic_with_zero_rc;
2165 }
2166 
2167 void LinearProgrammingConstraint::ReducedCostStrengtheningDeductions(
2168  double cp_objective_delta) {
2169  deductions_.clear();
2170 
2171  // TRICKY: while simplex_.GetObjectiveValue() use the objective scaling factor
2172  // stored in the lp_data_, all the other functions like GetReducedCost() or
2173  // GetVariableValue() do not.
2174  const double lp_objective_delta =
2175  cp_objective_delta / lp_data_.objective_scaling_factor();
2176  const int num_vars = integer_variables_.size();
2177  for (int i = 0; i < num_vars; i++) {
2178  const IntegerVariable cp_var = integer_variables_[i];
2179  const glop::ColIndex lp_var = glop::ColIndex(i);
2180  const double rc = simplex_.GetReducedCost(lp_var);
2181  const double value = simplex_.GetVariableValue(lp_var);
2182 
2183  if (rc == 0.0) continue;
2184  const double lp_other_bound = value + lp_objective_delta / rc;
2185  const double cp_other_bound =
2186  scaler_.UnscaleVariableValue(lp_var, lp_other_bound);
2187 
2188  if (rc > kLpEpsilon) {
2189  const double ub = ToDouble(integer_trail_->UpperBound(cp_var));
2190  const double new_ub = std::floor(cp_other_bound + kCpEpsilon);
2191  if (new_ub < ub) {
2192  // TODO(user): Because rc > kLpEpsilon, the lower_bound of cp_var
2193  // will be part of the reason returned by FillReducedCostsReason(), but
2194  // we actually do not need it here. Same below.
2195  const IntegerValue new_ub_int(static_cast<IntegerValue>(new_ub));
2196  deductions_.push_back(IntegerLiteral::LowerOrEqual(cp_var, new_ub_int));
2197  }
2198  } else if (rc < -kLpEpsilon) {
2199  const double lb = ToDouble(integer_trail_->LowerBound(cp_var));
2200  const double new_lb = std::ceil(cp_other_bound - kCpEpsilon);
2201  if (new_lb > lb) {
2202  const IntegerValue new_lb_int(static_cast<IntegerValue>(new_lb));
2203  deductions_.push_back(
2204  IntegerLiteral::GreaterOrEqual(cp_var, new_lb_int));
2205  }
2206  }
2207  }
2208 }
2209 
2210 namespace {
2211 
2212 // Add a cut of the form Sum_{outgoing arcs from S} lp >= rhs_lower_bound.
2213 //
2214 // Note that we used to also add the same cut for the incoming arcs, but because
2215 // of flow conservation on these problems, the outgoing flow is always the same
2216 // as the incoming flow, so adding this extra cut doesn't seem relevant.
2217 void AddOutgoingCut(
2218  int num_nodes, int subset_size, const std::vector<bool>& in_subset,
2219  const std::vector<int>& tails, const std::vector<int>& heads,
2220  const std::vector<Literal>& literals,
2221  const std::vector<double>& literal_lp_values, int64_t rhs_lower_bound,
2223  LinearConstraintManager* manager, Model* model) {
2224  // A node is said to be optional if it can be excluded from the subcircuit,
2225  // in which case there is a self-loop on that node.
2226  // If there are optional nodes, use extended formula:
2227  // sum(cut) >= 1 - optional_loop_in - optional_loop_out
2228  // where optional_loop_in's node is in subset, optional_loop_out's is out.
2229  // TODO(user): Favor optional loops fixed to zero at root.
2230  int num_optional_nodes_in = 0;
2231  int num_optional_nodes_out = 0;
2232  int optional_loop_in = -1;
2233  int optional_loop_out = -1;
2234  for (int i = 0; i < tails.size(); ++i) {
2235  if (tails[i] != heads[i]) continue;
2236  if (in_subset[tails[i]]) {
2237  num_optional_nodes_in++;
2238  if (optional_loop_in == -1 ||
2239  literal_lp_values[i] < literal_lp_values[optional_loop_in]) {
2240  optional_loop_in = i;
2241  }
2242  } else {
2243  num_optional_nodes_out++;
2244  if (optional_loop_out == -1 ||
2245  literal_lp_values[i] < literal_lp_values[optional_loop_out]) {
2246  optional_loop_out = i;
2247  }
2248  }
2249  }
2250 
2251  // TODO(user): The lower bound for CVRP is computed assuming all nodes must be
2252  // served, if it is > 1 we lower it to one in the presence of optional nodes.
2253  if (num_optional_nodes_in + num_optional_nodes_out > 0) {
2254  CHECK_GE(rhs_lower_bound, 1);
2255  rhs_lower_bound = 1;
2256  }
2257 
2258  LinearConstraintBuilder outgoing(model, IntegerValue(rhs_lower_bound),
2260  double sum_outgoing = 0.0;
2261 
2262  // Add outgoing arcs, compute outgoing flow.
2263  for (int i = 0; i < tails.size(); ++i) {
2264  if (in_subset[tails[i]] && !in_subset[heads[i]]) {
2265  sum_outgoing += literal_lp_values[i];
2266  CHECK(outgoing.AddLiteralTerm(literals[i], IntegerValue(1)));
2267  }
2268  }
2269 
2270  // Support optional nodes if any.
2271  if (num_optional_nodes_in + num_optional_nodes_out > 0) {
2272  // When all optionals of one side are excluded in lp solution, no cut.
2273  if (num_optional_nodes_in == subset_size &&
2274  (optional_loop_in == -1 ||
2275  literal_lp_values[optional_loop_in] > 1.0 - 1e-6)) {
2276  return;
2277  }
2278  if (num_optional_nodes_out == num_nodes - subset_size &&
2279  (optional_loop_out == -1 ||
2280  literal_lp_values[optional_loop_out] > 1.0 - 1e-6)) {
2281  return;
2282  }
2283 
2284  // There is no mandatory node in subset, add optional_loop_in.
2285  if (num_optional_nodes_in == subset_size) {
2286  CHECK(
2287  outgoing.AddLiteralTerm(literals[optional_loop_in], IntegerValue(1)));
2288  sum_outgoing += literal_lp_values[optional_loop_in];
2289  }
2290 
2291  // There is no mandatory node out of subset, add optional_loop_out.
2292  if (num_optional_nodes_out == num_nodes - subset_size) {
2293  CHECK(outgoing.AddLiteralTerm(literals[optional_loop_out],
2294  IntegerValue(1)));
2295  sum_outgoing += literal_lp_values[optional_loop_out];
2296  }
2297  }
2298 
2299  if (sum_outgoing < rhs_lower_bound - 1e-6) {
2300  manager->AddCut(outgoing.Build(), "Circuit", lp_values);
2301  }
2302 }
2303 
2304 } // namespace
2305 
2306 // We roughly follow the algorithm described in section 6 of "The Traveling
2307 // Salesman Problem, A computational Study", David L. Applegate, Robert E.
2308 // Bixby, Vasek Chvatal, William J. Cook.
2309 //
2310 // Note that this is mainly a "symmetric" case algo, but it does still work for
2311 // the asymmetric case.
2313  int num_nodes, const std::vector<int>& tails, const std::vector<int>& heads,
2314  const std::vector<Literal>& literals,
2316  absl::Span<const int64_t> demands, int64_t capacity,
2317  LinearConstraintManager* manager, Model* model) {
2318  if (num_nodes <= 2) return;
2319 
2320  // We will collect only the arcs with a positive lp_values to speed up some
2321  // computation below.
2322  struct Arc {
2323  int tail;
2324  int head;
2325  double lp_value;
2326  };
2327  std::vector<Arc> relevant_arcs;
2328 
2329  // Sort the arcs by non-increasing lp_values.
2330  std::vector<double> literal_lp_values(literals.size());
2331  std::vector<std::pair<double, int>> arc_by_decreasing_lp_values;
2332  auto* encoder = model->GetOrCreate<IntegerEncoder>();
2333  for (int i = 0; i < literals.size(); ++i) {
2334  double lp_value;
2335  const IntegerVariable direct_view = encoder->GetLiteralView(literals[i]);
2336  if (direct_view != kNoIntegerVariable) {
2337  lp_value = lp_values[direct_view];
2338  } else {
2339  lp_value =
2340  1.0 - lp_values[encoder->GetLiteralView(literals[i].Negated())];
2341  }
2342  literal_lp_values[i] = lp_value;
2343 
2344  if (lp_value < 1e-6) continue;
2345  relevant_arcs.push_back({tails[i], heads[i], lp_value});
2346  arc_by_decreasing_lp_values.push_back({lp_value, i});
2347  }
2348  std::sort(arc_by_decreasing_lp_values.begin(),
2349  arc_by_decreasing_lp_values.end(),
2350  std::greater<std::pair<double, int>>());
2351 
2352  // We will do a union-find by adding one by one the arc of the lp solution
2353  // in the order above. Every intermediate set during this construction will
2354  // be a candidate for a cut.
2355  //
2356  // In parallel to the union-find, to efficiently reconstruct these sets (at
2357  // most num_nodes), we construct a "decomposition forest" of the different
2358  // connected components. Note that we don't exploit any asymmetric nature of
2359  // the graph here. This is exactly the algo 6.3 in the book above.
2360  int num_components = num_nodes;
2361  std::vector<int> parent(num_nodes);
2362  std::vector<int> root(num_nodes);
2363  for (int i = 0; i < num_nodes; ++i) {
2364  parent[i] = i;
2365  root[i] = i;
2366  }
2367  auto get_root_and_compress_path = [&root](int node) {
2368  int r = node;
2369  while (root[r] != r) r = root[r];
2370  while (root[node] != r) {
2371  const int next = root[node];
2372  root[node] = r;
2373  node = next;
2374  }
2375  return r;
2376  };
2377  for (const auto pair : arc_by_decreasing_lp_values) {
2378  if (num_components == 2) break;
2379  const int tail = get_root_and_compress_path(tails[pair.second]);
2380  const int head = get_root_and_compress_path(heads[pair.second]);
2381  if (tail != head) {
2382  // Update the decomposition forest, note that the number of nodes is
2383  // growing.
2384  const int new_node = parent.size();
2385  parent.push_back(new_node);
2386  parent[head] = new_node;
2387  parent[tail] = new_node;
2388  --num_components;
2389 
2390  // It is important that the union-find representative is the same node.
2391  root.push_back(new_node);
2392  root[head] = new_node;
2393  root[tail] = new_node;
2394  }
2395  }
2396 
2397  // For each node in the decomposition forest, try to add a cut for the set
2398  // formed by the nodes and its children. To do that efficiently, we first
2399  // order the nodes so that for each node in a tree, the set of children forms
2400  // a consecutive span in the pre_order vector. This vector just lists the
2401  // nodes in the "pre-order" graph traversal order. The Spans will point inside
2402  // the pre_order vector, it is why we initialize it once and for all.
2403  int new_size = 0;
2404  std::vector<int> pre_order(num_nodes);
2405  std::vector<absl::Span<const int>> subsets;
2406  {
2407  std::vector<absl::InlinedVector<int, 2>> graph(parent.size());
2408  for (int i = 0; i < parent.size(); ++i) {
2409  if (parent[i] != i) graph[parent[i]].push_back(i);
2410  }
2411  std::vector<int> queue;
2412  std::vector<bool> seen(graph.size(), false);
2413  std::vector<int> start_index(parent.size());
2414  for (int i = num_nodes; i < parent.size(); ++i) {
2415  // Note that because of the way we constructed 'parent', the graph is a
2416  // binary tree. This is not required for the correctness of the algorithm
2417  // here though.
2418  CHECK(graph[i].empty() || graph[i].size() == 2);
2419  if (parent[i] != i) continue;
2420 
2421  // Explore the subtree rooted at node i.
2422  CHECK(!seen[i]);
2423  queue.push_back(i);
2424  while (!queue.empty()) {
2425  const int node = queue.back();
2426  if (seen[node]) {
2427  queue.pop_back();
2428  // All the children of node are in the span [start, end) of the
2429  // pre_order vector.
2430  const int start = start_index[node];
2431  if (new_size - start > 1) {
2432  subsets.emplace_back(&pre_order[start], new_size - start);
2433  }
2434  continue;
2435  }
2436  seen[node] = true;
2437  start_index[node] = new_size;
2438  if (node < num_nodes) pre_order[new_size++] = node;
2439  for (const int child : graph[node]) {
2440  if (!seen[child]) queue.push_back(child);
2441  }
2442  }
2443  }
2444  }
2445 
2446  // Compute the total demands in order to know the minimum incoming/outgoing
2447  // flow.
2448  int64_t total_demands = 0;
2449  if (!demands.empty()) {
2450  for (const int64_t demand : demands) total_demands += demand;
2451  }
2452 
2453  // Process each subsets and add any violated cut.
2454  CHECK_EQ(pre_order.size(), num_nodes);
2455  std::vector<bool> in_subset(num_nodes, false);
2456  for (const absl::Span<const int> subset : subsets) {
2457  CHECK_GT(subset.size(), 1);
2458  CHECK_LT(subset.size(), num_nodes);
2459 
2460  // These fields will be left untouched if demands.empty().
2461  bool contain_depot = false;
2462  int64_t subset_demand = 0;
2463 
2464  // Initialize "in_subset" and the subset demands.
2465  for (const int n : subset) {
2466  in_subset[n] = true;
2467  if (!demands.empty()) {
2468  if (n == 0) contain_depot = true;
2469  subset_demand += demands[n];
2470  }
2471  }
2472 
2473  // Compute a lower bound on the outgoing flow.
2474  //
2475  // TODO(user): This lower bound assume all nodes in subset must be served,
2476  // which is not the case. For TSP we do the correct thing in
2477  // AddOutgoingCut() but not for CVRP... Fix!!
2478  //
2479  // TODO(user): It could be very interesting to see if this "min outgoing
2480  // flow" cannot be automatically infered from the constraint in the
2481  // precedence graph. This might work if we assume that any kind of path
2482  // cumul constraint is encoded with constraints:
2483  // [edge => value_head >= value_tail + edge_weight].
2484  // We could take the minimum incoming edge weight per node in the set, and
2485  // use the cumul variable domain to infer some capacity.
2486  int64_t min_outgoing_flow = 1;
2487  if (!demands.empty()) {
2488  min_outgoing_flow =
2489  contain_depot
2490  ? (total_demands - subset_demand + capacity - 1) / capacity
2491  : (subset_demand + capacity - 1) / capacity;
2492  }
2493 
2494  // We still need to serve nodes with a demand of zero, and in the corner
2495  // case where all node in subset have a zero demand, the formula above
2496  // result in a min_outgoing_flow of zero.
2497  min_outgoing_flow = std::max(min_outgoing_flow, int64_t{1});
2498 
2499  // Compute the current outgoing flow out of the subset.
2500  //
2501  // This can take a significant portion of the running time, it is why it is
2502  // faster to do it only on arcs with non-zero lp values which should be in
2503  // linear number rather than the total number of arc which can be quadratic.
2504  //
2505  // TODO(user): For the symmetric case there is an even faster algo. See if
2506  // it can be generalized to the asymmetric one if become needed.
2507  // Reference is algo 6.4 of the "The Traveling Salesman Problem" book
2508  // mentionned above.
2509  double outgoing_flow = 0.0;
2510  for (const auto arc : relevant_arcs) {
2511  if (in_subset[arc.tail] && !in_subset[arc.head]) {
2512  outgoing_flow += arc.lp_value;
2513  }
2514  }
2515 
2516  // Add a cut if the current outgoing flow is not enough.
2517  if (outgoing_flow < min_outgoing_flow - 1e-6) {
2518  AddOutgoingCut(num_nodes, subset.size(), in_subset, tails, heads,
2519  literals, literal_lp_values,
2520  /*rhs_lower_bound=*/min_outgoing_flow, lp_values, manager,
2521  model);
2522  }
2523 
2524  // Sparse clean up.
2525  for (const int n : subset) in_subset[n] = false;
2526  }
2527 }
2528 
2529 namespace {
2530 
2531 // Returns for each literal its integer view, or the view of its negation.
2532 std::vector<IntegerVariable> GetAssociatedVariables(
2533  const std::vector<Literal>& literals, Model* model) {
2534  auto* encoder = model->GetOrCreate<IntegerEncoder>();
2535  std::vector<IntegerVariable> result;
2536  for (const Literal l : literals) {
2537  const IntegerVariable direct_view = encoder->GetLiteralView(l);
2538  if (direct_view != kNoIntegerVariable) {
2539  result.push_back(direct_view);
2540  } else {
2541  result.push_back(encoder->GetLiteralView(l.Negated()));
2542  DCHECK_NE(result.back(), kNoIntegerVariable);
2543  }
2544  }
2545  return result;
2546 }
2547 
2548 } // namespace
2549 
2550 // We use a basic algorithm to detect components that are not connected to the
2551 // rest of the graph in the LP solution, and add cuts to force some arcs to
2552 // enter and leave this component from outside.
2554  int num_nodes, const std::vector<int>& tails, const std::vector<int>& heads,
2555  const std::vector<Literal>& literals, Model* model) {
2556  CutGenerator result;
2557  result.vars = GetAssociatedVariables(literals, model);
2558  result.generate_cuts =
2559  [num_nodes, tails, heads, literals, model](
2561  LinearConstraintManager* manager) {
2563  num_nodes, tails, heads, literals, lp_values,
2564  /*demands=*/{}, /*capacity=*/0, manager, model);
2565  return true;
2566  };
2567  return result;
2568 }
2569 
2571  const std::vector<int>& tails,
2572  const std::vector<int>& heads,
2573  const std::vector<Literal>& literals,
2574  const std::vector<int64_t>& demands,
2575  int64_t capacity, Model* model) {
2576  CutGenerator result;
2577  result.vars = GetAssociatedVariables(literals, model);
2578  result.generate_cuts =
2579  [num_nodes, tails, heads, demands, capacity, literals, model](
2581  LinearConstraintManager* manager) {
2582  SeparateSubtourInequalities(num_nodes, tails, heads, literals,
2583  lp_values, demands, capacity, manager,
2584  model);
2585  return true;
2586  };
2587  return result;
2588 }
2589 
2590 std::function<IntegerLiteral()>
2592  // Gather all 0-1 variables that appear in this LP.
2593  std::vector<IntegerVariable> variables;
2594  for (IntegerVariable var : integer_variables_) {
2595  if (integer_trail_->LowerBound(var) == 0 &&
2596  integer_trail_->UpperBound(var) == 1) {
2597  variables.push_back(var);
2598  }
2599  }
2600  VLOG(1) << "HeuristicLPMostInfeasibleBinary has " << variables.size()
2601  << " variables.";
2602 
2603  return [this, variables]() {
2604  const double kEpsilon = 1e-6;
2605  // Find most fractional value.
2606  IntegerVariable fractional_var = kNoIntegerVariable;
2607  double fractional_distance_best = -1.0;
2608  for (const IntegerVariable var : variables) {
2609  // Skip ignored and fixed variables.
2610  if (integer_trail_->IsCurrentlyIgnored(var)) continue;
2611  const IntegerValue lb = integer_trail_->LowerBound(var);
2612  const IntegerValue ub = integer_trail_->UpperBound(var);
2613  if (lb == ub) continue;
2614 
2615  // Check variable's support is fractional.
2616  const double lp_value = this->GetSolutionValue(var);
2617  const double fractional_distance =
2618  std::min(std::ceil(lp_value - kEpsilon) - lp_value,
2619  lp_value - std::floor(lp_value + kEpsilon));
2620  if (fractional_distance < kEpsilon) continue;
2621 
2622  // Keep variable if it is farther from integrality than the previous.
2623  if (fractional_distance > fractional_distance_best) {
2624  fractional_var = var;
2625  fractional_distance_best = fractional_distance;
2626  }
2627  }
2628 
2629  if (fractional_var != kNoIntegerVariable) {
2630  IntegerLiteral::GreaterOrEqual(fractional_var, IntegerValue(1));
2631  }
2632  return IntegerLiteral();
2633  };
2634 }
2635 
2636 std::function<IntegerLiteral()>
2638  // Gather all 0-1 variables that appear in this LP.
2639  std::vector<IntegerVariable> variables;
2640  for (IntegerVariable var : integer_variables_) {
2641  if (integer_trail_->LowerBound(var) == 0 &&
2642  integer_trail_->UpperBound(var) == 1) {
2643  variables.push_back(var);
2644  }
2645  }
2646  VLOG(1) << "HeuristicLpReducedCostBinary has " << variables.size()
2647  << " variables.";
2648 
2649  // Store average of reduced cost from 1 to 0. The best heuristic only sets
2650  // variables to one and cares about cost to zero, even though classic
2651  // pseudocost will use max_var min(cost_to_one[var], cost_to_zero[var]).
2652  const int num_vars = variables.size();
2653  std::vector<double> cost_to_zero(num_vars, 0.0);
2654  std::vector<int> num_cost_to_zero(num_vars);
2655  int num_calls = 0;
2656 
2657  return [=]() mutable {
2658  const double kEpsilon = 1e-6;
2659 
2660  // Every 10000 calls, decay pseudocosts.
2661  num_calls++;
2662  if (num_calls == 10000) {
2663  for (int i = 0; i < num_vars; i++) {
2664  cost_to_zero[i] /= 2;
2665  num_cost_to_zero[i] /= 2;
2666  }
2667  num_calls = 0;
2668  }
2669 
2670  // Accumulate pseudo-costs of all unassigned variables.
2671  for (int i = 0; i < num_vars; i++) {
2672  const IntegerVariable var = variables[i];
2673  // Skip ignored and fixed variables.
2674  if (integer_trail_->IsCurrentlyIgnored(var)) continue;
2675  const IntegerValue lb = integer_trail_->LowerBound(var);
2676  const IntegerValue ub = integer_trail_->UpperBound(var);
2677  if (lb == ub) continue;
2678 
2679  const double rc = this->GetSolutionReducedCost(var);
2680  // Skip reduced costs that are nonzero because of numerical issues.
2681  if (std::abs(rc) < kEpsilon) continue;
2682 
2683  const double value = std::round(this->GetSolutionValue(var));
2684  if (value == 1.0 && rc < 0.0) {
2685  cost_to_zero[i] -= rc;
2686  num_cost_to_zero[i]++;
2687  }
2688  }
2689 
2690  // Select noninstantiated variable with highest pseudo-cost.
2691  int selected_index = -1;
2692  double best_cost = 0.0;
2693  for (int i = 0; i < num_vars; i++) {
2694  const IntegerVariable var = variables[i];
2695  // Skip ignored and fixed variables.
2696  if (integer_trail_->IsCurrentlyIgnored(var)) continue;
2697  if (integer_trail_->IsFixed(var)) continue;
2698 
2699  if (num_cost_to_zero[i] > 0 &&
2700  best_cost < cost_to_zero[i] / num_cost_to_zero[i]) {
2701  best_cost = cost_to_zero[i] / num_cost_to_zero[i];
2702  selected_index = i;
2703  }
2704  }
2705 
2706  if (selected_index >= 0) {
2707  return IntegerLiteral::GreaterOrEqual(variables[selected_index],
2708  IntegerValue(1));
2709  }
2710  return IntegerLiteral();
2711  };
2712 }
2713 
2714 void LinearProgrammingConstraint::UpdateAverageReducedCosts() {
2715  const int num_vars = integer_variables_.size();
2716  if (sum_cost_down_.size() < num_vars) {
2717  sum_cost_down_.resize(num_vars, 0.0);
2718  num_cost_down_.resize(num_vars, 0);
2719  sum_cost_up_.resize(num_vars, 0.0);
2720  num_cost_up_.resize(num_vars, 0);
2721  rc_scores_.resize(num_vars, 0.0);
2722  }
2723 
2724  // Decay averages.
2725  num_calls_since_reduced_cost_averages_reset_++;
2726  if (num_calls_since_reduced_cost_averages_reset_ == 10000) {
2727  for (int i = 0; i < num_vars; i++) {
2728  sum_cost_up_[i] /= 2;
2729  num_cost_up_[i] /= 2;
2730  sum_cost_down_[i] /= 2;
2731  num_cost_down_[i] /= 2;
2732  }
2733  num_calls_since_reduced_cost_averages_reset_ = 0;
2734  }
2735 
2736  // Accumulate reduced costs of all unassigned variables.
2737  for (int i = 0; i < num_vars; i++) {
2738  const IntegerVariable var = integer_variables_[i];
2739 
2740  // Skip ignored and fixed variables.
2741  if (integer_trail_->IsCurrentlyIgnored(var)) continue;
2742  if (integer_trail_->IsFixed(var)) continue;
2743 
2744  // Skip reduced costs that are zero or close.
2745  const double rc = lp_reduced_cost_[i];
2746  if (std::abs(rc) < kCpEpsilon) continue;
2747 
2748  if (rc < 0.0) {
2749  sum_cost_down_[i] -= rc;
2750  num_cost_down_[i]++;
2751  } else {
2752  sum_cost_up_[i] += rc;
2753  num_cost_up_[i]++;
2754  }
2755  }
2756 
2757  // Tricky, we artificially reset the rc_rev_int_repository_ to level zero
2758  // so that the rev_rc_start_ is zero.
2759  rc_rev_int_repository_.SetLevel(0);
2760  rc_rev_int_repository_.SetLevel(trail_->CurrentDecisionLevel());
2761  rev_rc_start_ = 0;
2762 
2763  // Cache the new score (higher is better) using the average reduced costs
2764  // as a signal.
2765  positions_by_decreasing_rc_score_.clear();
2766  for (int i = 0; i < num_vars; i++) {
2767  // If only one direction exist, we takes its value divided by 2, so that
2768  // such variable should have a smaller cost than the min of the two side
2769  // except if one direction have a really high reduced costs.
2770  const double a_up =
2771  num_cost_up_[i] > 0 ? sum_cost_up_[i] / num_cost_up_[i] : 0.0;
2772  const double a_down =
2773  num_cost_down_[i] > 0 ? sum_cost_down_[i] / num_cost_down_[i] : 0.0;
2774  if (num_cost_down_[i] > 0 && num_cost_up_[i] > 0) {
2775  rc_scores_[i] = std::min(a_up, a_down);
2776  } else {
2777  rc_scores_[i] = 0.5 * (a_down + a_up);
2778  }
2779 
2780  // We ignore scores of zero (i.e. no data) and will follow the default
2781  // search heuristic if all variables are like this.
2782  if (rc_scores_[i] > 0.0) {
2783  positions_by_decreasing_rc_score_.push_back({-rc_scores_[i], i});
2784  }
2785  }
2786  std::sort(positions_by_decreasing_rc_score_.begin(),
2787  positions_by_decreasing_rc_score_.end());
2788 }
2789 
2790 // TODO(user): Remove duplication with HeuristicLpReducedCostBinary().
2791 std::function<IntegerLiteral()>
2793  return [this]() { return this->LPReducedCostAverageDecision(); };
2794 }
2795 
2796 IntegerLiteral LinearProgrammingConstraint::LPReducedCostAverageDecision() {
2797  // Select noninstantiated variable with highest positive average reduced cost.
2798  int selected_index = -1;
2799  const int size = positions_by_decreasing_rc_score_.size();
2800  rc_rev_int_repository_.SaveState(&rev_rc_start_);
2801  for (int i = rev_rc_start_; i < size; ++i) {
2802  const int index = positions_by_decreasing_rc_score_[i].second;
2803  const IntegerVariable var = integer_variables_[index];
2804  if (integer_trail_->IsCurrentlyIgnored(var)) continue;
2805  if (integer_trail_->IsFixed(var)) continue;
2806  selected_index = index;
2807  rev_rc_start_ = i;
2808  break;
2809  }
2810 
2811  if (selected_index == -1) return IntegerLiteral();
2812  const IntegerVariable var = integer_variables_[selected_index];
2813 
2814  // If ceil(value) is current upper bound, try var == upper bound first.
2815  // Guarding with >= prevents numerical problems.
2816  // With 0/1 variables, this will tend to try setting to 1 first,
2817  // which produces more shallow trees.
2818  const IntegerValue ub = integer_trail_->UpperBound(var);
2819  const IntegerValue value_ceil(
2820  std::ceil(this->GetSolutionValue(var) - kCpEpsilon));
2821  if (value_ceil >= ub) {
2822  return IntegerLiteral::GreaterOrEqual(var, ub);
2823  }
2824 
2825  // If floor(value) is current lower bound, try var == lower bound first.
2826  // Guarding with <= prevents numerical problems.
2827  const IntegerValue lb = integer_trail_->LowerBound(var);
2828  const IntegerValue value_floor(
2829  std::floor(this->GetSolutionValue(var) + kCpEpsilon));
2830  if (value_floor <= lb) {
2831  return IntegerLiteral::LowerOrEqual(var, lb);
2832  }
2833 
2834  // Here lb < value_floor <= value_ceil < ub.
2835  // Try the most promising split between var <= floor or var >= ceil.
2836  const double a_up =
2837  num_cost_up_[selected_index] > 0
2838  ? sum_cost_up_[selected_index] / num_cost_up_[selected_index]
2839  : 0.0;
2840  const double a_down =
2841  num_cost_down_[selected_index] > 0
2842  ? sum_cost_down_[selected_index] / num_cost_down_[selected_index]
2843  : 0.0;
2844  if (a_down < a_up) {
2845  return IntegerLiteral::LowerOrEqual(var, value_floor);
2846  } else {
2847  return IntegerLiteral::GreaterOrEqual(var, value_ceil);
2848  }
2849 }
2850 
2852  std::string result = "LP statistics:\n";
2853  absl::StrAppend(&result, " final dimension: ", DimensionString(), "\n");
2854  absl::StrAppend(&result, " total number of simplex iterations: ",
2855  total_num_simplex_iterations_, "\n");
2856  absl::StrAppend(&result, " num solves: \n");
2857  for (int i = 0; i < num_solves_by_status_.size(); ++i) {
2858  if (num_solves_by_status_[i] == 0) continue;
2859  absl::StrAppend(&result, " - #",
2861  num_solves_by_status_[i], "\n");
2862  }
2863  absl::StrAppend(&result, constraint_manager_.Statistics());
2864  return result;
2865 }
2866 
2867 } // namespace sat
2868 } // namespace operations_research
int64_t head
#define CHECK(condition)
Definition: base/logging.h:491
ColIndex RowToColIndex(RowIndex row)
Definition: lp_types.h:49
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:105
int64_t bound
int64_t CapSub(int64_t x, int64_t y)
::PROTOBUF_NAMESPACE_ID::int32 linearization_level() const
const bool DEBUG_MODE
Definition: macros.h:24
int64_t min
Definition: alldiff_cst.cc:139
static constexpr SearchBranching LP_SEARCH
void SetObjectiveCoefficient(ColIndex col, Fractional value)
Definition: lp_data.cc:326
#define CHECK_GE(val1, val2)
Definition: base/logging.h:702
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
void LoadStateForNextSolve(const BasisState &state)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
IntegerLiteral UpperBoundAsLiteral(IntegerVariable i) const
Definition: integer.h:1392
#define CHECK_GT(val1, val2)
Definition: base/logging.h:703
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1315
std::vector< Literal > * MutableConflict()
Definition: sat_base.h:362
#define VLOG(verboselevel)
Definition: base/logging.h:979
const std::string name
Fractional GetVariableValue(ColIndex col) const
bool ReportConflict(absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.h:849
Fractional UnscaleDualValue(RowIndex row, Fractional value) const
std::vector< IntegerVariable > vars
Definition: cuts.h:43
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1345
const std::vector< ConstraintIndex > & LpConstraints() const
std::function< IntegerLiteral()> HeuristicLpMostInfeasibleBinary(Model *model)
void SetLevel(int level) final
Definition: rev.h:134
std::string GetProblemStatusString(ProblemStatus problem_status)
Definition: lp_types.cc:19
ColIndex col
Definition: markowitz.cc:183
GRBmodel * model
const absl::StrongVector< ConstraintIndex, ConstraintInfo > & AllConstraints() const
int64_t CapProd(int64_t x, int64_t y)
ABSL_MUST_USE_RESULT Status Solve(const LinearProgram &lp, TimeLimit *time_limit)
void SetConstraintBounds(RowIndex row, Fractional lower_bound, Fractional upper_bound)
Definition: lp_data.cc:309
#define DCHECK_GT(val1, val2)
Definition: base/logging.h:891
void SetObjectiveOffset(Fractional objective_offset)
Definition: lp_data.cc:331
void MakeAllCoefficientsPositive(LinearConstraint *constraint)
void ComputeCut(RoundingOptions options, const std::vector< double > &lp_values, const std::vector< IntegerValue > &lower_bounds, const std::vector< IntegerValue > &upper_bounds, ImpliedBoundsProcessor *ib_processor, LinearConstraint *cut)
Definition: cuts.cc:720
RowIndex row
Definition: markowitz.cc:182
void RemoveLevelZeroBounds(std::vector< IntegerLiteral > *reason) const
Definition: integer.cc:958
CutGenerator CreateCVRPCutGenerator(int num_nodes, const std::vector< int > &tails, const std::vector< int > &heads, const std::vector< Literal > &literals, const std::vector< int64_t > &demands, int64_t capacity, Model *model)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
int64_t tail
IntegerLiteral LowerBoundAsLiteral(IntegerVariable i) const
Definition: integer.h:1387
int64_t b
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.cc:1028
const IntegerVariable GetLiteralView(Literal lit) const
Definition: integer.h:454
double ComputeActivity(const LinearConstraint &constraint, const absl::StrongVector< IntegerVariable, double > &values)
bool AddProductTo(IntegerValue a, IntegerValue b, IntegerValue *result)
Definition: integer.h:114
void SetObjectiveCoefficient(IntegerVariable var, IntegerValue coeff)
#define CHECK_LT(val1, val2)
Definition: base/logging.h:701
Fractional GetReducedCost(ColIndex col) const
double ToDouble(IntegerValue value)
Definition: integer.h:70
IntegerVariable PositiveVariable(IntegerVariable i)
Definition: integer.h:142
static constexpr CostScalingAlgorithm MEAN_COST_SCALING
std::vector< std::pair< glop::ColIndex, IntegerValue > > GetTerms()
void DivideByGCD(LinearConstraint *constraint)
int64_t max
Definition: alldiff_cst.cc:140
Block * next
Fractional objective_scaling_factor() const
Definition: lp_data.h:261
double upper_bound
void resize(size_type new_size)
std::string GetDimensionString() const
Definition: lp_data.cc:425
StrictITIVector< RowIndex, Fractional > DenseColumn
Definition: lp_types.h:332
void SeparateSubtourInequalities(int num_nodes, const std::vector< int > &tails, const std::vector< int > &heads, const std::vector< Literal > &literals, const absl::StrongVector< IntegerVariable, double > &lp_values, absl::Span< const int64_t > demands, int64_t capacity, LinearConstraintManager *manager, Model *model)
Fractional VariableScalingFactor(ColIndex col) const
int64_t CapAdd(int64_t x, int64_t y)
Fractional UnscaleVariableValue(ColIndex col, Fractional value) const
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:887
::PROTOBUF_NAMESPACE_ID::int32 max_cut_rounds_at_level_zero() const
IntegerValue ComputeInfinityNorm(const LinearConstraint &constraint)
void AddOneConstraint(glop::RowIndex, const std::vector< std::pair< glop::ColIndex, IntegerValue >> &terms, IntegerValue lb, IntegerValue ub)
CutGenerator CreateStronglyConnectedGraphCutGenerator(int num_nodes, const std::vector< int > &tails, const std::vector< int > &heads, const std::vector< Literal > &literals, Model *model)
void SetParameters(const GlopParameters &parameters)
VariableStatus GetVariableStatus(ColIndex col) const
void SetIntegralityScale(ColIndex col, Fractional scale)
double lower_bound
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition: integer.h:1412
int64_t demand
Definition: resource.cc:125
static int64_t GCD64(int64_t x, int64_t y)
Definition: mathutil.h:107
const Collection::value_type::second_type & FindOrDie(const Collection &collection, const typename Collection::value_type::first_type &key)
Definition: map_util.h:206
bool DebugSlack(IntegerVariable first_slack, const LinearConstraint &initial_cut, const LinearConstraint &cut, const std::vector< SlackInfo > &info)
Definition: cuts.cc:1726
void SetPropagatorPriority(int id, int priority)
Definition: integer.cc:2019
void TransferToManager(const absl::StrongVector< IntegerVariable, double > &lp_solution, LinearConstraintManager *manager)
void ProcessVariables(const std::vector< double > &lp_values, const std::vector< IntegerValue > &lower_bounds, const std::vector< IntegerValue > &upper_bounds)
std::function< IntegerLiteral()> HeuristicLpReducedCostBinary(Model *model)
void push_back(const value_type &x)
int64_t capacity
bool IsFixedAtLevelZero(IntegerVariable var) const
Definition: integer.h:1417
int index
Definition: pack.cc:509
bool ChangeLp(const absl::StrongVector< IntegerVariable, double > &lp_solution, glop::BasisState *solution_state)
bool VariableIsPositive(IntegerVariable i)
Definition: integer.h:138
RowIndex ColToRowIndex(ColIndex col)
Definition: lp_types.h:52
void RelaxLinearReason(IntegerValue slack, absl::Span< const IntegerValue > coeffs, std::vector< IntegerLiteral > *reason) const
Definition: integer.cc:824
const DenseColumn & GetDualRay() const
void ProcessUpperBoundedConstraintWithSlackCreation(bool substitute_only_inner_variables, IntegerVariable first_slack, const absl::StrongVector< IntegerVariable, double > &lp_values, LinearConstraint *cut, std::vector< SlackInfo > *slack_infos)
Definition: cuts.cc:1593
void RecomputeCacheAndSeparateSomeImpliedBoundCuts(const absl::StrongVector< IntegerVariable, double > &lp_values)
Definition: cuts.cc:1583
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:698
std::vector< std::vector< std::pair< glop::RowIndex, IntegerValue > > > InterestingCandidates(ModelRandomGenerator *random)
LinearConstraint * mutable_cut()
Definition: cuts.h:254
const ScatteredRow & GetUnitRowLeftInverse(RowIndex row)
size_type size() const
ConstraintIndex Add(LinearConstraint ct, bool *added=nullptr)
bool IncrementalPropagate(const std::vector< int > &watch_indices) override
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:29
void SetObjectiveCoefficient(IntegerVariable ivar, IntegerValue coeff)
#define DCHECK(condition)
Definition: base/logging.h:885
const GlopParameters & GetParameters() const
ConstraintStatus GetConstraintStatus(RowIndex row) const
IntegerValue FloorRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:91
void RegisterReversibleClass(ReversibleInterface *rev)
Definition: integer.h:872
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 SetVariableBounds(ColIndex col, Fractional lower_bound, Fractional upper_bound)
Definition: lp_data.cc:249
bool IsCurrentlyIgnored(IntegerVariable i) const
Definition: integer.h:659
const DenseRow & GetDualRayRowCombination() const
std::pair< int64_t, int64_t > Arc
Definition: search.cc:3383
void WatchIntegerVariable(IntegerVariable i, int id, int watch_index=-1)
Definition: integer.h:1454
int Register(PropagatorInterface *propagator)
Definition: integer.cc:1996
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1349
void AddLpVariable(IntegerVariable var)
Definition: cuts.h:113
Collection of objects used to extend the Constraint Solver library.
const IntegerVariable kNoIntegerVariable(-1)
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1309
const double kEpsilon
Definition: lp_types.h:87
void assign(size_type n, const value_type &val)
SatParameters parameters
Fractional UnscaleReducedCost(ColIndex col, Fractional value) const
Fractional GetDualValue(RowIndex row) const
void WatchUpperBound(IntegerVariable var, int id, int watch_index=-1)
Definition: integer.h:1448
void SaveState(T *object)
Definition: rev.h:61
bool Add(glop::ColIndex col, IntegerValue value)
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="")
std::function< bool(const absl::StrongVector< IntegerVariable, double > &lp_values, LinearConstraintManager *manager)> generate_cuts
Definition: cuts.h:47
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:41
void ConvertToLinearConstraint(const std::vector< IntegerVariable > &integer_variables, IntegerValue upper_bound, LinearConstraint *result)
IntType IntTypeAbs(IntType t)
Definition: integer.h:78
void SetCoefficient(RowIndex row, ColIndex col, Fractional value)
Definition: lp_data.cc:317
StrictITIVector< ColIndex, Fractional > DenseRow
Definition: lp_types.h:303
bool AddLinearExpressionMultiple(IntegerValue multiplier, const std::vector< std::pair< glop::ColIndex, IntegerValue >> &terms)
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition: integer.h:1407
bool TrySimpleKnapsack(const LinearConstraint base_ct, const std::vector< double > &lp_values, const std::vector< IntegerValue > &lower_bounds, const std::vector< IntegerValue > &upper_bounds)
Definition: cuts.cc:1168
int64_t value
#define CHECK_NE(val1, val2)
Definition: base/logging.h:699
bool IsFixed(IntegerVariable i) const
Definition: integer.h:1353
const Constraint * ct
ColIndex GetBasis(RowIndex row) const
const SparseColumn & GetSparseColumn(ColIndex col) const
Definition: lp_data.cc:409
::PROTOBUF_NAMESPACE_ID::int32 max_integer_rounding_scaling() const
int64_t a