39 const double kMinCutViolation = 1e-4;
44 LinearConstraint GenerateKnapsackCutForCover(
45 const std::vector<IntegerVariable>& vars,
46 const std::vector<IntegerValue>& coeffs,
const IntegerValue upper_bound,
47 const IntegerTrail& integer_trail) {
48 CHECK_EQ(vars.size(), coeffs.size());
49 CHECK_GT(vars.size(), 0);
51 IntegerValue cut_upper_bound = IntegerValue(0);
52 IntegerValue max_coeff = coeffs[0];
54 IntegerValue slack = -upper_bound;
55 for (
int i = 0; i < vars.size(); ++i) {
56 const IntegerValue var_upper_bound =
57 integer_trail.LevelZeroUpperBound(vars[i]);
58 cut_upper_bound += var_upper_bound;
59 cut.vars.push_back(vars[i]);
60 cut.coeffs.push_back(IntegerValue(1));
61 max_coeff =
std::max(max_coeff, coeffs[i]);
62 slack += coeffs[i] * var_upper_bound;
64 CHECK_GT(slack, 0.0) <<
"Invalid cover for knapsack cut.";
65 cut_upper_bound -=
CeilRatio(slack, max_coeff);
67 cut.ub = cut_upper_bound;
68 VLOG(2) <<
"Generated Knapsack Constraint:" << cut.DebugString();
72 bool SolutionSatisfiesConstraint(
73 const LinearConstraint& constraint,
76 const double tolerance = 1e-6;
77 return (activity <= constraint.ub.value() + tolerance &&
78 activity >= constraint.lb.value() - tolerance)
83 bool SmallRangeAndAllCoefficientsMagnitudeAreTheSame(
84 const LinearConstraint& constraint, IntegerTrail* integer_trail) {
85 if (constraint.vars.empty())
return true;
87 const int64 magnitude = std::abs(constraint.coeffs[0].value());
88 for (
int i = 1; i < constraint.coeffs.size(); ++i) {
89 const IntegerVariable
var = constraint.vars[i];
90 if (integer_trail->LevelZeroUpperBound(
var) -
91 integer_trail->LevelZeroLowerBound(
var) >
95 if (std::abs(constraint.coeffs[i].value()) != magnitude) {
102 bool AllVarsTakeIntegerValue(
103 const std::vector<IntegerVariable> vars,
105 for (IntegerVariable
var : vars) {
106 if (std::abs(lp_values[
var] - std::round(lp_values[
var])) > 1e-6) {
122 int GetSmallestCoverSize(
const LinearConstraint& constraint,
123 const IntegerTrail& integer_trail) {
124 IntegerValue ub = constraint.ub;
125 std::vector<IntegerValue> sorted_terms;
126 for (
int i = 0; i < constraint.vars.size(); ++i) {
127 const IntegerValue coeff = constraint.coeffs[i];
128 const IntegerVariable
var = constraint.vars[i];
129 const IntegerValue var_ub = integer_trail.LevelZeroUpperBound(
var);
130 const IntegerValue var_lb = integer_trail.LevelZeroLowerBound(
var);
131 ub -= var_lb * coeff;
132 sorted_terms.push_back(coeff * (var_ub - var_lb));
134 std::sort(sorted_terms.begin(), sorted_terms.end(),
135 std::greater<IntegerValue>());
136 int smallest_cover_size = 0;
137 IntegerValue sorted_term_sum = IntegerValue(0);
138 while (sorted_term_sum <= ub &&
139 smallest_cover_size < constraint.vars.size()) {
140 sorted_term_sum += sorted_terms[smallest_cover_size++];
142 return smallest_cover_size;
145 bool ConstraintIsEligibleForLifting(
const LinearConstraint& constraint,
146 const IntegerTrail& integer_trail) {
147 for (
const IntegerVariable
var : constraint.vars) {
148 if (integer_trail.LevelZeroLowerBound(
var) != IntegerValue(0) ||
149 integer_trail.LevelZeroUpperBound(
var) != IntegerValue(1)) {
160 const std::vector<IntegerValue>& cut_vars_original_coefficients,
163 std::set<IntegerVariable> vars_in_cut;
164 for (IntegerVariable
var : cut->
vars) {
165 vars_in_cut.insert(
var);
168 std::vector<std::pair<IntegerValue, IntegerVariable>> non_zero_vars;
169 std::vector<std::pair<IntegerValue, IntegerVariable>> zero_vars;
170 for (
int i = 0; i < constraint.
vars.size(); ++i) {
171 const IntegerVariable
var = constraint.
vars[i];
176 if (vars_in_cut.find(
var) != vars_in_cut.end())
continue;
177 const IntegerValue coeff = constraint.
coeffs[i];
178 if (lp_values[
var] <= 1e-6) {
179 zero_vars.push_back({coeff,
var});
181 non_zero_vars.push_back({coeff,
var});
187 std::sort(non_zero_vars.rbegin(), non_zero_vars.rend());
188 std::sort(zero_vars.rbegin(), zero_vars.rend());
190 std::vector<std::pair<IntegerValue, IntegerVariable>> lifting_sequence(
191 std::move(non_zero_vars));
193 lifting_sequence.insert(lifting_sequence.end(), zero_vars.begin(),
197 std::vector<double> lifting_profits;
198 std::vector<double> lifting_weights;
199 for (
int i = 0; i < cut->
vars.size(); ++i) {
200 lifting_profits.push_back(cut->
coeffs[i].value());
201 lifting_weights.push_back(cut_vars_original_coefficients[i].
value());
205 bool is_lifted =
false;
206 bool is_solution_optimal =
false;
208 for (
auto entry : lifting_sequence) {
209 is_solution_optimal =
false;
210 const IntegerValue var_original_coeff = entry.first;
211 const IntegerVariable
var = entry.second;
212 const IntegerValue lifting_capacity = constraint.
ub - entry.first;
213 if (lifting_capacity <= IntegerValue(0))
continue;
214 knapsack_solver.
Init(lifting_profits, lifting_weights,
215 lifting_capacity.value());
221 const double knapsack_upper_bound =
223 const IntegerValue cut_coeff = cut->
ub - knapsack_upper_bound;
224 if (cut_coeff > IntegerValue(0)) {
227 cut->
coeffs.push_back(cut_coeff);
228 lifting_profits.push_back(cut_coeff.value());
229 lifting_weights.push_back(var_original_coeff.value());
239 IntegerValue ub = constraint.
ub;
241 for (
int i = 0; i < constraint.
vars.size(); ++i) {
242 const IntegerVariable
var = constraint.
vars[i];
244 const IntegerValue coeff = constraint.
coeffs[i];
245 if (var_ub.value() - lp_values[
var] <= 1.0 - kMinCutViolation) {
246 constraint_with_left_vars.
vars.push_back(
var);
247 constraint_with_left_vars.
coeffs.push_back(coeff);
251 ub -= coeff * var_lb;
254 constraint_with_left_vars.
ub = ub;
255 constraint_with_left_vars.
lb = constraint.
lb;
256 return constraint_with_left_vars;
261 IntegerValue term_sum = IntegerValue(0);
262 for (
int i = 0; i < constraint.
vars.size(); ++i) {
263 const IntegerVariable
var = constraint.
vars[i];
265 const IntegerValue coeff = constraint.
coeffs[i];
266 term_sum += coeff * var_ub;
268 if (term_sum <= constraint.
ub) {
269 VLOG(2) <<
"Filtered by cover filter";
279 std::vector<double> variable_upper_bound_distances;
280 for (
const IntegerVariable
var : preprocessed_constraint.
vars) {
282 variable_upper_bound_distances.push_back(var_ub.value() - lp_values[
var]);
285 const int smallest_cover_size =
286 GetSmallestCoverSize(preprocessed_constraint, integer_trail);
289 variable_upper_bound_distances.begin(),
290 variable_upper_bound_distances.begin() + smallest_cover_size - 1,
291 variable_upper_bound_distances.end());
292 double cut_lower_bound = 0.0;
293 for (
int i = 0; i < smallest_cover_size; ++i) {
294 cut_lower_bound += variable_upper_bound_distances[i];
296 if (cut_lower_bound >= 1.0 - kMinCutViolation) {
297 VLOG(2) <<
"Filtered by kappa heuristic";
306 std::sort(items.begin(), items.end(), std::greater<KnapsackItem>());
310 if (item.weight <= left_capacity) {
311 profit += item.profit;
312 left_capacity -= item.weight;
314 profit += (left_capacity / item.weight) * item.profit;
325 std::vector<KnapsackItem> items;
326 double capacity = -constraint.
ub.value() - 1.0;
327 double sum_variable_profit = 0;
328 for (
int i = 0; i < constraint.
vars.size(); ++i) {
329 const IntegerVariable
var = constraint.
vars[i];
332 const IntegerValue coeff = constraint.
coeffs[i];
334 item.
profit = var_ub.value() - lp_values[
var];
335 item.
weight = (coeff * (var_ub - var_lb)).value();
336 items.push_back(item);
338 sum_variable_profit += item.
profit;
343 if (sum_variable_profit - 1.0 + kMinCutViolation < 0.0)
return false;
346 const double knapsack_upper_bound =
348 if (knapsack_upper_bound < sum_variable_profit - 1.0 + kMinCutViolation) {
349 VLOG(2) <<
"Filtered by knapsack upper bound";
374 std::vector<LinearConstraint>* knapsack_constraints,
380 if (SmallRangeAndAllCoefficientsMagnitudeAreTheSame(constraint,
388 for (
int i = 0; i < constraint.
vars.size(); ++i) {
389 const IntegerVariable
var = constraint.
vars[i];
390 const IntegerValue coeff = constraint.
coeffs[i];
391 if (coeff > IntegerValue(0)) {
397 canonical_knapsack_form.
ub = constraint.
ub;
399 knapsack_constraints->push_back(canonical_knapsack_form);
406 for (
int i = 0; i < constraint.
vars.size(); ++i) {
407 const IntegerVariable
var = constraint.
vars[i];
408 const IntegerValue coeff = constraint.
coeffs[i];
409 if (coeff > IntegerValue(0)) {
415 canonical_knapsack_form.
ub = -constraint.
lb;
417 knapsack_constraints->push_back(canonical_knapsack_form);
423 const std::vector<LinearConstraint>& base_constraints,
424 const std::vector<IntegerVariable>& vars,
Model*
model) {
429 std::vector<LinearConstraint> knapsack_constraints;
437 if (constraint.vars.size() <= 2)
continue;
441 VLOG(1) <<
"#knapsack constraints: " << knapsack_constraints.size();
451 result.
generate_cuts = [implied_bounds_processor, knapsack_constraints, vars,
452 model, integer_trail](
459 if (AllVarsTakeIntegerValue(vars, lp_values))
return;
462 "Knapsack on demand cover cut generator");
463 int64 skipped_constraints = 0;
470 VLOG(2) <<
"Processing constraint: " << constraint.DebugString();
472 mutable_constraint = constraint;
474 lp_values, &mutable_constraint);
480 if (preprocessed_constraint.
vars.empty())
continue;
484 skipped_constraints++;
489 std::vector<double> profits;
490 profits.reserve(preprocessed_constraint.
vars.size());
493 std::vector<double> weights;
494 weights.reserve(preprocessed_constraint.
vars.size());
496 double capacity = -preprocessed_constraint.
ub.value() - 1.0;
502 double sum_variable_profit = 0;
506 for (
int i = 0; i < preprocessed_constraint.
vars.size(); ++i) {
507 const IntegerVariable
var = preprocessed_constraint.
vars[i];
511 const double variable_profit = var_ub - lp_values[
var];
512 profits.push_back(variable_profit);
514 sum_variable_profit += variable_profit;
517 weights.push_back(
weight);
522 std::vector<IntegerVariable> cut_vars;
523 std::vector<IntegerValue> cut_vars_original_coefficients;
525 VLOG(2) <<
"Knapsack size: " << profits.size();
529 const double time_limit_for_knapsack_solver =
535 bool is_solution_optimal =
false;
537 sum_variable_profit - 1.0 + kMinCutViolation);
541 auto time_limit_for_solver =
542 absl::make_unique<TimeLimit>(time_limit_for_knapsack_solver);
543 const double sum_of_distance_to_ub_for_vars_in_cover =
544 sum_variable_profit -
545 knapsack_solver.
Solve(time_limit_for_solver.get(),
546 &is_solution_optimal);
547 if (is_solution_optimal) {
548 VLOG(2) <<
"Knapsack Optimal solution found yay !";
550 if (time_limit_for_solver->LimitReached()) {
551 VLOG(1) <<
"Knapsack Solver run out of time limit.";
553 if (sum_of_distance_to_ub_for_vars_in_cover < 1.0 - kMinCutViolation) {
556 IntegerValue constraint_ub_for_cut = preprocessed_constraint.
ub;
557 std::set<IntegerVariable> vars_in_cut;
558 for (
int i = 0; i < preprocessed_constraint.
vars.size(); ++i) {
559 const IntegerVariable
var = preprocessed_constraint.
vars[i];
562 cut_vars.push_back(
var);
563 cut_vars_original_coefficients.push_back(
coefficient);
564 vars_in_cut.insert(
var);
571 cut_vars, cut_vars_original_coefficients, constraint_ub_for_cut,
575 bool is_lifted =
false;
576 if (ConstraintIsEligibleForLifting(cut, *integer_trail)) {
578 cut_vars_original_coefficients, *integer_trail,
584 CHECK(!SolutionSatisfiesConstraint(cut, lp_values));
585 manager->AddCut(cut, is_lifted ?
"LiftedKnapsack" :
"Knapsack",
589 if (skipped_constraints > 0) {
590 VLOG(2) <<
"Skipped constraints: " << skipped_constraints;
601 IntegerValue
GetFactorT(IntegerValue rhs_remainder, IntegerValue divisor,
602 IntegerValue max_t) {
604 return rhs_remainder == 0
610 IntegerValue rhs_remainder, IntegerValue divisor, IntegerValue t,
611 IntegerValue max_scaling) {
612 DCHECK_GE(max_scaling, 1);
616 DCHECK_LT(rhs_remainder, divisor);
623 const IntegerValue size = divisor - rhs_remainder;
624 if (max_scaling == 1 || size == 1) {
628 return [t, divisor](IntegerValue coeff) {
631 }
else if (size <= max_scaling) {
632 return [size, rhs_remainder, t, divisor](IntegerValue coeff) {
634 const IntegerValue remainder = t * coeff -
ratio * divisor;
635 const IntegerValue diff = remainder - rhs_remainder;
638 }
else if (max_scaling.value() * rhs_remainder.value() < divisor) {
648 return [t, divisor, max_scaling](IntegerValue coeff) {
650 const IntegerValue remainder = t * coeff -
ratio * divisor;
651 const IntegerValue bucket =
FloorRatio(remainder * max_scaling, divisor);
652 return max_scaling *
ratio + bucket;
677 return [size, rhs_remainder, t, divisor, max_scaling](IntegerValue coeff) {
679 const IntegerValue remainder = t * coeff -
ratio * divisor;
680 const IntegerValue diff = remainder - rhs_remainder;
681 const IntegerValue bucket =
682 diff > 0 ?
CeilRatio(diff * (max_scaling - 1), size)
684 return max_scaling *
ratio + bucket;
697 const int size = lp_values.size();
698 if (size == 0)
return;
701 CHECK_EQ(cut->
vars.size(), size);
702 CHECK_EQ(cut->
coeffs.size(), size);
711 relevant_indices_.clear();
712 relevant_lp_values_.clear();
713 relevant_coeffs_.clear();
714 relevant_bound_diffs_.clear();
716 adjusted_coeffs_.clear();
719 IntegerValue max_magnitude(0);
720 for (
int i = 0; i < size; ++i) {
723 max_magnitude =
std::max(max_magnitude, magnitude);
729 bool overflow =
false;
730 change_sign_at_postprocessing_.assign(size,
false);
731 for (
int i = 0; i < size; ++i) {
732 if (cut->
coeffs[i] == 0)
continue;
737 double lp_value = lp_values[i];
740 const IntegerValue bound_diff =
741 IntegerValue(
CapSub(ub.value(), lb.value()));
754 const double lb_dist = std::abs(lp_value -
ToDouble(lb));
755 const double ub_dist = std::abs(lp_value -
ToDouble(ub));
758 if ((bias * lb_dist > ub_dist && cut->
coeffs[i] < 0) ||
759 (lb_dist > bias * ub_dist && cut->
coeffs[i] > 0)) {
760 change_sign_at_postprocessing_[i] =
true;
762 lp_value = -lp_value;
777 if (bound_diff == 0) {
778 cut->
coeffs[i] = IntegerValue(0);
782 if (std::abs(lp_value) > 1e-2) {
783 relevant_coeffs_.push_back(cut->
coeffs[i]);
784 relevant_indices_.push_back(i);
785 relevant_lp_values_.push_back(lp_value);
786 relevant_bound_diffs_.push_back(bound_diff);
787 divisors_.push_back(magnitude);
792 if (relevant_coeffs_.empty()) {
793 VLOG(2) <<
"Issue, nothing to cut.";
797 CHECK_NE(max_magnitude, 0);
813 double best_scaled_violation = 0.01;
814 const IntegerValue remainder_threshold(max_magnitude / 1000);
825 if (overflow || max_magnitude >= threshold) {
826 VLOG(2) <<
"Issue, overflow.";
830 const IntegerValue max_t = threshold / max_magnitude;
841 const IntegerValue divisor_threshold = max_magnitude / 10;
842 for (
int i = 0; i < divisors_.size(); ++i) {
843 if (divisors_[i] <= divisor_threshold)
continue;
844 divisors_[new_size++] = divisors_[i];
846 divisors_.resize(new_size);
853 IntegerValue best_divisor(0);
854 for (
const IntegerValue divisor : divisors_) {
856 const IntegerValue initial_rhs_remainder =
858 if (initial_rhs_remainder <= remainder_threshold)
continue;
860 IntegerValue temp_ub = cut->
ub;
861 adjusted_coeffs_.clear();
878 const IntegerValue adjust_threshold =
879 (divisor - initial_rhs_remainder - 1) / IntegerValue(size);
880 if (adjust_threshold > 0) {
884 bool early_abort =
false;
885 double loss_lb = 0.0;
886 const double threshold =
ToDouble(initial_rhs_remainder);
888 for (
int i = 0; i < relevant_coeffs_.size(); ++i) {
890 const IntegerValue coeff = relevant_coeffs_[i];
891 const IntegerValue remainder =
892 CeilRatio(coeff, divisor) * divisor - coeff;
894 if (divisor - remainder <= initial_rhs_remainder) {
897 loss_lb +=
ToDouble(divisor - remainder) * relevant_lp_values_[i];
898 if (loss_lb >= threshold) {
905 const IntegerValue diff = relevant_bound_diffs_[i];
906 if (remainder > 0 && remainder <= adjust_threshold &&
907 CapProd(diff.value(), remainder.value()) <= adjust_threshold) {
908 temp_ub += remainder * diff;
909 adjusted_coeffs_.push_back({i, coeff + remainder});
913 if (early_abort)
continue;
917 const IntegerValue rhs_remainder =
918 temp_ub -
FloorRatio(temp_ub, divisor) * divisor;
919 if (rhs_remainder == 0)
continue;
922 rhs_remainder, divisor,
GetFactorT(rhs_remainder, divisor, max_t),
933 const double threshold = scaling *
ToDouble(rhs_remainder);
940 double violation = -
ToDouble(f(temp_ub));
941 double l2_norm = 0.0;
942 bool early_abort =
false;
943 int adjusted_coeffs_index = 0;
944 for (
int i = 0; i < relevant_coeffs_.size(); ++i) {
945 IntegerValue coeff = relevant_coeffs_[i];
948 if (adjusted_coeffs_index < adjusted_coeffs_.size() &&
949 adjusted_coeffs_[adjusted_coeffs_index].first == i) {
950 coeff = adjusted_coeffs_[adjusted_coeffs_index].second;
951 adjusted_coeffs_index++;
954 if (coeff == 0)
continue;
955 const IntegerValue new_coeff = f(coeff);
956 const double new_coeff_double =
ToDouble(new_coeff);
957 const double lp_value = relevant_lp_values_[i];
959 l2_norm += new_coeff_double * new_coeff_double;
960 violation += new_coeff_double * lp_value;
961 loss += (scaling *
ToDouble(coeff) - new_coeff_double) * lp_value;
962 if (loss >= threshold) {
967 if (early_abort)
continue;
971 violation /= sqrt(l2_norm);
972 if (violation > best_scaled_violation) {
973 best_scaled_violation = violation;
974 best_divisor = divisor;
978 if (best_divisor == 0) {
988 const IntegerValue initial_rhs_remainder =
990 const IntegerValue adjust_threshold =
991 (best_divisor - initial_rhs_remainder - 1) / IntegerValue(size);
992 if (adjust_threshold > 0) {
993 for (
int i = 0; i < relevant_indices_.size(); ++i) {
994 const int index = relevant_indices_[i];
995 const IntegerValue diff = relevant_bound_diffs_[i];
996 if (diff > adjust_threshold)
continue;
1000 const IntegerValue remainder =
1001 CeilRatio(coeff, best_divisor) * best_divisor - coeff;
1002 if (
CapProd(diff.value(), remainder.value()) <= adjust_threshold) {
1003 cut->
ub += remainder * diff;
1016 const IntegerValue rhs_remainder =
1018 IntegerValue factor_t =
GetFactorT(rhs_remainder, best_divisor, max_t);
1025 remainders_.clear();
1026 for (
int i = 0; i < size; ++i) {
1027 const IntegerValue coeff = cut->
coeffs[i];
1028 const IntegerValue r =
1029 coeff -
FloorRatio(coeff, best_divisor) * best_divisor;
1030 if (r > rhs_remainder) remainders_.push_back(r);
1033 if (remainders_.size() <= 100) {
1035 for (
const IntegerValue r : remainders_) {
1036 best_rs_.push_back(f(r));
1038 IntegerValue best_d = f(best_divisor);
1043 for (
const IntegerValue t :
1044 {IntegerValue(1),
GetFactorT(rhs_remainder, best_divisor, max_t)}) {
1045 for (IntegerValue s(2); s <= options.
max_scaling; ++s) {
1048 int num_strictly_better = 0;
1050 const IntegerValue d = g(best_divisor);
1051 for (
int i = 0; i < best_rs_.size(); ++i) {
1052 const IntegerValue temp = g(remainders_[i]);
1053 if (temp * best_d < best_rs_[i] * d)
break;
1054 if (temp * best_d > best_rs_[i] * d) num_strictly_better++;
1055 rs_.push_back(temp);
1057 if (rs_.size() == best_rs_.size() && num_strictly_better > 0) {
1069 cut->
ub = f(cut->
ub);
1074 num_lifted_booleans_ = 0;
1075 if (ib_processor !=
nullptr) {
1076 for (
int i = 0; i < size; ++i) {
1077 const IntegerValue coeff = cut->
coeffs[i];
1078 if (coeff == 0)
continue;
1080 IntegerVariable
var = cut->
vars[i];
1081 if (change_sign_at_postprocessing_[i]) {
1099 const IntegerValue coeff_b =
1101 CHECK_GE(coeff_b, 0);
1102 if (coeff_b == 0)
continue;
1104 ++num_lifted_booleans_;
1106 tmp_terms_.push_back({info.
bool_var, coeff_b});
1108 tmp_terms_.push_back({info.
bool_var, -coeff_b});
1109 cut->
ub =
CapAdd(-coeff_b.value(), cut->
ub.value());
1118 for (
int i = 0; i < size; ++i) {
1119 IntegerValue coeff = cut->
coeffs[i];
1120 if (coeff == 0)
continue;
1122 if (coeff == 0)
continue;
1123 if (change_sign_at_postprocessing_[i]) {
1124 cut->
ub = IntegerValue(
1126 tmp_terms_.push_back({cut->
vars[i], -coeff});
1128 cut->
ub = IntegerValue(
1130 tmp_terms_.push_back({cut->
vars[i], coeff});
1145 result.
vars = {z, x, y};
1149 [z, x, y, integer_trail](
1158 const int64 kMaxSafeInteger = (
int64{1} << 53) - 1;
1160 if (
CapProd(x_ub, y_ub) >= kMaxSafeInteger) {
1161 VLOG(3) <<
"Potential overflow in PositiveMultiplicationCutGenerator";
1165 const double x_lp_value = lp_values[x];
1166 const double y_lp_value = lp_values[y];
1167 const double z_lp_value = lp_values[z];
1175 auto try_add_above_cut = [manager, z_lp_value, x_lp_value, y_lp_value,
1176 x, y, z, &lp_values](
1178 if (-z_lp_value + x_lp_value * x_coeff + y_lp_value * y_coeff >=
1179 rhs + kMinCutViolation) {
1181 cut.
vars.push_back(z);
1182 cut.
coeffs.push_back(IntegerValue(-1));
1184 cut.
vars.push_back(x);
1185 cut.
coeffs.push_back(IntegerValue(x_coeff));
1188 cut.
vars.push_back(y);
1189 cut.
coeffs.push_back(IntegerValue(y_coeff));
1192 cut.
ub = IntegerValue(rhs);
1193 manager->AddCut(cut,
"PositiveProduct", lp_values);
1198 auto try_add_below_cut = [manager, z_lp_value, x_lp_value, y_lp_value,
1199 x, y, z, &lp_values](
1201 if (-z_lp_value + x_lp_value * x_coeff + y_lp_value * y_coeff <=
1202 rhs - kMinCutViolation) {
1204 cut.
vars.push_back(z);
1205 cut.
coeffs.push_back(IntegerValue(-1));
1207 cut.
vars.push_back(x);
1208 cut.
coeffs.push_back(IntegerValue(x_coeff));
1211 cut.
vars.push_back(y);
1212 cut.
coeffs.push_back(IntegerValue(y_coeff));
1214 cut.
lb = IntegerValue(rhs);
1216 manager->AddCut(cut,
"PositiveProduct", lp_values);
1227 try_add_above_cut(y_lb, x_lb, x_lb * y_lb);
1228 try_add_above_cut(y_ub, x_ub, x_ub * y_ub);
1229 try_add_below_cut(y_ub, x_lb, x_lb * y_ub);
1230 try_add_below_cut(y_lb, x_ub, x_ub * y_lb);
1239 result.
vars = {y, x};
1243 [y, x, integer_trail](
1249 if (x_lb == x_ub)
return;
1252 if (x_ub > (
int64{1} << 31))
return;
1255 const double y_lp_value = lp_values[y];
1256 const double x_lp_value = lp_values[x];
1261 const int64 y_lb = x_lb * x_lb;
1262 const int64 above_slope = x_ub + x_lb;
1263 const double max_lp_y = y_lb + above_slope * (x_lp_value - x_lb);
1264 if (y_lp_value >= max_lp_y + kMinCutViolation) {
1267 above_cut.
vars.push_back(y);
1268 above_cut.
coeffs.push_back(IntegerValue(1));
1269 above_cut.
vars.push_back(x);
1270 above_cut.
coeffs.push_back(IntegerValue(-above_slope));
1272 above_cut.
ub = IntegerValue(-x_lb * x_ub);
1273 manager->AddCut(above_cut,
"SquareUpper", lp_values);
1282 const int64 x_floor =
static_cast<int64>(std::floor(x_lp_value));
1283 const int64 below_slope = 2 * x_floor + 1;
1284 const double min_lp_y =
1285 below_slope * x_lp_value - x_floor - x_floor * x_floor;
1286 if (min_lp_y >= y_lp_value + kMinCutViolation) {
1290 below_cut.
vars.push_back(y);
1291 below_cut.
coeffs.push_back(IntegerValue(1));
1292 below_cut.
vars.push_back(x);
1293 below_cut.
coeffs.push_back(-IntegerValue(below_slope));
1294 below_cut.
lb = IntegerValue(-x_floor - x_floor * x_floor);
1296 manager->AddCut(below_cut,
"SquareLower", lp_values);
1307 false, IntegerVariable(0), lp_values,
1308 cut,
nullptr,
nullptr);
1313 auto it = cache_.find(
var);
1314 if (it != cache_.end())
return it->second;
1319 ImpliedBoundsProcessor::ComputeBestImpliedBound(
1320 IntegerVariable
var,
1322 std::vector<LinearConstraint>* implied_bound_cuts)
const {
1323 auto it = cache_.find(
var);
1324 if (it != cache_.end())
return it->second;
1325 BestImpliedBoundInfo result;
1341 const IntegerValue diff = entry.lower_bound - lb;
1343 const double bool_lp_value = entry.is_positive
1344 ? lp_values[entry.literal_view]
1345 : 1.0 - lp_values[entry.literal_view];
1346 const double slack_lp_value =
1351 if (slack_lp_value < -1e-6) {
1352 if (implied_bound_cuts !=
nullptr) {
1353 LinearConstraint ib_cut;
1354 std::vector<std::pair<IntegerVariable, IntegerValue>> terms;
1356 ib_cut.ub = IntegerValue(0);
1357 if (entry.is_positive) {
1359 terms.push_back({entry.literal_view, diff});
1360 terms.push_back({
var, IntegerValue(-1)});
1364 terms.push_back({entry.literal_view, -diff});
1365 terms.push_back({
var, IntegerValue(-1)});
1366 ib_cut.ub = -entry.lower_bound;
1369 implied_bound_cuts->push_back(std::move(ib_cut));
1376 if (slack_lp_value + 1e-4 < result.slack_lp_value ||
1377 (slack_lp_value < result.slack_lp_value + 1e-4 &&
1378 diff > result.bound_diff)) {
1379 result.bool_lp_value = bool_lp_value;
1380 result.slack_lp_value = slack_lp_value;
1382 result.bound_diff = diff;
1383 result.is_positive = entry.is_positive;
1384 result.bool_var = entry.literal_view;
1387 cache_[
var] = result;
1392 bool substitute_only_inner_variables, IntegerVariable first_slack,
1395 std::vector<LinearConstraint>* implied_bound_cuts)
const {
1397 IntegerValue new_ub = cut->
ub;
1398 bool changed =
false;
1401 int64 overflow_detection = 0;
1403 const int size = cut->
vars.size();
1404 for (
int i = 0; i < size; ++i) {
1405 IntegerVariable
var = cut->
vars[i];
1406 IntegerValue coeff = cut->
coeffs[i];
1419 ComputeBestImpliedBound(
var, lp_values, implied_bound_cuts);
1426 ComputeBestImpliedBound(
NegationOf(
var), lp_values, implied_bound_cuts);
1429 const int old_size = tmp_terms_.size();
1432 bool keep_term =
false;
1446 if (substitute_only_inner_variables) {
1449 if (lp_values[
var] -
ToDouble(lb) < 1e-2) keep_term =
true;
1450 if (
ToDouble(ub) - lp_values[
var] < 1e-2) keep_term =
true;
1454 if (slack_infos ==
nullptr) {
1461 tmp_terms_.push_back({
var, coeff});
1470 slack_info.
ub = ub - lb;
1476 VLOG(2) <<
"Overflow";
1479 if (slack_infos !=
nullptr) {
1480 tmp_terms_.push_back({first_slack, coeff});
1484 slack_info.
terms.push_back({
var, IntegerValue(1)});
1487 slack_infos->push_back(slack_info);
1494 VLOG(2) <<
"Overflow";
1497 if (slack_infos !=
nullptr) {
1498 tmp_terms_.push_back({first_slack, coeff});
1502 slack_info.
terms.push_back({
var, IntegerValue(1)});
1505 slack_infos->push_back(slack_info);
1513 for (
int i = old_size; i < tmp_terms_.size(); ++i) {
1514 overflow_detection =
1515 CapAdd(overflow_detection, std::abs(tmp_terms_[i].second.value()));
1520 VLOG(2) <<
"Overflow";
1523 if (!changed)
return;
1537 const std::vector<SlackInfo>& info) {
1539 IntegerValue new_ub = cut.
ub;
1540 for (
int i = 0; i < cut.
vars.size(); ++i) {
1542 if (cut.
vars[i] < first_slack) {
1543 tmp_terms_.push_back({cut.
vars[i], cut.
coeffs[i]});
1548 const IntegerValue multiplier = cut.
coeffs[i];
1549 const int index = (cut.
vars[i].value() - first_slack.value()) / 2;
1550 for (
const std::pair<IntegerVariable, IntegerValue>& term :
1551 info[
index].terms) {
1552 tmp_terms_.push_back({term.first, term.second * multiplier});
1554 new_ub -= multiplier * info[
index].offset;
1559 tmp_cut.
ub = new_ub;
1569 for (
int i = 0; i < initial_cut.
vars.size(); ++i) {
1570 tmp_terms_.push_back({initial_cut.
vars[i], initial_cut.
coeffs[i]});
1573 tmp_copy.
ub = new_ub;
1577 if (tmp_cut == tmp_copy)
return true;
1579 LOG(INFO) << first_slack;
1588 void TryToGenerateAllDiffCut(
1589 const std::vector<std::pair<double, IntegerVariable>>& sorted_vars_lp,
1594 std::vector<IntegerVariable> current_set_vars;
1596 for (
auto value_var : sorted_vars_lp) {
1597 sum += value_var.first;
1598 const IntegerVariable
var = value_var.second;
1602 current_union = current_union.
UnionWith(var_domain);
1603 current_set_vars.push_back(
var);
1604 const int64 required_min_sum =
1606 const int64 required_max_sum =
1608 if (sum < required_min_sum || sum > required_max_sum) {
1610 for (IntegerVariable
var : current_set_vars) {
1613 cut.
lb = IntegerValue(required_min_sum);
1614 cut.
ub = IntegerValue(required_max_sum);
1615 manager->
AddCut(cut,
"all_diff", lp_values);
1619 current_set_vars.clear();
1620 current_union =
Domain();
1628 const std::vector<IntegerVariable>& vars,
Model*
model) {
1634 [vars, integer_trail, trail](
1640 if (trail->CurrentDecisionLevel() > 0)
return;
1641 std::vector<std::pair<double, IntegerVariable>> sorted_vars;
1642 for (
const IntegerVariable
var : vars) {
1643 if (integer_trail->LevelZeroLowerBound(
var) ==
1644 integer_trail->LevelZeroUpperBound(
var)) {
1647 sorted_vars.push_back(std::make_pair(lp_values[
var],
var));
1649 std::sort(sorted_vars.begin(), sorted_vars.end());
1650 TryToGenerateAllDiffCut(sorted_vars, *integer_trail, lp_values,
1653 std::reverse(sorted_vars.begin(), sorted_vars.end());
1654 TryToGenerateAllDiffCut(sorted_vars, *integer_trail, lp_values,
1657 VLOG(1) <<
"Created all_diff cut generator of size: " << vars.size();
1663 IntegerValue MaxCornerDifference(
const IntegerVariable
var,
1664 const IntegerValue w1_i,
1665 const IntegerValue w2_i,
1666 const IntegerTrail& integer_trail) {
1667 const IntegerValue lb = integer_trail.LevelZeroLowerBound(
var);
1668 const IntegerValue ub = integer_trail.LevelZeroUpperBound(
var);
1669 return std::max((w2_i - w1_i) * lb, (w2_i - w1_i) * ub);
1678 IntegerValue MPlusCoefficient(
1679 const std::vector<IntegerVariable>& x_vars,
1680 const std::vector<LinearExpression>& exprs,
1682 const int max_index,
const IntegerTrail& integer_trail) {
1683 IntegerValue coeff = exprs[max_index].offset;
1686 for (
const IntegerVariable
var : x_vars) {
1687 const int target_index = variable_partition[
var];
1688 if (max_index != target_index) {
1689 coeff += MaxCornerDifference(
1700 double ComputeContribution(
1701 const IntegerVariable xi_var,
const std::vector<IntegerVariable>& z_vars,
1702 const std::vector<LinearExpression>& exprs,
1704 const IntegerTrail& integer_trail,
const int target_index) {
1705 CHECK_GE(target_index, 0);
1706 CHECK_LT(target_index, exprs.size());
1707 const LinearExpression& target_expr = exprs[target_index];
1708 const double xi_value = lp_values[xi_var];
1710 double contrib = wt_i.value() * xi_value;
1711 for (
int expr_index = 0; expr_index < exprs.size(); ++expr_index) {
1712 if (expr_index == target_index)
continue;
1713 const LinearExpression& max_expr = exprs[expr_index];
1714 const double z_max_value = lp_values[z_vars[expr_index]];
1715 const IntegerValue corner_value = MaxCornerDifference(
1718 contrib += corner_value.value() * z_max_value;
1725 const IntegerVariable target,
const std::vector<LinearExpression>& exprs,
1726 const std::vector<IntegerVariable>& z_vars,
Model*
model) {
1728 std::vector<IntegerVariable> x_vars;
1729 result.
vars = {target};
1730 const int num_exprs = exprs.size();
1731 for (
int i = 0; i < num_exprs; ++i) {
1732 result.
vars.push_back(z_vars[i]);
1733 x_vars.insert(x_vars.end(), exprs[i].vars.begin(), exprs[i].vars.end());
1737 DCHECK(std::all_of(x_vars.begin(), x_vars.end(), [](IntegerVariable
var) {
1738 return VariableIsPositive(var);
1740 result.
vars.insert(result.
vars.end(), x_vars.begin(), x_vars.end());
1744 [x_vars, z_vars, target, num_exprs, exprs, integer_trail,
model](
1748 lp_values.size(), -1);
1750 lp_values.size(), std::numeric_limits<double>::infinity());
1751 for (
int expr_index = 0; expr_index < num_exprs; ++expr_index) {
1752 for (
const IntegerVariable
var : x_vars) {
1753 const double contribution = ComputeContribution(
1754 var, z_vars, exprs, lp_values, *integer_trail, expr_index);
1755 const double prev_contribution = variable_partition_contrib[
var];
1756 if (contribution < prev_contribution) {
1757 variable_partition[
var] = expr_index;
1758 variable_partition_contrib[
var] = contribution;
1765 double violation = lp_values[target];
1766 cut.
AddTerm(target, IntegerValue(-1));
1768 for (
const IntegerVariable xi_var : x_vars) {
1769 const int input_index = variable_partition[xi_var];
1772 if (coeff != IntegerValue(0)) {
1775 violation -= coeff.value() * lp_values[xi_var];
1777 for (
int expr_index = 0; expr_index < num_exprs; ++expr_index) {
1778 const IntegerVariable z_var = z_vars[expr_index];
1779 const IntegerValue z_coeff = MPlusCoefficient(
1780 x_vars, exprs, variable_partition, expr_index, *integer_trail);
1781 if (z_coeff != IntegerValue(0)) {
1784 violation -= z_coeff.value() * lp_values[z_var];
1786 if (violation > 1e-2) {
1787 manager->
AddCut(cut.
Build(),
"LinMax", lp_values);
1794 IntegerVariable size,
1795 IntegerVariable end,
1800 result.
vars.push_back(start);
1801 result.
vars.push_back(size);
1802 result.
vars.push_back(end);
1807 [start, size, end, presence, trail, integer_trail,
model](
1811 if (trail->Assignment().LiteralIsAssigned(presence) &&
1812 trail->Assignment().LiteralIsTrue(presence)) {
1814 cut.
AddTerm(start, IntegerValue(1));
1815 cut.
AddTerm(size, IntegerValue(1));
1816 cut.
AddTerm(end, IntegerValue(-1));
1817 manager->
AddCut(cut.
Build(),
"Interval", lp_values);
1824 const std::vector<IntervalVariable>& intervals,
1825 const IntegerVariable
capacity,
const std::vector<IntegerVariable>& demands,
1829 result.
vars = demands;
1832 for (
const IntervalVariable
interval : intervals) {
1848 [intervals,
capacity, demands, integer_trail, intervals_repo,
model](
1853 std::vector<Event> events;
1856 for (
int i = 0; i < intervals.size(); ++i) {
1857 const IntervalVariable
interval = intervals[i];
1859 const IntegerVariable end_var = intervals_repo->EndVar(
interval);
1862 integer_trail->LevelZeroUpperBound(start_var);
1864 integer_trail->LevelZeroLowerBound(end_var);
1869 e1.interval_index = i;
1871 e1.demand = demands[i];
1873 e1.presence_literal = intervals_repo->IsOptional(
interval)
1874 ? intervals_repo->IsPresentLiteral(
interval)
1878 e2.positive =
false;
1879 events.push_back(e1);
1880 events.push_back(e2);
1886 std::sort(events.begin(), events.end(),
1887 [](
const Event i,
const Event j) {
1888 if (i.time == j.time) {
1889 if (i.positive == j.positive) {
1890 return i.interval_index < j.interval_index;
1894 return i.time < j.time;
1897 std::vector<Event> cut_events;
1898 bool added_positive_event =
false;
1899 for (
const Event& e : events) {
1901 added_positive_event =
true;
1902 cut_events.push_back(e);
1905 if (added_positive_event && cut_events.size() > 1) {
1907 bool cut_generated =
true;
1911 for (
const Event& cut_event : cut_events) {
1913 cut.
AddTerm(cut_event.demand, IntegerValue(1));
1916 cut_event.presence_literal,
1917 integer_trail->LevelZeroLowerBound(cut_event.demand));
1918 if (!cut_generated)
break;
1921 if (cut_generated) {
1924 manager->
AddCut(cut.
Build(),
"Cumulative", lp_values);
1929 for (
int i = 0; i < cut_events.size(); ++i) {
1930 if (cut_events[i].interval_index == e.interval_index) {
1933 cut_events[new_size] = cut_events[i];
1936 cut_events.resize(new_size);
1937 added_positive_event =
false;