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 
14 #include "ortools/sat/linear_programming_constraint.h"
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"
27 #include "ortools/base/commandlineflags.h"
28 #include "ortools/base/integral_types.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"
33 #include "ortools/base/strong_vector.h"
34 #include "ortools/glop/parameters.pb.h"
35 #include "ortools/glop/preprocessor.h"
36 #include "ortools/glop/status.h"
37 #include "ortools/graph/strongly_connected_components.h"
38 #include "ortools/lp_data/lp_types.h"
39 #include "ortools/sat/implied_bounds.h"
40 #include "ortools/sat/integer.h"
41 #include "ortools/sat/zero_half_cuts.h"
42 #include "ortools/util/saturated_arithmetic.h"
43 
44 namespace operations_research {
45 namespace sat {
46 
47 using glop::ColIndex;
48 using glop::Fractional;
49 using glop::RowIndex;
50 
ClearAndResize(int size)51 void ScatteredIntegerVector::ClearAndResize(int size) {
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 
Add(glop::ColIndex col,IntegerValue value)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() ||
71       add == std::numeric_limits<int64_t>::max())
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 
AddLinearExpressionMultiple(IntegerValue multiplier,const std::vector<std::pair<glop::ColIndex,IntegerValue>> & terms)81 bool ScatteredIntegerVector::AddLinearExpressionMultiple(
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 
ConvertToLinearConstraint(const std::vector<IntegerVariable> & integer_variables,IntegerValue upper_bound,LinearConstraint * result)109 void ScatteredIntegerVector::ConvertToLinearConstraint(
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>>
GetTerms()136 ScatteredIntegerVector::GetTerms() {
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.
LinearProgrammingConstraint(Model * model)156 LinearProgrammingConstraint::LinearProgrammingConstraint(Model* model)
157     : constraint_manager_(model),
158       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.
171   glop::GlopParameters parameters;
172   parameters.set_use_dual_simplex(true);
173   simplex_.SetParameters(parameters);
174   if (parameters_.use_branching_in_lp() ||
175       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 
AddLinearConstraint(const LinearConstraint & ct)183 void LinearProgrammingConstraint::AddLinearConstraint(
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 
GetOrCreateMirrorVariable(IntegerVariable positive_variable)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 
SetObjectiveCoefficient(IntegerVariable ivar,IntegerValue coeff)221 void LinearProgrammingConstraint::SetObjectiveCoefficient(IntegerVariable ivar,
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.
CreateLpFromConstraintManager()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 (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 
SolveLpForBranching()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 
FillReducedCostReasonIn(const glop::DenseRow & reduced_costs,std::vector<IntegerLiteral> * integer_reason)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 
BranchOnVar(IntegerVariable positive_var)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 
RegisterWith(Model * model)512 void LinearProgrammingConstraint::RegisterWith(Model* model) {
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 (!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 
SetLevel(int level)548 void LinearProgrammingConstraint::SetLevel(int level) {
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 
AddCutGenerator(CutGenerator generator)571 void LinearProgrammingConstraint::AddCutGenerator(CutGenerator generator) {
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 
IncrementalPropagate(const std::vector<int> & watch_indices)578 bool LinearProgrammingConstraint::IncrementalPropagate(
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 
GetVariableValueAtCpScale(glop::ColIndex var)610 glop::Fractional LinearProgrammingConstraint::GetVariableValueAtCpScale(
611     glop::ColIndex var) {
612   return scaler_.UnscaleVariableValue(var, simplex_.GetVariableValue(var));
613 }
614 
GetSolutionValue(IntegerVariable variable) const615 double LinearProgrammingConstraint::GetSolutionValue(
616     IntegerVariable variable) const {
617   return lp_solution_[gtl::FindOrDie(mirror_lp_variable_, variable).value()];
618 }
619 
GetSolutionReducedCost(IntegerVariable variable) const620 double LinearProgrammingConstraint::GetSolutionReducedCost(
621     IntegerVariable variable) const {
622   return lp_reduced_cost_[gtl::FindOrDie(mirror_lp_variable_, variable)
623                               .value()];
624 }
625 
UpdateBoundsOfLpVariables()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 
SolveLp()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 
666   if (simplex_.GetProblemStatus() == glop::ProblemStatus::OPTIMAL) {
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 
AddCutFromConstraints(const std::string & name,const std::vector<std::pair<RowIndex,IntegerValue>> & integer_multipliers)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) {
763       CHECK(VariableIsPositive(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 approach 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 = 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 
PostprocessAndAddCut(const std::string & name,const std::string & info,IntegerVariable first_new_var,IntegerVariable first_slack,const std::vector<ImpliedBoundsProcessor::SlackInfo> & ib_slack_infos,LinearConstraint * cut)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.
AddCGCuts()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.
RandomPick(const std::vector<RowIndex> & a,const std::vector<RowIndex> & b,ModelRandomGenerator * random,std::vector<std::pair<RowIndex,RowIndex>> * 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>
GetCoeff(ColIndex col,const ListOfTerms & terms)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 // Because we know the objective is integer, the constraint objective >= lb can
1060 // sometime cut the current lp optimal, and it can make a big difference to add
1061 // it. Or at least use it when constructing more advanced cuts. See
1062 // 'multisetcover_batch_0_case_115_instance_0_small_subset_elements_3_sumreqs
1063 //  _1295_candidates_41.fzn'
1064 //
1065 // TODO(user): It might be better to just integrate this with the MIR code so
1066 // that we not only consider MIR1 involving the objective but we also consider
1067 // combining it with other constraints.
AddObjectiveCut()1068 void LinearProgrammingConstraint::AddObjectiveCut() {
1069   if (integer_objective_.size() <= 1) return;
1070 
1071   // Clear temp data.
1072   tmp_lp_values_.clear();
1073   tmp_var_lbs_.clear();
1074   tmp_var_ubs_.clear();
1075   cut_.Clear();
1076 
1077   // We negate everything to have a <= base constraint.
1078   cut_.lb = kMinIntegerValue;
1079   cut_.ub = integer_objective_offset_ -
1080             integer_trail_->LevelZeroLowerBound(objective_cp_);
1081   for (const auto& [col, coeff] : integer_objective_) {
1082     const IntegerVariable var = integer_variables_[col.value()];
1083     cut_.vars.push_back(var);
1084     tmp_lp_values_.push_back(expanded_lp_solution_[var]);
1085     tmp_var_lbs_.push_back(integer_trail_->LevelZeroLowerBound(var));
1086     tmp_var_ubs_.push_back(integer_trail_->LevelZeroUpperBound(var));
1087     cut_.coeffs.push_back(-coeff);
1088   }
1089 
1090   // Because the objective has often large coefficient, we always try a MIR1
1091   // like heuristic to round it to reasonable values.
1092   RoundingOptions options;
1093   options.max_scaling = parameters_.max_integer_rounding_scaling();
1094   integer_rounding_cut_helper_.ComputeCut(options, tmp_lp_values_, tmp_var_lbs_,
1095                                           tmp_var_ubs_,
1096                                           &implied_bounds_processor_, &cut_);
1097 
1098   // Note that the cut will not be added if it is not good enough.
1099   constraint_manager_.AddCut(cut_, "Objective", expanded_lp_solution_);
1100 }
1101 
AddMirCuts()1102 void LinearProgrammingConstraint::AddMirCuts() {
1103   // Heuristic to generate MIR_n cuts by combining a small number of rows. This
1104   // works greedily and follow more or less the MIR cut description in the
1105   // literature. We have a current cut, and we add one more row to it while
1106   // eliminating a variable of the current cut whose LP value is far from its
1107   // bound.
1108   //
1109   // A notable difference is that we randomize the variable we eliminate and
1110   // the row we use to do so. We still have weights to indicate our preferred
1111   // choices. This allows to generate different cuts when called again and
1112   // again.
1113   //
1114   // TODO(user): We could combine n rows to make sure we eliminate n variables
1115   // far away from their bounds by solving exactly in integer small linear
1116   // system.
1117   absl::StrongVector<ColIndex, IntegerValue> dense_cut(
1118       integer_variables_.size(), IntegerValue(0));
1119   SparseBitset<ColIndex> non_zeros_(ColIndex(integer_variables_.size()));
1120 
1121   // We compute all the rows that are tight, these will be used as the base row
1122   // for the MIR_n procedure below.
1123   const RowIndex num_rows = lp_data_.num_constraints();
1124   std::vector<std::pair<RowIndex, IntegerValue>> base_rows;
1125   absl::StrongVector<RowIndex, double> row_weights(num_rows.value(), 0.0);
1126   for (RowIndex row(0); row < num_rows; ++row) {
1127     const auto status = simplex_.GetConstraintStatus(row);
1128     if (status == glop::ConstraintStatus::BASIC) continue;
1129     if (status == glop::ConstraintStatus::FREE) continue;
1130 
1131     if (status == glop::ConstraintStatus::AT_UPPER_BOUND ||
1132         status == glop::ConstraintStatus::FIXED_VALUE) {
1133       base_rows.push_back({row, IntegerValue(1)});
1134     }
1135     if (status == glop::ConstraintStatus::AT_LOWER_BOUND ||
1136         status == glop::ConstraintStatus::FIXED_VALUE) {
1137       base_rows.push_back({row, IntegerValue(-1)});
1138     }
1139 
1140     // For now, we use the dual values for the row "weights".
1141     //
1142     // Note that we use the dual at LP scale so that it make more sense when we
1143     // compare different rows since the LP has been scaled.
1144     //
1145     // TODO(user): In Kati Wolter PhD "Implementation of Cutting Plane
1146     // Separators for Mixed Integer Programs" which describe SCIP's MIR cuts
1147     // implementation (or at least an early version of it), a more complex score
1148     // is used.
1149     //
1150     // Note(user): Because we only consider tight rows under the current lp
1151     // solution (i.e. non-basic rows), most should have a non-zero dual values.
1152     // But there is some degenerate problem where these rows have a really low
1153     // weight (or even zero), and having only weight of exactly zero in
1154     // std::discrete_distribution will result in a crash.
1155     row_weights[row] = std::max(1e-8, std::abs(simplex_.GetDualValue(row)));
1156   }
1157 
1158   std::vector<double> weights;
1159   absl::StrongVector<RowIndex, bool> used_rows;
1160   std::vector<std::pair<RowIndex, IntegerValue>> integer_multipliers;
1161   for (const std::pair<RowIndex, IntegerValue>& entry : base_rows) {
1162     if (time_limit_->LimitReached()) break;
1163 
1164     // First try to generate a cut directly from this base row (MIR1).
1165     //
1166     // Note(user): We abort on success like it seems to be done in the
1167     // literature. Note that we don't succeed that often in generating an
1168     // efficient cut, so I am not sure aborting will make a big difference
1169     // speedwise. We might generate similar cuts though, but hopefully the cut
1170     // management can deal with that.
1171     integer_multipliers = {entry};
1172     if (AddCutFromConstraints("MIR_1", integer_multipliers)) {
1173       continue;
1174     }
1175 
1176     // Cleanup.
1177     for (const ColIndex col : non_zeros_.PositionsSetAtLeastOnce()) {
1178       dense_cut[col] = IntegerValue(0);
1179     }
1180     non_zeros_.SparseClearAll();
1181 
1182     // Copy cut.
1183     const IntegerValue multiplier = entry.second;
1184     for (const std::pair<ColIndex, IntegerValue> term :
1185          integer_lp_[entry.first].terms) {
1186       const ColIndex col = term.first;
1187       const IntegerValue coeff = term.second;
1188       non_zeros_.Set(col);
1189       dense_cut[col] += coeff * multiplier;
1190     }
1191 
1192     used_rows.assign(num_rows.value(), false);
1193     used_rows[entry.first] = true;
1194 
1195     // We will aggregate at most kMaxAggregation more rows.
1196     //
1197     // TODO(user): optim + tune.
1198     const int kMaxAggregation = 5;
1199     for (int i = 0; i < kMaxAggregation; ++i) {
1200       // First pick a variable to eliminate. We currently pick a random one with
1201       // a weight that depend on how far it is from its closest bound.
1202       IntegerValue max_magnitude(0);
1203       weights.clear();
1204       std::vector<ColIndex> col_candidates;
1205       for (const ColIndex col : non_zeros_.PositionsSetAtLeastOnce()) {
1206         if (dense_cut[col] == 0) continue;
1207 
1208         max_magnitude = std::max(max_magnitude, IntTypeAbs(dense_cut[col]));
1209         const int col_degree =
1210             lp_data_.GetSparseColumn(col).num_entries().value();
1211         if (col_degree <= 1) continue;
1212         if (simplex_.GetVariableStatus(col) != glop::VariableStatus::BASIC) {
1213           continue;
1214         }
1215 
1216         const IntegerVariable var = integer_variables_[col.value()];
1217         const double lp_value = expanded_lp_solution_[var];
1218         const double lb = ToDouble(integer_trail_->LevelZeroLowerBound(var));
1219         const double ub = ToDouble(integer_trail_->LevelZeroUpperBound(var));
1220         const double bound_distance = std::min(ub - lp_value, lp_value - lb);
1221         if (bound_distance > 1e-2) {
1222           weights.push_back(bound_distance);
1223           col_candidates.push_back(col);
1224         }
1225       }
1226       if (col_candidates.empty()) break;
1227 
1228       const ColIndex var_to_eliminate =
1229           col_candidates[std::discrete_distribution<>(weights.begin(),
1230                                                       weights.end())(*random_)];
1231 
1232       // What rows can we add to eliminate var_to_eliminate?
1233       std::vector<RowIndex> possible_rows;
1234       weights.clear();
1235       for (const auto entry : lp_data_.GetSparseColumn(var_to_eliminate)) {
1236         const RowIndex row = entry.row();
1237         const auto status = simplex_.GetConstraintStatus(row);
1238         if (status == glop::ConstraintStatus::BASIC) continue;
1239         if (status == glop::ConstraintStatus::FREE) continue;
1240 
1241         // We disallow all the rows that contain a variable that we already
1242         // eliminated (or are about to). This mean that we choose rows that
1243         // form a "triangular" matrix on the position we choose to eliminate.
1244         if (used_rows[row]) continue;
1245         used_rows[row] = true;
1246 
1247         // TODO(user): Instead of using FIXED_VALUE consider also both direction
1248         // when we almost have an equality? that is if the LP constraints bounds
1249         // are close from each others (<1e-6 ?). Initial experiments shows it
1250         // doesn't change much, so I kept this version for now. Note that it
1251         // might just be better to use the side that constrain the current lp
1252         // optimal solution (that we get from the status).
1253         bool add_row = false;
1254         if (status == glop::ConstraintStatus::FIXED_VALUE ||
1255             status == glop::ConstraintStatus::AT_UPPER_BOUND) {
1256           if (entry.coefficient() > 0.0) {
1257             if (dense_cut[var_to_eliminate] < 0) add_row = true;
1258           } else {
1259             if (dense_cut[var_to_eliminate] > 0) add_row = true;
1260           }
1261         }
1262         if (status == glop::ConstraintStatus::FIXED_VALUE ||
1263             status == glop::ConstraintStatus::AT_LOWER_BOUND) {
1264           if (entry.coefficient() > 0.0) {
1265             if (dense_cut[var_to_eliminate] > 0) add_row = true;
1266           } else {
1267             if (dense_cut[var_to_eliminate] < 0) add_row = true;
1268           }
1269         }
1270         if (add_row) {
1271           possible_rows.push_back(row);
1272           weights.push_back(row_weights[row]);
1273         }
1274       }
1275       if (possible_rows.empty()) break;
1276 
1277       const RowIndex row_to_combine =
1278           possible_rows[std::discrete_distribution<>(weights.begin(),
1279                                                      weights.end())(*random_)];
1280       const IntegerValue to_combine_coeff =
1281           GetCoeff(var_to_eliminate, integer_lp_[row_to_combine].terms);
1282       CHECK_NE(to_combine_coeff, 0);
1283 
1284       IntegerValue mult1 = -to_combine_coeff;
1285       IntegerValue mult2 = dense_cut[var_to_eliminate];
1286       CHECK_NE(mult2, 0);
1287       if (mult1 < 0) {
1288         mult1 = -mult1;
1289         mult2 = -mult2;
1290       }
1291 
1292       const IntegerValue gcd = IntegerValue(
1293           MathUtil::GCD64(std::abs(mult1.value()), std::abs(mult2.value())));
1294       CHECK_NE(gcd, 0);
1295       mult1 /= gcd;
1296       mult2 /= gcd;
1297 
1298       // Overflow detection.
1299       //
1300       // TODO(user): do that in the possible_rows selection? only problem is
1301       // that we do not have the integer coefficient there...
1302       for (std::pair<RowIndex, IntegerValue>& entry : integer_multipliers) {
1303         max_magnitude = std::max(max_magnitude, IntTypeAbs(entry.second));
1304       }
1305       if (CapAdd(CapProd(max_magnitude.value(), std::abs(mult1.value())),
1306                  CapProd(infinity_norms_[row_to_combine].value(),
1307                          std::abs(mult2.value()))) ==
1308           std::numeric_limits<int64_t>::max()) {
1309         break;
1310       }
1311 
1312       for (std::pair<RowIndex, IntegerValue>& entry : integer_multipliers) {
1313         entry.second *= mult1;
1314       }
1315       integer_multipliers.push_back({row_to_combine, mult2});
1316 
1317       // TODO(user): Not supper efficient to recombine the rows.
1318       if (AddCutFromConstraints(absl::StrCat("MIR_", i + 2),
1319                                 integer_multipliers)) {
1320         break;
1321       }
1322 
1323       // Minor optim: the computation below is only needed if we do one more
1324       // iteration.
1325       if (i + 1 == kMaxAggregation) break;
1326 
1327       for (ColIndex col : non_zeros_.PositionsSetAtLeastOnce()) {
1328         dense_cut[col] *= mult1;
1329       }
1330       for (const std::pair<ColIndex, IntegerValue> term :
1331            integer_lp_[row_to_combine].terms) {
1332         const ColIndex col = term.first;
1333         const IntegerValue coeff = term.second;
1334         non_zeros_.Set(col);
1335         dense_cut[col] += coeff * mult2;
1336       }
1337     }
1338   }
1339 }
1340 
AddZeroHalfCuts()1341 void LinearProgrammingConstraint::AddZeroHalfCuts() {
1342   if (time_limit_->LimitReached()) return;
1343 
1344   tmp_lp_values_.clear();
1345   tmp_var_lbs_.clear();
1346   tmp_var_ubs_.clear();
1347   for (const IntegerVariable var : integer_variables_) {
1348     tmp_lp_values_.push_back(expanded_lp_solution_[var]);
1349     tmp_var_lbs_.push_back(integer_trail_->LevelZeroLowerBound(var));
1350     tmp_var_ubs_.push_back(integer_trail_->LevelZeroUpperBound(var));
1351   }
1352 
1353   // TODO(user): See if it make sense to try to use implied bounds there.
1354   zero_half_cut_helper_.ProcessVariables(tmp_lp_values_, tmp_var_lbs_,
1355                                          tmp_var_ubs_);
1356   for (glop::RowIndex row(0); row < integer_lp_.size(); ++row) {
1357     // Even though we could use non-tight row, for now we prefer to use tight
1358     // ones.
1359     const auto status = simplex_.GetConstraintStatus(row);
1360     if (status == glop::ConstraintStatus::BASIC) continue;
1361     if (status == glop::ConstraintStatus::FREE) continue;
1362 
1363     zero_half_cut_helper_.AddOneConstraint(
1364         row, integer_lp_[row].terms, integer_lp_[row].lb, integer_lp_[row].ub);
1365   }
1366   for (const std::vector<std::pair<RowIndex, IntegerValue>>& multipliers :
1367        zero_half_cut_helper_.InterestingCandidates(random_)) {
1368     if (time_limit_->LimitReached()) break;
1369 
1370     // TODO(user): Make sure that if the resulting linear coefficients are not
1371     // too high, we do try a "divisor" of two and thus try a true zero-half cut
1372     // instead of just using our best MIR heuristic (which might still be better
1373     // though).
1374     AddCutFromConstraints("ZERO_HALF", multipliers);
1375   }
1376 }
1377 
UpdateSimplexIterationLimit(const int64_t min_iter,const int64_t max_iter)1378 void LinearProgrammingConstraint::UpdateSimplexIterationLimit(
1379     const int64_t min_iter, const int64_t max_iter) {
1380   if (parameters_.linearization_level() < 2) return;
1381   const int64_t num_degenerate_columns = CalculateDegeneracy();
1382   const int64_t num_cols = simplex_.GetProblemNumCols().value();
1383   if (num_cols <= 0) {
1384     return;
1385   }
1386   CHECK_GT(num_cols, 0);
1387   const int64_t decrease_factor = (10 * num_degenerate_columns) / num_cols;
1388   if (simplex_.GetProblemStatus() == glop::ProblemStatus::DUAL_FEASIBLE) {
1389     // We reached here probably because we predicted wrong. We use this as a
1390     // signal to increase the iterations or punish less for degeneracy compare
1391     // to the other part.
1392     if (is_degenerate_) {
1393       next_simplex_iter_ /= std::max(int64_t{1}, decrease_factor);
1394     } else {
1395       next_simplex_iter_ *= 2;
1396     }
1397   } else if (simplex_.GetProblemStatus() == glop::ProblemStatus::OPTIMAL) {
1398     if (is_degenerate_) {
1399       next_simplex_iter_ /= std::max(int64_t{1}, 2 * decrease_factor);
1400     } else {
1401       // This is the most common case. We use the size of the problem to
1402       // determine the limit and ignore the previous limit.
1403       next_simplex_iter_ = num_cols / 40;
1404     }
1405   }
1406   next_simplex_iter_ =
1407       std::max(min_iter, std::min(max_iter, next_simplex_iter_));
1408 }
1409 
Propagate()1410 bool LinearProgrammingConstraint::Propagate() {
1411   UpdateBoundsOfLpVariables();
1412 
1413   // TODO(user): It seems the time we loose by not stopping early might be worth
1414   // it because we end up with a better explanation at optimality.
1415   glop::GlopParameters parameters = simplex_.GetParameters();
1416   if (/* DISABLES CODE */ (false) && objective_is_defined_) {
1417     // We put a limit on the dual objective since there is no point increasing
1418     // it past our current objective upper-bound (we will already fail as soon
1419     // as we pass it). Note that this limit is properly transformed using the
1420     // objective scaling factor and offset stored in lp_data_.
1421     //
1422     // Note that we use a bigger epsilon here to be sure that if we abort
1423     // because of this, we will report a conflict.
1424     parameters.set_objective_upper_limit(
1425         static_cast<double>(integer_trail_->UpperBound(objective_cp_).value() +
1426                             100.0 * kCpEpsilon));
1427   }
1428 
1429   // Put an iteration limit on the work we do in the simplex for this call. Note
1430   // that because we are "incremental", even if we don't solve it this time we
1431   // will make progress towards a solve in the lower node of the tree search.
1432   if (trail_->CurrentDecisionLevel() == 0) {
1433     // TODO(user): Dynamically change the iteration limit for root node as
1434     // well.
1435     parameters.set_max_number_of_iterations(2000);
1436   } else {
1437     parameters.set_max_number_of_iterations(next_simplex_iter_);
1438   }
1439   if (parameters_.use_exact_lp_reason()) {
1440     parameters.set_change_status_to_imprecise(false);
1441     parameters.set_primal_feasibility_tolerance(1e-7);
1442     parameters.set_dual_feasibility_tolerance(1e-7);
1443   }
1444 
1445   simplex_.SetParameters(parameters);
1446   simplex_.NotifyThatMatrixIsUnchangedForNextSolve();
1447   if (!SolveLp()) return true;
1448 
1449   // Add new constraints to the LP and resolve?
1450   const int max_cuts_rounds =
1451       parameters_.cut_level() <= 0
1452           ? 0
1453           : (trail_->CurrentDecisionLevel() == 0
1454                  ? parameters_.max_cut_rounds_at_level_zero()
1455                  : 1);
1456   int cuts_round = 0;
1457   while (simplex_.GetProblemStatus() == glop::ProblemStatus::OPTIMAL &&
1458          cuts_round < max_cuts_rounds) {
1459     // We wait for the first batch of problem constraints to be added before we
1460     // begin to generate cuts. Note that we rely on num_solves_ since on some
1461     // problems there is no other constriants than the cuts.
1462     cuts_round++;
1463     if (num_solves_ > 1) {
1464       // This must be called first.
1465       implied_bounds_processor_.RecomputeCacheAndSeparateSomeImpliedBoundCuts(
1466           expanded_lp_solution_);
1467 
1468       // The "generic" cuts are currently part of this class as they are using
1469       // data from the current LP.
1470       //
1471       // TODO(user): Refactor so that they are just normal cut generators?
1472       if (trail_->CurrentDecisionLevel() == 0) {
1473         if (parameters_.add_objective_cut()) AddObjectiveCut();
1474         if (parameters_.add_mir_cuts()) AddMirCuts();
1475         if (parameters_.add_cg_cuts()) AddCGCuts();
1476         if (parameters_.add_zero_half_cuts()) AddZeroHalfCuts();
1477       }
1478 
1479       // Try to add cuts.
1480       if (!cut_generators_.empty() &&
1481           (trail_->CurrentDecisionLevel() == 0 ||
1482            !parameters_.only_add_cuts_at_level_zero())) {
1483         for (const CutGenerator& generator : cut_generators_) {
1484           if (!generator.generate_cuts(expanded_lp_solution_,
1485                                        &constraint_manager_)) {
1486             return false;
1487           }
1488         }
1489       }
1490 
1491       implied_bounds_processor_.IbCutPool().TransferToManager(
1492           expanded_lp_solution_, &constraint_manager_);
1493     }
1494 
1495     glop::BasisState state = simplex_.GetState();
1496     if (constraint_manager_.ChangeLp(expanded_lp_solution_, &state)) {
1497       simplex_.LoadStateForNextSolve(state);
1498       if (!CreateLpFromConstraintManager()) {
1499         return integer_trail_->ReportConflict({});
1500       }
1501       const double old_obj = simplex_.GetObjectiveValue();
1502       if (!SolveLp()) return true;
1503       if (simplex_.GetProblemStatus() == glop::ProblemStatus::OPTIMAL) {
1504         VLOG(1) << "Relaxation improvement " << old_obj << " -> "
1505                 << simplex_.GetObjectiveValue()
1506                 << " diff: " << simplex_.GetObjectiveValue() - old_obj
1507                 << " level: " << trail_->CurrentDecisionLevel();
1508       }
1509     } else {
1510       if (trail_->CurrentDecisionLevel() == 0) {
1511         lp_at_level_zero_is_final_ = true;
1512       }
1513       break;
1514     }
1515   }
1516 
1517   // A dual-unbounded problem is infeasible. We use the dual ray reason.
1518   if (simplex_.GetProblemStatus() == glop::ProblemStatus::DUAL_UNBOUNDED) {
1519     if (parameters_.use_exact_lp_reason()) {
1520       if (!FillExactDualRayReason()) return true;
1521     } else {
1522       FillReducedCostReasonIn(simplex_.GetDualRayRowCombination(),
1523                               &integer_reason_);
1524     }
1525     return integer_trail_->ReportConflict(integer_reason_);
1526   }
1527 
1528   // TODO(user): Update limits for DUAL_UNBOUNDED status as well.
1529   UpdateSimplexIterationLimit(/*min_iter=*/10, /*max_iter=*/1000);
1530 
1531   // Optimality deductions if problem has an objective.
1532   if (objective_is_defined_ &&
1533       (simplex_.GetProblemStatus() == glop::ProblemStatus::OPTIMAL ||
1534        simplex_.GetProblemStatus() == glop::ProblemStatus::DUAL_FEASIBLE)) {
1535     // TODO(user): Maybe do a bit less computation when we cannot propagate
1536     // anything.
1537     if (parameters_.use_exact_lp_reason()) {
1538       if (!ExactLpReasonning()) return false;
1539 
1540       // Display when the inexact bound would have propagated more.
1541       if (VLOG_IS_ON(2)) {
1542         const double relaxed_optimal_objective = simplex_.GetObjectiveValue();
1543         const IntegerValue approximate_new_lb(static_cast<int64_t>(
1544             std::ceil(relaxed_optimal_objective - kCpEpsilon)));
1545         const IntegerValue propagated_lb =
1546             integer_trail_->LowerBound(objective_cp_);
1547         if (approximate_new_lb > propagated_lb) {
1548           VLOG(2) << "LP objective [ " << ToDouble(propagated_lb) << ", "
1549                   << ToDouble(integer_trail_->UpperBound(objective_cp_))
1550                   << " ] approx_lb += "
1551                   << ToDouble(approximate_new_lb - propagated_lb) << " gap: "
1552                   << integer_trail_->UpperBound(objective_cp_) - propagated_lb;
1553         }
1554       }
1555     } else {
1556       // Try to filter optimal objective value. Note that GetObjectiveValue()
1557       // already take care of the scaling so that it returns an objective in the
1558       // CP world.
1559       FillReducedCostReasonIn(simplex_.GetReducedCosts(), &integer_reason_);
1560       const double objective_cp_ub =
1561           ToDouble(integer_trail_->UpperBound(objective_cp_));
1562       const double relaxed_optimal_objective = simplex_.GetObjectiveValue();
1563       ReducedCostStrengtheningDeductions(objective_cp_ub -
1564                                          relaxed_optimal_objective);
1565       if (!deductions_.empty()) {
1566         deductions_reason_ = integer_reason_;
1567         deductions_reason_.push_back(
1568             integer_trail_->UpperBoundAsLiteral(objective_cp_));
1569       }
1570 
1571       // Push new objective lb.
1572       const IntegerValue approximate_new_lb(static_cast<int64_t>(
1573           std::ceil(relaxed_optimal_objective - kCpEpsilon)));
1574       if (approximate_new_lb > integer_trail_->LowerBound(objective_cp_)) {
1575         const IntegerLiteral deduction =
1576             IntegerLiteral::GreaterOrEqual(objective_cp_, approximate_new_lb);
1577         if (!integer_trail_->Enqueue(deduction, {}, integer_reason_)) {
1578           return false;
1579         }
1580       }
1581 
1582       // Push reduced cost strengthening bounds.
1583       if (!deductions_.empty()) {
1584         const int trail_index_with_same_reason = integer_trail_->Index();
1585         for (const IntegerLiteral deduction : deductions_) {
1586           if (!integer_trail_->Enqueue(deduction, {}, deductions_reason_,
1587                                        trail_index_with_same_reason)) {
1588             return false;
1589           }
1590         }
1591       }
1592     }
1593   }
1594 
1595   // Copy more info about the current solution.
1596   if (simplex_.GetProblemStatus() == glop::ProblemStatus::OPTIMAL) {
1597     CHECK(lp_solution_is_set_);
1598 
1599     lp_objective_ = simplex_.GetObjectiveValue();
1600     lp_solution_is_integer_ = true;
1601     const int num_vars = integer_variables_.size();
1602     for (int i = 0; i < num_vars; i++) {
1603       lp_reduced_cost_[i] = scaler_.UnscaleReducedCost(
1604           glop::ColIndex(i), simplex_.GetReducedCost(glop::ColIndex(i)));
1605       if (std::abs(lp_solution_[i] - std::round(lp_solution_[i])) >
1606           kCpEpsilon) {
1607         lp_solution_is_integer_ = false;
1608       }
1609     }
1610 
1611     if (compute_reduced_cost_averages_) {
1612       UpdateAverageReducedCosts();
1613     }
1614   }
1615 
1616   if (parameters_.use_branching_in_lp() && objective_is_defined_ &&
1617       trail_->CurrentDecisionLevel() == 0 && !is_degenerate_ &&
1618       lp_solution_is_set_ && !lp_solution_is_integer_ &&
1619       parameters_.linearization_level() >= 2 &&
1620       compute_reduced_cost_averages_ &&
1621       simplex_.GetProblemStatus() == glop::ProblemStatus::OPTIMAL) {
1622     count_since_last_branching_++;
1623     if (count_since_last_branching_ < branching_frequency_) {
1624       return true;
1625     }
1626     count_since_last_branching_ = 0;
1627     bool branching_successful = false;
1628 
1629     // Strong branching on top max_num_branches variable.
1630     const int max_num_branches = 3;
1631     const int num_vars = integer_variables_.size();
1632     std::vector<std::pair<double, IntegerVariable>> branching_vars;
1633     for (int i = 0; i < num_vars; ++i) {
1634       const IntegerVariable var = integer_variables_[i];
1635       const IntegerVariable positive_var = PositiveVariable(var);
1636 
1637       // Skip non fractional variables.
1638       const double current_value = GetSolutionValue(positive_var);
1639       if (std::abs(current_value - std::round(current_value)) <= kCpEpsilon) {
1640         continue;
1641       }
1642 
1643       // Skip ignored variables.
1644       if (integer_trail_->IsCurrentlyIgnored(var)) continue;
1645 
1646       // We can use any metric to select a variable to branch on. Reduced cost
1647       // average is one of the most promissing metric. It captures the history
1648       // of the objective bound improvement in LP due to changes in the given
1649       // variable bounds.
1650       //
1651       // NOTE: We also experimented using PseudoCosts and most recent reduced
1652       // cost as metrics but it doesn't give better results on benchmarks.
1653       const double cost_i = rc_scores_[i];
1654       std::pair<double, IntegerVariable> branching_var =
1655           std::make_pair(-cost_i, positive_var);
1656       auto iterator = std::lower_bound(branching_vars.begin(),
1657                                        branching_vars.end(), branching_var);
1658 
1659       branching_vars.insert(iterator, branching_var);
1660       if (branching_vars.size() > max_num_branches) {
1661         branching_vars.resize(max_num_branches);
1662       }
1663     }
1664 
1665     for (const std::pair<double, IntegerVariable>& branching_var :
1666          branching_vars) {
1667       const IntegerVariable positive_var = branching_var.second;
1668       VLOG(2) << "Branching on: " << positive_var;
1669       if (BranchOnVar(positive_var)) {
1670         VLOG(2) << "Branching successful.";
1671         branching_successful = true;
1672       } else {
1673         break;
1674       }
1675     }
1676     if (!branching_successful) {
1677       branching_frequency_ *= 2;
1678     }
1679   }
1680   return true;
1681 }
1682 
1683 // Returns kMinIntegerValue in case of overflow.
1684 //
1685 // TODO(user): Because of PreventOverflow(), this should actually never happen.
GetImpliedLowerBound(const LinearConstraint & terms) const1686 IntegerValue LinearProgrammingConstraint::GetImpliedLowerBound(
1687     const LinearConstraint& terms) const {
1688   IntegerValue lower_bound(0);
1689   const int size = terms.vars.size();
1690   for (int i = 0; i < size; ++i) {
1691     const IntegerVariable var = terms.vars[i];
1692     const IntegerValue coeff = terms.coeffs[i];
1693     CHECK_NE(coeff, 0);
1694     const IntegerValue bound = coeff > 0 ? integer_trail_->LowerBound(var)
1695                                          : integer_trail_->UpperBound(var);
1696     if (!AddProductTo(bound, coeff, &lower_bound)) return kMinIntegerValue;
1697   }
1698   return lower_bound;
1699 }
1700 
PossibleOverflow(const LinearConstraint & constraint)1701 bool LinearProgrammingConstraint::PossibleOverflow(
1702     const LinearConstraint& constraint) {
1703   IntegerValue lower_bound(0);
1704   const int size = constraint.vars.size();
1705   for (int i = 0; i < size; ++i) {
1706     const IntegerVariable var = constraint.vars[i];
1707     const IntegerValue coeff = constraint.coeffs[i];
1708     CHECK_NE(coeff, 0);
1709     const IntegerValue bound = coeff > 0
1710                                    ? integer_trail_->LevelZeroLowerBound(var)
1711                                    : integer_trail_->LevelZeroUpperBound(var);
1712     if (!AddProductTo(bound, coeff, &lower_bound)) {
1713       return true;
1714     }
1715   }
1716   const int64_t slack = CapAdd(lower_bound.value(), -constraint.ub.value());
1717   if (slack == std::numeric_limits<int64_t>::min() ||
1718       slack == std::numeric_limits<int64_t>::max()) {
1719     return true;
1720   }
1721   return false;
1722 }
1723 
1724 namespace {
1725 
FloorRatio128(absl::int128 x,IntegerValue positive_div)1726 absl::int128 FloorRatio128(absl::int128 x, IntegerValue positive_div) {
1727   absl::int128 div128(positive_div.value());
1728   absl::int128 result = x / div128;
1729   if (result * div128 > x) return result - 1;
1730   return result;
1731 }
1732 
1733 }  // namespace
1734 
PreventOverflow(LinearConstraint * constraint,int max_pow)1735 void LinearProgrammingConstraint::PreventOverflow(LinearConstraint* constraint,
1736                                                   int max_pow) {
1737   // First, make all coefficient positive.
1738   MakeAllCoefficientsPositive(constraint);
1739 
1740   // Compute the min/max possible partial sum. Note that we need to use the
1741   // level zero bounds here since we might use this cut after backtrack.
1742   double sum_min = std::min(0.0, ToDouble(-constraint->ub));
1743   double sum_max = std::max(0.0, ToDouble(-constraint->ub));
1744   const int size = constraint->vars.size();
1745   for (int i = 0; i < size; ++i) {
1746     const IntegerVariable var = constraint->vars[i];
1747     const double coeff = ToDouble(constraint->coeffs[i]);
1748     sum_min +=
1749         coeff *
1750         std::min(0.0, ToDouble(integer_trail_->LevelZeroLowerBound(var)));
1751     sum_max +=
1752         coeff *
1753         std::max(0.0, ToDouble(integer_trail_->LevelZeroUpperBound(var)));
1754   }
1755   const double max_value = std::max({sum_max, -sum_min, sum_max - sum_min});
1756 
1757   const IntegerValue divisor(std::ceil(std::ldexp(max_value, -max_pow)));
1758   if (divisor <= 1) return;
1759 
1760   // To be correct, we need to shift all variable so that they are positive.
1761   //
1762   // Important: One might be tempted to think that using the current variable
1763   // bounds is okay here since we only use this to derive cut/constraint that
1764   // only needs to be locally valid. However, in some corner cases (like when
1765   // one term become zero), we might loose the fact that we used one of the
1766   // variable bound to derive the new constraint, so we will miss it in the
1767   // explanation !!
1768   //
1769   // TODO(user): This code is tricky and similar to the one to generate cuts.
1770   // Test and may reduce the duplication? note however that here we use int128
1771   // to deal with potential overflow.
1772   int new_size = 0;
1773   absl::int128 adjust = 0;
1774   for (int i = 0; i < size; ++i) {
1775     const IntegerValue old_coeff = constraint->coeffs[i];
1776     const IntegerValue new_coeff = FloorRatio(old_coeff, divisor);
1777 
1778     // Compute the rhs adjustement.
1779     const absl::int128 remainder =
1780         absl::int128(old_coeff.value()) -
1781         absl::int128(new_coeff.value()) * absl::int128(divisor.value());
1782     adjust +=
1783         remainder *
1784         absl::int128(
1785             integer_trail_->LevelZeroLowerBound(constraint->vars[i]).value());
1786 
1787     if (new_coeff == 0) continue;
1788     constraint->vars[new_size] = constraint->vars[i];
1789     constraint->coeffs[new_size] = new_coeff;
1790     ++new_size;
1791   }
1792   constraint->vars.resize(new_size);
1793   constraint->coeffs.resize(new_size);
1794 
1795   constraint->ub = IntegerValue(static_cast<int64_t>(
1796       FloorRatio128(absl::int128(constraint->ub.value()) - adjust, divisor)));
1797 }
1798 
1799 // TODO(user): combine this with RelaxLinearReason() to avoid the extra
1800 // magnitude vector and the weird precondition of RelaxLinearReason().
SetImpliedLowerBoundReason(const LinearConstraint & terms,IntegerValue slack)1801 void LinearProgrammingConstraint::SetImpliedLowerBoundReason(
1802     const LinearConstraint& terms, IntegerValue slack) {
1803   integer_reason_.clear();
1804   std::vector<IntegerValue> magnitudes;
1805   const int size = terms.vars.size();
1806   for (int i = 0; i < size; ++i) {
1807     const IntegerVariable var = terms.vars[i];
1808     const IntegerValue coeff = terms.coeffs[i];
1809     CHECK_NE(coeff, 0);
1810     if (coeff > 0) {
1811       magnitudes.push_back(coeff);
1812       integer_reason_.push_back(integer_trail_->LowerBoundAsLiteral(var));
1813     } else {
1814       magnitudes.push_back(-coeff);
1815       integer_reason_.push_back(integer_trail_->UpperBoundAsLiteral(var));
1816     }
1817   }
1818   CHECK_GE(slack, 0);
1819   if (slack > 0) {
1820     integer_trail_->RelaxLinearReason(slack, magnitudes, &integer_reason_);
1821   }
1822   integer_trail_->RemoveLevelZeroBounds(&integer_reason_);
1823 }
1824 
1825 std::vector<std::pair<RowIndex, IntegerValue>>
ScaleLpMultiplier(bool take_objective_into_account,const std::vector<std::pair<RowIndex,double>> & lp_multipliers,Fractional * scaling,int max_pow) const1826 LinearProgrammingConstraint::ScaleLpMultiplier(
1827     bool take_objective_into_account,
1828     const std::vector<std::pair<RowIndex, double>>& lp_multipliers,
1829     Fractional* scaling, int max_pow) const {
1830   double max_sum = 0.0;
1831   tmp_cp_multipliers_.clear();
1832   for (const std::pair<RowIndex, double>& p : lp_multipliers) {
1833     const RowIndex row = p.first;
1834     const Fractional lp_multi = p.second;
1835 
1836     // We ignore small values since these are likely errors and will not
1837     // contribute much to the new lp constraint anyway.
1838     if (std::abs(lp_multi) < kZeroTolerance) continue;
1839 
1840     // Remove trivial bad cases.
1841     //
1842     // TODO(user): It might be better (when possible) to use the OPTIMAL row
1843     // status since in most situation we do want the constraint we add to be
1844     // tight under the current LP solution. Only for infeasible problem we might
1845     // not have access to the status.
1846     if (lp_multi > 0.0 && integer_lp_[row].ub >= kMaxIntegerValue) {
1847       continue;
1848     }
1849     if (lp_multi < 0.0 && integer_lp_[row].lb <= kMinIntegerValue) {
1850       continue;
1851     }
1852 
1853     const Fractional cp_multi = scaler_.UnscaleDualValue(row, lp_multi);
1854     tmp_cp_multipliers_.push_back({row, cp_multi});
1855     max_sum += ToDouble(infinity_norms_[row]) * std::abs(cp_multi);
1856   }
1857 
1858   // This behave exactly like if we had another "objective" constraint with
1859   // an lp_multi of 1.0 and a cp_multi of 1.0.
1860   if (take_objective_into_account) {
1861     max_sum += ToDouble(objective_infinity_norm_);
1862   }
1863 
1864   *scaling = 1.0;
1865   std::vector<std::pair<RowIndex, IntegerValue>> integer_multipliers;
1866   if (max_sum == 0.0) {
1867     // Empty linear combinaison.
1868     return integer_multipliers;
1869   }
1870 
1871   // We want max_sum * scaling to be <= 2 ^ max_pow and fit on an int64_t.
1872   // We use a power of 2 as this seems to work better.
1873   const double threshold = std::ldexp(1, max_pow) / max_sum;
1874   if (threshold < 1.0) {
1875     // TODO(user): we currently do not support scaling down, so we just abort
1876     // in this case.
1877     return integer_multipliers;
1878   }
1879   while (2 * *scaling <= threshold) *scaling *= 2;
1880 
1881   // Scale the multipliers by *scaling.
1882   //
1883   // TODO(user): Maybe use int128 to avoid overflow?
1884   for (const auto entry : tmp_cp_multipliers_) {
1885     const IntegerValue coeff(std::round(entry.second * (*scaling)));
1886     if (coeff != 0) integer_multipliers.push_back({entry.first, coeff});
1887   }
1888   return integer_multipliers;
1889 }
1890 
ComputeNewLinearConstraint(const std::vector<std::pair<RowIndex,IntegerValue>> & integer_multipliers,ScatteredIntegerVector * scattered_vector,IntegerValue * upper_bound) const1891 bool LinearProgrammingConstraint::ComputeNewLinearConstraint(
1892     const std::vector<std::pair<RowIndex, IntegerValue>>& integer_multipliers,
1893     ScatteredIntegerVector* scattered_vector, IntegerValue* upper_bound) const {
1894   // Initialize the new constraint.
1895   *upper_bound = 0;
1896   scattered_vector->ClearAndResize(integer_variables_.size());
1897 
1898   // Compute the new constraint by taking the linear combination given by
1899   // integer_multipliers of the integer constraints in integer_lp_.
1900   for (const std::pair<RowIndex, IntegerValue> term : integer_multipliers) {
1901     const RowIndex row = term.first;
1902     const IntegerValue multiplier = term.second;
1903     CHECK_LT(row, integer_lp_.size());
1904 
1905     // Update the constraint.
1906     if (!scattered_vector->AddLinearExpressionMultiple(
1907             multiplier, integer_lp_[row].terms)) {
1908       return false;
1909     }
1910 
1911     // Update the upper bound.
1912     const IntegerValue bound =
1913         multiplier > 0 ? integer_lp_[row].ub : integer_lp_[row].lb;
1914     if (!AddProductTo(multiplier, bound, upper_bound)) return false;
1915   }
1916 
1917   return true;
1918 }
1919 
1920 // TODO(user): no need to update the multipliers.
AdjustNewLinearConstraint(std::vector<std::pair<glop::RowIndex,IntegerValue>> * integer_multipliers,ScatteredIntegerVector * scattered_vector,IntegerValue * upper_bound) const1921 void LinearProgrammingConstraint::AdjustNewLinearConstraint(
1922     std::vector<std::pair<glop::RowIndex, IntegerValue>>* integer_multipliers,
1923     ScatteredIntegerVector* scattered_vector, IntegerValue* upper_bound) const {
1924   const IntegerValue kMaxWantedCoeff(1e18);
1925   for (std::pair<RowIndex, IntegerValue>& term : *integer_multipliers) {
1926     const RowIndex row = term.first;
1927     const IntegerValue multiplier = term.second;
1928     if (multiplier == 0) continue;
1929 
1930     // We will only allow change of the form "multiplier += to_add" with to_add
1931     // in [-negative_limit, positive_limit].
1932     IntegerValue negative_limit = kMaxWantedCoeff;
1933     IntegerValue positive_limit = kMaxWantedCoeff;
1934 
1935     // Make sure we never change the sign of the multiplier, except if the
1936     // row is an equality in which case we don't care.
1937     if (integer_lp_[row].ub != integer_lp_[row].lb) {
1938       if (multiplier > 0) {
1939         negative_limit = std::min(negative_limit, multiplier);
1940       } else {
1941         positive_limit = std::min(positive_limit, -multiplier);
1942       }
1943     }
1944 
1945     // Make sure upper_bound + to_add * row_bound never overflow.
1946     const IntegerValue row_bound =
1947         multiplier > 0 ? integer_lp_[row].ub : integer_lp_[row].lb;
1948     if (row_bound != 0) {
1949       const IntegerValue limit1 = FloorRatio(
1950           std::max(IntegerValue(0), kMaxWantedCoeff - IntTypeAbs(*upper_bound)),
1951           IntTypeAbs(row_bound));
1952       const IntegerValue limit2 =
1953           FloorRatio(kMaxWantedCoeff, IntTypeAbs(row_bound));
1954       if ((*upper_bound > 0) == (row_bound > 0)) {  // Same sign.
1955         positive_limit = std::min(positive_limit, limit1);
1956         negative_limit = std::min(negative_limit, limit2);
1957       } else {
1958         negative_limit = std::min(negative_limit, limit1);
1959         positive_limit = std::min(positive_limit, limit2);
1960       }
1961     }
1962 
1963     // If we add the row to the scattered_vector, diff will indicate by how much
1964     // |upper_bound - ImpliedLB(scattered_vector)| will change. That correspond
1965     // to increasing the multiplier by 1.
1966     //
1967     // At this stage, we are not sure computing sum coeff * bound will not
1968     // overflow, so we use floating point numbers. It is fine to do so since
1969     // this is not directly involved in the actual exact constraint generation:
1970     // these variables are just used in an heuristic.
1971     double positive_diff = ToDouble(row_bound);
1972     double negative_diff = ToDouble(row_bound);
1973 
1974     // TODO(user): we could relax a bit some of the condition and allow a sign
1975     // change. It is just trickier to compute the diff when we allow such
1976     // changes.
1977     for (const auto entry : integer_lp_[row].terms) {
1978       const ColIndex col = entry.first;
1979       const IntegerValue coeff = entry.second;
1980       const IntegerValue abs_coef = IntTypeAbs(coeff);
1981       CHECK_NE(coeff, 0);
1982 
1983       const IntegerVariable var = integer_variables_[col.value()];
1984       const IntegerValue lb = integer_trail_->LowerBound(var);
1985       const IntegerValue ub = integer_trail_->UpperBound(var);
1986 
1987       // Moving a variable away from zero seems to improve the bound even
1988       // if it reduces the number of non-zero. Note that this is because of
1989       // this that positive_diff and negative_diff are not the same.
1990       const IntegerValue current = (*scattered_vector)[col];
1991       if (current == 0) {
1992         const IntegerValue overflow_limit(
1993             FloorRatio(kMaxWantedCoeff, abs_coef));
1994         positive_limit = std::min(positive_limit, overflow_limit);
1995         negative_limit = std::min(negative_limit, overflow_limit);
1996         if (coeff > 0) {
1997           positive_diff -= ToDouble(coeff) * ToDouble(lb);
1998           negative_diff -= ToDouble(coeff) * ToDouble(ub);
1999         } else {
2000           positive_diff -= ToDouble(coeff) * ToDouble(ub);
2001           negative_diff -= ToDouble(coeff) * ToDouble(lb);
2002         }
2003         continue;
2004       }
2005 
2006       // We don't want to change the sign of current (except if the variable is
2007       // fixed) or to have an overflow.
2008       //
2009       // Corner case:
2010       //  - IntTypeAbs(current) can be larger than kMaxWantedCoeff!
2011       //  - The code assumes that 2 * kMaxWantedCoeff do not overflow.
2012       const IntegerValue current_magnitude = IntTypeAbs(current);
2013       const IntegerValue other_direction_limit = FloorRatio(
2014           lb == ub
2015               ? kMaxWantedCoeff + std::min(current_magnitude,
2016                                            kMaxIntegerValue - kMaxWantedCoeff)
2017               : current_magnitude,
2018           abs_coef);
2019       const IntegerValue same_direction_limit(FloorRatio(
2020           std::max(IntegerValue(0), kMaxWantedCoeff - current_magnitude),
2021           abs_coef));
2022       if ((current > 0) == (coeff > 0)) {  // Same sign.
2023         negative_limit = std::min(negative_limit, other_direction_limit);
2024         positive_limit = std::min(positive_limit, same_direction_limit);
2025       } else {
2026         negative_limit = std::min(negative_limit, same_direction_limit);
2027         positive_limit = std::min(positive_limit, other_direction_limit);
2028       }
2029 
2030       // This is how diff change.
2031       const IntegerValue implied = current > 0 ? lb : ub;
2032       if (implied != 0) {
2033         positive_diff -= ToDouble(coeff) * ToDouble(implied);
2034         negative_diff -= ToDouble(coeff) * ToDouble(implied);
2035       }
2036     }
2037 
2038     // Only add a multiple of this row if it tighten the final constraint.
2039     // The positive_diff/negative_diff are supposed to be integer modulo the
2040     // double precision, so we only add a multiple if they seems far away from
2041     // zero.
2042     IntegerValue to_add(0);
2043     if (positive_diff <= -1.0 && positive_limit > 0) {
2044       to_add = positive_limit;
2045     }
2046     if (negative_diff >= 1.0 && negative_limit > 0) {
2047       // Pick this if it is better than the positive sign.
2048       if (to_add == 0 ||
2049           std::abs(ToDouble(negative_limit) * negative_diff) >
2050               std::abs(ToDouble(positive_limit) * positive_diff)) {
2051         to_add = -negative_limit;
2052       }
2053     }
2054     if (to_add != 0) {
2055       term.second += to_add;
2056       *upper_bound += to_add * row_bound;
2057 
2058       // TODO(user): we could avoid checking overflow here, but this is likely
2059       // not in the hot loop.
2060       CHECK(scattered_vector->AddLinearExpressionMultiple(
2061           to_add, integer_lp_[row].terms));
2062     }
2063   }
2064 }
2065 
2066 // The "exact" computation go as follow:
2067 //
2068 // Given any INTEGER linear combination of the LP constraints, we can create a
2069 // new integer constraint that is valid (its computation must not overflow
2070 // though). Lets call this "linear_combination <= ub". We can then always add to
2071 // it the inequality "objective_terms <= objective_var", so we get:
2072 // ImpliedLB(objective_terms + linear_combination) - ub <= objective_var.
2073 // where ImpliedLB() is computed from the variable current bounds.
2074 //
2075 // Now, if we use for the linear combination and approximation of the optimal
2076 // negated dual LP values (by scaling them and rounding them to integer), we
2077 // will get an EXACT objective lower bound that is more or less the same as the
2078 // inexact bound given by the LP relaxation. This allows to derive exact reasons
2079 // for any propagation done by this constraint.
ExactLpReasonning()2080 bool LinearProgrammingConstraint::ExactLpReasonning() {
2081   // Clear old reason and deductions.
2082   integer_reason_.clear();
2083   deductions_.clear();
2084   deductions_reason_.clear();
2085 
2086   // The row multipliers will be the negation of the LP duals.
2087   //
2088   // TODO(user): Provide and use a sparse API in Glop to get the duals.
2089   const RowIndex num_rows = simplex_.GetProblemNumRows();
2090   std::vector<std::pair<RowIndex, double>> lp_multipliers;
2091   for (RowIndex row(0); row < num_rows; ++row) {
2092     const double value = -simplex_.GetDualValue(row);
2093     if (std::abs(value) < kZeroTolerance) continue;
2094     lp_multipliers.push_back({row, value});
2095   }
2096 
2097   Fractional scaling;
2098   std::vector<std::pair<RowIndex, IntegerValue>> integer_multipliers =
2099       ScaleLpMultiplier(/*take_objective_into_account=*/true, lp_multipliers,
2100                         &scaling);
2101 
2102   IntegerValue rc_ub;
2103   if (!ComputeNewLinearConstraint(integer_multipliers, &tmp_scattered_vector_,
2104                                   &rc_ub)) {
2105     VLOG(1) << "Issue while computing the exact LP reason. Aborting.";
2106     return true;
2107   }
2108 
2109   // The "objective constraint" behave like if the unscaled cp multiplier was
2110   // 1.0, so we will multiply it by this number and add it to reduced_costs.
2111   const IntegerValue obj_scale(std::round(scaling));
2112   if (obj_scale == 0) {
2113     VLOG(1) << "Overflow during exact LP reasoning. scaling=" << scaling;
2114     return true;
2115   }
2116   CHECK(tmp_scattered_vector_.AddLinearExpressionMultiple(obj_scale,
2117                                                           integer_objective_));
2118   CHECK(AddProductTo(-obj_scale, integer_objective_offset_, &rc_ub));
2119   AdjustNewLinearConstraint(&integer_multipliers, &tmp_scattered_vector_,
2120                             &rc_ub);
2121 
2122   // Create the IntegerSumLE that will allow to propagate the objective and more
2123   // generally do the reduced cost fixing.
2124   LinearConstraint new_constraint;
2125   tmp_scattered_vector_.ConvertToLinearConstraint(integer_variables_, rc_ub,
2126                                                   &new_constraint);
2127   new_constraint.vars.push_back(objective_cp_);
2128   new_constraint.coeffs.push_back(-obj_scale);
2129   DivideByGCD(&new_constraint);
2130   PreventOverflow(&new_constraint);
2131   DCHECK(!PossibleOverflow(new_constraint));
2132   DCHECK(constraint_manager_.DebugCheckConstraint(new_constraint));
2133 
2134   // Corner case where prevent overflow removed all terms.
2135   if (new_constraint.vars.empty()) {
2136     trail_->MutableConflict()->clear();
2137     return new_constraint.ub >= 0;
2138   }
2139 
2140   IntegerSumLE* cp_constraint =
2141       new IntegerSumLE({}, new_constraint.vars, new_constraint.coeffs,
2142                        new_constraint.ub, model_);
2143   if (trail_->CurrentDecisionLevel() == 0) {
2144     // Since we will never ask the reason for a constraint at level 0, we just
2145     // keep the last one.
2146     optimal_constraints_.clear();
2147   }
2148   optimal_constraints_.emplace_back(cp_constraint);
2149   rev_optimal_constraints_size_ = optimal_constraints_.size();
2150   if (!cp_constraint->PropagateAtLevelZero()) return false;
2151   return cp_constraint->Propagate();
2152 }
2153 
FillExactDualRayReason()2154 bool LinearProgrammingConstraint::FillExactDualRayReason() {
2155   Fractional scaling;
2156   const glop::DenseColumn ray = simplex_.GetDualRay();
2157   std::vector<std::pair<RowIndex, double>> lp_multipliers;
2158   for (RowIndex row(0); row < ray.size(); ++row) {
2159     const double value = ray[row];
2160     if (std::abs(value) < kZeroTolerance) continue;
2161     lp_multipliers.push_back({row, value});
2162   }
2163   std::vector<std::pair<RowIndex, IntegerValue>> integer_multipliers =
2164       ScaleLpMultiplier(/*take_objective_into_account=*/false, lp_multipliers,
2165                         &scaling);
2166 
2167   IntegerValue new_constraint_ub;
2168   if (!ComputeNewLinearConstraint(integer_multipliers, &tmp_scattered_vector_,
2169                                   &new_constraint_ub)) {
2170     VLOG(1) << "Isse while computing the exact dual ray reason. Aborting.";
2171     return false;
2172   }
2173 
2174   AdjustNewLinearConstraint(&integer_multipliers, &tmp_scattered_vector_,
2175                             &new_constraint_ub);
2176 
2177   LinearConstraint new_constraint;
2178   tmp_scattered_vector_.ConvertToLinearConstraint(
2179       integer_variables_, new_constraint_ub, &new_constraint);
2180   DivideByGCD(&new_constraint);
2181   PreventOverflow(&new_constraint);
2182   DCHECK(!PossibleOverflow(new_constraint));
2183   DCHECK(constraint_manager_.DebugCheckConstraint(new_constraint));
2184 
2185   const IntegerValue implied_lb = GetImpliedLowerBound(new_constraint);
2186   if (implied_lb <= new_constraint.ub) {
2187     VLOG(1) << "LP exact dual ray not infeasible,"
2188             << " implied_lb: " << implied_lb.value() / scaling
2189             << " ub: " << new_constraint.ub.value() / scaling;
2190     return false;
2191   }
2192   const IntegerValue slack = (implied_lb - new_constraint.ub) - 1;
2193   SetImpliedLowerBoundReason(new_constraint, slack);
2194   return true;
2195 }
2196 
CalculateDegeneracy()2197 int64_t LinearProgrammingConstraint::CalculateDegeneracy() {
2198   const glop::ColIndex num_vars = simplex_.GetProblemNumCols();
2199   int num_non_basic_with_zero_rc = 0;
2200   for (glop::ColIndex i(0); i < num_vars; ++i) {
2201     const double rc = simplex_.GetReducedCost(i);
2202     if (rc != 0.0) continue;
2203     if (simplex_.GetVariableStatus(i) == glop::VariableStatus::BASIC) {
2204       continue;
2205     }
2206     num_non_basic_with_zero_rc++;
2207   }
2208   const int64_t num_cols = simplex_.GetProblemNumCols().value();
2209   is_degenerate_ = num_non_basic_with_zero_rc >= 0.3 * num_cols;
2210   return num_non_basic_with_zero_rc;
2211 }
2212 
ReducedCostStrengtheningDeductions(double cp_objective_delta)2213 void LinearProgrammingConstraint::ReducedCostStrengtheningDeductions(
2214     double cp_objective_delta) {
2215   deductions_.clear();
2216 
2217   // TRICKY: while simplex_.GetObjectiveValue() use the objective scaling factor
2218   // stored in the lp_data_, all the other functions like GetReducedCost() or
2219   // GetVariableValue() do not.
2220   const double lp_objective_delta =
2221       cp_objective_delta / lp_data_.objective_scaling_factor();
2222   const int num_vars = integer_variables_.size();
2223   for (int i = 0; i < num_vars; i++) {
2224     const IntegerVariable cp_var = integer_variables_[i];
2225     const glop::ColIndex lp_var = glop::ColIndex(i);
2226     const double rc = simplex_.GetReducedCost(lp_var);
2227     const double value = simplex_.GetVariableValue(lp_var);
2228 
2229     if (rc == 0.0) continue;
2230     const double lp_other_bound = value + lp_objective_delta / rc;
2231     const double cp_other_bound =
2232         scaler_.UnscaleVariableValue(lp_var, lp_other_bound);
2233 
2234     if (rc > kLpEpsilon) {
2235       const double ub = ToDouble(integer_trail_->UpperBound(cp_var));
2236       const double new_ub = std::floor(cp_other_bound + kCpEpsilon);
2237       if (new_ub < ub) {
2238         // TODO(user): Because rc > kLpEpsilon, the lower_bound of cp_var
2239         // will be part of the reason returned by FillReducedCostsReason(), but
2240         // we actually do not need it here. Same below.
2241         const IntegerValue new_ub_int(static_cast<IntegerValue>(new_ub));
2242         deductions_.push_back(IntegerLiteral::LowerOrEqual(cp_var, new_ub_int));
2243       }
2244     } else if (rc < -kLpEpsilon) {
2245       const double lb = ToDouble(integer_trail_->LowerBound(cp_var));
2246       const double new_lb = std::ceil(cp_other_bound - kCpEpsilon);
2247       if (new_lb > lb) {
2248         const IntegerValue new_lb_int(static_cast<IntegerValue>(new_lb));
2249         deductions_.push_back(
2250             IntegerLiteral::GreaterOrEqual(cp_var, new_lb_int));
2251       }
2252     }
2253   }
2254 }
2255 
2256 namespace {
2257 
2258 // Add a cut of the form Sum_{outgoing arcs from S} lp >= rhs_lower_bound.
2259 //
2260 // Note that we used to also add the same cut for the incoming arcs, but because
2261 // of flow conservation on these problems, the outgoing flow is always the same
2262 // as the incoming flow, so adding this extra cut doesn't seem relevant.
AddOutgoingCut(int num_nodes,int subset_size,const std::vector<bool> & in_subset,const std::vector<int> & tails,const std::vector<int> & heads,const std::vector<Literal> & literals,const std::vector<double> & literal_lp_values,int64_t rhs_lower_bound,const absl::StrongVector<IntegerVariable,double> & lp_values,LinearConstraintManager * manager,Model * model)2263 void AddOutgoingCut(
2264     int num_nodes, int subset_size, const std::vector<bool>& in_subset,
2265     const std::vector<int>& tails, const std::vector<int>& heads,
2266     const std::vector<Literal>& literals,
2267     const std::vector<double>& literal_lp_values, int64_t rhs_lower_bound,
2268     const absl::StrongVector<IntegerVariable, double>& lp_values,
2269     LinearConstraintManager* manager, Model* model) {
2270   // A node is said to be optional if it can be excluded from the subcircuit,
2271   // in which case there is a self-loop on that node.
2272   // If there are optional nodes, use extended formula:
2273   // sum(cut) >= 1 - optional_loop_in - optional_loop_out
2274   // where optional_loop_in's node is in subset, optional_loop_out's is out.
2275   // TODO(user): Favor optional loops fixed to zero at root.
2276   int num_optional_nodes_in = 0;
2277   int num_optional_nodes_out = 0;
2278   int optional_loop_in = -1;
2279   int optional_loop_out = -1;
2280   for (int i = 0; i < tails.size(); ++i) {
2281     if (tails[i] != heads[i]) continue;
2282     if (in_subset[tails[i]]) {
2283       num_optional_nodes_in++;
2284       if (optional_loop_in == -1 ||
2285           literal_lp_values[i] < literal_lp_values[optional_loop_in]) {
2286         optional_loop_in = i;
2287       }
2288     } else {
2289       num_optional_nodes_out++;
2290       if (optional_loop_out == -1 ||
2291           literal_lp_values[i] < literal_lp_values[optional_loop_out]) {
2292         optional_loop_out = i;
2293       }
2294     }
2295   }
2296 
2297   // TODO(user): The lower bound for CVRP is computed assuming all nodes must be
2298   // served, if it is > 1 we lower it to one in the presence of optional nodes.
2299   if (num_optional_nodes_in + num_optional_nodes_out > 0) {
2300     CHECK_GE(rhs_lower_bound, 1);
2301     rhs_lower_bound = 1;
2302   }
2303 
2304   LinearConstraintBuilder outgoing(model, IntegerValue(rhs_lower_bound),
2305                                    kMaxIntegerValue);
2306   double sum_outgoing = 0.0;
2307 
2308   // Add outgoing arcs, compute outgoing flow.
2309   for (int i = 0; i < tails.size(); ++i) {
2310     if (in_subset[tails[i]] && !in_subset[heads[i]]) {
2311       sum_outgoing += literal_lp_values[i];
2312       CHECK(outgoing.AddLiteralTerm(literals[i], IntegerValue(1)));
2313     }
2314   }
2315 
2316   // Support optional nodes if any.
2317   if (num_optional_nodes_in + num_optional_nodes_out > 0) {
2318     // When all optionals of one side are excluded in lp solution, no cut.
2319     if (num_optional_nodes_in == subset_size &&
2320         (optional_loop_in == -1 ||
2321          literal_lp_values[optional_loop_in] > 1.0 - 1e-6)) {
2322       return;
2323     }
2324     if (num_optional_nodes_out == num_nodes - subset_size &&
2325         (optional_loop_out == -1 ||
2326          literal_lp_values[optional_loop_out] > 1.0 - 1e-6)) {
2327       return;
2328     }
2329 
2330     // There is no mandatory node in subset, add optional_loop_in.
2331     if (num_optional_nodes_in == subset_size) {
2332       CHECK(
2333           outgoing.AddLiteralTerm(literals[optional_loop_in], IntegerValue(1)));
2334       sum_outgoing += literal_lp_values[optional_loop_in];
2335     }
2336 
2337     // There is no mandatory node out of subset, add optional_loop_out.
2338     if (num_optional_nodes_out == num_nodes - subset_size) {
2339       CHECK(outgoing.AddLiteralTerm(literals[optional_loop_out],
2340                                     IntegerValue(1)));
2341       sum_outgoing += literal_lp_values[optional_loop_out];
2342     }
2343   }
2344 
2345   if (sum_outgoing < rhs_lower_bound - 1e-6) {
2346     manager->AddCut(outgoing.Build(), "Circuit", lp_values);
2347   }
2348 }
2349 
2350 }  // namespace
2351 
2352 // We roughly follow the algorithm described in section 6 of "The Traveling
2353 // Salesman Problem, A computational Study", David L. Applegate, Robert E.
2354 // Bixby, Vasek Chvatal, William J. Cook.
2355 //
2356 // Note that this is mainly a "symmetric" case algo, but it does still work for
2357 // the asymmetric case.
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)2358 void SeparateSubtourInequalities(
2359     int num_nodes, const std::vector<int>& tails, const std::vector<int>& heads,
2360     const std::vector<Literal>& literals,
2361     const absl::StrongVector<IntegerVariable, double>& lp_values,
2362     absl::Span<const int64_t> demands, int64_t capacity,
2363     LinearConstraintManager* manager, Model* model) {
2364   if (num_nodes <= 2) return;
2365 
2366   // We will collect only the arcs with a positive lp_values to speed up some
2367   // computation below.
2368   struct Arc {
2369     int tail;
2370     int head;
2371     double lp_value;
2372   };
2373   std::vector<Arc> relevant_arcs;
2374 
2375   // Sort the arcs by non-increasing lp_values.
2376   std::vector<double> literal_lp_values(literals.size());
2377   std::vector<std::pair<double, int>> arc_by_decreasing_lp_values;
2378   auto* encoder = model->GetOrCreate<IntegerEncoder>();
2379   for (int i = 0; i < literals.size(); ++i) {
2380     double lp_value;
2381     const IntegerVariable direct_view = encoder->GetLiteralView(literals[i]);
2382     if (direct_view != kNoIntegerVariable) {
2383       lp_value = lp_values[direct_view];
2384     } else {
2385       lp_value =
2386           1.0 - lp_values[encoder->GetLiteralView(literals[i].Negated())];
2387     }
2388     literal_lp_values[i] = lp_value;
2389 
2390     if (lp_value < 1e-6) continue;
2391     relevant_arcs.push_back({tails[i], heads[i], lp_value});
2392     arc_by_decreasing_lp_values.push_back({lp_value, i});
2393   }
2394   std::sort(arc_by_decreasing_lp_values.begin(),
2395             arc_by_decreasing_lp_values.end(),
2396             std::greater<std::pair<double, int>>());
2397 
2398   // We will do a union-find by adding one by one the arc of the lp solution
2399   // in the order above. Every intermediate set during this construction will
2400   // be a candidate for a cut.
2401   //
2402   // In parallel to the union-find, to efficiently reconstruct these sets (at
2403   // most num_nodes), we construct a "decomposition forest" of the different
2404   // connected components. Note that we don't exploit any asymmetric nature of
2405   // the graph here. This is exactly the algo 6.3 in the book above.
2406   int num_components = num_nodes;
2407   std::vector<int> parent(num_nodes);
2408   std::vector<int> root(num_nodes);
2409   for (int i = 0; i < num_nodes; ++i) {
2410     parent[i] = i;
2411     root[i] = i;
2412   }
2413   auto get_root_and_compress_path = [&root](int node) {
2414     int r = node;
2415     while (root[r] != r) r = root[r];
2416     while (root[node] != r) {
2417       const int next = root[node];
2418       root[node] = r;
2419       node = next;
2420     }
2421     return r;
2422   };
2423   for (const auto pair : arc_by_decreasing_lp_values) {
2424     if (num_components == 2) break;
2425     const int tail = get_root_and_compress_path(tails[pair.second]);
2426     const int head = get_root_and_compress_path(heads[pair.second]);
2427     if (tail != head) {
2428       // Update the decomposition forest, note that the number of nodes is
2429       // growing.
2430       const int new_node = parent.size();
2431       parent.push_back(new_node);
2432       parent[head] = new_node;
2433       parent[tail] = new_node;
2434       --num_components;
2435 
2436       // It is important that the union-find representative is the same node.
2437       root.push_back(new_node);
2438       root[head] = new_node;
2439       root[tail] = new_node;
2440     }
2441   }
2442 
2443   // For each node in the decomposition forest, try to add a cut for the set
2444   // formed by the nodes and its children. To do that efficiently, we first
2445   // order the nodes so that for each node in a tree, the set of children forms
2446   // a consecutive span in the pre_order vector. This vector just lists the
2447   // nodes in the "pre-order" graph traversal order. The Spans will point inside
2448   // the pre_order vector, it is why we initialize it once and for all.
2449   int new_size = 0;
2450   std::vector<int> pre_order(num_nodes);
2451   std::vector<absl::Span<const int>> subsets;
2452   {
2453     std::vector<absl::InlinedVector<int, 2>> graph(parent.size());
2454     for (int i = 0; i < parent.size(); ++i) {
2455       if (parent[i] != i) graph[parent[i]].push_back(i);
2456     }
2457     std::vector<int> queue;
2458     std::vector<bool> seen(graph.size(), false);
2459     std::vector<int> start_index(parent.size());
2460     for (int i = num_nodes; i < parent.size(); ++i) {
2461       // Note that because of the way we constructed 'parent', the graph is a
2462       // binary tree. This is not required for the correctness of the algorithm
2463       // here though.
2464       CHECK(graph[i].empty() || graph[i].size() == 2);
2465       if (parent[i] != i) continue;
2466 
2467       // Explore the subtree rooted at node i.
2468       CHECK(!seen[i]);
2469       queue.push_back(i);
2470       while (!queue.empty()) {
2471         const int node = queue.back();
2472         if (seen[node]) {
2473           queue.pop_back();
2474           // All the children of node are in the span [start, end) of the
2475           // pre_order vector.
2476           const int start = start_index[node];
2477           if (new_size - start > 1) {
2478             subsets.emplace_back(&pre_order[start], new_size - start);
2479           }
2480           continue;
2481         }
2482         seen[node] = true;
2483         start_index[node] = new_size;
2484         if (node < num_nodes) pre_order[new_size++] = node;
2485         for (const int child : graph[node]) {
2486           if (!seen[child]) queue.push_back(child);
2487         }
2488       }
2489     }
2490   }
2491 
2492   // Compute the total demands in order to know the minimum incoming/outgoing
2493   // flow.
2494   int64_t total_demands = 0;
2495   if (!demands.empty()) {
2496     for (const int64_t demand : demands) total_demands += demand;
2497   }
2498 
2499   // Process each subsets and add any violated cut.
2500   CHECK_EQ(pre_order.size(), num_nodes);
2501   std::vector<bool> in_subset(num_nodes, false);
2502   for (const absl::Span<const int> subset : subsets) {
2503     CHECK_GT(subset.size(), 1);
2504     CHECK_LT(subset.size(), num_nodes);
2505 
2506     // These fields will be left untouched if demands.empty().
2507     bool contain_depot = false;
2508     int64_t subset_demand = 0;
2509 
2510     // Initialize "in_subset" and the subset demands.
2511     for (const int n : subset) {
2512       in_subset[n] = true;
2513       if (!demands.empty()) {
2514         if (n == 0) contain_depot = true;
2515         subset_demand += demands[n];
2516       }
2517     }
2518 
2519     // Compute a lower bound on the outgoing flow.
2520     //
2521     // TODO(user): This lower bound assume all nodes in subset must be served,
2522     // which is not the case. For TSP we do the correct thing in
2523     // AddOutgoingCut() but not for CVRP... Fix!!
2524     //
2525     // TODO(user): It could be very interesting to see if this "min outgoing
2526     // flow" cannot be automatically infered from the constraint in the
2527     // precedence graph. This might work if we assume that any kind of path
2528     // cumul constraint is encoded with constraints:
2529     //   [edge => value_head >= value_tail + edge_weight].
2530     // We could take the minimum incoming edge weight per node in the set, and
2531     // use the cumul variable domain to infer some capacity.
2532     int64_t min_outgoing_flow = 1;
2533     if (!demands.empty()) {
2534       min_outgoing_flow =
2535           contain_depot
2536               ? (total_demands - subset_demand + capacity - 1) / capacity
2537               : (subset_demand + capacity - 1) / capacity;
2538     }
2539 
2540     // We still need to serve nodes with a demand of zero, and in the corner
2541     // case where all node in subset have a zero demand, the formula above
2542     // result in a min_outgoing_flow of zero.
2543     min_outgoing_flow = std::max(min_outgoing_flow, int64_t{1});
2544 
2545     // Compute the current outgoing flow out of the subset.
2546     //
2547     // This can take a significant portion of the running time, it is why it is
2548     // faster to do it only on arcs with non-zero lp values which should be in
2549     // linear number rather than the total number of arc which can be quadratic.
2550     //
2551     // TODO(user): For the symmetric case there is an even faster algo. See if
2552     // it can be generalized to the asymmetric one if become needed.
2553     // Reference is algo 6.4 of the "The Traveling Salesman Problem" book
2554     // mentionned above.
2555     double outgoing_flow = 0.0;
2556     for (const auto arc : relevant_arcs) {
2557       if (in_subset[arc.tail] && !in_subset[arc.head]) {
2558         outgoing_flow += arc.lp_value;
2559       }
2560     }
2561 
2562     // Add a cut if the current outgoing flow is not enough.
2563     if (outgoing_flow < min_outgoing_flow - 1e-6) {
2564       AddOutgoingCut(num_nodes, subset.size(), in_subset, tails, heads,
2565                      literals, literal_lp_values,
2566                      /*rhs_lower_bound=*/min_outgoing_flow, lp_values, manager,
2567                      model);
2568     }
2569 
2570     // Sparse clean up.
2571     for (const int n : subset) in_subset[n] = false;
2572   }
2573 }
2574 
2575 namespace {
2576 
2577 // Returns for each literal its integer view, or the view of its negation.
GetAssociatedVariables(const std::vector<Literal> & literals,Model * model)2578 std::vector<IntegerVariable> GetAssociatedVariables(
2579     const std::vector<Literal>& literals, Model* model) {
2580   auto* encoder = model->GetOrCreate<IntegerEncoder>();
2581   std::vector<IntegerVariable> result;
2582   for (const Literal l : literals) {
2583     const IntegerVariable direct_view = encoder->GetLiteralView(l);
2584     if (direct_view != kNoIntegerVariable) {
2585       result.push_back(direct_view);
2586     } else {
2587       result.push_back(encoder->GetLiteralView(l.Negated()));
2588       DCHECK_NE(result.back(), kNoIntegerVariable);
2589     }
2590   }
2591   return result;
2592 }
2593 
2594 }  // namespace
2595 
2596 // We use a basic algorithm to detect components that are not connected to the
2597 // rest of the graph in the LP solution, and add cuts to force some arcs to
2598 // enter and leave this component from outside.
CreateStronglyConnectedGraphCutGenerator(int num_nodes,const std::vector<int> & tails,const std::vector<int> & heads,const std::vector<Literal> & literals,Model * model)2599 CutGenerator CreateStronglyConnectedGraphCutGenerator(
2600     int num_nodes, const std::vector<int>& tails, const std::vector<int>& heads,
2601     const std::vector<Literal>& literals, Model* model) {
2602   CutGenerator result;
2603   result.vars = GetAssociatedVariables(literals, model);
2604   result.generate_cuts =
2605       [num_nodes, tails, heads, literals, model](
2606           const absl::StrongVector<IntegerVariable, double>& lp_values,
2607           LinearConstraintManager* manager) {
2608         SeparateSubtourInequalities(
2609             num_nodes, tails, heads, literals, lp_values,
2610             /*demands=*/{}, /*capacity=*/0, manager, model);
2611         return true;
2612       };
2613   return result;
2614 }
2615 
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)2616 CutGenerator CreateCVRPCutGenerator(int num_nodes,
2617                                     const std::vector<int>& tails,
2618                                     const std::vector<int>& heads,
2619                                     const std::vector<Literal>& literals,
2620                                     const std::vector<int64_t>& demands,
2621                                     int64_t capacity, Model* model) {
2622   CutGenerator result;
2623   result.vars = GetAssociatedVariables(literals, model);
2624   result.generate_cuts =
2625       [num_nodes, tails, heads, demands, capacity, literals, model](
2626           const absl::StrongVector<IntegerVariable, double>& lp_values,
2627           LinearConstraintManager* manager) {
2628         SeparateSubtourInequalities(num_nodes, tails, heads, literals,
2629                                     lp_values, demands, capacity, manager,
2630                                     model);
2631         return true;
2632       };
2633   return result;
2634 }
2635 
2636 std::function<IntegerLiteral()>
HeuristicLpMostInfeasibleBinary(Model * model)2637 LinearProgrammingConstraint::HeuristicLpMostInfeasibleBinary(Model* model) {
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) << "HeuristicLPMostInfeasibleBinary has " << variables.size()
2647           << " variables.";
2648 
2649   return [this, variables]() {
2650     const double kEpsilon = 1e-6;
2651     // Find most fractional value.
2652     IntegerVariable fractional_var = kNoIntegerVariable;
2653     double fractional_distance_best = -1.0;
2654     for (const IntegerVariable var : variables) {
2655       // Skip ignored and fixed variables.
2656       if (integer_trail_->IsCurrentlyIgnored(var)) continue;
2657       const IntegerValue lb = integer_trail_->LowerBound(var);
2658       const IntegerValue ub = integer_trail_->UpperBound(var);
2659       if (lb == ub) continue;
2660 
2661       // Check variable's support is fractional.
2662       const double lp_value = this->GetSolutionValue(var);
2663       const double fractional_distance =
2664           std::min(std::ceil(lp_value - kEpsilon) - lp_value,
2665                    lp_value - std::floor(lp_value + kEpsilon));
2666       if (fractional_distance < kEpsilon) continue;
2667 
2668       // Keep variable if it is farther from integrality than the previous.
2669       if (fractional_distance > fractional_distance_best) {
2670         fractional_var = var;
2671         fractional_distance_best = fractional_distance;
2672       }
2673     }
2674 
2675     if (fractional_var != kNoIntegerVariable) {
2676       IntegerLiteral::GreaterOrEqual(fractional_var, IntegerValue(1));
2677     }
2678     return IntegerLiteral();
2679   };
2680 }
2681 
2682 std::function<IntegerLiteral()>
HeuristicLpReducedCostBinary(Model * model)2683 LinearProgrammingConstraint::HeuristicLpReducedCostBinary(Model* model) {
2684   // Gather all 0-1 variables that appear in this LP.
2685   std::vector<IntegerVariable> variables;
2686   for (IntegerVariable var : integer_variables_) {
2687     if (integer_trail_->LowerBound(var) == 0 &&
2688         integer_trail_->UpperBound(var) == 1) {
2689       variables.push_back(var);
2690     }
2691   }
2692   VLOG(1) << "HeuristicLpReducedCostBinary has " << variables.size()
2693           << " variables.";
2694 
2695   // Store average of reduced cost from 1 to 0. The best heuristic only sets
2696   // variables to one and cares about cost to zero, even though classic
2697   // pseudocost will use max_var min(cost_to_one[var], cost_to_zero[var]).
2698   const int num_vars = variables.size();
2699   std::vector<double> cost_to_zero(num_vars, 0.0);
2700   std::vector<int> num_cost_to_zero(num_vars);
2701   int num_calls = 0;
2702 
2703   return [=]() mutable {
2704     const double kEpsilon = 1e-6;
2705 
2706     // Every 10000 calls, decay pseudocosts.
2707     num_calls++;
2708     if (num_calls == 10000) {
2709       for (int i = 0; i < num_vars; i++) {
2710         cost_to_zero[i] /= 2;
2711         num_cost_to_zero[i] /= 2;
2712       }
2713       num_calls = 0;
2714     }
2715 
2716     // Accumulate pseudo-costs of all unassigned variables.
2717     for (int i = 0; i < num_vars; i++) {
2718       const IntegerVariable var = variables[i];
2719       // Skip ignored and fixed variables.
2720       if (integer_trail_->IsCurrentlyIgnored(var)) continue;
2721       const IntegerValue lb = integer_trail_->LowerBound(var);
2722       const IntegerValue ub = integer_trail_->UpperBound(var);
2723       if (lb == ub) continue;
2724 
2725       const double rc = this->GetSolutionReducedCost(var);
2726       // Skip reduced costs that are nonzero because of numerical issues.
2727       if (std::abs(rc) < kEpsilon) continue;
2728 
2729       const double value = std::round(this->GetSolutionValue(var));
2730       if (value == 1.0 && rc < 0.0) {
2731         cost_to_zero[i] -= rc;
2732         num_cost_to_zero[i]++;
2733       }
2734     }
2735 
2736     // Select noninstantiated variable with highest pseudo-cost.
2737     int selected_index = -1;
2738     double best_cost = 0.0;
2739     for (int i = 0; i < num_vars; i++) {
2740       const IntegerVariable var = variables[i];
2741       // Skip ignored and fixed variables.
2742       if (integer_trail_->IsCurrentlyIgnored(var)) continue;
2743       if (integer_trail_->IsFixed(var)) continue;
2744 
2745       if (num_cost_to_zero[i] > 0 &&
2746           best_cost < cost_to_zero[i] / num_cost_to_zero[i]) {
2747         best_cost = cost_to_zero[i] / num_cost_to_zero[i];
2748         selected_index = i;
2749       }
2750     }
2751 
2752     if (selected_index >= 0) {
2753       return IntegerLiteral::GreaterOrEqual(variables[selected_index],
2754                                             IntegerValue(1));
2755     }
2756     return IntegerLiteral();
2757   };
2758 }
2759 
UpdateAverageReducedCosts()2760 void LinearProgrammingConstraint::UpdateAverageReducedCosts() {
2761   const int num_vars = integer_variables_.size();
2762   if (sum_cost_down_.size() < num_vars) {
2763     sum_cost_down_.resize(num_vars, 0.0);
2764     num_cost_down_.resize(num_vars, 0);
2765     sum_cost_up_.resize(num_vars, 0.0);
2766     num_cost_up_.resize(num_vars, 0);
2767     rc_scores_.resize(num_vars, 0.0);
2768   }
2769 
2770   // Decay averages.
2771   num_calls_since_reduced_cost_averages_reset_++;
2772   if (num_calls_since_reduced_cost_averages_reset_ == 10000) {
2773     for (int i = 0; i < num_vars; i++) {
2774       sum_cost_up_[i] /= 2;
2775       num_cost_up_[i] /= 2;
2776       sum_cost_down_[i] /= 2;
2777       num_cost_down_[i] /= 2;
2778     }
2779     num_calls_since_reduced_cost_averages_reset_ = 0;
2780   }
2781 
2782   // Accumulate reduced costs of all unassigned variables.
2783   for (int i = 0; i < num_vars; i++) {
2784     const IntegerVariable var = integer_variables_[i];
2785 
2786     // Skip ignored and fixed variables.
2787     if (integer_trail_->IsCurrentlyIgnored(var)) continue;
2788     if (integer_trail_->IsFixed(var)) continue;
2789 
2790     // Skip reduced costs that are zero or close.
2791     const double rc = lp_reduced_cost_[i];
2792     if (std::abs(rc) < kCpEpsilon) continue;
2793 
2794     if (rc < 0.0) {
2795       sum_cost_down_[i] -= rc;
2796       num_cost_down_[i]++;
2797     } else {
2798       sum_cost_up_[i] += rc;
2799       num_cost_up_[i]++;
2800     }
2801   }
2802 
2803   // Tricky, we artificially reset the rc_rev_int_repository_ to level zero
2804   // so that the rev_rc_start_ is zero.
2805   rc_rev_int_repository_.SetLevel(0);
2806   rc_rev_int_repository_.SetLevel(trail_->CurrentDecisionLevel());
2807   rev_rc_start_ = 0;
2808 
2809   // Cache the new score (higher is better) using the average reduced costs
2810   // as a signal.
2811   positions_by_decreasing_rc_score_.clear();
2812   for (int i = 0; i < num_vars; i++) {
2813     // If only one direction exist, we takes its value divided by 2, so that
2814     // such variable should have a smaller cost than the min of the two side
2815     // except if one direction have a really high reduced costs.
2816     const double a_up =
2817         num_cost_up_[i] > 0 ? sum_cost_up_[i] / num_cost_up_[i] : 0.0;
2818     const double a_down =
2819         num_cost_down_[i] > 0 ? sum_cost_down_[i] / num_cost_down_[i] : 0.0;
2820     if (num_cost_down_[i] > 0 && num_cost_up_[i] > 0) {
2821       rc_scores_[i] = std::min(a_up, a_down);
2822     } else {
2823       rc_scores_[i] = 0.5 * (a_down + a_up);
2824     }
2825 
2826     // We ignore scores of zero (i.e. no data) and will follow the default
2827     // search heuristic if all variables are like this.
2828     if (rc_scores_[i] > 0.0) {
2829       positions_by_decreasing_rc_score_.push_back({-rc_scores_[i], i});
2830     }
2831   }
2832   std::sort(positions_by_decreasing_rc_score_.begin(),
2833             positions_by_decreasing_rc_score_.end());
2834 }
2835 
2836 // TODO(user): Remove duplication with HeuristicLpReducedCostBinary().
2837 std::function<IntegerLiteral()>
HeuristicLpReducedCostAverageBranching()2838 LinearProgrammingConstraint::HeuristicLpReducedCostAverageBranching() {
2839   return [this]() { return this->LPReducedCostAverageDecision(); };
2840 }
2841 
LPReducedCostAverageDecision()2842 IntegerLiteral LinearProgrammingConstraint::LPReducedCostAverageDecision() {
2843   // Select noninstantiated variable with highest positive average reduced cost.
2844   int selected_index = -1;
2845   const int size = positions_by_decreasing_rc_score_.size();
2846   rc_rev_int_repository_.SaveState(&rev_rc_start_);
2847   for (int i = rev_rc_start_; i < size; ++i) {
2848     const int index = positions_by_decreasing_rc_score_[i].second;
2849     const IntegerVariable var = integer_variables_[index];
2850     if (integer_trail_->IsCurrentlyIgnored(var)) continue;
2851     if (integer_trail_->IsFixed(var)) continue;
2852     selected_index = index;
2853     rev_rc_start_ = i;
2854     break;
2855   }
2856 
2857   if (selected_index == -1) return IntegerLiteral();
2858   const IntegerVariable var = integer_variables_[selected_index];
2859 
2860   // If ceil(value) is current upper bound, try var == upper bound first.
2861   // Guarding with >= prevents numerical problems.
2862   // With 0/1 variables, this will tend to try setting to 1 first,
2863   // which produces more shallow trees.
2864   const IntegerValue ub = integer_trail_->UpperBound(var);
2865   const IntegerValue value_ceil(
2866       std::ceil(this->GetSolutionValue(var) - kCpEpsilon));
2867   if (value_ceil >= ub) {
2868     return IntegerLiteral::GreaterOrEqual(var, ub);
2869   }
2870 
2871   // If floor(value) is current lower bound, try var == lower bound first.
2872   // Guarding with <= prevents numerical problems.
2873   const IntegerValue lb = integer_trail_->LowerBound(var);
2874   const IntegerValue value_floor(
2875       std::floor(this->GetSolutionValue(var) + kCpEpsilon));
2876   if (value_floor <= lb) {
2877     return IntegerLiteral::LowerOrEqual(var, lb);
2878   }
2879 
2880   // Here lb < value_floor <= value_ceil < ub.
2881   // Try the most promising split between var <= floor or var >= ceil.
2882   const double a_up =
2883       num_cost_up_[selected_index] > 0
2884           ? sum_cost_up_[selected_index] / num_cost_up_[selected_index]
2885           : 0.0;
2886   const double a_down =
2887       num_cost_down_[selected_index] > 0
2888           ? sum_cost_down_[selected_index] / num_cost_down_[selected_index]
2889           : 0.0;
2890   if (a_down < a_up) {
2891     return IntegerLiteral::LowerOrEqual(var, value_floor);
2892   } else {
2893     return IntegerLiteral::GreaterOrEqual(var, value_ceil);
2894   }
2895 }
2896 
Statistics() const2897 std::string LinearProgrammingConstraint::Statistics() const {
2898   std::string result = "LP statistics:\n";
2899   absl::StrAppend(&result, "  final dimension: ", DimensionString(), "\n");
2900   absl::StrAppend(&result, "  total number of simplex iterations: ",
2901                   total_num_simplex_iterations_, "\n");
2902   absl::StrAppend(&result, "  num solves: \n");
2903   for (int i = 0; i < num_solves_by_status_.size(); ++i) {
2904     if (num_solves_by_status_[i] == 0) continue;
2905     absl::StrAppend(&result, "    - #",
2906                     glop::GetProblemStatusString(glop::ProblemStatus(i)), ": ",
2907                     num_solves_by_status_[i], "\n");
2908   }
2909   absl::StrAppend(&result, constraint_manager_.Statistics());
2910   return result;
2911 }
2912 
2913 }  // namespace sat
2914 }  // namespace operations_research
2915